diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..06769c9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,227 @@ +# SPDX-License-Identifier: GPL-2.0 +# clang-format configuration file. Intended for clang-format >= 11. +# If the version is changed also check that CI tool frrbot is updated. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +# FRR: Right +AlignEscapedNewlines: Right +AlignOperands: Align +# FRR: true +AlignTrailingComments: true +# FRR: true +AlignConsecutiveMacros: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: false +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +# Linux: CommentPragmas: '^ IWYU pragma:' +CommentPragmas: '\$(FRR|clippy)' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +# Some taken from: +# git grep -h '^#define [^[:space:]]*frr_(each|with)[^[:space:]]*(' ./ \ +# | sed "s,^#define \([^[:space:]]*frr_(each|with)[^[:space:]]*\)(.*$, - '\1'," \ +# | LC_ALL=C sort -u +# and +# git grep -h '^#define [^[:space:]]*FOREACH[^[:space:]]*(' ./ +# | sed "s,^#define \([^[:space:]]*FOREACH[^)]*\)(.*, - '\1'," +# | LC_ALL=C sort -u +ForEachMacros: + # lib: outliers: + - 'FOR_ALL_INTERFACES' + # libyang outliers: + - 'LY_FOR_KEYS' + - 'LY_LIST_FOR' + - 'LYD_LIST_FOR_INST' + - 'LYD_LIST_FOR_INST_SAFE' + - 'LY_TREE_FOR' + - 'LY_TREE_DFS_BEGIN' + - 'LYD_TREE_DFS_BEGIN' + # ospfd outliers: + - 'LSDB_LOOP' + # first git grep + - 'darr_foreach_p' + - 'darr_foreach_i' + - 'frr_each' + - 'frr_each_safe' + - 'frr_each_from' + - 'frr_rev_each' + - 'frr_rev_each_safe' + - 'frr_rev_each_from' + - 'frr_with_mutex' + - 'frr_with_privs' + # second git grep + - 'AF_FOREACH' + - 'FOREACH_ADAPTER_IN_LIST' + - 'FOREACH_AFI_SAFI' + - 'FOREACH_AFI_SAFI_NSF' + - 'FOREACH_BE_APPLY_BATCH_IN_LIST' + - 'FOREACH_BE_CLIENT_BITS' + - 'FOREACH_BE_TXN_BATCH_IN_LIST' + - 'FOREACH_BE_TXN_IN_LIST' + - 'FOREACH_CMT_REC' + - 'FOREACH_MGMTD_BE_CLIENT_ID' + - 'FOREACH_MGMTD_DS_ID' + - 'FOREACH_SAFI' + - 'FOREACH_SESSION_IN_LIST' + - 'FOREACH_TXN_CFG_BATCH_IN_LIST' + - 'FOREACH_TXN_IN_LIST' + - 'FOREACH_TXN_REQ_IN_LIST' + - 'JSON_FOREACH' + - 'LIST_FOREACH' + - 'LIST_FOREACH_SAFE' + - 'RB_FOREACH' + - 'RB_FOREACH_REVERSE' + - 'RB_FOREACH_REVERSE_SAFE' + - 'RB_FOREACH_SAFE' + - 'RE_DEST_FOREACH_ROUTE' + - 'RE_DEST_FOREACH_ROUTE_SAFE' + - 'RNODE_FOREACH_RE' + - 'RNODE_FOREACH_RE_SAFE' + - 'SIMPLEQ_FOREACH' + - 'SIMPLEQ_FOREACH_SAFE' + - 'SLIST_FOREACH' + - 'SLIST_FOREACH_PREVPTR' + - 'SLIST_FOREACH_SAFE' + - 'SPLAY_FOREACH' + - 'STAILQ_FOREACH' + - 'STAILQ_FOREACH_SAFE' + - 'SUBGRP_FOREACH_ADJ' + - 'SUBGRP_FOREACH_ADJ_SAFE' + - 'SUBGRP_FOREACH_PEER' + - 'SUBGRP_FOREACH_PEER_SAFE' + - 'TAILQ_FOREACH' + - 'TAILQ_FOREACH_REVERSE' + - 'TAILQ_FOREACH_REVERSE_SAFE' + - 'TAILQ_FOREACH_SAFE' + - 'UPDGRP_FOREACH_SUBGRP' + - 'UPDGRP_FOREACH_SUBGRP_SAFE' + - 'XSIMPLEQ_FOREACH' + - 'XSIMPLEQ_FOREACH_SAFE' +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^(<|lib)' + Priority: 0 +## New: XXX whats it mean? +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +## Linux: MaxEmptyLinesToKeep: 1 +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +## Lowest Penalty Value wins. Values are used by clang-format to influence +## the brak decisions, it's a bit of voodoo magic though. +## Originally from linux which was "Taken from git's rules" +PenaltyBreakAssignment: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +# Don't break a string into multi-string-fragments +PenaltyBreakString: 1000 +# Allow going past the ColumnLimit to keep function arguments aligned +# with the open parenthesis. +PenaltyBreakBeforeFirstCallParameter: 1000 +# Try and stay under ColumnLimit, but not at the cost of incomprehensible code. +PenaltyExcessCharacter: 30 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +WhitespaceSensitiveMacros: + - "DEFPY" + - "DEFPY_HIDDEN" + - "DEFPY_NOSH" + - "DEFPY_YANG" + - "DEFPY_YANG_HIDDEN" + - "DEFPY_YANG_NOSH" + - "DEFSH" + - "DEFSH_HIDDEN" + - "DEFUN" + - "DEFUN_HIDDEN" + - "DEFUN_NOSH" + - "DEFUN_YANG" + - "DEFUN_YANG_HIDDEN" + - "DEFUN_YANG_NOSH" + - "DEFUNSH" + - "DEFUNSH_HIDDEN" +... diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e6e1310 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +**/*.a +**/*.o +**/*.la +**/*.lo +**/*.so +**/.libs +docker/alpine/pkgs +docker/centos/pkgs diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e0ea542 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 \ No newline at end of file diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9b6932c --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,24 @@ +# Following revs are all whitespace changes; use with +# git blame --ignore-revs-file .git-blame-ignore-revs <...> +# or to make it permanent +# git config blame.ignoreRevsFile .git-blame-ignore-revs +9fa6ec14737b94fdfb41539d96c7e4f84f3514b6 +701a01920eee5431d2052aad92aefbdf50ac2139 +bf2394f08bdc91a6cbd3784a1bfa3af3247bb06f +0157c327715ca367d13b7f02b2981f3484ccdeeb +787e762445d50ca5b52fafcf8dd6de08ab90916f +ac2914d3261a78cf78eec7a6e20ebbe42bb57150 +ac4d0be5874fafd14212d6007fff7495edc9b152 +d62a17aedeb0eebdba98238874bb13d62c48dbf9 +c14777c6bfd0a446c85243d3a9835054a259c276 +996c93142d3abfab0f6d6c800474e22a8cfbdbc5 +# require semicolon after macro XYZ +67b0f40c98aeb9bbc95370fe2be29e56a00a8748 +80413c2073a20774b264ab04f7a4ea4515699790 +960b9a53837d1aefa16bd531c7087f800dbe147b +96244aca23adec551c29b78f26605f8af8eea53e +8451921b70044a2c1075e7ba391f095fabee2550 +bf8d3d6aca3f20255a621ed1c148fd05b3a8ae5c +96941f80927ce31a41f7d1905717f099187be723 +# apply `black` python formatting for all tests/topotests +1a1c2a9f84d0ad1bdadc0cb47d6175d4ccc32544 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7319550 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,78 @@ +name: Bug report +description: Report a bug in the FRRouting software +labels: triage +body: + - type: markdown + attributes: + value: > + **This form is only for reporting a bug in the FRRouting software.** + If you need help troubleshooting your configuration, have a problem + building or installing the software, or want to ask a question or + discuss the project, learn how to [connect with the FRRouting + community](https://frrouting.org/community/). + + + **Do not include sensitive information in this report.** IP addresses + should be masked (example: 192.XXX.XXX.32/24). + - type: textarea + id: description + attributes: + label: Description + description: Provide a clear and concise description of the bug. + validations: + required: true + - type: textarea + id: version + attributes: + label: Version + description: > + Run the `show version` command in the VTY shell, and provide the output + here. (If possible, test the current development version of FRRouting + for this bug.) + render: text + validations: + required: true + - type: textarea + id: how-to-reproduce + attributes: + label: How to reproduce + description: > + Give a list of steps that someone else can follow to observe + the bug. Be as descriptive as possible, including any relevant + configuration files and commands used. Topology diagrams are + helpful when the bug involves more than one router. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: > + What do you expect to happen when following the steps above? + validations: + required: true + - type: textarea + id: actual-behavior + attributes: + label: Actual behavior + description: > + What actually happens when following the steps above? Include + screenshots, log file snippets, and/or platform routing tables + as appropriate. If a crash occurs, provide a backtrace. + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Additional context + description: > + Include any other relevant information about this bug here. + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have searched the open issues for this bug. + required: true + - label: I have not included sensitive information in this report. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE/pr.md b/.github/PULL_REQUEST_TEMPLATE/pr.md new file mode 100644 index 0000000..9f44f71 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pr.md @@ -0,0 +1,8 @@ +### Summary +[fill here] + +### Related Issue +[fill here if applicable] + +### Components +[bgpd, build, doc, ripd, ospfd, eigrpd, isisd, etc. etc.] diff --git a/.github/commitlint.config.js b/.github/commitlint.config.js new file mode 100644 index 0000000..2b420b6 --- /dev/null +++ b/.github/commitlint.config.js @@ -0,0 +1,46 @@ +module.exports = { + rules: { + 'header-max-length': [2, 'always', 72], + 'type-case': [2, 'always', 'lower-case'], + 'type-empty': [2, 'never'], + 'type-enum': [ + 2, + 'always', + [ + 'babeld', + 'bfdd', + 'bgpd', + 'build', + 'doc', + 'docker', + 'eigrpd', + 'fpm', + 'isisd', + 'ldpd', + 'lib', + 'mgmtd', + 'multi', + 'nhrpd', + 'ospf6d', + 'ospfd', + 'pathd', + 'pbrd', + 'pimd', + 'pim6d', + 'ripd', + 'ripngd', + 'sharpd', + 'staticd', + 'tests', + 'tools', + 'vtysh', + 'vrrpd', + 'yang', + 'zebra', + 'all', + ], + ], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + }, +}; diff --git a/.github/workflows/base-branch-label.yml b/.github/workflows/base-branch-label.yml new file mode 100644 index 0000000..5c5d829 --- /dev/null +++ b/.github/workflows/base-branch-label.yml @@ -0,0 +1,20 @@ +name: Add base branch label + +on: + pull_request_target: + types: + - opened + - reopened + +jobs: + label: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - uses: actions-ecosystem/action-add-labels@v1 + with: + labels: | + ${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/behind-base.yml b/.github/workflows/behind-base.yml new file mode 100644 index 0000000..16b6434 --- /dev/null +++ b/.github/workflows/behind-base.yml @@ -0,0 +1,28 @@ +name: Add rebase label if the branch is > 50 commits behind + +on: + pull_request_target: + types: [synchronize, opened, reopened, labeled, unlabeled] + +jobs: + behind: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Set custom variables + id: vars + run: | + echo "behind_by=$(git log --oneline origin/${{ github.base_ref }} ^${{ github.event.pull_request.head.sha }} | wc -l)" >> $GITHUB_OUTPUT + - name: Add rebase label if needed + if: ${{ steps.vars.outputs.behind_by > 50 }} + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: rebase diff --git a/.github/workflows/build-test-docker.yml b/.github/workflows/build-test-docker.yml new file mode 100644 index 0000000..3f53f32 --- /dev/null +++ b/.github/workflows/build-test-docker.yml @@ -0,0 +1,163 @@ +name: build-test + +on: + pull_request: + push: + branches: + - 'master' + - 'stable/**' + +defaults: + run: + shell: bash + +jobs: + build-docker: + name: Build the ubuntu 22.04 docker image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Build docker image + run: | + docker build -t frr-ubuntu22 -f docker/ubuntu-ci/Dockerfile . + docker save --output /tmp/frr-ubuntu22.tar frr-ubuntu22 + - name: Upload docker image artifact + uses: actions/upload-artifact@v4 + with: + name: ubuntu-image + path: /tmp/frr-ubuntu22.tar + - name: Clear any previous results + # So if all jobs are re-run then all tests will be re-run + run: | + rm -rf test-results* + mkdir -p test-results + touch test-results/cleared-results.txt + - name: Save cleared previous results + uses: actions/upload-artifact@v4 + with: + name: test-results + path: test-results + overwrite: true + - name: Cleanup + if: ${{ always() }} + run: rm -rf test-results* /tmp/frr-ubuntu22.tar + + test-docker: + name: Test ubuntu docker image + needs: build-docker + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Fetch docker image artifact + uses: actions/download-artifact@v4 + with: + name: ubuntu-image + path: /tmp + - name: Fetch previous results + if: ${{ github.run_attempt > 1 }} + uses: actions/download-artifact@v4 + with: + name: test-results + path: test-results + - name: Run topotests + run: | + uname -a + MODPKGVER=$(uname -r) + sudo apt-get update -y + # Github is running old kernels but installing newer packages :( + sudo apt-get install -y linux-modules-extra-azure linux-modules-${MODPKGVER} linux-modules-extra-${MODPKGVER} python3-xmltodict + sudo modprobe vrf || true + sudo modprobe mpls-iptunnel + sudo modprobe mpls-router + docker load --input /tmp/frr-ubuntu22.tar + + if ! grep CONFIG_IP_MROUTE_MULTIPLE_TABLES=y /boot/config*; then + ADD_DOCKER_ENV+="-e MROUTE_VRF_MISSING=1" + fi + echo "ADD_DOCKER_ENV: ${ADD_DOCKER_ENV}" + + if [ -f test-results/topotests.xml ]; then + ./tests/topotests/analyze.py -r test-results + ls -l test-results/topotests.xml + run_tests=$(./tests/topotests/analyze.py -r test-results | cut -f1 -d: | sort -u) + else + echo "No test results dir" + run_tests="" + fi + rm -rf test-results* /tmp/topotests + + echo RUN_TESTS: $run_tests + if docker run --init -i --privileged --name frr-ubuntu-cont ${ADD_DOCKER_ENV} -v /lib/modules:/lib/modules frr-ubuntu22 \ + bash -c 'cd ~/frr/tests/topotests ; sudo -E pytest -n$(($(nproc) * 5 / 2)) --dist=loadfile '$run_tests; then + echo "All tests passed." + exit 0 + fi + + # Grab the results from the container + if ! ./tests/topotests/analyze.py -Ar test-results -C frr-ubuntu-cont; then + if [ ! -d test-results ]; then + echo "ERROR: Basic failure in docker run, no test results directory available." >&2 + exit 1; + fi + if [ ! -f test-results/topotests.xml ]; then + # In this case we may be missing topotests.xml + echo "ERROR: No topotests.xml available perhaps docker run aborted?" >&2 + exit 1; + fi + echo "WARNING: analyyze.py returned error but grabbed results anyway." >&2 + fi + + # Save some information useful for debugging + cp /boot/config* test-results/ + sysctl -a > test-results/sysctl.out 2> /dev/null + + # Now get the failed tests (if any) from the archived results directory. + rerun_tests=$(./tests/topotests/analyze.py -r test-results | cut -f1 -d: | sort -u) + if [ -z "$rerun_tests" ]; then + echo "All tests passed during parallel run." + exit 0 + fi + + echo "ERROR: Some tests failed during parallel run, rerunning serially." >&2 + echo RERUN_TESTS: $rerun_tests >&2 + docker stop frr-ubuntu-cont + docker rm frr-ubuntu-cont + + mv test-results test-results-initial + if docker run --init -i --privileged --name frr-ubuntu-cont ${ADD_DOCKER_ENV} -v /lib/modules:/lib/modules frr-ubuntu22 \ + bash -c 'cd ~/frr/tests/topotests ; sudo -E pytest '$rerun_tests; then + echo "All rerun tests passed." + exit 0 + fi + echo "Some rerun tests still failed." + exit 1 + - name: Gather results + if: ${{ always() }} + run: | + if [ ! -d test-results ]; then + if ! ./tests/topotests/analyze.py -Ar test-results -C frr-ubuntu-cont; then + echo "ERROR: gathering results produced an error, perhaps due earlier run cancellation." >&2 + fi + fi + - name: Upload test results + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + test-results + test-results-initial + overwrite: true + - name: Cleanup + if: ${{ always() }} + run: | + rm -rf test-results* /tmp/frr-ubuntu22.tar + docker stop frr-ubuntu-cont || true + docker rm frr-ubuntu-cont || true + diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml new file mode 100644 index 0000000..96cd118 --- /dev/null +++ b/.github/workflows/commitlint.yml @@ -0,0 +1,30 @@ +name: commitlint + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - labeled + - unlabeled + +jobs: + commitlint: + if: ${{ github.repository == 'frrouting/frr' }} && ${{ github.base_ref == 'refs/heads/master' }} + name: Check if the commits meet the requirements of the guidelines + permissions: + contents: read + pull-requests: read + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Check Commit + uses: wagoid/commitlint-github-action@v5 + with: + configFile: .github/commitlint.config.js + helpURL: 'https://docs.frrouting.org/projects/dev-guide/en/latest/workflow.html#submitting-patches-and-enhancements' diff --git a/.github/workflows/conflicts.yml b/.github/workflows/conflicts.yml new file mode 100644 index 0000000..4b4e99f --- /dev/null +++ b/.github/workflows/conflicts.yml @@ -0,0 +1,21 @@ +name: Add a conflict label if PR needs to rebase + +on: + pull_request_target: + types: [opened, reopened, synchronize] + +jobs: + conflicts: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Check if PRs need a rebase (have some conflicts) + uses: eps1lon/actions-label-merge-conflict@releases/2.x + with: + dirtyLabel: "conflicts" + removeOnDirtyLabel: "no_conflicts" + repoToken: "${{ secrets.GITHUB_TOKEN }}" + commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." diff --git a/.github/workflows/docker-daily-master.yml b/.github/workflows/docker-daily-master.yml new file mode 100644 index 0000000..59787b4 --- /dev/null +++ b/.github/workflows/docker-daily-master.yml @@ -0,0 +1,53 @@ +name: Build daily 'master' images for Docker + +on: + schedule: + - cron: '59 23 * * *' + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +jobs: + docker_daily_master: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Custom variables + id: vars + run: | + # To package a specific git commit, the date of the commit gets + # appended to the latest release, e.g. 1.0.0_git20180204. + # This is the requirement by APKBUILD (abuild). + # More details: https://wiki.alpinelinux.org/wiki/APKBUILD_Reference. + echo ::set-output name=date::$(date +'%Y%m%d') + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: master + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + registry: quay.io + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_ROBOT_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/alpine/Dockerfile + push: true + tags: quay.io/frrouting/frr:master + build-args: PKGVER=${{ steps.vars.outputs.date }} + platforms: linux/amd64,linux/arm64,linux/arm/v7 diff --git a/.github/workflows/freeze.yml b/.github/workflows/freeze.yml new file mode 100644 index 0000000..a780298 --- /dev/null +++ b/.github/workflows/freeze.yml @@ -0,0 +1,17 @@ +name: Warn before merging if a "freeze" or "do not merge" label exists + +on: + pull_request_target: + types: [synchronize, opened, reopened, labeled, unlabeled] + +jobs: + freeze_warning: + if: ${{ contains(github.event.*.labels.*.name, 'freeze') || contains(github.event.*.labels.*.name, 'do not merge') }} + name: Warn before merging if a "freeze" or "do not merge" label exists + runs-on: ubuntu-latest + steps: + - name: Check for "freeze" label + run: | + echo "Pull request is labeled as 'freeze' or 'do not merge'" + echo "This workflow fails so that the pull request cannot be merged." + exit 1 diff --git a/.github/workflows/mergifyio_backport.yml b/.github/workflows/mergifyio_backport.yml new file mode 100644 index 0000000..455dcbe --- /dev/null +++ b/.github/workflows/mergifyio_backport.yml @@ -0,0 +1,22 @@ +name: Mergifyio backport + +on: [issue_comment] + +jobs: + mergifyio_backport: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions-ecosystem/action-regex-match@v2 + id: regex-match + with: + text: ${{ github.event.comment.body }} + regex: '[Mm]ergifyio backport ' + + - uses: actions-ecosystem/action-add-labels@v1 + if: ${{ steps.regex-match.outputs.match != '' }} + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: backport diff --git a/.github/workflows/size-label.yml b/.github/workflows/size-label.yml new file mode 100644 index 0000000..1ce0786 --- /dev/null +++ b/.github/workflows/size-label.yml @@ -0,0 +1,26 @@ +name: Add PRs size label + +on: pull_request_target + +jobs: + size-label: + if: github.repository == 'frrouting/frr' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: size-label + uses: "pascalgn/size-label-action@v0.4.2" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + with: + sizes: > + { + "0": "XS", + "20": "S", + "50": "M", + "200": "L", + "800": "XL", + "2000": "XXL" + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..cd1365b --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +name: Mark stale issues + +on: + workflow_dispatch: + schedule: + - cron: "30 1 * * *" + +permissions: + contents: read + +jobs: + stale: + if: github.repository == 'frrouting/frr' + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Comment or remove the `autoclose` label in order to avoid having this issue closed.' + stale-issue-label: autoclose + stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Comment or remove the `autoclose` label in order to avoid having this PR closed.' + stale-pr-label: autoclose + days-before-stale: 180 + days-before-close: 194 + days-before-pr-stale: 180 + days-before-pr-close: 194 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60b4189 --- /dev/null +++ b/.gitignore @@ -0,0 +1,121 @@ +### autoconf/automake root stuff + +/compile +/config.log +/config.h +/config.cache +/config.status +/config.guess +/config.sub +/config.version +/ltmain.sh +/stamp-h +/stamp-h[0-9]* +*-stamp +/INSTALL +/depcomp +/missing +/install-sh +/mkinstalldirs +/ylwrap +/autom4te*.cache +/configure.lineno +/configure +/config.h.in +/confdefs.h +/conftest +/conftest.err +/aclocal.m4 +/libtool +/libtool.orig +/test-driver +/test-suite.log + +/Makefile +/Makefile.in + +/symalyzer_report.html +/jquery-3.4.1.min.js +/jquery-3.4.1.min.js.tmp + +### autoconf/automake subdir stuff + +.deps +.libs + +### build outputs + +*.o +*.lo +*.a +*.la +*.so +*.loT +*.pb.h +*.pb-c.h +*.pb-c.c +*.pb.cc +*_clippy.c +*.bc +*.ll +*.cg.json +*.cg.dot +*.cg.svg +*.xref +*_tsexpand.h + +### gcov outputs + +*.gcno +*.gcov +*.gcda + +### dist + +*.tar.?z +*.tar.?z.asc +*.tar.asc +*.deb +*.ddeb +*.dsc +*.changes + +### other garbage + +.nfs* +.arch-inventory +.arch-ids +{arch} +build +.cache +.dir-locals.el +.msg +.rebase-* +*~ +*.bak +*.swp +*.pyc +*.dmp +__pycache__ +*.patch +*.diff +cscope.* +TAGS +tags +GTAGS +GSYMS +GRTAGS +GPATH +compile_commands.json +.ccls +.ccls-cache +.dirstamp +refix +.vscode +.kitchen +.emacs.desktop* + +/test-suite.log +pceplib/test/*.log +pceplib/test/*.trs +/tests/topotests/lib/mgmt_pb2.py diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..f238bf7 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile = black diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..ba9430b --- /dev/null +++ b/.pylintrc @@ -0,0 +1,9 @@ +[MASTER] +init-hook="import sys; sys.path.insert(0, '..')" +signature-mutators=common_config.retry,retry + +[FORMAT] +max-line-length = 88 + +[MESSAGES CONTROL] +disable=I,C,R,W diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..32b686c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +dist: focal +os: linux +language: c +services: + - docker +jobs: + include: + - script: + - docker/centos-7/build.sh + - docker images + name: centos7 + - script: + - docker/centos-8/build.sh + - docker images + name: centos8 + - script: + - sudo apt install -y linux-modules-extra-$(uname -r) + - docker build -t frr-ubuntu18:latest -f docker/ubuntu18-ci/Dockerfile . + - docker images + - uname -a + - docker run -d --privileged --name frr-ubuntu18 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu18:latest + - docker ps + - docker exec frr-ubuntu18 bash -c 'cd ~/frr ; make check' + - docker exec frr-ubuntu18 bash -c 'ps agxu ; lsmod | grep mpls || true' + - docker exec frr-ubuntu18 bash -c 'cd ~/frr/tests/topotests/ospf_topo1 ; sudo pytest test_ospf_topo1.py' + - docker exec frr-ubuntu18 bash -c 'cd ~/frr/tests/topotests/bgp_l3vpn_to_bgp_vrf ; sudo pytest test_bgp_l3vpn_to_bgp_vrf.py' + name: ubuntu18+minimalCI + - script: + - sudo apt install -y linux-modules-extra-$(uname -r) + - docker build -t frr-ubuntu20:latest -f docker/ubuntu20-ci/Dockerfile . + - docker images + - uname -a + - docker run -d --privileged --name frr-ubuntu20 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu20:latest + - docker ps + - docker exec frr-ubuntu20 bash -c 'cd ~/frr ; make check' + - docker exec frr-ubuntu20 bash -c 'ps agxu ; lsmod | grep mpls || true' + - docker exec frr-ubuntu20 bash -c 'cd ~/frr/tests/topotests/ospf_topo1 ; sudo pytest test_ospf_topo1.py' + - docker exec frr-ubuntu20 bash -c 'cd ~/frr/tests/topotests/bgp_l3vpn_to_bgp_vrf ; sudo pytest test_bgp_l3vpn_to_bgp_vrf.py' + name: ubuntu20+minimalCI + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..84ba352 --- /dev/null +++ b/COPYING @@ -0,0 +1,16 @@ +The FRRouting project consists of parts with various licenses. Any particular +file's license should be indicated at the top of the file with an SPDX License +identifier. + +The full text of all licenses used can be found in doc/licenses. + +The composite work (binary) resulting from compiling FRR is thought to always +be distributable under GPLv2 or later. However, please note that this is +simply an expression of the community's best-effort understanding, it is not a +legal statement, guarantee, or advice of any kind. If necessary, please +familiarize yourself with the specifics and/or consult a lawyer. + +Also please be advised that FRR's documentation is, for historical reasons, +licensed under a custom (but relatively permissive) license. This license +dates back to the GNU Zebra project and cannot easily be changed into something +more common, short of rewriting the entire documentation. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..0ce716e --- /dev/null +++ b/Makefile.am @@ -0,0 +1,363 @@ +## Process this file with automake to produce Makefile.in. + +AUTOMAKE_OPTIONS = subdir-objects 1.12 +ACLOCAL_AMFLAGS = -I m4 -Wall,no-override + +AM_CFLAGS = \ + $(AC_CFLAGS) \ + $(LIBYANG_CFLAGS) \ + $(SQLITE3_CFLAGS) \ + $(UNWIND_CFLAGS) \ + $(SAN_FLAGS) \ + $(WERROR) \ + # end +AM_CXXFLAGS = \ + $(AC_CXXFLAGS) \ + $(LIBYANG_CFLAGS) \ + $(WERROR) \ + # end + +# CPPFLAGS_BASE does not contain the include path for overriding assert.h, +# therefore should be used in tools that do *not* link libfrr or do not want +# assert() overridden +CPPFLAGS_BASE = \ + -I$(top_srcdir) -I$(top_srcdir)/include -I$(top_srcdir)/lib \ + -I$(top_builddir) \ + $(LUA_INCLUDE) \ + # end +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/assert \ + $(CPPFLAGS_BASE) \ + # end + +# AM_LDFLAGS is used for executables (daemons). LDFLAGS can be left alone, +# but if it is changed it should include $(AM_LDFLAGS) +AM_LDFLAGS = \ + -export-dynamic \ + $(AC_LDFLAGS) \ + $(AC_LDFLAGS_EXEC) \ + $(SAN_FLAGS) \ + # end + +# libraries need to use libxxx_LDFLAGS = $(LIB_LDFLAGS) -version-info X:Y:Z +LIB_LDFLAGS = \ + -export-dynamic \ + $(AC_LDFLAGS) \ + $(SAN_FLAGS) \ + # end + +# modules need to use xxx_LDFLAGS = $(MODULE_LDFLAGS) +MODULE_LDFLAGS = \ + -export-dynamic \ + -avoid-version \ + -module \ + -shared \ + $(AC_LDFLAGS) \ + $(SAN_FLAGS) \ + # end + +DEFS = @DEFS@ -DCONFDATE=$(CONFDATE) + +AR_FLAGS = @AR_FLAGS@ +ARFLAGS = @ARFLAGS@ +RANLIB = @RANLIB@ + +# these two targets are provided to easily grab autoconf/Makefile variables +# you can use either: +# eval `make VARFD=3 shvar-CFLAGS 3>&1 1>&2` +# CFLAGS="`make VARFD=3 var-CFLAGS 3>&1 1>&2`" +# where the former can be used to set several variables at once. Note the +# fd redirections -- this is to prevent garbage from make rebuilding other +# targets from causing issues. +.PHONY: shvar-% var-% +VARFD ?= 1 +shvar-%: + @echo "$*=\"$($*)\"" >&$(VARFD) +var-%: + @echo "$($*)" >&$(VARFD) + +if ONLY_CLIPPY +.DEFAULT_GOAL := clippy-only +endif +clippy-only: Makefile lib/clippy config.h +.PHONY: clippy-only + +# overwriting these vars breaks cross-compilation. let's be helpful and warn. +# +# note: "#AUTODERP# " will be removed from Makefile by configure. These are +# GNU make directives & automake will f*ck them up by trying to process them +# as automake directives. +# +#AUTODERP# null= +#AUTODERP# SPACE=$(null) $(null) +#AUTODERP# mkcheck_CC = $(findstring $(SPACE)CC=, $(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# mkcheck_CFLAGS = $(findstring $(SPACE)CFLAGS=, $(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# mkcheck_CPPFLAGS = $(findstring $(SPACE)CPPFLAGS=,$(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# mkcheck_CCLD = $(findstring $(SPACE)CCLD=, $(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# mkcheck_LD = $(findstring $(SPACE)LD=, $(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# mkcheck_LDFLAGS = $(findstring $(SPACE)LDFLAGS=, $(SPACE)$(MAKEOVERRIDES)) +#AUTODERP# # +#AUTODERP# ifneq ($(mkcheck_CC),) +#AUTODERP# $(warning WARNING: you have overwritten the "CC" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# ifneq ($(mkcheck_CFLAGS),) +#AUTODERP# $(warning WARNING: you have overwritten the "CFLAGS" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# ifneq ($(mkcheck_CPPFLAGS),) +#AUTODERP# $(warning WARNING: you have overwritten the "CPPFLAGS" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# ifneq ($(mkcheck_CCLD),) +#AUTODERP# $(warning WARNING: you have overwritten the "CCLD" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# ifneq ($(mkcheck_LD),) +#AUTODERP# $(warning WARNING: you have overwritten the "LD" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# ifneq ($(mkcheck_LDFLAGS),) +#AUTODERP# $(warning WARNING: you have overwritten the "LDFLAGS" variable on the make command line.) +#AUTODERP# endif +#AUTODERP# # +#AUTODERP# ifneq ($(mkcheck_CC)$(mkcheck_CFLAGS)$(mkcheck_CPPFLAGS)$(mkcheck_CCLD)$(mkcheck_LD)$(mkcheck_LDFLAGS),) +#AUTODERP# $(warning ------) +#AUTODERP# $(warning While overwriting these variables works most of the time, it is not recommended and can cause confusing build errors.) +#AUTODERP# $(warning This is especially problematic when cross-compiling, since tools that run on the build system during the build process will not be compiled correctly.) +#AUTODERP# $(warning All of these variables should be supplied to 'configure', and they will be remembered and correctly applied during 'make'.) +#AUTODERP# $(warning ------) +#AUTODERP# endif + +EXTRA_DIST = +EXTRA_PROGRAMS = +BUILT_SOURCES = +CLEANFILES = +DISTCLEANFILES = +SUFFIXES = + +bin_PROGRAMS = +sbin_PROGRAMS = +sbin_SCRIPTS = +noinst_PROGRAMS = +noinst_HEADERS = +noinst_LIBRARIES = +nodist_noinst_DATA = +lib_LTLIBRARIES = +module_LTLIBRARIES = +pkginclude_HEADERS = +nodist_pkginclude_HEADERS = +dist_yangmodels_DATA = +man_MANS = +vtysh_daemons = +clippy_scan = + +## libtool, the self-made GNU scourge +## ... this should fix relinking +## ... and AUTOMAKE_DUMMY is needed to prevent automake from treating this +## as overriding the normal targets... +$(AUTOMAKE_DUMMY)install-moduleLTLIBRARIES: install-libLTLIBRARIES +$(AUTOMAKE_DUMMY)install-binPROGRAMS: install-libLTLIBRARIES +$(AUTOMAKE_DUMMY)install-sbinPROGRAMS: install-libLTLIBRARIES + +# Include default rules to compile protobuf message sources +SUFFIXES += .proto .pb-c.c .pb-c.h + +# Rules + +AM_V_PROTOC_C = $(am__v_PROTOC_C_$(V)) +am__v_PROTOC_C_ = $(am__v_PROTOC_C_$(AM_DEFAULT_VERBOSITY)) +am__v_PROTOC_C_0 = @echo " PROTOC_C" $@; +am__v_PROTOC_C_1 = + +%.pb-c.c %.pb-c.h : %.proto + $(AM_V_PROTOC_C)$(PROTOC_C) -I$(top_srcdir) --c_out=$(top_builddir) $^ + $(AM_V_GEN)$(SED) -i -e '1i\ + #include "config.h"' $@ + +include doc/subdir.am +include doc/user/subdir.am +include doc/manpages/subdir.am +include doc/developer/subdir.am +include include/subdir.am +include lib/subdir.am +include mlag/subdir.am +include zebra/subdir.am +include watchfrr/subdir.am +include qpb/subdir.am +include fpm/subdir.am +include grpc/subdir.am +include tools/subdir.am + +include mgmtd/subdir.am + +include bgpd/subdir.am +include bgpd/rfp-example/librfp/subdir.am +include bgpd/rfp-example/rfptest/subdir.am +include ripd/subdir.am +include ripngd/subdir.am +include ospfd/subdir.am +include ospf6d/subdir.am +include ospfclient/subdir.am +include isisd/subdir.am +include nhrpd/subdir.am +include ldpd/subdir.am +include babeld/subdir.am +include eigrpd/subdir.am +include sharpd/subdir.am +include pimd/subdir.am +include pbrd/subdir.am +include staticd/subdir.am +include bfdd/subdir.am +include yang/subdir.am +include yang/libyang_plugins/subdir.am +include vrrpd/subdir.am +include pceplib/subdir.am +include pceplib/test/subdir.am +include pathd/subdir.am + +include vtysh/subdir.am +include tests/subdir.am +include tests/topotests/subdir.am + +if PKGSRC +rcdir=@pkgsrcrcdir@ +rc_SCRIPTS = \ + pkgsrc/bgpd.sh \ + pkgsrc/ospf6d.sh \ + pkgsrc/ospfd.sh \ + pkgsrc/ripd.sh \ + pkgsrc/ripngd.sh \ + pkgsrc/zebra.sh \ + pkgsrc/mgmtd.sh \ + # end +endif + +EXTRA_DIST += \ + aclocal.m4 \ + README.md \ + m4/README.txt \ + m4/libtool-whole-archive.patch \ + config.version \ + \ + python/clidef.py \ + python/clippy/__init__.py \ + python/clippy/elf.py \ + python/clippy/uidhash.py \ + python/makevars.py \ + python/makefile.py \ + python/tiabwarfo.py \ + python/xrelfo.py \ + python/xref2vtysh.py \ + python/test_xrelfo.py \ + python/runtests.py \ + \ + python/xrefstructs.json \ + \ + tools/etc/logrotate.d/frr \ + redhat/frr.pam \ + redhat/frr.spec \ + \ + snapcraft/snapcraft.yaml \ + snapcraft/README.snap_build.md \ + snapcraft/README.usage.md \ + snapcraft/extra_version_info.txt \ + snapcraft/scripts \ + snapcraft/defaults \ + snapcraft/helpers \ + snapcraft/snap \ + babeld/Makefile \ + mgmtd/Makefile \ + bgpd/Makefile \ + bgpd/rfp-example/librfp/Makefile \ + bgpd/rfp-example/rfptest/Makefile \ + doc/Makefile \ + doc/developer/Makefile \ + doc/manpages/Makefile \ + doc/user/Makefile \ + eigrpd/Makefile \ + fpm/Makefile \ + grpc/Makefile \ + isisd/Makefile \ + ldpd/Makefile \ + lib/Makefile \ + nhrpd/Makefile \ + ospf6d/Makefile \ + ospfclient/Makefile \ + ospfd/Makefile \ + pbrd/Makefile \ + pimd/Makefile \ + qpb/Makefile \ + ripd/Makefile \ + ripngd/Makefile \ + staticd/Makefile \ + tests/Makefile \ + tools/Makefile \ + vtysh/Makefile \ + watchfrr/Makefile \ + zebra/Makefile \ + vrrpd/Makefile \ + # end + +AM_V_LLVM_BC = $(am__v_LLVM_BC_$(V)) +am__v_LLVM_BC_ = $(am__v_LLVM_BC_$(AM_DEFAULT_VERBOSITY)) +am__v_LLVM_BC_0 = @echo " LLVM.BC " $@; +am__v_LLVM_BC_1 = + +AM_V_LLVM_LD = $(am__v_LLVM_LD_$(V)) +am__v_LLVM_LD_ = $(am__v_LLVM_LD_$(AM_DEFAULT_VERBOSITY)) +am__v_LLVM_LD_0 = @echo " LLVM.LD " $@; +am__v_LLVM_LD_1 = + +SUFFIXES += .lo.bc .o.bc + +.o.o.bc: + $(AM_V_LLVM_BC)$(COMPILE) -emit-llvm -c -o $@ $(patsubst %.o,%.c,$<) +.lo.lo.bc: + $(AM_V_LLVM_BC)$(COMPILE) -emit-llvm -c -o $@ $(patsubst %.lo,%.c,$<) + +%.cg.json: %.bc tools/frr-llvm-cg + tools/frr-llvm-cg -o $@ $< +%.cg.dot: %.cg.json + $(PYTHON) $(top_srcdir)/python/callgraph-dot.py $< $@ +%.cg.svg: %.cg.dot + @echo if the following command fails, you need to install graphviz. + @echo also, the output is nondeterministic. run it multiple times and use the nicest output. + @echo tuning parameters may yield nicer looking graphs as well. + fdp -GK=0.7 -Gstart=42231337 -Gmaxiter=2000 -Elen=2 -Gnodesep=1.5 -Tsvg -o$@ $< +# don't delete intermediaries +.PRECIOUS: %.cg.json %.cg.dot + +# .la.bc, .a.bc and .bc targets are generated by +# python/makefile.py +LLVM_LINK = llvm-link-$(llvm_version) + +clean-local: clean-python clean-llvm-bitcode +.PHONY: clean-python clean-llvm-bitcode +clean-python: + find . -name __pycache__ -o -name .pytest_cache | xargs rm -rf + find . -name "*.pyc" -o -name "*_clippy.c" | xargs rm -f + +clean-llvm-bitcode: + find . -name "*.bc" -o -name "*.cg.json" -o -name "*.cg.dot" -o -name "*.cg.svg" | xargs rm -f + +redistclean: + $(MAKE) distclean CONFIG_CLEAN_FILES="$(filter-out $(EXTRA_DIST), $(CONFIG_CLEAN_FILES))" + +indent: + tools/indent.py `find sharpd bgpd mgmtd eigrpd include isisd lib nhrpd ospf6d ospfd pimd qpb ripd vtysh zebra -name '*.[ch]' | grep -v include/linux` + +if HAVE_GCOV + +coverage: check + @ find . -name '*.o' -exec gcov {} \; + +yorn: + @ echo "OK to upload coverage to https://coverage.io [y/N]:" + @ read yn; test "$$yn" = "y" + +upload-check-coverage: + @ if [ "x${COMMIT}" = "x" ]; then echo "COMMIT required"; exit 1; fi + @ if [ "x${TOKEN}" = "x" ]; then echo "TOKEN required"; exit 1; fi + curl -s https://codecov.io/bash | bash -s - -C ${COMMIT} -t ${TOKEN} + +force-check-coverage: coverage upload-check-coverage + +check-coverage: coverage yorn upload-check-coverage + +endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..600a91e --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +

+Icon +

+ +FRRouting +========= + +FRR is free software that implements and manages various IPv4 and IPv6 routing +protocols. It runs on nearly all distributions of Linux and BSD and +supports all modern CPU architectures. + +FRR currently supports the following protocols: + +* BGP +* OSPFv2 +* OSPFv3 +* RIPv1 +* RIPv2 +* RIPng +* IS-IS +* PIM-SM/MSDP +* LDP +* BFD +* Babel +* PBR +* OpenFabric +* VRRP +* EIGRP (alpha) +* NHRP (alpha) + +Installation & Use +------------------ + +For source tarballs, see the +[releases page](https://github.com/FRRouting/frr/releases). + +For Debian and its derivatives, use the APT repository at +[https://deb.frrouting.org/](https://deb.frrouting.org/). + +Instructions on building and installing from source for supported platforms may +be found in the +[developer docs](http://docs.frrouting.org/projects/dev-guide/en/latest/building.html). + +Once installed, please refer to the [user guide](http://docs.frrouting.org/) +for instructions on use. + +Community +--------- + +The FRRouting email list server is located +[here](https://lists.frrouting.org/listinfo) and offers the following public +lists: + +| Topic | List | +|-------------------|------------------------------| +| Development | dev@lists.frrouting.org | +| Users & Operators | frog@lists.frrouting.org | +| Announcements | announce@lists.frrouting.org | + +For chat, we currently use [Slack](https://frrouting.slack.com). You can join +by clicking the "Slack" link under the +[Participate](https://frrouting.org/community) section of our website. + + +Contributing +------------ + +FRR maintains [developer's documentation](http://docs.frrouting.org/projects/dev-guide/en/latest/index.html) +which contains the [project workflow](http://docs.frrouting.org/projects/dev-guide/en/latest/workflow.html) +and expectations for contributors. Some technical documentation on project +internals is also available. + +We welcome and appreciate all contributions, no matter how small! + + +Security +-------- + +To report security issues, please use our security mailing list: + +``` +security [at] lists.frrouting.org +``` diff --git a/alpine/.gitignore b/alpine/.gitignore new file mode 100644 index 0000000..1b6593c --- /dev/null +++ b/alpine/.gitignore @@ -0,0 +1 @@ +/APKBUILD diff --git a/alpine/APKBUILD.in b/alpine/APKBUILD.in new file mode 100644 index 0000000..2cb3fee --- /dev/null +++ b/alpine/APKBUILD.in @@ -0,0 +1,70 @@ +# Maintainer: Arthur Jones +pkgname=frr +arch="all" +pkgver=@VERSION@ +pkgrel=0 +pkgdesc="FRRouting is a fork of quagga" +url="https://frrouting.org/" +license="GPL-2.0" +depends="json-c c-ares iproute2 python3 bash" +makedepends="ncurses-dev net-snmp-dev gawk texinfo perl + acct autoconf automake bash binutils bison bsd-compat-headers build-base + c-ares c-ares-dev ca-certificates cryptsetup-libs curl device-mapper-libs + expat fakeroot flex fortify-headers gdbm git gmp json-c-dev kmod + lddtree libacl libatomic libattr libblkid libburn libbz2 libc-dev + libcap-dev libcurl libedit libffi libgcc libgomp libisoburn libisofs + libltdl openssl libssh2 libstdc++ libtool libuuid + linux-headers lzip lzo m4 make mkinitfs mpc1 mpfr4 mtools musl-dev + ncurses-libs ncurses-terminfo ncurses-terminfo-base patch pax-utils pcre2 + perl pkgconf python3 python3-dev readline readline-dev sqlite-libs pcre2-dev + squashfs-tools sudo tar texinfo xorriso xz-libs py-pip rtrlib rtrlib-dev + py3-sphinx elfutils elfutils-dev protobuf-c-compiler protobuf-c-dev + lua5.3-dev lua5.3 gzip" +checkdepends="pytest py-setuptools" +install="$pkgname.pre-install $pkgname.pre-deinstall $pkgname.post-deinstall" +subpackages="$pkgname-dev $pkgname-doc $pkgname-dbg" +source="$pkgname-$pkgver.tar.gz" + +builddir="$srcdir"/$pkgname-$pkgver + +_sysconfdir=/etc +_sbindir=/usr/lib/frr +_libdir=/usr/lib +_user=frr + +build() { + cd "$builddir" + + ./configure \ + --prefix=/usr \ + --sysconfdir=$_sysconfdir \ + --localstatedir=/var \ + --sbindir=$_sbindir \ + --libdir=$_libdir \ + --enable-rpki \ + --enable-vtysh \ + --enable-multipath=64 \ + --enable-vty-group=frrvty \ + --enable-user=$_user \ + --enable-group=$_user \ + --enable-pcre2posix \ + --enable-scripting + make -j $(nproc) +} + +check() { + cd "$builddir" + + make -j 1 check +} + +package() { + cd "$builddir" + make DESTDIR="$pkgdir" install + + install -d $pkgdir/$_sysconfdir/frr + install -m 0644 tools/etc/frr/daemons $pkgdir/$_sysconfdir/frr/daemons + + install -d $pkgdir/$_sysconfdir/init.d + ln -s ${_sbindir}/frr $pkgdir/$_sysconfdir/init.d/frr +} diff --git a/alpine/frr.post-deinstall b/alpine/frr.post-deinstall new file mode 100755 index 0000000..8f5d3dc --- /dev/null +++ b/alpine/frr.post-deinstall @@ -0,0 +1,6 @@ +#!/bin/sh + +getent passwd frr > /dev/null && deluser frr +getent group frrvty > /dev/null && delgroup frrvty +getent group frr > /dev/null && delgroup frr +exit 0 diff --git a/alpine/frr.pre-deinstall b/alpine/frr.pre-deinstall new file mode 100755 index 0000000..72cf73b --- /dev/null +++ b/alpine/frr.pre-deinstall @@ -0,0 +1,4 @@ +#!/bin/sh + +/etc/init.d/frr stop +exit 0 diff --git a/alpine/frr.pre-install b/alpine/frr.pre-install new file mode 100755 index 0000000..da608cd --- /dev/null +++ b/alpine/frr.pre-install @@ -0,0 +1,10 @@ +#!/bin/sh + +for g in frr frrvty; do + ! getent group $g > /dev/null && addgroup -S $g +done + +! getent passwd frr > /dev/null && \ + adduser -S -D -h /var/run/frr -s /sbin/nologin -G frr -g frr frr + +adduser frr frrvty diff --git a/babeld/.gitignore b/babeld/.gitignore new file mode 100644 index 0000000..abb4d93 --- /dev/null +++ b/babeld/.gitignore @@ -0,0 +1,8 @@ +* +!*.c +!*.h +!LICENCE +!Makefile +!subdir.am +!.gitignore +*_clippy.c diff --git a/babeld/Makefile b/babeld/Makefile new file mode 100644 index 0000000..ae125e6 --- /dev/null +++ b/babeld/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. babeld/babeld +%: ALWAYS + @$(MAKE) -s -C .. babeld/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/babeld/babel_errors.c b/babeld/babel_errors.c new file mode 100644 index 0000000..b093bdb --- /dev/null +++ b/babeld/babel_errors.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Babel-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "babel_errors.h" + +/* clang-format off */ +static struct log_ref ferr_babel_err[] = { + { + .code = EC_BABEL_MEMORY, + .title = "BABEL Memory Errors", + .description = "Babel has failed to allocate memory, the system is about to run out of memory", + .suggestion = "Find the process that is causing memory shortages, remediate that process and restart FRR" + }, + { + .code = EC_BABEL_PACKET, + .title = "BABEL Packet Error", + .description = "Babel has detected a packet encode/decode problem", + .suggestion = "Collect relevant log files and file an Issue" + }, + { + .code = EC_BABEL_CONFIG, + .title = "BABEL Configuration Error", + .description = "Babel has detected a configuration error of some sort", + .suggestion = "Ensure that the configuration is correct" + }, + { + .code = EC_BABEL_ROUTE, + .title = "BABEL Route Error", + .description = "Babel has detected a routing error and has an inconsistent state", + .suggestion = "Gather data for filing an Issue and then restart FRR" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void babel_error_init(void) +{ + log_ref_add(ferr_babel_err); +} diff --git a/babeld/babel_errors.h b/babeld/babel_errors.h new file mode 100644 index 0000000..47539c7 --- /dev/null +++ b/babeld/babel_errors.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Babel-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __BABEL_ERRORS_H__ +#define __BABEL_ERRORS_H__ + +#include "lib/ferr.h" + +enum babel_log_refs { + EC_BABEL_MEMORY = BABEL_FERR_START, + EC_BABEL_PACKET, + EC_BABEL_CONFIG, + EC_BABEL_ROUTE, +}; + +extern void babel_error_init(void); + +#endif diff --git a/babeld/babel_filter.c b/babeld/babel_filter.c new file mode 100644 index 0000000..0534932 --- /dev/null +++ b/babeld/babel_filter.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "babel_filter.h" +#include "vty.h" +#include "filter.h" +#include "log.h" +#include "plist.h" +#include "distribute.h" +#include "util.h" + +int +babel_filter(int output, const unsigned char *prefix, unsigned short plen, + unsigned int ifindex) +{ + struct interface *ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); + babel_interface_nfo *babel_ifp = ifp ? babel_get_if_nfo(ifp) : NULL; + struct prefix p; + struct distribute *dist = NULL; + struct access_list *alist; + struct prefix_list *plist; + int distribute; + struct babel *babel; + afi_t family; + + p.family = v4mapped(prefix) ? AF_INET : AF_INET6; + p.prefixlen = v4mapped(prefix) ? plen - 96 : plen; + if (p.family == AF_INET) { + uchar_to_inaddr(&p.u.prefix4, prefix); + distribute = output ? DISTRIBUTE_V4_OUT : DISTRIBUTE_V4_IN; + family = AFI_IP; + } else { + uchar_to_in6addr(&p.u.prefix6, prefix); + distribute = output ? DISTRIBUTE_V6_OUT : DISTRIBUTE_V6_IN; + family = AFI_IP6; + } + + if (babel_ifp != NULL && babel_ifp->list[distribute]) { + if (access_list_apply (babel_ifp->list[distribute], &p) + == FILTER_DENY) { + debugf(BABEL_DEBUG_FILTER, + "%pFX filtered by distribute %s", + &p, output ? "out" : "in"); + return INFINITY; + } + } + if (babel_ifp != NULL && babel_ifp->prefix[distribute]) { + if (prefix_list_apply (babel_ifp->prefix[distribute], &p) + == PREFIX_DENY) { + debugf(BABEL_DEBUG_FILTER, "%pFX filtered by distribute %s", + &p, output ? "out" : "in"); + return INFINITY; + } + } + + /* All interface filter check. */ + babel = babel_lookup(); + if (babel) + dist = distribute_lookup (babel->distribute_ctx, NULL); + if (dist) { + if (dist->list[distribute]) { + alist = access_list_lookup (family, dist->list[distribute]); + + if (alist) { + if (access_list_apply (alist, &p) == FILTER_DENY) { + debugf(BABEL_DEBUG_FILTER,"%pFX filtered by distribute %s", + &p, output ? "out" : "in"); + return INFINITY; + } + } + } + if (dist->prefix[distribute]) { + plist = prefix_list_lookup (family, dist->prefix[distribute]); + if (plist) { + if (prefix_list_apply (plist, &p) == PREFIX_DENY) { + debugf(BABEL_DEBUG_FILTER,"%pFX filtered by distribute %s", + &p, output ? "out" : "in"); + return INFINITY; + } + } + } + } + return 0; +} diff --git a/babeld/babel_filter.h b/babeld/babel_filter.h new file mode 100644 index 0000000..d253543 --- /dev/null +++ b/babeld/babel_filter.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABELD_BABEL_FILTER_H +#define BABELD_BABEL_FILTER_H + +#include +#include "prefix.h" +#include "babel_interface.h" + +int babel_filter(int output, const unsigned char *prefix, unsigned short plen, + unsigned int index); + +#endif /* BABELD_BABEL_FILTER_H */ diff --git a/babeld/babel_interface.c b/babeld/babel_interface.c new file mode 100644 index 0000000..c4349b5 --- /dev/null +++ b/babeld/babel_interface.c @@ -0,0 +1,1357 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#include +#include "memory.h" +#include "log.h" +#include "command.h" +#include "prefix.h" +#include "vector.h" +#include "distribute.h" +#include "lib_errors.h" +#include "network.h" + +#include "babel_main.h" +#include "util.h" +#include "kernel.h" +#include "babel_interface.h" +#include "message.h" +#include "route.h" +#include "babel_zebra.h" +#include "neighbour.h" +#include "route.h" +#include "xroute.h" +#include "babel_errors.h" + +#ifndef VTYSH_EXTRACT_PL +#include "babeld/babel_interface_clippy.c" +#endif + +DEFINE_MTYPE_STATIC(BABELD, BABEL_IF, "Babel Interface"); + +#define IS_ENABLE(ifp) (babel_enable_if_lookup(ifp->name) >= 0) + +static int babel_enable_if_lookup (const char *ifname); +static int babel_enable_if_add (const char *ifname); +static int babel_enable_if_delete (const char *ifname); +static int interface_recalculate(struct interface *ifp); +static int interface_reset(struct interface *ifp); +static int babel_if_new_hook (struct interface *ifp); +static int babel_if_delete_hook (struct interface *ifp); +static int interface_config_write (struct vty *vty); +static babel_interface_nfo * babel_interface_allocate (void); +static void babel_interface_free (babel_interface_nfo *bi); + + +static vector babel_enable_if; /* enable interfaces (by cmd). */ + +int babel_ifp_up(struct interface *ifp) +{ + debugf(BABEL_DEBUG_IF, "receive an 'interface up'"); + + interface_recalculate(ifp); + return 0; +} + +int +babel_ifp_down(struct interface *ifp) +{ + debugf(BABEL_DEBUG_IF, "receive an 'interface down'"); + + if (ifp == NULL) { + return 0; + } + + interface_reset(ifp); + return 0; +} + +int babel_ifp_create (struct interface *ifp) +{ + debugf(BABEL_DEBUG_IF, "receive an 'interface add'"); + + interface_recalculate(ifp); + + return 0; + } + +int +babel_ifp_destroy(struct interface *ifp) +{ + debugf(BABEL_DEBUG_IF, "receive an 'interface delete'"); + + if (IS_ENABLE(ifp)) + interface_reset(ifp); + + return 0; +} + +int +babel_interface_address_add (ZAPI_CALLBACK_ARGS) +{ + babel_interface_nfo *babel_ifp; + struct connected *ifc; + struct prefix *prefix; + + debugf(BABEL_DEBUG_IF, "receive an 'interface address add'"); + + ifc = zebra_interface_address_read (ZEBRA_INTERFACE_ADDRESS_ADD, + zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + prefix = ifc->address; + + if (prefix->family == AF_INET) { + flush_interface_routes(ifc->ifp, 0); + babel_ifp = babel_get_if_nfo(ifc->ifp); + if (babel_ifp->ipv4 == NULL) { + babel_ifp->ipv4 = malloc(4); + if (babel_ifp->ipv4 == NULL) { + flog_err(EC_BABEL_MEMORY, "not enough memory"); + } else { + memcpy(babel_ifp->ipv4, &prefix->u.prefix4, 4); + } + } + } + + send_request(ifc->ifp, NULL, 0); + send_update(ifc->ifp, 0, NULL, 0); + + return 0; +} + +int +babel_interface_address_delete (ZAPI_CALLBACK_ARGS) +{ + babel_interface_nfo *babel_ifp; + struct connected *ifc; + struct prefix *prefix; + + debugf(BABEL_DEBUG_IF, "receive an 'interface address delete'"); + + ifc = zebra_interface_address_read (ZEBRA_INTERFACE_ADDRESS_DELETE, + zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + prefix = ifc->address; + + if (prefix->family == AF_INET) { + flush_interface_routes(ifc->ifp, 0); + babel_ifp = babel_get_if_nfo(ifc->ifp); + if (babel_ifp->ipv4 != NULL + && memcmp(babel_ifp->ipv4, &prefix->u.prefix4, IPV4_MAX_BYTELEN) + == 0) { + free(babel_ifp->ipv4); + babel_ifp->ipv4 = NULL; + } + } + + send_request(ifc->ifp, NULL, 0); + send_update(ifc->ifp, 0, NULL, 0); + + connected_free(&ifc); + return 0; +} + +/* Lookup function. */ +static int +babel_enable_if_lookup (const char *ifname) +{ + unsigned int i; + char *str; + + for (i = 0; i < vector_active (babel_enable_if); i++) + if ((str = vector_slot (babel_enable_if, i)) != NULL) + if (strcmp (str, ifname) == 0) + return i; + return -1; +} + +/* Add interface to babel_enable_if. */ +static int +babel_enable_if_add (const char *ifname) +{ + int ret; + struct interface *ifp = NULL; + + ret = babel_enable_if_lookup (ifname); + if (ret >= 0) + return -1; + + vector_set (babel_enable_if, strdup (ifname)); + + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + if (ifp != NULL) + interface_recalculate(ifp); + + return 1; +} + +/* Delete interface from babel_enable_if. */ +static int +babel_enable_if_delete (const char *ifname) +{ + int babel_enable_if_index; + char *str; + struct interface *ifp = NULL; + + babel_enable_if_index = babel_enable_if_lookup (ifname); + if (babel_enable_if_index < 0) + return -1; + + str = vector_slot (babel_enable_if, babel_enable_if_index); + free (str); + vector_unset (babel_enable_if, babel_enable_if_index); + + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + if (ifp != NULL) + interface_reset(ifp); + + return 1; +} + +/* [Babel Command] Babel enable on specified interface or matched network. */ +DEFUN (babel_network, + babel_network_cmd, + "network IF_OR_ADDR", + "Enable Babel protocol on specified interface or network.\n" + "Interface or address\n") +{ + int ret; + struct prefix p; + + ret = str2prefix (argv[1]->arg, &p); + + /* Given string is: */ + if (ret) /* an IPv4 or v6 network */ + return CMD_ERR_NO_MATCH; /* not implemented yet */ + else /* an interface name */ + ret = babel_enable_if_add (argv[1]->arg); + + if (ret < 0) { + vty_out (vty, "There is same network configuration %s\n", + argv[1]->arg); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* [Babel Command] Babel enable on specified interface or matched network. */ +DEFUN (no_babel_network, + no_babel_network_cmd, + "no network IF_OR_ADDR", + NO_STR + "Disable Babel protocol on specified interface or network.\n" + "Interface or address\n") +{ + int ret; + struct prefix p; + + ret = str2prefix (argv[2]->arg, &p); + + /* Given string is: */ + if (ret) /* an IPv4 or v6 network */ + return CMD_ERR_NO_MATCH; /* not implemented yet */ + else /* an interface name */ + ret = babel_enable_if_delete (argv[2]->arg); + + if (ret < 0) { + vty_out (vty, "can't find network %s\n",argv[2]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +/* There are a number of interface parameters that must be changed when + an interface becomes wired/wireless. In Quagga, they cannot be + configured separately. */ + +static void +babel_set_wired_internal(babel_interface_nfo *babel_ifp, int wired) +{ + if(wired) { + SET_FLAG(babel_ifp->flags, BABEL_IF_WIRED); + SET_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON); + babel_ifp->cost = BABEL_DEFAULT_RXCOST_WIRED; + babel_ifp->channel = BABEL_IF_CHANNEL_NONINTERFERING; + UNSET_FLAG(babel_ifp->flags, BABEL_IF_LQ); + } + else { + UNSET_FLAG(babel_ifp->flags, BABEL_IF_WIRED); + UNSET_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON); + babel_ifp->cost = BABEL_DEFAULT_RXCOST_WIRELESS; + babel_ifp->channel = BABEL_IF_CHANNEL_INTERFERING; + SET_FLAG(babel_ifp->flags, BABEL_IF_LQ); + } + +} + +/* [Interface Command] Tell the interface is wire. */ +DEFPY (babel_set_wired, + babel_set_wired_cmd, + "[no] babel wired", + NO_STR + "Babel interface commands\n" + "Enable wired optimizations\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + + assert (babel_ifp != NULL); + babel_set_wired_internal(babel_ifp, no ? 0 : 1); + return CMD_SUCCESS; +} + +/* [Interface Command] Tell the interface is wireless (default). */ +DEFPY (babel_set_wireless, + babel_set_wireless_cmd, + "[no] babel wireless", + NO_STR + "Babel interface commands\n" + "Disable wired optimizations (assume wireless)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + + assert (babel_ifp != NULL); + babel_set_wired_internal(babel_ifp, no ? 1 : 0); + return CMD_SUCCESS; +} + +/* [Interface Command] Enable split horizon. */ +DEFPY (babel_split_horizon, + babel_split_horizon_cmd, + "[no] babel split-horizon", + NO_STR + "Babel interface commands\n" + "Enable split horizon processing\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + + assert (babel_ifp != NULL); + if (!no) + SET_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON); + else + UNSET_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON); + return CMD_SUCCESS; +} + +/* [Interface Command]. */ +DEFPY (babel_set_hello_interval, + babel_set_hello_interval_cmd, + "[no] babel hello-interval (20-655340)", + NO_STR + "Babel interface commands\n" + "Time between scheduled hellos\n" + "Milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + babel_ifp->hello_interval = no ? + BABEL_DEFAULT_HELLO_INTERVAL : hello_interval; + return CMD_SUCCESS; +} + +/* [Interface Command]. */ +DEFPY (babel_set_update_interval, + babel_set_update_interval_cmd, + "[no] babel update-interval (20-655340)", + NO_STR + "Babel interface commands\n" + "Time between scheduled updates\n" + "Milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + babel_ifp->update_interval = no ? + BABEL_DEFAULT_UPDATE_INTERVAL : update_interval; + return CMD_SUCCESS; +} + +DEFPY (babel_set_rxcost, + babel_set_rxcost_cmd, + "[no] babel rxcost (1-65534)", + NO_STR + "Babel interface commands\n" + "Rxcost multiplier\n" + "Units\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + if (no) + rxcost = CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? + BABEL_DEFAULT_RXCOST_WIRED : BABEL_DEFAULT_RXCOST_WIRELESS; + + babel_ifp->cost = rxcost; + return CMD_SUCCESS; +} + +DEFPY (babel_set_rtt_decay, + babel_set_rtt_decay_cmd, + "[no] babel rtt-decay (1-256)", + NO_STR + "Babel interface commands\n" + "Decay factor for exponential moving average of RTT samples\n" + "Units of 1/256\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + babel_ifp->rtt_decay = no ? BABEL_DEFAULT_RTT_DECAY : rtt_decay; + return CMD_SUCCESS; +} + +DEFPY (babel_set_rtt_min, + babel_set_rtt_min_cmd, + "[no] babel rtt-min (1-65535)", + NO_STR + "Babel interface commands\n" + "Minimum RTT starting for increasing cost\n" + "Milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + /* The value is entered in milliseconds but stored as microseconds. */ + babel_ifp->rtt_min = no ? BABEL_DEFAULT_RTT_MIN : rtt_min * 1000; + return CMD_SUCCESS; +} + +DEFPY (babel_set_rtt_max, + babel_set_rtt_max_cmd, + "[no] babel rtt-max (1-65535)", + NO_STR + "Babel interface commands\n" + "Maximum RTT\n" + "Milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + /* The value is entered in milliseconds but stored as microseconds. */ + babel_ifp->rtt_max = no ? BABEL_DEFAULT_RTT_MAX : rtt_max * 1000; + return CMD_SUCCESS; +} + +DEFPY (babel_set_max_rtt_penalty, + babel_set_max_rtt_penalty_cmd, + "[no] babel max-rtt-penalty (0-65535)", + NO_STR + "Babel interface commands\n" + "Maximum additional cost due to RTT\n" + "Milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + babel_ifp->max_rtt_penalty = no ? + BABEL_DEFAULT_MAX_RTT_PENALTY : max_rtt_penalty; + return CMD_SUCCESS; +} + +DEFPY (babel_set_enable_timestamps, + babel_set_enable_timestamps_cmd, + "[no] babel enable-timestamps", + NO_STR + "Babel interface commands\n" + "Enable timestamps\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + if (!no) + SET_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS); + else + UNSET_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS); + return CMD_SUCCESS; +} + +DEFPY (babel_set_channel, + babel_set_channel_cmd, + "[no] babel channel <(1-254)$ch|interfering$interfering|" + "noninterfering$noninterfering>", + NO_STR + "Babel interface commands\n" + "Channel number for diversity routing\n" + "Number\n" + "Mark channel as interfering\n" + "Mark channel as noninterfering\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + babel_interface_nfo *babel_ifp; + + babel_ifp = babel_get_if_nfo(ifp); + assert (babel_ifp != NULL); + + if (no) + ch = CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? + BABEL_IF_CHANNEL_NONINTERFERING : BABEL_IF_CHANNEL_INTERFERING; + else if (interfering) + ch = BABEL_IF_CHANNEL_INTERFERING; + else if (noninterfering) + ch = BABEL_IF_CHANNEL_NONINTERFERING; + + babel_ifp->channel = ch; + return CMD_SUCCESS; +} + +/* This should be no more than half the hello interval, so that hellos + aren't sent late. The result is in milliseconds. */ +unsigned +jitter(babel_interface_nfo *babel_ifp, int urgent) +{ + unsigned interval = babel_ifp->hello_interval; + if(urgent) + interval = MIN(interval, 100); + else + interval = MIN(interval, 4000); + return roughly(interval) / 4; +} + +unsigned +update_jitter(babel_interface_nfo *babel_ifp, int urgent) +{ + unsigned interval = babel_ifp->hello_interval; + if(urgent) + interval = MIN(interval, 100); + else + interval = MIN(interval, 4000); + return roughly(interval); +} + +/* calculate babeld's specific datas of an interface (change when the interface + change) */ +static int +interface_recalculate(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + unsigned char *tmp = NULL; + int mtu, rc; + struct ipv6_mreq mreq; + + if (!IS_ENABLE(ifp)) + return -1; + + if (!if_is_operative(ifp) || !CHECK_FLAG(ifp->flags, IFF_RUNNING)) { + interface_reset(ifp); + return -1; + } + + SET_FLAG(babel_ifp->flags, BABEL_IF_IS_UP); + + mtu = MIN(ifp->mtu, ifp->mtu6); + + /* We need to be able to fit at least two messages into a packet, + so MTUs below 116 require lower layer fragmentation. */ + /* In IPv6, the minimum MTU is 1280, and every host must be able + to reassemble up to 1500 bytes, but I'd rather not rely on this. */ + if(mtu < 128) { + debugf(BABEL_DEBUG_IF, "Suspiciously low MTU %d on interface %s (%d).", + mtu, ifp->name, ifp->ifindex); + mtu = 128; + } + + /* 4 for Babel header; 40 for IPv6 header, 8 for UDP header, 12 for good luck. */ + babel_ifp->bufsize = mtu - 4 - 60; + tmp = babel_ifp->sendbuf; + babel_ifp->sendbuf = realloc(babel_ifp->sendbuf, babel_ifp->bufsize); + if(babel_ifp->sendbuf == NULL) { + flog_err(EC_BABEL_MEMORY, "Couldn't reallocate sendbuf."); + free(tmp); + babel_ifp->bufsize = 0; + return -1; + } + tmp = NULL; + + rc = resize_receive_buffer(mtu); + if(rc < 0) + zlog_warn("couldn't resize receive buffer for interface %s (%d) (%d bytes).", + ifp->name, ifp->ifindex, mtu); + + memset(&mreq, 0, sizeof(mreq)); + memcpy(&mreq.ipv6mr_multiaddr, protocol_group, 16); + mreq.ipv6mr_interface = ifp->ifindex; + + rc = setsockopt(protocol_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, + (char*)&mreq, sizeof(mreq)); + if (rc < 0 && errno != EADDRINUSE) { + flog_err_sys(EC_LIB_SOCKET, + "setsockopt(IPV6_JOIN_GROUP) on interface '%s': %s", + ifp->name, safe_strerror(errno)); + /* This is probably due to a missing link-local address, + so down this interface, and wait until the main loop + tries to up it again. */ + interface_reset(ifp); + return -1; + } + + set_timeout(&babel_ifp->hello_timeout, babel_ifp->hello_interval); + set_timeout(&babel_ifp->update_timeout, babel_ifp->update_interval); + send_hello(ifp); + send_request(ifp, NULL, 0); + + update_interface_metric(ifp); + + debugf(BABEL_DEBUG_COMMON, + "Upped interface %s (%s, cost=%d, channel=%d%s).", + ifp->name, + CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? "wired" : "wireless", + babel_ifp->cost, + babel_ifp->channel, + babel_ifp->ipv4 ? ", IPv4" : ""); + + if(rc > 0) + send_update(ifp, 0, NULL, 0); + + return 1; +} + +/* Reset the interface as it was new: it's not removed from the interface list, + and may be considered as a upped interface. */ +static int +interface_reset(struct interface *ifp) +{ + int rc; + struct ipv6_mreq mreq; + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + + if (!CHECK_FLAG(babel_ifp->flags, BABEL_IF_IS_UP)) + return 0; + + debugf(BABEL_DEBUG_IF, "interface reset: %s", ifp->name); + + UNSET_FLAG(babel_ifp->flags, BABEL_IF_IS_UP); + + flush_interface_routes(ifp, 0); + babel_ifp->buffered = 0; + babel_ifp->bufsize = 0; + free(babel_ifp->sendbuf); + babel_ifp->num_buffered_updates = 0; + babel_ifp->update_bufsize = 0; + if(babel_ifp->buffered_updates) + free(babel_ifp->buffered_updates); + babel_ifp->buffered_updates = NULL; + babel_ifp->sendbuf = NULL; + + if(ifp->ifindex > 0) { + memset(&mreq, 0, sizeof(mreq)); + memcpy(&mreq.ipv6mr_multiaddr, protocol_group, 16); + mreq.ipv6mr_interface = ifp->ifindex; + rc = setsockopt(protocol_socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + (char*)&mreq, sizeof(mreq)); + if(rc < 0) + flog_err_sys(EC_LIB_SOCKET, + "setsockopt(IPV6_LEAVE_GROUP) on interface '%s': %s", + ifp->name, safe_strerror(errno)); + } + + update_interface_metric(ifp); + + debugf(BABEL_DEBUG_COMMON,"Upped network %s (%s, cost=%d%s).", + ifp->name, + CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? "wired" : "wireless", + babel_ifp->cost, + babel_ifp->ipv4 ? ", IPv4" : ""); + + if (babel_ifp->ipv4 != NULL){ + free(babel_ifp->ipv4); + babel_ifp->ipv4 = NULL; + } + + return 1; +} + +/* Send retraction to all, and reset all interfaces statistics. */ +void +babel_interface_close_all(void) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + + FOR_ALL_INTERFACES(vrf, ifp) { + if(!if_up(ifp)) + continue; + send_wildcard_retraction(ifp); + /* Make sure that we expire quickly from our neighbours' + association caches. */ + send_hello_noupdate(ifp, 10); + flushbuf(ifp); + usleep(roughly(1000)); + gettime(&babel_now); + } + FOR_ALL_INTERFACES(vrf, ifp) { + if(!if_up(ifp)) + continue; + /* Make sure they got it. */ + send_wildcard_retraction(ifp); + send_hello_noupdate(ifp, 1); + flushbuf(ifp); + usleep(roughly(10000)); + gettime(&babel_now); + interface_reset(ifp); + } +} + +/* return "true" if address is one of our ipv6 addresses */ +int +is_interface_ll_address(struct interface *ifp, const unsigned char *address) +{ + struct connected *connected; + + if(!if_up(ifp)) + return 0; + + frr_each (if_connected, ifp->connected, connected) { + if (connected->address->family == AF_INET6 + && memcmp(&connected->address->u.prefix6, address, + IPV6_MAX_BYTELEN) + == 0) + return 1; + } + + return 0; +} + +static void +show_babel_interface_sub (struct vty *vty, struct interface *ifp) +{ + int is_up; + babel_interface_nfo *babel_ifp; + + vty_out (vty, "%s is %s\n", ifp->name, + ((is_up = if_is_operative(ifp)) ? "up" : "down")); + vty_out (vty, " ifindex %u, MTU %u bytes %s\n", + ifp->ifindex, MIN(ifp->mtu, ifp->mtu6), if_flag_dump(ifp->flags)); + + if (!IS_ENABLE(ifp)) + { + vty_out (vty, " Babel protocol is not enabled on this interface\n"); + return; + } + if (!is_up) + { + vty_out (vty, + " Babel protocol is enabled, but not running on this interface\n"); + return; + } + babel_ifp = babel_get_if_nfo (ifp); + vty_out (vty, " Babel protocol is running on this interface\n"); + vty_out (vty, " Operating mode is \"%s\"\n", + CHECK_FLAG(babel_ifp->flags, BABEL_IF_WIRED) ? "wired" : "wireless"); + vty_out (vty, " Split horizon mode is %s\n", + CHECK_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON) ? "On" : "Off"); + vty_out (vty, " Hello interval is %u ms\n", babel_ifp->hello_interval); + vty_out (vty, " Update interval is %u ms\n", babel_ifp->update_interval); + vty_out (vty, " Rxcost multiplier is %u\n", babel_ifp->cost); +} + +DEFUN (show_babel_interface, + show_babel_interface_cmd, + "show babel interface [IFNAME]", + SHOW_STR + "Babel information\n" + "Interface information\n" + "Interface\n") +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + + if (argc == 3) + { + FOR_ALL_INTERFACES (vrf, ifp) + show_babel_interface_sub (vty, ifp); + return CMD_SUCCESS; + } + if ((ifp = if_lookup_by_name (argv[3]->arg, VRF_DEFAULT)) == NULL) + { + vty_out (vty, "No such interface name\n"); + return CMD_WARNING; + } + show_babel_interface_sub (vty, ifp); + return CMD_SUCCESS; +} + +static void +show_babel_neighbour_sub (struct vty *vty, struct neighbour *neigh) +{ + vty_out (vty, + "Neighbour %s dev %s reach %04x rxcost %d txcost %d rtt %s rttcost %d%s.\n", + format_address(neigh->address), + neigh->ifp->name, + neigh->reach, + neighbour_rxcost(neigh), + neigh->txcost, + format_thousands(neigh->rtt), + neighbour_rttcost(neigh), + if_up(neigh->ifp) ? "" : " (down)"); +} + +DEFUN (show_babel_neighbour, + show_babel_neighbour_cmd, + "show babel neighbor [IFNAME]", + SHOW_STR + "Babel information\n" + "Print neighbors\n" + "Interface\n") +{ + struct neighbour *neigh; + struct interface *ifp; + + if (argc == 3) { + FOR_ALL_NEIGHBOURS(neigh) { + show_babel_neighbour_sub(vty, neigh); + } + return CMD_SUCCESS; + } + if ((ifp = if_lookup_by_name (argv[3]->arg, VRF_DEFAULT)) == NULL) + { + vty_out (vty, "No such interface name\n"); + return CMD_WARNING; + } + FOR_ALL_NEIGHBOURS(neigh) { + if(ifp->ifindex == neigh->ifp->ifindex) { + show_babel_neighbour_sub(vty, neigh); + } + } + return CMD_SUCCESS; +} + +static int +babel_prefix_eq(struct prefix *prefix, unsigned char *p, int plen) +{ + if(prefix->family == AF_INET6) { + if (prefix->prefixlen != plen + || memcmp(&prefix->u.prefix6, p, IPV6_MAX_BYTELEN) != 0) + return 0; + } else if(prefix->family == AF_INET) { + if (plen < 96 || !v4mapped(p) || prefix->prefixlen != plen - 96 + || memcmp(&prefix->u.prefix4, p + 12, IPV4_MAX_BYTELEN) != 0) + return 0; + } else { + return 0; + } + + return 1; +} + +static void +show_babel_routes_sub(struct babel_route *route, struct vty *vty, + struct prefix *prefix) +{ + const unsigned char *nexthop = + memcmp(route->nexthop, route->neigh->address, IPV6_MAX_BYTELEN) + == 0 + ? NULL + : route->nexthop; + char channels[100]; + + if (prefix + && !babel_prefix_eq(prefix, route->src->prefix, route->src->plen)) + return; + + if (route->channels[0] == 0) + channels[0] = '\0'; + else { + int k, j = 0; + snprintf(channels, sizeof(channels), " chan ("); + j = strlen(channels); + for (k = 0; k < DIVERSITY_HOPS; k++) { + if (route->channels[k] == 0) + break; + if (k > 0) + channels[j++] = ','; + snprintf(channels + j, 100 - j, "%u", + route->channels[k]); + j = strlen(channels); + } + snprintf(channels + j, 100 - j, ")"); + if (k == 0) + channels[0] = '\0'; + } + + vty_out (vty, + "%s metric %d refmetric %d id %s seqno %d%s age %d via %s neigh %s%s%s%s\n", + format_prefix(route->src->prefix, route->src->plen), + route_metric(route), route->refmetric, + format_eui64(route->src->id), + (int)route->seqno, + channels, + (int)(babel_now.tv_sec - route->time), + route->neigh->ifp->name, + format_address(route->neigh->address), + nexthop ? " nexthop " : "", + nexthop ? format_address(nexthop) : "", + route->installed ? " (installed)" : route_feasible(route) ? " (feasible)" : ""); +} + +static void +show_babel_xroutes_sub (struct xroute *xroute, struct vty *vty, + struct prefix *prefix) +{ + if(prefix && !babel_prefix_eq(prefix, xroute->prefix, xroute->plen)) + return; + + vty_out (vty, "%s metric %d (exported)\n", + format_prefix(xroute->prefix, xroute->plen), + xroute->metric); +} + +DEFUN (show_babel_route, + show_babel_route_cmd, + "show babel route", + SHOW_STR + "Babel information\n" + "Babel internal routing table\n") +{ + struct route_stream *routes = NULL; + struct xroute_stream *xroutes = NULL; + routes = route_stream(0); + if(routes) { + while(1) { + struct babel_route *route = route_stream_next(routes); + if(route == NULL) + break; + show_babel_routes_sub(route, vty, NULL); + } + route_stream_done(routes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + xroutes = xroute_stream(); + if(xroutes) { + while(1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if(xroute == NULL) + break; + show_babel_xroutes_sub(xroute, vty, NULL); + } + xroute_stream_done(xroutes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + return CMD_SUCCESS; +} + +DEFUN (show_babel_route_prefix, + show_babel_route_prefix_cmd, + "show babel route ", + SHOW_STR + "Babel information\n" + "Babel internal routing table\n" + "IPv4 prefix /\n" + "IPv6 prefix /\n") +{ + struct route_stream *routes = NULL; + struct xroute_stream *xroutes = NULL; + struct prefix prefix; + int ret; + + ret = str2prefix(argv[3]->arg, &prefix); + if(ret == 0) { + vty_out (vty, "%% Malformed address\n"); + return CMD_WARNING; + } + + routes = route_stream(0); + if(routes) { + while(1) { + struct babel_route *route = route_stream_next(routes); + if(route == NULL) + break; + show_babel_routes_sub(route, vty, &prefix); + } + route_stream_done(routes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + xroutes = xroute_stream(); + if(xroutes) { + while(1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if(xroute == NULL) + break; + show_babel_xroutes_sub(xroute, vty, &prefix); + } + xroute_stream_done(xroutes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + return CMD_SUCCESS; +} + + +DEFUN (show_babel_route_addr, + show_babel_route_addr_cmd, + "show babel route A.B.C.D", + SHOW_STR + "Babel information\n" + "Babel internal routing table\n" + "IPv4 address /\n") +{ + struct in_addr addr; + char buf[INET_ADDRSTRLEN + 8]; + char buf1[INET_ADDRSTRLEN + 8]; + struct route_stream *routes = NULL; + struct xroute_stream *xroutes = NULL; + struct prefix prefix; + int ret; + + ret = inet_aton (argv[3]->arg, &addr); + if (ret <= 0) { + vty_out (vty, "%% Malformed address\n"); + return CMD_WARNING; + } + + /* Quagga has no convenient prefix constructors. */ + snprintf(buf, sizeof(buf), "%s/%d", + inet_ntop(AF_INET, &addr, buf1, sizeof(buf1)), 32); + + ret = str2prefix(buf, &prefix); + if (ret == 0) { + vty_out (vty, "%% Parse error -- this shouldn't happen\n"); + return CMD_WARNING; + } + + routes = route_stream(0); + if(routes) { + while(1) { + struct babel_route *route = route_stream_next(routes); + if(route == NULL) + break; + show_babel_routes_sub(route, vty, &prefix); + } + route_stream_done(routes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + xroutes = xroute_stream(); + if(xroutes) { + while(1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if(xroute == NULL) + break; + show_babel_xroutes_sub(xroute, vty, &prefix); + } + xroute_stream_done(xroutes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + return CMD_SUCCESS; +} + +DEFUN (show_babel_route_addr6, + show_babel_route_addr6_cmd, + "show babel route X:X::X:X", + SHOW_STR + "Babel information\n" + "Babel internal routing table\n" + "IPv6 address /\n") +{ + struct in6_addr addr; + char buf1[INET6_ADDRSTRLEN]; + char buf[INET6_ADDRSTRLEN + 8]; + struct route_stream *routes = NULL; + struct xroute_stream *xroutes = NULL; + struct prefix prefix; + int ret; + + ret = inet_pton (AF_INET6, argv[3]->arg, &addr); + if (ret <= 0) { + vty_out (vty, "%% Malformed address\n"); + return CMD_WARNING; + } + + /* Quagga has no convenient prefix constructors. */ + snprintf(buf, sizeof(buf), "%s/%d", + inet_ntop(AF_INET6, &addr, buf1, sizeof(buf1)), 128); + + ret = str2prefix(buf, &prefix); + if (ret == 0) { + vty_out (vty, "%% Parse error -- this shouldn't happen\n"); + return CMD_WARNING; + } + + routes = route_stream(0); + if(routes) { + while(1) { + struct babel_route *route = route_stream_next(routes); + if(route == NULL) + break; + show_babel_routes_sub(route, vty, &prefix); + } + route_stream_done(routes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + xroutes = xroute_stream(); + if(xroutes) { + while(1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if(xroute == NULL) + break; + show_babel_xroutes_sub(xroute, vty, &prefix); + } + xroute_stream_done(xroutes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + return CMD_SUCCESS; +} + +DEFUN (show_babel_parameters, + show_babel_parameters_cmd, + "show babel parameters", + SHOW_STR + "Babel information\n" + "Configuration information\n") +{ + struct babel *babel_ctx; + + vty_out (vty, " -- Babel running configuration --\n"); + show_babel_main_configuration(vty); + + babel_ctx = babel_lookup(); + if (babel_ctx) { + vty_out (vty, " -- distribution lists --\n"); + config_show_distribute(vty, babel_ctx->distribute_ctx); + } + return CMD_SUCCESS; +} + +void +babel_if_init(void) +{ + /* initialize interface list */ + hook_register_prio(if_add, 0, babel_if_new_hook); + hook_register_prio(if_del, 0, babel_if_delete_hook); + + babel_enable_if = vector_init (1); + + /* install interface node and commands */ + if_cmd_init(interface_config_write); + + install_element(BABEL_NODE, &babel_network_cmd); + install_element(BABEL_NODE, &no_babel_network_cmd); + install_element(INTERFACE_NODE, &babel_split_horizon_cmd); + install_element(INTERFACE_NODE, &babel_set_wired_cmd); + install_element(INTERFACE_NODE, &babel_set_wireless_cmd); + install_element(INTERFACE_NODE, &babel_set_hello_interval_cmd); + install_element(INTERFACE_NODE, &babel_set_update_interval_cmd); + install_element(INTERFACE_NODE, &babel_set_rxcost_cmd); + install_element(INTERFACE_NODE, &babel_set_channel_cmd); + install_element(INTERFACE_NODE, &babel_set_rtt_decay_cmd); + install_element(INTERFACE_NODE, &babel_set_rtt_min_cmd); + install_element(INTERFACE_NODE, &babel_set_rtt_max_cmd); + install_element(INTERFACE_NODE, &babel_set_max_rtt_penalty_cmd); + install_element(INTERFACE_NODE, &babel_set_enable_timestamps_cmd); + + /* "show babel ..." commands */ + install_element(VIEW_NODE, &show_babel_interface_cmd); + install_element(VIEW_NODE, &show_babel_neighbour_cmd); + install_element(VIEW_NODE, &show_babel_route_cmd); + install_element(VIEW_NODE, &show_babel_route_prefix_cmd); + install_element(VIEW_NODE, &show_babel_route_addr_cmd); + install_element(VIEW_NODE, &show_babel_route_addr6_cmd); + install_element(VIEW_NODE, &show_babel_parameters_cmd); +} + +/* hooks: functions called respectively when struct interface is + created or deleted. */ +static int +babel_if_new_hook (struct interface *ifp) +{ + ifp->info = babel_interface_allocate(); + return 0; +} + +static int +babel_if_delete_hook (struct interface *ifp) +{ + babel_interface_free(ifp->info); + ifp->info = NULL; + return 0; +} + +/* Output an "interface" section for each of the known interfaces with +babeld-specific statement lines where appropriate. */ +static int +interface_config_write (struct vty *vty) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + int write = 0; + + FOR_ALL_INTERFACES (vrf, ifp) { + if_vty_config_start(vty, ifp); + if (ifp->desc) + vty_out (vty, " description %s\n",ifp->desc); + babel_interface_nfo *babel_ifp = babel_get_if_nfo (ifp); + /* wireless is the default*/ + if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_WIRED)) + { + vty_out (vty, " babel wired\n"); + write++; + } + if (babel_ifp->hello_interval != BABEL_DEFAULT_HELLO_INTERVAL) + { + vty_out (vty, " babel hello-interval %u\n", + babel_ifp->hello_interval); + write++; + } + if (babel_ifp->update_interval != BABEL_DEFAULT_UPDATE_INTERVAL) + { + vty_out (vty, " babel update-interval %u\n", + babel_ifp->update_interval); + write++; + } + if (CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS)) { + vty_out(vty, " babel enable-timestamps\n"); + write++; + } + if (babel_ifp->max_rtt_penalty != BABEL_DEFAULT_MAX_RTT_PENALTY) { + vty_out(vty, " babel max-rtt-penalty %u\n", + babel_ifp->max_rtt_penalty); + write++; + } + if (babel_ifp->rtt_decay != BABEL_DEFAULT_RTT_DECAY) { + vty_out(vty, " babel rtt-decay %u\n", babel_ifp->rtt_decay); + write++; + } + if (babel_ifp->rtt_min != BABEL_DEFAULT_RTT_MIN) { + vty_out(vty, " babel rtt-min %u\n", babel_ifp->rtt_min / 1000); + write++; + } + if (babel_ifp->rtt_max != BABEL_DEFAULT_RTT_MAX) { + vty_out(vty, " babel rtt-max %u\n", babel_ifp->rtt_max / 1000); + write++; + } + /* Some parameters have different defaults for wired/wireless. */ + if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_WIRED)) { + if (!CHECK_FLAG (babel_ifp->flags, BABEL_IF_SPLIT_HORIZON)) { + vty_out (vty, " no babel split-horizon\n"); + write++; + } + if (babel_ifp->cost != BABEL_DEFAULT_RXCOST_WIRED) { + vty_out (vty, " babel rxcost %u\n", babel_ifp->cost); + write++; + } + if (babel_ifp->channel == BABEL_IF_CHANNEL_INTERFERING) { + vty_out (vty, " babel channel interfering\n"); + write++; + } else if(babel_ifp->channel != BABEL_IF_CHANNEL_NONINTERFERING) { + vty_out (vty, " babel channel %d\n",babel_ifp->channel); + write++; + } + } else { + if (CHECK_FLAG (babel_ifp->flags, BABEL_IF_SPLIT_HORIZON)) { + vty_out (vty, " babel split-horizon\n"); + write++; + } + if (babel_ifp->cost != BABEL_DEFAULT_RXCOST_WIRELESS) { + vty_out (vty, " babel rxcost %u\n", babel_ifp->cost); + write++; + } + if (babel_ifp->channel == BABEL_IF_CHANNEL_NONINTERFERING) { + vty_out (vty, " babel channel noninterfering\n"); + write++; + } else if(babel_ifp->channel != BABEL_IF_CHANNEL_INTERFERING) { + vty_out (vty, " babel channel %d\n",babel_ifp->channel); + write++; + } + } + if_vty_config_end(vty); + write++; + } + return write; +} + +/* Output a "network" statement line for each of the enabled interfaces. */ +int +babel_enable_if_config_write (struct vty * vty) +{ + unsigned int i, lines = 0; + char *str; + + for (i = 0; i < vector_active (babel_enable_if); i++) + if ((str = vector_slot (babel_enable_if, i)) != NULL) + { + vty_out (vty, " network %s\n", str); + lines++; + } + return lines; +} + +/* functions to allocate or free memory for a babel_interface_nfo, filling + needed fields */ +static babel_interface_nfo * +babel_interface_allocate (void) +{ + babel_interface_nfo *babel_ifp; + babel_ifp = XCALLOC(MTYPE_BABEL_IF, sizeof(babel_interface_nfo)); + /* All flags are unset */ + babel_ifp->bucket_time = babel_now.tv_sec; + babel_ifp->bucket = BUCKET_TOKENS_MAX; + babel_ifp->hello_seqno = (frr_weak_random() & 0xFFFF); + babel_ifp->rtt_decay = BABEL_DEFAULT_RTT_DECAY; + babel_ifp->rtt_min = BABEL_DEFAULT_RTT_MIN; + babel_ifp->rtt_max = BABEL_DEFAULT_RTT_MAX; + babel_ifp->max_rtt_penalty = BABEL_DEFAULT_MAX_RTT_PENALTY; + babel_ifp->hello_interval = BABEL_DEFAULT_HELLO_INTERVAL; + babel_ifp->update_interval = BABEL_DEFAULT_UPDATE_INTERVAL; + babel_ifp->channel = BABEL_IF_CHANNEL_INTERFERING; + babel_set_wired_internal(babel_ifp, 0); + + return babel_ifp; +} + +static void +babel_interface_free (babel_interface_nfo *babel_ifp) +{ + if (babel_ifp->ipv4){ + free(babel_ifp->ipv4); + babel_ifp->ipv4 = NULL; + } + XFREE(MTYPE_BABEL_IF, babel_ifp); +} diff --git a/babeld/babel_interface.h b/babeld/babel_interface.h new file mode 100644 index 0000000..a585e23 --- /dev/null +++ b/babeld/babel_interface.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_INTERFACE_H +#define BABEL_INTERFACE_H + +#include +#include "zclient.h" +#include "vty.h" +#include "distribute.h" + +#define CONFIG_DEFAULT 0 +#define CONFIG_NO 1 +#define CONFIG_YES 2 + +/* babeld interface information */ +struct babel_interface { + unsigned short flags; /* see below */ + unsigned short cost; + int channel; + struct timeval hello_timeout; + struct timeval update_timeout; + struct timeval flush_timeout; + struct timeval update_flush_timeout; + unsigned char *ipv4; + int buffered; + int bufsize; + /* Relative position of the Hello message in the send buffer, or + (-1) if there is none. */ + int buffered_hello; + char have_buffered_id; + char have_buffered_nh; + char have_buffered_prefix; + unsigned char buffered_id[8]; + unsigned char buffered_nh[4]; + unsigned char buffered_prefix[16]; + unsigned char *sendbuf; + struct buffered_update *buffered_updates; + int num_buffered_updates; + int update_bufsize; + time_t bucket_time; + unsigned int bucket; + time_t last_update_time; + unsigned short hello_seqno; + unsigned hello_interval; + unsigned update_interval; + /* A higher value means we forget old RTT samples faster. Must be + between 1 and 256, inclusive. */ + unsigned int rtt_decay; + /* Parameters for computing the cost associated to RTT. */ + unsigned int rtt_min; + unsigned int rtt_max; + unsigned int max_rtt_penalty; + + /* For filter type slot. */ + struct access_list *list[DISTRIBUTE_MAX]; /* Access-list. */ + struct prefix_list *prefix[DISTRIBUTE_MAX]; /* Prefix-list. */ +}; + +typedef struct babel_interface babel_interface_nfo; +static inline babel_interface_nfo* babel_get_if_nfo(struct interface *ifp) +{ + return ((babel_interface_nfo*) ifp->info); +} + +/* babel_interface_nfo flags */ +#define BABEL_IF_IS_UP (1 << 0) +#define BABEL_IF_WIRED (1 << 1) +#define BABEL_IF_SPLIT_HORIZON (1 << 2) +#define BABEL_IF_LQ (1 << 3) +#define BABEL_IF_FARAWAY (1 << 4) +#define BABEL_IF_TIMESTAMPS (1 << 5) + +/* Only INTERFERING can appear on the wire. */ +#define BABEL_IF_CHANNEL_UNKNOWN 0 +#define BABEL_IF_CHANNEL_INTERFERING 255 +#define BABEL_IF_CHANNEL_NONINTERFERING -2 + +static inline int +if_up(struct interface *ifp) +{ + return (if_is_operative(ifp) && + CHECK_FLAG(babel_get_if_nfo(ifp)->flags, BABEL_IF_IS_UP)); +} + +struct buffered_update { + unsigned char id[8]; + unsigned char prefix[16]; + unsigned char plen; + unsigned char pad[3]; +}; + +/* init function */ +void babel_if_init(void); + +/* Callback functions for zebra client */ +int babel_interface_up (int, struct zclient *, zebra_size_t, vrf_id_t); +int babel_interface_down (int, struct zclient *, zebra_size_t, vrf_id_t); +int babel_interface_add (int, struct zclient *, zebra_size_t, vrf_id_t); +int babel_interface_delete (int, struct zclient *, zebra_size_t, vrf_id_t); +int babel_interface_address_add (int, struct zclient *, zebra_size_t, vrf_id_t); +int babel_interface_address_delete (int, struct zclient *, zebra_size_t, vrf_id_t); + +int babel_ifp_create(struct interface *ifp); +int babel_ifp_up(struct interface *ifp); +int babel_ifp_down(struct interface *ifp); +int babel_ifp_destroy(struct interface *ifp); + +unsigned jitter(babel_interface_nfo *, int); +unsigned update_jitter(babel_interface_nfo *babel_ifp, int urgent); +/* return "true" if "address" is one of our ipv6 addresses */ +int is_interface_ll_address(struct interface *ifp, const unsigned char *address); +/* Send retraction to all, and reset all interfaces statistics. */ +void babel_interface_close_all(void); +extern int babel_enable_if_config_write (struct vty *); + + +#endif diff --git a/babeld/babel_main.c b/babeld/babel_main.c new file mode 100644 index 0000000..10ab1b5 --- /dev/null +++ b/babeld/babel_main.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +/* include zebra library */ +#include +#include + +#include "getopt.h" +#include "if.h" +#include "log.h" +#include "frrevent.h" +#include "privs.h" +#include "sigevent.h" +#include "lib/version.h" +#include "command.h" +#include "vty.h" +#include "memory.h" +#include "libfrr.h" +#include "lib_errors.h" + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" +#include "kernel.h" +#include "babel_interface.h" +#include "neighbour.h" +#include "route.h" +#include "xroute.h" +#include "message.h" +#include "resend.h" +#include "babel_zebra.h" +#include "babel_errors.h" + +static void babel_fail(void); +static void babel_init_random(void); +static void babel_exit_properly(void); +static void babel_save_state_file(void); + + +struct event_loop *master; /* quagga's threads handler */ +struct timeval babel_now; /* current time */ + +unsigned char myid[8]; /* unique id (mac address of an interface) */ +int debug = 0; + +int resend_delay = -1; + +const unsigned char zeroes[16] = {0}; +const unsigned char ones[16] = + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +static char state_file[1024]; + +unsigned char protocol_group[16]; /* babel's link-local multicast address */ +int protocol_port; /* babel's port */ +int protocol_socket = -1; /* socket: communicate with others babeld */ + +static char *babel_vty_addr = NULL; +static int babel_vty_port = BABEL_VTY_PORT; + +/* babeld privileges */ +static zebra_capabilities_t _caps_p [] = +{ + ZCAP_NET_RAW, + ZCAP_BIND +}; + +struct zebra_privs_t babeld_privs = +{ +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + +static void +babel_sigexit(void) +{ + zlog_notice("Terminating on signal"); + + babel_exit_properly(); +} + +static void +babel_sigusr1 (void) +{ + zlog_rotate (); +} + +static struct frr_signal_t babel_signals[] = + { + { + .signal = SIGUSR1, + .handler = &babel_sigusr1, + }, + { + .signal = SIGINT, + .handler = &babel_sigexit, + }, + { + .signal = SIGTERM, + .handler = &babel_sigexit, + }, + }; + +struct option longopts[] = + { + { 0 } + }; + +static const struct frr_yang_module_info *const babeld_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(babeld, BABELD, + .vty_port = BABEL_VTY_PORT, + .proghelp = "Implementation of the BABEL routing protocol.", + + .signals = babel_signals, + .n_signals = array_size(babel_signals), + + .privs = &babeld_privs, + + .yang_modules = babeld_yang_modules, + .n_yang_modules = array_size(babeld_yang_modules), +); +/* clang-format on */ + +int +main(int argc, char **argv) +{ + int rc; + + frr_preinit (&babeld_di, argc, argv); + frr_opt_add ("", longopts, ""); + + babel_init_random(); + + /* set the Babel's default link-local multicast address and Babel's port */ + parse_address("ff02:0:0:0:0:0:1:6", protocol_group, NULL); + protocol_port = 6696; + + /* get options */ + while(1) { + int opt; + + opt = frr_getopt (argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) + { + case 0: + break; + default: + frr_help_exit(1); + } + } + + snprintf(state_file, sizeof(state_file), "%s/%s", frr_runstatedir, + "babel-state"); + + /* create the threads handler */ + master = frr_init (); + + /* Library inits. */ + babel_error_init(); + + resend_delay = BABEL_DEFAULT_RESEND_DELAY; + change_smoothing_half_life(BABEL_DEFAULT_SMOOTHING_HALF_LIFE); + + /* init some quagga's dependencies, and babeld's commands */ + hook_register_prio(if_real, 0, babel_ifp_create); + hook_register_prio(if_up, 0, babel_ifp_up); + hook_register_prio(if_down, 0, babel_ifp_down); + hook_register_prio(if_unreal, 0, babel_ifp_destroy); + babeld_quagga_init(); + /* init zebra client's structure and it's commands */ + /* this replace kernel_setup && kernel_setup_socket */ + babelz_zebra_init (); + + /* init buffer */ + rc = resize_receive_buffer(1500); + if(rc < 0) + babel_fail(); + + schedule_neighbours_check(5000, 1); + + frr_config_fork(); + frr_run(master); + + return 0; +} + +static void +babel_fail(void) +{ + exit(1); +} + +/* initialize random value, and set 'babel_now' by the way. */ +static void +babel_init_random(void) +{ + gettime(&babel_now); + int rc; + unsigned int seed; + + rc = read_random_bytes(&seed, sizeof(seed)); + if(rc < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, "read(random): %s", + safe_strerror(errno)); + seed = 42; + } + + seed ^= (babel_now.tv_sec ^ babel_now.tv_usec); + srandom(seed); +} + +/* + Load the state file: check last babeld's running state, usefull in case of + "/etc/init.d/babeld restart" + */ +void +babel_load_state_file(void) +{ + int fd; + int rc; + + fd = open(state_file, O_RDONLY); + if(fd < 0 && errno != ENOENT) + flog_err_sys(EC_LIB_SYSTEM_CALL, "open(babel-state: %s)", + safe_strerror(errno)); + rc = unlink(state_file); + if(fd >= 0 && rc < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, "unlink(babel-state): %s", + safe_strerror(errno)); + /* If we couldn't unlink it, it's probably stale. */ + goto fini; + } + if(fd >= 0) { + char buf[100]; + char buf2[100]; + int s; + long t; + rc = read(fd, buf, 99); + if(rc < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, "read(babel-state): %s", + safe_strerror(errno)); + } else { + buf[rc] = '\0'; + rc = sscanf(buf, "%99s %d %ld\n", buf2, &s, &t); + if(rc == 3 && s >= 0 && s <= 0xFFFF) { + unsigned char sid[8]; + rc = parse_eui64(buf2, sid); + if(rc < 0) { + flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state."); + } else { + struct timeval realnow; + debugf(BABEL_DEBUG_COMMON, + "Got %s %d %ld from babel-state.", + format_eui64(sid), s, t); + gettimeofday(&realnow, NULL); + if(memcmp(sid, myid, 8) == 0) + myseqno = seqno_plus(s, 1); + else + flog_err(EC_BABEL_CONFIG, + "ID mismatch in babel-state. id=%s; old=%s", + format_eui64(myid), + format_eui64(sid)); + } + } else { + flog_err(EC_BABEL_CONFIG, "Couldn't parse babel-state."); + } + } + goto fini; + } +fini: + if (fd >= 0) + close(fd); + return ; +} + +static void +babel_exit_properly(void) +{ + debugf(BABEL_DEBUG_COMMON, "Exiting..."); + usleep(roughly(10000)); + gettime(&babel_now); + + /* Uninstall and flush all routes. */ + debugf(BABEL_DEBUG_COMMON, "Uninstall routes."); + flush_all_routes(); + babel_interface_close_all(); + babel_zebra_close_connexion(); + babel_save_state_file(); + debugf(BABEL_DEBUG_COMMON, "Remove pid file."); + debugf(BABEL_DEBUG_COMMON, "Done."); + + vrf_terminate(); + frr_fini(); + + exit(0); +} + +static void +babel_save_state_file(void) +{ + int fd; + int rc; + + debugf(BABEL_DEBUG_COMMON, "Save state file."); + fd = open(state_file, O_WRONLY | O_TRUNC | O_CREAT, 0644); + if(fd < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, "creat(babel-state): %s", + safe_strerror(errno)); + unlink(state_file); + } else { + struct timeval realnow; + char buf[100]; + gettimeofday(&realnow, NULL); + rc = snprintf(buf, 100, "%s %d %ld\n", + format_eui64(myid), (int)myseqno, + (long)realnow.tv_sec); + if(rc < 0 || rc >= 100) { + flog_err(EC_BABEL_CONFIG, "write(babel-state): overflow."); + unlink(state_file); + } else { + rc = write(fd, buf, rc); + if(rc < 0) { + flog_err(EC_BABEL_CONFIG, "write(babel-state): %s", + safe_strerror(errno)); + unlink(state_file); + } + fsync(fd); + } + close(fd); + } +} + +void +show_babel_main_configuration (struct vty *vty) +{ + vty_out (vty, + "state file = %s\n" + "configuration file = %s\n" + "protocol information:\n" + " multicast address = %s\n" + " port = %d\n" + "vty address = %s\n" + "vty port = %d\n" + "id = %s\n" + "kernel_metric = %d\n", + state_file, + babeld_di.config_file, + format_address(protocol_group), + protocol_port, + babel_vty_addr ? babel_vty_addr : "None", + babel_vty_port, + format_eui64(myid), + kernel_metric); +} diff --git a/babeld/babel_main.h b/babeld/babel_main.h new file mode 100644 index 0000000..0f9792b --- /dev/null +++ b/babeld/babel_main.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_MAIN_H +#define BABEL_MAIN_H + +#include "vty.h" + +extern struct timeval babel_now; /* current time */ +extern struct event_loop *master; /* quagga's threads handler */ +extern int debug; +extern int resend_delay; + +extern unsigned char myid[8]; + +extern const unsigned char zeroes[16], ones[16]; + +extern int protocol_port; +extern unsigned char protocol_group[16]; +extern int protocol_socket; +extern int kernel_socket; +extern int max_request_hopcount; + +void babel_load_state_file(void); +void show_babel_main_configuration (struct vty *vty); + +#endif /* BABEL_MAIN_H */ diff --git a/babeld/babel_zebra.c b/babeld/babel_zebra.c new file mode 100644 index 0000000..bead9f2 --- /dev/null +++ b/babeld/babel_zebra.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +/* FRR's includes */ +#include +#include "command.h" +#include "zclient.h" +#include "stream.h" + +/* babel's includes*/ +#include "babel_zebra.h" +#include "babel_interface.h" +#include "xroute.h" +#include "util.h" + +void babelz_zebra_init(void); + + +/* we must use a pointer because of zclient.c's functions (new, free). */ +struct zclient *zclient; + +/* Debug types */ +static const struct { + int type; + int str_min_len; + const char *str; +} debug_type[] = { + {BABEL_DEBUG_COMMON, 1, "common"}, + {BABEL_DEBUG_KERNEL, 1, "kernel"}, + {BABEL_DEBUG_FILTER, 1, "filter"}, + {BABEL_DEBUG_TIMEOUT, 1, "timeout"}, + {BABEL_DEBUG_IF, 1, "interface"}, + {BABEL_DEBUG_ROUTE, 1, "route"}, + {BABEL_DEBUG_ALL, 1, "all"}, + {0, 0, NULL} +}; + +/* Zebra route add and delete treatment. */ +static int +babel_zebra_read_route (ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { + babel_route_add(&api); + } else { + babel_route_delete(&api); + } + + return 0; +} + +/* [Babel Command] */ +DEFUN (babel_redistribute_type, + babel_redistribute_type_cmd, + "[no] redistribute ", + NO_STR + "Redistribute\n" + "Redistribute IPv4 routes\n" + FRR_IP_REDIST_HELP_STR_BABELD + "Redistribute IPv6 routes\n" + FRR_IP6_REDIST_HELP_STR_BABELD) +{ + int negate = 0; + int family; + int afi; + int type; + int idx = 0; + + if (argv_find(argv, argc, "no", &idx)) + negate = 1; + argv_find(argv, argc, "redistribute", &idx); + family = str2family(argv[idx + 1]->text); + if (family < 0) + return CMD_WARNING_CONFIG_FAILED; + + afi = family2afi(family); + if (!afi) + return CMD_WARNING_CONFIG_FAILED; + + type = proto_redistnum(afi, argv[idx + 2]->text); + if (type < 0) { + vty_out (vty, "Invalid type %s\n", argv[idx + 2]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!negate) + zclient_redistribute (ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, 0, VRF_DEFAULT); + else { + zclient_redistribute (ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, 0, VRF_DEFAULT); + /* perhaps should we remove xroutes having the same type... */ + } + return CMD_SUCCESS; +} + +#ifndef NO_DEBUG +/* [Babel Command] */ +DEFUN (debug_babel, + debug_babel_cmd, + "debug babel ", + "Enable debug messages for specific or all part.\n" + "Babel information\n" + "Common messages (default)\n" + "Kernel messages\n" + "Filter messages\n" + "Timeout messages\n" + "Interface messages\n" + "Route messages\n" + "All messages\n") +{ + int i; + + for(i = 0; debug_type[i].str != NULL; i++) { + if (strncmp (debug_type[i].str, argv[2]->arg, + debug_type[i].str_min_len) == 0) { + SET_FLAG(debug, debug_type[i].type); + return CMD_SUCCESS; + } + } + + vty_out (vty, "Invalid type %s\n", argv[2]->arg); + + return CMD_WARNING_CONFIG_FAILED; +} + +/* [Babel Command] */ +DEFUN (no_debug_babel, + no_debug_babel_cmd, + "no debug babel ", + NO_STR + "Disable debug messages for specific or all part.\n" + "Babel information\n" + "Common messages (default)\n" + "Kernel messages\n" + "Filter messages\n" + "Timeout messages\n" + "Interface messages\n" + "Route messages\n" + "All messages\n") +{ + int i; + + for (i = 0; debug_type[i].str; i++) { + if (strncmp(debug_type[i].str, argv[3]->arg, + debug_type[i].str_min_len) == 0) { + UNSET_FLAG(debug, debug_type[i].type); + return CMD_SUCCESS; + } + } + + vty_out (vty, "Invalid type %s\n", argv[3]->arg); + + return CMD_WARNING_CONFIG_FAILED; +} +#endif /* NO_DEBUG */ + +/* Output "debug" statement lines, if necessary. */ +int +debug_babel_config_write (struct vty * vty) +{ +#ifdef NO_DEBUG + return 0; +#else + int i, lines = 0; + + if (debug == BABEL_DEBUG_ALL) + { + vty_out (vty, "debug babel all\n"); + lines++; + } + else + { + for (i = 0; debug_type[i].str != NULL; i++) + { + if (debug_type[i].type != BABEL_DEBUG_ALL + && CHECK_FLAG (debug, debug_type[i].type)) + { + vty_out (vty, "debug babel %s\n", debug_type[i].str); + lines++; + } + } + } + + if (lines) + { + vty_out (vty, "!\n"); + lines++; + } + return lines; +#endif /* NO_DEBUG */ +} + +DEFUN_NOSH (show_debugging_babel, + show_debugging_babel_cmd, + "show debugging [babel]", + SHOW_STR + DEBUG_STR + "Babel") +{ + vty_out(vty, "BABEL debugging status\n"); + + debug_babel_config_write(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static void +babel_zebra_connected (struct zclient *zclient) +{ + zclient_send_reg_requests (zclient, VRF_DEFAULT); +} + +static zclient_handler *const babel_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = babel_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = babel_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = babel_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = babel_zebra_read_route, +}; + +void babelz_zebra_init(void) +{ + zclient = zclient_new(master, &zclient_options_default, babel_handlers, + array_size(babel_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_BABEL, 0, &babeld_privs); + + zclient->zebra_connected = babel_zebra_connected; + + install_element(BABEL_NODE, &babel_redistribute_type_cmd); + install_element(ENABLE_NODE, &debug_babel_cmd); + install_element(ENABLE_NODE, &no_debug_babel_cmd); + install_element(CONFIG_NODE, &debug_babel_cmd); + install_element(CONFIG_NODE, &no_debug_babel_cmd); + + install_element(ENABLE_NODE, &show_debugging_babel_cmd); +} + +void +babel_zebra_close_connexion(void) +{ + zclient_stop(zclient); + zclient_free(zclient); +} diff --git a/babeld/babel_zebra.h b/babeld/babel_zebra.h new file mode 100644 index 0000000..7f960d3 --- /dev/null +++ b/babeld/babel_zebra.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_ZEBRA_H +#define BABEL_ZEBRA_H + +#include "vty.h" + +extern struct zclient *zclient; + +void babelz_zebra_init(void); +void babel_zebra_close_connexion(void); +extern int debug_babel_config_write (struct vty *); + +#endif diff --git a/babeld/babeld.c b/babeld/babeld.c new file mode 100644 index 0000000..6f0a5a7 --- /dev/null +++ b/babeld/babeld.c @@ -0,0 +1,896 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#include +#include "command.h" +#include "prefix.h" +#include "memory.h" +#include "table.h" +#include "distribute.h" +#include "prefix.h" +#include "filter.h" +#include "plist.h" +#include "lib_errors.h" +#include "network.h" +#include "if.h" + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" +#include "net.h" +#include "kernel.h" +#include "babel_interface.h" +#include "neighbour.h" +#include "route.h" +#include "message.h" +#include "resend.h" +#include "babel_filter.h" +#include "babel_zebra.h" +#include "babel_errors.h" + +#ifndef VTYSH_EXTRACT_PL +#include "babeld/babeld_clippy.c" +#endif + +DEFINE_MGROUP(BABELD, "babeld"); +DEFINE_MTYPE_STATIC(BABELD, BABEL, "Babel Structure"); + +static void babel_init_routing_process(struct event *thread); +static void babel_get_myid(void); +static void babel_initial_noise(void); +static void babel_read_protocol(struct event *thread); +static void babel_main_loop(struct event *thread); +static void babel_set_timer(struct timeval *timeout); +static void babel_fill_with_next_timeout(struct timeval *tv); +static void +babel_distribute_update (struct distribute_ctx *ctx, struct distribute *dist); + +/* Informations relative to the babel running daemon. */ +static struct babel *babel_routing_process = NULL; +static unsigned char *receive_buffer = NULL; +static int receive_buffer_size = 0; + +/* timeouts */ +struct timeval check_neighbours_timeout; +static time_t expiry_time; +static time_t source_expiry_time; + +/* Babel node structure. */ +static int babel_config_write (struct vty *vty); +static struct cmd_node cmd_babel_node = +{ + .name = "babel", + .node = BABEL_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = babel_config_write, +}; + +/* print current babel configuration on vty */ +static int +babel_config_write (struct vty *vty) +{ + int lines = 0; + int afi; + int i; + + /* list enabled debug modes */ + lines += debug_babel_config_write (vty); + + if (!babel_routing_process) + return lines; + vty_out (vty, "router babel\n"); + if (diversity_kind != DIVERSITY_NONE) + { + vty_out (vty, " babel diversity\n"); + lines++; + } + if (diversity_factor != BABEL_DEFAULT_DIVERSITY_FACTOR) + { + vty_out (vty, " babel diversity-factor %d\n",diversity_factor); + lines++; + } + if (resend_delay != BABEL_DEFAULT_RESEND_DELAY) + { + vty_out (vty, " babel resend-delay %u\n", resend_delay); + lines++; + } + if (smoothing_half_life != BABEL_DEFAULT_SMOOTHING_HALF_LIFE) + { + vty_out (vty, " babel smoothing-half-life %u\n", + smoothing_half_life); + lines++; + } + /* list enabled interfaces */ + lines = 1 + babel_enable_if_config_write (vty); + /* list redistributed protocols */ + for (afi = AFI_IP; afi <= AFI_IP6; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (i != zclient->redist_default && + vrf_bitmap_check(&zclient->redist[afi][i], VRF_DEFAULT)) { + vty_out(vty, " redistribute %s %s\n", + (afi == AFI_IP) ? "ipv4" : "ipv6", + zebra_route_string(i)); + lines++; + } + } + } + + lines += config_write_distribute (vty, babel_routing_process->distribute_ctx); + + vty_out (vty, "exit\n"); + + return lines; +} + + +static int +babel_create_routing_process (void) +{ + assert (babel_routing_process == NULL); + + /* Allocaste Babel instance. */ + babel_routing_process = XCALLOC(MTYPE_BABEL, sizeof(struct babel)); + + /* Initialize timeouts */ + gettime(&babel_now); + expiry_time = babel_now.tv_sec + roughly(30); + source_expiry_time = babel_now.tv_sec + roughly(300); + + /* Make socket for Babel protocol. */ + protocol_socket = babel_socket(protocol_port); + if (protocol_socket < 0) { + flog_err_sys(EC_LIB_SOCKET, "Couldn't create link local socket: %s", + safe_strerror(errno)); + goto fail; + } + + /* Threads. */ + event_add_read(master, babel_read_protocol, NULL, protocol_socket, + &babel_routing_process->t_read); + /* wait a little: zebra will announce interfaces, addresses, routes... */ + event_add_timer_msec(master, babel_init_routing_process, NULL, 200L, + &babel_routing_process->t_update); + + /* Distribute list install. */ + babel_routing_process->distribute_ctx = distribute_list_ctx_create (vrf_lookup_by_id(VRF_DEFAULT)); + distribute_list_add_hook (babel_routing_process->distribute_ctx, babel_distribute_update); + distribute_list_delete_hook (babel_routing_process->distribute_ctx, babel_distribute_update); + return 0; +fail: + XFREE(MTYPE_BABEL, babel_routing_process); + return -1; +} + +/* thread reading entries form others babel daemons */ +static void babel_read_protocol(struct event *thread) +{ + int rc; + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + struct sockaddr_in6 sin6; + + assert(babel_routing_process != NULL); + assert(protocol_socket >= 0); + + rc = babel_recv(protocol_socket, + receive_buffer, receive_buffer_size, + (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) { + if(errno != EAGAIN && errno != EINTR) { + flog_err_sys(EC_LIB_SOCKET, "recv: %s", safe_strerror(errno)); + } + } else { + FOR_ALL_INTERFACES(vrf, ifp) { + if(!if_up(ifp)) + continue; + if(ifp->ifindex == (ifindex_t)sin6.sin6_scope_id) { + parse_packet((unsigned char*)&sin6.sin6_addr, ifp, + receive_buffer, rc); + break; + } + } + } + + /* re-add thread */ + event_add_read(master, &babel_read_protocol, NULL, protocol_socket, + &babel_routing_process->t_read); +} + +/* Zebra will give some information, especially about interfaces. This function + must be call with a litte timeout wich may give zebra the time to do his job, + making these inits have sense. */ +static void babel_init_routing_process(struct event *thread) +{ + myseqno = (frr_weak_random() & 0xFFFF); + babel_get_myid(); + babel_load_state_file(); + debugf(BABEL_DEBUG_COMMON, "My ID is : %s.", format_eui64(myid)); + babel_initial_noise(); + babel_main_loop(thread);/* this function self-add to the t_update thread */ +} + +/* fill "myid" with an unique id (only if myid != {0}). */ +static void +babel_get_myid(void) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + int rc; + int i; + + /* if we already have an id (from state file), we return. */ + if (memcmp(myid, zeroes, 8) != 0) { + return; + } + + FOR_ALL_INTERFACES(vrf, ifp) { + /* ifp->ifindex is not necessarily valid at this point */ + int ifindex = if_nametoindex(ifp->name); + if(ifindex > 0) { + unsigned char eui[8]; + rc = if_eui64(ifindex, eui); + if(rc < 0) + continue; + memcpy(myid, eui, 8); + return; + } + } + + /* We failed to get a global EUI64 from the interfaces we were given. + Let's try to find an interface with a MAC address. */ + for(i = 1; i < 256; i++) { + char buf[IFNAMSIZ], *ifname; + unsigned char eui[8]; + ifname = if_indextoname(i, buf); + if(ifname == NULL) + continue; + rc = if_eui64(i, eui); + if(rc < 0) + continue; + memcpy(myid, eui, 8); + return; + } + + flog_err(EC_BABEL_CONFIG, "Couldn't find router id -- using random value."); + + rc = read_random_bytes(myid, 8); + if(rc < 0) { + flog_err(EC_BABEL_CONFIG, "read(random): %s (cannot assign an ID)", + safe_strerror(errno)); + exit(1); + } + /* Clear group and global bits */ + UNSET_FLAG (myid[0], 3); +} + +/* Make some noise so that others notice us, and send retractions in + case we were restarted recently */ +static void +babel_initial_noise(void) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + + FOR_ALL_INTERFACES(vrf, ifp) { + if(!if_up(ifp)) + continue; + /* Apply jitter before we send the first message. */ + usleep(roughly(10000)); + gettime(&babel_now); + send_hello(ifp); + send_wildcard_retraction(ifp); + } + + FOR_ALL_INTERFACES(vrf, ifp) { + if(!if_up(ifp)) + continue; + usleep(roughly(10000)); + gettime(&babel_now); + send_hello(ifp); + send_wildcard_retraction(ifp); + send_self_update(ifp); + send_request(ifp, NULL, 0); + flushupdates(ifp); + flushbuf(ifp); + } +} + +/* Delete all the added babel routes, make babeld only speak to zebra. */ +static void +babel_clean_routing_process(void) +{ + flush_all_routes(); + babel_interface_close_all(); + + /* cancel events */ + event_cancel(&babel_routing_process->t_read); + event_cancel(&babel_routing_process->t_update); + + distribute_list_delete(&babel_routing_process->distribute_ctx); + XFREE(MTYPE_BABEL, babel_routing_process); +} + +/* Function used with timeout. */ +static void babel_main_loop(struct event *thread) +{ + struct timeval tv; + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + + while(1) { + gettime(&babel_now); + + /* timeouts --------------------------------------------------------- */ + /* get the next timeout */ + babel_fill_with_next_timeout(&tv); + /* if there is no timeout, we must wait. */ + if(timeval_compare(&tv, &babel_now) > 0) { + timeval_minus(&tv, &tv, &babel_now); + debugf(BABEL_DEBUG_TIMEOUT, "babel main loop : timeout: %lld msecs", + (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000); + /* it happens often to have less than 1 ms, it's bad. */ + timeval_add_msec(&tv, &tv, 300); + babel_set_timer(&tv); + return; + } + + gettime(&babel_now); + + /* update database -------------------------------------------------- */ + if(timeval_compare(&check_neighbours_timeout, &babel_now) < 0) { + int msecs; + msecs = check_neighbours(); + /* Multiply by 3/2 to allow neighbours to expire. */ + msecs = MAX(3 * msecs / 2, 10); + schedule_neighbours_check(msecs, 1); + } + + if(babel_now.tv_sec >= expiry_time) { + expire_routes(); + expire_resend(); + expiry_time = babel_now.tv_sec + roughly(30); + } + + if(babel_now.tv_sec >= source_expiry_time) { + expire_sources(); + source_expiry_time = babel_now.tv_sec + roughly(300); + } + + FOR_ALL_INTERFACES(vrf, ifp) { + babel_interface_nfo *babel_ifp = NULL; + if(!if_up(ifp)) + continue; + babel_ifp = babel_get_if_nfo(ifp); + if(timeval_compare(&babel_now, &babel_ifp->hello_timeout) >= 0) + send_hello(ifp); + if(timeval_compare(&babel_now, &babel_ifp->update_timeout) >= 0) + send_update(ifp, 0, NULL, 0); + if(timeval_compare(&babel_now, + &babel_ifp->update_flush_timeout) >= 0) + flushupdates(ifp); + } + + if(resend_time.tv_sec != 0) { + if(timeval_compare(&babel_now, &resend_time) >= 0) + do_resend(); + } + + if(unicast_flush_timeout.tv_sec != 0) { + if(timeval_compare(&babel_now, &unicast_flush_timeout) >= 0) + flush_unicast(1); + } + + FOR_ALL_INTERFACES(vrf, ifp) { + babel_interface_nfo *babel_ifp = NULL; + if(!if_up(ifp)) + continue; + babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->flush_timeout.tv_sec != 0) { + if(timeval_compare(&babel_now, &babel_ifp->flush_timeout) >= 0) + flushbuf(ifp); + } + } + } + + assert(0); /* this line should never be reach */ +} + +static void +printIfMin(struct timeval *tv, int cmd, const char *tag, const char *ifname) +{ + static struct timeval curr_tv; + static char buffer[200]; + static const char *curr_tag = NULL; + + switch (cmd) { + case 0: /* reset timeval */ + curr_tv = *tv; + if(ifname != NULL) { + snprintf(buffer, 200L, "interface: %s; %s", ifname, tag); + curr_tag = buffer; + } else { + curr_tag = tag; + } + break; + case 1: /* take the min */ + if (tv->tv_sec == 0 && tv->tv_usec == 0) { /* if (tv == ∞) */ + break; + } + if (tv->tv_sec < curr_tv.tv_sec ||(tv->tv_sec == curr_tv.tv_sec && + tv->tv_usec < curr_tv.tv_usec)) { + curr_tv = *tv; + if(ifname != NULL) { + snprintf(buffer, 200L, "interface: %s; %s", ifname, tag); + curr_tag = buffer; + } else { + curr_tag = tag; + } + } + break; + case 2: /* print message */ + debugf(BABEL_DEBUG_TIMEOUT, "next timeout due to: %s", curr_tag); + break; + default: + break; + } +} + +static void +babel_fill_with_next_timeout(struct timeval *tv) +{ +#if (defined NO_DEBUG) +#define printIfMin(a,b,c,d) +#else +#define printIfMin(a, b, c, d) \ + if (unlikely(debug & BABEL_DEBUG_TIMEOUT)) { \ + printIfMin(a, b, c, d); \ + } + + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp = NULL; + + *tv = check_neighbours_timeout; + printIfMin(tv, 0, "check_neighbours_timeout", NULL); + timeval_min_sec(tv, expiry_time); + printIfMin(tv, 1, "expiry_time", NULL); + timeval_min_sec(tv, source_expiry_time); + printIfMin(tv, 1, "source_expiry_time", NULL); + timeval_min(tv, &resend_time); + printIfMin(tv, 1, "resend_time", NULL); + FOR_ALL_INTERFACES (vrf, ifp) { + babel_interface_nfo *babel_ifp = NULL; + if (!if_up(ifp)) + continue; + babel_ifp = babel_get_if_nfo(ifp); + timeval_min(tv, &babel_ifp->flush_timeout); + printIfMin(tv, 1, "flush_timeout", ifp->name); + timeval_min(tv, &babel_ifp->hello_timeout); + printIfMin(tv, 1, "hello_timeout", ifp->name); + timeval_min(tv, &babel_ifp->update_timeout); + printIfMin(tv, 1, "update_timeout", ifp->name); + timeval_min(tv, &babel_ifp->update_flush_timeout); + printIfMin(tv, 1, "update_flush_timeout", ifp->name); + } + timeval_min(tv, &unicast_flush_timeout); + printIfMin(tv, 1, "unicast_flush_timeout", NULL); + printIfMin(tv, 2, NULL, NULL); +#undef printIfMin +#endif +} + +/* set the t_update thread of the babel routing process to be launch in + 'timeout' (approximate at the milisecond) */ +static void +babel_set_timer(struct timeval *timeout) +{ + long msecs = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + event_cancel(&(babel_routing_process->t_update)); + event_add_timer_msec(master, babel_main_loop, NULL, msecs, + &babel_routing_process->t_update); +} + +void +schedule_neighbours_check(int msecs, int override) +{ + struct timeval timeout; + + timeval_add_msec(&timeout, &babel_now, msecs); + if(override) + check_neighbours_timeout = timeout; + else + timeval_min(&check_neighbours_timeout, &timeout); +} + +int +resize_receive_buffer(int size) +{ + if(size <= receive_buffer_size) + return 0; + + if(receive_buffer == NULL) { + receive_buffer = malloc(size); + if(receive_buffer == NULL) { + flog_err(EC_BABEL_MEMORY, "malloc(receive_buffer): %s", + safe_strerror(errno)); + return -1; + } + receive_buffer_size = size; + } else { + unsigned char *new; + new = realloc(receive_buffer, size); + if(new == NULL) { + flog_err(EC_BABEL_MEMORY, "realloc(receive_buffer): %s", + safe_strerror(errno)); + return -1; + } + receive_buffer = new; + receive_buffer_size = size; + } + return 1; +} + +static void +babel_distribute_update (struct distribute_ctx *ctx, struct distribute *dist) +{ + struct interface *ifp; + babel_interface_nfo *babel_ifp; + int type; + int family; + + if (! dist->ifname) + return; + + ifp = if_lookup_by_name (dist->ifname, VRF_DEFAULT); + if (ifp == NULL) + return; + + babel_ifp = babel_get_if_nfo(ifp); + + for (type = 0; type < DISTRIBUTE_MAX; type++) { + family = type == DISTRIBUTE_V4_IN || type == DISTRIBUTE_V4_OUT ? + AFI_IP : AFI_IP6; + if (dist->list[type]) + babel_ifp->list[type] = access_list_lookup (family, + dist->list[type]); + else + babel_ifp->list[type] = NULL; + if (dist->prefix[type]) + babel_ifp->prefix[type] = prefix_list_lookup (family, + dist->prefix[type]); + else + babel_ifp->prefix[type] = NULL; + } +} + +static void +babel_distribute_update_interface (struct interface *ifp) +{ + struct distribute *dist = NULL; + + if (babel_routing_process) + dist = distribute_lookup(babel_routing_process->distribute_ctx, ifp->name); + if (dist) + babel_distribute_update (babel_routing_process->distribute_ctx, dist); +} + +/* Update all interface's distribute list. */ +static void +babel_distribute_update_all (struct prefix_list *notused) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) + babel_distribute_update_interface (ifp); +} + +static void +babel_distribute_update_all_wrapper (struct access_list *notused) +{ + babel_distribute_update_all(NULL); +} + + +/* [Command] */ +DEFUN_NOSH (router_babel, + router_babel_cmd, + "router babel", + "Enable a routing process\n" + "Make Babel instance command\n") +{ + int ret; + + vty->node = BABEL_NODE; + + if (!babel_routing_process) { + ret = babel_create_routing_process (); + + /* Notice to user we couldn't create Babel. */ + if (ret < 0) { + zlog_warn ("can't create Babel"); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +/* [Command] */ +DEFUN (no_router_babel, + no_router_babel_cmd, + "no router babel", + NO_STR + "Disable a routing process\n" + "Remove Babel instance command\n") +{ + if(babel_routing_process) + babel_clean_routing_process(); + return CMD_SUCCESS; +} + +/* [Babel Command] */ +DEFUN (babel_diversity, + babel_diversity_cmd, + "babel diversity", + "Babel commands\n" + "Enable diversity-aware routing.\n") +{ + diversity_kind = DIVERSITY_CHANNEL; + return CMD_SUCCESS; +} + +/* [Babel Command] */ +DEFUN (no_babel_diversity, + no_babel_diversity_cmd, + "no babel diversity", + NO_STR + "Babel commands\n" + "Disable diversity-aware routing.\n") +{ + diversity_kind = DIVERSITY_NONE; + return CMD_SUCCESS; +} + +/* [Babel Command] */ +DEFPY (babel_diversity_factor, + babel_diversity_factor_cmd, + "[no] babel diversity-factor (1-256)$factor", + NO_STR + "Babel commands\n" + "Set the diversity factor.\n" + "Factor in units of 1/256.\n") +{ + diversity_factor = no ? BABEL_DEFAULT_DIVERSITY_FACTOR : factor; + return CMD_SUCCESS; +} + +/* [Babel Command] */ +DEFPY (babel_set_resend_delay, + babel_set_resend_delay_cmd, + "[no] babel resend-delay (20-655340)$delay", + NO_STR + "Babel commands\n" + "Time before resending a message\n" + "Milliseconds\n") +{ + resend_delay = no ? BABEL_DEFAULT_RESEND_DELAY : delay; + return CMD_SUCCESS; +} + +/* [Babel Command] */ +DEFPY (babel_set_smoothing_half_life, + babel_set_smoothing_half_life_cmd, + "[no] babel smoothing-half-life (0-65534)$seconds", + NO_STR + "Babel commands\n" + "Smoothing half-life\n" + "Seconds (0 to disable)\n") +{ + change_smoothing_half_life(no ? BABEL_DEFAULT_SMOOTHING_HALF_LIFE + : seconds); + return CMD_SUCCESS; +} + +DEFUN (babel_distribute_list, + babel_distribute_list_cmd, + "distribute-list ACCESSLIST4_NAME [WORD]", + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const char *ifname = NULL; + int prefix = (argv[1]->type == WORD_TKN) ? 1 : 0; + + if (argv[argc - 1]->type == VARIABLE_TKN) + ifname = argv[argc - 1]->arg; + + return distribute_list_parser(babel_routing_process->distribute_ctx, + prefix, true, argv[2 + prefix]->text, + argv[1 + prefix]->arg, ifname); +} + +ALIAS (babel_distribute_list, + babel_distribute_list_prefix_cmd, + "distribute-list prefix PREFIXLIST4_NAME [WORD]", + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") + +DEFUN (babel_no_distribute_list, + babel_no_distribute_list_cmd, + "no distribute-list ACCESSLIST4_NAME [WORD]", + NO_STR + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const char *ifname = NULL; + int prefix = (argv[2]->type == WORD_TKN) ? 1 : 0; + + if (argv[argc - 1]->type == VARIABLE_TKN) + ifname = argv[argc - 1]->arg; + + return distribute_list_no_parser(babel_routing_process->distribute_ctx, + vty, prefix, true, + argv[3 + prefix]->text, + argv[2 + prefix]->arg, ifname); +} + +ALIAS (babel_no_distribute_list, + babel_no_distribute_list_prefix_cmd, + "no distribute-list prefix PREFIXLIST4_NAME [WORD]", + NO_STR + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") + +DEFUN (babel_ipv6_distribute_list, + babel_ipv6_distribute_list_cmd, + "ipv6 distribute-list ACCESSLIST6_NAME [WORD]", + "IPv6\n" + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const char *ifname = NULL; + int prefix = (argv[2]->type == WORD_TKN) ? 1 : 0; + + if (argv[argc - 1]->type == VARIABLE_TKN) + ifname = argv[argc - 1]->arg; + + return distribute_list_parser(babel_routing_process->distribute_ctx, + prefix, false, argv[3 + prefix]->text, + argv[2 + prefix]->arg, ifname); +} + +ALIAS (babel_ipv6_distribute_list, + babel_ipv6_distribute_list_prefix_cmd, + "ipv6 distribute-list prefix PREFIXLIST6_NAME [WORD]", + "IPv6\n" + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") + +DEFUN (babel_no_ipv6_distribute_list, + babel_no_ipv6_distribute_list_cmd, + "no ipv6 distribute-list ACCESSLIST6_NAME [WORD]", + NO_STR + "IPv6\n" + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const char *ifname = NULL; + int prefix = (argv[3]->type == WORD_TKN) ? 1 : 0; + + if (argv[argc - 1]->type == VARIABLE_TKN) + ifname = argv[argc - 1]->arg; + + return distribute_list_no_parser(babel_routing_process->distribute_ctx, + vty, prefix, false, + argv[4 + prefix]->text, + argv[3 + prefix]->arg, ifname); +} + +ALIAS (babel_no_ipv6_distribute_list, + babel_no_ipv6_distribute_list_prefix_cmd, + "no ipv6 distribute-list prefix PREFIXLIST6_NAME [WORD]", + NO_STR + "IPv6\n" + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") + +void +babeld_quagga_init(void) +{ + + install_node(&cmd_babel_node); + + install_element(CONFIG_NODE, &router_babel_cmd); + install_element(CONFIG_NODE, &no_router_babel_cmd); + + install_default(BABEL_NODE); + install_element(BABEL_NODE, &babel_diversity_cmd); + install_element(BABEL_NODE, &no_babel_diversity_cmd); + install_element(BABEL_NODE, &babel_diversity_factor_cmd); + install_element(BABEL_NODE, &babel_set_resend_delay_cmd); + install_element(BABEL_NODE, &babel_set_smoothing_half_life_cmd); + + install_element(BABEL_NODE, &babel_distribute_list_cmd); + install_element(BABEL_NODE, &babel_distribute_list_prefix_cmd); + install_element(BABEL_NODE, &babel_no_distribute_list_cmd); + install_element(BABEL_NODE, &babel_no_distribute_list_prefix_cmd); + install_element(BABEL_NODE, &babel_ipv6_distribute_list_cmd); + install_element(BABEL_NODE, &babel_ipv6_distribute_list_prefix_cmd); + install_element(BABEL_NODE, &babel_no_ipv6_distribute_list_cmd); + install_element(BABEL_NODE, &babel_no_ipv6_distribute_list_prefix_cmd); + + vrf_cmd_init(NULL); + + babel_if_init(); + + /* Access list install. */ + access_list_init (); + access_list_add_hook (babel_distribute_update_all_wrapper); + access_list_delete_hook (babel_distribute_update_all_wrapper); + + /* Prefix list initialize.*/ + prefix_list_init (); + prefix_list_add_hook (babel_distribute_update_all); + prefix_list_delete_hook (babel_distribute_update_all); +} + +/* Stubs to adapt Babel's filtering calls to Quagga's infrastructure. */ + +int +input_filter(const unsigned char *id, + const unsigned char *prefix, unsigned short plen, + const unsigned char *neigh, unsigned int ifindex) +{ + return babel_filter(0, prefix, plen, ifindex); +} + +int +output_filter(const unsigned char *id, const unsigned char *prefix, + unsigned short plen, unsigned int ifindex) +{ + return babel_filter(1, prefix, plen, ifindex); +} + +/* There's no redistribute filter in Quagga -- the zebra daemon does its + own filtering. */ +int +redistribute_filter(const unsigned char *prefix, unsigned short plen, + unsigned int ifindex, int proto) +{ + return 0; +} + +struct babel *babel_lookup(void) +{ + return babel_routing_process; +} diff --git a/babeld/babeld.h b/babeld/babeld.h new file mode 100644 index 0000000..5573719 --- /dev/null +++ b/babeld/babeld.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_BABELD_H +#define BABEL_BABELD_H + +#include +#include "vty.h" + +#define INFINITY ((unsigned short)(~0)) + +#ifndef RTPROT_BABEL +#define RTPROT_BABEL 42 +#endif + +#define RTPROT_BABEL_LOCAL -2 + +#undef MAX +#undef MIN + +#define MAX(x,y) ((x)<=(y)?(y):(x)) +#define MIN(x,y) ((x)<=(y)?(x):(y)) + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define ATTRIBUTE(x) __attribute__ (x) +#else +#define ATTRIBUTE(x) /**/ +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) +#define COLD __attribute__ ((cold)) +#else +#define COLD /**/ +#endif + +#ifndef IF_NAMESIZE +#include +#include +#endif + +#ifdef HAVE_VALGRIND +#include +#else +#ifndef VALGRIND_MAKE_MEM_UNDEFINED +#define VALGRIND_MAKE_MEM_UNDEFINED(a, b) do {} while(0) +#endif +#ifndef VALGRIND_CHECK_MEM_IS_DEFINED +#define VALGRIND_CHECK_MEM_IS_DEFINED(a, b) do {} while(0) +#endif +#endif + + +#define BABEL_DEFAULT_CONFIG "babeld.conf" + +/* Values in milliseconds */ +#define BABEL_DEFAULT_HELLO_INTERVAL 4000 +#define BABEL_DEFAULT_UPDATE_INTERVAL 16000 +#define BABEL_DEFAULT_RESEND_DELAY 2000 +#define BABEL_DEFAULT_RTT_DECAY 42 + +/* Values in microseconds */ +#define BABEL_DEFAULT_RTT_MIN 10000 +#define BABEL_DEFAULT_RTT_MAX 120000 + +/* In units of seconds */ +#define BABEL_DEFAULT_SMOOTHING_HALF_LIFE 4 + +/* In units of 1/256. */ +#define BABEL_DEFAULT_DIVERSITY_FACTOR 256 + +#define BABEL_DEFAULT_RXCOST_WIRED 96 +#define BABEL_DEFAULT_RXCOST_WIRELESS 256 +#define BABEL_DEFAULT_MAX_RTT_PENALTY 150 + +/* Babel structure. */ +struct babel +{ + /* Babel threads. */ + struct event *t_read; /* on Babel protocol's socket */ + struct event *t_update; /* timers */ + /* distribute_ctx */ + struct distribute_ctx *distribute_ctx; +}; + +extern struct zebra_privs_t babeld_privs; + +extern void babeld_quagga_init(void); +extern int input_filter(const unsigned char *id, + const unsigned char *prefix, unsigned short plen, + const unsigned char *neigh, unsigned int ifindex); +extern int output_filter(const unsigned char *id, const unsigned char *prefix, + unsigned short plen, unsigned int ifindex); +extern int redistribute_filter(const unsigned char *prefix, unsigned short plen, + unsigned int ifindex, int proto); +extern int resize_receive_buffer(int size); +extern void schedule_neighbours_check(int msecs, int override); +extern struct babel *babel_lookup(void); + +#endif /* BABEL_BABELD_H */ diff --git a/babeld/kernel.c b/babeld/kernel.c new file mode 100644 index 0000000..aed6dc9 --- /dev/null +++ b/babeld/kernel.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT +/* +Copyright 2007, 2008 by Grégoire Henry, Julien Cristau and Juliusz Chroboczek +Copyright 2011, 2012 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "babeld.h" + + +#include +#include +#include +#include +#include + +#include +#include "prefix.h" +#include "zclient.h" +#include "kernel.h" +#include "privs.h" +#include "command.h" +#include "vty.h" +#include "memory.h" +#include "frrevent.h" +#include "nexthop.h" + +#include "util.h" +#include "babel_interface.h" +#include "babel_zebra.h" + + +static int +zebra_route(int add, int familt, const unsigned char *pref, unsigned short plen, + const unsigned char *gate, int ifindex, unsigned int metric); + +int +kernel_interface_operational(struct interface *interface) +{ + return if_is_operative(interface); +} + +int +kernel_interface_mtu(struct interface *interface) +{ + return MIN(interface->mtu, interface->mtu6); +} + +int +kernel_interface_wireless(struct interface *interface) +{ + return 0; +} + +int +kernel_route(enum babel_kernel_routes operation, const unsigned char *pref, + unsigned short plen, const unsigned char *gate, int ifindex, + unsigned int metric, const unsigned char *newgate, int newifindex, + unsigned int newmetric) +{ + int rc; + int family; + + /* Check that the protocol family is consistent. */ + if(plen >= 96 && v4mapped(pref)) { + if(!v4mapped(gate)) { + errno = EINVAL; + return -1; + } + family = AF_INET; + } else { + if(v4mapped(gate)) { + errno = EINVAL; + return -1; + } + family = AF_INET6; + } + + switch (operation) { + case ROUTE_ADD: + return zebra_route(1, family, pref, plen, gate, ifindex, metric); + case ROUTE_FLUSH: + return zebra_route(0, family, pref, plen, gate, ifindex, metric); + case ROUTE_MODIFY: + if(newmetric == metric && memcmp(newgate, gate, 16) == 0 && + newifindex == ifindex) + return 0; + debugf(BABEL_DEBUG_ROUTE, "Modify route: delete old; add new."); + rc = zebra_route(0, family, pref, plen, gate, ifindex, metric); + if (rc < 0) + return -1; + + rc = zebra_route(1, family, pref, plen, newgate, newifindex, + newmetric); + return rc; + } + + return 0; +} + +static int +zebra_route(int add, int family, const unsigned char *pref, unsigned short plen, + const unsigned char *gate, int ifindex, unsigned int metric) +{ + struct zapi_route api; /* quagga's communication system */ + struct prefix quagga_prefix; /* quagga's prefix */ + union g_addr babel_prefix_addr; /* babeld's prefix addr */ + struct zapi_nexthop *api_nh; /* next router to go - no ECMP */ + + api_nh = &api.nexthops[0]; + + /* convert to be understandable by quagga */ + /* convert given addresses */ + switch (family) { + case AF_INET: + uchar_to_inaddr(&babel_prefix_addr.ipv4, pref); + break; + case AF_INET6: + uchar_to_in6addr(&babel_prefix_addr.ipv6, pref); + break; + } + + /* make prefix structure */ + memset (&quagga_prefix, 0, sizeof(quagga_prefix)); + quagga_prefix.family = family; + switch (family) { + case AF_INET: + IPV4_ADDR_COPY (&quagga_prefix.u.prefix4, &babel_prefix_addr.ipv4); + /* our plen is for v4mapped's addr */ + quagga_prefix.prefixlen = plen - 96; + break; + case AF_INET6: + IPV6_ADDR_COPY (&quagga_prefix.u.prefix6, &babel_prefix_addr.ipv6); + quagga_prefix.prefixlen = plen; + break; + } + apply_mask(&quagga_prefix); + + memset(&api, 0, sizeof(api)); + api.type = ZEBRA_ROUTE_BABEL; + api.safi = SAFI_UNICAST; + api.vrf_id = VRF_DEFAULT; + api.prefix = quagga_prefix; + + if(metric >= KERNEL_INFINITY) { + zapi_route_set_blackhole(&api, BLACKHOLE_REJECT); + } else { + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api.nexthop_num = 1; + api_nh->ifindex = ifindex; + api_nh->vrf_id = VRF_DEFAULT; + switch (family) { + case AF_INET: + uchar_to_inaddr(&api_nh->gate.ipv4, gate); + if (IPV4_ADDR_SAME(&api_nh->gate.ipv4, &quagga_prefix.u.prefix4) + && quagga_prefix.prefixlen == IPV4_MAX_BITLEN) { + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } else { + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + } + break; + case AF_INET6: + uchar_to_in6addr(&api_nh->gate.ipv6, gate); + /* difference to IPv4: always leave the linklocal as nexthop */ + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + break; + } + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = metric; + } + + debugf(BABEL_DEBUG_ROUTE, "%s route (%s) to zebra", + add ? "adding" : "removing", + (family == AF_INET) ? "ipv4" : "ipv6"); + return zclient_route_send (add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, + zclient, &api); +} + +int +if_eui64(int ifindex, unsigned char *eui) +{ + struct interface *ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); + if (ifp == NULL) { + return -1; + } + + uint8_t len = (uint8_t)ifp->hw_addr_len; + char *tmp = (void*) ifp->hw_addr; + + if (len == 8) { + memcpy(eui, tmp, 8); + eui[0] ^= 2; + } else if (len == 6) { + memcpy(eui, tmp, 3); + eui[3] = 0xFF; + eui[4] = 0xFE; + memcpy(eui+5, tmp+3, 3); + } else { + return -1; + } + return 0; +} + +/* Like gettimeofday, but returns monotonic time. If POSIX clocks are not + available, falls back to gettimeofday but enforces monotonicity. */ +void +gettime(struct timeval *tv) +{ + monotime(tv); +} + +/* If /dev/urandom doesn't exist, this will fail with ENOENT, which the + caller will deal with gracefully. */ + +int +read_random_bytes(void *buf, size_t len) +{ + int fd; + int rc; + + fd = open("/dev/urandom", O_RDONLY); + if(fd < 0) { + rc = -1; + } else { + rc = read(fd, buf, len); + if(rc < 0 || (unsigned) rc < len) + rc = -1; + close(fd); + } + return rc; +} diff --git a/babeld/kernel.h b/babeld/kernel.h new file mode 100644 index 0000000..4145843 --- /dev/null +++ b/babeld/kernel.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifndef BABEL_KERNEL_H +#define BABEL_KERNEL_H + +#include +#include "babel_main.h" +#include "if.h" + +#define KERNEL_INFINITY 0xFFFF + +enum babel_kernel_routes { + ROUTE_FLUSH, + ROUTE_ADD, + ROUTE_MODIFY, +}; + +int kernel_interface_operational(struct interface *interface); +int kernel_interface_mtu(struct interface *interface); +int kernel_interface_wireless(struct interface *interface); +int kernel_route(enum babel_kernel_routes operation, const unsigned char *dest, + unsigned short plen, const unsigned char *gate, int ifindex, + unsigned int metric, const unsigned char *newgate, + int newifindex, unsigned int newmetric); +int if_eui64(int ifindex, unsigned char *eui); +void gettime(struct timeval *tv); +int read_random_bytes(void *buf, size_t len); + +#endif /* BABEL_KERNEL_H */ diff --git a/babeld/message.c b/babeld/message.c new file mode 100644 index 0000000..1b83eb9 --- /dev/null +++ b/babeld/message.c @@ -0,0 +1,1922 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#include +#include "if.h" + +#include "babeld.h" +#include "util.h" +#include "net.h" +#include "babel_interface.h" +#include "source.h" +#include "neighbour.h" +#include "route.h" +#include "xroute.h" +#include "resend.h" +#include "message.h" +#include "kernel.h" +#include "babel_main.h" +#include "babel_errors.h" + +static unsigned char packet_header[4] = {42, 2}; + +int split_horizon = 1; + +unsigned short myseqno = 0; + +#define UNICAST_BUFSIZE 1024 +static int unicast_buffered = 0; +static unsigned char *unicast_buffer = NULL; +struct neighbour *unicast_neighbour = NULL; +struct timeval unicast_flush_timeout = {0, 0}; + +/* Minimum TLV _body_ length for TLVs of particular types (0 = no limit). */ +static const unsigned char tlv_min_length[MESSAGE_MAX + 1] = +{ + [ MESSAGE_PAD1 ] = 0, + [ MESSAGE_PADN ] = 0, + [ MESSAGE_ACK_REQ ] = 6, + [ MESSAGE_ACK ] = 2, + [ MESSAGE_HELLO ] = 6, + [ MESSAGE_IHU ] = 6, + [ MESSAGE_ROUTER_ID ] = 10, + [ MESSAGE_NH ] = 2, + [ MESSAGE_UPDATE ] = 10, + [ MESSAGE_REQUEST ] = 2, + [ MESSAGE_MH_REQUEST ] = 14, +}; + +/* Checks whether an AE exists or must be silently ignored */ +static bool +known_ae(int ae) +{ + return ae <= 4; +} + +/* Parse a network prefix, encoded in the somewhat baroque compressed + representation used by Babel. Return the number of bytes parsed. */ +static int +network_prefix(int ae, int plen, unsigned int omitted, + const unsigned char *p, const unsigned char *dp, + unsigned int len, unsigned char *p_r) +{ + unsigned pb; + unsigned char prefix[16]; + int ret = -1; + + if(plen >= 0) + pb = (plen + 7) / 8; + else if(ae == 1) + pb = 4; + else + pb = 16; + + if(pb > 16) + return -1; + + memset(prefix, 0, 16); + + switch(ae) { + case 0: + ret = 0; + break; + case 1: + if(omitted > 4 || pb > 4 || (pb > omitted && len < pb - omitted)) + return -1; + memcpy(prefix, v4prefix, 12); + if(omitted) { + if (dp == NULL || !v4mapped(dp)) return -1; + memcpy(prefix, dp, 12 + omitted); + } + if(pb > omitted) memcpy(prefix + 12 + omitted, p, pb - omitted); + ret = pb - omitted; + break; + case 2: + if(omitted > 16 || (pb > omitted && len < pb - omitted)) return -1; + if(omitted) { + if (dp == NULL || v4mapped(dp)) return -1; + memcpy(prefix, dp, omitted); + } + if(pb > omitted) memcpy(prefix + omitted, p, pb - omitted); + ret = pb - omitted; + break; + case 3: + if(pb > 8 && len < pb - 8) return -1; + prefix[0] = 0xfe; + prefix[1] = 0x80; + if(pb > 8) memcpy(prefix + 8, p, pb - 8); + ret = pb - 8; + break; + default: + return -1; + } + + mask_prefix(p_r, prefix, plen < 0 ? 128 : ae == 1 ? plen + 96 : plen); + return ret; +} + +static bool parse_update_subtlv(const unsigned char *a, int alen, + unsigned char *channels) +{ + int type, len, i = 0; + + while(i < alen) { + type = a[i]; + if(type == SUBTLV_PAD1) { + i++; + continue; + } + + if(i + 1 >= alen) { + flog_err(EC_BABEL_PACKET, "Received truncated attributes."); + return false; + } + len = a[i + 1]; + if(i + len + 2 > alen) { + flog_err(EC_BABEL_PACKET, "Received truncated attributes."); + return false; + } + + if (CHECK_FLAG(type, SUBTLV_MANDATORY)) { + /* + * RFC 8966 - 4.4 + * If the mandatory bit is set, then the whole enclosing + * TLV MUST be silently ignored (except for updating the + * parser state by a Router-Id, Next Hop, or Update TLV, + * as described in the next section). + */ + debugf(BABEL_DEBUG_COMMON, + "Received Mandatory bit set but this FRR version is not prepared to handle it at this point"); + return true; + } else if (type == SUBTLV_PADN) { + /* Nothing. */ + } else if (type == SUBTLV_DIVERSITY) { + if (len > DIVERSITY_HOPS) { + flog_err( + EC_BABEL_PACKET, + "Received overlong channel information (%d > %d).n", + len, DIVERSITY_HOPS); + len = DIVERSITY_HOPS; + } + if (memchr(a + i + 2, 0, len) != NULL) { + /* 0 is reserved. */ + flog_err(EC_BABEL_PACKET, "Channel information contains 0!"); + return false; + } + memset(channels, 0, DIVERSITY_HOPS); + memcpy(channels, a + i + 2, len); + } else { + debugf(BABEL_DEBUG_COMMON, + "Received unknown route attribute %d.", type); + } + + i += len + 2; + } + return false; +} + +static int +parse_hello_subtlv(const unsigned char *a, int alen, + unsigned int *hello_send_us) +{ + int type, len, i = 0, ret = 0; + + while(i < alen) { + type = a[i]; + if(type == SUBTLV_PAD1) { + i++; + continue; + } + + if(i + 1 >= alen) { + flog_err(EC_BABEL_PACKET, + "Received truncated sub-TLV on Hello message."); + return -1; + } + len = a[i + 1]; + if(i + len + 2 > alen) { + flog_err(EC_BABEL_PACKET, + "Received truncated sub-TLV on Hello message."); + return -1; + } + + if (CHECK_FLAG(type, SUBTLV_MANDATORY)) { + /* + * RFC 8966 4.4 + * If the mandatory bit is set, then the whole enclosing + * TLV MUST be silently ignored (except for updating the + * parser state by a Router-Id, Next Hop, or Update TLV, as + * described in the next section). + */ + debugf(BABEL_DEBUG_COMMON, + "Received subtlv with Mandatory bit, this version of FRR is not prepared to handle this currently"); + return -2; + } else if (type == SUBTLV_PADN) { + /* Nothing to do. */ + } else if (type == SUBTLV_TIMESTAMP) { + if (len >= 4) { + DO_NTOHL(*hello_send_us, a + i + 2); + ret = 1; + } else { + flog_err( + EC_BABEL_PACKET, + "Received incorrect RTT sub-TLV on Hello message."); + } + } else { + debugf(BABEL_DEBUG_COMMON, + "Received unknown Hello sub-TLV type %d.", type); + } + + i += len + 2; + } + return ret; +} + +static int +parse_ihu_subtlv(const unsigned char *a, int alen, + unsigned int *hello_send_us, + unsigned int *hello_rtt_receive_time) +{ + int type, len, i = 0, ret = 0; + + while(i < alen) { + type = a[i]; + if(type == SUBTLV_PAD1) { + i++; + continue; + } + + if(i + 1 >= alen) { + flog_err(EC_BABEL_PACKET, + "Received truncated sub-TLV on IHU message."); + return -1; + } + len = a[i + 1]; + if(i + len + 2 > alen) { + flog_err(EC_BABEL_PACKET, + "Received truncated sub-TLV on IHU message."); + return -1; + } + + if(type == SUBTLV_PADN) { + /* Nothing to do. */ + } else if(type == SUBTLV_TIMESTAMP) { + if(len >= 8) { + DO_NTOHL(*hello_send_us, a + i + 2); + DO_NTOHL(*hello_rtt_receive_time, a + i + 6); + ret = 1; + } + else { + flog_err(EC_BABEL_PACKET, + "Received incorrect RTT sub-TLV on IHU message."); + } + } else { + debugf(BABEL_DEBUG_COMMON, + "Received unknown IHU sub-TLV type %d.", type); + } + + i += len + 2; + } + return ret; +} + +static int +parse_request_subtlv(int ae, const unsigned char *a, int alen, + unsigned char *src_prefix, unsigned char *src_plen) +{ + int type, len, i = 0; + int have_src_prefix = 0; + + while(i < alen) { + type = a[0]; + if(type == SUBTLV_PAD1) { + i++; + continue; + } + + if(i + 2 > alen) + goto fail; + + len = a[i + 1]; + if(i + 2 + len > alen) + goto fail; + + if(type == SUBTLV_PADN) { + /* Nothing to do. */ + } else if(type == SUBTLV_SOURCE_PREFIX) { + int rc; + if(len < 1) + goto fail; + if(a[i + 2] == 0) + goto fail; + if(have_src_prefix != 0) + goto fail; + rc = network_prefix(ae, a[i + 2], 0, a + i + 3, NULL, + len - 1, src_prefix); + if(rc < 0) + goto fail; + if(ae==1) + *src_plen = a[i + 2] + 96; + else + *src_plen = a[i + 2]; + have_src_prefix = 1; + } else { + debugf(BABEL_DEBUG_COMMON,"Received unknown%s Route Request sub-TLV %d.", + ((type & 0x80) != 0) ? " mandatory" : "", type); + if((type & 0x80) != 0) + return -1; + } + + i += len + 2; + } + return 1; + + fail: + flog_err(EC_BABEL_PACKET, "Received truncated sub-TLV on Route Request."); + return -1; +} + +static int +network_address(int ae, const unsigned char *a, unsigned int len, + unsigned char *a_r) +{ + return network_prefix(ae, -1, 0, a, NULL, len, a_r); +} + +static int +channels_len(unsigned char *channels) +{ + unsigned char *p = memchr(channels, 0, DIVERSITY_HOPS); + return p ? (p - channels) : DIVERSITY_HOPS; +} + +/* Check, that the provided frame consists of a valid Babel packet header + followed by a sequence of TLVs. TLVs of known types are also checked to meet + minimum length constraints defined for each. Return 0 for no errors. */ +static int +babel_packet_examin(const unsigned char *packet, int packetlen, int *blength) +{ + int i = 0, bodylen; + const unsigned char *message; + unsigned char type, len; + + if(packetlen < 4 || packet[0] != 42 || packet[1] != 2) + return 1; + DO_NTOHS(bodylen, packet + 2); + if(bodylen + 4 > packetlen) { + debugf(BABEL_DEBUG_COMMON, "Received truncated packet (%d + 4 > %d).", + bodylen, packetlen); + return 1; + } + while (i < bodylen){ + message = packet + 4 + i; + type = message[0]; + if(type == MESSAGE_PAD1) { + i++; + continue; + } + if(i + 2 > bodylen) { + debugf(BABEL_DEBUG_COMMON,"Received truncated message."); + return 1; + } + len = message[1]; + if(i + len + 2 > bodylen) { + debugf(BABEL_DEBUG_COMMON,"Received truncated message."); + return 1; + } + /* not Pad1 */ + if(type <= MESSAGE_MAX && tlv_min_length[type] && len < tlv_min_length[type]) { + debugf(BABEL_DEBUG_COMMON,"Undersized %u TLV", type); + return 1; + } + i += len + 2; + } + + *blength = bodylen; + return 0; +} + +void +parse_packet(const unsigned char *from, struct interface *ifp, + const unsigned char *packet, int packetlen) +{ + int i; + const unsigned char *message; + unsigned char type, len; + int bodylen; + struct neighbour *neigh; + int have_router_id = 0, have_v4_prefix = 0, have_v6_prefix = 0, + have_v4_nh = 0, have_v6_nh = 0; + unsigned char router_id[8], v4_prefix[16], v6_prefix[16], + v4_nh[16], v6_nh[16]; + int have_hello_rtt = 0; + /* Content of the RTT sub-TLV on IHU messages. */ + unsigned int hello_send_us = 0, hello_rtt_receive_time = 0; + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + + if (CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS)) { + /* We want to track exactly when we received this packet. */ + gettime(&babel_now); + } + + if(!linklocal(from)) { + flog_err(EC_BABEL_PACKET, + "Received packet from non-local address %s.", + format_address(from)); + return; + } + + if (babel_packet_examin (packet, packetlen, &bodylen)) { + flog_err(EC_BABEL_PACKET, + "Received malformed packet on %s from %s.", + ifp->name, format_address(from)); + return; + } + + neigh = find_neighbour(from, ifp); + if(neigh == NULL) { + flog_err(EC_BABEL_PACKET, "Couldn't allocate neighbour."); + return; + } + + i = 0; + while(i < bodylen) { + message = packet + 4 + i; + type = message[0]; + if(type == MESSAGE_PAD1) { + debugf(BABEL_DEBUG_COMMON,"Received pad1 from %s on %s.", + format_address(from), ifp->name); + i++; + continue; + } + len = message[1]; + + if(type == MESSAGE_PADN) { + debugf(BABEL_DEBUG_COMMON,"Received pad%d from %s on %s.", + len, format_address(from), ifp->name); + } else if(type == MESSAGE_ACK_REQ) { + unsigned short nonce, interval; + DO_NTOHS(nonce, message + 4); + DO_NTOHS(interval, message + 6); + debugf(BABEL_DEBUG_COMMON,"Received ack-req (%04X %d) from %s on %s.", + nonce, interval, format_address(from), ifp->name); + send_ack(neigh, nonce, interval); + } else if(type == MESSAGE_ACK) { + debugf(BABEL_DEBUG_COMMON,"Received ack from %s on %s.", + format_address(from), ifp->name); + /* Nothing right now */ + } else if(type == MESSAGE_HELLO) { + unsigned short seqno, interval, flags; + int changed; + unsigned int timestamp = 0; + +#define BABEL_UNICAST_HELLO 0x8000 + DO_NTOHS(flags, message + 2); + + /* + * RFC 8966 Appendix F + * TL;DR -> Please ignore Unicast hellos until FRR's + * BABEL is brought up to date + */ + if (CHECK_FLAG(flags, BABEL_UNICAST_HELLO)) { + debugf(BABEL_DEBUG_COMMON, + "Received Unicast Hello from %s on %s that FRR is not prepared to understand yet", + format_address(from), ifp->name); + goto done; + } + + DO_NTOHS(seqno, message + 4); + DO_NTOHS(interval, message + 6); + debugf(BABEL_DEBUG_COMMON, + "Received hello %d (%d) from %s on %s.", seqno, interval, + format_address(from), ifp->name); + + /* + * RFC 8966 Appendix F + * TL;DR -> Please ignore any Hello packets with the interval + * field set to 0 + */ + if (interval == 0) { + debugf(BABEL_DEBUG_COMMON, + "Received hello from %s on %s should be ignored as that this version of FRR does not know how to properly handle interval == 0", + format_address(from), ifp->name); + goto done; + } + + changed = update_neighbour(neigh, seqno, interval); + update_neighbour_metric(neigh, changed); + if (interval > 0) + /* Multiply by 3/2 to allow hellos to expire. */ + schedule_neighbours_check(interval * 15, 0); + /* Sub-TLV handling. */ + if (len > 8) { + if (parse_hello_subtlv(message + 8, len - 6, ×tamp) > 0) { + neigh->hello_send_us = timestamp; + neigh->hello_rtt_receive_time = babel_now; + have_hello_rtt = 1; + } + } + } else if(type == MESSAGE_IHU) { + unsigned short txcost, interval; + unsigned char address[16]; + int rc; + DO_NTOHS(txcost, message + 4); + DO_NTOHS(interval, message + 6); + rc = network_address(message[2], message + 8, len - 6, address); + if(rc < 0) goto fail; + debugf(BABEL_DEBUG_COMMON,"Received ihu %d (%d) from %s on %s for %s.", + txcost, interval, + format_address(from), ifp->name, + format_address(address)); + if(message[2] == 0 || is_interface_ll_address(ifp, address)) { + int changed = txcost != neigh->txcost; + neigh->txcost = txcost; + neigh->ihu_time = babel_now; + neigh->ihu_interval = interval; + update_neighbour_metric(neigh, changed); + if(interval > 0) + /* Multiply by 3/2 to allow neighbours to expire. */ + schedule_neighbours_check(interval * 45, 0); + /* RTT sub-TLV. */ + if(len > 10 + rc) + parse_ihu_subtlv(message + 8 + rc, len - 6 - rc, + &hello_send_us, &hello_rtt_receive_time); + } + } else if(type == MESSAGE_ROUTER_ID) { + memcpy(router_id, message + 4, 8); + have_router_id = 1; + debugf(BABEL_DEBUG_COMMON,"Received router-id %s from %s on %s.", + format_eui64(router_id), format_address(from), ifp->name); + } else if(type == MESSAGE_NH) { + unsigned char nh[16]; + int rc; + rc = network_address(message[2], message + 4, len - 2, nh); + if(rc <= 0) { + have_v4_nh = 0; + have_v6_nh = 0; + goto fail; + } + debugf(BABEL_DEBUG_COMMON,"Received nh %s (%d) from %s on %s.", + format_address(nh), message[2], + format_address(from), ifp->name); + if(message[2] == 1) { + memcpy(v4_nh, nh, 16); + have_v4_nh = 1; + } else { + memcpy(v6_nh, nh, 16); + have_v6_nh = 1; + } + } else if(type == MESSAGE_UPDATE) { + unsigned char prefix[16], *nh; + unsigned char plen; + unsigned char channels[DIVERSITY_HOPS]; + unsigned short interval, seqno, metric; + int rc, parsed_len; + bool ignore_update = false; + + DO_NTOHS(interval, message + 6); + DO_NTOHS(seqno, message + 8); + DO_NTOHS(metric, message + 10); + if(message[5] == 0 || + (message[2] == 1 ? have_v4_prefix : have_v6_prefix)) + rc = network_prefix(message[2], message[4], message[5], + message + 12, + message[2] == 1 ? v4_prefix : v6_prefix, + len - 10, prefix); + else + rc = -1; + if(rc < 0) { + if(message[3] & 0x80) + have_v4_prefix = have_v6_prefix = 0; + goto fail; + } + parsed_len = 10 + rc; + + plen = message[4] + (message[2] == 1 ? 96 : 0); + + if(message[3] & 0x80) { + if(message[2] == 1) { + memcpy(v4_prefix, prefix, 16); + have_v4_prefix = 1; + } else { + memcpy(v6_prefix, prefix, 16); + have_v6_prefix = 1; + } + } + if(message[3] & 0x40) { + if(message[2] == 1) { + memset(router_id, 0, 4); + memcpy(router_id + 4, prefix + 12, 4); + } else { + memcpy(router_id, prefix + 8, 8); + } + have_router_id = 1; + } + if(!have_router_id && message[2] != 0) { + flog_err(EC_BABEL_PACKET, + "Received prefix with no router id."); + goto fail; + } + debugf(BABEL_DEBUG_COMMON,"Received update%s%s for %s from %s on %s.", + (message[3] & 0x80) ? "/prefix" : "", + (message[3] & 0x40) ? "/id" : "", + format_prefix(prefix, plen), + format_address(from), ifp->name); + + if(message[2] == 0) { + if(metric < 0xFFFF) { + flog_err(EC_BABEL_PACKET, + "Received wildcard update with finite metric."); + goto done; + } + retract_neighbour_routes(neigh); + goto done; + } else if(message[2] == 1) { + if(!have_v4_nh) + goto fail; + nh = v4_nh; + } else if(have_v6_nh) { + nh = v6_nh; + } else { + nh = neigh->address; + } + + if(message[2] == 1) { + if(!babel_get_if_nfo(ifp)->ipv4) + goto done; + } + + if(CHECK_FLAG(babel_get_if_nfo(ifp)->flags, BABEL_IF_FARAWAY)) { + channels[0] = 0; + } else { + /* This will be overwritten by parse_update_subtlv below. */ + if(metric < 256) { + /* Assume non-interfering (wired) link. */ + channels[0] = 0; + } else { + /* Assume interfering. */ + channels[0] = BABEL_IF_CHANNEL_INTERFERING; + channels[1] = 0; + } + + if(parsed_len < len) + ignore_update = + parse_update_subtlv(message + 2 + parsed_len, + len - parsed_len, channels); + } + + if (!ignore_update) + update_route(router_id, prefix, plen, seqno, metric, + interval, neigh, nh, channels, channels_len(channels)); + } else if(type == MESSAGE_REQUEST) { + unsigned char prefix[16], src_prefix[16], plen, src_plen; + int rc, is_ss; + if(len < 2) goto fail; + if(!known_ae(message[2])) { + debugf(BABEL_DEBUG_COMMON,"Received request with unknown AE %d. Ignoring.", + message[2]); + goto done; + } + rc = network_prefix(message[2], message[3], 0, + message + 4, NULL, len - 2, prefix); + if(rc < 0) goto fail; + plen = message[3] + (message[2] == 1 ? 96 : 0); + debugf(BABEL_DEBUG_COMMON,"Received request for %s from %s on %s.", + message[2] == 0 ? "any" : format_prefix(prefix, plen), + format_address(from), ifp->name); + if(message[2] == 1) { + v4tov6(src_prefix, zeroes); + src_plen = 96; + } else { + memcpy(src_prefix, zeroes, 16); + src_plen = 0; + } + rc = parse_request_subtlv(message[2], message + 4 + rc, + len - 2 - rc, src_prefix, &src_plen); + if(rc < 0) + goto done; + is_ss = !is_default(src_prefix, src_plen); + if(message[2] == 0) { + struct babel_interface *neigh_ifp =babel_get_if_nfo(neigh->ifp); + if(is_ss) { + /* Wildcard requests don't carry a source prefix. */ + flog_err(EC_BABEL_PACKET, + "Received source-specific wildcard request."); + goto done; + } + /* If a neighbour is requesting a full route dump from us, + we might as well send it an IHU. */ + send_ihu(neigh, NULL); + /* Since nodes send wildcard requests on boot, booting + a large number of nodes at the same time may cause an + update storm. Ignore a wildcard request that happens + shortly after we sent a full update. */ + if(neigh_ifp->last_update_time < + (time_t)(babel_now.tv_sec - + MAX(neigh_ifp->hello_interval / 100, 1))) + send_update(neigh->ifp, 0, NULL, 0); + } else { + send_update(neigh->ifp, 0, prefix, plen); + } + } else if(type == MESSAGE_MH_REQUEST) { + unsigned char prefix[16], plen; + unsigned short seqno; + int rc; + DO_NTOHS(seqno, message + 4); + rc = network_prefix(message[2], message[3], 0, + message + 16, NULL, len - 14, prefix); + if(rc <= 0) goto fail; + plen = message[3] + (message[2] == 1 ? 96 : 0); + debugf(BABEL_DEBUG_COMMON,"Received request (%d) for %s from %s on %s (%s, %d).", + message[6], + format_prefix(prefix, plen), + format_address(from), ifp->name, + format_eui64(message + 8), seqno); + handle_request(neigh, prefix, plen, message[6], seqno, message + 8); + } else { + debugf(BABEL_DEBUG_COMMON,"Received unknown packet type %d from %s on %s.", + type, format_address(from), ifp->name); + } + done: + i += len + 2; + continue; + + fail: + flog_err(EC_BABEL_PACKET, + "Couldn't parse packet (%d, %d) from %s on %s.", + message[0], message[1], format_address(from), ifp->name); + goto done; + } + + /* We can calculate the RTT to this neighbour. */ + if(have_hello_rtt && hello_send_us && hello_rtt_receive_time) { + int remote_waiting_us, local_waiting_us; + unsigned int rtt, smoothed_rtt; + unsigned int old_rttcost; + int changed = 0; + remote_waiting_us = neigh->hello_send_us - hello_rtt_receive_time; + local_waiting_us = time_us(neigh->hello_rtt_receive_time) - + hello_send_us; + + /* Sanity checks (validity window of 10 minutes). */ + if(remote_waiting_us < 0 || local_waiting_us < 0 || + remote_waiting_us > 600000000 || local_waiting_us > 600000000) + return; + + rtt = MAX(0, local_waiting_us - remote_waiting_us); + debugf(BABEL_DEBUG_COMMON, "RTT to %s on %s sample result: %d us.", + format_address(from), ifp->name, rtt); + + old_rttcost = neighbour_rttcost(neigh); + if (valid_rtt(neigh)) { + /* Running exponential average. */ + smoothed_rtt = (babel_ifp->rtt_decay * rtt + + (256 - babel_ifp->rtt_decay) * neigh->rtt); + /* Rounding (up or down) to get closer to the sample. */ + neigh->rtt = (neigh->rtt >= rtt) ? smoothed_rtt / 256 : + (smoothed_rtt + 255) / 256; + } else { + /* We prefer to be conservative with new neighbours + (higher RTT) */ + assert(rtt <= 0x7FFFFFFF); + neigh->rtt = 2*rtt; + } + changed = (neighbour_rttcost(neigh) == old_rttcost ? 0 : 1); + update_neighbour_metric(neigh, changed); + neigh->rtt_time = babel_now; + } + return; +} + +/* Under normal circumstances, there are enough moderation mechanisms + elsewhere in the protocol to make sure that this last-ditch check + should never trigger. But I'm superstitious. */ + +static int +check_bucket(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->bucket == 0) { + int seconds = babel_now.tv_sec - babel_ifp->bucket_time; + if(seconds > 0) { + babel_ifp->bucket = MIN(BUCKET_TOKENS_MAX, + seconds * BUCKET_TOKENS_PER_SEC); + } + /* Reset bucket time unconditionally, in case clock is stepped. */ + babel_ifp->bucket_time = babel_now.tv_sec; + } + + if(babel_ifp->bucket > 0) { + babel_ifp->bucket--; + return 1; + } else { + return 0; + } +} + +static int fill_rtt_message(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + if(CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS) && + (babel_ifp->buffered_hello >= 0)) { + if(babel_ifp->sendbuf[babel_ifp->buffered_hello + 8] == SUBTLV_PADN && + babel_ifp->sendbuf[babel_ifp->buffered_hello + 9] == 4) { + unsigned int time; + /* Change the type of sub-TLV. */ + babel_ifp->sendbuf[babel_ifp->buffered_hello + 8] = + SUBTLV_TIMESTAMP; + gettime(&babel_now); + time = time_us(babel_now); + DO_HTONL(babel_ifp->sendbuf + babel_ifp->buffered_hello + 10, time); + return 1; + } else { + flog_err(EC_BABEL_PACKET, "No space left for timestamp sub-TLV (this shouldn't happen)"); + return -1; + } + } + return 0; +} + +void flushbuf(struct interface *ifp) +{ + int rc; + struct sockaddr_in6 sin6; + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + + assert(babel_ifp->buffered <= babel_ifp->bufsize); + + flushupdates(ifp); + + if(babel_ifp->buffered > 0) { + debugf(BABEL_DEBUG_COMMON," (flushing %d buffered bytes on %s)", + babel_ifp->buffered, ifp->name); + if(check_bucket(ifp)) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, protocol_group, 16); + sin6.sin6_port = htons(protocol_port); + sin6.sin6_scope_id = ifp->ifindex; + DO_HTONS(packet_header + 2, babel_ifp->buffered); + fill_rtt_message(ifp); + rc = babel_send(protocol_socket, + packet_header, sizeof(packet_header), + babel_ifp->sendbuf, babel_ifp->buffered, + (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) + flog_err(EC_BABEL_PACKET, "send: %s", safe_strerror(errno)); + } else { + flog_err(EC_BABEL_PACKET, "Bucket full, dropping packet to %s.", + ifp->name); + } + } + VALGRIND_MAKE_MEM_UNDEFINED(babel_ifp->sendbuf, babel_ifp->bufsize); + babel_ifp->buffered = 0; + babel_ifp->buffered_hello = -1; + babel_ifp->have_buffered_id = 0; + babel_ifp->have_buffered_nh = 0; + babel_ifp->have_buffered_prefix = 0; + babel_ifp->flush_timeout.tv_sec = 0; + babel_ifp->flush_timeout.tv_usec = 0; +} + +static void schedule_flush(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + unsigned msecs = jitter(babel_ifp, 0); + if(babel_ifp->flush_timeout.tv_sec != 0 && + timeval_minus_msec(&babel_ifp->flush_timeout, &babel_now) < msecs) + return; + set_timeout(&babel_ifp->flush_timeout, msecs); +} + +static void schedule_flush_now(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + /* Almost now */ + unsigned msecs = roughly(10); + if(babel_ifp->flush_timeout.tv_sec != 0 && + timeval_minus_msec(&babel_ifp->flush_timeout, &babel_now) < msecs) + return; + set_timeout(&babel_ifp->flush_timeout, msecs); +} + +static void schedule_unicast_flush(unsigned msecs) +{ + if(!unicast_neighbour) + return; + if(unicast_flush_timeout.tv_sec != 0 && + timeval_minus_msec(&unicast_flush_timeout, &babel_now) < msecs) + return; + unicast_flush_timeout.tv_usec = (babel_now.tv_usec + msecs * 1000) %1000000; + unicast_flush_timeout.tv_sec = + babel_now.tv_sec + (babel_now.tv_usec / 1000 + msecs) / 1000; +} + +static void ensure_space(struct interface *ifp, int space) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->bufsize - babel_ifp->buffered < space) + flushbuf(ifp); +} + +static void start_message(struct interface *ifp, int type, int len) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->bufsize - babel_ifp->buffered < len + 2) + flushbuf(ifp); + babel_ifp->sendbuf[babel_ifp->buffered++] = type; + babel_ifp->sendbuf[babel_ifp->buffered++] = len; +} + +static void end_message(struct interface *ifp, int type, int bytes) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + assert(babel_ifp->buffered >= bytes + 2 && + babel_ifp->sendbuf[babel_ifp->buffered - bytes - 2] == type && + babel_ifp->sendbuf[babel_ifp->buffered - bytes - 1] == bytes); + schedule_flush(ifp); +} + +static void accumulate_byte(struct interface *ifp, unsigned char value) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + babel_ifp->sendbuf[babel_ifp->buffered++] = value; +} + +static void accumulate_short(struct interface *ifp, unsigned short value) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + DO_HTONS(babel_ifp->sendbuf + babel_ifp->buffered, value); + babel_ifp->buffered += 2; +} + +static void accumulate_int(struct interface *ifp, unsigned int value) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + DO_HTONL(babel_ifp->sendbuf + babel_ifp->buffered, value); + babel_ifp->buffered += 4; +} + +static void +accumulate_bytes(struct interface *ifp, + const unsigned char *value, unsigned len) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + memcpy(babel_ifp->sendbuf + babel_ifp->buffered, value, len); + babel_ifp->buffered += len; +} + +static int start_unicast_message(struct neighbour *neigh, int type, int len) +{ + if(unicast_neighbour) { + if(neigh != unicast_neighbour || + unicast_buffered + len + 2 >= + MIN(UNICAST_BUFSIZE, babel_get_if_nfo(neigh->ifp)->bufsize)) + flush_unicast(0); + } + if(!unicast_buffer) + unicast_buffer = malloc(UNICAST_BUFSIZE); + if(!unicast_buffer) { + flog_err(EC_BABEL_MEMORY, "malloc(unicast_buffer): %s", + safe_strerror(errno)); + return -1; + } + + unicast_neighbour = neigh; + + unicast_buffer[unicast_buffered++] = type; + unicast_buffer[unicast_buffered++] = len; + return 1; +} + +static void end_unicast_message(struct neighbour *neigh, int type, int bytes) +{ + assert(unicast_neighbour == neigh && unicast_buffered >= bytes + 2 && + unicast_buffer[unicast_buffered - bytes - 2] == type && + unicast_buffer[unicast_buffered - bytes - 1] == bytes); + schedule_unicast_flush(jitter(babel_get_if_nfo(neigh->ifp), 0)); +} + +static void +accumulate_unicast_byte(struct neighbour *neigh, unsigned char value) +{ + unicast_buffer[unicast_buffered++] = value; +} + +static void +accumulate_unicast_short(struct neighbour *neigh, unsigned short value) +{ + DO_HTONS(unicast_buffer + unicast_buffered, value); + unicast_buffered += 2; +} + +static void accumulate_unicast_int(struct neighbour *neigh, unsigned int value) +{ + DO_HTONL(unicast_buffer + unicast_buffered, value); + unicast_buffered += 4; +} + +static void +accumulate_unicast_bytes(struct neighbour *neigh, + const unsigned char *value, unsigned len) +{ + memcpy(unicast_buffer + unicast_buffered, value, len); + unicast_buffered += len; +} + +void +send_ack(struct neighbour *neigh, unsigned short nonce, unsigned short interval) +{ + int rc; + debugf(BABEL_DEBUG_COMMON,"Sending ack (%04x) to %s on %s.", + nonce, format_address(neigh->address), neigh->ifp->name); + rc = start_unicast_message(neigh, MESSAGE_ACK, 2); + if(rc < 0) + return; + accumulate_unicast_short(neigh, nonce); + end_unicast_message(neigh, MESSAGE_ACK, 2); + /* Roughly yields a value no larger than 3/2, so this meets the deadline */ + schedule_unicast_flush(roughly(interval * 6)); +} + +void send_hello_noupdate(struct interface *ifp, unsigned interval) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + /* This avoids sending multiple hellos in a single packet, which breaks + link quality estimation. */ + if(babel_ifp->buffered_hello >= 0) + flushbuf(ifp); + + babel_ifp->hello_seqno = seqno_plus(babel_ifp->hello_seqno, 1); + set_timeout(&babel_ifp->hello_timeout, babel_ifp->hello_interval); + + if(!if_up(ifp)) + return; + + debugf(BABEL_DEBUG_COMMON,"Sending hello %d (%d) to %s.", + babel_ifp->hello_seqno, interval, ifp->name); + + start_message(ifp, MESSAGE_HELLO, + (babel_ifp->flags & BABEL_IF_TIMESTAMPS) ? 12 : 6); + babel_ifp->buffered_hello = babel_ifp->buffered - 2; + accumulate_short(ifp, 0); + accumulate_short(ifp, babel_ifp->hello_seqno); + accumulate_short(ifp, interval > 0xFFFF ? 0xFFFF : interval); + if (CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS)) { + /* Sub-TLV containing the local time of emission. We use a + Pad4 sub-TLV, which we'll fill just before sending. */ + accumulate_byte(ifp, SUBTLV_PADN); + accumulate_byte(ifp, 4); + accumulate_int(ifp, 0); + } + end_message(ifp, MESSAGE_HELLO, + CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS) ? 12 : 6); +} + +void send_hello(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + send_hello_noupdate(ifp, (babel_ifp->hello_interval + 9) / 10); + /* Send full IHU every 3 hellos, and marginal IHU each time */ + if(babel_ifp->hello_seqno % 3 == 0) + send_ihu(NULL, ifp); + else + send_marginal_ihu(ifp); +} + +void flush_unicast(int dofree) +{ + struct sockaddr_in6 sin6; + int rc; + + if(unicast_buffered == 0) + goto done; + + if(!if_up(unicast_neighbour->ifp)) + goto done; + + /* Preserve ordering of messages */ + flushbuf(unicast_neighbour->ifp); + + if(check_bucket(unicast_neighbour->ifp)) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, unicast_neighbour->address, 16); + sin6.sin6_port = htons(protocol_port); + sin6.sin6_scope_id = unicast_neighbour->ifp->ifindex; + DO_HTONS(packet_header + 2, unicast_buffered); + fill_rtt_message(unicast_neighbour->ifp); + rc = babel_send(protocol_socket, + packet_header, sizeof(packet_header), + unicast_buffer, unicast_buffered, + (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) + flog_err(EC_BABEL_PACKET, "send(unicast): %s", + safe_strerror(errno)); + } else { + flog_err(EC_BABEL_PACKET, + "Bucket full, dropping unicast packet to %s if %s.", + format_address(unicast_neighbour->address), + unicast_neighbour->ifp->name); + } + + done: + VALGRIND_MAKE_MEM_UNDEFINED(unicast_buffer, UNICAST_BUFSIZE); + unicast_buffered = 0; + if(dofree && unicast_buffer) { + free(unicast_buffer); + unicast_buffer = NULL; + } + unicast_neighbour = NULL; + unicast_flush_timeout.tv_sec = 0; + unicast_flush_timeout.tv_usec = 0; +} + +static void +really_send_update(struct interface *ifp, + const unsigned char *id, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, unsigned short metric, + unsigned char *channels, int channels_len) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + int add_metric, v4, real_plen, omit = 0; + const unsigned char *real_prefix; + unsigned short flags = 0; + int channels_size; + + if(diversity_kind != DIVERSITY_CHANNEL) + channels_len = -1; + + channels_size = channels_len >= 0 ? channels_len + 2 : 0; + + if(!if_up(ifp)) + return; + + add_metric = output_filter(id, prefix, plen, ifp->ifindex); + if(add_metric >= INFINITY) + return; + + metric = MIN(metric + add_metric, INFINITY); + /* Worst case */ + ensure_space(ifp, 20 + 12 + 28); + + v4 = (plen >= 96) && v4mapped(prefix); + + if(v4) { + if(!babel_ifp->ipv4) + return; + if(!babel_ifp->have_buffered_nh || + memcmp(babel_ifp->buffered_nh, babel_ifp->ipv4, 4) != 0) { + start_message(ifp, MESSAGE_NH, 6); + accumulate_byte(ifp, 1); + accumulate_byte(ifp, 0); + accumulate_bytes(ifp, babel_ifp->ipv4, 4); + end_message(ifp, MESSAGE_NH, 6); + memcpy(babel_ifp->buffered_nh, babel_ifp->ipv4, 4); + babel_ifp->have_buffered_nh = 1; + } + + real_prefix = prefix + 12; + real_plen = plen - 96; + } else { + if(babel_ifp->have_buffered_prefix) { + while(omit < plen / 8 && + babel_ifp->buffered_prefix[omit] == prefix[omit]) + omit++; + } + if(!babel_ifp->have_buffered_prefix || plen >= 48) + SET_FLAG(flags, 0x80); + real_prefix = prefix; + real_plen = plen; + } + + if(!babel_ifp->have_buffered_id + || memcmp(id, babel_ifp->buffered_id, 8) != 0) { + if(real_plen == 128 && memcmp(real_prefix + 8, id, 8) == 0) { + SET_FLAG(flags, 0x40); + } else { + start_message(ifp, MESSAGE_ROUTER_ID, 10); + accumulate_short(ifp, 0); + accumulate_bytes(ifp, id, 8); + end_message(ifp, MESSAGE_ROUTER_ID, 10); + } + memcpy(babel_ifp->buffered_id, id, sizeof(babel_ifp->buffered_id)); + babel_ifp->have_buffered_id = 1; + } + + start_message(ifp, MESSAGE_UPDATE, 10 + (real_plen + 7) / 8 - omit + + channels_size); + accumulate_byte(ifp, v4 ? 1 : 2); + accumulate_byte(ifp, flags); + accumulate_byte(ifp, real_plen); + accumulate_byte(ifp, omit); + accumulate_short(ifp, (babel_ifp->update_interval + 5) / 10); + accumulate_short(ifp, seqno); + accumulate_short(ifp, metric); + accumulate_bytes(ifp, real_prefix + omit, (real_plen + 7) / 8 - omit); + /* Note that an empty channels TLV is different from no such TLV. */ + if(channels_len >= 0) { + accumulate_byte(ifp, 2); + accumulate_byte(ifp, channels_len); + + if (channels && channels_len > 0) + accumulate_bytes(ifp, channels, channels_len); + } + end_message(ifp, MESSAGE_UPDATE, 10 + (real_plen + 7) / 8 - omit + + channels_size); + + if (CHECK_FLAG(flags, 0x80)) { + memcpy(babel_ifp->buffered_prefix, prefix, 16); + babel_ifp->have_buffered_prefix = 1; + } +} + +static int compare_buffered_updates(const void *av, const void *bv) +{ + const struct buffered_update *a = av, *b = bv; + int rc, v4a, v4b, ma, mb; + + rc = memcmp(a->id, b->id, 8); + if(rc != 0) + return rc; + + v4a = (a->plen >= 96 && v4mapped(a->prefix)); + v4b = (b->plen >= 96 && v4mapped(b->prefix)); + + if(v4a > v4b) + return 1; + else if(v4a < v4b) + return -1; + + ma = (!v4a && a->plen == 128 && memcmp(a->prefix + 8, a->id, 8) == 0); + mb = (!v4b && b->plen == 128 && memcmp(b->prefix + 8, b->id, 8) == 0); + + if(ma > mb) + return -1; + else if(mb > ma) + return 1; + + if(a->plen < b->plen) + return 1; + else if(a->plen > b->plen) + return -1; + + return memcmp(a->prefix, b->prefix, 16); +} + +void flushupdates(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = NULL; + struct xroute *xroute; + struct babel_route *route; + const unsigned char *last_prefix = NULL; + unsigned char last_plen = 0xFF; + int i; + + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) + flushupdates(ifp_aux); + return; + } + + babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->num_buffered_updates > 0) { + struct buffered_update *b = babel_ifp->buffered_updates; + int n = babel_ifp->num_buffered_updates; + + babel_ifp->buffered_updates = NULL; + babel_ifp->update_bufsize = 0; + babel_ifp->num_buffered_updates = 0; + + if(!if_up(ifp)) + goto done; + + debugf(BABEL_DEBUG_COMMON," (flushing %d buffered updates on %s (%d))", + n, ifp->name, ifp->ifindex); + + /* In order to send fewer update messages, we want to send updates + with the same router-id together, with IPv6 going out before IPv4. */ + + for(i = 0; i < n; i++) { + route = find_installed_route(b[i].prefix, b[i].plen); + if(route) + memcpy(b[i].id, route->src->id, 8); + else + memcpy(b[i].id, myid, 8); + } + + qsort(b, n, sizeof(struct buffered_update), compare_buffered_updates); + + for(i = 0; i < n; i++) { + /* The same update may be scheduled multiple times before it is + sent out. Since our buffer is now sorted, it is enough to + compare with the previous update. */ + + if(last_prefix) { + if(b[i].plen == last_plen && + memcmp(b[i].prefix, last_prefix, 16) == 0) + continue; + } + + xroute = find_xroute(b[i].prefix, b[i].plen); + route = find_installed_route(b[i].prefix, b[i].plen); + + if(xroute && (!route || xroute->metric <= kernel_metric)) { + really_send_update(ifp, myid, + xroute->prefix, xroute->plen, + myseqno, xroute->metric, + NULL, 0); + last_prefix = xroute->prefix; + last_plen = xroute->plen; + } else if(route) { + unsigned char channels[DIVERSITY_HOPS]; + int chlen; + struct interface *route_ifp = route->neigh->ifp; + struct babel_interface *babel_route_ifp = NULL; + unsigned short metric; + unsigned short seqno; + + seqno = route->seqno; + metric = + route_interferes(route, ifp) ? + route_metric(route) : + route_metric_noninterfering(route); + + if(metric < INFINITY) + satisfy_request(route->src->prefix, route->src->plen, + seqno, route->src->id, ifp); + if(CHECK_FLAG(babel_ifp->flags, BABEL_IF_SPLIT_HORIZON) && + route->neigh->ifp == ifp) + continue; + + babel_route_ifp = babel_get_if_nfo(route_ifp); + if(babel_route_ifp->channel ==BABEL_IF_CHANNEL_NONINTERFERING) { + memcpy(channels, route->channels, DIVERSITY_HOPS); + } else { + if(babel_route_ifp->channel == BABEL_IF_CHANNEL_UNKNOWN) + channels[0] = BABEL_IF_CHANNEL_INTERFERING; + else { + assert(babel_route_ifp->channel > 0 && + babel_route_ifp->channel <= 255); + channels[0] = babel_route_ifp->channel; + } + memcpy(channels + 1, route->channels, DIVERSITY_HOPS - 1); + } + + chlen = channels_len(channels); + really_send_update(ifp, route->src->id, + route->src->prefix, + route->src->plen, + seqno, metric, + channels, chlen); + update_source(route->src, seqno, metric); + last_prefix = route->src->prefix; + last_plen = route->src->plen; + } else { + /* There's no route for this prefix. This can happen shortly + after an xroute has been retracted, so send a retraction. */ + really_send_update(ifp, myid, b[i].prefix, b[i].plen, + myseqno, INFINITY, NULL, -1); + } + } + schedule_flush_now(ifp); + done: + free(b); + } + babel_ifp->update_flush_timeout.tv_sec = 0; + babel_ifp->update_flush_timeout.tv_usec = 0; +} + +static void schedule_update_flush(struct interface *ifp, int urgent) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + unsigned msecs; + + msecs = update_jitter(babel_ifp, urgent); + if(babel_ifp->update_flush_timeout.tv_sec != 0 && + timeval_minus_msec(&babel_ifp->update_flush_timeout, &babel_now) < msecs) + return; + set_timeout(&babel_ifp->update_flush_timeout, msecs); +} + +static void +buffer_update(struct interface *ifp, + const unsigned char *prefix, unsigned char plen) +{ + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + if(babel_ifp->num_buffered_updates > 0 && + babel_ifp->num_buffered_updates >= babel_ifp->update_bufsize) + flushupdates(ifp); + + if(babel_ifp->update_bufsize == 0) { + int n; + assert(babel_ifp->buffered_updates == NULL); + /* Allocate enough space to hold a full update. Since the + number of installed routes will grow over time, make sure we + have enough space to send a full-ish frame. */ + n = installed_routes_estimate() + xroutes_estimate() + 4; + n = MAX(n, babel_ifp->bufsize / 16); + again: + babel_ifp->buffered_updates = malloc(n *sizeof(struct buffered_update)); + if(babel_ifp->buffered_updates == NULL) { + flog_err(EC_BABEL_MEMORY, "malloc(buffered_updates): %s", + safe_strerror(errno)); + if(n > 4) { + /* Try again with a tiny buffer. */ + n = 4; + goto again; + } + return; + } + babel_ifp->update_bufsize = n; + babel_ifp->num_buffered_updates = 0; + } + + memcpy(babel_ifp->buffered_updates[babel_ifp->num_buffered_updates].prefix, + prefix, 16); + babel_ifp->buffered_updates[babel_ifp->num_buffered_updates].plen = plen; + babel_ifp->num_buffered_updates++; +} + +void +send_update(struct interface *ifp, int urgent, + const unsigned char *prefix, unsigned char plen) +{ + babel_interface_nfo *babel_ifp = NULL; + + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + struct babel_route *route; + FOR_ALL_INTERFACES(vrf, ifp_aux) + send_update(ifp_aux, urgent, prefix, plen); + if(prefix) { + /* Since flushupdates only deals with non-wildcard interfaces, we + need to do this now. */ + route = find_installed_route(prefix, plen); + if(route && route_metric(route) < INFINITY) + satisfy_request(prefix, plen, route->src->seqno, route->src->id, + NULL); + } + return; + } + + if(!if_up(ifp)) + return; + + babel_ifp = babel_get_if_nfo(ifp); + if(prefix) { + debugf(BABEL_DEBUG_COMMON,"Sending update to %s for %s.", + ifp->name, format_prefix(prefix, plen)); + buffer_update(ifp, prefix, plen); + } else { + struct route_stream *routes = NULL; + send_self_update(ifp); + debugf(BABEL_DEBUG_COMMON,"Sending update to %s for any.", ifp->name); + routes = route_stream(1); + if(routes) { + while(1) { + struct babel_route *route = route_stream_next(routes); + if(route == NULL) + break; + buffer_update(ifp, route->src->prefix, route->src->plen); + } + route_stream_done(routes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate route stream."); + } + set_timeout(&babel_ifp->update_timeout, babel_ifp->update_interval); + babel_ifp->last_update_time = babel_now.tv_sec; + } + schedule_update_flush(ifp, urgent); +} + +void +send_update_resend(struct interface *ifp, + const unsigned char *prefix, unsigned char plen) +{ + assert(prefix != NULL); + + send_update(ifp, 1, prefix, plen); + record_resend(RESEND_UPDATE, prefix, plen, 0, NULL, NULL, resend_delay); +} + +void send_wildcard_retraction(struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = NULL; + + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) + send_wildcard_retraction(ifp_aux); + return; + } + + if(!if_up(ifp)) + return; + + babel_ifp = babel_get_if_nfo(ifp); + start_message(ifp, MESSAGE_UPDATE, 10); + accumulate_byte(ifp, 0); + accumulate_byte(ifp, 0x40); + accumulate_byte(ifp, 0); + accumulate_byte(ifp, 0); + accumulate_short(ifp, 0xFFFF); + accumulate_short(ifp, myseqno); + accumulate_short(ifp, 0xFFFF); + end_message(ifp, MESSAGE_UPDATE, 10); + + babel_ifp->have_buffered_id = 0; +} + +void update_myseqno(void) +{ + myseqno = seqno_plus(myseqno, 1); +} + +void send_self_update(struct interface *ifp) +{ + struct xroute_stream *xroutes; + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) { + if(!if_up(ifp_aux)) + continue; + send_self_update(ifp_aux); + } + return; + } + + debugf(BABEL_DEBUG_COMMON,"Sending self update to %s.", ifp->name); + xroutes = xroute_stream(); + if(xroutes) { + while(1) { + struct xroute *xroute = xroute_stream_next(xroutes); + if(xroute == NULL) break; + send_update(ifp, 0, xroute->prefix, xroute->plen); + } + xroute_stream_done(xroutes); + } else { + flog_err(EC_BABEL_MEMORY, "Couldn't allocate xroute stream."); + } +} + +void send_ihu(struct neighbour *neigh, struct interface *ifp) +{ + babel_interface_nfo *babel_ifp = NULL; + int rxcost, interval; + int ll; + int send_rtt_data; + int msglen; + + if(neigh == NULL && ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) { + if(if_up(ifp_aux)) + continue; + send_ihu(NULL, ifp_aux); + } + return; + } + + if(neigh == NULL) { + struct neighbour *ngh; + FOR_ALL_NEIGHBOURS(ngh) { + if(ngh->ifp == ifp) + send_ihu(ngh, ifp); + } + return; + } + + + if(ifp && neigh->ifp != ifp) + return; + + ifp = neigh->ifp; + babel_ifp = babel_get_if_nfo(ifp); + if(!if_up(ifp)) + return; + + rxcost = neighbour_rxcost(neigh); + interval = (babel_ifp->hello_interval * 3 + 9) / 10; + + /* Conceptually, an IHU is a unicast message. We usually send them as + multicast, since this allows aggregation into a single packet and + avoids an ARP exchange. If we already have a unicast message queued + for this neighbour, however, we might as well piggyback the IHU. */ + debugf(BABEL_DEBUG_COMMON,"Sending %sihu %d on %s to %s.", + unicast_neighbour == neigh ? "unicast " : "", + rxcost, + neigh->ifp->name, + format_address(neigh->address)); + + ll = linklocal(neigh->address); + + if(CHECK_FLAG(babel_ifp->flags, BABEL_IF_TIMESTAMPS) && neigh->hello_send_us + /* Checks whether the RTT data is not too old to be sent. */ + && timeval_minus_msec(&babel_now, + &neigh->hello_rtt_receive_time) < 1000000) { + send_rtt_data = 1; + } else { + neigh->hello_send_us = 0; + send_rtt_data = 0; + } + + /* The length depends on the format of the address, and then an + optional 10-bytes sub-TLV for timestamps (used to compute a RTT). */ + msglen = (ll ? 14 : 22) + (send_rtt_data ? 10 : 0); + + if(unicast_neighbour != neigh) { + start_message(ifp, MESSAGE_IHU, msglen); + accumulate_byte(ifp, ll ? 3 : 2); + accumulate_byte(ifp, 0); + accumulate_short(ifp, rxcost); + accumulate_short(ifp, interval); + if(ll) + accumulate_bytes(ifp, neigh->address + 8, 8); + else + accumulate_bytes(ifp, neigh->address, 16); + if (send_rtt_data) { + accumulate_byte(ifp, SUBTLV_TIMESTAMP); + accumulate_byte(ifp, 8); + accumulate_int(ifp, neigh->hello_send_us); + accumulate_int(ifp, time_us(neigh->hello_rtt_receive_time)); + } + end_message(ifp, MESSAGE_IHU, msglen); + } else { + int rc; + rc = start_unicast_message(neigh, MESSAGE_IHU, msglen); + if(rc < 0) return; + accumulate_unicast_byte(neigh, ll ? 3 : 2); + accumulate_unicast_byte(neigh, 0); + accumulate_unicast_short(neigh, rxcost); + accumulate_unicast_short(neigh, interval); + if(ll) + accumulate_unicast_bytes(neigh, neigh->address + 8, 8); + else + accumulate_unicast_bytes(neigh, neigh->address, 16); + if (send_rtt_data) { + accumulate_unicast_byte(neigh, SUBTLV_TIMESTAMP); + accumulate_unicast_byte(neigh, 8); + accumulate_unicast_int(neigh, neigh->hello_send_us); + accumulate_unicast_int(neigh, + time_us(neigh->hello_rtt_receive_time)); + } + end_unicast_message(neigh, MESSAGE_IHU, msglen); + } +} + +/* Send IHUs to all marginal neighbours */ +void send_marginal_ihu(struct interface *ifp) +{ + struct neighbour *neigh; + FOR_ALL_NEIGHBOURS(neigh) { + if(ifp && neigh->ifp != ifp) + continue; + if(neigh->txcost >= 384 || CHECK_FLAG(neigh->reach, 0xF000) != 0xF000) + send_ihu(neigh, ifp); + } +} + +void +send_request(struct interface *ifp, + const unsigned char *prefix, unsigned char plen) +{ + int v4, pb, len; + + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) { + if(if_up(ifp_aux)) + continue; + send_request(ifp_aux, prefix, plen); + } + return; + } + + /* make sure any buffered updates go out before this request. */ + flushupdates(ifp); + + if(!if_up(ifp)) + return; + + debugf(BABEL_DEBUG_COMMON,"sending request to %s for %s.", + ifp->name, prefix ? format_prefix(prefix, plen) : "any"); + v4 = plen >= 96 && v4mapped(prefix); + pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; + len = !prefix ? 2 : 2 + pb; + + start_message(ifp, MESSAGE_REQUEST, len); + accumulate_byte(ifp, !prefix ? 0 : v4 ? 1 : 2); + accumulate_byte(ifp, !prefix ? 0 : v4 ? plen - 96 : plen); + if(prefix) { + if(v4) + accumulate_bytes(ifp, prefix + 12, pb); + else + accumulate_bytes(ifp, prefix, pb); + } + end_message(ifp, MESSAGE_REQUEST, len); +} + +void +send_unicast_request(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen) +{ + int rc, v4, pb, len; + + /* make sure any buffered updates go out before this request. */ + flushupdates(neigh->ifp); + + debugf(BABEL_DEBUG_COMMON,"sending unicast request to %s for %s.", + format_address(neigh->address), + prefix ? format_prefix(prefix, plen) : "any"); + v4 = plen >= 96 && v4mapped(prefix); + pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; + len = !prefix ? 2 : 2 + pb; + + rc = start_unicast_message(neigh, MESSAGE_REQUEST, len); + if(rc < 0) return; + accumulate_unicast_byte(neigh, !prefix ? 0 : v4 ? 1 : 2); + accumulate_unicast_byte(neigh, !prefix ? 0 : v4 ? plen - 96 : plen); + if(prefix) { + if(v4) + accumulate_unicast_bytes(neigh, prefix + 12, pb); + else + accumulate_unicast_bytes(neigh, prefix, pb); + } + end_unicast_message(neigh, MESSAGE_REQUEST, len); +} + +void +send_multihop_request(struct interface *ifp, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + unsigned short hop_count) +{ + int v4, pb, len; + + /* Make sure any buffered updates go out before this request. */ + flushupdates(ifp); + + if(ifp == NULL) { + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp_aux; + FOR_ALL_INTERFACES(vrf, ifp_aux) { + if(!if_up(ifp_aux)) + continue; + send_multihop_request(ifp_aux, prefix, plen, seqno, id, hop_count); + } + return; + } + + if(!if_up(ifp)) + return; + + debugf(BABEL_DEBUG_COMMON,"Sending request (%d) on %s for %s.", + hop_count, ifp->name, format_prefix(prefix, plen)); + v4 = plen >= 96 && v4mapped(prefix); + pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; + len = 6 + 8 + pb; + + start_message(ifp, MESSAGE_MH_REQUEST, len); + accumulate_byte(ifp, v4 ? 1 : 2); + accumulate_byte(ifp, v4 ? plen - 96 : plen); + accumulate_short(ifp, seqno); + accumulate_byte(ifp, hop_count); + accumulate_byte(ifp, 0); + accumulate_bytes(ifp, id, 8); + if(prefix) { + if(v4) + accumulate_bytes(ifp, prefix + 12, pb); + else + accumulate_bytes(ifp, prefix, pb); + } + end_message(ifp, MESSAGE_MH_REQUEST, len); +} + +void +send_unicast_multihop_request(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + unsigned short hop_count) +{ + int rc, v4, pb, len; + + /* Make sure any buffered updates go out before this request. */ + flushupdates(neigh->ifp); + + debugf(BABEL_DEBUG_COMMON,"Sending multi-hop request to %s for %s (%d hops).", + format_address(neigh->address), + format_prefix(prefix, plen), hop_count); + v4 = plen >= 96 && v4mapped(prefix); + pb = v4 ? ((plen - 96) + 7) / 8 : (plen + 7) / 8; + len = 6 + 8 + pb; + + rc = start_unicast_message(neigh, MESSAGE_MH_REQUEST, len); + if(rc < 0) return; + accumulate_unicast_byte(neigh, v4 ? 1 : 2); + accumulate_unicast_byte(neigh, v4 ? plen - 96 : plen); + accumulate_unicast_short(neigh, seqno); + accumulate_unicast_byte(neigh, hop_count); + accumulate_unicast_byte(neigh, 0); + accumulate_unicast_bytes(neigh, id, 8); + if(prefix) { + if(v4) + accumulate_unicast_bytes(neigh, prefix + 12, pb); + else + accumulate_unicast_bytes(neigh, prefix, pb); + } + end_unicast_message(neigh, MESSAGE_MH_REQUEST, len); +} + +void +send_request_resend(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, unsigned char *id) +{ + if(neigh) + send_unicast_multihop_request(neigh, prefix, plen, seqno, id, 127); + else + send_multihop_request(NULL, prefix, plen, seqno, id, 127); + + record_resend(RESEND_REQUEST, prefix, plen, seqno, id, + neigh ? neigh->ifp : NULL, resend_delay); +} + +void +handle_request(struct neighbour *neigh, const unsigned char *prefix, + unsigned char plen, unsigned char hop_count, + unsigned short seqno, const unsigned char *id) +{ + struct xroute *xroute; + struct babel_route *route; + struct neighbour *successor = NULL; + + xroute = find_xroute(prefix, plen); + route = find_installed_route(prefix, plen); + + if(xroute && (!route || xroute->metric <= kernel_metric)) { + if(hop_count > 0 && memcmp(id, myid, 8) == 0) { + if(seqno_compare(seqno, myseqno) > 0) { + if(seqno_minus(seqno, myseqno) > 100) { + /* Hopelessly out-of-date request */ + return; + } + update_myseqno(); + } + } + send_update(neigh->ifp, 1, prefix, plen); + return; + } + + if(route && + (memcmp(id, route->src->id, 8) != 0 || + seqno_compare(seqno, route->seqno) <= 0)) { + send_update(neigh->ifp, 1, prefix, plen); + return; + } + + if(hop_count <= 1) + return; + + if(route && memcmp(id, route->src->id, 8) == 0 && + seqno_minus(seqno, route->seqno) > 100) { + /* Hopelessly out-of-date */ + return; + } + + if(request_redundant(neigh->ifp, prefix, plen, seqno, id)) + return; + + /* Let's try to forward this request. */ + if(route && route_metric(route) < INFINITY) + successor = route->neigh; + + if(!successor || successor == neigh) { + /* We were about to forward a request to its requestor. Try to + find a different neighbour to forward the request to. */ + struct babel_route *other_route; + + other_route = find_best_route(prefix, plen, 0, neigh); + if(other_route && route_metric(other_route) < INFINITY) + successor = other_route->neigh; + } + + if(!successor || successor == neigh) + /* Give up */ + return; + + send_unicast_multihop_request(successor, prefix, plen, seqno, id, + hop_count - 1); + record_resend(RESEND_REQUEST, prefix, plen, seqno, id, + neigh->ifp, 0); +} diff --git a/babeld/message.h b/babeld/message.h new file mode 100644 index 0000000..7cf062a --- /dev/null +++ b/babeld/message.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifndef BABEL_MESSAGE_H +#define BABEL_MESSAGE_H + +#include "babel_interface.h" + +#define MAX_BUFFERED_UPDATES 200 + +#define BUCKET_TOKENS_MAX 200 +#define BUCKET_TOKENS_PER_SEC 40 + +/* A registry of assigned TLV and sub-TLV types is available at + http://www.pps.univ-paris-diderot.fr/~jch/software/babel/babel-tlv-registry.text +*/ +#define MESSAGE_PAD1 0 +#define MESSAGE_PADN 1 +#define MESSAGE_ACK_REQ 2 +#define MESSAGE_ACK 3 +#define MESSAGE_HELLO 4 +#define MESSAGE_IHU 5 +#define MESSAGE_ROUTER_ID 6 +#define MESSAGE_NH 7 +#define MESSAGE_UPDATE 8 +#define MESSAGE_REQUEST 9 +#define MESSAGE_MH_REQUEST 10 +#define MESSAGE_MAX 10 + +/* Protocol extension through sub-TLVs. */ +#define SUBTLV_PAD1 0 +#define SUBTLV_PADN 1 +#define SUBTLV_DIVERSITY 2 /* Also known as babelz. */ +#define SUBTLV_TIMESTAMP 3 /* Used to compute RTT. */ +#define SUBTLV_SOURCE_PREFIX 128 /* Source-specific routing. */ +#define SUBTLV_MANDATORY 0x80 + +extern unsigned short myseqno; + +extern int broadcast_ihu; +extern int split_horizon; + +extern struct neighbour *unicast_neighbour; +extern struct timeval unicast_flush_timeout; + +void parse_packet(const unsigned char *from, struct interface *ifp, + const unsigned char *packet, int packetlen); +void flushbuf(struct interface *ifp); +void flushupdates(struct interface *ifp); +void send_ack(struct neighbour *neigh, unsigned short nonce, + unsigned short interval); +void send_hello_noupdate(struct interface *ifp, unsigned interval); +void send_hello(struct interface *ifp); +void flush_unicast(int dofree); +void send_update(struct interface *ifp, int urgent, + const unsigned char *prefix, unsigned char plen); +void send_update_resend(struct interface *ifp, + const unsigned char *prefix, unsigned char plen); +void send_wildcard_retraction(struct interface *ifp); +void update_myseqno(void); +void send_self_update(struct interface *ifp); +void send_ihu(struct neighbour *neigh, struct interface *ifp); +void send_marginal_ihu(struct interface *ifp); +void send_request(struct interface *ifp, + const unsigned char *prefix, unsigned char plen); +void send_unicast_request(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen); +void send_multihop_request(struct interface *ifp, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + unsigned short hop_count); +void +send_unicast_multihop_request(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + unsigned short hop_count); +void send_request_resend(struct neighbour *neigh, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, unsigned char *id); +void handle_request(struct neighbour *neigh, const unsigned char *prefix, + unsigned char plen, unsigned char hop_count, + unsigned short seqno, const unsigned char *id); + +#endif diff --git a/babeld/neighbour.c b/babeld/neighbour.c new file mode 100644 index 0000000..65e613c --- /dev/null +++ b/babeld/neighbour.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include "if.h" + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" +#include "babel_interface.h" +#include "neighbour.h" +#include "source.h" +#include "route.h" +#include "message.h" +#include "resend.h" +#include "babel_errors.h" + +struct neighbour *neighs = NULL; + +static struct neighbour * +find_neighbour_nocreate(const unsigned char *address, struct interface *ifp) +{ + struct neighbour *neigh; + FOR_ALL_NEIGHBOURS(neigh) { + if(memcmp(address, neigh->address, 16) == 0 && neigh->ifp == ifp) + return neigh; + } + return NULL; +} + +void flush_neighbour(struct neighbour *neigh) +{ + debugf(BABEL_DEBUG_COMMON,"Flushing neighbour %s (reach 0x%04x)", + format_address(neigh->address), neigh->reach); + flush_neighbour_routes(neigh); + if(unicast_neighbour == neigh) + flush_unicast(1); + flush_resends(neigh); + + if(neighs == neigh) { + neighs = neigh->next; + } else { + struct neighbour *previous = neighs; + while(previous->next != neigh) + previous = previous->next; + previous->next = neigh->next; + } + free(neigh); +} + +struct neighbour * +find_neighbour(const unsigned char *address, struct interface *ifp) +{ + struct neighbour *neigh; + const struct timeval zero = {0, 0}; + + neigh = find_neighbour_nocreate(address, ifp); + if(neigh) + return neigh; + + debugf(BABEL_DEBUG_COMMON,"Creating neighbour %s on %s.", + format_address(address), ifp->name); + + neigh = malloc(sizeof(struct neighbour)); + if(neigh == NULL) { + flog_err(EC_BABEL_MEMORY, "malloc(neighbour): %s", + safe_strerror(errno)); + return NULL; + } + + neigh->hello_seqno = -1; + memcpy(neigh->address, address, 16); + neigh->reach = 0; + neigh->txcost = INFINITY; + neigh->ihu_time = babel_now; + neigh->hello_time = zero; + neigh->hello_interval = 0; + neigh->ihu_interval = 0; + neigh->hello_send_us = 0; + neigh->hello_rtt_receive_time = zero; + neigh->rtt = 0; + neigh->rtt_time = zero; + neigh->ifp = ifp; + neigh->next = neighs; + neighs = neigh; + send_hello(ifp); + return neigh; +} + +/* Recompute a neighbour's rxcost. Return true if anything changed. */ +int update_neighbour(struct neighbour *neigh, int hello, int hello_interval) +{ + int missed_hellos; + int rc = 0; + + if(hello < 0) { + if(neigh->hello_interval == 0) + return rc; + missed_hellos = + ((int)timeval_minus_msec(&babel_now, &neigh->hello_time) - + neigh->hello_interval * 7) / + (neigh->hello_interval * 10); + if(missed_hellos <= 0) + return rc; + timeval_add_msec(&neigh->hello_time, &neigh->hello_time, + missed_hellos * neigh->hello_interval * 10); + } else { + if(neigh->hello_seqno >= 0 && neigh->reach > 0) { + missed_hellos = seqno_minus(hello, neigh->hello_seqno) - 1; + if(missed_hellos < -8) { + /* Probably a neighbour that rebooted and lost its seqno. + Reboot the universe. */ + neigh->reach = 0; + missed_hellos = 0; + rc = 1; + } else if(missed_hellos < 0) { + if(hello_interval > neigh->hello_interval) { + /* This neighbour has increased its hello interval, + and we didn't notice. */ + neigh->reach <<= -missed_hellos; + missed_hellos = 0; + } else { + /* Late hello. Probably due to the link layer buffering + packets during a link outage. Ignore it, but reset + the expected seqno. */ + neigh->hello_seqno = hello; + hello = -1; + missed_hellos = 0; + } + rc = 1; + } + } else { + missed_hellos = 0; + } + neigh->hello_time = babel_now; + neigh->hello_interval = hello_interval; + } + + if(missed_hellos > 0) { + neigh->reach >>= missed_hellos; + neigh->hello_seqno = seqno_plus(neigh->hello_seqno, missed_hellos); + rc = 1; + } + + if(hello >= 0) { + neigh->hello_seqno = hello; + neigh->reach >>= 1; + SET_FLAG(neigh->reach, 0x8000); + if(CHECK_FLAG(neigh->reach, 0xFC00) != 0xFC00) + rc = 1; + } + + /* Make sure to give neighbours some feedback early after association */ + if(CHECK_FLAG(neigh->reach, 0xBF00) == 0x8000) { + /* A new neighbour */ + send_hello(neigh->ifp); + } else { + /* Don't send hellos, in order to avoid a positive feedback loop. */ + int a = CHECK_FLAG(neigh->reach, 0xC000); + int b = CHECK_FLAG(neigh->reach, 0x3000); + if((a == 0xC000 && b == 0) || (a == 0 && b == 0x3000)) { + /* Reachability is either 1100 or 0011 */ + send_self_update(neigh->ifp); + } + } + + if(CHECK_FLAG(neigh->reach, 0xFC00) == 0xC000) { + /* This is a newish neighbour, let's request a full route dump. + We ought to avoid this when the network is dense */ + send_unicast_request(neigh, NULL, 0); + send_ihu(neigh, NULL); + } + return rc; +} + +static int reset_txcost(struct neighbour *neigh) +{ + unsigned delay; + + delay = timeval_minus_msec(&babel_now, &neigh->ihu_time); + + if(neigh->ihu_interval > 0 && delay < neigh->ihu_interval * 10U * 3U) + return 0; + + /* If we're losing a lot of packets, we probably lost an IHU too */ + if (delay >= 180000 || CHECK_FLAG(neigh->reach, 0xFFF0) == 0 || + (neigh->ihu_interval > 0 && delay >= neigh->ihu_interval * 10U * 10U)) { + neigh->txcost = INFINITY; + neigh->ihu_time = babel_now; + return 1; + } + + return 0; +} + +unsigned neighbour_txcost(struct neighbour *neigh) +{ + return neigh->txcost; +} + +unsigned check_neighbours(void) +{ + struct neighbour *neigh; + int changed, rc; + unsigned msecs = 50000; + + debugf(BABEL_DEBUG_COMMON,"Checking neighbours."); + + neigh = neighs; + while(neigh) { + changed = update_neighbour(neigh, -1, 0); + + if(neigh->reach == 0 || + neigh->hello_time.tv_sec > babel_now.tv_sec || /* clock stepped */ + timeval_minus_msec(&babel_now, &neigh->hello_time) > 300000) { + struct neighbour *old = neigh; + neigh = neigh->next; + flush_neighbour(old); + continue; + } + + rc = reset_txcost(neigh); + changed = changed || rc; + + update_neighbour_metric(neigh, changed); + + if(neigh->hello_interval > 0) + msecs = MIN(msecs, neigh->hello_interval * 10U); + if(neigh->ihu_interval > 0) + msecs = MIN(msecs, neigh->ihu_interval * 10U); + neigh = neigh->next; + } + + return msecs; +} + +unsigned neighbour_rxcost(struct neighbour *neigh) +{ + unsigned delay; + unsigned short reach = neigh->reach; + + delay = timeval_minus_msec(&babel_now, &neigh->hello_time); + + if(CHECK_FLAG(reach, 0xFFF0) == 0 || delay >= 180000) { + return INFINITY; + } else if (CHECK_FLAG(babel_get_if_nfo(neigh->ifp)->flags, BABEL_IF_LQ)) { + int sreach = + (CHECK_FLAG(reach, 0x8000) >> 2) + + (CHECK_FLAG(reach, 0x4000) >> 1) + + CHECK_FLAG(reach, 0x3FFF); + /* 0 <= sreach <= 0x7FFF */ + int cost = (0x8000 * babel_get_if_nfo(neigh->ifp)->cost) / (sreach + 1); + /* cost >= interface->cost */ + if(delay >= 40000) + cost = (cost * (delay - 20000) + 10000) / 20000; + return MIN(cost, INFINITY); + } else { + /* To lose one hello is a misfortune, to lose two is carelessness. */ + if (CHECK_FLAG(reach, 0xC000) == 0xC000) + return babel_get_if_nfo(neigh->ifp)->cost; + else if (CHECK_FLAG(reach, 0xC000) == 0) + return INFINITY; + else if (CHECK_FLAG(reach, 0x2000)) + return babel_get_if_nfo(neigh->ifp)->cost; + else + return INFINITY; + } +} + +unsigned neighbour_rttcost(struct neighbour *neigh) +{ + struct interface *ifp = neigh->ifp; + babel_interface_nfo *babel_ifp = babel_get_if_nfo(ifp); + + if(!babel_ifp->max_rtt_penalty || !valid_rtt(neigh)) + return 0; + + /* Function: linear behaviour between rtt_min and rtt_max. */ + if(neigh->rtt <= babel_ifp->rtt_min) { + return 0; + } else if(neigh->rtt <= babel_ifp->rtt_max) { + unsigned long long tmp = + (unsigned long long)babel_ifp->max_rtt_penalty * + (neigh->rtt - babel_ifp->rtt_min) / + (babel_ifp->rtt_max - babel_ifp->rtt_min); + assert(CHECK_FLAG(tmp, 0x7FFFFFFF) == tmp); + return tmp; + } else { + return babel_ifp->max_rtt_penalty; + } +} + +unsigned neighbour_cost(struct neighbour *neigh) +{ + unsigned a, b, cost; + + if(!if_up(neigh->ifp)) + return INFINITY; + + a = neighbour_txcost(neigh); + + if(a >= INFINITY) + return INFINITY; + + b = neighbour_rxcost(neigh); + if(b >= INFINITY) + return INFINITY; + + if (!CHECK_FLAG(babel_get_if_nfo(neigh->ifp)->flags, BABEL_IF_LQ) + || (a < 256 && b < 256)) { + cost = a; + } else { + /* a = 256/alpha, b = 256/beta, where alpha and beta are the expected + probabilities of a packet getting through in the direct and reverse + directions. */ + a = MAX(a, 256); + b = MAX(b, 256); + /* 1/(alpha * beta), which is just plain ETX. */ + /* Since a and b are capped to 16 bits, overflow is impossible. */ + cost = (a * b + 128) >> 8; + } + + cost += neighbour_rttcost(neigh); + + return MIN(cost, INFINITY); +} + +int valid_rtt(struct neighbour *neigh) +{ + return (timeval_minus_msec(&babel_now, &neigh->rtt_time) < 180000) ? 1 : 0; +} diff --git a/babeld/neighbour.h b/babeld/neighbour.h new file mode 100644 index 0000000..2111663 --- /dev/null +++ b/babeld/neighbour.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifndef BABEL_NEIGHBOUR_H +#define BABEL_NEIGHBOUR_H + +struct neighbour { + struct neighbour *next; + /* This is -1 when unknown, so don't make it unsigned */ + int hello_seqno; + unsigned char address[16]; + unsigned short reach; + unsigned short txcost; + struct timeval hello_time; + struct timeval ihu_time; + unsigned short hello_interval; /* in centiseconds */ + unsigned short ihu_interval; /* in centiseconds */ + /* Used for RTT estimation. */ + /* Absolute time (modulo 2^32) at which the Hello was sent, + according to remote clock. */ + unsigned int hello_send_us; + struct timeval hello_rtt_receive_time; + unsigned int rtt; + struct timeval rtt_time; + struct interface *ifp; +}; + +extern struct neighbour *neighs; + +#define FOR_ALL_NEIGHBOURS(_neigh) \ + for(_neigh = neighs; _neigh; _neigh = _neigh->next) + +int neighbour_valid(struct neighbour *neigh); +void flush_neighbour(struct neighbour *neigh); +struct neighbour *find_neighbour(const unsigned char *address, + struct interface *ifp); +int update_neighbour(struct neighbour *neigh, int hello, int hello_interval); +unsigned check_neighbours(void); +unsigned neighbour_txcost(struct neighbour *neigh); +unsigned neighbour_rxcost(struct neighbour *neigh); +unsigned neighbour_rttcost(struct neighbour *neigh); +unsigned neighbour_cost(struct neighbour *neigh); +int valid_rtt(struct neighbour *neigh); + +#endif /* BABEL_NEIGHBOUR_H */ diff --git a/babeld/net.c b/babeld/net.c new file mode 100644 index 0000000..15ea2de --- /dev/null +++ b/babeld/net.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "babeld.h" +#include "util.h" +#include "net.h" +#include "sockopt.h" + +int +babel_socket(int port) +{ + struct sockaddr_in6 sin6; + int s, rc; + int saved_errno; + int one = 1, zero = 0; + + s = socket(PF_INET6, SOCK_DGRAM, 0); + if(s < 0) + return -1; + + rc = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if(rc < 0) + goto fail; + + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if(rc < 0) + goto fail; + + rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &zero, sizeof(zero)); + if(rc < 0) + goto fail; + + rc = setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &one, sizeof(one)); + if(rc < 0) + goto fail; + + rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &one, sizeof(one)); + if(rc < 0) + goto fail; + + setsockopt_ipv6_tclass (s, IPTOS_PREC_INTERNETCONTROL); + + rc = fcntl(s, F_GETFL, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_GETFD, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC); + if(rc < 0) + goto fail; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) + goto fail; + + return s; + + fail: + saved_errno = errno; + close(s); + errno = saved_errno; + return -1; +} + +int +babel_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen) +{ + struct iovec iovec; + struct msghdr msg; + int rc; + + memset(&msg, 0, sizeof(msg)); + iovec.iov_base = buf; + iovec.iov_len = buflen; + msg.msg_name = sin; + msg.msg_namelen = slen; + msg.msg_iov = &iovec; + msg.msg_iovlen = 1; + + rc = recvmsg(s, &msg, 0); + return rc; +} + +int +babel_send(int s, + void *buf1, int buflen1, void *buf2, int buflen2, + struct sockaddr *sin, int slen) +{ + struct iovec iovec[2]; + struct msghdr msg; + int rc; + + iovec[0].iov_base = buf1; + iovec[0].iov_len = buflen1; + iovec[1].iov_base = buf2; + iovec[1].iov_len = buflen2; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = sin; + msg.msg_namelen = slen; + msg.msg_iov = iovec; + msg.msg_iovlen = 2; + + again: + rc = sendmsg(s, &msg, 0); + if(rc < 0) { + if(errno == EINTR) + goto again; + else if(errno == EAGAIN) { + int rc2; + rc2 = wait_for_fd(1, s, 5); + if(rc2 > 0) + goto again; + errno = EAGAIN; + } + } + return rc; +} + +int +tcp_server_socket(int port, int local) +{ + struct sockaddr_in6 sin6; + int s, rc, saved_errno; + int one = 1; + + s = socket(PF_INET6, SOCK_STREAM, 0); + if(s < 0) + return -1; + + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_GETFL, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK)); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_GETFD, 0); + if(rc < 0) + goto fail; + + rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC); + if(rc < 0) + goto fail; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + if(local) { + rc = inet_pton(AF_INET6, "::1", &sin6.sin6_addr); + if(rc < 0) + goto fail; + } + rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6)); + if(rc < 0) + goto fail; + + rc = listen(s, 2); + if(rc < 0) + goto fail; + + return s; + + fail: + saved_errno = errno; + close(s); + errno = saved_errno; + return -1; +} diff --git a/babeld/net.h b/babeld/net.h new file mode 100644 index 0000000..2559602 --- /dev/null +++ b/babeld/net.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifndef BABEL_NET_H +#define BABEL_NET_H + +int babel_socket(int port); +int babel_recv(int s, void *buf, int buflen, struct sockaddr *sin, int slen); +int babel_send(int s, + void *buf1, int buflen1, void *buf2, int buflen2, + struct sockaddr *sin, int slen); +int tcp_server_socket(int port, int local); + +#endif /* BABEL_NET_H */ diff --git a/babeld/resend.c b/babeld/resend.c new file mode 100644 index 0000000..254faac --- /dev/null +++ b/babeld/resend.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include "if.h" + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" +#include "neighbour.h" +#include "resend.h" +#include "message.h" +#include "babel_interface.h" + +struct timeval resend_time = {0, 0}; +struct resend *to_resend = NULL; + +static int +resend_match(struct resend *resend, + int kind, const unsigned char *prefix, unsigned char plen) +{ + return (resend->kind == kind && + resend->plen == plen && memcmp(resend->prefix, prefix, 16) == 0); +} + +/* This is called by neigh.c when a neighbour is flushed */ + +void +flush_resends(struct neighbour *neigh) +{ + /* Nothing for now */ +} + +static struct resend * +find_resend(int kind, const unsigned char *prefix, unsigned char plen, + struct resend **previous_return) +{ + struct resend *current, *previous; + + previous = NULL; + current = to_resend; + while(current) { + if(resend_match(current, kind, prefix, plen)) { + if(previous_return) + *previous_return = previous; + return current; + } + previous = current; + current = current->next; + } + + return NULL; +} + +struct resend * +find_request(const unsigned char *prefix, unsigned char plen, + struct resend **previous_return) +{ + return find_resend(RESEND_REQUEST, prefix, plen, previous_return); +} + +int +record_resend(int kind, const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + struct interface *ifp, int delay) +{ + struct resend *resend; + unsigned int ifindex = ifp ? ifp->ifindex : 0; + + if((kind == RESEND_REQUEST && + input_filter(NULL, prefix, plen, NULL, ifindex) >= INFINITY) || + (kind == RESEND_UPDATE && + output_filter(NULL, prefix, plen, ifindex) >= INFINITY)) + return 0; + + if(delay >= 0xFFFF) + delay = 0xFFFF; + + resend = find_resend(kind, prefix, plen, NULL); + if(resend) { + if(resend->delay && delay) + resend->delay = MIN(resend->delay, delay); + else if(delay) + resend->delay = delay; + resend->time = babel_now; + resend->max = RESEND_MAX; + if(id && memcmp(resend->id, id, 8) == 0 && + seqno_compare(resend->seqno, seqno) > 0) { + return 0; + } + if(id) + memcpy(resend->id, id, 8); + else + memset(resend->id, 0, 8); + resend->seqno = seqno; + if(resend->ifp != ifp) + resend->ifp = NULL; + } else { + resend = malloc(sizeof(struct resend)); + if(resend == NULL) + return -1; + resend->kind = kind; + resend->max = RESEND_MAX; + resend->delay = delay; + memcpy(resend->prefix, prefix, 16); + resend->plen = plen; + resend->seqno = seqno; + if(id) + memcpy(resend->id, id, 8); + else + memset(resend->id, 0, 8); + resend->ifp = ifp; + resend->time = babel_now; + resend->next = to_resend; + to_resend = resend; + } + + if(resend->delay) { + struct timeval timeout; + timeval_add_msec(&timeout, &resend->time, resend->delay); + timeval_min(&resend_time, &timeout); + } + return 1; +} + +static int +resend_expired(struct resend *resend) +{ + switch(resend->kind) { + case RESEND_REQUEST: + return timeval_minus_msec(&babel_now, &resend->time) >= REQUEST_TIMEOUT; + default: + return resend->max <= 0; + } +} + +int +unsatisfied_request(const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id) +{ + struct resend *request; + + request = find_request(prefix, plen, NULL); + if(request == NULL || resend_expired(request)) + return 0; + + if(memcmp(request->id, id, 8) != 0 || + seqno_compare(request->seqno, seqno) <= 0) + return 1; + + return 0; +} + +/* Determine whether a given request should be forwarded. */ +int +request_redundant(struct interface *ifp, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id) +{ + struct resend *request; + + request = find_request(prefix, plen, NULL); + if(request == NULL || resend_expired(request)) + return 0; + + if(memcmp(request->id, id, 8) == 0 && + seqno_compare(request->seqno, seqno) > 0) + return 0; + + if(request->ifp != NULL && request->ifp != ifp) + return 0; + + if(request->max > 0) + /* Will be resent. */ + return 1; + + if(timeval_minus_msec(&babel_now, &request->time) < + (ifp ? MIN(babel_get_if_nfo(ifp)->hello_interval, 1000) : 1000)) + /* Fairly recent. */ + return 1; + + return 0; +} + +int +satisfy_request(const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + struct interface *ifp) +{ + struct resend *request, *previous; + + request = find_request(prefix, plen, &previous); + if(request == NULL) + return 0; + + if(ifp != NULL && request->ifp != ifp) + return 0; + + if(memcmp(request->id, id, 8) != 0 || + seqno_compare(request->seqno, seqno) <= 0) { + /* We cannot remove the request, as we may be walking the list right + now. Mark it as expired, so that expire_resend will remove it. */ + request->max = 0; + request->time.tv_sec = 0; + recompute_resend_time(); + return 1; + } + + return 0; +} + +void +expire_resend(void) +{ + struct resend *current, *previous; + int recompute = 0; + + previous = NULL; + current = to_resend; + while(current) { + if(resend_expired(current)) { + if(previous == NULL) { + to_resend = current->next; + free(current); + current = to_resend; + } else { + previous->next = current->next; + free(current); + current = previous->next; + } + recompute = 1; + } else { + previous = current; + current = current->next; + } + } + if(recompute) + recompute_resend_time(); +} + +void +recompute_resend_time(void) +{ + struct resend *request; + struct timeval resend = {0, 0}; + + request = to_resend; + while(request) { + if(!resend_expired(request) && request->delay > 0 && request->max > 0) { + struct timeval timeout; + timeval_add_msec(&timeout, &request->time, request->delay); + timeval_min(&resend, &timeout); + } + request = request->next; + } + + resend_time = resend; +} + +void +do_resend(void) +{ + struct resend *resend; + + resend = to_resend; + while(resend) { + if(!resend_expired(resend) && resend->delay > 0 && resend->max > 0) { + struct timeval timeout; + timeval_add_msec(&timeout, &resend->time, resend->delay); + if(timeval_compare(&babel_now, &timeout) >= 0) { + switch(resend->kind) { + case RESEND_REQUEST: + send_multihop_request(resend->ifp, + resend->prefix, resend->plen, + resend->seqno, resend->id, 127); + break; + case RESEND_UPDATE: + send_update(resend->ifp, 1, + resend->prefix, resend->plen); + break; + default: abort(); + } + resend->delay = MIN(0xFFFF, resend->delay * 2); + resend->max--; + } + } + resend = resend->next; + } + recompute_resend_time(); +} diff --git a/babeld/resend.h b/babeld/resend.h new file mode 100644 index 0000000..d0ec046 --- /dev/null +++ b/babeld/resend.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ +#ifndef BABEL_RESEND_H +#define BABEL_RESEND_H + +#define REQUEST_TIMEOUT 65000 +#define RESEND_MAX 3 + +#define RESEND_REQUEST 1 +#define RESEND_UPDATE 2 + +struct resend { + unsigned char kind; + unsigned char max; + unsigned short delay; + struct timeval time; + unsigned char prefix[16]; + unsigned char plen; + unsigned short seqno; + unsigned char id[8]; + struct interface *ifp; + struct resend *next; +}; + +extern struct timeval resend_time; + +struct resend *find_request(const unsigned char *prefix, unsigned char plen, + struct resend **previous_return); +void flush_resends(struct neighbour *neigh); +int record_resend(int kind, const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + struct interface *ifp, int delay); +int unsatisfied_request(const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id); +int request_redundant(struct interface *ifp, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id); +int satisfy_request(const unsigned char *prefix, unsigned char plen, + unsigned short seqno, const unsigned char *id, + struct interface *ifp); + +void expire_resend(void); +void recompute_resend_time(void); +void do_resend(void); + +#endif /* BABEL_RESEND_H */ diff --git a/babeld/route.c b/babeld/route.c new file mode 100644 index 0000000..2c7e923 --- /dev/null +++ b/babeld/route.c @@ -0,0 +1,1129 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#include +#include "if.h" + +#include "babeld.h" +#include "util.h" +#include "kernel.h" +#include "babel_interface.h" +#include "source.h" +#include "neighbour.h" +#include "route.h" +#include "xroute.h" +#include "message.h" +#include "resend.h" +#include "babel_errors.h" + +static void consider_route(struct babel_route *route); + +struct babel_route **routes = NULL; +static int route_slots = 0, max_route_slots = 0; +int kernel_metric = 0; +enum babel_diversity diversity_kind = DIVERSITY_NONE; +int diversity_factor = BABEL_DEFAULT_DIVERSITY_FACTOR; +int keep_unfeasible = 0; + +int smoothing_half_life = 0; +static int two_to_the_one_over_hl = 0; /* 2^(1/hl) * 0x10000 */ + +/* We maintain a list of "slots", ordered by prefix. Every slot + contains a linked list of the routes to this prefix, with the + installed route, if any, at the head of the list. */ + +static int +route_compare(const unsigned char *prefix, unsigned char plen, + struct babel_route *route) +{ + int i = memcmp(prefix, route->src->prefix, 16); + if(i != 0) + return i; + + if(plen < route->src->plen) + return -1; + else if(plen > route->src->plen) + return 1; + else + return 0; +} + +/* Performs binary search, returns -1 in case of failure. In the latter + case, new_return is the place where to insert the new element. */ + +static int +find_route_slot(const unsigned char *prefix, unsigned char plen, + int *new_return) +{ + int p, m, g, c; + + if(route_slots < 1) { + if(new_return) + *new_return = 0; + return -1; + } + + p = 0; g = route_slots - 1; + + do { + m = (p + g) / 2; + c = route_compare(prefix, plen, routes[m]); + if(c == 0) + return m; + else if(c < 0) + g = m - 1; + else + p = m + 1; + } while(p <= g); + + if(new_return) + *new_return = p; + + return -1; +} + +struct babel_route * +find_route(const unsigned char *prefix, unsigned char plen, + struct neighbour *neigh, const unsigned char *nexthop) +{ + struct babel_route *route; + int i = find_route_slot(prefix, plen, NULL); + + if(i < 0) + return NULL; + + route = routes[i]; + + while(route) { + if(route->neigh == neigh && memcmp(route->nexthop, nexthop, 16) == 0) + return route; + route = route->next; + } + + return NULL; +} + +struct babel_route * +find_installed_route(const unsigned char *prefix, unsigned char plen) +{ + int i = find_route_slot(prefix, plen, NULL); + + if(i >= 0 && routes[i]->installed) + return routes[i]; + + return NULL; +} + +/* Returns an overestimate of the number of installed routes. */ +int +installed_routes_estimate(void) +{ + return route_slots; +} + +static int +resize_route_table(int new_slots) +{ + struct babel_route **new_routes; + assert(new_slots >= route_slots); + + if(new_slots == 0) { + new_routes = NULL; + free(routes); + } else { + new_routes = realloc(routes, new_slots * sizeof(struct babel_route*)); + if(new_routes == NULL) + return -1; + } + + max_route_slots = new_slots; + routes = new_routes; + return 1; +} + +/* Insert a route into the table. If successful, retains the route. + On failure, caller must free the route. */ +static struct babel_route * +insert_route(struct babel_route *route) +{ + int i, n = 0; + + assert(!route->installed); + + i = find_route_slot(route->src->prefix, route->src->plen, &n); + + if(i < 0) { + if(route_slots >= max_route_slots) + resize_route_table(max_route_slots < 1 ? 8 : 2 * max_route_slots); + if(route_slots >= max_route_slots) + return NULL; + assert(routes); + route->next = NULL; + if(n < route_slots) + memmove(routes + n + 1, routes + n, + (route_slots - n) * sizeof(struct babel_route*)); + route_slots++; + routes[n] = route; + } else { + struct babel_route *r; + r = routes[i]; + while(r->next) + r = r->next; + r->next = route; + route->next = NULL; + } + + return route; +} + +void +flush_route(struct babel_route *route) +{ + int i; + struct source *src; + unsigned oldmetric; + int lost = 0; + + oldmetric = route_metric(route); + src = route->src; + + if(route->installed) { + uninstall_route(route); + lost = 1; + } + + i = find_route_slot(route->src->prefix, route->src->plen, NULL); + assert(i >= 0 && i < route_slots); + + if(route == routes[i]) { + routes[i] = route->next; + route->next = NULL; + free(route); + + if(routes[i] == NULL) { + if(i < route_slots - 1) + memmove(routes + i, routes + i + 1, + (route_slots - i - 1) * sizeof(struct babel_route*)); + routes[route_slots - 1] = NULL; + route_slots--; + } + + if(route_slots == 0) + resize_route_table(0); + else if(max_route_slots > 8 && route_slots < max_route_slots / 4) + resize_route_table(max_route_slots / 2); + } else { + struct babel_route *r = routes[i]; + while(r->next != route) + r = r->next; + r->next = route->next; + route->next = NULL; + free(route); + } + + if(lost) + route_lost(src, oldmetric); + + release_source(src); +} + +void +flush_all_routes(void) +{ + int i; + + /* Start from the end, to avoid shifting the table. */ + i = route_slots - 1; + while(i >= 0) { + while(i < route_slots) { + /* Uninstall first, to avoid calling route_lost. */ + if(routes[i]->installed) + uninstall_route(routes[i]); + flush_route(routes[i]); + } + i--; + } + + check_sources_released(); +} + +void +flush_neighbour_routes(struct neighbour *neigh) +{ + int i; + + i = 0; + while(i < route_slots) { + struct babel_route *r; + r = routes[i]; + while(r) { + if(r->neigh == neigh) { + flush_route(r); + goto again; + } + r = r->next; + } + i++; + again: + ; + } +} + +void +flush_interface_routes(struct interface *ifp, int v4only) +{ + int i; + + i = 0; + while(i < route_slots) { + struct babel_route *r; + r = routes[i]; + while(r) { + if(r->neigh->ifp == ifp && + (!v4only || v4mapped(r->nexthop))) { + flush_route(r); + goto again; + } + r = r->next; + } + i++; + again: + ; + } +} + +struct route_stream { + int installed; + int index; + struct babel_route *next; +}; + + +struct route_stream * +route_stream(int installed) +{ + struct route_stream *stream; + + stream = malloc(sizeof(struct route_stream)); + if(stream == NULL) + return NULL; + + stream->installed = installed; + stream->index = installed ? 0 : -1; + stream->next = NULL; + + return stream; +} + +struct babel_route * +route_stream_next(struct route_stream *stream) +{ + if(stream->installed) { + while(stream->index < route_slots && !routes[stream->index]->installed) + stream->index++; + + if(stream->index < route_slots) + return routes[stream->index++]; + else + return NULL; + } else { + struct babel_route *next; + if(!stream->next) { + stream->index++; + if(stream->index >= route_slots) + return NULL; + stream->next = routes[stream->index]; + } + next = stream->next; + stream->next = next->next; + return next; + } +} + +void +route_stream_done(struct route_stream *stream) +{ + free(stream); +} + +static int +metric_to_kernel(int metric) +{ + return metric < INFINITY ? kernel_metric : KERNEL_INFINITY; +} + +/* This is used to maintain the invariant that the installed route is at + the head of the list. */ +static void +move_installed_route(struct babel_route *route, int i) +{ + assert(i >= 0 && i < route_slots); + assert(route->installed); + + if(route != routes[i]) { + struct babel_route *r = routes[i]; + while(r->next != route) + r = r->next; + r->next = route->next; + route->next = routes[i]; + routes[i] = route; + } +} + +void +install_route(struct babel_route *route) +{ + int i, rc; + + if(route->installed) + return; + + if(!route_feasible(route)) + flog_err(EC_BABEL_ROUTE, + "Installing unfeasible route (this shouldn't happen)."); + + i = find_route_slot(route->src->prefix, route->src->plen, NULL); + assert(i >= 0 && i < route_slots); + + if(routes[i] != route && routes[i]->installed) { + flog_err( + EC_BABEL_ROUTE, + "Attempting to install duplicate route (this shouldn't happen)."); + return; + } + + rc = kernel_route(ROUTE_ADD, route->src->prefix, route->src->plen, + route->nexthop, + route->neigh->ifp->ifindex, + metric_to_kernel(route_metric(route)), NULL, 0, 0); + if(rc < 0) { + int save = errno; + flog_err(EC_BABEL_ROUTE, "kernel_route(ADD): %s", + safe_strerror(errno)); + if(save != EEXIST) + return; + } + route->installed = 1; + move_installed_route(route, i); + +} + +void +uninstall_route(struct babel_route *route) +{ + int rc; + + if(!route->installed) + return; + + rc = kernel_route(ROUTE_FLUSH, route->src->prefix, route->src->plen, + route->nexthop, + route->neigh->ifp->ifindex, + metric_to_kernel(route_metric(route)), NULL, 0, 0); + if(rc < 0) + flog_err(EC_BABEL_ROUTE, "kernel_route(FLUSH): %s", + safe_strerror(errno)); + + route->installed = 0; +} + +/* This is equivalent to uninstall_route followed with install_route, + but without the race condition. The destination of both routes + must be the same. */ + +static void +switch_routes(struct babel_route *old, struct babel_route *new) +{ + int rc; + + if(!old) { + install_route(new); + return; + } + + if(!old->installed) + return; + + if(!route_feasible(new)) + flog_err(EC_BABEL_ROUTE, + "Switching to unfeasible route (this shouldn't happen)."); + + rc = kernel_route(ROUTE_MODIFY, old->src->prefix, old->src->plen, + old->nexthop, old->neigh->ifp->ifindex, + metric_to_kernel(route_metric(old)), + new->nexthop, new->neigh->ifp->ifindex, + metric_to_kernel(route_metric(new))); + if(rc < 0) { + flog_err(EC_BABEL_ROUTE, "kernel_route(MODIFY): %s", + safe_strerror(errno)); + return; + } + + old->installed = 0; + new->installed = 1; + move_installed_route(new, find_route_slot(new->src->prefix, new->src->plen, + NULL)); +} + +static void +change_route_metric(struct babel_route *route, + unsigned refmetric, unsigned cost, unsigned add) +{ + int old, new; + int newmetric = MIN(refmetric + cost + add, INFINITY); + + old = metric_to_kernel(route_metric(route)); + new = metric_to_kernel(newmetric); + + if(route->installed && old != new) { + int rc; + rc = kernel_route(ROUTE_MODIFY, route->src->prefix, route->src->plen, + route->nexthop, route->neigh->ifp->ifindex, + old, + route->nexthop, route->neigh->ifp->ifindex, + new); + if(rc < 0) { + flog_err(EC_BABEL_ROUTE, "kernel_route(MODIFY metric): %s", + safe_strerror(errno)); + return; + } + } + + /* Update route->smoothed_metric using the old metric. */ + route_smoothed_metric(route); + + route->refmetric = refmetric; + route->cost = cost; + route->add_metric = add; + + if(smoothing_half_life == 0) { + route->smoothed_metric = route_metric(route); + route->smoothed_metric_time = babel_now.tv_sec; + } +} + +static void +retract_route(struct babel_route *route) +{ + /* We cannot simply remove the route from the kernel, as that might + cause a routing loop -- see RFC 6126 Sections 2.8 and 3.5.5. */ + change_route_metric(route, INFINITY, INFINITY, 0); +} + +int +route_feasible(struct babel_route *route) +{ + return update_feasible(route->src, route->seqno, route->refmetric); +} + +int +route_old(struct babel_route *route) +{ + return route->time < babel_now.tv_sec - route->hold_time * 7 / 8; +} + +int +route_expired(struct babel_route *route) +{ + return route->time < babel_now.tv_sec - route->hold_time; +} + +static int +channels_interfere(int ch1, int ch2) +{ + if(ch1 == BABEL_IF_CHANNEL_NONINTERFERING + || ch2 == BABEL_IF_CHANNEL_NONINTERFERING) + return 0; + if(ch1 == BABEL_IF_CHANNEL_INTERFERING + || ch2 == BABEL_IF_CHANNEL_INTERFERING) + return 1; + return ch1 == ch2; +} + +int +route_interferes(struct babel_route *route, struct interface *ifp) +{ + struct babel_interface *babel_ifp = NULL; + switch(diversity_kind) { + case DIVERSITY_NONE: + return 1; + case DIVERSITY_INTERFACE_1: + return route->neigh->ifp == ifp; + case DIVERSITY_CHANNEL_1: + case DIVERSITY_CHANNEL: + if(route->neigh->ifp == ifp) + return 1; + babel_ifp = babel_get_if_nfo(ifp); + if(channels_interfere(babel_ifp->channel, + babel_get_if_nfo(route->neigh->ifp)->channel)) + return 1; + if(diversity_kind == DIVERSITY_CHANNEL) { + int i; + for(i = 0; i < DIVERSITY_HOPS; i++) { + if(route->channels[i] == 0) + break; + if(channels_interfere(babel_ifp->channel, route->channels[i])) + return 1; + } + } + return 0; + } + + return 1; +} + +int +update_feasible(struct source *src, + unsigned short seqno, unsigned short refmetric) +{ + if(src == NULL) + return 1; + + if(src->time < babel_now.tv_sec - SOURCE_GC_TIME) + /* Never mind what is probably stale data */ + return 1; + + if(refmetric >= INFINITY) + /* Retractions are always feasible */ + return 1; + + return (seqno_compare(seqno, src->seqno) > 0 || + (src->seqno == seqno && refmetric < src->metric)); +} + +void +change_smoothing_half_life(int half_life) +{ + if(half_life <= 0) { + smoothing_half_life = 0; + two_to_the_one_over_hl = 0; + return; + } + + smoothing_half_life = half_life; + switch(smoothing_half_life) { + case 1: two_to_the_one_over_hl = 131072; break; + case 2: two_to_the_one_over_hl = 92682; break; + case 3: two_to_the_one_over_hl = 82570; break; + case 4: two_to_the_one_over_hl = 77935; break; + default: + /* 2^(1/x) is 1 + log(2)/x + O(1/x^2) at infinity. */ + two_to_the_one_over_hl = 0x10000 + 45426 / half_life; + } +} + +/* Update the smoothed metric, return the new value. */ +int +route_smoothed_metric(struct babel_route *route) +{ + int metric = route_metric(route); + + if(smoothing_half_life <= 0 || /* no smoothing */ + metric >= INFINITY || /* route retracted */ + route->smoothed_metric_time > babel_now.tv_sec || /* clock stepped */ + route->smoothed_metric == metric) { /* already converged */ + route->smoothed_metric = metric; + route->smoothed_metric_time = babel_now.tv_sec; + } else { + int diff; + /* We randomise the computation, to minimise global synchronisation + and hence oscillations. */ + while(route->smoothed_metric_time <= + babel_now.tv_sec - smoothing_half_life) { + diff = metric - route->smoothed_metric; + route->smoothed_metric += roughly(diff) / 2; + route->smoothed_metric_time += smoothing_half_life; + } + while(route->smoothed_metric_time < babel_now.tv_sec) { + diff = metric - route->smoothed_metric; + route->smoothed_metric += + roughly(diff) * (two_to_the_one_over_hl - 0x10000) / 0x10000; + route->smoothed_metric_time++; + } + + diff = metric - route->smoothed_metric; + if(diff > -4 && diff < 4) + route->smoothed_metric = metric; + } + + /* change_route_metric relies on this */ + assert(route->smoothed_metric_time == babel_now.tv_sec); + return route->smoothed_metric; +} + +static int +route_acceptable(struct babel_route *route, int feasible, + struct neighbour *exclude) +{ + if(route_expired(route)) + return 0; + if(feasible && !route_feasible(route)) + return 0; + if(exclude && route->neigh == exclude) + return 0; + return 1; +} + +/* Find the best route according to the weak ordering. Any + linearisation of the strong ordering (see consider_route) will do, + we use sm <= sm'. We could probably use a lexical ordering, but + that's probably overkill. */ + +struct babel_route * +find_best_route(const unsigned char *prefix, unsigned char plen, int feasible, + struct neighbour *exclude) +{ + struct babel_route *route = NULL, *r = NULL; + int i = find_route_slot(prefix, plen, NULL); + + if(i < 0) + return NULL; + + route = routes[i]; + while(route && !route_acceptable(route, feasible, exclude)) + route = route->next; + + if(!route) + return NULL; + + r = route->next; + while(r) { + if(route_acceptable(r, feasible, exclude) && + (route_smoothed_metric(r) < route_smoothed_metric(route))) + route = r; + r = r->next; + } + + return route; +} + +void +update_route_metric(struct babel_route *route) +{ + int oldmetric = route_metric(route); + int old_smoothed_metric = route_smoothed_metric(route); + + if(route_expired(route)) { + if(route->refmetric < INFINITY) { + route->seqno = seqno_plus(route->src->seqno, 1); + retract_route(route); + if(oldmetric < INFINITY) + route_changed(route, route->src, oldmetric); + } + } else { + struct neighbour *neigh = route->neigh; + int add_metric = input_filter(route->src->id, + route->src->prefix, route->src->plen, + neigh->address, + neigh->ifp->ifindex); + change_route_metric(route, route->refmetric, + neighbour_cost(route->neigh), add_metric); + if(route_metric(route) != oldmetric || + route_smoothed_metric(route) != old_smoothed_metric) + route_changed(route, route->src, oldmetric); + } +} + +/* Called whenever a neighbour's cost changes, to update the metric of + all routes through that neighbour. */ +void +update_neighbour_metric(struct neighbour *neigh, int changed) +{ + + if(changed) { + int i; + + for(i = 0; i < route_slots; i++) { + struct babel_route *r = routes[i]; + while(r) { + if(r->neigh == neigh) + update_route_metric(r); + r = r->next; + } + } + } +} + +void +update_interface_metric(struct interface *ifp) +{ + int i; + + for(i = 0; i < route_slots; i++) { + struct babel_route *r = routes[i]; + while(r) { + if(r->neigh->ifp == ifp) + update_route_metric(r); + r = r->next; + } + } +} + +/* This is called whenever we receive an update. */ +struct babel_route * +update_route(const unsigned char *router_id, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, unsigned short refmetric, + unsigned short interval, + struct neighbour *neigh, const unsigned char *nexthop, + const unsigned char *channels, int channels_len) +{ + struct babel_route *route; + struct source *src; + int metric, feasible; + int add_metric; + int hold_time = MAX((4 * interval) / 100 + interval / 50, 15); + + if(memcmp(router_id, myid, 8) == 0) + return NULL; + + if(martian_prefix(prefix, plen)) { + flog_err(EC_BABEL_ROUTE, "Rejecting martian route to %s through %s.", + format_prefix(prefix, plen), format_address(nexthop)); + return NULL; + } + + add_metric = input_filter(router_id, prefix, plen, + neigh->address, neigh->ifp->ifindex); + if(add_metric >= INFINITY) + return NULL; + + route = find_route(prefix, plen, neigh, nexthop); + + if(route && memcmp(route->src->id, router_id, 8) == 0) + /* Avoid scanning the source table. */ + src = route->src; + else + src = find_source(router_id, prefix, plen, 1, seqno); + + if(src == NULL) + return NULL; + + feasible = update_feasible(src, seqno, refmetric); + metric = MIN((int)refmetric + neighbour_cost(neigh) + add_metric, INFINITY); + + if(route) { + struct source *oldsrc; + unsigned short oldmetric; + int lost = 0; + + oldsrc = route->src; + oldmetric = route_metric(route); + + /* If a successor switches sources, we must accept his update even + if it makes a route unfeasible in order to break any routing loops + in a timely manner. If the source remains the same, we ignore + the update. */ + if(!feasible && route->installed) { + debugf(BABEL_DEBUG_COMMON,"Unfeasible update for installed route to %s (%s %d %d -> %s %d %d).", + format_prefix(src->prefix, src->plen), + format_eui64(route->src->id), + route->seqno, route->refmetric, + format_eui64(src->id), seqno, refmetric); + if(src != route->src) { + uninstall_route(route); + lost = 1; + } + } + + route->src = retain_source(src); + if((feasible || keep_unfeasible) && refmetric < INFINITY) + route->time = babel_now.tv_sec; + route->seqno = seqno; + + memset(&route->channels, 0, sizeof(route->channels)); + if(channels_len > 0) + memcpy(&route->channels, channels, + MIN(channels_len, DIVERSITY_HOPS)); + + change_route_metric(route, + refmetric, neighbour_cost(neigh), add_metric); + route->hold_time = hold_time; + + route_changed(route, oldsrc, oldmetric); + if(lost) + route_lost(oldsrc, oldmetric); + + if(!feasible) + send_unfeasible_request(neigh, route->installed && route_old(route), + seqno, metric, src); + release_source(oldsrc); + } else { + struct babel_route *new_route; + + if(refmetric >= INFINITY) + /* Somebody's retracting a route we never saw. */ + return NULL; + if(!feasible) { + send_unfeasible_request(neigh, 0, seqno, metric, src); + if(!keep_unfeasible) + return NULL; + } + + route = malloc(sizeof(struct babel_route)); + if(route == NULL) { + perror("malloc(route)"); + return NULL; + } + + route->src = retain_source(src); + route->refmetric = refmetric; + route->cost = neighbour_cost(neigh); + route->add_metric = add_metric; + route->seqno = seqno; + route->neigh = neigh; + memcpy(route->nexthop, nexthop, 16); + route->time = babel_now.tv_sec; + route->hold_time = hold_time; + route->smoothed_metric = MAX(route_metric(route), INFINITY / 2); + route->smoothed_metric_time = babel_now.tv_sec; + route->installed = 0; + memset(&route->channels, 0, sizeof(route->channels)); + if(channels_len > 0) + memcpy(&route->channels, channels, + MIN(channels_len, DIVERSITY_HOPS)); + route->next = NULL; + new_route = insert_route(route); + if(new_route == NULL) { + flog_err(EC_BABEL_ROUTE, "Couldn't insert route."); + free(route); + return NULL; + } + consider_route(route); + } + return route; +} + +/* We just received an unfeasible update. If it's any good, send + a request for a new seqno. */ +void +send_unfeasible_request(struct neighbour *neigh, int force, + unsigned short seqno, unsigned short metric, + struct source *src) +{ + struct babel_route *route = find_installed_route(src->prefix, src->plen); + + if(seqno_minus(src->seqno, seqno) > 100) { + /* Probably a source that lost its seqno. Let it time-out. */ + return; + } + + if(force || !route || route_metric(route) >= metric + 512) { + send_unicast_multihop_request(neigh, src->prefix, src->plen, + src->metric >= INFINITY ? + src->seqno : + seqno_plus(src->seqno, 1), + src->id, 127); + } +} + +/* This takes a feasible route and decides whether to install it. + This uses the strong ordering, which is defined by sm <= sm' AND + m <= m'. This ordering is not total, which is what causes + hysteresis. */ + +static void +consider_route(struct babel_route *route) +{ + struct babel_route *installed; + struct xroute *xroute; + + if(route->installed) + return; + + if(!route_feasible(route)) + return; + + xroute = find_xroute(route->src->prefix, route->src->plen); + if(xroute) + return; + + installed = find_installed_route(route->src->prefix, route->src->plen); + + if(installed == NULL) + goto install; + + if(route_metric(route) >= INFINITY) + return; + + if(route_metric(installed) >= INFINITY) + goto install; + + if(route_metric(installed) >= route_metric(route) && + route_smoothed_metric(installed) > route_smoothed_metric(route)) + goto install; + + return; + + install: + switch_routes(installed, route); + if(installed && route->installed) + send_triggered_update(route, installed->src, route_metric(installed)); + else + send_update(NULL, 1, route->src->prefix, route->src->plen); + return; +} + +void +retract_neighbour_routes(struct neighbour *neigh) +{ + int i; + + for(i = 0; i < route_slots; i++) { + struct babel_route *r = routes[i]; + while(r) { + if(r->neigh == neigh) { + if(r->refmetric != INFINITY) { + unsigned short oldmetric = route_metric(r); + retract_route(r); + if(oldmetric != INFINITY) + route_changed(r, r->src, oldmetric); + } + } + r = r->next; + } + } +} + +void +send_triggered_update(struct babel_route *route, struct source *oldsrc, + unsigned oldmetric) +{ + unsigned newmetric, diff; + /* 1 means send speedily, 2 means resend */ + int urgent; + + if(!route->installed) + return; + + newmetric = route_metric(route); + diff = + newmetric >= oldmetric ? newmetric - oldmetric : oldmetric - newmetric; + + if(route->src != oldsrc || (oldmetric < INFINITY && newmetric >= INFINITY)) + /* Switching sources can cause transient routing loops. + Retractions can cause blackholes. */ + urgent = 2; + else if(newmetric > oldmetric && oldmetric < 6 * 256 && diff >= 512) + /* Route getting significantly worse */ + urgent = 1; + else if(unsatisfied_request(route->src->prefix, route->src->plen, + route->seqno, route->src->id)) + /* Make sure that requests are satisfied speedily */ + urgent = 1; + else if(oldmetric >= INFINITY && newmetric < INFINITY) + /* New route */ + urgent = 0; + else if(newmetric < oldmetric && diff < 1024) + /* Route getting better. This may be a transient fluctuation, so + don't advertise it to avoid making routes unfeasible later on. */ + return; + else if(diff < 384) + /* Don't fret about trivialities */ + return; + else + urgent = 0; + + if(urgent >= 2) + send_update_resend(NULL, route->src->prefix, route->src->plen); + else + send_update(NULL, urgent, route->src->prefix, route->src->plen); + + if(oldmetric < INFINITY) { + if(newmetric >= oldmetric + 512) { + send_request_resend(NULL, route->src->prefix, route->src->plen, + route->src->metric >= INFINITY ? + route->src->seqno : + seqno_plus(route->src->seqno, 1), + route->src->id); + } else if(newmetric >= oldmetric + 288) { + send_request(NULL, route->src->prefix, route->src->plen); + } + } +} + +/* A route has just changed. Decide whether to switch to a different route or + send an update. */ +void +route_changed(struct babel_route *route, + struct source *oldsrc, unsigned short oldmetric) +{ + if(route->installed) { + struct babel_route *better_route; + /* Do this unconditionally -- microoptimisation is not worth it. */ + better_route = + find_best_route(route->src->prefix, route->src->plen, 1, NULL); + if(better_route && route_metric(better_route) < route_metric(route)) + consider_route(better_route); + } + + if(route->installed) { + /* We didn't change routes after all. */ + send_triggered_update(route, oldsrc, oldmetric); + } else { + /* Reconsider routes even when their metric didn't decrease, + they may not have been feasible before. */ + consider_route(route); + } +} + +/* We just lost the installed route to a given destination. */ +void +route_lost(struct source *src, unsigned oldmetric) +{ + struct babel_route *new_route; + new_route = find_best_route(src->prefix, src->plen, 1, NULL); + if(new_route) { + consider_route(new_route); + } else if(oldmetric < INFINITY) { + /* Avoid creating a blackhole. */ + send_update_resend(NULL, src->prefix, src->plen); + /* If the route was usable enough, try to get an alternate one. + If it was not, we could be dealing with oscillations around + the value of INFINITY. */ + if(oldmetric <= INFINITY / 2) + send_request_resend(NULL, src->prefix, src->plen, + src->metric >= INFINITY ? + src->seqno : seqno_plus(src->seqno, 1), + src->id); + } +} + +/* This is called periodically to flush old routes. It will also send + requests for routes that are about to expire. */ +void +expire_routes(void) +{ + struct babel_route *r; + int i; + + debugf(BABEL_DEBUG_COMMON,"Expiring old routes."); + + i = 0; + while(i < route_slots) { + r = routes[i]; + while(r) { + /* Protect against clock being stepped. */ + if(r->time > babel_now.tv_sec || route_old(r)) { + flush_route(r); + goto again; + } + + update_route_metric(r); + + if(r->installed && r->refmetric < INFINITY) { + if(route_old(r)) + /* Route about to expire, send a request. */ + send_unicast_request(r->neigh, + r->src->prefix, r->src->plen); + } + r = r->next; + } + i++; + again: + ; + } +} diff --git a/babeld/route.h b/babeld/route.h new file mode 100644 index 0000000..89427b8 --- /dev/null +++ b/babeld/route.h @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_ROUTE_H +#define BABEL_ROUTE_H + +#include "babel_interface.h" +#include "source.h" + +enum babel_diversity { + DIVERSITY_NONE, + DIVERSITY_INTERFACE_1, + DIVERSITY_CHANNEL_1, + DIVERSITY_CHANNEL, +}; + +#define DIVERSITY_HOPS 8 + +struct babel_route { + struct source *src; + unsigned short refmetric; + unsigned short cost; + unsigned short add_metric; + unsigned short seqno; + struct neighbour *neigh; + unsigned char nexthop[16]; + time_t time; + unsigned short hold_time; /* in seconds */ + unsigned short smoothed_metric; /* for route selection */ + time_t smoothed_metric_time; + short installed; + unsigned char channels[DIVERSITY_HOPS]; + struct babel_route *next; +}; + +struct route_stream; + +extern struct babel_route **routes; +extern int kernel_metric; +extern enum babel_diversity diversity_kind; +extern int diversity_factor; +extern int keep_unfeasible; +extern int smoothing_half_life; + +static inline int +route_metric(const struct babel_route *route) +{ + int m = (int)route->refmetric + route->cost + route->add_metric; + return MIN(m, INFINITY); +} + +static inline int +route_metric_noninterfering(const struct babel_route *route) +{ + int m = + (int)route->refmetric + + (diversity_factor * route->cost + 128) / 256 + + route->add_metric; + m = MAX(m, route->refmetric + 1); + return MIN(m, INFINITY); +} + +struct babel_route *find_route(const unsigned char *prefix, unsigned char plen, + struct neighbour *neigh, const unsigned char *nexthop); +struct babel_route *find_installed_route(const unsigned char *prefix, + unsigned char plen); +int installed_routes_estimate(void); +void flush_route(struct babel_route *route); +void flush_all_routes(void); +void flush_neighbour_routes(struct neighbour *neigh); +void flush_interface_routes(struct interface *ifp, int v4only); +struct route_stream *route_stream(int installed); +struct babel_route *route_stream_next(struct route_stream *stream); +void route_stream_done(struct route_stream *stream); +void install_route(struct babel_route *route); +void uninstall_route(struct babel_route *route); +int route_feasible(struct babel_route *route); +int route_old(struct babel_route *route); +int route_expired(struct babel_route *route); +int route_interferes(struct babel_route *route, struct interface *ifp); +int update_feasible(struct source *src, + unsigned short seqno, unsigned short refmetric); +void change_smoothing_half_life(int half_life); +int route_smoothed_metric(struct babel_route *route); +struct babel_route *find_best_route(const unsigned char *prefix, unsigned char plen, + int feasible, struct neighbour *exclude); +struct babel_route *install_best_route(const unsigned char prefix[16], + unsigned char plen); +void update_neighbour_metric(struct neighbour *neigh, int change); +void update_interface_metric(struct interface *ifp); +void update_route_metric(struct babel_route *route); +struct babel_route *update_route(const unsigned char *id, + const unsigned char *prefix, unsigned char plen, + unsigned short seqno, unsigned short refmetric, + unsigned short interval, struct neighbour *neigh, + const unsigned char *nexthop, + const unsigned char *channels, int channels_len); +void retract_neighbour_routes(struct neighbour *neigh); +void send_unfeasible_request(struct neighbour *neigh, int force, + unsigned short seqno, unsigned short metric, + struct source *src); +void send_triggered_update(struct babel_route *route, + struct source *oldsrc, unsigned oldmetric); +void route_changed(struct babel_route *route, + struct source *oldsrc, unsigned short oldmetric); +void route_lost(struct source *src, unsigned oldmetric); +void expire_routes(void); + +#endif diff --git a/babeld/source.c b/babeld/source.c new file mode 100644 index 0000000..049fa32 --- /dev/null +++ b/babeld/source.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" +#include "source.h" +#include "babel_interface.h" +#include "route.h" +#include "babel_errors.h" + +struct source *srcs = NULL; + +struct source* +find_source(const unsigned char *id, const unsigned char *p, unsigned char plen, + int create, unsigned short seqno) +{ + struct source *src; + + for(src = srcs; src; src = src->next) { + /* This should really be a hash table. For now, check the + last byte first. */ + if(src->id[7] != id[7]) + continue; + if(memcmp(src->id, id, 8) != 0) + continue; + if(src->plen != plen) + continue; + if(memcmp(src->prefix, p, 16) == 0) + return src; + } + + if(!create) + return NULL; + + src = malloc(sizeof(struct source)); + if(src == NULL) { + flog_err(EC_BABEL_MEMORY, "malloc(source): %s", safe_strerror(errno)); + return NULL; + } + + memcpy(src->id, id, 8); + memcpy(src->prefix, p, 16); + src->plen = plen; + src->seqno = seqno; + src->metric = INFINITY; + src->time = babel_now.tv_sec; + src->route_count = 0; + src->next = srcs; + srcs = src; + return src; +} + +struct source * +retain_source(struct source *src) +{ + assert(src->route_count < 0xffff); + src->route_count++; + return src; +} + +void +release_source(struct source *src) +{ + assert(src->route_count > 0); + src->route_count--; +} + +int +flush_source(struct source *src) +{ + if(src->route_count > 0) + /* The source is in use by a route. */ + return 0; + + if(srcs == src) { + srcs = src->next; + } else { + struct source *previous = srcs; + while(previous->next != src) + previous = previous->next; + previous->next = src->next; + } + + free(src); + return 1; +} + +void +update_source(struct source *src, + unsigned short seqno, unsigned short metric) +{ + if(metric >= INFINITY) + return; + + /* If a source is expired, pretend that it doesn't exist and update + it unconditionally. This makes ensures that old data will + eventually be overridden, and prevents us from getting stuck if + a router loses its sequence number. */ + if(src->time < babel_now.tv_sec - SOURCE_GC_TIME || + seqno_compare(src->seqno, seqno) < 0 || + (src->seqno == seqno && src->metric > metric)) { + src->seqno = seqno; + src->metric = metric; + } + src->time = babel_now.tv_sec; +} + +void +expire_sources(void) +{ + struct source *src; + + src = srcs; + while(src) { + if(src->time > babel_now.tv_sec) + /* clock stepped */ + src->time = babel_now.tv_sec; + if(src->time < babel_now.tv_sec - SOURCE_GC_TIME) { + struct source *old = src; + src = src->next; + flush_source(old); + continue; + } + src = src->next; + } +} + +void +check_sources_released(void) +{ + struct source *src; + + for(src = srcs; src; src = src->next) { + if(src->route_count != 0) + fprintf(stderr, "Warning: source %s %s has refcount %d.\n", + format_eui64(src->id), + format_prefix(src->prefix, src->plen), + (int)src->route_count); + } +} diff --git a/babeld/source.h b/babeld/source.h new file mode 100644 index 0000000..5b37ca9 --- /dev/null +++ b/babeld/source.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +*/ + +#ifndef BABEL_SOURCE_H +#define BABEL_SOURCE_H + +#define SOURCE_GC_TIME 200 + +struct source { + struct source *next; + unsigned char id[8]; + unsigned char prefix[16]; + unsigned char plen; + unsigned short seqno; + unsigned short metric; + unsigned short route_count; + time_t time; +}; + +struct source *find_source(const unsigned char *id, + const unsigned char *p, + unsigned char plen, + int create, unsigned short seqno); +struct source *retain_source(struct source *src); +void release_source(struct source *src); +int flush_source(struct source *src); +void update_source(struct source *src, + unsigned short seqno, unsigned short metric); +void expire_sources(void); +void check_sources_released(void); + +#endif diff --git a/babeld/subdir.am b/babeld/subdir.am new file mode 100644 index 0000000..d2d4252 --- /dev/null +++ b/babeld/subdir.am @@ -0,0 +1,50 @@ +# +# babeld +# + +if BABELD +sbin_PROGRAMS += babeld/babeld +vtysh_daemons += babeld +endif + +babeld_babeld_SOURCES = \ + babeld/babel_errors.c \ + babeld/babel_filter.c \ + babeld/babel_interface.c \ + babeld/babel_main.c \ + babeld/babel_zebra.c \ + babeld/babeld.c \ + babeld/kernel.c \ + babeld/message.c \ + babeld/neighbour.c \ + babeld/net.c \ + babeld/resend.c \ + babeld/route.c \ + babeld/source.c \ + babeld/util.c \ + babeld/xroute.c \ + # end + +noinst_HEADERS += \ + babeld/babel_errors.h \ + babeld/babel_filter.h \ + babeld/babel_interface.h \ + babeld/babel_main.h \ + babeld/babel_zebra.h \ + babeld/babeld.h \ + babeld/kernel.h \ + babeld/message.h \ + babeld/neighbour.h \ + babeld/net.h \ + babeld/resend.h \ + babeld/route.h \ + babeld/source.h \ + babeld/util.h \ + babeld/xroute.h \ + # end + +clippy_scan += \ + babeld/babel_interface.c \ + babeld/babeld.c + +babeld_babeld_LDADD = lib/libfrr.la $(LIBCAP) diff --git a/babeld/util.c b/babeld/util.c new file mode 100644 index 0000000..4facdab --- /dev/null +++ b/babeld/util.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "lib/network.h" + +#include "babel_main.h" +#include "babeld.h" +#include "util.h" + +int +roughly(int value) +{ + if(value < 0) + return -roughly(-value); + else if(value <= 1) + return value; + else + return value * 3 / 4 + frr_weak_random() % (value / 2); +} + +/* d = s1 - s2 */ +void +timeval_minus(struct timeval *d, + const struct timeval *s1, const struct timeval *s2) +{ + if(s1->tv_usec >= s2->tv_usec) { + d->tv_usec = s1->tv_usec - s2->tv_usec; + d->tv_sec = s1->tv_sec - s2->tv_sec; + } else { + d->tv_usec = s1->tv_usec + 1000000 - s2->tv_usec; + d->tv_sec = s1->tv_sec - s2->tv_sec - 1; + } +} + +unsigned +timeval_minus_msec(const struct timeval *s1, const struct timeval *s2) +{ + if(s1->tv_sec < s2->tv_sec) + return 0; + + /* Avoid overflow. */ + if(s1->tv_sec - s2->tv_sec > 2000000) + return 2000000000; + + if(s1->tv_sec > s2->tv_sec) + return + (unsigned)((unsigned)(s1->tv_sec - s2->tv_sec) * 1000 + + ((int)s1->tv_usec - s2->tv_usec) / 1000); + + if(s1->tv_usec <= s2->tv_usec) + return 0; + + return (unsigned)(s1->tv_usec - s2->tv_usec) / 1000u; +} + +/* d = s + msecs */ +void +timeval_add_msec(struct timeval *d, const struct timeval *s, int msecs) +{ + int usecs; + d->tv_sec = s->tv_sec + msecs / 1000; + usecs = s->tv_usec + (msecs % 1000) * 1000; + if(usecs < 1000000) { + d->tv_usec = usecs; + } else { + d->tv_usec = usecs - 1000000; + d->tv_sec++; + } +} + +void +set_timeout(struct timeval *timeout, int msecs) +{ + timeval_add_msec(timeout, &babel_now, roughly(msecs)); +} + +/* returns <0 if "s1" < "s2", etc. */ +int +timeval_compare(const struct timeval *s1, const struct timeval *s2) +{ + if(s1->tv_sec < s2->tv_sec) + return -1; + else if(s1->tv_sec > s2->tv_sec) + return 1; + else if(s1->tv_usec < s2->tv_usec) + return -1; + else if(s1->tv_usec > s2->tv_usec) + return 1; + else + return 0; +} + +/* set d at min(d, s) */ +/* {0, 0} represents infinity */ +void +timeval_min(struct timeval *d, const struct timeval *s) +{ + if(s->tv_sec == 0) + return; + + if(d->tv_sec == 0 || timeval_compare(d, s) > 0) { + *d = *s; + } +} + +/* set d to min(d, x) with x in [secs, secs+1] */ +void +timeval_min_sec(struct timeval *d, time_t secs) +{ + if(d->tv_sec == 0 || d->tv_sec > secs) { + d->tv_sec = secs; + d->tv_usec = frr_weak_random() % 1000000; + } +} + +/* parse a float value in second and return the corresponding mili-seconds. + For example: + parse_msec("12.342345") returns 12342 */ +int +parse_msec(const char *string) +{ + unsigned int in, fl; + int i, j; + + in = fl = 0; + i = 0; + while(string[i] == ' ' || string[i] == '\t') + i++; + while(string[i] >= '0' && string[i] <= '9') { + in = in * 10 + string[i] - '0'; + i++; + } + if(string[i] == '.') { + i++; + j = 0; + while(string[i] >= '0' && string[i] <= '9') { + fl = fl * 10 + string[i] - '0'; + i++; + j++; + } + + while(j > 3) { + fl /= 10; + j--; + } + while(j < 3) { + fl *= 10; + j++; + } + } + + while(string[i] == ' ' || string[i] == '\t') + i++; + + if(string[i] == '\0') + return in * 1000 + fl; + + return -1; +} + +/* There's no good name for a positive int in C, call it nat. */ +int +parse_nat(const char *string) +{ + long l; + char *end; + + l = strtol(string, &end, 0); + + while(*end == ' ' || *end == '\t') + end++; + if(*end != '\0') + return -1; + + if(l < 0 || l > INT_MAX) + return -1; + + return (int)l; +} + +unsigned char * +mask_prefix(unsigned char *restrict ret, + const unsigned char *restrict prefix, unsigned char plen) +{ + if (plen >= IPV6_MAX_BITLEN) { + memcpy(ret, prefix, IPV6_MAX_BYTELEN); + return ret; + } + + memset(ret, 0, 16); + memcpy(ret, prefix, plen / 8); + if(plen % 8 != 0) + ret[plen / 8] = + (prefix[plen / 8] & ((0xFF << (8 - (plen % 8))) & 0xFF)); + return ret; +} + +const unsigned char v4prefix[16] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 }; + +static const unsigned char llprefix[16] = + {0xFE, 0x80}; + +const char * +format_address(const unsigned char *address) +{ + static char buf[4][INET6_ADDRSTRLEN]; + static int i = 0; + i = (i + 1) % 4; + if(v4mapped(address)) + inet_ntop(AF_INET, address + 12, buf[i], INET6_ADDRSTRLEN); + else + inet_ntop(AF_INET6, address, buf[i], INET6_ADDRSTRLEN); + return buf[i]; +} + +const char * +format_prefix(const unsigned char *prefix, unsigned char plen) +{ + static char buf[4][INET6_ADDRSTRLEN + 4]; + static int i = 0; + int n; + i = (i + 1) % 4; + if(plen >= 96 && v4mapped(prefix)) { + inet_ntop(AF_INET, prefix + 12, buf[i], INET6_ADDRSTRLEN); + n = strlen(buf[i]); + snprintf(buf[i] + n, INET6_ADDRSTRLEN + 4 - n, "/%d", plen - 96); + } else { + inet_ntop(AF_INET6, prefix, buf[i], INET6_ADDRSTRLEN); + n = strlen(buf[i]); + snprintf(buf[i] + n, INET6_ADDRSTRLEN + 4 - n, "/%d", plen); + } + return buf[i]; +} + +const char * +format_eui64(const unsigned char *eui) +{ + static char buf[4][28]; + static int i = 0; + i = (i + 1) % 4; + snprintf(buf[i], 28, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + eui[0], eui[1], eui[2], eui[3], + eui[4], eui[5], eui[6], eui[7]); + return buf[i]; +} + +const char * +format_thousands(unsigned int value) +{ + static char buf[4][15]; + static int i = 0; + i = (i + 1) % 4; + snprintf(buf[i], 15, "%u.%.3u", value / 1000, value % 1000); + return buf[i]; +} + +int +parse_address(const char *address, unsigned char *addr_r, int *af_r) +{ + struct in_addr ina; + struct in6_addr ina6; + int rc; + + rc = inet_pton(AF_INET, address, &ina); + if(rc > 0) { + v4tov6(addr_r, (const unsigned char *)&ina); + if(af_r) *af_r = AF_INET; + return 0; + } + + rc = inet_pton(AF_INET6, address, &ina6); + if(rc > 0) { + memcpy(addr_r, &ina6, IPV6_MAX_BYTELEN); + if (af_r) + *af_r = AF_INET6; + return 0; + } + + return -1; +} + +int +parse_eui64(const char *eui, unsigned char *eui_r) +{ + int n; + n = sscanf(eui, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &eui_r[0], &eui_r[1], &eui_r[2], &eui_r[3], + &eui_r[4], &eui_r[5], &eui_r[6], &eui_r[7]); + if(n == 8) + return 0; + + n = sscanf(eui, "%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx-%02hhx", + &eui_r[0], &eui_r[1], &eui_r[2], &eui_r[3], + &eui_r[4], &eui_r[5], &eui_r[6], &eui_r[7]); + if(n == 8) + return 0; + + n = sscanf(eui, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &eui_r[0], &eui_r[1], &eui_r[2], + &eui_r[5], &eui_r[6], &eui_r[7]); + if(n == 6) { + eui_r[3] = 0xFF; + eui_r[4] = 0xFE; + return 0; + } + return -1; +} + +int +wait_for_fd(int direction, int fd, int msecs) +{ + fd_set fds; + int rc; + struct timeval tv; + + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + if(direction) + rc = select(fd + 1, NULL, &fds, NULL, &tv); + else + rc = select(fd + 1, &fds, NULL, NULL, &tv); + + return rc; +} + +int +martian_prefix(const unsigned char *prefix, int plen) +{ + return + (plen >= 8 && prefix[0] == 0xFF) || + (plen >= 10 && prefix[0] == 0xFE && (prefix[1] & 0xC0) == 0x80) || + (plen >= 128 && memcmp(prefix, zeroes, 15) == 0 && + (prefix[15] == 0 || prefix[15] == 1)) || + (plen >= 96 && v4mapped(prefix) && + ((plen >= 104 && (prefix[12] == 127 || prefix[12] == 0)) || + (plen >= 100 && (prefix[12] & 0xE0) == 0xE0))); +} + +int +linklocal(const unsigned char *address) +{ + return memcmp(address, llprefix, 8) == 0; +} + +int +v4mapped(const unsigned char *address) +{ + return memcmp(address, v4prefix, 12) == 0; +} + +void +v4tov6(unsigned char *dst, const unsigned char *src) +{ + memcpy(dst, v4prefix, 12); + memcpy(dst + 12, src, 4); +} + +void +inaddr_to_uchar(unsigned char *dest, const struct in_addr *src) +{ + v4tov6(dest, (const unsigned char *)src); + assert(v4mapped(dest)); +} + +void +uchar_to_inaddr(struct in_addr *dest, const unsigned char *src) +{ + assert(v4mapped(src)); + memcpy(dest, src + 12, 4); +} + +void +in6addr_to_uchar(unsigned char *dest, const struct in6_addr *src) +{ + memcpy(dest, src, IPV6_MAX_BYTELEN); +} + +void +uchar_to_in6addr(struct in6_addr *dest, const unsigned char *src) +{ + memcpy(dest, src, IPV6_MAX_BYTELEN); +} + +int +daemonise(void) +{ + int rc; + + fflush(stdout); + fflush(stderr); + + rc = fork(); + if(rc < 0) + return -1; + + if(rc > 0) + exit(0); + + rc = setsid(); + if(rc < 0) + return -1; + + return 1; +} diff --git a/babeld/util.h b/babeld/util.h new file mode 100644 index 0000000..ddc6a70 --- /dev/null +++ b/babeld/util.h @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_UTIL_H +#define BABEL_UTIL_H + +#include "babeld.h" +#include "babel_main.h" +#include "log.h" +#include "memory.h" + +DECLARE_MGROUP(BABELD); + +#if defined(i386) || defined(__mc68020__) || defined(__x86_64__) +#define DO_NTOHS(_d, _s) do{ _d = ntohs(*(const unsigned short*)(_s)); }while(0) +#define DO_NTOHL(_d, _s) do{ _d = ntohl(*(const unsigned*)(_s)); } while(0) +#define DO_HTONS(_d, _s) do{ *(unsigned short*)(_d) = htons(_s); } while(0) +#define DO_HTONL(_d, _s) do{ *(unsigned*)(_d) = htonl(_s); } while(0) +/* Some versions of gcc seem to be buggy, and ignore the packed attribute. + Disable this code until the issue is clarified. */ +/* #elif defined __GNUC__*/ +#else +#define DO_NTOHS(_d, _s) \ + do { short _dd; \ + memcpy(&(_dd), (_s), 2); \ + _d = ntohs(_dd); } while(0) +#define DO_NTOHL(_d, _s) \ + do { int _dd; \ + memcpy(&(_dd), (_s), 4); \ + _d = ntohl(_dd); } while(0) +#define DO_HTONS(_d, _s) \ + do { unsigned short _dd; \ + _dd = htons(_s); \ + memcpy((_d), &(_dd), 2); } while(0) +#define DO_HTONL(_d, _s) \ + do { unsigned _dd; \ + _dd = htonl(_s); \ + memcpy((_d), &(_dd), 4); } while(0) +#endif + +static inline int +seqno_compare(unsigned short s1, unsigned short s2) +{ + if(s1 == s2) + return 0; + else + return ((s2 - s1) & 0x8000) ? 1 : -1; +} + +static inline short +seqno_minus(unsigned short s1, unsigned short s2) +{ + return (short)((s1 - s2) & 0xFFFF); +} + +static inline unsigned short +seqno_plus(unsigned short s, int plus) +{ + return ((s + plus) & 0xFFFF); +} + +/* Returns a time in microseconds on 32 bits (thus modulo 2^32, + i.e. about 4295 seconds). */ +static inline unsigned int +time_us(const struct timeval t) +{ + return (unsigned int) (t.tv_sec * 1000000 + t.tv_usec); +} + +int roughly(int value); +void timeval_minus(struct timeval *d, + const struct timeval *s1, const struct timeval *s2); +unsigned timeval_minus_msec(const struct timeval *s1, const struct timeval *s2) + ATTRIBUTE ((pure)); +void timeval_add_msec(struct timeval *d, const struct timeval *s, int msecs); +void set_timeout (struct timeval *timeout, int msecs); +int timeval_compare(const struct timeval *s1, const struct timeval *s2) + ATTRIBUTE ((pure)); +void timeval_min(struct timeval *d, const struct timeval *s); +void timeval_min_sec(struct timeval *d, time_t secs); +int parse_nat(const char *string) ATTRIBUTE ((pure)); +int parse_msec(const char *string) ATTRIBUTE ((pure)); +unsigned char *mask_prefix(unsigned char *restrict ret, + const unsigned char *restrict prefix, + unsigned char plen); +const char *format_address(const unsigned char *address); +const char *format_prefix(const unsigned char *address, unsigned char prefix); +const char *format_eui64(const unsigned char *eui); +const char *format_thousands(unsigned int value); +int parse_address(const char *address, unsigned char *addr_r, int *af_r); +int parse_eui64(const char *eui, unsigned char *eui_r); +int wait_for_fd(int direction, int fd, int msecs); +int martian_prefix(const unsigned char *prefix, int plen) ATTRIBUTE ((pure)); +int linklocal(const unsigned char *address) ATTRIBUTE ((pure)); +int v4mapped(const unsigned char *address) ATTRIBUTE ((pure)); +void v4tov6(unsigned char *dst, const unsigned char *src); +void inaddr_to_uchar(unsigned char *dest, const struct in_addr *src); +void uchar_to_inaddr(struct in_addr *dest, const unsigned char *src); +void in6addr_to_uchar(unsigned char *dest, const struct in6_addr *src); +void uchar_to_in6addr(struct in6_addr *dest, const unsigned char *src); +int daemonise(void); +extern const unsigned char v4prefix[16]; + +static inline bool +is_default(const unsigned char *prefix, int plen) +{ + return plen == 0 || (plen == 96 && v4mapped(prefix)); +} + +/* If debugging is disabled, we want to avoid calling format_address + for every omitted debugging message. So debug is a macro. But + vararg macros are not portable. */ +#if defined NO_DEBUG + +#define debugf(...) do {} while(0) + +#else /* NO_DEBUG */ + +/* some levels */ +#define BABEL_DEBUG_COMMON (1 << 0) +#define BABEL_DEBUG_KERNEL (1 << 1) +#define BABEL_DEBUG_FILTER (1 << 2) +#define BABEL_DEBUG_TIMEOUT (1 << 3) +#define BABEL_DEBUG_IF (1 << 4) +#define BABEL_DEBUG_ROUTE (1 << 5) +#define BABEL_DEBUG_ALL (0xFFFF) + +#define debugf(level, ...) \ + do { \ + if (unlikely(debug & level)) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +#endif /* NO_DEBUG */ + +#endif /* BABEL_UTIL_H */ diff --git a/babeld/xroute.c b/babeld/xroute.c new file mode 100644 index 0000000..30204cd --- /dev/null +++ b/babeld/xroute.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#include +#include "if.h" +#include "log.h" + +#include "babeld.h" +#include "kernel.h" +#include "neighbour.h" +#include "message.h" +#include "route.h" +#include "xroute.h" +#include "util.h" +#include "babel_interface.h" + +static int xroute_add_new_route(unsigned char prefix[16], unsigned char plen, + unsigned short metric, unsigned int ifindex, + int proto, int send_updates); + +static struct xroute *xroutes; +static int numxroutes = 0, maxxroutes = 0; + +/* Add redistributed route to Babel table. */ +int +babel_route_add (struct zapi_route *api) +{ + unsigned char uchar_prefix[16]; + + switch (api->prefix.family) { + case AF_INET: + inaddr_to_uchar(uchar_prefix, &api->prefix.u.prefix4); + debugf(BABEL_DEBUG_ROUTE, "Adding new ipv4 route coming from Zebra."); + xroute_add_new_route(uchar_prefix, api->prefix.prefixlen + 96, + api->metric, api->nexthops[0].ifindex, 0, 1); + break; + case AF_INET6: + in6addr_to_uchar(uchar_prefix, &api->prefix.u.prefix6); + debugf(BABEL_DEBUG_ROUTE, "Adding new ipv6 route coming from Zebra."); + xroute_add_new_route(uchar_prefix, api->prefix.prefixlen, + api->metric, api->nexthops[0].ifindex, 0, 1); + break; + } + + return 0; +} + +/* Remove redistributed route from Babel table. */ +int +babel_route_delete (struct zapi_route *api) +{ + unsigned char uchar_prefix[16]; + struct xroute *xroute = NULL; + + switch (api->prefix.family) { + case AF_INET: + inaddr_to_uchar(uchar_prefix, &api->prefix.u.prefix4); + xroute = find_xroute(uchar_prefix, api->prefix.prefixlen + 96); + if (xroute != NULL) { + debugf(BABEL_DEBUG_ROUTE, "Removing ipv4 route (from zebra)."); + flush_xroute(xroute); + } + break; + case AF_INET6: + in6addr_to_uchar(uchar_prefix, &api->prefix.u.prefix6); + xroute = find_xroute(uchar_prefix, api->prefix.prefixlen); + if (xroute != NULL) { + debugf(BABEL_DEBUG_ROUTE, "Removing ipv6 route (from zebra)."); + flush_xroute(xroute); + } + break; + } + + return 0; +} + +struct xroute * +find_xroute(const unsigned char *prefix, unsigned char plen) +{ + int i; + for(i = 0; i < numxroutes; i++) { + if(xroutes[i].plen == plen && + memcmp(xroutes[i].prefix, prefix, 16) == 0) + return &xroutes[i]; + } + return NULL; +} + +void +flush_xroute(struct xroute *xroute) +{ + int i; + + i = xroute - xroutes; + assert(i >= 0 && i < numxroutes); + + if(i != numxroutes - 1) + memcpy(xroutes + i, xroutes + numxroutes - 1, sizeof(struct xroute)); + numxroutes--; + VALGRIND_MAKE_MEM_UNDEFINED(xroutes + numxroutes, sizeof(struct xroute)); + + if(numxroutes == 0) { + free(xroutes); + xroutes = NULL; + maxxroutes = 0; + } else if(maxxroutes > 8 && numxroutes < maxxroutes / 4) { + struct xroute *new_xroutes; + int n = maxxroutes / 2; + new_xroutes = realloc(xroutes, n * sizeof(struct xroute)); + if(new_xroutes == NULL) + return; + xroutes = new_xroutes; + maxxroutes = n; + } +} + +static int +add_xroute(unsigned char prefix[16], unsigned char plen, + unsigned short metric, unsigned int ifindex, int proto) +{ + struct xroute *xroute = find_xroute(prefix, plen); + if(xroute) { + if(xroute->metric <= metric) + return 0; + xroute->metric = metric; + return 1; + } + + if(numxroutes >= maxxroutes) { + struct xroute *new_xroutes; + int n = maxxroutes < 1 ? 8 : 2 * maxxroutes; + new_xroutes = xroutes == NULL ? + malloc(n * sizeof(struct xroute)) : + realloc(xroutes, n * sizeof(struct xroute)); + if(new_xroutes == NULL) + return -1; + maxxroutes = n; + xroutes = new_xroutes; + } + + memcpy(xroutes[numxroutes].prefix, prefix, 16); + xroutes[numxroutes].plen = plen; + xroutes[numxroutes].metric = metric; + xroutes[numxroutes].ifindex = ifindex; + xroutes[numxroutes].proto = proto; + numxroutes++; + return 1; +} + +/* Returns an overestimate of the number of xroutes. */ +int +xroutes_estimate(void) +{ + return numxroutes; +} + +struct xroute_stream { + int index; +}; + +struct +xroute_stream * +xroute_stream(void) +{ + struct xroute_stream *stream = malloc(sizeof(struct xroute_stream)); + if(stream == NULL) + return NULL; + + stream->index = 0; + return stream; +} + +struct xroute * +xroute_stream_next(struct xroute_stream *stream) +{ + if(stream->index < numxroutes) + return &xroutes[stream->index++]; + else + return NULL; +} + +void +xroute_stream_done(struct xroute_stream *stream) +{ + free(stream); +} + +/* add an xroute, verifying some conditions; return 0 if there is no changes */ +static int +xroute_add_new_route(unsigned char prefix[16], unsigned char plen, + unsigned short metric, unsigned int ifindex, + int proto, int send_updates) +{ + int rc; + if(martian_prefix(prefix, plen)) + return 0; + metric = redistribute_filter(prefix, plen, ifindex, proto); + if(metric < INFINITY) { + rc = add_xroute(prefix, plen, metric, ifindex, proto); + if(rc > 0) { + struct babel_route *route; + route = find_installed_route(prefix, plen); + if(route) + uninstall_route(route); + if(send_updates) + send_update(NULL, 0, prefix, plen); + return 1; + } + } + return 0; +} diff --git a/babeld/xroute.h b/babeld/xroute.h new file mode 100644 index 0000000..d2d191c --- /dev/null +++ b/babeld/xroute.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +/* +Copyright (c) 2007, 2008 by Juliusz Chroboczek +Copyright 2011 by Matthieu Boutier and Juliusz Chroboczek +*/ + +#ifndef BABEL_XROUTE_H +#define BABEL_XROUTE_H + +struct xroute { + unsigned char prefix[16]; + unsigned char plen; + unsigned short metric; + unsigned int ifindex; + int proto; +}; + +struct xroute_stream; + +struct xroute *find_xroute(const unsigned char *prefix, unsigned char plen); +void flush_xroute(struct xroute *xroute); +int babel_route_add (struct zapi_route *api); +int babel_route_delete (struct zapi_route *api); +int xroutes_estimate(void); +struct xroute_stream *xroute_stream(void); +struct xroute *xroute_stream_next(struct xroute_stream *stream); +void xroute_stream_done(struct xroute_stream *stream); + +#endif /* BABEL_XROUTE_H */ diff --git a/bfdd/.gitignore b/bfdd/.gitignore new file mode 100644 index 0000000..2b02091 --- /dev/null +++ b/bfdd/.gitignore @@ -0,0 +1,2 @@ +# ignore binary files +bfdd diff --git a/bfdd/Makefile b/bfdd/Makefile new file mode 100644 index 0000000..dfe7823 --- /dev/null +++ b/bfdd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. bfdd/bfdd +%: ALWAYS + @$(MAKE) -s -C .. bfdd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/bfdd/bfd.c b/bfdd/bfd.c new file mode 100644 index 0000000..3096f47 --- /dev/null +++ b/bfdd/bfd.c @@ -0,0 +1,2069 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2013 Cumulus Networks, LLC. All rights reserved. + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * bfd.c: implements the BFD protocol. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include + +#include "lib/jhash.h" +#include "lib/network.h" + +#include "bfd.h" + +DEFINE_MTYPE_STATIC(BFDD, BFDD_CONFIG, "long-lived configuration memory"); +DEFINE_MTYPE_STATIC(BFDD, BFDD_PROFILE, "long-lived profile memory"); +DEFINE_MTYPE_STATIC(BFDD, BFDD_SESSION_OBSERVER, "Session observer"); +DEFINE_MTYPE_STATIC(BFDD, BFDD_VRF, "BFD VRF"); + +/* + * Prototypes + */ +static uint32_t ptm_bfd_gen_ID(void); +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc); +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc); +static const char *get_diag_str(int diag); + +static void bs_admin_down_handler(struct bfd_session *bs, int nstate); +static void bs_down_handler(struct bfd_session *bs, int nstate); +static void bs_init_handler(struct bfd_session *bs, int nstate); +static void bs_up_handler(struct bfd_session *bs, int nstate); + +/** + * Remove BFD profile from all BFD sessions so we don't leave dangling + * pointers. + */ +static void bfd_profile_detach(struct bfd_profile *bp); + +/* Zeroed array with the size of an IPv6 address. */ +struct in6_addr zero_addr; + +/** BFD profiles list. */ +struct bfdproflist bplist; + +/* + * Functions + */ +struct bfd_profile *bfd_profile_lookup(const char *name) +{ + struct bfd_profile *bp; + + TAILQ_FOREACH (bp, &bplist, entry) { + if (strcmp(name, bp->name)) + continue; + + return bp; + } + + return NULL; +} + +static void bfd_profile_set_default(struct bfd_profile *bp) +{ + bp->admin_shutdown = false; + bp->detection_multiplier = BFD_DEFDETECTMULT; + bp->echo_mode = false; + bp->passive = false; + bp->minimum_ttl = BFD_DEF_MHOP_TTL; + bp->min_echo_rx = BFD_DEF_REQ_MIN_ECHO_RX; + bp->min_echo_tx = BFD_DEF_DES_MIN_ECHO_TX; + bp->min_rx = BFD_DEFREQUIREDMINRX; + bp->min_tx = BFD_DEFDESIREDMINTX; +} + +struct bfd_profile *bfd_profile_new(const char *name) +{ + struct bfd_profile *bp; + + /* Search for duplicates. */ + if (bfd_profile_lookup(name) != NULL) + return NULL; + + /* Allocate, name it and put into list. */ + bp = XCALLOC(MTYPE_BFDD_PROFILE, sizeof(*bp)); + strlcpy(bp->name, name, sizeof(bp->name)); + TAILQ_INSERT_TAIL(&bplist, bp, entry); + + /* Set default values. */ + bfd_profile_set_default(bp); + + return bp; +} + +void bfd_profile_free(struct bfd_profile *bp) +{ + /* Detach from any session. */ + if (bglobal.bg_shutdown == false) + bfd_profile_detach(bp); + + /* Remove from global list. */ + TAILQ_REMOVE(&bplist, bp, entry); + + XFREE(MTYPE_BFDD_PROFILE, bp); +} + +void bfd_profile_apply(const char *profname, struct bfd_session *bs) +{ + struct bfd_profile *bp; + + /* Remove previous profile if any. */ + if (bs->profile_name) { + /* We are changing profiles. */ + if (strcmp(bs->profile_name, profname)) { + XFREE(MTYPE_BFDD_PROFILE, bs->profile_name); + bs->profile_name = + XSTRDUP(MTYPE_BFDD_PROFILE, profname); + } + } else /* Save the current profile name (in case it doesn't exist). */ + bs->profile_name = XSTRDUP(MTYPE_BFDD_PROFILE, profname); + + /* Look up new profile to apply. */ + bp = bfd_profile_lookup(profname); + + /* Point to profile if it exists. */ + bs->profile = bp; + + /* Apply configuration. */ + bfd_session_apply(bs); +} + +void bfd_session_apply(struct bfd_session *bs) +{ + struct bfd_profile *bp; + uint32_t min_tx = bs->timers.desired_min_tx; + uint32_t min_rx = bs->timers.required_min_rx; + + /* Pick the source of configuration. */ + bp = bs->profile ? bs->profile : &bs->peer_profile; + + /* Set multiplier if not the default. */ + if (bs->peer_profile.detection_multiplier == BFD_DEFDETECTMULT) + bs->detect_mult = bp->detection_multiplier; + else + bs->detect_mult = bs->peer_profile.detection_multiplier; + + /* Set timers if not the default. */ + if (bs->peer_profile.min_tx == BFD_DEFDESIREDMINTX) + bs->timers.desired_min_tx = bp->min_tx; + else + bs->timers.desired_min_tx = bs->peer_profile.min_tx; + + if (bs->peer_profile.min_rx == BFD_DEFREQUIREDMINRX) + bs->timers.required_min_rx = bp->min_rx; + else + bs->timers.required_min_rx = bs->peer_profile.min_rx; + + /* We can only apply echo options on single hop sessions. */ + if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + /* Configure echo timers if they were default. */ + if (bs->peer_profile.min_echo_rx == BFD_DEF_REQ_MIN_ECHO_RX) + bs->timers.required_min_echo_rx = bp->min_echo_rx; + else + bs->timers.required_min_echo_rx = + bs->peer_profile.min_echo_rx; + + if (bs->peer_profile.min_echo_tx == BFD_DEF_DES_MIN_ECHO_TX) + bs->timers.desired_min_echo_tx = bp->min_echo_tx; + else + bs->timers.desired_min_echo_tx = + bs->peer_profile.min_echo_tx; + + /* Toggle echo if default value. */ + if (bs->peer_profile.echo_mode == false) + bfd_set_echo(bs, bp->echo_mode); + else + bfd_set_echo(bs, bs->peer_profile.echo_mode); + } else { + /* Configure the TTL packet filter. */ + if (bs->peer_profile.minimum_ttl == BFD_DEF_MHOP_TTL) + bs->mh_ttl = bp->minimum_ttl; + else + bs->mh_ttl = bs->peer_profile.minimum_ttl; + } + + /* Toggle 'passive-mode' if default value. */ + if (bs->peer_profile.passive == false) + bfd_set_passive_mode(bs, bp->passive); + else + bfd_set_passive_mode(bs, bs->peer_profile.passive); + + /* Toggle 'no shutdown' if default value. */ + if (bs->peer_profile.admin_shutdown == false) + bfd_set_shutdown(bs, bp->admin_shutdown); + else + bfd_set_shutdown(bs, bs->peer_profile.admin_shutdown); + + /* If session interval changed negotiate new timers. */ + if (bs->ses_state == PTM_BFD_UP + && (bs->timers.desired_min_tx != min_tx + || bs->timers.required_min_rx != min_rx)) + bfd_set_polling(bs); + + /* Send updated information to data plane. */ + bfd_dplane_update_session(bs); +} + +void bfd_profile_remove(struct bfd_session *bs) +{ + /* Remove any previous set profile name. */ + XFREE(MTYPE_BFDD_PROFILE, bs->profile_name); + bs->profile = NULL; + + bfd_session_apply(bs); +} + +void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, + struct sockaddr_any *local, bool mhop, const char *ifname, + const char *vrfname) +{ + memset(key, 0, sizeof(*key)); + + switch (peer->sa_sin.sin_family) { + case AF_INET: + key->family = AF_INET; + memcpy(&key->peer, &peer->sa_sin.sin_addr, + sizeof(peer->sa_sin.sin_addr)); + memcpy(&key->local, &local->sa_sin.sin_addr, + sizeof(local->sa_sin.sin_addr)); + break; + case AF_INET6: + key->family = AF_INET6; + memcpy(&key->peer, &peer->sa_sin6.sin6_addr, + sizeof(peer->sa_sin6.sin6_addr)); + memcpy(&key->local, &local->sa_sin6.sin6_addr, + sizeof(local->sa_sin6.sin6_addr)); + break; + } + + key->mhop = mhop; + if (ifname && ifname[0]) + strlcpy(key->ifname, ifname, sizeof(key->ifname)); + if (vrfname && vrfname[0]) + strlcpy(key->vrfname, vrfname, sizeof(key->vrfname)); + else + strlcpy(key->vrfname, VRF_DEFAULT_NAME, sizeof(key->vrfname)); +} + +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + struct peer_label *pl; + struct bfd_key key; + + /* Try to find label first. */ + if (bpc->bpc_has_label) { + pl = pl_find(bpc->bpc_label); + if (pl != NULL) { + bs = pl->pl_bs; + return bs; + } + } + + /* Otherwise fallback to peer/local hash lookup. */ + gen_bfd_key(&key, &bpc->bpc_peer, &bpc->bpc_local, bpc->bpc_mhop, + bpc->bpc_localif, bpc->bpc_vrfname); + + return bfd_key_lookup(key); +} + +/* + * Starts a disabled BFD session. + * + * A session is disabled when the specified interface/VRF doesn't exist + * yet. It might happen on FRR boot or with virtual interfaces. + */ +int bfd_session_enable(struct bfd_session *bs) +{ + struct interface *ifp = NULL; + struct vrf *vrf = NULL; + int psock; + + /* We are using data plane, we don't need software. */ + if (bs->bdc) + return 0; + + /* + * If the interface or VRF doesn't exist, then we must register + * the session but delay its start. + */ + if (bs->key.vrfname[0]) { + vrf = vrf_lookup_by_name(bs->key.vrfname); + if (vrf == NULL) { + zlog_err( + "session-enable: specified VRF %s doesn't exists.", + bs->key.vrfname); + return 0; + } + } else { + vrf = vrf_lookup_by_id(VRF_DEFAULT); + } + + assert(vrf); + + if (bs->key.ifname[0]) { + ifp = if_lookup_by_name(bs->key.ifname, vrf->vrf_id); + if (ifp == NULL) { + zlog_err( + "session-enable: specified interface %s (VRF %s) doesn't exist.", + bs->key.ifname, vrf->name); + return 0; + } + } + + /* Assign interface/VRF pointers. */ + bs->vrf = vrf; + + /* Assign interface pointer (if any). */ + bs->ifp = ifp; + + /* Attempt to use data plane. */ + if (bglobal.bg_use_dplane && bfd_dplane_add_session(bs) == 0) { + control_notify_config(BCM_NOTIFY_CONFIG_ADD, bs); + return 0; + } + + /* Sanity check: don't leak open sockets. */ + if (bs->sock != -1) { + if (bglobal.debug_peer_event) + zlog_debug("%s: previous socket open", __func__); + + close(bs->sock); + bs->sock = -1; + } + + /* + * Get socket for transmitting control packets. Note that if we + * could use the destination port (3784) for the source + * port we wouldn't need a socket per session. + */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6) == 0) { + psock = bp_peer_socket(bs); + if (psock == -1) + return 0; + } else { + psock = bp_peer_socketv6(bs); + if (psock == -1) + return 0; + } + + /* + * We've got a valid socket, lets start the timers and the + * protocol. + */ + bs->sock = psock; + + /* Only start timers if we are using active mode. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE) == 0) { + bfd_recvtimer_update(bs); + ptm_bfd_start_xmt_timer(bs, false); + } + + /* initialize RTT */ + bfd_rtt_init(bs); + + return 0; +} + +/* + * Disabled a running BFD session. + * + * A session is disabled when the specified interface/VRF gets removed + * (e.g. virtual interfaces). + */ +void bfd_session_disable(struct bfd_session *bs) +{ + /* We are using data plane, we don't need software. */ + if (bs->bdc) + return; + + /* Free up socket resources. */ + if (bs->sock != -1) { + close(bs->sock); + bs->sock = -1; + } + + /* Disable all timers. */ + bfd_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + ptm_bfd_echo_stop(bs); + + /* Set session down so it doesn't report UP and disabled. */ + ptm_bfd_sess_dn(bs, BD_PATH_DOWN); +} + +static uint32_t ptm_bfd_gen_ID(void) +{ + uint32_t session_id; + + /* + * RFC 5880, Section 6.8.1. recommends that we should generate + * random session identification numbers. + */ + do { + session_id = ((frr_weak_random() << 16) & 0xFFFF0000) + | (frr_weak_random() & 0x0000FFFF); + } while (session_id == 0 || bfd_id_lookup(session_id) != NULL); + + return session_id; +} + +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo) +{ + uint64_t jitter, xmt_TO; + int maxpercent; + + xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO; + + /* + * From section 6.5.2: trasmit interval should be randomly jittered + * between + * 75% and 100% of nominal value, unless detect_mult is 1, then should + * be + * between 75% and 90%. + */ + maxpercent = (bfd->detect_mult == 1) ? 16 : 26; + jitter = (xmt_TO * (75 + (frr_weak_random() % maxpercent))) / 100; + /* XXX remove that division above */ + + if (is_echo) + bfd_echo_xmttimer_update(bfd, jitter); + else + bfd_xmttimer_update(bfd, jitter); +} + +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd) +{ + /* Send the scheduled echo packet */ + /* if ipv4 use the new echo implementation that causes + * the packet to be looped in forwarding plane of peer + */ + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6) == 0) +#ifdef BFD_LINUX + ptm_bfd_echo_fp_snd(bfd); +#else + ptm_bfd_echo_snd(bfd); +#endif + else + ptm_bfd_echo_snd(bfd); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, true); +} + +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit) +{ + /* Send the scheduled control packet */ + ptm_bfd_snd(bfd, fbit); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, false); +} + +void ptm_bfd_echo_stop(struct bfd_session *bfd) +{ + bfd->echo_xmt_TO = 0; + bfd->echo_detect_TO = 0; + UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + + bfd_echo_xmttimer_delete(bfd); + bfd_echo_recvtimer_delete(bfd); +} + +void ptm_bfd_echo_start(struct bfd_session *bfd) +{ + bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO); + if (bfd->echo_detect_TO > 0) { + bfd_echo_recvtimer_update(bfd); + ptm_bfd_echo_xmt_TO(bfd); + } +} + +void ptm_bfd_sess_up(struct bfd_session *bfd) +{ + int old_state = bfd->ses_state; + + bfd->local_diag = 0; + bfd->ses_state = PTM_BFD_UP; + monotime(&bfd->uptime); + + /* Connection is up, lets negotiate timers. */ + bfd_set_polling(bfd); + + /* Start sending control packets with poll bit immediately. */ + ptm_bfd_snd(bfd, 0); + + control_notify(bfd, bfd->ses_state); + + if (old_state != bfd->ses_state) { + bfd->stats.session_up++; + if (bglobal.debug_peer_event) + zlog_debug("state-change: [%s] %s -> %s", + bs_to_string(bfd), state_list[old_state].str, + state_list[bfd->ses_state].str); + } +} + +void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag) +{ + int old_state = bfd->ses_state; + + bfd->local_diag = diag; + bfd->discrs.remote_discr = 0; + bfd->ses_state = PTM_BFD_DOWN; + bfd->polling = 0; + bfd->demand_mode = 0; + monotime(&bfd->downtime); + + /* + * Only attempt to send if we have a valid socket: + * this function might be called by session disablers and in + * this case we won't have a valid socket (i.e. interface was + * removed or VRF doesn't exist anymore). + */ + if (bfd->sock != -1) + ptm_bfd_snd(bfd, 0); + + /* Slow down the control packets, the connection is down. */ + bs_set_slow_timers(bfd); + + /* only signal clients when going from up->down state */ + if (old_state == PTM_BFD_UP) + control_notify(bfd, PTM_BFD_DOWN); + + /* Stop echo packet transmission if they are active */ + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + ptm_bfd_echo_stop(bfd); + + /* Stop attempting to transmit or expect control packets if passive. */ + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_PASSIVE)) { + bfd_recvtimer_delete(bfd); + bfd_xmttimer_delete(bfd); + } + + if (old_state != bfd->ses_state) { + bfd->stats.session_down++; + if (bglobal.debug_peer_event) + zlog_debug("state-change: [%s] %s -> %s reason:%s", + bs_to_string(bfd), state_list[old_state].str, + state_list[bfd->ses_state].str, + get_diag_str(bfd->local_diag)); + } + + /* clear peer's mac address */ + UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET); + memset(bfd->peer_hw_addr, 0, sizeof(bfd->peer_hw_addr)); + /* reset local address ,it might has been be changed after bfd is up*/ + memset(&bfd->local_address, 0, sizeof(bfd->local_address)); + + /* reset RTT */ + bfd_rtt_init(bfd); +} + +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc) +{ + struct bfd_session *bs; + + bs = bfd_id_lookup(ldisc); + if (bs == NULL) + return NULL; + + switch (bs->key.family) { + case AF_INET: + if (memcmp(&sa->sa_sin.sin_addr, &bs->key.peer, + sizeof(sa->sa_sin.sin_addr))) + return NULL; + break; + case AF_INET6: + if (memcmp(&sa->sa_sin6.sin6_addr, &bs->key.peer, + sizeof(sa->sa_sin6.sin6_addr))) + return NULL; + break; + } + + return bs; +} + +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, + struct sockaddr_any *peer, + struct sockaddr_any *local, + struct interface *ifp, + vrf_id_t vrfid, + bool is_mhop) +{ + struct vrf *vrf; + struct bfd_key key; + + /* Find our session using the ID signaled by the remote end. */ + if (cp->discrs.remote_discr) + return bfd_find_disc(peer, ntohl(cp->discrs.remote_discr)); + + /* Search for session without using discriminator. */ + vrf = vrf_lookup_by_id(vrfid); + + gen_bfd_key(&key, peer, local, is_mhop, ifp ? ifp->name : NULL, + vrf ? vrf->name : VRF_DEFAULT_NAME); + + /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */ + return bfd_key_lookup(key); +} + +void bfd_xmt_cb(struct event *t) +{ + struct bfd_session *bs = EVENT_ARG(t); + + ptm_bfd_xmt_TO(bs, 0); +} + +void bfd_echo_xmt_cb(struct event *t) +{ + struct bfd_session *bs = EVENT_ARG(t); + + if (bs->echo_xmt_TO > 0) + ptm_bfd_echo_xmt_TO(bs); +} + +/* Was ptm_bfd_detect_TO() */ +void bfd_recvtimer_cb(struct event *t) +{ + struct bfd_session *bs = EVENT_ARG(t); + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_sess_dn(bs, BD_CONTROL_EXPIRED); + break; + } +} + +/* Was ptm_bfd_echo_detect_TO() */ +void bfd_echo_recvtimer_cb(struct event *t) +{ + struct bfd_session *bs = EVENT_ARG(t); + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_sess_dn(bs, BD_ECHO_FAILED); + break; + } +} + +struct bfd_session *bfd_session_new(void) +{ + struct bfd_session *bs; + + bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs)); + + /* Set peer session defaults. */ + bfd_profile_set_default(&bs->peer_profile); + + bs->timers.desired_min_tx = BFD_DEFDESIREDMINTX; + bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX; + bs->timers.required_min_echo_rx = BFD_DEF_REQ_MIN_ECHO_RX; + bs->timers.desired_min_echo_tx = BFD_DEF_DES_MIN_ECHO_TX; + bs->detect_mult = BFD_DEFDETECTMULT; + bs->mh_ttl = BFD_DEF_MHOP_TTL; + bs->ses_state = PTM_BFD_DOWN; + + /* Initiate connection with slow timers. */ + bs_set_slow_timers(bs); + + /* Initiate remote settings as well. */ + bs->remote_timers = bs->cur_timers; + bs->remote_detect_mult = BFD_DEFDETECTMULT; + + bs->sock = -1; + monotime(&bs->uptime); + bs->downtime = bs->uptime; + + return bs; +} + +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel) +{ + /* New label treatment: + * - Check if the label is taken; + * - Try to allocate the memory for it and register; + */ + if (bs->pl == NULL) { + if (pl_find(nlabel) != NULL) { + /* Someone is already using it. */ + return -1; + } + + pl_new(nlabel, bs); + + return 0; + } + + /* + * Test label change consistency: + * - Do nothing if it's the same label; + * - Check if the future label is already taken; + * - Change label; + */ + if (strcmp(nlabel, bs->pl->pl_label) == 0) + return -1; + if (pl_find(nlabel) != NULL) + return -1; + + strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label)); + return 0; +} + +static void _bfd_session_update(struct bfd_session *bs, + struct bfd_peer_cfg *bpc) +{ + if (bpc->bpc_has_txinterval) { + bs->timers.desired_min_tx = bpc->bpc_txinterval * 1000; + bs->peer_profile.min_tx = bs->timers.desired_min_tx; + } + + if (bpc->bpc_has_recvinterval) { + bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000; + bs->peer_profile.min_rx = bs->timers.required_min_rx; + } + + if (bpc->bpc_has_detectmultiplier) { + bs->detect_mult = bpc->bpc_detectmultiplier; + bs->peer_profile.detection_multiplier = bs->detect_mult; + } + + if (bpc->bpc_has_echorecvinterval) { + bs->timers.required_min_echo_rx = bpc->bpc_echorecvinterval * 1000; + bs->peer_profile.min_echo_rx = bs->timers.required_min_echo_rx; + } + + if (bpc->bpc_has_echotxinterval) { + bs->timers.desired_min_echo_tx = bpc->bpc_echotxinterval * 1000; + bs->peer_profile.min_echo_tx = bs->timers.desired_min_echo_tx; + } + + if (bpc->bpc_has_label) + bfd_session_update_label(bs, bpc->bpc_label); + + if (bpc->bpc_cbit) + SET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT); + else + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT); + + if (bpc->bpc_has_minimum_ttl) { + bs->mh_ttl = bpc->bpc_minimum_ttl; + bs->peer_profile.minimum_ttl = bpc->bpc_minimum_ttl; + } + + bs->peer_profile.echo_mode = bpc->bpc_echo; + bfd_set_echo(bs, bpc->bpc_echo); + + /* + * Shutdown needs to be the last in order to avoid timers enable when + * the session is disabled. + */ + bs->peer_profile.admin_shutdown = bpc->bpc_shutdown; + bfd_set_passive_mode(bs, bpc->bpc_passive); + bfd_set_shutdown(bs, bpc->bpc_shutdown); + + /* + * Apply profile last: it also calls `bfd_set_shutdown`. + * + * There is no problem calling `shutdown` twice if the value doesn't + * change or if it is overridden by peer specific configuration. + */ + if (bpc->bpc_has_profile) + bfd_profile_apply(bpc->bpc_profile, bs); +} + +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ + /* User didn't want to update, return failure. */ + if (bpc->bpc_createonly) + return -1; + + _bfd_session_update(bs, bpc); + + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); + + return 0; +} + +void bfd_session_free(struct bfd_session *bs) +{ + struct bfd_session_observer *bso; + + bfd_session_disable(bs); + + /* Remove session from data plane if any. */ + bfd_dplane_delete_session(bs); + + bfd_key_delete(bs->key); + bfd_id_delete(bs->discrs.my_discr); + + /* Remove observer if any. */ + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + if (bso->bso_bs != bs) + continue; + + break; + } + if (bso != NULL) + bs_observer_del(bso); + + pl_free(bs->pl); + + XFREE(MTYPE_BFDD_PROFILE, bs->profile_name); + XFREE(MTYPE_BFDD_CONFIG, bs); +} + +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bfd, *l_bfd; + + /* check to see if this needs a new session */ + l_bfd = bs_peer_find(bpc); + if (l_bfd) { + /* Requesting a duplicated peer means update configuration. */ + if (bfd_session_update(l_bfd, bpc) == 0) + return l_bfd; + else + return NULL; + } + + /* Get BFD session storage with its defaults. */ + bfd = bfd_session_new(); + + /* + * Store interface/VRF name in case we need to delay session + * start. See `bfd_session_enable` for more information. + */ + if (bpc->bpc_has_localif) + strlcpy(bfd->key.ifname, bpc->bpc_localif, + sizeof(bfd->key.ifname)); + + if (bpc->bpc_has_vrfname) + strlcpy(bfd->key.vrfname, bpc->bpc_vrfname, + sizeof(bfd->key.vrfname)); + else + strlcpy(bfd->key.vrfname, VRF_DEFAULT_NAME, + sizeof(bfd->key.vrfname)); + + /* Copy remaining data. */ + if (bpc->bpc_ipv4 == false) + SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); + + bfd->key.family = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; + switch (bfd->key.family) { + case AF_INET: + memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin.sin_addr, + sizeof(bpc->bpc_peer.sa_sin.sin_addr)); + memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin.sin_addr, + sizeof(bpc->bpc_local.sa_sin.sin_addr)); + break; + + case AF_INET6: + memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin6.sin6_addr, + sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); + memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin6.sin6_addr, + sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); + break; + + default: + assert(1); + break; + } + + if (bpc->bpc_mhop) + SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); + + bfd->key.mhop = bpc->bpc_mhop; + + if (bs_registrate(bfd) == NULL) + return NULL; + + /* Apply other configurations. */ + _bfd_session_update(bfd, bpc); + + return bfd; +} + +struct bfd_session *bs_registrate(struct bfd_session *bfd) +{ + /* Registrate session into data structures. */ + bfd_key_insert(bfd); + bfd->discrs.my_discr = ptm_bfd_gen_ID(); + bfd_id_insert(bfd); + + /* Try to enable session and schedule for packet receive/send. */ + if (bfd_session_enable(bfd) == -1) { + /* Unrecoverable failure, remove the session/peer. */ + bfd_session_free(bfd); + return NULL; + } + + /* Add observer if we have moving parts. */ + if (bfd->key.ifname[0] || bfd->key.vrfname[0] || bfd->sock == -1) + bs_observer_add(bfd); + + if (bglobal.debug_peer_event) + zlog_debug("session-new: %s", bs_to_string(bfd)); + + control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd); + + return bfd; +} + +int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + + /* Find session and call free(). */ + bs = bs_peer_find(bpc); + if (bs == NULL) + return -1; + + /* This pointer is being referenced, don't let it be deleted. */ + if (bs->refcount > 0) { + zlog_err("session-delete: refcount failure: %" PRIu64" references", + bs->refcount); + return -1; + } + + if (bglobal.debug_peer_event) + zlog_debug("%s: %s", __func__, bs_to_string(bs)); + + control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); + + bfd_session_free(bs); + + return 0; +} + +void bfd_set_polling(struct bfd_session *bs) +{ + /* + * Start polling procedure: the only timers that require polling + * to change value without losing connection are: + * + * - Desired minimum transmission interval; + * - Required minimum receive interval; + * + * RFC 5880, Section 6.8.3. + */ + bs->polling = 1; +} + +/* + * bs__handler() functions implement the BFD state machine + * transition mechanism. `` is the current session state and + * the parameter `nstate` is the peer new state. + */ +static void bs_admin_down_handler(struct bfd_session *bs + __attribute__((__unused__)), + int nstate __attribute__((__unused__))) +{ + /* + * We are administratively down, there is no state machine + * handling. + */ +} + +static void bs_down_handler(struct bfd_session *bs, int nstate) +{ + switch (nstate) { + case PTM_BFD_ADM_DOWN: + /* + * Remote peer doesn't want to talk, so lets keep the + * connection down. + */ + case PTM_BFD_UP: + /* Peer can't be up yet, wait it go to 'init' or 'down'. */ + break; + + case PTM_BFD_DOWN: + /* + * Remote peer agreed that the path is down, lets try to + * bring it up. + */ + bs->ses_state = PTM_BFD_INIT; + + /* + * RFC 5880, Section 6.1. + * A system taking the Passive role MUST NOT begin + * sending BFD packets for a particular session until + * it has received a BFD packet for that session, and thus + * has learned the remote system's discriminator value. + * + * Now we can start transmission timer in passive mode. + */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE)) + ptm_bfd_xmt_TO(bs, 0); + + break; + + case PTM_BFD_INIT: + /* + * Remote peer told us his path is up, lets turn + * activate the session. + */ + ptm_bfd_sess_up(bs); + break; + + default: + if (bglobal.debug_peer_event) + zlog_debug("state-change: unhandled neighbor state: %d", + nstate); + break; + } +} + +static void bs_init_handler(struct bfd_session *bs, int nstate) +{ + switch (nstate) { + case PTM_BFD_ADM_DOWN: + /* + * Remote peer doesn't want to talk, so lets make the + * connection down. + */ + ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN); + break; + + case PTM_BFD_DOWN: + /* Remote peer hasn't moved to first stage yet. */ + break; + + case PTM_BFD_INIT: + case PTM_BFD_UP: + /* We agreed on the settings and the path is up. */ + ptm_bfd_sess_up(bs); + break; + + default: + if (bglobal.debug_peer_event) + zlog_debug("state-change: unhandled neighbor state: %d", + nstate); + break; + } +} + +static void bs_up_handler(struct bfd_session *bs, int nstate) +{ + switch (nstate) { + case PTM_BFD_ADM_DOWN: + case PTM_BFD_DOWN: + /* Peer lost or asked to shutdown connection. */ + ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN); + break; + + case PTM_BFD_INIT: + case PTM_BFD_UP: + /* Path is up and working. */ + break; + + default: + if (bglobal.debug_peer_event) + zlog_debug("state-change: unhandled neighbor state: %d", + nstate); + break; + } +} + +void bs_state_handler(struct bfd_session *bs, int nstate) +{ + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + bs_admin_down_handler(bs, nstate); + break; + case PTM_BFD_DOWN: + bs_down_handler(bs, nstate); + break; + case PTM_BFD_INIT: + bs_init_handler(bs, nstate); + break; + case PTM_BFD_UP: + bs_up_handler(bs, nstate); + break; + + default: + if (bglobal.debug_peer_event) + zlog_debug("state-change: [%s] is in invalid state: %d", + bs_to_string(bs), nstate); + break; + } +} + +/* + * Handles echo timer manipulation after updating timer. + */ +void bs_echo_timer_handler(struct bfd_session *bs) +{ + uint32_t old_timer; + + /* + * Before doing any echo handling, check if it is possible to + * use it. + * + * - Check for `echo-mode` configuration. + * - Check that we are not using multi hop (RFC 5883, + * Section 3). + * - Check that we are already at the up state. + */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO) == 0 + || CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + || bs->ses_state != PTM_BFD_UP) + return; + + /* Remote peer asked to stop echo. */ + if (bs->remote_timers.required_min_echo == 0) { + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + ptm_bfd_echo_stop(bs); + + return; + } + + /* + * Calculate the echo transmission timer: we must not send + * echo packets faster than the minimum required time + * announced by the remote system. + * + * RFC 5880, Section 6.8.9. + */ + old_timer = bs->echo_xmt_TO; + if (bs->remote_timers.required_min_echo > bs->timers.desired_min_echo_tx) + bs->echo_xmt_TO = bs->remote_timers.required_min_echo; + else + bs->echo_xmt_TO = bs->timers.desired_min_echo_tx; + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE) == 0 + || old_timer != bs->echo_xmt_TO) + ptm_bfd_echo_start(bs); +} + +/* + * RFC 5880 Section 6.5. + * + * When a BFD control packet with the final bit is received, we must + * update the session parameters. + */ +void bs_final_handler(struct bfd_session *bs) +{ + /* Start using our new timers. */ + bs->cur_timers.desired_min_tx = bs->timers.desired_min_tx; + bs->cur_timers.required_min_rx = bs->timers.required_min_rx; + + /* + * TODO: demand mode. See RFC 5880 Section 6.1. + * + * When using demand mode we must disable the detection timer + * for lost control packets. + */ + if (bs->demand_mode) { + /* Notify watchers about changed timers. */ + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); + return; + } + + /* + * Calculate transmission time based on new timers. + * + * Transmission calculation: + * Unless specified by exceptions at the end of Section 6.8.7, the + * transmission time will be determined by the system with the + * slowest rate. + * + * RFC 5880, Section 6.8.7. + */ + if (bs->timers.desired_min_tx > bs->remote_timers.required_min_rx) + bs->xmt_TO = bs->timers.desired_min_tx; + else + bs->xmt_TO = bs->remote_timers.required_min_rx; + + /* Apply new transmission timer immediately. */ + ptm_bfd_start_xmt_timer(bs, false); + + /* Notify watchers about changed timers. */ + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); +} + +void bs_set_slow_timers(struct bfd_session *bs) +{ + /* + * BFD connection must use slow timers before going up or after + * losing connectivity to avoid wasting bandwidth. + * + * RFC 5880, Section 6.8.3. + */ + bs->cur_timers.desired_min_tx = BFD_DEF_SLOWTX; + bs->cur_timers.required_min_rx = BFD_DEF_SLOWTX; + bs->cur_timers.required_min_echo = 0; + + /* Set the appropriated timeouts for slow connection. */ + bs->detect_TO = (BFD_DEFDETECTMULT * BFD_DEF_SLOWTX); + bs->xmt_TO = BFD_DEF_SLOWTX; +} + +void bfd_set_echo(struct bfd_session *bs, bool echo) +{ + if (echo) { + /* Check if echo mode is already active. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + return; + + SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + + /* Activate/update echo receive timeout timer. */ + if (bs->bdc == NULL) + bs_echo_timer_handler(bs); + } else { + /* Check if echo mode is already disabled. */ + if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + return; + + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + + /* Deactivate timeout timer. */ + if (bs->bdc == NULL) + ptm_bfd_echo_stop(bs); + } +} + +void bfd_set_shutdown(struct bfd_session *bs, bool shutdown) +{ + bool is_shutdown; + + /* + * Special case: we are batching changes and the previous state was + * not shutdown. Instead of potentially disconnect a running peer, + * we'll get the current status to validate we were really down. + */ + if (bs->ses_state == PTM_BFD_UP) + is_shutdown = false; + else + is_shutdown = CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + if (shutdown) { + /* Already shutdown. */ + if (is_shutdown) + return; + + SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Handle data plane shutdown case. */ + if (bs->bdc) { + bs->ses_state = PTM_BFD_ADM_DOWN; + bfd_dplane_update_session(bs); + control_notify(bs, bs->ses_state); + return; + } + + /* Disable all events. */ + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_ADM_DOWN; + control_notify(bs, bs->ses_state); + + /* Don't try to send packets with a disabled session. */ + if (bs->sock != -1) + ptm_bfd_snd(bs, 0); + } else { + /* Already working. */ + if (!is_shutdown) + return; + + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Handle data plane shutdown case. */ + if (bs->bdc) { + bs->ses_state = PTM_BFD_DOWN; + bfd_dplane_update_session(bs); + control_notify(bs, bs->ses_state); + return; + } + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_DOWN; + control_notify(bs, bs->ses_state); + + /* Enable timers if non passive, otherwise stop them. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE)) { + bfd_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + } else { + bfd_recvtimer_update(bs); + bfd_xmttimer_update(bs, bs->xmt_TO); + } + } +} + +void bfd_set_passive_mode(struct bfd_session *bs, bool passive) +{ + if (passive) { + SET_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE); + + /* Session is already up and running, nothing to do now. */ + if (bs->ses_state != PTM_BFD_DOWN) + return; + + /* Lets disable the timers since we are now passive. */ + bfd_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + } else { + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE); + + /* Session is already up and running, nothing to do now. */ + if (bs->ses_state != PTM_BFD_DOWN) + return; + + /* Session is down, let it attempt to start the connection. */ + bfd_xmttimer_update(bs, bs->xmt_TO); + bfd_recvtimer_update(bs); + } +} + +/* + * Helper functions. + */ +static const char *get_diag_str(int diag) +{ + for (int i = 0; diag_list[i].str; i++) { + if (diag_list[i].type == diag) + return diag_list[i].str; + } + return "N/A"; +} + +const char *satostr(const struct sockaddr_any *sa) +{ +#define INETSTR_BUFCOUNT 8 + static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN]; + static int bufidx; + const struct sockaddr_in *sin = &sa->sa_sin; + const struct sockaddr_in6 *sin6 = &sa->sa_sin6; + + bufidx += (bufidx + 1) % INETSTR_BUFCOUNT; + buf[bufidx][0] = 0; + + switch (sin->sin_family) { + case AF_INET: + inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + case AF_INET6: + inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + + default: + strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx])); + break; + } + + return buf[bufidx]; +} + +const char *diag2str(uint8_t diag) +{ + switch (diag) { + case 0: + return "ok"; + case 1: + return "control detection time expired"; + case 2: + return "echo function failed"; + case 3: + return "neighbor signaled session down"; + case 4: + return "forwarding plane reset"; + case 5: + return "path down"; + case 6: + return "concatenated path down"; + case 7: + return "administratively down"; + case 8: + return "reverse concatenated path down"; + default: + return "unknown"; + } +} + +int strtosa(const char *addr, struct sockaddr_any *sa) +{ + memset(sa, 0, sizeof(*sa)); + + if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) { + sa->sa_sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) { + sa->sa_sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + return -1; +} + +void integer2timestr(uint64_t time, char *buf, size_t buflen) +{ + uint64_t year, month, day, hour, minute, second; + int rv; + +#define MINUTES (60) +#define HOURS (60 * MINUTES) +#define DAYS (24 * HOURS) +#define MONTHS (30 * DAYS) +#define YEARS (12 * MONTHS) + if (time >= YEARS) { + year = time / YEARS; + time -= year * YEARS; + + rv = snprintfrr(buf, buflen, "%" PRIu64 " year(s), ", year); + buf += rv; + buflen -= rv; + } + if (time >= MONTHS) { + month = time / MONTHS; + time -= month * MONTHS; + + rv = snprintfrr(buf, buflen, "%" PRIu64 " month(s), ", month); + buf += rv; + buflen -= rv; + } + if (time >= DAYS) { + day = time / DAYS; + time -= day * DAYS; + + rv = snprintfrr(buf, buflen, "%" PRIu64 " day(s), ", day); + buf += rv; + buflen -= rv; + } + if (time >= HOURS) { + hour = time / HOURS; + time -= hour * HOURS; + + rv = snprintfrr(buf, buflen, "%" PRIu64 " hour(s), ", hour); + buf += rv; + buflen -= rv; + } + if (time >= MINUTES) { + minute = time / MINUTES; + time -= minute * MINUTES; + + rv = snprintfrr(buf, buflen, "%" PRIu64 " minute(s), ", minute); + buf += rv; + buflen -= rv; + } + second = time % MINUTES; + snprintfrr(buf, buflen, "%" PRIu64 " second(s)", second); +} + +const char *bs_to_string(const struct bfd_session *bs) +{ + static char buf[256]; + char addr_buf[INET6_ADDRSTRLEN]; + int pos; + bool is_mhop = CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); + + pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no"); + pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s", + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, + sizeof(addr_buf))); + pos += snprintf(buf + pos, sizeof(buf) - pos, " local:%s", + inet_ntop(bs->key.family, &bs->key.local, addr_buf, + sizeof(addr_buf))); + if (bs->key.vrfname[0]) + pos += snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s", + bs->key.vrfname); + if (bs->key.ifname[0]) + pos += snprintf(buf + pos, sizeof(buf) - pos, " ifname:%s", + bs->key.ifname); + + (void)pos; + + return buf; +} + +int bs_observer_add(struct bfd_session *bs) +{ + struct bfd_session_observer *bso; + + bso = XCALLOC(MTYPE_BFDD_SESSION_OBSERVER, sizeof(*bso)); + bso->bso_bs = bs; + bso->bso_addr.family = bs->key.family; + memcpy(&bso->bso_addr.u.prefix, &bs->key.local, + sizeof(bs->key.local)); + + TAILQ_INSERT_TAIL(&bglobal.bg_obslist, bso, bso_entry); + + return 0; +} + +void bs_observer_del(struct bfd_session_observer *bso) +{ + TAILQ_REMOVE(&bglobal.bg_obslist, bso, bso_entry); + XFREE(MTYPE_BFDD_SESSION_OBSERVER, bso); +} + +void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ + memset(bpc, 0, sizeof(*bpc)); + + bpc->bpc_ipv4 = (bs->key.family == AF_INET); + bpc->bpc_mhop = bs->key.mhop; + + switch (bs->key.family) { + case AF_INET: + bpc->bpc_peer.sa_sin.sin_family = AF_INET; + memcpy(&bpc->bpc_peer.sa_sin.sin_addr, &bs->key.peer, + sizeof(bpc->bpc_peer.sa_sin.sin_addr)); + + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) { + bpc->bpc_local.sa_sin.sin_family = AF_INET6; + memcpy(&bpc->bpc_local.sa_sin.sin_addr, &bs->key.local, + sizeof(bpc->bpc_local.sa_sin.sin_addr)); + } + break; + + case AF_INET6: + bpc->bpc_peer.sa_sin.sin_family = AF_INET6; + memcpy(&bpc->bpc_peer.sa_sin6.sin6_addr, &bs->key.peer, + sizeof(bpc->bpc_peer.sa_sin6.sin6_addr)); + + bpc->bpc_local.sa_sin6.sin6_family = AF_INET6; + memcpy(&bpc->bpc_local.sa_sin6.sin6_addr, &bs->key.local, + sizeof(bpc->bpc_local.sa_sin6.sin6_addr)); + break; + } + + if (bs->key.ifname[0]) { + bpc->bpc_has_localif = true; + strlcpy(bpc->bpc_localif, bs->key.ifname, + sizeof(bpc->bpc_localif)); + } + + if (bs->key.vrfname[0]) { + bpc->bpc_has_vrfname = true; + strlcpy(bpc->bpc_vrfname, bs->key.vrfname, + sizeof(bpc->bpc_vrfname)); + } +} + + +/* + * BFD hash data structures to find sessions. + */ +static struct hash *bfd_id_hash; +static struct hash *bfd_key_hash; + +static unsigned int bfd_id_hash_do(const void *p); +static unsigned int bfd_key_hash_do(const void *p); + +static void _bfd_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))); + +/* BFD hash for our discriminator. */ +static unsigned int bfd_id_hash_do(const void *p) +{ + const struct bfd_session *bs = p; + + return jhash_1word(bs->discrs.my_discr, 0); +} + +static bool bfd_id_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return bs1->discrs.my_discr == bs2->discrs.my_discr; +} + +/* BFD hash for single hop. */ +static unsigned int bfd_key_hash_do(const void *p) +{ + const struct bfd_session *bs = p; + struct bfd_key key = bs->key; + + /* + * Local address and interface name are optional and + * can be filled any time after session creation. + * Hash key should not depend on these fields. + */ + memset(&key.local, 0, sizeof(key.local)); + memset(key.ifname, 0, sizeof(key.ifname)); + + return jhash(&key, sizeof(key), 0); +} + +static bool bfd_key_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + if (bs1->key.family != bs2->key.family) + return false; + if (bs1->key.mhop != bs2->key.mhop) + return false; + if (memcmp(&bs1->key.peer, &bs2->key.peer, sizeof(bs1->key.peer))) + return false; + if (memcmp(bs1->key.vrfname, bs2->key.vrfname, + sizeof(bs1->key.vrfname))) + return false; + + /* + * Local address is optional and can be empty. + * If both addresses are not empty and different, + * then the keys are different. + */ + if (memcmp(&bs1->key.local, &zero_addr, sizeof(bs1->key.local)) + && memcmp(&bs2->key.local, &zero_addr, sizeof(bs2->key.local)) + && memcmp(&bs1->key.local, &bs2->key.local, sizeof(bs1->key.local))) + return false; + + /* + * Interface name is optional and can be empty. + * If both names are not empty and different, + * then the keys are different. + */ + if (bs1->key.ifname[0] && bs2->key.ifname[0] + && memcmp(bs1->key.ifname, bs2->key.ifname, + sizeof(bs1->key.ifname))) + return false; + + return true; +} + + +/* + * Hash public interface / exported functions. + */ + +/* Lookup functions. */ +struct bfd_session *bfd_id_lookup(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_lookup(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_key_lookup(struct bfd_key key) +{ + struct bfd_session bs; + + bs.key = key; + + return hash_lookup(bfd_key_hash, &bs); +} + +/* + * Delete functions. + * + * Delete functions searches and remove the item from the hash and + * returns a pointer to the removed item data. If the item was not found + * then it returns NULL. + * + * The data stored inside the hash is not free()ed, so you must do it + * manually after getting the pointer back. + */ +struct bfd_session *bfd_id_delete(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_release(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_key_delete(struct bfd_key key) +{ + struct bfd_session bs; + + bs.key = key; + + return hash_release(bfd_key_hash, &bs); +} + +/* Iteration functions. */ +void bfd_id_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_id_hash, hif, arg); +} + +void bfd_key_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_key_hash, hif, arg); +} + +/* + * Insert functions. + * + * Inserts session into hash and returns `true` on success, otherwise + * `false`. + */ +bool bfd_id_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_key_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_key_hash, bs, hash_alloc_intern) == bs); +} + +void bfd_initialize(void) +{ + bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, + "BFD session discriminator hash"); + bfd_key_hash = hash_create(bfd_key_hash_do, bfd_key_hash_cmp, + "BFD session hash"); + TAILQ_INIT(&bplist); +} + +static void _bfd_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct bfd_session *bs = hb->data; + + bfd_session_free(bs); +} + +void bfd_shutdown(void) +{ + struct bfd_profile *bp; + + /* + * Close and free all BFD sessions. + * + * _bfd_free() will call bfd_session_free() which will take care + * of removing the session from all hashes, so we just run an + * assert() here to make sure it really happened. + */ + bfd_id_iterate(_bfd_free, NULL); + assert(bfd_key_hash->count == 0); + + /* Now free the hashes themselves. */ + hash_free(bfd_id_hash); + hash_free(bfd_key_hash); + + /* Free all profile allocations. */ + while ((bp = TAILQ_FIRST(&bplist)) != NULL) + bfd_profile_free(bp); +} + +struct bfd_session_iterator { + int bsi_stop; + bool bsi_mhop; + const struct bfd_session *bsi_bs; +}; + +static int _bfd_session_next(struct hash_bucket *hb, void *arg) +{ + struct bfd_session_iterator *bsi = arg; + struct bfd_session *bs = hb->data; + + /* Previous entry signaled stop. */ + if (bsi->bsi_stop == 1) { + /* Match the single/multi hop sessions. */ + if (bs->key.mhop != bsi->bsi_mhop) + return HASHWALK_CONTINUE; + + bsi->bsi_bs = bs; + return HASHWALK_ABORT; + } + + /* We found the current item, stop in the next one. */ + if (bsi->bsi_bs == hb->data) { + bsi->bsi_stop = 1; + /* Set entry to NULL to signal end of list. */ + bsi->bsi_bs = NULL; + } else if (bsi->bsi_bs == NULL && bsi->bsi_mhop == bs->key.mhop) { + /* We want the first list item. */ + bsi->bsi_stop = 1; + bsi->bsi_bs = hb->data; + return HASHWALK_ABORT; + } + + return HASHWALK_CONTINUE; +} + +/* + * bfd_session_next: uses the current session to find the next. + * + * `bs` might point to NULL to get the first item of the data structure. + */ +const struct bfd_session *bfd_session_next(const struct bfd_session *bs, + bool mhop) +{ + struct bfd_session_iterator bsi; + + bsi.bsi_stop = 0; + bsi.bsi_bs = bs; + bsi.bsi_mhop = mhop; + hash_walk(bfd_key_hash, _bfd_session_next, &bsi); + if (bsi.bsi_stop == 0) + return NULL; + + return bsi.bsi_bs; +} + +static void _bfd_session_remove_manual(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct bfd_session *bs = hb->data; + + /* Delete only manually configured sessions. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0) + return; + + bs->refcount--; + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); + + /* Don't delete sessions still in use. */ + if (bs->refcount != 0) + return; + + bfd_session_free(bs); +} + +/* + * bfd_sessions_remove_manual: remove all manually configured sessions. + * + * NOTE: this function doesn't remove automatically created sessions. + */ +void bfd_sessions_remove_manual(void) +{ + hash_iterate(bfd_key_hash, _bfd_session_remove_manual, NULL); +} + +void bfd_profiles_remove(void) +{ + struct bfd_profile *bp; + + while ((bp = TAILQ_FIRST(&bplist)) != NULL) + bfd_profile_free(bp); +} + +/* + * Profile related hash functions. + */ +static void _bfd_profile_update(struct hash_bucket *hb, void *arg) +{ + struct bfd_profile *bp = arg; + struct bfd_session *bs = hb->data; + + /* This session is not using the profile. */ + if (bs->profile_name == NULL || strcmp(bs->profile_name, bp->name) != 0) + return; + + bfd_profile_apply(bp->name, bs); +} + +void bfd_profile_update(struct bfd_profile *bp) +{ + hash_iterate(bfd_key_hash, _bfd_profile_update, bp); +} + +static void _bfd_profile_detach(struct hash_bucket *hb, void *arg) +{ + struct bfd_profile *bp = arg; + struct bfd_session *bs = hb->data; + + /* This session is not using the profile. */ + if (bs->profile_name == NULL || strcmp(bs->profile_name, bp->name) != 0) + return; + + bfd_profile_remove(bs); +} + +static void bfd_profile_detach(struct bfd_profile *bp) +{ + hash_iterate(bfd_key_hash, _bfd_profile_detach, bp); +} + +/* + * VRF related functions. + */ +static int bfd_vrf_new(struct vrf *vrf) +{ + if (bglobal.debug_zebra) + zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); + + return 0; +} + +static int bfd_vrf_delete(struct vrf *vrf) +{ + if (bglobal.debug_zebra) + zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); + + return 0; +} + +static int bfd_vrf_enable(struct vrf *vrf) +{ + struct bfd_vrf_global *bvrf; + + /* a different name */ + if (!vrf->info) { + bvrf = XCALLOC(MTYPE_BFDD_VRF, sizeof(struct bfd_vrf_global)); + bvrf->vrf = vrf; + vrf->info = (void *)bvrf; + + /* Disable sockets if using data plane. */ + if (bglobal.bg_use_dplane) { + bvrf->bg_shop = -1; + bvrf->bg_mhop = -1; + bvrf->bg_shop6 = -1; + bvrf->bg_mhop6 = -1; + bvrf->bg_echo = -1; + bvrf->bg_echov6 = -1; + } + } else + bvrf = vrf->info; + + if (bglobal.debug_zebra) + zlog_debug("VRF enable add %s id %u", vrf->name, vrf->vrf_id); + + if (!bvrf->bg_shop) + bvrf->bg_shop = bp_udp_shop(vrf); + if (!bvrf->bg_mhop) + bvrf->bg_mhop = bp_udp_mhop(vrf); + if (!bvrf->bg_shop6) + bvrf->bg_shop6 = bp_udp6_shop(vrf); + if (!bvrf->bg_mhop6) + bvrf->bg_mhop6 = bp_udp6_mhop(vrf); + if (!bvrf->bg_echo) + bvrf->bg_echo = bp_echo_socket(vrf); + if (!bvrf->bg_echov6) + bvrf->bg_echov6 = bp_echov6_socket(vrf); + + if (!bvrf->bg_ev[0] && bvrf->bg_shop != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop, + &bvrf->bg_ev[0]); + if (!bvrf->bg_ev[1] && bvrf->bg_mhop != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop, + &bvrf->bg_ev[1]); + if (!bvrf->bg_ev[2] && bvrf->bg_shop6 != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6, + &bvrf->bg_ev[2]); + if (!bvrf->bg_ev[3] && bvrf->bg_mhop6 != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6, + &bvrf->bg_ev[3]); + if (!bvrf->bg_ev[4] && bvrf->bg_echo != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo, + &bvrf->bg_ev[4]); + if (!bvrf->bg_ev[5] && bvrf->bg_echov6 != -1) + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6, + &bvrf->bg_ev[5]); + + if (vrf->vrf_id != VRF_DEFAULT) { + bfdd_zclient_register(vrf->vrf_id); + bfdd_sessions_enable_vrf(vrf); + } + return 0; +} + +static int bfd_vrf_disable(struct vrf *vrf) +{ + struct bfd_vrf_global *bvrf; + + if (!vrf->info) + return 0; + bvrf = vrf->info; + + if (vrf->vrf_id != VRF_DEFAULT) { + bfdd_sessions_disable_vrf(vrf); + bfdd_zclient_unregister(vrf->vrf_id); + } + + if (bglobal.debug_zebra) + zlog_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id); + + /* Disable read/write poll triggering. */ + EVENT_OFF(bvrf->bg_ev[0]); + EVENT_OFF(bvrf->bg_ev[1]); + EVENT_OFF(bvrf->bg_ev[2]); + EVENT_OFF(bvrf->bg_ev[3]); + EVENT_OFF(bvrf->bg_ev[4]); + EVENT_OFF(bvrf->bg_ev[5]); + + /* Close all descriptors. */ + socket_close(&bvrf->bg_echo); + socket_close(&bvrf->bg_shop); + socket_close(&bvrf->bg_mhop); + if (bvrf->bg_shop6 != -1) + socket_close(&bvrf->bg_shop6); + if (bvrf->bg_mhop6 != -1) + socket_close(&bvrf->bg_mhop6); + socket_close(&bvrf->bg_echo); + if (bvrf->bg_echov6 != -1) + socket_close(&bvrf->bg_echov6); + + /* free context */ + XFREE(MTYPE_BFDD_VRF, bvrf); + vrf->info = NULL; + + return 0; +} + +void bfd_vrf_init(void) +{ + vrf_init(bfd_vrf_new, bfd_vrf_enable, bfd_vrf_disable, bfd_vrf_delete); +} + +void bfd_vrf_terminate(void) +{ + vrf_terminate(); +} + +struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd) +{ + struct vrf *vrf; + + if (!vrf_is_backend_netns()) { + vrf = vrf_lookup_by_id(VRF_DEFAULT); + if (vrf) + return (struct bfd_vrf_global *)vrf->info; + return NULL; + } + if (!bfd) + return NULL; + if (!bfd->vrf) + return NULL; + return bfd->vrf->info; +} + +unsigned long bfd_get_session_count(void) +{ + return bfd_key_hash->count; +} + +void bfd_rtt_init(struct bfd_session *bfd) +{ + uint8_t i; + + /* initialize RTT */ + bfd->rtt_valid = 0; + bfd->rtt_index = 0; + for (i = 0; i < BFD_RTT_SAMPLE; i++) + bfd->rtt[i] = 0; +} diff --git a/bfdd/bfd.h b/bfdd/bfd.h new file mode 100644 index 0000000..f4ff884 --- /dev/null +++ b/bfdd/bfd.h @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * bfd.h: implements the BFD protocol. + */ + +#ifndef _BFD_H_ +#define _BFD_H_ + +#include + +#include +#include +#include + +#include "lib/hash.h" +#include "lib/libfrr.h" +#include "lib/qobj.h" +#include "lib/queue.h" +#include "lib/vrf.h" + +#include "bfdctl.h" + +#ifdef BFD_DEBUG +#define BFDD_JSON_CONV_OPTIONS (JSON_C_TO_STRING_PRETTY) +#else +#define BFDD_JSON_CONV_OPTIONS (0) +#endif + +DECLARE_MGROUP(BFDD); +DECLARE_MTYPE(BFDD_CONTROL); +DECLARE_MTYPE(BFDD_NOTIFICATION); + +#define BFDD_SOCK_NAME "%s/bfdd.sock", frr_runstatedir + +/* bfd Authentication Type. */ +#define BFD_AUTH_NULL 0 +#define BFD_AUTH_SIMPLE 1 +#define BFD_AUTH_CRYPTOGRAPHIC 2 + +struct bfd_timers { + uint32_t desired_min_tx; + uint32_t required_min_rx; + uint32_t required_min_echo; +}; + +struct bfd_discrs { + uint32_t my_discr; + uint32_t remote_discr; +}; + +/* + * Format of control packet. From section 4) + */ +struct bfd_pkt { + union { + uint32_t byteFields; + struct { + uint8_t diag; + uint8_t flags; + uint8_t detect_mult; + uint8_t len; + }; + }; + struct bfd_discrs discrs; + struct bfd_timers timers; +}; + +/* + * Format of authentification. + */ +struct bfd_auth { + uint8_t type; + uint8_t length; +}; + + +/* + * Format of Echo packet. + */ +struct bfd_echo_pkt { + union { + uint32_t byteFields; + struct { + uint8_t ver; + uint8_t len; + uint16_t reserved; + }; + }; + uint32_t my_discr; + uint64_t time_sent_sec; + uint64_t time_sent_usec; +}; + + +/* Macros for manipulating control packets */ +#define BFD_VERMASK 0x07 +#define BFD_DIAGMASK 0x1F +#define BFD_GETVER(diag) ((diag >> 5) & BFD_VERMASK) +#define BFD_SETVER(diag, val) ((diag) |= (val & BFD_VERMASK) << 5) +#define BFD_VERSION 1 +#define BFD_PBIT 0x20 +#define BFD_FBIT 0x10 +#define BFD_CBIT 0x08 +#define BFD_ABIT 0x04 +#define BFD_DEMANDBIT 0x02 +#define BFD_MBIT 0x01 +#define BFD_GETMBIT(flags) (flags & BFD_MBIT) +#define BFD_SETDEMANDBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_DEMANDBIT; \ + } +#define BFD_SETPBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_PBIT; \ + } +#define BFD_GETPBIT(flags) (flags & BFD_PBIT) +#define BFD_SETFBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_FBIT; \ + } +#define BFD_GETFBIT(flags) (flags & BFD_FBIT) +#define BFD_SETSTATE(flags, val) \ + { \ + if ((val)) \ + flags |= (val & 0x3) << 6; \ + } +#define BFD_GETSTATE(flags) ((flags >> 6) & 0x3) +#define BFD_SETCBIT(flags, val) \ + { \ + if ((val)) \ + flags |= val; \ + } +#define BFD_GETCBIT(flags) (flags & BFD_CBIT) +#define BFD_ECHO_VERSION 1 +#define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt) + +enum bfd_diagnosticis { + BD_OK = 0, + /* Control Detection Time Expired. */ + BD_CONTROL_EXPIRED = 1, + /* Echo Function Failed. */ + BD_ECHO_FAILED = 2, + /* Neighbor Signaled Session Down. */ + BD_NEIGHBOR_DOWN = 3, + /* Forwarding Plane Reset. */ + BD_FORWARDING_RESET = 4, + /* Path Down. */ + BD_PATH_DOWN = 5, + /* Concatenated Path Down. */ + BD_CONCATPATH_DOWN = 6, + /* Administratively Down. */ + BD_ADMIN_DOWN = 7, + /* Reverse Concatenated Path Down. */ + BD_REVCONCATPATH_DOWN = 8, + /* 9..31: reserved. */ +}; + +/* BFD session flags */ +enum bfd_session_flags { + BFD_SESS_FLAG_NONE = 0, + BFD_SESS_FLAG_ECHO = 1 << 0, /* BFD Echo functionality */ + BFD_SESS_FLAG_ECHO_ACTIVE = 1 << 1, /* BFD Echo Packets are being sent + * actively + */ + BFD_SESS_FLAG_MH = 1 << 2, /* BFD Multi-hop session */ + BFD_SESS_FLAG_IPV6 = 1 << 4, /* BFD IPv6 session */ + BFD_SESS_FLAG_SEND_EVT_ACTIVE = 1 << 5, /* send event timer active */ + BFD_SESS_FLAG_SEND_EVT_IGNORE = 1 << 6, /* ignore send event when timer + * expires + */ + BFD_SESS_FLAG_SHUTDOWN = 1 << 7, /* disable BGP peer function */ + BFD_SESS_FLAG_CONFIG = 1 << 8, /* Session configured with bfd NB API */ + BFD_SESS_FLAG_CBIT = 1 << 9, /* CBIT is set */ + BFD_SESS_FLAG_PASSIVE = 1 << 10, /* Passive mode */ + BFD_SESS_FLAG_MAC_SET = 1 << 11, /* MAC of peer known */ +}; + +/* + * BFD session hash key. + * + * This structure must not have any padding bytes because their value is + * unspecified after the struct assignment. Even when all fields of two keys + * are the same, if the padding bytes are different, then the calculated hash + * value is different, and the hash lookup will fail. + * + * Currently, the structure fields are correctly aligned, and the "packed" + * attribute is added as a precaution. "family" and "mhop" fields are two-bytes + * to eliminate unaligned memory access to "peer" and "local". + */ +struct bfd_key { + uint16_t family; + uint16_t mhop; + struct in6_addr peer; + struct in6_addr local; + char ifname[IFNAMSIZ]; + char vrfname[VRF_NAMSIZ]; +} __attribute__((packed)); + +struct bfd_session_stats { + uint64_t rx_ctrl_pkt; + uint64_t tx_ctrl_pkt; + uint64_t rx_echo_pkt; + uint64_t tx_echo_pkt; + uint64_t session_up; + uint64_t session_down; + uint64_t znotification; +}; + +/** + * BFD session profile to override default configurations. + */ +struct bfd_profile { + /** Profile name. */ + char name[64]; + + /** Session detection multiplier. */ + uint8_t detection_multiplier; + /** Desired transmission interval (in microseconds). */ + uint32_t min_tx; + /** Minimum required receive interval (in microseconds). */ + uint32_t min_rx; + /** Administrative state. */ + bool admin_shutdown; + /** Passive mode. */ + bool passive; + /** Minimum expected TTL value. */ + uint8_t minimum_ttl; + + /** Echo mode (only applies to single hop). */ + bool echo_mode; + /** Desired echo transmission interval (in microseconds). */ + uint32_t min_echo_tx; + /** Minimum required echo receive interval (in microseconds). */ + uint32_t min_echo_rx; + + /** Profile list entry. */ + TAILQ_ENTRY(bfd_profile) entry; +}; + +/** Profile list type. */ +TAILQ_HEAD(bfdproflist, bfd_profile); + +/* bfd_session shortcut label forwarding. */ +struct peer_label; + +struct bfd_config_timers { + uint32_t desired_min_tx; + uint32_t required_min_rx; + uint32_t desired_min_echo_tx; + uint32_t required_min_echo_rx; +}; + +#define BFD_RTT_SAMPLE 8 + +/* + * Session state information + */ +struct bfd_session { + + /* protocol state per RFC 5880*/ + uint8_t ses_state; + struct bfd_discrs discrs; + uint8_t local_diag; + uint8_t demand_mode; + uint8_t detect_mult; + uint8_t remote_detect_mult; + uint8_t mh_ttl; + uint8_t remote_cbit; + + /** BFD profile name. */ + char *profile_name; + /** BFD pre configured profile. */ + struct bfd_profile *profile; + /** BFD peer configuration (without profile). */ + struct bfd_profile peer_profile; + + /* Timers */ + struct bfd_config_timers timers; + struct bfd_timers cur_timers; + uint64_t detect_TO; + struct event *echo_recvtimer_ev; + struct event *recvtimer_ev; + uint64_t xmt_TO; + uint64_t echo_xmt_TO; + struct event *xmttimer_ev; + struct event *echo_xmttimer_ev; + uint64_t echo_detect_TO; + + /* software object state */ + uint8_t polling; + + /* This and the localDiscr are the keys to state info */ + struct bfd_key key; + struct peer_label *pl; + + struct bfd_dplane_ctx *bdc; + struct sockaddr_any local_address; + uint8_t peer_hw_addr[ETH_ALEN]; + struct interface *ifp; + struct vrf *vrf; + + int sock; + + /* BFD session flags */ + enum bfd_session_flags flags; + + struct bfd_session_stats stats; + + struct timeval uptime; /* last up time */ + struct timeval downtime; /* last down time */ + + /* Remote peer data (for debugging mostly) */ + uint8_t remote_diag; + struct bfd_timers remote_timers; + + uint64_t refcount; /* number of pointers referencing this. */ + + uint8_t rtt_valid; /* number of valid samples */ + uint8_t rtt_index; /* last index added */ + uint64_t rtt[BFD_RTT_SAMPLE]; /* RRT in usec for echo to be looped */ +}; + +struct peer_label { + TAILQ_ENTRY(peer_label) pl_entry; + + struct bfd_session *pl_bs; + char pl_label[MAXNAMELEN]; +}; +TAILQ_HEAD(pllist, peer_label); + +struct bfd_diag_str_list { + const char *str; + int type; +}; + +struct bfd_state_str_list { + const char *str; + int type; +}; + +struct bfd_session_observer { + struct bfd_session *bso_bs; + char bso_entryname[MAXNAMELEN]; + struct prefix bso_addr; + + TAILQ_ENTRY(bfd_session_observer) bso_entry; +}; +TAILQ_HEAD(obslist, bfd_session_observer); + + +/* States defined per 4.1 */ +#define PTM_BFD_ADM_DOWN 0 +#define PTM_BFD_DOWN 1 +#define PTM_BFD_INIT 2 +#define PTM_BFD_UP 3 + + +/* Various constants */ +/* Retrieved from ptm_timer.h from Cumulus PTM sources. */ +#define BFD_DEF_DEMAND 0 +#define BFD_DEFDETECTMULT 3 +#define BFD_DEFDESIREDMINTX (300 * 1000) /* microseconds. */ +#define BFD_DEFREQUIREDMINRX (300 * 1000) /* microseconds. */ +#define BFD_DEF_DES_MIN_ECHO_TX (50 * 1000) /* microseconds. */ +#define BFD_DEF_REQ_MIN_ECHO_RX (50 * 1000) /* microseconds. */ +#define BFD_DEF_SLOWTX (1000 * 1000) /* microseconds. */ +/** Minimum multi hop TTL. */ +#define BFD_DEF_MHOP_TTL 254 +#define BFD_PKT_LEN 24 /* Length of control packet */ +#define BFD_TTL_VAL 255 +#define BFD_RCV_TTL_VAL 1 +#define BFD_TOS_VAL 0xC0 +#define BFD_PKT_INFO_VAL 1 +#define BFD_IPV6_PKT_INFO_VAL 1 +#define BFD_IPV6_ONLY_VAL 1 +#define BFD_SRCPORTINIT 49152 +#define BFD_SRCPORTMAX 65535 +#define BFD_DEFDESTPORT 3784 +#define BFD_DEF_ECHO_PORT 3785 +#define BFD_DEF_MHOP_DEST_PORT 4784 + +/* + * control.c + * + * Daemon control code to speak with local consumers. + */ + +/* See 'bfdctrl.h' for client protocol definitions. */ + +struct bfd_control_buffer { + size_t bcb_left; + size_t bcb_pos; + union { + struct bfd_control_msg *bcb_bcm; + uint8_t *bcb_buf; + }; +}; + +struct bfd_control_queue { + TAILQ_ENTRY(bfd_control_queue) bcq_entry; + + struct bfd_control_buffer bcq_bcb; +}; +TAILQ_HEAD(bcqueue, bfd_control_queue); + +struct bfd_notify_peer { + TAILQ_ENTRY(bfd_notify_peer) bnp_entry; + + struct bfd_session *bnp_bs; +}; +TAILQ_HEAD(bnplist, bfd_notify_peer); + +struct bfd_control_socket { + TAILQ_ENTRY(bfd_control_socket) bcs_entry; + + int bcs_sd; + struct event *bcs_ev; + struct event *bcs_outev; + struct bcqueue bcs_bcqueue; + + /* Notification data */ + uint64_t bcs_notify; + struct bnplist bcs_bnplist; + + enum bc_msg_version bcs_version; + enum bc_msg_type bcs_type; + + /* Message buffering */ + struct bfd_control_buffer bcs_bin; + struct bfd_control_buffer *bcs_bout; +}; +TAILQ_HEAD(bcslist, bfd_control_socket); + +int control_init(const char *path); +void control_shutdown(void); +int control_notify(struct bfd_session *bs, uint8_t notify_state); +int control_notify_config(const char *op, struct bfd_session *bs); +void control_accept(struct event *t); + + +/* + * bfdd.c + * + * Daemon specific code. + */ +struct bfd_vrf_global { + int bg_shop; + int bg_mhop; + int bg_shop6; + int bg_mhop6; + int bg_echo; + int bg_echov6; + struct vrf *vrf; + + struct event *bg_ev[6]; +}; + +/* Forward declaration of data plane context struct. */ +struct bfd_dplane_ctx; +TAILQ_HEAD(dplane_queue, bfd_dplane_ctx); + +struct bfd_global { + int bg_csock; + struct event *bg_csockev; + struct bcslist bg_bcslist; + + struct pllist bg_pllist; + + struct obslist bg_obslist; + + struct zebra_privs_t bfdd_privs; + + /** + * Daemon is exit()ing? Use this to avoid actions that expect a + * running system or to avoid unnecessary operations when quitting. + */ + bool bg_shutdown; + + /* Distributed BFD items. */ + bool bg_use_dplane; + int bg_dplane_sock; + struct event *bg_dplane_sockev; + struct dplane_queue bg_dplaneq; + + /* Debug options. */ + /* Show distributed BFD debug messages. */ + bool debug_dplane; + /* Show all peer state changes events. */ + bool debug_peer_event; + /* + * Show zebra message exchanges: + * - Interface add/delete. + * - Local address add/delete. + * - VRF add/delete. + */ + bool debug_zebra; + /* + * Show network level debug information: + * - Echo packets without session. + * - Unavailable peer sessions. + * - Network system call failures. + */ + bool debug_network; +}; + +extern struct bfd_global bglobal; +extern const struct bfd_diag_str_list diag_list[]; +extern const struct bfd_state_str_list state_list[]; + +void socket_close(int *s); + + +/* + * config.c + * + * Contains the code related with loading/reloading configuration. + */ +int parse_config(const char *fname); +int config_request_add(const char *jsonstr); +int config_request_del(const char *jsonstr); +char *config_response(const char *status, const char *error); +char *config_notify(struct bfd_session *bs); +char *config_notify_config(const char *op, struct bfd_session *bs); + +typedef int (*bpc_handle)(struct bfd_peer_cfg *, void *arg); +int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, + bpc_handle bh); + +struct peer_label *pl_new(const char *label, struct bfd_session *bs); +struct peer_label *pl_find(const char *label); +void pl_free(struct peer_label *pl); + + +/* + * logging - alias to zebra log + */ +#define zlog_fatal(msg, ...) \ + do { \ + zlog_err(msg, ##__VA_ARGS__); \ + assert(!msg); \ + abort(); \ + } while (0) + + +/* + * bfd_packet.c + * + * Contains the code related with receiving/seding, packing/unpacking BFD data. + */ +int bp_set_ttlv6(int sd, uint8_t value); +int bp_set_ttl(int sd, uint8_t value); +int bp_set_tosv6(int sd, uint8_t value); +int bp_set_tos(int sd, uint8_t value); +int bp_bind_dev(int sd, const char *dev); + +int bp_udp_shop(const struct vrf *vrf); +int bp_udp_mhop(const struct vrf *vrf); +int bp_udp6_shop(const struct vrf *vrf); +int bp_udp6_mhop(const struct vrf *vrf); +int bp_peer_socket(const struct bfd_session *bs); +int bp_peer_socketv6(const struct bfd_session *bs); +int bp_echo_socket(const struct vrf *vrf); +int bp_echov6_socket(const struct vrf *vrf); + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit); +void ptm_bfd_echo_snd(struct bfd_session *bfd); +void ptm_bfd_echo_fp_snd(struct bfd_session *bfd); + +void bfd_recv_cb(struct event *t); + + +/* + * event.c + * + * Contains the code related with event loop. + */ +typedef void (*bfd_ev_cb)(struct event *t); + +void bfd_recvtimer_update(struct bfd_session *bs); +void bfd_echo_recvtimer_update(struct bfd_session *bs); +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter); +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter); + +void bfd_xmttimer_delete(struct bfd_session *bs); +void bfd_echo_xmttimer_delete(struct bfd_session *bs); +void bfd_recvtimer_delete(struct bfd_session *bs); +void bfd_echo_recvtimer_delete(struct bfd_session *bs); + +void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); +void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); + + +/* + * bfd.c + * + * BFD protocol specific code. + */ +int bfd_session_enable(struct bfd_session *bs); +void bfd_session_disable(struct bfd_session *bs); +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc); +int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc); +void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag); +void ptm_bfd_sess_up(struct bfd_session *bfd); +void ptm_bfd_echo_stop(struct bfd_session *bfd); +void ptm_bfd_echo_start(struct bfd_session *bfd); +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit); +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo); +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, + struct sockaddr_any *peer, + struct sockaddr_any *local, + struct interface *ifp, + vrf_id_t vrfid, + bool is_mhop); + +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc); +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel); +void bfd_set_polling(struct bfd_session *bs); +void bs_state_handler(struct bfd_session *bs, int nstate); +void bs_echo_timer_handler(struct bfd_session *bs); +void bs_final_handler(struct bfd_session *bs); +void bs_set_slow_timers(struct bfd_session *bs); +const char *satostr(const struct sockaddr_any *sa); +const char *diag2str(uint8_t diag); +int strtosa(const char *addr, struct sockaddr_any *sa); +void integer2timestr(uint64_t time, char *buf, size_t buflen); +const char *bs_to_string(const struct bfd_session *bs); + +int bs_observer_add(struct bfd_session *bs); +void bs_observer_del(struct bfd_session_observer *bso); + +void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc); + +void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer, + struct sockaddr_any *local, bool mhop, const char *ifname, + const char *vrfname); +struct bfd_session *bfd_session_new(void); +struct bfd_session *bs_registrate(struct bfd_session *bs); +void bfd_session_free(struct bfd_session *bs); +const struct bfd_session *bfd_session_next(const struct bfd_session *bs, + bool mhop); +void bfd_sessions_remove_manual(void); +void bfd_profiles_remove(void); +void bfd_rtt_init(struct bfd_session *bfd); + +/** + * Set the BFD session echo state. + * + * \param bs the BFD session. + * \param echo the echo operational state. + */ +void bfd_set_echo(struct bfd_session *bs, bool echo); + +/** + * Set the BFD session functional state. + * + * \param bs the BFD session. + * \param shutdown the operational value. + */ +void bfd_set_shutdown(struct bfd_session *bs, bool shutdown); + +/** + * Set the BFD session passive mode. + * + * \param bs the BFD session. + * \param passive the passive mode. + */ +void bfd_set_passive_mode(struct bfd_session *bs, bool passive); + +/** + * Picks the BFD session configuration from the appropriated source: + * if using the default peer configuration prefer profile (if it exists), + * otherwise use session. + * + * \param bs the BFD session. + */ +void bfd_session_apply(struct bfd_session *bs); + +/* BFD hash data structures interface */ +void bfd_initialize(void); +void bfd_shutdown(void); +void bfd_vrf_init(void); +void bfd_vrf_terminate(void); +struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd); +struct bfd_session *bfd_id_lookup(uint32_t id); +struct bfd_session *bfd_key_lookup(struct bfd_key key); + +struct bfd_session *bfd_id_delete(uint32_t id); +struct bfd_session *bfd_key_delete(struct bfd_key key); + +bool bfd_id_insert(struct bfd_session *bs); +bool bfd_key_insert(struct bfd_session *bs); + +typedef void (*hash_iter_func)(struct hash_bucket *hb, void *arg); +void bfd_id_iterate(hash_iter_func hif, void *arg); +void bfd_key_iterate(hash_iter_func hif, void *arg); + +unsigned long bfd_get_session_count(void); + +/* Export callback functions for `event.c`. */ +extern struct event_loop *master; + +void bfd_recvtimer_cb(struct event *t); +void bfd_echo_recvtimer_cb(struct event *t); +void bfd_xmt_cb(struct event *t); +void bfd_echo_xmt_cb(struct event *t); + +extern struct in6_addr zero_addr; + +/** + * Creates a new profile entry and insert into the global list. + * + * \param name the BFD profile name. + * + * \returns `NULL` if it already exists otherwise the new entry. + */ +struct bfd_profile *bfd_profile_new(const char *name); + +/** + * Search for configured BFD profiles (profile name is case insensitive). + * + * \param name the BFD profile name. + * + * \returns `NULL` if it doesn't exist otherwise the entry. + */ +struct bfd_profile *bfd_profile_lookup(const char *name); + +/** + * Removes profile from list and free memory. + * + * \param bp the BFD profile. + */ +void bfd_profile_free(struct bfd_profile *bp); + +/** + * Apply a profile configuration to an existing BFD session. The non default + * values will not be overridden. + * + * NOTE: if the profile doesn't exist yet, then the profile will be applied + * once it begins to exist. + * + * \param profile_name the BFD profile name. + * \param bs the BFD session. + */ +void bfd_profile_apply(const char *profname, struct bfd_session *bs); + +/** + * Remove any applied profile from session and revert the session + * configuration. + * + * \param bs the BFD session. + */ +void bfd_profile_remove(struct bfd_session *bs); + +/** + * Apply new profile values to sessions using it. + * + * \param[in] bp the BFD profile that got updated. + */ +void bfd_profile_update(struct bfd_profile *bp); + +/* + * bfdd_vty.c + * + * BFD daemon vty shell commands. + */ +void bfdd_vty_init(void); + + +/* + * bfdd_cli.c + * + * BFD daemon CLI implementation. + */ +void bfdd_cli_init(void); + + +/* + * ptm_adapter.c + */ +void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv); +void bfdd_zclient_stop(void); +void bfdd_zclient_terminate(void); +void bfdd_zclient_unregister(vrf_id_t vrf_id); +void bfdd_zclient_register(vrf_id_t vrf_id); +void bfdd_sessions_enable_vrf(struct vrf *vrf); +void bfdd_sessions_disable_vrf(struct vrf *vrf); + +int ptm_bfd_notify(struct bfd_session *bs, uint8_t notify_state); + +/* + * dplane.c + */ + +/** + * Initialize BFD data plane infrastructure for distributed BFD implementation. + * + * \param sa socket address. + * \param salen socket address structure length. + * \param client `true` means connecting socket, `false` listening socket. + */ +void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client); + +/** + * Attempts to delegate the BFD session liveness detection to hardware. + * + * \param bs the BFD session data structure. + * + * \returns + * `0` on success and BFD daemon should do nothing or `-1` on failure + * and we should fallback to software implementation. + */ +int bfd_dplane_add_session(struct bfd_session *bs); + +/** + * Send new session settings to data plane. + * + * \param bs the BFD session to update. + */ +int bfd_dplane_update_session(const struct bfd_session *bs); + +/** + * Deletes session from data plane. + * + * \param bs the BFD session to delete. + * + * \returns `0` on success otherwise `-1`. + */ +int bfd_dplane_delete_session(struct bfd_session *bs); + +/** + * Asks the data plane for updated counters and update the session data + * structure. + * + * \param bs the BFD session that needs updating. + * + * \returns `0` on success otherwise `-1` on failure. + */ +int bfd_dplane_update_session_counters(struct bfd_session *bs); + +void bfd_dplane_show_counters(struct vty *vty); + +#endif /* _BFD_H_ */ diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c new file mode 100644 index 0000000..8110f43 --- /dev/null +++ b/bfdd/bfd_packet.c @@ -0,0 +1,1770 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2017 Cumulus Networks, Inc. All rights reserved. + * + * bfd_packet.c: implements the BFD protocol packet handling. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include +#include + +#ifdef GNU_LINUX +#include +#endif + +#ifdef BFD_LINUX +#include +#endif /* BFD_LINUX */ + +#include +#include + +#include "lib/sockopt.h" +#include "lib/checksum.h" +#include "lib/network.h" + +#include "bfd.h" + +/* + * Prototypes + */ +static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s); +int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, + size_t datalen); + +static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd); +ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, + ifindex_t *ifindex, struct sockaddr_any *local, + struct sockaddr_any *peer); +ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, + ifindex_t *ifindex, struct sockaddr_any *local, + struct sockaddr_any *peer); +int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen, + struct sockaddr *to, socklen_t tolen); +int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl, + uint32_t *my_discr, uint64_t *my_rtt); +#ifdef BFD_LINUX +ssize_t bfd_recv_ipv4_fp(int sd, uint8_t *msgbuf, size_t msgbuflen, + uint8_t *ttl, ifindex_t *ifindex, + struct sockaddr_any *local, struct sockaddr_any *peer); +void bfd_peer_mac_set(int sd, struct bfd_session *bfd, + struct sockaddr_any *peer, struct interface *ifp); +int bp_udp_send_fp(int sd, uint8_t *data, size_t datalen, + struct bfd_session *bfd); +ssize_t bfd_recv_fp_echo(int sd, uint8_t *msgbuf, size_t msgbuflen, + uint8_t *ttl, ifindex_t *ifindex, + struct sockaddr_any *local, struct sockaddr_any *peer); +#endif + +/* socket related prototypes */ +static void bp_set_ipopts(int sd); +static void bp_bind_ip(int sd, uint16_t port); +static void bp_set_ipv6opts(int sd); +static void bp_bind_ipv6(int sd, uint16_t port); + + +/* + * Functions + */ +int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data, + size_t datalen) +{ + struct sockaddr *sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + socklen_t slen; + ssize_t rv; + int sd = -1; + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &bs->key.peer, sizeof(sin6.sin6_addr)); + if (bs->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bs->ifp->ifindex; + + sin6.sin6_port = + (port) ? *port + : (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin6; + slen = sizeof(sin6); + } else { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr, &bs->key.peer, sizeof(sin.sin_addr)); + sin.sin_port = + (port) ? *port + : (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin; + slen = sizeof(sin); + } + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_len = slen; +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + rv = sendto(sd, data, datalen, 0, sa, slen); + if (rv <= 0) { + if (bglobal.debug_network) + zlog_debug("packet-send: send failure: %s", + strerror(errno)); + return -1; + } + if (rv < (ssize_t)datalen) { + if (bglobal.debug_network) + zlog_debug("packet-send: send partial: %s", + strerror(errno)); + } + + return 0; +} + +#ifdef BFD_LINUX +/* + * Compute the UDP checksum. + * + * Checksum is not set in the packet, just computed. + * + * pkt + * Packet, fully filled out except for checksum field. + * + * pktsize + * sizeof(*pkt) + * + * ip + * IP address that pkt will be transmitted from and to. + * + * Returns: + * Checksum in network byte order. + */ +static uint16_t bfd_pkt_checksum(struct udphdr *pkt, size_t pktsize, + struct in6_addr *ip, sa_family_t family) +{ + uint16_t chksum; + + pkt->check = 0; + + if (family == AF_INET6) { + struct ipv6_ph ph = {}; + + memcpy(&ph.src, ip, sizeof(ph.src)); + memcpy(&ph.dst, ip, sizeof(ph.dst)); + ph.ulpl = htons(pktsize); + ph.next_hdr = IPPROTO_UDP; + chksum = in_cksum_with_ph6(&ph, pkt, pktsize); + } else { + struct ipv4_ph ph = {}; + + memcpy(&ph.src, ip, sizeof(ph.src)); + memcpy(&ph.dst, ip, sizeof(ph.dst)); + ph.proto = IPPROTO_UDP; + ph.len = htons(pktsize); + chksum = in_cksum_with_ph4(&ph, pkt, pktsize); + } + + return chksum; +} + +/* + * This routine creates the entire ECHO packet so that it will be looped + * in the forwarding plane of the peer router instead of going up the + * stack in BFD to be looped. If we haven't learned the peers MAC yet + * no echo is sent. + * + * echo packet with src/dst IP equal to local IP + * dest MAC as peer's MAC + * + * currently support ipv4 + */ +void ptm_bfd_echo_fp_snd(struct bfd_session *bfd) +{ + int sd; + struct bfd_vrf_global *bvrf = bfd_vrf_look_by_session(bfd); + int total_len = 0; + struct ethhdr *eth; + struct udphdr *uh; + struct iphdr *iph; + struct bfd_echo_pkt *beph; + static char sendbuff[100]; + struct timeval time_sent; + + if (!bvrf) + return; + if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET)) + return; + if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + + memset(sendbuff, 0, sizeof(sendbuff)); + + /* add eth hdr */ + eth = (struct ethhdr *)(sendbuff); + memcpy(eth->h_source, bfd->ifp->hw_addr, sizeof(eth->h_source)); + memcpy(eth->h_dest, bfd->peer_hw_addr, sizeof(eth->h_dest)); + + total_len += sizeof(struct ethhdr); + + sd = bvrf->bg_echo; + eth->h_proto = htons(ETH_P_IP); + + /* add ip hdr */ + iph = (struct iphdr *)(sendbuff + sizeof(struct ethhdr)); + + iph->ihl = sizeof(struct ip) >> 2; + iph->version = IPVERSION; + iph->tos = IPTOS_PREC_INTERNETCONTROL; + iph->id = (uint16_t)frr_weak_random(); + iph->ttl = BFD_TTL_VAL; + iph->protocol = IPPROTO_UDP; + memcpy(&iph->saddr, &bfd->local_address.sa_sin.sin_addr, + sizeof(bfd->local_address.sa_sin.sin_addr)); + memcpy(&iph->daddr, &bfd->local_address.sa_sin.sin_addr, + sizeof(bfd->local_address.sa_sin.sin_addr)); + total_len += sizeof(struct iphdr); + + /* add udp hdr */ + uh = (struct udphdr *)(sendbuff + sizeof(struct iphdr) + + sizeof(struct ethhdr)); + uh->source = htons(BFD_DEF_ECHO_PORT); + uh->dest = htons(BFD_DEF_ECHO_PORT); + + total_len += sizeof(struct udphdr); + + /* add bfd echo */ + beph = (struct bfd_echo_pkt *)(sendbuff + sizeof(struct udphdr) + + sizeof(struct iphdr) + + sizeof(struct ethhdr)); + + beph->ver = BFD_ECHO_VERSION; + beph->len = BFD_ECHO_PKT_LEN; + beph->my_discr = htonl(bfd->discrs.my_discr); + + /* RTT calculation: add starting time in packet */ + monotime(&time_sent); + beph->time_sent_sec = htobe64(time_sent.tv_sec); + beph->time_sent_usec = htobe64(time_sent.tv_usec); + + total_len += sizeof(struct bfd_echo_pkt); + uh->len = + htons(total_len - sizeof(struct iphdr) - sizeof(struct ethhdr)); + uh->check = bfd_pkt_checksum( + uh, (total_len - sizeof(struct iphdr) - sizeof(struct ethhdr)), + (struct in6_addr *)&iph->saddr, AF_INET); + + iph->tot_len = htons(total_len - sizeof(struct ethhdr)); + iph->check = in_cksum((const void *)iph, sizeof(struct iphdr)); + + if (bp_udp_send_fp(sd, (uint8_t *)&sendbuff, total_len, bfd) == -1) + return; + + bfd->stats.tx_echo_pkt++; +} +#endif + +void ptm_bfd_echo_snd(struct bfd_session *bfd) +{ + struct sockaddr *sa; + socklen_t salen; + int sd; + struct bfd_echo_pkt bep; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct bfd_vrf_global *bvrf = bfd_vrf_look_by_session(bfd); + + if (!bvrf) + return; + if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + + memset(&bep, 0, sizeof(bep)); + bep.ver = BFD_ECHO_VERSION; + bep.len = BFD_ECHO_PKT_LEN; + bep.my_discr = htonl(bfd->discrs.my_discr); + + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6)) { + if (bvrf->bg_echov6 == -1) + return; + sd = bvrf->bg_echov6; + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + memcpy(&sin6.sin6_addr, &bfd->key.peer, sizeof(sin6.sin6_addr)); + if (bfd->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bfd->ifp->ifindex; + + sin6.sin6_port = htons(BFD_DEF_ECHO_PORT); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + + sa = (struct sockaddr *)&sin6; + salen = sizeof(sin6); + } else { + sd = bvrf->bg_echo; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + memcpy(&sin.sin_addr, &bfd->key.peer, sizeof(sin.sin_addr)); + sin.sin_port = htons(BFD_DEF_ECHO_PORT); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + + sa = (struct sockaddr *)&sin; + salen = sizeof(sin); + } + if (bp_udp_send(sd, BFD_TTL_VAL, (uint8_t *)&bep, sizeof(bep), sa, + salen) + == -1) + return; + + bfd->stats.tx_echo_pkt++; +} + +static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s) +{ + struct bfd_session *bfd; + uint32_t my_discr = 0; + uint64_t my_rtt = 0; + uint8_t ttl = 0; + + /* Receive and parse echo packet. */ + if (bp_bfd_echo_in(bvrf, s, &ttl, &my_discr, &my_rtt) == -1) + return 0; + + /* Your discriminator not zero - use it to find session */ + bfd = bfd_id_lookup(my_discr); + if (bfd == NULL) { + if (bglobal.debug_network) + zlog_debug("echo-packet: no matching session (id:%u)", + my_discr); + return -1; + } + + if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + if (bglobal.debug_network) + zlog_debug("echo-packet: echo disabled [%s] (id:%u)", + bs_to_string(bfd), my_discr); + return -1; + } + + /* RTT Calculation: add current RTT to samples */ + if (my_rtt != 0) { + bfd->rtt[bfd->rtt_index] = my_rtt; + bfd->rtt_index++; + if (bfd->rtt_index >= BFD_RTT_SAMPLE) + bfd->rtt_index = 0; + if (bfd->rtt_valid < BFD_RTT_SAMPLE) + bfd->rtt_valid++; + } + + bfd->stats.rx_echo_pkt++; + + /* Compute detect time */ + bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO; + + /* Update echo receive timeout. */ + if (bfd->echo_detect_TO > 0) + bfd_echo_recvtimer_update(bfd); + + return 0; +} + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit) +{ + struct bfd_pkt cp = {}; + + /* Set fields according to section 6.5.7 */ + cp.diag = bfd->local_diag; + BFD_SETVER(cp.diag, BFD_VERSION); + cp.flags = 0; + BFD_SETSTATE(cp.flags, bfd->ses_state); + + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_CBIT)) + BFD_SETCBIT(cp.flags, BFD_CBIT); + + BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND); + + /* + * Polling and Final can't be set at the same time. + * + * RFC 5880, Section 6.5. + */ + BFD_SETFBIT(cp.flags, fbit); + if (fbit == 0) + BFD_SETPBIT(cp.flags, bfd->polling); + + cp.detect_mult = bfd->detect_mult; + cp.len = BFD_PKT_LEN; + cp.discrs.my_discr = htonl(bfd->discrs.my_discr); + cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr); + if (bfd->polling) { + cp.timers.desired_min_tx = + htonl(bfd->timers.desired_min_tx); + cp.timers.required_min_rx = + htonl(bfd->timers.required_min_rx); + } else { + /* + * We can only announce current setting on poll, this + * avoids timing mismatch with our peer and give it + * the oportunity to learn. See `bs_final_handler` for + * more information. + */ + cp.timers.desired_min_tx = + htonl(bfd->cur_timers.desired_min_tx); + cp.timers.required_min_rx = + htonl(bfd->cur_timers.required_min_rx); + } + cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo_rx); + + if (_ptm_bfd_send(bfd, NULL, &cp, BFD_PKT_LEN) != 0) + return; + + bfd->stats.tx_ctrl_pkt++; +} + +#ifdef BFD_LINUX +/* + * receive the ipv4 echo packet that was loopback in the peers forwarding plane + */ +ssize_t bfd_recv_ipv4_fp(int sd, uint8_t *msgbuf, size_t msgbuflen, + uint8_t *ttl, ifindex_t *ifindex, + struct sockaddr_any *local, struct sockaddr_any *peer) +{ + ssize_t mlen; + struct sockaddr_ll msgaddr; + struct msghdr msghdr; + struct iovec iov[1]; + uint16_t recv_checksum; + uint16_t checksum; + struct iphdr *ip; + struct udphdr *uh; + + /* Prepare the recvmsg params. */ + iov[0].iov_base = msgbuf; + iov[0].iov_len = msgbuflen; + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &msgaddr; + msghdr.msg_namelen = sizeof(msgaddr); + msghdr.msg_iov = iov; + msghdr.msg_iovlen = 1; + + mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN || errno != EWOULDBLOCK || errno != EINTR) + zlog_err("%s: recv failed: %s", __func__, + strerror(errno)); + + return -1; + } + + ip = (struct iphdr *)(msgbuf + sizeof(struct ethhdr)); + + /* verify ip checksum */ + recv_checksum = ip->check; + ip->check = 0; + checksum = in_cksum((const void *)ip, sizeof(struct iphdr)); + if (recv_checksum != checksum) { + if (bglobal.debug_network) + zlog_debug( + "%s: invalid iphdr checksum expected 0x%x rcvd 0x%x", + __func__, checksum, recv_checksum); + return -1; + } + + *ttl = ip->ttl; + if (*ttl != 254) { + if (bglobal.debug_network) + zlog_debug("%s: invalid TTL: %u", __func__, *ttl); + return -1; + } + + local->sa_sin.sin_family = AF_INET; + memcpy(&local->sa_sin.sin_addr, &ip->saddr, sizeof(ip->saddr)); + peer->sa_sin.sin_family = AF_INET; + memcpy(&peer->sa_sin.sin_addr, &ip->daddr, sizeof(ip->daddr)); + + *ifindex = msgaddr.sll_ifindex; + + /* verify udp checksum */ + uh = (struct udphdr *)(msgbuf + sizeof(struct iphdr) + + sizeof(struct ethhdr)); + recv_checksum = uh->check; + uh->check = 0; + checksum = bfd_pkt_checksum(uh, ntohs(uh->len), + (struct in6_addr *)&ip->saddr, AF_INET); + if (recv_checksum != checksum) { + if (bglobal.debug_network) + zlog_debug( + "%s: invalid udphdr checksum expected 0x%x rcvd 0x%x", + __func__, checksum, recv_checksum); + return -1; + } + return mlen; +} +#endif + +ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, + ifindex_t *ifindex, struct sockaddr_any *local, + struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + ssize_t mlen; + struct sockaddr_in msgaddr; + struct msghdr msghdr; + struct iovec iov[1]; + uint8_t cmsgbuf[255]; + + /* Prepare the recvmsg params. */ + iov[0].iov_base = msgbuf; + iov[0].iov_len = msgbuflen; + + memset(&msghdr, 0, sizeof(msghdr)); + msghdr.msg_name = &msgaddr; + msghdr.msg_namelen = sizeof(msgaddr); + msghdr.msg_iov = iov; + msghdr.msg_iovlen = 1; + msghdr.msg_control = cmsgbuf; + msghdr.msg_controllen = sizeof(cmsgbuf); + + mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) + zlog_err("ipv4-recv: recv failed: %s", strerror(errno)); + + return -1; + } + + /* Get source address */ + peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL; + cm = CMSG_NXTHDR(&msghdr, cm)) { + if (cm->cmsg_level != IPPROTO_IP) + continue; + + switch (cm->cmsg_type) { +#ifdef BFD_LINUX + case IP_TTL: { + uint32_t ttlval; + + memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval)); + if (ttlval > 255) { + if (bglobal.debug_network) + zlog_debug("%s: invalid TTL: %u", + __func__, ttlval); + return -1; + } + *ttl = ttlval; + break; + } + + case IP_PKTINFO: { + struct in_pktinfo *pi = + (struct in_pktinfo *)CMSG_DATA(cm); + + if (pi == NULL) + break; + + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = pi->ipi_addr; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + local->sa_sin.sin_len = sizeof(local->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + + *ifindex = pi->ipi_ifindex; + break; + } +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + case IP_RECVTTL: { + memcpy(ttl, CMSG_DATA(cm), sizeof(*ttl)); + break; + } + + case IP_RECVDSTADDR: { + struct in_addr ia; + + memcpy(&ia, CMSG_DATA(cm), sizeof(ia)); + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = ia; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + local->sa_sin.sin_len = sizeof(local->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + break; + } +#endif /* BFD_BSD */ + + default: + /* + * On *BSDs we expect to land here when skipping + * the IP_RECVIF header. It will be handled by + * getsockopt_ifindex() below. + */ + /* NOTHING */ + break; + } + } + + /* OS agnostic way of getting interface name. */ + if (*ifindex == IFINDEX_INTERNAL) + *ifindex = getsockopt_ifindex(AF_INET, &msghdr); + + return mlen; +} + +ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl, + ifindex_t *ifindex, struct sockaddr_any *local, + struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + struct in6_pktinfo *pi6 = NULL; + ssize_t mlen; + uint32_t ttlval; + struct sockaddr_in6 msgaddr6; + struct msghdr msghdr6; + struct iovec iov[1]; + uint8_t cmsgbuf6[255]; + + /* Prepare the recvmsg params. */ + iov[0].iov_base = msgbuf; + iov[0].iov_len = msgbuflen; + + memset(&msghdr6, 0, sizeof(msghdr6)); + msghdr6.msg_name = &msgaddr6; + msghdr6.msg_namelen = sizeof(msgaddr6); + msghdr6.msg_iov = iov; + msghdr6.msg_iovlen = 1; + msghdr6.msg_control = cmsgbuf6; + msghdr6.msg_controllen = sizeof(cmsgbuf6); + + mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) + zlog_err("ipv6-recv: recv failed: %s", strerror(errno)); + + return -1; + } + + /* Get source address */ + peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL; + cm = CMSG_NXTHDR(&msghdr6, cm)) { + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + + if (cm->cmsg_type == IPV6_HOPLIMIT) { + memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval)); + if (ttlval > 255) { + if (bglobal.debug_network) + zlog_debug("%s: invalid TTL: %u", + __func__, ttlval); + return -1; + } + + *ttl = ttlval; + } else if (cm->cmsg_type == IPV6_PKTINFO) { + pi6 = (struct in6_pktinfo *)CMSG_DATA(cm); + if (pi6) { + local->sa_sin6.sin6_family = AF_INET6; + local->sa_sin6.sin6_addr = pi6->ipi6_addr; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + local->sa_sin6.sin6_len = sizeof(local->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + + *ifindex = pi6->ipi6_ifindex; + + /* Set scope ID for link local addresses. */ + if (IN6_IS_ADDR_LINKLOCAL( + &peer->sa_sin6.sin6_addr)) + peer->sa_sin6.sin6_scope_id = *ifindex; + if (IN6_IS_ADDR_LINKLOCAL( + &local->sa_sin6.sin6_addr)) + local->sa_sin6.sin6_scope_id = *ifindex; + } + } + } + + return mlen; +} + +static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd) +{ + if (sd == bvrf->bg_shop) { + EVENT_OFF(bvrf->bg_ev[0]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop, + &bvrf->bg_ev[0]); + } else if (sd == bvrf->bg_mhop) { + EVENT_OFF(bvrf->bg_ev[1]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop, + &bvrf->bg_ev[1]); + } else if (sd == bvrf->bg_shop6) { + EVENT_OFF(bvrf->bg_ev[2]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6, + &bvrf->bg_ev[2]); + } else if (sd == bvrf->bg_mhop6) { + EVENT_OFF(bvrf->bg_ev[3]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6, + &bvrf->bg_ev[3]); + } else if (sd == bvrf->bg_echo) { + EVENT_OFF(bvrf->bg_ev[4]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo, + &bvrf->bg_ev[4]); + } else if (sd == bvrf->bg_echov6) { + EVENT_OFF(bvrf->bg_ev[5]); + event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6, + &bvrf->bg_ev[5]); + } +} + +PRINTFRR(6, 7) +static void cp_debug(bool mhop, struct sockaddr_any *peer, + struct sockaddr_any *local, ifindex_t ifindex, + vrf_id_t vrfid, const char *fmt, ...) +{ + char buf[512], peerstr[128], localstr[128], portstr[64], vrfstr[64]; + va_list vl; + + /* Don't to any processing if debug is disabled. */ + if (bglobal.debug_network == false) + return; + + if (peer->sa_sin.sin_family) + snprintf(peerstr, sizeof(peerstr), " peer:%s", satostr(peer)); + else + peerstr[0] = 0; + + if (local->sa_sin.sin_family) + snprintf(localstr, sizeof(localstr), " local:%s", + satostr(local)); + else + localstr[0] = 0; + + if (ifindex != IFINDEX_INTERNAL) + snprintf(portstr, sizeof(portstr), " port:%u", ifindex); + else + portstr[0] = 0; + + if (vrfid != VRF_DEFAULT) + snprintf(vrfstr, sizeof(vrfstr), " vrf:%u", vrfid); + else + vrfstr[0] = 0; + + va_start(vl, fmt); + vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + + zlog_debug("control-packet: %s [mhop:%s%s%s%s%s]", buf, + mhop ? "yes" : "no", peerstr, localstr, portstr, vrfstr); +} + +static bool bfd_check_auth(const struct bfd_session *bfd, + const struct bfd_pkt *cp) +{ + if (CHECK_FLAG(cp->flags, BFD_ABIT)) { + /* RFC5880 4.1: Authentication Section is present. */ + struct bfd_auth *auth = (struct bfd_auth *)(cp + 1); + uint16_t pkt_auth_type = ntohs(auth->type); + + if (cp->len < BFD_PKT_LEN + sizeof(struct bfd_auth)) + return false; + + if (cp->len < BFD_PKT_LEN + auth->length) + return false; + + switch (pkt_auth_type) { + case BFD_AUTH_NULL: + return false; + case BFD_AUTH_SIMPLE: + /* RFC5880 6.7: To be finshed. */ + return false; + case BFD_AUTH_CRYPTOGRAPHIC: + /* RFC5880 6.7: To be finshed. */ + return false; + default: + /* RFC5880 6.7: To be finshed. */ + return false; + } + } + return true; +} + +void bfd_recv_cb(struct event *t) +{ + int sd = EVENT_FD(t); + struct bfd_session *bfd; + struct bfd_pkt *cp; + bool is_mhop; + ssize_t mlen = 0; + uint8_t ttl = 0; + vrf_id_t vrfid; + ifindex_t ifindex = IFINDEX_INTERNAL; + struct sockaddr_any local, peer; + uint8_t msgbuf[1516]; + struct interface *ifp = NULL; + struct bfd_vrf_global *bvrf = EVENT_ARG(t); + + /* Schedule next read. */ + bfd_sd_reschedule(bvrf, sd); + + /* Handle echo packets. */ + if (sd == bvrf->bg_echo || sd == bvrf->bg_echov6) { + ptm_bfd_process_echo_pkt(bvrf, sd); + return; + } + + /* Sanitize input/output. */ + memset(&local, 0, sizeof(local)); + memset(&peer, 0, sizeof(peer)); + + /* Handle control packets. */ + is_mhop = false; + if (sd == bvrf->bg_shop || sd == bvrf->bg_mhop) { + is_mhop = sd == bvrf->bg_mhop; + mlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex, + &local, &peer); + } else if (sd == bvrf->bg_shop6 || sd == bvrf->bg_mhop6) { + is_mhop = sd == bvrf->bg_mhop6; + mlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex, + &local, &peer); + } + + /* + * With netns backend, we have a separate socket in each VRF. It means + * that bvrf here is correct and we believe the bvrf->vrf->vrf_id. + * With VRF-lite backend, we have a single socket in the default VRF. + * It means that we can't believe the bvrf->vrf->vrf_id. But in + * VRF-lite, the ifindex is globally unique, so we can retrieve the + * correct vrf_id from the interface. + */ + vrfid = bvrf->vrf->vrf_id; + if (ifindex) { + ifp = if_lookup_by_index(ifindex, vrfid); + if (ifp) + vrfid = ifp->vrf->vrf_id; + } + + /* Implement RFC 5880 6.8.6 */ + if (mlen < BFD_PKT_LEN) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "too small (%zd bytes)", mlen); + return; + } + + /* Validate single hop packet TTL. */ + if ((!is_mhop) && (ttl != BFD_TTL_VAL)) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "invalid TTL: %d expected %d", ttl, BFD_TTL_VAL); + return; + } + + /* + * Parse the control header for inconsistencies: + * - Invalid version; + * - Bad multiplier configuration; + * - Short packets; + * - Invalid discriminator; + */ + cp = (struct bfd_pkt *)(msgbuf); + if (BFD_GETVER(cp->diag) != BFD_VERSION) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "bad version %d", BFD_GETVER(cp->diag)); + return; + } + + if (cp->detect_mult == 0) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "detect multiplier set to zero"); + return; + } + + if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "too small"); + return; + } + + if (BFD_GETMBIT(cp->flags)) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "detect non-zero Multipoint (M) flag"); + return; + } + + if (cp->discrs.my_discr == 0) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "'my discriminator' is zero"); + return; + } + + /* Find the session that this packet belongs. */ + bfd = ptm_bfd_sess_find(cp, &peer, &local, ifp, vrfid, is_mhop); + if (bfd == NULL) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "no session found"); + return; + } + /* + * We may have a situation where received packet is on wrong vrf + */ + if (bfd && bfd->vrf && bfd->vrf->vrf_id != vrfid) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "wrong vrfid."); + return; + } + + /* Ensure that existing good sessions are not overridden. */ + if (!cp->discrs.remote_discr && bfd->ses_state != PTM_BFD_DOWN && + bfd->ses_state != PTM_BFD_ADM_DOWN) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "'remote discriminator' is zero, not overridden"); + return; + } + + /* + * Multi hop: validate packet TTL. + * Single hop: set local address that received the packet. + * set peers mac address for echo packets + */ + if (is_mhop) { + if (ttl < bfd->mh_ttl) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "exceeded max hop count (expected %d, got %d)", + bfd->mh_ttl, ttl); + return; + } + } else { + + if (bfd->local_address.sa_sin.sin_family == AF_UNSPEC) + bfd->local_address = local; +#ifdef BFD_LINUX + if (ifp) + bfd_peer_mac_set(sd, bfd, &peer, ifp); +#endif + } + + bfd->stats.rx_ctrl_pkt++; + + /* + * If no interface was detected, save the interface where the + * packet came in. + */ + if (!is_mhop && bfd->ifp == NULL) + bfd->ifp = ifp; + + /* Log remote discriminator changes. */ + if ((bfd->discrs.remote_discr != 0) + && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "remote discriminator mismatch (expected %u, got %u)", + bfd->discrs.remote_discr, ntohl(cp->discrs.my_discr)); + + bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr); + + /* Check authentication. */ + if (!bfd_check_auth(bfd, cp)) { + cp_debug(is_mhop, &peer, &local, ifindex, vrfid, + "Authentication failed"); + return; + } + + /* Save remote diagnostics before state switch. */ + bfd->remote_diag = cp->diag & BFD_DIAGMASK; + + /* Update remote timers settings. */ + bfd->remote_timers.desired_min_tx = ntohl(cp->timers.desired_min_tx); + bfd->remote_timers.required_min_rx = ntohl(cp->timers.required_min_rx); + bfd->remote_timers.required_min_echo = + ntohl(cp->timers.required_min_echo); + bfd->remote_detect_mult = cp->detect_mult; + + if (BFD_GETCBIT(cp->flags)) + bfd->remote_cbit = 1; + else + bfd->remote_cbit = 0; + + /* State switch from section 6.2. */ + bs_state_handler(bfd, BFD_GETSTATE(cp->flags)); + + /* RFC 5880, Section 6.5: handle POLL/FINAL negotiation sequence. */ + if (bfd->polling && BFD_GETFBIT(cp->flags)) { + /* Disable polling. */ + bfd->polling = 0; + + /* Handle poll finalization. */ + bs_final_handler(bfd); + } + + /* + * Detection timeout calculation: + * The minimum detection timeout is the remote detection + * multipler (number of packets to be missed) times the agreed + * transmission interval. + * + * RFC 5880, Section 6.8.4. + */ + if (bfd->cur_timers.required_min_rx > bfd->remote_timers.desired_min_tx) + bfd->detect_TO = bfd->remote_detect_mult + * bfd->cur_timers.required_min_rx; + else + bfd->detect_TO = bfd->remote_detect_mult + * bfd->remote_timers.desired_min_tx; + + /* Apply new receive timer immediately. */ + bfd_recvtimer_update(bfd); + + /* Handle echo timers changes. */ + bs_echo_timer_handler(bfd); + + /* + * We've received a packet with the POLL bit set, we must send + * a control packet back with the FINAL bit set. + * + * RFC 5880, Section 6.5. + */ + if (BFD_GETPBIT(cp->flags)) { + /* We are finalizing a poll negotiation. */ + bs_final_handler(bfd); + + /* Send the control packet with the final bit immediately. */ + ptm_bfd_snd(bfd, 1); + } +} + +/* + * bp_bfd_echo_in: proccesses an BFD echo packet. On TTL == BFD_TTL_VAL + * the packet is looped back or returns the my discriminator ID along + * with the TTL. + * + * Returns -1 on error or loopback or 0 on success. + */ +int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl, + uint32_t *my_discr, uint64_t *my_rtt) +{ + struct bfd_echo_pkt *bep; + ssize_t rlen; + struct sockaddr_any local, peer; + ifindex_t ifindex = IFINDEX_INTERNAL; + vrf_id_t vrfid = VRF_DEFAULT; + uint8_t msgbuf[1516]; + size_t bfd_offset = 0; + + if (sd == bvrf->bg_echo) { +#ifdef BFD_LINUX + rlen = bfd_recv_ipv4_fp(sd, msgbuf, sizeof(msgbuf), ttl, + &ifindex, &local, &peer); + + /* silently drop echo packet that is looped in fastpath but + * still comes up to BFD + */ + if (rlen == -1) + return -1; + bfd_offset = sizeof(struct udphdr) + sizeof(struct iphdr) + + sizeof(struct ethhdr); +#else + rlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex, + &local, &peer); + bfd_offset = 0; +#endif + } else { + rlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex, + &local, &peer); + bfd_offset = 0; + } + + /* Short packet, better not risk reading it. */ + if (rlen < (ssize_t)sizeof(*bep)) { + cp_debug(false, &peer, &local, ifindex, vrfid, + "small echo packet"); + return -1; + } + + /* Test for loopback for ipv6, ipv4 is looped in forwarding plane */ + if ((*ttl == BFD_TTL_VAL) && (sd == bvrf->bg_echov6)) { + bp_udp_send(sd, *ttl - 1, msgbuf, rlen, + (struct sockaddr *)&peer, + (sd == bvrf->bg_echo) ? sizeof(peer.sa_sin) + : sizeof(peer.sa_sin6)); + return -1; + } + + /* Read my discriminator from BFD Echo packet. */ + bep = (struct bfd_echo_pkt *)(msgbuf + bfd_offset); + *my_discr = ntohl(bep->my_discr); + if (*my_discr == 0) { + cp_debug(false, &peer, &local, ifindex, vrfid, + "invalid echo packet discriminator (zero)"); + return -1; + } + +#ifdef BFD_LINUX + /* RTT Calculation: determine RTT time of IPv4 echo pkt */ + if (sd == bvrf->bg_echo) { + struct timeval time_sent = {0, 0}; + + time_sent.tv_sec = be64toh(bep->time_sent_sec); + time_sent.tv_usec = be64toh(bep->time_sent_usec); + *my_rtt = monotime_since(&time_sent, NULL); + } +#endif + + return 0; +} + +#ifdef BFD_LINUX +/* + * send a bfd packet with src/dst same IP so that the peer will receive + * the packet and forward it back to sender in the forwarding plane + */ +int bp_udp_send_fp(int sd, uint8_t *data, size_t datalen, + struct bfd_session *bfd) +{ + ssize_t wlen; + struct msghdr msg = {0}; + struct iovec iov[1]; + uint8_t msgctl[255]; + struct sockaddr_ll sadr_ll = {0}; + + sadr_ll.sll_ifindex = bfd->ifp->ifindex; + sadr_ll.sll_halen = ETH_ALEN; + memcpy(sadr_ll.sll_addr, bfd->peer_hw_addr, sizeof(bfd->peer_hw_addr)); + sadr_ll.sll_protocol = htons(ETH_P_IP); + + /* Prepare message data. */ + iov[0].iov_base = data; + iov[0].iov_len = datalen; + + memset(msgctl, 0, sizeof(msgctl)); + msg.msg_name = &sadr_ll; + msg.msg_namelen = sizeof(sadr_ll); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + /* Send echo to peer */ + wlen = sendmsg(sd, &msg, 0); + + if (wlen <= 0) { + if (bglobal.debug_network) + zlog_debug("%s: loopback failure: (%d) %s", __func__, + errno, strerror(errno)); + return -1; + } else if (wlen < (ssize_t)datalen) { + if (bglobal.debug_network) + zlog_debug("%s: partial send: %zd expected %zu", + __func__, wlen, datalen); + return -1; + } + + return 0; +} +#endif + +int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen, + struct sockaddr *to, socklen_t tolen) +{ + struct cmsghdr *cmsg; + ssize_t wlen; + int ttlval = ttl; + bool is_ipv6 = to->sa_family == AF_INET6; + struct msghdr msg; + struct iovec iov[1]; + uint8_t msgctl[255]; + + /* Prepare message data. */ + iov[0].iov_base = data; + iov[0].iov_len = datalen; + + memset(&msg, 0, sizeof(msg)); + memset(msgctl, 0, sizeof(msgctl)); + msg.msg_name = to; + msg.msg_namelen = tolen; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + /* Prepare the packet TTL information. */ + if (ttl > 0) { + /* Use ancillary data. */ + msg.msg_control = msgctl; + msg.msg_controllen = CMSG_LEN(sizeof(ttlval)); + + /* Configure the ancillary data. */ + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(ttlval)); + if (is_ipv6) { + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_HOPLIMIT; + } else { +#ifdef BFD_LINUX + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_TTL; +#else + /* FreeBSD does not support TTL in ancillary data. */ + msg.msg_control = NULL; + msg.msg_controllen = 0; + + bp_set_ttl(sd, ttl); +#endif /* BFD_BSD */ + } + memcpy(CMSG_DATA(cmsg), &ttlval, sizeof(ttlval)); + } + + /* Send echo back. */ + wlen = sendmsg(sd, &msg, 0); + if (wlen <= 0) { + if (bglobal.debug_network) + zlog_debug("%s: loopback failure: (%d) %s", __func__, + errno, strerror(errno)); + return -1; + } else if (wlen < (ssize_t)datalen) { + if (bglobal.debug_network) + zlog_debug("%s: partial send: %zd expected %zu", + __func__, wlen, datalen); + return -1; + } + + return 0; +} + + +/* + * Sockets creation. + */ + + +/* + * IPv4 sockets + */ +int bp_set_ttl(int sd, uint8_t value) +{ + int ttl = value; + + if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) { + zlog_warn("%s: setsockopt(IP_TTL, %d): %s", __func__, value, + strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tos(int sd, uint8_t value) +{ + int tos = value; + + if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) { + zlog_warn("%s: setsockopt(IP_TOS, %d): %s", __func__, value, + strerror(errno)); + return -1; + } + + return 0; +} + +static bool bp_set_reuse_addr(int sd) +{ + int one = 1; + + if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) { + zlog_warn("%s: setsockopt(SO_REUSEADDR, %d): %s", __func__, one, + strerror(errno)); + return false; + } + return true; +} + +static bool bp_set_reuse_port(int sd) +{ + int one = 1; + + if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) == -1) { + zlog_warn("%s: setsockopt(SO_REUSEPORT, %d): %s", __func__, one, + strerror(errno)); + return false; + } + return true; +} + + +static void bp_set_ipopts(int sd) +{ + int rcvttl = BFD_RCV_TTL_VAL; + + if (!bp_set_reuse_addr(sd)) + zlog_fatal("set-reuse-addr: failed"); + + if (!bp_set_reuse_port(sd)) + zlog_fatal("set-reuse-port: failed"); + + if (bp_set_ttl(sd, BFD_TTL_VAL) != 0) + zlog_fatal("set-ipopts: TTL configuration failed"); + + if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl)) + == -1) + zlog_fatal("set-ipopts: setsockopt(IP_RECVTTL, %d): %s", rcvttl, + strerror(errno)); + +#ifdef BFD_LINUX + int pktinfo = BFD_PKT_INFO_VAL; + + /* Figure out address and interface to do the peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) + == -1) + zlog_fatal("set-ipopts: setsockopt(IP_PKTINFO, %d): %s", + pktinfo, strerror(errno)); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + int yes = 1; + + /* Find out our address for peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1) + zlog_fatal("set-ipopts: setsockopt(IP_RECVDSTADDR, %d): %s", + yes, strerror(errno)); + + /* Find out interface where the packet came in. */ + if (setsockopt_ifindex(AF_INET, sd, yes) == -1) + zlog_fatal("set-ipopts: setsockopt_ipv4_ifindex(%d): %s", yes, + strerror(errno)); +#endif /* BFD_BSD */ +} + +static void bp_bind_ip(int sd, uint16_t port) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) + zlog_fatal("bind-ip: bind: %s", strerror(errno)); +} + +int bp_udp_shop(const struct vrf *vrf) +{ + int sd; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, + vrf->name); + } + if (sd == -1) + zlog_fatal("udp-shop: socket: %s", strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEFDESTPORT); + return sd; +} + +int bp_udp_mhop(const struct vrf *vrf) +{ + int sd; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, + vrf->name); + } + if (sd == -1) + zlog_fatal("udp-mhop: socket: %s", strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} + +int bp_peer_socket(const struct bfd_session *bs) +{ + int sd, pcount; + struct sockaddr_in sin; + static int srcPort = BFD_SRCPORTINIT; + const char *device_to_bind = NULL; + + if (bs->key.ifname[0]) + device_to_bind = (const char *)bs->key.ifname; + else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT) + || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]))) + device_to_bind = (const char *)bs->key.vrfname; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, + bs->vrf->vrf_id, device_to_bind); + } + if (sd == -1) { + zlog_err("ipv4-new: failed to create socket: %s", + strerror(errno)); + return -1; + } + + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttl(sd, BFD_TTL_VAL) != 0) { + close(sd); + return -1; + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tos(sd, BFD_TOS_VAL) != 0) { + close(sd); + return -1; + } + + /* Find an available source port in the proper range */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + memcpy(&sin.sin_addr, &bs->key.local, sizeof(sin.sin_addr)); + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + zlog_err("ipv4-new: failed to bind port: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin.sin_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0); + + return sd; +} + + +/* + * IPv6 sockets + */ + +int bp_peer_socketv6(const struct bfd_session *bs) +{ + int sd, pcount; + struct sockaddr_in6 sin6; + static int srcPort = BFD_SRCPORTINIT; + const char *device_to_bind = NULL; + + if (bs->key.ifname[0]) + device_to_bind = (const char *)bs->key.ifname; + else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT) + || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) + && bs->key.vrfname[0]))) + device_to_bind = (const char *)bs->key.vrfname; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, + bs->vrf->vrf_id, device_to_bind); + } + if (sd == -1) { + zlog_err("ipv6-new: failed to create socket: %s", + strerror(errno)); + return -1; + } + + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttlv6(sd, BFD_TTL_VAL) != 0) { + close(sd); + return -1; + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tosv6(sd, BFD_TOS_VAL) != 0) { + close(sd); + return -1; + } + + /* Find an available source port in the proper range */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + memcpy(&sin6.sin6_addr, &bs->key.local, sizeof(sin6.sin6_addr)); + if (bs->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = bs->ifp->ifindex; + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + zlog_err("ipv6-new: failed to bind port: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin6.sin6_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0); + + return sd; +} + +int bp_set_ttlv6(int sd, uint8_t value) +{ + int ttl = value; + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) + == -1) { + zlog_warn("set-ttlv6: setsockopt(IPV6_UNICAST_HOPS, %d): %s", + value, strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tosv6(int sd, uint8_t value) +{ + int tos = value; + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) + == -1) { + zlog_warn("set-tosv6: setsockopt(IPV6_TCLASS, %d): %s", value, + strerror(errno)); + return -1; + } + + return 0; +} + +static void bp_set_ipv6opts(int sd) +{ + int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL; + int ipv6_only = BFD_IPV6_ONLY_VAL; + + if (!bp_set_reuse_addr(sd)) + zlog_fatal("set-reuse-addr: failed"); + + if (!bp_set_reuse_port(sd)) + zlog_fatal("set-reuse-port: failed"); + + if (bp_set_ttlv6(sd, BFD_TTL_VAL) == -1) + zlog_fatal( + "set-ipv6opts: setsockopt(IPV6_UNICAST_HOPS, %d): %s", + BFD_TTL_VAL, strerror(errno)); + + if (setsockopt_ipv6_hoplimit(sd, BFD_RCV_TTL_VAL) == -1) + zlog_fatal("set-ipv6opts: setsockopt(IPV6_HOPLIMIT, %d): %s", + BFD_RCV_TTL_VAL, strerror(errno)); + + if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1) + zlog_fatal("set-ipv6opts: setsockopt(IPV6_PKTINFO, %d): %s", + ipv6_pktinfo, strerror(errno)); + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, + sizeof(ipv6_only)) + == -1) + zlog_fatal("set-ipv6opts: setsockopt(IPV6_V6ONLY, %d): %s", + ipv6_only, strerror(errno)); +} + +static void bp_bind_ipv6(int sd, uint16_t port) +{ + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = htons(port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) + zlog_fatal("bind-ipv6: bind: %s", strerror(errno)); +} + +int bp_udp6_shop(const struct vrf *vrf) +{ + int sd; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, + vrf->name); + } + if (sd == -1) { + if (errno != EAFNOSUPPORT) + zlog_fatal("udp6-shop: socket: %s", strerror(errno)); + else + zlog_warn("udp6-shop: V6 is not supported, continuing"); + + return -1; + } + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEFDESTPORT); + + return sd; +} + +int bp_udp6_mhop(const struct vrf *vrf) +{ + int sd; + + frr_with_privs(&bglobal.bfdd_privs) { + sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id, + vrf->name); + } + if (sd == -1) { + if (errno != EAFNOSUPPORT) + zlog_fatal("udp6-mhop: socket: %s", strerror(errno)); + else + zlog_warn("udp6-mhop: V6 is not supported, continuing"); + + return -1; + } + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} + +#ifdef BFD_LINUX +/* tcpdump -dd udp dst port 3785 */ +struct sock_filter my_filterudp[] = { + {0x28, 0, 0, 0x0000000c}, {0x15, 0, 8, 0x00000800}, + {0x30, 0, 0, 0x00000017}, {0x15, 0, 6, 0x00000011}, + {0x28, 0, 0, 0x00000014}, {0x45, 4, 0, 0x00001fff}, + {0xb1, 0, 0, 0x0000000e}, {0x48, 0, 0, 0x00000010}, + {0x15, 0, 1, 0x00000ec9}, {0x6, 0, 0, 0x00040000}, + {0x6, 0, 0, 0x00000000}, +}; + +#define MY_FILTER_LENGTH 11 + +int bp_echo_socket(const struct vrf *vrf) +{ + int s; + + frr_with_privs (&bglobal.bfdd_privs) { + s = vrf_socket(AF_PACKET, SOCK_RAW, ETH_P_IP, vrf->vrf_id, + vrf->name); + } + + if (s == -1) + zlog_fatal("echo-socket: socket: %s", strerror(errno)); + + struct sock_fprog pf; + struct sockaddr_ll sll = {0}; + + /* adjust filter for socket to only receive ECHO packets */ + pf.filter = my_filterudp; + pf.len = MY_FILTER_LENGTH; + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == + -1) { + zlog_warn("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__, + strerror(errno)); + close(s); + return -1; + } + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_protocol = htons(ETH_P_IP); + sll.sll_ifindex = 0; + if (bind(s, (struct sockaddr *)&sll, sizeof(sll)) < 0) { + zlog_warn("Failed to bind echo socket: %s", + safe_strerror(errno)); + close(s); + return -1; + } + + return s; +} +#else +int bp_echo_socket(const struct vrf *vrf) +{ + int s; + + frr_with_privs(&bglobal.bfdd_privs) { + s = vrf_socket(AF_INET, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name); + } + if (s == -1) + zlog_fatal("echo-socket: socket: %s", strerror(errno)); + + bp_set_ipopts(s); + bp_bind_ip(s, BFD_DEF_ECHO_PORT); + + return s; +} +#endif + +int bp_echov6_socket(const struct vrf *vrf) +{ + int s; + + frr_with_privs(&bglobal.bfdd_privs) { + s = vrf_socket(AF_INET6, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name); + } + if (s == -1) { + if (errno != EAFNOSUPPORT) + zlog_fatal("echov6-socket: socket: %s", + strerror(errno)); + else + zlog_warn("echov6-socket: V6 is not supported, continuing"); + + return -1; + } + + bp_set_ipv6opts(s); + bp_bind_ipv6(s, BFD_DEF_ECHO_PORT); + + return s; +} + +#ifdef BFD_LINUX +/* get peer's mac address to be used with Echo packets when they are looped in + * peers forwarding plane + */ +void bfd_peer_mac_set(int sd, struct bfd_session *bfd, + struct sockaddr_any *peer, struct interface *ifp) +{ + struct arpreq arpreq_; + + if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET)) + return; + if (ifp->flags & IFF_NOARP) + return; + + if (peer->sa_sin.sin_family == AF_INET) { + /* IPV4 */ + struct sockaddr_in *addr = + (struct sockaddr_in *)&arpreq_.arp_pa; + + memset(&arpreq_, 0, sizeof(struct arpreq)); + addr->sin_family = AF_INET; + memcpy(&addr->sin_addr.s_addr, &peer->sa_sin.sin_addr, + sizeof(addr->sin_addr)); + strlcpy(arpreq_.arp_dev, ifp->name, sizeof(arpreq_.arp_dev)); + + if (ioctl(sd, SIOCGARP, &arpreq_) < 0) { + if (bglobal.debug_network) + zlog_debug( + "BFD: getting peer's mac on %s failed error %s", + ifp->name, strerror(errno)); + UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET); + memset(bfd->peer_hw_addr, 0, sizeof(bfd->peer_hw_addr)); + + } else { + memcpy(bfd->peer_hw_addr, arpreq_.arp_ha.sa_data, + sizeof(bfd->peer_hw_addr)); + SET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET); + } + } +} +#endif diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h new file mode 100644 index 0000000..f1f8185 --- /dev/null +++ b/bfdd/bfdctl.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * bfdctl.h: all BFDd control socket protocol definitions. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#ifndef _BFDCTRL_H_ +#define _BFDCTRL_H_ + +#include + +#include +#include + +/* + * Auxiliary definitions + */ +struct sockaddr_any { + union { + struct sockaddr_in sa_sin; + struct sockaddr_in6 sa_sin6; + }; +}; + +#ifndef MAXNAMELEN +#define MAXNAMELEN 32 +#endif + +#define BPC_DEF_DETECTMULTIPLIER 3 +#define BPC_DEF_RECEIVEINTERVAL 300 /* milliseconds */ +#define BPC_DEF_TRANSMITINTERVAL 300 /* milliseconds */ +#define BPC_DEF_ECHORECEIVEINTERVAL 50 /* milliseconds */ +#define BPC_DEF_ECHOTRANSMITINTERVAL 50 /* milliseconds */ + +/* Peer status */ +enum bfd_peer_status { + BPS_SHUTDOWN = 0, /* == PTM_BFD_ADM_DOWN, "adm-down" */ + BPS_DOWN = 1, /* == PTM_BFD_DOWN, "down" */ + BPS_INIT = 2, /* == PTM_BFD_INIT, "init" */ + BPS_UP = 3, /* == PTM_BFD_UP, "up" */ +}; + +struct bfd_peer_cfg { + bool bpc_mhop; + bool bpc_ipv4; + struct sockaddr_any bpc_peer; + struct sockaddr_any bpc_local; + + bool bpc_has_label; + char bpc_label[MAXNAMELEN]; + + bool bpc_has_localif; + char bpc_localif[MAXNAMELEN + 1]; + + bool bpc_has_vrfname; + char bpc_vrfname[MAXNAMELEN + 1]; + + bool bpc_has_detectmultiplier; + uint8_t bpc_detectmultiplier; + + bool bpc_has_recvinterval; + uint64_t bpc_recvinterval; + + bool bpc_has_txinterval; + uint64_t bpc_txinterval; + + bool bpc_has_echorecvinterval; + uint64_t bpc_echorecvinterval; + + bool bpc_has_echotxinterval; + uint64_t bpc_echotxinterval; + + bool bpc_has_minimum_ttl; + uint8_t bpc_minimum_ttl; + + bool bpc_echo; + bool bpc_createonly; + bool bpc_shutdown; + + bool bpc_cbit; + bool bpc_passive; + + bool bpc_has_profile; + char bpc_profile[64]; + + /* Status information */ + enum bfd_peer_status bpc_bps; + uint32_t bpc_id; + uint32_t bpc_remoteid; + uint8_t bpc_diag; + uint8_t bpc_remotediag; + uint8_t bpc_remote_detectmultiplier; + uint64_t bpc_remote_recvinterval; + uint64_t bpc_remote_txinterval; + uint64_t bpc_remote_echointerval; + uint64_t bpc_lastevent; +}; + + +/* + * Protocol definitions + */ +enum bc_msg_version { + BMV_VERSION_1 = 1, +}; + +enum bc_msg_type { + BMT_RESPONSE = 1, + BMT_REQUEST_ADD = 2, + BMT_REQUEST_DEL = 3, + BMT_NOTIFY = 4, + BMT_NOTIFY_ADD = 5, + BMT_NOTIFY_DEL = 6, +}; + +/* Notify flags to use with bcm_notify. */ +#define BCM_NOTIFY_ALL ((uint64_t)-1) +#define BCM_NOTIFY_PEER_STATE (1ULL << 0) +#define BCM_NOTIFY_CONFIG (1ULL << 1) +#define BCM_NOTIFY_NONE 0 + +/* Response 'status' definitions. */ +#define BCM_RESPONSE_OK "ok" +#define BCM_RESPONSE_ERROR "error" + +/* Notify operation. */ +#define BCM_NOTIFY_PEER_STATUS "status" +#define BCM_NOTIFY_CONFIG_ADD "add" +#define BCM_NOTIFY_CONFIG_DELETE "delete" +#define BCM_NOTIFY_CONFIG_UPDATE "update" + +/* Notification special ID. */ +#define BCM_NOTIFY_ID 0 + +struct bfd_control_msg { + /* Total length without the header. */ + uint32_t bcm_length; + /* + * Message request/response id. + * All requests will have a correspondent response with the + * same id. + */ + uint16_t bcm_id; + /* Message type. */ + uint8_t bcm_type; + /* Message version. */ + uint8_t bcm_ver; + /* Message payload. */ + uint8_t bcm_data[0]; +}; + +#endif diff --git a/bfdd/bfdd.c b/bfdd/bfdd.c new file mode 100644 index 0000000..243cf5c --- /dev/null +++ b/bfdd/bfdd.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + */ + +#include + +#include +#include +#include +#include + +#include + +#include "filter.h" +#include "if.h" +#include "vrf.h" + +#include "bfd.h" +#include "bfdd_nb.h" +#include "bfddp_packet.h" +#include "lib/version.h" +#include "lib/command.h" + + +/* + * FRR related code. + */ +DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon"); +DEFINE_MTYPE(BFDD, BFDD_CONTROL, "control socket memory"); +DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "control notification data"); + +/* Master of threads. */ +struct event_loop *master; + +/* BFDd privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_SYS_ADMIN, ZCAP_NET_RAW}; + +/* BFD daemon information. */ +static struct frr_daemon_info bfdd_di; + +void socket_close(int *s) +{ + if (*s <= 0) + return; + + if (close(*s) != 0) + zlog_err("%s: close(%d): (%d) %s", __func__, *s, errno, + strerror(errno)); + + *s = -1; +} + +static void sigusr1_handler(void) +{ + zlog_rotate(); +} + +static void sigterm_handler(void) +{ + bglobal.bg_shutdown = true; + + /* Signalize shutdown. */ + frr_early_fini(); + + /* Stop receiving message from zebra. */ + bfdd_zclient_stop(); + + /* Shutdown controller to avoid receiving anymore commands. */ + control_shutdown(); + + /* Shutdown and free all protocol related memory. */ + bfd_shutdown(); + + bfd_vrf_terminate(); + + bfdd_zclient_terminate(); + + /* Terminate and free() FRR related memory. */ + frr_fini(); + + exit(0); +} + +static void sighup_handler(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, bfdd_di.config_file, config_default); +} + +static struct frr_signal_t bfd_signals[] = { + { + .signal = SIGUSR1, + .handler = &sigusr1_handler, + }, + { + .signal = SIGTERM, + .handler = &sigterm_handler, + }, + { + .signal = SIGINT, + .handler = &sigterm_handler, + }, + { + .signal = SIGHUP, + .handler = &sighup_handler, + }, +}; + +static const struct frr_yang_module_info *const bfdd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_bfdd_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(bfdd, BFD, + .vty_port = BFDD_VTY_PORT, + .proghelp = "Implementation of the BFD protocol.", + + .signals = bfd_signals, + .n_signals = array_size(bfd_signals), + + .privs = &bglobal.bfdd_privs, + + .yang_modules = bfdd_yang_modules, + .n_yang_modules = array_size(bfdd_yang_modules), +); +/* clang-format on */ + +#define OPTION_CTLSOCK 1001 +#define OPTION_DPLANEADDR 2000 +static const struct option longopts[] = { + {"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, + {"dplaneaddr", required_argument, NULL, OPTION_DPLANEADDR}, + {0} +}; + + +/* + * BFD daemon related code. + */ +struct bfd_global bglobal; + +const struct bfd_diag_str_list diag_list[] = { + {.str = "control-expired", .type = BD_CONTROL_EXPIRED}, + {.str = "echo-failed", .type = BD_ECHO_FAILED}, + {.str = "neighbor-down", .type = BD_NEIGHBOR_DOWN}, + {.str = "forwarding-reset", .type = BD_FORWARDING_RESET}, + {.str = "path-down", .type = BD_PATH_DOWN}, + {.str = "concatenated-path-down", .type = BD_CONCATPATH_DOWN}, + {.str = "administratively-down", .type = BD_ADMIN_DOWN}, + {.str = "reverse-concat-path-down", .type = BD_REVCONCATPATH_DOWN}, + {.str = NULL}, +}; + +const struct bfd_state_str_list state_list[] = { + {.str = "admin-down", .type = PTM_BFD_ADM_DOWN}, + {.str = "down", .type = PTM_BFD_DOWN}, + {.str = "init", .type = PTM_BFD_INIT}, + {.str = "up", .type = PTM_BFD_UP}, + {.str = NULL}, +}; + +static uint16_t +parse_port(const char *str) +{ + char *nulbyte; + long rv; + + errno = 0; + rv = strtol(str, &nulbyte, 10); + /* No conversion performed. */ + if (rv == 0 && errno == EINVAL) { + fprintf(stderr, "invalid BFD data plane address port: %s\n", + str); + exit(0); + } + /* Invalid number range. */ + if ((rv <= 0 || rv >= 65535) || errno == ERANGE) { + fprintf(stderr, "invalid BFD data plane port range: %s\n", + str); + exit(0); + } + /* There was garbage at the end of the string. */ + if (*nulbyte != 0) { + fprintf(stderr, "invalid BFD data plane port: %s\n", + str); + exit(0); + } + + return (uint16_t)rv; +} + +static void +distributed_bfd_init(const char *arg) +{ + char *sptr, *saux; + bool is_client = false; + size_t slen; + socklen_t salen; + char addr[64]; + char type[64]; + union { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_un sun; + } sa; + + /* Basic parsing: find ':' to figure out type part and address part. */ + sptr = strchr(arg, ':'); + if (sptr == NULL) { + fprintf(stderr, "invalid BFD data plane socket: %s\n", arg); + exit(1); + } + + /* Calculate type string length. */ + slen = (size_t)(sptr - arg); + + /* Copy the address part. */ + sptr++; + strlcpy(addr, sptr, sizeof(addr)); + + /* Copy type part. */ + strlcpy(type, arg, slen + 1); + + /* Reset address data. */ + memset(&sa, 0, sizeof(sa)); + + /* Fill the address information. */ + if (strcmp(type, "unix") == 0 || strcmp(type, "unixc") == 0) { + if (strcmp(type, "unixc") == 0) + is_client = true; + + salen = sizeof(sa.sun); + sa.sun.sun_family = AF_UNIX; + strlcpy(sa.sun.sun_path, addr, sizeof(sa.sun.sun_path)); + } else if (strcmp(type, "ipv4") == 0 || strcmp(type, "ipv4c") == 0) { + if (strcmp(type, "ipv4c") == 0) + is_client = true; + + salen = sizeof(sa.sin); + sa.sin.sin_family = AF_INET; + + /* Parse port if any. */ + sptr = strchr(addr, ':'); + if (sptr == NULL) { + sa.sin.sin_port = htons(BFD_DATA_PLANE_DEFAULT_PORT); + } else { + *sptr = 0; + sa.sin.sin_port = htons(parse_port(sptr + 1)); + } + + if (inet_pton(AF_INET, addr, &sa.sin.sin_addr) != 1) + errx(1, "%s: inet_pton: invalid address %s", __func__, + addr); + } else if (strcmp(type, "ipv6") == 0 || strcmp(type, "ipv6c") == 0) { + if (strcmp(type, "ipv6c") == 0) + is_client = true; + + salen = sizeof(sa.sin6); + sa.sin6.sin6_family = AF_INET6; + + /* Check for IPv6 enclosures '[]' */ + sptr = &addr[0]; + if (*sptr != '[') + errx(1, "%s: invalid IPv6 address format: %s", __func__, + addr); + + saux = strrchr(addr, ']'); + if (saux == NULL) + errx(1, "%s: invalid IPv6 address format: %s", __func__, + addr); + + /* Consume the '[]:' part. */ + slen = saux - sptr; + memmove(addr, addr + 1, slen); + addr[slen - 1] = 0; + + /* Parse port if any. */ + saux++; + sptr = strrchr(saux, ':'); + if (sptr == NULL) { + sa.sin6.sin6_port = htons(BFD_DATA_PLANE_DEFAULT_PORT); + } else { + *sptr = 0; + sa.sin6.sin6_port = htons(parse_port(sptr + 1)); + } + + if (inet_pton(AF_INET6, addr, &sa.sin6.sin6_addr) != 1) + errx(1, "%s: inet_pton: invalid address %s", __func__, + addr); + } else { + fprintf(stderr, "invalid BFD data plane socket type: %s\n", + type); + exit(1); + } + + /* Initialize BFD data plane listening socket. */ + bfd_dplane_init((struct sockaddr *)&sa, salen, is_client); +} + +static void bg_init(void) +{ + struct zebra_privs_t bfdd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, + }; + + TAILQ_INIT(&bglobal.bg_bcslist); + TAILQ_INIT(&bglobal.bg_obslist); + + memcpy(&bglobal.bfdd_privs, &bfdd_privs, + sizeof(bfdd_privs)); +} + +int main(int argc, char *argv[]) +{ + char ctl_path[512], dplane_addr[512]; + bool ctlsockused = false; + int opt; + + bglobal.bg_use_dplane = false; + + /* Initialize system sockets. */ + bg_init(); + + frr_preinit(&bfdd_di, argc, argv); + frr_opt_add("", longopts, + " --bfdctl Specify bfdd control socket\n" + " --dplaneaddr Specify BFD data plane address\n"); + + while (true) { + opt = frr_getopt(argc, argv, NULL); + if (opt == EOF) + break; + + switch (opt) { + case OPTION_CTLSOCK: + strlcpy(ctl_path, optarg, sizeof(ctl_path)); + ctlsockused = true; + break; + case OPTION_DPLANEADDR: + strlcpy(dplane_addr, optarg, sizeof(dplane_addr)); + bglobal.bg_use_dplane = true; + break; + + default: + frr_help_exit(1); + } + } + + if (!ctlsockused) + snprintf(ctl_path, sizeof(ctl_path), BFDD_SOCK_NAME); + + /* Initialize FRR infrastructure. */ + master = frr_init(); + + /* Initialize control socket. */ + control_init(ctl_path); + + /* Initialize BFD data structures. */ + bfd_initialize(); + + bfd_vrf_init(); + + access_list_init(); + + /* Initialize zebra connection. */ + bfdd_zclient_init(&bglobal.bfdd_privs); + + event_add_read(master, control_accept, NULL, bglobal.bg_csock, + &bglobal.bg_csockev); + + /* Install commands. */ + bfdd_vty_init(); + + /* read configuration file and daemonize */ + frr_config_fork(); + + /* Initialize BFD data plane listening socket. */ + if (bglobal.bg_use_dplane) + distributed_bfd_init(dplane_addr); + + frr_run(master); + /* NOTREACHED */ + + return 0; +} diff --git a/bfdd/bfdd_cli.c b/bfdd/bfdd_cli.c new file mode 100644 index 0000000..75034d2 --- /dev/null +++ b/bfdd/bfdd_cli.c @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon CLI implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound_cli.h" + +#include "bfdd/bfdd_cli_clippy.c" + +#include "bfd.h" +#include "bfdd_nb.h" + +/* + * Definitions. + */ +#define PEER_STR "Configure peer\n" +#define INTERFACE_NAME_STR "Configure interface name to use\n" +#define PEER_IPV4_STR "IPv4 peer address\n" +#define PEER_IPV6_STR "IPv6 peer address\n" +#define MHOP_STR "Configure multihop\n" +#define LOCAL_STR "Configure local address\n" +#define LOCAL_IPV4_STR "IPv4 local address\n" +#define LOCAL_IPV6_STR "IPv6 local address\n" +#define LOCAL_INTF_STR "Configure local interface name to use\n" +#define VRF_STR "Configure VRF\n" +#define VRF_NAME_STR "Configure VRF name\n" + +/* + * Prototypes. + */ +static bool +bfd_cli_is_single_hop(struct vty *vty) +{ + return strstr(VTY_CURR_XPATH, "/single-hop") != NULL; +} + +static bool +bfd_cli_is_profile(struct vty *vty) +{ + return strstr(VTY_CURR_XPATH, "/bfd/profile") != NULL; +} + +/* + * Functions. + */ +DEFPY_YANG_NOSH( + bfd_enter, bfd_enter_cmd, + "bfd", + "Configure BFD peers\n") +{ + int ret; + + nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_CREATE, NULL); + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(BFD_NODE, "/frr-bfdd:bfdd/bfd"); + + return ret; +} + +DEFUN_YANG( + bfd_config_reset, bfd_config_reset_cmd, + "no bfd", + NO_STR + "Configure BFD peers\n") +{ + nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_header(struct vty *vty, + const struct lyd_node *dnode + __attribute__((__unused__)), + bool show_defaults __attribute__((__unused__))) +{ + vty_out(vty, "!\nbfd\n"); +} + +void bfd_cli_show_header_end(struct vty *vty, const struct lyd_node *dnode + __attribute__((__unused__))) +{ + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); +} + +DEFPY_YANG_NOSH( + bfd_peer_enter, bfd_peer_enter_cmd, + "peer [{multihop$multihop|local-address |interface IFNAME$ifname|vrf NAME}]", + PEER_STR + PEER_IPV4_STR + PEER_IPV6_STR + MHOP_STR + LOCAL_STR + LOCAL_IPV4_STR + LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + VRF_STR + VRF_NAME_STR) +{ + int ret, slen; + char source_str[INET6_ADDRSTRLEN + 32]; + char xpath[XPATH_MAXLEN], xpath_srcaddr[XPATH_MAXLEN + 32]; + + if (multihop) { + if (!local_address_str) { + vty_out(vty, + "%% local-address is required when using multihop\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (ifname) { + vty_out(vty, + "%% interface is prohibited when using multihop\n"); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(source_str, sizeof(source_str), "[source-addr='%s']", + local_address_str); + } else + source_str[0] = 0; + + slen = snprintf(xpath, sizeof(xpath), + "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']", + multihop ? "multi-hop" : "single-hop", source_str, + peer_str); + if (ifname) + slen += snprintf(xpath + slen, sizeof(xpath) - slen, + "[interface='%s']", ifname); + else if (!multihop) + slen += snprintf(xpath + slen, sizeof(xpath) - slen, + "[interface='*']"); + if (vrf) + snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf); + else + snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", + VRF_DEFAULT_NAME); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + if (multihop == NULL && local_address_str != NULL) { + snprintf(xpath_srcaddr, sizeof(xpath_srcaddr), + "%s/source-addr", xpath); + nb_cli_enqueue_change(vty, xpath_srcaddr, NB_OP_MODIFY, + local_address_str); + } + + /* Apply settings immediately. */ + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(BFD_PEER_NODE, xpath); + + return ret; +} + +DEFPY_YANG( + bfd_no_peer, bfd_no_peer_cmd, + "no peer [{multihop$multihop|local-address |interface IFNAME$ifname|vrf NAME}]", + NO_STR + PEER_STR + PEER_IPV4_STR + PEER_IPV6_STR + MHOP_STR + LOCAL_STR + LOCAL_IPV4_STR + LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + VRF_STR + VRF_NAME_STR) +{ + int slen; + char xpath[XPATH_MAXLEN]; + char source_str[INET6_ADDRSTRLEN + 32]; + + if (multihop) { + if (!local_address_str) { + vty_out(vty, + "%% local-address is required when using multihop\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (ifname) { + vty_out(vty, + "%% interface is prohibited when using multihop\n"); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(source_str, sizeof(source_str), "[source-addr='%s']", + local_address_str); + } else + source_str[0] = 0; + + slen = snprintf(xpath, sizeof(xpath), + "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']", + multihop ? "multi-hop" : "single-hop", source_str, + peer_str); + if (ifname) + slen += snprintf(xpath + slen, sizeof(xpath) - slen, + "[interface='%s']", ifname); + else if (!multihop) + slen += snprintf(xpath + slen, sizeof(xpath) - slen, + "[interface='*']"); + if (vrf) + snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf); + else + snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", + VRF_DEFAULT_NAME); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + /* Apply settings immediatly. */ + return nb_cli_apply_changes(vty, NULL); +} + +static void _bfd_cli_show_peer(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults __attribute__((__unused__)), + bool mhop) +{ + const char *vrf = yang_dnode_get_string(dnode, "vrf"); + + vty_out(vty, " peer %s", + yang_dnode_get_string(dnode, "dest-addr")); + + if (mhop) + vty_out(vty, " multihop"); + + if (yang_dnode_exists(dnode, "source-addr")) + vty_out(vty, " local-address %s", + yang_dnode_get_string(dnode, "source-addr")); + + if (strcmp(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", vrf); + + if (!mhop) { + const char *ifname = + yang_dnode_get_string(dnode, "interface"); + if (strcmp(ifname, "*")) + vty_out(vty, " interface %s", ifname); + } + + vty_out(vty, "\n"); +} + +void bfd_cli_show_single_hop_peer(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + _bfd_cli_show_peer(vty, dnode, show_defaults, false); +} + +void bfd_cli_show_multi_hop_peer(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + _bfd_cli_show_peer(vty, dnode, show_defaults, true); +} + +void bfd_cli_show_peer_end(struct vty *vty, const struct lyd_node *dnode + __attribute__((__unused__))) +{ + vty_out(vty, " exit\n"); + vty_out(vty, " !\n"); +} + +DEFPY_YANG( + bfd_peer_shutdown, bfd_peer_shutdown_cmd, + "[no] shutdown", + NO_STR + "Disable BFD peer\n") +{ + nb_cli_enqueue_change(vty, "./administrative-down", NB_OP_MODIFY, + no ? "false" : "true"); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " %sshutdown\n", + yang_dnode_get_bool(dnode, NULL) ? "" : "no "); +} + +DEFPY_YANG( + bfd_peer_passive, bfd_peer_passive_cmd, + "[no] passive-mode", + NO_STR + "Don't attempt to start sessions\n") +{ + nb_cli_enqueue_change(vty, "./passive-mode", NB_OP_MODIFY, + no ? "false" : "true"); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_passive(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " %spassive-mode\n", + yang_dnode_get_bool(dnode, NULL) ? "" : "no "); +} + +DEFPY_YANG( + bfd_peer_minimum_ttl, bfd_peer_minimum_ttl_cmd, + "[no] minimum-ttl (1-254)$ttl", + NO_STR + "Expect packets with at least this TTL\n" + "Minimum TTL expected\n") +{ + if (bfd_cli_is_single_hop(vty)) { + vty_out(vty, "%% Minimum TTL is only available for multi hop sessions.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (no) + nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_DESTROY, + NULL); + else + nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_MODIFY, + ttl_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_bfd_peer_minimum_ttl, no_bfd_peer_minimum_ttl_cmd, + "no minimum-ttl", + NO_STR + "Expect packets with at least this TTL\n") +{ + nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_minimum_ttl(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " minimum-ttl %s\n", yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG( + bfd_peer_mult, bfd_peer_mult_cmd, + "detect-multiplier (2-255)$multiplier", + "Configure peer detection multiplier\n" + "Configure peer detection multiplier value\n") +{ + nb_cli_enqueue_change(vty, "./detection-multiplier", NB_OP_MODIFY, + multiplier_str); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_mult(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " detect-multiplier %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG( + bfd_peer_rx, bfd_peer_rx_cmd, + "receive-interval (10-60000)$interval", + "Configure peer receive interval\n" + "Configure peer receive interval value in milliseconds\n") +{ + char value[32]; + + snprintf(value, sizeof(value), "%ld", interval * 1000); + nb_cli_enqueue_change(vty, "./required-receive-interval", NB_OP_MODIFY, + value); + + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_rx(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + uint32_t value = yang_dnode_get_uint32(dnode, NULL); + + vty_out(vty, " receive-interval %u\n", value / 1000); +} + +DEFPY_YANG( + bfd_peer_tx, bfd_peer_tx_cmd, + "transmit-interval (10-60000)$interval", + "Configure peer transmit interval\n" + "Configure peer transmit interval value in milliseconds\n") +{ + char value[32]; + + snprintf(value, sizeof(value), "%ld", interval * 1000); + nb_cli_enqueue_change(vty, "./desired-transmission-interval", + NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_tx(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + uint32_t value = yang_dnode_get_uint32(dnode, NULL); + + vty_out(vty, " transmit-interval %u\n", value / 1000); +} + +DEFPY_YANG( + bfd_peer_echo, bfd_peer_echo_cmd, + "[no] echo-mode", + NO_STR + "Configure echo mode\n") +{ + if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) { + vty_out(vty, + "%% Echo mode is only available for single hop sessions.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!no && !bglobal.bg_use_dplane) { +#ifdef BFD_LINUX + vty_out(vty, + "%% Echo mode works correctly for IPv4, but only works when the peer is also FRR for IPv6.\n"); +#else + vty_out(vty, + "%% Current implementation of echo mode works only when the peer is also FRR.\n"); +#endif /* BFD_LINUX */ + } + + nb_cli_enqueue_change(vty, "./echo-mode", NB_OP_MODIFY, + no ? "false" : "true"); + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_echo(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " %secho-mode\n", + yang_dnode_get_bool(dnode, NULL) ? "" : "no "); +} + +DEFPY_YANG( + bfd_peer_echo_interval, bfd_peer_echo_interval_cmd, + "echo-interval (10-60000)$interval", + "Configure peer echo intervals\n" + "Configure peer echo rx/tx intervals value in milliseconds\n") +{ + char value[32]; + + if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) { + vty_out(vty, "%% Echo mode is only available for single hop sessions.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + snprintf(value, sizeof(value), "%ld", interval * 1000); + nb_cli_enqueue_change(vty, "./desired-echo-transmission-interval", + NB_OP_MODIFY, value); + nb_cli_enqueue_change(vty, "./required-echo-receive-interval", + NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + bfd_peer_echo_transmit_interval, bfd_peer_echo_transmit_interval_cmd, + "echo transmit-interval (10-60000)$interval", + "Configure peer echo intervals\n" + "Configure desired transmit interval\n" + "Configure interval value in milliseconds\n") +{ + char value[32]; + + if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) { + vty_out(vty, "%% Echo mode is only available for single hop sessions.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + snprintf(value, sizeof(value), "%ld", interval * 1000); + nb_cli_enqueue_change(vty, "./desired-echo-transmission-interval", + NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_desired_echo_transmission_interval( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + uint32_t value = yang_dnode_get_uint32(dnode, NULL); + + vty_out(vty, " echo transmit-interval %u\n", value / 1000); +} + +DEFPY_YANG( + bfd_peer_echo_receive_interval, bfd_peer_echo_receive_interval_cmd, + "echo receive-interval ", + "Configure peer echo intervals\n" + "Configure required receive interval\n" + "Disable echo packets receive\n" + "Configure interval value in milliseconds\n") +{ + char value[32]; + + if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) { + vty_out(vty, "%% Echo mode is only available for single hop sessions.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (disabled) + snprintf(value, sizeof(value), "0"); + else + snprintf(value, sizeof(value), "%ld", interval * 1000); + + nb_cli_enqueue_change(vty, "./required-echo-receive-interval", + NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_required_echo_receive_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + uint32_t value = yang_dnode_get_uint32(dnode, NULL); + + if (value) + vty_out(vty, " echo receive-interval %u\n", value / 1000); + else + vty_out(vty, " echo receive-interval disabled\n"); +} + +/* + * Profile commands. + */ +DEFPY_YANG_NOSH(bfd_profile, bfd_profile_cmd, + "profile BFDPROF$name", + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + char xpath[XPATH_MAXLEN]; + int rv; + + snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/profile[name='%s']", + name); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + /* Apply settings immediately. */ + rv = nb_cli_apply_changes(vty, NULL); + if (rv == CMD_SUCCESS) + VTY_PUSH_XPATH(BFD_PROFILE_NODE, xpath); + + return CMD_SUCCESS; +} + +DEFPY_YANG(no_bfd_profile, no_bfd_profile_cmd, + "no profile BFDPROF$name", + NO_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/profile[name='%s']", + name); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + /* Apply settings immediately. */ + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_show_profile(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " profile %s\n", yang_dnode_get_string(dnode, "name")); +} + +ALIAS_YANG(bfd_peer_mult, bfd_profile_mult_cmd, + "detect-multiplier (2-255)$multiplier", + "Configure peer detection multiplier\n" + "Configure peer detection multiplier value\n") + +ALIAS_YANG(bfd_peer_tx, bfd_profile_tx_cmd, + "transmit-interval (10-60000)$interval", + "Configure peer transmit interval\n" + "Configure peer transmit interval value in milliseconds\n") + +ALIAS_YANG(bfd_peer_rx, bfd_profile_rx_cmd, + "receive-interval (10-60000)$interval", + "Configure peer receive interval\n" + "Configure peer receive interval value in milliseconds\n") + +ALIAS_YANG(bfd_peer_shutdown, bfd_profile_shutdown_cmd, + "[no] shutdown", + NO_STR + "Disable BFD peer\n") + +ALIAS_YANG(bfd_peer_passive, bfd_profile_passive_cmd, + "[no] passive-mode", + NO_STR + "Don't attempt to start sessions\n") + +ALIAS_YANG(bfd_peer_minimum_ttl, bfd_profile_minimum_ttl_cmd, + "[no] minimum-ttl (1-254)$ttl", + NO_STR + "Expect packets with at least this TTL\n" + "Minimum TTL expected\n") + +ALIAS_YANG(no_bfd_peer_minimum_ttl, no_bfd_profile_minimum_ttl_cmd, + "no minimum-ttl", + NO_STR + "Expect packets with at least this TTL\n") + +ALIAS_YANG(bfd_peer_echo, bfd_profile_echo_cmd, + "[no] echo-mode", + NO_STR + "Configure echo mode\n") + +ALIAS_YANG(bfd_peer_echo_interval, bfd_profile_echo_interval_cmd, + "echo-interval (10-60000)$interval", + "Configure peer echo interval\n" + "Configure peer echo interval value in milliseconds\n") + +ALIAS_YANG( + bfd_peer_echo_transmit_interval, bfd_profile_echo_transmit_interval_cmd, + "echo transmit-interval (10-60000)$interval", + "Configure peer echo intervals\n" + "Configure desired transmit interval\n" + "Configure interval value in milliseconds\n") + +ALIAS_YANG( + bfd_peer_echo_receive_interval, bfd_profile_echo_receive_interval_cmd, + "echo receive-interval ", + "Configure peer echo intervals\n" + "Configure required receive interval\n" + "Disable echo packets receive\n" + "Configure interval value in milliseconds\n") + +DEFPY_YANG(bfd_peer_profile, bfd_peer_profile_cmd, + "[no] profile BFDPROF$pname", + NO_STR + "Use BFD profile settings\n" + BFD_PROFILE_NAME_STR) +{ + if (no) + nb_cli_enqueue_change(vty, "./profile", NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./profile", NB_OP_MODIFY, pname); + + return nb_cli_apply_changes(vty, NULL); +} + +void bfd_cli_peer_profile_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " profile %s\n", yang_dnode_get_string(dnode, NULL)); +} + +struct cmd_node bfd_profile_node = { + .name = "bfd profile", + .node = BFD_PROFILE_NODE, + .parent_node = BFD_NODE, + .prompt = "%s(config-bfd-profile)# ", +}; + +static void bfd_profile_var(vector comps, struct cmd_token *token) +{ + extern struct bfdproflist bplist; + struct bfd_profile *bp; + + TAILQ_FOREACH (bp, &bplist, entry) { + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, bp->name)); + } +} + +static const struct cmd_variable_handler bfd_vars[] = { + {.tokenname = "BFDPROF", .completions = bfd_profile_var}, + {.completions = NULL} +}; + +void +bfdd_cli_init(void) +{ + install_element(CONFIG_NODE, &bfd_enter_cmd); + install_element(CONFIG_NODE, &bfd_config_reset_cmd); + + install_element(BFD_NODE, &bfd_peer_enter_cmd); + install_element(BFD_NODE, &bfd_no_peer_cmd); + + install_element(BFD_PEER_NODE, &bfd_peer_shutdown_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_mult_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_rx_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_tx_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echo_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echo_interval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echo_transmit_interval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echo_receive_interval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_profile_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_passive_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_minimum_ttl_cmd); + install_element(BFD_PEER_NODE, &no_bfd_peer_minimum_ttl_cmd); + + /* Profile commands. */ + cmd_variable_handler_register(bfd_vars); + + install_node(&bfd_profile_node); + install_default(BFD_PROFILE_NODE); + + install_element(BFD_NODE, &bfd_profile_cmd); + install_element(BFD_NODE, &no_bfd_profile_cmd); + + install_element(BFD_PROFILE_NODE, &bfd_profile_mult_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_tx_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_rx_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_shutdown_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_echo_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_echo_interval_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_echo_transmit_interval_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_echo_receive_interval_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_passive_cmd); + install_element(BFD_PROFILE_NODE, &bfd_profile_minimum_ttl_cmd); + install_element(BFD_PROFILE_NODE, &no_bfd_profile_minimum_ttl_cmd); +} diff --git a/bfdd/bfdd_nb.c b/bfdd/bfdd_nb.c new file mode 100644 index 0000000..114fbc2 --- /dev/null +++ b/bfdd/bfdd_nb.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/log.h" +#include "lib/northbound.h" + +#include "bfdd_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_bfdd_info = { + .name = "frr-bfdd", + .nodes = { + { + .xpath = "/frr-bfdd:bfdd/bfd", + .cbs = { + .create = bfdd_bfd_create, + .destroy = bfdd_bfd_destroy, + .cli_show = bfd_cli_show_header, + .cli_show_end = bfd_cli_show_header_end, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile", + .cbs = { + .create = bfdd_bfd_profile_create, + .destroy = bfdd_bfd_profile_destroy, + .cli_show = bfd_cli_show_profile, + .cli_show_end = bfd_cli_show_peer_end, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/detection-multiplier", + .cbs = { + .modify = bfdd_bfd_profile_detection_multiplier_modify, + .cli_show = bfd_cli_show_mult, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/desired-transmission-interval", + .cbs = { + .modify = bfdd_bfd_profile_desired_transmission_interval_modify, + .cli_show = bfd_cli_show_tx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/required-receive-interval", + .cbs = { + .modify = bfdd_bfd_profile_required_receive_interval_modify, + .cli_show = bfd_cli_show_rx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/administrative-down", + .cbs = { + .modify = bfdd_bfd_profile_administrative_down_modify, + .cli_show = bfd_cli_show_shutdown, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/passive-mode", + .cbs = { + .modify = bfdd_bfd_profile_passive_mode_modify, + .cli_show = bfd_cli_show_passive, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/minimum-ttl", + .cbs = { + .modify = bfdd_bfd_profile_minimum_ttl_modify, + .cli_show = bfd_cli_show_minimum_ttl, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/echo-mode", + .cbs = { + .modify = bfdd_bfd_profile_echo_mode_modify, + .cli_show = bfd_cli_show_echo, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/desired-echo-transmission-interval", + .cbs = { + .modify = bfdd_bfd_profile_desired_echo_transmission_interval_modify, + .cli_show = bfd_cli_show_desired_echo_transmission_interval, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/profile/required-echo-receive-interval", + .cbs = { + .modify = bfdd_bfd_profile_required_echo_receive_interval_modify, + .cli_show = bfd_cli_show_required_echo_receive_interval, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop", + .cbs = { + .create = bfdd_bfd_sessions_single_hop_create, + .destroy = bfdd_bfd_sessions_single_hop_destroy, + .get_next = bfdd_bfd_sessions_single_hop_get_next, + .get_keys = bfdd_bfd_sessions_single_hop_get_keys, + .lookup_entry = bfdd_bfd_sessions_single_hop_lookup_entry, + .cli_show = bfd_cli_show_single_hop_peer, + .cli_show_end = bfd_cli_show_peer_end, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_source_addr_modify, + .destroy = bfdd_bfd_sessions_single_hop_source_addr_destroy, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/profile", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_profile_modify, + .destroy = bfdd_bfd_sessions_single_hop_profile_destroy, + .cli_show = bfd_cli_peer_profile_show, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify, + .cli_show = bfd_cli_show_mult, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify, + .cli_show = bfd_cli_show_tx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify, + .cli_show = bfd_cli_show_rx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify, + .cli_show = bfd_cli_show_shutdown, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/passive-mode", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_passive_mode_modify, + .cli_show = bfd_cli_show_passive, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_echo_mode_modify, + .cli_show = bfd_cli_show_echo, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify, + .cli_show = bfd_cli_show_desired_echo_transmission_interval, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/required-echo-receive-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify, + .cli_show = bfd_cli_show_required_echo_receive_interval, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop", + .cbs = { + .create = bfdd_bfd_sessions_multi_hop_create, + .destroy = bfdd_bfd_sessions_multi_hop_destroy, + .get_next = bfdd_bfd_sessions_multi_hop_get_next, + .get_keys = bfdd_bfd_sessions_multi_hop_get_keys, + .lookup_entry = bfdd_bfd_sessions_multi_hop_lookup_entry, + .cli_show = bfd_cli_show_multi_hop_peer, + .cli_show_end = bfd_cli_show_peer_end, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/profile", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_profile_modify, + .destroy = bfdd_bfd_sessions_single_hop_profile_destroy, + .cli_show = bfd_cli_peer_profile_show, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/detection-multiplier", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify, + .cli_show = bfd_cli_show_mult, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/desired-transmission-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify, + .cli_show = bfd_cli_show_tx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/required-receive-interval", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify, + .cli_show = bfd_cli_show_rx, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/administrative-down", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify, + .cli_show = bfd_cli_show_shutdown, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/passive-mode", + .cbs = { + .modify = bfdd_bfd_sessions_single_hop_passive_mode_modify, + .cli_show = bfd_cli_show_passive, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/minimum-ttl", + .cbs = { + .modify = bfdd_bfd_sessions_multi_hop_minimum_ttl_modify, + .cli_show = bfd_cli_show_minimum_ttl, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-discriminator", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-state", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-diagnostic", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-multiplier", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-discriminator", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-state", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-diagnostic", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-multiplier", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-transmission-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-receive-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/detection-mode", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-down-time", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-up-time", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-down-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-up-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-input-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-output-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-echo-transmission-interval", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-input-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem, + } + }, + { + .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-output-count", + .cbs = { + .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/bfdd/bfdd_nb.h b/bfdd/bfdd_nb.h new file mode 100644 index 0000000..b5b00b5 --- /dev/null +++ b/bfdd/bfdd_nb.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#ifndef _FRR_BFDD_NB_H_ +#define _FRR_BFDD_NB_H_ + +extern const struct frr_yang_module_info frr_bfdd_info; + +/* Mandatory callbacks. */ +int bfdd_bfd_create(struct nb_cb_create_args *args); +int bfdd_bfd_destroy(struct nb_cb_destroy_args *args); +int bfdd_bfd_profile_create(struct nb_cb_create_args *args); +int bfdd_bfd_profile_destroy(struct nb_cb_destroy_args *args); +int bfdd_bfd_profile_detection_multiplier_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_profile_desired_transmission_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_profile_required_receive_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_profile_administrative_down_modify(struct nb_cb_modify_args *args); +int bfdd_bfd_profile_passive_mode_modify(struct nb_cb_modify_args *args); +int bfdd_bfd_profile_minimum_ttl_modify(struct nb_cb_modify_args *args); +int bfdd_bfd_profile_echo_mode_modify(struct nb_cb_modify_args *args); +int bfdd_bfd_profile_desired_echo_transmission_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_profile_required_echo_receive_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_create(struct nb_cb_create_args *args); +int bfdd_bfd_sessions_single_hop_destroy(struct nb_cb_destroy_args *args); +const void * +bfdd_bfd_sessions_single_hop_get_next(struct nb_cb_get_next_args *args); +int bfdd_bfd_sessions_single_hop_get_keys(struct nb_cb_get_keys_args *args); +const void * +bfdd_bfd_sessions_single_hop_lookup_entry(struct nb_cb_lookup_entry_args *args); +int bfdd_bfd_sessions_single_hop_source_addr_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_source_addr_destroy( + struct nb_cb_destroy_args *args); +int bfdd_bfd_sessions_single_hop_profile_modify(struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_profile_destroy( + struct nb_cb_destroy_args *args); +int bfdd_bfd_sessions_single_hop_detection_multiplier_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_required_receive_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_administrative_down_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_passive_mode_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_echo_mode_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify( + struct nb_cb_modify_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_state_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args); +int bfdd_bfd_sessions_multi_hop_create(struct nb_cb_create_args *args); +int bfdd_bfd_sessions_multi_hop_destroy(struct nb_cb_destroy_args *args); +const void * +bfdd_bfd_sessions_multi_hop_get_next(struct nb_cb_get_next_args *args); +int bfdd_bfd_sessions_multi_hop_get_keys(struct nb_cb_get_keys_args *args); +const void * +bfdd_bfd_sessions_multi_hop_lookup_entry(struct nb_cb_lookup_entry_args *args); +int bfdd_bfd_sessions_multi_hop_detection_multiplier_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_multi_hop_desired_transmission_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_multi_hop_required_receive_interval_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_multi_hop_administrative_down_modify( + struct nb_cb_modify_args *args); +int bfdd_bfd_sessions_multi_hop_minimum_ttl_modify( + struct nb_cb_modify_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_local_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_state_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_diagnostic_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_multiplier_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_remote_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_state_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_diagnostic_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_multiplier_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_negotiated_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_negotiated_receive_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_detection_mode_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_last_down_time_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_last_up_time_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_session_down_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *bfdd_bfd_sessions_multi_hop_stats_session_up_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_control_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_control_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_negotiated_echo_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_echo_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +bfdd_bfd_sessions_multi_hop_stats_echo_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args); + +/* Optional 'cli_show' callbacks. */ +void bfd_cli_show_header(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_header_end(struct vty *vty, const struct lyd_node *dnode); +void bfd_cli_show_single_hop_peer(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_multi_hop_peer(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_peer_end(struct vty *vty, const struct lyd_node *dnode); +void bfd_cli_show_mult(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_tx(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_rx(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_echo(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_desired_echo_transmission_interval( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +void bfd_cli_show_required_echo_receive_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_profile(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_peer_profile_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_passive(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void bfd_cli_show_minimum_ttl(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + +#endif /* _FRR_BFDD_NB_H_ */ diff --git a/bfdd/bfdd_nb_config.c b/bfdd/bfdd_nb_config.c new file mode 100644 index 0000000..48fbe71 --- /dev/null +++ b/bfdd/bfdd_nb_config.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/log.h" +#include "lib/northbound.h" + +#include "bfd.h" +#include "bfdd_nb.h" + +/* + * Helpers. + */ +static void bfd_session_get_key(bool mhop, const struct lyd_node *dnode, + struct bfd_key *bk) +{ + const char *ifname = NULL, *vrfname = NULL; + struct sockaddr_any psa, lsa; + + /* Required destination parameter. */ + strtosa(yang_dnode_get_string(dnode, "dest-addr"), &psa); + + /* Get optional source address. */ + memset(&lsa, 0, sizeof(lsa)); + if (yang_dnode_exists(dnode, "source-addr")) + strtosa(yang_dnode_get_string(dnode, "source-addr"), &lsa); + + vrfname = yang_dnode_get_string(dnode, "vrf"); + + if (!mhop) { + ifname = yang_dnode_get_string(dnode, "interface"); + if (strcmp(ifname, "*") == 0) + ifname = NULL; + } + + /* Generate the corresponding key. */ + gen_bfd_key(bk, &psa, &lsa, mhop, ifname, vrfname); +} + +struct session_iter { + int count; + bool wildcard; +}; + +static int session_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct session_iter *iter = arg; + const char *ifname; + + ifname = yang_dnode_get_string(dnode, "interface"); + + if (strmatch(ifname, "*")) + iter->wildcard = true; + + iter->count++; + + return YANG_ITER_CONTINUE; +} + +static int bfd_session_create(struct nb_cb_create_args *args, bool mhop) +{ + const struct lyd_node *sess_dnode; + struct session_iter iter; + struct bfd_session *bs; + const char *dest; + const char *ifname; + const char *vrfname; + struct bfd_key bk; + struct prefix p; + + switch (args->event) { + case NB_EV_VALIDATE: + yang_dnode_get_prefix(&p, args->dnode, "dest-addr"); + + if (mhop) { + /* + * Do not allow IPv6 link-local address for multihop. + */ + if (p.family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + snprintf( + args->errmsg, args->errmsg_len, + "Cannot use link-local address for multihop sessions"); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + /* + * When `dest-addr` is IPv6 and link-local we must + * require interface name, otherwise we can't figure + * which interface to use to send the packets. + */ + ifname = yang_dnode_get_string(args->dnode, "interface"); + + if (p.family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6) + && strcmp(ifname, "*") == 0) { + snprintf( + args->errmsg, args->errmsg_len, + "When using link-local you must specify an interface"); + return NB_ERR_VALIDATION; + } + + iter.count = 0; + iter.wildcard = false; + + sess_dnode = yang_dnode_get_parent(args->dnode, "sessions"); + + dest = yang_dnode_get_string(args->dnode, "dest-addr"); + vrfname = yang_dnode_get_string(args->dnode, "vrf"); + + yang_dnode_iterate(session_iter_cb, &iter, sess_dnode, + "./single-hop[dest-addr='%s'][vrf='%s']", + dest, vrfname); + + if (iter.wildcard && iter.count > 1) { + snprintf( + args->errmsg, args->errmsg_len, + "It is not allowed to configure the same peer with and without ifname"); + return NB_ERR_VALIDATION; + } + break; + + case NB_EV_PREPARE: + bfd_session_get_key(mhop, args->dnode, &bk); + bs = bfd_key_lookup(bk); + + /* This session was already configured by another daemon. */ + if (bs != NULL) { + /* Now it is configured also by CLI. */ + SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); + bs->refcount++; + + args->resource->ptr = bs; + break; + } + + bs = bfd_session_new(); + + /* Fill the session key. */ + bfd_session_get_key(mhop, args->dnode, &bs->key); + + /* Set configuration flags. */ + bs->refcount = 1; + SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); + if (mhop) + SET_FLAG(bs->flags, BFD_SESS_FLAG_MH); + if (bs->key.family == AF_INET6) + SET_FLAG(bs->flags, BFD_SESS_FLAG_IPV6); + + args->resource->ptr = bs; + break; + + case NB_EV_APPLY: + bs = args->resource->ptr; + + /* Only attempt to registrate if freshly allocated. */ + if (bs->discrs.my_discr == 0 && bs_registrate(bs) == NULL) + return NB_ERR_RESOURCE; + + nb_running_set_entry(args->dnode, bs); + break; + + case NB_EV_ABORT: + bs = args->resource->ptr; + if (bs->refcount <= 1) + bfd_session_free(bs); + break; + } + + return NB_OK; +} + +static int bfd_session_destroy(enum nb_event event, + const struct lyd_node *dnode, bool mhop) +{ + struct bfd_session *bs; + struct bfd_key bk; + + switch (event) { + case NB_EV_VALIDATE: + bfd_session_get_key(mhop, dnode, &bk); + if (bfd_key_lookup(bk) == NULL) + return NB_ERR_INCONSISTENCY; + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_unset_entry(dnode); + /* CLI is not using this session anymore. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0) + break; + + UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG); + bs->refcount--; + /* There are still daemons using it. */ + if (bs->refcount > 0) + break; + + bfd_session_free(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd + */ +int bfdd_bfd_create(struct nb_cb_create_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* + * Set any non-NULL value to be able to call + * nb_running_unset_entry in bfdd_bfd_destroy. + */ + nb_running_set_entry(args->dnode, (void *)0x1); + + return NB_OK; +} + +int bfdd_bfd_destroy(struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* NOTHING */ + return NB_OK; + + case NB_EV_PREPARE: + /* NOTHING */ + return NB_OK; + + case NB_EV_APPLY: + /* + * We need to call this to unset pointers from + * the child nodes - sessions and profiles. + */ + nb_running_unset_entry(args->dnode); + + bfd_sessions_remove_manual(); + bfd_profiles_remove(); + break; + + case NB_EV_ABORT: + /* NOTHING */ + return NB_OK; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile + */ +int bfdd_bfd_profile_create(struct nb_cb_create_args *args) +{ + struct bfd_profile *bp; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "name"); + bp = bfd_profile_new(name); + nb_running_set_entry(args->dnode, bp); + + return NB_OK; +} + +int bfdd_bfd_profile_destroy(struct nb_cb_destroy_args *args) +{ + struct bfd_profile *bp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bp = nb_running_unset_entry(args->dnode); + bfd_profile_free(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/detection-multiplier + */ +int bfdd_bfd_profile_detection_multiplier_modify(struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->detection_multiplier = yang_dnode_get_uint8(args->dnode, NULL); + bfd_profile_update(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/desired-transmission-interval + */ +int bfdd_bfd_profile_desired_transmission_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->min_tx = yang_dnode_get_uint32(args->dnode, NULL); + bfd_profile_update(bp); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/required-receive-interval + */ +int bfdd_bfd_profile_required_receive_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->min_rx = yang_dnode_get_uint32(args->dnode, NULL); + bfd_profile_update(bp); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/administrative-down + */ +int bfdd_bfd_profile_administrative_down_modify(struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->admin_shutdown = yang_dnode_get_bool(args->dnode, NULL); + bfd_profile_update(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/passive-mode + */ +int bfdd_bfd_profile_passive_mode_modify(struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->passive = yang_dnode_get_bool(args->dnode, NULL); + bfd_profile_update(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/minimum-ttl + */ +int bfdd_bfd_profile_minimum_ttl_modify(struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->minimum_ttl = yang_dnode_get_uint8(args->dnode, NULL); + bfd_profile_update(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/echo-mode + */ +int bfdd_bfd_profile_echo_mode_modify(struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + bool echo; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + echo = yang_dnode_get_bool(args->dnode, NULL); + bp = nb_running_get_entry(args->dnode, NULL, true); + if (bp->echo_mode == echo) + return NB_OK; + + bp->echo_mode = echo; + bfd_profile_update(bp); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/desired-echo-transmission-interval + */ +int bfdd_bfd_profile_desired_echo_transmission_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->min_echo_tx = yang_dnode_get_uint32(args->dnode, NULL); + bfd_profile_update(bp); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/profile/required-echo-receive-interval + */ +int bfdd_bfd_profile_required_echo_receive_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_profile *bp; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bp = nb_running_get_entry(args->dnode, NULL, true); + bp->min_echo_rx = yang_dnode_get_uint32(args->dnode, NULL); + bfd_profile_update(bp); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop + */ +int bfdd_bfd_sessions_single_hop_create(struct nb_cb_create_args *args) +{ + return bfd_session_create(args, false); +} + +int bfdd_bfd_sessions_single_hop_destroy(struct nb_cb_destroy_args *args) +{ + return bfd_session_destroy(args->event, args->dnode, false); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr + */ +int bfdd_bfd_sessions_single_hop_source_addr_modify( + struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +int bfdd_bfd_sessions_single_hop_source_addr_destroy( + struct nb_cb_destroy_args *args) +{ + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/profile + */ +int bfdd_bfd_sessions_single_hop_profile_modify(struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bs = nb_running_get_entry(args->dnode, NULL, true); + bfd_profile_apply(yang_dnode_get_string(args->dnode, NULL), bs); + + return NB_OK; +} + +int bfdd_bfd_sessions_single_hop_profile_destroy( + struct nb_cb_destroy_args *args) +{ + struct bfd_session *bs; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + bs = nb_running_get_entry(args->dnode, NULL, true); + bfd_profile_remove(bs); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier + */ +int bfdd_bfd_sessions_single_hop_detection_multiplier_modify( + struct nb_cb_modify_args *args) +{ + uint8_t detection_multiplier = yang_dnode_get_uint8(args->dnode, NULL); + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.detection_multiplier = detection_multiplier; + bfd_session_apply(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval + */ +int bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.min_tx = + yang_dnode_get_uint32(args->dnode, NULL); + bfd_session_apply(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval + */ +int bfdd_bfd_sessions_single_hop_required_receive_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.min_rx = + yang_dnode_get_uint32(args->dnode, NULL); + bfd_session_apply(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down + */ +int bfdd_bfd_sessions_single_hop_administrative_down_modify( + struct nb_cb_modify_args *args) +{ + bool shutdown = yang_dnode_get_bool(args->dnode, NULL); + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + return NB_OK; + + case NB_EV_APPLY: + break; + + case NB_EV_ABORT: + return NB_OK; + } + + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.admin_shutdown = shutdown; + bfd_session_apply(bs); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/passive-mode + */ +int bfdd_bfd_sessions_single_hop_passive_mode_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + bool passive; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + return NB_OK; + + case NB_EV_APPLY: + break; + + case NB_EV_ABORT: + return NB_OK; + } + + passive = yang_dnode_get_bool(args->dnode, NULL); + + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.passive = passive; + bfd_session_apply(bs); + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode + */ +int bfdd_bfd_sessions_single_hop_echo_mode_modify( + struct nb_cb_modify_args *args) +{ + bool echo = yang_dnode_get_bool(args->dnode, NULL); + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + return NB_OK; + + case NB_EV_APPLY: + break; + + case NB_EV_ABORT: + return NB_OK; + } + + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.echo_mode = echo; + bfd_session_apply(bs); + + return NB_OK; +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval + */ +int bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.min_echo_tx = + yang_dnode_get_uint32(args->dnode, NULL); + bfd_session_apply(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/required-echo-receive-interval + */ +int bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + break; + + case NB_EV_PREPARE: + /* NOTHING */ + break; + + case NB_EV_APPLY: + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.min_echo_rx = + yang_dnode_get_uint32(args->dnode, NULL); + bfd_session_apply(bs); + break; + + case NB_EV_ABORT: + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop + */ +int bfdd_bfd_sessions_multi_hop_create(struct nb_cb_create_args *args) +{ + return bfd_session_create(args, true); +} + +int bfdd_bfd_sessions_multi_hop_destroy(struct nb_cb_destroy_args *args) +{ + return bfd_session_destroy(args->event, args->dnode, true); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop/minimum-ttl + */ +int bfdd_bfd_sessions_multi_hop_minimum_ttl_modify( + struct nb_cb_modify_args *args) +{ + struct bfd_session *bs; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + return NB_OK; + + case NB_EV_APPLY: + break; + + case NB_EV_ABORT: + return NB_OK; + } + + bs = nb_running_get_entry(args->dnode, NULL, true); + bs->peer_profile.minimum_ttl = yang_dnode_get_uint8(args->dnode, NULL); + bfd_session_apply(bs); + + return NB_OK; +} diff --git a/bfdd/bfdd_nb_state.c b/bfdd/bfdd_nb_state.c new file mode 100644 index 0000000..12acda8 --- /dev/null +++ b/bfdd/bfdd_nb_state.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/log.h" +#include "lib/northbound.h" + +#include "bfd.h" +#include "bfdd_nb.h" + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop + */ +const void * +bfdd_bfd_sessions_single_hop_get_next(struct nb_cb_get_next_args *args) +{ + return bfd_session_next(args->list_entry, false); +} + +int bfdd_bfd_sessions_single_hop_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct bfd_session *bs = args->list_entry; + char dstbuf[INET6_ADDRSTRLEN]; + + inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf)); + + args->keys->num = 3; + strlcpy(args->keys->key[0], dstbuf, sizeof(args->keys->key[0])); + strlcpy(args->keys->key[1], bs->key.ifname, sizeof(args->keys->key[1])); + strlcpy(args->keys->key[2], bs->key.vrfname, + sizeof(args->keys->key[2])); + + return NB_OK; +} + +const void * +bfdd_bfd_sessions_single_hop_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *dest_addr = args->keys->key[0]; + const char *ifname = args->keys->key[1]; + const char *vrf = args->keys->key[2]; + struct sockaddr_any psa, lsa; + struct bfd_key bk; + + strtosa(dest_addr, &psa); + memset(&lsa, 0, sizeof(lsa)); + gen_bfd_key(&bk, &psa, &lsa, false, ifname, vrf); + + return bfd_key_lookup(bk); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint32(args->xpath, bs->discrs.my_discr); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_state_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_enum(args->xpath, bs->ses_state); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_enum(args->xpath, bs->local_diag); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_int8(args->xpath, bs->detect_mult); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + if (bs->discrs.remote_discr == 0) + return NULL; + + return yang_data_new_uint32(args->xpath, bs->discrs.remote_discr); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_enum(args->xpath, bs->ses_state); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_enum(args->xpath, bs->remote_diag); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_int8(args->xpath, bs->remote_detect_mult); +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint32(args->xpath, + bs->remote_timers.desired_min_tx); +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint32(args->xpath, + bs->remote_timers.required_min_rx); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + int detection_mode; + + /* + * Detection mode: + * 1. Async with echo + * 2. Async without echo + * 3. Demand with echo + * 4. Demand without echo + * + * TODO: support demand mode. + */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + detection_mode = 1; + else + detection_mode = 2; + + return yang_data_new_enum(args->xpath, detection_mode); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* + * TODO: implement me. + * + * No yang support for time elements yet. + */ + return NULL; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* + * TODO: implement me. + * + * No yang support for time elements yet. + */ + return NULL; +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.session_down); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count + */ +struct yang_data *bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.session_up); +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.rx_ctrl_pkt); +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.tx_ctrl_pkt); +} + +/* + * XPath: + * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint32(args->xpath, + bs->remote_timers.required_min_echo); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.rx_echo_pkt); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count + */ +struct yang_data * +bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct bfd_session *bs = args->list_entry; + + return yang_data_new_uint64(args->xpath, bs->stats.tx_echo_pkt); +} + +/* + * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop + */ +const void * +bfdd_bfd_sessions_multi_hop_get_next(struct nb_cb_get_next_args *args) +{ + return bfd_session_next(args->list_entry, true); +} + +int bfdd_bfd_sessions_multi_hop_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct bfd_session *bs = args->list_entry; + char dstbuf[INET6_ADDRSTRLEN], srcbuf[INET6_ADDRSTRLEN]; + + inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf)); + inet_ntop(bs->key.family, &bs->key.local, srcbuf, sizeof(srcbuf)); + + args->keys->num = 4; + strlcpy(args->keys->key[0], srcbuf, sizeof(args->keys->key[0])); + strlcpy(args->keys->key[1], dstbuf, sizeof(args->keys->key[1])); + strlcpy(args->keys->key[2], bs->key.vrfname, + sizeof(args->keys->key[2])); + + return NB_OK; +} + +const void * +bfdd_bfd_sessions_multi_hop_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *source_addr = args->keys->key[0]; + const char *dest_addr = args->keys->key[1]; + const char *vrf = args->keys->key[2]; + struct sockaddr_any psa, lsa; + struct bfd_key bk; + + strtosa(dest_addr, &psa); + strtosa(source_addr, &lsa); + gen_bfd_key(&bk, &psa, &lsa, true, NULL, vrf); + + return bfd_key_lookup(bk); +} diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c new file mode 100644 index 0000000..496d501 --- /dev/null +++ b/bfdd/bfdd_vty.c @@ -0,0 +1,1051 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + */ + +#include + +#include "lib/command.h" +#include "lib/json.h" +#include "lib/log.h" +#include "lib/northbound_cli.h" +#include "lib/vty.h" + +#include "bfd.h" + +#include "bfdd/bfdd_vty_clippy.c" + +/* + * Commands help string definitions. + */ +#define PEER_IPV4_STR "IPv4 peer address\n" +#define PEER_IPV6_STR "IPv6 peer address\n" +#define MHOP_STR "Configure multihop\n" +#define LOCAL_STR "Configure local address\n" +#define LOCAL_IPV4_STR "IPv4 local address\n" +#define LOCAL_IPV6_STR "IPv6 local address\n" +#define LOCAL_INTF_STR "Configure local interface name to use\n" + +/* + * Prototypes + */ +static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, + const struct sockaddr_any *peer, + const struct sockaddr_any *local, + const char *ifname, const char *vrfname, + char *ebuf, size_t ebuflen); + +static void _display_peer_header(struct vty *vty, struct bfd_session *bs); +static struct json_object *__display_peer_json(struct bfd_session *bs); +static struct json_object *_peer_json_header(struct bfd_session *bs); +static void _display_peer_json(struct vty *vty, struct bfd_session *bs); +static void _display_peer(struct vty *vty, struct bfd_session *bs); +static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json); +static void _display_peer_iter(struct hash_bucket *hb, void *arg); +static void _display_peer_json_iter(struct hash_bucket *hb, void *arg); +static void _display_peer_counter(struct vty *vty, struct bfd_session *bs); +static struct json_object *__display_peer_counters_json(struct bfd_session *bs); +static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs); +static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg); +static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg); +static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json); +static void _display_rtt(uint32_t *min, uint32_t *avg, uint32_t *max, + struct bfd_session *bs); + +static struct bfd_session * +_find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv, + const char *label, const char *peer_str, + const char *local_str, const char *ifname, + const char *vrfname); + + +/* + * Show commands helper functions + */ +static void _display_peer_header(struct vty *vty, struct bfd_session *bs) +{ + char addr_buf[INET6_ADDRSTRLEN]; + + vty_out(vty, "\tpeer %s", + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, + sizeof(addr_buf))); + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + vty_out(vty, " multihop"); + + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + vty_out(vty, " local-address %s", + inet_ntop(bs->key.family, &bs->key.local, addr_buf, + sizeof(addr_buf))); + + if (bs->key.vrfname[0]) + vty_out(vty, " vrf %s", bs->key.vrfname); + if (bs->key.ifname[0]) + vty_out(vty, " interface %s", bs->key.ifname); + vty_out(vty, "\n"); + + if (bs->pl) + vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label); +} + +static void _display_peer(struct vty *vty, struct bfd_session *bs) +{ + char buf[256]; + time_t now; + uint32_t min = 0; + uint32_t avg = 0; + uint32_t max = 0; + + _display_peer_header(vty, bs); + + vty_out(vty, "\t\tID: %u\n", bs->discrs.my_discr); + vty_out(vty, "\t\tRemote ID: %u\n", bs->discrs.remote_discr); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE)) + vty_out(vty, "\t\tPassive mode\n"); + else + vty_out(vty, "\t\tActive mode\n"); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + vty_out(vty, "\t\tMinimum TTL: %d\n", bs->mh_ttl); + + vty_out(vty, "\t\tStatus: "); + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + vty_out(vty, "shutdown\n"); + break; + case PTM_BFD_DOWN: + vty_out(vty, "down\n"); + + now = monotime(NULL); + integer2timestr(now - bs->downtime.tv_sec, buf, sizeof(buf)); + vty_out(vty, "\t\tDowntime: %s\n", buf); + break; + case PTM_BFD_INIT: + vty_out(vty, "init\n"); + break; + case PTM_BFD_UP: + vty_out(vty, "up\n"); + + now = monotime(NULL); + integer2timestr(now - bs->uptime.tv_sec, buf, sizeof(buf)); + vty_out(vty, "\t\tUptime: %s\n", buf); + break; + + default: + vty_out(vty, "unknown\n"); + break; + } + + vty_out(vty, "\t\tDiagnostics: %s\n", diag2str(bs->local_diag)); + vty_out(vty, "\t\tRemote diagnostics: %s\n", diag2str(bs->remote_diag)); + vty_out(vty, "\t\tPeer Type: %s\n", + CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) ? "configured" : "dynamic"); + _display_rtt(&min, &avg, &max, bs); + vty_out(vty, "\t\tRTT min/avg/max: %u/%u/%u usec\n", min, avg, max); + + vty_out(vty, "\t\tLocal timers:\n"); + vty_out(vty, "\t\t\tDetect-multiplier: %u\n", + bs->detect_mult); + vty_out(vty, "\t\t\tReceive interval: %ums\n", + bs->timers.required_min_rx / 1000); + vty_out(vty, "\t\t\tTransmission interval: %ums\n", + bs->timers.desired_min_tx / 1000); + if (bs->timers.required_min_echo_rx != 0) + vty_out(vty, "\t\t\tEcho receive interval: %ums\n", + bs->timers.required_min_echo_rx / 1000); + else + vty_out(vty, "\t\t\tEcho receive interval: disabled\n"); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + vty_out(vty, "\t\t\tEcho transmission interval: %ums\n", + bs->timers.desired_min_echo_tx / 1000); + else + vty_out(vty, "\t\t\tEcho transmission interval: disabled\n"); + + vty_out(vty, "\t\tRemote timers:\n"); + vty_out(vty, "\t\t\tDetect-multiplier: %u\n", + bs->remote_detect_mult); + vty_out(vty, "\t\t\tReceive interval: %ums\n", + bs->remote_timers.required_min_rx / 1000); + vty_out(vty, "\t\t\tTransmission interval: %ums\n", + bs->remote_timers.desired_min_tx / 1000); + if (bs->remote_timers.required_min_echo != 0) + vty_out(vty, "\t\t\tEcho receive interval: %ums\n", + bs->remote_timers.required_min_echo / 1000); + else + vty_out(vty, "\t\t\tEcho receive interval: disabled\n"); + + vty_out(vty, "\n"); +} + +static struct json_object *_peer_json_header(struct bfd_session *bs) +{ + struct json_object *jo = json_object_new_object(); + char addr_buf[INET6_ADDRSTRLEN]; + + if (bs->key.mhop) + json_object_boolean_true_add(jo, "multihop"); + else + json_object_boolean_false_add(jo, "multihop"); + + json_object_string_add(jo, "peer", + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + json_object_string_add(jo, "local", + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + + if (bs->key.vrfname[0]) + json_object_string_add(jo, "vrf", bs->key.vrfname); + if (bs->key.ifname[0]) + json_object_string_add(jo, "interface", bs->key.ifname); + + if (bs->pl) + json_object_string_add(jo, "label", bs->pl->pl_label); + + return jo; +} + +static struct json_object *__display_peer_json(struct bfd_session *bs) +{ + struct json_object *jo = _peer_json_header(bs); + uint32_t min = 0; + uint32_t avg = 0; + uint32_t max = 0; + + if (bs->key.ifname[0]) + json_object_string_add(jo, "interface", bs->key.ifname); + json_object_int_add(jo, "id", bs->discrs.my_discr); + json_object_int_add(jo, "remote-id", bs->discrs.remote_discr); + json_object_boolean_add(jo, "passive-mode", + CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE)); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + json_object_int_add(jo, "minimum-ttl", bs->mh_ttl); + + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + json_object_string_add(jo, "status", "shutdown"); + break; + case PTM_BFD_DOWN: + json_object_string_add(jo, "status", "down"); + json_object_int_add(jo, "downtime", + monotime(NULL) - bs->downtime.tv_sec); + break; + case PTM_BFD_INIT: + json_object_string_add(jo, "status", "init"); + break; + case PTM_BFD_UP: + json_object_string_add(jo, "status", "up"); + json_object_int_add(jo, "uptime", + monotime(NULL) - bs->uptime.tv_sec); + break; + + default: + json_object_string_add(jo, "status", "unknown"); + break; + } + + json_object_string_add(jo, "diagnostic", diag2str(bs->local_diag)); + json_object_string_add(jo, "remote-diagnostic", + diag2str(bs->remote_diag)); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG)) + json_object_string_add(jo, "type", "configured"); + else + json_object_string_add(jo, "type", "dynamic"); + + json_object_int_add(jo, "receive-interval", + bs->timers.required_min_rx / 1000); + json_object_int_add(jo, "transmit-interval", + bs->timers.desired_min_tx / 1000); + json_object_int_add(jo, "echo-receive-interval", + bs->timers.required_min_echo_rx / 1000); + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + json_object_int_add(jo, "echo-transmit-interval", + bs->timers.desired_min_echo_tx / 1000); + else + json_object_int_add(jo, "echo-transmit-interval", 0); + + json_object_int_add(jo, "detect-multiplier", bs->detect_mult); + + json_object_int_add(jo, "remote-receive-interval", + bs->remote_timers.required_min_rx / 1000); + json_object_int_add(jo, "remote-transmit-interval", + bs->remote_timers.desired_min_tx / 1000); + json_object_int_add(jo, "remote-echo-receive-interval", + bs->remote_timers.required_min_echo / 1000); + json_object_int_add(jo, "remote-detect-multiplier", + bs->remote_detect_mult); + + _display_rtt(&min, &avg, &max, bs); + json_object_int_add(jo, "rtt-min", min); + json_object_int_add(jo, "rtt-avg", avg); + json_object_int_add(jo, "rtt-max", max); + + return jo; +} + +static void _display_peer_json(struct vty *vty, struct bfd_session *bs) +{ + struct json_object *jo = __display_peer_json(bs); + + vty_json(vty, jo); +} + +struct bfd_vrf_tuple { + const char *vrfname; + struct vty *vty; + struct json_object *jo; +}; + +static void _display_peer_iter(struct hash_bucket *hb, void *arg) +{ + struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg; + struct vty *vty; + struct bfd_session *bs = hb->data; + + if (!bvt) + return; + vty = bvt->vty; + + if (bvt->vrfname) { + if (!bs->key.vrfname[0] || + !strmatch(bs->key.vrfname, bvt->vrfname)) + return; + } + _display_peer(vty, bs); +} + +static void _display_peer_json_iter(struct hash_bucket *hb, void *arg) +{ + struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg; + struct json_object *jo, *jon = NULL; + struct bfd_session *bs = hb->data; + + if (!bvt) + return; + jo = bvt->jo; + + if (bvt->vrfname) { + if (!bs->key.vrfname[0] || + !strmatch(bs->key.vrfname, bvt->vrfname)) + return; + } + + jon = __display_peer_json(bs); + if (jon == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + json_object_array_add(jo, jon); +} + +static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json) +{ + struct json_object *jo; + struct bfd_vrf_tuple bvt = {0}; + + bvt.vrfname = vrfname; + + if (!use_json) { + bvt.vty = vty; + vty_out(vty, "BFD Peers:\n"); + bfd_id_iterate(_display_peer_iter, &bvt); + return; + } + + jo = json_object_new_array(); + bvt.jo = jo; + bfd_id_iterate(_display_peer_json_iter, &bvt); + + vty_json(vty, jo); +} + +static void _display_peer_counter(struct vty *vty, struct bfd_session *bs) +{ + _display_peer_header(vty, bs); + + /* Ask data plane for updated counters. */ + if (bfd_dplane_update_session_counters(bs) == -1) + zlog_debug("%s: failed to update BFD session counters (%s)", + __func__, bs_to_string(bs)); + + vty_out(vty, "\t\tControl packet input: %" PRIu64 " packets\n", + bs->stats.rx_ctrl_pkt); + vty_out(vty, "\t\tControl packet output: %" PRIu64 " packets\n", + bs->stats.tx_ctrl_pkt); + vty_out(vty, "\t\tEcho packet input: %" PRIu64 " packets\n", + bs->stats.rx_echo_pkt); + vty_out(vty, "\t\tEcho packet output: %" PRIu64 " packets\n", + bs->stats.tx_echo_pkt); + vty_out(vty, "\t\tSession up events: %" PRIu64 "\n", + bs->stats.session_up); + vty_out(vty, "\t\tSession down events: %" PRIu64 "\n", + bs->stats.session_down); + vty_out(vty, "\t\tZebra notifications: %" PRIu64 "\n", + bs->stats.znotification); + vty_out(vty, "\n"); +} + +static struct json_object *__display_peer_counters_json(struct bfd_session *bs) +{ + struct json_object *jo = _peer_json_header(bs); + + /* Ask data plane for updated counters. */ + if (bfd_dplane_update_session_counters(bs) == -1) + zlog_debug("%s: failed to update BFD session counters (%s)", + __func__, bs_to_string(bs)); + + json_object_int_add(jo, "control-packet-input", bs->stats.rx_ctrl_pkt); + json_object_int_add(jo, "control-packet-output", bs->stats.tx_ctrl_pkt); + json_object_int_add(jo, "echo-packet-input", bs->stats.rx_echo_pkt); + json_object_int_add(jo, "echo-packet-output", bs->stats.tx_echo_pkt); + json_object_int_add(jo, "session-up", bs->stats.session_up); + json_object_int_add(jo, "session-down", bs->stats.session_down); + json_object_int_add(jo, "zebra-notifications", bs->stats.znotification); + + return jo; +} + +static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs) +{ + struct json_object *jo = __display_peer_counters_json(bs); + + vty_json(vty, jo); +} + +static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg) +{ + struct bfd_vrf_tuple *bvt = arg; + struct vty *vty; + struct bfd_session *bs = hb->data; + + if (!bvt) + return; + vty = bvt->vty; + + if (bvt->vrfname) { + if (!bs->key.vrfname[0] || + !strmatch(bs->key.vrfname, bvt->vrfname)) + return; + } + + _display_peer_counter(vty, bs); +} + +static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg) +{ + struct json_object *jo, *jon = NULL; + struct bfd_session *bs = hb->data; + struct bfd_vrf_tuple *bvt = arg; + + if (!bvt) + return; + jo = bvt->jo; + + if (bvt->vrfname) { + if (!bs->key.vrfname[0] || + !strmatch(bs->key.vrfname, bvt->vrfname)) + return; + } + + jon = __display_peer_counters_json(bs); + if (jon == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + json_object_array_add(jo, jon); +} + +static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json) +{ + struct json_object *jo; + struct bfd_vrf_tuple bvt = {0}; + + bvt.vrfname = vrfname; + if (!use_json) { + bvt.vty = vty; + vty_out(vty, "BFD Peers:\n"); + bfd_id_iterate(_display_peer_counter_iter, &bvt); + return; + } + + jo = json_object_new_array(); + bvt.jo = jo; + bfd_id_iterate(_display_peer_counter_json_iter, &bvt); + + vty_json(vty, jo); +} + +static void _clear_peer_counter(struct bfd_session *bs) +{ + /* Clear only pkt stats, intention is not to loose system + events counters */ + bs->stats.rx_ctrl_pkt = 0; + bs->stats.tx_ctrl_pkt = 0; + bs->stats.rx_echo_pkt = 0; + bs->stats.tx_echo_pkt = 0; +} + +static void _display_peer_brief(struct vty *vty, struct bfd_session *bs) +{ + char addr_buf[INET6_ADDRSTRLEN]; + + vty_out(vty, "%-10u", bs->discrs.my_discr); + inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf)); + vty_out(vty, " %-40s", addr_buf); + inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf)); + vty_out(vty, " %-40s", addr_buf); + vty_out(vty, "%-15s\n", state_list[bs->ses_state].str); +} + +static void _display_peer_brief_iter(struct hash_bucket *hb, void *arg) +{ + struct bfd_vrf_tuple *bvt = arg; + struct vty *vty; + struct bfd_session *bs = hb->data; + + if (!bvt) + return; + vty = bvt->vty; + + if (bvt->vrfname) { + if (!bs->key.vrfname[0] || + !strmatch(bs->key.vrfname, bvt->vrfname)) + return; + } + + _display_peer_brief(vty, bs); +} + +static void _display_peers_brief(struct vty *vty, const char *vrfname, bool use_json) +{ + struct json_object *jo; + struct bfd_vrf_tuple bvt = {0}; + + bvt.vrfname = vrfname; + + if (!use_json) { + bvt.vty = vty; + + vty_out(vty, "Session count: %lu\n", bfd_get_session_count()); + vty_out(vty, "%-10s", "SessionId"); + vty_out(vty, " %-40s", "LocalAddress"); + vty_out(vty, " %-40s", "PeerAddress"); + vty_out(vty, "%-15s\n", "Status"); + + vty_out(vty, "%-10s", "========="); + vty_out(vty, " %-40s", "============"); + vty_out(vty, " %-40s", "==========="); + vty_out(vty, "%-15s\n", "======"); + + bfd_id_iterate(_display_peer_brief_iter, &bvt); + return; + } + + jo = json_object_new_array(); + bvt.jo = jo; + + bfd_id_iterate(_display_peer_json_iter, &bvt); + + vty_json(vty, jo); +} + +static struct bfd_session * +_find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv, + const char *label, const char *peer_str, + const char *local_str, const char *ifname, + const char *vrfname) +{ + int idx; + bool mhop; + struct bfd_session *bs = NULL; + struct peer_label *pl; + struct bfd_peer_cfg bpc; + struct sockaddr_any psa, lsa, *lsap; + char errormsg[128]; + + /* Look up the BFD peer. */ + if (label) { + pl = pl_find(label); + if (pl) + bs = pl->pl_bs; + } else if (peer_str) { + strtosa(peer_str, &psa); + if (local_str) { + strtosa(local_str, &lsa); + lsap = &lsa; + } else + lsap = NULL; + + idx = 0; + mhop = argv_find(argv, argc, "multihop", &idx); + + if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname, + errormsg, sizeof(errormsg)) + != 0) { + vty_out(vty, "%% Invalid peer configuration: %s\n", + errormsg); + return NULL; + } + + bs = bs_peer_find(&bpc); + } else { + vty_out(vty, "%% Invalid arguments\n"); + return NULL; + } + + /* Find peer data. */ + if (bs == NULL) { + vty_out(vty, "%% Unable to find 'peer %s", + label ? label : peer_str); + if (ifname) + vty_out(vty, " interface %s", ifname); + if (local_str) + vty_out(vty, " local-address %s", local_str); + if (vrfname) + vty_out(vty, " vrf %s", vrfname); + vty_out(vty, "'\n"); + + return NULL; + } + + return bs; +} + +void _display_rtt(uint32_t *min, uint32_t *avg, uint32_t *max, + struct bfd_session *bs) +{ +#ifdef BFD_LINUX + uint8_t i; + uint32_t average = 0; + + if (bs->rtt_valid == 0) + return; + + *max = bs->rtt[0]; + *min = 1000; + *avg = 0; + + for (i = 0; i < bs->rtt_valid; i++) { + if (bs->rtt[i] < *min) + *min = bs->rtt[i]; + if (bs->rtt[i] > *max) + *max = bs->rtt[i]; + average += bs->rtt[i]; + } + *avg = average / bs->rtt_valid; + +#endif +} + +/* + * Show commands. + */ +DEFPY(bfd_show_peers, bfd_show_peers_cmd, "show bfd [vrf NAME] peers [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" JSON_STR) +{ + char *vrf_name = NULL; + int idx_vrf = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + + _display_all_peers(vty, vrf_name, use_json(argc, argv)); + + return CMD_SUCCESS; +} + +DEFPY(bfd_show_peer, bfd_show_peer_cmd, + "show bfd [vrf NAME$vrf_name] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" + "Peer label\n" PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR + LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR JSON_STR) +{ + struct bfd_session *bs; + + /* Look up the BFD peer. */ + bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str, + ifname, vrf_name); + if (bs == NULL) + return CMD_WARNING_CONFIG_FAILED; + + if (use_json(argc, argv)) { + _display_peer_json(vty, bs); + } else { + vty_out(vty, "BFD Peer:\n"); + _display_peer(vty, bs); + } + + return CMD_SUCCESS; +} + +DEFPY(bfd_show_peer_counters, bfd_show_peer_counters_cmd, + "show bfd [vrf NAME$vrf_name] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> counters [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" + "Peer label\n" + PEER_IPV4_STR + PEER_IPV6_STR + MHOP_STR + LOCAL_STR + LOCAL_IPV4_STR + LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + "Show BFD peer counters information\n" + JSON_STR) +{ + struct bfd_session *bs; + + /* Look up the BFD peer. */ + bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str, + ifname, vrf_name); + if (bs == NULL) + return CMD_WARNING_CONFIG_FAILED; + + if (use_json(argc, argv)) + _display_peer_counters_json(vty, bs); + else + _display_peer_counter(vty, bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_show_peers_counters, bfd_show_peers_counters_cmd, + "show bfd [vrf NAME] peers counters [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" + "Show BFD peer counters information\n" + JSON_STR) +{ + char *vrf_name = NULL; + int idx_vrf = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + + _display_peers_counter(vty, vrf_name, use_json(argc, argv)); + + return CMD_SUCCESS; +} + +DEFPY(bfd_clear_peer_counters, bfd_clear_peer_counters_cmd, + "clear bfd [vrf ] peer $peer [{multihop|local-address $local|interface IFNAME$ifname}]> counters", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" + "Peer label\n" + PEER_IPV4_STR + PEER_IPV6_STR + MHOP_STR + LOCAL_STR + LOCAL_IPV4_STR + LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + "clear BFD peer counters information\n") +{ + struct bfd_session *bs; + + /* Look up the BFD peer. */ + bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str, + ifname, vrfname); + if (bs == NULL) + return CMD_WARNING_CONFIG_FAILED; + + _clear_peer_counter(bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_show_peers_brief, bfd_show_peers_brief_cmd, + "show bfd [vrf ] peers brief [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + VRF_CMD_HELP_STR + "BFD peers status\n" + "Show BFD peer information in tabular form\n" + JSON_STR) +{ + char *vrf_name = NULL; + int idx_vrf = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + + _display_peers_brief(vty, vrf_name, use_json(argc, argv)); + + return CMD_SUCCESS; +} + +DEFPY(show_bfd_distributed, show_bfd_distributed_cmd, + "show bfd distributed", + SHOW_STR + "Bidirection Forwarding Detection\n" + "Show BFD data plane (distributed BFD) statistics\n") +{ + bfd_dplane_show_counters(vty); + return CMD_SUCCESS; +} + +DEFPY( + bfd_debug_distributed, bfd_debug_distributed_cmd, + "[no] debug bfd distributed", + NO_STR + DEBUG_STR + "Bidirection Forwarding Detection\n" + "BFD data plane (distributed BFD) debugging\n") +{ + bglobal.debug_dplane = !no; + return CMD_SUCCESS; +} + +DEFPY( + bfd_debug_peer, bfd_debug_peer_cmd, + "[no] debug bfd peer", + NO_STR + DEBUG_STR + "Bidirection Forwarding Detection\n" + "Peer events debugging\n") +{ + bglobal.debug_peer_event = !no; + return CMD_SUCCESS; +} + +DEFPY( + bfd_debug_zebra, bfd_debug_zebra_cmd, + "[no] debug bfd zebra", + NO_STR + DEBUG_STR + "Bidirection Forwarding Detection\n" + "Zebra events debugging\n") +{ + bglobal.debug_zebra = !no; + return CMD_SUCCESS; +} + +DEFPY( + bfd_debug_network, bfd_debug_network_cmd, + "[no] debug bfd network", + NO_STR + DEBUG_STR + "Bidirection Forwarding Detection\n" + "Network layer debugging\n") +{ + bglobal.debug_network = !no; + return CMD_SUCCESS; +} + +/* + * Function definitions. + */ + +/* + * Configuration rules: + * + * Single hop: + * peer + (interface name) + * + * Multi hop: + * peer + local + (optional vrf) + * + * Anything else is misconfiguration. + */ +static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, + const struct sockaddr_any *peer, + const struct sockaddr_any *local, + const char *ifname, const char *vrfname, + char *ebuf, size_t ebuflen) +{ + memset(bpc, 0, sizeof(*bpc)); + + /* Defaults */ + bpc->bpc_shutdown = false; + bpc->bpc_detectmultiplier = BPC_DEF_DETECTMULTIPLIER; + bpc->bpc_recvinterval = BPC_DEF_RECEIVEINTERVAL; + bpc->bpc_txinterval = BPC_DEF_TRANSMITINTERVAL; + bpc->bpc_echorecvinterval = BPC_DEF_ECHORECEIVEINTERVAL; + bpc->bpc_echotxinterval = BPC_DEF_ECHOTRANSMITINTERVAL; + bpc->bpc_lastevent = monotime(NULL); + + /* Safety check: when no error buf is provided len must be zero. */ + if (ebuf == NULL) + ebuflen = 0; + + /* Peer is always mandatory. */ + if (peer == NULL) { + snprintf(ebuf, ebuflen, "peer must not be empty"); + return -1; + } + + /* Validate address families. */ + if (peer->sa_sin.sin_family == AF_INET) { + if (local && local->sa_sin.sin_family != AF_INET) { + snprintf(ebuf, ebuflen, + "local is IPv6, but peer is IPv4"); + return -1; + } + + bpc->bpc_ipv4 = true; + } else if (peer->sa_sin.sin_family == AF_INET6) { + if (local && local->sa_sin.sin_family != AF_INET6) { + snprintf(ebuf, ebuflen, + "local is IPv4, but peer is IPv6"); + return -1; + } + + bpc->bpc_ipv4 = false; + } else { + snprintf(ebuf, ebuflen, "invalid peer address family"); + return -1; + } + + /* Copy local and/or peer addresses. */ + if (local) + bpc->bpc_local = *local; + + bpc->bpc_peer = *peer; + bpc->bpc_mhop = mhop; + + /* Handle interface specification configuration. */ + if (ifname) { + bpc->bpc_has_localif = true; + if (strlcpy(bpc->bpc_localif, ifname, sizeof(bpc->bpc_localif)) + > sizeof(bpc->bpc_localif)) { + snprintf(ebuf, ebuflen, "interface name too long"); + return -1; + } + } + + /* Handle VRF configuration. */ + if (vrfname) { + bpc->bpc_has_vrfname = true; + if (strlcpy(bpc->bpc_vrfname, vrfname, sizeof(bpc->bpc_vrfname)) + > sizeof(bpc->bpc_vrfname)) { + snprintf(ebuf, ebuflen, "vrf name too long"); + return -1; + } + } else { + bpc->bpc_has_vrfname = true; + strlcpy(bpc->bpc_vrfname, VRF_DEFAULT_NAME, sizeof(bpc->bpc_vrfname)); + } + + return 0; +} + +DEFUN_NOSH(show_debugging_bfd, + show_debugging_bfd_cmd, + "show debugging [bfd]", + SHOW_STR + DEBUG_STR + "BFD daemon\n") +{ + vty_out(vty, "BFD debugging status:\n"); + if (bglobal.debug_dplane) + vty_out(vty, " Distributed BFD debugging is on.\n"); + if (bglobal.debug_peer_event) + vty_out(vty, " Peer events debugging is on.\n"); + if (bglobal.debug_zebra) + vty_out(vty, " Zebra events debugging is on.\n"); + if (bglobal.debug_network) + vty_out(vty, " Network layer debugging is on.\n"); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static int bfdd_write_config(struct vty *vty); +struct cmd_node bfd_node = { + .name = "bfd", + .node = BFD_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-bfd)# ", + .config_write = bfdd_write_config, +}; + +struct cmd_node bfd_peer_node = { + .name = "bfd peer", + .node = BFD_PEER_NODE, + .parent_node = BFD_NODE, + .prompt = "%s(config-bfd-peer)# ", +}; + +static int bfdd_write_config(struct vty *vty) +{ + struct lyd_node *dnode; + int written = 0; + + if (bglobal.debug_dplane) { + vty_out(vty, "debug bfd distributed\n"); + written = 1; + } + + if (bglobal.debug_peer_event) { + vty_out(vty, "debug bfd peer\n"); + written = 1; + } + + if (bglobal.debug_zebra) { + vty_out(vty, "debug bfd zebra\n"); + written = 1; + } + + if (bglobal.debug_network) { + vty_out(vty, "debug bfd network\n"); + written = 1; + } + + dnode = yang_dnode_get(running_config->dnode, "/frr-bfdd:bfdd"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + + return written; +} + +void bfdd_vty_init(void) +{ + install_element(ENABLE_NODE, &bfd_show_peers_counters_cmd); + install_element(ENABLE_NODE, &bfd_show_peer_counters_cmd); + install_element(ENABLE_NODE, &bfd_clear_peer_counters_cmd); + install_element(ENABLE_NODE, &bfd_show_peers_cmd); + install_element(ENABLE_NODE, &bfd_show_peer_cmd); + install_element(ENABLE_NODE, &bfd_show_peers_brief_cmd); + install_element(ENABLE_NODE, &show_bfd_distributed_cmd); + install_element(ENABLE_NODE, &show_debugging_bfd_cmd); + + install_element(ENABLE_NODE, &bfd_debug_distributed_cmd); + install_element(ENABLE_NODE, &bfd_debug_peer_cmd); + install_element(ENABLE_NODE, &bfd_debug_zebra_cmd); + install_element(ENABLE_NODE, &bfd_debug_network_cmd); + + install_element(CONFIG_NODE, &bfd_debug_distributed_cmd); + install_element(CONFIG_NODE, &bfd_debug_peer_cmd); + install_element(CONFIG_NODE, &bfd_debug_zebra_cmd); + install_element(CONFIG_NODE, &bfd_debug_network_cmd); + + /* Install BFD node and commands. */ + install_node(&bfd_node); + install_default(BFD_NODE); + + /* Install BFD peer node. */ + install_node(&bfd_peer_node); + install_default(BFD_PEER_NODE); + + bfdd_cli_init(); +} diff --git a/bfdd/bfddp_packet.h b/bfdd/bfddp_packet.h new file mode 100644 index 0000000..afcbdd7 --- /dev/null +++ b/bfdd/bfddp_packet.h @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MIT +/* + * BFD Data Plane protocol messages header. + * + * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael F. Zalamena + */ + +/** + * \file bfddp_packet.h + */ +#ifndef BFD_DP_PACKET_H +#define BFD_DP_PACKET_H + +#include + +#include + +/* + * Protocol definitions. + */ + +/** + * BFD protocol version as defined in RFC5880 Section 4.1 Generic BFD Control + * Packet Format. + */ +#define BFD_PROTOCOL_VERSION 1 + +/** Default data plane port. */ +#define BFD_DATA_PLANE_DEFAULT_PORT 50700 + +/** BFD single hop UDP port, as defined in RFC 5881 Section 4. Encapsulation. */ +#define BFD_SINGLE_HOP_PORT 3784 + +/** BFD multi hop UDP port, as defined in RFC 5883 Section 5. Encapsulation. */ +#define BFD_MULTI_HOP_PORT 4784 + +/** Default slow start multiplier. */ +#define SLOWSTART_DMULT 3 +/** Default slow start transmission speed. */ +#define SLOWSTART_TX 1000000u +/** Default slow start receive speed. */ +#define SLOWSTART_RX 1000000u +/** Default slow start echo receive speed. */ +#define SLOWSTART_ERX 0u + +/* + * BFD single hop source UDP ports. As defined in RFC 5881 Section 4. + * Encapsulation. + */ +#define BFD_SOURCE_PORT_BEGIN 49152 +#define BFD_SOURCE_PORT_END 65535 + +/** BFD data plane protocol version. */ +#define BFD_DP_VERSION 1 + +/** BFD data plane message types. */ +enum bfddp_message_type { + /** Ask for BFD daemon or data plane for echo packet. */ + ECHO_REQUEST = 0, + /** Answer a ECHO_REQUEST packet. */ + ECHO_REPLY = 1, + /** Add or update BFD peer session. */ + DP_ADD_SESSION = 2, + /** Delete BFD peer session. */ + DP_DELETE_SESSION = 3, + /** Tell BFD daemon state changed: timer expired or session down. */ + BFD_STATE_CHANGE = 4, + + /** Ask for BFD session counters. */ + DP_REQUEST_SESSION_COUNTERS = 5, + /** Tell BFD daemon about counters values. */ + BFD_SESSION_COUNTERS = 6, +}; + +/** + * `ECHO_REQUEST`/`ECHO_REPLY` data payload. + * + * Data plane might use whatever precision it wants for `dp_time` + * field, however if you want to be able to tell the delay between + * data plane packet send and BFD daemon packet processing you should + * use `gettimeofday()` and have the data plane clock synchronized with + * BFD daemon (not a problem if data plane runs in the same system). + * + * Normally data plane will only check the time stamp it sent to determine + * the whole packet trip time. + */ +struct bfddp_echo { + /** Filled by data plane. */ + uint64_t dp_time; + /** Filled by BFD daemon. */ + uint64_t bfdd_time; +}; + + +/** BFD session flags. */ +enum bfddp_session_flag { + /** Set when using multi hop. */ + SESSION_MULTIHOP = (1 << 0), + /** Set when using demand mode. */ + SESSION_DEMAND = (1 << 1), + /** Set when using cbit (Control Plane Independent). */ + SESSION_CBIT = (1 << 2), + /** Set when using echo mode. */ + SESSION_ECHO = (1 << 3), + /** Set when using IPv6. */ + SESSION_IPV6 = (1 << 4), + /** Set when using passive mode. */ + SESSION_PASSIVE = (1 << 5), + /** Set when session is administrative down. */ + SESSION_SHUTDOWN = (1 << 6), +}; + +/** + * `DP_ADD_SESSION`/`DP_DELETE_SESSION` data payload. + * + * `lid` is unique in BFD daemon so it might be used as key for data + * structures lookup. + */ +struct bfddp_session { + /** Important session flags. \see bfddp_session_flag. */ + uint32_t flags; + /** + * Session source address. + * + * Check `flags` field for `SESSION_IPV6` before using as IPv6. + */ + struct in6_addr src; + /** + * Session destination address. + * + * Check `flags` field for `SESSION_IPV6` before using as IPv6. + */ + struct in6_addr dst; + + /** Local discriminator. */ + uint32_t lid; + /** + * Minimum desired transmission interval (in microseconds) without + * jitter. + */ + uint32_t min_tx; + /** + * Required minimum receive interval rate (in microseconds) without + * jitter. + */ + uint32_t min_rx; + /** + * Minimum desired echo transmission interval (in microseconds) + * without jitter. + */ + uint32_t min_echo_tx; + /** + * Required minimum echo receive interval rate (in microseconds) + * without jitter. + */ + uint32_t min_echo_rx; + /** Amount of milliseconds to wait before starting the session */ + uint32_t hold_time; + + /** Minimum TTL. */ + uint8_t ttl; + /** Detection multiplier. */ + uint8_t detect_mult; + /** Reserved / zeroed. */ + uint16_t zero; + + /** Interface index (set to `0` when unavailable). */ + uint32_t ifindex; + /** Interface name (empty when unavailable). */ + char ifname[64]; + + /* TODO: missing authentication. */ +}; + +/** BFD packet state values as defined in RFC 5880, Section 4.1. */ +enum bfd_state_value { + /** Session is administratively down. */ + STATE_ADMINDOWN = 0, + /** Session is down or went down. */ + STATE_DOWN = 1, + /** Session is initializing. */ + STATE_INIT = 2, + /** Session is up. */ + STATE_UP = 3, +}; + +/** BFD diagnostic field values as defined in RFC 5880, Section 4.1. */ +enum bfd_diagnostic_value { + /** Nothing was diagnosed. */ + DIAG_NOTHING = 0, + /** Control detection time expired. */ + DIAG_CONTROL_EXPIRED = 1, + /** Echo function failed. */ + DIAG_ECHO_FAILED = 2, + /** Neighbor signaled down. */ + DIAG_DOWN = 3, + /** Forwarding plane reset. */ + DIAG_FP_RESET = 4, + /** Path down. */ + DIAG_PATH_DOWN = 5, + /** Concatenated path down. */ + DIAG_CONCAT_PATH_DOWN = 6, + /** Administratively down. */ + DIAG_ADMIN_DOWN = 7, + /** Reverse concatenated path down. */ + DIAG_REV_CONCAT_PATH_DOWN = 8, +}; + +/** BFD remote state flags. */ +enum bfd_remote_flags { + /** Control Plane Independent bit. */ + RBIT_CPI = (1 << 0), + /** Demand mode bit. */ + RBIT_DEMAND = (1 << 1), + /** Multipoint bit. */ + RBIT_MP = (1 << 2), +}; + +/** + * `BFD_STATE_CHANGE` data payload. + */ +struct bfddp_state_change { + /** Local discriminator. */ + uint32_t lid; + /** Remote discriminator. */ + uint32_t rid; + /** Remote configurations/bits set. \see bfd_remote_flags. */ + uint32_t remote_flags; + /** Remote minimum desired transmission interval. */ + uint32_t desired_tx; + /** Remote minimum receive interval. */ + uint32_t required_rx; + /** Remote minimum echo receive interval. */ + uint32_t required_echo_rx; + /** Remote state. \see bfd_state_values.*/ + uint8_t state; + /** Remote diagnostics (if any) */ + uint8_t diagnostics; + /** Remote detection multiplier. */ + uint8_t detection_multiplier; +}; + +/** + * BFD control packet state bits definition. + */ +enum bfddp_control_state_bits { + /** Used to request connection establishment signal. */ + STATE_POLL_BIT = (1 << 5), + /** Finalizes the connection establishment signal. */ + STATE_FINAL_BIT = (1 << 4), + /** Signalizes that forward plane doesn't depend on control plane. */ + STATE_CPI_BIT = (1 << 3), + /** Signalizes the use of authentication. */ + STATE_AUTH_BIT = (1 << 2), + /** Signalizes that peer is using demand mode. */ + STATE_DEMAND_BIT = (1 << 1), + /** Used in RFC 8562 implementation. */ + STATE_MULTI_BIT = (1 << 0), +}; + +/** + * BFD control packet. + * + * As defined in 'RFC 5880 Section 4.1 Generic BFD Control Packet Format'. + */ +struct bfddp_control_packet { + /** (3 bits version << 5) | (5 bits diag). */ + uint8_t version_diag; + /** + * (2 bits state << 6) | (6 bits flags) + * + * \see bfd_state_value, bfddp_control_state_bits. + */ + uint8_t state_bits; + /** Detection multiplier. */ + uint8_t detection_multiplier; + /** Packet length in bytes. */ + uint8_t length; + /** Our discriminator. */ + uint32_t local_id; + /** Remote system discriminator. */ + uint32_t remote_id; + /** Desired minimum send interval in microseconds. */ + uint32_t desired_tx; + /** Desired minimum receive interval in microseconds. */ + uint32_t required_rx; + /** Desired minimum echo receive interval in microseconds. */ + uint32_t required_echo_rx; +}; + +/** + * The protocol wire message header structure. + */ +struct bfddp_message_header { + /** Protocol version format. \see BFD_DP_VERSION. */ + uint8_t version; + /** Reserved / zero field. */ + uint8_t zero; + /** Message contents type. \see bfddp_message_type. */ + uint16_t type; + /** + * Message identification (to pair request/response). + * + * The ID `0` is reserved for asynchronous messages (e.g. unrequested + * messages). + */ + uint16_t id; + /** Message length. */ + uint16_t length; +}; + +/** + * Data plane session counters request. + * + * Message type: `DP_REQUEST_SESSION_COUNTERS`. + */ +struct bfddp_request_counters { + /** Session local discriminator. */ + uint32_t lid; +}; + +/** + * BFD session counters reply. + * + * Message type: `BFD_SESSION_COUNTERS`. + */ +struct bfddp_session_counters { + /** Session local discriminator. */ + uint32_t lid; + + /** Control packet bytes input. */ + uint64_t control_input_bytes; + /** Control packets input. */ + uint64_t control_input_packets; + /** Control packet bytes output. */ + uint64_t control_output_bytes; + /** Control packets output. */ + uint64_t control_output_packets; + + /** Echo packet bytes input. */ + uint64_t echo_input_bytes; + /** Echo packets input. */ + uint64_t echo_input_packets; + /** Echo packet bytes output. */ + uint64_t echo_output_bytes; + /** Echo packets output. */ + uint64_t echo_output_packets; +}; + +/** + * The protocol wire messages structure. + */ +struct bfddp_message { + /** Message header. \see bfddp_message_header. */ + struct bfddp_message_header header; + + /** Message payload. \see bfddp_message_type. */ + union { + struct bfddp_echo echo; + struct bfddp_session session; + struct bfddp_state_change state; + struct bfddp_control_packet control; + struct bfddp_request_counters counters_req; + struct bfddp_session_counters session_counters; + } data; +}; + +#endif /* BFD_DP_PACKET_H */ diff --git a/bfdd/config.c b/bfdd/config.c new file mode 100644 index 0000000..22d7d7d --- /dev/null +++ b/bfdd/config.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * config.c: implements the BFD daemon configuration handling. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include + +#include "lib/json.h" + +#include "bfd.h" + +DEFINE_MTYPE_STATIC(BFDD, BFDD_LABEL, "long-lived label memory"); + +/* + * Definitions + */ +enum peer_list_type { + PLT_IPV4, + PLT_IPV6, + PLT_LABEL, +}; + + +/* + * Prototypes + */ +static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg); +static int parse_list(struct json_object *jo, enum peer_list_type plt, + bpc_handle h, void *arg); +static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc); +static int parse_peer_label_config(struct json_object *jo, + struct bfd_peer_cfg *bpc); + +static int config_add(struct bfd_peer_cfg *bpc, void *arg); +static int config_del(struct bfd_peer_cfg *bpc, void *arg); + +static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs); + + +/* + * Implementation + */ +static int config_add(struct bfd_peer_cfg *bpc, + void *arg __attribute__((unused))) +{ + return ptm_bfd_sess_new(bpc) == NULL; +} + +static int config_del(struct bfd_peer_cfg *bpc, + void *arg __attribute__((unused))) +{ + return ptm_bfd_sess_del(bpc) != 0; +} + +static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg) +{ + const char *key, *sval; + struct json_object *jo_val; + struct json_object_iterator joi, join; + int error = 0; + + JSON_FOREACH (jo, joi, join) { + key = json_object_iter_peek_name(&joi); + jo_val = json_object_iter_peek_value(&joi); + + if (strcmp(key, "ipv4") == 0) { + error += parse_list(jo_val, PLT_IPV4, h, arg); + } else if (strcmp(key, "ipv6") == 0) { + error += parse_list(jo_val, PLT_IPV6, h, arg); + } else if (strcmp(key, "label") == 0) { + error += parse_list(jo_val, PLT_LABEL, h, arg); + } else { + sval = json_object_get_string(jo_val); + zlog_warn("%s:%d invalid configuration: %s", __func__, + __LINE__, sval); + error++; + } + } + + /* + * Our callers never call free() on json_object and only expect + * the return value, so lets free() it here. + */ + json_object_put(jo); + + return error; +} + +int parse_config(const char *fname) +{ + struct json_object *jo; + + jo = json_object_from_file(fname); + if (jo == NULL) + return -1; + + return parse_config_json(jo, config_add, NULL); +} + +static int parse_list(struct json_object *jo, enum peer_list_type plt, + bpc_handle h, void *arg) +{ + struct json_object *jo_val; + struct bfd_peer_cfg bpc; + int allen, idx; + int error = 0, result; + + allen = json_object_array_length(jo); + for (idx = 0; idx < allen; idx++) { + jo_val = json_object_array_get_idx(jo, idx); + + /* Set defaults. */ + memset(&bpc, 0, sizeof(bpc)); + bpc.bpc_detectmultiplier = BFD_DEFDETECTMULT; + bpc.bpc_recvinterval = BFD_DEFREQUIREDMINRX; + bpc.bpc_txinterval = BFD_DEFDESIREDMINTX; + bpc.bpc_echorecvinterval = BFD_DEF_REQ_MIN_ECHO_RX; + bpc.bpc_echotxinterval = BFD_DEF_DES_MIN_ECHO_TX; + + switch (plt) { + case PLT_IPV4: + zlog_debug("ipv4 peers %d:", allen); + bpc.bpc_ipv4 = true; + break; + case PLT_IPV6: + zlog_debug("ipv6 peers %d:", allen); + bpc.bpc_ipv4 = false; + break; + case PLT_LABEL: + zlog_debug("label peers %d:", allen); + if (parse_peer_label_config(jo_val, &bpc) != 0) { + error++; + continue; + } + break; + + default: + error++; + zlog_err("%s:%d: unsupported peer type", __func__, + __LINE__); + break; + } + + result = parse_peer_config(jo_val, &bpc); + error += result; + if (result == 0) + error += (h(&bpc, arg) != 0); + } + + return error; +} + +static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc) +{ + const char *key, *sval; + struct json_object *jo_val; + struct json_object_iterator joi, join; + int family_type = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; + int error = 0; + + zlog_debug(" peer: %s", bpc->bpc_ipv4 ? "ipv4" : "ipv6"); + + JSON_FOREACH (jo, joi, join) { + key = json_object_iter_peek_name(&joi); + jo_val = json_object_iter_peek_value(&joi); + + if (strcmp(key, "multihop") == 0) { + bpc->bpc_mhop = json_object_get_boolean(jo_val); + zlog_debug(" multihop: %s", + bpc->bpc_mhop ? "true" : "false"); + } else if (strcmp(key, "peer-address") == 0) { + sval = json_object_get_string(jo_val); + if (strtosa(sval, &bpc->bpc_peer) != 0 + || bpc->bpc_peer.sa_sin.sin_family != family_type) { + zlog_debug( + "%s:%d failed to parse peer-address '%s'", + __func__, __LINE__, sval); + error++; + } + zlog_debug(" peer-address: %s", sval); + } else if (strcmp(key, "local-address") == 0) { + sval = json_object_get_string(jo_val); + if (strtosa(sval, &bpc->bpc_local) != 0 + || bpc->bpc_local.sa_sin.sin_family + != family_type) { + zlog_debug( + "%s:%d failed to parse local-address '%s'", + __func__, __LINE__, sval); + error++; + } + zlog_debug(" local-address: %s", sval); + } else if (strcmp(key, "local-interface") == 0) { + bpc->bpc_has_localif = true; + sval = json_object_get_string(jo_val); + if (strlcpy(bpc->bpc_localif, sval, + sizeof(bpc->bpc_localif)) + > sizeof(bpc->bpc_localif)) { + zlog_debug( + " local-interface: %s (truncated)", + sval); + error++; + } else { + zlog_debug(" local-interface: %s", sval); + } + } else if (strcmp(key, "vrf-name") == 0) { + bpc->bpc_has_vrfname = true; + sval = json_object_get_string(jo_val); + if (strlcpy(bpc->bpc_vrfname, sval, + sizeof(bpc->bpc_vrfname)) + > sizeof(bpc->bpc_vrfname)) { + zlog_debug(" vrf-name: %s (truncated)", + sval); + error++; + } else { + zlog_debug(" vrf-name: %s", sval); + } + } else if (strcmp(key, "detect-multiplier") == 0) { + bpc->bpc_detectmultiplier = + json_object_get_int64(jo_val); + bpc->bpc_has_detectmultiplier = true; + zlog_debug(" detect-multiplier: %u", + bpc->bpc_detectmultiplier); + } else if (strcmp(key, "receive-interval") == 0) { + bpc->bpc_recvinterval = json_object_get_int64(jo_val); + bpc->bpc_has_recvinterval = true; + zlog_debug(" receive-interval: %" PRIu64, + bpc->bpc_recvinterval); + } else if (strcmp(key, "transmit-interval") == 0) { + bpc->bpc_txinterval = json_object_get_int64(jo_val); + bpc->bpc_has_txinterval = true; + zlog_debug(" transmit-interval: %" PRIu64, + bpc->bpc_txinterval); + } else if (strcmp(key, "echo-receive-interval") == 0) { + bpc->bpc_echorecvinterval = json_object_get_int64(jo_val); + bpc->bpc_has_echorecvinterval = true; + zlog_debug(" echo-receive-interval: %" PRIu64, + bpc->bpc_echorecvinterval); + } else if (strcmp(key, "echo-transmit-interval") == 0) { + bpc->bpc_echotxinterval = json_object_get_int64(jo_val); + bpc->bpc_has_echotxinterval = true; + zlog_debug(" echo-transmit-interval: %" PRIu64, + bpc->bpc_echotxinterval); + } else if (strcmp(key, "create-only") == 0) { + bpc->bpc_createonly = json_object_get_boolean(jo_val); + zlog_debug(" create-only: %s", + bpc->bpc_createonly ? "true" : "false"); + } else if (strcmp(key, "shutdown") == 0) { + bpc->bpc_shutdown = json_object_get_boolean(jo_val); + zlog_debug(" shutdown: %s", + bpc->bpc_shutdown ? "true" : "false"); + } else if (strcmp(key, "echo-mode") == 0) { + bpc->bpc_echo = json_object_get_boolean(jo_val); + zlog_debug(" echo-mode: %s", + bpc->bpc_echo ? "true" : "false"); + } else if (strcmp(key, "label") == 0) { + bpc->bpc_has_label = true; + sval = json_object_get_string(jo_val); + if (strlcpy(bpc->bpc_label, sval, + sizeof(bpc->bpc_label)) + > sizeof(bpc->bpc_label)) { + zlog_debug(" label: %s (truncated)", + sval); + error++; + } else { + zlog_debug(" label: %s", sval); + } + } else { + sval = json_object_get_string(jo_val); + zlog_warn("%s:%d invalid configuration: '%s: %s'", + __func__, __LINE__, key, sval); + error++; + } + } + + if (bpc->bpc_peer.sa_sin.sin_family == 0) { + zlog_debug("%s:%d no peer address provided", __func__, + __LINE__); + error++; + } + + return error; +} + +static int parse_peer_label_config(struct json_object *jo, + struct bfd_peer_cfg *bpc) +{ + struct peer_label *pl; + struct json_object *label; + const char *sval; + + /* Get label and translate it to BFD daemon key. */ + if (!json_object_object_get_ex(jo, "label", &label)) + return 1; + + sval = json_object_get_string(label); + + pl = pl_find(sval); + if (pl == NULL) + return 1; + + zlog_debug(" peer-label: %s", sval); + + /* Translate the label into BFD address keys. */ + bs_to_bpc(pl->pl_bs, bpc); + + return 0; +} + + +/* + * Control socket JSON parsing. + */ +int config_request_add(const char *jsonstr) +{ + struct json_object *jo; + + jo = json_tokener_parse(jsonstr); + if (jo == NULL) + return -1; + + return parse_config_json(jo, config_add, NULL); +} + +int config_request_del(const char *jsonstr) +{ + struct json_object *jo; + + jo = json_tokener_parse(jsonstr); + if (jo == NULL) + return -1; + + return parse_config_json(jo, config_del, NULL); +} + +char *config_response(const char *status, const char *error) +{ + struct json_object *resp, *jo; + char *jsonstr; + + resp = json_object_new_object(); + if (resp == NULL) + return NULL; + + /* Add 'status' response key. */ + jo = json_object_new_string(status); + if (jo == NULL) { + json_object_put(resp); + return NULL; + } + + json_object_object_add(resp, "status", jo); + + /* Add 'error' response key. */ + if (error != NULL) { + jo = json_object_new_string(error); + if (jo == NULL) { + json_object_put(resp); + return NULL; + } + + json_object_object_add(resp, "error", jo); + } + + /* Generate JSON response. */ + jsonstr = XSTRDUP( + MTYPE_BFDD_NOTIFICATION, + json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); + json_object_put(resp); + + return jsonstr; +} + +char *config_notify(struct bfd_session *bs) +{ + struct json_object *resp; + char *jsonstr; + time_t now; + + resp = json_object_new_object(); + if (resp == NULL) + return NULL; + + json_object_string_add(resp, "op", BCM_NOTIFY_PEER_STATUS); + + json_object_add_peer(resp, bs); + + /* Add status information */ + json_object_int_add(resp, "id", bs->discrs.my_discr); + json_object_int_add(resp, "remote-id", bs->discrs.my_discr); + + switch (bs->ses_state) { + case PTM_BFD_UP: + json_object_string_add(resp, "state", "up"); + + now = monotime(NULL); + json_object_int_add(resp, "uptime", now - bs->uptime.tv_sec); + break; + case PTM_BFD_ADM_DOWN: + json_object_string_add(resp, "state", "adm-down"); + break; + case PTM_BFD_DOWN: + json_object_string_add(resp, "state", "down"); + + now = monotime(NULL); + json_object_int_add(resp, "downtime", + now - bs->downtime.tv_sec); + break; + case PTM_BFD_INIT: + json_object_string_add(resp, "state", "init"); + break; + + default: + json_object_string_add(resp, "state", "unknown"); + break; + } + + json_object_int_add(resp, "diagnostics", bs->local_diag); + json_object_int_add(resp, "remote-diagnostics", bs->remote_diag); + + /* Generate JSON response. */ + jsonstr = XSTRDUP( + MTYPE_BFDD_NOTIFICATION, + json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); + json_object_put(resp); + + return jsonstr; +} + +char *config_notify_config(const char *op, struct bfd_session *bs) +{ + struct json_object *resp; + char *jsonstr; + + resp = json_object_new_object(); + if (resp == NULL) + return NULL; + + json_object_string_add(resp, "op", op); + + json_object_add_peer(resp, bs); + + /* On peer deletion we don't need to add any additional information. */ + if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0) + goto skip_config; + + json_object_int_add(resp, "detect-multiplier", bs->detect_mult); + json_object_int_add(resp, "receive-interval", + bs->timers.required_min_rx / 1000); + json_object_int_add(resp, "transmit-interval", + bs->timers.desired_min_tx / 1000); + json_object_int_add(resp, "echo-receive-interval", + bs->timers.required_min_echo_rx / 1000); + json_object_int_add(resp, "echo-transmit-interval", + bs->timers.desired_min_echo_tx / 1000); + + json_object_int_add(resp, "remote-detect-multiplier", + bs->remote_detect_mult); + json_object_int_add(resp, "remote-receive-interval", + bs->remote_timers.required_min_rx / 1000); + json_object_int_add(resp, "remote-transmit-interval", + bs->remote_timers.desired_min_tx / 1000); + json_object_int_add(resp, "remote-echo-receive-interval", + bs->remote_timers.required_min_echo / 1000); + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + json_object_boolean_true_add(resp, "echo-mode"); + else + json_object_boolean_false_add(resp, "echo-mode"); + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + json_object_boolean_true_add(resp, "shutdown"); + else + json_object_boolean_false_add(resp, "shutdown"); + +skip_config: + /* Generate JSON response. */ + jsonstr = XSTRDUP( + MTYPE_BFDD_NOTIFICATION, + json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); + json_object_put(resp); + + return jsonstr; +} + +int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, + bpc_handle bh) +{ + struct json_object *jo; + + jo = json_tokener_parse(jsonstr); + if (jo == NULL) + return -1; + + return parse_config_json(jo, bh, bcs); +} + +static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs) +{ + char addr_buf[INET6_ADDRSTRLEN]; + + /* Add peer 'key' information. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) + json_object_boolean_true_add(jo, "ipv6"); + else + json_object_boolean_false_add(jo, "ipv6"); + + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + json_object_boolean_true_add(jo, "multihop"); + json_object_string_add(jo, "peer-address", + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); + json_object_string_add(jo, "local-address", + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + if (bs->key.vrfname[0]) + json_object_string_add(jo, "vrf-name", bs->key.vrfname); + } else { + json_object_boolean_false_add(jo, "multihop"); + json_object_string_add(jo, "peer-address", + inet_ntop(bs->key.family, &bs->key.peer, + addr_buf, sizeof(addr_buf))); + if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) + json_object_string_add( + jo, "local-address", + inet_ntop(bs->key.family, &bs->key.local, + addr_buf, sizeof(addr_buf))); + if (bs->key.ifname[0]) + json_object_string_add(jo, "local-interface", + bs->key.ifname); + } + + if (bs->pl) + json_object_string_add(jo, "label", bs->pl->pl_label); + + return 0; +} + + +/* + * Label handling + */ +struct peer_label *pl_find(const char *label) +{ + struct peer_label *pl; + + TAILQ_FOREACH (pl, &bglobal.bg_pllist, pl_entry) { + if (strcmp(pl->pl_label, label) != 0) + continue; + + return pl; + } + + return NULL; +} + +struct peer_label *pl_new(const char *label, struct bfd_session *bs) +{ + struct peer_label *pl; + + pl = XCALLOC(MTYPE_BFDD_LABEL, sizeof(*pl)); + + if (strlcpy(pl->pl_label, label, sizeof(pl->pl_label)) + > sizeof(pl->pl_label)) + zlog_warn("%s:%d: label was truncated", __func__, __LINE__); + + pl->pl_bs = bs; + bs->pl = pl; + + TAILQ_INSERT_HEAD(&bglobal.bg_pllist, pl, pl_entry); + + return pl; +} + +void pl_free(struct peer_label *pl) +{ + if (pl == NULL) + return; + + /* Remove the pointer back. */ + pl->pl_bs->pl = NULL; + + TAILQ_REMOVE(&bglobal.bg_pllist, pl, pl_entry); + XFREE(MTYPE_BFDD_LABEL, pl); +} diff --git a/bfdd/control.c b/bfdd/control.c new file mode 100644 index 0000000..98fd813 --- /dev/null +++ b/bfdd/control.c @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * control.c: implements the BFD daemon control socket. It will be used + * to talk with clients daemon/scripts/consumers. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include +#include + +#include + +#include "bfd.h" + +/* + * Prototypes + */ +static int sock_set_nonblock(int fd); +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs); +static void control_queue_free(struct bfd_control_socket *bcs, + struct bfd_control_queue *bcq); +static int control_queue_dequeue(struct bfd_control_socket *bcs); +static int control_queue_enqueue(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, + struct bfd_session *bs); +static void control_notifypeer_free(struct bfd_control_socket *bcs, + struct bfd_notify_peer *bnp); +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, + struct bfd_session *bs); + + +struct bfd_control_socket *control_new(int sd); +static void control_free(struct bfd_control_socket *bcs); +static void control_reset_buf(struct bfd_control_buffer *bcb); +static void control_read(struct event *t); +static void control_write(struct event *t); + +static void control_handle_request_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_handle_request_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg); +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg); +static void control_handle_notify_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_handle_notify_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void _control_handle_notify(struct hash_bucket *hb, void *arg); +static void control_handle_notify(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_response(struct bfd_control_socket *bcs, uint16_t id, + const char *status, const char *error); + +static void _control_notify_config(struct bfd_control_socket *bcs, + const char *op, struct bfd_session *bs); +static void _control_notify(struct bfd_control_socket *bcs, + struct bfd_session *bs); + + +/* + * Functions + */ +static int sock_set_nonblock(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + zlog_warn("%s: fcntl F_GETFL: %s", __func__, strerror(errno)); + return -1; + } + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) == -1) { + zlog_warn("%s: fcntl F_SETFL: %s", __func__, strerror(errno)); + return -1; + } + + return 0; +} + +int control_init(const char *path) +{ + int sd; + mode_t umval; + struct sockaddr_un sun_ = { + .sun_family = AF_UNIX, + }; + + assert(path); + + strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path)); + + /* Remove previously created sockets. */ + unlink(sun_.sun_path); + + sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); + if (sd == -1) { + zlog_err("%s: socket: %s", __func__, strerror(errno)); + return -1; + } + + umval = umask(0); + if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) { + zlog_err("%s: bind: %s", __func__, strerror(errno)); + close(sd); + return -1; + } + umask(umval); + + if (listen(sd, SOMAXCONN) == -1) { + zlog_err("%s: listen: %s", __func__, strerror(errno)); + close(sd); + return -1; + } + + sock_set_nonblock(sd); + + bglobal.bg_csock = sd; + + return 0; +} + +void control_shutdown(void) +{ + struct bfd_control_socket *bcs; + + event_cancel(&bglobal.bg_csockev); + + socket_close(&bglobal.bg_csock); + + while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) { + bcs = TAILQ_FIRST(&bglobal.bg_bcslist); + control_free(bcs); + } +} + +void control_accept(struct event *t) +{ + int csock, sd = EVENT_FD(t); + + csock = accept(sd, NULL, 0); + if (csock == -1) { + zlog_warn("%s: accept: %s", __func__, strerror(errno)); + return; + } + + control_new(csock); + + event_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev); +} + + +/* + * Client handling + */ +struct bfd_control_socket *control_new(int sd) +{ + struct bfd_control_socket *bcs; + + bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs)); + + /* Disable notifications by default. */ + bcs->bcs_notify = 0; + + bcs->bcs_sd = sd; + event_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); + + TAILQ_INIT(&bcs->bcs_bcqueue); + TAILQ_INIT(&bcs->bcs_bnplist); + TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry); + + return bcs; +} + +static void control_free(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + struct bfd_notify_peer *bnp; + + event_cancel(&(bcs->bcs_ev)); + event_cancel(&(bcs->bcs_outev)); + + close(bcs->bcs_sd); + + TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry); + + /* Empty output queue. */ + while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) { + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + control_queue_free(bcs, bcq); + } + + /* Empty notification list. */ + while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) { + bnp = TAILQ_FIRST(&bcs->bcs_bnplist); + control_notifypeer_free(bcs, bnp); + } + + control_reset_buf(&bcs->bcs_bin); + XFREE(MTYPE_BFDD_CONTROL, bcs); +} + +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_notify_peer *bnp; + + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + return bnp; + + bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp)); + + TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry); + bnp->bnp_bs = bs; + bs->refcount++; + + return bnp; +} + +static void control_notifypeer_free(struct bfd_control_socket *bcs, + struct bfd_notify_peer *bnp) +{ + TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry); + bnp->bnp_bs->refcount--; + XFREE(MTYPE_BFDD_CONTROL, bnp); +} + +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_notify_peer *bnp; + + TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) { + if (bnp->bnp_bs == bs) + return bnp; + } + + return NULL; +} + +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + + bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq)); + + control_reset_buf(&bcq->bcq_bcb); + TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry); + + return bcq; +} + +static void control_queue_free(struct bfd_control_socket *bcs, + struct bfd_control_queue *bcq) +{ + control_reset_buf(&bcq->bcq_bcb); + TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry); + XFREE(MTYPE_BFDD_NOTIFICATION, bcq); +} + +static int control_queue_dequeue(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + + /* List is empty, nothing to do. */ + if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) + goto empty_list; + + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + control_queue_free(bcs, bcq); + + /* Get the next buffer to send. */ + if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) + goto empty_list; + + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + bcs->bcs_bout = &bcq->bcq_bcb; + + bcs->bcs_outev = NULL; + event_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + + return 1; + +empty_list: + event_cancel(&(bcs->bcs_outev)); + bcs->bcs_bout = NULL; + return 0; +} + +static int control_queue_enqueue(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + struct bfd_control_queue *bcq; + struct bfd_control_buffer *bcb; + + bcq = control_queue_new(bcs); + + bcb = &bcq->bcq_bcb; + bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length); + bcb->bcb_pos = 0; + bcb->bcb_bcm = bcm; + + /* If this is the first item, then dequeue and start using it. */ + if (bcs->bcs_bout == NULL) { + bcs->bcs_bout = bcb; + + /* New messages, active write events. */ + event_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + } + + return 0; +} + +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + struct bfd_control_queue *bcq, *bcqn; + struct bfd_control_buffer *bcb; + + /* Enqueue it somewhere. */ + if (control_queue_enqueue(bcs, bcm) == -1) + return -1; + + /* + * The item is either the first or the last. So we must first + * check the best case where the item is already the first. + */ + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + bcb = &bcq->bcq_bcb; + if (bcm == bcb->bcb_bcm) + return 0; + + /* + * The item was not the first, so it is the last. We'll try to + * assign it to the head of the queue, however if there is a + * transfer in progress, then we have to make the item as the + * next one. + * + * Interrupting the transfer of in progress message will cause + * the client to lose track of the message position/data. + */ + bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue); + TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry); + if (bcb->bcb_pos != 0) { + /* + * First position is already being sent, insert into + * second position. + */ + TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry); + } else { + /* + * Old message didn't start being sent, we still have + * time to put this one in the head of the queue. + */ + TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry); + bcb = &bcqn->bcq_bcb; + bcs->bcs_bout = bcb; + } + + return 0; +} + +static void control_reset_buf(struct bfd_control_buffer *bcb) +{ + /* Get ride of old data. */ + XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf); + bcb->bcb_pos = 0; + bcb->bcb_left = 0; +} + +static void control_read(struct event *t) +{ + struct bfd_control_socket *bcs = EVENT_ARG(t); + struct bfd_control_buffer *bcb = &bcs->bcs_bin; + int sd = bcs->bcs_sd; + struct bfd_control_msg bcm; + ssize_t bread; + size_t plen; + + /* + * Check if we have already downloaded message content, if so then skip + * to + * download the rest of it and process. + * + * Otherwise download a new message header and allocate the necessary + * memory. + */ + if (bcb->bcb_buf != NULL) + goto skip_header; + + bread = read(sd, &bcm, sizeof(bcm)); + if (bread == 0) { + control_free(bcs); + return; + } + if (bread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + goto schedule_next_read; + + zlog_warn("%s: read: %s", __func__, strerror(errno)); + control_free(bcs); + return; + } + + /* Validate header fields. */ + plen = ntohl(bcm.bcm_length); + if (plen < 2) { + zlog_debug("%s: client closed due small message length: %d", + __func__, bcm.bcm_length); + control_free(bcs); + return; + } + +#define FRR_BFD_MAXLEN 10 * 1024 + + if (plen > FRR_BFD_MAXLEN) { + zlog_debug("%s: client closed, invalid message length: %d", + __func__, bcm.bcm_length); + control_free(bcs); + return; + } + + if (bcm.bcm_ver != BMV_VERSION_1) { + zlog_debug("%s: client closed due bad version: %d", __func__, + bcm.bcm_ver); + control_free(bcs); + return; + } + + /* Prepare the buffer to load the message. */ + bcs->bcs_version = bcm.bcm_ver; + bcs->bcs_type = bcm.bcm_type; + + bcb->bcb_pos = sizeof(bcm); + bcb->bcb_left = plen; + bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(bcm) + bcb->bcb_left + 1); + if (bcb->bcb_buf == NULL) { + zlog_warn("%s: not enough memory for message size: %zu", + __func__, bcb->bcb_left); + control_free(bcs); + return; + } + + memcpy(bcb->bcb_buf, &bcm, sizeof(bcm)); + + /* Terminate data string with NULL for later processing. */ + bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0; + +skip_header: + /* Download the remaining data of the message and process it. */ + bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); + if (bread == 0) { + control_free(bcs); + return; + } + if (bread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + goto schedule_next_read; + + zlog_warn("%s: read: %s", __func__, strerror(errno)); + control_free(bcs); + return; + } + + bcb->bcb_pos += bread; + bcb->bcb_left -= bread; + /* We need more data, return to wait more. */ + if (bcb->bcb_left > 0) + goto schedule_next_read; + + switch (bcb->bcb_bcm->bcm_type) { + case BMT_REQUEST_ADD: + control_handle_request_add(bcs, bcb->bcb_bcm); + break; + case BMT_REQUEST_DEL: + control_handle_request_del(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY: + control_handle_notify(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY_ADD: + control_handle_notify_add(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY_DEL: + control_handle_notify_del(bcs, bcb->bcb_bcm); + break; + + default: + zlog_debug("%s: unhandled message type: %d", __func__, + bcb->bcb_bcm->bcm_type); + control_response(bcs, bcb->bcb_bcm->bcm_id, BCM_RESPONSE_ERROR, + "invalid message type"); + break; + } + + bcs->bcs_version = 0; + bcs->bcs_type = 0; + control_reset_buf(bcb); + +schedule_next_read: + bcs->bcs_ev = NULL; + event_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); +} + +static void control_write(struct event *t) +{ + struct bfd_control_socket *bcs = EVENT_ARG(t); + struct bfd_control_buffer *bcb = bcs->bcs_bout; + int sd = bcs->bcs_sd; + ssize_t bwrite; + + bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); + if (bwrite == 0) { + control_free(bcs); + return; + } + if (bwrite < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + bcs->bcs_outev = NULL; + event_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + return; + } + + zlog_warn("%s: write: %s", __func__, strerror(errno)); + control_free(bcs); + return; + } + + bcb->bcb_pos += bwrite; + bcb->bcb_left -= bwrite; + if (bcb->bcb_left > 0) { + bcs->bcs_outev = NULL; + event_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + return; + } + + control_queue_dequeue(bcs); +} + + +/* + * Message processing + */ +static void control_handle_request_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_request_add(json) == 0) + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + else + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "request add failed"); +} + +static void control_handle_request_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_request_del(json) == 0) + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + else + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "request del failed"); +} + +static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc) +{ + struct peer_label *pl; + + if (bpc->bpc_has_label) { + pl = pl_find(bpc->bpc_label); + if (pl) + return pl->pl_bs; + } + + return bs_peer_find(bpc); +} + +static void _control_handle_notify(struct hash_bucket *hb, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = hb->data; + + /* Notify peer configuration. */ + if (bcs->bcs_notify & BCM_NOTIFY_CONFIG) + _control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs); + + /* Notify peer status. */ + if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) + _control_notify(bcs, bs); +} + +static void control_handle_notify(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify)); + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + + /* + * If peer asked for notification configuration, send everything that + * was configured until the moment to sync up. + */ + if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE)) + bfd_id_iterate(_control_handle_notify, bcs); +} + +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = _notify_find_peer(bpc); + + if (bs == NULL) + return -1; + + control_notifypeer_new(bcs, bs); + + /* Notify peer status. */ + _control_notify(bcs, bs); + + return 0; +} + +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = _notify_find_peer(bpc); + struct bfd_notify_peer *bnp; + + if (bs == NULL) + return -1; + + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + control_notifypeer_free(bcs, bnp); + + return 0; +} + +static void control_handle_notify_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_notify_request(bcs, json, notify_add_cb) == 0) { + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + return; + } + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "failed to parse notify data"); +} + +static void control_handle_notify_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_notify_request(bcs, json, notify_del_cb) == 0) { + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + return; + } + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "failed to parse notify data"); +} + + +/* + * Internal functions used by the BFD daemon. + */ +static void control_response(struct bfd_control_socket *bcs, uint16_t id, + const char *status, const char *error) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_response(status, error); + if (jsonstr == NULL) { + zlog_warn("%s: config_response: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_RESPONSE; + bcm->bcm_id = id; + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue_first(bcs, bcm); +} + +static void _control_notify(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_notify(bs); + if (jsonstr == NULL) { + zlog_warn("%s: config_notify: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_NOTIFY; + bcm->bcm_id = htons(BCM_NOTIFY_ID); + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue(bcs, bcm); +} + +int control_notify(struct bfd_session *bs, uint8_t notify_state) +{ + struct bfd_control_socket *bcs; + struct bfd_notify_peer *bnp; + + /* Notify zebra listeners as well. */ + ptm_bfd_notify(bs, notify_state); + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) { + bnp = control_notifypeer_find(bcs, bs); + /* + * If the notification is not configured here, + * don't send it. + */ + if (bnp == NULL) + continue; + } + + _control_notify(bcs, bs); + } + + return 0; +} + +static void _control_notify_config(struct bfd_control_socket *bcs, + const char *op, struct bfd_session *bs) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_notify_config(op, bs); + if (jsonstr == NULL) { + zlog_warn("%s: config_notify_config: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_NOTIFY; + bcm->bcm_id = htons(BCM_NOTIFY_ID); + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue(bcs, bcm); +} + +int control_notify_config(const char *op, struct bfd_session *bs) +{ + struct bfd_control_socket *bcs; + struct bfd_notify_peer *bnp; + + /* Remove the control sockets notification for this peer. */ + if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) { + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + control_notifypeer_free(bcs, bnp); + } + } + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0) + continue; + + _control_notify_config(bcs, op, bs); + } + + return 0; +} diff --git a/bfdd/dplane.c b/bfdd/dplane.c new file mode 100644 index 0000000..d853981 --- /dev/null +++ b/bfdd/dplane.c @@ -0,0 +1,1176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD data plane implementation (distributed BFD). + * + * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#else +#include +#endif /* __FreeBSD__ */ + +#include +#include + +#include "lib/hook.h" +#include "lib/network.h" +#include "lib/printfrr.h" +#include "lib/stream.h" +#include "lib/frrevent.h" + +#include "bfd.h" +#include "bfddp_packet.h" + +#include "lib/openbsd-queue.h" + +DEFINE_MTYPE_STATIC(BFDD, BFDD_DPLANE_CTX, + "Data plane client allocated memory"); + +/** Data plane client socket buffer size. */ +#define BFD_DPLANE_CLIENT_BUF_SIZE 8192 + +struct bfd_dplane_ctx { + /** Client file descriptor. */ + int sock; + /** Is this a connected or accepted? */ + bool client; + /** Is the socket still connecting? */ + bool connecting; + /** Client/server address. */ + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_un sun; + } addr; + /** Address length. */ + socklen_t addrlen; + /** Data plane current last used ID. */ + uint16_t last_id; + + /** Input buffer data. */ + struct stream *inbuf; + /** Output buffer data. */ + struct stream *outbuf; + /** Input event data. */ + struct event *inbufev; + /** Output event data. */ + struct event *outbufev; + /** Connection event. */ + struct event *connectev; + + /** Amount of bytes read. */ + uint64_t in_bytes; + /** Amount of bytes read peak. */ + uint64_t in_bytes_peak; + /** Amount of bytes written. */ + uint64_t out_bytes; + /** Amount of bytes written peak. */ + uint64_t out_bytes_peak; + /** Amount of output buffer full events (`bfd_dplane_enqueue` failed). + */ + uint64_t out_fullev; + + /** Amount of messages read (full messages). */ + uint64_t in_msgs; + /** Amount of messages enqueued (maybe written). */ + uint64_t out_msgs; + + TAILQ_ENTRY(bfd_dplane_ctx) entry; +}; + +/** + * Callback type for `bfd_dplane_expect`. \see bfd_dplane_expect. + */ +typedef void (*bfd_dplane_expect_cb)(struct bfddp_message *msg, void *arg); + +static void bfd_dplane_client_connect(struct event *t); +static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc); +static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc); +static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc, + struct bfd_session *bs); + +/* + * BFD data plane helper functions. + */ +static const char *bfd_dplane_messagetype2str(enum bfddp_message_type bmt) +{ + switch (bmt) { + case ECHO_REQUEST: + return "ECHO_REQUEST"; + case ECHO_REPLY: + return "ECHO_REPLY"; + case DP_ADD_SESSION: + return "DP_ADD_SESSION"; + case DP_DELETE_SESSION: + return "DP_DELETE_SESSION"; + case BFD_STATE_CHANGE: + return "BFD_STATE_CHANGE"; + case DP_REQUEST_SESSION_COUNTERS: + return "DP_REQUEST_SESSION_COUNTERS"; + case BFD_SESSION_COUNTERS: + return "BFD_SESSION_COUNTERS"; + default: + return "UNKNOWN"; + } +} + +static void bfd_dplane_debug_message(const struct bfddp_message *msg) +{ + enum bfddp_message_type bmt; + char buf[256], addrs[256]; + uint32_t flags; + int rv; + + if (!bglobal.debug_dplane) + return; + + bmt = ntohs(msg->header.type); + zlog_debug("dplane-packet: [version=%d length=%d type=%s (%d)]", + msg->header.version, ntohs(msg->header.length), + bfd_dplane_messagetype2str(bmt), bmt); + + switch (bmt) { + case ECHO_REPLY: + case ECHO_REQUEST: + zlog_debug(" [dp_time=%" PRIu64 " bfdd_time=%" PRIu64 "]", + be64toh(msg->data.echo.dp_time), + be64toh(msg->data.echo.bfdd_time)); + break; + + case DP_ADD_SESSION: + case DP_DELETE_SESSION: + flags = ntohl(msg->data.session.flags); + if (flags & SESSION_IPV6) + snprintfrr(addrs, sizeof(addrs), "src=%pI6 dst=%pI6", + &msg->data.session.src, + &msg->data.session.dst); + else + snprintfrr(addrs, sizeof(addrs), "src=%pI4 dst=%pI4", + (struct in_addr *)&msg->data.session.src, + (struct in_addr *)&msg->data.session.dst); + + buf[0] = 0; + if (flags & SESSION_CBIT) + strlcat(buf, "cpi ", sizeof(buf)); + if (flags & SESSION_ECHO) + strlcat(buf, "echo ", sizeof(buf)); + if (flags & SESSION_IPV6) + strlcat(buf, "ipv6 ", sizeof(buf)); + if (flags & SESSION_DEMAND) + strlcat(buf, "demand ", sizeof(buf)); + if (flags & SESSION_PASSIVE) + strlcat(buf, "passive ", sizeof(buf)); + if (flags & SESSION_MULTIHOP) + strlcat(buf, "multihop ", sizeof(buf)); + if (flags & SESSION_SHUTDOWN) + strlcat(buf, "shutdown ", sizeof(buf)); + + /* Remove the last space to make things prettier. */ + rv = (int)strlen(buf); + if (rv > 0) + buf[rv - 1] = 0; + + zlog_debug( + " [flags=0x%08x{%s} %s ttl=%d detect_mult=%d " + "ifindex=%d ifname=%s]", + flags, buf, addrs, msg->data.session.ttl, + msg->data.session.detect_mult, + ntohl(msg->data.session.ifindex), + msg->data.session.ifname); + break; + + case BFD_STATE_CHANGE: + buf[0] = 0; + flags = ntohl(msg->data.state.remote_flags); + if (flags & RBIT_CPI) + strlcat(buf, "cbit ", sizeof(buf)); + if (flags & RBIT_DEMAND) + strlcat(buf, "demand ", sizeof(buf)); + if (flags & RBIT_MP) + strlcat(buf, "mp ", sizeof(buf)); + + /* Remove the last space to make things prettier. */ + rv = (int)strlen(buf); + if (rv > 0) + buf[rv - 1] = 0; + + zlog_debug( + " [lid=%u rid=%u flags=0x%02x{%s} state=%s " + "diagnostics=%s mult=%d tx=%u rx=%u erx=%u]", + ntohl(msg->data.state.lid), ntohl(msg->data.state.rid), + flags, buf, state_list[msg->data.state.state].str, + diag2str(msg->data.state.diagnostics), + msg->data.state.detection_multiplier, + ntohl(msg->data.state.desired_tx), + ntohl(msg->data.state.required_rx), + ntohl(msg->data.state.required_echo_rx)); + break; + + case DP_REQUEST_SESSION_COUNTERS: + zlog_debug(" [lid=%u]", ntohl(msg->data.counters_req.lid)); + break; + + case BFD_SESSION_COUNTERS: + zlog_debug( + " [lid=%u " + "control{in %" PRIu64 " bytes (%" PRIu64 + " packets), " + "out %" PRIu64 " bytes (%" PRIu64 + " packets)} " + "echo{in %" PRIu64 " bytes (%" PRIu64 + " packets), " + "out %" PRIu64 " bytes (%" PRIu64 " packets)}]", + ntohl(msg->data.session_counters.lid), + be64toh(msg->data.session_counters.control_input_bytes), + be64toh(msg->data.session_counters + .control_input_packets), + be64toh(msg->data.session_counters + .control_output_bytes), + be64toh(msg->data.session_counters + .control_output_packets), + be64toh(msg->data.session_counters.echo_input_bytes), + be64toh(msg->data.session_counters.echo_input_packets), + be64toh(msg->data.session_counters.echo_output_bytes), + be64toh(msg->data.session_counters + .echo_output_packets)); + break; + } +} + +/** + * Gets the next unused non zero identification. + * + * \param bdc the data plane context. + * + * \returns next usable id. + */ +static uint16_t bfd_dplane_next_id(struct bfd_dplane_ctx *bdc) +{ + bdc->last_id++; + + /* Don't use reserved id `0`. */ + if (bdc->last_id == 0) + bdc->last_id = 1; + + return bdc->last_id; +} + +static ssize_t bfd_dplane_flush(struct bfd_dplane_ctx *bdc) +{ + ssize_t total = 0; + int rv; + + while (STREAM_READABLE(bdc->outbuf)) { + /* Flush buffer contents to socket. */ + rv = stream_flush(bdc->outbuf, bdc->sock); + if (rv == -1) { + /* Interruption: try again. */ + if (errno == EAGAIN || errno == EWOULDBLOCK + || errno == EINTR) + continue; + + zlog_warn("%s: socket failed: %s", __func__, + strerror(errno)); + bfd_dplane_ctx_free(bdc); + return 0; + } + if (rv == 0) { + if (bglobal.debug_dplane) + zlog_info("%s: connection closed", __func__); + + bfd_dplane_ctx_free(bdc); + return 0; + } + + /* Account total written. */ + total += rv; + + /* Account output bytes. */ + bdc->out_bytes += (uint64_t)rv; + + /* Forward pointer. */ + stream_forward_getp(bdc->outbuf, (size_t)rv); + } + + /* Make more space for new data. */ + stream_pulldown(bdc->outbuf); + + /* Disable write ready events. */ + EVENT_OFF(bdc->outbufev); + + return total; +} + +static void bfd_dplane_write(struct event *t) +{ + struct bfd_dplane_ctx *bdc = EVENT_ARG(t); + + /* Handle connection stage. */ + if (bdc->connecting && bfd_dplane_client_connecting(bdc)) + return; + + bfd_dplane_flush(bdc); +} + +static void +bfd_dplane_session_state_change(struct bfd_dplane_ctx *bdc, + const struct bfddp_state_change *state) +{ + struct bfd_session *bs; + uint32_t flags; + int old_state; + + /* Look up session. */ + bs = bfd_id_lookup(ntohl(state->lid)); + if (bs == NULL) { + if (bglobal.debug_dplane) + zlog_debug("%s: failed to find session to update", + __func__); + return; + } + + flags = ntohl(state->remote_flags); + old_state = bs->ses_state; + + /* Update session state. */ + bs->ses_state = state->state; + bs->remote_diag = state->diagnostics; + bs->discrs.remote_discr = ntohl(state->rid); + bs->remote_cbit = !!(flags & RBIT_CPI); + bs->remote_detect_mult = state->detection_multiplier; + bs->remote_timers.desired_min_tx = ntohl(state->desired_tx); + bs->remote_timers.required_min_rx = ntohl(state->required_rx); + bs->remote_timers.required_min_echo = ntohl(state->required_echo_rx); + + /* Notify and update counters. */ + control_notify(bs, bs->ses_state); + + /* No state change. */ + if (old_state == bs->ses_state) + return; + + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + case PTM_BFD_DOWN: + /* Both states mean down. */ + if (old_state == PTM_BFD_ADM_DOWN || old_state == PTM_BFD_DOWN) + break; + + monotime(&bs->downtime); + bs->stats.session_down++; + break; + case PTM_BFD_UP: + monotime(&bs->uptime); + bs->stats.session_up++; + break; + case PTM_BFD_INIT: + /* NOTHING */ + break; + + default: + zlog_warn("%s: unhandled new state %d", __func__, + bs->ses_state); + break; + } + + if (bglobal.debug_peer_event) + zlog_debug("state-change: [data plane: %s] %s -> %s", + bs_to_string(bs), state_list[old_state].str, + state_list[bs->ses_state].str); +} + +/** + * Enqueue message in output buffer. + * + * \param[in,out] bdc data plane client context. + * \param[in] buf the message to buffer. + * \param[in] buflen the amount of bytes to buffer. + * + * \returns `-1` on failure (buffer full) or `0` on success. + */ +static int bfd_dplane_enqueue(struct bfd_dplane_ctx *bdc, const void *buf, + size_t buflen) +{ + size_t rlen; + + /* Handle not connected yet client. */ + if (bdc->client && bdc->sock == -1) + return -1; + + /* Not enough space. */ + if (buflen > STREAM_WRITEABLE(bdc->outbuf)) { + bdc->out_fullev++; + return -1; + } + + /* Show debug message if active. */ + bfd_dplane_debug_message((struct bfddp_message *)buf); + + /* Buffer the message. */ + stream_write(bdc->outbuf, buf, buflen); + + /* Account message as sent. */ + bdc->out_msgs++; + /* Register peak buffered bytes. */ + rlen = STREAM_READABLE(bdc->outbuf); + if (bdc->out_bytes_peak < rlen) + bdc->out_bytes_peak = rlen; + + /* Schedule if it is not yet. */ + if (bdc->outbufev == NULL) + event_add_write(master, bfd_dplane_write, bdc, bdc->sock, + &bdc->outbufev); + + return 0; +} + +static void bfd_dplane_echo_request_handle(struct bfd_dplane_ctx *bdc, + const struct bfddp_message *bm) +{ + struct bfddp_message msg = {}; + uint16_t msglen = sizeof(msg.header) + sizeof(msg.data.echo); + struct timeval tv; + + gettimeofday(&tv, NULL); + + /* Prepare header. */ + msg.header.version = BFD_DP_VERSION; + msg.header.type = htons(ECHO_REPLY); + msg.header.length = htons(msglen); + + /* Prepare payload. */ + msg.data.echo.dp_time = bm->data.echo.dp_time; + msg.data.echo.bfdd_time = + htobe64((uint64_t)((tv.tv_sec * 1000000) + tv.tv_usec)); + + /* Enqueue for output. */ + bfd_dplane_enqueue(bdc, &msg, msglen); +} + +static void bfd_dplane_handle_message(struct bfddp_message *msg, void *arg) +{ + enum bfddp_message_type bmt; + struct bfd_dplane_ctx *bdc = arg; + + /* Call the appropriated handler. */ + bmt = ntohs(msg->header.type); + switch (bmt) { + case ECHO_REQUEST: + bfd_dplane_echo_request_handle(bdc, msg); + break; + case BFD_STATE_CHANGE: + bfd_dplane_session_state_change(bdc, &msg->data.state); + break; + case ECHO_REPLY: + /* NOTHING: we don't do anything with this information. */ + break; + case DP_ADD_SESSION: + case DP_DELETE_SESSION: + case DP_REQUEST_SESSION_COUNTERS: + /* NOTHING: we are not supposed to receive this. */ + break; + case BFD_SESSION_COUNTERS: + /* + * NOTHING: caller of DP_REQUEST_SESSION_COUNTERS should + * handle this with `bfd_dplane_expect`. + */ + break; + + default: + zlog_debug("%s: unhandled message type %d", __func__, bmt); + break; + } +} + +/** + * Reads the socket immediately to receive data plane answer to query. + * + * \param bdc the data plane context. + * \param id the message ID waiting response. + * \param cb the callback to call when ready. + * \param arg the callback argument. + * + * \return + * `-2` on unavailability (try again), `-1` on failure or `0` on success. + */ +static int bfd_dplane_expect(struct bfd_dplane_ctx *bdc, uint16_t id, + bfd_dplane_expect_cb cb, void *arg) +{ + struct bfddp_message_header *bh; + size_t rlen = 0, reads = 0; + ssize_t rv; + + /* + * Don't attempt to read if buffer is full, otherwise we'll get a + * bogus 'connection closed' signal (rv == 0). + */ + if (bdc->inbuf->endp == bdc->inbuf->size) + goto skip_read; + +read_again: + /* Attempt to read message from client. */ + rv = stream_read_try(bdc->inbuf, bdc->sock, + STREAM_WRITEABLE(bdc->inbuf)); + if (rv == 0) { + if (bglobal.debug_dplane) + zlog_info("%s: socket closed", __func__); + + bfd_dplane_ctx_free(bdc); + return -1; + } + if (rv == -1) { + zlog_warn("%s: socket failed: %s", __func__, strerror(errno)); + bfd_dplane_ctx_free(bdc); + return -1; + } + + /* We got interrupted, reschedule read. */ + if (rv == -2) + return -2; + + /* Account read bytes. */ + bdc->in_bytes += (uint64_t)rv; + /* Register peak buffered bytes. */ + rlen = STREAM_READABLE(bdc->inbuf); + if (bdc->in_bytes_peak < rlen) + bdc->in_bytes_peak = rlen; + +skip_read: + while (rlen > 0) { + bh = (struct bfddp_message_header *)stream_pnt(bdc->inbuf); + /* Not enough data read. */ + if (ntohs(bh->length) > rlen) + goto read_again; + + /* Account full message read. */ + bdc->in_msgs++; + + /* Account this message as whole read for buffer reorganize. */ + reads++; + + /* Check for bad version. */ + if (bh->version != BFD_DP_VERSION) { + zlog_err("%s: bad data plane client version: %d", + __func__, bh->version); + return -1; + } + + /* Show debug message if active. */ + bfd_dplane_debug_message((struct bfddp_message *)bh); + + /* + * Handle incoming message with callback if the ID matches, + * otherwise fallback to default handler. + */ + if (id && ntohs(bh->id) == id) + cb((struct bfddp_message *)bh, arg); + else + bfd_dplane_handle_message((struct bfddp_message *)bh, + bdc); + + /* Advance current read pointer. */ + stream_forward_getp(bdc->inbuf, ntohs(bh->length)); + + /* Reduce the buffer available bytes. */ + rlen -= ntohs(bh->length); + + /* Reorganize buffer to handle more bytes read. */ + if (reads >= 3) { + stream_pulldown(bdc->inbuf); + reads = 0; + } + + /* We found the message, return to caller. */ + if (id && ntohs(bh->id) == id) + break; + } + + return 0; +} + +static void bfd_dplane_read(struct event *t) +{ + struct bfd_dplane_ctx *bdc = EVENT_ARG(t); + int rv; + + rv = bfd_dplane_expect(bdc, 0, bfd_dplane_handle_message, NULL); + if (rv == -1) + return; + + stream_pulldown(bdc->inbuf); + event_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev); +} + +static void _bfd_session_register_dplane(struct hash_bucket *hb, void *arg) +{ + struct bfd_session *bs = hb->data; + struct bfd_dplane_ctx *bdc = arg; + + if (bs->bdc != NULL) + return; + + /* Disable software session. */ + bfd_session_disable(bs); + + /* Move session to data plane. */ + _bfd_dplane_add_session(bdc, bs); +} + +static struct bfd_dplane_ctx *bfd_dplane_ctx_new(int sock) +{ + struct bfd_dplane_ctx *bdc; + + bdc = XCALLOC(MTYPE_BFDD_DPLANE_CTX, sizeof(*bdc)); + + bdc->sock = sock; + bdc->inbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE); + bdc->outbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE); + + /* If not socket ready, skip read and session registration. */ + if (sock == -1) + return bdc; + + event_add_read(master, bfd_dplane_read, bdc, sock, &bdc->inbufev); + + /* Register all unattached sessions. */ + bfd_key_iterate(_bfd_session_register_dplane, bdc); + + return bdc; +} + +static void _bfd_session_unregister_dplane(struct hash_bucket *hb, void *arg) +{ + struct bfd_session *bs = hb->data; + struct bfd_dplane_ctx *bdc = arg; + + if (bs->bdc != bdc) + return; + + bs->bdc = NULL; + + /* Fallback to software. */ + bfd_session_enable(bs); +} + +static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc) +{ + if (bglobal.debug_dplane) + zlog_debug("%s: terminating data plane client %d", __func__, + bdc->sock); + + /* Client mode has special treatment. */ + if (bdc->client) { + /* Disable connection event if any. */ + EVENT_OFF(bdc->connectev); + + /* Normal treatment on shutdown. */ + if (bglobal.bg_shutdown) + goto free_resources; + + /* Attempt reconnection. */ + socket_close(&bdc->sock); + EVENT_OFF(bdc->inbufev); + EVENT_OFF(bdc->outbufev); + event_add_timer(master, bfd_dplane_client_connect, bdc, 3, + &bdc->connectev); + return; + } + +free_resources: + /* Remove from the list of attached data planes. */ + TAILQ_REMOVE(&bglobal.bg_dplaneq, bdc, entry); + + /* Detach all associated sessions. */ + if (bglobal.bg_shutdown == false) + bfd_key_iterate(_bfd_session_unregister_dplane, bdc); + + /* Free resources. */ + socket_close(&bdc->sock); + stream_free(bdc->inbuf); + stream_free(bdc->outbuf); + EVENT_OFF(bdc->inbufev); + EVENT_OFF(bdc->outbufev); + XFREE(MTYPE_BFDD_DPLANE_CTX, bdc); +} + +static void _bfd_dplane_session_fill(const struct bfd_session *bs, + struct bfddp_message *msg) +{ + uint16_t msglen = sizeof(msg->header) + sizeof(msg->data.session); + + /* Message header. */ + msg->header.version = BFD_DP_VERSION; + msg->header.length = ntohs(msglen); + msg->header.type = ntohs(DP_ADD_SESSION); + + /* Message payload. */ + msg->data.session.dst = bs->key.peer; + msg->data.session.src = bs->key.local; + msg->data.session.detect_mult = bs->detect_mult; + + if (bs->ifp) { + msg->data.session.ifindex = htonl(bs->ifp->ifindex); + strlcpy(msg->data.session.ifname, bs->ifp->name, + sizeof(msg->data.session.ifname)); + } + if (bs->flags & BFD_SESS_FLAG_MH) { + msg->data.session.flags |= SESSION_MULTIHOP; + msg->data.session.ttl = bs->mh_ttl; + } else + msg->data.session.ttl = BFD_TTL_VAL; + + if (bs->flags & BFD_SESS_FLAG_IPV6) + msg->data.session.flags |= SESSION_IPV6; + if (bs->flags & BFD_SESS_FLAG_ECHO) + msg->data.session.flags |= SESSION_ECHO; + if (bs->flags & BFD_SESS_FLAG_CBIT) + msg->data.session.flags |= SESSION_CBIT; + if (bs->flags & BFD_SESS_FLAG_PASSIVE) + msg->data.session.flags |= SESSION_PASSIVE; + if (bs->flags & BFD_SESS_FLAG_SHUTDOWN) + msg->data.session.flags |= SESSION_SHUTDOWN; + + msg->data.session.flags = htonl(msg->data.session.flags); + msg->data.session.lid = htonl(bs->discrs.my_discr); + msg->data.session.min_tx = htonl(bs->timers.desired_min_tx); + msg->data.session.min_rx = htonl(bs->timers.required_min_rx); + msg->data.session.min_echo_tx = htonl(bs->timers.desired_min_echo_tx); + msg->data.session.min_echo_rx = htonl(bs->timers.required_min_echo_rx); +} + +static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc, + struct bfd_session *bs) +{ + int rv; + + /* Associate session. */ + bs->bdc = bdc; + + /* Reset previous state. */ + bs->remote_diag = 0; + bs->local_diag = 0; + bs->ses_state = PTM_BFD_DOWN; + + /* Enqueue message to data plane client. */ + rv = bfd_dplane_update_session(bs); + if (rv != 0) + bs->bdc = NULL; + + return rv; +} + +static void _bfd_dplane_update_session_counters(struct bfddp_message *msg, + void *arg) +{ + struct bfd_session *bs = arg; + + bs->stats.rx_ctrl_pkt = + be64toh(msg->data.session_counters.control_input_packets); + bs->stats.tx_ctrl_pkt = + be64toh(msg->data.session_counters.control_output_packets); + bs->stats.rx_echo_pkt = + be64toh(msg->data.session_counters.echo_input_packets); + bs->stats.tx_echo_pkt = + be64toh(msg->data.session_counters.echo_output_bytes); +} + +/** + * Send message to data plane requesting the session counters. + * + * \param bs the BFD session. + * + * \returns `0` on failure or the request id. + */ +static uint16_t bfd_dplane_request_counters(const struct bfd_session *bs) +{ + struct bfddp_message msg = {}; + size_t msglen = sizeof(msg.header) + sizeof(msg.data.counters_req); + + /* Fill header information. */ + msg.header.version = BFD_DP_VERSION; + msg.header.length = htons(msglen); + msg.header.type = htons(DP_REQUEST_SESSION_COUNTERS); + msg.header.id = htons(bfd_dplane_next_id(bs->bdc)); + + /* Session to get counters. */ + msg.data.counters_req.lid = htonl(bs->discrs.my_discr); + + /* If enqueue failed, let caller know. */ + if (bfd_dplane_enqueue(bs->bdc, &msg, msglen) == -1) + return 0; + + /* Flush socket. */ + bfd_dplane_flush(bs->bdc); + + return ntohs(msg.header.id); +} + +/* + * Data plane listening socket. + */ +static void bfd_dplane_accept(struct event *t) +{ + struct bfd_global *bg = EVENT_ARG(t); + struct bfd_dplane_ctx *bdc; + int sock; + + /* Accept new connection. */ + sock = accept(bg->bg_dplane_sock, NULL, 0); + if (sock == -1) { + zlog_warn("%s: accept failed: %s", __func__, strerror(errno)); + goto reschedule_and_return; + } + + /* Create and handle new connection. */ + bdc = bfd_dplane_ctx_new(sock); + TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry); + + if (bglobal.debug_dplane) + zlog_debug("%s: new data plane client connected", __func__); + +reschedule_and_return: + event_add_read(master, bfd_dplane_accept, bg, bg->bg_dplane_sock, + &bglobal.bg_dplane_sockev); +} + +/* + * Data plane connecting socket. + */ +static void _bfd_dplane_client_bootstrap(struct bfd_dplane_ctx *bdc) +{ + bdc->connecting = false; + + /* Clean up buffers. */ + stream_reset(bdc->inbuf); + stream_reset(bdc->outbuf); + + /* Ask for read notifications. */ + event_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev); + + /* Remove all sessions then register again to send them all. */ + bfd_key_iterate(_bfd_session_unregister_dplane, bdc); + bfd_key_iterate(_bfd_session_register_dplane, bdc); +} + +static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc) +{ + int rv; + socklen_t rvlen = sizeof(rv); + + /* Make sure `errno` is reset, then test `getsockopt` success. */ + errno = 0; + if (getsockopt(bdc->sock, SOL_SOCKET, SO_ERROR, &rv, &rvlen) == -1) + rv = -1; + + /* Connection successful. */ + if (rv == 0) { + if (bglobal.debug_dplane) + zlog_debug("%s: connected to server: %d", __func__, + bdc->sock); + + _bfd_dplane_client_bootstrap(bdc); + return false; + } + + switch (rv) { + case EINTR: + case EAGAIN: + case EALREADY: + case EINPROGRESS: + /* non error, wait more. */ + return true; + + default: + zlog_warn("%s: connection failed: %s", __func__, + strerror(errno)); + bfd_dplane_ctx_free(bdc); + return true; + } +} + +static void bfd_dplane_client_connect(struct event *t) +{ + struct bfd_dplane_ctx *bdc = EVENT_ARG(t); + int rv, sock; + socklen_t rvlen = sizeof(rv); + + /* Allocate new socket. */ + sock = socket(bdc->addr.sa.sa_family, SOCK_STREAM, 0); + if (sock == -1) { + zlog_warn("%s: failed to initialize socket: %s", __func__, + strerror(errno)); + goto reschedule_connect; + } + + /* Set non blocking socket. */ + set_nonblocking(sock); + + /* Set 'no delay' (disables nagle algorithm) for IPv4/IPv6. */ + rv = 1; + if (bdc->addr.sa.sa_family != AF_UNIX + && setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &rv, rvlen) == -1) + zlog_warn("%s: TCP_NODELAY: %s", __func__, strerror(errno)); + + /* Attempt to connect. */ + rv = connect(sock, &bdc->addr.sa, bdc->addrlen); + if (rv == -1 && (errno != EINPROGRESS && errno != EAGAIN)) { + zlog_warn("%s: data plane connection failed: %s", __func__, + strerror(errno)); + goto reschedule_connect; + } + + bdc->sock = sock; + if (rv == -1) { + if (bglobal.debug_dplane) + zlog_debug("%s: server connection in progress: %d", + __func__, sock); + + /* If we are not connected yet, ask for write notifications. */ + bdc->connecting = true; + event_add_write(master, bfd_dplane_write, bdc, bdc->sock, + &bdc->outbufev); + } else { + if (bglobal.debug_dplane) + zlog_debug("%s: server connection: %d", __func__, sock); + + /* Otherwise just start accepting data. */ + _bfd_dplane_client_bootstrap(bdc); + } + +reschedule_connect: + EVENT_OFF(bdc->inbufev); + EVENT_OFF(bdc->outbufev); + socket_close(&sock); + event_add_timer(master, bfd_dplane_client_connect, bdc, 3, + &bdc->connectev); +} + +static void bfd_dplane_client_init(const struct sockaddr *sa, socklen_t salen) +{ + struct bfd_dplane_ctx *bdc; + + /* Allocate context and copy address for reconnection. */ + bdc = bfd_dplane_ctx_new(-1); + if (salen <= sizeof(bdc->addr)) { + memcpy(&bdc->addr, sa, salen); + bdc->addrlen = sizeof(bdc->addr); + } else { + memcpy(&bdc->addr, sa, sizeof(bdc->addr)); + bdc->addrlen = sizeof(bdc->addr); + zlog_warn("%s: server address truncated (from %d to %d)", + __func__, salen, bdc->addrlen); + } + + bdc->client = true; + + event_add_timer(master, bfd_dplane_client_connect, bdc, 0, + &bdc->connectev); + + /* Insert into data plane lists. */ + TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry); +} + +/** + * Termination phase of the distributed BFD infrastructure: free all allocated + * resources. + */ +static int bfd_dplane_finish_late(void) +{ + struct bfd_dplane_ctx *bdc; + + if (bglobal.debug_dplane) + zlog_debug("%s: terminating distributed BFD", __func__); + + /* Free all data plane client contexts. */ + while ((bdc = TAILQ_FIRST(&bglobal.bg_dplaneq)) != NULL) + bfd_dplane_ctx_free(bdc); + + /* Cancel accept thread and close socket. */ + EVENT_OFF(bglobal.bg_dplane_sockev); + close(bglobal.bg_dplane_sock); + + return 0; +} + +/* + * Data plane exported functions. + */ +void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client) +{ + int sock; + + zlog_info("initializing distributed BFD"); + + /* Initialize queue header. */ + TAILQ_INIT(&bglobal.bg_dplaneq); + + /* Initialize listening socket. */ + bglobal.bg_dplane_sock = -1; + + /* Observe shutdown events. */ + hook_register(frr_fini, bfd_dplane_finish_late); + + /* Handle client mode. */ + if (client) { + bfd_dplane_client_init(sa, salen); + return; + } + + /* + * Data plane socket creation: + * - Set REUSEADDR option for taking over previously open socket. + * - Bind to address requested (maybe IPv4, IPv6, UNIX etc...). + * - Listen on that address for new connections. + * - Ask to be waken up when a new connection comes. + */ + sock = socket(sa->sa_family, SOCK_STREAM, 0); + if (sock == -1) { + zlog_warn("%s: failed to initialize socket: %s", __func__, + strerror(errno)); + return; + } + + if (sockopt_reuseaddr(sock) == -1) { + zlog_warn("%s: failed to set reuseaddr: %s", __func__, + strerror(errno)); + close(sock); + return; + } + + /* Handle UNIX socket: delete previous socket if any. */ + if (sa->sa_family == AF_UNIX) + unlink(((struct sockaddr_un *)sa)->sun_path); + + if (bind(sock, sa, salen) == -1) { + zlog_warn("%s: failed to bind socket: %s", __func__, + strerror(errno)); + close(sock); + return; + } + + if (listen(sock, SOMAXCONN) == -1) { + zlog_warn("%s: failed to put socket on listen: %s", __func__, + strerror(errno)); + close(sock); + return; + } + + bglobal.bg_dplane_sock = sock; + event_add_read(master, bfd_dplane_accept, &bglobal, sock, + &bglobal.bg_dplane_sockev); +} + +int bfd_dplane_add_session(struct bfd_session *bs) +{ + struct bfd_dplane_ctx *bdc; + + /* Select a data plane client to install session. */ + TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) { + if (_bfd_dplane_add_session(bdc, bs) == 0) + return 0; + } + + return -1; +} + +int bfd_dplane_update_session(const struct bfd_session *bs) +{ + struct bfddp_message msg = {}; + + if (bs->bdc == NULL) + return 0; + + _bfd_dplane_session_fill(bs, &msg); + + /* Enqueue message to data plane client. */ + return bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length)); +} + +int bfd_dplane_delete_session(struct bfd_session *bs) +{ + struct bfddp_message msg = {}; + int rv; + + /* Not using data plane, just return success. */ + if (bs->bdc == NULL) + return 0; + + /* Fill most of the common fields. */ + _bfd_dplane_session_fill(bs, &msg); + + /* Change the message type. */ + msg.header.type = ntohs(DP_DELETE_SESSION); + + /* Enqueue message to data plane client. */ + rv = bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length)); + + /* Remove association. */ + bs->bdc = NULL; + + return rv; +} + +/* + * Data plane CLI. + */ +void bfd_dplane_show_counters(struct vty *vty) +{ + struct bfd_dplane_ctx *bdc; + +#define SHOW_COUNTER(label, counter, formatter) \ + vty_out(vty, "%28s: %" formatter "\n", (label), (counter)) + + vty_out(vty, "%28s\n%28s\n", "Data plane", "=========="); + TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) { + SHOW_COUNTER("File descriptor", bdc->sock, "d"); + SHOW_COUNTER("Input bytes", bdc->in_bytes, PRIu64); + SHOW_COUNTER("Input bytes peak", bdc->in_bytes_peak, PRIu64); + SHOW_COUNTER("Input messages", bdc->in_msgs, PRIu64); + SHOW_COUNTER("Input current usage", STREAM_READABLE(bdc->inbuf), + "zu"); + SHOW_COUNTER("Output bytes", bdc->out_bytes, PRIu64); + SHOW_COUNTER("Output bytes peak", bdc->out_bytes_peak, PRIu64); + SHOW_COUNTER("Output messages", bdc->out_msgs, PRIu64); + SHOW_COUNTER("Output full events", bdc->out_fullev, PRIu64); + SHOW_COUNTER("Output current usage", + STREAM_READABLE(bdc->inbuf), "zu"); + vty_out(vty, "\n"); + } +#undef SHOW_COUNTER +} + +int bfd_dplane_update_session_counters(struct bfd_session *bs) +{ + uint16_t id; + int rv; + + /* If session is not using data plane, then just return success. */ + if (bs->bdc == NULL) + return 0; + + /* Make the request. */ + id = bfd_dplane_request_counters(bs); + if (id == 0) { + zlog_debug("%s: counters request failed", __func__); + return -1; + } + + /* Handle interruptions. */ + do { + rv = bfd_dplane_expect(bs->bdc, id, + _bfd_dplane_update_session_counters, bs); + } while (rv == -2); + + return rv; +} diff --git a/bfdd/event.c b/bfdd/event.c new file mode 100644 index 0000000..e797e71 --- /dev/null +++ b/bfdd/event.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * event.c: implements the BFD loop event handlers. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include "bfd.h" + +void tv_normalize(struct timeval *tv); + +void tv_normalize(struct timeval *tv) +{ + /* Remove seconds part from microseconds. */ + tv->tv_sec = tv->tv_usec / 1000000; + tv->tv_usec = tv->tv_usec % 1000000; +} + +void bfd_recvtimer_update(struct bfd_session *bs) +{ + struct timeval tv = {.tv_sec = 0, .tv_usec = bs->detect_TO}; + + /* Remove previous schedule if any. */ + bfd_recvtimer_delete(bs); + + /* Don't add event if peer is deactivated. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || + bs->sock == -1) + return; + + tv_normalize(&tv); + + event_add_timer_tv(master, bfd_recvtimer_cb, bs, &tv, + &bs->recvtimer_ev); +} + +void bfd_echo_recvtimer_update(struct bfd_session *bs) +{ + struct timeval tv = {.tv_sec = 0, .tv_usec = bs->echo_detect_TO}; + + /* Remove previous schedule if any. */ + bfd_echo_recvtimer_delete(bs); + + /* Don't add event if peer is deactivated. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || + bs->sock == -1) + return; + + tv_normalize(&tv); + + event_add_timer_tv(master, bfd_echo_recvtimer_cb, bs, &tv, + &bs->echo_recvtimer_ev); +} + +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter) +{ + struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; + + /* Remove previous schedule if any. */ + bfd_xmttimer_delete(bs); + + /* Don't add event if peer is deactivated. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || + bs->sock == -1) + return; + + tv_normalize(&tv); + + event_add_timer_tv(master, bfd_xmt_cb, bs, &tv, &bs->xmttimer_ev); +} + +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter) +{ + struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; + + /* Remove previous schedule if any. */ + bfd_echo_xmttimer_delete(bs); + + /* Don't add event if peer is deactivated. */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) || + bs->sock == -1) + return; + + tv_normalize(&tv); + + event_add_timer_tv(master, bfd_echo_xmt_cb, bs, &tv, + &bs->echo_xmttimer_ev); +} + +void bfd_recvtimer_delete(struct bfd_session *bs) +{ + EVENT_OFF(bs->recvtimer_ev); +} + +void bfd_echo_recvtimer_delete(struct bfd_session *bs) +{ + EVENT_OFF(bs->echo_recvtimer_ev); +} + +void bfd_xmttimer_delete(struct bfd_session *bs) +{ + EVENT_OFF(bs->xmttimer_ev); +} + +void bfd_echo_xmttimer_delete(struct bfd_session *bs) +{ + EVENT_OFF(bs->echo_xmttimer_ev); +} diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c new file mode 100644 index 0000000..b5ab2ef --- /dev/null +++ b/bfdd/ptm_adapter.c @@ -0,0 +1,986 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BFD PTM adapter code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + */ + +#include + +#include "lib/libfrr.h" +#include "lib/queue.h" +#include "lib/stream.h" +#include "lib/zclient.h" +#include "lib/printfrr.h" + +#include "lib/bfd.h" + +#include "bfd.h" + +/* + * Data structures + */ +struct ptm_client_notification { + struct bfd_session *pcn_bs; + struct ptm_client *pcn_pc; + + TAILQ_ENTRY(ptm_client_notification) pcn_entry; +}; +TAILQ_HEAD(pcnqueue, ptm_client_notification); + +struct ptm_client { + uint32_t pc_pid; + struct pcnqueue pc_pcnqueue; + + TAILQ_ENTRY(ptm_client) pc_entry; +}; +TAILQ_HEAD(pcqueue, ptm_client); + +static struct pcqueue pcqueue; +static struct zclient *zclient; + + +/* + * Prototypes + */ +static int _ptm_msg_address(struct stream *msg, int family, const void *addr); + +static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa); +static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, + struct bfd_peer_cfg *bpc, struct ptm_client **pc); + +static struct ptm_client *pc_lookup(uint32_t pid); +static struct ptm_client *pc_new(uint32_t pid); +static void pc_free(struct ptm_client *pc); +static void pc_free_all(void); +static struct ptm_client_notification *pcn_new(struct ptm_client *pc, + struct bfd_session *bs); +static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, + struct bfd_session *bs); +static void pcn_free(struct ptm_client_notification *pcn); + + +static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id); +static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id); +static void bfdd_client_register(struct stream *msg); +static void bfdd_client_deregister(struct stream *msg); + +/* + * Functions + */ +PRINTFRR(2, 3) +static void debug_printbpc(const struct bfd_peer_cfg *bpc, const char *fmt, ...) +{ + char timers[3][128] = {}; + char minttl_str[32] = {}; + char addr[3][128] = {}; + char profile[128] = {}; + char cbit_str[32]; + char msgbuf[512]; + va_list vl; + + /* Avoid debug calculations if it's disabled. */ + if (bglobal.debug_zebra == false) + return; + + snprintf(addr[0], sizeof(addr[0]), "peer:%s", satostr(&bpc->bpc_peer)); + if (bpc->bpc_local.sa_sin.sin_family) + snprintf(addr[1], sizeof(addr[1]), " local:%s", + satostr(&bpc->bpc_local)); + + if (bpc->bpc_has_localif) + snprintf(addr[2], sizeof(addr[2]), " ifname:%s", + bpc->bpc_localif); + + if (bpc->bpc_has_vrfname) + snprintf(addr[2], sizeof(addr[2]), " vrf:%s", bpc->bpc_vrfname); + + if (bpc->bpc_has_recvinterval) + snprintfrr(timers[0], sizeof(timers[0]), " rx:%" PRIu64, + bpc->bpc_recvinterval); + + if (bpc->bpc_has_txinterval) + snprintfrr(timers[1], sizeof(timers[1]), " tx:%" PRIu64, + bpc->bpc_recvinterval); + + if (bpc->bpc_has_detectmultiplier) + snprintf(timers[2], sizeof(timers[2]), " detect-multiplier:%d", + bpc->bpc_detectmultiplier); + + snprintf(cbit_str, sizeof(cbit_str), " cbit:0x%02x", bpc->bpc_cbit); + + if (bpc->bpc_has_minimum_ttl) + snprintf(minttl_str, sizeof(minttl_str), " minimum-ttl:%d", + bpc->bpc_minimum_ttl); + + if (bpc->bpc_has_profile) + snprintf(profile, sizeof(profile), " profile:%s", + bpc->bpc_profile); + + va_start(vl, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, vl); + va_end(vl); + + zlog_debug("%s [mhop:%s %s%s%s%s%s%s%s%s%s]", msgbuf, + bpc->bpc_mhop ? "yes" : "no", addr[0], addr[1], addr[2], + timers[0], timers[1], timers[2], cbit_str, minttl_str, + profile); +} + +static void _ptm_bfd_session_del(struct bfd_session *bs, uint8_t diag) +{ + if (bglobal.debug_peer_event) + zlog_debug("session-delete: %s", bs_to_string(bs)); + + /* Change state and notify peer. */ + bs->ses_state = PTM_BFD_DOWN; + bs->local_diag = diag; + ptm_bfd_snd(bs, 0); + + /* Session reached refcount == 0, lets delete it. */ + if (bs->refcount == 0) { + /* + * Sanity check: if there is a refcount bug, we can't delete + * the session a user configured manually. Lets leave a + * message here so we can catch the bug if it exists. + */ + if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG)) { + zlog_err( + "ptm-del-session: [%s] session refcount is zero but it was configured by CLI", + bs_to_string(bs)); + } else { + control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); + bfd_session_free(bs); + } + } +} + +static int _ptm_msg_address(struct stream *msg, int family, const void *addr) +{ + stream_putc(msg, family); + + switch (family) { + case AF_INET: + stream_put(msg, addr, sizeof(struct in_addr)); + stream_putc(msg, 32); + break; + + case AF_INET6: + stream_put(msg, addr, sizeof(struct in6_addr)); + stream_putc(msg, 128); + break; + + default: + assert(0); + break; + } + + return 0; +} + +int ptm_bfd_notify(struct bfd_session *bs, uint8_t notify_state) +{ + struct stream *msg; + + bs->stats.znotification++; + + /* + * Message format: + * - header: command, vrf + * - l: interface index + * - c: family + * - AF_INET: + * - 4 bytes: ipv4 + * - AF_INET6: + * - 16 bytes: ipv6 + * - c: prefix length + * - l: bfd status + * - c: family + * - AF_INET: + * - 4 bytes: ipv4 + * - AF_INET6: + * - 16 bytes: ipv6 + * - c: prefix length + * - c: cbit + * + * Commands: ZEBRA_BFD_DEST_REPLAY + * + * q(64), l(32), w(16), c(8) + */ + msg = zclient->obuf; + stream_reset(msg); + + /* TODO: VRF handling */ + if (bs->vrf) + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, bs->vrf->vrf_id); + else + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); + + /* This header will be handled by `zebra_ptm.c`. */ + stream_putl(msg, ZEBRA_INTERFACE_BFD_DEST_UPDATE); + + /* NOTE: Interface is a shortcut to avoid comparing source address. */ + if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && bs->ifp != NULL) + stream_putl(msg, bs->ifp->ifindex); + else + stream_putl(msg, IFINDEX_INTERNAL); + + /* BFD destination prefix information. */ + _ptm_msg_address(msg, bs->key.family, &bs->key.peer); + + /* BFD status */ + switch (notify_state) { + case PTM_BFD_UP: + stream_putl(msg, BFD_STATUS_UP); + break; + + case PTM_BFD_ADM_DOWN: + stream_putl(msg, BFD_STATUS_ADMIN_DOWN); + break; + + case PTM_BFD_DOWN: + case PTM_BFD_INIT: + stream_putl(msg, BFD_STATUS_DOWN); + break; + + default: + stream_putl(msg, BFD_STATUS_UNKNOWN); + break; + } + + /* BFD source prefix information. */ + _ptm_msg_address(msg, bs->key.family, &bs->key.local); + + stream_putc(msg, bs->remote_cbit); + + /* Write packet size. */ + stream_putw_at(msg, 0, stream_get_endp(msg)); + + return zclient_send_message(zclient); +} + +static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa) +{ + uint16_t family; + + STREAM_GETW(msg, family); + + switch (family) { + case AF_INET: + sa->sa_sin.sin_family = family; + STREAM_GET(&sa->sa_sin.sin_addr, msg, + sizeof(sa->sa_sin.sin_addr)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return; + + case AF_INET6: + sa->sa_sin6.sin6_family = family; + STREAM_GET(&sa->sa_sin6.sin6_addr, msg, + sizeof(sa->sa_sin6.sin6_addr)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return; + + default: + zlog_warn("ptm-read-address: invalid family: %d", family); + break; + } + +stream_failure: + memset(sa, 0, sizeof(*sa)); +} + +static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id, + struct bfd_peer_cfg *bpc, struct ptm_client **pc) +{ + uint32_t pid; + size_t ifnamelen; + + /* + * Register/Deregister/Update Message format: + * + * Old format (being used by PTM BFD). + * - header: Command, VRF + * - l: pid + * - w: family + * - AF_INET: + * - l: destination ipv4 + * - AF_INET6: + * - 16 bytes: destination IPv6 + * - command != ZEBRA_BFD_DEST_DEREGISTER + * - l: min_rx + * - l: min_tx + * - c: detect multiplier + * - c: is_multihop? + * - multihop: + * - w: family + * - AF_INET: + * - l: source IPv4 address + * - AF_INET6: + * - 16 bytes: source IPv6 address + * - c: ttl + * - no multihop + * - AF_INET6: + * - w: family + * - 16 bytes: source IPv6 address + * - c: ifname length + * - X bytes: interface name + * + * New format: + * - header: Command, VRF + * - l: pid + * - w: family + * - AF_INET: + * - l: destination IPv4 address + * - AF_INET6: + * - 16 bytes: destination IPv6 address + * - l: min_rx + * - l: min_tx + * - c: detect multiplier + * - c: is_multihop? + * - w: family + * - AF_INET: + * - l: source IPv4 address + * - AF_INET6: + * - 16 bytes: source IPv6 address + * - c: ttl + * - c: ifname length + * - X bytes: interface name + * - c: bfd_cbit + * - c: profile name length. + * - X bytes: profile name. + * + * q(64), l(32), w(16), c(8) + */ + + /* Initialize parameters return values. */ + memset(bpc, 0, sizeof(*bpc)); + *pc = NULL; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + *pc = pc_new(pid); + + /* Register/update peer information. */ + _ptm_msg_read_address(msg, &bpc->bpc_peer); + + /* Determine IP type from peer destination. */ + bpc->bpc_ipv4 = (bpc->bpc_peer.sa_sin.sin_family == AF_INET); + + /* Get peer configuration. */ + STREAM_GETL(msg, bpc->bpc_recvinterval); + bpc->bpc_has_recvinterval = + (bpc->bpc_recvinterval != BPC_DEF_RECEIVEINTERVAL); + + STREAM_GETL(msg, bpc->bpc_txinterval); + bpc->bpc_has_txinterval = + (bpc->bpc_txinterval != BPC_DEF_TRANSMITINTERVAL); + + STREAM_GETC(msg, bpc->bpc_detectmultiplier); + bpc->bpc_has_detectmultiplier = + (bpc->bpc_detectmultiplier != BPC_DEF_DETECTMULTIPLIER); + + /* Read (single|multi)hop and its options. */ + STREAM_GETC(msg, bpc->bpc_mhop); + + /* Read multihop source address and TTL. */ + _ptm_msg_read_address(msg, &bpc->bpc_local); + + /* Read the minimum TTL (0 means unset or invalid). */ + STREAM_GETC(msg, bpc->bpc_minimum_ttl); + if (bpc->bpc_minimum_ttl == 0) { + bpc->bpc_minimum_ttl = BFD_DEF_MHOP_TTL; + bpc->bpc_has_minimum_ttl = false; + } else { + bpc->bpc_minimum_ttl = (BFD_TTL_VAL + 1) - bpc->bpc_minimum_ttl; + bpc->bpc_has_minimum_ttl = true; + } + + /* + * Read interface name and make sure it fits our data + * structure, otherwise fail. + */ + STREAM_GETC(msg, ifnamelen); + if (ifnamelen >= sizeof(bpc->bpc_localif)) { + zlog_err("ptm-read: interface name is too big"); + return -1; + } + + bpc->bpc_has_localif = ifnamelen > 0; + if (bpc->bpc_has_localif) { + STREAM_GET(bpc->bpc_localif, msg, ifnamelen); + bpc->bpc_localif[ifnamelen] = 0; + } + + if (vrf_id != VRF_DEFAULT) { + struct vrf *vrf; + + vrf = vrf_lookup_by_id(vrf_id); + if (vrf) { + bpc->bpc_has_vrfname = true; + strlcpy(bpc->bpc_vrfname, vrf->name, sizeof(bpc->bpc_vrfname)); + } else { + zlog_err("ptm-read: vrf id %u could not be identified", + vrf_id); + return -1; + } + } else { + bpc->bpc_has_vrfname = true; + strlcpy(bpc->bpc_vrfname, VRF_DEFAULT_NAME, sizeof(bpc->bpc_vrfname)); + } + + /* Read control plane independant configuration. */ + STREAM_GETC(msg, bpc->bpc_cbit); + + /* Handle profile names. */ + STREAM_GETC(msg, ifnamelen); + bpc->bpc_has_profile = ifnamelen > 0; + if (bpc->bpc_has_profile) { + STREAM_GET(bpc->bpc_profile, msg, ifnamelen); + bpc->bpc_profile[ifnamelen] = 0; + } + + /* Sanity check: peer and local address must match IP types. */ + if (bpc->bpc_local.sa_sin.sin_family != AF_UNSPEC + && (bpc->bpc_local.sa_sin.sin_family + != bpc->bpc_peer.sa_sin.sin_family)) { + zlog_warn("ptm-read: peer family doesn't match local type"); + return -1; + } + + return 0; + +stream_failure: + return -1; +} + +static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id) +{ + struct ptm_client *pc; + struct bfd_session *bs; + struct bfd_peer_cfg bpc; + + /* Read the client context and peer data. */ + if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_REGISTER, vrf_id, &bpc, &pc) == -1) + return; + + debug_printbpc(&bpc, "ptm-add-dest: register peer"); + + /* Find or start new BFD session. */ + bs = bs_peer_find(&bpc); + if (bs == NULL) { + bs = ptm_bfd_sess_new(&bpc); + if (bs == NULL) { + if (bglobal.debug_zebra) + zlog_debug( + "ptm-add-dest: failed to create BFD session"); + return; + } + } else { + /* + * BFD session was already created, we are just updating the + * current peer. + * + * `ptm-bfd` (or `HAVE_BFDD == 0`) is the only implementation + * that allow users to set peer specific timers via protocol. + * BFD daemon (this code) on the other hand only supports + * changing peer configuration manually (through `peer` node) + * or via profiles. + */ + if (bpc.bpc_has_profile) + bfd_profile_apply(bpc.bpc_profile, bs); + } + + /* Create client peer notification register. */ + pcn_new(pc, bs); + + ptm_bfd_notify(bs, bs->ses_state); +} + +static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id) +{ + struct ptm_client *pc; + struct ptm_client_notification *pcn; + struct bfd_session *bs; + struct bfd_peer_cfg bpc; + + /* Read the client context and peer data. */ + if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_DEREGISTER, vrf_id, &bpc, &pc) == -1) + return; + + debug_printbpc(&bpc, "ptm-del-dest: deregister peer"); + + /* Find or start new BFD session. */ + bs = bs_peer_find(&bpc); + if (bs == NULL) { + if (bglobal.debug_zebra) + zlog_debug("ptm-del-dest: failed to find BFD session"); + return; + } + + /* Unregister client peer notification. */ + pcn = pcn_lookup(pc, bs); + if (pcn != NULL) { + pcn_free(pcn); + return; + } + + if (bglobal.debug_zebra) + zlog_debug("ptm-del-dest: failed to find BFD session"); + + /* + * XXX: We either got a double deregistration or the daemon who + * created this is no longer around. Lets try to delete it anyway + * and the worst case is the refcount will detain us. + */ + _ptm_bfd_session_del(bs, BD_NEIGHBOR_DOWN); +} + +/* + * header: command, VRF + * l: pid + */ +static void bfdd_client_register(struct stream *msg) +{ + uint32_t pid; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + pc_new(pid); + + return; + +stream_failure: + zlog_err("ptm-add-client: failed to register client"); +} + +/* + * header: command, VRF + * l: pid + */ +static void bfdd_client_deregister(struct stream *msg) +{ + struct ptm_client *pc; + uint32_t pid; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + pc = pc_lookup(pid); + if (pc == NULL) { + if (bglobal.debug_zebra) + zlog_debug("ptm-del-client: failed to find client: %u", + pid); + return; + } + + if (bglobal.debug_zebra) + zlog_debug("ptm-del-client: client pid %u", pid); + + pc_free(pc); + + return; + +stream_failure: + zlog_err("ptm-del-client: failed to deregister client"); +} + +static int bfdd_replay(ZAPI_CALLBACK_ARGS) +{ + struct stream *msg = zclient->ibuf; + uint32_t rcmd; + + STREAM_GETL(msg, rcmd); + + switch (rcmd) { + case ZEBRA_BFD_DEST_REGISTER: + case ZEBRA_BFD_DEST_UPDATE: + bfdd_dest_register(msg, vrf_id); + break; + case ZEBRA_BFD_DEST_DEREGISTER: + bfdd_dest_deregister(msg, vrf_id); + break; + case ZEBRA_BFD_CLIENT_REGISTER: + bfdd_client_register(msg); + break; + case ZEBRA_BFD_CLIENT_DEREGISTER: + bfdd_client_deregister(msg); + break; + + default: + if (bglobal.debug_zebra) + zlog_debug("ptm-replay: invalid message type %u", rcmd); + return -1; + } + + return 0; + +stream_failure: + zlog_err("ptm-replay: failed to find command"); + return -1; +} + +static void bfdd_zebra_connected(struct zclient *zc) +{ + struct stream *msg = zc->obuf; + + /* Clean-up and free ptm clients data memory. */ + pc_free_all(); + + /* + * The replay is an empty message just to trigger client daemons + * configuration replay. + */ + stream_reset(msg); + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); + stream_putl(msg, ZEBRA_BFD_DEST_REPLAY); + stream_putw_at(msg, 0, stream_get_endp(msg)); + + /* Ask for interfaces information. */ + zclient_create_header(msg, ZEBRA_INTERFACE_ADD, VRF_DEFAULT); + + /* Send requests. */ + zclient_send_message(zclient); +} + +static void bfdd_sessions_enable_interface(struct interface *ifp) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + struct vrf *vrf; + + vrf = ifp->vrf; + + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + bs = bso->bso_bs; + /* check vrf name */ + if (bs->key.vrfname[0] && + strcmp(vrf->name, bs->key.vrfname)) + continue; + + /* If Interface matches vrfname, then bypass iface check */ + if (vrf_is_backend_netns() || strcmp(ifp->name, vrf->name)) { + /* Interface name mismatch. */ + if (bs->key.ifname[0] && + strcmp(ifp->name, bs->key.ifname)) + continue; + } + + /* Skip enabled sessions. */ + if (bs->sock != -1) + continue; + + /* Try to enable it. */ + bfd_session_enable(bs); + } +} + +static void bfdd_sessions_disable_interface(struct interface *ifp) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + bs = bso->bso_bs; + + if (bs->ifp != ifp) + continue; + + /* Skip disabled sessions. */ + if (bs->sock == -1) { + bs->ifp = NULL; + continue; + } + + bfd_session_disable(bs); + bs->ifp = NULL; + } +} + +void bfdd_sessions_enable_vrf(struct vrf *vrf) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + + /* it may affect configs without interfaces */ + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + bs = bso->bso_bs; + if (bs->vrf) + continue; + if (bs->key.vrfname[0] && + strcmp(vrf->name, bs->key.vrfname)) + continue; + /* need to update the vrf information on + * bs so that callbacks are handled + */ + bs->vrf = vrf; + /* Skip enabled sessions. */ + if (bs->sock != -1) + continue; + /* Try to enable it. */ + bfd_session_enable(bs); + } +} + +void bfdd_sessions_disable_vrf(struct vrf *vrf) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + bs = bso->bso_bs; + if (bs->key.vrfname[0] && + strcmp(vrf->name, bs->key.vrfname)) + continue; + /* Skip disabled sessions. */ + if (bs->sock == -1) + continue; + + bfd_session_disable(bs); + bs->vrf = NULL; + } +} + +static int bfd_ifp_destroy(struct interface *ifp) +{ + if (bglobal.debug_zebra) + zlog_debug("zclient: delete interface %s (VRF %s(%u))", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id); + + bfdd_sessions_disable_interface(ifp); + + return 0; +} + +static void bfdd_sessions_enable_address(struct connected *ifc) +{ + struct bfd_session_observer *bso; + struct bfd_session *bs; + struct prefix prefix; + + TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) { + /* Skip enabled sessions. */ + bs = bso->bso_bs; + if (bs->sock != -1) + continue; + + /* Check address. */ + prefix = bso->bso_addr; + prefix.prefixlen = ifc->address->prefixlen; + if (prefix_cmp(&prefix, ifc->address)) + continue; + + /* Try to enable it. */ + bfd_session_enable(bs); + } +} + +static int bfdd_interface_address_update(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + if (bglobal.debug_zebra) + zlog_debug("zclient: %s local address %pFX (VRF %u)", + cmd == ZEBRA_INTERFACE_ADDRESS_ADD ? "add" + : "delete", + ifc->address, vrf_id); + + if (cmd == ZEBRA_INTERFACE_ADDRESS_ADD) + bfdd_sessions_enable_address(ifc); + else + connected_free(&ifc); + + return 0; +} + +static int bfd_ifp_create(struct interface *ifp) +{ + if (bglobal.debug_zebra) + zlog_debug("zclient: add interface %s (VRF %s(%u))", ifp->name, + ifp->vrf->name, ifp->vrf->vrf_id); + bfdd_sessions_enable_interface(ifp); + + return 0; +} + +static zclient_handler *const bfd_handlers[] = { + /* + * We'll receive all messages through replay, however it will + * contain a special field with the real command inside so we + * avoid having to create too many handlers. + */ + [ZEBRA_BFD_DEST_REPLAY] = bfdd_replay, + + /* Learn about new addresses being registered. */ + [ZEBRA_INTERFACE_ADDRESS_ADD] = bfdd_interface_address_update, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = bfdd_interface_address_update, +}; + +void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv) +{ + hook_register_prio(if_real, 0, bfd_ifp_create); + hook_register_prio(if_unreal, 0, bfd_ifp_destroy); + zclient = zclient_new(master, &zclient_options_default, bfd_handlers, + array_size(bfd_handlers)); + assert(zclient != NULL); + zclient_init(zclient, ZEBRA_ROUTE_BFD, 0, bfdd_priv); + + /* Send replay request on zebra connect. */ + zclient->zebra_connected = bfdd_zebra_connected; +} + +void bfdd_zclient_register(vrf_id_t vrf_id) +{ + if (!zclient || zclient->sock < 0) + return; + zclient_send_reg_requests(zclient, vrf_id); +} + +void bfdd_zclient_unregister(vrf_id_t vrf_id) +{ + if (!zclient || zclient->sock < 0) + return; + zclient_send_dereg_requests(zclient, vrf_id); +} + +void bfdd_zclient_stop(void) +{ + zclient_stop(zclient); + + /* Clean-up and free ptm clients data memory. */ + pc_free_all(); +} + +void bfdd_zclient_terminate(void) +{ + zclient_free(zclient); +} + + +/* + * Client handling. + */ +static struct ptm_client *pc_lookup(uint32_t pid) +{ + struct ptm_client *pc; + + TAILQ_FOREACH (pc, &pcqueue, pc_entry) { + if (pc->pc_pid != pid) + continue; + + break; + } + + return pc; +} + +static struct ptm_client *pc_new(uint32_t pid) +{ + struct ptm_client *pc; + + /* Look up first, if not found create the client. */ + pc = pc_lookup(pid); + if (pc != NULL) + return pc; + + /* Allocate the client data and save it. */ + pc = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*pc)); + + pc->pc_pid = pid; + TAILQ_INSERT_HEAD(&pcqueue, pc, pc_entry); + return pc; +} + +static void pc_free(struct ptm_client *pc) +{ + struct ptm_client_notification *pcn; + + TAILQ_REMOVE(&pcqueue, pc, pc_entry); + + while (!TAILQ_EMPTY(&pc->pc_pcnqueue)) { + pcn = TAILQ_FIRST(&pc->pc_pcnqueue); + pcn_free(pcn); + } + + XFREE(MTYPE_BFDD_CONTROL, pc); +} + +static void pc_free_all(void) +{ + struct ptm_client *pc; + + while (!TAILQ_EMPTY(&pcqueue)) { + pc = TAILQ_FIRST(&pcqueue); + pc_free(pc); + } +} + +static struct ptm_client_notification *pcn_new(struct ptm_client *pc, + struct bfd_session *bs) +{ + struct ptm_client_notification *pcn; + + /* Try to find an existing pcn fist. */ + pcn = pcn_lookup(pc, bs); + if (pcn != NULL) + return pcn; + + /* Save the client notification data. */ + pcn = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*pcn)); + + TAILQ_INSERT_HEAD(&pc->pc_pcnqueue, pcn, pcn_entry); + pcn->pcn_pc = pc; + pcn->pcn_bs = bs; + bs->refcount++; + + return pcn; +} + +static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, + struct bfd_session *bs) +{ + struct ptm_client_notification *pcn; + + TAILQ_FOREACH (pcn, &pc->pc_pcnqueue, pcn_entry) { + if (pcn->pcn_bs != bs) + continue; + + break; + } + + return pcn; +} + +static void pcn_free(struct ptm_client_notification *pcn) +{ + struct ptm_client *pc; + struct bfd_session *bs; + + /* Handle session de-registration. */ + bs = pcn->pcn_bs; + pcn->pcn_bs = NULL; + bs->refcount--; + + /* Log modification to users. */ + if (bglobal.debug_zebra) + zlog_debug("ptm-del-session: [%s] refcount=%" PRIu64, + bs_to_string(bs), bs->refcount); + + /* Set session down. */ + _ptm_bfd_session_del(bs, BD_NEIGHBOR_DOWN); + + /* Handle ptm_client deregistration. */ + pc = pcn->pcn_pc; + pcn->pcn_pc = NULL; + TAILQ_REMOVE(&pc->pc_pcnqueue, pcn, pcn_entry); + + XFREE(MTYPE_BFDD_NOTIFICATION, pcn); +} diff --git a/bfdd/subdir.am b/bfdd/subdir.am new file mode 100644 index 0000000..b86a189 --- /dev/null +++ b/bfdd/subdir.am @@ -0,0 +1,50 @@ +# +# bfdd +# + +if BFDD +noinst_LIBRARIES += bfdd/libbfd.a +sbin_PROGRAMS += bfdd/bfdd +vtysh_daemons += bfdd +man8 += $(MANBUILD)/frr-bfdd.8 +endif + +bfdd_libbfd_a_SOURCES = \ + bfdd/bfd.c \ + bfdd/bfdd_nb.c \ + bfdd/bfdd_nb_config.c \ + bfdd/bfdd_nb_state.c \ + bfdd/bfdd_vty.c \ + bfdd/bfdd_cli.c \ + bfdd/bfd_packet.c \ + bfdd/config.c \ + bfdd/control.c \ + bfdd/dplane.c \ + bfdd/event.c \ + bfdd/ptm_adapter.c \ + # end + +# Install headers so it can be used by external data plane +# implementations. +bfdd_headersdir = $(pkgincludedir)/bfdd +bfdd_headers_HEADERS = \ + bfdd/bfddp_packet.h \ + # end + +clippy_scan += \ + bfdd/bfdd_cli.c \ + bfdd/bfdd_vty.c \ + # end + +noinst_HEADERS += \ + bfdd/bfdctl.h \ + bfdd/bfdd_nb.h \ + bfdd/bfd.h \ + # end + +nodist_bfdd_bfdd_SOURCES = \ + yang/frr-bfdd.yang.c \ + # end + +bfdd_bfdd_SOURCES = bfdd/bfdd.c +bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la diff --git a/bgpd/.gitignore b/bgpd/.gitignore new file mode 100644 index 0000000..2e77195 --- /dev/null +++ b/bgpd/.gitignore @@ -0,0 +1,3 @@ +bgpd +bgp_btoa +bgpd.conf diff --git a/bgpd/Makefile b/bgpd/Makefile new file mode 100644 index 0000000..b8664a8 --- /dev/null +++ b/bgpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. bgpd/bgpd +%: ALWAYS + @$(MAKE) -s -C .. bgpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/bgpd/bgp_addpath.c b/bgpd/bgp_addpath.c new file mode 100644 index 0000000..f391c13 --- /dev/null +++ b/bgpd/bgp_addpath.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Addpath TX ID selection, and related utilities + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bgp_addpath.h" +#include "bgp_route.h" +#include "bgp_open.h" +#include "bgp_packet.h" + +static const struct bgp_addpath_strategy_names strat_names[BGP_ADDPATH_MAX] = { + { + .config_name = "addpath-tx-all-paths", + .human_name = "All", + .human_description = "Advertise all paths via addpath", + .type_json_name = "addpathTxAllPaths", + .id_json_name = "addpathTxIdAll" + }, + { + .config_name = "addpath-tx-bestpath-per-AS", + .human_name = "Best-Per-AS", + .human_description = "Advertise bestpath per AS via addpath", + .type_json_name = "addpathTxBestpathPerAS", + .id_json_name = "addpathTxIdBestPerAS" + }, + { + .config_name = "addpath-tx-best-selected", + .human_name = "Best-Selected", + .human_description = "Advertise best N selected paths via addpath", + .type_json_name = "addpathTxBestSelectedPaths", + .id_json_name = "addpathTxIdBestSelected" + }, +}; + +static const struct bgp_addpath_strategy_names unknown_names = { + .config_name = "addpath-tx-unknown", + .human_name = "Unknown-Addpath-Strategy", + .human_description = "Unknown Addpath Strategy", + .type_json_name = "addpathTxUnknown", + .id_json_name = "addpathTxIdUnknown" +}; + +/* + * Returns a structure full of strings associated with an addpath type. Will + * never return null. + */ +const struct bgp_addpath_strategy_names * +bgp_addpath_names(enum bgp_addpath_strat strat) +{ + if (strat < BGP_ADDPATH_MAX) + return &(strat_names[strat]); + else + return &unknown_names; +}; + +/* + * Returns if any peer is transmitting addpaths for a given afi/safi. + */ +bool bgp_addpath_is_addpath_used(struct bgp_addpath_bgp_data *d, afi_t afi, + safi_t safi) +{ + return d->total_peercount[afi][safi] > 0; +} + +/* + * Initialize the BGP instance level data for addpath. + */ +void bgp_addpath_init_bgp_data(struct bgp_addpath_bgp_data *d) +{ + safi_t safi; + afi_t afi; + int i; + + FOREACH_AFI_SAFI (afi, safi) { + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + d->id_allocators[afi][safi][i] = NULL; + d->peercount[afi][safi][i] = 0; + } + d->total_peercount[afi][safi] = 0; + } +} + +/* + * Free up resources associated with BGP route info structures. + */ +void bgp_addpath_free_info_data(struct bgp_addpath_info_data *d, + struct bgp_addpath_node_data *nd) +{ + int i; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + if (d->addpath_tx_id[i] != IDALLOC_INVALID) + idalloc_free_to_pool(&nd->free_ids[i], + d->addpath_tx_id[i]); + } +} + +/* + * Return the addpath ID used to send a particular route, to a particular peer, + * in a particular AFI/SAFI. + */ +uint32_t bgp_addpath_id_for_peer(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_addpath_info_data *d) +{ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + if (peer->addpath_type[afi][safi] < BGP_ADDPATH_MAX) + return d->addpath_tx_id[peer->addpath_type[afi][safi]]; + else + return IDALLOC_INVALID; +} + +/* + * Returns true if the path has an assigned addpath ID for any of the addpath + * strategies. + */ +bool bgp_addpath_info_has_ids(struct bgp_addpath_info_data *d) +{ + int i; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) + if (d->addpath_tx_id[i] != 0) + return true; + + return false; +} + +/* + * Releases any ID's associated with the BGP prefix. + */ +void bgp_addpath_free_node_data(struct bgp_addpath_bgp_data *bd, + struct bgp_addpath_node_data *nd, afi_t afi, + safi_t safi) +{ + int i; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + idalloc_drain_pool(bd->id_allocators[afi][safi][i], + &(nd->free_ids[i])); + } +} + +/* + * Check to see if the addpath strategy requires DMED to be configured to work. + */ +bool bgp_addpath_dmed_required(int strategy) +{ + return strategy == BGP_ADDPATH_BEST_PER_AS; +} + +/* + * Return true if this is a path we should advertise due to a + * configured addpath-tx knob + */ +bool bgp_addpath_tx_path(enum bgp_addpath_strat strat, struct bgp_path_info *pi) +{ + switch (strat) { + case BGP_ADDPATH_NONE: + return false; + case BGP_ADDPATH_ALL: + return true; + case BGP_ADDPATH_BEST_PER_AS: + if (CHECK_FLAG(pi->flags, BGP_PATH_DMED_SELECTED)) + return true; + else + return false; + case BGP_ADDPATH_BEST_SELECTED: + return true; + case BGP_ADDPATH_MAX: + return false; + } + + assert(!"Reached end of function we should never hit"); +} + +static void bgp_addpath_flush_type_rn(struct bgp *bgp, afi_t afi, safi_t safi, + enum bgp_addpath_strat addpath_type, + struct bgp_dest *dest) +{ + struct bgp_path_info *pi; + + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + idalloc_drain_pool( + bgp->tx_addpath.id_allocators[afi][safi][addpath_type], + &(dest->tx_addpath.free_ids[addpath_type])); + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (pi->tx_addpath.addpath_tx_id[addpath_type] + != IDALLOC_INVALID) { + idalloc_free( + bgp->tx_addpath + .id_allocators[afi][safi][addpath_type], + pi->tx_addpath.addpath_tx_id[addpath_type]); + pi->tx_addpath.addpath_tx_id[addpath_type] = + IDALLOC_INVALID; + } + } +} + +/* + * Purge all addpath ID's on a BGP instance associated with the addpath + * strategy, and afi/safi combination. This lets us let go of all memory held to + * track ID numbers associated with an addpath type not in use. Since + * post-bestpath ID processing is skipped for types not used, this is the only + * chance to free this data. + */ +static void bgp_addpath_flush_type(struct bgp *bgp, afi_t afi, safi_t safi, + enum bgp_addpath_strat addpath_type) +{ + struct bgp_dest *dest, *ndest; + + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (safi == SAFI_MPLS_VPN) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) + bgp_addpath_flush_type_rn(bgp, afi, safi, + addpath_type, ndest); + } else { + bgp_addpath_flush_type_rn(bgp, afi, safi, addpath_type, + dest); + } + } + + idalloc_destroy(bgp->tx_addpath.id_allocators[afi][safi][addpath_type]); + bgp->tx_addpath.id_allocators[afi][safi][addpath_type] = NULL; +} + +/* + * Allocate an Addpath ID for the given type on a path, if necessary. + */ +static void bgp_addpath_populate_path(struct id_alloc *allocator, + struct bgp_path_info *path, + enum bgp_addpath_strat addpath_type) +{ + if (bgp_addpath_tx_path(addpath_type, path)) { + path->tx_addpath.addpath_tx_id[addpath_type] = + idalloc_allocate(allocator); + } +} + +/* + * Compute addpath ID's on a BGP instance associated with the addpath strategy, + * and afi/safi combination. Since we won't waste the time computing addpath IDs + * for unused strategies, the first time a peer is configured to use a strategy, + * we have to backfill the data. + * In labeled-unicast, addpath allocations SHOULD be done in unicast SAFI. + */ +static void bgp_addpath_populate_type(struct bgp *bgp, afi_t afi, safi_t safi, + enum bgp_addpath_strat addpath_type) +{ + struct bgp_dest *dest, *ndest; + char buf[200]; + struct id_alloc *allocator; + + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + snprintf(buf, sizeof(buf), "Addpath ID Allocator %s:%d/%d", + bgp_addpath_names(addpath_type)->config_name, (int)afi, + (int)safi); + buf[sizeof(buf) - 1] = '\0'; + zlog_info("Computing addpath IDs for addpath type %s", + bgp_addpath_names(addpath_type)->human_name); + + bgp->tx_addpath.id_allocators[afi][safi][addpath_type] = + idalloc_new(buf); + + idalloc_reserve(bgp->tx_addpath.id_allocators[afi][safi][addpath_type], + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + + allocator = bgp->tx_addpath.id_allocators[afi][safi][addpath_type]; + + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + struct bgp_path_info *bi; + + if (safi == SAFI_MPLS_VPN) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) + for (bi = bgp_dest_get_bgp_path_info(ndest); bi; + bi = bi->next) + bgp_addpath_populate_path(allocator, bi, + addpath_type); + } else { + for (bi = bgp_dest_get_bgp_path_info(dest); bi; + bi = bi->next) + bgp_addpath_populate_path(allocator, bi, + addpath_type); + } + } +} + +/* + * Handle updates to a peer or group's addpath strategy. If after adjusting + * counts a addpath strategy is in use for the first time, or no longer in use, + * the IDs for that strategy will be populated or flushed. + */ +void bgp_addpath_type_changed(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct listnode *node, *nnode; + struct peer *peer; + int peer_count[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; + enum bgp_addpath_strat type; + + FOREACH_AFI_SAFI(afi, safi) { + for (type=0; typetx_addpath.total_peercount[afi][safi] = 0; + } + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + FOREACH_AFI_SAFI(afi, safi) { + type = peer->addpath_type[afi][safi]; + if (type != BGP_ADDPATH_NONE) { + peer_count[afi][safi][type] += 1; + bgp->tx_addpath.total_peercount[afi][safi] += 1; + } + } + } + + FOREACH_AFI_SAFI(afi, safi) { + for (type=0; typetx_addpath.peercount[afi][safi][type]; + int new = peer_count[afi][safi][type]; + + bgp->tx_addpath.peercount[afi][safi][type] = new; + + if (old == 0 && new != 0) { + bgp_addpath_populate_type(bgp, afi, safi, + type); + } else if (old != 0 && new == 0) { + bgp_addpath_flush_type(bgp, afi, safi, type); + } + } + } +} + +int bgp_addpath_capability_action(enum bgp_addpath_strat addpath_type, + uint8_t paths) +{ + int action = CAPABILITY_ACTION_UNSET; + + switch (addpath_type) { + case BGP_ADDPATH_ALL: + case BGP_ADDPATH_BEST_PER_AS: + action = CAPABILITY_ACTION_SET; + break; + case BGP_ADDPATH_BEST_SELECTED: + if (paths) + action = CAPABILITY_ACTION_SET; + else + action = CAPABILITY_ACTION_UNSET; + break; + case BGP_ADDPATH_NONE: + case BGP_ADDPATH_MAX: + action = CAPABILITY_ACTION_UNSET; + break; + } + + return action; +} + +/* + * Change the addpath type assigned to a peer, or peer group. In addition to + * adjusting the counts, peer sessions will be reset as needed to make the + * change take effect. + */ +void bgp_addpath_set_peer_type(struct peer *peer, afi_t afi, safi_t safi, + enum bgp_addpath_strat addpath_type, + uint8_t paths) +{ + struct bgp *bgp = peer->bgp; + enum bgp_addpath_strat old_type; + struct listnode *node, *nnode; + struct peer *tmp_peer; + struct peer_group *group; + int action = bgp_addpath_capability_action(addpath_type, paths); + + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + peer->addpath_best_selected[afi][safi] = paths; + + old_type = peer->addpath_type[afi][safi]; + if (addpath_type == old_type) + return; + + if (addpath_type == BGP_ADDPATH_NONE && peer->group && + !CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* A "no" config on a group member inherits group */ + addpath_type = peer->group->conf->addpath_type[afi][safi]; + } + + peer->addpath_type[afi][safi] = addpath_type; + + bgp_addpath_type_changed(bgp); + + if (addpath_type != BGP_ADDPATH_NONE) { + if (bgp_addpath_dmed_required(addpath_type)) { + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_DETERMINISTIC_MED)) { + zlog_warn( + "%s: enabling bgp deterministic-med, this is required for addpath-tx-bestpath-per-AS", + peer->host); + SET_FLAG(bgp->flags, + BGP_FLAG_DETERMINISTIC_MED); + bgp_recalculate_all_bestpaths(bgp); + } + } + } + + zlog_info("Resetting peer %s%pBP due to change in addpath config", + CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) ? "group " : "", + peer); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + group = peer->group; + + /* group will be null as peer_group_delete calls peer_delete on + * group->conf. That peer_delete will eventuallly end up here + * if the group was configured to tx addpaths. + */ + if (group != NULL) { + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, + tmp_peer)) { + if (tmp_peer->addpath_type[afi][safi] == + old_type) { + bgp_addpath_set_peer_type( + tmp_peer, afi, safi, + addpath_type, paths); + } + } + } + } else { + if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) && + !CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV)) + peer_change_action(peer, afi, safi, peer_change_reset); + } + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ADDPATH, action); +} + +/* + * Intended to run after bestpath. This function will take TX IDs from paths + * that no longer need them, and give them to paths that do. This prevents + * best-per-as updates from needing to do a separate withdraw and update just to + * swap out which path is sent. + */ +void bgp_addpath_update_ids(struct bgp *bgp, struct bgp_dest *bn, afi_t afi, + safi_t safi) +{ + int i; + struct bgp_path_info *pi; + struct id_alloc_pool **pool_ptr; + + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + struct id_alloc *alloc = + bgp->tx_addpath.id_allocators[afi][safi][i]; + pool_ptr = &(bn->tx_addpath.free_ids[i]); + + if (bgp->tx_addpath.peercount[afi][safi][i] == 0) + continue; + + /* Free Unused IDs back to the pool.*/ + for (pi = bgp_dest_get_bgp_path_info(bn); pi; pi = pi->next) { + if (pi->tx_addpath.addpath_tx_id[i] != IDALLOC_INVALID + && !bgp_addpath_tx_path(i, pi)) { + idalloc_free_to_pool(pool_ptr, + pi->tx_addpath.addpath_tx_id[i]); + pi->tx_addpath.addpath_tx_id[i] = + IDALLOC_INVALID; + } + } + + /* Give IDs to paths that need them (pulling from the pool) */ + for (pi = bgp_dest_get_bgp_path_info(bn); pi; pi = pi->next) { + if (pi->tx_addpath.addpath_tx_id[i] == IDALLOC_INVALID + && bgp_addpath_tx_path(i, pi)) { + pi->tx_addpath.addpath_tx_id[i] = + idalloc_allocate_prefer_pool( + alloc, pool_ptr); + } + } + + /* Free any IDs left in the pool to the main allocator */ + idalloc_drain_pool(alloc, pool_ptr); + } +} diff --git a/bgpd/bgp_addpath.h b/bgpd/bgp_addpath.h new file mode 100644 index 0000000..c267ebe --- /dev/null +++ b/bgpd/bgp_addpath.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Addpath TX ID selection, and related utilities + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates + */ + +#ifndef _QUAGGA_BGPD_TX_ADDPATH_H +#define _QUAGGA_BGPD_TX_ADDPATH_H + +#include +#include + +#include "bgpd/bgp_addpath_types.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_table.h" +#include "lib/json.h" + +struct bgp_addpath_capability { + uint16_t afi; + uint8_t safi; + uint8_t flags; +}; + +struct bgp_paths_limit_capability { + uint16_t afi; + uint8_t safi; + uint16_t paths_limit; +} __attribute__((packed)); + +#define BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE 1 + +void bgp_addpath_init_bgp_data(struct bgp_addpath_bgp_data *d); + +bool bgp_addpath_is_addpath_used(struct bgp_addpath_bgp_data *d, afi_t afi, + safi_t safi); + +void bgp_addpath_free_node_data(struct bgp_addpath_bgp_data *bd, + struct bgp_addpath_node_data *nd, + afi_t afi, safi_t safi); + +void bgp_addpath_free_info_data(struct bgp_addpath_info_data *d, + struct bgp_addpath_node_data *nd); + + +bool bgp_addpath_info_has_ids(struct bgp_addpath_info_data *d); + +uint32_t bgp_addpath_id_for_peer(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_addpath_info_data *d); + +const struct bgp_addpath_strategy_names * +bgp_addpath_names(enum bgp_addpath_strat strat); + +bool bgp_addpath_dmed_required(int strategy); + +/* + * Return true if this is a path we should advertise due to a configured + * addpath-tx knob + */ +bool bgp_addpath_tx_path(enum bgp_addpath_strat strat, + struct bgp_path_info *pi); +/* + * Change the type of addpath used for a peer. + */ +void bgp_addpath_set_peer_type(struct peer *peer, afi_t afi, safi_t safi, + enum bgp_addpath_strat addpath_type, + uint8_t paths); + +void bgp_addpath_update_ids(struct bgp *bgp, struct bgp_dest *dest, afi_t afi, + safi_t safi); + +void bgp_addpath_type_changed(struct bgp *bgp); +extern int bgp_addpath_capability_action(enum bgp_addpath_strat addpath_type, + uint8_t paths); +#endif diff --git a/bgpd/bgp_addpath_types.h b/bgpd/bgp_addpath_types.h new file mode 100644 index 0000000..8e32b04 --- /dev/null +++ b/bgpd/bgp_addpath_types.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Addpath TX ID selection, and related utilities + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates + */ + +#ifndef _QUAGGA_BGPD_TX_ADDPATH_DATA_H +#define _QUAGGA_BGPD_TX_ADDPATH_DATA_H +#include "lib/id_alloc.h" +#include + +enum bgp_addpath_strat { + BGP_ADDPATH_ALL = 0, + BGP_ADDPATH_BEST_PER_AS, + BGP_ADDPATH_BEST_SELECTED, + BGP_ADDPATH_MAX, + BGP_ADDPATH_NONE, +}; + +/* TX Addpath structures */ +struct bgp_addpath_bgp_data { + unsigned int peercount[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; + unsigned int total_peercount[AFI_MAX][SAFI_MAX]; + struct id_alloc *id_allocators[AFI_MAX][SAFI_MAX][BGP_ADDPATH_MAX]; +}; + +struct bgp_addpath_node_data { + struct id_alloc_pool *free_ids[BGP_ADDPATH_MAX]; +}; + +struct bgp_addpath_info_data { + uint32_t addpath_tx_id[BGP_ADDPATH_MAX]; +}; + +struct bgp_addpath_strategy_names { + const char *config_name; + const char *human_name; /* path detail non-json */ + const char *human_description; /* non-json peer descriptions */ + const char *type_json_name; /* json peer listings */ + const char *id_json_name; /* path json output for tx ID# */ +}; + +#endif diff --git a/bgpd/bgp_advertise.c b/bgpd/bgp_advertise.c new file mode 100644 index 0000000..d5c7e18 --- /dev/null +++ b/bgpd/bgp_advertise.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP advertisement and adjacency + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "hash.h" +#include "frrevent.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_updgrp.h" + +/* BGP advertise attribute is used for pack same attribute update into + one packet. To do that we maintain attribute hash in struct + peer. */ +struct bgp_advertise_attr *bgp_advertise_attr_new(void) +{ + return XCALLOC(MTYPE_BGP_ADVERTISE_ATTR, + sizeof(struct bgp_advertise_attr)); +} + +void bgp_advertise_attr_free(struct bgp_advertise_attr *baa) +{ + bgp_advertise_attr_fifo_fini(&baa->fifo); + + XFREE(MTYPE_BGP_ADVERTISE_ATTR, baa); +} + +static void *bgp_advertise_attr_hash_alloc(void *p) +{ + struct bgp_advertise_attr *ref = (struct bgp_advertise_attr *)p; + struct bgp_advertise_attr *baa; + + baa = bgp_advertise_attr_new(); + baa->attr = ref->attr; + + bgp_advertise_attr_fifo_init(&baa->fifo); + + return baa; +} + +unsigned int bgp_advertise_attr_hash_key(const void *p) +{ + const struct bgp_advertise_attr *baa = p; + + return attrhash_key_make(baa->attr); +} + +bool bgp_advertise_attr_hash_cmp(const void *p1, const void *p2) +{ + const struct bgp_advertise_attr *baa1 = p1; + const struct bgp_advertise_attr *baa2 = p2; + + return attrhash_cmp(baa1->attr, baa2->attr); +} + +/* BGP update and withdraw information is stored in BGP advertise + structure. This structure is referred from BGP adjacency + information. */ +struct bgp_advertise *bgp_advertise_new(void) +{ + return XCALLOC(MTYPE_BGP_ADVERTISE, sizeof(struct bgp_advertise)); +} + +void bgp_advertise_free(struct bgp_advertise *adv) +{ + if (adv->pathi) + /* bgp_advertise bgp_path_info reference */ + bgp_path_info_unlock(adv->pathi); + XFREE(MTYPE_BGP_ADVERTISE, adv); +} + +void bgp_advertise_add(struct bgp_advertise_attr *baa, + struct bgp_advertise *adv) +{ + bgp_advertise_attr_fifo_add_tail(&baa->fifo, adv); +} + +void bgp_advertise_delete(struct bgp_advertise_attr *baa, + struct bgp_advertise *adv) +{ + bgp_advertise_attr_fifo_del(&baa->fifo, adv); +} + +struct bgp_advertise_attr *bgp_advertise_attr_intern(struct hash *hash, + struct attr *attr) +{ + struct bgp_advertise_attr ref; + struct bgp_advertise_attr *baa; + + ref.attr = bgp_attr_intern(attr); + baa = (struct bgp_advertise_attr *)hash_get( + hash, &ref, bgp_advertise_attr_hash_alloc); + baa->refcnt++; + + return baa; +} + +void bgp_advertise_attr_unintern(struct hash *hash, + struct bgp_advertise_attr *baa) +{ + if (baa->refcnt) + baa->refcnt--; + + if (baa->refcnt && baa->attr) + bgp_attr_unintern(&baa->attr); + else { + if (baa->attr) { + hash_release(hash, baa); + bgp_attr_unintern(&baa->attr); + } + bgp_advertise_attr_free(baa); + } +} + +bool bgp_adj_out_lookup(struct peer *peer, struct bgp_dest *dest, + uint32_t addpath_tx_id) +{ + struct bgp_adj_out *adj; + struct peer_af *paf; + afi_t afi; + safi_t safi; + bool addpath_capable; + + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) + SUBGRP_FOREACH_PEER (adj->subgroup, paf) + if (paf->peer == peer) { + afi = SUBGRP_AFI(adj->subgroup); + safi = SUBGRP_SAFI(adj->subgroup); + addpath_capable = + bgp_addpath_encode_tx(peer, afi, safi); + + /* Match on a specific addpath_tx_id if we are + * using addpath for + * this + * peer and if an addpath_tx_id was specified */ + if (addpath_capable && addpath_tx_id + && adj->addpath_tx_id != addpath_tx_id) + continue; + + return (adj->adv + ? (adj->adv->baa ? true : false) + : (adj->attr ? true : false)); + } + + return false; +} + + +void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, struct attr *attr, + uint32_t addpath_id, struct bgp_labels *labels) +{ + struct bgp_adj_in *adj; + + for (adj = dest->adj_in; adj; adj = adj->next) { + if (adj->peer == peer && adj->addpath_rx_id == addpath_id) { + if (!attrhash_cmp(adj->attr, attr)) { + bgp_attr_unintern(&adj->attr); + adj->attr = bgp_attr_intern(attr); + } + if (!bgp_labels_cmp(adj->labels, labels)) { + bgp_labels_unintern(&adj->labels); + adj->labels = bgp_labels_intern(labels); + } + return; + } + } + adj = XCALLOC(MTYPE_BGP_ADJ_IN, sizeof(struct bgp_adj_in)); + adj->peer = peer_lock(peer); /* adj_in peer reference */ + adj->attr = bgp_attr_intern(attr); + adj->uptime = monotime(NULL); + adj->addpath_rx_id = addpath_id; + adj->labels = bgp_labels_intern(labels); + BGP_ADJ_IN_ADD(dest, adj); + peer->stat_pfx_adj_rib_in++; + bgp_dest_lock_node(dest); +} + +void bgp_adj_in_remove(struct bgp_dest **dest, struct bgp_adj_in *bai) +{ + bgp_attr_unintern(&bai->attr); + bgp_labels_unintern(&bai->labels); + if (bai->peer) + bai->peer->stat_pfx_adj_rib_in--; + BGP_ADJ_IN_DEL(*dest, bai); + *dest = bgp_dest_unlock_node(*dest); + peer_unlock(bai->peer); /* adj_in peer reference */ + XFREE(MTYPE_BGP_ADJ_IN, bai); +} + +bool bgp_adj_in_unset(struct bgp_dest **dest, struct peer *peer, + uint32_t addpath_id) +{ + struct bgp_adj_in *adj; + struct bgp_adj_in *adj_next; + + adj = (*dest)->adj_in; + + if (!adj) + return false; + + while (adj) { + adj_next = adj->next; + + if (adj->peer == peer && adj->addpath_rx_id == addpath_id) + bgp_adj_in_remove(dest, adj); + + adj = adj_next; + + assert(*dest); + } + + return true; +} diff --git a/bgpd/bgp_advertise.h b/bgpd/bgp_advertise.h new file mode 100644 index 0000000..8c83189 --- /dev/null +++ b/bgpd/bgp_advertise.h @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP advertisement and adjacency + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ADVERTISE_H +#define _QUAGGA_BGP_ADVERTISE_H + +#include "lib/typesafe.h" + +PREDECL_DLIST(bgp_adv_fifo); + +struct update_subgroup; +struct bgp_advertise; + +PREDECL_DLIST(bgp_advertise_attr_fifo); + +struct bgp_advertise_attr; + +/* BGP advertise attribute. */ +struct bgp_advertise { + /* FIFO for advertisement. */ + struct bgp_adv_fifo_item fifo; + + /* FIFO for this item in the bgp_advertise_attr fifo */ + struct bgp_advertise_attr_fifo_item item; + + /* Prefix information. */ + struct bgp_dest *dest; + + /* Reference pointer. */ + struct bgp_adj_out *adj; + + /* Advertisement attribute. */ + struct bgp_advertise_attr *baa; + + /* BGP info. */ + struct bgp_path_info *pathi; +}; + +DECLARE_DLIST(bgp_advertise_attr_fifo, struct bgp_advertise, item); +DECLARE_DLIST(bgp_adv_fifo, struct bgp_advertise, fifo); + +/* BGP advertise attribute. */ +struct bgp_advertise_attr { + /* Head of advertisement pointer. */ + struct bgp_advertise_attr_fifo_head fifo; + + /* Reference counter. */ + unsigned long refcnt; + + /* Attribute pointer to be announced. */ + struct attr *attr; +}; + +/* BGP adjacency out. */ +struct bgp_adj_out { + /* RB Tree of adjacency entries */ + RB_ENTRY(bgp_adj_out) adj_entry; + + /* Advertised subgroup. */ + struct update_subgroup *subgroup; + + /* Threading that makes the adj part of subgroup's adj queue */ + TAILQ_ENTRY(bgp_adj_out) subgrp_adj_train; + + /* Prefix information. */ + struct bgp_dest *dest; + + uint32_t addpath_tx_id; + + /* Attribute hash */ + uint32_t attr_hash; + + /* Advertised attribute. */ + struct attr *attr; + + /* VPN label information */ + struct bgp_labels *labels; + + /* Advertisement information. */ + struct bgp_advertise *adv; +}; + +RB_HEAD(bgp_adj_out_rb, bgp_adj_out); +RB_PROTOTYPE(bgp_adj_out_rb, bgp_adj_out, adj_entry, + bgp_adj_out_compare); + +/* BGP adjacency in. */ +struct bgp_adj_in { + /* Linked list pointer. */ + struct bgp_adj_in *next; + struct bgp_adj_in *prev; + + /* Received peer. */ + struct peer *peer; + + /* Received attribute. */ + struct attr *attr; + + /* VPN label information */ + struct bgp_labels *labels; + + /* timestamp (monotime) */ + time_t uptime; + + /* Addpath identifier */ + uint32_t addpath_rx_id; +}; + +/* BGP advertisement list. */ +struct bgp_synchronize { + struct bgp_adv_fifo_head update; + struct bgp_adv_fifo_head withdraw; +}; + +/* BGP adjacency linked list. */ +#define BGP_PATH_INFO_ADD(N, A, TYPE) \ + do { \ + (A)->prev = NULL; \ + (A)->next = (N)->TYPE; \ + if ((N)->TYPE) \ + (N)->TYPE->prev = (A); \ + (N)->TYPE = (A); \ + } while (0) + +#define BGP_PATH_INFO_DEL(N, A, TYPE) \ + do { \ + if ((A)->next) \ + (A)->next->prev = (A)->prev; \ + if ((A)->prev) \ + (A)->prev->next = (A)->next; \ + else \ + (N)->TYPE = (A)->next; \ + } while (0) + +#define BGP_ADJ_IN_ADD(N, A) BGP_PATH_INFO_ADD(N, A, adj_in) +#define BGP_ADJ_IN_DEL(N, A) BGP_PATH_INFO_DEL(N, A, adj_in) + +/* Prototypes. */ +extern bool bgp_adj_out_lookup(struct peer *peer, struct bgp_dest *dest, + uint32_t addpath_tx_id); +extern void bgp_adj_in_set(struct bgp_dest *dest, struct peer *peer, + struct attr *attr, uint32_t addpath_id, + struct bgp_labels *labels); +extern bool bgp_adj_in_unset(struct bgp_dest **dest, struct peer *peer, + uint32_t addpath_id); +extern void bgp_adj_in_remove(struct bgp_dest **dest, struct bgp_adj_in *bai); + +extern unsigned int bgp_advertise_attr_hash_key(const void *p); +extern bool bgp_advertise_attr_hash_cmp(const void *p1, const void *p2); +extern void bgp_advertise_add(struct bgp_advertise_attr *baa, + struct bgp_advertise *adv); +extern struct bgp_advertise *bgp_advertise_new(void); +extern void bgp_advertise_free(struct bgp_advertise *adv); +extern struct bgp_advertise_attr *bgp_advertise_attr_intern(struct hash *hash, + struct attr *attr); +extern struct bgp_advertise_attr *bgp_advertise_attr_new(void); +extern void bgp_advertise_delete(struct bgp_advertise_attr *baa, + struct bgp_advertise *adv); +extern void bgp_advertise_attr_unintern(struct hash *hash, + struct bgp_advertise_attr *baa); +extern void bgp_advertise_attr_free(struct bgp_advertise_attr *baa); + +#endif /* _QUAGGA_BGP_ADVERTISE_H */ diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c new file mode 100644 index 0000000..4c1615a --- /dev/null +++ b/bgpd/bgp_aspath.c @@ -0,0 +1,2481 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS path management routines. + * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2005 Sun Microsystems, Inc. + */ + +#include + +#include "hash.h" +#include "memory.h" +#include "vector.h" +#include "log.h" +#include "stream.h" +#include "command.h" +#include "jhash.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_filter.h" + +/* Attr. Flags and Attr. Type Code. */ +#define AS_HEADER_SIZE 2 + +/* Now FOUR octets are used for AS value. */ +#define AS_VALUE_SIZE sizeof(as_t) +/* This is the old one */ +#define AS16_VALUE_SIZE sizeof(as16_t) + +/* Maximum protocol segment length value */ +#define AS_SEGMENT_MAX 255 + +/* The following length and size macros relate specifically to Quagga's + * internal representation of AS-Segments, not per se to the on-wire + * sizes and lengths. At present (200508) they sort of match, however + * the ONLY functions which should now about the on-wire syntax are + * aspath_put, assegment_put and assegment_parse. + * + * aspath_put returns bytes written, the only definitive record of + * size of wire-format attribute.. + */ + +/* Calculated size in bytes of ASN segment data to hold N ASN's */ +#define ASSEGMENT_DATA_SIZE(N, S) \ + ((N) * ((S) ? AS_VALUE_SIZE : AS16_VALUE_SIZE)) + +/* Calculated size of segment struct to hold N ASN's */ +#define ASSEGMENT_SIZE(N,S) (AS_HEADER_SIZE + ASSEGMENT_DATA_SIZE (N,S)) + +/* AS segment octet length. */ +#define ASSEGMENT_LEN(X,S) ASSEGMENT_SIZE((X)->length,S) + +/* AS_SEQUENCE segments can be packed together */ +/* Can the types of X and Y be considered for packing? */ +#define ASSEGMENT_TYPES_PACKABLE(X, Y) \ + (((X)->type == (Y)->type) && ((X)->type == AS_SEQUENCE)) +/* Types and length of X,Y suitable for packing? */ +#define ASSEGMENTS_PACKABLE(X, Y) \ + (ASSEGMENT_TYPES_PACKABLE((X), (Y)) \ + && (((X)->length + (Y)->length) <= AS_SEGMENT_MAX)) + +/* As segment header - the on-wire representation + * NOT the internal representation! + */ +struct assegment_header { + uint8_t type; + uint8_t length; +}; + +/* Hash for aspath. This is the top level structure of AS path. */ +static struct hash *ashash; + +/* Stream for SNMP. See aspath_snmp_pathseg */ +static struct stream *snmp_stream; + +/* as-path orphan exclude list */ +static struct as_list_list_head as_exclude_list_orphan; + +/* Callers are required to initialize the memory */ +static as_t *assegment_data_new(int num) +{ + return (XMALLOC(MTYPE_AS_SEG_DATA, ASSEGMENT_DATA_SIZE(num, 1))); +} + +static void assegment_data_free(as_t *asdata) +{ + XFREE(MTYPE_AS_SEG_DATA, asdata); +} + +const char *const aspath_segment_type_str[] = { + "as-invalid", "as-set", "as-sequence", "as-confed-sequence", + "as-confed-set" +}; + +/* Get a new segment. Note that 0 is an allowed length, + * and will result in a segment with no allocated data segment. + * the caller should immediately assign data to the segment, as the segment + * otherwise is not generally valid + */ +static struct assegment *assegment_new(uint8_t type, unsigned short length) +{ + struct assegment *new; + + new = XCALLOC(MTYPE_AS_SEG, sizeof(struct assegment)); + + if (length) + new->as = assegment_data_new(length); + + new->length = length; + new->type = type; + + return new; +} + +static void assegment_free(struct assegment *seg) +{ + if (!seg) + return; + + assegment_data_free(seg->as); + memset(seg, 0xfe, sizeof(struct assegment)); + XFREE(MTYPE_AS_SEG, seg); + + return; +} + +/* free entire chain of segments */ +static void assegment_free_all(struct assegment *seg) +{ + struct assegment *prev; + + while (seg) { + prev = seg; + seg = seg->next; + assegment_free(prev); + } +} + +/* Duplicate just the given assegment and its data */ +static struct assegment *assegment_dup(struct assegment *seg) +{ + struct assegment *new; + + new = assegment_new(seg->type, seg->length); + memcpy(new->as, seg->as, ASSEGMENT_DATA_SIZE(new->length, 1)); + + return new; +} + +/* Duplicate entire chain of assegments, return the head */ +static struct assegment *assegment_dup_all(struct assegment *seg) +{ + struct assegment *new = NULL; + struct assegment *head = NULL; + + while (seg) { + if (head) { + new->next = assegment_dup(seg); + new = new->next; + } else + head = new = assegment_dup(seg); + + seg = seg->next; + } + return head; +} + +/* prepend the as number to given segment, given num of times */ +static struct assegment *assegment_prepend_asns(struct assegment *seg, + as_t asnum, int num) +{ + as_t *newas; + int i; + + if (!num) + return seg; + + if (num >= AS_SEGMENT_MAX) + return seg; /* we don't do huge prepends */ + + newas = assegment_data_new(seg->length + num); + if (newas == NULL) + return seg; + + for (i = 0; i < num; i++) + newas[i] = asnum; + + memcpy(newas + num, seg->as, ASSEGMENT_DATA_SIZE(seg->length, 1)); + assegment_data_free(seg->as); + seg->as = newas; + seg->length += num; + + return seg; +} + +/* append given array of as numbers to the segment */ +static struct assegment *assegment_append_asns(struct assegment *seg, + as_t *asnos, int num) +{ + as_t *newas; + + if (!seg) + return seg; + + newas = XREALLOC(MTYPE_AS_SEG_DATA, seg->as, + ASSEGMENT_DATA_SIZE(seg->length + num, 1)); + + seg->as = newas; + memcpy(seg->as + seg->length, asnos, + ASSEGMENT_DATA_SIZE(num, 1)); + seg->length += num; + return seg; +} + +static int int_cmp(const void *p1, const void *p2) +{ + const as_t *as1 = p1; + const as_t *as2 = p2; + + return (*as1 == *as2) ? 0 : ((*as1 > *as2) ? 1 : -1); +} + +/* normalise the segment. + * In particular, merge runs of AS_SEQUENCEs into one segment + * Internally, we do not care about the wire segment length limit, and + * we want each distinct AS_PATHs to have the exact same internal + * representation - eg, so that our hashing actually works.. + */ +static struct assegment *assegment_normalise(struct assegment *head) +{ + struct assegment *seg = head, *pin; + struct assegment *tmp; + + if (!head) + return head; + + while (seg) { + pin = seg; + + /* Sort values SET segments, for determinism in paths to aid + * creation of hash values / path comparisons + * and because it helps other lesser implementations ;) + */ + if (seg->type == AS_SET || seg->type == AS_CONFED_SET) { + int tail = 0; + int i; + + qsort(seg->as, seg->length, sizeof(as_t), int_cmp); + + /* weed out dupes */ + for (i = 1; i < seg->length; i++) { + if (seg->as[tail] == seg->as[i]) + continue; + + tail++; + if (tail < i) + seg->as[tail] = seg->as[i]; + } + /* seg->length can be 0.. */ + if (seg->length) + seg->length = tail + 1; + } + + /* read ahead from the current, pinned segment while the + * segments + * are packable/mergeable. Append all following packable + * segments + * to the segment we have pinned and remove these appended + * segments. + */ + while (pin->next && ASSEGMENT_TYPES_PACKABLE(pin, pin->next)) { + tmp = pin->next; + seg = pin->next; + + /* append the next sequence to the pinned sequence */ + pin = assegment_append_asns(pin, seg->as, seg->length); + + /* bypass the next sequence */ + pin->next = seg->next; + + /* get rid of the now referenceless segment */ + assegment_free(tmp); + } + + seg = pin->next; + } + return head; +} + +static struct aspath *aspath_new(enum asnotation_mode asnotation) +{ + struct aspath *as; + + as = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + as->asnotation = asnotation; + return as; +} + +/* Free AS path structure. */ +void aspath_free(struct aspath *aspath) +{ + if (!aspath) + return; + if (aspath->segments) + assegment_free_all(aspath->segments); + XFREE(MTYPE_AS_STR, aspath->str); + + if (aspath->json) { + json_object_free(aspath->json); + aspath->json = NULL; + } + + XFREE(MTYPE_AS_PATH, aspath); +} + +/* Unintern aspath from AS path bucket. */ +void aspath_unintern(struct aspath **aspath) +{ + struct aspath *ret; + struct aspath *asp; + + if (!*aspath) + return; + + asp = *aspath; + + if (asp->refcnt) + asp->refcnt--; + + if (asp->refcnt == 0) { + /* This aspath must exist in aspath hash table. */ + ret = hash_release(ashash, asp); + assert(ret != NULL); + aspath_free(asp); + *aspath = NULL; + } +} + +/* Return the start or end delimiters for a particular Segment type */ +#define AS_SEG_START 0 +#define AS_SEG_END 1 +static char aspath_delimiter_char(uint8_t type, uint8_t which) +{ + int i; + struct { + int type; + char start; + char end; + } aspath_delim_char[] = {{AS_SET, '{', '}'}, + {AS_CONFED_SET, '[', ']'}, + {AS_CONFED_SEQUENCE, '(', ')'}, + {0}}; + + for (i = 0; aspath_delim_char[i].type != 0; i++) { + if (aspath_delim_char[i].type == type) { + if (which == AS_SEG_START) + return aspath_delim_char[i].start; + else if (which == AS_SEG_END) + return aspath_delim_char[i].end; + } + } + return ' '; +} + +/* countup asns from this segment and index onward */ +static int assegment_count_asns(struct assegment *seg, int from) +{ + int count = 0; + while (seg) { + if (!from) + count += seg->length; + else { + count += (seg->length - from); + from = 0; + } + seg = seg->next; + } + return count; +} + +unsigned int aspath_count_confeds(struct aspath *aspath) +{ + int count = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_CONFED_SEQUENCE) + count += seg->length; + else if (seg->type == AS_CONFED_SET) + count++; + + seg = seg->next; + } + return count; +} + +unsigned int aspath_count_hops(const struct aspath *aspath) +{ + int count = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SEQUENCE) + count += seg->length; + else if (seg->type == AS_SET) + count++; + + seg = seg->next; + } + return count; +} + +/* Check if aspath has AS_SET or AS_CONFED_SET */ +bool aspath_check_as_sets(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SET || seg->type == AS_CONFED_SET) + return true; + seg = seg->next; + } + return false; +} + +/* Check if aspath has BGP_AS_ZERO */ +bool aspath_check_as_zero(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] == BGP_AS_ZERO) + return true; + seg = seg->next; + } + + return false; +} + +/* Estimate size aspath /might/ take if encoded into an + * ASPATH attribute. + * + * This is a quick estimate, not definitive! aspath_put() + * may return a different number!! + */ +unsigned int aspath_size(struct aspath *aspath) +{ + int size = 0; + struct assegment *seg = aspath->segments; + + while (seg) { + size += ASSEGMENT_SIZE(seg->length, 1); + seg = seg->next; + } + return size; +} + +/* Return highest public ASN in path */ +as_t aspath_highest(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + as_t highest = 0; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] > highest + && !BGP_AS_IS_PRIVATE(seg->as[i])) + highest = seg->as[i]; + seg = seg->next; + } + return highest; +} + +/* Return the left-most ASN in path */ +as_t aspath_leftmost(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + as_t leftmost = 0; + + if (seg && seg->length && seg->type == AS_SEQUENCE) + leftmost = seg->as[0]; + + return leftmost; +} + +/* Return 1 if there are any 4-byte ASes in the path */ +bool aspath_has_as4(struct aspath *aspath) +{ + struct assegment *seg = aspath->segments; + unsigned int i; + + while (seg) { + for (i = 0; i < seg->length; i++) + if (seg->as[i] > BGP_AS_MAX) + return true; + seg = seg->next; + } + return false; +} + +/* Convert aspath structure to string expression. */ +static void aspath_make_str_count(struct aspath *as, bool make_json) +{ + struct assegment *seg; + int str_size; + int len = 0; + char *str_buf; + json_object *jaspath_segments = NULL; + json_object *jseg = NULL; + json_object *jseg_list = NULL; + + if (make_json) { + as->json = json_object_new_object(); + jaspath_segments = json_object_new_array(); + } + + /* Empty aspath. */ + if (!as->segments) { + if (make_json) { + json_object_string_add(as->json, "string", "Local"); + json_object_object_add(as->json, "segments", + jaspath_segments); + json_object_int_add(as->json, "length", 0); + } + as->str = XMALLOC(MTYPE_AS_STR, 1); + as->str[0] = '\0'; + as->str_len = 0; + return; + } + + seg = as->segments; + +/* ASN takes 5 to 10 chars plus separator, see below. + * If there is one differing segment type, we need an additional + * 2 chars for segment delimiters, and the final '\0'. + * Hopefully this is large enough to avoid hitting the realloc + * code below for most common sequences. + * + * This was changed to 10 after the well-known BGP assertion, which + * had hit some parts of the Internet in May of 2009. + * plain format : '4294967295 ' : 10 + 1 + * astod format : '65535.65535 ': 11 + 1 + */ +#define ASN_STR_LEN (11 + 1) + str_size = MAX(assegment_count_asns(seg, 0) * ASN_STR_LEN + 2 + 1, + ASPATH_STR_DEFAULT_LEN); + str_buf = XMALLOC(MTYPE_AS_STR, str_size); + + while (seg) { + int i; + char separator; + + /* Check AS type validity. Set separator for segment */ + switch (seg->type) { + case AS_SET: + case AS_CONFED_SET: + separator = ','; + break; + case AS_SEQUENCE: + case AS_CONFED_SEQUENCE: + separator = ' '; + break; + default: + XFREE(MTYPE_AS_STR, str_buf); + as->str = NULL; + as->str_len = 0; + json_object_free(as->json); + as->json = NULL; + + return; + } + +/* We might need to increase str_buf, particularly if path has + * differing segments types, our initial guesstimate above will + * have been wrong. Need 11 chars for ASN, a separator each and + * potentially two segment delimiters, plus a space between each + * segment and trailing zero. + * + * This definitely didn't work with the value of 5 bytes and + * 32-bit ASNs. + */ +#define SEGMENT_STR_LEN(X) (((X)->length * ASN_STR_LEN) + 2 + 1 + 1) + if ((len + SEGMENT_STR_LEN(seg)) > str_size) { + str_size = len + SEGMENT_STR_LEN(seg); + str_buf = XREALLOC(MTYPE_AS_STR, str_buf, str_size); + } +#undef ASN_STR_LEN +#undef SEGMENT_STR_LEN + + if (seg->type != AS_SEQUENCE) + len += snprintf( + str_buf + len, str_size - len, "%c", + aspath_delimiter_char(seg->type, AS_SEG_START)); + + if (make_json) + jseg_list = json_object_new_array(); + + /* write out the ASNs, with their separators, bar the last one*/ + for (i = 0; i < seg->length; i++) { + if (make_json) + asn_asn2json_array(jseg_list, seg->as[i], + as->asnotation); + len += snprintfrr(str_buf + len, str_size - len, + ASN_FORMAT(as->asnotation), + &seg->as[i]); + + if (i < (seg->length - 1)) + len += snprintf(str_buf + len, str_size - len, + "%c", separator); + } + + if (make_json) { + jseg = json_object_new_object(); + json_object_string_add( + jseg, "type", + aspath_segment_type_str[seg->type]); + json_object_object_add(jseg, "list", jseg_list); + json_object_array_add(jaspath_segments, jseg); + } + + if (seg->type != AS_SEQUENCE) + len += snprintf( + str_buf + len, str_size - len, "%c", + aspath_delimiter_char(seg->type, AS_SEG_END)); + if (seg->next) + len += snprintf(str_buf + len, str_size - len, " "); + + seg = seg->next; + } + + assert(len < str_size); + + str_buf[len] = '\0'; + as->str = str_buf; + as->str_len = len; + + if (make_json) { + json_object_string_add(as->json, "string", str_buf); + json_object_object_add(as->json, "segments", jaspath_segments); + json_object_int_add(as->json, "length", aspath_count_hops(as)); + } + + return; +} + +void aspath_str_update(struct aspath *as, bool make_json) +{ + XFREE(MTYPE_AS_STR, as->str); + + if (as->json) { + json_object_free(as->json); + as->json = NULL; + } + + aspath_make_str_count(as, make_json); +} + +/* Intern allocated AS path. */ +struct aspath *aspath_intern(struct aspath *aspath) +{ + struct aspath *find; + + /* Assert this AS path structure is not interned and has the string + representation built. */ + assert(aspath->refcnt == 0); + assert(aspath->str); + + /* Check AS path hash. */ + find = hash_get(ashash, aspath, hash_alloc_intern); + if (find != aspath) + aspath_free(aspath); + + find->refcnt++; + + return find; +} + +/* Duplicate aspath structure. Created same aspath structure but + reference count and AS path string is cleared. */ +struct aspath *aspath_dup(struct aspath *aspath) +{ + unsigned short buflen = aspath->str_len + 1; + struct aspath *new; + + new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + new->json = NULL; + + if (aspath->segments) + new->segments = assegment_dup_all(aspath->segments); + + if (!aspath->str) + return new; + + new->str = XMALLOC(MTYPE_AS_STR, buflen); + new->str_len = aspath->str_len; + new->asnotation = aspath->asnotation; + + /* copy the string data */ + if (aspath->str_len > 0) + memcpy(new->str, aspath->str, buflen); + else + new->str[0] = '\0'; + + return new; +} + +static void *aspath_hash_alloc(void *arg) +{ + const struct aspath *aspath = arg; + struct aspath *new; + + /* Malformed AS path value. */ + assert(aspath->str); + + /* New aspath structure is needed. */ + new = XMALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + + /* Reuse segments and string representation */ + new->refcnt = 0; + new->segments = aspath->segments; + new->str = aspath->str; + new->str_len = aspath->str_len; + new->json = aspath->json; + new->asnotation = aspath->asnotation; + + return new; +} + +/* parse as-segment byte stream in struct assegment */ +static int assegments_parse(struct stream *s, size_t length, + struct assegment **result, int use32bit) +{ + struct assegment_header segh; + struct assegment *seg, *prev = NULL, *head = NULL; + size_t bytes = 0; + + /* empty aspath (ie iBGP or somesuch) */ + if (length == 0) + return 0; + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: got total byte length %lu", + (unsigned long)length); + /* basic checks */ + if ((STREAM_READABLE(s) < length) + || (STREAM_READABLE(s) < AS_HEADER_SIZE) + || (length % AS16_VALUE_SIZE)) + return -1; + + while (bytes < length) { + int i; + size_t seg_size; + + if ((length - bytes) <= AS_HEADER_SIZE) { + if (head) + assegment_free_all(head); + return -1; + } + + /* softly softly, get the header first on its own */ + segh.type = stream_getc(s); + segh.length = stream_getc(s); + + seg_size = ASSEGMENT_SIZE(segh.length, use32bit); + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: got type %d, length %d", + segh.type, segh.length); + + /* check it.. */ + if (((bytes + seg_size) > length) + /* 1771bis 4.3b: seg length contains one or more */ + || (segh.length == 0) + /* Paranoia in case someone changes type of segment length. + * Shift both values by 0x10 to make the comparison operate + * on more, than 8 bits (otherwise it's a warning, bug + * #564). + */ + || ((sizeof(segh.length) > 1) + && (0x10 + segh.length > 0x10 + AS_SEGMENT_MAX))) { + if (head) + assegment_free_all(head); + return -1; + } + + switch (segh.type) { + case AS_SEQUENCE: + case AS_SET: + case AS_CONFED_SEQUENCE: + case AS_CONFED_SET: + break; + default: + if (head) + assegment_free_all(head); + return -1; + } + + /* now its safe to trust lengths */ + seg = assegment_new(segh.type, segh.length); + + if (head) + prev->next = seg; + else /* it's the first segment */ + head = seg; + + for (i = 0; i < segh.length; i++) + seg->as[i] = + (use32bit) ? stream_getl(s) : stream_getw(s); + + bytes += seg_size; + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + zlog_debug( + "[AS4SEG] Parse aspath segment: Bytes now: %lu", + (unsigned long)bytes); + + prev = seg; + } + + *result = assegment_normalise(head); + return 0; +} + +/* AS path parse function. pnt is a pointer to byte stream and length + is length of byte stream. If there is same AS path in the the AS + path hash then return it else make new AS path structure. + + On error NULL is returned. + */ +struct aspath *aspath_parse(struct stream *s, size_t length, int use32bit, + enum asnotation_mode asnotation) +{ + struct aspath as; + struct aspath *find; + + /* If length is odd it's malformed AS path. */ + /* Nit-picking: if (use32bit == 0) it is malformed if odd, + * otherwise its malformed when length is larger than 2 and (length-2) + * is not dividable by 4. + * But... this time we're lazy + */ + if (length % AS16_VALUE_SIZE) + return NULL; + + memset(&as, 0, sizeof(as)); + as.asnotation = asnotation; + if (assegments_parse(s, length, &as.segments, use32bit) < 0) + return NULL; + + /* If already same aspath exist then return it. */ + find = hash_get(ashash, &as, aspath_hash_alloc); + + /* if the aspath was already hashed free temporary memory. */ + if (find->refcnt) { + assegment_free_all(as.segments); + /* aspath_key_make() always updates the string */ + XFREE(MTYPE_AS_STR, as.str); + if (as.json) { + json_object_free(as.json); + as.json = NULL; + } + } + + find->refcnt++; + + return find; +} + +static void assegment_data_put(struct stream *s, as_t *as, int num, + int use32bit) +{ + int i; + assert(num <= AS_SEGMENT_MAX); + + for (i = 0; i < num; i++) + if (use32bit) + stream_putl(s, as[i]); + else { + if (as[i] <= BGP_AS_MAX) + stream_putw(s, as[i]); + else + stream_putw(s, BGP_AS_TRANS); + } +} + +static size_t assegment_header_put(struct stream *s, uint8_t type, int length) +{ + size_t lenp; + assert(length <= AS_SEGMENT_MAX); + stream_putc(s, type); + lenp = stream_get_endp(s); + stream_putc(s, length); + return lenp; +} + +/* write aspath data to stream */ +size_t aspath_put(struct stream *s, struct aspath *as, int use32bit) +{ + struct assegment *seg = as->segments; + size_t bytes = 0; + + if (!seg || seg->length == 0) + return 0; + + /* + * Hey, what do we do when we have > STREAM_WRITABLE(s) here? + * At the moment, we would write out a partial aspath, and our + * peer + * will complain and drop the session :-/ + * + * The general assumption here is that many things tested will + * never happen. And, in real live, up to now, they have not. + */ + while (seg && (ASSEGMENT_LEN(seg, use32bit) <= STREAM_WRITEABLE(s))) { + struct assegment *next = seg->next; + int written = 0; + int asns_packed = 0; + size_t lenp; + + /* Overlength segments have to be split up */ + while ((seg->length - written) > AS_SEGMENT_MAX) { + assegment_header_put(s, seg->type, AS_SEGMENT_MAX); + assegment_data_put(s, (seg->as + written), + AS_SEGMENT_MAX, use32bit); + written += AS_SEGMENT_MAX; + bytes += ASSEGMENT_SIZE(AS_SEGMENT_MAX, use32bit); + } + + /* write the final segment, probably is also the first + */ + lenp = assegment_header_put(s, seg->type, + seg->length - written); + assegment_data_put(s, (seg->as + written), + seg->length - written, use32bit); + + /* Sequence-type segments can be 'packed' together + * Case of a segment which was overlength and split up + * will be missed here, but that doesn't matter. + */ + while (next && ASSEGMENTS_PACKABLE(seg, next)) { + /* NB: We should never normally get here given + * we + * normalise aspath data when parse them. + * However, better + * safe than sorry. We potentially could call + * assegment_normalise here instead, but it's + * cheaper and + * easier to do it on the fly here rather than + * go through + * the segment list twice every time we write + * out + * aspath's. + */ + + /* Next segment's data can fit in this one */ + assegment_data_put(s, next->as, next->length, use32bit); + + /* update the length of the segment header */ + stream_putc_at(s, lenp, + seg->length - written + next->length); + asns_packed += next->length; + + next = next->next; + } + + bytes += ASSEGMENT_SIZE(seg->length - written + asns_packed, + use32bit); + seg = next; + } + return bytes; +} + +/* This is for SNMP BGP4PATHATTRASPATHSEGMENT + * We have no way to manage the storage, so we use a static stream + * wrapper around aspath_put. + */ +uint8_t *aspath_snmp_pathseg(struct aspath *as, size_t *varlen) +{ +#define SNMP_PATHSEG_MAX 1024 + + if (!snmp_stream) + snmp_stream = stream_new(SNMP_PATHSEG_MAX); + else + stream_reset(snmp_stream); + + if (!as) { + *varlen = 0; + return NULL; + } + aspath_put(snmp_stream, as, 0); /* use 16 bit for now here */ + + *varlen = stream_get_endp(snmp_stream); + return stream_pnt(snmp_stream); +} + +static struct assegment *aspath_aggregate_as_set_add(struct aspath *aspath, + struct assegment *asset, + as_t as) +{ + int i; + + /* If this is first AS set member, create new as-set segment. */ + if (asset == NULL) { + asset = assegment_new(AS_SET, 1); + if (!aspath->segments) + aspath->segments = asset; + else { + struct assegment *seg = aspath->segments; + while (seg->next) + seg = seg->next; + seg->next = asset; + } + asset->as[0] = as; + } else { + /* Check this AS value already exists or not. */ + for (i = 0; i < asset->length; i++) + if (asset->as[i] == as) + return asset; + + asset->length++; + asset->as = XREALLOC(MTYPE_AS_SEG_DATA, asset->as, + asset->length * AS_VALUE_SIZE); + asset->as[asset->length - 1] = as; + } + + + return asset; +} + +/* Modify as1 using as2 for aggregation. */ +struct aspath *aspath_aggregate(struct aspath *as1, struct aspath *as2) +{ + int i; + int minlen = 0; + int match = 0; + int from; + struct assegment *seg1 = as1->segments; + struct assegment *seg2 = as2->segments; + struct aspath *aspath = NULL; + struct assegment *asset = NULL; + struct assegment *prevseg = NULL; + + /* First of all check common leading sequence. */ + while (seg1 && seg2) { + /* Check segment type. */ + if (seg1->type != seg2->type) + break; + + /* Minimum segment length. */ + minlen = MIN(seg1->length, seg2->length); + + for (match = 0; match < minlen; match++) + if (seg1->as[match] != seg2->as[match]) + break; + + if (match) { + struct assegment *seg = assegment_new(seg1->type, 0); + + seg = assegment_append_asns(seg, seg1->as, match); + + if (!aspath) { + aspath = aspath_new(as1->asnotation); + aspath->segments = seg; + } else + prevseg->next = seg; + + prevseg = seg; + } + + if (match != minlen || match != seg1->length + || seg1->length != seg2->length) + break; + /* We are moving on to the next segment to reset match */ + else + match = 0; + + seg1 = seg1->next; + seg2 = seg2->next; + } + + if (!aspath) + aspath = aspath_new(as1->asnotation); + + /* Make as-set using rest of all information. */ + from = match; + while (seg1) { + for (i = from; i < seg1->length; i++) + asset = aspath_aggregate_as_set_add(aspath, asset, + seg1->as[i]); + + from = 0; + seg1 = seg1->next; + } + + from = match; + while (seg2) { + for (i = from; i < seg2->length; i++) + asset = aspath_aggregate_as_set_add(aspath, asset, + seg2->as[i]); + + from = 0; + seg2 = seg2->next; + } + + assegment_normalise(aspath->segments); + aspath_str_update(aspath, false); + return aspath; +} + +/* When a BGP router receives an UPDATE with an MP_REACH_NLRI + attribute, check the leftmost AS number in the AS_PATH attribute is + or not the peer's AS number. */ +bool aspath_firstas_check(struct aspath *aspath, as_t asno) +{ + if ((aspath == NULL) || (aspath->segments == NULL)) + return false; + + if (aspath->segments && (aspath->segments->type == AS_SEQUENCE) + && (aspath->segments->as[0] == asno)) + return true; + + return false; +} + +unsigned int aspath_get_first_as(struct aspath *aspath) +{ + if (aspath == NULL || aspath->segments == NULL) + return 0; + + return aspath->segments->as[0]; +} + +unsigned int aspath_get_last_as(struct aspath *aspath) +{ + int i; + unsigned int last_as = 0; + const struct assegment *seg; + + if (aspath == NULL || aspath->segments == NULL) + return last_as; + + seg = aspath->segments; + + while (seg) { + if (seg->type == AS_SEQUENCE || seg->type == AS_CONFED_SEQUENCE) + for (i = 0; i < seg->length; i++) + last_as = seg->as[i]; + seg = seg->next; + } + + return last_as; +} + +/* AS path loop check. If aspath contains asno then return >= 1. */ +int aspath_loop_check(struct aspath *aspath, as_t asno) +{ + struct assegment *seg; + int count = 0; + + if ((aspath == NULL) || (aspath->segments == NULL)) + return 0; + + seg = aspath->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) + if (seg->as[i] == asno) + count++; + + seg = seg->next; + } + return count; +} + +/* AS path loop check. If aspath contains asno + * that is a confed id then return >= 1. + */ +int aspath_loop_check_confed(struct aspath *aspath, as_t asno) +{ + struct assegment *seg; + int count = 0; + + if (aspath == NULL || aspath->segments == NULL) + return 0; + + seg = aspath->segments; + + while (seg) { + unsigned int i; + + for (i = 0; i < seg->length; i++) + if (seg->type != AS_CONFED_SEQUENCE && + seg->type != AS_CONFED_SET && seg->as[i] == asno) + count++; + + seg = seg->next; + } + return count; +} + + +/* When all of AS path is private AS return 1. */ +bool aspath_private_as_check(struct aspath *aspath) +{ + struct assegment *seg; + + if (!(aspath && aspath->segments)) + return false; + + seg = aspath->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + if (!BGP_AS_IS_PRIVATE(seg->as[i])) + return false; + } + seg = seg->next; + } + return true; +} + +/* Replace all ASN instances of the regex rule with our own ASN */ +struct aspath *aspath_replace_regex_asn(struct aspath *aspath, + struct as_list *acl_list, as_t our_asn) +{ + struct aspath *new; + struct assegment *cur_seg; + struct as_list *cur_as_list; + struct as_filter *cur_as_filter; + char str_buf[ASPATH_STR_DEFAULT_LEN]; + uint32_t i; + + new = aspath_dup(aspath); + cur_seg = new->segments; + + while (cur_seg) { + cur_as_list = acl_list; + while (cur_as_list) { + cur_as_filter = cur_as_list->head; + while (cur_as_filter) { + for (i = 0; i < cur_seg->length; i++) { + snprintfrr(str_buf, + ASPATH_STR_DEFAULT_LEN, + ASN_FORMAT(new->asnotation), + &cur_seg->as[i]); + if (!regexec(cur_as_filter->reg, + str_buf, 0, NULL, 0)) + cur_seg->as[i] = our_asn; + } + cur_as_filter = cur_as_filter->next; + } + cur_as_list = cur_as_list->next; + } + cur_seg = cur_seg->next; + } + + aspath_str_update(new, false); + return new; +} + + +/* Replace all instances of the target ASN with our own ASN */ +struct aspath *aspath_replace_specific_asn(struct aspath *aspath, + as_t target_asn, as_t our_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + if (seg->as[i] == target_asn) + seg->as[i] = our_asn; + } + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Replace all ASNs with our own ASN */ +struct aspath *aspath_replace_all_asn(struct aspath *aspath, as_t our_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) + seg->as[i] = our_asn; + + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Replace all private ASNs with our own ASN */ +struct aspath *aspath_replace_private_asns(struct aspath *aspath, as_t asn, + as_t peer_asn) +{ + struct aspath *new; + struct assegment *seg; + + new = aspath_dup(aspath); + seg = new->segments; + + while (seg) { + int i; + + for (i = 0; i < seg->length; i++) { + /* Don't replace if public ASN or peer's ASN */ + if (BGP_AS_IS_PRIVATE(seg->as[i]) + && (seg->as[i] != peer_asn)) + seg->as[i] = asn; + } + seg = seg->next; + } + + aspath_str_update(new, false); + return new; +} + +/* Remove all private ASNs */ +struct aspath *aspath_remove_private_asns(struct aspath *aspath, as_t peer_asn) +{ + struct aspath *new; + struct assegment *seg; + struct assegment *new_seg; + struct assegment *last_new_seg; + int i; + int j; + int public = 0; + int peer = 0; + + new = XCALLOC(MTYPE_AS_PATH, sizeof(struct aspath)); + + new->json = NULL; + new_seg = NULL; + last_new_seg = NULL; + seg = aspath->segments; + while (seg) { + public = 0; + peer = 0; + for (i = 0; i < seg->length; i++) { + // ASN is public + if (!BGP_AS_IS_PRIVATE(seg->as[i])) + public++; + /* ASN matches peer's. + * Don't double-count if peer_asn is public. + */ + else if (seg->as[i] == peer_asn) + peer++; + } + + // The entire segment is public so copy it + if (public == seg->length) + new_seg = assegment_dup(seg); + + // The segment is a mix of public and private ASNs. Copy as many + // spots as + // there are public ASNs then come back and fill in only the + // public ASNs. + else { + /* length needs to account for all retained ASNs + * (public or peer_asn), not just public + */ + new_seg = assegment_new(seg->type, (public + peer)); + j = 0; + for (i = 0; i < seg->length; i++) { + // keep ASN if public or matches peer's ASN + if (!BGP_AS_IS_PRIVATE(seg->as[i]) + || (seg->as[i] == peer_asn)) { + new_seg->as[j] = seg->as[i]; + j++; + } + } + } + + // This is the first segment so set the aspath segments pointer + // to this one + if (!last_new_seg) + new->segments = new_seg; + else + last_new_seg->next = new_seg; + + last_new_seg = new_seg; + seg = seg->next; + } + if (!aspath->refcnt) + aspath_free(aspath); + aspath_str_update(new, false); + return new; +} + +/* AS path confed check. If aspath contains confed set or sequence then return + * 1. */ +bool aspath_confed_check(struct aspath *aspath) +{ + struct assegment *seg; + + if (!(aspath && aspath->segments)) + return false; + + seg = aspath->segments; + + while (seg) { + if (seg->type == AS_CONFED_SET + || seg->type == AS_CONFED_SEQUENCE) + return true; + seg = seg->next; + } + return false; +} + +/* Leftmost AS path segment confed check. If leftmost AS segment is of type + AS_CONFED_SEQUENCE or AS_CONFED_SET then return 1. */ +bool aspath_left_confed_check(struct aspath *aspath) +{ + + if (!(aspath && aspath->segments)) + return false; + + if ((aspath->segments->type == AS_CONFED_SEQUENCE) + || (aspath->segments->type == AS_CONFED_SET)) + return true; + + return false; +} + +/* Merge as1 to as2. as2 should be uninterned aspath. */ +static struct aspath *aspath_merge(struct aspath *as1, struct aspath *as2) +{ + struct assegment *last, *new; + + if (!as1 || !as2) + return NULL; + + last = new = assegment_dup_all(as1->segments); + + /* find the last valid segment */ + while (last && last->next) + last = last->next; + + if (last) + last->next = as2->segments; + as2->segments = new; + aspath_str_update(as2, false); + return as2; +} + +/* Prepend as1 to as2. as2 should be uninterned aspath. */ +struct aspath *aspath_prepend(struct aspath *as1, struct aspath *as2) +{ + struct assegment *as1segtail; + struct assegment *as2segtail; + struct assegment *as2seghead; + + if (!as1 || !as2) + return NULL; + + /* If as2 is empty, only need to dupe as1's chain onto as2 */ + if (as2->segments == NULL) { + as2->segments = assegment_dup_all(as1->segments); + aspath_str_update(as2, false); + return as2; + } + + /* If as1 is empty AS, no prepending to do. */ + if (as1->segments == NULL) + return as2; + + /* find the tail as1's segment chain. */ + as1segtail = as1->segments; + while (as1segtail && as1segtail->next) + as1segtail = as1segtail->next; + + /* Delete any AS_CONFED_SEQUENCE segment from as2. */ + if (as1segtail->type == AS_SEQUENCE + && as2->segments->type == AS_CONFED_SEQUENCE) + as2 = aspath_delete_confed_seq(as2); + + if (!as2->segments) { + as2->segments = assegment_dup_all(as1->segments); + aspath_str_update(as2, false); + return as2; + } + + /* Compare last segment type of as1 and first segment type of as2. */ + if (as1segtail->type != as2->segments->type) + return aspath_merge(as1, as2); + + if (as1segtail->type == AS_SEQUENCE) { + /* We have two chains of segments, as1->segments and seg2, + * and we have to attach them together, merging the attaching + * segments together into one. + * + * 1. dupe as1->segments onto head of as2 + * 2. merge seg2's asns onto last segment of this new chain + * 3. attach chain after seg2 + */ + + /* save as2 head */ + as2seghead = as2->segments; + + /* dupe as1 onto as2's head */ + as2segtail = as2->segments = assegment_dup_all(as1->segments); + + /* refind the tail of as2 */ + while (as2segtail && as2segtail->next) + as2segtail = as2segtail->next; + + /* merge the old head, seg2, into tail, seg1 */ + assegment_append_asns(as2segtail, as2seghead->as, + as2seghead->length); + + /* + * bypass the merged seg2, and attach any chain after it + * to chain descending from as2's head + */ + if (as2segtail) + as2segtail->next = as2seghead->next; + + /* as2->segments is now referenceless and useless */ + assegment_free(as2seghead); + + /* we've now prepended as1's segment chain to as2, merging + * the inbetween AS_SEQUENCE of seg2 in the process + */ + aspath_str_update(as2, false); + return as2; + } else { + /* AS_SET merge code is needed at here. */ + return aspath_merge(as1, as2); + } + /* XXX: Ermmm, what if as1 has multiple segments?? */ + + /* Not reached */ +} + +/* insert aspath exclude in head of orphan exclude list*/ +void as_exclude_set_orphan(struct aspath_exclude *ase) +{ + ase->exclude_aspath_acl = NULL; + as_list_list_add_head(&as_exclude_list_orphan, ase); +} + +void as_exclude_remove_orphan(struct aspath_exclude *ase) +{ + if (as_list_list_count(&as_exclude_list_orphan)) + as_list_list_del(&as_exclude_list_orphan, ase); +} + +/* currently provide only one exclude, not a list */ +struct aspath_exclude *as_exclude_lookup_orphan(const char *acl_name) +{ + struct aspath_exclude *ase = NULL; + char *name = NULL; + + frr_each (as_list_list, &as_exclude_list_orphan, ase) { + if (ase->exclude_aspath_acl_name) { + name = ase->exclude_aspath_acl_name; + if (!strcmp(name, acl_name)) + break; + } + } + if (ase) + as_exclude_remove_orphan(ase); + + return ase; +} + +/* Iterate over AS_PATH segments and wipe all occurrences of the + * listed AS numbers. Hence some segments may lose some or even + * all data on the way, the operation is implemented as a smarter + * version of aspath_dup(), which allocates memory to hold the new + * data, not the original. The new AS path is returned. + */ +struct aspath *aspath_filter_exclude(struct aspath *source, + struct aspath *exclude_list) +{ + struct assegment *srcseg, *exclseg, *lastseg; + struct aspath *newpath; + + newpath = aspath_new(source->asnotation); + lastseg = NULL; + + for (srcseg = source->segments; srcseg; srcseg = srcseg->next) { + unsigned i, y, newlen = 0, done = 0, skip_as; + struct assegment *newseg; + + /* Find out, how much ASns are we going to pick from this + * segment. + * We can't perform filtering right inline, because the size of + * the new segment isn't known at the moment yet. + */ + for (i = 0; i < srcseg->length; i++) { + skip_as = 0; + for (exclseg = exclude_list->segments; + exclseg && !skip_as; exclseg = exclseg->next) + for (y = 0; y < exclseg->length; y++) + if (srcseg->as[i] == exclseg->as[y]) { + skip_as = 1; + // There's no sense in testing + // the rest of exclusion list, + // bail out. + break; + } + if (!skip_as) + newlen++; + } + /* newlen is now the number of ASns to copy */ + if (!newlen) + continue; + + /* Actual copying. Allocate memory and iterate once more, + * performing filtering. */ + newseg = assegment_new(srcseg->type, newlen); + for (i = 0; i < srcseg->length; i++) { + skip_as = 0; + for (exclseg = exclude_list->segments; + exclseg && !skip_as; exclseg = exclseg->next) + for (y = 0; y < exclseg->length; y++) + if (srcseg->as[i] == exclseg->as[y]) { + skip_as = 1; + break; + } + if (skip_as) + continue; + newseg->as[done++] = srcseg->as[i]; + } + /* At his point newlen must be equal to done, and both must be + * positive. Append + * the filtered segment to the gross result. */ + if (!lastseg) + newpath->segments = newseg; + else + lastseg->next = newseg; + lastseg = newseg; + } + aspath_str_update(newpath, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + aspath_free(source); + return newpath; +} + +struct aspath *aspath_filter_exclude_all(struct aspath *source) +{ + struct aspath *newpath; + + newpath = aspath_new(source->asnotation); + + aspath_str_update(newpath, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + aspath_free(source); + return newpath; +} + +struct aspath *aspath_filter_exclude_acl(struct aspath *source, + struct as_list *acl_list) +{ + struct assegment *cur_seg, *new_seg, *prev_seg, *next_seg; + struct as_list *cur_as_list; + struct as_filter *cur_as_filter; + char str_buf[ASPATH_STR_DEFAULT_LEN]; + uint32_t nb_as_del; + uint32_t i, j; + + cur_seg = source->segments; + prev_seg = NULL; + /* segments from source aspath */ + while (cur_seg) { + next_seg = cur_seg->next; + cur_as_list = acl_list; + nb_as_del = 0; + /* aspath filter list from acl_list */ + while (cur_as_list) { + cur_as_filter = cur_as_list->head; + while (cur_as_filter) { + for (i = 0; i < cur_seg->length; i++) { + if (cur_seg->as[i] == 0) + continue; + + snprintfrr(str_buf, + ASPATH_STR_DEFAULT_LEN, + ASN_FORMAT(source->asnotation), + &cur_seg->as[i]); + if (!regexec(cur_as_filter->reg, + str_buf, 0, NULL, 0)) { + cur_seg->as[i] = 0; + nb_as_del++; + } + } + + cur_as_filter = cur_as_filter->next; + } + + cur_as_list = cur_as_list->next; + } + /* full segment is excluded remove it */ + if (nb_as_del == cur_seg->length) { + if (cur_seg == source->segments) + /* first segment */ + source->segments = cur_seg->next; + else if (prev_seg) + prev_seg->next = cur_seg->next; + assegment_free(cur_seg); + } + /* change in segment size -> new allocation and replace segment*/ + else if (nb_as_del) { + new_seg = assegment_new(cur_seg->type, + cur_seg->length - nb_as_del); + j = 0; + for (i = 0; i < cur_seg->length; i++) { + if (cur_seg->as[i] == 0) + continue; + new_seg->as[j] = cur_seg->as[i]; + j++; + } + new_seg->next = next_seg; + if (cur_seg == source->segments) + /* first segment */ + source->segments = new_seg; + else if (prev_seg) + prev_seg->next = new_seg; + assegment_free(cur_seg); + prev_seg = new_seg; + } else + prev_seg = cur_seg; + cur_seg = next_seg; + } + + + aspath_str_update(source, false); + /* We are happy returning even an empty AS_PATH, because the + * administrator + * might expect this very behaviour. There's a mean to avoid this, if + * necessary, + * by having a match rule against certain AS_PATH regexps in the + * route-map index. + */ + return source; +} + + +/* Add specified AS to the leftmost of aspath. */ +static struct aspath *aspath_add_asns(struct aspath *aspath, as_t asno, + uint8_t type, unsigned num) +{ + struct assegment *assegment = aspath->segments; + unsigned i; + + if (assegment && assegment->type == type) { + /* extend existing segment */ + aspath->segments = + assegment_prepend_asns(aspath->segments, asno, num); + } else { + /* prepend with new segment */ + struct assegment *newsegment = assegment_new(type, num); + for (i = 0; i < num; i++) + newsegment->as[i] = asno; + + /* insert potentially replacing empty segment */ + if (assegment && assegment->length == 0) { + newsegment->next = assegment->next; + assegment_free(assegment); + } else + newsegment->next = assegment; + aspath->segments = newsegment; + } + + aspath_str_update(aspath, false); + return aspath; +} + +/* Add specified AS to the leftmost of aspath num times. */ +struct aspath *aspath_add_seq_n(struct aspath *aspath, as_t asno, unsigned num) +{ + return aspath_add_asns(aspath, asno, AS_SEQUENCE, num); +} + +/* Add specified AS to the leftmost of aspath. */ +struct aspath *aspath_add_seq(struct aspath *aspath, as_t asno) +{ + return aspath_add_asns(aspath, asno, AS_SEQUENCE, 1); +} + +/* Compare leftmost AS value for MED check. If as1's leftmost AS and + as2's leftmost AS is same return 1. */ +bool aspath_cmp_left(const struct aspath *aspath1, const struct aspath *aspath2) +{ + const struct assegment *seg1; + const struct assegment *seg2; + + if (!(aspath1 && aspath2)) + return false; + + seg1 = aspath1->segments; + seg2 = aspath2->segments; + + /* If both paths are originated in this AS then we do want to compare + * MED */ + if (!seg1 && !seg2) + return true; + + /* find first non-confed segments for each */ + while (seg1 && ((seg1->type == AS_CONFED_SEQUENCE) + || (seg1->type == AS_CONFED_SET))) + seg1 = seg1->next; + + while (seg2 && ((seg2->type == AS_CONFED_SEQUENCE) + || (seg2->type == AS_CONFED_SET))) + seg2 = seg2->next; + + /* Check as1's */ + if (!(seg1 && seg2 && (seg1->type == AS_SEQUENCE) + && (seg2->type == AS_SEQUENCE))) + return false; + + if (seg1->as[0] == seg2->as[0]) + return true; + + return false; +} + +/* Truncate an aspath after a number of hops, and put the hops remaining + * at the front of another aspath. Needed for AS4 compat. + * + * Returned aspath is a /new/ aspath, which should either by free'd or + * interned by the caller, as desired. + */ +struct aspath *aspath_reconcile_as4(struct aspath *aspath, + struct aspath *as4path) +{ + struct assegment *seg, *newseg, *prevseg = NULL; + struct aspath *newpath = NULL, *mergedpath; + int hops, cpasns = 0; + + if (!aspath || !as4path) + return NULL; + + seg = aspath->segments; + + /* CONFEDs should get reconciled too.. */ + hops = (aspath_count_hops(aspath) + aspath_count_confeds(aspath)) + - aspath_count_hops(as4path); + + if (hops < 0) { + if (BGP_DEBUG(as4, AS4)) + flog_warn( + EC_BGP_ASPATH_FEWER_HOPS, + "[AS4] Fewer hops in AS_PATH than NEW_AS_PATH"); + /* Something's gone wrong. The RFC says we should now ignore + * AS4_PATH, + * which is daft behaviour - it contains vital loop-detection + * information which must have been removed from AS_PATH. + */ + hops = aspath_count_hops(aspath); + } + + if (!hops) { + newpath = aspath_dup(as4path); + aspath_str_update(newpath, false); + return newpath; + } + + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] got AS_PATH %s and AS4_PATH %s synthesizing now", + aspath->str, as4path->str); + + while (seg && hops > 0) { + switch (seg->type) { + case AS_SET: + case AS_CONFED_SET: + hops--; + cpasns = seg->length; + break; + case AS_CONFED_SEQUENCE: + /* Should never split a confed-sequence, if hop-count + * suggests we must then something's gone wrong + * somewhere. + * + * Most important goal is to preserve AS_PATHs prime + * function + * as loop-detector, so we fudge the numbers so that the + * entire + * confed-sequence is merged in. + */ + if (hops < seg->length) { + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] AS4PATHmangle: AS_CONFED_SEQUENCE falls across 2/4 ASN boundary somewhere, broken.."); + hops = seg->length; + } + fallthrough; + case AS_SEQUENCE: + cpasns = MIN(seg->length, hops); + hops -= seg->length; + } + + assert(cpasns <= seg->length); + + newseg = assegment_new(seg->type, 0); + newseg = assegment_append_asns(newseg, seg->as, cpasns); + + if (!newpath) { + newpath = aspath_new(aspath->asnotation); + newpath->segments = newseg; + } else + prevseg->next = newseg; + + prevseg = newseg; + seg = seg->next; + } + + /* We may be able to join some segments here, and we must + * do this because... we want normalised aspaths in out hash + * and we do not want to stumble in aspath_put. + */ + mergedpath = aspath_merge(newpath, aspath_dup(as4path)); + aspath_free(newpath); + mergedpath->segments = assegment_normalise(mergedpath->segments); + aspath_str_update(mergedpath, false); + + if (BGP_DEBUG(as4, AS4)) + zlog_debug("[AS4] result of synthesizing is %s", + mergedpath->str); + + return mergedpath; +} + +/* Compare leftmost AS value for MED check. If as1's leftmost AS and + as2's leftmost AS is same return 1. (confederation as-path + only). */ +bool aspath_cmp_left_confed(const struct aspath *aspath1, + const struct aspath *aspath2) +{ + if (!(aspath1 && aspath2)) + return false; + + if (!(aspath1->segments && aspath2->segments)) + return false; + + if ((aspath1->segments->type != AS_CONFED_SEQUENCE) + || (aspath2->segments->type != AS_CONFED_SEQUENCE)) + return false; + + if (aspath1->segments->as[0] == aspath2->segments->as[0]) + return true; + + return false; +} + +/* Delete all AS_CONFED_SEQUENCE/SET segments from aspath. + * RFC 5065 section 4.1.c.1 + * + * 1) if any path segments of the AS_PATH are of the type + * AS_CONFED_SEQUENCE or AS_CONFED_SET, those segments MUST be + * removed from the AS_PATH attribute, leaving the sanitized + * AS_PATH attribute to be operated on by steps 2, 3 or 4. + */ +struct aspath *aspath_delete_confed_seq(struct aspath *aspath) +{ + struct assegment *seg, *prev, *next; + char removed_confed_segment; + + if (!(aspath && aspath->segments)) + return aspath; + + seg = aspath->segments; + removed_confed_segment = 0; + next = NULL; + prev = NULL; + + while (seg) { + next = seg->next; + + if (seg->type == AS_CONFED_SEQUENCE + || seg->type == AS_CONFED_SET) { + /* This is the first segment in the aspath */ + if (aspath->segments == seg) + aspath->segments = seg->next; + else + prev->next = seg->next; + + assegment_free(seg); + removed_confed_segment = 1; + } else + prev = seg; + + seg = next; + } + + if (removed_confed_segment) + aspath_str_update(aspath, false); + + return aspath; +} + +/* Add new AS number to the leftmost part of the aspath as + AS_CONFED_SEQUENCE. */ +struct aspath *aspath_add_confed_seq(struct aspath *aspath, as_t asno) +{ + return aspath_add_asns(aspath, asno, AS_CONFED_SEQUENCE, 1); +} + +/* Add new as value to as path structure. */ +static void aspath_as_add(struct aspath *as, as_t asno) +{ + struct assegment *seg = as->segments; + + if (!seg) + return; + + /* Last segment search procedure. */ + while (seg->next) + seg = seg->next; + + assegment_append_asns(seg, &asno, 1); +} + +/* Add new as segment to the as path. */ +static void aspath_segment_add(struct aspath *as, int type) +{ + struct assegment *seg = as->segments; + struct assegment *new = assegment_new(type, 0); + + if (seg) { + while (seg->next) + seg = seg->next; + seg->next = new; + } else + as->segments = new; +} + +struct aspath *aspath_empty(enum asnotation_mode asnotation) +{ + return aspath_parse(NULL, 0, 1, asnotation); /* 32Bit ;-) */ +} + +struct aspath *aspath_empty_get(void) +{ + struct aspath *aspath; + + aspath = aspath_new(bgp_get_asnotation(NULL)); + aspath_make_str_count(aspath, false); + return aspath; +} + +unsigned long aspath_count(void) +{ + return ashash->count; +} + +/* + Theoretically, one as path can have: + + One BGP packet size should be less than 4096. + One BGP attribute size should be less than 4096 - BGP header size. + One BGP aspath size should be less than 4096 - BGP header size - + BGP mandantry attribute size. +*/ + +/* AS path string lexical token enum. */ +enum as_token { + as_token_asval, + as_token_set_start, + as_token_set_end, + as_token_confed_seq_start, + as_token_confed_seq_end, + as_token_confed_set_start, + as_token_confed_set_end, + as_token_unknown +}; + +/* Return next token and point for string parse. */ +static const char *aspath_gettoken(const char *buf, enum as_token *token, + unsigned long *asno) +{ + const char *p = buf; + as_t asval; + bool found = false; + + /* Skip separators (space for sequences, ',' for sets). */ + while (isspace((unsigned char)*p) || *p == ',') + p++; + + /* Check the end of the string and type specify characters + (e.g. {}()). */ + switch (*p) { + case '\0': + return NULL; + case '{': + *token = as_token_set_start; + p++; + return p; + case '}': + *token = as_token_set_end; + p++; + return p; + case '(': + *token = as_token_confed_seq_start; + p++; + return p; + case ')': + *token = as_token_confed_seq_end; + p++; + return p; + case '[': + *token = as_token_confed_set_start; + p++; + return p; + case ']': + *token = as_token_confed_set_end; + p++; + return p; + } + + asval = 0; + p = asn_str2asn_parse(p, &asval, &found); + if (found) { + *asno = asval; + *token = as_token_asval; + } else + *token = as_token_unknown; + return p; +} + +struct aspath *aspath_str2aspath(const char *str, + enum asnotation_mode asnotation) +{ + enum as_token token = as_token_unknown; + unsigned short as_type; + unsigned long asno = 0; + struct aspath *aspath; + int needtype; + + aspath = aspath_new(asnotation); + + /* We start default type as AS_SEQUENCE. */ + as_type = AS_SEQUENCE; + needtype = 1; + + while ((str = aspath_gettoken(str, &token, &asno)) != NULL) { + switch (token) { + case as_token_asval: + if (needtype) { + aspath_segment_add(aspath, as_type); + needtype = 0; + } + aspath_as_add(aspath, asno); + break; + case as_token_set_start: + as_type = AS_SET; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_set_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_confed_seq_start: + as_type = AS_CONFED_SEQUENCE; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_confed_seq_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_confed_set_start: + as_type = AS_CONFED_SET; + aspath_segment_add(aspath, as_type); + needtype = 0; + break; + case as_token_confed_set_end: + as_type = AS_SEQUENCE; + needtype = 1; + break; + case as_token_unknown: + default: + aspath_free(aspath); + return NULL; + } + } + + aspath_make_str_count(aspath, false); + + return aspath; +} + +/* Make hash value by raw aspath data. */ +unsigned int aspath_key_make(const void *p) +{ + const struct aspath *aspath = p; + unsigned int key = 0; + + if (!aspath->str) + aspath_str_update((struct aspath *)aspath, false); + + key = jhash(aspath->str, aspath->str_len, 2334325); + + return key; +} + +/* If two aspath have same value then return 1 else return 0 */ +bool aspath_cmp(const void *arg1, const void *arg2) +{ + const struct assegment *seg1 = ((const struct aspath *)arg1)->segments; + const struct assegment *seg2 = ((const struct aspath *)arg2)->segments; + + if (((const struct aspath *)arg1)->asnotation != + ((const struct aspath *)arg2)->asnotation) + return false; + + while (seg1 || seg2) { + int i; + if ((!seg1 && seg2) || (seg1 && !seg2)) + return false; + if (seg1->type != seg2->type) + return false; + if (seg1->length != seg2->length) + return false; + for (i = 0; i < seg1->length; i++) + if (seg1->as[i] != seg2->as[i]) + return false; + seg1 = seg1->next; + seg2 = seg2->next; + } + return true; +} + +/* AS path hash initialize. */ +void aspath_init(void) +{ + ashash = hash_create_size(32768, aspath_key_make, aspath_cmp, + "BGP AS Path"); + + as_list_list_init(&as_exclude_list_orphan); +} + +void aspath_finish(void) +{ + struct aspath_exclude *ase; + + hash_clean_and_free(&ashash, (void (*)(void *))aspath_free); + + if (snmp_stream) + stream_free(snmp_stream); + + while ((ase = as_list_list_pop(&as_exclude_list_orphan))) { + aspath_free(ase->aspath); + if (ase->exclude_aspath_acl_name) + XFREE(MTYPE_TMP, ase->exclude_aspath_acl_name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, ase); + } + as_list_list_fini(&as_exclude_list_orphan); +} + +/* return and as path value */ +const char *aspath_print(struct aspath *as) +{ + return as ? as->str : "(null)"; +} + +/* Printing functions */ +/* Feed the AS_PATH to the vty; the space suffix follows it only in case + * AS_PATH wasn't empty. + */ +void aspath_print_vty(struct vty *vty, struct aspath *as) +{ + vty_out(vty, "%s%s", as->str, as->str_len ? " " : ""); +} + +static void aspath_show_all_iterator(struct hash_bucket *bucket, + struct vty *vty) +{ + struct aspath *as; + + as = (struct aspath *)bucket->data; + + vty_out(vty, "[%p:%u] (%ld) ", (void *)bucket, bucket->key, as->refcnt); + vty_out(vty, "%s\n", as->str); +} + +/* Print all aspath and hash information. This function is used from + `show [ip] bgp paths' command. */ +void aspath_print_all_vty(struct vty *vty) +{ + hash_iterate(ashash, (void (*)(struct hash_bucket *, + void *))aspath_show_all_iterator, + vty); +} + +static struct aspath *bgp_aggr_aspath_lookup(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + return hash_lookup(aggregate->aspath_hash, aspath); +} + +static void *bgp_aggr_aspath_hash_alloc(void *p) +{ + struct aspath *ref = (struct aspath *)p; + struct aspath *aspath = NULL; + + aspath = aspath_dup(ref); + return aspath; +} + +static void bgp_aggr_aspath_prepare(struct hash_bucket *hb, void *arg) +{ + struct aspath *hb_aspath = hb->data; + struct aspath **aggr_aspath = arg; + struct aspath *aspath = NULL; + + if (*aggr_aspath) { + aspath = aspath_aggregate(*aggr_aspath, hb_aspath); + aspath_free(*aggr_aspath); + *aggr_aspath = aspath; + } else { + *aggr_aspath = aspath_dup(hb_aspath); + } +} + +void bgp_aggr_aspath_remove(void *arg) +{ + struct aspath *aspath = arg; + + aspath_free(aspath); +} + +void bgp_compute_aggregate_aspath(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + bgp_compute_aggregate_aspath_hash(aggregate, aspath); + + bgp_compute_aggregate_aspath_val(aggregate); + +} + +void bgp_compute_aggregate_aspath_hash(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + + if ((aggregate == NULL) || (aspath == NULL)) + return; + + /* Create hash if not already created. + */ + if (aggregate->aspath_hash == NULL) + aggregate->aspath_hash = hash_create( + aspath_key_make, aspath_cmp, + "BGP Aggregator as-path hash"); + + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath == NULL) { + /* Insert as-path into hash. + */ + aggr_aspath = hash_get(aggregate->aspath_hash, aspath, + bgp_aggr_aspath_hash_alloc); + } + + /* Increment reference counter. + */ + aggr_aspath->refcnt++; +} + +void bgp_compute_aggregate_aspath_val(struct bgp_aggregate *aggregate) +{ + if (aggregate == NULL) + return; + /* Re-compute aggregate's as-path. + */ + if (aggregate->aspath) { + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + } + if (aggregate->aspath_hash + && aggregate->aspath_hash->count) { + hash_iterate(aggregate->aspath_hash, + bgp_aggr_aspath_prepare, + &aggregate->aspath); + } +} + +void bgp_remove_aspath_from_aggregate(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + struct aspath *ret_aspath = NULL; + + if ((!aggregate) + || (!aggregate->aspath_hash) + || (!aspath)) + return; + + /* Look-up the aspath in the hash. + */ + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath) { + aggr_aspath->refcnt--; + + if (aggr_aspath->refcnt == 0) { + ret_aspath = hash_release(aggregate->aspath_hash, + aggr_aspath); + aspath_free(ret_aspath); + ret_aspath = NULL; + + /* Remove aggregate's old as-path. + */ + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + + bgp_compute_aggregate_aspath_val(aggregate); + } + } +} + +void bgp_remove_aspath_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct aspath *aspath) +{ + struct aspath *aggr_aspath = NULL; + struct aspath *ret_aspath = NULL; + + if ((!aggregate) + || (!aggregate->aspath_hash) + || (!aspath)) + return; + + /* Look-up the aspath in the hash. + */ + aggr_aspath = bgp_aggr_aspath_lookup(aggregate, aspath); + if (aggr_aspath) { + aggr_aspath->refcnt--; + + if (aggr_aspath->refcnt == 0) { + ret_aspath = hash_release(aggregate->aspath_hash, + aggr_aspath); + aspath_free(ret_aspath); + ret_aspath = NULL; + } + } +} + diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h new file mode 100644 index 0000000..f7e57fd --- /dev/null +++ b/bgpd/bgp_aspath.h @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS path related definitions. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ASPATH_H +#define _QUAGGA_BGP_ASPATH_H + +#include "lib/json.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_filter.h" +#include + +/* AS path segment type. */ +#define AS_SET 1 +#define AS_SEQUENCE 2 +#define AS_CONFED_SEQUENCE 3 +#define AS_CONFED_SET 4 + +/* Private AS range defined in RFC2270. */ +#define BGP_PRIVATE_AS_MIN 64512U +#define BGP_PRIVATE_AS_MAX UINT16_MAX + +/* Private 4 byte AS range defined in RFC6996. */ +#define BGP_PRIVATE_AS4_MIN 4200000000U +#define BGP_PRIVATE_AS4_MAX 4294967294U + +/* we leave BGP_AS_MAX as the 16bit AS MAX number. */ +#define BGP_AS_ZERO 0 +#define BGP_AS_MAX UINT16_MAX +#define BGP_AS4_MAX 4294967295U +/* Transition 16Bit AS as defined by IANA */ +#define BGP_AS_TRANS 23456U + +#define BGP_AS_IS_PRIVATE(ASN) \ + (((ASN) >= BGP_PRIVATE_AS_MIN && (ASN) <= BGP_PRIVATE_AS_MAX) \ + || ((ASN) >= BGP_PRIVATE_AS4_MIN && (ASN) <= BGP_PRIVATE_AS4_MAX)) + +/* AS_PATH segment data in abstracted form, no limit is placed on length */ +struct assegment { + struct assegment *next; + as_t *as; + unsigned short length; + uint8_t type; +}; + +/* AS path may be include some AsSegments. */ +struct aspath { + /* Reference count to this aspath. */ + unsigned long refcnt; + + /* segment data */ + struct assegment *segments; + + /* AS path as a json object */ + json_object *json; + + /* String expression of AS path. This string is used by vty output + and AS path regular expression match. */ + char *str; + unsigned short str_len; + + /* AS notation used by string expression of AS path */ + enum asnotation_mode asnotation; +}; + +#define ASPATH_STR_DEFAULT_LEN 32 + +/* `set as-path exclude ASn' */ +struct aspath_exclude { + struct as_list_list_item exclude_list; + struct aspath *aspath; + bool exclude_all; + char *exclude_aspath_acl_name; + struct as_list *exclude_aspath_acl; +}; +DECLARE_DLIST(as_list_list, struct aspath_exclude, exclude_list); + + +/* Prototypes. */ +extern void aspath_init(void); +extern void aspath_finish(void); +extern struct aspath *aspath_parse(struct stream *s, size_t length, + int use32bit, + enum asnotation_mode asnotation); + +extern struct aspath *aspath_dup(struct aspath *aspath); +extern struct aspath *aspath_aggregate(struct aspath *as1, struct aspath *as2); +extern struct aspath *aspath_prepend(struct aspath *as1, struct aspath *as2); +extern void as_exclude_set_orphan(struct aspath_exclude *ase); +extern void as_exclude_remove_orphan(struct aspath_exclude *ase); +extern struct aspath_exclude *as_exclude_lookup_orphan(const char *acl_name); +extern struct aspath *aspath_filter_exclude(struct aspath *source, + struct aspath *exclude_list); +extern struct aspath *aspath_filter_exclude_all(struct aspath *source); +extern struct aspath *aspath_filter_exclude_acl(struct aspath *source, + struct as_list *acl_list); +extern struct aspath *aspath_add_seq_n(struct aspath *aspath, as_t asno, + unsigned num); +extern struct aspath *aspath_add_seq(struct aspath *aspath, as_t asno); +extern struct aspath *aspath_add_confed_seq(struct aspath *aspath, as_t asno); +extern bool aspath_cmp(const void *as1, const void *as2); +extern bool aspath_cmp_left(const struct aspath *aspath1, + const struct aspath *aspath2); +extern bool aspath_cmp_left_confed(const struct aspath *as1, + const struct aspath *as2); +extern struct aspath *aspath_delete_confed_seq(struct aspath *aspath); +extern struct aspath *aspath_empty(enum asnotation_mode asnotation); +extern struct aspath *aspath_empty_get(void); +extern struct aspath *aspath_str2aspath(const char *str, + enum asnotation_mode asnotation); +extern void aspath_str_update(struct aspath *as, bool make_json); +extern void aspath_free(struct aspath *aspath); +extern struct aspath *aspath_intern(struct aspath *aspath); +extern void aspath_unintern(struct aspath **aspath); +extern const char *aspath_print(struct aspath *aspath); +extern void aspath_print_vty(struct vty *vty, struct aspath *aspath); +extern void aspath_print_all_vty(struct vty *vty); +extern unsigned int aspath_key_make(const void *p); +extern unsigned int aspath_get_first_as(struct aspath *aspath); +extern unsigned int aspath_get_last_as(struct aspath *aspath); +extern int aspath_loop_check(struct aspath *aspath, as_t asno); +extern int aspath_loop_check_confed(struct aspath *aspath, as_t asno); +extern bool aspath_private_as_check(struct aspath *aspath); +extern struct aspath *aspath_replace_regex_asn(struct aspath *aspath, + struct as_list *acl_list, + as_t our_asn); +extern struct aspath *aspath_replace_specific_asn(struct aspath *aspath, + as_t target_asn, + as_t our_asn); +extern struct aspath *aspath_replace_all_asn(struct aspath *aspath, + as_t our_asn); +extern struct aspath *aspath_replace_private_asns(struct aspath *aspath, + as_t asn, as_t peer_asn); +extern struct aspath *aspath_remove_private_asns(struct aspath *aspath, + as_t peer_asn); +extern bool aspath_firstas_check(struct aspath *aspath, as_t asno); +extern bool aspath_confed_check(struct aspath *aspath); +extern bool aspath_left_confed_check(struct aspath *aspath); +extern unsigned long aspath_count(void); +extern unsigned int aspath_count_hops(const struct aspath *aspath); +extern bool aspath_check_as_sets(struct aspath *aspath); +extern bool aspath_check_as_zero(struct aspath *aspath); +extern unsigned int aspath_count_confeds(struct aspath *aspath); +extern unsigned int aspath_size(struct aspath *aspath); +extern as_t aspath_highest(struct aspath *aspath); +extern as_t aspath_leftmost(struct aspath *aspath); +extern size_t aspath_put(struct stream *s, struct aspath *aspath, int use32bit); + +extern struct aspath *aspath_reconcile_as4(struct aspath *aspath, + struct aspath *as4path); +extern bool aspath_has_as4(struct aspath *aspath); + +/* For SNMP BGP4PATHATTRASPATHSEGMENT, might be useful for debug */ +extern uint8_t *aspath_snmp_pathseg(struct aspath *aspath, size_t *varlen); + +extern void bgp_compute_aggregate_aspath(struct bgp_aggregate *aggregate, + struct aspath *aspath); + +extern void bgp_compute_aggregate_aspath_hash(struct bgp_aggregate *aggregate, + struct aspath *aspath); +extern void bgp_compute_aggregate_aspath_val(struct bgp_aggregate *aggregate); +extern void bgp_remove_aspath_from_aggregate(struct bgp_aggregate *aggregate, + struct aspath *aspath); +extern void bgp_remove_aspath_from_aggregate_hash( + struct bgp_aggregate *aggregate, + struct aspath *aspath); + +extern void bgp_aggr_aspath_remove(void *arg); + +#endif /* _QUAGGA_BGP_ASPATH_H */ diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c new file mode 100644 index 0000000..e4ee589 --- /dev/null +++ b/bgpd/bgp_attr.c @@ -0,0 +1,5383 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP attributes management routines. + * Copyright (C) 1996, 97, 98, 1999 Kunihiro Ishiguro + */ + +#include + +#include "linklist.h" +#include "prefix.h" +#include "memory.h" +#include "vector.h" +#include "stream.h" +#include "log.h" +#include "hash.h" +#include "jhash.h" +#include "queue.h" +#include "table.h" +#include "filter.h" +#include "command.h" +#include "srv6.h" +#include "frrstr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_encap_types.h" +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgp_encap_types.h" +#include "bgp_vnc_types.h" +#endif +#include "bgp_evpn.h" +#include "bgp_flowspec_private.h" +#include "bgp_mac.h" + +/* Attribute strings for logging. */ +static const struct message attr_str[] = { + {BGP_ATTR_ORIGIN, "ORIGIN"}, + {BGP_ATTR_AS_PATH, "AS_PATH"}, + {BGP_ATTR_NEXT_HOP, "NEXT_HOP"}, + {BGP_ATTR_MULTI_EXIT_DISC, "MULTI_EXIT_DISC"}, + {BGP_ATTR_LOCAL_PREF, "LOCAL_PREF"}, + {BGP_ATTR_ATOMIC_AGGREGATE, "ATOMIC_AGGREGATE"}, + {BGP_ATTR_AGGREGATOR, "AGGREGATOR"}, + {BGP_ATTR_COMMUNITIES, "COMMUNITY"}, + {BGP_ATTR_ORIGINATOR_ID, "ORIGINATOR_ID"}, + {BGP_ATTR_CLUSTER_LIST, "CLUSTER_LIST"}, + {BGP_ATTR_MP_REACH_NLRI, "MP_REACH_NLRI"}, + {BGP_ATTR_MP_UNREACH_NLRI, "MP_UNREACH_NLRI"}, + {BGP_ATTR_EXT_COMMUNITIES, "EXT_COMMUNITIES"}, + {BGP_ATTR_AS4_PATH, "AS4_PATH"}, + {BGP_ATTR_AS4_AGGREGATOR, "AS4_AGGREGATOR"}, + {BGP_ATTR_PMSI_TUNNEL, "PMSI_TUNNEL_ATTRIBUTE"}, + {BGP_ATTR_ENCAP, "ENCAP"}, + {BGP_ATTR_OTC, "OTC"}, +#ifdef ENABLE_BGP_VNC_ATTR + {BGP_ATTR_VNC, "VNC"}, +#endif + {BGP_ATTR_LARGE_COMMUNITIES, "LARGE_COMMUNITY"}, + {BGP_ATTR_PREFIX_SID, "PREFIX_SID"}, + {BGP_ATTR_IPV6_EXT_COMMUNITIES, "IPV6_EXT_COMMUNITIES"}, + {BGP_ATTR_AIGP, "AIGP"}, + {0}}; + +static const struct message attr_flag_str[] = { + {BGP_ATTR_FLAG_OPTIONAL, "Optional"}, + {BGP_ATTR_FLAG_TRANS, "Transitive"}, + {BGP_ATTR_FLAG_PARTIAL, "Partial"}, + /* bgp_attr_flags_diagnose() relies on this bit being last in + this list */ + {BGP_ATTR_FLAG_EXTLEN, "Extended Length"}, + {0}}; + +static struct hash *cluster_hash; + +static void *cluster_hash_alloc(void *p) +{ + const struct cluster_list *val = (const struct cluster_list *)p; + struct cluster_list *cluster; + + cluster = XMALLOC(MTYPE_CLUSTER, sizeof(struct cluster_list)); + cluster->length = val->length; + + if (cluster->length) { + cluster->list = XMALLOC(MTYPE_CLUSTER_VAL, val->length); + memcpy(cluster->list, val->list, val->length); + } else + cluster->list = NULL; + + cluster->refcnt = 0; + + return cluster; +} + +/* Cluster list related functions. */ +static struct cluster_list *cluster_parse(struct in_addr *pnt, int length) +{ + struct cluster_list tmp = {}; + struct cluster_list *cluster; + + tmp.length = length; + tmp.list = length == 0 ? NULL : pnt; + + cluster = hash_get(cluster_hash, &tmp, cluster_hash_alloc); + cluster->refcnt++; + return cluster; +} + +bool cluster_loop_check(struct cluster_list *cluster, struct in_addr originator) +{ + int i; + + for (i = 0; i < cluster->length / 4; i++) + if (cluster->list[i].s_addr == originator.s_addr) + return true; + return false; +} + +static unsigned int cluster_hash_key_make(const void *p) +{ + const struct cluster_list *cluster = p; + + return jhash(cluster->list, cluster->length, 0); +} + +static bool cluster_hash_cmp(const void *p1, const void *p2) +{ + const struct cluster_list *cluster1 = p1; + const struct cluster_list *cluster2 = p2; + + if (cluster1->list == cluster2->list) + return true; + + if (!cluster1->list || !cluster2->list) + return false; + + if (cluster1->length != cluster2->length) + return false; + + return (memcmp(cluster1->list, cluster2->list, cluster1->length) == 0); +} + +static void cluster_free(struct cluster_list *cluster) +{ + XFREE(MTYPE_CLUSTER_VAL, cluster->list); + XFREE(MTYPE_CLUSTER, cluster); +} + +static struct cluster_list *cluster_intern(struct cluster_list *cluster) +{ + struct cluster_list *find; + + find = hash_get(cluster_hash, cluster, cluster_hash_alloc); + find->refcnt++; + + return find; +} + +static void cluster_unintern(struct cluster_list **cluster) +{ + if (!*cluster) + return; + + if ((*cluster)->refcnt) + (*cluster)->refcnt--; + + if ((*cluster)->refcnt == 0) { + void *p = hash_release(cluster_hash, *cluster); + assert(p == *cluster); + cluster_free(*cluster); + *cluster = NULL; + } +} + +static void cluster_init(void) +{ + cluster_hash = hash_create(cluster_hash_key_make, cluster_hash_cmp, + "BGP Cluster"); +} + +static void cluster_finish(void) +{ + hash_clean_and_free(&cluster_hash, (void (*)(void *))cluster_free); +} + +static struct hash *encap_hash = NULL; +#ifdef ENABLE_BGP_VNC +static struct hash *vnc_hash = NULL; +#endif +static struct hash *srv6_l3vpn_hash; +static struct hash *srv6_vpn_hash; + +struct bgp_attr_encap_subtlv *encap_tlv_dup(struct bgp_attr_encap_subtlv *orig) +{ + struct bgp_attr_encap_subtlv *new; + struct bgp_attr_encap_subtlv *tail; + struct bgp_attr_encap_subtlv *p; + + for (p = orig, tail = new = NULL; p; p = p->next) { + int size = sizeof(struct bgp_attr_encap_subtlv) + p->length; + if (tail) { + tail->next = XCALLOC(MTYPE_ENCAP_TLV, size); + tail = tail->next; + } else { + tail = new = XCALLOC(MTYPE_ENCAP_TLV, size); + } + assert(tail); + memcpy(tail, p, size); + tail->next = NULL; + } + + return new; +} + +static void encap_free(struct bgp_attr_encap_subtlv *p) +{ + struct bgp_attr_encap_subtlv *next; + while (p) { + next = p->next; + p->next = NULL; + XFREE(MTYPE_ENCAP_TLV, p); + p = next; + } +} + +void bgp_attr_flush_encap(struct attr *attr) +{ + if (!attr) + return; + + if (attr->encap_subtlvs) { + encap_free(attr->encap_subtlvs); + attr->encap_subtlvs = NULL; + } +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + if (vnc_subtlvs) { + encap_free(vnc_subtlvs); + bgp_attr_set_vnc_subtlvs(attr, NULL); + } +#endif +} + +/* + * Compare encap sub-tlv chains + * + * 1 = equivalent + * 0 = not equivalent + * + * This algorithm could be made faster if needed + */ +static bool encap_same(const struct bgp_attr_encap_subtlv *h1, + const struct bgp_attr_encap_subtlv *h2) +{ + const struct bgp_attr_encap_subtlv *p; + const struct bgp_attr_encap_subtlv *q; + + if (h1 == h2) + return true; + if (h1 == NULL || h2 == NULL) + return false; + + for (p = h1; p; p = p->next) { + for (q = h2; q; q = q->next) { + if ((p->type == q->type) && (p->length == q->length) + && !memcmp(p->value, q->value, p->length)) { + + break; + } + } + if (!q) + return false; + } + + for (p = h2; p; p = p->next) { + for (q = h1; q; q = q->next) { + if ((p->type == q->type) && (p->length == q->length) + && !memcmp(p->value, q->value, p->length)) { + + break; + } + } + if (!q) + return false; + } + + return true; +} + +static void *encap_hash_alloc(void *p) +{ + /* Encap structure is already allocated. */ + return p; +} + +typedef enum { + ENCAP_SUBTLV_TYPE, +#ifdef ENABLE_BGP_VNC + VNC_SUBTLV_TYPE +#endif +} encap_subtlv_type; + +static struct bgp_attr_encap_subtlv * +encap_intern(struct bgp_attr_encap_subtlv *encap, encap_subtlv_type type) +{ + struct bgp_attr_encap_subtlv *find; + struct hash *hash = encap_hash; +#ifdef ENABLE_BGP_VNC + if (type == VNC_SUBTLV_TYPE) + hash = vnc_hash; +#endif + + find = hash_get(hash, encap, encap_hash_alloc); + if (find != encap) + encap_free(encap); + find->refcnt++; + + return find; +} + +static void encap_unintern(struct bgp_attr_encap_subtlv **encapp, + encap_subtlv_type type) +{ + struct bgp_attr_encap_subtlv *encap = *encapp; + + if (!*encapp) + return; + + if (encap->refcnt) + encap->refcnt--; + + if (encap->refcnt == 0) { + struct hash *hash = encap_hash; +#ifdef ENABLE_BGP_VNC + if (type == VNC_SUBTLV_TYPE) + hash = vnc_hash; +#endif + hash_release(hash, encap); + encap_free(encap); + *encapp = NULL; + } +} + +static unsigned int encap_hash_key_make(const void *p) +{ + const struct bgp_attr_encap_subtlv *encap = p; + + return jhash(encap->value, encap->length, 0); +} + +static bool encap_hash_cmp(const void *p1, const void *p2) +{ + return encap_same((const struct bgp_attr_encap_subtlv *)p1, + (const struct bgp_attr_encap_subtlv *)p2); +} + +static void encap_init(void) +{ + encap_hash = hash_create(encap_hash_key_make, encap_hash_cmp, + "BGP Encap Hash"); +#ifdef ENABLE_BGP_VNC + vnc_hash = hash_create(encap_hash_key_make, encap_hash_cmp, + "BGP VNC Hash"); +#endif +} + +static void encap_finish(void) +{ + hash_clean_and_free(&encap_hash, (void (*)(void *))encap_free); +#ifdef ENABLE_BGP_VNC + hash_clean_and_free(&vnc_hash, (void (*)(void *))encap_free); +#endif +} + +static bool overlay_index_same(const struct attr *a1, const struct attr *a2) +{ + if (!a1 && a2) + return false; + if (!a2 && a1) + return false; + if (!a1 && !a2) + return true; + + return bgp_route_evpn_same(bgp_attr_get_evpn_overlay(a1), + bgp_attr_get_evpn_overlay(a2)); +} + +/* Unknown transit attribute. */ +static struct hash *transit_hash; + +static void transit_free(struct transit *transit) +{ + XFREE(MTYPE_TRANSIT_VAL, transit->val); + XFREE(MTYPE_TRANSIT, transit); +} + +static void *transit_hash_alloc(void *p) +{ + /* Transit structure is already allocated. */ + return p; +} + +static struct transit *transit_intern(struct transit *transit) +{ + struct transit *find; + + find = hash_get(transit_hash, transit, transit_hash_alloc); + if (find != transit) + transit_free(transit); + find->refcnt++; + + return find; +} + +static void transit_unintern(struct transit **transit) +{ + if (!*transit) + return; + + if ((*transit)->refcnt) + (*transit)->refcnt--; + + if ((*transit)->refcnt == 0) { + hash_release(transit_hash, *transit); + transit_free(*transit); + *transit = NULL; + } +} + +static bool bgp_attr_aigp_get_tlv_metric(uint8_t *pnt, int length, + uint64_t *aigp) +{ + uint8_t *data = pnt; + uint8_t tlv_type; + uint16_t tlv_length; + + while (length) { + tlv_type = *data; + ptr_get_be16(data + 1, &tlv_length); + (void)data; + + /* The value field of the AIGP TLV is always 8 octets + * long and its value is interpreted as an unsigned 64-bit + * integer. + */ + if (tlv_type == BGP_AIGP_TLV_METRIC) { + (void)ptr_get_be64(data + 3, aigp); + + /* If an AIGP attribute is received and its first AIGP + * TLV contains the maximum value 0xffffffffffffffff, + * the attribute SHOULD be considered to be malformed + * and SHOULD be discarded as specified in this section. + */ + if (*aigp == BGP_AIGP_TLV_METRIC_MAX) { + zlog_err("Bad AIGP TLV (%s) length: %llu", + BGP_AIGP_TLV_METRIC_DESC, + BGP_AIGP_TLV_METRIC_MAX); + return false; + } + + return true; + } + + data += tlv_length; + length -= tlv_length; + } + + return false; +} + +static uint64_t bgp_aigp_metric_total(struct bgp_path_info *bpi) +{ + uint64_t aigp = bgp_attr_get_aigp_metric(bpi->attr); + + if (bpi->nexthop) + return aigp + bpi->nexthop->metric; + else + return aigp; +} + +static void stream_put_bgp_aigp_tlv_metric(struct stream *s, + struct bgp_path_info *bpi) +{ + stream_putc(s, BGP_AIGP_TLV_METRIC); + stream_putw(s, BGP_AIGP_TLV_METRIC_LEN); + stream_putq(s, bgp_aigp_metric_total(bpi)); +} + +static bool bgp_attr_aigp_valid(uint8_t *pnt, int length) +{ + uint8_t *data = pnt; + uint8_t tlv_type; + uint16_t tlv_length; + uint8_t *end = data + length; + + if (length < 3) { + zlog_err("Bad AIGP attribute length (MUST be minimum 3): %u", + length); + return false; + } + + while (length) { + size_t data_len = end - data; + + tlv_type = *data; + + if (data_len - 1 < 2) + return false; + + ptr_get_be16(data + 1, &tlv_length); + (void)data; + + if (length < tlv_length) { + zlog_err( + "Bad AIGP attribute length: %u, but TLV length: %u", + length, tlv_length); + return false; + } + + if (tlv_length < 3) { + zlog_err("Bad AIGP TLV length (MUST be minimum 3): %u", + tlv_length); + return false; + } + + /* AIGP TLV, Length: 11 */ + if (tlv_type == BGP_AIGP_TLV_METRIC && + tlv_length != BGP_AIGP_TLV_METRIC_LEN) { + zlog_err("Bad AIGP TLV (%s) length: %u", + BGP_AIGP_TLV_METRIC_DESC, tlv_length); + return false; + } + + data += tlv_length; + length -= tlv_length; + } + + return true; +} + +static void *srv6_l3vpn_hash_alloc(void *p) +{ + return p; +} + +static void srv6_l3vpn_free(struct bgp_attr_srv6_l3vpn *l3vpn) +{ + XFREE(MTYPE_BGP_SRV6_L3VPN, l3vpn); +} + +static struct bgp_attr_srv6_l3vpn * +srv6_l3vpn_intern(struct bgp_attr_srv6_l3vpn *l3vpn) +{ + struct bgp_attr_srv6_l3vpn *find; + + find = hash_get(srv6_l3vpn_hash, l3vpn, srv6_l3vpn_hash_alloc); + if (find != l3vpn) + srv6_l3vpn_free(l3vpn); + find->refcnt++; + return find; +} + +static void srv6_l3vpn_unintern(struct bgp_attr_srv6_l3vpn **l3vpnp) +{ + struct bgp_attr_srv6_l3vpn *l3vpn = *l3vpnp; + + if (!*l3vpnp) + return; + + if (l3vpn->refcnt) + l3vpn->refcnt--; + + if (l3vpn->refcnt == 0) { + hash_release(srv6_l3vpn_hash, l3vpn); + srv6_l3vpn_free(l3vpn); + *l3vpnp = NULL; + } +} + +static void *srv6_vpn_hash_alloc(void *p) +{ + return p; +} + +static void srv6_vpn_free(struct bgp_attr_srv6_vpn *vpn) +{ + XFREE(MTYPE_BGP_SRV6_VPN, vpn); +} + +static struct bgp_attr_srv6_vpn *srv6_vpn_intern(struct bgp_attr_srv6_vpn *vpn) +{ + struct bgp_attr_srv6_vpn *find; + + find = hash_get(srv6_vpn_hash, vpn, srv6_vpn_hash_alloc); + if (find != vpn) + srv6_vpn_free(vpn); + find->refcnt++; + return find; +} + +static void srv6_vpn_unintern(struct bgp_attr_srv6_vpn **vpnp) +{ + struct bgp_attr_srv6_vpn *vpn = *vpnp; + + if (!*vpnp) + return; + + if (vpn->refcnt) + vpn->refcnt--; + + if (vpn->refcnt == 0) { + hash_release(srv6_vpn_hash, vpn); + srv6_vpn_free(vpn); + *vpnp = NULL; + } +} + +static uint32_t srv6_l3vpn_hash_key_make(const void *p) +{ + const struct bgp_attr_srv6_l3vpn *l3vpn = p; + uint32_t key = 0; + + key = jhash(&l3vpn->sid, 16, key); + key = jhash_1word(l3vpn->sid_flags, key); + key = jhash_1word(l3vpn->endpoint_behavior, key); + key = jhash_1word(l3vpn->loc_block_len, key); + key = jhash_1word(l3vpn->loc_node_len, key); + key = jhash_1word(l3vpn->func_len, key); + key = jhash_1word(l3vpn->arg_len, key); + key = jhash_1word(l3vpn->transposition_len, key); + key = jhash_1word(l3vpn->transposition_offset, key); + return key; +} + +static bool srv6_l3vpn_hash_cmp(const void *p1, const void *p2) +{ + const struct bgp_attr_srv6_l3vpn *l3vpn1 = p1; + const struct bgp_attr_srv6_l3vpn *l3vpn2 = p2; + + return sid_same(&l3vpn1->sid, &l3vpn2->sid) + && l3vpn1->sid_flags == l3vpn2->sid_flags + && l3vpn1->endpoint_behavior == l3vpn2->endpoint_behavior + && l3vpn1->loc_block_len == l3vpn2->loc_block_len + && l3vpn1->loc_node_len == l3vpn2->loc_node_len + && l3vpn1->func_len == l3vpn2->func_len + && l3vpn1->arg_len == l3vpn2->arg_len + && l3vpn1->transposition_len == l3vpn2->transposition_len + && l3vpn1->transposition_offset == l3vpn2->transposition_offset; +} + +static bool srv6_l3vpn_same(const struct bgp_attr_srv6_l3vpn *h1, + const struct bgp_attr_srv6_l3vpn *h2) +{ + if (h1 == h2) + return true; + else if (h1 == NULL || h2 == NULL) + return false; + else + return srv6_l3vpn_hash_cmp((const void *)h1, (const void *)h2); +} + +static unsigned int srv6_vpn_hash_key_make(const void *p) +{ + const struct bgp_attr_srv6_vpn *vpn = p; + uint32_t key = 0; + + key = jhash(&vpn->sid, 16, key); + key = jhash_1word(vpn->sid_flags, key); + return key; +} + +static bool srv6_vpn_hash_cmp(const void *p1, const void *p2) +{ + const struct bgp_attr_srv6_vpn *vpn1 = p1; + const struct bgp_attr_srv6_vpn *vpn2 = p2; + + return sid_same(&vpn1->sid, &vpn2->sid) + && vpn1->sid_flags == vpn2->sid_flags; +} + +static bool srv6_vpn_same(const struct bgp_attr_srv6_vpn *h1, + const struct bgp_attr_srv6_vpn *h2) +{ + if (h1 == h2) + return true; + else if (h1 == NULL || h2 == NULL) + return false; + else + return srv6_vpn_hash_cmp((const void *)h1, (const void *)h2); +} + +static void srv6_init(void) +{ + srv6_l3vpn_hash = + hash_create(srv6_l3vpn_hash_key_make, srv6_l3vpn_hash_cmp, + "BGP Prefix-SID SRv6-L3VPN-Service-TLV"); + srv6_vpn_hash = hash_create(srv6_vpn_hash_key_make, srv6_vpn_hash_cmp, + "BGP Prefix-SID SRv6-VPN-Service-TLV"); +} + +static void srv6_finish(void) +{ + hash_clean_and_free(&srv6_l3vpn_hash, + (void (*)(void *))srv6_l3vpn_free); + hash_clean_and_free(&srv6_vpn_hash, (void (*)(void *))srv6_vpn_free); +} + +static unsigned int transit_hash_key_make(const void *p) +{ + const struct transit *transit = p; + + return jhash(transit->val, transit->length, 0); +} + +static bool transit_hash_cmp(const void *p1, const void *p2) +{ + const struct transit *transit1 = p1; + const struct transit *transit2 = p2; + + return (transit1->length == transit2->length + && memcmp(transit1->val, transit2->val, transit1->length) == 0); +} + +static void transit_init(void) +{ + transit_hash = hash_create(transit_hash_key_make, transit_hash_cmp, + "BGP Transit Hash"); +} + +static void transit_finish(void) +{ + hash_clean_and_free(&transit_hash, (void (*)(void *))transit_free); +} + +/* Attribute hash routines. */ +static struct hash *attrhash; + +unsigned long int attr_count(void) +{ + return attrhash->count; +} + +unsigned long int attr_unknown_count(void) +{ + return transit_hash->count; +} + +unsigned int attrhash_key_make(const void *p) +{ + const struct attr *attr = (struct attr *)p; + uint32_t key = 0; +#define MIX(val) key = jhash_1word(val, key) +#define MIX3(a, b, c) key = jhash_3words((a), (b), (c), key) + + MIX3(attr->origin, attr->nexthop.s_addr, attr->med); + MIX3(attr->local_pref, attr->aggregator_as, + attr->aggregator_addr.s_addr); + MIX3(attr->weight, attr->mp_nexthop_global_in.s_addr, + attr->originator_id.s_addr); + MIX3(attr->tag, attr->label, attr->label_index); + + if (attr->aspath) + MIX(aspath_key_make(attr->aspath)); + if (bgp_attr_get_community(attr)) + MIX(community_hash_make(bgp_attr_get_community(attr))); + if (bgp_attr_get_lcommunity(attr)) + MIX(lcommunity_hash_make(bgp_attr_get_lcommunity(attr))); + if (bgp_attr_get_ecommunity(attr)) + MIX(ecommunity_hash_make(bgp_attr_get_ecommunity(attr))); + if (bgp_attr_get_ipv6_ecommunity(attr)) + MIX(ecommunity_hash_make(bgp_attr_get_ipv6_ecommunity(attr))); + if (bgp_attr_get_cluster(attr)) + MIX(cluster_hash_key_make(bgp_attr_get_cluster(attr))); + if (bgp_attr_get_transit(attr)) + MIX(transit_hash_key_make(bgp_attr_get_transit(attr))); + if (attr->encap_subtlvs) + MIX(encap_hash_key_make(attr->encap_subtlvs)); + if (attr->srv6_l3vpn) + MIX(srv6_l3vpn_hash_key_make(attr->srv6_l3vpn)); + if (attr->srv6_vpn) + MIX(srv6_vpn_hash_key_make(attr->srv6_vpn)); +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + if (vnc_subtlvs) + MIX(encap_hash_key_make(vnc_subtlvs)); +#endif + MIX(attr->mp_nexthop_len); + key = jhash(attr->mp_nexthop_global.s6_addr, IPV6_MAX_BYTELEN, key); + key = jhash(attr->mp_nexthop_local.s6_addr, IPV6_MAX_BYTELEN, key); + MIX3(attr->nh_ifindex, attr->nh_lla_ifindex, attr->distance); + MIX(attr->rmap_table_id); + MIX(attr->nh_type); + MIX(attr->bh_type); + MIX(attr->otc); + MIX(bgp_attr_get_aigp_metric(attr)); + + return key; +} + +bool attrhash_cmp(const void *p1, const void *p2) +{ + const struct attr *attr1 = p1; + const struct attr *attr2 = p2; + + if (attr1->flag == attr2->flag && attr1->origin == attr2->origin + && attr1->nexthop.s_addr == attr2->nexthop.s_addr + && attr1->aspath == attr2->aspath + && bgp_attr_get_community(attr1) + == bgp_attr_get_community(attr2) + && attr1->med == attr2->med + && attr1->local_pref == attr2->local_pref + && attr1->rmap_change_flags == attr2->rmap_change_flags) { + if (attr1->aggregator_as == attr2->aggregator_as && + attr1->aggregator_addr.s_addr == + attr2->aggregator_addr.s_addr && + attr1->weight == attr2->weight && + attr1->tag == attr2->tag && + attr1->label_index == attr2->label_index && + attr1->mp_nexthop_len == attr2->mp_nexthop_len && + bgp_attr_get_ecommunity(attr1) == + bgp_attr_get_ecommunity(attr2) && + bgp_attr_get_ipv6_ecommunity(attr1) == + bgp_attr_get_ipv6_ecommunity(attr2) && + bgp_attr_get_lcommunity(attr1) == + bgp_attr_get_lcommunity(attr2) && + bgp_attr_get_cluster(attr1) == + bgp_attr_get_cluster(attr2) && + bgp_attr_get_transit(attr1) == + bgp_attr_get_transit(attr2) && + bgp_attr_get_aigp_metric(attr1) == + bgp_attr_get_aigp_metric(attr2) && + attr1->rmap_table_id == attr2->rmap_table_id && + (attr1->encap_tunneltype == attr2->encap_tunneltype) && + encap_same(attr1->encap_subtlvs, attr2->encap_subtlvs) +#ifdef ENABLE_BGP_VNC + && encap_same(bgp_attr_get_vnc_subtlvs(attr1), + bgp_attr_get_vnc_subtlvs(attr2)) +#endif + && IPV6_ADDR_SAME(&attr1->mp_nexthop_global, + &attr2->mp_nexthop_global) && + IPV6_ADDR_SAME(&attr1->mp_nexthop_local, + &attr2->mp_nexthop_local) && + IPV4_ADDR_SAME(&attr1->mp_nexthop_global_in, + &attr2->mp_nexthop_global_in) && + IPV4_ADDR_SAME(&attr1->originator_id, + &attr2->originator_id) && + overlay_index_same(attr1, attr2) && + !memcmp(&attr1->esi, &attr2->esi, sizeof(esi_t)) && + attr1->es_flags == attr2->es_flags && + attr1->mm_sync_seqnum == attr2->mm_sync_seqnum && + attr1->df_pref == attr2->df_pref && + attr1->df_alg == attr2->df_alg && + attr1->nh_ifindex == attr2->nh_ifindex && + attr1->nh_lla_ifindex == attr2->nh_lla_ifindex && + attr1->nh_flags == attr2->nh_flags && + attr1->distance == attr2->distance && + srv6_l3vpn_same(attr1->srv6_l3vpn, attr2->srv6_l3vpn) && + srv6_vpn_same(attr1->srv6_vpn, attr2->srv6_vpn) && + attr1->srte_color == attr2->srte_color && + attr1->nh_type == attr2->nh_type && + attr1->bh_type == attr2->bh_type && + attr1->otc == attr2->otc) + return true; + } + + return false; +} + +static void attrhash_init(void) +{ + attrhash = + hash_create(attrhash_key_make, attrhash_cmp, "BGP Attributes"); +} + +/* + * special for hash_clean below + */ +static void attr_vfree(void *attr) +{ + XFREE(MTYPE_ATTR, attr); +} + +static void attrhash_finish(void) +{ + hash_clean_and_free(&attrhash, attr_vfree); +} + +static void attr_show_all_iterator(struct hash_bucket *bucket, struct vty *vty) +{ + struct attr *attr = bucket->data; + struct in6_addr *sid = NULL; + + if (attr->srv6_l3vpn) + sid = &attr->srv6_l3vpn->sid; + else if (attr->srv6_vpn) + sid = &attr->srv6_vpn->sid; + + vty_out(vty, "attr[%ld] nexthop %pI4\n", attr->refcnt, &attr->nexthop); + + vty_out(vty, + "\tflags: %" PRIu64 + " distance: %u med: %u local_pref: %u origin: %u weight: %u label: %u sid: %pI6 aigp_metric: %" PRIu64 + "\n", + attr->flag, attr->distance, attr->med, attr->local_pref, + attr->origin, attr->weight, attr->label, sid, attr->aigp_metric); + vty_out(vty, "\taspath: %s Community: %s Large Community: %s\n", + aspath_print(attr->aspath), + community_str(attr->community, false, false), + lcommunity_str(attr->lcommunity, false, false)); + vty_out(vty, "\tExtended Community: %s Extended IPv6 Community: %s\n", + ecommunity_str(attr->ecommunity), + ecommunity_str(attr->ipv6_ecommunity)); +} + +void attr_show_all(struct vty *vty) +{ + hash_iterate(attrhash, (void (*)(struct hash_bucket *, + void *))attr_show_all_iterator, + vty); +} + +static void *bgp_attr_hash_alloc(void *p) +{ + struct attr *val = (struct attr *)p; + struct attr *attr; + + attr = XMALLOC(MTYPE_ATTR, sizeof(struct attr)); + *attr = *val; + if (val->encap_subtlvs) { + val->encap_subtlvs = NULL; + } +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(val); + + if (vnc_subtlvs) + bgp_attr_set_vnc_subtlvs(val, NULL); +#endif + + attr->refcnt = 0; + return attr; +} + +/* Internet argument attribute. */ +struct attr *bgp_attr_intern(struct attr *attr) +{ + struct attr *find; + struct ecommunity *ecomm = NULL; + struct ecommunity *ipv6_ecomm = NULL; + struct lcommunity *lcomm = NULL; + struct community *comm = NULL; + + /* Intern referenced structure. */ + if (attr->aspath) { + if (!attr->aspath->refcnt) + attr->aspath = aspath_intern(attr->aspath); + else + attr->aspath->refcnt++; + } + + comm = bgp_attr_get_community(attr); + if (comm) { + if (!comm->refcnt) + bgp_attr_set_community(attr, community_intern(comm)); + else + comm->refcnt++; + } + + ecomm = bgp_attr_get_ecommunity(attr); + if (ecomm) { + if (!ecomm->refcnt) + bgp_attr_set_ecommunity(attr, ecommunity_intern(ecomm)); + else + ecomm->refcnt++; + } + + ipv6_ecomm = bgp_attr_get_ipv6_ecommunity(attr); + if (ipv6_ecomm) { + if (!ipv6_ecomm->refcnt) + bgp_attr_set_ipv6_ecommunity( + attr, ecommunity_intern(ipv6_ecomm)); + else + ipv6_ecomm->refcnt++; + } + + lcomm = bgp_attr_get_lcommunity(attr); + if (lcomm) { + if (!lcomm->refcnt) + bgp_attr_set_lcommunity(attr, lcommunity_intern(lcomm)); + else + lcomm->refcnt++; + } + + struct cluster_list *cluster = bgp_attr_get_cluster(attr); + + if (cluster) { + if (!cluster->refcnt) + bgp_attr_set_cluster(attr, cluster_intern(cluster)); + else + cluster->refcnt++; + } + + struct transit *transit = bgp_attr_get_transit(attr); + + if (transit) { + if (!transit->refcnt) + bgp_attr_set_transit(attr, transit_intern(transit)); + else + transit->refcnt++; + } + if (attr->encap_subtlvs) { + if (!attr->encap_subtlvs->refcnt) + attr->encap_subtlvs = encap_intern(attr->encap_subtlvs, + ENCAP_SUBTLV_TYPE); + else + attr->encap_subtlvs->refcnt++; + } + if (attr->srv6_l3vpn) { + if (!attr->srv6_l3vpn->refcnt) + attr->srv6_l3vpn = srv6_l3vpn_intern(attr->srv6_l3vpn); + else + attr->srv6_l3vpn->refcnt++; + } + if (attr->srv6_vpn) { + if (!attr->srv6_vpn->refcnt) + attr->srv6_vpn = srv6_vpn_intern(attr->srv6_vpn); + else + attr->srv6_vpn->refcnt++; + } +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + if (vnc_subtlvs) { + if (!vnc_subtlvs->refcnt) + bgp_attr_set_vnc_subtlvs( + attr, + encap_intern(vnc_subtlvs, VNC_SUBTLV_TYPE)); + else + vnc_subtlvs->refcnt++; + } +#endif + + /* At this point, attr only contains intern'd pointers. that means + * if we find it in attrhash, it has all the same pointers and we + * correctly updated the refcounts on these. + * If we don't find it, we need to allocate a one because in all + * cases this returns a new reference to a hashed attr, but the input + * wasn't on hash. */ + find = (struct attr *)hash_get(attrhash, attr, bgp_attr_hash_alloc); + find->refcnt++; + + return find; +} + +/* Make network statement's attribute. */ +struct attr *bgp_attr_default_set(struct attr *attr, struct bgp *bgp, + uint8_t origin) +{ + memset(attr, 0, sizeof(struct attr)); + + attr->origin = origin; + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); + attr->aspath = aspath_empty(bgp->asnotation); + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); + attr->weight = BGP_ATTR_DEFAULT_WEIGHT; + attr->tag = 0; + attr->label_index = BGP_INVALID_LABEL_INDEX; + attr->label = MPLS_INVALID_LABEL; + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + attr->mp_nexthop_len = IPV6_MAX_BYTELEN; + attr->local_pref = bgp->default_local_pref; + + return attr; +} + +/* Create the attributes for an aggregate */ +struct attr *bgp_attr_aggregate_intern( + struct bgp *bgp, uint8_t origin, struct aspath *aspath, + struct community *community, struct ecommunity *ecommunity, + struct lcommunity *lcommunity, struct bgp_aggregate *aggregate, + uint8_t atomic_aggregate, const struct prefix *p) +{ + struct attr attr; + struct attr *new; + route_map_result_t ret; + + memset(&attr, 0, sizeof(attr)); + + /* Origin attribute. */ + attr.origin = origin; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); + + /* MED */ + attr.med = 0; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + /* AS path attribute. */ + if (aspath) + attr.aspath = aspath_intern(aspath); + else + attr.aspath = aspath_empty(bgp->asnotation); + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); + + if (community) { + uint32_t gshut = COMMUNITY_GSHUT; + + /* If we are not shutting down ourselves and we are + * aggregating a route that contains the GSHUT community we + * need to remove that community when creating the aggregate */ + if (!bgp_in_graceful_shutdown(bgp) + && community_include(community, gshut)) { + community_del_val(community, &gshut); + } + + bgp_attr_set_community(&attr, community); + } + + if (ecommunity) + bgp_attr_set_ecommunity(&attr, ecommunity); + + if (lcommunity) + bgp_attr_set_lcommunity(&attr, lcommunity); + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr); + + attr.label_index = BGP_INVALID_LABEL_INDEX; + attr.label = MPLS_INVALID_LABEL; + attr.weight = BGP_ATTR_DEFAULT_WEIGHT; + attr.mp_nexthop_len = IPV6_MAX_BYTELEN; + if (!aggregate->as_set || atomic_aggregate) + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) + attr.aggregator_as = bgp->confed_id; + else + attr.aggregator_as = bgp->as; + attr.aggregator_addr = bgp->router_id; + + /* Aggregate are done for IPv4/IPv6 so checking ipv4 family, + * This should only be set for IPv4 AFI type + * based on RFC-4760: + * "An UPDATE message that carries no NLRI, + * other than the one encoded in + * the MP_REACH_NLRI attribute, + * SHOULD NOT carry the NEXT_HOP + * attribute" + */ + if (p->family == AF_INET) { + /* Next hop attribute. */ + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + attr.mp_nexthop_len = IPV4_MAX_BYTELEN; + } + + /* Apply route-map */ + if (aggregate->rmap.name) { + struct attr attr_tmp = attr; + struct bgp_path_info rmap_path; + + memset(&rmap_path, 0, sizeof(rmap_path)); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr_tmp; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_AGGREGATE); + + ret = route_map_apply(aggregate->rmap.map, p, &rmap_path); + + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* Free uninterned attribute. */ + bgp_attr_flush(&attr_tmp); + + /* Unintern original. */ + aspath_unintern(&attr.aspath); + return NULL; + } + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr_tmp); + + new = bgp_attr_intern(&attr_tmp); + } else { + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr); + + new = bgp_attr_intern(&attr); + } + + /* Always release the 'intern()'ed AS Path. */ + aspath_unintern(&attr.aspath); + + return new; +} + +/* Unintern just the sub-components of the attr, but not the attr */ +void bgp_attr_unintern_sub(struct attr *attr) +{ + struct ecommunity *ecomm = NULL; + struct ecommunity *ipv6_ecomm = NULL; + struct cluster_list *cluster; + struct lcommunity *lcomm = NULL; + struct community *comm = NULL; + struct transit *transit; + + /* aspath refcount shoud be decrement. */ + aspath_unintern(&attr->aspath); + UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH)); + + comm = bgp_attr_get_community(attr); + community_unintern(&comm); + bgp_attr_set_community(attr, NULL); + + ecomm = bgp_attr_get_ecommunity(attr); + ecommunity_unintern(&ecomm); + bgp_attr_set_ecommunity(attr, NULL); + + ipv6_ecomm = bgp_attr_get_ipv6_ecommunity(attr); + ecommunity_unintern(&ipv6_ecomm); + bgp_attr_set_ipv6_ecommunity(attr, NULL); + + lcomm = bgp_attr_get_lcommunity(attr); + lcommunity_unintern(&lcomm); + bgp_attr_set_lcommunity(attr, NULL); + + cluster = bgp_attr_get_cluster(attr); + cluster_unintern(&cluster); + bgp_attr_set_cluster(attr, NULL); + + transit = bgp_attr_get_transit(attr); + transit_unintern(&transit); + bgp_attr_set_transit(attr, NULL); + + encap_unintern(&attr->encap_subtlvs, ENCAP_SUBTLV_TYPE); + +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + encap_unintern(&vnc_subtlvs, VNC_SUBTLV_TYPE); + bgp_attr_set_vnc_subtlvs(attr, NULL); +#endif + + srv6_l3vpn_unintern(&attr->srv6_l3vpn); + srv6_vpn_unintern(&attr->srv6_vpn); +} + +/* Free bgp attribute and aspath. */ +void bgp_attr_unintern(struct attr **pattr) +{ + struct attr *attr = *pattr; + struct attr *ret; + struct attr tmp; + + /* Decrement attribute reference. */ + attr->refcnt--; + + tmp = *attr; + + /* If reference becomes zero then free attribute object. */ + if (attr->refcnt == 0) { + ret = hash_release(attrhash, attr); + assert(ret != NULL); + XFREE(MTYPE_ATTR, attr); + *pattr = NULL; + } + + bgp_attr_unintern_sub(&tmp); +} + +void bgp_attr_flush(struct attr *attr) +{ + struct ecommunity *ecomm; + struct ecommunity *ipv6_ecomm; + struct cluster_list *cluster; + struct lcommunity *lcomm; + struct community *comm; + + if (attr->aspath && !attr->aspath->refcnt) { + aspath_free(attr->aspath); + attr->aspath = NULL; + } + comm = bgp_attr_get_community(attr); + if (comm && !comm->refcnt) + community_free(&comm); + bgp_attr_set_community(attr, NULL); + + ecomm = bgp_attr_get_ecommunity(attr); + if (ecomm && !ecomm->refcnt) + ecommunity_free(&ecomm); + bgp_attr_set_ecommunity(attr, NULL); + + ipv6_ecomm = bgp_attr_get_ipv6_ecommunity(attr); + if (ipv6_ecomm && !ipv6_ecomm->refcnt) + ecommunity_free(&ipv6_ecomm); + bgp_attr_set_ipv6_ecommunity(attr, NULL); + + lcomm = bgp_attr_get_lcommunity(attr); + if (lcomm && !lcomm->refcnt) + lcommunity_free(&lcomm); + bgp_attr_set_lcommunity(attr, NULL); + + cluster = bgp_attr_get_cluster(attr); + if (cluster && !cluster->refcnt) { + cluster_free(cluster); + bgp_attr_set_cluster(attr, NULL); + } + + struct transit *transit = bgp_attr_get_transit(attr); + + if (transit && !transit->refcnt) { + transit_free(transit); + bgp_attr_set_transit(attr, NULL); + } + if (attr->encap_subtlvs && !attr->encap_subtlvs->refcnt) { + encap_free(attr->encap_subtlvs); + attr->encap_subtlvs = NULL; + } + if (attr->srv6_l3vpn && !attr->srv6_l3vpn->refcnt) { + srv6_l3vpn_free(attr->srv6_l3vpn); + attr->srv6_l3vpn = NULL; + } + if (attr->srv6_vpn && !attr->srv6_vpn->refcnt) { + srv6_vpn_free(attr->srv6_vpn); + attr->srv6_vpn = NULL; + } +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + if (vnc_subtlvs && !vnc_subtlvs->refcnt) { + encap_free(vnc_subtlvs); + bgp_attr_set_vnc_subtlvs(attr, NULL); + } +#endif +} + +/* Implement draft-scudder-idr-optional-transitive behaviour and + * avoid resetting sessions for malformed attributes which are + * are partial/optional and hence where the error likely was not + * introduced by the sending neighbour. + */ +static enum bgp_attr_parse_ret +bgp_attr_malformed(struct bgp_attr_parser_args *args, uint8_t subcode, + bgp_size_t length) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const uint8_t flags = args->flags; + /* startp and length must be special-cased, as whether or not to + * send the attribute data with the NOTIFY depends on the error, + * the caller therefore signals this with the seperate length argument + */ + uint8_t *notify_datap = (length > 0 ? args->startp : NULL); + + if (bgp_debug_update(peer, NULL, NULL, 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, sizeof(attr_str)); + + zlog_debug("%s: attributes: %s", __func__, attr_str); + } + + /* Only relax error handling for eBGP peers */ + if (peer->sort != BGP_PEER_EBGP) { + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_UPDATE_ERR, subcode, + notify_datap, length); + return BGP_ATTR_PARSE_ERROR; + } + + /* Adjust the stream getp to the end of the attribute, in case we can + * still proceed but the caller hasn't read all the attribute. + */ + stream_set_getp(BGP_INPUT(peer), + (args->startp - STREAM_DATA(BGP_INPUT(peer))) + + args->total); + + /* Partial optional attributes that are malformed should not cause + * the whole session to be reset. Instead treat it as a withdrawal + * of the routes, if possible. + */ + if (CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS) && + CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) && + CHECK_FLAG(flags, BGP_ATTR_FLAG_PARTIAL)) + return BGP_ATTR_PARSE_WITHDRAW; + + switch (args->type) { + /* where an attribute is relatively inconsequential, e.g. it does not + * affect route selection, and can be safely ignored, then any such + * attributes which are malformed should just be ignored and the route + * processed as normal. + */ + case BGP_ATTR_AS4_AGGREGATOR: + case BGP_ATTR_AGGREGATOR: + case BGP_ATTR_ATOMIC_AGGREGATE: + case BGP_ATTR_PREFIX_SID: + return BGP_ATTR_PARSE_PROCEED; + + /* Core attributes, particularly ones which may influence route + * selection, should be treat-as-withdraw. + */ + case BGP_ATTR_ORIGIN: + case BGP_ATTR_AS_PATH: + case BGP_ATTR_AS4_PATH: + case BGP_ATTR_NEXT_HOP: + case BGP_ATTR_MULTI_EXIT_DISC: + case BGP_ATTR_LOCAL_PREF: + case BGP_ATTR_COMMUNITIES: + case BGP_ATTR_EXT_COMMUNITIES: + case BGP_ATTR_IPV6_EXT_COMMUNITIES: + case BGP_ATTR_LARGE_COMMUNITIES: + case BGP_ATTR_ORIGINATOR_ID: + case BGP_ATTR_CLUSTER_LIST: + case BGP_ATTR_PMSI_TUNNEL: + case BGP_ATTR_ENCAP: + case BGP_ATTR_OTC: + return BGP_ATTR_PARSE_WITHDRAW; + case BGP_ATTR_MP_REACH_NLRI: + case BGP_ATTR_MP_UNREACH_NLRI: + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_UPDATE_ERR, subcode, + notify_datap, length); + return BGP_ATTR_PARSE_ERROR; + default: + /* Unknown attributes, that are handled by this function + * should be treated as withdraw, to prevent one more CVE + * from being introduced. + * RFC 7606 says: + * The "treat-as-withdraw" approach is generally preferred + * and the "session reset" approach is discouraged. + */ + flog_err(EC_BGP_ATTR_FLAG, + "%s(%u) attribute received, while it is not known how to handle it, treating as withdraw", + lookup_msg(attr_str, args->type, NULL), args->type); + break; + } + + return BGP_ATTR_PARSE_WITHDRAW; +} + +/* Find out what is wrong with the path attribute flag bits and log the error. + "Flag bits" here stand for Optional, Transitive and Partial, but not for + Extended Length. Checking O/T/P bits at once implies, that the attribute + being diagnosed is defined by RFC as either a "well-known" or an "optional, + non-transitive" attribute. */ +static void +bgp_attr_flags_diagnose(struct bgp_attr_parser_args *args, + uint8_t desired_flags /* how RFC says it must be */ +) +{ + uint8_t seen = 0, i; + uint8_t real_flags = args->flags; + const uint8_t attr_code = args->type; + + desired_flags &= ~BGP_ATTR_FLAG_EXTLEN; + real_flags &= ~BGP_ATTR_FLAG_EXTLEN; + for (i = 0; i <= 2; i++) /* O,T,P, but not E */ + if (CHECK_FLAG(desired_flags, attr_flag_str[i].key) + != CHECK_FLAG(real_flags, attr_flag_str[i].key)) { + flog_err(EC_BGP_ATTR_FLAG, + "%s attribute must%s be flagged as \"%s\"", + lookup_msg(attr_str, attr_code, NULL), + CHECK_FLAG(desired_flags, attr_flag_str[i].key) + ? "" + : " not", + attr_flag_str[i].str); + seen = 1; + } + if (!seen) { + zlog_debug( + "Strange, %s called for attr %s, but no problem found with flags (real flags 0x%x, desired 0x%x)", + __func__, lookup_msg(attr_str, attr_code, NULL), + real_flags, desired_flags); + } +} + +/* Required flags for attributes. EXTLEN will be masked off when testing, + * as will PARTIAL for optional+transitive attributes. + */ +const uint8_t attr_flags_values[] = { + [BGP_ATTR_ORIGIN] = BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_AS_PATH] = BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_NEXT_HOP] = BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_MULTI_EXIT_DISC] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_LOCAL_PREF] = BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_ATOMIC_AGGREGATE] = BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_AGGREGATOR] = BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_COMMUNITIES] = BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_ORIGINATOR_ID] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_CLUSTER_LIST] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_MP_REACH_NLRI] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_MP_UNREACH_NLRI] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_EXT_COMMUNITIES] = + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_AS4_PATH] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_AS4_AGGREGATOR] = + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_PMSI_TUNNEL] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_LARGE_COMMUNITIES] = + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_OTC] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_PREFIX_SID] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_IPV6_EXT_COMMUNITIES] = + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, + [BGP_ATTR_AIGP] = BGP_ATTR_FLAG_OPTIONAL, +}; +static const size_t attr_flags_values_max = array_size(attr_flags_values) - 1; + +static bool bgp_attr_flag_invalid(struct bgp_attr_parser_args *args) +{ + uint8_t mask = BGP_ATTR_FLAG_EXTLEN; + const uint8_t flags = args->flags; + const uint8_t attr_code = args->type; + struct peer *peer = args->peer; + + /* there may be attributes we don't know about */ + if (attr_code > attr_flags_values_max) + return false; + if (attr_flags_values[attr_code] == 0) + return false; + + /* If `neighbor X path-attribute ` is + * configured, then ignore checking optional, trasitive flags. + * The attribute/route will be discarded/withdrawned later instead + * of dropping the session. + */ + if (peer->discard_attrs[attr_code] || peer->withdraw_attrs[attr_code]) + return false; + + /* RFC4271, "For well-known attributes, the Transitive bit MUST be set + * to + * 1." + */ + if (!CHECK_FLAG(BGP_ATTR_FLAG_OPTIONAL, flags) + && !CHECK_FLAG(BGP_ATTR_FLAG_TRANS, flags)) { + flog_err( + EC_BGP_ATTR_FLAG, + "%s well-known attributes must have transitive flag set (%x)", + lookup_msg(attr_str, attr_code, NULL), flags); + return true; + } + + /* "For well-known attributes and for optional non-transitive + * attributes, + * the Partial bit MUST be set to 0." + */ + if (CHECK_FLAG(flags, BGP_ATTR_FLAG_PARTIAL)) { + if (!CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL)) { + flog_err(EC_BGP_ATTR_FLAG, + "%s well-known attribute must NOT have the partial flag set (%x)", + lookup_msg(attr_str, attr_code, NULL), flags); + return true; + } + if (CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) + && !CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS)) { + flog_err(EC_BGP_ATTR_FLAG, + "%s optional + transitive attribute must NOT have the partial flag set (%x)", + lookup_msg(attr_str, attr_code, NULL), flags); + return true; + } + } + + /* Optional transitive attributes may go through speakers that don't + * reocgnise them and set the Partial bit. + */ + if (CHECK_FLAG(flags, BGP_ATTR_FLAG_OPTIONAL) + && CHECK_FLAG(flags, BGP_ATTR_FLAG_TRANS)) + SET_FLAG(mask, BGP_ATTR_FLAG_PARTIAL); + + if ((flags & ~mask) == attr_flags_values[attr_code]) + return false; + + bgp_attr_flags_diagnose(args, attr_flags_values[attr_code]); + return true; +} + +/* Get origin attribute of the update message. */ +static enum bgp_attr_parse_ret +bgp_attr_origin(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* If any recognized attribute has Attribute Length that conflicts + with the expected length (based on the attribute type code), then + the Error Subcode is set to Attribute Length Error. The Data + field contains the erroneous attribute (type, length and + value). */ + if (length != 1) { + flog_err(EC_BGP_ATTR_LEN, + "Origin attribute length is not one %d", length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* Fetch origin attribute. */ + attr->origin = stream_getc(BGP_INPUT(peer)); + + /* If the ORIGIN attribute has an undefined value, then the Error + Subcode is set to Invalid Origin Attribute. The Data field + contains the unrecognized attribute (type, length and value). */ + if ((attr->origin != BGP_ORIGIN_IGP) && (attr->origin != BGP_ORIGIN_EGP) + && (attr->origin != BGP_ORIGIN_INCOMPLETE)) { + flog_err(EC_BGP_ATTR_ORIGIN, + "Origin attribute value is invalid %d", attr->origin); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_INVAL_ORIGIN, + args->total); + } + + /* Set oring attribute flag. */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGIN); + + return 0; +} + +/* Parse AS path information. This function is wrapper of + aspath_parse. */ +static int bgp_attr_aspath(struct bgp_attr_parser_args *args) +{ + struct attr *const attr = args->attr; + struct peer *const peer = args->peer; + const bgp_size_t length = args->length; + enum asnotation_mode asnotation; + + asnotation = bgp_get_asnotation( + args->peer && args->peer->bgp ? args->peer->bgp : NULL); + /* + * peer with AS4 => will get 4Byte ASnums + * otherwise, will get 16 Bit + */ + attr->aspath = + aspath_parse(peer->curr, length, + CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) && + CHECK_FLAG(peer->cap, PEER_CAP_AS4_ADV), + asnotation); + + /* In case of IBGP, length will be zero. */ + if (!attr->aspath) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, + "Malformed AS path from %s, length is %d", peer->host, + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, + 0); + } + + /* Conformant BGP speakers SHOULD NOT send BGP + * UPDATE messages containing AS_SET or AS_CONFED_SET. Upon receipt of + * such messages, conformant BGP speakers SHOULD use the "Treat-as- + * withdraw" error handling behavior as per [RFC7606]. + */ + if (peer->bgp && peer->bgp->reject_as_sets && + aspath_check_as_sets(attr->aspath)) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, + "AS_SET and AS_CONFED_SET are deprecated from %pBP", + peer); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, + 0); + } + + /* Set aspath attribute flag. */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS_PATH); + + return BGP_ATTR_PARSE_PROCEED; +} + +static enum bgp_attr_parse_ret bgp_attr_aspath_check(struct peer *const peer, + struct attr *const attr) +{ + /* These checks were part of bgp_attr_aspath, but with + * as4 we should to check aspath things when + * aspath synthesizing with as4_path has already taken place. + * Otherwise we check ASPATH and use the synthesized thing, and that is + * not right. + * So do the checks later, i.e. here + */ + struct aspath *aspath; + + /* Refresh peer's type. If we set e.g.: AS_EXTERNAL/AS_INTERNAL, + * then peer->sort remains BGP_PEER_EBGP/IBGP, hence we need to + * have an actual type before checking. + * This is especially a case for BGP confederation peers, to avoid + * receiving and treating AS_PATH as malformed. + */ + (void)peer_sort(peer); + + /* Confederation sanity check. */ + if ((peer->sort == BGP_PEER_CONFED + && !aspath_left_confed_check(attr->aspath)) + || (peer->sort == BGP_PEER_EBGP + && aspath_confed_check(attr->aspath))) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, "Malformed AS path from %s", + peer->host); + return BGP_ATTR_PARSE_WITHDRAW; + } + + /* First AS check for EBGP. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_ENFORCE_FIRST_AS)) { + if (peer->sort == BGP_PEER_EBGP + && !aspath_firstas_check(attr->aspath, peer->as)) { + flog_err(EC_BGP_ATTR_FIRST_AS, + "%s incorrect first AS (must be %u)", + peer->host, peer->as); + return BGP_ATTR_PARSE_WITHDRAW; + } + } + + /* Codification of AS 0 Processing */ + if (peer->sort == BGP_PEER_EBGP && aspath_check_as_zero(attr->aspath)) { + flog_err( + EC_BGP_ATTR_MAL_AS_PATH, + "Malformed AS path, AS number is 0 in the path from %s", + peer->host); + return BGP_ATTR_PARSE_WITHDRAW; + } + + /* local-as prepend */ + if (peer->change_local_as + && !CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND)) { + aspath = aspath_dup(attr->aspath); + aspath = aspath_add_seq(aspath, peer->change_local_as); + aspath_unintern(&attr->aspath); + attr->aspath = aspath_intern(aspath); + } + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Parse AS4 path information. This function is another wrapper of + aspath_parse. */ +static int bgp_attr_as4_path(struct bgp_attr_parser_args *args, + struct aspath **as4_path) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + enum asnotation_mode asnotation; + + asnotation = bgp_get_asnotation(peer->bgp); + + *as4_path = aspath_parse(peer->curr, length, 1, asnotation); + + /* In case of IBGP, length will be zero. */ + if (!*as4_path) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, + "Malformed AS4 path from %s, length is %d", peer->host, + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, + 0); + } + + /* Conformant BGP speakers SHOULD NOT send BGP + * UPDATE messages containing AS_SET or AS_CONFED_SET. Upon receipt of + * such messages, conformant BGP speakers SHOULD use the "Treat-as- + * withdraw" error handling behavior as per [RFC7606]. + */ + if (peer->bgp->reject_as_sets && aspath_check_as_sets(attr->aspath)) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, + "AS_SET and AS_CONFED_SET are deprecated from %pBP", + peer); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, + 0); + } + + /* Set aspath attribute flag. */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* + * Check that the nexthop attribute is valid. + */ +enum bgp_attr_parse_ret bgp_attr_nexthop_valid(struct peer *peer, + struct attr *attr) +{ + struct bgp *bgp = peer->bgp; + + if (ipv4_martian(&attr->nexthop) && !bgp->allow_martian) { + uint8_t data[7]; /* type(2) + length(1) + nhop(4) */ + + flog_err(EC_BGP_ATTR_MARTIAN_NH, "Martian nexthop %pI4", + &attr->nexthop); + data[0] = BGP_ATTR_FLAG_TRANS; + data[1] = BGP_ATTR_NEXT_HOP; + data[2] = BGP_ATTR_NHLEN_IPV4; + memcpy(&data[3], &attr->nexthop.s_addr, BGP_ATTR_NHLEN_IPV4); + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP, + data, 7); + return BGP_ATTR_PARSE_ERROR; + } + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Nexthop attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_nexthop(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* Check nexthop attribute length. */ + if (length != 4) { + flog_err(EC_BGP_ATTR_LEN, + "Nexthop attribute length isn't four [%d]", length); + + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + attr->nexthop.s_addr = stream_get_ipv4(peer->curr); + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* MED atrribute. */ +static enum bgp_attr_parse_ret bgp_attr_med(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* Length check. */ + if (length != 4) { + flog_err(EC_BGP_ATTR_LEN, + "MED attribute length isn't four [%d]", length); + + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + attr->med = stream_getl(peer->curr); + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Local preference attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_local_pref(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* if received from an internal neighbor, it SHALL be considered + * malformed if its length is not equal to 4. If malformed, the + * UPDATE message SHALL be handled using the approach of "treat-as- + * withdraw". + */ + if ((peer->sort == BGP_PEER_IBGP || + peer->sub_sort == BGP_PEER_EBGP_OAD) && + length != 4) { + flog_err(EC_BGP_ATTR_LEN, + "LOCAL_PREF attribute length isn't 4 [%u]", length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* If it is contained in an UPDATE message that is received from an + external peer, then this attribute MUST be ignored by the + receiving speaker. */ + if (peer->sort == BGP_PEER_EBGP && peer->sub_sort != BGP_PEER_EBGP_OAD) { + STREAM_FORWARD_GETP(peer->curr, length); + return BGP_ATTR_PARSE_PROCEED; + } + + STREAM_GETL(peer->curr, attr->local_pref); + + /* Set the local-pref flag. */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + + return BGP_ATTR_PARSE_PROCEED; + +stream_failure: + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); +} + +/* Atomic aggregate. */ +static int bgp_attr_atomic(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* Length check. */ + if (length != 0) { + flog_err(EC_BGP_ATTR_LEN, + "ATOMIC_AGGREGATE attribute length isn't 0 [%u]", + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto atomic_ignore; + + /* Set atomic aggregate flag. */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); + + return BGP_ATTR_PARSE_PROCEED; + +atomic_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Aggregator attribute */ +static int bgp_attr_aggregator(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + as_t aggregator_as; + + int wantedlen = 6; + + /* peer with AS4 will send 4 Byte AS, peer without will send 2 Byte */ + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) + && CHECK_FLAG(peer->cap, PEER_CAP_AS4_ADV)) + wantedlen = 8; + + if (length != wantedlen) { + flog_err(EC_BGP_ATTR_LEN, + "AGGREGATOR attribute length isn't %u [%u]", wantedlen, + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto aggregator_ignore; + + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) + aggregator_as = stream_getl(peer->curr); + else + aggregator_as = stream_getw(peer->curr); + + attr->aggregator_as = aggregator_as; + attr->aggregator_addr.s_addr = stream_get_ipv4(peer->curr); + + /* Codification of AS 0 Processing */ + if (aggregator_as == BGP_AS_ZERO) { + flog_err(EC_BGP_ATTR_LEN, + "%s: AGGREGATOR AS number is 0 for aspath: %s", + peer->host, aspath_print(attr->aspath)); + + if (bgp_debug_update(peer, NULL, NULL, 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, sizeof(attr_str)); + + zlog_debug("%s: attributes: %s", __func__, attr_str); + } + } else { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); + } + + return BGP_ATTR_PARSE_PROCEED; + +aggregator_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* New Aggregator attribute */ +static enum bgp_attr_parse_ret +bgp_attr_as4_aggregator(struct bgp_attr_parser_args *args, + as_t *as4_aggregator_as, + struct in_addr *as4_aggregator_addr) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + as_t aggregator_as; + + if (length != 8) { + flog_err(EC_BGP_ATTR_LEN, "New Aggregator length is not 8 [%d]", + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + 0); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto as4_aggregator_ignore; + + aggregator_as = stream_getl(peer->curr); + + *as4_aggregator_as = aggregator_as; + as4_aggregator_addr->s_addr = stream_get_ipv4(peer->curr); + + /* Codification of AS 0 Processing */ + if (aggregator_as == BGP_AS_ZERO) { + flog_err(EC_BGP_ATTR_LEN, + "%s: AS4_AGGREGATOR AS number is 0 for aspath: %s", + peer->host, aspath_print(attr->aspath)); + + if (bgp_debug_update(peer, NULL, NULL, 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, sizeof(attr_str)); + + zlog_debug("%s: attributes: %s", __func__, attr_str); + } + } else { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR); + } + + return BGP_ATTR_PARSE_PROCEED; + +as4_aggregator_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Munge Aggregator and New-Aggregator, AS_PATH and NEW_AS_PATH. + */ +static enum bgp_attr_parse_ret +bgp_attr_munge_as4_attrs(struct peer *const peer, struct attr *const attr, + struct aspath *as4_path, as_t as4_aggregator, + struct in_addr *as4_aggregator_addr) +{ + int ignore_as4_path = 0; + struct aspath *newpath; + + if (!attr->aspath) { + /* NULL aspath shouldn't be possible as bgp_attr_parse should + * have + * checked that all well-known, mandatory attributes were + * present. + * + * Can only be a problem with peer itself - hard error + */ + return BGP_ATTR_PARSE_ERROR; + } + + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { + /* peer can do AS4, so we ignore AS4_PATH and AS4_AGGREGATOR + * if given. + * It is worth a warning though, because the peer really + * should not send them + */ + if (BGP_DEBUG(as4, AS4)) { + if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH))) + zlog_debug("[AS4] %s %s AS4_PATH", peer->host, + "AS4 capable peer, yet it sent"); + + if (attr->flag + & (ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR))) + zlog_debug("[AS4] %s %s AS4_AGGREGATOR", + peer->host, + "AS4 capable peer, yet it sent"); + } + + return BGP_ATTR_PARSE_PROCEED; + } + + /* We have a asn16 peer. First, look for AS4_AGGREGATOR + * because that may override AS4_PATH + */ + if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_AGGREGATOR))) { + if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) { + /* received both. + * if the as_number in aggregator is not AS_TRANS, + * then AS4_AGGREGATOR and AS4_PATH shall be ignored + * and the Aggregator shall be taken as + * info on the aggregating node, and the AS_PATH + * shall be taken as the AS_PATH + * otherwise + * the Aggregator shall be ignored and the + * AS4_AGGREGATOR shall be taken as the + * Aggregating node and the AS_PATH is to be + * constructed "as in all other cases" + */ + if (attr->aggregator_as != BGP_AS_TRANS) { + /* ignore */ + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] %s BGP not AS4 capable peer send AGGREGATOR != AS_TRANS and AS4_AGGREGATOR, so ignore AS4_AGGREGATOR and AS4_PATH", + peer->host); + ignore_as4_path = 1; + } else { + /* "New_aggregator shall be taken as aggregator" + */ + attr->aggregator_as = as4_aggregator; + attr->aggregator_addr.s_addr = + as4_aggregator_addr->s_addr; + } + } else { + /* We received a AS4_AGGREGATOR but no AGGREGATOR. + * That is bogus - but reading the conditions + * we have to handle AS4_AGGREGATOR as if it were + * AGGREGATOR in that case + */ + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] %s BGP not AS4 capable peer send AS4_AGGREGATOR but no AGGREGATOR, will take it as if AGGREGATOR with AS_TRANS had been there", + peer->host); + attr->aggregator_as = as4_aggregator; + /* sweep it under the carpet and simulate a "good" + * AGGREGATOR */ + attr->flag |= (ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)); + } + } + + /* need to reconcile NEW_AS_PATH and AS_PATH */ + if (!ignore_as4_path + && (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS4_PATH)))) { + newpath = aspath_reconcile_as4(attr->aspath, as4_path); + if (!newpath) + return BGP_ATTR_PARSE_ERROR; + + aspath_unintern(&attr->aspath); + attr->aspath = aspath_intern(newpath); + } + return BGP_ATTR_PARSE_PROCEED; +} + +/* Community attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_community(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + if (length == 0) { + bgp_attr_set_community(attr, NULL); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto community_ignore; + + bgp_attr_set_community( + attr, + community_parse((uint32_t *)stream_pnt(peer->curr), length)); + + /* XXX: fix community_parse to use stream API and remove this */ + stream_forward_getp(peer->curr, length); + + /* The Community attribute SHALL be considered malformed if its + * length is not a non-zero multiple of 4. + */ + if (!bgp_attr_get_community(attr)) + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + + return BGP_ATTR_PARSE_PROCEED; + +community_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Originator ID attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_originator_id(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* if the ORIGINATOR_ID attribute is received from an external + * neighbor, it SHALL be discarded using the approach of "attribute + * discard". + */ + if (peer->sort == BGP_PEER_EBGP) { + stream_forward_getp(peer->curr, length); + return BGP_ATTR_PARSE_PROCEED; + } + + /* if received from an internal neighbor, it SHALL be considered + * malformed if its length is not equal to 4. If malformed, the + * UPDATE message SHALL be handled using the approach of "treat-as- + * withdraw". + */ + if (length != 4) { + flog_err(EC_BGP_ATTR_LEN, "Bad originator ID length %d", + length); + + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto originator_id_ignore; + + attr->originator_id.s_addr = stream_get_ipv4(peer->curr); + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); + + return BGP_ATTR_PARSE_PROCEED; + +originator_id_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Cluster list attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_cluster_list(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* if the CLUSTER_LIST attribute is received from an external + * neighbor, it SHALL be discarded using the approach of "attribute + * discard". + */ + if (peer->sort == BGP_PEER_EBGP) { + stream_forward_getp(peer->curr, length); + return BGP_ATTR_PARSE_PROCEED; + } + + /* if received from an internal neighbor, it SHALL be considered + * malformed if its length is not a non-zero multiple of 4. If + * malformed, the UPDATE message SHALL be handled using the approach + * of "treat-as-withdraw". + */ + if (length == 0 || length % 4) { + flog_err(EC_BGP_ATTR_LEN, "Bad cluster list length %d", length); + + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto cluster_list_ignore; + + bgp_attr_set_cluster( + attr, cluster_parse((struct in_addr *)stream_pnt(peer->curr), + length)); + + /* XXX: Fix cluster_parse to use stream API and then remove this */ + stream_forward_getp(peer->curr, length); + + return BGP_ATTR_PARSE_PROCEED; + +cluster_list_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* get locally configure or received srte-color value*/ +uint32_t bgp_attr_get_color(struct attr *attr) +{ + if (attr->srte_color) + return attr->srte_color; + if (attr->ecommunity) + return ecommunity_select_color(attr->ecommunity); + return 0; +} + +/* Multiprotocol reachability information parse. */ +int bgp_mp_reach_parse(struct bgp_attr_parser_args *args, + struct bgp_nlri *mp_update) +{ + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + bgp_size_t nlri_len; + size_t start; + struct stream *s; + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* Set end of packet. */ + s = BGP_INPUT(peer); + start = stream_get_getp(s); + +/* safe to read statically sized header? */ +#define BGP_MP_REACH_MIN_SIZE 5 +#define LEN_LEFT (length - (stream_get_getp(s) - start)) + if ((length > STREAM_READABLE(s)) || (length < BGP_MP_REACH_MIN_SIZE)) { + zlog_info("%s: %s sent invalid length, %lu, of MP_REACH_NLRI", + __func__, peer->host, (unsigned long)length); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + + /* Load AFI, SAFI. */ + pkt_afi = stream_getw(s); + pkt_safi = stream_getc(s); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + /* Log if AFI or SAFI is unrecognized. This is not an error + * unless + * the attribute is otherwise malformed. + */ + if (bgp_debug_update(peer, NULL, NULL, 0)) + zlog_debug( + "%s sent unrecognizable AFI, %s or, SAFI, %s, of MP_REACH_NLRI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + return BGP_ATTR_PARSE_ERROR; + } + + /* Get nexthop length. */ + attr->mp_nexthop_len = stream_getc(s); + + if (LEN_LEFT < attr->mp_nexthop_len) { + zlog_info( + "%s: %s sent next-hop length, %u, in MP_REACH_NLRI which goes past the end of attribute", + __func__, peer->host, attr->mp_nexthop_len); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + + /* Nexthop length check. */ + switch (attr->mp_nexthop_len) { + case 0: + if (safi != SAFI_FLOWSPEC) { + zlog_info("%s: %s sent wrong next-hop length, %d, in MP_REACH_NLRI", + __func__, peer->host, attr->mp_nexthop_len); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + break; + case BGP_ATTR_NHLEN_VPNV4: + stream_getl(s); /* RD high */ + stream_getl(s); /* RD low */ + /* + * NOTE: intentional fall through + * - for consistency in rx processing + */ + fallthrough; + case BGP_ATTR_NHLEN_IPV4: + stream_get(&attr->mp_nexthop_global_in, s, IPV4_MAX_BYTELEN); + /* Probably needed for RFC 2283 */ + if (attr->nexthop.s_addr == INADDR_ANY) + memcpy(&attr->nexthop.s_addr, + &attr->mp_nexthop_global_in, IPV4_MAX_BYTELEN); + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + case BGP_ATTR_NHLEN_VPNV6_GLOBAL: + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL) { + stream_getl(s); /* RD high */ + stream_getl(s); /* RD low */ + } + stream_get(&attr->mp_nexthop_global, s, IPV6_MAX_BYTELEN); + if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { + if (!peer->nexthop.ifp) { + zlog_warn("%s sent a v6 global attribute but address is a V6 LL and there's no peer interface information. Hence, withdrawing", + peer->host); + return BGP_ATTR_PARSE_WITHDRAW; + } + attr->nh_ifindex = peer->nexthop.ifp->ifindex; + if (if_is_operative(peer->nexthop.ifp)) + SET_FLAG(attr->nh_flags, + BGP_ATTR_NH_IF_OPERSTATE); + else + UNSET_FLAG(attr->nh_flags, + BGP_ATTR_NH_IF_OPERSTATE); + } + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + case BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL: + if (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { + stream_getl(s); /* RD high */ + stream_getl(s); /* RD low */ + } + stream_get(&attr->mp_nexthop_global, s, IPV6_MAX_BYTELEN); + if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { + if (!peer->nexthop.ifp) { + zlog_warn("%s sent a v6 global and LL attribute but global address is a V6 LL and there's no peer interface information. Hence, withdrawing", + peer->host); + return BGP_ATTR_PARSE_WITHDRAW; + } + attr->nh_ifindex = peer->nexthop.ifp->ifindex; + if (if_is_operative(peer->nexthop.ifp)) + SET_FLAG(attr->nh_flags, + BGP_ATTR_NH_IF_OPERSTATE); + else + UNSET_FLAG(attr->nh_flags, + BGP_ATTR_NH_IF_OPERSTATE); + } + if (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { + stream_getl(s); /* RD high */ + stream_getl(s); /* RD low */ + } + stream_get(&attr->mp_nexthop_local, s, IPV6_MAX_BYTELEN); + if (!IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_local)) { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "%s sent next-hops %pI6 and %pI6. Ignoring non-LL value", + peer->host, &attr->mp_nexthop_global, + &attr->mp_nexthop_local); + + attr->mp_nexthop_len = IPV6_MAX_BYTELEN; + } + if (!peer->nexthop.ifp) { + zlog_warn("%s sent a v6 LL next-hop and there's no peer interface information. Hence, withdrawing", + peer->host); + return BGP_ATTR_PARSE_WITHDRAW; + } + attr->nh_lla_ifindex = peer->nexthop.ifp->ifindex; + break; + default: + zlog_info("%s: %s sent wrong next-hop length, %d, in MP_REACH_NLRI", + __func__, peer->host, attr->mp_nexthop_len); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + + if (!LEN_LEFT) { + zlog_info("%s: %s sent SNPA which couldn't be read", + __func__, peer->host); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + + { + uint8_t val; + if ((val = stream_getc(s))) + flog_warn( + EC_BGP_DEFUNCT_SNPA_LEN, + "%s sent non-zero value, %u, for defunct SNPA-length field", + peer->host, val); + } + + /* must have nrli_len, what is left of the attribute */ + nlri_len = LEN_LEFT; + if (nlri_len > STREAM_READABLE(s)) { + zlog_info("%s: %s sent MP_REACH_NLRI which couldn't be read", + __func__, peer->host); + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + } + + if (!nlri_len) { + zlog_info("%s: %s sent a zero-length NLRI. Hence, treating as a EOR marker", + __func__, peer->host); + + mp_update->afi = afi; + mp_update->safi = safi; + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_ATTR, 0); + } + + mp_update->afi = afi; + mp_update->safi = safi; + mp_update->nlri = stream_pnt(s); + mp_update->length = nlri_len; + + stream_forward_getp(s, nlri_len); + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI); + + return BGP_ATTR_PARSE_PROCEED; +#undef LEN_LEFT +} + +/* Multiprotocol unreachable parse */ +int bgp_mp_unreach_parse(struct bgp_attr_parser_args *args, + struct bgp_nlri *mp_withdraw) +{ + struct stream *s; + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + uint16_t withdraw_len; + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + s = peer->curr; + +#define BGP_MP_UNREACH_MIN_SIZE 3 + if ((length > STREAM_READABLE(s)) || (length < BGP_MP_UNREACH_MIN_SIZE)) + return BGP_ATTR_PARSE_ERROR_NOTIFYPLS; + + pkt_afi = stream_getw(s); + pkt_safi = stream_getc(s); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + /* Log if AFI or SAFI is unrecognized. This is not an error + * unless + * the attribute is otherwise malformed. + */ + if (bgp_debug_update(peer, NULL, NULL, 0)) + zlog_debug( + "%s: MP_UNREACH received AFI %s or SAFI %s is unrecognized", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + return BGP_ATTR_PARSE_ERROR; + } + + withdraw_len = length - BGP_MP_UNREACH_MIN_SIZE; + + mp_withdraw->afi = afi; + mp_withdraw->safi = safi; + mp_withdraw->nlri = stream_pnt(s); + mp_withdraw->length = withdraw_len; + + stream_forward_getp(s, withdraw_len); + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Large Community attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_large_community(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* + * Large community follows new attribute format. + */ + if (length == 0) { + bgp_attr_set_lcommunity(attr, NULL); + /* Empty extcomm doesn't seem to be invalid per se */ + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto large_community_ignore; + + bgp_attr_set_lcommunity( + attr, lcommunity_parse(stream_pnt(peer->curr), length)); + /* XXX: fix ecommunity_parse to use stream API */ + stream_forward_getp(peer->curr, length); + + if (!bgp_attr_get_lcommunity(attr)) + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + + return BGP_ATTR_PARSE_PROCEED; + +large_community_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Extended Community attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_ext_communities(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + uint8_t sticky = 0; + bool proxy = false; + struct ecommunity *ecomm; + + if (length == 0) { + bgp_attr_set_ecommunity(attr, NULL); + /* Empty extcomm doesn't seem to be invalid per se */ + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + ecomm = ecommunity_parse( + stream_pnt(peer->curr), length, + CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE)); + bgp_attr_set_ecommunity(attr, ecomm); + /* XXX: fix ecommunity_parse to use stream API */ + stream_forward_getp(peer->curr, length); + + /* The Extended Community attribute SHALL be considered malformed if + * its length is not a non-zero multiple of 8. + */ + if (!bgp_attr_get_ecommunity(attr)) + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + + /* Extract DF election preference and mobility sequence number */ + attr->df_pref = bgp_attr_df_pref_from_ec(attr, &attr->df_alg); + + /* Extract MAC mobility sequence number, if any. */ + attr->mm_seqnum = bgp_attr_mac_mobility_seqnum(attr, &sticky); + attr->sticky = sticky; + + /* Check if this is a Gateway MAC-IP advertisement */ + attr->default_gw = bgp_attr_default_gw(attr); + + /* Handle scenario where router flag ecommunity is not + * set but default gw ext community is present. + * Use default gateway, set and propogate R-bit. + */ + if (attr->default_gw) + attr->router_flag = 1; + + /* Check EVPN Neighbor advertisement flags, R-bit */ + bgp_attr_evpn_na_flag(attr, &attr->router_flag, &proxy); + if (proxy) + attr->es_flags |= ATTR_ES_PROXY_ADVERT; + + /* Extract the Rmac, if any */ + if (bgp_attr_rmac(attr, &attr->rmac)) { + if (bgp_debug_update(peer, NULL, NULL, 1) + && bgp_mac_exist(&attr->rmac)) + zlog_debug("%s: router mac %pEA is self mac", __func__, + &attr->rmac); + } + + /* Get the tunnel type from encap extended community */ + bgp_attr_extcom_tunnel_type(attr, + (bgp_encap_types *)&attr->encap_tunneltype); + + /* Extract link bandwidth, if any. */ + (void)ecommunity_linkbw_present(bgp_attr_get_ecommunity(attr), + &attr->link_bw); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* IPv6 Extended Community attribute. */ +static enum bgp_attr_parse_ret +bgp_attr_ipv6_ext_communities(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + struct ecommunity *ipv6_ecomm = NULL; + + if (length == 0) { + bgp_attr_set_ipv6_ecommunity(attr, ipv6_ecomm); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto ipv6_ext_community_ignore; + + ipv6_ecomm = ecommunity_parse_ipv6(stream_pnt(peer->curr), length); + bgp_attr_set_ipv6_ecommunity(attr, ipv6_ecomm); + + /* XXX: fix ecommunity_parse to use stream API */ + stream_forward_getp(peer->curr, length); + + if (!ipv6_ecomm) + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + + /* Extract link bandwidth, if any. */ + (void)ecommunity_linkbw_present(bgp_attr_get_ipv6_ecommunity(attr), + &attr->link_bw); + + return BGP_ATTR_PARSE_PROCEED; + +ipv6_ext_community_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* Parse Tunnel Encap attribute in an UPDATE */ +static int bgp_attr_encap(struct bgp_attr_parser_args *args) +{ + uint16_t tunneltype = 0; + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + bgp_size_t length = args->length; + uint8_t type = args->type; + uint8_t flag = args->flags; + + if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS) + || !CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL)) { + zlog_err("Tunnel Encap attribute flag isn't optional and transitive %d", + flag); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + if (BGP_ATTR_ENCAP == type) { + /* read outer TLV type and length */ + uint16_t tlv_length; + + if (length < 4) { + zlog_err( + "Tunnel Encap attribute not long enough to contain outer T,L"); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + tunneltype = stream_getw(BGP_INPUT(peer)); + tlv_length = stream_getw(BGP_INPUT(peer)); + length -= 4; + + if (tlv_length != length) { + zlog_info("%s: tlv_length(%d) != length(%d)", + __func__, tlv_length, length); + } + } + + while (STREAM_READABLE(BGP_INPUT(peer)) >= 4) { + uint16_t subtype = 0; + uint16_t sublength = 0; + struct bgp_attr_encap_subtlv *tlv; + + if (BGP_ATTR_ENCAP == type) { + subtype = stream_getc(BGP_INPUT(peer)); + if (subtype < 128) { + sublength = stream_getc(BGP_INPUT(peer)); + length -= 2; + } else { + sublength = stream_getw(BGP_INPUT(peer)); + length -= 3; + } +#ifdef ENABLE_BGP_VNC + } else { + subtype = stream_getw(BGP_INPUT(peer)); + sublength = stream_getw(BGP_INPUT(peer)); + length -= 4; +#endif + } + + if (sublength > length) { + zlog_err("Tunnel Encap attribute sub-tlv length %d exceeds remaining length %d", + sublength, length); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + if (STREAM_READABLE(BGP_INPUT(peer)) < sublength) { + zlog_err("Tunnel Encap attribute sub-tlv length %d exceeds remaining stream length %zu", + sublength, STREAM_READABLE(BGP_INPUT(peer))); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + /* alloc and copy sub-tlv */ + /* TBD make sure these are freed when attributes are released */ + tlv = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + sublength); + tlv->type = subtype; + tlv->length = sublength; + stream_get(tlv->value, peer->curr, sublength); + length -= sublength; + + /* attach tlv to encap chain */ + if (BGP_ATTR_ENCAP == type) { + struct bgp_attr_encap_subtlv *stlv_last; + for (stlv_last = attr->encap_subtlvs; + stlv_last && stlv_last->next; + stlv_last = stlv_last->next) + ; + if (stlv_last) { + stlv_last->next = tlv; + } else { + attr->encap_subtlvs = tlv; + } +#ifdef ENABLE_BGP_VNC + } else { + struct bgp_attr_encap_subtlv *stlv_last; + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + for (stlv_last = vnc_subtlvs; + stlv_last && stlv_last->next; + stlv_last = stlv_last->next) + ; + if (stlv_last) + stlv_last->next = tlv; + else + bgp_attr_set_vnc_subtlvs(attr, tlv); +#endif + } + } + + if (BGP_ATTR_ENCAP == type) { + attr->encap_tunneltype = tunneltype; + } + + if (length) { + /* spurious leftover data */ + zlog_err("Tunnel Encap attribute length is bad: %d leftover octets", + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + + return 0; +} + + +/* SRv6 Service Data Sub-Sub-TLV attribute + * draft-ietf-bess-srv6-services-07 + */ +static enum bgp_attr_parse_ret +bgp_attr_srv6_service_data(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + uint8_t type, loc_block_len, loc_node_len, func_len, arg_len, + transposition_len, transposition_offset; + uint16_t length; + size_t headersz = sizeof(type) + sizeof(length); + + if (STREAM_READABLE(peer->curr) < headersz) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Data Sub-Sub-TLV attribute - insufficent data (need %zu for attribute header, have %zu remaining in UPDATE)", + headersz, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + type = stream_getc(peer->curr); + length = stream_getw(peer->curr); + + if (STREAM_READABLE(peer->curr) < length) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Data Sub-Sub-TLV attribute - insufficent data (need %hu for attribute data, have %zu remaining in UPDATE)", + length, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (length < BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Data Sub-Sub-TLV attribute - insufficient data (need %u, have %hu remaining in UPDATE)", + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH, + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (type == BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE) { + if (STREAM_READABLE(peer->curr) < + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Data Sub-Sub-TLV attribute - insufficient data (need %u, have %zu remaining in UPDATE)", + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH, + STREAM_READABLE(peer->curr)); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + loc_block_len = stream_getc(peer->curr); + loc_node_len = stream_getc(peer->curr); + func_len = stream_getc(peer->curr); + arg_len = stream_getc(peer->curr); + transposition_len = stream_getc(peer->curr); + transposition_offset = stream_getc(peer->curr); + + /* Log SRv6 Service Data Sub-Sub-TLV */ + if (BGP_DEBUG(vpn, VPN_LEAK_LABEL)) { + zlog_debug( + "%s: srv6-l3-srv-data loc-block-len=%u, loc-node-len=%u func-len=%u, arg-len=%u, transposition-len=%u, transposition-offset=%u", + __func__, loc_block_len, loc_node_len, func_len, + arg_len, transposition_len, + transposition_offset); + } + + attr->srv6_l3vpn->loc_block_len = loc_block_len; + attr->srv6_l3vpn->loc_node_len = loc_node_len; + attr->srv6_l3vpn->func_len = func_len; + attr->srv6_l3vpn->arg_len = arg_len; + attr->srv6_l3vpn->transposition_len = transposition_len; + attr->srv6_l3vpn->transposition_offset = transposition_offset; + } + + else { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "%s attr SRv6 Service Data Sub-Sub-TLV sub-sub-type=%u is not supported, skipped", + peer->host, type); + + stream_forward_getp(peer->curr, length); + } + + return BGP_ATTR_PARSE_PROCEED; +} + +/* SRv6 Service Sub-TLV attribute + * draft-ietf-bess-srv6-services-07 + */ +static enum bgp_attr_parse_ret +bgp_attr_srv6_service(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + struct in6_addr ipv6_sid; + uint8_t type, sid_flags; + uint16_t length, endpoint_behavior; + size_t headersz = sizeof(type) + sizeof(length); + enum bgp_attr_parse_ret err; + + if (STREAM_READABLE(peer->curr) < headersz) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Sub-TLV attribute - insufficent data (need %zu for attribute header, have %zu remaining in UPDATE)", + headersz, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + type = stream_getc(peer->curr); + length = stream_getw(peer->curr); + + if (STREAM_READABLE(peer->curr) < length) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Sub-TLV attribute - insufficent data (need %hu for attribute data, have %zu remaining in UPDATE)", + length, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (type == BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO) { + if (STREAM_READABLE(peer->curr) < + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO_LENGTH) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed SRv6 Service Sub-TLV attribute - insufficent data (need %d for attribute data, have %zu remaining in UPDATE)", + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO_LENGTH, + STREAM_READABLE(peer->curr)); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + stream_getc(peer->curr); + stream_get(&ipv6_sid, peer->curr, sizeof(ipv6_sid)); + sid_flags = stream_getc(peer->curr); + endpoint_behavior = stream_getw(peer->curr); + stream_getc(peer->curr); + + /* Log SRv6 Service Sub-TLV */ + if (BGP_DEBUG(vpn, VPN_LEAK_LABEL)) + zlog_debug( + "%s: srv6-l3-srv sid %pI6, sid-flags 0x%02x, end-behaviour 0x%04x", + __func__, &ipv6_sid, sid_flags, + endpoint_behavior); + + /* Configure from Info */ + if (attr->srv6_l3vpn) { + flog_err(EC_BGP_ATTRIBUTE_REPEATED, + "Prefix SID SRv6 L3VPN field repeated"); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_MAL_ATTR, args->total); + } + attr->srv6_l3vpn = XCALLOC(MTYPE_BGP_SRV6_L3VPN, + sizeof(struct bgp_attr_srv6_l3vpn)); + sid_copy(&attr->srv6_l3vpn->sid, &ipv6_sid); + attr->srv6_l3vpn->sid_flags = sid_flags; + attr->srv6_l3vpn->endpoint_behavior = endpoint_behavior; + attr->srv6_l3vpn->loc_block_len = 0; + attr->srv6_l3vpn->loc_node_len = 0; + attr->srv6_l3vpn->func_len = 0; + attr->srv6_l3vpn->arg_len = 0; + attr->srv6_l3vpn->transposition_len = 0; + attr->srv6_l3vpn->transposition_offset = 0; + + // Sub-Sub-TLV found + if (length > BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO_LENGTH) { + err = bgp_attr_srv6_service_data(args); + + if (err != BGP_ATTR_PARSE_PROCEED) + return err; + } + + attr->srv6_l3vpn = srv6_l3vpn_intern(attr->srv6_l3vpn); + } + + /* Placeholder code for unsupported type */ + else { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "%s attr SRv6 Service Sub-TLV sub-type=%u is not supported, skipped", + peer->host, type); + + stream_forward_getp(peer->curr, length); + } + + return BGP_ATTR_PARSE_PROCEED; +} + +/* + * Read an individual SID value returning how much data we have read + * Returns 0 if there was an error that needs to be passed up the stack + */ +static enum bgp_attr_parse_ret +bgp_attr_psid_sub(uint8_t type, uint16_t length, + struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + uint32_t label_index; + struct in6_addr ipv6_sid; + uint32_t srgb_base; + uint32_t srgb_range; + int srgb_count; + uint8_t sid_type, sid_flags; + + /* + * Check that we actually have at least as much data as + * specified by the length field + */ + if (STREAM_READABLE(peer->curr) < length) { + flog_err( + EC_BGP_ATTR_LEN, + "Prefix SID specifies length %hu, but only %zu bytes remain", + length, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (type == BGP_PREFIX_SID_LABEL_INDEX) { + if (length != BGP_PREFIX_SID_LABEL_INDEX_LENGTH) { + flog_err(EC_BGP_ATTR_LEN, + "Prefix SID label index length is %hu instead of %u", + length, BGP_PREFIX_SID_LABEL_INDEX_LENGTH); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* Ignore flags and reserved */ + stream_getc(peer->curr); + stream_getw(peer->curr); + + /* Fetch the label index and see if it is valid. */ + label_index = stream_getl(peer->curr); + if (label_index == BGP_INVALID_LABEL_INDEX) + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + + /* Store label index; subsequently, we'll check on + * address-family */ + attr->label_index = label_index; + } else if (type == BGP_PREFIX_SID_IPV6) { + if (length != BGP_PREFIX_SID_IPV6_LENGTH) { + flog_err(EC_BGP_ATTR_LEN, + "Prefix SID IPv6 length is %hu instead of %u", + length, BGP_PREFIX_SID_IPV6_LENGTH); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* Ignore reserved */ + stream_getc(peer->curr); + stream_getw(peer->curr); + + stream_get(&ipv6_sid, peer->curr, 16); + } else if (type == BGP_PREFIX_SID_ORIGINATOR_SRGB) { + /* + * ietf-idr-bgp-prefix-sid-05: + * Length is the total length of the value portion of the + * TLV: 2 + multiple of 6. + * + * peer->curr stream readp should be at the beginning of the 16 + * bit flag field at this point in the code. + */ + + /* + * Check that the TLV length field is sane: at least 2 bytes of + * flag, and at least 1 SRGB (these are 6 bytes each) + */ + if (length < (2 + BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH)) { + flog_err( + EC_BGP_ATTR_LEN, + "Prefix SID Originator SRGB length field claims length of %hu bytes, but the minimum for this TLV type is %u", + length, + 2 + BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* + * Check that the portion of the TLV containing the sequence of + * SRGBs corresponds to a multiple of the SRGB size; to get + * that length, we skip the 16 bit flags field + */ + stream_getw(peer->curr); + length -= 2; + if (length % BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH) { + flog_err( + EC_BGP_ATTR_LEN, + "Prefix SID Originator SRGB length field claims attribute SRGB sequence section is %hubytes, but it must be a multiple of %u", + length, BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + srgb_count = length / BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH; + + for (int i = 0; i < srgb_count; i++) { + stream_get(&srgb_base, peer->curr, 3); + stream_get(&srgb_range, peer->curr, 3); + } + } else if (type == BGP_PREFIX_SID_VPN_SID) { + if (length != BGP_PREFIX_SID_VPN_SID_LENGTH) { + flog_err(EC_BGP_ATTR_LEN, + "Prefix SID VPN SID length is %hu instead of %u", + length, BGP_PREFIX_SID_VPN_SID_LENGTH); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + /* Parse VPN-SID Sub-TLV */ + stream_getc(peer->curr); /* reserved */ + sid_type = stream_getc(peer->curr); /* sid_type */ + sid_flags = stream_getc(peer->curr); /* sid_flags */ + stream_get(&ipv6_sid, peer->curr, + sizeof(ipv6_sid)); /* sid_value */ + + /* Log VPN-SID Sub-TLV */ + if (BGP_DEBUG(vpn, VPN_LEAK_LABEL)) + zlog_debug( + "%s: vpn-sid: sid %pI6, sid-type 0x%02x sid-flags 0x%02x", + __func__, &ipv6_sid, sid_type, sid_flags); + + /* Configure from Info */ + if (attr->srv6_vpn) { + flog_err(EC_BGP_ATTRIBUTE_REPEATED, + "Prefix SID SRv6 VPN field repeated"); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_MAL_ATTR, args->total); + } + attr->srv6_vpn = XCALLOC(MTYPE_BGP_SRV6_VPN, + sizeof(struct bgp_attr_srv6_vpn)); + attr->srv6_vpn->sid_flags = sid_flags; + sid_copy(&attr->srv6_vpn->sid, &ipv6_sid); + attr->srv6_vpn = srv6_vpn_intern(attr->srv6_vpn); + } else if (type == BGP_PREFIX_SID_SRV6_L3_SERVICE) { + if (STREAM_READABLE(peer->curr) < 1) { + flog_err( + EC_BGP_ATTR_LEN, + "Prefix SID SRV6 L3 Service not enough data left, it must be at least 1 byte"); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + /* ignore reserved */ + stream_getc(peer->curr); + + return bgp_attr_srv6_service(args); + } + /* Placeholder code for Unsupported TLV */ + else { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "%s attr Prefix-SID sub-type=%u is not supported, skipped", + peer->host, type); + + stream_forward_getp(peer->curr, length); + } + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Prefix SID attribute + * draft-ietf-idr-bgp-prefix-sid-05 + */ +enum bgp_attr_parse_ret bgp_attr_prefix_sid(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + enum bgp_attr_parse_ret ret; + + uint8_t type; + uint16_t length; + size_t headersz = sizeof(type) + sizeof(length); + size_t psid_parsed_length = 0; + + while (STREAM_READABLE(peer->curr) > 0 + && psid_parsed_length < args->length) { + + if (STREAM_READABLE(peer->curr) < headersz) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed Prefix SID attribute - insufficent data (need %zu for attribute header, have %zu remaining in UPDATE)", + headersz, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + type = stream_getc(peer->curr); + length = stream_getw(peer->curr); + + if (STREAM_READABLE(peer->curr) < length) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed Prefix SID attribute - insufficient data (need %hu for attribute body, have %zu remaining in UPDATE)", + length, STREAM_READABLE(peer->curr)); + return bgp_attr_malformed(args, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + ret = bgp_attr_psid_sub(type, length, args); + + if (ret != BGP_ATTR_PARSE_PROCEED) + return ret; + + psid_parsed_length += length + headersz; + + if (psid_parsed_length > args->length) { + flog_err( + EC_BGP_ATTR_LEN, + "Malformed Prefix SID attribute - TLV overflow by attribute (need %zu for TLV length, have %zu overflowed in UPDATE)", + length + headersz, psid_parsed_length - (length + headersz)); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + } + + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* PMSI tunnel attribute (RFC 6514) + * Basic validation checks done here. + */ +static enum bgp_attr_parse_ret +bgp_attr_pmsi_tunnel(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + uint8_t tnl_type; + int attr_parse_len = 2 + BGP_LABEL_BYTES; + + /* Verify that the receiver is expecting "ingress replication" as we + * can only support that. + */ + if (length < attr_parse_len) { + flog_err(EC_BGP_ATTR_LEN, "Bad PMSI tunnel attribute length %d", + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + stream_getc(peer->curr); /* Flags */ + tnl_type = stream_getc(peer->curr); + if (tnl_type > PMSI_TNLTYPE_MAX) { + flog_err(EC_BGP_ATTR_PMSI_TYPE, + "Invalid PMSI tunnel attribute type %d", tnl_type); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, + args->total); + } + if (tnl_type == PMSI_TNLTYPE_INGR_REPL) { + if (length != 9) { + flog_err(EC_BGP_ATTR_PMSI_LEN, + "Bad PMSI tunnel attribute length %d for IR", + length); + return bgp_attr_malformed( + args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + } + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL); + bgp_attr_set_pmsi_tnl_type(attr, tnl_type); + stream_get(&attr->label, peer->curr, BGP_LABEL_BYTES); + + /* Forward read pointer of input stream. */ + stream_forward_getp(peer->curr, length - attr_parse_len); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* AIGP attribute (rfc7311) */ +static enum bgp_attr_parse_ret bgp_attr_aigp(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + uint8_t *s = stream_pnt(peer->curr); + uint64_t aigp = 0; + + /* If an AIGP attribute is received on a BGP session for which + * AIGP_SESSION is disabled, the attribute MUST be treated exactly + * as if it were an unrecognized non-transitive attribute. + * That is, it "MUST be quietly ignored and not passed along to + * other BGP peers". + * For Internal BGP (IBGP) sessions, and for External BGP (EBGP) + * sessions between members of the same BGP Confederation, + * the default value of AIGP_SESSION SHOULD be "enabled". + */ + if (peer->sort == BGP_PEER_EBGP && + (!CHECK_FLAG(peer->flags, PEER_FLAG_AIGP) || + peer->sub_sort != BGP_PEER_EBGP_OAD)) { + zlog_warn( + "%pBP received AIGP attribute, but eBGP peer do not support it", + peer); + goto aigp_ignore; + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto aigp_ignore; + + if (!bgp_attr_aigp_valid(s, length)) + goto aigp_ignore; + + /* Extract AIGP Metric TLV */ + if (bgp_attr_aigp_get_tlv_metric(s, length, &aigp)) + bgp_attr_set_aigp_metric(attr, aigp); + +aigp_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* OTC attribute. */ +static enum bgp_attr_parse_ret bgp_attr_otc(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + + /* Length check. */ + if (length != 4) { + flog_err(EC_BGP_ATTR_LEN, "OTC attribute length isn't 4 [%u]", + length); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + args->total); + } + + if (peer->discard_attrs[args->type] || peer->withdraw_attrs[args->type]) + goto otc_ignore; + + attr->otc = stream_getl(peer->curr); + if (!attr->otc) { + flog_err(EC_BGP_ATTR_MAL_AS_PATH, "OTC attribute value is 0"); + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_MAL_AS_PATH, + args->total); + } + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC); + + return BGP_ATTR_PARSE_PROCEED; + +otc_ignore: + stream_forward_getp(peer->curr, length); + + return bgp_attr_ignore(peer, args->type); +} + +/* BGP unknown attribute treatment. */ +static enum bgp_attr_parse_ret +bgp_attr_unknown(struct bgp_attr_parser_args *args) +{ + bgp_size_t total = args->total; + struct transit *transit; + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + uint8_t *const startp = args->startp; + const uint8_t type = args->type; + const uint8_t flag = args->flags; + const bgp_size_t length = args->length; + + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "%s Unknown attribute is received (type %d, length %d)", + peer->host, type, length); + + /* Forward read pointer of input stream. */ + stream_forward_getp(peer->curr, length); + + if (peer->discard_attrs[type] || peer->withdraw_attrs[type]) + return bgp_attr_ignore(peer, type); + + /* If any of the mandatory well-known attributes are not recognized, + then the Error Subcode is set to Unrecognized Well-known + Attribute. The Data field contains the unrecognized attribute + (type, length and value). */ + if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL)) { + return bgp_attr_malformed(args, BGP_NOTIFY_UPDATE_UNREC_ATTR, + args->total); + } + + /* Unrecognized non-transitive optional attributes must be quietly + ignored and not passed along to other BGP peers. */ + if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS)) + return BGP_ATTR_PARSE_PROCEED; + + /* If a path with recognized transitive optional attribute is + accepted and passed along to other BGP peers and the Partial bit + in the Attribute Flags octet is set to 1 by some previous AS, it + is not set back to 0 by the current AS. */ + SET_FLAG(*startp, BGP_ATTR_FLAG_PARTIAL); + + /* Store transitive attribute to the end of attr->transit. */ + transit = bgp_attr_get_transit(attr); + if (!transit) + transit = XCALLOC(MTYPE_TRANSIT, sizeof(struct transit)); + + transit->val = XREALLOC(MTYPE_TRANSIT_VAL, transit->val, + transit->length + total); + + memcpy(transit->val + transit->length, startp, total); + transit->length += total; + bgp_attr_set_transit(attr, transit); + + return BGP_ATTR_PARSE_PROCEED; +} + +/* Well-known attribute check. */ +static int bgp_attr_check(struct peer *peer, struct attr *attr, + bgp_size_t length) +{ + uint8_t type = 0; + + /* BGP Graceful-Restart End-of-RIB for IPv4 unicast is signaled as an + * empty UPDATE. Treat-as-withdraw, otherwise if we just ignore it, + * we will pass it to be processed as a normal UPDATE without mandatory + * attributes, that could lead to harmful behavior. + */ + if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV) && !attr->flag && + !length) + return BGP_ATTR_PARSE_WITHDRAW; + + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGIN))) + type = BGP_ATTR_ORIGIN; + + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) + type = BGP_ATTR_AS_PATH; + + /* RFC 2858 makes Next-Hop optional/ignored, if MP_REACH_NLRI is present + * and + * NLRI is empty. We can't easily check NLRI empty here though. + */ + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) + && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) + type = BGP_ATTR_NEXT_HOP; + + if (peer->sort == BGP_PEER_IBGP + && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) + type = BGP_ATTR_LOCAL_PREF; + + /* An UPDATE message that contains the MP_UNREACH_NLRI is not required + * to carry any other path attributes. Though if MP_REACH_NLRI or NLRI + * are present, it should. Check for any other attribute being present + * instead. + */ + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI)) && + CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI))) + return type ? BGP_ATTR_PARSE_MISSING_MANDATORY + : BGP_ATTR_PARSE_PROCEED; + + /* If any of the well-known mandatory attributes are not present + * in an UPDATE message, then "treat-as-withdraw" MUST be used. + */ + if (type) { + flog_warn(EC_BGP_MISSING_ATTRIBUTE, + "%s Missing well-known attribute %s.", peer->host, + lookup_msg(attr_str, type, NULL)); + return BGP_ATTR_PARSE_WITHDRAW; + } + return BGP_ATTR_PARSE_PROCEED; +} + +/* Read attribute of update packet. This function is called from + bgp_update_receive() in bgp_packet.c. */ +enum bgp_attr_parse_ret bgp_attr_parse(struct peer *peer, struct attr *attr, + bgp_size_t size, + struct bgp_nlri *mp_update, + struct bgp_nlri *mp_withdraw) +{ + enum bgp_attr_parse_ret ret; + uint8_t flag = 0; + uint8_t type = 0; + bgp_size_t length = 0; + uint8_t *startp, *endp; + uint8_t *attr_endp; + uint8_t seen[BGP_ATTR_BITMAP_SIZE]; + /* we need the as4_path only until we have synthesized the as_path with + * it */ + /* same goes for as4_aggregator */ + struct aspath *as4_path = NULL; + as_t as4_aggregator = 0; + struct in_addr as4_aggregator_addr = {.s_addr = 0}; + struct transit *transit; + + /* Initialize bitmap. */ + memset(seen, 0, BGP_ATTR_BITMAP_SIZE); + + /* End pointer of BGP attribute. */ + endp = BGP_INPUT_PNT(peer) + size; + + /* Get attributes to the end of attribute length. */ + while (BGP_INPUT_PNT(peer) < endp) { + startp = BGP_INPUT_PNT(peer); + + /* Fewer than three octets remain (or fewer than four + * octets, if the Attribute Flags field has the Extended + * Length bit set) when beginning to parse the attribute. + * That is, this case exists if there remains unconsumed + * data in the path attributes but yet insufficient data + * to encode a single minimum-sized path attribute. + * + * An error condition exists and the "treat-as-withdraw" + * approach MUST be used (unless some other, more severe + * error is encountered dictating a stronger approach), + * and the Total Attribute Length MUST be relied upon to + * enable the beginning of the NLRI field to be located. + */ + + /* Check remaining length check.*/ + if ((endp - startp) < BGP_ATTR_MIN_LEN) { + /* XXX warning: long int format, int arg (arg 5) */ + flog_warn( + EC_BGP_ATTRIBUTE_TOO_SMALL, + "%s: error BGP attribute length %lu is smaller than min len", + peer->host, + (unsigned long)(endp + - stream_pnt(BGP_INPUT(peer)))); + + if (peer->sort != BGP_PEER_EBGP) { + bgp_notify_send(peer->connection, + BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); + ret = BGP_ATTR_PARSE_ERROR; + } else { + ret = BGP_ATTR_PARSE_WITHDRAW; + } + + goto done; + } + + /* Fetch attribute flag and type. + * The lower-order four bits of the Attribute Flags octet are + * unused. They MUST be zero when sent and MUST be ignored when + * received. + */ + flag = 0xF0 & stream_getc(BGP_INPUT(peer)); + type = stream_getc(BGP_INPUT(peer)); + + /* Check whether Extended-Length applies and is in bounds */ + if (CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN) + && ((endp - startp) < (BGP_ATTR_MIN_LEN + 1))) { + flog_warn( + EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, + "%s: Extended length set, but just %lu bytes of attr header", + peer->host, + (unsigned long)(endp + - stream_pnt(BGP_INPUT(peer)))); + + if (peer->sort != BGP_PEER_EBGP) { + bgp_notify_send(peer->connection, + BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); + ret = BGP_ATTR_PARSE_ERROR; + } else { + ret = BGP_ATTR_PARSE_WITHDRAW; + } + + goto done; + } + + /* Check extended attribue length bit. */ + if (CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN)) + length = stream_getw(BGP_INPUT(peer)); + else + length = stream_getc(BGP_INPUT(peer)); + + /* Overflow check. */ + attr_endp = BGP_INPUT_PNT(peer) + length; + + if (attr_endp > endp) { + flog_warn( + EC_BGP_ATTRIBUTE_TOO_LARGE, + "%s: BGP type %d length %d is too large, attribute total length is %d. attr_endp is %p. endp is %p", + peer->host, type, length, size, attr_endp, + endp); + + /* Only relax error handling for eBGP peers */ + if (peer->sort != BGP_PEER_EBGP) { + /* + * RFC 4271 6.3 + * If any recognized attribute has an Attribute + * Length that conflicts with the expected length + * (based on the attribute type code), then the + * Error Subcode MUST be set to Attribute Length + * Error. The Data field MUST contain the erroneous + * attribute (type, length, and value). + * ---------- + * We do not currently have a good way to determine the + * length of the attribute independent of the length + * received in the message. Instead we send the + * minimum between the amount of data we have and the + * amount specified by the attribute length field. + * + * Instead of directly passing in the packet buffer and + * offset we use the stream_get* functions to read into + * a stack buffer, since they perform bounds checking + * and we are working with untrusted data. + */ + unsigned char ndata[peer->max_packet_size]; + + memset(ndata, 0x00, sizeof(ndata)); + size_t lfl = + CHECK_FLAG(flag, BGP_ATTR_FLAG_EXTLEN) ? 2 : 1; + /* Rewind to end of flag field */ + stream_rewind_getp(BGP_INPUT(peer), (1 + lfl)); + /* Type */ + stream_get(&ndata[0], BGP_INPUT(peer), 1); + /* Length */ + stream_get(&ndata[1], BGP_INPUT(peer), lfl); + /* Value */ + size_t atl = attr_endp - startp; + size_t ndl = MIN(atl, STREAM_READABLE(BGP_INPUT(peer))); + + stream_get(&ndata[lfl + 1], BGP_INPUT(peer), ndl); + + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, + ndata, ndl + lfl + 1); + + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } else { + /* Handling as per RFC7606 section 4, treat-as-withdraw approach + * must be followed when the total attribute length is in conflict + * with the enclosed path attribute length. + */ + flog_warn( + EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, + "%s: Attribute %s, parse error - treating as withdrawal", + peer->host, lookup_msg(attr_str, type, NULL)); + ret = BGP_ATTR_PARSE_WITHDRAW; + stream_forward_getp(BGP_INPUT(peer), endp - BGP_INPUT_PNT(peer)); + goto done; + } + } + + /* If attribute appears more than once in the UPDATE message, + * for MP_REACH_NLRI & MP_UNREACH_NLRI attributes + * the Error Subcode is set to Malformed Attribute List. + * For all other attributes, all the occurances of the attribute + * other than the first occurence is discarded. (RFC7606 3g) + */ + + if (CHECK_BITMAP(seen, type)) { + /* Only relax error handling for eBGP peers */ + if (peer->sort != BGP_PEER_EBGP || + type == BGP_ATTR_MP_REACH_NLRI || type == BGP_ATTR_MP_UNREACH_NLRI) { + flog_warn( + EC_BGP_ATTRIBUTE_REPEATED, + "%s: error BGP attribute type %d appears twice in a message", + peer->host, type); + + bgp_notify_send(peer->connection, + BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } else { + flog_warn( + EC_BGP_ATTRIBUTE_REPEATED, + "%s: error BGP attribute type %d appears twice in a message - discard attribute", + peer->host, type); + /* Adjust the stream getp to the end of the attribute, in case we + * haven't read all the attributes. + */ + stream_set_getp(BGP_INPUT(peer), + (startp - STREAM_DATA(BGP_INPUT(peer))) + (attr_endp - startp)); + continue; + } + } + + /* Set type to bitmap to check duplicate attribute. `type' is + unsigned char so it never overflow bitmap range. */ + + SET_BITMAP(seen, type); + + struct bgp_attr_parser_args attr_args = { + .peer = peer, + .length = length, + .attr = attr, + .type = type, + .flags = flag, + .startp = startp, + .total = attr_endp - startp, + }; + + + /* If any recognized attribute has Attribute Flags that conflict + with the Attribute Type Code, then the Error Subcode is set + to + Attribute Flags Error. The Data field contains the erroneous + attribute (type, length and value). */ + if (bgp_attr_flag_invalid(&attr_args)) { + ret = bgp_attr_malformed( + &attr_args, BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, + attr_args.total); + if (ret == BGP_ATTR_PARSE_PROCEED) + continue; + stream_forward_getp(BGP_INPUT(peer), endp - BGP_INPUT_PNT(peer)); + goto done; + } + + /* OK check attribute and store it's value. */ + switch (type) { + case BGP_ATTR_ORIGIN: + ret = bgp_attr_origin(&attr_args); + break; + case BGP_ATTR_AS_PATH: + ret = bgp_attr_aspath(&attr_args); + break; + case BGP_ATTR_AS4_PATH: + ret = bgp_attr_as4_path(&attr_args, &as4_path); + break; + case BGP_ATTR_NEXT_HOP: + ret = bgp_attr_nexthop(&attr_args); + break; + case BGP_ATTR_MULTI_EXIT_DISC: + ret = bgp_attr_med(&attr_args); + break; + case BGP_ATTR_LOCAL_PREF: + ret = bgp_attr_local_pref(&attr_args); + break; + case BGP_ATTR_ATOMIC_AGGREGATE: + ret = bgp_attr_atomic(&attr_args); + break; + case BGP_ATTR_AGGREGATOR: + ret = bgp_attr_aggregator(&attr_args); + break; + case BGP_ATTR_AS4_AGGREGATOR: + ret = bgp_attr_as4_aggregator(&attr_args, + &as4_aggregator, + &as4_aggregator_addr); + break; + case BGP_ATTR_COMMUNITIES: + ret = bgp_attr_community(&attr_args); + break; + case BGP_ATTR_LARGE_COMMUNITIES: + ret = bgp_attr_large_community(&attr_args); + break; + case BGP_ATTR_ORIGINATOR_ID: + ret = bgp_attr_originator_id(&attr_args); + break; + case BGP_ATTR_CLUSTER_LIST: + ret = bgp_attr_cluster_list(&attr_args); + break; + case BGP_ATTR_MP_REACH_NLRI: + ret = bgp_mp_reach_parse(&attr_args, mp_update); + break; + case BGP_ATTR_MP_UNREACH_NLRI: + ret = bgp_mp_unreach_parse(&attr_args, mp_withdraw); + break; + case BGP_ATTR_EXT_COMMUNITIES: + ret = bgp_attr_ext_communities(&attr_args); + break; +#ifdef ENABLE_BGP_VNC_ATTR + case BGP_ATTR_VNC: +#endif + case BGP_ATTR_ENCAP: + ret = bgp_attr_encap(&attr_args); + break; + case BGP_ATTR_PREFIX_SID: + ret = bgp_attr_prefix_sid(&attr_args); + break; + case BGP_ATTR_PMSI_TUNNEL: + ret = bgp_attr_pmsi_tunnel(&attr_args); + break; + case BGP_ATTR_IPV6_EXT_COMMUNITIES: + ret = bgp_attr_ipv6_ext_communities(&attr_args); + break; + case BGP_ATTR_OTC: + ret = bgp_attr_otc(&attr_args); + break; + case BGP_ATTR_AIGP: + ret = bgp_attr_aigp(&attr_args); + break; + default: + ret = bgp_attr_unknown(&attr_args); + break; + } + + if (ret == BGP_ATTR_PARSE_ERROR_NOTIFYPLS) { + bgp_notify_send(peer->connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } + + if (ret == BGP_ATTR_PARSE_ERROR) { + flog_warn(EC_BGP_ATTRIBUTE_PARSE_ERROR, + "%s: Attribute %s, parse error", peer->host, + lookup_msg(attr_str, type, NULL)); + goto done; + } + if (ret == BGP_ATTR_PARSE_WITHDRAW) { + flog_warn( + EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, + "%s: Attribute %s, parse error - treating as withdrawal", + peer->host, lookup_msg(attr_str, type, NULL)); + stream_forward_getp(BGP_INPUT(peer), endp - BGP_INPUT_PNT(peer)); + goto done; + } + + /* Check the fetched length. */ + if (BGP_INPUT_PNT(peer) != attr_endp) { + flog_warn(EC_BGP_ATTRIBUTE_FETCH_ERROR, + "%s: BGP attribute %s, fetch error", + peer->host, lookup_msg(attr_str, type, NULL)); + bgp_notify_send(peer->connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } + } + + /* + * draft-ietf-idr-bgp-prefix-sid-27#section-3: + * About Prefix-SID path attribute, + * Label-Index TLV(type1) and The Originator SRGB TLV(type-3) + * may only appear in a BGP Prefix-SID attribute attached to + * IPv4/IPv6 Labeled Unicast prefixes ([RFC8277]). + * It MUST be ignored when received for other BGP AFI/SAFI combinations. + */ + if (!attr->mp_nexthop_len || mp_update->safi != SAFI_LABELED_UNICAST) + attr->label_index = BGP_INVALID_LABEL_INDEX; + + /* Check final read pointer is same as end pointer. */ + if (BGP_INPUT_PNT(peer) != endp) { + flog_warn(EC_BGP_ATTRIBUTES_MISMATCH, + "%s: BGP attribute %s, length mismatch", peer->host, + lookup_msg(attr_str, type, NULL)); + bgp_notify_send(peer->connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_ATTR_LENG_ERR); + + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } + + /* + * RFC4271: If the NEXT_HOP attribute field is syntactically incorrect, + * then the Error Subcode MUST be set to Invalid NEXT_HOP Attribute. + * This is implemented below and will result in a NOTIFICATION. If the + * NEXT_HOP attribute is semantically incorrect, the error SHOULD be + * logged, and the route SHOULD be ignored. In this case, a NOTIFICATION + * message SHOULD NOT be sent. This is implemented elsewhere. + * + * RFC4760: An UPDATE message that carries no NLRI, other than the one + * encoded in the MP_REACH_NLRI attribute, SHOULD NOT carry the NEXT_HOP + * attribute. If such a message contains the NEXT_HOP attribute, the BGP + * speaker that receives the message SHOULD ignore this attribute. + */ + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) + && !CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) { + if (bgp_attr_nexthop_valid(peer, attr) < 0) { + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } + } + + /* Check all mandatory well-known attributes are present */ + ret = bgp_attr_check(peer, attr, length); + if (ret < 0) + goto done; + + /* + * At this place we can see whether we got AS4_PATH and/or + * AS4_AGGREGATOR from a 16Bit peer and act accordingly. + * We can not do this before we've read all attributes because + * the as4 handling does not say whether AS4_PATH has to be sent + * after AS_PATH or not - and when AS4_AGGREGATOR will be send + * in relationship to AGGREGATOR. + * So, to be defensive, we are not relying on any order and read + * all attributes first, including these 32bit ones, and now, + * afterwards, we look what and if something is to be done for as4. + * + * It is possible to not have AS_PATH, e.g. GR EoR and sole + * MP_UNREACH_NLRI. + */ + /* actually... this doesn't ever return failure currently, but + * better safe than sorry */ + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH)) + && bgp_attr_munge_as4_attrs(peer, attr, as4_path, as4_aggregator, + &as4_aggregator_addr)) { + bgp_notify_send(peer->connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + ret = BGP_ATTR_PARSE_ERROR; + goto done; + } + + /* + * Finally do the checks on the aspath we did not do yet + * because we waited for a potentially synthesized aspath. + */ + if (attr->flag & (ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) { + ret = bgp_attr_aspath_check(peer, attr); + if (ret != BGP_ATTR_PARSE_PROCEED) + goto done; + } + + ret = BGP_ATTR_PARSE_PROCEED; +done: + + /* + * At this stage, we have done all fiddling with as4, and the + * resulting info is in attr->aggregator resp. attr->aspath so + * we can chuck as4_aggregator and as4_path alltogether in order + * to save memory + */ + /* + * unintern - it is in the hash + * The flag that we got this is still there, but that + * does not do any trouble + */ + aspath_unintern(&as4_path); + + transit = bgp_attr_get_transit(attr); + /* If we received an UPDATE with mandatory attributes, then + * the unrecognized transitive optional attribute of that + * path MUST be passed. Otherwise, it's an error, and from + * security perspective it might be very harmful if we continue + * here with the unrecognized attributes. + */ + if (ret == BGP_ATTR_PARSE_PROCEED) { + /* Finally intern unknown attribute. */ + if (transit) + bgp_attr_set_transit(attr, transit_intern(transit)); + if (attr->encap_subtlvs) + attr->encap_subtlvs = encap_intern(attr->encap_subtlvs, + ENCAP_SUBTLV_TYPE); +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + if (vnc_subtlvs) + bgp_attr_set_vnc_subtlvs( + attr, + encap_intern(vnc_subtlvs, VNC_SUBTLV_TYPE)); +#endif + } else { + if (transit) { + transit_free(transit); + bgp_attr_set_transit(attr, NULL); + } + + bgp_attr_flush_encap(attr); + }; + + /* Sanity checks */ + transit = bgp_attr_get_transit(attr); + if (transit) + assert(transit->refcnt > 0); + if (attr->encap_subtlvs) + assert(attr->encap_subtlvs->refcnt > 0); +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(attr); + + if (vnc_subtlvs) + assert(vnc_subtlvs->refcnt > 0); +#endif + + return ret; +} + +/* + * Extract the tunnel type from extended community + */ +void bgp_attr_extcom_tunnel_type(struct attr *attr, + bgp_encap_types *tunnel_type) +{ + struct ecommunity *ecom; + uint32_t i; + + if (!attr) + return; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return; + + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = pnt[0]; + sub_type = pnt[1]; + if (!(type == ECOMMUNITY_ENCODE_OPAQUE && + sub_type == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP)) + continue; + *tunnel_type = ((pnt[6] << 8) | pnt[7]); + return; + } + + return; +} + +size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, + safi_t safi, struct bpacket_attr_vec_arr *vecarr, + struct attr *attr) +{ + size_t sizep; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + afi_t nh_afi; + + /* Set extended bit always to encode the attribute length as 2 bytes */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_MP_REACH_NLRI); + sizep = stream_get_endp(s); + stream_putw(s, 0); /* Marker: Attribute length. */ + + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putw(s, pkt_afi); /* AFI */ + stream_putc(s, pkt_safi); /* SAFI */ + + /* Nexthop AFI */ + if (afi == AFI_IP + && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST + || safi == SAFI_MPLS_VPN || safi == SAFI_MULTICAST)) + nh_afi = peer_cap_enhe(peer, afi, safi) ? AFI_IP6 : AFI_IP; + else if (safi == SAFI_FLOWSPEC) + nh_afi = afi; + else + nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len); + + /* Nexthop */ + bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, attr); + switch (nh_afi) { + case AFI_IP: + switch (safi) { + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + stream_putc(s, 4); + stream_put_ipv4(s, attr->nexthop.s_addr); + break; + case SAFI_MPLS_VPN: + stream_putc(s, 12); + stream_putl(s, 0); /* RD = 0, per RFC */ + stream_putl(s, 0); + stream_put(s, &attr->mp_nexthop_global_in, 4); + break; + case SAFI_ENCAP: + case SAFI_EVPN: + stream_putc(s, 4); + stream_put(s, &attr->mp_nexthop_global_in, 4); + break; + case SAFI_FLOWSPEC: + if (attr->mp_nexthop_len == 0) + stream_putc(s, 0); /* no nexthop for flowspec */ + else { + stream_putc(s, attr->mp_nexthop_len); + stream_put_ipv4(s, attr->nexthop.s_addr); + } + break; + case SAFI_UNSPEC: + case SAFI_MAX: + assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); + break; + } + break; + case AFI_IP6: + switch (safi) { + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_EVPN: { + if (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + stream_putc(s, + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL); + stream_put(s, &attr->mp_nexthop_global, + IPV6_MAX_BYTELEN); + stream_put(s, &attr->mp_nexthop_local, + IPV6_MAX_BYTELEN); + } else { + stream_putc(s, IPV6_MAX_BYTELEN); + stream_put(s, &attr->mp_nexthop_global, + IPV6_MAX_BYTELEN); + } + } break; + case SAFI_MPLS_VPN: { + if (attr->mp_nexthop_len == + BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) + stream_putc(s, attr->mp_nexthop_len); + else + stream_putc(s, BGP_ATTR_NHLEN_VPNV6_GLOBAL); + stream_putl(s, 0); /* RD = 0, per RFC */ + stream_putl(s, 0); + stream_put(s, &attr->mp_nexthop_global, + IPV6_MAX_BYTELEN); + if (attr->mp_nexthop_len == + BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { + stream_putl(s, 0); /* RD = 0, per RFC */ + stream_putl(s, 0); + stream_put(s, &attr->mp_nexthop_local, + IPV6_MAX_BYTELEN); + } + } break; + case SAFI_ENCAP: + stream_putc(s, IPV6_MAX_BYTELEN); + stream_put(s, &attr->mp_nexthop_global, + IPV6_MAX_BYTELEN); + break; + case SAFI_FLOWSPEC: + stream_putc(s, 0); /* no nexthop for flowspec */ + break; + case SAFI_UNSPEC: + case SAFI_MAX: + assert(!"SAFI's UNSPEC or MAX being specified are a DEV ESCAPE"); + break; + } + break; + case AFI_L2VPN: + if (safi != SAFI_FLOWSPEC) + flog_err( + EC_BGP_ATTR_NH_SEND_LEN, + "Bad nexthop when sending to %s, AFI %u SAFI %u nhlen %d", + peer->host, afi, safi, attr->mp_nexthop_len); + break; + case AFI_UNSPEC: + case AFI_MAX: + assert(!"DEV ESCAPE: AFI_UNSPEC or AFI_MAX should not be used here"); + break; + } + + /* SNPA */ + stream_putc(s, 0); + return sizep; +} + +void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, + const struct prefix *p, + const struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, bool addpath_capable, + uint32_t addpath_tx_id, struct attr *attr) +{ + switch (safi) { + case SAFI_UNSPEC: + case SAFI_MAX: + assert(!"Dev escape usage of SAFI_UNSPEC or MAX"); + break; + case SAFI_MPLS_VPN: + if (addpath_capable) + stream_putl(s, addpath_tx_id); + /* Label, RD, Prefix write. */ + stream_putc(s, p->prefixlen + 88); + stream_put(s, label, BGP_LABEL_BYTES); + stream_put(s, prd->val, 8); + stream_put(s, &p->u.prefix, PSIZE(p->prefixlen)); + break; + case SAFI_EVPN: + if (afi == AFI_L2VPN) + /* EVPN prefix - contents depend on type */ + bgp_evpn_encode_prefix(s, p, prd, label, num_labels, + attr, addpath_capable, + addpath_tx_id); + else + assert(!"Add encoding bits here for other AFI's"); + break; + case SAFI_LABELED_UNICAST: + /* Prefix write with label. */ + stream_put_labeled_prefix(s, p, label, addpath_capable, + addpath_tx_id); + break; + case SAFI_FLOWSPEC: + stream_putc(s, p->u.prefix_flowspec.prefixlen); + stream_put(s, (const void *)p->u.prefix_flowspec.ptr, + p->u.prefix_flowspec.prefixlen); + break; + + case SAFI_UNICAST: + case SAFI_MULTICAST: + stream_put_prefix_addpath(s, p, addpath_capable, addpath_tx_id); + break; + case SAFI_ENCAP: + assert(!"Please add proper encoding of SAFI_ENCAP"); + break; + } +} + +size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, + const struct prefix *p) +{ + int size = PSIZE(p->prefixlen); + + switch (safi) { + case SAFI_UNSPEC: + case SAFI_MAX: + assert(!"Attempting to figure size for a SAFI_UNSPEC/SAFI_MAX this is a DEV ESCAPE"); + break; + case SAFI_UNICAST: + case SAFI_MULTICAST: + break; + case SAFI_MPLS_VPN: + size += 88; + break; + case SAFI_ENCAP: + /* This has to be wrong, but I don't know what to put here */ + assert(!"Do we try to use this?"); + break; + case SAFI_LABELED_UNICAST: + size += BGP_LABEL_BYTES; + break; + case SAFI_EVPN: + /* + * TODO: Maximum possible for type-2, type-3 and type-5 + */ + if (afi == AFI_L2VPN) + size += 232; + else + assert(!"Attempting to figure size for SAFI_EVPN and !AFI_L2VPN and FRR will not have the proper values"); + break; + case SAFI_FLOWSPEC: + size = ((struct prefix_fs *)p)->prefix.prefixlen; + break; + } + + return size; +} + +/* + * Encodes the tunnel encapsulation attribute, + * and with ENABLE_BGP_VNC the VNC attribute which uses + * almost the same TLV format + */ +static void bgp_packet_mpattr_tea(struct bgp *bgp, struct peer *peer, + struct stream *s, struct attr *attr, + uint8_t attrtype) +{ + unsigned int attrlenfield = 0; + unsigned int attrhdrlen = 0; + struct bgp_attr_encap_subtlv *subtlvs; + struct bgp_attr_encap_subtlv *st; + const char *attrname; + + if (!attr || (attrtype == BGP_ATTR_ENCAP + && (!attr->encap_tunneltype + || attr->encap_tunneltype == BGP_ENCAP_TYPE_MPLS))) + return; + + switch (attrtype) { + case BGP_ATTR_ENCAP: + attrname = "Tunnel Encap"; + subtlvs = attr->encap_subtlvs; + if (subtlvs == NULL) /* nothing to do */ + return; + /* + * The tunnel encap attr has an "outer" tlv. + * T = tunneltype, + * L = total length of subtlvs, + * V = concatenated subtlvs. + */ + attrlenfield = 2 + 2; /* T + L */ + attrhdrlen = 1 + 1; /* subTLV T + L */ + break; + +#ifdef ENABLE_BGP_VNC_ATTR + case BGP_ATTR_VNC: + attrname = "VNC"; + subtlvs = bgp_attr_get_vnc_subtlvs(attr); + if (subtlvs == NULL) /* nothing to do */ + return; + attrlenfield = 0; /* no outer T + L */ + attrhdrlen = 2 + 2; /* subTLV T + L */ + break; +#endif + + default: + assert(0); + } + + /* compute attr length */ + for (st = subtlvs; st; st = st->next) { + attrlenfield += (attrhdrlen + st->length); + } + + if (attrlenfield > 0xffff) { + zlog_info("%s attribute is too long (length=%d), can't send it", + attrname, attrlenfield); + return; + } + + if (attrlenfield > 0xff) { + /* 2-octet length field */ + stream_putc(s, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, attrtype); + stream_putw(s, attrlenfield & 0xffff); + } else { + /* 1-octet length field */ + stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, attrtype); + stream_putc(s, attrlenfield & 0xff); + } + + if (attrtype == BGP_ATTR_ENCAP) { + /* write outer T+L */ + stream_putw(s, attr->encap_tunneltype); + stream_putw(s, attrlenfield - 4); + } + + /* write each sub-tlv */ + for (st = subtlvs; st; st = st->next) { + if (attrtype == BGP_ATTR_ENCAP) { + stream_putc(s, st->type); + stream_putc(s, st->length); +#ifdef ENABLE_BGP_VNC + } else { + stream_putw(s, st->type); + stream_putw(s, st->length); +#endif + } + stream_put(s, st->value, st->length); + } +} + +void bgp_packet_mpattr_end(struct stream *s, size_t sizep) +{ + /* Set MP attribute length. Don't count the (2) bytes used to encode + the attr length */ + stream_putw_at(s, sizep, (stream_get_endp(s) - sizep) - 2); +} + +static bool bgp_append_local_as(struct peer *peer, afi_t afi, safi_t safi) +{ + if (!BGP_AS_IS_PRIVATE(peer->local_as) + || (BGP_AS_IS_PRIVATE(peer->local_as) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE))) + return true; + return false; +} + +static void bgp_packet_ecommunity_attribute(struct stream *s, struct peer *peer, + struct ecommunity *ecomm, + bool transparent, int attribute) +{ + if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED || + peer->sub_sort == BGP_PEER_EBGP_OAD || transparent) { + if (ecomm->size * ecomm->unit_size > 255) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | + BGP_ATTR_FLAG_TRANS | + BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, attribute); + stream_putw(s, ecomm->size * ecomm->unit_size); + } else { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | + BGP_ATTR_FLAG_TRANS); + stream_putc(s, attribute); + stream_putc(s, ecomm->size * ecomm->unit_size); + } + stream_put(s, ecomm->val, ecomm->size * ecomm->unit_size); + } else { + uint8_t *pnt; + int tbit; + int ecom_tr_size = 0; + uint32_t i; + + for (i = 0; i < ecomm->size; i++) { + pnt = ecomm->val + (i * ecomm->unit_size); + tbit = *pnt; + + if (CHECK_FLAG(tbit, ECOMMUNITY_FLAG_NON_TRANSITIVE)) + continue; + + ecom_tr_size++; + } + + if (ecom_tr_size) { + if (ecom_tr_size * ecomm->unit_size > 255) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | + BGP_ATTR_FLAG_TRANS | + BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, attribute); + stream_putw(s, ecom_tr_size * ecomm->unit_size); + } else { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | + BGP_ATTR_FLAG_TRANS); + stream_putc(s, attribute); + stream_putc(s, ecom_tr_size * ecomm->unit_size); + } + + for (i = 0; i < ecomm->size; i++) { + pnt = ecomm->val + (i * ecomm->unit_size); + tbit = *pnt; + + if (CHECK_FLAG(tbit, + ECOMMUNITY_FLAG_NON_TRANSITIVE)) + continue; + + stream_put(s, pnt, ecomm->unit_size); + } + } + } +} + +/* Make attribute packet. */ +bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, + struct stream *s, struct attr *attr, + struct bpacket_attr_vec_arr *vecarr, + struct prefix *p, afi_t afi, safi_t safi, + struct peer *from, struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, uint32_t addpath_tx_id, + struct bgp_path_info *bpi) +{ + size_t cp; + size_t aspath_sizep; + struct aspath *aspath; + int send_as4_path = 0; + int send_as4_aggregator = 0; + bool use32bit = CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) + && CHECK_FLAG(peer->cap, PEER_CAP_AS4_ADV); + + if (!bgp) + bgp = peer->bgp; + + /* Remember current pointer. */ + cp = stream_get_endp(s); + + if (p + && !((afi == AFI_IP && safi == SAFI_UNICAST) + && !peer_cap_enhe(peer, afi, safi))) { + size_t mpattrlen_pos = 0; + + mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, + vecarr, attr); + bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, + num_labels, addpath_capable, + addpath_tx_id, attr); + bgp_packet_mpattr_end(s, mpattrlen_pos); + } + + /* Origin attribute. */ + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_ORIGIN); + stream_putc(s, 1); + stream_putc(s, attr->origin); + + /* AS path attribute. */ + + /* If remote-peer is EBGP */ + if (peer->sort == BGP_PEER_EBGP + && (!CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_AS_PATH_UNCHANGED) + || attr->aspath->segments == NULL) + && (!CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT))) { + aspath = aspath_dup(attr->aspath); + + /* Even though we may not be configured for confederations we + * may have + * RXed an AS_PATH with AS_CONFED_SEQUENCE or AS_CONFED_SET */ + aspath = aspath_delete_confed_seq(aspath); + + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + /* A confed member, so we need to do the + * AS_CONFED_SEQUENCE thing if it's outside a common + * administration. + * Configured confederation peers MUST be validated + * under BGP_PEER_CONFED, but if we have configured + * remote-as as AS_EXTERNAL, we need to check again + * if the peer belongs to us. + */ + if (bgp_confederation_peers_check(bgp, peer->as)) { + aspath = aspath_add_confed_seq(aspath, + peer->local_as); + } else { + /* Stuff our path CONFED_ID on the front */ + aspath = aspath_add_seq(aspath, bgp->confed_id); + } + } else { + if (peer->change_local_as) { + /* If replace-as is specified, we only use the + change_local_as when + advertising routes. */ + if (!CHECK_FLAG(peer->flags, + PEER_FLAG_LOCAL_AS_REPLACE_AS)) + if (bgp_append_local_as(peer, afi, + safi)) + aspath = aspath_add_seq( + aspath, peer->local_as); + aspath = aspath_add_seq(aspath, + peer->change_local_as); + } else { + aspath = aspath_add_seq(aspath, peer->local_as); + } + } + } else if (peer->sort == BGP_PEER_CONFED) { + /* A confed member, so we need to do the AS_CONFED_SEQUENCE + * thing */ + aspath = aspath_dup(attr->aspath); + aspath = aspath_add_confed_seq(aspath, peer->local_as); + } else + aspath = attr->aspath; + + /* If peer is not AS4 capable, then: + * - send the created AS_PATH out as AS4_PATH (optional, transitive), + * but ensure that no AS_CONFED_SEQUENCE and AS_CONFED_SET path + * segment + * types are in it (i.e. exclude them if they are there) + * AND do this only if there is at least one asnum > 65535 in the + * path! + * - send an AS_PATH out, but put 16Bit ASnums in it, not 32bit, and + * change + * all ASnums > 65535 to BGP_AS_TRANS + */ + + stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_AS_PATH); + aspath_sizep = stream_get_endp(s); + stream_putw(s, 0); + stream_putw_at(s, aspath_sizep, aspath_put(s, aspath, use32bit)); + + /* OLD session may need NEW_AS_PATH sent, if there are 4-byte ASNs + * in the path + */ + if (!use32bit && aspath_has_as4(aspath)) + send_as4_path = + 1; /* we'll do this later, at the correct place */ + + /* Nexthop attribute. */ + if (afi == AFI_IP && safi == SAFI_UNICAST + && !peer_cap_enhe(peer, afi, safi)) { + afi_t nh_afi = BGP_NEXTHOP_AFI_FROM_NHLEN(attr->mp_nexthop_len); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_NEXT_HOP); + bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, + attr); + stream_putc(s, 4); + stream_put_ipv4(s, attr->nexthop.s_addr); + } else if (peer_cap_enhe(from, afi, safi) + || (nh_afi == AFI_IP6)) { + /* + * Likely this is the case when an IPv4 prefix was + * received with Extended Next-hop capability in this + * or another vrf and is now being advertised to + * non-ENHE peers. Since peer_cap_enhe only checks + * peers in this vrf, also check the nh_afi to catch + * the case where the originator was in another vrf. + * Setting the mandatory (ipv4) next-hop attribute here + * to enable implicit next-hop self with correct A-F + * (ipv4 address family). + */ + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_NEXT_HOP); + bpacket_attr_vec_arr_set_vec(vecarr, BGP_ATTR_VEC_NH, s, + NULL); + stream_putc(s, 4); + stream_put_ipv4(s, 0); + } + } + + /* MED attribute. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC) + || bgp->maxmed_active) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_MULTI_EXIT_DISC); + stream_putc(s, 4); + stream_putl(s, (bgp->maxmed_active ? bgp->maxmed_value + : attr->med)); + } + + /* Local preference. */ + if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED || + peer->sub_sort == BGP_PEER_EBGP_OAD) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_LOCAL_PREF); + stream_putc(s, 4); + stream_putl(s, attr->local_pref); + } + + /* Atomic aggregate. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_ATOMIC_AGGREGATE); + stream_putc(s, 0); + } + + /* Aggregator. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)) { + /* Common to BGP_ATTR_AGGREGATOR, regardless of ASN size */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_AGGREGATOR); + + if (use32bit) { + /* AS4 capable peer */ + stream_putc(s, 8); + stream_putl(s, attr->aggregator_as); + } else { + /* 2-byte AS peer */ + stream_putc(s, 6); + + /* Is ASN representable in 2-bytes? Or must AS_TRANS be + * used? */ + if (attr->aggregator_as > UINT16_MAX) { + stream_putw(s, BGP_AS_TRANS); + + /* we have to send AS4_AGGREGATOR, too. + * we'll do that later in order to send + * attributes in ascending + * order. + */ + send_as4_aggregator = 1; + } else + stream_putw(s, (uint16_t)attr->aggregator_as); + } + stream_put_ipv4(s, attr->aggregator_addr.s_addr); + } + + /* Community attribute. */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) + && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES))) { + struct community *comm = NULL; + + comm = bgp_attr_get_community(attr); + if (comm->size * 4 > 255) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_COMMUNITIES); + stream_putw(s, comm->size * 4); + } else { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_COMMUNITIES); + stream_putc(s, comm->size * 4); + } + stream_put(s, comm->val, comm->size * 4); + } + + /* + * Large Community attribute. + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY) + && (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES))) { + if (lcom_length(bgp_attr_get_lcommunity(attr)) > 255) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); + stream_putw(s, + lcom_length(bgp_attr_get_lcommunity(attr))); + } else { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); + stream_putc(s, + lcom_length(bgp_attr_get_lcommunity(attr))); + } + stream_put(s, bgp_attr_get_lcommunity(attr)->val, + lcom_length(bgp_attr_get_lcommunity(attr))); + } + + /* Route Reflector. */ + if (peer->sort == BGP_PEER_IBGP && from + && from->sort == BGP_PEER_IBGP) { + struct cluster_list *cluster = bgp_attr_get_cluster(attr); + + /* Originator ID. */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_ORIGINATOR_ID); + stream_putc(s, 4); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + stream_put_in_addr(s, &attr->originator_id); + else + stream_put_in_addr(s, &from->remote_id); + + /* Cluster list. */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_CLUSTER_LIST); + + if (cluster) { + stream_putc(s, cluster->length + 4); + /* If this peer configuration's parent BGP has + * cluster_id. */ + if (bgp->config & BGP_CONFIG_CLUSTER_ID) + stream_put_in_addr(s, &bgp->cluster_id); + else + stream_put_in_addr(s, &bgp->router_id); + stream_put(s, cluster->list, cluster->length); + } else { + stream_putc(s, 4); + /* If this peer configuration's parent BGP has + * cluster_id. */ + if (bgp->config & BGP_CONFIG_CLUSTER_ID) + stream_put_in_addr(s, &bgp->cluster_id); + else + stream_put_in_addr(s, &bgp->router_id); + } + } + + /* Extended IPv6/Communities attributes. */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_EXT_COMMUNITY)) { + bool transparent = CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT) && + from && + CHECK_FLAG(from->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT); + + if (CHECK_FLAG(attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + bgp_packet_ecommunity_attribute(s, peer, ecomm, + transparent, + BGP_ATTR_EXT_COMMUNITIES); + } + + if (CHECK_FLAG(attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_IPV6_EXT_COMMUNITIES))) { + struct ecommunity *ecomm = + bgp_attr_get_ipv6_ecommunity(attr); + + bgp_packet_ecommunity_attribute(s, peer, ecomm, + transparent, + BGP_ATTR_IPV6_EXT_COMMUNITIES); + } + } + + /* Label index attribute. */ + if (safi == SAFI_LABELED_UNICAST) { + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) { + uint32_t label_index; + + label_index = attr->label_index; + + if (label_index != BGP_INVALID_LABEL_INDEX) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_PREFIX_SID); + stream_putc(s, 10); + stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX); + stream_putw(s, + BGP_PREFIX_SID_LABEL_INDEX_LENGTH); + stream_putc(s, 0); // reserved + stream_putw(s, 0); // flags + stream_putl(s, label_index); + } + } + } + + /* SRv6 Service Information Attribute. */ + if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_MPLS_VPN) { + if (attr->srv6_l3vpn) { + uint8_t subtlv_len = + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH + + BGP_ATTR_MIN_LEN + + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO_LENGTH; + uint8_t tlv_len = subtlv_len + BGP_ATTR_MIN_LEN + 1; + uint8_t attr_len = tlv_len + BGP_ATTR_MIN_LEN; + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_PREFIX_SID); + stream_putc(s, attr_len); + stream_putc(s, BGP_PREFIX_SID_SRV6_L3_SERVICE); + stream_putw(s, tlv_len); + stream_putc(s, 0); /* reserved */ + stream_putc(s, BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO); + stream_putw(s, subtlv_len); + stream_putc(s, 0); /* reserved */ + stream_put(s, &attr->srv6_l3vpn->sid, + sizeof(attr->srv6_l3vpn->sid)); /* sid */ + stream_putc(s, 0); /* sid_flags */ + stream_putw(s, + attr->srv6_l3vpn + ->endpoint_behavior); /* endpoint */ + stream_putc(s, 0); /* reserved */ + stream_putc( + s, + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE); + stream_putw( + s, + BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH); + stream_putc(s, attr->srv6_l3vpn->loc_block_len); + stream_putc(s, attr->srv6_l3vpn->loc_node_len); + stream_putc(s, attr->srv6_l3vpn->func_len); + stream_putc(s, attr->srv6_l3vpn->arg_len); + stream_putc(s, attr->srv6_l3vpn->transposition_len); + stream_putc(s, attr->srv6_l3vpn->transposition_offset); + } else if (attr->srv6_vpn) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_PREFIX_SID); + stream_putc(s, 22); /* tlv len */ + stream_putc(s, BGP_PREFIX_SID_VPN_SID); + stream_putw(s, 0x13); /* tlv len */ + stream_putc(s, 0x00); /* reserved */ + stream_putc(s, 0x01); /* sid_type */ + stream_putc(s, 0x00); /* sif_flags */ + stream_put(s, &attr->srv6_vpn->sid, + sizeof(attr->srv6_vpn->sid)); /* sid */ + } + } + + if (send_as4_path) { + /* If the peer is NOT As4 capable, AND */ + /* there are ASnums > 65535 in path THEN + * give out AS4_PATH */ + + /* Get rid of all AS_CONFED_SEQUENCE and AS_CONFED_SET + * path segments! + * Hm, I wonder... confederation things *should* only be at + * the beginning of an aspath, right? Then we should use + * aspath_delete_confed_seq for this, because it is already + * there! (JK) + * Folks, talk to me: what is reasonable here!? + */ + + /* Make sure dup aspath before the modification */ + if (aspath == attr->aspath) + aspath = aspath_dup(attr->aspath); + aspath = aspath_delete_confed_seq(aspath); + + stream_putc(s, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_AS4_PATH); + aspath_sizep = stream_get_endp(s); + stream_putw(s, 0); + stream_putw_at(s, aspath_sizep, aspath_put(s, aspath, 1)); + } + + if (aspath != attr->aspath) + aspath_free(aspath); + + if (send_as4_aggregator) { + /* send AS4_AGGREGATOR, at this place */ + /* this section of code moved here in order to ensure the + * correct + * *ascending* order of attributes + */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_AS4_AGGREGATOR); + stream_putc(s, 8); + stream_putl(s, attr->aggregator_as); + stream_put_ipv4(s, attr->aggregator_addr.s_addr); + } + + if (((afi == AFI_IP || afi == AFI_IP6) + && (safi == SAFI_ENCAP || safi == SAFI_MPLS_VPN)) + || (afi == AFI_L2VPN && safi == SAFI_EVPN)) { + /* Tunnel Encap attribute */ + bgp_packet_mpattr_tea(bgp, peer, s, attr, BGP_ATTR_ENCAP); + +#ifdef ENABLE_BGP_VNC_ATTR + /* VNC attribute */ + bgp_packet_mpattr_tea(bgp, peer, s, attr, BGP_ATTR_VNC); +#endif + } + + /* PMSI Tunnel */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_PMSI_TUNNEL); + stream_putc(s, 9); // Length + stream_putc(s, 0); // Flags + stream_putc(s, bgp_attr_get_pmsi_tnl_type(attr)); + stream_put(s, &(attr->label), + BGP_LABEL_BYTES); // MPLS Label / VXLAN VNI + stream_put_ipv4(s, attr->nexthop.s_addr); + // Unicast tunnel endpoint IP address + } + + /* OTC */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_OTC); + stream_putc(s, 4); + stream_putl(s, attr->otc); + } + + /* AIGP */ + if (bpi && attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AIGP) && + (CHECK_FLAG(peer->flags, PEER_FLAG_AIGP) || + peer->sub_sort == BGP_PEER_EBGP_OAD || + peer->sort != BGP_PEER_EBGP)) { + /* At the moment only AIGP Metric TLV exists for AIGP + * attribute. If more comes in, do not forget to update + * attr_len variable to include new ones. + */ + uint8_t attr_len = BGP_AIGP_TLV_METRIC_LEN; + + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_AIGP); + stream_putc(s, attr_len); + stream_put_bgp_aigp_tlv_metric(s, bpi); + } + + /* Unknown transit attribute. */ + struct transit *transit = bgp_attr_get_transit(attr); + + if (transit) + stream_put(s, transit->val, transit->length); + + /* Return total size of attribute. */ + return stream_get_endp(s) - cp; +} + +size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, safi_t safi) +{ + unsigned long attrlen_pnt; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + + /* Set extended bit always to encode the attribute length as 2 bytes */ + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); + + attrlen_pnt = stream_get_endp(s); + stream_putw(s, 0); /* Length of this attribute. */ + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + + return attrlen_pnt; +} + +void bgp_packet_mpunreach_prefix(struct stream *s, const struct prefix *p, + afi_t afi, safi_t safi, + const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, uint32_t addpath_tx_id, + struct attr *attr) +{ + uint8_t wlabel[4] = {0x80, 0x00, 0x00}; + + if (safi == SAFI_LABELED_UNICAST) { + label = (mpls_label_t *)wlabel; + num_labels = 1; + } + + bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, num_labels, + addpath_capable, addpath_tx_id, attr); +} + +void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt) +{ + bgp_packet_mpattr_end(s, attrlen_pnt); +} + +/* Initialization of attribute. */ +void bgp_attr_init(void) +{ + aspath_init(); + attrhash_init(); + community_init(); + ecommunity_init(); + lcommunity_init(); + cluster_init(); + transit_init(); + encap_init(); + srv6_init(); +} + +void bgp_attr_finish(void) +{ + aspath_finish(); + attrhash_finish(); + community_finish(); + ecommunity_finish(); + lcommunity_finish(); + cluster_finish(); + transit_finish(); + encap_finish(); + srv6_finish(); +} + +/* Make attribute packet. */ +void bgp_dump_routes_attr(struct stream *s, struct bgp_path_info *bpi, + const struct prefix *prefix) +{ + unsigned long cp; + unsigned long len; + size_t aspath_lenp; + struct aspath *aspath; + bool addpath_capable = false; + uint32_t addpath_tx_id = 0; + struct attr *attr = bpi->attr; + + /* Remember current pointer. */ + cp = stream_get_endp(s); + + /* Place holder of length. */ + stream_putw(s, 0); + + /* Origin attribute. */ + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_ORIGIN); + stream_putc(s, 1); + stream_putc(s, attr->origin); + + aspath = attr->aspath; + + stream_putc(s, BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_AS_PATH); + aspath_lenp = stream_get_endp(s); + stream_putw(s, 0); + + stream_putw_at(s, aspath_lenp, aspath_put(s, aspath, 1)); + + /* Nexthop attribute. */ + /* If it's an IPv6 prefix, don't dump the IPv4 nexthop to save space */ + if (prefix != NULL && prefix->family != AF_INET6) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_NEXT_HOP); + stream_putc(s, 4); + stream_put_ipv4(s, attr->nexthop.s_addr); + } + + /* MED attribute. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_MULTI_EXIT_DISC); + stream_putc(s, 4); + stream_putl(s, attr->med); + } + + /* Local preference. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_LOCAL_PREF); + stream_putc(s, 4); + stream_putl(s, attr->local_pref); + } + + /* Atomic aggregate. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { + stream_putc(s, BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_ATOMIC_AGGREGATE); + stream_putc(s, 0); + } + + /* Aggregator. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR)) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_AGGREGATOR); + stream_putc(s, 8); + stream_putl(s, attr->aggregator_as); + stream_put_ipv4(s, attr->aggregator_addr.s_addr); + } + + /* Community attribute. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + struct community *comm = NULL; + + comm = bgp_attr_get_community(attr); + if (comm->size * 4 > 255) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_COMMUNITIES); + stream_putw(s, comm->size * 4); + } else { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_COMMUNITIES); + stream_putc(s, comm->size * 4); + } + stream_put(s, comm->val, comm->size * 4); + } + + /* Large Community attribute. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { + if (lcom_length(bgp_attr_get_lcommunity(attr)) > 255) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS + | BGP_ATTR_FLAG_EXTLEN); + stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); + stream_putw(s, + lcom_length(bgp_attr_get_lcommunity(attr))); + } else { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_LARGE_COMMUNITIES); + stream_putc(s, + lcom_length(bgp_attr_get_lcommunity(attr))); + } + + stream_put(s, bgp_attr_get_lcommunity(attr)->val, + lcom_length(bgp_attr_get_lcommunity(attr))); + } + + /* Add a MP_NLRI attribute to dump the IPv6 next hop */ + if (prefix != NULL && prefix->family == AF_INET6 + && (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL + || attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL)) { + int sizep; + + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_MP_REACH_NLRI); + sizep = stream_get_endp(s); + + /* MP header */ + stream_putc(s, 0); /* Marker: Attribute length. */ + stream_putw(s, AFI_IP6); /* AFI */ + stream_putc(s, SAFI_UNICAST); /* SAFI */ + + /* Next hop */ + stream_putc(s, attr->mp_nexthop_len); + stream_put(s, &attr->mp_nexthop_global, IPV6_MAX_BYTELEN); + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + stream_put(s, &attr->mp_nexthop_local, + IPV6_MAX_BYTELEN); + + /* SNPA */ + stream_putc(s, 0); + + /* Prefix */ + stream_put_prefix_addpath(s, prefix, addpath_capable, + addpath_tx_id); + + /* Set MP attribute length. */ + stream_putc_at(s, sizep, (stream_get_endp(s) - sizep) - 1); + } + + /* Prefix SID */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) { + if (attr->label_index != BGP_INVALID_LABEL_INDEX) { + stream_putc(s, + BGP_ATTR_FLAG_OPTIONAL + | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_PREFIX_SID); + stream_putc(s, 10); + stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX); + stream_putc(s, BGP_PREFIX_SID_LABEL_INDEX_LENGTH); + stream_putc(s, 0); // reserved + stream_putw(s, 0); // flags + stream_putl(s, attr->label_index); + } + } + + /* OTC */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_OTC); + stream_putc(s, 4); + stream_putl(s, attr->otc); + } + + /* AIGP */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AIGP)) { + /* At the moment only AIGP Metric TLV exists for AIGP + * attribute. If more comes in, do not forget to update + * attr_len variable to include new ones. + */ + uint8_t attr_len = BGP_AIGP_TLV_METRIC_LEN; + + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); + stream_putc(s, BGP_ATTR_AIGP); + stream_putc(s, attr_len); + stream_put_bgp_aigp_tlv_metric(s, bpi); + } + + /* Return total size of attribute. */ + len = stream_get_endp(s) - cp - 2; + stream_putw_at(s, cp, len); +} + +void bgp_path_attribute_discard_vty(struct vty *vty, struct peer *peer, + const char *discard_attrs, bool set) +{ + int i, num_attributes; + char **attributes; + afi_t afi; + safi_t safi; + + + /* If `no` command specified without arbitrary attributes, + * then flush all. + */ + if (!discard_attrs) { + for (i = 1; i <= BGP_ATTR_MAX; i++) + peer->discard_attrs[i] = false; + goto discard_soft_clear; + } + + if (discard_attrs) { + frrstr_split(discard_attrs, " ", &attributes, &num_attributes); + + if (set) + for (i = 1; i <= BGP_ATTR_MAX; i++) + peer->discard_attrs[i] = false; + + for (i = 0; i < num_attributes; i++) { + uint8_t attr_num = strtoul(attributes[i], NULL, 10); + + XFREE(MTYPE_TMP, attributes[i]); + + /* Some of the attributes, just can't be ignored. */ + if (attr_num == BGP_ATTR_ORIGIN || + attr_num == BGP_ATTR_AS_PATH || + attr_num == BGP_ATTR_NEXT_HOP || + attr_num == BGP_ATTR_MULTI_EXIT_DISC || + attr_num == BGP_ATTR_MP_REACH_NLRI || + attr_num == BGP_ATTR_MP_UNREACH_NLRI || + attr_num == BGP_ATTR_EXT_COMMUNITIES) { + vty_out(vty, + "%% Can't discard path-attribute %s, ignoring.\n", + lookup_msg(attr_str, attr_num, NULL)); + continue; + } + + /* Ignore local-pref, originator-id, cluster-list only + * for eBGP. + */ + if (peer->sort != BGP_PEER_EBGP && + (attr_num == BGP_ATTR_LOCAL_PREF || + attr_num == BGP_ATTR_ORIGINATOR_ID || + attr_num == BGP_ATTR_CLUSTER_LIST)) { + vty_out(vty, + "%% Can discard path-attribute %s only for eBGP, ignoring.\n", + lookup_msg(attr_str, attr_num, NULL)); + continue; + } + + peer->discard_attrs[attr_num] = set; + } + XFREE(MTYPE_TMP, attributes); + discard_soft_clear: + /* Configuring path attributes to be discarded will trigger + * an inbound Route Refresh to ensure that the routing table + * is up to date. + */ + FOREACH_AFI_SAFI (afi, safi) + peer_clear_soft(peer, afi, safi, BGP_CLEAR_SOFT_IN); + } +} + +void bgp_path_attribute_withdraw_vty(struct vty *vty, struct peer *peer, + const char *withdraw_attrs, bool set) +{ + int i, num_attributes; + char **attributes; + afi_t afi; + safi_t safi; + + /* If `no` command specified without arbitrary attributes, + * then flush all. + */ + if (!withdraw_attrs) { + for (i = 1; i <= BGP_ATTR_MAX; i++) + peer->withdraw_attrs[i] = false; + goto withdraw_soft_clear; + } + + if (withdraw_attrs) { + frrstr_split(withdraw_attrs, " ", &attributes, &num_attributes); + + if (set) + for (i = 1; i <= BGP_ATTR_MAX; i++) + peer->withdraw_attrs[i] = false; + + for (i = 0; i < num_attributes; i++) { + uint8_t attr_num = strtoul(attributes[i], NULL, 10); + + XFREE(MTYPE_TMP, attributes[i]); + + /* Some of the attributes, just can't be ignored. */ + if (attr_num == BGP_ATTR_ORIGIN || + attr_num == BGP_ATTR_AS_PATH || + attr_num == BGP_ATTR_NEXT_HOP || + attr_num == BGP_ATTR_MULTI_EXIT_DISC || + attr_num == BGP_ATTR_MP_REACH_NLRI || + attr_num == BGP_ATTR_MP_UNREACH_NLRI || + attr_num == BGP_ATTR_EXT_COMMUNITIES) { + vty_out(vty, + "%% Can't treat-as-withdraw path-attribute %s, ignoring.\n", + lookup_msg(attr_str, attr_num, NULL)); + continue; + } + + /* Ignore local-pref, originator-id, cluster-list only + * for eBGP. + */ + if (peer->sort != BGP_PEER_EBGP && + (attr_num == BGP_ATTR_LOCAL_PREF || + attr_num == BGP_ATTR_ORIGINATOR_ID || + attr_num == BGP_ATTR_CLUSTER_LIST)) { + vty_out(vty, + "%% Can treat-as-withdraw path-attribute %s only for eBGP, ignoring.\n", + lookup_msg(attr_str, attr_num, NULL)); + continue; + } + + peer->withdraw_attrs[attr_num] = set; + } + XFREE(MTYPE_TMP, attributes); + withdraw_soft_clear: + /* Configuring path attributes to be treated as withdraw will + * trigger + * an inbound Route Refresh to ensure that the routing table + * is up to date. + */ + FOREACH_AFI_SAFI (afi, safi) + peer_clear_soft(peer, afi, safi, BGP_CLEAR_SOFT_IN); + } +} + +enum bgp_attr_parse_ret bgp_attr_ignore(struct peer *peer, uint8_t type) +{ + bool discard = peer->discard_attrs[type]; + bool withdraw = peer->withdraw_attrs[type]; + + if (bgp_debug_update(peer, NULL, NULL, 1) && (discard || withdraw)) + zlog_debug("%pBP: Ignoring attribute %s (%s)", peer, + lookup_msg(attr_str, type, NULL), + withdraw ? "treat-as-withdraw" : "discard"); + + return withdraw ? BGP_ATTR_PARSE_WITHDRAW : BGP_ATTR_PARSE_PROCEED; +} + +bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo) +{ + struct attr *attr = pi->attr; + struct ecommunity *ecom; + + if (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + return false; + + ecom = attr->ecommunity; + if (!ecom || !ecom->size) + return false; + + return soo_in_ecom(ecom, soo); +} diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h new file mode 100644 index 0000000..f353e76 --- /dev/null +++ b/bgpd/bgp_attr.h @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP attributes. + * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ATTR_H +#define _QUAGGA_BGP_ATTR_H + +#include "mpls.h" +#include "bgp_attr_evpn.h" +#include "bgpd/bgp_encap_types.h" +#include "srte.h" + +/* Simple bit mapping. */ +#define BITMAP_NBBY 8 + +#define SET_BITMAP(MAP, NUM) \ + SET_FLAG(MAP[(NUM) / BITMAP_NBBY], 1 << ((NUM) % BITMAP_NBBY)) + +#define CHECK_BITMAP(MAP, NUM) \ + CHECK_FLAG(MAP[(NUM) / BITMAP_NBBY], 1 << ((NUM) % BITMAP_NBBY)) + +#define BGP_MED_MAX UINT32_MAX + +/* BGP Attribute type range. */ +#define BGP_ATTR_TYPE_RANGE 256 +#define BGP_ATTR_BITMAP_SIZE (BGP_ATTR_TYPE_RANGE / BITMAP_NBBY) + +/* BGP Attribute flags. */ +#define BGP_ATTR_FLAG_OPTIONAL 0x80 /* Attribute is optional. */ +#define BGP_ATTR_FLAG_TRANS 0x40 /* Attribute is transitive. */ +#define BGP_ATTR_FLAG_PARTIAL 0x20 /* Attribute is partial. */ +#define BGP_ATTR_FLAG_EXTLEN 0x10 /* Extended length flag. */ + +/* BGP attribute header must bigger than 2. */ +#define BGP_ATTR_MIN_LEN 3 /* Attribute flag, type length. */ +#define BGP_ATTR_DEFAULT_WEIGHT 32768 + +/* Valid lengths for mp_nexthop_len */ +#define BGP_ATTR_NHLEN_IPV4 IPV4_MAX_BYTELEN +#define BGP_ATTR_NHLEN_VPNV4 8+IPV4_MAX_BYTELEN +#define BGP_ATTR_NHLEN_IPV6_GLOBAL IPV6_MAX_BYTELEN +#define BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL (IPV6_MAX_BYTELEN * 2) +#define BGP_ATTR_NHLEN_VPNV6_GLOBAL 8+IPV6_MAX_BYTELEN +#define BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL ((8+IPV6_MAX_BYTELEN) * 2) + +/* Prefix SID types */ +#define BGP_PREFIX_SID_LABEL_INDEX 1 +#define BGP_PREFIX_SID_IPV6 2 +#define BGP_PREFIX_SID_ORIGINATOR_SRGB 3 +#define BGP_PREFIX_SID_VPN_SID 4 +#define BGP_PREFIX_SID_SRV6_L3_SERVICE 5 +#define BGP_PREFIX_SID_SRV6_L2_SERVICE 6 + +#define BGP_PREFIX_SID_LABEL_INDEX_LENGTH 7 +#define BGP_PREFIX_SID_IPV6_LENGTH 19 +#define BGP_PREFIX_SID_ORIGINATOR_SRGB_LENGTH 6 +#define BGP_PREFIX_SID_VPN_SID_LENGTH 19 + +/* SRv6 Service Sub-TLV types */ +#define BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO 1 +#define BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_INFO_LENGTH 21 + +/* SRv6 Service Data Sub-Sub-TLV types */ +#define BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE 1 +#define BGP_PREFIX_SID_SRV6_L3_SERVICE_SID_STRUCTURE_LENGTH 6 + +#define BGP_ATTR_NH_AFI(afi, attr) \ + ((afi != AFI_L2VPN) ? afi : \ + ((attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) ? AFI_IP : AFI_IP6)) + +/* PMSI tunnel types (RFC 6514) */ + +struct bgp_attr_encap_subtlv { + struct bgp_attr_encap_subtlv *next; /* for chaining */ + /* Reference count of this attribute. */ + unsigned long refcnt; + uint16_t type; + uint16_t length; + uint8_t value[0]; /* will be extended */ +}; + +#ifdef ENABLE_BGP_VNC +/* + * old rfp<->rfapi representation + */ +struct bgp_tea_options { + struct bgp_tea_options *next; + uint8_t options_count; + uint16_t options_length; /* each TLV may be 256 in length */ + uint8_t type; + uint8_t length; + void *value; /* pointer to data */ +}; + +#endif + +enum pta_type { + PMSI_TNLTYPE_NO_INFO = 0, + PMSI_TNLTYPE_RSVP_TE_P2MP, + PMSI_TNLTYPE_MLDP_P2MP, + PMSI_TNLTYPE_PIM_SSM, + PMSI_TNLTYPE_PIM_SM, + PMSI_TNLTYPE_PIM_BIDIR, + PMSI_TNLTYPE_INGR_REPL, + PMSI_TNLTYPE_MLDP_MP2MP, + PMSI_TNLTYPE_MAX = PMSI_TNLTYPE_MLDP_MP2MP +}; + +/* + * Prefix-SID type-4 + * SRv6-VPN-SID-TLV + * draft-dawra-idr-srv6-vpn-04 + */ +struct bgp_attr_srv6_vpn { + unsigned long refcnt; + uint8_t sid_flags; + struct in6_addr sid; +}; + +/* + * Prefix-SID type-5 + * SRv6-L3VPN-Service-TLV + * draft-dawra-idr-srv6-vpn-05 + */ +struct bgp_attr_srv6_l3vpn { + unsigned long refcnt; + uint8_t sid_flags; + uint16_t endpoint_behavior; + struct in6_addr sid; + uint8_t loc_block_len; + uint8_t loc_node_len; + uint8_t func_len; + uint8_t arg_len; + uint8_t transposition_len; + uint8_t transposition_offset; +}; + +/* BGP core attribute structure. */ +struct attr { + /* AS Path structure */ + struct aspath *aspath; + + /* Community structure */ + struct community *community; + + /* Reference count of this attribute. */ + unsigned long refcnt; + + /* Flag of attribute is set or not. */ + uint64_t flag; + + /* Apart from in6_addr, the remaining static attributes */ + struct in_addr nexthop; + uint32_t med; + uint32_t local_pref; + ifindex_t nh_ifindex; + uint8_t nh_flags; + +#define BGP_ATTR_NH_VALID 0x01 +#define BGP_ATTR_NH_IF_OPERSTATE 0x02 +#define BGP_ATTR_NH_MP_PREFER_GLOBAL 0x04 /* MP Nexthop preference */ + + /* Path origin attribute */ + uint8_t origin; + + /* ES info */ + uint8_t es_flags; + /* Path is not "locally-active" on the advertising VTEP. This is + * translated into an ARP-ND ECOM. + */ +#define ATTR_ES_PROXY_ADVERT (1 << 0) + /* Destination ES is present locally. This flag is set on local + * paths and sync paths + */ +#define ATTR_ES_IS_LOCAL (1 << 1) + /* There are one or more non-best paths from ES peers. Note that + * this flag is only set on the local MAC-IP paths in the VNI + * route table (not set in the global routing table). And only + * non-proxy advertisements from an ES peer can result in this + * flag being set. + */ +#define ATTR_ES_PEER_ACTIVE (1 << 2) + /* There are one or more non-best proxy paths from ES peers */ +#define ATTR_ES_PEER_PROXY (1 << 3) + /* An ES peer has router bit set - only applicable if + * ATTR_ES_PEER_ACTIVE is set + */ +#define ATTR_ES_PEER_ROUTER (1 << 4) + + /* These two flags are only set on L3 routes installed in a + * VRF as a result of EVPN MAC-IP route + * XXX - while splitting up per-family attrs these need to be + * classified as non-EVPN + */ +#define ATTR_ES_L3_NHG_USE (1 << 5) +#define ATTR_ES_L3_NHG_ACTIVE (1 << 6) +#define ATTR_ES_L3_NHG (ATTR_ES_L3_NHG_USE | ATTR_ES_L3_NHG_ACTIVE) + + /* NA router flag (R-bit) support in EVPN */ + uint8_t router_flag; + + /* Distance as applied by Route map */ + uint8_t distance; + + /* EVPN DF preference for DF election on local ESs */ + uint8_t df_alg; + uint16_t df_pref; + + /* PMSI tunnel type (RFC 6514). */ + enum pta_type pmsi_tnl_type; + + /* has the route-map changed any attribute? + Used on the peer outbound side. */ + uint32_t rmap_change_flags; + + /* Multi-Protocol Nexthop, AFI IPv6 */ + struct in6_addr mp_nexthop_global; + struct in6_addr mp_nexthop_local; + + /* ifIndex corresponding to mp_nexthop_local. */ + ifindex_t nh_lla_ifindex; + + /* MPLS label */ + mpls_label_t label; + + /* Extended Communities attribute. */ + struct ecommunity *ecommunity; + + /* Extended Communities attribute. */ + struct ecommunity *ipv6_ecommunity; + + /* Large Communities attribute. */ + struct lcommunity *lcommunity; + + /* Route-Reflector Cluster attribute */ + struct cluster_list *cluster1; + + /* Unknown transitive attribute. */ + struct transit *transit; + + struct in_addr mp_nexthop_global_in; + + /* Aggregator Router ID attribute */ + struct in_addr aggregator_addr; + + /* Route Reflector Originator attribute */ + struct in_addr originator_id; + + /* Local weight, not actually an attribute */ + uint32_t weight; + + /* Aggregator ASN */ + as_t aggregator_as; + + /* MP Nexthop length */ + uint8_t mp_nexthop_len; + + /* Static MAC for EVPN */ + uint8_t sticky; + + /* Flag for default gateway extended community in EVPN */ + uint8_t default_gw; + + /* route tag */ + route_tag_t tag; + + /* Label index */ + uint32_t label_index; + + /* SRv6 VPN SID */ + struct bgp_attr_srv6_vpn *srv6_vpn; + + /* SRv6 L3VPN SID */ + struct bgp_attr_srv6_l3vpn *srv6_l3vpn; + + struct bgp_attr_encap_subtlv *encap_subtlvs; /* rfc5512 */ + +#ifdef ENABLE_BGP_VNC + struct bgp_attr_encap_subtlv *vnc_subtlvs; /* VNC-specific */ +#endif + /* EVPN */ + struct bgp_route_evpn evpn_overlay; + + /* EVPN MAC Mobility sequence number, if any. */ + uint32_t mm_seqnum; + /* highest MM sequence number rxed in a MAC-IP route from an + * ES peer (this includes both proxy and non-proxy MAC-IP + * advertisements from ES peers). + * This is only applicable to local paths in the VNI routing + * table and derived from other imported/non-best paths. + */ + uint32_t mm_sync_seqnum; + + /* EVPN local router-mac */ + struct ethaddr rmac; + + uint16_t encap_tunneltype; + + /* rmap set table */ + uint32_t rmap_table_id; + + /* Link bandwidth value, if any. */ + uint64_t link_bw; + + /* EVPN ES */ + esi_t esi; + + /* SR-TE Color */ + uint32_t srte_color; + + /* Nexthop type */ + enum nexthop_types_t nh_type; + + /* If NEXTHOP_TYPE_BLACKHOLE, then blackhole type */ + enum blackhole_type bh_type; + + /* OTC value if set */ + uint32_t otc; + + /* AIGP Metric */ + uint64_t aigp_metric; +}; + +/* rmap_change_flags definition */ +#define BATTR_RMAP_IPV4_NHOP_CHANGED (1 << 0) +#define BATTR_RMAP_NEXTHOP_PEER_ADDRESS (1 << 1) +#define BATTR_REFLECTED (1 << 2) +#define BATTR_RMAP_NEXTHOP_UNCHANGED (1 << 3) +#define BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED (1 << 4) +#define BATTR_RMAP_IPV6_LL_NHOP_CHANGED (1 << 5) +#define BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED (1 << 6) +#define BATTR_RMAP_LINK_BW_SET (1 << 7) +#define BATTR_RMAP_L3VPN_ACCEPT_GRE (1 << 8) +#define BATTR_RMAP_VPNV4_NHOP_CHANGED (1 << 9) +#define BATTR_RMAP_VPNV6_GLOBAL_NHOP_CHANGED (1 << 10) + +/* Router Reflector related structure. */ +struct cluster_list { + unsigned long refcnt; + int length; + struct in_addr *list; +}; + +/* Unknown transit attribute. */ +struct transit { + unsigned long refcnt; + int length; + uint8_t *val; +}; + +/* "(void) 0" will generate a compiler error. this is a safety check to + * ensure we're not using a value that exceeds the bit size of attr->flag. */ +#define ATTR_FLAG_BIT(X) \ + __builtin_choose_expr((X) >= 1 && (X) <= 64, 1ULL << ((X)-1), (void)0) + +#define BGP_CLUSTER_LIST_LENGTH(attr) \ + (((attr)->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)) \ + ? bgp_attr_get_cluster((attr))->length \ + : 0) + +enum bgp_attr_parse_ret { + BGP_ATTR_PARSE_PROCEED = 0, + BGP_ATTR_PARSE_ERROR = -1, + BGP_ATTR_PARSE_WITHDRAW = -2, + + /* only used internally, send notify + convert to BGP_ATTR_PARSE_ERROR + */ + BGP_ATTR_PARSE_ERROR_NOTIFYPLS = -3, + BGP_ATTR_PARSE_MISSING_MANDATORY = -4, +}; + +struct bpacket_attr_vec_arr; + +/* Prototypes. */ +extern void bgp_attr_init(void); +extern void bgp_attr_finish(void); +extern enum bgp_attr_parse_ret +bgp_attr_parse(struct peer *peer, struct attr *attr, bgp_size_t size, + struct bgp_nlri *mp_update, struct bgp_nlri *mp_withdraw); +extern struct attr *bgp_attr_intern(struct attr *attr); +extern void bgp_attr_unintern_sub(struct attr *attr); +extern void bgp_attr_unintern(struct attr **pattr); +extern void bgp_attr_flush(struct attr *attr); +extern struct attr *bgp_attr_default_set(struct attr *attr, struct bgp *bgp, + uint8_t origin); +extern struct attr *bgp_attr_aggregate_intern( + struct bgp *bgp, uint8_t origin, struct aspath *aspath, + struct community *community, struct ecommunity *ecommunity, + struct lcommunity *lcommunity, struct bgp_aggregate *aggregate, + uint8_t atomic_aggregate, const struct prefix *p); +extern bgp_size_t bgp_packet_attribute( + struct bgp *bgp, struct peer *peer, struct stream *s, struct attr *attr, + struct bpacket_attr_vec_arr *vecarr, struct prefix *p, afi_t afi, + safi_t safi, struct peer *from, struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, bool addpath_capable, + uint32_t addpath_tx_id, struct bgp_path_info *bpi); +extern void bgp_dump_routes_attr(struct stream *s, struct bgp_path_info *bpi, + const struct prefix *p); +extern bool attrhash_cmp(const void *arg1, const void *arg2); +extern unsigned int attrhash_key_make(const void *p); +extern void attr_show_all(struct vty *vty); +extern unsigned long int attr_count(void); +extern unsigned long int attr_unknown_count(void); +extern void bgp_path_attribute_discard_vty(struct vty *vty, struct peer *peer, + const char *discard_attrs, bool set); +extern void bgp_path_attribute_withdraw_vty(struct vty *vty, struct peer *peer, + const char *withdraw_attrs, + bool set); +extern enum bgp_attr_parse_ret bgp_attr_ignore(struct peer *peer, uint8_t type); + +/* Cluster list prototypes. */ +extern bool cluster_loop_check(struct cluster_list *cluster, + struct in_addr originator); + +/* Below exported for unit-test purposes only */ +struct bgp_attr_parser_args { + struct peer *peer; + bgp_size_t length; /* attribute data length; */ + bgp_size_t total; /* total length, inc header */ + struct attr *attr; + uint8_t type; + uint8_t flags; + uint8_t *startp; +}; +extern int bgp_mp_reach_parse(struct bgp_attr_parser_args *args, + struct bgp_nlri *mp_update); +extern int bgp_mp_unreach_parse(struct bgp_attr_parser_args *args, + struct bgp_nlri *mp_withdraw); +extern enum bgp_attr_parse_ret +bgp_attr_prefix_sid(struct bgp_attr_parser_args *args); + +extern struct bgp_attr_encap_subtlv * +encap_tlv_dup(struct bgp_attr_encap_subtlv *orig); + +extern void bgp_attr_flush_encap(struct attr *attr); + +extern void bgp_attr_extcom_tunnel_type(struct attr *attr, + bgp_encap_types *tunnel_type); + +/** + * Set of functions to encode MP_REACH_NLRI and MP_UNREACH_NLRI attributes. + * Typical call sequence is to call _start(), followed by multiple _prefix(), + * one for each NLRI that needs to be encoded into the UPDATE message, and + * finally the _end() function. + */ +extern size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, + afi_t afi, safi_t safi, + struct bpacket_attr_vec_arr *vecarr, + struct attr *attr); +extern void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, + const struct prefix *p, + const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, + uint32_t addpath_tx_id, struct attr *); +extern size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, + const struct prefix *p); +extern void bgp_packet_mpattr_end(struct stream *s, size_t sizep); + +extern size_t bgp_packet_mpunreach_start(struct stream *s, afi_t afi, + safi_t safi); +extern void bgp_packet_mpunreach_prefix( + struct stream *s, const struct prefix *p, afi_t afi, safi_t safi, + const struct prefix_rd *prd, mpls_label_t *label, uint8_t num_labels, + bool addpath_capable, uint32_t addpath_tx_id, struct attr *attr); +extern void bgp_packet_mpunreach_end(struct stream *s, size_t attrlen_pnt); + +extern enum bgp_attr_parse_ret bgp_attr_nexthop_valid(struct peer *peer, + struct attr *attr); + +extern uint32_t bgp_attr_get_color(struct attr *attr); + +static inline bool bgp_rmap_nhop_changed(uint32_t out_rmap_flags, + uint32_t in_rmap_flags) +{ + return ((CHECK_FLAG(out_rmap_flags, BATTR_RMAP_NEXTHOP_PEER_ADDRESS) || + CHECK_FLAG(out_rmap_flags, BATTR_RMAP_NEXTHOP_UNCHANGED) || + CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV4_NHOP_CHANGED) || + CHECK_FLAG(out_rmap_flags, BATTR_RMAP_VPNV4_NHOP_CHANGED) || + CHECK_FLAG(out_rmap_flags, + BATTR_RMAP_VPNV6_GLOBAL_NHOP_CHANGED) || + CHECK_FLAG(out_rmap_flags, + BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED) || + CHECK_FLAG(out_rmap_flags, + BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED) || + CHECK_FLAG(out_rmap_flags, BATTR_RMAP_IPV6_LL_NHOP_CHANGED) || + CHECK_FLAG(in_rmap_flags, BATTR_RMAP_NEXTHOP_UNCHANGED)) + ? true + : false); +} + +static inline uint32_t mac_mobility_seqnum(struct attr *attr) +{ + return (attr) ? attr->mm_seqnum : 0; +} + +static inline enum pta_type bgp_attr_get_pmsi_tnl_type(struct attr *attr) +{ + return attr->pmsi_tnl_type; +} + +static inline void bgp_attr_set_pmsi_tnl_type(struct attr *attr, + enum pta_type pmsi_tnl_type) +{ + attr->pmsi_tnl_type = pmsi_tnl_type; +} + +static inline struct ecommunity * +bgp_attr_get_ecommunity(const struct attr *attr) +{ + return attr->ecommunity; +} + +static inline void bgp_attr_set_ecommunity(struct attr *attr, + struct ecommunity *ecomm) +{ + attr->ecommunity = ecomm; + + if (ecomm) + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)); + else + UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)); +} + +static inline struct lcommunity * +bgp_attr_get_lcommunity(const struct attr *attr) +{ + return attr->lcommunity; +} + +static inline void bgp_attr_set_lcommunity(struct attr *attr, + struct lcommunity *lcomm) +{ + attr->lcommunity = lcomm; + + if (lcomm) + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)); + else + UNSET_FLAG(attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)); +} + +static inline struct community *bgp_attr_get_community(const struct attr *attr) +{ + return attr->community; +} + +static inline void bgp_attr_set_community(struct attr *attr, + struct community *comm) +{ + attr->community = comm; + + if (comm) + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)); + else + UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)); +} + +static inline struct ecommunity * +bgp_attr_get_ipv6_ecommunity(const struct attr *attr) +{ + return attr->ipv6_ecommunity; +} + +static inline void bgp_attr_set_ipv6_ecommunity(struct attr *attr, + struct ecommunity *ipv6_ecomm) +{ + attr->ipv6_ecommunity = ipv6_ecomm; + + if (ipv6_ecomm) + SET_FLAG(attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_IPV6_EXT_COMMUNITIES)); + else + UNSET_FLAG(attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_IPV6_EXT_COMMUNITIES)); +} + +static inline struct transit *bgp_attr_get_transit(const struct attr *attr) +{ + return attr->transit; +} + +static inline void bgp_attr_set_transit(struct attr *attr, + struct transit *transit) +{ + attr->transit = transit; +} + +static inline uint64_t bgp_attr_get_aigp_metric(const struct attr *attr) +{ + return attr->aigp_metric; +} + +static inline void bgp_attr_set_aigp_metric(struct attr *attr, uint64_t aigp) +{ + attr->aigp_metric = aigp; + + if (aigp) + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP)); +} + +static inline struct cluster_list *bgp_attr_get_cluster(const struct attr *attr) +{ + return attr->cluster1; +} + +static inline void bgp_attr_set_cluster(struct attr *attr, + struct cluster_list *cl) +{ + attr->cluster1 = cl; + + if (cl) + SET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)); + else + UNSET_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)); +} + +static inline const struct bgp_route_evpn * +bgp_attr_get_evpn_overlay(const struct attr *attr) +{ + return &attr->evpn_overlay; +} + +static inline void bgp_attr_set_evpn_overlay(struct attr *attr, + struct bgp_route_evpn *eo) +{ + memcpy(&attr->evpn_overlay, eo, sizeof(struct bgp_route_evpn)); +} + +static inline struct bgp_attr_encap_subtlv * +bgp_attr_get_vnc_subtlvs(const struct attr *attr) +{ +#ifdef ENABLE_BGP_VNC + return attr->vnc_subtlvs; +#else + return NULL; +#endif +} + +static inline void +bgp_attr_set_vnc_subtlvs(struct attr *attr, + struct bgp_attr_encap_subtlv *vnc_subtlvs) +{ +#ifdef ENABLE_BGP_VNC + attr->vnc_subtlvs = vnc_subtlvs; +#endif +} + +extern bool route_matches_soo(struct bgp_path_info *pi, struct ecommunity *soo); + +#endif /* _QUAGGA_BGP_ATTR_H */ diff --git a/bgpd/bgp_attr_evpn.c b/bgpd/bgp_attr_evpn.c new file mode 100644 index 0000000..bbc4ba9 --- /dev/null +++ b/bgpd/bgp_attr_evpn.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Ethernet-VPN Attribute handling file + * Copyright (C) 2016 6WIND + */ + +#include + +#include "command.h" +#include "filter.h" +#include "prefix.h" +#include "log.h" +#include "memory.h" +#include "stream.h" +#include "vxlan.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr_evpn.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" + +bool bgp_route_evpn_same(const struct bgp_route_evpn *e1, + const struct bgp_route_evpn *e2) +{ + return (e1->type == e2->type && + !memcmp(&(e1->eth_s_id), &(e2->eth_s_id), sizeof(esi_t)) && + !ipaddr_cmp(&(e1->gw_ip), &(e2->gw_ip))); +} + +void bgp_add_routermac_ecom(struct attr *attr, struct ethaddr *routermac) +{ + struct ecommunity_val routermac_ecom; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + memset(&routermac_ecom, 0, sizeof(routermac_ecom)); + routermac_ecom.val[0] = ECOMMUNITY_ENCODE_EVPN; + routermac_ecom.val[1] = ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC; + memcpy(&routermac_ecom.val[2], routermac->octet, ETH_ALEN); + if (!ecomm) { + bgp_attr_set_ecommunity(attr, ecommunity_new()); + ecomm = bgp_attr_get_ecommunity(attr); + } + ecommunity_add_val(ecomm, &routermac_ecom, false, false); + ecommunity_str(ecomm); +} + +/* converts to an esi + * returns 1 on success, 0 otherwise + * format accepted: AA:BB:CC:DD:EE:FF:GG:HH:II:JJ + * if id is null, check only is done + */ +bool str2esi(const char *str, esi_t *id) +{ + unsigned int a[ESI_BYTES]; + int i; + + if (!str) + return false; + if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, + a + 2, a + 3, a + 4, a + 5, a + 6, a + 7, a + 8, a + 9) + != ESI_BYTES) { + /* error in incoming str length */ + return false; + } + /* valid mac address */ + if (!id) + return true; + for (i = 0; i < ESI_BYTES; ++i) + id->val[i] = a[i] & 0xff; + return true; +} + +char *ecom_mac2str(char *ecom_mac) +{ + char *en; + + en = ecom_mac; + en += 2; + + return prefix_mac2str((struct ethaddr *)en, NULL, 0); +} + +/* Fetch router-mac from extended community */ +bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac) +{ + uint32_t i = 0; + struct ecommunity *ecom; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return false; + + /* If there is a router mac extended community, set RMAC in attr */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt = NULL; + uint8_t type = 0; + uint8_t sub_type = 0; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + + if (!(type == ECOMMUNITY_ENCODE_EVPN + && sub_type == ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC)) + continue; + + memcpy(rmac, pnt, ETH_ALEN); + return true; + } + return false; +} + +/* + * return true if attr contains default gw extended community + */ +uint8_t bgp_attr_default_gw(struct attr *attr) +{ + struct ecommunity *ecom; + uint32_t i; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return 0; + + /* If there is a default gw extendd community return true otherwise + * return 0 */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + + if ((type == ECOMMUNITY_ENCODE_OPAQUE + && sub_type == ECOMMUNITY_EVPN_SUBTYPE_DEF_GW)) + return 1; + } + + return 0; +} + +/* + * Fetch and return the DF preference and algorithm from + * DF election extended community, if present, else 0. + */ +uint16_t bgp_attr_df_pref_from_ec(struct attr *attr, uint8_t *alg) +{ + struct ecommunity *ecom; + uint32_t i; + uint16_t df_pref = 0; + + *alg = EVPN_MH_DF_ALG_SERVICE_CARVING; + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return 0; + + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + if (!(type == ECOMMUNITY_ENCODE_EVPN + && sub_type == ECOMMUNITY_EVPN_SUBTYPE_DF_ELECTION)) + continue; + + *alg = (*pnt++) & ECOMMUNITY_EVPN_SUBTYPE_DF_ALG_BITS; + + pnt += 3; + pnt = ptr_get_be16(pnt, &df_pref); + (void)pnt; /* consume value */ + break; + } + + return df_pref; +} + +/* + * Fetch and return the sequence number from MAC Mobility extended + * community, if present, else 0. + */ +uint32_t bgp_attr_mac_mobility_seqnum(struct attr *attr, uint8_t *sticky) +{ + struct ecommunity *ecom; + uint32_t i; + uint8_t flags = 0; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return 0; + + /* If there is a MAC Mobility extended community, return its + * sequence number. + * TODO: RFC is silent on handling of multiple MAC mobility extended + * communities for the same route. We will bail out upon the first + * one. + */ + for (i = 0; i < ecom->size; i++) { + const uint8_t *pnt; + uint8_t type, sub_type; + uint32_t seq_num; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + if (!(type == ECOMMUNITY_ENCODE_EVPN + && sub_type == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY)) + continue; + flags = *pnt++; + + if (flags & ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY) + *sticky = 1; + else + *sticky = 0; + + pnt++; + pnt = ptr_get_be32(pnt, &seq_num); + (void)pnt; /* consume value */ + return seq_num; + } + + return 0; +} + +/* + * return true if attr contains router flag extended community + */ +void bgp_attr_evpn_na_flag(struct attr *attr, + uint8_t *router_flag, bool *proxy) +{ + struct ecommunity *ecom; + uint32_t i; + uint8_t val; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return; + + /* If there is a evpn na extendd community set router_flag */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_EVPN && + sub_type == ECOMMUNITY_EVPN_SUBTYPE_ND) { + val = *pnt++; + + if (val & ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG) + *router_flag = 1; + + if (val & ECOMMUNITY_EVPN_SUBTYPE_PROXY_FLAG) + *proxy = true; + + break; + } + } +} + +/* dst prefix must be AF_INET or AF_INET6 prefix, to forge EVPN prefix */ +extern int bgp_build_evpn_prefix(int evpn_type, uint32_t eth_tag, + struct prefix *dst) +{ + struct evpn_addr *p_evpn_p; + struct prefix p2; + struct prefix *src = &p2; + + if (!dst || dst->family == 0) + return -1; + /* store initial prefix in src */ + prefix_copy(src, dst); + memset(dst, 0, sizeof(struct prefix)); + p_evpn_p = &(dst->u.prefix_evpn); + dst->family = AF_EVPN; + p_evpn_p->route_type = evpn_type; + if (evpn_type == BGP_EVPN_IP_PREFIX_ROUTE) { + p_evpn_p->prefix_addr.eth_tag = eth_tag; + p_evpn_p->prefix_addr.ip_prefix_length = p2.prefixlen; + if (src->family == AF_INET) { + SET_IPADDR_V4(&p_evpn_p->prefix_addr.ip); + memcpy(&p_evpn_p->prefix_addr.ip.ipaddr_v4, + &src->u.prefix4, + sizeof(struct in_addr)); + dst->prefixlen = (uint16_t)PREFIX_LEN_ROUTE_TYPE_5_IPV4; + } else { + SET_IPADDR_V6(&p_evpn_p->prefix_addr.ip); + memcpy(&p_evpn_p->prefix_addr.ip.ipaddr_v6, + &src->u.prefix6, + sizeof(struct in6_addr)); + dst->prefixlen = (uint16_t)PREFIX_LEN_ROUTE_TYPE_5_IPV6; + } + } else + return -1; + return 0; +} diff --git a/bgpd/bgp_attr_evpn.h b/bgpd/bgp_attr_evpn.h new file mode 100644 index 0000000..f8d3978 --- /dev/null +++ b/bgpd/bgp_attr_evpn.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* E-VPN attribute handling structure file + * Copyright (C) 2016 6WIND + */ + +#ifndef _QUAGGA_BGP_ATTR_EVPN_H +#define _QUAGGA_BGP_ATTR_EVPN_H + +#define MAX_ET 0xffffffff + +struct attr; + +enum overlay_index_type { + OVERLAY_INDEX_TYPE_NONE, + OVERLAY_INDEX_GATEWAY_IP, + OVERLAY_INDEX_ESI, + OVERLAY_INDEX_MAC, +}; + +/* + * Structure to store ovrelay index for EVPN type-5 route + * This structure stores ESI and Gateway IP overlay index. + * MAC overlay index is stored in the RMAC attribute. + */ +struct bgp_route_evpn { + enum overlay_index_type type; + esi_t eth_s_id; + struct ipaddr gw_ip; +}; + +extern bool str2esi(const char *str, esi_t *id); +extern char *ecom_mac2str(char *ecom_mac); + +extern void bgp_add_routermac_ecom(struct attr *attr, + struct ethaddr *routermac); +extern int bgp_build_evpn_prefix(int type, uint32_t eth_tag, + struct prefix *dst); +extern bool bgp_attr_rmac(struct attr *attr, struct ethaddr *rmac); +extern uint32_t bgp_attr_mac_mobility_seqnum(struct attr *attr, + uint8_t *sticky); +extern uint8_t bgp_attr_default_gw(struct attr *attr); + +extern void bgp_attr_evpn_na_flag(struct attr *attr, uint8_t *router_flag, + bool *proxy); +extern uint16_t bgp_attr_df_pref_from_ec(struct attr *attr, uint8_t *alg); + + +extern bool bgp_route_evpn_same(const struct bgp_route_evpn *e1, + const struct bgp_route_evpn *e2); +#endif /* _QUAGGA_BGP_ATTR_EVPN_H */ diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c new file mode 100644 index 0000000..14ff5f2 --- /dev/null +++ b/bgpd/bgp_bfd.c @@ -0,0 +1,630 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_bfd.c: BGP BFD handling routines + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "linklist.h" +#include "memory.h" +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "vrf.h" +#include "zclient.h" +#include "bfd.h" +#include "lib/json.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgp_fsm.h" +#include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_packet.h" + +DEFINE_MTYPE_STATIC(BGPD, BFD_CONFIG, "BFD configuration data"); + +extern struct zclient *zclient; + +static void bfd_session_status_update(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, + void *arg) +{ + struct peer *peer = arg; + + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: neighbor %s vrf %s(%u) bfd state %s -> %s", + __func__, peer->conf_if ? peer->conf_if : peer->host, + bfd_sess_vrf(bsp), bfd_sess_vrf_id(bsp), + bfd_get_status_str(bss->previous_state), + bfd_get_status_str(bss->state)); + + if (bss->state == BSS_DOWN && bss->previous_state == BSS_UP) { + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE) + && bfd_sess_cbit(bsp) && !bss->remote_cbit) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug( + "%s BFD DOWN message ignored in the process of graceful restart when C bit is cleared", + peer->host); + return; + } + peer->last_reset = PEER_DOWN_BFD_DOWN; + + /* rfc9384 */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_BFD_DOWN); + + BGP_EVENT_ADD(peer->connection, BGP_Stop); + } + + if (bss->state == BSS_UP && bss->previous_state != BSS_UP && + !peer_established(peer->connection)) { + if (!BGP_PEER_START_SUPPRESSED(peer)) { + bgp_fsm_nht_update(peer->connection, peer, true); + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + } +} + +void bgp_peer_config_apply(struct peer *p, struct peer_group *pg) +{ + struct listnode *n; + struct peer *pn; + struct peer *gconfig; + + /* When called on a group, apply to all peers. */ + if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)) { + for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn)) + bgp_peer_config_apply(pn, pg); + return; + } + + /* No group, just use current configuration. */ + if (pg == NULL || pg->conf->bfd_config == NULL) { + bfd_sess_set_timers(p->bfd_config->session, + p->bfd_config->detection_multiplier, + p->bfd_config->min_rx, + p->bfd_config->min_tx); + bfd_sess_set_cbit(p->bfd_config->session, p->bfd_config->cbit); + bfd_sess_set_profile(p->bfd_config->session, + p->bfd_config->profile); + bfd_sess_install(p->bfd_config->session); + return; + } + + /* + * Check if the group configuration was overwritten or apply group + * configuration. + */ + gconfig = pg->conf; + + /* + * If using default control plane independent configuration, + * then prefer group's (e.g. it means it wasn't manually configured). + */ + if (!p->bfd_config->cbit) + bfd_sess_set_cbit(p->bfd_config->session, + gconfig->bfd_config->cbit); + else + bfd_sess_set_cbit(p->bfd_config->session, p->bfd_config->cbit); + + /* If no profile was specified in peer, then use the group profile. */ + if (p->bfd_config->profile[0] == 0) + bfd_sess_set_profile(p->bfd_config->session, + gconfig->bfd_config->profile); + else + bfd_sess_set_profile(p->bfd_config->session, + p->bfd_config->profile); + + /* If no specific timers were configured, then use the group timers. */ + if (p->bfd_config->detection_multiplier == BFD_DEF_DETECT_MULT + || p->bfd_config->min_rx == BFD_DEF_MIN_RX + || p->bfd_config->min_tx == BFD_DEF_MIN_TX) + bfd_sess_set_timers(p->bfd_config->session, + gconfig->bfd_config->detection_multiplier, + gconfig->bfd_config->min_rx, + gconfig->bfd_config->min_tx); + else + bfd_sess_set_timers(p->bfd_config->session, + p->bfd_config->detection_multiplier, + p->bfd_config->min_rx, + p->bfd_config->min_tx); + + bfd_sess_install(p->bfd_config->session); +} + +void bgp_peer_bfd_update_source(struct peer *p) +{ + struct bfd_session_params *session = p->bfd_config->session; + const union sockunion *source; + bool changed = false; + int family; + union { + struct in_addr v4; + struct in6_addr v6; + } src, dst; + + /* Nothing to do for groups. */ + if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)) + return; + + /* Figure out the correct source to use. */ + if (CHECK_FLAG(p->flags, PEER_FLAG_UPDATE_SOURCE) && p->update_source) + source = p->update_source; + else + source = p->su_local; + + /* Update peer's source/destination addresses. */ + bfd_sess_addresses(session, &family, &src.v6, &dst.v6); + if (family == AF_INET) { + if ((source && source->sin.sin_addr.s_addr != src.v4.s_addr) || + p->connection->su.sin.sin_addr.s_addr != dst.v4.s_addr) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: address [%pI4->%pI4] to [%pI4->%pI4]", + __func__, &src.v4, &dst.v4, + source ? &source->sin.sin_addr + : &src.v4, + &p->connection->su.sin.sin_addr); + + bfd_sess_set_ipv4_addrs(session, + source ? &source->sin.sin_addr + : NULL, + &p->connection->su.sin.sin_addr); + changed = true; + } + } else { + if ((source && memcmp(&source->sin6, &src.v6, sizeof(src.v6))) || + memcmp(&p->connection->su.sin6, &dst.v6, sizeof(dst.v6))) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: address [%pI6->%pI6] to [%pI6->%pI6]", + __func__, &src.v6, &dst.v6, + source ? &source->sin6.sin6_addr + : &src.v6, + &p->connection->su.sin6.sin6_addr); + + bfd_sess_set_ipv6_addrs(session, + source ? &source->sin6.sin6_addr + : NULL, + &p->connection->su.sin6.sin6_addr); + changed = true; + } + } + + /* Update interface. */ + if (p->nexthop.ifp && bfd_sess_interface(session) == NULL) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: interface none to %s", __func__, + p->nexthop.ifp->name); + + bfd_sess_set_interface(session, p->nexthop.ifp->name); + changed = true; + } + + /* + * Update TTL. + * + * Two cases: + * - We detected that the peer is a hop away from us (remove multi hop). + * (this happens when `p->shared_network` is set to `true`) + * - eBGP multi hop / TTL security changed. + */ + if (!PEER_IS_MULTIHOP(p) && bfd_sess_hop_count(session) > 1) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: TTL %d to 1", __func__, + bfd_sess_hop_count(session)); + + bfd_sess_set_hop_count(session, 1); + changed = true; + } + if (PEER_IS_MULTIHOP(p) && p->ttl != bfd_sess_hop_count(session)) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug("%s: TTL %d to %d", __func__, + bfd_sess_hop_count(session), p->ttl); + + bfd_sess_set_hop_count(session, p->ttl); + changed = true; + } + + /* Update VRF. */ + if (bfd_sess_vrf_id(session) != p->bgp->vrf_id) { + if (BGP_DEBUG(bfd, BFD_LIB)) + zlog_debug( + "%s: VRF %s(%d) to %s(%d)", __func__, + bfd_sess_vrf(session), bfd_sess_vrf_id(session), + vrf_id_to_name(p->bgp->vrf_id), p->bgp->vrf_id); + + bfd_sess_set_vrf(session, p->bgp->vrf_id); + changed = true; + } + + if (changed) + bfd_sess_install(session); +} + +/** + * Reset BFD configuration data structure to its defaults settings. + */ +static void bgp_peer_bfd_reset(struct peer *p) +{ + /* Set defaults. */ + p->bfd_config->detection_multiplier = BFD_DEF_DETECT_MULT; + p->bfd_config->min_rx = BFD_DEF_MIN_RX; + p->bfd_config->min_tx = BFD_DEF_MIN_TX; + p->bfd_config->cbit = false; + p->bfd_config->profile[0] = 0; +} + +void bgp_peer_configure_bfd(struct peer *p, bool manual) +{ + /* Groups should not call this. */ + assert(!CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)); + + /* Already configured, skip it. */ + if (p->bfd_config) { + /* If manually active update flag. */ + if (!p->bfd_config->manual) + p->bfd_config->manual = manual; + + return; + } + + /* Allocate memory for configuration overrides. */ + p->bfd_config = XCALLOC(MTYPE_BFD_CONFIG, sizeof(*p->bfd_config)); + p->bfd_config->manual = manual; + + /* Create new session and assign callback. */ + p->bfd_config->session = bfd_sess_new(bfd_session_status_update, p); + bgp_peer_bfd_reset(p); + + /* Configure session with basic BGP peer data. */ + if (p->connection->su.sa.sa_family == AF_INET) + bfd_sess_set_ipv4_addrs(p->bfd_config->session, + p->su_local ? &p->su_local->sin.sin_addr + : NULL, + &p->connection->su.sin.sin_addr); + else + bfd_sess_set_ipv6_addrs(p->bfd_config->session, + p->su_local + ? &p->su_local->sin6.sin6_addr + : NULL, + &p->connection->su.sin6.sin6_addr); + + bfd_sess_set_vrf(p->bfd_config->session, p->bgp->vrf_id); + bfd_sess_set_hop_count(p->bfd_config->session, + PEER_IS_MULTIHOP(p) ? p->ttl : 1); + + if (p->nexthop.ifp) + bfd_sess_set_interface(p->bfd_config->session, + p->nexthop.ifp->name); +} + +static void bgp_peer_remove_bfd(struct peer *p) +{ + /* Groups should not call this. */ + assert(!CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)); + + /* + * Peer configuration was removed, however we must check if there + * is still a group configuration to keep this running. + */ + if (p->group && p->group->conf->bfd_config) { + p->bfd_config->manual = false; + bgp_peer_bfd_reset(p); + bgp_peer_config_apply(p, p->group); + return; + } + + if (p->bfd_config) + bfd_sess_free(&p->bfd_config->session); + + XFREE(MTYPE_BFD_CONFIG, p->bfd_config); +} + +static void bgp_group_configure_bfd(struct peer *p) +{ + struct listnode *n; + struct peer *pn; + + /* Peers should not call this. */ + assert(CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)); + + /* Already allocated: do nothing. */ + if (p->bfd_config) + return; + + p->bfd_config = XCALLOC(MTYPE_BFD_CONFIG, sizeof(*p->bfd_config)); + + /* Set defaults. */ + p->bfd_config->detection_multiplier = BFD_DEF_DETECT_MULT; + p->bfd_config->min_rx = BFD_DEF_MIN_RX; + p->bfd_config->min_tx = BFD_DEF_MIN_TX; + + for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn)) + bgp_peer_configure_bfd(pn, false); +} + +static void bgp_group_remove_bfd(struct peer *p) +{ + struct listnode *n; + struct peer *pn; + + /* Peers should not call this. */ + assert(CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)); + + /* Already freed: do nothing. */ + if (p->bfd_config == NULL) + return; + + /* Free configuration and point to `NULL`. */ + XFREE(MTYPE_BFD_CONFIG, p->bfd_config); + + /* Now that it is `NULL` recalculate configuration for all peers. */ + for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn)) { + if (pn->bfd_config->manual) + bgp_peer_config_apply(pn, NULL); + else + bgp_peer_remove_bfd(pn); + } +} + +void bgp_peer_remove_bfd_config(struct peer *p) +{ + if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)) + bgp_group_remove_bfd(p); + else + bgp_peer_remove_bfd(p); +} + +/* + * bgp_bfd_peer_config_write - Write the peer BFD configuration. + */ +void bgp_bfd_peer_config_write(struct vty *vty, const struct peer *peer, + const char *addr) +{ + /* + * Always show group BFD configuration, but peer only when explicitly + * configured. + */ + if ((!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) + && peer->bfd_config->manual) + || CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { +#if HAVE_BFDD > 0 + vty_out(vty, " neighbor %s bfd\n", addr); +#else + vty_out(vty, " neighbor %s bfd %d %d %d\n", addr, + peer->bfd_config->detection_multiplier, + peer->bfd_config->min_rx, peer->bfd_config->min_tx); +#endif /* HAVE_BFDD */ + } + + if (peer->bfd_config->profile[0]) + vty_out(vty, " neighbor %s bfd profile %s\n", addr, + peer->bfd_config->profile); + + if (peer->bfd_config->cbit) + vty_out(vty, " neighbor %s bfd check-control-plane-failure\n", + addr); +} + +/* + * bgp_bfd_show_info - Show the peer BFD information. + */ +void bgp_bfd_show_info(struct vty *vty, const struct peer *peer, + json_object *json_neigh) +{ + bfd_sess_show(vty, json_neigh, peer->bfd_config->session); +} + +DEFUN (neighbor_bfd, + neighbor_bfd_cmd, + "neighbor bfd", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enables BFD support\n") +{ + int idx_peer = 1; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_configure_bfd(peer); + else + bgp_peer_configure_bfd(peer, true); + + bgp_peer_config_apply(peer, peer->group); + + return CMD_SUCCESS; +} + +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + neighbor_bfd_param, + neighbor_bfd_param_cmd, + "neighbor bfd (2-255) (50-60000) (50-60000)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +{ + int idx_peer = 1; + int idx_number_1 = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + long detection_multiplier, min_rx, min_tx; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + detection_multiplier = strtol(argv[idx_number_1]->arg, NULL, 10); + min_rx = strtol(argv[idx_number_2]->arg, NULL, 10); + min_tx = strtol(argv[idx_number_3]->arg, NULL, 10); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_configure_bfd(peer); + else + bgp_peer_configure_bfd(peer, true); + + peer->bfd_config->detection_multiplier = detection_multiplier; + peer->bfd_config->min_rx = min_rx; + peer->bfd_config->min_tx = min_tx; + bgp_peer_config_apply(peer, peer->group); + + return CMD_SUCCESS; +} + +DEFUN (neighbor_bfd_check_controlplane_failure, + neighbor_bfd_check_controlplane_failure_cmd, + "[no] neighbor bfd check-control-plane-failure", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BFD support\n" + "Link dataplane status with BGP controlplane\n") +{ + const char *no = strmatch(argv[0]->text, "no") ? "no" : NULL; + int idx_peer = 0; + struct peer *peer; + + if (no) + idx_peer = 2; + else + idx_peer = 1; + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_configure_bfd(peer); + else + bgp_peer_configure_bfd(peer, true); + + peer->bfd_config->cbit = no == NULL; + bgp_peer_config_apply(peer, peer->group); + + return CMD_SUCCESS; + } + +DEFUN (no_neighbor_bfd, + no_neighbor_bfd_cmd, +#if HAVE_BFDD > 0 + "no neighbor bfd", +#else + "no neighbor bfd [(2-255) (50-60000) (50-60000)]", +#endif /* HAVE_BFDD */ + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Disables BFD support\n" +#if HAVE_BFDD == 0 + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n" +#endif /* !HAVE_BFDD */ +) +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_remove_bfd(peer); + else + bgp_peer_remove_bfd(peer); + + return CMD_SUCCESS; +} + +#if HAVE_BFDD > 0 +DEFUN(neighbor_bfd_profile, neighbor_bfd_profile_cmd, + "neighbor bfd profile BFDPROF", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BFD integration\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + int idx_peer = 1, idx_prof = 4; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_configure_bfd(peer); + else + bgp_peer_configure_bfd(peer, true); + + strlcpy(peer->bfd_config->profile, argv[idx_prof]->arg, + sizeof(peer->bfd_config->profile)); + bgp_peer_config_apply(peer, peer->group); + + return CMD_SUCCESS; +} + +DEFUN(no_neighbor_bfd_profile, no_neighbor_bfd_profile_cmd, + "no neighbor bfd profile [BFDPROF]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BFD integration\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!peer->bfd_config) + return CMD_SUCCESS; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + bgp_group_configure_bfd(peer); + else + bgp_peer_configure_bfd(peer, true); + + peer->bfd_config->profile[0] = 0; + bgp_peer_config_apply(peer, peer->group); + + return CMD_SUCCESS; +} +#endif /* HAVE_BFDD */ + +void bgp_bfd_init(struct event_loop *tm) +{ + /* Initialize BFD client functions */ + bfd_protocol_integration_init(zclient, tm); + + /* "neighbor bfd" commands. */ + install_element(BGP_NODE, &neighbor_bfd_cmd); + install_element(BGP_NODE, &neighbor_bfd_param_cmd); + install_element(BGP_NODE, &neighbor_bfd_check_controlplane_failure_cmd); + install_element(BGP_NODE, &no_neighbor_bfd_cmd); + +#if HAVE_BFDD > 0 + install_element(BGP_NODE, &neighbor_bfd_profile_cmd); + install_element(BGP_NODE, &no_neighbor_bfd_profile_cmd); +#endif /* HAVE_BFDD */ +} diff --git a/bgpd/bgp_bfd.h b/bgpd/bgp_bfd.h new file mode 100644 index 0000000..61b4b76 --- /dev/null +++ b/bgpd/bgp_bfd.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_bfd.h: BGP BFD definitions and structures + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#ifndef _QUAGGA_BGP_BFD_H +#define _QUAGGA_BGP_BFD_H + +#define PEER_IS_MULTIHOP(peer) \ + ((((peer)->sort == BGP_PEER_IBGP) && !(peer)->shared_network) \ + || is_ebgp_multihop_configured((peer))) + +extern void bgp_bfd_init(struct event_loop *tm); + +extern void bgp_bfd_peer_config_write(struct vty *vty, const struct peer *peer, + const char *addr); + +/** + * Show BFD information helper. + * + * \param vty the VTY pointer. + * \param peer the BGP configuration pointer. + * \param use_json unused. + * \param json_neigh JSON object when called as JSON command. + */ +extern void bgp_bfd_show_info(struct vty *vty, const struct peer *peer, + json_object *json_neigh); + +/** + * When called on a group it applies configuration to all peers in that group, + * otherwise just applies the configuration to a single peer. + * + * This function should be called when configuration changes either on group + * or peer. + * + * \param p the BGP peer pointer. + * \param pg the BGP group to copy configuration from (it is usually + * `p->group` exception when copying new group configuration + * see `peer_group2peer_config_copy` function case). + */ +extern void bgp_peer_config_apply(struct peer *p, struct peer_group *pg); + +/** + * Allocates and configure BFD session for peer. If it is already configured, + * then it does nothing. + * + * Always call `bgp_peer_config_apply` afterwards if you need the changes + * immediately applied. + */ +extern void bgp_peer_configure_bfd(struct peer *p, bool manual); + +/** + * Removes BFD configuration from either peer or peer group. + */ +extern void bgp_peer_remove_bfd_config(struct peer *p); + +/** + * Special function to handle the case of changing source address. This + * happens when the peer/group is configured with `neigbor X update-source Y`. + */ +extern void bgp_peer_bfd_update_source(struct peer *p); + +#endif /* _QUAGGA_BGP_BFD_H */ diff --git a/bgpd/bgp_bmp.c b/bgpd/bgp_bmp.c new file mode 100644 index 0000000..675e476 --- /dev/null +++ b/bgpd/bgp_bmp.c @@ -0,0 +1,2972 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BMP support. + * Copyright (C) 2018 Yasuhiro Ohara + * Copyright (C) 2019 David Lamparter for NetDEF, Inc. + */ + +#include + +#include "log.h" +#include "stream.h" +#include "sockunion.h" +#include "command.h" +#include "prefix.h" +#include "frrevent.h" +#include "linklist.h" +#include "queue.h" +#include "pullwr.h" +#include "memory.h" +#include "network.h" +#include "filter.h" +#include "lib_errors.h" +#include "stream.h" +#include "libfrr.h" +#include "lib/version.h" +#include "jhash.h" +#include "termtable.h" + +#include "bgpd/bgp_table.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_bmp.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_trace.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_label.h" + +static void bmp_close(struct bmp *bmp); +static struct bmp_bgp *bmp_bgp_find(struct bgp *bgp); +static void bmp_targets_put(struct bmp_targets *bt); +static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid); +static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer); +static void bmp_active_disconnected(struct bmp_active *ba); +static void bmp_active_put(struct bmp_active *ba); + +DEFINE_MGROUP(BMP, "BMP (BGP Monitoring Protocol)"); + +DEFINE_MTYPE_STATIC(BMP, BMP_CONN, "BMP connection state"); +DEFINE_MTYPE_STATIC(BMP, BMP_TARGETS, "BMP targets"); +DEFINE_MTYPE_STATIC(BMP, BMP_TARGETSNAME, "BMP targets name"); +DEFINE_MTYPE_STATIC(BMP, BMP_LISTENER, "BMP listener"); +DEFINE_MTYPE_STATIC(BMP, BMP_ACTIVE, "BMP active connection config"); +DEFINE_MTYPE_STATIC(BMP, BMP_ACLNAME, "BMP access-list name"); +DEFINE_MTYPE_STATIC(BMP, BMP_QUEUE, "BMP update queue item"); +DEFINE_MTYPE_STATIC(BMP, BMP, "BMP instance state"); +DEFINE_MTYPE_STATIC(BMP, BMP_MIRRORQ, "BMP route mirroring buffer"); +DEFINE_MTYPE_STATIC(BMP, BMP_PEER, "BMP per BGP peer data"); +DEFINE_MTYPE_STATIC(BMP, BMP_OPEN, "BMP stored BGP OPEN message"); + +DEFINE_QOBJ_TYPE(bmp_targets); + +static int bmp_bgp_cmp(const struct bmp_bgp *a, const struct bmp_bgp *b) +{ + if (a->bgp < b->bgp) + return -1; + if (a->bgp > b->bgp) + return 1; + return 0; +} + +static uint32_t bmp_bgp_hash(const struct bmp_bgp *e) +{ + return jhash(&e->bgp, sizeof(e->bgp), 0x55aa5a5a); +} + +DECLARE_HASH(bmp_bgph, struct bmp_bgp, bbi, bmp_bgp_cmp, bmp_bgp_hash); + +struct bmp_bgph_head bmp_bgph; + +static int bmp_bgp_peer_cmp(const struct bmp_bgp_peer *a, + const struct bmp_bgp_peer *b) +{ + if (a->peerid < b->peerid) + return -1; + if (a->peerid > b->peerid) + return 1; + return 0; +} + +static uint32_t bmp_bgp_peer_hash(const struct bmp_bgp_peer *e) +{ + return e->peerid; +} + +DECLARE_HASH(bmp_peerh, struct bmp_bgp_peer, bpi, + bmp_bgp_peer_cmp, bmp_bgp_peer_hash); + +struct bmp_peerh_head bmp_peerh; + +DECLARE_LIST(bmp_mirrorq, struct bmp_mirrorq, bmi); + +/* listener management */ + +static int bmp_listener_cmp(const struct bmp_listener *a, + const struct bmp_listener *b) +{ + int c; + + c = sockunion_cmp(&a->addr, &b->addr); + if (c) + return c; + if (a->port < b->port) + return -1; + if (a->port > b->port) + return 1; + return 0; +} + +DECLARE_SORTLIST_UNIQ(bmp_listeners, struct bmp_listener, bli, + bmp_listener_cmp); + +static void bmp_listener_put(struct bmp_listener *bl) +{ + bmp_listeners_del(&bl->targets->listeners, bl); + XFREE(MTYPE_BMP_LISTENER, bl); +} + +static int bmp_targets_cmp(const struct bmp_targets *a, + const struct bmp_targets *b) +{ + return strcmp(a->name, b->name); +} + +DECLARE_SORTLIST_UNIQ(bmp_targets, struct bmp_targets, bti, bmp_targets_cmp); + +DECLARE_LIST(bmp_session, struct bmp, bsi); + +DECLARE_DLIST(bmp_qlist, struct bmp_queue_entry, bli); + +static int bmp_qhash_cmp(const struct bmp_queue_entry *a, + const struct bmp_queue_entry *b) +{ + int ret; + if (a->afi == AFI_L2VPN && a->safi == SAFI_EVPN && b->afi == AFI_L2VPN + && b->safi == SAFI_EVPN) { + ret = prefix_cmp(&a->rd, &b->rd); + if (ret) + return ret; + } else if (a->afi == AFI_L2VPN && a->safi == SAFI_EVPN) + return 1; + else if (b->afi == AFI_L2VPN && b->safi == SAFI_EVPN) + return -1; + + if (a->afi == b->afi && a->safi == SAFI_MPLS_VPN && + b->safi == SAFI_MPLS_VPN) { + ret = prefix_cmp(&a->rd, &b->rd); + if (ret) + return ret; + } else if (a->safi == SAFI_MPLS_VPN) + return 1; + else if (b->safi == SAFI_MPLS_VPN) + return -1; + + ret = prefix_cmp(&a->p, &b->p); + if (ret) + return ret; + ret = memcmp(&a->peerid, &b->peerid, + offsetof(struct bmp_queue_entry, refcount) - + offsetof(struct bmp_queue_entry, peerid)); + return ret; +} + +static uint32_t bmp_qhash_hkey(const struct bmp_queue_entry *e) +{ + uint32_t key; + + key = prefix_hash_key((void *)&e->p); + key = jhash(&e->peerid, + offsetof(struct bmp_queue_entry, refcount) + - offsetof(struct bmp_queue_entry, peerid), + key); + if ((e->afi == AFI_L2VPN && e->safi == SAFI_EVPN) || + (e->safi == SAFI_MPLS_VPN)) + key = jhash(&e->rd, + offsetof(struct bmp_queue_entry, rd) + - offsetof(struct bmp_queue_entry, refcount) + + PSIZE(e->rd.prefixlen), + key); + + return key; +} + +DECLARE_HASH(bmp_qhash, struct bmp_queue_entry, bhi, + bmp_qhash_cmp, bmp_qhash_hkey); + +static int bmp_active_cmp(const struct bmp_active *a, + const struct bmp_active *b) +{ + int c; + + c = strcmp(a->hostname, b->hostname); + if (c) + return c; + if (a->port < b->port) + return -1; + if (a->port > b->port) + return 1; + return 0; +} + +DECLARE_SORTLIST_UNIQ(bmp_actives, struct bmp_active, bai, bmp_active_cmp); + +static struct bmp *bmp_new(struct bmp_targets *bt, int bmp_sock) +{ + struct bmp *new = XCALLOC(MTYPE_BMP_CONN, sizeof(struct bmp)); + afi_t afi; + safi_t safi; + + monotime(&new->t_up); + new->targets = bt; + new->socket = bmp_sock; + new->syncafi = AFI_MAX; + + FOREACH_AFI_SAFI (afi, safi) { + new->afistate[afi][safi] = bt->afimon[afi][safi] + ? BMP_AFI_NEEDSYNC : BMP_AFI_INACTIVE; + } + + bmp_session_add_tail(&bt->sessions, new); + return new; +} + +static void bmp_free(struct bmp *bmp) +{ + bmp_session_del(&bmp->targets->sessions, bmp); + XFREE(MTYPE_BMP_CONN, bmp); +} + +#define BMP_PEER_TYPE_GLOBAL_INSTANCE 0 +#define BMP_PEER_TYPE_RD_INSTANCE 1 +#define BMP_PEER_TYPE_LOCAL_INSTANCE 2 +#define BMP_PEER_TYPE_LOC_RIB_INSTANCE 3 + +static inline int bmp_get_peer_distinguisher(struct bmp *bmp, afi_t afi, + uint8_t peer_type, + uint64_t *result_ref) +{ + + /* remove this check when the other peer types get correct peer dist. + *(RFC7854) impl. + * for now, always return no error and 0 peer distinguisher as before + */ + if (peer_type != BMP_PEER_TYPE_LOC_RIB_INSTANCE) + return (*result_ref = 0); + + /* sending vrf_id or rd could be turned into an option at some point */ + struct bgp *bgp = bmp->targets->bgp; + + /* vrf default => ok, distinguisher 0 */ + if (bgp->inst_type == VRF_DEFAULT) + return (*result_ref = 0); + + /* use RD if set in VRF config for this AFI */ + struct prefix_rd *prd = &bgp->vpn_policy[afi].tovpn_rd; + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) { + memcpy(result_ref, prd->val, sizeof(prd->val)); + return 0; + } + + /* VRF has no id => error => message should be skipped */ + if (bgp->vrf_id == VRF_UNKNOWN) + return 1; + + /* use VRF id converted to ::vrf_id 64bits format */ + *result_ref = ((uint64_t)htonl(bgp->vrf_id)) << 32; + return 0; +} + +static void bmp_common_hdr(struct stream *s, uint8_t ver, uint8_t type) +{ + stream_putc(s, ver); + stream_putl(s, 0); /* dummy message length. will be set later. */ + stream_putc(s, type); +} + +static void bmp_per_peer_hdr(struct stream *s, struct bgp *bgp, + struct peer *peer, uint8_t flags, + uint8_t peer_type_flag, + uint64_t peer_distinguisher, + const struct timeval *tv) +{ +#define BMP_PEER_FLAG_V (1 << 7) +#define BMP_PEER_FLAG_L (1 << 6) +#define BMP_PEER_FLAG_A (1 << 5) + + bool is_locrib = peer_type_flag == BMP_PEER_TYPE_LOC_RIB_INSTANCE; + + /* Peer Type */ + stream_putc(s, peer_type_flag); + + /* Peer Flags */ + if (!is_locrib && peer->connection->su.sa.sa_family == AF_INET6) + SET_FLAG(flags, BMP_PEER_FLAG_V); + else + UNSET_FLAG(flags, BMP_PEER_FLAG_V); + stream_putc(s, flags); + + /* Peer Distinguisher */ + stream_put(s, (uint8_t *)&peer_distinguisher, 8); + + /* Peer Address */ + /* Set to 0 if it's a LOC-RIB INSTANCE (RFC 9069) or if it's not an + * IPv4/6 address + */ + if (is_locrib || (peer->connection->su.sa.sa_family != AF_INET6 && + peer->connection->su.sa.sa_family != AF_INET)) { + stream_putl(s, 0); + stream_putl(s, 0); + stream_putl(s, 0); + stream_putl(s, 0); + } else if (peer->connection->su.sa.sa_family == AF_INET6) + stream_put(s, &peer->connection->su.sin6.sin6_addr, IPV6_MAX_BYTELEN); + else if (peer->connection->su.sa.sa_family == AF_INET) { + stream_putl(s, 0); + stream_putl(s, 0); + stream_putl(s, 0); + stream_put_in_addr(s, &peer->connection->su.sin.sin_addr); + } + + /* Peer AS */ + /* set peer ASN but for LOC-RIB INSTANCE (RFC 9069) put the local bgp + * ASN + */ + as_t asn = !is_locrib ? peer->as : bgp->as; + + stream_putl(s, asn); + + /* Peer BGP ID */ + /* set router-id but for LOC-RIB INSTANCE (RFC 9069) put the instance + * router-id + */ + struct in_addr *bgp_id = + !is_locrib ? &peer->remote_id : &bgp->router_id; + + stream_put_in_addr(s, bgp_id); + + /* Timestamp */ + if (tv) { + stream_putl(s, tv->tv_sec); + stream_putl(s, tv->tv_usec); + } else { + stream_putl(s, 0); + stream_putl(s, 0); + } +} + +static void bmp_put_info_tlv(struct stream *s, uint16_t type, + const char *string) +{ + int len = strlen (string); + stream_putw(s, type); + stream_putw(s, len); + stream_put(s, string, len); +} + +static void __attribute__((unused)) +bmp_put_vrftablename_info_tlv(struct stream *s, struct bmp *bmp) +{ + +#define BMP_INFO_TYPE_VRFTABLENAME 3 + const char *vrftablename = "global"; + if (bmp->targets->bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { + struct vrf *vrf = vrf_lookup_by_id(bmp->targets->bgp->vrf_id); + + vrftablename = vrf ? vrf->name : NULL; + } + if (vrftablename != NULL) + bmp_put_info_tlv(s, BMP_INFO_TYPE_VRFTABLENAME, vrftablename); +} + +static int bmp_send_initiation(struct bmp *bmp) +{ + int len; + struct stream *s = stream_new(BGP_MAX_PACKET_SIZE); + + bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_INITIATION); + +#define BMP_INIT_INFO_TYPE_SYSDESCR 1 +#define BMP_INIT_INFO_TYPE_SYSNAME 2 + bmp_put_info_tlv(s, BMP_INIT_INFO_TYPE_SYSDESCR, + FRR_FULL_NAME " " FRR_VER_SHORT); + bmp_put_info_tlv(s, BMP_INIT_INFO_TYPE_SYSNAME, cmd_hostname_get()); + + len = stream_get_endp(s); + stream_putl_at(s, BMP_LENGTH_POS, len); /* message length is set. */ + + pullwr_write_stream(bmp->pullwr, s); + stream_free(s); + return 0; +} + +static void bmp_notify_put(struct stream *s, struct bgp_notify *nfy) +{ + size_t len_pos; + uint8_t marker[16] = { + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + }; + + stream_put(s, marker, sizeof(marker)); + len_pos = stream_get_endp(s); + stream_putw(s, 0); + stream_putc(s, BGP_MSG_NOTIFY); + stream_putc(s, nfy->code); + stream_putc(s, nfy->subcode); + stream_put(s, nfy->data, nfy->length); + + stream_putw_at(s, len_pos, stream_get_endp(s) - len_pos + + sizeof(marker)); +} + +static struct stream *bmp_peerstate(struct peer *peer, bool down) +{ + struct stream *s; + size_t len; + struct timeval uptime, uptime_real; + + uptime.tv_sec = peer->uptime; + uptime.tv_usec = 0; + monotime_to_realtime(&uptime, &uptime_real); + +#define BGP_BMP_MAX_PACKET_SIZE 1024 +#define BMP_PEERUP_INFO_TYPE_STRING 0 + s = stream_new(BGP_MAX_PACKET_SIZE); + + if (peer_established(peer->connection) && !down) { + struct bmp_bgp_peer *bbpeer; + + bmp_common_hdr(s, BMP_VERSION_3, + BMP_TYPE_PEER_UP_NOTIFICATION); + bmp_per_peer_hdr(s, peer->bgp, peer, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE, 0, + &uptime_real); + + /* Local Address (16 bytes) */ + if (peer->su_local->sa.sa_family == AF_INET6) + stream_put(s, &peer->su_local->sin6.sin6_addr, 16); + else if (peer->su_local->sa.sa_family == AF_INET) { + stream_putl(s, 0); + stream_putl(s, 0); + stream_putl(s, 0); + stream_put_in_addr(s, &peer->su_local->sin.sin_addr); + } + + /* Local Port, Remote Port */ + if (peer->su_local->sa.sa_family == AF_INET6) + stream_putw(s, htons(peer->su_local->sin6.sin6_port)); + else if (peer->su_local->sa.sa_family == AF_INET) + stream_putw(s, htons(peer->su_local->sin.sin_port)); + if (peer->su_remote->sa.sa_family == AF_INET6) + stream_putw(s, htons(peer->su_remote->sin6.sin6_port)); + else if (peer->su_remote->sa.sa_family == AF_INET) + stream_putw(s, htons(peer->su_remote->sin.sin_port)); + + static const uint8_t dummy_open[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x13, 0x01, + }; + + bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); + + if (bbpeer && bbpeer->open_tx) + stream_put(s, bbpeer->open_tx, bbpeer->open_tx_len); + else { + stream_put(s, dummy_open, sizeof(dummy_open)); + zlog_warn("bmp: missing TX OPEN message for peer %s", + peer->host); + } + if (bbpeer && bbpeer->open_rx) + stream_put(s, bbpeer->open_rx, bbpeer->open_rx_len); + else { + stream_put(s, dummy_open, sizeof(dummy_open)); + zlog_warn("bmp: missing RX OPEN message for peer %s", + peer->host); + } + + if (peer->desc) + bmp_put_info_tlv(s, BMP_PEERUP_INFO_TYPE_STRING, + peer->desc); + } else { + uint8_t type; + size_t type_pos; + + bmp_common_hdr(s, BMP_VERSION_3, + BMP_TYPE_PEER_DOWN_NOTIFICATION); + bmp_per_peer_hdr(s, peer->bgp, peer, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE, 0, + &uptime_real); + + type_pos = stream_get_endp(s); + stream_putc(s, 0); /* placeholder for down reason */ + + switch (peer->last_reset) { + case PEER_DOWN_NOTIFY_RECEIVED: + type = BMP_PEERDOWN_REMOTE_NOTIFY; + bmp_notify_put(s, &peer->notify); + break; + case PEER_DOWN_CLOSE_SESSION: + type = BMP_PEERDOWN_REMOTE_CLOSE; + break; + case PEER_DOWN_WAITING_NHT: + type = BMP_PEERDOWN_LOCAL_FSM; + stream_putw(s, BGP_FSM_TcpConnectionFails); + break; + /* + * TODO: Map remaining PEER_DOWN_* reasons to RFC event codes. + * TODO: Implement BMP_PEERDOWN_LOCAL_NOTIFY. + * + * See RFC7854 ss. 4.9 + */ + default: + type = BMP_PEERDOWN_LOCAL_FSM; + stream_putw(s, BMP_PEER_DOWN_NO_RELEVANT_EVENT_CODE); + break; + } + stream_putc_at(s, type_pos, type); + } + + len = stream_get_endp(s); + stream_putl_at(s, BMP_LENGTH_POS, len); /* message length is set. */ + return s; +} + + +static int bmp_send_peerup(struct bmp *bmp) +{ + struct peer *peer; + struct listnode *node; + struct stream *s; + + /* Walk down all peers */ + for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) { + s = bmp_peerstate(peer, false); + pullwr_write_stream(bmp->pullwr, s); + stream_free(s); + } + + return 0; +} + +/* XXX: kludge - filling the pullwr's buffer */ +static void bmp_send_all(struct bmp_bgp *bmpbgp, struct stream *s) +{ + struct bmp_targets *bt; + struct bmp *bmp; + + frr_each(bmp_targets, &bmpbgp->targets, bt) + frr_each(bmp_session, &bt->sessions, bmp) + pullwr_write_stream(bmp->pullwr, s); + stream_free(s); +} + +/* + * Route Mirroring + */ + +#define BMP_MIRROR_TLV_TYPE_BGP_MESSAGE 0 +#define BMP_MIRROR_TLV_TYPE_INFO 1 + +#define BMP_MIRROR_INFO_CODE_ERRORPDU 0 +#define BMP_MIRROR_INFO_CODE_LOSTMSGS 1 + +static struct bmp_mirrorq *bmp_pull_mirror(struct bmp *bmp) +{ + struct bmp_mirrorq *bmq; + + bmq = bmp->mirrorpos; + if (!bmq) + return NULL; + + bmp->mirrorpos = bmp_mirrorq_next(&bmp->targets->bmpbgp->mirrorq, bmq); + + bmq->refcount--; + if (!bmq->refcount) { + bmp->targets->bmpbgp->mirror_qsize -= sizeof(*bmq) + bmq->len; + bmp_mirrorq_del(&bmp->targets->bmpbgp->mirrorq, bmq); + } + return bmq; +} + +static void bmp_mirror_cull(struct bmp_bgp *bmpbgp) +{ + while (bmpbgp->mirror_qsize > bmpbgp->mirror_qsizelimit) { + struct bmp_mirrorq *bmq, *inner; + struct bmp_targets *bt; + struct bmp *bmp; + + bmq = bmp_mirrorq_first(&bmpbgp->mirrorq); + + frr_each(bmp_targets, &bmpbgp->targets, bt) { + if (!bt->mirror) + continue; + frr_each(bmp_session, &bt->sessions, bmp) { + if (bmp->mirrorpos != bmq) + continue; + + while ((inner = bmp_pull_mirror(bmp))) { + if (!inner->refcount) + XFREE(MTYPE_BMP_MIRRORQ, + inner); + } + + zlog_warn("bmp[%s] lost mirror messages due to buffer size limit", + bmp->remote); + bmp->mirror_lost = true; + pullwr_bump(bmp->pullwr); + } + } + } +} + +static int bmp_mirror_packet(struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *packet) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); + struct timeval tv; + struct bmp_mirrorq *qitem; + struct bmp_targets *bt; + struct bmp *bmp; + + frrtrace(3, frr_bgp, bmp_mirror_packet, peer, type, packet); + + gettimeofday(&tv, NULL); + + if (type == BGP_MSG_OPEN) { + struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer); + + XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); + + bbpeer->open_rx_len = size; + bbpeer->open_rx = XMALLOC(MTYPE_BMP_OPEN, size); + memcpy(bbpeer->open_rx, packet->data, size); + } + + if (!bmpbgp) + return 0; + + qitem = XCALLOC(MTYPE_BMP_MIRRORQ, sizeof(*qitem) + size); + qitem->peerid = peer->qobj_node.nid; + qitem->tv = tv; + qitem->len = size; + memcpy(qitem->data, packet->data, size); + + frr_each(bmp_targets, &bmpbgp->targets, bt) { + if (!bt->mirror) + continue; + frr_each(bmp_session, &bt->sessions, bmp) { + qitem->refcount++; + if (!bmp->mirrorpos) + bmp->mirrorpos = qitem; + pullwr_bump(bmp->pullwr); + } + } + if (qitem->refcount == 0) + XFREE(MTYPE_BMP_MIRRORQ, qitem); + else { + bmpbgp->mirror_qsize += sizeof(*qitem) + size; + bmp_mirrorq_add_tail(&bmpbgp->mirrorq, qitem); + + bmp_mirror_cull(bmpbgp); + + bmpbgp->mirror_qsizemax = MAX(bmpbgp->mirror_qsizemax, + bmpbgp->mirror_qsize); + } + return 0; +} + +static void bmp_wrmirror_lost(struct bmp *bmp, struct pullwr *pullwr) +{ + struct stream *s; + struct timeval tv; + + gettimeofday(&tv, NULL); + + s = stream_new(BGP_MAX_PACKET_SIZE); + + bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_ROUTE_MIRRORING); + bmp_per_peer_hdr(s, bmp->targets->bgp, bmp->targets->bgp->peer_self, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE, 0, &tv); + + stream_putw(s, BMP_MIRROR_TLV_TYPE_INFO); + stream_putw(s, 2); + stream_putw(s, BMP_MIRROR_INFO_CODE_LOSTMSGS); + stream_putl_at(s, BMP_LENGTH_POS, stream_get_endp(s)); + + bmp->cnt_mirror_overruns++; + pullwr_write_stream(bmp->pullwr, s); + stream_free(s); +} + +static bool bmp_wrmirror(struct bmp *bmp, struct pullwr *pullwr) +{ + struct bmp_mirrorq *bmq; + struct peer *peer; + bool written = false; + + if (bmp->mirror_lost) { + bmp_wrmirror_lost(bmp, pullwr); + bmp->mirror_lost = false; + return true; + } + + bmq = bmp_pull_mirror(bmp); + if (!bmq) + return false; + + peer = QOBJ_GET_TYPESAFE(bmq->peerid, peer); + if (!peer) { + zlog_info("bmp: skipping mirror message for deleted peer"); + goto out; + } + + struct stream *s; + s = stream_new(BGP_MAX_PACKET_SIZE); + + bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_ROUTE_MIRRORING); + bmp_per_peer_hdr(s, bmp->targets->bgp, peer, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE, 0, &bmq->tv); + + /* BMP Mirror TLV. */ + stream_putw(s, BMP_MIRROR_TLV_TYPE_BGP_MESSAGE); + stream_putw(s, bmq->len); + stream_putl_at(s, BMP_LENGTH_POS, stream_get_endp(s) + bmq->len); + + bmp->cnt_mirror++; + pullwr_write_stream(bmp->pullwr, s); + pullwr_write(bmp->pullwr, bmq->data, bmq->len); + + stream_free(s); + written = true; + +out: + if (!bmq->refcount) + XFREE(MTYPE_BMP_MIRRORQ, bmq); + return written; +} + +static int bmp_outgoing_packet(struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *packet) +{ + if (type == BGP_MSG_OPEN) { + frrtrace(2, frr_bgp, bmp_update_saved_open, peer, packet); + + struct bmp_bgp_peer *bbpeer = bmp_bgp_peer_get(peer); + + XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); + + bbpeer->open_tx_len = size; + bbpeer->open_tx = XMALLOC(MTYPE_BMP_OPEN, size); + memcpy(bbpeer->open_tx, packet->data, size); + } + return 0; +} + +static int bmp_peer_status_changed(struct peer *peer) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); + struct bmp_bgp_peer *bbpeer, *bbdopp; + + frrtrace(1, frr_bgp, bmp_peer_status_changed, peer); + + if (!bmpbgp) + return 0; + + if (peer->connection->status == Deleted) { + bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); + if (bbpeer) { + XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); + XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); + bmp_peerh_del(&bmp_peerh, bbpeer); + XFREE(MTYPE_BMP_PEER, bbpeer); + } + return 0; + } + + /* Check if this peer just went to Established */ + if ((peer->connection->ostatus != OpenConfirm) || + !(peer_established(peer->connection))) + return 0; + + if (peer->doppelganger && + (peer->doppelganger->connection->status != Deleted)) { + bbpeer = bmp_bgp_peer_get(peer); + bbdopp = bmp_bgp_peer_find(peer->doppelganger->qobj_node.nid); + if (bbdopp) { + XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); + XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); + + bbpeer->open_tx = bbdopp->open_tx; + bbpeer->open_tx_len = bbdopp->open_tx_len; + bbpeer->open_rx = bbdopp->open_rx; + bbpeer->open_rx_len = bbdopp->open_rx_len; + + bmp_peerh_del(&bmp_peerh, bbdopp); + XFREE(MTYPE_BMP_PEER, bbdopp); + } + } + + bmp_send_all(bmpbgp, bmp_peerstate(peer, false)); + return 0; +} + +static int bmp_peer_backward(struct peer *peer) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); + struct bmp_bgp_peer *bbpeer; + + frrtrace(1, frr_bgp, bmp_peer_backward_transition, peer); + + if (!bmpbgp) + return 0; + + bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); + if (bbpeer) { + XFREE(MTYPE_BMP_OPEN, bbpeer->open_tx); + bbpeer->open_tx_len = 0; + XFREE(MTYPE_BMP_OPEN, bbpeer->open_rx); + bbpeer->open_rx_len = 0; + } + + bmp_send_all(bmpbgp, bmp_peerstate(peer, true)); + return 0; +} + +static void bmp_eor(struct bmp *bmp, afi_t afi, safi_t safi, uint8_t flags, + uint8_t peer_type_flag) +{ + struct peer *peer; + struct listnode *node; + struct stream *s, *s2; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + + frrtrace(4, frr_bgp, bmp_eor, afi, safi, flags, peer_type_flag); + + s = stream_new(BGP_MAX_PACKET_SIZE); + + /* Make BGP update packet. */ + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* Unfeasible Routes Length */ + stream_putw(s, 0); + + if (afi == AFI_IP && safi == SAFI_UNICAST) { + /* Total Path Attribute Length */ + stream_putw(s, 0); + } else { + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + /* Total Path Attribute Length */ + stream_putw(s, 6); + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); + stream_putc(s, 3); + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + } + + bgp_packet_set_size(s); + + for (ALL_LIST_ELEMENTS_RO(bmp->targets->bgp->peer, node, peer)) { + if (!peer->afc_nego[afi][safi]) + continue; + + uint64_t peer_distinguisher = 0; + /* skip this message if peer distinguisher is not available */ + if (bmp_get_peer_distinguisher(bmp, afi, peer_type_flag, + &peer_distinguisher)) { + zlog_warn( + "skipping bmp message for reason: can't get peer distinguisher"); + continue; + } + + s2 = stream_new(BGP_MAX_PACKET_SIZE); + + bmp_common_hdr(s2, BMP_VERSION_3, + BMP_TYPE_ROUTE_MONITORING); + + bmp_per_peer_hdr(s2, bmp->targets->bgp, peer, flags, + peer_type_flag, peer_distinguisher, NULL); + + stream_putl_at(s2, BMP_LENGTH_POS, + stream_get_endp(s) + stream_get_endp(s2)); + + bmp->cnt_update++; + pullwr_write_stream(bmp->pullwr, s2); + pullwr_write_stream(bmp->pullwr, s); + stream_free(s2); + } + stream_free(s); +} + +static struct stream *bmp_update(const struct prefix *p, struct prefix_rd *prd, + struct peer *peer, struct attr *attr, + afi_t afi, safi_t safi, mpls_label_t *label, + uint32_t num_labels) +{ + struct bpacket_attr_vec_arr vecarr; + struct stream *s; + size_t attrlen_pos = 0, mpattrlen_pos = 0; + bgp_size_t total_attr_len = 0; + + bpacket_attr_vec_arr_reset(&vecarr); + + s = stream_new(BGP_MAX_PACKET_SIZE); + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* 2: withdrawn routes length */ + stream_putw(s, 0); + + /* 3: total attributes length - attrlen_pos stores the position */ + attrlen_pos = stream_get_endp(s); + stream_putw(s, 0); + + /* 5: Encode all the attributes, except MP_REACH_NLRI attr. */ + total_attr_len = + bgp_packet_attribute(NULL, peer, s, attr, &vecarr, NULL, afi, + safi, peer, NULL, NULL, 0, 0, 0, NULL); + + /* space check? */ + + /* peer_cap_enhe & add-path removed */ + if (afi == AFI_IP && safi == SAFI_UNICAST) + stream_put_prefix(s, p); + else { + size_t p1 = stream_get_endp(s); + + /* MPLS removed for now */ + + mpattrlen_pos = bgp_packet_mpattr_start(s, peer, afi, safi, + &vecarr, attr); + bgp_packet_mpattr_prefix(s, afi, safi, p, prd, label, + num_labels, 0, 0, attr); + bgp_packet_mpattr_end(s, mpattrlen_pos); + total_attr_len += stream_get_endp(s) - p1; + } + + /* set the total attribute length correctly */ + stream_putw_at(s, attrlen_pos, total_attr_len); + bgp_packet_set_size(s); + return s; +} + +static struct stream *bmp_withdraw(const struct prefix *p, + struct prefix_rd *prd, afi_t afi, + safi_t safi) +{ + struct stream *s; + size_t attrlen_pos = 0, mp_start, mplen_pos; + bgp_size_t total_attr_len = 0; + bgp_size_t unfeasible_len; + + s = stream_new(BGP_MAX_PACKET_SIZE); + + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + stream_putw(s, 0); + + if (afi == AFI_IP && safi == SAFI_UNICAST) { + stream_put_prefix(s, p); + unfeasible_len = stream_get_endp(s) - BGP_HEADER_SIZE + - BGP_UNFEASIBLE_LEN; + stream_putw_at(s, BGP_HEADER_SIZE, unfeasible_len); + stream_putw(s, 0); + } else { + attrlen_pos = stream_get_endp(s); + /* total attr length = 0 for now. reevaluate later */ + stream_putw(s, 0); + mp_start = stream_get_endp(s); + mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); + + bgp_packet_mpunreach_prefix(s, p, afi, safi, prd, NULL, 0, 0, 0, + NULL); + /* Set the mp_unreach attr's length */ + bgp_packet_mpunreach_end(s, mplen_pos); + + /* Set total path attribute length. */ + total_attr_len = stream_get_endp(s) - mp_start; + stream_putw_at(s, attrlen_pos, total_attr_len); + } + + bgp_packet_set_size(s); + return s; +} + +static void bmp_monitor(struct bmp *bmp, struct peer *peer, uint8_t flags, + uint8_t peer_type_flag, const struct prefix *p, + struct prefix_rd *prd, struct attr *attr, afi_t afi, + safi_t safi, time_t uptime, mpls_label_t *label, + uint32_t num_labels) +{ + struct stream *hdr, *msg; + struct timeval tv = { .tv_sec = uptime, .tv_usec = 0 }; + struct timeval uptime_real; + + uint64_t peer_distinguisher = 0; + /* skip this message if peer distinguisher is not available */ + if (bmp_get_peer_distinguisher(bmp, afi, peer_type_flag, + &peer_distinguisher)) { + zlog_warn( + "skipping bmp message for reason: can't get peer distinguisher"); + return; + } + + monotime_to_realtime(&tv, &uptime_real); + if (attr) + msg = bmp_update(p, prd, peer, attr, afi, safi, label, + num_labels); + else + msg = bmp_withdraw(p, prd, afi, safi); + + hdr = stream_new(BGP_MAX_PACKET_SIZE); + bmp_common_hdr(hdr, BMP_VERSION_3, BMP_TYPE_ROUTE_MONITORING); + bmp_per_peer_hdr(hdr, bmp->targets->bgp, peer, flags, peer_type_flag, + peer_distinguisher, + uptime == (time_t)(-1L) ? NULL : &uptime_real); + + stream_putl_at(hdr, BMP_LENGTH_POS, + stream_get_endp(hdr) + stream_get_endp(msg)); + + bmp->cnt_update++; + pullwr_write_stream(bmp->pullwr, hdr); + pullwr_write_stream(bmp->pullwr, msg); + stream_free(hdr); + stream_free(msg); +} + +static bool bmp_wrsync(struct bmp *bmp, struct pullwr *pullwr) +{ + uint8_t bpi_num_labels; + afi_t afi; + safi_t safi; + + if (bmp->syncafi == AFI_MAX) { + FOREACH_AFI_SAFI (afi, safi) { + if (bmp->afistate[afi][safi] != BMP_AFI_NEEDSYNC) + continue; + + bmp->afistate[afi][safi] = BMP_AFI_SYNC; + + bmp->syncafi = afi; + bmp->syncsafi = safi; + bmp->syncpeerid = 0; + memset(&bmp->syncpos, 0, sizeof(bmp->syncpos)); + bmp->syncpos.family = afi2family(afi); + bmp->syncrdpos = NULL; + zlog_info("bmp[%s] %s %s sending table", + bmp->remote, + afi2str(bmp->syncafi), + safi2str(bmp->syncsafi)); + /* break does not work here, 2 loops... */ + goto afibreak; + } + if (bmp->syncafi == AFI_MAX) + return false; + } + +afibreak: + afi = bmp->syncafi; + safi = bmp->syncsafi; + + if (!bmp->targets->afimon[afi][safi]) { + /* shouldn't happen */ + bmp->afistate[afi][safi] = BMP_AFI_INACTIVE; + bmp->syncafi = AFI_MAX; + bmp->syncsafi = SAFI_MAX; + return true; + } + + struct bgp_table *table = bmp->targets->bgp->rib[afi][safi]; + struct bgp_dest *bn = NULL; + struct bgp_path_info *bpi = NULL, *bpiter; + struct bgp_adj_in *adjin = NULL, *adjiter; + + if ((afi == AFI_L2VPN && safi == SAFI_EVPN) || + (safi == SAFI_MPLS_VPN)) { + /* initialize syncrdpos to the first + * mid-layer table entry + */ + if (!bmp->syncrdpos) { + bmp->syncrdpos = bgp_table_top(table); + if (!bmp->syncrdpos) + goto eor; + } + + /* look for a valid mid-layer table */ + do { + table = bgp_dest_get_bgp_table_info(bmp->syncrdpos); + if (table) { + break; + } + bmp->syncrdpos = bgp_route_next(bmp->syncrdpos); + } while (bmp->syncrdpos); + + /* mid-layer table completed */ + if (!bmp->syncrdpos) + goto eor; + } + + bn = bgp_node_lookup(table, &bmp->syncpos); + do { + if (!bn) { + bn = bgp_table_get_next(table, &bmp->syncpos); + if (!bn) { + if ((afi == AFI_L2VPN && safi == SAFI_EVPN) || + (safi == SAFI_MPLS_VPN)) { + /* reset bottom-layer pointer */ + memset(&bmp->syncpos, 0, + sizeof(bmp->syncpos)); + bmp->syncpos.family = afi2family(afi); + /* check whethere there is a valid + * next mid-layer table, otherwise + * declare table completed (eor) + */ + for (bmp->syncrdpos = bgp_route_next( + bmp->syncrdpos); + bmp->syncrdpos; + bmp->syncrdpos = bgp_route_next( + bmp->syncrdpos)) + if (bgp_dest_get_bgp_table_info( + bmp->syncrdpos)) + return true; + } + eor: + zlog_info("bmp[%s] %s %s table completed (EoR)", + bmp->remote, afi2str(afi), + safi2str(safi)); + + bmp_eor(bmp, afi, safi, BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE); + bmp_eor(bmp, afi, safi, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE); + bmp_eor(bmp, afi, safi, 0, + BMP_PEER_TYPE_LOC_RIB_INSTANCE); + + bmp->afistate[afi][safi] = BMP_AFI_LIVE; + bmp->syncafi = AFI_MAX; + bmp->syncsafi = SAFI_MAX; + return true; + } + bmp->syncpeerid = 0; + prefix_copy(&bmp->syncpos, bgp_dest_get_prefix(bn)); + } + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_POSTPOLICY) || + CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_LOC_RIB)) { + for (bpiter = bgp_dest_get_bgp_path_info(bn); bpiter; + bpiter = bpiter->next) { + if (!CHECK_FLAG(bpiter->flags, + BGP_PATH_VALID) && + !CHECK_FLAG(bpiter->flags, + BGP_PATH_SELECTED)) + continue; + if (bpiter->peer->qobj_node.nid + <= bmp->syncpeerid) + continue; + if (bpi && bpiter->peer->qobj_node.nid + > bpi->peer->qobj_node.nid) + continue; + bpi = bpiter; + } + } + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], + BMP_MON_PREPOLICY)) { + for (adjiter = bn->adj_in; adjiter; + adjiter = adjiter->next) { + if (adjiter->peer->qobj_node.nid + <= bmp->syncpeerid) + continue; + if (adjin && adjiter->peer->qobj_node.nid + > adjin->peer->qobj_node.nid) + continue; + adjin = adjiter; + } + } + if (bpi || adjin) + break; + + bn = NULL; + } while (1); + + if (adjin && bpi + && adjin->peer->qobj_node.nid < bpi->peer->qobj_node.nid) { + bpi = NULL; + bmp->syncpeerid = adjin->peer->qobj_node.nid; + } else if (adjin && bpi + && adjin->peer->qobj_node.nid > bpi->peer->qobj_node.nid) { + adjin = NULL; + bmp->syncpeerid = bpi->peer->qobj_node.nid; + } else if (bpi) { + bmp->syncpeerid = bpi->peer->qobj_node.nid; + } else if (adjin) { + bmp->syncpeerid = adjin->peer->qobj_node.nid; + } + + const struct prefix *bn_p = bgp_dest_get_prefix(bn); + struct prefix_rd *prd = NULL; + if (((afi == AFI_L2VPN) && (safi == SAFI_EVPN)) || + (safi == SAFI_MPLS_VPN)) + prd = (struct prefix_rd *)bgp_dest_get_prefix(bmp->syncrdpos); + + bpi_num_labels = BGP_PATH_INFO_NUM_LABELS(bpi); + + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED) && + CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_LOC_RIB)) { + bmp_monitor(bmp, bpi->peer, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, + bn_p, prd, bpi->attr, afi, safi, + bpi && bpi->extra ? bpi->extra->bgp_rib_uptime + : (time_t)(-1L), + bpi_num_labels ? bpi->extra->labels->label : NULL, + bpi_num_labels); + } + + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_VALID) && + CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_POSTPOLICY)) + bmp_monitor(bmp, bpi->peer, BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE, bn_p, prd, bpi->attr, + afi, safi, bpi->uptime, + bpi_num_labels ? bpi->extra->labels->label : NULL, + bpi_num_labels); + + if (adjin) + /* TODO: set label here when adjin supports labels */ + bmp_monitor(bmp, adjin->peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, + bn_p, prd, adjin->attr, afi, safi, adjin->uptime, + NULL, 0); + + if (bn) + bgp_dest_unlock_node(bn); + + return true; +} + +static struct bmp_queue_entry * +bmp_pull_from_queue(struct bmp_qlist_head *list, struct bmp_qhash_head *hash, + struct bmp_queue_entry **queuepos_ptr) +{ + struct bmp_queue_entry *bqe; + + bqe = *queuepos_ptr; + if (!bqe) + return NULL; + + *queuepos_ptr = bmp_qlist_next(list, bqe); + + bqe->refcount--; + if (!bqe->refcount) { + bmp_qhash_del(hash, bqe); + bmp_qlist_del(list, bqe); + } + return bqe; +} + +static inline struct bmp_queue_entry *bmp_pull(struct bmp *bmp) +{ + return bmp_pull_from_queue(&bmp->targets->updlist, + &bmp->targets->updhash, &bmp->queuepos); +} + +static inline struct bmp_queue_entry *bmp_pull_locrib(struct bmp *bmp) +{ + return bmp_pull_from_queue(&bmp->targets->locupdlist, + &bmp->targets->locupdhash, + &bmp->locrib_queuepos); +} + +/* TODO BMP_MON_LOCRIB find a way to merge properly this function with + * bmp_wrqueue or abstract it if possible + */ +static bool bmp_wrqueue_locrib(struct bmp *bmp, struct pullwr *pullwr) +{ + + struct bmp_queue_entry *bqe; + struct peer *peer; + struct bgp_dest *bn = NULL; + bool written = false; + uint8_t bpi_num_labels; + + bqe = bmp_pull_locrib(bmp); + if (!bqe) + return false; + + afi_t afi = bqe->afi; + safi_t safi = bqe->safi; + + if (!CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_LOC_RIB)) + goto out; + + switch (bmp->afistate[afi][safi]) { + case BMP_AFI_INACTIVE: + case BMP_AFI_NEEDSYNC: + goto out; + case BMP_AFI_SYNC: + if (prefix_cmp(&bqe->p, &bmp->syncpos) <= 0) + /* currently syncing but have already passed this + * prefix => send it. + */ + break; + + /* currently syncing & haven't reached this prefix yet + * => it'll be sent as part of the table sync, no need here + */ + goto out; + case BMP_AFI_LIVE: + break; + } + + peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); + if (!peer) { + /* skipping queued item for deleted peer + */ + goto out; + } + if (peer != bmp->targets->bgp->peer_self && !peer_established(peer->connection)) { + /* peer is neither self, nor established + */ + goto out; + } + + bool is_vpn = (bqe->afi == AFI_L2VPN && bqe->safi == SAFI_EVPN) || + (bqe->safi == SAFI_MPLS_VPN); + + struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL; + + bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi, + &bqe->p, prd); + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + if (!CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED)) + continue; + if (bpi->peer == peer) + break; + } + + bpi_num_labels = BGP_PATH_INFO_NUM_LABELS(bpi); + + bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_LOC_RIB_INSTANCE, &bqe->p, prd, + bpi ? bpi->attr : NULL, afi, safi, + bpi && bpi->extra ? bpi->extra->bgp_rib_uptime + : (time_t)(-1L), + bpi_num_labels ? bpi->extra->labels->label : NULL, + bpi_num_labels); + written = true; + +out: + if (!bqe->refcount) + XFREE(MTYPE_BMP_QUEUE, bqe); + + if (bn) + bgp_dest_unlock_node(bn); + + return written; +} + +static bool bmp_wrqueue(struct bmp *bmp, struct pullwr *pullwr) +{ + struct bmp_queue_entry *bqe; + struct peer *peer; + struct bgp_dest *bn = NULL; + bool written = false; + uint8_t bpi_num_labels; + + bqe = bmp_pull(bmp); + if (!bqe) + return false; + + afi_t afi = bqe->afi; + safi_t safi = bqe->safi; + + switch (bmp->afistate[afi][safi]) { + case BMP_AFI_INACTIVE: + case BMP_AFI_NEEDSYNC: + goto out; + case BMP_AFI_SYNC: + if (prefix_cmp(&bqe->p, &bmp->syncpos) <= 0) + /* currently syncing but have already passed this + * prefix => send it. */ + break; + + /* currently syncing & haven't reached this prefix yet + * => it'll be sent as part of the table sync, no need here */ + goto out; + case BMP_AFI_LIVE: + break; + } + + peer = QOBJ_GET_TYPESAFE(bqe->peerid, peer); + if (!peer) { + zlog_info("bmp: skipping queued item for deleted peer"); + goto out; + } + if (!peer_established(peer->connection)) + goto out; + + bool is_vpn = (bqe->afi == AFI_L2VPN && bqe->safi == SAFI_EVPN) || + (bqe->safi == SAFI_MPLS_VPN); + + struct prefix_rd *prd = is_vpn ? &bqe->rd : NULL; + bn = bgp_safi_node_lookup(bmp->targets->bgp->rib[afi][safi], safi, + &bqe->p, prd); + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_POSTPOLICY)) { + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; + bpi = bpi->next) { + if (!CHECK_FLAG(bpi->flags, BGP_PATH_VALID)) + continue; + if (bpi->peer == peer) + break; + } + + bpi_num_labels = BGP_PATH_INFO_NUM_LABELS(bpi); + + bmp_monitor(bmp, peer, BMP_PEER_FLAG_L, + BMP_PEER_TYPE_GLOBAL_INSTANCE, &bqe->p, prd, + bpi ? bpi->attr : NULL, afi, safi, + bpi ? bpi->uptime : monotime(NULL), + bpi_num_labels ? bpi->extra->labels->label : NULL, + bpi_num_labels); + written = true; + } + + if (CHECK_FLAG(bmp->targets->afimon[afi][safi], BMP_MON_PREPOLICY)) { + struct bgp_adj_in *adjin; + + for (adjin = bn ? bn->adj_in : NULL; adjin; + adjin = adjin->next) { + if (adjin->peer == peer) + break; + } + /* TODO: set label here when adjin supports labels */ + bmp_monitor(bmp, peer, 0, BMP_PEER_TYPE_GLOBAL_INSTANCE, + &bqe->p, prd, adjin ? adjin->attr : NULL, afi, safi, + adjin ? adjin->uptime : monotime(NULL), NULL, 0); + written = true; + } + +out: + if (!bqe->refcount) + XFREE(MTYPE_BMP_QUEUE, bqe); + + if (bn) + bgp_dest_unlock_node(bn); + + return written; +} + +static void bmp_wrfill(struct bmp *bmp, struct pullwr *pullwr) +{ + switch(bmp->state) { + case BMP_PeerUp: + bmp_send_peerup(bmp); + bmp->state = BMP_Run; + break; + + case BMP_Run: + if (bmp_wrmirror(bmp, pullwr)) + break; + if (bmp_wrqueue(bmp, pullwr)) + break; + if (bmp_wrqueue_locrib(bmp, pullwr)) + break; + if (bmp_wrsync(bmp, pullwr)) + break; + break; + } +} + +static void bmp_wrerr(struct bmp *bmp, struct pullwr *pullwr, bool eof) +{ + if (eof) + zlog_info("bmp[%s] disconnected", bmp->remote); + else + flog_warn(EC_LIB_SYSTEM_CALL, "bmp[%s] connection error: %s", + bmp->remote, strerror(errno)); + + bmp_close(bmp); + bmp_free(bmp); +} + +static struct bmp_queue_entry * +bmp_process_one(struct bmp_targets *bt, struct bmp_qhash_head *updhash, + struct bmp_qlist_head *updlist, struct bgp *bgp, afi_t afi, + safi_t safi, struct bgp_dest *bn, struct peer *peer) +{ + struct bmp_queue_entry *bqe, bqeref; + size_t refcount; + + refcount = bmp_session_count(&bt->sessions); + if (refcount == 0) + return NULL; + + memset(&bqeref, 0, sizeof(bqeref)); + prefix_copy(&bqeref.p, bgp_dest_get_prefix(bn)); + bqeref.peerid = peer->qobj_node.nid; + bqeref.afi = afi; + bqeref.safi = safi; + + if ((afi == AFI_L2VPN && safi == SAFI_EVPN && bn->pdest) || + (safi == SAFI_MPLS_VPN)) + prefix_copy(&bqeref.rd, + (struct prefix_rd *)bgp_dest_get_prefix(bn->pdest)); + + bqe = bmp_qhash_find(updhash, &bqeref); + if (bqe) { + if (bqe->refcount >= refcount) + /* nothing to do here */ + return NULL; + + bmp_qlist_del(updlist, bqe); + } else { + bqe = XMALLOC(MTYPE_BMP_QUEUE, sizeof(*bqe)); + memcpy(bqe, &bqeref, sizeof(*bqe)); + + bmp_qhash_add(updhash, bqe); + } + + bqe->refcount = refcount; + bmp_qlist_add_tail(updlist, bqe); + + return bqe; + + /* need to update correct queue pos for all sessions of the target after + * a call to this function + */ +} + +static int bmp_process(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *bn, struct peer *peer, bool withdraw) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(peer->bgp); + struct bmp_targets *bt; + struct bmp *bmp; + + if (frrtrace_enabled(frr_bgp, bmp_process)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(&bn->rn->p, pfxprint, sizeof(pfxprint)); + frrtrace(5, frr_bgp, bmp_process, peer, pfxprint, afi, safi, + withdraw); + } + + if (!bmpbgp) + return 0; + + frr_each(bmp_targets, &bmpbgp->targets, bt) { + /* check if any monitoring is enabled (ignoring loc-rib since it + * uses another hook & queue + */ + if (!CHECK_FLAG(bt->afimon[afi][safi], ~BMP_MON_LOC_RIB)) + continue; + + struct bmp_queue_entry *last_item = + bmp_process_one(bt, &bt->updhash, &bt->updlist, bgp, + afi, safi, bn, peer); + + /* if bmp_process_one returns NULL + * we don't have anything to do next + */ + if (!last_item) + continue; + + frr_each(bmp_session, &bt->sessions, bmp) { + if (!bmp->queuepos) + bmp->queuepos = last_item; + + pullwr_bump(bmp->pullwr); + } + } + return 0; +} + +static void bmp_stat_put_u32(struct stream *s, size_t *cnt, uint16_t type, + uint32_t value) +{ + stream_putw(s, type); + stream_putw(s, 4); + stream_putl(s, value); + (*cnt)++; +} + +static void bmp_stat_put_u64(struct stream *s, size_t *cnt, uint16_t type, + uint64_t value) +{ + stream_putw(s, type); + stream_putw(s, 8); + stream_putq(s, value); + (*cnt)++; +} + +static void bmp_stats(struct event *thread) +{ + struct bmp_targets *bt = EVENT_ARG(thread); + struct stream *s; + struct peer *peer; + struct listnode *node; + struct timeval tv; + + if (bt->stat_msec) + event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, + &bt->t_stats); + + gettimeofday(&tv, NULL); + + /* Walk down all peers */ + for (ALL_LIST_ELEMENTS_RO(bt->bgp->peer, node, peer)) { + size_t count = 0, count_pos, len; + + if (!peer_established(peer->connection)) + continue; + + s = stream_new(BGP_MAX_PACKET_SIZE); + bmp_common_hdr(s, BMP_VERSION_3, BMP_TYPE_STATISTICS_REPORT); + bmp_per_peer_hdr(s, bt->bgp, peer, 0, + BMP_PEER_TYPE_GLOBAL_INSTANCE, 0, &tv); + + count_pos = stream_get_endp(s); + stream_putl(s, 0); + + bmp_stat_put_u32(s, &count, BMP_STATS_PFX_REJECTED, + peer->stat_pfx_filter); + bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ASPATH, + peer->stat_pfx_aspath_loop); + bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_ORIGINATOR, + peer->stat_pfx_originator_loop); + bmp_stat_put_u32(s, &count, BMP_STATS_UPD_LOOP_CLUSTER, + peer->stat_pfx_cluster_loop); + bmp_stat_put_u32(s, &count, BMP_STATS_PFX_DUP_WITHDRAW, + peer->stat_pfx_dup_withdraw); + bmp_stat_put_u32(s, &count, BMP_STATS_UPD_7606_WITHDRAW, + peer->stat_upd_7606); + if (bt->stats_send_experimental) + bmp_stat_put_u32(s, &count, BMP_STATS_FRR_NH_INVALID, + peer->stat_pfx_nh_invalid); + bmp_stat_put_u64(s, &count, BMP_STATS_SIZE_ADJ_RIB_IN, + peer->stat_pfx_adj_rib_in); + bmp_stat_put_u64(s, &count, BMP_STATS_SIZE_LOC_RIB, + peer->stat_pfx_loc_rib); + + stream_putl_at(s, count_pos, count); + + len = stream_get_endp(s); + stream_putl_at(s, BMP_LENGTH_POS, len); + + bmp_send_all(bt->bmpbgp, s); + } +} + +/* read from the BMP socket to detect session termination */ +static void bmp_read(struct event *t) +{ + struct bmp *bmp = EVENT_ARG(t); + char buf[1024]; + ssize_t n; + + bmp->t_read = NULL; + + n = read(bmp->socket, buf, sizeof(buf)); + if (n >= 1) { + zlog_info("bmp[%s]: unexpectedly received %zu bytes", bmp->remote, n); + } else if (n == 0) { + /* the TCP session was terminated by the far end */ + bmp_wrerr(bmp, NULL, true); + return; + } else if (!(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) { + /* the TCP session experienced a fatal error, likely a timeout */ + bmp_wrerr(bmp, NULL, false); + return; + } + + event_add_read(bm->master, bmp_read, bmp, bmp->socket, &bmp->t_read); +} + +static struct bmp *bmp_open(struct bmp_targets *bt, int bmp_sock) +{ + union sockunion su, *sumem; + struct prefix p; + int on = 1; + struct access_list *acl = NULL; + enum filter_type ret; + char buf[SU_ADDRSTRLEN]; + struct bmp *bmp; + + sumem = sockunion_getpeername(bmp_sock); + if (!sumem) { + close(bmp_sock); + return NULL; + } + memcpy(&su, sumem, sizeof(su)); + sockunion_free(sumem); + + set_nonblocking(bmp_sock); + set_cloexec(bmp_sock); + + if (!sockunion2hostprefix(&su, &p)) { + close(bmp_sock); + return NULL; + } + + acl = NULL; + switch (p.family) { + case AF_INET: + acl = access_list_lookup(AFI_IP, bt->acl_name); + break; + case AF_INET6: + acl = access_list_lookup(AFI_IP6, bt->acl6_name); + break; + default: + break; + } + + ret = FILTER_PERMIT; + if (acl) { + ret = access_list_apply(acl, &p); + } + + sockunion2str(&su, buf, SU_ADDRSTRLEN); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ":%u", + su.sa.sa_family == AF_INET + ? ntohs(su.sin.sin_port) + : ntohs(su.sin6.sin6_port)); + + if (ret == FILTER_DENY) { + bt->cnt_aclrefused++; + zlog_info("bmp[%s] connection refused by access-list", buf); + close(bmp_sock); + return NULL; + } + bt->cnt_accept++; + + if (setsockopt(bmp_sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) + flog_err(EC_LIB_SOCKET, "bmp: %d can't setsockopt SO_KEEPALIVE: %s(%d)", + bmp_sock, safe_strerror(errno), errno); + if (setsockopt(bmp_sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) + flog_err(EC_LIB_SOCKET, "bmp: %d can't setsockopt TCP_NODELAY: %s(%d)", + bmp_sock, safe_strerror(errno), errno); + + zlog_info("bmp[%s] connection established", buf); + + /* Allocate new BMP structure and set up default values. */ + bmp = bmp_new(bt, bmp_sock); + strlcpy(bmp->remote, buf, sizeof(bmp->remote)); + + bmp->state = BMP_PeerUp; + bmp->pullwr = pullwr_new(bm->master, bmp_sock, bmp, bmp_wrfill, + bmp_wrerr); + event_add_read(bm->master, bmp_read, bmp, bmp_sock, &bmp->t_read); + bmp_send_initiation(bmp); + + return bmp; +} + +/* Accept BMP connection. */ +static void bmp_accept(struct event *thread) +{ + union sockunion su; + struct bmp_listener *bl = EVENT_ARG(thread); + int bmp_sock; + + /* We continue hearing BMP socket. */ + event_add_read(bm->master, bmp_accept, bl, bl->sock, &bl->t_accept); + + memset(&su, 0, sizeof(union sockunion)); + + /* We can handle IPv4 or IPv6 socket. */ + bmp_sock = sockunion_accept(bl->sock, &su); + if (bmp_sock < 0) { + zlog_info("bmp: accept_sock failed: %s", safe_strerror(errno)); + return; + } + bmp_open(bl->targets, bmp_sock); +} + +static void bmp_close(struct bmp *bmp) +{ + struct bmp_queue_entry *bqe; + struct bmp_mirrorq *bmq; + + EVENT_OFF(bmp->t_read); + + if (bmp->active) + bmp_active_disconnected(bmp->active); + + while ((bmq = bmp_pull_mirror(bmp))) + if (!bmq->refcount) + XFREE(MTYPE_BMP_MIRRORQ, bmq); + while ((bqe = bmp_pull(bmp))) + if (!bqe->refcount) + XFREE(MTYPE_BMP_QUEUE, bqe); + while ((bqe = bmp_pull_locrib(bmp))) + if (!bqe->refcount) + XFREE(MTYPE_BMP_QUEUE, bqe); + + EVENT_OFF(bmp->t_read); + pullwr_del(bmp->pullwr); + close(bmp->socket); +} + +static struct bmp_bgp *bmp_bgp_find(struct bgp *bgp) +{ + struct bmp_bgp dummy = { .bgp = bgp }; + return bmp_bgph_find(&bmp_bgph, &dummy); +} + +static struct bmp_bgp *bmp_bgp_get(struct bgp *bgp) +{ + struct bmp_bgp *bmpbgp; + + bmpbgp = bmp_bgp_find(bgp); + if (bmpbgp) + return bmpbgp; + + bmpbgp = XCALLOC(MTYPE_BMP, sizeof(*bmpbgp)); + bmpbgp->bgp = bgp; + bmpbgp->mirror_qsizelimit = ~0UL; + bmp_mirrorq_init(&bmpbgp->mirrorq); + bmp_bgph_add(&bmp_bgph, bmpbgp); + + return bmpbgp; +} + +static void bmp_bgp_put(struct bmp_bgp *bmpbgp) +{ + struct bmp_targets *bt; + struct bmp_listener *bl; + + bmp_bgph_del(&bmp_bgph, bmpbgp); + + frr_each_safe (bmp_targets, &bmpbgp->targets, bt) { + frr_each_safe (bmp_listeners, &bt->listeners, bl) + bmp_listener_put(bl); + + bmp_targets_put(bt); + } + + bmp_mirrorq_fini(&bmpbgp->mirrorq); + XFREE(MTYPE_BMP, bmpbgp); +} + +static int bmp_bgp_del(struct bgp *bgp) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); + + if (bmpbgp) + bmp_bgp_put(bmpbgp); + return 0; +} + +static struct bmp_bgp_peer *bmp_bgp_peer_find(uint64_t peerid) +{ + struct bmp_bgp_peer dummy = { .peerid = peerid }; + return bmp_peerh_find(&bmp_peerh, &dummy); +} + +static struct bmp_bgp_peer *bmp_bgp_peer_get(struct peer *peer) +{ + struct bmp_bgp_peer *bbpeer; + + bbpeer = bmp_bgp_peer_find(peer->qobj_node.nid); + if (bbpeer) + return bbpeer; + + bbpeer = XCALLOC(MTYPE_BMP_PEER, sizeof(*bbpeer)); + bbpeer->peerid = peer->qobj_node.nid; + bmp_peerh_add(&bmp_peerh, bbpeer); + + return bbpeer; +} + +static struct bmp_targets *bmp_targets_find1(struct bgp *bgp, const char *name) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); + struct bmp_targets dummy; + + if (!bmpbgp) + return NULL; + dummy.name = (char *)name; + return bmp_targets_find(&bmpbgp->targets, &dummy); +} + +static struct bmp_targets *bmp_targets_get(struct bgp *bgp, const char *name) +{ + struct bmp_targets *bt; + + bt = bmp_targets_find1(bgp, name); + if (bt) + return bt; + + bt = XCALLOC(MTYPE_BMP_TARGETS, sizeof(*bt)); + bt->name = XSTRDUP(MTYPE_BMP_TARGETSNAME, name); + bt->bgp = bgp; + bt->bmpbgp = bmp_bgp_get(bgp); + bt->stats_send_experimental = true; + bmp_session_init(&bt->sessions); + bmp_qhash_init(&bt->updhash); + bmp_qlist_init(&bt->updlist); + bmp_qhash_init(&bt->locupdhash); + bmp_qlist_init(&bt->locupdlist); + bmp_actives_init(&bt->actives); + bmp_listeners_init(&bt->listeners); + + QOBJ_REG(bt, bmp_targets); + bmp_targets_add(&bt->bmpbgp->targets, bt); + return bt; +} + +static void bmp_targets_put(struct bmp_targets *bt) +{ + struct bmp *bmp; + struct bmp_active *ba; + + EVENT_OFF(bt->t_stats); + + frr_each_safe (bmp_actives, &bt->actives, ba) + bmp_active_put(ba); + + frr_each_safe(bmp_session, &bt->sessions, bmp) { + bmp_close(bmp); + bmp_free(bmp); + } + + bmp_targets_del(&bt->bmpbgp->targets, bt); + QOBJ_UNREG(bt); + + bmp_listeners_fini(&bt->listeners); + bmp_actives_fini(&bt->actives); + bmp_qhash_fini(&bt->updhash); + bmp_qlist_fini(&bt->updlist); + bmp_qhash_fini(&bt->locupdhash); + bmp_qlist_fini(&bt->locupdlist); + + XFREE(MTYPE_BMP_ACLNAME, bt->acl_name); + XFREE(MTYPE_BMP_ACLNAME, bt->acl6_name); + bmp_session_fini(&bt->sessions); + + XFREE(MTYPE_BMP_TARGETSNAME, bt->name); + XFREE(MTYPE_BMP_TARGETS, bt); +} + +static struct bmp_listener *bmp_listener_find(struct bmp_targets *bt, + const union sockunion *su, + int port) +{ + struct bmp_listener dummy; + dummy.addr = *su; + dummy.port = port; + return bmp_listeners_find(&bt->listeners, &dummy); +} + +static struct bmp_listener *bmp_listener_get(struct bmp_targets *bt, + const union sockunion *su, + int port) +{ + struct bmp_listener *bl = bmp_listener_find(bt, su, port); + + if (bl) + return bl; + + bl = XCALLOC(MTYPE_BMP_LISTENER, sizeof(*bl)); + bl->targets = bt; + bl->addr = *su; + bl->port = port; + bl->sock = -1; + + bmp_listeners_add(&bt->listeners, bl); + return bl; +} + +static void bmp_listener_start(struct bmp_listener *bl) +{ + int sock, ret; + + sock = socket(bl->addr.sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) + return; + + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + sockopt_v6only(bl->addr.sa.sa_family, sock); + set_cloexec(sock); + + ret = sockunion_bind(sock, &bl->addr, bl->port, &bl->addr); + if (ret < 0) + goto out_sock; + + ret = listen(sock, 3); + if (ret < 0) + goto out_sock; + + bl->sock = sock; + event_add_read(bm->master, bmp_accept, bl, sock, &bl->t_accept); + return; +out_sock: + close(sock); +} + +static void bmp_listener_stop(struct bmp_listener *bl) +{ + EVENT_OFF(bl->t_accept); + + if (bl->sock != -1) + close(bl->sock); + bl->sock = -1; +} + +static struct bmp_active *bmp_active_find(struct bmp_targets *bt, + const char *hostname, int port) +{ + struct bmp_active dummy; + dummy.hostname = (char *)hostname; + dummy.port = port; + return bmp_actives_find(&bt->actives, &dummy); +} + +static struct bmp_active *bmp_active_get(struct bmp_targets *bt, + const char *hostname, int port) +{ + struct bmp_active *ba; + + ba = bmp_active_find(bt, hostname, port); + if (ba) + return ba; + + ba = XCALLOC(MTYPE_BMP_ACTIVE, sizeof(*ba)); + ba->targets = bt; + ba->hostname = XSTRDUP(MTYPE_TMP, hostname); + ba->port = port; + ba->minretry = BMP_DFLT_MINRETRY; + ba->maxretry = BMP_DFLT_MAXRETRY; + ba->socket = -1; + + bmp_actives_add(&bt->actives, ba); + return ba; +} + +static void bmp_active_put(struct bmp_active *ba) +{ + EVENT_OFF(ba->t_timer); + EVENT_OFF(ba->t_read); + EVENT_OFF(ba->t_write); + + bmp_actives_del(&ba->targets->actives, ba); + + if (ba->bmp) { + ba->bmp->active = NULL; + bmp_close(ba->bmp); + bmp_free(ba->bmp); + } + if (ba->socket != -1) + close(ba->socket); + + XFREE(MTYPE_TMP, ba->ifsrc); + XFREE(MTYPE_TMP, ba->hostname); + XFREE(MTYPE_BMP_ACTIVE, ba); +} + +static void bmp_active_setup(struct bmp_active *ba); + +static void bmp_active_connect(struct bmp_active *ba) +{ + enum connect_result res; + struct interface *ifp; + vrf_id_t vrf_id = VRF_DEFAULT; + int res_bind; + + for (; ba->addrpos < ba->addrtotal; ba->addrpos++) { + if (ba->ifsrc) { + if (ba->targets && ba->targets->bgp) + vrf_id = ba->targets->bgp->vrf_id; + + /* find interface and related */ + /* address with same family */ + ifp = if_lookup_by_name(ba->ifsrc, vrf_id); + if (!ifp) { + zlog_warn("bmp[%s]: failed to find interface", + ba->ifsrc); + continue; + } + + if (bgp_update_address(ifp, &ba->addrs[ba->addrpos], + &ba->addrsrc)){ + zlog_warn("bmp[%s]: failed to find matching address", + ba->ifsrc); + continue; + } + zlog_info("bmp[%s]: selected source address : %pSU", + ba->ifsrc, &ba->addrsrc); + } + + ba->socket = sockunion_socket(&ba->addrs[ba->addrpos]); + if (ba->socket < 0) { + zlog_warn("bmp[%s]: failed to create socket", + ba->hostname); + continue; + } + + set_nonblocking(ba->socket); + + if (!sockunion_is_null(&ba->addrsrc)) { + res_bind = sockunion_bind(ba->socket, &ba->addrsrc, 0, + &ba->addrsrc); + if (res_bind < 0) { + zlog_warn( + "bmp[%s]: no bind currently to source address %pSU:%d", + ba->hostname, &ba->addrsrc, ba->port); + close(ba->socket); + ba->socket = -1; + sockunion_init(&ba->addrsrc); + continue; + } + } + + + res = sockunion_connect(ba->socket, &ba->addrs[ba->addrpos], + htons(ba->port), 0); + switch (res) { + case connect_error: + zlog_warn("bmp[%s]: failed to connect to %pSU:%d", + ba->hostname, &ba->addrs[ba->addrpos], + ba->port); + close(ba->socket); + ba->socket = -1; + sockunion_init(&ba->addrsrc); + continue; + case connect_success: + zlog_info("bmp[%s]: connected to %pSU:%d", + ba->hostname, &ba->addrs[ba->addrpos], + ba->port); + break; + case connect_in_progress: + zlog_warn("bmp[%s]: connect in progress %pSU:%d", + ba->hostname, &ba->addrs[ba->addrpos], + ba->port); + bmp_active_setup(ba); + return; + } + } + + /* exhausted all addresses */ + ba->curretry += ba->curretry / 2; + bmp_active_setup(ba); +} + +static void bmp_active_resolved(struct resolver_query *resq, const char *errstr, + int numaddrs, union sockunion *addr) +{ + struct bmp_active *ba = container_of(resq, struct bmp_active, resq); + unsigned i; + + if (numaddrs <= 0) { + zlog_warn("bmp[%s]: hostname resolution failed: %s", + ba->hostname, errstr); + ba->last_err = errstr; + ba->curretry += ba->curretry / 2; + ba->addrpos = 0; + ba->addrtotal = 0; + bmp_active_setup(ba); + return; + } + + if (numaddrs > (int)array_size(ba->addrs)) + numaddrs = array_size(ba->addrs); + + ba->addrpos = 0; + ba->addrtotal = numaddrs; + for (i = 0; i < ba->addrtotal; i++) + memcpy(&ba->addrs[i], &addr[i], sizeof(ba->addrs[0])); + + bmp_active_connect(ba); +} + +static void bmp_active_thread(struct event *t) +{ + struct bmp_active *ba = EVENT_ARG(t); + socklen_t slen; + int status, ret; + vrf_id_t vrf_id; + + /* all 3 end up here, though only timer or read+write are active + * at a time */ + EVENT_OFF(ba->t_timer); + EVENT_OFF(ba->t_read); + EVENT_OFF(ba->t_write); + + ba->last_err = NULL; + + if (ba->socket == -1) { + /* get vrf_id */ + if (!ba->targets || !ba->targets->bgp) + vrf_id = VRF_DEFAULT; + else + vrf_id = ba->targets->bgp->vrf_id; + resolver_resolve(&ba->resq, AF_UNSPEC, vrf_id, ba->hostname, + bmp_active_resolved); + return; + } + + slen = sizeof(status); + ret = getsockopt(ba->socket, SOL_SOCKET, SO_ERROR, (void *)&status, + &slen); + + if (ret < 0 || status != 0) { + ba->last_err = strerror(status); + zlog_warn("bmp[%s]: failed to connect to %pSU:%d: %s", + ba->hostname, &ba->addrs[ba->addrpos], ba->port, + ba->last_err); + goto out_next; + } + + zlog_warn("bmp[%s]: outbound connection to %pSU:%d", ba->hostname, + &ba->addrs[ba->addrpos], ba->port); + + ba->bmp = bmp_open(ba->targets, ba->socket); + if (!ba->bmp) + goto out_next; + + ba->bmp->active = ba; + ba->socket = -1; + ba->curretry = ba->minretry; + return; + +out_next: + close(ba->socket); + ba->socket = -1; + ba->addrpos++; + bmp_active_connect(ba); +} + +static void bmp_active_disconnected(struct bmp_active *ba) +{ + ba->bmp = NULL; + bmp_active_setup(ba); +} + +static void bmp_active_setup(struct bmp_active *ba) +{ + EVENT_OFF(ba->t_timer); + EVENT_OFF(ba->t_read); + EVENT_OFF(ba->t_write); + + if (ba->bmp) + return; + if (ba->resq.callback) + return; + + if (ba->curretry > ba->maxretry) + ba->curretry = ba->maxretry; + + if (ba->socket == -1) + event_add_timer_msec(bm->master, bmp_active_thread, ba, + ba->curretry, &ba->t_timer); + else { + event_add_read(bm->master, bmp_active_thread, ba, ba->socket, + &ba->t_read); + event_add_write(bm->master, bmp_active_thread, ba, ba->socket, + &ba->t_write); + } +} + +static struct cmd_node bmp_node = { + .name = "bmp", + .node = BMP_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-bgp-bmp)# " +}; + +static void bmp_targets_autocomplete(vector comps, struct cmd_token *token) +{ + struct bgp *bgp; + struct bmp_targets *target; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); + + if (!bmpbgp) + continue; + + frr_each_safe (bmp_targets, &bmpbgp->targets, target) + vector_set(comps, + XSTRDUP(MTYPE_COMPLETION, target->name)); + } +} + +static const struct cmd_variable_handler bmp_targets_var_handlers[] = { + {.tokenname = "BMPTARGETS", .completions = bmp_targets_autocomplete}, + {.completions = NULL}}; + +#define BMP_STR "BGP Monitoring Protocol\n" + +#include "bgpd/bgp_bmp_clippy.c" + +DEFPY_NOSH(bmp_targets_main, + bmp_targets_cmd, + "bmp targets BMPTARGETS", + BMP_STR + "Create BMP target group\n" + "Name of the BMP target group\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bmp_targets *bt; + + bt = bmp_targets_get(bgp, bmptargets); + + VTY_PUSH_CONTEXT_SUB(BMP_NODE, bt); + return CMD_SUCCESS; +} + +DEFPY(no_bmp_targets_main, + no_bmp_targets_cmd, + "no bmp targets BMPTARGETS", + NO_STR + BMP_STR + "Delete BMP target group\n" + "Name of the BMP target group\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bmp_targets *bt; + + bt = bmp_targets_find1(bgp, bmptargets); + if (!bt) { + vty_out(vty, "%% BMP target group not found\n"); + return CMD_WARNING; + } + bmp_targets_put(bt); + return CMD_SUCCESS; +} + +DEFPY(bmp_listener_main, + bmp_listener_cmd, + "bmp listener port (1-65535)", + BMP_STR + "Listen for inbound BMP connections\n" + "IPv6 address to listen on\n" + "IPv4 address to listen on\n" + "TCP Port number\n" + "TCP Port number\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + struct bmp_listener *bl; + + bl = bmp_listener_get(bt, listener, port); + if (bl->sock == -1) + bmp_listener_start(bl); + + return CMD_SUCCESS; +} + +DEFPY(no_bmp_listener_main, + no_bmp_listener_cmd, + "no bmp listener port (1-65535)", + NO_STR + BMP_STR + "Create BMP listener\n" + "IPv6 address to listen on\n" + "IPv4 address to listen on\n" + "TCP Port number\n" + "TCP Port number\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + struct bmp_listener *bl; + + bl = bmp_listener_find(bt, listener, port); + if (!bl) { + vty_out(vty, "%% BMP listener not found\n"); + return CMD_WARNING; + } + bmp_listener_stop(bl); + bmp_listener_put(bl); + return CMD_SUCCESS; +} + +DEFPY(bmp_connect, + bmp_connect_cmd, + "[no] bmp connect HOSTNAME port (1-65535) {min-retry (100-86400000)|max-retry (100-86400000)} [source-interface ]", + NO_STR + BMP_STR + "Actively establish connection to monitoring station\n" + "Monitoring station hostname or address\n" + "TCP port\n" + "TCP port\n" + "Minimum connection retry interval\n" + "Minimum connection retry interval (milliseconds)\n" + "Maximum connection retry interval\n" + "Maximum connection retry interval (milliseconds)\n" + "Source interface to use\n" + "Define an interface\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + struct bmp_active *ba; + + if (no) { + ba = bmp_active_find(bt, hostname, port); + if (!ba) { + vty_out(vty, "%% No such active connection found\n"); + return CMD_WARNING; + } + /* connection deletion need same hostname port and interface */ + if (ba->ifsrc || srcif) + if ((!ba->ifsrc) || (!srcif) || + !strcmp(ba->ifsrc, srcif)) { + vty_out(vty, + "%% No such active connection found\n"); + return CMD_WARNING; + } + bmp_active_put(ba); + return CMD_SUCCESS; + } + + ba = bmp_active_get(bt, hostname, port); + if (srcif) + ba->ifsrc = XSTRDUP(MTYPE_TMP, srcif); + if (min_retry_str) + ba->minretry = min_retry; + if (max_retry_str) + ba->maxretry = max_retry; + ba->curretry = ba->minretry; + bmp_active_setup(ba); + + return CMD_SUCCESS; +} + +DEFPY(bmp_acl, + bmp_acl_cmd, + "[no] $af access-list ACCESSLIST_NAME$access_list", + NO_STR + IP_STR + IPV6_STR + "Access list to restrict BMP sessions\n" + "Access list name\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + char **what; + + if (no) + access_list = NULL; + if (!strcmp(af, "ipv6")) + what = &bt->acl6_name; + else + what = &bt->acl_name; + + XFREE(MTYPE_BMP_ACLNAME, *what); + if (access_list) + *what = XSTRDUP(MTYPE_BMP_ACLNAME, access_list); + + return CMD_SUCCESS; +} + +DEFPY(bmp_stats_cfg, + bmp_stats_cmd, + "[no] bmp stats [interval (100-86400000)]", + NO_STR + BMP_STR + "Send BMP statistics messages\n" + "Specify BMP stats interval\n" + "Interval (milliseconds) to send BMP Stats in\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + + EVENT_OFF(bt->t_stats); + if (no) + bt->stat_msec = 0; + else if (interval_str) + bt->stat_msec = interval; + else + bt->stat_msec = BMP_STAT_DEFAULT_TIMER; + + if (bt->stat_msec) + event_add_timer_msec(bm->master, bmp_stats, bt, bt->stat_msec, + &bt->t_stats); + return CMD_SUCCESS; +} + +DEFPY(bmp_stats_send_experimental, + bmp_stats_send_experimental_cmd, + "[no] bmp stats send-experimental", + NO_STR + BMP_STR + "Send BMP statistics messages\n" + "Send experimental BMP stats [65531-65534]\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + + bt->stats_send_experimental = !no; + + return CMD_SUCCESS; +} + +#define BMP_POLICY_IS_LOCRIB(str) ((str)[0] == 'l') /* __l__oc-rib */ +#define BMP_POLICY_IS_PRE(str) ((str)[1] == 'r') /* p__r__e-policy */ + +DEFPY(bmp_monitor_cfg, bmp_monitor_cmd, + "[no] bmp monitor $policy", + NO_STR BMP_STR + "Send BMP route monitoring messages\n" BGP_AF_STR BGP_AF_STR BGP_AF_STR + BGP_AF_STR BGP_AF_STR BGP_AF_STR BGP_AF_STR + "Send state before policy and filter processing\n" + "Send state with policy and filters applied\n" + "Send state after decision process is applied\n") +{ + int index = 0; + uint8_t flag, prev; + afi_t afi; + safi_t safi; + + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + struct bmp *bmp; + + argv_find_and_parse_afi(argv, argc, &index, &afi); + argv_find_and_parse_safi(argv, argc, &index, &safi); + + if (BMP_POLICY_IS_LOCRIB(policy)) + flag = BMP_MON_LOC_RIB; + else if (BMP_POLICY_IS_PRE(policy)) + flag = BMP_MON_PREPOLICY; + else + flag = BMP_MON_POSTPOLICY; + + prev = bt->afimon[afi][safi]; + if (no) + bt->afimon[afi][safi] &= ~flag; + else + bt->afimon[afi][safi] |= flag; + + if (prev == bt->afimon[afi][safi]) + return CMD_SUCCESS; + + frr_each (bmp_session, &bt->sessions, bmp) { + if (bmp->syncafi == afi && bmp->syncsafi == safi) { + bmp->syncafi = AFI_MAX; + bmp->syncsafi = SAFI_MAX; + } + + if (!bt->afimon[afi][safi]) { + bmp->afistate[afi][safi] = BMP_AFI_INACTIVE; + continue; + } + + bmp->afistate[afi][safi] = BMP_AFI_NEEDSYNC; + } + + return CMD_SUCCESS; +} + +DEFPY(bmp_mirror_cfg, + bmp_mirror_cmd, + "[no] bmp mirror", + NO_STR + BMP_STR + "Send BMP route mirroring messages\n") +{ + VTY_DECLVAR_CONTEXT_SUB(bmp_targets, bt); + struct bmp *bmp; + + if (bt->mirror == !no) + return CMD_SUCCESS; + + bt->mirror = !no; + if (bt->mirror) + return CMD_SUCCESS; + + frr_each (bmp_session, &bt->sessions, bmp) { + struct bmp_mirrorq *bmq; + + while ((bmq = bmp_pull_mirror(bmp))) + if (!bmq->refcount) + XFREE(MTYPE_BMP_MIRRORQ, bmq); + } + return CMD_SUCCESS; +} + +DEFPY(bmp_mirror_limit_cfg, + bmp_mirror_limit_cmd, + "bmp mirror buffer-limit (0-4294967294)", + BMP_STR + "Route Mirroring settings\n" + "Configure maximum memory used for buffered mirroring messages\n" + "Limit in bytes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bmp_bgp *bmpbgp; + + bmpbgp = bmp_bgp_get(bgp); + bmpbgp->mirror_qsizelimit = buffer_limit; + + return CMD_SUCCESS; +} + +DEFPY(no_bmp_mirror_limit_cfg, + no_bmp_mirror_limit_cmd, + "no bmp mirror buffer-limit [(0-4294967294)]", + NO_STR + BMP_STR + "Route Mirroring settings\n" + "Configure maximum memory used for buffered mirroring messages\n" + "Limit in bytes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bmp_bgp *bmpbgp; + + bmpbgp = bmp_bgp_get(bgp); + bmpbgp->mirror_qsizelimit = ~0UL; + + return CMD_SUCCESS; +} + + +DEFPY(show_bmp, + show_bmp_cmd, + "show bmp", + SHOW_STR + BMP_STR) +{ + struct bmp_bgp *bmpbgp; + struct bmp_targets *bt; + struct bmp_listener *bl; + struct bmp_active *ba; + struct bmp *bmp; + struct ttable *tt; + char uptime[BGP_UPTIME_LEN]; + char *out; + + frr_each(bmp_bgph, &bmp_bgph, bmpbgp) { + vty_out(vty, "BMP state for BGP %s:\n\n", + bmpbgp->bgp->name_pretty); + vty_out(vty, " Route Mirroring %9zu bytes (%zu messages) pending\n", + bmpbgp->mirror_qsize, + bmp_mirrorq_count(&bmpbgp->mirrorq)); + vty_out(vty, " %9zu bytes maximum buffer used\n", + bmpbgp->mirror_qsizemax); + if (bmpbgp->mirror_qsizelimit != ~0UL) + vty_out(vty, " %9zu bytes buffer size limit\n", + bmpbgp->mirror_qsizelimit); + vty_out(vty, "\n"); + + frr_each(bmp_targets, &bmpbgp->targets, bt) { + vty_out(vty, " Targets \"%s\":\n", bt->name); + vty_out(vty, " Route Mirroring %sabled\n", + bt->mirror ? "en" : "dis"); + + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + + uint8_t afimon_flag = bt->afimon[afi][safi]; + + if (!afimon_flag) + continue; + + const char *pre_str = + CHECK_FLAG(afimon_flag, + BMP_MON_PREPOLICY) + ? "pre-policy " + : ""; + const char *post_str = + CHECK_FLAG(afimon_flag, + BMP_MON_POSTPOLICY) + ? "post-policy " + : ""; + const char *locrib_str = + CHECK_FLAG(afimon_flag, BMP_MON_LOC_RIB) + ? "loc-rib" + : ""; + + vty_out(vty, + " Route Monitoring %s %s %s%s%s\n", + afi2str(afi), safi2str(safi), pre_str, + post_str, locrib_str); + } + + vty_out(vty, " Listeners:\n"); + frr_each (bmp_listeners, &bt->listeners, bl) + vty_out(vty, " %pSU:%d\n", &bl->addr, + bl->port); + + vty_out(vty, "\n Outbound connections:\n"); + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "remote|state||timer|local"); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + frr_each (bmp_actives, &bt->actives, ba) { + const char *state_str = "?"; + + if (ba->bmp) { + peer_uptime(ba->bmp->t_up.tv_sec, + uptime, sizeof(uptime), + false, NULL); + ttable_add_row(tt, + "%s:%d|Up|%s|%s|%pSU", + ba->hostname, ba->port, + ba->bmp->remote, uptime, + &ba->addrsrc); + continue; + } + + uptime[0] = '\0'; + + if (ba->t_timer) { + long trem = event_timer_remain_second( + ba->t_timer); + + peer_uptime(monotime(NULL) - trem, + uptime, sizeof(uptime), + false, NULL); + state_str = "RetryWait"; + } else if (ba->t_read) { + state_str = "Connecting"; + } else if (ba->resq.callback) { + state_str = "Resolving"; + } + + ttable_add_row(tt, "%s:%d|%s|%s|%s|%pSU", + ba->hostname, ba->port, + state_str, + ba->last_err ? ba->last_err : "", + uptime, &ba->addrsrc); + continue; + } + out = ttable_dump(tt, "\n"); + vty_out(vty, "%s", out); + XFREE(MTYPE_TMP, out); + ttable_del(tt); + + vty_out(vty, "\n %zu connected clients:\n", + bmp_session_count(&bt->sessions)); + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "remote|uptime|MonSent|MirrSent|MirrLost|ByteSent|ByteQ|ByteQKernel"); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + frr_each (bmp_session, &bt->sessions, bmp) { + uint64_t total; + size_t q, kq; + + pullwr_stats(bmp->pullwr, &total, &q, &kq); + + peer_uptime(bmp->t_up.tv_sec, uptime, + sizeof(uptime), false, NULL); + + ttable_add_row(tt, "%s|%s|%Lu|%Lu|%Lu|%Lu|%zu|%zu", + bmp->remote, uptime, + bmp->cnt_update, + bmp->cnt_mirror, + bmp->cnt_mirror_overruns, + total, q, kq); + } + out = ttable_dump(tt, "\n"); + vty_out(vty, "%s", out); + XFREE(MTYPE_TMP, out); + ttable_del(tt); + vty_out(vty, "\n"); + } + } + + return CMD_SUCCESS; +} + +static int bmp_config_write(struct bgp *bgp, struct vty *vty) +{ + struct bmp_bgp *bmpbgp = bmp_bgp_find(bgp); + struct bmp_targets *bt; + struct bmp_listener *bl; + struct bmp_active *ba; + afi_t afi; + safi_t safi; + + if (!bmpbgp) + return 0; + + if (bmpbgp->mirror_qsizelimit != ~0UL) + vty_out(vty, " !\n bmp mirror buffer-limit %zu\n", + bmpbgp->mirror_qsizelimit); + + frr_each(bmp_targets, &bmpbgp->targets, bt) { + vty_out(vty, " !\n bmp targets %s\n", bt->name); + + if (bt->acl6_name) + vty_out(vty, " ipv6 access-list %s\n", bt->acl6_name); + if (bt->acl_name) + vty_out(vty, " ip access-list %s\n", bt->acl_name); + + if (!bt->stats_send_experimental) + vty_out(vty, " no bmp stats send-experimental\n"); + + if (bt->stat_msec) + vty_out(vty, " bmp stats interval %d\n", + bt->stat_msec); + + if (bt->mirror) + vty_out(vty, " bmp mirror\n"); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(bt->afimon[afi][safi], + BMP_MON_PREPOLICY)) + vty_out(vty, " bmp monitor %s %s pre-policy\n", + afi2str_lower(afi), safi2str(safi)); + if (CHECK_FLAG(bt->afimon[afi][safi], + BMP_MON_POSTPOLICY)) + vty_out(vty, + " bmp monitor %s %s post-policy\n", + afi2str_lower(afi), safi2str(safi)); + if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) + vty_out(vty, " bmp monitor %s %s loc-rib\n", + afi2str_lower(afi), safi2str(safi)); + } + frr_each (bmp_listeners, &bt->listeners, bl) + vty_out(vty, " \n bmp listener %pSU port %d\n", + &bl->addr, bl->port); + + frr_each (bmp_actives, &bt->actives, ba) { + vty_out(vty, " bmp connect %s port %u min-retry %u max-retry %u", + ba->hostname, ba->port, + ba->minretry, ba->maxretry); + + if (ba->ifsrc) + vty_out(vty, " source-interface %s\n", ba->ifsrc); + else + vty_out(vty, "\n"); + } + vty_out(vty, " exit\n"); + } + + return 0; +} + +static int bgp_bmp_init(struct event_loop *tm) +{ + install_node(&bmp_node); + install_default(BMP_NODE); + + cmd_variable_handler_register(bmp_targets_var_handlers); + + install_element(BGP_NODE, &bmp_targets_cmd); + install_element(BGP_NODE, &no_bmp_targets_cmd); + + install_element(BMP_NODE, &bmp_listener_cmd); + install_element(BMP_NODE, &no_bmp_listener_cmd); + install_element(BMP_NODE, &bmp_connect_cmd); + install_element(BMP_NODE, &bmp_acl_cmd); + install_element(BMP_NODE, &bmp_stats_send_experimental_cmd); + install_element(BMP_NODE, &bmp_stats_cmd); + install_element(BMP_NODE, &bmp_monitor_cmd); + install_element(BMP_NODE, &bmp_mirror_cmd); + + install_element(BGP_NODE, &bmp_mirror_limit_cmd); + install_element(BGP_NODE, &no_bmp_mirror_limit_cmd); + + install_element(VIEW_NODE, &show_bmp_cmd); + + resolver_init(tm); + return 0; +} + +static int bmp_route_update(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *bn, + struct bgp_path_info *old_route, + struct bgp_path_info *new_route) +{ + bool is_locribmon_enabled = false; + bool is_withdraw = old_route && !new_route; + struct bgp_path_info *updated_route = + is_withdraw ? old_route : new_route; + + + /* this should never happen */ + if (!updated_route) { + zlog_warn("%s: no updated route found!", __func__); + return 0; + } + + struct bmp_bgp *bmpbgp = bmp_bgp_get(bgp); + struct peer *peer = updated_route->peer; + struct bmp_targets *bt; + struct bmp *bmp; + + frr_each (bmp_targets, &bmpbgp->targets, bt) { + if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) { + is_locribmon_enabled = true; + break; + } + } + + if (!is_locribmon_enabled) + return 0; + + /* route is not installed in locrib anymore and rib uptime was saved */ + if (old_route && old_route->extra) + bgp_path_info_extra_get(old_route)->bgp_rib_uptime = + (time_t)(-1L); + + /* route is installed in locrib from now on so + * save rib uptime in bgp_path_info_extra + */ + if (new_route) + bgp_path_info_extra_get(new_route)->bgp_rib_uptime = + monotime(NULL); + + frr_each (bmp_targets, &bmpbgp->targets, bt) { + if (CHECK_FLAG(bt->afimon[afi][safi], BMP_MON_LOC_RIB)) { + + struct bmp_queue_entry *last_item = bmp_process_one( + bt, &bt->locupdhash, &bt->locupdlist, bgp, afi, + safi, bn, peer); + + /* if bmp_process_one returns NULL + * we don't have anything to do next + */ + if (!last_item) + continue; + + frr_each (bmp_session, &bt->sessions, bmp) { + if (!bmp->locrib_queuepos) + bmp->locrib_queuepos = last_item; + + pullwr_bump(bmp->pullwr); + }; + } + }; + + return 0; +} + +static int bgp_bmp_early_fini(void) +{ + resolver_terminate(); + + return 0; +} + +static int bgp_bmp_module_init(void) +{ + hook_register(bgp_packet_dump, bmp_mirror_packet); + hook_register(bgp_packet_send, bmp_outgoing_packet); + hook_register(peer_status_changed, bmp_peer_status_changed); + hook_register(peer_backward_transition, bmp_peer_backward); + hook_register(bgp_process, bmp_process); + hook_register(bgp_inst_config_write, bmp_config_write); + hook_register(bgp_inst_delete, bmp_bgp_del); + hook_register(frr_late_init, bgp_bmp_init); + hook_register(bgp_route_update, bmp_route_update); + hook_register(frr_early_fini, bgp_bmp_early_fini); + return 0; +} + +FRR_MODULE_SETUP(.name = "bgpd_bmp", .version = FRR_VERSION, + .description = "bgpd BMP module", + .init = bgp_bmp_module_init, +); diff --git a/bgpd/bgp_bmp.h b/bgpd/bgp_bmp.h new file mode 100644 index 0000000..33247c4 --- /dev/null +++ b/bgpd/bgp_bmp.h @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BMP support. + * Copyright (C) 2018 Yasuhiro Ohara + * Copyright (C) 2019 David Lamparter for NetDEF, Inc. + */ + +#ifndef _BGP_BMP_H_ +#define _BGP_BMP_H_ + +#include "zebra.h" +#include "typesafe.h" +#include "pullwr.h" +#include "qobj.h" +#include "resolver.h" + +#define BMP_VERSION_3 3 + +#define BMP_LENGTH_POS 1 + +/* BMP message types */ +#define BMP_TYPE_ROUTE_MONITORING 0 +#define BMP_TYPE_STATISTICS_REPORT 1 +#define BMP_TYPE_PEER_DOWN_NOTIFICATION 2 +#define BMP_TYPE_PEER_UP_NOTIFICATION 3 +#define BMP_TYPE_INITIATION 4 +#define BMP_TYPE_TERMINATION 5 +#define BMP_TYPE_ROUTE_MIRRORING 6 + +#define BMP_READ_BUFSIZ 1024 + +/* bmp->state */ +#define BMP_None 0 +#define BMP_PeerUp 2 +#define BMP_Run 3 + +/* This one is for BMP Route Monitoring messages, i.e. delivering updates + * in somewhat processed (as opposed to fully raw, see mirroring below) form. + * RFC explicitly says that we can skip old updates if we haven't sent them out + * yet and another newer update for the same prefix arrives. + * + * So, at most one of these can exist for each (bgp, afi, safi, prefix, peerid) + * tuple; if some prefix is "re-added" to the queue, the existing entry is + * instead moved to the end of the queue. This ensures that the queue size is + * bounded by the BGP table size. + * + * bmp_qlist is the queue itself while bmp_qhash is used to efficiently check + * whether a tuple is already on the list. The queue is maintained per + * bmp_target. + * + * refcount = number of "struct bmp *" whose queue position is before this + * entry, i.e. number of BMP sessions where we still want to send this out. + * Decremented on send so we know when we're done with an entry (i.e. this + * always happens from the front of the queue.) + */ + +PREDECL_DLIST(bmp_qlist); +PREDECL_HASH(bmp_qhash); + +struct bmp_queue_entry { + struct bmp_qlist_item bli; + struct bmp_qhash_item bhi; + + struct prefix p; + uint64_t peerid; + afi_t afi; + safi_t safi; + + size_t refcount; + + /* initialized only for L2VPN/EVPN (S)AFIs */ + struct prefix_rd rd; +}; + +/* This is for BMP Route Mirroring, which feeds fully raw BGP PDUs out to BMP + * receivers. So, this goes directly off packet RX/TX handling instead of + * grabbing bits from tables. + * + * There is *one* queue for each "struct bgp *" where we throw everything on, + * with a size limit. Refcount works the same as for monitoring above. + */ + +PREDECL_LIST(bmp_mirrorq); + +struct bmp_mirrorq { + struct bmp_mirrorq_item bmi; + + size_t refcount; + uint64_t peerid; + struct timeval tv; + + size_t len; + uint8_t data[0]; +}; + +enum { + BMP_AFI_INACTIVE = 0, + BMP_AFI_NEEDSYNC, + BMP_AFI_SYNC, + BMP_AFI_LIVE, +}; + +PREDECL_LIST(bmp_session); + +struct bmp_active; +struct bmp_targets; + +/* an established BMP session to a peer */ +struct bmp { + struct bmp_session_item bsi; + struct bmp_targets *targets; + struct bmp_active *active; + + int socket; + char remote[SU_ADDRSTRLEN + 6]; + struct event *t_read; + + struct pullwr *pullwr; + + int state; + + /* queue positions must remain synced with refcounts in the items. + * Whenever appending a queue item, we need to know the correct number + * of "struct bmp *" that want it, and when moving these positions + * ahead we need to make sure that refcount is decremented. Also, on + * disconnects we need to walk the queue and drop our reference. + */ + struct bmp_queue_entry *locrib_queuepos; + struct bmp_queue_entry *queuepos; + struct bmp_mirrorq *mirrorpos; + bool mirror_lost; + + /* enum BMP_AFI_* */ + uint8_t afistate[AFI_MAX][SAFI_MAX]; + + /* counters for the various BMP packet types */ + uint64_t cnt_update, cnt_mirror; + /* number of times this peer wasn't fast enough in consuming the + * mirror queue + */ + uint64_t cnt_mirror_overruns; + struct timeval t_up; + + /* synchronization / startup works by repeatedly finding the next + * table entry, the sync* fields note down what we sent last + */ + struct prefix syncpos; + struct bgp_dest *syncrdpos; + uint64_t syncpeerid; + afi_t syncafi; + safi_t syncsafi; +}; + +/* config & state for an active outbound connection. When the connection + * succeeds, "bmp" is set up. + */ + +PREDECL_SORTLIST_UNIQ(bmp_actives); + +#define BMP_DFLT_MINRETRY 30000 +#define BMP_DFLT_MAXRETRY 720000 + +struct bmp_active { + struct bmp_actives_item bai; + struct bmp_targets *targets; + struct bmp *bmp; + + char *hostname; + int port; + unsigned minretry, maxretry; + char *ifsrc; + union sockunion addrsrc; + + struct resolver_query resq; + + unsigned curretry; + unsigned addrpos, addrtotal; + union sockunion addrs[8]; + int socket; + const char *last_err; + struct event *t_timer, *t_read, *t_write; +}; + +/* config & state for passive / listening sockets */ +PREDECL_SORTLIST_UNIQ(bmp_listeners); + +struct bmp_listener { + struct bmp_listeners_item bli; + + struct bmp_targets *targets; + + union sockunion addr; + int port; + + struct event *t_accept; + int sock; +}; + +/* bmp_targets - plural since it may contain multiple bmp_listener & + * bmp_active items. If they have the same config, BMP session should be + * put in the same targets since that's a bit more effective. + */ +PREDECL_SORTLIST_UNIQ(bmp_targets); + +struct bmp_targets { + struct bmp_targets_item bti; + + struct bmp_bgp *bmpbgp; + struct bgp *bgp; + char *name; + + struct bmp_listeners_head listeners; + + char *acl_name; + char *acl6_name; +#define BMP_STAT_DEFAULT_TIMER 60000 + int stat_msec; + + /* only supporting: + * - IPv4 / unicast & multicast & VPN + * - IPv6 / unicast & multicast & VPN + * - L2VPN / EVPN + */ +#define BMP_MON_PREPOLICY (1 << 0) +#define BMP_MON_POSTPOLICY (1 << 1) +#define BMP_MON_LOC_RIB (1 << 2) + + uint8_t afimon[AFI_MAX][SAFI_MAX]; + bool mirror; + + struct bmp_actives_head actives; + + struct event *t_stats; + struct bmp_session_head sessions; + + struct bmp_qhash_head updhash; + struct bmp_qlist_head updlist; + + struct bmp_qhash_head locupdhash; + struct bmp_qlist_head locupdlist; + + uint64_t cnt_accept, cnt_aclrefused; + + bool stats_send_experimental; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bmp_targets); + +/* per struct peer * data. Lookup by peer->qobj_node.nid, created on demand, + * deleted in peer_backward hook. */ +PREDECL_HASH(bmp_peerh); + +struct bmp_bgp_peer { + struct bmp_peerh_item bpi; + + uint64_t peerid; + /* struct peer *peer; */ + + uint8_t *open_rx; + size_t open_rx_len; + + uint8_t *open_tx; + size_t open_tx_len; +}; + +/* per struct bgp * data */ +PREDECL_HASH(bmp_bgph); + +#define BMP_PEER_DOWN_NO_RELEVANT_EVENT_CODE 0x00 + +struct bmp_bgp { + struct bmp_bgph_item bbi; + + struct bgp *bgp; + struct bmp_targets_head targets; + + struct bmp_mirrorq_head mirrorq; + size_t mirror_qsize, mirror_qsizemax; + + size_t mirror_qsizelimit; +}; + +enum { + BMP_PEERDOWN_LOCAL_NOTIFY = 1, + BMP_PEERDOWN_LOCAL_FSM = 2, + BMP_PEERDOWN_REMOTE_NOTIFY = 3, + BMP_PEERDOWN_REMOTE_CLOSE = 4, + BMP_PEERDOWN_ENDMONITOR = 5, +}; + +enum { + BMP_STATS_PFX_REJECTED = 0, + BMP_STATS_PFX_DUP_ADV = 1, + BMP_STATS_PFX_DUP_WITHDRAW = 2, + BMP_STATS_UPD_LOOP_CLUSTER = 3, + BMP_STATS_UPD_LOOP_ASPATH = 4, + BMP_STATS_UPD_LOOP_ORIGINATOR = 5, + BMP_STATS_UPD_LOOP_CONFED = 6, + BMP_STATS_SIZE_ADJ_RIB_IN = 7, + BMP_STATS_SIZE_LOC_RIB = 8, + BMP_STATS_SIZE_ADJ_RIB_IN_SAFI = 9, + BMP_STATS_SIZE_LOC_RIB_IN_SAFI = 10, + BMP_STATS_UPD_7606_WITHDRAW = 11, + BMP_STATS_PFX_7606_WITHDRAW = 12, + BMP_STATS_UPD_DUP = 13, + BMP_STATS_FRR_NH_INVALID = 65531, +}; + +DECLARE_MGROUP(BMP); + +#endif /*_BGP_BMP_H_*/ diff --git a/bgpd/bgp_btoa.c b/bgpd/bgp_btoa.c new file mode 100644 index 0000000..1d5034e --- /dev/null +++ b/bgpd/bgp_btoa.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP dump to ascii converter + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include +#include + +#include "zebra.h" +#include "stream.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "memory.h" +#include "privs.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" + +/* privileges */ +static zebra_capabilities_t _caps_p[] = { + ZCAP_BIND, ZCAP_NET_RAW, ZCAP_NET_ADMIN, +}; + +struct zebra_privs_t bgpd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +enum MRT_MSG_TYPES { + MSG_NULL, + MSG_START, /* sender is starting up */ + MSG_DIE, /* receiver should shut down */ + MSG_I_AM_DEAD, /* sender is shutting down */ + MSG_PEER_DOWN, /* sender's peer is down */ + MSG_PROTOCOL_BGP, /* msg is a BGP packet */ + MSG_PROTOCOL_RIP, /* msg is a RIP packet */ + MSG_PROTOCOL_IDRP, /* msg is an IDRP packet */ + MSG_PROTOCOL_RIPNG, /* msg is a RIPNG packet */ + MSG_PROTOCOL_BGP4PLUS, /* msg is a BGP4+ packet */ + MSG_PROTOCOL_BGP4PLUS_01, /* msg is a BGP4+ (draft 01) packet */ + MSG_PROTOCOL_OSPF, /* msg is an OSPF packet */ + MSG_TABLE_DUMP /* routing table dump */ +}; + +static void attr_parse(struct stream *s, uint16_t len) +{ + unsigned int flag; + unsigned int type; + uint16_t length; + uint16_t lim; + + lim = s->getp + len; + + printf("%s s->getp %zd, len %d, lim %d\n", __func__, s->getp, len, lim); + + while (s->getp < lim) { + flag = stream_getc(s); + type = stream_getc(s); + + if (flag & BGP_ATTR_FLAG_EXTLEN) + length = stream_getw(s); + else + length = stream_getc(s); + + printf("FLAG: %d\n", flag); + printf("TYPE: %d\n", type); + printf("Len: %d\n", length); + + switch (type) { + case BGP_ATTR_ORIGIN: { + uint8_t origin; + origin = stream_getc(s); + printf("ORIGIN: %d\n", origin); + } break; + case BGP_ATTR_AS_PATH: { + struct aspath *aspath; + + aspath = aspath_parse(s, length, 1, + bgp_get_asnotation(NULL)); + printf("ASPATH: %s\n", aspath->str); + aspath_free(aspath); + } break; + case BGP_ATTR_NEXT_HOP: { + struct in_addr nexthop; + nexthop.s_addr = stream_get_ipv4(s); + printf("NEXTHOP: %pI4\n", &nexthop); + } break; + default: + stream_getw_from(s, length); + break; + } + } +} + +int main(int argc, char **argv) +{ + int ret; + int fd; + struct stream *s; + time_t now; + int type; + int subtype; + size_t len; + int source_as; + int dest_as; + ifindex_t ifindex; + int family; + struct in_addr sip; + struct in_addr dip; + uint16_t viewno, seq_num; + struct prefix_ipv4 p; + char tbuf[32]; + + s = stream_new(10000); + + if (argc != 2) { + fprintf(stderr, "Usage: %s FILENAME\n", argv[0]); + exit(1); + } + fd = open(argv[1], O_RDONLY); + if (fd < 0) { + fprintf(stdout, + "%% Can't open configuration file %s due to '%s'.\n", + argv[1], safe_strerror(errno)); + exit(1); + } + + while (1) { + stream_reset(s); + + ret = stream_read(s, fd, 12); + if (ret != 12) { + if (!ret) + printf("END OF FILE\n"); + else if (ret < 0) + printf("ERROR OF READ\n"); + else + printf("UNDERFLOW\n"); + break; + } + + /* Extract header. */ + now = stream_getl(s); + type = stream_getw(s); + subtype = stream_getw(s); + len = stream_getl(s); + + printf("TIME: %s", ctime_r(&now, tbuf)); + + /* printf ("TYPE: %d/%d\n", type, subtype); */ + + if (type == MSG_PROTOCOL_BGP4MP) + printf("TYPE: BGP4MP"); + else if (type == MSG_PROTOCOL_BGP4MP_ET) + printf("TYPE: BGP4MP_ET"); + else if (type == MSG_TABLE_DUMP) + printf("TYPE: MSG_TABLE_DUMP"); + else + printf("TYPE: Unknown %d", type); + + if (type == MSG_TABLE_DUMP) + switch (subtype) { + case AFI_IP: + printf("/AFI_IP\n"); + break; + case AFI_IP6: + printf("/AFI_IP6\n"); + break; + default: + printf("/UNKNOWN %d", subtype); + break; + } + else { + switch (subtype) { + case BGP4MP_STATE_CHANGE: + printf("/CHANGE\n"); + break; + case BGP4MP_MESSAGE: + printf("/MESSAGE\n"); + break; + case BGP4MP_ENTRY: + printf("/ENTRY\n"); + break; + case BGP4MP_SNAPSHOT: + printf("/SNAPSHOT\n"); + break; + default: + printf("/UNKNOWN %d", subtype); + break; + } + } + + printf("len: %zd\n", len); + + ret = stream_read(s, fd, len); + if (ret != (int)len) { + if (!ret) + printf("END OF FILE 2\n"); + else if (ret < 0) + printf("ERROR OF READ 2\n"); + else + printf("UNDERFLOW 2\n"); + break; + } + + /* printf ("now read %d\n", len); */ + + if (type == MSG_TABLE_DUMP) { + uint8_t status; + time_t originated; + struct in_addr peer; + uint16_t attrlen; + + viewno = stream_getw(s); + seq_num = stream_getw(s); + printf("VIEW: %d\n", viewno); + printf("SEQUENCE: %d\n", seq_num); + + /* start */ + while (s->getp < len - 16) { + p.prefix.s_addr = stream_get_ipv4(s); + p.prefixlen = stream_getc(s); + printf("PREFIX: %pI4/%d\n", &p.prefix, + p.prefixlen); + + status = stream_getc(s); + originated = stream_getl(s); + peer.s_addr = stream_get_ipv4(s); + source_as = stream_getw(s); + + printf("FROM: %pI4 AS%d\n", &peer, source_as); + printf("ORIGINATED: %s", ctime_r(&originated, + tbuf)); + + attrlen = stream_getw(s); + printf("ATTRLEN: %d\n", attrlen); + + attr_parse(s, attrlen); + + printf("STATUS: 0x%x\n", status); + } + } else { + source_as = stream_getw(s); + dest_as = stream_getw(s); + printf("source_as: %d\n", source_as); + printf("dest_as: %d\n", dest_as); + + ifindex = stream_getw(s); + family = stream_getw(s); + + printf("ifindex: %d\n", ifindex); + printf("family: %d\n", family); + + sip.s_addr = stream_get_ipv4(s); + dip.s_addr = stream_get_ipv4(s); + + printf("saddr: %pI4\n", &sip); + printf("daddr: %pI4\n", &dip); + + printf("\n"); + } + } + close(fd); + return 0; +} diff --git a/bgpd/bgp_clist.c b/bgpd/bgp_clist.c new file mode 100644 index 0000000..153cbd6 --- /dev/null +++ b/bgpd/bgp_clist.c @@ -0,0 +1,1453 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP community-list and extcommunity-list. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" +#include "stream.h" +#include "jhash.h" +#include "frrstr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_clist.h" + +/* Calculate new sequential number. */ +static int64_t bgp_clist_new_seq_get(struct community_list *list) +{ + int64_t maxseq; + int64_t newseq; + struct community_entry *entry; + + maxseq = 0; + + for (entry = list->head; entry; entry = entry->next) { + if (maxseq < entry->seq) + maxseq = entry->seq; + } + + newseq = ((maxseq / 5) * 5) + 5; + + return (newseq > UINT_MAX) ? UINT_MAX : newseq; +} + +/* Return community-list entry which has same seq number. */ +static struct community_entry *bgp_clist_seq_check(struct community_list *list, + int64_t seq) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) + if (entry->seq == seq) + return entry; + return NULL; +} + +static uint32_t bgp_clist_hash_key_community_list(const void *data) +{ + struct community_list *cl = (struct community_list *) data; + + if (cl->name_hash) + return cl->name_hash; + + cl->name_hash = bgp_clist_hash_key(cl->name); + return cl->name_hash; +} + +static bool bgp_clist_hash_cmp_community_list(const void *a1, const void *a2) +{ + const struct community_list *cl1 = a1; + const struct community_list *cl2 = a2; + + if (cl1->name_hash != cl2->name_hash) + return false; + + if (strcmp(cl1->name, cl2->name) == 0) + return true; + + return false; +} + +/* Lookup master structure for community-list or + extcommunity-list. */ +struct community_list_master * +community_list_master_lookup(struct community_list_handler *ch, int master) +{ + if (ch) + switch (master) { + case COMMUNITY_LIST_MASTER: + return &ch->community_list; + case EXTCOMMUNITY_LIST_MASTER: + return &ch->extcommunity_list; + case LARGE_COMMUNITY_LIST_MASTER: + return &ch->lcommunity_list; + } + return NULL; +} + +/* Allocate a new community list entry. */ +static struct community_entry *community_entry_new(void) +{ + return XCALLOC(MTYPE_COMMUNITY_LIST_ENTRY, + sizeof(struct community_entry)); +} + +/* Free community list entry. */ +static void community_entry_free(struct community_entry *entry) +{ + switch (entry->style) { + case COMMUNITY_LIST_STANDARD: + if (entry->u.com) + community_free(&entry->u.com); + break; + case LARGE_COMMUNITY_LIST_STANDARD: + if (entry->u.lcom) + lcommunity_free(&entry->u.lcom); + break; + case EXTCOMMUNITY_LIST_STANDARD: + /* In case of standard extcommunity-list, configuration string + is made by ecommunity_ecom2str(). */ + XFREE(MTYPE_ECOMMUNITY_STR, entry->config); + if (entry->u.ecom) + ecommunity_free(&entry->u.ecom); + break; + case COMMUNITY_LIST_EXPANDED: + case EXTCOMMUNITY_LIST_EXPANDED: + case LARGE_COMMUNITY_LIST_EXPANDED: + XFREE(MTYPE_COMMUNITY_LIST_CONFIG, entry->config); + if (entry->reg) + bgp_regex_free(entry->reg); + break; + default: + break; + } + XFREE(MTYPE_COMMUNITY_LIST_ENTRY, entry); +} + +/* Allocate a new community-list. */ +static struct community_list *community_list_new(void) +{ + return XCALLOC(MTYPE_COMMUNITY_LIST, sizeof(struct community_list)); +} + +/* Free community-list. */ +static void community_list_free(struct community_list *list) +{ + XFREE(MTYPE_COMMUNITY_LIST_NAME, list->name); + XFREE(MTYPE_COMMUNITY_LIST, list); +} + +static struct community_list * +community_list_insert(struct community_list_handler *ch, const char *name, + int master) +{ + size_t i; + long number; + struct community_list *new; + struct community_list *point; + struct community_list_list *list; + struct community_list_master *cm; + + /* Lookup community-list master. */ + cm = community_list_master_lookup(ch, master); + if (!cm) + return NULL; + + /* Allocate new community_list and copy given name. */ + new = community_list_new(); + new->name = XSTRDUP(MTYPE_COMMUNITY_LIST_NAME, name); + new->name_hash = bgp_clist_hash_key_community_list(new); + + /* Save for later */ + (void)hash_get(cm->hash, new, hash_alloc_intern); + + /* If name is made by all digit character. We treat it as + number. */ + for (number = 0, i = 0; i < strlen(name); i++) { + if (isdigit((unsigned char)name[i])) + number = (number * 10) + (name[i] - '0'); + else + break; + } + + /* In case of name is all digit character */ + if (i == strlen(name)) { + new->sort = COMMUNITY_LIST_NUMBER; + + /* Set access_list to number list. */ + list = &cm->num; + + for (point = list->head; point; point = point->next) + if (atol(point->name) >= number) + break; + } else { + new->sort = COMMUNITY_LIST_STRING; + + /* Set access_list to string list. */ + list = &cm->str; + + /* Set point to insertion point. */ + for (point = list->head; point; point = point->next) + if (strcmp(point->name, name) >= 0) + break; + } + + /* Link to upper list. */ + new->parent = list; + + /* In case of this is the first element of master. */ + if (list->head == NULL) { + list->head = list->tail = new; + return new; + } + + /* In case of insertion is made at the tail of access_list. */ + if (point == NULL) { + new->prev = list->tail; + list->tail->next = new; + list->tail = new; + return new; + } + + /* In case of insertion is made at the head of access_list. */ + if (point == list->head) { + new->next = list->head; + list->head->prev = new; + list->head = new; + return new; + } + + /* Insertion is made at middle of the access_list. */ + new->next = point; + new->prev = point->prev; + + if (point->prev) + point->prev->next = new; + point->prev = new; + + return new; +} + +struct community_list *community_list_lookup(struct community_list_handler *ch, + const char *name, + uint32_t name_hash, + int master) +{ + struct community_list lookup; + struct community_list_master *cm; + + if (!name) + return NULL; + + cm = community_list_master_lookup(ch, master); + if (!cm) + return NULL; + + lookup.name = (char *)name; + lookup.name_hash = name_hash; + return hash_get(cm->hash, &lookup, NULL); +} + +static struct community_list * +community_list_get(struct community_list_handler *ch, const char *name, + int master) +{ + struct community_list *list; + + list = community_list_lookup(ch, name, 0, master); + if (!list) + list = community_list_insert(ch, name, master); + return list; +} + +static void community_list_delete(struct community_list_master *cm, + struct community_list *list) +{ + struct community_list_list *clist; + struct community_entry *entry, *next; + + for (entry = list->head; entry; entry = next) { + next = entry->next; + community_entry_free(entry); + } + + clist = list->parent; + + if (list->next) + list->next->prev = list->prev; + else + clist->tail = list->prev; + + if (list->prev) + list->prev->next = list->next; + else + clist->head = list->next; + + hash_release(cm->hash, list); + community_list_free(list); +} + +static bool community_list_empty_p(struct community_list *list) +{ + return list->head == NULL && list->tail == NULL; +} + +/* Delete community-list entry from the list. */ +static void community_list_entry_delete(struct community_list_master *cm, + struct community_list *list, + struct community_entry *entry) +{ + if (entry->next) + entry->next->prev = entry->prev; + else + list->tail = entry->prev; + + if (entry->prev) + entry->prev->next = entry->next; + else + list->head = entry->next; + + community_entry_free(entry); + + if (community_list_empty_p(list)) + community_list_delete(cm, list); +} + +/* + * Replace community-list entry in the list. Note that entry is the new one + * and replace is one one being replaced. + */ +static void community_list_entry_replace(struct community_list *list, + struct community_entry *replace, + struct community_entry *entry) +{ + if (replace->next) { + entry->next = replace->next; + replace->next->prev = entry; + } else { + entry->next = NULL; + list->tail = entry; + } + + if (replace->prev) { + entry->prev = replace->prev; + replace->prev->next = entry; + } else { + entry->prev = NULL; + list->head = entry; + } + + community_entry_free(replace); +} + +/* Add community-list entry to the list. */ +static void community_list_entry_add(struct community_list *list, + struct community_entry *entry, + struct community_list_handler *ch, + int master) +{ + struct community_entry *replace; + struct community_entry *point; + + /* Automatic assignment of seq no. */ + if (entry->seq == COMMUNITY_SEQ_NUMBER_AUTO) + entry->seq = bgp_clist_new_seq_get(list); + + if (list->tail && entry->seq > list->tail->seq) + point = NULL; + else { + replace = bgp_clist_seq_check(list, entry->seq); + if (replace) { + community_list_entry_replace(list, replace, entry); + return; + } + + /* Check insert point. */ + for (point = list->head; point; point = point->next) + if (point->seq >= entry->seq) + break; + } + + /* In case of this is the first element of the list. */ + entry->next = point; + + if (point) { + if (point->prev) + point->prev->next = entry; + else + list->head = entry; + + entry->prev = point->prev; + point->prev = entry; + } else { + if (list->tail) + list->tail->next = entry; + else + list->head = entry; + + entry->prev = list->tail; + list->tail = entry; + } +} + +/* Lookup community-list entry from the list. */ +static struct community_entry * +community_list_entry_lookup(struct community_list *list, const void *arg, + int direct) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + switch (entry->style) { + case COMMUNITY_LIST_STANDARD: + if (entry->direct == direct + && community_cmp(entry->u.com, arg)) + return entry; + break; + case EXTCOMMUNITY_LIST_STANDARD: + if (entry->direct == direct + && ecommunity_cmp(entry->u.ecom, arg)) + return entry; + break; + case LARGE_COMMUNITY_LIST_STANDARD: + if (entry->direct == direct + && lcommunity_cmp(entry->u.lcom, arg)) + return entry; + break; + case COMMUNITY_LIST_EXPANDED: + case EXTCOMMUNITY_LIST_EXPANDED: + case LARGE_COMMUNITY_LIST_EXPANDED: + if (entry->direct == direct + && strcmp(entry->config, arg) == 0) + return entry; + break; + default: + break; + } + } + return NULL; +} + +static char *community_str_get(struct community *com, int i) +{ + uint32_t comval; + uint16_t as; + uint16_t val; + char *str; + + memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); + comval = ntohl(comval); + + switch (comval) { + case COMMUNITY_GSHUT: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "graceful-shutdown"); + break; + case COMMUNITY_ACCEPT_OWN: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: + str = XSTRDUP(MTYPE_COMMUNITY_STR, + "route-filter-translated-v4"); + break; + case COMMUNITY_ROUTE_FILTER_v4: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v4"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: + str = XSTRDUP(MTYPE_COMMUNITY_STR, + "route-filter-translated-v6"); + break; + case COMMUNITY_ROUTE_FILTER_v6: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v6"); + break; + case COMMUNITY_LLGR_STALE: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "llgr-stale"); + break; + case COMMUNITY_NO_LLGR: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-llgr"); + break; + case COMMUNITY_ACCEPT_OWN_NEXTHOP: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own-nexthop"); + break; + case COMMUNITY_BLACKHOLE: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "blackhole"); + break; + case COMMUNITY_NO_EXPORT: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-export"); + break; + case COMMUNITY_NO_ADVERTISE: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-advertise"); + break; + case COMMUNITY_LOCAL_AS: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "local-AS"); + break; + case COMMUNITY_NO_PEER: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-peer"); + break; + default: + str = XSTRDUP(MTYPE_COMMUNITY_STR, "65536:65535"); + as = (comval >> 16) & 0xFFFF; + val = comval & 0xFFFF; + snprintf(str, strlen(str), "%u:%d", as, val); + break; + } + + return str; +} + +/* Internal function to perform regular expression match for + * a single community. */ +static bool community_regexp_include(regex_t *reg, struct community *com, int i) +{ + char *str; + int rv; + + /* When there is no communities attribute it is treated as empty string. + */ + if (com == NULL || com->size == 0) + str = XSTRDUP(MTYPE_COMMUNITY_STR, ""); + else + str = community_str_get(com, i); + + /* Regular expression match. */ + rv = regexec(reg, str, 0, NULL, 0); + + XFREE(MTYPE_COMMUNITY_STR, str); + + return rv == 0; +} + +/* Internal function to perform regular expression match for community + attribute. */ +static bool community_regexp_match(struct community *com, regex_t *reg) +{ + const char *str; + char *regstr; + int rv; + + /* When there is no communities attribute it is treated as empty + string. */ + if (com == NULL || com->size == 0) + str = ""; + else + str = community_str(com, false, true); + + regstr = bgp_alias2community_str(str); + + /* Regular expression match. */ + rv = regexec(reg, regstr, 0, NULL, 0); + + XFREE(MTYPE_TMP, regstr); + + return rv == 0; +} + +static char *lcommunity_str_get(struct lcommunity *lcom, int i) +{ + struct lcommunity_val lcomval; + uint32_t globaladmin; + uint32_t localdata1; + uint32_t localdata2; + char *str; + const uint8_t *ptr; + + ptr = lcom->val + (i * LCOMMUNITY_SIZE); + + memcpy(&lcomval, ptr, LCOMMUNITY_SIZE); + + /* Allocate memory. 48 bytes taken off bgp_lcommunity.c */ + ptr = (uint8_t *)lcomval.val; + ptr = ptr_get_be32(ptr, &globaladmin); + ptr = ptr_get_be32(ptr, &localdata1); + ptr = ptr_get_be32(ptr, &localdata2); + (void)ptr; /* consume value */ + + str = XMALLOC(MTYPE_LCOMMUNITY_STR, 48); + snprintf(str, 48, "%u:%u:%u", globaladmin, localdata1, localdata2); + + return str; +} + +/* Internal function to perform regular expression match for + * a single community. */ +static bool lcommunity_regexp_include(regex_t *reg, struct lcommunity *lcom, + int i) +{ + char *str; + + /* When there is no communities attribute it is treated as empty string. + */ + if (lcom == NULL || lcom->size == 0) + str = XSTRDUP(MTYPE_LCOMMUNITY_STR, ""); + else + str = lcommunity_str_get(lcom, i); + + /* Regular expression match. */ + if (regexec(reg, str, 0, NULL, 0) == 0) { + XFREE(MTYPE_LCOMMUNITY_STR, str); + return true; + } + + XFREE(MTYPE_LCOMMUNITY_STR, str); + /* No match. */ + return false; +} + +static bool lcommunity_regexp_match(struct lcommunity *com, regex_t *reg) +{ + const char *str; + char *regstr; + int rv; + + /* When there is no communities attribute it is treated as empty + string. */ + if (com == NULL || com->size == 0) + str = ""; + else + str = lcommunity_str(com, false, true); + + regstr = bgp_alias2community_str(str); + + /* Regular expression match. */ + rv = regexec(reg, regstr, 0, NULL, 0); + + XFREE(MTYPE_TMP, regstr); + + return rv == 0; +} + + +static bool ecommunity_regexp_match(struct ecommunity *ecom, regex_t *reg) +{ + const char *str; + + /* When there is no communities attribute it is treated as empty + string. */ + if (ecom == NULL || ecom->size == 0) + str = ""; + else + str = ecommunity_str(ecom); + + /* Regular expression match. */ + if (regexec(reg, str, 0, NULL, 0) == 0) + return true; + + /* No match. */ + return false; +} + +/* When given community attribute matches to the community-list return + 1 else return 0. */ +bool community_list_match(struct community *com, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == COMMUNITY_LIST_STANDARD) { + if (community_match(com, entry->u.com)) + return entry->direct == COMMUNITY_PERMIT; + } else if (entry->style == COMMUNITY_LIST_EXPANDED) { + if (community_regexp_match(com, entry->reg)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +bool lcommunity_list_match(struct lcommunity *lcom, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) { + if (lcommunity_match(lcom, entry->u.lcom)) + return entry->direct == COMMUNITY_PERMIT; + } else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED) { + if (lcommunity_regexp_match(lcom, entry->reg)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + + +/* Perform exact matching. In case of expanded large-community-list, do + * same thing as lcommunity_list_match(). + */ +bool lcommunity_list_exact_match(struct lcommunity *lcom, + struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) { + if (lcommunity_cmp(lcom, entry->u.lcom)) + return entry->direct == COMMUNITY_PERMIT; + } else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED) { + if (lcommunity_regexp_match(lcom, entry->reg)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +bool ecommunity_list_match(struct ecommunity *ecom, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == EXTCOMMUNITY_LIST_STANDARD) { + if (ecommunity_match(ecom, entry->u.ecom)) + return entry->direct == COMMUNITY_PERMIT; + } else if (entry->style == EXTCOMMUNITY_LIST_EXPANDED) { + if (ecommunity_regexp_match(ecom, entry->reg)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +/* Perform exact matching. In case of expanded community-list, do + same thing as community_list_match(). */ +bool community_list_exact_match(struct community *com, + struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == COMMUNITY_LIST_STANDARD) { + if (community_cmp(com, entry->u.com)) + return entry->direct == COMMUNITY_PERMIT; + } else if (entry->style == COMMUNITY_LIST_EXPANDED) { + if (community_regexp_match(com, entry->reg)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +bool community_list_any_match(struct community *com, struct community_list *list) +{ + struct community_entry *entry; + uint32_t val; + int i; + + for (i = 0; i < com->size; i++) { + val = community_val_get(com, i); + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style == COMMUNITY_LIST_STANDARD && + community_include(entry->u.com, val)) + return entry->direct == COMMUNITY_PERMIT; + if ((entry->style == COMMUNITY_LIST_EXPANDED) && + community_regexp_include(entry->reg, com, i)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +/* Delete all permitted communities in the list from com. */ +struct community *community_list_match_delete(struct community *com, + struct community_list *list) +{ + struct community_entry *entry; + uint32_t val; + uint32_t com_index_to_delete[com->size]; + int delete_index = 0; + int i; + + /* Loop over each community value and evaluate each against the + * community-list. If we need to delete a community value add its index + * to com_index_to_delete. + */ + for (i = 0; i < com->size; i++) { + val = community_val_get(com, i); + + for (entry = list->head; entry; entry = entry->next) { + if ((entry->style == COMMUNITY_LIST_STANDARD) && + community_include(entry->u.com, val)) { + if (entry->direct == COMMUNITY_PERMIT) { + com_index_to_delete[delete_index] = i; + delete_index++; + } + break; + } else if ((entry->style == COMMUNITY_LIST_EXPANDED) && + community_regexp_include(entry->reg, com, i)) { + if (entry->direct == COMMUNITY_PERMIT) { + com_index_to_delete[delete_index] = i; + delete_index++; + } + break; + } + } + } + + /* Delete all of the communities we flagged for deletion */ + for (i = delete_index - 1; i >= 0; i--) { + val = community_val_get(com, com_index_to_delete[i]); + val = htonl(val); + community_del_val(com, &val); + } + + return com; +} + +/* To avoid duplicated entry in the community-list, this function + compares specified entry to existing entry. */ +static bool community_list_dup_check(struct community_list *list, + struct community_entry *new) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry->style != new->style) + continue; + + if (entry->direct != new->direct) + continue; + + switch (entry->style) { + case COMMUNITY_LIST_STANDARD: + if (community_cmp(entry->u.com, new->u.com)) + return true; + break; + case LARGE_COMMUNITY_LIST_STANDARD: + if (lcommunity_cmp(entry->u.lcom, new->u.lcom)) + return true; + break; + case EXTCOMMUNITY_LIST_STANDARD: + if (ecommunity_cmp(entry->u.ecom, new->u.ecom)) + return true; + break; + case COMMUNITY_LIST_EXPANDED: + case EXTCOMMUNITY_LIST_EXPANDED: + case LARGE_COMMUNITY_LIST_EXPANDED: + if (strcmp(entry->config, new->config) == 0) + return true; + break; + default: + break; + } + } + return false; +} + +/* Set community-list. */ +int community_list_set(struct community_list_handler *ch, const char *name, + const char *str, const char *seq, int direct, int style) +{ + struct community_entry *entry = NULL; + struct community_list *list; + struct community *com = NULL; + regex_t *regex = NULL; + int64_t seqnum = COMMUNITY_SEQ_NUMBER_AUTO; + + if (seq) + seqnum = (int64_t)atol(seq); + + /* Get community list. */ + list = community_list_get(ch, name, COMMUNITY_LIST_MASTER); + + /* When community-list already has entry, new entry should have same + style. If you want to have mixed style community-list, you can + comment out this check. */ + if (!community_list_empty_p(list)) { + struct community_entry *first; + + first = list->head; + + if (style != first->style) { + return (first->style == COMMUNITY_LIST_STANDARD + ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT + : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); + } + } + + if (style == COMMUNITY_LIST_STANDARD) + com = community_str2com(str); + else + regex = bgp_regcomp(str); + + if (!com && !regex) + return COMMUNITY_LIST_ERR_MALFORMED_VAL; + + entry = community_entry_new(); + entry->direct = direct; + entry->style = style; + entry->u.com = com; + entry->reg = regex; + entry->seq = seqnum; + entry->config = + (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL); + + /* Do not put duplicated community entry. */ + if (community_list_dup_check(list, entry)) + community_entry_free(entry); + else { + community_list_entry_add(list, entry, ch, + COMMUNITY_LIST_MASTER); + route_map_notify_dependencies(name, RMAP_EVENT_CLIST_ADDED); + } + + return 0; +} + +/* Unset community-list */ +void community_list_unset(struct community_list_handler *ch, const char *name, + const char *str, const char *seq, int direct, + int style) +{ + struct community_list_master *cm = NULL; + struct community_entry *entry = NULL; + struct community_list *list; + struct community *com = NULL; + + /* Lookup community list. */ + list = community_list_lookup(ch, name, 0, COMMUNITY_LIST_MASTER); + if (list == NULL) + return; + + cm = community_list_master_lookup(ch, COMMUNITY_LIST_MASTER); + /* Delete all of entry belongs to this community-list. */ + if (!str) { + community_list_delete(cm, list); + route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED); + return; + } + + if (style == COMMUNITY_LIST_STANDARD) + com = community_str2com(str); + + if (com) { + entry = community_list_entry_lookup(list, com, direct); + community_free(&com); + } else + entry = community_list_entry_lookup(list, str, direct); + + if (!entry) + return; + + community_list_entry_delete(cm, list, entry); + route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED); +} + +bool lcommunity_list_any_match(struct lcommunity *lcom, + struct community_list *list) +{ + struct community_entry *entry; + uint8_t *ptr; + int i; + + for (i = 0; i < lcom->size; i++) { + ptr = lcom->val + (i * LCOMMUNITY_SIZE); + + for (entry = list->head; entry; entry = entry->next) { + if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD) && + lcommunity_include(entry->u.lcom, ptr)) + return entry->direct == COMMUNITY_PERMIT; + if ((entry->style == LARGE_COMMUNITY_LIST_EXPANDED) && + lcommunity_regexp_include(entry->reg, lcom, i)) + return entry->direct == COMMUNITY_PERMIT; + } + } + return false; +} + +/* Delete all permitted large communities in the list from com. */ +struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom, + struct community_list *list) +{ + struct community_entry *entry; + uint32_t com_index_to_delete[lcom->size]; + uint8_t *ptr; + int delete_index = 0; + int i; + + /* Loop over each lcommunity value and evaluate each against the + * community-list. If we need to delete a community value add its index + * to com_index_to_delete. + */ + for (i = 0; i < lcom->size; i++) { + ptr = lcom->val + (i * LCOMMUNITY_SIZE); + for (entry = list->head; entry; entry = entry->next) { + if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD) && + lcommunity_include(entry->u.lcom, ptr)) { + if (entry->direct == COMMUNITY_PERMIT) { + com_index_to_delete[delete_index] = i; + delete_index++; + } + break; + } + + else if ((entry->style == + LARGE_COMMUNITY_LIST_EXPANDED) && + lcommunity_regexp_include(entry->reg, lcom, + i)) { + if (entry->direct == COMMUNITY_PERMIT) { + com_index_to_delete[delete_index] = i; + delete_index++; + } + break; + } + } + } + + /* Delete all of the communities we flagged for deletion */ + for (i = delete_index - 1; i >= 0; i--) { + ptr = lcom->val + (com_index_to_delete[i] * LCOMMUNITY_SIZE); + lcommunity_del_val(lcom, ptr); + } + + return lcom; +} + +/* Delete all permitted extended communities in the list from ecom.*/ +struct ecommunity *ecommunity_list_match_delete(struct ecommunity *ecom, + struct community_list *list) +{ + struct community_entry *entry; + uint32_t com_index_to_delete[ecom->size]; + uint8_t *ptr; + uint32_t delete_index = 0; + uint32_t i; + struct ecommunity local_ecom = {.size = 1}; + struct ecommunity_val local_eval = {0}; + + for (i = 0; i < ecom->size; i++) { + local_ecom.val = ecom->val + (i * ECOMMUNITY_SIZE); + for (entry = list->head; entry; entry = entry->next) { + if (((entry->style == EXTCOMMUNITY_LIST_STANDARD) && + ecommunity_include(entry->u.ecom, &local_ecom)) || + ((entry->style == EXTCOMMUNITY_LIST_EXPANDED) && + ecommunity_regexp_match(ecom, entry->reg))) { + if (entry->direct == COMMUNITY_PERMIT) { + com_index_to_delete[delete_index] = i; + delete_index++; + } + break; + } + } + } + + /* Delete all of the extended communities we flagged for deletion */ + for (i = delete_index; i > 0; i--) { + ptr = ecom->val + (com_index_to_delete[i-1] * ECOMMUNITY_SIZE); + memcpy(&local_eval.val, ptr, sizeof(local_eval.val)); + ecommunity_del_val(ecom, &local_eval); + } + + return ecom; +} + +/* Helper to check if every octet do not exceed UINT_MAX */ +bool lcommunity_list_valid(const char *community, int style) +{ + int octets; + char **splits, **communities; + char *endptr; + int num, num_communities; + regex_t *regres; + int invalid = 0; + + frrstr_split(community, " ", &communities, &num_communities); + + for (int j = 0; j < num_communities; j++) { + octets = 0; + frrstr_split(communities[j], ":", &splits, &num); + + for (int i = 0; i < num; i++) { + if (strlen(splits[i]) == 0) + /* There is no digit to check */ + invalid++; + + if (style == LARGE_COMMUNITY_LIST_STANDARD) { + if (*splits[i] == '-') + /* Must not be negative */ + invalid++; + else if (strtoul(splits[i], &endptr, 10) + > UINT_MAX) + /* Larger than 4 octets */ + invalid++; + else if (*endptr) + /* Not all characters were digits */ + invalid++; + } else { + regres = bgp_regcomp(communities[j]); + if (!regres) + /* malformed regex */ + invalid++; + else + bgp_regex_free(regres); + } + + octets++; + XFREE(MTYPE_TMP, splits[i]); + } + XFREE(MTYPE_TMP, splits); + + if (octets != 3) + invalid++; + + XFREE(MTYPE_TMP, communities[j]); + } + XFREE(MTYPE_TMP, communities); + + return (invalid > 0) ? false : true; +} + +/* Set lcommunity-list. */ +int lcommunity_list_set(struct community_list_handler *ch, const char *name, + const char *str, const char *seq, int direct, int style) +{ + struct community_entry *entry = NULL; + struct community_list *list; + struct lcommunity *lcom = NULL; + regex_t *regex = NULL; + int64_t seqnum = COMMUNITY_SEQ_NUMBER_AUTO; + + if (seq) + seqnum = (int64_t)atol(seq); + + /* Get community list. */ + list = community_list_get(ch, name, LARGE_COMMUNITY_LIST_MASTER); + + /* When community-list already has entry, new entry should have same + style. If you want to have mixed style community-list, you can + comment out this check. */ + if (!community_list_empty_p(list)) { + struct community_entry *first; + + first = list->head; + + if (style != first->style) { + return (first->style == COMMUNITY_LIST_STANDARD + ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT + : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); + } + } + + if (str) { + if (style == LARGE_COMMUNITY_LIST_STANDARD) + lcom = lcommunity_str2com(str); + else + regex = bgp_regcomp(str); + + if (!lcom && !regex) + return COMMUNITY_LIST_ERR_MALFORMED_VAL; + } + + entry = community_entry_new(); + entry->direct = direct; + entry->style = style; + entry->u.lcom = lcom; + entry->reg = regex; + entry->seq = seqnum; + entry->config = + (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL); + + /* Do not put duplicated community entry. */ + if (community_list_dup_check(list, entry)) + community_entry_free(entry); + else { + community_list_entry_add(list, entry, ch, + LARGE_COMMUNITY_LIST_MASTER); + route_map_notify_dependencies(name, RMAP_EVENT_LLIST_ADDED); + } + + return 0; +} + +/* Unset community-list. When str is NULL, delete all of + community-list entry belongs to the specified name. */ +void lcommunity_list_unset(struct community_list_handler *ch, const char *name, + const char *str, const char *seq, int direct, + int style) +{ + struct community_list_master *cm = NULL; + struct community_entry *entry = NULL; + struct community_list *list; + struct lcommunity *lcom = NULL; + regex_t *regex = NULL; + + /* Lookup community list. */ + list = community_list_lookup(ch, name, 0, LARGE_COMMUNITY_LIST_MASTER); + if (list == NULL) + return; + + cm = community_list_master_lookup(ch, LARGE_COMMUNITY_LIST_MASTER); + /* Delete all of entry belongs to this community-list. */ + if (!str) { + community_list_delete(cm, list); + route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED); + return; + } + + if (style == LARGE_COMMUNITY_LIST_STANDARD) + lcom = lcommunity_str2com(str); + else + regex = bgp_regcomp(str); + + if (!lcom && !regex) + return; + + if (lcom) + entry = community_list_entry_lookup(list, lcom, direct); + else + entry = community_list_entry_lookup(list, str, direct); + + if (lcom) + lcommunity_free(&lcom); + if (regex) + bgp_regex_free(regex); + + if (!entry) + return; + + community_list_entry_delete(cm, list, entry); + route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED); +} + +/* Set extcommunity-list. */ +int extcommunity_list_set(struct community_list_handler *ch, const char *name, + const char *str, const char *seq, int direct, + int style) +{ + struct community_entry *entry = NULL; + struct community_list *list; + struct ecommunity *ecom = NULL; + regex_t *regex = NULL; + int64_t seqnum = COMMUNITY_SEQ_NUMBER_AUTO; + + if (seq) + seqnum = (int64_t)atol(seq); + + if (str == NULL) + return COMMUNITY_LIST_ERR_MALFORMED_VAL; + + /* Get community list. */ + list = community_list_get(ch, name, EXTCOMMUNITY_LIST_MASTER); + + /* When community-list already has entry, new entry should have same + style. If you want to have mixed style community-list, you can + comment out this check. */ + if (!community_list_empty_p(list)) { + struct community_entry *first; + + first = list->head; + + if (style != first->style) { + return (first->style == EXTCOMMUNITY_LIST_STANDARD + ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT + : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT); + } + } + + if (style == EXTCOMMUNITY_LIST_STANDARD) + ecom = ecommunity_str2com(str, 0, 1); + else + regex = bgp_regcomp(str); + + if (!ecom && !regex) + return COMMUNITY_LIST_ERR_MALFORMED_VAL; + + if (ecom) + ecom->str = + ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0); + + entry = community_entry_new(); + entry->direct = direct; + entry->style = style; + if (ecom) + entry->config = ecommunity_ecom2str( + ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); + else if (regex) + entry->config = XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str); + + entry->u.ecom = ecom; + entry->reg = regex; + entry->seq = seqnum; + + /* Do not put duplicated community entry. */ + if (community_list_dup_check(list, entry)) + community_entry_free(entry); + else { + community_list_entry_add(list, entry, ch, + EXTCOMMUNITY_LIST_MASTER); + route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_ADDED); + } + + return 0; +} + +/* Unset extcommunity-list. + * + * When str is NULL, delete all extcommunity-list entries belonging to the + * specified name. + */ +void extcommunity_list_unset(struct community_list_handler *ch, + const char *name, const char *str, const char *seq, + int direct, int style) +{ + struct community_list_master *cm = NULL; + struct community_entry *entry = NULL; + struct community_list *list; + struct ecommunity *ecom = NULL; + + /* Lookup extcommunity list. */ + list = community_list_lookup(ch, name, 0, EXTCOMMUNITY_LIST_MASTER); + if (list == NULL) + return; + + cm = community_list_master_lookup(ch, EXTCOMMUNITY_LIST_MASTER); + /* Delete all of entry belongs to this extcommunity-list. */ + if (!str) { + community_list_delete(cm, list); + route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED); + return; + } + + if (style == EXTCOMMUNITY_LIST_STANDARD) + ecom = ecommunity_str2com(str, 0, 1); + + if (ecom) { + entry = community_list_entry_lookup(list, ecom, direct); + ecommunity_free(&ecom); + } else + entry = community_list_entry_lookup(list, str, direct); + + if (!entry) + return; + + community_list_entry_delete(cm, list, entry); + route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED); +} + +/* Initializa community-list. Return community-list handler. */ +struct community_list_handler *community_list_init(void) +{ + struct community_list_handler *ch; + ch = XCALLOC(MTYPE_COMMUNITY_LIST_HANDLER, + sizeof(struct community_list_handler)); + + ch->community_list.hash = + hash_create_size(4, bgp_clist_hash_key_community_list, + bgp_clist_hash_cmp_community_list, + "Community List Number Quick Lookup"); + + ch->extcommunity_list.hash = + hash_create_size(4, bgp_clist_hash_key_community_list, + bgp_clist_hash_cmp_community_list, + "Extended Community List Quick Lookup"); + + ch->lcommunity_list.hash = + hash_create_size(4, bgp_clist_hash_key_community_list, + bgp_clist_hash_cmp_community_list, + "Large Community List Quick Lookup"); + + return ch; +} + +/* Terminate community-list. */ +void community_list_terminate(struct community_list_handler *ch) +{ + struct community_list_master *cm; + struct community_list *list; + + cm = &ch->community_list; + while ((list = cm->num.head) != NULL) + community_list_delete(cm, list); + while ((list = cm->str.head) != NULL) + community_list_delete(cm, list); + hash_free(cm->hash); + + cm = &ch->lcommunity_list; + while ((list = cm->num.head) != NULL) + community_list_delete(cm, list); + while ((list = cm->str.head) != NULL) + community_list_delete(cm, list); + hash_free(cm->hash); + + cm = &ch->extcommunity_list; + while ((list = cm->num.head) != NULL) + community_list_delete(cm, list); + while ((list = cm->str.head) != NULL) + community_list_delete(cm, list); + hash_free(cm->hash); + + XFREE(MTYPE_COMMUNITY_LIST_HANDLER, ch); +} + +static int bgp_community_list_vector_walker(struct hash_bucket *bucket, + void *data) +{ + vector *comps = data; + struct community_list *list = bucket->data; + + vector_set(*comps, XSTRDUP(MTYPE_COMPLETION, list->name)); + + return 1; +} + +static void bgp_community_list_cmd_completion(vector comps, + struct cmd_token *token) +{ + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, COMMUNITY_LIST_MASTER); + + hash_walk(cm->hash, bgp_community_list_vector_walker, &comps); +} + +static void bgp_lcommunity_list_cmd_completion(vector comps, + struct cmd_token *token) +{ + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, + LARGE_COMMUNITY_LIST_MASTER); + + hash_walk(cm->hash, bgp_community_list_vector_walker, &comps); +} + +static void bgp_extcommunity_list_cmd_completion(vector comps, + struct cmd_token *token) +{ + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, EXTCOMMUNITY_LIST_MASTER); + + hash_walk(cm->hash, bgp_community_list_vector_walker, &comps); +} + +static const struct cmd_variable_handler community_list_handlers[] = { + {.tokenname = "COMMUNITY_LIST_NAME", + .completions = bgp_community_list_cmd_completion}, + {.completions = NULL}}; + +static const struct cmd_variable_handler lcommunity_list_handlers[] = { + {.tokenname = "LCOMMUNITY_LIST_NAME", + .completions = bgp_lcommunity_list_cmd_completion}, + {.completions = NULL}}; + +static const struct cmd_variable_handler extcommunity_list_handlers[] = { + {.tokenname = "EXTCOMMUNITY_LIST_NAME", + .completions = bgp_extcommunity_list_cmd_completion}, + {.completions = NULL}}; + +void bgp_community_list_command_completion_setup(void) +{ + cmd_variable_handler_register(community_list_handlers); + cmd_variable_handler_register(lcommunity_list_handlers); + cmd_variable_handler_register(extcommunity_list_handlers); +} diff --git a/bgpd/bgp_clist.h b/bgpd/bgp_clist.h new file mode 100644 index 0000000..29bd880 --- /dev/null +++ b/bgpd/bgp_clist.h @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Community list. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_CLIST_H +#define _QUAGGA_BGP_CLIST_H + +#include "jhash.h" + +/* Master Community-list. */ +#define COMMUNITY_LIST_MASTER 0 +#define EXTCOMMUNITY_LIST_MASTER 1 +#define LARGE_COMMUNITY_LIST_MASTER 2 + +/* Community-list deny and permit. */ +#define COMMUNITY_DENY 0 +#define COMMUNITY_PERMIT 1 + +/* Number and string based community-list name. */ +#define COMMUNITY_LIST_STRING 0 +#define COMMUNITY_LIST_NUMBER 1 + +#define COMMUNITY_SEQ_NUMBER_AUTO -1 + +/* Community-list entry types. */ +#define COMMUNITY_LIST_STANDARD 0 /* Standard community-list. */ +#define COMMUNITY_LIST_EXPANDED 1 /* Expanded community-list. */ +#define EXTCOMMUNITY_LIST_STANDARD 2 /* Standard extcommunity-list. */ +#define EXTCOMMUNITY_LIST_EXPANDED 3 /* Expanded extcommunity-list. */ +#define LARGE_COMMUNITY_LIST_STANDARD 4 /* Standard Large community-list. */ +#define LARGE_COMMUNITY_LIST_EXPANDED 5 /* Expanded Large community-list. */ + +/* Community-list. */ +struct community_list { + /* Name of the community-list. */ + char *name; + + /* Stored hash value of name, to further speed up hash operations */ + uint32_t name_hash; + + /* String or number. */ + int sort; + + /* Link to upper list. */ + struct community_list_list *parent; + + /* Linked list for other community-list. */ + struct community_list *next; + struct community_list *prev; + + /* Community-list entry in this community-list. */ + struct community_entry *head; + struct community_entry *tail; +}; + +/* Each entry in community-list. */ +struct community_entry { + struct community_entry *next; + struct community_entry *prev; + + /* Permit or deny. */ + uint8_t direct; + + /* Standard or expanded. */ + uint8_t style; + + /* Sequence number. */ + int64_t seq; + + /* Community structure. */ + union { + struct community *com; + struct ecommunity *ecom; + struct lcommunity *lcom; + } u; + + /* Configuration string. */ + char *config; + + /* Expanded community-list regular expression. */ + regex_t *reg; +}; + +/* Linked list of community-list. */ +struct community_list_list { + struct community_list *head; + struct community_list *tail; +}; + +/* Master structure of community-list and extcommunity-list. */ +struct community_list_master { + struct community_list_list num; + struct community_list_list str; + struct hash *hash; +}; + +/* Community-list handler. community_list_init() returns this + structure as handler. */ +struct community_list_handler { + /* Community-list. */ + struct community_list_master community_list; + + /* Exteded community-list. */ + struct community_list_master extcommunity_list; + + /* Large community-list. */ + struct community_list_master lcommunity_list; +}; + +/* Error code of community-list. */ +#define COMMUNITY_LIST_ERR_MALFORMED_VAL -1 +#define COMMUNITY_LIST_ERR_STANDARD_CONFLICT -2 +#define COMMUNITY_LIST_ERR_EXPANDED_CONFLICT -3 +/* Handler. */ +extern struct community_list_handler *bgp_clist; + +/* Prototypes. */ +extern struct community_list_handler *community_list_init(void); +extern void community_list_terminate(struct community_list_handler *ch); + +extern int community_list_set(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); +extern void community_list_unset(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); +extern int extcommunity_list_set(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); +extern void extcommunity_list_unset(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); +extern int lcommunity_list_set(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); +extern bool lcommunity_list_valid(const char *community, int style); +extern void lcommunity_list_unset(struct community_list_handler *ch, + const char *name, const char *str, + const char *seq, int direct, int style); + +extern struct community_list_master * +community_list_master_lookup(struct community_list_handler *ch, int master); + +extern struct community_list * +community_list_lookup(struct community_list_handler *c, const char *name, + uint32_t name_hash, int master); + +extern bool community_list_match(struct community *com, + struct community_list *list); +extern bool ecommunity_list_match(struct ecommunity *ecom, + struct community_list *list); +extern bool lcommunity_list_match(struct lcommunity *lcom, + struct community_list *list); +extern bool community_list_exact_match(struct community *com, + struct community_list *list); +extern bool lcommunity_list_exact_match(struct lcommunity *lcom, + struct community_list *list); +extern bool community_list_any_match(struct community *com, + struct community_list *list); +extern struct community * +community_list_match_delete(struct community *com, struct community_list *list); +extern bool lcommunity_list_any_match(struct lcommunity *lcom, + struct community_list *list); +extern struct lcommunity * +lcommunity_list_match_delete(struct lcommunity *lcom, + struct community_list *list); +extern struct ecommunity * +ecommunity_list_match_delete(struct ecommunity *ecom, + struct community_list *list); + +static inline uint32_t bgp_clist_hash_key(char *name) +{ + return jhash(name, strlen(name), 0xdeadbeaf); +} + +extern void bgp_community_list_command_completion_setup(void); + +#endif /* _QUAGGA_BGP_CLIST_H */ diff --git a/bgpd/bgp_community.c b/bgpd/bgp_community.c new file mode 100644 index 0000000..8e4c430 --- /dev/null +++ b/bgpd/bgp_community.c @@ -0,0 +1,1043 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Community attribute related functions. + * Copyright (C) 1998, 2001 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "hash.h" +#include "memory.h" +#include "jhash.h" +#include "frrstr.h" + +#include "bgpd/bgp_memory.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" + +/* Hash of community attribute. */ +static struct hash *comhash; + +/* Allocate a new communities value. */ +static struct community *community_new(void) +{ + return XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); +} + +/* Free communities value. */ +void community_free(struct community **com) +{ + if (!(*com)) + return; + + XFREE(MTYPE_COMMUNITY_VAL, (*com)->val); + XFREE(MTYPE_COMMUNITY_STR, (*com)->str); + + if ((*com)->json) { + json_object_free((*com)->json); + (*com)->json = NULL; + } + + XFREE(MTYPE_COMMUNITY, (*com)); +} + +/* Add one community value to the community. */ +void community_add_val(struct community *com, uint32_t val) +{ + com->size++; + com->val = XREALLOC(MTYPE_COMMUNITY_VAL, com->val, com_length(com)); + + val = htonl(val); + memcpy(com_lastval(com), &val, sizeof(uint32_t)); +} + +/* Delete one community. */ +void community_del_val(struct community *com, uint32_t *val) +{ + int i = 0; + int c = 0; + + if (!com->val) + return; + + while (i < com->size) { + if (memcmp(com->val + i, val, sizeof(uint32_t)) == 0) { + c = com->size - i - 1; + + if (c > 0) + memmove(com->val + i, com->val + (i + 1), + c * sizeof(*val)); + + com->size--; + + if (com->size > 0) + com->val = XREALLOC(MTYPE_COMMUNITY_VAL, + com->val, com_length(com)); + else { + XFREE(MTYPE_COMMUNITY_VAL, com->val); + } + return; + } + i++; + } +} + +/* Delete all communities listed in com2 from com1 */ +struct community *community_delete(struct community *com1, + struct community *com2) +{ + int i = 0; + + while (i < com2->size) { + community_del_val(com1, com2->val + i); + i++; + } + + return com1; +} + +/* Callback function from qsort(). */ +static int community_compare(const void *a1, const void *a2) +{ + uint32_t v1; + uint32_t v2; + + memcpy(&v1, a1, sizeof(uint32_t)); + memcpy(&v2, a2, sizeof(uint32_t)); + v1 = ntohl(v1); + v2 = ntohl(v2); + + if (v1 < v2) + return -1; + if (v1 > v2) + return 1; + return 0; +} + +bool community_include(struct community *com, uint32_t val) +{ + int i; + + val = htonl(val); + + for (i = 0; i < com->size; i++) + if (memcmp(&val, com_nthval(com, i), sizeof(uint32_t)) == 0) + return true; + return false; +} + +uint32_t community_val_get(struct community *com, int i) +{ + uint8_t *p; + uint32_t val; + + p = (uint8_t *)com->val; + p += (i * COMMUNITY_SIZE); + + memcpy(&val, p, sizeof(uint32_t)); + + return ntohl(val); +} + +/* Sort and uniq given community. */ +struct community *community_uniq_sort(struct community *com) +{ + int i; + struct community *new; + uint32_t val; + + if (!com) + return NULL; + + new = community_new(); + new->json = NULL; + + for (i = 0; i < com->size; i++) { + val = community_val_get(com, i); + + if (!community_include(new, val)) + community_add_val(new, val); + } + + qsort(new->val, new->size, sizeof(uint32_t), community_compare); + + return new; +} + +/* Convert communities attribute to string. + + For Well-known communities value, below keyword is used. + + 0xFFFF0000 "graceful-shutdown" + 0xFFFF0001 "accept-own" + 0xFFFF0002 "route-filter-translated-v4" + 0xFFFF0003 "route-filter-v4" + 0xFFFF0004 "route-filter-translated-v6" + 0xFFFF0005 "route-filter-v6" + 0xFFFF0006 "llgr-stale" + 0xFFFF0007 "no-llgr" + 0xFFFF0008 "accept-own-nexthop" + 0xFFFF029A "blackhole" + 0xFFFFFF01 "no-export" + 0xFFFFFF02 "no-advertise" + 0xFFFFFF03 "local-AS" + 0xFFFFFF04 "no-peer" + + For other values, "AS:VAL" format is used. */ +static void set_community_string(struct community *com, bool make_json, + bool translate_alias) +{ + int i; + char *str; + int len; + int first; + uint32_t comval; + uint16_t as; + uint16_t val; + json_object *json_community_list = NULL; + json_object *json_string = NULL; + + if (!com) + return; + + if (make_json) { + com->json = json_object_new_object(); + json_community_list = json_object_new_array(); + } + + /* When communities attribute is empty. */ + if (com->size == 0) { + str = XMALLOC(MTYPE_COMMUNITY_STR, 1); + str[0] = '\0'; + + if (make_json) { + json_object_string_add(com->json, "string", ""); + json_object_object_add(com->json, "list", + json_community_list); + } + com->str = str; + return; + } + + /* Memory allocation is time consuming work. So we calculate + required string length first. */ + len = 0; + + for (i = 0; i < com->size; i++) { + memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); + comval = ntohl(comval); + + switch (comval) { + case COMMUNITY_GSHUT: + len += strlen(" graceful-shutdown"); + break; + case COMMUNITY_ACCEPT_OWN: + len += strlen(" accept-own"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: + len += strlen(" route-filter-translated-v4"); + break; + case COMMUNITY_ROUTE_FILTER_v4: + len += strlen(" route-filter-v4"); + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: + len += strlen(" route-filter-translated-v6"); + break; + case COMMUNITY_ROUTE_FILTER_v6: + len += strlen(" route-filter-v6"); + break; + case COMMUNITY_LLGR_STALE: + len += strlen(" llgr-stale"); + break; + case COMMUNITY_NO_LLGR: + len += strlen(" no-llgr"); + break; + case COMMUNITY_ACCEPT_OWN_NEXTHOP: + len += strlen(" accept-own-nexthop"); + break; + case COMMUNITY_BLACKHOLE: + len += strlen(" blackhole"); + break; + case COMMUNITY_NO_EXPORT: + len += strlen(" no-export"); + break; + case COMMUNITY_NO_ADVERTISE: + len += strlen(" no-advertise"); + break; + case COMMUNITY_LOCAL_AS: + len += strlen(" local-AS"); + break; + case COMMUNITY_NO_PEER: + len += strlen(" no-peer"); + break; + default: + len = BUFSIZ; + break; + } + } + + /* Allocate memory. */ + str = XCALLOC(MTYPE_COMMUNITY_STR, len); + first = 1; + + /* Fill in string. */ + for (i = 0; i < com->size; i++) { + memcpy(&comval, com_nthval(com, i), sizeof(uint32_t)); + comval = ntohl(comval); + + if (first) + first = 0; + else + strlcat(str, " ", len); + + switch (comval) { + case COMMUNITY_GSHUT: + strlcat(str, "graceful-shutdown", len); + if (make_json) { + json_string = json_object_new_string( + "gracefulShutdown"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ACCEPT_OWN: + strlcat(str, "accept-own", len); + if (make_json) { + json_string = json_object_new_string( + "acceptown"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: + strlcat(str, "route-filter-translated-v4", len); + if (make_json) { + json_string = json_object_new_string( + "routeFilterTranslatedV4"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ROUTE_FILTER_v4: + strlcat(str, "route-filter-v4", len); + if (make_json) { + json_string = json_object_new_string( + "routeFilterV4"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: + strlcat(str, "route-filter-translated-v6", len); + if (make_json) { + json_string = json_object_new_string( + "routeFilterTranslatedV6"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ROUTE_FILTER_v6: + strlcat(str, "route-filter-v6", len); + if (make_json) { + json_string = json_object_new_string( + "routeFilterV6"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_LLGR_STALE: + strlcat(str, "llgr-stale", len); + if (make_json) { + json_string = json_object_new_string( + "llgrStale"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_NO_LLGR: + strlcat(str, "no-llgr", len); + if (make_json) { + json_string = json_object_new_string( + "noLlgr"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_ACCEPT_OWN_NEXTHOP: + strlcat(str, "accept-own-nexthop", len); + if (make_json) { + json_string = json_object_new_string( + "acceptownnexthop"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_BLACKHOLE: + strlcat(str, "blackhole", len); + if (make_json) { + json_string = json_object_new_string( + "blackhole"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_NO_EXPORT: + strlcat(str, "no-export", len); + if (make_json) { + json_string = + json_object_new_string("noExport"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_NO_ADVERTISE: + strlcat(str, "no-advertise", len); + if (make_json) { + json_string = + json_object_new_string("noAdvertise"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_LOCAL_AS: + strlcat(str, "local-AS", len); + if (make_json) { + json_string = json_object_new_string("localAs"); + json_object_array_add(json_community_list, + json_string); + } + break; + case COMMUNITY_NO_PEER: + strlcat(str, "no-peer", len); + if (make_json) { + json_string = json_object_new_string("noPeer"); + json_object_array_add(json_community_list, + json_string); + } + break; + default: + as = (comval >> 16) & 0xFFFF; + val = comval & 0xFFFF; + char buf[32]; + snprintf(buf, sizeof(buf), "%u:%d", as, val); + const char *com2alias = + translate_alias ? bgp_community2alias(buf) + : buf; + + strlcat(str, com2alias, len); + if (make_json) { + json_string = json_object_new_string(com2alias); + json_object_array_add(json_community_list, + json_string); + } + break; + } + } + + if (make_json) { + json_object_string_add(com->json, "string", str); + json_object_object_add(com->json, "list", json_community_list); + } + com->str = str; +} + +/* Intern communities attribute. */ +struct community *community_intern(struct community *com) +{ + struct community *find; + + /* Assert this community structure is not interned. */ + assert(com->refcnt == 0); + + /* Lookup community hash. */ + find = (struct community *)hash_get(comhash, com, hash_alloc_intern); + + /* Arguemnt com is allocated temporary. So when it is not used in + hash, it should be freed. */ + if (find != com) + community_free(&com); + + /* Increment refrence counter. */ + find->refcnt++; + + /* Make string. */ + if (!find->str) + set_community_string(find, false, true); + + return find; +} + +/* Free community attribute. */ +void community_unintern(struct community **com) +{ + struct community *ret; + + if (!*com) + return; + + if ((*com)->refcnt) + (*com)->refcnt--; + + /* Pull off from hash. */ + if ((*com)->refcnt == 0) { + /* Community value com must exist in hash. */ + ret = (struct community *)hash_release(comhash, *com); + assert(ret != NULL); + + community_free(com); + } +} + +/* Create new community attribute. */ +struct community *community_parse(uint32_t *pnt, unsigned short length) +{ + struct community tmp; + struct community *new; + + /* If length is malformed return NULL. */ + if (length % COMMUNITY_SIZE) + return NULL; + + /* Make temporary community for hash look up. */ + tmp.size = length / COMMUNITY_SIZE; + tmp.val = pnt; + + new = community_uniq_sort(&tmp); + + return community_intern(new); +} + +struct community *community_dup(struct community *com) +{ + struct community *new; + + new = XCALLOC(MTYPE_COMMUNITY, sizeof(struct community)); + new->size = com->size; + if (new->size) { + new->val = XMALLOC(MTYPE_COMMUNITY_VAL, + com->size * COMMUNITY_SIZE); + memcpy(new->val, com->val, com->size * COMMUNITY_SIZE); + } else + new->val = NULL; + return new; +} + +/* Return string representation of communities attribute. */ +char *community_str(struct community *com, bool make_json, bool translate_alias) +{ + if (!com) + return NULL; + + if (make_json && !com->json && com->str) + XFREE(MTYPE_COMMUNITY_STR, com->str); + + if (!com->str) + set_community_string(com, make_json, translate_alias); + return com->str; +} + +/* Make hash value of community attribute. This function is used by + hash package.*/ +unsigned int community_hash_make(const struct community *com) +{ + uint32_t *pnt = com->val; + + return jhash2(pnt, com->size, 0x43ea96c1); +} + +bool community_match(const struct community *com1, const struct community *com2) +{ + int i = 0; + int j = 0; + + if (com1 == NULL && com2 == NULL) + return true; + + if (com1 == NULL || com2 == NULL) + return false; + + if (com1->size < com2->size) + return false; + + /* Every community on com2 needs to be on com1 for this to match */ + while (i < com1->size && j < com2->size) { + if (memcmp(com1->val + i, com2->val + j, sizeof(uint32_t)) == 0) + j++; + i++; + } + + if (j == com2->size) + return true; + else + return false; +} + +bool community_cmp(const struct community *com1, const struct community *com2) +{ + if (com1 == NULL && com2 == NULL) + return true; + if (com1 == NULL || com2 == NULL) + return false; + + if (com1->size == com2->size) + if (memcmp(com1->val, com2->val, com1->size * COMMUNITY_SIZE) + == 0) + return true; + return false; +} + +/* Add com2 to the end of com1. */ +struct community *community_merge(struct community *com1, + struct community *com2) +{ + com1->val = XREALLOC(MTYPE_COMMUNITY_VAL, com1->val, + (com1->size + com2->size) * COMMUNITY_SIZE); + + memcpy(com1->val + com1->size, com2->val, com2->size * COMMUNITY_SIZE); + com1->size += com2->size; + + return com1; +} + +/* Community token enum. */ +enum community_token { + community_token_val, + community_token_gshut, + community_token_accept_own, + community_token_route_filter_translated_v4, + community_token_route_filter_v4, + community_token_route_filter_translated_v6, + community_token_route_filter_v6, + community_token_llgr_stale, + community_token_no_llgr, + community_token_accept_own_nexthop, + community_token_blackhole, + community_token_no_export, + community_token_no_advertise, + community_token_local_as, + community_token_no_peer, + community_token_unknown +}; + +/* Helper to check if a given community is valid */ +static bool community_valid(const char *community) +{ + int octets = 0; + char **splits; + int num; + int invalid = 0; + + frrstr_split(community, ":", &splits, &num); + + for (int i = 0; i < num; i++) { + if (strtoul(splits[i], NULL, 10) > UINT16_MAX) + invalid++; + + if (strlen(splits[i]) == 0) + invalid++; + + octets++; + XFREE(MTYPE_TMP, splits[i]); + } + XFREE(MTYPE_TMP, splits); + + return (octets < 2 || invalid) ? false : true; +} + +/* Get next community token from string. */ +static const char * +community_gettoken(const char *buf, enum community_token *token, uint32_t *val) +{ + const char *p = buf; + + /* Skip white space. */ + while (isspace((unsigned char)*p)) + p++; + + /* Check the end of the line. */ + if (*p == '\0') + return NULL; + + /* Well known community string check. */ + if (isalpha((unsigned char)*p)) { + if (strncmp(p, "graceful-shutdown", strlen("graceful-shutdown")) + == 0) { + *val = COMMUNITY_GSHUT; + *token = community_token_gshut; + p += strlen("graceful-shutdown"); + return p; + } + if (strncmp(p, "accept-own-nexthop", + strlen("accept-own-nexthop")) + == 0) { + *val = COMMUNITY_ACCEPT_OWN_NEXTHOP; + *token = community_token_accept_own_nexthop; + p += strlen("accept-own-nexthop"); + return p; + } + if (strncmp(p, "accept-own", strlen("accept-own")) + == 0) { + *val = COMMUNITY_ACCEPT_OWN; + *token = community_token_accept_own; + p += strlen("accept-own"); + return p; + } + if (strncmp(p, "route-filter-translated-v4", + strlen("route-filter-translated-v4")) + == 0) { + *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v4; + *token = community_token_route_filter_translated_v4; + p += strlen("route-filter-translated-v4"); + return p; + } + if (strncmp(p, "route-filter-v4", strlen("route-filter-v4")) + == 0) { + *val = COMMUNITY_ROUTE_FILTER_v4; + *token = community_token_route_filter_v4; + p += strlen("route-filter-v4"); + return p; + } + if (strncmp(p, "route-filter-translated-v6", + strlen("route-filter-translated-v6")) + == 0) { + *val = COMMUNITY_ROUTE_FILTER_TRANSLATED_v6; + *token = community_token_route_filter_translated_v6; + p += strlen("route-filter-translated-v6"); + return p; + } + if (strncmp(p, "route-filter-v6", strlen("route-filter-v6")) + == 0) { + *val = COMMUNITY_ROUTE_FILTER_v6; + *token = community_token_route_filter_v6; + p += strlen("route-filter-v6"); + return p; + } + if (strncmp(p, "llgr-stale", strlen("llgr-stale")) + == 0) { + *val = COMMUNITY_LLGR_STALE; + *token = community_token_llgr_stale; + p += strlen("llgr-stale"); + return p; + } + if (strncmp(p, "no-llgr", strlen("no-llgr")) + == 0) { + *val = COMMUNITY_NO_LLGR; + *token = community_token_no_llgr; + p += strlen("no-llgr"); + return p; + } + if (strncmp(p, "blackhole", strlen("blackhole")) + == 0) { + *val = COMMUNITY_BLACKHOLE; + *token = community_token_blackhole; + p += strlen("blackhole"); + return p; + } + if (strncmp(p, "no-export", strlen("no-export")) == 0) { + *val = COMMUNITY_NO_EXPORT; + *token = community_token_no_export; + p += strlen("no-export"); + return p; + } + if (strncmp(p, "no-advertise", strlen("no-advertise")) == 0) { + *val = COMMUNITY_NO_ADVERTISE; + *token = community_token_no_advertise; + p += strlen("no-advertise"); + return p; + } + if (strncmp(p, "local-AS", strlen("local-AS")) == 0) { + *val = COMMUNITY_LOCAL_AS; + *token = community_token_local_as; + p += strlen("local-AS"); + return p; + } + if (strncmp(p, "no-peer", strlen("no-peer")) == 0) { + *val = COMMUNITY_NO_PEER; + *token = community_token_no_peer; + p += strlen("no-peer"); + return p; + } + + /* Unknown string. */ + *token = community_token_unknown; + return NULL; + } + + /* Community value. */ + if (isdigit((unsigned char)*p)) { + int separator = 0; + int digit = 0; + uint32_t community_low = 0; + uint32_t community_high = 0; + + if (!community_valid(p)) { + *token = community_token_unknown; + return NULL; + } + + while (isdigit((unsigned char)*p) || *p == ':') { + if (*p == ':') { + if (separator) { + *token = community_token_unknown; + return NULL; + } else { + separator = 1; + digit = 0; + + if (community_low > UINT16_MAX) { + *token = + community_token_unknown; + return NULL; + } + + community_high = community_low << 16; + community_low = 0; + } + } else { + digit = 1; + community_low *= 10; + community_low += (*p - '0'); + } + p++; + } + if (!digit) { + *token = community_token_unknown; + return NULL; + } + + *val = community_high + community_low; + *token = community_token_val; + return p; + } + *token = community_token_unknown; + return NULL; +} + +/* convert string to community structure */ +struct community *community_str2com(const char *str) +{ + struct community *com = NULL; + struct community *com_sort = NULL; + uint32_t val = 0; + enum community_token token = community_token_unknown; + + do { + str = community_gettoken(str, &token, &val); + + switch (token) { + case community_token_val: + case community_token_gshut: + case community_token_accept_own: + case community_token_route_filter_translated_v4: + case community_token_route_filter_v4: + case community_token_route_filter_translated_v6: + case community_token_route_filter_v6: + case community_token_llgr_stale: + case community_token_no_llgr: + case community_token_accept_own_nexthop: + case community_token_blackhole: + case community_token_no_export: + case community_token_no_advertise: + case community_token_local_as: + case community_token_no_peer: + if (com == NULL) { + com = community_new(); + com->json = NULL; + } + community_add_val(com, val); + break; + case community_token_unknown: + if (com) + community_free(&com); + return NULL; + } + } while (str); + + com_sort = community_uniq_sort(com); + community_free(&com); + + return com_sort; +} + +/* Return communities hash entry count. */ +unsigned long community_count(void) +{ + return comhash->count; +} + +/* Return communities hash. */ +struct hash *community_hash(void) +{ + return comhash; +} + +/* Initialize comminity related hash. */ +void community_init(void) +{ + comhash = + hash_create((unsigned int (*)(const void *))community_hash_make, + (bool (*)(const void *, const void *))community_cmp, + "BGP Community Hash"); +} + +static void community_hash_free(void *data) +{ + struct community *com = data; + + community_free(&com); +} + +void community_finish(void) +{ + hash_clean_and_free(&comhash, community_hash_free); +} + +static struct community *bgp_aggr_community_lookup( + struct bgp_aggregate *aggregate, + struct community *community) +{ + return hash_lookup(aggregate->community_hash, community); +} + +static void *bgp_aggr_community_hash_alloc(void *p) +{ + struct community *ref = (struct community *)p; + struct community *community = NULL; + + community = community_dup(ref); + return community; +} + +static void bgp_aggr_community_prepare(struct hash_bucket *hb, void *arg) +{ + struct community *hb_community = hb->data; + struct community **aggr_community = arg; + + if (*aggr_community) + *aggr_community = community_merge(*aggr_community, + hb_community); + else + *aggr_community = community_dup(hb_community); +} + +void bgp_aggr_community_remove(void *arg) +{ + struct community *community = arg; + + community_free(&community); +} + +void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate, + struct community *community) +{ + bgp_compute_aggregate_community_hash(aggregate, community); + bgp_compute_aggregate_community_val(aggregate); +} + + +void bgp_compute_aggregate_community_hash(struct bgp_aggregate *aggregate, + struct community *community) +{ + struct community *aggr_community = NULL; + + if ((aggregate == NULL) || (community == NULL)) + return; + + /* Create hash if not already created. + */ + if (aggregate->community_hash == NULL) + aggregate->community_hash = hash_create( + (unsigned int (*)(const void *))community_hash_make, + (bool (*)(const void *, const void *))community_cmp, + "BGP Aggregator community hash"); + + aggr_community = bgp_aggr_community_lookup(aggregate, community); + if (aggr_community == NULL) { + /* Insert community into hash. + */ + aggr_community = hash_get(aggregate->community_hash, community, + bgp_aggr_community_hash_alloc); + } + + /* Increment reference counter. + */ + aggr_community->refcnt++; +} + +void bgp_compute_aggregate_community_val(struct bgp_aggregate *aggregate) +{ + struct community *commerge = NULL; + + if (aggregate == NULL) + return; + + /* Re-compute aggregate's community. + */ + if (aggregate->community) + community_free(&aggregate->community); + if (aggregate->community_hash && + aggregate->community_hash->count) { + hash_iterate(aggregate->community_hash, + bgp_aggr_community_prepare, + &aggregate->community); + commerge = aggregate->community; + aggregate->community = community_uniq_sort(commerge); + if (commerge) + community_free(&commerge); + } +} + + + +void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate, + struct community *community) +{ + struct community *aggr_community = NULL; + struct community *ret_comm = NULL; + + if ((!aggregate) + || (!aggregate->community_hash) + || (!community)) + return; + + /* Look-up the community in the hash. + */ + aggr_community = bgp_aggr_community_lookup(aggregate, community); + if (aggr_community) { + aggr_community->refcnt--; + + if (aggr_community->refcnt == 0) { + ret_comm = hash_release(aggregate->community_hash, + aggr_community); + community_free(&ret_comm); + + bgp_compute_aggregate_community_val(aggregate); + } + } +} + +void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct community *community) +{ + + struct community *aggr_community = NULL; + struct community *ret_comm = NULL; + + if ((!aggregate) + || (!aggregate->community_hash) + || (!community)) + return; + + /* Look-up the community in the hash. + */ + aggr_community = bgp_aggr_community_lookup(aggregate, community); + if (aggr_community) { + aggr_community->refcnt--; + + if (aggr_community->refcnt == 0) { + ret_comm = hash_release(aggregate->community_hash, + aggr_community); + community_free(&ret_comm); + } + } +} diff --git a/bgpd/bgp_community.h b/bgpd/bgp_community.h new file mode 100644 index 0000000..7c7e7af --- /dev/null +++ b/bgpd/bgp_community.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Community attribute related functions. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_COMMUNITY_H +#define _QUAGGA_BGP_COMMUNITY_H + +#include "lib/json.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" + +/* Communities attribute. */ +struct community { + /* Reference count of communities value. */ + unsigned long refcnt; + + /* Communities value size. */ + int size; + + /* Communities value. */ + uint32_t *val; + + /* Communities as a json object */ + json_object *json; + + /* String of community attribute. This sring is used by vty output + and expanded community-list for regular expression match. */ + char *str; +}; + +/* Well-known communities value. */ +#define COMMUNITY_GSHUT 0xFFFF0000 +#define COMMUNITY_ACCEPT_OWN 0xFFFF0001 +#define COMMUNITY_ROUTE_FILTER_TRANSLATED_v4 0xFFFF0002 +#define COMMUNITY_ROUTE_FILTER_v4 0xFFFF0003 +#define COMMUNITY_ROUTE_FILTER_TRANSLATED_v6 0xFFFF0004 +#define COMMUNITY_ROUTE_FILTER_v6 0xFFFF0005 +#define COMMUNITY_LLGR_STALE 0xFFFF0006 +#define COMMUNITY_NO_LLGR 0xFFFF0007 +#define COMMUNITY_ACCEPT_OWN_NEXTHOP 0xFFFF0008 +#define COMMUNITY_BLACKHOLE 0xFFFF029A +#define COMMUNITY_NO_EXPORT 0xFFFFFF01 +#define COMMUNITY_NO_ADVERTISE 0xFFFFFF02 +#define COMMUNITY_NO_EXPORT_SUBCONFED 0xFFFFFF03 +#define COMMUNITY_LOCAL_AS 0xFFFFFF03 +#define COMMUNITY_NO_PEER 0xFFFFFF04 + +#define COMMUNITY_SIZE 4 + +/* Macros of community attribute. */ +#define com_length(X) ((X)->size * COMMUNITY_SIZE) +#define com_lastval(X) ((X)->val + (X)->size - 1) +#define com_nthval(X,n) ((X)->val + (n)) + +/* Prototypes of communities attribute functions. */ +extern void community_init(void); +extern void community_finish(void); +extern void community_free(struct community **comm); +extern struct community *community_uniq_sort(struct community *com); +extern struct community *community_parse(uint32_t *pnt, unsigned short length); +extern struct community *community_intern(struct community *com); +extern void community_unintern(struct community **com); +extern char *community_str(struct community *com, bool make_json, + bool translate_alias); +extern unsigned int community_hash_make(const struct community *com); +extern struct community *community_str2com(const char *str); +extern bool community_match(const struct community *com1, + const struct community *com2); +extern bool community_cmp(const struct community *c1, + const struct community *c2); +extern struct community *community_merge(struct community *com1, + struct community *com2); +extern struct community *community_delete(struct community *com1, + struct community *com2); +extern struct community *community_dup(struct community *com); +extern bool community_include(struct community *com, uint32_t val); +extern void community_add_val(struct community *com, uint32_t val); +extern void community_del_val(struct community *com, uint32_t *val); +extern unsigned long community_count(void); +extern struct hash *community_hash(void); +extern uint32_t community_val_get(struct community *com, int i); +extern void bgp_compute_aggregate_community(struct bgp_aggregate *aggregate, + struct community *community); + +extern void bgp_compute_aggregate_community_val( + struct bgp_aggregate *aggregate); +extern void bgp_compute_aggregate_community_hash( + struct bgp_aggregate *aggregate, + struct community *community); +extern void bgp_remove_community_from_aggregate(struct bgp_aggregate *aggregate, + struct community *community); +extern void bgp_remove_comm_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct community *community); +extern void bgp_aggr_community_remove(void *arg); + +/* This implies that when propagating routes into a VRF, the ACCEPT_OWN + * community SHOULD NOT be propagated. + */ +static inline void community_strip_accept_own(struct attr *attr) +{ + struct community *old_com = bgp_attr_get_community(attr); + struct community *new_com = NULL; + uint32_t val = COMMUNITY_ACCEPT_OWN; + + if (old_com && community_include(old_com, val)) { + new_com = community_dup(old_com); + val = htonl(val); + community_del_val(new_com, &val); + + if (!old_com->refcnt) + community_free(&old_com); + + if (!new_com->size) { + community_free(&new_com); + bgp_attr_set_community(attr, NULL); + } else { + bgp_attr_set_community(attr, new_com); + } + } +} + +#endif /* _QUAGGA_BGP_COMMUNITY_H */ diff --git a/bgpd/bgp_community_alias.c b/bgpd/bgp_community_alias.c new file mode 100644 index 0000000..96dc1ec --- /dev/null +++ b/bgpd/bgp_community_alias.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP community, large-community aliasing. + * + * Copyright (C) 2021 Donatas Abraitis + */ + +#include "zebra.h" + +#include "memory.h" +#include "lib/jhash.h" +#include "frrstr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_community_alias.h" + +static struct hash *bgp_ca_alias_hash; +static struct hash *bgp_ca_community_hash; + +static unsigned int bgp_ca_community_hash_key(const void *p) +{ + const struct community_alias *ca = p; + + return jhash(ca->community, sizeof(ca->community), 0); +} + +static bool bgp_ca_community_hash_cmp(const void *p1, const void *p2) +{ + const struct community_alias *ca1 = p1; + const struct community_alias *ca2 = p2; + + return (strcmp(ca1->community, ca2->community) == 0); +} + +static unsigned int bgp_ca_alias_hash_key(const void *p) +{ + const struct community_alias *ca = p; + + return jhash(ca->alias, sizeof(ca->alias), 0); +} + +static bool bgp_ca_alias_hash_cmp(const void *p1, const void *p2) +{ + const struct community_alias *ca1 = p1; + const struct community_alias *ca2 = p2; + + return (strcmp(ca1->alias, ca2->alias) == 0); +} + +static void *bgp_community_alias_alloc(void *p) +{ + const struct community_alias *ca = p; + struct communtiy_alias *new; + + new = XCALLOC(MTYPE_COMMUNITY_ALIAS, sizeof(struct community_alias)); + memcpy(new, ca, sizeof(struct community_alias)); + + return new; +} + +void bgp_community_alias_init(void) +{ + bgp_ca_community_hash = hash_create(bgp_ca_community_hash_key, + bgp_ca_community_hash_cmp, + "BGP community alias (community)"); + bgp_ca_alias_hash = + hash_create(bgp_ca_alias_hash_key, bgp_ca_alias_hash_cmp, + "BGP community alias (alias)"); +} + +static void bgp_ca_free(void *ca) +{ + XFREE(MTYPE_COMMUNITY_ALIAS, ca); +} + +void bgp_community_alias_finish(void) +{ + hash_clean_and_free(&bgp_ca_community_hash, bgp_ca_free); + hash_clean_and_free(&bgp_ca_alias_hash, bgp_ca_free); +} + +static void bgp_community_alias_show_iterator(struct hash_bucket *hb, + struct vty *vty) +{ + struct community_alias *ca = hb->data; + + vty_out(vty, "bgp community alias %s %s\n", ca->community, ca->alias); +} + +int bgp_community_alias_write(struct vty *vty) +{ + hash_iterate(bgp_ca_community_hash, + (void (*)(struct hash_bucket *, + void *))bgp_community_alias_show_iterator, + vty); + return 1; +} + +void bgp_ca_community_insert(struct community_alias *ca) +{ + (void)hash_get(bgp_ca_community_hash, ca, bgp_community_alias_alloc); +} + +void bgp_ca_alias_insert(struct community_alias *ca) +{ + (void)hash_get(bgp_ca_alias_hash, ca, bgp_community_alias_alloc); +} + +void bgp_ca_community_delete(struct community_alias *ca) +{ + struct community_alias *data = hash_release(bgp_ca_community_hash, ca); + + XFREE(MTYPE_COMMUNITY_ALIAS, data); +} + +void bgp_ca_alias_delete(struct community_alias *ca) +{ + struct community_alias *data = hash_release(bgp_ca_alias_hash, ca); + + XFREE(MTYPE_COMMUNITY_ALIAS, data); +} + +struct community_alias *bgp_ca_community_lookup(struct community_alias *ca) +{ + return hash_lookup(bgp_ca_community_hash, ca); +} + +struct community_alias *bgp_ca_alias_lookup(struct community_alias *ca) +{ + return hash_lookup(bgp_ca_alias_hash, ca); +} + +const char *bgp_community2alias(char *community) +{ + struct community_alias ca; + struct community_alias *find; + + memset(&ca, 0, sizeof(ca)); + strlcpy(ca.community, community, sizeof(ca.community)); + + find = bgp_ca_community_lookup(&ca); + if (find) + return find->alias; + + return community; +} + +const char *bgp_alias2community(char *alias) +{ + struct community_alias ca; + struct community_alias *find; + + memset(&ca, 0, sizeof(ca)); + strlcpy(ca.alias, alias, sizeof(ca.alias)); + + find = bgp_ca_alias_lookup(&ca); + if (find) + return find->community; + + return alias; +} + +/* Communities structs have `->str` which is used + * for vty outputs and extended BGP community lists + * with regexp. + * This is a helper to convert already aliased version + * of communities into numerical-only format. + */ +char *bgp_alias2community_str(const char *str) +{ + char **aliases; + char *comstr; + int num, i; + + frrstr_split(str, " ", &aliases, &num); + const char *communities[num]; + + for (i = 0; i < num; i++) + communities[i] = bgp_alias2community(aliases[i]); + + comstr = frrstr_join(communities, num, " "); + + for (i = 0; i < num; i++) + XFREE(MTYPE_TMP, aliases[i]); + XFREE(MTYPE_TMP, aliases); + + return comstr; +} + +static int bgp_community_alias_vector_walker(struct hash_bucket *bucket, + void *data) +{ + vector *comps = data; + struct community_alias *alias = bucket->data; + + vector_set(*comps, XSTRDUP(MTYPE_COMPLETION, alias->alias)); + + return 1; +} + +static void bgp_community_alias_cmd_completion(vector comps, + struct cmd_token *token) +{ + hash_walk(bgp_ca_alias_hash, bgp_community_alias_vector_walker, &comps); +} + +static const struct cmd_variable_handler community_alias_handlers[] = { + {.varname = "alias_name", + .completions = bgp_community_alias_cmd_completion}, + {.tokenname = "ALIAS_NAME", + .completions = bgp_community_alias_cmd_completion}, + {.completions = NULL}}; + +void bgp_community_alias_command_completion_setup(void) +{ + cmd_variable_handler_register(community_alias_handlers); +} diff --git a/bgpd/bgp_community_alias.h b/bgpd/bgp_community_alias.h new file mode 100644 index 0000000..cb7983e --- /dev/null +++ b/bgpd/bgp_community_alias.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP community, large-community aliasing. + * + * Copyright (C) 2021 Donatas Abraitis + */ + +#include "bgpd/bgp_lcommunity.h" + +#ifndef FRR_BGP_COMMUNITY_ALIAS_H +#define FRR_BGP_COMMUNITY_ALIAS_H + +struct community_alias { + /* Human readable community string */ + char community[LCOMMUNITY_SIZE * 3]; + + /* Human readable community alias */ + char alias[BUFSIZ]; +}; + +extern void bgp_community_alias_init(void); +extern void bgp_community_alias_finish(void); +extern struct community_alias *bgp_ca_alias_lookup(struct community_alias *ca); +extern struct community_alias * +bgp_ca_community_lookup(struct community_alias *ca); +extern void bgp_ca_community_insert(struct community_alias *ca); +extern void bgp_ca_alias_insert(struct community_alias *ca); +extern void bgp_ca_community_delete(struct community_alias *ca); +extern void bgp_ca_alias_delete(struct community_alias *ca); +extern int bgp_community_alias_write(struct vty *vty); +extern const char *bgp_community2alias(char *community); +extern const char *bgp_alias2community(char *alias); +extern char *bgp_alias2community_str(const char *str); +extern void bgp_community_alias_command_completion_setup(void); + +#endif /* FRR_BGP_COMMUNITY_ALIAS_H */ diff --git a/bgpd/bgp_conditional_adv.c b/bgpd/bgp_conditional_adv.c new file mode 100644 index 0000000..2d96b44 --- /dev/null +++ b/bgpd/bgp_conditional_adv.c @@ -0,0 +1,519 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Conditional advertisement + * Copyright (C) 2020 Samsung R&D Institute India - Bangalore. + * Madhurilatha Kuruganti + */ + +#include + +#include "bgpd/bgp_conditional_adv.h" +#include "bgpd/bgp_vty.h" + +static route_map_result_t +bgp_check_rmap_prefixes_in_bgp_table(struct bgp_table *table, + struct route_map *rmap) +{ + struct attr dummy_attr = {0}; + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info path = {0}; + struct bgp_path_info_extra path_extra = {0}; + const struct prefix *dest_p; + route_map_result_t ret = RMAP_DENYMATCH; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + dest_p = bgp_dest_get_prefix(dest); + assert(dest_p); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + dummy_attr = *pi->attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&path, &path_extra, dest, pi, + pi->peer, &dummy_attr); + + RESET_FLAG(dummy_attr.rmap_change_flags); + + ret = route_map_apply(rmap, dest_p, &path); + bgp_attr_flush(&dummy_attr); + + if (ret == RMAP_PERMITMATCH) { + bgp_dest_unlock_node(dest); + bgp_cond_adv_debug( + "%s: Condition map routes present in BGP table", + __func__); + + return ret; + } + } + } + + bgp_cond_adv_debug("%s: Condition map routes not present in BGP table", + __func__); + + return ret; +} + +static void bgp_conditional_adv_routes(struct peer *peer, afi_t afi, + safi_t safi, struct bgp_table *table, + struct route_map *rmap, + enum update_type update_type) +{ + bool addpath_capable; + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info path; + struct peer_af *paf; + const struct prefix *dest_p; + struct update_subgroup *subgrp; + struct attr advmap_attr = {0}, attr = {0}; + struct bgp_path_info_extra path_extra = {0}; + route_map_result_t ret; + + paf = peer_af_find(peer, afi, safi); + if (!paf) + return; + + subgrp = PAF_SUBGRP(paf); + /* Ignore if subgroup doesn't exist (implies AF is not negotiated) */ + if (!subgrp) + return; + + subgrp->pscount = 0; + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING); + + bgp_cond_adv_debug("%s: %s routes to/from %s for %s", __func__, + update_type == UPDATE_TYPE_ADVERTISE ? "Advertise" + : "Withdraw", + peer->host, get_afi_safi_str(afi, safi, false)); + + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + dest_p = bgp_dest_get_prefix(dest); + assert(dest_p); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + advmap_attr = *pi->attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&path, &path_extra, dest, pi, + pi->peer, &advmap_attr); + + RESET_FLAG(advmap_attr.rmap_change_flags); + + ret = route_map_apply(rmap, dest_p, &path); + if (ret != RMAP_PERMITMATCH || + !bgp_check_selected(pi, peer, addpath_capable, afi, + safi)) { + bgp_attr_flush(&advmap_attr); + continue; + } + + /* Skip route-map checks in + * subgroup_announce_check while executing from + * the conditional advertise scanner process. + * otherwise when route-map is also configured + * on same peer, routes in advertise-map may not + * be advertised as expected. + */ + if (update_type == UPDATE_TYPE_ADVERTISE && + subgroup_announce_check(dest, pi, subgrp, dest_p, + &attr, &advmap_attr)) { + if (!bgp_adj_out_set_subgroup(dest, subgrp, + &attr, pi)) + bgp_attr_flush(&attr); + } else { + /* If default originate is enabled for + * the peer, do not send explicit + * withdraw. This will prevent deletion + * of default route advertised through + * default originate. + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE) && + is_default_prefix(dest_p)) + break; + + bgp_adj_out_unset_subgroup( + dest, subgrp, 1, + bgp_addpath_id_for_peer( + peer, afi, safi, + &pi->tx_addpath)); + + bgp_attr_flush(&advmap_attr); + } + } + } + UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING); +} + +/* Handler of conditional advertisement timer event. + * Each route in the condition-map is evaluated. + */ +static void bgp_conditional_adv_timer(struct event *t) +{ + afi_t afi; + safi_t safi; + int pfx_rcd_safi; + struct bgp *bgp = NULL; + struct peer *peer = NULL; + struct peer_af *paf = NULL; + struct bgp_table *table = NULL; + struct bgp_filter *filter = NULL; + struct listnode *node, *nnode = NULL; + struct update_subgroup *subgrp = NULL; + route_map_result_t ret; + bool advmap_table_changed = false; + + bgp = EVENT_ARG(t); + assert(bgp); + + event_add_timer(bm->master, bgp_conditional_adv_timer, bgp, + bgp->condition_check_period, &bgp->t_condition_check); + + /* loop through each peer and check if we have peers with + * advmap_table_change attribute set, to make sure we send + * conditional advertisements properly below. + * peer->advmap_table_change is added on incoming BGP UPDATES, + * but here it's used for outgoing UPDATES, hence we need to + * check if at least one peer got advmap_table_change. + */ + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (peer->advmap_table_change) { + advmap_table_changed = true; + break; + } + } + + /* loop through each peer and advertise or withdraw routes if + * advertise-map is configured and prefix(es) in condition-map + * does exist(exist-map)/not exist(non-exist-map) in BGP table + * based on condition(exist-map or non-exist map) + */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if (!peer_established(peer->connection)) + continue; + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc_nego[afi][safi]) + continue; + + /* labeled-unicast routes are installed in the unicast + * table so in order to display the correct PfxRcd value + * we must look at SAFI_UNICAST + */ + pfx_rcd_safi = (safi == SAFI_LABELED_UNICAST) + ? SAFI_UNICAST + : safi; + + table = bgp->rib[afi][pfx_rcd_safi]; + if (!table) + continue; + + filter = &peer->filter[afi][safi]; + + if (!filter->advmap.aname || !filter->advmap.cname + || !filter->advmap.amap || !filter->advmap.cmap) + continue; + + if (!peer->advmap_config_change[afi][safi] && + !advmap_table_changed) + continue; + + if (BGP_DEBUG(cond_adv, COND_ADV)) { + if (peer->advmap_table_change) + zlog_debug( + "%s: %s - routes changed in BGP table.", + __func__, peer->host); + if (peer->advmap_config_change[afi][safi]) + zlog_debug( + "%s: %s for %s - advertise/condition map configuration is changed.", + __func__, peer->host, + get_afi_safi_str(afi, safi, + false)); + } + + /* cmap (route-map attached to exist-map or + * non-exist-map) map validation + */ + ret = bgp_check_rmap_prefixes_in_bgp_table( + table, filter->advmap.cmap); + + /* Derive conditional advertisement status from + * condition and return value of condition-map + * validation. + */ + if (filter->advmap.condition == CONDITION_EXIST) + filter->advmap.update_type = + (ret == RMAP_PERMITMATCH) + ? UPDATE_TYPE_ADVERTISE + : UPDATE_TYPE_WITHDRAW; + else + filter->advmap.update_type = + (ret == RMAP_PERMITMATCH) + ? UPDATE_TYPE_WITHDRAW + : UPDATE_TYPE_ADVERTISE; + + /* + * Update condadv update type so + * subgroup_announce_check() can properly apply + * outbound policy according to advertisement state + */ + paf = peer_af_find(peer, afi, safi); + if (paf && (SUBGRP_PEER(PAF_SUBGRP(paf)) + ->filter[afi][safi] + .advmap.update_type != + filter->advmap.update_type)) { + /* Handle change to peer advmap */ + bgp_cond_adv_debug( + "%s: advmap.update_type changed for peer %s, adjusting update_group.", + __func__, peer->host); + + update_group_adjust_peer(paf); + } + + /* Send regular update as per the existing policy. + * There is a change in route-map, match-rule, ACLs, + * or route-map filter configuration on the same peer. + */ + if (peer->advmap_config_change[afi][safi]) { + + bgp_cond_adv_debug( + "%s: Configuration is changed on peer %s for %s, send the normal update first.", + __func__, peer->host, + get_afi_safi_str(afi, safi, false)); + if (paf) { + update_subgroup_split_peer(paf, NULL); + subgrp = paf->subgroup; + + if (subgrp && subgrp->update_group) + subgroup_announce_table( + paf->subgroup, NULL); + } + peer->advmap_config_change[afi][safi] = false; + } + + /* Send update as per the conditional advertisement */ + bgp_conditional_adv_routes(peer, afi, safi, table, + filter->advmap.amap, + filter->advmap.update_type); + } + peer->advmap_table_change = false; + } +} + +void bgp_conditional_adv_enable(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp *bgp = peer->bgp; + + assert(bgp); + + /* This flag is used to monitor conditional routes status in BGP table, + * and advertise/withdraw routes only when there is a change in BGP + * table w.r.t conditional routes + */ + peer->advmap_config_change[afi][safi] = true; + + /* advertise-map is already configured on at least one of its + * neighbors (AFI/SAFI). So just increment the counter. + */ + if (++bgp->condition_filter_count > 1) { + bgp_cond_adv_debug("%s: condition_filter_count %d", __func__, + bgp->condition_filter_count); + + return; + } + + /* Register for conditional routes polling timer */ + if (!event_is_scheduled(bgp->t_condition_check)) + event_add_timer(bm->master, bgp_conditional_adv_timer, bgp, 0, + &bgp->t_condition_check); +} + +void bgp_conditional_adv_disable(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp *bgp = peer->bgp; + + assert(bgp); + + /* advertise-map is not configured on any of its neighbors or + * it is configured on more than one neighbor(AFI/SAFI). + * So there's nothing to do except decrementing the counter. + */ + if (--bgp->condition_filter_count != 0) { + bgp_cond_adv_debug("%s: condition_filter_count %d", __func__, + bgp->condition_filter_count); + + return; + } + + /* Last filter removed. So cancel conditional routes polling thread. */ + EVENT_OFF(bgp->t_condition_check); +} + +static void peer_advertise_map_filter_update(struct peer *peer, afi_t afi, + safi_t safi, const char *amap_name, + struct route_map *amap, + const char *cmap_name, + struct route_map *cmap, + bool condition, bool set) +{ + struct bgp_filter *filter; + bool filter_exists = false; + + filter = &peer->filter[afi][safi]; + + /* advertise-map is already configured. */ + if (filter->advmap.aname) { + filter_exists = true; + XFREE(MTYPE_BGP_FILTER_NAME, filter->advmap.aname); + XFREE(MTYPE_BGP_FILTER_NAME, filter->advmap.cname); + } + + route_map_counter_decrement(filter->advmap.amap); + + /* Removed advertise-map configuration */ + if (!set) { + memset(&filter->advmap, 0, sizeof(filter->advmap)); + + /* decrement condition_filter_count delete timer if + * this is the last advertise-map to be removed. + */ + if (filter_exists) + bgp_conditional_adv_disable(peer, afi, safi); + + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, 1); + + return; + } + + /* Update filter data with newly configured values. */ + filter->advmap.aname = XSTRDUP(MTYPE_BGP_FILTER_NAME, amap_name); + filter->advmap.cname = XSTRDUP(MTYPE_BGP_FILTER_NAME, cmap_name); + filter->advmap.amap = amap; + filter->advmap.cmap = cmap; + filter->advmap.condition = condition; + route_map_counter_increment(filter->advmap.amap); + peer->advmap_config_change[afi][safi] = true; + + /* Increment condition_filter_count and/or create timer. */ + if (!filter_exists) { + filter->advmap.update_type = UPDATE_TYPE_ADVERTISE; + bgp_conditional_adv_enable(peer, afi, safi); + } + + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, 1); +} + +/* Set advertise-map to the peer. */ +int peer_advertise_map_set(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, bool condition) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set configuration on peer. */ + peer_advertise_map_filter_update(peer, afi, safi, advertise_name, + advertise_map, condition_name, + condition_map, condition, true); + + /* Check if handling a regular peer & Skip peer-group mechanics. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][RMAP_OUT], + PEER_FT_ADVERTISE_MAP); + return 0; + } + + /* + * Set configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][RMAP_OUT], + PEER_FT_ADVERTISE_MAP)) + continue; + + /* Set configuration on peer-group member. */ + peer_advertise_map_filter_update( + member, afi, safi, advertise_name, advertise_map, + condition_name, condition_map, condition, true); + } + + return 0; +} + +/* Unset advertise-map from the peer. */ +int peer_advertise_map_unset(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, bool condition) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* advertise-map is not configured */ + if (!peer->filter[afi][safi].advmap.aname) + return 0; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][RMAP_OUT], + PEER_FT_ADVERTISE_MAP); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].advmap.aname, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].advmap.amap); + } else + peer_advertise_map_filter_update( + peer, afi, safi, advertise_name, advertise_map, + condition_name, condition_map, condition, false); + + /* Check if handling a regular peer and skip peer-group mechanics. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + bgp_cond_adv_debug("%s: Send normal update to %s for %s", + __func__, peer->host, + get_afi_safi_str(afi, safi, false)); + + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][RMAP_OUT], + PEER_FT_ADVERTISE_MAP)) + continue; + /* Remove configuration on peer-group member. */ + peer_advertise_map_filter_update( + member, afi, safi, advertise_name, advertise_map, + condition_name, condition_map, condition, false); + + /* Process peer route updates. */ + bgp_cond_adv_debug("%s: Send normal update to %s for %s ", + __func__, member->host, + get_afi_safi_str(afi, safi, false)); + } + + return 0; +} diff --git a/bgpd/bgp_conditional_adv.h b/bgpd/bgp_conditional_adv.h new file mode 100644 index 0000000..b1be593 --- /dev/null +++ b/bgpd/bgp_conditional_adv.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Conditional advertisement + * Copyright (C) 2020 Samsung R&D Institute India - Bangalore. + * Madhurilatha Kuruganti + */ + +#ifndef _FRR_BGP_CONDITION_ADV_H +#define _FRR_BGP_CONDITION_ADV_H +#include +#include "prefix.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_updgrp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Macro to log debug message */ +#define bgp_cond_adv_debug(...) \ + do { \ + if (BGP_DEBUG(cond_adv, COND_ADV)) \ + zlog_debug("" __VA_ARGS__); \ + } while (0) + +/* Polling time for monitoring condition-map routes in route table */ +#define DEFAULT_CONDITIONAL_ROUTES_POLL_TIME 60 + +extern void bgp_conditional_adv_enable(struct peer *peer, afi_t afi, + safi_t safi); +extern void bgp_conditional_adv_disable(struct peer *peer, afi_t afi, + safi_t safi); +extern int peer_advertise_map_set(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, + bool condition); +extern int peer_advertise_map_unset(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, + bool condition); +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_BGP_CONDITION_ADV_H */ diff --git a/bgpd/bgp_damp.c b/bgpd/bgp_damp.c new file mode 100644 index 0000000..339bfae --- /dev/null +++ b/bgpd/bgp_damp.c @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP flap dampening + * Copyright (C) 2001 IP Infusion Inc. + */ + +#include +#include + +#include "prefix.h" +#include "memory.h" +#include "command.h" +#include "log.h" +#include "frrevent.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_vty.h" + +static void bgp_reuselist_add(struct reuselist *list, struct bgp_damp_info *info) +{ + assert(info); + SLIST_INSERT_HEAD(list, info, entry); +} + +static void bgp_reuselist_del(struct reuselist *list, struct bgp_damp_info *info) +{ + assert(info); + SLIST_REMOVE(list, info, bgp_damp_info, entry); +} + +static void bgp_reuselist_switch(struct reuselist *source, + struct bgp_damp_info *info, + struct reuselist *target) +{ + assert(source && target && info); + SLIST_REMOVE(source, info, bgp_damp_info, entry); + SLIST_INSERT_HEAD(target, info, entry); +} + +static void bgp_damp_info_unclaim(struct bgp_damp_info *bdi, + struct reuselist *list) +{ + assert(bdi && bdi->config); + if (bdi->index == BGP_DAMP_NO_REUSE_LIST_INDEX) + bgp_reuselist_del(&bdi->config->no_reuse_list, bdi); + else + bgp_reuselist_del(list ? list + : &bdi->config->reuse_list[bdi->index], + bdi); + bdi->config = NULL; +} + +static void bgp_damp_info_claim(struct bgp_damp_info *bdi, + struct bgp_damp_config *bdc) +{ + assert(bdc && bdi); + if (bdi->config == NULL) { + bdi->config = bdc; + return; + } + bgp_damp_info_unclaim(bdi, NULL); + bdi->config = bdc; + bdi->afi = bdc->afi; + bdi->safi = bdc->safi; +} + +struct bgp_damp_config *get_active_bdc_from_pi(struct bgp_path_info *pi, + afi_t afi, safi_t safi) +{ + if (!pi) + return NULL; + if (CHECK_FLAG(pi->peer->af_flags[afi][safi], + PEER_FLAG_CONFIG_DAMPENING)) + return &pi->peer->damp[afi][safi]; + if (peer_group_active(pi->peer)) + if (CHECK_FLAG(pi->peer->group->conf->af_flags[afi][safi], + PEER_FLAG_CONFIG_DAMPENING)) + return &pi->peer->group->conf->damp[afi][safi]; + if (CHECK_FLAG(pi->peer->bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) + return &pi->peer->bgp->damp[afi][safi]; + return NULL; +} + +/* Calculate reuse list index by penalty value. */ +static int bgp_reuse_index(int penalty, struct bgp_damp_config *bdc) +{ + unsigned int i; + unsigned int index; + + /* + * reuse_limit can't be zero, this is for Coverity + * to bypass division by zero test. + */ + assert(bdc->reuse_limit); + + i = (int)(((double)penalty / bdc->reuse_limit - 1.0) + * bdc->scale_factor); + + if (i >= bdc->reuse_index_size) + i = bdc->reuse_index_size - 1; + + index = bdc->reuse_index[i] - bdc->reuse_index[0]; + + return (bdc->reuse_offset + index) % bdc->reuse_list_size; +} + +/* Add BGP dampening information to reuse list. */ +static void bgp_reuse_list_add(struct bgp_damp_info *bdi, + struct bgp_damp_config *bdc) +{ + bgp_damp_info_claim(bdi, bdc); + bdi->index = bgp_reuse_index(bdi->penalty, bdc); + bgp_reuselist_add(&bdc->reuse_list[bdi->index], bdi); +} + +/* Delete BGP dampening information from reuse list. */ +static void bgp_reuse_list_delete(struct bgp_damp_info *bdi) +{ + bgp_damp_info_unclaim(bdi, NULL); +} + +static void bgp_no_reuse_list_add(struct bgp_damp_info *bdi, + struct bgp_damp_config *bdc) +{ + bgp_damp_info_claim(bdi, bdc); + bdi->index = BGP_DAMP_NO_REUSE_LIST_INDEX; + bgp_reuselist_add(&bdc->no_reuse_list, bdi); +} + +static void bgp_no_reuse_list_delete(struct bgp_damp_info *bdi) +{ + bgp_damp_info_unclaim(bdi, NULL); +} + +/* Return decayed penalty value. */ +int bgp_damp_decay(time_t tdiff, int penalty, struct bgp_damp_config *bdc) +{ + unsigned int i; + + i = (int)((double)tdiff / DELTA_T); + + if (i == 0) + return penalty; + + if (i >= bdc->decay_array_size) + return 0; + + return (int)(penalty * bdc->decay_array[i]); +} + +/* Handler of reuse timer event. Each route in the current reuse-list + is evaluated. RFC2439 Section 4.8.7. */ +static void bgp_reuse_timer(struct event *t) +{ + struct bgp_damp_info *bdi, *bdi_next; + struct reuselist plist; + struct bgp *bgp; + time_t t_now, t_diff; + struct bgp_damp_config *bdc = EVENT_ARG(t); + + bdc->t_reuse = NULL; + event_add_timer(bm->master, bgp_reuse_timer, bdc, DELTA_REUSE, + &bdc->t_reuse); + + t_now = monotime(NULL); + + /* 1. save a pointer to the current queue head and zero the list head + * list head entry. */ + assert(bdc->reuse_offset < bdc->reuse_list_size); + plist = bdc->reuse_list[bdc->reuse_offset]; + SLIST_INIT(&bdc->reuse_list[bdc->reuse_offset]); + + /* 2. set offset = modulo reuse-list-size ( offset + 1 ), thereby + rotating the circular queue of list-heads. */ + bdc->reuse_offset = (bdc->reuse_offset + 1) % bdc->reuse_list_size; + assert(bdc->reuse_offset < bdc->reuse_list_size); + + /* 3. if ( the saved list head pointer is non-empty ) */ + SLIST_FOREACH_SAFE (bdi, &plist, entry, bdi_next) { + bgp = bdi->path->peer->bgp; + + /* Set t-diff = t-now - t-updated. */ + t_diff = t_now - bdi->t_updated; + + /* Set figure-of-merit = figure-of-merit * decay-array-ok + * [t-diff] */ + bdi->penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); + + /* Set t-updated = t-now. */ + bdi->t_updated = t_now; + + /* if (figure-of-merit < reuse). */ + if (bdi->penalty < bdc->reuse_limit) { + /* Reuse the route. */ + bgp_path_info_unset_flag(bdi->dest, bdi->path, + BGP_PATH_DAMPED); + bdi->suppress_time = 0; + + if (bdi->lastrecord == BGP_RECORD_UPDATE) { + bgp_path_info_unset_flag(bdi->dest, bdi->path, + BGP_PATH_HISTORY); + bgp_aggregate_increment( + bgp, bgp_dest_get_prefix(bdi->dest), + bdi->path, bdi->afi, bdi->safi); + bgp_process(bgp, bdi->dest, bdi->path, bdi->afi, + bdi->safi); + } + + if (bdi->penalty <= bdc->reuse_limit / 2.0) { + bgp_damp_info_free(bdi, &plist, 1); + } else { + bdi->index = BGP_DAMP_NO_REUSE_LIST_INDEX; + bgp_reuselist_switch(&plist, bdi, + &bdc->no_reuse_list); + } + } else { + /* Re-insert into another list (See RFC2439 Section + * 4.8.6). */ + bdi->index = bgp_reuse_index(bdi->penalty, bdc); + bgp_reuselist_switch(&plist, bdi, + &bdc->reuse_list[bdi->index]); + } + } + + assert(SLIST_EMPTY(&plist)); +} + +/* A route becomes unreachable (RFC2439 Section 4.8.2). */ +int bgp_damp_withdraw(struct bgp_path_info *path, struct bgp_dest *dest, + afi_t afi, safi_t safi, int attr_change) +{ + time_t t_now; + struct bgp_damp_info *bdi = NULL; + unsigned int last_penalty = 0; + struct bgp_damp_config *bdc; + + bdc = get_active_bdc_from_pi(path, afi, safi); + if (!bdc) + return BGP_DAMP_USED; + + t_now = monotime(NULL); + /* Processing Unreachable Messages. */ + if (path->extra) + bdi = path->extra->damp_info; + + if (bdi == NULL) { + /* If there is no previous stability history. */ + + /* RFC2439 said: + 1. allocate a damping structure. + 2. set figure-of-merit = 1. + 3. withdraw the route. */ + + bdi = XCALLOC(MTYPE_BGP_DAMP_INFO, + sizeof(struct bgp_damp_info)); + bdi->path = path; + bdi->dest = dest; + bdi->penalty = + (attr_change ? DEFAULT_PENALTY / 2 : DEFAULT_PENALTY); + bdi->flap = 1; + bdi->start_time = t_now; + bdi->suppress_time = 0; + bdi->index = BGP_DAMP_NO_REUSE_LIST_INDEX; + bdi->afi = afi; + bdi->safi = safi; + (bgp_path_info_extra_get(path))->damp_info = bdi; + bgp_no_reuse_list_add(bdi, bdc); + } else { + if (bdi->config != bdc) { + bgp_damp_info_claim(bdi, bdc); + if (bdi->index == BGP_DAMP_NO_REUSE_LIST_INDEX) + bgp_reuselist_add(&bdc->no_reuse_list, bdi); + else + bgp_reuselist_add(&bdc->reuse_list[bdi->index], + bdi); + } + last_penalty = bdi->penalty; + + /* 1. Set t-diff = t-now - t-updated. */ + bdi->penalty = (bgp_damp_decay(t_now - bdi->t_updated, + bdi->penalty, bdc) + + (attr_change ? DEFAULT_PENALTY / 2 + : DEFAULT_PENALTY)); + + if (bdi->penalty > bdc->ceiling) + bdi->penalty = bdc->ceiling; + + bdi->flap++; + } + + assert((dest == bdi->dest) && (path == bdi->path)); + + bdi->lastrecord = BGP_RECORD_WITHDRAW; + bdi->t_updated = t_now; + + /* Make this route as historical status. */ + bgp_path_info_set_flag(dest, path, BGP_PATH_HISTORY); + + /* Remove the route from a reuse list if it is on one. */ + if (CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED)) { + /* If decay rate isn't equal to 0, reinsert brn. */ + if (bdi->penalty != last_penalty) { + bgp_reuse_list_delete(bdi); + bgp_reuse_list_add(bdi, bdc); + } + return BGP_DAMP_SUPPRESSED; + } + + /* If not suppressed before, do annonunce this withdraw and + insert into reuse_list. */ + if (bdi->penalty >= bdc->suppress_value) { + bgp_path_info_set_flag(dest, path, BGP_PATH_DAMPED); + bdi->suppress_time = t_now; + bgp_no_reuse_list_delete(bdi); + bgp_reuse_list_add(bdi, bdc); + } + return BGP_DAMP_USED; +} + +int bgp_damp_update(struct bgp_path_info *path, struct bgp_dest *dest, + afi_t afi, safi_t safi) +{ + time_t t_now; + struct bgp_damp_info *bdi; + int status; + struct bgp_damp_config *bdc; + + bdc = get_active_bdc_from_pi(path, afi, safi); + assert(bdc); + + if (!path->extra || !((bdi = path->extra->damp_info))) + return BGP_DAMP_USED; + + t_now = monotime(NULL); + bgp_path_info_unset_flag(dest, path, BGP_PATH_HISTORY); + + bdi->lastrecord = BGP_RECORD_UPDATE; + bdi->penalty = + bgp_damp_decay(t_now - bdi->t_updated, bdi->penalty, bdc); + + if (!CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED) + && (bdi->penalty < bdc->suppress_value)) + status = BGP_DAMP_USED; + else if (CHECK_FLAG(bdi->path->flags, BGP_PATH_DAMPED) + && (bdi->penalty < bdc->reuse_limit)) { + bgp_path_info_unset_flag(dest, path, BGP_PATH_DAMPED); + bgp_reuse_list_delete(bdi); + bgp_no_reuse_list_add(bdi, bdc); + bdi->suppress_time = 0; + status = BGP_DAMP_USED; + } else + status = BGP_DAMP_SUPPRESSED; + + if (bdi->penalty > bdc->reuse_limit / 2.0) + bdi->t_updated = t_now; + else + bgp_damp_info_free(bdi, NULL, 0); + + return status; +} + +void bgp_damp_info_free(struct bgp_damp_info *bdi, struct reuselist *list, + int withdraw) +{ + assert(bdi); + + afi_t afi = bdi->afi; + safi_t safi = bdi->safi; + struct bgp_path_info *bpi = bdi->path; + struct bgp_dest *dest = bdi->dest; + struct bgp *bgp = bpi->peer->bgp; + const struct prefix *p = bgp_dest_get_prefix(bdi->dest); + + bgp_damp_info_unclaim(bdi, list); + + bpi->extra->damp_info = NULL; + bgp_path_info_unset_flag(dest, bpi, BGP_PATH_HISTORY | BGP_PATH_DAMPED); + if (bdi->lastrecord == BGP_RECORD_WITHDRAW && withdraw) { + bgp_aggregate_decrement(bgp, p, bpi, afi, SAFI_UNICAST); + bgp_path_info_delete(dest, bpi); + bgp_process(bgp, dest, bpi, afi, safi); + } + + XFREE(MTYPE_BGP_DAMP_INFO, bdi); +} + +static void bgp_damp_parameter_set(time_t hlife, unsigned int reuse, + unsigned int sup, time_t maxsup, + struct bgp_damp_config *bdc) +{ + double reuse_max_ratio; + unsigned int i; + double j; + + bdc->suppress_value = sup; + bdc->half_life = hlife; + bdc->reuse_limit = reuse; + bdc->max_suppress_time = maxsup; + + /* Initialize params per bgp_damp_config. */ + bdc->reuse_index_size = REUSE_ARRAY_SIZE; + + bdc->ceiling = (int)(bdc->reuse_limit + * (pow(2, (double)bdc->max_suppress_time + / bdc->half_life))); + + /* Decay-array computations */ + bdc->decay_array_size = ceil((double)bdc->max_suppress_time / DELTA_T); + bdc->decay_array = XMALLOC(MTYPE_BGP_DAMP_ARRAY, + sizeof(double) * (bdc->decay_array_size)); + bdc->decay_array[0] = 1.0; + bdc->decay_array[1] = + exp((1.0 / ((double)bdc->half_life / DELTA_T)) * log(0.5)); + + /* Calculate decay values for all possible times */ + for (i = 2; i < bdc->decay_array_size; i++) + bdc->decay_array[i] = + bdc->decay_array[i - 1] * bdc->decay_array[1]; + + /* Reuse-list computations */ + i = ceil((double)bdc->max_suppress_time / DELTA_REUSE) + 1; + if (i > REUSE_LIST_SIZE || i == 0) + i = REUSE_LIST_SIZE; + bdc->reuse_list_size = i; + + bdc->reuse_list = + XCALLOC(MTYPE_BGP_DAMP_ARRAY, + bdc->reuse_list_size * sizeof(struct reuselist)); + /* Reuse-array computations */ + bdc->reuse_index = XCALLOC(MTYPE_BGP_DAMP_ARRAY, + sizeof(int) * bdc->reuse_index_size); + + reuse_max_ratio = (double)bdc->ceiling / bdc->reuse_limit; + j = (exp((double)bdc->max_suppress_time / bdc->half_life) * log10(2.0)); + if (reuse_max_ratio > j && j != 0) + reuse_max_ratio = j; + + bdc->scale_factor = + (double)bdc->reuse_index_size / (reuse_max_ratio - 1); + + for (i = 0; i < bdc->reuse_index_size; i++) { + bdc->reuse_index[i] = + (int)(((double)bdc->half_life / DELTA_REUSE) + * log10(1.0 + / (bdc->reuse_limit + * (1.0 + + ((double)i / bdc->scale_factor)))) + / log10(0.5)); + } +} + +int bgp_damp_enable(struct bgp *bgp, afi_t afi, safi_t safi, time_t half, + unsigned int reuse, unsigned int suppress, time_t max) +{ + struct bgp_damp_config *bdc = &bgp->damp[afi][safi]; + + if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) { + if (bdc->half_life == half && bdc->reuse_limit == reuse + && bdc->suppress_value == suppress + && bdc->max_suppress_time == max) + return 0; + bgp_damp_disable(bgp, afi, safi); + } + + SET_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING); + bgp_damp_parameter_set(half, reuse, suppress, max, bdc); + bdc->afi = afi; + bdc->safi = safi; + + /* Register reuse timer. */ + event_add_timer(bm->master, bgp_reuse_timer, bdc, DELTA_REUSE, + &bdc->t_reuse); + + return 0; +} + +/* Clean all the bgp_damp_info stored in reuse_list and no_reuse_list. */ +void bgp_damp_info_clean(struct bgp *bgp, struct bgp_damp_config *bdc, + afi_t afi, safi_t safi) +{ + struct bgp_damp_info *bdi; + struct reuselist *list; + unsigned int i; + + bdc->reuse_offset = 0; + for (i = 0; i < bdc->reuse_list_size; ++i) { + list = &bdc->reuse_list[i]; + while ((bdi = SLIST_FIRST(list)) != NULL) { + if (bdi->lastrecord == BGP_RECORD_UPDATE) { + bgp_aggregate_increment(bgp, + bgp_dest_get_prefix( + bdi->dest), + bdi->path, bdi->afi, + bdi->safi); + bgp_process(bgp, bdi->dest, bdi->path, bdi->afi, + bdi->safi); + } + bgp_damp_info_free(bdi, list, 1); + } + } + + while ((bdi = SLIST_FIRST(&bdc->no_reuse_list)) != NULL) + bgp_damp_info_free(bdi, &bdc->no_reuse_list, 1); + + /* Free decay array */ + XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->decay_array); + bdc->decay_array_size = 0; + + /* Free reuse index array */ + XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->reuse_index); + bdc->reuse_index_size = 0; + + XFREE(MTYPE_BGP_DAMP_ARRAY, bdc->reuse_list); + bdc->reuse_list_size = 0; + + EVENT_OFF(bdc->t_reuse); +} + +/* Disable route flap dampening for a bgp instance. + * + * Please note that this function also gets used to free memory when deleting a + * bgp instance. + */ +int bgp_damp_disable(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_damp_config *bdc; + + bdc = &bgp->damp[afi][safi]; + if (!bdc) + return 0; + + /* If it wasn't enabled, there's nothing to do. */ + if (!CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) + return 0; + + /* Cancel reuse event. */ + EVENT_OFF(bdc->t_reuse); + + /* Clean BGP dampening information. */ + bgp_damp_info_clean(bgp, bdc, afi, safi); + + UNSET_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING); + + return 0; +} + +void bgp_config_write_damp(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_damp_config *bdc; + + bdc = &bgp->damp[afi][safi]; + if (bdc->half_life == DEFAULT_HALF_LIFE * 60 && + bdc->reuse_limit == DEFAULT_REUSE && + bdc->suppress_value == DEFAULT_SUPPRESS && + bdc->max_suppress_time == bdc->half_life * 4) + vty_out(vty, " bgp dampening\n"); + else if (bdc->half_life != DEFAULT_HALF_LIFE * 60 && + bdc->reuse_limit == DEFAULT_REUSE && + bdc->suppress_value == DEFAULT_SUPPRESS && + bdc->max_suppress_time == bdc->half_life * 4) + vty_out(vty, " bgp dampening %lld\n", bdc->half_life / 60LL); + else + vty_out(vty, " bgp dampening %lld %d %d %lld\n", + bdc->half_life / 60LL, bdc->reuse_limit, + bdc->suppress_value, bdc->max_suppress_time / 60LL); +} + +static const char *bgp_get_reuse_time(struct bgp_damp_config *bdc, + unsigned int penalty, char *buf, + size_t len, bool use_json, + json_object *json) +{ + time_t reuse_time = 0; + struct tm tm; + int time_store = 0; + + if (penalty > bdc->reuse_limit) { + reuse_time = (int)(DELTA_T * + ((log((double)bdc->reuse_limit / penalty)) / + (log(bdc->decay_array[1])))); + + if (reuse_time > bdc->max_suppress_time) + reuse_time = bdc->max_suppress_time; + + gmtime_r(&reuse_time, &tm); + } else + reuse_time = 0; + + /* Making formatted timer strings. */ + if (reuse_time == 0) { + if (use_json) + json_object_int_add(json, "reuseTimerMsecs", 0); + else + snprintf(buf, len, "00:00:00"); + } else if (reuse_time < ONE_DAY_SECOND) { + if (use_json) { + time_store = (3600000 * tm.tm_hour) + + (60000 * tm.tm_min) + + (1000 * tm.tm_sec); + json_object_int_add(json, "reuseTimerMsecs", + time_store); + } else + snprintf(buf, len, "%02d:%02d:%02d", tm.tm_hour, + tm.tm_min, tm.tm_sec); + } else if (reuse_time < ONE_WEEK_SECOND) { + if (use_json) { + time_store = (86400000 * tm.tm_yday) + + (3600000 * tm.tm_hour) + + (60000 * tm.tm_min) + + (1000 * tm.tm_sec); + json_object_int_add(json, "reuseTimerMsecs", + time_store); + } else + snprintf(buf, len, "%dd%02dh%02dm", tm.tm_yday, + tm.tm_hour, tm.tm_min); + } else { + if (use_json) { + time_store = + (604800000 * tm.tm_yday / 7) + + (86400000 + * (tm.tm_yday - ((tm.tm_yday / 7) * 7))) + + (3600000 * tm.tm_hour) + (60000 * tm.tm_min) + + (1000 * tm.tm_sec); + json_object_int_add(json, "reuseTimerMsecs", + time_store); + } else + snprintf(buf, len, "%02dw%dd%02dh", tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7), + tm.tm_hour); + } + + return buf; +} + +void bgp_damp_info_vty(struct vty *vty, struct bgp *bgp, + struct bgp_path_info *path, afi_t afi, safi_t safi, + json_object *json_path) +{ + struct bgp_damp_info *bdi; + time_t t_now, t_diff; + char timebuf[BGP_UPTIME_LEN] = {}; + int penalty; + struct bgp_damp_config *bdc = &bgp->damp[afi][safi]; + + if (!path->extra) + return; + + /* BGP dampening information. */ + bdi = path->extra->damp_info; + + /* If dampening is not enabled or there is no dampening information, + return immediately. */ + if (!CHECK_FLAG(path->peer->bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING) || + !bdi) + return; + + /* Calculate new penalty. */ + t_now = monotime(NULL); + t_diff = t_now - bdi->t_updated; + penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); + + if (json_path) { + json_object_int_add(json_path, "dampeningPenalty", penalty); + json_object_int_add(json_path, "dampeningFlapCount", bdi->flap); + peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, 1, + json_path); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + bgp_get_reuse_time(bdc, penalty, timebuf, + BGP_UPTIME_LEN, 1, json_path); + } else { + vty_out(vty, + " Dampinfo: penalty %d, flapped %d times in %s", + penalty, bdi->flap, + peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, 0, + json_path)); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, ", reuse in %s", + bgp_get_reuse_time(bdc, penalty, timebuf, + BGP_UPTIME_LEN, 0, + json_path)); + + vty_out(vty, "\n"); + } +} + + +const char *bgp_damp_reuse_time_vty(struct vty *vty, struct bgp_path_info *path, + char *timebuf, size_t len, afi_t afi, + safi_t safi, bool use_json, + json_object *json) +{ + struct bgp_damp_info *bdi; + time_t t_now, t_diff; + int penalty; + struct bgp_damp_config *bdc; + + bdc = get_active_bdc_from_pi(path, afi, safi); + if (!bdc) + return NULL; + + if (!path->extra) + return NULL; + + /* BGP dampening information. */ + bdi = path->extra->damp_info; + + /* If dampening is not enabled or there is no dampening information, + return immediately. */ + if (!CHECK_FLAG(path->peer->bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING) || + !bdi) + return NULL; + + /* Calculate new penalty. */ + t_now = monotime(NULL); + t_diff = t_now - bdi->t_updated; + penalty = bgp_damp_decay(t_diff, bdi->penalty, bdc); + + return bgp_get_reuse_time(bdc, penalty, timebuf, len, use_json, json); +} + + +static int bgp_print_dampening_parameters(struct bgp *bgp, struct vty *vty, + afi_t afi, safi_t safi, bool use_json) +{ + if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) { + struct bgp_damp_config *bdc = &bgp->damp[afi][safi]; + + if (use_json) { + json_object *json = json_object_new_object(); + + json_object_int_add(json, "halfLifeSecs", + bdc->half_life); + json_object_int_add(json, "reusePenalty", + bdc->reuse_limit); + json_object_int_add(json, "suppressPenalty", + bdc->suppress_value); + json_object_int_add(json, "maxSuppressTimeSecs", + bdc->max_suppress_time); + json_object_int_add(json, "maxSuppressPenalty", + bdc->ceiling); + + vty_json(vty, json); + } else { + vty_out(vty, "Half-life time: %lld min\n", + (long long)bdc->half_life / 60); + vty_out(vty, "Reuse penalty: %d\n", bdc->reuse_limit); + vty_out(vty, "Suppress penalty: %d\n", + bdc->suppress_value); + vty_out(vty, "Max suppress time: %lld min\n", + (long long)bdc->max_suppress_time / 60); + vty_out(vty, "Max suppress penalty: %u\n", + bdc->ceiling); + vty_out(vty, "\n"); + } + } else if (!use_json) + vty_out(vty, "dampening not enabled for %s\n", + get_afi_safi_str(afi, safi, false)); + + return CMD_SUCCESS; +} + +int bgp_show_dampening_parameters(struct vty *vty, afi_t afi, safi_t safi, + uint16_t show_flags) +{ + struct bgp *bgp; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + bgp = bgp_get_default(); + if (bgp == NULL) { + vty_out(vty, "No BGP process is configured\n"); + return CMD_WARNING; + } + + if (!CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL)) + return bgp_print_dampening_parameters(bgp, vty, afi, safi, + use_json); + + if (CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + || CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6)) { + afi = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) ? AFI_IP + : AFI_IP6; + FOREACH_SAFI (safi) { + if (strmatch(get_afi_safi_str(afi, safi, true), + "Unknown")) + continue; + + if (!use_json) + vty_out(vty, "\nFor address family: %s\n\n", + get_afi_safi_str(afi, safi, false)); + + bgp_print_dampening_parameters(bgp, vty, afi, safi, + use_json); + } + } else { + FOREACH_AFI_SAFI (afi, safi) { + if (strmatch(get_afi_safi_str(afi, safi, true), + "Unknown")) + continue; + + if (!use_json) + vty_out(vty, "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, false)); + + bgp_print_dampening_parameters(bgp, vty, afi, safi, + use_json); + } + } + return CMD_SUCCESS; +} + +void bgp_peer_damp_enable(struct peer *peer, afi_t afi, safi_t safi, time_t half, + unsigned int reuse, unsigned int suppress, time_t max) +{ + struct bgp_damp_config *bdc; + + if (!peer) + return; + bdc = &peer->damp[afi][safi]; + if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_CONFIG_DAMPENING)) { + if (bdc->half_life == half && bdc->reuse_limit == reuse && + bdc->suppress_value == suppress && + bdc->max_suppress_time == max) + return; + bgp_peer_damp_disable(peer, afi, safi); + } + SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_CONFIG_DAMPENING); + bgp_damp_parameter_set(half, reuse, suppress, max, bdc); + bdc->afi = afi; + bdc->safi = safi; + event_add_timer(bm->master, bgp_reuse_timer, bdc, DELTA_REUSE, + &bdc->t_reuse); +} + +/* Disable route flap dampening for a peer. + * + * Please note that this function also gets used to free memory when deleting a + * peer or peer group. + */ +void bgp_peer_damp_disable(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_damp_config *bdc; + + if (!peer_af_flag_check(peer, afi, safi, PEER_FLAG_CONFIG_DAMPENING)) + return; + bdc = &peer->damp[afi][safi]; + if (!bdc) + return; + bgp_damp_info_clean(peer->bgp, bdc, afi, safi); + UNSET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_CONFIG_DAMPENING); +} + +void bgp_config_write_peer_damp(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi) +{ + struct bgp_damp_config *bdc; + + bdc = &peer->damp[afi][safi]; + if (bdc->half_life == DEFAULT_HALF_LIFE * 60 && + bdc->reuse_limit == DEFAULT_REUSE && + bdc->suppress_value == DEFAULT_SUPPRESS && + bdc->max_suppress_time == bdc->half_life * 4) + vty_out(vty, " neighbor %s dampening\n", peer->host); + else if (bdc->half_life != DEFAULT_HALF_LIFE * 60 && + bdc->reuse_limit == DEFAULT_REUSE && + bdc->suppress_value == DEFAULT_SUPPRESS && + bdc->max_suppress_time == bdc->half_life * 4) + vty_out(vty, " neighbor %s dampening %lld\n", peer->host, + bdc->half_life / 60LL); + else + vty_out(vty, " neighbor %s dampening %lld %d %d %lld\n", + peer->host, bdc->half_life / 60LL, bdc->reuse_limit, + bdc->suppress_value, bdc->max_suppress_time / 60LL); +} + +static void bgp_print_peer_dampening_parameters(struct vty *vty, + struct peer *peer, afi_t afi, + safi_t safi, bool use_json, + json_object *json) +{ + struct bgp_damp_config *bdc; + + if (!peer) + return; + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_CONFIG_DAMPENING)) { + bdc = &peer->damp[afi][safi]; + if (!bdc) + return; + if (use_json) { + json_object_int_add(json, "halfLifeSecs", + bdc->half_life); + json_object_int_add(json, "reusePenalty", + bdc->reuse_limit); + json_object_int_add(json, "suppressPenalty", + bdc->suppress_value); + json_object_int_add(json, "maxSuppressTimeSecs", + bdc->max_suppress_time); + json_object_int_add(json, "maxSuppressPenalty", + bdc->ceiling); + } else { + vty_out(vty, "Half-life time: %lld min\n", + (long long)bdc->half_life / 60); + vty_out(vty, "Reuse penalty: %d\n", bdc->reuse_limit); + vty_out(vty, "Suppress penalty: %d\n", + bdc->suppress_value); + vty_out(vty, "Max suppress time: %lld min\n", + (long long)bdc->max_suppress_time / 60); + vty_out(vty, "Max suppress penalty: %u\n", bdc->ceiling); + vty_out(vty, "\n"); + } + } else if (!use_json) + vty_out(vty, "neighbor dampening not enabled for %s\n", + get_afi_safi_str(afi, safi, false)); +} + +void bgp_show_peer_dampening_parameters(struct vty *vty, struct peer *peer, + afi_t afi, safi_t safi, bool use_json) +{ + json_object *json; + + if (use_json) { + json = json_object_new_object(); + json_object_string_add(json, "addressFamily", + get_afi_safi_str(afi, safi, false)); + bgp_print_peer_dampening_parameters(vty, peer, afi, safi, true, + json); + vty_out(vty, "%s\n", + json_object_to_json_string_ext(json, + JSON_C_TO_STRING_PRETTY)); + json_object_free(json); + } else { + vty_out(vty, "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, false)); + bgp_print_peer_dampening_parameters(vty, peer, afi, safi, false, + NULL); + } +} diff --git a/bgpd/bgp_damp.h b/bgpd/bgp_damp.h new file mode 100644 index 0000000..851c6f9 --- /dev/null +++ b/bgpd/bgp_damp.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP flap dampening + * Copyright (C) 2001 IP Infusion Inc. + */ + +#ifndef _QUAGGA_BGP_DAMP_H +#define _QUAGGA_BGP_DAMP_H + +#include "bgpd/bgp_table.h" + +/* Structure maintained on a per-route basis. */ +struct bgp_damp_info { + /* Figure-of-merit. */ + unsigned int penalty; + + /* Number of flapping. */ + unsigned int flap; + + /* First flap time */ + time_t start_time; + + /* Last time penalty was updated. */ + time_t t_updated; + + /* Time of route start to be suppressed. */ + time_t suppress_time; + + /* Back reference to associated dampening configuration. */ + struct bgp_damp_config *config; + + /* Back reference to bgp_path_info. */ + struct bgp_path_info *path; + + /* Back reference to bgp_node. */ + struct bgp_dest *dest; + + /* Current index in the reuse_list. */ + int index; +#define BGP_DAMP_NO_REUSE_LIST_INDEX \ + (-1) /* index for elements on no_reuse_list */ + + /* Last time message type. */ + uint8_t lastrecord; +#define BGP_RECORD_UPDATE 1U +#define BGP_RECORD_WITHDRAW 2U + + afi_t afi; + safi_t safi; + + SLIST_ENTRY(bgp_damp_info) entry; +}; + +SLIST_HEAD(reuselist, bgp_damp_info); + +/* Specified parameter set configuration. */ +struct bgp_damp_config { + /* Value over which routes suppressed. */ + unsigned int suppress_value; + + /* Value below which suppressed routes reused. */ + unsigned int reuse_limit; + + /* Max time a route can be suppressed. */ + time_t max_suppress_time; + + /* Time during which accumulated penalty reduces by half. */ + time_t half_life; + + /* Non-configurable parameters but fixed at implementation time. + * To change this values, init_bgp_damp() should be modified. + */ + unsigned int reuse_list_size; /* Number of reuse lists */ + unsigned int reuse_index_size; /* Size of reuse index array */ + + /* Non-configurable parameters. Most of these are calculated from + * the configurable parameters above. + */ + unsigned int ceiling; /* Max value a penalty can attain */ + unsigned int decay_rate_per_tick; /* Calculated from half-life */ + unsigned int decay_array_size; /* Calculated using config parameters */ + unsigned int reuse_scale_factor; + double scale_factor; + + /* Decay array per-set based. */ + double *decay_array; + + /* Reuse index array per-set based. */ + int *reuse_index; + + /* Reuse list array per-set based. */ + struct reuselist *reuse_list; + unsigned int reuse_offset; + safi_t safi; + + /* All dampening information which is not on reuse list. */ + struct reuselist no_reuse_list; + + /* Reuse timer thread per-set base. */ + struct event *t_reuse; + + afi_t afi; +}; + +#define BGP_DAMP_NONE 0 +#define BGP_DAMP_USED 1 +#define BGP_DAMP_SUPPRESSED 2 + +/* Time granularity for reuse lists */ +#define DELTA_REUSE 10 + +/* Time granularity for decay arrays */ +#define DELTA_T 5 + +#define DEFAULT_PENALTY 1000 + +#define DEFAULT_HALF_LIFE 15 +#define DEFAULT_REUSE 750 +#define DEFAULT_SUPPRESS 2000 + +#define REUSE_LIST_SIZE 256 +#define REUSE_ARRAY_SIZE 1024 + +extern struct bgp_damp_config *get_active_bdc_from_pi(struct bgp_path_info *pi, + afi_t afi, safi_t safi); +extern int bgp_damp_enable(struct bgp *bgp, afi_t afi, safi_t safi, time_t half, + unsigned int reuse, unsigned int suppress, + time_t max); +extern int bgp_damp_disable(struct bgp *bgp, afi_t afi, safi_t safi); +extern int bgp_damp_withdraw(struct bgp_path_info *path, struct bgp_dest *dest, + afi_t afi, safi_t safi, int attr_change); +extern int bgp_damp_update(struct bgp_path_info *path, struct bgp_dest *dest, + afi_t afi, safi_t saff); +extern void bgp_damp_info_free(struct bgp_damp_info *bdi, + struct reuselist *list, int withdraw); +extern void bgp_damp_info_clean(struct bgp *bgp, struct bgp_damp_config *bdc, + afi_t afi, safi_t safi); +extern void bgp_damp_config_clean(struct bgp_damp_config *bdc); +extern int bgp_damp_decay(time_t tdiff, int penalty, + struct bgp_damp_config *bdc); +extern void bgp_config_write_damp(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi); +extern void bgp_damp_info_vty(struct vty *vty, struct bgp *bgp, + struct bgp_path_info *path, afi_t afi, + safi_t safi, json_object *json_path); +extern const char *bgp_damp_reuse_time_vty(struct vty *vty, + struct bgp_path_info *path, + char *timebuf, size_t len, afi_t afi, + safi_t safi, bool use_json, + json_object *json); +extern int bgp_show_dampening_parameters(struct vty *vty, afi_t afi, + safi_t safi, uint16_t show_flags); +extern void bgp_peer_damp_enable(struct peer *peer, afi_t afi, safi_t safi, + time_t half, unsigned int reuse, + unsigned int suppress, time_t max); +extern void bgp_peer_damp_disable(struct peer *peer, afi_t afi, safi_t safi); +extern void bgp_config_write_peer_damp(struct vty *vty, struct peer *peer, + afi_t afi, safi_t safi); +extern void bgp_show_peer_dampening_parameters(struct vty *vty, + struct peer *peer, afi_t afi, + safi_t safi, bool use_json); + +#endif /* _QUAGGA_BGP_DAMP_H */ diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c new file mode 100644 index 0000000..6228432 --- /dev/null +++ b/bgpd/bgp_debug.c @@ -0,0 +1,2802 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP-4, BGP-4+ packet debug routine + * Copyright (C) 1996, 97, 99 Kunihiro Ishiguro + */ + +#include + +#include "lib/bfd.h" +#include "lib/printfrr.h" +#include "prefix.h" +#include "linklist.h" +#include "stream.h" +#include "command.h" +#include "log.h" +#include "sockunion.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" +#include "hook.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_packet.h" + +#include "bgpd/bgp_debug_clippy.c" + +DEFINE_HOOK(bgp_hook_config_write_debug, (struct vty *vty, bool running), + (vty, running)); + +unsigned long conf_bgp_debug_as4; +unsigned long conf_bgp_debug_neighbor_events; +unsigned long conf_bgp_debug_events; +unsigned long conf_bgp_debug_packet; +unsigned long conf_bgp_debug_filter; +unsigned long conf_bgp_debug_keepalive; +unsigned long conf_bgp_debug_update; +unsigned long conf_bgp_debug_bestpath; +unsigned long conf_bgp_debug_zebra; +unsigned long conf_bgp_debug_nht; +unsigned long conf_bgp_debug_update_groups; +unsigned long conf_bgp_debug_vpn; +unsigned long conf_bgp_debug_flowspec; +unsigned long conf_bgp_debug_labelpool; +unsigned long conf_bgp_debug_pbr; +unsigned long conf_bgp_debug_graceful_restart; +unsigned long conf_bgp_debug_evpn_mh; +unsigned long conf_bgp_debug_bfd; +unsigned long conf_bgp_debug_cond_adv; + +unsigned long term_bgp_debug_as4; +unsigned long term_bgp_debug_neighbor_events; +unsigned long term_bgp_debug_events; +unsigned long term_bgp_debug_packet; +unsigned long term_bgp_debug_filter; +unsigned long term_bgp_debug_keepalive; +unsigned long term_bgp_debug_update; +unsigned long term_bgp_debug_bestpath; +unsigned long term_bgp_debug_zebra; +unsigned long term_bgp_debug_nht; +unsigned long term_bgp_debug_update_groups; +unsigned long term_bgp_debug_vpn; +unsigned long term_bgp_debug_flowspec; +unsigned long term_bgp_debug_labelpool; +unsigned long term_bgp_debug_pbr; +unsigned long term_bgp_debug_graceful_restart; +unsigned long term_bgp_debug_evpn_mh; +unsigned long term_bgp_debug_bfd; +unsigned long term_bgp_debug_cond_adv; + +struct list *bgp_debug_neighbor_events_peers = NULL; +struct list *bgp_debug_keepalive_peers = NULL; +struct list *bgp_debug_update_out_peers = NULL; +struct list *bgp_debug_update_in_peers = NULL; +struct list *bgp_debug_update_prefixes = NULL; +struct list *bgp_debug_bestpath_prefixes = NULL; +struct list *bgp_debug_zebra_prefixes = NULL; + +/* messages for BGP-4 status */ +const struct message bgp_status_msg[] = {{Idle, "Idle"}, + {Connect, "Connect"}, + {Active, "Active"}, + {OpenSent, "OpenSent"}, + {OpenConfirm, "OpenConfirm"}, + {Established, "Established"}, + {Clearing, "Clearing"}, + {Deleted, "Deleted"}, + {0}}; + +/* BGP message type string. */ +const char *const bgp_type_str[] = {NULL, "OPEN", "UPDATE", + "NOTIFICATION", "KEEPALIVE", "ROUTE-REFRESH", + "CAPABILITY"}; + +/* message for BGP-4 Notify */ +static const struct message bgp_notify_msg[] = { + {BGP_NOTIFY_HEADER_ERR, "Message Header Error"}, + {BGP_NOTIFY_OPEN_ERR, "OPEN Message Error"}, + {BGP_NOTIFY_UPDATE_ERR, "UPDATE Message Error"}, + {BGP_NOTIFY_HOLD_ERR, "Hold Timer Expired"}, + {BGP_NOTIFY_FSM_ERR, "Neighbor Events Error"}, + {BGP_NOTIFY_CEASE, "Cease"}, + {BGP_NOTIFY_ROUTE_REFRESH_ERR, "ROUTE-REFRESH Message Error"}, + {BGP_NOTIFY_SEND_HOLD_ERR, "Send Hold Timer Expired"}, + {0}}; + +static const struct message bgp_notify_head_msg[] = { + {BGP_NOTIFY_HEADER_NOT_SYNC, "/Connection Not Synchronized"}, + {BGP_NOTIFY_HEADER_BAD_MESLEN, "/Bad Message Length"}, + {BGP_NOTIFY_HEADER_BAD_MESTYPE, "/Bad Message Type"}, + {0}}; + +static const struct message bgp_notify_open_msg[] = { + {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, + {BGP_NOTIFY_OPEN_UNSUP_VERSION, "/Unsupported Version Number"}, + {BGP_NOTIFY_OPEN_BAD_PEER_AS, "/Bad Peer AS"}, + {BGP_NOTIFY_OPEN_BAD_BGP_IDENT, "/Bad BGP Identifier"}, + {BGP_NOTIFY_OPEN_UNSUP_PARAM, "/Unsupported Optional Parameter"}, + {BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, "/Unacceptable Hold Time"}, + {BGP_NOTIFY_OPEN_UNSUP_CAPBL, "/Unsupported Capability"}, + {BGP_NOTIFY_OPEN_ROLE_MISMATCH, "/Role Mismatch"}, + {0}}; + +static const struct message bgp_notify_update_msg[] = { + {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, + {BGP_NOTIFY_UPDATE_MAL_ATTR, "/Malformed Attribute List"}, + {BGP_NOTIFY_UPDATE_UNREC_ATTR, "/Unrecognized Well-known Attribute"}, + {BGP_NOTIFY_UPDATE_MISS_ATTR, "/Missing Well-known Attribute"}, + {BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR, "/Attribute Flags Error"}, + {BGP_NOTIFY_UPDATE_ATTR_LENG_ERR, "/Attribute Length Error"}, + {BGP_NOTIFY_UPDATE_INVAL_ORIGIN, "/Invalid ORIGIN Attribute"}, + {BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP, "/Invalid NEXT_HOP Attribute"}, + {BGP_NOTIFY_UPDATE_OPT_ATTR_ERR, "/Optional Attribute Error"}, + {BGP_NOTIFY_UPDATE_INVAL_NETWORK, "/Invalid Network Field"}, + {BGP_NOTIFY_UPDATE_MAL_AS_PATH, "/Malformed AS_PATH"}, + {0}}; + +static const struct message bgp_notify_cease_msg[] = { + {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, + {BGP_NOTIFY_CEASE_MAX_PREFIX, "/Maximum Number of Prefixes Reached"}, + {BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN, "/Administrative Shutdown"}, + {BGP_NOTIFY_CEASE_PEER_UNCONFIG, "/Peer De-configured"}, + {BGP_NOTIFY_CEASE_ADMIN_RESET, "/Administrative Reset"}, + {BGP_NOTIFY_CEASE_CONNECT_REJECT, "/Connection Rejected"}, + {BGP_NOTIFY_CEASE_CONFIG_CHANGE, "/Other Configuration Change"}, + {BGP_NOTIFY_CEASE_COLLISION_RESOLUTION, + "/Connection Collision Resolution"}, + {BGP_NOTIFY_CEASE_OUT_OF_RESOURCE, "/Out of Resources"}, + {BGP_NOTIFY_CEASE_HARD_RESET, "/Hard Reset"}, + {BGP_NOTIFY_CEASE_BFD_DOWN, "/BFD Down"}, + {0}}; + +static const struct message bgp_notify_route_refresh_msg[] = { + {BGP_NOTIFY_SUBCODE_UNSPECIFIC, "/Unspecific"}, + {BGP_NOTIFY_ROUTE_REFRESH_INVALID_MSG_LEN, "/Invalid Message Length"}, + {0}}; + +static const struct message bgp_notify_fsm_msg[] = { + {BGP_NOTIFY_FSM_ERR_SUBCODE_UNSPECIFIC, "/Unspecific"}, + {BGP_NOTIFY_FSM_ERR_SUBCODE_OPENSENT, + "/Receive Unexpected Message in OpenSent State"}, + {BGP_NOTIFY_FSM_ERR_SUBCODE_OPENCONFIRM, + "/Receive Unexpected Message in OpenConfirm State"}, + {BGP_NOTIFY_FSM_ERR_SUBCODE_ESTABLISHED, + "/Receive Unexpected Message in Established State"}, + {0}}; + +/* Origin strings. */ +const char *const bgp_origin_str[] = {"i", "e", "?"}; +const char *const bgp_origin_long_str[] = {"IGP", "EGP", "incomplete"}; + +static void bgp_debug_print_evpn_prefix(struct vty *vty, const char *desc, + struct prefix *p); +/* Given a string return a pointer the corresponding peer structure */ +static struct peer *bgp_find_peer(struct vty *vty, const char *peer_str) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int ret; + union sockunion su; + struct peer *peer; + + if (!bgp) { + return NULL; + } + ret = str2sockunion(peer_str, &su); + + /* 'swpX' string */ + if (ret < 0) { + peer = peer_lookup_by_conf_if(bgp, peer_str); + + if (!peer) + peer = peer_lookup_by_hostname(bgp, peer_str); + + return peer; + } else + return peer_lookup(bgp, &su); +} + +static void bgp_debug_list_free(struct list *list) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + if (list) + for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { + listnode_delete(list, filter); + prefix_free(&filter->p); + XFREE(MTYPE_BGP_DEBUG_STR, filter->host); + XFREE(MTYPE_BGP_DEBUG_STR, filter->plist_name); + XFREE(MTYPE_BGP_DEBUG_FILTER, filter); + } +} + +/* + * Print the desc along with a list of peers/prefixes this debug is + * enabled for + */ +static void bgp_debug_list_print(struct vty *vty, const char *desc, + struct list *list) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + vty_out(vty, "%s", desc); + + if (list && !list_isempty(list)) { + vty_out(vty, " for:\n"); + for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { + if (filter->host) + vty_out(vty, " %s", filter->host); + + if (filter->plist_name) + vty_out(vty, " with prefix-list %s", + filter->plist_name); + + if (filter->p && filter->p->family == AF_EVPN) + bgp_debug_print_evpn_prefix(vty, "", filter->p); + else if (filter->p) + vty_out(vty, " %pFX", filter->p); + + vty_out(vty, "\n"); + } + } + + vty_out(vty, "\n"); +} + +/* + * Print the command to enable the debug for each peer/prefix this debug is + * enabled for + */ +static int bgp_debug_list_conf_print(struct vty *vty, const char *desc, + struct list *list) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + int write = 0; + + if (list && !list_isempty(list)) { + for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { + if (filter->host && filter->plist_name) { + vty_out(vty, "%s %s prefix-list %s\n", desc, + filter->host, filter->plist_name); + write++; + } else if (filter->host) { + vty_out(vty, "%s %s\n", desc, filter->host); + write++; + } + + if (filter->p && filter->p->family == AF_EVPN) { + bgp_debug_print_evpn_prefix(vty, desc, + filter->p); + write++; + } else if (filter->p) { + vty_out(vty, "%s %pFX\n", desc, filter->p); + write++; + } + } + } + + if (!write) { + vty_out(vty, "%s\n", desc); + write++; + } + + return write; +} + +static void bgp_debug_list_add_entry(struct list *list, const char *host, + const struct prefix *p, + const char *plist_name) +{ + struct bgp_debug_filter *filter; + + filter = XCALLOC(MTYPE_BGP_DEBUG_FILTER, + sizeof(struct bgp_debug_filter)); + + if (host) { + filter->host = XSTRDUP(MTYPE_BGP_DEBUG_STR, host); + filter->plist_name = NULL; + filter->plist_v4 = NULL; + filter->plist_v6 = NULL; + filter->p = NULL; + } else if (p) { + filter->host = NULL; + filter->plist_name = NULL; + filter->plist_v4 = NULL; + filter->plist_v6 = NULL; + filter->p = prefix_new(); + prefix_copy(filter->p, p); + } + + if (plist_name) { + filter->plist_name = XSTRDUP(MTYPE_BGP_DEBUG_STR, plist_name); + filter->plist_v4 = prefix_list_lookup(AFI_IP, + filter->plist_name); + filter->plist_v6 = prefix_list_lookup(AFI_IP6, + filter->plist_name); + } + + listnode_add(list, filter); +} + +static bool bgp_debug_list_remove_entry(struct list *list, const char *host, + const struct prefix *p) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { + if (host && strcmp(filter->host, host) == 0) { + listnode_delete(list, filter); + XFREE(MTYPE_BGP_DEBUG_STR, filter->host); + XFREE(MTYPE_BGP_DEBUG_STR, filter->plist_name); + XFREE(MTYPE_BGP_DEBUG_FILTER, filter); + return true; + } else if (p && filter->p->prefixlen == p->prefixlen + && prefix_match(filter->p, p)) { + listnode_delete(list, filter); + prefix_free(&filter->p); + XFREE(MTYPE_BGP_DEBUG_FILTER, filter); + return true; + } + } + + return false; +} + +static bool bgp_debug_list_has_entry(struct list *list, const char *host, + const struct prefix *p, + const char *plist_name) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(list, node, nnode, filter)) { + if (host && plist_name) { + if (strmatch(filter->host, host) && filter->plist_name && + strmatch(filter->plist_name, plist_name)) + return true; + } else if (host) { + if (strmatch(filter->host, host)) + return true; + } else if (p) { + if (filter->p->prefixlen == p->prefixlen + && prefix_match(filter->p, p)) { + return true; + } + } + } + + return false; +} + +bool bgp_debug_peer_updout_enabled(char *host) +{ + return (bgp_debug_list_has_entry(bgp_debug_update_out_peers, host, NULL, + NULL)); +} + +/* Dump attribute. */ +bool bgp_dump_attr(struct attr *attr, char *buf, size_t size) +{ + if (!attr) + return false; + + buf[0] = '\0'; + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP))) + snprintfrr(buf, size, "nexthop %pI4", &attr->nexthop); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGIN))) + snprintf(buf + strlen(buf), size - strlen(buf), ", origin %s", + bgp_origin_str[attr->origin]); + + /* Add MP case. */ + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL + || attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + snprintfrr(buf + strlen(buf), size - strlen(buf), + ", mp_nexthop %pI6", &attr->mp_nexthop_global); + + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + snprintfrr(buf + strlen(buf), size - strlen(buf), "(%pI6)", + &attr->mp_nexthop_local); + + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) + snprintfrr(buf, size, "nexthop %pI4", &attr->nexthop); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", localpref %u", attr->local_pref); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", aigp-metric %" PRIu64, + (unsigned long long)bgp_attr_get_aigp_metric(attr)); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) + snprintf(buf + strlen(buf), size - strlen(buf), ", metric %u", + attr->med); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", community %s", + community_str(bgp_attr_get_community(attr), false, + true)); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", large-community %s", + lcommunity_str(bgp_attr_get_lcommunity(attr), false, + true)); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", extcommunity %s", + ecommunity_str(bgp_attr_get_ecommunity(attr))); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", atomic-aggregate"); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) + snprintfrr(buf + strlen(buf), size - strlen(buf), + ", aggregated by %u %pI4", attr->aggregator_as, + &attr->aggregator_addr); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID))) + snprintfrr(buf + strlen(buf), size - strlen(buf), + ", originator %pI4", &attr->originator_id); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST))) { + struct cluster_list *cluster; + int i; + + snprintf(buf + strlen(buf), size - strlen(buf), + ", clusterlist"); + + cluster = bgp_attr_get_cluster(attr); + for (i = 0; i < cluster->length / 4; i++) + snprintfrr(buf + strlen(buf), size - strlen(buf), + " %pI4", &cluster->list[i]); + } + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL))) + snprintf(buf + strlen(buf), size - strlen(buf), + ", pmsi tnltype %u", bgp_attr_get_pmsi_tnl_type(attr)); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AS_PATH))) + snprintf(buf + strlen(buf), size - strlen(buf), ", path %s", + aspath_print(attr->aspath)); + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID))) { + if (attr->label_index != BGP_INVALID_LABEL_INDEX) + snprintf(buf + strlen(buf), size - strlen(buf), + ", label-index %u", attr->label_index); + } + + if (strlen(buf) > 1) + return true; + else + return false; +} + +const char *bgp_notify_code_str(char code) +{ + return lookup_msg(bgp_notify_msg, code, "Unrecognized Error Code"); +} + +const char *bgp_notify_subcode_str(char code, char subcode) +{ + + switch (code) { + case BGP_NOTIFY_HEADER_ERR: + return lookup_msg(bgp_notify_head_msg, subcode, + "Unrecognized Error Subcode"); + case BGP_NOTIFY_OPEN_ERR: + return lookup_msg(bgp_notify_open_msg, subcode, + "Unrecognized Error Subcode"); + case BGP_NOTIFY_UPDATE_ERR: + return lookup_msg(bgp_notify_update_msg, subcode, + "Unrecognized Error Subcode"); + case BGP_NOTIFY_HOLD_ERR: + case BGP_NOTIFY_SEND_HOLD_ERR: + break; + case BGP_NOTIFY_FSM_ERR: + return lookup_msg(bgp_notify_fsm_msg, subcode, + "Unrecognized Error Subcode"); + case BGP_NOTIFY_CEASE: + return lookup_msg(bgp_notify_cease_msg, subcode, + "Unrecognized Error Subcode"); + case BGP_NOTIFY_ROUTE_REFRESH_ERR: + return lookup_msg(bgp_notify_route_refresh_msg, subcode, + "Unrecognized Error Subcode"); + } + return ""; +} + +/* extract notify admin reason if correctly present */ +const char *bgp_notify_admin_message(char *buf, size_t bufsz, uint8_t *data, + size_t datalen) +{ + memset(buf, 0, bufsz); + if (!data || datalen < 1) + return buf; + + uint8_t len = data[0]; + if (!len || len > datalen - 1) + return buf; + + return zlog_sanitize(buf, bufsz, data + 1, len); +} + +/* dump notify packet */ +void bgp_notify_print(struct peer *peer, struct bgp_notify *bgp_notify, + const char *direct, bool hard_reset) +{ + const char *subcode_str; + const char *code_str; + const char *msg_str = NULL; + char msg_buf[1024]; + + if (BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS) + || CHECK_FLAG(peer->bgp->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { + code_str = bgp_notify_code_str(bgp_notify->code); + subcode_str = bgp_notify_subcode_str(bgp_notify->code, + bgp_notify->subcode); + + if (bgp_notify->code == BGP_NOTIFY_CEASE + && (bgp_notify->subcode == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN + || bgp_notify->subcode + == BGP_NOTIFY_CEASE_ADMIN_RESET)) { + msg_str = bgp_notify_admin_message( + msg_buf, sizeof(msg_buf), bgp_notify->raw_data, + bgp_notify->length); + } + + if (msg_str) { + zlog_info( + "%%NOTIFICATION%s: %s neighbor %s %d/%d (%s%s) \"%s\"", + hard_reset ? "(Hard Reset)" : "", + strcmp(direct, "received") == 0 + ? "received from" + : "sent to", + peer->host, bgp_notify->code, + bgp_notify->subcode, code_str, subcode_str, + msg_str); + } else { + msg_str = bgp_notify->data ? bgp_notify->data : ""; + zlog_info( + "%%NOTIFICATION%s: %s neighbor %s %d/%d (%s%s) %d bytes %s", + hard_reset ? "(Hard Reset)" : "", + strcmp(direct, "received") == 0 + ? "received from" + : "sent to", + peer->host, bgp_notify->code, + bgp_notify->subcode, code_str, subcode_str, + bgp_notify->length, msg_str); + } + } +} + +static void bgp_debug_clear_updgrp_update_dbg(struct bgp *bgp) +{ + if (!bgp) + bgp = bgp_get_default(); + update_group_walk(bgp, update_group_clear_update_dbg, NULL); +} + +static void bgp_debug_print_evpn_prefix(struct vty *vty, const char *desc, + struct prefix *p) +{ + char evpn_desc[PREFIX2STR_BUFFER + INET_ADDRSTRLEN]; + char buf[PREFIX2STR_BUFFER]; + char buf2[ETHER_ADDR_STRLEN]; + + if (p->u.prefix_evpn.route_type == BGP_EVPN_MAC_IP_ROUTE) { + if (is_evpn_prefix_ipaddr_none((struct prefix_evpn *)p)) { + snprintf( + evpn_desc, sizeof(evpn_desc), + "l2vpn evpn type macip mac %s", + prefix_mac2str(&p->u.prefix_evpn.macip_addr.mac, + buf2, sizeof(buf2))); + } else { + uint8_t family = is_evpn_prefix_ipaddr_v4( + (struct prefix_evpn *)p) ? + AF_INET : AF_INET6; + snprintf( + evpn_desc, sizeof(evpn_desc), + "l2vpn evpn type macip mac %s ip %s", + prefix_mac2str(&p->u.prefix_evpn.macip_addr.mac, + buf2, sizeof(buf2)), + inet_ntop( + family, + &p->u.prefix_evpn.macip_addr.ip.ip.addr, + buf, PREFIX2STR_BUFFER)); + } + } else if (p->u.prefix_evpn.route_type == BGP_EVPN_IMET_ROUTE) { + snprintfrr(evpn_desc, sizeof(evpn_desc), + "l2vpn evpn type multicast ip %pI4", + &p->u.prefix_evpn.imet_addr.ip.ipaddr_v4); + } else if (p->u.prefix_evpn.route_type == BGP_EVPN_IP_PREFIX_ROUTE) { + uint8_t family = is_evpn_prefix_ipaddr_v4( + (struct prefix_evpn *)p) ? AF_INET + : AF_INET6; + snprintf(evpn_desc, sizeof(evpn_desc), + "l2vpn evpn type prefix ip %s/%d", + inet_ntop(family, + &p->u.prefix_evpn.prefix_addr.ip.ip.addr, + buf, PREFIX2STR_BUFFER), + p->u.prefix_evpn.prefix_addr.ip_prefix_length); + } + + vty_out(vty, "%s %s\n", desc, evpn_desc); +} + +static int bgp_debug_parse_evpn_prefix(struct vty *vty, struct cmd_token **argv, + int argc, struct prefix *argv_p) +{ + struct ethaddr mac = {}; + struct ipaddr ip = {}; + int evpn_type = 0; + int mac_idx = 0; + int ip_idx = 0; + + if (bgp_evpn_cli_parse_type(&evpn_type, argv, argc) < 0) + return CMD_WARNING; + + if (evpn_type == BGP_EVPN_MAC_IP_ROUTE) { + memset(&ip, 0, sizeof(ip)); + + if (argv_find(argv, argc, "mac", &mac_idx)) + if (!prefix_str2mac(argv[mac_idx + 1]->arg, &mac)) { + vty_out(vty, "%% Malformed MAC address\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "ip", &ip_idx)) + if (str2ipaddr(argv[ip_idx + 1]->arg, &ip) != 0) { + vty_out(vty, "%% Malformed IP address\n"); + return CMD_WARNING; + } + + build_evpn_type2_prefix((struct prefix_evpn *)argv_p, + &mac, &ip); + } else if (evpn_type == BGP_EVPN_IMET_ROUTE) { + memset(&ip, 0, sizeof(ip)); + + if (argv_find(argv, argc, "ip", &ip_idx)) + if (str2ipaddr(argv[ip_idx + 1]->arg, &ip) != 0) { + vty_out(vty, "%% Malformed IP address\n"); + return CMD_WARNING; + } + + build_evpn_type3_prefix((struct prefix_evpn *)argv_p, + ip.ipaddr_v4); + } else if (evpn_type == BGP_EVPN_IP_PREFIX_ROUTE) { + struct prefix ip_prefix; + + memset(&ip_prefix, 0, sizeof(ip_prefix)); + if (argv_find(argv, argc, "ip", &ip_idx)) { + (void)str2prefix(argv[ip_idx + 1]->arg, &ip_prefix); + apply_mask(&ip_prefix); + } + build_type5_prefix_from_ip_prefix( + (struct prefix_evpn *)argv_p, + &ip_prefix); + } + + return CMD_SUCCESS; +} + +/* Debug option setting interface. */ +unsigned long bgp_debug_option = 0; + +int debug(unsigned int option) +{ + return bgp_debug_option & option; +} + +DEFUN (debug_bgp_as4, + debug_bgp_as4_cmd, + "debug bgp as4", + DEBUG_STR + BGP_STR + "BGP AS4 actions\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(as4, AS4); + else { + TERM_DEBUG_ON(as4, AS4); + vty_out(vty, "BGP as4 debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_as4, + no_debug_bgp_as4_cmd, + "no debug bgp as4", + NO_STR + DEBUG_STR + BGP_STR + "BGP AS4 actions\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(as4, AS4); + else { + TERM_DEBUG_OFF(as4, AS4); + vty_out(vty, "BGP as4 debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_as4_segment, + debug_bgp_as4_segment_cmd, + "debug bgp as4 segment", + DEBUG_STR + BGP_STR + "BGP AS4 actions\n" + "BGP AS4 aspath segment handling\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(as4, AS4_SEGMENT); + else { + TERM_DEBUG_ON(as4, AS4_SEGMENT); + vty_out(vty, "BGP as4 segment debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_as4_segment, + no_debug_bgp_as4_segment_cmd, + "no debug bgp as4 segment", + NO_STR + DEBUG_STR + BGP_STR + "BGP AS4 actions\n" + "BGP AS4 aspath segment handling\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(as4, AS4_SEGMENT); + else { + TERM_DEBUG_OFF(as4, AS4_SEGMENT); + vty_out(vty, "BGP as4 segment debugging is off\n"); + } + return CMD_SUCCESS; +} + +/* debug bgp neighbor_events */ +DEFUN (debug_bgp_neighbor_events, + debug_bgp_neighbor_events_cmd, + "debug bgp neighbor-events", + DEBUG_STR + BGP_STR + "BGP Neighbor Events\n") +{ + bgp_debug_list_free(bgp_debug_neighbor_events_peers); + + if (vty->node == CONFIG_NODE) + DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); + else { + TERM_DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); + vty_out(vty, "BGP neighbor-events debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_neighbor_events_peer, + debug_bgp_neighbor_events_peer_cmd, + "debug bgp neighbor-events ", + DEBUG_STR + BGP_STR + "BGP Neighbor Events\n" + "BGP neighbor IP address to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n") +{ + int idx_peer = 3; + const char *host = argv[idx_peer]->arg; + + if (!bgp_debug_neighbor_events_peers) + bgp_debug_neighbor_events_peers = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_neighbor_events_peers, host, + NULL, NULL)) { + vty_out(vty, + "BGP neighbor-events debugging is already enabled for %s\n", + host); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_neighbor_events_peers, host, NULL, + NULL); + + if (vty->node == CONFIG_NODE) + DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); + else { + TERM_DEBUG_ON(neighbor_events, NEIGHBOR_EVENTS); + vty_out(vty, "BGP neighbor-events debugging is on for %s\n", + host); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_neighbor_events, + no_debug_bgp_neighbor_events_cmd, + "no debug bgp neighbor-events", + NO_STR + DEBUG_STR + BGP_STR + "Neighbor Events\n") +{ + bgp_debug_list_free(bgp_debug_neighbor_events_peers); + + if (vty->node == CONFIG_NODE) + DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); + else { + TERM_DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); + vty_out(vty, "BGP neighbor-events debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_neighbor_events_peer, + no_debug_bgp_neighbor_events_peer_cmd, + "no debug bgp neighbor-events ", + NO_STR + DEBUG_STR + BGP_STR + "Neighbor Events\n" + "BGP neighbor IP address to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n") +{ + int idx_peer = 4; + int found_peer = 0; + const char *host = argv[idx_peer]->arg; + + if (bgp_debug_neighbor_events_peers + && !list_isempty(bgp_debug_neighbor_events_peers)) { + found_peer = bgp_debug_list_remove_entry( + bgp_debug_neighbor_events_peers, host, NULL); + + if (list_isempty(bgp_debug_neighbor_events_peers)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); + else + TERM_DEBUG_OFF(neighbor_events, + NEIGHBOR_EVENTS); + } + } + + if (found_peer) + vty_out(vty, "BGP neighbor-events debugging is off for %s\n", + host); + else + vty_out(vty, + "BGP neighbor-events debugging was not enabled for %s\n", + host); + + return CMD_SUCCESS; +} + +/* debug bgp nht */ +DEFUN (debug_bgp_nht, + debug_bgp_nht_cmd, + "debug bgp nht", + DEBUG_STR + BGP_STR + "BGP nexthop tracking events\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(nht, NHT); + else { + TERM_DEBUG_ON(nht, NHT); + vty_out(vty, "BGP nexthop tracking debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_nht, + no_debug_bgp_nht_cmd, + "no debug bgp nht", + NO_STR + DEBUG_STR + BGP_STR + "BGP nexthop tracking events\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(nht, NHT); + else { + TERM_DEBUG_OFF(nht, NHT); + vty_out(vty, "BGP nexthop tracking debugging is off\n"); + } + return CMD_SUCCESS; +} + +/* debug bgp keepalives */ +DEFUN (debug_bgp_keepalive, + debug_bgp_keepalive_cmd, + "debug bgp keepalives", + DEBUG_STR + BGP_STR + "BGP keepalives\n") +{ + bgp_debug_list_free(bgp_debug_keepalive_peers); + + if (vty->node == CONFIG_NODE) + DEBUG_ON(keepalive, KEEPALIVE); + else { + TERM_DEBUG_ON(keepalive, KEEPALIVE); + vty_out(vty, "BGP keepalives debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_keepalive_peer, + debug_bgp_keepalive_peer_cmd, + "debug bgp keepalives ", + DEBUG_STR + BGP_STR + "BGP keepalives\n" + "BGP IPv4 neighbor to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n") +{ + int idx_peer = 3; + const char *host = argv[idx_peer]->arg; + + if (!bgp_debug_keepalive_peers) + bgp_debug_keepalive_peers = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_keepalive_peers, host, NULL, + NULL)) { + vty_out(vty, + "BGP keepalive debugging is already enabled for %s\n", + host); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_keepalive_peers, host, NULL, NULL); + + if (vty->node == CONFIG_NODE) + DEBUG_ON(keepalive, KEEPALIVE); + else { + TERM_DEBUG_ON(keepalive, KEEPALIVE); + vty_out(vty, "BGP keepalives debugging is on for %s\n", host); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_keepalive, + no_debug_bgp_keepalive_cmd, + "no debug bgp keepalives", + NO_STR + DEBUG_STR + BGP_STR + "BGP keepalives\n") +{ + bgp_debug_list_free(bgp_debug_keepalive_peers); + + if (vty->node == CONFIG_NODE) + DEBUG_OFF(keepalive, KEEPALIVE); + else { + TERM_DEBUG_OFF(keepalive, KEEPALIVE); + vty_out(vty, "BGP keepalives debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_keepalive_peer, + no_debug_bgp_keepalive_peer_cmd, + "no debug bgp keepalives ", + NO_STR + DEBUG_STR + BGP_STR + "BGP keepalives\n" + "BGP neighbor IP address to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n") +{ + int idx_peer = 4; + int found_peer = 0; + const char *host = argv[idx_peer]->arg; + + if (bgp_debug_keepalive_peers + && !list_isempty(bgp_debug_keepalive_peers)) { + found_peer = bgp_debug_list_remove_entry( + bgp_debug_keepalive_peers, host, NULL); + + if (list_isempty(bgp_debug_keepalive_peers)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(keepalive, KEEPALIVE); + else + TERM_DEBUG_OFF(keepalive, KEEPALIVE); + } + } + + if (found_peer) + vty_out(vty, "BGP keepalives debugging is off for %s\n", host); + else + vty_out(vty, + "BGP keepalives debugging was not enabled for %s\n", + host); + + return CMD_SUCCESS; +} + +/* debug bgp bestpath */ +DEFPY (debug_bgp_bestpath_prefix, + debug_bgp_bestpath_prefix_cmd, + "debug bgp bestpath $prefix", + DEBUG_STR + BGP_STR + "BGP bestpath\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + if (!bgp_debug_bestpath_prefixes) + bgp_debug_bestpath_prefixes = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_bestpath_prefixes, NULL, prefix, + NULL)) { + vty_out(vty, + "BGP bestpath debugging is already enabled for %s\n", + prefix_str); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_bestpath_prefixes, NULL, prefix, + NULL); + + if (vty->node == CONFIG_NODE) { + DEBUG_ON(bestpath, BESTPATH); + } else { + TERM_DEBUG_ON(bestpath, BESTPATH); + vty_out(vty, "BGP bestpath debugging is on for %s\n", + prefix_str); + } + + return CMD_SUCCESS; +} + +DEFPY (no_debug_bgp_bestpath_prefix, + no_debug_bgp_bestpath_prefix_cmd, + "no debug bgp bestpath $prefix", + NO_STR + DEBUG_STR + BGP_STR + "BGP bestpath\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + bool found_prefix = false; + + if (bgp_debug_bestpath_prefixes + && !list_isempty(bgp_debug_bestpath_prefixes)) { + found_prefix = bgp_debug_list_remove_entry( + bgp_debug_bestpath_prefixes, NULL, prefix); + + if (list_isempty(bgp_debug_bestpath_prefixes)) { + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(bestpath, BESTPATH); + } else { + TERM_DEBUG_OFF(bestpath, BESTPATH); + vty_out(vty, + "BGP bestpath debugging (per prefix) is off\n"); + } + } + } + + if (found_prefix) + vty_out(vty, "BGP bestpath debugging is off for %s\n", + prefix_str); + else + vty_out(vty, "BGP bestpath debugging was not enabled for %s\n", + prefix_str); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_bestpath, + no_debug_bgp_bestpath_cmd, + "no debug bgp bestpath", + NO_STR + DEBUG_STR + BGP_STR + "BGP bestpath\n") +{ + bgp_debug_list_free(bgp_debug_bestpath_prefixes); + + if (vty->node == CONFIG_NODE) + DEBUG_OFF(bestpath, BESTPATH); + else { + TERM_DEBUG_OFF(bestpath, BESTPATH); + vty_out(vty, "BGP bestpath debugging is off\n"); + } + return CMD_SUCCESS; +} + +/* debug bgp updates */ +DEFUN (debug_bgp_update, + debug_bgp_update_cmd, + "debug bgp updates", + DEBUG_STR + BGP_STR + "BGP updates\n") +{ + bgp_debug_list_free(bgp_debug_update_in_peers); + bgp_debug_list_free(bgp_debug_update_out_peers); + bgp_debug_list_free(bgp_debug_update_prefixes); + + if (vty->node == CONFIG_NODE) { + DEBUG_ON(update, UPDATE_IN); + DEBUG_ON(update, UPDATE_OUT); + } else { + TERM_DEBUG_ON(update, UPDATE_IN); + TERM_DEBUG_ON(update, UPDATE_OUT); + vty_out(vty, "BGP updates debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFPY (debug_bgp_update_detail, + debug_bgp_update_detail_cmd, + "[no] debug bgp updates detail", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n" + "Show detailed information about updates\n") +{ + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(update, UPDATE_DETAIL); + else + DEBUG_ON(update, UPDATE_DETAIL); + } else { + if (no) + TERM_DEBUG_OFF(update, UPDATE_DETAIL); + else + TERM_DEBUG_ON(update, UPDATE_DETAIL); + vty_out(vty, "BGP updates detail debugging is on\n"); + } + + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_update_direct, + debug_bgp_update_direct_cmd, + "debug bgp updates ", + DEBUG_STR + BGP_STR + "BGP updates\n" + "Inbound updates\n" + "Outbound updates\n") +{ + int idx_in_out = 3; + + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) + bgp_debug_list_free(bgp_debug_update_in_peers); + else + bgp_debug_list_free(bgp_debug_update_out_peers); + + if (vty->node == CONFIG_NODE) { + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) + DEBUG_ON(update, UPDATE_IN); + else + DEBUG_ON(update, UPDATE_OUT); + } else { + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) { + TERM_DEBUG_ON(update, UPDATE_IN); + vty_out(vty, "BGP updates debugging is on (inbound)\n"); + } else { + TERM_DEBUG_ON(update, UPDATE_OUT); + vty_out(vty, + "BGP updates debugging is on (outbound)\n"); + } + } + return CMD_SUCCESS; +} + +DEFPY (debug_bgp_update_direct_peer, + debug_bgp_update_direct_peer_cmd, + "debug bgp updates [prefix-list PREFIXLIST_NAME$plist]", + DEBUG_STR + BGP_STR + "BGP updates\n" + "Inbound updates\n" + "Outbound updates\n" + "BGP neighbor IP address to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n" + "Use prefix-list to filter prefixes to debug\n" + "Name of prefix-list\n") +{ + int idx_in_out = 3; + int idx_peer = 4; + const char *host = argv[idx_peer]->arg; + int inbound; + + if (!bgp_debug_update_in_peers) + bgp_debug_update_in_peers = list_new(); + + if (!bgp_debug_update_out_peers) + bgp_debug_update_out_peers = list_new(); + + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) + inbound = 1; + else + inbound = 0; + + if (inbound) { + if (bgp_debug_list_has_entry(bgp_debug_update_in_peers, host, + NULL, plist)) { + vty_out(vty, + "BGP inbound update debugging is already enabled for %s\n", + host); + return CMD_SUCCESS; + } + } + + else { + if (bgp_debug_list_has_entry(bgp_debug_update_out_peers, host, + NULL, plist)) { + vty_out(vty, + "BGP outbound update debugging is already enabled for %s\n", + host); + return CMD_SUCCESS; + } + } + + if (inbound) + bgp_debug_list_add_entry(bgp_debug_update_in_peers, host, NULL, + plist); + else { + struct peer *peer; + struct peer_af *paf; + int afidx; + + bgp_debug_list_add_entry(bgp_debug_update_out_peers, host, NULL, + plist); + peer = bgp_find_peer(vty, host); + + if (peer) { + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; + afidx++) { + paf = peer->peer_af_array[afidx]; + if (paf != NULL) { + if (PAF_SUBGRP(paf)) { + UPDGRP_PEER_DBG_EN( + PAF_SUBGRP(paf) + ->update_group); + } + } + } + } + } + + if (vty->node == CONFIG_NODE) { + if (inbound) + DEBUG_ON(update, UPDATE_IN); + else + DEBUG_ON(update, UPDATE_OUT); + } else { + if (inbound) { + TERM_DEBUG_ON(update, UPDATE_IN); + vty_out(vty, + "BGP updates debugging is on (inbound) for %s\n", + argv[idx_peer]->arg); + } else { + TERM_DEBUG_ON(update, UPDATE_OUT); + vty_out(vty, + "BGP updates debugging is on (outbound) for %s\n", + argv[idx_peer]->arg); + } + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_update_direct, + no_debug_bgp_update_direct_cmd, + "no debug bgp updates ", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n" + "Inbound updates\n" + "Outbound updates\n") +{ + int idx_in_out = 4; + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) { + bgp_debug_list_free(bgp_debug_update_in_peers); + + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(update, UPDATE_IN); + } else { + TERM_DEBUG_OFF(update, UPDATE_IN); + vty_out(vty, + "BGP updates debugging is off (inbound)\n"); + } + } else { + bgp_debug_list_free(bgp_debug_update_out_peers); + + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(update, UPDATE_OUT); + } else { + TERM_DEBUG_OFF(update, UPDATE_OUT); + vty_out(vty, + "BGP updates debugging is off (outbound)\n"); + } + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_update_direct_peer, + no_debug_bgp_update_direct_peer_cmd, + "no debug bgp updates [prefix-list PREFIXLIST_NAME]", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n" + "Inbound updates\n" + "Outbound updates\n" + "BGP neighbor IP address to debug\n" + "BGP IPv6 neighbor to debug\n" + "BGP neighbor on interface to debug\n" + "Use prefix-list to filter prefixes to debug\n" + "Name of prefix-list\n") +{ + int idx_in_out = 4; + int idx_peer = 5; + int inbound; + int found_peer = 0; + const char *host = argv[idx_peer]->arg; + + if (strncmp("i", argv[idx_in_out]->arg, 1) == 0) + inbound = 1; + else + inbound = 0; + + if (inbound && bgp_debug_update_in_peers + && !list_isempty(bgp_debug_update_in_peers)) { + found_peer = bgp_debug_list_remove_entry( + bgp_debug_update_in_peers, host, NULL); + + if (list_isempty(bgp_debug_update_in_peers)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(update, UPDATE_IN); + else { + TERM_DEBUG_OFF(update, UPDATE_IN); + vty_out(vty, + "BGP updates debugging (inbound) is off\n"); + } + } + } + + if (!inbound && bgp_debug_update_out_peers + && !list_isempty(bgp_debug_update_out_peers)) { + found_peer = bgp_debug_list_remove_entry( + bgp_debug_update_out_peers, host, NULL); + + if (list_isempty(bgp_debug_update_out_peers)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(update, UPDATE_OUT); + else { + TERM_DEBUG_OFF(update, UPDATE_OUT); + vty_out(vty, + "BGP updates debugging (outbound) is off\n"); + } + } + + struct peer *peer; + struct peer_af *paf; + int afidx; + peer = bgp_find_peer(vty, host); + + if (peer) { + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; + afidx++) { + paf = peer->peer_af_array[afidx]; + if (paf != NULL) { + if (PAF_SUBGRP(paf)) { + UPDGRP_PEER_DBG_DIS( + PAF_SUBGRP(paf) + ->update_group); + } + } + } + } + } + + if (found_peer) + if (inbound) + vty_out(vty, + "BGP updates debugging (inbound) is off for %s\n", + host); + else + vty_out(vty, + "BGP updates debugging (outbound) is off for %s\n", + host); + else if (inbound) + vty_out(vty, + "BGP updates debugging (inbound) was not enabled for %s\n", + host); + else + vty_out(vty, + "BGP updates debugging (outbound) was not enabled for %s\n", + host); + + return CMD_SUCCESS; +} + +DEFPY (debug_bgp_update_prefix_afi_safi, + debug_bgp_update_prefix_afi_safi_cmd, + "debug bgp updates prefix l2vpn$afi evpn$safi type < mac [ip ]| ip | ip >", + DEBUG_STR + BGP_STR + "BGP updates\n" + "Specify a prefix to debug\n" + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + MAC_STR MAC_STR MAC_STR + IP_STR + "IPv4 address\n" + "IPv6 address\n" + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + IP_STR + "IPv4 address\n" + "IPv6 address\n" + EVPN_TYPE_5_HELP_STR + EVPN_TYPE_5_HELP_STR + IP_STR + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + struct prefix argv_p; + int ret = CMD_SUCCESS; + + ret = bgp_debug_parse_evpn_prefix(vty, argv, argc, &argv_p); + if (ret != CMD_SUCCESS) + return ret; + + if (!bgp_debug_update_prefixes) + bgp_debug_update_prefixes = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_update_prefixes, NULL, &argv_p, + NULL)) { + vty_out(vty, + "BGP updates debugging is already enabled for %pFX\n", + &argv_p); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_update_prefixes, NULL, &argv_p, NULL); + + if (vty->node == CONFIG_NODE) { + DEBUG_ON(update, UPDATE_PREFIX); + } else { + TERM_DEBUG_ON(update, UPDATE_PREFIX); + vty_out(vty, "BGP updates debugging is on for %pFX\n", &argv_p); + } + + return CMD_SUCCESS; +} + +DEFPY (no_debug_bgp_update_prefix_afi_safi, + no_debug_bgp_update_prefix_afi_safi_cmd, + "no debug bgp updates prefix l2vpn$afi evpn$safi type < mac [ip ]| ip | ip >", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n" + "Specify a prefix to debug\n" + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + MAC_STR MAC_STR MAC_STR + IP_STR + "IPv4 address\n" + "IPv6 address\n" + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + IP_STR + "IPv4 address\n" + "IPv6 address\n" + EVPN_TYPE_5_HELP_STR + EVPN_TYPE_5_HELP_STR + IP_STR + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + struct prefix argv_p; + bool found_prefix = false; + int ret = CMD_SUCCESS; + + ret = bgp_debug_parse_evpn_prefix(vty, argv, argc, &argv_p); + if (ret != CMD_SUCCESS) + return ret; + + if (bgp_debug_update_prefixes + && !list_isempty(bgp_debug_update_prefixes)) { + found_prefix = bgp_debug_list_remove_entry( + bgp_debug_update_prefixes, NULL, &argv_p); + + if (list_isempty(bgp_debug_update_prefixes)) { + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(update, UPDATE_PREFIX); + } else { + TERM_DEBUG_OFF(update, UPDATE_PREFIX); + vty_out(vty, + "BGP updates debugging (per prefix) is off\n"); + } + } + } + + if (found_prefix) + vty_out(vty, "BGP updates debugging is off for %pFX\n", + &argv_p); + else + vty_out(vty, "BGP updates debugging was not enabled for %pFX\n", + &argv_p); + + return ret; +} + + +DEFPY (debug_bgp_update_prefix, + debug_bgp_update_prefix_cmd, + "debug bgp updates prefix $prefix", + DEBUG_STR + BGP_STR + "BGP updates\n" + "Specify a prefix to debug\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + if (!bgp_debug_update_prefixes) + bgp_debug_update_prefixes = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_update_prefixes, NULL, prefix, + NULL)) { + vty_out(vty, + "BGP updates debugging is already enabled for %s\n", + prefix_str); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_update_prefixes, NULL, prefix, NULL); + + if (vty->node == CONFIG_NODE) { + DEBUG_ON(update, UPDATE_PREFIX); + } else { + TERM_DEBUG_ON(update, UPDATE_PREFIX); + vty_out(vty, "BGP updates debugging is on for %s\n", + prefix_str); + } + + return CMD_SUCCESS; +} + +DEFPY (no_debug_bgp_update_prefix, + no_debug_bgp_update_prefix_cmd, + "no debug bgp updates prefix $prefix", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n" + "Specify a prefix to debug\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + bool found_prefix = false; + + if (bgp_debug_update_prefixes + && !list_isempty(bgp_debug_update_prefixes)) { + found_prefix = bgp_debug_list_remove_entry( + bgp_debug_update_prefixes, NULL, prefix); + + if (list_isempty(bgp_debug_update_prefixes)) { + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(update, UPDATE_PREFIX); + } else { + TERM_DEBUG_OFF(update, UPDATE_PREFIX); + vty_out(vty, + "BGP updates debugging (per prefix) is off\n"); + } + } + } + + if (found_prefix) + vty_out(vty, "BGP updates debugging is off for %s\n", + prefix_str); + else + vty_out(vty, "BGP updates debugging was not enabled for %s\n", + prefix_str); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_update, + no_debug_bgp_update_cmd, + "no debug bgp updates", + NO_STR + DEBUG_STR + BGP_STR + "BGP updates\n") +{ + struct listnode *ln; + struct bgp *bgp; + + bgp_debug_list_free(bgp_debug_update_in_peers); + bgp_debug_list_free(bgp_debug_update_out_peers); + bgp_debug_list_free(bgp_debug_update_prefixes); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, ln, bgp)) + bgp_debug_clear_updgrp_update_dbg(bgp); + + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(update, UPDATE_IN); + DEBUG_OFF(update, UPDATE_OUT); + DEBUG_OFF(update, UPDATE_PREFIX); + } else { + TERM_DEBUG_OFF(update, UPDATE_IN); + TERM_DEBUG_OFF(update, UPDATE_OUT); + TERM_DEBUG_OFF(update, UPDATE_PREFIX); + vty_out(vty, "BGP updates debugging is off\n"); + } + return CMD_SUCCESS; +} + +/* debug bgp zebra */ +DEFUN (debug_bgp_zebra, + debug_bgp_zebra_cmd, + "debug bgp zebra", + DEBUG_STR + BGP_STR + "BGP Zebra messages\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(zebra, ZEBRA); + else { + TERM_DEBUG_ON(zebra, ZEBRA); + vty_out(vty, "BGP zebra debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_graceful_restart, + debug_bgp_graceful_restart_cmd, + "debug bgp graceful-restart", + DEBUG_STR + BGP_STR + GR_DEBUG) +{ + if (vty->node == CONFIG_NODE) { + DEBUG_ON(graceful_restart, GRACEFUL_RESTART); + } else { + TERM_DEBUG_ON(graceful_restart, GRACEFUL_RESTART); + vty_out(vty, "BGP Graceful Restart debugging is on\n"); + } + return CMD_SUCCESS; +} + + +DEFPY (debug_bgp_zebra_prefix, + debug_bgp_zebra_prefix_cmd, + "debug bgp zebra prefix $prefix", + DEBUG_STR + BGP_STR + "BGP Zebra messages\n" + "Specify a prefix to debug\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + if (!bgp_debug_zebra_prefixes) + bgp_debug_zebra_prefixes = list_new(); + + if (bgp_debug_list_has_entry(bgp_debug_zebra_prefixes, NULL, prefix, + NULL)) { + vty_out(vty, "BGP zebra debugging is already enabled for %s\n", + prefix_str); + return CMD_SUCCESS; + } + + bgp_debug_list_add_entry(bgp_debug_zebra_prefixes, NULL, prefix, NULL); + + if (vty->node == CONFIG_NODE) + DEBUG_ON(zebra, ZEBRA); + else { + TERM_DEBUG_ON(zebra, ZEBRA); + vty_out(vty, "BGP zebra debugging is on for %s\n", prefix_str); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_zebra, + no_debug_bgp_zebra_cmd, + "no debug bgp zebra", + NO_STR + DEBUG_STR + BGP_STR + "BGP Zebra messages\n") +{ + bgp_debug_list_free(bgp_debug_zebra_prefixes); + + if (vty->node == CONFIG_NODE) + DEBUG_OFF(zebra, ZEBRA); + else { + TERM_DEBUG_OFF(zebra, ZEBRA); + vty_out(vty, "BGP zebra debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_graceful_restart, + no_debug_bgp_graceful_restart_cmd, + "no debug bgp graceful-restart", + DEBUG_STR + BGP_STR + GR_DEBUG + NO_STR) +{ + if (vty->node == CONFIG_NODE) { + DEBUG_OFF(graceful_restart, GRACEFUL_RESTART); + } else { + TERM_DEBUG_OFF(graceful_restart, GRACEFUL_RESTART); + vty_out(vty, "BGP Graceful Restart debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFPY (no_debug_bgp_zebra_prefix, + no_debug_bgp_zebra_prefix_cmd, + "no debug bgp zebra prefix $prefix", + NO_STR + DEBUG_STR + BGP_STR + "BGP Zebra messages\n" + "Specify a prefix to debug\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + bool found_prefix = false; + + if (bgp_debug_zebra_prefixes + && !list_isempty(bgp_debug_zebra_prefixes)) { + found_prefix = bgp_debug_list_remove_entry( + bgp_debug_zebra_prefixes, NULL, prefix); + + if (list_isempty(bgp_debug_zebra_prefixes)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(zebra, ZEBRA); + else { + TERM_DEBUG_OFF(zebra, ZEBRA); + vty_out(vty, "BGP zebra debugging is off\n"); + } + } + } + + if (found_prefix) + vty_out(vty, "BGP zebra debugging is off for %s\n", prefix_str); + else + vty_out(vty, "BGP zebra debugging was not enabled for %s\n", + prefix_str); + + return CMD_SUCCESS; +} + +/* debug bgp update-groups */ +DEFUN (debug_bgp_update_groups, + debug_bgp_update_groups_cmd, + "debug bgp update-groups", + DEBUG_STR + BGP_STR + "BGP update-groups\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(update_groups, UPDATE_GROUPS); + else { + TERM_DEBUG_ON(update_groups, UPDATE_GROUPS); + vty_out(vty, "BGP update-groups debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_update_groups, + no_debug_bgp_update_groups_cmd, + "no debug bgp update-groups", + NO_STR + DEBUG_STR + BGP_STR + "BGP update-groups\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(update_groups, UPDATE_GROUPS); + else { + TERM_DEBUG_OFF(update_groups, UPDATE_GROUPS); + vty_out(vty, "BGP update-groups debugging is off\n"); + } + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_vpn, + debug_bgp_vpn_cmd, + "debug bgp vpn ", + DEBUG_STR + BGP_STR + "VPN routes\n" + "leaked from vrf to vpn\n" + "leaked to vrf from vpn\n" + "route-map updates\n" + "labels\n") +{ + int idx = 3; + + if (argv_find(argv, argc, "leak-from-vrf", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_ON(vpn, VPN_LEAK_FROM_VRF); + else + TERM_DEBUG_ON(vpn, VPN_LEAK_FROM_VRF); + } else if (argv_find(argv, argc, "leak-to-vrf", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_ON(vpn, VPN_LEAK_TO_VRF); + else + TERM_DEBUG_ON(vpn, VPN_LEAK_TO_VRF); + } else if (argv_find(argv, argc, "rmap-event", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_ON(vpn, VPN_LEAK_RMAP_EVENT); + else + TERM_DEBUG_ON(vpn, VPN_LEAK_RMAP_EVENT); + } else if (argv_find(argv, argc, "label", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_ON(vpn, VPN_LEAK_LABEL); + else + TERM_DEBUG_ON(vpn, VPN_LEAK_LABEL); + } else { + vty_out(vty, "%% unknown debug bgp vpn keyword\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (vty->node != CONFIG_NODE) + vty_out(vty, "enabled debug bgp vpn %s\n", argv[idx]->text); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_vpn, + no_debug_bgp_vpn_cmd, + "no debug bgp vpn ", + NO_STR + DEBUG_STR + BGP_STR + "VPN routes\n" + "leaked from vrf to vpn\n" + "leaked to vrf from vpn\n" + "route-map updates\n" + "labels\n") +{ + int idx = 4; + + if (argv_find(argv, argc, "leak-from-vrf", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); + else + TERM_DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); + + } else if (argv_find(argv, argc, "leak-to-vrf", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); + else + TERM_DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); + } else if (argv_find(argv, argc, "rmap-event", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); + else + TERM_DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); + } else if (argv_find(argv, argc, "label", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(vpn, VPN_LEAK_LABEL); + else + TERM_DEBUG_OFF(vpn, VPN_LEAK_LABEL); + } else { + vty_out(vty, "%% unknown debug bgp vpn keyword\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (vty->node != CONFIG_NODE) + vty_out(vty, "disabled debug bgp vpn %s\n", argv[idx]->text); + return CMD_SUCCESS; +} + +/* debug bgp pbr */ +DEFUN (debug_bgp_pbr, + debug_bgp_pbr_cmd, + "debug bgp pbr [error]", + DEBUG_STR + BGP_STR + "BGP policy based routing\n" + "BGP PBR error\n") +{ + int idx = 3; + + if (argv_find(argv, argc, "error", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_ON(pbr, PBR_ERROR); + else { + TERM_DEBUG_ON(pbr, PBR_ERROR); + vty_out(vty, "BGP policy based routing error is on\n"); + } + return CMD_SUCCESS; + } + if (vty->node == CONFIG_NODE) + DEBUG_ON(pbr, PBR); + else { + TERM_DEBUG_ON(pbr, PBR); + vty_out(vty, "BGP policy based routing is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_pbr, + no_debug_bgp_pbr_cmd, + "no debug bgp pbr [error]", + NO_STR + DEBUG_STR + BGP_STR + "BGP policy based routing\n" + "BGP PBR Error\n") +{ + int idx = 3; + + if (argv_find(argv, argc, "error", &idx)) { + if (vty->node == CONFIG_NODE) + DEBUG_OFF(pbr, PBR_ERROR); + else { + TERM_DEBUG_OFF(pbr, PBR_ERROR); + vty_out(vty, "BGP policy based routing error is off\n"); + } + return CMD_SUCCESS; + } + if (vty->node == CONFIG_NODE) + DEBUG_OFF(pbr, PBR); + else { + TERM_DEBUG_OFF(pbr, PBR); + vty_out(vty, "BGP policy based routing is off\n"); + } + return CMD_SUCCESS; +} + +DEFPY (debug_bgp_evpn_mh, + debug_bgp_evpn_mh_cmd, + "[no$no] debug bgp evpn mh ", + NO_STR + DEBUG_STR + BGP_STR + "EVPN\n" + "Multihoming\n" + "Ethernet Segment debugging\n" + "Route debugging\n") +{ + if (es) { + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(evpn_mh, EVPN_MH_ES); + else + DEBUG_ON(evpn_mh, EVPN_MH_ES); + } else { + if (no) { + TERM_DEBUG_OFF(evpn_mh, EVPN_MH_ES); + vty_out(vty, + "BGP EVPN-MH ES debugging is off\n"); + } else { + TERM_DEBUG_ON(evpn_mh, EVPN_MH_ES); + vty_out(vty, + "BGP EVPN-MH ES debugging is on\n"); + } + } + } + if (rt) { + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(evpn_mh, EVPN_MH_RT); + else + DEBUG_ON(evpn_mh, EVPN_MH_RT); + } else { + if (no) { + TERM_DEBUG_OFF(evpn_mh, EVPN_MH_RT); + vty_out(vty, + "BGP EVPN-MH route debugging is off\n"); + } else { + TERM_DEBUG_ON(evpn_mh, EVPN_MH_RT); + vty_out(vty, + "BGP EVPN-MH route debugging is on\n"); + } + } + } + + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_labelpool, + debug_bgp_labelpool_cmd, + "debug bgp labelpool", + DEBUG_STR + BGP_STR + "label pool\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(labelpool, LABELPOOL); + else + TERM_DEBUG_ON(labelpool, LABELPOOL); + + if (vty->node != CONFIG_NODE) + vty_out(vty, "enabled debug bgp labelpool\n"); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_labelpool, + no_debug_bgp_labelpool_cmd, + "no debug bgp labelpool", + NO_STR + DEBUG_STR + BGP_STR + "label pool\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(labelpool, LABELPOOL); + else + TERM_DEBUG_OFF(labelpool, LABELPOOL); + + + if (vty->node != CONFIG_NODE) + vty_out(vty, "disabled debug bgp labelpool\n"); + + return CMD_SUCCESS; +} + +DEFPY(debug_bgp_bfd, debug_bgp_bfd_cmd, + "[no] debug bgp bfd", + NO_STR + DEBUG_STR + BGP_STR + "Bidirection Forwarding Detection\n") +{ + if (vty->node == CONFIG_NODE) { + if (no) { + DEBUG_OFF(bfd, BFD_LIB); + bfd_protocol_integration_set_debug(false); + } else { + DEBUG_ON(bfd, BFD_LIB); + bfd_protocol_integration_set_debug(true); + } + } else { + if (no) { + TERM_DEBUG_OFF(bfd, BFD_LIB); + bfd_protocol_integration_set_debug(false); + } else { + TERM_DEBUG_ON(bfd, BFD_LIB); + bfd_protocol_integration_set_debug(true); + } + } + + return CMD_SUCCESS; +} + +DEFPY (debug_bgp_cond_adv, + debug_bgp_cond_adv_cmd, + "[no$no] debug bgp conditional-advertisement", + NO_STR + DEBUG_STR + BGP_STR + "BGP conditional advertisement\n") +{ + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(cond_adv, COND_ADV); + else + DEBUG_ON(cond_adv, COND_ADV); + } else { + if (no) { + TERM_DEBUG_OFF(cond_adv, COND_ADV); + vty_out(vty, + "BGP conditional advertisement debugging is off\n"); + } else { + TERM_DEBUG_ON(cond_adv, COND_ADV); + vty_out(vty, + "BGP conditional advertisement debugging is on\n"); + } + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp, + no_debug_bgp_cmd, + "no debug bgp", + NO_STR + DEBUG_STR + BGP_STR) +{ + struct bgp *bgp; + struct listnode *ln; + + bgp_debug_list_free(bgp_debug_neighbor_events_peers); + bgp_debug_list_free(bgp_debug_keepalive_peers); + bgp_debug_list_free(bgp_debug_update_in_peers); + bgp_debug_list_free(bgp_debug_update_out_peers); + bgp_debug_list_free(bgp_debug_update_prefixes); + bgp_debug_list_free(bgp_debug_bestpath_prefixes); + bgp_debug_list_free(bgp_debug_zebra_prefixes); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, ln, bgp)) + bgp_debug_clear_updgrp_update_dbg(bgp); + + TERM_DEBUG_OFF(keepalive, KEEPALIVE); + TERM_DEBUG_OFF(update, UPDATE_IN); + TERM_DEBUG_OFF(update, UPDATE_OUT); + TERM_DEBUG_OFF(update, UPDATE_PREFIX); + TERM_DEBUG_OFF(bestpath, BESTPATH); + TERM_DEBUG_OFF(as4, AS4); + TERM_DEBUG_OFF(as4, AS4_SEGMENT); + TERM_DEBUG_OFF(neighbor_events, NEIGHBOR_EVENTS); + TERM_DEBUG_OFF(zebra, ZEBRA); + TERM_DEBUG_OFF(nht, NHT); + TERM_DEBUG_OFF(vpn, VPN_LEAK_FROM_VRF); + TERM_DEBUG_OFF(vpn, VPN_LEAK_TO_VRF); + TERM_DEBUG_OFF(vpn, VPN_LEAK_RMAP_EVENT); + TERM_DEBUG_OFF(vpn, VPN_LEAK_LABEL); + TERM_DEBUG_OFF(flowspec, FLOWSPEC); + TERM_DEBUG_OFF(labelpool, LABELPOOL); + TERM_DEBUG_OFF(pbr, PBR); + TERM_DEBUG_OFF(pbr, PBR_ERROR); + TERM_DEBUG_OFF(graceful_restart, GRACEFUL_RESTART); + TERM_DEBUG_OFF(evpn_mh, EVPN_MH_ES); + TERM_DEBUG_OFF(evpn_mh, EVPN_MH_RT); + TERM_DEBUG_OFF(bfd, BFD_LIB); + TERM_DEBUG_OFF(cond_adv, COND_ADV); + + vty_out(vty, "All possible debugging has been turned off\n"); + + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_bgp, + show_debugging_bgp_cmd, + "show debugging [bgp]", + SHOW_STR + DEBUG_STR + BGP_STR) +{ + vty_out(vty, "BGP debugging status:\n"); + + if (BGP_DEBUG(as4, AS4)) + vty_out(vty, " BGP as4 debugging is on\n"); + + if (BGP_DEBUG(as4, AS4_SEGMENT)) + vty_out(vty, " BGP as4 aspath segment debugging is on\n"); + + if (BGP_DEBUG(bestpath, BESTPATH)) + bgp_debug_list_print(vty, " BGP bestpath debugging is on", + bgp_debug_bestpath_prefixes); + + if (BGP_DEBUG(keepalive, KEEPALIVE)) + bgp_debug_list_print(vty, " BGP keepalives debugging is on", + bgp_debug_keepalive_peers); + + if (BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS)) + bgp_debug_list_print(vty, + " BGP neighbor-events debugging is on", + bgp_debug_neighbor_events_peers); + + if (BGP_DEBUG(nht, NHT)) + vty_out(vty, " BGP next-hop tracking debugging is on\n"); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + vty_out(vty, " BGP update-groups debugging is on\n"); + + if (BGP_DEBUG(update, UPDATE_PREFIX)) + bgp_debug_list_print(vty, " BGP updates debugging is on", + bgp_debug_update_prefixes); + + if (BGP_DEBUG(update, UPDATE_IN)) + bgp_debug_list_print(vty, + " BGP updates debugging is on (inbound)", + bgp_debug_update_in_peers); + + if (BGP_DEBUG(update, UPDATE_OUT)) + bgp_debug_list_print(vty, + " BGP updates debugging is on (outbound)", + bgp_debug_update_out_peers); + + if (BGP_DEBUG(zebra, ZEBRA)) + bgp_debug_list_print(vty, " BGP zebra debugging is on", + bgp_debug_zebra_prefixes); + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + vty_out(vty, " BGP graceful-restart debugging is on\n"); + + if (BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)) + vty_out(vty, + " BGP route leak from vrf to vpn debugging is on\n"); + if (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF)) + vty_out(vty, + " BGP route leak to vrf from vpn debugging is on\n"); + if (BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT)) + vty_out(vty, " BGP vpn route-map event debugging is on\n"); + if (BGP_DEBUG(vpn, VPN_LEAK_LABEL)) + vty_out(vty, " BGP vpn label event debugging is on\n"); + if (BGP_DEBUG(flowspec, FLOWSPEC)) + vty_out(vty, " BGP flowspec debugging is on\n"); + if (BGP_DEBUG(labelpool, LABELPOOL)) + vty_out(vty, " BGP labelpool debugging is on\n"); + + if (BGP_DEBUG(pbr, PBR)) + vty_out(vty, " BGP policy based routing debugging is on\n"); + if (BGP_DEBUG(pbr, PBR_ERROR)) + vty_out(vty, " BGP policy based routing error debugging is on\n"); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + vty_out(vty, " BGP EVPN-MH ES debugging is on\n"); + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + vty_out(vty, " BGP EVPN-MH route debugging is on\n"); + + if (BGP_DEBUG(bfd, BFD_LIB)) + vty_out(vty, " BGP BFD library debugging is on\n"); + + if (BGP_DEBUG(cond_adv, COND_ADV)) + vty_out(vty, + " BGP conditional advertisement debugging is on\n"); + + cmd_show_lib_debugs(vty); + + hook_call(bgp_hook_config_write_debug, vty, false); + + return CMD_SUCCESS; +} + +static int bgp_config_write_debug(struct vty *vty) +{ + int write = 0; + + if (CONF_BGP_DEBUG(as4, AS4)) { + vty_out(vty, "debug bgp as4\n"); + write++; + } + + if (CONF_BGP_DEBUG(as4, AS4_SEGMENT)) { + vty_out(vty, "debug bgp as4 segment\n"); + write++; + } + + if (CONF_BGP_DEBUG(bestpath, BESTPATH)) { + write += bgp_debug_list_conf_print(vty, "debug bgp bestpath", + bgp_debug_bestpath_prefixes); + } + + if (CONF_BGP_DEBUG(keepalive, KEEPALIVE)) { + write += bgp_debug_list_conf_print(vty, "debug bgp keepalives", + bgp_debug_keepalive_peers); + } + + if (CONF_BGP_DEBUG(neighbor_events, NEIGHBOR_EVENTS)) { + write += bgp_debug_list_conf_print( + vty, "debug bgp neighbor-events", + bgp_debug_neighbor_events_peers); + } + + if (CONF_BGP_DEBUG(nht, NHT)) { + vty_out(vty, "debug bgp nht\n"); + write++; + } + + if (CONF_BGP_DEBUG(update_groups, UPDATE_GROUPS)) { + vty_out(vty, "debug bgp update-groups\n"); + write++; + } + + if (CONF_BGP_DEBUG(update, UPDATE_PREFIX)) { + write += bgp_debug_list_conf_print(vty, + "debug bgp updates prefix", + bgp_debug_update_prefixes); + } + + if (CONF_BGP_DEBUG(update, UPDATE_IN)) { + write += bgp_debug_list_conf_print(vty, "debug bgp updates in", + bgp_debug_update_in_peers); + } + + if (CONF_BGP_DEBUG(update, UPDATE_OUT)) { + write += bgp_debug_list_conf_print(vty, "debug bgp updates out", + bgp_debug_update_out_peers); + } + + if (CONF_BGP_DEBUG(update, UPDATE_DETAIL)) { + vty_out(vty, "debug bgp updates detail\n"); + write++; + } + + if (CONF_BGP_DEBUG(zebra, ZEBRA)) { + if (!bgp_debug_zebra_prefixes + || list_isempty(bgp_debug_zebra_prefixes)) { + vty_out(vty, "debug bgp zebra\n"); + write++; + } else { + write += bgp_debug_list_conf_print( + vty, "debug bgp zebra prefix", + bgp_debug_zebra_prefixes); + } + } + + if (CONF_BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)) { + vty_out(vty, "debug bgp vpn leak-from-vrf\n"); + write++; + } + if (CONF_BGP_DEBUG(vpn, VPN_LEAK_TO_VRF)) { + vty_out(vty, "debug bgp vpn leak-to-vrf\n"); + write++; + } + if (CONF_BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT)) { + vty_out(vty, "debug bgp vpn rmap-event\n"); + write++; + } + if (CONF_BGP_DEBUG(vpn, VPN_LEAK_LABEL)) { + vty_out(vty, "debug bgp vpn label\n"); + write++; + } + if (CONF_BGP_DEBUG(flowspec, FLOWSPEC)) { + vty_out(vty, "debug bgp flowspec\n"); + write++; + } + if (CONF_BGP_DEBUG(labelpool, LABELPOOL)) { + vty_out(vty, "debug bgp labelpool\n"); + write++; + } + + if (CONF_BGP_DEBUG(pbr, PBR)) { + vty_out(vty, "debug bgp pbr\n"); + write++; + } + if (CONF_BGP_DEBUG(pbr, PBR_ERROR)) { + vty_out(vty, "debug bgp pbr error\n"); + write++; + } + + if (CONF_BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) { + vty_out(vty, "debug bgp graceful-restart\n"); + write++; + } + + if (CONF_BGP_DEBUG(evpn_mh, EVPN_MH_ES)) { + vty_out(vty, "debug bgp evpn mh es\n"); + write++; + } + if (CONF_BGP_DEBUG(evpn_mh, EVPN_MH_RT)) { + vty_out(vty, "debug bgp evpn mh route\n"); + write++; + } + + if (CONF_BGP_DEBUG(bfd, BFD_LIB)) { + vty_out(vty, "debug bgp bfd\n"); + write++; + } + + if (CONF_BGP_DEBUG(cond_adv, COND_ADV)) { + vty_out(vty, "debug bgp conditional-advertisement\n"); + write++; + } + + if (hook_call(bgp_hook_config_write_debug, vty, true)) + write++; + + return write; +} + +static int bgp_config_write_debug(struct vty *vty); +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = bgp_config_write_debug, +}; + +void bgp_debug_init(void) +{ + install_node(&debug_node); + + install_element(ENABLE_NODE, &show_debugging_bgp_cmd); + + install_element(ENABLE_NODE, &debug_bgp_as4_cmd); + install_element(CONFIG_NODE, &debug_bgp_as4_cmd); + install_element(ENABLE_NODE, &debug_bgp_as4_segment_cmd); + install_element(CONFIG_NODE, &debug_bgp_as4_segment_cmd); + + install_element(ENABLE_NODE, &debug_bgp_neighbor_events_cmd); + install_element(CONFIG_NODE, &debug_bgp_neighbor_events_cmd); + install_element(ENABLE_NODE, &debug_bgp_nht_cmd); + install_element(CONFIG_NODE, &debug_bgp_nht_cmd); + install_element(ENABLE_NODE, &debug_bgp_keepalive_cmd); + install_element(CONFIG_NODE, &debug_bgp_keepalive_cmd); + install_element(ENABLE_NODE, &debug_bgp_update_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_cmd); + install_element(ENABLE_NODE, &debug_bgp_update_detail_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_detail_cmd); + install_element(ENABLE_NODE, &debug_bgp_zebra_cmd); + install_element(CONFIG_NODE, &debug_bgp_zebra_cmd); + install_element(ENABLE_NODE, &debug_bgp_update_groups_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_groups_cmd); + install_element(ENABLE_NODE, &debug_bgp_bestpath_prefix_cmd); + install_element(CONFIG_NODE, &debug_bgp_bestpath_prefix_cmd); + + install_element(ENABLE_NODE, &debug_bgp_graceful_restart_cmd); + install_element(CONFIG_NODE, &debug_bgp_graceful_restart_cmd); + + /* debug bgp updates (in|out) */ + install_element(ENABLE_NODE, &debug_bgp_update_direct_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_direct_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_direct_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_direct_cmd); + + /* debug bgp updates (in|out) A.B.C.D */ + install_element(ENABLE_NODE, &debug_bgp_update_direct_peer_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_direct_peer_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_direct_peer_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_direct_peer_cmd); + + /* debug bgp updates prefix A.B.C.D/M */ + install_element(ENABLE_NODE, &debug_bgp_update_prefix_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_prefix_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_prefix_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_prefix_cmd); + install_element(ENABLE_NODE, &debug_bgp_update_prefix_afi_safi_cmd); + install_element(CONFIG_NODE, &debug_bgp_update_prefix_afi_safi_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_prefix_afi_safi_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_prefix_afi_safi_cmd); + + /* debug bgp zebra prefix A.B.C.D/M */ + install_element(ENABLE_NODE, &debug_bgp_zebra_prefix_cmd); + install_element(CONFIG_NODE, &debug_bgp_zebra_prefix_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_zebra_prefix_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_zebra_prefix_cmd); + + install_element(ENABLE_NODE, &no_debug_bgp_as4_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_as4_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_as4_segment_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_as4_segment_cmd); + + /* debug bgp neighbor-events A.B.C.D */ + install_element(ENABLE_NODE, &debug_bgp_neighbor_events_peer_cmd); + install_element(CONFIG_NODE, &debug_bgp_neighbor_events_peer_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_neighbor_events_peer_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_neighbor_events_peer_cmd); + + /* debug bgp keepalive A.B.C.D */ + install_element(ENABLE_NODE, &debug_bgp_keepalive_peer_cmd); + install_element(CONFIG_NODE, &debug_bgp_keepalive_peer_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_keepalive_peer_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_keepalive_peer_cmd); + + install_element(ENABLE_NODE, &no_debug_bgp_neighbor_events_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_neighbor_events_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_nht_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_nht_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_keepalive_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_keepalive_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_zebra_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_zebra_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_update_groups_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_update_groups_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_bestpath_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_bestpath_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_bestpath_prefix_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_bestpath_prefix_cmd); + + install_element(ENABLE_NODE, &no_debug_bgp_graceful_restart_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_graceful_restart_cmd); + + install_element(ENABLE_NODE, &debug_bgp_vpn_cmd); + install_element(CONFIG_NODE, &debug_bgp_vpn_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_vpn_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_vpn_cmd); + + install_element(ENABLE_NODE, &debug_bgp_labelpool_cmd); + install_element(CONFIG_NODE, &debug_bgp_labelpool_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_labelpool_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_labelpool_cmd); + + /* debug bgp pbr */ + install_element(ENABLE_NODE, &debug_bgp_pbr_cmd); + install_element(CONFIG_NODE, &debug_bgp_pbr_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_pbr_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_pbr_cmd); + + install_element(ENABLE_NODE, &debug_bgp_evpn_mh_cmd); + install_element(CONFIG_NODE, &debug_bgp_evpn_mh_cmd); + + /* debug bgp bfd */ + install_element(ENABLE_NODE, &debug_bgp_bfd_cmd); + install_element(CONFIG_NODE, &debug_bgp_bfd_cmd); + + /* debug bgp conditional advertisement */ + install_element(ENABLE_NODE, &debug_bgp_cond_adv_cmd); + install_element(CONFIG_NODE, &debug_bgp_cond_adv_cmd); +} + +/* Return true if this prefix is on the per_prefix_list of prefixes to debug + * for BGP_DEBUG_TYPE + */ +static int bgp_debug_per_prefix(const struct prefix *p, + unsigned long term_bgp_debug_type, + unsigned int BGP_DEBUG_TYPE, + struct list *per_prefix_list) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + if (term_bgp_debug_type & BGP_DEBUG_TYPE) { + /* We are debugging all prefixes so return true */ + if (!per_prefix_list || list_isempty(per_prefix_list)) + return 1; + + else { + if (!p) + return 0; + + for (ALL_LIST_ELEMENTS(per_prefix_list, node, nnode, + filter)) + if (filter->p->prefixlen == p->prefixlen + && prefix_match(filter->p, p)) + return 1; + + return 0; + } + } + + return 0; +} + +/* Return true if this peer is on the per_peer_list of peers to debug + * for BGP_DEBUG_TYPE + */ +static bool bgp_debug_per_peer(char *host, const struct prefix *p, + unsigned long term_bgp_debug_type, + unsigned int BGP_DEBUG_TYPE, + struct list *per_peer_list) +{ + struct bgp_debug_filter *filter; + struct listnode *node, *nnode; + + if (term_bgp_debug_type & BGP_DEBUG_TYPE) { + /* We are debugging all peers so return true */ + if (!per_peer_list || list_isempty(per_peer_list)) + return true; + + if (!host) + return false; + + for (ALL_LIST_ELEMENTS(per_peer_list, node, nnode, filter)) + if (strmatch(filter->host, host) && + filter->plist_name && p) { + struct prefix_list *plist; + afi_t afi = family2afi(p->family); + + plist = (afi == AFI_IP) ? filter->plist_v4 + : filter->plist_v6; + + if (!plist) + continue; + + return prefix_list_apply(plist, p) == + PREFIX_PERMIT; + } else if (strmatch(filter->host, host)) { + return true; + } + + return false; + } + + return false; +} + +bool bgp_debug_neighbor_events(const struct peer *peer) +{ + char *host = NULL; + + if (peer) + host = peer->host; + + return bgp_debug_per_peer(host, NULL, term_bgp_debug_neighbor_events, + BGP_DEBUG_NEIGHBOR_EVENTS, + bgp_debug_neighbor_events_peers); +} + +bool bgp_debug_keepalive(const struct peer *peer) +{ + char *host = NULL; + + if (peer) + host = peer->host; + + return bgp_debug_per_peer(host, NULL, term_bgp_debug_keepalive, + BGP_DEBUG_KEEPALIVE, + bgp_debug_keepalive_peers); +} + +bool bgp_debug_update(const struct peer *peer, const struct prefix *p, + struct update_group *updgrp, unsigned int inbound) +{ + char *host = NULL; + + if (peer) + host = peer->host; + + if (inbound) { + if (bgp_debug_per_peer(host, p, term_bgp_debug_update, + BGP_DEBUG_UPDATE_IN, + bgp_debug_update_in_peers)) + return true; + } + + /* outbound */ + else { + if (bgp_debug_per_peer(host, p, term_bgp_debug_update, + BGP_DEBUG_UPDATE_OUT, + bgp_debug_update_out_peers)) + return true; + + /* Check if update debugging implicitly enabled for the group. + */ + if (updgrp && UPDGRP_DBG_ON(updgrp)) + return true; + } + + + if (BGP_DEBUG(update, UPDATE_PREFIX)) { + if (bgp_debug_per_prefix(p, term_bgp_debug_update, + BGP_DEBUG_UPDATE_PREFIX, + bgp_debug_update_prefixes)) + return true; + } + + return false; +} + +bool bgp_debug_bestpath(struct bgp_dest *dest) +{ + if (BGP_DEBUG(bestpath, BESTPATH)) { + if (bgp_debug_per_prefix( + bgp_dest_get_prefix(dest), term_bgp_debug_bestpath, + BGP_DEBUG_BESTPATH, bgp_debug_bestpath_prefixes)) + return true; + } + + return false; +} + +bool bgp_debug_zebra(const struct prefix *p) +{ + if (BGP_DEBUG(zebra, ZEBRA)) { + if (bgp_debug_per_prefix(p, term_bgp_debug_zebra, + BGP_DEBUG_ZEBRA, + bgp_debug_zebra_prefixes)) + return true; + } + + return false; +} + +const char *bgp_debug_rdpfxpath2str(afi_t afi, safi_t safi, + const struct prefix_rd *prd, + union prefixconstptr pu, + mpls_label_t *label, uint8_t num_labels, + int addpath_valid, uint32_t addpath_id, + struct bgp_route_evpn *overlay_index, + char *str, int size) +{ + char tag_buf[30]; + char overlay_index_buf[INET6_ADDRSTRLEN + 14]; + const struct prefix_evpn *evp; + int len = 0; + + /* ' with addpath ID ' 17 + * max strlen of uint32 + 10 + * +/- (just in case) + 1 + * null terminator + 1 + * ============================ 29 */ + char pathid_buf[30]; + + if (size < BGP_PRD_PATH_STRLEN) + return NULL; + + /* Note: Path-id is created by default, but only included in update + * sometimes. */ + pathid_buf[0] = '\0'; + if (addpath_valid) + snprintf(pathid_buf, sizeof(pathid_buf), " with addpath ID %u", + addpath_id); + + overlay_index_buf[0] = '\0'; + if (overlay_index && overlay_index->type == OVERLAY_INDEX_GATEWAY_IP) { + char obuf[INET6_ADDRSTRLEN]; + + obuf[0] = '\0'; + evp = pu.evp; + if (is_evpn_prefix_ipaddr_v4(evp)) + inet_ntop(AF_INET, &overlay_index->gw_ip, obuf, + sizeof(obuf)); + else if (is_evpn_prefix_ipaddr_v6(evp)) + inet_ntop(AF_INET6, &overlay_index->gw_ip, obuf, + sizeof(obuf)); + + snprintf(overlay_index_buf, sizeof(overlay_index_buf), + " gateway IP %s", obuf); + } + + tag_buf[0] = '\0'; + if (bgp_labeled_safi(safi) && num_labels) { + + if (safi == SAFI_EVPN) { + char tag_buf2[20]; + + bgp_evpn_label2str(label, num_labels, tag_buf2, 20); + snprintf(tag_buf, sizeof(tag_buf), " label %s", + tag_buf2); + } else { + uint32_t label_value; + + label_value = decode_label(label); + snprintf(tag_buf, sizeof(tag_buf), " label %u", + label_value); + } + } + + if (prd) { + len += snprintfrr(str + len, size - len, "RD "); + len += snprintfrr(str + len, size - len, + BGP_RD_AS_FORMAT(bgp_get_asnotation(NULL)), + prd); + snprintfrr(str + len, size - len, " %pFX%s%s%s %s %s", pu.p, + overlay_index_buf, tag_buf, pathid_buf, afi2str(afi), + safi2str(safi)); + } else if (safi == SAFI_FLOWSPEC) { + char return_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; + const struct prefix_fs *fs = pu.fs; + + bgp_fs_nlri_get_string((unsigned char *)fs->prefix.ptr, + fs->prefix.prefixlen, + return_string, + NLRI_STRING_FORMAT_DEBUG, NULL, + family2afi(fs->prefix.family)); + snprintf(str, size, "FS %s Match{%s}", afi2str(afi), + return_string); + } else + snprintfrr(str, size, "%pFX%s%s %s %s", pu.p, tag_buf, + pathid_buf, afi2str(afi), safi2str(safi)); + + return str; +} diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h new file mode 100644 index 0000000..061d966 --- /dev/null +++ b/bgpd/bgp_debug.h @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP message debug header. + * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_DEBUG_H +#define _QUAGGA_BGP_DEBUG_H + +#include "hook.h" +#include "vty.h" + +#include "bgp_attr.h" +#include "bgp_updgrp.h" + +DECLARE_HOOK(bgp_hook_config_write_debug, (struct vty *vty, bool running), + (vty, running)); + +/* sort of packet direction */ +#define DUMP_ON 1 +#define DUMP_SEND 2 +#define DUMP_RECV 4 + +/* for dump_update */ +#define DUMP_WITHDRAW 8 +#define DUMP_NLRI 16 + +/* dump detail */ +#define DUMP_DETAIL 32 + +/* RD + Prefix + Path-Id */ +#define BGP_PRD_PATH_STRLEN \ + (PREFIX_STRLEN + RD_ADDRSTRLEN + INET6_ADDRSTRLEN + 34) + +extern int dump_open; +extern int dump_update; +extern int dump_keepalive; +extern int dump_notify; + +extern int Debug_Event; +extern int Debug_Keepalive; +extern int Debug_Update; +extern int Debug_Radix; + +#define NLRI 1 +#define WITHDRAW 2 +#define NO_OPT 3 +#define SEND 4 +#define RECV 5 +#define DETAIL 6 + +/* Prototypes. */ +extern void bgp_debug_init(void); +extern void bgp_packet_dump(struct stream *); + +extern int debug(unsigned int option); + +extern unsigned long conf_bgp_debug_as4; +extern unsigned long conf_bgp_debug_neighbor_events; +extern unsigned long conf_bgp_debug_packet; +extern unsigned long conf_bgp_debug_keepalive; +extern unsigned long conf_bgp_debug_update; +extern unsigned long conf_bgp_debug_bestpath; +extern unsigned long conf_bgp_debug_zebra; +extern unsigned long conf_bgp_debug_nht; +extern unsigned long conf_bgp_debug_update_groups; +extern unsigned long conf_bgp_debug_vpn; +extern unsigned long conf_bgp_debug_flowspec; +extern unsigned long conf_bgp_debug_labelpool; +extern unsigned long conf_bgp_debug_pbr; +extern unsigned long conf_bgp_debug_graceful_restart; +extern unsigned long conf_bgp_debug_evpn_mh; +extern unsigned long conf_bgp_debug_bfd; +extern unsigned long conf_bgp_debug_cond_adv; + +extern unsigned long term_bgp_debug_as4; +extern unsigned long term_bgp_debug_neighbor_events; +extern unsigned long term_bgp_debug_packet; +extern unsigned long term_bgp_debug_keepalive; +extern unsigned long term_bgp_debug_update; +extern unsigned long term_bgp_debug_bestpath; +extern unsigned long term_bgp_debug_zebra; +extern unsigned long term_bgp_debug_nht; +extern unsigned long term_bgp_debug_update_groups; +extern unsigned long term_bgp_debug_vpn; +extern unsigned long term_bgp_debug_flowspec; +extern unsigned long term_bgp_debug_labelpool; +extern unsigned long term_bgp_debug_pbr; +extern unsigned long term_bgp_debug_graceful_restart; +extern unsigned long term_bgp_debug_evpn_mh; +extern unsigned long term_bgp_debug_bfd; +extern unsigned long term_bgp_debug_cond_adv; + +extern struct list *bgp_debug_neighbor_events_peers; +extern struct list *bgp_debug_keepalive_peers; +extern struct list *bgp_debug_update_in_peers; +extern struct list *bgp_debug_update_out_peers; +extern struct list *bgp_debug_update_prefixes; +extern struct list *bgp_debug_bestpath_prefixes; +extern struct list *bgp_debug_zebra_prefixes; + +struct bgp_debug_filter { + char *host; + char *plist_name; + struct prefix_list *plist_v4; + struct prefix_list *plist_v6; + struct prefix *p; +}; + +#define BGP_DEBUG_AS4 0x01 +#define BGP_DEBUG_AS4_SEGMENT 0x02 + +#define BGP_DEBUG_BESTPATH 0x01 +#define BGP_DEBUG_NEIGHBOR_EVENTS 0x01 +#define BGP_DEBUG_PACKET 0x01 +#define BGP_DEBUG_KEEPALIVE 0x01 +#define BGP_DEBUG_UPDATE_IN 0x01 +#define BGP_DEBUG_UPDATE_OUT 0x02 +#define BGP_DEBUG_UPDATE_PREFIX 0x04 +#define BGP_DEBUG_UPDATE_DETAIL 0x08 +#define BGP_DEBUG_ZEBRA 0x01 +#define BGP_DEBUG_NHT 0x01 +#define BGP_DEBUG_UPDATE_GROUPS 0x01 +#define BGP_DEBUG_VPN_LEAK_FROM_VRF 0x01 +#define BGP_DEBUG_VPN_LEAK_TO_VRF 0x02 +#define BGP_DEBUG_VPN_LEAK_RMAP_EVENT 0x04 +#define BGP_DEBUG_VPN_LEAK_LABEL 0x08 +#define BGP_DEBUG_FLOWSPEC 0x01 +#define BGP_DEBUG_LABELPOOL 0x01 +#define BGP_DEBUG_PBR 0x01 +#define BGP_DEBUG_PBR_ERROR 0x02 +#define BGP_DEBUG_EVPN_MH_ES 0x01 +#define BGP_DEBUG_EVPN_MH_RT 0x02 + +#define BGP_DEBUG_GRACEFUL_RESTART 0x01 + +#define BGP_DEBUG_BFD_LIB 0x01 +#define BGP_DEBUG_COND_ADV 0x01 + +#define CONF_DEBUG_ON(a, b) (conf_bgp_debug_ ## a |= (BGP_DEBUG_ ## b)) +#define CONF_DEBUG_OFF(a, b) (conf_bgp_debug_ ## a &= ~(BGP_DEBUG_ ## b)) + +#define TERM_DEBUG_ON(a, b) (term_bgp_debug_ ## a |= (BGP_DEBUG_ ## b)) +#define TERM_DEBUG_OFF(a, b) (term_bgp_debug_ ## a &= ~(BGP_DEBUG_ ## b)) + +#define DEBUG_ON(a, b) \ + do { \ + CONF_DEBUG_ON(a, b); \ + TERM_DEBUG_ON(a, b); \ + } while (0) +#define DEBUG_OFF(a, b) \ + do { \ + CONF_DEBUG_OFF(a, b); \ + TERM_DEBUG_OFF(a, b); \ + } while (0) + +#define BGP_DEBUG(a, b) (unlikely(term_bgp_debug_##a & BGP_DEBUG_##b)) +#define CONF_BGP_DEBUG(a, b) (unlikely(conf_bgp_debug_##a & BGP_DEBUG_##b)) + +extern const char *const bgp_type_str[]; + +extern bool bgp_dump_attr(struct attr *attr, char *buf, size_t size); +extern bool bgp_debug_peer_updout_enabled(char *host); +extern const char *bgp_notify_code_str(char code); +extern const char *bgp_notify_subcode_str(char code, char subcode); +extern void bgp_notify_print(struct peer *peer, struct bgp_notify *bgp_notify, + const char *direct, bool hard_reset); + +extern const struct message bgp_status_msg[]; +extern bool bgp_debug_neighbor_events(const struct peer *peer); +extern bool bgp_debug_keepalive(const struct peer *peer); +extern bool bgp_debug_update(const struct peer *peer, const struct prefix *p, + struct update_group *updgrp, unsigned int inbound); +extern bool bgp_debug_bestpath(struct bgp_dest *dest); +extern bool bgp_debug_zebra(const struct prefix *p); + +extern const char *bgp_debug_rdpfxpath2str( + afi_t afi, safi_t safi, const struct prefix_rd *prd, + union prefixconstptr pu, mpls_label_t *label, uint8_t num_labels, + int addpath_valid, uint32_t addpath_id, + struct bgp_route_evpn *overlay_index, char *str, int size); +const char *bgp_notify_admin_message(char *buf, size_t bufsz, uint8_t *data, + size_t datalen); + +#endif /* _QUAGGA_BGP_DEBUG_H */ diff --git a/bgpd/bgp_dump.c b/bgpd/bgp_dump.c new file mode 100644 index 0000000..53b5212 --- /dev/null +++ b/bgpd/bgp_dump.c @@ -0,0 +1,876 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP-4 dump routine + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include +#include + +#include "log.h" +#include "stream.h" +#include "sockunion.h" +#include "command.h" +#include "prefix.h" +#include "frrevent.h" +#include "linklist.h" +#include "queue.h" +#include "memory.h" +#include "filter.h" + +#include "bgpd/bgp_table.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_packet.h" + +enum bgp_dump_type { + BGP_DUMP_ALL, + BGP_DUMP_ALL_ET, + BGP_DUMP_UPDATES, + BGP_DUMP_UPDATES_ET, + BGP_DUMP_ROUTES +}; + +static const struct bgp_dump_type_map { + enum bgp_dump_type type; + const char *str; +} bgp_dump_type_map[] = { + {BGP_DUMP_ALL, "all"}, {BGP_DUMP_ALL_ET, "all-et"}, + {BGP_DUMP_UPDATES, "updates"}, {BGP_DUMP_UPDATES_ET, "updates-et"}, + {BGP_DUMP_ROUTES, "routes-mrt"}, {0, NULL}, +}; + +enum MRT_MSG_TYPES { + MSG_NULL, + MSG_START, /* sender is starting up */ + MSG_DIE, /* receiver should shut down */ + MSG_I_AM_DEAD, /* sender is shutting down */ + MSG_PEER_DOWN, /* sender's peer is down */ + MSG_PROTOCOL_BGP, /* msg is a BGP packet */ + MSG_PROTOCOL_RIP, /* msg is a RIP packet */ + MSG_PROTOCOL_IDRP, /* msg is an IDRP packet */ + MSG_PROTOCOL_RIPNG, /* msg is a RIPNG packet */ + MSG_PROTOCOL_BGP4PLUS, /* msg is a BGP4+ packet */ + MSG_PROTOCOL_BGP4PLUS_01, /* msg is a BGP4+ (draft 01) packet */ + MSG_PROTOCOL_OSPF, /* msg is an OSPF packet */ + MSG_TABLE_DUMP, /* routing table dump */ + MSG_TABLE_DUMP_V2 /* routing table dump, version 2 */ +}; + +struct bgp_dump { + enum bgp_dump_type type; + + char *filename; + + FILE *fp; + + unsigned int interval; + + char *interval_str; + + struct event *t_interval; +}; + +static int bgp_dump_unset(struct bgp_dump *bgp_dump); +static void bgp_dump_interval_func(struct event *); + +/* BGP packet dump output buffer. */ +struct stream *bgp_dump_obuf; + +/* BGP dump strucuture for 'dump bgp all' */ +struct bgp_dump bgp_dump_all; + +/* BGP dump structure for 'dump bgp updates' */ +struct bgp_dump bgp_dump_updates; + +/* BGP dump structure for 'dump bgp routes' */ +struct bgp_dump bgp_dump_routes; + +static FILE *bgp_dump_open_file(struct bgp_dump *bgp_dump) +{ + int ret; + time_t clock; + struct tm tm; + char fullpath[MAXPATHLEN]; + char realpath[MAXPATHLEN]; + mode_t oldumask; + + time(&clock); + localtime_r(&clock, &tm); + + if (bgp_dump->filename[0] != DIRECTORY_SEP) { + snprintf(fullpath, sizeof(fullpath), "%s/%s", vty_get_cwd(), + bgp_dump->filename); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* user supplied date/time format string */ + ret = strftime(realpath, MAXPATHLEN, fullpath, &tm); + } else + ret = strftime(realpath, MAXPATHLEN, bgp_dump->filename, &tm); +#pragma GCC diagnostic pop + + if (ret == 0) { + flog_warn(EC_BGP_DUMP, "%s: strftime error", __func__); + return NULL; + } + + if (bgp_dump->fp) + fclose(bgp_dump->fp); + + + oldumask = umask(0777 & ~LOGFILE_MASK); + bgp_dump->fp = fopen(realpath, "w"); + + if (bgp_dump->fp == NULL) { + flog_warn(EC_BGP_DUMP, "%s: %s: %s", __func__, realpath, + strerror(errno)); + umask(oldumask); + return NULL; + } + umask(oldumask); + + return bgp_dump->fp; +} + +static int bgp_dump_interval_add(struct bgp_dump *bgp_dump, int interval) +{ + int secs_into_day; + time_t t; + struct tm tm; + + if (interval > 0) { + /* Periodic dump every interval seconds */ + if ((interval < 86400) && ((86400 % interval) == 0)) { + /* Dump at predictable times: if a day has a whole + * number of + * intervals, dump every interval seconds starting from + * midnight + */ + (void)time(&t); + localtime_r(&t, &tm); + secs_into_day = tm.tm_sec + 60 * tm.tm_min + + 60 * 60 * tm.tm_hour; + interval = interval + - secs_into_day % interval; /* always > 0 */ + } + event_add_timer(bm->master, bgp_dump_interval_func, bgp_dump, + interval, &bgp_dump->t_interval); + } else { + /* One-off dump: execute immediately, don't affect any scheduled + * dumps */ + event_add_event(bm->master, bgp_dump_interval_func, bgp_dump, 0, + &bgp_dump->t_interval); + } + + return 0; +} + +/* Dump common header. */ +static void bgp_dump_header(struct stream *obuf, int type, int subtype, + int dump_type) +{ + struct timeval clock; + long msecs; + time_t secs; + + if ((dump_type == BGP_DUMP_ALL_ET || dump_type == BGP_DUMP_UPDATES_ET) + && type == MSG_PROTOCOL_BGP4MP) + type = MSG_PROTOCOL_BGP4MP_ET; + + gettimeofday(&clock, NULL); + + secs = clock.tv_sec; + msecs = clock.tv_usec; + + /* Put dump packet header. */ + stream_putl(obuf, secs); + stream_putw(obuf, type); + stream_putw(obuf, subtype); + stream_putl(obuf, 0); /* len */ + + /* Adding microseconds for the MRT Extended Header */ + if (type == MSG_PROTOCOL_BGP4MP_ET) + stream_putl(obuf, msecs); +} + +static void bgp_dump_set_size(struct stream *s, int type) +{ + /* + * The BGP_DUMP_HEADER_SIZE stay at 12 event when ET: + * "The Microsecond Timestamp is included in the computation + * of the Length field value." (RFC6396 2011) + */ + stream_putl_at(s, 8, stream_get_endp(s) - BGP_DUMP_HEADER_SIZE); +} + +static void bgp_dump_routes_index_table(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node; + uint16_t peerno = 1; + struct stream *obuf; + + obuf = bgp_dump_obuf; + stream_reset(obuf); + + /* MRT header */ + bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, TABLE_DUMP_V2_PEER_INDEX_TABLE, + BGP_DUMP_ROUTES); + + /* Collector BGP ID */ + stream_put_in_addr(obuf, &bgp->router_id); + + /* View name */ + if (bgp->name_pretty) { + stream_putw(obuf, strlen(bgp->name_pretty)); + stream_put(obuf, bgp->name_pretty, strlen(bgp->name_pretty)); + } else { + stream_putw(obuf, 0); + } + + /* Peer count ( plus one extra internal peer ) */ + stream_putw(obuf, listcount(bgp->peer) + 1); + + /* Populate fake peer at index 0, for locally originated routes */ + /* Peer type (IPv4) */ + stream_putc(obuf, + TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP); + /* Peer BGP ID (0.0.0.0) */ + stream_putl(obuf, 0); + /* Peer IP address (0.0.0.0) */ + stream_putl(obuf, 0); + /* Peer ASN (0) */ + stream_putl(obuf, 0); + + /* Walk down all peers */ + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + int family = sockunion_family(&peer->connection->su); + + /* Peer's type */ + if (family == AF_INET) { + stream_putc( + obuf, + TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP); + } else if (family == AF_INET6) { + stream_putc( + obuf, + TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 + + TABLE_DUMP_V2_PEER_INDEX_TABLE_IP6); + } + + /* Peer's BGP ID */ + stream_put_in_addr(obuf, &peer->remote_id); + + /* Peer's IP address */ + if (family == AF_INET) { + stream_put_in_addr(obuf, + &peer->connection->su.sin.sin_addr); + } else if (family == AF_INET6) { + stream_write(obuf, + (uint8_t *)&peer->connection->su.sin6 + .sin6_addr, + IPV6_MAX_BYTELEN); + } + + /* Peer's AS number. */ + /* Note that, as this is an AS4 compliant quagga, the RIB is + * always AS4 */ + stream_putl(obuf, peer->as); + + /* Store the peer number for this peer */ + peer->table_dump_index = peerno; + peerno++; + } + + bgp_dump_set_size(obuf, MSG_TABLE_DUMP_V2); + + fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_routes.fp); + fflush(bgp_dump_routes.fp); +} + +static struct bgp_path_info * +bgp_dump_route_node_record(int afi, struct bgp_dest *dest, + struct bgp_path_info *path, unsigned int seq) +{ + struct stream *obuf; + size_t sizep; + size_t endp; + bool addpath_capable; + const struct prefix *p = bgp_dest_get_prefix(dest); + + obuf = bgp_dump_obuf; + stream_reset(obuf); + + addpath_capable = bgp_addpath_encode_rx(path->peer, afi, SAFI_UNICAST); + + /* MRT header */ + if (afi == AFI_IP && addpath_capable) + bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, + TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH, + BGP_DUMP_ROUTES); + else if (afi == AFI_IP) + bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, + TABLE_DUMP_V2_RIB_IPV4_UNICAST, + BGP_DUMP_ROUTES); + else if (afi == AFI_IP6 && addpath_capable) + bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, + TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH, + BGP_DUMP_ROUTES); + else if (afi == AFI_IP6) + bgp_dump_header(obuf, MSG_TABLE_DUMP_V2, + TABLE_DUMP_V2_RIB_IPV6_UNICAST, + BGP_DUMP_ROUTES); + + /* Sequence number */ + stream_putl(obuf, seq); + + /* Prefix length */ + stream_putc(obuf, p->prefixlen); + + /* Prefix */ + if (afi == AFI_IP) { + /* We'll dump only the useful bits (those not 0), but have to + * align on 8 bits */ + stream_write(obuf, (uint8_t *)&p->u.prefix4, + (p->prefixlen + 7) / 8); + } else if (afi == AFI_IP6) { + /* We'll dump only the useful bits (those not 0), but have to + * align on 8 bits */ + stream_write(obuf, (uint8_t *)&p->u.prefix6, + (p->prefixlen + 7) / 8); + } + + /* Save where we are now, so we can overwride the entry count later */ + sizep = stream_get_endp(obuf); + + /* Entry count */ + uint16_t entry_count = 0; + + /* Entry count, note that this is overwritten later */ + stream_putw(obuf, 0); + + endp = stream_get_endp(obuf); + for (; path; path = path->next) { + size_t cur_endp; + + /* Peer index */ + stream_putw(obuf, path->peer->table_dump_index); + + /* Originated */ + stream_putl(obuf, time(NULL) - (monotime(NULL) - path->uptime)); + + /*Path Identifier*/ + if (addpath_capable) { + stream_putl(obuf, path->addpath_rx_id); + } + + /* Dump attribute. */ + /* Skip prefix & AFI/SAFI for MP_NLRI */ + bgp_dump_routes_attr(obuf, path, p); + + cur_endp = stream_get_endp(obuf); + if (cur_endp > BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE + + BGP_DUMP_MSG_HEADER + + BGP_DUMP_HEADER_SIZE) { + stream_set_endp(obuf, endp); + break; + } + + entry_count++; + endp = cur_endp; + } + + /* Overwrite the entry count, now that we know the right number */ + stream_putw_at(obuf, sizep, entry_count); + + bgp_dump_set_size(obuf, MSG_TABLE_DUMP_V2); + fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_routes.fp); + + return path; +} + + +/* Runs under child process. */ +static unsigned int bgp_dump_routes_func(int afi, int first_run, + unsigned int seq) +{ + struct bgp_path_info *path; + struct bgp_dest *dest; + struct bgp *bgp; + struct bgp_table *table; + + bgp = bgp_get_default(); + if (!bgp) + return seq; + + if (bgp_dump_routes.fp == NULL) + return seq; + + /* Note that bgp_dump_routes_index_table will do ipv4 and ipv6 peers, + so this should only be done on the first call to + bgp_dump_routes_func. + ( this function will be called once for ipv4 and once for ipv6 ) */ + if (first_run) + bgp_dump_routes_index_table(bgp); + + /* Walk down each BGP route. */ + table = bgp->rib[afi][SAFI_UNICAST]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + path = bgp_dest_get_bgp_path_info(dest); + while (path) { + path = bgp_dump_route_node_record(afi, dest, path, seq); + seq++; + } + } + + fflush(bgp_dump_routes.fp); + + return seq; +} + +static void bgp_dump_interval_func(struct event *t) +{ + struct bgp_dump *bgp_dump; + bgp_dump = EVENT_ARG(t); + + /* Reschedule dump even if file couldn't be opened this time... */ + if (bgp_dump_open_file(bgp_dump) != NULL) { + /* In case of bgp_dump_routes, we need special route dump + * function. */ + if (bgp_dump->type == BGP_DUMP_ROUTES) { + unsigned int seq = bgp_dump_routes_func(AFI_IP, 1, 0); + bgp_dump_routes_func(AFI_IP6, 0, seq); + /* Close the file now. For a RIB dump there's no point + * in leaving + * it open until the next scheduled dump starts. */ + fclose(bgp_dump->fp); + bgp_dump->fp = NULL; + } + } + + /* if interval is set reschedule */ + if (bgp_dump->interval > 0) + bgp_dump_interval_add(bgp_dump, bgp_dump->interval); +} + +/* Dump common information. */ +static void bgp_dump_common(struct stream *obuf, struct peer *peer, + int forceas4) +{ + char empty[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + /* Source AS number and Destination AS number. */ + if (forceas4 || CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { + stream_putl(obuf, peer->as); + stream_putl(obuf, peer->local_as); + } else { + stream_putw(obuf, peer->as); + stream_putw(obuf, peer->local_as); + } + + if (peer->connection->su.sa.sa_family == AF_INET) { + stream_putw(obuf, peer->ifp ? peer->ifp->ifindex : 0); + stream_putw(obuf, AFI_IP); + + stream_put(obuf, &peer->connection->su.sin.sin_addr, + IPV4_MAX_BYTELEN); + + if (peer->su_local) + stream_put(obuf, &peer->su_local->sin.sin_addr, + IPV4_MAX_BYTELEN); + else + stream_put(obuf, empty, IPV4_MAX_BYTELEN); + } else if (peer->connection->su.sa.sa_family == AF_INET6) { + /* Interface Index and Address family. */ + stream_putw(obuf, peer->ifp ? peer->ifp->ifindex : 0); + stream_putw(obuf, AFI_IP6); + + /* Source IP Address and Destination IP Address. */ + stream_put(obuf, &peer->connection->su.sin6.sin6_addr, + IPV6_MAX_BYTELEN); + + if (peer->su_local) + stream_put(obuf, &peer->su_local->sin6.sin6_addr, + IPV6_MAX_BYTELEN); + else + stream_put(obuf, empty, IPV6_MAX_BYTELEN); + } +} + +/* Dump BGP status change. */ +int bgp_dump_state(struct peer *peer) +{ + struct stream *obuf; + + /* If dump file pointer is disabled return immediately. */ + if (bgp_dump_all.fp == NULL) + return 0; + + /* Make dump stream. */ + obuf = bgp_dump_obuf; + stream_reset(obuf); + + bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_STATE_CHANGE_AS4, + bgp_dump_all.type); + bgp_dump_common(obuf, peer, 1); /* force this in as4speak*/ + + stream_putw(obuf, peer->connection->ostatus); + stream_putw(obuf, peer->connection->status); + + /* Set length. */ + bgp_dump_set_size(obuf, MSG_PROTOCOL_BGP4MP); + + /* Write to the stream. */ + fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump_all.fp); + fflush(bgp_dump_all.fp); + return 0; +} + +static void bgp_dump_packet_func(struct bgp_dump *bgp_dump, struct peer *peer, + struct stream *packet) +{ + struct stream *obuf; + bool addpath_capable = false; + /* If dump file pointer is disabled return immediately. */ + if (bgp_dump->fp == NULL) + return; + if (peer->connection->su.sa.sa_family == AF_INET) { + addpath_capable = + bgp_addpath_encode_rx(peer, AFI_IP, SAFI_UNICAST); + } else if (peer->connection->su.sa.sa_family == AF_INET6) { + addpath_capable = + bgp_addpath_encode_rx(peer, AFI_IP6, SAFI_UNICAST); + } + + /* Make dump stream. */ + obuf = bgp_dump_obuf; + stream_reset(obuf); + + /* Dump header and common part. */ + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) && addpath_capable) { + bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, + BGP4MP_MESSAGE_AS4_ADDPATH, bgp_dump->type); + } else if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV)) { + bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE_AS4, + bgp_dump->type); + } else if (addpath_capable) { + bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, + BGP4MP_MESSAGE_ADDPATH, bgp_dump->type); + } else { + bgp_dump_header(obuf, MSG_PROTOCOL_BGP4MP, BGP4MP_MESSAGE, + bgp_dump->type); + } + bgp_dump_common(obuf, peer, 0); + + /* Packet contents. */ + stream_put(obuf, STREAM_DATA(packet), stream_get_endp(packet)); + + /* Set length. */ + bgp_dump_set_size(obuf, MSG_PROTOCOL_BGP4MP); + + /* Write to the stream. */ + fwrite(STREAM_DATA(obuf), stream_get_endp(obuf), 1, bgp_dump->fp); + fflush(bgp_dump->fp); +} + +/* Called from bgp_packet.c when BGP packet is received. */ +static int bgp_dump_packet(struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *packet) +{ + /* bgp_dump_all. */ + bgp_dump_packet_func(&bgp_dump_all, peer, packet); + + /* bgp_dump_updates. */ + if (type == BGP_MSG_UPDATE) + bgp_dump_packet_func(&bgp_dump_updates, peer, packet); + return 0; +} + +static unsigned int bgp_dump_parse_time(const char *str) +{ + int i; + int len; + int seen_h; + int seen_m; + int time; + unsigned int total; + + time = 0; + total = 0; + seen_h = 0; + seen_m = 0; + len = strlen(str); + + for (i = 0; i < len; i++) { + if (isdigit((unsigned char)str[i])) { + time *= 10; + time += str[i] - '0'; + } else if (str[i] == 'H' || str[i] == 'h') { + if (seen_h) + return 0; + if (seen_m) + return 0; + total += time * 60 * 60; + time = 0; + seen_h = 1; + } else if (str[i] == 'M' || str[i] == 'm') { + if (seen_m) + return 0; + total += time * 60; + time = 0; + seen_m = 1; + } else + return 0; + } + return total + time; +} + +static int bgp_dump_set(struct vty *vty, struct bgp_dump *bgp_dump, + enum bgp_dump_type type, const char *path, + const char *interval_str) +{ + unsigned int interval; + + /* Don't schedule duplicate dumps if the dump command is given twice */ + if (bgp_dump->filename && strcmp(path, bgp_dump->filename) == 0 + && type == bgp_dump->type) { + if (interval_str) { + if (bgp_dump->interval_str + && strcmp(bgp_dump->interval_str, interval_str) + == 0) + return CMD_SUCCESS; + } else { + if (!bgp_dump->interval_str) + return CMD_SUCCESS; + } + } + + /* Removing previous config */ + bgp_dump_unset(bgp_dump); + + if (interval_str) { + /* Check interval string. */ + interval = bgp_dump_parse_time(interval_str); + if (interval == 0) { + vty_out(vty, "Malformed interval string\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Setting interval string */ + bgp_dump->interval_str = + XSTRDUP(MTYPE_BGP_DUMP_STR, interval_str); + } else { + interval = 0; + } + + /* Set type. */ + bgp_dump->type = type; + + /* Set interval */ + bgp_dump->interval = interval; + + /* Set file name. */ + bgp_dump->filename = XSTRDUP(MTYPE_BGP_DUMP_STR, path); + + /* Create interval thread. */ + bgp_dump_interval_add(bgp_dump, interval); + + /* This should be called when interval is expired. */ + bgp_dump_open_file(bgp_dump); + + return CMD_SUCCESS; +} + +static int bgp_dump_unset(struct bgp_dump *bgp_dump) +{ + /* Removing file name. */ + XFREE(MTYPE_BGP_DUMP_STR, bgp_dump->filename); + + /* Closing file. */ + if (bgp_dump->fp) { + fclose(bgp_dump->fp); + bgp_dump->fp = NULL; + } + + /* Removing interval event. */ + EVENT_OFF(bgp_dump->t_interval); + + bgp_dump->interval = 0; + + /* Removing interval string. */ + XFREE(MTYPE_BGP_DUMP_STR, bgp_dump->interval_str); + + return CMD_SUCCESS; +} + +DEFUN (dump_bgp_all, + dump_bgp_all_cmd, + "dump bgp PATH [INTERVAL]", + "Dump packet\n" + "BGP packet dump\n" + "Dump all BGP packets\nDump all BGP packets (Extended Timestamp Header)\n" + "Dump BGP updates only\nDump BGP updates only (Extended Timestamp Header)\n" + "Dump whole BGP routing table\n" + "Output filename\n" + "Interval of output\n") +{ + int idx_dump_routes = 2; + int idx_path = 3; + int idx_interval = 4; + int bgp_dump_type = 0; + const char *interval = NULL; + struct bgp_dump *bgp_dump_struct = NULL; + const struct bgp_dump_type_map *map = NULL; + + for (map = bgp_dump_type_map; map->str; map++) + if (strmatch(argv[idx_dump_routes]->text, map->str)) + bgp_dump_type = map->type; + + switch (bgp_dump_type) { + case BGP_DUMP_ALL: + case BGP_DUMP_ALL_ET: + bgp_dump_struct = &bgp_dump_all; + break; + case BGP_DUMP_UPDATES: + case BGP_DUMP_UPDATES_ET: + bgp_dump_struct = &bgp_dump_updates; + break; + case BGP_DUMP_ROUTES: + default: + bgp_dump_struct = &bgp_dump_routes; + break; + } + + /* When an interval is given */ + if (argc == idx_interval + 1) + interval = argv[idx_interval]->arg; + + return bgp_dump_set(vty, bgp_dump_struct, bgp_dump_type, + argv[idx_path]->arg, interval); +} + +DEFUN (no_dump_bgp_all, + no_dump_bgp_all_cmd, + "no dump bgp [PATH [INTERVAL]]", + NO_STR + "Stop dump packet\n" + "Stop BGP packet dump\n" + "Stop dump process all\n" + "Stop dump process all-et\n" + "Stop dump process updates\n" + "Stop dump process updates-et\n" + "Stop dump process route-mrt\n" + "Output filename\n" + "Interval of output\n") +{ + int idx_dump_routes = 3; + int bgp_dump_type = 0; + const struct bgp_dump_type_map *map = NULL; + struct bgp_dump *bgp_dump_struct = NULL; + + for (map = bgp_dump_type_map; map->str; map++) + if (strmatch(argv[idx_dump_routes]->text, map->str)) + bgp_dump_type = map->type; + + switch (bgp_dump_type) { + case BGP_DUMP_ALL: + case BGP_DUMP_ALL_ET: + bgp_dump_struct = &bgp_dump_all; + break; + case BGP_DUMP_UPDATES: + case BGP_DUMP_UPDATES_ET: + bgp_dump_struct = &bgp_dump_updates; + break; + case BGP_DUMP_ROUTES: + default: + bgp_dump_struct = &bgp_dump_routes; + break; + } + + return bgp_dump_unset(bgp_dump_struct); +} + +static int config_write_bgp_dump(struct vty *vty); +/* BGP node structure. */ +static struct cmd_node bgp_dump_node = { + .name = "dump", + .node = DUMP_NODE, + .prompt = "", + .config_write = config_write_bgp_dump, +}; + +static int config_write_bgp_dump(struct vty *vty) +{ + if (bgp_dump_all.filename) { + const char *type_str = "all"; + if (bgp_dump_all.type == BGP_DUMP_ALL_ET) + type_str = "all-et"; + + if (bgp_dump_all.interval_str) + vty_out(vty, "dump bgp %s %s %s\n", type_str, + bgp_dump_all.filename, + bgp_dump_all.interval_str); + else + vty_out(vty, "dump bgp %s %s\n", type_str, + bgp_dump_all.filename); + } + if (bgp_dump_updates.filename) { + const char *type_str = "updates"; + if (bgp_dump_updates.type == BGP_DUMP_UPDATES_ET) + type_str = "updates-et"; + + if (bgp_dump_updates.interval_str) + vty_out(vty, "dump bgp %s %s %s\n", type_str, + bgp_dump_updates.filename, + bgp_dump_updates.interval_str); + else + vty_out(vty, "dump bgp %s %s\n", type_str, + bgp_dump_updates.filename); + } + if (bgp_dump_routes.filename) { + if (bgp_dump_routes.interval_str) + vty_out(vty, "dump bgp routes-mrt %s %s\n", + bgp_dump_routes.filename, + bgp_dump_routes.interval_str); + else + vty_out(vty, "dump bgp routes-mrt %s\n", + bgp_dump_routes.filename); + } + return 0; +} + +/* Initialize BGP packet dump functionality. */ +void bgp_dump_init(void) +{ + memset(&bgp_dump_all, 0, sizeof(bgp_dump_all)); + memset(&bgp_dump_updates, 0, sizeof(bgp_dump_updates)); + memset(&bgp_dump_routes, 0, sizeof(bgp_dump_routes)); + + bgp_dump_obuf = + stream_new(BGP_MAX_PACKET_SIZE + BGP_MAX_PACKET_SIZE_OVERFLOW); + + install_node(&bgp_dump_node); + + install_element(CONFIG_NODE, &dump_bgp_all_cmd); + install_element(CONFIG_NODE, &no_dump_bgp_all_cmd); + + hook_register(bgp_packet_dump, bgp_dump_packet); + hook_register(peer_status_changed, bgp_dump_state); +} + +void bgp_dump_finish(void) +{ + bgp_dump_unset(&bgp_dump_all); + bgp_dump_unset(&bgp_dump_updates); + bgp_dump_unset(&bgp_dump_routes); + + stream_free(bgp_dump_obuf); + bgp_dump_obuf = NULL; + hook_unregister(bgp_packet_dump, bgp_dump_packet); + hook_unregister(peer_status_changed, bgp_dump_state); +} diff --git a/bgpd/bgp_dump.h b/bgpd/bgp_dump.h new file mode 100644 index 0000000..2235bbc --- /dev/null +++ b/bgpd/bgp_dump.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP dump routine. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_DUMP_H +#define _QUAGGA_BGP_DUMP_H + +/* MRT compatible packet dump values. */ +/* type value */ +#define MSG_PROTOCOL_BGP4MP 16 +#define MSG_PROTOCOL_BGP4MP_ET 17 + +/* subtype value */ +#define BGP4MP_STATE_CHANGE 0 +#define BGP4MP_MESSAGE 1 +#define BGP4MP_ENTRY 2 +#define BGP4MP_SNAPSHOT 3 +#define BGP4MP_MESSAGE_AS4 4 +#define BGP4MP_STATE_CHANGE_AS4 5 +#define BGP4MP_MESSAGE_ADDPATH 8 +#define BGP4MP_MESSAGE_AS4_ADDPATH 9 +#define BGP4MP_MESSAGE_LOCAL_ADDPATH 10 +#define BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH 11 + +#define BGP_DUMP_HEADER_SIZE 12 +#define BGP_DUMP_MSG_HEADER 40 + +#define TABLE_DUMP_V2_PEER_INDEX_TABLE 1 +#define TABLE_DUMP_V2_RIB_IPV4_UNICAST 2 +#define TABLE_DUMP_V2_RIB_IPV4_MULTICAST 3 +#define TABLE_DUMP_V2_RIB_IPV6_UNICAST 4 +#define TABLE_DUMP_V2_RIB_IPV6_MULTICAST 5 +#define TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH 8 +#define TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH 9 +#define TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH 10 +#define TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH 11 +#define TABLE_DUMP_V2_RIB_GENERIC_ADDPATH 12 + +#define TABLE_DUMP_V2_PEER_INDEX_TABLE_IP 0 +#define TABLE_DUMP_V2_PEER_INDEX_TABLE_IP6 1 +#define TABLE_DUMP_V2_PEER_INDEX_TABLE_AS2 0 +#define TABLE_DUMP_V2_PEER_INDEX_TABLE_AS4 2 + +extern void bgp_dump_init(void); +extern void bgp_dump_finish(void); +extern int bgp_dump_state(struct peer *peer); + +#endif /* _QUAGGA_BGP_DUMP_H */ diff --git a/bgpd/bgp_ecommunity.c b/bgpd/bgp_ecommunity.c new file mode 100644 index 0000000..1beb030 --- /dev/null +++ b/bgpd/bgp_ecommunity.c @@ -0,0 +1,1985 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Extended Communities Attribute + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include + +#include "hash.h" +#include "memory.h" +#include "prefix.h" +#include "command.h" +#include "queue.h" +#include "filter.h" +#include "jhash.h" +#include "stream.h" + +#include "lib/printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_flowspec_private.h" +#include "bgpd/bgp_pbr.h" + +/* struct used to dump the rate contained in FS set traffic-rate EC */ +union traffic_rate { + float rate_float; + uint8_t rate_byte[4]; +}; + +/* Hash of community attribute. */ +static struct hash *ecomhash; + +/* Allocate a new ecommunities. */ +struct ecommunity *ecommunity_new(void) +{ + struct ecommunity *ecom; + + ecom = (struct ecommunity *)XCALLOC(MTYPE_ECOMMUNITY, + sizeof(struct ecommunity)); + ecom->unit_size = ECOMMUNITY_SIZE; + return ecom; +} + +void ecommunity_strfree(char **s) +{ + XFREE(MTYPE_ECOMMUNITY_STR, *s); +} + +/* Free ecommunities. */ +void ecommunity_free(struct ecommunity **ecom) +{ + if (!(*ecom)) + return; + + XFREE(MTYPE_ECOMMUNITY_VAL, (*ecom)->val); + XFREE(MTYPE_ECOMMUNITY_STR, (*ecom)->str); + XFREE(MTYPE_ECOMMUNITY, *ecom); +} + +static void ecommunity_hash_free(struct ecommunity *ecom) +{ + ecommunity_free(&ecom); +} + + +/* Add a new Extended Communities value to Extended Communities + Attribute structure. When the value is already exists in the + structure, we don't add the value. Newly added value is sorted by + numerical order. When the value is added to the structure return 1 + else return 0. + The additional parameters 'unique' and 'overwrite' ensure a particular + extended community (based on type and sub-type) is present only + once and whether the new value should replace what is existing or + not. +*/ +static bool ecommunity_add_val_internal(struct ecommunity *ecom, + const void *eval, + bool unique, bool overwrite, + uint8_t ecom_size) +{ + uint32_t c, ins_idx; + const struct ecommunity_val *eval4 = (struct ecommunity_val *)eval; + const struct ecommunity_val_ipv6 *eval6 = + (struct ecommunity_val_ipv6 *)eval; + + /* When this is fist value, just add it. */ + if (ecom->val == NULL) { + ecom->size = 1; + ecom->val = XMALLOC(MTYPE_ECOMMUNITY_VAL, + ecom_length_size(ecom, ecom_size)); + memcpy(ecom->val, eval, ecom_size); + return true; + } + + /* If the value already exists in the structure return 0. */ + /* check also if the extended community itself exists. */ + c = 0; + + ins_idx = UINT32_MAX; + for (uint8_t *p = ecom->val; c < ecom->size; + p += ecom_size, c++) { + if (unique) { + if (ecom_size == ECOMMUNITY_SIZE) { + if (p[0] == eval4->val[0] && + p[1] == eval4->val[1]) { + if (overwrite) { + memcpy(p, eval4->val, + ecom_size); + return true; + } + return false; + } + } else { + if (p[0] == eval6->val[0] && + p[1] == eval6->val[1]) { + if (overwrite) { + memcpy(p, eval6->val, + ecom_size); + return true; + } + return false; + } + } + } + int ret = memcmp(p, eval, ecom_size); + if (ret == 0) + return false; + if (ret > 0) { + if (!unique) + break; + if (ins_idx == UINT32_MAX) + ins_idx = c; + } + } + + if (ins_idx == UINT32_MAX) + ins_idx = c; + + /* Add the value to the structure with numerical sorting. */ + ecom->size++; + ecom->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom->val, + ecom_length_size(ecom, ecom_size)); + + memmove(ecom->val + ((ins_idx + 1) * ecom_size), + ecom->val + (ins_idx * ecom_size), + (ecom->size - 1 - ins_idx) * ecom_size); + memcpy(ecom->val + (ins_idx * ecom_size), + eval, ecom_size); + + return true; +} + +/* Add a new Extended Communities value to Extended Communities + * Attribute structure. When the value is already exists in the + * structure, we don't add the value. Newly added value is sorted by + * numerical order. When the value is added to the structure return 1 + * else return 0. + */ +bool ecommunity_add_val(struct ecommunity *ecom, struct ecommunity_val *eval, + bool unique, bool overwrite) +{ + return ecommunity_add_val_internal(ecom, (const void *)eval, unique, + overwrite, ECOMMUNITY_SIZE); +} + +bool ecommunity_add_val_ipv6(struct ecommunity *ecom, + struct ecommunity_val_ipv6 *eval, + bool unique, bool overwrite) +{ + return ecommunity_add_val_internal(ecom, (const void *)eval, unique, + overwrite, IPV6_ECOMMUNITY_SIZE); +} + +static struct ecommunity * +ecommunity_uniq_sort_internal(struct ecommunity *ecom, + unsigned short ecom_size) +{ + uint32_t i; + struct ecommunity *new; + const void *eval; + + if (!ecom) + return NULL; + + new = ecommunity_new(); + new->unit_size = ecom_size; + new->disable_ieee_floating = ecom->disable_ieee_floating; + + for (i = 0; i < ecom->size; i++) { + eval = (void *)(ecom->val + (i * ecom_size)); + ecommunity_add_val_internal(new, eval, false, false, ecom_size); + } + return new; +} + +/* This function takes pointer to Extended Communites structure then + * create a new Extended Communities structure by uniq and sort each + * Extended Communities value. + */ +struct ecommunity *ecommunity_uniq_sort(struct ecommunity *ecom) +{ + return ecommunity_uniq_sort_internal(ecom, ECOMMUNITY_SIZE); +} + +/* Parse Extended Communites Attribute in BGP packet. */ +static struct ecommunity *ecommunity_parse_internal(uint8_t *pnt, + unsigned short length, + unsigned short size_ecom, + bool disable_ieee_floating) +{ + struct ecommunity tmp; + struct ecommunity *new; + + /* Length check. */ + if (length % size_ecom) + return NULL; + + /* Prepare tmporary structure for making a new Extended Communities + Attribute. */ + tmp.size = length / size_ecom; + tmp.val = pnt; + tmp.disable_ieee_floating = disable_ieee_floating; + + /* Create a new Extended Communities Attribute by uniq and sort each + Extended Communities value */ + new = ecommunity_uniq_sort_internal(&tmp, size_ecom); + + return ecommunity_intern(new); +} + +struct ecommunity *ecommunity_parse(uint8_t *pnt, unsigned short length, + bool disable_ieee_floating) +{ + return ecommunity_parse_internal(pnt, length, ECOMMUNITY_SIZE, + disable_ieee_floating); +} + +struct ecommunity *ecommunity_parse_ipv6(uint8_t *pnt, unsigned short length) +{ + return ecommunity_parse_internal(pnt, length, IPV6_ECOMMUNITY_SIZE, + false); +} + +/* Duplicate the Extended Communities Attribute structure. */ +struct ecommunity *ecommunity_dup(struct ecommunity *ecom) +{ + struct ecommunity *new; + + new = XCALLOC(MTYPE_ECOMMUNITY, sizeof(struct ecommunity)); + new->size = ecom->size; + new->unit_size = ecom->unit_size; + if (new->size) { + new->val = XMALLOC(MTYPE_ECOMMUNITY_VAL, + ecom->size * ecom->unit_size); + memcpy(new->val, ecom->val, + (size_t)ecom->size * (size_t)ecom->unit_size); + } else + new->val = NULL; + return new; +} + +/* Return string representation of ecommunities attribute. */ +const char *ecommunity_str(struct ecommunity *ecom) +{ + if (!ecom) + return "(null)"; + + if (!ecom->str) + ecom->str = + ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0); + return ecom->str; +} + +/* Merge two Extended Communities Attribute structure. */ +struct ecommunity *ecommunity_merge(struct ecommunity *ecom1, + struct ecommunity *ecom2) +{ + ecom1->val = XREALLOC(MTYPE_ECOMMUNITY_VAL, ecom1->val, + (size_t)(ecom1->size + ecom2->size) + * (size_t)ecom1->unit_size); + + memcpy(ecom1->val + (ecom1->size * ecom1->unit_size), ecom2->val, + (size_t)ecom2->size * (size_t)ecom1->unit_size); + ecom1->size += ecom2->size; + + return ecom1; +} + +/* Intern Extended Communities Attribute. */ +struct ecommunity *ecommunity_intern(struct ecommunity *ecom) +{ + struct ecommunity *find; + + assert(ecom->refcnt == 0); + find = (struct ecommunity *)hash_get(ecomhash, ecom, hash_alloc_intern); + if (find != ecom) + ecommunity_free(&ecom); + + find->refcnt++; + + if (!find->str) + find->str = + ecommunity_ecom2str(find, ECOMMUNITY_FORMAT_DISPLAY, 0); + + return find; +} + +/* Unintern Extended Communities Attribute. */ +void ecommunity_unintern(struct ecommunity **ecom) +{ + struct ecommunity *ret; + + if (!*ecom) + return; + + if ((*ecom)->refcnt) + (*ecom)->refcnt--; + + /* Pull off from hash. */ + if ((*ecom)->refcnt == 0) { + /* Extended community must be in the hash. */ + ret = (struct ecommunity *)hash_release(ecomhash, *ecom); + assert(ret != NULL); + + ecommunity_free(ecom); + } +} + +/* Utinity function to make hash key. */ +unsigned int ecommunity_hash_make(const void *arg) +{ + const struct ecommunity *ecom = arg; + int size = ecom->size * ecom->unit_size; + + return jhash(ecom->val, size, 0x564321ab); +} + +/* Compare two Extended Communities Attribute structure. */ +bool ecommunity_cmp(const void *arg1, const void *arg2) +{ + const struct ecommunity *ecom1 = arg1; + const struct ecommunity *ecom2 = arg2; + + if (ecom1 == NULL && ecom2 == NULL) + return true; + + if (ecom1 == NULL || ecom2 == NULL) + return false; + + if (ecom1->unit_size != ecom2->unit_size) + return false; + + return (ecom1->size == ecom2->size + && memcmp(ecom1->val, ecom2->val, ecom1->size * + ecom1->unit_size) == 0); +} + +static void ecommunity_color_str(char *buf, size_t bufsz, uint8_t *ptr) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x03 | Sub-Type(0x0b) | Flags | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Color Value | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + uint32_t colorid; + + memcpy(&colorid, ptr + 3, 4); + colorid = ntohl(colorid); + snprintf(buf, bufsz, "Color:%d", colorid); +} + +/* Initialize Extended Comminities related hash. */ +void ecommunity_init(void) +{ + ecomhash = hash_create(ecommunity_hash_make, ecommunity_cmp, + "BGP ecommunity hash"); +} + +void ecommunity_finish(void) +{ + hash_clean_and_free(&ecomhash, (void (*)(void *))ecommunity_hash_free); +} + +/* Extended Communities token enum. */ +enum ecommunity_token { + ecommunity_token_unknown = 0, + ecommunity_token_rt, + ecommunity_token_nt, + ecommunity_token_soo, + ecommunity_token_color, + ecommunity_token_val, + ecommunity_token_rt6, + ecommunity_token_val6, +}; + +static const char *ecommunity_origin_validation_state2str( + enum ecommunity_origin_validation_states state) +{ + switch (state) { + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_VALID: + return "valid"; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTFOUND: + return "not-found"; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_INVALID: + return "invalid"; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTUSED: + return "not-used"; + } + + return "ERROR"; +} + +static void ecommunity_origin_validation_state_str(char *buf, size_t bufsz, + uint8_t *ptr) +{ + /* Origin Validation State is encoded in the last octet + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x43 | 0x00 | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved |validationstate| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + uint8_t state = *(ptr + ECOMMUNITY_SIZE - 3); + + snprintf(buf, bufsz, "OVS:%s", + ecommunity_origin_validation_state2str(state)); + + (void)ptr; /* consume value */ +} + +bool ecommunity_node_target_match(struct ecommunity *ecom, + struct in_addr *local_id) +{ + uint32_t i; + bool match = false; + + if (!ecom || !ecom->size) + return NULL; + + for (i = 0; i < ecom->size; i++) { + const uint8_t *pnt; + uint8_t type, sub_type; + + pnt = (ecom->val + (i * ECOMMUNITY_SIZE)); + type = *pnt++; + sub_type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_IP && + sub_type == ECOMMUNITY_NODE_TARGET) { + /* Node Target ID is encoded as A.B.C.D:0 */ + if (IPV4_ADDR_SAME((struct in_addr *)pnt, local_id)) + match = true; + (void)pnt; + } + } + + return match; +} + +static void ecommunity_node_target_str(char *buf, size_t bufsz, uint8_t *ptr, + int format) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x01 or 0x41 | Sub-Type(0x09) | Target BGP Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Target BGP Identifier (cont.) | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + struct in_addr node_id = {}; + + IPV4_ADDR_COPY(&node_id, (struct in_addr *)ptr); + + + snprintfrr(buf, bufsz, "%s%pI4%s", + format == ECOMMUNITY_FORMAT_COMMUNITY_LIST ? "nt " : "NT:", + &node_id, + format == ECOMMUNITY_FORMAT_COMMUNITY_LIST ? ":0" : ""); + + (void)ptr; /* consume value */ +} + +static int ecommunity_encode_internal(uint8_t type, uint8_t sub_type, + int trans, as_t as, + struct in_addr *ip, + struct in6_addr *ip6, + uint32_t val, + void *eval_ptr) +{ + struct ecommunity_val *eval = (struct ecommunity_val *)eval_ptr; + struct ecommunity_val_ipv6 *eval6 = + (struct ecommunity_val_ipv6 *)eval_ptr; + + assert(eval); + if (type == ECOMMUNITY_ENCODE_AS) { + if (as > BGP_AS_MAX) + return -1; + } else if (type == ECOMMUNITY_ENCODE_IP + || type == ECOMMUNITY_ENCODE_AS4) { + if (val > UINT16_MAX) + return -1; + } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP && + sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6 && + (!ip6 || val > UINT16_MAX)) { + return -1; + } + + /* Fill in the values. */ + eval->val[0] = type; + if (!trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = sub_type; + if (type == ECOMMUNITY_ENCODE_AS) { + encode_route_target_as(as, val, eval, trans); + } else if (type == ECOMMUNITY_ENCODE_IP) { + if (sub_type == ECOMMUNITY_NODE_TARGET) + encode_node_target(ip, eval, trans); + else + encode_route_target_ip(ip, val, eval, trans); + } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP && + sub_type == ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) { + memcpy(&eval6->val[2], ip6, sizeof(struct in6_addr)); + eval6->val[18] = (val >> 8) & 0xff; + eval6->val[19] = val & 0xff; + } else if (type == ECOMMUNITY_ENCODE_OPAQUE && + sub_type == ECOMMUNITY_COLOR) { + encode_color(val, eval); + } else { + encode_route_target_as4(as, val, eval, trans); + } + + return 0; +} + +/* + * Encode BGP extended community from passed values. Supports types + * defined in RFC 4360 and well-known sub-types. + */ +static int ecommunity_encode(uint8_t type, uint8_t sub_type, int trans, as_t as, + struct in_addr ip, uint32_t val, + struct ecommunity_val *eval) +{ + return ecommunity_encode_internal(type, sub_type, trans, as, + &ip, NULL, val, (void *)eval); +} + +/* Get next Extended Communities token from the string. */ +static const char *ecommunity_gettoken(const char *str, void *eval_ptr, + enum ecommunity_token *token, int type) +{ + int ret; + int dot = 0; + int digit = 0; + int separator = 0; + const char *p = str; + char *endptr; + struct in_addr ip; + struct in6_addr ip6; + as_t as = 0; + uint32_t val = 0; + uint32_t val_color = 0; + uint8_t ecomm_type = 0; + uint8_t sub_type = 0; + char buf[INET_ADDRSTRLEN + 1]; + struct ecommunity_val *eval = (struct ecommunity_val *)eval_ptr; + uint64_t tmp_as = 0; + static const char str_color[5] = "color"; + const char *ptr_color; + bool val_color_set = false; + + /* Skip white space. */ + while (isspace((unsigned char)*p)) { + p++; + str++; + } + + /* Check the end of the line. */ + if (*p == '\0') + return NULL; + + /* "rt", "nt", "soo", and "color" keyword parse. */ + /* "rt" */ + if (tolower((unsigned char)*p) == 'r') { + p++; + if (tolower((unsigned char)*p) == 't') { + p++; + if (*p != '\0' && tolower((int)*p) == '6') + *token = ecommunity_token_rt6; + else + *token = ecommunity_token_rt; + return p; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_rt; + return p; + } + goto error; + } + + /* "nt" */ + if (tolower((unsigned char)*p) == 'n') { + p++; + if (tolower((unsigned char)*p) == 't') { + p++; + *token = ecommunity_token_nt; + return p; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_nt; + return p; + } + goto error; + } + + /* "soo" */ + if (tolower((unsigned char)*p) == 's') { + p++; + if (tolower((unsigned char)*p) == 'o') { + p++; + if (tolower((unsigned char)*p) == 'o') { + p++; + *token = ecommunity_token_soo; + return p; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_soo; + return p; + } + goto error; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_soo; + return p; + } + goto error; + } + + /* "color" */ + if (tolower((unsigned char)*p) == 'c') { + ptr_color = &str_color[0]; + for (unsigned int i = 0; i < 5; i++) { + if (tolower((unsigned char)*p) != *ptr_color) + break; + + p++; + ptr_color++; + } + if (isspace((unsigned char)*p) || *p == '\0') { + *token = ecommunity_token_color; + return p; + } + goto error; + } + /* What a mess, there are several possibilities: + * + * a) A.B.C.D:MN + * b) EF:OPQR + * c) GHJK:MN + * d) :MN (only with rt6) + * + * A.B.C.D: Four Byte IP + * EF: Two byte ASN + * GHJK: Four-byte ASN + * MN: Two byte value + * OPQR: Four byte value + * + */ + /* IPv6 case : look for last ':' */ + if (*token == ecommunity_token_rt6 || + *token == ecommunity_token_val6) { + char *limit; + + limit = endptr = strrchr(p, ':'); + if (!endptr) + goto error; + + endptr++; + errno = 0; + tmp_as = strtoul(endptr, &endptr, 10); + /* 'unsigned long' is a uint64 on 64-bit + * systems, and uint32 on 32-bit systems. So for + * 64-bit we can just directly check the value + * against BGP_AS4_MAX/UINT32_MAX, and for + * 32-bit we can check for errno (set to ERANGE + * upon overflow). + */ + if (*endptr != '\0' || tmp_as == BGP_AS4_MAX || errno) + goto error; + as = (as_t)tmp_as; + + memcpy(buf, p, (limit - p)); + buf[limit - p] = '\0'; + ret = inet_pton(AF_INET6, buf, &ip6); + if (ret == 0) + goto error; + + ecomm_type = ECOMMUNITY_ENCODE_TRANS_EXP; + if (ecommunity_encode_internal(ecomm_type, + ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6, + 1, 0, NULL, &ip6, as, eval_ptr)) + goto error; + + *token = ecommunity_token_val6; + while (isdigit((int)*p) || *p == ':' || *p == '.') { + p++; + } + return p; + } + while (isdigit((unsigned char)*p) || *p == ':' || *p == '.') { + if (*p == ':') { + if (separator) + goto error; + + separator = 1; + digit = 0; + + if ((p - str) > INET_ADDRSTRLEN) + goto error; + memset(buf, 0, INET_ADDRSTRLEN + 1); + memcpy(buf, str, p - str); + + if (dot == 3) { + /* Parsing A.B.C.D in: + * A.B.C.D:MN + */ + ret = inet_aton(buf, &ip); + if (ret == 0) + goto error; + } else if (dot == 1) { + /* Parsing A.B AS number in: + * A.B:MN + */ + if (!asn_str2asn(buf, &as)) + goto error; + } else { + /* Parsing A AS number in A:MN */ + errno = 0; + tmp_as = strtoul(buf, &endptr, 10); + /* 'unsigned long' is a uint64 on 64-bit + * systems, and uint32 on 32-bit systems. So for + * 64-bit we can just directly check the value + * against BGP_AS4_MAX/UINT32_MAX, and for + * 32-bit we can check for errno (set to ERANGE + * upon overflow). + */ + if (*endptr != '\0' || tmp_as > BGP_AS4_MAX || + errno) + goto error; + as = (as_t)tmp_as; + } + } else if (*p == '.') { + if (separator) + goto error; + /* either IP or AS format */ + dot++; + if (dot > 1) + ecomm_type = ECOMMUNITY_ENCODE_IP; + if (dot >= 4) + goto error; + } else { + digit = 1; + + /* We're past the IP/ASN part, + * or we have a color + */ + if (separator) { + val *= 10; + val += (*p - '0'); + val_color_set = false; + } else { + val_color *= 10; + val_color += (*p - '0'); + val_color_set = true; + } + } + p++; + } + + /* Low digit part must be there. */ + if (!digit && (!separator || !val_color_set)) + goto error; + + if (ecomm_type != ECOMMUNITY_ENCODE_IP) { + /* Encode result into extended community for AS format or color. */ + if (as > BGP_AS_MAX) + ecomm_type = ECOMMUNITY_ENCODE_AS4; + else if (as > 0) + ecomm_type = ECOMMUNITY_ENCODE_AS; + else if (val_color) { + ecomm_type = ECOMMUNITY_ENCODE_OPAQUE; + sub_type = ECOMMUNITY_COLOR; + val = val_color; + } + } + if (ecommunity_encode(ecomm_type, sub_type, 1, as, ip, val, eval)) + goto error; + *token = ecommunity_token_val; + return p; + +error: + *token = ecommunity_token_unknown; + return p; +} + +static struct ecommunity *ecommunity_str2com_internal(const char *str, int type, + int keyword_included, + bool is_ipv6_extcomm) +{ + struct ecommunity *ecom = NULL; + enum ecommunity_token token = ecommunity_token_unknown; + struct ecommunity_val_ipv6 eval; + int keyword = 0; + + if (is_ipv6_extcomm) + token = ecommunity_token_rt6; + while ((str = ecommunity_gettoken(str, (void *)&eval, &token, type))) { + switch (token) { + case ecommunity_token_rt: + case ecommunity_token_nt: + case ecommunity_token_rt6: + case ecommunity_token_soo: + case ecommunity_token_color: + if (!keyword_included || keyword) { + if (ecom) + ecommunity_free(&ecom); + return NULL; + } + keyword = 1; + + if (token == ecommunity_token_rt || + token == ecommunity_token_rt6) + type = ECOMMUNITY_ROUTE_TARGET; + if (token == ecommunity_token_soo) + type = ECOMMUNITY_SITE_ORIGIN; + if (token == ecommunity_token_nt) + type = ECOMMUNITY_NODE_TARGET; + if (token == ecommunity_token_color) + type = ECOMMUNITY_COLOR; + break; + case ecommunity_token_val: + if (keyword_included) { + if (!keyword) { + ecommunity_free(&ecom); + return NULL; + } + keyword = 0; + } + if (ecom == NULL) + ecom = ecommunity_new(); + eval.val[1] = type; + ecommunity_add_val_internal(ecom, (void *)&eval, + false, false, + ecom->unit_size); + break; + case ecommunity_token_val6: + if (keyword_included) { + if (!keyword) { + ecommunity_free(&ecom); + return NULL; + } + keyword = 0; + } + if (ecom == NULL) + ecom = ecommunity_new(); + ecom->unit_size = IPV6_ECOMMUNITY_SIZE; + eval.val[1] = type; + ecommunity_add_val_internal(ecom, (void *)&eval, false, false, + ecom->unit_size); + break; + case ecommunity_token_unknown: + if (ecom) + ecommunity_free(&ecom); + return NULL; + } + } + return ecom; +} + +/* Convert string to extended community attribute. + * + * When type is already known, please specify both str and type. str + * should not include keyword such as "rt" and "soo". Type is + * ECOMMUNITY_ROUTE_TARGET or ECOMMUNITY_SITE_ORIGIN. + * keyword_included should be zero. + * + * For example route-map's "set extcommunity" command case: + * + * "rt 100:1 100:2 100:3" -> str = "100:1 100:2 100:3" + * type = ECOMMUNITY_ROUTE_TARGET + * keyword_included = 0 + * + * "soo 100:1" -> str = "100:1" + * type = ECOMMUNITY_SITE_ORIGIN + * keyword_included = 0 + * + * When string includes keyword for each extended community value. + * Please specify keyword_included as non-zero value. + * + * For example standard extcommunity-list case: + * + * "rt 100:1 rt 100:2 soo 100:1" -> str = "rt 100:1 rt 100:2 soo 100:1" + * type = 0 + * keyword_include = 1 + */ +struct ecommunity *ecommunity_str2com(const char *str, int type, + int keyword_included) +{ + return ecommunity_str2com_internal(str, type, + keyword_included, false); +} + +struct ecommunity *ecommunity_str2com_ipv6(const char *str, int type, + int keyword_included) +{ + return ecommunity_str2com_internal(str, type, + keyword_included, true); +} + +static int ecommunity_rt_soo_str_internal(char *buf, size_t bufsz, + const uint8_t *pnt, int type, + int sub_type, int format, + unsigned short ecom_size) +{ + int len = 0; + const char *prefix; + char buf_local[INET6_ADDRSTRLEN]; + + /* For parse Extended Community attribute tupple. */ + struct ecommunity_as eas; + struct ecommunity_ip eip; + struct ecommunity_ip6 eip6; + + /* Determine prefix for string, if any. */ + switch (format) { + case ECOMMUNITY_FORMAT_COMMUNITY_LIST: + prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "rt " : "soo "); + break; + case ECOMMUNITY_FORMAT_DISPLAY: + prefix = (sub_type == ECOMMUNITY_ROUTE_TARGET ? "RT:" : "SoO:"); + break; + case ECOMMUNITY_FORMAT_ROUTE_MAP: + prefix = ""; + break; + default: + prefix = ""; + break; + } + + /* Put string into buffer. */ + if (type == ECOMMUNITY_ENCODE_AS4) { + pnt = ptr_get_be32(pnt, &eas.as); + eas.val = (*pnt++ << 8); + eas.val |= (*pnt++); + + len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as, eas.val); + } else if (type == ECOMMUNITY_ENCODE_AS) { + if (ecom_size == ECOMMUNITY_SIZE) { + eas.as = (*pnt++ << 8); + eas.as |= (*pnt++); + pnt = ptr_get_be32(pnt, &eas.val); + + len = snprintf(buf, bufsz, "%s%u:%u", prefix, eas.as, + eas.val); + } else { + /* this is an IPv6 ext community + * first 16 bytes stands for IPv6 addres + */ + memcpy(&eip6.ip, pnt, 16); + pnt += 16; + eip6.val = (*pnt++ << 8); + eip6.val |= (*pnt++); + + inet_ntop(AF_INET6, &eip6.ip, buf_local, + sizeof(buf_local)); + len = snprintf(buf, bufsz, "%s%s:%u", prefix, + buf_local, eip6.val); + } + } else if (type == ECOMMUNITY_ENCODE_IP) { + memcpy(&eip.ip, pnt, 4); + pnt += 4; + eip.val = (*pnt++ << 8); + eip.val |= (*pnt++); + + len = snprintfrr(buf, bufsz, "%s%pI4:%u", prefix, &eip.ip, + eip.val); + } + + /* consume value */ + (void)pnt; + + return len; +} + +static int ecommunity_rt_soo_str(char *buf, size_t bufsz, const uint8_t *pnt, + int type, int sub_type, int format) +{ + return ecommunity_rt_soo_str_internal(buf, bufsz, pnt, type, + sub_type, format, + ECOMMUNITY_SIZE); +} + +/* Helper function to convert IEEE-754 Floating Point to uint32 */ +static uint32_t ieee_float_uint32_to_uint32(uint32_t u) +{ + union { + float r; + uint32_t d; + } f = {.d = u}; + + return (uint32_t)f.r; +} + +static int ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt, + bool disable_ieee_floating) +{ + int len = 0; + as_t as; + uint32_t bw_tmp, bw; + char bps_buf[20] = {0}; + + as = (*pnt++ << 8); + as |= (*pnt++); + (void)ptr_get_be32(pnt, &bw_tmp); + + bw = disable_ieee_floating ? bw_tmp + : ieee_float_uint32_to_uint32(bw_tmp); + + if (bw >= ONE_GBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Gbps", + (float)(bw / ONE_GBPS_BYTES)); + else if (bw >= ONE_MBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Mbps", + (float)(bw / ONE_MBPS_BYTES)); + else if (bw >= ONE_KBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Kbps", + (float)(bw / ONE_KBPS_BYTES)); + else + snprintf(bps_buf, sizeof(bps_buf), "%u bps", bw * 8); + + len = snprintf(buf, bufsz, "LB:%u:%u (%s)", as, bw, bps_buf); + return len; +} + +static int ipv6_ecommunity_lb_str(char *buf, size_t bufsz, const uint8_t *pnt, + size_t length) +{ + int len = 0; + as_t as = 0; + uint64_t bw = 0; + char bps_buf[20] = { 0 }; + + if (length < IPV6_ECOMMUNITY_SIZE) + goto done; + + pnt += 2; /* Reserved */ + pnt = ptr_get_be64(pnt, &bw); + (void)ptr_get_be32(pnt, &as); + + if (bw >= ONE_GBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Gbps", + (float)(bw / ONE_GBPS_BYTES)); + else if (bw >= ONE_MBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Mbps", + (float)(bw / ONE_MBPS_BYTES)); + else if (bw >= ONE_KBPS_BYTES) + snprintf(bps_buf, sizeof(bps_buf), "%.3f Kbps", + (float)(bw / ONE_KBPS_BYTES)); + else + snprintfrr(bps_buf, sizeof(bps_buf), "%" PRIu64 " bps", bw * 8); + +done: + len = snprintfrr(buf, bufsz, "LB:%u:%" PRIu64 " (%s)", as, bw, bps_buf); + return len; +} + +bool ecommunity_has_route_target(struct ecommunity *ecom) +{ + uint32_t i; + uint8_t *pnt; + uint8_t type = 0; + uint8_t sub_type = 0; + + if (!ecom) + return false; + for (i = 0; i < ecom->size; i++) { + /* Retrieve value field */ + pnt = ecom->val + (i * ecom->unit_size); + + /* High-order octet is the type */ + type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_AS || + type == ECOMMUNITY_ENCODE_IP || + type == ECOMMUNITY_ENCODE_AS4) { + /* Low-order octet of type. */ + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_ROUTE_TARGET) + return true; + } + } + return false; +} + +/* Convert extended community attribute to string. + * Due to historical reason of industry standard implementation, there + * are three types of format: + * + * route-map set extcommunity format: + * "rt 100:1 100:2soo 100:3" + * + * extcommunity-list: + * "rt 100:1 rt 100:2 soo 100:3" + * + * show bgp: + * "RT:100:1 RT:100:2 SoO:100:3" + * + * For each format please use below definition for format: + * ECOMMUNITY_FORMAT_ROUTE_MAP + * ECOMMUNITY_FORMAT_COMMUNITY_LIST + * ECOMMUNITY_FORMAT_DISPLAY + * + * Filter is added to display only ECOMMUNITY_ROUTE_TARGET in some cases. + * 0 value displays all. + */ +char *ecommunity_ecom2str(struct ecommunity *ecom, int format, int filter) +{ + uint32_t i; + uint8_t *pnt; + uint8_t type = 0; + uint8_t sub_type = 0; + int str_size; + char *str_buf; + + if (!ecom || ecom->size == 0) + return XCALLOC(MTYPE_ECOMMUNITY_STR, 1); + + /* ecom strlen + space + null term */ + str_size = (ecom->size * (ECOMMUNITY_STRLEN + 1)) + 1; + str_buf = XCALLOC(MTYPE_ECOMMUNITY_STR, str_size); + + char encbuf[128]; + + for (i = 0; i < ecom->size; i++) { + bool unk_ecom = false; + memset(encbuf, 0x00, sizeof(encbuf)); + + /* Space between each value. */ + if (i > 0) + strlcat(str_buf, " ", str_size); + + /* Retrieve value field */ + pnt = ecom->val + (i * ecom->unit_size); + + uint8_t *data = pnt; + uint8_t *end = data + ecom->unit_size; + size_t len = end - data; + + /* Sanity check for extended communities lenght, to avoid + * overrun when dealing with bits, e.g. ptr_get_be64(). + */ + if (len < ecom->unit_size) { + unk_ecom = true; + goto unknown; + } + + /* High-order octet is the type */ + type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_AS || type == ECOMMUNITY_ENCODE_IP + || type == ECOMMUNITY_ENCODE_AS4) { + /* Low-order octet of type. */ + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET + && sub_type != ECOMMUNITY_SITE_ORIGIN) { + if (sub_type == + ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 && + type == ECOMMUNITY_ENCODE_IP) { + struct in_addr *ipv4 = + (struct in_addr *)pnt; + snprintfrr(encbuf, sizeof(encbuf), + "NH:%pI4:%d", ipv4, pnt[5]); + } else if (sub_type == + ECOMMUNITY_LINK_BANDWIDTH && + type == ECOMMUNITY_ENCODE_AS) { + ecommunity_lb_str( + encbuf, sizeof(encbuf), pnt, + ecom->disable_ieee_floating); + } else if (sub_type == + ECOMMUNITY_EXTENDED_LINK_BANDWIDTH && + type == ECOMMUNITY_ENCODE_AS4) { + ipv6_ecommunity_lb_str(encbuf, + sizeof(encbuf), + pnt, len); + } else if (sub_type == ECOMMUNITY_NODE_TARGET && + type == ECOMMUNITY_ENCODE_IP) { + ecommunity_node_target_str( + encbuf, sizeof(encbuf), pnt, + format); + } else + unk_ecom = true; + } else { + ecommunity_rt_soo_str(encbuf, sizeof(encbuf), + pnt, type, sub_type, + format); + } + } else if (type == ECOMMUNITY_ENCODE_OPAQUE) { + if (filter == ECOMMUNITY_ROUTE_TARGET) + continue; + if (*pnt == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP) { + uint16_t tunneltype; + memcpy(&tunneltype, pnt + 5, 2); + tunneltype = ntohs(tunneltype); + + snprintf(encbuf, sizeof(encbuf), "ET:%d", + tunneltype); + } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_DEF_GW) { + strlcpy(encbuf, "Default Gateway", + sizeof(encbuf)); + } else if (*pnt == ECOMMUNITY_COLOR) { + ecommunity_color_str(encbuf, sizeof(encbuf), + pnt); + } else { + unk_ecom = true; + } + } else if (type == ECOMMUNITY_ENCODE_EVPN) { + if (filter == ECOMMUNITY_ROUTE_TARGET) + continue; + if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC) { + struct ethaddr rmac; + pnt++; + memcpy(&rmac, pnt, ETH_ALEN); + + snprintf(encbuf, sizeof(encbuf), + "Rmac:%02x:%02x:%02x:%02x:%02x:%02x", + (uint8_t)rmac.octet[0], + (uint8_t)rmac.octet[1], + (uint8_t)rmac.octet[2], + (uint8_t)rmac.octet[3], + (uint8_t)rmac.octet[4], + (uint8_t)rmac.octet[5]); + } else if (*pnt + == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY) { + uint32_t seqnum; + uint8_t flags = *++pnt; + + memcpy(&seqnum, pnt + 2, 4); + seqnum = ntohl(seqnum); + + snprintf(encbuf, sizeof(encbuf), "MM:%u", + seqnum); + + if (CHECK_FLAG( + flags, + ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY)) + strlcat(encbuf, ", sticky MAC", + sizeof(encbuf)); + } else if (*pnt == ECOMMUNITY_EVPN_SUBTYPE_ND) { + uint8_t flags = *++pnt; + + if (CHECK_FLAG( + flags, + ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG)) + strlcpy(encbuf, "ND:Router Flag", + sizeof(encbuf)); + if (CHECK_FLAG( + flags, + ECOMMUNITY_EVPN_SUBTYPE_PROXY_FLAG)) + strlcpy(encbuf, "ND:Proxy", + sizeof(encbuf)); + } else if (*pnt + == ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT) { + struct ethaddr mac; + + pnt++; + memcpy(&mac, pnt, ETH_ALEN); + snprintf(encbuf, + sizeof(encbuf), + "ES-Import-Rt:%02x:%02x:%02x:%02x:%02x:%02x", + (uint8_t)mac.octet[0], + (uint8_t)mac.octet[1], + (uint8_t)mac.octet[2], + (uint8_t)mac.octet[3], + (uint8_t)mac.octet[4], + (uint8_t)mac.octet[5]); + } else if (*pnt + == ECOMMUNITY_EVPN_SUBTYPE_ESI_LABEL) { + uint8_t flags = *++pnt; + + snprintf(encbuf, + sizeof(encbuf), "ESI-label-Rt:%s", + (flags & + ECOMMUNITY_EVPN_SUBTYPE_ESI_SA_FLAG) ? + "SA":"AA"); + } else if (*pnt + == ECOMMUNITY_EVPN_SUBTYPE_DF_ELECTION) { + uint8_t alg; + uint16_t pref; + uint16_t bmap; + + alg = *(pnt + 1); + memcpy(&bmap, pnt + 2, 2); + bmap = ntohs(bmap); + memcpy(&pref, pnt + 5, 2); + pref = ntohs(pref); + + if (bmap) + snprintf( + encbuf, sizeof(encbuf), + "DF: (alg: %u, bmap: 0x%x pref: %u)", + alg, bmap, pref); + else + snprintf(encbuf, sizeof(encbuf), + "DF: (alg: %u, pref: %u)", alg, + pref); + } else + unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_REDIRECT_IP_NH) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_REDIRECT_IP_NH) { + snprintf(encbuf, sizeof(encbuf), + "FS:redirect IP 0x%x", *(pnt + 5)); + } else + unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_TRANS_EXP || + type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 || + type == ECOMMUNITY_EXTENDED_COMMUNITY_PART_3) { + sub_type = *pnt++; + + if (sub_type == ECOMMUNITY_ROUTE_TARGET) { + char buf[ECOMMUNITY_STRLEN]; + + memset(buf, 0, sizeof(buf)); + ecommunity_rt_soo_str_internal(buf, sizeof(buf), + (const uint8_t *)pnt, + type & + ~ECOMMUNITY_ENCODE_TRANS_EXP, + ECOMMUNITY_ROUTE_TARGET, + format, + ecom->unit_size); + snprintf(encbuf, sizeof(encbuf), "%s", buf); + } else if (sub_type == + ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) { + char buf[64]; + + memset(buf, 0, sizeof(buf)); + ecommunity_rt_soo_str_internal(buf, sizeof(buf), + (const uint8_t *)pnt, + type & + ~ECOMMUNITY_ENCODE_TRANS_EXP, + ECOMMUNITY_ROUTE_TARGET, + ECOMMUNITY_FORMAT_DISPLAY, + ecom->unit_size); + snprintf(encbuf, sizeof(encbuf), + "FS:redirect VRF %s", buf); + } else if (sub_type == ECOMMUNITY_REDIRECT_VRF) { + char buf[16]; + + memset(buf, 0, sizeof(buf)); + ecommunity_rt_soo_str(buf, sizeof(buf), + (const uint8_t *)pnt, + type & + ~ECOMMUNITY_ENCODE_TRANS_EXP, + ECOMMUNITY_ROUTE_TARGET, + ECOMMUNITY_FORMAT_DISPLAY); + snprintf(encbuf, sizeof(encbuf), + "FS:redirect VRF %s", buf); + snprintf(encbuf, sizeof(encbuf), + "FS:redirect VRF %s", buf); + } else if (type != ECOMMUNITY_ENCODE_TRANS_EXP) + unk_ecom = true; + else if (sub_type == ECOMMUNITY_TRAFFIC_ACTION) { + char action[64]; + + if (*(pnt+3) == + 1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL) + strlcpy(action, "terminate (apply)", + sizeof(action)); + else + strlcpy(action, "eval stops", + sizeof(action)); + + if (*(pnt+3) == + 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE) + strlcat(action, ", sample", + sizeof(action)); + + + snprintf(encbuf, sizeof(encbuf), "FS:action %s", + action); + } else if (sub_type == ECOMMUNITY_TRAFFIC_RATE) { + union traffic_rate data; + + data.rate_byte[3] = *(pnt+2); + data.rate_byte[2] = *(pnt+3); + data.rate_byte[1] = *(pnt+4); + data.rate_byte[0] = *(pnt+5); + snprintf(encbuf, sizeof(encbuf), "FS:rate %f", + data.rate_float); + } else if (sub_type == ECOMMUNITY_TRAFFIC_MARKING) { + snprintf(encbuf, sizeof(encbuf), + "FS:marking %u", *(pnt + 5)); + } else + unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_AS_NON_TRANS) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_LINK_BANDWIDTH) + ecommunity_lb_str(encbuf, sizeof(encbuf), pnt, + ecom->disable_ieee_floating); + else if (sub_type == ECOMMUNITY_EXTENDED_LINK_BANDWIDTH) + ipv6_ecommunity_lb_str(encbuf, sizeof(encbuf), + pnt, len); + else + unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_IP_NON_TRANS) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_NODE_TARGET) + ecommunity_node_target_str( + encbuf, sizeof(encbuf), pnt, format); + else + unk_ecom = true; + } else if (type == ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS) { + sub_type = *pnt++; + if (sub_type == ECOMMUNITY_ORIGIN_VALIDATION_STATE) + ecommunity_origin_validation_state_str( + encbuf, sizeof(encbuf), pnt); + else + unk_ecom = true; + } else { + sub_type = *pnt++; + unk_ecom = true; + } + +unknown: + if (unk_ecom) + snprintf(encbuf, sizeof(encbuf), "UNK:%d, %d", type, + sub_type); + + int r = strlcat(str_buf, encbuf, str_size); + assert(r < str_size); + } + + return str_buf; +} + +bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2) +{ + uint32_t i, j; + + if (!e1 || !e2) + return false; + for (i = 0; i < e1->size; ++i) { + for (j = 0; j < e2->size; ++j) { + if (!memcmp(e1->val + (i * e1->unit_size), + e2->val + (j * e2->unit_size), + e1->unit_size)) + return true; + } + } + return false; +} + +bool ecommunity_match(const struct ecommunity *ecom1, + const struct ecommunity *ecom2) +{ + uint32_t i = 0; + uint32_t j = 0; + + if (ecom1 == NULL && ecom2 == NULL) + return true; + + if (ecom1 == NULL || ecom2 == NULL) + return false; + + if (ecom1->size < ecom2->size) + return false; + + /* Every community on com2 needs to be on com1 for this to match */ + while (i < ecom1->size && j < ecom2->size) { + if (memcmp(ecom1->val + i * ecom1->unit_size, + ecom2->val + j * ecom2->unit_size, + ecom2->unit_size) + == 0) + j++; + i++; + } + + if (j == ecom2->size) + return true; + else + return false; +} + +/* return last occurence of color */ +/* it will be the greatest color value */ +extern uint32_t ecommunity_select_color(const struct ecommunity *ecom) +{ + + uint32_t aux_color = 0; + uint8_t *p; + uint32_t c = 0; + + /* If the value already exists in the structure return 0. */ + + for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) { + if (p == NULL) + break; + + if (p[0] == ECOMMUNITY_ENCODE_OPAQUE && + p[1] == ECOMMUNITY_COLOR) + ptr_get_be32((const uint8_t *)&p[4], &aux_color); + } + return aux_color; +} + + +/* return first occurence of type */ +extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *ecom, + uint8_t type, uint8_t subtype) +{ + uint8_t *p; + uint32_t c; + + /* If the value already exists in the structure return 0. */ + c = 0; + for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) { + if (p == NULL) { + continue; + } + if (p[0] == type && p[1] == subtype) + return (struct ecommunity_val *)p; + } + return NULL; +} + +/* remove ext. community matching type and subtype + * return 1 on success ( removed ), 0 otherwise (not present) + */ +bool ecommunity_strip(struct ecommunity *ecom, uint8_t type, + uint8_t subtype) +{ + uint8_t *p, *q, *new; + uint32_t c, found = 0; + /* When this is fist value, just add it. */ + if (ecom == NULL || ecom->val == NULL) + return false; + + /* Check if any existing ext community matches. */ + /* Certain extended communities like the Route Target can be present + * multiple times, handle that. + */ + c = 0; + for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) { + if (p[0] == type && p[1] == subtype) + found++; + } + /* If no matching ext community exists, return. */ + if (found == 0) + return false; + + /* Handle the case where everything needs to be stripped. */ + if (found == ecom->size) { + XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val); + ecom->size = 0; + return true; + } + + /* Strip matching ext community(ies). */ + new = XMALLOC(MTYPE_ECOMMUNITY_VAL, + (ecom->size - found) * ecom->unit_size); + q = new; + for (c = 0, p = ecom->val; c < ecom->size; c++, p += ecom->unit_size) { + if (!(p[0] == type && p[1] == subtype)) { + memcpy(q, p, ecom->unit_size); + q += ecom->unit_size; + } + } + XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val); + ecom->val = new; + ecom->size -= found; + return true; +} + +/* + * Remove specified extended community value from extended community. + * Returns 1 if value was present (and hence, removed), 0 otherwise. + */ +bool ecommunity_del_val(struct ecommunity *ecom, struct ecommunity_val *eval) +{ + uint8_t *p; + uint32_t c, found = 0; + + /* Make sure specified value exists. */ + if (ecom == NULL || ecom->val == NULL) + return false; + c = 0; + for (p = ecom->val; c < ecom->size; p += ecom->unit_size, c++) { + if (!memcmp(p, eval->val, ecom->unit_size)) { + found = 1; + break; + } + } + if (found == 0) + return false; + + /* Delete the selected value */ + ecom->size--; + if (ecom->size) { + p = XMALLOC(MTYPE_ECOMMUNITY_VAL, ecom->size * ecom->unit_size); + if (c != 0) + memcpy(p, ecom->val, c * ecom->unit_size); + if ((ecom->size - c) != 0) + memcpy(p + (c)*ecom->unit_size, + ecom->val + (c + 1) * ecom->unit_size, + (ecom->size - c) * ecom->unit_size); + XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val); + ecom->val = p; + } else + XFREE(MTYPE_ECOMMUNITY_VAL, ecom->val); + + return true; +} + +int ecommunity_fill_pbr_action(struct ecommunity_val *ecom_eval, + struct bgp_pbr_entry_action *api, + afi_t afi) +{ + if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_RATE) { + api->action = ACTION_TRAFFICRATE; + api->u.r.rate_info[3] = ecom_eval->val[4]; + api->u.r.rate_info[2] = ecom_eval->val[5]; + api->u.r.rate_info[1] = ecom_eval->val[6]; + api->u.r.rate_info[0] = ecom_eval->val[7]; + } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_ACTION) { + api->action = ACTION_TRAFFIC_ACTION; + /* else distribute code is set by default */ + if (ecom_eval->val[5] & (1 << FLOWSPEC_TRAFFIC_ACTION_TERMINAL)) + api->u.za.filter |= TRAFFIC_ACTION_TERMINATE; + else + api->u.za.filter |= TRAFFIC_ACTION_DISTRIBUTE; + if (ecom_eval->val[5] == 1 << FLOWSPEC_TRAFFIC_ACTION_SAMPLE) + api->u.za.filter |= TRAFFIC_ACTION_SAMPLE; + + } else if (ecom_eval->val[1] == ECOMMUNITY_TRAFFIC_MARKING) { + api->action = ACTION_MARKING; + api->u.marking_dscp = ecom_eval->val[7]; + } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_VRF) { + /* must use external function */ + return 0; + } else if (ecom_eval->val[1] == ECOMMUNITY_REDIRECT_IP_NH && + afi == AFI_IP) { + /* see draft-ietf-idr-flowspec-redirect-ip-02 + * Q1: how come a ext. community can host ipv6 address + * Q2 : from cisco documentation: + * Announces the reachability of one or more flowspec NLRI. + * When a BGP speaker receives an UPDATE message with the + * redirect-to-IP extended community, it is expected to + * create a traffic filtering rule for every flow-spec + * NLRI in the message that has this path as its best + * path. The filter entry matches the IP packets + * described in the NLRI field and redirects them or + * copies them towards the IPv4 or IPv6 address specified + * in the 'Network Address of Next- Hop' + * field of the associated MP_REACH_NLRI. + */ + struct ecommunity_ip *ip_ecom = + (struct ecommunity_ip *)&ecom_eval->val[2]; + + api->u.zr.redirect_ip_v4 = ip_ecom->ip; + } else + return -1; + return 0; +} + +static struct ecommunity *bgp_aggr_ecommunity_lookup( + struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity) +{ + return hash_lookup(aggregate->ecommunity_hash, ecommunity); +} + +static void *bgp_aggr_ecommunty_hash_alloc(void *p) +{ + struct ecommunity *ref = (struct ecommunity *)p; + struct ecommunity *ecommunity = NULL; + + ecommunity = ecommunity_dup(ref); + return ecommunity; +} + +static void bgp_aggr_ecommunity_prepare(struct hash_bucket *hb, void *arg) +{ + struct ecommunity *hb_ecommunity = hb->data; + struct ecommunity **aggr_ecommunity = arg; + + if (*aggr_ecommunity) + *aggr_ecommunity = ecommunity_merge(*aggr_ecommunity, + hb_ecommunity); + else + *aggr_ecommunity = ecommunity_dup(hb_ecommunity); +} + +void bgp_aggr_ecommunity_remove(void *arg) +{ + struct ecommunity *ecommunity = arg; + + ecommunity_free(&ecommunity); +} + +void bgp_compute_aggregate_ecommunity(struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity) +{ + bgp_compute_aggregate_ecommunity_hash(aggregate, ecommunity); + bgp_compute_aggregate_ecommunity_val(aggregate); +} + + +void bgp_compute_aggregate_ecommunity_hash(struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity) +{ + struct ecommunity *aggr_ecommunity = NULL; + + if ((aggregate == NULL) || (ecommunity == NULL)) + return; + + /* Create hash if not already created. + */ + if (aggregate->ecommunity_hash == NULL) + aggregate->ecommunity_hash = hash_create( + ecommunity_hash_make, ecommunity_cmp, + "BGP Aggregator ecommunity hash"); + + aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity); + if (aggr_ecommunity == NULL) { + /* Insert ecommunity into hash. + */ + aggr_ecommunity = hash_get(aggregate->ecommunity_hash, + ecommunity, + bgp_aggr_ecommunty_hash_alloc); + } + + /* Increment reference counter. + */ + aggr_ecommunity->refcnt++; +} + +void bgp_compute_aggregate_ecommunity_val(struct bgp_aggregate *aggregate) +{ + struct ecommunity *ecommerge = NULL; + + if (aggregate == NULL) + return; + + /* Re-compute aggregate's ecommunity. + */ + if (aggregate->ecommunity) + ecommunity_free(&aggregate->ecommunity); + if (aggregate->ecommunity_hash + && aggregate->ecommunity_hash->count) { + hash_iterate(aggregate->ecommunity_hash, + bgp_aggr_ecommunity_prepare, + &aggregate->ecommunity); + ecommerge = aggregate->ecommunity; + aggregate->ecommunity = ecommunity_uniq_sort(ecommerge); + if (ecommerge) + ecommunity_free(&ecommerge); + } +} + +void bgp_remove_ecommunity_from_aggregate(struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity) +{ + struct ecommunity *aggr_ecommunity = NULL; + struct ecommunity *ret_ecomm = NULL; + + if ((!aggregate) + || (!aggregate->ecommunity_hash) + || (!ecommunity)) + return; + + /* Look-up the ecommunity in the hash. + */ + aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity); + if (aggr_ecommunity) { + aggr_ecommunity->refcnt--; + + if (aggr_ecommunity->refcnt == 0) { + ret_ecomm = hash_release(aggregate->ecommunity_hash, + aggr_ecommunity); + ecommunity_free(&ret_ecomm); + bgp_compute_aggregate_ecommunity_val(aggregate); + } + } +} + +void bgp_remove_ecomm_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity) +{ + + struct ecommunity *aggr_ecommunity = NULL; + struct ecommunity *ret_ecomm = NULL; + + if ((!aggregate) + || (!aggregate->ecommunity_hash) + || (!ecommunity)) + return; + + /* Look-up the ecommunity in the hash. + */ + aggr_ecommunity = bgp_aggr_ecommunity_lookup(aggregate, ecommunity); + if (aggr_ecommunity) { + aggr_ecommunity->refcnt--; + + if (aggr_ecommunity->refcnt == 0) { + ret_ecomm = hash_release(aggregate->ecommunity_hash, + aggr_ecommunity); + ecommunity_free(&ret_ecomm); + } + } +} + +struct ecommunity * +ecommunity_add_origin_validation_state(enum rpki_states rpki_state, + struct ecommunity *old) +{ + struct ecommunity *new = NULL; + struct ecommunity ovs_ecomm = {0}; + struct ecommunity_val ovs_eval; + + encode_origin_validation_state(rpki_state, &ovs_eval); + + if (old) { + new = ecommunity_dup(old); + ecommunity_add_val(new, &ovs_eval, true, true); + if (!old->refcnt) + ecommunity_free(&old); + } else { + ovs_ecomm.size = 1; + ovs_ecomm.unit_size = ECOMMUNITY_SIZE; + ovs_ecomm.val = (uint8_t *)&ovs_eval.val; + new = ecommunity_dup(&ovs_ecomm); + } + + return new; +} + +/* + * return the BGP link bandwidth extended community, if present; + * the actual bandwidth is returned via param + */ +const uint8_t *ecommunity_linkbw_present(struct ecommunity *ecom, uint64_t *bw) +{ + const uint8_t *data; + uint32_t i; + + if (bw) + *bw = 0; + + if (!ecom || !ecom->size) + return NULL; + + for (i = 0; i < ecom->size; i++) { + const uint8_t *pnt; + uint8_t type, sub_type; + + data = pnt = (ecom->val + (i * ecom->unit_size)); + type = *pnt++; + sub_type = *pnt++; + + const uint8_t *end = data + ecom->unit_size; + size_t len = end - data; + + /* Sanity check for extended communities lenght, to avoid + * overrun when dealing with bits, e.g. ptr_get_be64(). + */ + if (len < ecom->unit_size) + return NULL; + + if ((type == ECOMMUNITY_ENCODE_AS || + type == ECOMMUNITY_ENCODE_AS_NON_TRANS) && + sub_type == ECOMMUNITY_LINK_BANDWIDTH) { + uint32_t bwval; + + pnt += 2; /* bandwidth is encoded as AS:val */ + pnt = ptr_get_be32(pnt, &bwval); + (void)pnt; /* consume value */ + if (bw) + *bw = (uint64_t)(ecom->disable_ieee_floating + ? bwval + : ieee_float_uint32_to_uint32( + bwval)); + return data; + } else if (type == ECOMMUNITY_ENCODE_AS4 && + sub_type == ECOMMUNITY_EXTENDED_LINK_BANDWIDTH) { + uint64_t bwval; + + if (len < IPV6_ECOMMUNITY_SIZE) + return NULL; + + pnt += 2; /* Reserved */ + pnt = ptr_get_be64(pnt, &bwval); + (void)pnt; + + if (bw) + *bw = bwval; + + return data; + } + } + + return NULL; +} + + +struct ecommunity *ecommunity_replace_linkbw(as_t as, struct ecommunity *ecom, + uint64_t cum_bw, + bool disable_ieee_floating, + bool extended) +{ + struct ecommunity *new; + const uint8_t *eval; + uint8_t type; + uint64_t cur_bw; + + /* Nothing to replace if link-bandwidth doesn't exist or + * is non-transitive - just return existing extcommunity. + */ + new = ecom; + if (!ecom || !ecom->size) + return new; + + eval = ecommunity_linkbw_present(ecom, &cur_bw); + if (!eval) + return new; + + type = *eval; + if (type & ECOMMUNITY_FLAG_NON_TRANSITIVE) + return new; + + /* Transitive link-bandwidth exists, replace with the passed + * (cumulative) bandwidth value. We need to create a new + * extcommunity for this - refer to AS-Path replace function + * for reference. + */ + if (cum_bw > 0xFFFFFFFF) + cum_bw = 0xFFFFFFFF; + + if (extended) { + struct ecommunity_val_ipv6 lb_eval; + + encode_lb_extended_extcomm(as, cum_bw, false, &lb_eval); + new = ecommunity_dup(ecom); + ecommunity_add_val_ipv6(new, &lb_eval, true, true); + } else { + struct ecommunity_val lb_eval; + + encode_lb_extcomm(as > BGP_AS_MAX ? BGP_AS_TRANS : as, cum_bw, + false, &lb_eval, disable_ieee_floating); + new = ecommunity_dup(ecom); + ecommunity_add_val(new, &lb_eval, true, true); + } + + return new; +} + +bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo) +{ + if (ecom && soo) { + if ((ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_AS4, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecom, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_SITE_ORIGIN)) && + ecommunity_include(ecom, soo)) + return true; + } + return false; +} diff --git a/bgpd/bgp_ecommunity.h b/bgpd/bgp_ecommunity.h new file mode 100644 index 0000000..929e4e6 --- /dev/null +++ b/bgpd/bgp_ecommunity.h @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Extended Communities Attribute. + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ECOMMUNITY_H +#define _QUAGGA_BGP_ECOMMUNITY_H + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_rpki.h" +#include "bgpd/bgpd.h" + +#define ONE_GBPS_BYTES (1000 * 1000 * 1000 / 8) +#define ONE_MBPS_BYTES (1000 * 1000 / 8) +#define ONE_KBPS_BYTES (1000 / 8) + +/* Refer to rfc7153 for the IANA registry definitions. These are + * updated by other standards like rfc7674. + */ +/* High-order octet of the Extended Communities type field. */ +#define ECOMMUNITY_ENCODE_AS 0x00 +#define ECOMMUNITY_ENCODE_IP 0x01 +#define ECOMMUNITY_ENCODE_AS4 0x02 +#define ECOMMUNITY_ENCODE_OPAQUE 0x03 +#define ECOMMUNITY_ENCODE_EVPN 0x06 +#define ECOMMUNITY_ENCODE_REDIRECT_IP_NH 0x08 /* Flow Spec */ +/* Generic Transitive Experimental */ +#define ECOMMUNITY_ENCODE_TRANS_EXP 0x80 + +/* RFC7674 */ +#define ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 0x81 +#define ECOMMUNITY_EXTENDED_COMMUNITY_PART_3 0x82 + +/* Non-transitive extended community types. */ +#define ECOMMUNITY_ENCODE_AS_NON_TRANS 0x40 +#define ECOMMUNITY_ENCODE_IP_NON_TRANS 0x41 +#define ECOMMUNITY_ENCODE_AS4_NON_TRANS 0x42 +#define ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS 0x43 + +/* Low-order octet of the Extended Communities type field. */ +/* Note: This really depends on the high-order octet. This means that + * multiple definitions for the same value are possible. + */ +#define ECOMMUNITY_ORIGIN_VALIDATION_STATE 0x00 +#define ECOMMUNITY_ROUTE_TARGET 0x02 +#define ECOMMUNITY_SITE_ORIGIN 0x03 +#define ECOMMUNITY_LINK_BANDWIDTH 0x04 +#define ECOMMUNITY_TRAFFIC_RATE 0x06 /* Flow Spec */ +#define ECOMMUNITY_TRAFFIC_ACTION 0x07 +#define ECOMMUNITY_REDIRECT_VRF 0x08 +#define ECOMMUNITY_TRAFFIC_MARKING 0x09 +#define ECOMMUNITY_REDIRECT_IP_NH 0x00 +#define ECOMMUNITY_COLOR 0x0b /* RFC9012 - color */ + +/* from IANA: bgp-extended-communities/bgp-extended-communities.xhtml + * 0x0c Flow-spec Redirect to IPv4 - draft-ietf-idr-flowspec-redirect + */ +#define ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4 0x0c +/* RFC 8956 */ +#define ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6 0x0d + +/* https://datatracker.ietf.org/doc/html/draft-li-idr-link-bandwidth-ext-01 + * Sub-type is allocated by IANA, just the draft is not yet updated with the + * new value. + */ +#define ECOMMUNITY_EXTENDED_LINK_BANDWIDTH 0x0006 + +/* Low-order octet of the Extended Communities type field for EVPN types */ +#define ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY 0x00 +#define ECOMMUNITY_EVPN_SUBTYPE_ESI_LABEL 0x01 +#define ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT 0x02 +#define ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC 0x03 +#define ECOMMUNITY_EVPN_SUBTYPE_DF_ELECTION 0x06 +#define ECOMMUNITY_EVPN_SUBTYPE_DEF_GW 0x0d +#define ECOMMUNITY_EVPN_SUBTYPE_ND 0x08 + +#define ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY 0x01 + +/* DF alg bits - only lower 5 bits are applicable */ +#define ECOMMUNITY_EVPN_SUBTYPE_DF_ALG_BITS 0x1f + +#define ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG 0x01 +#define ECOMMUNITY_EVPN_SUBTYPE_ND_OVERRIDE_FLAG 0x02 +#define ECOMMUNITY_EVPN_SUBTYPE_PROXY_FLAG 0x04 + +#define ECOMMUNITY_EVPN_SUBTYPE_ESI_SA_FLAG (1 << 0) /* single-active */ + +/* Low-order octet of the Extended Communities type field for OPAQUE types */ +#define ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP 0x0c + +/* Extended communities attribute string format. */ +#define ECOMMUNITY_FORMAT_ROUTE_MAP 0 +#define ECOMMUNITY_FORMAT_COMMUNITY_LIST 1 +#define ECOMMUNITY_FORMAT_DISPLAY 2 + +/* Extended Communities value is eight octet long. */ +#define ECOMMUNITY_SIZE 8 +#define IPV6_ECOMMUNITY_SIZE 20 + +/* Extended Community Origin Validation State */ +enum ecommunity_origin_validation_states { + ECOMMUNITY_ORIGIN_VALIDATION_STATE_VALID, + ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTFOUND, + ECOMMUNITY_ORIGIN_VALIDATION_STATE_INVALID, + ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTUSED +}; + +/* Extended Communities type flag. */ +#define ECOMMUNITY_FLAG_NON_TRANSITIVE 0x40 + +/* Extended Community readable string length */ +#define ECOMMUNITY_STRLEN 64 + +/* Node Target Extended Communities */ +#define ECOMMUNITY_NODE_TARGET 0x09 +#define ECOMMUNITY_NODE_TARGET_RESERVED 0 + +/* Extended Communities attribute. */ +struct ecommunity { + /* Reference counter. */ + unsigned long refcnt; + + /* Size of Each Unit of Extended Communities attribute. + * to differentiate between IPv6 ext comm and ext comm + */ + uint8_t unit_size; + + /* Disable IEEE floating-point encoding for extended community */ + bool disable_ieee_floating; + + /* Size of Extended Communities attribute. */ + uint32_t size; + + /* Extended Communities value. */ + uint8_t *val; + + /* Human readable format string. */ + char *str; +}; + +struct ecommunity_as { + as_t as; + uint32_t val; +}; + +struct ecommunity_ip { + struct in_addr ip; + uint16_t val; +}; + +struct ecommunity_ip6 { + struct in6_addr ip; + uint16_t val; +}; + +/* Extended community value is eight octet. */ +struct ecommunity_val { + char val[ECOMMUNITY_SIZE]; +}; + +/* IPv6 Extended community value is eight octet. */ +struct ecommunity_val_ipv6 { + char val[IPV6_ECOMMUNITY_SIZE]; +}; + +#define ecom_length_size(X, Y) ((X)->size * (Y)) + +/* + * Encode BGP Route Target AS:nn. + */ +static inline void encode_route_target_as(as_t as, uint32_t val, + struct ecommunity_val *eval, + bool trans) +{ + eval->val[0] = ECOMMUNITY_ENCODE_AS; + if (!trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = ECOMMUNITY_ROUTE_TARGET; + eval->val[2] = (as >> 8) & 0xff; + eval->val[3] = as & 0xff; + eval->val[4] = (val >> 24) & 0xff; + eval->val[5] = (val >> 16) & 0xff; + eval->val[6] = (val >> 8) & 0xff; + eval->val[7] = val & 0xff; +} + +/* + * Encode BGP Route Target IP:nn. + */ +static inline void encode_route_target_ip(struct in_addr *ip, uint16_t val, + struct ecommunity_val *eval, + bool trans) +{ + eval->val[0] = ECOMMUNITY_ENCODE_IP; + if (!trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = ECOMMUNITY_ROUTE_TARGET; + memcpy(&eval->val[2], ip, sizeof(struct in_addr)); + eval->val[6] = (val >> 8) & 0xff; + eval->val[7] = val & 0xff; +} + +/* + * Encode BGP Route Target AS4:nn. + */ +static inline void encode_route_target_as4(as_t as, uint16_t val, + struct ecommunity_val *eval, + bool trans) +{ + eval->val[0] = ECOMMUNITY_ENCODE_AS4; + if (!trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = ECOMMUNITY_ROUTE_TARGET; + eval->val[2] = (as >> 24) & 0xff; + eval->val[3] = (as >> 16) & 0xff; + eval->val[4] = (as >> 8) & 0xff; + eval->val[5] = as & 0xff; + eval->val[6] = (val >> 8) & 0xff; + eval->val[7] = val & 0xff; +} + +/* Helper function to convert uint32 to IEEE-754 Floating Point */ +static uint32_t uint32_to_ieee_float_uint32(uint32_t u) +{ + union { + float r; + uint32_t d; + } f = {.r = (float)u}; + + return f.d; +} + +/* + * Encode BGP Link Bandwidth extended community + * bandwidth (bw) is in bytes-per-sec + */ +static inline void encode_lb_extcomm(as_t as, uint64_t bw, bool non_trans, + struct ecommunity_val *eval, + bool disable_ieee_floating) +{ + uint64_t bandwidth = disable_ieee_floating + ? bw + : uint32_to_ieee_float_uint32(bw); + + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_AS; + if (non_trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = ECOMMUNITY_LINK_BANDWIDTH; + eval->val[2] = (as >> 8) & 0xff; + eval->val[3] = as & 0xff; + eval->val[4] = (bandwidth >> 24) & 0xff; + eval->val[5] = (bandwidth >> 16) & 0xff; + eval->val[6] = (bandwidth >> 8) & 0xff; + eval->val[7] = bandwidth & 0xff; +} + +/* + * Encode BGP Link Bandwidth inside IPv6 Extended Community, + * bandwidth is in bytes per second. + */ +static inline void encode_lb_extended_extcomm(as_t as, uint64_t bandwidth, + bool non_trans, + struct ecommunity_val_ipv6 *eval) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_AS4; + if (non_trans) + eval->val[0] |= ECOMMUNITY_FLAG_NON_TRANSITIVE; + eval->val[1] = ECOMMUNITY_EXTENDED_LINK_BANDWIDTH; + eval->val[4] = (bandwidth >> 56) & 0xff; + eval->val[5] = (bandwidth >> 48) & 0xff; + eval->val[6] = (bandwidth >> 40) & 0xff; + eval->val[7] = (bandwidth >> 32) & 0xff; + eval->val[8] = (bandwidth >> 24) & 0xff; + eval->val[9] = (bandwidth >> 16) & 0xff; + eval->val[10] = (bandwidth >> 8) & 0xff; + eval->val[11] = bandwidth & 0xff; + eval->val[12] = (as >> 24) & 0xff; + eval->val[13] = (as >> 16) & 0xff; + eval->val[14] = (as >> 8) & 0xff; + eval->val[15] = as & 0xff; +} + +static inline void encode_origin_validation_state(enum rpki_states state, + struct ecommunity_val *eval) +{ + enum ecommunity_origin_validation_states ovs_state = + ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTUSED; + + switch (state) { + case RPKI_VALID: + ovs_state = ECOMMUNITY_ORIGIN_VALIDATION_STATE_VALID; + break; + case RPKI_NOTFOUND: + ovs_state = ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTFOUND; + break; + case RPKI_INVALID: + ovs_state = ECOMMUNITY_ORIGIN_VALIDATION_STATE_INVALID; + break; + case RPKI_NOT_BEING_USED: + break; + } + + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS; + eval->val[1] = ECOMMUNITY_ORIGIN_VALIDATION_STATE; + eval->val[7] = ovs_state; +} + +static inline void encode_node_target(struct in_addr *node_id, + struct ecommunity_val *eval, bool trans) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x01 or 0x41 | Sub-Type(0x09) | Target BGP Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Target BGP Identifier (cont.) | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_IP; + if (!trans) + eval->val[0] |= ECOMMUNITY_ENCODE_IP_NON_TRANS; + eval->val[1] = ECOMMUNITY_NODE_TARGET; + memcpy(&eval->val[2], node_id, sizeof(*node_id)); + eval->val[6] = ECOMMUNITY_NODE_TARGET_RESERVED; + eval->val[7] = ECOMMUNITY_NODE_TARGET_RESERVED; +} + +/* + * Encode BGP Color extended community + * is's a transitive opaque Extended community (RFC 9012 4.3) + * flag is set to 0 + * RFC 9012 14.10: No values have currently been registered. + * 4.3: this field MUST be set to zero by the originator + * and ignored by the receiver; + * + */ +static inline void encode_color(uint32_t color_id, struct ecommunity_val *eval) +{ + /* + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | 0x03 | Sub-Type(0x0b) | Flags | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Color Value | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE; + eval->val[1] = ECOMMUNITY_COLOR; + eval->val[2] = 0x00; + eval->val[3] = 0x00; + eval->val[4] = (color_id >> 24) & 0xff; + eval->val[5] = (color_id >> 16) & 0xff; + eval->val[6] = (color_id >> 8) & 0xff; + eval->val[7] = color_id & 0xff; +} + +extern void ecommunity_init(void); +extern void ecommunity_finish(void); +extern void ecommunity_free(struct ecommunity **); +extern struct ecommunity *ecommunity_parse(uint8_t *, unsigned short, + bool disable_ieee_floating); +extern struct ecommunity *ecommunity_parse_ipv6(uint8_t *pnt, + unsigned short length); +extern struct ecommunity *ecommunity_dup(struct ecommunity *); +extern struct ecommunity *ecommunity_merge(struct ecommunity *, + struct ecommunity *); +extern struct ecommunity *ecommunity_uniq_sort(struct ecommunity *); +extern struct ecommunity *ecommunity_intern(struct ecommunity *); +extern bool ecommunity_cmp(const void *arg1, const void *arg2); +extern void ecommunity_unintern(struct ecommunity **ecommunity); +extern unsigned int ecommunity_hash_make(const void *); +extern struct ecommunity *ecommunity_str2com(const char *, int, int); +extern struct ecommunity *ecommunity_str2com_ipv6(const char *str, int type, + int keyword_included); +extern char *ecommunity_ecom2str(struct ecommunity *, int, int); +extern bool ecommunity_has_route_target(struct ecommunity *ecom); +extern void ecommunity_strfree(char **s); +extern bool ecommunity_include(struct ecommunity *e1, struct ecommunity *e2); +extern bool ecommunity_match(const struct ecommunity *, + const struct ecommunity *); +extern const char *ecommunity_str(struct ecommunity *ecom); +extern struct ecommunity_val *ecommunity_lookup(const struct ecommunity *, + uint8_t, uint8_t); + +extern uint32_t ecommunity_select_color(const struct ecommunity *ecom); +extern bool ecommunity_add_val(struct ecommunity *ecom, + struct ecommunity_val *eval, + bool unique, bool overwrite); +extern bool ecommunity_add_val_ipv6(struct ecommunity *ecom, + struct ecommunity_val_ipv6 *eval, + bool unique, bool overwrite); + +/* for vpn */ +extern struct ecommunity *ecommunity_new(void); +extern bool ecommunity_strip(struct ecommunity *ecom, uint8_t type, + uint8_t subtype); +extern struct ecommunity *ecommunity_new(void); +extern bool ecommunity_del_val(struct ecommunity *ecom, + struct ecommunity_val *eval); +struct bgp_pbr_entry_action; +extern int ecommunity_fill_pbr_action(struct ecommunity_val *ecom_eval, + struct bgp_pbr_entry_action *api, + afi_t afi); + +extern void bgp_compute_aggregate_ecommunity( + struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity); + +extern void bgp_compute_aggregate_ecommunity_hash( + struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity); +extern void bgp_compute_aggregate_ecommunity_val( + struct bgp_aggregate *aggregate); +extern void bgp_remove_ecommunity_from_aggregate( + struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity); +extern void bgp_remove_ecomm_from_aggregate_hash( + struct bgp_aggregate *aggregate, + struct ecommunity *ecommunity); +extern void bgp_aggr_ecommunity_remove(void *arg); +extern const uint8_t *ecommunity_linkbw_present(struct ecommunity *ecom, + uint64_t *bw); +extern struct ecommunity * +ecommunity_replace_linkbw(as_t as, struct ecommunity *ecom, uint64_t cum_bw, + bool disable_ieee_floating, bool extended); + +extern bool soo_in_ecom(struct ecommunity *ecom, struct ecommunity *soo); + +static inline void ecommunity_strip_rts(struct ecommunity *ecom) +{ + uint8_t subtype = ECOMMUNITY_ROUTE_TARGET; + + ecommunity_strip(ecom, ECOMMUNITY_ENCODE_AS, subtype); + ecommunity_strip(ecom, ECOMMUNITY_ENCODE_IP, subtype); + ecommunity_strip(ecom, ECOMMUNITY_ENCODE_AS4, subtype); +} +extern struct ecommunity * +ecommunity_add_origin_validation_state(enum rpki_states rpki_state, + struct ecommunity *ecom); +extern struct ecommunity *ecommunity_add_node_target(struct in_addr *node_id, + struct ecommunity *old, + bool non_trans); +extern bool ecommunity_node_target_match(struct ecommunity *ecomm, + struct in_addr *local_id); +#endif /* _QUAGGA_BGP_ECOMMUNITY_H */ diff --git a/bgpd/bgp_encap_tlv.c b/bgpd/bgp_encap_tlv.c new file mode 100644 index 0000000..fde1197 --- /dev/null +++ b/bgpd/bgp_encap_tlv.c @@ -0,0 +1,993 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015, LabN Consulting, L.L.C. + */ + +#include + +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "filter.h" +#include "stream.h" + +#include "bgpd.h" +#include "bgp_attr.h" + +#include "bgp_encap_types.h" +#include "bgp_encap_tlv.h" + +/*********************************************************************** + * SUBTLV ENCODE + ***********************************************************************/ + +/* rfc5512 4.1 */ +static struct bgp_attr_encap_subtlv *subtlv_encode_encap_l2tpv3_over_ip( + struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 4 + st->cookie_length; + + /* sanity check */ + assert(st->cookie_length <= sizeof(st->cookie)); + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; + new->length = total; + p = new->value; + + *p++ = (st->sessionid & 0xff000000) >> 24; + *p++ = (st->sessionid & 0xff0000) >> 16; + *p++ = (st->sessionid & 0xff00) >> 8; + *p++ = (st->sessionid & 0xff); + memcpy(p, st->cookie, st->cookie_length); + return new; +} + +/* rfc5512 4.1 */ +static struct bgp_attr_encap_subtlv * +subtlv_encode_encap_gre(struct bgp_tea_subtlv_encap_gre_key *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 4; + + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; + new->length = total; + p = new->value; + + *p++ = (st->gre_key & 0xff000000) >> 24; + *p++ = (st->gre_key & 0xff0000) >> 16; + *p++ = (st->gre_key & 0xff00) >> 8; + *p++ = (st->gre_key & 0xff); + return new; +} + +static struct bgp_attr_encap_subtlv * +subtlv_encode_encap_pbb(struct bgp_tea_subtlv_encap_pbb *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 1 + 3 + 6 + 2; /* flags + isid + madaddr + vid */ + + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION; + new->length = total; + p = new->value; + + *p++ = (st->flag_isid ? 0x80 : 0) | (st->flag_vid ? 0x40 : 0) | 0; + if (st->flag_isid) { + *p = (st->isid & 0xff0000) >> 16; + *(p + 1) = (st->isid & 0xff00) >> 8; + *(p + 2) = (st->isid & 0xff); + } + p += 3; + memcpy(p, st->macaddr, 6); + p += 6; + if (st->flag_vid) { + *p++ = (st->vid & 0xf00) >> 8; + *p++ = st->vid & 0xff; + } + return new; +} + +/* rfc5512 4.2 */ +static struct bgp_attr_encap_subtlv * +subtlv_encode_proto_type(struct bgp_tea_subtlv_proto_type *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 2; + + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE; + new->length = total; + p = new->value; + + *p++ = (st->proto & 0xff00) >> 8; + *p++ = (st->proto & 0xff); + return new; +} + +/* rfc5512 4.3 */ +static struct bgp_attr_encap_subtlv * +subtlv_encode_color(struct bgp_tea_subtlv_color *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 8; + + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_COLOR; + new->length = total; + p = new->value; + + *p++ = 0x03; /* transitive*/ + *p++ = 0x0b; + *p++ = 0; /* reserved */ + *p++ = 0; /* reserved */ + + *p++ = (st->color & 0xff000000) >> 24; + *p++ = (st->color & 0xff0000) >> 16; + *p++ = (st->color & 0xff00) >> 8; + *p++ = (st->color & 0xff); + + return new; +} + +/* rfc 5566 4. */ +static struct bgp_attr_encap_subtlv * +subtlv_encode_ipsec_ta(struct bgp_tea_subtlv_ipsec_ta *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + int total = 2 + st->authenticator_length; + + /* sanity check */ + assert(st->authenticator_length <= sizeof(st->value)); + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA; + new->length = total; + p = new->value; + + *p++ = (st->authenticator_type & 0xff00) >> 8; + *p++ = st->authenticator_type & 0xff; + memcpy(p, st->value, st->authenticator_length); + return new; +} + +/* draft-rosen-idr-tunnel-encaps 2.1 */ +static struct bgp_attr_encap_subtlv * +subtlv_encode_remote_endpoint(struct bgp_tea_subtlv_remote_endpoint *st) +{ + struct bgp_attr_encap_subtlv *new; + uint8_t *p; + + int total = (st->family == AF_INET ? 8 : 20); + + assert(total <= 0xff); + + new = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + total); + assert(new); + new->type = BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT; + new->length = total; + p = new->value; + if (st->family == AF_INET) { + memcpy(p, &(st->ip_address.v4.s_addr), IPV4_MAX_BYTELEN); + p += IPV4_MAX_BYTELEN; + } else { + assert(st->family == AF_INET6); + memcpy(p, &(st->ip_address.v6.s6_addr), IPV6_MAX_BYTELEN); + p += IPV6_MAX_BYTELEN; + } + memcpy(p, &(st->as4), 4); + return new; +} + +/*********************************************************************** + * TUNNEL TYPE-SPECIFIC TLV ENCODE + ***********************************************************************/ + +/* + * requires "extra" and "last" to be defined in caller + */ +#define ENC_SUBTLV(flag, function, field) \ + do { \ + struct bgp_attr_encap_subtlv *new; \ + if (CHECK_FLAG(bet->valid_subtlvs, (flag))) { \ + new = function(&bet->field); \ + if (last) { \ + last->next = new; \ + } else { \ + attr->encap_subtlvs = new; \ + } \ + last = new; \ + } \ + } while (0) + +void bgp_encap_type_l2tpv3overip_to_tlv( + struct bgp_encap_type_l2tpv3_over_ip *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_L2TPV3_OVER_IP; + + assert(CHECK_FLAG(bet->valid_subtlvs, BGP_TEA_SUBTLV_ENCAP)); + + ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_l2tpv3_over_ip, + st_encap); + ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, + st_proto); + ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); + ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, + subtlv_encode_remote_endpoint, st_endpoint); +} + +void bgp_encap_type_gre_to_tlv( + struct bgp_encap_type_gre *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_GRE; + + ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_gre, st_encap); + ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, + st_proto); + ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); + ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, + subtlv_encode_remote_endpoint, st_endpoint); +} + +void bgp_encap_type_ip_in_ip_to_tlv( + struct bgp_encap_type_ip_in_ip *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_IP_IN_IP; + + ENC_SUBTLV(BGP_TEA_SUBTLV_PROTO_TYPE, subtlv_encode_proto_type, + st_proto); + ENC_SUBTLV(BGP_TEA_SUBTLV_COLOR, subtlv_encode_color, st_color); + ENC_SUBTLV(BGP_TEA_SUBTLV_REMOTE_ENDPOINT, + subtlv_encode_remote_endpoint, st_endpoint); +} + +void bgp_encap_type_transmit_tunnel_endpoint( + struct bgp_encap_type_transmit_tunnel_endpoint + *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT; + + /* no subtlvs for this type */ +} + +void bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( + struct bgp_encap_type_ipsec_in_tunnel_mode *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE; + + ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, + st_ipsec_ta); +} + +void bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode + *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = + BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE; + + ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, + st_ipsec_ta); +} + +void bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode + *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = + BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE; + + ENC_SUBTLV(BGP_TEA_SUBTLV_IPSEC_TA, subtlv_encode_ipsec_ta, + st_ipsec_ta); +} + +void bgp_encap_type_pbb_to_tlv( + struct bgp_encap_type_pbb *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *last; + + /* advance to last subtlv */ + for (last = attr->encap_subtlvs; last && last->next; last = last->next) + ; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_PBB; + + assert(CHECK_FLAG(bet->valid_subtlvs, BGP_TEA_SUBTLV_ENCAP)); + ENC_SUBTLV(BGP_TEA_SUBTLV_ENCAP, subtlv_encode_encap_pbb, st_encap); +} + +void bgp_encap_type_vxlan_to_tlv( + struct bgp_encap_type_vxlan *bet, /* input structure */ + struct attr *attr) +{ + struct bgp_attr_encap_subtlv *tlv; + uint32_t vnid; + + attr->encap_tunneltype = BGP_ENCAP_TYPE_VXLAN; + + if (bet == NULL || !bet->vnid) + return; + XFREE(MTYPE_ENCAP_TLV, attr->encap_subtlvs); + tlv = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + 12); + tlv->type = 1; /* encapsulation type */ + tlv->length = 12; + if (bet->vnid) { + vnid = htonl(bet->vnid | VXLAN_ENCAP_MASK_VNID_VALID); + memcpy(&tlv->value, &vnid, 4); + } + if (bet->mac_address) { + char *ptr = (char *)&tlv->value + 4; + memcpy(ptr, bet->mac_address, 6); + } + attr->encap_subtlvs = tlv; + return; +} + +void bgp_encap_type_nvgre_to_tlv( + struct bgp_encap_type_nvgre *bet, /* input structure */ + struct attr *attr) +{ + attr->encap_tunneltype = BGP_ENCAP_TYPE_NVGRE; +} + +void bgp_encap_type_mpls_to_tlv( + struct bgp_encap_type_mpls *bet, /* input structure */ + struct attr *attr) +{ + return; /* no encap attribute for MPLS */ +} + +void bgp_encap_type_mpls_in_gre_to_tlv( + struct bgp_encap_type_mpls_in_gre *bet, /* input structure */ + struct attr *attr) +{ + attr->encap_tunneltype = BGP_ENCAP_TYPE_MPLS_IN_GRE; +} + +void bgp_encap_type_vxlan_gpe_to_tlv( + struct bgp_encap_type_vxlan_gpe *bet, /* input structure */ + struct attr *attr) +{ + + attr->encap_tunneltype = BGP_ENCAP_TYPE_VXLAN_GPE; +} + +void bgp_encap_type_mpls_in_udp_to_tlv( + struct bgp_encap_type_mpls_in_udp *bet, /* input structure */ + struct attr *attr) +{ + + attr->encap_tunneltype = BGP_ENCAP_TYPE_MPLS_IN_UDP; +} + + +/*********************************************************************** + * SUBTLV DECODE + ***********************************************************************/ +/* rfc5512 4.1 */ +static int subtlv_decode_encap_l2tpv3_over_ip( + struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) +{ + if (subtlv->length < 4) { + zlog_debug("%s, subtlv length %d is less than 4", __func__, + subtlv->length); + return -1; + } + + ptr_get_be32(subtlv->value, &st->sessionid); + st->cookie_length = subtlv->length - 4; + if (st->cookie_length > sizeof(st->cookie)) { + zlog_debug("%s, subtlv length %d is greater than %d", __func__, + st->cookie_length, (int)sizeof(st->cookie)); + return -1; + } + memcpy(st->cookie, subtlv->value + 4, st->cookie_length); + return 0; +} + +/* rfc5512 4.1 */ +static int subtlv_decode_encap_gre(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_encap_gre_key *st) +{ + if (subtlv->length != 4) { + zlog_debug("%s, subtlv length %d does not equal 4", __func__, + subtlv->length); + return -1; + } + ptr_get_be32(subtlv->value, &st->gre_key); + return 0; +} + +static int subtlv_decode_encap_pbb(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_encap_pbb *st) +{ + if (subtlv->length != 1 + 3 + 6 + 2) { + zlog_debug("%s, subtlv length %d does not equal %d", __func__, + subtlv->length, 1 + 3 + 6 + 2); + return -1; + } + if (subtlv->value[0] & 0x80) { + st->flag_isid = 1; + st->isid = (subtlv->value[1] << 16) | (subtlv->value[2] << 8) + | subtlv->value[3]; + } + if (subtlv->value[0] & 0x40) { + st->flag_vid = 1; + st->vid = ((subtlv->value[10] & 0x0f) << 8) | subtlv->value[11]; + } + memcpy(st->macaddr, subtlv->value + 4, 6); + return 0; +} + +/* rfc5512 4.2 */ +static int subtlv_decode_proto_type(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_proto_type *st) +{ + if (subtlv->length != 2) { + zlog_debug("%s, subtlv length %d does not equal 2", __func__, + subtlv->length); + return -1; + } + st->proto = (subtlv->value[0] << 8) | subtlv->value[1]; + return 0; +} + +/* rfc5512 4.3 */ +static int subtlv_decode_color(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_color *st) +{ + if (subtlv->length != 8) { + zlog_debug("%s, subtlv length %d does not equal 8", __func__, + subtlv->length); + return -1; + } + if ((subtlv->value[0] != 0x03) || (subtlv->value[1] != 0x0b) + || (subtlv->value[2] != 0) || (subtlv->value[3] != 0)) { + zlog_debug("%s, subtlv value 1st 4 bytes are not 0x030b0000", + __func__); + return -1; + } + ptr_get_be32(subtlv->value + 4, &st->color); + return 0; +} + +/* rfc 5566 4. */ +static int subtlv_decode_ipsec_ta(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_ipsec_ta *st) +{ + st->authenticator_length = subtlv->length - 2; + if (st->authenticator_length > sizeof(st->value)) { + zlog_debug( + "%s, authenticator length %d exceeds storage maximum %d", + __func__, st->authenticator_length, + (int)sizeof(st->value)); + return -1; + } + st->authenticator_type = (subtlv->value[0] << 8) | subtlv->value[1]; + memcpy(st->value, subtlv->value + 2, st->authenticator_length); + return 0; +} + +/* draft-rosen-idr-tunnel-encaps 2.1 */ +static int +subtlv_decode_remote_endpoint(struct bgp_attr_encap_subtlv *subtlv, + struct bgp_tea_subtlv_remote_endpoint *st) +{ + int i; + if (subtlv->length != 8 && subtlv->length != 20) { + zlog_debug("%s, subtlv length %d does not equal 8 or 20", + __func__, subtlv->length); + return -1; + } + if (subtlv->length == 8) { + st->family = AF_INET; + memcpy(&st->ip_address.v4.s_addr, subtlv->value, + IPV4_MAX_BYTELEN); + } else { + st->family = AF_INET6; + memcpy(&(st->ip_address.v6.s6_addr), subtlv->value, + IPV6_MAX_BYTELEN); + } + i = subtlv->length - 4; + ptr_get_be32(subtlv->value + i, &st->as4); + return 0; +} + +/*********************************************************************** + * TUNNEL TYPE-SPECIFIC TLV DECODE + ***********************************************************************/ + +int tlv_to_bgp_encap_type_l2tpv3overip( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_l2tpv3_over_ip *bet) /* caller-allocated */ +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: + rc |= subtlv_decode_encap_l2tpv3_over_ip( + st, &bet->st_encap); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); + break; + + case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: + rc |= subtlv_decode_proto_type(st, &bet->st_proto); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); + break; + + case BGP_ENCAP_SUBTLV_TYPE_COLOR: + rc |= subtlv_decode_color(st, &bet->st_color); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_gre( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_gre *bet) /* caller-allocated */ +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: + rc |= subtlv_decode_encap_gre(st, &bet->st_encap); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); + break; + + case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: + rc |= subtlv_decode_proto_type(st, &bet->st_proto); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); + break; + + case BGP_ENCAP_SUBTLV_TYPE_COLOR: + rc |= subtlv_decode_color(st, &bet->st_color); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_ip_in_ip( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_ip_in_ip *bet) /* caller-allocated */ +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE: + rc |= subtlv_decode_proto_type(st, &bet->st_proto); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_PROTO_TYPE); + break; + + case BGP_ENCAP_SUBTLV_TYPE_COLOR: + rc |= subtlv_decode_color(st, &bet->st_color); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_COLOR); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_transmit_tunnel_endpoint( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_transmit_tunnel_endpoint *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_ipsec_in_tunnel_mode *bet) /* caller-allocated */ +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: + rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: + rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA: + rc |= subtlv_decode_ipsec_ta(st, &bet->st_ipsec_ta); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_IPSEC_TA); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_vxlan(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_vxlan *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_nvgre(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_nvgre *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_mpls_in_gre(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_gre *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_vxlan_gpe(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_vxlan_gpe *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_mpls_in_udp(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_udp *bet) +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} + +int tlv_to_bgp_encap_type_pbb( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_pbb *bet) /* caller-allocated */ +{ + struct bgp_attr_encap_subtlv *st; + int rc = 0; + + for (st = stlv; st; st = st->next) { + switch (st->type) { + case BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION: + rc |= subtlv_decode_encap_pbb(st, &bet->st_encap); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_ENCAP); + break; + + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + rc |= subtlv_decode_remote_endpoint(st, + &bet->st_endpoint); + SET_SUBTLV_FLAG(bet, BGP_TEA_SUBTLV_REMOTE_ENDPOINT); + break; + + default: + zlog_debug("%s: unexpected subtlv type %d", __func__, + st->type); + rc |= -1; + break; + } + } + return rc; +} diff --git a/bgpd/bgp_encap_tlv.h b/bgpd/bgp_encap_tlv.h new file mode 100644 index 0000000..e718844 --- /dev/null +++ b/bgpd/bgp_encap_tlv.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015, LabN Consulting, L.L.C. + */ + +#ifndef _QUAGGA_BGP_ENCAP_TLV_H +#define _QUAGGA_BGP_ENCAP_TLV_H + +/*********************************************************************** + * TUNNEL TYPE-SPECIFIC TLV ENCODE + ***********************************************************************/ + +extern void +bgp_encap_type_l2tpv3overip_to_tlv(struct bgp_encap_type_l2tpv3_over_ip *bet, + struct attr *attr); + +extern void bgp_encap_type_gre_to_tlv(struct bgp_encap_type_gre *bet, + struct attr *attr); + +extern void bgp_encap_type_ip_in_ip_to_tlv(struct bgp_encap_type_ip_in_ip *bet, + struct attr *attr); + +extern void bgp_encap_type_transmit_tunnel_endpoint( + struct bgp_encap_type_transmit_tunnel_endpoint *bet, struct attr *attr); + +extern void bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( + struct bgp_encap_type_ipsec_in_tunnel_mode *bet, struct attr *attr); + +extern void bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet, + struct attr *attr); + +extern void bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet, + struct attr *attr); + +extern void bgp_encap_type_pbb_to_tlv(struct bgp_encap_type_pbb *bet, + struct attr *attr); + +extern void bgp_encap_type_vxlan_to_tlv(struct bgp_encap_type_vxlan *bet, + struct attr *attr); + +extern void bgp_encap_type_nvgre_to_tlv(struct bgp_encap_type_nvgre *bet, + struct attr *attr); + +extern void bgp_encap_type_mpls_to_tlv(struct bgp_encap_type_mpls *bet, + struct attr *attr); + +extern void +bgp_encap_type_mpls_in_gre_to_tlv(struct bgp_encap_type_mpls_in_gre *bet, + struct attr *attr); + +extern void +bgp_encap_type_vxlan_gpe_to_tlv(struct bgp_encap_type_vxlan_gpe *bet, + struct attr *attr); + +extern void +bgp_encap_type_mpls_in_udp_to_tlv(struct bgp_encap_type_mpls_in_udp *bet, + struct attr *attr); + +/*********************************************************************** + * TUNNEL TYPE-SPECIFIC TLV DECODE + ***********************************************************************/ + +extern int tlv_to_bgp_encap_type_l2tpv3overip( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_l2tpv3_over_ip *bet); /* caller-allocated */ + +extern int tlv_to_bgp_encap_type_gre( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_gre *bet); /* caller-allocated */ + +extern int tlv_to_bgp_encap_type_ip_in_ip( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_ip_in_ip *bet); /* caller-allocated */ + +extern int tlv_to_bgp_encap_type_transmit_tunnel_endpoint( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_transmit_tunnel_endpoint *bet); + +extern int tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_ipsec_in_tunnel_mode *bet); /* caller-allocated */ + +extern int tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet); + +extern int tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( + struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet); + +extern int tlv_to_bgp_encap_type_vxlan(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_vxlan *bet); + +extern int tlv_to_bgp_encap_type_nvgre(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_nvgre *bet); + +extern int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls *bet); + +extern int tlv_to_bgp_encap_type_mpls(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls *bet); + +extern int +tlv_to_bgp_encap_type_mpls_in_gre(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_gre *bet); + +extern int +tlv_to_bgp_encap_type_vxlan_gpe(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_vxlan_gpe *bet); + +extern int +tlv_to_bgp_encap_type_mpls_in_udp(struct bgp_attr_encap_subtlv *stlv, + struct bgp_encap_type_mpls_in_udp *bet); + +extern int tlv_to_bgp_encap_type_pbb( + struct bgp_attr_encap_subtlv *stlv, /* subtlv chain */ + struct bgp_encap_type_pbb *bet); /* caller-allocated */ + +#endif /* _QUAGGA_BGP_ENCAP_TLV_H */ diff --git a/bgpd/bgp_encap_types.h b/bgpd/bgp_encap_types.h new file mode 100644 index 0000000..ea5b54c --- /dev/null +++ b/bgpd/bgp_encap_types.h @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015, LabN Consulting, L.L.C. + */ + +#ifndef _QUAGGA_BGP_ENCAP_TYPES_H +#define _QUAGGA_BGP_ENCAP_TYPES_H + +#include "bgpd/bgp_ecommunity.h" + +/* from + * http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#tunnel-types + */ +typedef enum { + BGP_ENCAP_TYPE_RESERVED = 0, + BGP_ENCAP_TYPE_L2TPV3_OVER_IP = 1, + BGP_ENCAP_TYPE_GRE = 2, + BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT = 3, + BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE = 4, + BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE = 5, + BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE = 6, + BGP_ENCAP_TYPE_IP_IN_IP = 7, + BGP_ENCAP_TYPE_VXLAN = 8, + BGP_ENCAP_TYPE_NVGRE = 9, + BGP_ENCAP_TYPE_MPLS = 10, /* NOTE: Encap SAFI&Attribute not used */ + BGP_ENCAP_TYPE_MPLS_IN_GRE = 11, + BGP_ENCAP_TYPE_VXLAN_GPE = 12, + BGP_ENCAP_TYPE_MPLS_IN_UDP = 13, + BGP_ENCAP_TYPE_PBB +} bgp_encap_types; + +typedef enum { + BGP_ENCAP_SUBTLV_TYPE_ENCAPSULATION = 1, + BGP_ENCAP_SUBTLV_TYPE_PROTO_TYPE = 2, + BGP_ENCAP_SUBTLV_TYPE_IPSEC_TA = 3, + BGP_ENCAP_SUBTLV_TYPE_COLOR = 4, + BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT = + 6 /* speculative, IANA assignment TBD */ +} bgp_encap_subtlv_types; + +/* + * Tunnel Encapsulation Attribute subtlvs + */ +struct bgp_tea_subtlv_encap_l2tpv3_over_ip { + uint32_t sessionid; + uint8_t cookie_length; + uint8_t cookie[8]; +}; + +struct bgp_tea_subtlv_encap_gre_key { + uint32_t gre_key; +}; + +struct bgp_tea_subtlv_encap_pbb { + uint32_t flag_isid : 1; + uint32_t flag_vid : 1; + uint32_t isid : 24; + uint16_t vid : 12; + uint8_t macaddr[6]; +}; + +struct bgp_tea_subtlv_proto_type { + uint16_t proto; /* ether-type */ +}; + +struct bgp_tea_subtlv_color { + uint32_t color; +}; + +/* per draft-rosen-idr-tunnel-encaps */ +struct bgp_tea_subtlv_remote_endpoint { + uint8_t family; /* IPv4 or IPv6 */ + union { + struct in_addr v4; + struct in6_addr v6; + } ip_address; + as_t as4; /* always 4 bytes */ +}; + +/* + * This is the length of the value part of the ipsec tunnel authenticator + * subtlv. Currently we only support the length for authenticator type 1. + */ +#define BGP_ENCAP_SUBTLV_IPSEC_TA_SIZE 20 + +struct bgp_tea_subtlv_ipsec_ta { + uint16_t authenticator_type; /* only type 1 is supported so far */ + uint8_t authenticator_length; /* octets in value field */ + uint8_t value[BGP_ENCAP_SUBTLV_IPSEC_TA_SIZE]; +}; + +/* + * Subtlv valid flags + * TBD change names to add "VALID" + */ +#define BGP_TEA_SUBTLV_ENCAP 0x00000001 +#define BGP_TEA_SUBTLV_PROTO_TYPE 0x00000002 +#define BGP_TEA_SUBTLV_COLOR 0x00000004 +#define BGP_TEA_SUBTLV_IPSEC_TA 0x00000008 +#define BGP_TEA_SUBTLV_REMOTE_ENDPOINT 0x00000010 + +#define CHECK_SUBTLV_FLAG(ptr, flag) CHECK_FLAG((ptr)->valid_subtlvs, (flag)) +#define SET_SUBTLV_FLAG(ptr, flag) SET_FLAG((ptr)->valid_subtlvs, (flag)) +#define UNSET_SUBTLV_FLAG(ptr, flag) UNSET_FLAG((ptr)->valid_subtlvs, (flag)) + +/* + * Tunnel Type-specific APIs + */ +struct bgp_encap_type_reserved { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_l2tpv3_over_ip { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_encap_l2tpv3_over_ip st_encap; + struct bgp_tea_subtlv_proto_type st_proto; /* optional */ + struct bgp_tea_subtlv_color st_color; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_gre { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_encap_gre_key st_encap; /* optional */ + struct bgp_tea_subtlv_proto_type st_proto; /* optional */ + struct bgp_tea_subtlv_color st_color; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_ip_in_ip { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_proto_type st_proto; /* optional */ + struct bgp_tea_subtlv_color st_color; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_transmit_tunnel_endpoint { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_ipsec_in_tunnel_mode { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_ipsec_ta st_ipsec_ta; /* optional */ + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ +}; + +#define VXLAN_ENCAP_MASK_VNID_VALID 0x80000000 +#define VXLAN_ENCAP_MASK_MAC_VALID 0x40000000 + +struct bgp_encap_type_vxlan { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* draft-ietf-idr-tunnel-encaps-02 */ + uint32_t vnid; /* does not include V and M bit */ + uint8_t *mac_address; /* optional */ +}; + +struct bgp_encap_type_nvgre { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_mpls { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_mpls_in_gre { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_vxlan_gpe { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_mpls_in_udp { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + /* No subtlvs defined in spec? */ +}; + +struct bgp_encap_type_pbb { + uint32_t valid_subtlvs; + struct bgp_tea_subtlv_remote_endpoint st_endpoint; /* optional */ + struct bgp_tea_subtlv_encap_pbb st_encap; +}; + +static inline void encode_encap_extcomm(bgp_encap_types tnl_type, + struct ecommunity_val *eval) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE; + eval->val[1] = ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP; + eval->val[6] = ((tnl_type) >> 8) & 0xff; + eval->val[7] = (tnl_type)&0xff; +} + +#endif /* _QUAGGA_BGP_ENCAP_TYPES_H */ diff --git a/bgpd/bgp_errors.c b/bgpd/bgp_errors.c new file mode 100644 index 0000000..cfcefed --- /dev/null +++ b/bgpd/bgp_errors.c @@ -0,0 +1,498 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Don Slice + */ + +#include + +#include "lib/ferr.h" +#include "bgp_errors.h" + +/* clang-format off */ +static struct log_ref ferr_bgp_warn[] = { + { + .code = EC_BGP_ASPATH_FEWER_HOPS, + .title = "BGP AS-path conversion has failed", + .description = "BGP has attempted to convert a AS2 to AS4 path and has failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_BGP_DEFUNCT_SNPA_LEN, + .title = "BGP has received a value in a reserved field", + .description = "BGP has received a non-zero value in a reserved field that was used for SNPA-length at one point in time", + .suggestion = "BGP has peered with either a router that is attempting to send SNPA data or it has received a corrupted packet. If we are peering with a SNPA aware router(unlikely) upgrade that router, else open an Issue after gathering relevant log files", + }, + { + .code = EC_BGP_MISSING_ATTRIBUTE, + .title = "BGP has received an update with missing a missing attribute", + .description = "BGP received update packets must have some minimum attribute information within them", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_TOO_SMALL, + .title = "BGP udate packet with attribute data that is too small", + .description = "BGP has received an update packet that is too small to parse a given attribute. This typically means that something has gone wrong between us and the remote peer", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, + .title = "BGP udate packet with extended attribute data that is too small", + .description = "BGP has received an update packet that is too small to parse a given extended attribute. This typically means that something has gone wrong between us and the remote peer", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_REPEATED, + .title = "BGP update packet received with a repeated attribute", + .description = "BGP has received an update packet with a attribute that is repeated more than one time for a particular route. This typically means that something has gone wrong between us and the remote peer", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_TOO_LARGE, + .title = "BGP udate packet with attribute data that is too large", + .description = "BGP has received an update packet that has too much data in a particular attribute. This typically means that something has gone wrong between us and the remote peer", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_PARSE_ERROR, + .title = "BGP update packet with attribute data has a parse error, specific to the attribute", + .description = "BGP has received an update packet with an attribute that when parsed does not make sense in some manner", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, + .title = "BGP update packet with a broken optional attribute has caused a withdraw of associated routes", + .description = "BGP has received a update packet with optional attributes that did not parse correctly, instead of resetting the peer, withdraw associated routes and note that this has happened", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTE_FETCH_ERROR, + .title = "BGP update packet with a broken length", + .description = "BGP has received a update packet with an attribute that has an incorrect length", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_ATTRIBUTES_MISMATCH, + .title = "BGP update packet with a length different than attribute data length", + .description = "BGP has received a update packet with attributes that when parsed do not correctly add up to packet data length", + .suggestion = "Gather log data from this and remote peer and open an Issue with this data", + }, + { + .code = EC_BGP_DUMP, + .title = "BGP MRT dump subsystem has encountered an issue", + .description = "BGP has found that the attempted write of MRT data to a dump file has failed", + .suggestion = "Ensure BGP has permissions to write the specified file", + }, + { + .code = EC_BGP_UPDATE_PACKET_SHORT, + .title = "BGP Update Packet is to Small", + .description = "The update packet received from a peer is to small", + .suggestion = "Determine the source of the update packet and examine that peer for what has gone wrong", + }, + { + .code = EC_BGP_UPDATE_PACKET_LONG, + .title = "BGP Update Packet is to large", + .description = "The update packet received from a peer is to large", + .suggestion = "Determine the source of the update packet and examine that peer for what has gone wrong", + }, + { + .code = EC_BGP_UNRECOGNIZED_CAPABILITY, + .title = "Unknown BGP Capability Received", + .description = "The negotiation of capabilities has received a capability that we do not know what to do with", + .suggestion = "Determine the source of the capability and remove the capability from what is sent", + }, + { + .code = EC_BGP_NO_TCP_MD5, + .title = "Unable to set TCP MD5 option on socket", + .description = "BGP attempted to setup TCP MD5 configuration on the socket as per configuration but was unable to", + .suggestion = "Please collect log files and open Issue", + }, + { + .code = EC_BGP_EVPN_PMSI_PRESENT, + .title = "BGP Received a EVPN NLRI with PMSI included", + .description = "BGP has received a type-3 NLRI with PMSI information. At this time FRR is not capable of properly handling this NLRI type", + .suggestion = "Setup peer to not send this type of data to FRR" + }, + { + .code = EC_BGP_EVPN_VPN_VNI, + .title = "BGP has received a local macip and cannot properly handle it", + .description = "BGP has received a local macip from zebra and has no way to properly handle the macip because the vni is not setup properly", + .suggestion = "Ensure proper setup of BGP EVPN", + }, + { + .code = EC_BGP_EVPN_ESI, + .title = "BGP has received a local ESI for deletion", + .description = "BGP has received a local ESI for deletion but when attempting to find the stored data internally was unable to find the information for deletion", + .suggestion = "Gather logging and open an Issue", + }, + { + .code = EC_BGP_INVALID_LABEL_STACK, + .title = "BGP has received a label stack in a NLRI that does not have the BOS marked", + .description = "BGP when it receives a NLRI with a label stack should have the BOS marked, this received packet does not have this", + .suggestion = "Gather log information from here and remote peer and open an Issue", + }, + { + .code = EC_BGP_ZEBRA_SEND, + .title = "BGP has attempted to send data to zebra and has failed to do so", + .description = "BGP has attempted to send data to zebra but has been unable to do so", + .suggestion = "Gather log data, open an Issue and restart FRR" + }, + { + .code = EC_BGP_CAPABILITY_INVALID_LENGTH, + .title = "BGP has received a capability with an invalid length", + .description = "BGP has received a capability from it's peer who's size is wrong", + .suggestion = "Gather log files from here and from peer and open an Issue", + }, + { + .code = EC_BGP_CAPABILITY_INVALID_DATA, + .title = "BGP has received capability data with invalid information", + .description = "BGP has noticed that during processing of capability information that data was wrong", + .suggestion = "Gather log files from here and from peer and open an Issue", + }, + { + .code = EC_BGP_CAPABILITY_VENDOR, + .title = "BGP has received capability data specific to a particular vendor", + .description = "BGP has received a capability that is vendor specific and as such we have no knowledge of how to use this capability in FRR", + .suggestion = "On peer turn off this feature" + }, + { + .code = EC_BGP_CAPABILITY_UNKNOWN, + .title = "BGP has received capability data for a unknown capability", + .description = "BGP has received a capability that it does not know how to decode. This may be due to a new feature that has not been coded into FRR or it may be a bug in the remote peer", + .suggestion = "Gather log files from here and from peer and open an Issue", + }, + { + .code = EC_BGP_INVALID_NEXTHOP_LENGTH, + .title = "BGP is attempting to write an invalid nexthop length value", + .description = "BGP is in the process of building NLRI information for a peer and has discovered an inconsistent internal state", + .suggestion = "Gather log files and open an Issue, restart FRR", + }, + { + .code = EC_BGP_SENDQ_STUCK_WARN, + .title = "BGP has been unable to send anything to a peer for an extended time", + .description = "The BGP peer does not seem to be receiving or processing any data received from us, causing updates to be delayed.", + .suggestion = "Check connectivity to the peer and that it is not overloaded", + }, + { + .code = END_FERR, + } +}; + +static struct log_ref ferr_bgp_err[] = { + { + .code = EC_BGP_ATTR_FLAG, + .title = "BGP attribute flag is incorrect", + .description = "BGP attribute flag is set to the wrong value (Optional/Transitive/Partial)", + .suggestion = "Determine the source of the attribute and determine why the attribute flag has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_LEN, + .title = "BGP attribute length is incorrect", + .description = "BGP attribute length is incorrect", + .suggestion = "Determine the source of the attribute and determine why the attribute length has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_ORIGIN, + .title = "BGP attribute origin value invalid", + .description = "BGP attribute origin value is invalid", + .suggestion = "Determine the source of the attribute and determine why the origin attribute has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_MAL_AS_PATH, + .title = "BGP as path is invalid", + .description = "BGP as path has been malformed", + .suggestion = "Determine the source of the update and determine why the as path has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_FIRST_AS, + .title = "BGP as path first as is invalid", + .description = "BGP update has invalid first as in as path", + .suggestion = "Determine the source of the update and determine why the as path first as value has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_PMSI_TYPE, + .title = "BGP PMSI tunnel attribute type is invalid", + .description = "BGP update has invalid type for PMSI tunnel", + .suggestion = "Determine the source of the update and determine why the PMSI tunnel attribute type has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_PMSI_LEN, + .title = "BGP PMSI tunnel attribute length is invalid", + .description = "BGP update has invalid length for PMSI tunnel", + .suggestion = "Determine the source of the update and determine why the PMSI tunnel attribute length has been set incorrectly" + }, + { + .code = EC_BGP_ATTR_AIGP, + .title = "BGP AIGP attribute is incorrect", + .description = "BGP AIGP attribute is incorrect", + .suggestion = "Determine the source of the attribute and determine why the AIGP attribute has been set incorrectly" + }, + { + .code = EC_BGP_PEER_GROUP, + .title = "BGP peergroup operated on in error", + .description = "BGP operating on peer-group instead of peers included", + .suggestion = "Ensure the config doesn't contain peergroups contained within peergroups" + }, + { + .code = EC_BGP_PEER_DELETE, + .title = "BGP failed to delete peer structure", + .description = "BGP was unable to delete peer structure when address-family removed", + .suggestion = "Determine if all expected peers are removed and restart FRR if not. Most likely a bug" + }, + { + .code = EC_BGP_TABLE_CHUNK, + .title = "BGP failed to get table chunk memory", + .description = "BGP unable to get chunk memory for table manager", + .suggestion = "Ensure there is adequate memory on the device to support the table requirements" + }, + { + .code = EC_BGP_MACIP_LEN, + .title = "BGP received MACIP with invalid IP addr len", + .description = "BGP received MACIP with invalid IP addr len from Zebra", + .suggestion = "Verify MACIP entries inserted in Zebra are correct. Most likely a bug" + }, + { + .code = EC_BGP_LM_ERROR, + .title = "BGP received invalid label manager message", + .description = "BGP received invalid label manager message from label manager", + .suggestion = "Label manager sent invalid essage to BGP for wrong protocol, instance, etc. Most likely a bug" + }, + { + .code = EC_BGP_JSON_MEM_ERROR, + .title = "BGP unable to allocate memory for JSON output", + .description = "BGP attempted to generate JSON output and was unable to allocate the memory required", + .suggestion = "Ensure that the device has adequate memory to support the required functions" + }, + { + .code = EC_BGP_UPDGRP_ATTR_LEN, + .title = "BGP update had attributes too long to send", + .description = "BGP attempted to send an update but the attributes were too long to fit", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_UPDGRP_CREATE, + .title = "BGP update group creation failed", + .description = "BGP attempted to create an update group but was unable to", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_UPDATE_SND, + .title = "BGP error creating update packet", + .description = "BGP attempted to create an update packet but was unable to", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_PKT_OPEN, + .title = "BGP error receiving open packet", + .description = "BGP received an open from a peer that was invalid", + .suggestion = "Determine the sending peer and correct his invalid open packet" + }, + { + .code = EC_BGP_SND_FAIL, + .title = "BGP error sending to peer", + .description = "BGP attempted to respond to open from a peer and failed", + .suggestion = "BGP attempted to respond to an open and could not sene the packet. Check local IP address for source" + }, + { + .code = EC_BGP_INVALID_STATUS, + .title = "BGP error receiving from peer", + .description = "BGP received an update from a peer but status was incorrect", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_UPDATE_RCV, + .title = "BGP error receiving update packet", + .description = "BGP received an invalid update packet", + .suggestion = "Determine the source of the update and resolve the invalid update being sent" + }, + { + .code = EC_BGP_NO_CAP, + .title = "BGP error due to capability not enabled", + .description = "BGP attempted a function that did not have the capability enabled", + .suggestion = "Enable the capability if this functionality is desired" + }, + { + .code = EC_BGP_NOTIFY_RCV, + .title = "BGP error receiving notify message", + .description = "BGP unable to process notification message", + .suggestion = "BGP notify received while in stopped state. If the problem persists, report for troubleshooting" + }, + { + .code = EC_BGP_KEEP_RCV, + .title = "BGP error receiving keepalive packet", + .description = "BGP unable to process keepalive packet", + .suggestion = "BGP keepalive received while in stopped state. If the problem persists, report for troubleshooting" + }, + { + .code = EC_BGP_RFSH_RCV, + .title = "BGP error receiving route refresh message", + .description = "BGP unable to process route refresh message", + .suggestion = "BGP route refresh received while in stopped state. If the problem persists, report for troubleshooting"}, + { + .code = EC_BGP_CAP_RCV, + .title = "BGP error capability message", + .description = "BGP unable to process received capability", + .suggestion = "BGP capability message received while in stopped state. If the problem persists, report for troubleshooting" + }, + { + .code = EC_BGP_NH_UPD, + .title = "BGP error with nexthopo update", + .description = "BGP unable to process nexthop update", + .suggestion = "BGP received nexthop update but nexthop is not reachable in this bgp instance. Report for troubleshooting" + }, + { + .code = EC_BGP_LABEL, + .title = "Failure to apply label", + .description = "BGP attempted to attempted to apply a label but could not", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_MULTIPATH, + .title = "Multipath specified is invalid", + .description = "BGP was started with an invalid ecmp/multipath value", + .suggestion = "Correct the ecmp/multipath value supplied when starting the BGP daemon" + }, + { + .code = EC_BGP_PKT_PROCESS, + .title = "Failure to process a packet", + .description = "BGP attempted to process a received packet but could not", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_CONNECT, + .title = "Failure to connect to peer", + .description = "BGP attempted to send open to peer but couldn't connect", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_FSM, + .title = "BGP FSM issue", + .description = "BGP neighbor transition problem", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_VNI, + .title = "BGP VNI creation issue", + .description = "BGP could not create a new VNI", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_NO_DFLT, + .title = "BGP default instance missing", + .description = "BGP could not find default instance", + .suggestion = "Define a default instance of BGP since some feature requires it's existence" + }, + { + .code = EC_BGP_VTEP_INVALID, + .title = "BGP remote VTEP invalid", + .description = "BGP remote VTEP is invalid and cannot be used", + .suggestion = "Correct remote VTEP configuration or resolve the source of the problem" + }, + { + .code = EC_BGP_ES_INVALID, + .title = "BGP ES route error", + .description = "BGP ES route incorrect, learned both local and remote", + .suggestion = "Correct configuration or addressing so that same not learned both local and remote" + }, + { + .code = EC_BGP_EVPN_ROUTE_DELETE, + .title = "BGP EVPN route delete error", + .description = "BGP attempted to delete an EVPN route and failed", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_EVPN_FAIL, + .title = "BGP EVPN install/uninstall error", + .description = "BGP attempted to install or uninstall an EVPN prefix and failed", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_EVPN_ROUTE_INVALID, + .title = "BGP EVPN route received with invalid contents", + .description = "BGP received an EVPN route with invalid contents", + .suggestion = "Determine the source of the EVPN route and resolve whatever is causing invalid contents" + }, + { + .code = EC_BGP_EVPN_ROUTE_CREATE, + .title = "BGP EVPN route create error", + .description = "BGP attempted to create an EVPN route and failed", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_ES_CREATE, + .title = "BGP EVPN ES entry create error", + .description = "BGP attempted to create an EVPN ES entry and failed", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_BGP_EVPN_AS_MISMATCH, + .title = "BGP AS configuration issue", + .description = "BGP configuration attempted for a different AS than currently configured", + .suggestion = "Correct the configuration so that the correct BGP AS number is used" + }, + { + .code = EC_BGP_EVPN_INSTANCE_MISMATCH, + .title = "BGP EVPN AS and process name mismatch", + .description = "BGP configuration has AS and process name mismatch", + .suggestion = "Correct the configuration so that the BGP AS number and instance name are consistent" + }, + { + .code = EC_BGP_FLOWSPEC_PACKET, + .title = "BGP Flowspec packet processing error", + .description = "The BGP flowspec subsystem has detected a error in the send or receive of a packet", + .suggestion = "Gather log files from both sides of the peering relationship and open an issue" + }, + { + .code = EC_BGP_FLOWSPEC_INSTALLATION, + .title = "BGP Flowspec Installation/removal Error", + .description = "The BGP flowspec subsystem has detected that there was a failure for installation/removal/modification of Flowspec from the dataplane", + .suggestion = "Gather log files from the router and open an issue, Restart FRR" + }, + { + .code = EC_BGP_DOPPELGANGER_CONFIG, + .title = "BGP has detected a configuration overwrite during peer collision resolution", + .description = "As part of BGP startup, the peer and ourselves can start connections to each other at the same time. During this process BGP received additional configuration, but it was only applied to one of the two nascent connections. Depending on the result of collision detection and resolution this configuration might be lost. To remedy this, after performing collision detection and resolution the peer session has been reset in order to apply the new configuration.", + .suggestion = "Gather data and open a Issue so that this developmental escape can be fixed, the peer should have been reset", + }, + { + .code = EC_BGP_ROUTER_ID_SAME, + .title = "BGP has detected a duplicate router id during collision resolution", + .description = "As part of normal collision detection for opening a connection to a peer, BGP has detected that the remote peer's router-id is the same as ours", + .suggestion = "Change one of the two router-id's", + }, + { + .code = EC_BGP_INVALID_BGP_INSTANCE, + .title = "BGP instance for the specific vrf is invalid", + .description = "Indicates that specified bgp instance is NULL", + .suggestion = "Get log files from router and open an issue", + }, + { + .code = EC_BGP_INVALID_ROUTE, + .title = "BGP route node is invalid", + .description = "BGP route for the specified AFI/SAFI is NULL", + .suggestion = "Get log files from router and open an issue", + }, + { + .code = EC_BGP_NO_LL_ADDRESS_AVAILABLE, + .title = "BGP v6 peer with no LL address on outgoing interface", + .description = "BGP when using a v6 peer requires a v6 LL address to be configured on the outgoing interface as per RFC 4291 section 2.1", + .suggestion = "Add a v6 LL address to the outgoing interfaces as per RFC", + }, + { + .code = EC_BGP_SENDQ_STUCK_PROPER, + .title = "BGP is shutting down a peer due to being unable to send anything for an extended time", + .description = "No BGP updates were successfully sent to the peer for more than twice the holdtime.", + .suggestion = "Check connectivity to the peer and that it is not overloaded", + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void bgp_error_init(void) +{ + log_ref_add(ferr_bgp_err); + log_ref_add(ferr_bgp_warn); +} diff --git a/bgpd/bgp_errors.h b/bgpd/bgp_errors.h new file mode 100644 index 0000000..4567f87 --- /dev/null +++ b/bgpd/bgp_errors.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Don Slice + */ + +#ifndef __BGP_ERRORS_H__ +#define __BGP_ERRORS_H__ + +#include "lib/ferr.h" + +enum bgp_log_refs { + + EC_BGP_ATTR_FLAG = BGP_FERR_START, + EC_BGP_ATTR_LEN, + EC_BGP_ATTR_ORIGIN, + EC_BGP_ATTR_MAL_AS_PATH, + EC_BGP_ATTR_FIRST_AS, + EC_BGP_ATTR_MARTIAN_NH, + EC_BGP_ATTR_PMSI_TYPE, + EC_BGP_ATTR_PMSI_LEN, + EC_BGP_ATTR_NH_SEND_LEN, + EC_BGP_ATTR_AIGP, + EC_BGP_PEER_GROUP, + EC_BGP_PEER_DELETE, + EC_BGP_TABLE_CHUNK, + EC_BGP_MACIP_LEN, + EC_BGP_LM_ERROR, + EC_BGP_JSON_MEM_ERROR, + EC_BGP_UPDGRP_ATTR_LEN, + EC_BGP_UPDGRP_CREATE, + EC_BGP_UPDATE_SND, + EC_BGP_PKT_OPEN, + EC_BGP_SND_FAIL, + EC_BGP_INVALID_STATUS, + EC_BGP_UPDATE_RCV, + EC_BGP_NO_CAP, + EC_BGP_NOTIFY_RCV, + EC_BGP_KEEP_RCV, + EC_BGP_RFSH_RCV, + EC_BGP_CAP_RCV, + EC_BGP_NH_UPD, + EC_BGP_LABEL, + EC_BGP_MULTIPATH, + EC_BGP_PKT_PROCESS, + EC_BGP_CONNECT, + EC_BGP_FSM, + EC_BGP_VNI, + EC_BGP_NO_DFLT, + EC_BGP_VTEP_INVALID, + EC_BGP_ES_INVALID, + EC_BGP_EVPN_ROUTE_DELETE, + EC_BGP_EVPN_FAIL, + EC_BGP_EVPN_ROUTE_INVALID, + EC_BGP_EVPN_ROUTE_CREATE, + EC_BGP_ES_CREATE, + EC_BGP_EVPN_AS_MISMATCH, + EC_BGP_EVPN_INSTANCE_MISMATCH, + EC_BGP_FLOWSPEC_PACKET, + EC_BGP_FLOWSPEC_INSTALLATION, + EC_BGP_ASPATH_FEWER_HOPS, + EC_BGP_DEFUNCT_SNPA_LEN, + EC_BGP_MISSING_ATTRIBUTE, + EC_BGP_ATTRIBUTE_TOO_SMALL, + EC_BGP_EXT_ATTRIBUTE_TOO_SMALL, + EC_BGP_ATTRIBUTE_REPEATED, + EC_BGP_ATTRIBUTE_TOO_LARGE, + EC_BGP_ATTRIBUTE_PARSE_ERROR, + EC_BGP_ATTRIBUTE_PARSE_WITHDRAW, + EC_BGP_ATTRIBUTE_FETCH_ERROR, + EC_BGP_ATTRIBUTES_MISMATCH, + EC_BGP_DUMP, + EC_BGP_UPDATE_PACKET_SHORT, + EC_BGP_UPDATE_PACKET_LONG, + EC_BGP_UNRECOGNIZED_CAPABILITY, + EC_BGP_NO_TCP_MD5, + EC_BGP_EVPN_PMSI_PRESENT, + EC_BGP_EVPN_VPN_VNI, + EC_BGP_EVPN_ESI, + EC_BGP_INVALID_LABEL_STACK, + EC_BGP_ZEBRA_SEND, + EC_BGP_CAPABILITY_INVALID_LENGTH, + EC_BGP_CAPABILITY_INVALID_DATA, + EC_BGP_CAPABILITY_VENDOR, + EC_BGP_CAPABILITY_UNKNOWN, + EC_BGP_INVALID_NEXTHOP_LENGTH, + EC_BGP_DOPPELGANGER_CONFIG, + EC_BGP_ROUTER_ID_SAME, + EC_BGP_INVALID_BGP_INSTANCE, + EC_BGP_INVALID_ROUTE, + EC_BGP_NO_LL_ADDRESS_AVAILABLE, + EC_BGP_SENDQ_STUCK_WARN, + EC_BGP_SENDQ_STUCK_PROPER, +}; + +extern void bgp_error_init(void); + +#endif diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c new file mode 100644 index 0000000..7af6ff7 --- /dev/null +++ b/bgpd/bgp_evpn.c @@ -0,0 +1,7883 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Ethernet-VPN Packet and vty Processing File + * Copyright (C) 2016 6WIND + * Copyright (C) 2017 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "filter.h" +#include "prefix.h" +#include "log.h" +#include "memory.h" +#include "stream.h" +#include "hash.h" +#include "jhash.h" +#include "zclient.h" + +#include "lib/printfrr.h" + +#include "bgpd/bgp_attr_evpn.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_trace.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_packet.h" + +/* + * Definitions and external declarations. + */ +DEFINE_QOBJ_TYPE(bgpevpn); +DEFINE_QOBJ_TYPE(bgp_evpn_es); + +DEFINE_MTYPE_STATIC(BGPD, BGP_EVPN_INFO, "BGP EVPN instance information"); +DEFINE_MTYPE_STATIC(BGPD, VRF_ROUTE_TARGET, "L3 Route Target"); + +/* + * Static function declarations + */ +static void bgp_evpn_remote_ip_hash_init(struct bgpevpn *evpn); +static void bgp_evpn_remote_ip_hash_destroy(struct bgpevpn *evpn); +static void bgp_evpn_remote_ip_hash_add(struct bgpevpn *vpn, + struct bgp_path_info *pi); +static void bgp_evpn_remote_ip_hash_del(struct bgpevpn *vpn, + struct bgp_path_info *pi); +static void bgp_evpn_remote_ip_hash_iterate(struct bgpevpn *vpn, + void (*func)(struct hash_bucket *, + void *), + void *arg); +static void bgp_evpn_link_to_vni_svi_hash(struct bgp *bgp, struct bgpevpn *vpn); +static void bgp_evpn_unlink_from_vni_svi_hash(struct bgp *bgp, + struct bgpevpn *vpn); +static unsigned int vni_svi_hash_key_make(const void *p); +static bool vni_svi_hash_cmp(const void *p1, const void *p2); +static void bgp_evpn_remote_ip_process_nexthops(struct bgpevpn *vpn, + struct ipaddr *addr, + bool resolve); +static void bgp_evpn_remote_ip_hash_link_nexthop(struct hash_bucket *bucket, + void *args); +static void bgp_evpn_remote_ip_hash_unlink_nexthop(struct hash_bucket *bucket, + void *args); +static struct in_addr zero_vtep_ip; + +/* + * Private functions. + */ + +/* + * Make vni hash key. + */ +static unsigned int vni_hash_key_make(const void *p) +{ + const struct bgpevpn *vpn = p; + return (jhash_1word(vpn->vni, 0)); +} + +/* + * Comparison function for vni hash + */ +static bool vni_hash_cmp(const void *p1, const void *p2) +{ + const struct bgpevpn *vpn1 = p1; + const struct bgpevpn *vpn2 = p2; + + return vpn1->vni == vpn2->vni; +} + +int vni_list_cmp(void *p1, void *p2) +{ + const struct bgpevpn *vpn1 = p1; + const struct bgpevpn *vpn2 = p2; + + return vpn1->vni - vpn2->vni; +} + +/* + * Make vrf import route target hash key. + */ +static unsigned int vrf_import_rt_hash_key_make(const void *p) +{ + const struct vrf_irt_node *irt = p; + const char *pnt = irt->rt.val; + + return jhash(pnt, 8, 0x5abc1234); +} + +/* + * Comparison function for vrf import rt hash + */ +static bool vrf_import_rt_hash_cmp(const void *p1, const void *p2) +{ + const struct vrf_irt_node *irt1 = p1; + const struct vrf_irt_node *irt2 = p2; + + return (memcmp(irt1->rt.val, irt2->rt.val, ECOMMUNITY_SIZE) == 0); +} + +/* + * Create a new vrf import_rt in evpn instance + */ +static struct vrf_irt_node *vrf_import_rt_new(struct ecommunity_val *rt) +{ + struct bgp *bgp_evpn = NULL; + struct vrf_irt_node *irt; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) { + flog_err(EC_BGP_NO_DFLT, + "vrf import rt new - evpn instance not created yet"); + return NULL; + } + + irt = XCALLOC(MTYPE_BGP_EVPN_VRF_IMPORT_RT, + sizeof(struct vrf_irt_node)); + + irt->rt = *rt; + irt->vrfs = list_new(); + + /* Add to hash */ + (void)hash_get(bgp_evpn->vrf_import_rt_hash, irt, hash_alloc_intern); + + return irt; +} + +/* + * Free the vrf import rt node + */ +static void vrf_import_rt_free(struct vrf_irt_node *irt) +{ + struct bgp *bgp_evpn = NULL; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) { + flog_err(EC_BGP_NO_DFLT, + "vrf import rt free - evpn instance not created yet"); + return; + } + + hash_release(bgp_evpn->vrf_import_rt_hash, irt); + list_delete(&irt->vrfs); + XFREE(MTYPE_BGP_EVPN_VRF_IMPORT_RT, irt); +} + +static void hash_vrf_import_rt_free(struct vrf_irt_node *irt) +{ + XFREE(MTYPE_BGP_EVPN_VRF_IMPORT_RT, irt); +} + +/* + * Function to lookup Import RT node - used to map a RT to set of + * VNIs importing routes with that RT. + */ +static struct vrf_irt_node *lookup_vrf_import_rt(struct ecommunity_val *rt) +{ + struct bgp *bgp_evpn = NULL; + struct vrf_irt_node *irt; + struct vrf_irt_node tmp; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) { + flog_err( + EC_BGP_NO_DFLT, + "vrf import rt lookup - evpn instance not created yet"); + return NULL; + } + + memset(&tmp, 0, sizeof(tmp)); + memcpy(&tmp.rt, rt, ECOMMUNITY_SIZE); + irt = hash_lookup(bgp_evpn->vrf_import_rt_hash, &tmp); + return irt; +} + +/* + * Is specified VRF present on the RT's list of "importing" VRFs? + */ +static int is_vrf_present_in_irt_vrfs(struct list *vrfs, struct bgp *bgp_vrf) +{ + struct listnode *node = NULL, *nnode = NULL; + struct bgp *tmp_bgp_vrf = NULL; + + for (ALL_LIST_ELEMENTS(vrfs, node, nnode, tmp_bgp_vrf)) { + if (tmp_bgp_vrf == bgp_vrf) + return 1; + } + return 0; +} + +/* + * Make import route target hash key. + */ +static unsigned int import_rt_hash_key_make(const void *p) +{ + const struct irt_node *irt = p; + const char *pnt = irt->rt.val; + + return jhash(pnt, 8, 0xdeadbeef); +} + +/* + * Comparison function for import rt hash + */ +static bool import_rt_hash_cmp(const void *p1, const void *p2) +{ + const struct irt_node *irt1 = p1; + const struct irt_node *irt2 = p2; + + return (memcmp(irt1->rt.val, irt2->rt.val, ECOMMUNITY_SIZE) == 0); +} + +/* + * Create a new import_rt + */ +static struct irt_node *import_rt_new(struct bgp *bgp, + struct ecommunity_val *rt) +{ + struct irt_node *irt; + + irt = XCALLOC(MTYPE_BGP_EVPN_IMPORT_RT, sizeof(struct irt_node)); + + irt->rt = *rt; + irt->vnis = list_new(); + + /* Add to hash */ + (void)hash_get(bgp->import_rt_hash, irt, hash_alloc_intern); + + return irt; +} + +/* + * Free the import rt node + */ +static void import_rt_free(struct bgp *bgp, struct irt_node *irt) +{ + hash_release(bgp->import_rt_hash, irt); + list_delete(&irt->vnis); + XFREE(MTYPE_BGP_EVPN_IMPORT_RT, irt); +} + +static void hash_import_rt_free(struct irt_node *irt) +{ + XFREE(MTYPE_BGP_EVPN_IMPORT_RT, irt); +} + +/* + * Function to lookup Import RT node - used to map a RT to set of + * VNIs importing routes with that RT. + */ +static struct irt_node *lookup_import_rt(struct bgp *bgp, + struct ecommunity_val *rt) +{ + struct irt_node *irt; + struct irt_node tmp; + + memset(&tmp, 0, sizeof(tmp)); + memcpy(&tmp.rt, rt, ECOMMUNITY_SIZE); + irt = hash_lookup(bgp->import_rt_hash, &tmp); + return irt; +} + +/* + * Is specified VNI present on the RT's list of "importing" VNIs? + */ +static int is_vni_present_in_irt_vnis(struct list *vnis, struct bgpevpn *vpn) +{ + struct listnode *node, *nnode; + struct bgpevpn *tmp_vpn; + + for (ALL_LIST_ELEMENTS(vnis, node, nnode, tmp_vpn)) { + if (tmp_vpn == vpn) + return 1; + } + + return 0; +} + +/* Flag if the route is injectable into EVPN. + * This would be following category: + * Non-imported route, + * Non-EVPN imported route, + */ +bool is_route_injectable_into_evpn_non_supp(struct bgp_path_info *pi) +{ + struct bgp_path_info *parent_pi; + struct bgp_table *table; + struct bgp_dest *dest; + + if (pi->sub_type != BGP_ROUTE_IMPORTED || !pi->extra || + !pi->extra->vrfleak || !pi->extra->vrfleak->parent) + return true; + + parent_pi = (struct bgp_path_info *)pi->extra->vrfleak->parent; + dest = parent_pi->net; + if (!dest) + return true; + table = bgp_dest_table(dest); + if (table && + table->afi == AFI_L2VPN && + table->safi == SAFI_EVPN) + return false; + + return true; +} + +/* Flag if the route is injectable into EVPN. + * This would be following category: + * Non-imported route, + * Non-EVPN imported route, + * Non Aggregate suppressed route. + */ +bool is_route_injectable_into_evpn(struct bgp_path_info *pi) +{ + /* do not import aggr suppressed routes */ + if (bgp_path_suppressed(pi)) + return false; + + return is_route_injectable_into_evpn_non_supp(pi); +} + +/* + * Compare Route Targets. + */ +int bgp_evpn_route_target_cmp(struct ecommunity *ecom1, + struct ecommunity *ecom2) +{ + if (ecom1 && !ecom2) + return -1; + + if (!ecom1 && ecom2) + return 1; + + if (!ecom1 && !ecom2) + return 0; + + if (ecom1->str && !ecom2->str) + return -1; + + if (!ecom1->str && ecom2->str) + return 1; + + if (!ecom1->str && !ecom2->str) + return 0; + + return strcmp(ecom1->str, ecom2->str); +} + +/* + * Compare L3 Route Targets. + */ +static int evpn_vrf_route_target_cmp(struct vrf_route_target *rt1, + struct vrf_route_target *rt2) +{ + return bgp_evpn_route_target_cmp(rt1->ecom, rt2->ecom); +} + +void bgp_evpn_xxport_delete_ecomm(void *val) +{ + struct ecommunity *ecomm = val; + ecommunity_free(&ecomm); +} + +/* + * Delete l3 Route Target. + */ +static void evpn_vrf_rt_del(void *val) +{ + struct vrf_route_target *l3rt = val; + + ecommunity_free(&l3rt->ecom); + + XFREE(MTYPE_VRF_ROUTE_TARGET, l3rt); +} + +/* + * Allocate a new l3 Route Target. + */ +static struct vrf_route_target *evpn_vrf_rt_new(struct ecommunity *ecom) +{ + struct vrf_route_target *l3rt; + + l3rt = XCALLOC(MTYPE_VRF_ROUTE_TARGET, sizeof(struct vrf_route_target)); + + l3rt->ecom = ecom; + + return l3rt; +} + +/* + * Mask off global-admin field of specified extended community (RT), + * just retain the local-admin field. + */ +static inline void mask_ecom_global_admin(struct ecommunity_val *dst, + const struct ecommunity_val *src) +{ + uint8_t type; + + type = src->val[0]; + dst->val[0] = 0; + if (type == ECOMMUNITY_ENCODE_AS) { + dst->val[2] = dst->val[3] = 0; + } else if (type == ECOMMUNITY_ENCODE_AS4 + || type == ECOMMUNITY_ENCODE_IP) { + dst->val[2] = dst->val[3] = 0; + dst->val[4] = dst->val[5] = 0; + } +} + +/* + * Converts the RT to Ecommunity Value and adjusts masking based + * on flags set for RT. + */ +static void vrf_rt2ecom_val(struct ecommunity_val *to_eval, + const struct vrf_route_target *l3rt, int iter) +{ + const struct ecommunity_val *eval; + + eval = (const struct ecommunity_val *)(l3rt->ecom->val + + (iter * ECOMMUNITY_SIZE)); + /* If using "automatic" or "wildcard *" RT, + * we only care about the local-admin sub-field. + * This is to facilitate using L3VNI(VRF-VNI) + * as the RT for EBGP peering too and simplify + * configurations by allowing any ASN via '*'. + */ + memcpy(to_eval, eval, ECOMMUNITY_SIZE); + + if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO) || + CHECK_FLAG(l3rt->flags, BGP_VRF_RT_WILD)) + mask_ecom_global_admin(to_eval, eval); +} + +/* + * Map one RT to specified VRF. + * bgp_vrf = BGP vrf instance + */ +static void map_vrf_to_rt(struct bgp *bgp_vrf, struct vrf_route_target *l3rt) +{ + uint32_t i = 0; + + for (i = 0; i < l3rt->ecom->size; i++) { + struct vrf_irt_node *irt = NULL; + struct ecommunity_val eval_tmp; + + /* Adjust masking for value */ + vrf_rt2ecom_val(&eval_tmp, l3rt, i); + + irt = lookup_vrf_import_rt(&eval_tmp); + + if (irt && is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) + return; /* Already mapped. */ + + if (!irt) + irt = vrf_import_rt_new(&eval_tmp); + + /* Add VRF to the list for this RT. */ + listnode_add(irt->vrfs, bgp_vrf); + } +} + +/* + * Unmap specified VRF from specified RT. If there are no other + * VRFs for this RT, then the RT hash is deleted. + * bgp_vrf: BGP VRF specific instance + */ +static void unmap_vrf_from_rt(struct bgp *bgp_vrf, + struct vrf_route_target *l3rt) +{ + uint32_t i; + + for (i = 0; i < l3rt->ecom->size; i++) { + struct vrf_irt_node *irt; + struct ecommunity_val eval_tmp; + + /* Adjust masking for value */ + vrf_rt2ecom_val(&eval_tmp, l3rt, i); + + irt = lookup_vrf_import_rt(&eval_tmp); + + if (!irt) + return; /* Not mapped */ + + /* Delete VRF from list for this RT. */ + listnode_delete(irt->vrfs, bgp_vrf); + + if (!listnode_head(irt->vrfs)) + vrf_import_rt_free(irt); + } +} + +/* + * Map one RT to specified VNI. + */ +static void map_vni_to_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct ecommunity_val *eval) +{ + struct irt_node *irt; + struct ecommunity_val eval_tmp; + + /* If using "automatic" RT, we only care about the local-admin + * sub-field. + * This is to facilitate using VNI as the RT for EBGP peering too. + */ + memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); + if (!is_import_rt_configured(vpn)) + mask_ecom_global_admin(&eval_tmp, eval); + + irt = lookup_import_rt(bgp, &eval_tmp); + if (irt) + if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) + /* Already mapped. */ + return; + + if (!irt) + irt = import_rt_new(bgp, &eval_tmp); + + /* Add VNI to the hash list for this RT. */ + listnode_add(irt->vnis, vpn); +} + +/* + * Unmap specified VNI from specified RT. If there are no other + * VNIs for this RT, then the RT hash is deleted. + */ +static void unmap_vni_from_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct irt_node *irt) +{ + /* Delete VNI from hash list for this RT. */ + listnode_delete(irt->vnis, vpn); + if (!listnode_head(irt->vnis)) { + import_rt_free(bgp, irt); + } +} + +static void bgp_evpn_get_rmac_nexthop(struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct attr *attr, uint8_t flags) +{ + struct bgp *bgp_vrf = vpn->bgp_vrf; + + memset(&attr->rmac, 0, sizeof(struct ethaddr)); + if (!bgp_vrf) + return; + + if (p->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return; + + /* Copy sys (pip) RMAC and PIP IP as nexthop + * in case of route is self MAC-IP, + * advertise-pip and advertise-svi-ip features + * are enabled. + * Otherwise, for all host MAC-IP route's + * copy anycast RMAC. + */ + if (CHECK_FLAG(flags, BGP_EVPN_MACIP_TYPE_SVI_IP) + && bgp_vrf->evpn_info->advertise_pip && + bgp_vrf->evpn_info->is_anycast_mac) { + /* copy sys rmac */ + memcpy(&attr->rmac, &bgp_vrf->evpn_info->pip_rmac, + ETH_ALEN); + attr->nexthop = bgp_vrf->evpn_info->pip_ip; + attr->mp_nexthop_global_in = + bgp_vrf->evpn_info->pip_ip; + } else + memcpy(&attr->rmac, &bgp_vrf->rmac, ETH_ALEN); +} + +/* + * Create RT extended community automatically from passed information: + * of the form AS:VNI. + * NOTE: We use only the lower 16 bits of the AS. This is sufficient as + * the need is to get a RT value that will be unique across different + * VNIs but the same across routers (in the same AS) for a particular + * VNI. + */ +static void form_auto_rt(struct bgp *bgp, vni_t vni, struct list *rtl, + bool is_l3) +{ + struct ecommunity_val eval; + struct ecommunity *ecomadd; + struct ecommunity *ecom; + struct vrf_route_target *l3rt; + struct vrf_route_target *newrt; + bool ecom_found = false; + struct listnode *node; + + if (bgp->advertise_autort_rfc8365) + vni |= EVPN_AUTORT_VXLAN; + encode_route_target_as((bgp->as & 0xFFFF), vni, &eval, true); + + ecomadd = ecommunity_new(); + ecommunity_add_val(ecomadd, &eval, false, false); + + if (is_l3) { + for (ALL_LIST_ELEMENTS_RO(rtl, node, l3rt)) + if (ecommunity_cmp(ecomadd, l3rt->ecom)) { + ecom_found = true; + break; + } + } else { + for (ALL_LIST_ELEMENTS_RO(rtl, node, ecom)) + if (ecommunity_cmp(ecomadd, ecom)) { + ecom_found = true; + break; + } + } + + if (!ecom_found) { + if (is_l3) { + newrt = evpn_vrf_rt_new(ecomadd); + /* Label it as autoderived */ + SET_FLAG(newrt->flags, BGP_VRF_RT_AUTO); + listnode_add_sort(rtl, newrt); + } else + listnode_add_sort(rtl, ecomadd); + } else + ecommunity_free(&ecomadd); +} + +/* + * Derive RD and RT for a VNI automatically. Invoked at the time of + * creation of a VNI. + */ +static void derive_rd_rt_for_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + bgp_evpn_derive_auto_rd(bgp, vpn); + bgp_evpn_derive_auto_rt_import(bgp, vpn); + bgp_evpn_derive_auto_rt_export(bgp, vpn); +} + +/* + * Convert nexthop (remote VTEP IP) into an IPv6 address. + */ +static void evpn_convert_nexthop_to_ipv6(struct attr *attr) +{ + if (BGP_ATTR_NEXTHOP_AFI_IP6(attr)) + return; + ipv4_to_ipv4_mapped_ipv6(&attr->mp_nexthop_global, attr->nexthop); + attr->mp_nexthop_len = IPV6_MAX_BYTELEN; +} + +/* + * Wrapper for node get in global table. + */ +struct bgp_dest *bgp_evpn_global_node_get(struct bgp_table *table, afi_t afi, + safi_t safi, + const struct prefix_evpn *evp, + struct prefix_rd *prd, + const struct bgp_path_info *local_pi) +{ + struct prefix_evpn global_p; + + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) { + /* prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy of the prefix + */ + evpn_type1_prefix_global_copy(&global_p, evp); + evp = &global_p; + } else if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && + local_pi) { + /* + * prefix in the global table needs MAC/IP, ensure they are + * present, using one's from local table's path_info. + */ + if (is_evpn_prefix_ipaddr_none(evp)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy( + &global_p, evp, NULL /* mac */, + evpn_type2_path_info_get_ip(local_pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy( + &global_p, evp, + evpn_type2_path_info_get_mac(local_pi), + NULL /* ip */); + } + + evp = &global_p; + } + return bgp_afi_node_get(table, afi, safi, (struct prefix *)evp, prd); +} + +/* + * Wrapper for node lookup in global table. + */ +struct bgp_dest *bgp_evpn_global_node_lookup( + struct bgp_table *table, safi_t safi, const struct prefix_evpn *evp, + struct prefix_rd *prd, const struct bgp_path_info *local_pi) +{ + struct prefix_evpn global_p; + + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) { + /* prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy of the prefix + */ + evpn_type1_prefix_global_copy(&global_p, evp); + evp = &global_p; + } else if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE && + local_pi) { + /* + * prefix in the global table needs MAC/IP, ensure they are + * present, using one's from local table's path_info. + */ + if (is_evpn_prefix_ipaddr_none(evp)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy( + &global_p, evp, NULL /* mac */, + evpn_type2_path_info_get_ip(local_pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy( + &global_p, evp, + evpn_type2_path_info_get_mac(local_pi), + NULL /* ip */); + } + + evp = &global_p; + } + return bgp_safi_node_lookup(table, safi, (struct prefix *)evp, prd); +} + +/* + * Wrapper for node get in VNI IP table. + */ +struct bgp_dest *bgp_evpn_vni_ip_node_get(struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi) +{ + struct prefix_evpn vni_p; + + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE && parent_pi) { + /* prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy for the VNI + */ + evpn_type1_prefix_vni_ip_copy(&vni_p, evp, + parent_pi->attr->nexthop); + evp = &vni_p; + } else if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + /* Only MAC-IP should go into this table, not mac-only */ + assert(is_evpn_prefix_ipaddr_none(evp) == false); + + /* + * prefix in the vni IP table doesn't include MAC so + * we need to create a different copy of the prefix. + */ + evpn_type2_prefix_vni_ip_copy(&vni_p, evp); + evp = &vni_p; + } + return bgp_node_get(table, (struct prefix *)evp); +} + +/* + * Wrapper for node lookup in VNI IP table. + */ +struct bgp_dest * +bgp_evpn_vni_ip_node_lookup(const struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi) +{ + struct prefix_evpn vni_p; + + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE && parent_pi) { + /* prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy for the VNI + */ + evpn_type1_prefix_vni_ip_copy(&vni_p, evp, + parent_pi->attr->nexthop); + evp = &vni_p; + } else if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + /* Only MAC-IP should go into this table, not mac-only */ + assert(is_evpn_prefix_ipaddr_none(evp) == false); + + /* + * prefix in the vni IP table doesn't include MAC so + * we need to create a different copy of the prefix. + */ + evpn_type2_prefix_vni_ip_copy(&vni_p, evp); + evp = &vni_p; + } + return bgp_node_lookup(table, (struct prefix *)evp); +} + +/* + * Wrapper for node get in VNI MAC table. + */ +struct bgp_dest * +bgp_evpn_vni_mac_node_get(struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi) +{ + struct prefix_evpn vni_p; + + /* Only type-2 should ever go into this table */ + assert(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE); + + /* + * prefix in the vni MAC table doesn't include IP so + * we need to create a different copy of the prefix. + */ + evpn_type2_prefix_vni_mac_copy(&vni_p, evp); + evp = &vni_p; + return bgp_node_get(table, (struct prefix *)evp); +} + +/* + * Wrapper for node lookup in VNI MAC table. + */ +struct bgp_dest * +bgp_evpn_vni_mac_node_lookup(const struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi) +{ + struct prefix_evpn vni_p; + + /* Only type-2 should ever go into this table */ + assert(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE); + + /* + * prefix in the vni MAC table doesn't include IP so + * we need to create a different copy of the prefix. + */ + evpn_type2_prefix_vni_mac_copy(&vni_p, evp); + evp = &vni_p; + return bgp_node_lookup(table, (struct prefix *)evp); +} + +/* + * Wrapper for node get in both VNI tables. + */ +struct bgp_dest *bgp_evpn_vni_node_get(struct bgpevpn *vpn, + const struct prefix_evpn *p, + const struct bgp_path_info *parent_pi) +{ + if ((p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && + (is_evpn_prefix_ipaddr_none(p) == true)) + return bgp_evpn_vni_mac_node_get(vpn->mac_table, p, parent_pi); + + return bgp_evpn_vni_ip_node_get(vpn->ip_table, p, parent_pi); +} + +/* + * Wrapper for node lookup in both VNI tables. + */ +struct bgp_dest *bgp_evpn_vni_node_lookup(const struct bgpevpn *vpn, + const struct prefix_evpn *p, + const struct bgp_path_info *parent_pi) +{ + if ((p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && + (is_evpn_prefix_ipaddr_none(p) == true)) + return bgp_evpn_vni_mac_node_lookup(vpn->mac_table, p, + parent_pi); + + return bgp_evpn_vni_ip_node_lookup(vpn->ip_table, p, parent_pi); +} + +/* + * Add (update) or delete MACIP from zebra. + */ +static enum zclient_send_status bgp_zebra_send_remote_macip( + struct bgp *bgp, struct bgpevpn *vpn, const struct prefix_evpn *p, + const struct ethaddr *mac, struct in_addr remote_vtep_ip, int add, + uint8_t flags, uint32_t seq, esi_t *esi) +{ + struct stream *s; + uint16_t ipa_len; + static struct in_addr zero_remote_vtep_ip; + bool esi_valid; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: No zclient or zclient->sock exists", + __func__); + return ZCLIENT_SEND_SUCCESS; + } + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing remote macip", + __func__); + return ZCLIENT_SEND_SUCCESS; + } + + if (!esi) + esi = zero_esi; + s = zclient->obuf; + stream_reset(s); + + zclient_create_header( + s, add ? ZEBRA_REMOTE_MACIP_ADD : ZEBRA_REMOTE_MACIP_DEL, + bgp->vrf_id); + stream_putl(s, vpn ? vpn->vni : 0); + + if (mac) /* Mac Addr */ + stream_put(s, &mac->octet, ETH_ALEN); + else + stream_put(s, &p->prefix.macip_addr.mac.octet, ETH_ALEN); + + /* IP address length and IP address, if any. */ + if (is_evpn_prefix_ipaddr_none(p)) + stream_putw(s, 0); + else { + ipa_len = is_evpn_prefix_ipaddr_v4(p) ? IPV4_MAX_BYTELEN + : IPV6_MAX_BYTELEN; + stream_putw(s, ipa_len); + stream_put(s, &p->prefix.macip_addr.ip.ip.addr, ipa_len); + } + /* If the ESI is valid that becomes the nexthop; tape out the + * VTEP-IP for that case + */ + if (bgp_evpn_is_esi_valid(esi)) { + esi_valid = true; + stream_put_in_addr(s, &zero_remote_vtep_ip); + } else { + esi_valid = false; + stream_put_in_addr(s, &remote_vtep_ip); + } + + /* TX flags - MAC sticky status and/or gateway mac */ + /* Also TX the sequence number of the best route. */ + if (add) { + stream_putc(s, flags); + stream_putl(s, seq); + stream_put(s, esi, sizeof(esi_t)); + } + + stream_putw_at(s, 0, stream_get_endp(s)); + + if (bgp_debug_zebra(NULL)) { + char esi_buf[ESI_STR_LEN]; + + if (esi_valid) + esi_to_str(esi, esi_buf, sizeof(esi_buf)); + else + snprintf(esi_buf, sizeof(esi_buf), "-"); + zlog_debug( + "Tx %s MACIP, VNI %u MAC %pEA IP %pIA flags 0x%x seq %u remote VTEP %pI4 esi %s", + add ? "ADD" : "DEL", (vpn ? vpn->vni : 0), + (mac ? mac : &p->prefix.macip_addr.mac), + &p->prefix.macip_addr.ip, flags, seq, &remote_vtep_ip, + esi_buf); + } + + frrtrace(5, frr_bgp, evpn_mac_ip_zsend, add, vpn, p, remote_vtep_ip, + esi); + + return zclient_send_message(zclient); +} + +/* + * Add (update) or delete remote VTEP from zebra. + */ +static enum zclient_send_status +bgp_zebra_send_remote_vtep(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, int flood_control, + int add) +{ + struct stream *s; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: No zclient or zclient->sock exists", + __func__); + return ZCLIENT_SEND_SUCCESS; + } + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing remote vtep", + __func__); + return ZCLIENT_SEND_SUCCESS; + } + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header( + s, add ? ZEBRA_REMOTE_VTEP_ADD : ZEBRA_REMOTE_VTEP_DEL, + bgp->vrf_id); + stream_putl(s, vpn ? vpn->vni : 0); + if (is_evpn_prefix_ipaddr_v4(p)) + stream_put_in_addr(s, &p->prefix.imet_addr.ip.ipaddr_v4); + else if (is_evpn_prefix_ipaddr_v6(p)) { + flog_err( + EC_BGP_VTEP_INVALID, + "Bad remote IP when trying to %s remote VTEP for VNI %u", + add ? "ADD" : "DEL", (vpn ? vpn->vni : 0)); + return ZCLIENT_SEND_FAILURE; + } + stream_putl(s, flood_control); + + stream_putw_at(s, 0, stream_get_endp(s)); + + if (bgp_debug_zebra(NULL)) + zlog_debug("Tx %s Remote VTEP, VNI %u remote VTEP %pI4", + add ? "ADD" : "DEL", (vpn ? vpn->vni : 0), + &p->prefix.imet_addr.ip.ipaddr_v4); + + frrtrace(3, frr_bgp, evpn_bum_vtep_zsend, add, vpn, p); + + return zclient_send_message(zclient); +} + +/* + * Build extended communities for EVPN prefix route. + */ +static void build_evpn_type5_route_extcomm(struct bgp *bgp_vrf, + struct attr *attr) +{ + struct ecommunity ecom_encap; + struct ecommunity_val eval; + struct ecommunity_val eval_rmac; + bgp_encap_types tnl_type; + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + struct ecommunity *old_ecom; + struct ecommunity *ecom; + struct list *vrf_export_rtl = NULL; + + /* Encap */ + tnl_type = BGP_ENCAP_TYPE_VXLAN; + memset(&ecom_encap, 0, sizeof(ecom_encap)); + encode_encap_extcomm(tnl_type, &eval); + ecom_encap.size = 1; + ecom_encap.unit_size = ECOMMUNITY_SIZE; + ecom_encap.val = (uint8_t *)eval.val; + + /* Add Encap */ + if (bgp_attr_get_ecommunity(attr)) { + old_ecom = bgp_attr_get_ecommunity(attr); + ecom = ecommunity_merge(ecommunity_dup(old_ecom), &ecom_encap); + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } else + ecom = ecommunity_dup(&ecom_encap); + bgp_attr_set_ecommunity(attr, ecom); + attr->encap_tunneltype = tnl_type; + + /* Add the export RTs for L3VNI/VRF */ + vrf_export_rtl = bgp_vrf->vrf_export_rtl; + for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, l3rt)) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), + l3rt->ecom)); + + /* add the router mac extended community */ + if (!is_zero_mac(&attr->rmac)) { + encode_rmac_extcomm(&eval_rmac, &attr->rmac); + ecommunity_add_val(bgp_attr_get_ecommunity(attr), &eval_rmac, + true, true); + } +} + +/* + * Build extended communities for EVPN route. + * This function is applicable for type-2 and type-3 routes. The layer-2 RT + * and ENCAP extended communities are applicable for all routes. + * The default gateway extended community and MAC mobility (sticky) extended + * community are added as needed based on passed settings - only for type-2 + * routes. Likewise, the layer-3 RT and Router MAC extended communities are + * added, if present, based on passed settings - only for non-link-local + * type-2 routes. + */ +static void build_evpn_route_extcomm(struct bgpevpn *vpn, struct attr *attr, + int add_l3_ecomm, + struct ecommunity *macvrf_soo) +{ + struct ecommunity ecom_encap; + struct ecommunity ecom_sticky; + struct ecommunity ecom_default_gw; + struct ecommunity ecom_na; + struct ecommunity_val eval; + struct ecommunity_val eval_sticky; + struct ecommunity_val eval_default_gw; + struct ecommunity_val eval_rmac; + struct ecommunity_val eval_na; + bool proxy; + + bgp_encap_types tnl_type; + struct listnode *node, *nnode; + struct ecommunity *ecom; + struct vrf_route_target *l3rt; + uint32_t seqnum; + struct list *vrf_export_rtl = NULL; + + /* Encap */ + tnl_type = BGP_ENCAP_TYPE_VXLAN; + memset(&ecom_encap, 0, sizeof(ecom_encap)); + encode_encap_extcomm(tnl_type, &eval); + ecom_encap.size = 1; + ecom_encap.unit_size = ECOMMUNITY_SIZE; + ecom_encap.val = (uint8_t *)eval.val; + + /* Add Encap */ + bgp_attr_set_ecommunity(attr, ecommunity_dup(&ecom_encap)); + attr->encap_tunneltype = tnl_type; + + /* Add the export RTs for L2VNI */ + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) + bgp_attr_set_ecommunity( + attr, + ecommunity_merge(bgp_attr_get_ecommunity(attr), ecom)); + + /* Add the export RTs for L3VNI if told to - caller determines + * when this should be done. + */ + if (add_l3_ecomm) { + vrf_export_rtl = bgpevpn_get_vrf_export_rtl(vpn); + if (vrf_export_rtl && !list_isempty(vrf_export_rtl)) { + for (ALL_LIST_ELEMENTS(vrf_export_rtl, node, nnode, + l3rt)) + bgp_attr_set_ecommunity( + attr, + ecommunity_merge( + bgp_attr_get_ecommunity(attr), + l3rt->ecom)); + } + } + + /* Add MAC mobility (sticky) if needed. */ + if (attr->sticky) { + seqnum = 0; + memset(&ecom_sticky, 0, sizeof(ecom_sticky)); + encode_mac_mobility_extcomm(1, seqnum, &eval_sticky); + ecom_sticky.size = 1; + ecom_sticky.unit_size = ECOMMUNITY_SIZE; + ecom_sticky.val = (uint8_t *)eval_sticky.val; + bgp_attr_set_ecommunity( + attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), + &ecom_sticky)); + } + + /* Add RMAC, if told to. */ + if (add_l3_ecomm) { + encode_rmac_extcomm(&eval_rmac, &attr->rmac); + ecommunity_add_val(bgp_attr_get_ecommunity(attr), &eval_rmac, + true, true); + } + + /* Add default gateway, if needed. */ + if (attr->default_gw) { + memset(&ecom_default_gw, 0, sizeof(ecom_default_gw)); + encode_default_gw_extcomm(&eval_default_gw); + ecom_default_gw.size = 1; + ecom_default_gw.unit_size = ECOMMUNITY_SIZE; + ecom_default_gw.val = (uint8_t *)eval_default_gw.val; + bgp_attr_set_ecommunity( + attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), + &ecom_default_gw)); + } + + proxy = !!(attr->es_flags & ATTR_ES_PROXY_ADVERT); + if (attr->router_flag || proxy) { + memset(&ecom_na, 0, sizeof(ecom_na)); + encode_na_flag_extcomm(&eval_na, attr->router_flag, proxy); + ecom_na.size = 1; + ecom_na.unit_size = ECOMMUNITY_SIZE; + ecom_na.val = (uint8_t *)eval_na.val; + bgp_attr_set_ecommunity( + attr, ecommunity_merge(bgp_attr_get_ecommunity(attr), + &ecom_na)); + } + + /* Add MAC-VRF SoO, if configured */ + if (macvrf_soo) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(attr->ecommunity, macvrf_soo)); +} + +/* + * Add MAC mobility extended community to attribute. + */ +static void add_mac_mobility_to_attr(uint32_t seq_num, struct attr *attr) +{ + struct ecommunity ecom_tmp; + struct ecommunity_val eval; + uint8_t *ecom_val_ptr; + uint32_t i; + uint8_t *pnt; + int type = 0; + int sub_type = 0; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + /* Build MM */ + encode_mac_mobility_extcomm(0, seq_num, &eval); + + /* Find current MM ecommunity */ + ecom_val_ptr = NULL; + + if (ecomm) { + for (i = 0; i < ecomm->size; i++) { + pnt = ecomm->val + (i * ecomm->unit_size); + type = *pnt++; + sub_type = *pnt++; + + if (type == ECOMMUNITY_ENCODE_EVPN + && sub_type + == ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY) { + ecom_val_ptr = + (ecomm->val + (i * ecomm->unit_size)); + break; + } + } + } + + /* Update the existing MM ecommunity */ + if (ecom_val_ptr) { + memcpy(ecom_val_ptr, eval.val, sizeof(char) * ecomm->unit_size); + } + /* Add MM to existing */ + else { + memset(&ecom_tmp, 0, sizeof(ecom_tmp)); + ecom_tmp.size = 1; + ecom_tmp.unit_size = ECOMMUNITY_SIZE; + ecom_tmp.val = (uint8_t *)eval.val; + + if (ecomm) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(ecomm, &ecom_tmp)); + else + bgp_attr_set_ecommunity(attr, + ecommunity_dup(&ecom_tmp)); + } +} + +/* Install EVPN route into zebra. */ +enum zclient_send_status evpn_zebra_install(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *pi) +{ + uint8_t flags; + int flood_control = VXLAN_FLOOD_DISABLED; + uint32_t seq; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + flags = 0; + + if (pi->sub_type == BGP_ROUTE_IMPORTED) { + if (pi->attr->sticky) + SET_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY); + if (pi->attr->default_gw) + SET_FLAG(flags, ZEBRA_MACIP_TYPE_GW); + if (is_evpn_prefix_ipaddr_v6(p) && + pi->attr->router_flag) + SET_FLAG(flags, ZEBRA_MACIP_TYPE_ROUTER_FLAG); + + seq = mac_mobility_seqnum(pi->attr); + /* if local ES notify zebra that this is a sync path */ + if (bgp_evpn_attr_is_local_es(pi->attr)) { + SET_FLAG(flags, ZEBRA_MACIP_TYPE_SYNC_PATH); + if (bgp_evpn_attr_is_proxy(pi->attr)) + SET_FLAG(flags, + ZEBRA_MACIP_TYPE_PROXY_ADVERT); + } + } else { + if (!bgp_evpn_attr_is_sync(pi->attr)) + return 0; + + /* if a local path is being turned around and sent + * to zebra it is because it is a sync path on + * a local ES + */ + SET_FLAG(flags, ZEBRA_MACIP_TYPE_SYNC_PATH); + /* supply the highest peer seq number to zebra + * for MM seq syncing + */ + seq = bgp_evpn_attr_get_sync_seq(pi->attr); + /* if any of the paths from the peer have the ROUTER + * flag set install the local entry as a router entry + */ + if (is_evpn_prefix_ipaddr_v6(p) && + (pi->attr->es_flags & + ATTR_ES_PEER_ROUTER)) + SET_FLAG(flags, + ZEBRA_MACIP_TYPE_ROUTER_FLAG); + + if (!(pi->attr->es_flags & ATTR_ES_PEER_ACTIVE)) + SET_FLAG(flags, + ZEBRA_MACIP_TYPE_PROXY_ADVERT); + } + + ret = bgp_zebra_send_remote_macip( + bgp, vpn, p, + (is_evpn_prefix_ipaddr_none(p) + ? NULL /* MAC update */ + : evpn_type2_path_info_get_mac( + pi) /* MAC-IP update */), + pi->attr->nexthop, 1, flags, seq, + bgp_evpn_attr_get_esi(pi->attr)); + } else if (p->prefix.route_type == BGP_EVPN_AD_ROUTE) { + ret = bgp_evpn_remote_es_evi_add(bgp, vpn, p); + } else { + switch (bgp_attr_get_pmsi_tnl_type(pi->attr)) { + case PMSI_TNLTYPE_INGR_REPL: + flood_control = VXLAN_FLOOD_HEAD_END_REPL; + break; + + case PMSI_TNLTYPE_PIM_SM: + flood_control = VXLAN_FLOOD_PIM_SM; + break; + + case PMSI_TNLTYPE_NO_INFO: + case PMSI_TNLTYPE_RSVP_TE_P2MP: + case PMSI_TNLTYPE_MLDP_P2MP: + case PMSI_TNLTYPE_PIM_SSM: + case PMSI_TNLTYPE_PIM_BIDIR: + case PMSI_TNLTYPE_MLDP_MP2MP: + flood_control = VXLAN_FLOOD_DISABLED; + break; + } + + ret = bgp_zebra_send_remote_vtep(bgp, vpn, p, flood_control, 1); + } + + return ret; +} + +/* Uninstall EVPN route from zebra. */ +enum zclient_send_status evpn_zebra_uninstall(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *pi, + bool is_sync) +{ + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + ret = bgp_zebra_send_remote_macip( + bgp, vpn, p, + (is_evpn_prefix_ipaddr_none(p) + ? NULL /* MAC update */ + : evpn_type2_path_info_get_mac( + pi) /* MAC-IP update */), + (is_sync ? zero_vtep_ip : pi->attr->nexthop), 0, 0, 0, + NULL); + else if (p->prefix.route_type == BGP_EVPN_AD_ROUTE) + ret = bgp_evpn_remote_es_evi_del(bgp, vpn, p); + else + ret = bgp_zebra_send_remote_vtep(bgp, vpn, p, + VXLAN_FLOOD_DISABLED, 0); + + return ret; +} + +/* + * Due to MAC mobility, the prior "local" best route has been supplanted + * by a "remote" best route. The prior route has to be deleted and withdrawn + * from peers. + */ +static void evpn_delete_old_local_route(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest, + struct bgp_path_info *old_local, + struct bgp_path_info *new_select) +{ + struct bgp_dest *global_dest; + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) { + char esi_buf[ESI_STR_LEN]; + char esi_buf2[ESI_STR_LEN]; + struct prefix_evpn *evp = + (struct prefix_evpn *)bgp_dest_get_prefix(dest); + + zlog_debug("local path deleted %pFX es %s; new-path-es %s", evp, + esi_to_str(&old_local->attr->esi, esi_buf, + sizeof(esi_buf)), + new_select ? esi_to_str(&new_select->attr->esi, + esi_buf2, sizeof(esi_buf2)) + : ""); + } + + /* Locate route node in the global EVPN routing table. Note that + * this table is a 2-level tree (RD-level + Prefix-level) similar to + * L3VPN routes. + */ + global_dest = bgp_evpn_global_node_lookup( + bgp->rib[afi][safi], safi, + (const struct prefix_evpn *)bgp_dest_get_prefix(dest), + &vpn->prd, old_local); + if (global_dest) { + /* Delete route entry in the global EVPN table. */ + delete_evpn_route_entry(bgp, afi, safi, global_dest, &pi); + + /* Schedule for processing - withdraws to peers happen from + * this table. + */ + if (pi) + bgp_process(bgp, global_dest, pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + /* Delete route entry in the VNI route table, caller to remove. */ + bgp_path_info_delete(dest, old_local); +} + +/* + * Calculate the best path for an EVPN route. Install/update best path in zebra, + * if appropriate. + * Note: vpn is NULL for local EAD-ES routes. + */ +int evpn_route_select_install(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest, struct bgp_path_info *pi) +{ + struct bgp_path_info *old_select, *new_select, *first; + struct bgp_path_info_pair old_and_new; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + int ret = 0; + + first = bgp_dest_get_bgp_path_info(dest); + SET_FLAG(pi->flags, BGP_PATH_UNSORTED); + if (pi != first) { + if (pi->next) + pi->next->prev = pi->prev; + if (pi->prev) + pi->prev->next = pi->next; + + if (first) + first->prev = pi; + pi->next = first; + pi->prev = NULL; + bgp_dest_set_bgp_path_info(dest, pi); + } + + /* Compute the best path. */ + bgp_best_selection(bgp, dest, &bgp->maxpaths[afi][safi], &old_and_new, + afi, safi); + old_select = old_and_new.old; + new_select = old_and_new.new; + + /* If the best path hasn't changed - see if there is still something to + * update to zebra RIB. + * Remote routes and SYNC route (i.e. local routes with + * SYNCED_FROM_PEER flag) need to updated to zebra on any attr + * change. + */ + if (old_select && old_select == new_select + && old_select->type == ZEBRA_ROUTE_BGP + && (old_select->sub_type == BGP_ROUTE_IMPORTED || + bgp_evpn_attr_is_sync(old_select->attr)) + && !CHECK_FLAG(dest->flags, BGP_NODE_USER_CLEAR) + && !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) + && !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + if (bgp_zebra_has_route_changed(old_select)) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) + ret = evpn_zebra_install(bgp, vpn, + (const struct prefix_evpn + *) + bgp_dest_get_prefix( + dest), + old_select); + else + bgp_zebra_route_install(dest, old_select, bgp, + true, vpn, false); + } + + UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG); + bgp_zebra_clear_route_change_flags(dest); + return ret; + } + + /* If the user did a "clear" this flag will be set */ + UNSET_FLAG(dest->flags, BGP_NODE_USER_CLEAR); + + /* bestpath has changed; update relevant fields and install or uninstall + * into the zebra RIB. + */ + if (old_select || new_select) + bgp_bump_version(dest); + + if (old_select) + bgp_path_info_unset_flag(dest, old_select, BGP_PATH_SELECTED); + if (new_select) { + bgp_path_info_set_flag(dest, new_select, BGP_PATH_SELECTED); + bgp_path_info_unset_flag(dest, new_select, + BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(new_select->flags, BGP_PATH_LINK_BW_CHG); + } + + /* a local entry with the SYNC flag also results in a MAC-IP update + * to zebra + */ + if (new_select && new_select->type == ZEBRA_ROUTE_BGP + && (new_select->sub_type == BGP_ROUTE_IMPORTED || + bgp_evpn_attr_is_sync(new_select->attr))) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) + ret = evpn_zebra_install(bgp, vpn, + (const struct prefix_evpn *) + bgp_dest_get_prefix( + dest), + new_select); + else + bgp_zebra_route_install(dest, new_select, bgp, true, + vpn, false); + + /* If an old best existed and it was a "local" route, the only + * reason + * it would be supplanted is due to MAC mobility procedures. So, + * we + * need to do an implicit delete and withdraw that route from + * peers. + */ + if (new_select->sub_type == BGP_ROUTE_IMPORTED && + old_select && old_select->peer == bgp->peer_self + && old_select->type == ZEBRA_ROUTE_BGP + && old_select->sub_type == BGP_ROUTE_STATIC + && vpn) + evpn_delete_old_local_route(bgp, vpn, dest, + old_select, new_select); + } else { + if (old_select && old_select->type == ZEBRA_ROUTE_BGP && + old_select->sub_type == BGP_ROUTE_IMPORTED) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || + CHECK_FLAG(bgp->flags, BGP_FLAG_VNI_DOWN)) + ret = evpn_zebra_uninstall(bgp, vpn, + (const struct prefix_evpn + *) + bgp_dest_get_prefix( + dest), + old_select, false); + else + bgp_zebra_route_install(dest, old_select, bgp, + false, vpn, false); + } + } + + /* Clear any route change flags. */ + bgp_zebra_clear_route_change_flags(dest); + + /* Reap old select bgp_path_info, if it has been removed */ + if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) + bgp_path_info_reap(dest, old_select); + + return ret; +} + +static struct bgp_path_info *bgp_evpn_route_get_local_path( + struct bgp *bgp, struct bgp_dest *dest) +{ + struct bgp_path_info *tmp_pi; + struct bgp_path_info *local_pi = NULL; + + for (tmp_pi = bgp_dest_get_bgp_path_info(dest); tmp_pi; + tmp_pi = tmp_pi->next) { + if (bgp_evpn_is_path_local(bgp, tmp_pi)) { + local_pi = tmp_pi; + break; + } + } + + return local_pi; +} + +static int update_evpn_type5_route_entry(struct bgp *bgp_evpn, + struct bgp *bgp_vrf, afi_t afi, + safi_t safi, struct bgp_dest *dest, + struct attr *attr, int *route_changed, + struct bgp_path_info **entry) +{ + struct attr *attr_new = NULL; + struct bgp_path_info *pi = NULL; + struct bgp_labels bgp_labels = {}; + struct bgp_path_info *local_pi = NULL; + struct bgp_path_info *tmp_pi = NULL; + + *route_changed = 0; + + /* See if this is an update of an existing route, or a new add. */ + local_pi = bgp_evpn_route_get_local_path(bgp_evpn, dest); + + /* + * create a new route entry if one doesn't exist. + * Otherwise see if route attr has changed + */ + if (!local_pi) { + + /* route has changed as this is the first entry */ + *route_changed = 1; + + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(attr); + + /* create the route info from attribute */ + pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, + bgp_evpn->peer_self, attr_new, dest); + SET_FLAG(pi->flags, BGP_PATH_VALID); + + /* Type-5 routes advertise the L3-VNI */ + bgp_path_info_extra_get(pi); + vni2label(bgp_vrf->l3vni, &bgp_labels.label[0]); + bgp_labels.num_labels = 1; + if (!bgp_path_info_labels_same(pi, &bgp_labels.label[0], + bgp_labels.num_labels)) { + bgp_labels_unintern(&pi->extra->labels); + pi->extra->labels = bgp_labels_intern(&bgp_labels); + } + + + /* add the route entry to route node*/ + bgp_path_info_add(dest, pi); + *entry = pi; + } else { + tmp_pi = local_pi; + if (!attrhash_cmp(tmp_pi->attr, attr)) { + + /* attribute changed */ + *route_changed = 1; + + /* The attribute has changed. */ + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(attr); + bgp_path_info_set_flag(dest, tmp_pi, + BGP_PATH_ATTR_CHANGED); + + /* Restore route, if needed. */ + if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, tmp_pi); + + /* Unintern existing, set to new. */ + bgp_attr_unintern(&tmp_pi->attr); + tmp_pi->attr = attr_new; + tmp_pi->uptime = monotime(NULL); + } + *entry = local_pi; + } + return 0; +} + +/* update evpn type-5 route entry */ +static int update_evpn_type5_route(struct bgp *bgp_vrf, struct prefix_evpn *evp, + struct attr *src_attr, afi_t src_afi, + safi_t src_safi) +{ + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct attr attr; + struct bgp_dest *dest = NULL; + struct bgp *bgp_evpn = NULL; + int route_changed = 0; + struct bgp_path_info *pi = NULL; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return 0; + + /* Build path attribute for this route - use the source attr, if + * present, else treat as locally originated. + */ + if (src_attr) + attr = *src_attr; + else { + memset(&attr, 0, sizeof(attr)); + bgp_attr_default_set(&attr, bgp_vrf, BGP_ORIGIN_IGP); + } + + /* Advertise Primary IP (PIP) is enabled, send individual + * IP (default instance router-id) as nexthop. + * PIP is disabled or vrr interface is not present + * use anycast-IP as nexthop and anycast RMAC. + */ + if (!bgp_vrf->evpn_info->advertise_pip || + (!bgp_vrf->evpn_info->is_anycast_mac)) { + attr.nexthop = bgp_vrf->originator_ip; + attr.mp_nexthop_global_in = bgp_vrf->originator_ip; + memcpy(&attr.rmac, &bgp_vrf->rmac, ETH_ALEN); + } else { + /* copy sys rmac */ + memcpy(&attr.rmac, &bgp_vrf->evpn_info->pip_rmac, ETH_ALEN); + if (bgp_vrf->evpn_info->pip_ip.s_addr != INADDR_ANY) { + attr.nexthop = bgp_vrf->evpn_info->pip_ip; + attr.mp_nexthop_global_in = bgp_vrf->evpn_info->pip_ip; + } else if (bgp_vrf->evpn_info->pip_ip.s_addr == INADDR_ANY) + if (bgp_debug_zebra(NULL)) + zlog_debug( + "VRF %s evp %pFX advertise-pip primary ip is not configured", + vrf_id_to_name(bgp_vrf->vrf_id), evp); + } + + if (bgp_debug_zebra(NULL)) + zlog_debug( + "VRF %s type-5 route evp %pFX RMAC %pEA nexthop %pI4", + vrf_id_to_name(bgp_vrf->vrf_id), evp, &attr.rmac, + &attr.nexthop); + + frrtrace(4, frr_bgp, evpn_advertise_type5, bgp_vrf->vrf_id, evp, + &attr.rmac, attr.nexthop); + + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + if (src_afi == AFI_IP6 && + CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP)) { + if (src_attr && + !IN6_IS_ADDR_UNSPECIFIED(&src_attr->mp_nexthop_global)) { + attr.evpn_overlay.type = OVERLAY_INDEX_GATEWAY_IP; + SET_IPADDR_V6(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v6, + &src_attr->mp_nexthop_global, + sizeof(struct in6_addr)); + } + } else if (src_afi == AFI_IP && + CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP)) { + if (src_attr && src_attr->nexthop.s_addr != 0) { + attr.evpn_overlay.type = OVERLAY_INDEX_GATEWAY_IP; + SET_IPADDR_V4(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v4, + &src_attr->nexthop, sizeof(struct in_addr)); + } + } + + /* Setup RT and encap extended community */ + build_evpn_type5_route_extcomm(bgp_vrf, &attr); + + /* get the route node in global table */ + dest = bgp_evpn_global_node_get(bgp_evpn->rib[afi][safi], afi, safi, + evp, &bgp_vrf->vrf_prd, NULL); + assert(dest); + + /* create or update the route entry within the route node */ + update_evpn_type5_route_entry(bgp_evpn, bgp_vrf, afi, safi, dest, &attr, + &route_changed, &pi); + + /* schedule for processing and unlock node */ + if (route_changed) { + bgp_process(bgp_evpn, dest, pi, afi, safi); + bgp_dest_unlock_node(dest); + } + + /* uninten temporary */ + if (!src_attr) + aspath_unintern(&attr.aspath); + return 0; +} + +static void bgp_evpn_get_sync_info(struct bgp *bgp, esi_t *esi, + struct bgp_dest *dest, uint32_t loc_seq, + uint32_t *max_sync_seq, bool *active_on_peer, + bool *peer_router, bool *proxy_from_peer, + const struct ethaddr *mac) +{ + struct bgp_path_info *tmp_pi; + struct bgp_path_info *second_best_path = NULL; + uint32_t tmp_mm_seq = 0; + esi_t *tmp_esi; + int paths_eq; + struct ethaddr *tmp_mac; + bool mac_cmp = false; + struct prefix_evpn *evp = (struct prefix_evpn *)&dest->rn->p; + + + /* mac comparison is not needed for MAC-only routes */ + if (mac && !is_evpn_prefix_ipaddr_none(evp)) + mac_cmp = true; + + /* find the best non-local path. a local path can only be present + * as best path + */ + for (tmp_pi = bgp_dest_get_bgp_path_info(dest); tmp_pi; + tmp_pi = tmp_pi->next) { + if (tmp_pi->sub_type != BGP_ROUTE_IMPORTED || + !CHECK_FLAG(tmp_pi->flags, BGP_PATH_VALID)) + continue; + + /* ignore paths that have a different mac */ + if (mac_cmp) { + tmp_mac = evpn_type2_path_info_get_mac(tmp_pi); + if (memcmp(mac, tmp_mac, sizeof(*mac))) + continue; + } + + if (bgp_evpn_path_info_cmp(bgp, tmp_pi, second_best_path, + &paths_eq, false)) + second_best_path = tmp_pi; + } + + if (!second_best_path) + return; + + tmp_esi = bgp_evpn_attr_get_esi(second_best_path->attr); + /* if this has the same ES desination as the local path + * it is a sync path + */ + if (!memcmp(esi, tmp_esi, sizeof(esi_t))) { + tmp_mm_seq = mac_mobility_seqnum(second_best_path->attr); + if (tmp_mm_seq < loc_seq) + return; + + /* we have a non-proxy path from the ES peer. */ + if (second_best_path->attr->es_flags & + ATTR_ES_PROXY_ADVERT) { + *proxy_from_peer = true; + } else { + *active_on_peer = true; + } + + if (second_best_path->attr->router_flag) + *peer_router = true; + + /* we use both proxy and non-proxy imports to + * determine the max sync sequence + */ + if (tmp_mm_seq > *max_sync_seq) + *max_sync_seq = tmp_mm_seq; + } +} + +/* Bubble up sync-info from all paths (non-best) to the local-path. + * This is need for MM sequence number syncing and proxy advertisement. + * Note: The local path can only exist as a best path in the + * VPN route table. It will take precedence over all sync paths. + */ +static void update_evpn_route_entry_sync_info(struct bgp *bgp, + struct bgp_dest *dest, + struct attr *attr, + uint32_t loc_seq, bool setup_sync, + const struct ethaddr *mac) +{ + esi_t *esi; + struct prefix_evpn *evp = + (struct prefix_evpn *)bgp_dest_get_prefix(dest); + + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return; + + esi = bgp_evpn_attr_get_esi(attr); + if (bgp_evpn_is_esi_valid(esi)) { + if (setup_sync) { + uint32_t max_sync_seq = 0; + bool active_on_peer = false; + bool peer_router = false; + bool proxy_from_peer = false; + + bgp_evpn_get_sync_info(bgp, esi, dest, loc_seq, + &max_sync_seq, &active_on_peer, + &peer_router, &proxy_from_peer, + mac); + attr->mm_sync_seqnum = max_sync_seq; + if (active_on_peer) + attr->es_flags |= ATTR_ES_PEER_ACTIVE; + else + attr->es_flags &= ~ATTR_ES_PEER_ACTIVE; + if (proxy_from_peer) + attr->es_flags |= ATTR_ES_PEER_PROXY; + else + attr->es_flags &= ~ATTR_ES_PEER_PROXY; + if (peer_router) + attr->es_flags |= ATTR_ES_PEER_ROUTER; + else + attr->es_flags &= ~ATTR_ES_PEER_ROUTER; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) { + char esi_buf[ESI_STR_LEN]; + + zlog_debug( + "setup sync info for %pFX es %s max_seq %d %s%s%s", + evp, + esi_to_str(esi, esi_buf, + sizeof(esi_buf)), + max_sync_seq, + (attr->es_flags & ATTR_ES_PEER_ACTIVE) + ? "peer-active " + : "", + (attr->es_flags & ATTR_ES_PEER_PROXY) + ? "peer-proxy " + : "", + (attr->es_flags & ATTR_ES_PEER_ROUTER) + ? "peer-router " + : ""); + } + } + } else { + attr->mm_sync_seqnum = 0; + attr->es_flags &= ~ATTR_ES_PEER_ACTIVE; + attr->es_flags &= ~ATTR_ES_PEER_PROXY; + } +} + +/* + * Create or update EVPN route entry. This could be in the VNI route tables + * or the global route table. + */ +static int update_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, + afi_t afi, safi_t safi, + struct bgp_dest *dest, struct attr *attr, + const struct ethaddr *mac, + const struct ipaddr *ip, int add, + struct bgp_path_info **pi, uint8_t flags, + uint32_t seq, bool vpn_rt, bool *old_is_sync) +{ + struct bgp_path_info *tmp_pi; + struct bgp_path_info *local_pi; + struct attr *attr_new; + struct attr local_attr; + struct bgp_labels bgp_labels = {}; + int route_change = 1; + uint8_t sticky = 0; + const struct prefix_evpn *evp; + + *pi = NULL; + evp = (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + /* See if this is an update of an existing route, or a new add. */ + local_pi = bgp_evpn_route_get_local_path(bgp, dest); + + /* If route doesn't exist already, create a new one, if told to. + * Otherwise act based on whether the attributes of the route have + * changed or not. + */ + if (!local_pi && !add) + return 0; + + if (old_is_sync && local_pi) + *old_is_sync = bgp_evpn_attr_is_sync(local_pi->attr); + + /* if a local path is being added with a non-zero esi look + * for SYNC paths from ES peers and bubble up the sync-info + */ + update_evpn_route_entry_sync_info(bgp, dest, attr, seq, vpn_rt, mac); + + /* For non-GW MACs, update MAC mobility seq number, if needed. */ + if (seq && !CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW)) + add_mac_mobility_to_attr(seq, attr); + + if (!local_pi) { + local_attr = *attr; + + /* Extract MAC mobility sequence number, if any. */ + local_attr.mm_seqnum = + bgp_attr_mac_mobility_seqnum(&local_attr, &sticky); + local_attr.sticky = sticky; + + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(&local_attr); + + /* Create new route with its attribute. */ + tmp_pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, + bgp->peer_self, attr_new, dest); + SET_FLAG(tmp_pi->flags, BGP_PATH_VALID); + bgp_path_info_extra_get(tmp_pi); + + /* The VNI goes into the 'label' field of the route */ + vni2label(vpn->vni, &bgp_labels.label[0]); + bgp_labels.num_labels = 1; + + /* Type-2 routes may carry a second VNI - the L3-VNI. + * Only attach second label if we are advertising two labels for + * type-2 routes. + */ + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS)) { + vni_t l3vni; + + l3vni = bgpevpn_get_l3vni(vpn); + if (l3vni) { + vni2label(l3vni, &bgp_labels.label[1]); + bgp_labels.num_labels++; + } + } + + if (!bgp_path_info_labels_same(tmp_pi, &bgp_labels.label[0], + bgp_labels.num_labels)) { + bgp_labels_unintern(&tmp_pi->extra->labels); + tmp_pi->extra->labels = bgp_labels_intern(&bgp_labels); + } + + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + if (mac) + evpn_type2_path_info_set_mac(tmp_pi, *mac); + else if (ip) + evpn_type2_path_info_set_ip(tmp_pi, *ip); + } + + /* Mark route as self type-2 route */ + if (flags && CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SVI_IP)) + tmp_pi->extra->evpn->af_flags = + BGP_EVPN_MACIP_TYPE_SVI_IP; + bgp_path_info_add(dest, tmp_pi); + } else { + tmp_pi = local_pi; + if (attrhash_cmp(tmp_pi->attr, attr) + && !CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) + route_change = 0; + else { + /* + * The attributes have changed, type-2 routes needs to + * be advertised with right labels. + */ + vni2label(vpn->vni, &bgp_labels.label[0]); + bgp_labels.num_labels = 1; + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + && CHECK_FLAG(vpn->flags, + VNI_FLAG_USE_TWO_LABELS)) { + vni_t l3vni; + + l3vni = bgpevpn_get_l3vni(vpn); + if (l3vni) { + vni2label(l3vni, &bgp_labels.label[1]); + bgp_labels.num_labels++; + } + } + if (!bgp_path_info_labels_same(tmp_pi, + &bgp_labels.label[0], + bgp_labels.num_labels)) { + bgp_labels_unintern(&tmp_pi->extra->labels); + tmp_pi->extra->labels = + bgp_labels_intern(&bgp_labels); + } + + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + if (mac) + evpn_type2_path_info_set_mac(tmp_pi, + *mac); + else if (ip) + evpn_type2_path_info_set_ip(tmp_pi, + *ip); + } + + /* The attribute has changed. */ + /* Add (or update) attribute to hash. */ + local_attr = *attr; + bgp_path_info_set_flag(dest, tmp_pi, + BGP_PATH_ATTR_CHANGED); + + /* Extract MAC mobility sequence number, if any. */ + local_attr.mm_seqnum = bgp_attr_mac_mobility_seqnum( + &local_attr, &sticky); + local_attr.sticky = sticky; + + attr_new = bgp_attr_intern(&local_attr); + + /* Restore route, if needed. */ + if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, tmp_pi); + + /* Unintern existing, set to new. */ + bgp_attr_unintern(&tmp_pi->attr); + tmp_pi->attr = attr_new; + tmp_pi->uptime = monotime(NULL); + } + } + + /* local MAC-IP routes in the VNI table are linked to + * the destination ES + */ + if (route_change && vpn_rt + && (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE)) + bgp_evpn_path_es_link(tmp_pi, vpn->vni, + bgp_evpn_attr_get_esi(tmp_pi->attr)); + + /* Return back the route entry. */ + *pi = tmp_pi; + return route_change; +} + +static void evpn_zebra_reinstall_best_route(struct bgp *bgp, + struct bgpevpn *vpn, + struct bgp_dest *dest) +{ + struct bgp_path_info *tmp_ri; + struct bgp_path_info *curr_select = NULL; + + for (tmp_ri = bgp_dest_get_bgp_path_info(dest); tmp_ri; + tmp_ri = tmp_ri->next) { + if (CHECK_FLAG(tmp_ri->flags, BGP_PATH_SELECTED)) { + curr_select = tmp_ri; + break; + } + } + + if (curr_select && curr_select->type == ZEBRA_ROUTE_BGP + && (curr_select->sub_type == BGP_ROUTE_IMPORTED || + bgp_evpn_attr_is_sync(curr_select->attr))) + if (curr_select && curr_select->type == ZEBRA_ROUTE_BGP && + (curr_select->sub_type == BGP_ROUTE_IMPORTED || + bgp_evpn_attr_is_sync(curr_select->attr))) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) + evpn_zebra_install(bgp, vpn, + (const struct prefix_evpn *) + bgp_dest_get_prefix( + dest), + curr_select); + else + bgp_zebra_route_install(dest, curr_select, bgp, + true, vpn, false); + } +} + +/* + * If the local route was not selected evict it and tell zebra to re-add + * the best remote dest. + * + * Typically a local path added by zebra is expected to be selected as + * best. In which case when a remote path wins as best (later) + * evpn_route_select_install itself evicts the older-local-best path. + * + * However if bgp's add and zebra's add cross paths (race condition) it + * is possible that the local path is no longer the "older" best path. + * It is a path that was never designated as best and hence requires + * additional handling to prevent bgp from injecting and holding on to a + * non-best local path. + */ +static struct bgp_dest * +evpn_cleanup_local_non_best_route(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest, + struct bgp_path_info *local_pi) +{ + /* local path was not picked as the winner; kick it out */ + if (bgp_debug_zebra(NULL)) + zlog_debug("evicting local evpn prefix %pBD as remote won", + dest); + + evpn_delete_old_local_route(bgp, vpn, dest, local_pi, NULL); + + /* tell zebra to re-add the best remote path */ + evpn_zebra_reinstall_best_route(bgp, vpn, dest); + + return bgp_path_info_reap(dest, local_pi); +} + +static inline bool bgp_evpn_route_add_l3_ecomm_ok(struct bgpevpn *vpn, + const struct prefix_evpn *p, + esi_t *esi) +{ + return p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + && (is_evpn_prefix_ipaddr_v4(p) + || (is_evpn_prefix_ipaddr_v6(p) + && !IN6_IS_ADDR_LINKLOCAL( + &p->prefix.macip_addr.ip.ipaddr_v6))) + && CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS) + && bgpevpn_get_l3vni(vpn) && bgp_evpn_es_add_l3_ecomm_ok(esi); +} + +/* + * Create or update EVPN route (of type based on prefix) for specified VNI + * and schedule for processing. + */ +static int update_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, + struct prefix_evpn *p, uint8_t flags, + uint32_t seq, esi_t *esi) +{ + struct bgp_dest *dest; + struct attr attr; + struct attr *attr_new; + int add_l3_ecomm = 0; + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + int route_change; + bool old_is_sync = false; + bool mac_only = false; + struct ecommunity *macvrf_soo = NULL; + + memset(&attr, 0, sizeof(attr)); + + /* Build path-attribute for this route. */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + attr.nexthop = vpn->originator_ip; + attr.mp_nexthop_global_in = vpn->originator_ip; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.sticky = CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) ? 1 : 0; + attr.default_gw = CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW) ? 1 : 0; + attr.router_flag = CHECK_FLAG(flags, + ZEBRA_MACIP_TYPE_ROUTER_FLAG) ? 1 : 0; + if (CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_PROXY_ADVERT)) + attr.es_flags |= ATTR_ES_PROXY_ADVERT; + + if (esi && bgp_evpn_is_esi_valid(esi)) { + memcpy(&attr.esi, esi, sizeof(esi_t)); + attr.es_flags |= ATTR_ES_IS_LOCAL; + } + + /* PMSI is only needed for type-3 routes */ + if (p->prefix.route_type == BGP_EVPN_IMET_ROUTE) { + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL); + bgp_attr_set_pmsi_tnl_type(&attr, PMSI_TNLTYPE_INGR_REPL); + } + + /* router mac is only needed for type-2 routes here. */ + if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + uint8_t af_flags = 0; + + if (CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SVI_IP)) + SET_FLAG(af_flags, BGP_EVPN_MACIP_TYPE_SVI_IP); + + bgp_evpn_get_rmac_nexthop(vpn, p, &attr, af_flags); + } + + if (bgp_debug_zebra(NULL)) { + char buf3[ESI_STR_LEN]; + + zlog_debug( + "VRF %s vni %u type-%u route evp %pFX RMAC %pEA nexthop %pI4 esi %s", + vpn->bgp_vrf ? vrf_id_to_name(vpn->bgp_vrf->vrf_id) + : "None", + vpn->vni, p->prefix.route_type, p, &attr.rmac, + &attr.mp_nexthop_global_in, + esi_to_str(esi, buf3, sizeof(buf3))); + } + + vni2label(vpn->vni, &(attr.label)); + + /* Include L3 VNI related RTs and RMAC for type-2 routes, if they're + * IPv4 or IPv6 global addresses and we're advertising L3VNI with + * these routes. + */ + add_l3_ecomm = bgp_evpn_route_add_l3_ecomm_ok( + vpn, p, (attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL); + + if (bgp->evpn_info) + macvrf_soo = bgp->evpn_info->soo; + + /* Set up extended community. */ + build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo); + + /* First, create (or fetch) route node within the VNI. + * NOTE: There is no RD here. + */ + dest = bgp_evpn_vni_node_get(vpn, p, NULL); + + if ((p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && + (is_evpn_prefix_ipaddr_none(p) == true)) + mac_only = true; + + /* Create or update route entry. */ + route_change = update_evpn_route_entry( + bgp, vpn, afi, safi, dest, &attr, + (mac_only ? NULL : &p->prefix.macip_addr.mac), NULL /* ip */, 1, + &pi, flags, seq, true /* setup_sync */, &old_is_sync); + assert(pi); + attr_new = pi->attr; + + /* lock ri to prevent freeing in evpn_route_select_install */ + bgp_path_info_lock(pi); + + /* Perform route selection. Normally, the local route in the + * VNI is expected to win and be the best route. However, if + * there is a race condition where a host moved from local to + * remote and the remote route was received in BGP just prior + * to the local MACIP notification from zebra, the remote + * route would win, and we should evict the defunct local route + * and (re)install the remote route into zebra. + */ + evpn_route_select_install(bgp, vpn, dest, pi); + /* + * If the new local route was not selected evict it and tell zebra + * to re-add the best remote dest. BGP doesn't retain non-best local + * routes. + */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + route_change = 0; + } else { + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + route_change = 0; + dest = evpn_cleanup_local_non_best_route(bgp, vpn, dest, + pi); + } else { + bool new_is_sync; + + /* If the local path already existed and is still the + * best path we need to also check if it transitioned + * from being a sync path to a non-sync path. If it + * it did we need to notify zebra that the sync-path + * has been removed. + */ + new_is_sync = bgp_evpn_attr_is_sync(pi->attr); + if (!new_is_sync && old_is_sync) { + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_DELETE_IN_PROGRESS)) + evpn_zebra_uninstall(bgp, vpn, p, pi, + true); + else + bgp_zebra_route_install(dest, pi, bgp, + false, vpn, + true); + } + } + } + bgp_path_info_unlock(pi); + + if (dest) + bgp_dest_unlock_node(dest); + + /* If this is a new route or some attribute has changed, export the + * route to the global table. The route will be advertised to peers + * from there. Note that this table is a 2-level tree (RD-level + + * Prefix-level) similar to L3VPN routes. + */ + if (route_change) { + struct bgp_path_info *global_pi; + + dest = bgp_evpn_global_node_get(bgp->rib[afi][safi], afi, safi, + p, &vpn->prd, NULL); + update_evpn_route_entry( + bgp, vpn, afi, safi, dest, attr_new, NULL /* mac */, + NULL /* ip */, 1, &global_pi, flags, seq, + false /* setup_sync */, NULL /* old_is_sync */); + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, dest, global_pi, afi, safi); + bgp_dest_unlock_node(dest); + } + + /* Unintern temporary. */ + aspath_unintern(&attr.aspath); + + return 0; +} + +/* + * Delete EVPN route entry. + * The entry can be in ESI/VNI table or the global table. + */ +void delete_evpn_route_entry(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *dest, + struct bgp_path_info **pi) +{ + struct bgp_path_info *tmp_pi; + + *pi = NULL; + + /* Now, find matching route. */ + for (tmp_pi = bgp_dest_get_bgp_path_info(dest); tmp_pi; + tmp_pi = tmp_pi->next) + if (tmp_pi->peer == bgp->peer_self + && tmp_pi->type == ZEBRA_ROUTE_BGP + && tmp_pi->sub_type == BGP_ROUTE_STATIC) + break; + + *pi = tmp_pi; + + /* Mark route for delete. */ + if (tmp_pi) + bgp_path_info_delete(dest, tmp_pi); +} + +/* Delete EVPN type5 route */ +static int delete_evpn_type5_route(struct bgp *bgp_vrf, struct prefix_evpn *evp) +{ + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi = NULL; + struct bgp *bgp_evpn = NULL; /* evpn bgp instance */ + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return 0; + + /* locate the global route entry for this type-5 prefix */ + dest = bgp_evpn_global_node_lookup(bgp_evpn->rib[afi][safi], safi, evp, + &bgp_vrf->vrf_prd, NULL); + if (!dest) + return 0; + + frrtrace(2, frr_bgp, evpn_withdraw_type5, bgp_vrf->vrf_id, evp); + + delete_evpn_route_entry(bgp_evpn, afi, safi, dest, &pi); + if (pi) + bgp_process(bgp_evpn, dest, pi, afi, safi); + bgp_dest_unlock_node(dest); + return 0; +} + +/* + * Delete EVPN route (of type based on prefix) for specified VNI and + * schedule for processing. + */ +static int delete_evpn_route(struct bgp *bgp, struct bgpevpn *vpn, + struct prefix_evpn *p) +{ + struct bgp_dest *dest, *global_dest; + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + /* First, locate the route node within the VNI. If it doesn't exist, + * there + * is nothing further to do. + * NOTE: There is no RD here. + */ + dest = bgp_evpn_vni_node_lookup(vpn, p, NULL); + if (!dest) + return 0; + + /* Next, locate route node in the global EVPN routing table. Note that + * this table is a 2-level tree (RD-level + Prefix-level) similar to + * L3VPN routes. + */ + global_dest = bgp_evpn_global_node_lookup(bgp->rib[afi][safi], safi, p, + &vpn->prd, NULL); + if (global_dest) { + /* Delete route entry in the global EVPN table. */ + delete_evpn_route_entry(bgp, afi, safi, global_dest, &pi); + + /* Schedule for processing - withdraws to peers happen from + * this table. + */ + if (pi) + bgp_process(bgp, global_dest, pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + /* Delete route entry in the VNI route table. This can just be removed. + */ + delete_evpn_route_entry(bgp, afi, safi, dest, &pi); + if (pi) { + bgp_path_info_delete(dest, pi); + evpn_route_select_install(bgp, vpn, dest, pi); + } + + /* dest should still exist due to locking make coverity happy */ + assert(dest); + bgp_dest_unlock_node(dest); + + return 0; +} + +void bgp_evpn_update_type2_route_entry(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest, + struct bgp_path_info *local_pi, + const char *caller) +{ + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp_path_info *pi; + struct attr attr; + struct attr *attr_new; + uint32_t seq; + int add_l3_ecomm = 0; + struct bgp_dest *global_dest; + struct bgp_path_info *global_pi; + struct prefix_evpn evp; + int route_change; + bool old_is_sync = false; + struct ecommunity *macvrf_soo = NULL; + + if (CHECK_FLAG(local_pi->flags, BGP_PATH_REMOVED)) + return; + + /* + * VNI table MAC-IP prefixes don't have MAC so make sure it's set from + * path info here. + */ + if (is_evpn_prefix_ipaddr_none((struct prefix_evpn *)&dest->rn->p)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy( + &evp, (struct prefix_evpn *)&dest->rn->p, NULL /* mac */, + evpn_type2_path_info_get_ip(local_pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy( + &evp, (struct prefix_evpn *)&dest->rn->p, + evpn_type2_path_info_get_mac(local_pi), NULL /* ip */); + } + + /* + * Build attribute per local route as the MAC mobility and + * some other values could differ for different routes. The + * attributes will be shared in the hash table. + */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + attr.nexthop = vpn->originator_ip; + attr.mp_nexthop_global_in = vpn->originator_ip; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.sticky = (local_pi->attr->sticky) ? 1 : 0; + attr.router_flag = (local_pi->attr->router_flag) ? 1 : 0; + attr.es_flags = local_pi->attr->es_flags; + if (local_pi->attr->default_gw) { + attr.default_gw = 1; + if (is_evpn_prefix_ipaddr_v6(&evp)) + attr.router_flag = 1; + } + memcpy(&attr.esi, &local_pi->attr->esi, sizeof(esi_t)); + bgp_evpn_get_rmac_nexthop(vpn, &evp, &attr, + local_pi->extra->evpn->af_flags); + vni2label(vpn->vni, &(attr.label)); + /* Add L3 VNI RTs and RMAC for non IPv6 link-local if + * using L3 VNI for type-2 routes also. + */ + add_l3_ecomm = bgp_evpn_route_add_l3_ecomm_ok( + vpn, &evp, + (attr.es_flags & ATTR_ES_IS_LOCAL) ? &attr.esi : NULL); + + if (bgp->evpn_info) + macvrf_soo = bgp->evpn_info->soo; + + /* Set up extended community. */ + build_evpn_route_extcomm(vpn, &attr, add_l3_ecomm, macvrf_soo); + seq = mac_mobility_seqnum(local_pi->attr); + + if (bgp_debug_zebra(NULL)) { + char buf3[ESI_STR_LEN]; + + zlog_debug( + "VRF %s vni %u evp %pFX RMAC %pEA nexthop %pI4 esi %s esf 0x%x from %s", + vpn->bgp_vrf ? vrf_id_to_name(vpn->bgp_vrf->vrf_id) + : " ", + vpn->vni, &evp, &attr.rmac, &attr.mp_nexthop_global_in, + esi_to_str(&attr.esi, buf3, sizeof(buf3)), + attr.es_flags, caller); + } + + /* Update the route entry. */ + route_change = update_evpn_route_entry( + bgp, vpn, afi, safi, dest, &attr, NULL /* mac */, NULL /* ip */, + 0, &pi, 0, seq, true /* setup_sync */, &old_is_sync); + + assert(pi); + attr_new = pi->attr; + /* lock ri to prevent freeing in evpn_route_select_install */ + bgp_path_info_lock(pi); + + /* Perform route selection. Normally, the local route in the + * VNI is expected to win and be the best route. However, + * under peculiar situations (e.g., tunnel (next hop) IP change + * that causes best selection to be based on next hop), a + * remote route could win. If the local route is the best, + * ensure it is updated in the global EVPN route table and + * advertised to peers; otherwise, ensure it is evicted and + * (re)install the remote route into zebra. + */ + evpn_route_select_install(bgp, vpn, dest, pi); + + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + route_change = 0; + } else { + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + route_change = 0; + evpn_cleanup_local_non_best_route(bgp, vpn, dest, pi); + } else { + bool new_is_sync; + + /* If the local path already existed and is still the + * best path we need to also check if it transitioned + * from being a sync path to a non-sync path. If it + * it did we need to notify zebra that the sync-path + * has been removed. + */ + new_is_sync = bgp_evpn_attr_is_sync(pi->attr); + if (!new_is_sync && old_is_sync) { + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_DELETE_IN_PROGRESS)) + (void)evpn_zebra_uninstall(bgp, vpn, + &evp, pi, + true); + else + bgp_zebra_route_install(dest, pi, bgp, + false, vpn, + true); + } + } + } + + + /* unlock pi */ + bgp_path_info_unlock(pi); + + if (route_change) { + /* Update route in global routing table. */ + global_dest = bgp_evpn_global_node_get( + bgp->rib[afi][safi], afi, safi, &evp, &vpn->prd, NULL); + assert(global_dest); + update_evpn_route_entry( + bgp, vpn, afi, safi, global_dest, attr_new, + NULL /* mac */, NULL /* ip */, 0, &global_pi, 0, + mac_mobility_seqnum(attr_new), false /* setup_sync */, + NULL /* old_is_sync */); + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, global_dest, global_pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + /* Unintern temporary. */ + aspath_unintern(&attr.aspath); +} + +static void update_type2_route(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest) +{ + struct bgp_path_info *tmp_pi; + + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return; + + /* Identify local route. */ + for (tmp_pi = bgp_dest_get_bgp_path_info(dest); tmp_pi; + tmp_pi = tmp_pi->next) { + if (tmp_pi->peer == bgp->peer_self && + tmp_pi->type == ZEBRA_ROUTE_BGP && + tmp_pi->sub_type == BGP_ROUTE_STATIC) + break; + } + + if (!tmp_pi) + return; + + bgp_evpn_update_type2_route_entry(bgp, vpn, dest, tmp_pi, __func__); +} + +/* + * Update all type-2 (MACIP) local routes for this VNI - these should also + * be scheduled for advertise to peers. + */ +static void update_all_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct bgp_dest *dest; + + /* Walk this VNI's route MAC & IP table and update local type-2 + * routes. For any routes updated, update corresponding entry in the + * global table too. + */ + for (dest = bgp_table_top(vpn->mac_table); dest; + dest = bgp_route_next(dest)) + update_type2_route(bgp, vpn, dest); + + for (dest = bgp_table_top(vpn->ip_table); dest; + dest = bgp_route_next(dest)) + update_type2_route(bgp, vpn, dest); +} + +/* + * Delete all type-2 (MACIP) local routes for this VNI - only from the + * global routing table. These are also scheduled for withdraw from peers. + */ +static void delete_global_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rddest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + rddest = bgp_node_lookup(bgp->rib[afi][safi], + (struct prefix *)&vpn->prd); + if (rddest) { + table = bgp_dest_get_bgp_table_info(rddest); + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix( + dest); + + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + continue; + + delete_evpn_route_entry(bgp, afi, safi, dest, &pi); + if (pi) + bgp_process(bgp, dest, pi, afi, safi); + } + + /* Unlock RD node. */ + bgp_dest_unlock_node(rddest); + } +} + +static struct bgp_dest *delete_vni_type2_route(struct bgp *bgp, + struct bgp_dest *dest) +{ + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return dest; + + delete_evpn_route_entry(bgp, afi, safi, dest, &pi); + + /* Route entry in local table gets deleted immediately. */ + if (pi) + dest = bgp_path_info_reap(dest, pi); + + return dest; +} + +static void delete_vni_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct bgp_dest *dest; + + /* Next, walk this VNI's MAC & IP route table and delete local type-2 + * routes. + */ + for (dest = bgp_table_top(vpn->mac_table); dest; + dest = bgp_route_next(dest)) { + dest = delete_vni_type2_route(bgp, dest); + assert(dest); + } + + for (dest = bgp_table_top(vpn->ip_table); dest; + dest = bgp_route_next(dest)) { + dest = delete_vni_type2_route(bgp, dest); + assert(dest); + } +} + +/* + * Delete all type-2 (MACIP) local routes for this VNI - from the global + * table as well as the per-VNI route table. + */ +static void delete_all_type2_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + /* First, walk the global route table for this VNI's type-2 local + * routes. + * EVPN routes are a 2-level table, first get the RD table. + */ + delete_global_type2_routes(bgp, vpn); + delete_vni_type2_routes(bgp, vpn); +} + +/* + * Delete all routes in the per-VNI route table. + */ +static void delete_all_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi, *nextpi; + + /* Walk this VNI's MAC & IP route table and delete all routes. */ + for (dest = bgp_table_top(vpn->mac_table); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { + bgp_evpn_remote_ip_hash_del(vpn, pi); + bgp_path_info_delete(dest, pi); + dest = bgp_path_info_reap(dest, pi); + + assert(dest); + } + } + + for (dest = bgp_table_top(vpn->ip_table); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { + bgp_path_info_delete(dest, pi); + dest = bgp_path_info_reap(dest, pi); + + assert(dest); + } + } +} + +/* BUM traffic flood mode per-l2-vni */ +static int bgp_evpn_vni_flood_mode_get(struct bgp *bgp, + struct bgpevpn *vpn) +{ + /* if flooding has been globally disabled per-vni mode is + * not relevant + */ + if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) + return VXLAN_FLOOD_DISABLED; + + /* if mcast group ip has been specified we use a PIM-SM MDT */ + if (vpn->mcast_grp.s_addr != INADDR_ANY) + return VXLAN_FLOOD_PIM_SM; + + /* default is ingress replication */ + return VXLAN_FLOOD_HEAD_END_REPL; +} + +/* + * Update (and advertise) local routes for a VNI. Invoked upon the VNI + * export RT getting modified or change to tunnel IP. Note that these + * situations need the route in the per-VNI table as well as the global + * table to be updated (as attributes change). + */ +int update_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + int ret; + struct prefix_evpn p; + + update_type1_routes_for_evi(bgp, vpn); + + /* Update and advertise the type-3 route (only one) followed by the + * locally learnt type-2 routes (MACIP) - for this VNI. + * + * RT-3 only if doing head-end replication + */ + if (bgp_evpn_vni_flood_mode_get(bgp, vpn) + == VXLAN_FLOOD_HEAD_END_REPL) { + build_evpn_type3_prefix(&p, vpn->originator_ip); + ret = update_evpn_route(bgp, vpn, &p, 0, 0, NULL); + if (ret) + return ret; + } + + update_all_type2_routes(bgp, vpn); + return 0; +} + +/* Update Type-2/3 Routes for L2VNI. + * Called by hash_iterate() + */ +static void update_routes_for_vni_hash(struct hash_bucket *bucket, + struct bgp *bgp) +{ + struct bgpevpn *vpn; + + if (!bucket) + return; + + vpn = (struct bgpevpn *)bucket->data; + update_routes_for_vni(bgp, vpn); +} + +/* + * Delete (and withdraw) local routes for specified VNI from the global + * table and per-VNI table. After this, remove all other routes from + * the per-VNI table. Invoked upon the VNI being deleted or EVPN + * (advertise-all-vni) being disabled. + */ +static int delete_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + int ret; + struct prefix_evpn p; + + /* Delete and withdraw locally learnt type-2 routes (MACIP) + * followed by type-3 routes (only one) - for this VNI. + */ + delete_all_type2_routes(bgp, vpn); + + build_evpn_type3_prefix(&p, vpn->originator_ip); + + /* + * To handle the following scenario: + * - Say, the new zebra announce fifo list has few vni Evpn prefixes yet + * to be sent to zebra. + * - At this point if we have triggers like "no advertise-all-vni" or + * "networking restart", where a vni is going down. + * + * Perform the below + * 1) send withdraw routes to zebra immediately in case it is installed. + * 2) before we blow up the vni table, we need to walk the list and + * pop all the dest whose za_vpn points to this vni. + */ + SET_FLAG(bgp->flags, BGP_FLAG_VNI_DOWN); + ret = delete_evpn_route(bgp, vpn, &p); + UNSET_FLAG(bgp->flags, BGP_FLAG_VNI_DOWN); + if (ret) + return ret; + + /* Delete all routes from the per-VNI table. */ + delete_all_vni_routes(bgp, vpn); + return 0; +} + +/* + * There is a flood mcast IP address change. Update the mcast-grp and + * remove the type-3 route if any. A new type-3 route will be generated + * post tunnel_ip update if the new flood mode is head-end-replication. + */ +static int bgp_evpn_mcast_grp_change(struct bgp *bgp, struct bgpevpn *vpn, + struct in_addr mcast_grp) +{ + struct prefix_evpn p; + + vpn->mcast_grp = mcast_grp; + + if (is_vni_live(vpn)) { + build_evpn_type3_prefix(&p, vpn->originator_ip); + delete_evpn_route(bgp, vpn, &p); + } + + return 0; +} + +/* + * If there is a tunnel endpoint IP address (VTEP-IP) change for this VNI. + - Deletes tip_hash entry for old VTEP-IP + - Adds tip_hash entry/refcount for new VTEP-IP + - Deletes prior type-3 route for L2VNI (if needed) + - Updates originator_ip + * Note: Route re-advertisement happens elsewhere after other processing + * other changes. + */ +static void handle_tunnel_ip_change(struct bgp *bgp_vrf, struct bgp *bgp_evpn, + struct bgpevpn *vpn, + struct in_addr originator_ip) +{ + struct prefix_evpn p; + struct in_addr old_vtep_ip; + + if (bgp_vrf) /* L3VNI */ + old_vtep_ip = bgp_vrf->originator_ip; + else /* L2VNI */ + old_vtep_ip = vpn->originator_ip; + + /* TIP didn't change, nothing to do */ + if (IPV4_ADDR_SAME(&old_vtep_ip, &originator_ip)) + return; + + /* If L2VNI is not live, we only need to update the originator_ip. + * L3VNIs are updated immediately, so we can't bail out early. + */ + if (!bgp_vrf && !is_vni_live(vpn)) { + vpn->originator_ip = originator_ip; + return; + } + + /* Update the tunnel-ip hash */ + bgp_tip_del(bgp_evpn, &old_vtep_ip); + if (bgp_tip_add(bgp_evpn, &originator_ip)) + /* The originator_ip was not already present in the + * bgp martian next-hop table as a tunnel-ip, so we + * need to go back and filter routes matching the new + * martian next-hop. + */ + bgp_filter_evpn_routes_upon_martian_change(bgp_evpn, + BGP_MARTIAN_TUN_IP); + + if (!bgp_vrf) { + /* Need to withdraw type-3 route as the originator IP is part + * of the key. + */ + build_evpn_type3_prefix(&p, vpn->originator_ip); + delete_evpn_route(bgp_evpn, vpn, &p); + + vpn->originator_ip = originator_ip; + } else + bgp_vrf->originator_ip = originator_ip; + + return; +} + +static struct bgp_path_info * +bgp_create_evpn_bgp_path_info(struct bgp_path_info *parent_pi, + struct bgp_dest *dest, struct attr *attr) +{ + struct attr *attr_new; + struct bgp_path_info *pi; + + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(attr); + + /* Create new route with its attribute. */ + pi = info_make(parent_pi->type, BGP_ROUTE_IMPORTED, 0, parent_pi->peer, + attr_new, dest); + SET_FLAG(pi->flags, BGP_PATH_VALID); + bgp_path_info_extra_get(pi); + if (!pi->extra->vrfleak) + pi->extra->vrfleak = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK, + sizeof(struct bgp_path_info_extra_vrfleak)); + pi->extra->vrfleak->parent = bgp_path_info_lock(parent_pi); + bgp_dest_lock_node((struct bgp_dest *)parent_pi->net); + if (parent_pi->extra) + pi->extra->igpmetric = parent_pi->extra->igpmetric; + + if (BGP_PATH_INFO_NUM_LABELS(parent_pi)) + pi->extra->labels = bgp_labels_intern(parent_pi->extra->labels); + + bgp_path_info_add(dest, pi); + + return pi; +} + +/* + * Install route entry into the VRF routing table and invoke route selection. + */ +static int install_evpn_route_entry_in_vrf(struct bgp *bgp_vrf, + const struct prefix_evpn *evp, + struct bgp_path_info *parent_pi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct attr attr; + struct attr *attr_new; + int ret = 0; + struct prefix p; + struct prefix *pp = &p; + afi_t afi = 0; + safi_t safi = 0; + bool new_pi = false; + bool use_l3nhg = false; + bool is_l3nhg_active = false; + char buf1[INET6_ADDRSTRLEN]; + + memset(pp, 0, sizeof(struct prefix)); + ip_prefix_from_evpn_prefix(evp, pp); + + if (bgp_debug_zebra(NULL)) + zlog_debug( + "vrf %s: import evpn prefix %pFX parent %p flags 0x%x", + vrf_id_to_name(bgp_vrf->vrf_id), evp, parent_pi, + parent_pi->flags); + + if (bgp_vrf->vrf_id == VRF_UNKNOWN) + return -1; + + /* Create (or fetch) route within the VRF. */ + /* NOTE: There is no RD here. */ + if (is_evpn_prefix_ipaddr_v4(evp)) { + afi = AFI_IP; + safi = SAFI_UNICAST; + dest = bgp_node_get(bgp_vrf->rib[afi][safi], pp); + } else if (is_evpn_prefix_ipaddr_v6(evp)) { + afi = AFI_IP6; + safi = SAFI_UNICAST; + dest = bgp_node_get(bgp_vrf->rib[afi][safi], pp); + } else + return 0; + + /* EVPN routes currently only support a IPv4 next hop which corresponds + * to the remote VTEP. When importing into a VRF, if it is IPv6 host + * or prefix route, we have to convert the next hop to an IPv4-mapped + * address for the rest of the code to flow through. In the case of IPv4, + * make sure to set the flag for next hop attribute. + */ + attr = *parent_pi->attr; + if (attr.evpn_overlay.type != OVERLAY_INDEX_GATEWAY_IP) { + if (afi == AFI_IP6) + evpn_convert_nexthop_to_ipv6(&attr); + else { + attr.nexthop = attr.mp_nexthop_global_in; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + } + } else { + + /* + * If gateway IP overlay index is specified in the NLRI of + * EVPN RT-5, this gateway IP should be used as the nexthop + * for the prefix in the VRF + */ + if (bgp_debug_zebra(NULL)) { + zlog_debug( + "Install gateway IP %s as nexthop for prefix %pFX in vrf %s", + inet_ntop(pp->family, &attr.evpn_overlay.gw_ip, + buf1, sizeof(buf1)), pp, + vrf_id_to_name(bgp_vrf->vrf_id)); + } + + if (afi == AFI_IP6) { + memcpy(&attr.mp_nexthop_global, + &attr.evpn_overlay.gw_ip.ipaddr_v6, + sizeof(struct in6_addr)); + attr.mp_nexthop_len = IPV6_MAX_BYTELEN; + } else { + attr.nexthop = attr.evpn_overlay.gw_ip.ipaddr_v4; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + } + } + + bgp_evpn_es_vrf_use_nhg(bgp_vrf, &parent_pi->attr->esi, &use_l3nhg, + &is_l3nhg_active, NULL); + if (use_l3nhg) + attr.es_flags |= ATTR_ES_L3_NHG_USE; + if (is_l3nhg_active) + attr.es_flags |= ATTR_ES_L3_NHG_ACTIVE; + + /* Check if route entry is already present. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) { + pi = bgp_create_evpn_bgp_path_info(parent_pi, dest, &attr); + new_pi = true; + } else { + if (attrhash_cmp(pi->attr, &attr) + && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + bgp_dest_unlock_node(dest); + return 0; + } + /* The attribute has changed. */ + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(&attr); + + /* Restore route, if needed. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, pi); + + /* Mark if nexthop has changed. */ + if ((afi == AFI_IP + && !IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) + || (afi == AFI_IP6 + && !IPV6_ADDR_SAME(&pi->attr->mp_nexthop_global, + &attr_new->mp_nexthop_global))) + SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); + + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + /* Unintern existing, set to new. */ + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + pi->uptime = monotime(NULL); + } + + /* Gateway IP nexthop should be resolved */ + if (attr.evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) { + if (bgp_find_or_add_nexthop(bgp_vrf, bgp_vrf, afi, safi, pi, + NULL, 0, NULL)) + bgp_path_info_set_flag(dest, pi, BGP_PATH_VALID); + else { + if (BGP_DEBUG(nht, NHT)) { + inet_ntop(pp->family, + &attr.evpn_overlay.gw_ip, + buf1, sizeof(buf1)); + zlog_debug("%s: gateway IP NH unresolved", + buf1); + } + bgp_path_info_unset_flag(dest, pi, BGP_PATH_VALID); + } + } else { + + /* as it is an importation, change nexthop */ + bgp_path_info_set_flag(dest, pi, BGP_PATH_ANNC_NH_SELF); + } + + /* Link path to evpn nexthop */ + bgp_evpn_path_nh_add(bgp_vrf, pi); + + bgp_aggregate_increment(bgp_vrf, bgp_dest_get_prefix(dest), pi, afi, + safi); + + /* Perform route selection and update zebra, if required. */ + bgp_process(bgp_vrf, dest, pi, afi, safi); + + /* Process for route leaking. */ + vpn_leak_from_vrf_update(bgp_get_default(), bgp_vrf, pi); + + if (bgp_debug_zebra(NULL)) { + struct ipaddr nhip = {}; + + if (pi->net->rn->p.family == AF_INET6) { + SET_IPADDR_V6(&nhip); + IPV6_ADDR_COPY(&nhip.ipaddr_v6, &pi->attr->mp_nexthop_global); + } else { + SET_IPADDR_V4(&nhip); + IPV4_ADDR_COPY(&nhip.ipaddr_v4, &pi->attr->nexthop); + } + zlog_debug("... %s pi %s dest %p (l %d) pi %p (l %d, f 0x%x) nh %pIA", + new_pi ? "new" : "update", + bgp_vrf->name_pretty, dest, + bgp_dest_get_lock_count(dest), pi, pi->lock, + pi->flags, &nhip); + } + + bgp_dest_unlock_node(dest); + + return ret; +} + +/* + * Common handling for vni route tables install/selection. + */ +static int install_evpn_route_entry_in_vni_common( + struct bgp *bgp, struct bgpevpn *vpn, const struct prefix_evpn *p, + struct bgp_dest *dest, struct bgp_path_info *parent_pi) +{ + struct bgp_path_info *pi; + struct bgp_path_info *local_pi; + struct attr *attr_new; + int ret; + bool old_local_es = false; + bool new_local_es; + + /* Check if route entry is already present. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) { + /* Create an info */ + pi = bgp_create_evpn_bgp_path_info(parent_pi, dest, + parent_pi->attr); + + if (p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + if (is_evpn_type2_dest_ipaddr_none(dest)) + evpn_type2_path_info_set_ip( + pi, p->prefix.macip_addr.ip); + else + evpn_type2_path_info_set_mac( + pi, p->prefix.macip_addr.mac); + } + + new_local_es = bgp_evpn_attr_is_local_es(pi->attr); + } else { + /* Return early if attributes haven't changed + * and dest isn't flagged for removal. + * dest will be unlocked by either + * install_evpn_route_entry_in_vni_mac() or + * install_evpn_route_entry_in_vni_ip() + */ + if (attrhash_cmp(pi->attr, parent_pi->attr) && + !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + return 0; + /* The attribute has changed. */ + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(parent_pi->attr); + + /* Restore route, if needed. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, pi); + + /* Mark if nexthop has changed. */ + if (!IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) + SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); + + old_local_es = bgp_evpn_attr_is_local_es(pi->attr); + new_local_es = bgp_evpn_attr_is_local_es(attr_new); + /* If ESI is different or if its type has changed we + * need to reinstall the path in zebra + */ + if ((old_local_es != new_local_es) + || memcmp(&pi->attr->esi, &attr_new->esi, + sizeof(attr_new->esi))) { + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("VNI %d path %pFX chg to %s es", + vpn->vni, &pi->net->rn->p, + new_local_es ? "local" + : "non-local"); + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + } + + /* Unintern existing, set to new. */ + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + pi->uptime = monotime(NULL); + } + + /* Add this route to remote IP hashtable */ + bgp_evpn_remote_ip_hash_add(vpn, pi); + + /* Perform route selection and update zebra, if required. */ + ret = evpn_route_select_install(bgp, vpn, dest, pi); + + /* if the best path is a local path with a non-zero ES + * sync info against the local path may need to be updated + * when a remote path is added/updated (including changes + * from sync-path to remote-path) + */ + local_pi = bgp_evpn_route_get_local_path(bgp, dest); + if (local_pi && (old_local_es || new_local_es)) + bgp_evpn_update_type2_route_entry(bgp, vpn, dest, local_pi, + __func__); + + return ret; +} + +/* + * Common handling for vni route tables uninstall/selection. + */ +static int uninstall_evpn_route_entry_in_vni_common( + struct bgp *bgp, struct bgpevpn *vpn, const struct prefix_evpn *p, + struct bgp_dest *dest, struct bgp_path_info *parent_pi) +{ + struct bgp_path_info *pi; + struct bgp_path_info *local_pi; + int ret; + + /* Find matching route entry. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) + return 0; + + bgp_evpn_remote_ip_hash_del(vpn, pi); + + /* Mark entry for deletion */ + bgp_path_info_delete(dest, pi); + + /* Perform route selection and update zebra, if required. */ + ret = evpn_route_select_install(bgp, vpn, dest, pi); + + /* if the best path is a local path with a non-zero ES + * sync info against the local path may need to be updated + * when a remote path is deleted + */ + local_pi = bgp_evpn_route_get_local_path(bgp, dest); + if (local_pi && bgp_evpn_attr_is_local_es(local_pi->attr)) + bgp_evpn_update_type2_route_entry(bgp, vpn, dest, local_pi, + __func__); + + return ret; +} + +/* + * Install route entry into VNI IP table and invoke route selection. + */ +static int install_evpn_route_entry_in_vni_ip(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret; + struct bgp_dest *dest; + + /* Ignore MAC Only Type-2 */ + if ((p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && + (is_evpn_prefix_ipaddr_none(p) == true)) + return 0; + + /* Create (or fetch) route within the VNI IP table. */ + dest = bgp_evpn_vni_ip_node_get(vpn->ip_table, p, parent_pi); + + ret = install_evpn_route_entry_in_vni_common(bgp, vpn, p, dest, + parent_pi); + + bgp_dest_unlock_node(dest); + + return ret; +} + +/* + * Install route entry into VNI MAC table and invoke route selection. + */ +static int install_evpn_route_entry_in_vni_mac(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret; + struct bgp_dest *dest; + + /* Only type-2 routes go into this table */ + if (p->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return 0; + + /* Create (or fetch) route within the VNI MAC table. */ + dest = bgp_evpn_vni_mac_node_get(vpn->mac_table, p, parent_pi); + + ret = install_evpn_route_entry_in_vni_common(bgp, vpn, p, dest, + parent_pi); + + bgp_dest_unlock_node(dest); + + return ret; +} + +/* + * Uninstall route entry from VNI IP table and invoke route selection. + */ +static int uninstall_evpn_route_entry_in_vni_ip(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret; + struct bgp_dest *dest; + + /* Ignore MAC Only Type-2 */ + if ((p->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) && + (is_evpn_prefix_ipaddr_none(p) == true)) + return 0; + + /* Locate route within the VNI IP table. */ + dest = bgp_evpn_vni_ip_node_lookup(vpn->ip_table, p, parent_pi); + if (!dest) + return 0; + + ret = uninstall_evpn_route_entry_in_vni_common(bgp, vpn, p, dest, + parent_pi); + + bgp_dest_unlock_node(dest); + + return ret; +} + +/* + * Uninstall route entry from VNI IP table and invoke route selection. + */ +static int +uninstall_evpn_route_entry_in_vni_mac(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret; + struct bgp_dest *dest; + + /* Only type-2 routes go into this table */ + if (p->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return 0; + + /* Locate route within the VNI MAC table. */ + dest = bgp_evpn_vni_mac_node_lookup(vpn->mac_table, p, parent_pi); + if (!dest) + return 0; + + ret = uninstall_evpn_route_entry_in_vni_common(bgp, vpn, p, dest, + parent_pi); + + bgp_dest_unlock_node(dest); + + return ret; +} +/* + * Uninstall route entry from the VRF routing table and send message + * to zebra, if appropriate. + */ +static int uninstall_evpn_route_entry_in_vrf(struct bgp *bgp_vrf, + const struct prefix_evpn *evp, + struct bgp_path_info *parent_pi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + int ret = 0; + struct prefix p; + struct prefix *pp = &p; + afi_t afi = 0; + safi_t safi = 0; + + memset(pp, 0, sizeof(struct prefix)); + ip_prefix_from_evpn_prefix(evp, pp); + + if (bgp_debug_zebra(NULL)) + zlog_debug( + "vrf %s: unimport evpn prefix %pFX parent %p flags 0x%x", + vrf_id_to_name(bgp_vrf->vrf_id), evp, parent_pi, + parent_pi->flags); + + /* Locate route within the VRF. */ + /* NOTE: There is no RD here. */ + if (is_evpn_prefix_ipaddr_v4(evp)) { + afi = AFI_IP; + safi = SAFI_UNICAST; + dest = bgp_node_lookup(bgp_vrf->rib[afi][safi], pp); + } else { + afi = AFI_IP6; + safi = SAFI_UNICAST; + dest = bgp_node_lookup(bgp_vrf->rib[afi][safi], pp); + } + + if (!dest) + return 0; + + /* Find matching route entry. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) { + bgp_dest_unlock_node(dest); + return 0; + } + + if (bgp_debug_zebra(NULL)) { + struct ipaddr nhip = {}; + + if (pi->net->rn->p.family == AF_INET6) { + SET_IPADDR_V6(&nhip); + IPV6_ADDR_COPY(&nhip.ipaddr_v6, &pi->attr->mp_nexthop_global); + } else { + SET_IPADDR_V4(&nhip); + IPV4_ADDR_COPY(&nhip.ipaddr_v4, &pi->attr->nexthop); + } + + zlog_debug("... delete pi %s dest %p (l %d) pi %p (l %d, f 0x%x) nh %pIA", + bgp_vrf->name_pretty, dest, + bgp_dest_get_lock_count(dest), pi, pi->lock, + pi->flags, &nhip); + } + + /* Process for route leaking. */ + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp_vrf, pi); + + bgp_aggregate_decrement(bgp_vrf, bgp_dest_get_prefix(dest), pi, afi, + safi); + + /* Force deletion */ + SET_FLAG(dest->flags, BGP_NODE_PROCESS_CLEAR); + + /* Mark entry for deletion */ + bgp_path_info_delete(dest, pi); + + /* Unlink path to evpn nexthop */ + bgp_evpn_path_nh_del(bgp_vrf, pi); + + /* Perform route selection and update zebra, if required. */ + bgp_process(bgp_vrf, dest, pi, afi, safi); + + /* Unlock route node. */ + bgp_dest_unlock_node(dest); + + return ret; +} + +/* + * Install route entry into the VNI routing tables. + */ +static int install_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret = 0; + + if (bgp_debug_update(parent_pi->peer, NULL, NULL, 1)) + zlog_debug( + "%s (%u): Installing EVPN %pFX route in VNI %u IP/MAC table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + ret = install_evpn_route_entry_in_vni_mac(bgp, vpn, p, parent_pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "%s (%u): Failed to install EVPN %pFX route in VNI %u MAC table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + return ret; + } + + ret = install_evpn_route_entry_in_vni_ip(bgp, vpn, p, parent_pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "%s (%u): Failed to install EVPN %pFX route in VNI %u IP table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + return ret; + } + + return ret; +} + +/* + * Uninstall route entry from the VNI routing tables. + */ +static int uninstall_evpn_route_entry(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret = 0; + + if (bgp_debug_update(parent_pi->peer, NULL, NULL, 1)) + zlog_debug( + "%s (%u): Uninstalling EVPN %pFX route from VNI %u IP/MAC table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + ret = uninstall_evpn_route_entry_in_vni_ip(bgp, vpn, p, parent_pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "%s (%u): Failed to uninstall EVPN %pFX route from VNI %u IP table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + return ret; + } + + ret = uninstall_evpn_route_entry_in_vni_mac(bgp, vpn, p, parent_pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "%s (%u): Failed to uninstall EVPN %pFX route from VNI %u MAC table", + vrf_id_to_name(bgp->vrf_id), bgp->vrf_id, p, vpn->vni); + + return ret; + } + + return ret; +} + +/* + * Given a route entry and a VRF, see if this route entry should be + * imported into the VRF i.e., RTs match + Site-of-Origin check passes. + */ +static int is_route_matching_for_vrf(struct bgp *bgp_vrf, + struct bgp_path_info *pi) +{ + struct attr *attr = pi->attr; + struct ecommunity *ecom; + uint32_t i; + + assert(attr); + /* Route should have valid RT to be even considered. */ + if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + return 0; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return 0; + + /* For each extended community RT, see if it matches this VNI. If any RT + * matches, we're done. + */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + struct ecommunity_val *eval; + struct ecommunity_val eval_tmp; + struct vrf_irt_node *irt; + + /* Only deal with RTs */ + pnt = (ecom->val + (i * ecom->unit_size)); + eval = (struct ecommunity_val *)(ecom->val + + (i * ecom->unit_size)); + type = *pnt++; + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET) + continue; + + /* See if this RT matches specified VNIs import RTs */ + irt = lookup_vrf_import_rt(eval); + if (irt) + if (is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) + return 1; + + /* Also check for non-exact match. In this, we mask out the AS + * and + * only check on the local-admin sub-field. This is to + * facilitate using + * VNI as the RT for EBGP peering too. + */ + irt = NULL; + if (type == ECOMMUNITY_ENCODE_AS + || type == ECOMMUNITY_ENCODE_AS4 + || type == ECOMMUNITY_ENCODE_IP) { + memcpy(&eval_tmp, eval, ecom->unit_size); + mask_ecom_global_admin(&eval_tmp, eval); + irt = lookup_vrf_import_rt(&eval_tmp); + } + if (irt) + if (is_vrf_present_in_irt_vrfs(irt->vrfs, bgp_vrf)) + return 1; + } + + return 0; +} + +/* + * Given a route entry and a VNI, see if this route entry should be + * imported into the VNI i.e., RTs match. + */ +static int is_route_matching_for_vni(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_path_info *pi) +{ + struct attr *attr = pi->attr; + struct ecommunity *ecom; + uint32_t i; + + assert(attr); + /* Route should have valid RT to be even considered. */ + if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + return 0; + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return 0; + + /* For each extended community RT, see if it matches this VNI. If any RT + * matches, we're done. + */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + struct ecommunity_val *eval; + struct ecommunity_val eval_tmp; + struct irt_node *irt; + + /* Only deal with RTs */ + pnt = (ecom->val + (i * ecom->unit_size)); + eval = (struct ecommunity_val *)(ecom->val + + (i * ecom->unit_size)); + type = *pnt++; + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET) + continue; + + /* See if this RT matches specified VNIs import RTs */ + irt = lookup_import_rt(bgp, eval); + if (irt) + if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) + return 1; + + /* Also check for non-exact match. In this, we mask out the AS + * and + * only check on the local-admin sub-field. This is to + * facilitate using + * VNI as the RT for EBGP peering too. + */ + irt = NULL; + if (type == ECOMMUNITY_ENCODE_AS + || type == ECOMMUNITY_ENCODE_AS4 + || type == ECOMMUNITY_ENCODE_IP) { + memcpy(&eval_tmp, eval, ecom->unit_size); + mask_ecom_global_admin(&eval_tmp, eval); + irt = lookup_import_rt(bgp, &eval_tmp); + } + if (irt) + if (is_vni_present_in_irt_vnis(irt->vnis, vpn)) + return 1; + } + + return 0; +} + +static bool bgp_evpn_route_matches_macvrf_soo(struct bgp_path_info *pi, + const struct prefix_evpn *evp) +{ + struct bgp *bgp_evpn = bgp_get_evpn(); + struct ecommunity *macvrf_soo; + bool ret = false; + + if (!bgp_evpn || !bgp_evpn->evpn_info) + return false; + + /* We only stamp the mac-vrf soo on routes from our local L2VNI. + * No need to filter additional EVPN routes that originated outside + * the MAC-VRF/L2VNI. + */ + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE && + evp->prefix.route_type != BGP_EVPN_IMET_ROUTE) + return false; + + macvrf_soo = bgp_evpn->evpn_info->soo; + ret = route_matches_soo(pi, macvrf_soo); + + if (ret && bgp_debug_zebra(NULL)) { + char *ecom_str; + + ecom_str = ecommunity_ecom2str(macvrf_soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + zlog_debug( + "import of evpn prefix %pFX skipped, local mac-vrf soo %s", + evp, ecom_str); + ecommunity_strfree(&ecom_str); + } + + return ret; +} + +/* This API will scan evpn routes for checking attribute's rmac + * macthes with bgp instance router mac. It avoid installing + * route into bgp vrf table and remote rmac in bridge table. + */ +static int bgp_evpn_route_rmac_self_check(struct bgp *bgp_vrf, + const struct prefix_evpn *evp, + struct bgp_path_info *pi) +{ + /* evpn route could have learnt prior to L3vni has come up, + * perform rmac check before installing route and + * remote router mac. + * The route will be removed from global bgp table once + * SVI comes up with MAC and stored in hash, triggers + * bgp_mac_rescan_all_evpn_tables. + */ + if (memcmp(&bgp_vrf->rmac, &pi->attr->rmac, ETH_ALEN) == 0) { + if (bgp_debug_update(pi->peer, NULL, NULL, 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(pi->attr, attr_str, sizeof(attr_str)); + + zlog_debug( + "%s: bgp %u prefix %pFX with attr %s - DENIED due to self mac", + __func__, bgp_vrf->vrf_id, evp, attr_str); + } + + return 1; + } + + return 0; +} + +/* don't import hosts that are locally attached */ +static inline bool +bgp_evpn_skip_vrf_import_of_local_es(struct bgp *bgp_vrf, + const struct prefix_evpn *evp, + struct bgp_path_info *pi, int install) +{ + esi_t *esi; + + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + esi = bgp_evpn_attr_get_esi(pi->attr); + + /* Don't import routes that point to a local destination */ + if (bgp_evpn_attr_is_local_es(pi->attr)) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) { + char esi_buf[ESI_STR_LEN]; + + zlog_debug( + "vrf %s of evpn prefix %pFX skipped, local es %s", + install ? "import" : "unimport", evp, + esi_to_str(esi, esi_buf, + sizeof(esi_buf))); + } + return true; + } + } + return false; +} + +/* + * Install or uninstall a mac-ip route in the provided vrf if + * there is a rt match + */ +int bgp_evpn_route_entry_install_if_vrf_match(struct bgp *bgp_vrf, + struct bgp_path_info *pi, + int install) +{ + int ret = 0; + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(pi->net); + + /* Consider "valid" remote routes applicable for + * this VRF. + */ + if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_NORMAL)) + return 0; + + if (is_route_matching_for_vrf(bgp_vrf, pi)) { + if (bgp_evpn_route_rmac_self_check(bgp_vrf, evp, pi)) + return 0; + + /* don't import hosts that are locally attached */ + if (install && (bgp_evpn_skip_vrf_import_of_local_es( + bgp_vrf, evp, pi, install) || + bgp_evpn_route_matches_macvrf_soo(pi, evp))) + return 0; + + if (install) + ret = install_evpn_route_entry_in_vrf(bgp_vrf, evp, pi); + else + ret = uninstall_evpn_route_entry_in_vrf(bgp_vrf, evp, + pi); + + if (ret) + flog_err(EC_BGP_EVPN_FAIL, + "Failed to %s EVPN %pFX route in VRF %s", + install ? "install" : "uninstall", evp, + vrf_id_to_name(bgp_vrf->vrf_id)); + } + + return ret; +} + +/* + * Install or uninstall mac-ip routes are appropriate for this + * particular VRF. + */ +static int install_uninstall_routes_for_vrf(struct bgp *bgp_vrf, bool install) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rd_dest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + int ret; + struct bgp *bgp_evpn = NULL; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return -1; + + /* Walk entire global routing table and evaluate routes which could be + * imported into this VRF. Note that we need to loop through all global + * routes to determine which route matches the import rt on vrf + */ + for (rd_dest = bgp_table_top(bgp_evpn->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + table = bgp_dest_get_bgp_table_info(rd_dest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix( + dest); + + /* if not mac-ip route skip this route */ + if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + || evp->prefix.route_type + == BGP_EVPN_IP_PREFIX_ROUTE)) + continue; + + /* if not a mac+ip route skip this route */ + if (!(is_evpn_prefix_ipaddr_v4(evp) + || is_evpn_prefix_ipaddr_v6(evp))) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + ret = bgp_evpn_route_entry_install_if_vrf_match( + bgp_vrf, pi, install); + if (ret) { + bgp_dest_unlock_node(rd_dest); + bgp_dest_unlock_node(dest); + return ret; + } + } + } + } + + return 0; +} + +/* + * Install or uninstall routes of specified type that are appropriate for this + * particular VNI. + */ +static int install_uninstall_routes_for_vni(struct bgp *bgp, + struct bgpevpn *vpn, bool install) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rd_dest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + int ret; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Walk entire global routing table and evaluate routes which could be + * imported into this VPN. Note that we cannot just look at the routes + * for + * the VNI's RD - remote routes applicable for this VNI could have any + * RD. + */ + /* EVPN routes are a 2-level table. */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + table = bgp_dest_get_bgp_table_info(rd_dest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix( + dest); + + if (evp->prefix.route_type != BGP_EVPN_IMET_ROUTE && + evp->prefix.route_type != BGP_EVPN_AD_ROUTE && + evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + /* Consider "valid" remote routes applicable for + * this VNI. */ + if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_NORMAL)) + continue; + + if (!is_route_matching_for_vni(bgp, vpn, pi)) + continue; + + if (install) { + if (bgp_evpn_route_matches_macvrf_soo( + pi, evp)) + continue; + + ret = install_evpn_route_entry(bgp, vpn, + evp, pi); + } else + ret = uninstall_evpn_route_entry( + bgp, vpn, evp, pi); + + if (ret) { + flog_err(EC_BGP_EVPN_FAIL, + "%u: Failed to %s EVPN %s route in VNI %u", + bgp->vrf_id, + install ? "install" + : "uninstall", + evp->prefix.route_type == + BGP_EVPN_MAC_IP_ROUTE + ? "MACIP" + : "IMET", + vpn->vni); + + bgp_dest_unlock_node(rd_dest); + bgp_dest_unlock_node(dest); + return ret; + } + } + } + } + + return 0; +} + +/* Install any existing remote routes applicable for this VRF into VRF RIB. This + * is invoked upon l3vni-add or l3vni import rt change + */ +static int install_routes_for_vrf(struct bgp *bgp_vrf) +{ + install_uninstall_routes_for_vrf(bgp_vrf, true); + return 0; +} + +/* + * Install any existing remote routes applicable for this VNI into its + * routing table. This is invoked when a VNI becomes "live" or its Import + * RT is changed. + */ +static int install_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + /* + * Install type-3 routes followed by type-2 routes - the ones applicable + * for this VNI. + */ + return install_uninstall_routes_for_vni(bgp, vpn, true); +} + +/* uninstall routes from l3vni vrf. */ +static int uninstall_routes_for_vrf(struct bgp *bgp_vrf) +{ + install_uninstall_routes_for_vrf(bgp_vrf, false); + return 0; +} + +/* + * Uninstall any existing remote routes for this VNI. One scenario in which + * this is invoked is upon an import RT change. + */ +static int uninstall_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + /* + * Uninstall type-2 routes followed by type-3 routes - the ones + * applicable for this VNI. + */ + return install_uninstall_routes_for_vni(bgp, vpn, false); +} + +/* + * Install or uninstall route in matching VRFs (list). + */ +static int install_uninstall_route_in_vrfs(struct bgp *bgp_def, afi_t afi, + safi_t safi, struct prefix_evpn *evp, + struct bgp_path_info *pi, + struct list *vrfs, int install) +{ + struct bgp *bgp_vrf; + struct listnode *node, *nnode; + + /* Only type-2/type-5 routes go into a VRF */ + if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) + return 0; + + /* if it is type-2 route and not a mac+ip route skip this route */ + if ((evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + && !(is_evpn_prefix_ipaddr_v4(evp) + || is_evpn_prefix_ipaddr_v6(evp))) + return 0; + + for (ALL_LIST_ELEMENTS(vrfs, node, nnode, bgp_vrf)) { + int ret; + + /* don't import hosts that are locally attached */ + if (install && bgp_evpn_skip_vrf_import_of_local_es( + bgp_vrf, evp, pi, install)) + return 0; + + if (install) + ret = install_evpn_route_entry_in_vrf(bgp_vrf, evp, pi); + else + ret = uninstall_evpn_route_entry_in_vrf(bgp_vrf, evp, + pi); + + if (ret) { + flog_err(EC_BGP_EVPN_FAIL, + "%u: Failed to %s prefix %pFX in VRF %s", + bgp_def->vrf_id, + install ? "install" : "uninstall", evp, + vrf_id_to_name(bgp_vrf->vrf_id)); + return ret; + } + } + + return 0; +} + +/* + * Install or uninstall route in matching VNIs (list). + */ +static int install_uninstall_route_in_vnis(struct bgp *bgp, afi_t afi, + safi_t safi, struct prefix_evpn *evp, + struct bgp_path_info *pi, + struct list *vnis, int install) +{ + struct bgpevpn *vpn; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(vnis, node, nnode, vpn)) { + int ret; + + if (!is_vni_live(vpn)) + continue; + + if (install) + ret = install_evpn_route_entry(bgp, vpn, evp, pi); + else + ret = uninstall_evpn_route_entry(bgp, vpn, evp, pi); + + if (ret) { + flog_err(EC_BGP_EVPN_FAIL, + "%u: Failed to %s EVPN %s route in VNI %u", + bgp->vrf_id, install ? "install" : "uninstall", + evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + ? "MACIP" + : "IMET", + vpn->vni); + return ret; + } + } + + return 0; +} + +/* + * Install or uninstall route for appropriate VNIs/ESIs. + */ +static int bgp_evpn_install_uninstall_table(struct bgp *bgp, afi_t afi, + safi_t safi, const struct prefix *p, + struct bgp_path_info *pi, + int import, bool in_vni_rt, + bool in_vrf_rt) +{ + struct prefix_evpn *evp = (struct prefix_evpn *)p; + struct attr *attr = pi->attr; + struct ecommunity *ecom; + uint32_t i; + struct prefix_evpn ad_evp; + + assert(attr); + + /* Only type-1, type-2, type-3, type-4 and type-5 + * are supported currently + */ + if (!(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + || evp->prefix.route_type == BGP_EVPN_IMET_ROUTE + || evp->prefix.route_type == BGP_EVPN_ES_ROUTE + || evp->prefix.route_type == BGP_EVPN_AD_ROUTE + || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) + return 0; + + /* If we don't have Route Target, nothing much to do. */ + if (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) + return 0; + + /* EAD prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy for the VNI + */ + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) + evp = evpn_type1_prefix_vni_ip_copy(&ad_evp, evp, + attr->nexthop); + + ecom = bgp_attr_get_ecommunity(attr); + if (!ecom || !ecom->size) + return -1; + + /* Filter routes carrying a Site-of-Origin that matches our + * local MAC-VRF SoO. + */ + if (import && bgp_evpn_route_matches_macvrf_soo(pi, evp)) + return 0; + + /* An EVPN route belongs to a VNI or a VRF or an ESI based on the RTs + * attached to the route */ + for (i = 0; i < ecom->size; i++) { + uint8_t *pnt; + uint8_t type, sub_type; + struct ecommunity_val *eval; + struct ecommunity_val eval_tmp; + struct irt_node *irt; /* import rt for l2vni */ + struct vrf_irt_node *vrf_irt; /* import rt for l3vni */ + struct bgp_evpn_es *es; + + /* Only deal with RTs */ + pnt = (ecom->val + (i * ecom->unit_size)); + eval = (struct ecommunity_val *)(ecom->val + + (i * ecom->unit_size)); + type = *pnt++; + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET) + continue; + + /* non-local MAC-IP routes in the global route table are linked + * to the destination ES + */ + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + bgp_evpn_path_es_link(pi, 0, + bgp_evpn_attr_get_esi(pi->attr)); + + /* + * macip routes (type-2) are imported into VNI and VRF tables. + * IMET route is imported into VNI table. + * prefix routes are imported into VRF table. + */ + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE || + evp->prefix.route_type == BGP_EVPN_IMET_ROUTE || + evp->prefix.route_type == BGP_EVPN_AD_ROUTE || + evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) { + + irt = in_vni_rt ? lookup_import_rt(bgp, eval) : NULL; + if (irt) + install_uninstall_route_in_vnis( + bgp, afi, safi, evp, pi, irt->vnis, + import); + + vrf_irt = in_vrf_rt ? lookup_vrf_import_rt(eval) : NULL; + if (vrf_irt) + install_uninstall_route_in_vrfs( + bgp, afi, safi, evp, pi, vrf_irt->vrfs, + import); + + /* Also check for non-exact match. + * In this, we mask out the AS and + * only check on the local-admin sub-field. + * This is to facilitate using + * VNI as the RT for EBGP peering too. + */ + irt = NULL; + vrf_irt = NULL; + if (type == ECOMMUNITY_ENCODE_AS + || type == ECOMMUNITY_ENCODE_AS4 + || type == ECOMMUNITY_ENCODE_IP) { + memcpy(&eval_tmp, eval, ecom->unit_size); + mask_ecom_global_admin(&eval_tmp, eval); + if (in_vni_rt) + irt = lookup_import_rt(bgp, &eval_tmp); + if (in_vrf_rt) + vrf_irt = + lookup_vrf_import_rt(&eval_tmp); + } + + if (irt) + install_uninstall_route_in_vnis( + bgp, afi, safi, evp, pi, irt->vnis, + import); + if (vrf_irt) + install_uninstall_route_in_vrfs( + bgp, afi, safi, evp, pi, vrf_irt->vrfs, + import); + } + + /* es route is imported into the es table */ + if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE) { + + /* we will match based on the entire esi to avoid + * import of an es route for esi2 into esi1 + */ + es = bgp_evpn_es_find(&evp->prefix.es_addr.esi); + if (es && bgp_evpn_is_es_local(es)) + bgp_evpn_es_route_install_uninstall( + bgp, es, afi, safi, evp, pi, import); + } + } + + return 0; +} + +/* + * Install or uninstall route for appropriate VNIs/ESIs. + */ +static int install_uninstall_evpn_route(struct bgp *bgp, afi_t afi, safi_t safi, + const struct prefix *p, + struct bgp_path_info *pi, int import) +{ + return bgp_evpn_install_uninstall_table(bgp, afi, safi, p, pi, import, + true, true); +} + +void bgp_evpn_import_type2_route(struct bgp_path_info *pi, int import) +{ + struct bgp *bgp_evpn; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return; + + install_uninstall_evpn_route(bgp_evpn, AFI_L2VPN, SAFI_EVPN, + &pi->net->rn->p, pi, import); +} + +/* + * delete and withdraw all ipv4 and ipv6 routes in the vrf table as type-5 + * routes + */ +static void delete_withdraw_vrf_routes(struct bgp *bgp_vrf) +{ + /* Delete ipv4 default route and withdraw from peers */ + if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST)) + bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP, + SAFI_UNICAST, false); + + /* delete all ipv4 routes and withdraw from peers */ + if (advertise_type5_routes(bgp_vrf, AFI_IP)) + bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST); + + /* Delete ipv6 default route and withdraw from peers */ + if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST)) + bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6, + SAFI_UNICAST, false); + + /* delete all ipv6 routes and withdraw from peers */ + if (advertise_type5_routes(bgp_vrf, AFI_IP6)) + bgp_evpn_withdraw_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST); +} + +/* + * update and advertise all ipv4 and ipv6 routes in thr vrf table as type-5 + * routes + */ +void update_advertise_vrf_routes(struct bgp *bgp_vrf) +{ + struct bgp *bgp_evpn = NULL; /* EVPN bgp instance */ + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return; + + /* update all ipv4 routes */ + if (advertise_type5_routes(bgp_vrf, AFI_IP)) + bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP, SAFI_UNICAST); + + /* update ipv4 default route and withdraw from peers */ + if (evpn_default_originate_set(bgp_vrf, AFI_IP, SAFI_UNICAST)) + bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP, + SAFI_UNICAST, true); + + /* update all ipv6 routes */ + if (advertise_type5_routes(bgp_vrf, AFI_IP6)) + bgp_evpn_advertise_type5_routes(bgp_vrf, AFI_IP6, SAFI_UNICAST); + + /* update ipv6 default route and withdraw from peers */ + if (evpn_default_originate_set(bgp_vrf, AFI_IP6, SAFI_UNICAST)) + bgp_evpn_install_uninstall_default_route(bgp_vrf, AFI_IP6, + SAFI_UNICAST, true); + +} + +/* + * update and advertise local routes for a VRF as type-5 routes. + * This is invoked upon RD change for a VRF. Note taht the processing is only + * done in the global route table using the routes which already exist in the + * VRF routing table + */ +static void update_router_id_vrf(struct bgp *bgp_vrf) +{ + /* skip if the RD is configured */ + if (is_vrf_rd_configured(bgp_vrf)) + return; + + /* derive the RD for the VRF based on new router-id */ + bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); + + /* update advertise ipv4|ipv6 routes as type-5 routes */ + update_advertise_vrf_routes(bgp_vrf); +} + +/* + * Delete and withdraw all type-5 routes for the RD corresponding to VRF. + * This is invoked upon VRF RD change. The processing is done only from global + * table. + */ +static void withdraw_router_id_vrf(struct bgp *bgp_vrf) +{ + /* skip if the RD is configured */ + if (is_vrf_rd_configured(bgp_vrf)) + return; + + /* delete/withdraw ipv4|ipv6 routes as type-5 routes */ + delete_withdraw_vrf_routes(bgp_vrf); +} + +static void update_advertise_vni_route(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest) +{ + struct bgp_dest *global_dest; + struct bgp_path_info *pi, *global_pi; + struct attr *attr; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + struct prefix_evpn tmp_evp; + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + /* + * We have already processed type-3 routes. + * Process only type-1 and type-2 routes here. + */ + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE && + evp->prefix.route_type != BGP_EVPN_AD_ROUTE) + return; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP && + pi->sub_type == BGP_ROUTE_STATIC) + break; + if (!pi) + return; + + /* + * VNI table MAC-IP prefixes don't have MAC so make sure it's + * set from path info here. + */ + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + if (is_evpn_prefix_ipaddr_none(evp)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy( + &tmp_evp, evp, NULL /* mac */, + evpn_type2_path_info_get_ip(pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy( + &tmp_evp, evp, evpn_type2_path_info_get_mac(pi), + NULL /* ip */); + } + } else { + memcpy(&tmp_evp, evp, sizeof(tmp_evp)); + } + + /* Create route in global routing table using this route entry's + * attribute. + */ + attr = pi->attr; + global_dest = bgp_evpn_global_node_get(bgp->rib[afi][safi], afi, safi, + &tmp_evp, &vpn->prd, NULL); + assert(global_dest); + + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + /* Type-2 route */ + update_evpn_route_entry( + bgp, vpn, afi, safi, global_dest, attr, NULL /* mac */, + NULL /* ip */, 1, &global_pi, 0, + mac_mobility_seqnum(attr), false /* setup_sync */, + NULL /* old_is_sync */); + } else { + /* Type-1 route */ + struct bgp_evpn_es *es; + int route_changed = 0; + + es = bgp_evpn_es_find(&evp->prefix.ead_addr.esi); + bgp_evpn_mh_route_update(bgp, es, vpn, afi, safi, global_dest, + attr, &global_pi, &route_changed); + } + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, global_dest, global_pi, afi, safi); + bgp_dest_unlock_node(global_dest); +} + +/* + * Update and advertise local routes for a VNI. Invoked upon router-id + * change. Note that the processing is done only on the global route table + * using routes that already exist in the per-VNI table. + */ +static void update_advertise_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct prefix_evpn p; + struct bgp_dest *dest, *global_dest; + struct bgp_path_info *pi; + struct attr *attr; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + /* Locate type-3 route for VNI in the per-VNI table and use its + * attributes to create and advertise the type-3 route for this VNI + * in the global table. + * + * RT-3 only if doing head-end replication + */ + if (bgp_evpn_vni_flood_mode_get(bgp, vpn) + == VXLAN_FLOOD_HEAD_END_REPL) { + build_evpn_type3_prefix(&p, vpn->originator_ip); + dest = bgp_evpn_vni_node_lookup(vpn, &p, NULL); + if (!dest) /* unexpected */ + return; + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && + pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + break; + if (!pi) { + bgp_dest_unlock_node(dest); + return; + } + + attr = pi->attr; + + global_dest = bgp_evpn_global_node_get( + bgp->rib[afi][safi], afi, safi, &p, &vpn->prd, NULL); + update_evpn_route_entry( + bgp, vpn, afi, safi, global_dest, attr, NULL /* mac */, + NULL /* ip */, 1, &pi, 0, mac_mobility_seqnum(attr), + false /* setup_sync */, NULL /* old_is_sync */); + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, global_dest, pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + /* Now, walk this VNI's MAC & IP route table and use the route and its + * attribute to create and schedule route in global table. + */ + for (dest = bgp_table_top(vpn->mac_table); dest; + dest = bgp_route_next(dest)) + update_advertise_vni_route(bgp, vpn, dest); + + for (dest = bgp_table_top(vpn->ip_table); dest; + dest = bgp_route_next(dest)) + update_advertise_vni_route(bgp, vpn, dest); +} + +/* + * Delete (and withdraw) local routes for a VNI - only from the global + * table. Invoked upon router-id change. + */ +static int delete_withdraw_vni_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct prefix_evpn p; + struct bgp_dest *global_dest; + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + + /* Delete and withdraw locally learnt type-2 routes (MACIP) + * for this VNI - from the global table. + */ + delete_global_type2_routes(bgp, vpn); + + /* Remove type-3 route for this VNI from global table. */ + build_evpn_type3_prefix(&p, vpn->originator_ip); + global_dest = bgp_evpn_global_node_lookup(bgp->rib[afi][safi], safi, &p, + &vpn->prd, NULL); + if (global_dest) { + /* Delete route entry in the global EVPN table. */ + delete_evpn_route_entry(bgp, afi, safi, global_dest, &pi); + + /* Schedule for processing - withdraws to peers happen from + * this table. + */ + if (pi) + bgp_process(bgp, global_dest, pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + + delete_global_ead_evi_routes(bgp, vpn); + return 0; +} + +/* + * Handle router-id change. Update and advertise local routes corresponding + * to this VNI from peers. Note that this is invoked after updating the + * router-id. The routes in the per-VNI table are used to create routes in + * the global table and schedule them. + */ +static void update_router_id_vni(struct hash_bucket *bucket, struct bgp *bgp) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + + /* Skip VNIs with configured RD. */ + if (is_rd_configured(vpn)) + return; + + bgp_evpn_derive_auto_rd(bgp, vpn); + update_advertise_vni_routes(bgp, vpn); +} + +/* + * Handle router-id change. Delete and withdraw local routes corresponding + * to this VNI from peers. Note that this is invoked prior to updating + * the router-id and is done only on the global route table, the routes + * are needed in the per-VNI table to re-advertise with new router id. + */ +static void withdraw_router_id_vni(struct hash_bucket *bucket, struct bgp *bgp) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + + /* Skip VNIs with configured RD. */ + if (is_rd_configured(vpn)) + return; + + delete_withdraw_vni_routes(bgp, vpn); +} + +/* + * Create RT-3 for a VNI and schedule for processing and advertisement. + * This is invoked upon flooding mode changing to head-end replication. + */ +static void create_advertise_type3(struct hash_bucket *bucket, void *data) +{ + struct bgpevpn *vpn = bucket->data; + struct bgp *bgp = data; + struct prefix_evpn p; + + if (!vpn || !is_vni_live(vpn) || + bgp_evpn_vni_flood_mode_get(bgp, vpn) + != VXLAN_FLOOD_HEAD_END_REPL) + return; + + build_evpn_type3_prefix(&p, vpn->originator_ip); + if (update_evpn_route(bgp, vpn, &p, 0, 0, NULL)) + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "Type3 route creation failure for VNI %u", vpn->vni); +} + +/* + * Delete RT-3 for a VNI and schedule for processing and withdrawal. + * This is invoked upon flooding mode changing to drop BUM packets. + */ +static void delete_withdraw_type3(struct hash_bucket *bucket, void *data) +{ + struct bgpevpn *vpn = bucket->data; + struct bgp *bgp = data; + struct prefix_evpn p; + + if (!vpn || !is_vni_live(vpn)) + return; + + build_evpn_type3_prefix(&p, vpn->originator_ip); + delete_evpn_route(bgp, vpn, &p); +} + +/* + * Process received EVPN type-2 route (advertise or withdraw). + */ +static int process_type2_route(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id) +{ + struct prefix_rd prd; + struct prefix_evpn p = {}; + struct bgp_route_evpn evpn = {}; + uint8_t ipaddr_len; + uint8_t macaddr_len; + /* holds the VNI(s) as in packet */ + mpls_label_t label[BGP_MAX_LABELS] = {}; + uint8_t num_labels = 0; + uint32_t eth_tag; + int ret = 0; + + /* Type-2 route should be either 33, 37 or 49 bytes or an + * additional 3 bytes if there is a second label (VNI): + * RD (8), ESI (10), Eth Tag (4), MAC Addr Len (1), + * MAC Addr (6), IP len (1), IP (0, 4 or 16), + * MPLS Lbl1 (3), MPLS Lbl2 (0 or 3) + */ + if (psize != 33 && psize != 37 && psize != 49 && psize != 36 + && psize != 40 && psize != 52) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-2 NLRI with invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + struct stream *pkt = stream_new(psize); + stream_put(pkt, pfx, psize); + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + STREAM_GET(&prd.val, pkt, 8); + + /* Make EVPN prefix. */ + p.family = AF_EVPN; + p.prefixlen = EVPN_ROUTE_PREFIXLEN; + p.prefix.route_type = BGP_EVPN_MAC_IP_ROUTE; + + /* Copy Ethernet Seg Identifier */ + if (attr) { + STREAM_GET(&attr->esi, pkt, sizeof(esi_t)); + + if (bgp_evpn_is_esi_local_and_non_bypass(&attr->esi)) + attr->es_flags |= ATTR_ES_IS_LOCAL; + else + attr->es_flags &= ~ATTR_ES_IS_LOCAL; + } else { + STREAM_FORWARD_GETP(pkt, sizeof(esi_t)); + } + + /* Copy Ethernet Tag */ + STREAM_GET(ð_tag, pkt, 4); + p.prefix.macip_addr.eth_tag = ntohl(eth_tag); + + /* Get the MAC Addr len */ + STREAM_GETC(pkt, macaddr_len); + + /* Get the MAC Addr */ + if (macaddr_len == (ETH_ALEN * 8)) { + STREAM_GET(&p.prefix.macip_addr.mac.octet, pkt, ETH_ALEN); + } else { + flog_err( + EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-2 NLRI with unsupported MAC address length %d", + peer->bgp->vrf_id, peer->host, macaddr_len); + goto fail; + } + + + /* Get the IP. */ + STREAM_GETC(pkt, ipaddr_len); + + if (ipaddr_len != 0 && ipaddr_len != IPV4_MAX_BITLEN + && ipaddr_len != IPV6_MAX_BITLEN) { + flog_err( + EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-2 NLRI with unsupported IP address length %d", + peer->bgp->vrf_id, peer->host, ipaddr_len); + goto fail; + } + + if (ipaddr_len) { + ipaddr_len /= 8; /* Convert to bytes. */ + p.prefix.macip_addr.ip.ipa_type = (ipaddr_len == IPV4_MAX_BYTELEN) + ? IPADDR_V4 + : IPADDR_V6; + STREAM_GET(&p.prefix.macip_addr.ip.ip.addr, pkt, ipaddr_len); + } + + /* Get the VNI(s). Stored as bytes here. */ + STREAM_GET(&label[0], pkt, BGP_LABEL_BYTES); + num_labels++; + + /* Do we have a second VNI? */ + if (STREAM_READABLE(pkt)) { + num_labels++; + STREAM_GET(&label[1], pkt, BGP_LABEL_BYTES); + } + + /* Process the route. */ + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, + &label[0], num_labels, 0, &evpn); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label[0], + num_labels, &evpn); + goto done; + +fail: +stream_failure: + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-2 NLRI - corrupt, discarding", + peer->bgp->vrf_id, peer->host); + ret = -1; +done: + stream_free(pkt); + return ret; +} + +/* + * Process received EVPN type-3 route (advertise or withdraw). + */ +static int process_type3_route(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id) +{ + struct prefix_rd prd; + struct prefix_evpn p; + uint8_t ipaddr_len; + uint32_t eth_tag; + + /* Type-3 route should be either 17 or 29 bytes: RD (8), Eth Tag (4), + * IP len (1) and IP (4 or 16). + */ + if (psize != 17 && psize != 29) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-3 NLRI with invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + /* If PMSI is present, log if it is anything other than IR. + * Note: We just simply ignore the values as it is not clear if + * doing anything else is better. + */ + if (attr && + (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL))) { + enum pta_type pmsi_tnl_type = bgp_attr_get_pmsi_tnl_type(attr); + + if (pmsi_tnl_type != PMSI_TNLTYPE_INGR_REPL + && pmsi_tnl_type != PMSI_TNLTYPE_PIM_SM) { + flog_warn( + EC_BGP_EVPN_PMSI_PRESENT, + "%u:%s - Rx EVPN Type-3 NLRI with unsupported PTA %d", + peer->bgp->vrf_id, peer->host, pmsi_tnl_type); + } + } + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, pfx, 8); + pfx += 8; + + /* Make EVPN prefix. */ + memset(&p, 0, sizeof(p)); + p.family = AF_EVPN; + p.prefixlen = EVPN_ROUTE_PREFIXLEN; + p.prefix.route_type = BGP_EVPN_IMET_ROUTE; + + /* Copy Ethernet Tag */ + memcpy(ð_tag, pfx, 4); + p.prefix.imet_addr.eth_tag = ntohl(eth_tag); + pfx += 4; + + /* Get the IP. */ + ipaddr_len = *pfx++; + if (ipaddr_len == IPV4_MAX_BITLEN) { + p.prefix.imet_addr.ip.ipa_type = IPADDR_V4; + memcpy(&p.prefix.imet_addr.ip.ip.addr, pfx, IPV4_MAX_BYTELEN); + } else { + flog_err( + EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-3 NLRI with unsupported IP address length %d", + peer->bgp->vrf_id, peer->host, ipaddr_len); + return -1; + } + + /* Process the route. */ + if (attr) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, + 0, 0, NULL); + else + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, + NULL); + return 0; +} + +/* + * Process received EVPN type-5 route (advertise or withdraw). + */ +static int process_type5_route(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id) +{ + struct prefix_rd prd; + struct prefix_evpn p; + struct bgp_route_evpn evpn; + uint8_t ippfx_len; + uint32_t eth_tag; + mpls_label_t label; /* holds the VNI as in the packet */ + bool is_valid_update = true; + + /* Type-5 route should be 34 or 58 bytes: + * RD (8), ESI (10), Eth Tag (4), IP len (1), IP (4 or 16), + * GW (4 or 16) and VNI (3). + * Note that the IP and GW should both be IPv4 or both IPv6. + */ + if (psize != 34 && psize != 58) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-5 NLRI with invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, pfx, 8); + pfx += 8; + + /* Make EVPN prefix. */ + memset(&p, 0, sizeof(p)); + p.family = AF_EVPN; + p.prefixlen = EVPN_ROUTE_PREFIXLEN; + p.prefix.route_type = BGP_EVPN_IP_PREFIX_ROUTE; + + /* Additional information outside of prefix - ESI and GW IP */ + memset(&evpn, 0, sizeof(evpn)); + + /* Fetch ESI overlay index */ + if (attr) + memcpy(&evpn.eth_s_id, pfx, sizeof(esi_t)); + pfx += ESI_BYTES; + + /* Fetch Ethernet Tag. */ + memcpy(ð_tag, pfx, 4); + p.prefix.prefix_addr.eth_tag = ntohl(eth_tag); + pfx += 4; + + /* Fetch IP prefix length. */ + ippfx_len = *pfx++; + if (ippfx_len > IPV6_MAX_BITLEN) { + flog_err( + EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-5 NLRI with invalid IP Prefix length %d", + peer->bgp->vrf_id, peer->host, ippfx_len); + return -1; + } + p.prefix.prefix_addr.ip_prefix_length = ippfx_len; + + /* Determine IPv4 or IPv6 prefix */ + /* Since the address and GW are from the same family, this just becomes + * a simple check on the total size. + */ + if (psize == 34) { + SET_IPADDR_V4(&p.prefix.prefix_addr.ip); + memcpy(&p.prefix.prefix_addr.ip.ipaddr_v4, pfx, 4); + pfx += 4; + SET_IPADDR_V4(&evpn.gw_ip); + memcpy(&evpn.gw_ip.ipaddr_v4, pfx, 4); + pfx += 4; + } else { + SET_IPADDR_V6(&p.prefix.prefix_addr.ip); + memcpy(&p.prefix.prefix_addr.ip.ipaddr_v6, pfx, + IPV6_MAX_BYTELEN); + pfx += IPV6_MAX_BYTELEN; + SET_IPADDR_V6(&evpn.gw_ip); + memcpy(&evpn.gw_ip.ipaddr_v6, pfx, IPV6_MAX_BYTELEN); + pfx += IPV6_MAX_BYTELEN; + } + + /* Get the VNI (in MPLS label field). Stored as bytes here. */ + memset(&label, 0, sizeof(label)); + memcpy(&label, pfx, BGP_LABEL_BYTES); + + /* + * If in future, we are required to access additional fields, + * we MUST increment pfx by BGP_LABEL_BYTES in before reading the next + * field + */ + + /* + * An update containing a non-zero gateway IP and a non-zero ESI + * at the same time is should be treated as withdraw + */ + if (bgp_evpn_is_esi_valid(&evpn.eth_s_id) && + !ipaddr_is_zero(&evpn.gw_ip)) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%s - Rx EVPN Type-5 ESI and gateway-IP both non-zero.", + peer->host); + is_valid_update = false; + } else if (bgp_evpn_is_esi_valid(&evpn.eth_s_id)) + evpn.type = OVERLAY_INDEX_ESI; + else if (!ipaddr_is_zero(&evpn.gw_ip)) + evpn.type = OVERLAY_INDEX_GATEWAY_IP; + if (attr) { + if (is_zero_mac(&attr->rmac) && + !bgp_evpn_is_esi_valid(&evpn.eth_s_id) && + ipaddr_is_zero(&evpn.gw_ip) && label == 0) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%s - Rx EVPN Type-5 ESI, gateway-IP, RMAC and label all zero", + peer->host); + is_valid_update = false; + } + + if (is_mcast_mac(&attr->rmac) || is_bcast_mac(&attr->rmac)) + is_valid_update = false; + } + + /* Process the route. */ + if (attr && is_valid_update) + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, + &label, 1, 0, &evpn); + else { + if (!is_valid_update) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, BUFSIZ); + zlog_warn( + "Invalid update from peer %s vrf %u prefix %pFX attr %s - treat as withdraw", + peer->hostname, peer->bgp->vrf_id, &p, + attr_str); + } + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, &label, 1, + &evpn); + } + + return 0; +} + +static void evpn_mpattr_encode_type5(struct stream *s, const struct prefix *p, + const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + struct attr *attr) +{ + int len; + char temp[16]; + const struct evpn_addr *p_evpn_p; + + memset(&temp, 0, sizeof(temp)); + if (p->family != AF_EVPN) + return; + p_evpn_p = &(p->u.prefix_evpn); + + /* len denites the total len of IP and GW-IP in the route + IP and GW-IP have to be both ipv4 or ipv6 + */ + if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) + len = 8; /* IP and GWIP are both ipv4 */ + else + len = 32; /* IP and GWIP are both ipv6 */ + /* Prefix contains RD, ESI, EthTag, IP length, IP, GWIP and VNI */ + stream_putc(s, 8 + 10 + 4 + 1 + len + 3); + stream_put(s, prd->val, 8); + if (attr && attr->evpn_overlay.type == OVERLAY_INDEX_ESI) + stream_put(s, &attr->esi, sizeof(esi_t)); + else + stream_put(s, 0, sizeof(esi_t)); + stream_putl(s, p_evpn_p->prefix_addr.eth_tag); + stream_putc(s, p_evpn_p->prefix_addr.ip_prefix_length); + if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) + stream_put_ipv4(s, p_evpn_p->prefix_addr.ip.ipaddr_v4.s_addr); + else + stream_put(s, &p_evpn_p->prefix_addr.ip.ipaddr_v6, 16); + if (attr && attr->evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) { + const struct bgp_route_evpn *evpn_overlay = + bgp_attr_get_evpn_overlay(attr); + + if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) + stream_put_ipv4(s, + evpn_overlay->gw_ip.ipaddr_v4.s_addr); + else + stream_put(s, &(evpn_overlay->gw_ip.ipaddr_v6), 16); + } else { + if (IS_IPADDR_V4(&p_evpn_p->prefix_addr.ip)) + stream_put_ipv4(s, 0); + else + stream_put(s, &temp, 16); + } + + if (num_labels) + stream_put(s, label, 3); + else + stream_put3(s, 0); +} + +/* + * Cleanup specific VNI upon EVPN (advertise-all-vni) being disabled. + */ +static void cleanup_vni_on_disable(struct hash_bucket *bucket, struct bgp *bgp) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + + /* Remove EVPN routes and schedule for processing. */ + delete_routes_for_vni(bgp, vpn); + + /* Clear "live" flag and see if hash needs to be freed. */ + UNSET_FLAG(vpn->flags, VNI_FLAG_LIVE); + if (!is_vni_configured(vpn)) + bgp_evpn_free(bgp, vpn); +} + +/* + * Free a VNI entry; iterator function called during cleanup. + */ +static void free_vni_entry(struct hash_bucket *bucket, struct bgp *bgp) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + + delete_all_vni_routes(bgp, vpn); + bgp_evpn_free(bgp, vpn); +} + +/* + * Derive AUTO import RT for BGP VRF - L3VNI + */ +static void evpn_auto_rt_import_add_for_vrf(struct bgp *bgp_vrf) +{ + struct bgp *bgp_evpn = NULL; + + form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl, true); + + /* Map RT to VRF */ + bgp_evpn = bgp_get_evpn(); + + if (!bgp_evpn) + return; + + bgp_evpn_map_vrf_to_its_rts(bgp_vrf); +} + +/* + * Delete AUTO import RT from BGP VRF - L3VNI + */ +static void evpn_auto_rt_import_delete_for_vrf(struct bgp *bgp_vrf) +{ + evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_import_rtl, + true); +} + +/* + * Derive AUTO export RT for BGP VRF - L3VNI + */ +static void evpn_auto_rt_export_add_for_vrf(struct bgp *bgp_vrf) +{ + form_auto_rt(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl, true); +} + +/* + * Delete AUTO export RT from BGP VRF - L3VNI + */ +static void evpn_auto_rt_export_delete_for_vrf(struct bgp *bgp_vrf) +{ + evpn_rt_delete_auto(bgp_vrf, bgp_vrf->l3vni, bgp_vrf->vrf_export_rtl, + true); +} + +static void bgp_evpn_handle_export_rt_change_for_vrf(struct bgp *bgp_vrf) +{ + struct bgp *bgp_evpn = NULL; + struct listnode *node = NULL; + struct bgpevpn *vpn = NULL; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return; + + /* update all type-5 routes */ + update_advertise_vrf_routes(bgp_vrf); + + /* update all type-2 routes */ + for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) + update_routes_for_vni(bgp_evpn, vpn); +} + +/* + * Handle autort change for a given VNI. + */ +static void update_autort_vni(struct hash_bucket *bucket, struct bgp *bgp) +{ + struct bgpevpn *vpn = bucket->data; + + if (!is_import_rt_configured(vpn)) { + if (is_vni_live(vpn)) + bgp_evpn_uninstall_routes(bgp, vpn); + bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); + list_delete_all_node(vpn->import_rtl); + bgp_evpn_derive_auto_rt_import(bgp, vpn); + if (is_vni_live(vpn)) + bgp_evpn_install_routes(bgp, vpn); + } + if (!is_export_rt_configured(vpn)) { + list_delete_all_node(vpn->export_rtl); + bgp_evpn_derive_auto_rt_export(bgp, vpn); + if (is_vni_live(vpn)) + bgp_evpn_handle_export_rt_change(bgp, vpn); + } +} + +/* + * Handle autort change for L3VNI. + */ +static void update_autort_l3vni(struct bgp *bgp) +{ + if ((CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) + && (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD))) + return; + + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) { + if (is_l3vni_live(bgp)) + uninstall_routes_for_vrf(bgp); + + /* Cleanup the RT to VRF mapping */ + bgp_evpn_unmap_vrf_from_its_rts(bgp); + + /* Remove auto generated RT */ + evpn_auto_rt_import_delete_for_vrf(bgp); + + list_delete_all_node(bgp->vrf_import_rtl); + + /* Map auto derive or configured RTs */ + evpn_auto_rt_import_add_for_vrf(bgp); + } + + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { + list_delete_all_node(bgp->vrf_export_rtl); + + evpn_auto_rt_export_delete_for_vrf(bgp); + + evpn_auto_rt_export_add_for_vrf(bgp); + + if (is_l3vni_live(bgp)) + bgp_evpn_map_vrf_to_its_rts(bgp); + } + + if (!is_l3vni_live(bgp)) + return; + + /* advertise type-5 routes if needed */ + update_advertise_vrf_routes(bgp); + + /* install all remote routes belonging to this l3vni + * into corresponding vrf + */ + install_routes_for_vrf(bgp); +} + +/* + * Public functions. + */ + +/* withdraw type-5 route corresponding to ip prefix */ +void bgp_evpn_withdraw_type5_route(struct bgp *bgp_vrf, const struct prefix *p, + afi_t afi, safi_t safi) +{ + int ret = 0; + struct prefix_evpn evp; + + build_type5_prefix_from_ip_prefix(&evp, p); + ret = delete_evpn_type5_route(bgp_vrf, &evp); + if (ret) + flog_err( + EC_BGP_EVPN_ROUTE_DELETE, + "%u failed to delete type-5 route for prefix %pFX in vrf %s", + bgp_vrf->vrf_id, p, vrf_id_to_name(bgp_vrf->vrf_id)); +} + +/* withdraw all type-5 routes for an address family */ +void bgp_evpn_withdraw_type5_routes(struct bgp *bgp_vrf, afi_t afi, safi_t safi) +{ + struct bgp_table *table = NULL; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi; + + table = bgp_vrf->rib[afi][safi]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + /* Only care about "selected" routes. Also ensure that + * these are routes that are injectable into EVPN. + */ + /* TODO: Support for AddPath for EVPN. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) + && is_route_injectable_into_evpn(pi)) { + bgp_evpn_withdraw_type5_route( + bgp_vrf, bgp_dest_get_prefix(dest), afi, + safi); + break; + } + } + } +} + +/* + * evpn - enable advertisement of default g/w + */ +void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf, afi_t afi, + safi_t safi, bool add) +{ + struct prefix ip_prefix; + + /* form the default prefix 0.0.0.0/0 */ + memset(&ip_prefix, 0, sizeof(ip_prefix)); + ip_prefix.family = afi2family(afi); + + if (add) { + bgp_evpn_advertise_type5_route(bgp_vrf, &ip_prefix, + NULL, afi, safi); + } else { + bgp_evpn_withdraw_type5_route(bgp_vrf, &ip_prefix, + afi, safi); + } +} + + +/* + * Advertise IP prefix as type-5 route. The afi/safi and src_attr passed + * to this function correspond to those of the source IP prefix (best + * path in the case of the attr. In the case of a local prefix (when we + * are advertising local subnets), the src_attr will be NULL. + */ +void bgp_evpn_advertise_type5_route(struct bgp *bgp_vrf, const struct prefix *p, + struct attr *src_attr, afi_t afi, + safi_t safi) +{ + int ret = 0; + struct prefix_evpn evp; + + build_type5_prefix_from_ip_prefix(&evp, p); + ret = update_evpn_type5_route(bgp_vrf, &evp, src_attr, afi, safi); + if (ret) + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "%u: Failed to create type-5 route for prefix %pFX", + bgp_vrf->vrf_id, p); +} + +/* Inject all prefixes of a particular address-family (currently, IPv4 or + * IPv6 unicast) into EVPN as type-5 routes. This is invoked when the + * advertisement is enabled. + */ +void bgp_evpn_advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi, + safi_t safi) +{ + struct bgp_table *table = NULL; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi; + + table = bgp_vrf->rib[afi][safi]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + /* Need to identify the "selected" route entry to use its + * attribute. Also, ensure that the route is injectable + * into EVPN. + * TODO: Support for AddPath for EVPN. + */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) + && is_route_injectable_into_evpn(pi)) { + + /* apply the route-map */ + if (bgp_vrf->adv_cmd_rmap[afi][safi].map) { + route_map_result_t ret; + struct bgp_path_info tmp_pi; + struct bgp_path_info_extra tmp_pie; + struct attr tmp_attr; + + tmp_attr = *pi->attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&tmp_pi, &tmp_pie, + dest, pi, pi->peer, + &tmp_attr); + + RESET_FLAG(tmp_attr.rmap_change_flags); + + ret = route_map_apply( + bgp_vrf->adv_cmd_rmap[afi][safi] + .map, + bgp_dest_get_prefix(dest), + &tmp_pi); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&tmp_attr); + continue; + } + bgp_evpn_advertise_type5_route( + bgp_vrf, + bgp_dest_get_prefix(dest), + &tmp_attr, afi, safi); + } else + bgp_evpn_advertise_type5_route( + bgp_vrf, + bgp_dest_get_prefix(dest), + pi->attr, afi, safi); + break; + } + } + } +} + +static void rt_list_remove_node(struct list *rt_list, + struct ecommunity *ecomdel, bool is_l3) +{ + struct listnode *node = NULL, *nnode = NULL, *node_to_del = NULL; + struct vrf_route_target *l3rt = NULL; + struct ecommunity *ecom = NULL; + + if (is_l3) { + for (ALL_LIST_ELEMENTS(rt_list, node, nnode, l3rt)) { + if (ecommunity_match(l3rt->ecom, ecomdel)) { + evpn_vrf_rt_del(l3rt); + node_to_del = node; + break; + } + } + } else { + for (ALL_LIST_ELEMENTS(rt_list, node, nnode, ecom)) { + if (ecommunity_match(ecom, ecomdel)) { + ecommunity_free(&ecom); + node_to_del = node; + break; + } + } + } + + + if (node_to_del) + list_delete_node(rt_list, node_to_del); +} + +void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl, + bool is_l3) +{ + struct ecommunity *ecom_auto; + struct ecommunity_val eval; + + if (bgp->advertise_autort_rfc8365) + vni |= EVPN_AUTORT_VXLAN; + + encode_route_target_as((bgp->as & 0xFFFF), vni, &eval, true); + + ecom_auto = ecommunity_new(); + ecommunity_add_val(ecom_auto, &eval, false, false); + + rt_list_remove_node(rtl, ecom_auto, is_l3); + + ecommunity_free(&ecom_auto); +} + +static void evpn_vrf_rt_routes_map(struct bgp *bgp_vrf) +{ + /* map VRFs to its RTs and install routes matching this new RT */ + if (is_l3vni_live(bgp_vrf)) { + bgp_evpn_map_vrf_to_its_rts(bgp_vrf); + install_routes_for_vrf(bgp_vrf); + } +} + +static void evpn_vrf_rt_routes_unmap(struct bgp *bgp_vrf) +{ + /* uninstall routes from vrf */ + if (is_l3vni_live(bgp_vrf)) + uninstall_routes_for_vrf(bgp_vrf); + + /* Cleanup the RT to VRF mapping */ + bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); +} + +static bool rt_list_has_cfgd_rt(struct list *rt_list) +{ + struct listnode *node = NULL, *nnode = NULL; + struct vrf_route_target *l3rt = NULL; + + for (ALL_LIST_ELEMENTS(rt_list, node, nnode, l3rt)) { + if (!CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) + return true; + } + + return false; +} + +static void unconfigure_import_rt_for_vrf_fini(struct bgp *bgp_vrf) +{ + if (!bgp_vrf->vrf_import_rtl) + return; /* this should never fail */ + + if (!is_l3vni_live(bgp_vrf)) + return; /* Nothing to do if no vni */ + + /* fall back to auto-generated RT if this was the last RT */ + if (list_isempty(bgp_vrf->vrf_import_rtl)) + evpn_auto_rt_import_add_for_vrf(bgp_vrf); +} + +static void unconfigure_export_rt_for_vrf_fini(struct bgp *bgp_vrf) +{ + + if (!bgp_vrf->vrf_export_rtl) + return; /* this should never fail */ + + if (!is_l3vni_live(bgp_vrf)) + return; /* Nothing to do if no vni */ + + /* fall back to auto-generated RT if this was the last RT */ + if (list_isempty(bgp_vrf->vrf_export_rtl)) + evpn_auto_rt_export_add_for_vrf(bgp_vrf); + + bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +} + +void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomadd, + bool is_wildcard) +{ + struct vrf_route_target *newrt; + + newrt = evpn_vrf_rt_new(ecomadd); + + if (is_wildcard) + SET_FLAG(newrt->flags, BGP_VRF_RT_WILD); + + evpn_vrf_rt_routes_unmap(bgp_vrf); + + /* Remove auto generated RT if not configured */ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) + evpn_auto_rt_import_delete_for_vrf(bgp_vrf); + + /* Add the newly configured RT to RT list */ + listnode_add_sort(bgp_vrf->vrf_import_rtl, newrt); + + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); + + evpn_vrf_rt_routes_map(bgp_vrf); +} + +void bgp_evpn_configure_import_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ + if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) + return; /* Already configured */ + + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD); + + if (!is_l3vni_live(bgp_vrf)) + return; /* Wait for VNI before adding rts */ + + evpn_vrf_rt_routes_unmap(bgp_vrf); + + evpn_auto_rt_import_add_for_vrf(bgp_vrf); + + evpn_vrf_rt_routes_map(bgp_vrf); +} + +void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomdel) +{ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) + return; /* Already un-configured */ + + evpn_vrf_rt_routes_unmap(bgp_vrf); + + /* Remove rt */ + rt_list_remove_node(bgp_vrf->vrf_import_rtl, ecomdel, true); + + if (!rt_list_has_cfgd_rt(bgp_vrf->vrf_import_rtl)) + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD); + + unconfigure_import_rt_for_vrf_fini(bgp_vrf); + + evpn_vrf_rt_routes_map(bgp_vrf); +} + +void bgp_evpn_unconfigure_import_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) + return; /* Already un-configured */ + + evpn_vrf_rt_routes_unmap(bgp_vrf); + + /* remove auto-generated RT */ + evpn_auto_rt_import_delete_for_vrf(bgp_vrf); + + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD); + + unconfigure_import_rt_for_vrf_fini(bgp_vrf); + + evpn_vrf_rt_routes_map(bgp_vrf); +} + +void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomadd) +{ + struct vrf_route_target *newrt; + + newrt = evpn_vrf_rt_new(ecomadd); + + /* Remove auto generated RT if not configured */ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) + evpn_auto_rt_export_delete_for_vrf(bgp_vrf); + + /* Add the new RT to the RT list */ + listnode_add_sort(bgp_vrf->vrf_export_rtl, newrt); + + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); + + if (is_l3vni_live(bgp_vrf)) + bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +} + +void bgp_evpn_configure_export_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ + if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) + return; /* Already configured */ + + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD); + + if (!is_l3vni_live(bgp_vrf)) + return; /* Wait for VNI before adding rts */ + + evpn_auto_rt_export_add_for_vrf(bgp_vrf); + + bgp_evpn_handle_export_rt_change_for_vrf(bgp_vrf); +} + +void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomdel) +{ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) + return; /* Already un-configured */ + + /* Remove rt */ + rt_list_remove_node(bgp_vrf->vrf_export_rtl, ecomdel, true); + + if (!rt_list_has_cfgd_rt(bgp_vrf->vrf_export_rtl)) + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD); + + unconfigure_export_rt_for_vrf_fini(bgp_vrf); +} + +void bgp_evpn_unconfigure_export_auto_rt_for_vrf(struct bgp *bgp_vrf) +{ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) + return; /* Already un-configured */ + + /* remove auto-generated RT */ + evpn_auto_rt_export_delete_for_vrf(bgp_vrf); + + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD); + + unconfigure_export_rt_for_vrf_fini(bgp_vrf); +} + +/* + * Handle change to BGP router id. This is invoked twice by the change + * handler, first before the router id has been changed and then after + * the router id has been changed. The first invocation will result in + * local routes for all VNIs/VRF being deleted and withdrawn and the next + * will result in the routes being re-advertised. + */ +void bgp_evpn_handle_router_id_update(struct bgp *bgp, int withdraw) +{ + struct listnode *node; + struct bgp *bgp_vrf; + + if (withdraw) { + + /* delete and withdraw all the type-5 routes + stored in the global table for this vrf + */ + withdraw_router_id_vrf(bgp); + + /* delete all the VNI routes (type-2/type-3) routes for all the + * L2-VNIs + */ + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void *))withdraw_router_id_vni, + bgp); + + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->evpn_info->advertise_pip && + (bgp_vrf->evpn_info->pip_ip_static.s_addr + == INADDR_ANY)) + bgp_vrf->evpn_info->pip_ip.s_addr + = INADDR_ANY; + } + } + } else { + + /* Assign new default instance router-id */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->evpn_info->advertise_pip && + (bgp_vrf->evpn_info->pip_ip_static.s_addr + == INADDR_ANY)) { + bgp_vrf->evpn_info->pip_ip = + bgp->router_id; + /* advertise type-5 routes with + * new nexthop + */ + update_advertise_vrf_routes(bgp_vrf); + } + } + } + + /* advertise all routes in the vrf as type-5 routes with the new + * RD + */ + update_router_id_vrf(bgp); + + /* advertise all the VNI routes (type-2/type-3) routes with the + * new RD + */ + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void *))update_router_id_vni, + bgp); + } +} + +/* + * Handle change to auto-RT algorithm - update and advertise local routes. + */ +void bgp_evpn_handle_autort_change(struct bgp *bgp) +{ + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void*))update_autort_vni, + bgp); + if (bgp->l3vni) + update_autort_l3vni(bgp); +} + +/* + * Handle change to export RT - update and advertise local routes. + */ +int bgp_evpn_handle_export_rt_change(struct bgp *bgp, struct bgpevpn *vpn) +{ + return update_routes_for_vni(bgp, vpn); +} + +void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw) +{ + if (withdraw) + delete_withdraw_vrf_routes(bgp_vrf); + else + update_advertise_vrf_routes(bgp_vrf); +} + +/* + * Handle change to RD. This is invoked twice by the change handler, + * first before the RD has been changed and then after the RD has + * been changed. The first invocation will result in local routes + * of this VNI being deleted and withdrawn and the next will result + * in the routes being re-advertised. + */ +void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn, + int withdraw) +{ + if (withdraw) + delete_withdraw_vni_routes(bgp, vpn); + else + update_advertise_vni_routes(bgp, vpn); +} + +/* "mac-vrf soo" vty handler + * Handle change to the global MAC-VRF Site-of-Origin: + * - Unimport routes with new SoO from VNI/VRF + * - Import routes with old SoO into VNI/VRF + * - Update SoO on local VNI routes + re-advertise + */ +void bgp_evpn_handle_global_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *new_soo) +{ + struct ecommunity *old_soo; + + old_soo = bgp->evpn_info->soo; + + /* cleanup and bail out if old_soo == new_soo */ + if (ecommunity_match(old_soo, new_soo)) { + ecommunity_free(&new_soo); + return; + } + + /* set new_soo */ + bgp->evpn_info->soo = new_soo; + + /* Unimport routes matching the new_soo */ + bgp_filter_evpn_routes_upon_martian_change(bgp, BGP_MARTIAN_SOO); + + /* Reimport routes with old_soo and !new_soo. + */ + bgp_reimport_evpn_routes_upon_martian_change( + bgp, BGP_MARTIAN_SOO, (void *)old_soo, (void *)new_soo); + + /* Update locally originated routes for all L2VNIs */ + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void *))update_routes_for_vni_hash, + bgp); + + /* clear old_soo */ + ecommunity_free(&old_soo); +} + +/* + * Install routes for this VNI. Invoked upon change to Import RT. + */ +int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + return install_routes_for_vni(bgp, vpn); +} + +/* + * Uninstall all routes installed for this VNI. Invoked upon change + * to Import RT. + */ +int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + return uninstall_routes_for_vni(bgp, vpn); +} + +/* + * TODO: Hardcoded for a maximum of 2 VNIs right now + */ +char *bgp_evpn_label2str(mpls_label_t *label, uint8_t num_labels, char *buf, + int len) +{ + vni_t vni1, vni2; + + vni1 = label2vni(label); + if (num_labels == 2) { + vni2 = label2vni(label + 1); + snprintf(buf, len, "%u/%u", vni1, vni2); + } else + snprintf(buf, len, "%u", vni1); + return buf; +} + +/* + * Function to convert evpn route to json format. + * NOTE: We don't use prefix2str as the output here is a bit different. + */ +void bgp_evpn_route2json(const struct prefix_evpn *p, json_object *json) +{ + char buf1[ETHER_ADDR_STRLEN]; + char buf2[PREFIX2STR_BUFFER]; + uint8_t family; + uint8_t prefixlen; + + if (!json) + return; + + json_object_int_add(json, "routeType", p->prefix.route_type); + + switch (p->prefix.route_type) { + case BGP_EVPN_MAC_IP_ROUTE: + json_object_int_add(json, "ethTag", + p->prefix.macip_addr.eth_tag); + json_object_int_add(json, "macLen", 8 * ETH_ALEN); + json_object_string_add(json, "mac", + prefix_mac2str(&p->prefix.macip_addr.mac, buf1, + sizeof(buf1))); + + if (!is_evpn_prefix_ipaddr_none(p)) { + family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : + AF_INET6; + prefixlen = (family == AF_INET) ? + IPV4_MAX_BITLEN : IPV6_MAX_BITLEN; + inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr, + buf2, PREFIX2STR_BUFFER); + json_object_int_add(json, "ipLen", prefixlen); + json_object_string_add(json, "ip", buf2); + } + break; + + case BGP_EVPN_IMET_ROUTE: + json_object_int_add(json, "ethTag", + p->prefix.imet_addr.eth_tag); + family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; + prefixlen = (family == AF_INET) ? IPV4_MAX_BITLEN : + IPV6_MAX_BITLEN; + inet_ntop(family, &p->prefix.imet_addr.ip.ip.addr, buf2, + PREFIX2STR_BUFFER); + json_object_int_add(json, "ipLen", prefixlen); + json_object_string_add(json, "ip", buf2); + break; + + case BGP_EVPN_IP_PREFIX_ROUTE: + json_object_int_add(json, "ethTag", + p->prefix.prefix_addr.eth_tag); + family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; + inet_ntop(family, &p->prefix.prefix_addr.ip.ip.addr, + buf2, sizeof(buf2)); + json_object_int_add(json, "ipLen", + p->prefix.prefix_addr.ip_prefix_length); + json_object_string_add(json, "ip", buf2); + break; + + default: + break; + } +} + +/* + * Encode EVPN prefix in Update (MP_REACH) + */ +void bgp_evpn_encode_prefix(struct stream *s, const struct prefix *p, + const struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, struct attr *attr, + bool addpath_capable, uint32_t addpath_tx_id) +{ + struct prefix_evpn *evp = (struct prefix_evpn *)p; + int len, ipa_len = 0; + + if (addpath_capable) + stream_putl(s, addpath_tx_id); + + /* Route type */ + stream_putc(s, evp->prefix.route_type); + + switch (evp->prefix.route_type) { + case BGP_EVPN_MAC_IP_ROUTE: + if (is_evpn_prefix_ipaddr_v4(evp)) + ipa_len = IPV4_MAX_BYTELEN; + else if (is_evpn_prefix_ipaddr_v6(evp)) + ipa_len = IPV6_MAX_BYTELEN; + /* RD, ESI, EthTag, MAC+len, IP len, [IP], 1 VNI */ + len = 8 + 10 + 4 + 1 + 6 + 1 + ipa_len + 3; + if (ipa_len && num_labels > 1) /* There are 2 VNIs */ + len += 3; + stream_putc(s, len); + stream_put(s, prd->val, 8); /* RD */ + if (attr) + stream_put(s, &attr->esi, ESI_BYTES); + else + stream_put(s, 0, 10); + stream_putl(s, evp->prefix.macip_addr.eth_tag); /* Ethernet Tag ID */ + stream_putc(s, 8 * ETH_ALEN); /* Mac Addr Len - bits */ + stream_put(s, evp->prefix.macip_addr.mac.octet, 6); /* Mac Addr */ + stream_putc(s, 8 * ipa_len); /* IP address Length */ + if (ipa_len) /* IP */ + stream_put(s, &evp->prefix.macip_addr.ip.ip.addr, + ipa_len); + /* 1st label is the L2 VNI */ + stream_put(s, label, BGP_LABEL_BYTES); + /* Include 2nd label (L3 VNI) if advertising MAC+IP */ + if (ipa_len && num_labels > 1) + stream_put(s, label + 1, BGP_LABEL_BYTES); + break; + + case BGP_EVPN_IMET_ROUTE: + stream_putc(s, 17); // TODO: length - assumes IPv4 address + stream_put(s, prd->val, 8); /* RD */ + stream_putl(s, evp->prefix.imet_addr.eth_tag); /* Ethernet Tag ID */ + stream_putc(s, IPV4_MAX_BITLEN); /* IP address Length - bits */ + /* Originating Router's IP Addr */ + stream_put_in_addr(s, &evp->prefix.imet_addr.ip.ipaddr_v4); + break; + + case BGP_EVPN_ES_ROUTE: + stream_putc(s, 23); /* TODO: length: assumes ipv4 VTEP */ + stream_put(s, prd->val, 8); /* RD */ + stream_put(s, evp->prefix.es_addr.esi.val, 10); /* ESI */ + stream_putc(s, IPV4_MAX_BITLEN); /* IP address Length - bits */ + /* VTEP IP */ + stream_put_in_addr(s, &evp->prefix.es_addr.ip.ipaddr_v4); + break; + + case BGP_EVPN_AD_ROUTE: + /* RD, ESI, EthTag, 1 VNI */ + len = RD_BYTES + ESI_BYTES + EVPN_ETH_TAG_BYTES + BGP_LABEL_BYTES; + stream_putc(s, len); + stream_put(s, prd->val, RD_BYTES); /* RD */ + stream_put(s, evp->prefix.ead_addr.esi.val, ESI_BYTES); /* ESI */ + stream_putl(s, evp->prefix.ead_addr.eth_tag); /* Ethernet Tag */ + stream_put(s, label, BGP_LABEL_BYTES); + break; + + case BGP_EVPN_IP_PREFIX_ROUTE: + /* TODO: AddPath support. */ + evpn_mpattr_encode_type5(s, p, prd, label, num_labels, attr); + break; + + default: + break; + } +} + +int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, bool withdraw) +{ + uint8_t *pnt; + uint8_t *lim; + afi_t afi; + safi_t safi; + uint32_t addpath_id; + bool addpath_capable; + int psize = 0; + uint8_t rtype; + struct prefix p; + + /* Start processing the NLRI - there may be multiple in the MP_REACH */ + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + + for (; pnt < lim; pnt += psize) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + /* Deal with path-id if AddPath is supported. */ + if (addpath_capable) { + /* When packet overflow occurs return immediately. */ + if (pnt + BGP_ADDPATH_ID_LEN > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + pnt += BGP_ADDPATH_ID_LEN; + } + + /* All EVPN NLRI types start with type and length. */ + if (pnt + 2 > lim) + return BGP_NLRI_PARSE_ERROR_EVPN_MISSING_TYPE; + + rtype = *pnt++; + psize = *pnt++; + + /* When packet overflow occur return immediately. */ + if (pnt + psize > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + switch (rtype) { + case BGP_EVPN_MAC_IP_ROUTE: + if (process_type2_route(peer, afi, safi, + withdraw ? NULL : attr, pnt, + psize, addpath_id)) { + flog_err( + EC_BGP_EVPN_FAIL, + "%u:%s - Error in processing EVPN type-2 NLRI size %d", + peer->bgp->vrf_id, peer->host, psize); + return BGP_NLRI_PARSE_ERROR_EVPN_TYPE2_SIZE; + } + break; + + case BGP_EVPN_IMET_ROUTE: + if (process_type3_route(peer, afi, safi, + withdraw ? NULL : attr, pnt, + psize, addpath_id)) { + flog_err( + EC_BGP_PKT_PROCESS, + "%u:%s - Error in processing EVPN type-3 NLRI size %d", + peer->bgp->vrf_id, peer->host, psize); + return BGP_NLRI_PARSE_ERROR_EVPN_TYPE3_SIZE; + } + break; + + case BGP_EVPN_ES_ROUTE: + if (bgp_evpn_type4_route_process(peer, afi, safi, + withdraw ? NULL : attr, pnt, + psize, addpath_id)) { + flog_err( + EC_BGP_PKT_PROCESS, + "%u:%s - Error in processing EVPN type-4 NLRI size %d", + peer->bgp->vrf_id, peer->host, psize); + return BGP_NLRI_PARSE_ERROR_EVPN_TYPE4_SIZE; + } + break; + + case BGP_EVPN_AD_ROUTE: + if (bgp_evpn_type1_route_process(peer, afi, safi, + withdraw ? NULL : attr, pnt, + psize, addpath_id)) { + flog_err( + EC_BGP_PKT_PROCESS, + "%u:%s - Error in processing EVPN type-1 NLRI size %d", + peer->bgp->vrf_id, peer->host, psize); + return BGP_NLRI_PARSE_ERROR_EVPN_TYPE1_SIZE; + } + break; + + case BGP_EVPN_IP_PREFIX_ROUTE: + if (process_type5_route(peer, afi, safi, + withdraw ? NULL : attr, pnt, + psize, addpath_id)) { + flog_err( + EC_BGP_PKT_PROCESS, + "%u:%s - Error in processing EVPN type-5 NLRI size %d", + peer->bgp->vrf_id, peer->host, psize); + return BGP_NLRI_PARSE_ERROR_EVPN_TYPE5_SIZE; + } + break; + + default: + break; + } + } + + /* Packet length consistency check. */ + if (pnt != lim) + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + + return BGP_NLRI_PARSE_OK; +} + +/* + * Map the RTs (configured or automatically derived) of a VRF to the VRF. + * The mapping will be used during route processing. + * bgp_vrf: specific bgp vrf instance on which RT is configured + */ +void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf) +{ + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + + for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) + map_vrf_to_rt(bgp_vrf, l3rt); +} + +/* + * Unmap the RTs (configured or automatically derived) of a VRF from the VRF. + */ +void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf) +{ + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + + for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) + unmap_vrf_from_rt(bgp_vrf, l3rt); +} + +/* + * Map the RTs (configured or automatically derived) of a VNI to the VNI. + * The mapping will be used during route processing. + */ +void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn) +{ + uint32_t i; + struct ecommunity_val *eval; + struct listnode *node, *nnode; + struct ecommunity *ecom; + + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + for (i = 0; i < ecom->size; i++) { + eval = (struct ecommunity_val *)(ecom->val + + (i + * ECOMMUNITY_SIZE)); + map_vni_to_rt(bgp, vpn, eval); + } + } +} + +/* + * Unmap the RTs (configured or automatically derived) of a VNI from the VNI. + */ +void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, struct bgpevpn *vpn) +{ + uint32_t i; + struct ecommunity_val *eval; + struct listnode *node, *nnode; + struct ecommunity *ecom; + + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + for (i = 0; i < ecom->size; i++) { + struct irt_node *irt; + struct ecommunity_val eval_tmp; + + eval = (struct ecommunity_val *)(ecom->val + + (i + * ECOMMUNITY_SIZE)); + /* If using "automatic" RT, we only care about the + * local-admin sub-field. + * This is to facilitate using VNI as the RT for EBGP + * peering too. + */ + memcpy(&eval_tmp, eval, ECOMMUNITY_SIZE); + if (!is_import_rt_configured(vpn)) + mask_ecom_global_admin(&eval_tmp, eval); + + irt = lookup_import_rt(bgp, &eval_tmp); + if (irt) + unmap_vni_from_rt(bgp, vpn, irt); + } + } +} + +/* + * Derive Import RT automatically for VNI and map VNI to RT. + * The mapping will be used during route processing. + */ +void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, struct bgpevpn *vpn) +{ + form_auto_rt(bgp, vpn->vni, vpn->import_rtl, false); + UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); + + /* Map RT to VNI */ + bgp_evpn_map_vni_to_its_rts(bgp, vpn); +} + +/* + * Derive Export RT automatically for VNI. + */ +void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, struct bgpevpn *vpn) +{ + form_auto_rt(bgp, vpn->vni, vpn->export_rtl, false); + UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); +} + +/* + * Derive RD automatically for VNI using passed information - it + * is of the form RouterId:unique-id-for-vni. + */ +void bgp_evpn_derive_auto_rd_for_vrf(struct bgp *bgp) +{ + if (is_vrf_rd_configured(bgp)) + return; + + form_auto_rd(bgp->router_id, bgp->vrf_rd_id, &bgp->vrf_prd); +} + +/* + * Derive RD automatically for VNI using passed information - it + * is of the form RouterId:unique-id-for-vni. + */ +void bgp_evpn_derive_auto_rd(struct bgp *bgp, struct bgpevpn *vpn) +{ + char buf[BGP_EVPN_PREFIX_RD_LEN]; + + vpn->prd.family = AF_UNSPEC; + vpn->prd.prefixlen = 64; + snprintfrr(buf, sizeof(buf), "%pI4:%hu", &bgp->router_id, vpn->rd_id); + (void)str2prefix_rd(buf, &vpn->prd); + if (vpn->prd_pretty) + XFREE(MTYPE_BGP_NAME, vpn->prd_pretty); + UNSET_FLAG(vpn->flags, VNI_FLAG_RD_CFGD); +} + +/* + * Lookup L3-VNI + */ +bool bgp_evpn_lookup_l3vni_l2vni_table(vni_t vni) +{ + struct list *inst = bm->bgp; + struct listnode *node; + struct bgp *bgp_vrf; + + for (ALL_LIST_ELEMENTS_RO(inst, node, bgp_vrf)) { + if (bgp_vrf->l3vni == vni) + return true; + } + + return false; +} + +/* + * Lookup VNI. + */ +struct bgpevpn *bgp_evpn_lookup_vni(struct bgp *bgp, vni_t vni) +{ + struct bgpevpn *vpn; + struct bgpevpn tmp; + + memset(&tmp, 0, sizeof(tmp)); + tmp.vni = vni; + vpn = hash_lookup(bgp->vnihash, &tmp); + return vpn; +} + +/* + * Create a new vpn - invoked upon configuration or zebra notification. + */ +struct bgpevpn *bgp_evpn_new(struct bgp *bgp, vni_t vni, + struct in_addr originator_ip, + vrf_id_t tenant_vrf_id, + struct in_addr mcast_grp, + ifindex_t svi_ifindex) +{ + struct bgpevpn *vpn; + + vpn = XCALLOC(MTYPE_BGP_EVPN, sizeof(struct bgpevpn)); + + /* Set values - RD and RT set to defaults. */ + vpn->vni = vni; + vpn->originator_ip = originator_ip; + vpn->tenant_vrf_id = tenant_vrf_id; + vpn->mcast_grp = mcast_grp; + vpn->svi_ifindex = svi_ifindex; + + /* Initialize route-target import and export lists */ + vpn->import_rtl = list_new(); + vpn->import_rtl->cmp = + (int (*)(void *, void *))bgp_evpn_route_target_cmp; + vpn->import_rtl->del = bgp_evpn_xxport_delete_ecomm; + vpn->export_rtl = list_new(); + vpn->export_rtl->cmp = + (int (*)(void *, void *))bgp_evpn_route_target_cmp; + vpn->export_rtl->del = bgp_evpn_xxport_delete_ecomm; + bf_assign_index(bm->rd_idspace, vpn->rd_id); + derive_rd_rt_for_vni(bgp, vpn); + + /* Initialize EVPN route tables. */ + vpn->ip_table = bgp_table_init(bgp, AFI_L2VPN, SAFI_EVPN); + vpn->mac_table = bgp_table_init(bgp, AFI_L2VPN, SAFI_EVPN); + + /* Add to hash */ + (void)hash_get(bgp->vnihash, vpn, hash_alloc_intern); + + bgp_evpn_remote_ip_hash_init(vpn); + bgp_evpn_link_to_vni_svi_hash(bgp, vpn); + + /* add to l2vni list on corresponding vrf */ + bgpevpn_link_to_l3vni(vpn); + + bgp_evpn_vni_es_init(vpn); + + QOBJ_REG(vpn, bgpevpn); + return vpn; +} + +/* + * Free a given VPN - called in multiple scenarios such as zebra + * notification, configuration being deleted, advertise-all-vni disabled etc. + * This just frees appropriate memory, caller should have taken other + * needed actions. + */ +void bgp_evpn_free(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct bgp_dest *dest = NULL; + struct bgp_dest *dest_next = NULL; + + for (dest = zebra_announce_first(&bm->zebra_announce_head); dest; + dest = dest_next) { + dest_next = zebra_announce_next(&bm->zebra_announce_head, dest); + if (dest->za_vpn == vpn) { + zebra_announce_del(&bm->zebra_announce_head, dest); + bgp_path_info_unlock(dest->za_bgp_pi); + bgp_dest_unlock_node(dest); + } + } + + bgp_evpn_remote_ip_hash_destroy(vpn); + bgp_evpn_vni_es_cleanup(vpn); + bgpevpn_unlink_from_l3vni(vpn); + bgp_table_unlock(vpn->ip_table); + bgp_table_unlock(vpn->mac_table); + bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); + list_delete(&vpn->import_rtl); + list_delete(&vpn->export_rtl); + bf_release_index(bm->rd_idspace, vpn->rd_id); + hash_release(bgp->vni_svi_hash, vpn); + hash_release(bgp->vnihash, vpn); + if (vpn->prd_pretty) + XFREE(MTYPE_BGP_NAME, vpn->prd_pretty); + QOBJ_UNREG(vpn); + XFREE(MTYPE_BGP_EVPN, vpn); +} + +static void hash_evpn_free(struct bgpevpn *vpn) +{ + XFREE(MTYPE_BGP_EVPN, vpn); +} + +/* + * Import evpn route from global table to VNI/VRF/ESI. + */ +int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi, + const struct prefix *p, struct bgp_path_info *pi) +{ + return install_uninstall_evpn_route(bgp, afi, safi, p, pi, 1); +} + +/* + * Unimport evpn route from VNI/VRF/ESI. + */ +int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi, + const struct prefix *p, struct bgp_path_info *pi) +{ + return install_uninstall_evpn_route(bgp, afi, safi, p, pi, 0); +} + +/* Refresh previously-discarded EVPN routes carrying "self" MAC-VRF SoO. + * Walk global EVPN rib + import remote routes with old_soo && !new_soo. + */ +void bgp_reimport_evpn_routes_upon_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *old_soo, + struct ecommunity *new_soo) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rd_dest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* EVPN routes are a 2-level table: outer=prefix_rd, inner=prefix_evpn. + * A remote route could have any RD, so we need to walk them all. + */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + table = bgp_dest_get_bgp_table_info(rd_dest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + const struct prefix *p; + struct prefix_evpn *evp; + + p = bgp_dest_get_prefix(dest); + evp = (struct prefix_evpn *)p; + + /* On export we only add MAC-VRF SoO to RT-2/3, so we + * can skip evaluation of other RTs. + */ + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE && + evp->prefix.route_type != BGP_EVPN_IMET_ROUTE) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + bool old_soo_fnd = false; + bool new_soo_fnd = false; + + /* Only consider routes learned from peers */ + if (!(pi->type == ZEBRA_ROUTE_BGP && + pi->sub_type == BGP_ROUTE_NORMAL)) + continue; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + continue; + + old_soo_fnd = route_matches_soo(pi, old_soo); + new_soo_fnd = route_matches_soo(pi, new_soo); + + if (old_soo_fnd && !new_soo_fnd) { + if (bgp_debug_update(pi->peer, p, NULL, + 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(pi->attr, + attr_str, BUFSIZ); + + zlog_debug( + "mac-vrf soo changed: evaluating reimport of prefix %pBD with attr %s", + dest, attr_str); + } + + bgp_evpn_import_route(bgp, afi, safi, p, + pi); + } + } + } + } +} + +/* Filter learned (!local) EVPN routes carrying "self" attributes. + * Walk the Global EVPN loc-rib unimporting martian routes from the appropriate + * L2VNIs (MAC-VRFs) / L3VNIs (IP-VRFs), and deleting them from the Global + * loc-rib when applicable (based on martian_type). + * This function is the handler for new martian entries, which is triggered by + * events occurring on the local system, + * e.g. + * - New VTEP-IP + * + bgp_zebra_process_local_vni + * + bgp_zebra_process_local_l3vni + * - New MAC-VRF Site-of-Origin + * + bgp_evpn_handle_global_macvrf_soo_change + * This will likely be extended in the future to cover these events too: + * - New Interface IP + * + bgp_interface_address_add + * - New Interface MAC + * + bgp_ifp_up + * + bgp_ifp_create + * - New RMAC + * + bgp_zebra_process_local_l3vni + */ +void bgp_filter_evpn_routes_upon_martian_change( + struct bgp *bgp, enum bgp_martian_type martian_type) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rd_dest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + struct ecommunity *macvrf_soo; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + macvrf_soo = bgp->evpn_info->soo; + + /* EVPN routes are a 2-level table: outer=prefix_rd, inner=prefix_evpn. + * A remote route could have any RD, so we need to walk them all. + */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + table = bgp_dest_get_bgp_table_info(rd_dest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + struct bgp_path_info *next; + + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (next = pi->next, 1); pi = next) { + bool affected = false; + const struct prefix *p; + + /* Only consider routes learned from peers */ + if (!(pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_NORMAL)) + continue; + + p = bgp_dest_get_prefix(dest); + + switch (martian_type) { + case BGP_MARTIAN_TUN_IP: + affected = bgp_nexthop_self( + bgp, afi, pi->type, + pi->sub_type, pi->attr, dest); + break; + case BGP_MARTIAN_SOO: + affected = route_matches_soo( + pi, macvrf_soo); + break; + case BGP_MARTIAN_IF_IP: + case BGP_MARTIAN_IF_MAC: + case BGP_MARTIAN_RMAC: + break; + } + + if (affected) { + if (bgp_debug_update(pi->peer, p, NULL, + 1)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(pi->attr, + attr_str, + sizeof(attr_str)); + + zlog_debug( + "%u: prefix %pBD with attr %s - DISCARDED due to Martian/%s", + bgp->vrf_id, dest, + attr_str, + bgp_martian_type2str( + martian_type)); + } + + + bgp_evpn_unimport_route(bgp, afi, safi, + p, pi); + + /* For now, retain existing handling of + * tip_hash updates: (Self SoO routes + * are unimported from L2VNI/VRF but + * retained in global loc-rib, but Self + * IP/MAC routes are also deleted from + * global loc-rib). + * TODO: use consistent handling for all + * martian types + */ + if (martian_type == BGP_MARTIAN_TUN_IP) + bgp_rib_remove(dest, pi, + pi->peer, afi, + safi); + } + } + } + } +} + +/* Refresh previously-discarded EVPN routes carrying "self" attributes. + * This function is the handler for deleted martian entries, which is triggered + * by events occurring on the local system, + * e.g. + * - Del MAC-VRF Site-of-Origin + * + bgp_evpn_handle_global_macvrf_soo_change + * This will likely be extended in the future to cover these events too: + * - Del VTEP-IP + * + bgp_zebra_process_local_vni + * + bgp_zebra_process_local_l3vni + * - Del Interface IP + * + bgp_interface_address_delete + * - Del Interface MAC + * + bgp_ifp_down + * + bgp_ifp_destroy + * - Del RMAC + * + bgp_zebra_process_local_l3vni + */ +void bgp_reimport_evpn_routes_upon_martian_change( + struct bgp *bgp, enum bgp_martian_type martian_type, void *old_martian, + void *new_martian) +{ + struct listnode *node; + struct peer *peer; + safi_t safi; + afi_t afi; + struct ecommunity *old_soo, *new_soo; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Self-SoO routes are held in the global EVPN loc-rib, so we can + * reimport routes w/o triggering soft-reconfig/route-refresh. + */ + if (martian_type == BGP_MARTIAN_SOO) { + old_soo = (struct ecommunity *)old_martian; + new_soo = (struct ecommunity *)new_martian; + + /* If !old_soo, then we can skip the reimport because we + * wouldn't have filtered anything via the self-SoO import check + */ + if (old_martian) + bgp_reimport_evpn_routes_upon_macvrf_soo_change( + bgp, old_soo, new_soo); + + return; + } + + /* Self-TIP/IP/MAC/RMAC routes are deleted from the global EVPN + * loc-rib, so we need to re-learn the routes via soft-reconfig/ + * route-refresh. + */ + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + continue; + + if (peer->connection->status != Established) + continue; + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SOFT_RECONFIG)) { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "Processing EVPN Martian/%s change on peer %s (inbound, soft-reconfig)", + bgp_martian_type2str(martian_type), + peer->host); + + bgp_soft_reconfig_in(peer, afi, safi); + } else { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "Processing EVPN Martian/%s change on peer %s", + bgp_martian_type2str(martian_type), + peer->host); + bgp_route_refresh_send(peer, afi, safi, 0, + REFRESH_IMMEDIATE, 0, + BGP_ROUTE_REFRESH_NORMAL); + } + } +} + +/* + * Handle del of a local MACIP. + */ +int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni, struct ethaddr *mac, + struct ipaddr *ip, int state) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + struct bgp_dest *dest; + + /* Lookup VNI hash - should exist. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn || !is_vni_live(vpn)) { + flog_warn(EC_BGP_EVPN_VPN_VNI, + "%u: VNI hash entry for VNI %u %s at MACIP DEL", + bgp->vrf_id, vni, vpn ? "not live" : "not found"); + return -1; + } + + build_evpn_type2_prefix(&p, mac, ip); + if (state == ZEBRA_NEIGH_ACTIVE) { + /* Remove EVPN type-2 route and schedule for processing. */ + delete_evpn_route(bgp, vpn, &p); + } else { + /* Re-instate the current remote best path if any */ + dest = bgp_evpn_vni_node_lookup(vpn, &p, NULL); + if (dest) { + evpn_zebra_reinstall_best_route(bgp, vpn, dest); + bgp_dest_unlock_node(dest); + } + } + + return 0; +} + +/* + * Handle add of a local MACIP. + */ +int bgp_evpn_local_macip_add(struct bgp *bgp, vni_t vni, struct ethaddr *mac, + struct ipaddr *ip, uint8_t flags, uint32_t seq, esi_t *esi) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + + /* Lookup VNI hash - should exist. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn || !is_vni_live(vpn)) { + flog_warn(EC_BGP_EVPN_VPN_VNI, + "%u: VNI hash entry for VNI %u %s at MACIP ADD", + bgp->vrf_id, vni, vpn ? "not live" : "not found"); + return -1; + } + + /* Create EVPN type-2 route and schedule for processing. */ + build_evpn_type2_prefix(&p, mac, ip); + if (update_evpn_route(bgp, vpn, &p, flags, seq, esi)) { + flog_err( + EC_BGP_EVPN_ROUTE_CREATE, + "%u:Failed to create Type-2 route, VNI %u %s MAC %pEA IP %pIA (flags: 0x%x)", + bgp->vrf_id, vpn->vni, + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) + ? "sticky gateway" + : "", + mac, ip, flags); + return -1; + } + + return 0; +} + +static void link_l2vni_hash_to_l3vni(struct hash_bucket *bucket, + struct bgp *bgp_vrf) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct bgp *bgp_evpn = NULL; + + bgp_evpn = bgp_get_evpn(); + assert(bgp_evpn); + + if (vpn->tenant_vrf_id == bgp_vrf->vrf_id) + bgpevpn_link_to_l3vni(vpn); +} + +int bgp_evpn_local_l3vni_add(vni_t l3vni, vrf_id_t vrf_id, + struct ethaddr *svi_rmac, + struct ethaddr *vrr_rmac, + struct in_addr originator_ip, int filter, + ifindex_t svi_ifindex, + bool is_anycast_mac) +{ + struct bgp *bgp_vrf = NULL; /* bgp VRF instance */ + struct bgp *bgp_evpn = NULL; /* EVPN bgp instance */ + struct listnode *node = NULL; + struct bgpevpn *vpn = NULL; + as_t as = 0; + + /* get the EVPN instance - required to get the AS number for VRF + * auto-creatio + */ + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) { + flog_err( + EC_BGP_NO_DFLT, + "Cannot process L3VNI %u ADD - EVPN BGP instance not yet created", + l3vni); + return -1; + } + + if (CHECK_FLAG(bgp_evpn->flags, BGP_FLAG_DELETE_IN_PROGRESS)) { + flog_err(EC_BGP_NO_DFLT, + "Cannot process L3VNI %u ADD - EVPN BGP instance is shutting down", + l3vni); + return -1; + } + + as = bgp_evpn->as; + + /* if the BGP vrf instance doesn't exist - create one */ + bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp_vrf) { + + int ret = 0; + + ret = bgp_get_vty(&bgp_vrf, &as, vrf_id_to_name(vrf_id), + vrf_id == VRF_DEFAULT + ? BGP_INSTANCE_TYPE_DEFAULT + : BGP_INSTANCE_TYPE_VRF, + NULL, ASNOTATION_UNDEFINED); + switch (ret) { + case BGP_ERR_AS_MISMATCH: + flog_err(EC_BGP_EVPN_AS_MISMATCH, + "BGP instance is already running; AS is %s", + bgp_vrf->as_pretty); + return -1; + case BGP_ERR_INSTANCE_MISMATCH: + flog_err(EC_BGP_EVPN_INSTANCE_MISMATCH, + "BGP instance type mismatch"); + return -1; + } + + /* mark as auto created */ + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_AUTO); + } + + /* associate the vrf with l3vni and related parameters */ + bgp_vrf->l3vni = l3vni; + bgp_vrf->l3vni_svi_ifindex = svi_ifindex; + bgp_vrf->evpn_info->is_anycast_mac = is_anycast_mac; + + /* Update tip_hash of the EVPN underlay BGP instance (bgp_evpn) + * if the VTEP-IP (originator_ip) has changed + */ + handle_tunnel_ip_change(bgp_vrf, bgp_evpn, vpn, originator_ip); + + /* copy anycast MAC from VRR MAC */ + memcpy(&bgp_vrf->rmac, vrr_rmac, ETH_ALEN); + /* copy sys RMAC from SVI MAC */ + memcpy(&bgp_vrf->evpn_info->pip_rmac_zebra, svi_rmac, ETH_ALEN); + /* PIP user configured mac is not present use svi mac as sys mac */ + if (is_zero_mac(&bgp_vrf->evpn_info->pip_rmac_static)) + memcpy(&bgp_vrf->evpn_info->pip_rmac, svi_rmac, ETH_ALEN); + + if (bgp_debug_zebra(NULL)) + zlog_debug( + "VRF %s vni %u pip %s RMAC %pEA sys RMAC %pEA static RMAC %pEA is_anycast_mac %s", + vrf_id_to_name(bgp_vrf->vrf_id), bgp_vrf->l3vni, + bgp_vrf->evpn_info->advertise_pip ? "enable" + : "disable", + &bgp_vrf->rmac, &bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->evpn_info->pip_rmac_static, + is_anycast_mac ? "Enable" : "Disable"); + + /* set the right filter - are we using l3vni only for prefix routes? */ + if (filter) { + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY); + + /* + * VNI_FLAG_USE_TWO_LABELS flag for linked L2VNIs should not be + * set before linking vrf to L3VNI. Thus, no need to clear + * that explicitly. + */ + } else { + UNSET_FLAG(bgp_vrf->vrf_flags, + BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY); + + for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) { + if (!CHECK_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS)) { + + /* + * If we are flapping VNI_FLAG_USE_TWO_LABELS + * flag, update all MACIP routes in this VNI + */ + SET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); + update_all_type2_routes(bgp_evpn, vpn); + } + } + } + + /* Map auto derive or configured RTs */ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD) || + CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) + evpn_auto_rt_import_add_for_vrf(bgp_vrf); + else + bgp_evpn_map_vrf_to_its_rts(bgp_vrf); + + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD) || + CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) + evpn_auto_rt_export_add_for_vrf(bgp_vrf); + + /* auto derive RD */ + bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); + + /* link all corresponding l2vnis */ + hash_iterate(bgp_evpn->vnihash, + (void (*)(struct hash_bucket *, + void *))link_l2vni_hash_to_l3vni, + bgp_vrf); + + /* Only update all corresponding type-2 routes if we are advertising two + * labels along with type-2 routes + */ + if (!filter) + for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) + update_routes_for_vni(bgp_evpn, vpn); + + /* advertise type-5 routes if needed */ + update_advertise_vrf_routes(bgp_vrf); + + /* install all remote routes belonging to this l3vni into correspondng + * vrf */ + install_routes_for_vrf(bgp_vrf); + + return 0; +} + +int bgp_evpn_local_l3vni_del(vni_t l3vni, vrf_id_t vrf_id) +{ + struct bgp *bgp_vrf = NULL; /* bgp vrf instance */ + struct bgp *bgp_evpn = NULL; /* EVPN bgp instance */ + struct listnode *node = NULL; + struct listnode *next = NULL; + struct bgpevpn *vpn = NULL; + + bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp_vrf) { + flog_err( + EC_BGP_NO_DFLT, + "Cannot process L3VNI %u Del - Could not find BGP instance", + l3vni); + return -1; + } + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) { + flog_err( + EC_BGP_NO_DFLT, + "Cannot process L3VNI %u Del - Could not find EVPN BGP instance", + l3vni); + return -1; + } + + if (CHECK_FLAG(bgp_evpn->flags, BGP_FLAG_DELETE_IN_PROGRESS)) { + flog_err(EC_BGP_NO_DFLT, + "Cannot process L3VNI %u ADD - EVPN BGP instance is shutting down", + l3vni); + return -1; + } + + /* Remove remote routes from BGT VRF even if BGP_VRF_AUTO is configured, + * bgp_delete would not remove/decrement bgp_path_info of the ip_prefix + * routes. This will uninstalling the routes from zebra and decremnt the + * bgp info count. + */ + uninstall_routes_for_vrf(bgp_vrf); + + /* delete/withdraw all type-5 routes */ + delete_withdraw_vrf_routes(bgp_vrf); + + /* Tunnel is no longer active. + * Delete VTEP-IP from EVPN underlay's tip_hash. + */ + bgp_tip_del(bgp_evpn, &bgp_vrf->originator_ip); + + /* remove the l3vni from vrf instance */ + bgp_vrf->l3vni = 0; + + /* remove the Rmac from the BGP vrf */ + memset(&bgp_vrf->rmac, 0, sizeof(struct ethaddr)); + memset(&bgp_vrf->evpn_info->pip_rmac_zebra, 0, ETH_ALEN); + if (is_zero_mac(&bgp_vrf->evpn_info->pip_rmac_static) && + !is_zero_mac(&bgp_vrf->evpn_info->pip_rmac)) + memset(&bgp_vrf->evpn_info->pip_rmac, 0, ETH_ALEN); + + /* remove default import RT or Unmap non-default import RT */ + if (!list_isempty(bgp_vrf->vrf_import_rtl)) { + bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) + list_delete_all_node(bgp_vrf->vrf_import_rtl); + } + + /* remove default export RT */ + if (!list_isempty(bgp_vrf->vrf_export_rtl) && + !CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { + list_delete_all_node(bgp_vrf->vrf_export_rtl); + } + + /* update all corresponding local mac-ip routes */ + if (!CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY)) { + for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) { + UNSET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); + update_routes_for_vni(bgp_evpn, vpn); + } + } + + /* If any L2VNIs point to this instance, unlink them. */ + for (ALL_LIST_ELEMENTS(bgp_vrf->l2vnis, node, next, vpn)) + bgpevpn_unlink_from_l3vni(vpn); + + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY); + + /* Delete the instance if it was autocreated */ + if (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_AUTO)) + bgp_delete(bgp_vrf); + + return 0; +} + +/* + * When bgp instance goes down also clean up what might have been left over + * from evpn. + */ +void bgp_evpn_instance_down(struct bgp *bgp) +{ + /* If we have a stale local vni, delete it */ + if (bgp->l3vni) + bgp_evpn_local_l3vni_del(bgp->l3vni, bgp->vrf_id); +} + +/* + * Handle del of a local VNI. + */ +int bgp_evpn_local_vni_del(struct bgp *bgp, vni_t vni) +{ + struct bgpevpn *vpn; + + /* Locate VNI hash */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) + return 0; + + /* Remove all local EVPN routes and schedule for processing (to + * withdraw from peers). + */ + delete_routes_for_vni(bgp, vpn); + + bgp_evpn_unlink_from_vni_svi_hash(bgp, vpn); + + vpn->svi_ifindex = 0; + /* Tunnel is no longer active. + * Delete VTEP-IP from EVPN underlay's tip_hash. + */ + bgp_tip_del(bgp, &vpn->originator_ip); + + /* Clear "live" flag and see if hash needs to be freed. */ + UNSET_FLAG(vpn->flags, VNI_FLAG_LIVE); + if (!is_vni_configured(vpn)) + bgp_evpn_free(bgp, vpn); + + return 0; +} + +/* + * Handle add (or update) of a local VNI. The VNI changes we care + * about are for the local-tunnel-ip and the (tenant) VRF. + */ +int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni, + struct in_addr originator_ip, + vrf_id_t tenant_vrf_id, + struct in_addr mcast_grp, + ifindex_t svi_ifindex) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + struct bgp *bgp_evpn = bgp_get_evpn(); + + /* Lookup VNI. If present and no change, exit. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (vpn) { + + if (is_vni_live(vpn) + && IPV4_ADDR_SAME(&vpn->originator_ip, &originator_ip) + && IPV4_ADDR_SAME(&vpn->mcast_grp, &mcast_grp) + && vpn->tenant_vrf_id == tenant_vrf_id + && vpn->svi_ifindex == svi_ifindex) + /* Probably some other param has changed that we don't + * care about. */ + return 0; + + bgp_evpn_mcast_grp_change(bgp, vpn, mcast_grp); + + if (vpn->svi_ifindex != svi_ifindex) { + + /* + * Unresolve all the gateway IP nexthops for this VNI + * for old SVI + */ + bgp_evpn_remote_ip_hash_iterate( + vpn, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_remote_ip_hash_unlink_nexthop, + vpn); + bgp_evpn_unlink_from_vni_svi_hash(bgp, vpn); + vpn->svi_ifindex = svi_ifindex; + bgp_evpn_link_to_vni_svi_hash(bgp, vpn); + + /* + * Resolve all the gateway IP nexthops for this VNI + * for new SVI + */ + bgp_evpn_remote_ip_hash_iterate( + vpn, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_remote_ip_hash_link_nexthop, + vpn); + } + + /* Update tenant_vrf_id if it has changed. */ + if (vpn->tenant_vrf_id != tenant_vrf_id) { + + /* + * Unresolve all the gateway IP nexthops for this VNI + * in old tenant vrf + */ + bgp_evpn_remote_ip_hash_iterate( + vpn, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_remote_ip_hash_unlink_nexthop, + vpn); + bgpevpn_unlink_from_l3vni(vpn); + vpn->tenant_vrf_id = tenant_vrf_id; + bgpevpn_link_to_l3vni(vpn); + + /* + * Resolve all the gateway IP nexthops for this VNI + * in new tenant vrf + */ + bgp_evpn_remote_ip_hash_iterate( + vpn, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_remote_ip_hash_link_nexthop, + vpn); + } + + /* If tunnel endpoint IP has changed, update (and delete prior + * type-3 route, if needed.) + */ + handle_tunnel_ip_change(NULL, bgp, vpn, originator_ip); + + /* Update all routes with new endpoint IP and/or export RT + * for VRFs + */ + if (is_vni_live(vpn)) + update_routes_for_vni(bgp, vpn); + } else { + /* Create or update as appropriate. */ + vpn = bgp_evpn_new(bgp, vni, originator_ip, tenant_vrf_id, + mcast_grp, svi_ifindex); + } + + /* if the VNI is live already, there is nothing more to do */ + if (is_vni_live(vpn)) + return 0; + + /* Mark as "live" */ + SET_FLAG(vpn->flags, VNI_FLAG_LIVE); + + /* Tunnel is newly active. + * Add TIP to tip_hash of the EVPN underlay instance (bgp_get_evpn()). + */ + if (bgp_tip_add(bgp, &originator_ip)) + /* The originator_ip was not already present in the + * bgp martian next-hop table as a tunnel-ip, so we + * need to go back and filter routes matching the new + * martian next-hop. + */ + bgp_filter_evpn_routes_upon_martian_change(bgp_evpn, + BGP_MARTIAN_TUN_IP); + + /* + * Create EVPN type-3 route and schedule for processing. + * + * RT-3 only if doing head-end replication + */ + if (bgp_evpn_vni_flood_mode_get(bgp, vpn) + == VXLAN_FLOOD_HEAD_END_REPL) { + build_evpn_type3_prefix(&p, vpn->originator_ip); + if (update_evpn_route(bgp, vpn, &p, 0, 0, NULL)) { + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "%u: Type3 route creation failure for VNI %u", + bgp->vrf_id, vni); + return -1; + } + } + + /* If we have learnt and retained remote routes (VTEPs, MACs) for this + * VNI, + * install them. + */ + install_routes_for_vni(bgp, vpn); + + /* If we are advertising gateway mac-ip + It needs to be conveyed again to zebra */ + bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, vpn->vni); + + /* advertise svi mac-ip knob to zebra */ + bgp_zebra_advertise_svi_macip(bgp, vpn->advertise_svi_macip, vpn->vni); + + return 0; +} + +/* + * Handle change in setting for BUM handling. The supported values + * are head-end replication and dropping all BUM packets. Any change + * should be registered with zebra. Also, if doing head-end replication, + * need to advertise local VNIs as EVPN RT-3 wheras, if BUM packets are + * to be dropped, the RT-3s must be withdrawn. + */ +void bgp_evpn_flood_control_change(struct bgp *bgp) +{ + zlog_info("L2VPN EVPN BUM handling is %s", + bgp->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL ? + "Flooding" : "Flooding Disabled"); + + bgp_zebra_vxlan_flood_control(bgp, bgp->vxlan_flood_ctrl); + if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_HEAD_END_REPL) + hash_iterate(bgp->vnihash, create_advertise_type3, bgp); + else if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) + hash_iterate(bgp->vnihash, delete_withdraw_type3, bgp); +} + +/* + * Cleanup EVPN information on disable - Need to delete and withdraw + * EVPN routes from peers. + */ +void bgp_evpn_cleanup_on_disable(struct bgp *bgp) +{ + hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, + void *))cleanup_vni_on_disable, + bgp); +} + +/* + * Cleanup EVPN information - invoked at the time of bgpd exit or when the + * BGP instance (default) is being freed. + */ +void bgp_evpn_cleanup(struct bgp *bgp) +{ + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, void *))free_vni_entry, + bgp); + + hash_clean_and_free(&bgp->import_rt_hash, + (void (*)(void *))hash_import_rt_free); + + hash_clean_and_free(&bgp->vrf_import_rt_hash, + (void (*)(void *))hash_vrf_import_rt_free); + + hash_clean_and_free(&bgp->vni_svi_hash, + (void (*)(void *))hash_evpn_free); + + /* + * Why is the vnihash freed at the top of this function and + * then deleted here? + */ + hash_clean_and_free(&bgp->vnihash, NULL); + + list_delete(&bgp->vrf_import_rtl); + list_delete(&bgp->vrf_export_rtl); + list_delete(&bgp->l2vnis); + + if (bgp->evpn_info) { + ecommunity_free(&bgp->evpn_info->soo); + XFREE(MTYPE_BGP_EVPN_INFO, bgp->evpn_info); + } + + if (bgp->vrf_prd_pretty) + XFREE(MTYPE_BGP_NAME, bgp->vrf_prd_pretty); +} + +/* + * Initialization for EVPN + * Create + * VNI hash table + * hash for RT to VNI + */ +void bgp_evpn_init(struct bgp *bgp) +{ + bgp->vnihash = + hash_create(vni_hash_key_make, vni_hash_cmp, "BGP VNI Hash"); + bgp->vni_svi_hash = + hash_create(vni_svi_hash_key_make, vni_svi_hash_cmp, + "BGP VNI hash based on SVI ifindex"); + bgp->import_rt_hash = + hash_create(import_rt_hash_key_make, import_rt_hash_cmp, + "BGP Import RT Hash"); + bgp->vrf_import_rt_hash = + hash_create(vrf_import_rt_hash_key_make, vrf_import_rt_hash_cmp, + "BGP VRF Import RT Hash"); + bgp->vrf_import_rtl = list_new(); + bgp->vrf_import_rtl->cmp = + (int (*)(void *, void *))evpn_vrf_route_target_cmp; + bgp->vrf_import_rtl->del = evpn_vrf_rt_del; + bgp->vrf_export_rtl = list_new(); + bgp->vrf_export_rtl->cmp = + (int (*)(void *, void *))evpn_vrf_route_target_cmp; + bgp->vrf_export_rtl->del = evpn_vrf_rt_del; + bgp->l2vnis = list_new(); + bgp->l2vnis->cmp = vni_list_cmp; + bgp->evpn_info = + XCALLOC(MTYPE_BGP_EVPN_INFO, sizeof(struct bgp_evpn_info)); + /* By default Duplicate Address Dection is enabled. + * Max-moves (N) 5, detection time (M) 180 + * default action is warning-only + * freeze action permanently freezes address, + * and freeze time (auto-recovery) is disabled. + */ + if (bgp->evpn_info) { + bgp->evpn_info->dup_addr_detect = true; + bgp->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; + bgp->evpn_info->dad_max_moves = EVPN_DAD_DEFAULT_MAX_MOVES; + bgp->evpn_info->dad_freeze = false; + bgp->evpn_info->dad_freeze_time = 0; + /* Initialize zebra vxlan */ + bgp_zebra_dup_addr_detection(bgp); + /* Enable PIP feature by default for bgp vrf instance */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { + struct bgp *bgp_default; + + bgp->evpn_info->advertise_pip = true; + bgp_default = bgp_get_default(); + if (bgp_default) + bgp->evpn_info->pip_ip = bgp_default->router_id; + } + } + + /* Default BUM handling is to do head-end replication. */ + bgp->vxlan_flood_ctrl = VXLAN_FLOOD_HEAD_END_REPL; + + bgp_evpn_nh_init(bgp); +} + +void bgp_evpn_vrf_delete(struct bgp *bgp_vrf) +{ + bgp_evpn_unmap_vrf_from_its_rts(bgp_vrf); + bgp_evpn_nh_finish(bgp_vrf); +} + +/* + * Get the prefixlen of the ip prefix carried within the type5 evpn route. + */ +int bgp_evpn_get_type5_prefixlen(const struct prefix *pfx) +{ + struct prefix_evpn *evp = (struct prefix_evpn *)pfx; + + if (!pfx || pfx->family != AF_EVPN) + return 0; + + if (evp->prefix.route_type != BGP_EVPN_IP_PREFIX_ROUTE) + return 0; + + return evp->prefix.prefix_addr.ip_prefix_length; +} + +/* + * Should we register nexthop for this EVPN prefix for nexthop tracking? + */ +bool bgp_evpn_is_prefix_nht_supported(const struct prefix *pfx) +{ + struct prefix_evpn *evp = (struct prefix_evpn *)pfx; + + /* + * EVPN routes should be marked as valid only if the nexthop is + * reachable. Only if this happens, the route should be imported + * (into VNI or VRF routing tables) and/or advertised. + * Note: This is currently applied for EVPN type-1, type-2, + * type-3, type-4 and type-5 routes. + * It may be tweaked later on for other routes, or + * even removed completely when all routes are handled. + */ + if (pfx && pfx->family == AF_EVPN + && (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + || evp->prefix.route_type == BGP_EVPN_AD_ROUTE + || evp->prefix.route_type == BGP_EVPN_ES_ROUTE + || evp->prefix.route_type == BGP_EVPN_IMET_ROUTE + || evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE)) + return true; + + return false; +} + +static void *bgp_evpn_remote_ip_hash_alloc(void *p) +{ + const struct evpn_remote_ip *key = (const struct evpn_remote_ip *)p; + struct evpn_remote_ip *ip; + + ip = XMALLOC(MTYPE_EVPN_REMOTE_IP, sizeof(struct evpn_remote_ip)); + *ip = *key; + ip->macip_path_list = list_new(); + + return ip; +} + +static unsigned int bgp_evpn_remote_ip_hash_key_make(const void *p) +{ + const struct evpn_remote_ip *ip = p; + const struct ipaddr *addr = &ip->addr; + + if (IS_IPADDR_V4(addr)) + return jhash_1word(addr->ipaddr_v4.s_addr, 0); + + return jhash2(addr->ipaddr_v6.s6_addr32, + array_size(addr->ipaddr_v6.s6_addr32), 0); +} + +static bool bgp_evpn_remote_ip_hash_cmp(const void *p1, const void *p2) +{ + const struct evpn_remote_ip *ip1 = p1; + const struct evpn_remote_ip *ip2 = p2; + + return !ipaddr_cmp(&ip1->addr, &ip2->addr); +} + +static void bgp_evpn_remote_ip_hash_init(struct bgpevpn *vpn) +{ + if (!evpn_resolve_overlay_index()) + return; + + vpn->remote_ip_hash = hash_create(bgp_evpn_remote_ip_hash_key_make, + bgp_evpn_remote_ip_hash_cmp, + "BGP EVPN remote IP hash"); +} + +static void bgp_evpn_remote_ip_hash_free(struct hash_bucket *bucket, void *args) +{ + struct evpn_remote_ip *ip = (struct evpn_remote_ip *)bucket->data; + struct bgpevpn *vpn = (struct bgpevpn *)args; + + bgp_evpn_remote_ip_process_nexthops(vpn, &ip->addr, false); + + list_delete(&ip->macip_path_list); + + hash_release(vpn->remote_ip_hash, ip); + XFREE(MTYPE_EVPN_REMOTE_IP, ip); +} + +static void bgp_evpn_remote_ip_hash_destroy(struct bgpevpn *vpn) +{ + if (!evpn_resolve_overlay_index() || vpn->remote_ip_hash == NULL) + return; + + hash_iterate(vpn->remote_ip_hash, + (void (*)(struct hash_bucket *, void *))bgp_evpn_remote_ip_hash_free, + vpn); + + hash_free(vpn->remote_ip_hash); + vpn->remote_ip_hash = NULL; +} + +/* Add a remote MAC/IP route to hash table */ +static void bgp_evpn_remote_ip_hash_add(struct bgpevpn *vpn, + struct bgp_path_info *pi) +{ + struct evpn_remote_ip tmp; + struct evpn_remote_ip *ip; + struct prefix_evpn *evp; + + if (!evpn_resolve_overlay_index()) + return; + + if (pi->type != ZEBRA_ROUTE_BGP || pi->sub_type != BGP_ROUTE_IMPORTED + || !CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + return; + + evp = (struct prefix_evpn *)&pi->net->rn->p; + + if (evp->family != AF_EVPN + || evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE + || is_evpn_prefix_ipaddr_none(evp)) + return; + + tmp.addr = evp->prefix.macip_addr.ip; + ip = hash_lookup(vpn->remote_ip_hash, &tmp); + if (ip) { + if (listnode_lookup(ip->macip_path_list, pi) != NULL) + return; + (void)listnode_add(ip->macip_path_list, pi); + return; + } + + ip = hash_get(vpn->remote_ip_hash, &tmp, bgp_evpn_remote_ip_hash_alloc); + (void)listnode_add(ip->macip_path_list, pi); + + bgp_evpn_remote_ip_process_nexthops(vpn, &ip->addr, true); +} + +/* Delete a remote MAC/IP route from hash table */ +static void bgp_evpn_remote_ip_hash_del(struct bgpevpn *vpn, + struct bgp_path_info *pi) +{ + struct evpn_remote_ip tmp; + struct evpn_remote_ip *ip; + struct prefix_evpn *evp; + + if (!evpn_resolve_overlay_index()) + return; + + evp = (struct prefix_evpn *)&pi->net->rn->p; + + if (evp->family != AF_EVPN + || evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE + || is_evpn_prefix_ipaddr_none(evp)) + return; + + tmp.addr = evp->prefix.macip_addr.ip; + ip = hash_lookup(vpn->remote_ip_hash, &tmp); + if (ip == NULL) + return; + + listnode_delete(ip->macip_path_list, pi); + + if (ip->macip_path_list->count == 0) { + bgp_evpn_remote_ip_process_nexthops(vpn, &ip->addr, false); + hash_release(vpn->remote_ip_hash, ip); + list_delete(&ip->macip_path_list); + XFREE(MTYPE_EVPN_REMOTE_IP, ip); + } +} + +static void bgp_evpn_remote_ip_hash_iterate(struct bgpevpn *vpn, + void (*func)(struct hash_bucket *, + void *), + void *arg) +{ + if (!evpn_resolve_overlay_index()) + return; + + hash_iterate(vpn->remote_ip_hash, func, arg); +} + +static void show_remote_ip_entry(struct hash_bucket *bucket, void *args) +{ + char buf[INET6_ADDRSTRLEN]; + struct listnode *node = NULL; + struct bgp_path_info *pi = NULL; + struct vty *vty = (struct vty *)args; + struct evpn_remote_ip *ip = (struct evpn_remote_ip *)bucket->data; + + vty_out(vty, " Remote IP: %s\n", + ipaddr2str(&ip->addr, buf, sizeof(buf))); + vty_out(vty, " Linked MAC/IP routes:\n"); + for (ALL_LIST_ELEMENTS_RO(ip->macip_path_list, node, pi)) + vty_out(vty, " %pFX\n", &pi->net->rn->p); +} + +void bgp_evpn_show_remote_ip_hash(struct hash_bucket *bucket, void *args) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct vty *vty = (struct vty *)args; + + vty_out(vty, "VNI: %u\n", vpn->vni); + bgp_evpn_remote_ip_hash_iterate( + vpn, + (void (*)(struct hash_bucket *, void *))show_remote_ip_entry, + vty); + vty_out(vty, "\n"); +} + +static void bgp_evpn_remote_ip_hash_link_nexthop(struct hash_bucket *bucket, + void *args) +{ + struct evpn_remote_ip *ip = (struct evpn_remote_ip *)bucket->data; + struct bgpevpn *vpn = (struct bgpevpn *)args; + + bgp_evpn_remote_ip_process_nexthops(vpn, &ip->addr, true); +} + +static void bgp_evpn_remote_ip_hash_unlink_nexthop(struct hash_bucket *bucket, + void *args) +{ + struct evpn_remote_ip *ip = (struct evpn_remote_ip *)bucket->data; + struct bgpevpn *vpn = (struct bgpevpn *)args; + + bgp_evpn_remote_ip_process_nexthops(vpn, &ip->addr, false); +} + +static unsigned int vni_svi_hash_key_make(const void *p) +{ + const struct bgpevpn *vpn = p; + + return jhash_1word(vpn->svi_ifindex, 0); +} + +static bool vni_svi_hash_cmp(const void *p1, const void *p2) +{ + const struct bgpevpn *vpn1 = p1; + const struct bgpevpn *vpn2 = p2; + + return (vpn1->svi_ifindex == vpn2->svi_ifindex); +} + +static struct bgpevpn *bgp_evpn_vni_svi_hash_lookup(struct bgp *bgp, + ifindex_t svi) +{ + struct bgpevpn *vpn; + struct bgpevpn tmp; + + memset(&tmp, 0, sizeof(tmp)); + tmp.svi_ifindex = svi; + vpn = hash_lookup(bgp->vni_svi_hash, &tmp); + return vpn; +} + +static void bgp_evpn_link_to_vni_svi_hash(struct bgp *bgp, struct bgpevpn *vpn) +{ + if (vpn->svi_ifindex == 0) + return; + + (void)hash_get(bgp->vni_svi_hash, vpn, hash_alloc_intern); +} + +static void bgp_evpn_unlink_from_vni_svi_hash(struct bgp *bgp, + struct bgpevpn *vpn) +{ + if (vpn->svi_ifindex == 0) + return; + + hash_release(bgp->vni_svi_hash, vpn); +} + +void bgp_evpn_show_vni_svi_hash(struct hash_bucket *bucket, void *args) +{ + struct bgpevpn *evpn = (struct bgpevpn *)bucket->data; + struct vty *vty = (struct vty *)args; + + vty_out(vty, "SVI: %u VNI: %u\n", evpn->svi_ifindex, evpn->vni); +} + +/* + * This function is called for a bgp_nexthop_cache entry when the nexthop is + * gateway IP overlay index. + * This function returns true if there is a remote MAC/IP route for the gateway + * IP in the EVI of the nexthop SVI. + */ +bool bgp_evpn_is_gateway_ip_resolved(struct bgp_nexthop_cache *bnc) +{ + struct bgp *bgp_evpn = NULL; + struct bgpevpn *vpn = NULL; + struct evpn_remote_ip tmp; + struct prefix *p; + + if (!evpn_resolve_overlay_index()) + return false; + + if (!bnc->nexthop || bnc->nexthop->ifindex == 0) + return false; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return false; + + /* + * Gateway IP is resolved by nht over SVI interface. + * Use this SVI to find corresponding EVI(L2 context) + */ + vpn = bgp_evpn_vni_svi_hash_lookup(bgp_evpn, bnc->nexthop->ifindex); + if (!vpn) + return false; + + if (vpn->bgp_vrf != bnc->bgp) + return false; + + /* + * Check if the gateway IP is present in the EVI remote_ip_hash table + * which stores all the remote IP addresses received via MAC/IP routes + * in this EVI + */ + memset(&tmp, 0, sizeof(tmp)); + + p = &bnc->prefix; + if (p->family == AF_INET) { + tmp.addr.ipa_type = IPADDR_V4; + memcpy(&(tmp.addr.ipaddr_v4), &(p->u.prefix4), + sizeof(struct in_addr)); + } else if (p->family == AF_INET6) { + tmp.addr.ipa_type = IPADDR_V6; + memcpy(&(tmp.addr.ipaddr_v6), &(p->u.prefix6), + sizeof(struct in6_addr)); + } else + return false; + + if (hash_lookup(vpn->remote_ip_hash, &tmp) == NULL) + return false; + + return true; +} + +/* Resolve/Unresolve nexthops when a MAC/IP route is added/deleted */ +static void bgp_evpn_remote_ip_process_nexthops(struct bgpevpn *vpn, + struct ipaddr *addr, + bool resolve) +{ + afi_t afi; + struct prefix p; + struct bgp_nexthop_cache *bnc; + struct bgp_nexthop_cache_head *tree = NULL; + + if (!vpn->bgp_vrf || vpn->svi_ifindex == 0) + return; + + memset(&p, 0, sizeof(p)); + + if (addr->ipa_type == IPADDR_V4) { + afi = AFI_IP; + p.family = AF_INET; + memcpy(&(p.u.prefix4), &(addr->ipaddr_v4), + sizeof(struct in_addr)); + p.prefixlen = IPV4_MAX_BITLEN; + } else if (addr->ipa_type == IPADDR_V6) { + afi = AFI_IP6; + p.family = AF_INET6; + memcpy(&(p.u.prefix6), &(addr->ipaddr_v6), + sizeof(struct in6_addr)); + p.prefixlen = IPV6_MAX_BITLEN; + } else + return; + + tree = &vpn->bgp_vrf->nexthop_cache_table[afi]; + bnc = bnc_find(tree, &p, 0, 0); + + if (!bnc || !bnc->is_evpn_gwip_nexthop) + return; + + if (!bnc->nexthop || bnc->nexthop->ifindex != vpn->svi_ifindex) + return; + + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s(%u): vni %u mac/ip %s for NH %pFX", + vpn->bgp_vrf->name_pretty, vpn->tenant_vrf_id, + vpn->vni, (resolve ? "add" : "delete"), + &bnc->prefix); + + /* + * MAC/IP route or SVI or tenant vrf being added to EVI. + * Set nexthop as valid only if it is already L3 reachable + */ + if (resolve && bnc->flags & BGP_NEXTHOP_EVPN_INCOMPLETE) { + bnc->flags &= ~BGP_NEXTHOP_EVPN_INCOMPLETE; + bnc->flags |= BGP_NEXTHOP_VALID; + bnc->change_flags |= BGP_NEXTHOP_MACIP_CHANGED; + evaluate_paths(bnc); + } + + /* MAC/IP route or SVI or tenant vrf being deleted from EVI */ + if (!resolve && bnc->flags & BGP_NEXTHOP_VALID) { + bnc->flags &= ~BGP_NEXTHOP_VALID; + bnc->flags |= BGP_NEXTHOP_EVPN_INCOMPLETE; + bnc->change_flags |= BGP_NEXTHOP_MACIP_CHANGED; + evaluate_paths(bnc); + } +} + +void bgp_evpn_handle_resolve_overlay_index_set(struct hash_bucket *bucket, + void *arg) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + bgp_evpn_remote_ip_hash_init(vpn); + + for (dest = bgp_table_top(vpn->ip_table); dest; + dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + bgp_evpn_remote_ip_hash_add(vpn, pi); +} + +void bgp_evpn_handle_resolve_overlay_index_unset(struct hash_bucket *bucket, + void *arg) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + + bgp_evpn_remote_ip_hash_destroy(vpn); +} + +/* + * Helper function for getting the correct label index for l3vni. + * + * Returns the label with the l3vni of the path's label stack. + * + * L3vni is always last label. Type5 will only + * have one label, Type2 will have two. + * + */ +mpls_label_t *bgp_evpn_path_info_labels_get_l3vni(mpls_label_t *labels, + uint8_t num_labels) +{ + if (!labels) + return NULL; + + if (!num_labels) + return NULL; + + return &labels[num_labels - 1]; +} + +/* + * Returns the l3vni of the path converted from the label stack. + */ +vni_t bgp_evpn_path_info_get_l3vni(const struct bgp_path_info *pi) +{ + if (!pi->extra) + return 0; + + return label2vni( + bgp_evpn_path_info_labels_get_l3vni(pi->extra->labels->label, + pi->extra->labels + ->num_labels)); +} + +/* + * Returns true if the l3vni of any of this path doesn't match vrf's l3vni. + */ +static bool bgp_evpn_path_is_dvni(const struct bgp *bgp_vrf, + const struct bgp_path_info *pi) +{ + vni_t vni = 0; + + vni = bgp_evpn_path_info_get_l3vni(pi); + + if ((vni > 0) && (vni != bgp_vrf->l3vni)) + return true; + + return false; +} + +/* + * Returns true if the l3vni of any of the mpath's doesn't match vrf's l3vni. + */ +bool bgp_evpn_mpath_has_dvni(const struct bgp *bgp_vrf, + struct bgp_path_info *mpinfo) +{ + for (; mpinfo; mpinfo = bgp_path_info_mpath_next(mpinfo)) { + if (bgp_evpn_path_is_dvni(bgp_vrf, mpinfo)) + return true; + } + + return false; +} + +/* Upon aggregate set trigger unimport suppressed routes + * from EVPN + */ +void bgp_aggr_supp_withdraw_from_evpn(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *agg_dest, *dest, *top; + const struct prefix *aggr_p; + struct bgp_aggregate *bgp_aggregate; + struct bgp_table *table; + struct bgp_path_info *pi; + + if (!bgp_get_evpn() && !advertise_type5_routes(bgp, afi)) + return; + + /* Aggregate-address table walk. */ + table = bgp->rib[afi][safi]; + for (agg_dest = bgp_table_top(bgp->aggregate[afi][safi]); agg_dest; + agg_dest = bgp_route_next(agg_dest)) { + bgp_aggregate = bgp_dest_get_bgp_aggregate_info(agg_dest); + + if (bgp_aggregate == NULL) + continue; + + aggr_p = bgp_dest_get_prefix(agg_dest); + + /* Look all nodes below the aggregate prefix in + * global AFI/SAFI table (IPv4/IPv6). + * Trigger withdrawal (this will be Type-5 routes only) + * from EVPN Global table. + */ + top = bgp_node_get(table, aggr_p); + for (dest = bgp_node_get(table, aggr_p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= aggr_p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* Only Suppressed route remove from EVPN */ + if (!bgp_path_suppressed(pi)) + continue; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s aggregated %pFX remove suppressed route %pFX", + __func__, aggr_p, dest_p); + + if (!is_route_injectable_into_evpn_non_supp(pi)) + continue; + + bgp_evpn_withdraw_type5_route(bgp, dest_p, afi, + safi); + } + } + } +} diff --git a/bgpd/bgp_evpn.h b/bgpd/bgp_evpn.h new file mode 100644 index 0000000..dc82bcf --- /dev/null +++ b/bgpd/bgp_evpn.h @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* E-VPN header for packet handling + * Copyright (C) 2016 6WIND + */ + +#ifndef _QUAGGA_BGP_EVPN_H +#define _QUAGGA_BGP_EVPN_H + +#include "vxlan.h" +#include "bgpd.h" + +#define EVPN_ROUTE_STRLEN 200 /* Must be >> MAC + IPv6 strings. */ +#define EVPN_AUTORT_VXLAN 0x10000000 + +#define EVPN_ENABLED(bgp) (bgp)->advertise_all_vni +static inline int is_evpn_enabled(void) +{ + struct bgp *bgp = NULL; + + bgp = bgp_get_evpn(); + return bgp ? EVPN_ENABLED(bgp) : 0; +} + +static inline int advertise_type5_routes(struct bgp *bgp_vrf, + afi_t afi) +{ + if (!bgp_vrf->l3vni) + return 0; + + if ((afi == AFI_IP) + && ((CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST)) + || (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP)))) + return 1; + + if ((afi == AFI_IP6) + && ((CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST)) + || (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP)))) + return 1; + + return 0; +} + +/* Flag if the route's parent is a EVPN route. */ +static inline struct bgp_path_info * +get_route_parent_evpn(struct bgp_path_info *ri) +{ + struct bgp_path_info *parent_ri; + + /* If not imported (or doesn't have a parent), bail. */ + if (ri->sub_type != BGP_ROUTE_IMPORTED || !ri->extra || + !ri->extra->vrfleak || !ri->extra->vrfleak->parent) + return NULL; + + /* Determine parent recursively */ + for (parent_ri = ri->extra->vrfleak->parent; + parent_ri->extra && parent_ri->extra->vrfleak && + parent_ri->extra->vrfleak->parent; + parent_ri = parent_ri->extra->vrfleak->parent) + ; + + return parent_ri; +} + +/* Flag if the route's parent is a EVPN route. */ +static inline int is_route_parent_evpn(struct bgp_path_info *ri) +{ + struct bgp_path_info *parent_ri; + struct bgp_table *table; + struct bgp_dest *dest; + + parent_ri = get_route_parent_evpn(ri); + if (!parent_ri) + return 0; + + /* See if of family L2VPN/EVPN */ + dest = parent_ri->net; + if (!dest) + return 0; + table = bgp_dest_table(dest); + if (table && + table->afi == AFI_L2VPN && + table->safi == SAFI_EVPN) + return 1; + return 0; +} + +/* Flag if the route path's family is EVPN. */ +static inline bool is_pi_family_evpn(struct bgp_path_info *pi) +{ + return is_pi_family_matching(pi, AFI_L2VPN, SAFI_EVPN); +} + +static inline bool evpn_resolve_overlay_index(void) +{ + struct bgp *bgp = NULL; + + bgp = bgp_get_evpn(); + return bgp ? bgp->resolve_overlay_index : false; +} + +extern void bgp_evpn_advertise_type5_route(struct bgp *bgp_vrf, + const struct prefix *p, + struct attr *src_attr, afi_t afi, + safi_t safi); +extern void bgp_evpn_withdraw_type5_route(struct bgp *bgp_vrf, + const struct prefix *p, afi_t afi, + safi_t safi); +extern void bgp_evpn_withdraw_type5_routes(struct bgp *bgp_vrf, afi_t afi, + safi_t safi); +extern void bgp_evpn_advertise_type5_routes(struct bgp *bgp_vrf, afi_t afi, + safi_t safi); +extern void bgp_evpn_vrf_delete(struct bgp *bgp_vrf); +extern void bgp_evpn_handle_router_id_update(struct bgp *bgp, int withdraw); +extern char *bgp_evpn_label2str(mpls_label_t *label, uint8_t num_labels, + char *buf, int len); +extern void bgp_evpn_route2json(const struct prefix_evpn *p, json_object *json); +extern void bgp_evpn_encode_prefix(struct stream *s, const struct prefix *p, + const struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + struct attr *attr, bool addpath_capable, + uint32_t addpath_tx_id); +extern int bgp_nlri_parse_evpn(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, bool withdraw); +extern int bgp_evpn_import_route(struct bgp *bgp, afi_t afi, safi_t safi, + const struct prefix *p, + struct bgp_path_info *ri); +extern int bgp_evpn_unimport_route(struct bgp *bgp, afi_t afi, safi_t safi, + const struct prefix *p, + struct bgp_path_info *ri); +extern void +bgp_reimport_evpn_routes_upon_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *old_soo, + struct ecommunity *new_soo); +extern void bgp_reimport_evpn_routes_upon_martian_change( + struct bgp *bgp, enum bgp_martian_type martian_type, void *old_martian, + void *new_martian); +extern void +bgp_filter_evpn_routes_upon_martian_change(struct bgp *bgp, + enum bgp_martian_type martian_type); +extern int bgp_evpn_local_macip_del(struct bgp *bgp, vni_t vni, + struct ethaddr *mac, struct ipaddr *ip, + int state); +extern int bgp_evpn_local_macip_add(struct bgp *bgp, vni_t vni, + struct ethaddr *mac, struct ipaddr *ip, + uint8_t flags, uint32_t seq, esi_t *esi); +extern int bgp_evpn_local_l3vni_add(vni_t vni, vrf_id_t vrf_id, + struct ethaddr *rmac, + struct ethaddr *vrr_rmac, + struct in_addr originator_ip, int filter, + ifindex_t svi_ifindex, bool is_anycast_mac); +extern int bgp_evpn_local_l3vni_del(vni_t vni, vrf_id_t vrf_id); +extern void bgp_evpn_instance_down(struct bgp *bgp); +extern int bgp_evpn_local_vni_del(struct bgp *bgp, vni_t vni); +extern int bgp_evpn_local_vni_add(struct bgp *bgp, vni_t vni, + struct in_addr originator_ip, + vrf_id_t tenant_vrf_id, + struct in_addr mcast_grp, + ifindex_t svi_ifindex); +extern void bgp_evpn_flood_control_change(struct bgp *bgp); +extern void bgp_evpn_cleanup_on_disable(struct bgp *bgp); +extern void bgp_evpn_cleanup(struct bgp *bgp); +extern void bgp_evpn_init(struct bgp *bgp); +extern int bgp_evpn_get_type5_prefixlen(const struct prefix *pfx); +extern bool bgp_evpn_is_prefix_nht_supported(const struct prefix *pfx); +extern void update_advertise_vrf_routes(struct bgp *bgp_vrf); +extern void bgp_evpn_show_remote_ip_hash(struct hash_bucket *bucket, + void *args); +extern void bgp_evpn_show_vni_svi_hash(struct hash_bucket *bucket, void *args); +extern bool bgp_evpn_is_gateway_ip_resolved(struct bgp_nexthop_cache *bnc); +extern void +bgp_evpn_handle_resolve_overlay_index_set(struct hash_bucket *bucket, + void *arg); +extern void +bgp_evpn_handle_resolve_overlay_index_unset(struct hash_bucket *bucket, + void *arg); +extern mpls_label_t *bgp_evpn_path_info_labels_get_l3vni(mpls_label_t *labels, + uint8_t num_labels); +extern vni_t bgp_evpn_path_info_get_l3vni(const struct bgp_path_info *pi); +extern bool bgp_evpn_mpath_has_dvni(const struct bgp *bgp_vrf, + struct bgp_path_info *mpinfo); +extern bool is_route_injectable_into_evpn(struct bgp_path_info *pi); +extern bool is_route_injectable_into_evpn_non_supp(struct bgp_path_info *pi); +extern void bgp_aggr_supp_withdraw_from_evpn(struct bgp *bgp, afi_t afi, + safi_t safi); + +extern enum zclient_send_status evpn_zebra_install(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p, + struct bgp_path_info *pi); +extern enum zclient_send_status +evpn_zebra_uninstall(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p, struct bgp_path_info *pi, + bool is_sync); +#endif /* _QUAGGA_BGP_EVPN_H */ diff --git a/bgpd/bgp_evpn_mh.c b/bgpd/bgp_evpn_mh.c new file mode 100644 index 0000000..d723a2b --- /dev/null +++ b/bgpd/bgp_evpn_mh.c @@ -0,0 +1,5055 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* EVPN Multihoming procedures + * + * Copyright (C) 2019 Cumulus Networks, Inc. + * Anuradha Karuppiah + * + */ + +#include + +#include "command.h" +#include "filter.h" +#include "prefix.h" +#include "log.h" +#include "memory.h" +#include "stream.h" +#include "hash.h" +#include "jhash.h" +#include "zclient.h" + +#include "lib/printfrr.h" + +#include "bgpd/bgp_attr_evpn.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_nhg.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_trace.h" + +static void bgp_evpn_local_es_down(struct bgp *bgp, + struct bgp_evpn_es *es); +static void bgp_evpn_local_type1_evi_route_del(struct bgp *bgp, + struct bgp_evpn_es *es); +static struct bgp_evpn_es_vtep * +bgp_evpn_es_vtep_add(struct bgp *bgp, struct bgp_evpn_es *es, + struct in_addr vtep_ip, bool esr, uint8_t df_alg, + uint16_t df_pref, int *zret); +static enum zclient_send_status bgp_evpn_es_vtep_del(struct bgp *bgp, + struct bgp_evpn_es *es, + struct in_addr vtep_ip, + bool esr); +static void bgp_evpn_es_cons_checks_pend_add(struct bgp_evpn_es *es); +static void bgp_evpn_es_cons_checks_pend_del(struct bgp_evpn_es *es); +static struct bgp_evpn_es_evi * +bgp_evpn_local_es_evi_do_del(struct bgp_evpn_es_evi *es_evi); +static uint32_t bgp_evpn_es_get_active_vtep_cnt(struct bgp_evpn_es *es); +static void bgp_evpn_l3nhg_update_on_vtep_chg(struct bgp_evpn_es *es); +static struct bgp_evpn_es *bgp_evpn_es_new(struct bgp *bgp, const esi_t *esi); +static void bgp_evpn_es_free(struct bgp_evpn_es *es, const char *caller); +static void bgp_evpn_path_es_unlink(struct bgp_path_es_info *es_info); +static void bgp_evpn_mac_update_on_es_local_chg(struct bgp_evpn_es *es, + bool is_local); + +esi_t zero_esi_buf, *zero_esi = &zero_esi_buf; +static void bgp_evpn_run_consistency_checks(struct event *t); +static void bgp_evpn_path_nh_info_free(struct bgp_path_evpn_nh_info *nh_info); +static void bgp_evpn_path_nh_unlink(struct bgp_path_evpn_nh_info *nh_info); + +/****************************************************************************** + * per-ES (Ethernet Segment) routing table + * + * Following routes are added to the ES's routing table - + * 1. Local and remote ESR (Type-4) + * 2. Local EAD-per-ES (Type-1). + * + * Key for these routes is {ESI, VTEP-IP} so the path selection is practically + * a no-op i.e. all paths lead to same VTEP-IP (i.e. result in the same VTEP + * being added to same ES). + * + * Note the following routes go into the VNI routing table (instead of the + * ES routing table) - + * 1. Remote EAD-per-ES + * 2. Local and remote EAD-per-EVI + */ + +/* Calculate the best path for a multi-homing (Type-1 or Type-4) route + * installed in the ES's routing table. + */ +static int bgp_evpn_es_route_select_install(struct bgp *bgp, + struct bgp_evpn_es *es, + struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + int ret = 0; + int zret = 0; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp_path_info *old_select; /* old best */ + struct bgp_path_info *new_select; /* new best */ + struct bgp_path_info_pair old_and_new; + + SET_FLAG(pi->flags, BGP_PATH_UNSORTED); + + /* Compute the best path. */ + bgp_best_selection(bgp, dest, &bgp->maxpaths[afi][safi], &old_and_new, + afi, safi); + old_select = old_and_new.old; + new_select = old_and_new.new; + + /* + * If the best path hasn't changed - see if something needs to be + * updated + */ + if (old_select && old_select == new_select + && old_select->type == ZEBRA_ROUTE_BGP + && old_select->sub_type == BGP_ROUTE_IMPORTED + && !CHECK_FLAG(dest->flags, BGP_NODE_USER_CLEAR) + && !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) + && !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + if (bgp_zebra_has_route_changed(old_select)) { + bgp_evpn_es_vtep_add(bgp, es, old_select->attr->nexthop, + true /*esr*/, + old_select->attr->df_alg, + old_select->attr->df_pref, &zret); + } + UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); + bgp_zebra_clear_route_change_flags(dest); + return ret; + } + + /* If the user did a "clear" this flag will be set */ + UNSET_FLAG(dest->flags, BGP_NODE_USER_CLEAR); + + /* bestpath has changed; update relevant fields and install or uninstall + * into the zebra RIB. + */ + if (old_select || new_select) + bgp_bump_version(dest); + + if (old_select) + bgp_path_info_unset_flag(dest, old_select, BGP_PATH_SELECTED); + if (new_select) { + bgp_path_info_set_flag(dest, new_select, BGP_PATH_SELECTED); + bgp_path_info_unset_flag(dest, new_select, + BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); + } + + if (new_select && new_select->type == ZEBRA_ROUTE_BGP + && new_select->sub_type == BGP_ROUTE_IMPORTED) { + bgp_evpn_es_vtep_add(bgp, es, new_select->attr->nexthop, + true /*esr */, new_select->attr->df_alg, + new_select->attr->df_pref, &zret); + } else { + if (old_select && old_select->type == ZEBRA_ROUTE_BGP + && old_select->sub_type == BGP_ROUTE_IMPORTED) + bgp_evpn_es_vtep_del( + bgp, es, old_select->attr->nexthop, + true /*esr*/); + } + + /* Clear any route change flags. */ + bgp_zebra_clear_route_change_flags(dest); + + /* Reap old select bgp_path_info, if it has been removed */ + if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) + bgp_path_info_reap(dest, old_select); + + return ret; +} + +/* Install Type-1/Type-4 route entry in the per-ES routing table */ +static int bgp_evpn_es_route_install(struct bgp *bgp, + struct bgp_evpn_es *es, struct prefix_evpn *p, + struct bgp_path_info *parent_pi) +{ + int ret = 0; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi = NULL; + struct attr *attr_new = NULL; + + /* Create (or fetch) route within the VNI. + * NOTE: There is no RD here. + */ + dest = bgp_node_get(es->route_table, (struct prefix *)p); + + /* Check if route entry is already present. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) { + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(parent_pi->attr); + + /* Create new route with its attribute. */ + pi = info_make(parent_pi->type, BGP_ROUTE_IMPORTED, 0, + parent_pi->peer, attr_new, dest); + SET_FLAG(pi->flags, BGP_PATH_VALID); + bgp_path_info_extra_get(pi); + if (!pi->extra->vrfleak) + pi->extra->vrfleak = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK, + sizeof(struct bgp_path_info_extra_vrfleak)); + pi->extra->vrfleak->parent = bgp_path_info_lock(parent_pi); + bgp_dest_lock_node((struct bgp_dest *)parent_pi->net); + bgp_path_info_add(dest, pi); + } else { + if (attrhash_cmp(pi->attr, parent_pi->attr) + && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + bgp_dest_unlock_node(dest); + return 0; + } + /* The attribute has changed. */ + /* Add (or update) attribute to hash. */ + attr_new = bgp_attr_intern(parent_pi->attr); + + /* Restore route, if needed. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, pi); + + /* Mark if nexthop has changed. */ + if (!IPV4_ADDR_SAME(&pi->attr->nexthop, &attr_new->nexthop)) + SET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); + + /* Unintern existing, set to new. */ + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + pi->uptime = monotime(NULL); + } + + /* Perform route selection and update zebra, if required. */ + ret = bgp_evpn_es_route_select_install(bgp, es, dest, pi); + + bgp_dest_unlock_node(dest); + + return ret; +} + +/* Uninstall Type-1/Type-4 route entry from the ES routing table */ +static int bgp_evpn_es_route_uninstall(struct bgp *bgp, struct bgp_evpn_es *es, + struct prefix_evpn *p, struct bgp_path_info *parent_pi) +{ + int ret; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + if (!es->route_table) + return 0; + + /* Locate route within the ESI. + * NOTE: There is no RD here. + */ + dest = bgp_node_lookup(es->route_table, (struct prefix *)p); + if (!dest) + return 0; + + /* Find matching route entry. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->extra && pi->extra->vrfleak && + (struct bgp_path_info *)pi->extra->vrfleak->parent == + parent_pi) + break; + + if (!pi) { + bgp_dest_unlock_node(dest); + return 0; + } + + /* Mark entry for deletion */ + bgp_path_info_delete(dest, pi); + + /* Perform route selection and update zebra, if required. */ + ret = bgp_evpn_es_route_select_install(bgp, es, dest, pi); + + /* Unlock route node. */ + bgp_dest_unlock_node(dest); + + return ret; +} + +/* Install or unistall a Type-4 route in the per-ES routing table */ +int bgp_evpn_es_route_install_uninstall(struct bgp *bgp, struct bgp_evpn_es *es, + afi_t afi, safi_t safi, struct prefix_evpn *evp, + struct bgp_path_info *pi, int install) +{ + int ret = 0; + + if (install) + ret = bgp_evpn_es_route_install(bgp, es, evp, pi); + else + ret = bgp_evpn_es_route_uninstall(bgp, es, evp, pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "%u: Failed to %s EVPN %s route in ESI %s", + bgp->vrf_id, + install ? "install" : "uninstall", + "ES", es->esi_str); + return ret; + } + return 0; +} + +/* Delete (and withdraw) local routes for specified ES from global and ES table. + * Also remove all remote routes from the per ES table. Invoked when ES + * is deleted. + */ +static void bgp_evpn_es_route_del_all(struct bgp *bgp, struct bgp_evpn_es *es) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi, *nextpi; + + /* de-activate the ES */ + bgp_evpn_local_es_down(bgp, es); + bgp_evpn_local_type1_evi_route_del(bgp, es); + + /* Walk this ES's routing table and delete all routes. */ + for (dest = bgp_table_top(es->route_table); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (nextpi = pi->next, 1); pi = nextpi) { + bgp_path_info_delete(dest, pi); + dest = bgp_path_info_reap(dest, pi); + + assert(dest); + } + } +} + +/***************************************************************************** + * Base APIs for creating MH routes (Type-1 or Type-4) on local ethernet + * segment updates. + */ + +/* create or update local EVPN type1/type4 route entry. + * + * This could be in - + * the ES table if ESR/EAD-ES (or) + * the VNI table if EAD-EVI (or) + * the global table if ESR/EAD-ES/EAD-EVI + * + * Note: vpn is applicable only to EAD-EVI routes (NULL for EAD-ES and + * ESR). + */ +int bgp_evpn_mh_route_update(struct bgp *bgp, struct bgp_evpn_es *es, + struct bgpevpn *vpn, afi_t afi, safi_t safi, + struct bgp_dest *dest, struct attr *attr, + struct bgp_path_info **ri, int *route_changed) +{ + struct bgp_path_info *tmp_pi = NULL; + struct bgp_path_info *local_pi = NULL; /* local route entry if any */ + struct bgp_path_info *remote_pi = NULL; /* remote route entry if any */ + struct bgp_labels bgp_labels = {}; + struct attr *attr_new = NULL; + struct prefix_evpn *evp; + + *ri = NULL; + evp = (struct prefix_evpn *)bgp_dest_get_prefix(dest); + *route_changed = 1; + + /* locate the local and remote entries if any */ + for (tmp_pi = bgp_dest_get_bgp_path_info(dest); tmp_pi; + tmp_pi = tmp_pi->next) { + if (tmp_pi->peer == bgp->peer_self + && tmp_pi->type == ZEBRA_ROUTE_BGP + && tmp_pi->sub_type == BGP_ROUTE_STATIC) + local_pi = tmp_pi; + if (tmp_pi->type == ZEBRA_ROUTE_BGP + && tmp_pi->sub_type == BGP_ROUTE_IMPORTED + && CHECK_FLAG(tmp_pi->flags, BGP_PATH_VALID)) + remote_pi = tmp_pi; + } + + /* we don't expect to see a remote_pi at this point as + * an ES route has {esi, vtep_ip} as the key in the ES-rt-table + * in the VNI-rt-table. + */ + if (remote_pi) { + flog_err( + EC_BGP_ES_INVALID, + "%u ERROR: local es route for ESI: %s vtep %pI4 also learnt from remote", + bgp->vrf_id, es ? es->esi_str : "Null", + es ? &es->originator_ip : NULL); + return -1; + } + + /* create or update the entry */ + if (!local_pi) { + + /* Add or update attribute to hash */ + attr_new = bgp_attr_intern(attr); + + /* Create new route with its attribute. */ + tmp_pi = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, + bgp->peer_self, attr_new, dest); + SET_FLAG(tmp_pi->flags, BGP_PATH_VALID); + + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) { + bgp_path_info_extra_get(tmp_pi); + bgp_labels.num_labels = 1; + if (vpn) + vni2label(vpn->vni, &bgp_labels.label[0]); + if (!bgp_path_info_labels_same(tmp_pi, + &bgp_labels.label[0], + bgp_labels.num_labels)) { + bgp_labels_unintern(&tmp_pi->extra->labels); + tmp_pi->extra->labels = + bgp_labels_intern(&bgp_labels); + } + } + + /* add the newly created path to the route-node */ + bgp_path_info_add(dest, tmp_pi); + } else { + tmp_pi = local_pi; + if (attrhash_cmp(tmp_pi->attr, attr) + && !CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) + *route_changed = 0; + else { + /* The attribute has changed. + * Add (or update) attribute to hash. + */ + attr_new = bgp_attr_intern(attr); + bgp_path_info_set_flag(dest, tmp_pi, + BGP_PATH_ATTR_CHANGED); + + /* Restore route, if needed. */ + if (CHECK_FLAG(tmp_pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, tmp_pi); + + /* Unintern existing, set to new. */ + bgp_attr_unintern(&tmp_pi->attr); + tmp_pi->attr = attr_new; + tmp_pi->uptime = monotime(NULL); + } + } + + if (*route_changed) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "local ES %s vni %u route-type %s nexthop %pI4 updated", + es ? es->esi_str : "Null", vpn ? vpn->vni : 0, + evp->prefix.route_type == BGP_EVPN_ES_ROUTE + ? "esr" + : (vpn ? "ead-evi" : "ead-es"), + &attr->mp_nexthop_global_in); + + frrtrace(4, frr_bgp, evpn_mh_local_ead_es_evi_route_upd, + &es->esi, (vpn ? vpn->vni : 0), evp->prefix.route_type, + attr->mp_nexthop_global_in); + } + + /* Return back th*e route entry. */ + *ri = tmp_pi; + return 0; +} + +/* Delete local EVPN ESR (type-4) and EAD (type-1) route + * + * Note: vpn is applicable only to EAD-EVI routes (NULL for EAD-ES and + * ESR). + */ +static int bgp_evpn_mh_route_delete(struct bgp *bgp, struct bgp_evpn_es *es, + struct bgpevpn *vpn, + struct bgp_evpn_es_frag *es_frag, + struct prefix_evpn *p) +{ + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp_path_info *pi; + struct bgp_dest *dest = NULL; /* dest in esi table */ + struct bgp_dest *global_dest = NULL; /* dest in global table */ + struct bgp_table *rt_table; + struct prefix_rd *prd; + + if (vpn) { + rt_table = vpn->ip_table; + prd = &vpn->prd; + } else { + rt_table = es->route_table; + prd = &es_frag->prd; + } + + /* First, locate the route node within the ESI or VNI. + * If it doesn't exist, ther is nothing to do. + * Note: there is no RD here. + */ + dest = bgp_node_lookup(rt_table, (struct prefix *)p); + if (!dest) + return 0; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "local ES %s vni %u route-type %s nexthop %pI4 delete", + es->esi_str, vpn ? vpn->vni : 0, + p->prefix.route_type == BGP_EVPN_ES_ROUTE + ? "esr" + : (vpn ? "ead-evi" : "ead-es"), + &es->originator_ip); + + frrtrace(4, frr_bgp, evpn_mh_local_ead_es_evi_route_del, &es->esi, + (vpn ? vpn->vni : 0), p->prefix.route_type, es->originator_ip); + /* Next, locate route node in the global EVPN routing table. + * Note that this table is a 2-level tree (RD-level + Prefix-level) + */ + global_dest = bgp_evpn_global_node_lookup(bgp->rib[afi][safi], safi, p, + prd, NULL); + if (global_dest) { + + /* Delete route entry in the global EVPN table. */ + delete_evpn_route_entry(bgp, afi, safi, global_dest, &pi); + + /* Schedule for processing - withdraws to peers happen from + * this table. + */ + if (pi) + bgp_process(bgp, global_dest, pi, afi, safi); + bgp_dest_unlock_node(global_dest); + } + + /* + * Delete route entry in the ESI or VNI routing table. + * This can just be removed. + */ + delete_evpn_route_entry(bgp, afi, safi, dest, &pi); + if (pi) + dest = bgp_path_info_reap(dest, pi); + + assert(dest); + bgp_dest_unlock_node(dest); + + return 0; +} + +/* + * This function is called when the VNI RD changes. + * Delete all EAD/EVI local routes for this VNI from the global routing table. + * These routes are scheduled for withdraw from peers. + */ +int delete_global_ead_evi_routes(struct bgp *bgp, struct bgpevpn *vpn) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *rdrn, *bd; + struct bgp_table *table; + struct bgp_path_info *pi; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Find the RD node for the VNI in the global table */ + rdrn = bgp_node_lookup(bgp->rib[afi][safi], (struct prefix *)&vpn->prd); + if (rdrn && bgp_dest_has_bgp_path_info_data(rdrn)) { + table = bgp_dest_get_bgp_table_info(rdrn); + + /* + * Iterate over all the routes in this table and delete EAD/EVI + * routes + */ + for (bd = bgp_table_top(table); bd; bd = bgp_route_next(bd)) { + struct prefix_evpn *evp = (struct prefix_evpn *)&bd->rn->p; + + if (evp->prefix.route_type != BGP_EVPN_AD_ROUTE) + continue; + + delete_evpn_route_entry(bgp, afi, safi, bd, &pi); + if (pi) + bgp_process(bgp, bd, pi, afi, safi); + } + } + + /* Unlock RD node. */ + if (rdrn) + bgp_dest_unlock_node(rdrn); + + return 0; +} + +/***************************************************************************** + * Ethernet Segment (Type-4) Routes + * ESRs are used for DF election. Currently service-carving described in + * RFC 7432 is NOT supported. Instead preference based DF election is + * used by default. + * Reference: draft-ietf-bess-evpn-pref-df + */ +/* Build extended community for EVPN ES (type-4) route */ +static void bgp_evpn_type4_route_extcomm_build(struct bgp_evpn_es *es, + struct attr *attr) +{ + struct ecommunity ecom_encap; + struct ecommunity ecom_es_rt; + struct ecommunity ecom_df; + struct ecommunity_val eval; + struct ecommunity_val eval_es_rt; + struct ecommunity_val eval_df; + bgp_encap_types tnl_type; + struct ethaddr mac; + + /* Encap */ + tnl_type = BGP_ENCAP_TYPE_VXLAN; + memset(&ecom_encap, 0, sizeof(ecom_encap)); + encode_encap_extcomm(tnl_type, &eval); + ecom_encap.size = 1; + ecom_encap.unit_size = ECOMMUNITY_SIZE; + ecom_encap.val = (uint8_t *)eval.val; + bgp_attr_set_ecommunity(attr, ecommunity_dup(&ecom_encap)); + + /* ES import RT */ + memset(&mac, 0, sizeof(mac)); + memset(&ecom_es_rt, 0, sizeof(ecom_es_rt)); + es_get_system_mac(&es->esi, &mac); + encode_es_rt_extcomm(&eval_es_rt, &mac); + ecom_es_rt.size = 1; + ecom_es_rt.unit_size = ECOMMUNITY_SIZE; + ecom_es_rt.val = (uint8_t *)eval_es_rt.val; + bgp_attr_set_ecommunity( + attr, + ecommunity_merge(bgp_attr_get_ecommunity(attr), &ecom_es_rt)); + + /* DF election extended community */ + memset(&ecom_df, 0, sizeof(ecom_df)); + encode_df_elect_extcomm(&eval_df, es->df_pref); + ecom_df.size = 1; + ecom_df.val = (uint8_t *)eval_df.val; + bgp_attr_set_ecommunity( + attr, + ecommunity_merge(bgp_attr_get_ecommunity(attr), &ecom_df)); +} + +/* Create or update local type-4 route */ +static int bgp_evpn_type4_route_update(struct bgp *bgp, + struct bgp_evpn_es *es, struct prefix_evpn *p) +{ + int ret = 0; + int route_changed = 0; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct attr attr; + struct attr *attr_new = NULL; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi = NULL; + + memset(&attr, 0, sizeof(attr)); + + /* Build path-attribute for this route. */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + attr.nexthop = es->originator_ip; + attr.mp_nexthop_global_in = es->originator_ip; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + /* Set up extended community. */ + bgp_evpn_type4_route_extcomm_build(es, &attr); + + /* First, create (or fetch) route node within the ESI. */ + /* NOTE: There is no RD here. */ + dest = bgp_node_get(es->route_table, (struct prefix *)p); + + /* Create or update route entry. */ + ret = bgp_evpn_mh_route_update(bgp, es, NULL, afi, safi, dest, &attr, + &pi, &route_changed); + if (ret != 0) + flog_err( + EC_BGP_ES_INVALID, + "%u ERROR: Failed to updated ES route ESI: %s VTEP %pI4", + bgp->vrf_id, es->esi_str, &es->originator_ip); + + assert(pi); + attr_new = pi->attr; + + /* Perform route selection; + * this is just to set the flags correctly + * as local route in the ES always wins. + */ + bgp_evpn_es_route_select_install(bgp, es, dest, pi); + bgp_dest_unlock_node(dest); + + /* If this is a new route or some attribute has changed, export the + * route to the global table. The route will be advertised to peers + * from there. Note that this table is a 2-level tree (RD-level + + * Prefix-level) similar to L3VPN routes. + */ + if (route_changed) { + struct bgp_path_info *global_pi; + + dest = bgp_evpn_global_node_get(bgp->rib[afi][safi], afi, safi, + p, &es->es_base_frag->prd, + NULL); + bgp_evpn_mh_route_update(bgp, es, NULL, afi, safi, dest, + attr_new, &global_pi, &route_changed); + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, dest, global_pi, afi, safi); + bgp_dest_unlock_node(dest); + } + + /* Unintern temporary. */ + aspath_unintern(&attr.aspath); + return 0; +} + +/* Delete local type-4 route */ +static int bgp_evpn_type4_route_delete(struct bgp *bgp, + struct bgp_evpn_es *es, struct prefix_evpn *p) +{ + if (!es->es_base_frag) + return -1; + + return bgp_evpn_mh_route_delete(bgp, es, NULL /* l2vni */, + es->es_base_frag, p); +} + +/* Process remote/received EVPN type-4 route (advertise or withdraw) */ +int bgp_evpn_type4_route_process(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id) +{ + esi_t esi; + uint8_t ipaddr_len; + struct in_addr vtep_ip; + struct prefix_rd prd; + struct prefix_evpn p; + + /* Type-4 route should be either 23 or 35 bytes + * RD (8), ESI (10), ip-len (1), ip (4 or 16) + */ + if (psize != BGP_EVPN_TYPE4_V4_PSIZE && + psize != BGP_EVPN_TYPE4_V6_PSIZE) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-4 NLRI with invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, pfx, RD_BYTES); + pfx += RD_BYTES; + + /* get the ESI */ + memcpy(&esi, pfx, ESI_BYTES); + pfx += ESI_BYTES; + + + /* Get the IP. */ + ipaddr_len = *pfx++; + if (ipaddr_len == IPV4_MAX_BITLEN) { + memcpy(&vtep_ip, pfx, IPV4_MAX_BYTELEN); + } else { + flog_err( + EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-4 NLRI with unsupported IP address length %d", + peer->bgp->vrf_id, peer->host, ipaddr_len); + return -1; + } + + build_evpn_type4_prefix(&p, &esi, vtep_ip); + /* Process the route. */ + if (attr) { + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, + 0, 0, NULL); + } else { + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, + NULL); + } + return 0; +} + +/* Check if a prefix belongs to the local ES */ +static bool bgp_evpn_type4_prefix_match(struct prefix_evpn *p, + struct bgp_evpn_es *es) +{ + return (p->prefix.route_type == BGP_EVPN_ES_ROUTE) && + !memcmp(&p->prefix.es_addr.esi, &es->esi, sizeof(esi_t)); +} + +/* Import remote ESRs on local ethernet segment add */ +static int bgp_evpn_type4_remote_routes_import(struct bgp *bgp, + struct bgp_evpn_es *es, bool install) +{ + int ret; + afi_t afi; + safi_t safi; + struct bgp_dest *rd_dest, *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Walk entire global routing table and evaluate routes which could be + * imported into this Ethernet Segment. + */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + table = bgp_dest_get_bgp_table_info(rd_dest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + struct prefix_evpn *evp = + (struct prefix_evpn *)bgp_dest_get_prefix(dest); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + /* + * Consider "valid" remote routes applicable for + * this ES. + */ + if (!(CHECK_FLAG(pi->flags, BGP_PATH_VALID) + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_NORMAL)) + continue; + + if (!bgp_evpn_type4_prefix_match(evp, es)) + continue; + + if (install) + ret = bgp_evpn_es_route_install( + bgp, es, evp, pi); + else + ret = bgp_evpn_es_route_uninstall( + bgp, es, evp, pi); + + if (ret) { + flog_err( + EC_BGP_EVPN_FAIL, + "Failed to %s EVPN %pFX route in ESI %s", + install ? "install" + : "uninstall", + evp, es->esi_str); + + bgp_dest_unlock_node(rd_dest); + bgp_dest_unlock_node(dest); + return ret; + } + } + } + } + return 0; +} + +/***************************************************************************** + * Ethernet Auto Discovery (EAD/Type-1) route handling + * There are two types of EAD routes - + * 1. EAD-per-ES - Key: {ESI, ET=0xffffffff} + * 2. EAD-per-EVI - Key: {ESI, ET=0} + */ + +/* Extended communities associated with EAD-per-ES */ +static void +bgp_evpn_type1_es_route_extcomm_build(struct bgp_evpn_es_frag *es_frag, + struct attr *attr) +{ + struct ecommunity ecom_encap; + struct ecommunity ecom_esi_label; + struct ecommunity_val eval; + struct ecommunity_val eval_esi_label; + bgp_encap_types tnl_type; + struct listnode *evi_node, *rt_node; + struct ecommunity *ecom; + struct bgp_evpn_es_evi *es_evi; + + /* Encap */ + tnl_type = BGP_ENCAP_TYPE_VXLAN; + memset(&ecom_encap, 0, sizeof(ecom_encap)); + encode_encap_extcomm(tnl_type, &eval); + ecom_encap.size = 1; + ecom_encap.unit_size = ECOMMUNITY_SIZE; + ecom_encap.val = (uint8_t *)eval.val; + bgp_attr_set_ecommunity(attr, ecommunity_dup(&ecom_encap)); + + /* ESI label */ + encode_esi_label_extcomm(&eval_esi_label, + false /*single_active*/); + ecom_esi_label.size = 1; + ecom_esi_label.unit_size = ECOMMUNITY_SIZE; + ecom_esi_label.val = (uint8_t *)eval_esi_label.val; + bgp_attr_set_ecommunity(attr, + ecommunity_merge(bgp_attr_get_ecommunity(attr), + &ecom_esi_label)); + + /* Add export RTs for all L2-VNIs associated with this ES */ + /* XXX - suppress EAD-ES advertisment if there are no EVIs associated + * with it. + */ + if (listcount(bgp_mh_info->ead_es_export_rtl)) { + for (ALL_LIST_ELEMENTS_RO(bgp_mh_info->ead_es_export_rtl, + rt_node, ecom)) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(attr->ecommunity, ecom)); + } else { + for (ALL_LIST_ELEMENTS_RO(es_frag->es_evi_frag_list, evi_node, + es_evi)) { + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + continue; + for (ALL_LIST_ELEMENTS_RO(es_evi->vpn->export_rtl, + rt_node, ecom)) + bgp_attr_set_ecommunity( + attr, ecommunity_merge(attr->ecommunity, + ecom)); + } + } +} + +/* Extended communities associated with EAD-per-EVI */ +static void bgp_evpn_type1_evi_route_extcomm_build(struct bgp_evpn_es *es, + struct bgpevpn *vpn, struct attr *attr) +{ + struct ecommunity ecom_encap; + struct ecommunity_val eval; + bgp_encap_types tnl_type; + struct listnode *rt_node; + struct ecommunity *ecom; + + /* Encap */ + tnl_type = BGP_ENCAP_TYPE_VXLAN; + memset(&ecom_encap, 0, sizeof(ecom_encap)); + encode_encap_extcomm(tnl_type, &eval); + ecom_encap.size = 1; + ecom_encap.unit_size = ECOMMUNITY_SIZE; + ecom_encap.val = (uint8_t *)eval.val; + bgp_attr_set_ecommunity(attr, ecommunity_dup(&ecom_encap)); + + /* Add export RTs for the L2-VNI */ + for (ALL_LIST_ELEMENTS_RO(vpn->export_rtl, rt_node, ecom)) + bgp_attr_set_ecommunity( + attr, + ecommunity_merge(bgp_attr_get_ecommunity(attr), ecom)); +} + +/* Update EVPN EAD (type-1) route - + * vpn - valid for EAD-EVI routes and NULL for EAD-ES routes + */ +static int bgp_evpn_type1_route_update(struct bgp *bgp, struct bgp_evpn_es *es, + struct bgpevpn *vpn, + struct bgp_evpn_es_frag *es_frag, + struct prefix_evpn *p) +{ + int ret = 0; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct attr attr; + struct attr *attr_new = NULL; + struct bgp_dest *dest = NULL; + struct bgp_path_info *pi = NULL; + int route_changed = 0; + struct prefix_rd *global_rd; + + memset(&attr, 0, sizeof(attr)); + + /* Build path-attribute for this route. */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + attr.nexthop = es->originator_ip; + attr.mp_nexthop_global_in = es->originator_ip; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + if (vpn) { + /* EAD-EVI route update */ + /* MPLS label */ + vni2label(vpn->vni, &(attr.label)); + + /* Set up extended community */ + bgp_evpn_type1_evi_route_extcomm_build(es, vpn, &attr); + + /* First, create (or fetch) route node within the VNI. */ + dest = bgp_node_get(vpn->ip_table, (struct prefix *)p); + + /* Create or update route entry. */ + ret = bgp_evpn_mh_route_update(bgp, es, vpn, afi, safi, dest, + &attr, &pi, &route_changed); + if (ret != 0) + flog_err( + EC_BGP_ES_INVALID, + "%u Failed to update EAD-EVI route ESI: %s VNI %u VTEP %pI4", + bgp->vrf_id, es->esi_str, vpn->vni, + &es->originator_ip); + global_rd = &vpn->prd; + } else { + /* EAD-ES route update */ + /* MPLS label is 0 for EAD-ES route */ + + /* Set up extended community */ + bgp_evpn_type1_es_route_extcomm_build(es_frag, &attr); + + /* First, create (or fetch) route node within the ES. */ + /* NOTE: There is no RD here. */ + /* XXX: fragment ID must be included as a part of the prefix. */ + dest = bgp_node_get(es->route_table, (struct prefix *)p); + + /* Create or update route entry. */ + ret = bgp_evpn_mh_route_update(bgp, es, vpn, afi, safi, dest, + &attr, &pi, &route_changed); + if (ret != 0) { + flog_err( + EC_BGP_ES_INVALID, + "%u ERROR: Failed to updated EAD-ES route ESI: %s VTEP %pI4", + bgp->vrf_id, es->esi_str, &es->originator_ip); + } + global_rd = &es_frag->prd; + } + + + assert(pi); + attr_new = pi->attr; + + /* Perform route selection; + * this is just to set the flags correctly as local route in + * the ES always wins. + */ + evpn_route_select_install(bgp, vpn, dest, pi); + bgp_dest_unlock_node(dest); + + /* If this is a new route or some attribute has changed, export the + * route to the global table. The route will be advertised to peers + * from there. Note that this table is a 2-level tree (RD-level + + * Prefix-level) similar to L3VPN routes. + */ + if (route_changed) { + struct bgp_path_info *global_pi; + + dest = bgp_evpn_global_node_get(bgp->rib[afi][safi], afi, safi, + p, global_rd, NULL); + bgp_evpn_mh_route_update(bgp, es, vpn, afi, safi, dest, + attr_new, &global_pi, &route_changed); + + /* Schedule for processing and unlock node. */ + bgp_process(bgp, dest, global_pi, afi, safi); + bgp_dest_unlock_node(dest); + } + + /* Unintern temporary. */ + aspath_unintern(&attr.aspath); + return 0; +} + +/* + * This function is called when the export RT for a VNI changes. + * Update all type-1 local routes for this VNI from VNI/ES tables and the global + * table and advertise these routes to peers. + */ + +static void bgp_evpn_ead_es_route_update(struct bgp *bgp, + struct bgp_evpn_es *es) +{ + struct listnode *node; + struct bgp_evpn_es_frag *es_frag; + struct prefix_evpn p; + + build_evpn_type1_prefix(&p, BGP_EVPN_AD_ES_ETH_TAG, &es->esi, + es->originator_ip); + for (ALL_LIST_ELEMENTS_RO(es->es_frag_list, node, es_frag)) { + if (!listcount(es_frag->es_evi_frag_list)) + continue; + + p.prefix.ead_addr.frag_id = es_frag->rd_id; + if (bgp_evpn_type1_route_update(bgp, es, NULL, es_frag, &p)) + flog_err( + EC_BGP_EVPN_ROUTE_CREATE, + "EAD-ES route creation failure for ESI %s frag %u", + es->esi_str, es_frag->rd_id); + } +} + +static void bgp_evpn_ead_evi_route_update(struct bgp *bgp, + struct bgp_evpn_es *es, + struct bgpevpn *vpn, + struct prefix_evpn *p) +{ + if (bgp_evpn_type1_route_update(bgp, es, vpn, NULL, p)) + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "EAD-EVI route creation failure for ESI %s VNI %u", + es->esi_str, vpn->vni); +} + +void update_type1_routes_for_evi(struct bgp *bgp, struct bgpevpn *vpn) +{ + struct prefix_evpn p; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + + + RB_FOREACH (es_evi, bgp_es_evi_rb_head, &vpn->es_evi_rb_tree) { + es = es_evi->es; + + if (es_evi->vpn != vpn) + continue; + + /* Update EAD-ES */ + if (bgp_evpn_local_es_is_active(es)) + bgp_evpn_ead_es_route_update(bgp, es); + + /* Update EAD-EVI */ + if (CHECK_FLAG(es->flags, BGP_EVPNES_ADV_EVI)) { + build_evpn_type1_prefix(&p, BGP_EVPN_AD_EVI_ETH_TAG, + &es->esi, es->originator_ip); + bgp_evpn_ead_evi_route_update(bgp, es, vpn, &p); + } + } +} + +/* Delete local Type-1 route */ +static void bgp_evpn_ead_es_route_delete(struct bgp *bgp, + struct bgp_evpn_es *es) +{ + struct listnode *node; + struct bgp_evpn_es_frag *es_frag; + struct prefix_evpn p; + + build_evpn_type1_prefix(&p, BGP_EVPN_AD_ES_ETH_TAG, &es->esi, + es->originator_ip); + for (ALL_LIST_ELEMENTS_RO(es->es_frag_list, node, es_frag)) { + p.prefix.ead_addr.frag_id = es_frag->rd_id; + bgp_evpn_mh_route_delete(bgp, es, NULL, es_frag, &p); + } +} + +static int bgp_evpn_ead_evi_route_delete(struct bgp *bgp, + struct bgp_evpn_es *es, + struct bgpevpn *vpn, + struct prefix_evpn *p) +{ + return bgp_evpn_mh_route_delete(bgp, es, vpn, NULL, p); +} + +/* Generate EAD-EVI for all VNIs */ +static void bgp_evpn_local_type1_evi_route_add(struct bgp *bgp, + struct bgp_evpn_es *es) +{ + struct listnode *evi_node; + struct prefix_evpn p; + struct bgp_evpn_es_evi *es_evi; + + /* EAD-per-EVI routes have been suppressed */ + if (!bgp_mh_info->ead_evi_tx) + return; + + if (CHECK_FLAG(es->flags, BGP_EVPNES_ADV_EVI)) + /* EAD-EVI route add for this ES is already done */ + return; + + SET_FLAG(es->flags, BGP_EVPNES_ADV_EVI); + build_evpn_type1_prefix(&p, BGP_EVPN_AD_EVI_ETH_TAG, + &es->esi, es->originator_ip); + + for (ALL_LIST_ELEMENTS_RO(es->es_evi_list, evi_node, es_evi)) { + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + continue; + bgp_evpn_ead_evi_route_update(bgp, es, es_evi->vpn, &p); + } +} + +/* + * Withdraw EAD-EVI for all VNIs + */ +static void bgp_evpn_local_type1_evi_route_del(struct bgp *bgp, + struct bgp_evpn_es *es) +{ + struct listnode *evi_node; + struct prefix_evpn p; + struct bgp_evpn_es_evi *es_evi; + + /* Delete and withdraw locally learnt EAD-EVI route */ + if (!CHECK_FLAG(es->flags, BGP_EVPNES_ADV_EVI)) + /* EAD-EVI route has not been advertised for this ES */ + return; + + UNSET_FLAG(es->flags, BGP_EVPNES_ADV_EVI); + build_evpn_type1_prefix(&p, BGP_EVPN_AD_EVI_ETH_TAG, + &es->esi, es->originator_ip); + for (ALL_LIST_ELEMENTS_RO(es->es_evi_list, evi_node, es_evi)) { + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + continue; + if (bgp_evpn_mh_route_delete(bgp, es, es_evi->vpn, NULL, &p)) + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "%u: Type4 route creation failure for ESI %s", + bgp->vrf_id, es->esi_str); + } +} + +/* + * Process received EVPN type-1 route (advertise or withdraw). + */ +int bgp_evpn_type1_route_process(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id) +{ + struct prefix_rd prd; + esi_t esi; + uint32_t eth_tag; + mpls_label_t label; + struct in_addr vtep_ip; + struct prefix_evpn p; + + if (psize != BGP_EVPN_TYPE1_PSIZE) { + flog_err(EC_BGP_EVPN_ROUTE_INVALID, + "%u:%s - Rx EVPN Type-1 NLRI with invalid length %d", + peer->bgp->vrf_id, peer->host, psize); + return -1; + } + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, pfx, RD_BYTES); + pfx += RD_BYTES; + + /* get the ESI */ + memcpy(&esi, pfx, ESI_BYTES); + pfx += ESI_BYTES; + + /* Copy Ethernet Tag */ + memcpy(ð_tag, pfx, EVPN_ETH_TAG_BYTES); + eth_tag = ntohl(eth_tag); + pfx += EVPN_ETH_TAG_BYTES; + + memcpy(&label, pfx, BGP_LABEL_BYTES); + + /* EAD route prefix doesn't include the nexthop in the global + * table + */ + vtep_ip.s_addr = INADDR_ANY; + build_evpn_type1_prefix(&p, eth_tag, &esi, vtep_ip); + /* Process the route. */ + if (attr) { + bgp_update(peer, (struct prefix *)&p, addpath_id, attr, afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, + 0, 0, NULL); + } else { + bgp_withdraw(peer, (struct prefix *)&p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, &prd, NULL, 0, + NULL); + } + return 0; +} + +void bgp_evpn_mh_config_ead_export_rt(struct bgp *bgp, + struct ecommunity *ecomcfg, bool del) +{ + struct listnode *node, *nnode, *node_to_del; + struct ecommunity *ecom; + struct bgp_evpn_es *es; + + if (del) { + if (ecomcfg == NULL) { + /* Reset to default and process all routes. */ + for (ALL_LIST_ELEMENTS(bgp_mh_info->ead_es_export_rtl, + node, nnode, ecom)) { + ecommunity_free(&ecom); + list_delete_node(bgp_mh_info->ead_es_export_rtl, + node); + } + } + + /* Delete a specific export RT */ + else { + node_to_del = NULL; + + for (ALL_LIST_ELEMENTS(bgp_mh_info->ead_es_export_rtl, + node, nnode, ecom)) { + if (ecommunity_match(ecom, ecomcfg)) { + ecommunity_free(&ecom); + node_to_del = node; + break; + } + } + + assert(node_to_del); + list_delete_node(bgp_mh_info->ead_es_export_rtl, + node_to_del); + } + } else { + listnode_add_sort(bgp_mh_info->ead_es_export_rtl, ecomcfg); + } + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("local ES del/re-add EAD route on export RT change"); + /* + * walk through all active ESs withdraw the old EAD and + * generate a new one + */ + RB_FOREACH (es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree) { + if (!bgp_evpn_is_es_local(es) || + !bgp_evpn_local_es_is_active(es)) + continue; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "local ES %s del/re-add EAD route on export RT change", + es->esi_str); + + /* + * withdraw EAD-ES. XXX - this should technically not be + * needed; can be removed after testing + */ + bgp_evpn_ead_es_route_delete(bgp, es); + + /* generate EAD-ES */ + bgp_evpn_ead_es_route_update(bgp, es); + } +} + +/*****************************************************************************/ +/* Ethernet Segment Management + * 1. Ethernet Segment is a collection of links attached to the same + * server (MHD) or switch (MHN) + * 2. An Ethernet Segment can span multiple PEs and is identified by the + * 10-byte ES-ID. + * 3. Local ESs are configured in zebra and sent to BGP + * 4. Remote ESs are created by BGP when one or more ES-EVIs reference it i.e. + * created on first reference and release on last de-reference + * 5. An ES can be both local and remote. Infact most local ESs are expected + * to have an ES peer. + */ + +/* A list of remote VTEPs is maintained for each ES. This list includes - + * 1. VTEPs for which we have imported the ESR i.e. ES-peers + * 2. VTEPs that have an "active" ES-EVI VTEP i.e. EAD-per-ES and EAD-per-EVI + * have been imported into one or more VNIs + */ +static int bgp_evpn_es_vtep_cmp(void *p1, void *p2) +{ + const struct bgp_evpn_es_vtep *es_vtep1 = p1; + const struct bgp_evpn_es_vtep *es_vtep2 = p2; + + return es_vtep1->vtep_ip.s_addr - es_vtep2->vtep_ip.s_addr; +} + +static struct bgp_evpn_es_vtep *bgp_evpn_es_vtep_new(struct bgp_evpn_es *es, + struct in_addr vtep_ip) +{ + struct bgp_evpn_es_vtep *es_vtep; + + es_vtep = XCALLOC(MTYPE_BGP_EVPN_ES_VTEP, sizeof(*es_vtep)); + + es_vtep->es = es; + es_vtep->vtep_ip.s_addr = vtep_ip.s_addr; + inet_ntop(AF_INET, &es_vtep->vtep_ip, es_vtep->vtep_str, + sizeof(es_vtep->vtep_str)); + listnode_init(&es_vtep->es_listnode, es_vtep); + listnode_add_sort(es->es_vtep_list, &es_vtep->es_listnode); + + return es_vtep; +} + +static void bgp_evpn_es_vtep_free(struct bgp_evpn_es_vtep *es_vtep) +{ + struct bgp_evpn_es *es = es_vtep->es; + + if (CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ESR) || + es_vtep->evi_cnt) + /* as long as there is some reference we can't free it */ + return; + + list_delete_node(es->es_vtep_list, &es_vtep->es_listnode); + XFREE(MTYPE_BGP_EVPN_ES_VTEP, es_vtep); +} + +/* check if VTEP is already part of the list */ +static struct bgp_evpn_es_vtep *bgp_evpn_es_vtep_find(struct bgp_evpn_es *es, + struct in_addr vtep_ip) +{ + struct listnode *node = NULL; + struct bgp_evpn_es_vtep *es_vtep; + + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { + if (es_vtep->vtep_ip.s_addr == vtep_ip.s_addr) + return es_vtep; + } + return NULL; +} + +/* Send the remote ES to zebra for NHG programming */ +static enum zclient_send_status +bgp_zebra_send_remote_es_vtep(struct bgp *bgp, struct bgp_evpn_es_vtep *es_vtep, + bool add) +{ + struct bgp_evpn_es *es = es_vtep->es; + struct stream *s; + uint32_t flags = 0; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: No zclient or zclient->sock exists", + __func__); + return ZCLIENT_SEND_SUCCESS; + } + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("No zebra instance, not installing remote es %s", + es->esi_str); + return ZCLIENT_SEND_SUCCESS; + } + + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) + flags |= ZAPI_ES_VTEP_FLAG_ESR_RXED; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + add ? ZEBRA_REMOTE_ES_VTEP_ADD : ZEBRA_REMOTE_ES_VTEP_DEL, + bgp->vrf_id); + stream_put(s, &es->esi, sizeof(esi_t)); + stream_put_ipv4(s, es_vtep->vtep_ip.s_addr); + if (add) { + stream_putl(s, flags); + stream_putc(s, es_vtep->df_alg); + stream_putw(s, es_vtep->df_pref); + } + + stream_putw_at(s, 0, stream_get_endp(s)); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("Tx %s Remote ESI %s VTEP %pI4", add ? "ADD" : "DEL", + es->esi_str, &es_vtep->vtep_ip); + + frrtrace(3, frr_bgp, evpn_mh_vtep_zsend, add, es, es_vtep); + + return zclient_send_message(zclient); +} + +static enum zclient_send_status bgp_evpn_es_vtep_re_eval_active( + struct bgp *bgp, struct bgp_evpn_es_vtep *es_vtep, bool param_change) +{ + bool old_active; + bool new_active; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + old_active = CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE); + /* currently we need an active EVI reference to use the VTEP as + * a nexthop. this may change... + */ + if (es_vtep->evi_cnt) + SET_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE); + else + UNSET_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE); + + new_active = CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE); + + if ((old_active != new_active) || (new_active && param_change)) { + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vtep %pI4 %s df %u/%u", + es_vtep->es->esi_str, &es_vtep->vtep_ip, + new_active ? "active" : "inactive", + es_vtep->df_alg, es_vtep->df_pref); + + /* send remote ES to zebra */ + ret = bgp_zebra_send_remote_es_vtep(bgp, es_vtep, new_active); + + /* The NHG is updated first for efficient failover handling. + * Note the NHG can be de-activated while there are bgp + * routes referencing it. Zebra is capable of handling that + * elegantly by holding the NHG till all routes using it are + * removed. + */ + bgp_evpn_l3nhg_update_on_vtep_chg(es_vtep->es); + /* queue up the es for background consistency checks */ + bgp_evpn_es_cons_checks_pend_add(es_vtep->es); + } + + return ret; +} + +static struct bgp_evpn_es_vtep * +bgp_evpn_es_vtep_add(struct bgp *bgp, struct bgp_evpn_es *es, + struct in_addr vtep_ip, bool esr, uint8_t df_alg, + uint16_t df_pref, int *zret) +{ + struct bgp_evpn_es_vtep *es_vtep; + bool param_change = false; + + es_vtep = bgp_evpn_es_vtep_find(es, vtep_ip); + + if (!es_vtep) + es_vtep = bgp_evpn_es_vtep_new(es, vtep_ip); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vtep %pI4 add %s df %u/%u", + es_vtep->es->esi_str, &es_vtep->vtep_ip, + esr ? "esr" : "ead", df_alg, df_pref); + + if (esr) { + SET_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ESR); + if ((es_vtep->df_pref != df_pref) + || (es_vtep->df_alg != df_alg)) { + param_change = true; + es_vtep->df_pref = df_pref; + es_vtep->df_alg = df_alg; + } + } else { + ++es_vtep->evi_cnt; + } + + *zret = bgp_evpn_es_vtep_re_eval_active(bgp, es_vtep, param_change); + + return es_vtep; +} + +static enum zclient_send_status +bgp_evpn_es_vtep_do_del(struct bgp *bgp, struct bgp_evpn_es_vtep *es_vtep, + bool esr) +{ + bool param_change = false; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vtep %pI4 del %s", es_vtep->es->esi_str, + &es_vtep->vtep_ip, esr ? "esr" : "ead"); + if (esr) { + UNSET_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ESR); + if (es_vtep->df_pref || es_vtep->df_alg) { + param_change = true; + es_vtep->df_pref = 0; + es_vtep->df_alg = 0; + } + } else { + if (es_vtep->evi_cnt) + --es_vtep->evi_cnt; + } + + ret = bgp_evpn_es_vtep_re_eval_active(bgp, es_vtep, param_change); + bgp_evpn_es_vtep_free(es_vtep); + + return ret; +} + +static enum zclient_send_status bgp_evpn_es_vtep_del(struct bgp *bgp, + struct bgp_evpn_es *es, + struct in_addr vtep_ip, + bool esr) +{ + struct bgp_evpn_es_vtep *es_vtep; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + es_vtep = bgp_evpn_es_vtep_find(es, vtep_ip); + if (es_vtep) + ret = bgp_evpn_es_vtep_do_del(bgp, es_vtep, esr); + + return ret; +} + +/********************** ES MAC-IP paths ************************************* + * 1. Local MAC-IP routes in the VNI routing table are linked to the + * destination ES (macip_evi_path_list) for efficient updates on ES oper + * state changes. + * 2. Non-local MAC-IP routes in the global routing table are linked to + * the detination for efficient updates on - + * a. VTEP add/del - this results in a L3NHG update. + * b. ES-VRF add/del - this may result in the host route being migrated to + * L3NHG or vice versa (flat multipath list). + ****************************************************************************/ +static void bgp_evpn_path_es_info_free(struct bgp_path_es_info *es_info) +{ + bgp_evpn_path_es_unlink(es_info); + XFREE(MTYPE_BGP_EVPN_PATH_ES_INFO, es_info); +} + +void bgp_evpn_path_mh_info_free(struct bgp_path_mh_info *mh_info) +{ + if (mh_info->es_info) + bgp_evpn_path_es_info_free(mh_info->es_info); + if (mh_info->nh_info) + bgp_evpn_path_nh_info_free(mh_info->nh_info); + XFREE(MTYPE_BGP_EVPN_PATH_MH_INFO, mh_info); +} + +static struct bgp_path_es_info * +bgp_evpn_path_es_info_new(struct bgp_path_info *pi, vni_t vni) +{ + struct bgp_path_info_extra *e; + struct bgp_path_mh_info *mh_info; + struct bgp_path_es_info *es_info; + + e = bgp_path_info_extra_get(pi); + + /* If mh_info doesn't exist allocate it */ + mh_info = e->evpn->mh_info; + if (!mh_info) + e->evpn->mh_info = mh_info = + XCALLOC(MTYPE_BGP_EVPN_PATH_MH_INFO, + sizeof(struct bgp_path_mh_info)); + + /* If es_info doesn't exist allocate it */ + es_info = mh_info->es_info; + if (!es_info) { + mh_info->es_info = es_info = + XCALLOC(MTYPE_BGP_EVPN_PATH_ES_INFO, + sizeof(struct bgp_path_es_info)); + es_info->vni = vni; + es_info->pi = pi; + } + + return es_info; +} + +static void bgp_evpn_path_es_unlink(struct bgp_path_es_info *es_info) +{ + struct bgp_evpn_es *es = es_info->es; + struct bgp_path_info *pi; + + if (!es) + return; + + pi = es_info->pi; + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("vni %u path %pFX unlinked from es %s", es_info->vni, + &pi->net->rn->p, es->esi_str); + + if (es_info->vni) + list_delete_node(es->macip_evi_path_list, + &es_info->es_listnode); + else + list_delete_node(es->macip_global_path_list, + &es_info->es_listnode); + + es_info->es = NULL; + + /* if there are no other references against the ES it + * needs to be freed + */ + bgp_evpn_es_free(es, __func__); + + /* Note we don't free the path es_info on unlink; it will be freed up + * along with the path. + */ +} + +void bgp_evpn_path_es_link(struct bgp_path_info *pi, vni_t vni, esi_t *esi) +{ + struct bgp_path_es_info *es_info; + struct bgp_evpn_es *es; + struct bgp *bgp_evpn; + + es_info = (pi->extra && pi->extra->evpn && pi->extra->evpn->mh_info) + ? pi->extra->evpn->mh_info->es_info + : NULL; + /* if the esi is zero just unlink the path from the old es */ + if (!esi || !memcmp(esi, zero_esi, sizeof(*esi))) { + if (es_info) + bgp_evpn_path_es_unlink(es_info); + return; + } + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return; + + /* setup es_info against the path if it doesn't aleady exist */ + if (!es_info) + es_info = bgp_evpn_path_es_info_new(pi, vni); + + /* find-create ES */ + es = bgp_evpn_es_find(esi); + if (!es) + es = bgp_evpn_es_new(bgp_evpn, esi); + + /* dup check */ + if (es_info->es == es) + return; + + /* unlink old ES if any */ + bgp_evpn_path_es_unlink(es_info); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("vni %u path %pFX linked to es %s", vni, &pi->net->rn->p, + es->esi_str); + + /* link mac-ip path to the new destination ES */ + es_info->es = es; + listnode_init(&es_info->es_listnode, es_info); + if (es_info->vni) + listnode_add(es->macip_evi_path_list, &es_info->es_listnode); + else + listnode_add(es->macip_global_path_list, &es_info->es_listnode); +} + +static bool bgp_evpn_is_macip_path(struct bgp_path_info *pi) +{ + struct prefix_evpn *evp; + + /* Only MAC-IP routes need to be linked (MAC-only routes can be + * skipped) as these lists are maintained for managing + * host routes in the tenant VRF + */ + evp = (struct prefix_evpn *)&pi->net->rn->p; + return is_evpn_prefix_ipaddr_v4(evp) || is_evpn_prefix_ipaddr_v6(evp); +} + +/* When a remote ES is added to a VRF, routes using that as + * a destination need to be migrated to a L3NHG or viceversa. + * This is done indirectly by re-attempting an install of the + * route in the associated VRFs. As a part of the VRF install use + * of l3 NHG is evaluated and this results in the + * attr.es_flag ATTR_ES_L3_NHG_USE being set or cleared. + */ +static void +bgp_evpn_es_path_update_on_es_vrf_chg(struct bgp_evpn_es_vrf *es_vrf, + const char *reason) +{ + struct listnode *node; + struct bgp_path_es_info *es_info; + struct bgp_path_info *pi; + struct bgp_evpn_es *es = es_vrf->es; + + if (!bgp_mh_info->host_routes_use_l3nhg) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("update paths linked to es %s on es-vrf %s %s", + es->esi_str, es_vrf->bgp_vrf->name_pretty, reason); + + for (ALL_LIST_ELEMENTS_RO(es->macip_global_path_list, node, es_info)) { + pi = es_info->pi; + + if (!bgp_evpn_is_macip_path(pi)) + continue; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "update path %pFX linked to es %s on vrf chg", + &pi->net->rn->p, es->esi_str); + bgp_evpn_route_entry_install_if_vrf_match(es_vrf->bgp_vrf, pi, + 1); + } +} + +static void bgp_evpn_es_frag_free(struct bgp_evpn_es_frag *es_frag) +{ + struct bgp_evpn_es *es = es_frag->es; + + if (es->es_base_frag == es_frag) + es->es_base_frag = NULL; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s frag %u free", es->esi_str, es_frag->rd_id); + list_delete_node(es->es_frag_list, &es_frag->es_listnode); + + /* EVIs that are advertised using the info in this fragment */ + list_delete(&es_frag->es_evi_frag_list); + + bf_release_index(bm->rd_idspace, es_frag->rd_id); + + + XFREE(MTYPE_BGP_EVPN_ES_FRAG, es_frag); +} + +static void bgp_evpn_es_frag_free_unused(struct bgp_evpn_es_frag *es_frag) +{ + if ((es_frag->es->es_base_frag == es_frag) || + listcount(es_frag->es_evi_frag_list)) + return; + + bgp_evpn_es_frag_free(es_frag); +} + +static void bgp_evpn_es_frag_free_all(struct bgp_evpn_es *es) +{ + struct listnode *node; + struct listnode *nnode; + struct bgp_evpn_es_frag *es_frag; + + for (ALL_LIST_ELEMENTS(es->es_frag_list, node, nnode, es_frag)) + bgp_evpn_es_frag_free(es_frag); +} + +static struct bgp_evpn_es_frag *bgp_evpn_es_frag_new(struct bgp_evpn_es *es) +{ + struct bgp_evpn_es_frag *es_frag; + char buf[BGP_EVPN_PREFIX_RD_LEN]; + struct bgp *bgp; + + es_frag = XCALLOC(MTYPE_BGP_EVPN_ES_FRAG, sizeof(*es_frag)); + bf_assign_index(bm->rd_idspace, es_frag->rd_id); + es_frag->prd.family = AF_UNSPEC; + es_frag->prd.prefixlen = 64; + bgp = bgp_get_evpn(); + snprintfrr(buf, sizeof(buf), "%pI4:%hu", &bgp->router_id, + es_frag->rd_id); + (void)str2prefix_rd(buf, &es_frag->prd); + + /* EVIs that are advertised using the info in this fragment */ + es_frag->es_evi_frag_list = list_new(); + listset_app_node_mem(es_frag->es_evi_frag_list); + + /* Link the fragment to the parent ES */ + es_frag->es = es; + listnode_init(&es_frag->es_listnode, es_frag); + listnode_add(es->es_frag_list, &es_frag->es_listnode); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s frag %u new", es->esi_str, es_frag->rd_id); + return es_frag; +} + +static struct bgp_evpn_es_frag * +bgp_evpn_es_find_frag_with_space(struct bgp_evpn_es *es) +{ + struct listnode *node; + struct bgp_evpn_es_frag *es_frag; + + for (ALL_LIST_ELEMENTS_RO(es->es_frag_list, node, es_frag)) { + if (listcount(es_frag->es_evi_frag_list) < + bgp_mh_info->evi_per_es_frag) + return es_frag; + } + + /* No frags where found with space; allocate a new one */ + return bgp_evpn_es_frag_new(es); +} + +/* Link the ES-EVI to one of the ES fragments */ +static void bgp_evpn_es_frag_evi_add(struct bgp_evpn_es_evi *es_evi) +{ + struct bgp_evpn_es_frag *es_frag; + struct bgp_evpn_es *es = es_evi->es; + + if (es_evi->es_frag || + !(CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL))) + return; + + es_frag = bgp_evpn_es_find_frag_with_space(es); + + es_evi->es_frag = es_frag; + listnode_init(&es_evi->es_frag_listnode, es_evi); + listnode_add(es_frag->es_evi_frag_list, &es_evi->es_frag_listnode); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vni %d linked to frag %u", es->esi_str, + es_evi->vpn->vni, es_frag->rd_id); +} + +/* UnLink the ES-EVI from the ES fragment */ +static void bgp_evpn_es_frag_evi_del(struct bgp_evpn_es_evi *es_evi, + bool send_ead_del_if_empty) +{ + struct bgp_evpn_es_frag *es_frag = es_evi->es_frag; + struct prefix_evpn p; + struct bgp_evpn_es *es; + struct bgp *bgp; + + if (!es_frag) + return; + + es = es_frag->es; + es_evi->es_frag = NULL; + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vni %d unlinked from frag %u", es->esi_str, + es_evi->vpn->vni, es_frag->rd_id); + + list_delete_node(es_frag->es_evi_frag_list, &es_evi->es_frag_listnode); + + /* + * if there are no other EVIs on the fragment deleted the EAD-ES for + * the fragment + */ + if (send_ead_del_if_empty && !listcount(es_frag->es_evi_frag_list)) { + bgp = bgp_get_evpn(); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s frag %u ead-es route delete", + es->esi_str, es_frag->rd_id); + build_evpn_type1_prefix(&p, BGP_EVPN_AD_ES_ETH_TAG, &es->esi, + es->originator_ip); + p.prefix.ead_addr.frag_id = es_frag->rd_id; + bgp_evpn_mh_route_delete(bgp, es, NULL, es_frag, &p); + } + + /* We don't attempt to coalesce frags that may not be full. Instead we + * only free up the frag when it is completely empty. + */ + bgp_evpn_es_frag_free_unused(es_frag); +} + +/* Link the ES-EVIs to one of the ES fragments */ +static void bgp_evpn_es_frag_evi_update_all(struct bgp_evpn_es *es, bool add) +{ + struct listnode *node; + struct bgp_evpn_es_evi *es_evi; + + for (ALL_LIST_ELEMENTS_RO(es->es_evi_list, node, es_evi)) { + if (add) + bgp_evpn_es_frag_evi_add(es_evi); + else + bgp_evpn_es_frag_evi_del(es_evi, false); + } +} + +/* compare ES-IDs for the global ES RB tree */ +static int bgp_es_rb_cmp(const struct bgp_evpn_es *es1, + const struct bgp_evpn_es *es2) +{ + return memcmp(&es1->esi, &es2->esi, ESI_BYTES); +} +RB_GENERATE(bgp_es_rb_head, bgp_evpn_es, rb_node, bgp_es_rb_cmp); + +struct bgp_evpn_es *bgp_evpn_es_find(const esi_t *esi) +{ + struct bgp_evpn_es tmp; + + memcpy(&tmp.esi, esi, sizeof(esi_t)); + return RB_FIND(bgp_es_rb_head, &bgp_mh_info->es_rb_tree, &tmp); +} + +static struct bgp_evpn_es *bgp_evpn_es_new(struct bgp *bgp, const esi_t *esi) +{ + struct bgp_evpn_es *es; + + es = XCALLOC(MTYPE_BGP_EVPN_ES, sizeof(struct bgp_evpn_es)); + + /* set the ESI */ + memcpy(&es->esi, esi, sizeof(esi_t)); + + /* Initialise the VTEP list */ + es->es_vtep_list = list_new(); + listset_app_node_mem(es->es_vtep_list); + es->es_vtep_list->cmp = bgp_evpn_es_vtep_cmp; + + esi_to_str(&es->esi, es->esi_str, sizeof(es->esi_str)); + + /* Initialize the ES routing table */ + es->route_table = bgp_table_init(bgp, AFI_L2VPN, SAFI_EVPN); + + /* Add to rb_tree */ + RB_INSERT(bgp_es_rb_head, &bgp_mh_info->es_rb_tree, es); + + /* Initialise the ES-EVI list */ + es->es_evi_list = list_new(); + listset_app_node_mem(es->es_evi_list); + + /* Initialise the ES-VRF list used for L3NHG management */ + es->es_vrf_list = list_new(); + listset_app_node_mem(es->es_vrf_list); + + /* Initialise the route list used for efficient event handling */ + es->macip_evi_path_list = list_new(); + listset_app_node_mem(es->macip_evi_path_list); + es->macip_global_path_list = list_new(); + listset_app_node_mem(es->macip_global_path_list); + es->es_frag_list = list_new(); + listset_app_node_mem(es->es_frag_list); + + QOBJ_REG(es, bgp_evpn_es); + + return es; +} + +/* Free a given ES - + * This just frees appropriate memory, caller should have taken other + * needed actions. + */ +static void bgp_evpn_es_free(struct bgp_evpn_es *es, const char *caller) +{ + if ((es->flags & (BGP_EVPNES_LOCAL | BGP_EVPNES_REMOTE)) + || listcount(es->macip_evi_path_list) + || listcount(es->macip_global_path_list)) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("%s: es %s free", caller, es->esi_str); + + /* cleanup resources maintained against the ES */ + list_delete(&es->es_evi_list); + list_delete(&es->es_vrf_list); + list_delete(&es->es_vtep_list); + list_delete(&es->macip_evi_path_list); + list_delete(&es->macip_global_path_list); + list_delete(&es->es_frag_list); + bgp_table_unlock(es->route_table); + + /* remove the entry from various databases */ + RB_REMOVE(bgp_es_rb_head, &bgp_mh_info->es_rb_tree, es); + bgp_evpn_es_cons_checks_pend_del(es); + + QOBJ_UNREG(es); + XFREE(MTYPE_BGP_EVPN_ES, es); +} + +static inline bool bgp_evpn_is_es_local_and_non_bypass(struct bgp_evpn_es *es) +{ + return (es->flags & BGP_EVPNES_LOCAL) + && !(es->flags & BGP_EVPNES_BYPASS); +} + +/* init local info associated with the ES */ +static void bgp_evpn_es_local_info_set(struct bgp *bgp, struct bgp_evpn_es *es) +{ + bool old_is_local; + bool is_local; + + if (CHECK_FLAG(es->flags, BGP_EVPNES_LOCAL)) + return; + + old_is_local = bgp_evpn_is_es_local_and_non_bypass(es); + SET_FLAG(es->flags, BGP_EVPNES_LOCAL); + + listnode_init(&es->es_listnode, es); + listnode_add(bgp_mh_info->local_es_list, &es->es_listnode); + + /* setup the first ES fragment; more fragments may be allocated based + * on the the number of EVI entries + */ + es->es_base_frag = bgp_evpn_es_frag_new(es); + /* distribute ES-EVIs to one or more ES fragments */ + bgp_evpn_es_frag_evi_update_all(es, true); + + is_local = bgp_evpn_is_es_local_and_non_bypass(es); + if (old_is_local != is_local) + bgp_evpn_mac_update_on_es_local_chg(es, is_local); +} + +/* clear any local info associated with the ES */ +static void bgp_evpn_es_local_info_clear(struct bgp_evpn_es *es, bool finish) +{ + bool old_is_local; + bool is_local; + + if (!CHECK_FLAG(es->flags, BGP_EVPNES_LOCAL)) + return; + + /* clear the es frag references and free them up */ + bgp_evpn_es_frag_evi_update_all(es, false); + es->es_base_frag = NULL; + bgp_evpn_es_frag_free_all(es); + + old_is_local = bgp_evpn_is_es_local_and_non_bypass(es); + UNSET_FLAG(es->flags, BGP_EVPNES_LOCAL); + + is_local = bgp_evpn_is_es_local_and_non_bypass(es); + if (!finish && (old_is_local != is_local)) + bgp_evpn_mac_update_on_es_local_chg(es, is_local); + + /* remove from the ES local list */ + list_delete_node(bgp_mh_info->local_es_list, &es->es_listnode); + + bgp_evpn_es_free(es, __func__); +} + +/* eval remote info associated with the ES */ +static void bgp_evpn_es_remote_info_re_eval(struct bgp_evpn_es *es) +{ + if (es->remote_es_evi_cnt) { + SET_FLAG(es->flags, BGP_EVPNES_REMOTE); + } else { + if (CHECK_FLAG(es->flags, BGP_EVPNES_REMOTE)) { + UNSET_FLAG(es->flags, BGP_EVPNES_REMOTE); + bgp_evpn_es_free(es, __func__); + } + } +} + +/* If ES is present and local it needs to be active/oper-up for + * including L3 EC + */ +bool bgp_evpn_es_add_l3_ecomm_ok(esi_t *esi) +{ + struct bgp_evpn_es *es; + + if (!esi || !bgp_mh_info->suppress_l3_ecomm_on_inactive_es) + return true; + + es = bgp_evpn_es_find(esi); + + return (!es || !(es->flags & BGP_EVPNES_LOCAL) + || bgp_evpn_local_es_is_active(es)); +} + +static bool bgp_evpn_is_valid_local_path(struct bgp_path_info *pi) +{ + return (CHECK_FLAG(pi->flags, BGP_PATH_VALID) + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC); +} + +/* Update all local MAC-IP routes in the VNI routing table associated + * with the ES. When the ES is down the routes are advertised without + * the L3 extcomm + */ +static void bgp_evpn_mac_update_on_es_oper_chg(struct bgp_evpn_es *es) +{ + struct listnode *node; + struct bgp_path_es_info *es_info; + struct bgp_path_info *pi; + struct bgp *bgp; + struct bgpevpn *vpn; + + if (!bgp_mh_info->suppress_l3_ecomm_on_inactive_es) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("update paths linked to es %s on oper chg", + es->esi_str); + + bgp = bgp_get_evpn(); + for (ALL_LIST_ELEMENTS_RO(es->macip_evi_path_list, node, es_info)) { + pi = es_info->pi; + + if (!bgp_evpn_is_valid_local_path(pi)) + continue; + + if (!bgp_evpn_is_macip_path(pi)) + continue; + + vpn = bgp_evpn_lookup_vni(bgp, es_info->vni); + if (!vpn) + continue; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "update path %d %pFX linked to es %s on oper chg", + es_info->vni, &pi->net->rn->p, es->esi_str); + + bgp_evpn_update_type2_route_entry(bgp, vpn, pi->net, pi, + __func__); + } +} + +static bool bgp_evpn_is_valid_bgp_path(struct bgp_path_info *pi) +{ + return (CHECK_FLAG(pi->flags, BGP_PATH_VALID) + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_NORMAL); +} + +/* If an ES is no longer local (or becomes local) we need to re-install + * paths using that ES as destination. This is needed as the criteria + * for best path selection has changed. + */ +static void bgp_evpn_mac_update_on_es_local_chg(struct bgp_evpn_es *es, + bool is_local) +{ + struct listnode *node; + struct bgp_path_es_info *es_info; + struct bgp_path_info *pi; + bool tmp_local; + struct attr *attr_new; + struct attr attr_tmp; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("update paths linked to es %s on chg to %s", + es->esi_str, is_local ? "local" : "non-local"); + + for (ALL_LIST_ELEMENTS_RO(es->macip_global_path_list, node, es_info)) { + pi = es_info->pi; + + /* Consider "valid" remote routes */ + if (!bgp_evpn_is_valid_bgp_path(pi)) + continue; + + if (!pi->attr) + continue; + + tmp_local = !!(pi->attr->es_flags & ATTR_ES_IS_LOCAL); + if (tmp_local == is_local) + continue; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug( + "update path %pFX linked to es %s on chg to %s", + &pi->net->rn->p, es->esi_str, + is_local ? "local" : "non-local"); + + attr_tmp = *pi->attr; + if (is_local) + attr_tmp.es_flags |= ATTR_ES_IS_LOCAL; + else + attr_tmp.es_flags &= ~ATTR_ES_IS_LOCAL; + attr_new = bgp_attr_intern(&attr_tmp); + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + bgp_evpn_import_type2_route(pi, 1); + } +} + +static void bgp_evpn_local_es_deactivate(struct bgp *bgp, + struct bgp_evpn_es *es) +{ + struct prefix_evpn p; + int ret; + + /* withdraw ESR */ + /* Delete and withdraw locally learnt ES route */ + build_evpn_type4_prefix(&p, &es->esi, es->originator_ip); + ret = bgp_evpn_type4_route_delete(bgp, es, &p); + if (ret) { + flog_err(EC_BGP_EVPN_ROUTE_DELETE, + "%u failed to delete type-4 route for ESI %s", + bgp->vrf_id, es->esi_str); + } + + /* withdraw EAD-EVI */ + if (!bgp_mh_info->ead_evi_adv_for_down_links) + bgp_evpn_local_type1_evi_route_del(bgp, es); + + /* withdraw EAD-ES */ + bgp_evpn_ead_es_route_delete(bgp, es); + + bgp_evpn_mac_update_on_es_oper_chg(es); +} + +/* Process ES link oper-down by withdrawing ES-EAD and ESR */ +static void bgp_evpn_local_es_down(struct bgp *bgp, struct bgp_evpn_es *es) +{ + bool old_active; + + if (!CHECK_FLAG(es->flags, BGP_EVPNES_OPER_UP)) + return; + + old_active = bgp_evpn_local_es_is_active(es); + UNSET_FLAG(es->flags, BGP_EVPNES_OPER_UP); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("local es %s down", es->esi_str); + + if (old_active) + bgp_evpn_local_es_deactivate(bgp, es); +} + +static void bgp_evpn_local_es_activate(struct bgp *bgp, struct bgp_evpn_es *es, + bool regen_ead, bool regen_esr) +{ + struct prefix_evpn p; + + if (regen_esr) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("local es %s generate ESR", es->esi_str); + /* generate ESR */ + build_evpn_type4_prefix(&p, &es->esi, es->originator_ip); + if (bgp_evpn_type4_route_update(bgp, es, &p)) + flog_err(EC_BGP_EVPN_ROUTE_CREATE, + "%u: Type4 route creation failure for ESI %s", + bgp->vrf_id, es->esi_str); + } + + if (regen_ead) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("local es %s generate EAD", es->esi_str); + /* generate EAD-EVI */ + bgp_evpn_local_type1_evi_route_add(bgp, es); + + /* generate EAD-ES */ + bgp_evpn_ead_es_route_update(bgp, es); + } + + bgp_evpn_mac_update_on_es_oper_chg(es); +} + +/* Process ES link oper-up by generating ES-EAD and ESR */ +static void bgp_evpn_local_es_up(struct bgp *bgp, struct bgp_evpn_es *es, + bool regen_esr) +{ + bool regen_ead = false; + bool active = false; + + if (!CHECK_FLAG(es->flags, BGP_EVPNES_OPER_UP)) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("local es %s up", es->esi_str); + + SET_FLAG(es->flags, BGP_EVPNES_OPER_UP); + regen_esr = true; + regen_ead = true; + } + + active = bgp_evpn_local_es_is_active(es); + if (active && (regen_ead || regen_esr)) + bgp_evpn_local_es_activate(bgp, es, regen_ead, regen_esr); +} + +/* If an ethernet segment is in LACP bypass we cannot advertise + * reachability to it i.e. EAD-per-ES and ESR is not advertised in + * bypass state. + * PS: EAD-per-EVI will continue to be advertised + */ +static void bgp_evpn_local_es_bypass_update(struct bgp *bgp, + struct bgp_evpn_es *es, bool bypass) +{ + bool old_bypass = !!(es->flags & BGP_EVPNES_BYPASS); + bool old_active; + bool new_active; + bool old_is_local; + bool is_local; + + if (bypass == old_bypass) + return; + + old_active = bgp_evpn_local_es_is_active(es); + old_is_local = bgp_evpn_is_es_local_and_non_bypass(es); + if (bypass) + SET_FLAG(es->flags, BGP_EVPNES_BYPASS); + else + UNSET_FLAG(es->flags, BGP_EVPNES_BYPASS); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("local es %s bypass %s", es->esi_str, + bypass ? "set" : "clear"); + + new_active = bgp_evpn_local_es_is_active(es); + if (old_active != new_active) { + if (new_active) + bgp_evpn_local_es_activate(bgp, es, true, true); + else + bgp_evpn_local_es_deactivate(bgp, es); + } + + is_local = bgp_evpn_is_es_local_and_non_bypass(es); + if (old_is_local != is_local) + bgp_evpn_mac_update_on_es_local_chg(es, is_local); +} + +static void bgp_evpn_local_es_do_del(struct bgp *bgp, struct bgp_evpn_es *es) +{ + struct bgp_evpn_es_evi *es_evi; + struct listnode *evi_node, *evi_next_node; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("del local es %s", es->esi_str); + + /* Delete all local EVPN ES routes from ESI table + * and schedule for processing (to withdraw from peers)) + */ + bgp_evpn_es_route_del_all(bgp, es); + + /* release all local ES EVIs associated with the ES */ + for (ALL_LIST_ELEMENTS(es->es_evi_list, evi_node, + evi_next_node, es_evi)) { + bgp_evpn_local_es_evi_do_del(es_evi); + } + + /* Clear local info associated with the ES and free it up if there is + * no remote reference + */ + bgp_evpn_es_local_info_clear(es, false); +} + +bool bgp_evpn_is_esi_local_and_non_bypass(esi_t *esi) +{ + struct bgp_evpn_es *es = NULL; + + /* Lookup ESI hash - should exist. */ + es = bgp_evpn_es_find(esi); + + return es && bgp_evpn_is_es_local_and_non_bypass(es); +} + +int bgp_evpn_local_es_del(struct bgp *bgp, esi_t *esi) +{ + struct bgp_evpn_es *es = NULL; + + /* Lookup ESI hash - should exist. */ + es = bgp_evpn_es_find(esi); + if (!es) { + flog_warn(EC_BGP_EVPN_ESI, "%u: ES missing at local ES DEL", + bgp->vrf_id); + return -1; + } + + bgp_evpn_local_es_do_del(bgp, es); + return 0; +} + +/* Handle device to ES id association. Results in the creation of a local + * ES. + */ +int bgp_evpn_local_es_add(struct bgp *bgp, esi_t *esi, + struct in_addr originator_ip, bool oper_up, + uint16_t df_pref, bool bypass) +{ + struct bgp_evpn_es *es; + bool new_es = true; + bool regen_esr = false; + + /* create the new es */ + es = bgp_evpn_es_find(esi); + if (es) { + if (CHECK_FLAG(es->flags, BGP_EVPNES_LOCAL)) + new_es = false; + } else + es = bgp_evpn_es_new(bgp, esi); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("add local es %s orig-ip %pI4 df_pref %u %s", + es->esi_str, &originator_ip, df_pref, + bypass ? "bypass" : ""); + + es->originator_ip = originator_ip; + if (df_pref != es->df_pref) { + es->df_pref = df_pref; + regen_esr = true; + } + bgp_evpn_es_local_info_set(bgp, es); + + /* import all remote Type-4 routes in the ES table */ + if (new_es) + bgp_evpn_type4_remote_routes_import(bgp, es, + true /* install */); + + /* create and advertise EAD-EVI routes for the ES - + * XXX - till an ES-EVI reference is created there is really nothing to + * advertise + */ + if (bgp_mh_info->ead_evi_adv_for_down_links) + bgp_evpn_local_type1_evi_route_add(bgp, es); + + bgp_evpn_local_es_bypass_update(bgp, es, bypass); + + /* If the ES link is operationally up generate EAD-ES. EAD-EVI + * can be generated even if the link is inactive. + */ + if (oper_up) + bgp_evpn_local_es_up(bgp, es, regen_esr); + else + bgp_evpn_local_es_down(bgp, es); + + return 0; +} + +static void bgp_evpn_es_json_frag_fill(json_object *json_frags, + struct bgp_evpn_es *es) +{ + json_object *json_frag; + struct listnode *node; + struct bgp_evpn_es_frag *es_frag; + + for (ALL_LIST_ELEMENTS_RO(es->es_frag_list, node, es_frag)) { + json_frag = json_object_new_object(); + + json_object_string_addf(json_frag, "rd", "%pRDP", + &es_frag->prd); + json_object_int_add(json_frag, "eviCount", + listcount(es_frag->es_evi_frag_list)); + + json_object_array_add(json_frags, json_frag); + } +} + +static void bgp_evpn_es_frag_show_detail(struct vty *vty, + struct bgp_evpn_es *es) +{ + struct listnode *node; + struct bgp_evpn_es_frag *es_frag; + + for (ALL_LIST_ELEMENTS_RO(es->es_frag_list, node, es_frag)) { + vty_out(vty, " %pRDP EVIs: %d\n", &es_frag->prd, + listcount(es_frag->es_evi_frag_list)); + } +} + +static char *bgp_evpn_es_vteps_str(char *vtep_str, struct bgp_evpn_es *es, + size_t vtep_str_size) +{ + char vtep_flag_str[BGP_EVPN_FLAG_STR_SZ]; + struct listnode *node; + struct bgp_evpn_es_vtep *es_vtep; + bool first = true; + char ip_buf[INET_ADDRSTRLEN]; + + vtep_str[0] = '\0'; + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { + vtep_flag_str[0] = '\0'; + + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) + strlcat(vtep_flag_str, "E", sizeof(vtep_flag_str)); + if (es_vtep->flags & BGP_EVPNES_VTEP_ACTIVE) + strlcat(vtep_flag_str, "A", sizeof(vtep_flag_str)); + + if (!strlen(vtep_flag_str)) + strlcat(vtep_flag_str, "-", sizeof(vtep_flag_str)); + if (first) + first = false; + else + strlcat(vtep_str, ",", vtep_str_size); + strlcat(vtep_str, + inet_ntop(AF_INET, &es_vtep->vtep_ip, ip_buf, + sizeof(ip_buf)), + vtep_str_size); + strlcat(vtep_str, "(", vtep_str_size); + strlcat(vtep_str, vtep_flag_str, vtep_str_size); + strlcat(vtep_str, ")", vtep_str_size); + } + + return vtep_str; +} + +static void bgp_evpn_es_json_vtep_fill(json_object *json_vteps, + struct bgp_evpn_es_vtep *es_vtep) +{ + json_object *json_vtep_entry; + json_object *json_flags; + char alg_buf[EVPN_DF_ALG_STR_LEN]; + + json_vtep_entry = json_object_new_object(); + + json_object_string_addf(json_vtep_entry, "vtep_ip", "%pI4", + &es_vtep->vtep_ip); + if (es_vtep->flags & (BGP_EVPNES_VTEP_ESR | + BGP_EVPNES_VTEP_ACTIVE)) { + json_flags = json_object_new_array(); + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) + json_array_string_add(json_flags, "esr"); + if (es_vtep->flags & BGP_EVPNES_VTEP_ACTIVE) + json_array_string_add(json_flags, "active"); + json_object_object_add(json_vtep_entry, "flags", json_flags); + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) { + json_object_int_add(json_vtep_entry, "dfPreference", + es_vtep->df_pref); + json_object_string_add( + json_vtep_entry, "dfAlgorithm", + evpn_es_df_alg2str(es_vtep->df_alg, alg_buf, + sizeof(alg_buf))); + } + } + + json_object_array_add(json_vteps, + json_vtep_entry); +} + +static void bgp_evpn_es_vteps_show_detail(struct vty *vty, + struct bgp_evpn_es *es) +{ + char vtep_flag_str[BGP_EVPN_FLAG_STR_SZ]; + struct listnode *node; + struct bgp_evpn_es_vtep *es_vtep; + char alg_buf[EVPN_DF_ALG_STR_LEN]; + + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { + vtep_flag_str[0] = '\0'; + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) + strlcat(vtep_flag_str, "E", sizeof(vtep_flag_str)); + if (es_vtep->flags & BGP_EVPNES_VTEP_ACTIVE) + strlcat(vtep_flag_str, "A", sizeof(vtep_flag_str)); + + if (!strlen(vtep_flag_str)) + strlcat(vtep_flag_str, "-", sizeof(vtep_flag_str)); + + vty_out(vty, " %pI4 flags: %s", &es_vtep->vtep_ip, + vtep_flag_str); + + if (es_vtep->flags & BGP_EVPNES_VTEP_ESR) + vty_out(vty, " df_alg: %s df_pref: %u\n", + evpn_es_df_alg2str(es_vtep->df_alg, alg_buf, + sizeof(alg_buf)), + es_vtep->df_pref); + else + vty_out(vty, "\n"); + } +} + +static void bgp_evpn_es_show_entry(struct vty *vty, + struct bgp_evpn_es *es, json_object *json) +{ + struct listnode *node; + struct bgp_evpn_es_vtep *es_vtep; + + if (json) { + json_object *json_vteps; + json_object *json_types; + + json_object_string_add(json, "esi", es->esi_str); + if (es->es_base_frag) + json_object_string_addf(json, "rd", "%pRDP", + &es->es_base_frag->prd); + + if (es->flags & (BGP_EVPNES_LOCAL | BGP_EVPNES_REMOTE)) { + json_types = json_object_new_array(); + if (es->flags & BGP_EVPNES_LOCAL) + json_array_string_add(json_types, "local"); + if (es->flags & BGP_EVPNES_REMOTE) + json_array_string_add(json_types, "remote"); + json_object_object_add(json, "type", json_types); + } + + if (listcount(es->es_vtep_list)) { + json_vteps = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, + node, es_vtep)) { + bgp_evpn_es_json_vtep_fill(json_vteps, es_vtep); + } + json_object_object_add(json, "vteps", json_vteps); + } + json_object_int_add(json, "vniCount", + listcount(es->es_evi_list)); + } else { + char type_str[4]; + char vtep_str[ES_VTEP_LIST_STR_SZ + BGP_EVPN_VTEPS_FLAG_STR_SZ]; + + type_str[0] = '\0'; + if (es->flags & BGP_EVPNES_BYPASS) + strlcat(type_str, "B", sizeof(type_str)); + if (es->flags & BGP_EVPNES_LOCAL) + strlcat(type_str, "L", sizeof(type_str)); + if (es->flags & BGP_EVPNES_REMOTE) + strlcat(type_str, "R", sizeof(type_str)); + if (es->inconsistencies) + strlcat(type_str, "I", sizeof(type_str)); + + bgp_evpn_es_vteps_str(vtep_str, es, sizeof(vtep_str)); + + vty_out(vty, "%-30s %-5s %-21pRDP %-8d %s\n", es->esi_str, + type_str, + es->es_base_frag ? &es->es_base_frag->prd : NULL, + listcount(es->es_evi_list), vtep_str); + } +} + +static void bgp_evpn_es_show_entry_detail(struct vty *vty, + struct bgp_evpn_es *es, json_object *json) +{ + if (json) { + json_object *json_flags; + json_object *json_incons; + json_object *json_vteps; + json_object *json_frags; + struct listnode *node; + struct bgp_evpn_es_vtep *es_vtep; + + /* Add the "brief" info first */ + bgp_evpn_es_show_entry(vty, es, json); + if (es->flags + & (BGP_EVPNES_OPER_UP | BGP_EVPNES_ADV_EVI + | BGP_EVPNES_BYPASS)) { + json_flags = json_object_new_array(); + if (es->flags & BGP_EVPNES_OPER_UP) + json_array_string_add(json_flags, "up"); + if (es->flags & BGP_EVPNES_ADV_EVI) + json_array_string_add(json_flags, + "advertiseEVI"); + if (es->flags & BGP_EVPNES_BYPASS) + json_array_string_add(json_flags, "bypass"); + json_object_object_add(json, "flags", json_flags); + } + json_object_string_addf(json, "originator_ip", "%pI4", + &es->originator_ip); + json_object_int_add(json, "remoteVniCount", + es->remote_es_evi_cnt); + json_object_int_add(json, "vrfCount", + listcount(es->es_vrf_list)); + json_object_int_add(json, "macipPathCount", + listcount(es->macip_evi_path_list)); + json_object_int_add(json, "macipGlobalPathCount", + listcount(es->macip_global_path_list)); + json_object_int_add(json, "inconsistentVniVtepCount", + es->incons_evi_vtep_cnt); + if (es->flags & BGP_EVPNES_LOCAL) + json_object_int_add(json, "localEsDfPreference", + es->df_pref); + if (listcount(es->es_vtep_list)) { + json_vteps = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, + es_vtep)) { + bgp_evpn_es_json_vtep_fill(json_vteps, es_vtep); + } + json_object_object_add(json, "vteps", json_vteps); + } + if (listcount(es->es_frag_list)) { + json_frags = json_object_new_array(); + bgp_evpn_es_json_frag_fill(json_frags, es); + json_object_object_add(json, "fragments", json_frags); + } + if (es->inconsistencies) { + json_incons = json_object_new_array(); + if (es->inconsistencies & BGP_EVPNES_INCONS_VTEP_LIST) + json_array_string_add(json_incons, + "vni-vtep-mismatch"); + json_object_object_add(json, "inconsistencies", + json_incons); + } + } else { + char incons_str[BGP_EVPNES_INCONS_STR_SZ]; + char type_str[4]; + + type_str[0] = '\0'; + if (es->flags & BGP_EVPNES_LOCAL) + strlcat(type_str, "L", sizeof(type_str)); + if (es->flags & BGP_EVPNES_REMOTE) + strlcat(type_str, "R", sizeof(type_str)); + + vty_out(vty, "ESI: %s\n", es->esi_str); + vty_out(vty, " Type: %s\n", type_str); + vty_out(vty, " RD: %pRDP\n", + es->es_base_frag ? &es->es_base_frag->prd : NULL); + vty_out(vty, " Originator-IP: %pI4\n", &es->originator_ip); + if (es->flags & BGP_EVPNES_LOCAL) + vty_out(vty, " Local ES DF preference: %u\n", + es->df_pref); + if (es->flags & BGP_EVPNES_BYPASS) + vty_out(vty, " LACP bypass: on\n"); + vty_out(vty, " VNI Count: %d\n", listcount(es->es_evi_list)); + vty_out(vty, " Remote VNI Count: %d\n", + es->remote_es_evi_cnt); + vty_out(vty, " VRF Count: %d\n", listcount(es->es_vrf_list)); + vty_out(vty, " MACIP EVI Path Count: %d\n", + listcount(es->macip_evi_path_list)); + vty_out(vty, " MACIP Global Path Count: %d\n", + listcount(es->macip_global_path_list)); + vty_out(vty, " Inconsistent VNI VTEP Count: %d\n", + es->incons_evi_vtep_cnt); + if (es->inconsistencies) { + incons_str[0] = '\0'; + if (es->inconsistencies & BGP_EVPNES_INCONS_VTEP_LIST) + strlcat(incons_str, "vni-vtep-mismatch", + sizeof(incons_str)); + } else { + strlcpy(incons_str, "-", sizeof(incons_str)); + } + vty_out(vty, " Inconsistencies: %s\n", + incons_str); + if (listcount(es->es_frag_list)) { + vty_out(vty, " Fragments:\n"); + bgp_evpn_es_frag_show_detail(vty, es); + } + if (listcount(es->es_vtep_list)) { + vty_out(vty, " VTEPs:\n"); + bgp_evpn_es_vteps_show_detail(vty, es); + } + vty_out(vty, "\n"); + } +} + +/* Display all ESs */ +void bgp_evpn_es_show(struct vty *vty, bool uj, bool detail) +{ + struct bgp_evpn_es *es; + json_object *json_array = NULL; + json_object *json = NULL; + + if (uj) { + /* create an array of ESs */ + json_array = json_object_new_array(); + } else { + if (!detail) { + vty_out(vty, + "ES Flags: B - bypass, L local, R remote, I inconsistent\n"); + vty_out(vty, + "VTEP Flags: E ESR/Type-4, A active nexthop\n"); + vty_out(vty, + "%-30s %-5s %-21s %-8s %s\n", + "ESI", "Flags", "RD", "#VNIs", "VTEPs"); + } + } + + RB_FOREACH(es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree) { + if (uj) + /* create a separate json object for each ES */ + json = json_object_new_object(); + if (detail) + bgp_evpn_es_show_entry_detail(vty, es, json); + else + bgp_evpn_es_show_entry(vty, es, json); + /* add ES to the json array */ + if (uj) + json_object_array_add(json_array, json); + } + + /* print the array of json-ESs */ + if (uj) + vty_json(vty, json_array); +} + +/* Display specific ES */ +void bgp_evpn_es_show_esi(struct vty *vty, esi_t *esi, bool uj) +{ + struct bgp_evpn_es *es; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + es = bgp_evpn_es_find(esi); + if (es) { + bgp_evpn_es_show_entry_detail(vty, es, json); + } else { + if (!uj) + vty_out(vty, "ESI not found\n"); + } + + if (uj) + vty_json(vty, json); +} + +/*****************************************************************************/ +/* Ethernet Segment to VRF association - + * 1. Each ES-EVI entry is associated with a tenant VRF. This associaton + * triggers the creation of an ES-VRF entry. + * 2. The ES-VRF entry is maintained for the purpose of L3-NHG creation + * 3. Type-2/MAC-IP routes are imported into a tenant VRF and programmed as + * a /32 or host route entry in the dataplane. If the destination of + * the host route is a remote-ES the route is programmed with the + * corresponding (keyed in by {vrf,ES-id}) L3-NHG. + * 4. The reason for this indirection (route->L3-NHG, L3-NHG->list-of-VTEPs) + * is to avoid route updates to the dplane when a remote-ES link flaps i.e. + * instead of updating all the dependent routes the NHG's contents are updated. + * This reduces the amount of datplane updates (nhg updates vs. route updates) + * allowing for a faster failover. + * + * XXX - can the L3 SVI index change without change in vpn->bgp_vrf + * association? If yes we need to handle that by updating all the L3 NHGs + * in that VRF. + */ +/******************************** L3 NHG management *************************/ +static void bgp_evpn_l3nhg_zebra_add_v4_or_v6(struct bgp_evpn_es_vrf *es_vrf, + bool v4_nhg) +{ + uint32_t nhg_id = v4_nhg ? es_vrf->nhg_id : es_vrf->v6_nhg_id; + struct bgp_evpn_es *es = es_vrf->es; + struct listnode *node; + struct bgp_evpn_es_vtep *es_vtep; + struct nexthop nh; + struct zapi_nexthop *api_nh; + struct zapi_nhg api_nhg = {}; + + /* Skip installation of L3-NHG if host routes used */ + if (!nhg_id) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u %s nhg %u to zebra", es->esi_str, + es_vrf->bgp_vrf->vrf_id, + v4_nhg ? "v4_nhg" : "v6_nhg", nhg_id); + + frrtrace(4, frr_bgp, evpn_mh_nhg_zsend, true, v4_nhg, nhg_id, es_vrf); + + /* only the gateway ip changes for each NH. rest of the params + * are constant + */ + memset(&nh, 0, sizeof(nh)); + nh.vrf_id = es_vrf->bgp_vrf->vrf_id; + nh.flags = NEXTHOP_FLAG_ONLINK; + nh.ifindex = es_vrf->bgp_vrf->l3vni_svi_ifindex; + nh.weight = 1; + nh.type = + v4_nhg ? NEXTHOP_TYPE_IPV4_IFINDEX : NEXTHOP_TYPE_IPV6_IFINDEX; + + api_nhg.id = nhg_id; + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { + if (!CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE)) + continue; + + /* Don't overrun the zapi buffer. */ + if (api_nhg.nexthop_num == MULTIPATH_NUM) + break; + + /* overwrite the gw */ + if (v4_nhg) + nh.gate.ipv4 = es_vtep->vtep_ip; + else + ipv4_to_ipv4_mapped_ipv6(&nh.gate.ipv6, + es_vtep->vtep_ip); + + /* convert to zapi format */ + api_nh = &api_nhg.nexthops[api_nhg.nexthop_num]; + zapi_nexthop_from_nexthop(api_nh, &nh); + + ++api_nhg.nexthop_num; + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("nhg %u vtep %pI4 l3-svi %d", api_nhg.id, + &es_vtep->vtep_ip, + es_vrf->bgp_vrf->l3vni_svi_ifindex); + + frrtrace(3, frr_bgp, evpn_mh_nh_zsend, nhg_id, es_vtep, es_vrf); + } + + if (!api_nhg.nexthop_num) + return; + + zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); +} + +static bool bgp_evpn_l3nhg_zebra_ok(struct bgp_evpn_es_vrf *es_vrf) +{ + if (!bgp_mh_info->host_routes_use_l3nhg) + return false; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return false; + + return true; +} + +static void bgp_evpn_l3nhg_zebra_add(struct bgp_evpn_es_vrf *es_vrf) +{ + if (!bgp_evpn_l3nhg_zebra_ok(es_vrf)) + return; + + bgp_evpn_l3nhg_zebra_add_v4_or_v6(es_vrf, true /*v4_nhg*/); + bgp_evpn_l3nhg_zebra_add_v4_or_v6(es_vrf, false /*v4_nhg*/); +} + +static void bgp_evpn_l3nhg_zebra_del_v4_or_v6(struct bgp_evpn_es_vrf *es_vrf, + bool v4_nhg) +{ + struct zapi_nhg api_nhg = {}; + + api_nhg.id = v4_nhg ? es_vrf->nhg_id : es_vrf->v6_nhg_id; + + /* Skip installation of L3-NHG if host routes used */ + if (!api_nhg.id) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u %s nhg %u to zebra", + es_vrf->es->esi_str, es_vrf->bgp_vrf->vrf_id, + v4_nhg ? "v4_nhg" : "v6_nhg", api_nhg.id); + + + frrtrace(4, frr_bgp, evpn_mh_nhg_zsend, false, v4_nhg, api_nhg.id, + es_vrf); + + zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); +} + +static void bgp_evpn_l3nhg_zebra_del(struct bgp_evpn_es_vrf *es_vrf) +{ + if (!bgp_evpn_l3nhg_zebra_ok(es_vrf)) + return; + + bgp_evpn_l3nhg_zebra_del_v4_or_v6(es_vrf, true /*v4_nhg*/); + bgp_evpn_l3nhg_zebra_del_v4_or_v6(es_vrf, false /*v4_nhg*/); +} + +static void bgp_evpn_l3nhg_deactivate(struct bgp_evpn_es_vrf *es_vrf) +{ + if (!(es_vrf->flags & BGP_EVPNES_VRF_NHG_ACTIVE)) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u nhg %u de-activate", + es_vrf->es->esi_str, es_vrf->bgp_vrf->vrf_id, + es_vrf->nhg_id); + bgp_evpn_l3nhg_zebra_del(es_vrf); + es_vrf->flags &= ~BGP_EVPNES_VRF_NHG_ACTIVE; + /* MAC-IPs can now be installed via the L3NHG */ + bgp_evpn_es_path_update_on_es_vrf_chg(es_vrf, "l3nhg-deactivate"); +} + +static void bgp_evpn_l3nhg_activate(struct bgp_evpn_es_vrf *es_vrf, bool update) +{ + if (!bgp_evpn_es_get_active_vtep_cnt(es_vrf->es)) { + bgp_evpn_l3nhg_deactivate(es_vrf); + return; + } + + if (es_vrf->flags & BGP_EVPNES_VRF_NHG_ACTIVE) { + if (!update) + return; + } else { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u nhg %u activate", + es_vrf->es->esi_str, es_vrf->bgp_vrf->vrf_id, + es_vrf->nhg_id); + es_vrf->flags |= BGP_EVPNES_VRF_NHG_ACTIVE; + /* MAC-IPs can now be installed via the L3NHG */ + bgp_evpn_es_path_update_on_es_vrf_chg(es_vrf, "l3nhg_activate"); + } + + bgp_evpn_l3nhg_zebra_add(es_vrf); +} + +/* when a VTEP is activated or de-activated against an ES associated + * VRFs' NHG needs to be updated + */ +static void bgp_evpn_l3nhg_update_on_vtep_chg(struct bgp_evpn_es *es) +{ + struct bgp_evpn_es_vrf *es_vrf; + struct listnode *es_vrf_node; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s nhg update on vtep chg", es->esi_str); + + for (ALL_LIST_ELEMENTS_RO(es->es_vrf_list, es_vrf_node, es_vrf)) + bgp_evpn_l3nhg_activate(es_vrf, true /* update */); +} + +/* compare ES-IDs for the ES-VRF RB tree maintained per-VRF */ +static int bgp_es_vrf_rb_cmp(const struct bgp_evpn_es_vrf *es_vrf1, + const struct bgp_evpn_es_vrf *es_vrf2) +{ + return memcmp(&es_vrf1->es->esi, &es_vrf2->es->esi, ESI_BYTES); +} +RB_GENERATE(bgp_es_vrf_rb_head, bgp_evpn_es_vrf, rb_node, bgp_es_vrf_rb_cmp); + +/* Initialize the ES tables maintained per-tenant vrf */ +void bgp_evpn_vrf_es_init(struct bgp *bgp_vrf) +{ + /* Initialize the ES-VRF RB tree */ + RB_INIT(bgp_es_vrf_rb_head, &bgp_vrf->es_vrf_rb_tree); +} + +/* find the ES-VRF in the per-VRF RB tree */ +static struct bgp_evpn_es_vrf *bgp_evpn_es_vrf_find(struct bgp_evpn_es *es, + struct bgp *bgp_vrf) +{ + struct bgp_evpn_es_vrf es_vrf; + + es_vrf.es = es; + + return RB_FIND(bgp_es_vrf_rb_head, &bgp_vrf->es_vrf_rb_tree, &es_vrf); +} + +/* allocate a new ES-VRF and setup L3NHG for it */ +static struct bgp_evpn_es_vrf *bgp_evpn_es_vrf_create(struct bgp_evpn_es *es, + struct bgp *bgp_vrf) +{ + struct bgp_evpn_es_vrf *es_vrf; + + es_vrf = XCALLOC(MTYPE_BGP_EVPN_ES_VRF, sizeof(*es_vrf)); + + es_vrf->es = es; + es_vrf->bgp_vrf = bgp_vrf; + + /* insert into the VRF-ESI rb tree */ + RB_INSERT(bgp_es_vrf_rb_head, &bgp_vrf->es_vrf_rb_tree, es_vrf); + + /* add to the ES's VRF list */ + listnode_init(&es_vrf->es_listnode, es_vrf); + listnode_add(es->es_vrf_list, &es_vrf->es_listnode); + + /* setup the L3 NHG id for the ES */ + es_vrf->nhg_id = bgp_nhg_id_alloc(); + es_vrf->v6_nhg_id = bgp_nhg_id_alloc(); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u nhg %u v6_nhg %d create", es->esi_str, + bgp_vrf->vrf_id, es_vrf->nhg_id, es_vrf->v6_nhg_id); + bgp_evpn_l3nhg_activate(es_vrf, false /* update */); + + /* update paths in the VRF that may already be associated with + * this destination ES + */ + bgp_evpn_es_path_update_on_es_vrf_chg(es_vrf, "es-vrf-create"); + + return es_vrf; +} + +/* remove the L3-NHG associated with the ES-VRF and free it */ +static void bgp_evpn_es_vrf_delete(struct bgp_evpn_es_vrf *es_vrf) +{ + struct bgp_evpn_es *es = es_vrf->es; + struct bgp *bgp_vrf = es_vrf->bgp_vrf; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s vrf %u nhg %u delete", es->esi_str, + bgp_vrf->vrf_id, es_vrf->nhg_id); + + /* Remove the NHG resources */ + bgp_evpn_l3nhg_deactivate(es_vrf); + if (es_vrf->nhg_id) + bgp_nhg_id_free(es_vrf->nhg_id); + es_vrf->nhg_id = 0; + if (es_vrf->v6_nhg_id) + bgp_nhg_id_free(es_vrf->v6_nhg_id); + es_vrf->v6_nhg_id = 0; + + /* remove from the ES's VRF list */ + list_delete_node(es->es_vrf_list, &es_vrf->es_listnode); + + /* remove from the VRF-ESI rb tree */ + RB_REMOVE(bgp_es_vrf_rb_head, &bgp_vrf->es_vrf_rb_tree, es_vrf); + + /* update paths in the VRF that may already be associated with + * this destination ES + */ + bgp_evpn_es_path_update_on_es_vrf_chg(es_vrf, "es-vrf-delete"); + + XFREE(MTYPE_BGP_EVPN_ES_VRF, es_vrf); +} + +/* deref and delete if there are no references */ +void bgp_evpn_es_vrf_deref(struct bgp_evpn_es_evi *es_evi) +{ + struct bgp_evpn_es_vrf *es_vrf = es_evi->es_vrf; + + if (!es_vrf) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es-evi %s vni %u vrf %u de-ref", + es_evi->es->esi_str, es_evi->vpn->vni, + es_vrf->bgp_vrf->vrf_id); + + es_evi->es_vrf = NULL; + if (es_vrf->ref_cnt) + --es_vrf->ref_cnt; + + if (!es_vrf->ref_cnt) + bgp_evpn_es_vrf_delete(es_vrf); +} + +/* find or create and reference */ +void bgp_evpn_es_vrf_ref(struct bgp_evpn_es_evi *es_evi, struct bgp *bgp_vrf) +{ + struct bgp_evpn_es *es = es_evi->es; + struct bgp_evpn_es_vrf *es_vrf = es_evi->es_vrf; + struct bgp *old_bgp_vrf = NULL; + + if (es_vrf) + old_bgp_vrf = es_vrf->bgp_vrf; + + if (old_bgp_vrf == bgp_vrf) + return; + + /* deref the old ES-VRF */ + bgp_evpn_es_vrf_deref(es_evi); + + if (!bgp_vrf) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es-evi %s vni %u vrf %u ref", es_evi->es->esi_str, + es_evi->vpn->vni, bgp_vrf->vrf_id); + + /* find-create the new ES-VRF */ + es_vrf = bgp_evpn_es_vrf_find(es, bgp_vrf); + if (!es_vrf) + es_vrf = bgp_evpn_es_vrf_create(es, bgp_vrf); + + es_evi->es_vrf = es_vrf; + ++es_vrf->ref_cnt; +} + +/* When the L2-VNI is associated with a L3-VNI/VRF update all the + * associated ES-EVI entries + */ +void bgp_evpn_es_evi_vrf_deref(struct bgpevpn *vpn) +{ + struct bgp_evpn_es_evi *es_evi; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es-vrf de-ref for vni %u", vpn->vni); + + RB_FOREACH (es_evi, bgp_es_evi_rb_head, &vpn->es_evi_rb_tree) + bgp_evpn_es_vrf_deref(es_evi); +} +void bgp_evpn_es_evi_vrf_ref(struct bgpevpn *vpn) +{ + struct bgp_evpn_es_evi *es_evi; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es-vrf ref for vni %u", vpn->vni); + + RB_FOREACH (es_evi, bgp_es_evi_rb_head, &vpn->es_evi_rb_tree) + bgp_evpn_es_vrf_ref(es_evi, vpn->bgp_vrf); +} + +/* 1. If ES-VRF is not present install the host route with the exploded/flat + * multi-path list. + * 2. If ES-VRF is present - + * - if L3NHG has not been activated for the ES-VRF (this could be because + * all the PEs attached to the VRF are down) do not install the route + * in zebra. + * - if L3NHG has been activated install the route via that L3NHG + */ +void bgp_evpn_es_vrf_use_nhg(struct bgp *bgp_vrf, esi_t *esi, bool *use_l3nhg, + bool *is_l3nhg_active, + struct bgp_evpn_es_vrf **es_vrf_p) +{ + struct bgp_evpn_es *es; + struct bgp_evpn_es_vrf *es_vrf; + + if (!bgp_mh_info->host_routes_use_l3nhg) + return; + + es = bgp_evpn_es_find(esi); + if (!es) + return; + + es_vrf = bgp_evpn_es_vrf_find(es, bgp_vrf); + if (!es_vrf) + return; + + *use_l3nhg = true; + if (es_vrf->flags & BGP_EVPNES_VRF_NHG_ACTIVE) + *is_l3nhg_active = true; + if (es_vrf_p) + *es_vrf_p = es_vrf; +} + +/* returns false if legacy-exploded mp needs to be used for route install */ +bool bgp_evpn_path_es_use_nhg(struct bgp *bgp_vrf, struct bgp_path_info *pi, + uint32_t *nhg_p) +{ + esi_t *esi; + struct bgp_evpn_es_vrf *es_vrf = NULL; + struct bgp_path_info *parent_pi; + struct bgp_dest *bd; + struct prefix_evpn *evp; + struct bgp_path_info *mpinfo; + bool use_l3nhg = false; + bool is_l3nhg_active = false; + + *nhg_p = 0; + + /* we don't support NHG for routes leaked from another VRF yet */ + if (pi->extra && pi->extra->vrfleak && pi->extra->vrfleak->bgp_orig) + return false; + + parent_pi = get_route_parent_evpn(pi); + if (!parent_pi) + return false; + + bd = parent_pi->net; + if (!bd) + return false; + + evp = (struct prefix_evpn *)&bd->rn->p; + if (evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return false; + + /* non-es path, use legacy-exploded multipath */ + esi = bgp_evpn_attr_get_esi(parent_pi->attr); + if (!memcmp(esi, zero_esi, sizeof(*esi))) + return false; + + /* we don't support NHG for d-vni yet */ + if (bgp_evpn_mpath_has_dvni(bgp_vrf, pi)) + return false; + + bgp_evpn_es_vrf_use_nhg(bgp_vrf, esi, &use_l3nhg, &is_l3nhg_active, + &es_vrf); + + /* L3NHG support is disabled, use legacy-exploded multipath */ + if (!use_l3nhg) + return false; + + /* if the NHG has not been installed we cannot install the route yet, + * return a 0-NHG to indicate that + */ + if (!is_l3nhg_active) + return true; + + /* this needs to be set the v6NHG if v6route */ + if (is_evpn_prefix_ipaddr_v6(evp)) + *nhg_p = es_vrf->v6_nhg_id; + else + *nhg_p = es_vrf->nhg_id; + + for (mpinfo = bgp_path_info_mpath_next(pi); mpinfo; + mpinfo = bgp_path_info_mpath_next(mpinfo)) { + /* if any of the paths have a different ESI we can't use + * the NHG associated with the ES. fallback to legacy-exploded + * multipath + */ + if (memcmp(esi, bgp_evpn_attr_get_esi(mpinfo->attr), + sizeof(*esi))) + return false; + } + + return true; +} + +static void bgp_evpn_es_vrf_show_entry(struct vty *vty, + struct bgp_evpn_es_vrf *es_vrf, + json_object *json) +{ + struct bgp_evpn_es *es = es_vrf->es; + struct bgp *bgp_vrf = es_vrf->bgp_vrf; + + if (json) { + json_object *json_types; + + json_object_string_add(json, "esi", es->esi_str); + json_object_string_add(json, "vrf", bgp_vrf->name_pretty); + + if (es_vrf->flags & (BGP_EVPNES_VRF_NHG_ACTIVE)) { + json_types = json_object_new_array(); + if (es_vrf->flags & BGP_EVPNES_VRF_NHG_ACTIVE) + json_array_string_add(json_types, "active"); + json_object_object_add(json, "flags", json_types); + } + + json_object_int_add(json, "ipv4NHG", es_vrf->nhg_id); + json_object_int_add(json, "ipv6NHG", es_vrf->v6_nhg_id); + json_object_int_add(json, "refCount", es_vrf->ref_cnt); + } else { + char flags_str[4]; + + flags_str[0] = '\0'; + if (es_vrf->flags & BGP_EVPNES_VRF_NHG_ACTIVE) + strlcat(flags_str, "A", sizeof(flags_str)); + + vty_out(vty, "%-30s %-15s %-5s %-8u %-8u %u\n", es->esi_str, + bgp_vrf->name_pretty, flags_str, es_vrf->nhg_id, + es_vrf->v6_nhg_id, es_vrf->ref_cnt); + } +} + +static void bgp_evpn_es_vrf_show_es(struct vty *vty, json_object *json_array, + struct bgp_evpn_es *es) +{ + json_object *json = NULL; + struct listnode *es_vrf_node; + struct bgp_evpn_es_vrf *es_vrf; + + for (ALL_LIST_ELEMENTS_RO(es->es_vrf_list, es_vrf_node, es_vrf)) { + /* create a separate json object for each ES-VRF */ + if (json_array) + json = json_object_new_object(); + bgp_evpn_es_vrf_show_entry(vty, es_vrf, json); + /* add ES-VRF to the json array */ + if (json_array) + json_object_array_add(json_array, json); + } +} + +/* Display all ES VRFs */ +void bgp_evpn_es_vrf_show(struct vty *vty, bool uj, struct bgp_evpn_es *es) +{ + json_object *json_array = NULL; + + if (uj) { + /* create an array of ESs */ + json_array = json_object_new_array(); + } else { + vty_out(vty, "ES-VRF Flags: A Active\n"); + vty_out(vty, "%-30s %-15s %-5s %-8s %-8s %s\n", "ESI", "VRF", + "Flags", "IPv4-NHG", "IPv6-NHG", "Ref"); + } + + if (es) { + bgp_evpn_es_vrf_show_es(vty, json_array, es); + } else { + RB_FOREACH (es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree) + bgp_evpn_es_vrf_show_es(vty, json_array, es); + } + + /* print the array of json-ESs */ + if (uj) + vty_json(vty, json_array); +} + +/* Display specific ES VRF */ +void bgp_evpn_es_vrf_show_esi(struct vty *vty, esi_t *esi, bool uj) +{ + struct bgp_evpn_es *es; + + es = bgp_evpn_es_find(esi); + if (es) { + bgp_evpn_es_vrf_show(vty, uj, es); + } else { + if (!uj) + vty_out(vty, "ESI not found\n"); + } +} + +/*****************************************************************************/ +/* Ethernet Segment to EVI association - + * 1. The ES-EVI entry is maintained as a RB tree per L2-VNI + * (bgpevpn->es_evi_rb_tree). + * 2. Each local ES-EVI entry is rxed from zebra and then used by BGP to + * advertises an EAD-EVI (Type-1 EVPN) route + * 3. The remote ES-EVI is created when a bgp_evpn_es_evi_vtep references + * it. + */ + +/* A list of remote VTEPs is maintained for each ES-EVI. This list includes - + * 1. VTEPs for which we have imported the EAD-per-ES Type1 route + * 2. VTEPs for which we have imported the EAD-per-EVI Type1 route + * VTEPs for which both routes have been rxed are activated. Activation + * creates a NHG in the parent ES. + */ +static int bgp_evpn_es_evi_vtep_cmp(void *p1, void *p2) +{ + const struct bgp_evpn_es_evi_vtep *evi_vtep1 = p1; + const struct bgp_evpn_es_evi_vtep *evi_vtep2 = p2; + + return evi_vtep1->vtep_ip.s_addr - evi_vtep2->vtep_ip.s_addr; +} + +static struct bgp_evpn_es_evi_vtep *bgp_evpn_es_evi_vtep_new( + struct bgp_evpn_es_evi *es_evi, struct in_addr vtep_ip) +{ + struct bgp_evpn_es_evi_vtep *evi_vtep; + + evi_vtep = XCALLOC(MTYPE_BGP_EVPN_ES_EVI_VTEP, sizeof(*evi_vtep)); + + evi_vtep->es_evi = es_evi; + evi_vtep->vtep_ip.s_addr = vtep_ip.s_addr; + listnode_init(&evi_vtep->es_evi_listnode, evi_vtep); + listnode_add_sort(es_evi->es_evi_vtep_list, &evi_vtep->es_evi_listnode); + + return evi_vtep; +} + +static void bgp_evpn_es_evi_vtep_free(struct bgp_evpn_es_evi_vtep *evi_vtep) +{ + struct bgp_evpn_es_evi *es_evi = evi_vtep->es_evi; + + if (evi_vtep->flags & (BGP_EVPN_EVI_VTEP_EAD)) + /* as long as there is some reference we can't free it */ + return; + + list_delete_node(es_evi->es_evi_vtep_list, &evi_vtep->es_evi_listnode); + XFREE(MTYPE_BGP_EVPN_ES_EVI_VTEP, evi_vtep); +} + +/* check if VTEP is already part of the list */ +static struct bgp_evpn_es_evi_vtep *bgp_evpn_es_evi_vtep_find( + struct bgp_evpn_es_evi *es_evi, struct in_addr vtep_ip) +{ + struct listnode *node = NULL; + struct bgp_evpn_es_evi_vtep *evi_vtep; + + for (ALL_LIST_ELEMENTS_RO(es_evi->es_evi_vtep_list, node, evi_vtep)) { + if (evi_vtep->vtep_ip.s_addr == vtep_ip.s_addr) + return evi_vtep; + } + return NULL; +} + +/* A VTEP can be added as "active" attach to an ES if EAD-per-ES and + * EAD-per-EVI routes are rxed from it. + */ +static enum zclient_send_status +bgp_evpn_es_evi_vtep_re_eval_active(struct bgp *bgp, + struct bgp_evpn_es_evi_vtep *evi_vtep) +{ + bool old_active; + bool new_active; + uint32_t ead_activity_flags; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + old_active = CHECK_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE); + + if (bgp_mh_info->ead_evi_rx) + /* Both EAD-per-ES and EAD-per-EVI routes must be rxed from a PE + * before it can be activated. + */ + ead_activity_flags = BGP_EVPN_EVI_VTEP_EAD; + else + /* EAD-per-ES is sufficent to activate the PE */ + ead_activity_flags = BGP_EVPN_EVI_VTEP_EAD_PER_ES; + + if ((evi_vtep->flags & ead_activity_flags) == ead_activity_flags) + SET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE); + else + UNSET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE); + + new_active = CHECK_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE); + + if (old_active == new_active) + return ret; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("es %s evi %u vtep %pI4 %s", + evi_vtep->es_evi->es->esi_str, + evi_vtep->es_evi->vpn->vni, &evi_vtep->vtep_ip, + new_active ? "active" : "inactive"); + + /* add VTEP to parent es */ + if (new_active) { + evi_vtep->es_vtep = + bgp_evpn_es_vtep_add(bgp, evi_vtep->es_evi->es, + evi_vtep->vtep_ip, false /*esr*/, + 0, 0, &ret); + } else { + if (evi_vtep->es_vtep) { + ret = bgp_evpn_es_vtep_do_del(bgp, evi_vtep->es_vtep, + false /*esr*/); + evi_vtep->es_vtep = NULL; + } + } + /* queue up the parent es for background consistency checks */ + bgp_evpn_es_cons_checks_pend_add(evi_vtep->es_evi->es); + + return ret; +} + +static enum zclient_send_status +bgp_evpn_es_evi_vtep_add(struct bgp *bgp, struct bgp_evpn_es_evi *es_evi, + struct in_addr vtep_ip, bool ead_es) +{ + struct bgp_evpn_es_evi_vtep *evi_vtep; + + evi_vtep = bgp_evpn_es_evi_vtep_find(es_evi, vtep_ip); + + if (!evi_vtep) + evi_vtep = bgp_evpn_es_evi_vtep_new(es_evi, vtep_ip); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("add es %s evi %u vtep %pI4 %s", + evi_vtep->es_evi->es->esi_str, + evi_vtep->es_evi->vpn->vni, &evi_vtep->vtep_ip, + ead_es ? "ead_es" : "ead_evi"); + + frrtrace(4, frr_bgp, evpn_mh_es_evi_vtep_add, + &evi_vtep->es_evi->es->esi, evi_vtep->es_evi->vpn->vni, + evi_vtep->vtep_ip, ead_es); + + if (ead_es) + SET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_EAD_PER_ES); + else + SET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_EAD_PER_EVI); + + return bgp_evpn_es_evi_vtep_re_eval_active(bgp, evi_vtep); +} + +static enum zclient_send_status +bgp_evpn_es_evi_vtep_del(struct bgp *bgp, struct bgp_evpn_es_evi *es_evi, + struct in_addr vtep_ip, bool ead_es) +{ + struct bgp_evpn_es_evi_vtep *evi_vtep; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + evi_vtep = bgp_evpn_es_evi_vtep_find(es_evi, vtep_ip); + if (!evi_vtep) + return ret; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("del es %s evi %u vtep %pI4 %s", + evi_vtep->es_evi->es->esi_str, + evi_vtep->es_evi->vpn->vni, &evi_vtep->vtep_ip, + ead_es ? "ead_es" : "ead_evi"); + + frrtrace(4, frr_bgp, evpn_mh_es_evi_vtep_del, + &evi_vtep->es_evi->es->esi, evi_vtep->es_evi->vpn->vni, + evi_vtep->vtep_ip, ead_es); + + if (ead_es) + UNSET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_EAD_PER_ES); + else + UNSET_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_EAD_PER_EVI); + + ret = bgp_evpn_es_evi_vtep_re_eval_active(bgp, evi_vtep); + bgp_evpn_es_evi_vtep_free(evi_vtep); + + return ret; +} + +/* compare ES-IDs for the ES-EVI RB tree maintained per-VNI */ +static int bgp_es_evi_rb_cmp(const struct bgp_evpn_es_evi *es_evi1, + const struct bgp_evpn_es_evi *es_evi2) +{ + return memcmp(&es_evi1->es->esi, &es_evi2->es->esi, ESI_BYTES); +} +RB_GENERATE(bgp_es_evi_rb_head, bgp_evpn_es_evi, rb_node, bgp_es_evi_rb_cmp); + +/* find the ES-EVI in the per-L2-VNI RB tree */ +static struct bgp_evpn_es_evi *bgp_evpn_es_evi_find(struct bgp_evpn_es *es, + struct bgpevpn *vpn) +{ + struct bgp_evpn_es_evi es_evi; + + es_evi.es = es; + + return RB_FIND(bgp_es_evi_rb_head, &vpn->es_evi_rb_tree, &es_evi); +} + +/* allocate a new ES-EVI and insert it into the per-L2-VNI and per-ES + * tables. + */ +static struct bgp_evpn_es_evi *bgp_evpn_es_evi_new(struct bgp_evpn_es *es, + struct bgpevpn *vpn) +{ + struct bgp_evpn_es_evi *es_evi; + + es_evi = XCALLOC(MTYPE_BGP_EVPN_ES_EVI, sizeof(*es_evi)); + + es_evi->es = es; + es_evi->vpn = vpn; + + /* Initialise the VTEP list */ + es_evi->es_evi_vtep_list = list_new(); + listset_app_node_mem(es_evi->es_evi_vtep_list); + es_evi->es_evi_vtep_list->cmp = bgp_evpn_es_evi_vtep_cmp; + + /* insert into the VNI-ESI rb tree */ + RB_INSERT(bgp_es_evi_rb_head, &vpn->es_evi_rb_tree, es_evi); + + /* add to the ES's VNI list */ + listnode_init(&es_evi->es_listnode, es_evi); + listnode_add(es->es_evi_list, &es_evi->es_listnode); + + bgp_evpn_es_vrf_ref(es_evi, vpn->bgp_vrf); + + return es_evi; +} + +/* remove the ES-EVI from the per-L2-VNI and per-ES tables and free + * up the memory. + */ +static struct bgp_evpn_es_evi * +bgp_evpn_es_evi_free(struct bgp_evpn_es_evi *es_evi) +{ + struct bgp_evpn_es *es = es_evi->es; + struct bgpevpn *vpn = es_evi->vpn; + + /* cannot free the element as long as there is a local or remote + * reference + */ + if (es_evi->flags & (BGP_EVPNES_EVI_LOCAL | BGP_EVPNES_EVI_REMOTE)) + return es_evi; + bgp_evpn_es_frag_evi_del(es_evi, false); + bgp_evpn_es_vrf_deref(es_evi); + + /* remove from the ES's VNI list */ + list_delete_node(es->es_evi_list, &es_evi->es_listnode); + + /* remove from the VNI-ESI rb tree */ + RB_REMOVE(bgp_es_evi_rb_head, &vpn->es_evi_rb_tree, es_evi); + + /* free the VTEP list */ + list_delete(&es_evi->es_evi_vtep_list); + + /* remove from the VNI-ESI rb tree */ + XFREE(MTYPE_BGP_EVPN_ES_EVI, es_evi); + + return NULL; +} + +/* init local info associated with the ES-EVI */ +static void bgp_evpn_es_evi_local_info_set(struct bgp_evpn_es_evi *es_evi) +{ + struct bgpevpn *vpn = es_evi->vpn; + + if (CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + return; + + SET_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL); + listnode_init(&es_evi->l2vni_listnode, es_evi); + listnode_add(vpn->local_es_evi_list, &es_evi->l2vni_listnode); + bgp_evpn_es_frag_evi_add(es_evi); +} + +/* clear any local info associated with the ES-EVI */ +static struct bgp_evpn_es_evi * +bgp_evpn_es_evi_local_info_clear(struct bgp_evpn_es_evi *es_evi) +{ + struct bgpevpn *vpn = es_evi->vpn; + + UNSET_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL); + list_delete_node(vpn->local_es_evi_list, &es_evi->l2vni_listnode); + + return bgp_evpn_es_evi_free(es_evi); +} + +/* eval remote info associated with the ES */ +static void bgp_evpn_es_evi_remote_info_re_eval(struct bgp_evpn_es_evi *es_evi) +{ + struct bgp_evpn_es *es = es_evi->es; + + /* if there are remote VTEPs the ES-EVI is classified as "remote" */ + if (listcount(es_evi->es_evi_vtep_list)) { + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_REMOTE)) { + SET_FLAG(es_evi->flags, BGP_EVPNES_EVI_REMOTE); + ++es->remote_es_evi_cnt; + /* set remote on the parent es */ + bgp_evpn_es_remote_info_re_eval(es); + } + } else { + if (CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_REMOTE)) { + UNSET_FLAG(es_evi->flags, BGP_EVPNES_EVI_REMOTE); + if (es->remote_es_evi_cnt) + --es->remote_es_evi_cnt; + bgp_evpn_es_evi_free(es_evi); + /* check if "remote" can be cleared from the + * parent es. + */ + bgp_evpn_es_remote_info_re_eval(es); + } + } +} + +static struct bgp_evpn_es_evi * +bgp_evpn_local_es_evi_do_del(struct bgp_evpn_es_evi *es_evi) +{ + struct prefix_evpn p; + struct bgp_evpn_es *es = es_evi->es; + struct bgp *bgp; + + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + return es_evi; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("del local es %s evi %u", + es_evi->es->esi_str, + es_evi->vpn->vni); + + bgp = bgp_get_evpn(); + + /* remove the es_evi from the es_frag before sending the update */ + bgp_evpn_es_frag_evi_del(es_evi, true); + if (bgp) { + /* update EAD-ES with new list of VNIs */ + if (bgp_evpn_local_es_is_active(es)) + bgp_evpn_ead_es_route_update(bgp, es); + + /* withdraw and delete EAD-EVI */ + if (CHECK_FLAG(es->flags, BGP_EVPNES_ADV_EVI)) { + build_evpn_type1_prefix(&p, BGP_EVPN_AD_EVI_ETH_TAG, + &es->esi, es->originator_ip); + if (bgp_evpn_ead_evi_route_delete(bgp, es, es_evi->vpn, + &p)) + flog_err(EC_BGP_EVPN_ROUTE_DELETE, + "%u: EAD-EVI route deletion failure for ESI %s VNI %u", + bgp->vrf_id, es->esi_str, + es_evi->vpn->vni); + } + } + + return bgp_evpn_es_evi_local_info_clear(es_evi); +} + +int bgp_evpn_local_es_evi_del(struct bgp *bgp, esi_t *esi, vni_t vni) +{ + struct bgpevpn *vpn; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + char buf[ESI_STR_LEN]; + + es = bgp_evpn_es_find(esi); + if (!es) { + flog_err( + EC_BGP_ES_CREATE, + "%u: Failed to deref VNI %d from ESI %s; ES not present", + bgp->vrf_id, vni, + esi_to_str(esi, buf, sizeof(buf))); + return -1; + } + + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + flog_err( + EC_BGP_ES_CREATE, + "%u: Failed to deref VNI %d from ESI %s; VNI not present", + bgp->vrf_id, vni, es->esi_str); + return -1; + } + + es_evi = bgp_evpn_es_evi_find(es, vpn); + if (!es_evi) { + flog_err( + EC_BGP_ES_CREATE, + "%u: Failed to deref VNI %d from ESI %s; ES-VNI not present", + bgp->vrf_id, vni, es->esi_str); + return -1; + } + + bgp_evpn_local_es_evi_do_del(es_evi); + return 0; +} + +/* Create ES-EVI and advertise the corresponding EAD routes */ +int bgp_evpn_local_es_evi_add(struct bgp *bgp, esi_t *esi, vni_t vni) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + char buf[ESI_STR_LEN]; + + es = bgp_evpn_es_find(esi); + if (!es) { + flog_err( + EC_BGP_ES_CREATE, + "%u: Failed to associate VNI %d with ESI %s; ES not present", + bgp->vrf_id, vni, + esi_to_str(esi, buf, sizeof(buf))); + return -1; + } + + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + flog_err( + EC_BGP_ES_CREATE, + "%u: Failed to associate VNI %d with ESI %s; VNI not present", + bgp->vrf_id, vni, es->esi_str); + return -1; + } + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("add local es %s evi %u", + es->esi_str, vni); + + es_evi = bgp_evpn_es_evi_find(es, vpn); + + if (es_evi) { + if (CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_LOCAL)) + /* dup */ + return 0; + } else + es_evi = bgp_evpn_es_evi_new(es, vpn); + + bgp_evpn_es_evi_local_info_set(es_evi); + + /* generate an EAD-EVI for this new VNI */ + if (CHECK_FLAG(es->flags, BGP_EVPNES_ADV_EVI)) { + build_evpn_type1_prefix(&p, BGP_EVPN_AD_EVI_ETH_TAG, &es->esi, + es->originator_ip); + bgp_evpn_ead_evi_route_update(bgp, es, vpn, &p); + } + + /* update EAD-ES */ + if (bgp_evpn_local_es_is_active(es)) + bgp_evpn_ead_es_route_update(bgp, es); + + return 0; +} + +/* Add remote ES-EVI entry. This is actually the remote VTEP add and the + * ES-EVI is implicity created on first VTEP's reference. + */ +enum zclient_send_status bgp_evpn_remote_es_evi_add(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p) +{ + char buf[ESI_STR_LEN]; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + bool ead_es; + const esi_t *esi = &p->prefix.ead_addr.esi; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + if (!vpn) + /* local EAD-ES need not be sent back to zebra */ + return ret; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("add remote %s es %s evi %u vtep %pI4", + p->prefix.ead_addr.eth_tag ? "ead-es" : "ead-evi", + esi_to_str(esi, buf, sizeof(buf)), vpn->vni, + &p->prefix.ead_addr.ip.ipaddr_v4); + + es = bgp_evpn_es_find(esi); + if (!es) + es = bgp_evpn_es_new(bgp, esi); + + es_evi = bgp_evpn_es_evi_find(es, vpn); + if (!es_evi) + es_evi = bgp_evpn_es_evi_new(es, vpn); + + ead_es = !!p->prefix.ead_addr.eth_tag; + ret = bgp_evpn_es_evi_vtep_add(bgp, es_evi, + p->prefix.ead_addr.ip.ipaddr_v4, ead_es); + + bgp_evpn_es_evi_remote_info_re_eval(es_evi); + return ret; +} + +/* A remote VTEP has withdrawn. The es-evi-vtep will be deleted and the + * parent es-evi freed up implicitly in last VTEP's deref. + */ +enum zclient_send_status bgp_evpn_remote_es_evi_del(struct bgp *bgp, + struct bgpevpn *vpn, + const struct prefix_evpn *p) +{ + char buf[ESI_STR_LEN]; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + bool ead_es; + enum zclient_send_status ret = ZCLIENT_SEND_SUCCESS; + + if (!vpn) + /* local EAD-ES need not be sent back to zebra */ + return ret; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug( + "del remote %s es %s evi %u vtep %pI4", + p->prefix.ead_addr.eth_tag ? "ead-es" : "ead-evi", + esi_to_str(&p->prefix.ead_addr.esi, buf, sizeof(buf)), + vpn->vni, &p->prefix.ead_addr.ip.ipaddr_v4); + + es = bgp_evpn_es_find(&p->prefix.ead_addr.esi); + if (!es) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug( + "del remote %s es %s evi %u vtep %pI4, NO es", + p->prefix.ead_addr.eth_tag ? "ead-es" + : "ead-evi", + esi_to_str(&p->prefix.ead_addr.esi, buf, + sizeof(buf)), + vpn->vni, &p->prefix.ead_addr.ip.ipaddr_v4); + return ret; + } + es_evi = bgp_evpn_es_evi_find(es, vpn); + if (!es_evi) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug( + "del remote %s es %s evi %u vtep %pI4, NO es-evi", + p->prefix.ead_addr.eth_tag ? "ead-es" + : "ead-evi", + esi_to_str(&p->prefix.ead_addr.esi, buf, + sizeof(buf)), + vpn->vni, + &p->prefix.ead_addr.ip.ipaddr_v4); + return ret; + } + + ead_es = !!p->prefix.ead_addr.eth_tag; + ret = bgp_evpn_es_evi_vtep_del(bgp, es_evi, + p->prefix.ead_addr.ip.ipaddr_v4, ead_es); + bgp_evpn_es_evi_remote_info_re_eval(es_evi); + + return ret; +} + +/* If a VNI is being deleted we need to force del all remote VTEPs */ +static void bgp_evpn_remote_es_evi_flush(struct bgp_evpn_es_evi *es_evi) +{ + struct listnode *node = NULL; + struct listnode *nnode = NULL; + struct bgp_evpn_es_evi_vtep *evi_vtep; + struct bgp *bgp; + + bgp = bgp_get_evpn(); + if (!bgp) + return; + + /* delete all VTEPs */ + for (ALL_LIST_ELEMENTS(es_evi->es_evi_vtep_list, node, nnode, + evi_vtep)) { + evi_vtep->flags &= ~(BGP_EVPN_EVI_VTEP_EAD_PER_ES + | BGP_EVPN_EVI_VTEP_EAD_PER_EVI); + bgp_evpn_es_evi_vtep_re_eval_active(bgp, evi_vtep); + bgp_evpn_es_evi_vtep_free(evi_vtep); + } + /* delete the EVI */ + bgp_evpn_es_evi_remote_info_re_eval(es_evi); +} + +/* Initialize the ES tables maintained per-L2_VNI */ +void bgp_evpn_vni_es_init(struct bgpevpn *vpn) +{ + /* Initialize the ES-EVI RB tree */ + RB_INIT(bgp_es_evi_rb_head, &vpn->es_evi_rb_tree); + + /* Initialize the local list maintained for quick walks by type */ + vpn->local_es_evi_list = list_new(); + listset_app_node_mem(vpn->local_es_evi_list); +} + +/* Cleanup the ES info maintained per-L2_VNI */ +void bgp_evpn_vni_es_cleanup(struct bgpevpn *vpn) +{ + struct bgp_evpn_es_evi *es_evi; + struct bgp_evpn_es_evi *es_evi_next; + + RB_FOREACH_SAFE(es_evi, bgp_es_evi_rb_head, + &vpn->es_evi_rb_tree, es_evi_next) { + es_evi = bgp_evpn_local_es_evi_do_del(es_evi); + if (es_evi) + bgp_evpn_remote_es_evi_flush(es_evi); + } + + list_delete(&vpn->local_es_evi_list); +} + +static char *bgp_evpn_es_evi_vteps_str(char *vtep_str, + struct bgp_evpn_es_evi *es_evi, + size_t vtep_str_size) +{ + char vtep_flag_str[BGP_EVPN_FLAG_STR_SZ]; + struct listnode *node; + struct bgp_evpn_es_evi_vtep *evi_vtep; + bool first = true; + char ip_buf[INET_ADDRSTRLEN]; + + vtep_str[0] = '\0'; + for (ALL_LIST_ELEMENTS_RO(es_evi->es_evi_vtep_list, node, evi_vtep)) { + vtep_flag_str[0] = '\0'; + if (evi_vtep->flags & BGP_EVPN_EVI_VTEP_EAD_PER_ES) + strlcat(vtep_flag_str, "E", sizeof(vtep_flag_str)); + if (evi_vtep->flags & BGP_EVPN_EVI_VTEP_EAD_PER_EVI) + strlcat(vtep_flag_str, "V", sizeof(vtep_flag_str)); + + if (!strnlen(vtep_flag_str, sizeof(vtep_flag_str))) + strlcpy(vtep_flag_str, "-", sizeof(vtep_flag_str)); + if (first) + first = false; + else + strlcat(vtep_str, ",", vtep_str_size); + strlcat(vtep_str, + inet_ntop(AF_INET, &evi_vtep->vtep_ip, ip_buf, + sizeof(ip_buf)), + vtep_str_size); + strlcat(vtep_str, "(", vtep_str_size); + strlcat(vtep_str, vtep_flag_str, vtep_str_size); + strlcat(vtep_str, ")", vtep_str_size); + } + + return vtep_str; +} + +static void bgp_evpn_es_evi_json_vtep_fill(json_object *json_vteps, + struct bgp_evpn_es_evi_vtep *evi_vtep) +{ + json_object *json_vtep_entry; + json_object *json_flags; + + json_vtep_entry = json_object_new_object(); + + json_object_string_addf(json_vtep_entry, "vtep_ip", "%pI4", + &evi_vtep->vtep_ip); + if (evi_vtep->flags & (BGP_EVPN_EVI_VTEP_EAD_PER_ES | + BGP_EVPN_EVI_VTEP_EAD_PER_EVI)) { + json_flags = json_object_new_array(); + if (evi_vtep->flags & BGP_EVPN_EVI_VTEP_EAD_PER_ES) + json_array_string_add(json_flags, "ead-per-es"); + if (evi_vtep->flags & BGP_EVPN_EVI_VTEP_EAD_PER_EVI) + json_array_string_add(json_flags, "ead-per-evi"); + json_object_object_add(json_vtep_entry, + "flags", json_flags); + } + + json_object_array_add(json_vteps, + json_vtep_entry); +} + +static void bgp_evpn_es_evi_show_entry(struct vty *vty, + struct bgp_evpn_es_evi *es_evi, json_object *json) +{ + struct listnode *node; + struct bgp_evpn_es_evi_vtep *evi_vtep; + + if (json) { + json_object *json_vteps; + json_object *json_types; + + json_object_string_add(json, "esi", es_evi->es->esi_str); + if (es_evi->vpn) + json_object_int_add(json, "vni", es_evi->vpn->vni); + + if (es_evi->flags & (BGP_EVPNES_EVI_LOCAL | + BGP_EVPNES_EVI_REMOTE)) { + json_types = json_object_new_array(); + if (es_evi->flags & BGP_EVPNES_EVI_LOCAL) + json_array_string_add(json_types, "local"); + if (es_evi->flags & BGP_EVPNES_EVI_REMOTE) + json_array_string_add(json_types, "remote"); + json_object_object_add(json, "type", json_types); + } + + if (listcount(es_evi->es_evi_vtep_list)) { + json_vteps = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(es_evi->es_evi_vtep_list, + node, evi_vtep)) { + bgp_evpn_es_evi_json_vtep_fill(json_vteps, + evi_vtep); + } + json_object_object_add(json, "vteps", json_vteps); + } + } else { + char type_str[4]; + char vtep_str[ES_VTEP_LIST_STR_SZ + BGP_EVPN_VTEPS_FLAG_STR_SZ]; + + type_str[0] = '\0'; + if (es_evi->flags & BGP_EVPNES_EVI_LOCAL) + strlcat(type_str, "L", sizeof(type_str)); + if (es_evi->flags & BGP_EVPNES_EVI_REMOTE) + strlcat(type_str, "R", sizeof(type_str)); + if (es_evi->flags & BGP_EVPNES_EVI_INCONS_VTEP_LIST) + strlcat(type_str, "I", sizeof(type_str)); + + bgp_evpn_es_evi_vteps_str(vtep_str, es_evi, sizeof(vtep_str)); + + vty_out(vty, "%-8d %-30s %-5s %s\n", + es_evi->vpn->vni, es_evi->es->esi_str, + type_str, vtep_str); + } +} + +static void bgp_evpn_es_evi_show_entry_detail(struct vty *vty, + struct bgp_evpn_es_evi *es_evi, json_object *json) +{ + enum asnotation_mode mode; + + mode = bgp_get_asnotation(es_evi->vpn->bgp_vrf); + + if (json) { + json_object *json_flags; + + /* Add the "brief" info first */ + bgp_evpn_es_evi_show_entry(vty, es_evi, json); + if (es_evi->es_frag) + json_object_string_addf(json, "esFragmentRd", + BGP_RD_AS_FORMAT(mode), + &es_evi->es_frag->prd); + if (es_evi->flags & BGP_EVPNES_EVI_INCONS_VTEP_LIST) { + json_flags = json_object_new_array(); + json_array_string_add(json_flags, "es-vtep-mismatch"); + json_object_object_add(json, "flags", json_flags); + } + } else { + char vtep_str[ES_VTEP_LIST_STR_SZ + BGP_EVPN_VTEPS_FLAG_STR_SZ]; + char type_str[4]; + + type_str[0] = '\0'; + if (es_evi->flags & BGP_EVPNES_EVI_LOCAL) + strlcat(type_str, "L", sizeof(type_str)); + if (es_evi->flags & BGP_EVPNES_EVI_REMOTE) + strlcat(type_str, "R", sizeof(type_str)); + + bgp_evpn_es_evi_vteps_str(vtep_str, es_evi, sizeof(vtep_str)); + if (!strlen(vtep_str)) + strlcpy(vtep_str, "-", sizeof(type_str)); + + vty_out(vty, "VNI: %d ESI: %s\n", + es_evi->vpn->vni, es_evi->es->esi_str); + vty_out(vty, " Type: %s\n", type_str); + if (es_evi->es_frag) { + vty_out(vty, " ES fragment RD: "); + vty_out(vty, BGP_RD_AS_FORMAT(mode), + &es_evi->es_frag->prd); + vty_out(vty, "\n"); + } + vty_out(vty, " Inconsistencies: %s\n", + (es_evi->flags & BGP_EVPNES_EVI_INCONS_VTEP_LIST) ? + "es-vtep-mismatch":"-"); + vty_out(vty, " VTEPs: %s\n", vtep_str); + vty_out(vty, "\n"); + } +} + +static void bgp_evpn_es_evi_show_one_vni(struct bgpevpn *vpn, struct vty *vty, + json_object *json_array, bool detail) +{ + struct bgp_evpn_es_evi *es_evi; + json_object *json = NULL; + + RB_FOREACH(es_evi, bgp_es_evi_rb_head, &vpn->es_evi_rb_tree) { + if (json_array) + /* create a separate json object for each ES */ + json = json_object_new_object(); + if (detail) + bgp_evpn_es_evi_show_entry_detail(vty, es_evi, json); + else + bgp_evpn_es_evi_show_entry(vty, es_evi, json); + /* add ES to the json array */ + if (json_array) + json_object_array_add(json_array, json); + } +} + +struct es_evi_show_ctx { + struct vty *vty; + json_object *json; + int detail; +}; + +static void bgp_evpn_es_evi_show_one_vni_hash_cb(struct hash_bucket *bucket, + void *ctxt) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct es_evi_show_ctx *wctx = (struct es_evi_show_ctx *)ctxt; + + bgp_evpn_es_evi_show_one_vni(vpn, wctx->vty, wctx->json, wctx->detail); +} + +/* Display all ES EVIs */ +void bgp_evpn_es_evi_show(struct vty *vty, bool uj, bool detail) +{ + json_object *json_array = NULL; + struct es_evi_show_ctx wctx; + struct bgp *bgp; + + if (uj) { + /* create an array of ES-EVIs */ + json_array = json_object_new_array(); + } + + wctx.vty = vty; + wctx.json = json_array; + wctx.detail = detail; + + bgp = bgp_get_evpn(); + + if (!json_array && !detail) { + vty_out(vty, "Flags: L local, R remote, I inconsistent\n"); + vty_out(vty, "VTEP-Flags: E EAD-per-ES, V EAD-per-EVI\n"); + vty_out(vty, "%-8s %-30s %-5s %s\n", + "VNI", "ESI", "Flags", "VTEPs"); + } + + if (bgp) + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void *))bgp_evpn_es_evi_show_one_vni_hash_cb, + &wctx); + if (uj) + vty_json(vty, json_array); +} + +/* Display specific ES EVI */ +void bgp_evpn_es_evi_show_vni(struct vty *vty, vni_t vni, + bool uj, bool detail) +{ + struct bgpevpn *vpn = NULL; + json_object *json_array = NULL; + struct bgp *bgp; + + if (uj) { + /* create an array of ES-EVIs */ + json_array = json_object_new_array(); + } + + bgp = bgp_get_evpn(); + if (bgp) + vpn = bgp_evpn_lookup_vni(bgp, vni); + + if (vpn) { + if (!json_array && !detail) { + vty_out(vty, "Flags: L local, R remote, I inconsistent\n"); + vty_out(vty, "VTEP-Flags: E EAD-per-ES, V EAD-per-EVI\n"); + vty_out(vty, "%-8s %-30s %-5s %s\n", + "VNI", "ESI", "Flags", "VTEPs"); + } + + bgp_evpn_es_evi_show_one_vni(vpn, vty, json_array, detail); + } else { + if (!uj) + vty_out(vty, "VNI not found\n"); + } + + if (uj) + vty_json(vty, json_array); +} + +/***************************************************************************** + * Ethernet Segment Consistency checks + * Consistency checking is done to detect misconfig or mis-cabling. When + * an inconsistency is detected it is simply logged (and displayed via + * show commands) at this point. A more drastic action can be executed (based + * on user config) in the future. + */ +static void bgp_evpn_es_cons_checks_timer_start(void) +{ + if (!bgp_mh_info->consistency_checking || bgp_mh_info->t_cons_check) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("periodic consistency checking started"); + + event_add_timer(bm->master, bgp_evpn_run_consistency_checks, NULL, + BGP_EVPN_CONS_CHECK_INTERVAL, + &bgp_mh_info->t_cons_check); +} + +/* queue up the es for background consistency checks */ +static void bgp_evpn_es_cons_checks_pend_add(struct bgp_evpn_es *es) +{ + if (!bgp_mh_info->consistency_checking) + /* consistency checking is not enabled */ + return; + + if (CHECK_FLAG(es->flags, BGP_EVPNES_CONS_CHECK_PEND)) + /* already queued for consistency checking */ + return; + + /* start the periodic timer for consistency checks if it is not + * already running */ + bgp_evpn_es_cons_checks_timer_start(); + + SET_FLAG(es->flags, BGP_EVPNES_CONS_CHECK_PEND); + listnode_init(&es->pend_es_listnode, es); + listnode_add_after(bgp_mh_info->pend_es_list, + listtail_unchecked(bgp_mh_info->pend_es_list), + &es->pend_es_listnode); +} + +/* pull the ES from the consistency check list */ +static void bgp_evpn_es_cons_checks_pend_del(struct bgp_evpn_es *es) +{ + if (!CHECK_FLAG(es->flags, BGP_EVPNES_CONS_CHECK_PEND)) + return; + + UNSET_FLAG(es->flags, BGP_EVPNES_CONS_CHECK_PEND); + list_delete_node(bgp_mh_info->pend_es_list, + &es->pend_es_listnode); +} + +/* Number of active VTEPs associated with the ES-per-EVI */ +static uint32_t bgp_evpn_es_evi_get_active_vtep_cnt( + struct bgp_evpn_es_evi *es_evi) +{ + struct bgp_evpn_es_evi_vtep *evi_vtep; + struct listnode *node; + uint32_t vtep_cnt = 0; + + for (ALL_LIST_ELEMENTS_RO(es_evi->es_evi_vtep_list, node, evi_vtep)) { + if (CHECK_FLAG(evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE)) + ++vtep_cnt; + } + + return vtep_cnt; +} + +/* Number of active VTEPs associated with the ES */ +static uint32_t bgp_evpn_es_get_active_vtep_cnt(struct bgp_evpn_es *es) +{ + struct listnode *node; + uint32_t vtep_cnt = 0; + struct bgp_evpn_es_vtep *es_vtep; + + for (ALL_LIST_ELEMENTS_RO(es->es_vtep_list, node, es_vtep)) { + if (CHECK_FLAG(es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE)) + ++vtep_cnt; + } + + return vtep_cnt; +} + +static struct bgp_evpn_es_vtep *bgp_evpn_es_get_next_active_vtep( + struct bgp_evpn_es *es, struct bgp_evpn_es_vtep *es_vtep) +{ + struct listnode *node; + struct bgp_evpn_es_vtep *next_es_vtep; + + if (es_vtep) + node = listnextnode_unchecked(&es_vtep->es_listnode); + else + node = listhead(es->es_vtep_list); + + for (; node; node = listnextnode_unchecked(node)) { + next_es_vtep = listgetdata(node); + if (CHECK_FLAG(next_es_vtep->flags, BGP_EVPNES_VTEP_ACTIVE)) + return next_es_vtep; + } + + return NULL; +} + +static struct bgp_evpn_es_evi_vtep *bgp_evpn_es_evi_get_next_active_vtep( + struct bgp_evpn_es_evi *es_evi, + struct bgp_evpn_es_evi_vtep *evi_vtep) +{ + struct listnode *node; + struct bgp_evpn_es_evi_vtep *next_evi_vtep; + + if (evi_vtep) + node = listnextnode_unchecked(&evi_vtep->es_evi_listnode); + else + node = listhead(es_evi->es_evi_vtep_list); + + for (; node; node = listnextnode_unchecked(node)) { + next_evi_vtep = listgetdata(node); + if (CHECK_FLAG(next_evi_vtep->flags, BGP_EVPN_EVI_VTEP_ACTIVE)) + return next_evi_vtep; + } + + return NULL; +} + +static void bgp_evpn_es_evi_set_inconsistent(struct bgp_evpn_es_evi *es_evi) +{ + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_INCONS_VTEP_LIST)) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("inconsistency detected - es %s evi %u vtep list mismatch", + es_evi->es->esi_str, + es_evi->vpn->vni); + SET_FLAG(es_evi->flags, BGP_EVPNES_EVI_INCONS_VTEP_LIST); + + /* update parent ES with the incosistency setting */ + if (!es_evi->es->incons_evi_vtep_cnt && + BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("inconsistency detected - es %s vtep list mismatch", + es_evi->es->esi_str); + ++es_evi->es->incons_evi_vtep_cnt; + SET_FLAG(es_evi->es->inconsistencies, + BGP_EVPNES_INCONS_VTEP_LIST); + } +} + +static uint32_t bgp_evpn_es_run_consistency_checks(struct bgp_evpn_es *es) +{ + int proc_cnt = 0; + int es_active_vtep_cnt; + int evi_active_vtep_cnt; + struct bgp_evpn_es_evi *es_evi; + struct listnode *evi_node; + struct bgp_evpn_es_vtep *es_vtep; + struct bgp_evpn_es_evi_vtep *evi_vtep; + + /* reset the inconsistencies and re-evaluate */ + es->incons_evi_vtep_cnt = 0; + es->inconsistencies = 0; + + es_active_vtep_cnt = bgp_evpn_es_get_active_vtep_cnt(es); + for (ALL_LIST_ELEMENTS_RO(es->es_evi_list, + evi_node, es_evi)) { + ++proc_cnt; + + /* reset the inconsistencies on the EVI and re-evaluate*/ + UNSET_FLAG(es_evi->flags, BGP_EVPNES_EVI_INCONS_VTEP_LIST); + + evi_active_vtep_cnt = + bgp_evpn_es_evi_get_active_vtep_cnt(es_evi); + if (es_active_vtep_cnt != evi_active_vtep_cnt) { + bgp_evpn_es_evi_set_inconsistent(es_evi); + continue; + } + + if (!es_active_vtep_cnt) + continue; + + es_vtep = NULL; + evi_vtep = NULL; + while ((es_vtep = bgp_evpn_es_get_next_active_vtep( + es, es_vtep))) { + evi_vtep = bgp_evpn_es_evi_get_next_active_vtep(es_evi, + evi_vtep); + if (!evi_vtep) { + bgp_evpn_es_evi_set_inconsistent(es_evi); + break; + } + if (es_vtep->vtep_ip.s_addr != + evi_vtep->vtep_ip.s_addr) { + /* inconsistency detected; set it and move + * to the next evi + */ + bgp_evpn_es_evi_set_inconsistent(es_evi); + break; + } + } + } + + return proc_cnt; +} + +static void bgp_evpn_run_consistency_checks(struct event *t) +{ + int proc_cnt = 0; + struct listnode *node; + struct listnode *nextnode; + struct bgp_evpn_es *es; + + for (ALL_LIST_ELEMENTS(bgp_mh_info->pend_es_list, + node, nextnode, es)) { + ++proc_cnt; + /* run consistency checks on the ES and remove it from the + * pending list + */ + proc_cnt += bgp_evpn_es_run_consistency_checks(es); + bgp_evpn_es_cons_checks_pend_del(es); + if (proc_cnt > 500) + break; + } + + /* restart the timer */ + event_add_timer(bm->master, bgp_evpn_run_consistency_checks, NULL, + BGP_EVPN_CONS_CHECK_INTERVAL, + &bgp_mh_info->t_cons_check); +} + +/***************************************************************************** + * EVPN-Nexthop and RMAC management: nexthops associated with Type-2 routes + * that have an ES as destination are consolidated by BGP into a per-VRF + * nh->rmac mapping which is sent to zebra. Zebra installs the nexthop + * as a remote neigh/fdb entry with a dummy (type-1) prefix referencing it. + * + * This handling is needed because Type-2 routes with ES as dest use NHG + * that is setup using EAD routes (i.e. such NHGs do not include the + * RMAC info). + ****************************************************************************/ +static void bgp_evpn_nh_zebra_update_send(struct bgp_evpn_nh *nh, bool add) +{ + struct stream *s; + struct bgp *bgp_vrf = nh->bgp_vrf; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp_vrf)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("No zebra instance, not %s remote nh %s", + add ? "adding" : "deleting", nh->nh_str); + return; + } + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header( + s, add ? ZEBRA_EVPN_REMOTE_NH_ADD : ZEBRA_EVPN_REMOTE_NH_DEL, + bgp_vrf->vrf_id); + stream_putl(s, bgp_vrf->vrf_id); + stream_put(s, &nh->ip, sizeof(nh->ip)); + if (add) + stream_put(s, &nh->rmac, sizeof(nh->rmac)); + + stream_putw_at(s, 0, stream_get_endp(s)); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES) || bgp_debug_zebra(NULL)) { + if (add) + zlog_debug("evpn %s nh %s rmac %pEA add to zebra", + nh->bgp_vrf->name_pretty, nh->nh_str, + &nh->rmac); + else + zlog_debug("evpn %s nh %s del to zebra", + nh->bgp_vrf->name_pretty, nh->nh_str); + } + + frrtrace(2, frr_bgp, evpn_mh_nh_rmac_zsend, add, nh); + + zclient_send_message(zclient); +} + +static void bgp_evpn_nh_zebra_update(struct bgp_evpn_nh *nh, bool add) +{ + if (add && !is_zero_mac(&nh->rmac)) { + nh->flags |= BGP_EVPN_NH_READY_FOR_ZEBRA; + bgp_evpn_nh_zebra_update_send(nh, true); + } else { + if (!(nh->flags & BGP_EVPN_NH_READY_FOR_ZEBRA)) + return; + nh->flags &= ~BGP_EVPN_NH_READY_FOR_ZEBRA; + bgp_evpn_nh_zebra_update_send(nh, false); + } +} + +static void *bgp_evpn_nh_alloc(void *p) +{ + struct bgp_evpn_nh *tmp_n = p; + struct bgp_evpn_nh *n; + + n = XCALLOC(MTYPE_BGP_EVPN_NH, sizeof(struct bgp_evpn_nh)); + *n = *tmp_n; + + return ((void *)n); +} + +static struct bgp_evpn_nh *bgp_evpn_nh_find(struct bgp *bgp_vrf, + struct ipaddr *ip) +{ + struct bgp_evpn_nh tmp; + struct bgp_evpn_nh *n; + + memset(&tmp, 0, sizeof(tmp)); + memcpy(&tmp.ip, ip, sizeof(struct ipaddr)); + n = hash_lookup(bgp_vrf->evpn_nh_table, &tmp); + + return n; +} + +/* Add nexthop entry - implicitly created on first path reference */ +static struct bgp_evpn_nh *bgp_evpn_nh_add(struct bgp *bgp_vrf, + struct ipaddr *ip, + struct bgp_path_info *pi) +{ + struct bgp_evpn_nh tmp_n; + struct bgp_evpn_nh *n = NULL; + + memset(&tmp_n, 0, sizeof(tmp_n)); + memcpy(&tmp_n.ip, ip, sizeof(struct ipaddr)); + n = hash_get(bgp_vrf->evpn_nh_table, &tmp_n, bgp_evpn_nh_alloc); + ipaddr2str(ip, n->nh_str, sizeof(n->nh_str)); + n->bgp_vrf = bgp_vrf; + + n->pi_list = list_new(); + listset_app_node_mem(n->pi_list); + + /* Setup ref_pi when the nh is created */ + if (CHECK_FLAG(pi->flags, BGP_PATH_VALID) && pi->attr) { + n->ref_pi = pi; + memcpy(&n->rmac, &pi->attr->rmac, ETH_ALEN); + } + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh %s rmac %pEA add", + n->bgp_vrf->name_pretty, n->nh_str, &n->rmac); + bgp_evpn_nh_zebra_update(n, true); + return n; +} + +/* Delete nexthop entry if there are no paths referencing it */ +static void bgp_evpn_nh_del(struct bgp_evpn_nh *n) +{ + struct bgp_evpn_nh *tmp_n; + struct bgp *bgp_vrf = n->bgp_vrf; + + if (listcount(n->pi_list)) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh %s del to zebra", + bgp_vrf->name_pretty, n->nh_str); + + bgp_evpn_nh_zebra_update(n, false); + list_delete(&n->pi_list); + tmp_n = hash_release(bgp_vrf->evpn_nh_table, n); + XFREE(MTYPE_BGP_EVPN_NH, tmp_n); +} + +static void hash_evpn_nh_free(struct bgp_evpn_nh *ben) +{ + XFREE(MTYPE_BGP_EVPN_NH, ben); +} + +static unsigned int bgp_evpn_nh_hash_keymake(const void *p) +{ + const struct bgp_evpn_nh *n = p; + const struct ipaddr *ip = &n->ip; + + if (IS_IPADDR_V4(ip)) + return jhash_1word(ip->ipaddr_v4.s_addr, 0); + + return jhash2(ip->ipaddr_v6.s6_addr32, + array_size(ip->ipaddr_v6.s6_addr32), 0); +} + +static bool bgp_evpn_nh_cmp(const void *p1, const void *p2) +{ + const struct bgp_evpn_nh *n1 = p1; + const struct bgp_evpn_nh *n2 = p2; + + if (n1 == NULL && n2 == NULL) + return true; + + if (n1 == NULL || n2 == NULL) + return false; + + return (ipaddr_cmp(&n1->ip, &n2->ip) == 0); +} + +void bgp_evpn_nh_init(struct bgp *bgp_vrf) +{ + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh init", bgp_vrf->name_pretty); + bgp_vrf->evpn_nh_table = hash_create( + bgp_evpn_nh_hash_keymake, bgp_evpn_nh_cmp, "BGP EVPN NH table"); +} + +static void bgp_evpn_nh_flush_entry(struct bgp_evpn_nh *nh) +{ + struct listnode *node; + struct listnode *nnode; + struct bgp_path_evpn_nh_info *nh_info; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh %s flush", nh->bgp_vrf->name_pretty, + nh->nh_str); + + /* force flush paths */ + for (ALL_LIST_ELEMENTS(nh->pi_list, node, nnode, nh_info)) + bgp_evpn_path_nh_del(nh->bgp_vrf, nh_info->pi); +} + +static void bgp_evpn_nh_flush_cb(struct hash_bucket *bucket, void *ctxt) +{ + struct bgp_evpn_nh *nh = (struct bgp_evpn_nh *)bucket->data; + + bgp_evpn_nh_flush_entry(nh); +} + +void bgp_evpn_nh_finish(struct bgp *bgp_vrf) +{ + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh finish", bgp_vrf->name_pretty); + hash_iterate( + bgp_vrf->evpn_nh_table, + (void (*)(struct hash_bucket *, void *))bgp_evpn_nh_flush_cb, + NULL); + hash_clean_and_free(&bgp_vrf->evpn_nh_table, + (void (*)(void *))hash_evpn_nh_free); +} + +static void bgp_evpn_nh_update_ref_pi(struct bgp_evpn_nh *nh) +{ + struct listnode *node; + struct bgp_path_info *pi; + struct bgp_path_evpn_nh_info *nh_info; + + if (nh->ref_pi) + return; + + for (ALL_LIST_ELEMENTS_RO(nh->pi_list, node, nh_info)) { + pi = nh_info->pi; + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) || !pi->attr) + continue; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn %s nh %s ref_pi update", + nh->bgp_vrf->name_pretty, nh->nh_str); + nh->ref_pi = pi; + /* If we have a new pi copy rmac from it and update + * zebra if the new rmac is different + */ + if (memcmp(&nh->rmac, &nh->ref_pi->attr->rmac, ETH_ALEN)) { + memcpy(&nh->rmac, &nh->ref_pi->attr->rmac, ETH_ALEN); + bgp_evpn_nh_zebra_update(nh, true); + } + break; + } +} + +static void bgp_evpn_nh_clear_ref_pi(struct bgp_evpn_nh *nh, + struct bgp_path_info *pi) +{ + if (nh->ref_pi != pi) + return; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("evpn vrf %s nh %s ref_pi clear", + nh->bgp_vrf->name_pretty, nh->nh_str); + nh->ref_pi = NULL; + /* try to find another ref_pi */ + bgp_evpn_nh_update_ref_pi(nh); + /* couldn't find one - clear the old rmac and notify zebra */ + if (!nh->ref_pi) { + memset(&nh->rmac, 0, ETH_ALEN); + bgp_evpn_nh_zebra_update(nh, true); + } +} + +static void bgp_evpn_path_nh_info_free(struct bgp_path_evpn_nh_info *nh_info) +{ + bgp_evpn_path_nh_unlink(nh_info); + XFREE(MTYPE_BGP_EVPN_PATH_NH_INFO, nh_info); +} + +static struct bgp_path_evpn_nh_info * +bgp_evpn_path_nh_info_new(struct bgp_path_info *pi) +{ + struct bgp_path_info_extra *e; + struct bgp_path_mh_info *mh_info; + struct bgp_path_evpn_nh_info *nh_info; + + e = bgp_path_info_extra_get(pi); + + /* If mh_info doesn't exist allocate it */ + mh_info = e->evpn->mh_info; + if (!mh_info) + e->evpn->mh_info = mh_info = + XCALLOC(MTYPE_BGP_EVPN_PATH_MH_INFO, + sizeof(struct bgp_path_mh_info)); + + /* If nh_info doesn't exist allocate it */ + nh_info = mh_info->nh_info; + if (!nh_info) { + mh_info->nh_info = nh_info = + XCALLOC(MTYPE_BGP_EVPN_PATH_NH_INFO, + sizeof(struct bgp_path_evpn_nh_info)); + nh_info->pi = pi; + } + + return nh_info; +} + +static void bgp_evpn_path_nh_unlink(struct bgp_path_evpn_nh_info *nh_info) +{ + struct bgp_evpn_nh *nh = nh_info->nh; + struct bgp_path_info *pi; + char prefix_buf[PREFIX_STRLEN]; + + if (!nh) + return; + + pi = nh_info->pi; + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("path %s unlinked from %s nh %s pathcount %u", + pi->net ? prefix2str(&pi->net->rn->p, prefix_buf, + sizeof(prefix_buf)) + : "", + nh->bgp_vrf->name_pretty, nh->nh_str, + listcount(nh->pi_list)); + + list_delete_node(nh->pi_list, &nh_info->nh_listnode); + + nh_info->nh = NULL; + + /* check if the ref_pi need to be updated */ + bgp_evpn_nh_clear_ref_pi(nh, pi); + + /* if there are no other references against the nh it + * needs to be freed + */ + bgp_evpn_nh_del(nh); + + /* Note we don't free the path nh_info on unlink; it will be freed up + * along with the path. + */ +} + +static void bgp_evpn_path_nh_link(struct bgp *bgp_vrf, struct bgp_path_info *pi) +{ + struct bgp_path_evpn_nh_info *nh_info; + struct bgp_evpn_nh *nh; + struct ipaddr ip; + + /* EVPN nexthop setup in bgp has been turned off */ + if (!bgp_mh_info->bgp_evpn_nh_setup) + return; + + if (!bgp_vrf->evpn_nh_table) { + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("path %pFX linked to %s failed", + &pi->net->rn->p, bgp_vrf->name_pretty); + return; + } + + nh_info = (pi->extra && pi->extra->evpn && pi->extra->evpn->mh_info) + ? pi->extra->evpn->mh_info->nh_info + : NULL; + + /* if NHG is not being used for this path we don't need to manage the + * nexthops in bgp (they are managed by zebra instead) + */ + if (!(pi->attr->es_flags & ATTR_ES_L3_NHG_USE)) { + if (nh_info) + bgp_evpn_path_nh_unlink(nh_info); + return; + } + + /* setup nh_info against the path if it doesn't aleady exist */ + if (!nh_info) + nh_info = bgp_evpn_path_nh_info_new(pi); + + /* find-create nh */ + memset(&ip, 0, sizeof(ip)); + if (pi->net->rn->p.family == AF_INET6) { + SET_IPADDR_V6(&ip); + memcpy(&ip.ipaddr_v6, &pi->attr->mp_nexthop_global, + sizeof(ip.ipaddr_v6)); + } else { + SET_IPADDR_V4(&ip); + memcpy(&ip.ipaddr_v4, &pi->attr->nexthop, sizeof(ip.ipaddr_v4)); + } + + nh = bgp_evpn_nh_find(bgp_vrf, &ip); + if (!nh) + nh = bgp_evpn_nh_add(bgp_vrf, &ip, pi); + + /* dup check */ + if (nh_info->nh == nh) { + /* Check if any of the paths are now valid */ + bgp_evpn_nh_update_ref_pi(nh); + return; + } + + /* unlink old nh if any */ + bgp_evpn_path_nh_unlink(nh_info); + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("path %pFX linked to %s nh %s pathcount %u", + &pi->net->rn->p, nh->bgp_vrf->name_pretty, + nh->nh_str, listcount(nh->pi_list)); + + /* link mac-ip path to the new nh */ + nh_info->nh = nh; + listnode_init(&nh_info->nh_listnode, nh_info); + listnode_add(nh->pi_list, &nh_info->nh_listnode); + /* If a new valid path got linked to the nh see if can get the rmac + * from it + */ + bgp_evpn_nh_update_ref_pi(nh); + if (BGP_DEBUG(evpn_mh, EVPN_MH_ES)) { + if (!nh->ref_pi) + zlog_debug( + "path %pFX linked to nh %s %s with no valid pi", + &pi->net->rn->p, nh->bgp_vrf->name_pretty, + nh->nh_str); + } +} + +void bgp_evpn_path_nh_del(struct bgp *bgp_vrf, struct bgp_path_info *pi) +{ + struct bgp_path_evpn_nh_info *nh_info; + + nh_info = (pi->extra && pi->extra->evpn && pi->extra->evpn->mh_info) + ? pi->extra->evpn->mh_info->nh_info + : NULL; + + if (!nh_info) + return; + + bgp_evpn_path_nh_unlink(nh_info); +} + +void bgp_evpn_path_nh_add(struct bgp *bgp_vrf, struct bgp_path_info *pi) +{ + bgp_evpn_path_nh_link(bgp_vrf, pi); +} + +static void bgp_evpn_nh_show_entry(struct bgp_evpn_nh *nh, struct vty *vty, + json_object *json_array) +{ + json_object *json = NULL; + char mac_buf[ETHER_ADDR_STRLEN]; + char prefix_buf[PREFIX_STRLEN]; + + if (json_array) + /* create a separate json object for each ES */ + json = json_object_new_object(); + + prefix_mac2str(&nh->rmac, mac_buf, sizeof(mac_buf)); + if (nh->ref_pi && nh->ref_pi->net) + prefix2str(&nh->ref_pi->net->rn->p, prefix_buf, sizeof(prefix_buf)); + else + prefix_buf[0] = '\0'; + if (json) { + json_object_string_add(json, "vrf", nh->bgp_vrf->name_pretty); + json_object_string_add(json, "ip", nh->nh_str); + json_object_string_add(json, "rmac", mac_buf); + json_object_string_add(json, "basePath", prefix_buf); + json_object_int_add(json, "pathCount", listcount(nh->pi_list)); + } else { + vty_out(vty, "%-15s %-15s %-17s %-10d %s\n", + nh->bgp_vrf->name_pretty, nh->nh_str, mac_buf, + listcount(nh->pi_list), prefix_buf); + } + + /* add ES to the json array */ + if (json_array) + json_object_array_add(json_array, json); +} + +struct nh_show_ctx { + struct vty *vty; + json_object *json; +}; + +static void bgp_evpn_nh_show_hash_cb(struct hash_bucket *bucket, void *ctxt) +{ + struct bgp_evpn_nh *nh = (struct bgp_evpn_nh *)bucket->data; + struct nh_show_ctx *wctx = (struct nh_show_ctx *)ctxt; + + bgp_evpn_nh_show_entry(nh, wctx->vty, wctx->json); +} + +/* Display all evpn nexthops */ +void bgp_evpn_nh_show(struct vty *vty, bool uj) +{ + json_object *json_array = NULL; + struct bgp *bgp_vrf; + struct listnode *node; + struct nh_show_ctx wctx; + + if (uj) { + /* create an array of nexthops */ + json_array = json_object_new_array(); + } else { + vty_out(vty, "%-15s %-15s %-17s %-10s %s\n", "VRF", "IP", + "RMAC", "#Paths", "Base Path"); + } + + wctx.vty = vty; + wctx.json = json_array; + + /* walk through all vrfs */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + hash_iterate(bgp_vrf->evpn_nh_table, + (void (*)(struct hash_bucket *, + void *))bgp_evpn_nh_show_hash_cb, + &wctx); + } + + /* print the array of json-ESs */ + if (uj) + vty_json(vty, json_array); +} + +/*****************************************************************************/ +void bgp_evpn_mh_init(void) +{ + bm->mh_info = XCALLOC(MTYPE_BGP_EVPN_MH_INFO, sizeof(*bm->mh_info)); + + /* setup ES tables */ + RB_INIT(bgp_es_rb_head, &bgp_mh_info->es_rb_tree); + /* local ES list */ + bgp_mh_info->local_es_list = list_new(); + listset_app_node_mem(bgp_mh_info->local_es_list); + /* list of ESs with pending processing */ + bgp_mh_info->pend_es_list = list_new(); + listset_app_node_mem(bgp_mh_info->pend_es_list); + + bgp_mh_info->ead_evi_rx = BGP_EVPN_MH_EAD_EVI_RX_DEF; + bgp_mh_info->ead_evi_tx = BGP_EVPN_MH_EAD_EVI_TX_DEF; + bgp_mh_info->ead_es_export_rtl = list_new(); + bgp_mh_info->ead_es_export_rtl->cmp = + (int (*)(void *, void *))bgp_evpn_route_target_cmp; + bgp_mh_info->ead_es_export_rtl->del = bgp_evpn_xxport_delete_ecomm; + + /* config knobs - XXX add cli to control it */ + bgp_mh_info->ead_evi_adv_for_down_links = true; + bgp_mh_info->consistency_checking = true; + bgp_mh_info->host_routes_use_l3nhg = BGP_EVPN_MH_USE_ES_L3NHG_DEF; + bgp_mh_info->suppress_l3_ecomm_on_inactive_es = true; + bgp_mh_info->bgp_evpn_nh_setup = true; + bgp_mh_info->evi_per_es_frag = BGP_EVPN_MAX_EVI_PER_ES_FRAG; + + memset(&zero_esi_buf, 0, sizeof(esi_t)); +} + +void bgp_evpn_mh_finish(void) +{ + struct bgp_evpn_es *es; + struct bgp_evpn_es *es_next; + + if (BGP_DEBUG(evpn_mh, EVPN_MH_RT)) + zlog_debug("evpn mh finish"); + + RB_FOREACH_SAFE (es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree, + es_next) { + bgp_evpn_es_local_info_clear(es, true); + } + if (bgp_mh_info->t_cons_check) + EVENT_OFF(bgp_mh_info->t_cons_check); + list_delete(&bgp_mh_info->local_es_list); + list_delete(&bgp_mh_info->pend_es_list); + list_delete(&bgp_mh_info->ead_es_export_rtl); + + XFREE(MTYPE_BGP_EVPN_MH_INFO, bgp_mh_info); +} + +/* This function is called when disable-ead-evi-rx knob flaps */ +void bgp_evpn_switch_ead_evi_rx(void) +{ + struct bgp *bgp; + struct bgp_evpn_es *es; + struct bgp_evpn_es_evi *es_evi; + struct listnode *evi_node = NULL; + struct listnode *evi_next = NULL; + struct bgp_evpn_es_evi_vtep *vtep; + struct listnode *vtep_node = NULL; + struct listnode *vtep_next = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return; + + /* + * Process all the remote es_evi_vteps and reevaluate if the es_evi_vtep + * is active. + */ + RB_FOREACH(es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree) { + if (!CHECK_FLAG(es->flags, BGP_EVPNES_REMOTE)) + continue; + + for (ALL_LIST_ELEMENTS(es->es_evi_list, evi_node, evi_next, + es_evi)) { + if (!CHECK_FLAG(es_evi->flags, BGP_EVPNES_EVI_REMOTE)) + continue; + + for (ALL_LIST_ELEMENTS(es_evi->es_evi_vtep_list, + vtep_node, vtep_next, vtep)) + bgp_evpn_es_evi_vtep_re_eval_active(bgp, vtep); + } + } +} diff --git a/bgpd/bgp_evpn_mh.h b/bgpd/bgp_evpn_mh.h new file mode 100644 index 0000000..5d393c3 --- /dev/null +++ b/bgpd/bgp_evpn_mh.h @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* EVPN header for multihoming procedures + * + * Copyright (C) 2019 Cumulus Networks + * Anuradha Karuppiah + * + */ + +#ifndef _FRR_BGP_EVPN_MH_H +#define _FRR_BGP_EVPN_MH_H + +#include "vxlan.h" +#include "bgpd.h" +#include "bgp_evpn.h" +#include "bgp_evpn_private.h" + +#define BGP_EVPN_AD_ES_ETH_TAG 0xffffffff +#define BGP_EVPN_AD_EVI_ETH_TAG 0 + +#define BGP_EVPNES_INCONS_STR_SZ 80 +#define BGP_EVPN_VTEPS_FLAG_STR_SZ (BGP_EVPN_FLAG_STR_SZ * ES_VTEP_MAX_CNT) + +#define BGP_EVPN_CONS_CHECK_INTERVAL 60 + +#define BGP_EVPN_MH_USE_ES_L3NHG_DEF true + +/* XXX - tune this */ +#define BGP_EVPN_MAX_EVI_PER_ES_FRAG 128 + +/* An ES can result in multiple EAD-per-ES route. Each EAD fragment is + * associated with an unique RD + */ +struct bgp_evpn_es_frag { + /* frag is associated with a parent ES */ + struct bgp_evpn_es *es; + + /* Id for deriving the RD automatically for this ES fragment */ + uint16_t rd_id; + /* RD for this ES fragment */ + struct prefix_rd prd; + + /* Memory used for linking bgp_evpn_es_frag to + * bgp_evpn_es->es_frag_list + */ + struct listnode es_listnode; + + /* List of ES-EVIs associated with this fragment */ + struct list *es_evi_frag_list; +}; + +/* Ethernet Segment entry - + * - Local and remote ESs are maintained in a global RB tree, + * bgp_mh_info->es_rb_tree using ESI as key + * - Local ESs are received from zebra (BGP_EVPNES_LOCAL) + * - Remotes ESs are implicitly created (by reference) by a remote ES-EVI + * (BGP_EVPNES_REMOTE) + * - An ES can be simultaneously LOCAL and REMOTE; infact all LOCAL ESs are + * expected to have REMOTE ES peers. + */ +struct bgp_evpn_es { + /* Ethernet Segment Identifier */ + esi_t esi; + char esi_str[ESI_STR_LEN]; + + /* es flags */ + uint32_t flags; + /* created via zebra config */ +#define BGP_EVPNES_LOCAL (1 << 0) + /* created implicitly by a remote ES-EVI reference */ +#define BGP_EVPNES_REMOTE (1 << 1) + /* local ES link is oper-up */ +#define BGP_EVPNES_OPER_UP (1 << 2) + /* enable generation of EAD-EVI routes */ +#define BGP_EVPNES_ADV_EVI (1 << 3) + /* consistency checks pending */ +#define BGP_EVPNES_CONS_CHECK_PEND (1 << 4) + /* ES is in LACP bypass mode - don't advertise EAD-ES or ESR */ +#define BGP_EVPNES_BYPASS (1 << 5) + /* bits needed for printing the flags + null */ +#define BGP_EVPN_FLAG_STR_SZ 7 + + /* memory used for adding the es to bgp->es_rb_tree */ + RB_ENTRY(bgp_evpn_es) rb_node; + + /* [EVPNES_LOCAL] memory used for linking the es to + * bgp_mh_info->local_es_list + */ + struct listnode es_listnode; + + /* memory used for linking the es to "processing" pending list + * bgp_mh_info->pend_es_list + */ + struct listnode pend_es_listnode; + + /* [EVPNES_LOCAL] List of RDs for this ES (bgp_evpn_es_frag) */ + struct list *es_frag_list; + struct bgp_evpn_es_frag *es_base_frag; + + /* [EVPNES_LOCAL] originator ip address */ + struct in_addr originator_ip; + + /* [EVPNES_LOCAL] Route table for EVPN routes for this ESI- + * - Type-4 local and remote routes + * - Type-1 local routes + */ + struct bgp_table *route_table; + + /* list of PEs (bgp_evpn_es_vtep) attached to the ES */ + struct list *es_vtep_list; + + /* List of ES-EVIs associated with this ES */ + struct list *es_evi_list; + + /* List of ES-VRFs associated with this ES */ + struct list *es_vrf_list; + + /* List of MAC-IP VNI paths using this ES as destination - + * element is bgp_path_info_extra->es_info + * Note: Only local/zebra-added MACIP paths in the VNI + * routing table are linked to this list + */ + struct list *macip_evi_path_list; + + /* List of MAC-IP paths in the global routing table using this + * ES as destination - data is bgp_path_info_extra->es_info + * Note: Only non-local/imported MACIP paths in the global + * routing table are linked to this list + */ + struct list *macip_global_path_list; + + /* Number of remote VNIs referencing this ES */ + uint32_t remote_es_evi_cnt; + + uint32_t inconsistencies; + /* there are one or more EVIs whose VTEP list doesn't match + * with the ES's VTEP list + */ +#define BGP_EVPNES_INCONS_VTEP_LIST (1 << 0) + + /* number of es-evi entries whose VTEP list doesn't match + * with the ES's + */ + uint32_t incons_evi_vtep_cnt; + + /* preference config for BUM-DF election. advertised via the ESR. */ + uint16_t df_pref; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bgp_evpn_es); +RB_HEAD(bgp_es_rb_head, bgp_evpn_es); +RB_PROTOTYPE(bgp_es_rb_head, bgp_evpn_es, rb_node, bgp_es_rb_cmp); + +/* PE attached to an ES */ +struct bgp_evpn_es_vtep { + struct bgp_evpn_es *es; /* parent ES */ + struct in_addr vtep_ip; + + char vtep_str[INET6_ADDRSTRLEN]; + + uint32_t flags; + /* Rxed a Type4 route from this PE */ +#define BGP_EVPNES_VTEP_ESR (1 << 0) + /* Active (rxed EAD-ES and EAD-EVI) and can be included as + * a nexthop + */ +#define BGP_EVPNES_VTEP_ACTIVE (1 << 1) + + uint32_t evi_cnt; /* es_evis referencing this vtep as an active path */ + + /* Algorithm and preference for DF election. Rxed via the ESR */ + uint8_t df_alg; + uint16_t df_pref; + + /* memory used for adding the entry to es->es_vtep_list */ + struct listnode es_listnode; +}; + +/* ES-VRF element needed for managing L3 NHGs. It is implicitly created + * when an ES-EVI is associated with a tenant VRF + */ +struct bgp_evpn_es_vrf { + struct bgp_evpn_es *es; + struct bgp *bgp_vrf; + + uint32_t flags; +/* NHG can only be activated if there are active VTEPs in the ES and + * there is a valid L3-VNI associated with the VRF + */ +#define BGP_EVPNES_VRF_NHG_ACTIVE (1 << 0) + + /* memory used for adding the es_vrf to + * es_vrf->bgp_vrf->es_vrf_rb_tree + */ + RB_ENTRY(bgp_evpn_es_vrf) rb_node; + + /* memory used for linking the es_vrf to es_vrf->es->es_vrf_list */ + struct listnode es_listnode; + + uint32_t nhg_id; + uint32_t v6_nhg_id; + + /* Number of ES-EVI entries associated with this ES-VRF */ + uint32_t ref_cnt; +}; + +/* ES per-EVI info + * - ES-EVIs are maintained per-L2-VNI (vpn->es_evi_rb_tree) + * - ES-EVIs are also linked to the parent ES (es->es_evi_list) + * - Local ES-EVIs are created by zebra (via config). They are linked to a + * per-VNI list (vpn->local_es_evi_list) for quick access + * - Remote ES-EVIs are created implicitly when a bgp_evpn_es_evi_vtep + * references it. + */ +struct bgp_evpn_es_evi { + struct bgp_evpn_es *es; + /* Only applicableif EVI_LOCAL */ + struct bgp_evpn_es_frag *es_frag; + struct bgpevpn *vpn; + + /* ES-EVI flags */ + uint32_t flags; +/* local ES-EVI, created by zebra */ +#define BGP_EVPNES_EVI_LOCAL (1 << 0) +/* created via a remote VTEP imported by BGP */ +#define BGP_EVPNES_EVI_REMOTE (1 << 1) +#define BGP_EVPNES_EVI_INCONS_VTEP_LIST (1 << 2) + + /* memory used for adding the es_evi to es_evi->vpn->es_evi_rb_tree */ + RB_ENTRY(bgp_evpn_es_evi) rb_node; + /* memory used for linking the es_evi to + * es_evi->vpn->local_es_evi_list + */ + struct listnode l2vni_listnode; + /* memory used for linking the es_evi to + * es_evi->es->es_evi_list + */ + struct listnode es_listnode; + + /* memory used for linking the es_evi to + * es_evi->es_frag->es_evi_frag_list + */ + struct listnode es_frag_listnode; + /* list of PEs (bgp_evpn_es_evi_vtep) attached to the ES for this VNI */ + struct list *es_evi_vtep_list; + + struct bgp_evpn_es_vrf *es_vrf; +}; + +/* PE attached to an ES for a VNI. This entry is created when an EAD-per-ES + * or EAD-per-EVI Type1 route is imported into the VNI. + */ +struct bgp_evpn_es_evi_vtep { + struct bgp_evpn_es_evi *es_evi; /* parent ES-EVI */ + struct in_addr vtep_ip; + + uint32_t flags; + /* Rxed an EAD-per-ES route from the PE */ +#define BGP_EVPN_EVI_VTEP_EAD_PER_ES (1 << 0) /* rxed EAD-per-ES */ + /* Rxed an EAD-per-EVI route from the PE */ +#define BGP_EVPN_EVI_VTEP_EAD_PER_EVI (1 << 1) /* rxed EAD-per-EVI */ + /* VTEP is active i.e. will result in the creation of an es-vtep */ +#define BGP_EVPN_EVI_VTEP_ACTIVE (1 << 2) +#define BGP_EVPN_EVI_VTEP_EAD (BGP_EVPN_EVI_VTEP_EAD_PER_ES |\ + BGP_EVPN_EVI_VTEP_EAD_PER_EVI) + + /* memory used for adding the entry to es_evi->es_evi_vtep_list */ + struct listnode es_evi_listnode; + struct bgp_evpn_es_vtep *es_vtep; +}; + +/* A nexthop is created when a path (imported from an EVPN type-2 route) + * is added to the VRF route table using that nexthop. + * It is added on first pi reference and removed on last pi deref. + */ +struct bgp_evpn_nh { + /* backpointer to the VRF */ + struct bgp *bgp_vrf; + /* nexthop/VTEP IP */ + struct ipaddr ip; + /* description for easy logging */ + char nh_str[INET6_ADDRSTRLEN]; + struct ethaddr rmac; + /* pi from which we are pulling the nh RMAC */ + struct bgp_path_info *ref_pi; + /* List of VRF paths using this nexthop */ + struct list *pi_list; + uint8_t flags; +#define BGP_EVPN_NH_READY_FOR_ZEBRA (1 << 0) +}; + +/* multihoming information stored in bgp_master */ +#define bgp_mh_info (bm->mh_info) +struct bgp_evpn_mh_info { + /* RB tree of Ethernet segments (used for EVPN-MH) */ + struct bgp_es_rb_head es_rb_tree; + /* List of local ESs */ + struct list *local_es_list; + /* List of ESs with pending/periodic processing */ + struct list *pend_es_list; + /* periodic timer for running background consistency checks */ + struct event *t_cons_check; + + /* config knobs for optimizing or interop */ + /* Generate EAD-EVI routes even if the ES is oper-down. This can be + * enabled as an optimization to avoid a storm of updates when an ES + * link flaps. + */ + bool ead_evi_adv_for_down_links; + /* Enable ES consistency checking */ + bool consistency_checking; + /* Use L3 NHGs for host routes in symmetric IRB */ + bool host_routes_use_l3nhg; + /* Some vendors are not generating the EAD-per-EVI route. This knob + * can be turned off to activate a remote ES-PE when the EAD-per-ES + * route is rxed i.e. not wait on the EAD-per-EVI route + */ + bool ead_evi_rx; +#define BGP_EVPN_MH_EAD_EVI_RX_DEF true + /* Skip EAD-EVI advertisements by turning off this knob */ + bool ead_evi_tx; +#define BGP_EVPN_MH_EAD_EVI_TX_DEF true + /* If the Local ES is inactive we advertise the MAC-IP without the + * L3 ecomm + */ + bool suppress_l3_ecomm_on_inactive_es; + /* Setup EVPN PE nexthops and their RMAC in bgpd */ + bool bgp_evpn_nh_setup; + + /* If global export-rts are configured that is used for sending + * sending the ead-per-es route instead of the L2-VNI(s) RTs + */ + struct list *ead_es_export_rtl; + + /* Number of EVIs in an ES fragment - used of EAD-per-ES route + * construction + */ + uint32_t evi_per_es_frag; +}; + +/****************************************************************************/ +static inline int bgp_evpn_is_es_local(struct bgp_evpn_es *es) +{ + return CHECK_FLAG(es->flags, BGP_EVPNES_LOCAL) ? 1 : 0; +} + +extern esi_t *zero_esi; +static inline bool bgp_evpn_is_esi_valid(esi_t *esi) +{ + return !!memcmp(esi, zero_esi, sizeof(esi_t)); +} + +static inline esi_t *bgp_evpn_attr_get_esi(struct attr *attr) +{ + return attr ? &attr->esi : zero_esi; +} + +static inline bool bgp_evpn_attr_is_sync(struct attr *attr) +{ + return attr ? !!(attr->es_flags & + (ATTR_ES_PEER_PROXY | ATTR_ES_PEER_ACTIVE)) : false; +} + +static inline uint32_t bgp_evpn_attr_get_sync_seq(struct attr *attr) +{ + return attr ? attr->mm_sync_seqnum : 0; +} + +static inline bool bgp_evpn_attr_is_active_on_peer(struct attr *attr) +{ + return attr ? + !!(attr->es_flags & ATTR_ES_PEER_ACTIVE) : false; +} + +static inline bool bgp_evpn_attr_is_router_on_peer(struct attr *attr) +{ + return attr ? + !!(attr->es_flags & ATTR_ES_PEER_ROUTER) : false; +} + +static inline bool bgp_evpn_attr_is_proxy(struct attr *attr) +{ + return attr ? !!(attr->es_flags & ATTR_ES_PROXY_ADVERT) : false; +} + +static inline bool bgp_evpn_attr_is_local_es(struct attr *attr) +{ + return attr ? !!(attr->es_flags & ATTR_ES_IS_LOCAL) : false; +} + +static inline bool bgp_evpn_local_es_is_active(struct bgp_evpn_es *es) +{ + return (es->flags & BGP_EVPNES_OPER_UP) + && !(es->flags & BGP_EVPNES_BYPASS); +} + +/****************************************************************************/ +extern int bgp_evpn_es_route_install_uninstall(struct bgp *bgp, + struct bgp_evpn_es *es, afi_t afi, safi_t safi, + struct prefix_evpn *evp, struct bgp_path_info *pi, + int install); +extern void update_type1_routes_for_evi(struct bgp *bgp, struct bgpevpn *vpn); +extern int delete_global_ead_evi_routes(struct bgp *bgp, struct bgpevpn *vpn); +extern int bgp_evpn_mh_route_update(struct bgp *bgp, struct bgp_evpn_es *es, + struct bgpevpn *vpn, afi_t afi, safi_t safi, + struct bgp_dest *dest, struct attr *attr, + struct bgp_path_info **ri, + int *route_changed); +int bgp_evpn_type1_route_process(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id); +int bgp_evpn_type4_route_process(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, uint8_t *pfx, int psize, + uint32_t addpath_id); +extern int bgp_evpn_local_es_add(struct bgp *bgp, esi_t *esi, + struct in_addr originator_ip, bool oper_up, + uint16_t df_pref, bool bypass); +extern int bgp_evpn_local_es_del(struct bgp *bgp, esi_t *esi); +extern int bgp_evpn_local_es_evi_add(struct bgp *bgp, esi_t *esi, vni_t vni); +extern int bgp_evpn_local_es_evi_del(struct bgp *bgp, esi_t *esi, vni_t vni); +extern enum zclient_send_status +bgp_evpn_remote_es_evi_add(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p); +extern enum zclient_send_status +bgp_evpn_remote_es_evi_del(struct bgp *bgp, struct bgpevpn *vpn, + const struct prefix_evpn *p); +extern void bgp_evpn_mh_init(void); +extern void bgp_evpn_mh_finish(void); +void bgp_evpn_vni_es_init(struct bgpevpn *vpn); +void bgp_evpn_vni_es_cleanup(struct bgpevpn *vpn); +void bgp_evpn_es_show_esi(struct vty *vty, esi_t *esi, bool uj); +void bgp_evpn_es_show(struct vty *vty, bool uj, bool detail); +void bgp_evpn_es_evi_show_vni(struct vty *vty, vni_t vni, + bool uj, bool detail); +void bgp_evpn_es_evi_show(struct vty *vty, bool uj, bool detail); +struct bgp_evpn_es *bgp_evpn_es_find(const esi_t *esi); +extern void bgp_evpn_vrf_es_init(struct bgp *bgp_vrf); +extern bool bgp_evpn_is_esi_local_and_non_bypass(esi_t *esi); +extern void bgp_evpn_es_vrf_deref(struct bgp_evpn_es_evi *es_evi); +extern void bgp_evpn_es_vrf_ref(struct bgp_evpn_es_evi *es_evi, + struct bgp *bgp_vrf); +extern void bgp_evpn_path_mh_info_free(struct bgp_path_mh_info *mh_info); +extern void bgp_evpn_path_es_link(struct bgp_path_info *pi, vni_t vni, + esi_t *esi); +extern bool bgp_evpn_path_es_use_nhg(struct bgp *bgp_vrf, + struct bgp_path_info *pi, uint32_t *nhg_p); +extern void bgp_evpn_es_vrf_show(struct vty *vty, bool uj, + struct bgp_evpn_es *es); +extern void bgp_evpn_es_vrf_show_esi(struct vty *vty, esi_t *esi, bool uj); +extern void bgp_evpn_switch_ead_evi_rx(void); +extern bool bgp_evpn_es_add_l3_ecomm_ok(esi_t *esi); +extern void bgp_evpn_es_vrf_use_nhg(struct bgp *bgp_vrf, esi_t *esi, + bool *use_l3nhg, bool *is_l3nhg_active, + struct bgp_evpn_es_vrf **es_vrf_p); +extern void bgp_evpn_nh_init(struct bgp *bgp_vrf); +extern void bgp_evpn_nh_finish(struct bgp *bgp_vrf); +extern void bgp_evpn_nh_show(struct vty *vty, bool uj); +extern void bgp_evpn_path_nh_add(struct bgp *bgp_vrf, struct bgp_path_info *pi); +extern void bgp_evpn_path_nh_del(struct bgp *bgp_vrf, struct bgp_path_info *pi); +extern void bgp_evpn_mh_config_ead_export_rt(struct bgp *bgp, + struct ecommunity *ecom, bool del); + +#endif /* _FRR_BGP_EVPN_MH_H */ diff --git a/bgpd/bgp_evpn_private.h b/bgpd/bgp_evpn_private.h new file mode 100644 index 0000000..07bba9b --- /dev/null +++ b/bgpd/bgp_evpn_private.h @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP EVPN internal definitions + * Copyright (C) 2017 Cumulus Networks, Inc. + */ + +#ifndef _BGP_EVPN_PRIVATE_H +#define _BGP_EVPN_PRIVATE_H + +#include "vxlan.h" +#include "zebra.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" + +#define RT_ADDRSTRLEN 28 + +/* EVPN prefix lengths. This represents the sizeof struct evpn_addr + * in bits */ +#define EVPN_ROUTE_PREFIXLEN (sizeof(struct evpn_addr) * 8) + +/* EVPN route RD buffer length */ +#define BGP_EVPN_PREFIX_RD_LEN 100 + +/* packet sizes for EVPN routes */ +/* Type-1 route should be 25 bytes + * RD (8), ESI (10), eth-tag (4), vni (3) + */ +#define BGP_EVPN_TYPE1_PSIZE 25 +/* Type-4 route should be either 23 or 35 bytes + * RD (8), ESI (10), ip-len (1), ip (4 or 16) + */ +#define BGP_EVPN_TYPE4_V4_PSIZE 23 +#define BGP_EVPN_TYPE4_V6_PSIZE 34 + +RB_HEAD(bgp_es_evi_rb_head, bgp_evpn_es_evi); +RB_PROTOTYPE(bgp_es_evi_rb_head, bgp_evpn_es_evi, rb_node, + bgp_es_evi_rb_cmp); +/* + * Hash table of EVIs. Right now, the only type of EVI supported is with + * VxLAN encapsulation, hence each EVI corresponds to a L2 VNI. + * The VNIs are not "created" through BGP but through some other interface + * on the system. This table stores VNIs that BGP comes to know as present + * on the system (through interaction with zebra) as well as pre-configured + * VNIs (which need to be defined in the system to become "live"). + */ +struct bgpevpn { + vni_t vni; + vrf_id_t tenant_vrf_id; + ifindex_t svi_ifindex; + uint32_t flags; +#define VNI_FLAG_CFGD 0x1 /* VNI is user configured */ +#define VNI_FLAG_LIVE 0x2 /* VNI is "live" */ +#define VNI_FLAG_RD_CFGD 0x4 /* RD is user configured. */ +#define VNI_FLAG_IMPRT_CFGD 0x8 /* Import RT is user configured */ +#define VNI_FLAG_EXPRT_CFGD 0x10 /* Export RT is user configured */ +#define VNI_FLAG_USE_TWO_LABELS 0x20 /* Attach both L2-VNI and L3-VNI if + needed for this VPN */ + + struct bgp *bgp_vrf; /* back pointer to the vrf instance */ + + /* Flag to indicate if we are + * advertising the g/w mac ip for + * this VNI*/ + uint8_t advertise_gw_macip; + + /* Flag to indicate if we are + * advertising subnet for this VNI */ + uint8_t advertise_subnet; + + /* Flag to indicate if we are advertising the svi mac ip for this VNI*/ + uint8_t advertise_svi_macip; + + /* Id for deriving the RD + * automatically for this VNI */ + uint16_t rd_id; + + /* RD for this VNI. */ + struct prefix_rd prd; + char *prd_pretty; + + /* Route type 3 field */ + struct in_addr originator_ip; + + /* PIM-SM MDT group for BUM flooding */ + struct in_addr mcast_grp; + + /* Import and Export RTs. */ + struct list *import_rtl; + struct list *export_rtl; + + /* + * EVPN route that uses gateway IP overlay index as its nexthop + * needs to do a recursive lookup. + * A remote MAC/IP entry should be present for the gateway IP. + * Maintain a hash of the addresses received via remote MAC/IP routes + * for efficient gateway IP recursive lookup in this EVI + */ + struct hash *remote_ip_hash; + + /* Route tables for EVPN routes for + * this VNI. */ + struct bgp_table *ip_table; + struct bgp_table *mac_table; + + /* RB tree of ES-EVIs */ + struct bgp_es_evi_rb_head es_evi_rb_tree; + + /* List of local ESs */ + struct list *local_es_evi_list; + + QOBJ_FIELDS; +}; + +DECLARE_QOBJ_TYPE(bgpevpn); + +/* Mapping of Import RT to VNIs. + * The Import RTs of all VNIs are maintained in a hash table with each + * RT linking to all VNIs that will import routes matching this RT. + */ +struct irt_node { + /* RT */ + struct ecommunity_val rt; + + /* List of VNIs importing routes matching this RT. */ + struct list *vnis; +}; + +/* Mapping of Import RT to VRFs. + * The Import RTs of all VRFss are maintained in a hash table with each + * RT linking to all VRFs that will import routes matching this RT. + */ +struct vrf_irt_node { + /* RT */ + struct ecommunity_val rt; + + /* List of VNIs importing routes matching this RT. */ + struct list *vrfs; +}; + + +#define RT_TYPE_IMPORT 1 +#define RT_TYPE_EXPORT 2 +#define RT_TYPE_BOTH 3 + +#define EVPN_DAD_DEFAULT_TIME 180 /* secs */ +#define EVPN_DAD_DEFAULT_MAX_MOVES 5 /* default from RFC 7432 */ +#define EVPN_DAD_DEFAULT_AUTO_RECOVERY_TIME 1800 /* secs */ + +struct bgp_evpn_info { + /* enable disable dup detect */ + bool dup_addr_detect; + + /* Detection time(M) */ + int dad_time; + /* Detection max moves(N) */ + uint32_t dad_max_moves; + /* Permanent freeze */ + bool dad_freeze; + /* Recovery time */ + uint32_t dad_freeze_time; + + /* EVPN enable - advertise svi macip routes */ + int advertise_svi_macip; + + /* MAC-VRF Site-of-Origin + * - added to all routes exported from L2VNI + * - Type-2/3 routes with matching SoO not imported into L2VNI + * - Type-2/5 routes with matching SoO not imported into L3VNI + */ + struct ecommunity *soo; + + /* PIP feature knob */ + bool advertise_pip; + /* PIP IP (sys ip) */ + struct in_addr pip_ip; + struct in_addr pip_ip_static; + /* PIP MAC (sys MAC) */ + struct ethaddr pip_rmac; + struct ethaddr pip_rmac_static; + struct ethaddr pip_rmac_zebra; + bool is_anycast_mac; +}; + +/* This structure defines an entry in remote_ip_hash */ +struct evpn_remote_ip { + struct ipaddr addr; + struct list *macip_path_list; +}; + +/* + * Wrapper struct for l3 RT's + */ +struct vrf_route_target { + /* flags based on config to determine how RTs are handled */ + uint8_t flags; +#define BGP_VRF_RT_AUTO (1 << 0) +#define BGP_VRF_RT_WILD (1 << 1) + + struct ecommunity *ecom; +}; + +static inline int is_vrf_rd_configured(struct bgp *bgp_vrf) +{ + return (CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD)); +} + +static inline int bgp_evpn_vrf_rd_matches_existing(struct bgp *bgp_vrf, + struct prefix_rd *prd) +{ + return (memcmp(&bgp_vrf->vrf_prd.val, prd->val, ECOMMUNITY_SIZE) == 0); +} + +static inline vni_t bgpevpn_get_l3vni(struct bgpevpn *vpn) +{ + return vpn->bgp_vrf ? vpn->bgp_vrf->l3vni : 0; +} + +static inline void bgpevpn_get_rmac(struct bgpevpn *vpn, struct ethaddr *rmac) +{ + memset(rmac, 0, sizeof(struct ethaddr)); + if (!vpn->bgp_vrf) + return; + memcpy(rmac, &vpn->bgp_vrf->rmac, sizeof(struct ethaddr)); +} + +static inline struct list *bgpevpn_get_vrf_export_rtl(struct bgpevpn *vpn) +{ + if (!vpn->bgp_vrf) + return NULL; + + return vpn->bgp_vrf->vrf_export_rtl; +} + +static inline struct list *bgpevpn_get_vrf_import_rtl(struct bgpevpn *vpn) +{ + if (!vpn->bgp_vrf) + return NULL; + + return vpn->bgp_vrf->vrf_import_rtl; +} + +extern void bgp_evpn_es_evi_vrf_ref(struct bgpevpn *vpn); +extern void bgp_evpn_es_evi_vrf_deref(struct bgpevpn *vpn); + +static inline void bgpevpn_unlink_from_l3vni(struct bgpevpn *vpn) +{ + /* bail if vpn is not associated to bgp_vrf */ + if (!vpn->bgp_vrf) + return; + + UNSET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); + listnode_delete(vpn->bgp_vrf->l2vnis, vpn); + + bgp_evpn_es_evi_vrf_deref(vpn); + + /* remove the backpointer to the vrf instance */ + bgp_unlock(vpn->bgp_vrf); + vpn->bgp_vrf = NULL; +} + +static inline void bgpevpn_link_to_l3vni(struct bgpevpn *vpn) +{ + struct bgp *bgp_vrf = NULL; + + /* bail if vpn is already associated to vrf */ + if (vpn->bgp_vrf) + return; + + bgp_vrf = bgp_lookup_by_vrf_id(vpn->tenant_vrf_id); + if (!bgp_vrf) + return; + + /* associate the vpn to the bgp_vrf instance */ + vpn->bgp_vrf = bgp_lock(bgp_vrf); + listnode_add_sort(bgp_vrf->l2vnis, vpn); + + /* + * If L3VNI is configured, + * check if we are advertising two labels for this vpn + */ + if (bgp_vrf->l3vni && + !CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY)) + SET_FLAG(vpn->flags, VNI_FLAG_USE_TWO_LABELS); + + bgp_evpn_es_evi_vrf_ref(vpn); +} + +static inline int is_vni_configured(struct bgpevpn *vpn) +{ + return (CHECK_FLAG(vpn->flags, VNI_FLAG_CFGD)); +} + +static inline int is_vni_live(struct bgpevpn *vpn) +{ + return (CHECK_FLAG(vpn->flags, VNI_FLAG_LIVE)); +} + +static inline int is_l3vni_live(struct bgp *bgp_vrf) +{ + return (bgp_vrf->l3vni && bgp_vrf->l3vni_svi_ifindex); +} + +static inline int is_rd_configured(struct bgpevpn *vpn) +{ + return (CHECK_FLAG(vpn->flags, VNI_FLAG_RD_CFGD)); +} + +static inline int bgp_evpn_rd_matches_existing(struct bgpevpn *vpn, + struct prefix_rd *prd) +{ + return (memcmp(&vpn->prd.val, prd->val, ECOMMUNITY_SIZE) == 0); +} + +static inline int is_import_rt_configured(struct bgpevpn *vpn) +{ + return (CHECK_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD)); +} + +static inline int is_export_rt_configured(struct bgpevpn *vpn) +{ + return (CHECK_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD)); +} + +static inline void encode_es_rt_extcomm(struct ecommunity_val *eval, + struct ethaddr *mac) +{ + memset(eval, 0, sizeof(struct ecommunity_val)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ES_IMPORT_RT; + memcpy(&eval->val[2], mac, ETH_ALEN); +} + +static inline void encode_df_elect_extcomm(struct ecommunity_val *eval, + uint16_t pref) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_DF_ELECTION; + eval->val[2] = EVPN_MH_DF_ALG_PREF; + eval->val[6] = (pref >> 8) & 0xff; + eval->val[7] = pref & 0xff; +} + +static inline void encode_esi_label_extcomm(struct ecommunity_val *eval, + bool single_active) +{ + memset(eval, 0, sizeof(struct ecommunity_val)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ESI_LABEL; + if (single_active) + eval->val[2] |= (1 << 0); +} + +static inline void encode_rmac_extcomm(struct ecommunity_val *eval, + struct ethaddr *rmac) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC; + memcpy(&eval->val[2], rmac, ETH_ALEN); +} + +static inline void encode_default_gw_extcomm(struct ecommunity_val *eval) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_OPAQUE; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_DEF_GW; +} + +static inline void encode_mac_mobility_extcomm(int static_mac, uint32_t seq, + struct ecommunity_val *eval) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY; + if (static_mac) + eval->val[2] = ECOMMUNITY_EVPN_SUBTYPE_MACMOBILITY_FLAG_STICKY; + eval->val[4] = (seq >> 24) & 0xff; + eval->val[5] = (seq >> 16) & 0xff; + eval->val[6] = (seq >> 8) & 0xff; + eval->val[7] = seq & 0xff; +} + +static inline void encode_na_flag_extcomm(struct ecommunity_val *eval, + uint8_t na_flag, bool proxy) +{ + memset(eval, 0, sizeof(*eval)); + eval->val[0] = ECOMMUNITY_ENCODE_EVPN; + eval->val[1] = ECOMMUNITY_EVPN_SUBTYPE_ND; + if (na_flag) + eval->val[2] |= ECOMMUNITY_EVPN_SUBTYPE_ND_ROUTER_FLAG; + if (proxy) + eval->val[2] |= ECOMMUNITY_EVPN_SUBTYPE_PROXY_FLAG; +} + +static inline void ip_prefix_from_type5_prefix(const struct prefix_evpn *evp, + struct prefix *ip) +{ + memset(ip, 0, sizeof(struct prefix)); + if (is_evpn_prefix_ipaddr_v4(evp)) { + ip->family = AF_INET; + ip->prefixlen = evp->prefix.prefix_addr.ip_prefix_length; + memcpy(&(ip->u.prefix4), &(evp->prefix.prefix_addr.ip.ip), + IPV4_MAX_BYTELEN); + } else if (is_evpn_prefix_ipaddr_v6(evp)) { + ip->family = AF_INET6; + ip->prefixlen = evp->prefix.prefix_addr.ip_prefix_length; + memcpy(&(ip->u.prefix6), &(evp->prefix.prefix_addr.ip.ip), + IPV6_MAX_BYTELEN); + } +} + +static inline int is_evpn_prefix_default(const struct prefix *evp) +{ + if (evp->family != AF_EVPN) + return 0; + + return ((evp->u.prefix_evpn.prefix_addr.ip_prefix_length == 0) ? + 1 : 0); +} + +static inline void ip_prefix_from_type2_prefix(const struct prefix_evpn *evp, + struct prefix *ip) +{ + memset(ip, 0, sizeof(struct prefix)); + if (is_evpn_prefix_ipaddr_v4(evp)) { + ip->family = AF_INET; + ip->prefixlen = IPV4_MAX_BITLEN; + memcpy(&(ip->u.prefix4), &(evp->prefix.macip_addr.ip.ip), + IPV4_MAX_BYTELEN); + } else if (is_evpn_prefix_ipaddr_v6(evp)) { + ip->family = AF_INET6; + ip->prefixlen = IPV6_MAX_BITLEN; + memcpy(&(ip->u.prefix6), &(evp->prefix.macip_addr.ip.ip), + IPV6_MAX_BYTELEN); + } +} + +static inline void ip_prefix_from_evpn_prefix(const struct prefix_evpn *evp, + struct prefix *ip) +{ + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + ip_prefix_from_type2_prefix(evp, ip); + else if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) + ip_prefix_from_type5_prefix(evp, ip); +} + +static inline void build_evpn_type2_prefix(struct prefix_evpn *p, + struct ethaddr *mac, + struct ipaddr *ip) +{ + memset(p, 0, sizeof(struct prefix_evpn)); + p->family = AF_EVPN; + p->prefixlen = EVPN_ROUTE_PREFIXLEN; + p->prefix.route_type = BGP_EVPN_MAC_IP_ROUTE; + memcpy(&p->prefix.macip_addr.mac.octet, mac->octet, ETH_ALEN); + p->prefix.macip_addr.ip.ipa_type = IPADDR_NONE; + memcpy(&p->prefix.macip_addr.ip, ip, sizeof(*ip)); +} + +static inline void +build_type5_prefix_from_ip_prefix(struct prefix_evpn *evp, + const struct prefix *ip_prefix) +{ + struct ipaddr ip; + + memset(&ip, 0, sizeof(struct ipaddr)); + if (ip_prefix->family == AF_INET) { + ip.ipa_type = IPADDR_V4; + memcpy(&ip.ipaddr_v4, &ip_prefix->u.prefix4, + sizeof(struct in_addr)); + } else { + ip.ipa_type = IPADDR_V6; + memcpy(&ip.ipaddr_v6, &ip_prefix->u.prefix6, + sizeof(struct in6_addr)); + } + + memset(evp, 0, sizeof(struct prefix_evpn)); + evp->family = AF_EVPN; + evp->prefixlen = EVPN_ROUTE_PREFIXLEN; + evp->prefix.route_type = BGP_EVPN_IP_PREFIX_ROUTE; + evp->prefix.prefix_addr.ip_prefix_length = ip_prefix->prefixlen; + evp->prefix.prefix_addr.ip.ipa_type = ip.ipa_type; + memcpy(&evp->prefix.prefix_addr.ip, &ip, sizeof(struct ipaddr)); +} + +static inline void build_evpn_type3_prefix(struct prefix_evpn *p, + struct in_addr originator_ip) +{ + memset(p, 0, sizeof(struct prefix_evpn)); + p->family = AF_EVPN; + p->prefixlen = EVPN_ROUTE_PREFIXLEN; + p->prefix.route_type = BGP_EVPN_IMET_ROUTE; + p->prefix.imet_addr.ip.ipa_type = IPADDR_V4; + p->prefix.imet_addr.ip.ipaddr_v4 = originator_ip; +} + +static inline void build_evpn_type4_prefix(struct prefix_evpn *p, + esi_t *esi, + struct in_addr originator_ip) +{ + memset(p, 0, sizeof(struct prefix_evpn)); + p->family = AF_EVPN; + p->prefixlen = EVPN_ROUTE_PREFIXLEN; + p->prefix.route_type = BGP_EVPN_ES_ROUTE; + p->prefix.es_addr.ip_prefix_length = IPV4_MAX_BITLEN; + p->prefix.es_addr.ip.ipa_type = IPADDR_V4; + p->prefix.es_addr.ip.ipaddr_v4 = originator_ip; + memcpy(&p->prefix.es_addr.esi, esi, sizeof(esi_t)); +} + +static inline void build_evpn_type1_prefix(struct prefix_evpn *p, + uint32_t eth_tag, + esi_t *esi, + struct in_addr originator_ip) +{ + memset(p, 0, sizeof(struct prefix_evpn)); + p->family = AF_EVPN; + p->prefixlen = EVPN_ROUTE_PREFIXLEN; + p->prefix.route_type = BGP_EVPN_AD_ROUTE; + p->prefix.ead_addr.eth_tag = eth_tag; + p->prefix.ead_addr.ip.ipa_type = IPADDR_V4; + p->prefix.ead_addr.ip.ipaddr_v4 = originator_ip; + memcpy(&p->prefix.ead_addr.esi, esi, sizeof(esi_t)); +} + +static inline void evpn_type1_prefix_global_copy(struct prefix_evpn *global_p, + const struct prefix_evpn *vni_p) +{ + memcpy(global_p, vni_p, sizeof(*global_p)); + global_p->prefix.ead_addr.ip.ipa_type = 0; + global_p->prefix.ead_addr.ip.ipaddr_v4.s_addr = INADDR_ANY; + global_p->prefix.ead_addr.frag_id = 0; +} + +/* EAD prefix in the global table doesn't include the VTEP-IP so + * we need to create a different copy for the VNI + */ +static inline struct prefix_evpn * +evpn_type1_prefix_vni_ip_copy(struct prefix_evpn *vni_p, + const struct prefix_evpn *global_p, + struct in_addr originator_ip) +{ + memcpy(vni_p, global_p, sizeof(*vni_p)); + vni_p->prefix.ead_addr.ip.ipa_type = IPADDR_V4; + vni_p->prefix.ead_addr.ip.ipaddr_v4 = originator_ip; + + return vni_p; +} + +static inline void evpn_type2_prefix_global_copy( + struct prefix_evpn *global_p, const struct prefix_evpn *vni_p, + const struct ethaddr *mac, const struct ipaddr *ip) +{ + memcpy(global_p, vni_p, sizeof(*global_p)); + + if (mac) + global_p->prefix.macip_addr.mac = *mac; + + if (ip) + global_p->prefix.macip_addr.ip = *ip; +} + +static inline void +evpn_type2_prefix_vni_ip_copy(struct prefix_evpn *vni_p, + const struct prefix_evpn *global_p) +{ + memcpy(vni_p, global_p, sizeof(*vni_p)); + memset(&vni_p->prefix.macip_addr.mac, 0, sizeof(struct ethaddr)); +} + +static inline void +evpn_type2_prefix_vni_mac_copy(struct prefix_evpn *vni_p, + const struct prefix_evpn *global_p) +{ + memcpy(vni_p, global_p, sizeof(*vni_p)); + memset(&vni_p->prefix.macip_addr.ip, 0, sizeof(struct ipaddr)); +} + +/* Get MAC of path_info prefix */ +static inline struct ethaddr * +evpn_type2_path_info_get_mac(const struct bgp_path_info *local_pi) +{ + assert(local_pi->extra && local_pi->extra->evpn); + return &local_pi->extra->evpn->vni_info.mac; +} + +/* Get IP of path_info prefix */ +static inline struct ipaddr * +evpn_type2_path_info_get_ip(const struct bgp_path_info *local_pi) +{ + assert(local_pi->extra && local_pi->extra->evpn); + return &local_pi->extra->evpn->vni_info.ip; +} + +/* Set MAC of path_info prefix */ +static inline void evpn_type2_path_info_set_mac(struct bgp_path_info *local_pi, + const struct ethaddr mac) +{ + assert(local_pi->extra && local_pi->extra->evpn); + local_pi->extra->evpn->vni_info.mac = mac; +} + +/* Set IP of path_info prefix */ +static inline void evpn_type2_path_info_set_ip(struct bgp_path_info *local_pi, + const struct ipaddr ip) +{ + assert(local_pi->extra && local_pi->extra->evpn); + local_pi->extra->evpn->vni_info.ip = ip; +} + +/* Is the IP empty for the RT's dest? */ +static inline bool is_evpn_type2_dest_ipaddr_none(const struct bgp_dest *dest) +{ + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + assert(evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE); + return is_evpn_prefix_ipaddr_none(evp); +} + +static inline int evpn_default_originate_set(struct bgp *bgp, afi_t afi, + safi_t safi) +{ + if (afi == AFI_IP && + CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4)) + return 1; + else if (afi == AFI_IP6 && + CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6)) + return 1; + return 0; +} + +static inline void es_get_system_mac(esi_t *esi, + struct ethaddr *mac) +{ + /* + * for type-1 and type-3 ESIs, + * the system mac starts at val[1] + */ + memcpy(mac, &esi->val[1], ETH_ALEN); +} + +static inline bool bgp_evpn_is_svi_macip_enabled(struct bgpevpn *vpn) +{ + struct bgp *bgp_evpn = NULL; + + bgp_evpn = bgp_get_evpn(); + + return (bgp_evpn->evpn_info->advertise_svi_macip || + vpn->advertise_svi_macip); +} + +static inline bool bgp_evpn_is_path_local(struct bgp *bgp, + struct bgp_path_info *pi) +{ + return (pi->peer == bgp->peer_self + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC); +} + +extern struct zclient *zclient; + +extern void bgp_evpn_install_uninstall_default_route(struct bgp *bgp_vrf, + afi_t afi, safi_t safi, + bool add); +extern void evpn_rt_delete_auto(struct bgp *bgp, vni_t vni, struct list *rtl, + bool is_l3); +extern void bgp_evpn_configure_export_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomadd); +extern void bgp_evpn_configure_export_auto_rt_for_vrf(struct bgp *bgp_vrf); +extern void bgp_evpn_unconfigure_export_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomdel); +extern void bgp_evpn_unconfigure_export_auto_rt_for_vrf(struct bgp *bgp_vrf); +extern void bgp_evpn_configure_import_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomadd, + bool is_wildcard); +extern void bgp_evpn_configure_import_auto_rt_for_vrf(struct bgp *bgp_vrf); +extern void bgp_evpn_unconfigure_import_rt_for_vrf(struct bgp *bgp_vrf, + struct ecommunity *ecomdel); +extern void bgp_evpn_unconfigure_import_auto_rt_for_vrf(struct bgp *bgp_vrf); +extern int bgp_evpn_handle_export_rt_change(struct bgp *bgp, + struct bgpevpn *vpn); +extern void bgp_evpn_handle_autort_change(struct bgp *bgp); +extern void bgp_evpn_handle_vrf_rd_change(struct bgp *bgp_vrf, int withdraw); +extern void bgp_evpn_handle_rd_change(struct bgp *bgp, struct bgpevpn *vpn, + int withdraw); +void bgp_evpn_handle_global_macvrf_soo_change(struct bgp *bgp, + struct ecommunity *new_soo); +extern int bgp_evpn_install_routes(struct bgp *bgp, struct bgpevpn *vpn); +extern int bgp_evpn_uninstall_routes(struct bgp *bgp, struct bgpevpn *vpn); +extern void bgp_evpn_map_vrf_to_its_rts(struct bgp *bgp_vrf); +extern void bgp_evpn_unmap_vrf_from_its_rts(struct bgp *bgp_vrf); +extern void bgp_evpn_map_vni_to_its_rts(struct bgp *bgp, struct bgpevpn *vpn); +extern void bgp_evpn_unmap_vni_from_its_rts(struct bgp *bgp, + struct bgpevpn *vpn); +extern void bgp_evpn_derive_auto_rt_import(struct bgp *bgp, + struct bgpevpn *vpn); +extern void bgp_evpn_derive_auto_rt_export(struct bgp *bgp, + struct bgpevpn *vpn); +extern void bgp_evpn_derive_auto_rd(struct bgp *bgp, struct bgpevpn *vpn); +extern void bgp_evpn_derive_auto_rd_for_vrf(struct bgp *bgp); +extern struct bgpevpn *bgp_evpn_lookup_vni(struct bgp *bgp, vni_t vni); +extern struct bgpevpn *bgp_evpn_new(struct bgp *bgp, vni_t vni, + struct in_addr originator_ip, + vrf_id_t tenant_vrf_id, + struct in_addr mcast_grp, + ifindex_t svi_ifindex); +extern void bgp_evpn_free(struct bgp *bgp, struct bgpevpn *vpn); +extern bool bgp_evpn_lookup_l3vni_l2vni_table(vni_t vni); +extern int update_routes_for_vni(struct bgp *bgp, struct bgpevpn *vpn); +extern void delete_evpn_route_entry(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *dest, + struct bgp_path_info **pi); +int vni_list_cmp(void *p1, void *p2); +extern int evpn_route_select_install(struct bgp *bgp, struct bgpevpn *vpn, + struct bgp_dest *dest, + struct bgp_path_info *pi); +extern struct bgp_dest * +bgp_evpn_global_node_get(struct bgp_table *table, afi_t afi, safi_t safi, + const struct prefix_evpn *evp, struct prefix_rd *prd, + const struct bgp_path_info *local_pi); +extern struct bgp_dest *bgp_evpn_global_node_lookup( + struct bgp_table *table, safi_t safi, const struct prefix_evpn *evp, + struct prefix_rd *prd, const struct bgp_path_info *local_pi); +extern struct bgp_dest * +bgp_evpn_vni_ip_node_get(struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi); +extern struct bgp_dest * +bgp_evpn_vni_ip_node_lookup(const struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi); +extern struct bgp_dest * +bgp_evpn_vni_mac_node_get(struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi); +extern struct bgp_dest * +bgp_evpn_vni_mac_node_lookup(const struct bgp_table *const table, + const struct prefix_evpn *evp, + const struct bgp_path_info *parent_pi); +extern struct bgp_dest * +bgp_evpn_vni_node_get(struct bgpevpn *vpn, const struct prefix_evpn *p, + const struct bgp_path_info *parent_pi); +extern struct bgp_dest * +bgp_evpn_vni_node_lookup(const struct bgpevpn *vpn, const struct prefix_evpn *p, + const struct bgp_path_info *parent_pi); + +extern void bgp_evpn_import_route_in_vrfs(struct bgp_path_info *pi, int import); +extern void bgp_evpn_update_type2_route_entry(struct bgp *bgp, + struct bgpevpn *vpn, + struct bgp_dest *rn, + struct bgp_path_info *local_pi, + const char *caller); +extern int bgp_evpn_route_entry_install_if_vrf_match(struct bgp *bgp_vrf, + struct bgp_path_info *pi, + int install); +extern void bgp_evpn_import_type2_route(struct bgp_path_info *pi, int import); +extern void bgp_evpn_xxport_delete_ecomm(void *val); +extern int bgp_evpn_route_target_cmp(struct ecommunity *ecom1, + struct ecommunity *ecom2); +#endif /* _BGP_EVPN_PRIVATE_H */ diff --git a/bgpd/bgp_evpn_vty.c b/bgpd/bgp_evpn_vty.c new file mode 100644 index 0000000..846a82b --- /dev/null +++ b/bgpd/bgp_evpn_vty.c @@ -0,0 +1,7632 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Ethernet-VPN Packet and vty Processing File + * Copyright (C) 2017 6WIND + * + * This file is part of FRRouting + */ + +#include +#include "command.h" +#include "prefix.h" +#include "lib/json.h" +#include "lib/printfrr.h" +#include "lib/vxlan.h" +#include "stream.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_vpn.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_community.h" + +#define SHOW_DISPLAY_STANDARD 0 +#define SHOW_DISPLAY_TAGS 1 +#define SHOW_DISPLAY_OVERLAY 2 +#define VNI_STR_LEN 32 + +/* + * Context for VNI hash walk - used by callbacks. + */ +struct vni_walk_ctx { + struct bgp *bgp; + struct vty *vty; + struct in_addr vtep_ip; + json_object *json; + int detail; + int type; + bool mac_table; +}; + +int argv_find_and_parse_oly_idx(struct cmd_token **argv, int argc, int *oly_idx, + enum overlay_index_type *oly) +{ + *oly = OVERLAY_INDEX_TYPE_NONE; + if (argv_find(argv, argc, "gateway-ip", oly_idx)) + *oly = OVERLAY_INDEX_GATEWAY_IP; + return 1; +} + +static void display_vrf_import_rt(struct vty *vty, struct vrf_irt_node *irt, + json_object *json) +{ + const uint8_t *pnt; + uint8_t type, sub_type; + struct ecommunity_as eas; + struct ecommunity_ip eip; + struct listnode *node, *nnode; + struct bgp *tmp_bgp_vrf = NULL; + json_object *json_rt = NULL; + json_object *json_vrfs = NULL; + char rt_buf[RT_ADDRSTRLEN]; + + if (json) { + json_rt = json_object_new_object(); + json_vrfs = json_object_new_array(); + } + + pnt = (uint8_t *)&irt->rt.val; + type = *pnt++; + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET) + return; + + memset(&eas, 0, sizeof(eas)); + switch (type) { + case ECOMMUNITY_ENCODE_AS: + eas.as = (*pnt++ << 8); + eas.as |= (*pnt++); + ptr_get_be32(pnt, &eas.val); + + snprintf(rt_buf, sizeof(rt_buf), "%u:%u", eas.as, eas.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + case ECOMMUNITY_ENCODE_IP: + memcpy(&eip.ip, pnt, 4); + pnt += 4; + eip.val = (*pnt++ << 8); + eip.val |= (*pnt++); + + snprintfrr(rt_buf, sizeof(rt_buf), "%pI4:%u", &eip.ip, eip.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + case ECOMMUNITY_ENCODE_AS4: + pnt = ptr_get_be32(pnt, &eas.val); + eas.val = (*pnt++ << 8); + eas.val |= (*pnt++); + + snprintf(rt_buf, sizeof(rt_buf), "%u:%u", eas.as, eas.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + default: + return; + } + + if (!json) { + vty_out(vty, + "\nList of VRFs importing routes with this route-target:\n"); + } + + for (ALL_LIST_ELEMENTS(irt->vrfs, node, nnode, tmp_bgp_vrf)) { + if (json) + json_object_array_add( + json_vrfs, + json_object_new_string( + vrf_id_to_name(tmp_bgp_vrf->vrf_id))); + else + vty_out(vty, " %s\n", + vrf_id_to_name(tmp_bgp_vrf->vrf_id)); + } + + if (json) { + json_object_object_add(json_rt, "vrfs", json_vrfs); + json_object_object_add(json, rt_buf, json_rt); + } +} + +static void show_vrf_import_rt_entry(struct hash_bucket *bucket, void *args[]) +{ + json_object *json = NULL; + struct vty *vty = NULL; + struct vrf_irt_node *irt = (struct vrf_irt_node *)bucket->data; + + vty = (struct vty *)args[0]; + json = (struct json_object *)args[1]; + + display_vrf_import_rt(vty, irt, json); +} + +static void display_import_rt(struct vty *vty, struct irt_node *irt, + json_object *json) +{ + const uint8_t *pnt; + uint8_t type, sub_type; + struct ecommunity_as eas; + struct ecommunity_ip eip; + struct listnode *node, *nnode; + struct bgpevpn *tmp_vpn; + json_object *json_rt = NULL; + json_object *json_vnis = NULL; + char rt_buf[RT_ADDRSTRLEN]; + + if (json) { + json_rt = json_object_new_object(); + json_vnis = json_object_new_array(); + } + + /* TODO: This needs to go into a function */ + + pnt = (uint8_t *)&irt->rt.val; + type = *pnt++; + sub_type = *pnt++; + if (sub_type != ECOMMUNITY_ROUTE_TARGET) + return; + + memset(&eas, 0, sizeof(eas)); + switch (type) { + case ECOMMUNITY_ENCODE_AS: + eas.as = (*pnt++ << 8); + eas.as |= (*pnt++); + ptr_get_be32(pnt, &eas.val); + + snprintf(rt_buf, sizeof(rt_buf), "%u:%u", eas.as, eas.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + case ECOMMUNITY_ENCODE_IP: + memcpy(&eip.ip, pnt, 4); + pnt += 4; + eip.val = (*pnt++ << 8); + eip.val |= (*pnt++); + + snprintfrr(rt_buf, sizeof(rt_buf), "%pI4:%u", &eip.ip, eip.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + case ECOMMUNITY_ENCODE_AS4: + pnt = ptr_get_be32(pnt, &eas.val); + eas.val = (*pnt++ << 8); + eas.val |= (*pnt++); + + snprintf(rt_buf, sizeof(rt_buf), "%u:%u", eas.as, eas.val); + + if (json) + json_object_string_add(json_rt, "rt", rt_buf); + else + vty_out(vty, "Route-target: %s", rt_buf); + + break; + + default: + return; + } + + if (!json) { + vty_out(vty, + "\nList of VNIs importing routes with this route-target:\n"); + } + + for (ALL_LIST_ELEMENTS(irt->vnis, node, nnode, tmp_vpn)) { + if (json) + json_object_array_add( + json_vnis, json_object_new_int(tmp_vpn->vni)); + else + vty_out(vty, " %u\n", tmp_vpn->vni); + } + + if (json) { + json_object_object_add(json_rt, "vnis", json_vnis); + json_object_object_add(json, rt_buf, json_rt); + } +} + +static void show_import_rt_entry(struct hash_bucket *bucket, void *args[]) +{ + json_object *json = NULL; + struct vty *vty = NULL; + struct irt_node *irt = (struct irt_node *)bucket->data; + + vty = args[0]; + json = args[1]; + + display_import_rt(vty, irt, json); + + return; +} + +static void bgp_evpn_show_route_rd_header(struct vty *vty, + struct bgp_dest *rd_dest, + json_object *json, char *rd_str, + int len) +{ + uint16_t type; + struct rd_as rd_as; + struct rd_ip rd_ip; + const uint8_t *pnt; + const struct prefix *p = bgp_dest_get_prefix(rd_dest); + + pnt = p->u.val; + + /* Decode RD type. */ + type = decode_rd_type(pnt); + + if (!json) + vty_out(vty, "Route Distinguisher: "); + + switch (type) { + case RD_TYPE_AS: + decode_rd_as(pnt + 2, &rd_as); + snprintf(rd_str, len, "%u:%d", rd_as.as, rd_as.val); + if (json) + json_object_string_add(json, "rd", rd_str); + else + vty_out(vty, "%s\n", rd_str); + break; + + case RD_TYPE_AS4: + decode_rd_as4(pnt + 2, &rd_as); + snprintf(rd_str, len, "%u:%d", rd_as.as, rd_as.val); + if (json) + json_object_string_add(json, "rd", rd_str); + else + vty_out(vty, "%s\n", rd_str); + break; + + case RD_TYPE_IP: + decode_rd_ip(pnt + 2, &rd_ip); + snprintfrr(rd_str, len, "%pI4:%d", &rd_ip.ip, rd_ip.val); + if (json) + json_object_string_add(json, "rd", rd_str); + else + vty_out(vty, "%s\n", rd_str); + break; + + default: + if (json) { + snprintf(rd_str, len, "Unknown"); + json_object_string_add(json, "rd", rd_str); + } else { + snprintf(rd_str, len, "Unknown RD type"); + vty_out(vty, "%s\n", rd_str); + } + break; + } +} + +static void bgp_evpn_show_route_header(struct vty *vty, struct bgp *bgp, + uint64_t tbl_ver, json_object *json) +{ + char ri_header[] = + " Network Next Hop Metric LocPrf Weight Path\n"; + + if (json) + return; + + vty_out(vty, + "BGP table version is %" PRIu64 ", local router ID is %pI4\n", + tbl_ver, &bgp->router_id); + vty_out(vty, + "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal\n"); + vty_out(vty, "Origin codes: i - IGP, e - EGP, ? - incomplete\n"); + vty_out(vty, + "EVPN type-1 prefix: [1]:[EthTag]:[ESI]:[IPlen]:[VTEP-IP]:[Frag-id]\n"); + vty_out(vty, + "EVPN type-2 prefix: [2]:[EthTag]:[MAClen]:[MAC]:[IPlen]:[IP]\n"); + vty_out(vty, "EVPN type-3 prefix: [3]:[EthTag]:[IPlen]:[OrigIP]\n"); + vty_out(vty, "EVPN type-4 prefix: [4]:[ESI]:[IPlen]:[OrigIP]\n"); + vty_out(vty, "EVPN type-5 prefix: [5]:[EthTag]:[IPlen]:[IP]\n\n"); + vty_out(vty, "%s", ri_header); +} + +static void display_l3vni(struct vty *vty, struct bgp *bgp_vrf, + json_object *json) +{ + char *ecom_str; + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + struct bgp *bgp_evpn = NULL; + json_object *json_import_rtl = NULL; + json_object *json_export_rtl = NULL; + + bgp_evpn = bgp_get_evpn(); + json_import_rtl = json_export_rtl = 0; + + if (json) { + json_import_rtl = json_object_new_array(); + json_export_rtl = json_object_new_array(); + json_object_int_add(json, "vni", bgp_vrf->l3vni); + json_object_string_add(json, "type", "L3"); + json_object_string_add(json, "inKernel", "True"); + json_object_string_addf(json, "rd", + BGP_RD_AS_FORMAT(bgp_vrf->asnotation), + &bgp_vrf->vrf_prd); + json_object_string_addf(json, "originatorIp", "%pI4", + &bgp_vrf->originator_ip); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "siteOfOrigin", ecom_str); + ecommunity_strfree(&ecom_str); + } + json_object_string_add(json, "advertiseGatewayMacip", "n/a"); + json_object_string_add(json, "advertiseSviMacIp", "n/a"); + if (bgp_vrf->evpn_info) { + json_object_string_add(json, "advertisePip", + bgp_vrf->evpn_info->advertise_pip + ? "Enabled" + : "Disabled"); + json_object_string_addf(json, "sysIP", "%pI4", + &bgp_vrf->evpn_info->pip_ip); + json_object_string_addf(json, "sysMac", "%pEA", + &bgp_vrf->evpn_info->pip_rmac); + } + json_object_string_addf(json, "rmac", "%pEA", &bgp_vrf->rmac); + } else { + vty_out(vty, "VNI: %d", bgp_vrf->l3vni); + vty_out(vty, " (known to the kernel)"); + vty_out(vty, "\n"); + + vty_out(vty, " Type: %s\n", "L3"); + vty_out(vty, " Tenant VRF: %s\n", + vrf_id_to_name(bgp_vrf->vrf_id)); + vty_out(vty, " RD: "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp_vrf->asnotation), + &bgp_vrf->vrf_prd); + vty_out(vty, "\n"); + vty_out(vty, " Originator IP: %pI4\n", + &bgp_vrf->originator_ip); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " MAC-VRF Site-of-Origin: %s\n", + ecom_str); + ecommunity_strfree(&ecom_str); + } + vty_out(vty, " Advertise-gw-macip : %s\n", "n/a"); + vty_out(vty, " Advertise-svi-macip : %s\n", "n/a"); + if (bgp_vrf->evpn_info) { + vty_out(vty, " Advertise-pip: %s\n", + bgp_vrf->evpn_info->advertise_pip ? "Yes" + : "No"); + vty_out(vty, " System-IP: %pI4\n", + &bgp_vrf->evpn_info->pip_ip); + vty_out(vty, " System-MAC: %pEA\n", + &bgp_vrf->evpn_info->pip_rmac); + } + vty_out(vty, " Router-MAC: %pEA\n", &bgp_vrf->rmac); + } + + if (!json) + vty_out(vty, " Import Route Target:\n"); + + for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_import_rtl, node, nnode, l3rt)) { + ecom_str = ecommunity_ecom2str(l3rt->ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) + json_object_array_add(json_import_rtl, + json_object_new_string(ecom_str)); + else + vty_out(vty, " %s\n", ecom_str); + + ecommunity_strfree(&ecom_str); + } + + if (json) + json_object_object_add(json, "importRts", json_import_rtl); + else + vty_out(vty, " Export Route Target:\n"); + + for (ALL_LIST_ELEMENTS(bgp_vrf->vrf_export_rtl, node, nnode, l3rt)) { + ecom_str = ecommunity_ecom2str(l3rt->ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) + json_object_array_add(json_export_rtl, + json_object_new_string(ecom_str)); + else + vty_out(vty, " %s\n", ecom_str); + + ecommunity_strfree(&ecom_str); + } + + if (json) + json_object_object_add(json, "exportRts", json_export_rtl); +} + +static void display_vni(struct vty *vty, struct bgpevpn *vpn, json_object *json) +{ + char *ecom_str; + struct listnode *node, *nnode; + struct ecommunity *ecom; + json_object *json_import_rtl = NULL; + json_object *json_export_rtl = NULL; + struct bgp *bgp_evpn; + enum asnotation_mode asnotation; + + bgp_evpn = bgp_get_evpn(); + asnotation = bgp_get_asnotation(bgp_evpn); + + if (json) { + json_import_rtl = json_object_new_array(); + json_export_rtl = json_object_new_array(); + json_object_int_add(json, "vni", vpn->vni); + json_object_string_add(json, "type", "L2"); + json_object_string_add(json, "inKernel", + is_vni_live(vpn) ? "True" : "False"); + json_object_string_addf( + json, "rd", BGP_RD_AS_FORMAT(asnotation), &vpn->prd); + json_object_string_addf(json, "originatorIp", "%pI4", + &vpn->originator_ip); + json_object_string_addf(json, "mcastGroup", "%pI4", + &vpn->mcast_grp); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "siteOfOrigin", ecom_str); + ecommunity_strfree(&ecom_str); + } + /* per vni knob is enabled -- Enabled + * Global knob is enabled -- Active + * default -- Disabled + */ + if (!vpn->advertise_gw_macip && + bgp_evpn && bgp_evpn->advertise_gw_macip) + json_object_string_add(json, "advertiseGatewayMacip", + "Active"); + else if (vpn->advertise_gw_macip) + json_object_string_add(json, "advertiseGatewayMacip", + "Enabled"); + else + json_object_string_add(json, "advertiseGatewayMacip", + "Disabled"); + if (!vpn->advertise_svi_macip && bgp_evpn && + bgp_evpn->evpn_info && + bgp_evpn->evpn_info->advertise_svi_macip) + json_object_string_add(json, "advertiseSviMacIp", + "Active"); + else if (vpn->advertise_svi_macip) + json_object_string_add(json, "advertiseSviMacIp", + "Enabled"); + else + json_object_string_add(json, "advertiseSviMacIp", + "Disabled"); + json_object_string_add( + json, "sviInterface", + ifindex2ifname(vpn->svi_ifindex, vpn->tenant_vrf_id)); + } else { + vty_out(vty, "VNI: %u", vpn->vni); + if (is_vni_live(vpn)) + vty_out(vty, " (known to the kernel)"); + vty_out(vty, "\n"); + + vty_out(vty, " Type: %s\n", "L2"); + vty_out(vty, " Tenant-Vrf: %s\n", + vrf_id_to_name(vpn->tenant_vrf_id)); + vty_out(vty, " RD: "); + vty_out(vty, BGP_RD_AS_FORMAT(asnotation), &vpn->prd); + vty_out(vty, "\n"); + vty_out(vty, " Originator IP: %pI4\n", &vpn->originator_ip); + vty_out(vty, " Mcast group: %pI4\n", &vpn->mcast_grp); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " MAC-VRF Site-of-Origin: %s\n", + ecom_str); + ecommunity_strfree(&ecom_str); + } + if (!vpn->advertise_gw_macip && + bgp_evpn && bgp_evpn->advertise_gw_macip) + vty_out(vty, " Advertise-gw-macip : %s\n", + "Active"); + else if (vpn->advertise_gw_macip) + vty_out(vty, " Advertise-gw-macip : %s\n", + "Enabled"); + else + vty_out(vty, " Advertise-gw-macip : %s\n", + "Disabled"); + if (!vpn->advertise_svi_macip && bgp_evpn && + bgp_evpn->evpn_info && + bgp_evpn->evpn_info->advertise_svi_macip) + vty_out(vty, " Advertise-svi-macip : %s\n", + "Active"); + else if (vpn->advertise_svi_macip) + vty_out(vty, " Advertise-svi-macip : %s\n", + "Enabled"); + else + vty_out(vty, " Advertise-svi-macip : %s\n", + "Disabled"); + vty_out(vty, " SVI interface : %s\n", + ifindex2ifname(vpn->svi_ifindex, vpn->tenant_vrf_id)); + } + + if (!json) + vty_out(vty, " Import Route Target:\n"); + + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + ecom_str = ecommunity_ecom2str(ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) + json_object_array_add(json_import_rtl, + json_object_new_string(ecom_str)); + else + vty_out(vty, " %s\n", ecom_str); + + ecommunity_strfree(&ecom_str); + } + + if (json) + json_object_object_add(json, "importRts", json_import_rtl); + else + vty_out(vty, " Export Route Target:\n"); + + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { + ecom_str = ecommunity_ecom2str(ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) + json_object_array_add(json_export_rtl, + json_object_new_string(ecom_str)); + else + vty_out(vty, " %s\n", ecom_str); + + ecommunity_strfree(&ecom_str); + } + + if (json) + json_object_object_add(json, "exportRts", json_export_rtl); +} + +static void show_esi_routes(struct bgp *bgp, + struct bgp_evpn_es *es, + struct vty *vty, + json_object *json) +{ + int header = 1; + struct bgp_dest *dest; + struct bgp_path_info *pi; + uint32_t prefix_cnt, path_cnt; + uint64_t tbl_ver; + + prefix_cnt = path_cnt = 0; + + tbl_ver = es->route_table->version; + for (dest = bgp_table_top(es->route_table); dest; + dest = bgp_route_next(dest)) { + int add_prefix_to_json = 0; + json_object *json_paths = NULL; + json_object *json_prefix = NULL; + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (json) + json_prefix = json_object_new_object(); + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi) { + /* Overall header/legend displayed once. */ + if (header) { + bgp_evpn_show_route_header(vty, bgp, + tbl_ver, json); + header = 0; + } + + prefix_cnt++; + } + + if (json) + json_paths = json_object_new_array(); + + /* For EVPN, the prefix is displayed for each path (to fit in + * with code that already exists). + */ + for (; pi; pi = pi->next) { + json_object *json_path = NULL; + + if (json) + json_path = json_object_new_array(); + + route_vty_out(vty, p, pi, 0, SAFI_EVPN, json_path, + false); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + add_prefix_to_json = 1; + } + + if (json) { + if (add_prefix_to_json) { + json_object_string_addf(json_prefix, "prefix", + "%pFX", p); + json_object_int_add(json_prefix, "prefixLen", + p->prefixlen); + json_object_object_add(json_prefix, "paths", + json_paths); + json_object_object_addf(json, json_prefix, + "%pFX", p); + } else { + json_object_free(json_paths); + json_object_free(json_prefix); + json_paths = NULL; + json_prefix = NULL; + } + } + } + + if (json) { + json_object_int_add(json, "numPrefix", prefix_cnt); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (prefix_cnt == 0) + vty_out(vty, "No EVPN prefixes exist for this ESI\n"); + else + vty_out(vty, "\nDisplayed %u prefixes (%u paths)\n", + prefix_cnt, path_cnt); + } +} + +/* Display all MAC-IP VNI routes linked to an ES */ +static void bgp_evpn_show_routes_mac_ip_es(struct vty *vty, esi_t *esi, + json_object *json, int detail, + bool global_table) +{ + struct bgp_dest *bd; + struct bgp_path_info *pi; + int header = detail ? 0 : 1; + uint32_t path_cnt; + struct listnode *node; + struct bgp_evpn_es *es; + struct bgp_path_es_info *es_info; + struct bgp *bgp = bgp_get_evpn(); + json_object *json_paths = NULL; + + if (!bgp) + return; + + path_cnt = 0; + + if (json) + json_paths = json_object_new_array(); + + RB_FOREACH (es, bgp_es_rb_head, &bgp_mh_info->es_rb_tree) { + struct list *es_list; + + if (esi && memcmp(esi, &es->esi, sizeof(*esi))) + continue; + + if (global_table) + es_list = es->macip_global_path_list; + else + es_list = es->macip_evi_path_list; + + for (ALL_LIST_ELEMENTS_RO(es_list, node, es_info)) { + json_object *json_path = NULL; + + pi = es_info->pi; + bd = pi->net; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + continue; + + /* Overall header/legend displayed once. */ + if (header) { + bgp_evpn_show_route_header(vty, bgp, 0, json); + header = 0; + } + + path_cnt++; + + if (json) + json_path = json_object_new_array(); + + if (detail) + route_vty_out_detail( + vty, bgp, bd, bgp_dest_get_prefix(bd), + pi, AFI_L2VPN, SAFI_EVPN, + RPKI_NOT_BEING_USED, json_path); + else + route_vty_out(vty, &bd->rn->p, pi, 0, SAFI_EVPN, + json_path, false); + + if (json) + json_object_array_add(json_paths, json_path); + } + } + + if (json) { + json_object_object_add(json, "paths", json_paths); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (path_cnt == 0) + vty_out(vty, "There are no MAC-IP ES paths"); + else + vty_out(vty, "\nDisplayed %u paths\n", path_cnt); + vty_out(vty, "\n"); + } +} + +static void bgp_evpn_show_routes_mac_ip_evi_es(struct vty *vty, esi_t *esi, + json_object *json, int detail) +{ + bgp_evpn_show_routes_mac_ip_es(vty, esi, json, detail, false); +} + +static void bgp_evpn_show_routes_mac_ip_global_es(struct vty *vty, esi_t *esi, + json_object *json, int detail) +{ + bgp_evpn_show_routes_mac_ip_es(vty, esi, json, detail, true); +} + +static void show_vni_routes(struct bgp *bgp, struct bgpevpn *vpn, + struct vty *vty, int type, bool mac_table, + struct in_addr vtep_ip, json_object *json, + int detail) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_table *table; + int header = detail ? 0 : 1; + uint64_t tbl_ver; + uint32_t prefix_cnt, path_cnt; + + prefix_cnt = path_cnt = 0; + + if (mac_table) + table = vpn->mac_table; + else + table = vpn->ip_table; + + tbl_ver = table->version; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + int add_prefix_to_json = 0; + json_object *json_paths = NULL; + json_object *json_prefix = NULL; + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (type && evp->prefix.route_type != type) + continue; + + if (json) + json_prefix = json_object_new_object(); + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi) { + /* Overall header/legend displayed once. */ + if (header) { + bgp_evpn_show_route_header(vty, bgp, + tbl_ver, json); + header = 0; + } + + prefix_cnt++; + } + + if (json) + json_paths = json_object_new_array(); + + /* For EVPN, the prefix is displayed for each path (to fit in + * with code that already exists). + */ + for (; pi; pi = pi->next) { + struct prefix tmp_p; + json_object *json_path = NULL; + + if (vtep_ip.s_addr != INADDR_ANY + && !IPV4_ADDR_SAME(&(vtep_ip), + &(pi->attr->nexthop))) + continue; + + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) { + /* + * VNI IP/MAC table prefixes don't have MAC/IP + * respectively so make sure it's set from path + * info here. + */ + if (is_evpn_prefix_ipaddr_none(evp)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy( + (struct prefix_evpn *)&tmp_p, + evp, NULL /* mac */, + evpn_type2_path_info_get_ip( + pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy( + (struct prefix_evpn *)&tmp_p, + evp, + evpn_type2_path_info_get_mac( + pi), + NULL /* ip */); + } + } else + memcpy(&tmp_p, p, sizeof(tmp_p)); + + + if (json) + json_path = json_object_new_array(); + + if (detail) + route_vty_out_detail(vty, bgp, dest, &tmp_p, pi, + AFI_L2VPN, SAFI_EVPN, + RPKI_NOT_BEING_USED, + json_path); + + else + route_vty_out(vty, &tmp_p, pi, 0, SAFI_EVPN, + json_path, false); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + add_prefix_to_json = 1; + } + + if (json) { + if (add_prefix_to_json) { + json_object_string_addf(json_prefix, "prefix", + "%pFX", p); + json_object_int_add(json_prefix, "prefixLen", + p->prefixlen); + json_object_object_add(json_prefix, "paths", + json_paths); + json_object_object_addf(json, json_prefix, + "%pFX", p); + } else { + json_object_free(json_paths); + json_object_free(json_prefix); + json_paths = NULL; + json_prefix = NULL; + } + } + } + + if (json) { + json_object_int_add(json, "numPrefix", prefix_cnt); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (prefix_cnt == 0) + vty_out(vty, "No EVPN prefixes %sexist for this VNI", + type ? "(of requested type) " : ""); + else + vty_out(vty, "\nDisplayed %u prefixes (%u paths)%s\n", + prefix_cnt, path_cnt, + type ? " (of requested type)" : ""); + vty_out(vty, "\n"); + } +} + +static void show_vni_routes_hash(struct hash_bucket *bucket, void *arg) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct vni_walk_ctx *wctx = arg; + struct vty *vty = wctx->vty; + json_object *json = wctx->json; + json_object *json_vni = NULL; + char vni_str[VNI_STR_LEN]; + + snprintf(vni_str, sizeof(vni_str), "%u", vpn->vni); + if (json) { + json_vni = json_object_new_object(); + json_object_int_add(json_vni, "vni", vpn->vni); + } else { + vty_out(vty, "\nVNI: %u\n\n", vpn->vni); + } + + show_vni_routes(wctx->bgp, vpn, wctx->vty, wctx->type, wctx->mac_table, + wctx->vtep_ip, json_vni, wctx->detail); + + if (json) + json_object_object_add(json, vni_str, json_vni); +} + +static void show_vni_routes_all_hash(struct hash_bucket *bucket, void *arg) +{ + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + struct vni_walk_ctx *wctx = arg; + struct vty *vty = wctx->vty; + json_object *json = wctx->json; + json_object *json_vni = NULL; + json_object *json_vni_mac = NULL; + char vni_str[VNI_STR_LEN]; + + snprintf(vni_str, sizeof(vni_str), "%u", vpn->vni); + if (json) { + json_vni = json_object_new_object(); + json_object_int_add(json_vni, "vni", vpn->vni); + } else { + vty_out(vty, "\nVNI: %u\n\n", vpn->vni); + } + + show_vni_routes(wctx->bgp, vpn, wctx->vty, 0, false, wctx->vtep_ip, + json_vni, wctx->detail); + + if (json) + json_object_object_add(json, vni_str, json_vni); + + if (json) + json_vni_mac = json_object_new_object(); + else + vty_out(vty, "\nVNI: %u MAC Table\n\n", vpn->vni); + + show_vni_routes(wctx->bgp, vpn, wctx->vty, 0, true, wctx->vtep_ip, + json_vni_mac, wctx->detail); + + if (json) + json_object_object_add(json_vni, "macTable", json_vni_mac); +} + +static void show_l3vni_entry(struct vty *vty, struct bgp *bgp, + json_object *json) +{ + json_object *json_vni = NULL; + json_object *json_import_rtl = NULL; + json_object *json_export_rtl = NULL; + char buf1[10]; + char buf2[INET6_ADDRSTRLEN]; + char rt_buf[25]; + char *ecom_str; + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + struct bgp *bgp_evpn; + + if (!bgp->l3vni) + return; + + bgp_evpn = bgp_get_evpn(); + + if (json) { + json_vni = json_object_new_object(); + json_import_rtl = json_object_new_array(); + json_export_rtl = json_object_new_array(); + } + + /* if an l3vni is present in bgp it is live */ + buf1[0] = '\0'; + snprintf(buf1, sizeof(buf1), "*"); + + if (json) { + json_object_int_add(json_vni, "vni", bgp->l3vni); + json_object_string_add(json_vni, "type", "L3"); + json_object_string_add(json_vni, "inKernel", "True"); + json_object_string_addf(json_vni, "originatorIp", "%pI4", + &bgp->originator_ip); + json_object_string_addf(json_vni, "rd", + BGP_RD_AS_FORMAT(bgp->asnotation), + &bgp->vrf_prd); + json_object_string_add(json_vni, "advertiseGatewayMacip", + "n/a"); + json_object_string_add(json_vni, "advertiseSviMacIp", "n/a"); + json_object_string_add( + json_vni, "advertisePip", + bgp->evpn_info->advertise_pip ? "Enabled" : "Disabled"); + json_object_string_addf(json_vni, "sysIP", "%pI4", + &bgp->evpn_info->pip_ip); + json_object_string_add(json_vni, "sysMAC", + prefix_mac2str(&bgp->evpn_info->pip_rmac, + buf2, sizeof(buf2))); + json_object_string_add( + json_vni, "rmac", + prefix_mac2str(&bgp->rmac, buf2, sizeof(buf2))); + } else { + vty_out(vty, "%-1s %-10u %-4s ", buf1, bgp->l3vni, "L3"); + vty_out(vty, BGP_RD_AS_FORMAT_SPACE(bgp->asnotation), + &bgp->vrf_prd); + } + + for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, l3rt)) { + ecom_str = ecommunity_ecom2str(l3rt->ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) { + json_object_array_add(json_import_rtl, + json_object_new_string(ecom_str)); + } else { + if (listcount(bgp->vrf_import_rtl) > 1) + snprintf(rt_buf, sizeof(rt_buf), "%s, ...", + ecom_str); + else + snprintf(rt_buf, sizeof(rt_buf), "%s", + ecom_str); + vty_out(vty, " %-25s", rt_buf); + } + + ecommunity_strfree(&ecom_str); + + /* If there are multiple import RTs we break here and show only + * one */ + if (!json) + break; + } + + if (json) + json_object_object_add(json_vni, "importRTs", json_import_rtl); + + for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, l3rt)) { + ecom_str = ecommunity_ecom2str(l3rt->ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) { + json_object_array_add(json_export_rtl, + json_object_new_string(ecom_str)); + } else { + if (listcount(bgp->vrf_export_rtl) > 1) + snprintf(rt_buf, sizeof(rt_buf), "%s, ...", + ecom_str); + else + snprintf(rt_buf, sizeof(rt_buf), "%s", + ecom_str); + vty_out(vty, " %-25s", rt_buf); + } + + ecommunity_strfree(&ecom_str); + + /* If there are multiple export RTs we break here and show only + * one */ + if (!json) { + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " %-25s", ecom_str); + ecommunity_strfree(&ecom_str); + } + vty_out(vty, " %-37s", vrf_id_to_name(bgp->vrf_id)); + break; + } + } + + if (json) { + char vni_str[VNI_STR_LEN]; + + json_object_object_add(json_vni, "exportRTs", json_export_rtl); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json_vni, "siteOfOrigin", + ecom_str); + ecommunity_strfree(&ecom_str); + } + snprintf(vni_str, sizeof(vni_str), "%u", bgp->l3vni); + json_object_object_add(json, vni_str, json_vni); + } else + vty_out(vty, "\n"); +} + +static void show_vni_entry(struct hash_bucket *bucket, void *args[]) +{ + struct vty *vty; + json_object *json; + json_object *json_vni = NULL; + json_object *json_import_rtl = NULL; + json_object *json_export_rtl = NULL; + struct bgpevpn *vpn = (struct bgpevpn *)bucket->data; + char buf1[10]; + char rt_buf[25]; + char *ecom_str; + struct listnode *node, *nnode; + struct ecommunity *ecom; + struct bgp *bgp_evpn; + enum asnotation_mode asnotation; + + vty = args[0]; + json = args[1]; + + bgp_evpn = bgp_get_evpn(); + asnotation = bgp_get_asnotation(bgp_evpn); + + if (json) { + json_vni = json_object_new_object(); + json_import_rtl = json_object_new_array(); + json_export_rtl = json_object_new_array(); + } + + buf1[0] = '\0'; + if (is_vni_live(vpn)) + snprintf(buf1, sizeof(buf1), "*"); + + if (json) { + json_object_int_add(json_vni, "vni", vpn->vni); + json_object_string_add(json_vni, "type", "L2"); + json_object_string_add(json_vni, "inKernel", + is_vni_live(vpn) ? "True" : "False"); + json_object_string_addf(json_vni, "rd", + BGP_RD_AS_FORMAT(asnotation), + &vpn->prd); + json_object_string_addf(json_vni, "originatorIp", "%pI4", + &vpn->originator_ip); + json_object_string_addf(json_vni, "mcastGroup", "%pI4", + &vpn->mcast_grp); + /* per vni knob is enabled -- Enabled + * Global knob is enabled -- Active + * default -- Disabled + */ + if (!vpn->advertise_gw_macip && bgp_evpn + && bgp_evpn->advertise_gw_macip) + json_object_string_add( + json_vni, "advertiseGatewayMacip", "Active"); + else if (vpn->advertise_gw_macip) + json_object_string_add( + json_vni, "advertiseGatewayMacip", "Enabled"); + else + json_object_string_add( + json_vni, "advertiseGatewayMacip", "Disabled"); + if (!vpn->advertise_svi_macip && bgp_evpn + && bgp_evpn->evpn_info->advertise_svi_macip) + json_object_string_add(json_vni, "advertiseSviMacIp", + "Active"); + else if (vpn->advertise_svi_macip) + json_object_string_add(json_vni, "advertiseSviMacIp", + "Enabled"); + else + json_object_string_add(json_vni, "advertiseSviMacIp", + "Disabled"); + } else { + vty_out(vty, "%-1s %-10u %-4s ", buf1, vpn->vni, "L2"); + vty_out(vty, BGP_RD_AS_FORMAT_SPACE(asnotation), &vpn->prd); + } + + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + ecom_str = ecommunity_ecom2str(ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) { + json_object_array_add(json_import_rtl, + json_object_new_string(ecom_str)); + } else { + if (listcount(vpn->import_rtl) > 1) + snprintf(rt_buf, sizeof(rt_buf), "%s, ...", + ecom_str); + else + snprintf(rt_buf, sizeof(rt_buf), "%s", + ecom_str); + vty_out(vty, " %-25s", rt_buf); + } + + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + + /* If there are multiple import RTs we break here and show only + * one */ + if (!json) + break; + } + + if (json) + json_object_object_add(json_vni, "importRTs", json_import_rtl); + + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { + ecom_str = ecommunity_ecom2str(ecom, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (json) { + json_object_array_add(json_export_rtl, + json_object_new_string(ecom_str)); + } else { + if (listcount(vpn->export_rtl) > 1) + snprintf(rt_buf, sizeof(rt_buf), "%s, ...", + ecom_str); + else + snprintf(rt_buf, sizeof(rt_buf), "%s", + ecom_str); + vty_out(vty, " %-25s", rt_buf); + } + + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + + /* If there are multiple export RTs we break here and show only + * one */ + if (!json) { + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " %-25s", ecom_str); + ecommunity_strfree(&ecom_str); + } + vty_out(vty, " %-37s", + vrf_id_to_name(vpn->tenant_vrf_id)); + break; + } + } + + if (json) { + char vni_str[VNI_STR_LEN]; + + json_object_object_add(json_vni, "exportRTs", json_export_rtl); + if (bgp_evpn && bgp_evpn->evpn_info) { + ecom_str = ecommunity_ecom2str( + bgp_evpn->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json_vni, "siteOfOrigin", + ecom_str); + ecommunity_strfree(&ecom_str); + } + snprintf(vni_str, sizeof(vni_str), "%u", vpn->vni); + json_object_object_add(json, vni_str, json_vni); + } else + vty_out(vty, "\n"); +} + +static int bgp_show_ethernet_vpn(struct vty *vty, struct prefix_rd *prd, + enum bgp_show_type type, void *output_arg, + int option, bool use_json) +{ + afi_t afi = AFI_L2VPN; + struct bgp *bgp; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_path_info *pi; + int rd_header; + int header = 1; + char rd_str[RD_ADDRSTRLEN]; + int no_display; + + unsigned long output_count = 0; + unsigned long total_count = 0; + json_object *json = NULL; + json_object *json_array = NULL; + json_object *json_prefix_info = NULL; + + memset(rd_str, 0, RD_ADDRSTRLEN); + + bgp = bgp_get_evpn(); + if (bgp == NULL) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + + if (use_json) + json = json_object_new_object(); + + for (dest = bgp_table_top(bgp->rib[afi][SAFI_EVPN]); dest; + dest = bgp_route_next(dest)) { + uint64_t tbl_ver; + json_object *json_nroute = NULL; + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(p->u.val, prd->val, 8) != 0) + continue; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + rd_header = 1; + tbl_ver = table->version; + + for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { + pi = bgp_dest_get_bgp_path_info(rm); + if (pi == NULL) + continue; + + no_display = 0; + for (; pi; pi = pi->next) { + struct community *picomm = NULL; + + picomm = bgp_attr_get_community(pi->attr); + + total_count++; + if (type == bgp_show_type_neighbor) { + struct peer *peer = output_arg; + + if (peer_cmp(peer, pi->peer) != 0) + continue; + } + if (type == bgp_show_type_lcommunity_exact) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity( + pi->attr) || + !lcommunity_cmp( + bgp_attr_get_lcommunity( + pi->attr), + lcom)) + continue; + } + if (type == bgp_show_type_lcommunity) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity( + pi->attr) || + !lcommunity_match( + bgp_attr_get_lcommunity( + pi->attr), + lcom)) + continue; + } + if (type == bgp_show_type_community) { + struct community *com = output_arg; + + if (!picomm || + !community_match(picomm, com)) + continue; + } + if (type == bgp_show_type_community_exact) { + struct community *com = output_arg; + + if (!picomm || + !community_cmp(picomm, com)) + continue; + } + if (header) { + if (use_json) { + json_object_int_add( + json, "bgpTableVersion", + tbl_ver); + json_object_string_addf( + json, + "bgpLocalRouterId", + "%pI4", + &bgp->router_id); + json_object_int_add( + json, + "defaultLocPrf", + bgp->default_local_pref); + asn_asn2json(json, "localAS", + bgp->as, + bgp->asnotation); + } else { + if (option == SHOW_DISPLAY_TAGS) + vty_out(vty, + V4_HEADER_TAG); + else if ( + option + == SHOW_DISPLAY_OVERLAY) + vty_out(vty, + V4_HEADER_OVERLAY); + else { + bgp_evpn_show_route_header(vty, bgp, tbl_ver, NULL); + } + } + header = 0; + } + if (rd_header) { + if (use_json) + json_nroute = + json_object_new_object(); + bgp_evpn_show_route_rd_header( + vty, dest, json_nroute, rd_str, + RD_ADDRSTRLEN); + rd_header = 0; + } + if (use_json && !json_array) + json_array = json_object_new_array(); + + if (option == SHOW_DISPLAY_TAGS) + route_vty_out_tag( + vty, bgp_dest_get_prefix(rm), + pi, no_display, SAFI_EVPN, + json_array); + else if (option == SHOW_DISPLAY_OVERLAY) + route_vty_out_overlay( + vty, bgp_dest_get_prefix(rm), + pi, no_display, json_array); + else + route_vty_out(vty, + bgp_dest_get_prefix(rm), + pi, no_display, SAFI_EVPN, + json_array, false); + no_display = 1; + } + + if (no_display) + output_count++; + + if (use_json && json_array) { + const struct prefix *p = + bgp_dest_get_prefix(rm); + + json_prefix_info = json_object_new_object(); + + json_object_string_addf(json_prefix_info, + "prefix", "%pFX", p); + + json_object_int_add(json_prefix_info, + "prefixLen", p->prefixlen); + + json_object_object_add(json_prefix_info, + "paths", json_array); + json_object_object_addf(json_nroute, + json_prefix_info, + "%pFX", p); + json_array = NULL; + } + } + + if (use_json && json_nroute) + json_object_object_add(json, rd_str, json_nroute); + } + + if (use_json) { + json_object_int_add(json, "numPrefix", output_count); + json_object_int_add(json, "totalPrefix", total_count); + vty_json(vty, json); + } else { + if (output_count == 0) + vty_out(vty, "No prefixes displayed, %ld exist\n", + total_count); + else + vty_out(vty, + "\nDisplayed %ld out of %ld total prefixes\n", + output_count, total_count); + } + return CMD_SUCCESS; +} + +DEFUN(show_ip_bgp_l2vpn_evpn, + show_ip_bgp_l2vpn_evpn_cmd, + "show [ip] bgp l2vpn evpn [json]", + SHOW_STR IP_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR JSON_STR) +{ + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, + SHOW_DISPLAY_STANDARD, + use_json(argc, argv)); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_rd, + show_ip_bgp_l2vpn_evpn_rd_cmd, + "show [ip] bgp l2vpn evpn rd [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + JSON_STR) +{ + int idx_ext_community = 0; + int ret; + struct prefix_rd prd; + int rd_all = 0; + + if (argv_find(argv, argc, "all", &rd_all)) + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, + NULL, SHOW_DISPLAY_STANDARD, + use_json(argc, argv)); + + argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, + SHOW_DISPLAY_STANDARD, + use_json(argc, argv)); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_all_tags, + show_ip_bgp_l2vpn_evpn_all_tags_cmd, + "show [ip] bgp l2vpn evpn all tags", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information about all EVPN NLRIs\n" + "Display BGP tags for prefixes\n") +{ + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, + SHOW_DISPLAY_TAGS, 0); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_rd_tags, + show_ip_bgp_l2vpn_evpn_rd_tags_cmd, + "show [ip] bgp l2vpn evpn rd tags", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Display BGP tags for prefixes\n") +{ + int idx_ext_community = 0; + int ret; + struct prefix_rd prd; + int rd_all = 0; + + if (argv_find(argv, argc, "all", &rd_all)) + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, + NULL, SHOW_DISPLAY_TAGS, 0); + + argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, + SHOW_DISPLAY_TAGS, 0); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_neighbor_routes, + show_ip_bgp_l2vpn_evpn_neighbor_routes_cmd, + "show [ip] bgp l2vpn evpn neighbors routes [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "IPv4 Neighbor to display information about\n" + "IPv6 Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display routes learned from neighbor\n" JSON_STR) +{ + int idx = 0; + struct peer *peer; + char *peerstr = NULL; + bool uj = use_json(argc, argv); + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp *bgp = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) { + vty_out(vty, "No index\n"); + return CMD_WARNING; + } + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx]->arg); + return CMD_WARNING; + } + if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_neighbor, peer, + SHOW_DISPLAY_STANDARD, uj); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_rd_neighbor_routes, + show_ip_bgp_l2vpn_evpn_rd_neighbor_routes_cmd, + "show [ip] bgp l2vpn evpn rd neighbors routes [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Detailed information on TCP and BGP neighbor connections\n" + "IPv4 Neighbor to display information about\n" + "IPv6 Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display routes learned from neighbor\n" JSON_STR) +{ + int idx_ext_community = 0; + int idx = 0; + int ret; + struct peer *peer; + char *peerstr = NULL; + struct prefix_rd prd = {}; + bool uj = use_json(argc, argv); + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + struct bgp *bgp = NULL; + int rd_all = 0; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) { + vty_out(vty, "No index\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "all", &rd_all)) { + argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", + &idx_ext_community); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "Malformed Route Distinguisher"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + } + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx]->arg); + return CMD_WARNING; + } + if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + + if (rd_all) + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_neighbor, + peer, SHOW_DISPLAY_STANDARD, uj); + else + return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_neighbor, + peer, SHOW_DISPLAY_STANDARD, uj); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes, + show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes_cmd, + "show [ip] bgp l2vpn evpn neighbors advertised-routes [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "IPv4 Neighbor to display information about\n" + "IPv6 Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes advertised to a BGP neighbor\n" JSON_STR) +{ + int idx = 0; + struct peer *peer; + bool uj = use_json(argc, argv); + struct bgp *bgp = NULL; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + char *peerstr = NULL; + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) { + vty_out(vty, "No index\n"); + return CMD_WARNING; + } + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx]->arg); + return CMD_WARNING; + } + if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + return show_adj_route_vpn(vty, peer, NULL, AFI_L2VPN, SAFI_EVPN, uj); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes, + show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes_cmd, + "show [ip] bgp l2vpn evpn rd neighbors advertised-routes [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Detailed information on TCP and BGP neighbor connections\n" + "IPv4 Neighbor to display information about\n" + "IPv6 Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes advertised to a BGP neighbor\n" JSON_STR) +{ + int idx_ext_community = 0; + int idx = 0; + int ret; + struct peer *peer; + struct prefix_rd prd; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + char *peerstr = NULL; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + int rd_all = 0; + + if (uj) + argc--; + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) { + vty_out(vty, "No index\n"); + return CMD_WARNING; + } + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx]->arg); + return CMD_WARNING; + } + if (!peer || !peer->afc[AFI_L2VPN][SAFI_EVPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "all", &rd_all)) + return show_adj_route_vpn(vty, peer, NULL, AFI_L2VPN, SAFI_EVPN, + uj); + else { + argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", + &idx_ext_community); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "Malformed Route Distinguisher"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + } + + return show_adj_route_vpn(vty, peer, &prd, AFI_L2VPN, SAFI_EVPN, uj); +} + +DEFUN(show_ip_bgp_l2vpn_evpn_all_overlay, + show_ip_bgp_l2vpn_evpn_all_overlay_cmd, + "show [ip] bgp l2vpn evpn all overlay [json]", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information about all EVPN NLRIs\n" + "Display BGP Overlay Information for prefixes\n" + JSON_STR) +{ + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, NULL, + SHOW_DISPLAY_OVERLAY, + use_json(argc, argv)); +} + +DEFUN(show_ip_bgp_evpn_rd_overlay, + show_ip_bgp_evpn_rd_overlay_cmd, + "show [ip] bgp l2vpn evpn rd overlay", + SHOW_STR + IP_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Display BGP Overlay Information for prefixes\n") +{ + int idx_ext_community = 0; + int ret; + struct prefix_rd prd; + int rd_all = 0; + + if (argv_find(argv, argc, "all", &rd_all)) + return bgp_show_ethernet_vpn(vty, NULL, bgp_show_type_normal, + NULL, SHOW_DISPLAY_OVERLAY, + use_json(argc, argv)); + + argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", &idx_ext_community); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_ethernet_vpn(vty, &prd, bgp_show_type_normal, NULL, + SHOW_DISPLAY_OVERLAY, + use_json(argc, argv)); +} + +DEFUN(show_bgp_l2vpn_evpn_com, + show_bgp_l2vpn_evpn_com_cmd, + "show bgp l2vpn evpn \ + \ + [exact-match] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Display routes matching the community\n" + "Community number where AA and NN are (0-65535)\n" + "Display routes matching the large-community\n" + "List of large-community numbers\n" + "Exact match of the communities\n" + JSON_STR) +{ + int idx = 0; + int ret = 0; + const char *clist_number_or_name; + int show_type = bgp_show_type_normal; + struct community *com; + struct lcommunity *lcom; + + if (argv_find(argv, argc, "large-community", &idx)) { + clist_number_or_name = argv[++idx]->arg; + show_type = bgp_show_type_lcommunity; + + if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) + show_type = bgp_show_type_lcommunity_exact; + + lcom = lcommunity_str2com(clist_number_or_name); + if (!lcom) { + vty_out(vty, "%% Large-community malformed\n"); + return CMD_WARNING; + } + + ret = bgp_show_ethernet_vpn(vty, NULL, show_type, lcom, + SHOW_DISPLAY_STANDARD, + use_json(argc, argv)); + + lcommunity_free(&lcom); + } else if (argv_find(argv, argc, "community", &idx)) { + clist_number_or_name = argv[++idx]->arg; + show_type = bgp_show_type_community; + + if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) + show_type = bgp_show_type_community_exact; + + com = community_str2com(clist_number_or_name); + + if (!com) { + vty_out(vty, "%% Community malformed: %s\n", + clist_number_or_name); + return CMD_WARNING; + } + + ret = bgp_show_ethernet_vpn(vty, NULL, show_type, com, + SHOW_DISPLAY_STANDARD, + use_json(argc, argv)); + community_free(&com); + } + + return ret; +} + +/* For testing purpose, static route of EVPN RT-5. */ +DEFUN(evpnrt5_network, + evpnrt5_network_cmd, + "network rd ASN:NN_OR_IP-ADDRESS:NN ethtag WORD label WORD esi WORD gwip routermac WORD [route-map RMAP_NAME]", + "Specify a network to announce via BGP\n" + "IP prefix\n" + "IPv6 prefix\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "Ethernet Tag\n" + "Ethernet Tag Value\n" + "BGP label\n" + "label value\n" + "Ethernet Segment Identifier\n" + "ESI value ( 00:11:22:33:44:55:66:77:88:99 format) \n" + "Gateway IP\n" + "Gateway IP ( A.B.C.D )\n" + "Gateway IPv6 ( X:X::X:X )\n" + "Router Mac Ext Comm\n" + "Router Mac address Value ( aa:bb:cc:dd:ee:ff format)\n" + "Route-map to modify the attributes\n" + "Name of the route map\n") +{ + int idx_ipv4_prefixlen = 1; + int idx_route_distinguisher = 3; + int idx_label = 7; + int idx_esi = 9; + int idx_gwip = 11; + int idx_ethtag = 5; + int idx_routermac = 13; + + return bgp_static_set(vty, false, argv[idx_ipv4_prefixlen]->arg, + argv[idx_route_distinguisher]->arg, + argv[idx_label]->arg, AFI_L2VPN, SAFI_EVPN, NULL, + 0, 0, BGP_EVPN_IP_PREFIX_ROUTE, + argv[idx_esi]->arg, argv[idx_gwip]->arg, + argv[idx_ethtag]->arg, argv[idx_routermac]->arg); +} + +/* For testing purpose, static route of EVPN RT-5. */ +DEFUN(no_evpnrt5_network, + no_evpnrt5_network_cmd, + "no network rd ASN:NN_OR_IP-ADDRESS:NN ethtag WORD label WORD esi WORD gwip ", + NO_STR + "Specify a network to announce via BGP\n" + "IP prefix\n" + "IPv6 prefix\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "Ethernet Tag\n" + "Ethernet Tag Value\n" + "BGP label\n" + "label value\n" + "Ethernet Segment Identifier\n" + "ESI value ( 00:11:22:33:44:55:66:77:88:99 format) \n" + "Gateway IP\n" "Gateway IP ( A.B.C.D )\n" "Gateway IPv6 ( X:X::X:X )\n") +{ + int idx_ipv4_prefixlen = 2; + int idx_ext_community = 4; + int idx_label = 8; + int idx_ethtag = 6; + int idx_esi = 10; + int idx_gwip = 12; + + return bgp_static_set(vty, true, argv[idx_ipv4_prefixlen]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_L2VPN, SAFI_EVPN, NULL, + 0, 0, BGP_EVPN_IP_PREFIX_ROUTE, argv[idx_esi]->arg, + argv[idx_gwip]->arg, argv[idx_ethtag]->arg, NULL); +} + +static void evpn_import_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn) +{ + evpn_rt_delete_auto(bgp, vpn->vni, vpn->import_rtl, false); +} + +static void evpn_export_rt_delete_auto(struct bgp *bgp, struct bgpevpn *vpn) +{ + evpn_rt_delete_auto(bgp, vpn->vni, vpn->export_rtl, false); +} + +/* + * Configure the Import RTs for a VNI (vty handler). Caller expected to + * check that this is a change. + */ +static void evpn_configure_import_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct ecommunity *ecomadd) +{ + /* If the VNI is "live", we need to uninstall routes using the current + * import RT(s) first before we update the import RT, and subsequently + * install routes. + */ + if (is_vni_live(vpn)) + bgp_evpn_uninstall_routes(bgp, vpn); + + /* Cleanup the RT to VNI mapping and get rid of existing import RT. */ + bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); + + /* If the auto route-target is in use we must remove it */ + evpn_import_rt_delete_auto(bgp, vpn); + + /* Add new RT and rebuild the RT to VNI mapping */ + listnode_add_sort(vpn->import_rtl, ecomadd); + + SET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); + bgp_evpn_map_vni_to_its_rts(bgp, vpn); + + /* Install routes that match new import RT */ + if (is_vni_live(vpn)) + bgp_evpn_install_routes(bgp, vpn); +} + +/* + * Unconfigure Import RT(s) for a VNI (vty handler). + */ +static void evpn_unconfigure_import_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct ecommunity *ecomdel) +{ + struct listnode *node, *nnode, *node_to_del; + struct ecommunity *ecom; + + /* Along the lines of "configure" except we have to reset to the + * automatic value. + */ + if (is_vni_live(vpn)) + bgp_evpn_uninstall_routes(bgp, vpn); + + /* Cleanup the RT to VNI mapping and get rid of existing import RT. */ + bgp_evpn_unmap_vni_from_its_rts(bgp, vpn); + + /* Delete all import RTs */ + if (ecomdel == NULL) { + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + ecommunity_free(&ecom); + list_delete_node(vpn->import_rtl, node); + } + } + + /* Delete a specific import RT */ + else { + node_to_del = NULL; + + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, ecom)) { + if (ecommunity_match(ecom, ecomdel)) { + ecommunity_free(&ecom); + node_to_del = node; + break; + } + } + + if (node_to_del) + list_delete_node(vpn->import_rtl, node_to_del); + } + + assert(vpn->import_rtl); + /* Reset to auto RT - this also rebuilds the RT to VNI mapping */ + if (list_isempty(vpn->import_rtl)) { + UNSET_FLAG(vpn->flags, VNI_FLAG_IMPRT_CFGD); + bgp_evpn_derive_auto_rt_import(bgp, vpn); + } + /* Rebuild the RT to VNI mapping */ + else + bgp_evpn_map_vni_to_its_rts(bgp, vpn); + + /* Install routes that match new import RT */ + if (is_vni_live(vpn)) + bgp_evpn_install_routes(bgp, vpn); +} + +/* + * Configure the Export RT for a VNI (vty handler). Caller expected to + * check that this is a change. Note that only a single export RT is + * allowed for a VNI and any change to configuration is implemented as + * a "replace" (similar to other configuration). + */ +static void evpn_configure_export_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct ecommunity *ecomadd) +{ + /* If the auto route-target is in use we must remove it */ + evpn_export_rt_delete_auto(bgp, vpn); + + listnode_add_sort(vpn->export_rtl, ecomadd); + SET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); + + if (is_vni_live(vpn)) + bgp_evpn_handle_export_rt_change(bgp, vpn); +} + +/* + * Unconfigure the Export RT for a VNI (vty handler) + */ +static void evpn_unconfigure_export_rt(struct bgp *bgp, struct bgpevpn *vpn, + struct ecommunity *ecomdel) +{ + struct listnode *node, *nnode, *node_to_del; + struct ecommunity *ecom; + + /* Delete all export RTs */ + if (ecomdel == NULL) { + /* Reset to default and process all routes. */ + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { + ecommunity_free(&ecom); + list_delete_node(vpn->export_rtl, node); + } + } + + /* Delete a specific export RT */ + else { + node_to_del = NULL; + + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, ecom)) { + if (ecommunity_match(ecom, ecomdel)) { + ecommunity_free(&ecom); + node_to_del = node; + break; + } + } + + if (node_to_del) + list_delete_node(vpn->export_rtl, node_to_del); + } + + assert(vpn->export_rtl); + if (list_isempty(vpn->export_rtl)) { + UNSET_FLAG(vpn->flags, VNI_FLAG_EXPRT_CFGD); + bgp_evpn_derive_auto_rt_export(bgp, vpn); + } + + if (is_vni_live(vpn)) + bgp_evpn_handle_export_rt_change(bgp, vpn); +} + +/* + * Configure RD for VRF + */ +static void evpn_configure_vrf_rd(struct bgp *bgp_vrf, struct prefix_rd *rd, + const char *rd_pretty) +{ + /* If we have already advertise type-5 routes with a diffrent RD, we + * have to delete and withdraw them firs + */ + bgp_evpn_handle_vrf_rd_change(bgp_vrf, 1); + + if (bgp_vrf->vrf_prd_pretty) + XFREE(MTYPE_BGP_NAME, bgp_vrf->vrf_prd_pretty); + + /* update RD */ + memcpy(&bgp_vrf->vrf_prd, rd, sizeof(struct prefix_rd)); + bgp_vrf->vrf_prd_pretty = XSTRDUP(MTYPE_BGP_NAME, rd_pretty); + SET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD); + + /* We have a new RD for VRF. + * Advertise all type-5 routes again with the new RD + */ + bgp_evpn_handle_vrf_rd_change(bgp_vrf, 0); +} + +/* + * Unconfigure RD for VRF + */ +static void evpn_unconfigure_vrf_rd(struct bgp *bgp_vrf) +{ + /* If we have already advertise type-5 routes with a diffrent RD, we + * have to delete and withdraw them firs + */ + bgp_evpn_handle_vrf_rd_change(bgp_vrf, 1); + + /* fall back to default RD */ + bgp_evpn_derive_auto_rd_for_vrf(bgp_vrf); + UNSET_FLAG(bgp_vrf->vrf_flags, BGP_VRF_RD_CFGD); + if (bgp_vrf->vrf_prd_pretty) + XFREE(MTYPE_BGP_NAME, bgp_vrf->vrf_prd_pretty); + /* We have a new RD for VRF. + * Advertise all type-5 routes again with the new RD + */ + bgp_evpn_handle_vrf_rd_change(bgp_vrf, 0); +} + +/* + * Configure RD for a VNI (vty handler) + */ +static void evpn_configure_rd(struct bgp *bgp, struct bgpevpn *vpn, + struct prefix_rd *rd, const char *rd_pretty) +{ + /* If the VNI is "live", we need to delete and withdraw this VNI's + * local routes with the prior RD first. Then, after updating RD, + * need to re-advertise. + */ + if (is_vni_live(vpn)) + bgp_evpn_handle_rd_change(bgp, vpn, 1); + + /* update RD */ + memcpy(&vpn->prd, rd, sizeof(struct prefix_rd)); + vpn->prd_pretty = XSTRDUP(MTYPE_BGP_NAME, rd_pretty); + SET_FLAG(vpn->flags, VNI_FLAG_RD_CFGD); + + if (is_vni_live(vpn)) + bgp_evpn_handle_rd_change(bgp, vpn, 0); +} + +/* + * Unconfigure RD for a VNI (vty handler) + */ +static void evpn_unconfigure_rd(struct bgp *bgp, struct bgpevpn *vpn) +{ + /* If the VNI is "live", we need to delete and withdraw this VNI's + * local routes with the prior RD first. Then, after resetting RD + * to automatic value, need to re-advertise. + */ + if (is_vni_live(vpn)) + bgp_evpn_handle_rd_change(bgp, vpn, 1); + + /* reset RD to default */ + bgp_evpn_derive_auto_rd(bgp, vpn); + + if (is_vni_live(vpn)) + bgp_evpn_handle_rd_change(bgp, vpn, 0); +} + +/* + * Create VNI, if not already present (VTY handler). Mark as configured. + */ +static struct bgpevpn *evpn_create_update_vni(struct bgp *bgp, vni_t vni) +{ + struct bgpevpn *vpn; + struct in_addr mcast_grp = {INADDR_ANY}; + + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + /* Check if this L2VNI is already configured as L3VNI */ + if (bgp_evpn_lookup_l3vni_l2vni_table(vni)) { + flog_err( + EC_BGP_VNI, + "%u: Failed to create L2VNI %u, it is configured as L3VNI", + bgp->vrf_id, vni); + return NULL; + } + + /* tenant vrf will be updated when we get local_vni_add from + * zebra + */ + vpn = bgp_evpn_new(bgp, vni, bgp->router_id, 0, mcast_grp, 0); + } + + /* Mark as configured. */ + SET_FLAG(vpn->flags, VNI_FLAG_CFGD); + return vpn; +} + +/* + * Delete VNI. If VNI does not exist in the system (i.e., just + * configuration), all that is needed is to free it. Otherwise, + * any parameters configured for the VNI need to be reset (with + * appropriate action) and the VNI marked as unconfigured; the + * VNI will continue to exist, purely as a "learnt" entity. + */ +static void evpn_delete_vni(struct bgp *bgp, struct bgpevpn *vpn) +{ + if (!is_vni_live(vpn)) { + bgp_evpn_free(bgp, vpn); + return; + } + + /* We need to take the unconfigure action for each parameter of this VNI + * that is configured. Some optimization is possible, but not worth the + * additional code for an operation that should be pretty rare. + */ + UNSET_FLAG(vpn->flags, VNI_FLAG_CFGD); + + /* First, deal with the export side - RD and export RT changes. */ + if (is_rd_configured(vpn)) + evpn_unconfigure_rd(bgp, vpn); + if (is_export_rt_configured(vpn)) + evpn_unconfigure_export_rt(bgp, vpn, NULL); + + /* Next, deal with the import side. */ + if (is_import_rt_configured(vpn)) + evpn_unconfigure_import_rt(bgp, vpn, NULL); +} + +/* + * Display import RT mapping to VRFs (vty handler) + * bgp_evpn: evpn bgp instance + */ +static void evpn_show_vrf_import_rts(struct vty *vty, struct bgp *bgp_evpn, + json_object *json) +{ + void *args[2]; + + args[0] = vty; + args[1] = json; + + hash_iterate(bgp_evpn->vrf_import_rt_hash, + (void (*)(struct hash_bucket *, + void *))show_vrf_import_rt_entry, + args); +} + +/* + * Display import RT mapping to VNIs (vty handler) + */ +static void evpn_show_import_rts(struct vty *vty, struct bgp *bgp, + json_object *json) +{ + void *args[2]; + + args[0] = vty; + args[1] = json; + + hash_iterate( + bgp->import_rt_hash, + (void (*)(struct hash_bucket *, void *))show_import_rt_entry, + args); +} + +/* + * Display EVPN routes for all VNIs - vty handler. + */ +static void evpn_show_routes_vni_all(struct vty *vty, struct bgp *bgp, int type, + bool mac_table, struct in_addr vtep_ip, + json_object *json, int detail) +{ + uint32_t num_vnis; + struct vni_walk_ctx wctx; + + num_vnis = hashcount(bgp->vnihash); + if (!num_vnis) + return; + memset(&wctx, 0, sizeof(wctx)); + wctx.bgp = bgp; + wctx.vty = vty; + wctx.type = type; + wctx.mac_table = mac_table; + wctx.vtep_ip = vtep_ip; + wctx.json = json; + wctx.detail = detail; + hash_iterate(bgp->vnihash, (void (*)(struct hash_bucket *, + void *))show_vni_routes_hash, + &wctx); +} + +/* + * Display EVPN routes for all VNIs & all types - vty handler. + */ +static void evpn_show_routes_vni_all_type_all(struct vty *vty, struct bgp *bgp, + struct in_addr vtep_ip, + json_object *json, int detail) +{ + uint32_t num_vnis; + struct vni_walk_ctx wctx; + + num_vnis = hashcount(bgp->vnihash); + if (!num_vnis) + return; + + memset(&wctx, 0, sizeof(struct vni_walk_ctx)); + wctx.bgp = bgp; + wctx.vty = vty; + wctx.vtep_ip = vtep_ip; + wctx.json = json; + wctx.detail = detail; + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, + void *))show_vni_routes_all_hash, + &wctx); +} + +/* + * Display EVPN routes for a VNI -- for specific type-3 route (vty handler). + */ +static void evpn_show_route_vni_multicast(struct vty *vty, struct bgp *bgp, + vni_t vni, struct in_addr orig_ip, + json_object *json) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + struct bgp_dest *dest; + struct bgp_path_info *pi; + uint32_t path_cnt = 0; + afi_t afi; + safi_t safi; + json_object *json_paths = NULL; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Locate VNI. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + vty_out(vty, "VNI not found\n"); + return; + } + + /* See if route exists. */ + build_evpn_type3_prefix(&p, orig_ip); + dest = bgp_evpn_vni_node_lookup(vpn, &p, NULL); + if (!dest || !bgp_dest_has_bgp_path_info_data(dest)) { + if (!json) + vty_out(vty, "%% Network not in table\n"); + + if (dest) + bgp_dest_unlock_node(dest); + + return; + } + + if (json) + json_paths = json_object_new_array(); + + /* Prefix and num paths displayed once per prefix. */ + route_vty_out_detail_header(vty, bgp, dest, bgp_dest_get_prefix(dest), + NULL, afi, safi, json, false); + + /* Display each path for this prefix. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + json_object *json_path = NULL; + + if (json) + json_path = json_object_new_array(); + + route_vty_out_detail(vty, bgp, dest, bgp_dest_get_prefix(dest), + pi, afi, safi, RPKI_NOT_BEING_USED, + json_path); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + } + + if (json) { + if (path_cnt) + json_object_object_add(json, "paths", json_paths); + + json_object_int_add(json, "numPaths", path_cnt); + } else { + vty_out(vty, "\nDisplayed %u paths for requested prefix\n", + path_cnt); + } + + bgp_dest_unlock_node(dest); +} + +/* + * Display EVPN routes for a VNI -- for specific MAC and/or IP (vty handler). + * By definition, only matching type-2 route will be displayed. + */ +static void evpn_show_route_vni_macip(struct vty *vty, struct bgp *bgp, + vni_t vni, struct ethaddr *mac, + struct ipaddr *ip, json_object *json) +{ + struct bgpevpn *vpn; + struct prefix_evpn p; + struct prefix_evpn tmp_p; + struct bgp_dest *dest; + struct bgp_path_info *pi; + uint32_t path_cnt = 0; + afi_t afi; + safi_t safi; + json_object *json_paths = NULL; + struct ethaddr empty_mac = {}; + struct ipaddr empty_ip = {}; + const struct prefix_evpn *evp; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* Locate VNI. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + if (!json) + vty_out(vty, "VNI not found\n"); + return; + } + + build_evpn_type2_prefix(&p, mac ? mac : &empty_mac, + ip ? ip : &empty_ip); + + /* See if route exists. Look for both non-sticky and sticky. */ + dest = bgp_evpn_vni_node_lookup(vpn, &p, NULL); + if (!dest || !bgp_dest_has_bgp_path_info_data(dest)) { + if (!json) + vty_out(vty, "%% Network not in table\n"); + + if (dest) + bgp_dest_unlock_node(dest); + + return; + } + + /* + * MAC is per-path, we have to walk the path_info's and look for it + * first here. + */ + if (ip && mac) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (memcmp(mac, evpn_type2_path_info_get_mac(pi), + sizeof(*mac)) == 0) + break; + } + + if (!pi) { + if (!json) + vty_out(vty, "%% Network not in table\n"); + return; + } + } + + if (json) + json_paths = json_object_new_array(); + + /* Prefix and num paths displayed once per prefix. */ + route_vty_out_detail_header(vty, bgp, dest, (struct prefix *)&p, NULL, + afi, safi, json, false); + + evp = (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + + /* Display each path for this prefix. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + json_object *json_path = NULL; + + /* skip non-matching MACs */ + if (ip && mac && + memcmp(mac, evpn_type2_path_info_get_mac(pi), + sizeof(*mac)) != 0) + continue; + + if (json) + json_path = json_object_new_array(); + + /* + * VNI table MAC-IP prefixes don't have MAC so + * make sure it's set from path info + * here. + */ + if (is_evpn_prefix_ipaddr_none(evp)) { + /* VNI MAC -> Global */ + evpn_type2_prefix_global_copy(&tmp_p, evp, + NULL /* mac */, + evpn_type2_path_info_get_ip( + pi)); + } else { + /* VNI IP -> Global */ + evpn_type2_prefix_global_copy(&tmp_p, evp, + evpn_type2_path_info_get_mac( + pi), + NULL /* ip */); + } + + route_vty_out_detail(vty, bgp, dest, (struct prefix *)&tmp_p, + pi, afi, safi, RPKI_NOT_BEING_USED, + json_path); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + } + + if (json) { + if (path_cnt) + json_object_object_add(json, "paths", json_paths); + + json_object_int_add(json, "numPaths", path_cnt); + } else { + vty_out(vty, "\nDisplayed %u paths for requested prefix\n", + path_cnt); + } + + bgp_dest_unlock_node(dest); +} + +/* Disaplay EVPN routes for a ESI - VTY handler */ +static void evpn_show_routes_esi(struct vty *vty, struct bgp *bgp, + esi_t *esi, json_object *json) +{ + struct bgp_evpn_es *es = NULL; + + /* locate the ES */ + es = bgp_evpn_es_find(esi); + if (!es) { + if (!json) + vty_out(vty, "ESI not found\n"); + return; + } + + show_esi_routes(bgp, es, vty, json); +} + +/* + * Display EVPN routes for a VNI - vty handler. + * If 'type' is non-zero, only routes matching that type are shown. + * If the vtep_ip is non zero, only routes behind that vtep are shown + */ +static void evpn_show_routes_vni(struct vty *vty, struct bgp *bgp, vni_t vni, + int type, bool mac_table, + struct in_addr vtep_ip, json_object *json) +{ + struct bgpevpn *vpn; + + /* Locate VNI. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + if (!json) + vty_out(vty, "VNI not found\n"); + return; + } + + /* Walk this VNI's route table and display appropriate routes. */ + show_vni_routes(bgp, vpn, vty, type, mac_table, vtep_ip, json, 0); +} + +/* + * Display BGP EVPN routing table -- for specific RD and MAC and/or + * IP (vty handler). By definition, only matching type-2 route will be + * displayed. + */ +static void evpn_show_route_rd_macip(struct vty *vty, struct bgp *bgp, + struct prefix_rd *prd, struct ethaddr *mac, + struct ipaddr *ip, json_object *json) +{ + struct prefix_evpn p; + struct bgp_dest *dest; + struct bgp_path_info *pi; + afi_t afi; + safi_t safi; + uint32_t path_cnt = 0; + json_object *json_paths = NULL; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + + /* See if route exists. Look for both non-sticky and sticky. */ + build_evpn_type2_prefix(&p, mac, ip); + dest = bgp_safi_node_lookup(bgp->rib[afi][safi], safi, + (struct prefix *)&p, prd); + if (!dest || !bgp_dest_has_bgp_path_info_data(dest)) { + if (!json) + vty_out(vty, "%% Network not in table\n"); + + if (dest) + bgp_dest_unlock_node(dest); + + return; + } + + /* Prefix and num paths displayed once per prefix. */ + route_vty_out_detail_header(vty, bgp, dest, bgp_dest_get_prefix(dest), + prd, afi, safi, json, false); + + if (json) + json_paths = json_object_new_array(); + + /* Display each path for this prefix. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + json_object *json_path = NULL; + + if (json) + json_path = json_object_new_array(); + + route_vty_out_detail(vty, bgp, dest, bgp_dest_get_prefix(dest), + pi, afi, safi, RPKI_NOT_BEING_USED, + json_path); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + } + + if (json && path_cnt) { + if (path_cnt) + json_object_object_addf(json, json_paths, "%pFX", &p); + json_object_int_add(json, "numPaths", path_cnt); + } else { + vty_out(vty, "\nDisplayed %u paths for requested prefix\n", + path_cnt); + } + + bgp_dest_unlock_node(dest); +} + +/* + * Display BGP EVPN routing table -- for specific RD (vty handler) + * If 'type' is non-zero, only routes matching that type are shown. + */ +static void evpn_show_route_rd(struct vty *vty, struct bgp *bgp, + struct prefix_rd *prd, int type, + json_object *json) +{ + struct bgp_dest *rd_dest; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + int rd_header = 1; + afi_t afi; + safi_t safi; + uint32_t prefix_cnt, path_cnt; + json_object *json_rd = NULL; + int add_rd_to_json = 0; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + prefix_cnt = path_cnt = 0; + + rd_dest = bgp_node_lookup(bgp->rib[afi][safi], (struct prefix *)prd); + if (!rd_dest) + return; + + table = bgp_dest_get_bgp_table_info(rd_dest); + if (table == NULL) { + bgp_dest_unlock_node(rd_dest); + return; + } + + if (json) { + json_rd = json_object_new_object(); + json_object_string_addf(json_rd, "rd", + BGP_RD_AS_FORMAT(bgp->asnotation), prd); + } + + bgp_dest_unlock_node(rd_dest); + + /* Display all prefixes with this RD. */ + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix(dest); + json_object *json_prefix = NULL; + json_object *json_paths = NULL; + int add_prefix_to_json = 0; + + if (type && evp->prefix.route_type != type) + continue; + + if (json) + json_prefix = json_object_new_object(); + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi) { + /* RD header and legend - once overall. */ + if (rd_header && !json) { + vty_out(vty, + "EVPN type-1 prefix: [1]:[EthTag]:[ESI]:[IPlen]:[VTEP-IP]:[Frag-id]\n"); + vty_out(vty, + "EVPN type-2 prefix: [2]:[EthTag]:[MAClen]:[MAC]\n"); + vty_out(vty, + "EVPN type-3 prefix: [3]:[EthTag]:[IPlen]:[OrigIP]\n"); + vty_out(vty, + "EVPN type-4 prefix: [4]:[ESI]:[IPlen]:[OrigIP]\n"); + vty_out(vty, + "EVPN type-5 prefix: [5]:[EthTag]:[IPlen]:[IP]\n\n"); + rd_header = 0; + } + + /* Prefix and num paths displayed once per prefix. */ + route_vty_out_detail_header( + vty, bgp, dest, bgp_dest_get_prefix(dest), prd, + afi, safi, json_prefix, false); + + prefix_cnt++; + } + + if (json) + json_paths = json_object_new_array(); + + /* Display each path for this prefix. */ + for (; pi; pi = pi->next) { + json_object *json_path = NULL; + + if (json) + json_path = json_object_new_array(); + + route_vty_out_detail( + vty, bgp, dest, bgp_dest_get_prefix(dest), pi, + afi, safi, RPKI_NOT_BEING_USED, json_path); + + if (json) + json_object_array_add(json_paths, json_path); + + path_cnt++; + add_prefix_to_json = 1; + add_rd_to_json = 1; + } + + if (json) { + if (add_prefix_to_json) { + json_object_object_add(json_prefix, "paths", + json_paths); + json_object_object_addf(json_rd, json_prefix, + "%pFX", evp); + } else { + json_object_free(json_paths); + json_object_free(json_prefix); + json_paths = NULL; + json_prefix = NULL; + } + } + } + + if (json) { + if (add_rd_to_json) + json_object_object_addf( + json, json_rd, + BGP_RD_AS_FORMAT(bgp->asnotation), prd); + else { + json_object_free(json_rd); + json_rd = NULL; + } + + json_object_int_add(json, "numPrefix", prefix_cnt); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (prefix_cnt == 0) + vty_out(vty, "No prefixes exist with this RD%s\n", + type ? " (of requested type)" : ""); + else + vty_out(vty, + "\nDisplayed %u prefixes (%u paths) with this RD%s\n", + prefix_cnt, path_cnt, + type ? " (of requested type)" : ""); + } +} + +/* + * Display BGP EVPN routing table -- all RDs and MAC and/or IP + * (vty handler). Only matching type-2 routes will be displayed. + */ +static void evpn_show_route_rd_all_macip(struct vty *vty, struct bgp *bgp, + struct ethaddr *mac, struct ipaddr *ip, + json_object *json) +{ + struct bgp_dest *rd_dest; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + afi_t afi = AFI_L2VPN; + safi_t safi = SAFI_EVPN; + uint32_t prefix_cnt, path_cnt; + prefix_cnt = path_cnt = 0; + + /* EVPN routing table is a 2-level table with the first level being + * the RD. We need to look in every RD we know about. + */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + json_object *json_paths = NULL; /* paths array for prefix */ + json_object *json_prefix = NULL; /* prefix within an RD */ + json_object *json_rd = NULL; /* holds all prefixes for RD */ + char rd_str[RD_ADDRSTRLEN]; + int add_rd_to_json = 0; + struct prefix_evpn ep; + const struct prefix *rd_destp = bgp_dest_get_prefix(rd_dest); + + table = bgp_dest_get_bgp_table_info(rd_dest); + if (table == NULL) + continue; + + prefix_rd2str((struct prefix_rd *)rd_destp, rd_str, + sizeof(rd_str), bgp->asnotation); + + /* Construct an RT-2 from the user-supplied mac(ip), + * then search the l2vpn evpn table for it. + */ + build_evpn_type2_prefix(&ep, mac, ip); + dest = bgp_safi_node_lookup(bgp->rib[afi][safi], safi, + (struct prefix *)&ep, + (struct prefix_rd *)rd_destp); + if (!dest) + continue; + + if (json) + json_rd = json_object_new_object(); + + const struct prefix *p = bgp_dest_get_prefix(dest); + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi) { + /* RD header - per RD. */ + bgp_evpn_show_route_rd_header(vty, rd_dest, json_rd, + rd_str, RD_ADDRSTRLEN); + prefix_cnt++; + } + + if (json) { + json_prefix = json_object_new_object(); + json_paths = json_object_new_array(); + json_object_string_addf(json_prefix, "prefix", "%pFX", + p); + json_object_int_add(json_prefix, "prefixLen", + p->prefixlen); + } else + /* Prefix and num paths displayed once per prefix. */ + route_vty_out_detail_header( + vty, bgp, dest, p, (struct prefix_rd *)rd_destp, + AFI_L2VPN, SAFI_EVPN, json_prefix, false); + + /* For EVPN, the prefix is displayed for each path (to + * fit in with code that already exists). + */ + for (; pi; pi = pi->next) { + json_object *json_path = NULL; + + add_rd_to_json = 1; + path_cnt++; + + if (json) + json_path = json_object_new_array(); + + route_vty_out_detail(vty, bgp, dest, p, pi, AFI_L2VPN, + SAFI_EVPN, RPKI_NOT_BEING_USED, + json_path); + + if (json) + json_object_array_add(json_paths, json_path); + else + vty_out(vty, "\n"); + } + + if (json) { + json_object_object_add(json_prefix, "paths", + json_paths); + json_object_object_addf(json_rd, json_prefix, "%pFX", + p); + if (add_rd_to_json) + json_object_object_add(json, rd_str, json_rd); + else { + json_object_free(json_rd); + json_rd = NULL; + } + } + + bgp_dest_unlock_node(dest); + } + + if (json) { + json_object_int_add(json, "numPrefix", prefix_cnt); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (prefix_cnt == 0) { + vty_out(vty, "No Matching EVPN prefixes exist\n"); + } else { + vty_out(vty, "Displayed %u prefixes (%u paths)\n", + prefix_cnt, path_cnt); + } + } +} + +/* + * Display BGP EVPN routing table - all routes (vty handler). + * If 'type' is non-zero, only routes matching that type are shown. + */ +static void evpn_show_all_routes(struct vty *vty, struct bgp *bgp, int type, + json_object *json, int detail, bool self_orig) +{ + struct bgp_dest *rd_dest; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + int header = detail ? 0 : 1; + int rd_header; + afi_t afi; + safi_t safi; + uint32_t prefix_cnt, path_cnt; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + prefix_cnt = path_cnt = 0; + + /* EVPN routing table is a 2-level table with the first level being + * the RD. + */ + for (rd_dest = bgp_table_top(bgp->rib[afi][safi]); rd_dest; + rd_dest = bgp_route_next(rd_dest)) { + char rd_str[RD_ADDRSTRLEN]; + json_object *json_rd = NULL; /* contains routes for an RD */ + int add_rd_to_json = 0; + uint64_t tbl_ver; + const struct prefix *rd_destp = bgp_dest_get_prefix(rd_dest); + + table = bgp_dest_get_bgp_table_info(rd_dest); + if (table == NULL) + continue; + + tbl_ver = table->version; + prefix_rd2str((struct prefix_rd *)rd_destp, rd_str, + sizeof(rd_str), bgp->asnotation); + + if (json) + json_rd = json_object_new_object(); + + rd_header = 1; + + /* Display all prefixes for an RD */ + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + json_object *json_prefix = + NULL; /* contains prefix under a RD */ + json_object *json_paths = + NULL; /* array of paths under a prefix*/ + const struct prefix_evpn *evp = + (const struct prefix_evpn *)bgp_dest_get_prefix( + dest); + int add_prefix_to_json = 0; + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (type && evp->prefix.route_type != type) + continue; + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi) { + if (self_orig && (pi->peer != bgp->peer_self)) + continue; + + /* Overall header/legend displayed once. */ + if (header) { + bgp_evpn_show_route_header(vty, bgp, + tbl_ver, + json); + if (!json) + vty_out(vty, + "%19s Extended Community\n" + , " "); + header = 0; + } + + /* RD header - per RD. */ + if (rd_header) { + bgp_evpn_show_route_rd_header( + vty, rd_dest, json_rd, rd_str, + RD_ADDRSTRLEN); + rd_header = 0; + } + + prefix_cnt++; + } + + if (json) { + json_prefix = json_object_new_object(); + json_paths = json_object_new_array(); + json_object_string_addf(json_prefix, "prefix", + "%pFX", p); + json_object_int_add(json_prefix, "prefixLen", + p->prefixlen); + } + + /* Prefix and num paths displayed once per prefix. */ + if (detail) + route_vty_out_detail_header( + vty, bgp, dest, + bgp_dest_get_prefix(dest), + (struct prefix_rd *)rd_destp, AFI_L2VPN, + SAFI_EVPN, json_prefix, false); + + /* For EVPN, the prefix is displayed for each path (to + * fit in + * with code that already exists). + */ + for (; pi; pi = pi->next) { + json_object *json_path = NULL; + + path_cnt++; + add_prefix_to_json = 1; + add_rd_to_json = 1; + + if (json) + json_path = json_object_new_array(); + + if (detail) { + route_vty_out_detail( + vty, bgp, dest, + bgp_dest_get_prefix(dest), pi, + AFI_L2VPN, SAFI_EVPN, + RPKI_NOT_BEING_USED, json_path); + } else + route_vty_out(vty, p, pi, 0, SAFI_EVPN, + json_path, false); + + if (json) + json_object_array_add(json_paths, + json_path); + } + + if (json) { + if (add_prefix_to_json) { + json_object_object_add(json_prefix, + "paths", + json_paths); + json_object_object_addf(json_rd, + json_prefix, + "%pFX", p); + } else { + json_object_free(json_prefix); + json_object_free(json_paths); + json_prefix = NULL; + json_paths = NULL; + } + } + } + + if (json) { + if (add_rd_to_json) + json_object_object_add(json, rd_str, json_rd); + else { + json_object_free(json_rd); + json_rd = NULL; + } + } + } + + if (json) { + json_object_int_add(json, "numPrefix", prefix_cnt); + json_object_int_add(json, "numPaths", path_cnt); + } else { + if (prefix_cnt == 0) { + vty_out(vty, "No EVPN prefixes %sexist\n", + type ? "(of requested type) " : ""); + } else { + vty_out(vty, "\nDisplayed %u prefixes (%u paths)%s\n", + prefix_cnt, path_cnt, + type ? " (of requested type)" : ""); + } + } +} + +int bgp_evpn_show_all_routes(struct vty *vty, struct bgp *bgp, int type, + bool use_json, int detail) +{ + json_object *json = NULL; + + if (use_json) + json = json_object_new_object(); + + evpn_show_all_routes(vty, bgp, type, json, detail, false); + + if (use_json) + /* + * We are using no_pretty here because under extremely high + * settings (lots of routes with many different paths) this can + * save several minutes of output when FRR is run on older cpu's + * or more underperforming routers out there. So for route + * scale, we need to use no_pretty json. + */ + vty_json_no_pretty(vty, json); + return CMD_SUCCESS; +} + +/* + * Display specified VNI (vty handler) + */ +static void evpn_show_vni(struct vty *vty, struct bgp *bgp, vni_t vni, + json_object *json) +{ + uint8_t found = 0; + struct bgpevpn *vpn; + + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (vpn) { + found = 1; + display_vni(vty, vpn, json); + } else { + struct bgp *bgp_temp; + struct listnode *node = NULL; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) { + if (bgp_temp->l3vni == vni) { + found = 1; + display_l3vni(vty, bgp_temp, json); + } + } + } + + if (!found) { + if (json) { + vty_out(vty, "{}\n"); + } else { + vty_out(vty, "VNI not found\n"); + return; + } + } +} + +/* + * Display a VNI (upon user query). + */ +static void evpn_show_all_vnis(struct vty *vty, struct bgp *bgp, + json_object *json) +{ + void *args[2]; + struct bgp *bgp_temp = NULL; + struct listnode *node; + + + if (!json) { + vty_out(vty, "Flags: * - Kernel\n"); + vty_out(vty, " %-10s %-4s %-21s %-25s %-25s %-25s %-37s\n", + "VNI", "Type", "RD", "Import RT", "Export RT", + "MAC-VRF Site-of-Origin", "Tenant VRF"); + } + + /* print all L2 VNIS */ + args[0] = vty; + args[1] = json; + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, void *))show_vni_entry, + args); + + /* print all L3 VNIs */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) + show_l3vni_entry(vty, bgp_temp, json); +} + +/* + * evpn - enable advertisement of svi MAC-IP + */ +static void evpn_set_advertise_svi_macip(struct bgp *bgp, struct bgpevpn *vpn, + uint32_t set) +{ + if (!vpn) { + if (set && bgp->evpn_info->advertise_svi_macip) + return; + else if (!set && !bgp->evpn_info->advertise_svi_macip) + return; + + bgp->evpn_info->advertise_svi_macip = set; + bgp_zebra_advertise_svi_macip(bgp, + bgp->evpn_info->advertise_svi_macip, 0); + } else { + if (set && vpn->advertise_svi_macip) + return; + else if (!set && !vpn->advertise_svi_macip) + return; + + vpn->advertise_svi_macip = set; + bgp_zebra_advertise_svi_macip(bgp, vpn->advertise_svi_macip, + vpn->vni); + } +} + +/* + * evpn - enable advertisement of default g/w + */ +static void evpn_set_advertise_default_gw(struct bgp *bgp, struct bgpevpn *vpn) +{ + if (!vpn) { + if (bgp->advertise_gw_macip) + return; + + bgp->advertise_gw_macip = 1; + bgp_zebra_advertise_gw_macip(bgp, bgp->advertise_gw_macip, 0); + } else { + if (vpn->advertise_gw_macip) + return; + + vpn->advertise_gw_macip = 1; + bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, + vpn->vni); + } + return; +} + +/* + * evpn - disable advertisement of default g/w + */ +static void evpn_unset_advertise_default_gw(struct bgp *bgp, + struct bgpevpn *vpn) +{ + if (!vpn) { + if (!bgp->advertise_gw_macip) + return; + + bgp->advertise_gw_macip = 0; + bgp_zebra_advertise_gw_macip(bgp, bgp->advertise_gw_macip, 0); + } else { + if (!vpn->advertise_gw_macip) + return; + + vpn->advertise_gw_macip = 0; + bgp_zebra_advertise_gw_macip(bgp, vpn->advertise_gw_macip, + vpn->vni); + } + return; +} + +/* + * evpn - enable advertisement of default g/w + */ +static void evpn_process_default_originate_cmd(struct bgp *bgp_vrf, + afi_t afi, bool add) +{ + safi_t safi = SAFI_UNICAST; /* ipv4/ipv6 unicast */ + + if (add) { + /* bail if we are already advertising default route */ + if (evpn_default_originate_set(bgp_vrf, afi, safi)) + return; + + if (afi == AFI_IP) + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4); + else if (afi == AFI_IP6) + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6); + } else { + /* bail out if we havent advertised the default route */ + if (!evpn_default_originate_set(bgp_vrf, afi, safi)) + return; + if (afi == AFI_IP) + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4); + else if (afi == AFI_IP6) + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6); + } + + bgp_evpn_install_uninstall_default_route(bgp_vrf, afi, safi, add); +} + +/* + * evpn - enable advertisement of default g/w + */ +static void evpn_set_advertise_subnet(struct bgp *bgp, + struct bgpevpn *vpn) +{ + if (vpn->advertise_subnet) + return; + + vpn->advertise_subnet = 1; + bgp_zebra_advertise_subnet(bgp, vpn->advertise_subnet, vpn->vni); +} + +/* + * evpn - disable advertisement of default g/w + */ +static void evpn_unset_advertise_subnet(struct bgp *bgp, struct bgpevpn *vpn) +{ + if (!vpn->advertise_subnet) + return; + + vpn->advertise_subnet = 0; + bgp_zebra_advertise_subnet(bgp, vpn->advertise_subnet, vpn->vni); +} + +/* + * EVPN (VNI advertisement) enabled. Register with zebra. + */ +static void evpn_set_advertise_all_vni(struct bgp *bgp) +{ + bgp->advertise_all_vni = 1; + bgp_set_evpn(bgp); + bgp_zebra_advertise_all_vni(bgp, bgp->advertise_all_vni); +} + +/* + * EVPN (VNI advertisement) disabled. De-register with zebra. Cleanup VNI + * cache, EVPN routes (delete and withdraw from peers). + */ +static void evpn_unset_advertise_all_vni(struct bgp *bgp) +{ + bgp->advertise_all_vni = 0; + bgp_set_evpn(bgp_get_default()); + bgp_zebra_advertise_all_vni(bgp, bgp->advertise_all_vni); + bgp_evpn_cleanup_on_disable(bgp); +} + +/* Set resolve overlay index flag */ +static void bgp_evpn_set_unset_resolve_overlay_index(struct bgp *bgp, bool set) +{ + if (set == bgp->resolve_overlay_index) + return; + + if (set) { + bgp->resolve_overlay_index = true; + hash_iterate(bgp->vnihash, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_handle_resolve_overlay_index_set, + NULL); + } else { + hash_iterate( + bgp->vnihash, + (void (*)(struct hash_bucket *, void *)) + bgp_evpn_handle_resolve_overlay_index_unset, + NULL); + bgp->resolve_overlay_index = false; + } +} + +/* + * EVPN - use RFC8365 to auto-derive RT + */ +static void evpn_set_advertise_autort_rfc8365(struct bgp *bgp) +{ + bgp->advertise_autort_rfc8365 = 1; + bgp_evpn_handle_autort_change(bgp); +} + +/* + * EVPN - don't use RFC8365 to auto-derive RT + */ +static void evpn_unset_advertise_autort_rfc8365(struct bgp *bgp) +{ + bgp->advertise_autort_rfc8365 = 0; + bgp_evpn_handle_autort_change(bgp); +} + +static void write_vni_config(struct vty *vty, struct bgpevpn *vpn) +{ + char *ecom_str; + struct listnode *node, *nnode; + struct ecommunity *ecom; + + if (is_vni_configured(vpn)) { + vty_out(vty, " vni %u\n", vpn->vni); + if (is_rd_configured(vpn)) + vty_out(vty, " rd %s\n", vpn->prd_pretty); + + if (is_import_rt_configured(vpn)) { + for (ALL_LIST_ELEMENTS(vpn->import_rtl, node, nnode, + ecom)) { + ecom_str = ecommunity_ecom2str( + ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " route-target import %s\n", + ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } + } + + if (is_export_rt_configured(vpn)) { + for (ALL_LIST_ELEMENTS(vpn->export_rtl, node, nnode, + ecom)) { + ecom_str = ecommunity_ecom2str( + ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " route-target export %s\n", + ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } + } + + if (vpn->advertise_gw_macip) + vty_out(vty, " advertise-default-gw\n"); + + if (vpn->advertise_svi_macip) + vty_out(vty, " advertise-svi-ip\n"); + + if (vpn->advertise_subnet) + vty_out(vty, " advertise-subnet\n"); + + vty_out(vty, " exit-vni\n"); + } +} + +#include "bgpd/bgp_evpn_vty_clippy.c" + +DEFPY(bgp_evpn_flood_control, + bgp_evpn_flood_control_cmd, + "[no$no] flooding ", + NO_STR + "Specify handling for BUM packets\n" + "Do not flood any BUM packets\n" + "Flood BUM packets using head-end replication\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + enum vxlan_flood_control flood_ctrl; + + if (!bgp) + return CMD_WARNING; + + if (disable && !no) + flood_ctrl = VXLAN_FLOOD_DISABLED; + else if (her || no) + flood_ctrl = VXLAN_FLOOD_HEAD_END_REPL; + else + return CMD_WARNING; + + if (bgp->vxlan_flood_ctrl == flood_ctrl) + return CMD_SUCCESS; + + bgp->vxlan_flood_ctrl = flood_ctrl; + bgp_evpn_flood_control_change(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_advertise_default_gw_vni, + bgp_evpn_advertise_default_gw_vni_cmd, + "advertise-default-gw", + "Advertise default g/w mac-ip routes in EVPN for a VNI\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + evpn_set_advertise_default_gw(bgp, vpn); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_advertise_default_vni_gw, + no_bgp_evpn_advertise_default_gw_vni_cmd, + "no advertise-default-gw", + NO_STR + "Withdraw default g/w mac-ip routes from EVPN for a VNI\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + evpn_unset_advertise_default_gw(bgp, vpn); + + return CMD_SUCCESS; +} + + +DEFUN (bgp_evpn_advertise_default_gw, + bgp_evpn_advertise_default_gw_cmd, + "advertise-default-gw", + "Advertise All default g/w mac-ip routes in EVPN\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under the EVPN VRF\n"); + return CMD_WARNING; + } + + evpn_set_advertise_default_gw(bgp, NULL); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_advertise_default_gw, + no_bgp_evpn_advertise_default_gw_cmd, + "no advertise-default-gw", + NO_STR + "Withdraw All default g/w mac-ip routes from EVPN\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + + evpn_unset_advertise_default_gw(bgp, NULL); + + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_advertise_all_vni, + bgp_evpn_advertise_all_vni_cmd, + "advertise-all-vni", + "Advertise All local VNIs\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = NULL; + + if (!bgp) + return CMD_WARNING; + + bgp_evpn = bgp_get_evpn(); + if (bgp_evpn && bgp_evpn != bgp) { + vty_out(vty, "%% Please unconfigure EVPN in %s\n", + bgp_evpn->name_pretty); + return CMD_WARNING_CONFIG_FAILED; + } + + evpn_set_advertise_all_vni(bgp); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_advertise_all_vni, + no_bgp_evpn_advertise_all_vni_cmd, + "no advertise-all-vni", + NO_STR + "Advertise All local VNIs\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + evpn_unset_advertise_all_vni(bgp); + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_advertise_autort_rfc8365, + bgp_evpn_advertise_autort_rfc8365_cmd, + "autort rfc8365-compatible", + "Auto-derivation of RT\n" + "Auto-derivation of RT using RFC8365\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + evpn_set_advertise_autort_rfc8365(bgp); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_advertise_autort_rfc8365, + no_bgp_evpn_advertise_autort_rfc8365_cmd, + "no autort rfc8365-compatible", + NO_STR + "Auto-derivation of RT\n" + "Auto-derivation of RT using RFC8365\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + evpn_unset_advertise_autort_rfc8365(bgp); + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_default_originate, + bgp_evpn_default_originate_cmd, + "default-originate ", + "originate a default route\n" + "ipv4 address family\n" + "ipv6 address family\n") +{ + afi_t afi = 0; + int idx_afi = 0; + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); + evpn_process_default_originate_cmd(bgp_vrf, afi, true); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_default_originate, + no_bgp_evpn_default_originate_cmd, + "no default-originate ", + NO_STR + "withdraw a default route\n" + "ipv4 address family\n" + "ipv6 address family\n") +{ + afi_t afi = 0; + int idx_afi = 0; + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); + evpn_process_default_originate_cmd(bgp_vrf, afi, false); + return CMD_SUCCESS; +} + +DEFPY (dup_addr_detection, + dup_addr_detection_cmd, + "dup-addr-detection [max-moves (2-1000)$max_moves_val time (2-1800)$time_val]", + "Duplicate address detection\n" + "Max allowed moves before address detected as duplicate\n" + "Num of max allowed moves (2-1000) default 5\n" + "Duplicate address detection time\n" + "Time in seconds (2-1800) default 180\n") +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp_vrf)) { + vty_out(vty, + "This command is only supported under the EVPN VRF\n"); + return CMD_WARNING; + } + + bgp_vrf->evpn_info->dup_addr_detect = true; + + if (time_val) + bgp_vrf->evpn_info->dad_time = time_val; + if (max_moves_val) + bgp_vrf->evpn_info->dad_max_moves = max_moves_val; + + bgp_zebra_dup_addr_detection(bgp_vrf); + + return CMD_SUCCESS; +} + +DEFPY (dup_addr_detection_auto_recovery, + dup_addr_detection_auto_recovery_cmd, + "dup-addr-detection freeze ", + "Duplicate address detection\n" + "Duplicate address detection freeze\n" + "Duplicate address detection permanent freeze\n" + "Duplicate address detection freeze time (30-3600)\n") +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + uint32_t freeze_time = freeze_time_val; + + if (!bgp_vrf) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp_vrf)) { + vty_out(vty, + "This command is only supported under the EVPN VRF\n"); + return CMD_WARNING; + } + + bgp_vrf->evpn_info->dup_addr_detect = true; + bgp_vrf->evpn_info->dad_freeze = true; + bgp_vrf->evpn_info->dad_freeze_time = freeze_time; + + bgp_zebra_dup_addr_detection(bgp_vrf); + + return CMD_SUCCESS; +} + +DEFPY (no_dup_addr_detection, + no_dup_addr_detection_cmd, + "no dup-addr-detection [max-moves (2-1000)$max_moves_val time (2-1800)$time_val | freeze ]", + NO_STR + "Duplicate address detection\n" + "Max allowed moves before address detected as duplicate\n" + "Num of max allowed moves (2-1000) default 5\n" + "Duplicate address detection time\n" + "Time in seconds (2-1800) default 180\n" + "Duplicate address detection freeze\n" + "Duplicate address detection permanent freeze\n" + "Duplicate address detection freeze time (30-3600)\n") +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + uint32_t max_moves = (uint32_t)max_moves_val; + uint32_t freeze_time = (uint32_t)freeze_time_val; + + if (!bgp_vrf) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp_vrf)) { + vty_out(vty, + "This command is only supported under the EVPN VRF\n"); + return CMD_WARNING; + } + + if (argc == 2) { + if (!bgp_vrf->evpn_info->dup_addr_detect) + return CMD_SUCCESS; + /* Reset all parameters to default. */ + bgp_vrf->evpn_info->dup_addr_detect = false; + bgp_vrf->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; + bgp_vrf->evpn_info->dad_max_moves = EVPN_DAD_DEFAULT_MAX_MOVES; + bgp_vrf->evpn_info->dad_freeze = false; + bgp_vrf->evpn_info->dad_freeze_time = 0; + } else { + if (max_moves) { + if (bgp_vrf->evpn_info->dad_max_moves != max_moves) { + vty_out(vty, + "%% Value does not match with config\n"); + return CMD_SUCCESS; + } + bgp_vrf->evpn_info->dad_max_moves = + EVPN_DAD_DEFAULT_MAX_MOVES; + } + + if (time_val) { + if (bgp_vrf->evpn_info->dad_time != time_val) { + vty_out(vty, + "%% Value does not match with config\n"); + return CMD_SUCCESS; + } + bgp_vrf->evpn_info->dad_time = EVPN_DAD_DEFAULT_TIME; + } + + if (freeze_time) { + if (bgp_vrf->evpn_info->dad_freeze_time + != freeze_time) { + vty_out(vty, + "%% Value does not match with config\n"); + return CMD_SUCCESS; + } + bgp_vrf->evpn_info->dad_freeze_time = 0; + bgp_vrf->evpn_info->dad_freeze = false; + } + + if (permanent_val) { + if (bgp_vrf->evpn_info->dad_freeze_time) { + vty_out(vty, + "%% Value does not match with config\n"); + return CMD_SUCCESS; + } + bgp_vrf->evpn_info->dad_freeze = false; + } + } + + bgp_zebra_dup_addr_detection(bgp_vrf); + + return CMD_SUCCESS; +} + +DEFPY(bgp_evpn_advertise_svi_ip, + bgp_evpn_advertise_svi_ip_cmd, + "[no$no] advertise-svi-ip", + NO_STR + "Advertise svi mac-ip routes in EVPN\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING; + + if (no) + evpn_set_advertise_svi_macip(bgp, NULL, 0); + else { + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + evpn_set_advertise_svi_macip(bgp, NULL, 1); + } + + return CMD_SUCCESS; +} + +DEFPY(bgp_evpn_advertise_svi_ip_vni, + bgp_evpn_advertise_svi_ip_vni_cmd, + "[no$no] advertise-svi-ip", + NO_STR + "Advertise svi mac-ip routes in EVPN for a VNI\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + if (no) + evpn_set_advertise_svi_macip(bgp, vpn, 0); + else + evpn_set_advertise_svi_macip(bgp, vpn, 1); + + return CMD_SUCCESS; +} + +DEFPY(macvrf_soo_global, macvrf_soo_global_cmd, + "mac-vrf soo ASN:NN_OR_IP-ADDRESS:NN$soo", + "EVPN MAC-VRF\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = bgp_get_evpn(); + struct ecommunity *ecomm_soo; + + if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info) + return CMD_WARNING; + + if (bgp != bgp_evpn) { + vty_out(vty, + "%% Please configure MAC-VRF SoO in the EVPN underlay: %s\n", + bgp_evpn->name_pretty); + return CMD_WARNING_CONFIG_FAILED; + } + + ecomm_soo = ecommunity_str2com(soo, ECOMMUNITY_SITE_ORIGIN, 0); + if (!ecomm_soo) { + vty_out(vty, "%% Malformed SoO extended community\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ecommunity_str(ecomm_soo); + + bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn, ecomm_soo); + + return CMD_SUCCESS; +} + +DEFPY(no_macvrf_soo_global, no_macvrf_soo_global_cmd, + "no mac-vrf soo [ASN:NN_OR_IP-ADDRESS:NN$soo]", + NO_STR + "EVPN MAC-VRF\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgp *bgp_evpn = bgp_get_evpn(); + + if (!bgp || !bgp_evpn || !bgp_evpn->evpn_info) + return CMD_WARNING; + + if (bgp_evpn) + bgp_evpn_handle_global_macvrf_soo_change(bgp_evpn, + NULL /* new_soo */); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (bgp_evpn_advertise_vni_subnet, + bgp_evpn_advertise_vni_subnet_cmd, + "advertise-subnet", + "Advertise the subnet corresponding to VNI\n") +{ + struct bgp *bgp_vrf = NULL; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + bgp_vrf = bgp_lookup_by_vrf_id(vpn->tenant_vrf_id); + if (!bgp_vrf) + return CMD_WARNING; + + evpn_set_advertise_subnet(bgp, vpn); + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_bgp_evpn_advertise_vni_subnet, + no_bgp_evpn_advertise_vni_subnet_cmd, + "no advertise-subnet", + NO_STR + "Advertise All local VNIs\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + evpn_unset_advertise_subnet(bgp, vpn); + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_advertise_type5, + bgp_evpn_advertise_type5_cmd, + "advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR " [gateway-ip] [route-map RMAP_NAME]", + "Advertise prefix routes\n" + BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "advertise gateway IP overlay index\n" + "route-map for filtering specific routes\n" + "Name of the route map\n") +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */ + int idx_afi = 0; + int idx_safi = 0; + int idx_rmap = 0; + afi_t afi = 0; + safi_t safi = 0; + int ret = 0; + int rmap_changed = 0; + enum overlay_index_type oly = OVERLAY_INDEX_TYPE_NONE; + int idx_oly = 0; + bool adv_flag_changed = false; + + argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); + argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); + argv_find_and_parse_oly_idx(argv, argc, &idx_oly, &oly); + + ret = argv_find(argv, argc, "route-map", &idx_rmap); + if (ret) { + if (!bgp_vrf->adv_cmd_rmap[afi][safi].name) + rmap_changed = 1; + else if (strcmp(argv[idx_rmap + 1]->arg, + bgp_vrf->adv_cmd_rmap[afi][safi].name) + != 0) + rmap_changed = 1; + } else if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { + rmap_changed = 1; + } + + if (!(afi == AFI_IP || afi == AFI_IP6)) { + vty_out(vty, + "%% Only ipv4 or ipv6 address families are supported\n"); + return CMD_WARNING; + } + + if (safi != SAFI_UNICAST) { + vty_out(vty, + "%% Only ipv4 unicast or ipv6 unicast are supported\n"); + return CMD_WARNING; + } + + if ((oly != OVERLAY_INDEX_TYPE_NONE) + && (oly != OVERLAY_INDEX_GATEWAY_IP)) { + vty_out(vty, "%% Unknown overlay-index type specified\n"); + return CMD_WARNING; + } + + if (afi == AFI_IP) { + if ((!CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST)) + && (!CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP))) { + + /* + * this is the case for first time ever configuration + * adv ipv4 unicast is enabled for the first time. + * So no need to reset any flag + */ + if (oly == OVERLAY_INDEX_TYPE_NONE) + SET_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST); + else if (oly == OVERLAY_INDEX_GATEWAY_IP) + SET_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP); + } else if ((oly == OVERLAY_INDEX_TYPE_NONE) + && (!CHECK_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST))) { + + /* + * This is modify case from gateway-ip + * to no overlay index + */ + adv_flag_changed = true; + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP); + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST); + } else if ((oly == OVERLAY_INDEX_GATEWAY_IP) + && (!CHECK_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP))) { + + /* + * This is modify case from no overlay index + * to gateway-ip + */ + adv_flag_changed = true; + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST); + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP); + } else { + + /* + * Command is issued with the same option + * (no overlay index or gateway-ip) which was + * already configured. So nothing to do. + * However, route-map may have been modified. + * check if route-map has been modified. + * If not, return an error + */ + if (!rmap_changed) + return CMD_WARNING; + } + } else { + if ((!CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST)) + && (!CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP))) { + + /* + * this is the case for first time ever configuration + * adv ipv6 unicast is enabled for the first time. + * So no need to reset any flag + */ + if (oly == OVERLAY_INDEX_TYPE_NONE) + SET_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST); + else if (oly == OVERLAY_INDEX_GATEWAY_IP) + SET_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP); + } else if ((oly == OVERLAY_INDEX_TYPE_NONE) + && (!CHECK_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST))) { + + /* + * This is modify case from gateway-ip + * to no overlay index + */ + adv_flag_changed = true; + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP); + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST); + } else if ((oly == OVERLAY_INDEX_GATEWAY_IP) + && (!CHECK_FLAG( + bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP))) { + + /* + * This is modify case from no overlay index + * to gateway-ip + */ + adv_flag_changed = true; + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST); + SET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP); + } else { + + /* + * Command is issued with the same option + * (no overlay index or gateway-ip) which was + * already configured. So nothing to do. + * However, route-map may have been modified. + * check if route-map has been modified. + * If not, return an error + */ + if (!rmap_changed) + return CMD_WARNING; + } + } + + if ((rmap_changed) || (adv_flag_changed)) { + + /* If either of these are changed, then FRR needs to + * withdraw already advertised type5 routes. + */ + bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); + if (rmap_changed) { + if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_vrf->adv_cmd_rmap[afi][safi].name); + route_map_counter_decrement( + bgp_vrf->adv_cmd_rmap[afi][safi].map); + bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL; + bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL; + } + } + } + + /* set the route-map for advertise command */ + if (ret && argv[idx_rmap + 1]->arg) { + bgp_vrf->adv_cmd_rmap[afi][safi].name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, argv[idx_rmap + 1]->arg); + bgp_vrf->adv_cmd_rmap[afi][safi].map = + route_map_lookup_by_name(argv[idx_rmap + 1]->arg); + route_map_counter_increment( + bgp_vrf->adv_cmd_rmap[afi][safi].map); + } + + /* advertise type-5 routes */ + if (advertise_type5_routes(bgp_vrf, afi)) + bgp_evpn_advertise_type5_routes(bgp_vrf, afi, safi); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_advertise_type5, + no_bgp_evpn_advertise_type5_cmd, + "no advertise " BGP_AFI_CMD_STR "" BGP_SAFI_CMD_STR " [route-map WORD]", + NO_STR + "Advertise prefix routes\n" + BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "route-map for filtering specific routes\n" + "Name of the route map\n") +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */ + int idx_afi = 0; + int idx_safi = 0; + afi_t afi = 0; + safi_t safi = 0; + + if (!bgp_vrf) + return CMD_WARNING; + + argv_find_and_parse_afi(argv, argc, &idx_afi, &afi); + argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); + + if (!(afi == AFI_IP || afi == AFI_IP6)) { + vty_out(vty, + "%% Only ipv4 or ipv6 address families are supported\n"); + return CMD_WARNING; + } + + if (safi != SAFI_UNICAST) { + vty_out(vty, + "%% Only ipv4 unicast or ipv6 unicast are supported\n"); + return CMD_WARNING; + } + + if (afi == AFI_IP) { + + /* if we are not advertising ipv4 prefix as type-5 + * nothing to do + */ + if ((CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST)) || + (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP))) { + bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST); + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP); + } + } else { + + /* if we are not advertising ipv6 prefix as type-5 + * nothing to do + */ + if ((CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST)) || + (CHECK_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP))){ + bgp_evpn_withdraw_type5_routes(bgp_vrf, afi, safi); + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST); + UNSET_FLAG(bgp_vrf->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP); + } + } + + /* clear the route-map information for advertise ipv4/ipv6 unicast */ + if (bgp_vrf->adv_cmd_rmap[afi][safi].name) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_vrf->adv_cmd_rmap[afi][safi].name); + bgp_vrf->adv_cmd_rmap[afi][safi].name = NULL; + bgp_vrf->adv_cmd_rmap[afi][safi].map = NULL; + } + + return CMD_SUCCESS; +} + +DEFPY (bgp_evpn_use_es_l3nhg, + bgp_evpn_use_es_l3nhg_cmd, + "[no$no] use-es-l3nhg", + NO_STR + "use L3 nexthop group for host routes with ES destination\n") +{ + bgp_mh_info->host_routes_use_l3nhg = no ? false :true; + return CMD_SUCCESS; +} + +DEFPY (bgp_evpn_ead_evi_rx_disable, + bgp_evpn_ead_evi_rx_disable_cmd, + "[no$no] disable-ead-evi-rx", + NO_STR + "Activate PE on EAD-ES even if EAD-EVI is not received\n") +{ + bool ead_evi_rx = no? true :false; + + if (ead_evi_rx != bgp_mh_info->ead_evi_rx) { + bgp_mh_info->ead_evi_rx = ead_evi_rx; + bgp_evpn_switch_ead_evi_rx(); + } + return CMD_SUCCESS; +} + +DEFPY (bgp_evpn_ead_evi_tx_disable, + bgp_evpn_ead_evi_tx_disable_cmd, + "[no$no] disable-ead-evi-tx", + NO_STR + "Don't advertise EAD-EVI for local ESs\n") +{ + bgp_mh_info->ead_evi_tx = no? true :false; + return CMD_SUCCESS; +} + +DEFPY (bgp_evpn_enable_resolve_overlay_index, + bgp_evpn_enable_resolve_overlay_index_cmd, + "[no$no] enable-resolve-overlay-index", + NO_STR + "Enable Recursive Resolution of type-5 route overlay index\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (bgp != bgp_get_evpn()) { + vty_out(vty, "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + bgp_evpn_set_unset_resolve_overlay_index(bgp, no ? false : true); + return CMD_SUCCESS; +} + +DEFPY (bgp_evpn_advertise_pip_ip_mac, + bgp_evpn_advertise_pip_ip_mac_cmd, + "[no$no] advertise-pip [ip [mac ]]", + NO_STR + "evpn system primary IP\n" + IP_STR + "ip address\n" + MAC_STR MAC_STR MAC_STR) +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); /* bgp vrf instance */ + struct bgp *bgp_evpn = NULL; + + if (!bgp_vrf || EVPN_ENABLED(bgp_vrf)) { + vty_out(vty, + "This command is supported under L3VNI BGP EVPN VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + bgp_evpn = bgp_get_evpn(); + + if (!no) { + /* pip is already enabled */ + if (argc == 1 && bgp_vrf->evpn_info->advertise_pip) + return CMD_SUCCESS; + + bgp_vrf->evpn_info->advertise_pip = true; + if (ip.s_addr != INADDR_ANY) { + /* Already configured with same IP */ + if (IPV4_ADDR_SAME(&ip, + &bgp_vrf->evpn_info->pip_ip_static)) + return CMD_SUCCESS; + + bgp_vrf->evpn_info->pip_ip_static = ip; + bgp_vrf->evpn_info->pip_ip = ip; + } else { + bgp_vrf->evpn_info->pip_ip_static.s_addr + = INADDR_ANY; + /* default instance router-id assignemt */ + if (bgp_evpn) + bgp_vrf->evpn_info->pip_ip = + bgp_evpn->router_id; + } + /* parse sys mac */ + if (!is_zero_mac(&mac->eth_addr)) { + /* Already configured with same MAC */ + if (memcmp(&bgp_vrf->evpn_info->pip_rmac_static, + &mac->eth_addr, ETH_ALEN) == 0) + return CMD_SUCCESS; + + memcpy(&bgp_vrf->evpn_info->pip_rmac_static, + &mac->eth_addr, ETH_ALEN); + memcpy(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->evpn_info->pip_rmac_static, + ETH_ALEN); + } else { + /* Copy zebra sys mac */ + if (!is_zero_mac(&bgp_vrf->evpn_info->pip_rmac_zebra)) + memcpy(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->evpn_info->pip_rmac_zebra, + ETH_ALEN); + } + } else { + if (argc == 2) { + if (!bgp_vrf->evpn_info->advertise_pip) + return CMD_SUCCESS; + /* Disable PIP feature */ + bgp_vrf->evpn_info->advertise_pip = false; + /* copy anycast mac */ + memcpy(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->rmac, ETH_ALEN); + } else { + /* remove MAC-IP option retain PIP knob. */ + if ((ip.s_addr != INADDR_ANY) && + !IPV4_ADDR_SAME(&ip, + &bgp_vrf->evpn_info->pip_ip_static)) { + vty_out(vty, + "%% BGP EVPN PIP IP does not match\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!is_zero_mac(&mac->eth_addr) && + memcmp(&bgp_vrf->evpn_info->pip_rmac_static, + &mac->eth_addr, ETH_ALEN) != 0) { + vty_out(vty, + "%% BGP EVPN PIP MAC does not match\n"); + return CMD_WARNING_CONFIG_FAILED; + } + /* pip_rmac can carry vrr_rmac reset only if it matches + * with static value. + */ + if (memcmp(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->evpn_info->pip_rmac_static, + ETH_ALEN) == 0) { + /* Copy zebra sys mac */ + if (!is_zero_mac( + &bgp_vrf->evpn_info->pip_rmac_zebra)) + memcpy(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->evpn_info->pip_rmac_zebra, + ETH_ALEN); + else { + /* copy anycast mac */ + memcpy(&bgp_vrf->evpn_info->pip_rmac, + &bgp_vrf->rmac, ETH_ALEN); + } + } + } + /* reset user configured sys MAC */ + memset(&bgp_vrf->evpn_info->pip_rmac_static, 0, ETH_ALEN); + /* reset user configured sys IP */ + bgp_vrf->evpn_info->pip_ip_static.s_addr = INADDR_ANY; + /* Assign default PIP IP (bgp instance router-id) */ + if (bgp_evpn) + bgp_vrf->evpn_info->pip_ip = bgp_evpn->router_id; + else + bgp_vrf->evpn_info->pip_ip.s_addr = INADDR_ANY; + } + + if (is_evpn_enabled()) { + struct listnode *node = NULL; + struct bgpevpn *vpn = NULL; + + /* + * At this point if bgp_evpn is NULL and evpn is enabled + * something stupid has gone wrong + */ + assert(bgp_evpn); + + update_advertise_vrf_routes(bgp_vrf); + + /* Update (svi) type-2 routes */ + for (ALL_LIST_ELEMENTS_RO(bgp_vrf->l2vnis, node, vpn)) { + if (!bgp_evpn_is_svi_macip_enabled(vpn)) + continue; + update_routes_for_vni(bgp_evpn, vpn); + } + } + + return CMD_SUCCESS; +} + +/* + * Display VNI information - for all or a specific VNI + */ +DEFUN(show_bgp_l2vpn_evpn_vni, + show_bgp_l2vpn_evpn_vni_cmd, + "show bgp l2vpn evpn vni [" CMD_VNI_RANGE "] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Show VNI\n" + "VNI number\n" + JSON_STR) +{ + struct bgp *bgp_evpn; + vni_t vni; + int idx = 0; + bool uj = false; + json_object *json = NULL; + uint32_t num_l2vnis = 0; + uint32_t num_l3vnis = 0; + uint32_t num_vnis = 0; + struct listnode *node = NULL; + struct bgp *bgp_temp = NULL; + + uj = use_json(argc, argv); + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return CMD_WARNING; + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + if (uj) + json = json_object_new_object(); + + if ((uj && argc == ((idx + 1) + 2)) || (!uj && argc == (idx + 1) + 1)) { + + num_l2vnis = hashcount(bgp_evpn->vnihash); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_temp)) { + if (bgp_temp->l3vni) + num_l3vnis++; + } + num_vnis = num_l2vnis + num_l3vnis; + if (uj) { + json_object_string_add(json, "advertiseGatewayMacip", + bgp_evpn->advertise_gw_macip + ? "Enabled" + : "Disabled"); + json_object_string_add(json, "advertiseSviMacIp", + bgp_evpn->evpn_info->advertise_svi_macip + ? "Enabled" : "Disabled"); + json_object_string_add(json, "advertiseAllVnis", + is_evpn_enabled() ? "Enabled" + : "Disabled"); + json_object_string_add( + json, "flooding", + bgp_evpn->vxlan_flood_ctrl == + VXLAN_FLOOD_HEAD_END_REPL + ? "Head-end replication" + : "Disabled"); + json_object_string_add( + json, "vxlanFlooding", + bgp_evpn->vxlan_flood_ctrl == + VXLAN_FLOOD_HEAD_END_REPL + ? "Enabled" + : "Disabled"); + json_object_int_add(json, "numVnis", num_vnis); + json_object_int_add(json, "numL2Vnis", num_l2vnis); + json_object_int_add(json, "numL3Vnis", num_l3vnis); + } else { + vty_out(vty, "Advertise Gateway Macip: %s\n", + bgp_evpn->advertise_gw_macip ? "Enabled" + : "Disabled"); + vty_out(vty, "Advertise SVI Macip: %s\n", + bgp_evpn->evpn_info->advertise_svi_macip ? "Enabled" + : "Disabled"); + vty_out(vty, "Advertise All VNI flag: %s\n", + is_evpn_enabled() ? "Enabled" : "Disabled"); + vty_out(vty, "BUM flooding: %s\n", + bgp_evpn->vxlan_flood_ctrl == + VXLAN_FLOOD_HEAD_END_REPL + ? "Head-end replication" + : "Disabled"); + vty_out(vty, "VXLAN flooding: %s\n", + bgp_evpn->vxlan_flood_ctrl == + VXLAN_FLOOD_HEAD_END_REPL + ? "Enabled" + : "Disabled"); + vty_out(vty, "Number of L2 VNIs: %u\n", num_l2vnis); + vty_out(vty, "Number of L3 VNIs: %u\n", num_l3vnis); + } + evpn_show_all_vnis(vty, bgp_evpn, json); + } else { + int vni_idx = 0; + + if (!argv_find(argv, argc, "vni", &vni_idx)) + return CMD_WARNING; + + /* Display specific VNI */ + vni = strtoul(argv[vni_idx + 1]->arg, NULL, 10); + evpn_show_vni(vty, bgp_evpn, vni, json); + } + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(show_bgp_l2vpn_evpn_vni_remote_ip_hash, + show_bgp_l2vpn_evpn_vni_remote_ip_hash_cmd, + "show bgp l2vpn evpn vni remote-ip-hash", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Show VNI\n" + "Remote IP hash\n") +{ + struct bgp *bgp_evpn; + int idx = 0; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return CMD_WARNING; + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + hash_iterate(bgp_evpn->vnihash, + (void (*)(struct hash_bucket *, + void *))bgp_evpn_show_remote_ip_hash, + vty); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(show_bgp_l2vpn_evpn_vni_svi_hash, + show_bgp_l2vpn_evpn_vni_svi_hash_cmd, + "show bgp l2vpn evpn vni-svi-hash", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Show vni-svi-hash\n") +{ + struct bgp *bgp_evpn; + int idx = 0; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return CMD_WARNING; + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + hash_iterate(bgp_evpn->vni_svi_hash, + (void (*)(struct hash_bucket *, + void *))bgp_evpn_show_vni_svi_hash, + vty); + + return CMD_SUCCESS; +} + +DEFPY(show_bgp_l2vpn_evpn_es_evi, + show_bgp_l2vpn_evpn_es_evi_cmd, + "show bgp l2vpn evpn es-evi [vni (1-16777215)$vni] [json$uj] [detail$detail]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "ES per EVI\n" + "VxLAN Network Identifier\n" + "VNI\n" + JSON_STR + "Detailed information\n") +{ + if (vni) + bgp_evpn_es_evi_show_vni(vty, vni, !!uj, !!detail); + else + bgp_evpn_es_evi_show(vty, !!uj, !!detail); + + return CMD_SUCCESS; +} + +DEFPY(show_bgp_l2vpn_evpn_es, + show_bgp_l2vpn_evpn_es_cmd, + "show bgp l2vpn evpn es [NAME$esi_str|detail$detail] [json$uj]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Ethernet Segment\n" + "ES ID\n" + "Detailed information\n" + JSON_STR) +{ + esi_t esi; + + if (esi_str) { + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + bgp_evpn_es_show_esi(vty, &esi, uj); + } else { + + bgp_evpn_es_show(vty, uj, !!detail); + } + + return CMD_SUCCESS; +} + +DEFPY(show_bgp_l2vpn_evpn_es_vrf, show_bgp_l2vpn_evpn_es_vrf_cmd, + "show bgp l2vpn evpn es-vrf [NAME$esi_str] [json$uj]", + SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR + "Ethernet Segment\n" + "ES ID\n" JSON_STR) +{ + esi_t esi; + + if (esi_str) { + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + bgp_evpn_es_vrf_show_esi(vty, &esi, uj); + } else { + + bgp_evpn_es_vrf_show(vty, uj, NULL); + } + + return CMD_SUCCESS; +} + +DEFPY(show_bgp_l2vpn_evpn_nh, + show_bgp_l2vpn_evpn_nh_cmd, + "show bgp l2vpn evpn next-hops [json$uj]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Nexthops\n" + JSON_STR) +{ + bgp_evpn_nh_show(vty, uj); + + return CMD_SUCCESS; +} + +/* + * Display EVPN neighbor summary. + */ +DEFUN(show_bgp_l2vpn_evpn_summary, show_bgp_l2vpn_evpn_summary_cmd, + "show bgp [vrf VRFNAME] l2vpn evpn summary [established|failed] [|remote-as <(1-4294967295)|internal|external>>] [terse] [wide] [json]", + SHOW_STR BGP_STR + "bgp vrf\n" + "vrf name\n" L2VPN_HELP_STR EVPN_HELP_STR + "Summary of BGP neighbor status\n" + "Show only sessions in Established state\n" + "Show only sessions not in Established state\n" + "Show only the specified neighbor session\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Show only the specified remote AS sessions\n" + "AS number\n" + "Internal (iBGP) AS sessions\n" + "External (eBGP) AS sessions\n" + "Shorten the information on BGP instances\n" + "Increase table width for longer output\n" JSON_STR) +{ + int idx_vrf = 0; + int idx = 0; + char *vrf = NULL; + char *neighbor = NULL; + as_t as = 0; /* 0 means AS filter not set */ + int as_type = AS_UNSPECIFIED; + uint16_t show_flags = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf = argv[++idx_vrf]->arg; + + if (argv_find(argv, argc, "failed", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_FAILED); + + if (argv_find(argv, argc, "established", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_ESTABLISHED); + + + if (argv_find(argv, argc, "neighbor", &idx)) + neighbor = argv[idx + 1]->arg; + + if (argv_find(argv, argc, "remote-as", &idx)) { + if (argv[idx + 1]->arg[0] == 'i') + as_type = AS_INTERNAL; + else if (argv[idx + 1]->arg[0] == 'e') + as_type = AS_EXTERNAL; + else + as = (as_t)atoi(argv[idx + 1]->arg); + } + + if (argv_find(argv, argc, "terse", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_TERSE); + + if (argv_find(argv, argc, "wide", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + if (use_json(argc, argv)) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + return bgp_show_summary_vty(vty, vrf, AFI_L2VPN, SAFI_EVPN, neighbor, + as_type, as, show_flags); +} + +static int bgp_evpn_cli_parse_type_cmp(int *type, const char *type_str) +{ + if ((strncmp(type_str, "ma", 2) == 0) || (strmatch(type_str, "2"))) + *type = BGP_EVPN_MAC_IP_ROUTE; + else if ((strncmp(type_str, "mu", 2) == 0) || (strmatch(type_str, "3"))) + *type = BGP_EVPN_IMET_ROUTE; + else if ((strncmp(type_str, "es", 2) == 0) || (strmatch(type_str, "4"))) + *type = BGP_EVPN_ES_ROUTE; + else if ((strncmp(type_str, "ea", 2) == 0) || (strmatch(type_str, "1"))) + *type = BGP_EVPN_AD_ROUTE; + else if ((strncmp(type_str, "p", 1) == 0) || (strmatch(type_str, "5"))) + *type = BGP_EVPN_IP_PREFIX_ROUTE; + else + return -1; + + return 0; +} + +int bgp_evpn_cli_parse_type(int *type, struct cmd_token **argv, int argc) +{ + int type_idx = 0; + + if (argv_find(argv, argc, "type", &type_idx)) { + /* Specific type is requested */ + if (bgp_evpn_cli_parse_type_cmp(type, + argv[type_idx + 1]->arg) != 0) + return -1; + } + + return 0; +} + +/* + * Display global EVPN routing table. + */ +DEFUN(show_bgp_l2vpn_evpn_route, + show_bgp_l2vpn_evpn_route_cmd, + "show bgp l2vpn evpn route [detail] [type "EVPN_TYPE_ALL_LIST"] ["BGP_SELF_ORIG_CMD_STR"] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "Display Detailed Information\n" + EVPN_TYPE_HELP_STR + EVPN_TYPE_ALL_LIST_HELP_STR + BGP_SELF_ORIG_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + int detail = 0; + int type = 0; + bool uj = false; + int arg_idx = 0; + bool self_orig = false; + json_object *json = NULL; + + uj = use_json(argc, argv); + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + if (uj) + json = json_object_new_object(); + + if (bgp_evpn_cli_parse_type(&type, argv, argc) < 0) + return CMD_WARNING; + + if (argv_find(argv, argc, "detail", &detail)) + detail = 1; + + if (argv_find(argv, argc, BGP_SELF_ORIG_CMD_STR, &arg_idx)) + self_orig = true; + + evpn_show_all_routes(vty, bgp, type, json, detail, self_orig); + + /* + * This is an extremely expensive operation at scale + * and as such we need to save as much time as is + * possible. + */ + if (uj) + vty_json_no_pretty(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display global EVPN routing table for specific RD. + */ +DEFUN(show_bgp_l2vpn_evpn_route_rd, + show_bgp_l2vpn_evpn_route_rd_cmd, + "show bgp l2vpn evpn route rd [type "EVPN_TYPE_ALL_LIST"] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR + "All VPN Route Distinguishers\n" + EVPN_TYPE_HELP_STR + EVPN_TYPE_ALL_LIST_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + int ret = 0; + struct prefix_rd prd; + int type = 0; + bool uj = false; + json_object *json = NULL; + int idx_ext_community = 0; + int rd_all = 0; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + if (!argv_find(argv, argc, "all", &rd_all)) { + /* get the RD */ + if (argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", + &idx_ext_community)) { + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + } + } + + if (bgp_evpn_cli_parse_type(&type, argv, argc) < 0) + return CMD_WARNING; + + if (rd_all) + evpn_show_all_routes(vty, bgp, type, json, 1, false); + else + evpn_show_route_rd(vty, bgp, &prd, type, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display global EVPN routing table for specific RD and MACIP. + */ +DEFUN(show_bgp_l2vpn_evpn_route_rd_macip, + show_bgp_l2vpn_evpn_route_rd_macip_cmd, + "show bgp l2vpn evpn route rd mac WORD [ip WORD] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR + "All VPN Route Distinguishers\n" + "MAC\n" + "MAC address (e.g., 00:e0:ec:20:12:62)\n" + "IP\n" + "IP address (IPv4 or IPv6)\n" + JSON_STR) +{ + struct bgp *bgp; + int ret = 0; + struct prefix_rd prd; + struct ethaddr mac; + struct ipaddr ip; + int idx_ext_community = 0; + int mac_idx = 0; + int ip_idx = 0; + bool uj = false; + json_object *json = NULL; + int rd_all = 0; + + memset(&mac, 0, sizeof(struct ethaddr)); + memset(&ip, 0, sizeof(struct ipaddr)); + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + /* get the prd */ + if (!argv_find(argv, argc, "all", &rd_all)) { + if (argv_find(argv, argc, "ASN:NN_OR_IP-ADDRESS:NN", + &idx_ext_community)) { + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + } + } + + /* get the mac */ + if (argv_find(argv, argc, "mac", &mac_idx)) { + if (!prefix_str2mac(argv[mac_idx + 1]->arg, &mac)) { + vty_out(vty, "%% Malformed MAC address\n"); + return CMD_WARNING; + } + } + + /* get the ip if specified */ + if (argv_find(argv, argc, "ip", &ip_idx)) { + if (str2ipaddr(argv[ip_idx + 1]->arg, &ip) != 0) { + vty_out(vty, "%% Malformed IP address\n"); + return CMD_WARNING; + } + } + + if (rd_all) + evpn_show_route_rd_all_macip(vty, bgp, &mac, &ip, json); + else + evpn_show_route_rd_macip(vty, bgp, &prd, &mac, &ip, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* Display per ESI routing table */ +DEFUN(show_bgp_l2vpn_evpn_route_esi, + show_bgp_l2vpn_evpn_route_esi_cmd, + "show bgp l2vpn evpn route esi ESI [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "Ethernet Segment Identifier\n" + "ESI ID\n" + JSON_STR) +{ + bool uj = false; + esi_t esi; + struct bgp *bgp = NULL; + json_object *json = NULL; + + memset(&esi, 0, sizeof(esi)); + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + /* get the ESI - ESI-ID is at argv[6] */ + if (!str_to_esi(argv[6]->arg, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + + evpn_show_routes_esi(vty, bgp, &esi, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + + +/* + * Display per-VNI EVPN routing table. + */ +DEFUN(show_bgp_l2vpn_evpn_route_vni, show_bgp_l2vpn_evpn_route_vni_cmd, + "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " [ | vtep A.B.C.D>] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + EVPN_TYPE_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + "Remote VTEP\n" + "Remote VTEP IP address\n" + JSON_STR) +{ + vni_t vni; + struct bgp *bgp; + struct in_addr vtep_ip; + int type = 0; + int idx = 0; + int vtep_idx = 0; + bool uj = false; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + vtep_ip.s_addr = 0; + + vni = strtoul(argv[idx + 3]->arg, NULL, 10); + + if (bgp_evpn_cli_parse_type(&type, argv, argc) < 0) + return CMD_WARNING; + + if (argv_find(argv, argc, "vtep", &vtep_idx)) { + if (!inet_aton(argv[vtep_idx + 1]->arg, &vtep_ip)) { + vty_out(vty, "%% Malformed VTEP IP address\n"); + return CMD_WARNING; + } + } + + evpn_show_routes_vni(vty, bgp, vni, type, false, vtep_ip, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN routing table for specific MACIP. + */ +DEFUN(show_bgp_l2vpn_evpn_route_vni_macip, + show_bgp_l2vpn_evpn_route_vni_macip_cmd, + "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " mac WORD [ip WORD] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + "MAC\n" + "MAC address (e.g., 00:e0:ec:20:12:62)\n" + "IP\n" + "IP address (IPv4 or IPv6)\n" + JSON_STR) +{ + vni_t vni; + struct bgp *bgp; + struct ethaddr mac; + struct ipaddr ip; + int idx = 0; + bool uj = false; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + /* get the VNI */ + vni = strtoul(argv[idx + 3]->arg, NULL, 10); + + /* get the mac */ + if (!prefix_str2mac(argv[idx + 5]->arg, &mac)) { + vty_out(vty, "%% Malformed MAC address\n"); + return CMD_WARNING; + } + + /* get the ip */ + memset(&ip, 0, sizeof(ip)); + if ((!uj && ((argc == (idx + 1 + 7)) && argv[idx + 7]->arg != NULL)) + || (uj + && ((argc == (idx + 1 + 8)) && argv[idx + 7]->arg != NULL))) { + if (str2ipaddr(argv[idx + 7]->arg, &ip) != 0) { + vty_out(vty, "%% Malformed IP address\n"); + return CMD_WARNING; + } + } + + evpn_show_route_vni_macip(vty, bgp, vni, &mac, &ip, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN routing table for specific multicast IP (remote VTEP). + */ +DEFUN(show_bgp_l2vpn_evpn_route_vni_multicast, + show_bgp_l2vpn_evpn_route_vni_multicast_cmd, + "show bgp l2vpn evpn route vni " CMD_VNI_RANGE " multicast A.B.C.D [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + EVPN_TYPE_3_HELP_STR + "Originating Router IP address\n" + JSON_STR) +{ + vni_t vni; + struct bgp *bgp; + int ret; + struct in_addr orig_ip; + int idx = 0; + bool uj = false; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + /* get the VNI */ + vni = strtoul(argv[idx + 3]->arg, NULL, 10); + + /* get the ip */ + ret = inet_aton(argv[idx + 5]->arg, &orig_ip); + if (!ret) { + vty_out(vty, "%% Malformed Originating Router IP address\n"); + return CMD_WARNING; + } + + evpn_show_route_vni_multicast(vty, bgp, vni, orig_ip, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN routing table - for all VNIs. + */ +DEFUN(show_bgp_l2vpn_evpn_route_vni_all, + show_bgp_l2vpn_evpn_route_vni_all_cmd, + "show bgp l2vpn evpn route vni all [detail] [vtep A.B.C.D] [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "All VNIs\n" + "Print Detailed Output\n" + "Remote VTEP\n" + "Remote VTEP IP address\n" + JSON_STR) +{ + struct bgp *bgp; + struct in_addr vtep_ip; + int idx = 0; + bool uj = false; + json_object *json = NULL; + /* Detail Adjust. Adjust indexes according to detail option */ + int da = 0; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + if (!argv_find(argv, argc, "evpn", &idx)) + return CMD_WARNING; + + if (argv_find(argv, argc, "detail", &da)) + da = 1; + + /* vtep-ip position depends on detail option */ + vtep_ip.s_addr = 0; + if ((!uj && (argc == (idx + 1 + 5 + da) && argv[idx + 5 + da]->arg)) + || (uj + && (argc == (idx + 1 + 6 + da) && argv[idx + 5 + da]->arg))) { + if (!inet_aton(argv[idx + 5 + da]->arg, &vtep_ip)) { + vty_out(vty, "%% Malformed VTEP IP address\n"); + return CMD_WARNING; + } + } + + evpn_show_routes_vni_all(vty, bgp, 0, false, vtep_ip, json, da); + + if (uj) { + vty_json(vty, json); + json_object_free(json); + } + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN ALL routing tables - for all VNIs. + */ +DEFPY(show_bgp_vni_all, + show_bgp_vni_all_cmd, + "show bgp vni all [vtep A.B.C.D$addr] [detail$detail] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_ALL_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + DETAIL_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni_all_type_all(vty, bgp, addr, json, !!detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN EAD routing table - for all VNIs. + */ +DEFPY(show_bgp_vni_all_ead, + show_bgp_vni_all_ead_cmd, + "show bgp vni all type <1|ead> [vtep A.B.C.D$addr] []", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_ALL_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_1_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + DETAIL_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni_all(vty, bgp, BGP_EVPN_AD_ROUTE, false, addr, json, + !!detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN MAC routing table - for all VNIs. + */ +DEFPY(show_bgp_vni_all_macip_mac, + show_bgp_vni_all_macip_mac_cmd, + "show bgp vni all type <2|macip> mac [vtep A.B.C.D$addr] []", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_ALL_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + "MAC Table\n" + VTEP_HELP_STR + VTEP_IP_HELP_STR + DETAIL_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni_all(vty, bgp, BGP_EVPN_MAC_IP_ROUTE, true, addr, + json, !!detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN IP routing table - for all VNIs. + */ +DEFPY(show_bgp_vni_all_macip_ip, + show_bgp_vni_all_macip_ip_cmd, + "show bgp vni all type <2|macip> ip [vtep A.B.C.D$addr] []", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_ALL_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + "IP Table\n" + VTEP_HELP_STR + VTEP_IP_HELP_STR + DETAIL_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni_all(vty, bgp, BGP_EVPN_MAC_IP_ROUTE, false, addr, + json, !!detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN Multicast routing table - for all VNIs. + */ +DEFPY(show_bgp_vni_all_imet, + show_bgp_vni_all_imet_cmd, + "show bgp vni all type <3|multicast> [vtep A.B.C.D$addr] []", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_ALL_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + DETAIL_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni_all(vty, bgp, BGP_EVPN_IMET_ROUTE, false, addr, + json, !!detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN ALL routing tables - for select VNI + */ +DEFPY(show_bgp_vni, + show_bgp_vni_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni [vtep A.B.C.D$addr] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + json_object *json_mac = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) { + json = json_object_new_object(); + json_mac = json_object_new_object(); + } + + evpn_show_routes_vni(vty, bgp, vni, 0, false, addr, json); + + if (!uj) + vty_out(vty, "\n\nMAC Table:\n\n"); + + evpn_show_routes_vni(vty, bgp, vni, 0, true, addr, json_mac); + + if (uj) { + json_object_object_add(json, "macTable", json_mac); + vty_json(vty, json); + } + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN EAD routing table - for select VNI + */ +DEFPY(show_bgp_vni_ead, + show_bgp_vni_ead_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni type <1|ead> [vtep A.B.C.D$addr] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_1_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni(vty, bgp, vni, BGP_EVPN_AD_ROUTE, false, addr, + json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN MAC-IP MAC routing table - for select VNI + */ +DEFPY(show_bgp_vni_macip_mac, + show_bgp_vni_macip_mac_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni type <2|macip> mac [vtep A.B.C.D$addr] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + "MAC Table\n" + VTEP_HELP_STR + VTEP_IP_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni(vty, bgp, vni, BGP_EVPN_MAC_IP_ROUTE, true, addr, + json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN MAC-IP IP routing table - for select VNI + */ +DEFPY(show_bgp_vni_macip_ip, + show_bgp_vni_macip_ip_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni type <2|macip> ip [vtep A.B.C.D$addr] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + "IP Table\n" + VTEP_HELP_STR + VTEP_IP_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni(vty, bgp, vni, BGP_EVPN_MAC_IP_ROUTE, false, addr, + json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN Multicast routing table - for select VNI + */ +DEFPY(show_bgp_vni_imet, + show_bgp_vni_imet_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni type <3|multicast> [vtep A.B.C.D$addr] [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + VTEP_HELP_STR + VTEP_IP_HELP_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_routes_vni(vty, bgp, vni, BGP_EVPN_IMET_ROUTE, false, addr, + json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN MACIP MAC routing table - for select VNI & MAC + */ +DEFPY(show_bgp_vni_macip_mac_addr, + show_bgp_vni_macip_mac_addr_cmd, + "show bgp vni "CMD_VNI_RANGE"$vni type <2|macip> mac X:X:X:X:X:X [json$uj]", + SHOW_STR + BGP_STR + VNI_HELP_STR + VNI_NUM_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + "MAC Table\n" + MAC_STR + JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + evpn_show_route_vni_macip(vty, bgp, vni, &mac->eth_addr, NULL, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display per-VNI EVPN MACIP IP routing table - for select VNI & IP + */ +DEFPY(show_bgp_vni_macip_ip_addr, show_bgp_vni_macip_ip_addr_cmd, + "show bgp vni " CMD_VNI_RANGE + "$vni type <2|macip> ip [json$uj]", + SHOW_STR BGP_STR VNI_HELP_STR VNI_NUM_HELP_STR EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR EVPN_TYPE_2_HELP_STR + "IP Table\n" IP_ADDR_STR IP6_ADDR_STR JSON_STR) +{ + struct bgp *bgp; + json_object *json = NULL; + struct ipaddr ip_addr = {.ipa_type = IPADDR_NONE}; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + /* check if we need json output */ + if (uj) + json = json_object_new_object(); + + if (sockunion_family(ip) == AF_INET) { + ip_addr.ipa_type = IPADDR_V4; + ip_addr.ipaddr_v4.s_addr = sockunion2ip(ip); + } else { + ip_addr.ipa_type = IPADDR_V6; + memcpy(&ip_addr.ipaddr_v6, &ip->sin6.sin6_addr, + sizeof(struct in6_addr)); + } + evpn_show_route_vni_macip(vty, bgp, vni, NULL, &ip_addr, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN( + show_bgp_l2vpn_evpn_route_mac_ip_evi_es, + show_bgp_l2vpn_evpn_route_mac_ip_evi_es_cmd, + "show bgp l2vpn evpn route mac-ip-evi-es [NAME$esi_str|detail$detail] [json$uj]", + SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR + "EVPN route information\n" + "MAC IP routes in the EVI tables linked to the ES\n" + "ES ID\n" + "Detailed information\n" JSON_STR) +{ + esi_t esi; + esi_t *esi_p; + json_object *json = NULL; + + if (esi_str) { + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + esi_p = &esi; + } else { + esi_p = NULL; + } + + if (uj) + json = json_object_new_object(); + bgp_evpn_show_routes_mac_ip_evi_es(vty, esi_p, json, !!detail); + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN( + show_bgp_l2vpn_evpn_route_mac_ip_global_es, + show_bgp_l2vpn_evpn_route_mac_ip_global_es_cmd, + "show bgp l2vpn evpn route mac-ip-global-es [NAME$esi_str|detail$detail] [json$uj]", + SHOW_STR BGP_STR L2VPN_HELP_STR EVPN_HELP_STR + "EVPN route information\n" + "MAC IP routes in the global table linked to the ES\n" + "ES ID\n" + "Detailed information\n" JSON_STR) +{ + esi_t esi; + esi_t *esi_p; + json_object *json = NULL; + + if (esi_str) { + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + esi_p = &esi; + } else { + esi_p = NULL; + } + + if (uj) + json = json_object_new_object(); + bgp_evpn_show_routes_mac_ip_global_es(vty, esi_p, json, !!detail); + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display EVPN import route-target hash table + */ +DEFUN(show_bgp_l2vpn_evpn_vrf_import_rt, + show_bgp_l2vpn_evpn_vrf_import_rt_cmd, + "show bgp l2vpn evpn vrf-import-rt [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Show vrf import route target\n" + JSON_STR) +{ + bool uj = false; + struct bgp *bgp_evpn = NULL; + json_object *json = NULL; + + bgp_evpn = bgp_get_evpn(); + if (!bgp_evpn) + return CMD_WARNING; + + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + evpn_show_vrf_import_rts(vty, bgp_evpn, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* + * Display EVPN import route-target hash table + */ +DEFUN(show_bgp_l2vpn_evpn_import_rt, + show_bgp_l2vpn_evpn_import_rt_cmd, + "show bgp l2vpn evpn import-rt [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Show import route target\n" + JSON_STR) +{ + struct bgp *bgp; + bool uj = false; + json_object *json = NULL; + + bgp = bgp_get_evpn(); + if (!bgp) + return CMD_WARNING; + + uj = use_json(argc, argv); + if (uj) + json = json_object_new_object(); + + evpn_show_import_rts(vty, bgp, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN(test_es_add, + test_es_add_cmd, + "[no$no] test es NAME$esi_str [state NAME$state_str]", + NO_STR + "Test\n" + "Ethernet-segment\n" + "Ethernet-Segment Identifier\n" + "ES link state\n" + "up|down\n" +) +{ + int ret = 0; + esi_t esi; + struct bgp *bgp; + struct in_addr vtep_ip; + bool oper_up; + + bgp = bgp_get_evpn(); + if (!bgp) { + vty_out(vty, "%% EVPN BGP instance not yet created\n"); + return CMD_WARNING; + } + + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + + if (no) { + ret = bgp_evpn_local_es_del(bgp, &esi); + if (ret == -1) { + vty_out(vty, "%% Failed to delete ES\n"); + return CMD_WARNING; + } + } else { + if (state_str && !strcmp(state_str, "up")) + oper_up = true; + else + oper_up = false; + vtep_ip = bgp->router_id; + + ret = bgp_evpn_local_es_add(bgp, &esi, vtep_ip, oper_up, + EVPN_MH_DF_PREF_MIN, false); + if (ret == -1) { + vty_out(vty, "%% Failed to add ES\n"); + return CMD_WARNING; + } + } + return CMD_SUCCESS; +} + +DEFPY_HIDDEN(test_es_vni_add, + test_es_vni_add_cmd, + "[no$no] test es NAME$esi_str vni (1-16777215)$vni", + NO_STR + "Test\n" + "Ethernet-segment\n" + "Ethernet-Segment Identifier\n" + "VNI\n" + "1-16777215\n" +) +{ + int ret = 0; + esi_t esi; + struct bgp *bgp; + + bgp = bgp_get_evpn(); + if (!bgp) { + vty_out(vty, "%% EVPN BGP instance not yet created\n"); + return CMD_WARNING; + } + + if (!str_to_esi(esi_str, &esi)) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING; + } + + if (no) { + ret = bgp_evpn_local_es_evi_del(bgp, &esi, vni); + if (ret == -1) { + vty_out(vty, "%% Failed to deref ES VNI\n"); + return CMD_WARNING; + } + } else { + ret = bgp_evpn_local_es_evi_add(bgp, &esi, vni); + if (ret == -1) { + vty_out(vty, "%% Failed to ref ES VNI\n"); + return CMD_WARNING; + } + } + return CMD_SUCCESS; +} + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_vni, show_bgp_evpn_vni_cmd, + "show bgp evpn vni [" CMD_VNI_RANGE "]", SHOW_STR BGP_STR EVPN_HELP_STR + "Show VNI\n" + "VNI number\n") + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_summary, show_bgp_evpn_summary_cmd, + "show bgp evpn summary [json]", SHOW_STR BGP_STR EVPN_HELP_STR + "Summary of BGP neighbor status\n" JSON_STR) + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route, show_bgp_evpn_route_cmd, + "show bgp evpn route [detail] [type ]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + "Display Detailed Information\n" + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR) + +ALIAS_HIDDEN( + show_bgp_l2vpn_evpn_route_rd, show_bgp_evpn_route_rd_cmd, + "show bgp evpn route rd ASN:NN_OR_IP-ADDRESS:NN [type ]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR) + +ALIAS_HIDDEN( + show_bgp_l2vpn_evpn_route_rd_macip, show_bgp_evpn_route_rd_macip_cmd, + "show bgp evpn route rd ASN:NN_OR_IP-ADDRESS:NN mac WORD [ip WORD]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR + "MAC\n" + "MAC address (e.g., 00:e0:ec:20:12:62)\n" + "IP\n" + "IP address (IPv4 or IPv6)\n") + +ALIAS_HIDDEN( + show_bgp_l2vpn_evpn_route_vni, show_bgp_evpn_route_vni_cmd, + "show bgp evpn route vni " CMD_VNI_RANGE " [ | vtep A.B.C.D>]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + EVPN_TYPE_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + "Remote VTEP\n" + "Remote VTEP IP address\n") + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_macip, + show_bgp_evpn_route_vni_macip_cmd, + "show bgp evpn route vni " CMD_VNI_RANGE " mac WORD [ip WORD]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + "MAC\n" + "MAC address (e.g., 00:e0:ec:20:12:62)\n" + "IP\n" + "IP address (IPv4 or IPv6)\n") + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_multicast, + show_bgp_evpn_route_vni_multicast_cmd, + "show bgp evpn route vni " CMD_VNI_RANGE " multicast A.B.C.D", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "VNI number\n" + EVPN_TYPE_3_HELP_STR + "Originating Router IP address\n") + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_route_vni_all, show_bgp_evpn_route_vni_all_cmd, + "show bgp evpn route vni all [detail] [vtep A.B.C.D]", + SHOW_STR BGP_STR EVPN_HELP_STR + EVPN_RT_HELP_STR + "VXLAN Network Identifier\n" + "All VNIs\n" + "Print Detailed Output\n" + "Remote VTEP\n" + "Remote VTEP IP address\n") + +ALIAS_HIDDEN(show_bgp_l2vpn_evpn_import_rt, show_bgp_evpn_import_rt_cmd, + "show bgp evpn import-rt", + SHOW_STR BGP_STR EVPN_HELP_STR "Show import route target\n") + +DEFUN_NOSH (bgp_evpn_vni, + bgp_evpn_vni_cmd, + "vni " CMD_VNI_RANGE, + "VXLAN Network Identifier\n" + "VNI number\n") +{ + vni_t vni; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgpevpn *vpn; + + if (!bgp) + return CMD_WARNING; + + vni = strtoul(argv[1]->arg, NULL, 10); + + /* Create VNI, or mark as configured. */ + vpn = evpn_create_update_vni(bgp, vni); + if (!vpn) { + vty_out(vty, "%% Failed to create VNI \n"); + return CMD_WARNING; + } + + VTY_PUSH_CONTEXT_SUB(BGP_EVPN_VNI_NODE, vpn); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vni, + no_bgp_evpn_vni_cmd, + "no vni " CMD_VNI_RANGE, + NO_STR + "VXLAN Network Identifier\n" + "VNI number\n") +{ + vni_t vni; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct bgpevpn *vpn; + + if (!bgp) + return CMD_WARNING; + + vni = strtoul(argv[2]->arg, NULL, 10); + + /* Check if we should disallow. */ + vpn = bgp_evpn_lookup_vni(bgp, vni); + if (!vpn) { + vty_out(vty, "%% Specified VNI does not exist\n"); + return CMD_WARNING; + } + if (!is_vni_configured(vpn)) { + vty_out(vty, "%% Specified VNI is not configured\n"); + return CMD_WARNING; + } + + evpn_delete_vni(bgp, vpn); + return CMD_SUCCESS; +} + +DEFUN_NOSH (exit_vni, + exit_vni_cmd, + "exit-vni", + "Exit from VNI mode\n") +{ + if (vty->node == BGP_EVPN_VNI_NODE) + vty->node = BGP_EVPN_NODE; + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_vrf_rd, + bgp_evpn_vrf_rd_cmd, + "rd ASN:NN_OR_IP-ADDRESS:NN", + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR) +{ + int ret; + struct prefix_rd prd; + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + + ret = str2prefix_rd(argv[1]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + /* If same as existing value, there is nothing more to do. */ + if (bgp_evpn_vrf_rd_matches_existing(bgp_vrf, &prd)) + return CMD_SUCCESS; + + /* Configure or update the RD. */ + evpn_configure_vrf_rd(bgp_vrf, &prd, argv[1]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vrf_rd, + no_bgp_evpn_vrf_rd_cmd, + "no rd ASN:NN_OR_IP-ADDRESS:NN", + NO_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR) +{ + int ret; + struct prefix_rd prd; + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + + ret = str2prefix_rd(argv[2]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + /* Check if we should disallow. */ + if (!is_vrf_rd_configured(bgp_vrf)) { + vty_out(vty, "%% RD is not configured for this VRF\n"); + return CMD_WARNING; + } + + if (!bgp_evpn_vrf_rd_matches_existing(bgp_vrf, &prd)) { + vty_out(vty, + "%% RD specified does not match configuration for this VRF\n"); + return CMD_WARNING; + } + + evpn_unconfigure_vrf_rd(bgp_vrf); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vrf_rd_without_val, + no_bgp_evpn_vrf_rd_without_val_cmd, + "no rd", + NO_STR + EVPN_RT_DIST_HELP_STR) +{ + struct bgp *bgp_vrf = VTY_GET_CONTEXT(bgp); + + if (!bgp_vrf) + return CMD_WARNING; + + /* Check if we should disallow. */ + if (!is_vrf_rd_configured(bgp_vrf)) { + vty_out(vty, "%% RD is not configured for this VRF\n"); + return CMD_WARNING; + } + + evpn_unconfigure_vrf_rd(bgp_vrf); + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_vni_rd, + bgp_evpn_vni_rd_cmd, + "rd ASN:NN_OR_IP-ADDRESS:NN", + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR) +{ + struct prefix_rd prd; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + int ret; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + ret = str2prefix_rd(argv[1]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + /* If same as existing value, there is nothing more to do. */ + if (bgp_evpn_rd_matches_existing(vpn, &prd)) + return CMD_SUCCESS; + + /* Configure or update the RD. */ + evpn_configure_rd(bgp, vpn, &prd, argv[1]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vni_rd, + no_bgp_evpn_vni_rd_cmd, + "no rd ASN:NN_OR_IP-ADDRESS:NN", + NO_STR + EVPN_RT_DIST_HELP_STR + EVPN_ASN_IP_HELP_STR) +{ + struct prefix_rd prd; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + int ret; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + ret = str2prefix_rd(argv[2]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + /* Check if we should disallow. */ + if (!is_rd_configured(vpn)) { + vty_out(vty, "%% RD is not configured for this VNI\n"); + return CMD_WARNING; + } + + if (!bgp_evpn_rd_matches_existing(vpn, &prd)) { + vty_out(vty, + "%% RD specified does not match configuration for this VNI\n"); + return CMD_WARNING; + } + + evpn_unconfigure_rd(bgp, vpn); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vni_rd_without_val, + no_bgp_evpn_vni_rd_without_val_cmd, + "no rd", + NO_STR + EVPN_RT_DIST_HELP_STR) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + /* Check if we should disallow. */ + if (!is_rd_configured(vpn)) { + vty_out(vty, "%% RD is not configured for this VNI\n"); + return CMD_WARNING; + } + + evpn_unconfigure_rd(bgp, vpn); + return CMD_SUCCESS; +} + +/* + * Loop over all extended-communities in the route-target list rtl and + * return 1 if we find ecomtarget + */ +static bool bgp_evpn_rt_matches_existing(struct list *rtl, + struct ecommunity *ecomtarget) +{ + struct listnode *node; + struct ecommunity *ecom; + + for (ALL_LIST_ELEMENTS_RO(rtl, node, ecom)) { + if (ecommunity_match(ecom, ecomtarget)) + return true; + } + + return false; +} + +/* + * L3 RT version of above. + */ +static bool bgp_evpn_vrf_rt_matches_existing(struct list *rtl, + struct ecommunity *ecomtarget) +{ + struct listnode *node; + struct vrf_route_target *l3rt; + + for (ALL_LIST_ELEMENTS_RO(rtl, node, l3rt)) { + if (ecommunity_match(l3rt->ecom, ecomtarget)) + return true; + } + + return false; +} + +/* display L3VNI related info for a VRF instance */ +DEFUN (show_bgp_vrf_l3vni_info, + show_bgp_vrf_l3vni_info_cmd, + "show bgp vrf VRFNAME vni [json]", + SHOW_STR + BGP_STR + "show bgp vrf\n" + "VRF Name\n" + "L3-VNI\n" + JSON_STR) +{ + char buf[ETHER_ADDR_STRLEN]; + int idx_vrf = 3; + const char *name = NULL; + struct bgp *bgp = NULL; + struct listnode *node = NULL; + struct bgpevpn *vpn = NULL; + struct vrf_route_target *l3rt; + json_object *json = NULL; + json_object *json_vnis = NULL; + json_object *json_export_rts = NULL; + json_object *json_import_rts = NULL; + bool uj = use_json(argc, argv); + + if (uj) { + json = json_object_new_object(); + json_vnis = json_object_new_array(); + json_export_rts = json_object_new_array(); + json_import_rts = json_object_new_array(); + } + + name = argv[idx_vrf]->arg; + bgp = bgp_lookup_by_name(name); + if (strmatch(name, VRF_DEFAULT_NAME)) + bgp = bgp_get_default(); + + if (!bgp) { + if (!uj) + vty_out(vty, "BGP instance for VRF %s not found\n", + name); + else { + json_object_string_add(json, "warning", + "BGP instance not found"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + } + return CMD_WARNING; + } + + if (!json) { + vty_out(vty, "BGP VRF: %s\n", name); + vty_out(vty, " Local-Ip: %pI4\n", &bgp->originator_ip); + vty_out(vty, " L3-VNI: %u\n", bgp->l3vni); + vty_out(vty, " Rmac: %s\n", + prefix_mac2str(&bgp->rmac, buf, sizeof(buf))); + vty_out(vty, " VNI Filter: %s\n", + CHECK_FLAG(bgp->vrf_flags, + BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY) + ? "prefix-routes-only" + : "none"); + vty_out(vty, " L2-VNI List:\n"); + vty_out(vty, " "); + for (ALL_LIST_ELEMENTS_RO(bgp->l2vnis, node, vpn)) + vty_out(vty, "%u ", vpn->vni); + vty_out(vty, "\n"); + vty_out(vty, " Export-RTs:\n"); + vty_out(vty, " "); + for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, l3rt)) + vty_out(vty, "%s ", ecommunity_str(l3rt->ecom)); + vty_out(vty, "\n"); + vty_out(vty, " Import-RTs:\n"); + vty_out(vty, " "); + for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, l3rt)) + vty_out(vty, "%s ", ecommunity_str(l3rt->ecom)); + vty_out(vty, "\n"); + vty_out(vty, " RD: "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), &bgp->vrf_prd); + vty_out(vty, "\n"); + } else { + json_object_string_add(json, "vrf", name); + json_object_string_addf(json, "local-ip", "%pI4", + &bgp->originator_ip); + json_object_int_add(json, "l3vni", bgp->l3vni); + json_object_string_add( + json, "rmac", + prefix_mac2str(&bgp->rmac, buf, sizeof(buf))); + json_object_string_add( + json, "vniFilter", + CHECK_FLAG(bgp->vrf_flags, + BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY) + ? "prefix-routes-only" + : "none"); + /* list of l2vnis */ + for (ALL_LIST_ELEMENTS_RO(bgp->l2vnis, node, vpn)) + json_object_array_add(json_vnis, + json_object_new_int(vpn->vni)); + json_object_object_add(json, "l2vnis", json_vnis); + + /* export rts */ + for (ALL_LIST_ELEMENTS_RO(bgp->vrf_export_rtl, node, l3rt)) + json_object_array_add( + json_export_rts, + json_object_new_string( + ecommunity_str(l3rt->ecom))); + json_object_object_add(json, "export-rts", json_export_rts); + + /* import rts */ + for (ALL_LIST_ELEMENTS_RO(bgp->vrf_import_rtl, node, l3rt)) + json_object_array_add( + json_import_rts, + json_object_new_string( + ecommunity_str(l3rt->ecom))); + json_object_object_add(json, "import-rts", json_import_rts); + json_object_string_addf(json, "rd", + BGP_RD_AS_FORMAT(bgp->asnotation), + &bgp->vrf_prd); + } + + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +static int add_rt(struct bgp *bgp, struct ecommunity *ecom, bool is_import, + bool is_wildcard) +{ + /* Do nothing if we already have this route-target */ + if (is_import) { + if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_import_rtl, + ecom)) + bgp_evpn_configure_import_rt_for_vrf(bgp, ecom, + is_wildcard); + else + return -1; + } else { + if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_export_rtl, + ecom)) + bgp_evpn_configure_export_rt_for_vrf(bgp, ecom); + else + return -1; + } + + return 0; +} + +static int del_rt(struct bgp *bgp, struct ecommunity *ecom, bool is_import) +{ + /* Verify we already have this route-target */ + if (is_import) { + if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_import_rtl, + ecom)) + return -1; + + bgp_evpn_unconfigure_import_rt_for_vrf(bgp, ecom); + } else { + if (!bgp_evpn_vrf_rt_matches_existing(bgp->vrf_export_rtl, + ecom)) + return -1; + + bgp_evpn_unconfigure_export_rt_for_vrf(bgp, ecom); + } + + return 0; +} + +static int parse_rtlist(struct bgp *bgp, struct vty *vty, int argc, + struct cmd_token **argv, int rt_idx, bool is_add, + bool is_import) +{ + int ret = CMD_SUCCESS; + bool is_wildcard = false; + struct ecommunity *ecom = NULL; + + for (int i = rt_idx; i < argc; i++) { + is_wildcard = false; + + /* + * Special handling for wildcard '*' here. + * + * Let's just convert it to 0 here so we dont have to modify + * the ecommunity parser. + */ + if ((argv[i]->arg)[0] == '*') { + (argv[i]->arg)[0] = '0'; + is_wildcard = true; + } + + ecom = ecommunity_str2com(argv[i]->arg, ECOMMUNITY_ROUTE_TARGET, + 0); + + /* Put it back as was */ + if (is_wildcard) + (argv[i]->arg)[0] = '*'; + + if (!ecom) { + vty_out(vty, "%% Malformed Route Target list\n"); + ret = CMD_WARNING; + continue; + } + + ecommunity_str(ecom); + + if (is_add) { + if (add_rt(bgp, ecom, is_import, is_wildcard) != 0) { + vty_out(vty, + "%% RT specified already configured for this VRF: %s\n", + argv[i]->arg); + ecommunity_free(&ecom); + ret = CMD_WARNING; + } + + } else { + if (del_rt(bgp, ecom, is_import) != 0) { + vty_out(vty, + "%% RT specified does not match configuration for this VRF: %s\n", + argv[i]->arg); + ret = CMD_WARNING; + } + + ecommunity_free(&ecom); + } + } + + return ret; +} + +/* import/export rt for l3vni-vrf */ +DEFUN (bgp_evpn_vrf_rt, + bgp_evpn_vrf_rt_cmd, + "route-target RTLIST...", + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN|*:OPQR|*:MN)\n") +{ + int ret = CMD_SUCCESS; + int tmp_ret = CMD_SUCCESS; + int rt_type; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + if (!bgp) + return CMD_WARNING_CONFIG_FAILED; + + if (!strcmp(argv[1]->arg, "import")) + rt_type = RT_TYPE_IMPORT; + else if (!strcmp(argv[1]->arg, "export")) + rt_type = RT_TYPE_EXPORT; + else if (!strcmp(argv[1]->arg, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[2]->arg, "auto")) { + vty_out(vty, "%% `auto` cannot be configured via list\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (rt_type != RT_TYPE_IMPORT) { + for (int i = 2; i < argc; i++) { + if ((argv[i]->arg)[0] == '*') { + vty_out(vty, + "%% Wildcard '*' only applicable for import\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + /* Add/update the import route-target */ + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) + tmp_ret = parse_rtlist(bgp, vty, argc, argv, 2, true, true); + + if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) + ret = tmp_ret; + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) + tmp_ret = parse_rtlist(bgp, vty, argc, argv, 2, true, false); + + if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) + ret = tmp_ret; + + return ret; +} + +DEFPY (bgp_evpn_vrf_rt_auto, + bgp_evpn_vrf_rt_auto_cmd, + "route-target $type auto", + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + "Automatically derive route target\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int rt_type; + + if (!bgp) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(type, "import")) + rt_type = RT_TYPE_IMPORT; + else if (strmatch(type, "export")) + rt_type = RT_TYPE_EXPORT; + else if (strmatch(type, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) + bgp_evpn_configure_import_auto_rt_for_vrf(bgp); + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) + bgp_evpn_configure_export_auto_rt_for_vrf(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vrf_rt, + no_bgp_evpn_vrf_rt_cmd, + "no route-target RTLIST...", + NO_STR + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int ret = CMD_SUCCESS; + int tmp_ret = CMD_SUCCESS; + int rt_type; + + if (!bgp) + return CMD_WARNING_CONFIG_FAILED; + + if (!strcmp(argv[2]->arg, "import")) + rt_type = RT_TYPE_IMPORT; + else if (!strcmp(argv[2]->arg, "export")) + rt_type = RT_TYPE_EXPORT; + else if (!strcmp(argv[2]->arg, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!strcmp(argv[3]->arg, "auto")) { + vty_out(vty, "%% `auto` cannot be unconfigured via list\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (rt_type == RT_TYPE_IMPORT) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) { + vty_out(vty, + "%% Import RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } else if (rt_type == RT_TYPE_EXPORT) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { + vty_out(vty, + "%% Export RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } else if (rt_type == RT_TYPE_BOTH) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD) + && !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { + vty_out(vty, + "%% Import/Export RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (rt_type != RT_TYPE_IMPORT) { + for (int i = 3; i < argc; i++) { + if ((argv[i]->arg)[0] == '*') { + vty_out(vty, + "%% Wildcard '*' only applicable for import\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) + tmp_ret = parse_rtlist(bgp, vty, argc, argv, 3, false, true); + + if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) + ret = tmp_ret; + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) + tmp_ret = parse_rtlist(bgp, vty, argc, argv, 3, false, false); + + if (ret == CMD_SUCCESS && tmp_ret != CMD_SUCCESS) + ret = tmp_ret; + + return ret; +} + +DEFPY (no_bgp_evpn_vrf_rt_auto, + no_bgp_evpn_vrf_rt_auto_cmd, + "no route-target $type auto", + NO_STR + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + "Automatically derive route target\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int rt_type; + + if (!bgp) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(type, "import")) + rt_type = RT_TYPE_IMPORT; + else if (strmatch(type, "export")) + rt_type = RT_TYPE_EXPORT; + else if (strmatch(type, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (rt_type == RT_TYPE_IMPORT) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) { + vty_out(vty, + "%% Import AUTO RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } else if (rt_type == RT_TYPE_EXPORT) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) { + vty_out(vty, + "%% Export AUTO RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } else if (rt_type == RT_TYPE_BOTH) { + if (!CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD) && + !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) { + vty_out(vty, + "%% Import/Export AUTO RT is not configured for this VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) + bgp_evpn_unconfigure_import_auto_rt_for_vrf(bgp); + + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) + bgp_evpn_unconfigure_export_auto_rt_for_vrf(bgp); + + return CMD_SUCCESS; +} + +DEFPY(bgp_evpn_ead_ess_frag_evi_limit, bgp_evpn_ead_es_frag_evi_limit_cmd, + "[no$no] ead-es-frag evi-limit (1-1000)$limit", + NO_STR + "EAD ES fragment config\n" + "EVIs per-fragment\n" + "limit\n") +{ + bgp_mh_info->evi_per_es_frag = + no ? BGP_EVPN_MAX_EVI_PER_ES_FRAG : limit; + + return CMD_SUCCESS; +} + +DEFUN(bgp_evpn_ead_es_rt, bgp_evpn_ead_es_rt_cmd, + "ead-es-route-target export RT", + "EAD ES Route Target\n" + "export\n" + "Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct ecommunity *ecomadd = NULL; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + /* Add/update the export route-target */ + ecomadd = ecommunity_str2com(argv[2]->arg, ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomadd) { + vty_out(vty, "%% Malformed Route Target list\n"); + return CMD_WARNING; + } + ecommunity_str(ecomadd); + + /* Do nothing if we already have this export route-target */ + if (!bgp_evpn_rt_matches_existing(bgp_mh_info->ead_es_export_rtl, + ecomadd)) + bgp_evpn_mh_config_ead_export_rt(bgp, ecomadd, false); + else + ecommunity_free(&ecomadd); + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_evpn_ead_es_rt, no_bgp_evpn_ead_es_rt_cmd, + "no ead-es-route-target export RT", + NO_STR + "EAD ES Route Target\n" + "export\n" EVPN_ASN_IP_HELP_STR) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + struct ecommunity *ecomdel = NULL; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + ecomdel = ecommunity_str2com(argv[3]->arg, ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomdel) { + vty_out(vty, "%% Malformed Route Target list\n"); + return CMD_WARNING; + } + ecommunity_str(ecomdel); + + if (!bgp_evpn_rt_matches_existing(bgp_mh_info->ead_es_export_rtl, + ecomdel)) { + ecommunity_free(&ecomdel); + vty_out(vty, + "%% RT specified does not match EAD-ES RT configuration\n"); + return CMD_WARNING; + } + bgp_evpn_mh_config_ead_export_rt(bgp, ecomdel, true); + + ecommunity_free(&ecomdel); + return CMD_SUCCESS; +} + +DEFUN (bgp_evpn_vni_rt, + bgp_evpn_vni_rt_cmd, + "route-target RT", + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + "Route target (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + int rt_type; + struct ecommunity *ecomadd = NULL; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[1]->text, "import")) + rt_type = RT_TYPE_IMPORT; + else if (!strcmp(argv[1]->text, "export")) + rt_type = RT_TYPE_EXPORT; + else if (!strcmp(argv[1]->text, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING; + } + + /* Add/update the import route-target */ + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_IMPORT) { + /* Note that first of the two RTs is created for "both" type */ + ecomadd = ecommunity_str2com(argv[2]->arg, + ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomadd) { + vty_out(vty, "%% Malformed Route Target list\n"); + return CMD_WARNING; + } + ecommunity_str(ecomadd); + + /* Do nothing if we already have this import route-target */ + if (!bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomadd)) + evpn_configure_import_rt(bgp, vpn, ecomadd); + else + ecommunity_free(&ecomadd); + } + + /* Add/update the export route-target */ + if (rt_type == RT_TYPE_BOTH || rt_type == RT_TYPE_EXPORT) { + /* Note that second of the two RTs is created for "both" type */ + ecomadd = ecommunity_str2com(argv[2]->arg, + ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomadd) { + vty_out(vty, "%% Malformed Route Target list\n"); + return CMD_WARNING; + } + ecommunity_str(ecomadd); + + /* Do nothing if we already have this export route-target */ + if (!bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomadd)) + evpn_configure_export_rt(bgp, vpn, ecomadd); + else + ecommunity_free(&ecomadd); + } + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vni_rt, + no_bgp_evpn_vni_rt_cmd, + "no route-target RT", + NO_STR + "Route Target\n" + "import and export\n" + "import\n" + "export\n" + EVPN_ASN_IP_HELP_STR) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + int rt_type, found_ecomdel; + struct ecommunity *ecomdel = NULL; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[2]->text, "import")) + rt_type = RT_TYPE_IMPORT; + else if (!strcmp(argv[2]->text, "export")) + rt_type = RT_TYPE_EXPORT; + else if (!strcmp(argv[2]->text, "both")) + rt_type = RT_TYPE_BOTH; + else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING; + } + + /* The user did "no route-target import", check to see if there are any + * import route-targets configured. */ + if (rt_type == RT_TYPE_IMPORT) { + if (!is_import_rt_configured(vpn)) { + vty_out(vty, + "%% Import RT is not configured for this VNI\n"); + return CMD_WARNING; + } + } else if (rt_type == RT_TYPE_EXPORT) { + if (!is_export_rt_configured(vpn)) { + vty_out(vty, + "%% Export RT is not configured for this VNI\n"); + return CMD_WARNING; + } + } else if (rt_type == RT_TYPE_BOTH) { + if (!is_import_rt_configured(vpn) + && !is_export_rt_configured(vpn)) { + vty_out(vty, + "%% Import/Export RT is not configured for this VNI\n"); + return CMD_WARNING; + } + } + + ecomdel = ecommunity_str2com(argv[3]->arg, ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomdel) { + vty_out(vty, "%% Malformed Route Target list\n"); + return CMD_WARNING; + } + ecommunity_str(ecomdel); + + if (rt_type == RT_TYPE_IMPORT) { + if (!bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomdel)) { + ecommunity_free(&ecomdel); + vty_out(vty, + "%% RT specified does not match configuration for this VNI\n"); + return CMD_WARNING; + } + evpn_unconfigure_import_rt(bgp, vpn, ecomdel); + } else if (rt_type == RT_TYPE_EXPORT) { + if (!bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomdel)) { + ecommunity_free(&ecomdel); + vty_out(vty, + "%% RT specified does not match configuration for this VNI\n"); + return CMD_WARNING; + } + evpn_unconfigure_export_rt(bgp, vpn, ecomdel); + } else if (rt_type == RT_TYPE_BOTH) { + found_ecomdel = 0; + + if (bgp_evpn_rt_matches_existing(vpn->import_rtl, ecomdel)) { + evpn_unconfigure_import_rt(bgp, vpn, ecomdel); + found_ecomdel = 1; + } + + if (bgp_evpn_rt_matches_existing(vpn->export_rtl, ecomdel)) { + evpn_unconfigure_export_rt(bgp, vpn, ecomdel); + found_ecomdel = 1; + } + + if (!found_ecomdel) { + ecommunity_free(&ecomdel); + vty_out(vty, + "%% RT specified does not match configuration for this VNI\n"); + return CMD_WARNING; + } + } + + ecommunity_free(&ecomdel); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_evpn_vni_rt_without_val, + no_bgp_evpn_vni_rt_without_val_cmd, + "no route-target ", + NO_STR + "Route Target\n" + "import\n" + "export\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + VTY_DECLVAR_CONTEXT_SUB(bgpevpn, vpn); + int rt_type; + + if (!bgp) + return CMD_WARNING; + + if (!EVPN_ENABLED(bgp)) { + vty_out(vty, + "This command is only supported under EVPN VRF\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[2]->text, "import")) { + rt_type = RT_TYPE_IMPORT; + } else if (!strcmp(argv[2]->text, "export")) { + rt_type = RT_TYPE_EXPORT; + } else { + vty_out(vty, "%% Invalid Route Target type\n"); + return CMD_WARNING; + } + + /* Check if we should disallow. */ + if (rt_type == RT_TYPE_IMPORT) { + if (!is_import_rt_configured(vpn)) { + vty_out(vty, + "%% Import RT is not configured for this VNI\n"); + return CMD_WARNING; + } + } else { + if (!is_export_rt_configured(vpn)) { + vty_out(vty, + "%% Export RT is not configured for this VNI\n"); + return CMD_WARNING; + } + } + + /* Unconfigure the RT. */ + if (rt_type == RT_TYPE_IMPORT) + evpn_unconfigure_import_rt(bgp, vpn, NULL); + else + evpn_unconfigure_export_rt(bgp, vpn, NULL); + return CMD_SUCCESS; +} + +static int vni_cmp(const void **a, const void **b) +{ + const struct bgpevpn *first = *a; + const struct bgpevpn *secnd = *b; + + return secnd->vni - first->vni; +} + +/* + * Output EVPN configuration information. + */ +void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + if (bgp->advertise_all_vni) + vty_out(vty, " advertise-all-vni\n"); + + if (hashcount(bgp->vnihash)) { + struct list *vnilist = hash_to_list(bgp->vnihash); + struct listnode *ln; + struct bgpevpn *data; + + list_sort(vnilist, vni_cmp); + for (ALL_LIST_ELEMENTS_RO(vnilist, ln, data)) + write_vni_config(vty, data); + + list_delete(&vnilist); + } + + if (bgp->advertise_autort_rfc8365) + vty_out(vty, " autort rfc8365-compatible\n"); + + if (bgp->advertise_gw_macip) + vty_out(vty, " advertise-default-gw\n"); + + if (bgp->evpn_info->advertise_svi_macip) + vty_out(vty, " advertise-svi-ip\n"); + + if (bgp->evpn_info->soo) { + char *ecom_str; + + ecom_str = ecommunity_ecom2str(bgp->evpn_info->soo, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " mac-vrf soo %s\n", ecom_str); + ecommunity_strfree(&ecom_str); + } + + if (bgp->resolve_overlay_index) + vty_out(vty, " enable-resolve-overlay-index\n"); + + if (bgp_mh_info->evi_per_es_frag != BGP_EVPN_MAX_EVI_PER_ES_FRAG) + vty_out(vty, " ead-es-frag evi-limit %u\n", + bgp_mh_info->evi_per_es_frag); + + if (bgp_mh_info->host_routes_use_l3nhg != + BGP_EVPN_MH_USE_ES_L3NHG_DEF) { + if (bgp_mh_info->host_routes_use_l3nhg) + vty_out(vty, " use-es-l3nhg\n"); + else + vty_out(vty, " no use-es-l3nhg\n"); + } + + if (bgp_mh_info->ead_evi_rx != BGP_EVPN_MH_EAD_EVI_RX_DEF) { + if (bgp_mh_info->ead_evi_rx) + vty_out(vty, " no disable-ead-evi-rx\n"); + else + vty_out(vty, " disable-ead-evi-rx\n"); + } + + if (bgp_mh_info->ead_evi_tx != BGP_EVPN_MH_EAD_EVI_TX_DEF) { + if (bgp_mh_info->ead_evi_tx) + vty_out(vty, " no disable-ead-evi-tx\n"); + else + vty_out(vty, " disable-ead-evi-tx\n"); + } + + if (!bgp->evpn_info->dup_addr_detect) + vty_out(vty, " no dup-addr-detection\n"); + + if (bgp->evpn_info->dad_max_moves != + EVPN_DAD_DEFAULT_MAX_MOVES || + bgp->evpn_info->dad_time != EVPN_DAD_DEFAULT_TIME) + vty_out(vty, " dup-addr-detection max-moves %u time %u\n", + bgp->evpn_info->dad_max_moves, + bgp->evpn_info->dad_time); + + if (bgp->evpn_info->dad_freeze) { + if (bgp->evpn_info->dad_freeze_time) + vty_out(vty, + " dup-addr-detection freeze %u\n", + bgp->evpn_info->dad_freeze_time); + else + vty_out(vty, + " dup-addr-detection freeze permanent\n"); + } + + if (bgp->vxlan_flood_ctrl == VXLAN_FLOOD_DISABLED) + vty_out(vty, " flooding disable\n"); + + if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST)) { + if (bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name) + vty_out(vty, " advertise ipv4 unicast route-map %s\n", + bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name); + else + vty_out(vty, + " advertise ipv4 unicast\n"); + } else if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP)) { + if (bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name) + vty_out(vty, + " advertise ipv4 unicast gateway-ip route-map %s\n", + bgp->adv_cmd_rmap[AFI_IP][SAFI_UNICAST].name); + else + vty_out(vty, " advertise ipv4 unicast gateway-ip\n"); + } + + /* EAD ES export route-target */ + if (listcount(bgp_mh_info->ead_es_export_rtl)) { + struct ecommunity *ecom; + char *ecom_str; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(bgp_mh_info->ead_es_export_rtl, node, + ecom)) { + + ecom_str = ecommunity_ecom2str( + ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " ead-es-route-target export %s\n", + ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } + } + + if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST)) { + if (bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name) + vty_out(vty, + " advertise ipv6 unicast route-map %s\n", + bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name); + else + vty_out(vty, + " advertise ipv6 unicast\n"); + } else if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP)) { + if (bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name) + vty_out(vty, + " advertise ipv6 unicast gateway-ip route-map %s\n", + bgp->adv_cmd_rmap[AFI_IP6][SAFI_UNICAST].name); + else + vty_out(vty, " advertise ipv6 unicast gateway-ip\n"); + } + + if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4)) + vty_out(vty, " default-originate ipv4\n"); + + if (CHECK_FLAG(bgp->af_flags[AFI_L2VPN][SAFI_EVPN], + BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6)) + vty_out(vty, " default-originate ipv6\n"); + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { + if (!bgp->evpn_info->advertise_pip) + vty_out(vty, " no advertise-pip\n"); + if (bgp->evpn_info->advertise_pip) { + if (bgp->evpn_info->pip_ip_static.s_addr + != INADDR_ANY) { + vty_out(vty, " advertise-pip ip %pI4", + &bgp->evpn_info->pip_ip_static); + if (!is_zero_mac(&( + bgp->evpn_info->pip_rmac_static))) { + char buf[ETHER_ADDR_STRLEN]; + + vty_out(vty, " mac %s", + prefix_mac2str( + &bgp->evpn_info + ->pip_rmac, + buf, sizeof(buf))); + } + vty_out(vty, "\n"); + } + } + } + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_RD_CFGD)) + vty_out(vty, " rd %s\n", bgp->vrf_prd_pretty); + + /* import route-target */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_RT_CFGD)) { + char *ecom_str; + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + + for (ALL_LIST_ELEMENTS(bgp->vrf_import_rtl, node, nnode, + l3rt)) { + + if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) + continue; + + ecom_str = ecommunity_ecom2str( + l3rt->ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_WILD)) { + char *vni_str = NULL; + + vni_str = strchr(ecom_str, ':'); + if (!vni_str) { + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + continue; + } + + /* Move pointer to vni */ + vni_str += 1; + + vty_out(vty, " route-target import *:%s\n", + vni_str); + + } else + vty_out(vty, " route-target import %s\n", + ecom_str); + + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } + } + + /* import route-target auto */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_IMPORT_AUTO_RT_CFGD)) + vty_out(vty, " route-target import auto\n"); + + /* export route-target */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_RT_CFGD)) { + char *ecom_str; + struct listnode *node, *nnode; + struct vrf_route_target *l3rt; + + for (ALL_LIST_ELEMENTS(bgp->vrf_export_rtl, node, nnode, + l3rt)) { + + if (CHECK_FLAG(l3rt->flags, BGP_VRF_RT_AUTO)) + continue; + + ecom_str = ecommunity_ecom2str( + l3rt->ecom, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " route-target export %s\n", ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } + } + + /* export route-target auto */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_EXPORT_AUTO_RT_CFGD)) + vty_out(vty, " route-target export auto\n"); +} + +void bgp_ethernetvpn_init(void) +{ + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_all_tags_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_rd_tags_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_l2vpn_evpn_neighbor_routes_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_l2vpn_evpn_rd_neighbor_routes_cmd); + install_element( + VIEW_NODE, + &show_ip_bgp_l2vpn_evpn_neighbor_advertised_routes_cmd); + install_element( + VIEW_NODE, + &show_ip_bgp_l2vpn_evpn_rd_neighbor_advertised_routes_cmd); + install_element(VIEW_NODE, &show_ip_bgp_evpn_rd_overlay_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_all_overlay_cmd); + install_element(BGP_EVPN_NODE, &no_evpnrt5_network_cmd); + install_element(BGP_EVPN_NODE, &evpnrt5_network_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_all_vni_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_all_vni_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_autort_rfc8365_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_autort_rfc8365_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_default_gw_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_default_gw_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_svi_ip_cmd); + install_element(BGP_EVPN_NODE, &macvrf_soo_global_cmd); + install_element(BGP_EVPN_NODE, &no_macvrf_soo_global_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_type5_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_advertise_type5_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_default_originate_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_default_originate_cmd); + install_element(BGP_EVPN_NODE, &dup_addr_detection_cmd); + install_element(BGP_EVPN_NODE, &dup_addr_detection_auto_recovery_cmd); + install_element(BGP_EVPN_NODE, &no_dup_addr_detection_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_flood_control_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_advertise_pip_ip_mac_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_use_es_l3nhg_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_ead_evi_rx_disable_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_ead_evi_tx_disable_cmd); + install_element(BGP_EVPN_NODE, + &bgp_evpn_enable_resolve_overlay_index_cmd); + + /* test commands */ + install_element(BGP_EVPN_NODE, &test_es_add_cmd); + install_element(BGP_EVPN_NODE, &test_es_vni_add_cmd); + + /* "show bgp l2vpn evpn" commands. */ + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_es_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_es_evi_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_es_vrf_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_nh_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vni_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vni_remote_ip_hash_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vni_svi_hash_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_summary_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_rd_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_rd_macip_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_esi_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_cmd); + install_element(VIEW_NODE, + &show_bgp_l2vpn_evpn_route_vni_multicast_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_macip_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_route_vni_all_cmd); + install_element(VIEW_NODE, + &show_bgp_l2vpn_evpn_route_mac_ip_evi_es_cmd); + install_element(VIEW_NODE, + &show_bgp_l2vpn_evpn_route_mac_ip_global_es_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_import_rt_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_vrf_import_rt_cmd); + + /* "show bgp vni" commands. */ + install_element(VIEW_NODE, &show_bgp_vni_all_cmd); + install_element(VIEW_NODE, &show_bgp_vni_all_ead_cmd); + install_element(VIEW_NODE, &show_bgp_vni_all_macip_mac_cmd); + install_element(VIEW_NODE, &show_bgp_vni_all_macip_ip_cmd); + install_element(VIEW_NODE, &show_bgp_vni_all_imet_cmd); + install_element(VIEW_NODE, &show_bgp_vni_cmd); + install_element(VIEW_NODE, &show_bgp_vni_ead_cmd); + install_element(VIEW_NODE, &show_bgp_vni_macip_mac_cmd); + install_element(VIEW_NODE, &show_bgp_vni_macip_ip_cmd); + install_element(VIEW_NODE, &show_bgp_vni_imet_cmd); + install_element(VIEW_NODE, &show_bgp_vni_macip_mac_addr_cmd); + install_element(VIEW_NODE, &show_bgp_vni_macip_ip_addr_cmd); + + /* "show bgp evpn" commands. */ + install_element(VIEW_NODE, &show_bgp_evpn_vni_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_summary_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_rd_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_rd_macip_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_vni_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_vni_multicast_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_vni_macip_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_route_vni_all_cmd); + install_element(VIEW_NODE, &show_bgp_evpn_import_rt_cmd); + install_element(VIEW_NODE, &show_bgp_vrf_l3vni_info_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_com_cmd); + + install_element(BGP_EVPN_NODE, &bgp_evpn_vni_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_vni_cmd); + install_element(BGP_EVPN_VNI_NODE, &exit_vni_cmd); + install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_vni_rd_cmd); + install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rd_cmd); + install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rd_without_val_cmd); + install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_vni_rt_cmd); + install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rt_cmd); + install_element(BGP_EVPN_VNI_NODE, &no_bgp_evpn_vni_rt_without_val_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rd_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rd_cmd); + install_element(BGP_NODE, &no_bgp_evpn_vrf_rd_without_val_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rt_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rt_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_vrf_rt_auto_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_vrf_rt_auto_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_ead_es_rt_cmd); + install_element(BGP_EVPN_NODE, &no_bgp_evpn_ead_es_rt_cmd); + install_element(BGP_EVPN_NODE, &bgp_evpn_ead_es_frag_evi_limit_cmd); + install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_advertise_svi_ip_vni_cmd); + install_element(BGP_EVPN_VNI_NODE, + &bgp_evpn_advertise_default_gw_vni_cmd); + install_element(BGP_EVPN_VNI_NODE, + &no_bgp_evpn_advertise_default_gw_vni_cmd); + install_element(BGP_EVPN_VNI_NODE, &bgp_evpn_advertise_vni_subnet_cmd); + install_element(BGP_EVPN_VNI_NODE, + &no_bgp_evpn_advertise_vni_subnet_cmd); +} diff --git a/bgpd/bgp_evpn_vty.h b/bgpd/bgp_evpn_vty.h new file mode 100644 index 0000000..83c41a5 --- /dev/null +++ b/bgpd/bgp_evpn_vty.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* EVPN VTY functions to EVPN + * Copyright (C) 2017 6WIND + */ + +#ifndef _FRR_BGP_EVPN_VTY_H +#define _FRR_BGP_EVPN_VTY_H + +extern void bgp_config_write_evpn_info(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi); +extern void bgp_ethernetvpn_init(void); + +#define L2VPN_HELP_STR "Layer 2 Virtual Private Network\n" +#define EVPN_HELP_STR "Ethernet Virtual Private Network\n" +#define VNI_HELP_STR "VXLAN Network Identifier\n" +#define VNI_NUM_HELP_STR "VNI number\n" +#define VNI_ALL_HELP_STR "All VNIs\n" +#define DETAIL_HELP_STR "Print Detailed Output\n" +#define VTEP_HELP_STR "Remote VTEP\n" +#define VTEP_IP_HELP_STR "Remote VTEP IP address\n" + +extern int argv_find_and_parse_oly_idx(struct cmd_token **argv, int argc, + int *oly_idx, + enum overlay_index_type *oly); + +/* Parse type from "type ", return -1 on failure */ +extern int bgp_evpn_cli_parse_type(int *type, struct cmd_token **argv, + int argc); + +extern int bgp_evpn_show_all_routes(struct vty *vty, struct bgp *bgp, int type, + bool use_json, int detail); + +#endif /* _QUAGGA_BGP_EVPN_VTY_H */ diff --git a/bgpd/bgp_filter.c b/bgpd/bgp_filter.c new file mode 100644 index 0000000..002f054 --- /dev/null +++ b/bgpd/bgp_filter.c @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS path filter list. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "log.h" +#include "memory.h" +#include "buffer.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_regex.h" + +/* List of AS list. */ +struct as_list_list { + struct as_list *head; + struct as_list *tail; +}; + +/* AS path filter master. */ +struct as_list_master { + /* List of access_list which name is string. */ + struct as_list_list str; + + /* Hook function which is executed when new access_list is added. */ + void (*add_hook)(char *); + + /* Hook function which is executed when access_list is deleted. */ + void (*delete_hook)(const char *); +}; + + + +/* Calculate new sequential number. */ +static int64_t bgp_alist_new_seq_get(struct as_list *list) +{ + int64_t maxseq; + int64_t newseq; + struct as_filter *entry; + + maxseq = 0; + + for (entry = list->head; entry; entry = entry->next) { + if (maxseq < entry->seq) + maxseq = entry->seq; + } + + newseq = ((maxseq / 5) * 5) + 5; + + return (newseq > UINT_MAX) ? UINT_MAX : newseq; +} + +/* Return as-list entry which has same seq number. */ +static struct as_filter *bgp_aslist_seq_check(struct as_list *list, int64_t seq) +{ + struct as_filter *entry; + + for (entry = list->head; entry; entry = entry->next) + if (entry->seq == seq) + return entry; + + return NULL; +} + +/* as-path access-list 10 permit AS1. */ + +static struct as_list_master as_list_master = {{NULL, NULL}, + NULL, + NULL}; + +/* Allocate new AS filter. */ +static struct as_filter *as_filter_new(void) +{ + return XCALLOC(MTYPE_AS_FILTER, sizeof(struct as_filter)); +} + +/* Free allocated AS filter. */ +static void as_filter_free(struct as_filter *asfilter) +{ + if (asfilter->reg) + bgp_regex_free(asfilter->reg); + XFREE(MTYPE_AS_FILTER_STR, asfilter->reg_str); + XFREE(MTYPE_AS_FILTER, asfilter); +} + +/* Make new AS filter. */ +static struct as_filter *as_filter_make(regex_t *reg, const char *reg_str, + enum as_filter_type type) +{ + struct as_filter *asfilter; + + asfilter = as_filter_new(); + asfilter->reg = reg; + asfilter->type = type; + asfilter->reg_str = XSTRDUP(MTYPE_AS_FILTER_STR, reg_str); + + return asfilter; +} + +static struct as_filter *as_filter_lookup(struct as_list *aslist, + const char *reg_str, + enum as_filter_type type) +{ + struct as_filter *asfilter; + + for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) + if (strcmp(reg_str, asfilter->reg_str) == 0) + return asfilter; + return NULL; +} + +static void as_filter_entry_replace(struct as_list *list, + struct as_filter *replace, + struct as_filter *entry) +{ + if (replace->next) { + entry->next = replace->next; + replace->next->prev = entry; + } else { + entry->next = NULL; + list->tail = entry; + } + + if (replace->prev) { + entry->prev = replace->prev; + replace->prev->next = entry; + } else { + entry->prev = NULL; + list->head = entry; + } + + as_filter_free(replace); +} + +static void as_list_filter_add(struct as_list *aslist, + struct as_filter *asfilter) +{ + struct as_filter *point; + struct as_filter *replace; + + if (aslist->tail && asfilter->seq > aslist->tail->seq) + point = NULL; + else { + replace = bgp_aslist_seq_check(aslist, asfilter->seq); + if (replace) { + as_filter_entry_replace(aslist, replace, asfilter); + goto hook; + } + + /* Check insert point. */ + for (point = aslist->head; point; point = point->next) + if (point->seq >= asfilter->seq) + break; + } + + asfilter->next = point; + + if (point) { + if (point->prev) + point->prev->next = asfilter; + else + aslist->head = asfilter; + + asfilter->prev = point->prev; + point->prev = asfilter; + } else { + if (aslist->tail) + aslist->tail->next = asfilter; + else + aslist->head = asfilter; + + asfilter->prev = aslist->tail; + aslist->tail = asfilter; + } + +hook: + /* Run hook function. */ + if (as_list_master.add_hook) + (*as_list_master.add_hook)(aslist->name); +} + +/* Lookup as_list from list of as_list by name. */ +struct as_list *as_list_lookup(const char *name) +{ + struct as_list *aslist; + + if (name == NULL) + return NULL; + + for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) + if (strcmp(aslist->name, name) == 0) + return aslist; + return NULL; +} + +static struct as_list *as_list_new(void) +{ + return XCALLOC(MTYPE_AS_LIST, sizeof(struct as_list)); +} + +static void as_list_free(struct as_list *aslist) +{ + + XFREE (MTYPE_AS_STR, aslist->name); + XFREE (MTYPE_AS_LIST, aslist); +} + +/* Insert new AS list to list of as_list. Each as_list is sorted by + the name. */ +static struct as_list *as_list_insert(const char *name) +{ + struct as_list *aslist; + struct as_list *point; + struct as_list_list *list; + + /* Allocate new access_list and copy given name. */ + aslist = as_list_new(); + aslist->name = XSTRDUP(MTYPE_AS_STR, name); + assert(aslist->name); + + /* Set access_list to string list. */ + list = &as_list_master.str; + + /* Set point to insertion point. */ + for (point = list->head; point; point = point->next) + if (strcmp(point->name, name) >= 0) + break; + + /* In case of this is the first element of master. */ + if (list->head == NULL) { + list->head = list->tail = aslist; + return aslist; + } + + /* In case of insertion is made at the tail of access_list. */ + if (point == NULL) { + aslist->prev = list->tail; + list->tail->next = aslist; + list->tail = aslist; + return aslist; + } + + /* In case of insertion is made at the head of access_list. */ + if (point == list->head) { + aslist->next = list->head; + list->head->prev = aslist; + list->head = aslist; + return aslist; + } + + /* Insertion is made at middle of the access_list. */ + aslist->next = point; + aslist->prev = point->prev; + + if (point->prev) + point->prev->next = aslist; + point->prev = aslist; + + return aslist; +} + +static struct as_list *as_list_get(const char *name) +{ + struct as_list *aslist; + + aslist = as_list_lookup(name); + if (aslist == NULL) + aslist = as_list_insert(name); + + return aslist; +} + +static const char *filter_type_str(enum as_filter_type type) +{ + switch (type) { + case AS_FILTER_PERMIT: + return "permit"; + case AS_FILTER_DENY: + return "deny"; + default: + return ""; + } +} + +static void as_list_delete(struct as_list *aslist) +{ + struct as_list_list *list; + struct as_filter *filter, *next; + + for (filter = aslist->head; filter; filter = next) { + next = filter->next; + as_filter_free(filter); + } + + list = &as_list_master.str; + + if (aslist->next) + aslist->next->prev = aslist->prev; + else + list->tail = aslist->prev; + + if (aslist->prev) + aslist->prev->next = aslist->next; + else + list->head = aslist->next; + + as_list_free(aslist); +} + +static bool as_list_empty(struct as_list *aslist) +{ + return aslist->head == NULL && aslist->tail == NULL; +} + +static void as_list_filter_delete(struct as_list *aslist, + struct as_filter *asfilter) +{ + char *name = XSTRDUP(MTYPE_AS_STR, aslist->name); + + if (asfilter->next) + asfilter->next->prev = asfilter->prev; + else + aslist->tail = asfilter->prev; + + if (asfilter->prev) + asfilter->prev->next = asfilter->next; + else + aslist->head = asfilter->next; + + as_filter_free(asfilter); + + /* If access_list becomes empty delete it from access_master. */ + if (as_list_empty(aslist)) + as_list_delete(aslist); + + /* Run hook function. */ + if (as_list_master.delete_hook) + (*as_list_master.delete_hook)(name); + XFREE(MTYPE_AS_STR, name); +} + +static bool as_filter_match(struct as_filter *asfilter, struct aspath *aspath) +{ + return bgp_regexec(asfilter->reg, aspath) != REG_NOMATCH; +} + +/* Apply AS path filter to AS. */ +enum as_filter_type as_list_apply(struct as_list *aslist, void *object) +{ + struct as_filter *asfilter; + struct aspath *aspath; + + aspath = (struct aspath *)object; + + if (aslist == NULL) + return AS_FILTER_DENY; + + for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { + if (as_filter_match(asfilter, aspath)) + return asfilter->type; + } + return AS_FILTER_DENY; +} + +/* Add hook function. */ +void as_list_add_hook(void (*func)(char *)) +{ + as_list_master.add_hook = func; +} + +/* Delete hook function. */ +void as_list_delete_hook(void (*func)(const char *)) +{ + as_list_master.delete_hook = func; +} + +static bool as_list_dup_check(struct as_list *aslist, struct as_filter *new) +{ + struct as_filter *asfilter; + + for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { + if (asfilter->type == new->type + && strcmp(asfilter->reg_str, new->reg_str) == 0) + return true; + } + return false; +} + +bool config_bgp_aspath_validate(const char *regstr) +{ + char valid_chars[] = "1234567890_^|[,{}() ]$*+.?-\\"; + + if (strspn(regstr, valid_chars) == strlen(regstr)) + return true; + return false; +} + +DEFUN(as_path, bgp_as_path_cmd, + "bgp as-path access-list AS_PATH_FILTER_NAME [seq (0-4294967295)] LINE...", + BGP_STR + "BGP autonomous system path filter\n" + "Specify an access list name\n" + "Regular expression access list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify packets to reject\n" + "Specify packets to forward\n" + "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") +{ + int idx = 0; + enum as_filter_type type; + struct as_filter *asfilter; + struct as_list *aslist; + struct aspath_exclude *ase; + regex_t *regex; + char *regstr; + int64_t seqnum = ASPATH_SEQ_NUMBER_AUTO; + + /* Retrieve access list name */ + argv_find(argv, argc, "AS_PATH_FILTER_NAME", &idx); + char *alname = argv[idx]->arg; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seqnum = (int64_t)atol(argv[idx]->arg); + + /* Check the filter type. */ + type = argv_find(argv, argc, "deny", &idx) ? AS_FILTER_DENY + : AS_FILTER_PERMIT; + + /* Check AS path regex. */ + argv_find(argv, argc, "LINE", &idx); + regstr = argv_concat(argv, argc, idx); + + regex = bgp_regcomp(regstr); + if (!regex) { + vty_out(vty, "can't compile regexp %s\n", regstr); + XFREE(MTYPE_TMP, regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!config_bgp_aspath_validate(regstr)) { + vty_out(vty, "Invalid character in as-path access-list %s\n", + regstr); + XFREE(MTYPE_TMP, regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + asfilter = as_filter_make(regex, regstr, type); + + XFREE(MTYPE_TMP, regstr); + + /* Install new filter to the access_list. */ + aslist = as_list_get(alname); + + if (seqnum == ASPATH_SEQ_NUMBER_AUTO) + seqnum = bgp_alist_new_seq_get(aslist); + + asfilter->seq = seqnum; + + /* Duplicate insertion check. */; + if (as_list_dup_check(aslist, asfilter)) + as_filter_free(asfilter); + else + as_list_filter_add(aslist, asfilter); + + /* init the exclude rule list*/ + as_list_list_init(&aslist->exclude_rule); + + /* get aspath orphan exclude that are using this acl */ + ase = as_exclude_lookup_orphan(alname); + if (ase) { + as_list_list_add_head(&aslist->exclude_rule, ase); + /* set reverse pointer */ + ase->exclude_aspath_acl = aslist; + /* set list of aspath excludes using that acl */ + while ((ase = as_exclude_lookup_orphan(alname))) { + as_list_list_add_head(&aslist->exclude_rule, ase); + ase->exclude_aspath_acl = aslist; + } + } + + return CMD_SUCCESS; +} + +DEFUN(no_as_path, no_bgp_as_path_cmd, + "no bgp as-path access-list AS_PATH_FILTER_NAME [seq (0-4294967295)] LINE...", + NO_STR + BGP_STR + "BGP autonomous system path filter\n" + "Specify an access list name\n" + "Regular expression access list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify packets to reject\n" + "Specify packets to forward\n" + "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n") +{ + int idx = 0; + enum as_filter_type type; + struct as_filter *asfilter; + struct as_list *aslist; + struct aspath_exclude *ase; + char *regstr; + regex_t *regex; + + char *aslistname = + argv_find(argv, argc, "AS_PATH_FILTER_NAME", &idx) ? argv[idx]->arg : NULL; + + /* Lookup AS list from AS path list. */ + aslist = as_list_lookup(aslistname); + if (aslist == NULL) { + vty_out(vty, "bgp as-path access-list %s doesn't exist\n", + aslistname); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check the filter type. */ + if (argv_find(argv, argc, "permit", &idx)) + type = AS_FILTER_PERMIT; + else if (argv_find(argv, argc, "deny", &idx)) + type = AS_FILTER_DENY; + else { + vty_out(vty, "filter type must be [permit|deny]\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Compile AS path. */ + argv_find(argv, argc, "LINE", &idx); + regstr = argv_concat(argv, argc, idx); + + if (!config_bgp_aspath_validate(regstr)) { + vty_out(vty, "Invalid character in as-path access-list %s\n", + regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + regex = bgp_regcomp(regstr); + if (!regex) { + vty_out(vty, "can't compile regexp %s\n", regstr); + XFREE(MTYPE_TMP, regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Lookup asfilter. */ + asfilter = as_filter_lookup(aslist, regstr, type); + + bgp_regex_free(regex); + + if (asfilter == NULL) { + vty_out(vty, "Regex entered %s does not exist\n", regstr); + XFREE(MTYPE_TMP, regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_TMP, regstr); + + /* put aspath exclude list into orphan */ + if (as_list_list_count(&aslist->exclude_rule)) + while ((ase = as_list_list_pop(&aslist->exclude_rule))) + as_exclude_set_orphan(ase); + + as_list_list_fini(&aslist->exclude_rule); + as_list_filter_delete(aslist, asfilter); + + return CMD_SUCCESS; +} + +DEFUN (no_as_path_all, + no_bgp_as_path_all_cmd, + "no bgp as-path access-list AS_PATH_FILTER_NAME", + NO_STR + BGP_STR + "BGP autonomous system path filter\n" + "Specify an access list name\n" + "Regular expression access list name\n") +{ + int idx_word = 4; + struct as_list *aslist; + + aslist = as_list_lookup(argv[idx_word]->arg); + if (aslist == NULL) { + vty_out(vty, "bgp as-path access-list %s doesn't exist\n", + argv[idx_word]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + as_list_delete(aslist); + + /* Run hook function. */ + if (as_list_master.delete_hook) + (*as_list_master.delete_hook)(argv[idx_word]->arg); + + return CMD_SUCCESS; +} + +static void as_list_show(struct vty *vty, struct as_list *aslist, + json_object *json) +{ + struct as_filter *asfilter; + json_object *json_aslist = NULL; + + if (json) { + json_aslist = json_object_new_array(); + json_object_object_add(json, aslist->name, json_aslist); + } else + vty_out(vty, "AS path access list %s\n", aslist->name); + + for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) { + if (json) { + json_object *json_asfilter = json_object_new_object(); + + json_object_int_add(json_asfilter, "sequenceNumber", + asfilter->seq); + json_object_string_add(json_asfilter, "type", + filter_type_str(asfilter->type)); + json_object_string_add(json_asfilter, "regExp", + asfilter->reg_str); + + json_object_array_add(json_aslist, json_asfilter); + } else + vty_out(vty, " %s %s\n", + filter_type_str(asfilter->type), + asfilter->reg_str); + } +} + +static void as_list_show_all(struct vty *vty, json_object *json) +{ + struct as_list *aslist; + + for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) + as_list_show(vty, aslist, json); +} + +DEFUN (show_as_path_access_list, + show_bgp_as_path_access_list_cmd, + "show bgp as-path-access-list AS_PATH_FILTER_NAME [json]", + SHOW_STR + BGP_STR + "List AS path access lists\n" + "AS path access list name\n" + JSON_STR) +{ + int idx_word = 3; + struct as_list *aslist; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + aslist = as_list_lookup(argv[idx_word]->arg); + if (aslist) + as_list_show(vty, aslist, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +ALIAS (show_as_path_access_list, + show_ip_as_path_access_list_cmd, + "show ip as-path-access-list AS_PATH_FILTER_NAME [json]", + SHOW_STR + IP_STR + "List AS path access lists\n" + "AS path access list name\n" + JSON_STR) + +DEFUN (show_as_path_access_list_all, + show_bgp_as_path_access_list_all_cmd, + "show bgp as-path-access-list [json]", + SHOW_STR + BGP_STR + "List AS path access lists\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + as_list_show_all(vty, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +ALIAS (show_as_path_access_list_all, + show_ip_as_path_access_list_all_cmd, + "show ip as-path-access-list [json]", + SHOW_STR + IP_STR + "List AS path access lists\n" + JSON_STR) + +static int config_write_as_list(struct vty *vty) +{ + struct as_list *aslist; + struct as_filter *asfilter; + int write = 0; + + for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) + for (asfilter = aslist->head; asfilter; + asfilter = asfilter->next) { + vty_out(vty, + "bgp as-path access-list %s seq %" PRId64 + " %s %s\n", + aslist->name, asfilter->seq, + filter_type_str(asfilter->type), + asfilter->reg_str); + write++; + } + return write; +} + +static int config_write_as_list(struct vty *vty); +static struct cmd_node as_list_node = { + .name = "as list", + .node = AS_LIST_NODE, + .prompt = "", + .config_write = config_write_as_list, +}; + +static void bgp_aspath_filter_cmd_completion(vector comps, + struct cmd_token *token) +{ + struct as_list *aslist; + + for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, aslist->name)); +} + +static const struct cmd_variable_handler aspath_filter_handlers[] = { + {.tokenname = "AS_PATH_FILTER_NAME", + .completions = bgp_aspath_filter_cmd_completion}, + {.completions = NULL}}; + +/* Register functions. */ +void bgp_filter_init(void) +{ + install_node(&as_list_node); + + install_element(CONFIG_NODE, &bgp_as_path_cmd); + install_element(CONFIG_NODE, &no_bgp_as_path_cmd); + install_element(CONFIG_NODE, &no_bgp_as_path_all_cmd); + + install_element(VIEW_NODE, &show_bgp_as_path_access_list_cmd); + install_element(VIEW_NODE, &show_ip_as_path_access_list_cmd); + install_element(VIEW_NODE, &show_bgp_as_path_access_list_all_cmd); + install_element(VIEW_NODE, &show_ip_as_path_access_list_all_cmd); + + cmd_variable_handler_register(aspath_filter_handlers); +} + +void bgp_filter_reset(void) +{ + struct as_list *aslist; + struct as_list *next; + + for (aslist = as_list_master.str.head; aslist; aslist = next) { + next = aslist->next; + as_list_delete(aslist); + } + + assert(as_list_master.str.head == NULL); + assert(as_list_master.str.tail == NULL); +} diff --git a/bgpd/bgp_filter.h b/bgpd/bgp_filter.h new file mode 100644 index 0000000..77a3f3c --- /dev/null +++ b/bgpd/bgp_filter.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS path filter list. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_FILTER_H +#define _QUAGGA_BGP_FILTER_H + +#include + +#define ASPATH_SEQ_NUMBER_AUTO -1 + +enum as_filter_type { AS_FILTER_DENY, AS_FILTER_PERMIT }; + +/* Element of AS path filter. */ +struct as_filter { + struct as_filter *next; + struct as_filter *prev; + + enum as_filter_type type; + + regex_t *reg; + char *reg_str; + + /* Sequence number. */ + int64_t seq; +}; + +PREDECL_DLIST(as_list_list); +/* AS path filter list. */ +struct as_list { + char *name; + + struct as_list *next; + struct as_list *prev; + + struct as_filter *head; + struct as_filter *tail; + + /* Changes in AS path */ + struct as_list_list_head exclude_rule; +}; + + +extern void bgp_filter_init(void); +extern void bgp_filter_reset(void); + +extern enum as_filter_type as_list_apply(struct as_list *, void *); + +extern struct as_list *as_list_lookup(const char *); +extern void as_list_add_hook(void (*func)(char *)); +extern void as_list_delete_hook(void (*func)(const char *)); +extern bool config_bgp_aspath_validate(const char *regstr); + +#endif /* _QUAGGA_BGP_FILTER_H */ diff --git a/bgpd/bgp_flowspec.c b/bgpd/bgp_flowspec.c new file mode 100644 index 0000000..6165bf8 --- /dev/null +++ b/bgpd/bgp_flowspec.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP FlowSpec for packet handling + * Portions: + * Copyright (C) 2017 ChinaTelecom SDN Group + * Copyright (C) 2018 6WIND + */ + +#include +#include + +#include "prefix.h" +#include "lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_flowspec_private.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" + +static int bgp_fs_nlri_validate(uint8_t *nlri_content, uint32_t len, + afi_t afi) +{ + uint32_t offset = 0; + int type; + int ret = 0, error = 0; + + while (offset < len-1) { + type = nlri_content[offset]; + offset++; + switch (type) { + case FLOWSPEC_DEST_PREFIX: + case FLOWSPEC_SRC_PREFIX: + ret = bgp_flowspec_ip_address( + BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, &error, + afi, NULL); + break; + case FLOWSPEC_FLOW_LABEL: + if (afi == AFI_IP) + return -1; + ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, &error); + break; + case FLOWSPEC_IP_PROTOCOL: + case FLOWSPEC_PORT: + case FLOWSPEC_DEST_PORT: + case FLOWSPEC_SRC_PORT: + case FLOWSPEC_ICMP_TYPE: + case FLOWSPEC_ICMP_CODE: + ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, &error); + break; + case FLOWSPEC_TCP_FLAGS: + case FLOWSPEC_FRAGMENT: + ret = bgp_flowspec_bitmask_decode( + BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, &error); + break; + case FLOWSPEC_PKT_LEN: + case FLOWSPEC_DSCP: + ret = bgp_flowspec_op_decode( + BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, &error); + break; + default: + error = -1; + break; + } + offset += ret; + if (error < 0) + break; + } + return error; +} + +int bgp_nlri_parse_flowspec(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, bool withdraw) +{ + uint8_t *pnt; + uint8_t *lim; + afi_t afi; + safi_t safi; + int psize = 0; + struct prefix p; + void *temp; + + /* Start processing the NLRI - there may be multiple in the MP_REACH */ + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + + /* + * All other AFI/SAFI's treat no attribute as a implicit + * withdraw. Flowspec should as well. + */ + if (!attr) + withdraw = true; + + if (packet->length >= FLOWSPEC_NLRI_SIZELIMIT_EXTENDED) { + flog_err(EC_BGP_FLOWSPEC_PACKET, + "BGP flowspec nlri length maximum reached (%u)", + packet->length); + return BGP_NLRI_PARSE_ERROR_FLOWSPEC_NLRI_SIZELIMIT; + } + + for (; pnt < lim; pnt += psize) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + /* All FlowSpec NLRI begin with length. */ + if (pnt + 1 > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + psize = *pnt++; + if (psize >= FLOWSPEC_NLRI_SIZELIMIT) { + psize &= 0x0f; + psize = psize << 8; + psize |= *pnt++; + } + /* When packet overflow occur return immediately. */ + if (pnt + psize > lim) { + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "Flowspec NLRI length inconsistent ( size %u seen)", + psize); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + if (psize == 0) { + flog_err(EC_BGP_FLOWSPEC_PACKET, + "Flowspec NLRI length 0 which makes no sense"); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + if (bgp_fs_nlri_validate(pnt, psize, afi) < 0) { + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "Bad flowspec format or NLRI options not supported"); + return BGP_NLRI_PARSE_ERROR_FLOWSPEC_BAD_FORMAT; + } + p.family = AF_FLOWSPEC; + p.prefixlen = 0; + /* Flowspec encoding is in bytes */ + p.u.prefix_flowspec.prefixlen = psize; + p.u.prefix_flowspec.family = afi2family(afi); + temp = XCALLOC(MTYPE_TMP, psize); + memcpy(temp, pnt, psize); + p.u.prefix_flowspec.ptr = (uintptr_t) temp; + + if (BGP_DEBUG(flowspec, FLOWSPEC)) { + char return_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; + char local_string[BGP_FLOWSPEC_NLRI_STRING_MAX*2+16]; + char ec_string[BGP_FLOWSPEC_NLRI_STRING_MAX]; + char *s = NULL; + + bgp_fs_nlri_get_string((unsigned char *) + p.u.prefix_flowspec.ptr, + p.u.prefix_flowspec.prefixlen, + return_string, + NLRI_STRING_FORMAT_MIN, NULL, + afi); + snprintf(ec_string, sizeof(ec_string), + "EC{none}"); + if (attr && bgp_attr_get_ecommunity(attr)) { + s = ecommunity_ecom2str( + bgp_attr_get_ecommunity(attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + snprintf(ec_string, sizeof(ec_string), + "EC{%s}", + s == NULL ? "none" : s); + + if (s) + ecommunity_strfree(&s); + } + snprintf(local_string, sizeof(local_string), + "FS Rx %s %s %s %s", withdraw ? + "Withdraw":"Update", + afi2str(afi), return_string, + attr != NULL ? ec_string : ""); + zlog_info("%s", local_string); + } + /* Process the route. */ + if (!withdraw) { + bgp_update(peer, &p, 0, attr, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, 0, NULL); + } else { + bgp_withdraw(peer, &p, 0, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, NULL, NULL, 0, NULL); + } + + XFREE(MTYPE_TMP, temp); + } + return BGP_NLRI_PARSE_OK; +} diff --git a/bgpd/bgp_flowspec.h b/bgpd/bgp_flowspec.h new file mode 100644 index 0000000..bc2f005 --- /dev/null +++ b/bgpd/bgp_flowspec.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Flowspec header for packet handling + * Copyright (C) 2018 6WIND + */ + +#ifndef _FRR_BGP_FLOWSPEC_H +#define _FRR_BGP_FLOWSPEC_H + +#define NLRI_STRING_FORMAT_LARGE 0 +#define NLRI_STRING_FORMAT_DEBUG 1 +#define NLRI_STRING_FORMAT_MIN 2 +#define NLRI_STRING_FORMAT_JSON 3 +#define NLRI_STRING_FORMAT_JSON_SIMPLE 4 + +#define BGP_FLOWSPEC_NLRI_STRING_MAX 512 + +extern int bgp_nlri_parse_flowspec(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, bool withdraw); + +extern void bgp_flowspec_vty_init(void); + +extern int bgp_show_table_flowspec(struct vty *vty, struct bgp *bgp, afi_t afi, + struct bgp_table *table, + enum bgp_show_type type, void *output_arg, + bool use_json, int is_last, + unsigned long *output_cum, + unsigned long *total_cum); + +extern void bgp_fs_nlri_get_string(unsigned char *nlri_content, size_t len, + char *return_string, int format, + json_object *json_path, + afi_t afi); + +extern void route_vty_out_flowspec(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + json_object *json_paths); +extern int bgp_fs_config_write_pbr(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi); + +extern int bgp_flowspec_display_match_per_ip(afi_t afi, struct bgp_table *rib, + struct prefix *match, + int prefix_check, struct vty *vty, + bool use_json, + json_object *json_paths); + +#endif /* _FRR_BGP_FLOWSPEC_H */ diff --git a/bgpd/bgp_flowspec_private.h b/bgpd/bgp_flowspec_private.h new file mode 100644 index 0000000..049cb6d --- /dev/null +++ b/bgpd/bgp_flowspec_private.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Flowspec header . private structs and defines + * Copyright (C) 2018 6WIND + */ + +#ifndef _FRR_BGP_FLOWSPEC_PRIVATE_H +#define _FRR_BGP_FLOWSPEC_PRIVATE_H + +#define FLOWSPEC_NLRI_SIZELIMIT 240 +#define FLOWSPEC_NLRI_SIZELIMIT_EXTENDED 4095 + +/* Flowspec raffic action bit*/ +#define FLOWSPEC_TRAFFIC_ACTION_TERMINAL 1 +#define FLOWSPEC_TRAFFIC_ACTION_SAMPLE 0 +#define FLOWSPEC_TRAFFIC_ACTION_DISTRIBUTE 1 + +/* Flow Spec Component Types */ +#define NUM_OF_FLOWSPEC_MATCH_TYPES 12 +#define FLOWSPEC_DEST_PREFIX 1 +#define FLOWSPEC_SRC_PREFIX 2 +#define FLOWSPEC_IP_PROTOCOL 3 +#define FLOWSPEC_PORT 4 +#define FLOWSPEC_DEST_PORT 5 +#define FLOWSPEC_SRC_PORT 6 +#define FLOWSPEC_ICMP_TYPE 7 +#define FLOWSPEC_ICMP_CODE 8 +#define FLOWSPEC_TCP_FLAGS 9 +#define FLOWSPEC_PKT_LEN 10 +#define FLOWSPEC_DSCP 11 +#define FLOWSPEC_FRAGMENT 12 +#define FLOWSPEC_FLOW_LABEL 13 /* For IPv6 only */ + +#endif /* _FRR_BGP_FLOWSPEC_PRIVATE_H */ diff --git a/bgpd/bgp_flowspec_util.c b/bgpd/bgp_flowspec_util.c new file mode 100644 index 0000000..66426ab --- /dev/null +++ b/bgpd/bgp_flowspec_util.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP FlowSpec Utilities + * Portions: + * Copyright (C) 2017 ChinaTelecom SDN Group + * Copyright (C) 2018 6WIND + */ + +#include "zebra.h" + +#include "lib/printfrr.h" + +#include "prefix.h" +#include "lib_errors.h" + +#include "bgp_route.h" +#include "bgp_table.h" +#include "bgp_flowspec_util.h" +#include "bgp_flowspec_private.h" +#include "bgp_pbr.h" +#include "bgp_errors.h" + +static void hex2bin(uint8_t *hex, int *bin) +{ + int remainder = *hex; + int i = 0; + + while (remainder >= 1 && i < 8) { + bin[7-i] = remainder % 2; + remainder = remainder / 2; + i++; + } + for (; i < 8; i++) + bin[7-i] = 0; +} + +static int hexstr2num(uint8_t *hexstr, int len) +{ + int i = 0; + int num = 0; + + for (i = 0; i < len; i++) + num = hexstr[i] + 16*16*num; + return num; +} + +/* call bgp_flowspec_op_decode + * returns offset + */ +static int bgp_flowspec_call_non_opaque_decode(uint8_t *nlri_content, int len, + struct bgp_pbr_match_val *mval, + uint8_t *match_num, int *error) +{ + int ret; + + ret = bgp_flowspec_op_decode( + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, + nlri_content, + len, + mval, error); + if (*error < 0) + flog_err(EC_BGP_FLOWSPEC_PACKET, + "%s: flowspec_op_decode error %d", __func__, *error); + else + *match_num = *error; + return ret; +} + + +bool bgp_flowspec_contains_prefix(const struct prefix *pfs, + struct prefix *input, int prefix_check) +{ + uint32_t offset = 0; + int type; + int ret = 0, error = 0; + uint8_t *nlri_content = (uint8_t *)pfs->u.prefix_flowspec.ptr; + size_t len = pfs->u.prefix_flowspec.prefixlen; + afi_t afi = family2afi(pfs->u.prefix_flowspec.family); + struct prefix compare; + + error = 0; + while (offset < len-1 && error >= 0) { + type = nlri_content[offset]; + offset++; + switch (type) { + case FLOWSPEC_DEST_PREFIX: + case FLOWSPEC_SRC_PREFIX: + memset(&compare, 0, sizeof(compare)); + ret = bgp_flowspec_ip_address( + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, + nlri_content+offset, + len - offset, + &compare, &error, + afi, NULL); + if (ret <= 0) + break; + if (prefix_check && + compare.prefixlen != input->prefixlen) + break; + if (compare.family != input->family) + break; + if ((input->family == AF_INET) && + IPV4_ADDR_SAME(&input->u.prefix4, + &compare.u.prefix4)) + return true; + if ((input->family == AF_INET6) && + IPV6_ADDR_SAME(&input->u.prefix6.s6_addr, + &compare.u.prefix6.s6_addr)) + return true; + break; + case FLOWSPEC_FLOW_LABEL: + if (afi == AFI_IP) { + error = -1; + continue; + } + ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content+offset, + len - offset, + NULL, &error); + break; + case FLOWSPEC_IP_PROTOCOL: + case FLOWSPEC_PORT: + case FLOWSPEC_DEST_PORT: + case FLOWSPEC_SRC_PORT: + case FLOWSPEC_ICMP_TYPE: + case FLOWSPEC_ICMP_CODE: + ret = bgp_flowspec_op_decode(BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content+offset, + len - offset, + NULL, &error); + break; + case FLOWSPEC_FRAGMENT: + case FLOWSPEC_TCP_FLAGS: + ret = bgp_flowspec_bitmask_decode( + BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content+offset, + len - offset, + NULL, &error); + break; + case FLOWSPEC_PKT_LEN: + case FLOWSPEC_DSCP: + ret = bgp_flowspec_op_decode( + BGP_FLOWSPEC_VALIDATE_ONLY, + nlri_content + offset, + len - offset, NULL, + &error); + break; + default: + error = -1; + break; + } + offset += ret; + } + return false; +} + +/* + * handle the flowspec address src/dst or generic address NLRI + * return number of bytes analysed ( >= 0). + */ +int bgp_flowspec_ip_address(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error, + afi_t afi, + uint8_t *ipv6_offset) +{ + char *display = (char *)result; /* for return_string */ + struct prefix *prefix = (struct prefix *)result; + uint32_t offset = 0; + struct prefix prefix_local; + int psize; + uint8_t prefix_offset = 0; + + *error = 0; + memset(&prefix_local, 0, sizeof(prefix_local)); + /* read the prefix length */ + prefix_local.prefixlen = nlri_ptr[offset]; + psize = PSIZE(prefix_local.prefixlen); + offset++; + prefix_local.family = afi2family(afi); + if (prefix_local.family == AF_INET6) { + prefix_offset = nlri_ptr[offset]; + if (ipv6_offset) + *ipv6_offset = prefix_offset; + offset++; + } + /* Prefix length check. */ + if (prefix_local.prefixlen > prefix_blen(&prefix_local) * 8) { + *error = -1; + return offset; + } + /* When packet overflow occur return immediately. */ + if (psize + offset > max_len) { + *error = -1; + return offset; + } + /* Defensive coding, double-check + * the psize fits in a struct prefix + */ + if (psize > (ssize_t)sizeof(prefix_local.u)) { + *error = -1; + return offset; + } + + memcpy(&prefix_local.u.prefix, &nlri_ptr[offset], psize); + offset += psize; + switch (type) { + case BGP_FLOWSPEC_RETURN_STRING: + if (prefix_local.family == AF_INET6) { + int ret; + + ret = snprintfrr( + display, BGP_FLOWSPEC_STRING_DISPLAY_MAX, + "%pFX/off %u", &prefix_local, prefix_offset); + if (ret < 0) { + *error = -1; + break; + } + } else + prefix2str(&prefix_local, display, + BGP_FLOWSPEC_STRING_DISPLAY_MAX); + break; + case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: + if (prefix) + prefix_copy(prefix, &prefix_local); + break; + case BGP_FLOWSPEC_VALIDATE_ONLY: + case BGP_FLOWSPEC_RETURN_JSON: + break; + } + return offset; +} + +/* + * handle the flowspec operator NLRI + * return number of bytes analysed + * if there is an error, the passed error param is used to give error: + * -1 if decoding error, + * if result is a string, its assumed length + * is BGP_FLOWSPEC_STRING_DISPLAY_MAX + */ +int bgp_flowspec_op_decode(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error) +{ + int op[8]; + int len, value, value_size; + int loop = 0; + char *ptr = (char *)result; /* for return_string */ + uint32_t offset = 0; + int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; + int len_written; + struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; + + *error = 0; + do { + if (loop > BGP_PBR_MATCH_VAL_MAX) + *error = -2; + hex2bin(&nlri_ptr[offset], op); + offset++; + len = 2*op[2]+op[3]; + value_size = 1 << len; + value = hexstr2num(&nlri_ptr[offset], value_size); + /* can not be < and > at the same time */ + if (op[5] == 1 && op[6] == 1) + *error = -1; + /* if first element, AND bit can not be set */ + if (op[1] == 1 && loop == 0) + *error = -1; + switch (type) { + case BGP_FLOWSPEC_RETURN_STRING: + if (loop) { + len_written = snprintf(ptr, len_string, + ", "); + len_string -= len_written; + ptr += len_written; + } + if (op[5] == 1) { + len_written = snprintf(ptr, len_string, + "<"); + len_string -= len_written; + ptr += len_written; + } + if (op[6] == 1) { + len_written = snprintf(ptr, len_string, + ">"); + len_string -= len_written; + ptr += len_written; + } + if (op[7] == 1) { + len_written = snprintf(ptr, len_string, + "="); + len_string -= len_written; + ptr += len_written; + } + len_written = snprintf(ptr, len_string, + " %d ", value); + len_string -= len_written; + ptr += len_written; + break; + case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: + /* limitation: stop converting */ + if (*error == -2) + break; + mval->value = value; + if (op[5] == 1) + mval->compare_operator |= + OPERATOR_COMPARE_LESS_THAN; + if (op[6] == 1) + mval->compare_operator |= + OPERATOR_COMPARE_GREATER_THAN; + if (op[7] == 1) + mval->compare_operator |= + OPERATOR_COMPARE_EQUAL_TO; + if (op[1] == 1) + mval->unary_operator = OPERATOR_UNARY_AND; + else + mval->unary_operator = OPERATOR_UNARY_OR; + mval++; + break; + case BGP_FLOWSPEC_VALIDATE_ONLY: + case BGP_FLOWSPEC_RETURN_JSON: + /* no action */ + break; + } + offset += value_size; + loop++; + } while (op[0] == 0 && offset < max_len - 1); + if (offset > max_len) + *error = -1; + /* use error parameter to count the number of entries */ + if (*error == 0) + *error = loop; + return offset; +} + + +/* + * handle the flowspec tcpflags or fragment field + * return number of bytes analysed + * if there is an error, the passed error param is used to give error: + * -1 if decoding error, + * if result is a string, its assumed length + * is BGP_FLOWSPEC_STRING_DISPLAY_MAX + */ +int bgp_flowspec_bitmask_decode(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error) +{ + int op[8]; + int len, value_size, loop = 0, value; + char *ptr = (char *)result; /* for return_string */ + struct bgp_pbr_match_val *mval = (struct bgp_pbr_match_val *)result; + uint32_t offset = 0; + int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; + int len_written; + + *error = 0; + do { + if (loop > BGP_PBR_MATCH_VAL_MAX) { + *error = -2; + return offset; + } + hex2bin(&nlri_ptr[offset], op); + /* if first element, AND bit can not be set */ + if (op[1] == 1 && loop == 0) + *error = -1; + offset++; + len = 2 * op[2] + op[3]; + value_size = 1 << len; + value = hexstr2num(&nlri_ptr[offset], value_size); + switch (type) { + case BGP_FLOWSPEC_RETURN_STRING: + if (op[1] == 1 && loop != 0) { + len_written = snprintf(ptr, len_string, + ",&"); + len_string -= len_written; + ptr += len_written; + } else if (op[1] == 0 && loop != 0) { + len_written = snprintf(ptr, len_string, + ",|"); + len_string -= len_written; + ptr += len_written; + } + if (op[7] == 1) { + len_written = snprintf(ptr, len_string, + "= "); + len_string -= len_written; + ptr += len_written; + } else { + len_written = snprintf(ptr, len_string, + "∋ "); + len_string -= len_written; + ptr += len_written; + } + if (op[6] == 1) { + len_written = snprintf(ptr, len_string, + "! "); + len_string -= len_written; + ptr += len_written; + } + len_written = snprintf(ptr, len_string, + "%d", value); + len_string -= len_written; + ptr += len_written; + break; + case BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE: + /* limitation: stop converting */ + if (*error == -2) + break; + mval->value = value; + if (op[6] == 1) { + /* different from */ + mval->compare_operator |= + OPERATOR_COMPARE_LESS_THAN; + mval->compare_operator |= + OPERATOR_COMPARE_GREATER_THAN; + } else + mval->compare_operator |= + OPERATOR_COMPARE_EQUAL_TO; + if (op[7] == 1) + mval->compare_operator |= + OPERATOR_COMPARE_EXACT_MATCH; + if (op[1] == 1) + mval->unary_operator = + OPERATOR_UNARY_AND; + else + mval->unary_operator = + OPERATOR_UNARY_OR; + mval++; + break; + case BGP_FLOWSPEC_VALIDATE_ONLY: + case BGP_FLOWSPEC_RETURN_JSON: + /* no action */ + break; + } + offset += value_size; + loop++; + } while (op[0] == 0 && offset < max_len - 1); + if (offset > max_len) + *error = -1; + /* use error parameter to count the number of entries */ + if (*error == 0) + *error = loop; + return offset; +} + +int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len, + struct bgp_pbr_entry_main *bpem, + afi_t afi) +{ + int offset = 0, error = 0; + struct prefix *prefix; + struct bgp_pbr_match_val *mval; + uint8_t *match_num; + uint8_t bitmask = 0; + int ret = 0, type; + uint8_t *prefix_offset; + + while (offset < len - 1 && error >= 0) { + type = nlri_content[offset]; + offset++; + switch (type) { + case FLOWSPEC_DEST_PREFIX: + case FLOWSPEC_SRC_PREFIX: + bitmask = 0; + if (type == FLOWSPEC_DEST_PREFIX) { + bitmask |= PREFIX_DST_PRESENT; + prefix = &bpem->dst_prefix; + prefix_offset = &bpem->dst_prefix_offset; + } else { + bitmask |= PREFIX_SRC_PRESENT; + prefix = &bpem->src_prefix; + prefix_offset = &bpem->src_prefix_offset; + } + ret = bgp_flowspec_ip_address( + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, + nlri_content + offset, + len - offset, + prefix, &error, + afi, prefix_offset); + if (error < 0) + flog_err(EC_BGP_FLOWSPEC_PACKET, + "%s: flowspec_ip_address error %d", + __func__, error); + else { + /* if src or dst address is 0.0.0.0, + * ignore that rule + */ + if (prefix->family == AF_INET + && prefix->u.prefix4.s_addr == INADDR_ANY) + bpem->match_bitmask_iprule |= bitmask; + else if (prefix->family == AF_INET6 + && !memcmp(&prefix->u.prefix6, + &in6addr_any, + sizeof(struct in6_addr))) + bpem->match_bitmask_iprule |= bitmask; + else + bpem->match_bitmask |= bitmask; + } + offset += ret; + break; + case FLOWSPEC_FLOW_LABEL: + if (afi == AFI_IP) { + error = -1; + continue; + } + match_num = &(bpem->match_flowlabel_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->flow_label); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_IP_PROTOCOL: + match_num = &(bpem->match_protocol_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->protocol); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_PORT: + match_num = &(bpem->match_port_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->port); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_DEST_PORT: + match_num = &(bpem->match_dst_port_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->dst_port); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_SRC_PORT: + match_num = &(bpem->match_src_port_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->src_port); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_ICMP_TYPE: + match_num = &(bpem->match_icmp_type_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->icmp_type); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_ICMP_CODE: + match_num = &(bpem->match_icmp_code_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->icmp_code); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_PKT_LEN: + match_num = + &(bpem->match_packet_length_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->packet_length); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_DSCP: + match_num = &(bpem->match_dscp_num); + mval = (struct bgp_pbr_match_val *) + &(bpem->dscp); + offset += bgp_flowspec_call_non_opaque_decode( + nlri_content + offset, + len - offset, + mval, match_num, + &error); + break; + case FLOWSPEC_TCP_FLAGS: + ret = bgp_flowspec_bitmask_decode( + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, + nlri_content + offset, + len - offset, + &bpem->tcpflags, &error); + if (error < 0) + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "%s: flowspec_tcpflags_decode error %d", + __func__, error); + else + bpem->match_tcpflags_num = error; + /* contains the number of slots used */ + offset += ret; + break; + case FLOWSPEC_FRAGMENT: + ret = bgp_flowspec_bitmask_decode( + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE, + nlri_content + offset, + len - offset, &bpem->fragment, + &error); + if (error < 0) + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "%s: flowspec_fragment_type_decode error %d", + __func__, error); + else + bpem->match_fragment_num = error; + offset += ret; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unknown type %d", + __func__, type); + } + } + if (bpem->match_packet_length_num || bpem->match_fragment_num + || bpem->match_tcpflags_num || bpem->match_dscp_num + || bpem->match_icmp_code_num || bpem->match_icmp_type_num + || bpem->match_port_num || bpem->match_src_port_num + || bpem->match_dst_port_num || bpem->match_protocol_num + || bpem->match_bitmask || bpem->match_flowlabel_num) + bpem->type = BGP_PBR_IPSET; + else if ((bpem->match_bitmask_iprule & PREFIX_SRC_PRESENT) || + (bpem->match_bitmask_iprule & PREFIX_DST_PRESENT)) + /* the extracted policy rule may not need an + * iptables/ipset filtering. check this may not be + * a standard ip rule : permit any to any ( eg) + */ + bpem->type = BGP_PBR_IPRULE; + else + bpem->type = BGP_PBR_UNDEFINED; + return error; +} + +/* return 1 if FS entry invalid or no NH IP */ +bool bgp_flowspec_get_first_nh(struct bgp *bgp, struct bgp_path_info *pi, + struct prefix *p, afi_t afi) +{ + struct bgp_pbr_entry_main api; + int i; + struct bgp_dest *dest = pi->net; + struct bgp_pbr_entry_action *api_action; + + memset(&api, 0, sizeof(api)); + if (bgp_pbr_build_and_validate_entry(bgp_dest_get_prefix(dest), pi, + &api) + < 0) + return true; + for (i = 0; i < api.action_num; i++) { + api_action = &api.actions[i]; + if (api_action->action != ACTION_REDIRECT_IP) + continue; + p->family = afi2family(afi); + if (afi == AFI_IP) { + p->prefixlen = IPV4_MAX_BITLEN; + p->u.prefix4 = api_action->u.zr.redirect_ip_v4; + } else { + p->prefixlen = IPV6_MAX_BITLEN; + memcpy(&p->u.prefix6, &api_action->u.zr.redirect_ip_v6, + sizeof(struct in6_addr)); + } + return false; + } + return true; +} diff --git a/bgpd/bgp_flowspec_util.h b/bgpd/bgp_flowspec_util.h new file mode 100644 index 0000000..de15660 --- /dev/null +++ b/bgpd/bgp_flowspec_util.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Flowspec header for utilities + * Copyright (C) 2018 6WIND + */ + +#ifndef _FRR_BGP_FLOWSPEC_UTIL_H +#define _FRR_BGP_FLOWSPEC_UTIL_H + +#include "zclient.h" + +#define BGP_FLOWSPEC_STRING_DISPLAY_MAX 512 + +enum bgp_flowspec_util_nlri_t { + BGP_FLOWSPEC_VALIDATE_ONLY = 0, + BGP_FLOWSPEC_RETURN_STRING = 1, + BGP_FLOWSPEC_CONVERT_TO_NON_OPAQUE = 2, + BGP_FLOWSPEC_RETURN_JSON = 3, +}; + + +extern int bgp_flowspec_op_decode(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error); + +extern int bgp_flowspec_ip_address(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error, + afi_t afi, uint8_t *ipv6_offset); + +extern int bgp_flowspec_bitmask_decode(enum bgp_flowspec_util_nlri_t type, + uint8_t *nlri_ptr, + uint32_t max_len, + void *result, int *error); + +struct bgp_pbr_entry_main; +extern int bgp_flowspec_match_rules_fill(uint8_t *nlri_content, int len, + struct bgp_pbr_entry_main *bpem, + afi_t afi); + +extern bool bgp_flowspec_contains_prefix(const struct prefix *pfs, + struct prefix *input, + int prefix_check); + +extern bool bgp_flowspec_get_first_nh(struct bgp *bgp, struct bgp_path_info *pi, + struct prefix *nh, afi_t afi); + +#endif /* _FRR_BGP_FLOWSPEC_UTIL_H */ diff --git a/bgpd/bgp_flowspec_vty.c b/bgpd/bgp_flowspec_vty.c new file mode 100644 index 0000000..d4ccca8 --- /dev/null +++ b/bgpd/bgp_flowspec_vty.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP FlowSpec VTY + * Copyright (C) 2018 6WIND + */ + +#include +#include "command.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_flowspec_private.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_pbr.h" + +/* Local Structures and variables declarations + * This code block hosts the struct declared that host the flowspec rules + * as well as some structure used to convert to stringx + */ + +static const struct message bgp_flowspec_display_large[] = { + {FLOWSPEC_DEST_PREFIX, "Destination Address"}, + {FLOWSPEC_SRC_PREFIX, "Source Address"}, + {FLOWSPEC_IP_PROTOCOL, "IP Protocol"}, + {FLOWSPEC_PORT, "Port"}, + {FLOWSPEC_DEST_PORT, "Destination Port"}, + {FLOWSPEC_SRC_PORT, "Source Port"}, + {FLOWSPEC_ICMP_TYPE, "ICMP Type"}, + {FLOWSPEC_ICMP_CODE, "ICMP Code"}, + {FLOWSPEC_TCP_FLAGS, "TCP Flags"}, + {FLOWSPEC_PKT_LEN, "Packet Length"}, + {FLOWSPEC_DSCP, "DSCP field"}, + {FLOWSPEC_FRAGMENT, "Packet Fragment"}, + {FLOWSPEC_FLOW_LABEL, "Packet Flow Label"}, + {0} +}; + +static const struct message bgp_flowspec_display_min[] = { + {FLOWSPEC_DEST_PREFIX, "to"}, + {FLOWSPEC_SRC_PREFIX, "from"}, + {FLOWSPEC_IP_PROTOCOL, "proto"}, + {FLOWSPEC_PORT, "port"}, + {FLOWSPEC_DEST_PORT, "dstp"}, + {FLOWSPEC_SRC_PORT, "srcp"}, + {FLOWSPEC_ICMP_TYPE, "type"}, + {FLOWSPEC_ICMP_CODE, "code"}, + {FLOWSPEC_TCP_FLAGS, "tcp"}, + {FLOWSPEC_PKT_LEN, "pktlen"}, + {FLOWSPEC_DSCP, "dscp"}, + {FLOWSPEC_FRAGMENT, "pktfrag"}, + {FLOWSPEC_FLOW_LABEL, "flwlbl"}, + {0} +}; + +#define FS_STRING_UPDATE(count, ptr, format, remaining_len) do { \ + int _len_written; \ + \ + if (((format) == NLRI_STRING_FORMAT_DEBUG) && (count)) {\ + _len_written = snprintf((ptr), (remaining_len), \ + ", "); \ + (remaining_len) -= _len_written; \ + (ptr) += _len_written; \ + } else if (((format) == NLRI_STRING_FORMAT_MIN) \ + && (count)) { \ + _len_written = snprintf((ptr), (remaining_len), \ + " "); \ + (remaining_len) -= _len_written; \ + (ptr) += _len_written; \ + } \ + count++; \ + } while (0) + +/* Parse FLOWSPEC NLRI + * passed return_string string has assumed length + * BGP_FLOWSPEC_STRING_DISPLAY_MAX + */ +void bgp_fs_nlri_get_string(unsigned char *nlri_content, size_t len, + char *return_string, int format, + json_object *json_path, + afi_t afi) +{ + uint32_t offset = 0; + int type; + int ret = 0, error = 0; + char *ptr = return_string; + char local_string[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; + int count = 0; + char extra[2] = ""; + char pre_extra[2] = ""; + const struct message *bgp_flowspec_display; + enum bgp_flowspec_util_nlri_t type_util; + int len_string = BGP_FLOWSPEC_STRING_DISPLAY_MAX; + int len_written; + + if (format == NLRI_STRING_FORMAT_LARGE) { + snprintf(pre_extra, sizeof(pre_extra), "\t"); + snprintf(extra, sizeof(extra), "\n"); + bgp_flowspec_display = bgp_flowspec_display_large; + } else + bgp_flowspec_display = bgp_flowspec_display_min; + /* if needed. type_util can be set to other values */ + type_util = BGP_FLOWSPEC_RETURN_STRING; + error = 0; + while (offset < len-1 && error >= 0) { + type = nlri_content[offset]; + offset++; + switch (type) { + case FLOWSPEC_DEST_PREFIX: + case FLOWSPEC_SRC_PREFIX: + ret = bgp_flowspec_ip_address( + type_util, + nlri_content+offset, + len - offset, + local_string, &error, + afi, NULL); + if (ret <= 0) + break; + if (json_path) { + json_object_string_add(json_path, + lookup_msg(bgp_flowspec_display, type, ""), + local_string); + break; + } + FS_STRING_UPDATE(count, ptr, format, len_string); + len_written = snprintf(ptr, len_string, "%s%s %s%s", + pre_extra, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string, extra); + len_string -= len_written; + ptr += len_written; + break; + case FLOWSPEC_FLOW_LABEL: + case FLOWSPEC_IP_PROTOCOL: + case FLOWSPEC_PORT: + case FLOWSPEC_DEST_PORT: + case FLOWSPEC_SRC_PORT: + case FLOWSPEC_ICMP_TYPE: + case FLOWSPEC_ICMP_CODE: + ret = bgp_flowspec_op_decode(type_util, + nlri_content+offset, + len - offset, + local_string, &error); + if (ret <= 0) + break; + if (json_path) { + json_object_string_add(json_path, + lookup_msg(bgp_flowspec_display, type, ""), + local_string); + break; + } + FS_STRING_UPDATE(count, ptr, format, len_string); + len_written = snprintf(ptr, len_string, "%s%s %s%s", + pre_extra, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string, extra); + len_string -= len_written; + ptr += len_written; + break; + case FLOWSPEC_TCP_FLAGS: + ret = bgp_flowspec_bitmask_decode( + type_util, + nlri_content+offset, + len - offset, + local_string, &error); + if (ret <= 0) + break; + if (json_path) { + json_object_string_add(json_path, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string); + break; + } + FS_STRING_UPDATE(count, ptr, format, len_string); + len_written = snprintf(ptr, len_string, "%s%s %s%s", + pre_extra, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string, extra); + len_string -= len_written; + ptr += len_written; + break; + case FLOWSPEC_PKT_LEN: + case FLOWSPEC_DSCP: + ret = bgp_flowspec_op_decode( + type_util, + nlri_content + offset, + len - offset, local_string, + &error); + if (ret <= 0) + break; + if (json_path) { + json_object_string_add(json_path, + lookup_msg(bgp_flowspec_display, type, ""), + local_string); + break; + } + FS_STRING_UPDATE(count, ptr, format, len_string); + len_written = snprintf(ptr, len_string, "%s%s %s%s", + pre_extra, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string, extra); + len_string -= len_written; + ptr += len_written; + break; + case FLOWSPEC_FRAGMENT: + ret = bgp_flowspec_bitmask_decode( + type_util, + nlri_content+offset, + len - offset, + local_string, &error); + if (ret <= 0) + break; + if (json_path) { + json_object_string_add(json_path, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string); + break; + } + FS_STRING_UPDATE(count, ptr, format, len_string); + len_written = snprintf(ptr, len_string, "%s%s %s%s", + pre_extra, + lookup_msg(bgp_flowspec_display, + type, ""), + local_string, extra); + len_string -= len_written; + ptr += len_written; + break; + default: + error = -1; + break; + } + offset += ret; + } +} + +void route_vty_out_flowspec(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + json_object *json_paths) +{ + struct attr *attr; + char return_string[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; + char *s1 = NULL, *s2 = NULL; + json_object *json_nlri_path = NULL; + json_object *json_ecom_path = NULL; + json_object *json_time_path = NULL; + char timebuf[BGP_UPTIME_LEN]; + struct ecommunity *ipv6_ecomm = NULL; + + if (p == NULL || p->family != AF_FLOWSPEC) + return; + if (json_paths) { + if (display == NLRI_STRING_FORMAT_JSON) + json_nlri_path = json_object_new_object(); + else + json_nlri_path = json_paths; + } + if (display == NLRI_STRING_FORMAT_LARGE && path) + vty_out(vty, "BGP flowspec entry: (flags 0x%x)\n", + path->flags); + bgp_fs_nlri_get_string((unsigned char *) + p->u.prefix_flowspec.ptr, + p->u.prefix_flowspec.prefixlen, + return_string, + display, + json_nlri_path, + family2afi(p->u.prefix_flowspec + .family)); + if (display == NLRI_STRING_FORMAT_LARGE) + vty_out(vty, "%s", return_string); + else if (display == NLRI_STRING_FORMAT_DEBUG) + vty_out(vty, "%s", return_string); + else if (display == NLRI_STRING_FORMAT_MIN) + vty_out(vty, " %-30s", return_string); + else if (json_paths && display == NLRI_STRING_FORMAT_JSON) + json_object_array_add(json_paths, json_nlri_path); + if (!path) + return; + + if (path->attr) + ipv6_ecomm = bgp_attr_get_ipv6_ecommunity(path->attr); + + if (path->attr && (bgp_attr_get_ecommunity(path->attr) || ipv6_ecomm)) { + /* Print attribute */ + attr = path->attr; + if (bgp_attr_get_ecommunity(attr)) + s1 = ecommunity_ecom2str(bgp_attr_get_ecommunity(attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, + 0); + if (ipv6_ecomm) + s2 = ecommunity_ecom2str( + ipv6_ecomm, ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + if (!s1 && !s2) + return; + if (display == NLRI_STRING_FORMAT_LARGE) + vty_out(vty, "\t%s%s%s\n", s1 ? s1 : "", + s2 && s1 ? " " : "", s2 ? s2 : ""); + else if (display == NLRI_STRING_FORMAT_MIN) + vty_out(vty, "%s%s", s1 ? s1 : "", s2 ? s2 : ""); + else if (json_paths) { + json_ecom_path = json_object_new_object(); + if (s1) + json_object_string_add(json_ecom_path, + "ecomlist", s1); + if (s2) + json_object_string_add(json_ecom_path, + "ecom6list", s2); + if (display == NLRI_STRING_FORMAT_JSON) + json_object_array_add(json_paths, + json_ecom_path); + } + if (display == NLRI_STRING_FORMAT_LARGE) { + char local_buff[INET6_ADDRSTRLEN]; + + local_buff[0] = '\0'; + if (p->u.prefix_flowspec.family == AF_INET + && attr->nexthop.s_addr != INADDR_ANY) + inet_ntop(AF_INET, &attr->nexthop.s_addr, + local_buff, sizeof(local_buff)); + else if (p->u.prefix_flowspec.family == AF_INET6 && + attr->mp_nexthop_len != 0 && + attr->mp_nexthop_len != BGP_ATTR_NHLEN_IPV4 && + attr->mp_nexthop_len != BGP_ATTR_NHLEN_VPNV4) + inet_ntop(AF_INET6, &attr->mp_nexthop_global, + local_buff, sizeof(local_buff)); + if (local_buff[0] != '\0') + vty_out(vty, "\tNLRI NH %s\n", + local_buff); + } + XFREE(MTYPE_ECOMMUNITY_STR, s1); + XFREE(MTYPE_ECOMMUNITY_STR, s2); + } + peer_uptime(path->uptime, timebuf, BGP_UPTIME_LEN, 0, NULL); + if (display == NLRI_STRING_FORMAT_LARGE) { + vty_out(vty, "\treceived for %8s\n", timebuf); + } else if (json_paths) { + json_time_path = json_object_new_object(); + json_object_string_add(json_time_path, + "time", timebuf); + if (display == NLRI_STRING_FORMAT_JSON) + json_object_array_add(json_paths, json_time_path); + } + if (display == NLRI_STRING_FORMAT_LARGE) { + struct bgp_path_info_extra *extra = + bgp_path_info_extra_get(path); + bool list_began = false; + + if (extra->flowspec && extra->flowspec->bgp_fs_pbr && + listcount(extra->flowspec->bgp_fs_pbr)) { + struct listnode *node; + struct bgp_pbr_match_entry *bpme; + struct bgp_pbr_match *bpm; + struct list *list_bpm; + + list_bpm = list_new(); + vty_out(vty, "\tinstalled in PBR"); + for (ALL_LIST_ELEMENTS_RO(extra->flowspec->bgp_fs_pbr, node, + bpme)) { + bpm = bpme->backpointer; + if (listnode_lookup(list_bpm, bpm)) + continue; + listnode_add(list_bpm, bpm); + if (!list_began) { + vty_out(vty, " ("); + list_began = true; + } else + vty_out(vty, ", "); + vty_out(vty, "%s", bpm->ipset_name); + } + list_delete(&list_bpm); + } + if (extra->flowspec && extra->flowspec->bgp_fs_iprule && + listcount(extra->flowspec->bgp_fs_iprule)) { + struct listnode *node; + struct bgp_pbr_rule *bpr; + + if (!list_began) + vty_out(vty, "\tinstalled in PBR"); + for (ALL_LIST_ELEMENTS_RO(extra->flowspec->bgp_fs_iprule, + node, bpr)) { + if (!bpr->action) + continue; + if (!list_began) { + vty_out(vty, " ("); + list_began = true; + } else + vty_out(vty, ", "); + vty_out(vty, "-ipv4-rule %d action lookup %u-", + bpr->priority, + bpr->action->table_id); + } + } + if (list_began) + vty_out(vty, ")\n"); + else + vty_out(vty, "\tnot installed in PBR\n"); + } +} + +int bgp_show_table_flowspec(struct vty *vty, struct bgp *bgp, afi_t afi, + struct bgp_table *table, enum bgp_show_type type, + void *output_arg, bool use_json, int is_last, + unsigned long *output_cum, unsigned long *total_cum) +{ + struct bgp_path_info *pi; + struct bgp_dest *dest; + unsigned long total_count = 0; + json_object *json_paths = NULL; + int display = NLRI_STRING_FORMAT_LARGE; + + if (type != bgp_show_type_detail) + return CMD_SUCCESS; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + pi = bgp_dest_get_bgp_path_info(dest); + if (pi == NULL) + continue; + if (use_json) { + json_paths = json_object_new_array(); + display = NLRI_STRING_FORMAT_JSON; + } + for (; pi; pi = pi->next) { + total_count++; + route_vty_out_flowspec(vty, bgp_dest_get_prefix(dest), + pi, display, json_paths); + } + if (use_json) { + vty_json(vty, json_paths); + json_paths = NULL; + } + } + if (total_count && !use_json) + vty_out(vty, + "\nDisplayed %ld flowspec entries\n", + total_count); + return CMD_SUCCESS; +} + +DEFUN (debug_bgp_flowspec, + debug_bgp_flowspec_cmd, + "debug bgp flowspec", + DEBUG_STR + BGP_STR + "BGP allow flowspec debugging entries\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_ON(flowspec, FLOWSPEC); + else { + TERM_DEBUG_ON(flowspec, FLOWSPEC); + vty_out(vty, "BGP flowspec debugging is on\n"); + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_bgp_flowspec, + no_debug_bgp_flowspec_cmd, + "no debug bgp flowspec", + NO_STR + DEBUG_STR + BGP_STR + "BGP allow flowspec debugging entries\n") +{ + if (vty->node == CONFIG_NODE) + DEBUG_OFF(flowspec, FLOWSPEC); + else { + TERM_DEBUG_OFF(flowspec, FLOWSPEC); + vty_out(vty, "BGP flowspec debugging is off\n"); + } + return CMD_SUCCESS; +} + +int bgp_fs_config_write_pbr(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + struct bgp_pbr_interface *pbr_if; + bool declare_node = false; + struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; + struct bgp_pbr_interface_head *head; + bool bgp_pbr_interface_any; + + if (!bgp_pbr_cfg || safi != SAFI_FLOWSPEC) + return 0; + if (afi == AFI_IP) { + head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); + bgp_pbr_interface_any = bgp_pbr_cfg->pbr_interface_any_ipv4; + } else if (afi == AFI_IP6) { + head = &(bgp_pbr_cfg->ifaces_by_name_ipv6); + bgp_pbr_interface_any = bgp_pbr_cfg->pbr_interface_any_ipv6; + } else { + return 0; + } + if (!RB_EMPTY(bgp_pbr_interface_head, head) || + !bgp_pbr_interface_any) + declare_node = true; + RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { + vty_out(vty, " local-install %s\n", pbr_if->name); + } + return declare_node ? 1 : 0; +} + +static int bgp_fs_local_install_interface(struct bgp *bgp, + const char *no, const char *ifname, + afi_t afi) +{ + struct bgp_pbr_interface *pbr_if; + struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; + struct bgp_pbr_interface_head *head; + bool *bgp_pbr_interface_any; + + if (!bgp_pbr_cfg) + return CMD_SUCCESS; + if (afi == AFI_IP) { + head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); + bgp_pbr_interface_any = &(bgp_pbr_cfg->pbr_interface_any_ipv4); + } else { + head = &(bgp_pbr_cfg->ifaces_by_name_ipv6); + bgp_pbr_interface_any = &(bgp_pbr_cfg->pbr_interface_any_ipv6); + } + if (no) { + if (!ifname) { + if (*bgp_pbr_interface_any) { + *bgp_pbr_interface_any = false; + /* remove all other interface list */ + bgp_pbr_reset(bgp, afi); + } + return CMD_SUCCESS; + } + pbr_if = bgp_pbr_interface_lookup(ifname, head); + if (!pbr_if) + return CMD_SUCCESS; + RB_REMOVE(bgp_pbr_interface_head, head, pbr_if); + return CMD_SUCCESS; + } + if (ifname) { + pbr_if = bgp_pbr_interface_lookup(ifname, head); + if (pbr_if) + return CMD_SUCCESS; + pbr_if = XCALLOC(MTYPE_TMP, + sizeof(struct bgp_pbr_interface)); + strlcpy(pbr_if->name, ifname, IFNAMSIZ); + RB_INSERT(bgp_pbr_interface_head, head, pbr_if); + *bgp_pbr_interface_any = false; + } else { + /* set to default */ + if (!*bgp_pbr_interface_any) { + /* remove all other interface list + */ + bgp_pbr_reset(bgp, afi); + *bgp_pbr_interface_any = true; + } + } + return CMD_SUCCESS; +} + +DEFUN (bgp_fs_local_install_ifname, + bgp_fs_local_install_ifname_cmd, + "[no] local-install INTERFACE", + NO_STR + "Apply local policy routing\n" + "Interface name\n") +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int idx = 0; + const char *no = strmatch(argv[0]->text, "no") ? "no" : NULL; + char *ifname = argv_find(argv, argc, "INTERFACE", &idx) ? + argv[idx]->arg : NULL; + + return bgp_fs_local_install_interface(bgp, no, ifname, + bgp_node_afi(vty)); +} + +extern int bgp_flowspec_display_match_per_ip(afi_t afi, struct bgp_table *rib, + struct prefix *match, + int prefix_check, struct vty *vty, + bool use_json, + json_object *json_paths) +{ + struct bgp_dest *dest; + const struct prefix *prefix; + int display = 0; + + for (dest = bgp_table_top(rib); dest; dest = bgp_route_next(dest)) { + prefix = bgp_dest_get_prefix(dest); + + if (prefix->family != AF_FLOWSPEC) + continue; + + if (bgp_flowspec_contains_prefix(prefix, match, prefix_check)) { + route_vty_out_flowspec( + vty, prefix, bgp_dest_get_bgp_path_info(dest), + use_json ? NLRI_STRING_FORMAT_JSON + : NLRI_STRING_FORMAT_LARGE, + json_paths); + display++; + } + } + return display; +} + +void bgp_flowspec_vty_init(void) +{ + install_element(ENABLE_NODE, &debug_bgp_flowspec_cmd); + install_element(CONFIG_NODE, &debug_bgp_flowspec_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_flowspec_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_flowspec_cmd); + install_element(BGP_FLOWSPECV4_NODE, &bgp_fs_local_install_ifname_cmd); + install_element(BGP_FLOWSPECV6_NODE, &bgp_fs_local_install_ifname_cmd); +} diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c new file mode 100644 index 0000000..d41ef8a --- /dev/null +++ b/bgpd/bgp_fsm.c @@ -0,0 +1,3174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP-4 Finite State Machine + * From RFC1771 [A Border Gateway Protocol 4 (BGP-4)] + * Copyright (C) 1996, 97, 98 Kunihiro Ishiguro + */ + +#include + +#include "linklist.h" +#include "prefix.h" +#include "sockunion.h" +#include "frrevent.h" +#include "log.h" +#include "stream.h" +#include "ringbuf.h" +#include "memory.h" +#include "plist.h" +#include "workqueue.h" +#include "queue.h" +#include "filter.h" +#include "command.h" +#include "lib_errors.h" +#include "zclient.h" +#include "lib/json.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_memory.h" +#include "bgpd/bgp_keepalives.h" +#include "bgpd/bgp_io.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" + +DEFINE_HOOK(peer_backward_transition, (struct peer * peer), (peer)); +DEFINE_HOOK(peer_status_changed, (struct peer * peer), (peer)); + +/* Definition of display strings corresponding to FSM events. This should be + * kept consistent with the events defined in bgpd.h + */ +static const char *const bgp_event_str[] = { + NULL, + "BGP_Start", + "BGP_Stop", + "TCP_connection_open", + "TCP_connection_open_w_delay", + "TCP_connection_closed", + "TCP_connection_open_failed", + "TCP_fatal_error", + "ConnectRetry_timer_expired", + "Hold_Timer_expired", + "KeepAlive_timer_expired", + "DelayOpen_timer_expired", + "Receive_OPEN_message", + "Receive_KEEPALIVE_message", + "Receive_UPDATE_message", + "Receive_NOTIFICATION_message", + "Clearing_Completed", +}; + +/* BGP FSM (finite state machine) has three types of functions. Type + one is thread functions. Type two is event functions. Type three + is FSM functions. Timer functions are set by bgp_timer_set + function. */ + +/* BGP event function. */ +void bgp_event(struct event *event); + +/* BGP thread functions. */ +static void bgp_start_timer(struct event *event); +static void bgp_connect_timer(struct event *event); +static void bgp_holdtime_timer(struct event *event); +static void bgp_delayopen_timer(struct event *event); + +/* Register peer with NHT */ +int bgp_peer_reg_with_nht(struct peer *peer) +{ + int connected = 0; + + if (peer->sort == BGP_PEER_EBGP && peer->ttl == BGP_DEFAULT_TTL + && !CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) + && !CHECK_FLAG(peer->bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + connected = 1; + + return bgp_find_or_add_nexthop(peer->bgp, peer->bgp, + family2afi( + peer->connection->su.sa.sa_family), + SAFI_UNICAST, NULL, peer, connected, + NULL); +} + +static void peer_xfer_stats(struct peer *peer_dst, struct peer *peer_src) +{ + /* Copy stats over. These are only the pre-established state stats */ + peer_dst->open_in += peer_src->open_in; + peer_dst->open_out += peer_src->open_out; + peer_dst->keepalive_in += peer_src->keepalive_in; + peer_dst->keepalive_out += peer_src->keepalive_out; + peer_dst->notify_in += peer_src->notify_in; + peer_dst->notify_out += peer_src->notify_out; + peer_dst->dynamic_cap_in += peer_src->dynamic_cap_in; + peer_dst->dynamic_cap_out += peer_src->dynamic_cap_out; +} + +static struct peer *peer_xfer_conn(struct peer *from_peer) +{ + struct peer *peer; + afi_t afi; + safi_t safi; + enum bgp_fsm_events last_evt, last_maj_evt; + struct peer_connection *keeper, *going_away; + + assert(from_peer != NULL); + + /* + * Keeper is the connection that is staying around + */ + keeper = from_peer->connection; + peer = from_peer->doppelganger; + + if (!peer || !CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + return from_peer; + + /* + * from_peer is pointing at the non config node and + * at this point peer is pointing at the CONFIG node + * peer ( non incoming connection ). The going_away pointer + * is the connection that is being placed on to + * the non Config node for deletion. + */ + going_away = peer->connection; + + /* + * Let's check that we are not going to loose known configuration + * state based upon doppelganger rules. + */ + FOREACH_AFI_SAFI (afi, safi) { + if (from_peer->afc[afi][safi] != peer->afc[afi][safi]) { + flog_err( + EC_BGP_DOPPELGANGER_CONFIG, + "from_peer->afc[%d][%d] is not the same as what we are overwriting", + afi, safi); + return NULL; + } + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: peer transfer %p fd %d -> %p fd %d)", + from_peer->host, from_peer, from_peer->connection->fd, + peer, peer->connection->fd); + + bgp_writes_off(going_away); + bgp_reads_off(going_away); + bgp_writes_off(keeper); + bgp_reads_off(keeper); + + /* + * Before exchanging FD remove doppelganger from + * keepalive peer hash. It could be possible conf peer + * fd is set to -1. If blocked on lock then keepalive + * thread can access peer pointer with fd -1. + */ + bgp_keepalives_off(keeper); + + EVENT_OFF(going_away->t_routeadv); + EVENT_OFF(going_away->t_connect); + EVENT_OFF(going_away->t_delayopen); + EVENT_OFF(going_away->t_connect_check_r); + EVENT_OFF(going_away->t_connect_check_w); + EVENT_OFF(keeper->t_routeadv); + EVENT_OFF(keeper->t_connect); + EVENT_OFF(keeper->t_delayopen); + EVENT_OFF(keeper->t_connect_check_r); + EVENT_OFF(keeper->t_connect_check_w); + EVENT_OFF(keeper->t_process_packet); + + /* + * At this point in time, it is possible that there are packets pending + * on various buffers. Those need to be transferred or dropped, + * otherwise we'll get spurious failures during session establishment. + */ + peer->connection = keeper; + keeper->peer = peer; + from_peer->connection = going_away; + going_away->peer = from_peer; + + peer->as = from_peer->as; + peer->v_holdtime = from_peer->v_holdtime; + peer->v_keepalive = from_peer->v_keepalive; + peer->v_routeadv = from_peer->v_routeadv; + peer->v_delayopen = from_peer->v_delayopen; + peer->v_gr_restart = from_peer->v_gr_restart; + peer->cap = from_peer->cap; + peer->remote_role = from_peer->remote_role; + last_evt = peer->last_event; + last_maj_evt = peer->last_major_event; + peer->last_event = from_peer->last_event; + peer->last_major_event = from_peer->last_major_event; + from_peer->last_event = last_evt; + from_peer->last_major_event = last_maj_evt; + peer->remote_id = from_peer->remote_id; + peer->last_reset = from_peer->last_reset; + peer->max_packet_size = from_peer->max_packet_size; + + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(peer->bgp, + peer->bgp->peer); + + if (bgp_peer_gr_mode_get(peer) == PEER_DISABLE) { + + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { + peer_nsf_stop(peer); + } + } + + if (peer->hostname) { + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + peer->hostname = NULL; + } + if (from_peer->hostname != NULL) { + peer->hostname = from_peer->hostname; + from_peer->hostname = NULL; + } + + if (peer->domainname) { + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + peer->domainname = NULL; + } + if (from_peer->domainname != NULL) { + peer->domainname = from_peer->domainname; + from_peer->domainname = NULL; + } + + if (peer->soft_version) { + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + peer->soft_version = NULL; + } + if (from_peer->soft_version) { + peer->soft_version = from_peer->soft_version; + from_peer->soft_version = NULL; + } + + FOREACH_AFI_SAFI (afi, safi) { + peer->af_sflags[afi][safi] = from_peer->af_sflags[afi][safi]; + peer->af_cap[afi][safi] = from_peer->af_cap[afi][safi]; + peer->afc_nego[afi][safi] = from_peer->afc_nego[afi][safi]; + peer->afc_adv[afi][safi] = from_peer->afc_adv[afi][safi]; + peer->afc_recv[afi][safi] = from_peer->afc_recv[afi][safi]; + peer->orf_plist[afi][safi] = from_peer->orf_plist[afi][safi]; + peer->llgr[afi][safi] = from_peer->llgr[afi][safi]; + peer->addpath_paths_limit[afi][safi] = + from_peer->addpath_paths_limit[afi][safi]; + } + + if (bgp_getsockname(peer) < 0) { + flog_err(EC_LIB_SOCKET, + "%%bgp_getsockname() failed for %s peer %s fd %d (from_peer fd %d)", + (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER) + ? "accept" + : ""), + peer->host, going_away->fd, keeper->fd); + BGP_EVENT_ADD(going_away, BGP_Stop); + BGP_EVENT_ADD(keeper, BGP_Stop); + return NULL; + } + if (going_away->status > Active) { + if (bgp_getsockname(from_peer) < 0) { + flog_err(EC_LIB_SOCKET, + "%%bgp_getsockname() failed for %s from_peer %s fd %d (peer fd %d)", + + (CHECK_FLAG(from_peer->sflags, + PEER_STATUS_ACCEPT_PEER) + ? "accept" + : ""), + from_peer->host, going_away->fd, keeper->fd); + bgp_stop(going_away); + from_peer = NULL; + } + } + + + // Note: peer_xfer_stats() must be called with I/O turned OFF + if (from_peer) + peer_xfer_stats(peer, from_peer); + + /* Register peer for NHT. This is to allow RAs to be enabled when + * needed, even on a passive connection. + */ + bgp_peer_reg_with_nht(peer); + if (from_peer) + bgp_replace_nexthop_by_peer(from_peer, peer); + + bgp_reads_on(keeper); + bgp_writes_on(keeper); + event_add_event(bm->master, bgp_process_packet, keeper, 0, + &keeper->t_process_packet); + + return (peer); +} + +/* Hook function called after bgp event is occered. And vty's + neighbor command invoke this function after making neighbor + structure. */ +void bgp_timer_set(struct peer_connection *connection) +{ + afi_t afi; + safi_t safi; + struct peer *peer = connection->peer; + + switch (connection->status) { + case Idle: + /* First entry point of peer's finite state machine. In Idle + status start timer is on unless peer is shutdown or peer is + inactive. All other timer must be turned off */ + if (BGP_PEER_START_SUPPRESSED(peer) || !peer_active(peer) + || peer->bgp->vrf_id == VRF_UNKNOWN) { + EVENT_OFF(connection->t_start); + } else { + BGP_TIMER_ON(connection->t_start, bgp_start_timer, + peer->v_start); + } + EVENT_OFF(connection->t_connect); + EVENT_OFF(connection->t_holdtime); + bgp_keepalives_off(connection); + EVENT_OFF(connection->t_routeadv); + EVENT_OFF(connection->t_delayopen); + break; + + case Connect: + /* After start timer is expired, the peer moves to Connect + status. Make sure start timer is off and connect timer is + on. */ + EVENT_OFF(connection->t_start); + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER_DELAYOPEN)) + BGP_TIMER_ON(connection->t_connect, bgp_connect_timer, + (peer->v_delayopen + peer->v_connect)); + else + BGP_TIMER_ON(connection->t_connect, bgp_connect_timer, + peer->v_connect); + + EVENT_OFF(connection->t_holdtime); + bgp_keepalives_off(connection); + EVENT_OFF(connection->t_routeadv); + break; + + case Active: + /* Active is waiting connection from remote peer. And if + connect timer is expired, change status to Connect. */ + EVENT_OFF(connection->t_start); + /* If peer is passive mode, do not set connect timer. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSIVE) + || CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { + EVENT_OFF(connection->t_connect); + } else { + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER_DELAYOPEN)) + BGP_TIMER_ON(connection->t_connect, + bgp_connect_timer, + (peer->v_delayopen + + peer->v_connect)); + else + BGP_TIMER_ON(connection->t_connect, + bgp_connect_timer, peer->v_connect); + } + EVENT_OFF(connection->t_holdtime); + bgp_keepalives_off(connection); + EVENT_OFF(connection->t_routeadv); + break; + + case OpenSent: + /* OpenSent status. */ + EVENT_OFF(connection->t_start); + EVENT_OFF(connection->t_connect); + if (peer->v_holdtime != 0) { + BGP_TIMER_ON(connection->t_holdtime, bgp_holdtime_timer, + peer->v_holdtime); + } else { + EVENT_OFF(connection->t_holdtime); + } + bgp_keepalives_off(connection); + EVENT_OFF(connection->t_routeadv); + EVENT_OFF(connection->t_delayopen); + break; + + case OpenConfirm: + /* OpenConfirm status. */ + EVENT_OFF(connection->t_start); + EVENT_OFF(connection->t_connect); + + /* + * If the negotiated Hold Time value is zero, then the Hold Time + * timer and KeepAlive timers are not started. + * Additionally if a different hold timer has been negotiated + * than we must stop then start the timer again + */ + EVENT_OFF(connection->t_holdtime); + if (peer->v_holdtime == 0) + bgp_keepalives_off(connection); + else { + BGP_TIMER_ON(connection->t_holdtime, bgp_holdtime_timer, + peer->v_holdtime); + bgp_keepalives_on(connection); + } + EVENT_OFF(connection->t_routeadv); + EVENT_OFF(connection->t_delayopen); + break; + + case Established: + /* In Established status start and connect timer is turned + off. */ + EVENT_OFF(connection->t_start); + EVENT_OFF(connection->t_connect); + EVENT_OFF(connection->t_delayopen); + + /* + * Same as OpenConfirm, if holdtime is zero then both holdtime + * and keepalive must be turned off. + * Additionally if a different hold timer has been negotiated + * then we must stop then start the timer again + */ + EVENT_OFF(connection->t_holdtime); + if (peer->v_holdtime == 0) + bgp_keepalives_off(connection); + else { + BGP_TIMER_ON(connection->t_holdtime, bgp_holdtime_timer, + peer->v_holdtime); + bgp_keepalives_on(connection); + } + break; + case Deleted: + EVENT_OFF(peer->connection->t_gr_restart); + EVENT_OFF(peer->connection->t_gr_stale); + + FOREACH_AFI_SAFI (afi, safi) + EVENT_OFF(peer->t_llgr_stale[afi][safi]); + + EVENT_OFF(peer->connection->t_pmax_restart); + EVENT_OFF(peer->t_refresh_stalepath); + fallthrough; + case Clearing: + EVENT_OFF(connection->t_start); + EVENT_OFF(connection->t_connect); + EVENT_OFF(connection->t_holdtime); + bgp_keepalives_off(connection); + EVENT_OFF(connection->t_routeadv); + EVENT_OFF(connection->t_delayopen); + break; + case BGP_STATUS_MAX: + flog_err(EC_LIB_DEVELOPMENT, + "BGP_STATUS_MAX while a legal state is not valid state for the FSM"); + break; + } +} + +/* BGP start timer. This function set BGP_Start event to thread value + and process event. */ +static void bgp_start_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Timer (start timer expire).", peer->host); + + EVENT_VAL(thread) = BGP_Start; + bgp_event(thread); /* bgp_event unlocks peer */ +} + +/* BGP connect retry timer. */ +static void bgp_connect_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + /* stop the DelayOpenTimer if it is running */ + EVENT_OFF(connection->t_delayopen); + + assert(!connection->t_write); + assert(!connection->t_read); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Timer (connect timer expire)", peer->host); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) + bgp_stop(connection); + else { + EVENT_VAL(thread) = ConnectRetry_timer_expired; + bgp_event(thread); /* bgp_event unlocks peer */ + } +} + +/* BGP holdtime timer. */ +static void bgp_holdtime_timer(struct event *thread) +{ + atomic_size_t inq_count; + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Timer (holdtime timer expire)", + peer->host); + + /* + * Given that we do not have any expectation of ordering + * for handling packets from a peer -vs- handling + * the hold timer for a peer as that they are both + * events on the peer. If we have incoming + * data on the peers inq, let's give the system a chance + * to handle that data. This can be especially true + * for systems where we are heavily loaded for one + * reason or another. + */ + inq_count = atomic_load_explicit(&connection->ibuf->count, + memory_order_relaxed); + if (inq_count) + BGP_TIMER_ON(connection->t_holdtime, bgp_holdtime_timer, + peer->v_holdtime); + + EVENT_VAL(thread) = Hold_Timer_expired; + bgp_event(thread); /* bgp_event unlocks peer */ +} + +void bgp_routeadv_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Timer (routeadv timer expire)", peer->host); + + peer->synctime = monotime(NULL); + + event_add_timer_msec(bm->master, bgp_generate_updgrp_packets, connection, + 0, &connection->t_generate_updgrp_packets); + + /* MRAI timer will be started again when FIFO is built, no need to + * do it here. + */ +} + +/* RFC 4271 DelayOpenTimer */ +void bgp_delayopen_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Timer (DelayOpentimer expire)", + peer->host); + + EVENT_VAL(thread) = DelayOpen_timer_expired; + bgp_event(thread); /* bgp_event unlocks peer */ +} + +/* BGP Peer Down Cause */ +const char *const peer_down_str[] = { + "", + "Router ID changed", + "Remote AS changed", + "Local AS change", + "Cluster ID changed", + "Confederation identifier changed", + "Confederation peer changed", + "RR client config change", + "RS client config change", + "Update source change", + "Address family activated", + "Admin. shutdown", + "User reset", + "BGP Notification received", + "BGP Notification send", + "Peer closed the session", + "Neighbor deleted", + "Peer-group add member", + "Peer-group delete member", + "Capability changed", + "Passive config change", + "Multihop config change", + "NSF peer closed the session", + "Intf peering v6only config change", + "BFD down received", + "Interface down", + "Neighbor address lost", + "No path to specified Neighbor", + "Waiting for Peer IPv6 LLA", + "Waiting for VRF to be initialized", + "No AFI/SAFI activated for peer", + "AS Set config change", + "Waiting for peer OPEN", + "Reached received prefix count", + "Socket Error", + "Admin. shutdown (RTT)", + "Suppress Fib Turned On or Off", +}; + +static void bgp_graceful_restart_timer_off(struct peer_connection *connection, + struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT)) + return; + + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + EVENT_OFF(connection->t_gr_stale); + + if (peer_dynamic_neighbor(peer) && + !(CHECK_FLAG(peer->flags, PEER_FLAG_DELETE))) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s (dynamic neighbor) deleted (%s)", + peer->host, __func__); + peer_delete(peer); + } + + bgp_timer_set(connection); +} + +static void bgp_llgr_stale_timer_expire(struct event *thread) +{ + struct peer_af *paf; + struct peer *peer; + afi_t afi; + safi_t safi; + + paf = EVENT_ARG(thread); + + peer = paf->peer; + afi = paf->afi; + safi = paf->safi; + + /* If the timer for the "Long-lived Stale Time" expires before the + * session is re-established, the helper MUST delete all the + * stale routes from the neighbor that it is retaining. + */ + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP Long-lived stale timer (%s) expired", peer, + get_afi_safi_str(afi, safi, false)); + + UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_LLGR_WAIT); + + bgp_clear_stale_route(peer, afi, safi); + + bgp_graceful_restart_timer_off(peer->connection, peer); +} + +static void bgp_set_llgr_stale(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_table *table; + struct attr attr; + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + struct bgp_dest *rm; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) + for (pi = bgp_dest_get_bgp_path_info(rm); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if (bgp_attr_get_community(pi->attr) && + community_include( + bgp_attr_get_community( + pi->attr), + COMMUNITY_NO_LLGR)) + continue; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Long-lived set stale community (LLGR_STALE) for: %pFX", + peer, &dest->rn->p); + + attr = *pi->attr; + bgp_attr_add_llgr_community(&attr); + pi->attr = bgp_attr_intern(&attr); + bgp_recalculate_afi_safi_bestpaths( + peer->bgp, afi, safi); + + break; + } + } + } else { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if (bgp_attr_get_community(pi->attr) && + community_include( + bgp_attr_get_community(pi->attr), + COMMUNITY_NO_LLGR)) + continue; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Long-lived set stale community (LLGR_STALE) for: %pFX", + peer, &dest->rn->p); + + attr = *pi->attr; + bgp_attr_add_llgr_community(&attr); + pi->attr = bgp_attr_intern(&attr); + bgp_recalculate_afi_safi_bestpaths(peer->bgp, + afi, safi); + + break; + } + } +} + +static void bgp_graceful_restart_timer_expire(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + struct peer *tmp_peer; + struct listnode *node, *nnode; + struct peer_af *paf; + afi_t afi; + safi_t safi; + + if (bgp_debug_neighbor_events(peer)) { + zlog_debug("%pBP graceful restart timer expired", peer); + zlog_debug("%pBP graceful restart stalepath timer stopped", + peer); + } + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->nsf[afi][safi]) + continue; + + /* Once the "Restart Time" period ends, the LLGR period is + * said to have begun and the following procedures MUST be + * performed: + * + * The helper router MUST start a timer for the + * "Long-lived Stale Time". + * + * The helper router MUST attach the LLGR_STALE community + * for the stale routes being retained. Note that this + * requirement implies that the routes would need to be + * readvertised, to disseminate the modified community. + */ + if (peer->llgr[afi][safi].stale_time) { + paf = peer_af_find(peer, afi, safi); + if (!paf) + continue; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Long-lived stale timer (%s) started for %d sec", + peer, + get_afi_safi_str(afi, safi, false), + peer->llgr[afi][safi].stale_time); + + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT); + + bgp_set_llgr_stale(peer, afi, safi); + bgp_clear_stale_route(peer, afi, safi); + + event_add_timer(bm->master, bgp_llgr_stale_timer_expire, + paf, peer->llgr[afi][safi].stale_time, + &peer->t_llgr_stale[afi][safi]); + + for (ALL_LIST_ELEMENTS(peer->bgp->peer, node, nnode, + tmp_peer)) + bgp_announce_route(tmp_peer, afi, safi, false); + } else { + bgp_clear_stale_route(peer, afi, safi); + } + } + + bgp_graceful_restart_timer_off(connection, peer); +} + +static void bgp_graceful_stale_timer_expire(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + afi_t afi; + safi_t safi; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP graceful restart stalepath timer expired", + peer); + + /* NSF delete stale route */ + FOREACH_AFI_SAFI_NSF (afi, safi) + if (peer->nsf[afi][safi]) + bgp_clear_stale_route(peer, afi, safi); +} + +/* Selection deferral timer processing function */ +static void bgp_graceful_deferral_timer_expire(struct event *thread) +{ + struct afi_safi_info *info; + afi_t afi; + safi_t safi; + struct bgp *bgp; + + info = EVENT_ARG(thread); + afi = info->afi; + safi = info->safi; + bgp = info->bgp; + + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug( + "afi %d, safi %d : graceful restart deferral timer expired", + afi, safi); + + bgp->gr_info[afi][safi].eor_required = 0; + bgp->gr_info[afi][safi].eor_received = 0; + XFREE(MTYPE_TMP, info); + + /* Best path selection */ + bgp_best_path_select_defer(bgp, afi, safi); +} + +static bool bgp_update_delay_applicable(struct bgp *bgp) +{ + /* update_delay_over flag should be reset (set to 0) for any new + applicability of the update-delay during BGP process lifetime. + And it should be set after an occurence of the update-delay is + over)*/ + if (!bgp->update_delay_over) + return true; + return false; +} + +bool bgp_update_delay_active(struct bgp *bgp) +{ + if (bgp->t_update_delay) + return true; + return false; +} + +bool bgp_update_delay_configured(struct bgp *bgp) +{ + if (bgp->v_update_delay) + return true; + return false; +} + +/* Do the post-processing needed when bgp comes out of the read-only mode + on ending the update delay. */ +void bgp_update_delay_end(struct bgp *bgp) +{ + EVENT_OFF(bgp->t_update_delay); + EVENT_OFF(bgp->t_establish_wait); + + /* Reset update-delay related state */ + bgp->update_delay_over = 1; + bgp->established = 0; + bgp->restarted_peers = 0; + bgp->implicit_eors = 0; + bgp->explicit_eors = 0; + + frr_timestamp(3, bgp->update_delay_end_time, + sizeof(bgp->update_delay_end_time)); + + /* + * Add an end-of-initial-update marker to the main process queues so + * that + * the route advertisement timer for the peers can be started. Also set + * the zebra and peer update hold flags. These flags are used to achieve + * three stages in the update-delay post processing: + * 1. Finish best-path selection for all the prefixes held on the + * queues. + * (routes in BGP are updated, and peers sync queues are populated + * too) + * 2. As the eoiu mark is reached in the bgp process routine, ship all + * the + * routes to zebra. With that zebra should see updates from BGP + * close + * to each other. + * 3. Unblock the peer update writes. With that peer update packing + * with + * the prefixes should be at its maximum. + */ + bgp_add_eoiu_mark(bgp); + bgp->main_zebra_update_hold = 1; + bgp->main_peers_update_hold = 1; + + /* + * Resume the queue processing. This should trigger the event that would + * take care of processing any work that was queued during the read-only + * mode. + */ + work_queue_unplug(bgp->process_queue); +} + +/** + * see bgp_fsm.h + */ +void bgp_start_routeadv(struct bgp *bgp) +{ + struct listnode *node, *nnode; + struct peer *peer; + + zlog_info("%s, update hold status %d", __func__, + bgp->main_peers_update_hold); + + if (bgp->main_peers_update_hold) + return; + + frr_timestamp(3, bgp->update_delay_peers_resume_time, + sizeof(bgp->update_delay_peers_resume_time)); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + struct peer_connection *connection = peer->connection; + + if (!peer_established(connection)) + continue; + + EVENT_OFF(connection->t_routeadv); + BGP_TIMER_ON(connection->t_routeadv, bgp_routeadv_timer, 0); + } +} + +/** + * see bgp_fsm.h + */ +void bgp_adjust_routeadv(struct peer *peer) +{ + time_t nowtime = monotime(NULL); + double diff; + unsigned long remain; + struct peer_connection *connection = peer->connection; + + /* Bypass checks for special case of MRAI being 0 */ + if (peer->v_routeadv == 0) { + /* Stop existing timer, just in case it is running for a + * different + * duration and schedule write thread immediately. + */ + EVENT_OFF(connection->t_routeadv); + + peer->synctime = monotime(NULL); + /* If suppress fib pending is enabled, route is advertised to + * peers when the status is received from the FIB. The delay + * is added to update group packet generate which will allow + * more routes to be sent in the update message + */ + BGP_UPDATE_GROUP_TIMER_ON(&connection->t_generate_updgrp_packets, + bgp_generate_updgrp_packets); + return; + } + + + /* + * CASE I: + * If the last update was written more than MRAI back, expire the timer + * instantly so that we can send the update out sooner. + * + * <------- MRAI ---------> + * |-----------------|-----------------------| + * <------------- m ------------> + * ^ ^ ^ + * | | | + * | | current time + * | timer start + * last write + * + * m > MRAI + */ + diff = difftime(nowtime, peer->last_update); + if (diff > (double)peer->v_routeadv) { + EVENT_OFF(connection->t_routeadv); + BGP_TIMER_ON(connection->t_routeadv, bgp_routeadv_timer, 0); + return; + } + + /* + * CASE II: + * - Find when to expire the MRAI timer. + * If MRAI timer is not active, assume we can start it now. + * + * <------- MRAI ---------> + * |------------|-----------------------| + * <-------- m ----------><----- r -----> + * ^ ^ ^ + * | | | + * | | current time + * | timer start + * last write + * + * (MRAI - m) < r + */ + if (connection->t_routeadv) + remain = event_timer_remain_second(connection->t_routeadv); + else + remain = peer->v_routeadv; + diff = peer->v_routeadv - diff; + if (diff <= (double)remain) { + EVENT_OFF(connection->t_routeadv); + BGP_TIMER_ON(connection->t_routeadv, bgp_routeadv_timer, diff); + } +} + +static bool bgp_maxmed_onstartup_applicable(struct bgp *bgp) +{ + if (!bgp->maxmed_onstartup_over) + return true; + return false; +} + +bool bgp_maxmed_onstartup_configured(struct bgp *bgp) +{ + if (bgp->v_maxmed_onstartup != BGP_MAXMED_ONSTARTUP_UNCONFIGURED) + return true; + return false; +} + +bool bgp_maxmed_onstartup_active(struct bgp *bgp) +{ + if (bgp->t_maxmed_onstartup) + return true; + return false; +} + +void bgp_maxmed_update(struct bgp *bgp) +{ + uint8_t maxmed_active; + uint32_t maxmed_value; + + if (bgp->v_maxmed_admin) { + maxmed_active = 1; + maxmed_value = bgp->maxmed_admin_value; + } else if (bgp->t_maxmed_onstartup) { + maxmed_active = 1; + maxmed_value = bgp->maxmed_onstartup_value; + } else { + maxmed_active = 0; + maxmed_value = BGP_MAXMED_VALUE_DEFAULT; + } + + if (bgp->maxmed_active != maxmed_active + || bgp->maxmed_value != maxmed_value) { + bgp->maxmed_active = maxmed_active; + bgp->maxmed_value = maxmed_value; + + update_group_announce(bgp); + } +} + +int bgp_fsm_error_subcode(int status) +{ + int fsm_err_subcode = BGP_NOTIFY_FSM_ERR_SUBCODE_UNSPECIFIC; + + switch (status) { + case OpenSent: + fsm_err_subcode = BGP_NOTIFY_FSM_ERR_SUBCODE_OPENSENT; + break; + case OpenConfirm: + fsm_err_subcode = BGP_NOTIFY_FSM_ERR_SUBCODE_OPENCONFIRM; + break; + case Established: + fsm_err_subcode = BGP_NOTIFY_FSM_ERR_SUBCODE_ESTABLISHED; + break; + default: + break; + } + + return fsm_err_subcode; +} + +/* The maxmed onstartup timer expiry callback. */ +static void bgp_maxmed_onstartup_timer(struct event *thread) +{ + struct bgp *bgp; + + zlog_info("Max med on startup ended - timer expired."); + + bgp = EVENT_ARG(thread); + EVENT_OFF(bgp->t_maxmed_onstartup); + bgp->maxmed_onstartup_over = 1; + + bgp_maxmed_update(bgp); +} + +static void bgp_maxmed_onstartup_begin(struct bgp *bgp) +{ + /* Applicable only once in the process lifetime on the startup */ + if (bgp->maxmed_onstartup_over) + return; + + zlog_info("Begin maxmed onstartup mode - timer %d seconds", + bgp->v_maxmed_onstartup); + + event_add_timer(bm->master, bgp_maxmed_onstartup_timer, bgp, + bgp->v_maxmed_onstartup, &bgp->t_maxmed_onstartup); + + if (!bgp->v_maxmed_admin) { + bgp->maxmed_active = 1; + bgp->maxmed_value = bgp->maxmed_onstartup_value; + } + + /* Route announce to all peers should happen after this in + * bgp_establish() */ +} + +static void bgp_maxmed_onstartup_process_status_change(struct peer *peer) +{ + if (peer_established(peer->connection) && !peer->bgp->established) { + bgp_maxmed_onstartup_begin(peer->bgp); + } +} + +/* The update delay timer expiry callback. */ +static void bgp_update_delay_timer(struct event *thread) +{ + struct bgp *bgp; + + zlog_info("Update delay ended - timer expired."); + + bgp = EVENT_ARG(thread); + EVENT_OFF(bgp->t_update_delay); + bgp_update_delay_end(bgp); +} + +/* The establish wait timer expiry callback. */ +static void bgp_establish_wait_timer(struct event *thread) +{ + struct bgp *bgp; + + zlog_info("Establish wait - timer expired."); + + bgp = EVENT_ARG(thread); + EVENT_OFF(bgp->t_establish_wait); + bgp_check_update_delay(bgp); +} + +/* Steps to begin the update delay: + - initialize queues if needed + - stop the queue processing + - start the timer */ +static void bgp_update_delay_begin(struct bgp *bgp) +{ + struct listnode *node, *nnode; + struct peer *peer; + + /* Stop the processing of queued work. Enqueue shall continue */ + work_queue_plug(bgp->process_queue); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + peer->update_delay_over = 0; + + /* Start the update-delay timer */ + event_add_timer(bm->master, bgp_update_delay_timer, bgp, + bgp->v_update_delay, &bgp->t_update_delay); + + if (bgp->v_establish_wait != bgp->v_update_delay) + event_add_timer(bm->master, bgp_establish_wait_timer, bgp, + bgp->v_establish_wait, &bgp->t_establish_wait); + + frr_timestamp(3, bgp->update_delay_begin_time, + sizeof(bgp->update_delay_begin_time)); +} + +static void bgp_update_delay_process_status_change(struct peer *peer) +{ + if (peer_established(peer->connection)) { + if (!peer->bgp->established++) { + bgp_update_delay_begin(peer->bgp); + zlog_info( + "Begin read-only mode - update-delay timer %d seconds", + peer->bgp->v_update_delay); + } + if (CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV)) + bgp_update_restarted_peers(peer); + } + if (peer->connection->ostatus == Established && + bgp_update_delay_active(peer->bgp)) { + /* Adjust the update-delay state to account for this flap. + NOTE: Intentionally skipping adjusting implicit_eors or + explicit_eors + counters. Extra sanity check in bgp_check_update_delay() + should + be enough to take care of any additive discrepancy in bgp eor + counters */ + peer->bgp->established--; + peer->update_delay_over = 0; + } +} + +/* Called after event occurred, this function change status and reset + read/write and timer thread. */ +void bgp_fsm_change_status(struct peer_connection *connection, + enum bgp_fsm_status status) +{ + struct peer *peer = connection->peer; + struct bgp *bgp = peer->bgp; + uint32_t peer_count; + + peer_count = bgp->established_peers; + + if (status == Established) + bgp->established_peers++; + else if ((peer_established(connection)) && (status != Established)) + bgp->established_peers--; + + if (bgp_debug_neighbor_events(peer)) { + struct vrf *vrf = vrf_lookup_by_id(bgp->vrf_id); + + zlog_debug("%s : vrf %s(%u), Status: %s established_peers %u", __func__, + vrf ? vrf->name : "Unknown", bgp->vrf_id, + lookup_msg(bgp_status_msg, status, NULL), + bgp->established_peers); + } + + /* Set to router ID to the value provided by RIB if there are no peers + * in the established state and peer count did not change + */ + if ((peer_count != bgp->established_peers) && + (bgp->established_peers == 0)) + bgp_router_id_zebra_bump(bgp->vrf_id, NULL); + + /* Transition into Clearing or Deleted must /always/ clear all routes.. + * (and must do so before actually changing into Deleted.. + */ + if (status >= Clearing && (peer->established || peer == bgp->peer_self)) { + bgp_clear_route_all(peer); + + /* If no route was queued for the clear-node processing, + * generate the + * completion event here. This is needed because if there are no + * routes + * to trigger the background clear-node thread, the event won't + * get + * generated and the peer would be stuck in Clearing. Note that + * this + * event is for the peer and helps the peer transition out of + * Clearing + * state; it should not be generated per (AFI,SAFI). The event + * is + * directly posted here without calling clear_node_complete() as + * we + * shouldn't do an extra unlock. This event will get processed + * after + * the state change that happens below, so peer will be in + * Clearing + * (or Deleted). + */ + if (!work_queue_is_scheduled(peer->clear_node_queue) && + status != Deleted) + BGP_EVENT_ADD(connection, Clearing_Completed); + } + + /* Preserve old status and change into new status. */ + connection->ostatus = connection->status; + connection->status = status; + + /* Reset received keepalives counter on every FSM change */ + peer->rtt_keepalive_rcv = 0; + + /* Fire backward transition hook if that's the case */ + if (connection->ostatus == Established && + connection->status != Established) + hook_call(peer_backward_transition, peer); + + /* Save event that caused status change. */ + peer->last_major_event = peer->cur_event; + + /* Operations after status change */ + hook_call(peer_status_changed, peer); + + if (status == Established) + UNSET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); + + /* If max-med processing is applicable, do the necessary. */ + if (status == Established) { + if (bgp_maxmed_onstartup_configured(peer->bgp) + && bgp_maxmed_onstartup_applicable(peer->bgp)) + bgp_maxmed_onstartup_process_status_change(peer); + else + peer->bgp->maxmed_onstartup_over = 1; + } + + /* If update-delay processing is applicable, do the necessary. */ + if (bgp_update_delay_configured(peer->bgp) + && bgp_update_delay_applicable(peer->bgp)) + bgp_update_delay_process_status_change(peer); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s fd %d went from %s to %s", peer->host, + connection->fd, + lookup_msg(bgp_status_msg, connection->ostatus, NULL), + lookup_msg(bgp_status_msg, connection->status, NULL)); +} + +/* Flush the event queue and ensure the peer is shut down */ +static enum bgp_fsm_state_progress +bgp_clearing_completed(struct peer_connection *connection) +{ + enum bgp_fsm_state_progress rc = bgp_stop(connection); + + if (rc >= BGP_FSM_SUCCESS) + event_cancel_event_ready(bm->master, connection); + + return rc; +} + +/* Administrative BGP peer stop event. */ +/* May be called multiple times for the same peer */ +enum bgp_fsm_state_progress bgp_stop(struct peer_connection *connection) +{ + afi_t afi; + safi_t safi; + char orf_name[BUFSIZ]; + enum bgp_fsm_state_progress ret = BGP_FSM_SUCCESS; + struct peer *peer = connection->peer; + struct bgp *bgp = peer->bgp; + struct graceful_restart_info *gr_info = NULL; + + peer->nsf_af_count = 0; + + /* deregister peer */ + if (peer->bfd_config + && peer->last_reset == PEER_DOWN_UPDATE_SOURCE_CHANGE) + bfd_sess_uninstall(peer->bfd_config->session); + + if (peer_dynamic_neighbor_no_nsf(peer) && + !(CHECK_FLAG(peer->flags, PEER_FLAG_DELETE))) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s (dynamic neighbor) deleted (%s)", + peer->host, __func__); + peer_delete(peer); + return BGP_FSM_FAILURE_AND_DELETE; + } + + /* Can't do this in Clearing; events are used for state transitions */ + if (connection->status != Clearing) { + /* Delete all existing events of the peer */ + event_cancel_event_ready(bm->master, connection); + } + + /* Increment Dropped count. */ + if (peer_established(connection)) { + peer->dropped++; + + /* Notify BGP conditional advertisement process */ + peer->advmap_table_change = true; + + /* bgp log-neighbor-changes of neighbor Down */ + if (CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { + struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); + + zlog_info( + "%%ADJCHANGE: neighbor %pBP in vrf %s Down %s", + peer, + vrf ? ((vrf->vrf_id != VRF_DEFAULT) + ? vrf->name + : VRF_DEFAULT_NAME) + : "", + peer_down_str[(int)peer->last_reset]); + } + + /* graceful restart */ + if (connection->t_gr_stale) { + EVENT_OFF(connection->t_gr_stale); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP graceful restart stalepath timer stopped", + peer); + } + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { + if (bgp_debug_neighbor_events(peer)) { + zlog_debug( + "%pBP graceful restart timer started for %d sec", + peer, peer->v_gr_restart); + zlog_debug( + "%pBP graceful restart stalepath timer started for %d sec", + peer, peer->bgp->stalepath_time); + } + BGP_TIMER_ON(connection->t_gr_restart, + bgp_graceful_restart_timer_expire, + peer->v_gr_restart); + BGP_TIMER_ON(connection->t_gr_stale, + bgp_graceful_stale_timer_expire, + peer->bgp->stalepath_time); + } else { + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + + FOREACH_AFI_SAFI_NSF (afi, safi) + peer->nsf[afi][safi] = 0; + } + + /* Stop route-refresh stalepath timer */ + if (peer->t_refresh_stalepath) { + EVENT_OFF(peer->t_refresh_stalepath); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP route-refresh restart stalepath timer stopped", + peer); + } + + /* If peer reset before receiving EOR, decrement EOR count and + * cancel the selection deferral timer if there are no + * pending EOR messages to be received + */ + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc_nego[afi][safi] + || CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) + continue; + + gr_info = &bgp->gr_info[afi][safi]; + if (!gr_info) + continue; + + if (gr_info->eor_required) + gr_info->eor_required--; + + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("peer %s, EOR_required %d", + peer->host, + gr_info->eor_required); + + /* There is no pending EOR message */ + if (gr_info->eor_required == 0) { + if (gr_info->t_select_deferral) { + void *info = EVENT_ARG( + gr_info->t_select_deferral); + XFREE(MTYPE_TMP, info); + } + EVENT_OFF(gr_info->t_select_deferral); + gr_info->eor_received = 0; + } + } + } + + /* set last reset time */ + peer->resettime = peer->uptime = monotime(NULL); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("%s remove from all update group", + peer->host); + update_group_remove_peer_afs(peer); + + /* Reset peer synctime */ + peer->synctime = 0; + } + + /* stop keepalives */ + bgp_keepalives_off(connection); + + /* Stop read and write threads. */ + bgp_writes_off(connection); + bgp_reads_off(connection); + + EVENT_OFF(connection->t_connect_check_r); + EVENT_OFF(connection->t_connect_check_w); + + /* Stop all timers. */ + EVENT_OFF(connection->t_start); + EVENT_OFF(connection->t_connect); + EVENT_OFF(connection->t_holdtime); + EVENT_OFF(connection->t_routeadv); + EVENT_OFF(peer->connection->t_delayopen); + + /* Clear input and output buffer. */ + frr_with_mutex (&connection->io_mtx) { + if (connection->ibuf) + stream_fifo_clean(connection->ibuf); + if (connection->obuf) + stream_fifo_clean(connection->obuf); + + if (connection->ibuf_work) + ringbuf_wipe(connection->ibuf_work); + + if (peer->curr) { + stream_free(peer->curr); + peer->curr = NULL; + } + } + + /* Close of file descriptor. */ + if (connection->fd >= 0) { + close(connection->fd); + connection->fd = -1; + } + + /* Reset capabilities. */ + peer->cap = 0; + + /* Resetting neighbor role to the default value */ + peer->remote_role = ROLE_UNDEFINED; + + FOREACH_AFI_SAFI (afi, safi) { + /* Reset all negotiated variables */ + peer->afc_nego[afi][safi] = 0; + peer->afc_adv[afi][safi] = 0; + peer->afc_recv[afi][safi] = 0; + + /* peer address family capability flags*/ + peer->af_cap[afi][safi] = 0; + + /* peer address family status flags*/ + peer->af_sflags[afi][safi] = 0; + + /* Received ORF prefix-filter */ + peer->orf_plist[afi][safi] = NULL; + + if ((connection->status == OpenConfirm) || + peer_established(connection)) { + /* ORF received prefix-filter pnt */ + snprintf(orf_name, sizeof(orf_name), "%s.%d.%d", + peer->host, afi, safi); + prefix_bgp_orf_remove_all(afi, orf_name); + } + } + + /* Reset keepalive and holdtime */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) { + peer->v_keepalive = peer->keepalive; + peer->v_holdtime = peer->holdtime; + } else { + peer->v_keepalive = peer->bgp->default_keepalive; + peer->v_holdtime = peer->bgp->default_holdtime; + } + + /* Reset DelayOpenTime */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER_DELAYOPEN)) + peer->v_delayopen = peer->delayopen; + else + peer->v_delayopen = peer->bgp->default_delayopen; + + peer->update_time = 0; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) + && !(CHECK_FLAG(peer->flags, PEER_FLAG_DELETE))) { + peer_delete(peer); + ret = BGP_FSM_FAILURE_AND_DELETE; + } else { + bgp_peer_conf_if_to_su_update(connection); + } + return ret; +} + +/* BGP peer is stoped by the error. */ +static enum bgp_fsm_state_progress +bgp_stop_with_error(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + /* Double start timer. */ + peer->v_start *= 2; + + /* Overflow check. */ + if (peer->v_start >= (60 * 2)) + peer->v_start = (60 * 2); + + if (peer_dynamic_neighbor_no_nsf(peer)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s (dynamic neighbor) deleted (%s)", + peer->host, __func__); + peer_delete(peer); + return BGP_FSM_FAILURE; + } + + return bgp_stop(connection); +} + + +/* something went wrong, send notify and tear down */ +enum bgp_fsm_state_progress +bgp_stop_with_notify(struct peer_connection *connection, uint8_t code, + uint8_t sub_code) +{ + struct peer *peer = connection->peer; + + /* Send notify to remote peer */ + bgp_notify_send(connection, code, sub_code); + + if (peer_dynamic_neighbor_no_nsf(peer)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s (dynamic neighbor) deleted (%s)", + peer->host, __func__); + peer_delete(peer); + return BGP_FSM_FAILURE; + } + + /* Clear start timer value to default. */ + peer->v_start = BGP_INIT_START_TIMER; + + return bgp_stop(connection); +} + +/** + * Determines whether a TCP session has successfully established for a peer and + * events as appropriate. + * + * This function is called when setting up a new session. After connect() is + * called on the peer's socket (in bgp_start()), the fd is passed to poll() + * to wait for connection success or failure. When poll() returns, this + * function is called to evaluate the result. + * + * Due to differences in behavior of poll() on Linux and BSD - specifically, + * the value of .revents in the case of a closed connection - this function is + * scheduled both for a read and a write event. The write event is triggered + * when the connection is established. A read event is triggered when the + * connection is closed. Thus we need to cancel whichever one did not occur. + */ +static void bgp_connect_check(struct event *thread) +{ + int status; + socklen_t slen; + int ret; + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_READS_ON)); + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_WRITES_ON)); + assert(!connection->t_read); + assert(!connection->t_write); + + EVENT_OFF(connection->t_connect_check_r); + EVENT_OFF(connection->t_connect_check_w); + + /* Check file descriptor. */ + slen = sizeof(status); + ret = getsockopt(connection->fd, SOL_SOCKET, SO_ERROR, (void *)&status, + &slen); + + /* If getsockopt is fail, this is fatal error. */ + if (ret < 0) { + zlog_err("can't get sockopt for nonblocking connect: %d(%s)", + errno, safe_strerror(errno)); + BGP_EVENT_ADD(connection, TCP_fatal_error); + return; + } + + /* When status is 0 then TCP connection is established. */ + if (status == 0) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER_DELAYOPEN)) + BGP_EVENT_ADD(connection, + TCP_connection_open_w_delay); + else + BGP_EVENT_ADD(connection, TCP_connection_open); + return; + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [Event] Connect failed %d(%s)", + peer->host, status, safe_strerror(status)); + BGP_EVENT_ADD(connection, TCP_connection_open_failed); + return; + } +} + +/* TCP connection open. Next we send open message to remote peer. And + add read thread for reading open message. */ +static enum bgp_fsm_state_progress +bgp_connect_success(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (connection->fd < 0) { + flog_err(EC_BGP_CONNECT, "%s peer's fd is negative value %d", + __func__, connection->fd); + return bgp_stop(connection); + } + + if (bgp_getsockname(peer) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s: bgp_getsockname(): failed for peer %s, fd %d", + __func__, peer->host, connection->fd); + bgp_notify_send(peer->connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(connection->status)); + bgp_writes_on(connection); + return BGP_FSM_FAILURE; + } + + /* + * If we are doing nht for a peer that ls v6 LL based + * massage the event system to make things happy + */ + bgp_nht_interface_events(peer); + + bgp_reads_on(connection); + + if (bgp_debug_neighbor_events(peer)) { + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) + zlog_debug("%s open active, local address %pSU", + peer->host, peer->su_local); + else + zlog_debug("%s passive open", peer->host); + } + + /* Send an open message */ + bgp_open_send(connection); + + return BGP_FSM_SUCCESS; +} + +/* TCP connection open with RFC 4271 optional session attribute DelayOpen flag + * set. + */ +static enum bgp_fsm_state_progress +bgp_connect_success_w_delayopen(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (connection->fd < 0) { + flog_err(EC_BGP_CONNECT, "%s: peer's fd is negative value %d", + __func__, connection->fd); + return bgp_stop(connection); + } + + if (bgp_getsockname(peer) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s: bgp_getsockname(): failed for peer %s, fd %d", + __func__, peer->host, connection->fd); + bgp_notify_send(peer->connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(connection->status)); + bgp_writes_on(connection); + return BGP_FSM_FAILURE; + } + + /* + * If we are doing nht for a peer that ls v6 LL based + * massage the event system to make things happy + */ + bgp_nht_interface_events(peer); + + bgp_reads_on(connection); + + if (bgp_debug_neighbor_events(peer)) { + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) + zlog_debug("%s open active, local address %pSU", + peer->host, peer->su_local); + else + zlog_debug("%s passive open", peer->host); + } + + /* set the DelayOpenTime to the inital value */ + peer->v_delayopen = peer->delayopen; + + /* Start the DelayOpenTimer if it is not already running */ + if (!peer->connection->t_delayopen) + BGP_TIMER_ON(peer->connection->t_delayopen, bgp_delayopen_timer, + peer->v_delayopen); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] BGP OPEN message delayed for %d seconds", + peer->host, peer->delayopen); + + return BGP_FSM_SUCCESS; +} + +/* TCP connect fail */ +static enum bgp_fsm_state_progress +bgp_connect_fail(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (peer_dynamic_neighbor_no_nsf(peer)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s (dynamic neighbor) deleted (%s)", + peer->host, __func__); + peer_delete(peer); + return BGP_FSM_FAILURE_AND_DELETE; + } + + /* + * If we are doing nht for a peer that ls v6 LL based + * massage the event system to make things happy + */ + bgp_nht_interface_events(peer); + + return bgp_stop(connection); +} + +/* after connect is called(), getpeername is able to return + * port and address on non established streams + */ +static void bgp_connect_in_progress_update_connection(struct peer *peer) +{ + if (bgp_getsockname(peer) < 0) { + if (!peer->su_remote && + !BGP_CONNECTION_SU_UNSPEC(peer->connection)) { + /* if connect initiated, then dest port and dest addresses are well known */ + peer->su_remote = sockunion_dup(&peer->connection->su); + if (sockunion_family(peer->su_remote) == AF_INET) + peer->su_remote->sin.sin_port = + htons(peer->port); + else if (sockunion_family(peer->su_remote) == AF_INET6) + peer->su_remote->sin6.sin6_port = + htons(peer->port); + } + } +} + +/* This function is the first starting point of all BGP connection. It + * try to connect to remote peer with non-blocking IO. + */ +static enum bgp_fsm_state_progress bgp_start(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + int status; + + bgp_peer_conf_if_to_su_update(connection); + + if (connection->su.sa.sa_family == AF_UNSPEC) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s [FSM] Unable to get neighbor's IP address, waiting...", + peer->host); + peer->last_reset = PEER_DOWN_NBR_ADDR; + return BGP_FSM_FAILURE; + } + + if (BGP_PEER_START_SUPPRESSED(peer)) { + if (bgp_debug_neighbor_events(peer)) + flog_err(EC_BGP_FSM, + "%s [FSM] Trying to start suppressed peer - this is never supposed to happen!", + peer->host); + if (CHECK_FLAG(peer->sflags, PEER_STATUS_RTT_SHUTDOWN)) + peer->last_reset = PEER_DOWN_RTT_SHUTDOWN; + else if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) + peer->last_reset = PEER_DOWN_USER_SHUTDOWN; + else if (CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHUTDOWN)) + peer->last_reset = PEER_DOWN_USER_SHUTDOWN; + else if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) + peer->last_reset = PEER_DOWN_PFX_COUNT; + return BGP_FSM_FAILURE; + } + + /* Clear remote router-id. */ + peer->remote_id.s_addr = INADDR_ANY; + + /* Clear peer capability flag. */ + peer->cap = 0; + + if (peer->bgp->vrf_id == VRF_UNKNOWN) { + if (bgp_debug_neighbor_events(peer)) + flog_err( + EC_BGP_FSM, + "%s [FSM] In a VRF that is not initialised yet", + peer->host); + peer->last_reset = PEER_DOWN_VRF_UNINIT; + return BGP_FSM_FAILURE; + } + + /* Register peer for NHT. If next hop is already resolved, proceed + * with connection setup, else wait. + */ + if (!bgp_peer_reg_with_nht(peer)) { + if (bgp_zebra_num_connects()) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s [FSM] Waiting for NHT, no path to neighbor present", + peer->host); + peer->last_reset = PEER_DOWN_WAITING_NHT; + BGP_EVENT_ADD(connection, TCP_connection_open_failed); + return BGP_FSM_SUCCESS; + } + } + + assert(!connection->t_write); + assert(!connection->t_read); + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_WRITES_ON)); + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_READS_ON)); + status = bgp_connect(connection); + + switch (status) { + case connect_error: + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Connect error", peer->host); + BGP_EVENT_ADD(connection, TCP_connection_open_failed); + break; + case connect_success: + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Connect immediately success, fd %d", + peer->host, connection->fd); + + BGP_EVENT_ADD(connection, TCP_connection_open); + break; + case connect_in_progress: + /* To check nonblocking connect, we wait until socket is + readable or writable. */ + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Non blocking connect waiting result, fd %d", + peer->host, connection->fd); + if (connection->fd < 0) { + flog_err(EC_BGP_FSM, "%s peer's fd is negative value %d", + __func__, peer->connection->fd); + return BGP_FSM_FAILURE; + } + bgp_connect_in_progress_update_connection(peer); + + /* + * - when the socket becomes ready, poll() will signify POLLOUT + * - if it fails to connect, poll() will signify POLLHUP + * - POLLHUP is handled as a 'read' event by thread.c + * + * therefore, we schedule both a read and a write event with + * bgp_connect_check() as the handler for each and cancel the + * unused event in that function. + */ + event_add_read(bm->master, bgp_connect_check, connection, + connection->fd, &connection->t_connect_check_r); + event_add_write(bm->master, bgp_connect_check, connection, + connection->fd, &connection->t_connect_check_w); + break; + } + return BGP_FSM_SUCCESS; +} + +/* Connect retry timer is expired when the peer status is Connect. */ +static enum bgp_fsm_state_progress +bgp_reconnect(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + enum bgp_fsm_state_progress ret; + + ret = bgp_stop(connection); + if (ret < BGP_FSM_SUCCESS) + return ret; + + /* Send graceful restart capabilty */ + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(peer->bgp, + peer->bgp->peer); + + return bgp_start(connection); +} + +static enum bgp_fsm_state_progress +bgp_fsm_open(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + /* If DelayOpen is active, we may still need to send an open message */ + if ((connection->status == Connect) || (connection->status == Active)) + bgp_open_send(connection); + + /* Send keepalive and make keepalive timer */ + bgp_keepalive_send(peer); + + return BGP_FSM_SUCCESS; +} + +/* FSM error, unexpected event. This is error of BGP connection. So cut the + peer and change to Idle status. */ +static enum bgp_fsm_state_progress +bgp_fsm_event_error(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + flog_err(EC_BGP_FSM, "%s [FSM] unexpected packet received in state %s", + peer->host, + lookup_msg(bgp_status_msg, connection->status, NULL)); + + return bgp_stop_with_notify(connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(connection->status)); +} + +/* Hold timer expire. This is error of BGP connection. So cut the + peer and change to Idle status. */ +static enum bgp_fsm_state_progress +bgp_fsm_holdtime_expire(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [FSM] Hold timer expire", peer->host); + + /* RFC8538 updates RFC 4724 by defining an extension that permits + * the Graceful Restart procedures to be performed when the BGP + * speaker receives a BGP NOTIFICATION message or the Hold Time expires. + */ + if (peer_established(connection) && + bgp_has_graceful_restart_notification(peer)) + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)) + SET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + + return bgp_stop_with_notify(connection, BGP_NOTIFY_HOLD_ERR, 0); +} + +/* RFC 4271 DelayOpenTimer_Expires event */ +static enum bgp_fsm_state_progress +bgp_fsm_delayopen_timer_expire(struct peer_connection *connection) +{ + /* Stop the DelayOpenTimer */ + EVENT_OFF(connection->t_delayopen); + + /* Send open message to peer */ + bgp_open_send(connection); + + /* Set the HoldTimer to a large value (4 minutes) */ + connection->peer->v_holdtime = 245; + + return BGP_FSM_SUCCESS; +} + +/* Start the selection deferral timer thread for the specified AFI, SAFI */ +static int bgp_start_deferral_timer(struct bgp *bgp, afi_t afi, safi_t safi, + struct graceful_restart_info *gr_info) +{ + struct afi_safi_info *thread_info; + + /* If the deferral timer is active, then increment eor count */ + if (gr_info->t_select_deferral) { + gr_info->eor_required++; + return 0; + } + + /* Start the deferral timer when the first peer enabled for the graceful + * restart is established + */ + if (gr_info->eor_required == 0) { + thread_info = XMALLOC(MTYPE_TMP, sizeof(struct afi_safi_info)); + + thread_info->afi = afi; + thread_info->safi = safi; + thread_info->bgp = bgp; + + event_add_timer(bm->master, bgp_graceful_deferral_timer_expire, + thread_info, bgp->select_defer_time, + &gr_info->t_select_deferral); + } + gr_info->eor_required++; + /* Send message to RIB indicating route update pending */ + if (gr_info->af_enabled[afi][safi] == false) { + gr_info->af_enabled[afi][safi] = true; + /* Send message to RIB */ + bgp_zebra_update(bgp, afi, safi, + ZEBRA_CLIENT_ROUTE_UPDATE_PENDING); + } + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("Started the deferral timer for %s eor_required %d", + get_afi_safi_str(afi, safi, false), + gr_info->eor_required); + return 0; +} + +/* Update the graceful restart information for the specified AFI, SAFI */ +static int bgp_update_gr_info(struct peer *peer, afi_t afi, safi_t safi) +{ + struct graceful_restart_info *gr_info; + struct bgp *bgp = peer->bgp; + int ret = 0; + + if ((afi < AFI_IP) || (afi >= AFI_MAX)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("%s : invalid afi %d", __func__, afi); + return -1; + } + + if ((safi < SAFI_UNICAST) || (safi > SAFI_MPLS_VPN)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("%s : invalid safi %d", __func__, safi); + return -1; + } + + /* Restarting router */ + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) + && BGP_PEER_RESTARTING_MODE(peer)) { + /* Check if the forwarding state is preserved */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_GR_PRESERVE_FWD)) { + gr_info = &(bgp->gr_info[afi][safi]); + ret = bgp_start_deferral_timer(bgp, afi, safi, gr_info); + } + } + return ret; +} + +/** + * Transition to Established state. + * + * Convert peer from stub to full fledged peer, set some timers, and generate + * initial updates. + */ +static enum bgp_fsm_state_progress +bgp_establish(struct peer_connection *connection) +{ + afi_t afi; + safi_t safi; + int nsf_af_count = 0; + enum bgp_fsm_state_progress ret = BGP_FSM_SUCCESS; + struct peer *other; + int status; + struct peer *peer = connection->peer; + struct peer *orig = peer; + + other = peer->doppelganger; + hash_release(peer->bgp->peerhash, peer); + if (other) + hash_release(peer->bgp->peerhash, other); + + peer = peer_xfer_conn(peer); + if (!peer) { + flog_err(EC_BGP_CONNECT, "%%Neighbor failed in xfer_conn"); + + /* + * A failure of peer_xfer_conn but not putting the peers + * back in the hash ends up with a situation where incoming + * connections are rejected, as that the peer is not found + * when a lookup is done + */ + (void)hash_get(orig->bgp->peerhash, orig, hash_alloc_intern); + if (other) + (void)hash_get(other->bgp->peerhash, other, + hash_alloc_intern); + return BGP_FSM_FAILURE; + } + /* + * At this point the connections have been possibly swapped + * let's reset it. + */ + connection = peer->connection; + + if (other == peer) + ret = BGP_FSM_SUCCESS_STATE_TRANSFER; + + /* Reset capability open status flag. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN)) + SET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); + + /* Clear start timer value to default. */ + peer->v_start = BGP_INIT_START_TIMER; + + /* Increment established count. */ + peer->established++; + bgp_fsm_change_status(connection, Established); + + /* bgp log-neighbor-changes of neighbor Up */ + if (CHECK_FLAG(peer->bgp->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES)) { + struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); + zlog_info("%%ADJCHANGE: neighbor %pBP in vrf %s Up", peer, + vrf ? ((vrf->vrf_id != VRF_DEFAULT) + ? vrf->name + : VRF_DEFAULT_NAME) + : ""); + } + /* assign update-group/subgroup */ + update_group_adjust_peer_afs(peer); + + /* graceful restart */ + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + if (bgp_debug_neighbor_events(peer)) { + if (BGP_PEER_RESTARTING_MODE(peer)) + zlog_debug("%pBP BGP_RESTARTING_MODE", peer); + else if (BGP_PEER_HELPER_MODE(peer)) + zlog_debug("%pBP BGP_HELPER_MODE", peer); + } + + FOREACH_AFI_SAFI_NSF (afi, safi) { + if (peer->afc_nego[afi][safi] && + CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV) && + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV)) { + if (peer->nsf[afi][safi] && + !CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV)) + bgp_clear_stale_route(peer, afi, safi); + + peer->nsf[afi][safi] = 1; + nsf_af_count++; + } else { + if (peer->nsf[afi][safi]) + bgp_clear_stale_route(peer, afi, safi); + peer->nsf[afi][safi] = 0; + } + /* Update the graceful restart information */ + if (peer->afc_nego[afi][safi]) { + if (!BGP_SELECT_DEFER_DISABLE(peer->bgp)) { + status = bgp_update_gr_info(peer, afi, safi); + if (status < 0) + zlog_err( + "Error in updating graceful restart for %s", + get_afi_safi_str(afi, safi, + false)); + } else { + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) && + BGP_PEER_RESTARTING_MODE(peer) && + CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_GR_PRESERVE_FWD)) + peer->bgp->gr_info[afi][safi] + .eor_required++; + } + } + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + if ((bgp_peer_gr_mode_get(peer) == PEER_GR) + || ((bgp_peer_gr_mode_get(peer) == PEER_GLOBAL_INHERIT) + && (bgp_global_gr_mode_get(peer->bgp) == GLOBAL_GR))) { + FOREACH_AFI_SAFI (afi, safi) + /* Send route processing complete + message to RIB */ + bgp_zebra_update( + peer->bgp, afi, safi, + ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE); + } + } else { + /* Peer sends R-bit. In this case, we need to send + * ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE to Zebra. */ + if (CHECK_FLAG(peer->cap, + PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV)) { + FOREACH_AFI_SAFI (afi, safi) + /* Send route processing complete + message to RIB */ + bgp_zebra_update( + peer->bgp, afi, safi, + ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE); + } + } + + peer->nsf_af_count = nsf_af_count; + + if (nsf_af_count) + SET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + else { + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + if (connection->t_gr_stale) { + EVENT_OFF(connection->t_gr_stale); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP graceful restart stalepath timer stopped", + peer); + } + } + + if (connection->t_gr_restart) { + EVENT_OFF(connection->t_gr_restart); + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP graceful restart timer stopped", peer); + } + + /* Reset uptime, turn on keepalives, send current table. */ + if (!peer->v_holdtime) + bgp_keepalives_on(connection); + + peer->uptime = monotime(NULL); + + /* Send route-refresh when ORF is enabled. + * Stop Long-lived Graceful Restart timers. + */ + FOREACH_AFI_SAFI (afi, safi) { + if (peer->t_llgr_stale[afi][safi]) { + EVENT_OFF(peer->t_llgr_stale[afi][safi]); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Long-lived stale timer stopped for afi/safi: %d/%d", + peer, afi, safi); + } + + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV)) { + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) + bgp_route_refresh_send( + peer, afi, safi, ORF_TYPE_PREFIX, + REFRESH_IMMEDIATE, 0, + BGP_ROUTE_REFRESH_NORMAL); + } + } + + /* First update is deferred until ORF or ROUTE-REFRESH is received */ + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV)) + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV)) + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH); + } + + bgp_announce_peer(peer); + + /* Start the route advertisement timer to send updates to the peer - if + * BGP + * is not in read-only mode. If it is, the timer will be started at the + * end + * of read-only mode. + */ + if (!bgp_update_delay_active(peer->bgp)) { + EVENT_OFF(peer->connection->t_routeadv); + BGP_TIMER_ON(peer->connection->t_routeadv, bgp_routeadv_timer, + 0); + } + + if (peer->doppelganger && + (peer->doppelganger->connection->status != Deleted)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "[Event] Deleting stub connection for peer %s", + peer->host); + + if (peer->doppelganger->connection->status > Active) + bgp_notify_send(peer->doppelganger->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + else + peer_delete(peer->doppelganger); + } + + /* + * If we are replacing the old peer for a doppelganger + * then switch it around in the bgp->peerhash + * the doppelgangers su and this peer's su are the same + * so the hash_release is the same for either. + */ + (void)hash_get(peer->bgp->peerhash, peer, hash_alloc_intern); + + /* Start BFD peer if not already running. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + + return ret; +} + +/* Keepalive packet is received. */ +static enum bgp_fsm_state_progress +bgp_fsm_keepalive(struct peer_connection *connection) +{ + EVENT_OFF(connection->t_holdtime); + return BGP_FSM_SUCCESS; +} + +/* Update packet is received. */ +static enum bgp_fsm_state_progress +bgp_fsm_update(struct peer_connection *connection) +{ + EVENT_OFF(connection->t_holdtime); + return BGP_FSM_SUCCESS; +} + +/* This is empty event. */ +static enum bgp_fsm_state_progress bgp_ignore(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + flog_err(EC_BGP_FSM, + "%s [FSM] Ignoring event %s in state %s, prior events %s, %s, fd %d", + peer->host, bgp_event_str[peer->cur_event], + lookup_msg(bgp_status_msg, connection->status, NULL), + bgp_event_str[peer->last_event], + bgp_event_str[peer->last_major_event], connection->fd); + return BGP_FSM_SUCCESS; +} + +/* This is to handle unexpected events.. */ +static enum bgp_fsm_state_progress +bgp_fsm_exception(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + flog_err(EC_BGP_FSM, + "%s [FSM] Unexpected event %s in state %s, prior events %s, %s, fd %d", + peer->host, bgp_event_str[peer->cur_event], + lookup_msg(bgp_status_msg, connection->status, NULL), + bgp_event_str[peer->last_event], + bgp_event_str[peer->last_major_event], connection->fd); + return bgp_stop(connection); +} + +void bgp_fsm_nht_update(struct peer_connection *connection, struct peer *peer, + bool has_valid_nexthops) +{ + if (!peer) + return; + + switch (connection->status) { + case Idle: + if (has_valid_nexthops) + BGP_EVENT_ADD(connection, BGP_Start); + break; + case Connect: + if (!has_valid_nexthops) { + EVENT_OFF(connection->t_connect); + BGP_EVENT_ADD(connection, TCP_fatal_error); + } + break; + case Active: + if (has_valid_nexthops) { + EVENT_OFF(connection->t_connect); + BGP_EVENT_ADD(connection, ConnectRetry_timer_expired); + } + break; + case OpenSent: + case OpenConfirm: + case Established: + if (!has_valid_nexthops + && (peer->gtsm_hops == BGP_GTSM_HOPS_CONNECTED + || peer->bgp->fast_convergence)) + BGP_EVENT_ADD(connection, TCP_fatal_error); + break; + case Clearing: + case Deleted: + case BGP_STATUS_MAX: + break; + } +} + +/* Finite State Machine structure */ +static const struct { + enum bgp_fsm_state_progress (*func)(struct peer_connection *); + enum bgp_fsm_status next_state; +} FSM[BGP_STATUS_MAX - 1][BGP_EVENTS_MAX - 1] = { + { + /* Idle state: In Idle state, all events other than BGP_Start is + ignored. With BGP_Start event, finite state machine calls + bgp_start(). */ + {bgp_start, Connect}, /* BGP_Start */ + {bgp_stop, Idle}, /* BGP_Stop */ + {bgp_stop, Idle}, /* TCP_connection_open */ + {bgp_stop, Idle}, /* TCP_connection_open_w_delay */ + {bgp_stop, Idle}, /* TCP_connection_closed */ + {bgp_ignore, Idle}, /* TCP_connection_open_failed */ + {bgp_stop, Idle}, /* TCP_fatal_error */ + {bgp_ignore, Idle}, /* ConnectRetry_timer_expired */ + {bgp_ignore, Idle}, /* Hold_Timer_expired */ + {bgp_ignore, Idle}, /* KeepAlive_timer_expired */ + {bgp_ignore, Idle}, /* DelayOpen_timer_expired */ + {bgp_ignore, Idle}, /* Receive_OPEN_message */ + {bgp_ignore, Idle}, /* Receive_KEEPALIVE_message */ + {bgp_ignore, Idle}, /* Receive_UPDATE_message */ + {bgp_ignore, Idle}, /* Receive_NOTIFICATION_message */ + {bgp_ignore, Idle}, /* Clearing_Completed */ + }, + { + /* Connect */ + {bgp_ignore, Connect}, /* BGP_Start */ + {bgp_stop, Idle}, /* BGP_Stop */ + {bgp_connect_success, OpenSent}, /* TCP_connection_open */ + {bgp_connect_success_w_delayopen, + Connect}, /* TCP_connection_open_w_delay */ + {bgp_stop, Idle}, /* TCP_connection_closed */ + {bgp_connect_fail, Active}, /* TCP_connection_open_failed */ + {bgp_connect_fail, Idle}, /* TCP_fatal_error */ + {bgp_reconnect, Connect}, /* ConnectRetry_timer_expired */ + {bgp_fsm_exception, Idle}, /* Hold_Timer_expired */ + {bgp_fsm_exception, Idle}, /* KeepAlive_timer_expired */ + {bgp_fsm_delayopen_timer_expire, + OpenSent}, /* DelayOpen_timer_expired */ + {bgp_fsm_open, OpenConfirm}, /* Receive_OPEN_message */ + {bgp_fsm_exception, Idle}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_exception, Idle}, /* Receive_UPDATE_message */ + {bgp_stop, Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_exception, Idle}, /* Clearing_Completed */ + }, + { + /* Active, */ + {bgp_ignore, Active}, /* BGP_Start */ + {bgp_stop, Idle}, /* BGP_Stop */ + {bgp_connect_success, OpenSent}, /* TCP_connection_open */ + {bgp_connect_success_w_delayopen, + Active}, /* TCP_connection_open_w_delay */ + {bgp_stop, Idle}, /* TCP_connection_closed */ + {bgp_ignore, Active}, /* TCP_connection_open_failed */ + {bgp_fsm_exception, Idle}, /* TCP_fatal_error */ + {bgp_start, Connect}, /* ConnectRetry_timer_expired */ + {bgp_fsm_exception, Idle}, /* Hold_Timer_expired */ + {bgp_fsm_exception, Idle}, /* KeepAlive_timer_expired */ + {bgp_fsm_delayopen_timer_expire, + OpenSent}, /* DelayOpen_timer_expired */ + {bgp_fsm_open, OpenConfirm}, /* Receive_OPEN_message */ + {bgp_fsm_exception, Idle}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_exception, Idle}, /* Receive_UPDATE_message */ + {bgp_fsm_exception, Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_exception, Idle}, /* Clearing_Completed */ + }, + { + /* OpenSent, */ + {bgp_ignore, OpenSent}, /* BGP_Start */ + {bgp_stop, Idle}, /* BGP_Stop */ + {bgp_stop, Active}, /* TCP_connection_open */ + {bgp_fsm_exception, Idle}, /* TCP_connection_open_w_delay */ + {bgp_stop, Active}, /* TCP_connection_closed */ + {bgp_stop, Active}, /* TCP_connection_open_failed */ + {bgp_stop, Active}, /* TCP_fatal_error */ + {bgp_fsm_exception, Idle}, /* ConnectRetry_timer_expired */ + {bgp_fsm_holdtime_expire, Idle}, /* Hold_Timer_expired */ + {bgp_fsm_exception, Idle}, /* KeepAlive_timer_expired */ + {bgp_fsm_exception, Idle}, /* DelayOpen_timer_expired */ + {bgp_fsm_open, OpenConfirm}, /* Receive_OPEN_message */ + {bgp_fsm_event_error, Idle}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_event_error, Idle}, /* Receive_UPDATE_message */ + {bgp_fsm_event_error, Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_exception, Idle}, /* Clearing_Completed */ + }, + { + /* OpenConfirm, */ + {bgp_ignore, OpenConfirm}, /* BGP_Start */ + {bgp_stop, Idle}, /* BGP_Stop */ + {bgp_stop, Idle}, /* TCP_connection_open */ + {bgp_fsm_exception, Idle}, /* TCP_connection_open_w_delay */ + {bgp_stop, Idle}, /* TCP_connection_closed */ + {bgp_stop, Idle}, /* TCP_connection_open_failed */ + {bgp_stop, Idle}, /* TCP_fatal_error */ + {bgp_fsm_exception, Idle}, /* ConnectRetry_timer_expired */ + {bgp_fsm_holdtime_expire, Idle}, /* Hold_Timer_expired */ + {bgp_ignore, OpenConfirm}, /* KeepAlive_timer_expired */ + {bgp_fsm_exception, Idle}, /* DelayOpen_timer_expired */ + {bgp_fsm_exception, Idle}, /* Receive_OPEN_message */ + {bgp_establish, Established}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_exception, Idle}, /* Receive_UPDATE_message */ + {bgp_stop_with_error, Idle}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_exception, Idle}, /* Clearing_Completed */ + }, + { + /* Established, */ + {bgp_ignore, Established}, /* BGP_Start */ + {bgp_stop, Clearing}, /* BGP_Stop */ + {bgp_stop, Clearing}, /* TCP_connection_open */ + {bgp_fsm_exception, Idle}, /* TCP_connection_open_w_delay */ + {bgp_stop, Clearing}, /* TCP_connection_closed */ + {bgp_stop, Clearing}, /* TCP_connection_open_failed */ + {bgp_stop, Clearing}, /* TCP_fatal_error */ + {bgp_stop, Clearing}, /* ConnectRetry_timer_expired */ + {bgp_fsm_holdtime_expire, Clearing}, /* Hold_Timer_expired */ + {bgp_ignore, Established}, /* KeepAlive_timer_expired */ + {bgp_fsm_exception, Idle}, /* DelayOpen_timer_expired */ + {bgp_stop, Clearing}, /* Receive_OPEN_message */ + {bgp_fsm_keepalive, + Established}, /* Receive_KEEPALIVE_message */ + {bgp_fsm_update, Established}, /* Receive_UPDATE_message */ + {bgp_stop_with_error, + Clearing}, /* Receive_NOTIFICATION_message */ + {bgp_fsm_exception, Idle}, /* Clearing_Completed */ + }, + { + /* Clearing, */ + {bgp_ignore, Clearing}, /* BGP_Start */ + {bgp_stop, Clearing}, /* BGP_Stop */ + {bgp_stop, Clearing}, /* TCP_connection_open */ + {bgp_stop, Clearing}, /* TCP_connection_open_w_delay */ + {bgp_stop, Clearing}, /* TCP_connection_closed */ + {bgp_stop, Clearing}, /* TCP_connection_open_failed */ + {bgp_stop, Clearing}, /* TCP_fatal_error */ + {bgp_stop, Clearing}, /* ConnectRetry_timer_expired */ + {bgp_stop, Clearing}, /* Hold_Timer_expired */ + {bgp_stop, Clearing}, /* KeepAlive_timer_expired */ + {bgp_stop, Clearing}, /* DelayOpen_timer_expired */ + {bgp_stop, Clearing}, /* Receive_OPEN_message */ + {bgp_stop, Clearing}, /* Receive_KEEPALIVE_message */ + {bgp_stop, Clearing}, /* Receive_UPDATE_message */ + {bgp_stop, Clearing}, /* Receive_NOTIFICATION_message */ + {bgp_clearing_completed, Idle}, /* Clearing_Completed */ + }, + { + /* Deleted, */ + {bgp_ignore, Deleted}, /* BGP_Start */ + {bgp_ignore, Deleted}, /* BGP_Stop */ + {bgp_ignore, Deleted}, /* TCP_connection_open */ + {bgp_ignore, Deleted}, /* TCP_connection_open_w_delay */ + {bgp_ignore, Deleted}, /* TCP_connection_closed */ + {bgp_ignore, Deleted}, /* TCP_connection_open_failed */ + {bgp_ignore, Deleted}, /* TCP_fatal_error */ + {bgp_ignore, Deleted}, /* ConnectRetry_timer_expired */ + {bgp_ignore, Deleted}, /* Hold_Timer_expired */ + {bgp_ignore, Deleted}, /* KeepAlive_timer_expired */ + {bgp_ignore, Deleted}, /* DelayOpen_timer_expired */ + {bgp_ignore, Deleted}, /* Receive_OPEN_message */ + {bgp_ignore, Deleted}, /* Receive_KEEPALIVE_message */ + {bgp_ignore, Deleted}, /* Receive_UPDATE_message */ + {bgp_ignore, Deleted}, /* Receive_NOTIFICATION_message */ + {bgp_ignore, Deleted}, /* Clearing_Completed */ + }, +}; + +/* Execute event process. */ +void bgp_event(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + enum bgp_fsm_events event; + struct peer *peer = connection->peer; + + event = EVENT_VAL(thread); + + peer_lock(peer); + bgp_event_update(connection, event); + peer_unlock(peer); +} + +int bgp_event_update(struct peer_connection *connection, + enum bgp_fsm_events event) +{ + enum bgp_fsm_status next; + enum bgp_fsm_state_progress ret = 0; + int fsm_result = FSM_PEER_NOOP; + int passive_conn = 0; + int dyn_nbr; + struct peer *peer = connection->peer; + + passive_conn = + (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) ? 1 : 0; + dyn_nbr = peer_dynamic_neighbor(peer); + + /* Logging this event. */ + next = FSM[connection->status - 1][event - 1].next_state; + + if (bgp_debug_neighbor_events(peer) && connection->status != next) + zlog_debug("%s [FSM] %s (%s->%s), fd %d", peer->host, + bgp_event_str[event], + lookup_msg(bgp_status_msg, connection->status, NULL), + lookup_msg(bgp_status_msg, next, NULL), + connection->fd); + + peer->last_event = peer->cur_event; + peer->cur_event = event; + + /* Call function. */ + if (FSM[connection->status - 1][event - 1].func) + ret = (*(FSM[connection->status - 1][event - 1].func))( + connection); + + switch (ret) { + case BGP_FSM_SUCCESS: + case BGP_FSM_SUCCESS_STATE_TRANSFER: + if (ret == BGP_FSM_SUCCESS_STATE_TRANSFER && + next == Established) { + /* The case when doppelganger swap accurred in + bgp_establish. + Update the peer pointer accordingly */ + fsm_result = FSM_PEER_TRANSFERRED; + } + + /* If status is changed. */ + if (next != connection->status) { + bgp_fsm_change_status(connection, next); + + /* + * If we're going to ESTABLISHED then we executed a + * peer transfer. In this case we can either return + * FSM_PEER_TRANSITIONED or FSM_PEER_TRANSFERRED. + * Opting for TRANSFERRED since transfer implies + * session establishment. + */ + if (fsm_result != FSM_PEER_TRANSFERRED) + fsm_result = FSM_PEER_TRANSITIONED; + } + + /* Make sure timer is set. */ + bgp_timer_set(connection); + break; + case BGP_FSM_FAILURE: + /* + * If we got a return value of -1, that means there was an + * error, restart the FSM. Since bgp_stop() was called on the + * peer. only a few fields are safe to access here. In any case + * we need to indicate that the peer was stopped in the return + * code. + */ + if (!dyn_nbr && !passive_conn && peer->bgp && + ret != BGP_FSM_FAILURE_AND_DELETE) { + flog_err(EC_BGP_FSM, + "%s [FSM] Failure handling event %s in state %s, prior events %s, %s, fd %d, last reset: %s", + peer->host, bgp_event_str[peer->cur_event], + lookup_msg(bgp_status_msg, connection->status, + NULL), + bgp_event_str[peer->last_event], + bgp_event_str[peer->last_major_event], + connection->fd, + peer_down_str[peer->last_reset]); + bgp_stop(connection); + bgp_fsm_change_status(connection, Idle); + bgp_timer_set(connection); + } + fsm_result = FSM_PEER_STOPPED; + break; + case BGP_FSM_FAILURE_AND_DELETE: + fsm_result = FSM_PEER_STOPPED; + break; + } + + return fsm_result; +} +/* BGP GR Code */ + +int bgp_gr_lookup_n_update_all_peer(struct bgp *bgp, + enum global_mode global_new_state, + enum global_mode global_old_state) +{ + struct peer *peer = {0}; + struct listnode *node = {0}; + struct listnode *nnode = {0}; + enum peer_mode peer_old_state = PEER_INVALID; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR] Peer: (%s) :", __func__, + peer->host); + + peer_old_state = bgp_peer_gr_mode_get(peer); + + if (peer_old_state == PEER_GLOBAL_INHERIT) { + + /* + *Reset only these peers and send a + *new open message with the change capabilities. + *Considering the mode to be "global_new_state" and + *do all operation accordingly + */ + + switch (global_new_state) { + case GLOBAL_HELPER: + BGP_PEER_GR_HELPER_ENABLE(peer); + break; + case GLOBAL_GR: + BGP_PEER_GR_ENABLE(peer); + break; + case GLOBAL_DISABLE: + BGP_PEER_GR_DISABLE(peer); + break; + case GLOBAL_INVALID: + zlog_debug("%s [BGP_GR] GLOBAL_INVALID", + __func__); + return BGP_ERR_GR_OPERATION_FAILED; + } + } + } + + bgp->global_gr_present_state = global_new_state; + + return BGP_GR_SUCCESS; +} + +int bgp_gr_update_all(struct bgp *bgp, enum global_gr_command global_gr_cmd) +{ + enum global_mode global_new_state = GLOBAL_INVALID; + enum global_mode global_old_state = GLOBAL_INVALID; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR]START: global_gr_cmd :%s:", __func__, + print_global_gr_cmd(global_gr_cmd)); + + global_old_state = bgp_global_gr_mode_get(bgp); + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] global_old_gr_state :%s:", + print_global_gr_mode(global_old_state)); + + if (global_old_state != GLOBAL_INVALID) { + global_new_state = + bgp->GLOBAL_GR_FSM[global_old_state][global_gr_cmd]; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] global_new_gr_state :%s:", + print_global_gr_mode(global_new_state)); + } else { + zlog_err("%s [BGP_GR] global_old_state == GLOBAL_INVALID", + __func__); + return BGP_ERR_GR_OPERATION_FAILED; + } + + if (global_new_state == GLOBAL_INVALID) { + zlog_err("%s [BGP_GR] global_new_state == GLOBAL_INVALID", + __func__); + return BGP_ERR_GR_INVALID_CMD; + } + if (global_new_state == global_old_state) { + /* Trace msg */ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "%s [BGP_GR] global_new_state == global_old_state :%s", + __func__, + print_global_gr_mode(global_new_state)); + return BGP_GR_NO_OPERATION; + } + + return bgp_gr_lookup_n_update_all_peer(bgp, global_new_state, + global_old_state); +} + +const char *print_peer_gr_mode(enum peer_mode pr_mode) +{ + const char *peer_gr_mode = NULL; + + switch (pr_mode) { + case PEER_HELPER: + peer_gr_mode = "PEER_HELPER"; + break; + case PEER_GR: + peer_gr_mode = "PEER_GR"; + break; + case PEER_DISABLE: + peer_gr_mode = "PEER_DISABLE"; + break; + case PEER_INVALID: + peer_gr_mode = "PEER_INVALID"; + break; + case PEER_GLOBAL_INHERIT: + peer_gr_mode = "PEER_GLOBAL_INHERIT"; + break; + } + + return peer_gr_mode; +} + +const char *print_peer_gr_cmd(enum peer_gr_command pr_gr_cmd) +{ + const char *peer_gr_cmd = NULL; + + switch (pr_gr_cmd) { + case PEER_GR_CMD: + peer_gr_cmd = "PEER_GR_CMD"; + break; + case NO_PEER_GR_CMD: + peer_gr_cmd = "NO_PEER_GR_CMD"; + break; + case PEER_DISABLE_CMD: + peer_gr_cmd = "PEER_DISABLE_GR_CMD"; + break; + case NO_PEER_DISABLE_CMD: + peer_gr_cmd = "NO_PEER_DISABLE_GR_CMD"; + break; + case PEER_HELPER_CMD: + peer_gr_cmd = "PEER_HELPER_CMD"; + break; + case NO_PEER_HELPER_CMD: + peer_gr_cmd = "NO_PEER_HELPER_CMD"; + break; + } + + return peer_gr_cmd; +} + +const char *print_global_gr_mode(enum global_mode gl_mode) +{ + const char *global_gr_mode = "???"; + + switch (gl_mode) { + case GLOBAL_HELPER: + global_gr_mode = "GLOBAL_HELPER"; + break; + case GLOBAL_GR: + global_gr_mode = "GLOBAL_GR"; + break; + case GLOBAL_DISABLE: + global_gr_mode = "GLOBAL_DISABLE"; + break; + case GLOBAL_INVALID: + global_gr_mode = "GLOBAL_INVALID"; + break; + } + + return global_gr_mode; +} + +const char *print_global_gr_cmd(enum global_gr_command gl_gr_cmd) +{ + const char *global_gr_cmd = NULL; + + switch (gl_gr_cmd) { + case GLOBAL_GR_CMD: + global_gr_cmd = "GLOBAL_GR_CMD"; + break; + case NO_GLOBAL_GR_CMD: + global_gr_cmd = "NO_GLOBAL_GR_CMD"; + break; + case GLOBAL_DISABLE_CMD: + global_gr_cmd = "GLOBAL_DISABLE_CMD"; + break; + case NO_GLOBAL_DISABLE_CMD: + global_gr_cmd = "NO_GLOBAL_DISABLE_CMD"; + break; + } + + return global_gr_cmd; +} + +enum global_mode bgp_global_gr_mode_get(struct bgp *bgp) +{ + return bgp->global_gr_present_state; +} + +enum peer_mode bgp_peer_gr_mode_get(struct peer *peer) +{ + return peer->peer_gr_present_state; +} + +int bgp_neighbor_graceful_restart(struct peer *peer, + enum peer_gr_command peer_gr_cmd) +{ + enum peer_mode peer_new_state = PEER_INVALID; + enum peer_mode peer_old_state = PEER_INVALID; + struct bgp_peer_gr peer_state; + int result = BGP_GR_FAILURE; + + /* + * fetch peer_old_state from peer structure also + * fetch global_old_state from bgp structure, + * peer had a back pointer to bgpo struct ; + */ + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR] START:Peer: (%s) : peer_gr_cmd :%s:", + __func__, peer->host, + print_peer_gr_cmd(peer_gr_cmd)); + + peer_old_state = bgp_peer_gr_mode_get(peer); + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR] peer_old_state: %d", __func__, + peer_old_state); + + if (peer_old_state == PEER_INVALID) + return BGP_ERR_GR_OPERATION_FAILED; + + peer_state = peer->PEER_GR_FSM[peer_old_state][peer_gr_cmd]; + peer_new_state = peer_state.next_state; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR] peer_new_state: %d", __func__, + peer_new_state); + + if (peer_new_state == PEER_INVALID) + return BGP_ERR_GR_INVALID_CMD; + + if (peer_new_state != peer_old_state) { + result = peer_state.action_fun(peer, peer_old_state, + peer_new_state); + } else { + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] peer_old_state == peer_new_state !"); + return BGP_GR_NO_OPERATION; + } + + if (result == BGP_GR_SUCCESS) { + + /* Update the mode i.e peer_new_state into the peer structure */ + peer->peer_gr_present_state = peer_new_state; + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Successfully change the state of the peer to : %s : !", + print_peer_gr_mode(peer_new_state)); + + return BGP_GR_SUCCESS; + } + + return result; +} + +unsigned int bgp_peer_gr_action(struct peer *peer, enum peer_mode old_peer_state, + enum peer_mode new_peer_state) +{ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "%s [BGP_GR] Move peer from old_peer_state :%s: to new_peer_state :%s: !!!!", + __func__, print_peer_gr_mode(old_peer_state), + print_peer_gr_mode(new_peer_state)); + + enum global_mode bgp_gr_global_mode = GLOBAL_INVALID; + unsigned int ret = BGP_GR_FAILURE; + + if (old_peer_state == new_peer_state) { + /* Nothing to do over here as the present and old state is the + * same */ + return BGP_GR_NO_OPERATION; + } + if ((old_peer_state == PEER_INVALID) + || (new_peer_state == PEER_INVALID)) { + /* something bad happend , print error message */ + return BGP_ERR_GR_INVALID_CMD; + } + + bgp_gr_global_mode = bgp_global_gr_mode_get(peer->bgp); + + if ((old_peer_state == PEER_GLOBAL_INHERIT) + && (new_peer_state != PEER_GLOBAL_INHERIT)) { + + /* fetch the Mode running in the Global state machine + *from the bgp structure into a variable called + *bgp_gr_global_mode + */ + + /* Here we are checking if the + *1. peer_new_state == global_mode == helper_mode + *2. peer_new_state == global_mode == GR_mode + *3. peer_new_state == global_mode == disabled_mode + */ + + BGP_PEER_GR_GLOBAL_INHERIT_UNSET(peer); + + if ((int)new_peer_state == (int)bgp_gr_global_mode) { + /* This is incremental updates i.e no tear down + * of the existing session + * as the peer is already working in the same mode. + */ + ret = BGP_GR_SUCCESS; + } else { + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Peer state changed from :%s ", + print_peer_gr_mode(old_peer_state)); + + bgp_peer_move_to_gr_mode(peer, new_peer_state); + + ret = BGP_GR_SUCCESS; + } + } + /* In the case below peer is going into Global inherit mode i.e. + * the peer would work as the mode configured at the global level + */ + else if ((new_peer_state == PEER_GLOBAL_INHERIT) + && (old_peer_state != PEER_GLOBAL_INHERIT)) { + /* Here in this case it would be destructive + * in all the cases except one case when, + * Global GR is configured Disabled + * and present_peer_state is not disable + */ + + BGP_PEER_GR_GLOBAL_INHERIT_SET(peer); + + if ((int)old_peer_state == (int)bgp_gr_global_mode) { + /* This is incremental updates + *i.e no tear down of the existing session + *as the peer is already working in the same mode. + */ + ret = BGP_GR_SUCCESS; + } else { + /* Destructive always */ + /* Tear down the old session + * and send the new capability + * as per the bgp_gr_global_mode + */ + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Peer state changed from :%s", + print_peer_gr_mode(old_peer_state)); + + bgp_peer_move_to_gr_mode(peer, bgp_gr_global_mode); + + ret = BGP_GR_SUCCESS; + } + } else { + /* + *This else case, it include all the cases except --> + *(new_peer_state != Peer_Global) && + *( old_peer_state != Peer_Global ) + */ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] Peer state changed from :%s", + print_peer_gr_mode(old_peer_state)); + + bgp_peer_move_to_gr_mode(peer, new_peer_state); + + ret = BGP_GR_SUCCESS; + } + + return ret; +} + +inline void bgp_peer_move_to_gr_mode(struct peer *peer, int new_state) + +{ + int bgp_global_gr_mode = bgp_global_gr_mode_get(peer->bgp); + + switch (new_state) { + case PEER_HELPER: + BGP_PEER_GR_HELPER_ENABLE(peer); + break; + case PEER_GR: + BGP_PEER_GR_ENABLE(peer); + break; + case PEER_DISABLE: + BGP_PEER_GR_DISABLE(peer); + break; + case PEER_GLOBAL_INHERIT: + BGP_PEER_GR_GLOBAL_INHERIT_SET(peer); + + if (bgp_global_gr_mode == GLOBAL_HELPER) { + BGP_PEER_GR_HELPER_ENABLE(peer); + } else if (bgp_global_gr_mode == GLOBAL_GR) { + BGP_PEER_GR_ENABLE(peer); + } else if (bgp_global_gr_mode == GLOBAL_DISABLE) { + BGP_PEER_GR_DISABLE(peer); + } else { + zlog_err( + "[BGP_GR] Default switch inherit mode ::: SOMETHING IS WRONG !!!"); + } + break; + default: + zlog_err( + "[BGP_GR] Default switch mode ::: SOMETHING IS WRONG !!!"); + break; + } + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] Peer state changed --to--> : %d : !", + new_state); +} + +void bgp_peer_gr_flags_update(struct peer *peer) +{ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s [BGP_GR] called !", __func__); + if (CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) + SET_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART_HELPER); + else + UNSET_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART_HELPER); + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Peer %s Flag PEER_FLAG_GRACEFUL_RESTART_HELPER : %s : !", + peer->host, + (CHECK_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART_HELPER) + ? "Set" + : "UnSet")); + if (CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)) + SET_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART); + else + UNSET_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART); + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Peer %s Flag PEER_FLAG_GRACEFUL_RESTART : %s : !", + peer->host, + (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART) + ? "Set" + : "UnSet")); + if (CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT)) + SET_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT); + else + UNSET_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT); + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Peer %s Flag PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT : %s : !", + peer->host, + (CHECK_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT) + ? "Set" + : "UnSet")); + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART) + && !CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART_HELPER)) { + zlog_debug("[BGP_GR] Peer %s UNSET PEER_STATUS_NSF_MODE!", + peer->host); + + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { + + peer_nsf_stop(peer); + zlog_debug( + "[BGP_GR] Peer %s UNSET PEER_STATUS_NSF_WAIT!", + peer->host); + } + } +} diff --git a/bgpd/bgp_fsm.h b/bgpd/bgp_fsm.h new file mode 100644 index 0000000..bcdd491 --- /dev/null +++ b/bgpd/bgp_fsm.h @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP-4 Finite State Machine + * From RFC1771 [A Border Gateway Protocol 4 (BGP-4)] + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_FSM_H +#define _QUAGGA_BGP_FSM_H + +enum bgp_fsm_state_progress { + BGP_FSM_FAILURE_AND_DELETE = -2, + BGP_FSM_FAILURE = -1, + BGP_FSM_SUCCESS = 0, + BGP_FSM_SUCCESS_STATE_TRANSFER = 1, +}; + +/* Macro for BGP read, write and timer thread. */ +#define BGP_TIMER_ON(T, F, V) \ + do { \ + if ((connection->status != Deleted)) \ + event_add_timer(bm->master, (F), connection, (V), \ + &(T)); \ + } while (0) + +#define BGP_EVENT_ADD(C, E) \ + do { \ + if ((C)->status != Deleted) \ + event_add_event(bm->master, bgp_event, (C), (E), NULL); \ + } while (0) + +#define BGP_UPDATE_GROUP_TIMER_ON(T, F) \ + do { \ + if (BGP_SUPPRESS_FIB_ENABLED(peer->bgp) && \ + PEER_ROUTE_ADV_DELAY(peer)) \ + event_add_timer_msec(bm->master, (F), connection, \ + (BGP_DEFAULT_UPDATE_ADVERTISEMENT_TIME * \ + 1000), \ + (T)); \ + else \ + event_add_timer_msec(bm->master, (F), connection, 0, \ + (T)); \ + } while (0) + +#define BGP_MSEC_JITTER 10 + +/* Status codes for bgp_event_update() */ +#define FSM_PEER_NOOP 0 +#define FSM_PEER_STOPPED 1 +#define FSM_PEER_TRANSFERRED 2 +#define FSM_PEER_TRANSITIONED 3 + +#define BGP_PEER_GR_HELPER_ENABLE(peer) \ + do { \ + UNSET_FLAG( \ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART); \ + SET_FLAG( \ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER);\ + } while (0) + +#define BGP_PEER_GR_ENABLE(peer)\ + do { \ + SET_FLAG( \ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART); \ + UNSET_FLAG( \ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER);\ + } while (0) + +#define BGP_PEER_GR_DISABLE(peer)\ + do { \ + UNSET_FLAG( \ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART);\ + UNSET_FLAG(\ + peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER);\ + } while (0) + +#define BGP_PEER_GR_GLOBAL_INHERIT_SET(peer) \ + SET_FLAG(peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT) + +#define BGP_PEER_GR_GLOBAL_INHERIT_UNSET(peer) \ + UNSET_FLAG(peer->peer_gr_new_status_flag, \ + PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT) + +#define BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) \ + (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV) \ + && CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) + +#define BGP_PEER_RESTARTING_MODE(peer) \ + (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART) && \ + CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_ADV) && \ + !CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV)) + +#define BGP_PEER_HELPER_MODE(peer) \ + (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART_HELPER) && \ + CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV) && \ + !CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_ADV)) + +/* Prototypes. */ + +/* + * Update FSM for peer based on whether we have valid nexthops or not. + */ +extern void bgp_fsm_nht_update(struct peer_connection *connection, + struct peer *peer, bool has_valid_nexthops); +extern void bgp_event(struct event *event); +extern int bgp_event_update(struct peer_connection *connection, + enum bgp_fsm_events event); +extern enum bgp_fsm_state_progress bgp_stop(struct peer_connection *connection); +extern void bgp_timer_set(struct peer_connection *connection); +extern void bgp_routeadv_timer(struct event *event); +extern void bgp_fsm_change_status(struct peer_connection *connection, + enum bgp_fsm_status status); +extern const char *const peer_down_str[]; +extern void bgp_update_delay_end(struct bgp *); +extern void bgp_maxmed_update(struct bgp *); +extern bool bgp_maxmed_onstartup_configured(struct bgp *); +extern bool bgp_maxmed_onstartup_active(struct bgp *); +extern int bgp_fsm_error_subcode(int status); +extern enum bgp_fsm_state_progress +bgp_stop_with_notify(struct peer_connection *connection, uint8_t code, + uint8_t sub_code); + +/** + * Start the route advertisement timer (that honors MRAI) for all the + * peers. Typically called at the end of initial convergence, coming + * out of read-only mode. + */ +extern void bgp_start_routeadv(struct bgp *); + +/** + * See if the route advertisement timer needs to be adjusted for a + * peer. For example, if the last update was written to the peer a + * long while back, we don't need to wait for the periodic advertisement + * timer to expire to send the new set of prefixes. It should fire + * instantly and updates should go out sooner. + */ +extern void bgp_adjust_routeadv(struct peer *); + +#include "hook.h" +DECLARE_HOOK(peer_backward_transition, (struct peer *peer), (peer)); +DECLARE_HOOK(peer_established, (struct peer *peer), (peer)); + +int bgp_gr_update_all(struct bgp *bgp, enum global_gr_command global_gr_cmd); +int bgp_neighbor_graceful_restart(struct peer *peer, + enum peer_gr_command peer_gr_cmd); +unsigned int bgp_peer_gr_action(struct peer *peer, enum peer_mode old_peer_state, + enum peer_mode new_peer_state); +void bgp_peer_move_to_gr_mode(struct peer *peer, int new_state); +unsigned int bgp_peer_gr_helper_enable(struct peer *peer); +unsigned int bgp_peer_gr_enable(struct peer *peer); +unsigned int bgp_peer_gr_global_inherit(struct peer *peer); +unsigned int bgp_peer_gr_disable(struct peer *peer); +enum peer_mode bgp_peer_gr_mode_get(struct peer *peer); +enum global_mode bgp_global_gr_mode_get(struct bgp *bgp); +enum peer_mode bgp_get_peer_gr_mode_from_flags(struct peer *peer); +unsigned int bgp_peer_gr_global_inherit_unset(struct peer *peer); +int bgp_gr_lookup_n_update_all_peer(struct bgp *bgp, + enum global_mode global_new_state, + enum global_mode global_old_state); +void bgp_peer_gr_flags_update(struct peer *peer); +const char *print_peer_gr_mode(enum peer_mode pr_mode); +const char *print_peer_gr_cmd(enum peer_gr_command pr_gr_cmd); +const char *print_global_gr_mode(enum global_mode gl_mode); +const char *print_global_gr_cmd(enum global_gr_command gl_gr_cmd); +int bgp_peer_reg_with_nht(struct peer *peer); +#endif /* _QUAGGA_BGP_FSM_H */ diff --git a/bgpd/bgp_io.c b/bgpd/bgp_io.c new file mode 100644 index 0000000..b07e69a --- /dev/null +++ b/bgpd/bgp_io.c @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP I/O. + * Implements packet I/O in a pthread. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ + +/* clang-format off */ +#include +#include // for pthread_mutex_unlock, pthread_mutex_lock +#include // for writev + +#include "frr_pthread.h" +#include "linklist.h" // for list_delete, list_delete_all_node, lis... +#include "log.h" // for zlog_debug, safe_strerror, zlog_err +#include "memory.h" // for MTYPE_TMP, XCALLOC, XFREE +#include "network.h" // for ERRNO_IO_RETRY +#include "stream.h" // for stream_get_endp, stream_getw_from, str... +#include "ringbuf.h" // for ringbuf_remain, ringbuf_peek, ringbuf_... +#include "frrevent.h" // for EVENT_OFF, EVENT_ARG, thread... + +#include "bgpd/bgp_io.h" +#include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events, bgp_type_str +#include "bgpd/bgp_errors.h" // for expanded error reference information +#include "bgpd/bgp_fsm.h" // for BGP_EVENT_ADD, bgp_event +#include "bgpd/bgp_packet.h" // for bgp_notify_io_invalid... +#include "bgpd/bgp_trace.h" // for frrtraces +#include "bgpd/bgpd.h" // for peer, BGP_MARKER_SIZE, bgp_master, bm +/* clang-format on */ + +/* forward declarations */ +static uint16_t bgp_write(struct peer_connection *connection); +static uint16_t bgp_read(struct peer_connection *connection, int *code_p); +static void bgp_process_writes(struct event *event); +static void bgp_process_reads(struct event *event); +static bool validate_header(struct peer_connection *connection); + +/* generic i/o status codes */ +#define BGP_IO_TRANS_ERR (1 << 0) /* EAGAIN or similar occurred */ +#define BGP_IO_FATAL_ERR (1 << 1) /* some kind of fatal TCP error */ +#define BGP_IO_WORK_FULL_ERR (1 << 2) /* No room in work buffer */ + +/* Thread external API ----------------------------------------------------- */ + +void bgp_writes_on(struct peer_connection *connection) +{ + struct frr_pthread *fpt = bgp_pth_io; + + assert(fpt->running); + + assert(connection->status != Deleted); + assert(connection->obuf); + assert(connection->ibuf); + assert(connection->ibuf_work); + assert(!connection->t_connect_check_r); + assert(!connection->t_connect_check_w); + assert(connection->fd); + + event_add_write(fpt->master, bgp_process_writes, connection, + connection->fd, &connection->t_write); + SET_FLAG(connection->thread_flags, PEER_THREAD_WRITES_ON); +} + +void bgp_writes_off(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + struct frr_pthread *fpt = bgp_pth_io; + assert(fpt->running); + + event_cancel_async(fpt->master, &connection->t_write, NULL); + EVENT_OFF(connection->t_generate_updgrp_packets); + + UNSET_FLAG(peer->connection->thread_flags, PEER_THREAD_WRITES_ON); +} + +void bgp_reads_on(struct peer_connection *connection) +{ + struct frr_pthread *fpt = bgp_pth_io; + assert(fpt->running); + + assert(connection->status != Deleted); + assert(connection->ibuf); + assert(connection->fd); + assert(connection->ibuf_work); + assert(connection->obuf); + assert(!connection->t_connect_check_r); + assert(!connection->t_connect_check_w); + assert(connection->fd); + + event_add_read(fpt->master, bgp_process_reads, connection, + connection->fd, &connection->t_read); + + SET_FLAG(connection->thread_flags, PEER_THREAD_READS_ON); +} + +void bgp_reads_off(struct peer_connection *connection) +{ + struct frr_pthread *fpt = bgp_pth_io; + assert(fpt->running); + + event_cancel_async(fpt->master, &connection->t_read, NULL); + EVENT_OFF(connection->t_process_packet); + EVENT_OFF(connection->t_process_packet_error); + + UNSET_FLAG(connection->thread_flags, PEER_THREAD_READS_ON); +} + +/* Thread internal functions ----------------------------------------------- */ + +/* + * Called from I/O pthread when a file descriptor has become ready for writing. + */ +static void bgp_process_writes(struct event *thread) +{ + static struct peer *peer; + struct peer_connection *connection = EVENT_ARG(thread); + uint16_t status; + bool reschedule; + bool fatal = false; + + peer = connection->peer; + + if (connection->fd < 0) + return; + + struct frr_pthread *fpt = bgp_pth_io; + + frr_with_mutex (&connection->io_mtx) { + status = bgp_write(connection); + reschedule = (stream_fifo_head(connection->obuf) != NULL); + } + + /* no problem */ + if (CHECK_FLAG(status, BGP_IO_TRANS_ERR)) { + } + + /* problem */ + if (CHECK_FLAG(status, BGP_IO_FATAL_ERR)) { + reschedule = false; + fatal = true; + } + + /* If suppress fib pending is enabled, route is advertised to peers when + * the status is received from the FIB. The delay is added + * to update group packet generate which will allow more routes to be + * sent in the update message + */ + if (reschedule) { + event_add_write(fpt->master, bgp_process_writes, connection, + connection->fd, &connection->t_write); + } else if (!fatal) { + BGP_UPDATE_GROUP_TIMER_ON(&connection->t_generate_updgrp_packets, + bgp_generate_updgrp_packets); + } +} + +static int read_ibuf_work(struct peer_connection *connection) +{ + /* static buffer for transferring packets */ + /* shorter alias to peer's input buffer */ + struct ringbuf *ibw = connection->ibuf_work; + /* packet size as given by header */ + uint16_t pktsize = 0; + struct stream *pkt; + + /* ============================================== */ + frr_with_mutex (&connection->io_mtx) { + if (connection->ibuf->count >= bm->inq_limit) + return -ENOMEM; + } + + /* check that we have enough data for a header */ + if (ringbuf_remain(ibw) < BGP_HEADER_SIZE) + return 0; + + /* check that header is valid */ + if (!validate_header(connection)) + return -EBADMSG; + + /* header is valid; retrieve packet size */ + ringbuf_peek(ibw, BGP_MARKER_SIZE, &pktsize, sizeof(pktsize)); + + pktsize = ntohs(pktsize); + + /* if this fails we are seriously screwed */ + assert(pktsize <= connection->peer->max_packet_size); + + /* + * If we have that much data, chuck it into its own + * stream and append to input queue for processing. + * + * Otherwise, come back later. + */ + if (ringbuf_remain(ibw) < pktsize) + return 0; + + pkt = stream_new(pktsize); + assert(STREAM_WRITEABLE(pkt) == pktsize); + assert(ringbuf_get(ibw, pkt->data, pktsize) == pktsize); + stream_set_endp(pkt, pktsize); + + frrtrace(2, frr_bgp, packet_read, connection->peer, pkt); + frr_with_mutex (&connection->io_mtx) { + stream_fifo_push(connection->ibuf, pkt); + } + + return pktsize; +} + +/* + * Called from I/O pthread when a file descriptor has become ready for reading, + * or has hung up. + * + * We read as much data as possible, process as many packets as we can and + * place them on peer->connection.ibuf for secondary processing by the main + * thread. + */ +static void bgp_process_reads(struct event *thread) +{ + /* clang-format off */ + struct peer_connection *connection = EVENT_ARG(thread); + static struct peer *peer; /* peer to read from */ + uint16_t status; /* bgp_read status code */ + bool fatal = false; /* whether fatal error occurred */ + bool added_pkt = false; /* whether we pushed onto ->connection.ibuf */ + int code = 0; /* FSM code if error occurred */ + static bool ibuf_full_logged; /* Have we logged full already */ + int ret = 1; + /* clang-format on */ + + peer = connection->peer; + + if (bm->terminating || connection->fd < 0) + return; + + struct frr_pthread *fpt = bgp_pth_io; + + frr_with_mutex (&connection->io_mtx) { + status = bgp_read(connection, &code); + } + + /* error checking phase */ + if (CHECK_FLAG(status, BGP_IO_TRANS_ERR)) { + /* no problem; just don't process packets */ + goto done; + } + + if (CHECK_FLAG(status, BGP_IO_FATAL_ERR)) { + /* problem; tear down session */ + fatal = true; + + /* Handle the error in the main pthread, include the + * specific state change from 'bgp_read'. + */ + event_add_event(bm->master, bgp_packet_process_error, connection, + code, &connection->t_process_packet_error); + goto done; + } + + while (true) { + ret = read_ibuf_work(connection); + if (ret <= 0) + break; + + added_pkt = true; + } + + switch (ret) { + case -EBADMSG: + fatal = true; + break; + case -ENOMEM: + if (!ibuf_full_logged) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s [Event] Peer Input-Queue is full: limit (%u)", + peer->host, bm->inq_limit); + + ibuf_full_logged = true; + } + break; + default: + ibuf_full_logged = false; + break; + } + +done: + /* handle invalid header */ + if (fatal) { + /* wipe buffer just in case someone screwed up */ + ringbuf_wipe(connection->ibuf_work); + return; + } + + event_add_read(fpt->master, bgp_process_reads, connection, + connection->fd, &connection->t_read); + if (added_pkt) + event_add_event(bm->master, bgp_process_packet, connection, 0, + &connection->t_process_packet); +} + +/* + * Flush peer output buffer. + * + * This function pops packets off of peer->connection.obuf and writes them to + * peer->connection.fd. The amount of packets written is equal to the minimum of + * peer->wpkt_quanta and the number of packets on the output buffer, unless an + * error occurs. + * + * If write() returns an error, the appropriate FSM event is generated. + * + * The return value is equal to the number of packets written + * (which may be zero). + */ +static uint16_t bgp_write(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + uint8_t type; + struct stream *s; + int update_last_write = 0; + unsigned int count; + uint32_t uo = 0; + uint16_t status = 0; + uint32_t wpkt_quanta_old; + + int writenum = 0; + int num; + unsigned int iovsz; + unsigned int strmsz; + unsigned int total_written; + time_t now; + + wpkt_quanta_old = atomic_load_explicit(&peer->bgp->wpkt_quanta, + memory_order_relaxed); + struct stream *ostreams[wpkt_quanta_old]; + struct stream **streams = ostreams; + struct iovec iov[wpkt_quanta_old]; + + s = stream_fifo_head(connection->obuf); + + if (!s) + goto done; + + count = iovsz = 0; + while (count < wpkt_quanta_old && iovsz < array_size(iov) && s) { + ostreams[iovsz] = s; + iov[iovsz].iov_base = stream_pnt(s); + iov[iovsz].iov_len = STREAM_READABLE(s); + writenum += STREAM_READABLE(s); + s = s->next; + ++iovsz; + ++count; + } + + strmsz = iovsz; + total_written = 0; + + do { + num = writev(connection->fd, iov, iovsz); + + if (num < 0) { + if (!ERRNO_IO_RETRY(errno)) { + BGP_EVENT_ADD(connection, TCP_fatal_error); + SET_FLAG(status, BGP_IO_FATAL_ERR); + } else { + SET_FLAG(status, BGP_IO_TRANS_ERR); + } + + break; + } else if (num != writenum) { + unsigned int msg_written = 0; + unsigned int ic = iovsz; + + for (unsigned int i = 0; i < ic; i++) { + size_t ss = iov[i].iov_len; + + if (ss > (unsigned int) num) + break; + + msg_written++; + iovsz--; + writenum -= ss; + num -= ss; + } + + total_written += msg_written; + + assert(total_written < count); + + memmove(&iov, &iov[msg_written], + sizeof(iov[0]) * iovsz); + streams = &streams[msg_written]; + stream_forward_getp(streams[0], num); + iov[0].iov_base = stream_pnt(streams[0]); + iov[0].iov_len = STREAM_READABLE(streams[0]); + + writenum -= num; + num = 0; + assert(writenum > 0); + } else { + total_written = strmsz; + } + + } while (num != writenum); + + /* Handle statistics */ + for (unsigned int i = 0; i < total_written; i++) { + s = stream_fifo_pop(connection->obuf); + + assert(s == ostreams[i]); + + /* Retrieve BGP packet type. */ + stream_set_getp(s, BGP_MARKER_SIZE + 2); + type = stream_getc(s); + + switch (type) { + case BGP_MSG_OPEN: + atomic_fetch_add_explicit(&peer->open_out, 1, + memory_order_relaxed); + break; + case BGP_MSG_UPDATE: + atomic_fetch_add_explicit(&peer->update_out, 1, + memory_order_relaxed); + uo++; + break; + case BGP_MSG_NOTIFY: + atomic_fetch_add_explicit(&peer->notify_out, 1, + memory_order_relaxed); + /* Double start timer. */ + peer->v_start *= 2; + + /* Overflow check. */ + if (peer->v_start >= (60 * 2)) + peer->v_start = (60 * 2); + + /* + * Handle Graceful Restart case where the state changes + * to Connect instead of Idle. + */ + BGP_EVENT_ADD(connection, BGP_Stop); + goto done; + + case BGP_MSG_KEEPALIVE: + atomic_fetch_add_explicit(&peer->keepalive_out, 1, + memory_order_relaxed); + break; + case BGP_MSG_ROUTE_REFRESH_NEW: + case BGP_MSG_ROUTE_REFRESH_OLD: + atomic_fetch_add_explicit(&peer->refresh_out, 1, + memory_order_relaxed); + break; + case BGP_MSG_CAPABILITY: + atomic_fetch_add_explicit(&peer->dynamic_cap_out, 1, + memory_order_relaxed); + break; + } + + stream_free(s); + ostreams[i] = NULL; + update_last_write = 1; + } + +done : { + now = monotime(NULL); + /* + * Update last_update if UPDATEs were written. + * Note: that these are only updated at end, + * not per message (i.e., per loop) + */ + if (uo) + atomic_store_explicit(&peer->last_update, now, + memory_order_relaxed); + + /* If we TXed any flavor of packet */ + if (update_last_write) { + atomic_store_explicit(&peer->last_write, now, + memory_order_relaxed); + peer->last_sendq_ok = now; + } +} + + return status; +} + +uint8_t ibuf_scratch[BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE * BGP_READ_PACKET_MAX]; +/* + * Reads a chunk of data from peer->connection.fd into + * peer->connection.ibuf_work. + * + * code_p + * Pointer to location to store FSM event code in case of fatal error. + * + * @return status flag (see top-of-file) + * + * PLEASE NOTE: If we ever transform the bgp_read to be a pthread + * per peer then we need to rethink the global ibuf_scratch + * data structure above. + */ +static uint16_t bgp_read(struct peer_connection *connection, int *code_p) +{ + size_t readsize; /* how many bytes we want to read */ + ssize_t nbytes; /* how many bytes we actually read */ + size_t ibuf_work_space; /* space we can read into the work buf */ + uint16_t status = 0; + + ibuf_work_space = ringbuf_space(connection->ibuf_work); + + if (ibuf_work_space == 0) { + SET_FLAG(status, BGP_IO_WORK_FULL_ERR); + return status; + } + + readsize = MIN(ibuf_work_space, sizeof(ibuf_scratch)); + + nbytes = read(connection->fd, ibuf_scratch, readsize); + + /* EAGAIN or EWOULDBLOCK; come back later */ + if (nbytes < 0 && ERRNO_IO_RETRY(errno)) { + SET_FLAG(status, BGP_IO_TRANS_ERR); + } else if (nbytes < 0) { + /* Fatal error; tear down session */ + flog_err(EC_BGP_UPDATE_RCV, + "%s [Error] bgp_read_packet error: %s", + connection->peer->host, safe_strerror(errno)); + + /* Handle the error in the main pthread. */ + if (code_p) + *code_p = TCP_fatal_error; + + SET_FLAG(status, BGP_IO_FATAL_ERR); + + } else if (nbytes == 0) { + /* Received EOF / TCP session closed */ + if (bgp_debug_neighbor_events(connection->peer)) + zlog_debug("%s [Event] BGP connection closed fd %d", + connection->peer->host, connection->fd); + + /* Handle the error in the main pthread. */ + if (code_p) + *code_p = TCP_connection_closed; + + SET_FLAG(status, BGP_IO_FATAL_ERR); + } else { + assert(ringbuf_put(connection->ibuf_work, ibuf_scratch, + nbytes) == (size_t)nbytes); + } + + return status; +} + +/* + * Called after we have read a BGP packet header. Validates marker, message + * type and packet length. If any of these aren't correct, sends a notify. + * + * Assumes that there are at least BGP_HEADER_SIZE readable bytes in the input + * buffer. + */ +static bool validate_header(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + uint16_t size; + uint8_t type; + struct ringbuf *pkt = connection->ibuf_work; + + static const uint8_t m_correct[BGP_MARKER_SIZE] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + uint8_t m_rx[BGP_MARKER_SIZE] = {0x00}; + + if (ringbuf_peek(pkt, 0, m_rx, BGP_MARKER_SIZE) != BGP_MARKER_SIZE) + return false; + + if (memcmp(m_correct, m_rx, BGP_MARKER_SIZE) != 0) { + bgp_notify_io_invalid(peer, BGP_NOTIFY_HEADER_ERR, + BGP_NOTIFY_HEADER_NOT_SYNC, NULL, 0); + return false; + } + + /* Get size and type in network byte order. */ + ringbuf_peek(pkt, BGP_MARKER_SIZE, &size, sizeof(size)); + ringbuf_peek(pkt, BGP_MARKER_SIZE + 2, &type, sizeof(type)); + + size = ntohs(size); + + /* BGP type check. */ + if (type != BGP_MSG_OPEN && type != BGP_MSG_UPDATE + && type != BGP_MSG_NOTIFY && type != BGP_MSG_KEEPALIVE + && type != BGP_MSG_ROUTE_REFRESH_NEW + && type != BGP_MSG_ROUTE_REFRESH_OLD + && type != BGP_MSG_CAPABILITY) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s unknown message type 0x%02x", peer->host, + type); + + bgp_notify_io_invalid(peer, BGP_NOTIFY_HEADER_ERR, + BGP_NOTIFY_HEADER_BAD_MESTYPE, &type, 1); + return false; + } + + /* Minimum packet length check. */ + if ((size < BGP_HEADER_SIZE) || (size > peer->max_packet_size) + || (type == BGP_MSG_OPEN && size < BGP_MSG_OPEN_MIN_SIZE) + || (type == BGP_MSG_UPDATE && size < BGP_MSG_UPDATE_MIN_SIZE) + || (type == BGP_MSG_NOTIFY && size < BGP_MSG_NOTIFY_MIN_SIZE) + || (type == BGP_MSG_KEEPALIVE && size != BGP_MSG_KEEPALIVE_MIN_SIZE) + || (type == BGP_MSG_ROUTE_REFRESH_NEW + && size < BGP_MSG_ROUTE_REFRESH_MIN_SIZE) + || (type == BGP_MSG_ROUTE_REFRESH_OLD + && size < BGP_MSG_ROUTE_REFRESH_MIN_SIZE) + || (type == BGP_MSG_CAPABILITY + && size < BGP_MSG_CAPABILITY_MIN_SIZE)) { + if (bgp_debug_neighbor_events(peer)) { + zlog_debug("%s bad message length - %d for %s", + peer->host, size, + type == 128 ? "ROUTE-REFRESH" + : bgp_type_str[(int)type]); + } + + uint16_t nsize = htons(size); + + bgp_notify_io_invalid(peer, BGP_NOTIFY_HEADER_ERR, + BGP_NOTIFY_HEADER_BAD_MESLEN, + (unsigned char *)&nsize, 2); + return false; + } + + return true; +} diff --git a/bgpd/bgp_io.h b/bgpd/bgp_io.h new file mode 100644 index 0000000..8d48112 --- /dev/null +++ b/bgpd/bgp_io.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP I/O. + * Implements packet I/O in a pthread. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ + +#ifndef _FRR_BGP_IO_H +#define _FRR_BGP_IO_H + +#define BGP_WRITE_PACKET_MAX 64U +#define BGP_READ_PACKET_MAX 10U + +#include "bgpd/bgpd.h" +#include "frr_pthread.h" + +struct peer_connection; + +/** + * Start function for write thread. + * + * @param arg - unused + */ +extern void *bgp_io_start(void *arg); + +/** + * Start function for write thread. + * + * Uninitializes all resources and stops the thread. + * + * @param result - where to store data result, unused + */ +extern int bgp_io_stop(void **result, struct frr_pthread *fpt); + +/** + * Turns on packet writing for a peer. + * + * After this function is called, any packets placed on connection->obuf will be + * written to connection->fd until no more packets remain. + * + * Additionally, it becomes unsafe to perform socket actions on connection->fd. + * + * @param peer - peer to register + */ +extern void bgp_writes_on(struct peer_connection *peer); + +/** + * Turns off packet writing for a peer. + * + * After this function returns, packets placed on connection->obuf will not be + * written to connection->fd by the I/O thread. + * + * After this function returns it becomes safe to perform socket actions on + * connection->fd. + * + * @param connection - connection to deregister + * @param flush - as described + */ +extern void bgp_writes_off(struct peer_connection *connection); + +/** + * Turns on packet reading for a peer. + * + * After this function is called, any packets received on connection->fd + * will be read and copied into the FIFO queue connection->ibuf. + * + * Additionally, it becomes unsafe to perform socket actions on connection->fd. + * + * Whenever one or more packets are placed onto connection->ibuf, a task of type + * THREAD_EVENT will be placed on the main thread whose handler is + * + * bgp_packet.c:bgp_process_packet() + * + * @param connection - The connection to register + */ +extern void bgp_reads_on(struct peer_connection *connection); + +/** + * Turns off packet reading for a peer. + * + * After this function is called, any packets received on connection->fd + * will not be read by the I/O thread. + * + * After this function returns it becomes safe to perform socket actions on + * connection->fd. + * + * @param connection - The connection to register for + */ +extern void bgp_reads_off(struct peer_connection *connection); + +#endif /* _FRR_BGP_IO_H */ diff --git a/bgpd/bgp_keepalives.c b/bgpd/bgp_keepalives.c new file mode 100644 index 0000000..92123c2 --- /dev/null +++ b/bgpd/bgp_keepalives.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Keepalives. + * Implements a producer thread to generate BGP keepalives for peers. + * Copyright (C) 2017 Cumulus Networks, Inc. + * Quentin Young + */ + +/* clang-format off */ +#include +#include // for pthread_mutex_lock, pthread_mutex_unlock + +#include "frr_pthread.h" // for frr_pthread +#include "hash.h" // for hash, hash_clean, hash_create_size... +#include "log.h" // for zlog_debug +#include "memory.h" // for MTYPE_TMP, XFREE, XCALLOC, XMALLOC +#include "monotime.h" // for monotime, monotime_since + +#include "bgpd/bgpd.h" // for peer, PEER_EVENT_KEEPALIVES_ON, peer... +#include "bgpd/bgp_debug.h" // for bgp_debug_neighbor_events +#include "bgpd/bgp_packet.h" // for bgp_keepalive_send +#include "bgpd/bgp_keepalives.h" +/* clang-format on */ + +DEFINE_MTYPE_STATIC(BGPD, BGP_PKAT, "Peer KeepAlive Timer"); +DEFINE_MTYPE_STATIC(BGPD, BGP_COND, "BGP Peer pthread Conditional"); +DEFINE_MTYPE_STATIC(BGPD, BGP_MUTEX, "BGP Peer pthread Mutex"); + +/* + * Peer KeepAlive Timer. + * Associates a peer with the time of its last keepalive. + */ +struct pkat { + /* the peer to send keepalives to */ + struct peer *peer; + /* absolute time of last keepalive sent */ + struct timeval last; +}; + +/* List of peers we are sending keepalives for, and associated mutex. */ +static pthread_mutex_t *peerhash_mtx; +static pthread_cond_t *peerhash_cond; +static struct hash *peerhash; + +static struct pkat *pkat_new(struct peer *peer) +{ + struct pkat *pkat = XMALLOC(MTYPE_BGP_PKAT, sizeof(struct pkat)); + pkat->peer = peer; + monotime(&pkat->last); + return pkat; +} + +static void pkat_del(void *pkat) +{ + XFREE(MTYPE_BGP_PKAT, pkat); +} + + +/* + * Callback for hash_iterate. Determines if a peer needs a keepalive and if so, + * generates and sends it. + * + * For any given peer, if the elapsed time since its last keepalive exceeds its + * configured keepalive timer, a keepalive is sent to the peer and its + * last-sent time is reset. Additionally, If the elapsed time does not exceed + * the configured keepalive timer, but the time until the next keepalive is due + * is within a hardcoded tolerance, a keepalive is sent as if the configured + * timer was exceeded. Doing this helps alleviate nanosecond sleeps between + * ticks by grouping together peers who are due for keepalives at roughly the + * same time. This tolerance value is arbitrarily chosen to be 100ms. + * + * In addition, this function calculates the maximum amount of time that the + * keepalive thread can sleep before another tick needs to take place. This is + * equivalent to shortest time until a keepalive is due for any one peer. + * + * @return maximum time to wait until next update (0 if infinity) + */ +static void peer_process(struct hash_bucket *hb, void *arg) +{ + struct pkat *pkat = hb->data; + + struct timeval *next_update = arg; + + static struct timeval elapsed; // elapsed time since keepalive + static struct timeval ka = {0}; // peer->v_keepalive as a timeval + static struct timeval diff; // ka - elapsed + + static const struct timeval tolerance = {0, 100000}; + + uint32_t v_ka = atomic_load_explicit(&pkat->peer->v_keepalive, + memory_order_relaxed); + + /* 0 keepalive timer means no keepalives */ + if (v_ka == 0) + return; + + /* calculate elapsed time since last keepalive */ + monotime_since(&pkat->last, &elapsed); + + /* calculate difference between elapsed time and configured time */ + ka.tv_sec = v_ka; + timersub(&ka, &elapsed, &diff); + + int send_keepalive = + elapsed.tv_sec >= ka.tv_sec || timercmp(&diff, &tolerance, <); + + if (send_keepalive) { + if (bgp_debug_keepalive(pkat->peer)) + zlog_debug("%s [FSM] Timer (keepalive timer expire)", + pkat->peer->host); + + bgp_keepalive_send(pkat->peer); + monotime(&pkat->last); + memset(&elapsed, 0, sizeof(elapsed)); + diff = ka; + } + + /* if calculated next update for this peer < current delay, use it */ + if (next_update->tv_sec < 0 || timercmp(&diff, next_update, <)) + *next_update = diff; +} + +static bool peer_hash_cmp(const void *f, const void *s) +{ + const struct pkat *p1 = f; + const struct pkat *p2 = s; + + return p1->peer == p2->peer; +} + +static unsigned int peer_hash_key(const void *arg) +{ + const struct pkat *pkat = arg; + return (uintptr_t)pkat->peer; +} + +/* Cleanup handler / deinitializer. */ +static void bgp_keepalives_finish(void *arg) +{ + hash_clean_and_free(&peerhash, pkat_del); + + pthread_mutex_unlock(peerhash_mtx); + pthread_mutex_destroy(peerhash_mtx); + pthread_cond_destroy(peerhash_cond); + + XFREE(MTYPE_BGP_MUTEX, peerhash_mtx); + XFREE(MTYPE_BGP_COND, peerhash_cond); +} + +/* + * Entry function for peer keepalive generation pthread. + */ +void *bgp_keepalives_start(void *arg) +{ + struct frr_pthread *fpt = arg; + fpt->master->owner = pthread_self(); + + struct timeval currtime = {0, 0}; + struct timeval aftertime = {0, 0}; + struct timeval next_update = {0, 0}; + struct timespec next_update_ts = {0, 0}; + + /* + * The RCU mechanism for each pthread is initialized in a "locked" + * state. That's ok for pthreads using the frr_pthread, + * event_fetch event loop, because that event loop unlocks regularly. + * For foreign pthreads, the lock needs to be unlocked so that the + * background rcu pthread can run. + */ + rcu_read_unlock(); + + peerhash_mtx = XCALLOC(MTYPE_BGP_MUTEX, sizeof(pthread_mutex_t)); + peerhash_cond = XCALLOC(MTYPE_BGP_COND, sizeof(pthread_cond_t)); + + /* initialize mutex */ + pthread_mutex_init(peerhash_mtx, NULL); + + /* use monotonic clock with condition variable */ + pthread_condattr_t attrs; + pthread_condattr_init(&attrs); + pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC); + pthread_cond_init(peerhash_cond, &attrs); + pthread_condattr_destroy(&attrs); + + /* + * We are not using normal FRR pthread mechanics and are + * not using fpt_run + */ + frr_pthread_set_name(fpt); + + /* initialize peer hashtable */ + peerhash = hash_create_size(2048, peer_hash_key, peer_hash_cmp, NULL); + pthread_mutex_lock(peerhash_mtx); + + /* register cleanup handler */ + pthread_cleanup_push(&bgp_keepalives_finish, NULL); + + /* notify anybody waiting on us that we are done starting up */ + frr_pthread_notify_running(fpt); + + while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) { + if (peerhash->count > 0) + pthread_cond_timedwait(peerhash_cond, peerhash_mtx, + &next_update_ts); + else + while (peerhash->count == 0 + && atomic_load_explicit(&fpt->running, + memory_order_relaxed)) + pthread_cond_wait(peerhash_cond, peerhash_mtx); + + monotime(&currtime); + + next_update.tv_sec = -1; + + hash_iterate(peerhash, peer_process, &next_update); + if (next_update.tv_sec == -1) + memset(&next_update, 0, sizeof(next_update)); + + monotime_since(&currtime, &aftertime); + + timeradd(&currtime, &next_update, &next_update); + TIMEVAL_TO_TIMESPEC(&next_update, &next_update_ts); + } + + /* clean up */ + pthread_cleanup_pop(1); + + return NULL; +} + +/* --- thread external functions ------------------------------------------- */ + +void bgp_keepalives_on(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) + return; + + struct frr_pthread *fpt = bgp_pth_ka; + assert(fpt->running); + + /* placeholder bucket data to use for fast key lookups */ + static struct pkat holder = {0}; + + /* + * We need to ensure that bgp_keepalives_init was called first + */ + assert(peerhash_mtx); + + frr_with_mutex (peerhash_mtx) { + holder.peer = peer; + if (!hash_lookup(peerhash, &holder)) { + struct pkat *pkat = pkat_new(peer); + (void)hash_get(peerhash, pkat, hash_alloc_intern); + peer_lock(peer); + } + SET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); + /* Force the keepalive thread to wake up */ + pthread_cond_signal(peerhash_cond); + } +} + +void bgp_keepalives_off(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if (!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)) + return; + + struct frr_pthread *fpt = bgp_pth_ka; + assert(fpt->running); + + /* placeholder bucket data to use for fast key lookups */ + static struct pkat holder = {0}; + + /* + * We need to ensure that bgp_keepalives_init was called first + */ + assert(peerhash_mtx); + + frr_with_mutex (peerhash_mtx) { + holder.peer = peer; + struct pkat *res = hash_release(peerhash, &holder); + if (res) { + pkat_del(res); + peer_unlock(peer); + } + UNSET_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON); + } +} + +int bgp_keepalives_stop(struct frr_pthread *fpt, void **result) +{ + assert(fpt->running); + + frr_with_mutex (peerhash_mtx) { + atomic_store_explicit(&fpt->running, false, + memory_order_relaxed); + pthread_cond_signal(peerhash_cond); + } + + pthread_join(fpt->thread, result); + return 0; +} diff --git a/bgpd/bgp_keepalives.h b/bgpd/bgp_keepalives.h new file mode 100644 index 0000000..33e574d --- /dev/null +++ b/bgpd/bgp_keepalives.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Keepalives. + * Implements a producer thread to generate BGP keepalives for peers. + * Copyright (C) 2017 Cumulus Networks, Inc. + * Quentin Young + */ + +#ifndef _FRR_BGP_KEEPALIVES_H +#define _FRR_BGP_KEEPALIVES_H + +#include "frr_pthread.h" +#include "bgpd.h" + +/** + * Turns on keepalives for a peer. + * + * This function adds the peer to an internal list of peers to generate + * keepalives for. + * + * At set intervals, a BGP KEEPALIVE packet is generated and placed on + * peer->obuf. This operation is thread-safe with respect to peer->obuf. + * + * peer->v_keepalive determines the interval. Changing this value before + * unregistering this peer with bgp_keepalives_off() results in undefined + * behavior. + * + * If the peer is already registered for keepalives via this function, nothing + * happens. + */ +extern void bgp_keepalives_on(struct peer_connection *connection); + +/** + * Turns off keepalives for a peer. + * + * Removes the peer from the internal list of peers to generate keepalives for. + * + * If the peer is already unregistered for keepalives, nothing happens. + */ +extern void bgp_keepalives_off(struct peer_connection *connection); + +/** + * Pre-run initialization function for keepalives pthread. + * + * Initializes synchronization primitives. This should be called before + * anything else to avoid race conditions. + */ +extern void bgp_keepalives_init(void); + +/** + * Entry function for keepalives pthread. + * + * This function loops over an internal list of peers, generating keepalives at + * regular intervals as determined by each peer's keepalive timer. + * + * See bgp_keepalives_on() for additional details. + * + * @param arg pthread arg, not used + */ +extern void *bgp_keepalives_start(void *arg); + +/** + * Stops the thread and blocks until it terminates. + */ +int bgp_keepalives_stop(struct frr_pthread *fpt, void **result); + +#endif /* _FRR_BGP_KEEPALIVES_H */ diff --git a/bgpd/bgp_label.c b/bgpd/bgp_label.c new file mode 100644 index 0000000..f03b820 --- /dev/null +++ b/bgpd/bgp_label.c @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP carrying label information + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "frrevent.h" +#include "prefix.h" +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "log.h" +#include "memory.h" +#include "nexthop.h" +#include "mpls.h" +#include "jhash.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" + +extern struct zclient *zclient; + + +/* MPLS Labels hash routines. */ +static struct hash *labels_hash; + +static void *bgp_labels_hash_alloc(void *p) +{ + const struct bgp_labels *labels = p; + struct bgp_labels *new; + uint8_t i; + + new = XMALLOC(MTYPE_BGP_LABELS, sizeof(struct bgp_labels)); + + new->num_labels = labels->num_labels; + for (i = 0; i < labels->num_labels; i++) + new->label[i] = labels->label[i]; + + return new; +} + +static uint32_t bgp_labels_hash_key_make(const void *p) +{ + const struct bgp_labels *labels = p; + uint32_t key = 0; + + if (labels->num_labels) + key = jhash(&labels->label, + labels->num_labels * sizeof(mpls_label_t), key); + + return key; +} + +static bool bgp_labels_hash_cmp(const void *p1, const void *p2) +{ + return bgp_labels_cmp(p1, p2); +} + +void bgp_labels_init(void) +{ + labels_hash = hash_create(bgp_labels_hash_key_make, bgp_labels_hash_cmp, + "BGP Labels hash"); +} + +/* + * special for hash_clean below + */ +static void bgp_labels_free(void *labels) +{ + XFREE(MTYPE_BGP_LABELS, labels); +} + +void bgp_labels_finish(void) +{ + hash_clean_and_free(&labels_hash, bgp_labels_free); +} + +struct bgp_labels *bgp_labels_intern(struct bgp_labels *labels) +{ + struct bgp_labels *find; + + if (!labels) + return NULL; + + if (!labels->num_labels) + /* do not intern void labels structure */ + return NULL; + + find = (struct bgp_labels *)hash_get(labels_hash, labels, + bgp_labels_hash_alloc); + find->refcnt++; + + return find; +} + +void bgp_labels_unintern(struct bgp_labels **plabels) +{ + struct bgp_labels *labels = *plabels; + struct bgp_labels *ret; + + if (!*plabels) + return; + + /* Decrement labels reference. */ + labels->refcnt--; + + /* If reference becomes zero then free labels object. */ + if (labels->refcnt == 0) { + ret = hash_release(labels_hash, labels); + assert(ret != NULL); + bgp_labels_free(labels); + *plabels = NULL; + } +} + +bool bgp_labels_cmp(const struct bgp_labels *labels1, + const struct bgp_labels *labels2) +{ + uint8_t i; + + if (!labels1 && !labels2) + return true; + + if (!labels1 && labels2) + return false; + + if (labels1 && !labels2) + return false; + + if (labels1->num_labels != labels2->num_labels) + return false; + + for (i = 0; i < labels1->num_labels; i++) { + if (labels1->label[i] != labels2->label[i]) + return false; + } + + return true; +} + +int bgp_parse_fec_update(void) +{ + struct stream *s; + struct bgp_dest *dest; + struct bgp *bgp; + struct bgp_table *table; + struct prefix p; + uint32_t label; + afi_t afi; + safi_t safi; + + s = zclient->ibuf; + + memset(&p, 0, sizeof(p)); + p.family = stream_getw(s); + p.prefixlen = stream_getc(s); + stream_get(p.u.val, s, PSIZE(p.prefixlen)); + label = stream_getl(s); + + /* hack for the bgp instance & SAFI = have to send/receive it */ + afi = family2afi(p.family); + safi = SAFI_UNICAST; + bgp = bgp_get_default(); + if (!bgp) { + zlog_debug("no default bgp instance"); + return -1; + } + + table = bgp->rib[afi][safi]; + if (!table) { + zlog_debug("no %u unicast table", p.family); + return -1; + } + dest = bgp_node_lookup(table, &p); + if (!dest) { + zlog_debug("no node for the prefix"); + return -1; + } + + /* treat it as implicit withdraw - the label is invalid */ + if (label == MPLS_INVALID_LABEL) + bgp_unset_valid_label(&dest->local_label); + else { + dest->local_label = mpls_lse_encode(label, 0, 0, 1); + bgp_set_valid_label(&dest->local_label); + } + SET_FLAG(dest->flags, BGP_NODE_LABEL_CHANGED); + bgp_process(bgp, dest, NULL, afi, safi); + bgp_dest_unlock_node(dest); + return 1; +} + +mpls_label_t bgp_adv_label(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *to, afi_t afi, safi_t safi) +{ + struct peer *from; + mpls_label_t remote_label; + int reflect; + + if (!dest || !pi || !to) + return MPLS_INVALID_LABEL; + + remote_label = BGP_PATH_INFO_NUM_LABELS(pi) + ? pi->extra->labels->label[0] + : MPLS_INVALID_LABEL; + from = pi->peer; + reflect = + ((from->sort == BGP_PEER_IBGP) && (to->sort == BGP_PEER_IBGP)); + + if (reflect + && !CHECK_FLAG(to->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) + return remote_label; + + if (CHECK_FLAG(to->af_flags[afi][safi], PEER_FLAG_NEXTHOP_UNCHANGED)) + return remote_label; + + return dest->local_label; +} + +static void bgp_send_fec_register_label_msg(struct bgp_dest *dest, bool reg, + uint32_t label_index) +{ + struct stream *s; + int command; + const struct prefix *p; + uint16_t flags = 0; + size_t flags_pos = 0; + mpls_label_t *local_label = &(dest->local_label); + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + bool have_label_to_reg; + + mpls_lse_decode(*local_label, &label, &ttl, &exp, &bos); + + have_label_to_reg = bgp_is_valid_label(local_label) && + label != MPLS_LABEL_IMPLICIT_NULL; + + p = bgp_dest_get_prefix(dest); + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return; + + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug("%s: FEC %sregister %pBD label_index=%u label=%u", + __func__, reg ? "" : "un", dest, label_index, label); + /* If the route node has a local_label assigned or the + * path node has an MPLS SR label index allowing zebra to + * derive the label, proceed with registration. */ + s = zclient->obuf; + stream_reset(s); + command = (reg) ? ZEBRA_FEC_REGISTER : ZEBRA_FEC_UNREGISTER; + zclient_create_header(s, command, VRF_DEFAULT); + flags_pos = stream_get_endp(s); /* save position of 'flags' */ + stream_putw(s, flags); /* initial flags */ + stream_putw(s, PREFIX_FAMILY(p)); + stream_put_prefix(s, p); + if (reg) { + /* label index takes precedence over auto-assigned label. */ + if (label_index != 0) { + flags |= ZEBRA_FEC_REGISTER_LABEL_INDEX; + stream_putl(s, label_index); + } else if (have_label_to_reg) { + flags |= ZEBRA_FEC_REGISTER_LABEL; + stream_putl(s, label); + } + SET_FLAG(dest->flags, BGP_NODE_REGISTERED_FOR_LABEL); + } else + UNSET_FLAG(dest->flags, BGP_NODE_REGISTERED_FOR_LABEL); + + /* Set length and flags */ + stream_putw_at(s, 0, stream_get_endp(s)); + + /* + * We only need to write new flags if this is a register + */ + if (reg) + stream_putw_at(s, flags_pos, flags); + + zclient_send_message(zclient); +} + +/** + * This is passed as the callback function to bgp_labelpool.c:bgp_lp_get() + * by bgp_reg_dereg_for_label() when a label needs to be obtained from + * label pool. + * Note that it will reject the allocated label if a label index is found, + * because the label index supposes predictable labels + */ +int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, + bool allocated) +{ + struct bgp_dest *dest; + + dest = labelid; + + /* + * if the route had been removed or the request has gone then reject + * the allocated label. The requesting code will have done what is + * required to allocate the correct label + */ + if (!CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) { + bgp_dest_unlock_node(dest); + return -1; + } + + dest = bgp_dest_unlock_node(dest); + assert(dest); + + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug("%s: FEC %pBD label=%u, allocated=%d", __func__, + dest, new_label, allocated); + + if (!allocated) { + /* + * previously-allocated label is now invalid, set to implicit + * null until new label arrives + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_REGISTERED_FOR_LABEL)) { + UNSET_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED); + dest->local_label = mpls_lse_encode( + MPLS_LABEL_IMPLICIT_NULL, 0, 0, 1); + bgp_set_valid_label(&dest->local_label); + } + } + + dest->local_label = mpls_lse_encode(new_label, 0, 0, 1); + bgp_set_valid_label(&dest->local_label); + + /* + * Get back to registering the FEC + */ + bgp_send_fec_register_label_msg(dest, true, 0); + + return 0; +} + +void bgp_reg_dereg_for_label(struct bgp_dest *dest, struct bgp_path_info *pi, + bool reg) +{ + bool with_label_index = false; + const struct prefix *p; + bool have_label_to_reg; + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + + mpls_lse_decode(dest->local_label, &label, &ttl, &exp, &bos); + + have_label_to_reg = bgp_is_valid_label(&dest->local_label) && + label != MPLS_LABEL_IMPLICIT_NULL; + + p = bgp_dest_get_prefix(dest); + + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug("%s: %pFX: %s ", __func__, p, + (reg ? "reg" : "dereg")); + + if (reg) { + assert(pi); + /* + * Determine if we will let zebra should derive label from + * label index instead of bgpd requesting from label pool + */ + if (CHECK_FLAG(pi->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) + && pi->attr->label_index != BGP_INVALID_LABEL_INDEX) { + with_label_index = true; + UNSET_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED); + } else { + /* + * If no label has been registered -- assume any label + * from label pool will do. This means that label index + * always takes precedence over auto-assigned labels. + */ + if (!have_label_to_reg) { + SET_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED); + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug( + "%s: Requesting label from LP for %pFX", + __func__, p); + /* bgp_reg_for_label_callback() will deal with + * fec registration when it gets a label from + * the pool. This means we'll never register + * FECs withoutvalid labels. + */ + bgp_lp_get(LP_TYPE_BGP_LU, dest, + bgp_reg_for_label_callback); + return; + } + } + } else { + UNSET_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED); + bgp_lp_release(LP_TYPE_BGP_LU, dest, label); + } + + bgp_send_fec_register_label_msg( + dest, reg, with_label_index ? pi->attr->label_index : 0); +} + +static int bgp_nlri_get_labels(struct peer *peer, uint8_t *pnt, uint8_t plen, + mpls_label_t *label) +{ + uint8_t *data = pnt; + uint8_t *lim = pnt + plen; + uint8_t llen = 0; + uint8_t label_depth = 0; + + if (plen < BGP_LABEL_BYTES) + return 0; + + for (; data < lim; data += BGP_LABEL_BYTES) { + memcpy(label, data, BGP_LABEL_BYTES); + llen += BGP_LABEL_BYTES; + + bgp_set_valid_label(label); + label_depth += 1; + + if (bgp_is_withdraw_label(label) || label_bos(label)) + break; + } + + /* If we RX multiple labels we will end up keeping only the last + * one. We do not yet support a label stack greater than 1. */ + if (label_depth > 1) + zlog_info("%pBP rcvd UPDATE with label stack %d deep", peer, + label_depth); + + if (!(bgp_is_withdraw_label(label) || label_bos(label))) + flog_warn( + EC_BGP_INVALID_LABEL_STACK, + "%pBP rcvd UPDATE with invalid label stack - no bottom of stack", + peer); + + return llen; +} + +int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet) +{ + uint8_t *pnt; + uint8_t *lim; + struct prefix p; + int psize = 0; + int prefixlen; + afi_t afi; + safi_t safi; + bool addpath_capable; + uint32_t addpath_id; + mpls_label_t label = MPLS_INVALID_LABEL; + uint8_t llen; + + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + + for (; pnt < lim; pnt += psize) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + if (addpath_capable) { + + /* When packet overflow occurs return immediately. */ + if (pnt + BGP_ADDPATH_ID_LEN > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + pnt += BGP_ADDPATH_ID_LEN; + + if (pnt >= lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + /* Fetch prefix length. */ + prefixlen = *pnt++; + p.family = afi2family(packet->afi); + psize = PSIZE(prefixlen); + + /* sanity check against packet data */ + if ((pnt + psize) > lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / L-U (prefix length %d exceeds packet size %u)", + peer->host, prefixlen, (uint)(lim - pnt)); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + /* Fill in the labels */ + llen = bgp_nlri_get_labels(peer, pnt, psize, &label); + if (llen == 0) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (wrong label length 0)", + peer->host); + return BGP_NLRI_PARSE_ERROR_LABEL_LENGTH; + } + p.prefixlen = prefixlen - BSIZE(llen); + + /* There needs to be at least one label */ + if (prefixlen < 24) { + flog_err(EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (wrong label length %d)", + peer->host, prefixlen); + return BGP_NLRI_PARSE_ERROR_LABEL_LENGTH; + } + + if ((afi == AFI_IP && p.prefixlen > IPV4_MAX_BITLEN) + || (afi == AFI_IP6 && p.prefixlen > IPV6_MAX_BITLEN)) + return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; + + /* Fetch prefix from NLRI packet */ + memcpy(&p.u.prefix, pnt + llen, psize - llen); + + /* Check address. */ + if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST) { + if (IN_CLASSD(ntohl(p.u.prefix4.s_addr))) { + /* From RFC4271 Section 6.3: + * + * If a prefix in the NLRI field is semantically + * incorrect + * (e.g., an unexpected multicast IP address), + * an error SHOULD + * be logged locally, and the prefix SHOULD be + * ignored. + */ + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv4 labeled-unicast NLRI is multicast address %pI4, ignoring", + peer->host, &p.u.prefix4); + continue; + } + } + + /* Check address. */ + if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST) { + if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 labeled-unicast NLRI is link-local address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + + if (IN6_IS_ADDR_MULTICAST(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 unicast NLRI is multicast address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + } + + if (attr) { + bgp_update(peer, &p, addpath_id, attr, packet->afi, + safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, + NULL, &label, 1, 0, NULL); + } else { + bgp_withdraw(peer, &p, addpath_id, packet->afi, + SAFI_UNICAST, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, NULL, &label, 1, NULL); + } + } + + /* Packet length consistency check. */ + if (pnt != lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / L-U (%td data remaining after parsing)", + peer->host, lim - pnt); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + return BGP_NLRI_PARSE_OK; +} + +bool bgp_labels_same(const mpls_label_t *tbl_a, const uint8_t num_labels_a, + const mpls_label_t *tbl_b, const uint8_t num_labels_b) +{ + uint32_t i; + + if (num_labels_a != num_labels_b) + return false; + if (num_labels_a == 0) + return true; + + for (i = 0; i < num_labels_a; i++) { + if (tbl_a[i] != tbl_b[i]) + return false; + } + return true; +} diff --git a/bgpd/bgp_label.h b/bgpd/bgp_label.h new file mode 100644 index 0000000..2ffd5b6 --- /dev/null +++ b/bgpd/bgp_label.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP carrying Label information + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#ifndef _BGP_LABEL_H +#define _BGP_LABEL_H + +#define BGP_LABEL_BYTES 3 +#define BGP_LABEL_BITS 24 +#define BGP_WITHDRAW_LABEL 0x800000 +#define BGP_PREVENT_VRF_2_VRF_LEAK 0xFFFFFFFE + +struct bgp_dest; +struct bgp_path_info; +struct peer; + +/* Maximum number of labels we can process or send with a prefix. We + * really do only 1 for MPLS (BGP-LU) but we can do 2 for EVPN-VxLAN. + */ +#define BGP_MAX_LABELS 2 + +/* MPLS label(s) - VNI(s) for EVPN-VxLAN */ +struct bgp_labels { + mpls_label_t label[BGP_MAX_LABELS]; + uint8_t num_labels; + + unsigned long refcnt; +}; + +extern void bgp_labels_init(void); +extern void bgp_labels_finish(void); +extern struct bgp_labels *bgp_labels_intern(struct bgp_labels *labels); +extern void bgp_labels_unintern(struct bgp_labels **plabels); +extern bool bgp_labels_cmp(const struct bgp_labels *labels1, + const struct bgp_labels *labels2); + +extern int bgp_reg_for_label_callback(mpls_label_t new_label, void *labelid, + bool allocated); +extern void bgp_reg_dereg_for_label(struct bgp_dest *dest, + struct bgp_path_info *pi, bool reg); +extern int bgp_parse_fec_update(void); +extern mpls_label_t bgp_adv_label(struct bgp_dest *dest, + struct bgp_path_info *pi, struct peer *to, + afi_t afi, safi_t safi); + +extern int bgp_nlri_parse_label(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet); +extern bool bgp_labels_same(const mpls_label_t *tbl_a, + const uint8_t num_labels_a, + const mpls_label_t *tbl_b, + const uint8_t num_labels_b); + +static inline int bgp_labeled_safi(safi_t safi) +{ + /* NOTE: This API really says a label (tag) MAY be present. Not all EVPN + * routes will have a label. + */ + if ((safi == SAFI_LABELED_UNICAST) || (safi == SAFI_MPLS_VPN) + || (safi == SAFI_EVPN)) + return 1; + return 0; +} + +static inline int bgp_is_withdraw_label(mpls_label_t *label) +{ + uint8_t *pkt = (uint8_t *)label; + + /* The check on pkt[2] for 0x00 or 0x02 is in case bgp_set_valid_label() + * was called on the withdraw label */ + if (((pkt[0] == 0x80) || (pkt[0] == 0x00)) && (pkt[1] == 0x00) + && ((pkt[2] == 0x00) || (pkt[2] == 0x02))) + return 1; + return 0; +} + +static inline int bgp_is_valid_label(const mpls_label_t *label) +{ + uint8_t *t = (uint8_t *)label; + if (!t) + return 0; + return (t[2] & 0x02); +} + +static inline void bgp_set_valid_label(mpls_label_t *label) +{ + uint8_t *t = (uint8_t *)label; + if (t) + t[2] |= 0x02; +} + +static inline void bgp_unset_valid_label(mpls_label_t *label) +{ + uint8_t *t = (uint8_t *)label; + if (t) + t[2] &= ~0x02; +} + +static inline void bgp_register_for_label(struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + bgp_reg_dereg_for_label(dest, pi, true); +} + +static inline void bgp_unregister_for_label(struct bgp_dest *dest) +{ + bgp_reg_dereg_for_label(dest, NULL, false); +} + +/* Return BOS value of label stream */ +static inline uint8_t label_bos(mpls_label_t *label) +{ + uint8_t *t = (uint8_t *)label; + return (t[2] & 0x01); +}; + +#endif /* _BGP_LABEL_H */ diff --git a/bgpd/bgp_labelpool.c b/bgpd/bgp_labelpool.c new file mode 100644 index 0000000..23e0c19 --- /dev/null +++ b/bgpd/bgp_labelpool.c @@ -0,0 +1,1750 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Label Pool - Manage label chunk allocations from zebra asynchronously + * + * Copyright (C) 2018 LabN Consulting, L.L.C. + */ + +#include + +#include "log.h" +#include "memory.h" +#include "stream.h" +#include "mpls.h" +#include "vty.h" +#include "linklist.h" +#include "skiplist.h" +#include "workqueue.h" +#include "mpls.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_labelpool.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_rd.h" + +#define BGP_LABELPOOL_ENABLE_TESTS 0 + +#include "bgpd/bgp_labelpool_clippy.c" + + +#if BGP_LABELPOOL_ENABLE_TESTS +static void lptest_init(void); +static void lptest_finish(void); +#endif + +static void bgp_sync_label_manager(struct event *e); + +/* + * Remember where pool data are kept + */ +static struct labelpool *lp; + +/* + * Number of labels requested at a time from the zebra label manager. + * We start small but double the request size each time up to a + * maximum size. + * + * The label space is 20 bits which is shared with other FRR processes + * on this host, so to avoid greedily requesting a mostly wasted chunk, + * we limit the chunk size to 1/16 of the label space (that's the -4 bits + * in the definition below). This limit slightly increases our cost of + * finding free labels in our allocated chunks. + */ +#define LP_CHUNK_SIZE_MIN 128 +#define LP_CHUNK_SIZE_MAX (1 << (20 - 4)) + +DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CHUNK, "BGP Label Chunk"); +DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_FIFO, "BGP Label FIFO item"); +DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CB, "BGP Dynamic Label Assignment"); +DEFINE_MTYPE_STATIC(BGPD, BGP_LABEL_CBQ, "BGP Dynamic Label Callback"); + +struct lp_chunk { + uint32_t first; + uint32_t last; + uint32_t nfree; /* un-allocated count */ + uint32_t idx_last_allocated; /* start looking here */ + bitfield_t allocated_map; +}; + +/* + * label control block + */ +struct lp_lcb { + mpls_label_t label; /* MPLS_LABEL_NONE = not allocated */ + int type; + void *labelid; /* unique ID */ + /* + * callback for label allocation and loss + * + * allocated: false = lost + */ + int (*cbfunc)(mpls_label_t label, void *lblid, bool alloc); +}; + +struct lp_fifo { + struct lp_fifo_item fifo; + struct lp_lcb lcb; +}; + +DECLARE_LIST(lp_fifo, struct lp_fifo, fifo); + +struct lp_cbq_item { + int (*cbfunc)(mpls_label_t label, void *lblid, bool alloc); + int type; + mpls_label_t label; + void *labelid; + bool allocated; /* false = lost */ +}; + +static wq_item_status lp_cbq_docallback(struct work_queue *wq, void *data) +{ + struct lp_cbq_item *lcbq = data; + int rc; + int debug = BGP_DEBUG(labelpool, LABELPOOL); + + if (debug) + zlog_debug("%s: calling callback with labelid=%p label=%u allocated=%d", + __func__, lcbq->labelid, lcbq->label, lcbq->allocated); + + if (lcbq->label == MPLS_LABEL_NONE) { + /* shouldn't happen */ + flog_err(EC_BGP_LABEL, "%s: error: label==MPLS_LABEL_NONE", + __func__); + return WQ_SUCCESS; + } + + rc = (*(lcbq->cbfunc))(lcbq->label, lcbq->labelid, lcbq->allocated); + + if (lcbq->allocated && rc) { + /* + * Callback rejected allocation. This situation could arise + * if there was a label request followed by the requestor + * deciding it didn't need the assignment (e.g., config + * change) while the reply to the original request (with + * label) was in the work queue. + */ + if (debug) + zlog_debug("%s: callback rejected allocation, releasing labelid=%p label=%u", + __func__, lcbq->labelid, lcbq->label); + + uintptr_t lbl = lcbq->label; + void *labelid; + struct lp_lcb *lcb; + + /* + * If the rejected label was marked inuse by this labelid, + * release the label back to the pool. + * + * Further, if the rejected label was still assigned to + * this labelid in the LCB, delete the LCB. + */ + if (!skiplist_search(lp->inuse, (void *)lbl, &labelid)) { + if (labelid == lcbq->labelid) { + if (!skiplist_search(lp->ledger, labelid, + (void **)&lcb)) { + if (lcbq->label == lcb->label) + skiplist_delete(lp->ledger, + labelid, NULL); + } + skiplist_delete(lp->inuse, (void *)lbl, NULL); + } + } + } + + return WQ_SUCCESS; +} + +static void lp_cbq_item_free(struct work_queue *wq, void *data) +{ + XFREE(MTYPE_BGP_LABEL_CBQ, data); +} + +static void lp_lcb_free(void *goner) +{ + XFREE(MTYPE_BGP_LABEL_CB, goner); +} + +static void lp_chunk_free(void *goner) +{ + struct lp_chunk *chunk = (struct lp_chunk *)goner; + + bf_free(chunk->allocated_map); + XFREE(MTYPE_BGP_LABEL_CHUNK, goner); +} + +void bgp_lp_init(struct event_loop *master, struct labelpool *pool) +{ + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug("%s: entry", __func__); + + lp = pool; /* Set module pointer to pool data */ + + lp->ledger = skiplist_new(0, NULL, lp_lcb_free); + lp->inuse = skiplist_new(0, NULL, NULL); + lp->chunks = list_new(); + lp->chunks->del = lp_chunk_free; + lp_fifo_init(&lp->requests); + lp->callback_q = work_queue_new(master, "label callbacks"); + + lp->callback_q->spec.workfunc = lp_cbq_docallback; + lp->callback_q->spec.del_item_data = lp_cbq_item_free; + lp->callback_q->spec.max_retries = 0; + + lp->next_chunksize = LP_CHUNK_SIZE_MIN; + +#if BGP_LABELPOOL_ENABLE_TESTS + lptest_init(); +#endif +} + +/* check if a label callback was for a BGP LU node, and if so, unlock it */ +static void check_bgp_lu_cb_unlock(struct lp_lcb *lcb) +{ + if (lcb->type == LP_TYPE_BGP_LU) + bgp_dest_unlock_node(lcb->labelid); +} + +/* check if a label callback was for a BGP LU node, and if so, lock it */ +static void check_bgp_lu_cb_lock(struct lp_lcb *lcb) +{ + if (lcb->type == LP_TYPE_BGP_LU) + bgp_dest_lock_node(lcb->labelid); +} + +void bgp_lp_finish(void) +{ + struct lp_fifo *lf; + struct work_queue_item *item, *titem; + struct listnode *node; + struct lp_chunk *chunk; + +#if BGP_LABELPOOL_ENABLE_TESTS + lptest_finish(); +#endif + if (!lp) + return; + + skiplist_free(lp->ledger); + lp->ledger = NULL; + + skiplist_free(lp->inuse); + lp->inuse = NULL; + + for (ALL_LIST_ELEMENTS_RO(lp->chunks, node, chunk)) + bgp_zebra_release_label_range(chunk->first, chunk->last); + + list_delete(&lp->chunks); + + while ((lf = lp_fifo_pop(&lp->requests))) { + check_bgp_lu_cb_unlock(&lf->lcb); + XFREE(MTYPE_BGP_LABEL_FIFO, lf); + } + lp_fifo_fini(&lp->requests); + + /* we must unlock path infos for LU callbacks; but we cannot do that + * in the deletion callback of the workqueue, as that is also called + * to remove an element from the queue after it has been run, resulting + * in a double unlock. Hence we need to iterate over our queues and + * lists and manually perform the unlocking (ugh) + */ + STAILQ_FOREACH_SAFE (item, &lp->callback_q->items, wq, titem) + check_bgp_lu_cb_unlock(item->data); + + work_queue_free_and_null(&lp->callback_q); + + lp = NULL; +} + +static mpls_label_t get_label_from_pool(void *labelid) +{ + struct listnode *node; + struct lp_chunk *chunk; + int debug = BGP_DEBUG(labelpool, LABELPOOL); + + /* + * Find a free label + */ + for (ALL_LIST_ELEMENTS_RO(lp->chunks, node, chunk)) { + uintptr_t lbl; + unsigned int index; + + if (debug) + zlog_debug("%s: chunk first=%u last=%u", + __func__, chunk->first, chunk->last); + + /* + * don't look in chunks with no available labels + */ + if (!chunk->nfree) + continue; + + /* + * roll through bitfield starting where we stopped + * last time + */ + index = bf_find_next_clear_bit_wrap( + &chunk->allocated_map, chunk->idx_last_allocated + 1, + 0); + + /* + * since chunk->nfree is non-zero, we should always get + * a valid index + */ + assert(index != WORD_MAX); + + lbl = chunk->first + index; + if (skiplist_insert(lp->inuse, (void *)lbl, labelid)) { + /* something is very wrong */ + zlog_err("%s: unable to insert inuse label %u (id %p)", + __func__, (uint32_t)lbl, labelid); + return MPLS_LABEL_NONE; + } + + /* + * Success + */ + bf_set_bit(chunk->allocated_map, index); + chunk->idx_last_allocated = index; + chunk->nfree -= 1; + + return lbl; + } + + return MPLS_LABEL_NONE; +} + +/* + * Success indicated by value of "label" field in returned LCB + */ +static struct lp_lcb *lcb_alloc( + int type, + void *labelid, + int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)) +{ + /* + * Set up label control block + */ + struct lp_lcb *new = XCALLOC(MTYPE_BGP_LABEL_CB, + sizeof(struct lp_lcb)); + + new->label = get_label_from_pool(labelid); + new->type = type; + new->labelid = labelid; + new->cbfunc = cbfunc; + + return new; +} + +/* + * Callers who need labels must supply a type, labelid, and callback. + * The type is a value defined in bgp_labelpool.h (add types as needed). + * The callback is for asynchronous notification of label allocation. + * The labelid is passed as an argument to the callback. It should be unique + * to the requested label instance. + * + * If zebra is not connected, callbacks with labels will be delayed + * until connection is established. If zebra connection is lost after + * labels have been assigned, existing assignments via this labelpool + * module will continue until reconnection. + * + * When connection to zebra is reestablished, previous label assignments + * will be invalidated (via callbacks having the "allocated" parameter unset) + * and new labels will be automatically reassigned by this labelpool module + * (that is, a requestor does not need to call bgp_lp_get() again if it is + * notified via callback that its label has been lost: it will eventually + * get another callback with a new label assignment). + * + * The callback function should return 0 to accept the allocation + * and non-zero to refuse it. The callback function return value is + * ignored for invalidations (i.e., when the "allocated" parameter is false) + * + * Prior requests for a given labelid are detected so that requests and + * assignments are not duplicated. + */ +void bgp_lp_get( + int type, + void *labelid, + int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)) +{ + struct lp_lcb *lcb; + int requested = 0; + int debug = BGP_DEBUG(labelpool, LABELPOOL); + + if (debug) + zlog_debug("%s: labelid=%p", __func__, labelid); + + /* + * Have we seen this request before? + */ + if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { + requested = 1; + } else { + lcb = lcb_alloc(type, labelid, cbfunc); + if (debug) + zlog_debug("%s: inserting lcb=%p label=%u", + __func__, lcb, lcb->label); + int rc = skiplist_insert(lp->ledger, labelid, lcb); + + if (rc) { + /* shouldn't happen */ + flog_err(EC_BGP_LABEL, + "%s: can't insert new LCB into ledger list", + __func__); + XFREE(MTYPE_BGP_LABEL_CB, lcb); + return; + } + } + + if (lcb->label != MPLS_LABEL_NONE) { + /* + * Fast path: we filled the request from local pool (or + * this is a duplicate request that we filled already). + * Enqueue response work item with new label. + */ + struct lp_cbq_item *q; + + q = XCALLOC(MTYPE_BGP_LABEL_CBQ, sizeof(struct lp_cbq_item)); + + q->cbfunc = lcb->cbfunc; + q->type = lcb->type; + q->label = lcb->label; + q->labelid = lcb->labelid; + q->allocated = true; + + /* if this is a LU request, lock node before queueing */ + check_bgp_lu_cb_lock(lcb); + + work_queue_add(lp->callback_q, q); + + return; + } + + if (requested) + return; + + if (debug) + zlog_debug("%s: slow path. lcb=%p label=%u", + __func__, lcb, lcb->label); + + /* + * Slow path: we are out of labels in the local pool, + * so remember the request and also get another chunk from + * the label manager. + * + * We track number of outstanding label requests: don't + * need to get a chunk for each one. + */ + + struct lp_fifo *lf = XCALLOC(MTYPE_BGP_LABEL_FIFO, + sizeof(struct lp_fifo)); + + lf->lcb = *lcb; + /* if this is a LU request, lock node before queueing */ + check_bgp_lu_cb_lock(lcb); + + lp_fifo_add_tail(&lp->requests, lf); + + if (lp_fifo_count(&lp->requests) > lp->pending_count) { + if (!bgp_zebra_request_label_range(MPLS_LABEL_BASE_ANY, + lp->next_chunksize, true)) + return; + + lp->pending_count += lp->next_chunksize; + if ((lp->next_chunksize << 1) <= LP_CHUNK_SIZE_MAX) + lp->next_chunksize <<= 1; + } + + event_add_timer(bm->master, bgp_sync_label_manager, NULL, 1, + &bm->t_bgp_sync_label_manager); +} + +void bgp_lp_release( + int type, + void *labelid, + mpls_label_t label) +{ + struct lp_lcb *lcb; + + if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { + if (label == lcb->label && type == lcb->type) { + struct listnode *node; + struct lp_chunk *chunk; + uintptr_t lbl = label; + bool deallocated = false; + + /* no longer in use */ + skiplist_delete(lp->inuse, (void *)lbl, NULL); + + /* no longer requested */ + skiplist_delete(lp->ledger, labelid, NULL); + + /* + * Find the chunk this label belongs to and + * deallocate the label + */ + for (ALL_LIST_ELEMENTS_RO(lp->chunks, node, chunk)) { + uint32_t index; + + if ((label < chunk->first) || + (label > chunk->last)) + continue; + + index = label - chunk->first; + assert(bf_test_index(chunk->allocated_map, + index)); + bf_release_index(chunk->allocated_map, index); + chunk->nfree += 1; + deallocated = true; + break; + } + assert(deallocated); + if (deallocated && + chunk->nfree == chunk->last - chunk->first + 1 && + lp_fifo_count(&lp->requests) == 0) { + bgp_zebra_release_label_range(chunk->first, + chunk->last); + list_delete_node(lp->chunks, node); + lp_chunk_free(chunk); + lp->next_chunksize = LP_CHUNK_SIZE_MIN; + } + } + } +} + +static void bgp_sync_label_manager(struct event *e) +{ + int debug = BGP_DEBUG(labelpool, LABELPOOL); + struct lp_fifo *lf; + + while ((lf = lp_fifo_pop(&lp->requests))) { + struct lp_lcb *lcb; + void *labelid = lf->lcb.labelid; + + if (skiplist_search(lp->ledger, labelid, (void **)&lcb)) { + /* request no longer in effect */ + + if (debug) { + zlog_debug("%s: labelid %p: request no longer in effect", + __func__, labelid); + } + /* if this was a BGP_LU request, unlock node + */ + check_bgp_lu_cb_unlock(lcb); + goto finishedrequest; + } + + /* have LCB */ + if (lcb->label != MPLS_LABEL_NONE) { + /* request already has a label */ + if (debug) { + zlog_debug("%s: labelid %p: request already has a label: %u=0x%x, lcb=%p", + __func__, labelid, lcb->label, + lcb->label, lcb); + } + /* if this was a BGP_LU request, unlock node + */ + check_bgp_lu_cb_unlock(lcb); + + goto finishedrequest; + } + + lcb->label = get_label_from_pool(lcb->labelid); + + if (lcb->label == MPLS_LABEL_NONE) { + /* + * Out of labels in local pool, await next chunk + */ + if (debug) { + zlog_debug("%s: out of labels, await more", + __func__); + } + + lp_fifo_add_tail(&lp->requests, lf); + event_add_timer(bm->master, bgp_sync_label_manager, + NULL, 1, &bm->t_bgp_sync_label_manager); + break; + } + + /* + * we filled the request from local pool. + * Enqueue response work item with new label. + */ + struct lp_cbq_item *q = XCALLOC(MTYPE_BGP_LABEL_CBQ, + sizeof(struct lp_cbq_item)); + + q->cbfunc = lcb->cbfunc; + q->type = lcb->type; + q->label = lcb->label; + q->labelid = lcb->labelid; + q->allocated = true; + + if (debug) + zlog_debug("%s: assigning label %u to labelid %p", + __func__, q->label, q->labelid); + + work_queue_add(lp->callback_q, q); + +finishedrequest: + XFREE(MTYPE_BGP_LABEL_FIFO, lf); + } +} + +void bgp_lp_event_chunk(uint32_t first, uint32_t last) +{ + struct lp_chunk *chunk; + uint32_t labelcount; + + if (last < first) { + flog_err(EC_BGP_LABEL, + "%s: zebra label chunk invalid: first=%u, last=%u", + __func__, first, last); + return; + } + + chunk = XCALLOC(MTYPE_BGP_LABEL_CHUNK, sizeof(struct lp_chunk)); + + labelcount = last - first + 1; + + chunk->first = first; + chunk->last = last; + chunk->nfree = labelcount; + bf_init(chunk->allocated_map, labelcount); + + /* + * Optimize for allocation by adding the new (presumably larger) + * chunk at the head of the list so it is examined first. + */ + listnode_add_head(lp->chunks, chunk); + + lp->pending_count -= labelcount; +} + +/* + * continue using allocated labels until zebra returns + */ +void bgp_lp_event_zebra_down(void) +{ + /* rats. */ +} + +/* + * Inform owners of previously-allocated labels that their labels + * are not valid. Request chunk from zebra large enough to satisfy + * previously-allocated labels plus any outstanding requests. + */ +void bgp_lp_event_zebra_up(void) +{ + unsigned int labels_needed; + unsigned int chunks_needed; + void *labelid; + struct lp_lcb *lcb; + + lp->reconnect_count++; + /* + * Get label chunk allocation request dispatched to zebra + */ + labels_needed = lp_fifo_count(&lp->requests) + + skiplist_count(lp->inuse); + + if (labels_needed > lp->next_chunksize) { + while ((lp->next_chunksize < labels_needed) && + (lp->next_chunksize << 1 <= LP_CHUNK_SIZE_MAX)) + + lp->next_chunksize <<= 1; + } + + /* round up */ + chunks_needed = (labels_needed + lp->next_chunksize - 1) / lp->next_chunksize; + labels_needed = chunks_needed * lp->next_chunksize; + + /* + * Invalidate current list of chunks + */ + list_delete_all_node(lp->chunks); + + if (labels_needed && !bgp_zebra_request_label_range(MPLS_LABEL_BASE_ANY, + labels_needed, true)) + return; + lp->pending_count += labels_needed; + + /* + * Invalidate any existing labels and requeue them as requests + */ + while (!skiplist_first(lp->inuse, NULL, &labelid)) { + + /* + * Get LCB + */ + if (!skiplist_search(lp->ledger, labelid, (void **)&lcb)) { + + if (lcb->label != MPLS_LABEL_NONE) { + /* + * invalidate + */ + struct lp_cbq_item *q; + + q = XCALLOC(MTYPE_BGP_LABEL_CBQ, + sizeof(struct lp_cbq_item)); + q->cbfunc = lcb->cbfunc; + q->type = lcb->type; + q->label = lcb->label; + q->labelid = lcb->labelid; + q->allocated = false; + check_bgp_lu_cb_lock(lcb); + work_queue_add(lp->callback_q, q); + + lcb->label = MPLS_LABEL_NONE; + } + + /* + * request queue + */ + struct lp_fifo *lf = XCALLOC(MTYPE_BGP_LABEL_FIFO, + sizeof(struct lp_fifo)); + + lf->lcb = *lcb; + check_bgp_lu_cb_lock(lcb); + lp_fifo_add_tail(&lp->requests, lf); + } + + skiplist_delete_first(lp->inuse); + } + + event_add_timer(bm->master, bgp_sync_label_manager, NULL, 1, + &bm->t_bgp_sync_label_manager); +} + +DEFUN(show_bgp_labelpool_summary, show_bgp_labelpool_summary_cmd, + "show bgp labelpool summary [json]", + SHOW_STR BGP_STR + "BGP Labelpool information\n" + "BGP Labelpool summary\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (!lp) { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + + if (uj) { + json = json_object_new_object(); + json_object_int_add(json, "ledger", skiplist_count(lp->ledger)); + json_object_int_add(json, "inUse", skiplist_count(lp->inuse)); + json_object_int_add(json, "requests", + lp_fifo_count(&lp->requests)); + json_object_int_add(json, "labelChunks", listcount(lp->chunks)); + json_object_int_add(json, "pending", lp->pending_count); + json_object_int_add(json, "reconnects", lp->reconnect_count); + vty_json(vty, json); + } else { + vty_out(vty, "Labelpool Summary\n"); + vty_out(vty, "-----------------\n"); + vty_out(vty, "%-13s %d\n", + "Ledger:", skiplist_count(lp->ledger)); + vty_out(vty, "%-13s %d\n", "InUse:", skiplist_count(lp->inuse)); + vty_out(vty, "%-13s %zu\n", + "Requests:", lp_fifo_count(&lp->requests)); + vty_out(vty, "%-13s %d\n", + "LabelChunks:", listcount(lp->chunks)); + vty_out(vty, "%-13s %d\n", "Pending:", lp->pending_count); + vty_out(vty, "%-13s %d\n", "Reconnects:", lp->reconnect_count); + } + return CMD_SUCCESS; +} + +DEFUN(show_bgp_labelpool_ledger, show_bgp_labelpool_ledger_cmd, + "show bgp labelpool ledger [json]", + SHOW_STR BGP_STR + "BGP Labelpool information\n" + "BGP Labelpool ledger\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL, *json_elem = NULL; + struct lp_lcb *lcb = NULL; + struct bgp_dest *dest; + void *cursor = NULL; + const struct prefix *p; + int rc, count; + + if (!lp) { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + + if (uj) { + count = skiplist_count(lp->ledger); + if (!count) { + vty_out(vty, "{}\n"); + return CMD_SUCCESS; + } + json = json_object_new_array(); + } else { + vty_out(vty, "Prefix Label\n"); + vty_out(vty, "---------------------------\n"); + } + + for (rc = skiplist_next(lp->ledger, (void **)&dest, (void **)&lcb, + &cursor); + !rc; rc = skiplist_next(lp->ledger, (void **)&dest, (void **)&lcb, + &cursor)) { + if (uj) { + json_elem = json_object_new_object(); + json_object_array_add(json, json_elem); + } + switch (lcb->type) { + case LP_TYPE_BGP_LU: + if (!CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) + if (uj) { + json_object_string_add( + json_elem, "prefix", "INVALID"); + json_object_int_add(json_elem, "label", + lcb->label); + } else + vty_out(vty, "%-18s %u\n", + "INVALID", lcb->label); + else { + p = bgp_dest_get_prefix(dest); + if (uj) { + json_object_string_addf( + json_elem, "prefix", "%pFX", p); + json_object_int_add(json_elem, "label", + lcb->label); + } else + vty_out(vty, "%-18pFX %u\n", p, + lcb->label); + } + break; + case LP_TYPE_VRF: + if (uj) { + json_object_string_add(json_elem, "prefix", + "VRF"); + json_object_int_add(json_elem, "label", + lcb->label); + } else + vty_out(vty, "%-18s %u\n", "VRF", + lcb->label); + + break; + case LP_TYPE_NEXTHOP: + if (uj) { + json_object_string_add(json_elem, "prefix", + "nexthop"); + json_object_int_add(json_elem, "label", + lcb->label); + } else + vty_out(vty, "%-18s %u\n", "nexthop", + lcb->label); + break; + case LP_TYPE_BGP_L3VPN_BIND: + if (uj) { + json_object_string_add(json_elem, "prefix", + "l3vpn-bind"); + json_object_int_add(json_elem, "label", + lcb->label); + } else + vty_out(vty, "%-18s %u\n", "l3vpn-bind", + lcb->label); + break; + } + } + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +DEFUN(show_bgp_labelpool_inuse, show_bgp_labelpool_inuse_cmd, + "show bgp labelpool inuse [json]", + SHOW_STR BGP_STR + "BGP Labelpool information\n" + "BGP Labelpool inuse\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL, *json_elem = NULL; + struct bgp_dest *dest; + mpls_label_t label; + struct lp_lcb *lcb; + void *cursor = NULL; + const struct prefix *p; + int rc, count; + + if (!lp) { + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + if (!lp) { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + + if (uj) { + count = skiplist_count(lp->inuse); + if (!count) { + vty_out(vty, "{}\n"); + return CMD_SUCCESS; + } + json = json_object_new_array(); + } else { + vty_out(vty, "Prefix Label\n"); + vty_out(vty, "---------------------------\n"); + } + for (rc = skiplist_next(lp->inuse, (void **)&label, (void **)&dest, + &cursor); + !rc; rc = skiplist_next(lp->ledger, (void **)&label, + (void **)&dest, &cursor)) { + if (skiplist_search(lp->ledger, dest, (void **)&lcb)) + continue; + + if (uj) { + json_elem = json_object_new_object(); + json_object_array_add(json, json_elem); + } + + switch (lcb->type) { + case LP_TYPE_BGP_LU: + if (!CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) + if (uj) { + json_object_string_add( + json_elem, "prefix", "INVALID"); + json_object_int_add(json_elem, "label", + label); + } else + vty_out(vty, "INVALID %u\n", + label); + else { + p = bgp_dest_get_prefix(dest); + if (uj) { + json_object_string_addf( + json_elem, "prefix", "%pFX", p); + json_object_int_add(json_elem, "label", + label); + } else + vty_out(vty, "%-18pFX %u\n", p, + label); + } + break; + case LP_TYPE_VRF: + if (uj) { + json_object_string_add(json_elem, "prefix", + "VRF"); + json_object_int_add(json_elem, "label", label); + } else + vty_out(vty, "%-18s %u\n", "VRF", + label); + break; + case LP_TYPE_NEXTHOP: + if (uj) { + json_object_string_add(json_elem, "prefix", + "nexthop"); + json_object_int_add(json_elem, "label", label); + } else + vty_out(vty, "%-18s %u\n", "nexthop", + label); + break; + case LP_TYPE_BGP_L3VPN_BIND: + if (uj) { + json_object_string_add(json_elem, "prefix", + "l3vpn-bind"); + json_object_int_add(json_elem, "label", label); + } else + vty_out(vty, "%-18s %u\n", "l3vpn-bind", + label); + break; + } + } + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +DEFUN(show_bgp_labelpool_requests, show_bgp_labelpool_requests_cmd, + "show bgp labelpool requests [json]", + SHOW_STR BGP_STR + "BGP Labelpool information\n" + "BGP Labelpool requests\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL, *json_elem = NULL; + struct bgp_dest *dest; + const struct prefix *p; + struct lp_fifo *item, *next; + int count; + + if (!lp) { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + + if (uj) { + count = lp_fifo_count(&lp->requests); + if (!count) { + vty_out(vty, "{}\n"); + return CMD_SUCCESS; + } + json = json_object_new_array(); + } else { + vty_out(vty, "Prefix \n"); + vty_out(vty, "----------------\n"); + } + + for (item = lp_fifo_first(&lp->requests); item; item = next) { + next = lp_fifo_next_safe(&lp->requests, item); + dest = item->lcb.labelid; + if (uj) { + json_elem = json_object_new_object(); + json_object_array_add(json, json_elem); + } + switch (item->lcb.type) { + case LP_TYPE_BGP_LU: + if (!CHECK_FLAG(dest->flags, + BGP_NODE_LABEL_REQUESTED)) { + if (uj) + json_object_string_add( + json_elem, "prefix", "INVALID"); + else + vty_out(vty, "INVALID\n"); + } else { + p = bgp_dest_get_prefix(dest); + if (uj) + json_object_string_addf( + json_elem, "prefix", "%pFX", p); + else + vty_out(vty, "%-18pFX\n", p); + } + break; + case LP_TYPE_VRF: + if (uj) + json_object_string_add(json_elem, "prefix", + "VRF"); + else + vty_out(vty, "VRF\n"); + break; + case LP_TYPE_NEXTHOP: + if (uj) + json_object_string_add(json_elem, "prefix", + "nexthop"); + else + vty_out(vty, "Nexthop\n"); + break; + case LP_TYPE_BGP_L3VPN_BIND: + if (uj) + json_object_string_add(json_elem, "prefix", + "l3vpn-bind"); + else + vty_out(vty, "L3VPN-BIND\n"); + break; + } + } + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +DEFUN(show_bgp_labelpool_chunks, show_bgp_labelpool_chunks_cmd, + "show bgp labelpool chunks [json]", + SHOW_STR BGP_STR + "BGP Labelpool information\n" + "BGP Labelpool chunks\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL, *json_elem; + struct listnode *node; + struct lp_chunk *chunk; + int count; + + if (!lp) { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No existing BGP labelpool\n"); + return (CMD_WARNING); + } + + if (uj) { + count = listcount(lp->chunks); + if (!count) { + vty_out(vty, "{}\n"); + return CMD_SUCCESS; + } + json = json_object_new_array(); + } else { + vty_out(vty, "%10s %10s %10s %10s\n", "First", "Last", "Size", + "nfree"); + vty_out(vty, "-------------------------------------------\n"); + } + + for (ALL_LIST_ELEMENTS_RO(lp->chunks, node, chunk)) { + uint32_t size; + + size = chunk->last - chunk->first + 1; + + if (uj) { + json_elem = json_object_new_object(); + json_object_array_add(json, json_elem); + json_object_int_add(json_elem, "first", chunk->first); + json_object_int_add(json_elem, "last", chunk->last); + json_object_int_add(json_elem, "size", size); + json_object_int_add(json_elem, "numberFree", + chunk->nfree); + } else + vty_out(vty, "%10u %10u %10u %10u\n", chunk->first, + chunk->last, size, chunk->nfree); + } + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +static void show_bgp_nexthop_label_afi(struct vty *vty, afi_t afi, + struct bgp *bgp, bool detail) +{ + struct bgp_label_per_nexthop_cache_head *tree; + struct bgp_label_per_nexthop_cache *iter; + safi_t safi; + void *src; + char buf[PREFIX2STR_BUFFER]; + char labelstr[MPLS_LABEL_STRLEN]; + struct bgp_dest *dest; + struct bgp_path_info *path; + struct bgp *bgp_path; + struct bgp_table *table; + time_t tbuf; + + vty_out(vty, "Current BGP label nexthop cache for %s, VRF %s\n", + afi2str(afi), bgp->name_pretty); + + tree = &bgp->mpls_labels_per_nexthop[afi]; + frr_each (bgp_label_per_nexthop_cache, tree, iter) { + if (afi2family(afi) == AF_INET) + src = (void *)&iter->nexthop.u.prefix4; + else + src = (void *)&iter->nexthop.u.prefix6; + + vty_out(vty, " %s, label %s #paths %u\n", + inet_ntop(afi2family(afi), src, buf, sizeof(buf)), + mpls_label2str(1, &iter->label, labelstr, + sizeof(labelstr), 0, true), + iter->path_count); + if (iter->nh) + vty_out(vty, " if %s\n", + ifindex2ifname(iter->nh->ifindex, + iter->nh->vrf_id)); + tbuf = time(NULL) - (monotime(NULL) - iter->last_update); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, buf)); + if (!detail) + continue; + vty_out(vty, " Paths:\n"); + LIST_FOREACH (path, &(iter->paths), + mplsvpn.blnc.label_nh_thread) { + dest = path->net; + table = bgp_dest_table(dest); + assert(dest && table); + afi = family2afi(bgp_dest_get_prefix(dest)->family); + safi = table->safi; + bgp_path = table->bgp; + + if (dest->pdest) { + vty_out(vty, " %d/%d %pBD RD ", afi, safi, + dest); + + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest)); + vty_out(vty, " %s flags 0x%x\n", + bgp_path->name_pretty, path->flags); + } else + vty_out(vty, " %d/%d %pBD %s flags 0x%x\n", + afi, safi, dest, bgp_path->name_pretty, + path->flags); + } + } +} + +DEFPY(show_bgp_nexthop_label, show_bgp_nexthop_label_cmd, + "show bgp [ VIEWVRFNAME] label-nexthop [detail]", + SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR + "BGP label per-nexthop table\n" + "Show detailed information\n") +{ + int idx = 0; + char *vrf = NULL; + struct bgp *bgp; + bool detail = false; + int afi; + + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[++idx]->arg; + bgp = bgp_lookup_by_name(vrf); + } else + bgp = bgp_get_default(); + + if (!bgp) + return CMD_SUCCESS; + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + for (afi = AFI_IP; afi <= AFI_IP6; afi++) + show_bgp_nexthop_label_afi(vty, afi, bgp, detail); + return CMD_SUCCESS; +} + +#if BGP_LABELPOOL_ENABLE_TESTS +/*------------------------------------------------------------------------ + * Testing code start + *------------------------------------------------------------------------*/ + +DEFINE_MTYPE_STATIC(BGPD, LABELPOOL_TEST, "Label pool test"); + +#define LPT_STAT_INSERT_FAIL 0 +#define LPT_STAT_DELETE_FAIL 1 +#define LPT_STAT_ALLOCATED 2 +#define LPT_STAT_DEALLOCATED 3 +#define LPT_STAT_MAX 4 + +const char *lpt_counter_names[] = { + "sl insert failures", + "sl delete failures", + "labels allocated", + "labels deallocated", +}; + +static uint8_t lpt_generation; +static bool lpt_inprogress; +static struct skiplist *lp_tests; +static unsigned int lpt_test_cb_tcb_lookup_fails; +static unsigned int lpt_release_tcb_lookup_fails; +static unsigned int lpt_test_event_tcb_lookup_fails; +static unsigned int lpt_stop_tcb_lookup_fails; + +struct lp_test { + uint8_t generation; + unsigned int request_maximum; + unsigned int request_blocksize; + uintptr_t request_count; /* match type of labelid */ + int label_type; + struct skiplist *labels; + struct timeval starttime; + struct skiplist *timestamps_alloc; + struct skiplist *timestamps_dealloc; + struct event *event_thread; + unsigned int counter[LPT_STAT_MAX]; +}; + +/* test parameters */ +#define LPT_MAX_COUNT 500000 /* get this many labels in all */ +#define LPT_BLKSIZE 10000 /* this many at a time, then yield */ +#define LPT_TS_INTERVAL 10000 /* timestamp every this many labels */ + + +static int test_cb(mpls_label_t label, void *labelid, bool allocated) +{ + uintptr_t generation; + struct lp_test *tcb; + + generation = ((uintptr_t)labelid >> 24) & 0xff; + + if (skiplist_search(lp_tests, (void *)generation, (void **)&tcb)) { + + /* couldn't find current test in progress */ + ++lpt_test_cb_tcb_lookup_fails; + return -1; /* reject allocation */ + } + + if (allocated) { + ++tcb->counter[LPT_STAT_ALLOCATED]; + if (!(tcb->counter[LPT_STAT_ALLOCATED] % LPT_TS_INTERVAL)) { + uintptr_t time_ms; + + time_ms = monotime_since(&tcb->starttime, NULL) / 1000; + skiplist_insert(tcb->timestamps_alloc, + (void *)(uintptr_t)tcb + ->counter[LPT_STAT_ALLOCATED], + (void *)time_ms); + } + if (skiplist_insert(tcb->labels, labelid, + (void *)(uintptr_t)label)) { + ++tcb->counter[LPT_STAT_INSERT_FAIL]; + return -1; + } + } else { + ++tcb->counter[LPT_STAT_DEALLOCATED]; + if (!(tcb->counter[LPT_STAT_DEALLOCATED] % LPT_TS_INTERVAL)) { + uintptr_t time_ms; + + time_ms = monotime_since(&tcb->starttime, NULL) / 1000; + skiplist_insert(tcb->timestamps_dealloc, + (void *)(uintptr_t)tcb + ->counter[LPT_STAT_ALLOCATED], + (void *)time_ms); + } + if (skiplist_delete(tcb->labels, labelid, 0)) { + ++tcb->counter[LPT_STAT_DELETE_FAIL]; + return -1; + } + } + return 0; +} + +static void labelpool_test_event_handler(struct event *thread) +{ + struct lp_test *tcb; + + if (skiplist_search(lp_tests, (void *)(uintptr_t)(lpt_generation), + (void **)&tcb)) { + + /* couldn't find current test in progress */ + ++lpt_test_event_tcb_lookup_fails; + return; + } + + /* + * request a bunch of labels + */ + for (unsigned int i = 0; (i < tcb->request_blocksize) && + (tcb->request_count < tcb->request_maximum); + ++i) { + + uintptr_t id; + + ++tcb->request_count; + + /* + * construct 32-bit id from request_count and generation + */ + id = ((uintptr_t)tcb->generation << 24) | + (tcb->request_count & 0x00ffffff); + bgp_lp_get(LP_TYPE_VRF, (void *)id, test_cb); + } + + if (tcb->request_count < tcb->request_maximum) + thread_add_event(bm->master, labelpool_test_event_handler, NULL, + 0, &tcb->event_thread); +} + +static void lptest_stop(void) +{ + struct lp_test *tcb; + + if (!lpt_inprogress) + return; + + if (skiplist_search(lp_tests, (void *)(uintptr_t)(lpt_generation), + (void **)&tcb)) { + + /* couldn't find current test in progress */ + ++lpt_stop_tcb_lookup_fails; + return; + } + + if (tcb->event_thread) + event_cancel(&tcb->event_thread); + + lpt_inprogress = false; +} + +static int lptest_start(struct vty *vty) +{ + struct lp_test *tcb; + + if (lpt_inprogress) { + vty_out(vty, "test already in progress\n"); + return -1; + } + + if (skiplist_count(lp_tests) >= + (1 << (8 * sizeof(lpt_generation))) - 1) { + /* + * Too many test runs + */ + vty_out(vty, "too many tests: clear first\n"); + return -1; + } + + /* + * We pack the generation and request number into the labelid; + * make sure they fit. + */ + unsigned int n1 = LPT_MAX_COUNT; + unsigned int sh = 0; + unsigned int label_bits; + + label_bits = 8 * (sizeof(tcb->request_count) - sizeof(lpt_generation)); + + /* n1 should be same type as tcb->request_maximum */ + assert(sizeof(n1) == sizeof(tcb->request_maximum)); + + while (n1 >>= 1) + ++sh; + sh += 1; /* number of bits needed to hold LPT_MAX_COUNT */ + + if (sh > label_bits) { + vty_out(vty, + "Sorry, test iteration count too big on this platform (LPT_MAX_COUNT %u, need %u bits, but label_bits is only %u)\n", + LPT_MAX_COUNT, sh, label_bits); + return -1; + } + + lpt_inprogress = true; + ++lpt_generation; + + tcb = XCALLOC(MTYPE_LABELPOOL_TEST, sizeof(*tcb)); + + tcb->generation = lpt_generation; + tcb->label_type = LP_TYPE_VRF; + tcb->request_maximum = LPT_MAX_COUNT; + tcb->request_blocksize = LPT_BLKSIZE; + tcb->labels = skiplist_new(0, NULL, NULL); + tcb->timestamps_alloc = skiplist_new(0, NULL, NULL); + tcb->timestamps_dealloc = skiplist_new(0, NULL, NULL); + thread_add_event(bm->master, labelpool_test_event_handler, NULL, 0, + &tcb->event_thread); + monotime(&tcb->starttime); + + skiplist_insert(lp_tests, (void *)(uintptr_t)tcb->generation, tcb); + return 0; +} + +DEFPY(start_labelpool_perf_test, start_labelpool_perf_test_cmd, + "debug bgp lptest start", + DEBUG_STR BGP_STR + "label pool test\n" + "start\n") +{ + lptest_start(vty); + return CMD_SUCCESS; +} + +static void lptest_print_stats(struct vty *vty, struct lp_test *tcb) +{ + unsigned int i; + + vty_out(vty, "Global Lookup Failures in test_cb: %5u\n", + lpt_test_cb_tcb_lookup_fails); + vty_out(vty, "Global Lookup Failures in release: %5u\n", + lpt_release_tcb_lookup_fails); + vty_out(vty, "Global Lookup Failures in event: %5u\n", + lpt_test_event_tcb_lookup_fails); + vty_out(vty, "Global Lookup Failures in stop: %5u\n", + lpt_stop_tcb_lookup_fails); + vty_out(vty, "\n"); + + if (!tcb) { + if (skiplist_search(lp_tests, (void *)(uintptr_t)lpt_generation, + (void **)&tcb)) { + vty_out(vty, "Error: can't find test %u\n", + lpt_generation); + return; + } + } + + vty_out(vty, "Test Generation %u:\n", tcb->generation); + + vty_out(vty, "Counter Value\n"); + for (i = 0; i < LPT_STAT_MAX; ++i) { + vty_out(vty, "%20s: %10u\n", lpt_counter_names[i], + tcb->counter[i]); + } + vty_out(vty, "\n"); + + if (tcb->timestamps_alloc) { + void *Key; + void *Value; + void *cursor; + + float elapsed; + + vty_out(vty, "%10s %10s\n", "Count", "Seconds"); + + cursor = NULL; + while (!skiplist_next(tcb->timestamps_alloc, &Key, &Value, + &cursor)) { + + elapsed = ((float)(uintptr_t)Value) / 1000; + + vty_out(vty, "%10llu %10.3f\n", + (unsigned long long)(uintptr_t)Key, elapsed); + } + vty_out(vty, "\n"); + } +} + +DEFPY(show_labelpool_perf_test, show_labelpool_perf_test_cmd, + "debug bgp lptest show", + DEBUG_STR BGP_STR + "label pool test\n" + "show\n") +{ + + if (lp_tests) { + void *Key; + void *Value; + void *cursor; + + cursor = NULL; + while (!skiplist_next(lp_tests, &Key, &Value, &cursor)) { + lptest_print_stats(vty, (struct lp_test *)Value); + } + } else { + vty_out(vty, "no test results\n"); + } + return CMD_SUCCESS; +} + +DEFPY(stop_labelpool_perf_test, stop_labelpool_perf_test_cmd, + "debug bgp lptest stop", + DEBUG_STR BGP_STR + "label pool test\n" + "stop\n") +{ + + if (lpt_inprogress) { + lptest_stop(); + lptest_print_stats(vty, NULL); + } else { + vty_out(vty, "no test in progress\n"); + } + return CMD_SUCCESS; +} + +DEFPY(clear_labelpool_perf_test, clear_labelpool_perf_test_cmd, + "debug bgp lptest clear", + DEBUG_STR BGP_STR + "label pool test\n" + "clear\n") +{ + + if (lpt_inprogress) { + lptest_stop(); + } + if (lp_tests) { + while (!skiplist_first(lp_tests, NULL, NULL)) + /* del function of skiplist cleans up tcbs */ + skiplist_delete_first(lp_tests); + } + return CMD_SUCCESS; +} + +/* + * With the "release" command, we can release labels at intervals through + * the ID space. Thus we can to exercise the bitfield-wrapping behavior + * of the allocator in a subsequent test. + */ +/* clang-format off */ +DEFPY(release_labelpool_perf_test, release_labelpool_perf_test_cmd, + "debug bgp lptest release test GENERATION$generation every (1-5)$every_nth", + DEBUG_STR + BGP_STR + "label pool test\n" + "release labels\n" + "\"test\"\n" + "test number\n" + "\"every\"\n" + "label fraction denominator\n") +{ + /* clang-format on */ + + unsigned long testnum; + char *end; + struct lp_test *tcb; + + testnum = strtoul(generation, &end, 0); + if (*end) { + vty_out(vty, "Invalid test number: \"%s\"\n", generation); + return CMD_SUCCESS; + } + if (lpt_inprogress && (testnum == lpt_generation)) { + vty_out(vty, + "Error: Test %lu is still in progress (stop first)\n", + testnum); + return CMD_SUCCESS; + } + + if (skiplist_search(lp_tests, (void *)(uintptr_t)testnum, + (void **)&tcb)) { + + /* couldn't find current test in progress */ + vty_out(vty, "Error: Can't look up test number: \"%lu\"\n", + testnum); + ++lpt_release_tcb_lookup_fails; + return CMD_SUCCESS; + } + + void *Key, *cKey; + void *Value, *cValue; + void *cursor; + unsigned int iteration; + int rc; + + cursor = NULL; + iteration = 0; + rc = skiplist_next(tcb->labels, &Key, &Value, &cursor); + + while (!rc) { + cKey = Key; + cValue = Value; + + /* find next item before we delete this one */ + rc = skiplist_next(tcb->labels, &Key, &Value, &cursor); + + if (!(iteration % every_nth)) { + bgp_lp_release(tcb->label_type, cKey, + (mpls_label_t)(uintptr_t)cValue); + skiplist_delete(tcb->labels, cKey, NULL); + ++tcb->counter[LPT_STAT_DEALLOCATED]; + } + ++iteration; + } + + return CMD_SUCCESS; +} + +static void lptest_delete(void *val) +{ + struct lp_test *tcb = (struct lp_test *)val; + void *Key; + void *Value; + void *cursor; + + if (tcb->labels) { + cursor = NULL; + while (!skiplist_next(tcb->labels, &Key, &Value, &cursor)) + bgp_lp_release(tcb->label_type, Key, + (mpls_label_t)(uintptr_t)Value); + skiplist_free(tcb->labels); + tcb->labels = NULL; + } + if (tcb->timestamps_alloc) { + cursor = NULL; + skiplist_free(tcb->timestamps_alloc); + tcb->timestamps_alloc = NULL; + } + + if (tcb->timestamps_dealloc) { + cursor = NULL; + skiplist_free(tcb->timestamps_dealloc); + tcb->timestamps_dealloc = NULL; + } + + if (tcb->event_thread) + event_cancel(&tcb->event_thread); + + memset(tcb, 0, sizeof(*tcb)); + + XFREE(MTYPE_LABELPOOL_TEST, tcb); +} + +static void lptest_init(void) +{ + lp_tests = skiplist_new(0, NULL, lptest_delete); +} + +static void lptest_finish(void) +{ + if (lp_tests) { + skiplist_free(lp_tests); + lp_tests = NULL; + } +} + +/*------------------------------------------------------------------------ + * Testing code end + *------------------------------------------------------------------------*/ +#endif /* BGP_LABELPOOL_ENABLE_TESTS */ + +void bgp_lp_vty_init(void) +{ + install_element(VIEW_NODE, &show_bgp_labelpool_summary_cmd); + install_element(VIEW_NODE, &show_bgp_labelpool_ledger_cmd); + install_element(VIEW_NODE, &show_bgp_labelpool_inuse_cmd); + install_element(VIEW_NODE, &show_bgp_labelpool_requests_cmd); + install_element(VIEW_NODE, &show_bgp_labelpool_chunks_cmd); + +#if BGP_LABELPOOL_ENABLE_TESTS + install_element(ENABLE_NODE, &start_labelpool_perf_test_cmd); + install_element(ENABLE_NODE, &show_labelpool_perf_test_cmd); + install_element(ENABLE_NODE, &stop_labelpool_perf_test_cmd); + install_element(ENABLE_NODE, &release_labelpool_perf_test_cmd); + install_element(ENABLE_NODE, &clear_labelpool_perf_test_cmd); +#endif /* BGP_LABELPOOL_ENABLE_TESTS */ +} + +DEFINE_MTYPE_STATIC(BGPD, LABEL_PER_NEXTHOP_CACHE, + "BGP Label Per Nexthop entry"); + +/* The nexthops values are compared to + * find in the tree the appropriate cache entry + */ +int bgp_label_per_nexthop_cache_cmp(const struct bgp_label_per_nexthop_cache *a, + const struct bgp_label_per_nexthop_cache *b) +{ + return prefix_cmp(&a->nexthop, &b->nexthop); +} + +struct bgp_label_per_nexthop_cache * +bgp_label_per_nexthop_new(struct bgp_label_per_nexthop_cache_head *tree, + struct prefix *nexthop) +{ + struct bgp_label_per_nexthop_cache *blnc; + + blnc = XCALLOC(MTYPE_LABEL_PER_NEXTHOP_CACHE, + sizeof(struct bgp_label_per_nexthop_cache)); + blnc->tree = tree; + blnc->label = MPLS_INVALID_LABEL; + prefix_copy(&blnc->nexthop, nexthop); + LIST_INIT(&(blnc->paths)); + bgp_label_per_nexthop_cache_add(tree, blnc); + + return blnc; +} + +struct bgp_label_per_nexthop_cache * +bgp_label_per_nexthop_find(struct bgp_label_per_nexthop_cache_head *tree, + struct prefix *nexthop) +{ + struct bgp_label_per_nexthop_cache blnc = {}; + + if (!tree) + return NULL; + + memcpy(&blnc.nexthop, nexthop, sizeof(struct prefix)); + return bgp_label_per_nexthop_cache_find(tree, &blnc); +} + +void bgp_label_per_nexthop_free(struct bgp_label_per_nexthop_cache *blnc) +{ + if (blnc->label != MPLS_INVALID_LABEL) { + bgp_zebra_send_nexthop_label(ZEBRA_MPLS_LABELS_DELETE, + blnc->label, blnc->nh->ifindex, + blnc->nh->vrf_id, ZEBRA_LSP_BGP, + &blnc->nexthop, 0, NULL); + bgp_lp_release(LP_TYPE_NEXTHOP, blnc, blnc->label); + } + bgp_label_per_nexthop_cache_del(blnc->tree, blnc); + if (blnc->nh) + nexthop_free(blnc->nh); + blnc->nh = NULL; + XFREE(MTYPE_LABEL_PER_NEXTHOP_CACHE, blnc); +} + +void bgp_label_per_nexthop_init(void) +{ + install_element(VIEW_NODE, &show_bgp_nexthop_label_cmd); +} diff --git a/bgpd/bgp_labelpool.h b/bgpd/bgp_labelpool.h new file mode 100644 index 0000000..a17482d --- /dev/null +++ b/bgpd/bgp_labelpool.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Label Pool - Manage label chunk allocations from zebra asynchronously + * + * Copyright (C) 2018 LabN Consulting, L.L.C. + */ + +#ifndef _FRR_BGP_LABELPOOL_H +#define _FRR_BGP_LABELPOOL_H + +#include + +#include "mpls.h" + +/* + * Types used in bgp_lp_get for debug tracking; add more as needed + */ +#define LP_TYPE_VRF 0x00000001 +#define LP_TYPE_BGP_LU 0x00000002 +#define LP_TYPE_NEXTHOP 0x00000003 +#define LP_TYPE_BGP_L3VPN_BIND 0x00000004 + +PREDECL_LIST(lp_fifo); + +struct labelpool { + struct skiplist *ledger; /* all requests */ + struct skiplist *inuse; /* individual labels */ + struct list *chunks; /* granted by zebra */ + struct lp_fifo_head requests; /* blocked on zebra */ + struct work_queue *callback_q; + uint32_t pending_count; /* requested from zebra */ + uint32_t reconnect_count; /* zebra reconnections */ + uint32_t next_chunksize; /* request this many labels */ +}; + +extern void bgp_lp_init(struct event_loop *master, struct labelpool *pool); +extern void bgp_lp_finish(void); +extern void bgp_lp_get(int type, void *labelid, + int (*cbfunc)(mpls_label_t label, void *labelid, bool allocated)); +extern void bgp_lp_release(int type, void *labelid, mpls_label_t label); +extern void bgp_lp_event_chunk(uint32_t first, uint32_t last); +extern void bgp_lp_event_zebra_down(void); +extern void bgp_lp_event_zebra_up(void); +extern void bgp_lp_vty_init(void); + +struct bgp_label_per_nexthop_cache; +PREDECL_RBTREE_UNIQ(bgp_label_per_nexthop_cache); + +extern int +bgp_label_per_nexthop_cache_cmp(const struct bgp_label_per_nexthop_cache *a, + const struct bgp_label_per_nexthop_cache *b); + +struct bgp_label_per_nexthop_cache { + + /* RB-tree entry. */ + struct bgp_label_per_nexthop_cache_item entry; + + /* the nexthop is the key of the list */ + struct prefix nexthop; + + /* calculated label */ + mpls_label_t label; + + /* number of path_vrfs */ + unsigned int path_count; + + /* back pointer to bgp instance */ + struct bgp *to_bgp; + + /* copy a nexthop resolution from bgp nexthop tracking + * used to extract the interface nexthop + */ + struct nexthop *nh; + + /* list of path_vrfs using it */ + LIST_HEAD(path_lists, bgp_path_info) paths; + + time_t last_update; + + /* Back pointer to the cache tree this entry belongs to. */ + struct bgp_label_per_nexthop_cache_head *tree; +}; + +DECLARE_RBTREE_UNIQ(bgp_label_per_nexthop_cache, + struct bgp_label_per_nexthop_cache, entry, + bgp_label_per_nexthop_cache_cmp); + +void bgp_label_per_nexthop_free(struct bgp_label_per_nexthop_cache *blnc); + +struct bgp_label_per_nexthop_cache * +bgp_label_per_nexthop_new(struct bgp_label_per_nexthop_cache_head *tree, + struct prefix *nexthop); +struct bgp_label_per_nexthop_cache * +bgp_label_per_nexthop_find(struct bgp_label_per_nexthop_cache_head *tree, + struct prefix *nexthop); +void bgp_label_per_nexthop_init(void); +#endif /* _FRR_BGP_LABELPOOL_H */ diff --git a/bgpd/bgp_lcommunity.c b/bgpd/bgp_lcommunity.c new file mode 100644 index 0000000..1b8c22a --- /dev/null +++ b/bgpd/bgp_lcommunity.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Large Communities Attribute + * + * Copyright (C) 2016 Keyur Patel + */ + +#include + +#include "hash.h" +#include "memory.h" +#include "prefix.h" +#include "command.h" +#include "filter.h" +#include "jhash.h" +#include "stream.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_aspath.h" + +/* Hash of community attribute. */ +static struct hash *lcomhash; + +/* Allocate a new lcommunities. */ +static struct lcommunity *lcommunity_new(void) +{ + return XCALLOC(MTYPE_LCOMMUNITY, sizeof(struct lcommunity)); +} + +/* Allocate lcommunities. */ +void lcommunity_free(struct lcommunity **lcom) +{ + if (!(*lcom)) + return; + + XFREE(MTYPE_LCOMMUNITY_VAL, (*lcom)->val); + XFREE(MTYPE_LCOMMUNITY_STR, (*lcom)->str); + if ((*lcom)->json) + json_object_free((*lcom)->json); + XFREE(MTYPE_LCOMMUNITY, *lcom); +} + +static void lcommunity_hash_free(struct lcommunity *lcom) +{ + lcommunity_free(&lcom); +} + +/* Add a new Large Communities value to Large Communities + Attribute structure. When the value is already exists in the + structure, we don't add the value. Newly added value is sorted by + numerical order. When the value is added to the structure return 1 + else return 0. */ +static bool lcommunity_add_val(struct lcommunity *lcom, + struct lcommunity_val *lval) +{ + uint8_t *p; + int ret; + int c; + + /* When this is fist value, just add it. */ + if (lcom->val == NULL) { + lcom->size++; + lcom->val = XMALLOC(MTYPE_LCOMMUNITY_VAL, lcom_length(lcom)); + memcpy(lcom->val, lval->val, LCOMMUNITY_SIZE); + return true; + } + + /* If the value already exists in the structure return 0. */ + c = 0; + for (p = lcom->val; c < lcom->size; p += LCOMMUNITY_SIZE, c++) { + ret = memcmp(p, lval->val, LCOMMUNITY_SIZE); + if (ret == 0) + return false; + if (ret > 0) + break; + } + + /* Add the value to the structure with numerical sorting. */ + lcom->size++; + lcom->val = + XREALLOC(MTYPE_LCOMMUNITY_VAL, lcom->val, lcom_length(lcom)); + + memmove(lcom->val + (c + 1) * LCOMMUNITY_SIZE, + lcom->val + c * LCOMMUNITY_SIZE, + (lcom->size - 1 - c) * LCOMMUNITY_SIZE); + memcpy(lcom->val + c * LCOMMUNITY_SIZE, lval->val, LCOMMUNITY_SIZE); + + return true; +} + +/* This function takes pointer to Large Communites structure then + create a new Large Communities structure by uniq and sort each + Large Communities value. */ +struct lcommunity *lcommunity_uniq_sort(struct lcommunity *lcom) +{ + int i; + struct lcommunity *new; + struct lcommunity_val *lval; + + if (!lcom) + return NULL; + + new = lcommunity_new(); + + for (i = 0; i < lcom->size; i++) { + lval = (struct lcommunity_val *)(lcom->val + + (i * LCOMMUNITY_SIZE)); + lcommunity_add_val(new, lval); + } + return new; +} + +/* Parse Large Communites Attribute in BGP packet. */ +struct lcommunity *lcommunity_parse(uint8_t *pnt, unsigned short length) +{ + struct lcommunity tmp; + struct lcommunity *new; + + /* Length check. */ + if (length % LCOMMUNITY_SIZE) + return NULL; + + /* Prepare tmporary structure for making a new Large Communities + Attribute. */ + tmp.size = length / LCOMMUNITY_SIZE; + tmp.val = pnt; + + /* Create a new Large Communities Attribute by uniq and sort each + Large Communities value */ + new = lcommunity_uniq_sort(&tmp); + + return lcommunity_intern(new); +} + +/* Duplicate the Large Communities Attribute structure. */ +struct lcommunity *lcommunity_dup(struct lcommunity *lcom) +{ + struct lcommunity *new; + + new = lcommunity_new(); + new->size = lcom->size; + if (new->size) { + new->val = XMALLOC(MTYPE_LCOMMUNITY_VAL, lcom_length(lcom)); + memcpy(new->val, lcom->val, lcom_length(lcom)); + } else + new->val = NULL; + return new; +} + +/* Merge two Large Communities Attribute structure. */ +struct lcommunity *lcommunity_merge(struct lcommunity *lcom1, + struct lcommunity *lcom2) +{ + lcom1->val = XREALLOC(MTYPE_LCOMMUNITY_VAL, lcom1->val, + lcom_length(lcom1) + lcom_length(lcom2)); + + memcpy(lcom1->val + lcom_length(lcom1), lcom2->val, lcom_length(lcom2)); + lcom1->size += lcom2->size; + + return lcom1; +} + +static void set_lcommunity_string(struct lcommunity *lcom, bool make_json, + bool translate_alias) +{ + int i; + int len; + char *str_buf; + const uint8_t *pnt; + uint32_t global, local1, local2; + json_object *json_lcommunity_list = NULL; + json_object *json_string = NULL; + + /* 3 32-bit integers, 2 colons, and a space */ +#define LCOMMUNITY_STRLEN (10 * 3 + 2 + 1) + + if (!lcom) + return; + + if (make_json) { + lcom->json = json_object_new_object(); + json_lcommunity_list = json_object_new_array(); + } + + if (lcom->size == 0) { + str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, 1); + + if (make_json) { + json_object_string_add(lcom->json, "string", ""); + json_object_object_add(lcom->json, "list", + json_lcommunity_list); + } + + lcom->str = str_buf; + return; + } + + /* 1 space + lcom->size lcom strings + null terminator */ + size_t str_buf_sz = (LCOMMUNITY_STRLEN * lcom->size) + 2; + str_buf = XCALLOC(MTYPE_LCOMMUNITY_STR, str_buf_sz); + + len = 0; + for (i = 0; i < lcom->size; i++) { + if (i > 0) + len = strlcat(str_buf, " ", str_buf_sz); + + pnt = lcom->val + (i * LCOMMUNITY_SIZE); + pnt = ptr_get_be32(pnt, &global); + pnt = ptr_get_be32(pnt, &local1); + pnt = ptr_get_be32(pnt, &local2); + (void)pnt; + + char lcsb[LCOMMUNITY_STRLEN + 1]; + + snprintf(lcsb, sizeof(lcsb), "%u:%u:%u", global, local1, + local2); + + /* + * Aliases can cause havoc, if the alias length is greater + * than the LCOMMUNITY_STRLEN for a particular item + * then we need to realloc the memory associated + * with the string so that it can fit + */ + const char *com2alias = + translate_alias ? bgp_community2alias(lcsb) : lcsb; + size_t individual_len = strlen(com2alias); + if (individual_len + len > str_buf_sz) { + str_buf_sz = individual_len + len + 1; + str_buf = XREALLOC(MTYPE_LCOMMUNITY_STR, str_buf, + str_buf_sz); + } + + len = strlcat(str_buf, com2alias, str_buf_sz); + + if (make_json) { + json_string = json_object_new_string(com2alias); + json_object_array_add(json_lcommunity_list, + json_string); + } + } + + if (make_json) { + json_object_string_add(lcom->json, "string", str_buf); + json_object_object_add(lcom->json, "list", + json_lcommunity_list); + } + + lcom->str = str_buf; +} + +/* Intern Large Communities Attribute. */ +struct lcommunity *lcommunity_intern(struct lcommunity *lcom) +{ + struct lcommunity *find; + + assert(lcom->refcnt == 0); + + find = (struct lcommunity *)hash_get(lcomhash, lcom, hash_alloc_intern); + + if (find != lcom) + lcommunity_free(&lcom); + + find->refcnt++; + + if (!find->str) + set_lcommunity_string(find, false, true); + + return find; +} + +/* Unintern Large Communities Attribute. */ +void lcommunity_unintern(struct lcommunity **lcom) +{ + struct lcommunity *ret; + + if (!*lcom) + return; + + if ((*lcom)->refcnt) + (*lcom)->refcnt--; + + /* Pull off from hash. */ + if ((*lcom)->refcnt == 0) { + /* Large community must be in the hash. */ + ret = (struct lcommunity *)hash_release(lcomhash, *lcom); + assert(ret != NULL); + + lcommunity_free(lcom); + } +} + +/* Return string representation of lcommunities attribute. */ +char *lcommunity_str(struct lcommunity *lcom, bool make_json, + bool translate_alias) +{ + if (!lcom) + return NULL; + + if (make_json && !lcom->json && lcom->str) + XFREE(MTYPE_LCOMMUNITY_STR, lcom->str); + + if (!lcom->str) + set_lcommunity_string(lcom, make_json, translate_alias); + + return lcom->str; +} + +/* Utility function to make hash key. */ +unsigned int lcommunity_hash_make(const void *arg) +{ + const struct lcommunity *lcom = arg; + int size = lcom_length(lcom); + + return jhash(lcom->val, size, 0xab125423); +} + +/* Compare two Large Communities Attribute structure. */ +bool lcommunity_cmp(const void *arg1, const void *arg2) +{ + const struct lcommunity *lcom1 = arg1; + const struct lcommunity *lcom2 = arg2; + + if (lcom1 == NULL && lcom2 == NULL) + return true; + + if (lcom1 == NULL || lcom2 == NULL) + return false; + + return (lcom1->size == lcom2->size + && memcmp(lcom1->val, lcom2->val, lcom_length(lcom1)) == 0); +} + +/* Return communities hash. */ +struct hash *lcommunity_hash(void) +{ + return lcomhash; +} + +/* Initialize Large Comminities related hash. */ +void lcommunity_init(void) +{ + lcomhash = hash_create(lcommunity_hash_make, lcommunity_cmp, + "BGP lcommunity hash"); +} + +void lcommunity_finish(void) +{ + hash_clean_and_free(&lcomhash, (void (*)(void *))lcommunity_hash_free); +} + +/* Get next Large Communities token from the string. + * Assumes str is space-delimeted and describes 0 or more + * valid large communities + */ +static const char *lcommunity_gettoken(const char *str, + struct lcommunity_val *lval) +{ + const char *p = str; + + /* Skip white space. */ + while (isspace((unsigned char)*p)) { + p++; + str++; + } + + /* Check the end of the line. */ + if (*p == '\0') + return NULL; + + /* Community value. */ + int separator = 0; + int digit = 0; + uint32_t globaladmin = 0; + uint32_t localdata1 = 0; + uint32_t localdata2 = 0; + + while (*p && *p != ' ') { + /* large community valid chars */ + assert(isdigit((unsigned char)*p) || *p == ':'); + + if (*p == ':') { + separator++; + digit = 0; + if (separator == 1) { + globaladmin = localdata2; + } else { + localdata1 = localdata2; + } + localdata2 = 0; + } else { + digit = 1; + /* left shift the accumulated value and add current + * digit + */ + localdata2 *= 10; + localdata2 += (*p - '0'); + } + p++; + } + + /* Assert str was a valid large community */ + assert(separator == 2 && digit == 1); + + /* + * Copy the large comm. + */ + lval->val[0] = (globaladmin >> 24) & 0xff; + lval->val[1] = (globaladmin >> 16) & 0xff; + lval->val[2] = (globaladmin >> 8) & 0xff; + lval->val[3] = globaladmin & 0xff; + lval->val[4] = (localdata1 >> 24) & 0xff; + lval->val[5] = (localdata1 >> 16) & 0xff; + lval->val[6] = (localdata1 >> 8) & 0xff; + lval->val[7] = localdata1 & 0xff; + lval->val[8] = (localdata2 >> 24) & 0xff; + lval->val[9] = (localdata2 >> 16) & 0xff; + lval->val[10] = (localdata2 >> 8) & 0xff; + lval->val[11] = localdata2 & 0xff; + + return p; +} + +/* + Convert string to large community attribute. + When type is already known, please specify both str and type. + + When string includes keyword for each large community value. + Please specify keyword_included as non-zero value. +*/ +struct lcommunity *lcommunity_str2com(const char *str) +{ + struct lcommunity *lcom = NULL; + struct lcommunity_val lval; + + if (!lcommunity_list_valid(str, LARGE_COMMUNITY_LIST_STANDARD)) + return NULL; + + do { + str = lcommunity_gettoken(str, &lval); + if (lcom == NULL) + lcom = lcommunity_new(); + lcommunity_add_val(lcom, &lval); + } while (str); + + return lcom; +} + +bool lcommunity_include(struct lcommunity *lcom, uint8_t *ptr) +{ + int i; + uint8_t *lcom_ptr; + + for (i = 0; i < lcom->size; i++) { + lcom_ptr = lcom->val + (i * LCOMMUNITY_SIZE); + if (memcmp(ptr, lcom_ptr, LCOMMUNITY_SIZE) == 0) + return true; + } + return false; +} + +bool lcommunity_match(const struct lcommunity *lcom1, + const struct lcommunity *lcom2) +{ + int i = 0; + int j = 0; + + if (lcom1 == NULL && lcom2 == NULL) + return true; + + if (lcom1 == NULL || lcom2 == NULL) + return false; + + if (lcom1->size < lcom2->size) + return false; + + /* Every community on com2 needs to be on com1 for this to match */ + while (i < lcom1->size && j < lcom2->size) { + if (memcmp(lcom1->val + (i * LCOMMUNITY_SIZE), + lcom2->val + (j * LCOMMUNITY_SIZE), LCOMMUNITY_SIZE) + == 0) + j++; + i++; + } + + if (j == lcom2->size) + return true; + else + return false; +} + +/* Delete one lcommunity. */ +void lcommunity_del_val(struct lcommunity *lcom, uint8_t *ptr) +{ + int i = 0; + int c = 0; + + if (!lcom->val) + return; + + while (i < lcom->size) { + if (memcmp(lcom->val + i * LCOMMUNITY_SIZE, ptr, + LCOMMUNITY_SIZE) + == 0) { + c = lcom->size - i - 1; + + if (c > 0) + memmove(lcom->val + i * LCOMMUNITY_SIZE, + lcom->val + (i + 1) * LCOMMUNITY_SIZE, + c * LCOMMUNITY_SIZE); + + lcom->size--; + + if (lcom->size > 0) + lcom->val = + XREALLOC(MTYPE_LCOMMUNITY_VAL, + lcom->val, lcom_length(lcom)); + else { + XFREE(MTYPE_LCOMMUNITY_VAL, lcom->val); + } + return; + } + i++; + } +} + +static struct lcommunity *bgp_aggr_lcommunity_lookup( + struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity) +{ + return hash_lookup(aggregate->lcommunity_hash, lcommunity); +} + +static void *bgp_aggr_lcommunty_hash_alloc(void *p) +{ + struct lcommunity *ref = (struct lcommunity *)p; + struct lcommunity *lcommunity = NULL; + + lcommunity = lcommunity_dup(ref); + return lcommunity; +} + +static void bgp_aggr_lcommunity_prepare(struct hash_bucket *hb, void *arg) +{ + struct lcommunity *hb_lcommunity = hb->data; + struct lcommunity **aggr_lcommunity = arg; + + if (*aggr_lcommunity) + *aggr_lcommunity = lcommunity_merge(*aggr_lcommunity, + hb_lcommunity); + else + *aggr_lcommunity = lcommunity_dup(hb_lcommunity); +} + +void bgp_aggr_lcommunity_remove(void *arg) +{ + struct lcommunity *lcommunity = arg; + + lcommunity_free(&lcommunity); +} + +void bgp_compute_aggregate_lcommunity(struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity) +{ + + bgp_compute_aggregate_lcommunity_hash(aggregate, lcommunity); + bgp_compute_aggregate_lcommunity_val(aggregate); +} + +void bgp_compute_aggregate_lcommunity_hash(struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity) +{ + + struct lcommunity *aggr_lcommunity = NULL; + + if ((aggregate == NULL) || (lcommunity == NULL)) + return; + + /* Create hash if not already created. + */ + if (aggregate->lcommunity_hash == NULL) + aggregate->lcommunity_hash = hash_create( + lcommunity_hash_make, lcommunity_cmp, + "BGP Aggregator lcommunity hash"); + + aggr_lcommunity = bgp_aggr_lcommunity_lookup(aggregate, lcommunity); + if (aggr_lcommunity == NULL) { + /* Insert lcommunity into hash. + */ + aggr_lcommunity = hash_get(aggregate->lcommunity_hash, + lcommunity, + bgp_aggr_lcommunty_hash_alloc); + } + + /* Increment reference counter. + */ + aggr_lcommunity->refcnt++; +} + +void bgp_compute_aggregate_lcommunity_val(struct bgp_aggregate *aggregate) +{ + struct lcommunity *lcommerge = NULL; + + if (aggregate == NULL) + return; + + /* Re-compute aggregate's lcommunity. + */ + if (aggregate->lcommunity) + lcommunity_free(&aggregate->lcommunity); + if (aggregate->lcommunity_hash && + aggregate->lcommunity_hash->count) { + hash_iterate(aggregate->lcommunity_hash, + bgp_aggr_lcommunity_prepare, + &aggregate->lcommunity); + lcommerge = aggregate->lcommunity; + aggregate->lcommunity = lcommunity_uniq_sort(lcommerge); + if (lcommerge) + lcommunity_free(&lcommerge); + } +} + +void bgp_remove_lcommunity_from_aggregate(struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity) +{ + struct lcommunity *aggr_lcommunity = NULL; + struct lcommunity *ret_lcomm = NULL; + + if ((!aggregate) + || (!aggregate->lcommunity_hash) + || (!lcommunity)) + return; + + /* Look-up the lcommunity in the hash. + */ + aggr_lcommunity = bgp_aggr_lcommunity_lookup(aggregate, lcommunity); + if (aggr_lcommunity) { + aggr_lcommunity->refcnt--; + + if (aggr_lcommunity->refcnt == 0) { + ret_lcomm = hash_release(aggregate->lcommunity_hash, + aggr_lcommunity); + lcommunity_free(&ret_lcomm); + + bgp_compute_aggregate_lcommunity_val(aggregate); + + } + } +} + +void bgp_remove_lcomm_from_aggregate_hash(struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity) +{ + struct lcommunity *aggr_lcommunity = NULL; + struct lcommunity *ret_lcomm = NULL; + + if ((!aggregate) + || (!aggregate->lcommunity_hash) + || (!lcommunity)) + return; + + /* Look-up the lcommunity in the hash. + */ + aggr_lcommunity = bgp_aggr_lcommunity_lookup(aggregate, lcommunity); + if (aggr_lcommunity) { + aggr_lcommunity->refcnt--; + + if (aggr_lcommunity->refcnt == 0) { + ret_lcomm = hash_release(aggregate->lcommunity_hash, + aggr_lcommunity); + lcommunity_free(&ret_lcomm); + } + } +} diff --git a/bgpd/bgp_lcommunity.h b/bgpd/bgp_lcommunity.h new file mode 100644 index 0000000..151b79f --- /dev/null +++ b/bgpd/bgp_lcommunity.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Large Communities Attribute. + * + * Copyright (C) 2016 Keyur Patel + */ + +#ifndef _QUAGGA_BGP_LCOMMUNITY_H +#define _QUAGGA_BGP_LCOMMUNITY_H + +#include "lib/json.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_clist.h" + +/* Large Communities value is twelve octets long. */ +#define LCOMMUNITY_SIZE 12 + +/* Large Communities attribute. */ +struct lcommunity { + /* Reference counter. */ + unsigned long refcnt; + + /* Size of Extended Communities attribute. */ + int size; + + /* Large Communities value. */ + uint8_t *val; + + /* Large Communities as a json object */ + json_object *json; + + /* Human readable format string. */ + char *str; +}; + +/* Large community value is 12 octets. */ +struct lcommunity_val { + char val[LCOMMUNITY_SIZE]; +}; + +#define lcom_length(X) ((X)->size * LCOMMUNITY_SIZE) + +extern void lcommunity_init(void); +extern void lcommunity_finish(void); +extern void lcommunity_free(struct lcommunity **); +extern struct lcommunity *lcommunity_parse(uint8_t *, unsigned short); +extern struct lcommunity *lcommunity_dup(struct lcommunity *); +extern struct lcommunity *lcommunity_merge(struct lcommunity *, + struct lcommunity *); +extern struct lcommunity *lcommunity_uniq_sort(struct lcommunity *); +extern struct lcommunity *lcommunity_intern(struct lcommunity *); +extern bool lcommunity_cmp(const void *arg1, const void *arg2); +extern void lcommunity_unintern(struct lcommunity **); +extern unsigned int lcommunity_hash_make(const void *); +extern struct hash *lcommunity_hash(void); +extern struct lcommunity *lcommunity_str2com(const char *); +extern bool lcommunity_match(const struct lcommunity *, + const struct lcommunity *); +extern char *lcommunity_str(struct lcommunity *, bool make_json, + bool translate_alias); +extern bool lcommunity_include(struct lcommunity *lcom, uint8_t *ptr); +extern void lcommunity_del_val(struct lcommunity *lcom, uint8_t *ptr); + +extern void bgp_compute_aggregate_lcommunity( + struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity); + +extern void bgp_compute_aggregate_lcommunity_hash( + struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity); +extern void bgp_compute_aggregate_lcommunity_val( + struct bgp_aggregate *aggregate); + +extern void bgp_remove_lcommunity_from_aggregate( + struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity); +extern void bgp_remove_lcomm_from_aggregate_hash( + struct bgp_aggregate *aggregate, + struct lcommunity *lcommunity); +extern void bgp_aggr_lcommunity_remove(void *arg); + +#endif /* _QUAGGA_BGP_LCOMMUNITY_H */ diff --git a/bgpd/bgp_mac.c b/bgpd/bgp_mac.c new file mode 100644 index 0000000..28fb452 --- /dev/null +++ b/bgpd/bgp_mac.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGPd - Mac hash code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include +#include +#include + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_memory.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_evpn_private.h" + +DEFINE_MTYPE_STATIC(BGPD, BSM, "Mac Hash Entry"); +DEFINE_MTYPE_STATIC(BGPD, BSM_STRING, "Mac Hash Entry Intf String"); + +struct bgp_self_mac { + struct ethaddr macaddr; + struct list *ifp_list; +}; + +static unsigned int bgp_mac_hash_key_make(const void *data) +{ + const struct bgp_self_mac *bsm = data; + + return jhash(&bsm->macaddr, ETH_ALEN, 0xa5a5dead); +} + +static bool bgp_mac_hash_cmp(const void *d1, const void *d2) +{ + const struct bgp_self_mac *bsm1 = d1; + const struct bgp_self_mac *bsm2 = d2; + + if (memcmp(&bsm1->macaddr, &bsm2->macaddr, ETH_ALEN) == 0) + return true; + + return false; +} + +void bgp_mac_init(void) +{ + bm->self_mac_hash = hash_create(bgp_mac_hash_key_make, bgp_mac_hash_cmp, + "BGP MAC Hash"); +} + +static void bgp_mac_hash_free(void *data) +{ + struct bgp_self_mac *bsm = data; + + if (bsm->ifp_list) + list_delete(&bsm->ifp_list); + + XFREE(MTYPE_BSM, bsm); +} + +void bgp_mac_finish(void) +{ + hash_clean_and_free(&bm->self_mac_hash, bgp_mac_hash_free); +} + +static void bgp_mac_hash_interface_string_del(void *val) +{ + char *data = val; + + XFREE(MTYPE_BSM_STRING, data); +} + +static void *bgp_mac_hash_alloc(void *p) +{ + const struct bgp_self_mac *orig = p; + struct bgp_self_mac *bsm; + + bsm = XCALLOC(MTYPE_BSM, sizeof(struct bgp_self_mac)); + memcpy(&bsm->macaddr, &orig->macaddr, ETH_ALEN); + + bsm->ifp_list = list_new(); + bsm->ifp_list->del = bgp_mac_hash_interface_string_del; + + return bsm; +} + +struct bgp_mac_find_internal { + struct bgp_self_mac *bsm; + const char *ifname; +}; + +static void bgp_mac_find_ifp_internal(struct hash_bucket *bucket, void *arg) +{ + struct bgp_mac_find_internal *bmfi = arg; + struct bgp_self_mac *bsm = bucket->data; + struct listnode *node; + char *name; + + for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) { + if (strcmp(name, bmfi->ifname) == 0) { + bmfi->bsm = bsm; + return; + } + } +} + +static struct bgp_self_mac *bgp_mac_find_interface_name(const char *ifname) +{ + struct bgp_mac_find_internal bmfi; + + bmfi.bsm = NULL; + bmfi.ifname = ifname; + hash_iterate(bm->self_mac_hash, bgp_mac_find_ifp_internal, &bmfi); + + return bmfi.bsm; +} + +static void bgp_process_mac_rescan_table(struct bgp *bgp, struct peer *peer, + struct bgp_table *table, + struct ethaddr *macaddr) +{ + struct bgp_dest *pdest, *dest; + struct bgp_path_info *pi; + uint8_t num_labels; + mpls_label_t *label_pnt; + + for (pdest = bgp_table_top(table); pdest; + pdest = bgp_route_next(pdest)) { + struct bgp_table *sub = pdest->info; + const struct prefix *pdest_p = bgp_dest_get_prefix(pdest); + + if (!sub) + continue; + + for (dest = bgp_table_top(sub); dest; + dest = bgp_route_next(dest)) { + bool dest_affected; + const struct prefix *p = bgp_dest_get_prefix(dest); + struct prefix_evpn *pevpn = (struct prefix_evpn *)dest; + struct prefix_rd prd; + struct bgp_route_evpn *evpn; + + if (pevpn->family == AF_EVPN + && pevpn->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + && memcmp(&p->u.prefix_evpn.macip_addr.mac, macaddr, + ETH_ALEN) + == 0) + dest_affected = true; + else + dest_affected = false; + + for (pi = dest->info; pi; pi = pi->next) { + if (pi->peer == peer) + break; + } + + if (!pi) + continue; + + /* + * If the mac address is not the same then + * we don't care and since we are looking + */ + if ((memcmp(&pi->attr->rmac, macaddr, ETH_ALEN) != 0) + && !dest_affected) + continue; + + num_labels = BGP_PATH_INFO_NUM_LABELS(pi); + label_pnt = num_labels ? &pi->extra->labels->label[0] + : NULL; + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, pdest_p->u.val, 8); + + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + if (bgp_debug_update(peer, p, NULL, 1)) { + char pfx_buf[BGP_PRD_PATH_STRLEN]; + + bgp_debug_rdpfxpath2str( + AFI_L2VPN, SAFI_EVPN, &prd, + p, label_pnt, num_labels, + pi->addpath_rx_id ? 1 : 0, + pi->addpath_rx_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug( + "%s skip update of %s marked as removed", + peer->host, pfx_buf); + } + continue; + } + + memcpy(&evpn, bgp_attr_get_evpn_overlay(pi->attr), + sizeof(evpn)); + bgp_update(peer, p, pi->addpath_rx_id, pi->attr, + AFI_L2VPN, SAFI_EVPN, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, label_pnt, + num_labels, 1, evpn); + } + } +} + +static void bgp_mac_rescan_evpn_table(struct bgp *bgp, struct ethaddr *macaddr) +{ + struct listnode *node; + struct peer *peer; + safi_t safi; + afi_t afi; + + afi = AFI_L2VPN; + safi = SAFI_EVPN; + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + continue; + + if (!peer_established(peer->connection)) + continue; + + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "Processing EVPN MAC interface change on peer %s %s", + peer->host, + CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SOFT_RECONFIG) + ? "(inbound, soft-reconfig)" + : ""); + + if (!bgp_soft_reconfig_in(peer, afi, safi)) { + struct bgp_table *table = bgp->rib[afi][safi]; + + bgp_process_mac_rescan_table(bgp, peer, table, macaddr); + } + } +} + +static void bgp_mac_rescan_all_evpn_tables(struct ethaddr *macaddr) +{ + struct listnode *node; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + struct bgp_table *table = bgp->rib[AFI_L2VPN][SAFI_EVPN]; + + if (table) + bgp_mac_rescan_evpn_table(bgp, macaddr); + } +} + +static void bgp_mac_remove_ifp_internal(struct bgp_self_mac *bsm, char *ifname, + struct ethaddr *macaddr) +{ + struct listnode *node = NULL; + char *name; + + for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) { + if (strcmp(name, ifname) == 0) + break; + } + + if (node) { + list_delete_node(bsm->ifp_list, node); + XFREE(MTYPE_BSM_STRING, name); + } + + if (bsm->ifp_list->count == 0) { + struct ethaddr mac = *macaddr; + + hash_release(bm->self_mac_hash, bsm); + list_delete(&bsm->ifp_list); + XFREE(MTYPE_BSM, bsm); + + bgp_mac_rescan_all_evpn_tables(&mac); + } +} + +/* Add/Update entry of the 'bgp mac hash' table. + * A rescan of the EVPN tables is only needed if + * a new hash bucket is allocated. + * Learning an existing mac on a new interface (or + * having an existing mac move from one interface to + * another) does not result in changes to self mac + * state, so we shouldn't trigger a rescan. + */ +void bgp_mac_add_mac_entry(struct interface *ifp) +{ + struct bgp_self_mac lookup; + struct bgp_self_mac *bsm; + struct bgp_self_mac *old_bsm; + char *ifname; + bool mac_added = false; + + memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN); + bsm = hash_lookup(bm->self_mac_hash, &lookup); + if (!bsm) { + bsm = hash_get(bm->self_mac_hash, &lookup, bgp_mac_hash_alloc); + /* mac is new, rescan needs to be triggered */ + mac_added = true; + } + + /* + * Does this happen to be a move + */ + old_bsm = bgp_mac_find_interface_name(ifp->name); + ifname = XSTRDUP(MTYPE_BSM_STRING, ifp->name); + + if (bsm->ifp_list->count == 0) { + + listnode_add(bsm->ifp_list, ifname); + if (old_bsm) + bgp_mac_remove_ifp_internal(old_bsm, ifname, + &old_bsm->macaddr); + } else { + /* + * If old mac address is the same as the new, + * then there is nothing to do here + */ + if (old_bsm == bsm) { + XFREE(MTYPE_BSM_STRING, ifname); + return; + } + + if (old_bsm) + bgp_mac_remove_ifp_internal(old_bsm, ifp->name, + &old_bsm->macaddr); + + listnode_add(bsm->ifp_list, ifname); + } + + if (mac_added) + bgp_mac_rescan_all_evpn_tables(&bsm->macaddr); +} + +void bgp_mac_del_mac_entry(struct interface *ifp) +{ + struct bgp_self_mac lookup; + struct bgp_self_mac *bsm; + + memcpy(&lookup.macaddr, &ifp->hw_addr, ETH_ALEN); + bsm = hash_lookup(bm->self_mac_hash, &lookup); + if (!bsm) + return; + + /* + * Write code to allow old mac address to no-longer + * win if we happen to have received it from a peer. + */ + bgp_mac_remove_ifp_internal(bsm, ifp->name, &bsm->macaddr); +} + +/* This API checks MAC address against any of local + * assigned (SVIs) MAC address. + * An example: router-mac attribute in any of evpn update + * requires to compare against local mac. + */ +bool bgp_mac_exist(const struct ethaddr *mac) +{ + struct bgp_self_mac lookup; + struct bgp_self_mac *bsm; + static uint8_t tmp [ETHER_ADDR_STRLEN] = {0}; + + if (memcmp(mac, &tmp, ETH_ALEN) == 0) + return false; + + memcpy(&lookup.macaddr, mac, ETH_ALEN); + bsm = hash_lookup(bm->self_mac_hash, &lookup); + if (!bsm) + return false; + + return true; +} + +/* This API checks EVPN type-2 prefix and compares + * mac against any of local assigned (SVIs) MAC + * address. + */ +bool bgp_mac_entry_exists(const struct prefix *p) +{ + const struct prefix_evpn *pevpn = (const struct prefix_evpn *)p; + + if (pevpn->family != AF_EVPN) + return false; + + if (pevpn->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE) + return false; + + return bgp_mac_exist(&p->u.prefix_evpn.macip_addr.mac); +} + +static void bgp_mac_show_mac_entry(struct hash_bucket *bucket, void *arg) +{ + struct vty *vty = arg; + struct bgp_self_mac *bsm = bucket->data; + struct listnode *node; + char *name; + char buf_mac[ETHER_ADDR_STRLEN]; + + vty_out(vty, "Mac Address: %s ", + prefix_mac2str(&bsm->macaddr, buf_mac, sizeof(buf_mac))); + + for (ALL_LIST_ELEMENTS_RO(bsm->ifp_list, node, name)) + vty_out(vty, "%s ", name); + + vty_out(vty, "\n"); +} + +void bgp_mac_dump_table(struct vty *vty) +{ + hash_iterate(bm->self_mac_hash, bgp_mac_show_mac_entry, vty); +} diff --git a/bgpd/bgp_mac.h b/bgpd/bgp_mac.h new file mode 100644 index 0000000..d971561 --- /dev/null +++ b/bgpd/bgp_mac.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGPd - Mac hash header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __BGP_MAC_H__ +#define __BGP_MAC_H__ + +void bgp_mac_init(void); +void bgp_mac_finish(void); + +/* + * Functions to add/delete the mac entry from the appropriate + * bgp hash's. Additionally to do some additional processing + * to allow the win/loss to be processed. + */ +void bgp_mac_add_mac_entry(struct interface *ifp); +void bgp_mac_del_mac_entry(struct interface *ifp); + +void bgp_mac_dump_table(struct vty *vty); + +/* + * Function to lookup the prefix and see if we have a matching mac + */ +bool bgp_mac_entry_exists(const struct prefix *p); +bool bgp_mac_exist(const struct ethaddr *mac); + +#endif diff --git a/bgpd/bgp_main.c b/bgpd/bgp_main.c new file mode 100644 index 0000000..97658d3 --- /dev/null +++ b/bgpd/bgp_main.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Main routine of bgpd. + * Copyright (C) 1996, 97, 98, 1999 Kunihiro Ishiguro + */ + +#include + +#include +#include "vector.h" +#include "command.h" +#include "getopt.h" +#include "frrevent.h" +#include +#include "memory.h" +#include "prefix.h" +#include "log.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "routemap.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "queue.h" +#include "vrf.h" +#include "bfd.h" +#include "libfrr.h" +#include "ns.h" +#include "libagentx.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_keepalives.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_script.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_nhg.h" +#include "bgpd/bgp_routemap_nb.h" +#include "bgpd/bgp_community_alias.h" + +DEFINE_HOOK(bgp_hook_config_write_vrf, (struct vty *vty, struct vrf *vrf), + (vty, vrf)); + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#endif + +DEFINE_HOOK(bgp_hook_vrf_update, (struct vrf *vrf, bool enabled), + (vrf, enabled)); + +/* bgpd options, we use GNU getopt library. */ +static const struct option longopts[] = { + { "bgp_port", required_argument, NULL, 'p' }, + { "listenon", required_argument, NULL, 'l' }, + { "no_kernel", no_argument, NULL, 'n' }, + { "skip_runas", no_argument, NULL, 'S' }, + { "ecmp", required_argument, NULL, 'e' }, + { "int_num", required_argument, NULL, 'I' }, + { "no_zebra", no_argument, NULL, 'Z' }, + { "socket_size", required_argument, NULL, 's' }, + { "v6-with-v4-nexthops", no_argument, NULL, 'v' }, + { 0 } +}; + +/* signal definitions */ +void sighup(void); +void sigint(void); +void sigusr1(void); + +static void bgp_exit(int); +static void bgp_vrf_terminate(void); + +static struct frr_signal_t bgp_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +/* privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_NET_RAW, + ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN}; + +struct zebra_privs_t bgpd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +static struct frr_daemon_info bgpd_di; + +/* SIGHUP handler. */ +void sighup(void) +{ + zlog_info("SIGHUP received, ignoring"); + + return; + + /* + * This is turned off for the moment. There is all + * sorts of config turned off by bgp_terminate + * that is not setup properly again in bgp_reset. + * I see no easy way to do this nor do I see that + * this is a desirable way to reload config + * given the yang work. + */ + /* Terminate all thread. */ + /* + * bgp_terminate(); + * bgp_reset(); + * zlog_info("bgpd restarting!"); + + * Reload config file. + * vty_read_config(NULL, bgpd_di.config_file, config_default); + */ + /* Try to return to normal operation. */ +} + +/* SIGINT handler. */ +__attribute__((__noreturn__)) void sigint(void) +{ + zlog_notice("Terminating on signal"); + assert(bm->terminating == false); + bm->terminating = true; /* global flag that shutting down */ + + /* Disable BFD events to avoid wasting processing. */ + bfd_protocol_integration_set_shutdown(true); + + bgp_terminate(); + + bgp_exit(0); + + exit(0); +} + +/* SIGUSR1 handler. */ +void sigusr1(void) +{ + zlog_rotate(); +} + +/* + Try to free up allocations we know about so that diagnostic tools such as + valgrind are able to better illuminate leaks. + + Zebra route removal and protocol teardown are not meant to be done here. + For example, "retain_mode" may be set. +*/ +static __attribute__((__noreturn__)) void bgp_exit(int status) +{ + struct bgp *bgp, *bgp_default, *bgp_evpn; + struct listnode *node, *nnode; + + /* it only makes sense for this to be called on a clean exit */ + assert(status == 0); + + frr_early_fini(); + + bgp_close(); + + bgp_default = bgp_get_default(); + bgp_evpn = bgp_get_evpn(); + + /* reverse bgp_master_init */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp_default == bgp || bgp_evpn == bgp) + continue; + bgp_delete(bgp); + } + if (bgp_evpn && bgp_evpn != bgp_default) + bgp_delete(bgp_evpn); + if (bgp_default) + bgp_delete(bgp_default); + + bgp_evpn_mh_finish(); + bgp_nhg_finish(); + + zebra_announce_fini(&bm->zebra_announce_head); + + /* reverse bgp_dump_init */ + bgp_dump_finish(); + + /* BGP community aliases */ + bgp_community_alias_finish(); + + /* reverse bgp_route_init */ + bgp_route_finish(); + + /* cleanup route maps */ + bgp_route_map_terminate(); + + /* reverse bgp_attr_init */ + bgp_attr_finish(); + + /* reverse bgp_labels_init */ + bgp_labels_finish(); + + /* stop pthreads */ + bgp_pthreads_finish(); + + /* reverse access_list_init */ + access_list_add_hook(NULL); + access_list_delete_hook(NULL); + access_list_reset(); + + /* reverse bgp_filter_init */ + as_list_add_hook(NULL); + as_list_delete_hook(NULL); + bgp_filter_reset(); + + /* reverse prefix_list_init */ + prefix_list_add_hook(NULL); + prefix_list_delete_hook(NULL); + prefix_list_reset(); + + /* reverse community_list_init */ + community_list_terminate(bgp_clist); + + bgp_vrf_terminate(); +#ifdef ENABLE_BGP_VNC + vnc_zebra_destroy(); +#endif + bgp_zebra_destroy(); + + bf_free(bm->rd_idspace); + list_delete(&bm->bgp); + list_delete(&bm->addresses); + + bgp_lp_finish(); + + memset(bm, 0, sizeof(*bm)); + + frr_fini(); + exit(status); +} + +static int bgp_vrf_new(struct vrf *vrf) +{ + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); + + return 0; +} + +static int bgp_vrf_delete(struct vrf *vrf) +{ + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); + + return 0; +} + +static int bgp_vrf_enable(struct vrf *vrf) +{ + struct bgp *bgp; + vrf_id_t old_vrf_id; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("VRF enable add %s id %u", vrf->name, vrf->vrf_id); + + bgp = bgp_lookup_by_name(vrf->name); + if (bgp && bgp->vrf_id != vrf->vrf_id) { + old_vrf_id = bgp->vrf_id; + /* We have instance configured, link to VRF and make it "up". */ + bgp_vrf_link(bgp, vrf); + + bgp_handle_socket(bgp, vrf, old_vrf_id, true); + bgp_instance_up(bgp); + hook_call(bgp_hook_vrf_update, vrf, true); + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP6); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP6); + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, + bgp_get_default(), bgp); + vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP, + bgp_get_default(), bgp); + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, + bgp_get_default(), bgp); + vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP6, + bgp_get_default(), bgp); + } + + return 0; +} + +static int bgp_vrf_disable(struct vrf *vrf) +{ + struct bgp *bgp; + + if (vrf->vrf_id == VRF_DEFAULT) + return 0; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id); + + bgp = bgp_lookup_by_name(vrf->name); + if (bgp) { + + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP6); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, + bgp_get_default(), bgp); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP, + bgp_get_default(), bgp); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, + bgp_get_default(), bgp); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, AFI_IP6, + bgp_get_default(), bgp); + + bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, false); + /* We have instance configured, unlink from VRF and make it + * "down". */ + bgp_instance_down(bgp); + bgp_vrf_unlink(bgp, vrf); + hook_call(bgp_hook_vrf_update, vrf, false); + } + + /* Note: This is a callback, the VRF will be deleted by the caller. */ + return 0; +} + +static int bgp_vrf_config_write(struct vty *vty) +{ + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (vrf->vrf_id == VRF_DEFAULT) { + vty_out(vty, "!\n"); + continue; + } + vty_out(vty, "vrf %s\n", vrf->name); + + hook_call(bgp_hook_config_write_vrf, vty, vrf); + + vty_out(vty, "exit-vrf\n!\n"); + } + + return 0; +} + +static void bgp_vrf_init(void) +{ + vrf_init(bgp_vrf_new, bgp_vrf_enable, bgp_vrf_disable, bgp_vrf_delete); + vrf_cmd_init(bgp_vrf_config_write); +} + +static void bgp_vrf_terminate(void) +{ + vrf_terminate(); +} + +static const struct frr_yang_module_info *const bgpd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &frr_bgp_route_map_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(bgpd, BGP, + .vty_port = BGP_VTY_PORT, + .proghelp = "Implementation of the BGP routing protocol.", + + .signals = bgp_signals, + .n_signals = array_size(bgp_signals), + + .privs = &bgpd_privs, + + .yang_modules = bgpd_yang_modules, + .n_yang_modules = array_size(bgpd_yang_modules), +); +/* clang-format on */ + +#define DEPRECATED_OPTIONS "" + +/* Main routine of bgpd. Treatment of argument and start bgp finite + state machine is handled at here. */ +int main(int argc, char **argv) +{ + int opt; + int tmp_port; + + int bgp_port = BGP_PORT_DEFAULT; + struct list *addresses = list_new(); + int no_fib_flag = 0; + int no_zebra_flag = 0; + int skip_runas = 0; + int instance = 0; + int buffer_size = BGP_SOCKET_SNDBUF_SIZE; + char *address; + struct listnode *node; + + addresses->cmp = (int (*)(void *, void *))strcmp; + + frr_preinit(&bgpd_di, argc, argv); + frr_opt_add("p:l:SnZe:I:s:" DEPRECATED_OPTIONS, longopts, + " -p, --bgp_port Set BGP listen port number (0 means do not listen).\n" + " -l, --listenon Listen on specified address (implies -n)\n" + " -n, --no_kernel Do not install route to kernel.\n" + " -Z, --no_zebra Do not communicate with Zebra.\n" + " -S, --skip_runas Skip capabilities checks, and changing user and group IDs.\n" + " -e, --ecmp Specify ECMP to use.\n" + " -I, --int_num Set instance number (label-manager)\n" + " -s, --socket_size Set BGP peer socket send buffer size\n" + " , --v6-with-v4-nexthop Allow BGP to form v6 neighbors using v4 nexthops\n"); + + /* Command line argument treatment. */ + while (1) { + opt = frr_getopt(argc, argv, 0); + + if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { + fprintf(stderr, + "The -%c option no longer exists.\nPlease refer to the manual.\n", + opt); + continue; + } + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'p': + tmp_port = atoi(optarg); + if (tmp_port < 0 || tmp_port > 0xffff) + bgp_port = BGP_PORT_DEFAULT; + else + bgp_port = tmp_port; + break; + case 'e': { + unsigned long int parsed_multipath = + strtoul(optarg, NULL, 10); + if (parsed_multipath == 0 + || parsed_multipath > MULTIPATH_NUM + || parsed_multipath > UINT_MAX) { + flog_err( + EC_BGP_MULTIPATH, + "Multipath Number specified must be less than %u and greater than 0", + MULTIPATH_NUM); + return 1; + } + multipath_num = parsed_multipath; + break; + } + case 'l': + listnode_add_sort_nodup(addresses, optarg); + break; + case 'n': + no_fib_flag = 1; + break; + case 'Z': + no_zebra_flag = 1; + break; + case 'S': + skip_runas = 1; + break; + case 'I': + instance = atoi(optarg); + if (instance > (unsigned short)-1) + zlog_err("Instance %i out of range (0..%u)", + instance, (unsigned short)-1); + break; + case 's': + buffer_size = atoi(optarg); + break; + case 'v': + bm->v6_with_v4_nexthops = true; + break; + default: + frr_help_exit(1); + } + } + if (skip_runas) + memset(&bgpd_privs, 0, sizeof(bgpd_privs)); + + /* BGP master init. */ + bgp_master_init(frr_init(), buffer_size, addresses); + bm->port = bgp_port; + if (bgp_port == 0) + bgp_option_set(BGP_OPT_NO_LISTEN); + if (no_fib_flag || no_zebra_flag) + bgp_option_set(BGP_OPT_NO_FIB); + if (no_zebra_flag) + bgp_option_set(BGP_OPT_NO_ZEBRA); + bgp_error_init(); + /* Initializations. */ + libagentx_init(); + bgp_vrf_init(); + + +#ifdef HAVE_SCRIPTING + bgp_script_init(); +#endif + + /* BGP related initialization. */ + bgp_init((unsigned short)instance); + + if (list_isempty(bm->addresses)) { + snprintf(bgpd_di.startinfo, sizeof(bgpd_di.startinfo), + ", bgp@:%d", bm->port); + } else { + for (ALL_LIST_ELEMENTS_RO(bm->addresses, node, address)) + snprintf(bgpd_di.startinfo + strlen(bgpd_di.startinfo), + sizeof(bgpd_di.startinfo) + - strlen(bgpd_di.startinfo), + ", bgp@%s:%d", address, bm->port); + } + + bgp_if_init(); + + frr_config_fork(); + /* must be called after fork() */ + bgp_gr_apply_running_config(); + bgp_pthreads_run(); + frr_run(bm->master); + + /* Not reached. */ + return 0; +} diff --git a/bgpd/bgp_memory.c b/bgpd/bgp_memory.c new file mode 100644 index 0000000..c1804fb --- /dev/null +++ b/bgpd/bgp_memory.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* bgpd memory type definitions + * + * Copyright (C) 2015 David Lamparter + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bgp_memory.h" + +/* this file is temporary in nature; definitions should be moved to the + * files they're used in */ + +DEFINE_MGROUP(BGPD, "bgpd"); +DEFINE_MTYPE(BGPD, BGP, "BGP instance"); +DEFINE_MTYPE(BGPD, BGP_NAME, "BGP Name data"); +DEFINE_MTYPE(BGPD, BGP_LISTENER, "BGP listen socket details"); +DEFINE_MTYPE(BGPD, BGP_PEER, "BGP peer"); +DEFINE_MTYPE(BGPD, BGP_PEER_CONNECTION, "BGP peer connection"); +DEFINE_MTYPE(BGPD, BGP_PEER_HOST, "BGP peer hostname"); +DEFINE_MTYPE(BGPD, BGP_PEER_IFNAME, "BGP peer ifname"); +DEFINE_MTYPE(BGPD, PEER_GROUP, "Peer group"); +DEFINE_MTYPE(BGPD, PEER_GROUP_HOST, "BGP Peer group hostname"); +DEFINE_MTYPE(BGPD, PEER_DESC, "Peer description"); +DEFINE_MTYPE(BGPD, PEER_PASSWORD, "Peer password string"); +DEFINE_MTYPE(BGPD, BGP_PEER_AF, "BGP peer af"); +DEFINE_MTYPE(BGPD, BGP_UPDGRP, "BGP update group"); +DEFINE_MTYPE(BGPD, BGP_UPD_SUBGRP, "BGP update subgroup"); +DEFINE_MTYPE(BGPD, BGP_PACKET, "BGP packet"); +DEFINE_MTYPE(BGPD, ATTR, "BGP attribute"); +DEFINE_MTYPE(BGPD, AS_PATH, "BGP aspath"); +DEFINE_MTYPE(BGPD, AS_SEG, "BGP aspath seg"); +DEFINE_MTYPE(BGPD, AS_SEG_DATA, "BGP aspath segment data"); +DEFINE_MTYPE(BGPD, AS_STR, "BGP aspath str"); + +DEFINE_MTYPE(BGPD, BGP_TABLE, "BGP table"); +DEFINE_MTYPE(BGPD, BGP_NODE, "BGP node"); +DEFINE_MTYPE(BGPD, BGP_ROUTE, "BGP route"); +DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA, "BGP ancillary route info"); +DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA_EVPN, "BGP extra info for EVPN"); +DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA_FS, "BGP extra info for flowspec"); +DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA_VRFLEAK, "BGP extra info for vrf leaking"); +DEFINE_MTYPE(BGPD, BGP_ROUTE_EXTRA_VNC, "BGP extra info for vnc"); +DEFINE_MTYPE(BGPD, BGP_CONN, "BGP connected"); +DEFINE_MTYPE(BGPD, BGP_STATIC, "BGP static"); +DEFINE_MTYPE(BGPD, BGP_ADVERTISE_ATTR, "BGP adv attr"); +DEFINE_MTYPE(BGPD, BGP_ADVERTISE, "BGP adv"); +DEFINE_MTYPE(BGPD, BGP_SYNCHRONISE, "BGP synchronise"); +DEFINE_MTYPE(BGPD, BGP_ADJ_IN, "BGP adj in"); +DEFINE_MTYPE(BGPD, BGP_ADJ_OUT, "BGP adj out"); +DEFINE_MTYPE(BGPD, BGP_MPATH_INFO, "BGP multipath info"); + +DEFINE_MTYPE(BGPD, AS_LIST, "BGP AS list"); +DEFINE_MTYPE(BGPD, AS_FILTER, "BGP AS filter"); +DEFINE_MTYPE(BGPD, AS_FILTER_STR, "BGP AS filter str"); + +DEFINE_MTYPE(BGPD, COMMUNITY_ALIAS, "community alias"); + +DEFINE_MTYPE(BGPD, COMMUNITY, "community"); +DEFINE_MTYPE(BGPD, COMMUNITY_VAL, "community val"); +DEFINE_MTYPE(BGPD, COMMUNITY_STR, "community str"); + +DEFINE_MTYPE(BGPD, ECOMMUNITY, "extcommunity"); +DEFINE_MTYPE(BGPD, ECOMMUNITY_VAL, "extcommunity val"); +DEFINE_MTYPE(BGPD, ECOMMUNITY_STR, "extcommunity str"); + +DEFINE_MTYPE(BGPD, COMMUNITY_LIST, "community-list"); +DEFINE_MTYPE(BGPD, COMMUNITY_LIST_NAME, "community-list name"); +DEFINE_MTYPE(BGPD, COMMUNITY_LIST_ENTRY, "community-list entry"); +DEFINE_MTYPE(BGPD, COMMUNITY_LIST_CONFIG, "community-list config"); +DEFINE_MTYPE(BGPD, COMMUNITY_LIST_HANDLER, "community-list handler"); + +DEFINE_MTYPE(BGPD, CLUSTER, "Cluster list"); +DEFINE_MTYPE(BGPD, CLUSTER_VAL, "Cluster list val"); + +DEFINE_MTYPE(BGPD, BGP_PROCESS_QUEUE, "BGP Process queue"); +DEFINE_MTYPE(BGPD, BGP_CLEAR_NODE_QUEUE, "BGP node clear queue"); + +DEFINE_MTYPE(BGPD, TRANSIT, "BGP transit attr"); +DEFINE_MTYPE(BGPD, TRANSIT_VAL, "BGP transit val"); + +DEFINE_MTYPE(BGPD, BGP_DEBUG_FILTER, "BGP debug filter"); +DEFINE_MTYPE(BGPD, BGP_DEBUG_STR, "BGP debug filter string"); + +DEFINE_MTYPE(BGPD, BGP_DISTANCE, "BGP distance"); +DEFINE_MTYPE(BGPD, BGP_NEXTHOP_CACHE, "BGP nexthop"); +DEFINE_MTYPE(BGPD, BGP_CONFED_LIST, "BGP confed list"); +DEFINE_MTYPE(BGPD, PEER_UPDATE_SOURCE, "BGP peer update interface"); +DEFINE_MTYPE(BGPD, PEER_CONF_IF, "BGP peer config interface"); +DEFINE_MTYPE(BGPD, BGP_DAMP_INFO, "Dampening info"); +DEFINE_MTYPE(BGPD, BGP_DAMP_ARRAY, "BGP Dampening array"); +DEFINE_MTYPE(BGPD, BGP_DAMP_REUSELIST, "BGP Dampening reuse list"); +DEFINE_MTYPE(BGPD, BGP_REGEXP, "BGP regexp"); +DEFINE_MTYPE(BGPD, BGP_AGGREGATE, "BGP aggregate"); +DEFINE_MTYPE(BGPD, BGP_ADDR, "BGP own address"); +DEFINE_MTYPE(BGPD, TIP_ADDR, "BGP own tunnel-ip address"); + +DEFINE_MTYPE(BGPD, BGP_REDIST, "BGP redistribution"); +DEFINE_MTYPE(BGPD, BGP_FILTER_NAME, "BGP Filter Information"); +DEFINE_MTYPE(BGPD, BGP_DUMP_STR, "BGP Dump String Information"); +DEFINE_MTYPE(BGPD, ENCAP_TLV, "ENCAP TLV"); + +DEFINE_MTYPE(BGPD, BGP_LABELS, "BGP LABELS"); + +DEFINE_MTYPE(BGPD, BGP_TEA_OPTIONS, "BGP TEA Options"); +DEFINE_MTYPE(BGPD, BGP_TEA_OPTIONS_VALUE, "BGP TEA Options Value"); + +DEFINE_MTYPE(BGPD, LCOMMUNITY, "Large Community"); +DEFINE_MTYPE(BGPD, LCOMMUNITY_STR, "Large Community display string"); +DEFINE_MTYPE(BGPD, LCOMMUNITY_VAL, "Large Community value"); + +DEFINE_MTYPE(BGPD, BGP_EVPN, "BGP EVPN Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_MH_INFO, "BGP EVPN MH Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES_VTEP, "BGP EVPN ES VTEP"); +DEFINE_MTYPE(BGPD, BGP_EVPN_PATH_MH_INFO, "BGP EVPN PATH MH Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_PATH_ES_INFO, "BGP EVPN PATH ES Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_PATH_NH_INFO, "BGP EVPN PATH NH Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_NH, "BGP EVPN Nexthop"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES_EVI_VTEP, "BGP EVPN ES-EVI VTEP"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES, "BGP EVPN ESI Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES_FRAG, "BGP EVPN ES Fragment Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES_EVI, "BGP EVPN ES-per-EVI Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_ES_VRF, "BGP EVPN ES-per-VRF Information"); +DEFINE_MTYPE(BGPD, BGP_EVPN_IMPORT_RT, "BGP EVPN Import RT"); +DEFINE_MTYPE(BGPD, BGP_EVPN_VRF_IMPORT_RT, "BGP EVPN VRF Import RT"); + +DEFINE_MTYPE(BGPD, BGP_SRV6_L3VPN, "BGP prefix-sid srv6 l3vpn servcie"); +DEFINE_MTYPE(BGPD, BGP_SRV6_VPN, "BGP prefix-sid srv6 vpn service"); +DEFINE_MTYPE(BGPD, BGP_SRV6_SID, "BGP srv6 segment-id"); +DEFINE_MTYPE(BGPD, BGP_SRV6_FUNCTION, "BGP srv6 function"); +DEFINE_MTYPE(BGPD, EVPN_REMOTE_IP, "BGP EVPN Remote IP hash entry"); + +DEFINE_MTYPE(BGPD, BGP_NOTIFICATION, "BGP Notification Message"); + +DEFINE_MTYPE(BGPD, BGP_SOFT_VERSION, "Software Version"); diff --git a/bgpd/bgp_memory.h b/bgpd/bgp_memory.h new file mode 100644 index 0000000..4ae49a2 --- /dev/null +++ b/bgpd/bgp_memory.h @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* bgpd memory type declarations + * + * Copyright (C) 2015 David Lamparter + */ + +#ifndef _QUAGGA_BGP_MEMORY_H +#define _QUAGGA_BGP_MEMORY_H + +#include "memory.h" + +DECLARE_MGROUP(BGPD); +DECLARE_MTYPE(BGP); +DECLARE_MTYPE(BGP_NAME); +DECLARE_MTYPE(BGP_LISTENER); +DECLARE_MTYPE(BGP_PEER); +DECLARE_MTYPE(BGP_PEER_CONNECTION); +DECLARE_MTYPE(BGP_PEER_HOST); +DECLARE_MTYPE(BGP_PEER_IFNAME); +DECLARE_MTYPE(PEER_GROUP); +DECLARE_MTYPE(PEER_GROUP_HOST); +DECLARE_MTYPE(PEER_DESC); +DECLARE_MTYPE(PEER_PASSWORD); +DECLARE_MTYPE(BGP_PEER_AF); +DECLARE_MTYPE(BGP_UPDGRP); +DECLARE_MTYPE(BGP_UPD_SUBGRP); +DECLARE_MTYPE(BGP_PACKET); +DECLARE_MTYPE(ATTR); +DECLARE_MTYPE(AS_PATH); +DECLARE_MTYPE(AS_SEG); +DECLARE_MTYPE(AS_SEG_DATA); +DECLARE_MTYPE(AS_STR); + +DECLARE_MTYPE(BGP_TABLE); +DECLARE_MTYPE(BGP_NODE); +DECLARE_MTYPE(BGP_ROUTE); +DECLARE_MTYPE(BGP_ROUTE_EXTRA); +DECLARE_MTYPE(BGP_ROUTE_EXTRA_EVPN); +DECLARE_MTYPE(BGP_ROUTE_EXTRA_FS); +DECLARE_MTYPE(BGP_ROUTE_EXTRA_VRFLEAK); +DECLARE_MTYPE(BGP_ROUTE_EXTRA_VNC); +DECLARE_MTYPE(BGP_CONN); +DECLARE_MTYPE(BGP_STATIC); +DECLARE_MTYPE(BGP_ADVERTISE_ATTR); +DECLARE_MTYPE(BGP_ADVERTISE); +DECLARE_MTYPE(BGP_SYNCHRONISE); +DECLARE_MTYPE(BGP_ADJ_IN); +DECLARE_MTYPE(BGP_ADJ_OUT); +DECLARE_MTYPE(BGP_MPATH_INFO); + +DECLARE_MTYPE(AS_LIST); +DECLARE_MTYPE(AS_FILTER); +DECLARE_MTYPE(AS_FILTER_STR); + +DECLARE_MTYPE(COMMUNITY_ALIAS); + +DECLARE_MTYPE(COMMUNITY); +DECLARE_MTYPE(COMMUNITY_VAL); +DECLARE_MTYPE(COMMUNITY_STR); + +DECLARE_MTYPE(ECOMMUNITY); +DECLARE_MTYPE(ECOMMUNITY_VAL); +DECLARE_MTYPE(ECOMMUNITY_STR); + +DECLARE_MTYPE(COMMUNITY_LIST); +DECLARE_MTYPE(COMMUNITY_LIST_NAME); +DECLARE_MTYPE(COMMUNITY_LIST_ENTRY); +DECLARE_MTYPE(COMMUNITY_LIST_CONFIG); +DECLARE_MTYPE(COMMUNITY_LIST_HANDLER); + +DECLARE_MTYPE(CLUSTER); +DECLARE_MTYPE(CLUSTER_VAL); + +DECLARE_MTYPE(BGP_PROCESS_QUEUE); +DECLARE_MTYPE(BGP_CLEAR_NODE_QUEUE); + +DECLARE_MTYPE(TRANSIT); +DECLARE_MTYPE(TRANSIT_VAL); + +DECLARE_MTYPE(BGP_DEBUG_FILTER); +DECLARE_MTYPE(BGP_DEBUG_STR); + +DECLARE_MTYPE(BGP_DISTANCE); +DECLARE_MTYPE(BGP_NEXTHOP_CACHE); +DECLARE_MTYPE(BGP_CONFED_LIST); +DECLARE_MTYPE(PEER_UPDATE_SOURCE); +DECLARE_MTYPE(PEER_CONF_IF); +DECLARE_MTYPE(BGP_DAMP_INFO); +DECLARE_MTYPE(BGP_DAMP_ARRAY); +DECLARE_MTYPE(BGP_DAMP_REUSELIST); +DECLARE_MTYPE(BGP_REGEXP); +DECLARE_MTYPE(BGP_AGGREGATE); +DECLARE_MTYPE(BGP_ADDR); +DECLARE_MTYPE(TIP_ADDR); + +DECLARE_MTYPE(BGP_REDIST); +DECLARE_MTYPE(BGP_FILTER_NAME); +DECLARE_MTYPE(BGP_DUMP_STR); +DECLARE_MTYPE(ENCAP_TLV); + +DECLARE_MTYPE(BGP_LABELS); + +DECLARE_MTYPE(BGP_TEA_OPTIONS); +DECLARE_MTYPE(BGP_TEA_OPTIONS_VALUE); + +DECLARE_MTYPE(LCOMMUNITY); +DECLARE_MTYPE(LCOMMUNITY_STR); +DECLARE_MTYPE(LCOMMUNITY_VAL); + +DECLARE_MTYPE(BGP_EVPN_MH_INFO); +DECLARE_MTYPE(BGP_EVPN_ES); +DECLARE_MTYPE(BGP_EVPN_ES_FRAG); +DECLARE_MTYPE(BGP_EVPN_ES_EVI); +DECLARE_MTYPE(BGP_EVPN_ES_VRF); +DECLARE_MTYPE(BGP_EVPN_ES_VTEP); +DECLARE_MTYPE(BGP_EVPN_PATH_ES_INFO); +DECLARE_MTYPE(BGP_EVPN_PATH_MH_INFO); +DECLARE_MTYPE(BGP_EVPN_PATH_NH_INFO); +DECLARE_MTYPE(BGP_EVPN_NH); +DECLARE_MTYPE(BGP_EVPN_ES_EVI_VTEP); + +DECLARE_MTYPE(BGP_EVPN); +DECLARE_MTYPE(BGP_EVPN_IMPORT_RT); +DECLARE_MTYPE(BGP_EVPN_VRF_IMPORT_RT); + +DECLARE_MTYPE(BGP_SRV6_L3VPN); +DECLARE_MTYPE(BGP_SRV6_VPN); +DECLARE_MTYPE(BGP_SRV6_SID); +DECLARE_MTYPE(BGP_SRV6_FUNCTION); + +DECLARE_MTYPE(EVPN_REMOTE_IP); + +DECLARE_MTYPE(BGP_NOTIFICATION); + +DECLARE_MTYPE(BGP_SOFT_VERSION); + +#endif /* _QUAGGA_BGP_MEMORY_H */ diff --git a/bgpd/bgp_mpath.c b/bgpd/bgp_mpath.c new file mode 100644 index 0000000..e12d84b --- /dev/null +++ b/bgpd/bgp_mpath.c @@ -0,0 +1,924 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Multipath + * Copyright (C) 2010 Google Inc. + * + * This file is part of Quagga + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "linklist.h" +#include "sockunion.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_mpath.h" + +/* + * bgp_maximum_paths_set + * + * Record maximum-paths configuration for BGP instance + */ +int bgp_maximum_paths_set(struct bgp *bgp, afi_t afi, safi_t safi, int peertype, + uint16_t maxpaths, bool same_clusterlen) +{ + if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX)) + return -1; + + switch (peertype) { + case BGP_PEER_IBGP: + bgp->maxpaths[afi][safi].maxpaths_ibgp = maxpaths; + bgp->maxpaths[afi][safi].same_clusterlen = same_clusterlen; + break; + case BGP_PEER_EBGP: + bgp->maxpaths[afi][safi].maxpaths_ebgp = maxpaths; + break; + default: + return -1; + } + + return 0; +} + +/* + * bgp_maximum_paths_unset + * + * Remove maximum-paths configuration from BGP instance + */ +int bgp_maximum_paths_unset(struct bgp *bgp, afi_t afi, safi_t safi, + int peertype) +{ + if (!bgp || (afi >= AFI_MAX) || (safi >= SAFI_MAX)) + return -1; + + switch (peertype) { + case BGP_PEER_IBGP: + bgp->maxpaths[afi][safi].maxpaths_ibgp = multipath_num; + bgp->maxpaths[afi][safi].same_clusterlen = false; + break; + case BGP_PEER_EBGP: + bgp->maxpaths[afi][safi].maxpaths_ebgp = multipath_num; + break; + default: + return -1; + } + + return 0; +} + +/* + * bgp_interface_same + * + * Return true if ifindex for ifp1 and ifp2 are the same, else return false. + */ +static int bgp_interface_same(struct interface *ifp1, struct interface *ifp2) +{ + if (!ifp1 && !ifp2) + return 1; + + if (!ifp1 && ifp2) + return 0; + + if (ifp1 && !ifp2) + return 0; + + return (ifp1->ifindex == ifp2->ifindex); +} + + +/* + * bgp_path_info_nexthop_cmp + * + * Compare the nexthops of two paths. Return value is less than, equal to, + * or greater than zero if bpi1 is respectively less than, equal to, + * or greater than bpi2. + */ +int bgp_path_info_nexthop_cmp(struct bgp_path_info *bpi1, + struct bgp_path_info *bpi2) +{ + int compare; + struct in6_addr addr1, addr2; + + compare = IPV4_ADDR_CMP(&bpi1->attr->nexthop, &bpi2->attr->nexthop); + if (!compare) { + if (bpi1->attr->mp_nexthop_len == bpi2->attr->mp_nexthop_len) { + switch (bpi1->attr->mp_nexthop_len) { + case BGP_ATTR_NHLEN_IPV4: + case BGP_ATTR_NHLEN_VPNV4: + compare = IPV4_ADDR_CMP( + &bpi1->attr->mp_nexthop_global_in, + &bpi2->attr->mp_nexthop_global_in); + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + case BGP_ATTR_NHLEN_VPNV6_GLOBAL: + compare = IPV6_ADDR_CMP( + &bpi1->attr->mp_nexthop_global, + &bpi2->attr->mp_nexthop_global); + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + addr1 = (CHECK_FLAG(bpi1->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + ? bpi1->attr->mp_nexthop_global + : bpi1->attr->mp_nexthop_local; + addr2 = (CHECK_FLAG(bpi2->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + ? bpi2->attr->mp_nexthop_global + : bpi2->attr->mp_nexthop_local; + + if (!CHECK_FLAG(bpi1->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL) && + !CHECK_FLAG(bpi2->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + compare = !bgp_interface_same( + bpi1->peer->ifp, + bpi2->peer->ifp); + + if (!compare) + compare = IPV6_ADDR_CMP(&addr1, &addr2); + break; + } + } + + /* This can happen if one IPv6 peer sends you global and + * link-local + * nexthops but another IPv6 peer only sends you global + */ + else if (bpi1->attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL + || bpi1->attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + compare = IPV6_ADDR_CMP(&bpi1->attr->mp_nexthop_global, + &bpi2->attr->mp_nexthop_global); + if (!compare) { + if (bpi1->attr->mp_nexthop_len + < bpi2->attr->mp_nexthop_len) + compare = -1; + else + compare = 1; + } + } + } + + /* + * If both nexthops are same then check + * if they belong to same VRF + */ + if (!compare && bpi1->attr->nh_type != NEXTHOP_TYPE_BLACKHOLE) { + if (bpi1->extra && bpi1->extra->vrfleak && + bpi1->extra->vrfleak->bgp_orig && bpi2->extra && + bpi2->extra->vrfleak && bpi2->extra->vrfleak->bgp_orig) { + if (bpi1->extra->vrfleak->bgp_orig->vrf_id != + bpi2->extra->vrfleak->bgp_orig->vrf_id) { + compare = 1; + } + } + } + + return compare; +} + +/* + * bgp_path_info_mpath_cmp + * + * This function determines our multipath list ordering. By ordering + * the list we can deterministically select which paths are included + * in the multipath set. The ordering also helps in detecting changes + * in the multipath selection so we can detect whether to send an + * update to zebra. + * + * The order of paths is determined first by received nexthop, and then + * by peer address if the nexthops are the same. + */ +static int bgp_path_info_mpath_cmp(void *val1, void *val2) +{ + struct bgp_path_info *bpi1, *bpi2; + int compare; + + bpi1 = val1; + bpi2 = val2; + + compare = bgp_path_info_nexthop_cmp(bpi1, bpi2); + + if (!compare) { + if (!bpi1->peer->su_remote && !bpi2->peer->su_remote) + compare = 0; + else if (!bpi1->peer->su_remote) + compare = 1; + else if (!bpi2->peer->su_remote) + compare = -1; + else + compare = sockunion_cmp(bpi1->peer->su_remote, + bpi2->peer->su_remote); + } + + return compare; +} + +/* + * bgp_mp_list_init + * + * Initialize the mp_list, which holds the list of multipaths + * selected by bgp_best_selection + */ +void bgp_mp_list_init(struct list *mp_list) +{ + assert(mp_list); + memset(mp_list, 0, sizeof(struct list)); + mp_list->cmp = bgp_path_info_mpath_cmp; +} + +/* + * bgp_mp_list_clear + * + * Clears all entries out of the mp_list + */ +void bgp_mp_list_clear(struct list *mp_list) +{ + assert(mp_list); + list_delete_all_node(mp_list); +} + +/* + * bgp_mp_list_add + * + * Adds a multipath entry to the mp_list + */ +void bgp_mp_list_add(struct list *mp_list, struct bgp_path_info *mpinfo) +{ + assert(mp_list && mpinfo); + listnode_add_sort(mp_list, mpinfo); +} + +/* + * bgp_path_info_mpath_new + * + * Allocate and zero memory for a new bgp_path_info_mpath element + */ +static struct bgp_path_info_mpath *bgp_path_info_mpath_new(void) +{ + struct bgp_path_info_mpath *new_mpath; + + new_mpath = XCALLOC(MTYPE_BGP_MPATH_INFO, + sizeof(struct bgp_path_info_mpath)); + + return new_mpath; +} + +/* + * bgp_path_info_mpath_free + * + * Release resources for a bgp_path_info_mpath element and zero out pointer + */ +void bgp_path_info_mpath_free(struct bgp_path_info_mpath **mpath) +{ + if (mpath && *mpath) { + if ((*mpath)->mp_attr) + bgp_attr_unintern(&(*mpath)->mp_attr); + XFREE(MTYPE_BGP_MPATH_INFO, *mpath); + } +} + +/* + * bgp_path_info_mpath_get + * + * Fetch the mpath element for the given bgp_path_info. Used for + * doing lazy allocation. + */ +static struct bgp_path_info_mpath * +bgp_path_info_mpath_get(struct bgp_path_info *path) +{ + struct bgp_path_info_mpath *mpath; + + if (!path) + return NULL; + + if (!path->mpath) { + mpath = bgp_path_info_mpath_new(); + path->mpath = mpath; + mpath->mp_info = path; + } + return path->mpath; +} + +/* + * bgp_path_info_mpath_enqueue + * + * Enqueue a path onto the multipath list given the previous multipath + * list entry + */ +static void bgp_path_info_mpath_enqueue(struct bgp_path_info *prev_info, + struct bgp_path_info *path) +{ + struct bgp_path_info_mpath *prev, *mpath; + + prev = bgp_path_info_mpath_get(prev_info); + mpath = bgp_path_info_mpath_get(path); + if (!prev || !mpath) + return; + + mpath->mp_next = prev->mp_next; + mpath->mp_prev = prev; + if (prev->mp_next) + prev->mp_next->mp_prev = mpath; + prev->mp_next = mpath; + + SET_FLAG(path->flags, BGP_PATH_MULTIPATH); +} + +/* + * bgp_path_info_mpath_dequeue + * + * Remove a path from the multipath list + */ +void bgp_path_info_mpath_dequeue(struct bgp_path_info *path) +{ + struct bgp_path_info_mpath *mpath = path->mpath; + if (!mpath) + return; + if (mpath->mp_prev) + mpath->mp_prev->mp_next = mpath->mp_next; + if (mpath->mp_next) + mpath->mp_next->mp_prev = mpath->mp_prev; + mpath->mp_next = mpath->mp_prev = NULL; + UNSET_FLAG(path->flags, BGP_PATH_MULTIPATH); +} + +/* + * bgp_path_info_mpath_next + * + * Given a bgp_path_info, return the next multipath entry + */ +struct bgp_path_info *bgp_path_info_mpath_next(struct bgp_path_info *path) +{ + if (!path->mpath || !path->mpath->mp_next) + return NULL; + return path->mpath->mp_next->mp_info; +} + +/* + * bgp_path_info_mpath_first + * + * Given bestpath bgp_path_info, return the first multipath entry. + */ +struct bgp_path_info *bgp_path_info_mpath_first(struct bgp_path_info *path) +{ + return bgp_path_info_mpath_next(path); +} + +/* + * bgp_path_info_mpath_count + * + * Given the bestpath bgp_path_info, return the number of multipath entries + */ +uint32_t bgp_path_info_mpath_count(struct bgp_path_info *path) +{ + if (!path->mpath) + return 0; + return path->mpath->mp_count; +} + +/* + * bgp_path_info_mpath_count_set + * + * Sets the count of multipaths into bestpath's mpath element + */ +static void bgp_path_info_mpath_count_set(struct bgp_path_info *path, + uint16_t count) +{ + struct bgp_path_info_mpath *mpath; + if (!count && !path->mpath) + return; + mpath = bgp_path_info_mpath_get(path); + if (!mpath) + return; + mpath->mp_count = count; +} + +/* + * bgp_path_info_mpath_lb_update + * + * Update cumulative info related to link-bandwidth + */ +static void bgp_path_info_mpath_lb_update(struct bgp_path_info *path, bool set, + bool all_paths_lb, uint64_t cum_bw) +{ + struct bgp_path_info_mpath *mpath; + + mpath = path->mpath; + if (mpath == NULL) { + if (!set || (cum_bw == 0 && !all_paths_lb)) + return; + + mpath = bgp_path_info_mpath_get(path); + if (!mpath) + return; + } + if (set) { + if (cum_bw) + SET_FLAG(mpath->mp_flags, BGP_MP_LB_PRESENT); + else + UNSET_FLAG(mpath->mp_flags, BGP_MP_LB_PRESENT); + if (all_paths_lb) + SET_FLAG(mpath->mp_flags, BGP_MP_LB_ALL); + else + UNSET_FLAG(mpath->mp_flags, BGP_MP_LB_ALL); + mpath->cum_bw = cum_bw; + } else { + mpath->mp_flags = 0; + mpath->cum_bw = 0; + } +} + +/* + * bgp_path_info_mpath_attr + * + * Given bestpath bgp_path_info, return aggregated attribute set used + * for advertising the multipath route + */ +struct attr *bgp_path_info_mpath_attr(struct bgp_path_info *path) +{ + if (!path->mpath) + return NULL; + return path->mpath->mp_attr; +} + +/* + * bgp_path_info_chkwtd + * + * Return if we should attempt to do weighted ECMP or not + * The path passed in is the bestpath. + */ +bool bgp_path_info_mpath_chkwtd(struct bgp *bgp, struct bgp_path_info *path) +{ + /* Check if told to ignore weights or not multipath */ + if (bgp->lb_handling == BGP_LINK_BW_IGNORE_BW || !path->mpath) + return false; + + /* All paths in multipath should have associated weight (bandwidth) + * unless told explicitly otherwise. + */ + if (bgp->lb_handling != BGP_LINK_BW_SKIP_MISSING && + bgp->lb_handling != BGP_LINK_BW_DEFWT_4_MISSING) + return (path->mpath->mp_flags & BGP_MP_LB_ALL); + + /* At least one path should have bandwidth. */ + return (path->mpath->mp_flags & BGP_MP_LB_PRESENT); +} + +/* + * bgp_path_info_mpath_attr + * + * Given bestpath bgp_path_info, return cumulative bandwidth + * computed for all multipaths with bandwidth info + */ +uint64_t bgp_path_info_mpath_cumbw(struct bgp_path_info *path) +{ + if (!path->mpath) + return 0; + return path->mpath->cum_bw; +} + +/* + * bgp_path_info_mpath_attr_set + * + * Sets the aggregated attribute into bestpath's mpath element + */ +static void bgp_path_info_mpath_attr_set(struct bgp_path_info *path, + struct attr *attr) +{ + struct bgp_path_info_mpath *mpath; + if (!attr && !path->mpath) + return; + mpath = bgp_path_info_mpath_get(path); + if (!mpath) + return; + mpath->mp_attr = attr; +} + +/* + * bgp_path_info_mpath_update + * + * Compare and sync up the multipath list with the mp_list generated by + * bgp_best_selection + */ +void bgp_path_info_mpath_update(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *new_best, + struct bgp_path_info *old_best, + struct list *mp_list, + struct bgp_maxpaths_cfg *mpath_cfg) +{ + uint16_t maxpaths, mpath_count, old_mpath_count; + uint64_t bwval; + uint64_t cum_bw, old_cum_bw; + struct listnode *mp_node, *mp_next_node; + struct bgp_path_info *cur_mpath, *new_mpath, *next_mpath, *prev_mpath; + int mpath_changed, debug; + bool all_paths_lb; + char path_buf[PATH_ADDPATH_STR_BUFFER]; + + mpath_changed = 0; + maxpaths = multipath_num; + mpath_count = 0; + cur_mpath = NULL; + old_mpath_count = 0; + old_cum_bw = cum_bw = 0; + prev_mpath = new_best; + mp_node = listhead(mp_list); + debug = bgp_debug_bestpath(dest); + + if (new_best) { + mpath_count++; + if (new_best != old_best) + bgp_path_info_mpath_dequeue(new_best); + maxpaths = (new_best->peer->sort == BGP_PEER_IBGP) + ? mpath_cfg->maxpaths_ibgp + : mpath_cfg->maxpaths_ebgp; + } + + if (old_best) { + cur_mpath = bgp_path_info_mpath_first(old_best); + old_mpath_count = bgp_path_info_mpath_count(old_best); + old_cum_bw = bgp_path_info_mpath_cumbw(old_best); + bgp_path_info_mpath_count_set(old_best, 0); + bgp_path_info_mpath_lb_update(old_best, false, false, 0); + bgp_path_info_mpath_dequeue(old_best); + } + + if (debug) + zlog_debug("%pBD(%s): starting mpath update, newbest %s num candidates %d old-mpath-count %d old-cum-bw %" PRIu64, + dest, bgp->name_pretty, + new_best ? new_best->peer->host : "NONE", + mp_list ? listcount(mp_list) : 0, old_mpath_count, + old_cum_bw); + + /* + * We perform an ordered walk through both lists in parallel. + * The reason for the ordered walk is that if there are paths + * that were previously multipaths and are still multipaths, the walk + * should encounter them in both lists at the same time. Otherwise + * there will be paths that are in one list or another, and we + * will deal with these separately. + * + * Note that new_best might be somewhere in the mp_list, so we need + * to skip over it + */ + all_paths_lb = true; /* We'll reset if any path doesn't have LB. */ + while (mp_node || cur_mpath) { + struct bgp_path_info *tmp_info; + + /* + * We can bail out of this loop if all existing paths on the + * multipath list have been visited (for cleanup purposes) and + * the maxpath requirement is fulfulled + */ + if (!cur_mpath && (mpath_count >= maxpaths)) + break; + + mp_next_node = mp_node ? listnextnode(mp_node) : NULL; + next_mpath = + cur_mpath ? bgp_path_info_mpath_next(cur_mpath) : NULL; + tmp_info = mp_node ? listgetdata(mp_node) : NULL; + + if (debug) + zlog_debug("%pBD(%s): comparing candidate %s with existing mpath %s", + dest, bgp->name_pretty, + tmp_info ? tmp_info->peer->host : "NONE", + cur_mpath ? cur_mpath->peer->host : "NONE"); + + /* + * If equal, the path was a multipath and is still a multipath. + * Insert onto new multipath list if maxpaths allows. + */ + if (mp_node && (listgetdata(mp_node) == cur_mpath)) { + list_delete_node(mp_list, mp_node); + bgp_path_info_mpath_dequeue(cur_mpath); + if ((mpath_count < maxpaths) + && prev_mpath + && bgp_path_info_nexthop_cmp(prev_mpath, + cur_mpath)) { + bgp_path_info_mpath_enqueue(prev_mpath, + cur_mpath); + prev_mpath = cur_mpath; + mpath_count++; + if (ecommunity_linkbw_present(bgp_attr_get_ecommunity( + cur_mpath->attr), + &bwval) || + ecommunity_linkbw_present( + bgp_attr_get_ipv6_ecommunity( + cur_mpath->attr), + &bwval)) + cum_bw += bwval; + else + all_paths_lb = false; + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + cur_mpath, path_buf, + sizeof(path_buf)); + zlog_debug("%pBD: %s is still multipath, cur count %d", + dest, path_buf, mpath_count); + } + } else { + mpath_changed = 1; + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + cur_mpath, path_buf, + sizeof(path_buf)); + zlog_debug("%pBD: remove mpath %s nexthop %pI4, cur count %d", + dest, path_buf, + &cur_mpath->attr->nexthop, + mpath_count); + } + } + mp_node = mp_next_node; + cur_mpath = next_mpath; + continue; + } + + if (cur_mpath + && (!mp_node + || (bgp_path_info_mpath_cmp(cur_mpath, + listgetdata(mp_node)) + < 0))) { + /* + * If here, we have an old multipath and either the + * mp_list + * is finished or the next mp_node points to a later + * multipath, so we need to purge this path from the + * multipath list + */ + bgp_path_info_mpath_dequeue(cur_mpath); + mpath_changed = 1; + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + cur_mpath, path_buf, sizeof(path_buf)); + zlog_debug("%pBD: remove mpath %s nexthop %pI4, cur count %d", + dest, path_buf, + &cur_mpath->attr->nexthop, + mpath_count); + } + cur_mpath = next_mpath; + } else { + /* + * If here, we have a path on the mp_list that was not + * previously + * a multipath (due to non-equivalance or maxpaths + * exceeded), + * or the matching multipath is sorted later in the + * multipath + * list. Before we enqueue the path on the new multipath + * list, + * make sure its not on the old_best multipath list or + * referenced + * via next_mpath: + * - If next_mpath points to this new path, update + * next_mpath to + * point to the multipath after this one + * - Dequeue the path from the multipath list just to + * make sure + */ + new_mpath = listgetdata(mp_node); + list_delete_node(mp_list, mp_node); + assert(new_mpath); + assert(prev_mpath); + if ((mpath_count < maxpaths) && (new_mpath != new_best) + && bgp_path_info_nexthop_cmp(prev_mpath, + new_mpath)) { + bgp_path_info_mpath_dequeue(new_mpath); + + bgp_path_info_mpath_enqueue(prev_mpath, + new_mpath); + prev_mpath = new_mpath; + mpath_changed = 1; + mpath_count++; + if (ecommunity_linkbw_present(bgp_attr_get_ecommunity( + new_mpath->attr), + &bwval) || + ecommunity_linkbw_present( + bgp_attr_get_ipv6_ecommunity( + new_mpath->attr), + &bwval)) + cum_bw += bwval; + else + all_paths_lb = false; + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + new_mpath, path_buf, + sizeof(path_buf)); + zlog_debug("%pBD: add mpath %s nexthop %pI4, cur count %d", + dest, path_buf, + &new_mpath->attr->nexthop, + mpath_count); + } + } + mp_node = mp_next_node; + } + } + + if (new_best) { + bgp_path_info_mpath_count_set(new_best, mpath_count - 1); + if (mpath_count <= 1 || + (!ecommunity_linkbw_present(bgp_attr_get_ecommunity( + new_best->attr), + &bwval) && + !ecommunity_linkbw_present(bgp_attr_get_ipv6_ecommunity( + new_best->attr), + &bwval))) + all_paths_lb = false; + else + cum_bw += bwval; + bgp_path_info_mpath_lb_update(new_best, true, + all_paths_lb, cum_bw); + + if (debug) + zlog_debug("%pBD(%s): New mpath count (incl newbest) %d mpath-change %s all_paths_lb %d cum_bw %" PRIu64, + dest, bgp->name_pretty, mpath_count, + mpath_changed ? "YES" : "NO", all_paths_lb, + cum_bw); + + if (mpath_changed + || (bgp_path_info_mpath_count(new_best) != old_mpath_count)) + SET_FLAG(new_best->flags, BGP_PATH_MULTIPATH_CHG); + if ((mpath_count - 1) != old_mpath_count || + old_cum_bw != cum_bw) + SET_FLAG(new_best->flags, BGP_PATH_LINK_BW_CHG); + } +} + +/* + * bgp_mp_dmed_deselect + * + * Clean up multipath information for BGP_PATH_DMED_SELECTED path that + * is not selected as best path + */ +void bgp_mp_dmed_deselect(struct bgp_path_info *dmed_best) +{ + struct bgp_path_info *mpinfo, *mpnext; + + if (!dmed_best) + return; + + for (mpinfo = bgp_path_info_mpath_first(dmed_best); mpinfo; + mpinfo = mpnext) { + mpnext = bgp_path_info_mpath_next(mpinfo); + bgp_path_info_mpath_dequeue(mpinfo); + } + + bgp_path_info_mpath_count_set(dmed_best, 0); + UNSET_FLAG(dmed_best->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(dmed_best->flags, BGP_PATH_LINK_BW_CHG); + assert(bgp_path_info_mpath_first(dmed_best) == NULL); +} + +/* + * bgp_path_info_mpath_aggregate_update + * + * Set the multipath aggregate attribute. We need to see if the + * aggregate has changed and then set the ATTR_CHANGED flag on the + * bestpath info so that a peer update will be generated. The + * change is detected by generating the current attribute, + * interning it, and then comparing the interned pointer with the + * current value. We can skip this generate/compare step if there + * is no change in multipath selection and no attribute change in + * any multipath. + */ +void bgp_path_info_mpath_aggregate_update(struct bgp_path_info *new_best, + struct bgp_path_info *old_best) +{ + struct bgp_path_info *mpinfo; + struct aspath *aspath; + struct aspath *asmerge; + struct attr *new_attr, *old_attr; + uint8_t origin; + struct community *community, *commerge; + struct ecommunity *ecomm, *ecommerge; + struct lcommunity *lcomm, *lcommerge; + struct attr attr = {0}; + + if (old_best && (old_best != new_best) + && (old_attr = bgp_path_info_mpath_attr(old_best))) { + bgp_attr_unintern(&old_attr); + bgp_path_info_mpath_attr_set(old_best, NULL); + } + + if (!new_best) + return; + + if (!bgp_path_info_mpath_count(new_best)) { + if ((new_attr = bgp_path_info_mpath_attr(new_best))) { + bgp_attr_unintern(&new_attr); + bgp_path_info_mpath_attr_set(new_best, NULL); + SET_FLAG(new_best->flags, BGP_PATH_ATTR_CHANGED); + } + return; + } + + attr = *new_best->attr; + + if (new_best->peer + && CHECK_FLAG(new_best->peer->bgp->flags, + BGP_FLAG_MULTIPATH_RELAX_AS_SET)) { + + /* aggregate attribute from multipath constituents */ + aspath = aspath_dup(attr.aspath); + origin = attr.origin; + community = + bgp_attr_get_community(&attr) + ? community_dup(bgp_attr_get_community(&attr)) + : NULL; + ecomm = (bgp_attr_get_ecommunity(&attr)) + ? ecommunity_dup(bgp_attr_get_ecommunity(&attr)) + : NULL; + lcomm = (bgp_attr_get_lcommunity(&attr)) + ? lcommunity_dup(bgp_attr_get_lcommunity(&attr)) + : NULL; + + for (mpinfo = bgp_path_info_mpath_first(new_best); mpinfo; + mpinfo = bgp_path_info_mpath_next(mpinfo)) { + asmerge = + aspath_aggregate(aspath, mpinfo->attr->aspath); + aspath_free(aspath); + aspath = asmerge; + + if (origin < mpinfo->attr->origin) + origin = mpinfo->attr->origin; + + if (bgp_attr_get_community(mpinfo->attr)) { + if (community) { + commerge = community_merge( + community, + bgp_attr_get_community( + mpinfo->attr)); + community = + community_uniq_sort(commerge); + community_free(&commerge); + } else + community = community_dup( + bgp_attr_get_community( + mpinfo->attr)); + } + + if (bgp_attr_get_ecommunity(mpinfo->attr)) { + if (ecomm) { + ecommerge = ecommunity_merge( + ecomm, bgp_attr_get_ecommunity( + mpinfo->attr)); + ecomm = ecommunity_uniq_sort(ecommerge); + ecommunity_free(&ecommerge); + } else + ecomm = ecommunity_dup( + bgp_attr_get_ecommunity( + mpinfo->attr)); + } + if (bgp_attr_get_lcommunity(mpinfo->attr)) { + if (lcomm) { + lcommerge = lcommunity_merge( + lcomm, bgp_attr_get_lcommunity( + mpinfo->attr)); + lcomm = lcommunity_uniq_sort(lcommerge); + lcommunity_free(&lcommerge); + } else + lcomm = lcommunity_dup( + bgp_attr_get_lcommunity( + mpinfo->attr)); + } + } + + attr.aspath = aspath; + attr.origin = origin; + if (community) + bgp_attr_set_community(&attr, community); + if (ecomm) + bgp_attr_set_ecommunity(&attr, ecomm); + if (lcomm) + bgp_attr_set_lcommunity(&attr, lcomm); + + /* Zap multipath attr nexthop so we set nexthop to self */ + attr.nexthop.s_addr = INADDR_ANY; + memset(&attr.mp_nexthop_global, 0, sizeof(struct in6_addr)); + + /* TODO: should we set ATOMIC_AGGREGATE and AGGREGATOR? */ + } + + new_attr = bgp_attr_intern(&attr); + + if (new_attr != bgp_path_info_mpath_attr(new_best)) { + if ((old_attr = bgp_path_info_mpath_attr(new_best))) + bgp_attr_unintern(&old_attr); + bgp_path_info_mpath_attr_set(new_best, new_attr); + SET_FLAG(new_best->flags, BGP_PATH_ATTR_CHANGED); + } else + bgp_attr_unintern(&new_attr); +} diff --git a/bgpd/bgp_mpath.h b/bgpd/bgp_mpath.h new file mode 100644 index 0000000..129682d --- /dev/null +++ b/bgpd/bgp_mpath.h @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Multipath + * Copyright (C) 2010 Google Inc. + * + * This file is part of Quagga + */ + +#ifndef _FRR_BGP_MPATH_H +#define _FRR_BGP_MPATH_H + +/* Supplemental information linked to bgp_path_info for keeping track of + * multipath selections, lazily allocated to save memory + */ +struct bgp_path_info_mpath { + /* Points to the first multipath (on bestpath) or the next multipath */ + struct bgp_path_info_mpath *mp_next; + + /* Points to the previous multipath or NULL on bestpath */ + struct bgp_path_info_mpath *mp_prev; + + /* Points to bgp_path_info associated with this multipath info */ + struct bgp_path_info *mp_info; + + /* When attached to best path, the number of selected multipaths */ + uint16_t mp_count; + + /* Flags - relevant as noted. */ + uint16_t mp_flags; +#define BGP_MP_LB_PRESENT 0x1 /* Link-bandwidth present for >= 1 path */ +#define BGP_MP_LB_ALL 0x2 /* Link-bandwidth present for all multipaths */ + + /* Aggregated attribute for advertising multipath route */ + struct attr *mp_attr; + + /* Cumulative bandiwdth of all multipaths - attached to best path. */ + uint64_t cum_bw; +}; + +/* Functions to support maximum-paths configuration */ +extern int bgp_maximum_paths_set(struct bgp *bgp, afi_t afi, safi_t safi, + int peertype, uint16_t maxpaths, + bool clusterlen); +extern int bgp_maximum_paths_unset(struct bgp *bgp, afi_t afi, safi_t safi, + int peertype); + +/* Functions used by bgp_best_selection to record current + * multipath selections + */ +extern int bgp_path_info_nexthop_cmp(struct bgp_path_info *bpi1, + struct bgp_path_info *bpi2); +extern void bgp_mp_list_init(struct list *mp_list); +extern void bgp_mp_list_clear(struct list *mp_list); +extern void bgp_mp_list_add(struct list *mp_list, struct bgp_path_info *mpinfo); +extern void bgp_mp_dmed_deselect(struct bgp_path_info *dmed_best); +extern void bgp_path_info_mpath_update(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *new_best, + struct bgp_path_info *old_best, + struct list *mp_list, + struct bgp_maxpaths_cfg *mpath_cfg); +extern void +bgp_path_info_mpath_aggregate_update(struct bgp_path_info *new_best, + struct bgp_path_info *old_best); + +/* Unlink and free multipath information associated with a bgp_path_info */ +extern void bgp_path_info_mpath_dequeue(struct bgp_path_info *path); +extern void bgp_path_info_mpath_free(struct bgp_path_info_mpath **mpath); + +/* Walk list of multipaths associated with a best path */ +extern struct bgp_path_info * +bgp_path_info_mpath_first(struct bgp_path_info *path); +extern struct bgp_path_info * +bgp_path_info_mpath_next(struct bgp_path_info *path); + +/* Accessors for multipath information */ +extern uint32_t bgp_path_info_mpath_count(struct bgp_path_info *path); +extern struct attr *bgp_path_info_mpath_attr(struct bgp_path_info *path); +extern bool bgp_path_info_mpath_chkwtd(struct bgp *bgp, + struct bgp_path_info *path); +extern uint64_t bgp_path_info_mpath_cumbw(struct bgp_path_info *path); + +#endif /* _FRR_BGP_MPATH_H */ diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c new file mode 100644 index 0000000..e8951ba --- /dev/null +++ b/bgpd/bgp_mplsvpn.c @@ -0,0 +1,4332 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* MPLS-VPN + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "log.h" +#include "memory.h" +#include "stream.h" +#include "queue.h" +#include "filter.h" +#include "mpls.h" +#include "json.h" +#include "zclient.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_vpn.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_memory.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#endif + +DEFINE_MTYPE_STATIC(BGPD, MPLSVPN_NH_LABEL_BIND_CACHE, + "BGP MPLSVPN nexthop label bind cache"); + +/* + * Definitions and external declarations. + */ +extern struct zclient *zclient; + +extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, + int *index, afi_t *afi) +{ + int ret = 0; + if (argv_find(argv, argc, "vpnv4", index)) { + ret = 1; + if (afi) + *afi = AFI_IP; + } else if (argv_find(argv, argc, "vpnv6", index)) { + ret = 1; + if (afi) + *afi = AFI_IP6; + } + return ret; +} + +uint32_t decode_label(mpls_label_t *label_pnt) +{ + uint32_t l; + uint8_t *pnt = (uint8_t *)label_pnt; + + l = ((uint32_t)*pnt++ << 12); + l |= (uint32_t)*pnt++ << 4; + l |= (uint32_t)((*pnt & 0xf0) >> 4); + return l; +} + +void encode_label(mpls_label_t label, mpls_label_t *label_pnt) +{ + uint8_t *pnt = (uint8_t *)label_pnt; + if (pnt == NULL) + return; + if (label == BGP_PREVENT_VRF_2_VRF_LEAK) { + *label_pnt = label; + return; + } + *pnt++ = (label >> 12) & 0xff; + *pnt++ = (label >> 4) & 0xff; + *pnt++ = ((label << 4) + 1) & 0xff; /* S=1 */ +} + +int bgp_nlri_parse_vpn(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet) +{ + struct prefix p; + uint8_t psize = 0; + uint8_t prefixlen; + uint16_t type; + struct rd_as rd_as; + struct rd_ip rd_ip; + struct prefix_rd prd = {0}; + mpls_label_t label = {0}; + afi_t afi; + safi_t safi; + bool addpath_capable; + uint32_t addpath_id; + int ret = 0; + + /* Make prefix_rd */ + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + struct stream *data = stream_new(packet->length); + stream_put(data, packet->nlri, packet->length); + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + +#define VPN_PREFIXLEN_MIN_BYTES (3 + 8) /* label + RD */ + while (STREAM_READABLE(data) > 0) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + if (addpath_capable) { + STREAM_GET(&addpath_id, data, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + } + + if (STREAM_READABLE(data) < 1) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (truncated NLRI of size %u; no prefix length)", + peer->host, packet->length); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + goto done; + } + + /* Fetch prefix length. */ + STREAM_GETC(data, prefixlen); + p.family = afi2family(packet->afi); + psize = PSIZE(prefixlen); + + if (prefixlen < VPN_PREFIXLEN_MIN_BYTES * 8) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (prefix length %d less than VPN min length)", + peer->host, prefixlen); + ret = BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; + goto done; + } + + /* sanity check against packet data */ + if (STREAM_READABLE(data) < psize) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (prefix length %d exceeds packet size %u)", + peer->host, prefixlen, packet->length); + ret = BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + goto done; + } + + /* sanity check against storage for the IP address portion */ + if ((psize - VPN_PREFIXLEN_MIN_BYTES) > (ssize_t)sizeof(p.u)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (psize %d exceeds storage size %zu)", + peer->host, + prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8, + sizeof(p.u)); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + goto done; + } + + /* Sanity check against max bitlen of the address family */ + if ((psize - VPN_PREFIXLEN_MIN_BYTES) > prefix_blen(&p)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (psize %d exceeds family (%u) max byte len %u)", + peer->host, + prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8, + p.family, prefix_blen(&p)); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + goto done; + } + + /* Copy label to prefix. */ + if (STREAM_READABLE(data) < BGP_LABEL_BYTES) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (truncated NLRI of size %u; no label)", + peer->host, packet->length); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + goto done; + } + + STREAM_GET(&label, data, BGP_LABEL_BYTES); + bgp_set_valid_label(&label); + + /* Copy routing distinguisher to rd. */ + if (STREAM_READABLE(data) < 8) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (truncated NLRI of size %u; no RD)", + peer->host, packet->length); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + goto done; + } + STREAM_GET(&prd.val, data, 8); + + /* Decode RD type. */ + type = decode_rd_type(prd.val); + + switch (type) { + case RD_TYPE_AS: + decode_rd_as(&prd.val[2], &rd_as); + break; + + case RD_TYPE_AS4: + decode_rd_as4(&prd.val[2], &rd_as); + break; + + case RD_TYPE_IP: + decode_rd_ip(&prd.val[2], &rd_ip); + break; + +#ifdef ENABLE_BGP_VNC + case RD_TYPE_VNC_ETH: + break; +#endif + + default: + flog_err(EC_BGP_UPDATE_RCV, "Unknown RD type %d", type); + break; /* just report */ + } + + /* exclude label & RD */ + p.prefixlen = prefixlen - VPN_PREFIXLEN_MIN_BYTES * 8; + STREAM_GET(p.u.val, data, psize - VPN_PREFIXLEN_MIN_BYTES); + + if (attr) { + bgp_update(peer, &p, addpath_id, attr, packet->afi, + SAFI_MPLS_VPN, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, &label, 1, 0, NULL); + } else { + bgp_withdraw(peer, &p, addpath_id, packet->afi, + SAFI_MPLS_VPN, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, &prd, &label, 1, NULL); + } + } + /* Packet length consistency check. */ + if (STREAM_READABLE(data) != 0) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (%zu data remaining after parsing)", + peer->host, STREAM_READABLE(data)); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + goto done; + +stream_failure: + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error / VPN (NLRI of size %u - length error)", + peer->host, packet->length); + ret = BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + +done: + stream_free(data); + return ret; + +#undef VPN_PREFIXLEN_MIN_BYTES +} + +/* + * This function informs zebra of the label this vrf sets on routes + * leaked to VPN. Zebra should install this label in the kernel with + * an action of "pop label and then use this vrf's IP FIB to route the PDU." + * + * Sending this vrf-label association is qualified by a) whether vrf->vpn + * exporting is active ("export vpn" is enabled, vpn-policy RD and RT list + * are set) and b) whether vpn-policy label is set. + * + * If any of these conditions do not hold, then we send MPLS_LABEL_NONE + * for this vrf, which zebra interprets to mean "delete this vrf-label + * association." + */ +void vpn_leak_zebra_vrf_label_update(struct bgp *bgp, afi_t afi) +{ + mpls_label_t label = MPLS_LABEL_NONE; + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) { + zlog_debug( + "%s: vrf %s: afi %s: vrf_id not set, can't set zebra vrf label", + __func__, bgp->name_pretty, afi2str(afi)); + } + return; + } + + if (vpn_leak_to_vpn_active(bgp, afi, NULL, false)) { + label = bgp->vpn_policy[afi].tovpn_label; + } + + if (debug) { + zlog_debug("%s: vrf %s: afi %s: setting label %d for vrf id %d", + __func__, bgp->name_pretty, afi2str(afi), label, + bgp->vrf_id); + } + + if (label == BGP_PREVENT_VRF_2_VRF_LEAK) + label = MPLS_LABEL_NONE; + zclient_send_vrf_label(zclient, bgp->vrf_id, afi, label, ZEBRA_LSP_BGP); + bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = label; +} + +/* + * If zebra tells us vrf has become unconfigured, tell zebra not to + * use this label to forward to the vrf anymore + */ +void vpn_leak_zebra_vrf_label_withdraw(struct bgp *bgp, afi_t afi) +{ + mpls_label_t label = MPLS_LABEL_NONE; + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) { + zlog_debug( + "%s: vrf_id not set, can't delete zebra vrf label", + __func__); + } + return; + } + + if (debug) { + zlog_debug("%s: deleting label for vrf %s (id=%d)", __func__, + bgp->name_pretty, bgp->vrf_id); + } + + zclient_send_vrf_label(zclient, bgp->vrf_id, afi, label, ZEBRA_LSP_BGP); + bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = label; +} + +/* + * This function informs zebra of the srv6-function this vrf sets on routes + * leaked to VPN. Zebra should install this srv6-function in the kernel with + * an action of "End.DT4/6's IP FIB to route the PDU." + */ +void vpn_leak_zebra_vrf_sid_update_per_af(struct bgp *bgp, afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + enum seg6local_action_t act; + struct seg6local_context ctx = {}; + struct in6_addr *tovpn_sid = NULL; + struct in6_addr *tovpn_sid_ls = NULL; + struct vrf *vrf; + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) + zlog_debug("%s: vrf %s: afi %s: vrf_id not set, can't set zebra vrf label", + __func__, bgp->name_pretty, afi2str(afi)); + return; + } + + tovpn_sid = bgp->vpn_policy[afi].tovpn_sid; + if (!tovpn_sid) { + if (debug) + zlog_debug("%s: vrf %s: afi %s: sid not set", __func__, + bgp->name_pretty, afi2str(afi)); + return; + } + + if (debug) + zlog_debug("%s: vrf %s: afi %s: setting sid %pI6 for vrf id %d", + __func__, bgp->name_pretty, afi2str(afi), tovpn_sid, + bgp->vrf_id); + + vrf = vrf_lookup_by_id(bgp->vrf_id); + if (!vrf) + return; + + ctx.table = vrf->data.l.table_id; + act = afi == AFI_IP ? ZEBRA_SEG6_LOCAL_ACTION_END_DT4 + : ZEBRA_SEG6_LOCAL_ACTION_END_DT6; + zclient_send_localsid(zclient, tovpn_sid, bgp->vrf_id, act, &ctx); + + tovpn_sid_ls = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); + *tovpn_sid_ls = *tovpn_sid; + if (bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent) + XFREE(MTYPE_BGP_SRV6_SID, + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent); + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent = tovpn_sid_ls; +} + +/* + * This function informs zebra of the srv6-function this vrf sets on routes + * leaked to VPN. Zebra should install this srv6-function in the kernel with + * an action of "End.DT46's IP FIB to route the PDU." + */ +void vpn_leak_zebra_vrf_sid_update_per_vrf(struct bgp *bgp) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + enum seg6local_action_t act; + struct seg6local_context ctx = {}; + struct in6_addr *tovpn_sid = NULL; + struct in6_addr *tovpn_sid_ls = NULL; + struct vrf *vrf; + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) + zlog_debug( + "%s: vrf %s: vrf_id not set, can't set zebra vrf label", + __func__, bgp->name_pretty); + return; + } + + tovpn_sid = bgp->tovpn_sid; + if (!tovpn_sid) { + if (debug) + zlog_debug("%s: vrf %s: sid not set", __func__, + bgp->name_pretty); + return; + } + + if (debug) + zlog_debug("%s: vrf %s: setting sid %pI6 for vrf id %d", + __func__, bgp->name_pretty, tovpn_sid, bgp->vrf_id); + + vrf = vrf_lookup_by_id(bgp->vrf_id); + if (!vrf) + return; + + ctx.table = vrf->data.l.table_id; + act = ZEBRA_SEG6_LOCAL_ACTION_END_DT46; + zclient_send_localsid(zclient, tovpn_sid, bgp->vrf_id, act, &ctx); + + tovpn_sid_ls = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); + *tovpn_sid_ls = *tovpn_sid; + if (bgp->tovpn_zebra_vrf_sid_last_sent) + XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_zebra_vrf_sid_last_sent); + bgp->tovpn_zebra_vrf_sid_last_sent = tovpn_sid_ls; +} + +/* + * This function informs zebra of the srv6-function this vrf sets on routes + * leaked to VPN. Zebra should install this srv6-function in the kernel with + * an action of "End.DT4/6/46's IP FIB to route the PDU." + */ +void vpn_leak_zebra_vrf_sid_update(struct bgp *bgp, afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (bgp->vpn_policy[afi].tovpn_sid) + return vpn_leak_zebra_vrf_sid_update_per_af(bgp, afi); + + if (bgp->tovpn_sid) + return vpn_leak_zebra_vrf_sid_update_per_vrf(bgp); + + if (debug) + zlog_debug("%s: vrf %s: afi %s: sid not set", __func__, + bgp->name_pretty, afi2str(afi)); +} + +/* + * If zebra tells us vrf has become unconfigured, tell zebra not to + * use this srv6-function to forward to the vrf anymore + */ +void vpn_leak_zebra_vrf_sid_withdraw_per_af(struct bgp *bgp, afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) + zlog_debug("%s: vrf %s: afi %s: vrf_id not set, can't set zebra vrf label", + __func__, bgp->name_pretty, afi2str(afi)); + return; + } + + if (debug) + zlog_debug("%s: deleting sid for vrf %s afi (id=%d)", __func__, + bgp->name_pretty, bgp->vrf_id); + + zclient_send_localsid(zclient, + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent, + bgp->vrf_id, ZEBRA_SEG6_LOCAL_ACTION_UNSPEC, NULL); + XFREE(MTYPE_BGP_SRV6_SID, + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent); + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent = NULL; +} + +/* + * If zebra tells us vrf has become unconfigured, tell zebra not to + * use this srv6-function to forward to the vrf anymore + */ +void vpn_leak_zebra_vrf_sid_withdraw_per_vrf(struct bgp *bgp) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (bgp->vrf_id == VRF_UNKNOWN) { + if (debug) + zlog_debug( + "%s: vrf %s: vrf_id not set, can't set zebra vrf label", + __func__, bgp->name_pretty); + return; + } + + if (debug) + zlog_debug("%s: deleting sid for vrf %s (id=%d)", __func__, + bgp->name_pretty, bgp->vrf_id); + + zclient_send_localsid(zclient, bgp->tovpn_zebra_vrf_sid_last_sent, + bgp->vrf_id, ZEBRA_SEG6_LOCAL_ACTION_UNSPEC, + NULL); + XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_zebra_vrf_sid_last_sent); + bgp->tovpn_zebra_vrf_sid_last_sent = NULL; +} + +/* + * If zebra tells us vrf has become unconfigured, tell zebra not to + * use this srv6-function to forward to the vrf anymore + */ +void vpn_leak_zebra_vrf_sid_withdraw(struct bgp *bgp, afi_t afi) +{ + if (bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent) + vpn_leak_zebra_vrf_sid_withdraw_per_af(bgp, afi); + + if (bgp->tovpn_zebra_vrf_sid_last_sent) + vpn_leak_zebra_vrf_sid_withdraw_per_vrf(bgp); +} + +int vpn_leak_label_callback( + mpls_label_t label, + void *labelid, + bool allocated) +{ + struct vpn_policy *vp = (struct vpn_policy *)labelid; + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + + if (debug) + zlog_debug("%s: label=%u, allocated=%d", + __func__, label, allocated); + + if (!allocated) { + /* + * previously-allocated label is now invalid + */ + if (CHECK_FLAG(vp->flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO) && + (vp->tovpn_label != MPLS_LABEL_NONE)) { + + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, + vp->afi, bgp_get_default(), vp->bgp); + vp->tovpn_label = MPLS_LABEL_NONE; + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, + vp->afi, bgp_get_default(), vp->bgp); + } + return 0; + } + + /* + * New label allocation + */ + if (!CHECK_FLAG(vp->flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { + + /* + * not currently configured for auto label, reject allocation + */ + return -1; + } + + if (vp->tovpn_label != MPLS_LABEL_NONE) { + if (label == vp->tovpn_label) { + /* already have same label, accept but do nothing */ + return 0; + } + /* Shouldn't happen: different label allocation */ + flog_err(EC_BGP_LABEL, + "%s: %s had label %u but got new assignment %u", + __func__, vp->bgp->name_pretty, vp->tovpn_label, + label); + /* use new one */ + } + + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, + vp->afi, bgp_get_default(), vp->bgp); + vp->tovpn_label = label; + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, + vp->afi, bgp_get_default(), vp->bgp); + + return 0; +} + +static void sid_register(struct bgp *bgp, const struct in6_addr *sid, + const char *locator_name) +{ + struct bgp_srv6_function *func; + func = XCALLOC(MTYPE_BGP_SRV6_FUNCTION, + sizeof(struct bgp_srv6_function)); + func->sid = *sid; + snprintf(func->locator_name, sizeof(func->locator_name), + "%s", locator_name); + listnode_add(bgp->srv6_functions, func); +} + +void srv6_function_free(struct bgp_srv6_function *func) +{ + XFREE(MTYPE_BGP_SRV6_FUNCTION, func); +} + +void sid_unregister(struct bgp *bgp, const struct in6_addr *sid) +{ + struct listnode *node, *nnode; + struct bgp_srv6_function *func; + + for (ALL_LIST_ELEMENTS(bgp->srv6_functions, node, nnode, func)) + if (sid_same(&func->sid, sid)) { + listnode_delete(bgp->srv6_functions, func); + srv6_function_free(func); + } +} + +static bool sid_exist(struct bgp *bgp, const struct in6_addr *sid) +{ + struct listnode *node; + struct bgp_srv6_function *func; + + for (ALL_LIST_ELEMENTS_RO(bgp->srv6_functions, node, func)) + if (sid_same(&func->sid, sid)) + return true; + return false; +} + +/* + * This function generates a new SID based on bgp->srv6_locator_chunks and + * index. The locator and generated SID are stored in arguments sid_locator + * and sid, respectively. + * + * if index != 0: try to allocate as index-mode + * else: try to allocate as auto-mode + */ +static uint32_t alloc_new_sid(struct bgp *bgp, uint32_t index, + struct srv6_locator_chunk *sid_locator_chunk, + struct in6_addr *sid) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + struct listnode *node; + struct srv6_locator_chunk *chunk; + bool alloced = false; + int label = 0; + uint8_t offset = 0; + uint8_t func_len = 0, shift_len = 0; + uint32_t index_max = 0; + + if (!bgp || !sid_locator_chunk || !sid) + return false; + + for (ALL_LIST_ELEMENTS_RO(bgp->srv6_locator_chunks, node, chunk)) { + if (chunk->function_bits_length > + BGP_PREFIX_SID_SRV6_MAX_FUNCTION_LENGTH) { + if (debug) + zlog_debug( + "%s: invalid SRv6 Locator chunk (%pFX): Function Length must be less or equal to %d", + __func__, &chunk->prefix, + BGP_PREFIX_SID_SRV6_MAX_FUNCTION_LENGTH); + continue; + } + + index_max = (1 << chunk->function_bits_length) - 1; + + if (index > index_max) { + if (debug) + zlog_debug( + "%s: skipped SRv6 Locator chunk (%pFX): Function Length is too short to support specified index (%u)", + __func__, &chunk->prefix, index); + continue; + } + + *sid = chunk->prefix.prefix; + *sid_locator_chunk = *chunk; + offset = chunk->block_bits_length + chunk->node_bits_length; + func_len = chunk->function_bits_length; + shift_len = BGP_PREFIX_SID_SRV6_MAX_FUNCTION_LENGTH - func_len; + + if (index != 0) { + label = index << shift_len; + if (label < MPLS_LABEL_UNRESERVED_MIN) { + if (debug) + zlog_debug( + "%s: skipped to allocate SRv6 SID (%pFX): Label (%u) is too small to use", + __func__, &chunk->prefix, + label); + continue; + } + + transpose_sid(sid, label, offset, func_len); + if (sid_exist(bgp, sid)) + continue; + alloced = true; + break; + } + + for (uint32_t i = 1; i < index_max; i++) { + label = i << shift_len; + if (label < MPLS_LABEL_UNRESERVED_MIN) { + if (debug) + zlog_debug( + "%s: skipped to allocate SRv6 SID (%pFX): Label (%u) is too small to use", + __func__, &chunk->prefix, + label); + continue; + } + transpose_sid(sid, label, offset, func_len); + if (sid_exist(bgp, sid)) + continue; + alloced = true; + break; + } + } + + if (!alloced) + return 0; + + sid_register(bgp, sid, bgp->srv6_locator_name); + return label; +} + +void ensure_vrf_tovpn_sid_per_af(struct bgp *bgp_vpn, struct bgp *bgp_vrf, + afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + struct srv6_locator_chunk *tovpn_sid_locator; + struct in6_addr *tovpn_sid; + uint32_t tovpn_sid_index = 0, tovpn_sid_transpose_label; + bool tovpn_sid_auto = false; + + if (debug) + zlog_debug("%s: try to allocate new SID for vrf %s: afi %s", + __func__, bgp_vrf->name_pretty, afi2str(afi)); + + /* skip when tovpn sid is already allocated on vrf instance */ + if (bgp_vrf->vpn_policy[afi].tovpn_sid) + return; + + /* + * skip when bgp vpn instance ins't allocated + * or srv6 locator chunk isn't allocated + */ + if (!bgp_vpn || !bgp_vpn->srv6_locator_chunks) + return; + + tovpn_sid_index = bgp_vrf->vpn_policy[afi].tovpn_sid_index; + tovpn_sid_auto = CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO); + + /* skip when VPN isn't configured on vrf-instance */ + if (tovpn_sid_index == 0 && !tovpn_sid_auto) + return; + + /* check invalid case both configured index and auto */ + if (tovpn_sid_index != 0 && tovpn_sid_auto) { + zlog_err("%s: index-mode and auto-mode both selected. ignored.", + __func__); + return; + } + + tovpn_sid_locator = srv6_locator_chunk_alloc(); + tovpn_sid = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); + + tovpn_sid_transpose_label = alloc_new_sid(bgp_vpn, tovpn_sid_index, + tovpn_sid_locator, tovpn_sid); + + if (tovpn_sid_transpose_label == 0) { + if (debug) + zlog_debug( + "%s: not allocated new sid for vrf %s: afi %s", + __func__, bgp_vrf->name_pretty, afi2str(afi)); + srv6_locator_chunk_free(&tovpn_sid_locator); + XFREE(MTYPE_BGP_SRV6_SID, tovpn_sid); + return; + } + + if (debug) + zlog_debug("%s: new sid %pI6 allocated for vrf %s: afi %s", + __func__, tovpn_sid, bgp_vrf->name_pretty, + afi2str(afi)); + + bgp_vrf->vpn_policy[afi].tovpn_sid = tovpn_sid; + bgp_vrf->vpn_policy[afi].tovpn_sid_locator = tovpn_sid_locator; + bgp_vrf->vpn_policy[afi].tovpn_sid_transpose_label = + tovpn_sid_transpose_label; +} + +void ensure_vrf_tovpn_sid_per_vrf(struct bgp *bgp_vpn, struct bgp *bgp_vrf) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + struct srv6_locator_chunk *tovpn_sid_locator; + struct in6_addr *tovpn_sid; + uint32_t tovpn_sid_index = 0, tovpn_sid_transpose_label; + bool tovpn_sid_auto = false; + + if (debug) + zlog_debug("%s: try to allocate new SID for vrf %s", __func__, + bgp_vrf->name_pretty); + + /* skip when tovpn sid is already allocated on vrf instance */ + if (bgp_vrf->tovpn_sid) + return; + + /* + * skip when bgp vpn instance ins't allocated + * or srv6 locator chunk isn't allocated + */ + if (!bgp_vpn || !bgp_vpn->srv6_locator_chunks) + return; + + tovpn_sid_index = bgp_vrf->tovpn_sid_index; + tovpn_sid_auto = CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_TOVPN_SID_AUTO); + + /* skip when VPN isn't configured on vrf-instance */ + if (tovpn_sid_index == 0 && !tovpn_sid_auto) + return; + + /* check invalid case both configured index and auto */ + if (tovpn_sid_index != 0 && tovpn_sid_auto) { + zlog_err("%s: index-mode and auto-mode both selected. ignored.", + __func__); + return; + } + + tovpn_sid_locator = srv6_locator_chunk_alloc(); + tovpn_sid = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); + + tovpn_sid_transpose_label = alloc_new_sid(bgp_vpn, tovpn_sid_index, + tovpn_sid_locator, tovpn_sid); + + if (tovpn_sid_transpose_label == 0) { + if (debug) + zlog_debug("%s: not allocated new sid for vrf %s", + __func__, bgp_vrf->name_pretty); + srv6_locator_chunk_free(&tovpn_sid_locator); + XFREE(MTYPE_BGP_SRV6_SID, tovpn_sid); + return; + } + + if (debug) + zlog_debug("%s: new sid %pI6 allocated for vrf %s", __func__, + tovpn_sid, bgp_vrf->name_pretty); + + bgp_vrf->tovpn_sid = tovpn_sid; + bgp_vrf->tovpn_sid_locator = tovpn_sid_locator; + bgp_vrf->tovpn_sid_transpose_label = tovpn_sid_transpose_label; +} + +void ensure_vrf_tovpn_sid(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi) +{ + /* per-af sid */ + if (bgp_vrf->vpn_policy[afi].tovpn_sid_index != 0 || + CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO)) + return ensure_vrf_tovpn_sid_per_af(bgp_vpn, bgp_vrf, afi); + + /* per-vrf sid */ + if (bgp_vrf->tovpn_sid_index != 0 || + CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_TOVPN_SID_AUTO)) + return ensure_vrf_tovpn_sid_per_vrf(bgp_vpn, bgp_vrf); +} + +void delete_vrf_tovpn_sid_per_af(struct bgp *bgp_vpn, struct bgp *bgp_vrf, + afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + uint32_t tovpn_sid_index = 0; + bool tovpn_sid_auto = false; + + if (debug) + zlog_debug("%s: try to remove SID for vrf %s: afi %s", __func__, + bgp_vrf->name_pretty, afi2str(afi)); + + tovpn_sid_index = bgp_vrf->vpn_policy[afi].tovpn_sid_index; + tovpn_sid_auto = CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO); + + /* skip when VPN is configured on vrf-instance */ + if (tovpn_sid_index != 0 || tovpn_sid_auto) + return; + + srv6_locator_chunk_free(&bgp_vrf->vpn_policy[afi].tovpn_sid_locator); + + if (bgp_vrf->vpn_policy[afi].tovpn_sid) { + sid_unregister(bgp_vpn, bgp_vrf->vpn_policy[afi].tovpn_sid); + XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->vpn_policy[afi].tovpn_sid); + } + bgp_vrf->vpn_policy[afi].tovpn_sid_transpose_label = 0; +} + +void delete_vrf_tovpn_sid_per_vrf(struct bgp *bgp_vpn, struct bgp *bgp_vrf) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + uint32_t tovpn_sid_index = 0; + bool tovpn_sid_auto = false; + + if (debug) + zlog_debug("%s: try to remove SID for vrf %s", __func__, + bgp_vrf->name_pretty); + + tovpn_sid_index = bgp_vrf->tovpn_sid_index; + tovpn_sid_auto = + CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VPN_POLICY_TOVPN_SID_AUTO); + + /* skip when VPN is configured on vrf-instance */ + if (tovpn_sid_index != 0 || tovpn_sid_auto) + return; + + srv6_locator_chunk_free(&bgp_vrf->tovpn_sid_locator); + + if (bgp_vrf->tovpn_sid) { + sid_unregister(bgp_vpn, bgp_vrf->tovpn_sid); + XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->tovpn_sid); + } + bgp_vrf->tovpn_sid_transpose_label = 0; +} + +void delete_vrf_tovpn_sid(struct bgp *bgp_vpn, struct bgp *bgp_vrf, afi_t afi) +{ + delete_vrf_tovpn_sid_per_af(bgp_vpn, bgp_vrf, afi); + delete_vrf_tovpn_sid_per_vrf(bgp_vpn, bgp_vrf); +} + +/* + * This function embeds upper `len` bits of `label` in `sid`, + * starting at offset `offset` as seen from the MSB of `sid`. + * + * e.g. Given that `label` is 0x12345 and `len` is 16, + * then `label` will be embedded in `sid` as follows: + * + * <---- len -----> + * label: 0001 0002 0003 0004 0005 + * sid: .... 0001 0002 0003 0004 + * <---- len -----> + * ^ + * | + * offset from MSB + * + * e.g. Given that `label` is 0x12345 and `len` is 8, + * `label` will be embedded in `sid` as follows: + * + * <- len -> + * label: 0001 0002 0003 0004 0005 + * sid: .... 0001 0002 0000 0000 + * <- len -> + * ^ + * | + * offset from MSB + */ +void transpose_sid(struct in6_addr *sid, uint32_t label, uint8_t offset, + uint8_t len) +{ + for (uint8_t idx = 0; idx < len; idx++) { + uint8_t tidx = offset + idx; + sid->s6_addr[tidx / 8] &= ~(0x1 << (7 - tidx % 8)); + if (label >> (19 - idx) & 0x1) + sid->s6_addr[tidx / 8] |= 0x1 << (7 - tidx % 8); + } +} + +static bool leak_update_nexthop_valid(struct bgp *to_bgp, struct bgp_dest *bn, + struct attr *new_attr, afi_t afi, + safi_t safi, + struct bgp_path_info *source_bpi, + struct bgp_path_info *bpi, + struct bgp *bgp_orig, + const struct prefix *p, int debug) +{ + struct bgp_path_info *bpi_ultimate; + struct bgp *bgp_nexthop; + struct bgp_table *table; + bool nh_valid; + + bpi_ultimate = bgp_get_imported_bpi_ultimate(source_bpi); + table = bgp_dest_table(bpi_ultimate->net); + + if (bpi->extra && bpi->extra->vrfleak && bpi->extra->vrfleak->bgp_orig) + bgp_nexthop = bpi->extra->vrfleak->bgp_orig; + else + bgp_nexthop = bgp_orig; + + /* + * No nexthop tracking for redistributed routes, for + * EVPN-imported routes that get leaked, or for routes + * leaked between VRFs with accept-own community. + */ + if (bpi_ultimate->sub_type == BGP_ROUTE_REDISTRIBUTE || + is_pi_family_evpn(bpi_ultimate) || + CHECK_FLAG(bpi_ultimate->flags, BGP_PATH_ACCEPT_OWN)) + nh_valid = true; + else if (bpi_ultimate->type == ZEBRA_ROUTE_BGP && + bpi_ultimate->sub_type == BGP_ROUTE_STATIC && table && + (table->safi == SAFI_UNICAST || + table->safi == SAFI_LABELED_UNICAST)) { + /* the route is defined with the "network " command */ + + if (CHECK_FLAG(bgp_nexthop->flags, BGP_FLAG_IMPORT_CHECK)) + nh_valid = bgp_find_or_add_nexthop(to_bgp, bgp_nexthop, + afi, SAFI_UNICAST, + bpi_ultimate, NULL, + 0, p); + else + /* if "no bgp network import-check" is set, + * then mark the nexthop as valid. + */ + nh_valid = true; + } else + /* + * TBD do we need to do anything about the + * 'connected' parameter? + */ + nh_valid = bgp_find_or_add_nexthop(to_bgp, bgp_nexthop, afi, + safi, bpi, NULL, 0, p); + + /* + * If you are using SRv6 VPN instead of MPLS, it need to check + * the SID allocation. If the sid is not allocated, the rib + * will be invalid. + */ + if (to_bgp->srv6_enabled && + (!new_attr->srv6_l3vpn && !new_attr->srv6_vpn)) { + nh_valid = false; + } + + if (debug) + zlog_debug("%s: %pFX nexthop is %svalid (in %s)", __func__, p, + (nh_valid ? "" : "not "), bgp_nexthop->name_pretty); + + return nh_valid; +} + +/* + * returns pointer to new bgp_path_info upon success + */ +static struct bgp_path_info * +leak_update(struct bgp *to_bgp, struct bgp_dest *bn, + struct attr *new_attr, /* already interned */ + afi_t afi, safi_t safi, struct bgp_path_info *source_bpi, + mpls_label_t *label, uint8_t num_labels, struct bgp *bgp_orig, + struct prefix *nexthop_orig, int nexthop_self_flag, int debug) +{ + const struct prefix *p = bgp_dest_get_prefix(bn); + struct bgp_path_info *bpi; + struct bgp_path_info *new; + struct bgp_path_info_extra *extra; + struct bgp_path_info *parent = source_bpi; + struct bgp_labels bgp_labels = {}; + bool labelssame; + uint8_t i; + + if (debug) + zlog_debug( + "%s: entry: leak-to=%s, p=%pBD, type=%d, sub_type=%d", + __func__, to_bgp->name_pretty, bn, source_bpi->type, + source_bpi->sub_type); + + /* + * Routes that are redistributed into BGP from zebra do not get + * nexthop tracking, unless MPLS allocation per nexthop is + * performed. In the default case nexthop tracking does not apply, + * if those routes are subsequently imported to other RIBs within + * BGP, the leaked routes do not carry the original + * BGP_ROUTE_REDISTRIBUTE sub_type. Therefore, in order to determine + * if the route we are currently leaking should have nexthop + * tracking, we must find the ultimate parent so we can check its + * sub_type. + * + * As of now, source_bpi may at most be a second-generation route + * (only one hop back to ultimate parent for vrf-vpn-vrf scheme). + * Using a loop here supports more complex intra-bgp import-export + * schemes that could be implemented in the future. + * + */ + + /* + * match parent + */ + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->parent == parent) + break; + } + + bgp_labels.num_labels = num_labels; + for (i = 0; i < num_labels; i++) { + bgp_labels.label[i] = label[i]; + bgp_set_valid_label(&bgp_labels.label[i]); + } + + if (bpi) { + labelssame = bgp_path_info_labels_same(bpi, bgp_labels.label, + bgp_labels.num_labels); + + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_REMOVED) + && CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + if (debug) { + zlog_debug( + "%s: ->%s(s_flags: 0x%x b_flags: 0x%x): %pFX: Found route, being removed, not leaking", + __func__, to_bgp->name_pretty, + source_bpi->flags, bpi->flags, p); + } + return NULL; + } + + if (attrhash_cmp(bpi->attr, new_attr) && labelssame + && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + + bgp_attr_unintern(&new_attr); + if (debug) + zlog_debug( + "%s: ->%s: %pBD: Found route, no change", + __func__, to_bgp->name_pretty, bn); + return NULL; + } + + /* If the RT was changed via extended communities as an + * import/export list, we should withdraw implicitly the old + * path from VRFs. + * For instance, RT list was modified using route-maps: + * route-map test permit 10 + * set extcommunity rt none + */ + if (CHECK_FLAG(bpi->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) && + CHECK_FLAG(new_attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + if (!ecommunity_cmp( + bgp_attr_get_ecommunity(bpi->attr), + bgp_attr_get_ecommunity(new_attr))) { + vpn_leak_to_vrf_withdraw(bpi); + bgp_aggregate_decrement(to_bgp, p, bpi, afi, + safi); + bgp_path_info_delete(bn, bpi); + } + } + + /* attr is changed */ + bgp_path_info_set_flag(bn, bpi, BGP_PATH_ATTR_CHANGED); + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(bn, bpi); + else + bgp_aggregate_decrement(to_bgp, p, bpi, afi, safi); + bgp_attr_unintern(&bpi->attr); + bpi->attr = new_attr; + bpi->uptime = monotime(NULL); + + /* + * update labels + */ + if (!labelssame) { + bgp_path_info_extra_get(bpi); + bgp_labels_unintern(&bpi->extra->labels); + bpi->extra->labels = bgp_labels_intern(&bgp_labels); + } + + if (nexthop_self_flag) + bgp_path_info_set_flag(bn, bpi, BGP_PATH_ANNC_NH_SELF); + + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_ACCEPT_OWN)) + bgp_path_info_set_flag(bn, bpi, BGP_PATH_ACCEPT_OWN); + + if (leak_update_nexthop_valid(to_bgp, bn, new_attr, afi, safi, + source_bpi, bpi, bgp_orig, p, + debug)) + bgp_path_info_set_flag(bn, bpi, BGP_PATH_VALID); + else + bgp_path_info_unset_flag(bn, bpi, BGP_PATH_VALID); + + /* Process change. */ + bgp_aggregate_increment(to_bgp, p, bpi, afi, safi); + bgp_process(to_bgp, bn, bpi, afi, safi); + + if (debug) + zlog_debug("%s: ->%s: %pBD Found route, changed attr", + __func__, to_bgp->name_pretty, bn); + + bgp_dest_unlock_node(bn); + + return bpi; + } + + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_REMOVED)) { + if (debug) { + zlog_debug( + "%s: ->%s(s_flags: 0x%x): %pFX: New route, being removed, not leaking", + __func__, to_bgp->name_pretty, + source_bpi->flags, p); + } + return NULL; + } + + new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_IMPORTED, 0, + to_bgp->peer_self, new_attr, bn); + + bgp_path_info_extra_get(new); + if (!new->extra->vrfleak) + new->extra->vrfleak = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK, + sizeof(struct bgp_path_info_extra_vrfleak)); + + if (source_bpi->peer) { + extra = bgp_path_info_extra_get(new); + extra->vrfleak->peer_orig = peer_lock(source_bpi->peer); + } + + if (nexthop_self_flag) + bgp_path_info_set_flag(bn, new, BGP_PATH_ANNC_NH_SELF); + + if (CHECK_FLAG(source_bpi->flags, BGP_PATH_ACCEPT_OWN)) + bgp_path_info_set_flag(bn, new, BGP_PATH_ACCEPT_OWN); + + if (bgp_labels.num_labels) + new->extra->labels = bgp_labels_intern(&bgp_labels); + + new->extra->vrfleak->parent = bgp_path_info_lock(parent); + bgp_dest_lock_node( + (struct bgp_dest *)parent->net); + + new->extra->vrfleak->bgp_orig = bgp_lock(bgp_orig); + + if (nexthop_orig) + new->extra->vrfleak->nexthop_orig = *nexthop_orig; + + if (leak_update_nexthop_valid(to_bgp, bn, new_attr, afi, safi, + source_bpi, new, bgp_orig, p, debug)) + bgp_path_info_set_flag(bn, new, BGP_PATH_VALID); + else + bgp_path_info_unset_flag(bn, new, BGP_PATH_VALID); + + bgp_aggregate_increment(to_bgp, p, new, afi, safi); + bgp_path_info_add(bn, new); + + bgp_process(to_bgp, bn, new, afi, safi); + + if (debug) + zlog_debug("%s: ->%s: %pBD: Added new route", __func__, + to_bgp->name_pretty, bn); + + bgp_dest_unlock_node(bn); + + return new; +} + +void bgp_mplsvpn_path_nh_label_unlink(struct bgp_path_info *pi) +{ + struct bgp_label_per_nexthop_cache *blnc; + + if (!pi) + return; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_MPLSVPN_LABEL_NH)) + return; + + blnc = pi->mplsvpn.blnc.label_nexthop_cache; + + if (!blnc) + return; + + LIST_REMOVE(pi, mplsvpn.blnc.label_nh_thread); + pi->mplsvpn.blnc.label_nexthop_cache->path_count--; + pi->mplsvpn.blnc.label_nexthop_cache = NULL; + UNSET_FLAG(pi->flags, BGP_PATH_MPLSVPN_LABEL_NH); + + if (LIST_EMPTY(&(blnc->paths))) + bgp_label_per_nexthop_free(blnc); +} + +/* Called upon reception of a ZAPI Message from zebra, about + * a new available label. + */ +static int bgp_mplsvpn_get_label_per_nexthop_cb(mpls_label_t label, + void *context, bool allocated) +{ + struct bgp_label_per_nexthop_cache *blnc = context; + mpls_label_t old_label; + int debug = BGP_DEBUG(vpn, VPN_LEAK_LABEL); + struct bgp_path_info *pi; + struct bgp_table *table; + + old_label = blnc->label; + + if (debug) + zlog_debug("%s: label=%u, allocated=%d, nexthop=%pFX", __func__, + label, allocated, &blnc->nexthop); + if (allocated) + /* update the entry with the new label */ + blnc->label = label; + else + /* + * previously-allocated label is now invalid + * eg: zebra deallocated the labels and notifies it + */ + blnc->label = MPLS_INVALID_LABEL; + + if (old_label == blnc->label) + return 0; /* no change */ + + /* update paths */ + if (blnc->label != MPLS_INVALID_LABEL) + bgp_zebra_send_nexthop_label(ZEBRA_MPLS_LABELS_ADD, blnc->label, + blnc->nh->ifindex, + blnc->nh->vrf_id, ZEBRA_LSP_BGP, + &blnc->nexthop, 0, NULL); + + LIST_FOREACH (pi, &(blnc->paths), mplsvpn.blnc.label_nh_thread) { + if (!pi->net) + continue; + table = bgp_dest_table(pi->net); + if (!table) + continue; + vpn_leak_from_vrf_update(blnc->to_bgp, table->bgp, pi); + } + + return 0; +} + +/* Get a per label nexthop value: + * - Find and return a per label nexthop from the cache + * - else allocate a new per label nexthop cache entry and request a + * label to zebra. Return MPLS_INVALID_LABEL + */ +static mpls_label_t +_vpn_leak_from_vrf_get_per_nexthop_label(struct bgp_path_info *pi, + struct bgp *to_bgp, + struct bgp *from_bgp, afi_t afi) +{ + struct bgp_nexthop_cache *bnc = pi->nexthop; + struct bgp_label_per_nexthop_cache *blnc; + struct bgp_label_per_nexthop_cache_head *tree; + struct prefix *nh_pfx = NULL; + struct prefix nh_gate = {0}; + + /* extract the nexthop from the BNC nexthop cache */ + switch (bnc->nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + /* the nexthop is recursive */ + nh_gate.family = AF_INET; + nh_gate.prefixlen = IPV4_MAX_BITLEN; + IPV4_ADDR_COPY(&nh_gate.u.prefix4, &bnc->nexthop->gate.ipv4); + nh_pfx = &nh_gate; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + /* the nexthop is recursive */ + nh_gate.family = AF_INET6; + nh_gate.prefixlen = IPV6_MAX_BITLEN; + IPV6_ADDR_COPY(&nh_gate.u.prefix6, &bnc->nexthop->gate.ipv6); + nh_pfx = &nh_gate; + break; + case NEXTHOP_TYPE_IFINDEX: + /* the nexthop is direcly connected */ + nh_pfx = &bnc->prefix; + break; + case NEXTHOP_TYPE_BLACKHOLE: + assert(!"Blackhole nexthop. Already checked by the caller."); + } + + /* find or allocate a nexthop label cache entry */ + tree = &from_bgp->mpls_labels_per_nexthop[family2afi(nh_pfx->family)]; + blnc = bgp_label_per_nexthop_find(tree, nh_pfx); + if (!blnc) { + blnc = bgp_label_per_nexthop_new(tree, nh_pfx); + blnc->to_bgp = to_bgp; + /* request a label to zebra for this nexthop + * the response from zebra will trigger the callback + */ + bgp_lp_get(LP_TYPE_NEXTHOP, blnc, + bgp_mplsvpn_get_label_per_nexthop_cb); + } + + if (pi->mplsvpn.blnc.label_nexthop_cache == blnc) + /* no change */ + return blnc->label; + + /* Unlink from any existing nexthop cache. Free the entry if unused. + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + + /* updates NHT pi list reference */ + LIST_INSERT_HEAD(&(blnc->paths), pi, mplsvpn.blnc.label_nh_thread); + pi->mplsvpn.blnc.label_nexthop_cache = blnc; + pi->mplsvpn.blnc.label_nexthop_cache->path_count++; + SET_FLAG(pi->flags, BGP_PATH_MPLSVPN_LABEL_NH); + blnc->last_update = monotime(NULL); + + /* then add or update the selected nexthop */ + if (!blnc->nh) + blnc->nh = nexthop_dup(bnc->nexthop, NULL); + else if (!nexthop_same(bnc->nexthop, blnc->nh)) { + nexthop_free(blnc->nh); + blnc->nh = nexthop_dup(bnc->nexthop, NULL); + if (blnc->label != MPLS_INVALID_LABEL) { + bgp_zebra_send_nexthop_label( + ZEBRA_MPLS_LABELS_REPLACE, blnc->label, + bnc->nexthop->ifindex, bnc->nexthop->vrf_id, + ZEBRA_LSP_BGP, &blnc->nexthop, 0, NULL); + } + } + + return blnc->label; +} + +static mpls_label_t bgp_mplsvpn_get_vpn_label(struct vpn_policy *bgp_policy) +{ + if (bgp_policy->tovpn_label == MPLS_LABEL_NONE && + CHECK_FLAG(bgp_policy->flags, BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { + bgp_lp_get(LP_TYPE_VRF, bgp_policy, vpn_leak_label_callback); + return MPLS_INVALID_LABEL; + } + return bgp_policy->tovpn_label; +} + +/* Filter out all the cases where a per nexthop label is not possible: + * - return an invalid label when the nexthop is invalid + * - return the per VRF label when the per nexthop label is not supported + * Otherwise, find or request a per label nexthop. + */ +static mpls_label_t +vpn_leak_from_vrf_get_per_nexthop_label(afi_t afi, struct bgp_path_info *pi, + struct bgp *from_bgp, + struct bgp *to_bgp) +{ + struct bgp_path_info *bpi_ultimate = bgp_get_imported_bpi_ultimate(pi); + struct bgp *bgp_nexthop = NULL; + bool nh_valid; + afi_t nh_afi; + bool is_bgp_static_route; + + is_bgp_static_route = bpi_ultimate->sub_type == BGP_ROUTE_STATIC && + bpi_ultimate->type == ZEBRA_ROUTE_BGP; + + if (is_bgp_static_route == false && afi == AFI_IP && + CHECK_FLAG(pi->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) && + (pi->attr->nexthop.s_addr == INADDR_ANY || + !ipv4_unicast_valid(&pi->attr->nexthop))) { + /* IPv4 nexthop in standard BGP encoding format. + * Format of address is not valid (not any, not unicast). + * Fallback to the per VRF label. + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + } + + if (is_bgp_static_route == false && afi == AFI_IP && + pi->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4 && + (pi->attr->mp_nexthop_global_in.s_addr == INADDR_ANY || + !ipv4_unicast_valid(&pi->attr->mp_nexthop_global_in))) { + /* IPv4 nexthop is in MP-BGP encoding format. + * Format of address is not valid (not any, not unicast). + * Fallback to the per VRF label. + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + } + + if (is_bgp_static_route == false && afi == AFI_IP6 && + (pi->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || + pi->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) && + (IN6_IS_ADDR_UNSPECIFIED(&pi->attr->mp_nexthop_global) || + IN6_IS_ADDR_LOOPBACK(&pi->attr->mp_nexthop_global) || + IN6_IS_ADDR_MULTICAST(&pi->attr->mp_nexthop_global))) { + /* IPv6 nexthop is in MP-BGP encoding format. + * Format of address is not valid + * Fallback to the per VRF label. + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + } + + /* Check the next-hop reachability. + * Get the bgp instance where the bgp_path_info originates. + */ + if (pi->extra && pi->extra->vrfleak && pi->extra->vrfleak->bgp_orig) + bgp_nexthop = pi->extra->vrfleak->bgp_orig; + else + bgp_nexthop = from_bgp; + + nh_afi = BGP_ATTR_NH_AFI(afi, pi->attr); + nh_valid = bgp_find_or_add_nexthop(from_bgp, bgp_nexthop, nh_afi, + SAFI_UNICAST, pi, NULL, 0, NULL); + + if (!nh_valid && is_bgp_static_route && + !CHECK_FLAG(from_bgp->flags, BGP_FLAG_IMPORT_CHECK)) { + /* "network" prefixes not routable, but since 'no bgp network + * import-check' is configured, they are always valid in the BGP + * table. Fallback to the per-vrf label + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + } + + if (!nh_valid || !pi->nexthop || pi->nexthop->nexthop_num == 0 || + !pi->nexthop->nexthop) { + /* invalid next-hop: + * do not send the per-vrf label + * otherwise, when the next-hop becomes valid, + * we will have 2 BGP updates: + * - one with the per-vrf label + * - the second with the per-nexthop label + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return MPLS_INVALID_LABEL; + } + + if (pi->nexthop->nexthop_num > 1 || + pi->nexthop->nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { + /* Blackhole or ECMP routes + * is not compatible with per-nexthop label. + * Fallback to per-vrf label. + */ + bgp_mplsvpn_path_nh_label_unlink(pi); + return bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + } + + return _vpn_leak_from_vrf_get_per_nexthop_label(pi, to_bgp, from_bgp, + afi); +} + +/* cf vnc_import_bgp_add_route_mode_nvegroup() and add_vnc_route() */ +void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ + struct bgp *from_bgp, /* from */ + struct bgp_path_info *path_vrf) /* route */ +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + const struct prefix *p = bgp_dest_get_prefix(path_vrf->net); + afi_t afi = family2afi(p->family); + struct attr static_attr = {0}; + struct attr *new_attr = NULL; + safi_t safi = SAFI_MPLS_VPN; + mpls_label_t label_val; + mpls_label_t label; + struct bgp_dest *bn; + const char *debugmsg; + int nexthop_self_flag = 0; + struct ecommunity *old_ecom; + struct ecommunity *new_ecom = NULL; + struct ecommunity *rtlist_ecom; + + if (debug) + zlog_debug("%s: from vrf %s", __func__, from_bgp->name_pretty); + + if (debug && bgp_attr_get_ecommunity(path_vrf->attr)) { + char *s = ecommunity_ecom2str( + bgp_attr_get_ecommunity(path_vrf->attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + zlog_debug("%s: %s path_vrf->type=%d, EC{%s}", __func__, + from_bgp->name, path_vrf->type, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + if (!to_bgp) + return; + + if (!afi) { + if (debug) + zlog_debug("%s: can't get afi of prefix", __func__); + return; + } + + /* Is this route exportable into the VPN table? */ + if (!is_route_injectable_into_vpn(path_vrf)) + return; + + if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg, false)) { + if (debug) + zlog_debug("%s: %s skipping: %s", __func__, + from_bgp->name, debugmsg); + return; + } + + /* shallow copy */ + static_attr = *path_vrf->attr; + + if (debug && bgp_attr_get_ecommunity(&static_attr)) { + char *s = ecommunity_ecom2str( + bgp_attr_get_ecommunity(&static_attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + zlog_debug("%s: post route map static_attr.ecommunity{%s}", + __func__, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + /* + * Add the vpn-policy rt-list + */ + + /* Export with the 'from' instance's export RTs. */ + /* If doing VRF-to-VRF leaking, strip existing RTs first. */ + old_ecom = bgp_attr_get_ecommunity(&static_attr); + rtlist_ecom = from_bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]; + if (old_ecom) { + new_ecom = ecommunity_dup(old_ecom); + if (CHECK_FLAG(from_bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) + ecommunity_strip_rts(new_ecom); + if (rtlist_ecom) + new_ecom = ecommunity_merge(new_ecom, rtlist_ecom); + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } else if (rtlist_ecom) { + new_ecom = ecommunity_dup(rtlist_ecom); + } else { + new_ecom = NULL; + } + + bgp_attr_set_ecommunity(&static_attr, new_ecom); + + /* + * route map handling + */ + if (from_bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = to_bgp->peer_self; + info.attr = &static_attr; + ret = route_map_apply(from_bgp->vpn_policy[afi] + .rmap[BGP_VPN_POLICY_DIR_TOVPN], + p, &info); + if (RMAP_DENYMATCH == ret) { + bgp_attr_flush(&static_attr); /* free any added parts */ + if (debug) + zlog_debug("%s: vrf %s route map \"%s\" says DENY, returning", + __func__, from_bgp->name_pretty, + from_bgp->vpn_policy[afi] + .rmap[BGP_VPN_POLICY_DIR_TOVPN] + ->name); + return; + } + } + + new_ecom = bgp_attr_get_ecommunity(&static_attr); + if (!ecommunity_has_route_target(new_ecom)) { + ecommunity_free(&new_ecom); + if (debug) + zlog_debug("%s: %s skipping: waiting for a valid export rt list.", + __func__, from_bgp->name_pretty); + return; + } + + if (debug && bgp_attr_get_ecommunity(&static_attr)) { + char *s = ecommunity_ecom2str( + bgp_attr_get_ecommunity(&static_attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + zlog_debug("%s: post merge static_attr.ecommunity{%s}", + __func__, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + community_strip_accept_own(&static_attr); + + /* Nexthop */ + /* if policy nexthop not set, use 0 */ + if (CHECK_FLAG(from_bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_NEXTHOP_SET)) { + struct prefix *nexthop = + &from_bgp->vpn_policy[afi].tovpn_nexthop; + + switch (nexthop->family) { + case AF_INET: + /* prevent mp_nexthop_global_in <- self in bgp_route.c + */ + static_attr.nexthop.s_addr = nexthop->u.prefix4.s_addr; + + static_attr.mp_nexthop_global_in = nexthop->u.prefix4; + static_attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + break; + + case AF_INET6: + static_attr.mp_nexthop_global = nexthop->u.prefix6; + static_attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + + default: + assert(0); + } + } else { + if (!CHECK_FLAG(from_bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) { + if (afi == AFI_IP && + !BGP_ATTR_NEXTHOP_AFI_IP6(path_vrf->attr)) { + /* + * For ipv4, copy to multiprotocol + * nexthop field + */ + static_attr.mp_nexthop_global_in = + static_attr.nexthop; + static_attr.mp_nexthop_len = + BGP_ATTR_NHLEN_IPV4; + /* + * XXX Leave static_attr.nexthop + * intact for NHT + */ + static_attr.flag &= + ~ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + } + } else { + /* Update based on next-hop family to account for + * RFC 5549 (BGP unnumbered) scenario. Note that + * specific action is only needed for the case of + * IPv4 nexthops as the attr has been copied + * otherwise. + */ + if (afi == AFI_IP + && !BGP_ATTR_NEXTHOP_AFI_IP6(path_vrf->attr)) { + static_attr.mp_nexthop_global_in.s_addr = + static_attr.nexthop.s_addr; + static_attr.mp_nexthop_len = + BGP_ATTR_NHLEN_IPV4; + static_attr.flag |= + ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + } + } + nexthop_self_flag = 1; + } + + if (CHECK_FLAG(from_bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP)) + /* per nexthop label mode */ + label_val = vpn_leak_from_vrf_get_per_nexthop_label( + afi, path_vrf, from_bgp, to_bgp); + else + label_val = + bgp_mplsvpn_get_vpn_label(&from_bgp->vpn_policy[afi]); + + if (label_val == MPLS_INVALID_LABEL) { + /* no valid label for the moment + * when the 'bgp_mplsvpn_get_label_per_nexthop_cb' callback gets + * a valid label value, it will call the current function again. + */ + if (debug) + zlog_debug( + "%s: %s skipping: waiting for a valid per-label nexthop.", + __func__, from_bgp->name_pretty); + bgp_attr_flush(&static_attr); + return; + } + if (label_val == MPLS_LABEL_NONE) + encode_label(MPLS_LABEL_IMPLICIT_NULL, &label); + else + encode_label(label_val, &label); + + /* Set originator ID to "me" */ + SET_FLAG(static_attr.flag, ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)); + static_attr.originator_id = to_bgp->router_id; + + /* Set SID for SRv6 VPN */ + if (from_bgp->vpn_policy[afi].tovpn_sid_locator) { + struct srv6_locator_chunk *locator = + from_bgp->vpn_policy[afi].tovpn_sid_locator; + encode_label( + from_bgp->vpn_policy[afi].tovpn_sid_transpose_label, + &label); + static_attr.srv6_l3vpn = XCALLOC(MTYPE_BGP_SRV6_L3VPN, + sizeof(struct bgp_attr_srv6_l3vpn)); + static_attr.srv6_l3vpn->sid_flags = 0x00; + static_attr.srv6_l3vpn->endpoint_behavior = + afi == AFI_IP + ? (CHECK_FLAG(locator->flags, SRV6_LOCATOR_USID) + ? SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID + : SRV6_ENDPOINT_BEHAVIOR_END_DT4) + : (CHECK_FLAG(locator->flags, SRV6_LOCATOR_USID) + ? SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID + : SRV6_ENDPOINT_BEHAVIOR_END_DT6); + static_attr.srv6_l3vpn->loc_block_len = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->block_bits_length; + static_attr.srv6_l3vpn->loc_node_len = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->node_bits_length; + static_attr.srv6_l3vpn->func_len = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->function_bits_length; + static_attr.srv6_l3vpn->arg_len = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->argument_bits_length; + static_attr.srv6_l3vpn->transposition_len = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->function_bits_length; + static_attr.srv6_l3vpn->transposition_offset = + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->block_bits_length + + from_bgp->vpn_policy[afi] + .tovpn_sid_locator->node_bits_length; + ; + memcpy(&static_attr.srv6_l3vpn->sid, + &from_bgp->vpn_policy[afi] + .tovpn_sid_locator->prefix.prefix, + sizeof(struct in6_addr)); + } else if (from_bgp->tovpn_sid_locator) { + struct srv6_locator_chunk *locator = + from_bgp->tovpn_sid_locator; + encode_label(from_bgp->tovpn_sid_transpose_label, &label); + static_attr.srv6_l3vpn = + XCALLOC(MTYPE_BGP_SRV6_L3VPN, + sizeof(struct bgp_attr_srv6_l3vpn)); + static_attr.srv6_l3vpn->sid_flags = 0x00; + static_attr.srv6_l3vpn->endpoint_behavior = + CHECK_FLAG(locator->flags, SRV6_LOCATOR_USID) + ? SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID + : SRV6_ENDPOINT_BEHAVIOR_END_DT46; + static_attr.srv6_l3vpn->loc_block_len = + from_bgp->tovpn_sid_locator->block_bits_length; + static_attr.srv6_l3vpn->loc_node_len = + from_bgp->tovpn_sid_locator->node_bits_length; + static_attr.srv6_l3vpn->func_len = + from_bgp->tovpn_sid_locator->function_bits_length; + static_attr.srv6_l3vpn->arg_len = + from_bgp->tovpn_sid_locator->argument_bits_length; + static_attr.srv6_l3vpn->transposition_len = + from_bgp->tovpn_sid_locator->function_bits_length; + static_attr.srv6_l3vpn->transposition_offset = + from_bgp->tovpn_sid_locator->block_bits_length + + from_bgp->tovpn_sid_locator->node_bits_length; + memcpy(&static_attr.srv6_l3vpn->sid, + &from_bgp->tovpn_sid_locator->prefix.prefix, + sizeof(struct in6_addr)); + } + + + new_attr = bgp_attr_intern( + &static_attr); /* hashed refcounted everything */ + bgp_attr_flush(&static_attr); /* free locally-allocated parts */ + + if (debug && bgp_attr_get_ecommunity(new_attr)) { + char *s = ecommunity_ecom2str(bgp_attr_get_ecommunity(new_attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + zlog_debug("%s: new_attr->ecommunity{%s}", __func__, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + /* Now new_attr is an allocated interned attr */ + + bn = bgp_afi_node_get(to_bgp->rib[afi][safi], afi, safi, p, + &(from_bgp->vpn_policy[afi].tovpn_rd)); + + struct bgp_path_info *new_info; + + new_info = + leak_update(to_bgp, bn, new_attr, afi, safi, path_vrf, &label, + 1, from_bgp, NULL, nexthop_self_flag, debug); + + /* + * Routes actually installed in the vpn RIB must also be + * offered to all vrfs (because now they originate from + * the vpn RIB). + * + * Acceptance into other vrfs depends on rt-lists. + * Originating vrf will not accept the looped back route + * because of loop checking. + */ + if (new_info) + vpn_leak_to_vrf_update(from_bgp, new_info, NULL); + else + bgp_dest_unlock_node(bn); +} + +void vpn_leak_from_vrf_withdraw(struct bgp *to_bgp, /* to */ + struct bgp *from_bgp, /* from */ + struct bgp_path_info *path_vrf) /* route */ +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + const struct prefix *p = bgp_dest_get_prefix(path_vrf->net); + afi_t afi = family2afi(p->family); + safi_t safi = SAFI_MPLS_VPN; + struct bgp_path_info *bpi; + struct bgp_dest *bn; + const char *debugmsg; + + if (debug) { + zlog_debug( + "%s: entry: leak-from=%s, p=%pBD, type=%d, sub_type=%d", + __func__, from_bgp->name_pretty, path_vrf->net, + path_vrf->type, path_vrf->sub_type); + } + + if (!to_bgp) + return; + + if (!afi) { + if (debug) + zlog_debug("%s: can't get afi of prefix", __func__); + return; + } + + /* Is this route exportable into the VPN table? */ + if (!is_route_injectable_into_vpn(path_vrf)) + return; + + if (!vpn_leak_to_vpn_active(from_bgp, afi, &debugmsg, true)) { + if (debug) + zlog_debug("%s: skipping: %s", __func__, debugmsg); + return; + } + + bn = bgp_safi_node_lookup(to_bgp->rib[afi][safi], safi, p, + &(from_bgp->vpn_policy[afi].tovpn_rd)); + + if (!bn) + return; + if (debug) + zlog_debug("%s: withdrawing (path_vrf=%p)", __func__, path_vrf); + + /* + * vrf -> vpn + * match original bpi imported from + */ + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->parent == path_vrf) { + break; + } + } + + if (bpi) { + /* withdraw from looped vrfs as well */ + vpn_leak_to_vrf_withdraw(bpi); + + bgp_aggregate_decrement(to_bgp, p, bpi, afi, safi); + bgp_path_info_delete(bn, bpi); + bgp_process(to_bgp, bn, bpi, afi, safi); + } + bgp_dest_unlock_node(bn); +} + +void vpn_leak_from_vrf_withdraw_all(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + struct bgp_dest *pdest; + safi_t safi = SAFI_MPLS_VPN; + + /* + * Walk vpn table, delete bpi with bgp_orig == from_bgp + */ + for (pdest = bgp_table_top(to_bgp->rib[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + + struct bgp_table *table; + struct bgp_dest *bn; + struct bgp_path_info *bpi, *next; + + /* This is the per-RD table of prefixes */ + table = bgp_dest_get_bgp_table_info(pdest); + + if (!table) + continue; + + for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { + bpi = bgp_dest_get_bgp_path_info(bn); + if (debug && bpi) { + zlog_debug("%s: looking at prefix %pBD", + __func__, bn); + } + + for (; (bpi != NULL) && (next = bpi->next, 1); + bpi = next) { + if (debug) + zlog_debug("%s: type %d, sub_type %d", + __func__, bpi->type, + bpi->sub_type); + if (bpi->sub_type != BGP_ROUTE_IMPORTED) + continue; + if (!bpi->extra || !bpi->extra->vrfleak) + continue; + if ((struct bgp *)bpi->extra->vrfleak->bgp_orig == + from_bgp) { + /* delete route */ + if (debug) + zlog_debug("%s: deleting it", + __func__); + /* withdraw from leak-to vrfs as well */ + vpn_leak_to_vrf_withdraw(bpi); + bgp_aggregate_decrement( + to_bgp, bgp_dest_get_prefix(bn), + bpi, afi, safi); + bgp_path_info_delete(bn, bpi); + bgp_process(to_bgp, bn, bpi, afi, safi); + bgp_mplsvpn_path_nh_label_unlink( + bpi->extra->vrfleak->parent); + } + } + } + } +} + +void vpn_leak_from_vrf_update_all(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi) +{ + struct bgp_dest *bn; + struct bgp_path_info *bpi; + int debug = BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF); + + if (debug) + zlog_debug("%s: entry, afi=%d, vrf=%s", __func__, afi, + from_bgp->name_pretty); + + for (bn = bgp_table_top(from_bgp->rib[afi][SAFI_UNICAST]); bn; + bn = bgp_route_next(bn)) { + + if (debug) + zlog_debug("%s: node=%p", __func__, bn); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; + bpi = bpi->next) { + if (debug) + zlog_debug( + "%s: calling vpn_leak_from_vrf_update", + __func__); + vpn_leak_from_vrf_update(to_bgp, from_bgp, bpi); + } + } +} + +static struct bgp *bgp_lookup_by_rd(struct bgp_path_info *bpi, + struct prefix_rd *rd, afi_t afi) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + if (!rd) + return NULL; + + /* If ACCEPT_OWN is not enabled for this path - return. */ + if (!CHECK_FLAG(bpi->flags, BGP_PATH_ACCEPT_OWN)) + return NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (!CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) + continue; + + /* Check if we have source VRF by RD value */ + if (memcmp(&bgp->vpn_policy[afi].tovpn_rd.val, rd->val, + ECOMMUNITY_SIZE) == 0) + return bgp; + } + + return NULL; +} + +static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ + struct bgp *from_bgp, /* from */ + struct bgp_path_info *path_vpn, + struct prefix_rd *prd) +{ + const struct prefix *p = bgp_dest_get_prefix(path_vpn->net); + afi_t afi = family2afi(p->family); + + struct attr static_attr = {0}; + struct attr *new_attr = NULL; + struct bgp_dest *bn; + safi_t safi = SAFI_UNICAST; + const char *debugmsg; + struct prefix nexthop_orig; + mpls_label_t *label_pnt = NULL; + uint8_t num_labels = 0; + int nexthop_self_flag = 1; + struct bgp_path_info *bpi_ultimate = NULL; + struct bgp_path_info *bpi; + int origin_local = 0; + struct bgp *src_vrf; + struct interface *ifp = NULL; + char rd_buf[RD_ADDRSTRLEN]; + + int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); + + if (!vpn_leak_from_vpn_active(to_bgp, afi, &debugmsg)) { + if (debug) + zlog_debug( + "%s: from vpn (%s) to vrf (%s), skipping: %s", + __func__, from_bgp->name_pretty, + to_bgp->name_pretty, debugmsg); + return; + } + + /* + * For VRF-2-VRF route-leaking, + * the source will be the originating VRF. + * + * If ACCEPT_OWN mechanism is enabled, then we SHOULD(?) + * get the source VRF (BGP) by looking at the RD. + */ + struct bgp *src_bgp = bgp_lookup_by_rd(path_vpn, prd, afi); + + if (path_vpn->extra && path_vpn->extra->vrfleak && + path_vpn->extra->vrfleak->bgp_orig) + src_vrf = path_vpn->extra->vrfleak->bgp_orig; + else if (src_bgp) + src_vrf = src_bgp; + else + src_vrf = from_bgp; + + /* Check for intersection of route targets */ + if (!ecommunity_include( + to_bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + bgp_attr_get_ecommunity(path_vpn->attr))) { + if (debug) + zlog_debug( + "from vpn (%s) to vrf (%s), skipping after no intersection of route targets", + from_bgp->name_pretty, to_bgp->name_pretty); + return; + } + + rd_buf[0] = '\0'; + if (debug && prd) + prefix_rd2str(prd, rd_buf, sizeof(rd_buf), to_bgp->asnotation); + + /* A route MUST NOT ever be accepted back into its source VRF, even if + * it carries one or more RTs that match that VRF. + */ + if (CHECK_FLAG(path_vpn->flags, BGP_PATH_ACCEPT_OWN) && prd && + memcmp(&prd->val, &to_bgp->vpn_policy[afi].tovpn_rd.val, + ECOMMUNITY_SIZE) == 0) { + if (debug) + zlog_debug( + "%s: skipping import, match RD (%s) of src VRF (%s) and the prefix (%pFX)", + __func__, rd_buf, to_bgp->name_pretty, p); + return; + } + + if (debug) + zlog_debug("%s: updating RD %s, %pFX to %s", __func__, rd_buf, + p, to_bgp->name_pretty); + + /* shallow copy */ + static_attr = *path_vpn->attr; + + struct ecommunity *old_ecom; + struct ecommunity *new_ecom; + + /* If doing VRF-to-VRF leaking, strip RTs. */ + old_ecom = bgp_attr_get_ecommunity(&static_attr); + if (old_ecom && CHECK_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + new_ecom = ecommunity_dup(old_ecom); + ecommunity_strip_rts(new_ecom); + bgp_attr_set_ecommunity(&static_attr, new_ecom); + + if (new_ecom->size == 0) { + ecommunity_free(&new_ecom); + bgp_attr_set_ecommunity(&static_attr, NULL); + } + + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } + + community_strip_accept_own(&static_attr); + + bn = bgp_afi_node_get(to_bgp->rib[afi][safi], afi, safi, p, NULL); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->parent == path_vpn) + break; + } + + if (bpi && leak_update_nexthop_valid(to_bgp, bn, &static_attr, afi, safi, + path_vpn, bpi, src_vrf, p, debug)) + SET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_VALID); + else + UNSET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_VALID); + + /* + * Nexthop: stash and clear + * + * Nexthop is valid in context of VPN core, but not in destination vrf. + * Stash it for later label resolution by vrf ingress path and then + * overwrite with 0, i.e., "me", for the sake of vrf advertisement. + */ + uint8_t nhfamily = NEXTHOP_FAMILY(path_vpn->attr->mp_nexthop_len); + + memset(&nexthop_orig, 0, sizeof(nexthop_orig)); + nexthop_orig.family = nhfamily; + + /* If the path has accept-own community and the source VRF + * is valid, reset next-hop to self, to allow importing own + * routes between different VRFs on the same node. + */ + + if (src_bgp) + subgroup_announce_reset_nhop(nhfamily, &static_attr); + + bpi_ultimate = bgp_get_imported_bpi_ultimate(path_vpn); + + /* The nh ifindex may not be defined (when the route is + * imported from the network statement => BGP_ROUTE_STATIC) + * or to the real interface. + * Rewrite the nh ifindex to VRF's interface. + * Let the kernel to decide with double lookup the real next-hop + * interface when installing the route. + */ + if (src_vrf->vrf_id != VRF_DEFAULT && + (src_bgp || bpi_ultimate->sub_type == BGP_ROUTE_STATIC || + bpi_ultimate->sub_type == BGP_ROUTE_REDISTRIBUTE)) { + ifp = if_get_vrf_loopback(src_vrf->vrf_id); + if (ifp) + static_attr.nh_ifindex = ifp->ifindex; + } + + switch (nhfamily) { + case AF_INET: + /* save */ + nexthop_orig.u.prefix4 = path_vpn->attr->mp_nexthop_global_in; + nexthop_orig.prefixlen = IPV4_MAX_BITLEN; + + if (CHECK_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + static_attr.nexthop.s_addr = + nexthop_orig.u.prefix4.s_addr; + + static_attr.mp_nexthop_global_in = + path_vpn->attr->mp_nexthop_global_in; + static_attr.mp_nexthop_len = + path_vpn->attr->mp_nexthop_len; + } + static_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + break; + case AF_INET6: + /* save */ + nexthop_orig.u.prefix6 = path_vpn->attr->mp_nexthop_global; + nexthop_orig.prefixlen = IPV6_MAX_BITLEN; + + if (CHECK_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + static_attr.mp_nexthop_global = nexthop_orig.u.prefix6; + } + break; + } + + if (!ifp && static_attr.nh_ifindex) + ifp = if_lookup_by_index(static_attr.nh_ifindex, + src_vrf->vrf_id); + + if (ifp && if_is_operative(ifp)) + SET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_IF_OPERSTATE); + else + UNSET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_IF_OPERSTATE); + + /* + * route map handling + */ + if (to_bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN]) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = to_bgp->peer_self; + info.attr = &static_attr; + info.extra = path_vpn->extra; /* Used for source-vrf filter */ + ret = route_map_apply(to_bgp->vpn_policy[afi] + .rmap[BGP_VPN_POLICY_DIR_FROMVPN], + p, &info); + if (RMAP_DENYMATCH == ret) { + bgp_attr_flush(&static_attr); /* free any added parts */ + if (debug) + zlog_debug( + "%s: vrf %s vpn-policy route map \"%s\" says DENY, returning", + __func__, to_bgp->name_pretty, + to_bgp->vpn_policy[afi] + .rmap[BGP_VPN_POLICY_DIR_FROMVPN] + ->name); + return; + } + /* + * if route-map changed nexthop, don't nexthop-self on output + */ + if (!CHECK_FLAG(static_attr.rmap_change_flags, + BATTR_RMAP_NEXTHOP_UNCHANGED)) + nexthop_self_flag = 0; + } + + new_attr = bgp_attr_intern(&static_attr); + bgp_attr_flush(&static_attr); + + /* + * ensure labels are copied + * + * However, there is a special case: if the route originated in + * another local VRF (as opposed to arriving via VPN), then the + * nexthop is reached by hairpinning through this router (me) + * using IP forwarding only (no LSP). Therefore, the route + * imported to the VRF should not have labels attached. Note + * that nexthop tracking is also involved: eliminating the + * labels for these routes enables the non-labeled nexthops + * from the originating VRF to be considered valid for this route. + */ + if (!CHECK_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + /* + * if original route was unicast, + * then it did not arrive over vpn + */ + if (bpi_ultimate->net) { + struct bgp_table *table; + + table = bgp_dest_table(bpi_ultimate->net); + if (table && (table->safi == SAFI_UNICAST)) + origin_local = 1; + } + + num_labels = origin_local ? 0 + : BGP_PATH_INFO_NUM_LABELS(path_vpn); + label_pnt = num_labels ? path_vpn->extra->labels->label : NULL; + } + + if (debug) + zlog_debug("%s: pfx %pBD: num_labels %d", __func__, + path_vpn->net, num_labels); + + if (!leak_update(to_bgp, bn, new_attr, afi, safi, path_vpn, label_pnt, + num_labels, src_vrf, &nexthop_orig, nexthop_self_flag, + debug)) + bgp_dest_unlock_node(bn); +} + +bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp, + struct attr *attr, afi_t afi) +{ + struct ecommunity *ecom_route_target = bgp_attr_get_ecommunity(attr); + int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); + struct listnode *node; + const char *debugmsg; + struct bgp *to_bgp; + + /* Loop over BGP instances */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, to_bgp)) { + if (!vpn_leak_from_vpn_active(to_bgp, afi, &debugmsg)) { + if (debug) + zlog_debug( + "%s: from vpn (%s) to vrf (%s) afi %s, skipping: %s", + __func__, from_bgp->name_pretty, + to_bgp->name_pretty, afi2str(afi), + debugmsg); + continue; + } + + /* Check for intersection of route targets */ + if (!ecommunity_include( + to_bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + ecom_route_target)) { + if (debug) + zlog_debug( + "%s: from vpn (%s) to vrf (%s) afi %s %s, skipping after no intersection of route targets", + __func__, from_bgp->name_pretty, + to_bgp->name_pretty, afi2str(afi), + ecommunity_str(ecom_route_target)); + continue; + } + return false; + } + + if (debug) + zlog_debug( + "%s: from vpn (%s) afi %s %s, no import - must be filtered", + __func__, from_bgp->name_pretty, afi2str(afi), + ecommunity_str(ecom_route_target)); + + return true; +} + +void vpn_leak_to_vrf_update(struct bgp *from_bgp, + struct bgp_path_info *path_vpn, + struct prefix_rd *prd) +{ + struct listnode *mnode, *mnnode; + struct bgp *bgp; + + int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); + + if (debug) + zlog_debug("%s: start (path_vpn=%p)", __func__, path_vpn); + + /* Loop over VRFs */ + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + if (!path_vpn->extra || !path_vpn->extra->vrfleak || + path_vpn->extra->vrfleak->bgp_orig != bgp) { /* no loop */ + vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn, + prd); + } + } +} + +void vpn_leak_to_vrf_withdraw(struct bgp_path_info *path_vpn) +{ + const struct prefix *p; + afi_t afi; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp; + struct listnode *mnode, *mnnode; + struct bgp_dest *bn; + struct bgp_path_info *bpi; + const char *debugmsg; + + int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); + + if (debug) + zlog_debug("%s: entry: p=%pBD, type=%d, sub_type=%d", __func__, + path_vpn->net, path_vpn->type, path_vpn->sub_type); + + if (debug) + zlog_debug("%s: start (path_vpn=%p)", __func__, path_vpn); + + if (!path_vpn->net) { +#ifdef ENABLE_BGP_VNC + /* BGP_ROUTE_RFP routes do not have path_vpn->net set (yet) */ + if (path_vpn->type == ZEBRA_ROUTE_BGP + && path_vpn->sub_type == BGP_ROUTE_RFP) { + + return; + } +#endif + if (debug) + zlog_debug( + "%s: path_vpn->net unexpectedly NULL, no prefix, bailing", + __func__); + return; + } + + p = bgp_dest_get_prefix(path_vpn->net); + afi = family2afi(p->family); + + /* Loop over VRFs */ + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + if (!vpn_leak_from_vpn_active(bgp, afi, &debugmsg)) { + if (debug) + zlog_debug("%s: from %s, skipping: %s", + __func__, bgp->name_pretty, + debugmsg); + continue; + } + + /* Check for intersection of route targets */ + if (!ecommunity_include( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + bgp_attr_get_ecommunity(path_vpn->attr))) { + + continue; + } + + if (debug) + zlog_debug("%s: withdrawing from vrf %s", __func__, + bgp->name_pretty); + + bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, NULL); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; + bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + (struct bgp_path_info *)bpi->extra->vrfleak->parent == + path_vpn) { + break; + } + } + + if (bpi) { + if (debug) + zlog_debug("%s: deleting bpi %p", __func__, + bpi); + bgp_aggregate_decrement(bgp, p, bpi, afi, safi); + bgp_path_info_delete(bn, bpi); + bgp_process(bgp, bn, bpi, afi, safi); + } + bgp_dest_unlock_node(bn); + } +} + +void vpn_leak_to_vrf_withdraw_all(struct bgp *to_bgp, afi_t afi) +{ + struct bgp_dest *bn; + struct bgp_path_info *bpi, *next; + safi_t safi = SAFI_UNICAST; + int debug = BGP_DEBUG(vpn, VPN_LEAK_TO_VRF); + + if (debug) + zlog_debug("%s: entry", __func__); + /* + * Walk vrf table, delete bpi with bgp_orig in a different vrf + */ + for (bn = bgp_table_top(to_bgp->rib[afi][safi]); bn; + bn = bgp_route_next(bn)) { + for (bpi = bgp_dest_get_bgp_path_info(bn); + (bpi != NULL) && (next = bpi->next, 1); bpi = next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->bgp_orig != to_bgp && + bpi->extra->vrfleak->parent && + is_pi_family_vpn(bpi->extra->vrfleak->parent)) { + /* delete route */ + bgp_aggregate_decrement(to_bgp, + bgp_dest_get_prefix(bn), + bpi, afi, safi); + bgp_path_info_delete(bn, bpi); + bgp_process(to_bgp, bn, bpi, afi, safi); + } + } + } +} + +void vpn_leak_no_retain(struct bgp *to_bgp, struct bgp *vpn_from, afi_t afi) +{ + struct bgp_dest *pdest; + safi_t safi = SAFI_MPLS_VPN; + + assert(vpn_from); + + /* + * Walk vpn table + */ + for (pdest = bgp_table_top(vpn_from->rib[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + struct bgp_table *table; + struct bgp_dest *bn; + struct bgp_path_info *bpi; + + /* This is the per-RD table of prefixes */ + table = bgp_dest_get_bgp_table_info(pdest); + + if (!table) + continue; + + for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { + struct bgp_path_info *next; + + for (bpi = bgp_dest_get_bgp_path_info(bn); + (bpi != NULL) && (next = bpi->next, 1); + bpi = next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->bgp_orig == to_bgp) + continue; + + if (bpi->sub_type != BGP_ROUTE_NORMAL) + continue; + + if (!vpn_leak_to_vrf_no_retain_filter_check( + vpn_from, bpi->attr, afi)) + /* do not filter */ + continue; + + bgp_unlink_nexthop(bpi); + bgp_rib_remove(bn, bpi, bpi->peer, afi, safi); + } + } + } +} + +void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *vpn_from, + afi_t afi) +{ + struct bgp_dest *pdest; + safi_t safi = SAFI_MPLS_VPN; + + assert(vpn_from); + + /* + * Walk vpn table + */ + for (pdest = bgp_table_top(vpn_from->rib[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + struct bgp_table *table; + struct bgp_dest *bn; + struct bgp_path_info *bpi; + + /* This is the per-RD table of prefixes */ + table = bgp_dest_get_bgp_table_info(pdest); + + if (!table) + continue; + + for (bn = bgp_table_top(table); bn; bn = bgp_route_next(bn)) { + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; + bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->bgp_orig == to_bgp) + continue; + + vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from, + bpi, NULL); + } + } + } +} + +/* + * This function is called for definition/deletion/change to a route-map + */ +static void vpn_policy_routemap_update(struct bgp *bgp, const char *rmap_name) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT); + afi_t afi; + struct route_map *rmap; + + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT + && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) { + + return; + } + + rmap = route_map_lookup_by_name(rmap_name); /* NULL if deleted */ + + for (afi = 0; afi < AFI_MAX; ++afi) { + + if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN] + && !strcmp(rmap_name, + bgp->vpn_policy[afi] + .rmap_name[BGP_VPN_POLICY_DIR_TOVPN])) { + + if (debug) + zlog_debug( + "%s: rmap \"%s\" matches vrf-policy tovpn for as %d afi %s", + __func__, rmap_name, bgp->as, + afi2str(afi)); + + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + if (debug) + zlog_debug("%s: after vpn_leak_prechange", + __func__); + + /* in case of definition/deletion */ + bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN] = + rmap; + + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + if (debug) + zlog_debug("%s: after vpn_leak_postchange", + __func__); + } + + if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN] + && !strcmp(rmap_name, + bgp->vpn_policy[afi] + .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN])) { + + if (debug) { + zlog_debug("%s: rmap \"%s\" matches vrf-policy fromvpn for as %d afi %s", + __func__, rmap_name, bgp->as, + afi2str(afi)); + } + + vpn_leak_prechange(BGP_VPN_POLICY_DIR_FROMVPN, afi, + bgp_get_default(), bgp); + + /* in case of definition/deletion */ + bgp->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN] = + rmap; + + vpn_leak_postchange(BGP_VPN_POLICY_DIR_FROMVPN, afi, + bgp_get_default(), bgp); + } + } +} + +/* This API is used during router-id change, reflect VPNs + * auto RD and RT values and readvertise routes to VPN table. + */ +void vpn_handle_router_id_update(struct bgp *bgp, bool withdraw, + bool is_config) +{ + afi_t afi; + int debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) + | BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + char *vname; + const char *export_name; + char buf[RD_ADDRSTRLEN]; + struct bgp *bgp_import; + struct listnode *node; + struct ecommunity *ecom; + enum vpn_policy_direction idir, edir; + + /* + * Router-id change that is not explicitly configured + * (a change from zebra, frr restart for example) + * should not replace a configured vpn RD/RT. + */ + if (!is_config) { + if (debug) + zlog_debug("%s: skipping non explicit router-id change", + __func__); + return; + } + + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT + && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + return; + + export_name = bgp->name ? bgp->name : VRF_DEFAULT_NAME; + idir = BGP_VPN_POLICY_DIR_FROMVPN; + edir = BGP_VPN_POLICY_DIR_TOVPN; + + for (afi = 0; afi < AFI_MAX; ++afi) { + if (!vpn_leak_to_vpn_active(bgp, afi, NULL, false)) + continue; + + if (withdraw) { + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, + afi, bgp_get_default(), bgp); + if (debug) + zlog_debug("%s: %s after to_vpn vpn_leak_prechange", + __func__, export_name); + + /* Remove import RT from VRFs */ + ecom = bgp->vpn_policy[afi].rtlist[edir]; + for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi]. + export_vrf, node, vname)) { + if (strcmp(vname, VRF_DEFAULT_NAME) == 0) + bgp_import = bgp_get_default(); + else + bgp_import = bgp_lookup_by_name(vname); + if (!bgp_import) + continue; + + ecommunity_del_val( + bgp_import->vpn_policy[afi] + .rtlist[idir], + (struct ecommunity_val *)ecom->val); + } + } else { + /* New router-id derive auto RD and RT and export + * to VPN + */ + form_auto_rd(bgp->router_id, bgp->vrf_rd_id, + &bgp->vrf_prd_auto); + bgp->vpn_policy[afi].tovpn_rd = bgp->vrf_prd_auto; + prefix_rd2str(&bgp->vpn_policy[afi].tovpn_rd, buf, + sizeof(buf), bgp->asnotation); + + /* free up pre-existing memory if any and allocate + * the ecommunity attribute with new RD/RT + */ + if (bgp->vpn_policy[afi].rtlist[edir]) + ecommunity_free( + &bgp->vpn_policy[afi].rtlist[edir]); + bgp->vpn_policy[afi].rtlist[edir] = ecommunity_str2com( + buf, ECOMMUNITY_ROUTE_TARGET, 0); + + /* Update import_vrf rt_list */ + ecom = bgp->vpn_policy[afi].rtlist[edir]; + for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi]. + export_vrf, node, vname)) { + if (strcmp(vname, VRF_DEFAULT_NAME) == 0) + bgp_import = bgp_get_default(); + else + bgp_import = bgp_lookup_by_name(vname); + if (!bgp_import) + continue; + if (bgp_import->vpn_policy[afi].rtlist[idir]) + bgp_import->vpn_policy[afi].rtlist[idir] + = ecommunity_merge( + bgp_import->vpn_policy[afi] + .rtlist[idir], ecom); + else + bgp_import->vpn_policy[afi].rtlist[idir] + = ecommunity_dup(ecom); + } + + /* Update routes to VPN */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, + afi, bgp_get_default(), + bgp); + if (debug) + zlog_debug("%s: %s after to_vpn vpn_leak_postchange", + __func__, export_name); + } + } +} + +void vpn_policy_routemap_event(const char *rmap_name) +{ + int debug = BGP_DEBUG(vpn, VPN_LEAK_RMAP_EVENT); + struct listnode *mnode, *mnnode; + struct bgp *bgp; + + if (debug) + zlog_debug("%s: entry", __func__); + + if (bm->bgp == NULL) /* may be called during cleanup */ + return; + + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) + vpn_policy_routemap_update(bgp, rmap_name); +} + +void vrf_import_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi, safi_t safi) +{ + const char *export_name; + enum vpn_policy_direction idir, edir; + char *vname, *tmp_name; + char buf[RD_ADDRSTRLEN]; + struct ecommunity *ecom; + bool first_export = false; + int debug; + struct listnode *node; + bool is_inst_match = false; + + export_name = to_bgp->name ? to_bgp->name : VRF_DEFAULT_NAME; + idir = BGP_VPN_POLICY_DIR_FROMVPN; + edir = BGP_VPN_POLICY_DIR_TOVPN; + + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + /* + * Cross-ref both VRFs. Also, note if this is the first time + * any VRF is importing from "import_vrf". + */ + vname = (from_bgp->name ? XSTRDUP(MTYPE_TMP, from_bgp->name) + : XSTRDUP(MTYPE_TMP, VRF_DEFAULT_NAME)); + + /* Check the import_vrf list of destination vrf for the source vrf name, + * insert otherwise. + */ + for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi].import_vrf, + node, tmp_name)) { + if (strcmp(vname, tmp_name) == 0) { + is_inst_match = true; + break; + } + } + if (!is_inst_match) + listnode_add(to_bgp->vpn_policy[afi].import_vrf, + vname); + else + XFREE(MTYPE_TMP, vname); + + /* Check if the source vrf already exports to any vrf, + * first time export requires to setup auto derived RD/RT values. + * Add the destination vrf name to export vrf list if it is + * not present. + */ + is_inst_match = false; + vname = XSTRDUP(MTYPE_TMP, export_name); + if (!listcount(from_bgp->vpn_policy[afi].export_vrf)) { + first_export = true; + } else { + for (ALL_LIST_ELEMENTS_RO(from_bgp->vpn_policy[afi].export_vrf, + node, tmp_name)) { + if (strcmp(vname, tmp_name) == 0) { + is_inst_match = true; + break; + } + } + } + if (!is_inst_match) + listnode_add(from_bgp->vpn_policy[afi].export_vrf, + vname); + else + XFREE(MTYPE_TMP, vname); + + /* Update import RT for current VRF using export RT of the VRF we're + * importing from. First though, make sure "import_vrf" has that + * set. + */ + if (first_export) { + form_auto_rd(from_bgp->router_id, from_bgp->vrf_rd_id, + &from_bgp->vrf_prd_auto); + from_bgp->vpn_policy[afi].tovpn_rd = from_bgp->vrf_prd_auto; + SET_FLAG(from_bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET); + prefix_rd2str(&from_bgp->vpn_policy[afi].tovpn_rd, buf, + sizeof(buf), from_bgp->asnotation); + from_bgp->vpn_policy[afi].rtlist[edir] = + ecommunity_str2com(buf, ECOMMUNITY_ROUTE_TARGET, 0); + SET_FLAG(from_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_EXPORT); + from_bgp->vpn_policy[afi].tovpn_label = + BGP_PREVENT_VRF_2_VRF_LEAK; + } + ecom = from_bgp->vpn_policy[afi].rtlist[edir]; + if (to_bgp->vpn_policy[afi].rtlist[idir]) + to_bgp->vpn_policy[afi].rtlist[idir] = + ecommunity_merge(to_bgp->vpn_policy[afi] + .rtlist[idir], ecom); + else + to_bgp->vpn_policy[afi].rtlist[idir] = ecommunity_dup(ecom); + SET_FLAG(to_bgp->af_flags[afi][safi], BGP_CONFIG_VRF_TO_VRF_IMPORT); + + if (debug) { + const char *from_name; + char *ecom1, *ecom2; + + from_name = from_bgp->name ? from_bgp->name : + VRF_DEFAULT_NAME; + + ecom1 = ecommunity_ecom2str( + to_bgp->vpn_policy[afi].rtlist[idir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + ecom2 = ecommunity_ecom2str( + to_bgp->vpn_policy[afi].rtlist[edir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + zlog_debug( + "%s from %s to %s first_export %u import-rt %s export-rt %s", + __func__, from_name, export_name, first_export, ecom1, + ecom2); + + ecommunity_strfree(&ecom1); + ecommunity_strfree(&ecom2); + } + + /* Does "import_vrf" first need to export its routes or that + * is already done and we just need to import those routes + * from the global table? + */ + if (first_export) + vpn_leak_postchange(edir, afi, bgp_get_default(), from_bgp); + else + vpn_leak_postchange(idir, afi, bgp_get_default(), to_bgp); +} + +void vrf_unimport_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi, safi_t safi) +{ + const char *export_name, *tmp_name; + enum vpn_policy_direction idir, edir; + char *vname; + struct ecommunity *ecom = NULL; + struct listnode *node; + int debug; + + export_name = to_bgp->name ? to_bgp->name : VRF_DEFAULT_NAME; + tmp_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; + idir = BGP_VPN_POLICY_DIR_FROMVPN; + edir = BGP_VPN_POLICY_DIR_TOVPN; + + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + /* Were we importing from "import_vrf"? */ + for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi].import_vrf, node, + vname)) { + if (strcmp(vname, tmp_name) == 0) + break; + } + + /* + * We do not check in the cli if the passed in bgp + * instance is actually imported into us before + * we call this function. As such if we do not + * find this in the import_vrf list than + * we just need to return safely. + */ + if (!vname) + return; + + if (debug) + zlog_debug("%s from %s to %s", __func__, tmp_name, export_name); + + /* Remove "import_vrf" from our import list. */ + listnode_delete(to_bgp->vpn_policy[afi].import_vrf, vname); + XFREE(MTYPE_TMP, vname); + + /* Remove routes imported from "import_vrf". */ + /* TODO: In the current logic, we have to first remove all + * imported routes and then (if needed) import back routes + */ + vpn_leak_prechange(idir, afi, bgp_get_default(), to_bgp); + + if (to_bgp->vpn_policy[afi].import_vrf->count == 0) { + if (!to_bgp->vpn_policy[afi].rmap[idir]) + UNSET_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT); + if (to_bgp->vpn_policy[afi].rtlist[idir]) + ecommunity_free(&to_bgp->vpn_policy[afi].rtlist[idir]); + } else { + ecom = from_bgp->vpn_policy[afi].rtlist[edir]; + if (ecom) + ecommunity_del_val(to_bgp->vpn_policy[afi].rtlist[idir], + (struct ecommunity_val *)ecom->val); + vpn_leak_postchange(idir, afi, bgp_get_default(), to_bgp); + } + + /* + * What? + * So SA is assuming that since the ALL_LIST_ELEMENTS_RO + * below is checking for NULL that export_vrf can be + * NULL, consequently it is complaining( like a cabbage ) + * that we could dereference and crash in the listcount(..) + * check below. + * So make it happy, under protest, with liberty and justice + * for all. + */ + assert(from_bgp->vpn_policy[afi].export_vrf); + + /* Remove us from "import_vrf's" export list. If no other VRF + * is importing from "import_vrf", cleanup appropriately. + */ + for (ALL_LIST_ELEMENTS_RO(from_bgp->vpn_policy[afi].export_vrf, + node, vname)) { + if (strcmp(vname, export_name) == 0) + break; + } + + /* + * If we have gotten to this point then the vname must + * exist. If not, we are in a world of trouble and + * have slag sitting around. + * + * import_vrf and export_vrf must match in having + * the in/out names as appropriate. + * export_vrf list could have been cleaned up + * as part of no router bgp source instnace. + */ + if (!vname) + return; + + listnode_delete(from_bgp->vpn_policy[afi].export_vrf, vname); + XFREE(MTYPE_TMP, vname); + + if (!listcount(from_bgp->vpn_policy[afi].export_vrf)) { + vpn_leak_prechange(edir, afi, bgp_get_default(), from_bgp); + ecommunity_free(&from_bgp->vpn_policy[afi].rtlist[edir]); + UNSET_FLAG(from_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_EXPORT); + memset(&from_bgp->vpn_policy[afi].tovpn_rd, 0, + sizeof(struct prefix_rd)); + UNSET_FLAG(from_bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET); + from_bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; + + } +} + +/* For testing purpose, static route of MPLS-VPN. */ +DEFUN (vpnv4_network, + vpnv4_network_cmd, + "network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", + "Specify a network to announce via BGP\n" + "IPv4 prefix\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "VPN NLRI label (tag)\n" + "VPN NLRI label (tag)\n" + "Label value\n") +{ + int idx_ipv4_prefixlen = 1; + int idx_ext_community = 3; + int idx_label = 5; + + return bgp_static_set(vty, false, argv[idx_ipv4_prefixlen]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_IP, SAFI_MPLS_VPN, NULL, + 0, 0, 0, NULL, NULL, NULL, NULL); +} + +DEFUN (vpnv4_network_route_map, + vpnv4_network_route_map_cmd, + "network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575) route-map RMAP_NAME", + "Specify a network to announce via BGP\n" + "IPv4 prefix\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "VPN NLRI label (tag)\n" + "VPN NLRI label (tag)\n" + "Label value\n" + "route map\n" + "route map name\n") +{ + int idx_ipv4_prefixlen = 1; + int idx_ext_community = 3; + int idx_label = 5; + int idx_rmap = 7; + + return bgp_static_set(vty, false, argv[idx_ipv4_prefixlen]->arg, + argv[idx_ext_community]->arg, argv[idx_label]->arg, + AFI_IP, SAFI_MPLS_VPN, argv[idx_rmap]->arg, 0, 0, + 0, NULL, NULL, NULL, NULL); +} + +/* For testing purpose, static route of MPLS-VPN. */ +DEFUN (no_vpnv4_network, + no_vpnv4_network_cmd, + "no network A.B.C.D/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", + NO_STR + "Specify a network to announce via BGP\n" + "IPv4 prefix\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "VPN NLRI label (tag)\n" + "VPN NLRI label (tag)\n" + "Label value\n") +{ + int idx_ipv4_prefixlen = 2; + int idx_ext_community = 4; + int idx_label = 6; + + return bgp_static_set(vty, true, argv[idx_ipv4_prefixlen]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_IP, SAFI_MPLS_VPN, NULL, + 0, 0, 0, NULL, NULL, NULL, NULL); +} + +DEFUN (vpnv6_network, + vpnv6_network_cmd, + "network X:X::X:X/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575) [route-map RMAP_NAME]", + "Specify a network to announce via BGP\n" + "IPv6 prefix /, e.g., 3ffe::/16\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "VPN NLRI label (tag)\n" + "VPN NLRI label (tag)\n" + "Label value\n" + "route map\n" + "route map name\n") +{ + int idx_ipv6_prefix = 1; + int idx_ext_community = 3; + int idx_label = 5; + int idx_rmap = 7; + + if (argc == 8) + return bgp_static_set(vty, false, argv[idx_ipv6_prefix]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_IP6, + SAFI_MPLS_VPN, argv[idx_rmap]->arg, 0, 0, + 0, NULL, NULL, NULL, NULL); + else + return bgp_static_set(vty, false, argv[idx_ipv6_prefix]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_IP6, + SAFI_MPLS_VPN, NULL, 0, 0, 0, NULL, NULL, + NULL, NULL); +} + +/* For testing purpose, static route of MPLS-VPN. */ +DEFUN (no_vpnv6_network, + no_vpnv6_network_cmd, + "no network X:X::X:X/M rd ASN:NN_OR_IP-ADDRESS:NN (0-1048575)", + NO_STR + "Specify a network to announce via BGP\n" + "IPv6 prefix /, e.g., 3ffe::/16\n" + "Specify Route Distinguisher\n" + "VPN Route Distinguisher\n" + "VPN NLRI label (tag)\n" + "VPN NLRI label (tag)\n" + "Label value\n") +{ + int idx_ipv6_prefix = 2; + int idx_ext_community = 4; + int idx_label = 6; + + return bgp_static_set(vty, true, argv[idx_ipv6_prefix]->arg, + argv[idx_ext_community]->arg, + argv[idx_label]->arg, AFI_IP6, SAFI_MPLS_VPN, + NULL, 0, 0, 0, NULL, NULL, NULL, NULL); +} + +int bgp_show_mpls_vpn(struct vty *vty, afi_t afi, struct prefix_rd *prd, + enum bgp_show_type type, void *output_arg, int tags, + bool use_json) +{ + struct bgp *bgp; + struct bgp_table *table; + uint16_t show_flags = 0; + + if (use_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + bgp = bgp_get_default(); + if (bgp == NULL) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + table = bgp->rib[afi][SAFI_MPLS_VPN]; + return bgp_show_table_rd(vty, bgp, afi, SAFI_MPLS_VPN, table, prd, type, + output_arg, show_flags); +} + +DEFUN (show_bgp_ip_vpn_all_rd, + show_bgp_ip_vpn_all_rd_cmd, + "show bgp "BGP_AFI_CMD_STR" vpn all [rd ] [json]", + SHOW_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display VPN NLRI specific information\n" + "Display VPN NLRI specific information\n" + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + JSON_STR) +{ + int ret; + struct prefix_rd prd; + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + /* Constrain search if user supplies RD && RD != "all" */ + if (argv_find(argv, argc, "rd", &idx) + && strcmp(argv[idx + 1]->arg, "all")) { + ret = str2prefix_rd(argv[idx + 1]->arg, &prd); + if (!ret) { + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_mpls_vpn(vty, afi, &prd, + bgp_show_type_normal, NULL, 0, + use_json(argc, argv)); + } else { + return bgp_show_mpls_vpn(vty, afi, NULL, + bgp_show_type_normal, NULL, 0, + use_json(argc, argv)); + } + } + return CMD_SUCCESS; +} + +ALIAS(show_bgp_ip_vpn_all_rd, + show_bgp_ip_vpn_rd_cmd, + "show bgp "BGP_AFI_CMD_STR" vpn rd [json]", + SHOW_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display VPN NLRI specific information\n" + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + JSON_STR) + +#ifdef KEEP_OLD_VPN_COMMANDS +DEFUN (show_ip_bgp_vpn_rd, + show_ip_bgp_vpn_rd_cmd, + "show ip bgp "BGP_AFI_CMD_STR" vpn rd ", + SHOW_STR + IP_STR + BGP_STR + BGP_AFI_HELP_STR + BGP_AF_MODIFIER_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n") +{ + int idx_ext_community = argc - 1; + int ret; + struct prefix_rd prd; + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + if (!strcmp(argv[idx_ext_community]->arg, "all")) + return bgp_show_mpls_vpn(vty, afi, NULL, + bgp_show_type_normal, NULL, 0, + 0); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_normal, + NULL, 0, 0); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_all, + show_ip_bgp_vpn_all_cmd, + "show [ip] bgp ", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR) +{ + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) + return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_normal, + NULL, 0, 0); + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_all_tags, + show_ip_bgp_vpn_all_tags_cmd, + "show [ip] bgp all tags", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4/VPNV6 NLRIs\n" + "Display BGP tags for prefixes\n") +{ + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) + return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_normal, + NULL, 1, 0); + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_rd_tags, + show_ip_bgp_vpn_rd_tags_cmd, + "show [ip] bgp rd tags", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Display BGP tags for prefixes\n") +{ + int idx_ext_community = 5; + int ret; + struct prefix_rd prd; + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + if (!strcmp(argv[idx_ext_community]->arg, "all")) + return bgp_show_mpls_vpn(vty, afi, NULL, + bgp_show_type_normal, NULL, 1, + 0); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + return bgp_show_mpls_vpn(vty, afi, &prd, bgp_show_type_normal, + NULL, 1, 0); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_all_neighbor_routes, + show_ip_bgp_vpn_all_neighbor_routes_cmd, + "show [ip] bgp all neighbors A.B.C.D routes [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4/VPNv6 NLRIs\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Display routes learned from neighbor\n" + JSON_STR) +{ + int idx_ipv4 = 6; + union sockunion su; + struct peer *peer; + int ret; + bool uj = use_json(argc, argv); + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + ret = str2sockunion(argv[idx_ipv4]->arg, &su); + if (ret < 0) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx_ipv4]->arg); + return CMD_WARNING; + } + + peer = peer_lookup(NULL, &su); + if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + return bgp_show_mpls_vpn(vty, afi, NULL, bgp_show_type_neighbor, + &su, 0, uj); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_rd_neighbor_routes, + show_ip_bgp_vpn_rd_neighbor_routes_cmd, + "show [ip] bgp rd neighbors A.B.C.D routes [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Display routes learned from neighbor\n" + JSON_STR) +{ + int idx_ext_community = 5; + int idx_ipv4 = 7; + int ret; + union sockunion su; + struct peer *peer; + struct prefix_rd prd; + bool prefix_rd_all = false; + bool uj = use_json(argc, argv); + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + if (!strcmp(argv[idx_ext_community]->arg, "all")) + prefix_rd_all = true; + else { + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "Malformed Route Distinguisher"); + vty_out(vty, "%s\n", + json_object_to_json_string( + json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + } + + ret = str2sockunion(argv[idx_ipv4]->arg, &su); + if (ret < 0) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx_ext_community]->arg); + return CMD_WARNING; + } + + peer = peer_lookup(NULL, &su); + if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + if (prefix_rd_all) + return bgp_show_mpls_vpn(vty, afi, NULL, + bgp_show_type_neighbor, &su, 0, + uj); + else + return bgp_show_mpls_vpn(vty, afi, &prd, + bgp_show_type_neighbor, &su, 0, + uj); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_all_neighbor_advertised_routes, + show_ip_bgp_vpn_all_neighbor_advertised_routes_cmd, + "show [ip] bgp all neighbors A.B.C.D advertised-routes [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4/VPNv6 NLRIs\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Display the routes advertised to a BGP neighbor\n" + JSON_STR) +{ + int idx_ipv4 = 6; + int ret; + struct peer *peer; + union sockunion su; + bool uj = use_json(argc, argv); + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + ret = str2sockunion(argv[idx_ipv4]->arg, &su); + if (ret < 0) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx_ipv4]->arg); + return CMD_WARNING; + } + peer = peer_lookup(NULL, &su); + if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + return show_adj_route_vpn(vty, peer, NULL, AFI_IP, + SAFI_MPLS_VPN, uj); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_vpn_rd_neighbor_advertised_routes, + show_ip_bgp_vpn_rd_neighbor_advertised_routes_cmd, + "show [ip] bgp rd neighbors A.B.C.D advertised-routes [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information for a route distinguisher\n" + "VPN Route Distinguisher\n" + "All VPN Route Distinguishers\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Display the routes advertised to a BGP neighbor\n" + JSON_STR) +{ + int idx_ext_community = 5; + int idx_ipv4 = 7; + int ret; + struct peer *peer; + struct prefix_rd prd; + union sockunion su; + bool uj = use_json(argc, argv); + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + ret = str2sockunion(argv[idx_ipv4]->arg, &su); + if (ret < 0) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "Malformed address"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "Malformed address: %s\n", + argv[idx_ext_community]->arg); + return CMD_WARNING; + } + peer = peer_lookup(NULL, &su); + if (!peer || !peer->afc[afi][SAFI_MPLS_VPN]) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[idx_ext_community]->arg, "all")) + return show_adj_route_vpn(vty, peer, NULL, AFI_IP, + SAFI_MPLS_VPN, uj); + ret = str2prefix_rd(argv[idx_ext_community]->arg, &prd); + if (!ret) { + if (uj) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "Malformed Route Distinguisher"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, + "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + return show_adj_route_vpn(vty, peer, &prd, AFI_IP, + SAFI_MPLS_VPN, uj); + } + return CMD_SUCCESS; +} +#endif /* KEEP_OLD_VPN_COMMANDS */ + +void bgp_mplsvpn_init(void) +{ + install_element(BGP_VPNV4_NODE, &vpnv4_network_cmd); + install_element(BGP_VPNV4_NODE, &vpnv4_network_route_map_cmd); + install_element(BGP_VPNV4_NODE, &no_vpnv4_network_cmd); + + install_element(BGP_VPNV6_NODE, &vpnv6_network_cmd); + install_element(BGP_VPNV6_NODE, &no_vpnv6_network_cmd); + + install_element(VIEW_NODE, &show_bgp_ip_vpn_all_rd_cmd); + install_element(VIEW_NODE, &show_bgp_ip_vpn_rd_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_cmd); + install_element(VIEW_NODE, &show_ip_bgp_vpn_all_cmd); + install_element(VIEW_NODE, &show_ip_bgp_vpn_all_tags_cmd); + install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_tags_cmd); + install_element(VIEW_NODE, &show_ip_bgp_vpn_all_neighbor_routes_cmd); + install_element(VIEW_NODE, &show_ip_bgp_vpn_rd_neighbor_routes_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_vpn_all_neighbor_advertised_routes_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_vpn_rd_neighbor_advertised_routes_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ +} + +vrf_id_t get_first_vrf_for_redirect_with_rt(struct ecommunity *eckey) +{ + struct listnode *mnode, *mnnode; + struct bgp *bgp; + afi_t afi = AFI_IP; + + if (eckey->unit_size == IPV6_ECOMMUNITY_SIZE) + afi = AFI_IP6; + + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + struct ecommunity *ec; + + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + ec = bgp->vpn_policy[afi].import_redirect_rtlist; + + if (ec && eckey->unit_size != ec->unit_size) + continue; + + if (ecommunity_include(ec, eckey)) + return bgp->vrf_id; + } + return VRF_UNKNOWN; +} + +/* + * The purpose of this function is to process leaks that were deferred + * from earlier per-vrf configuration due to not-yet-existing default + * vrf, in other words, configuration such as: + * + * router bgp MMM vrf FOO + * address-family ipv4 unicast + * rd vpn export 1:1 + * exit-address-family + * + * router bgp NNN + * ... + * + * This function gets called when the default instance ("router bgp NNN") + * is created. + */ +void vpn_leak_postchange_all(void) +{ + struct listnode *next; + struct bgp *bgp; + struct bgp *bgp_default = bgp_get_default(); + + assert(bgp_default); + + /* First, do any exporting from VRFs to the single VPN RIB */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { + + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + vpn_leak_postchange( + BGP_VPN_POLICY_DIR_TOVPN, + AFI_IP, + bgp_default, + bgp); + + vpn_leak_postchange( + BGP_VPN_POLICY_DIR_TOVPN, + AFI_IP6, + bgp_default, + bgp); + } + + /* Now, do any importing to VRFs from the single VPN RIB */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { + + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + vpn_leak_postchange( + BGP_VPN_POLICY_DIR_FROMVPN, + AFI_IP, + bgp_default, + bgp); + + vpn_leak_postchange( + BGP_VPN_POLICY_DIR_FROMVPN, + AFI_IP6, + bgp_default, + bgp); + } +} + +/* When a bgp vrf instance is unconfigured, remove its routes + * from the VPN table and this vrf could be importing routes from other + * bgp vrf instnaces, unimport them. + * VRF X and VRF Y are exporting routes to each other. + * When VRF X is deleted, unimport its routes from all target vrfs, + * also VRF Y should unimport its routes from VRF X table. + * This will ensure VPN table is cleaned up appropriately. + */ +void bgp_vpn_leak_unimport(struct bgp *from_bgp) +{ + struct bgp *bgp_default = bgp_get_default(); + struct bgp *to_bgp; + const char *tmp_name; + char *vname; + struct listnode *node, *next; + safi_t safi = SAFI_UNICAST; + afi_t afi; + bool is_vrf_leak_bind; + int debug; + + if (from_bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + return; + + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + tmp_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; + + for (afi = 0; afi < AFI_MAX; ++afi) { + /* vrf leak is for IPv4 and IPv6 Unicast only */ + if (afi != AFI_IP && afi != AFI_IP6) + continue; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, to_bgp)) { + if (from_bgp == to_bgp) + continue; + + /* Unimport and remove source vrf from the + * other vrfs import list. + */ + struct vpn_policy *to_vpolicy; + + is_vrf_leak_bind = false; + to_vpolicy = &(to_bgp->vpn_policy[afi]); + for (ALL_LIST_ELEMENTS_RO(to_vpolicy->import_vrf, node, + vname)) { + if (strcmp(vname, tmp_name) == 0) { + is_vrf_leak_bind = true; + break; + } + } + /* skip this bgp instance as there is no leak to this + * vrf instance. + */ + if (!is_vrf_leak_bind) + continue; + + if (debug) + zlog_debug("%s: unimport routes from %s to_bgp %s afi %s import vrfs count %u", + __func__, from_bgp->name_pretty, + to_bgp->name_pretty, afi2str(afi), + to_vpolicy->import_vrf->count); + + vrf_unimport_from_vrf(to_bgp, from_bgp, afi, safi); + + /* readd vrf name as unimport removes import vrf name + * from the destination vrf's import list where the + * `import vrf` configuration still exist. + */ + vname = XSTRDUP(MTYPE_TMP, tmp_name); + listnode_add(to_bgp->vpn_policy[afi].import_vrf, + vname); + SET_FLAG(to_bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT); + + /* If to_bgp exports its routes to the bgp vrf + * which is being deleted, un-import the + * to_bgp routes from VPN. + */ + for (ALL_LIST_ELEMENTS_RO(to_bgp->vpn_policy[afi] + .export_vrf, node, + vname)) { + if (strcmp(vname, tmp_name) == 0) { + vrf_unimport_from_vrf(from_bgp, to_bgp, + afi, safi); + break; + } + } + } + + if (bgp_default && + !CHECK_FLAG(bgp_default->af_flags[afi][SAFI_MPLS_VPN], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL)) { + /* 'from_bgp' instance will be deleted + * so force to unset importation to update VPN labels + */ + UNSET_FLAG(from_bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT); + vpn_leak_no_retain(from_bgp, bgp_default, afi); + } + } + return; +} + +/* When a router bgp is configured, there could be a bgp vrf + * instance importing routes from this newly configured + * bgp vrf instance. Export routes from configured + * bgp vrf to VPN. + * VRF Y has import from bgp vrf x, + * when a bgp vrf x instance is created, export its routes + * to VRF Y instance. + */ +void bgp_vpn_leak_export(struct bgp *from_bgp) +{ + afi_t afi; + const char *export_name; + char *vname; + struct listnode *node, *next; + struct ecommunity *ecom; + enum vpn_policy_direction idir, edir; + safi_t safi = SAFI_UNICAST; + struct bgp *to_bgp; + int debug; + + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + idir = BGP_VPN_POLICY_DIR_FROMVPN; + edir = BGP_VPN_POLICY_DIR_TOVPN; + + export_name = from_bgp->name ? from_bgp->name : VRF_DEFAULT_NAME; + + for (afi = 0; afi < AFI_MAX; ++afi) { + /* vrf leak is for IPv4 and IPv6 Unicast only */ + if (afi != AFI_IP && afi != AFI_IP6) + continue; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, to_bgp)) { + if (from_bgp == to_bgp) + continue; + + /* bgp instance has import list, check to see if newly + * configured bgp instance is the list. + */ + struct vpn_policy *to_vpolicy; + + to_vpolicy = &(to_bgp->vpn_policy[afi]); + for (ALL_LIST_ELEMENTS_RO(to_vpolicy->import_vrf, + node, vname)) { + if (strcmp(vname, export_name) != 0) + continue; + + if (debug) + zlog_debug("%s: found from_bgp %s in to_bgp %s import list, import routes.", + __func__, + export_name, to_bgp->name_pretty); + + ecom = from_bgp->vpn_policy[afi].rtlist[edir]; + /* remove import rt, it will be readded + * as part of import from vrf. + */ + if (ecom) + ecommunity_del_val( + to_vpolicy->rtlist[idir], + (struct ecommunity_val *) + ecom->val); + vrf_import_from_vrf(to_bgp, from_bgp, + afi, safi); + break; + + } + } + } +} + +/* The nexthops values are compared to + * find in the tree the appropriate cache entry + */ +int bgp_mplsvpn_nh_label_bind_cmp( + const struct bgp_mplsvpn_nh_label_bind_cache *a, + const struct bgp_mplsvpn_nh_label_bind_cache *b) +{ + if (prefix_cmp(&a->nexthop, &b->nexthop)) + return 1; + if (a->orig_label > b->orig_label) + return 1; + if (a->orig_label < b->orig_label) + return -1; + return 0; +} + +static void bgp_mplsvpn_nh_label_bind_send_nexthop_label( + struct bgp_mplsvpn_nh_label_bind_cache *bmnc, int cmd) +{ + struct prefix pfx_nh, *p = NULL; + uint8_t num_labels = 0, lsp_num_labels; + mpls_label_t label[MPLS_MAX_LABELS]; + struct nexthop *nh; + ifindex_t ifindex = IFINDEX_INTERNAL; + vrf_id_t vrf_id = VRF_DEFAULT; + uint32_t i; + + if (bmnc->nh == NULL) + return; + nh = bmnc->nh; + switch (nh->type) { + case NEXTHOP_TYPE_IFINDEX: + p = &bmnc->nexthop; + label[num_labels] = bmnc->orig_label; + num_labels += 1; + ifindex = nh->ifindex; + vrf_id = nh->vrf_id; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (nh->type == NEXTHOP_TYPE_IPV4 || + nh->type == NEXTHOP_TYPE_IPV4_IFINDEX) { + pfx_nh.family = AF_INET; + pfx_nh.prefixlen = IPV4_MAX_BITLEN; + IPV4_ADDR_COPY(&pfx_nh.u.prefix4, &nh->gate.ipv4); + } else { + pfx_nh.family = AF_INET6; + pfx_nh.prefixlen = IPV6_MAX_BITLEN; + IPV6_ADDR_COPY(&pfx_nh.u.prefix6, &nh->gate.ipv6); + } + p = &pfx_nh; + if (nh->nh_label) { + if (nh->nh_label->num_labels + 1 > MPLS_MAX_LABELS) { + /* label stack overflow. no label switching will be performed + */ + flog_err(EC_BGP_LABEL, + "%s [Error] BGP label %u->%u to %pFX, forged label stack too big: %u. Abort LSP installation", + bmnc->bgp_vpn->name_pretty, + bmnc->new_label, bmnc->orig_label, + &bmnc->nexthop, + nh->nh_label->num_labels + 1); + return; + } + lsp_num_labels = nh->nh_label->num_labels; + for (i = 0; i < lsp_num_labels; i++) + label[num_labels + i] = nh->nh_label->label[i]; + num_labels = lsp_num_labels; + } + label[num_labels] = bmnc->orig_label; + num_labels += 1; + if (nh->type == NEXTHOP_TYPE_IPV4_IFINDEX || + nh->type == NEXTHOP_TYPE_IPV6_IFINDEX) { + ifindex = nh->ifindex; + vrf_id = nh->vrf_id; + } + break; + case NEXTHOP_TYPE_BLACKHOLE: + return; + } + bgp_zebra_send_nexthop_label(cmd, bmnc->new_label, ifindex, vrf_id, + ZEBRA_LSP_BGP, p, num_labels, &label[0]); +} + +void bgp_mplsvpn_nh_label_bind_free( + struct bgp_mplsvpn_nh_label_bind_cache *bmnc) +{ + if (bmnc->allocation_in_progress) { + bmnc->allocation_in_progress = false; + bgp_mplsvpn_nh_label_bind_cache_del( + &bmnc->bgp_vpn->mplsvpn_nh_label_bind, bmnc); + return; + } + if (bmnc->new_label != MPLS_INVALID_LABEL) { + bgp_mplsvpn_nh_label_bind_send_nexthop_label( + bmnc, ZEBRA_MPLS_LABELS_DELETE); + bgp_lp_release(LP_TYPE_BGP_L3VPN_BIND, bmnc, bmnc->new_label); + } + bgp_mplsvpn_nh_label_bind_cache_del( + &bmnc->bgp_vpn->mplsvpn_nh_label_bind, bmnc); + + if (bmnc->nh) + nexthop_free(bmnc->nh); + + XFREE(MTYPE_MPLSVPN_NH_LABEL_BIND_CACHE, bmnc); +} + +struct bgp_mplsvpn_nh_label_bind_cache * +bgp_mplsvpn_nh_label_bind_new(struct bgp_mplsvpn_nh_label_bind_cache_head *tree, + struct prefix *p, mpls_label_t orig_label) +{ + struct bgp_mplsvpn_nh_label_bind_cache *bmnc; + + bmnc = XCALLOC(MTYPE_MPLSVPN_NH_LABEL_BIND_CACHE, + sizeof(struct bgp_mplsvpn_nh_label_bind_cache)); + bmnc->new_label = MPLS_INVALID_LABEL; + prefix_copy(&bmnc->nexthop, p); + bmnc->orig_label = orig_label; + + LIST_INIT(&(bmnc->paths)); + bgp_mplsvpn_nh_label_bind_cache_add(tree, bmnc); + + return bmnc; +} + +struct bgp_mplsvpn_nh_label_bind_cache *bgp_mplsvpn_nh_label_bind_find( + struct bgp_mplsvpn_nh_label_bind_cache_head *tree, struct prefix *p, + mpls_label_t orig_label) +{ + struct bgp_mplsvpn_nh_label_bind_cache bmnc = {0}; + + if (!tree) + return NULL; + prefix_copy(&bmnc.nexthop, p); + bmnc.orig_label = orig_label; + + return bgp_mplsvpn_nh_label_bind_cache_find(tree, &bmnc); +} + +/* Called to check if the incoming l3vpn path entry + * has mpls label information + */ +bool bgp_mplsvpn_path_uses_valid_mpls_label(struct bgp_path_info *pi) +{ + if (pi->attr && pi->attr->srv6_l3vpn) + /* srv6 sid */ + return false; + + if (pi->attr && + CHECK_FLAG(pi->attr->flag, ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID)) && + pi->attr->label_index != BGP_INVALID_LABEL_INDEX) + /* prefix_sid attribute */ + return false; + + if (!bgp_path_info_has_valid_label(pi)) + /* invalid MPLS label */ + return false; + return true; +} + +mpls_label_t bgp_mplsvpn_nh_label_bind_get_label(struct bgp_path_info *pi) +{ + mpls_label_t label; + struct bgp_mplsvpn_nh_label_bind_cache *bmnc; + + bmnc = pi->mplsvpn.bmnc.nh_label_bind_cache; + if (!bmnc || bmnc->new_label == MPLS_INVALID_LABEL) + /* allocation in progress + * or path not eligible for local label + */ + return MPLS_INVALID_LABEL; + + label = mpls_lse_encode(bmnc->new_label, 0, 0, 1); + bgp_set_valid_label(&label); + + return label; +} + +/* Called upon reception of a ZAPI Message from zebra, about + * a new available label. + */ +static int bgp_mplsvpn_nh_label_bind_get_local_label_cb(mpls_label_t label, + void *context, + bool allocated) +{ + struct bgp_mplsvpn_nh_label_bind_cache *bmnc = context; + struct bgp_table *table; + struct bgp_path_info *pi; + + if (BGP_DEBUG(labelpool, LABELPOOL)) + zlog_debug("%s: label=%u, allocated=%d, nexthop=%pFX, label %u", + __func__, label, allocated, &bmnc->nexthop, + bmnc->orig_label); + if (allocated) + /* update the entry with the new label */ + bmnc->new_label = label; + else + /* + * previously-allocated label is now invalid + * eg: zebra deallocated the labels and notifies it + */ + bmnc->new_label = MPLS_INVALID_LABEL; + + if (!bmnc->allocation_in_progress) { + bgp_mplsvpn_nh_label_bind_free(bmnc); + return 0; + } + bmnc->allocation_in_progress = false; + + if (bmnc->new_label != MPLS_INVALID_LABEL) + /* + * Create the LSP : bmnc->orig_label, + * via bmnc->prefix, interface bnc->nexthop->ifindex + */ + bgp_mplsvpn_nh_label_bind_send_nexthop_label( + bmnc, ZEBRA_MPLS_LABELS_ADD); + + LIST_FOREACH (pi, &(bmnc->paths), mplsvpn.bmnc.nh_label_bind_thread) { + /* we can advertise it */ + if (!pi->net) + continue; + table = bgp_dest_table(pi->net); + if (!table) + continue; + SET_FLAG(pi->net->flags, BGP_NODE_LABEL_CHANGED); + bgp_process(table->bgp, pi->net, pi, table->afi, table->safi); + } + + return 0; +} + +void bgp_mplsvpn_path_nh_label_bind_unlink(struct bgp_path_info *pi) +{ + struct bgp_mplsvpn_nh_label_bind_cache *bmnc; + + if (!pi) + return; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND)) + return; + + bmnc = pi->mplsvpn.bmnc.nh_label_bind_cache; + + if (!bmnc) + return; + + LIST_REMOVE(pi, mplsvpn.bmnc.nh_label_bind_thread); + pi->mplsvpn.bmnc.nh_label_bind_cache->path_count--; + pi->mplsvpn.bmnc.nh_label_bind_cache = NULL; + SET_FLAG(pi->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND); + + if (LIST_EMPTY(&(bmnc->paths))) + bgp_mplsvpn_nh_label_bind_free(bmnc); +} + +void bgp_mplsvpn_nh_label_bind_register_local_label(struct bgp *bgp, + struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + struct bgp_mplsvpn_nh_label_bind_cache *bmnc; + struct bgp_mplsvpn_nh_label_bind_cache_head *tree; + mpls_label_t label; + + label = BGP_PATH_INFO_NUM_LABELS(pi) + ? decode_label(&pi->extra->labels->label[0]) + : MPLS_INVALID_LABEL; + + tree = &bgp->mplsvpn_nh_label_bind; + bmnc = bgp_mplsvpn_nh_label_bind_find(tree, &pi->nexthop->prefix, label); + if (!bmnc) { + bmnc = bgp_mplsvpn_nh_label_bind_new(tree, &pi->nexthop->prefix, + label); + bmnc->bgp_vpn = bgp; + bmnc->allocation_in_progress = true; + bgp_lp_get(LP_TYPE_BGP_L3VPN_BIND, bmnc, + bgp_mplsvpn_nh_label_bind_get_local_label_cb); + } + + if (pi->mplsvpn.bmnc.nh_label_bind_cache == bmnc) + /* no change */ + return; + + bgp_mplsvpn_path_nh_label_bind_unlink(pi); + + /* updates NHT pi list reference */ + LIST_INSERT_HEAD(&(bmnc->paths), pi, mplsvpn.bmnc.nh_label_bind_thread); + pi->mplsvpn.bmnc.nh_label_bind_cache = bmnc; + pi->mplsvpn.bmnc.nh_label_bind_cache->path_count++; + SET_FLAG(pi->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND); + bmnc->last_update = monotime(NULL); + + /* Add or update the selected nexthop */ + if (!bmnc->nh) + bmnc->nh = nexthop_dup(pi->nexthop->nexthop, NULL); + else if (!nexthop_same(pi->nexthop->nexthop, bmnc->nh)) { + nexthop_free(bmnc->nh); + bmnc->nh = nexthop_dup(pi->nexthop->nexthop, NULL); + if (bmnc->new_label != MPLS_INVALID_LABEL) + bgp_mplsvpn_nh_label_bind_send_nexthop_label( + bmnc, ZEBRA_MPLS_LABELS_REPLACE); + } +} + +static void show_bgp_mplsvpn_nh_label_bind_internal(struct vty *vty, + struct bgp *bgp, + bool detail) +{ + struct bgp_mplsvpn_nh_label_bind_cache_head *tree; + struct bgp_mplsvpn_nh_label_bind_cache *iter; + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_path_info *path; + struct bgp *bgp_path; + struct bgp_table *table; + time_t tbuf; + char buf[32]; + + vty_out(vty, "Current BGP mpls-vpn nexthop label bind cache, %s\n", + bgp->name_pretty); + + tree = &bgp->mplsvpn_nh_label_bind; + frr_each (bgp_mplsvpn_nh_label_bind_cache, tree, iter) { + if (iter->nexthop.family == AF_INET) + vty_out(vty, " %pI4", &iter->nexthop.u.prefix4); + else + vty_out(vty, " %pI6", &iter->nexthop.u.prefix6); + vty_out(vty, ", label %u, local label %u #paths %u\n", + iter->orig_label, iter->new_label, iter->path_count); + if (iter->nh) + vty_out(vty, " interface %s\n", + ifindex2ifname(iter->nh->ifindex, + iter->nh->vrf_id)); + tbuf = time(NULL) - (monotime(NULL) - iter->last_update); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, buf)); + if (!detail) + continue; + vty_out(vty, " Paths:\n"); + LIST_FOREACH (path, &(iter->paths), + mplsvpn.bmnc.nh_label_bind_thread) { + dest = path->net; + table = bgp_dest_table(dest); + assert(dest && table); + afi = family2afi(bgp_dest_get_prefix(dest)->family); + safi = table->safi; + bgp_path = table->bgp; + + vty_out(vty, " %d/%d %pBD %s flags 0x%x\n", afi, + safi, dest, bgp_path->name_pretty, path->flags); + } + } +} + + +DEFUN(show_bgp_mplsvpn_nh_label_bind, show_bgp_mplsvpn_nh_label_bind_cmd, + "show bgp [ VIEWVRFNAME] mplsvpn-nh-label-bind [detail]", + SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR + "BGP mplsvpn nexthop label binding entries\n" + "Show detailed information\n") +{ + int idx = 0; + char *vrf = NULL; + struct bgp *bgp; + bool detail = false; + + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[++idx]->arg; + bgp = bgp_lookup_by_name(vrf); + } else + bgp = bgp_get_default(); + + if (!bgp) + return CMD_SUCCESS; + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + show_bgp_mplsvpn_nh_label_bind_internal(vty, bgp, detail); + return CMD_SUCCESS; +} + +void bgp_mplsvpn_nexthop_init(void) +{ + install_element(VIEW_NODE, &show_bgp_mplsvpn_nh_label_bind_cmd); +} diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h new file mode 100644 index 0000000..92a9fba --- /dev/null +++ b/bgpd/bgp_mplsvpn.h @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* MPLS-VPN + * Copyright (C) 2000 Kunihiro Ishiguro + * + * This file is part of GxNU Zebra. + */ + +#ifndef _QUAGGA_BGP_MPLSVPN_H +#define _QUAGGA_BGP_MPLSVPN_H + +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_label.h" + +#define MPLS_LABEL_IS_SPECIAL(label) ((label) <= MPLS_LABEL_EXTENSION) +#define MPLS_LABEL_IS_NULL(label) \ + ((label) == MPLS_LABEL_IPV4_EXPLICIT_NULL \ + || (label) == MPLS_LABEL_IPV6_EXPLICIT_NULL \ + || (label) == MPLS_LABEL_IMPLICIT_NULL) + +#define BGP_VPNVX_HELP_STR BGP_AF_STR BGP_AF_STR + +#define V4_HEADER \ + " Network Next Hop Metric LocPrf Weight Path\n" +#define V4_HEADER_TAG " Network Next Hop In tag/Out tag\n" +#define V4_HEADER_OVERLAY \ + " Network Next Hop EthTag Overlay Index RouterMac\n" + +#define BGP_PREFIX_SID_SRV6_MAX_FUNCTION_LENGTH 20 + +extern void bgp_mplsvpn_init(void); +extern void bgp_mplsvpn_path_nh_label_unlink(struct bgp_path_info *pi); +extern int bgp_nlri_parse_vpn(struct peer *, struct attr *, struct bgp_nlri *); +extern uint32_t decode_label(mpls_label_t *); +extern void encode_label(mpls_label_t, mpls_label_t *); + +extern int argv_find_and_parse_vpnvx(struct cmd_token **argv, int argc, + int *index, afi_t *afi); +extern int bgp_show_mpls_vpn(struct vty *vty, afi_t afi, struct prefix_rd *prd, + enum bgp_show_type type, void *output_arg, + int tags, bool use_json); + +extern void vpn_leak_from_vrf_update(struct bgp *to_bgp, struct bgp *from_bgp, + struct bgp_path_info *path_vrf); + +extern void vpn_leak_from_vrf_withdraw(struct bgp *to_bgp, struct bgp *from_bgp, + struct bgp_path_info *path_vrf); + +extern void vpn_leak_from_vrf_withdraw_all(struct bgp *to_bgp, + struct bgp *from_bgp, afi_t afi); + +extern void vpn_leak_from_vrf_update_all(struct bgp *to_bgp, + struct bgp *from_bgp, afi_t afi); + +extern void vpn_leak_to_vrf_withdraw_all(struct bgp *to_bgp, afi_t afi); + +extern void vpn_leak_no_retain(struct bgp *to_bgp, struct bgp *vpn_from, + afi_t afi); + +extern void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi); + +extern bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp, + struct attr *attr, + afi_t afi); + +extern void vpn_leak_to_vrf_update(struct bgp *from_bgp, + struct bgp_path_info *path_vpn, + struct prefix_rd *prd); + +extern void vpn_leak_to_vrf_withdraw(struct bgp_path_info *path_vpn); + +extern void vpn_leak_zebra_vrf_label_update(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_label_withdraw(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_sid_update(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_sid_update_per_af(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_sid_update_per_vrf(struct bgp *bgp); +extern void vpn_leak_zebra_vrf_sid_withdraw(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_sid_withdraw_per_af(struct bgp *bgp, afi_t afi); +extern void vpn_leak_zebra_vrf_sid_withdraw_per_vrf(struct bgp *bgp); +extern int vpn_leak_label_callback(mpls_label_t label, void *lblid, bool alloc); +extern void ensure_vrf_tovpn_sid(struct bgp *vpn, struct bgp *vrf, afi_t afi); +extern void delete_vrf_tovpn_sid(struct bgp *vpn, struct bgp *vrf, afi_t afi); +extern void delete_vrf_tovpn_sid_per_af(struct bgp *vpn, struct bgp *vrf, + afi_t afi); +extern void delete_vrf_tovpn_sid_per_vrf(struct bgp *vpn, struct bgp *vrf); +extern void ensure_vrf_tovpn_sid_per_af(struct bgp *vpn, struct bgp *vrf, + afi_t afi); +extern void ensure_vrf_tovpn_sid_per_vrf(struct bgp *vpn, struct bgp *vrf); +extern void transpose_sid(struct in6_addr *sid, uint32_t label, uint8_t offset, + uint8_t size); +extern void vrf_import_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi, safi_t safi); +void vrf_unimport_from_vrf(struct bgp *to_bgp, struct bgp *from_bgp, + afi_t afi, safi_t safi); + +static inline bool is_bgp_vrf_mplsvpn(struct bgp *bgp) +{ + afi_t afi; + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + for (afi = 0; afi < AFI_MAX; ++afi) { + if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) + || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)) + return true; + } + return false; +} + +static inline int vpn_leak_to_vpn_active(struct bgp *bgp_vrf, afi_t afi, + const char **pmsg, + bool ignore_export_rt_list) +{ + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF + && bgp_vrf->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { + + if (pmsg) + *pmsg = "source bgp instance neither vrf nor default"; + return 0; + } + + /* Is vrf configured to export to vpn? */ + if (!CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) + && !CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) { + if (pmsg) + *pmsg = "export not set"; + return 0; + } + + /* Before performing withdrawal, VPN activation is checked; however, + * when the route-map modifies the export route-target (RT) list, it + * becomes challenging to determine if VPN prefixes were previously + * present, or not. The 'ignore_export_rt_list' parameter will be + * used to force the withdraw operation by not checking the possible + * route-map changes. + * Of the 'ignore_export_rt_list' is set to false, check the following: + * - Is there an RT list set? + * - Is there a route-map that sets RT communities + */ + if (!ignore_export_rt_list && + !bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN] && + (!bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN] || + !bgp_route_map_has_extcommunity_rt( + bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]))) { + if (pmsg) + *pmsg = "rtlist tovpn not defined"; + return 0; + } + + /* Is there an RD set? */ + if (!CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) { + if (pmsg) + *pmsg = "rd not defined"; + return 0; + } + + /* Is a route-map specified, but not defined? */ + if (bgp_vrf->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN] && + !bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_TOVPN]) { + if (pmsg) + *pmsg = "route-map tovpn named but not defined"; + return 0; + } + + /* Is there a "manual" export label that isn't allocated yet? */ + if (!CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO) && + bgp_vrf->vpn_policy[afi].tovpn_label != BGP_PREVENT_VRF_2_VRF_LEAK && + bgp_vrf->vpn_policy[afi].tovpn_label != MPLS_LABEL_NONE && + (bgp_vrf->vpn_policy[afi].tovpn_label >= MPLS_LABEL_UNRESERVED_MIN && + !CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG))) { + if (!bgp_zebra_request_label_range(bgp_vrf->vpn_policy[afi] + .tovpn_label, + 1, false)) { + if (pmsg) + *pmsg = "manual label could not be allocated"; + return 0; + } + SET_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG); + } + + return 1; +} + +static inline int vpn_leak_from_vpn_active(struct bgp *bgp_vrf, afi_t afi, + const char **pmsg) +{ + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF + && bgp_vrf->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { + + if (pmsg) + *pmsg = "destination bgp instance neither vrf nor default"; + return 0; + } + + if (bgp_vrf->vrf_id == VRF_UNKNOWN) { + if (pmsg) + *pmsg = "destination bgp instance vrf is VRF_UNKNOWN"; + return 0; + } + + /* Is vrf configured to import from vpn? */ + if (!CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) + && !CHECK_FLAG(bgp_vrf->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + if (pmsg) + *pmsg = "import not set"; + return 0; + } + + /* Is there an RT list set? */ + if (!bgp_vrf->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN]) { + if (pmsg) + *pmsg = "rtlist fromvpn not defined"; + return 0; + } + + /* Is a route-map specified, but not defined? */ + if (bgp_vrf->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN] && + !bgp_vrf->vpn_policy[afi].rmap[BGP_VPN_POLICY_DIR_FROMVPN]) { + if (pmsg) + *pmsg = "route-map fromvpn named but not defined"; + return 0; + } + return 1; +} + +static inline void vpn_leak_prechange(enum vpn_policy_direction direction, + afi_t afi, struct bgp *bgp_vpn, + struct bgp *bgp_vrf) +{ + /* Detect when default bgp instance is not (yet) defined by config */ + if (!bgp_vpn) + return; + + if ((direction == BGP_VPN_POLICY_DIR_FROMVPN) && + vpn_leak_from_vpn_active(bgp_vrf, afi, NULL)) { + + vpn_leak_to_vrf_withdraw_all(bgp_vrf, afi); + } + if ((direction == BGP_VPN_POLICY_DIR_TOVPN) && + vpn_leak_to_vpn_active(bgp_vrf, afi, NULL, true)) { + vpn_leak_from_vrf_withdraw_all(bgp_vpn, bgp_vrf, afi); + } +} + +static inline void vpn_leak_postchange(enum vpn_policy_direction direction, + afi_t afi, struct bgp *bgp_vpn, + struct bgp *bgp_vrf) +{ + /* Detect when default bgp instance is not (yet) defined by config */ + if (!bgp_vpn) + return; + + if (direction == BGP_VPN_POLICY_DIR_FROMVPN) { + /* trigger a flush to re-sync with ADJ-RIB-in */ + if (!CHECK_FLAG(bgp_vpn->af_flags[afi][SAFI_MPLS_VPN], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL)) + bgp_clear_soft_in(bgp_vpn, afi, SAFI_MPLS_VPN); + vpn_leak_to_vrf_update_all(bgp_vrf, bgp_vpn, afi); + } + if (direction == BGP_VPN_POLICY_DIR_TOVPN) { + + if (bgp_vrf->vpn_policy[afi].tovpn_label != + bgp_vrf->vpn_policy[afi] + .tovpn_zebra_vrf_label_last_sent) { + vpn_leak_zebra_vrf_label_update(bgp_vrf, afi); + } + + if (bgp_vrf->vpn_policy[afi].tovpn_sid_index == 0 && + !CHECK_FLAG(bgp_vrf->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO) && + bgp_vrf->tovpn_sid_index == 0 && + !CHECK_FLAG(bgp_vrf->vrf_flags, BGP_VRF_TOVPN_SID_AUTO)) + delete_vrf_tovpn_sid(bgp_vpn, bgp_vrf, afi); + + if (!bgp_vrf->vpn_policy[afi].tovpn_sid && !bgp_vrf->tovpn_sid) + ensure_vrf_tovpn_sid(bgp_vpn, bgp_vrf, afi); + + if ((!bgp_vrf->vpn_policy[afi].tovpn_sid && + bgp_vrf->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent) || + (!bgp_vrf->tovpn_sid && + bgp_vrf->tovpn_zebra_vrf_sid_last_sent)) + vpn_leak_zebra_vrf_sid_withdraw(bgp_vrf, afi); + + if (bgp_vrf->vpn_policy[afi].tovpn_sid) { + if (sid_diff(bgp_vrf->vpn_policy[afi].tovpn_sid, + bgp_vrf->vpn_policy[afi] + .tovpn_zebra_vrf_sid_last_sent)) { + vpn_leak_zebra_vrf_sid_update(bgp_vrf, afi); + } + } else if (bgp_vrf->tovpn_sid) { + if (sid_diff(bgp_vrf->tovpn_sid, + bgp_vrf->tovpn_zebra_vrf_sid_last_sent)) { + vpn_leak_zebra_vrf_sid_update(bgp_vrf, afi); + } + } + + vpn_leak_from_vrf_update_all(bgp_vpn, bgp_vrf, afi); + } +} + +/* Flag if the route is injectable into VPN. This would be either a + * non-imported route or a non-VPN imported route. + */ +static inline bool is_route_injectable_into_vpn(struct bgp_path_info *pi) +{ + struct bgp_path_info *parent_pi; + struct bgp_table *table; + struct bgp_dest *dest; + + if (pi->sub_type != BGP_ROUTE_IMPORTED || !pi->extra || + !pi->extra->vrfleak || !pi->extra->vrfleak->parent) + return true; + + parent_pi = (struct bgp_path_info *)pi->extra->vrfleak->parent; + dest = parent_pi->net; + if (!dest) + return true; + table = bgp_dest_table(dest); + if (table && + (table->afi == AFI_IP || table->afi == AFI_IP6) && + table->safi == SAFI_MPLS_VPN) + return false; + return true; +} + +/* Flag if the route path's family is VPN. */ +static inline bool is_pi_family_vpn(struct bgp_path_info *pi) +{ + return (is_pi_family_matching(pi, AFI_IP, SAFI_MPLS_VPN) || + is_pi_family_matching(pi, AFI_IP6, SAFI_MPLS_VPN)); +} + +extern void vpn_policy_routemap_event(const char *rmap_name); + +extern vrf_id_t get_first_vrf_for_redirect_with_rt(struct ecommunity *eckey); + +extern void vpn_leak_postchange_all(void); +extern void vpn_handle_router_id_update(struct bgp *bgp, bool withdraw, + bool is_config); +extern void bgp_vpn_leak_unimport(struct bgp *from_bgp); +extern void bgp_vpn_leak_export(struct bgp *from_bgp); + +extern bool bgp_mplsvpn_path_uses_valid_mpls_label(struct bgp_path_info *pi); +extern int +bgp_mplsvpn_nh_label_bind_cmp(const struct bgp_mplsvpn_nh_label_bind_cache *a, + const struct bgp_mplsvpn_nh_label_bind_cache *b); +extern void bgp_mplsvpn_path_nh_label_bind_unlink(struct bgp_path_info *pi); +extern void bgp_mplsvpn_nh_label_bind_register_local_label( + struct bgp *bgp, struct bgp_dest *dest, struct bgp_path_info *pi); +mpls_label_t bgp_mplsvpn_nh_label_bind_get_label(struct bgp_path_info *pi); + +/* used to bind a local label to the (label, nexthop) values + * from an incoming BGP mplsvpn update + */ +struct bgp_mplsvpn_nh_label_bind_cache { + + /* RB-tree entry. */ + struct bgp_mplsvpn_nh_label_bind_cache_item entry; + + /* The nexthop and the vpn label are the key of the list. + * Only received BGP MPLSVPN updates may use that structure. + * orig_label is the original label received from the BGP Update. + */ + struct prefix nexthop; + mpls_label_t orig_label; + + /* resolved interface for the paths */ + struct nexthop *nh; + + /* number of mplsvpn path */ + unsigned int path_count; + + /* back pointer to bgp instance */ + struct bgp *bgp_vpn; + + /* MPLS label allocated value. + * When the next-hop is changed because of 'next-hop-self' or + * because it is an eBGP peer, the redistributed orig_label value + * is unmodified, unless the 'l3vpn-multi-domain-switching' + * is enabled: a new_label value is allocated: + * - The new_label value is sent in the advertised BGP update, + * instead of the label value. + * - An MPLS entry is set to swap with . + */ + mpls_label_t new_label; + + /* list of path_vrfs using it */ + LIST_HEAD(mplsvpn_nh_label_bind_path_lists, bgp_path_info) paths; + + time_t last_update; + + bool allocation_in_progress; +}; + +DECLARE_RBTREE_UNIQ(bgp_mplsvpn_nh_label_bind_cache, + struct bgp_mplsvpn_nh_label_bind_cache, entry, + bgp_mplsvpn_nh_label_bind_cmp); + +void bgp_mplsvpn_nh_label_bind_free( + struct bgp_mplsvpn_nh_label_bind_cache *bmnc); + +struct bgp_mplsvpn_nh_label_bind_cache * +bgp_mplsvpn_nh_label_bind_new(struct bgp_mplsvpn_nh_label_bind_cache_head *tree, + struct prefix *p, mpls_label_t orig_label); +struct bgp_mplsvpn_nh_label_bind_cache *bgp_mplsvpn_nh_label_bind_find( + struct bgp_mplsvpn_nh_label_bind_cache_head *tree, struct prefix *p, + mpls_label_t orig_label); +void bgp_mplsvpn_nexthop_init(void); +extern void sid_unregister(struct bgp *bgp, const struct in6_addr *sid); + +#endif /* _QUAGGA_BGP_MPLSVPN_H */ diff --git a/bgpd/bgp_mplsvpn_snmp.c b/bgpd/bgp_mplsvpn_snmp.c new file mode 100644 index 0000000..3344e9e --- /dev/null +++ b/bgpd/bgp_mplsvpn_snmp.c @@ -0,0 +1,1686 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* MPLS/BGP L3VPN MIB + * Copyright (C) 2020 Volta Networks Inc + */ + +#include + +#include +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "frrevent.h" +#include "smux.h" +#include "filter.h" +#include "hook.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_mplsvpn_snmp.h" + +#define BGP_mplsvpn_notif_enable_true 1 +#define BGP_mplsvpn_notif_enable_false 2 + +/* MPLSL3VPN MIB described in RFC4382 */ +#define MPLSL3VPNMIB 1, 3, 6, 1, 2, 1, 10, 166, 11 + +/* MPLSL3VPN Scalars */ +#define MPLSL3VPNCONFIGUREDVRFS 1 +#define MPLSL3VPNACTIVEVRFS 2 +#define MPLSL3VPNCONNECTEDINTERFACES 3 +#define MPLSL3VPNNOTIFICATIONENABLE 4 +#define MPLSL3VPNCONFMAXPOSSRTS 5 +#define MPLSL3VPNVRFCONFRTEMXTHRSHTIME 6 +#define MPLSL3VPNILLLBLRCVTHRSH 7 + +/* MPLSL3VPN IFConf Table */ +#define MPLSL3VPNIFVPNCLASSIFICATION 1 +#define MPLSL3VPNIFCONFSTORAGETYPE 2 +#define MPLSL3VPNIFCONFROWSTATUS 3 + +/* MPLSL3VPN VRF Table */ +#define MPLSL3VPNVRFVPNID 1 +#define MPLSL3VPNVRFDESC 2 +#define MPLSL3VPNVRFRD 3 +#define MPLSL3VPNVRFCREATIONTIME 4 +#define MPLSL3VPNVRFOPERSTATUS 5 +#define MPLSL3VPNVRFACTIVEINTERFACES 6 +#define MPLSL3VPNVRFASSOCIATEDINTERFACES 7 +#define MPLSL3VPNVRFCONFMIDRTETHRESH 8 +#define MPLSL3VPNVRFCONFHIGHRTETHRSH 9 +#define MPLSL3VPNVRFCONFMAXROUTES 10 +#define MPLSL3VPNVRFCONFLASTCHANGED 11 +#define MPLSL3VPNVRFCONFROWSTATUS 12 +#define MPLSL3VPNVRFCONFADMINSTATUS 13 +#define MPLSL3VPNVRFCONFSTORAGETYPE 14 + +/* MPLSL3VPN RT Table */ +#define MPLSL3VPNVRFRT 1 +#define MPLSL3VPNVRFRTDESCR 2 +#define MPLSL3VPNVRFRTROWSTATUS 3 +#define MPLSL3VPNVRFRTSTORAGETYPE 4 + +/* MPLSL3VPN PERF Table */ +#define MPLSL3VPNVRFPERFROUTESADDED 1 +#define MPLSL3VPNVRFPERFROUTESDELETED 2 +#define MPLSL3VPNVRFPERFCURRNUMROUTES 3 + +/* MPLSL3VPN RTE Table */ +#define MPLSL3VPNVRFRTEINETCIDRDESTTYPE 1 +#define MPLSL3VPNVRFRTEINETCIDRDEST 2 +#define MPLSL3VPNVRFRTEINETCIDRPFXLEN 3 +#define MPLSL3VPNVRFRTEINETCIDRPOLICY 4 +#define MPLSL3VPNVRFRTEINETCIDRNHOPTYPE 5 +#define MPLSL3VPNVRFRTEINETCIDRNEXTHOP 6 +#define MPLSL3VPNVRFRTEINETCIDRIFINDEX 7 +#define MPLSL3VPNVRFRTEINETCIDRTYPE 8 +#define MPLSL3VPNVRFRTEINETCIDRPROTO 9 +#define MPLSL3VPNVRFRTEINETCIDRAGE 10 +#define MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS 11 +#define MPLSL3VPNVRFRTEINETCIDRMETRIC1 12 +#define MPLSL3VPNVRFRTEINETCIDRMETRIC2 13 +#define MPLSL3VPNVRFRTEINETCIDRMETRIC3 14 +#define MPLSL3VPNVRFRTEINETCIDRMETRIC4 15 +#define MPLSL3VPNVRFRTEINETCIDRMETRIC5 16 +#define MPLSL3VPNVRFRTEINETCIDRXCPOINTER 17 +#define MPLSL3VPNVRFRTEINETCIDRSTATUS 18 + +/* BGP Trap */ +#define MPLSL3VPNVRFUP 1 +#define MPLSL3VPNDOWN 2 + +/* SNMP value hack. */ +#define INTEGER ASN_INTEGER +#define INTEGER32 ASN_INTEGER +#define COUNTER32 ASN_COUNTER +#define OCTET_STRING ASN_OCTET_STR +#define IPADDRESS ASN_IPADDRESS +#define GAUGE32 ASN_UNSIGNED +#define TIMETICKS ASN_TIMETICKS +#define OID ASN_OBJECT_ID + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +#define RT_PREAMBLE_SIZE 20 + +/* BGP-MPLS-MIB instances */ +static oid mpls_l3vpn_oid[] = {MPLSL3VPNMIB}; +static oid mpls_l3vpn_trap_oid[] = {MPLSL3VPNMIB, 0}; +static char rd_buf[RD_ADDRSTRLEN]; +/* Notifications enabled by default */ +static uint8_t bgp_mplsvpn_notif_enable = SNMP_TRUE; +static oid mpls_l3vpn_policy_oid[2] = {0, 0}; +static const char *empty_nhop = ""; +char rt_description[VRF_NAMSIZ + RT_PREAMBLE_SIZE]; + +static uint8_t *mplsL3vpnConfiguredVrfs(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnActiveVrfs(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnConnectedInterfaces(struct variable *, oid[], size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnNotificationEnable(struct variable *, oid[], size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnVrfConfMaxPossRts(struct variable *, oid[], size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnVrfConfRteMxThrshTime(struct variable *, oid[], + size_t *, int, size_t *, + WriteMethod **); + +static uint8_t *mplsL3vpnIllLblRcvThrsh(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnVrfTable(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnVrfRtTable(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnIfConfTable(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnPerfTable(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *mplsL3vpnRteTable(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); + + +static struct variable mpls_l3vpn_variables[] = { + /* BGP version. */ + {MPLSL3VPNCONFIGUREDVRFS, + GAUGE32, + RONLY, + mplsL3vpnConfiguredVrfs, + 3, + {1, 1, 1} }, + {MPLSL3VPNACTIVEVRFS, + GAUGE32, + RONLY, + mplsL3vpnActiveVrfs, + 3, + {1, 1, 2} }, + {MPLSL3VPNCONNECTEDINTERFACES, + GAUGE32, + RONLY, + mplsL3vpnConnectedInterfaces, + 3, + {1, 1, 3} }, + {MPLSL3VPNNOTIFICATIONENABLE, + INTEGER, + RWRITE, + mplsL3vpnNotificationEnable, + 3, + {1, 1, 4} }, + {MPLSL3VPNCONFMAXPOSSRTS, + GAUGE32, + RONLY, + mplsL3vpnVrfConfMaxPossRts, + 3, + {1, 1, 5} }, + {MPLSL3VPNVRFCONFRTEMXTHRSHTIME, + GAUGE32, + RONLY, + mplsL3vpnVrfConfRteMxThrshTime, + 3, + {1, 1, 6} }, + {MPLSL3VPNILLLBLRCVTHRSH, + GAUGE32, + RONLY, + mplsL3vpnIllLblRcvThrsh, + 3, + {1, 1, 7} }, + + /* Ifconf Table */ + {MPLSL3VPNIFVPNCLASSIFICATION, + INTEGER, + RONLY, + mplsL3vpnIfConfTable, + 5, + {1, 2, 1, 1, 2} }, + {MPLSL3VPNIFCONFSTORAGETYPE, + INTEGER, + RONLY, + mplsL3vpnIfConfTable, + 5, + {1, 2, 1, 1, 4} }, + {MPLSL3VPNIFCONFROWSTATUS, + INTEGER, + RONLY, + mplsL3vpnIfConfTable, + 5, + {1, 2, 1, 1, 5} }, + + /* mplsL3VpnVrf Table */ + {MPLSL3VPNVRFVPNID, + OCTET_STRING, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 2} }, + {MPLSL3VPNVRFDESC, + OCTET_STRING, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 3} }, + {MPLSL3VPNVRFRD, + OCTET_STRING, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 4} }, + {MPLSL3VPNVRFCREATIONTIME, + TIMETICKS, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 5} }, + {MPLSL3VPNVRFOPERSTATUS, + INTEGER, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 6} }, + {MPLSL3VPNVRFACTIVEINTERFACES, + GAUGE32, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 7} }, + {MPLSL3VPNVRFASSOCIATEDINTERFACES, + GAUGE32, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 8} }, + {MPLSL3VPNVRFCONFMIDRTETHRESH, + GAUGE32, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 9} }, + {MPLSL3VPNVRFCONFHIGHRTETHRSH, + GAUGE32, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 10} }, + {MPLSL3VPNVRFCONFMAXROUTES, + GAUGE32, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 11} }, + {MPLSL3VPNVRFCONFLASTCHANGED, + TIMETICKS, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 12} }, + {MPLSL3VPNVRFCONFROWSTATUS, + INTEGER, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 13} }, + {MPLSL3VPNVRFCONFADMINSTATUS, + INTEGER, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 14} }, + {MPLSL3VPNVRFCONFSTORAGETYPE, + INTEGER, + RONLY, + mplsL3vpnVrfTable, + 5, + {1, 2, 2, 1, 15} }, + + /* mplsL3vpnVrfRt Table */ + {MPLSL3VPNVRFRT, + OCTET_STRING, + RONLY, + mplsL3vpnVrfRtTable, + 5, + {1, 2, 3, 1, 4} }, + {MPLSL3VPNVRFRTDESCR, + OCTET_STRING, + RONLY, + mplsL3vpnVrfRtTable, + 5, + {1, 2, 3, 1, 5} }, + {MPLSL3VPNVRFRTROWSTATUS, + INTEGER, + RONLY, + mplsL3vpnVrfRtTable, + 5, + {1, 2, 3, 1, 6} }, + {MPLSL3VPNVRFRTSTORAGETYPE, + INTEGER, + RONLY, + mplsL3vpnVrfRtTable, + 5, + {1, 2, 3, 1, 7} }, + + /* mplsL3VpnPerfTable */ + {MPLSL3VPNVRFPERFROUTESADDED, + COUNTER32, + RONLY, + mplsL3vpnPerfTable, + 5, + {1, 3, 1, 1, 1} }, + {MPLSL3VPNVRFPERFROUTESDELETED, + COUNTER32, + RONLY, + mplsL3vpnPerfTable, + 5, + {1, 3, 1, 1, 2} }, + {MPLSL3VPNVRFPERFCURRNUMROUTES, + GAUGE32, + RONLY, + mplsL3vpnPerfTable, + 5, + {1, 3, 1, 1, 3} }, + + /* mplsVpnRteTable */ + {MPLSL3VPNVRFRTEINETCIDRDESTTYPE, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 1} }, + {MPLSL3VPNVRFRTEINETCIDRDEST, + OCTET_STRING, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 2} }, + {MPLSL3VPNVRFRTEINETCIDRPFXLEN, + GAUGE32, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 3} }, + {MPLSL3VPNVRFRTEINETCIDRPOLICY, + OID, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 4} }, + {MPLSL3VPNVRFRTEINETCIDRNHOPTYPE, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 5} }, + {MPLSL3VPNVRFRTEINETCIDRNEXTHOP, + OCTET_STRING, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 6} }, + {MPLSL3VPNVRFRTEINETCIDRIFINDEX, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 7} }, + {MPLSL3VPNVRFRTEINETCIDRTYPE, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 8} }, + {MPLSL3VPNVRFRTEINETCIDRPROTO, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 9} }, + {MPLSL3VPNVRFRTEINETCIDRAGE, + GAUGE32, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 10} }, + {MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS, + GAUGE32, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 11} }, + {MPLSL3VPNVRFRTEINETCIDRMETRIC1, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 12} }, + {MPLSL3VPNVRFRTEINETCIDRMETRIC2, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 13} }, + {MPLSL3VPNVRFRTEINETCIDRMETRIC3, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 14} }, + {MPLSL3VPNVRFRTEINETCIDRMETRIC4, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 15} }, + {MPLSL3VPNVRFRTEINETCIDRMETRIC5, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 16} }, + {MPLSL3VPNVRFRTEINETCIDRXCPOINTER, + OCTET_STRING, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 17} }, + {MPLSL3VPNVRFRTEINETCIDRSTATUS, + INTEGER, + RONLY, + mplsL3vpnRteTable, + 5, + {1, 4, 1, 1, 18} }, +}; + +/* timeticks are in hundredths of a second */ +static void bgp_mpls_l3vpn_update_timeticks(time_t *counter) +{ + struct timeval tv; + + monotime(&tv); + *counter = (tv.tv_sec * 100) + (tv.tv_usec / 10000); +} + +static int bgp_mpls_l3vpn_update_last_changed(struct bgp *bgp) +{ + if (bgp->snmp_stats) + bgp_mpls_l3vpn_update_timeticks( + &(bgp->snmp_stats->modify_time)); + return 0; +} + +static uint32_t bgp_mpls_l3vpn_current_routes(struct bgp *l3vpn_bgp) +{ + uint32_t count = 0; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + table = l3vpn_bgp->rib[AFI_IP][SAFI_UNICAST]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + pi = bgp_dest_get_bgp_path_info(dest); + for (; pi; pi = pi->next) + count++; + } + table = l3vpn_bgp->rib[AFI_IP6][SAFI_UNICAST]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + pi = bgp_dest_get_bgp_path_info(dest); + for (; pi; pi = pi->next) + count++; + } + return count; +} + +static int bgp_init_snmp_stats(struct bgp *bgp) +{ + if (is_bgp_vrf_mplsvpn(bgp)) { + if (bgp->snmp_stats == NULL) { + bgp->snmp_stats = XCALLOC(MTYPE_BGP_NAME, + sizeof(struct bgp_snmp_stats)); + /* fix up added routes */ + if (bgp->snmp_stats) { + bgp->snmp_stats->routes_added = + bgp_mpls_l3vpn_current_routes(bgp); + bgp_mpls_l3vpn_update_timeticks( + &(bgp->snmp_stats->creation_time)); + } + } + } else { + if (bgp->snmp_stats) { + XFREE(MTYPE_BGP_NAME, bgp->snmp_stats); + bgp->snmp_stats = NULL; + } + } + /* Something changed - update the timestamp */ + bgp_mpls_l3vpn_update_last_changed(bgp); + return 0; +} + +static int bgp_snmp_update_route_stats(struct bgp_dest *dest, + struct bgp_path_info *pi, bool added) +{ + struct bgp_table *table; + + if (dest) { + table = bgp_dest_table(dest); + /* only update if we have a stats block - MPLSVPN vrfs for now*/ + if (table && table->bgp && table->bgp->snmp_stats) { + if (added) + table->bgp->snmp_stats->routes_added++; + else + table->bgp->snmp_stats->routes_deleted++; + } + } + return 0; +} + +static bool is_bgp_vrf_active(struct bgp *bgp) +{ + struct vrf *vrf; + struct interface *ifp; + + /* if there is one interface in the vrf which is up then it is deemed + * active + */ + vrf = vrf_lookup_by_id(bgp->vrf_id); + if (vrf == NULL) + return false; + RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) { + /* if we are in a vrf skip the l3mdev */ + if (bgp->name && strncmp(ifp->name, bgp->name, VRF_NAMSIZ) == 0) + continue; + + if (if_is_up(ifp)) + return true; + } + return false; +} + +/* BGP Traps. */ +static struct trap_object l3vpn_trap_list[] = {{5, {1, 2, 1, 1, 5} }, + {5, {1, 2, 2, 1, 6} } }; + +static int bgp_vrf_check_update_active(struct bgp *bgp, struct interface *ifp) +{ + bool new_active = false; + oid trap; + struct index_oid trap_index[2]; + + if (!is_bgp_vrf_mplsvpn(bgp) || bgp->snmp_stats == NULL + || !bgp_mplsvpn_notif_enable) + return 0; + new_active = is_bgp_vrf_active(bgp); + if (bgp->snmp_stats->active != new_active) { + /* add trap in here */ + bgp->snmp_stats->active = new_active; + + /* send relevent trap */ + if (bgp->snmp_stats->active) + trap = MPLSL3VPNVRFUP; + else + trap = MPLSL3VPNDOWN; + + /* + * first index vrf_name + ifindex + * second index vrf_name + */ + trap_index[1].indexlen = strnlen(bgp->name, VRF_NAMSIZ); + oid_copy_str(trap_index[0].indexname, bgp->name, + trap_index[1].indexlen); + oid_copy_str(trap_index[1].indexname, bgp->name, + trap_index[1].indexlen); + trap_index[0].indexlen = + trap_index[1].indexlen + sizeof(ifindex_t); + oid_copy_int(trap_index[0].indexname + trap_index[1].indexlen, + (int *)&(ifp->ifindex)); + + smux_trap_multi_index( + mpls_l3vpn_variables, array_size(mpls_l3vpn_variables), + mpls_l3vpn_trap_oid, array_size(mpls_l3vpn_trap_oid), + mpls_l3vpn_oid, sizeof(mpls_l3vpn_oid) / sizeof(oid), + trap_index, array_size(trap_index), l3vpn_trap_list, + array_size(l3vpn_trap_list), trap); + } + bgp_mpls_l3vpn_update_last_changed(bgp); + return 0; +} + +static uint8_t *mplsL3vpnConfiguredVrfs(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + uint32_t count = 0; + + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (is_bgp_vrf_mplsvpn(bgp)) + count++; + } + return SNMP_INTEGER(count); +} + +static uint8_t *mplsL3vpnActiveVrfs(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + uint32_t count = 0; + + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (is_bgp_vrf_mplsvpn(bgp) && is_bgp_vrf_active(bgp)) + count++; + } + return SNMP_INTEGER(count); +} + +static uint8_t *mplsL3vpnConnectedInterfaces(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + uint32_t count = 0; + struct vrf *vrf; + + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (is_bgp_vrf_mplsvpn(bgp)) { + vrf = vrf_lookup_by_name(bgp->name); + if (vrf == NULL) + continue; + + count += vrf_interface_count(vrf); + } + } + + return SNMP_INTEGER(count); +} + +static int write_mplsL3vpnNotificationEnable(int action, uint8_t *var_val, + uint8_t var_val_type, + size_t var_val_len, uint8_t *statP, + oid *name, size_t length) +{ + uint32_t intval; + + if (var_val_type != ASN_INTEGER) + return SNMP_ERR_WRONGTYPE; + + if (var_val_len != sizeof(long)) + return SNMP_ERR_WRONGLENGTH; + + intval = *(long *)var_val; + bgp_mplsvpn_notif_enable = intval; + return SNMP_ERR_NOERROR; +} + +static uint8_t *mplsL3vpnNotificationEnable(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + *write_method = write_mplsL3vpnNotificationEnable; + return SNMP_INTEGER(bgp_mplsvpn_notif_enable); +} + +static uint8_t *mplsL3vpnVrfConfMaxPossRts(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + return SNMP_INTEGER(0); +} + +static uint8_t *mplsL3vpnVrfConfRteMxThrshTime(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + return SNMP_INTEGER(0); +} + +static uint8_t *mplsL3vpnIllLblRcvThrsh(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + return SNMP_INTEGER(0); +} + + +static struct bgp *bgp_lookup_by_name_next(char *vrf_name) +{ + struct bgp *bgp, *bgp_next = NULL; + struct listnode *node, *nnode; + bool first = false; + + /* + * the vrfs are not stored alphabetically but since we are using the + * vrf name as an index we need the getnext function to return them + * in a atrict order. Thus run through and find the best next one. + */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (!is_bgp_vrf_mplsvpn(bgp)) + continue; + if (strnlen(vrf_name, VRF_NAMSIZ) == 0 && bgp_next == NULL) { + first = true; + bgp_next = bgp; + continue; + } + if (first || strncmp(bgp->name, vrf_name, VRF_NAMSIZ) > 0) { + if (bgp_next == NULL) + bgp_next = bgp; + else if (strncmp(bgp->name, bgp_next->name, VRF_NAMSIZ) + < 0) + bgp_next = bgp; + } + } + return bgp_next; +} + +/* 1.3.6.1.2.1.10.166.11.1.2.1.1.x = 14*/ +#define IFCONFTAB_NAMELEN 14 +static struct bgp *bgpL3vpnIfConf_lookup(struct variable *v, oid name[], + size_t *length, char *vrf_name, + ifindex_t *ifindex, int exact) +{ + struct bgp *bgp = NULL; + size_t namelen = v ? v->namelen : IFCONFTAB_NAMELEN; + struct interface *ifp; + int vrf_name_len, len; + + /* too long ? */ + if (*length - namelen > (VRF_NAMSIZ + sizeof(uint32_t))) + return NULL; + /* do we have index info in the oid ? */ + if (*length - namelen != 0 && *length - namelen >= sizeof(uint32_t)) { + /* copy the info from the oid */ + vrf_name_len = *length - (namelen + sizeof(ifindex_t)); + oid2string(name + namelen, vrf_name_len, vrf_name); + oid2int(name + namelen + vrf_name_len, ifindex); + } + + if (exact) { + /* Check the length. */ + bgp = bgp_lookup_by_name(vrf_name); + if (bgp && !is_bgp_vrf_mplsvpn(bgp)) + return NULL; + if (!bgp) + return NULL; + ifp = if_lookup_by_index(*ifindex, bgp->vrf_id); + if (!ifp) + return NULL; + } else { + if (strnlen(vrf_name, VRF_NAMSIZ) == 0) + bgp = bgp_lookup_by_name_next(vrf_name); + else + bgp = bgp_lookup_by_name(vrf_name); + + while (bgp) { + ifp = if_vrf_lookup_by_index_next(*ifindex, + bgp->vrf_id); + if (ifp) { + vrf_name_len = strnlen(bgp->name, VRF_NAMSIZ); + *ifindex = ifp->ifindex; + len = vrf_name_len + sizeof(ifindex_t); + oid_copy_str(name + namelen, bgp->name, + vrf_name_len); + oid_copy_int(name + namelen + vrf_name_len, + ifindex); + *length = len + namelen; + + return bgp; + } + *ifindex = 0; + bgp = bgp_lookup_by_name_next(bgp->name); + } + + return NULL; + } + return bgp; +} + +static uint8_t *mplsL3vpnIfConfTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + char vrf_name[VRF_NAMSIZ]; + ifindex_t ifindex = 0; + struct bgp *l3vpn_bgp; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(vrf_name, 0, VRF_NAMSIZ); + l3vpn_bgp = bgpL3vpnIfConf_lookup(v, name, length, vrf_name, &ifindex, + exact); + if (!l3vpn_bgp) + return NULL; + + switch (v->magic) { + case MPLSL3VPNIFVPNCLASSIFICATION: + return SNMP_INTEGER(2); + case MPLSL3VPNIFCONFSTORAGETYPE: + return SNMP_INTEGER(2); + case MPLSL3VPNIFCONFROWSTATUS: + return SNMP_INTEGER(1); + } + return NULL; +} + +/* 1.3.6.1.2.1.10.166.11.1.2.2.1.x = 14*/ +#define VRFTAB_NAMELEN 14 + +static struct bgp *bgpL3vpnVrf_lookup(struct variable *v, oid name[], + size_t *length, char *vrf_name, int exact) +{ + struct bgp *bgp = NULL; + size_t namelen = v ? v->namelen : VRFTAB_NAMELEN; + int len; + + if (*length - namelen > VRF_NAMSIZ) + return NULL; + oid2string(name + namelen, *length - namelen, vrf_name); + if (exact) { + /* Check the length. */ + bgp = bgp_lookup_by_name(vrf_name); + if (bgp && !is_bgp_vrf_mplsvpn(bgp)) + return NULL; + } else { + bgp = bgp_lookup_by_name_next(vrf_name); + + if (bgp == NULL) + return NULL; + + len = strnlen(bgp->name, VRF_NAMSIZ); + oid_copy_str(name + namelen, bgp->name, len); + *length = len + namelen; + } + return bgp; +} + +static uint8_t *mplsL3vpnVrfTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + char vrf_name[VRF_NAMSIZ]; + struct bgp *l3vpn_bgp; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(vrf_name, 0, VRF_NAMSIZ); + l3vpn_bgp = bgpL3vpnVrf_lookup(v, name, length, vrf_name, exact); + + if (!l3vpn_bgp) + return NULL; + + switch (v->magic) { + case MPLSL3VPNVRFVPNID: + *var_len = 0; + return NULL; + case MPLSL3VPNVRFDESC: + *var_len = strnlen(l3vpn_bgp->name, VRF_NAMSIZ); + return (uint8_t *)l3vpn_bgp->name; + case MPLSL3VPNVRFRD: + /* + * this is a horror show but the MIB dicates one RD per vrf + * and not one RD per AFI as we (FRR) have. So this little gem + * returns the V4 one if it's set OR the v6 one if it's set or + * zero-length string id neither are set + */ + memset(rd_buf, 0, RD_ADDRSTRLEN); + if (CHECK_FLAG(l3vpn_bgp->vpn_policy[AFI_IP].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) + prefix_rd2str(&l3vpn_bgp->vpn_policy[AFI_IP].tovpn_rd, + rd_buf, sizeof(rd_buf), + bgp_get_asnotation(l3vpn_bgp)); + else if (CHECK_FLAG(l3vpn_bgp->vpn_policy[AFI_IP6].flags, + BGP_VPN_POLICY_TOVPN_RD_SET)) + prefix_rd2str(&l3vpn_bgp->vpn_policy[AFI_IP6].tovpn_rd, + rd_buf, sizeof(rd_buf), + bgp_get_asnotation(l3vpn_bgp)); + + *var_len = strnlen(rd_buf, RD_ADDRSTRLEN); + return (uint8_t *)rd_buf; + case MPLSL3VPNVRFCREATIONTIME: + return SNMP_INTEGER( + (uint32_t)l3vpn_bgp->snmp_stats->creation_time); + case MPLSL3VPNVRFOPERSTATUS: + if (l3vpn_bgp->snmp_stats->active) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(2); + case MPLSL3VPNVRFACTIVEINTERFACES: + return SNMP_INTEGER(bgp_vrf_interfaces(l3vpn_bgp, true)); + case MPLSL3VPNVRFASSOCIATEDINTERFACES: + return SNMP_INTEGER(bgp_vrf_interfaces(l3vpn_bgp, false)); + case MPLSL3VPNVRFCONFMIDRTETHRESH: + return SNMP_INTEGER(0); + case MPLSL3VPNVRFCONFHIGHRTETHRSH: + return SNMP_INTEGER(0); + case MPLSL3VPNVRFCONFMAXROUTES: + return SNMP_INTEGER(0); + case MPLSL3VPNVRFCONFLASTCHANGED: + return SNMP_INTEGER( + (uint32_t)l3vpn_bgp->snmp_stats->modify_time); + case MPLSL3VPNVRFCONFROWSTATUS: + return SNMP_INTEGER(1); + case MPLSL3VPNVRFCONFADMINSTATUS: + return SNMP_INTEGER(1); + case MPLSL3VPNVRFCONFSTORAGETYPE: + return SNMP_INTEGER(2); + } + return NULL; +} + +/* 1.3.6.1.2.1.10.166.11.1.2.3.1.x = 14*/ +#define VRFRTTAB_NAMELEN 14 +static struct bgp *bgpL3vpnVrfRt_lookup(struct variable *v, oid name[], + size_t *length, char *vrf_name, + uint32_t *rt_index, uint8_t *rt_type, + int exact) +{ + uint32_t type_index_size; + struct bgp *l3vpn_bgp; + size_t namelen = v ? v->namelen : VRFRTTAB_NAMELEN; + int vrf_name_len, len; + + /* too long ? */ + if (*length - namelen + > (VRF_NAMSIZ + sizeof(uint32_t)) + sizeof(uint8_t)) + return NULL; + + type_index_size = sizeof(uint32_t) + sizeof(uint8_t); + /* do we have index info in the oid ? */ + if (*length - namelen != 0 && *length - namelen >= type_index_size) { + /* copy the info from the oid */ + vrf_name_len = *length - (namelen + type_index_size); + oid2string(name + namelen, vrf_name_len, vrf_name); + oid2int(name + namelen + vrf_name_len, (int *)rt_index); + *rt_type = name[namelen + vrf_name_len + sizeof(uint32_t)]; + } + + /* validate the RT index is in range */ + if (*rt_index > AFI_IP6) + return NULL; + + if (exact) { + l3vpn_bgp = bgp_lookup_by_name(vrf_name); + if (l3vpn_bgp && !is_bgp_vrf_mplsvpn(l3vpn_bgp)) + return NULL; + if (!l3vpn_bgp) + return NULL; + if ((*rt_index != AFI_IP) && (*rt_index != AFI_IP6)) + return NULL; + /* do we have RT config */ + if (!(l3vpn_bgp->vpn_policy[*rt_index] + .rtlist[BGP_VPN_POLICY_DIR_FROMVPN] + || l3vpn_bgp->vpn_policy[*rt_index] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN])) + return NULL; + return l3vpn_bgp; + } + if (strnlen(vrf_name, VRF_NAMSIZ) == 0) + l3vpn_bgp = bgp_lookup_by_name_next(vrf_name); + else + l3vpn_bgp = bgp_lookup_by_name(vrf_name); + while (l3vpn_bgp) { + switch (*rt_index) { + case 0: + *rt_index = AFI_IP; + break; + case AFI_IP: + *rt_index = AFI_IP6; + break; + case AFI_IP6: + *rt_index = 0; + continue; + } + if (*rt_index) { + switch (*rt_type) { + case 0: + *rt_type = MPLSVPNVRFRTTYPEIMPORT; + break; + case MPLSVPNVRFRTTYPEIMPORT: + *rt_type = MPLSVPNVRFRTTYPEEXPORT; + break; + case MPLSVPNVRFRTTYPEEXPORT: + case MPLSVPNVRFRTTYPEBOTH: + *rt_type = 0; + break; + } + if (*rt_type) { + bool import, export; + + import = + (!!l3vpn_bgp->vpn_policy[*rt_index].rtlist + [BGP_VPN_POLICY_DIR_FROMVPN]); + export = + (!!l3vpn_bgp->vpn_policy[*rt_index].rtlist + [BGP_VPN_POLICY_DIR_TOVPN]); + if (*rt_type == MPLSVPNVRFRTTYPEIMPORT + && !import) + continue; + if (*rt_type == MPLSVPNVRFRTTYPEEXPORT + && !export) + continue; + /* ckeck for both */ + if (*rt_type == MPLSVPNVRFRTTYPEIMPORT && import + && export + && ecommunity_cmp( + l3vpn_bgp->vpn_policy[*rt_index].rtlist + [BGP_VPN_POLICY_DIR_FROMVPN], + l3vpn_bgp->vpn_policy[*rt_index].rtlist + [BGP_VPN_POLICY_DIR_TOVPN])) + *rt_type = MPLSVPNVRFRTTYPEBOTH; + + /* we have a match copy the oid info */ + vrf_name_len = + strnlen(l3vpn_bgp->name, VRF_NAMSIZ); + len = vrf_name_len + sizeof(uint32_t) + + sizeof(uint8_t); + oid_copy_str(name + namelen, l3vpn_bgp->name, + vrf_name_len); + oid_copy_int(name + namelen + vrf_name_len, + (int *)rt_index); + name[(namelen + len) - 1] = *rt_type; + *length = len + namelen; + return l3vpn_bgp; + } + l3vpn_bgp = bgp_lookup_by_name_next(l3vpn_bgp->name); + } + } + return NULL; +} + +static const char *rt_type2str(uint8_t rt_type) +{ + switch (rt_type) { + case MPLSVPNVRFRTTYPEIMPORT: + return "import"; + case MPLSVPNVRFRTTYPEEXPORT: + return "export"; + case MPLSVPNVRFRTTYPEBOTH: + return "both"; + default: + return "unknown"; + } +} +static uint8_t *mplsL3vpnVrfRtTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + char vrf_name[VRF_NAMSIZ]; + struct bgp *l3vpn_bgp; + uint32_t rt_index = 0; + uint8_t rt_type = 0; + char *rt_b = NULL; + static char rt_b_str[BUFSIZ] = {}; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(vrf_name, 0, VRF_NAMSIZ); + l3vpn_bgp = bgpL3vpnVrfRt_lookup(v, name, length, vrf_name, &rt_index, + &rt_type, exact); + + if (!l3vpn_bgp) + return NULL; + + switch (v->magic) { + case MPLSL3VPNVRFRT: + switch (rt_type) { + case MPLSVPNVRFRTTYPEIMPORT: + rt_b = ecommunity_ecom2str( + l3vpn_bgp->vpn_policy[rt_index] + .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + break; + case MPLSVPNVRFRTTYPEEXPORT: + case MPLSVPNVRFRTTYPEBOTH: + rt_b = ecommunity_ecom2str( + l3vpn_bgp->vpn_policy[rt_index] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN], + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + break; + default: + break; + } + if (rt_b) { + *var_len = strnlen(rt_b, ECOMMUNITY_STRLEN); + strlcpy(rt_b_str, rt_b, sizeof(rt_b_str)); + XFREE(MTYPE_ECOMMUNITY_STR, rt_b); + } else { + *var_len = 0; + } + return (uint8_t *)rt_b_str; + case MPLSL3VPNVRFRTDESCR: + /* since we dont have a description generate one */ + memset(rt_description, 0, VRF_NAMSIZ + RT_PREAMBLE_SIZE); + snprintf(rt_description, VRF_NAMSIZ + RT_PREAMBLE_SIZE, + "RT %s for VRF %s", rt_type2str(rt_type), + l3vpn_bgp->name); + *var_len = + strnlen(rt_description, VRF_NAMSIZ + RT_PREAMBLE_SIZE); + return (uint8_t *)rt_description; + case MPLSL3VPNVRFRTROWSTATUS: + return SNMP_INTEGER(1); + case MPLSL3VPNVRFRTSTORAGETYPE: + return SNMP_INTEGER(2); + } + return NULL; +} + +/* 1.3.6.1.2.1.10.166.11.1.3.1.1.x = 14*/ +#define PERFTAB_NAMELEN 14 + +static uint8_t *mplsL3vpnPerfTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + char vrf_name[VRF_NAMSIZ]; + struct bgp *l3vpn_bgp; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(vrf_name, 0, VRF_NAMSIZ); + l3vpn_bgp = bgpL3vpnVrf_lookup(v, name, length, vrf_name, exact); + + if (!l3vpn_bgp) + return NULL; + + switch (v->magic) { + case MPLSL3VPNVRFPERFROUTESADDED: + return SNMP_INTEGER(l3vpn_bgp->snmp_stats->routes_added); + case MPLSL3VPNVRFPERFROUTESDELETED: + return SNMP_INTEGER(l3vpn_bgp->snmp_stats->routes_deleted); + case MPLSL3VPNVRFPERFCURRNUMROUTES: + return SNMP_INTEGER(bgp_mpls_l3vpn_current_routes(l3vpn_bgp)); + } + return NULL; +} + +static struct bgp_path_info * +bgp_lookup_route(struct bgp *l3vpn_bgp, struct bgp_dest **dest, + struct prefix *prefix, uint16_t policy, struct ipaddr *nexthop) +{ + struct bgp_path_info *pi = NULL; + struct bgp_table *table; + + switch (prefix->family) { + case AF_INET: + table = l3vpn_bgp->rib[AFI_IP][SAFI_UNICAST]; + break; + case AF_INET6: + table = l3vpn_bgp->rib[AFI_IP6][SAFI_UNICAST]; + break; + default: + return NULL; + } + + /*get the prefix */ + *dest = bgp_node_lookup(table, prefix); + if (*dest == NULL) + return NULL; + + /* now find the right path */ + pi = bgp_dest_get_bgp_path_info(*dest); + for (; pi; pi = pi->next) { + switch (nexthop->ipa_type) { + case IPADDR_V4: + if (nexthop->ip._v4_addr.s_addr + == pi->attr->nexthop.s_addr) + return pi; + break; + case IPADDR_V6: + if (memcmp(&nexthop->ip._v6_addr, + &pi->attr->mp_nexthop_global, + sizeof(struct in6_addr)) + == 0) + return pi; + break; + case IPADDR_NONE: + return pi; + } + } + return NULL; +} + +static struct bgp_path_info *bgp_lookup_route_next(struct bgp **l3vpn_bgp, + struct bgp_dest **dest, + struct prefix *prefix, + uint16_t *policy, + struct ipaddr *nexthop) +{ + struct bgp_path_info *pi = NULL; + struct bgp_table *table; + const struct prefix *p; + uint8_t family; + + /* First route?*/ + if (prefix->prefixlen == 0) { + /* try V4 table */ + table = (*l3vpn_bgp)->rib[AFI_IP][SAFI_UNICAST]; + for (*dest = bgp_table_top(table); *dest; + *dest = bgp_route_next(*dest)) { + pi = bgp_dest_get_bgp_path_info(*dest); + if (pi) + break; + } + + if (!pi) { + /* try V6 table */ + table = (*l3vpn_bgp)->rib[AFI_IP6][SAFI_UNICAST]; + for (*dest = bgp_table_top(table); *dest; + *dest = bgp_route_next(*dest)) { + pi = bgp_dest_get_bgp_path_info(*dest); + if (pi) + break; + } + } + return pi; + } + /* real next search for the entry first use exact lookup */ + pi = bgp_lookup_route(*l3vpn_bgp, dest, prefix, *policy, nexthop); + + if (pi == NULL) + return NULL; + + p = bgp_dest_get_prefix(*dest); + family = p->family; + + /* We have found the input path let's find the next one in the list */ + if (pi->next) { + /* ensure OID is always higher for multipath routes by + * incrementing opaque policy oid + */ + *policy += 1; + return pi->next; + } + + /* No more paths in the input route so find the next route */ + for (; *l3vpn_bgp; + *l3vpn_bgp = bgp_lookup_by_name_next((*l3vpn_bgp)->name)) { + *policy = 0; + if (!*dest) { + table = (*l3vpn_bgp)->rib[AFI_IP][SAFI_UNICAST]; + *dest = bgp_table_top(table); + family = AF_INET; + } else + *dest = bgp_route_next(*dest); + + while (true) { + for (; *dest; *dest = bgp_route_next(*dest)) { + pi = bgp_dest_get_bgp_path_info(*dest); + + if (pi) + return pi; + } + if (family == AF_INET) { + table = (*l3vpn_bgp) + ->rib[AFI_IP6][SAFI_UNICAST]; + *dest = bgp_table_top(table); + family = AF_INET6; + continue; + } + break; + } + } + + return NULL; +} + +static bool is_addr_type(oid id) +{ + switch (id) { + case INETADDRESSTYPEUNKNOWN: + case INETADDRESSTYPEIPV4: + case INETADDRESSTYPEIPV6: + return true; + } + return false; +} + +/* 1.3.6.1.2.1.10.166.11.1.4.1.1.x = 14*/ +#define PERFTAB_NAMELEN 14 + +static struct bgp_path_info *bgpL3vpnRte_lookup(struct variable *v, oid name[], + size_t *length, char *vrf_name, + struct bgp **l3vpn_bgp, + struct bgp_dest **dest, + uint16_t *policy, int exact) +{ + uint8_t i; + uint8_t vrf_name_len = 0; + struct bgp_path_info *pi = NULL; + size_t namelen = v ? v->namelen : IFCONFTAB_NAMELEN; + struct prefix prefix = {0}; + struct ipaddr nexthop = {0}; + uint8_t prefix_type; + uint8_t nexthop_type; + + if ((uint32_t)(*length - namelen) > (VRF_NAMSIZ + 37)) + return NULL; + + if (*length - namelen != 0) { + /* parse incoming OID */ + for (i = namelen; i < (*length); i++) { + if (is_addr_type(name[i])) + break; + vrf_name_len++; + } + if (vrf_name_len > VRF_NAMSIZ) + return NULL; + + oid2string(name + namelen, vrf_name_len, vrf_name); + prefix_type = name[i++]; + switch (prefix_type) { + case INETADDRESSTYPEUNKNOWN: + prefix.family = AF_UNSPEC; + break; + case INETADDRESSTYPEIPV4: + prefix.family = AF_INET; + oid2in_addr(&name[i], sizeof(struct in_addr), + &prefix.u.prefix4); + i += sizeof(struct in_addr); + break; + case INETADDRESSTYPEIPV6: + prefix.family = AF_INET6; + oid2in6_addr(&name[i], &prefix.u.prefix6); + i += sizeof(struct in6_addr); + break; + } + prefix.prefixlen = (uint8_t)name[i++]; + *policy |= name[i++] << 8; + *policy |= name[i++]; + nexthop_type = name[i++]; + switch (nexthop_type) { + case INETADDRESSTYPEUNKNOWN: + nexthop.ipa_type = (prefix.family == AF_INET) + ? IPADDR_V4 + : IPADDR_V6; + break; + case INETADDRESSTYPEIPV4: + nexthop.ipa_type = IPADDR_V4; + oid2in_addr(&name[i], sizeof(struct in_addr), + &nexthop.ip._v4_addr); + /* i += sizeof(struct in_addr); */ + break; + case INETADDRESSTYPEIPV6: + nexthop.ipa_type = IPADDR_V6; + oid2in6_addr(&name[i], &nexthop.ip._v6_addr); + /* i += sizeof(struct in6_addr); */ + break; + } + } + + if (exact) { + *l3vpn_bgp = bgp_lookup_by_name(vrf_name); + if (*l3vpn_bgp && !is_bgp_vrf_mplsvpn(*l3vpn_bgp)) + return NULL; + if (*l3vpn_bgp == NULL) + return NULL; + + /* now lookup the route in this bgp table */ + pi = bgp_lookup_route(*l3vpn_bgp, dest, &prefix, *policy, + &nexthop); + } else { + int str_len; + + str_len = strnlen(vrf_name, VRF_NAMSIZ); + if (str_len == 0) { + *l3vpn_bgp = bgp_lookup_by_name_next(vrf_name); + } else + /* otherwise lookup the one we have */ + *l3vpn_bgp = bgp_lookup_by_name(vrf_name); + + if (*l3vpn_bgp == NULL) + return NULL; + + pi = bgp_lookup_route_next(l3vpn_bgp, dest, &prefix, policy, + &nexthop); + if (pi) { + uint8_t vrf_name_len = + strnlen((*l3vpn_bgp)->name, VRF_NAMSIZ); + const struct prefix *p = bgp_dest_get_prefix(*dest); + uint8_t oid_index; + bool v4 = (p->family == AF_INET); + uint8_t addr_len = v4 ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + struct attr *attr = pi->attr; + + /* copy the index parameters */ + oid_copy_str(&name[namelen], (*l3vpn_bgp)->name, + vrf_name_len); + oid_index = namelen + vrf_name_len; + if (v4) { + name[oid_index++] = INETADDRESSTYPEIPV4; + oid_copy_in_addr(&name[oid_index], + &p->u.prefix4); + } else { + name[oid_index++] = INETADDRESSTYPEIPV6; + oid_copy_in6_addr(&name[oid_index], + &p->u.prefix6); + } + + oid_index += addr_len; + name[oid_index++] = p->prefixlen; + name[oid_index++] = *policy >> 8; + name[oid_index++] = *policy & 0xff; + + if (!BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { + if (attr->nexthop.s_addr == INADDR_ANY) + name[oid_index++] = + INETADDRESSTYPEUNKNOWN; + else { + name[oid_index++] = INETADDRESSTYPEIPV4; + oid_copy_in_addr(&name[oid_index], + &attr->nexthop); + oid_index += sizeof(struct in_addr); + } + } else { + if (IN6_IS_ADDR_UNSPECIFIED( + &attr->mp_nexthop_global)) + name[oid_index++] = + INETADDRESSTYPEUNKNOWN; + else { + name[oid_index++] = INETADDRESSTYPEIPV6; + oid_copy_in6_addr( + &name[oid_index], + &attr->mp_nexthop_global); + oid_index += sizeof(struct in6_addr); + } + } + *length = oid_index; + } + } + return pi; +} + +static uint8_t *mplsL3vpnRteTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + char vrf_name[VRF_NAMSIZ]; + struct bgp *l3vpn_bgp; + struct bgp_dest *dest; + struct bgp_path_info *pi, *bpi_ultimate; + const struct prefix *p; + uint16_t policy = 0; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(vrf_name, 0, VRF_NAMSIZ); + pi = bgpL3vpnRte_lookup(v, name, length, vrf_name, &l3vpn_bgp, &dest, + &policy, exact); + + + if (!pi) + return NULL; + + bpi_ultimate = bgp_get_imported_bpi_ultimate(pi); + + p = bgp_dest_get_prefix(dest); + + if (!p) + return NULL; + + switch (v->magic) { + case MPLSL3VPNVRFRTEINETCIDRDESTTYPE: + switch (p->family) { + case AF_INET: + return SNMP_INTEGER(INETADDRESSTYPEIPV4); + case AF_INET6: + return SNMP_INTEGER(INETADDRESSTYPEIPV6); + default: + return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN); + } + case MPLSL3VPNVRFRTEINETCIDRDEST: + switch (p->family) { + case AF_INET: + return SNMP_IPADDRESS(p->u.prefix4); + case AF_INET6: + return SNMP_IP6ADDRESS(p->u.prefix6); + default: + *var_len = 0; + return NULL; + } + case MPLSL3VPNVRFRTEINETCIDRPFXLEN: + return SNMP_INTEGER(p->prefixlen); + case MPLSL3VPNVRFRTEINETCIDRPOLICY: + *var_len = sizeof(mpls_l3vpn_policy_oid); + mpls_l3vpn_policy_oid[0] = policy >> 8; + mpls_l3vpn_policy_oid[1] = policy & 0xff; + return (uint8_t *)mpls_l3vpn_policy_oid; + case MPLSL3VPNVRFRTEINETCIDRNHOPTYPE: + if (!BGP_ATTR_NEXTHOP_AFI_IP6(pi->attr)) { + if (pi->attr->nexthop.s_addr == INADDR_ANY) + return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN); + else + return SNMP_INTEGER(INETADDRESSTYPEIPV4); + } else if (IN6_IS_ADDR_UNSPECIFIED( + &pi->attr->mp_nexthop_global)) + return SNMP_INTEGER(INETADDRESSTYPEUNKNOWN); + else + return SNMP_INTEGER(INETADDRESSTYPEIPV6); + + case MPLSL3VPNVRFRTEINETCIDRNEXTHOP: + if (!BGP_ATTR_NEXTHOP_AFI_IP6(pi->attr)) + if (pi->attr->nexthop.s_addr == INADDR_ANY) { + *var_len = 0; + return (uint8_t *)empty_nhop; + } else + return SNMP_IPADDRESS(pi->attr->nexthop); + else if (IN6_IS_ADDR_UNSPECIFIED( + &pi->attr->mp_nexthop_global)) { + *var_len = 0; + return (uint8_t *)empty_nhop; + } else + return SNMP_IP6ADDRESS(pi->attr->mp_nexthop_global); + + case MPLSL3VPNVRFRTEINETCIDRIFINDEX: + if (pi->nexthop && pi->nexthop->nexthop) + return SNMP_INTEGER(pi->nexthop->nexthop->ifindex); + else + return SNMP_INTEGER(0); + case MPLSL3VPNVRFRTEINETCIDRTYPE: + if (pi->nexthop && pi->nexthop->nexthop) { + switch (pi->nexthop->nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + return SNMP_INTEGER( + MPLSL3VPNVRFRTECIDRTYPELOCAL); + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + return SNMP_INTEGER( + MPLSL3VPNVRFRTECIDRTYPEREMOTE); + case NEXTHOP_TYPE_BLACKHOLE: + switch (pi->nexthop->nexthop->bh_type) { + case BLACKHOLE_REJECT: + return SNMP_INTEGER( + MPLSL3VPNVRFRTECIDRTYPEREJECT); + case BLACKHOLE_UNSPEC: + case BLACKHOLE_NULL: + case BLACKHOLE_ADMINPROHIB: + return SNMP_INTEGER( + MPLSL3VPNVRFRTECIDRTYPEBLACKHOLE); + } + break; + } + } else + return SNMP_INTEGER(MPLSL3VPNVRFRTECIDRTYPEOTHER); + break; + case MPLSL3VPNVRFRTEINETCIDRPROTO: + switch (pi->type) { + case ZEBRA_ROUTE_CONNECT: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLLOCAL); + case ZEBRA_ROUTE_STATIC: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLNETMGMT); + case ZEBRA_ROUTE_RIP: + case ZEBRA_ROUTE_RIPNG: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLRIP); + case ZEBRA_ROUTE_OSPF: + case ZEBRA_ROUTE_OSPF6: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLOSPF); + case ZEBRA_ROUTE_ISIS: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLISIS); + case ZEBRA_ROUTE_BGP: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLBGP); + case ZEBRA_ROUTE_EIGRP: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLCISCOEIGRP); + default: + return SNMP_INTEGER(IANAIPROUTEPROTOCOLOTHER); + } + case MPLSL3VPNVRFRTEINETCIDRAGE: + return SNMP_INTEGER(pi->uptime); + case MPLSL3VPNVRFRTEINETCIDRNEXTHOPAS: + return SNMP_INTEGER(pi->peer ? pi->peer->as : 0); + case MPLSL3VPNVRFRTEINETCIDRMETRIC1: + if (bpi_ultimate->extra) + return SNMP_INTEGER(bpi_ultimate->extra->igpmetric); + else + return SNMP_INTEGER(0); + case MPLSL3VPNVRFRTEINETCIDRMETRIC2: + return SNMP_INTEGER(-1); + case MPLSL3VPNVRFRTEINETCIDRMETRIC3: + return SNMP_INTEGER(-1); + case MPLSL3VPNVRFRTEINETCIDRMETRIC4: + return SNMP_INTEGER(-1); + case MPLSL3VPNVRFRTEINETCIDRMETRIC5: + return SNMP_INTEGER(-1); + case MPLSL3VPNVRFRTEINETCIDRXCPOINTER: + return SNMP_OCTET(0); + case MPLSL3VPNVRFRTEINETCIDRSTATUS: + return SNMP_INTEGER(1); + } + return NULL; +} + +void bgp_mpls_l3vpn_module_init(void) +{ + hook_register(bgp_vrf_status_changed, bgp_vrf_check_update_active); + hook_register(bgp_snmp_init_stats, bgp_init_snmp_stats); + hook_register(bgp_snmp_update_last_changed, + bgp_mpls_l3vpn_update_last_changed); + hook_register(bgp_snmp_update_stats, bgp_snmp_update_route_stats); + REGISTER_MIB("mplsL3VpnMIB", mpls_l3vpn_variables, variable, + mpls_l3vpn_oid); +} diff --git a/bgpd/bgp_mplsvpn_snmp.h b/bgpd/bgp_mplsvpn_snmp.h new file mode 100644 index 0000000..f7fa46c --- /dev/null +++ b/bgpd/bgp_mplsvpn_snmp.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* MPLS/BGP L3VPN MIB + * Copyright (C) 2020 Volta Networks Inc + */ + +void bgp_mpls_l3vpn_module_init(void); + +#define MPLSL3VPNVRFRTECIDRTYPEOTHER 1 +#define MPLSL3VPNVRFRTECIDRTYPEREJECT 2 +#define MPLSL3VPNVRFRTECIDRTYPELOCAL 3 +#define MPLSL3VPNVRFRTECIDRTYPEREMOTE 4 +#define MPLSL3VPNVRFRTECIDRTYPEBLACKHOLE 5 + +#define MPLSVPNVRFRTTYPEIMPORT 1 +#define MPLSVPNVRFRTTYPEEXPORT 2 +#define MPLSVPNVRFRTTYPEBOTH 3 diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c new file mode 100644 index 0000000..e09dbc2 --- /dev/null +++ b/bgpd/bgp_network.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP network related fucntions + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "frrevent.h" +#include "sockunion.h" +#include "sockopt.h" +#include "memory.h" +#include "log.h" +#include "if.h" +#include "prefix.h" +#include "command.h" +#include "privs.h" +#include "linklist.h" +#include "network.h" +#include "queue.h" +#include "hash.h" +#include "filter.h" +#include "ns.h" +#include "lib_errors.h" +#include "nexthop.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_nht.h" + +extern struct zebra_privs_t bgpd_privs; + +static char *bgp_get_bound_name(struct peer_connection *connection); + +void bgp_dump_listener_info(struct vty *vty) +{ + struct listnode *node; + struct bgp_listener *listener; + + vty_out(vty, "Name fd Address\n"); + vty_out(vty, "---------------------------\n"); + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) + vty_out(vty, "%-16s %d %pSU\n", + listener->name ? listener->name : VRF_DEFAULT_NAME, + listener->fd, &listener->su); +} + +/* + * Set MD5 key for the socket, for the given IPv4 peer address. + * If the password is NULL or zero-length, the option will be disabled. + */ +static int bgp_md5_set_socket(int socket, union sockunion *su, + uint16_t prefixlen, const char *password) +{ + int ret = -1; + int en = ENOSYS; +#if HAVE_DECL_TCP_MD5SIG + union sockunion su2; +#endif /* HAVE_TCP_MD5SIG */ + + assert(socket >= 0); + +#if HAVE_DECL_TCP_MD5SIG + /* Ensure there is no extraneous port information. */ + memcpy(&su2, su, sizeof(union sockunion)); + if (su2.sa.sa_family == AF_INET) + su2.sin.sin_port = 0; + else + su2.sin6.sin6_port = 0; + + /* For addresses, use the non-extended signature functionality */ + if ((su2.sa.sa_family == AF_INET && prefixlen == IPV4_MAX_BITLEN) + || (su2.sa.sa_family == AF_INET6 && prefixlen == IPV6_MAX_BITLEN)) + ret = sockopt_tcp_signature(socket, &su2, password); + else + ret = sockopt_tcp_signature_ext(socket, &su2, prefixlen, + password); + en = errno; +#endif /* HAVE_TCP_MD5SIG */ + + if (ret < 0) { + switch (ret) { + case -2: + flog_warn( + EC_BGP_NO_TCP_MD5, + "Unable to set TCP MD5 option on socket for peer %pSU (sock=%d): This platform does not support MD5 auth for prefixes", + su, socket); + break; + default: + flog_warn( + EC_BGP_NO_TCP_MD5, + "Unable to set TCP MD5 option on socket for peer %pSU (sock=%d): %s", + su, socket, safe_strerror(en)); + } + } + + return ret; +} + +/* Helper for bgp_connect */ +static int bgp_md5_set_connect(int socket, union sockunion *su, + uint16_t prefixlen, const char *password) +{ + int ret = -1; + +#if HAVE_DECL_TCP_MD5SIG + frr_with_privs(&bgpd_privs) { + ret = bgp_md5_set_socket(socket, su, prefixlen, password); + } +#endif /* HAVE_TCP_MD5SIG */ + + return ret; +} + +static int bgp_md5_set_password(struct peer_connection *connection, + const char *password) +{ + struct listnode *node; + int ret = 0; + struct bgp_listener *listener; + struct peer *peer = connection->peer; + + /* + * Set or unset the password on the listen socket(s). Outbound + * connections are taken care of in bgp_connect() below. + */ + frr_with_privs(&bgpd_privs) { + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) + if (listener->su.sa.sa_family == + connection->su.sa.sa_family) { + uint16_t prefixlen = + connection->su.sa.sa_family == AF_INET + ? IPV4_MAX_BITLEN + : IPV6_MAX_BITLEN; + + /* + * if we have stored a BGP vrf instance in the + * listener it must match the bgp instance in + * the peer otherwise the peer bgp instance + * must be the default vrf or a view instance + */ + if (!listener->bgp) { + if (peer->bgp->vrf_id != VRF_DEFAULT) + continue; + } else if (listener->bgp != peer->bgp) + continue; + + ret = bgp_md5_set_socket(listener->fd, + &connection->su, + prefixlen, password); + break; + } + } + return ret; +} + +int bgp_md5_set_prefix(struct bgp *bgp, struct prefix *p, const char *password) +{ + int ret = 0; + union sockunion su; + struct listnode *node; + struct bgp_listener *listener; + + /* Set or unset the password on the listen socket(s). */ + frr_with_privs(&bgpd_privs) { + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) + if (listener->su.sa.sa_family == p->family + && ((bgp->vrf_id == VRF_DEFAULT) + || (listener->bgp == bgp))) { + prefix2sockunion(p, &su); + ret = bgp_md5_set_socket(listener->fd, &su, + p->prefixlen, + password); + break; + } + } + + return ret; +} + +int bgp_md5_unset_prefix(struct bgp *bgp, struct prefix *p) +{ + return bgp_md5_set_prefix(bgp, p, NULL); +} + +int bgp_md5_set(struct peer_connection *connection) +{ + /* Set the password from listen socket. */ + return bgp_md5_set_password(connection, connection->peer->password); +} + +static void bgp_update_setsockopt_tcp_keepalive(struct bgp *bgp, int fd) +{ + if (!bgp) + return; + if (bgp->tcp_keepalive_idle != 0) { + int ret; + + ret = setsockopt_tcp_keepalive(fd, bgp->tcp_keepalive_idle, + bgp->tcp_keepalive_intvl, + bgp->tcp_keepalive_probes); + if (ret < 0) + zlog_err( + "Can't set TCP keepalive on socket %d, idle %u intvl %u probes %u", + fd, bgp->tcp_keepalive_idle, + bgp->tcp_keepalive_intvl, + bgp->tcp_keepalive_probes); + } +} + +int bgp_md5_unset(struct peer_connection *connection) +{ + /* Unset the password from listen socket. */ + return bgp_md5_set_password(connection, NULL); +} + +int bgp_set_socket_ttl(struct peer_connection *connection) +{ + int ret = 0; + struct peer *peer = connection->peer; + + if (!peer->gtsm_hops) { + ret = sockopt_ttl(connection->su.sa.sa_family, connection->fd, + peer->ttl); + if (ret) { + flog_err( + EC_LIB_SOCKET, + "%s: Can't set TxTTL on peer (rtrid %pI4) socket, err = %d", + __func__, &peer->remote_id, errno); + return ret; + } + } else { + /* On Linux, setting minttl without setting ttl seems to mess + with the + outgoing ttl. Therefore setting both. + */ + ret = sockopt_ttl(connection->su.sa.sa_family, connection->fd, + MAXTTL); + if (ret) { + flog_err( + EC_LIB_SOCKET, + "%s: Can't set TxTTL on peer (rtrid %pI4) socket, err = %d", + __func__, &peer->remote_id, errno); + return ret; + } + ret = sockopt_minttl(connection->su.sa.sa_family, connection->fd, + MAXTTL + 1 - peer->gtsm_hops); + if (ret) { + flog_err( + EC_LIB_SOCKET, + "%s: Can't set MinTTL on peer (rtrid %pI4) socket, err = %d", + __func__, &peer->remote_id, errno); + return ret; + } + } + + return ret; +} + +/* + * Obtain the BGP instance that the incoming connection should be processed + * against. This is important because more than one VRF could be using the + * same IP address space. The instance is got by obtaining the device to + * which the incoming connection is bound to. This could either be a VRF + * or it could be an interface, which in turn determines the VRF. + */ +static int bgp_get_instance_for_inc_conn(int sock, struct bgp **bgp_inst) +{ +#ifndef SO_BINDTODEVICE + /* only Linux has SO_BINDTODEVICE, but we're in Linux-specific code here + * anyway since the assumption is that the interface name returned by + * getsockopt() is useful in identifying the VRF, particularly with + * Linux's + * VRF l3master device. The whole mechanism is specific to Linux, so... + * when other platforms add VRF support, this will need handling here as + * well. (or, some restructuring) */ + *bgp_inst = bgp_get_default(); + return !*bgp_inst; + +#else + char name[VRF_NAMSIZ + 1]; + socklen_t name_len = VRF_NAMSIZ; + struct bgp *bgp; + int rc; + struct listnode *node, *nnode; + + *bgp_inst = NULL; + name[0] = '\0'; + rc = getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, name, &name_len); + if (rc != 0) { +#if defined(HAVE_CUMULUS) + flog_err(EC_LIB_SOCKET, + "[Error] BGP SO_BINDTODEVICE get failed (%s), sock %d", + safe_strerror(errno), sock); + return -1; +#endif + } + + if (!strlen(name)) { + *bgp_inst = bgp_get_default(); + return 0; /* default instance. */ + } + + /* First try match to instance; if that fails, check for interfaces. */ + bgp = bgp_lookup_by_name(name); + if (bgp) { + if (!bgp->vrf_id) // unexpected + return -1; + *bgp_inst = bgp; + return 0; + } + + /* TODO - This will be optimized once interfaces move into the NS */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + struct interface *ifp; + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + continue; + + ifp = if_lookup_by_name(name, bgp->vrf_id); + if (ifp) { + *bgp_inst = bgp; + return 0; + } + } + + /* We didn't match to either an instance or an interface. */ + return -1; +#endif +} + +int bgp_tcp_mss_set(struct peer *peer) +{ + struct listnode *node; + int ret = 0; + struct bgp_listener *listener; + uint32_t min_mss = 0; + struct peer *p; + + for (ALL_LIST_ELEMENTS_RO(peer->bgp->peer, node, p)) { + if (!CHECK_FLAG(p->flags, PEER_FLAG_TCP_MSS)) + continue; + + if (!p->tcp_mss) + continue; + + if (!min_mss) + min_mss = p->tcp_mss; + + min_mss = MIN(min_mss, p->tcp_mss); + } + + frr_with_privs(&bgpd_privs) { + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) { + if (listener->su.sa.sa_family != + peer->connection->su.sa.sa_family) + continue; + + if (!listener->bgp) { + if (peer->bgp->vrf_id != VRF_DEFAULT) + continue; + } else if (listener->bgp != peer->bgp) + continue; + + /* Set TCP MSS per listener only if there is at least + * one peer that is in passive mode. Otherwise, TCP MSS + * is set per socket via bgp_connect(). + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSIVE)) + sockopt_tcp_mss_set(listener->fd, min_mss); + + break; + } + } + + return ret; +} + +static void bgp_socket_set_buffer_size(const int fd) +{ + if (getsockopt_so_sendbuf(fd) < (int)bm->socket_buffer) + setsockopt_so_sendbuf(fd, bm->socket_buffer); + if (getsockopt_so_recvbuf(fd) < (int)bm->socket_buffer) + setsockopt_so_recvbuf(fd, bm->socket_buffer); +} + +/* Accept bgp connection. */ +static void bgp_accept(struct event *thread) +{ + int bgp_sock; + int accept_sock; + union sockunion su; + struct bgp_listener *listener = EVENT_ARG(thread); + struct peer *peer, *peer1; + struct peer_connection *connection, *connection1; + char buf[SU_ADDRSTRLEN]; + struct bgp *bgp = NULL; + + sockunion_init(&su); + + bgp = bgp_lookup_by_name(listener->name); + + /* Register accept thread. */ + accept_sock = EVENT_FD(thread); + if (accept_sock < 0) { + flog_err_sys(EC_LIB_SOCKET, + "[Error] BGP accept socket fd is negative: %d", + accept_sock); + return; + } + + event_add_read(bm->master, bgp_accept, listener, accept_sock, + &listener->thread); + + /* Accept client connection. */ + bgp_sock = sockunion_accept(accept_sock, &su); + int save_errno = errno; + if (bgp_sock < 0) { + if (save_errno == EINVAL) { + struct vrf *vrf = + bgp ? vrf_lookup_by_id(bgp->vrf_id) : NULL; + + /* + * It appears that sometimes, when VRFs are deleted on + * the system, it takes a little while for us to get + * notified about that. In the meantime we endlessly + * loop on accept(), because the socket, having been + * bound to a now-deleted VRF device, is in some weird + * state which causes accept() to fail. + * + * To avoid this, if we see accept() fail with EINVAL, + * we cancel ourselves and trust that when the VRF + * deletion notification comes in the event handler for + * that will take care of cleaning us up. + */ + flog_err_sys( + EC_LIB_SOCKET, + "[Error] accept() failed with error \"%s\" on BGP listener socket %d for BGP instance in VRF \"%s\"; refreshing socket", + safe_strerror(save_errno), accept_sock, + VRF_LOGNAME(vrf)); + EVENT_OFF(listener->thread); + } else { + flog_err_sys( + EC_LIB_SOCKET, + "[Error] BGP socket accept failed (%s); retrying", + safe_strerror(save_errno)); + } + return; + } + set_nonblocking(bgp_sock); + + /* Obtain BGP instance this connection is meant for. + * - if it is a VRF netns sock, then BGP is in listener structure + * - otherwise, the bgp instance need to be demultiplexed + */ + if (listener->bgp) + bgp = listener->bgp; + else if (bgp_get_instance_for_inc_conn(bgp_sock, &bgp)) { + if (bgp_debug_neighbor_events(NULL)) + zlog_debug( + "[Event] Could not get instance for incoming conn from %s", + inet_sutop(&su, buf)); + close(bgp_sock); + return; + } + + bgp_socket_set_buffer_size(bgp_sock); + + /* Set TCP keepalive when TCP keepalive is enabled */ + bgp_update_setsockopt_tcp_keepalive(bgp, bgp_sock); + + /* Check remote IP address */ + peer1 = peer_lookup(bgp, &su); + + if (!peer1) { + peer1 = peer_lookup_dynamic_neighbor(bgp, &su); + if (peer1) { + connection1 = peer1->connection; + /* Dynamic neighbor has been created, let it proceed */ + connection1->fd = bgp_sock; + + if (bgp_set_socket_ttl(connection1) < 0) { + peer1->last_reset = PEER_DOWN_SOCKET_ERROR; + zlog_err("%s: Unable to set min/max TTL on peer %s (dynamic), error received: %s(%d)", + __func__, peer1->host, + safe_strerror(errno), errno); + return; + } + + /* Set the user configured MSS to TCP socket */ + if (CHECK_FLAG(peer1->flags, PEER_FLAG_TCP_MSS)) + sockopt_tcp_mss_set(bgp_sock, peer1->tcp_mss); + + frr_with_privs (&bgpd_privs) { + vrf_bind(peer1->bgp->vrf_id, bgp_sock, + bgp_get_bound_name(connection1)); + } + bgp_peer_reg_with_nht(peer1); + bgp_fsm_change_status(connection1, Active); + EVENT_OFF(connection1->t_start); + + if (peer_active(peer1)) { + if (CHECK_FLAG(peer1->flags, + PEER_FLAG_TIMER_DELAYOPEN)) + BGP_EVENT_ADD(connection1, + TCP_connection_open_w_delay); + else + BGP_EVENT_ADD(connection1, + TCP_connection_open); + } + + return; + } + } + + if (!peer1) { + if (bgp_debug_neighbor_events(NULL)) { + zlog_debug( + "[Event] %s connection rejected(%s:%u:%s) - not configured and not valid for dynamic", + inet_sutop(&su, buf), bgp->name_pretty, bgp->as, + VRF_LOGNAME(vrf_lookup_by_id(bgp->vrf_id))); + } + close(bgp_sock); + return; + } + + connection1 = peer1->connection; + if (CHECK_FLAG(peer1->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(peer1->bgp->flags, BGP_FLAG_SHUTDOWN)) { + if (bgp_debug_neighbor_events(peer1)) + zlog_debug( + "[Event] connection from %s rejected(%s:%u:%s) due to admin shutdown", + inet_sutop(&su, buf), bgp->name_pretty, bgp->as, + VRF_LOGNAME(vrf_lookup_by_id(bgp->vrf_id))); + close(bgp_sock); + return; + } + + /* + * Do not accept incoming connections in Clearing state. This can result + * in incorect state transitions - e.g., the connection goes back to + * Established and then the Clearing_Completed event is generated. Also, + * block incoming connection in Deleted state. + */ + if (connection1->status == Clearing || connection1->status == Deleted) { + if (bgp_debug_neighbor_events(peer1)) + zlog_debug("[Event] Closing incoming conn for %s (%p) state %d", + peer1->host, peer1, + peer1->connection->status); + close(bgp_sock); + return; + } + + /* Check that at least one AF is activated for the peer. */ + if (!peer_active(peer1)) { + if (bgp_debug_neighbor_events(peer1)) + zlog_debug( + "%s - incoming conn rejected - no AF activated for peer", + peer1->host); + close(bgp_sock); + return; + } + + /* Do not try to reconnect if the peer reached maximum + * prefixes, restart timer is still running or the peer + * is shutdown. + */ + if (BGP_PEER_START_SUPPRESSED(peer1)) { + if (bgp_debug_neighbor_events(peer1)) { + if (peer1->shut_during_cfg) + zlog_debug( + "[Event] Incoming BGP connection rejected from %s due to configuration being currently read in", + peer1->host); + else + zlog_debug( + "[Event] Incoming BGP connection rejected from %s due to maximum-prefix or shutdown", + peer1->host); + } + close(bgp_sock); + return; + } + + if (bgp_debug_neighbor_events(peer1)) + zlog_debug("[Event] connection from %s fd %d, active peer status %d fd %d", + inet_sutop(&su, buf), bgp_sock, connection1->status, + connection1->fd); + + if (peer1->doppelganger) { + /* We have an existing connection. Kill the existing one and run + with this one. + */ + if (bgp_debug_neighbor_events(peer1)) + zlog_debug( + "[Event] New active connection from peer %s, Killing previous active connection", + peer1->host); + peer_delete(peer1->doppelganger); + } + + peer = peer_create(&su, peer1->conf_if, peer1->bgp, peer1->local_as, + peer1->as, peer1->as_type, NULL, false, NULL); + + connection = peer->connection; + + peer_xfer_config(peer, peer1); + bgp_peer_gr_flags_update(peer); + + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(peer->bgp, + peer->bgp->peer); + + if (bgp_peer_gr_mode_get(peer) == PEER_DISABLE) { + + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) { + peer_nsf_stop(peer); + } + } + + peer->doppelganger = peer1; + peer1->doppelganger = peer; + connection->fd = bgp_sock; + + if (bgp_set_socket_ttl(connection) < 0) + if (bgp_debug_neighbor_events(peer)) + zlog_debug("[Event] Unable to set min/max TTL on peer %s, Continuing", + peer->host); + + frr_with_privs(&bgpd_privs) { + vrf_bind(peer->bgp->vrf_id, bgp_sock, + bgp_get_bound_name(peer->connection)); + } + bgp_peer_reg_with_nht(peer); + bgp_fsm_change_status(connection, Active); + EVENT_OFF(connection->t_start); /* created in peer_create() */ + + SET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); + /* Make dummy peer until read Open packet. */ + if (peer_established(connection1) && + CHECK_FLAG(peer1->sflags, PEER_STATUS_NSF_MODE)) { + /* If we have an existing established connection with graceful + * restart + * capability announced with one or more address families, then + * drop + * existing established connection and move state to connect. + */ + peer1->last_reset = PEER_DOWN_NSF_CLOSE_SESSION; + + if (CHECK_FLAG(peer1->flags, PEER_FLAG_GRACEFUL_RESTART) + || CHECK_FLAG(peer1->flags, + PEER_FLAG_GRACEFUL_RESTART_HELPER)) + SET_FLAG(peer1->sflags, PEER_STATUS_NSF_WAIT); + + bgp_event_update(connection1, TCP_connection_closed); + } + + if (peer_active(peer)) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER_DELAYOPEN)) + BGP_EVENT_ADD(connection, TCP_connection_open_w_delay); + else + BGP_EVENT_ADD(connection, TCP_connection_open); + } + + /* + * If we are doing nht for a peer that is v6 LL based + * massage the event system to make things happy + */ + bgp_nht_interface_events(peer); +} + +/* BGP socket bind. */ +static char *bgp_get_bound_name(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + if ((peer->bgp->vrf_id == VRF_DEFAULT) && !peer->ifname + && !peer->conf_if) + return NULL; + + if (connection->su.sa.sa_family != AF_INET && + connection->su.sa.sa_family != AF_INET6) + return NULL; // unexpected + + /* For IPv6 peering, interface (unnumbered or link-local with interface) + * takes precedence over VRF. For IPv4 peering, explicit interface or + * VRF are the situations to bind. + */ + if (connection->su.sa.sa_family == AF_INET6 && peer->conf_if) + return peer->conf_if; + + if (peer->ifname) + return peer->ifname; + + if (peer->bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + return NULL; + + return peer->bgp->name; +} + +int bgp_update_address(struct interface *ifp, const union sockunion *dst, + union sockunion *addr) +{ + struct prefix *p, *sel, d; + struct connected *connected; + int common; + + if (!sockunion2hostprefix(dst, &d)) + return 1; + + sel = NULL; + common = -1; + + frr_each (if_connected, ifp->connected, connected) { + p = connected->address; + if (p->family != d.family) + continue; + if (prefix_common_bits(p, &d) > common) { + sel = p; + common = prefix_common_bits(sel, &d); + } + } + + if (!sel) + return 1; + + prefix2sockunion(sel, addr); + return 0; +} + +/* Update source selection. */ +static int bgp_update_source(struct peer_connection *connection) +{ + struct interface *ifp; + union sockunion addr; + int ret = 0; + struct peer *peer = connection->peer; + + sockunion_init(&addr); + + /* Source is specified with interface name. */ + if (peer->update_if) { + ifp = if_lookup_by_name(peer->update_if, peer->bgp->vrf_id); + if (!ifp) + return -1; + + if (bgp_update_address(ifp, &connection->su, &addr)) + return -1; + + ret = sockunion_bind(connection->fd, &addr, 0, &addr); + } + + /* Source is specified with IP address. */ + if (peer->update_source) + ret = sockunion_bind(connection->fd, peer->update_source, 0, + peer->update_source); + + return ret; +} + +/* BGP try to connect to the peer. */ +int bgp_connect(struct peer_connection *connection) +{ + struct peer *peer = connection->peer; + + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_WRITES_ON)); + assert(!CHECK_FLAG(connection->thread_flags, PEER_THREAD_READS_ON)); + ifindex_t ifindex = 0; + + if (peer->conf_if && BGP_CONNECTION_SU_UNSPEC(connection)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("Peer address not learnt: Returning from connect"); + return 0; + } + frr_with_privs(&bgpd_privs) { + /* Make socket for the peer. */ + connection->fd = + vrf_sockunion_socket(&connection->su, peer->bgp->vrf_id, + bgp_get_bound_name(connection)); + } + if (connection->fd < 0) { + peer->last_reset = PEER_DOWN_SOCKET_ERROR; + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: Failure to create socket for connection to %s, error received: %s(%d)", + __func__, peer->host, safe_strerror(errno), + errno); + return -1; + } + + set_nonblocking(connection->fd); + + /* Set the user configured MSS to TCP socket */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_TCP_MSS)) + sockopt_tcp_mss_set(connection->fd, peer->tcp_mss); + + bgp_socket_set_buffer_size(connection->fd); + + /* Set TCP keepalive when TCP keepalive is enabled */ + bgp_update_setsockopt_tcp_keepalive(peer->bgp, connection->fd); + + if (bgp_set_socket_ttl(peer->connection) < 0) { + peer->last_reset = PEER_DOWN_SOCKET_ERROR; + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: Failure to set socket ttl for connection to %s, error received: %s(%d)", + __func__, peer->host, safe_strerror(errno), + errno); + + return -1; + } + + sockopt_reuseaddr(connection->fd); + sockopt_reuseport(connection->fd); + +#ifdef IPTOS_PREC_INTERNETCONTROL + frr_with_privs(&bgpd_privs) { + if (sockunion_family(&connection->su) == AF_INET) + setsockopt_ipv4_tos(connection->fd, bm->ip_tos); + else if (sockunion_family(&connection->su) == AF_INET6) + setsockopt_ipv6_tclass(connection->fd, bm->ip_tos); + } +#endif + + if (peer->password) { + uint16_t prefixlen = peer->connection->su.sa.sa_family == AF_INET + ? IPV4_MAX_BITLEN + : IPV6_MAX_BITLEN; + + if (!BGP_CONNECTION_SU_UNSPEC(connection)) + bgp_md5_set(connection); + + bgp_md5_set_connect(connection->fd, &connection->su, prefixlen, + peer->password); + } + + /* Update source bind. */ + if (bgp_update_source(connection) < 0) { + peer->last_reset = PEER_DOWN_SOCKET_ERROR; + return connect_error; + } + + /* If the peer is passive mode, force to move to Active mode. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSIVE)) { + BGP_EVENT_ADD(connection, TCP_connection_open_failed); + return BGP_FSM_SUCCESS; + } + + if (peer->conf_if || peer->ifname) + ifindex = ifname2ifindex(peer->conf_if ? peer->conf_if + : peer->ifname, + peer->bgp->vrf_id); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [Event] Connect start to %s fd %d", peer->host, + peer->host, connection->fd); + + /* Connect to the remote peer. */ + return sockunion_connect(connection->fd, &connection->su, + htons(peer->port), ifindex); +} + +/* After TCP connection is established. Get local address and port. */ +int bgp_getsockname(struct peer *peer) +{ + if (peer->su_local) { + sockunion_free(peer->su_local); + peer->su_local = NULL; + } + + if (peer->su_remote) { + sockunion_free(peer->su_remote); + peer->su_remote = NULL; + } + + peer->su_local = sockunion_getsockname(peer->connection->fd); + peer->su_remote = sockunion_getpeername(peer->connection->fd); + + if (!bgp_zebra_nexthop_set(peer->su_local, peer->su_remote, + &peer->nexthop, peer)) { + flog_err( + EC_BGP_NH_UPD, + "%s: nexthop_set failed, local: %pSUp remote: %pSUp update_if: %s resetting connection - intf %s", + peer->host, peer->su_local, peer->su_remote, + peer->update_if ? peer->update_if : "(None)", + peer->nexthop.ifp ? peer->nexthop.ifp->name + : "(Unknown)"); + return -1; + } + return 0; +} + + +static int bgp_listener(int sock, struct sockaddr *sa, socklen_t salen, + struct bgp *bgp) +{ + struct bgp_listener *listener; + int ret, en; + + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + + frr_with_privs(&bgpd_privs) { + +#ifdef IPTOS_PREC_INTERNETCONTROL + if (sa->sa_family == AF_INET) + setsockopt_ipv4_tos(sock, bm->ip_tos); + else if (sa->sa_family == AF_INET6) + setsockopt_ipv6_tclass(sock, bm->ip_tos); +#endif + + sockopt_v6only(sa->sa_family, sock); + + ret = bind(sock, sa, salen); + en = errno; + } + + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "bind: %s", safe_strerror(en)); + return ret; + } + + ret = listen(sock, SOMAXCONN); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "listen: %s", safe_strerror(errno)); + return ret; + } + + listener = XCALLOC(MTYPE_BGP_LISTENER, sizeof(*listener)); + listener->fd = sock; + listener->name = XSTRDUP(MTYPE_BGP_LISTENER, bgp->name); + + /* this socket is in a vrf record bgp back pointer */ + if (bgp->vrf_id != VRF_DEFAULT) + listener->bgp = bgp; + + memcpy(&listener->su, sa, salen); + event_add_read(bm->master, bgp_accept, listener, sock, + &listener->thread); + listnode_add(bm->listen_sockets, listener); + + return 0; +} + +/* IPv6 supported version of BGP server socket setup. */ +int bgp_socket(struct bgp *bgp, unsigned short port, const char *address) +{ + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + static const struct addrinfo req = { + .ai_family = AF_UNSPEC, + .ai_flags = AI_PASSIVE, + .ai_socktype = SOCK_STREAM, + }; + int ret, count; + char port_str[BUFSIZ]; + + snprintf(port_str, sizeof(port_str), "%d", port); + port_str[sizeof(port_str) - 1] = '\0'; + + frr_with_privs(&bgpd_privs) { + ret = vrf_getaddrinfo(address, port_str, &req, &ainfo_save, + bgp->vrf_id); + } + if (ret != 0) { + flog_err_sys(EC_LIB_SOCKET, "getaddrinfo: %s", + gai_strerror(ret)); + return -1; + } + if (bgp_option_check(BGP_OPT_NO_ZEBRA) && + bgp->vrf_id != VRF_DEFAULT) { + freeaddrinfo(ainfo_save); + return -1; + } + count = 0; + for (ainfo = ainfo_save; ainfo; ainfo = ainfo->ai_next) { + int sock; + + if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) + continue; + + frr_with_privs(&bgpd_privs) { + sock = vrf_socket(ainfo->ai_family, + ainfo->ai_socktype, + ainfo->ai_protocol, + bgp->vrf_id, + (bgp->inst_type + == BGP_INSTANCE_TYPE_VRF + ? bgp->name : NULL)); + } + if (sock < 0) { + flog_err_sys(EC_LIB_SOCKET, "socket: %s", + safe_strerror(errno)); + continue; + } + + /* if we intend to implement ttl-security, this socket needs + * ttl=255 */ + sockopt_ttl(ainfo->ai_family, sock, MAXTTL); + + ret = bgp_listener(sock, ainfo->ai_addr, ainfo->ai_addrlen, + bgp); + if (ret == 0) + ++count; + else + close(sock); + } + freeaddrinfo(ainfo_save); + if (count == 0 && bgp->inst_type != BGP_INSTANCE_TYPE_VRF) { + flog_err( + EC_LIB_SOCKET, + "%s: no usable addresses please check other programs usage of specified port %d", + __func__, port); + flog_err_sys(EC_LIB_SOCKET, "%s: Program cannot continue", + __func__); + exit(-1); + } + + return 0; +} + +/* this function closes vrf socket + * this should be called only for vrf socket with netns backend + */ +void bgp_close_vrf_socket(struct bgp *bgp) +{ + struct listnode *node, *next; + struct bgp_listener *listener; + + if (!bgp) + return; + + if (bm->listen_sockets == NULL) + return; + + for (ALL_LIST_ELEMENTS(bm->listen_sockets, node, next, listener)) { + if (listener->bgp == bgp) { + EVENT_OFF(listener->thread); + close(listener->fd); + listnode_delete(bm->listen_sockets, listener); + XFREE(MTYPE_BGP_LISTENER, listener->name); + XFREE(MTYPE_BGP_LISTENER, listener); + } + } +} + +/* this function closes main socket + */ +void bgp_close(void) +{ + struct listnode *node, *next; + struct bgp_listener *listener; + + if (bm->listen_sockets == NULL) + return; + + for (ALL_LIST_ELEMENTS(bm->listen_sockets, node, next, listener)) { + if (listener->bgp) + continue; + EVENT_OFF(listener->thread); + close(listener->fd); + listnode_delete(bm->listen_sockets, listener); + XFREE(MTYPE_BGP_LISTENER, listener->name); + XFREE(MTYPE_BGP_LISTENER, listener); + } +} diff --git a/bgpd/bgp_network.h b/bgpd/bgp_network.h new file mode 100644 index 0000000..7a0b3cc --- /dev/null +++ b/bgpd/bgp_network.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP network related header + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_NETWORK_H +#define _QUAGGA_BGP_NETWORK_H + +#define BGP_SOCKET_SNDBUF_SIZE 65536 + +struct bgp_listener { + int fd; + union sockunion su; + struct event *thread; + struct bgp *bgp; + char *name; +}; + +extern void bgp_dump_listener_info(struct vty *vty); +extern int bgp_socket(struct bgp *bgp, unsigned short port, + const char *address); +extern void bgp_close_vrf_socket(struct bgp *bgp); +extern void bgp_close(void); +extern int bgp_connect(struct peer_connection *connection); +extern int bgp_getsockname(struct peer *peer); + +extern int bgp_md5_set_prefix(struct bgp *bgp, struct prefix *p, + const char *password); +extern int bgp_md5_unset_prefix(struct bgp *bgp, struct prefix *p); +extern int bgp_md5_set(struct peer_connection *connection); +extern int bgp_md5_unset(struct peer_connection *connection); +extern int bgp_set_socket_ttl(struct peer_connection *connection); +extern int bgp_tcp_mss_set(struct peer *peer); +extern int bgp_update_address(struct interface *ifp, const union sockunion *dst, + union sockunion *addr); + +#endif /* _QUAGGA_BGP_NETWORK_H */ diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c new file mode 100644 index 0000000..98eb956 --- /dev/null +++ b/bgpd/bgp_nexthop.c @@ -0,0 +1,1394 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP nexthop scan + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "frrevent.h" +#include "prefix.h" +#include "lib/json.h" +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "log.h" +#include "memory.h" +#include "hash.h" +#include "jhash.h" +#include "nexthop.h" +#include "queue.h" +#include "filter.h" +#include "printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_mplsvpn.h" + +DEFINE_MTYPE_STATIC(BGPD, MARTIAN_STRING, "BGP Martian Addr Intf String"); + +int bgp_nexthop_cache_compare(const struct bgp_nexthop_cache *a, + const struct bgp_nexthop_cache *b) +{ + if (a->srte_color < b->srte_color) + return -1; + if (a->srte_color > b->srte_color) + return 1; + + if (a->ifindex_ipv6_ll < b->ifindex_ipv6_ll) + return -1; + if (a->ifindex_ipv6_ll > b->ifindex_ipv6_ll) + return 1; + + return prefix_cmp(&a->prefix, &b->prefix); +} + +void bnc_nexthop_free(struct bgp_nexthop_cache *bnc) +{ + nexthops_free(bnc->nexthop); +} + +struct bgp_nexthop_cache *bnc_new(struct bgp_nexthop_cache_head *tree, + struct prefix *prefix, uint32_t srte_color, + ifindex_t ifindex) +{ + struct bgp_nexthop_cache *bnc; + + bnc = XCALLOC(MTYPE_BGP_NEXTHOP_CACHE, + sizeof(struct bgp_nexthop_cache)); + bnc->prefix = *prefix; + bnc->ifindex_ipv6_ll = ifindex; + bnc->srte_color = srte_color; + bnc->tree = tree; + LIST_INIT(&(bnc->paths)); + bgp_nexthop_cache_add(tree, bnc); + + return bnc; +} + +bool bnc_existing_for_prefix(struct bgp_nexthop_cache *bnc) +{ + struct bgp_nexthop_cache *bnc_tmp; + + frr_each (bgp_nexthop_cache, bnc->tree, bnc_tmp) { + if (bnc_tmp == bnc) + continue; + if (prefix_cmp(&bnc->prefix, &bnc_tmp->prefix) == 0) + return true; + } + return false; +} + +void bnc_free(struct bgp_nexthop_cache *bnc) +{ + bnc_nexthop_free(bnc); + bgp_nexthop_cache_del(bnc->tree, bnc); + XFREE(MTYPE_BGP_NEXTHOP_CACHE, bnc); +} + +struct bgp_nexthop_cache *bnc_find(struct bgp_nexthop_cache_head *tree, + struct prefix *prefix, uint32_t srte_color, + ifindex_t ifindex) +{ + struct bgp_nexthop_cache bnc = {}; + + if (!tree) + return NULL; + + bnc.prefix = *prefix; + bnc.srte_color = srte_color; + bnc.ifindex_ipv6_ll = ifindex; + return bgp_nexthop_cache_find(tree, &bnc); +} + +/* Reset and free all BGP nexthop cache. */ +static void bgp_nexthop_cache_reset(struct bgp_nexthop_cache_head *tree) +{ + struct bgp_nexthop_cache *bnc; + + while (bgp_nexthop_cache_count(tree) > 0) { + bnc = bgp_nexthop_cache_first(tree); + + while (!LIST_EMPTY(&(bnc->paths))) { + struct bgp_path_info *path = LIST_FIRST(&(bnc->paths)); + + bgp_mplsvpn_path_nh_label_unlink(path); + bgp_mplsvpn_path_nh_label_bind_unlink(path); + + path_nh_map(path, bnc, false); + } + + bnc_free(bnc); + } +} + +static void *bgp_tip_hash_alloc(void *p) +{ + const struct in_addr *val = (const struct in_addr *)p; + struct tip_addr *addr; + + addr = XMALLOC(MTYPE_TIP_ADDR, sizeof(struct tip_addr)); + addr->refcnt = 0; + addr->addr.s_addr = val->s_addr; + + return addr; +} + +static void bgp_tip_hash_free(void *addr) +{ + XFREE(MTYPE_TIP_ADDR, addr); +} + +static unsigned int bgp_tip_hash_key_make(const void *p) +{ + const struct tip_addr *addr = p; + + return jhash_1word(addr->addr.s_addr, 0); +} + +static bool bgp_tip_hash_cmp(const void *p1, const void *p2) +{ + const struct tip_addr *addr1 = p1; + const struct tip_addr *addr2 = p2; + + return addr1->addr.s_addr == addr2->addr.s_addr; +} + +void bgp_tip_hash_init(struct bgp *bgp) +{ + bgp->tip_hash = hash_create(bgp_tip_hash_key_make, bgp_tip_hash_cmp, + "BGP TIP hash"); +} + +void bgp_tip_hash_destroy(struct bgp *bgp) +{ + hash_clean_and_free(&bgp->tip_hash, bgp_tip_hash_free); +} + +/* Add/Update Tunnel-IP entry of bgp martian next-hop table. + * + * Returns true only if we add a _new_ TIP so the caller knows that an + * actionable change has occurred. If we find an existing TIP then we + * only need to update the refcnt, since the collection of known TIPs + * has not changed. + */ +bool bgp_tip_add(struct bgp *bgp, struct in_addr *tip) +{ + struct tip_addr tmp; + struct tip_addr *addr; + bool tip_added = false; + + tmp.addr = *tip; + + addr = hash_lookup(bgp->tip_hash, &tmp); + if (!addr) { + addr = hash_get(bgp->tip_hash, &tmp, bgp_tip_hash_alloc); + tip_added = true; + } + + addr->refcnt++; + + return tip_added; +} + +void bgp_tip_del(struct bgp *bgp, struct in_addr *tip) +{ + struct tip_addr tmp; + struct tip_addr *addr; + + tmp.addr = *tip; + + addr = hash_lookup(bgp->tip_hash, &tmp); + /* may have been deleted earlier by bgp_interface_down() */ + if (addr == NULL) + return; + + addr->refcnt--; + + if (addr->refcnt == 0) { + hash_release(bgp->tip_hash, addr); + XFREE(MTYPE_TIP_ADDR, addr); + } +} + +/* BGP own address structure */ +struct bgp_addr { + struct prefix p; + struct list *ifp_name_list; +}; + +static void show_address_entry(struct hash_bucket *bucket, void *args) +{ + struct vty *vty = (struct vty *)args; + struct bgp_addr *addr = (struct bgp_addr *)bucket->data; + char *name; + struct listnode *node; + char str[INET6_ADDRSTRLEN] = {0}; + + vty_out(vty, "addr: %s, count: %d : ", + inet_ntop(addr->p.family, &(addr->p.u.prefix), + str, INET6_ADDRSTRLEN), + addr->ifp_name_list->count); + + for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { + vty_out(vty, " %s,", name); + } + + vty_out(vty, "\n"); +} + +void bgp_nexthop_show_address_hash(struct vty *vty, struct bgp *bgp) +{ + hash_iterate(bgp->address_hash, + (void (*)(struct hash_bucket *, void *))show_address_entry, + vty); +} + +static void bgp_address_hash_string_del(void *val) +{ + char *data = val; + + XFREE(MTYPE_MARTIAN_STRING, data); +} + +static void *bgp_address_hash_alloc(void *p) +{ + struct bgp_addr *copy_addr = p; + struct bgp_addr *addr = NULL; + + addr = XMALLOC(MTYPE_BGP_ADDR, sizeof(struct bgp_addr)); + prefix_copy(&addr->p, ©_addr->p); + + addr->ifp_name_list = list_new(); + addr->ifp_name_list->del = bgp_address_hash_string_del; + + return addr; +} + +static void bgp_address_hash_free(void *data) +{ + struct bgp_addr *addr = data; + + list_delete(&addr->ifp_name_list); + XFREE(MTYPE_BGP_ADDR, addr); +} + +static unsigned int bgp_address_hash_key_make(const void *p) +{ + const struct bgp_addr *addr = p; + + return prefix_hash_key(&addr->p); +} + +static bool bgp_address_hash_cmp(const void *p1, const void *p2) +{ + const struct bgp_addr *addr1 = p1; + const struct bgp_addr *addr2 = p2; + + return prefix_same(&addr1->p, &addr2->p); +} + +void bgp_address_init(struct bgp *bgp) +{ + bgp->address_hash = + hash_create(bgp_address_hash_key_make, bgp_address_hash_cmp, + "BGP Connected Address Hash"); +} + +void bgp_address_destroy(struct bgp *bgp) +{ + hash_clean_and_free(&bgp->address_hash, bgp_address_hash_free); +} + +static void bgp_address_add(struct bgp *bgp, struct connected *ifc, + struct prefix *p) +{ + struct bgp_addr tmp; + struct bgp_addr *addr; + struct listnode *node; + char *name; + + tmp.p = *p; + + if (tmp.p.family == AF_INET) + tmp.p.prefixlen = IPV4_MAX_BITLEN; + else if (tmp.p.family == AF_INET6) + tmp.p.prefixlen = IPV6_MAX_BITLEN; + + addr = hash_get(bgp->address_hash, &tmp, bgp_address_hash_alloc); + + for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { + if (strcmp(ifc->ifp->name, name) == 0) + break; + } + if (!node) { + name = XSTRDUP(MTYPE_MARTIAN_STRING, ifc->ifp->name); + listnode_add(addr->ifp_name_list, name); + } +} + +static void bgp_address_del(struct bgp *bgp, struct connected *ifc, + struct prefix *p) +{ + struct bgp_addr tmp; + struct bgp_addr *addr; + struct listnode *node; + char *name; + + tmp.p = *p; + + if (tmp.p.family == AF_INET) + tmp.p.prefixlen = IPV4_MAX_BITLEN; + else if (tmp.p.family == AF_INET6) + tmp.p.prefixlen = IPV6_MAX_BITLEN; + + addr = hash_lookup(bgp->address_hash, &tmp); + /* may have been deleted earlier by bgp_interface_down() */ + if (addr == NULL) + return; + + for (ALL_LIST_ELEMENTS_RO(addr->ifp_name_list, node, name)) { + if (strcmp(ifc->ifp->name, name) == 0) + break; + } + + if (node) { + list_delete_node(addr->ifp_name_list, node); + XFREE(MTYPE_MARTIAN_STRING, name); + } + + if (addr->ifp_name_list->count == 0) { + hash_release(bgp->address_hash, addr); + list_delete(&addr->ifp_name_list); + XFREE(MTYPE_BGP_ADDR, addr); + } +} + + +struct bgp_connected_ref { + unsigned int refcnt; +}; + +void bgp_connected_add(struct bgp *bgp, struct connected *ifc) +{ + struct prefix p; + struct prefix *addr; + struct bgp_dest *dest; + struct bgp_connected_ref *bc; + struct listnode *node, *nnode; + struct peer *peer; + struct peer_connection *connection; + + addr = ifc->address; + + p = *(CONNECTED_PREFIX(ifc)); + if (addr->family == AF_INET) { + apply_mask_ipv4((struct prefix_ipv4 *)&p); + + if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) + return; + + bgp_address_add(bgp, ifc, addr); + + dest = bgp_node_get(bgp->connected_table[AFI_IP], &p); + bc = bgp_dest_get_bgp_connected_ref_info(dest); + if (bc) + bc->refcnt++; + else { + bc = XCALLOC(MTYPE_BGP_CONN, + sizeof(struct bgp_connected_ref)); + bc->refcnt = 1; + bgp_dest_set_bgp_connected_ref_info(dest, bc); + } + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->conf_if && + (strcmp(peer->conf_if, ifc->ifp->name) == 0) && + !peer_established(peer->connection) && + !CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) { + connection = peer->connection; + if (peer_active(peer)) + BGP_EVENT_ADD(connection, BGP_Stop); + BGP_EVENT_ADD(connection, BGP_Start); + } + } + } else if (addr->family == AF_INET6) { + apply_mask_ipv6((struct prefix_ipv6 *)&p); + + if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) + return; + + if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) + return; + + bgp_address_add(bgp, ifc, addr); + + dest = bgp_node_get(bgp->connected_table[AFI_IP6], &p); + + bc = bgp_dest_get_bgp_connected_ref_info(dest); + if (bc) + bc->refcnt++; + else { + bc = XCALLOC(MTYPE_BGP_CONN, + sizeof(struct bgp_connected_ref)); + bc->refcnt = 1; + bgp_dest_set_bgp_connected_ref_info(dest, bc); + } + } +} + +void bgp_connected_delete(struct bgp *bgp, struct connected *ifc) +{ + struct prefix p; + struct prefix *addr; + struct bgp_dest *dest = NULL; + struct bgp_connected_ref *bc; + + addr = ifc->address; + + p = *(CONNECTED_PREFIX(ifc)); + apply_mask(&p); + if (addr->family == AF_INET) { + if (prefix_ipv4_any((struct prefix_ipv4 *)&p)) + return; + + bgp_address_del(bgp, ifc, addr); + + dest = bgp_node_lookup(bgp->connected_table[AFI_IP], &p); + } else if (addr->family == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&p.u.prefix6)) + return; + + if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) + return; + + bgp_address_del(bgp, ifc, addr); + + dest = bgp_node_lookup(bgp->connected_table[AFI_IP6], &p); + } + + if (!dest) + return; + + bc = bgp_dest_get_bgp_connected_ref_info(dest); + bc->refcnt--; + if (bc->refcnt == 0) { + XFREE(MTYPE_BGP_CONN, bc); + bgp_dest_set_bgp_connected_ref_info(dest, NULL); + } + + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); +} + +static void bgp_connected_cleanup(struct route_table *table, + struct route_node *rn) +{ + struct bgp_connected_ref *bc; + struct bgp_dest *bn = bgp_dest_from_rnode(rn); + + bc = bgp_dest_get_bgp_connected_ref_info(bn); + if (!bc) + return; + + XFREE(MTYPE_BGP_CONN, bc); + bgp_dest_set_bgp_connected_ref_info(bn, NULL); +} + +bool bgp_nexthop_self(struct bgp *bgp, afi_t afi, uint8_t type, + uint8_t sub_type, struct attr *attr, + struct bgp_dest *dest) +{ + uint8_t new_afi = afi == AFI_IP ? AF_INET : AF_INET6; + struct bgp_addr tmp_addr = {{0}}, *addr = NULL; + struct tip_addr tmp_tip, *tip = NULL; + const struct prefix *p = bgp_dest_get_prefix(dest); + bool is_bgp_static_route = + ((type == ZEBRA_ROUTE_BGP) && (sub_type == BGP_ROUTE_STATIC)) + ? true + : false; + + if (!is_bgp_static_route) + new_afi = BGP_ATTR_NEXTHOP_AFI_IP6(attr) ? AF_INET6 : AF_INET; + + tmp_addr.p.family = new_afi; + switch (new_afi) { + case AF_INET: + if (is_bgp_static_route) { + tmp_addr.p.u.prefix4 = p->u.prefix4; + tmp_addr.p.prefixlen = p->prefixlen; + } else { + /* Here we need to find out which nexthop to be used*/ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { + tmp_addr.p.u.prefix4 = attr->nexthop; + tmp_addr.p.prefixlen = IPV4_MAX_BITLEN; + } else if ((attr->mp_nexthop_len) + && ((attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV4) + || (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_VPNV4))) { + tmp_addr.p.u.prefix4 = + attr->mp_nexthop_global_in; + tmp_addr.p.prefixlen = IPV4_MAX_BITLEN; + } else + return false; + } + break; + case AF_INET6: + if (is_bgp_static_route) { + tmp_addr.p.u.prefix6 = p->u.prefix6; + tmp_addr.p.prefixlen = p->prefixlen; + } else { + tmp_addr.p.u.prefix6 = attr->mp_nexthop_global; + tmp_addr.p.prefixlen = IPV6_MAX_BITLEN; + } + break; + default: + break; + } + + addr = hash_lookup(bgp->address_hash, &tmp_addr); + if (addr) + return true; + + if (new_afi == AF_INET && hashcount(bgp->tip_hash)) { + memset(&tmp_tip, 0, sizeof(tmp_tip)); + tmp_tip.addr = attr->nexthop; + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { + tmp_tip.addr = attr->nexthop; + } else if ((attr->mp_nexthop_len) && + ((attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4) + || (attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV4))) { + tmp_tip.addr = attr->mp_nexthop_global_in; + } + + tip = hash_lookup(bgp->tip_hash, &tmp_tip); + if (tip) + return true; + } + + return false; +} + +bool bgp_multiaccess_check_v4(struct in_addr nexthop, struct peer *peer) +{ + struct bgp_dest *dest1; + struct bgp_dest *dest2; + struct prefix p; + int ret; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = nexthop; + + dest1 = bgp_node_match(peer->bgp->connected_table[AFI_IP], &p); + if (!dest1) + return false; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = peer->connection->su.sin.sin_addr; + + dest2 = bgp_node_match(peer->bgp->connected_table[AFI_IP], &p); + if (!dest2) { + bgp_dest_unlock_node(dest1); + return false; + } + + ret = (dest1 == dest2); + + bgp_dest_unlock_node(dest1); + bgp_dest_unlock_node(dest2); + + return ret; +} + +bool bgp_multiaccess_check_v6(struct in6_addr nexthop, struct peer *peer) +{ + struct bgp_dest *dest1; + struct bgp_dest *dest2; + struct prefix p; + int ret; + + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_BITLEN; + p.u.prefix6 = nexthop; + + dest1 = bgp_node_match(peer->bgp->connected_table[AFI_IP6], &p); + if (!dest1) + return false; + + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_BITLEN; + p.u.prefix6 = peer->connection->su.sin6.sin6_addr; + + dest2 = bgp_node_match(peer->bgp->connected_table[AFI_IP6], &p); + if (!dest2) { + bgp_dest_unlock_node(dest1); + return false; + } + + ret = (dest1 == dest2); + + bgp_dest_unlock_node(dest1); + bgp_dest_unlock_node(dest2); + + return ret; +} + +bool bgp_subgrp_multiaccess_check_v6(struct in6_addr nexthop, + struct update_subgroup *subgrp, + struct peer *exclude) +{ + struct bgp_dest *dest1 = NULL, *dest2 = NULL; + struct peer_af *paf = NULL; + struct prefix p = {0}, np = {0}; + struct bgp *bgp = NULL; + + np.family = AF_INET6; + np.prefixlen = IPV6_MAX_BITLEN; + np.u.prefix6 = nexthop; + + p.family = AF_INET; + p.prefixlen = IPV6_MAX_BITLEN; + + bgp = SUBGRP_INST(subgrp); + dest1 = bgp_node_match(bgp->connected_table[AFI_IP6], &np); + if (!dest1) + return false; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + /* Skip peer we're told to exclude - e.g., source of route. */ + if (paf->peer == exclude) + continue; + + p.u.prefix6 = paf->peer->connection->su.sin6.sin6_addr; + dest2 = bgp_node_match(bgp->connected_table[AFI_IP6], &p); + if (dest1 == dest2) { + bgp_dest_unlock_node(dest1); + bgp_dest_unlock_node(dest2); + return true; + } + + if (dest2) + bgp_dest_unlock_node(dest2); + } + + bgp_dest_unlock_node(dest1); + return false; +} + +bool bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop, + struct update_subgroup *subgrp, + struct peer *exclude) +{ + struct bgp_dest *dest1, *dest2; + struct peer_af *paf; + struct prefix p, np; + struct bgp *bgp; + + np.family = AF_INET; + np.prefixlen = IPV4_MAX_BITLEN; + np.u.prefix4 = nexthop; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + + bgp = SUBGRP_INST(subgrp); + dest1 = bgp_node_match(bgp->connected_table[AFI_IP], &np); + if (!dest1) + return false; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + /* Skip peer we're told to exclude - e.g., source of route. */ + if (paf->peer == exclude) + continue; + + p.u.prefix4 = paf->peer->connection->su.sin.sin_addr; + + dest2 = bgp_node_match(bgp->connected_table[AFI_IP], &p); + if (dest1 == dest2) { + bgp_dest_unlock_node(dest1); + bgp_dest_unlock_node(dest2); + return true; + } + + if (dest2) + bgp_dest_unlock_node(dest2); + } + + bgp_dest_unlock_node(dest1); + return false; +} + +static void bgp_show_bgp_path_info_flags(uint32_t flags, json_object *json) +{ + json_object *json_flags = NULL; + + if (!json) + return; + + json_flags = json_object_new_object(); + json_object_boolean_add(json_flags, "igpChanged", + CHECK_FLAG(flags, BGP_PATH_IGP_CHANGED)); + json_object_boolean_add(json_flags, "damped", + CHECK_FLAG(flags, BGP_PATH_DAMPED)); + json_object_boolean_add(json_flags, "history", + CHECK_FLAG(flags, BGP_PATH_HISTORY)); + json_object_boolean_add(json_flags, "bestpath", + CHECK_FLAG(flags, BGP_PATH_SELECTED)); + json_object_boolean_add(json_flags, "valid", + CHECK_FLAG(flags, BGP_PATH_VALID)); + json_object_boolean_add(json_flags, "attrChanged", + CHECK_FLAG(flags, BGP_PATH_ATTR_CHANGED)); + json_object_boolean_add(json_flags, "deterministicMedCheck", + CHECK_FLAG(flags, BGP_PATH_DMED_CHECK)); + json_object_boolean_add(json_flags, "deterministicMedSelected", + CHECK_FLAG(flags, BGP_PATH_DMED_SELECTED)); + json_object_boolean_add(json_flags, "stale", + CHECK_FLAG(flags, BGP_PATH_STALE)); + json_object_boolean_add(json_flags, "removed", + CHECK_FLAG(flags, BGP_PATH_REMOVED)); + json_object_boolean_add(json_flags, "counted", + CHECK_FLAG(flags, BGP_PATH_COUNTED)); + json_object_boolean_add(json_flags, "multipath", + CHECK_FLAG(flags, BGP_PATH_MULTIPATH)); + json_object_boolean_add(json_flags, "multipathChanged", + CHECK_FLAG(flags, BGP_PATH_MULTIPATH_CHG)); + json_object_boolean_add(json_flags, "ribAttributeChanged", + CHECK_FLAG(flags, BGP_PATH_RIB_ATTR_CHG)); + json_object_boolean_add(json_flags, "nexthopSelf", + CHECK_FLAG(flags, BGP_PATH_ANNC_NH_SELF)); + json_object_boolean_add(json_flags, "linkBandwidthChanged", + CHECK_FLAG(flags, BGP_PATH_LINK_BW_CHG)); + json_object_boolean_add(json_flags, "acceptOwn", + CHECK_FLAG(flags, BGP_PATH_ACCEPT_OWN)); + json_object_object_add(json, "flags", json_flags); +} + +static void bgp_show_nexthop_paths(struct vty *vty, struct bgp *bgp, + struct bgp_nexthop_cache *bnc, + json_object *json) +{ + struct bgp_dest *dest; + struct bgp_path_info *path; + afi_t afi; + safi_t safi; + struct bgp_table *table; + struct bgp *bgp_path; + json_object *paths = NULL; + json_object *json_path = NULL; + + if (json) + paths = json_object_new_array(); + else + vty_out(vty, " Paths:\n"); + LIST_FOREACH (path, &(bnc->paths), nh_thread) { + dest = path->net; + assert(dest && bgp_dest_table(dest)); + afi = family2afi(bgp_dest_get_prefix(dest)->family); + table = bgp_dest_table(dest); + safi = table->safi; + bgp_path = table->bgp; + + + if (json) { + json_path = json_object_new_object(); + json_object_string_add(json_path, "afi", afi2str(afi)); + json_object_string_add(json_path, "safi", + safi2str(safi)); + json_object_string_addf(json_path, "prefix", "%pBD", + dest); + if (dest->pdest) + json_object_string_addf( + json_path, "rd", + BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest)); + json_object_string_add( + json_path, "vrf", + vrf_id_to_name(bgp_path->vrf_id)); + bgp_show_bgp_path_info_flags(path->flags, json_path); + json_object_array_add(paths, json_path); + continue; + } + if (dest->pdest) { + vty_out(vty, " %d/%d %pBD RD ", afi, safi, dest); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest)); + vty_out(vty, " %s flags 0x%x\n", bgp_path->name_pretty, + path->flags); + } else + vty_out(vty, " %d/%d %pBD %s flags 0x%x\n", + afi, safi, dest, bgp_path->name_pretty, path->flags); + } + if (json) + json_object_object_add(json, "paths", paths); +} + +static void bgp_show_nexthops_detail(struct vty *vty, struct bgp *bgp, + struct bgp_nexthop_cache *bnc, + json_object *json) +{ + struct nexthop *nexthop; + json_object *json_gates = NULL; + json_object *json_gate = NULL; + + if (json) + json_gates = json_object_new_array(); + for (nexthop = bnc->nexthop; nexthop; nexthop = nexthop->next) { + if (json) { + json_gate = json_object_new_object(); + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV6: + json_object_string_addf(json_gate, "ip", "%pI6", + &nexthop->gate.ipv6); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + json_object_string_addf(json_gate, "ip", "%pI6", + &nexthop->gate.ipv6); + json_object_string_add( + json_gate, "interfaceName", + ifindex2ifname( + bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bgp->vrf_id)); + break; + case NEXTHOP_TYPE_IPV4: + json_object_string_addf(json_gate, "ip", "%pI4", + &nexthop->gate.ipv4); + break; + case NEXTHOP_TYPE_IFINDEX: + json_object_string_add( + json_gate, "interfaceName", + ifindex2ifname( + bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bgp->vrf_id)); + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + json_object_string_addf(json_gate, "ip", "%pI4", + &nexthop->gate.ipv4); + json_object_string_add( + json_gate, "interfaceName", + ifindex2ifname( + bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bgp->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + json_object_boolean_true_add(json_gate, + "unreachable"); + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + json_object_boolean_true_add(json_gate, + "reject"); + break; + case BLACKHOLE_ADMINPROHIB: + json_object_boolean_true_add( + json_gate, "adminProhibited"); + break; + case BLACKHOLE_NULL: + json_object_boolean_true_add( + json_gate, "blackhole"); + break; + case BLACKHOLE_UNSPEC: + break; + } + break; + default: + break; + } + json_object_array_add(json_gates, json_gate); + continue; + } + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + vty_out(vty, " gate %pI6", &nexthop->gate.ipv6); + if (nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX && + bnc->ifindex_ipv6_ll) + vty_out(vty, ", if %s\n", + ifindex2ifname(bnc->ifindex_ipv6_ll, + bgp->vrf_id)); + else if (nexthop->ifindex) + vty_out(vty, ", if %s\n", + ifindex2ifname(nexthop->ifindex, + bgp->vrf_id)); + else + vty_out(vty, "\n"); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + vty_out(vty, " gate %pI4", &nexthop->gate.ipv4); + if (nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX && + bnc->ifindex_ipv6_ll) + vty_out(vty, ", if %s\n", + ifindex2ifname(bnc->ifindex_ipv6_ll, + bgp->vrf_id)); + else if (nexthop->ifindex) + vty_out(vty, ", if %s\n", + ifindex2ifname(nexthop->ifindex, + bgp->vrf_id)); + else + vty_out(vty, "\n"); + break; + case NEXTHOP_TYPE_IFINDEX: + vty_out(vty, " if %s\n", + ifindex2ifname(bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bgp->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + vty_out(vty, " blackhole\n"); + break; + default: + vty_out(vty, " invalid nexthop type %u\n", + nexthop->type); + } + } + if (json) + json_object_object_add(json, "nexthops", json_gates); +} + +static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp, + struct bgp_nexthop_cache *bnc, bool specific, + json_object *json) +{ + char buf[PREFIX2STR_BUFFER]; + time_t tbuf; + char timebuf[32]; + struct peer *peer; + json_object *json_last_update = NULL; + json_object *json_nexthop = NULL; + + peer = (struct peer *)bnc->nht_info; + + if (json) + json_nexthop = json_object_new_object(); + if (bnc->srte_color) { + if (json) + json_object_int_add(json_nexthop, "srteColor", + bnc->srte_color); + else + vty_out(vty, " SR-TE color %u -", bnc->srte_color); + } + inet_ntop(bnc->prefix.family, &bnc->prefix.u.prefix, buf, sizeof(buf)); + if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID)) { + if (json) { + json_object_boolean_true_add(json_nexthop, "valid"); + json_object_boolean_true_add(json_nexthop, "complete"); + json_object_int_add(json_nexthop, "igpMetric", + bnc->metric); + json_object_int_add(json_nexthop, "pathCount", + bnc->path_count); + if (peer) + json_object_string_add(json_nexthop, "peer", + peer->host); + if (bnc->is_evpn_gwip_nexthop) + json_object_boolean_true_add(json_nexthop, + "isEvpnGatewayIp"); + json_object_string_addf(json, "resolvedPrefix", "%pFX", + &bnc->resolved_prefix); + } else { + vty_out(vty, " %s valid [IGP metric %d], #paths %d", + buf, bnc->metric, bnc->path_count); + if (peer) + vty_out(vty, ", peer %s", peer->host); + if (bnc->is_evpn_gwip_nexthop) + vty_out(vty, " EVPN Gateway IP"); + vty_out(vty, "\n Resolved prefix %pFX", + &bnc->resolved_prefix); + vty_out(vty, "\n"); + } + bgp_show_nexthops_detail(vty, bgp, bnc, json_nexthop); + } else if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_EVPN_INCOMPLETE)) { + if (json) { + json_object_boolean_true_add(json_nexthop, "valid"); + json_object_boolean_false_add(json_nexthop, "complete"); + json_object_int_add(json_nexthop, "igpMetric", + bnc->metric); + json_object_int_add(json_nexthop, "pathCount", + bnc->path_count); + if (bnc->is_evpn_gwip_nexthop) + json_object_boolean_true_add(json_nexthop, + "isEvpnGatewayIp"); + } else { + vty_out(vty, + " %s overlay index unresolved [IGP metric %d], #paths %d", + buf, bnc->metric, bnc->path_count); + if (bnc->is_evpn_gwip_nexthop) + vty_out(vty, " EVPN Gateway IP"); + vty_out(vty, "\n"); + } + bgp_show_nexthops_detail(vty, bgp, bnc, json_nexthop); + } else { + if (json) { + json_object_boolean_false_add(json_nexthop, "valid"); + json_object_boolean_false_add(json_nexthop, "complete"); + json_object_int_add(json_nexthop, "pathCount", + bnc->path_count); + if (peer) + json_object_string_add(json_nexthop, "peer", + peer->host); + if (bnc->is_evpn_gwip_nexthop) + json_object_boolean_true_add(json_nexthop, + "isEvpnGatewayIp"); + if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) + json_object_boolean_false_add(json_nexthop, + "isConnected"); + if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED)) + json_object_boolean_false_add(json_nexthop, + "isRegistered"); + } else { + vty_out(vty, " %s invalid, #paths %d", buf, + bnc->path_count); + if (peer) + vty_out(vty, ", peer %s", peer->host); + if (bnc->is_evpn_gwip_nexthop) + vty_out(vty, " EVPN Gateway IP"); + vty_out(vty, "\n"); + if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) + vty_out(vty, " Must be Connected\n"); + if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED)) + vty_out(vty, " Is not Registered\n"); + } + } + tbuf = time(NULL) - (monotime(NULL) - bnc->last_update); + if (json) { + if (!specific) { + json_last_update = json_object_new_object(); + json_object_int_add(json_last_update, "epoch", tbuf); + json_object_string_add(json_last_update, "string", + ctime_r(&tbuf, timebuf)); + json_object_object_add(json_nexthop, "lastUpdate", + json_last_update); + } else { + json_object_int_add(json_nexthop, "lastUpdate", tbuf); + } + } else { + vty_out(vty, " Last update: %s", ctime_r(&tbuf, timebuf)); + } + + /* show paths dependent on nexthop, if needed. */ + if (specific) + bgp_show_nexthop_paths(vty, bgp, bnc, json_nexthop); + if (json) + json_object_object_add(json, buf, json_nexthop); +} + +static void bgp_show_nexthops(struct vty *vty, struct bgp *bgp, + bool import_table, json_object *json, afi_t afi, + bool detail) +{ + struct bgp_nexthop_cache *bnc; + struct bgp_nexthop_cache_head(*tree)[AFI_MAX]; + json_object *json_afi = NULL; + bool found = false; + + if (!json) { + if (import_table) + vty_out(vty, "Current BGP import check cache:\n"); + else + vty_out(vty, "Current BGP nexthop cache:\n"); + } + if (import_table) + tree = &bgp->import_check_table; + else + tree = &bgp->nexthop_cache_table; + + if (afi == AFI_IP || afi == AFI_IP6) { + if (json) + json_afi = json_object_new_object(); + frr_each (bgp_nexthop_cache, &(*tree)[afi], bnc) { + bgp_show_nexthop(vty, bgp, bnc, detail, json_afi); + found = true; + } + if (found && json) + json_object_object_add( + json, (afi == AFI_IP) ? "ipv4" : "ipv6", + json_afi); + return; + } + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + if (json && (afi == AFI_IP || afi == AFI_IP6)) + json_afi = json_object_new_object(); + frr_each (bgp_nexthop_cache, &(*tree)[afi], bnc) + bgp_show_nexthop(vty, bgp, bnc, detail, json_afi); + if (json && (afi == AFI_IP || afi == AFI_IP6)) + json_object_object_add( + json, (afi == AFI_IP) ? "ipv4" : "ipv6", + json_afi); + } +} + +static int show_ip_bgp_nexthop_table(struct vty *vty, const char *name, + const char *nhopip_str, bool import_table, + json_object *json, afi_t afi, bool detail) +{ + struct bgp *bgp; + + if (name && !strmatch(name, VRF_DEFAULT_NAME)) + bgp = bgp_lookup_by_name(name); + else + bgp = bgp_get_default(); + if (!bgp) { + if (!json) + vty_out(vty, "%% No such BGP instance exist\n"); + return CMD_WARNING; + } + + if (nhopip_str) { + struct prefix nhop; + struct bgp_nexthop_cache_head (*tree)[AFI_MAX]; + struct bgp_nexthop_cache *bnc; + bool found = false; + json_object *json_afi = NULL; + + if (!str2prefix(nhopip_str, &nhop)) { + if (!json) + vty_out(vty, "nexthop address is malformed\n"); + return CMD_WARNING; + } + tree = import_table ? &bgp->import_check_table + : &bgp->nexthop_cache_table; + if (json) + json_afi = json_object_new_object(); + frr_each (bgp_nexthop_cache, &(*tree)[family2afi(nhop.family)], + bnc) { + if (prefix_cmp(&bnc->prefix, &nhop)) + continue; + bgp_show_nexthop(vty, bgp, bnc, true, json_afi); + found = true; + } + if (json) + json_object_object_add( + json, + (family2afi(nhop.family) == AFI_IP) ? "ipv4" + : "ipv6", + json_afi); + if (!found && !json) + vty_out(vty, "nexthop %s does not have entry\n", + nhopip_str); + } else + bgp_show_nexthops(vty, bgp, import_table, json, afi, detail); + + return CMD_SUCCESS; +} + +static void bgp_show_all_instances_nexthops_vty(struct vty *vty, + json_object *json, afi_t afi, + bool detail) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + const char *inst_name; + json_object *json_instance = NULL; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + inst_name = (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name; + if (json) + json_instance = json_object_new_object(); + else + vty_out(vty, "\nInstance %s:\n", inst_name); + + bgp_show_nexthops(vty, bgp, false, json_instance, afi, detail); + + if (json) + json_object_object_add(json, inst_name, json_instance); + } +} + +#include "bgpd/bgp_nexthop_clippy.c" + +DEFPY (show_ip_bgp_nexthop, + show_ip_bgp_nexthop_cmd, + "show [ip] bgp [ VIEWVRFNAME$vrf] nexthop [$nhop] [] [detail$detail] [json$uj]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + "BGP nexthop table\n" + "IPv4 nexthop address\n" + "IPv6 nexthop address\n" + "BGP nexthop IPv4 table\n" + "IPv4 nexthop address\n" + "BGP nexthop IPv6 table\n" + "IPv6 nexthop address\n" + "Show detailed information\n" + JSON_STR) +{ + int rc = 0; + json_object *json = NULL; + afi_t afiz = AFI_UNSPEC; + + if (uj) + json = json_object_new_object(); + + if (afi) + afiz = bgp_vty_afi_from_str(afi); + + rc = show_ip_bgp_nexthop_table(vty, vrf, nhop_str, false, json, afiz, + detail); + + if (uj) + vty_json(vty, json); + + return rc; +} + +DEFPY (show_ip_bgp_import_check, + show_ip_bgp_import_check_cmd, + "show [ip] bgp [ VIEWVRFNAME$vrf] import-check-table [detail$detail] [json$uj]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + "BGP import check table\n" + "Show detailed information\n" + JSON_STR) +{ + int rc = 0; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + rc = show_ip_bgp_nexthop_table(vty, vrf, NULL, true, json, AFI_UNSPEC, + detail); + + if (uj) + vty_json(vty, json); + + return rc; +} + +DEFPY (show_ip_bgp_instance_all_nexthop, + show_ip_bgp_instance_all_nexthop_cmd, + "show [ip] bgp all nexthop [$afi] [detail$detail] [json$uj]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_ALL_HELP_STR + "BGP nexthop table\n" + "BGP IPv4 nexthop table\n" + "BGP IPv6 nexthop table\n" + "Show detailed information\n" + JSON_STR) +{ + json_object *json = NULL; + afi_t afiz = AFI_UNSPEC; + + if (uj) + json = json_object_new_object(); + + if (afi) + afiz = bgp_vty_afi_from_str(afi); + + bgp_show_all_instances_nexthops_vty(vty, json, afiz, detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +void bgp_scan_init(struct bgp *bgp) +{ + afi_t afi; + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + bgp_nexthop_cache_init(&bgp->nexthop_cache_table[afi]); + bgp_nexthop_cache_init(&bgp->import_check_table[afi]); + bgp->connected_table[afi] = bgp_table_init(bgp, afi, + SAFI_UNICAST); + } +} + +void bgp_scan_vty_init(void) +{ + install_element(VIEW_NODE, &show_ip_bgp_nexthop_cmd); + install_element(VIEW_NODE, &show_ip_bgp_import_check_cmd); + install_element(VIEW_NODE, &show_ip_bgp_instance_all_nexthop_cmd); +} + +void bgp_scan_finish(struct bgp *bgp) +{ + afi_t afi; + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + /* Only the current one needs to be reset. */ + bgp_nexthop_cache_reset(&bgp->nexthop_cache_table[afi]); + bgp_nexthop_cache_reset(&bgp->import_check_table[afi]); + + bgp->connected_table[afi]->route_table->cleanup = + bgp_connected_cleanup; + bgp_table_unlock(bgp->connected_table[afi]); + bgp->connected_table[afi] = NULL; + } +} + +char *bgp_nexthop_dump_bnc_flags(struct bgp_nexthop_cache *bnc, char *buf, + size_t len) +{ + if (bnc->flags == 0) { + snprintfrr(buf, len, "None "); + return buf; + } + + snprintfrr(buf, len, "%s%s%s%s%s%s%s", + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID) ? "Valid " : "", + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED) ? "Reg " : "", + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED) ? "Conn " : "", + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED) ? "Notify " + : "", + CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE) ? "Static " : "", + CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH) + ? "Static Exact " + : "", + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID) + ? "Label Valid " + : ""); + + return buf; +} + +char *bgp_nexthop_dump_bnc_change_flags(struct bgp_nexthop_cache *bnc, + char *buf, size_t len) +{ + if (bnc->flags == 0) { + snprintfrr(buf, len, "None "); + return buf; + } + + snprintfrr(buf, len, "%s%s%s", + CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED) + ? "Changed " + : "", + CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_METRIC_CHANGED) + ? "Metric " + : "", + CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_CONNECTED_CHANGED) + ? "Connected " + : ""); + + return buf; +} diff --git a/bgpd/bgp_nexthop.h b/bgpd/bgp_nexthop.h new file mode 100644 index 0000000..430c8f1 --- /dev/null +++ b/bgpd/bgp_nexthop.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP nexthop scan + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_NEXTHOP_H +#define _QUAGGA_BGP_NEXTHOP_H + +#include "if.h" +#include "queue.h" +#include "prefix.h" +#include "bgp_table.h" + +#define NEXTHOP_FAMILY(nexthop_len) \ + (((nexthop_len) == 4 || (nexthop_len) == 12 \ + ? AF_INET \ + : ((nexthop_len) == 16 || (nexthop_len) == 24 \ + || (nexthop_len) == 32 \ + || (nexthop_len) == 48 \ + ? AF_INET6 \ + : AF_UNSPEC))) + +#define BGP_MP_NEXTHOP_FAMILY NEXTHOP_FAMILY + +PREDECL_RBTREE_UNIQ(bgp_nexthop_cache); + +/* BGP nexthop cache value structure. */ +struct bgp_nexthop_cache { + afi_t afi; + + /* The ifindex of the outgoing interface *if* it's a v6 LL */ + ifindex_t ifindex_ipv6_ll; + + /* RB-tree entry. */ + struct bgp_nexthop_cache_item entry; + + /* IGP route's metric. */ + uint32_t metric; + + /* Nexthop number and nexthop linked list.*/ + uint8_t nexthop_num; + + /* This flag is set to TRUE for a bnc that is gateway IP overlay index + * nexthop. + */ + bool is_evpn_gwip_nexthop; + + uint16_t change_flags; +#define BGP_NEXTHOP_CHANGED (1 << 0) +#define BGP_NEXTHOP_METRIC_CHANGED (1 << 1) +#define BGP_NEXTHOP_CONNECTED_CHANGED (1 << 2) +#define BGP_NEXTHOP_MACIP_CHANGED (1 << 3) + + struct nexthop *nexthop; + time_t last_update; + uint16_t flags; + +/* + * If the nexthop is EVPN gateway IP NH, VALID flag is set only if the nexthop + * is RIB reachable as well as MAC/IP is present + */ +#define BGP_NEXTHOP_VALID (1 << 0) +#define BGP_NEXTHOP_REGISTERED (1 << 1) +#define BGP_NEXTHOP_CONNECTED (1 << 2) +#define BGP_NEXTHOP_PEER_NOTIFIED (1 << 3) +#define BGP_STATIC_ROUTE (1 << 4) +#define BGP_STATIC_ROUTE_EXACT_MATCH (1 << 5) +#define BGP_NEXTHOP_LABELED_VALID (1 << 6) + +/* + * This flag is added for EVPN gateway IP nexthops. + * If the nexthop is RIB reachable, but a MAC/IP is not yet + * resolved, this flag is set. + * Following table explains the combination of L3 and L2 reachability w.r.t. + * VALID and INCOMPLETE flags + * + * | MACIP resolved | MACIP unresolved + *----------------|----------------|------------------ + * L3 reachable | VALID = 1 | VALID = 0 + * | INCOMPLETE = 0 | INCOMPLETE = 1 + * ---------------|----------------|-------------------- + * L3 unreachable | VALID = 0 | VALID = 0 + * | INCOMPLETE = 0 | INCOMPLETE = 0 + */ +#define BGP_NEXTHOP_EVPN_INCOMPLETE (1 << 7) + + uint32_t srte_color; + + /* Back pointer to the cache tree this entry belongs to. */ + struct bgp_nexthop_cache_head *tree; + + struct prefix prefix; + struct prefix resolved_prefix; + void *nht_info; /* In BGP, peer session */ + LIST_HEAD(path_list, bgp_path_info) paths; + unsigned int path_count; + struct bgp *bgp; +}; + +extern int bgp_nexthop_cache_compare(const struct bgp_nexthop_cache *a, + const struct bgp_nexthop_cache *b); +DECLARE_RBTREE_UNIQ(bgp_nexthop_cache, struct bgp_nexthop_cache, entry, + bgp_nexthop_cache_compare); + +/* Own tunnel-ip address structure */ +struct tip_addr { + struct in_addr addr; + int refcnt; +}; + +/* Forward declaration(s). */ +struct peer; +struct update_subgroup; +struct bgp_dest; +struct attr; + +#define BNC_FLAG_DUMP_SIZE 180 +extern char *bgp_nexthop_dump_bnc_flags(struct bgp_nexthop_cache *bnc, + char *buf, size_t len); +extern char *bgp_nexthop_dump_bnc_change_flags(struct bgp_nexthop_cache *bnc, + char *buf, size_t len); +extern void bgp_connected_add(struct bgp *bgp, struct connected *c); +extern void bgp_connected_delete(struct bgp *bgp, struct connected *c); +extern bool bgp_subgrp_multiaccess_check_v4(struct in_addr nexthop, + struct update_subgroup *subgrp, + struct peer *exclude); +extern bool bgp_subgrp_multiaccess_check_v6(struct in6_addr nexthop, + struct update_subgroup *subgrp, + struct peer *exclude); +extern bool bgp_multiaccess_check_v4(struct in_addr nexthop, struct peer *peer); +extern bool bgp_multiaccess_check_v6(struct in6_addr nexthop, + struct peer *peer); +extern int bgp_config_write_scan_time(struct vty *); +extern bool bgp_nexthop_self(struct bgp *bgp, afi_t afi, uint8_t type, + uint8_t sub_type, struct attr *attr, + struct bgp_dest *dest); +extern struct bgp_nexthop_cache *bnc_new(struct bgp_nexthop_cache_head *tree, + struct prefix *prefix, + uint32_t srte_color, + ifindex_t ifindex); +extern bool bnc_existing_for_prefix(struct bgp_nexthop_cache *bnc); +extern void bnc_free(struct bgp_nexthop_cache *bnc); +extern struct bgp_nexthop_cache *bnc_find(struct bgp_nexthop_cache_head *tree, + struct prefix *prefix, + uint32_t srte_color, + ifindex_t ifindex); +extern void bnc_nexthop_free(struct bgp_nexthop_cache *bnc); +extern void bgp_scan_init(struct bgp *bgp); +extern void bgp_scan_finish(struct bgp *bgp); +extern void bgp_scan_vty_init(void); +extern void bgp_address_init(struct bgp *bgp); +extern void bgp_address_destroy(struct bgp *bgp); +extern bool bgp_tip_add(struct bgp *bgp, struct in_addr *tip); +extern void bgp_tip_del(struct bgp *bgp, struct in_addr *tip); +extern void bgp_tip_hash_init(struct bgp *bgp); +extern void bgp_tip_hash_destroy(struct bgp *bgp); + +extern void bgp_nexthop_show_address_hash(struct vty *vty, struct bgp *bgp); +#endif /* _QUAGGA_BGP_NEXTHOP_H */ diff --git a/bgpd/bgp_nhg.c b/bgpd/bgp_nhg.c new file mode 100644 index 0000000..bf0ba77 --- /dev/null +++ b/bgpd/bgp_nhg.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Nexthop Group Support + * Copyright (C) 2023 NVIDIA Corporation + * Copyright (C) 2023 6WIND + */ + +#include + +#include +#include +#include + + +/**************************************************************************** + * L3 NHGs are used for fast failover of nexthops in the dplane. These are + * the APIs for allocating L3 NHG ids. Management of the L3 NHG itself is + * left to the application using it. + * PS: Currently EVPN host routes is the only app using L3 NHG for fast + * failover of remote ES links. + ***************************************************************************/ +static bitfield_t bgp_nh_id_bitmap; +static uint32_t bgp_nhg_start; + +/* XXX - currently we do nothing on the callbacks */ +static void bgp_nhg_add_cb(const char *name) +{ +} + +static void bgp_nhg_modify_cb(const struct nexthop_group_cmd *nhgc) +{ +} + +static void bgp_nhg_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ +} + +static void bgp_nhg_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ +} + +static void bgp_nhg_del_cb(const char *name) +{ +} + +static void bgp_nhg_zebra_init(void) +{ + static bool bgp_nhg_zebra_inited; + + if (bgp_nhg_zebra_inited) + return; + + bgp_nhg_zebra_inited = true; + bgp_nhg_start = zclient_get_nhg_start(ZEBRA_ROUTE_BGP); + nexthop_group_init(bgp_nhg_add_cb, bgp_nhg_modify_cb, + bgp_nhg_add_nexthop_cb, bgp_nhg_del_nexthop_cb, + bgp_nhg_del_cb); +} + +void bgp_nhg_init(void) +{ + uint32_t id_max; + + id_max = MIN(ZEBRA_NHG_PROTO_SPACING - 1, 16 * 1024); + bf_init(bgp_nh_id_bitmap, id_max); + bf_assign_zero_index(bgp_nh_id_bitmap); + + if (BGP_DEBUG(nht, NHT) || BGP_DEBUG(evpn_mh, EVPN_MH_ES)) + zlog_debug("bgp nhg range %u - %u", bgp_nhg_start + 1, + bgp_nhg_start + id_max); +} + +void bgp_nhg_finish(void) +{ + bf_free(bgp_nh_id_bitmap); +} + +uint32_t bgp_nhg_id_alloc(void) +{ + uint32_t nhg_id = 0; + + bgp_nhg_zebra_init(); + bf_assign_index(bgp_nh_id_bitmap, nhg_id); + if (nhg_id) + nhg_id += bgp_nhg_start; + + return nhg_id; +} + +void bgp_nhg_id_free(uint32_t nhg_id) +{ + if (!nhg_id || (nhg_id <= bgp_nhg_start)) + return; + + nhg_id -= bgp_nhg_start; + + bf_release_index(bgp_nh_id_bitmap, nhg_id); +} diff --git a/bgpd/bgp_nhg.h b/bgpd/bgp_nhg.h new file mode 100644 index 0000000..370e8ab --- /dev/null +++ b/bgpd/bgp_nhg.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Nexthop Group Support + * Copyright (C) 2023 NVIDIA Corporation + * Copyright (C) 2023 6WIND + */ + +#ifndef _BGP_NHG_H +#define _BGP_NHG_H + +#include "nexthop_group.h" + +/* APIs for setting up and allocating L3 nexthop group ids */ +extern uint32_t bgp_nhg_id_alloc(void); +extern void bgp_nhg_id_free(uint32_t nhg_id); +extern void bgp_nhg_init(void); +void bgp_nhg_finish(void); + +#endif /* _BGP_NHG_H */ diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c new file mode 100644 index 0000000..bdaf945 --- /dev/null +++ b/bgpd/bgp_nht.c @@ -0,0 +1,1605 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Nexthop tracking + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "frrevent.h" +#include "prefix.h" +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "log.h" +#include "memory.h" +#include "nexthop.h" +#include "vrf.h" +#include "filter.h" +#include "nexthop_group.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_ecommunity.h" + +extern struct zclient *zclient; + +static void register_zebra_rnh(struct bgp_nexthop_cache *bnc); +static void unregister_zebra_rnh(struct bgp_nexthop_cache *bnc); +static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p); +static void bgp_nht_ifp_initial(struct event *thread); + +static int bgp_isvalid_nexthop(struct bgp_nexthop_cache *bnc) +{ + return (bgp_zebra_num_connects() == 0 + || (bnc && CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID) + && bnc->nexthop_num > 0)); +} + +static int bgp_isvalid_nexthop_for_ebgp(struct bgp_nexthop_cache *bnc, + struct bgp_path_info *path) +{ + struct interface *ifp = NULL; + struct nexthop *nexthop; + struct bgp_interface *iifp; + struct peer *peer; + + if (!path->extra || !path->extra->vrfleak || + !path->extra->vrfleak->peer_orig) + return false; + + peer = path->extra->vrfleak->peer_orig; + + /* only connected ebgp peers are valid */ + if (peer->sort != BGP_PEER_EBGP || peer->ttl != BGP_DEFAULT_TTL || + CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) || + CHECK_FLAG(peer->bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + return false; + + for (nexthop = bnc->nexthop; nexthop; nexthop = nexthop->next) { + if (nexthop->type == NEXTHOP_TYPE_IFINDEX || + nexthop->type == NEXTHOP_TYPE_IPV4_IFINDEX || + nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) { + ifp = if_lookup_by_index(bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bnc->bgp->vrf_id); + } + if (!ifp) + continue; + iifp = ifp->info; + if (CHECK_FLAG(iifp->flags, BGP_INTERFACE_MPLS_BGP_FORWARDING)) + return true; + } + return false; +} + +static int bgp_isvalid_nexthop_for_mplsovergre(struct bgp_nexthop_cache *bnc, + struct bgp_path_info *path) +{ + struct interface *ifp = NULL; + struct nexthop *nexthop; + + for (nexthop = bnc->nexthop; nexthop; nexthop = nexthop->next) { + if (nexthop->type != NEXTHOP_TYPE_BLACKHOLE) { + ifp = if_lookup_by_index(bnc->ifindex_ipv6_ll + ? bnc->ifindex_ipv6_ll + : nexthop->ifindex, + bnc->bgp->vrf_id); + if (ifp && (ifp->ll_type == ZEBRA_LLT_IPGRE || + ifp->ll_type == ZEBRA_LLT_IP6GRE)) + break; + } + } + if (!ifp) + return false; + + if (CHECK_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_L3VPN_ACCEPT_GRE)) + return true; + + return false; +} + +static int bgp_isvalid_nexthop_for_mpls(struct bgp_nexthop_cache *bnc, + struct bgp_path_info *path) +{ + return (bnc && (bnc->nexthop_num > 0 && + (CHECK_FLAG(path->flags, BGP_PATH_ACCEPT_OWN) || + CHECK_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID) || + bgp_isvalid_nexthop_for_ebgp(bnc, path) || + bgp_isvalid_nexthop_for_mplsovergre(bnc, path)))); +} + +static bool bgp_isvalid_nexthop_for_l3vpn(struct bgp_nexthop_cache *bnc, + struct bgp_path_info *path) +{ + if (bgp_zebra_num_connects() == 0) + return 1; + + if (path->attr->srv6_l3vpn || path->attr->srv6_vpn) { + /* In the case of SRv6-VPN, we need to track the reachability to the + * SID (in other words, IPv6 address). We check that the SID is + * available in the BGP update; then if it is available, we check + * for the nexthop reachability. + */ + if (bnc && (bnc->nexthop_num > 0 && bgp_isvalid_nexthop(bnc))) + return 1; + return 0; + } + /* + * In the case of MPLS-VPN, the label is learned from LDP or other + * protocols, and nexthop tracking is enabled for the label. + * The value is recorded as BGP_NEXTHOP_LABELED_VALID. + * - Otherwise check for mpls-gre acceptance + */ + return bgp_isvalid_nexthop_for_mpls(bnc, path); +} + +static void bgp_unlink_nexthop_check(struct bgp_nexthop_cache *bnc) +{ + if (LIST_EMPTY(&(bnc->paths)) && !bnc->nht_info) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s: freeing bnc %pFX(%d)(%u)(%s)", __func__, + &bnc->prefix, bnc->ifindex_ipv6_ll, + bnc->srte_color, bnc->bgp->name_pretty); + /* only unregister if this is the last nh for this prefix*/ + if (!bnc_existing_for_prefix(bnc)) + unregister_zebra_rnh(bnc); + bnc_free(bnc); + } +} + +void bgp_unlink_nexthop(struct bgp_path_info *path) +{ + struct bgp_nexthop_cache *bnc = path->nexthop; + + bgp_mplsvpn_path_nh_label_unlink(path); + bgp_mplsvpn_path_nh_label_bind_unlink(path); + + if (!bnc) + return; + + path_nh_map(path, NULL, false); + + bgp_unlink_nexthop_check(bnc); +} + +void bgp_replace_nexthop_by_peer(struct peer *from, struct peer *to) +{ + struct prefix pp; + struct prefix pt; + struct bgp_nexthop_cache *bncp, *bnct; + afi_t afi; + ifindex_t ifindex = 0; + + if (!sockunion2hostprefix(&from->connection->su, &pp)) + return; + + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (from->conf_if && + IN6_IS_ADDR_LINKLOCAL(&from->connection->su.sin6.sin6_addr)) + ifindex = from->connection->su.sin6.sin6_scope_id; + + afi = family2afi(pp.family); + bncp = bnc_find(&from->bgp->nexthop_cache_table[afi], &pp, 0, ifindex); + + if (!sockunion2hostprefix(&to->connection->su, &pt)) + return; + + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + ifindex = 0; + if (to->conf_if && + IN6_IS_ADDR_LINKLOCAL(&to->connection->su.sin6.sin6_addr)) + ifindex = to->connection->su.sin6.sin6_scope_id; + bnct = bnc_find(&to->bgp->nexthop_cache_table[afi], &pt, 0, ifindex); + + if (bnct != bncp) + return; + + if (bnct) + bnct->nht_info = to; +} + +/* + * Returns the bnc whose bnc->nht_info matches the LL peer by + * looping through the IPv6 nexthop table + */ +static struct bgp_nexthop_cache * +bgp_find_ipv6_nexthop_matching_peer(struct peer *peer) +{ + struct bgp_nexthop_cache *bnc; + + frr_each (bgp_nexthop_cache, &peer->bgp->nexthop_cache_table[AFI_IP6], + bnc) { + if (bnc->nht_info == peer) { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug( + "Found bnc: %pFX(%u)(%u)(%p) for peer: %s(%s) %p", + &bnc->prefix, bnc->ifindex_ipv6_ll, + bnc->srte_color, bnc, peer->host, + peer->bgp->name_pretty, peer); + } + return bnc; + } + } + + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Could not find bnc for peer %s(%s) %p in v6 nexthop table", + peer->host, peer->bgp->name_pretty, peer); + + return NULL; +} + +void bgp_unlink_nexthop_by_peer(struct peer *peer) +{ + struct prefix p; + struct bgp_nexthop_cache *bnc; + afi_t afi = family2afi(peer->connection->su.sa.sa_family); + ifindex_t ifindex = 0; + + if (!sockunion2hostprefix(&peer->connection->su, &p)) { + /* + * In scenarios where unnumbered BGP session is brought + * down by shutting down the interface before unconfiguring + * the BGP neighbor, neighbor information in peer->su.sa + * will be cleared when the interface is shutdown. So + * during the deletion of unnumbered bgp peer, above check + * will return true. Therefore, in this case,BGP needs to + * find the bnc whose bnc->nht_info matches the + * peer being deleted and free it. + */ + bnc = bgp_find_ipv6_nexthop_matching_peer(peer); + } else { + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (afi == AFI_IP6 && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + ifindex = peer->connection->su.sin6.sin6_scope_id; + bnc = bnc_find(&peer->bgp->nexthop_cache_table[afi], &p, 0, + ifindex); + } + + if (!bnc) + return; + + /* cleanup the peer reference */ + bnc->nht_info = NULL; + + bgp_unlink_nexthop_check(bnc); +} + +/* + * A route and its nexthop might belong to different VRFs. Therefore, + * we need both the bgp_route and bgp_nexthop pointers. + */ +int bgp_find_or_add_nexthop(struct bgp *bgp_route, struct bgp *bgp_nexthop, + afi_t afi, safi_t safi, struct bgp_path_info *pi, + struct peer *peer, int connected, + const struct prefix *orig_prefix) +{ + struct bgp_nexthop_cache_head *tree = NULL; + struct bgp_nexthop_cache *bnc; + struct bgp_path_info *bpi_ultimate; + struct prefix p; + uint32_t srte_color = 0; + int is_bgp_static_route = 0; + ifindex_t ifindex = 0; + + if (pi) { + is_bgp_static_route = ((pi->type == ZEBRA_ROUTE_BGP) + && (pi->sub_type == BGP_ROUTE_STATIC)) + ? 1 + : 0; + + /* Since Extended Next-hop Encoding (RFC5549) support, we want + to derive + address-family from the next-hop. */ + if (!is_bgp_static_route) + afi = BGP_ATTR_MP_NEXTHOP_LEN_IP6(pi->attr) ? AFI_IP6 + : AFI_IP; + + /* Validation for the ipv4 mapped ipv6 nexthop. */ + if (IS_MAPPED_IPV6(&pi->attr->mp_nexthop_global)) { + afi = AFI_IP; + } + + /* This will return true if the global IPv6 NH is a link local + * addr */ + if (make_prefix(afi, pi, &p) < 0) + return 1; + + /* + * If it's a V6 nexthop, path is learnt from a v6 LL peer, + * and if the NH prefix matches peer's LL address then + * set the ifindex to peer's interface index so that + * correct nexthop can be found in nexthop tree. + * + * NH could be set to different v6 LL address (compared to + * peer's LL) using route-map. In such a scenario, do not set + * the ifindex. + */ + if (afi == AFI_IP6 && + IN6_IS_ADDR_LINKLOCAL( + &pi->peer->connection->su.sin6.sin6_addr) && + IPV6_ADDR_SAME(&pi->peer->connection->su.sin6.sin6_addr, + &p.u.prefix6)) + ifindex = pi->peer->connection->su.sin6.sin6_scope_id; + + if (!is_bgp_static_route && orig_prefix + && prefix_same(&p, orig_prefix)) { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug( + "%s(%pFX): prefix loops through itself", + __func__, &p); + } + return 0; + } + + srte_color = bgp_attr_get_color(pi->attr); + + } else if (peer) { + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (afi == AFI_IP6 && peer->conf_if && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) { + ifindex = peer->connection->su.sin6.sin6_scope_id; + if (ifindex == 0) { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug( + "%s: Unable to locate ifindex, waiting till we have one", + peer->conf_if); + } + return 0; + } + } + + if (!sockunion2hostprefix(&peer->connection->su, &p)) { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug( + "%s: Attempting to register with unknown AFI %d (not %d or %d)", + __func__, afi, AFI_IP, AFI_IP6); + } + return 0; + } + } else + return 0; + + if (is_bgp_static_route) + tree = &bgp_nexthop->import_check_table[afi]; + else + tree = &bgp_nexthop->nexthop_cache_table[afi]; + + bnc = bnc_find(tree, &p, srte_color, ifindex); + if (!bnc) { + bnc = bnc_new(tree, &p, srte_color, ifindex); + bnc->afi = afi; + bnc->bgp = bgp_nexthop; + if (BGP_DEBUG(nht, NHT)) + zlog_debug("Allocated bnc %pFX(%d)(%u)(%s) peer %p", + &bnc->prefix, bnc->ifindex_ipv6_ll, + bnc->srte_color, bnc->bgp->name_pretty, + peer); + } else { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Found existing bnc %pFX(%d)(%s) flags 0x%x ifindex %d #paths %d peer %p", + &bnc->prefix, bnc->ifindex_ipv6_ll, + bnc->bgp->name_pretty, bnc->flags, + bnc->ifindex_ipv6_ll, bnc->path_count, + bnc->nht_info); + } + + if (pi && is_route_parent_evpn(pi)) + bnc->is_evpn_gwip_nexthop = true; + + if (is_bgp_static_route && !CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE)) { + SET_FLAG(bnc->flags, BGP_STATIC_ROUTE); + + /* If we're toggling the type, re-register */ + if ((CHECK_FLAG(bgp_route->flags, BGP_FLAG_IMPORT_CHECK)) + && !CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH)) { + SET_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + } else if ((!CHECK_FLAG(bgp_route->flags, + BGP_FLAG_IMPORT_CHECK)) + && CHECK_FLAG(bnc->flags, + BGP_STATIC_ROUTE_EXACT_MATCH)) { + UNSET_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + } + } + /* When nexthop is already known, but now requires 'connected' + * resolution, + * re-register it. The reverse scenario where the nexthop currently + * requires + * 'connected' resolution does not need a re-register (i.e., we treat + * 'connected-required' as an override) except in the scenario where + * this + * is actually a case of tracking a peer for connectivity (e.g., after + * disable connected-check). + * NOTE: We don't track the number of paths separately for 'connected- + * required' vs 'connected-not-required' as this change is not a common + * scenario. + */ + else if (connected && !CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) { + SET_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + } else if (peer && !connected + && CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) { + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + } + if (peer && (bnc->ifindex_ipv6_ll != ifindex)) { + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + bnc->ifindex_ipv6_ll = ifindex; + } + if (bgp_route->inst_type == BGP_INSTANCE_TYPE_VIEW) { + SET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + SET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + } else if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED) + && !is_default_host_route(&bnc->prefix)) + register_zebra_rnh(bnc); + + if (pi && pi->nexthop != bnc) { + /* Unlink from existing nexthop cache, if any. This will also + * free + * the nexthop cache entry, if appropriate. + */ + bgp_unlink_nexthop(pi); + + /* updates NHT pi list reference */ + path_nh_map(pi, bnc, true); + + bpi_ultimate = bgp_get_imported_bpi_ultimate(pi); + if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID) && bnc->metric) + (bgp_path_info_extra_get(bpi_ultimate))->igpmetric = + bnc->metric; + else if (bpi_ultimate->extra) + bpi_ultimate->extra->igpmetric = 0; + } else if (peer) { + /* + * Let's not accidentally save the peer data for a peer + * we are going to throw away in a second or so. + * When we come back around we'll fix up this + * data properly in replace_nexthop_by_peer + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + bnc->nht_info = (void *)peer; /* NHT peer reference */ + } + + /* + * We are cheating here. Views have no associated underlying + * ability to detect nexthops. So when we have a view + * just tell everyone the nexthop is valid + */ + if (bgp_route->inst_type == BGP_INSTANCE_TYPE_VIEW) + return 1; + else if (safi == SAFI_UNICAST && pi && + pi->sub_type == BGP_ROUTE_IMPORTED && + BGP_PATH_INFO_NUM_LABELS(pi) && !bnc->is_evpn_gwip_nexthop) + return bgp_isvalid_nexthop_for_l3vpn(bnc, pi); + else if (safi == SAFI_MPLS_VPN && pi && + pi->sub_type != BGP_ROUTE_IMPORTED) + /* avoid not redistributing mpls vpn routes */ + return 1; + else + /* mpls-vpn routes with BGP_ROUTE_IMPORTED subtype */ + return (bgp_isvalid_nexthop(bnc)); +} + +void bgp_delete_connected_nexthop(afi_t afi, struct peer *peer) +{ + struct bgp_nexthop_cache *bnc; + struct prefix p; + ifindex_t ifindex = 0; + + if (!peer) + return; + + /* + * In case the below check evaluates true and if + * the bnc has not been freed at this point, then + * we might have to do something similar to what's + * done in bgp_unlink_nexthop_by_peer(). Since + * bgp_unlink_nexthop_by_peer() loops through the + * nodes of V6 nexthop cache to find the bnc, it is + * currently not being called here. + */ + if (!sockunion2hostprefix(&peer->connection->su, &p)) + return; + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (afi == AFI_IP6 && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + ifindex = peer->connection->su.sin6.sin6_scope_id; + bnc = bnc_find(&peer->bgp->nexthop_cache_table[family2afi(p.family)], + &p, 0, ifindex); + if (!bnc) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Cannot find connected NHT node for peer %s(%s)", + peer->host, peer->bgp->name_pretty); + return; + } + + if (bnc->nht_info != peer) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Connected NHT %p node for peer %s(%s) points to %p", + bnc, peer->host, bnc->bgp->name_pretty, + bnc->nht_info); + return; + } + + bnc->nht_info = NULL; + + if (LIST_EMPTY(&(bnc->paths))) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Freeing connected NHT node %p for peer %s(%s)", + bnc, peer->host, bnc->bgp->name_pretty); + unregister_zebra_rnh(bnc); + bnc_free(bnc); + } +} + +static void bgp_process_nexthop_update(struct bgp_nexthop_cache *bnc, + struct zapi_route *nhr, + bool import_check) +{ + struct nexthop *nexthop; + struct nexthop *oldnh; + struct nexthop *nhlist_head = NULL; + struct nexthop *nhlist_tail = NULL; + int i; + bool evpn_resolved = false; + + bnc->last_update = monotime(NULL); + bnc->change_flags = 0; + + /* debug print the input */ + if (BGP_DEBUG(nht, NHT)) { + char bnc_buf[BNC_FLAG_DUMP_SIZE]; + + zlog_debug( + "%s(%u): Rcvd NH update %pFX(%u)(%u) - metric %d/%d #nhops %d/%d flags %s", + bnc->bgp->name_pretty, bnc->bgp->vrf_id, &nhr->prefix, + bnc->ifindex_ipv6_ll, bnc->srte_color, nhr->metric, + bnc->metric, nhr->nexthop_num, bnc->nexthop_num, + bgp_nexthop_dump_bnc_flags(bnc, bnc_buf, + sizeof(bnc_buf))); + } + + if (nhr->metric != bnc->metric) + bnc->change_flags |= BGP_NEXTHOP_METRIC_CHANGED; + + if (nhr->nexthop_num != bnc->nexthop_num) + bnc->change_flags |= BGP_NEXTHOP_CHANGED; + + if (import_check && (nhr->type == ZEBRA_ROUTE_BGP || + !prefix_same(&bnc->prefix, &nhr->prefix))) { + SET_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_LABELED_VALID); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_EVPN_INCOMPLETE); + + bnc_nexthop_free(bnc); + bnc->nexthop = NULL; + + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "%s: Import Check does not resolve to the same prefix for %pFX received %pFX or matching route is BGP", + __func__, &bnc->prefix, &nhr->prefix); + } else if (nhr->nexthop_num) { + struct peer *peer = bnc->nht_info; + + prefix_copy(&bnc->resolved_prefix, &nhr->prefix); + + /* notify bgp fsm if nbr ip goes from invalid->valid */ + if (!bnc->nexthop_num) + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); + + if (!bnc->is_evpn_gwip_nexthop) + bnc->flags |= BGP_NEXTHOP_VALID; + bnc->metric = nhr->metric; + bnc->nexthop_num = nhr->nexthop_num; + + bnc->flags &= ~BGP_NEXTHOP_LABELED_VALID; /* check below */ + + for (i = 0; i < nhr->nexthop_num; i++) { + int num_labels = 0; + + nexthop = nexthop_from_zapi_nexthop(&nhr->nexthops[i]); + + /* + * Turn on RA for the v6 nexthops + * we receive from bgp. This is to allow us + * to work with v4 routing over v6 nexthops + */ + if (peer && !peer->ifp + && CHECK_FLAG(peer->flags, + PEER_FLAG_CAPABILITY_ENHE) + && nhr->prefix.family == AF_INET6 + && nexthop->type != NEXTHOP_TYPE_BLACKHOLE) { + struct interface *ifp; + + ifp = if_lookup_by_index(nexthop->ifindex, + nexthop->vrf_id); + if (ifp) + zclient_send_interface_radv_req( + zclient, nexthop->vrf_id, ifp, + true, + BGP_UNNUM_DEFAULT_RA_INTERVAL); + } + /* There is at least one label-switched path */ + if (nexthop->nh_label && + nexthop->nh_label->num_labels) { + + bnc->flags |= BGP_NEXTHOP_LABELED_VALID; + num_labels = nexthop->nh_label->num_labels; + } + + if (BGP_DEBUG(nht, NHT)) { + char buf[NEXTHOP_STRLEN]; + zlog_debug( + " nhop via %s (%d labels)", + nexthop2str(nexthop, buf, sizeof(buf)), + num_labels); + } + + if (nhlist_tail) { + nhlist_tail->next = nexthop; + nhlist_tail = nexthop; + } else { + nhlist_tail = nexthop; + nhlist_head = nexthop; + } + + /* No need to evaluate the nexthop if we have already + * determined + * that there has been a change. + */ + if (bnc->change_flags & BGP_NEXTHOP_CHANGED) + continue; + + for (oldnh = bnc->nexthop; oldnh; oldnh = oldnh->next) + if (nexthop_same(oldnh, nexthop)) + break; + + if (!oldnh) + bnc->change_flags |= BGP_NEXTHOP_CHANGED; + } + bnc_nexthop_free(bnc); + bnc->nexthop = nhlist_head; + + /* + * Gateway IP nexthop is L3 reachable. Mark it as + * BGP_NEXTHOP_VALID only if it is recursively resolved with a + * remote EVPN RT-2. + * Else, mark it as BGP_NEXTHOP_EVPN_INCOMPLETE. + * When its mapping with EVPN RT-2 is established, unset + * BGP_NEXTHOP_EVPN_INCOMPLETE and set BGP_NEXTHOP_VALID. + */ + if (bnc->is_evpn_gwip_nexthop) { + evpn_resolved = bgp_evpn_is_gateway_ip_resolved(bnc); + + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "EVPN gateway IP %pFX recursive MAC/IP lookup %s", + &bnc->prefix, + (evpn_resolved ? "successful" + : "failed")); + + if (evpn_resolved) { + bnc->flags |= BGP_NEXTHOP_VALID; + bnc->flags &= ~BGP_NEXTHOP_EVPN_INCOMPLETE; + bnc->change_flags |= BGP_NEXTHOP_MACIP_CHANGED; + } else { + bnc->flags |= BGP_NEXTHOP_EVPN_INCOMPLETE; + bnc->flags &= ~BGP_NEXTHOP_VALID; + } + } + } else { + memset(&bnc->resolved_prefix, 0, sizeof(bnc->resolved_prefix)); + bnc->flags &= ~BGP_NEXTHOP_EVPN_INCOMPLETE; + bnc->flags &= ~BGP_NEXTHOP_VALID; + bnc->flags &= ~BGP_NEXTHOP_LABELED_VALID; + bnc->nexthop_num = nhr->nexthop_num; + + /* notify bgp fsm if nbr ip goes from valid->invalid */ + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); + + bnc_nexthop_free(bnc); + bnc->nexthop = NULL; + } + + evaluate_paths(bnc); +} + +static void bgp_nht_ifp_table_handle(struct bgp *bgp, + struct bgp_nexthop_cache_head *table, + struct interface *ifp, bool up) +{ + struct bgp_nexthop_cache *bnc; + struct nexthop *nhop; + uint8_t other_nh_count; + bool nhop_ll_found = false; + bool nhop_found = false; + + if (ifp->ifindex == IFINDEX_INTERNAL) { + zlog_warn("%s: The interface %s ignored", __func__, ifp->name); + return; + } + + frr_each (bgp_nexthop_cache, table, bnc) { + other_nh_count = 0; + nhop_ll_found = bnc->ifindex_ipv6_ll == ifp->ifindex; + for (nhop = bnc->nexthop; nhop; nhop = nhop->next) { + if (nhop->ifindex == bnc->ifindex_ipv6_ll) + continue; + + if (nhop->ifindex != ifp->ifindex) { + other_nh_count++; + continue; + } + if (nhop->vrf_id != ifp->vrf->vrf_id) { + other_nh_count++; + continue; + } + nhop_found = true; + } + + if (!nhop_found && !nhop_ll_found) + /* The event interface does not match the nexthop cache + * entry */ + continue; + + if (!up && other_nh_count > 0) + /* Down event ignored in case of multiple next-hop + * interfaces. The other might interfaces might be still + * up. The cases where all interfaces are down or a bnc + * is invalid are processed by a separate zebra rnh + * messages. + */ + continue; + + if (!nhop_ll_found) { + evaluate_paths(bnc); + continue; + } + + bnc->last_update = monotime(NULL); + bnc->change_flags = 0; + + /* + * For interface based routes ( ala the v6 LL routes + * that this was written for ) the metric received + * for the connected route is 0 not 1. + */ + bnc->metric = 0; + if (up) { + SET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + SET_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED); + /* change nexthop number only for ll */ + bnc->nexthop_num = 1; + } else { + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + SET_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED); + bnc->nexthop_num = 0; + } + + evaluate_paths(bnc); + } +} +static void bgp_nht_ifp_handle(struct interface *ifp, bool up) +{ + struct bgp *bgp; + + bgp = ifp->vrf->info; + if (!bgp) + return; + + bgp_nht_ifp_table_handle(bgp, &bgp->nexthop_cache_table[AFI_IP], ifp, + up); + bgp_nht_ifp_table_handle(bgp, &bgp->import_check_table[AFI_IP], ifp, + up); + bgp_nht_ifp_table_handle(bgp, &bgp->nexthop_cache_table[AFI_IP6], ifp, + up); + bgp_nht_ifp_table_handle(bgp, &bgp->import_check_table[AFI_IP6], ifp, + up); +} + +void bgp_nht_ifp_up(struct interface *ifp) +{ + bgp_nht_ifp_handle(ifp, true); +} + +void bgp_nht_ifp_down(struct interface *ifp) +{ + bgp_nht_ifp_handle(ifp, false); +} + +static void bgp_nht_ifp_initial(struct event *thread) +{ + ifindex_t ifindex = EVENT_VAL(thread); + struct bgp *bgp = EVENT_ARG(thread); + struct interface *ifp = if_lookup_by_index(ifindex, bgp->vrf_id); + + if (!ifp) + return; + + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "Handle NHT initial update for Intf %s(%d) status %s", + ifp->name, ifp->ifindex, if_is_up(ifp) ? "up" : "down"); + + if (if_is_up(ifp)) + bgp_nht_ifp_up(ifp); + else + bgp_nht_ifp_down(ifp); +} + +/* + * So the bnc code has the ability to handle interface up/down + * events to properly handle v6 LL peering. + * What is happening here: + * The event system for peering expects the nht code to + * report on the tracking events after we move to active + * So let's give the system a chance to report on that event + * in a manner that is expected. + */ +void bgp_nht_interface_events(struct peer *peer) +{ + struct bgp *bgp = peer->bgp; + struct bgp_nexthop_cache_head *table; + struct bgp_nexthop_cache *bnc; + struct prefix p; + ifindex_t ifindex = 0; + + if (!IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + return; + + if (!sockunion2hostprefix(&peer->connection->su, &p)) + return; + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (peer->conf_if && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + ifindex = peer->connection->su.sin6.sin6_scope_id; + + table = &bgp->nexthop_cache_table[AFI_IP6]; + bnc = bnc_find(table, &p, 0, ifindex); + if (!bnc) + return; + + if (bnc->ifindex_ipv6_ll) + event_add_event(bm->master, bgp_nht_ifp_initial, bnc->bgp, + bnc->ifindex_ipv6_ll, NULL); +} + +void bgp_nexthop_update(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr) +{ + struct bgp_nexthop_cache_head *tree = NULL; + struct bgp_nexthop_cache *bnc_nhc, *bnc_import; + struct bgp *bgp, *bgp_default; + struct bgp_path_info *pi; + struct bgp_dest *dest; + safi_t safi; + afi_t afi; + + if (!vrf->info) { + flog_err(EC_BGP_NH_UPD, + "parse nexthop update: instance not found for vrf_id %u", + vrf->vrf_id); + return; + } + + bgp = (struct bgp *)vrf->info; + afi = family2afi(match->family); + tree = &bgp->nexthop_cache_table[afi]; + + bnc_nhc = bnc_find(tree, match, nhr->srte_color, 0); + if (bnc_nhc) + bgp_process_nexthop_update(bnc_nhc, nhr, false); + else if (BGP_DEBUG(nht, NHT)) + zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for nexthop cache", + &nhr->prefix, nhr->srte_color, + bgp->name_pretty); + + tree = &bgp->import_check_table[afi]; + + bnc_import = bnc_find(tree, match, nhr->srte_color, 0); + if (bnc_import) { + bgp_process_nexthop_update(bnc_import, nhr, true); + + bgp_default = bgp_get_default(); + safi = nhr->safi; + if (bgp != bgp_default && bgp->rib[afi][safi]) { + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, + match, NULL); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) + if (pi->peer == bgp->peer_self && + pi->type == ZEBRA_ROUTE_BGP && + pi->sub_type == BGP_ROUTE_STATIC) + vpn_leak_from_vrf_update(bgp_default, + bgp, pi); + } + } else if (BGP_DEBUG(nht, NHT)) + zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for import check", + &nhr->prefix, nhr->srte_color, + bgp->name_pretty); + + /* + * HACK: if any BGP route is dependant on an SR-policy that doesn't + * exist, zebra will never send NH updates relative to that policy. In + * that case, whenever we receive an update about a colorless NH, update + * the corresponding colorful NHs that share the same endpoint but that + * are inactive. This ugly hack should work around the problem at the + * cost of a performance pernalty. Long term, what should be done is to + * make zebra's RNH subsystem aware of SR-TE colors (like bgpd is), + * which should provide a better infrastructure to solve this issue in + * a more efficient and elegant way. + */ + if (nhr->srte_color == 0) { + struct bgp_nexthop_cache *bnc_iter; + + frr_each (bgp_nexthop_cache, &bgp->nexthop_cache_table[afi], + bnc_iter) { + if (!prefix_same(match, &bnc_iter->prefix) || + bnc_iter->srte_color == 0) + continue; + + bgp_process_nexthop_update(bnc_iter, nhr, false); + } + } +} + +/* + * Cleanup nexthop registration and status information for BGP nexthops + * pertaining to this VRF. This is invoked upon VRF deletion. + */ +void bgp_cleanup_nexthops(struct bgp *bgp) +{ + for (afi_t afi = AFI_IP; afi < AFI_MAX; afi++) { + struct bgp_nexthop_cache *bnc; + + frr_each (bgp_nexthop_cache, &bgp->nexthop_cache_table[afi], + bnc) { + /* Clear relevant flags. */ + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_VALID); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_EVPN_INCOMPLETE); + } + } +} + +/** + * make_prefix - make a prefix structure from the path (essentially + * path's node. + */ +static int make_prefix(int afi, struct bgp_path_info *pi, struct prefix *p) +{ + + int is_bgp_static = ((pi->type == ZEBRA_ROUTE_BGP) + && (pi->sub_type == BGP_ROUTE_STATIC)) + ? 1 + : 0; + struct bgp_dest *net = pi->net; + const struct prefix *p_orig = bgp_dest_get_prefix(net); + struct in_addr ipv4; + + if (p_orig->family == AF_FLOWSPEC) { + if (!pi->peer) + return -1; + return bgp_flowspec_get_first_nh(pi->peer->bgp, + pi, p, afi); + } + memset(p, 0, sizeof(struct prefix)); + switch (afi) { + case AFI_IP: + p->family = AF_INET; + if (is_bgp_static) { + p->u.prefix4 = p_orig->u.prefix4; + p->prefixlen = p_orig->prefixlen; + } else { + if (IS_MAPPED_IPV6(&pi->attr->mp_nexthop_global)) { + ipv4_mapped_ipv6_to_ipv4( + &pi->attr->mp_nexthop_global, &ipv4); + p->u.prefix4 = ipv4; + p->prefixlen = IPV4_MAX_BITLEN; + } else { + if (p_orig->family == AF_EVPN) + p->u.prefix4 = + pi->attr->mp_nexthop_global_in; + else + p->u.prefix4 = pi->attr->nexthop; + p->prefixlen = IPV4_MAX_BITLEN; + } + } + break; + case AFI_IP6: + p->family = AF_INET6; + if (pi->attr->srv6_l3vpn) { + IPV6_ADDR_COPY(&(p->u.prefix6), + &(pi->attr->srv6_l3vpn->sid)); + p->prefixlen = IPV6_MAX_BITLEN; + } else if (is_bgp_static) { + p->u.prefix6 = p_orig->u.prefix6; + p->prefixlen = p_orig->prefixlen; + } else { + /* If we receive MP_REACH nexthop with ::(LL) + * or LL(LL), use LL address as nexthop cache. + */ + if (pi->attr && + pi->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL && + (IN6_IS_ADDR_UNSPECIFIED( + &pi->attr->mp_nexthop_global) || + IN6_IS_ADDR_LINKLOCAL(&pi->attr->mp_nexthop_global))) + p->u.prefix6 = pi->attr->mp_nexthop_local; + /* If we receive MR_REACH with (GA)::(LL) + * then check for route-map to choose GA or LL + */ + else if (pi->attr && + pi->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + if (CHECK_FLAG(pi->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + p->u.prefix6 = + pi->attr->mp_nexthop_global; + else + p->u.prefix6 = + pi->attr->mp_nexthop_local; + } else + p->u.prefix6 = pi->attr->mp_nexthop_global; + p->prefixlen = IPV6_MAX_BITLEN; + } + break; + default: + if (BGP_DEBUG(nht, NHT)) { + zlog_debug( + "%s: Attempting to make prefix with unknown AFI %d (not %d or %d)", + __func__, afi, AFI_IP, AFI_IP6); + } + break; + } + return 0; +} + +/** + * sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister + * command to Zebra. + * ARGUMENTS: + * struct bgp_nexthop_cache *bnc -- the nexthop structure. + * int command -- command to send to zebra + * RETURNS: + * void. + */ +static void sendmsg_zebra_rnh(struct bgp_nexthop_cache *bnc, int command) +{ + bool exact_match = false; + bool resolve_via_default = false; + int ret; + + if (!zclient) + return; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bnc->bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing NHT entry", + __func__); + return; + } + + if (!bgp_zebra_num_connects()) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: We have not connected yet, cannot send nexthops", + __func__); + } + if (command == ZEBRA_NEXTHOP_REGISTER) { + if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_CONNECTED)) + exact_match = true; + if (CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE_EXACT_MATCH)) + resolve_via_default = true; + } + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: sending cmd %s for %pFX (vrf %s)", __func__, + zserv_command_string(command), &bnc->prefix, + bnc->bgp->name_pretty); + + ret = zclient_send_rnh(zclient, command, &bnc->prefix, SAFI_UNICAST, + exact_match, resolve_via_default, + bnc->bgp->vrf_id); + if (ret == ZCLIENT_SEND_FAILURE) { + flog_warn(EC_BGP_ZEBRA_SEND, + "sendmsg_nexthop: zclient_send_message() failed"); + return; + } + + if (command == ZEBRA_NEXTHOP_REGISTER) + SET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + else if (command == ZEBRA_NEXTHOP_UNREGISTER) + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + return; +} + +/** + * register_zebra_rnh - register a NH/route with Zebra for notification + * when the route or the route to the nexthop changes. + * ARGUMENTS: + * struct bgp_nexthop_cache *bnc + * RETURNS: + * void. + */ +static void register_zebra_rnh(struct bgp_nexthop_cache *bnc) +{ + /* Check if we have already registered */ + if (bnc->flags & BGP_NEXTHOP_REGISTERED) + return; + + if (bnc->ifindex_ipv6_ll) { + SET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + return; + } + + sendmsg_zebra_rnh(bnc, ZEBRA_NEXTHOP_REGISTER); +} + +/** + * unregister_zebra_rnh -- Unregister the route/nexthop from Zebra. + * ARGUMENTS: + * struct bgp_nexthop_cache *bnc + * RETURNS: + * void. + */ +static void unregister_zebra_rnh(struct bgp_nexthop_cache *bnc) +{ + struct bgp_nexthop_cache *import; + struct bgp_nexthop_cache *nexthop; + + struct bgp *bgp = bnc->bgp; + + /* Check if we have already registered */ + if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED)) + return; + + if (bnc->ifindex_ipv6_ll) { + UNSET_FLAG(bnc->flags, BGP_NEXTHOP_REGISTERED); + return; + } + + import = bnc_find(&bgp->import_check_table[bnc->afi], &bnc->prefix, 0, + 0); + nexthop = bnc_find(&bgp->nexthop_cache_table[bnc->afi], &bnc->prefix, 0, + 0); + + /* + * If this entry has both a import and a nexthop entry + * then let's not send the unregister quite as of yet + * wait until we only have 1 left + */ + if (import && nexthop) + return; + + sendmsg_zebra_rnh(bnc, ZEBRA_NEXTHOP_UNREGISTER); +} + +/** + * evaluate_paths - Evaluate the paths/nets associated with a nexthop. + * ARGUMENTS: + * struct bgp_nexthop_cache *bnc -- the nexthop structure. + * RETURNS: + * void. + */ +void evaluate_paths(struct bgp_nexthop_cache *bnc) +{ + struct bgp_dest *dest; + struct bgp_path_info *path; + struct bgp_path_info *bpi_ultimate; + int afi; + struct peer *peer = (struct peer *)bnc->nht_info; + struct bgp_table *table; + safi_t safi; + struct bgp *bgp_path; + const struct prefix *p; + + if (BGP_DEBUG(nht, NHT)) { + char bnc_buf[BNC_FLAG_DUMP_SIZE]; + char chg_buf[BNC_FLAG_DUMP_SIZE]; + + zlog_debug( + "NH update for %pFX(%d)(%u)(%s) - flags %s chgflags %s- evaluate paths", + &bnc->prefix, bnc->ifindex_ipv6_ll, bnc->srte_color, + bnc->bgp->name_pretty, + bgp_nexthop_dump_bnc_flags(bnc, bnc_buf, + sizeof(bnc_buf)), + bgp_nexthop_dump_bnc_change_flags(bnc, chg_buf, + sizeof(bnc_buf))); + } + + LIST_FOREACH (path, &(bnc->paths), nh_thread) { + if (path->type == ZEBRA_ROUTE_BGP && + (path->sub_type == BGP_ROUTE_NORMAL || + path->sub_type == BGP_ROUTE_STATIC || + path->sub_type == BGP_ROUTE_IMPORTED)) + /* evaluate the path */ + ; + else if (path->sub_type == BGP_ROUTE_REDISTRIBUTE) { + /* evaluate the path for redistributed routes + * except those from VNC + */ + if ((path->type == ZEBRA_ROUTE_VNC) || + (path->type == ZEBRA_ROUTE_VNC_DIRECT)) + continue; + } else + /* don't evaluate the path */ + continue; + + dest = path->net; + assert(dest && bgp_dest_table(dest)); + p = bgp_dest_get_prefix(dest); + afi = family2afi(p->family); + table = bgp_dest_table(dest); + safi = table->safi; + + /* + * handle routes from other VRFs (they can have a + * nexthop in THIS VRF). bgp_path is the bgp instance + * that owns the route referencing this nexthop. + */ + bgp_path = table->bgp; + + /* + * Path becomes valid/invalid depending on whether the nexthop + * reachable/unreachable. + * + * In case of unicast routes that were imported from vpn + * and that have labels, they are valid only if there are + * nexthops with labels + * + * If the nexthop is EVPN gateway-IP, + * do not check for a valid label. + */ + + bool bnc_is_valid_nexthop = false; + bool path_valid = false; + + if (safi == SAFI_UNICAST && + path->sub_type == BGP_ROUTE_IMPORTED && + BGP_PATH_INFO_NUM_LABELS(path) && + (path->attr->evpn_overlay.type != OVERLAY_INDEX_GATEWAY_IP)) { + bnc_is_valid_nexthop = + bgp_isvalid_nexthop_for_l3vpn(bnc, path) + ? true + : false; + } else if (safi == SAFI_MPLS_VPN && + path->sub_type != BGP_ROUTE_IMPORTED) { + /* avoid not redistributing mpls vpn routes */ + bnc_is_valid_nexthop = true; + } else { + /* mpls-vpn routes with BGP_ROUTE_IMPORTED subtype */ + if (bgp_update_martian_nexthop( + bnc->bgp, afi, safi, path->type, + path->sub_type, path->attr, dest)) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "%s: prefix %pBD (vrf %s), ignoring path due to martian or self-next-hop", + __func__, dest, bgp_path->name); + } else + bnc_is_valid_nexthop = + bgp_isvalid_nexthop(bnc) ? true : false; + } + + if (BGP_DEBUG(nht, NHT)) { + + if (dest->pdest) { + char rd_buf[RD_ADDRSTRLEN]; + + prefix_rd2str( + (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest), + rd_buf, sizeof(rd_buf), + bgp_get_asnotation(bnc->bgp)); + zlog_debug( + "... eval path %d/%d %pBD RD %s %s flags 0x%x", + afi, safi, dest, rd_buf, + bgp_path->name_pretty, path->flags); + } else + zlog_debug( + "... eval path %d/%d %pBD %s flags 0x%x", + afi, safi, dest, bgp_path->name_pretty, + path->flags); + } + + /* Skip paths marked for removal or as history. */ + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED) + || CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + continue; + + /* Copy the metric to the path. Will be used for bestpath + * computation */ + bpi_ultimate = bgp_get_imported_bpi_ultimate(path); + if (bgp_isvalid_nexthop(bnc) && bnc->metric) + (bgp_path_info_extra_get(bpi_ultimate))->igpmetric = + bnc->metric; + else if (bpi_ultimate->extra) + bpi_ultimate->extra->igpmetric = 0; + + if (CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_METRIC_CHANGED) || + CHECK_FLAG(bnc->change_flags, BGP_NEXTHOP_CHANGED) || + bgp_attr_get_color(path->attr)) + SET_FLAG(path->flags, BGP_PATH_IGP_CHANGED); + + path_valid = CHECK_FLAG(path->flags, BGP_PATH_VALID); + if (path->type == ZEBRA_ROUTE_BGP && + path->sub_type == BGP_ROUTE_STATIC && + !CHECK_FLAG(bgp_path->flags, BGP_FLAG_IMPORT_CHECK)) + /* static routes with 'no bgp network import-check' are + * always valid. if nht is called with static routes, + * the vpn exportation needs to be triggered + */ + vpn_leak_from_vrf_update(bgp_get_default(), bgp_path, + path); + else if (path->sub_type == BGP_ROUTE_REDISTRIBUTE && + safi == SAFI_UNICAST && + (bgp_path->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp_path->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) + /* redistribute routes are always valid + * if nht is called with redistribute routes, the vpn + * exportation needs to be triggered + */ + vpn_leak_from_vrf_update(bgp_get_default(), bgp_path, + path); + else if (path_valid != bnc_is_valid_nexthop) { + if (path_valid) { + /* No longer valid, clear flag; also for EVPN + * routes, unimport from VRFs if needed. + */ + bgp_aggregate_decrement(bgp_path, p, path, afi, + safi); + bgp_path_info_unset_flag(dest, path, + BGP_PATH_VALID); + if (safi == SAFI_EVPN && + bgp_evpn_is_prefix_nht_supported(bgp_dest_get_prefix(dest))) + bgp_evpn_unimport_route(bgp_path, + afi, safi, bgp_dest_get_prefix(dest), path); + if (safi == SAFI_UNICAST && + (bgp_path->inst_type != + BGP_INSTANCE_TYPE_VIEW)) + vpn_leak_from_vrf_withdraw( + bgp_get_default(), bgp_path, + path); + } else { + /* Path becomes valid, set flag; also for EVPN + * routes, import from VRFs if needed. + */ + bgp_path_info_set_flag(dest, path, + BGP_PATH_VALID); + bgp_aggregate_increment(bgp_path, p, path, afi, + safi); + if (safi == SAFI_EVPN && + bgp_evpn_is_prefix_nht_supported(bgp_dest_get_prefix(dest))) + bgp_evpn_import_route(bgp_path, + afi, safi, bgp_dest_get_prefix(dest), path); + if (safi == SAFI_UNICAST && + (bgp_path->inst_type != + BGP_INSTANCE_TYPE_VIEW)) + vpn_leak_from_vrf_update( + bgp_get_default(), bgp_path, + path); + } + } + + bgp_process(bgp_path, dest, path, afi, safi); + } + + if (peer) { + int valid_nexthops = bgp_isvalid_nexthop(bnc); + + if (valid_nexthops) { + /* + * Peering cannot occur across a blackhole nexthop + */ + if (bnc->nexthop_num == 1 && bnc->nexthop + && bnc->nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { + peer->last_reset = PEER_DOWN_WAITING_NHT; + valid_nexthops = 0; + } else + peer->last_reset = PEER_DOWN_WAITING_OPEN; + } else + peer->last_reset = PEER_DOWN_WAITING_NHT; + + if (!CHECK_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED)) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug( + "%s: Updating peer (%s(%s)) status with NHT nexthops %d", + __func__, peer->host, + peer->bgp->name_pretty, + !!valid_nexthops); + bgp_fsm_nht_update(peer->connection, peer, + !!valid_nexthops); + SET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); + } + } + + RESET_FLAG(bnc->change_flags); +} + +/** + * path_nh_map - make or break path-to-nexthop association. + * ARGUMENTS: + * path - pointer to the path structure + * bnc - pointer to the nexthop structure + * make - if set, make the association. if unset, just break the existing + * association. + */ +void path_nh_map(struct bgp_path_info *path, struct bgp_nexthop_cache *bnc, + bool make) +{ + if (path->nexthop) { + LIST_REMOVE(path, nh_thread); + path->nexthop->path_count--; + path->nexthop = NULL; + } + if (make) { + LIST_INSERT_HEAD(&(bnc->paths), path, nh_thread); + path->nexthop = bnc; + path->nexthop->path_count++; + } +} + +/* + * This function is called to register nexthops to zebra + * as that we may have tried to install the nexthops + * before we actually have a zebra connection + */ +void bgp_nht_register_nexthops(struct bgp *bgp) +{ + for (afi_t afi = AFI_IP; afi < AFI_MAX; afi++) { + struct bgp_nexthop_cache *bnc; + + frr_each (bgp_nexthop_cache, &bgp->nexthop_cache_table[afi], + bnc) { + register_zebra_rnh(bnc); + } + } +} + +void bgp_nht_reg_enhe_cap_intfs(struct peer *peer) +{ + struct bgp *bgp; + struct bgp_nexthop_cache *bnc; + struct nexthop *nhop; + struct interface *ifp; + struct prefix p; + ifindex_t ifindex = 0; + + if (peer->ifp) + return; + + bgp = peer->bgp; + if (!sockunion2hostprefix(&peer->connection->su, &p)) { + zlog_warn("%s: Unable to convert sockunion to prefix for %s", + __func__, peer->host); + return; + } + + if (p.family != AF_INET6) + return; + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (peer->conf_if && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + ifindex = peer->connection->su.sin6.sin6_scope_id; + + bnc = bnc_find(&bgp->nexthop_cache_table[AFI_IP6], &p, 0, ifindex); + if (!bnc) + return; + + if (peer != bnc->nht_info) + return; + + for (nhop = bnc->nexthop; nhop; nhop = nhop->next) { + ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + + if (!ifp) + continue; + + zclient_send_interface_radv_req(zclient, + nhop->vrf_id, + ifp, true, + BGP_UNNUM_DEFAULT_RA_INTERVAL); + } +} + +void bgp_nht_dereg_enhe_cap_intfs(struct peer *peer) +{ + struct bgp *bgp; + struct bgp_nexthop_cache *bnc; + struct nexthop *nhop; + struct interface *ifp; + struct prefix p; + ifindex_t ifindex = 0; + + if (peer->ifp) + return; + + bgp = peer->bgp; + + if (!sockunion2hostprefix(&peer->connection->su, &p)) { + zlog_warn("%s: Unable to convert sockunion to prefix for %s", + __func__, peer->host); + return; + } + + if (p.family != AF_INET6) + return; + /* + * Gather the ifindex for if up/down events to be + * tagged into this fun + */ + if (peer->conf_if && + IN6_IS_ADDR_LINKLOCAL(&peer->connection->su.sin6.sin6_addr)) + ifindex = peer->connection->su.sin6.sin6_scope_id; + + bnc = bnc_find(&bgp->nexthop_cache_table[AFI_IP6], &p, 0, ifindex); + if (!bnc) + return; + + if (peer != bnc->nht_info) + return; + + for (nhop = bnc->nexthop; nhop; nhop = nhop->next) { + ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + + if (!ifp) + continue; + + zclient_send_interface_radv_req(zclient, nhop->vrf_id, ifp, 0, + 0); + } +} diff --git a/bgpd/bgp_nht.h b/bgpd/bgp_nht.h new file mode 100644 index 0000000..e7c6fdc --- /dev/null +++ b/bgpd/bgp_nht.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Nexthop tracking + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#ifndef _BGP_NHT_H +#define _BGP_NHT_H + +/** + * bgp_nexthop_update() - process a nexthop update message from Zebra. + */ +extern void bgp_nexthop_update(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr); + +/** + * bgp_find_or_add_nexthop() - lookup the nexthop cache table for the bnc + * object. If not found, create a new object and register with ZEBRA for + * nexthop notification. + * ARGUMENTS: + * bgp_route - BGP instance of route + * bgp_nexthop - BGP instance of nexthop + * a - afi: AFI_IP or AF_IP6 + * safi - safi: to check which table nhs are being imported to + * p - path for which the nexthop object is being looked up + * peer - The BGP peer associated with this NHT + * connected - True if NH MUST be a connected route + */ +extern int bgp_find_or_add_nexthop(struct bgp *bgp_route, + struct bgp *bgp_nexthop, afi_t a, + safi_t safi, struct bgp_path_info *p, + struct peer *peer, int connected, + const struct prefix *orig_prefix); + +/** + * bgp_unlink_nexthop() - Unlink the nexthop object from the path structure. + * ARGUMENTS: + * p - path structure. + */ +extern void bgp_unlink_nexthop(struct bgp_path_info *p); +void bgp_unlink_nexthop_by_peer(struct peer *peer); +void bgp_replace_nexthop_by_peer(struct peer *from, struct peer *to); +/** + * bgp_delete_connected_nexthop() - Reset the 'peer' pointer for a connected + * nexthop entry. If no paths reference the nexthop, it will be unregistered + * and freed. + * ARGUMENTS: + * afi - afi: AFI_IP or AF_IP6 + * peer - Ptr to peer + */ +extern void bgp_delete_connected_nexthop(afi_t afi, struct peer *peer); + +/* + * Cleanup nexthop registration and status information for BGP nexthops + * pertaining to this VRF. This is invoked upon VRF deletion. + */ +extern void bgp_cleanup_nexthops(struct bgp *bgp); + +/* + * Add or remove the tracking of the bgp_path_info that + * uses this nexthop + */ +extern void path_nh_map(struct bgp_path_info *path, + struct bgp_nexthop_cache *bnc, bool make); +/* + * When we actually have the connection to + * the zebra daemon, we need to reregister + * any nexthops we may have sitting around + */ +extern void bgp_nht_register_nexthops(struct bgp *bgp); + +/* + * When we have the the PEER_FLAG_CAPABILITY_ENHE flag + * set on a peer *after* it has been brought up we need + * to notice and setup the interface based RA, + * this code can walk the registered nexthops and + * register the important ones with zebra for RA. + */ +extern void bgp_nht_reg_enhe_cap_intfs(struct peer *peer); +extern void bgp_nht_dereg_enhe_cap_intfs(struct peer *peer); +extern void evaluate_paths(struct bgp_nexthop_cache *bnc); + +extern void bgp_nht_ifp_up(struct interface *ifp); +extern void bgp_nht_ifp_down(struct interface *ifp); + +extern void bgp_nht_interface_events(struct peer *peer); +#endif /* _BGP_NHT_H */ diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c new file mode 100644 index 0000000..248d478 --- /dev/null +++ b/bgpd/bgp_open.c @@ -0,0 +1,2076 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP open message handling + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "linklist.h" +#include "prefix.h" +#include "stream.h" +#include "frrevent.h" +#include "log.h" +#include "command.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "lib/json.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_memory.h" + +const struct message capcode_str[] = { + { CAPABILITY_CODE_MP, "MultiProtocol Extensions" }, + { CAPABILITY_CODE_REFRESH, "Route Refresh" }, + { CAPABILITY_CODE_ORF, "Cooperative Route Filtering" }, + { CAPABILITY_CODE_RESTART, "Graceful Restart" }, + { CAPABILITY_CODE_AS4, "4-octet AS number" }, + { CAPABILITY_CODE_ADDPATH, "AddPath" }, + { CAPABILITY_CODE_DYNAMIC, "Dynamic" }, + { CAPABILITY_CODE_ENHE, "Extended Next Hop Encoding" }, + { CAPABILITY_CODE_FQDN, "FQDN" }, + { CAPABILITY_CODE_ENHANCED_RR, "Enhanced Route Refresh" }, + { CAPABILITY_CODE_EXT_MESSAGE, "BGP Extended Message" }, + { CAPABILITY_CODE_LLGR, "Long-lived BGP Graceful Restart" }, + { CAPABILITY_CODE_ROLE, "Role" }, + { CAPABILITY_CODE_SOFT_VERSION, "Software Version" }, + { CAPABILITY_CODE_PATHS_LIMIT, "Paths-Limit" }, + { 0 } +}; + +/* Minimum sizes for length field of each cap (so not inc. the header) */ +const size_t cap_minsizes[] = { + [CAPABILITY_CODE_MP] = CAPABILITY_CODE_MP_LEN, + [CAPABILITY_CODE_REFRESH] = CAPABILITY_CODE_REFRESH_LEN, + [CAPABILITY_CODE_ORF] = CAPABILITY_CODE_ORF_LEN, + [CAPABILITY_CODE_RESTART] = CAPABILITY_CODE_RESTART_LEN, + [CAPABILITY_CODE_AS4] = CAPABILITY_CODE_AS4_LEN, + [CAPABILITY_CODE_ADDPATH] = CAPABILITY_CODE_ADDPATH_LEN, + [CAPABILITY_CODE_DYNAMIC] = CAPABILITY_CODE_DYNAMIC_LEN, + [CAPABILITY_CODE_ENHE] = CAPABILITY_CODE_ENHE_LEN, + [CAPABILITY_CODE_FQDN] = CAPABILITY_CODE_MIN_FQDN_LEN, + [CAPABILITY_CODE_ENHANCED_RR] = CAPABILITY_CODE_ENHANCED_LEN, + [CAPABILITY_CODE_EXT_MESSAGE] = CAPABILITY_CODE_EXT_MESSAGE_LEN, + [CAPABILITY_CODE_LLGR] = CAPABILITY_CODE_LLGR_LEN, + [CAPABILITY_CODE_ROLE] = CAPABILITY_CODE_ROLE_LEN, + [CAPABILITY_CODE_SOFT_VERSION] = CAPABILITY_CODE_SOFT_VERSION_LEN, + [CAPABILITY_CODE_PATHS_LIMIT] = CAPABILITY_CODE_PATHS_LIMIT_LEN, +}; + +/* value the capability must be a multiple of. + * 0-data capabilities won't be checked against this. + * Other capabilities whose data doesn't fall on convenient boundaries for this + * table should be set to 1. + */ +const size_t cap_modsizes[] = { + [CAPABILITY_CODE_MP] = 4, + [CAPABILITY_CODE_REFRESH] = 1, + [CAPABILITY_CODE_ORF] = 1, + [CAPABILITY_CODE_RESTART] = 1, + [CAPABILITY_CODE_AS4] = 4, + [CAPABILITY_CODE_ADDPATH] = 4, + [CAPABILITY_CODE_DYNAMIC] = 1, + [CAPABILITY_CODE_ENHE] = 6, + [CAPABILITY_CODE_FQDN] = 1, + [CAPABILITY_CODE_ENHANCED_RR] = 1, + [CAPABILITY_CODE_EXT_MESSAGE] = 1, + [CAPABILITY_CODE_LLGR] = 1, + [CAPABILITY_CODE_ROLE] = 1, + [CAPABILITY_CODE_SOFT_VERSION] = 1, + [CAPABILITY_CODE_PATHS_LIMIT] = 5, +}; + +/* BGP-4 Multiprotocol Extentions lead us to the complex world. We can + negotiate remote peer supports extentions or not. But if + remote-peer doesn't supports negotiation process itself. We would + like to do manual configuration. + + So there is many configurable point. First of all we want set each + peer whether we send capability negotiation to the peer or not. + Next, if we send capability to the peer we want to set my capability + inforation at each peer. */ + +void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, + json_object *json_neigh) +{ + char *pnt; + char *end; + struct capability_mp_data mpc; + struct capability_header *hdr; + json_object *json_cap = NULL; + + if (use_json) + json_cap = json_object_new_object(); + + pnt = peer->notify.data; + end = pnt + peer->notify.length; + + while (pnt < end) { + if (pnt + sizeof(struct capability_mp_data) + 2 > end) + return; + + hdr = (struct capability_header *)pnt; + if (pnt + hdr->length + 2 > end) + return; + + memcpy(&mpc, pnt + 2, sizeof(struct capability_mp_data)); + + if (hdr->code == CAPABILITY_CODE_MP) { + afi_t afi; + safi_t safi; + + (void)bgp_map_afi_safi_iana2int(ntohs(mpc.afi), + mpc.safi, &afi, &safi); + + if (use_json) { + switch (afi) { + case AFI_IP: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolAfi", + "IPv4"); + break; + case AFI_IP6: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolAfi", + "IPv6"); + break; + case AFI_L2VPN: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolAfi", + "L2VPN"); + break; + case AFI_UNSPEC: + case AFI_MAX: + json_object_int_add( + json_cap, + "capabilityErrorMultiProtocolAfiUnknown", + ntohs(mpc.afi)); + break; + } + switch (safi) { + case SAFI_UNICAST: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "unicast"); + break; + case SAFI_MULTICAST: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "multicast"); + break; + case SAFI_LABELED_UNICAST: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "labeled-unicast"); + break; + case SAFI_MPLS_VPN: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "MPLS-labeled VPN"); + break; + case SAFI_ENCAP: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "encap"); + break; + case SAFI_EVPN: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "EVPN"); + break; + case SAFI_FLOWSPEC: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "flowspec"); + break; + case SAFI_UNSPEC: + case SAFI_MAX: + json_object_int_add( + json_cap, + "capabilityErrorMultiProtocolSafiUnknown", + mpc.safi); + break; + } + } else { + vty_out(vty, + " Capability error for: Multi protocol "); + switch (afi) { + case AFI_IP: + vty_out(vty, "AFI IPv4, "); + break; + case AFI_IP6: + vty_out(vty, "AFI IPv6, "); + break; + case AFI_L2VPN: + vty_out(vty, "AFI L2VPN, "); + break; + case AFI_UNSPEC: + case AFI_MAX: + vty_out(vty, "AFI Unknown %d, ", + ntohs(mpc.afi)); + break; + } + switch (safi) { + case SAFI_UNICAST: + vty_out(vty, "SAFI Unicast"); + break; + case SAFI_MULTICAST: + vty_out(vty, "SAFI Multicast"); + break; + case SAFI_LABELED_UNICAST: + vty_out(vty, "SAFI Labeled-unicast"); + break; + case SAFI_MPLS_VPN: + vty_out(vty, "SAFI MPLS-labeled VPN"); + break; + case SAFI_ENCAP: + vty_out(vty, "SAFI ENCAP"); + break; + case SAFI_FLOWSPEC: + vty_out(vty, "SAFI FLOWSPEC"); + break; + case SAFI_EVPN: + vty_out(vty, "SAFI EVPN"); + break; + case SAFI_UNSPEC: + case SAFI_MAX: + vty_out(vty, "SAFI Unknown %d ", + mpc.safi); + break; + } + vty_out(vty, "\n"); + } + } else if (hdr->code >= 128) { + if (use_json) + json_object_int_add( + json_cap, + "capabilityErrorVendorSpecificCapabilityCode", + hdr->code); + else + vty_out(vty, + " Capability error: vendor specific capability code %d", + hdr->code); + } else { + if (use_json) + json_object_int_add( + json_cap, + "capabilityErrorUnknownCapabilityCode", + hdr->code); + else + vty_out(vty, + " Capability error: unknown capability code %d", + hdr->code); + } + pnt += hdr->length + 2; + } + if (use_json) + json_object_object_add(json_neigh, "capabilityErrors", + json_cap); +} + +static void bgp_capability_mp_data(struct stream *s, + struct capability_mp_data *mpc) +{ + mpc->afi = stream_getw(s); + mpc->reserved = stream_getc(s); + mpc->safi = stream_getc(s); +} + +/* Set negotiated capability value. */ +static int bgp_capability_mp(struct peer *peer, struct capability_header *hdr) +{ + struct capability_mp_data mpc; + struct stream *s = BGP_INPUT(peer); + afi_t afi; + safi_t safi; + + /* Verify length is 4 */ + if (hdr->length != 4) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_LENGTH, + "MP Cap: Received invalid length %d, non-multiple of 4", + hdr->length); + return -1; + } + + bgp_capability_mp_data(s, &mpc); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s", + peer->host, lookup_msg(capcode_str, hdr->code, NULL), + iana_afi2str(mpc.afi), iana_safi2str(mpc.safi)); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(mpc.afi, mpc.safi, &afi, &safi)) + return -1; + + /* Now safi remapped, and afi/safi are valid array indices */ + peer->afc_recv[afi][safi] = 1; + + if (peer->afc[afi][safi]) + peer->afc_nego[afi][safi] = 1; + else + return -1; + + return 0; +} + +static void bgp_capability_orf_not_support(struct peer *peer, iana_afi_t afi, + iana_safi_t safi, uint8_t type, + uint8_t mode) +{ + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %d/%d has ORF type/mode %d/%d not supported", + peer->host, afi, safi, type, mode); +} + +const struct message orf_type_str[] = { { ORF_TYPE_RESERVED, "Reserved" }, + { ORF_TYPE_PREFIX, "Prefixlist" }, + { 0 } }; + +const struct message orf_mode_str[] = { { ORF_MODE_RECEIVE, "Receive" }, + { ORF_MODE_SEND, "Send" }, + { ORF_MODE_BOTH, "Both" }, + { 0 } }; + +static int bgp_capability_orf_entry(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + struct capability_mp_data mpc; + uint8_t num; + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + uint8_t type; + uint8_t mode; + uint16_t sm_cap = 0; /* capability send-mode receive */ + uint16_t rm_cap = 0; /* capability receive-mode receive */ + int i; + + /* ORF Entry header */ + bgp_capability_mp_data(s, &mpc); + num = stream_getc(s); + pkt_afi = mpc.afi; + pkt_safi = mpc.safi; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s ORF Cap entry for afi/safi: %s/%s", peer->host, + iana_afi2str(mpc.afi), iana_safi2str(mpc.safi)); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + zlog_info( + "%s Addr-family %d/%d not supported. Ignoring the ORF capability", + peer->host, pkt_afi, pkt_safi); + return 0; + } + + mpc.afi = pkt_afi; + mpc.safi = safi; + + /* validate number field */ + if (CAPABILITY_CODE_ORF_LEN + (num * 2) > hdr->length) { + zlog_info( + "%s ORF Capability entry length error, Cap length %u, num %u", + peer->host, hdr->length, num); + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + for (i = 0; i < num; i++) { + type = stream_getc(s); + mode = stream_getc(s); + + /* ORF Mode error check */ + switch (mode) { + case ORF_MODE_BOTH: + case ORF_MODE_SEND: + case ORF_MODE_RECEIVE: + break; + default: + bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, + type, mode); + continue; + } + /* ORF Type and afi/safi error checks */ + /* capcode versus type */ + switch (hdr->code) { + case CAPABILITY_CODE_ORF: + switch (type) { + case ORF_TYPE_RESERVED: + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %d/%d has reserved ORF type, ignoring", + peer->host, afi, safi); + break; + case ORF_TYPE_PREFIX: + break; + default: + bgp_capability_orf_not_support( + peer, pkt_afi, pkt_safi, type, mode); + continue; + } + break; + default: + bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, + type, mode); + continue; + } + + /* AFI vs SAFI */ + if (!((afi == AFI_IP && safi == SAFI_UNICAST) + || (afi == AFI_IP && safi == SAFI_MULTICAST) + || (afi == AFI_IP6 && safi == SAFI_UNICAST))) { + bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, + type, mode); + continue; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s OPEN has %s ORF capability as %s for afi/safi: %s/%s", + peer->host, + lookup_msg(orf_type_str, type, NULL), + lookup_msg(orf_mode_str, mode, NULL), + iana_afi2str(pkt_afi), iana_safi2str(pkt_safi)); + + if (hdr->code == CAPABILITY_CODE_ORF) { + sm_cap = PEER_CAP_ORF_PREFIX_SM_RCV; + rm_cap = PEER_CAP_ORF_PREFIX_RM_RCV; + } else { + bgp_capability_orf_not_support(peer, pkt_afi, pkt_safi, + type, mode); + continue; + } + + switch (mode) { + case ORF_MODE_BOTH: + SET_FLAG(peer->af_cap[afi][safi], sm_cap); + SET_FLAG(peer->af_cap[afi][safi], rm_cap); + break; + case ORF_MODE_SEND: + SET_FLAG(peer->af_cap[afi][safi], sm_cap); + break; + case ORF_MODE_RECEIVE: + SET_FLAG(peer->af_cap[afi][safi], rm_cap); + break; + } + } + return 0; +} + +static int bgp_capability_restart(struct peer *peer, + struct capability_header *caphdr) +{ + struct stream *s = BGP_INPUT(peer); + uint16_t restart_flag_time; + size_t end = stream_get_getp(s) + caphdr->length; + + /* Verify length is a multiple of 4 */ + if ((caphdr->length - 2) % 4) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_LENGTH, + "Restart Cap: Received invalid length %d, non-multiple of 4", + caphdr->length); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); + restart_flag_time = stream_getw(s); + + /* The most significant bit is defined in [RFC4724] as + * the Restart State ("R") bit. + */ + if (CHECK_FLAG(restart_flag_time, GRACEFUL_RESTART_R_BIT)) + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + else + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + + /* The second most significant bit is defined in this + * document as the Graceful Notification ("N") bit. + */ + if (CHECK_FLAG(restart_flag_time, GRACEFUL_RESTART_N_BIT)) + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + else + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + + UNSET_FLAG(restart_flag_time, 0xF000); + peer->v_gr_restart = restart_flag_time; + + if (bgp_debug_neighbor_events(peer)) { + zlog_debug( + "%s Peer has%srestarted. Restart Time: %d, N-bit set: %s", + peer->host, + CHECK_FLAG(peer->cap, + PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV) + ? " " + : " not ", + peer->v_gr_restart, + CHECK_FLAG(peer->cap, + PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV) + ? "yes" + : "no"); + } + + while (stream_get_getp(s) + 4 <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi = stream_getw(s); + iana_safi_t pkt_safi = stream_getc(s); + uint8_t flag = stream_getc(s); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not supported. Ignore the Graceful Restart capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Graceful Restart capability", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Address family %s is%spreserved", + peer->host, get_afi_safi_str(afi, safi, false), + CHECK_FLAG( + peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV) + ? " " + : " not "); + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV); + if (CHECK_FLAG(flag, GRACEFUL_RESTART_F_BIT)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV); + } + } + return 0; +} + +static int bgp_capability_llgr(struct peer *peer, + struct capability_header *caphdr) +{ + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + caphdr->length; + + SET_FLAG(peer->cap, PEER_CAP_LLGR_RCV); + + while (stream_get_getp(s) + BGP_CAP_LLGR_MIN_PACKET_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi = stream_getw(s); + iana_safi_t pkt_safi = stream_getc(s); + uint8_t flags = stream_getc(s); + uint32_t stale_time = stream_get3(s); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not supported. Ignore the Long-lived Graceful Restart capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else if (!peer->afc[afi][safi] + || !CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Long-lived Graceful Restart capability", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) Long-lived Graceful Restart capability stale time %u sec", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), stale_time); + + peer->llgr[afi][safi].flags = flags; + peer->llgr[afi][safi].stale_time = + MIN(stale_time, peer->bgp->llgr_stale_time); + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_LLGR_AF_RCV); + } + } + + return 0; +} + +/* Unlike other capability parsing routines, this one returns 0 on error */ +static as_t bgp_capability_as4(struct peer *peer, struct capability_header *hdr) +{ + if (hdr->length != CAPABILITY_CODE_AS4_LEN) { + flog_err(EC_BGP_PKT_OPEN, + "%s AS4 capability has incorrect data length %d", + peer->host, hdr->length); + return -1; + } + + as_t as4 = stream_getl(BGP_INPUT(peer)); + + SET_FLAG(peer->cap, PEER_CAP_AS4_RCV); + + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "%s [AS4] about to set cap PEER_CAP_AS4_RCV, got as4 %u", + peer->host, as4); + return as4; +} + +static int bgp_capability_ext_message(struct peer *peer, + struct capability_header *hdr) +{ + if (hdr->length != CAPABILITY_CODE_EXT_MESSAGE_LEN) { + flog_err( + EC_BGP_PKT_OPEN, + "%s: BGP Extended Message capability has incorrect data length %d", + peer->host, hdr->length); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_EXTENDED_MESSAGE_RCV); + + return 0; +} + +static int bgp_capability_addpath(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + hdr->length; + + /* Verify length is a multiple of 4 */ + if (hdr->length % CAPABILITY_CODE_ADDPATH_LEN) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_LENGTH, + "Add Path: Received invalid length %d, non-multiple of 4", + hdr->length); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV); + + while (stream_get_getp(s) + CAPABILITY_CODE_ADDPATH_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi = stream_getw(s); + iana_safi_t pkt_safi = stream_getc(s); + uint8_t send_receive = stream_getc(s); + + /* If any other value (other than 1-3) is received, then + * the capability SHOULD be treated as not understood + * and ignored. + */ + if (!send_receive || send_receive > 3) { + flog_warn(EC_BGP_CAPABILITY_INVALID_DATA, + "Add Path: Received invalid send/receive value %u in Add Path capability", + send_receive); + continue; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s%s%s", + peer->host, + lookup_msg(capcode_str, hdr->code, NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + CHECK_FLAG(send_receive, BGP_ADDPATH_RX) + ? ", receive" + : "", + CHECK_FLAG(send_receive, BGP_ADDPATH_TX) + ? ", transmit" + : ""); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not supported. Ignore the Addpath Attribute for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not enabled. Ignore the AddPath capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } + + if (CHECK_FLAG(send_receive, BGP_ADDPATH_RX)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV); + else + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV); + + if (CHECK_FLAG(send_receive, BGP_ADDPATH_TX)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV); + else + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV); + } + + return 0; +} + +static int bgp_capability_paths_limit(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + hdr->length; + + if (hdr->length % CAPABILITY_CODE_PATHS_LIMIT_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Paths-Limit: Received invalid length %d, non-multiple of %d", + hdr->length, CAPABILITY_CODE_PATHS_LIMIT_LEN); + return -1; + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV)) { + flog_warn(EC_BGP_CAPABILITY_INVALID_DATA, + "Paths-Limit: Received Paths-Limit capability without Add-Path capability"); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + + while (stream_get_getp(s) + CAPABILITY_CODE_PATHS_LIMIT_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi = stream_getw(s); + iana_safi_t pkt_safi = stream_getc(s); + uint16_t paths_limit = stream_getw(s); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s limit: %u", + peer->host, + lookup_msg(capcode_str, hdr->code, NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), paths_limit); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_PATHS_LIMIT_AF_RCV); + peer->addpath_paths_limit[afi][safi].receive = paths_limit; + } + + return 0; +} + +static int bgp_capability_enhe(struct peer *peer, struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + hdr->length; + + /* Verify length is a multiple of 4 */ + if (hdr->length % 6) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_LENGTH, + "Extended NH: Received invalid length %d, non-multiple of 6", + hdr->length); + return -1; + } + + while (stream_get_getp(s) + 6 <= end) { + iana_afi_t pkt_afi = stream_getw(s); + afi_t afi; + iana_safi_t pkt_safi = stream_getw(s); + safi_t safi; + iana_afi_t pkt_nh_afi = stream_getw(s); + afi_t nh_afi; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Received with afi/safi/next-hop afi: %s/%s/%u", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), pkt_nh_afi); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Addr-family %s/%s(afi/safi) not supported. Ignore the ENHE Attribute for this AFI/SAFI", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + continue; + } + + /* RFC 5549 specifies use of this capability only for IPv4 AFI, + * with + * the Nexthop AFI being IPv6. A future spec may introduce other + * possibilities, so we ignore other values with a log. Also, + * only + * SAFI_UNICAST and SAFI_LABELED_UNICAST are currently supported + * (and expected). + */ + nh_afi = afi_iana2int(pkt_nh_afi); + + if (afi != AFI_IP || nh_afi != AFI_IP6 + || !(safi == SAFI_UNICAST || safi == SAFI_MPLS_VPN + || safi == SAFI_LABELED_UNICAST)) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_DATA, + "%s Unexpected afi/safi/next-hop afi: %s/%s/%u in Extended Next-hop capability, ignoring", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), pkt_nh_afi); + continue; + } + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_RCV); + + if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_ADV)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ENHE_AF_NEGO); + } + + SET_FLAG(peer->cap, PEER_CAP_ENHE_RCV); + + return 0; +} + +static int bgp_capability_hostname(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + char str[BGP_MAX_HOSTNAME + 1]; + size_t end = stream_get_getp(s) + hdr->length; + uint8_t len; + + len = stream_getc(s); + if (stream_get_getp(s) + len > end) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_DATA, + "%s: Received malformed hostname capability from peer %s", + __func__, peer->host); + return -1; + } + + if (len > BGP_MAX_HOSTNAME) { + stream_get(str, s, BGP_MAX_HOSTNAME); + stream_forward_getp(s, len - BGP_MAX_HOSTNAME); + len = BGP_MAX_HOSTNAME; /* to set the '\0' below */ + } else if (len) + stream_get(str, s, len); + + if (len) { + str[len] = '\0'; + + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + + peer->hostname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); + } + + if (stream_get_getp(s) + 1 > end) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_DATA, + "%s: Received invalid domain name len (hostname capability) from peer %s", + __func__, peer->host); + return -1; + } + + len = stream_getc(s); + if (stream_get_getp(s) + len > end) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_DATA, + "%s: Received runt domain name (hostname capability) from peer %s", + __func__, peer->host); + return -1; + } + + if (len > BGP_MAX_HOSTNAME) { + stream_get(str, s, BGP_MAX_HOSTNAME); + stream_forward_getp(s, len - BGP_MAX_HOSTNAME); + len = BGP_MAX_HOSTNAME; /* to set the '\0' below */ + } else if (len) + stream_get(str, s, len); + + if (len) { + str[len] = '\0'; + + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + + peer->domainname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); + } + + SET_FLAG(peer->cap, PEER_CAP_HOSTNAME_RCV); + + if (bgp_debug_neighbor_events(peer)) { + zlog_debug("%s received hostname %s, domainname %s", peer->host, + peer->hostname, peer->domainname); + } + + return 0; +} + +static int bgp_capability_role(struct peer *peer, struct capability_header *hdr) +{ + if (hdr->length != CAPABILITY_CODE_ROLE_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Role: Received invalid length %d", hdr->length); + return -1; + } + + uint8_t role = stream_getc(BGP_INPUT(peer)); + + SET_FLAG(peer->cap, PEER_CAP_ROLE_RCV); + + peer->remote_role = role; + return 0; +} + +static int bgp_capability_software_version(struct peer *peer, + struct capability_header *hdr) +{ + struct stream *s = BGP_INPUT(peer); + char str[BGP_MAX_SOFT_VERSION + 1]; + size_t end = stream_get_getp(s) + hdr->length; + uint8_t len; + + len = stream_getc(s); + if (stream_get_getp(s) + len > end) { + flog_warn( + EC_BGP_CAPABILITY_INVALID_DATA, + "%s: Received malformed Software Version capability from peer %s", + __func__, peer->host); + return -1; + } + + SET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_RCV); + + if (len > BGP_MAX_SOFT_VERSION) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "%s: Received Software Version, but the length is too big, truncating, from peer %s", + __func__, peer->host); + stream_get(str, s, BGP_MAX_SOFT_VERSION); + stream_forward_getp(s, len - BGP_MAX_SOFT_VERSION); + len = BGP_MAX_SOFT_VERSION; + } else if (len) { + stream_get(str, s, len); + } + + if (len) { + str[len] = '\0'; + + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + + peer->soft_version = XSTRDUP(MTYPE_BGP_SOFT_VERSION, str); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s received Software Version: %s", + peer->host, peer->soft_version); + } + + return 0; +} + +/** + * Parse given capability. + * XXX: This is reading into a stream, but not using stream API + * + * @param[out] mp_capability Set to 1 on return iff one or more Multiprotocol + * capabilities were encountered. + */ +static int bgp_capability_parse(struct peer *peer, size_t length, + int *mp_capability, uint8_t **error) +{ + int ret; + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + length; + uint16_t restart_flag_time = 0; + + assert(STREAM_READABLE(s) >= length); + + while (stream_get_getp(s) < end) { + size_t start; + uint8_t *sp = stream_pnt(s); + struct capability_header caphdr; + + ret = 0; + /* We need at least capability code and capability length. */ + if (stream_get_getp(s) + 2 > end) { + zlog_info("%s Capability length error (< header)", + peer->host); + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + caphdr.code = stream_getc(s); + caphdr.length = stream_getc(s); + start = stream_get_getp(s); + + /* Capability length check sanity check. */ + if (start + caphdr.length > end) { + zlog_info("%s Capability length error (< length)", + peer->host); + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability (%u), length %u", + peer->host, + lookup_msg(capcode_str, caphdr.code, NULL), + caphdr.code, caphdr.length); + + /* Length sanity check, type-specific, for known capabilities */ + switch (caphdr.code) { + case CAPABILITY_CODE_MP: + case CAPABILITY_CODE_REFRESH: + case CAPABILITY_CODE_ORF: + case CAPABILITY_CODE_RESTART: + case CAPABILITY_CODE_AS4: + case CAPABILITY_CODE_ADDPATH: + case CAPABILITY_CODE_DYNAMIC: + case CAPABILITY_CODE_ENHE: + case CAPABILITY_CODE_FQDN: + case CAPABILITY_CODE_ENHANCED_RR: + case CAPABILITY_CODE_EXT_MESSAGE: + case CAPABILITY_CODE_ROLE: + case CAPABILITY_CODE_SOFT_VERSION: + case CAPABILITY_CODE_PATHS_LIMIT: + /* Check length. */ + if (caphdr.length < cap_minsizes[caphdr.code]) { + zlog_info( + "%s %s Capability length error: got %u, expected at least %u", + peer->host, + lookup_msg(capcode_str, caphdr.code, + NULL), + caphdr.length, + (unsigned)cap_minsizes[caphdr.code]); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + if (caphdr.length + && caphdr.length % cap_modsizes[caphdr.code] != 0) { + zlog_info( + "%s %s Capability length error: got %u, expected a multiple of %u", + peer->host, + lookup_msg(capcode_str, caphdr.code, + NULL), + caphdr.length, + (unsigned)cap_modsizes[caphdr.code]); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + break; + /* we deliberately ignore unknown codes, see below */ + default: + break; + } + + switch (caphdr.code) { + case CAPABILITY_CODE_MP: { + *mp_capability = 1; + + /* Ignore capability when override-capability is set. */ + if (!CHECK_FLAG(peer->flags, + PEER_FLAG_OVERRIDE_CAPABILITY)) { + /* Set negotiated value. */ + ret = bgp_capability_mp(peer, &caphdr); + + /* Unsupported Capability. */ + if (ret < 0) { + /* Store return data. */ + memcpy(*error, sp, caphdr.length + 2); + *error += caphdr.length + 2; + } + ret = 0; /* Don't return error for this */ + } + } break; + case CAPABILITY_CODE_ENHANCED_RR: + case CAPABILITY_CODE_REFRESH: { + /* BGP refresh capability */ + if (caphdr.code == CAPABILITY_CODE_ENHANCED_RR) + SET_FLAG(peer->cap, PEER_CAP_ENHANCED_RR_RCV); + else + SET_FLAG(peer->cap, PEER_CAP_REFRESH_RCV); + } break; + case CAPABILITY_CODE_ORF: + ret = bgp_capability_orf_entry(peer, &caphdr); + break; + case CAPABILITY_CODE_RESTART: + ret = bgp_capability_restart(peer, &caphdr); + break; + case CAPABILITY_CODE_LLGR: + ret = bgp_capability_llgr(peer, &caphdr); + break; + case CAPABILITY_CODE_DYNAMIC: + SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV); + break; + case CAPABILITY_CODE_AS4: + /* Already handled as a special-case parsing of the + * capabilities + * at the beginning of OPEN processing. So we care not a + * jot + * for the value really, only error case. + */ + if (!bgp_capability_as4(peer, &caphdr)) + ret = -1; + break; + case CAPABILITY_CODE_ADDPATH: + ret = bgp_capability_addpath(peer, &caphdr); + break; + case CAPABILITY_CODE_ENHE: + ret = bgp_capability_enhe(peer, &caphdr); + break; + case CAPABILITY_CODE_EXT_MESSAGE: + ret = bgp_capability_ext_message(peer, &caphdr); + break; + case CAPABILITY_CODE_FQDN: + ret = bgp_capability_hostname(peer, &caphdr); + break; + case CAPABILITY_CODE_ROLE: + ret = bgp_capability_role(peer, &caphdr); + break; + case CAPABILITY_CODE_SOFT_VERSION: + ret = bgp_capability_software_version(peer, &caphdr); + break; + case CAPABILITY_CODE_PATHS_LIMIT: + ret = bgp_capability_paths_limit(peer, &caphdr); + break; + default: + if (caphdr.code > 128) { + /* We don't send Notification for unknown vendor + specific + capabilities. It seems reasonable for now... + */ + flog_warn(EC_BGP_CAPABILITY_VENDOR, + "%s Vendor specific capability %d", + peer->host, caphdr.code); + } else { + flog_warn( + EC_BGP_CAPABILITY_UNKNOWN, + "%s unrecognized capability code: %d - ignored", + peer->host, caphdr.code); + memcpy(*error, sp, caphdr.length + 2); + *error += caphdr.length + 2; + } + } + + if (ret < 0) { + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + if (stream_get_getp(s) != (start + caphdr.length)) { + if (stream_get_getp(s) > (start + caphdr.length)) + flog_warn( + EC_BGP_CAPABILITY_INVALID_LENGTH, + "%s Cap-parser for %s read past cap-length, %u!", + peer->host, + lookup_msg(capcode_str, caphdr.code, + NULL), + caphdr.length); + stream_set_getp(s, start + caphdr.length); + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + UNSET_FLAG(restart_flag_time, 0xF000); + peer->v_gr_restart = restart_flag_time; + } + } + return 0; +} + +static bool strict_capability_same(struct peer *peer) +{ + int i, j; + + for (i = AFI_IP; i < AFI_MAX; i++) + for (j = SAFI_UNICAST; j < SAFI_MAX; j++) + if (peer->afc[i][j] != peer->afc_nego[i][j]) + return false; + return true; +} + + +static bool bgp_role_violation(struct peer *peer) +{ + uint8_t local_role = peer->local_role; + uint8_t remote_role = peer->remote_role; + + if (local_role != ROLE_UNDEFINED && remote_role != ROLE_UNDEFINED && + !((local_role == ROLE_PEER && remote_role == ROLE_PEER) || + (local_role == ROLE_PROVIDER && remote_role == ROLE_CUSTOMER) || + (local_role == ROLE_CUSTOMER && remote_role == ROLE_PROVIDER) || + (local_role == ROLE_RS_SERVER && remote_role == ROLE_RS_CLIENT) || + (local_role == ROLE_RS_CLIENT && + remote_role == ROLE_RS_SERVER))) { + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_ROLE_MISMATCH); + return true; + } + if (remote_role == ROLE_UNDEFINED && + CHECK_FLAG(peer->flags, PEER_FLAG_ROLE_STRICT_MODE)) { + const char *err_msg = + "Strict mode. Please set the role on your side."; + bgp_notify_send_with_data(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_ROLE_MISMATCH, + (uint8_t *)err_msg, strlen(err_msg)); + return true; + } + return false; +} + + +/* peek into option, stores ASN to *as4 if the AS4 capability was found. + * Returns 0 if no as4 found, as4cap value otherwise. + */ +as_t peek_for_as4_capability(struct peer *peer, uint16_t length) +{ + struct stream *s = BGP_INPUT(peer); + size_t orig_getp = stream_get_getp(s); + size_t end = orig_getp + length; + as_t as4 = 0; + + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "%s [AS4] rcv OPEN w/ OPTION parameter len: %u, peeking for as4", + peer->host, length); + /* the error cases we DONT handle, we ONLY try to read as4 out of + * correctly formatted options. + */ + while (stream_get_getp(s) < end) { + uint8_t opt_type; + uint16_t opt_length; + + /* Ensure we can read the option type */ + if (stream_get_getp(s) + 1 > end) + goto end; + + /* Fetch the option type */ + opt_type = stream_getc(s); + + /* + * Check the length and fetch the opt_length + * If the peer is BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(peer) + * then we do a getw which is 2 bytes. So we need to + * ensure that we can read that as well + */ + if (BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(peer)) { + if (stream_get_getp(s) + 2 > end) + goto end; + + opt_length = stream_getw(s); + } else { + if (stream_get_getp(s) + 1 > end) + goto end; + + opt_length = stream_getc(s); + } + + /* Option length check. */ + if (stream_get_getp(s) + opt_length > end) + goto end; + + if (opt_type == BGP_OPEN_OPT_CAP) { + unsigned long capd_start = stream_get_getp(s); + unsigned long capd_end = capd_start + opt_length; + + assert(capd_end <= end); + + while (stream_get_getp(s) < capd_end) { + struct capability_header hdr; + + if (stream_get_getp(s) + 2 > capd_end) + goto end; + + hdr.code = stream_getc(s); + hdr.length = stream_getc(s); + + if ((stream_get_getp(s) + hdr.length) + > capd_end) + goto end; + + if (hdr.code == CAPABILITY_CODE_AS4) { + if (BGP_DEBUG(as4, AS4)) + zlog_debug( + "[AS4] found AS4 capability, about to parse"); + as4 = bgp_capability_as4(peer, &hdr); + + goto end; + } + stream_forward_getp(s, hdr.length); + } + } + } + +end: + stream_set_getp(s, orig_getp); + return as4; +} + +/** + * Parse open option. + * + * @param[out] mp_capability @see bgp_capability_parse() for semantics. + */ +int bgp_open_option_parse(struct peer *peer, uint16_t length, + int *mp_capability) +{ + int ret = 0; + uint8_t *error; + uint8_t error_data[BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE]; + struct stream *s = BGP_INPUT(peer); + size_t end = stream_get_getp(s) + length; + + error = error_data; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s rcv OPEN w/ OPTION parameter len: %u", + peer->host, length); + + /* Unset any previously received GR capability. */ + UNSET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); + + while (stream_get_getp(s) < end) { + uint8_t opt_type; + uint16_t opt_length; + + /* + * Check that we can read the opt_type and fetch it + */ + if (STREAM_READABLE(s) < 1) { + zlog_info("%s Option length error", peer->host); + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + opt_type = stream_getc(s); + + /* + * Check the length of the stream to ensure that + * FRR can properly read the opt_length. Then read it + */ + if (BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(peer)) { + if (STREAM_READABLE(s) < 2) { + zlog_info("%s Option length error", peer->host); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + opt_length = stream_getw(s); + } else { + if (STREAM_READABLE(s) < 1) { + zlog_info("%s Option length error", peer->host); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + opt_length = stream_getc(s); + } + + /* Option length check. */ + if (STREAM_READABLE(s) < opt_length) { + zlog_info("%s Option length error (%d)", peer->host, + opt_length); + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return -1; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s rcvd OPEN w/ optional parameter type %u (%s) len %u", + peer->host, opt_type, + opt_type == BGP_OPEN_OPT_CAP ? "Capability" + : "Unknown", + opt_length); + + switch (opt_type) { + case BGP_OPEN_OPT_CAP: + ret = bgp_capability_parse(peer, opt_length, + mp_capability, &error); + break; + default: + bgp_notify_send(peer->connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNSUP_PARAM); + ret = -1; + break; + } + + /* Parse error. To accumulate all unsupported capability codes, + bgp_capability_parse does not return -1 when encounter + unsupported capability code. To detect that, please check + error and erro_data pointer, like below. */ + if (ret < 0) + return -1; + } + + /* All OPEN option is parsed. Check capability when strict compare + flag is enabled.*/ + if (CHECK_FLAG(peer->flags, PEER_FLAG_STRICT_CAP_MATCH)) { + /* If Unsupported Capability exists or local capability does + * not negotiated with remote peer + */ + if (error != error_data || !strict_capability_same(peer)) { + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNSUP_CAPBL, + error_data, + error - error_data); + return -1; + } + } + + /* Extended Message Support */ + peer->max_packet_size = + (CHECK_FLAG(peer->cap, PEER_CAP_EXTENDED_MESSAGE_RCV) + && CHECK_FLAG(peer->cap, PEER_CAP_EXTENDED_MESSAGE_ADV)) + ? BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE + : BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE; + + /* Check that roles are corresponding to each other */ + if (bgp_role_violation(peer)) + return -1; + + /* Check there are no common AFI/SAFIs and send Unsupported Capability + error. */ + if (*mp_capability + && !CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { + if (!peer->afc_nego[AFI_IP][SAFI_UNICAST] + && !peer->afc_nego[AFI_IP][SAFI_MULTICAST] + && !peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] + && !peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] + && !peer->afc_nego[AFI_IP][SAFI_ENCAP] + && !peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] + && !peer->afc_nego[AFI_IP6][SAFI_UNICAST] + && !peer->afc_nego[AFI_IP6][SAFI_MULTICAST] + && !peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] + && !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] + && !peer->afc_nego[AFI_IP6][SAFI_ENCAP] + && !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] + && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) { + flog_err(EC_BGP_PKT_OPEN, + "%s [Error] Configured AFI/SAFIs do not overlap with received MP capabilities", + peer->host); + + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNSUP_CAPBL, + error_data, + error - error_data); + } + } + return 0; +} + +static void bgp_open_capability_orf(struct stream *s, struct peer *peer, + afi_t afi, safi_t safi, uint8_t code, + bool ext_opt_params) +{ + uint16_t cap_len; + uint8_t orf_len; + unsigned long capp; + unsigned long orfp; + unsigned long numberp; + int number_of_orfs = 0; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putc(s, BGP_OPEN_OPT_CAP); + capp = stream_get_endp(s); /* Set Capability Len Pointer */ + ext_opt_params ? stream_putw(s, 0) + : stream_putc(s, 0); /* Capability Length */ + stream_putc(s, code); /* Capability Code */ + orfp = stream_get_endp(s); /* Set ORF Len Pointer */ + stream_putc(s, 0); /* ORF Length */ + stream_putw(s, pkt_afi); + stream_putc(s, 0); + stream_putc(s, pkt_safi); + numberp = stream_get_endp(s); /* Set Number Pointer */ + stream_putc(s, 0); /* Number of ORFs */ + + /* Address Prefix ORF */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_SM) + || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ORF_PREFIX_RM)) { + stream_putc(s, ORF_TYPE_PREFIX); + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM) + && CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_RM)) { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + stream_putc(s, ORF_MODE_BOTH); + } else if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM)) { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + stream_putc(s, ORF_MODE_SEND); + } else { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + stream_putc(s, ORF_MODE_RECEIVE); + } + number_of_orfs++; + } + + /* Total Number of ORFs. */ + stream_putc_at(s, numberp, number_of_orfs); + + /* Total ORF Len. */ + orf_len = stream_get_endp(s) - orfp - 1; + stream_putc_at(s, orfp, orf_len); + + /* Total Capability Len. */ + cap_len = stream_get_endp(s) - capp - 1; + ext_opt_params ? stream_putw_at(s, capp, cap_len) + : stream_putc_at(s, capp, cap_len); +} + +static void bgp_peer_send_gr_capability(struct stream *s, struct peer *peer, + bool ext_opt_params) +{ + int len; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + afi_t afi; + safi_t safi; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + uint32_t restart_time; + unsigned long capp = 0; + unsigned long rcapp = 0; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART) + && !CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART_HELPER)) + return; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] Sending helper Capability for Peer :%s :", + peer->host); + + SET_FLAG(peer->cap, PEER_CAP_RESTART_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + capp = stream_get_endp(s); /* Set Capability Len Pointer */ + ext_opt_params ? stream_putw(s, 0) + : stream_putc(s, 0); /* Capability Length */ + stream_putc(s, CAPABILITY_CODE_RESTART); + /* Set Restart Capability Len Pointer */ + rcapp = stream_get_endp(s); + stream_putc(s, 0); + restart_time = peer->bgp->restart_time; + if (peer->bgp->t_startup) { + SET_FLAG(restart_time, GRACEFUL_RESTART_R_BIT); + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_ADV); + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] Sending R-Bit for peer: %s", + peer->host); + } + + if (CHECK_FLAG(peer->bgp->flags, BGP_FLAG_GRACEFUL_NOTIFICATION)) { + SET_FLAG(restart_time, GRACEFUL_RESTART_N_BIT); + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_ADV); + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] Sending N-Bit for peer: %s", + peer->host); + } + + stream_putw(s, restart_time); + + /* Send address-family specific graceful-restart capability + * only when GR config is present + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) { + if (CHECK_FLAG(peer->bgp->flags, BGP_FLAG_GR_PRESERVE_FWD) + && BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] F bit Set"); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] Sending GR Capability for AFI :%d :, SAFI :%d:", + afi, safi); + + /* Convert AFI, SAFI to values for + * packet. + */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + if (CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_GR_PRESERVE_FWD)) + stream_putc(s, GRACEFUL_RESTART_F_BIT); + else + stream_putc(s, 0); + } + } + + /* Total Graceful restart capability Len. */ + len = stream_get_endp(s) - rcapp - 1; + stream_putc_at(s, rcapp, len); + + /* Total Capability Len. */ + len = stream_get_endp(s) - capp - 1; + ext_opt_params ? stream_putw_at(s, capp, len - 1) + : stream_putc_at(s, capp, len); +} + +static void bgp_peer_send_llgr_capability(struct stream *s, struct peer *peer, + bool ext_opt_params) +{ + int len; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + afi_t afi; + safi_t safi; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + unsigned long capp = 0; + unsigned long rcapp = 0; + + if (!CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV)) + return; + + SET_FLAG(peer->cap, PEER_CAP_LLGR_ADV); + + stream_putc(s, BGP_OPEN_OPT_CAP); + capp = stream_get_endp(s); /* Set Capability Len Pointer */ + ext_opt_params ? stream_putw(s, 0) + : stream_putc(s, 0); /* Capability Length */ + stream_putc(s, CAPABILITY_CODE_LLGR); + + rcapp = stream_get_endp(s); + stream_putc(s, 0); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putc(s, LLGR_F_BIT); + stream_put3(s, peer->bgp->llgr_stale_time); + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_LLGR_AF_ADV); + } + + /* Total Long-lived Graceful Restart capability Len. */ + len = stream_get_endp(s) - rcapp - 1; + stream_putc_at(s, rcapp, len); + + /* Total Capability Len. */ + len = stream_get_endp(s) - capp - 1; + ext_opt_params ? stream_putw_at(s, capp, len - 1) + : stream_putc_at(s, capp, len); +} + +/* Fill in capability open option to the packet. */ +uint16_t bgp_open_capability(struct stream *s, struct peer *peer, + bool ext_opt_params) +{ + uint16_t len; + unsigned long cp, capp, rcapp, eopl = 0; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + afi_t afi; + safi_t safi; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + as_t local_as; + uint8_t afi_safi_count = 0; + bool adv_addpath_tx = false; + + /* Non-Ext OP Len. */ + cp = stream_get_endp(s); + stream_putc(s, 0); + + if (ext_opt_params) { + /* Non-Ext OP Len. */ + stream_putc_at(s, cp, BGP_OPEN_NON_EXT_OPT_LEN); + + /* Non-Ext OP Type */ + stream_putc(s, BGP_OPEN_NON_EXT_OPT_TYPE_EXTENDED_LENGTH); + + /* Extended Opt. Parm. Length */ + eopl = stream_get_endp(s); + stream_putw(s, 0); + } + + /* Do not send capability. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN) + || CHECK_FLAG(peer->flags, PEER_FLAG_DONT_CAPABILITY)) + return 0; + + /* MP capability for configured AFI, SAFI */ + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc[afi][safi]) { + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + peer->afc_adv[afi][safi] = 1; + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params + ? stream_putw(s, CAPABILITY_CODE_MP_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_MP_LEN + 2); + stream_putc(s, CAPABILITY_CODE_MP); + stream_putc(s, CAPABILITY_CODE_MP_LEN); + stream_putw(s, pkt_afi); + stream_putc(s, 0); + stream_putc(s, pkt_safi); + + /* Extended nexthop capability - currently + * supporting RFC-5549 for + * Link-Local peering only + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE) && + peer->connection->su.sa.sa_family == AF_INET6 && + afi == AFI_IP && + (safi == SAFI_UNICAST || safi == SAFI_MPLS_VPN || + safi == SAFI_LABELED_UNICAST)) { + /* RFC 5549 Extended Next Hop Encoding + */ + SET_FLAG(peer->cap, PEER_CAP_ENHE_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params + ? stream_putw(s, + CAPABILITY_CODE_ENHE_LEN + + 2) + : stream_putc(s, + CAPABILITY_CODE_ENHE_LEN + + 2); + stream_putc(s, CAPABILITY_CODE_ENHE); + stream_putc(s, CAPABILITY_CODE_ENHE_LEN); + + SET_FLAG(peer->af_cap[AFI_IP][safi], + PEER_CAP_ENHE_AF_ADV); + stream_putw(s, pkt_afi); + stream_putw(s, pkt_safi); + stream_putw(s, afi_int2iana(AFI_IP6)); + + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ENHE_AF_RCV)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ENHE_AF_NEGO); + } + } + } + + /* Route refresh. */ + SET_FLAG(peer->cap, PEER_CAP_REFRESH_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, CAPABILITY_CODE_REFRESH_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_REFRESH_LEN + 2); + stream_putc(s, CAPABILITY_CODE_REFRESH); + stream_putc(s, CAPABILITY_CODE_REFRESH_LEN); + + /* Enhanced Route Refresh. */ + SET_FLAG(peer->cap, PEER_CAP_ENHANCED_RR_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, CAPABILITY_CODE_ENHANCED_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_ENHANCED_LEN + 2); + stream_putc(s, CAPABILITY_CODE_ENHANCED_RR); + stream_putc(s, CAPABILITY_CODE_ENHANCED_LEN); + + /* AS4 */ + SET_FLAG(peer->cap, PEER_CAP_AS4_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, CAPABILITY_CODE_AS4_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_AS4_LEN + 2); + stream_putc(s, CAPABILITY_CODE_AS4); + stream_putc(s, CAPABILITY_CODE_AS4_LEN); + if (peer->change_local_as) + local_as = peer->change_local_as; + else + local_as = peer->local_as; + stream_putl(s, local_as); + + /* Extended Message Support */ + SET_FLAG(peer->cap, PEER_CAP_EXTENDED_MESSAGE_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, CAPABILITY_CODE_EXT_MESSAGE_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_EXT_MESSAGE_LEN + 2); + stream_putc(s, CAPABILITY_CODE_EXT_MESSAGE); + stream_putc(s, CAPABILITY_CODE_EXT_MESSAGE_LEN); + + /* Role*/ + if (peer->local_role != ROLE_UNDEFINED) { + SET_FLAG(peer->cap, PEER_CAP_ROLE_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + stream_putc(s, CAPABILITY_CODE_ROLE_LEN + 2); + stream_putc(s, CAPABILITY_CODE_ROLE); + stream_putc(s, CAPABILITY_CODE_ROLE_LEN); + stream_putc(s, peer->local_role); + } + + /* AddPath */ + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc[afi][safi]) { + afi_safi_count++; + + /* Only advertise addpath TX if a feature that + * will use it is + * configured */ + if (peer->addpath_type[afi][safi] != BGP_ADDPATH_NONE) + adv_addpath_tx = true; + + /* If we have enabled labeled unicast, we MUST check + * against unicast SAFI because addpath IDs are + * allocated under unicast SAFI, the same as the RIB + * is managed in unicast SAFI. + */ + if (safi == SAFI_LABELED_UNICAST) + if (peer->addpath_type[afi][SAFI_UNICAST] != + BGP_ADDPATH_NONE) + adv_addpath_tx = true; + } + } + + SET_FLAG(peer->cap, PEER_CAP_ADDPATH_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params + ? stream_putw(s, (CAPABILITY_CODE_ADDPATH_LEN * afi_safi_count) + + 2) + : stream_putc(s, (CAPABILITY_CODE_ADDPATH_LEN * afi_safi_count) + + 2); + stream_putc(s, CAPABILITY_CODE_ADDPATH); + stream_putc(s, CAPABILITY_CODE_ADDPATH_LEN * afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc[afi][safi]) { + bool adv_addpath_rx = + !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DISABLE_ADDPATH_RX); + uint8_t flags = 0; + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + + if (adv_addpath_rx) { + SET_FLAG(flags, BGP_ADDPATH_RX); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV); + } else { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV); + } + + if (adv_addpath_tx) { + SET_FLAG(flags, BGP_ADDPATH_TX); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV); + if (safi == SAFI_LABELED_UNICAST) + SET_FLAG( + peer->af_cap[afi][SAFI_UNICAST], + PEER_CAP_ADDPATH_AF_TX_ADV); + } else { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV); + } + + stream_putc(s, flags); + } + } + + /* Paths-Limit capability */ + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params ? stream_putw(s, (CAPABILITY_CODE_PATHS_LIMIT_LEN * + afi_safi_count) + + 2) + : stream_putc(s, (CAPABILITY_CODE_PATHS_LIMIT_LEN * + afi_safi_count) + + 2); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT_LEN * afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putw(s, peer->addpath_paths_limit[afi][safi].send); + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_PATHS_LIMIT_AF_ADV); + } + + /* ORF capability. */ + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM) + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_RM)) { + bgp_open_capability_orf(s, peer, afi, safi, + CAPABILITY_CODE_ORF, + ext_opt_params); + } + } + + /* Dynamic capability. */ + if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) { + SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + ext_opt_params + ? stream_putw(s, CAPABILITY_CODE_DYNAMIC_LEN + 2) + : stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN + 2); + stream_putc(s, CAPABILITY_CODE_DYNAMIC); + stream_putc(s, CAPABILITY_CODE_DYNAMIC_LEN); + } + + /* FQDN capability */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_FQDN) + && cmd_hostname_get()) { + SET_FLAG(peer->cap, PEER_CAP_HOSTNAME_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + rcapp = stream_get_endp(s); /* Ptr to length placeholder */ + ext_opt_params ? stream_putw(s, 0) + : stream_putc(s, 0); /* Capability Length */ + stream_putc(s, CAPABILITY_CODE_FQDN); + capp = stream_get_endp(s); + stream_putc(s, 0); /* dummy len for now */ + len = strlen(cmd_hostname_get()); + if (len > BGP_MAX_HOSTNAME) + len = BGP_MAX_HOSTNAME; + + stream_putc(s, len); + stream_put(s, cmd_hostname_get(), len); + if (cmd_domainname_get()) { + len = strlen(cmd_domainname_get()); + if (len > BGP_MAX_HOSTNAME) + len = BGP_MAX_HOSTNAME; + + stream_putc(s, len); + stream_put(s, cmd_domainname_get(), len); + } else + stream_putc(s, 0); /* 0 length */ + + /* Set the lengths straight */ + len = stream_get_endp(s) - rcapp - 1; + ext_opt_params ? stream_putw_at(s, rcapp, len - 1) + : stream_putc_at(s, rcapp, len); + + len = stream_get_endp(s) - capp - 1; + stream_putc_at(s, capp, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Sending hostname cap with hn = %s, dn = %s", + peer->host, cmd_hostname_get(), + cmd_domainname_get()); + } + + bgp_peer_send_gr_capability(s, peer, ext_opt_params); + bgp_peer_send_llgr_capability(s, peer, ext_opt_params); + + /* Software Version capability + * An implementation is REQUIRED Extended Optional Parameters + * Length for BGP OPEN Message support as defined in [RFC9072]. + * The inclusion of the Software Version Capability is OPTIONAL. + * If an implementation supports the inclusion of the capability, + * the implementation MUST include a configuration switch to enable + * or disable its use, and that switch MUST be off by default. + */ + if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_SOFT_VERSION) || + peer->sort == BGP_PEER_IBGP || peer->sub_sort == BGP_PEER_EBGP_OAD) { + SET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_ADV); + stream_putc(s, BGP_OPEN_OPT_CAP); + rcapp = stream_get_endp(s); + ext_opt_params ? stream_putw(s, 0) + : stream_putc(s, 0); /* Capability Length */ + stream_putc(s, CAPABILITY_CODE_SOFT_VERSION); + capp = stream_get_endp(s); + stream_putc(s, 0); /* dummy placeholder len */ + + /* The Capability Length SHOULD be no greater than 64. + * This is the limit to allow other capabilities as much + * space as they require. + */ + len = strlen(cmd_software_version_get()); + if (len > BGP_MAX_SOFT_VERSION) + len = BGP_MAX_SOFT_VERSION; + + stream_putc(s, len); + stream_put(s, cmd_software_version_get(), len); + + /* Software Version capability Len. */ + len = stream_get_endp(s) - rcapp - 1; + ext_opt_params ? stream_putw_at(s, rcapp, len - 1) + : stream_putc_at(s, rcapp, len); + + /* Total Capability Len. */ + len = stream_get_endp(s) - capp - 1; + stream_putc_at(s, capp, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Sending Software Version cap, value: %s", + peer->host, cmd_software_version_get()); + } + + /* Total Opt Parm Len. */ + len = stream_get_endp(s) - cp - 1; + + if (ext_opt_params) { + len = stream_get_endp(s) - eopl - 2; + stream_putw_at(s, eopl, len); + } else { + stream_putc_at(s, cp, len); + } + + return len; +} diff --git a/bgpd/bgp_open.h b/bgpd/bgp_open.h new file mode 100644 index 0000000..3a8cba9 --- /dev/null +++ b/bgpd/bgp_open.h @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP open message handling + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_OPEN_H +#define _QUAGGA_BGP_OPEN_H + +/* Standard header for capability TLV */ +struct capability_header { + uint8_t code; + uint8_t length; +}; + +/* Generic MP capability data */ +struct capability_mp_data { + uint16_t afi; /* iana_afi_t */ + uint8_t reserved; + uint8_t safi; /* iana_safi_t */ +}; + +struct graceful_restart_af { + uint16_t afi; + uint8_t safi; + uint8_t flag; +}; + +/* + * +--------------------------------------------------+ + * | Address Family Identifier (16 bits) | + * +--------------------------------------------------+ + * | Subsequent Address Family Identifier (8 bits) | + * +--------------------------------------------------+ + * | Flags for Address Family (8 bits) | + * +--------------------------------------------------+ + * | Long-lived Stale Time (24 bits) | + * +--------------------------------------------------+ + */ +#define BGP_CAP_LLGR_MIN_PACKET_LEN 7 + +/* Capability Code */ +#define CAPABILITY_CODE_MP 1 /* Multiprotocol Extensions */ +#define CAPABILITY_CODE_REFRESH 2 /* Route Refresh Capability */ +#define CAPABILITY_CODE_ORF 3 /* Cooperative Route Filtering Capability */ +#define CAPABILITY_CODE_RESTART 64 /* Graceful Restart Capability */ +#define CAPABILITY_CODE_AS4 65 /* 4-octet AS number Capability */ +#define CAPABILITY_CODE_DYNAMIC 67 /* Dynamic Capability */ +#define CAPABILITY_CODE_ADDPATH 69 /* Addpath Capability */ +#define CAPABILITY_CODE_ENHANCED_RR 70 /* Enhanced Route Refresh capability */ +#define CAPABILITY_CODE_LLGR 71 /* Long-lived Graceful Restart */ +#define CAPABILITY_CODE_FQDN 73 /* Advertise hostname capability */ +#define CAPABILITY_CODE_SOFT_VERSION 75 /* Software Version capability */ +#define CAPABILITY_CODE_ENHE 5 /* Extended Next Hop Encoding */ +#define CAPABILITY_CODE_EXT_MESSAGE 6 /* Extended Message Support */ +#define CAPABILITY_CODE_ROLE 9 /* Role Capability */ +#define CAPABILITY_CODE_PATHS_LIMIT 76 /* Paths Limit Capability */ + +/* Capability Length */ +#define CAPABILITY_CODE_MP_LEN 4 +#define CAPABILITY_CODE_REFRESH_LEN 0 +#define CAPABILITY_CODE_DYNAMIC_LEN 0 +#define CAPABILITY_CODE_RESTART_LEN 2 /* Receiving only case */ +#define CAPABILITY_CODE_AS4_LEN 4 +#define CAPABILITY_CODE_ADDPATH_LEN 4 +#define CAPABILITY_CODE_PATHS_LIMIT_LEN 5 +#define CAPABILITY_CODE_ENHE_LEN 6 /* NRLI AFI = 2, SAFI = 2, Nexthop AFI = 2 */ +#define CAPABILITY_CODE_MIN_FQDN_LEN 2 +#define CAPABILITY_CODE_ENHANCED_LEN 0 +#define CAPABILITY_CODE_LLGR_LEN 0 +#define CAPABILITY_CODE_ORF_LEN 5 +#define CAPABILITY_CODE_EXT_MESSAGE_LEN 0 /* Extended Message Support */ +#define CAPABILITY_CODE_ROLE_LEN 1 +#define CAPABILITY_CODE_SOFT_VERSION_LEN 1 + +/* Cooperative Route Filtering Capability. */ + +/* ORF Type */ +#define ORF_TYPE_RESERVED 0 +#define ORF_TYPE_PREFIX 64 + +/* ORF Mode */ +#define ORF_MODE_RECEIVE 1 +#define ORF_MODE_SEND 2 +#define ORF_MODE_BOTH 3 + +/* Capability Message Action. */ +#define CAPABILITY_ACTION_SET 0 +#define CAPABILITY_ACTION_UNSET 1 + +/* Graceful Restart */ +#define GRACEFUL_RESTART_R_BIT 0x8000 +#define GRACEFUL_RESTART_N_BIT 0x4000 +#define GRACEFUL_RESTART_F_BIT 0x80 + +/* Long-lived Graceful Restart */ +#define LLGR_F_BIT 0x80 + +/* Optional Parameters */ +#define BGP_OPEN_NON_EXT_OPT_LEN 255 /* Non-Ext OP Len. */ +#define BGP_OPEN_NON_EXT_OPT_TYPE_EXTENDED_LENGTH 255 /* Non-Ext OP Type */ +#define BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(peer) \ + (CHECK_FLAG(peer->flags, PEER_FLAG_EXTENDED_OPT_PARAMS) \ + || CHECK_FLAG(peer->sflags, PEER_STATUS_EXT_OPT_PARAMS_LENGTH)) + +extern int bgp_open_option_parse(struct peer *peer, uint16_t length, + int *mp_capability); +extern uint16_t bgp_open_capability(struct stream *s, struct peer *peer, + bool ext_opt_params); +extern void bgp_capability_vty_out(struct vty *vty, struct peer *peer, + bool use_json, json_object *json_neigh); +extern as_t peek_for_as4_capability(struct peer *peer, uint16_t length); +extern const struct message capcode_str[]; +extern const struct message orf_type_str[]; +extern const struct message orf_mode_str[]; +extern const size_t cap_minsizes[]; +extern const size_t cap_modsizes[]; + +#endif /* _QUAGGA_BGP_OPEN_H */ diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c new file mode 100644 index 0000000..010a31a --- /dev/null +++ b/bgpd/bgp_packet.c @@ -0,0 +1,4197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP packet management routine. + * Contains utility functions for constructing and consuming BGP messages. + * Copyright (C) 2017 Cumulus Networks + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include +#include + +#include "frrevent.h" +#include "stream.h" +#include "network.h" +#include "prefix.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "sockunion.h" /* for inet_ntop () */ +#include "sockopt.h" +#include "linklist.h" +#include "plist.h" +#include "queue.h" +#include "filter.h" +#include "lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_bmp.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_io.h" +#include "bgpd/bgp_keepalives.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_trace.h" + +DEFINE_HOOK(bgp_packet_dump, + (struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *s), + (peer, type, size, s)); + +DEFINE_HOOK(bgp_packet_send, + (struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *s), + (peer, type, size, s)); + +/** + * Sets marker and type fields for a BGP message. + * + * @param s the stream containing the packet + * @param type the packet type + * @return the size of the stream + */ +int bgp_packet_set_marker(struct stream *s, uint8_t type) +{ + int i; + + /* Fill in marker. */ + for (i = 0; i < BGP_MARKER_SIZE; i++) + stream_putc(s, 0xff); + + /* Dummy total length. This field is should be filled in later on. */ + stream_putw(s, 0); + + /* BGP packet type. */ + stream_putc(s, type); + + /* Return current stream size. */ + return stream_get_endp(s); +} + +/** + * Sets size field for a BGP message. + * + * Size field is set to the size of the stream passed. + * + * @param s the stream containing the packet + */ +void bgp_packet_set_size(struct stream *s) +{ + int cp; + + /* Preserve current pointer. */ + cp = stream_get_endp(s); + stream_putw_at(s, BGP_MARKER_SIZE, cp); +} + +/* + * Push a packet onto the beginning of the peer's output queue. + * This function acquires the peer's write mutex before proceeding. + */ +static void bgp_packet_add(struct peer_connection *connection, + struct peer *peer, struct stream *s) +{ + intmax_t delta; + uint32_t holdtime; + intmax_t sendholdtime; + + frr_with_mutex (&connection->io_mtx) { + /* if the queue is empty, reset the "last OK" timestamp to + * now, otherwise if we write another packet immediately + * after it'll get confused + */ + if (!stream_fifo_count_safe(connection->obuf)) + peer->last_sendq_ok = monotime(NULL); + + stream_fifo_push(connection->obuf, s); + + delta = monotime(NULL) - peer->last_sendq_ok; + + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) + holdtime = atomic_load_explicit(&peer->holdtime, + memory_order_relaxed); + else + holdtime = peer->bgp->default_holdtime; + + sendholdtime = holdtime * 2; + + /* Note that when we're here, we're adding some packet to the + * OutQ. That includes keepalives when there is nothing to + * do, so there's a guarantee we pass by here once in a while. + * + * That implies there is no need to go set up another separate + * timer that ticks down SendHoldTime, as we'll be here sooner + * or later anyway and will see the checks below failing. + */ + if (!holdtime) { + /* no holdtime, do nothing. */ + } else if (delta > sendholdtime) { + flog_err( + EC_BGP_SENDQ_STUCK_PROPER, + "%pBP has not made any SendQ progress for 2 holdtimes (%jds), terminating session", + peer, sendholdtime); + bgp_stop_with_notify(connection, + BGP_NOTIFY_SEND_HOLD_ERR, 0); + } else if (delta > (intmax_t)holdtime && + monotime(NULL) - peer->last_sendq_warn > 5) { + flog_warn( + EC_BGP_SENDQ_STUCK_WARN, + "%pBP has not made any SendQ progress for 1 holdtime (%us), peer overloaded?", + peer, holdtime); + peer->last_sendq_warn = monotime(NULL); + } + } +} + +static struct stream *bgp_update_packet_eor(struct peer *peer, afi_t afi, + safi_t safi) +{ + struct stream *s; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + + if (DISABLE_BGP_ANNOUNCE) + return NULL; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("send End-of-RIB for %s to %s", + get_afi_safi_str(afi, safi, false), peer->host); + + s = stream_new(peer->max_packet_size); + + /* Make BGP update packet. */ + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* Unfeasible Routes Length */ + stream_putw(s, 0); + + if (afi == AFI_IP && safi == SAFI_UNICAST) { + /* Total Path Attribute Length */ + stream_putw(s, 0); + } else { + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + /* Total Path Attribute Length */ + stream_putw(s, 6); + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_MP_UNREACH_NLRI); + stream_putc(s, 3); + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + } + + bgp_packet_set_size(s); + return s; +} + +/* Called when there is a change in the EOR(implicit or explicit) status of a + * peer. Ends the update-delay if all expected peers are done with EORs. */ +void bgp_check_update_delay(struct bgp *bgp) +{ + struct listnode *node, *nnode; + struct peer *peer = NULL; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("Checking update delay, T: %d R: %d I:%d E: %d", + bgp->established, bgp->restarted_peers, + bgp->implicit_eors, bgp->explicit_eors); + + if (bgp->established + <= bgp->restarted_peers + bgp->implicit_eors + bgp->explicit_eors) { + /* + * This is an extra sanity check to make sure we wait for all + * the eligible configured peers. This check is performed if + * establish wait timer is on, or establish wait option is not + * given with the update-delay command + */ + if (bgp->t_establish_wait + || (bgp->v_establish_wait == bgp->v_update_delay)) + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (CHECK_FLAG(peer->flags, + PEER_FLAG_CONFIG_NODE) + && !CHECK_FLAG(peer->flags, + PEER_FLAG_SHUTDOWN) + && !CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_SHUTDOWN) + && !peer->update_delay_over) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + " Peer %s pending, continuing read-only mode", + peer->host); + return; + } + } + + zlog_info( + "Update delay ended, restarted: %d, EORs implicit: %d, explicit: %d", + bgp->restarted_peers, bgp->implicit_eors, + bgp->explicit_eors); + bgp_update_delay_end(bgp); + } +} + +/* + * Called if peer is known to have restarted. The restart-state bit in + * Graceful-Restart capability is used for that + */ +void bgp_update_restarted_peers(struct peer *peer) +{ + if (!bgp_update_delay_active(peer->bgp)) + return; /* BGP update delay has ended */ + if (peer->update_delay_over) + return; /* This peer has already been considered */ + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("Peer %s: Checking restarted", peer->host); + + if (peer_established(peer->connection)) { + peer->update_delay_over = 1; + peer->bgp->restarted_peers++; + bgp_check_update_delay(peer->bgp); + } +} + +/* + * Called as peer receives a keep-alive. Determines if this occurence can be + * taken as an implicit EOR for this peer. + * NOTE: The very first keep-alive after the Established state of a peer is + * considered implicit EOR for the update-delay purposes + */ +void bgp_update_implicit_eors(struct peer *peer) +{ + if (!bgp_update_delay_active(peer->bgp)) + return; /* BGP update delay has ended */ + if (peer->update_delay_over) + return; /* This peer has already been considered */ + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("Peer %s: Checking implicit EORs", peer->host); + + if (peer_established(peer->connection)) { + peer->update_delay_over = 1; + peer->bgp->implicit_eors++; + bgp_check_update_delay(peer->bgp); + } +} + +/* + * Should be called only when there is a change in the EOR_RECEIVED status + * for any afi/safi on a peer. + */ +static void bgp_update_explicit_eors(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + if (!bgp_update_delay_active(peer->bgp)) + return; /* BGP update delay has ended */ + if (peer->update_delay_over) + return; /* This peer has already been considered */ + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("Peer %s: Checking explicit EORs", peer->host); + + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc_nego[afi][safi] + && !CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + " afi %d safi %d didn't receive EOR", + afi, safi); + return; + } + } + + peer->update_delay_over = 1; + peer->bgp->explicit_eors++; + bgp_check_update_delay(peer->bgp); +} + +/** + * Frontend for NLRI parsing, to fan-out to AFI/SAFI specific parsers. + * + * mp_withdraw, if set, is used to nullify attr structure on most of the + * calling safi function and for evpn, passed as parameter + */ +int bgp_nlri_parse(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, bool mp_withdraw) +{ + switch (packet->safi) { + case SAFI_UNICAST: + case SAFI_MULTICAST: + return bgp_nlri_parse_ip(peer, mp_withdraw ? NULL : attr, + packet); + case SAFI_LABELED_UNICAST: + return bgp_nlri_parse_label(peer, mp_withdraw ? NULL : attr, + packet); + case SAFI_MPLS_VPN: + return bgp_nlri_parse_vpn(peer, mp_withdraw ? NULL : attr, + packet); + case SAFI_EVPN: + return bgp_nlri_parse_evpn(peer, attr, packet, mp_withdraw); + case SAFI_FLOWSPEC: + return bgp_nlri_parse_flowspec(peer, attr, packet, mp_withdraw); + } + return BGP_NLRI_PARSE_ERROR; +} + + +/* + * Check if route-refresh request from peer is pending (received before EoR), + * and process it now. + */ +static void bgp_process_pending_refresh(struct peer *peer, afi_t afi, + safi_t safi) +{ + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_REFRESH_PENDING)) { + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_REFRESH_PENDING); + bgp_route_refresh_send(peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_BORR); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP sending route-refresh (BoRR) for %s/%s (for pending REQUEST)", + peer, afi2str(afi), safi2str(safi)); + + SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_BORR_SEND); + UNSET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EORR_SEND); + bgp_announce_route(peer, afi, safi, true); + } +} + +/* + * Checks a variety of conditions to determine whether the peer needs to be + * rescheduled for packet generation again, and does so if necessary. + * + * @param peer to check for rescheduling + */ +static void bgp_write_proceed_actions(struct peer *peer) +{ + afi_t afi; + safi_t safi; + struct peer_af *paf; + struct bpacket *next_pkt; + struct update_subgroup *subgrp; + enum bgp_af_index index; + struct peer_connection *connection = peer->connection; + + for (index = BGP_AF_START; index < BGP_AF_MAX; index++) { + paf = peer->peer_af_array[index]; + if (!paf) + continue; + + subgrp = paf->subgroup; + if (!subgrp) + continue; + + next_pkt = paf->next_pkt_to_send; + if (next_pkt && next_pkt->buffer) { + BGP_TIMER_ON(connection->t_generate_updgrp_packets, + bgp_generate_updgrp_packets, 0); + return; + } + + /* No packets readily available for AFI/SAFI, are there + * subgroup packets + * that need to be generated? */ + if (bpacket_queue_is_full(SUBGRP_INST(subgrp), + SUBGRP_PKTQ(subgrp)) + || subgroup_packets_to_build(subgrp)) { + BGP_TIMER_ON(connection->t_generate_updgrp_packets, + bgp_generate_updgrp_packets, 0); + return; + } + + afi = paf->afi; + safi = paf->safi; + + /* No packets to send, see if EOR is pending */ + if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + if (!subgrp->t_coalesce && peer->afc_nego[afi][safi] + && peer->synctime + && !CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND) + && safi != SAFI_MPLS_VPN) { + BGP_TIMER_ON(connection->t_generate_updgrp_packets, + bgp_generate_updgrp_packets, 0); + return; + } + } + } +} + +/* + * Generate advertisement information (withdraws, updates, EOR) from each + * update group a peer belongs to, encode this information into packets, and + * enqueue the packets onto the peer's output buffer. + */ +void bgp_generate_updgrp_packets(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + struct stream *s; + struct peer_af *paf; + struct bpacket *next_pkt; + uint32_t wpq; + uint32_t generated = 0; + afi_t afi; + safi_t safi; + + wpq = atomic_load_explicit(&peer->bgp->wpkt_quanta, + memory_order_relaxed); + + /* + * The code beyond this part deals with update packets, proceed only + * if peer is Established and updates are not on hold (as part of + * update-delay processing). + */ + if (!peer_established(peer->connection)) + return; + + if ((peer->bgp->main_peers_update_hold) + || bgp_update_delay_active(peer->bgp)) + return; + + if (peer->connection->t_routeadv) + return; + + /* + * Since the following is a do while loop + * let's stop adding to the outq if we are + * already at the limit. + */ + if (connection->obuf->count >= bm->outq_limit) { + bgp_write_proceed_actions(peer); + return; + } + + do { + enum bgp_af_index index; + + s = NULL; + for (index = BGP_AF_START; index < BGP_AF_MAX; index++) { + paf = peer->peer_af_array[index]; + if (!paf || !PAF_SUBGRP(paf)) + continue; + + afi = paf->afi; + safi = paf->safi; + next_pkt = paf->next_pkt_to_send; + + /* + * Try to generate a packet for the peer if we are at + * the end of the list. Always try to push out + * WITHDRAWs first. + */ + if (!next_pkt || !next_pkt->buffer) { + next_pkt = subgroup_withdraw_packet( + PAF_SUBGRP(paf)); + if (!next_pkt || !next_pkt->buffer) + subgroup_update_packet(PAF_SUBGRP(paf)); + next_pkt = paf->next_pkt_to_send; + } + + /* + * If we still don't have a packet to send to the peer, + * then try to find out out if we have to send eor or + * if not, skip to the next AFI, SAFI. Don't send the + * EOR prematurely; if the subgroup's coalesce timer is + * running, the adjacency-out structure is not created + * yet. + */ + if (!next_pkt || !next_pkt->buffer) { + if (!paf->t_announce_route) { + /* Make sure we supress BGP UPDATES + * for normal processing later again. + */ + UNSET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + /* If route-refresh BoRR message was + * already sent and we are done with + * re-announcing tables for a decent + * afi/safi, we ready to send + * EoRR request. + */ + if (CHECK_FLAG( + peer->af_sflags[afi][safi], + PEER_STATUS_BORR_SEND)) { + bgp_route_refresh_send( + peer, afi, safi, 0, 0, + 0, + BGP_ROUTE_REFRESH_EORR); + + SET_FLAG(peer->af_sflags[afi] + [safi], + PEER_STATUS_EORR_SEND); + UNSET_FLAG( + peer->af_sflags[afi] + [safi], + PEER_STATUS_BORR_SEND); + + if (bgp_debug_neighbor_events( + peer)) + zlog_debug( + "%pBP sending route-refresh (EoRR) for %s/%s", + peer, + afi2str(afi), + safi2str(safi)); + } + } + + /* rfc4724 says: + * Although the End-of-RIB marker is + * specified for the purpose of BGP + * graceful restart, it is noted that + * the generation of such a marker upon + * completion of the initial update would + * be useful for routing convergence in + * general, and thus the practice is + * recommended. + */ + if (!(PAF_SUBGRP(paf))->t_coalesce && + peer->afc_nego[afi][safi] && + peer->synctime && + !CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND)) { + /* If EOR is disabled, the message is + * not sent. + */ + if (!BGP_SEND_EOR(peer->bgp, afi, safi)) + continue; + + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND); + + /* Update EOR send time */ + peer->eor_stime[afi][safi] = + monotime(NULL); + + BGP_UPDATE_EOR_PKT(peer, afi, safi, s); + bgp_process_pending_refresh(peer, afi, + safi); + } + continue; + } + + /* Update packet send time */ + peer->pkt_stime[afi][safi] = monotime(NULL); + + /* Found a packet template to send, overwrite + * packet with appropriate attributes from peer + * and advance peer */ + s = bpacket_reformat_for_peer(next_pkt, paf); + bgp_packet_add(connection, peer, s); + bpacket_queue_advance_peer(paf); + } + } while (s && (++generated < wpq) && + (connection->obuf->count <= bm->outq_limit)); + + if (generated) + bgp_writes_on(connection); + + bgp_write_proceed_actions(peer); +} + +/* + * Creates a BGP Keepalive packet and appends it to the peer's output queue. + */ +void bgp_keepalive_send(struct peer *peer) +{ + struct stream *s; + + s = stream_new(BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE); + + /* Make keepalive packet. */ + bgp_packet_set_marker(s, BGP_MSG_KEEPALIVE); + + /* Set packet size. */ + bgp_packet_set_size(s); + + /* Dump packet if debug option is set. */ + /* bgp_packet_dump (s); */ + + if (bgp_debug_keepalive(peer)) + zlog_debug("%s sending KEEPALIVE", peer->host); + + /* Add packet to the peer. */ + bgp_packet_add(peer->connection, peer, s); + + bgp_writes_on(peer->connection); +} + +/* + * Creates a BGP Open packet and appends it to the peer's output queue. + * Sets capabilities as necessary. + */ +void bgp_open_send(struct peer_connection *connection) +{ + struct stream *s; + uint16_t send_holdtime; + as_t local_as; + struct peer *peer = connection->peer; + + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) + send_holdtime = peer->holdtime; + else + send_holdtime = peer->bgp->default_holdtime; + + /* local-as Change */ + if (peer->change_local_as) + local_as = peer->change_local_as; + else + local_as = peer->local_as; + + s = stream_new(BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE); + + /* Make open packet. */ + bgp_packet_set_marker(s, BGP_MSG_OPEN); + + /* Set open packet values. */ + stream_putc(s, BGP_VERSION_4); /* BGP version */ + stream_putw(s, (local_as <= BGP_AS_MAX) ? (uint16_t)local_as + : BGP_AS_TRANS); + stream_putw(s, send_holdtime); /* Hold Time */ + stream_put_in_addr(s, &peer->local_id); /* BGP Identifier */ + + /* Set capabilities */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_EXTENDED_OPT_PARAMS)) { + (void)bgp_open_capability(s, peer, true); + } else { + struct stream *tmp = stream_new(STREAM_SIZE(s)); + + stream_copy(tmp, s); + if (bgp_open_capability(tmp, peer, false) + > BGP_OPEN_NON_EXT_OPT_LEN) { + stream_free(tmp); + (void)bgp_open_capability(s, peer, true); + } else { + stream_copy(s, tmp); + stream_free(tmp); + } + } + + /* Set BGP packet length. */ + bgp_packet_set_size(s); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s sending OPEN, version %d, my as %u, holdtime %d, id %pI4", + peer->host, BGP_VERSION_4, local_as, send_holdtime, + &peer->local_id); + + /* Dump packet if debug option is set. */ + /* bgp_packet_dump (s); */ + hook_call(bgp_packet_send, peer, BGP_MSG_OPEN, stream_get_endp(s), s); + + /* Add packet to the peer. */ + bgp_packet_add(connection, peer, s); + + bgp_writes_on(connection); +} + +/* + * Writes NOTIFICATION message directly to a peer socket without waiting for + * the I/O thread. + * + * There must be exactly one stream on the peer->connection->obuf FIFO, and the + * data within this stream must match the format of a BGP NOTIFICATION message. + * Transmission is best-effort. + * + * @requires peer->connection->io_mtx + * @param peer + * @return 0 + */ +static void bgp_write_notify(struct peer_connection *connection, + struct peer *peer) +{ + int ret, val; + uint8_t type; + struct stream *s; + + /* There should be at least one packet. */ + s = stream_fifo_pop(connection->obuf); + + if (!s) + return; + + assert(stream_get_endp(s) >= BGP_HEADER_SIZE); + + /* + * socket is in nonblocking mode, if we can't deliver the NOTIFY, well, + * we only care about getting a clean shutdown at this point. + */ + ret = write(connection->fd, STREAM_DATA(s), stream_get_endp(s)); + + /* + * only connection reset/close gets counted as TCP_fatal_error, failure + * to write the entire NOTIFY doesn't get different FSM treatment + */ + if (ret <= 0) { + stream_free(s); + BGP_EVENT_ADD(connection, TCP_fatal_error); + return; + } + + /* Disable Nagle, make NOTIFY packet go out right away */ + val = 1; + (void)setsockopt(connection->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, + sizeof(val)); + + /* Retrieve BGP packet type. */ + stream_set_getp(s, BGP_MARKER_SIZE + 2); + type = stream_getc(s); + + assert(type == BGP_MSG_NOTIFY); + + /* Type should be notify. */ + atomic_fetch_add_explicit(&peer->notify_out, 1, memory_order_relaxed); + + /* Double start timer. */ + peer->v_start *= 2; + + /* Overflow check. */ + if (peer->v_start >= (60 * 2)) + peer->v_start = (60 * 2); + + /* + * Handle Graceful Restart case where the state changes to + * Connect instead of Idle + */ + BGP_EVENT_ADD(connection, BGP_Stop); + + stream_free(s); +} + +/* + * Encapsulate an original BGP CEASE Notification into Hard Reset + */ +static uint8_t *bgp_notify_encapsulate_hard_reset(uint8_t code, uint8_t subcode, + uint8_t *data, size_t datalen) +{ + uint8_t *message = XCALLOC(MTYPE_BGP_NOTIFICATION, datalen + 2); + + /* ErrCode */ + message[0] = code; + /* Subcode */ + message[1] = subcode; + /* Data */ + if (datalen) + memcpy(message + 2, data, datalen); + + return message; +} + +/* + * Decapsulate an original BGP CEASE Notification from Hard Reset + */ +struct bgp_notify bgp_notify_decapsulate_hard_reset(struct bgp_notify *notify) +{ + struct bgp_notify bn = {}; + + bn.code = notify->raw_data[0]; + bn.subcode = notify->raw_data[1]; + bn.length = notify->length - 2; + + bn.raw_data = XMALLOC(MTYPE_BGP_NOTIFICATION, bn.length); + memcpy(bn.raw_data, notify->raw_data + 2, bn.length); + + return bn; +} + +/* Check if Graceful-Restart N-bit is exchanged */ +bool bgp_has_graceful_restart_notification(struct peer *peer) +{ + return CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV) && + CHECK_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_ADV); +} + +/* + * Check if to send BGP CEASE Notification/Hard Reset? + */ +bool bgp_notify_send_hard_reset(struct peer *peer, uint8_t code, + uint8_t subcode) +{ + /* When the "N" bit has been exchanged, a Hard Reset message is used to + * indicate to the peer that the session is to be fully terminated. + */ + if (!bgp_has_graceful_restart_notification(peer)) + return false; + + /* + * https://datatracker.ietf.org/doc/html/rfc8538#section-5.1 + */ + if (code == BGP_NOTIFY_CEASE) { + switch (subcode) { + case BGP_NOTIFY_CEASE_MAX_PREFIX: + case BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN: + case BGP_NOTIFY_CEASE_PEER_UNCONFIG: + case BGP_NOTIFY_CEASE_HARD_RESET: + case BGP_NOTIFY_CEASE_BFD_DOWN: + return true; + case BGP_NOTIFY_CEASE_ADMIN_RESET: + /* Provide user control: + * `bgp hard-adminstrative-reset` + */ + if (CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_HARD_ADMIN_RESET)) + return true; + else + return false; + default: + break; + } + } + + return false; +} + +/* + * Check if received BGP CEASE Notification/Hard Reset? + */ +bool bgp_notify_received_hard_reset(struct peer *peer, uint8_t code, + uint8_t subcode) +{ + /* When the "N" bit has been exchanged, a Hard Reset message is used to + * indicate to the peer that the session is to be fully terminated. + */ + if (!bgp_has_graceful_restart_notification(peer)) + return false; + + if (code == BGP_NOTIFY_CEASE && subcode == BGP_NOTIFY_CEASE_HARD_RESET) + return true; + + return false; +} + +/* + * Creates a BGP Notify and appends it to the peer's output queue. + * + * This function attempts to write the packet from the thread it is called + * from, to ensure the packet gets out ASAP. + * + * This function may be called from multiple threads. Since the function + * modifies I/O buffer(s) in the peer, these are locked for the duration of the + * call to prevent tampering from other threads. + * + * Delivery of the NOTIFICATION is attempted once and is best-effort. After + * return, the peer structure *must* be reset; no assumptions about session + * state are valid. + * + * @param peer + * @param code BGP error code + * @param sub_code BGP error subcode + * @param data Data portion + * @param datalen length of data portion + */ +static void bgp_notify_send_internal(struct peer_connection *connection, + uint8_t code, uint8_t sub_code, + uint8_t *data, size_t datalen, + bool use_curr) +{ + struct stream *s; + struct peer *peer = connection->peer; + bool hard_reset = bgp_notify_send_hard_reset(peer, code, sub_code); + + /* Lock I/O mutex to prevent other threads from pushing packets */ + frr_mutex_lock_autounlock(&connection->io_mtx); + /* ============================================== */ + + /* Allocate new stream. */ + s = stream_new(peer->max_packet_size); + + /* Make notify packet. */ + bgp_packet_set_marker(s, BGP_MSG_NOTIFY); + + /* Check if we should send Hard Reset Notification or not */ + if (hard_reset) { + uint8_t *hard_reset_message = bgp_notify_encapsulate_hard_reset( + code, sub_code, data, datalen); + + /* Hard Reset encapsulates another NOTIFICATION message + * in its data portion. + */ + stream_putc(s, BGP_NOTIFY_CEASE); + stream_putc(s, BGP_NOTIFY_CEASE_HARD_RESET); + stream_write(s, hard_reset_message, datalen + 2); + + XFREE(MTYPE_BGP_NOTIFICATION, hard_reset_message); + } else { + stream_putc(s, code); + stream_putc(s, sub_code); + if (data) + stream_write(s, data, datalen); + } + + /* Set BGP packet length. */ + bgp_packet_set_size(s); + + /* wipe output buffer */ + stream_fifo_clean(connection->obuf); + + /* + * If possible, store last packet for debugging purposes. This check is + * in place because we are sometimes called with a doppelganger peer, + * who tends to have a plethora of fields nulled out. + * + * Some callers should not attempt this - the io pthread for example + * should not touch internals of the peer struct. + */ + if (use_curr && peer->curr) { + size_t packetsize = stream_get_endp(peer->curr); + assert(packetsize <= peer->max_packet_size); + if (peer->last_reset_cause) + stream_free(peer->last_reset_cause); + peer->last_reset_cause = stream_dup(peer->curr); + } + + /* For debug */ + { + struct bgp_notify bgp_notify; + int first = 0; + int i; + char c[4]; + + bgp_notify.code = code; + bgp_notify.subcode = sub_code; + bgp_notify.data = NULL; + bgp_notify.length = datalen; + bgp_notify.raw_data = data; + + peer->notify.code = bgp_notify.code; + peer->notify.subcode = bgp_notify.subcode; + peer->notify.length = bgp_notify.length; + + if (bgp_notify.length && data) { + bgp_notify.data = XMALLOC(MTYPE_BGP_NOTIFICATION, + bgp_notify.length * 3); + for (i = 0; i < bgp_notify.length; i++) + if (first) { + snprintf(c, sizeof(c), " %02x", + data[i]); + + strlcat(bgp_notify.data, c, + bgp_notify.length); + + } else { + first = 1; + snprintf(c, sizeof(c), "%02x", data[i]); + + strlcpy(bgp_notify.data, c, + bgp_notify.length); + } + } + bgp_notify_print(peer, &bgp_notify, "sending", hard_reset); + + if (bgp_notify.data) { + if (data) { + XFREE(MTYPE_BGP_NOTIFICATION, + peer->notify.data); + peer->notify.data = XCALLOC( + MTYPE_BGP_NOTIFICATION, datalen); + memcpy(peer->notify.data, data, datalen); + } + + XFREE(MTYPE_BGP_NOTIFICATION, bgp_notify.data); + bgp_notify.length = 0; + } + } + + /* peer reset cause */ + if (code == BGP_NOTIFY_CEASE) { + if (sub_code == BGP_NOTIFY_CEASE_ADMIN_RESET) + peer->last_reset = PEER_DOWN_USER_RESET; + else if (sub_code == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN) { + if (CHECK_FLAG(peer->sflags, PEER_STATUS_RTT_SHUTDOWN)) + peer->last_reset = PEER_DOWN_RTT_SHUTDOWN; + else + peer->last_reset = PEER_DOWN_USER_SHUTDOWN; + } else + peer->last_reset = PEER_DOWN_NOTIFY_SEND; + } else + peer->last_reset = PEER_DOWN_NOTIFY_SEND; + + /* Add packet to peer's output queue */ + stream_fifo_push(connection->obuf, s); + + bgp_peer_gr_flags_update(peer); + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(peer->bgp, + peer->bgp->peer); + + bgp_write_notify(connection, peer); +} + +/* + * Creates a BGP Notify and appends it to the peer's output queue. + * + * This function attempts to write the packet from the thread it is called + * from, to ensure the packet gets out ASAP. + * + * @param peer + * @param code BGP error code + * @param sub_code BGP error subcode + */ +void bgp_notify_send(struct peer_connection *connection, uint8_t code, + uint8_t sub_code) +{ + bgp_notify_send_internal(connection, code, sub_code, NULL, 0, true); +} + +/* + * Enqueue notification; called from the main pthread, peer object access is ok. + */ +void bgp_notify_send_with_data(struct peer_connection *connection, uint8_t code, + uint8_t sub_code, uint8_t *data, size_t datalen) +{ + bgp_notify_send_internal(connection, code, sub_code, data, datalen, + true); +} + +/* + * For use by the io pthread, queueing a notification but avoiding access to + * the peer object. + */ +void bgp_notify_io_invalid(struct peer *peer, uint8_t code, uint8_t sub_code, + uint8_t *data, size_t datalen) +{ + /* Avoid touching the peer object */ + bgp_notify_send_internal(peer->connection, code, sub_code, data, + datalen, false); +} + +/* + * Creates BGP Route Refresh packet and appends it to the peer's output queue. + * + * @param peer + * @param afi Address Family Identifier + * @param safi Subsequent Address Family Identifier + * @param orf_type Outbound Route Filtering type + * @param when_to_refresh Whether to refresh immediately or defer + * @param remove Whether to remove ORF for specified AFI/SAFI + * @param subtype BGP enhanced route refresh optional subtypes + */ +void bgp_route_refresh_send(struct peer *peer, afi_t afi, safi_t safi, + uint8_t orf_type, uint8_t when_to_refresh, + int remove, uint8_t subtype) +{ + struct stream *s; + struct bgp_filter *filter; + int orf_refresh = 0; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + + if (DISABLE_BGP_ANNOUNCE) + return; + + filter = &peer->filter[afi][safi]; + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + s = stream_new(peer->max_packet_size); + + /* Make BGP update packet. */ + if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_RCV)) + bgp_packet_set_marker(s, BGP_MSG_ROUTE_REFRESH_NEW); + else + bgp_packet_set_marker(s, BGP_MSG_ROUTE_REFRESH_OLD); + + /* Encode Route Refresh message. */ + stream_putw(s, pkt_afi); + if (subtype) + stream_putc(s, subtype); + else + stream_putc(s, 0); + stream_putc(s, pkt_safi); + + if (orf_type == ORF_TYPE_PREFIX) + if (remove || filter->plist[FILTER_IN].plist) { + uint16_t orf_len; + unsigned long orfp; + + orf_refresh = 1; + stream_putc(s, when_to_refresh); + stream_putc(s, orf_type); + orfp = stream_get_endp(s); + stream_putw(s, 0); + + if (remove) { + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND); + stream_putc(s, ORF_COMMON_PART_REMOVE_ALL); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP sending REFRESH_REQ to remove ORF(%d) (%s) for afi/safi: %s/%s", + peer, orf_type, + (when_to_refresh == + REFRESH_DEFER + ? "defer" + : "immediate"), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else { + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND); + prefix_bgp_orf_entry( + s, filter->plist[FILTER_IN].plist, + ORF_COMMON_PART_ADD, + ORF_COMMON_PART_PERMIT, + ORF_COMMON_PART_DENY); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP sending REFRESH_REQ with pfxlist ORF(%d) (%s) for afi/safi: %s/%s", + peer, orf_type, + (when_to_refresh == + REFRESH_DEFER + ? "defer" + : "immediate"), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } + + /* Total ORF Entry Len. */ + orf_len = stream_get_endp(s) - orfp - 2; + stream_putw_at(s, orfp, orf_len); + } + + /* Set packet size. */ + bgp_packet_set_size(s); + + if (bgp_debug_neighbor_events(peer)) { + if (!orf_refresh) + zlog_debug( + "%pBP sending REFRESH_REQ for afi/safi: %s/%s", + peer, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } + + /* Add packet to the peer. */ + bgp_packet_add(peer->connection, peer, s); + + bgp_writes_on(peer->connection); +} + +/* + * Create a BGP Capability packet and append it to the peer's output queue. + * + * @param peer + * @param afi Address Family Identifier + * @param safi Subsequent Address Family Identifier + * @param capability_code BGP Capability Code + * @param action Set or Remove capability + */ +void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi, + int capability_code, int action) +{ + struct stream *s; + iana_afi_t pkt_afi = IANA_AFI_IPV4; + iana_safi_t pkt_safi = IANA_SAFI_UNICAST; + unsigned long cap_len; + uint16_t len; + uint32_t gr_restart_time; + uint8_t addpath_afi_safi_count = 0; + bool adv_addpath_tx = false; + unsigned long number_of_orfs_p; + uint8_t number_of_orfs = 0; + const char *capability = lookup_msg(capcode_str, capability_code, + "Unknown"); + const char *hostname = cmd_hostname_get(); + const char *domainname = cmd_domainname_get(); + + if (!peer_established(peer->connection)) + return; + + if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) || + !CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV)) + return; + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + s = stream_new(peer->max_packet_size); + + /* Make BGP update packet. */ + bgp_packet_set_marker(s, BGP_MSG_CAPABILITY); + + /* Encode MP_EXT capability. */ + switch (capability_code) { + case CAPABILITY_CODE_SOFT_VERSION: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_SOFT_VERSION); + cap_len = stream_get_endp(s); + stream_putc(s, 0); /* Capability Length */ + + /* The Capability Length SHOULD be no greater than 64. + * This is the limit to allow other capabilities as much + * space as they require. + */ + const char *soft_version = cmd_software_version_get(); + + len = strlen(soft_version); + if (len > BGP_MAX_SOFT_VERSION) + len = BGP_MAX_SOFT_VERSION; + + stream_putc(s, len); + stream_put(s, soft_version, len); + + /* Software Version capability Len. */ + len = stream_get_endp(s) - cap_len - 1; + stream_putc_at(s, cap_len, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + COND_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_MP: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_MP); + stream_putc(s, CAPABILITY_CODE_MP_LEN); + stream_putw(s, pkt_afi); + stream_putc(s, 0); + stream_putc(s, pkt_safi); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + break; + case CAPABILITY_CODE_RESTART: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_RESTART); + cap_len = stream_get_endp(s); + stream_putc(s, 0); + gr_restart_time = peer->bgp->restart_time; + + if (peer->bgp->t_startup) { + SET_FLAG(gr_restart_time, GRACEFUL_RESTART_R_BIT); + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_ADV); + } + + if (CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_GRACEFUL_NOTIFICATION)) { + SET_FLAG(gr_restart_time, GRACEFUL_RESTART_N_BIT); + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_ADV); + } + + stream_putw(s, gr_restart_time); + + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + if (CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_GR_PRESERVE_FWD)) + stream_putc(s, GRACEFUL_RESTART_F_BIT); + else + stream_putc(s, 0); + } + } + + len = stream_get_endp(s) - cap_len - 1; + stream_putc_at(s, cap_len, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + COND_FLAG(peer->cap, PEER_CAP_RESTART_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_LLGR: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_LLGR); + cap_len = stream_get_endp(s); + stream_putc(s, 0); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putc(s, LLGR_F_BIT); + stream_put3(s, peer->bgp->llgr_stale_time); + + SET_FLAG(peer->af_cap[afi][safi], PEER_CAP_LLGR_AF_ADV); + } + + len = stream_get_endp(s) - cap_len - 1; + stream_putc_at(s, cap_len, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + COND_FLAG(peer->cap, PEER_CAP_LLGR_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_ADDPATH: + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc[afi][safi]) { + addpath_afi_safi_count++; + + /* Only advertise addpath TX if a feature that + * will use it is + * configured */ + if (peer->addpath_type[afi][safi] != + BGP_ADDPATH_NONE) + adv_addpath_tx = true; + + /* If we have enabled labeled unicast, we MUST check + * against unicast SAFI because addpath IDs are + * allocated under unicast SAFI, the same as the RIB + * is managed in unicast SAFI. + */ + if (safi == SAFI_LABELED_UNICAST) + if (peer->addpath_type[afi][SAFI_UNICAST] != + BGP_ADDPATH_NONE) + adv_addpath_tx = true; + } + } + + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_ADDPATH); + stream_putc(s, CAPABILITY_CODE_ADDPATH_LEN * + addpath_afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (peer->afc[afi][safi]) { + bool adv_addpath_rx = + !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DISABLE_ADDPATH_RX); + uint8_t flags = 0; + + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + + if (adv_addpath_rx) { + SET_FLAG(flags, BGP_ADDPATH_RX); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV); + } else { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV); + } + + if (adv_addpath_tx) { + SET_FLAG(flags, BGP_ADDPATH_TX); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV); + if (safi == SAFI_LABELED_UNICAST) + SET_FLAG(peer->af_cap[afi] + [SAFI_UNICAST], + PEER_CAP_ADDPATH_AF_TX_ADV); + } else { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV); + } + + stream_putc(s, flags); + } + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + COND_FLAG(peer->cap, PEER_CAP_ADDPATH_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_PATHS_LIMIT: + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + addpath_afi_safi_count++; + } + + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT); + stream_putc(s, CAPABILITY_CODE_PATHS_LIMIT_LEN * + addpath_afi_safi_count); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, + &pkt_safi); + + stream_putw(s, pkt_afi); + stream_putc(s, pkt_safi); + stream_putw(s, + peer->addpath_paths_limit[afi][safi].send); + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s, limit: %u", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + peer->addpath_paths_limit[afi][safi] + .send); + } + + COND_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_ORF: + /* Convert AFI, SAFI to values for packet. */ + bgp_map_afi_safi_int2iana(afi, safi, &pkt_afi, &pkt_safi); + + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_ORF); + cap_len = stream_get_endp(s); + stream_putc(s, 0); + + stream_putw(s, pkt_afi); /* Address Family Identifier */ + stream_putc(s, 0); /* Reserved */ + stream_putc(s, + pkt_safi); /* Subsequent Address Family Identifier */ + + number_of_orfs_p = + stream_get_endp(s); /* Number of ORFs pointer */ + stream_putc(s, 0); /* Number of ORFs */ + + /* Address Prefix ORF */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM) || + CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_RM)) { + stream_putc(s, ORF_TYPE_PREFIX); + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM) && + CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_RM)) { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + stream_putc(s, ORF_MODE_BOTH); + } else if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ORF_PREFIX_SM)) { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + stream_putc(s, ORF_MODE_SEND); + } else { + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + stream_putc(s, ORF_MODE_RECEIVE); + } + number_of_orfs++; + } else { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV); + } + + /* Total Number of ORFs. */ + stream_putc_at(s, number_of_orfs_p, number_of_orfs); + + len = stream_get_endp(s) - cap_len - 1; + stream_putc_at(s, cap_len, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + break; + case CAPABILITY_CODE_FQDN: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_FQDN); + cap_len = stream_get_endp(s); + stream_putc(s, 0); /* Capability Length */ + + len = strlen(hostname); + if (len > BGP_MAX_HOSTNAME) + len = BGP_MAX_HOSTNAME; + + stream_putc(s, len); + stream_put(s, hostname, len); + + if (domainname) { + len = strlen(domainname); + if (len > BGP_MAX_HOSTNAME) + len = BGP_MAX_HOSTNAME; + + stream_putc(s, len); + stream_put(s, domainname, len); + } else + stream_putc(s, 0); + + len = stream_get_endp(s) - cap_len - 1; + stream_putc_at(s, cap_len, len); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP sending CAPABILITY has %s %s for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + COND_FLAG(peer->cap, PEER_CAP_HOSTNAME_ADV, + action == CAPABILITY_ACTION_SET); + break; + case CAPABILITY_CODE_REFRESH: + case CAPABILITY_CODE_AS4: + case CAPABILITY_CODE_DYNAMIC: + case CAPABILITY_CODE_ENHANCED_RR: + case CAPABILITY_CODE_ENHE: + case CAPABILITY_CODE_EXT_MESSAGE: + break; + case CAPABILITY_CODE_ROLE: + stream_putc(s, action); + stream_putc(s, CAPABILITY_CODE_ROLE); + stream_putc(s, CAPABILITY_CODE_ROLE_LEN); + stream_putc(s, peer->local_role); + COND_FLAG(peer->cap, PEER_CAP_ROLE_ADV, + action == CAPABILITY_ACTION_SET); + break; + default: + break; + } + + /* Set packet size. */ + bgp_packet_set_size(s); + + /* Add packet to the peer. */ + bgp_packet_add(peer->connection, peer, s); + + bgp_writes_on(peer->connection); +} + +/* RFC1771 6.8 Connection collision detection. */ +static int bgp_collision_detect(struct peer_connection *connection, + struct peer *new, struct in_addr remote_id) +{ + struct peer *peer; + struct peer_connection *other; + + /* + * Upon receipt of an OPEN message, the local system must examine + * all of its connections that are in the OpenConfirm state. A BGP + * speaker may also examine connections in an OpenSent state if it + * knows the BGP Identifier of the peer by means outside of the + * protocol. If among these connections there is a connection to a + * remote BGP speaker whose BGP Identifier equals the one in the + * OPEN message, then the local system performs the following + * collision resolution procedure: + */ + peer = new->doppelganger; + if (peer == NULL) + return 0; + + other = peer->connection; + + /* + * Do not accept the new connection in Established or Clearing + * states. Note that a peer GR is handled by closing the existing + * connection upon receipt of new one. + */ + if (peer_established(other) || + other->status == Clearing) { + bgp_notify_send(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + return -1; + } + + if ((other->status != OpenConfirm) && + (other->status != OpenSent)) + return 0; + + /* + * 1. The BGP Identifier of the local system is + * compared to the BGP Identifier of the remote + * system (as specified in the OPEN message). + * + * If the BGP Identifiers of the peers + * involved in the connection collision + * are identical, then the connection + * initiated by the BGP speaker with the + * larger AS number is preserved. + */ + if (ntohl(peer->local_id.s_addr) < ntohl(remote_id.s_addr) + || (ntohl(peer->local_id.s_addr) == ntohl(remote_id.s_addr) + && peer->local_as < peer->as)) + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) { + /* + * 2. If the value of the local BGP + * Identifier is less than the remote one, + * the local system closes BGP connection + * that already exists (the one that is + * already in the OpenConfirm state), + * and accepts BGP connection initiated by + * the remote system. + */ + bgp_notify_send(other, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + return 1; + } else { + bgp_notify_send(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + return -1; + } + else { + if (ntohl(peer->local_id.s_addr) == ntohl(remote_id.s_addr) + && peer->local_as == peer->as) + flog_err(EC_BGP_ROUTER_ID_SAME, + "Peer's router-id %pI4 is the same as ours", + &remote_id); + + /* + * 3. Otherwise, the local system closes newly + * created BGP connection (the one associated with the + * newly received OPEN message), and continues to use + * the existing one (the one that is already in the + * OpenConfirm state). + */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER)) { + bgp_notify_send(other, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + return 1; + } else { + bgp_notify_send(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_COLLISION_RESOLUTION); + return -1; + } + } +} + +/* Packet processing routines ---------------------------------------------- */ +/* + * This is a family of functions designed to be called from + * bgp_process_packet(). These functions all share similar behavior and should + * adhere to the following invariants and restrictions: + * + * Return codes + * ------------ + * The return code of any one of those functions should be one of the FSM event + * codes specified in bgpd.h. If a NOTIFY was sent, this event code MUST be + * BGP_Stop. Otherwise, the code SHOULD correspond to the function's expected + * packet type. For example, bgp_open_receive() should return BGP_Stop upon + * error and Receive_OPEN_message otherwise. + * + * If no action is necessary, the correct return code is BGP_PACKET_NOOP as + * defined below. + * + * Side effects + * ------------ + * - May send NOTIFY messages + * - May not modify peer->connection->status + * - May not call bgp_event_update() + */ + +#define BGP_PACKET_NOOP 0 + +/** + * Process BGP OPEN message for peer. + * + * If any errors are encountered in the OPEN message, immediately sends NOTIFY + * and returns BGP_Stop. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_open_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + int ret; + uint8_t version; + uint16_t optlen; + uint16_t holdtime; + uint16_t send_holdtime; + as_t remote_as; + as_t as4 = 0, as4_be; + struct in_addr remote_id; + int mp_capability; + uint8_t notify_data_remote_as[2]; + uint8_t notify_data_remote_as4[4]; + uint8_t notify_data_remote_id[4]; + uint16_t *holdtime_ptr; + + /* Parse open packet. */ + version = stream_getc(peer->curr); + memcpy(notify_data_remote_as, stream_pnt(peer->curr), 2); + remote_as = stream_getw(peer->curr); + holdtime_ptr = (uint16_t *)stream_pnt(peer->curr); + holdtime = stream_getw(peer->curr); + memcpy(notify_data_remote_id, stream_pnt(peer->curr), 4); + remote_id.s_addr = stream_get_ipv4(peer->curr); + + /* BEGIN to read the capability here, but dont do it yet */ + mp_capability = 0; + optlen = stream_getc(peer->curr); + + /* Extended Optional Parameters Length for BGP OPEN Message */ + if (optlen == BGP_OPEN_NON_EXT_OPT_LEN + || CHECK_FLAG(peer->flags, PEER_FLAG_EXTENDED_OPT_PARAMS)) { + uint8_t opttype; + + if (STREAM_READABLE(peer->curr) < 1) { + flog_err( + EC_BGP_PKT_OPEN, + "%s: stream does not have enough bytes for extended optional parameters", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return BGP_Stop; + } + + opttype = stream_getc(peer->curr); + if (opttype == BGP_OPEN_NON_EXT_OPT_TYPE_EXTENDED_LENGTH) { + if (STREAM_READABLE(peer->curr) < 2) { + flog_err( + EC_BGP_PKT_OPEN, + "%s: stream does not have enough bytes to read the extended optional parameters optlen", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return BGP_Stop; + } + optlen = stream_getw(peer->curr); + SET_FLAG(peer->sflags, + PEER_STATUS_EXT_OPT_PARAMS_LENGTH); + } + } + + /* Receive OPEN message log */ + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s rcv OPEN%s, version %d, remote-as (in open) %u, holdtime %d, id %pI4", + peer->host, + CHECK_FLAG(peer->sflags, + PEER_STATUS_EXT_OPT_PARAMS_LENGTH) + ? " (Extended)" + : "", + version, remote_as, holdtime, &remote_id); + + if (optlen != 0) { + /* If not enough bytes, it is an error. */ + if (STREAM_READABLE(peer->curr) < optlen) { + flog_err(EC_BGP_PKT_OPEN, + "%s: stream has not enough bytes (%u)", + peer->host, optlen); + bgp_notify_send(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + return BGP_Stop; + } + + /* We need the as4 capability value *right now* because + * if it is there, we have not got the remote_as yet, and + * without + * that we do not know which peer is connecting to us now. + */ + as4 = peek_for_as4_capability(peer, optlen); + } + + as4_be = htonl(as4); + memcpy(notify_data_remote_as4, &as4_be, 4); + + /* Just in case we have a silly peer who sends AS4 capability set to 0 + */ + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) && !as4) { + flog_err(EC_BGP_PKT_OPEN, + "%s bad OPEN, got AS4 capability, but AS4 set to 0", + peer->host); + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as4, 4); + return BGP_Stop; + } + + /* Codification of AS 0 Processing */ + if (remote_as == BGP_AS_ZERO) { + flog_err(EC_BGP_PKT_OPEN, "%s bad OPEN, got AS set to 0", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS); + return BGP_Stop; + } + + if (remote_as == BGP_AS_TRANS) { + /* Take the AS4 from the capability. We must have received the + * capability now! Otherwise we have a asn16 peer who uses + * BGP_AS_TRANS, for some unknown reason. + */ + if (as4 == BGP_AS_TRANS) { + flog_err( + EC_BGP_PKT_OPEN, + "%s [AS4] NEW speaker using AS_TRANS for AS4, not allowed", + peer->host); + bgp_notify_send_with_data(connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as4, 4); + return BGP_Stop; + } + + if (!as4 && BGP_DEBUG(as4, AS4)) + zlog_debug( + "%s [AS4] OPEN remote_as is AS_TRANS, but no AS4. Odd, but proceeding.", + peer->host); + else if (as4 < BGP_AS_MAX && BGP_DEBUG(as4, AS4)) + zlog_debug( + "%s [AS4] OPEN remote_as is AS_TRANS, but AS4 (%u) fits in 2-bytes, very odd peer.", + peer->host, as4); + if (as4) + remote_as = as4; + } else { + /* We may have a partner with AS4 who has an asno < BGP_AS_MAX + */ + /* If we have got the capability, peer->as4cap must match + * remote_as */ + if (CHECK_FLAG(peer->cap, PEER_CAP_AS4_RCV) + && as4 != remote_as) { + /* raise error, log this, close session */ + flog_err( + EC_BGP_PKT_OPEN, + "%s bad OPEN, got AS4 capability, but remote_as %u mismatch with 16bit 'myasn' %u in open", + peer->host, as4, remote_as); + bgp_notify_send_with_data(connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as4, 4); + return BGP_Stop; + } + } + + /* rfc6286: + * If the BGP Identifier field of the OPEN message + * is zero, or if it is the same as the BGP Identifier + * of the local BGP speaker and the message is from an + * internal peer, then the Error Subcode is set to + * "Bad BGP Identifier". + */ + if (remote_id.s_addr == INADDR_ANY + || (peer->sort == BGP_PEER_IBGP + && ntohl(peer->local_id.s_addr) == ntohl(remote_id.s_addr))) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s bad OPEN, wrong router identifier %pI4", + peer->host, &remote_id); + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_BGP_IDENT, + notify_data_remote_id, 4); + return BGP_Stop; + } + + /* Peer BGP version check. */ + if (version != BGP_VERSION_4) { + uint16_t maxver = htons(BGP_VERSION_4); + /* XXX this reply may not be correct if version < 4 XXX */ + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s bad protocol version, remote requested %d, local request %d", + peer->host, version, BGP_VERSION_4); + /* Data must be in network byte order here */ + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNSUP_VERSION, + (uint8_t *)&maxver, 2); + return BGP_Stop; + } + + /* Check neighbor as number. */ + if (peer->as_type == AS_UNSPECIFIED) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s bad OPEN, remote AS is unspecified currently", + peer->host); + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as, 2); + return BGP_Stop; + } else if (peer->as_type == AS_INTERNAL) { + if (remote_as != peer->bgp->as) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s bad OPEN, remote AS is %u, internal specified", + peer->host, remote_as); + bgp_notify_send_with_data(connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as, 2); + return BGP_Stop; + } + peer->as = peer->local_as; + } else if (peer->as_type == AS_EXTERNAL) { + if (remote_as == peer->bgp->as) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s bad OPEN, remote AS is %u, external specified", + peer->host, remote_as); + bgp_notify_send_with_data(connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as, 2); + return BGP_Stop; + } + peer->as = remote_as; + } else if ((peer->as_type == AS_SPECIFIED) && (remote_as != peer->as)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s bad OPEN, remote AS is %u, expected %u", + peer->host, remote_as, peer->as); + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_BAD_PEER_AS, + notify_data_remote_as, 2); + return BGP_Stop; + } + + /* + * When collision is detected and this peer is closed. + * Return immediately. + */ + ret = bgp_collision_detect(connection, peer, remote_id); + if (ret < 0) + return BGP_Stop; + + /* Get sockname. */ + if (bgp_getsockname(peer) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s: bgp_getsockname() failed for peer: %s", + __func__, peer->host); + return BGP_Stop; + } + + /* Set remote router-id */ + peer->remote_id = remote_id; + + /* From the rfc: Upon receipt of an OPEN message, a BGP speaker MUST + calculate the value of the Hold Timer by using the smaller of its + configured Hold Time and the Hold Time received in the OPEN message. + The Hold Time MUST be either zero or at least three seconds. An + implementation may reject connections on the basis of the Hold Time. + */ + + if (holdtime < 3 && holdtime != 0) { + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, + (uint8_t *)holdtime_ptr, 2); + return BGP_Stop; + } + + /* Send notification message when Hold Time received in the OPEN message + * is smaller than configured minimum Hold Time. */ + if (holdtime < peer->bgp->default_min_holdtime + && peer->bgp->default_min_holdtime != 0) { + bgp_notify_send_with_data(connection, BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNACEP_HOLDTIME, + (uint8_t *)holdtime_ptr, 2); + return BGP_Stop; + } + + /* From the rfc: A reasonable maximum time between KEEPALIVE messages + would be one third of the Hold Time interval. KEEPALIVE messages + MUST NOT be sent more frequently than one per second. An + implementation MAY adjust the rate at which it sends KEEPALIVE + messages as a function of the Hold Time interval. */ + + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) + send_holdtime = peer->holdtime; + else + send_holdtime = peer->bgp->default_holdtime; + + if (holdtime < send_holdtime) + peer->v_holdtime = holdtime; + else + peer->v_holdtime = send_holdtime; + + /* Set effective keepalive to 1/3 the effective holdtime. + * Use configured keeplive when < effective keepalive. + */ + peer->v_keepalive = peer->v_holdtime / 3; + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) { + if (peer->keepalive && peer->keepalive < peer->v_keepalive) + peer->v_keepalive = peer->keepalive; + } else { + if (peer->bgp->default_keepalive + && peer->bgp->default_keepalive < peer->v_keepalive) + peer->v_keepalive = peer->bgp->default_keepalive; + } + + /* If another side disabled sending Software Version capability, + * we MUST drop the previous from showing in the outputs to avoid + * stale information and due to security reasons. + */ + if (peer->soft_version) + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + + /* Open option part parse. */ + if (optlen != 0) { + if (bgp_open_option_parse(peer, optlen, &mp_capability) < 0) + return BGP_Stop; + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s rcvd OPEN w/ OPTION parameter len: 0", + peer->host); + } + + /* + * Assume that the peer supports the locally configured set of + * AFI/SAFIs if the peer did not send us any Mulitiprotocol + * capabilities, or if 'override-capability' is configured. + */ + if (!mp_capability + || CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) { + peer->afc_nego[AFI_IP][SAFI_UNICAST] = + peer->afc[AFI_IP][SAFI_UNICAST]; + peer->afc_nego[AFI_IP][SAFI_MULTICAST] = + peer->afc[AFI_IP][SAFI_MULTICAST]; + peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] = + peer->afc[AFI_IP][SAFI_LABELED_UNICAST]; + peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] = + peer->afc[AFI_IP][SAFI_FLOWSPEC]; + peer->afc_nego[AFI_IP6][SAFI_UNICAST] = + peer->afc[AFI_IP6][SAFI_UNICAST]; + peer->afc_nego[AFI_IP6][SAFI_MULTICAST] = + peer->afc[AFI_IP6][SAFI_MULTICAST]; + peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] = + peer->afc[AFI_IP6][SAFI_LABELED_UNICAST]; + peer->afc_nego[AFI_L2VPN][SAFI_EVPN] = + peer->afc[AFI_L2VPN][SAFI_EVPN]; + peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] = + peer->afc[AFI_IP6][SAFI_FLOWSPEC]; + } + + /* Verify valid local address present based on negotiated + * address-families. */ + if (peer->afc_nego[AFI_IP][SAFI_UNICAST] + || peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] + || peer->afc_nego[AFI_IP][SAFI_MULTICAST] + || peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] + || peer->afc_nego[AFI_IP][SAFI_ENCAP]) { + if (peer->nexthop.v4.s_addr == INADDR_ANY) { +#if defined(HAVE_CUMULUS) + zlog_warn("%s: No local IPv4 addr, BGP routing may not work", + peer->host); +#endif + } + } + if (peer->afc_nego[AFI_IP6][SAFI_UNICAST] + || peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] + || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] + || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] + || peer->afc_nego[AFI_IP6][SAFI_ENCAP]) { + if (IN6_IS_ADDR_UNSPECIFIED(&peer->nexthop.v6_global) && + !bm->v6_with_v4_nexthops) { + flog_err(EC_BGP_SND_FAIL, +"%s: No local IPv6 address, and zebra does not support V6 routing with v4 nexthops, BGP routing for V6 will not work", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_SUBCODE_UNSPECIFIC); + return BGP_Stop; + } + } + peer->rtt = sockopt_tcp_rtt(connection->fd); + + return Receive_OPEN_message; +} + +/** + * Process BGP KEEPALIVE message for peer. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_keepalive_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + if (bgp_debug_keepalive(peer)) + zlog_debug("%s KEEPALIVE rcvd", peer->host); + + bgp_update_implicit_eors(peer); + + peer->rtt = sockopt_tcp_rtt(connection->fd); + + /* If the peer's RTT is higher than expected, shutdown + * the peer automatically. + */ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_RTT_SHUTDOWN)) + return Receive_KEEPALIVE_message; + + if (peer->rtt > peer->rtt_expected) { + peer->rtt_keepalive_rcv++; + + if (peer->rtt_keepalive_rcv > peer->rtt_keepalive_conf) { + char rtt_shutdown_reason[BUFSIZ] = {}; + + snprintfrr( + rtt_shutdown_reason, + sizeof(rtt_shutdown_reason), + "shutdown due to high round-trip-time (%dms > %dms, hit %u times)", + peer->rtt, peer->rtt_expected, + peer->rtt_keepalive_rcv); + zlog_warn("%s %s", peer->host, rtt_shutdown_reason); + SET_FLAG(peer->sflags, PEER_STATUS_RTT_SHUTDOWN); + peer_tx_shutdown_message_set(peer, rtt_shutdown_reason); + peer_flag_set(peer, PEER_FLAG_SHUTDOWN); + } + } else { + if (peer->rtt_keepalive_rcv) + peer->rtt_keepalive_rcv--; + } + + return Receive_KEEPALIVE_message; +} + +static void bgp_refresh_stalepath_timer_expire(struct event *thread) +{ + struct peer_af *paf; + + paf = EVENT_ARG(thread); + + afi_t afi = paf->afi; + safi_t safi = paf->safi; + struct peer *peer = paf->peer; + + peer->t_refresh_stalepath = NULL; + + if (peer->nsf[afi][safi]) + bgp_clear_stale_route(peer, afi, safi); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP route-refresh (BoRR) timer expired for afi/safi: %d/%d", + peer, afi, safi); + + bgp_timer_set(peer->connection); +} + +/** + * Process BGP UPDATE message for peer. + * + * Parses UPDATE and creates attribute object. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_update_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + int ret, nlri_ret; + uint8_t *end; + struct stream *s; + struct attr attr; + bgp_size_t attribute_len; + bgp_size_t update_len; + bgp_size_t withdraw_len; + bool restart = false; + + enum NLRI_TYPES { + NLRI_UPDATE, + NLRI_WITHDRAW, + NLRI_MP_UPDATE, + NLRI_MP_WITHDRAW, + NLRI_TYPE_MAX + }; + struct bgp_nlri nlris[NLRI_TYPE_MAX]; + + /* Status must be Established. */ + if (!peer_established(connection)) { + flog_err(EC_BGP_INVALID_STATUS, + "%s [FSM] Update packet received under status %s", + peer->host, + lookup_msg(bgp_status_msg, peer->connection->status, + NULL)); + bgp_notify_send(connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(peer->connection->status)); + return BGP_Stop; + } + + /* Set initial values. */ + memset(&attr, 0, sizeof(attr)); + attr.label_index = BGP_INVALID_LABEL_INDEX; + attr.label = MPLS_INVALID_LABEL; + memset(&nlris, 0, sizeof(nlris)); + memset(peer->rcvd_attr_str, 0, BUFSIZ); + peer->rcvd_attr_printed = 0; + + s = peer->curr; + end = stream_pnt(s) + size; + + /* RFC1771 6.3 If the Unfeasible Routes Length or Total Attribute + Length is too large (i.e., if Unfeasible Routes Length + Total + Attribute Length + 23 exceeds the message Length), then the Error + Subcode is set to Malformed Attribute List. */ + if (stream_pnt(s) + 2 > end) { + flog_err(EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (packet length is short for unfeasible length)", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + return BGP_Stop; + } + + /* Unfeasible Route Length. */ + withdraw_len = stream_getw(s); + + /* Unfeasible Route Length check. */ + if (stream_pnt(s) + withdraw_len > end) { + flog_err(EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (packet unfeasible length overflow %d)", + peer->host, withdraw_len); + bgp_notify_send(connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + return BGP_Stop; + } + + /* Unfeasible Route packet format check. */ + if (withdraw_len > 0) { + nlris[NLRI_WITHDRAW].afi = AFI_IP; + nlris[NLRI_WITHDRAW].safi = SAFI_UNICAST; + nlris[NLRI_WITHDRAW].nlri = stream_pnt(s); + nlris[NLRI_WITHDRAW].length = withdraw_len; + stream_forward_getp(s, withdraw_len); + } + + /* Attribute total length check. */ + if (stream_pnt(s) + 2 > end) { + flog_warn( + EC_BGP_UPDATE_PACKET_SHORT, + "%s [Error] Packet Error (update packet is short for attribute length)", + peer->host); + bgp_notify_send(peer->connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + return BGP_Stop; + } + + /* Fetch attribute total length. */ + attribute_len = stream_getw(s); + + /* Attribute length check. */ + if (stream_pnt(s) + attribute_len > end) { + flog_warn( + EC_BGP_UPDATE_PACKET_LONG, + "%s [Error] Packet Error (update packet attribute length overflow %d)", + peer->host, attribute_len); + bgp_notify_send(connection, BGP_NOTIFY_UPDATE_ERR, + BGP_NOTIFY_UPDATE_MAL_ATTR); + return BGP_Stop; + } + + /* Certain attribute parsing errors should not be considered bad enough + * to reset the session for, most particularly any partial/optional + * attributes that have 'tunneled' over speakers that don't understand + * them. Instead we withdraw only the prefix concerned. + * + * Complicates the flow a little though.. + */ + enum bgp_attr_parse_ret attr_parse_ret = BGP_ATTR_PARSE_PROCEED; +/* This define morphs the update case into a withdraw when lower levels + * have signalled an error condition where this is best. + */ +#define NLRI_ATTR_ARG (attr_parse_ret != BGP_ATTR_PARSE_WITHDRAW ? &attr : NULL) + + /* Parse attribute when it exists. */ + if (attribute_len) { + attr_parse_ret = bgp_attr_parse(peer, &attr, attribute_len, + &nlris[NLRI_MP_UPDATE], + &nlris[NLRI_MP_WITHDRAW]); + if (attr_parse_ret == BGP_ATTR_PARSE_ERROR) { + bgp_attr_unintern_sub(&attr); + return BGP_Stop; + } + } + + /* Logging the attribute. */ + if (attr_parse_ret == BGP_ATTR_PARSE_WITHDRAW + || BGP_DEBUG(update, UPDATE_IN) + || BGP_DEBUG(update, UPDATE_PREFIX)) { + ret = bgp_dump_attr(&attr, peer->rcvd_attr_str, + sizeof(peer->rcvd_attr_str)); + + peer->stat_upd_7606++; + + if (attr_parse_ret == BGP_ATTR_PARSE_WITHDRAW) + flog_err( + EC_BGP_UPDATE_RCV, + "%pBP rcvd UPDATE with errors in attr(s)!! Withdrawing route.", + peer); + + if (ret && bgp_debug_update(peer, NULL, NULL, 1) && + BGP_DEBUG(update, UPDATE_DETAIL)) { + zlog_debug("%pBP rcvd UPDATE w/ attr: %s", peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + } + + /* Network Layer Reachability Information. */ + update_len = end - stream_pnt(s); + + /* If we received MP_UNREACH_NLRI attribute, but also NLRIs, then + * NLRIs should be handled as a new data. Though, if we received + * NLRIs without mandatory attributes, they should be ignored. + */ + if (update_len && attribute_len && + attr_parse_ret != BGP_ATTR_PARSE_MISSING_MANDATORY) { + /* Set NLRI portion to structure. */ + nlris[NLRI_UPDATE].afi = AFI_IP; + nlris[NLRI_UPDATE].safi = SAFI_UNICAST; + nlris[NLRI_UPDATE].nlri = stream_pnt(s); + nlris[NLRI_UPDATE].length = update_len; + stream_forward_getp(s, update_len); + + if (CHECK_FLAG(attr.flag, ATTR_FLAG_BIT(BGP_ATTR_MP_REACH_NLRI))) { + /* + * We skipped nexthop attribute validation earlier so + * validate the nexthop now. + */ + if (bgp_attr_nexthop_valid(peer, &attr) < 0) { + bgp_attr_unintern_sub(&attr); + return BGP_Stop; + } + } + } + + if (BGP_DEBUG(update, UPDATE_IN) && BGP_DEBUG(update, UPDATE_DETAIL)) + zlog_debug("%pBP rcvd UPDATE wlen %d attrlen %d alen %d", peer, + withdraw_len, attribute_len, update_len); + + /* Parse any given NLRIs */ + for (int i = NLRI_UPDATE; i < NLRI_TYPE_MAX; i++) { + if (!nlris[i].nlri) + continue; + + /* NLRI is processed iff the peer if configured for the specific + * afi/safi */ + if (!peer->afc[nlris[i].afi][nlris[i].safi]) { + zlog_info( + "%s [Info] UPDATE for non-enabled AFI/SAFI %u/%u", + peer->host, nlris[i].afi, nlris[i].safi); + continue; + } + + /* EoR handled later */ + if (nlris[i].length == 0) + continue; + + switch (i) { + case NLRI_UPDATE: + case NLRI_MP_UPDATE: + nlri_ret = bgp_nlri_parse(peer, NLRI_ATTR_ARG, + &nlris[i], 0); + break; + case NLRI_WITHDRAW: + case NLRI_MP_WITHDRAW: + nlri_ret = bgp_nlri_parse(peer, NLRI_ATTR_ARG, + &nlris[i], 1); + break; + default: + nlri_ret = BGP_NLRI_PARSE_ERROR; + } + + if (nlri_ret < BGP_NLRI_PARSE_OK + && nlri_ret != BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW) { + flog_err(EC_BGP_UPDATE_RCV, + "%s [Error] Error parsing NLRI", peer->host); + if (peer_established(connection)) + bgp_notify_send(connection, + BGP_NOTIFY_UPDATE_ERR, + i <= NLRI_WITHDRAW + ? BGP_NOTIFY_UPDATE_INVAL_NETWORK + : BGP_NOTIFY_UPDATE_OPT_ATTR_ERR); + bgp_attr_unintern_sub(&attr); + return BGP_Stop; + } + } + + /* EoR checks + * + * Non-MP IPv4/Unicast EoR is a completely empty UPDATE + * and MP EoR should have only an empty MP_UNREACH + */ + if (!update_len && !withdraw_len && nlris[NLRI_MP_UPDATE].length == 0) { + afi_t afi = 0; + safi_t safi; + struct graceful_restart_info *gr_info; + + /* Restarting router */ + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) + && BGP_PEER_RESTARTING_MODE(peer)) + restart = true; + + /* Non-MP IPv4/Unicast is a completely emtpy UPDATE - already + * checked + * update and withdraw NLRI lengths are 0. + */ + if (!attribute_len) { + afi = AFI_IP; + safi = SAFI_UNICAST; + } else if (attr.flag & ATTR_FLAG_BIT(BGP_ATTR_MP_UNREACH_NLRI) + && nlris[NLRI_MP_WITHDRAW].length == 0) { + afi = nlris[NLRI_MP_WITHDRAW].afi; + safi = nlris[NLRI_MP_WITHDRAW].safi; + } + + if (afi && peer->afc[afi][safi]) { + struct vrf *vrf = vrf_lookup_by_id(peer->bgp->vrf_id); + + /* End-of-RIB received */ + if (!CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED); + bgp_update_explicit_eors(peer); + /* Update graceful restart information */ + gr_info = &(peer->bgp->gr_info[afi][safi]); + if (restart) + gr_info->eor_received++; + /* If EOR received from all peers and selection + * deferral timer is running, cancel the timer + * and invoke the best path calculation + */ + if (gr_info->eor_required + == gr_info->eor_received) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s %d, %s %d", + "EOR REQ", + gr_info->eor_required, + "EOR RCV", + gr_info->eor_received); + if (gr_info->t_select_deferral) { + void *info = EVENT_ARG( + gr_info->t_select_deferral); + XFREE(MTYPE_TMP, info); + } + EVENT_OFF(gr_info->t_select_deferral); + gr_info->eor_required = 0; + gr_info->eor_received = 0; + /* Best path selection */ + bgp_best_path_select_defer(peer->bgp, + afi, safi); + } + } + + /* NSF delete stale route */ + if (peer->nsf[afi][safi]) + bgp_clear_stale_route(peer, afi, safi); + + zlog_info( + "%s: rcvd End-of-RIB for %s from %s in vrf %s", + __func__, get_afi_safi_str(afi, safi, false), + peer->host, vrf ? vrf->name : VRF_DEFAULT_NAME); + } + } + + /* Everything is done. We unintern temporary structures which + interned in bgp_attr_parse(). */ + bgp_attr_unintern_sub(&attr); + + peer->update_time = monotime(NULL); + + /* Notify BGP Conditional advertisement scanner process */ + peer->advmap_table_change = true; + + return Receive_UPDATE_message; +} + +/** + * Process BGP NOTIFY message for peer. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_notify_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + struct bgp_notify outer = {}; + struct bgp_notify inner = {}; + bool hard_reset = false; + + if (peer->notify.data) { + XFREE(MTYPE_BGP_NOTIFICATION, peer->notify.data); + peer->notify.length = 0; + peer->notify.hard_reset = false; + } + + outer.code = stream_getc(peer->curr); + outer.subcode = stream_getc(peer->curr); + outer.length = size - 2; + outer.data = NULL; + outer.raw_data = NULL; + if (outer.length) { + outer.raw_data = XMALLOC(MTYPE_BGP_NOTIFICATION, outer.length); + memcpy(outer.raw_data, stream_pnt(peer->curr), outer.length); + } + + hard_reset = + bgp_notify_received_hard_reset(peer, outer.code, outer.subcode); + if (hard_reset && outer.length) { + inner = bgp_notify_decapsulate_hard_reset(&outer); + peer->notify.hard_reset = true; + } else { + inner = outer; + } + + /* Preserv notify code and sub code. */ + peer->notify.code = inner.code; + peer->notify.subcode = inner.subcode; + /* For further diagnostic record returned Data. */ + if (inner.length) { + peer->notify.length = inner.length; + peer->notify.data = + XMALLOC(MTYPE_BGP_NOTIFICATION, inner.length); + memcpy(peer->notify.data, inner.raw_data, inner.length); + } + + /* For debug */ + { + int i; + int first = 0; + char c[4]; + + if (inner.length) { + inner.data = XMALLOC(MTYPE_BGP_NOTIFICATION, + inner.length * 3); + for (i = 0; i < inner.length; i++) + if (first) { + snprintf(c, sizeof(c), " %02x", + stream_getc(peer->curr)); + + strlcat(inner.data, c, + inner.length * 3); + + } else { + first = 1; + snprintf(c, sizeof(c), "%02x", + stream_getc(peer->curr)); + + strlcpy(inner.data, c, + inner.length * 3); + } + } + + bgp_notify_print(peer, &inner, "received", hard_reset); + if (inner.length) { + XFREE(MTYPE_BGP_NOTIFICATION, inner.data); + inner.length = 0; + } + if (outer.length) { + XFREE(MTYPE_BGP_NOTIFICATION, outer.data); + XFREE(MTYPE_BGP_NOTIFICATION, outer.raw_data); + + /* If this is a Hard Reset notification, we MUST free + * the inner (encapsulated) notification too. + */ + if (hard_reset) + XFREE(MTYPE_BGP_NOTIFICATION, inner.raw_data); + outer.length = 0; + } + } + + /* peer count update */ + atomic_fetch_add_explicit(&peer->notify_in, 1, memory_order_relaxed); + + peer->last_reset = PEER_DOWN_NOTIFY_RECEIVED; + + /* We have to check for Notify with Unsupported Optional Parameter. + in that case we fallback to open without the capability option. + But this done in bgp_stop. We just mark it here to avoid changing + the fsm tables. */ + if (inner.code == BGP_NOTIFY_OPEN_ERR && + inner.subcode == BGP_NOTIFY_OPEN_UNSUP_PARAM) + UNSET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); + + /* If Graceful-Restart N-bit (Notification) is exchanged, + * and it's not a Hard Reset, let's retain the routes. + */ + if (bgp_has_graceful_restart_notification(peer) && !hard_reset && + CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)) + SET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + + bgp_peer_gr_flags_update(peer); + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(peer->bgp, + peer->bgp->peer); + + return Receive_NOTIFICATION_message; +} + +/** + * Process BGP ROUTEREFRESH message for peer. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_route_refresh_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + struct stream *s; + struct peer_af *paf; + struct update_group *updgrp; + struct peer *updgrp_peer; + uint8_t subtype; + bool force_update = false; + bgp_size_t msg_length = + size - (BGP_MSG_ROUTE_REFRESH_MIN_SIZE - BGP_HEADER_SIZE); + + /* If peer does not have the capability, send notification. */ + if (!CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_ADV)) { + flog_err(EC_BGP_NO_CAP, + "%s [Error] BGP route refresh is not enabled", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_HEADER_ERR, + BGP_NOTIFY_HEADER_BAD_MESTYPE); + return BGP_Stop; + } + + /* Status must be Established. */ + if (!peer_established(connection)) { + flog_err(EC_BGP_INVALID_STATUS, + "%s [Error] Route refresh packet received under status %s", + peer->host, + lookup_msg(bgp_status_msg, peer->connection->status, + NULL)); + bgp_notify_send(connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(peer->connection->status)); + return BGP_Stop; + } + + s = peer->curr; + + /* Parse packet. */ + pkt_afi = stream_getw(s); + subtype = stream_getc(s); + pkt_safi = stream_getc(s); + + /* Convert AFI, SAFI to internal values and check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + zlog_info( + "%s REFRESH_REQ for unrecognized afi/safi: %s/%s - ignored", + peer->host, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + return BGP_PACKET_NOOP; + } + + if (size != BGP_MSG_ROUTE_REFRESH_MIN_SIZE - BGP_HEADER_SIZE) { + uint8_t *end; + uint8_t when_to_refresh; + uint8_t orf_type; + uint16_t orf_len; + + if (subtype) { + /* If the length, excluding the fixed-size message + * header, of the received ROUTE-REFRESH message with + * Message Subtype 1 and 2 is not 4, then the BGP + * speaker MUST send a NOTIFICATION message with the + * Error Code of "ROUTE-REFRESH Message Error" and the + * subcode of "Invalid Message Length". + */ + if (msg_length != 4) { + zlog_err( + "%s Enhanced Route Refresh message length error", + peer->host); + bgp_notify_send(connection, + BGP_NOTIFY_ROUTE_REFRESH_ERR, + BGP_NOTIFY_ROUTE_REFRESH_INVALID_MSG_LEN); + } + + /* When the BGP speaker receives a ROUTE-REFRESH message + * with a "Message Subtype" field other than 0, 1, or 2, + * it MUST ignore the received ROUTE-REFRESH message. + */ + if (subtype > 2) + zlog_err( + "%s Enhanced Route Refresh invalid subtype", + peer->host); + } + + if (msg_length < 5) { + zlog_info("%s ORF route refresh length error", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_SUBCODE_UNSPECIFIC); + return BGP_Stop; + } + + when_to_refresh = stream_getc(s); + end = stream_pnt(s) + (size - 5); + + while ((stream_pnt(s) + 2) < end) { + orf_type = stream_getc(s); + orf_len = stream_getw(s); + + /* orf_len in bounds? */ + if ((stream_pnt(s) + orf_len) > end) + break; /* XXX: Notify instead?? */ + if (orf_type == ORF_TYPE_PREFIX) { + uint8_t *p_pnt = stream_pnt(s); + uint8_t *p_end = stream_pnt(s) + orf_len; + struct orf_prefix orfp; + uint8_t common = 0; + uint32_t seq; + int psize; + char name[BUFSIZ]; + int ret = CMD_SUCCESS; + + if (bgp_debug_neighbor_events(peer)) { + zlog_debug( + "%pBP rcvd Prefixlist ORF(%d) length %d", + peer, orf_type, orf_len); + } + + /* ORF prefix-list name */ + snprintf(name, sizeof(name), "%s.%d.%d", + peer->host, afi, safi); + + /* we're going to read at least 1 byte of common + * ORF header, + * and 7 bytes of ORF Address-filter entry from + * the stream + */ + if (p_pnt < p_end && + *p_pnt & ORF_COMMON_PART_REMOVE_ALL) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd Remove-All pfxlist ORF request", + peer); + prefix_bgp_orf_remove_all(afi, name); + break; + } + + if (orf_len < 7) + break; + + while (p_pnt < p_end) { + /* If the ORF entry is malformed, want + * to read as much of it + * as possible without going beyond the + * bounds of the entry, + * to maximise debug information. + */ + int ok; + memset(&orfp, 0, sizeof(orfp)); + common = *p_pnt++; + /* after ++: p_pnt <= p_end */ + ok = ((uint32_t)(p_end - p_pnt) + >= sizeof(uint32_t)); + if (ok) { + memcpy(&seq, p_pnt, + sizeof(uint32_t)); + p_pnt += sizeof(uint32_t); + orfp.seq = ntohl(seq); + } else + p_pnt = p_end; + + /* val checked in prefix_bgp_orf_set */ + if (p_pnt < p_end) + orfp.ge = *p_pnt++; + + /* val checked in prefix_bgp_orf_set */ + if (p_pnt < p_end) + orfp.le = *p_pnt++; + + if ((ok = (p_pnt < p_end))) + orfp.p.prefixlen = *p_pnt++; + + /* afi checked already */ + orfp.p.family = afi2family(afi); + + /* 0 if not ok */ + psize = PSIZE(orfp.p.prefixlen); + /* valid for family ? */ + if (psize > prefix_blen(&orfp.p)) { + ok = 0; + psize = prefix_blen(&orfp.p); + } + /* valid for packet ? */ + if (psize > (p_end - p_pnt)) { + ok = 0; + psize = p_end - p_pnt; + } + + if (psize > 0) + memcpy(&orfp.p.u.prefix, p_pnt, + psize); + p_pnt += psize; + + if (bgp_debug_neighbor_events(peer)) { + char buf[INET6_BUFSIZ]; + + zlog_debug( + "%pBP rcvd %s %s seq %u %s/%d ge %d le %d%s", + peer, + (common & ORF_COMMON_PART_REMOVE + ? "Remove" + : "Add"), + (common & ORF_COMMON_PART_DENY + ? "deny" + : "permit"), + orfp.seq, + inet_ntop( + orfp.p.family, + &orfp.p.u.prefix, + buf, + INET6_BUFSIZ), + orfp.p.prefixlen, + orfp.ge, orfp.le, + ok ? "" : " MALFORMED"); + } + + if (ok) + ret = prefix_bgp_orf_set( + name, afi, &orfp, + (common & ORF_COMMON_PART_DENY + ? 0 + : 1), + (common & ORF_COMMON_PART_REMOVE + ? 0 + : 1)); + + if (!ok || (ok && ret != CMD_SUCCESS)) { + zlog_info( + "%pBP Received misformatted prefixlist ORF. Remove All pfxlist", + peer); + prefix_bgp_orf_remove_all(afi, + name); + break; + } + } + + peer->orf_plist[afi][safi] = + prefix_bgp_orf_lookup(afi, name); + } + stream_forward_getp(s, orf_len); + } + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP rcvd Refresh %s ORF request", peer, + when_to_refresh == REFRESH_DEFER + ? "Defer" + : "Immediate"); + if (when_to_refresh == REFRESH_DEFER) + return BGP_PACKET_NOOP; + } + + /* First update is deferred until ORF or ROUTE-REFRESH is received */ + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH)) + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH); + + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) { + if (peer->orf_plist[afi][safi]) { + updgrp = PAF_UPDGRP(paf); + updgrp_peer = UPDGRP_PEER(updgrp); + updgrp_peer->orf_plist[afi][safi] = + peer->orf_plist[afi][safi]; + } + + /* Avoid supressing duplicate routes later + * when processing in subgroup_announce_table(). + */ + force_update = true; + + /* If the peer is configured for default-originate clear the + * SUBGRP_STATUS_DEFAULT_ORIGINATE flag so that we will + * re-advertise the + * default + */ + if (CHECK_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) + UNSET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + } + + if (subtype == BGP_ROUTE_REFRESH_BORR) { + /* A BGP speaker that has received the Graceful Restart + * Capability from its neighbor MUST ignore any BoRRs for + * an from the neighbor before the speaker + * receives the EoR for the given from the + * neighbor. + */ + if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV) + && !CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (BoRR) for %s/%s before EoR", + peer, afi2str(afi), safi2str(safi)); + return BGP_PACKET_NOOP; + } + + if (peer->t_refresh_stalepath) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (BoRR) for %s/%s, whereas BoRR already received", + peer, afi2str(afi), safi2str(safi)); + return BGP_PACKET_NOOP; + } + + SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_BORR_RECEIVED); + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EORR_RECEIVED); + + /* When a BGP speaker receives a BoRR message from + * a peer, it MUST mark all the routes with the given + * Address Family Identifier and Subsequent Address + * Family Identifier, [RFC2918], from + * that peer as stale. + */ + if (peer_active_nego(peer)) { + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH); + bgp_set_stale_route(peer, afi, safi); + } + + if (peer_established(peer->connection)) + event_add_timer(bm->master, + bgp_refresh_stalepath_timer_expire, paf, + peer->bgp->stalepath_time, + &peer->t_refresh_stalepath); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (BoRR) for %s/%s, triggering timer for %u seconds", + peer, afi2str(afi), safi2str(safi), + peer->bgp->stalepath_time); + } else if (subtype == BGP_ROUTE_REFRESH_EORR) { + if (!peer->t_refresh_stalepath) { + zlog_err( + "%pBP rcvd route-refresh (EoRR) for %s/%s, whereas no BoRR received", + peer, afi2str(afi), safi2str(safi)); + return BGP_PACKET_NOOP; + } + + EVENT_OFF(peer->t_refresh_stalepath); + + SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_EORR_RECEIVED); + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_BORR_RECEIVED); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (EoRR) for %s/%s, stopping BoRR timer", + peer, afi2str(afi), safi2str(safi)); + + if (peer->nsf[afi][safi]) + bgp_clear_stale_route(peer, afi, safi); + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (REQUEST) for %s/%s", + peer, afi2str(afi), safi2str(safi)); + + /* In response to a "normal route refresh request" from the + * peer, the speaker MUST send a BoRR message. + */ + if (CHECK_FLAG(peer->cap, PEER_CAP_ENHANCED_RR_RCV)) { + /* For a BGP speaker that supports the BGP Graceful + * Restart, it MUST NOT send a BoRR for an + * to a neighbor before it sends the EoR for the + * to the neighbor. + */ + if (!CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP rcvd route-refresh (REQUEST) for %s/%s before EoR", + peer, afi2str(afi), + safi2str(safi)); + /* Can't send BoRR now, postpone after EoR */ + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_REFRESH_PENDING); + return BGP_PACKET_NOOP; + } + + bgp_route_refresh_send(peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_BORR); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP sending route-refresh (BoRR) for %s/%s", + peer, afi2str(afi), safi2str(safi)); + + /* Set flag Ready-To-Send to know when we can send EoRR + * message. + */ + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_BORR_SEND); + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EORR_SEND); + } + } + + /* Perform route refreshment to the peer */ + bgp_announce_route(peer, afi, safi, force_update); + + /* No FSM action necessary */ + return BGP_PACKET_NOOP; +} + +static void bgp_dynamic_capability_addpath(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + size_t len = end - data; + afi_t afi; + safi_t safi; + + if (action == CAPABILITY_ACTION_SET) { + if (len % CAPABILITY_CODE_ADDPATH_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Add Path: Received invalid length %zu, non-multiple of 4", + len); + return; + } + + SET_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV); + + while (data + CAPABILITY_CODE_ADDPATH_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + struct bgp_addpath_capability bac; + + memcpy(&bac, data, sizeof(bac)); + pkt_afi = ntohs(bac.afi); + pkt_safi = safi_int2iana(bac.safi); + + /* If any other value (other than 1-3) is received, + * then the capability SHOULD be treated as not + * understood and ignored. + */ + if (!bac.flags || bac.flags > 3) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Add Path: Received invalid send/receive value %u in Add Path capability", + bac.flags); + goto ignore; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s%s%s", + peer->host, + lookup_msg(capcode_str, hdr->code, + NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + (bac.flags & BGP_ADDPATH_RX) + ? ", receive" + : "", + (bac.flags & BGP_ADDPATH_TX) + ? ", transmit" + : ""); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Addpath Attribute for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the AddPath capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } + + if (CHECK_FLAG(bac.flags, BGP_ADDPATH_RX)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV); + else + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV); + + if (CHECK_FLAG(bac.flags, BGP_ADDPATH_TX)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV); + else + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV); + +ignore: + data += CAPABILITY_CODE_ADDPATH_LEN; + } + } else { + FOREACH_AFI_SAFI (afi, safi) { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV); + } + + UNSET_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV); + } +} + +static void bgp_dynamic_capability_paths_limit(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + size_t len = end - data; + afi_t afi; + safi_t safi; + + if (action == CAPABILITY_ACTION_SET) { + if (len % CAPABILITY_CODE_PATHS_LIMIT_LEN) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "Paths-Limit: Received invalid length %zu, non-multiple of %d", + len, CAPABILITY_CODE_PATHS_LIMIT_LEN); + return; + } + + if (!CHECK_FLAG(peer->cap, PEER_CAP_ADDPATH_RCV)) { + flog_warn(EC_BGP_CAPABILITY_INVALID_DATA, + "Paths-Limit: Received Paths-Limit capability without Add-Path capability"); + goto ignore; + } + + SET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + + while (data + CAPABILITY_CODE_PATHS_LIMIT_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + uint16_t paths_limit = 0; + struct bgp_paths_limit_capability bpl = {}; + + memcpy(&bpl, data, sizeof(bpl)); + pkt_afi = ntohs(bpl.afi); + pkt_safi = safi_int2iana(bpl.safi); + paths_limit = ntohs(bpl.paths_limit); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s OPEN has %s capability for afi/safi: %s/%s limit: %u", + peer->host, + lookup_msg(capcode_str, hdr->code, + NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), paths_limit); + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Paths-Limit capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto ignore; + } + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV); + peer->addpath_paths_limit[afi][safi].receive = + paths_limit; +ignore: + data += CAPABILITY_CODE_PATHS_LIMIT_LEN; + } + } else { + FOREACH_AFI_SAFI (afi, safi) + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV); + + UNSET_FLAG(peer->cap, PEER_CAP_PATHS_LIMIT_RCV); + } +} + +static void bgp_dynamic_capability_orf(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + size_t len = end - data; + + struct capability_mp_data mpc; + uint8_t num; + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + uint8_t type; + uint8_t mode; + uint16_t sm_cap = PEER_CAP_ORF_PREFIX_SM_RCV; + uint16_t rm_cap = PEER_CAP_ORF_PREFIX_RM_RCV; + int i; + + if (data + CAPABILITY_CODE_ORF_LEN > end) { + flog_warn(EC_BGP_CAPABILITY_INVALID_LENGTH, + "ORF: Received invalid length %zu, less than %d", len, + CAPABILITY_CODE_ORF_LEN); + return; + } + + /* ORF Entry header */ + memcpy(&mpc, data, sizeof(mpc)); + data += sizeof(mpc); + num = *data++; + pkt_afi = ntohs(mpc.afi); + pkt_safi = mpc.safi; + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, &safi)) { + zlog_info("%pBP Addr-family %d/%d not supported. Ignoring the ORF capability", + peer, pkt_afi, pkt_safi); + return; + } + + /* validate number field */ + if (CAPABILITY_CODE_ORF_LEN + (num * 2) > hdr->length) { + zlog_info("%pBP ORF Capability entry length error, Cap length %u, num %u", + peer, hdr->length, num); + return; + } + + if (action == CAPABILITY_ACTION_UNSET) { + UNSET_FLAG(peer->af_cap[afi][safi], sm_cap); + UNSET_FLAG(peer->af_cap[afi][safi], rm_cap); + return; + } + + for (i = 0; i < num; i++) { + if (data + 1 > end) { + flog_err(EC_BGP_CAPABILITY_INVALID_LENGTH, + "%pBP ORF Capability entry length (type) error, Cap length %u, num %u", + peer, hdr->length, num); + return; + } + type = *data++; + + if (data + 1 > end) { + flog_err(EC_BGP_CAPABILITY_INVALID_LENGTH, + "%pBP ORF Capability entry length (mode) error, Cap length %u, num %u", + peer, hdr->length, num); + return; + } + mode = *data++; + + /* ORF Mode error check */ + switch (mode) { + case ORF_MODE_BOTH: + case ORF_MODE_SEND: + case ORF_MODE_RECEIVE: + break; + default: + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP Addr-family %d/%d has ORF type/mode %d/%d not supported", + peer, afi, safi, type, mode); + continue; + } + + if (!((afi == AFI_IP && safi == SAFI_UNICAST) || + (afi == AFI_IP && safi == SAFI_MULTICAST) || + (afi == AFI_IP6 && safi == SAFI_UNICAST))) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP Addr-family %d/%d unsupported AFI/SAFI received", + peer, afi, safi); + continue; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP OPEN has %s ORF capability as %s for afi/safi: %s/%s", + peer, lookup_msg(orf_type_str, type, NULL), + lookup_msg(orf_mode_str, mode, NULL), + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + switch (mode) { + case ORF_MODE_BOTH: + SET_FLAG(peer->af_cap[afi][safi], sm_cap); + SET_FLAG(peer->af_cap[afi][safi], rm_cap); + break; + case ORF_MODE_SEND: + SET_FLAG(peer->af_cap[afi][safi], sm_cap); + UNSET_FLAG(peer->af_cap[afi][safi], rm_cap); + break; + case ORF_MODE_RECEIVE: + SET_FLAG(peer->af_cap[afi][safi], rm_cap); + UNSET_FLAG(peer->af_cap[afi][safi], sm_cap); + break; + } + } +} + +static void bgp_dynamic_capability_role(uint8_t *pnt, int action, + struct peer *peer) +{ + uint8_t role; + + if (action == CAPABILITY_ACTION_SET) { + SET_FLAG(peer->cap, PEER_CAP_ROLE_RCV); + memcpy(&role, pnt + 3, sizeof(role)); + + peer->remote_role = role; + } else { + UNSET_FLAG(peer->cap, PEER_CAP_ROLE_RCV); + peer->remote_role = ROLE_UNDEFINED; + } +} + +static void bgp_dynamic_capability_fqdn(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + char str[BGP_MAX_HOSTNAME + 1] = {}; + uint8_t len; + + if (action == CAPABILITY_ACTION_SET) { + /* hostname */ + if (data + 1 >= end) { + zlog_err("%pBP: Received invalid FQDN capability (host name length)", + peer); + return; + } + + len = *data; + if (data + len + 1 > end) { + zlog_err("%pBP: Received invalid FQDN capability length (host name) %d", + peer, hdr->length); + return; + } + data++; + + if (len > BGP_MAX_HOSTNAME) { + memcpy(&str, data, BGP_MAX_HOSTNAME); + str[BGP_MAX_HOSTNAME] = '\0'; + } else if (len) { + memcpy(&str, data, len); + str[len] = '\0'; + } + data += len; + + if (len) { + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + + peer->hostname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); + } + + if (data + 1 >= end) { + zlog_err("%pBP: Received invalid FQDN capability (domain name length)", + peer); + return; + } + + /* domainname */ + len = *data; + if (data + len + 1 > end) { + zlog_err("%pBP: Received invalid FQDN capability length (domain name) %d", + peer, len); + return; + } + data++; + + if (len > BGP_MAX_HOSTNAME) { + memcpy(&str, data, BGP_MAX_HOSTNAME); + str[BGP_MAX_HOSTNAME] = '\0'; + } else if (len) { + memcpy(&str, data, len); + str[len] = '\0'; + } + /* data += len; In case new code is ever added */ + + if (len) { + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + + peer->domainname = XSTRDUP(MTYPE_BGP_PEER_HOST, str); + } + + SET_FLAG(peer->cap, PEER_CAP_HOSTNAME_RCV); + } else { + UNSET_FLAG(peer->cap, PEER_CAP_HOSTNAME_RCV); + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + } +} + +static void bgp_dynamic_capability_llgr(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + size_t len = end - data; + afi_t afi; + safi_t safi; + + if (action == CAPABILITY_ACTION_SET) { + if (len < BGP_CAP_LLGR_MIN_PACKET_LEN) { + zlog_err("%pBP: Received invalid Long-Lived Graceful-Restart capability length %zu", + peer, len); + return; + } + + SET_FLAG(peer->cap, PEER_CAP_LLGR_RCV); + + while (data + BGP_CAP_LLGR_MIN_PACKET_LEN <= end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + struct graceful_restart_af graf; + + memcpy(&graf, data, sizeof(graf)); + pkt_afi = ntohs(graf.afi); + pkt_safi = safi_int2iana(graf.safi); + + /* Stale time is after AFI/SAFI/flags. + * It's encoded as 24 bits (= 3 bytes), so we need to + * put it into 32 bits. + */ + uint32_t stale_time; + uint8_t *stale_time_ptr = data + 4; + + stale_time = stale_time_ptr[0] << 16; + stale_time |= stale_time_ptr[1] << 8; + stale_time |= stale_time_ptr[2]; + + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not supported. Ignore the Long-lived Graceful Restart capability for this AFI/SAFI", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else if (!peer->afc[afi][safi] || + !CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) not enabled. Ignore the Long-lived Graceful Restart capability", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Addr-family %s/%s(afi/safi) Long-lived Graceful Restart capability stale time %u sec", + peer->host, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi), + stale_time); + + peer->llgr[afi][safi].flags = graf.flag; + peer->llgr[afi][safi].stale_time = + MIN(stale_time, + peer->bgp->llgr_stale_time); + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_LLGR_AF_RCV); + } + + data += BGP_CAP_LLGR_MIN_PACKET_LEN; + } + } else { + FOREACH_AFI_SAFI (afi, safi) { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_LLGR_AF_RCV); + + peer->llgr[afi][safi].flags = 0; + peer->llgr[afi][safi].stale_time = + BGP_DEFAULT_LLGR_STALE_TIME; + } + + UNSET_FLAG(peer->cap, PEER_CAP_LLGR_RCV); + } +} + +static void bgp_dynamic_capability_graceful_restart(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ +#define GRACEFUL_RESTART_CAPABILITY_PER_AFI_SAFI_SIZE 4 + uint16_t gr_restart_flag_time; + uint8_t *data = pnt + 3; + uint8_t *end = pnt + hdr->length; + size_t len = end - data; + afi_t afi; + safi_t safi; + + if (action == CAPABILITY_ACTION_SET) { + if (len < sizeof(gr_restart_flag_time)) { + zlog_err("%pBP: Received invalid Graceful-Restart capability length %d", + peer, hdr->length); + return; + } + + SET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); + ptr_get_be16(data, &gr_restart_flag_time); + data += sizeof(gr_restart_flag_time); + + if (CHECK_FLAG(gr_restart_flag_time, GRACEFUL_RESTART_R_BIT)) + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + else + UNSET_FLAG(peer->cap, + PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + + if (CHECK_FLAG(gr_restart_flag_time, GRACEFUL_RESTART_N_BIT)) + SET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + else + UNSET_FLAG(peer->cap, + PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + + UNSET_FLAG(gr_restart_flag_time, 0xF000); + peer->v_gr_restart = gr_restart_flag_time; + + while (data + GRACEFUL_RESTART_CAPABILITY_PER_AFI_SAFI_SIZE <= + end) { + afi_t afi; + safi_t safi; + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + struct graceful_restart_af graf; + + memcpy(&graf, data, sizeof(graf)); + pkt_afi = ntohs(graf.afi); + pkt_safi = safi_int2iana(graf.safi); + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: Addr-family %s/%s(afi/safi) not supported. Ignore the Graceful Restart capability for this AFI/SAFI", + peer, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else if (!peer->afc[afi][safi]) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: Addr-family %s/%s(afi/safi) not enabled. Ignore the Graceful Restart capability", + peer, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } else { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: Address family %s is%spreserved", + peer, + get_afi_safi_str(afi, safi, + false), + CHECK_FLAG(peer->af_cap[afi] + [safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV) + ? " " + : " not "); + + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV); + if (CHECK_FLAG(graf.flag, + GRACEFUL_RESTART_F_BIT)) + SET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV); + } + + data += GRACEFUL_RESTART_CAPABILITY_PER_AFI_SAFI_SIZE; + } + } else { + FOREACH_AFI_SAFI (afi, safi) { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV); + } + + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + UNSET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); + } +} + +static void bgp_dynamic_capability_software_version(uint8_t *pnt, int action, + struct capability_header *hdr, + struct peer *peer) +{ + uint8_t *data = pnt + 3; + uint8_t *end = data + hdr->length; + uint8_t len = *data; + char soft_version[BGP_MAX_SOFT_VERSION + 1] = {}; + + if (action == CAPABILITY_ACTION_SET) { + if (data + len + 1 > end) { + zlog_err("%pBP: Received invalid Software Version capability length %d", + peer, len); + return; + } + data++; + + if (len > BGP_MAX_SOFT_VERSION) + len = BGP_MAX_SOFT_VERSION; + + memcpy(&soft_version, data, len); + soft_version[len] = '\0'; + + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + peer->soft_version = XSTRDUP(MTYPE_BGP_SOFT_VERSION, + soft_version); + + SET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_RCV); + } else { + UNSET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_RCV); + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + } +} + +/** + * Parse BGP CAPABILITY message for peer. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +static int bgp_capability_msg_parse(struct peer *peer, uint8_t *pnt, + bgp_size_t length) +{ + uint8_t *end; + struct capability_mp_data mpc; + struct capability_header *hdr; + uint8_t action; + iana_afi_t pkt_afi; + afi_t afi; + iana_safi_t pkt_safi; + safi_t safi; + const char *capability; + + end = pnt + length; + + while (pnt < end) { + /* We need at least action, capability code and capability + * length. */ + if (pnt + 3 > end) { + zlog_err("%pBP: Capability length error", peer); + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_SUBCODE_UNSPECIFIC); + /* + * If we did not return then + * pnt += length; + */ + return BGP_Stop; + } + action = *pnt; + hdr = (struct capability_header *)(pnt + 1); + + /* Action value check. */ + if (action != CAPABILITY_ACTION_SET + && action != CAPABILITY_ACTION_UNSET) { + zlog_err("%pBP: Capability Action Value error %d", peer, + action); + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_SUBCODE_UNSPECIFIC); + goto done; + } + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: CAPABILITY has action: %d, code: %u, length %u", + peer, action, hdr->code, hdr->length); + + /* Capability length check. */ + if ((pnt + hdr->length + 3) > end) { + zlog_err("%pBP: Capability length error", peer); + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_SUBCODE_UNSPECIFIC); + /* + * If we did not return then + * pnt += length; + */ + return BGP_Stop; + } + + /* Ignore capability when override-capability is set. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_OVERRIDE_CAPABILITY)) + goto done; + + capability = lookup_msg(capcode_str, hdr->code, "Unknown"); + + /* Length sanity check, type-specific, for known capabilities */ + switch (hdr->code) { + case CAPABILITY_CODE_MP: + case CAPABILITY_CODE_REFRESH: + case CAPABILITY_CODE_ORF: + case CAPABILITY_CODE_RESTART: + case CAPABILITY_CODE_AS4: + case CAPABILITY_CODE_ADDPATH: + case CAPABILITY_CODE_DYNAMIC: + case CAPABILITY_CODE_ENHE: + case CAPABILITY_CODE_FQDN: + case CAPABILITY_CODE_ENHANCED_RR: + case CAPABILITY_CODE_EXT_MESSAGE: + case CAPABILITY_CODE_ROLE: + case CAPABILITY_CODE_SOFT_VERSION: + case CAPABILITY_CODE_PATHS_LIMIT: + if (hdr->length < cap_minsizes[hdr->code]) { + zlog_info("%pBP: %s Capability length error: got %u, expected at least %u", + peer, capability, hdr->length, + (unsigned int)cap_minsizes[hdr->code]); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + goto done; + } + if (hdr->length && + hdr->length % cap_modsizes[hdr->code] != 0) { + zlog_info("%pBP %s Capability length error: got %u, expected a multiple of %u", + peer, capability, hdr->length, + (unsigned int)cap_modsizes[hdr->code]); + bgp_notify_send(peer->connection, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_MALFORMED_ATTR); + goto done; + } + break; + default: + break; + } + + switch (hdr->code) { + case CAPABILITY_CODE_SOFT_VERSION: + bgp_dynamic_capability_software_version(pnt, action, + hdr, peer); + break; + case CAPABILITY_CODE_MP: + if (hdr->length < sizeof(struct capability_mp_data)) { + zlog_err("%pBP: Capability (%s) structure is not properly filled out, expected at least %zu bytes but header length specified is %d", + peer, capability, + sizeof(struct capability_mp_data), + hdr->length); + goto done; + } + + memcpy(&mpc, pnt + 3, sizeof(struct capability_mp_data)); + pkt_afi = ntohs(mpc.afi); + pkt_safi = mpc.safi; + + /* Convert AFI, SAFI to internal values. */ + if (bgp_map_afi_safi_iana2int(pkt_afi, pkt_safi, &afi, + &safi)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: Dynamic Capability %s afi/safi invalid (%s/%s)", + peer, capability, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + goto done; + } + + /* Address family check. */ + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP: CAPABILITY has %s %s CAP for afi/safi: %s/%s", + peer, + action == CAPABILITY_ACTION_SET + ? "Advertising" + : "Removing", + capability, iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + + if (action == CAPABILITY_ACTION_SET) { + peer->afc_recv[afi][safi] = 1; + if (peer->afc[afi][safi]) { + peer->afc_nego[afi][safi] = 1; + bgp_announce_route(peer, afi, safi, + false); + } + } else { + peer->afc_recv[afi][safi] = 0; + peer->afc_nego[afi][safi] = 0; + + if (peer_active_nego(peer)) + bgp_clear_route(peer, afi, safi); + else + goto done; + } + break; + case CAPABILITY_CODE_RESTART: + bgp_dynamic_capability_graceful_restart(pnt, action, + hdr, peer); + break; + case CAPABILITY_CODE_LLGR: + bgp_dynamic_capability_llgr(pnt, action, hdr, peer); + break; + case CAPABILITY_CODE_ADDPATH: + bgp_dynamic_capability_addpath(pnt, action, hdr, peer); + break; + case CAPABILITY_CODE_PATHS_LIMIT: + bgp_dynamic_capability_paths_limit(pnt, action, hdr, + peer); + break; + case CAPABILITY_CODE_ORF: + bgp_dynamic_capability_orf(pnt, action, hdr, peer); + break; + case CAPABILITY_CODE_FQDN: + bgp_dynamic_capability_fqdn(pnt, action, hdr, peer); + break; + case CAPABILITY_CODE_REFRESH: + case CAPABILITY_CODE_AS4: + case CAPABILITY_CODE_DYNAMIC: + case CAPABILITY_CODE_ENHANCED_RR: + case CAPABILITY_CODE_ENHE: + case CAPABILITY_CODE_EXT_MESSAGE: + break; + case CAPABILITY_CODE_ROLE: + bgp_dynamic_capability_role(pnt, action, peer); + break; + default: + flog_warn(EC_BGP_UNRECOGNIZED_CAPABILITY, + "%pBP: unrecognized capability code: %d - ignored", + peer, hdr->code); + break; + } + +done: + pnt += hdr->length + 3; + } + + /* No FSM action necessary */ + return BGP_PACKET_NOOP; +} + +/** + * Parse BGP CAPABILITY message for peer. + * + * Exported for unit testing. + * + * @param peer + * @param size size of the packet + * @return as in summary + */ +int bgp_capability_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t size) +{ + uint8_t *pnt; + + /* Fetch pointer. */ + pnt = stream_pnt(peer->curr); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s rcv CAPABILITY", peer->host); + + if (!CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV) || + !CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV)) { + flog_err(EC_BGP_NO_CAP, + "%s [Error] BGP dynamic capability is not enabled", + peer->host); + bgp_notify_send(connection, BGP_NOTIFY_HEADER_ERR, + BGP_NOTIFY_HEADER_BAD_MESTYPE); + return BGP_Stop; + } + + /* Status must be Established. */ + if (!peer_established(connection)) { + flog_err(EC_BGP_NO_CAP, + "%s [Error] Dynamic capability packet received under status %s", + peer->host, + lookup_msg(bgp_status_msg, connection->status, NULL)); + bgp_notify_send(connection, BGP_NOTIFY_FSM_ERR, + bgp_fsm_error_subcode(connection->status)); + return BGP_Stop; + } + + /* Parse packet. */ + return bgp_capability_msg_parse(peer, pnt, size); +} + +/** + * Processes a peer's input buffer. + * + * This function sidesteps the event loop and directly calls bgp_event_update() + * after processing each BGP message. This is necessary to ensure proper + * ordering of FSM events and unifies the behavior that was present previously, + * whereby some of the packet handling functions would update the FSM and some + * would not, making event flow difficult to understand. Please think twice + * before hacking this. + * + * Thread type: EVENT_EVENT + * @param thread + * @return 0 + */ +void bgp_process_packet(struct event *thread) +{ + /* Yes first of all get peer pointer. */ + struct peer *peer; // peer + struct peer_connection *connection; + uint32_t rpkt_quanta_old; // how many packets to read + int fsm_update_result; // return code of bgp_event_update() + int mprc; // message processing return code + + connection = EVENT_ARG(thread); + peer = connection->peer; + rpkt_quanta_old = atomic_load_explicit(&peer->bgp->rpkt_quanta, + memory_order_relaxed); + fsm_update_result = 0; + + /* Guard against scheduled events that occur after peer deletion. */ + if (connection->status == Deleted || connection->status == Clearing) + return; + + unsigned int processed = 0; + + while (processed < rpkt_quanta_old) { + uint8_t type = 0; + bgp_size_t size; + char notify_data_length[2]; + + frr_with_mutex (&connection->io_mtx) { + peer->curr = stream_fifo_pop(connection->ibuf); + } + + if (peer->curr == NULL) // no packets to process, hmm... + return; + + /* skip the marker and copy the packet length */ + stream_forward_getp(peer->curr, BGP_MARKER_SIZE); + memcpy(notify_data_length, stream_pnt(peer->curr), 2); + + /* read in the packet length and type */ + size = stream_getw(peer->curr); + type = stream_getc(peer->curr); + + hook_call(bgp_packet_dump, peer, type, size, peer->curr); + + /* adjust size to exclude the marker + length + type */ + size -= BGP_HEADER_SIZE; + + /* Read rest of the packet and call each sort of packet routine + */ + switch (type) { + case BGP_MSG_OPEN: + frrtrace(2, frr_bgp, open_process, peer, size); + atomic_fetch_add_explicit(&peer->open_in, 1, + memory_order_relaxed); + mprc = bgp_open_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_PKT_OPEN, + "%s: BGP OPEN receipt failed for peer: %s", + __func__, peer->host); + break; + case BGP_MSG_UPDATE: + frrtrace(2, frr_bgp, update_process, peer, size); + atomic_fetch_add_explicit(&peer->update_in, 1, + memory_order_relaxed); + peer->readtime = monotime(NULL); + mprc = bgp_update_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_UPDATE_RCV, + "%s: BGP UPDATE receipt failed for peer: %s", + __func__, peer->host); + break; + case BGP_MSG_NOTIFY: + frrtrace(2, frr_bgp, notification_process, peer, size); + atomic_fetch_add_explicit(&peer->notify_in, 1, + memory_order_relaxed); + mprc = bgp_notify_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_NOTIFY_RCV, + "%s: BGP NOTIFY receipt failed for peer: %s", + __func__, peer->host); + break; + case BGP_MSG_KEEPALIVE: + frrtrace(2, frr_bgp, keepalive_process, peer, size); + peer->readtime = monotime(NULL); + atomic_fetch_add_explicit(&peer->keepalive_in, 1, + memory_order_relaxed); + mprc = bgp_keepalive_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_KEEP_RCV, + "%s: BGP KEEPALIVE receipt failed for peer: %s", + __func__, peer->host); + break; + case BGP_MSG_ROUTE_REFRESH_NEW: + case BGP_MSG_ROUTE_REFRESH_OLD: + frrtrace(2, frr_bgp, refresh_process, peer, size); + atomic_fetch_add_explicit(&peer->refresh_in, 1, + memory_order_relaxed); + mprc = bgp_route_refresh_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_RFSH_RCV, + "%s: BGP ROUTEREFRESH receipt failed for peer: %s", + __func__, peer->host); + break; + case BGP_MSG_CAPABILITY: + frrtrace(2, frr_bgp, capability_process, peer, size); + atomic_fetch_add_explicit(&peer->dynamic_cap_in, 1, + memory_order_relaxed); + mprc = bgp_capability_receive(connection, peer, size); + if (mprc == BGP_Stop) + flog_err( + EC_BGP_CAP_RCV, + "%s: BGP CAPABILITY receipt failed for peer: %s", + __func__, peer->host); + break; + default: + /* Suppress uninitialized variable warning */ + mprc = 0; + (void)mprc; + /* + * The message type should have been sanitized before + * we ever got here. Receipt of a message with an + * invalid header at this point is indicative of a + * security issue. + */ + assert (!"Message of invalid type received during input processing"); + } + + /* delete processed packet */ + stream_free(peer->curr); + peer->curr = NULL; + processed++; + + /* Update FSM */ + if (mprc != BGP_PACKET_NOOP) + fsm_update_result = bgp_event_update(connection, mprc); + else + continue; + + /* + * If peer was deleted, do not process any more packets. This + * is usually due to executing BGP_Stop or a stub deletion. + */ + if (fsm_update_result == FSM_PEER_TRANSFERRED + || fsm_update_result == FSM_PEER_STOPPED) + break; + } + + if (fsm_update_result != FSM_PEER_TRANSFERRED + && fsm_update_result != FSM_PEER_STOPPED) { + frr_with_mutex (&connection->io_mtx) { + // more work to do, come back later + if (connection->ibuf->count > 0) + event_add_event(bm->master, bgp_process_packet, + connection, 0, + &connection->t_process_packet); + } + } +} + +/* Send EOR when routes are processed by selection deferral timer */ +void bgp_send_delayed_eor(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node, *nnode; + + /* EOR message sent in bgp_write_proceed_actions */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_write_proceed_actions(peer); +} + +/* + * Task callback to handle socket error encountered in the io pthread. We avoid + * having the io pthread try to enqueue fsm events or mess with the peer + * struct. + */ +void bgp_packet_process_error(struct event *thread) +{ + struct peer_connection *connection; + struct peer *peer; + int code; + + connection = EVENT_ARG(thread); + peer = connection->peer; + code = EVENT_VAL(thread); + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s [Event] BGP error %d on fd %d", peer->host, code, + connection->fd); + + /* Closed connection or error on the socket */ + if (peer_established(connection)) { + if ((CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART) + || CHECK_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART_HELPER)) + && CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)) { + peer->last_reset = PEER_DOWN_NSF_CLOSE_SESSION; + SET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + } else + peer->last_reset = PEER_DOWN_CLOSE_SESSION; + } + + bgp_event_update(connection, code); +} diff --git a/bgpd/bgp_packet.h b/bgpd/bgp_packet.h new file mode 100644 index 0000000..b67acf2 --- /dev/null +++ b/bgpd/bgp_packet.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP packet management header. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_PACKET_H +#define _QUAGGA_BGP_PACKET_H + +#include "hook.h" + +DECLARE_HOOK(bgp_packet_dump, + (struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *s), + (peer, type, size, s)); + +DECLARE_HOOK(bgp_packet_send, + (struct peer *peer, uint8_t type, bgp_size_t size, + struct stream *s), + (peer, type, size, s)); + +#define BGP_NLRI_LENGTH 1U +#define BGP_TOTAL_ATTR_LEN 2U +#define BGP_UNFEASIBLE_LEN 2U + +/* When to refresh */ +#define REFRESH_IMMEDIATE 1 +#define REFRESH_DEFER 2 + +/* ORF Common part flag */ +#define ORF_COMMON_PART_ADD 0x00 +#define ORF_COMMON_PART_REMOVE 0x80 +#define ORF_COMMON_PART_REMOVE_ALL 0xC0 +#define ORF_COMMON_PART_PERMIT 0x00 +#define ORF_COMMON_PART_DENY 0x20 + +#define BGP_UPDATE_EOR_PKT(_peer, _afi, _safi, _s) \ + do { \ + _s = bgp_update_packet_eor(_peer, _afi, _safi); \ + if (_s) { \ + bgp_packet_add(_peer->connection, _peer, _s); \ + } \ + } while (0) + +/* Packet send and receive function prototypes. */ +extern void bgp_keepalive_send(struct peer *peer); +extern void bgp_open_send(struct peer_connection *connection); +extern void bgp_notify_send(struct peer_connection *connection, uint8_t code, + uint8_t sub_code); +extern void bgp_notify_send_with_data(struct peer_connection *connection, + uint8_t code, uint8_t sub_code, + uint8_t *data, size_t datalen); +void bgp_notify_io_invalid(struct peer *peer, uint8_t code, uint8_t sub_code, + uint8_t *data, size_t datalen); +extern void bgp_route_refresh_send(struct peer *peer, afi_t afi, safi_t safi, + uint8_t orf_type, uint8_t when_to_refresh, + int remove, uint8_t subtype); +extern void bgp_capability_send(struct peer *peer, afi_t afi, safi_t safi, + int capabilty_code, int action); + +extern int bgp_capability_receive(struct peer_connection *connection, + struct peer *peer, bgp_size_t length); +extern int bgp_nlri_parse(struct peer *peer, struct attr *attr, + struct bgp_nlri *nlri, bool mp_withdraw); + +extern void bgp_update_restarted_peers(struct peer *peer); +extern void bgp_update_implicit_eors(struct peer *peer); +extern void bgp_check_update_delay(struct bgp *peer); + +extern int bgp_packet_set_marker(struct stream *s, uint8_t type); +extern void bgp_packet_set_size(struct stream *s); + +extern void bgp_generate_updgrp_packets(struct event *event); +extern void bgp_process_packet(struct event *event); + +extern void bgp_send_delayed_eor(struct bgp *bgp); + +/* Task callback to handle socket error encountered in the io pthread */ +void bgp_packet_process_error(struct event *thread); +extern struct bgp_notify +bgp_notify_decapsulate_hard_reset(struct bgp_notify *notify); +extern bool bgp_has_graceful_restart_notification(struct peer *peer); +extern bool bgp_notify_send_hard_reset(struct peer *peer, uint8_t code, + uint8_t subcode); +extern bool bgp_notify_received_hard_reset(struct peer *peer, uint8_t code, + uint8_t subcode); + +#endif /* _QUAGGA_BGP_PACKET_H */ diff --git a/bgpd/bgp_pbr.c b/bgpd/bgp_pbr.c new file mode 100644 index 0000000..43682de --- /dev/null +++ b/bgpd/bgp_pbr.c @@ -0,0 +1,2936 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP pbr + * Copyright (C) 6WIND + */ + +#include "zebra.h" +#include "prefix.h" +#include "zclient.h" +#include "jhash.h" +#include "pbr.h" + +#include "lib/printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_flowspec_private.h" +#include "bgpd/bgp_errors.h" + +DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH_ENTRY, "PBR match entry"); +DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH, "PBR match"); +DEFINE_MTYPE_STATIC(BGPD, PBR_ACTION, "PBR action"); +DEFINE_MTYPE_STATIC(BGPD, PBR_RULE, "PBR rule"); +DEFINE_MTYPE_STATIC(BGPD, PBR, "BGP PBR Context"); +DEFINE_MTYPE_STATIC(BGPD, PBR_VALMASK, "BGP PBR Val Mask Value"); + +/* chain strings too long to fit in one line */ +#define FSPEC_ACTION_EXCEED_LIMIT "flowspec actions exceeds limit" +#define IPV6_FRAGMENT_INVALID "fragment not valid for IPv6 for this implementation" + +RB_GENERATE(bgp_pbr_interface_head, bgp_pbr_interface, + id_entry, bgp_pbr_interface_compare); +struct bgp_pbr_interface_head ifaces_by_name_ipv4 = + RB_INITIALIZER(&ifaces_by_name_ipv4); + +static int bgp_pbr_match_counter_unique; +static int bgp_pbr_match_entry_counter_unique; +static int bgp_pbr_action_counter_unique; +static int bgp_pbr_match_iptable_counter_unique; + +struct bgp_pbr_match_iptable_unique { + uint32_t unique; + struct bgp_pbr_match *bpm_found; +}; + +struct bgp_pbr_match_entry_unique { + uint32_t unique; + struct bgp_pbr_match_entry *bpme_found; +}; + +struct bgp_pbr_action_unique { + uint32_t unique; + struct bgp_pbr_action *bpa_found; +}; + +struct bgp_pbr_rule_unique { + uint32_t unique; + struct bgp_pbr_rule *bpr_found; +}; + +static int bgp_pbr_rule_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_rule *bpr = (struct bgp_pbr_rule *)bucket->data; + struct bgp_pbr_rule_unique *bpru = (struct bgp_pbr_rule_unique *) + arg; + uint32_t unique = bpru->unique; + + if (bpr->unique == unique) { + bpru->bpr_found = bpr; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +static int bgp_pbr_action_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_action *bpa = (struct bgp_pbr_action *)bucket->data; + struct bgp_pbr_action_unique *bpau = (struct bgp_pbr_action_unique *) + arg; + uint32_t unique = bpau->unique; + + if (bpa->unique == unique) { + bpau->bpa_found = bpa; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +static int bgp_pbr_match_entry_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_match_entry *bpme = + (struct bgp_pbr_match_entry *)bucket->data; + struct bgp_pbr_match_entry_unique *bpmeu = + (struct bgp_pbr_match_entry_unique *)arg; + uint32_t unique = bpmeu->unique; + + if (bpme->unique == unique) { + bpmeu->bpme_found = bpme; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +struct bgp_pbr_match_ipsetname { + char *ipsetname; + struct bgp_pbr_match *bpm_found; +}; + +static int bgp_pbr_match_pername_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; + struct bgp_pbr_match_ipsetname *bpmi = + (struct bgp_pbr_match_ipsetname *)arg; + char *ipset_name = bpmi->ipsetname; + + if (!strncmp(ipset_name, bpm->ipset_name, + ZEBRA_IPSET_NAME_SIZE)) { + bpmi->bpm_found = bpm; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +static int bgp_pbr_match_iptable_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; + struct bgp_pbr_match_iptable_unique *bpmiu = + (struct bgp_pbr_match_iptable_unique *)arg; + uint32_t unique = bpmiu->unique; + + if (bpm->unique2 == unique) { + bpmiu->bpm_found = bpm; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +struct bgp_pbr_match_unique { + uint32_t unique; + struct bgp_pbr_match *bpm_found; +}; + +static int bgp_pbr_match_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; + struct bgp_pbr_match_unique *bpmu = (struct bgp_pbr_match_unique *) + arg; + uint32_t unique = bpmu->unique; + + if (bpm->unique == unique) { + bpmu->bpm_found = bpm; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +static int snprintf_bgp_pbr_match_val(char *str, int len, + struct bgp_pbr_match_val *mval, + const char *prepend) +{ + char *ptr = str; + int delta; + + if (prepend) { + delta = snprintf(ptr, len, "%s", prepend); + ptr += delta; + len -= delta; + } else { + if (mval->unary_operator & OPERATOR_UNARY_OR) { + delta = snprintf(ptr, len, ", or "); + ptr += delta; + len -= delta; + } + if (mval->unary_operator & OPERATOR_UNARY_AND) { + delta = snprintf(ptr, len, ", and "); + ptr += delta; + len -= delta; + } + } + if (mval->compare_operator & OPERATOR_COMPARE_LESS_THAN) { + delta = snprintf(ptr, len, "<"); + ptr += delta; + len -= delta; + } + if (mval->compare_operator & OPERATOR_COMPARE_GREATER_THAN) { + delta = snprintf(ptr, len, ">"); + ptr += delta; + len -= delta; + } + if (mval->compare_operator & OPERATOR_COMPARE_EQUAL_TO) { + delta = snprintf(ptr, len, "="); + ptr += delta; + len -= delta; + } + if (mval->compare_operator & OPERATOR_COMPARE_EXACT_MATCH) { + delta = snprintf(ptr, len, "match"); + ptr += delta; + len -= delta; + } + ptr += snprintf(ptr, len, " %u", mval->value); + return (int)(ptr - str); +} + +#define INCREMENT_DISPLAY(_ptr, _cnt, _len) do { \ + int sn_delta; \ + \ + if (_cnt) { \ + sn_delta = snprintf((_ptr), (_len), "; ");\ + (_len) -= sn_delta; \ + (_ptr) += sn_delta; \ + } \ + (_cnt)++; \ + } while (0) + +/* this structure can be used for port range, + * but also for other values range like packet length range + */ +struct bgp_pbr_range_port { + uint16_t min_port; + uint16_t max_port; +}; + +/* this structure can be used to filter with a mask + * for instance it supports not instructions like for + * tcpflags + */ +struct bgp_pbr_val_mask { + uint16_t val; + uint16_t mask; +}; + +/* this structure is used to pass instructs + * so that BGP can create pbr instructions to ZEBRA + */ +struct bgp_pbr_filter { + uint8_t type; + vrf_id_t vrf_id; + uint8_t family; + struct prefix *src; + struct prefix *dst; + uint8_t bitmask_iprule; + uint8_t protocol; + struct bgp_pbr_range_port *pkt_len; + struct bgp_pbr_range_port *src_port; + struct bgp_pbr_range_port *dst_port; + struct bgp_pbr_val_mask *tcp_flags; + struct bgp_pbr_val_mask *dscp; + struct bgp_pbr_val_mask *flow_label; + struct bgp_pbr_val_mask *pkt_len_val; + struct bgp_pbr_val_mask *fragment; +}; + +/* this structure is used to contain OR instructions + * so that BGP can create multiple pbr instructions + * to ZEBRA + */ +struct bgp_pbr_or_filter { + struct list *tcpflags; + struct list *dscp; + struct list *flowlabel; + struct list *pkt_len; + struct list *fragment; + struct list *icmp_type; + struct list *icmp_code; +}; + +static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp, + struct bgp_path_info *path, + struct bgp_pbr_filter *bpf, + struct nexthop *nh, + float *rate); + +static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add); + +static bool bgp_pbr_extract_enumerate_unary_opposite( + uint8_t unary_operator, + struct bgp_pbr_val_mask *and_valmask, + struct list *or_valmask, uint32_t value, + uint8_t type_entry) +{ + if (unary_operator == OPERATOR_UNARY_AND && and_valmask) { + if (type_entry == FLOWSPEC_TCP_FLAGS) { + and_valmask->mask |= + TCP_HEADER_ALL_FLAGS & + ~(value); + } else if (type_entry == FLOWSPEC_DSCP || + type_entry == FLOWSPEC_FLOW_LABEL || + type_entry == FLOWSPEC_PKT_LEN || + type_entry == FLOWSPEC_FRAGMENT) { + and_valmask->val = value; + and_valmask->mask = 1; /* inverse */ + } + } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) { + and_valmask = XCALLOC(MTYPE_PBR_VALMASK, + sizeof(struct bgp_pbr_val_mask)); + if (type_entry == FLOWSPEC_TCP_FLAGS) { + and_valmask->val = TCP_HEADER_ALL_FLAGS; + and_valmask->mask |= + TCP_HEADER_ALL_FLAGS & + ~(value); + } else if (type_entry == FLOWSPEC_DSCP || + type_entry == FLOWSPEC_FLOW_LABEL || + type_entry == FLOWSPEC_FRAGMENT || + type_entry == FLOWSPEC_PKT_LEN) { + and_valmask->val = value; + and_valmask->mask = 1; /* inverse */ + } + listnode_add(or_valmask, and_valmask); + } else if (type_entry == FLOWSPEC_ICMP_CODE || + type_entry == FLOWSPEC_ICMP_TYPE) + return false; + return true; +} + +/* TCP : FIN and SYN -> val = ALL; mask = 3 + * TCP : not (FIN and SYN) -> val = ALL; mask = ALL & ~(FIN|RST) + * other variables type: dscp, pkt len, fragment, flow label + * - value is copied in bgp_pbr_val_mask->val value + * - if negate form is identifierd, bgp_pbr_val_mask->mask set to 1 + */ +static bool bgp_pbr_extract_enumerate_unary(struct bgp_pbr_match_val list[], + int num, uint8_t unary_operator, + void *valmask, uint8_t type_entry) +{ + int i = 0; + struct bgp_pbr_val_mask *and_valmask = NULL; + struct list *or_valmask = NULL; + bool ret; + + if (valmask) { + if (unary_operator == OPERATOR_UNARY_AND) { + and_valmask = (struct bgp_pbr_val_mask *)valmask; + memset(and_valmask, 0, sizeof(struct bgp_pbr_val_mask)); + } else if (unary_operator == OPERATOR_UNARY_OR) { + or_valmask = (struct list *)valmask; + } + } + for (i = 0; i < num; i++) { + if (i != 0 && list[i].unary_operator != + unary_operator) + return false; + if (!(list[i].compare_operator & + OPERATOR_COMPARE_EQUAL_TO) && + !(list[i].compare_operator & + OPERATOR_COMPARE_EXACT_MATCH)) { + if ((list[i].compare_operator & + OPERATOR_COMPARE_LESS_THAN) && + (list[i].compare_operator & + OPERATOR_COMPARE_GREATER_THAN)) { + ret = bgp_pbr_extract_enumerate_unary_opposite( + unary_operator, and_valmask, + or_valmask, list[i].value, + type_entry); + if (!ret) + return ret; + continue; + } + return false; + } + if (unary_operator == OPERATOR_UNARY_AND && and_valmask) { + if (type_entry == FLOWSPEC_TCP_FLAGS) + and_valmask->mask |= + TCP_HEADER_ALL_FLAGS & list[i].value; + } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) { + and_valmask = XCALLOC(MTYPE_PBR_VALMASK, + sizeof(struct bgp_pbr_val_mask)); + if (type_entry == FLOWSPEC_TCP_FLAGS) { + and_valmask->val = TCP_HEADER_ALL_FLAGS; + and_valmask->mask |= + TCP_HEADER_ALL_FLAGS & list[i].value; + } else if (type_entry == FLOWSPEC_DSCP || + type_entry == FLOWSPEC_FLOW_LABEL || + type_entry == FLOWSPEC_ICMP_TYPE || + type_entry == FLOWSPEC_ICMP_CODE || + type_entry == FLOWSPEC_FRAGMENT || + type_entry == FLOWSPEC_PKT_LEN) + and_valmask->val = list[i].value; + listnode_add(or_valmask, and_valmask); + } + } + if (unary_operator == OPERATOR_UNARY_AND && and_valmask + && type_entry == FLOWSPEC_TCP_FLAGS) + and_valmask->val = TCP_HEADER_ALL_FLAGS; + return true; +} + +/* if unary operator can either be UNARY_OR/AND/OR-AND. + * in the latter case, combinationf of both is not handled + */ +static bool bgp_pbr_extract_enumerate(struct bgp_pbr_match_val list[], + int num, uint8_t unary_operator, + void *valmask, uint8_t type_entry) +{ + bool ret; + uint8_t unary_operator_val; + bool double_check = false; + + if ((unary_operator & OPERATOR_UNARY_OR) && + (unary_operator & OPERATOR_UNARY_AND)) { + unary_operator_val = OPERATOR_UNARY_AND; + double_check = true; + } else + unary_operator_val = unary_operator; + ret = bgp_pbr_extract_enumerate_unary(list, num, unary_operator_val, + valmask, type_entry); + if (!ret && double_check) + ret = bgp_pbr_extract_enumerate_unary(list, num, + OPERATOR_UNARY_OR, + valmask, + type_entry); + return ret; +} + +/* returns the unary operator that is in the list + * return 0 if both operators are used + */ +static uint8_t bgp_pbr_match_val_get_operator(struct bgp_pbr_match_val list[], + int num) + +{ + int i; + uint8_t unary_operator = OPERATOR_UNARY_AND; + + for (i = 0; i < num; i++) { + if (i == 0) + continue; + if (list[i].unary_operator & OPERATOR_UNARY_OR) + unary_operator = OPERATOR_UNARY_OR; + if ((list[i].unary_operator & OPERATOR_UNARY_AND + && unary_operator == OPERATOR_UNARY_OR) || + (list[i].unary_operator & OPERATOR_UNARY_OR + && unary_operator == OPERATOR_UNARY_AND)) + return 0; + } + return unary_operator; +} + + +/* return true if extraction ok + */ +static bool bgp_pbr_extract(struct bgp_pbr_match_val list[], + int num, + struct bgp_pbr_range_port *range) +{ + int i = 0; + bool exact_match = false; + + if (range) + memset(range, 0, sizeof(struct bgp_pbr_range_port)); + + if (num > 2) + return false; + for (i = 0; i < num; i++) { + if (i != 0 && (list[i].compare_operator == + OPERATOR_COMPARE_EQUAL_TO)) + return false; + if (i == 0 && (list[i].compare_operator == + OPERATOR_COMPARE_EQUAL_TO)) { + if (range) + range->min_port = list[i].value; + exact_match = true; + } + if (exact_match && i > 0) + return false; + if (list[i].compare_operator == + (OPERATOR_COMPARE_GREATER_THAN + + OPERATOR_COMPARE_EQUAL_TO)) { + if (range) + range->min_port = list[i].value; + } else if (list[i].compare_operator == + (OPERATOR_COMPARE_LESS_THAN + + OPERATOR_COMPARE_EQUAL_TO)) { + if (range) + range->max_port = list[i].value; + } else if (list[i].compare_operator == + OPERATOR_COMPARE_LESS_THAN) { + if (range) + range->max_port = list[i].value - 1; + } else if (list[i].compare_operator == + OPERATOR_COMPARE_GREATER_THAN) { + if (range) + range->min_port = list[i].value + 1; + } + } + return true; +} + +static int bgp_pbr_validate_policy_route(struct bgp_pbr_entry_main *api) +{ + bool enumerate_icmp = false; + + if (api->type == BGP_PBR_UNDEFINED) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: pbr entry undefined. cancel."); + return 0; + } + /* because bgp pbr entry may contain unsupported + * combinations, a message will be displayed here if + * not supported. + * for now, only match/set supported is + * - combination src/dst => redirect nexthop [ + rate] + * - combination src/dst => redirect VRF [ + rate] + * - combination src/dst => drop + * - combination srcport + @IP + */ + if (api->match_protocol_num > 1) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match protocol operations:multiple protocols ( %d). ignoring.", + api->match_protocol_num); + return 0; + } + if (api->src_prefix_offset > 0 || + api->dst_prefix_offset > 0) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match prefix offset:" + "implementation does not support it."); + return 0; + } + if (api->match_protocol_num == 1 && + api->protocol[0].value != PROTOCOL_UDP && + api->protocol[0].value != PROTOCOL_ICMP && + api->protocol[0].value != PROTOCOL_ICMPV6 && + api->protocol[0].value != PROTOCOL_TCP) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match protocol operations:protocol (%d) not supported. ignoring", + api->match_protocol_num); + return 0; + } + if (!bgp_pbr_extract(api->src_port, api->match_src_port_num, NULL)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match src port operations:too complex. ignoring."); + return 0; + } + if (!bgp_pbr_extract(api->dst_port, api->match_dst_port_num, NULL)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match dst port operations:too complex. ignoring."); + return 0; + } + if (!bgp_pbr_extract_enumerate(api->tcpflags, + api->match_tcpflags_num, + OPERATOR_UNARY_AND | + OPERATOR_UNARY_OR, NULL, + FLOWSPEC_TCP_FLAGS)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match tcp flags:too complex. ignoring."); + return 0; + } + if (!bgp_pbr_extract(api->icmp_type, api->match_icmp_type_num, NULL)) { + if (!bgp_pbr_extract_enumerate(api->icmp_type, + api->match_icmp_type_num, + OPERATOR_UNARY_OR, NULL, + FLOWSPEC_ICMP_TYPE)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match icmp type operations:too complex. ignoring."); + return 0; + } + enumerate_icmp = true; + } + if (!bgp_pbr_extract(api->icmp_code, api->match_icmp_code_num, NULL)) { + if (!bgp_pbr_extract_enumerate(api->icmp_code, + api->match_icmp_code_num, + OPERATOR_UNARY_OR, NULL, + FLOWSPEC_ICMP_CODE)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match icmp code operations:too complex. ignoring."); + return 0; + } else if (api->match_icmp_type_num > 1 && + !enumerate_icmp) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match icmp code is enumerate, and icmp type is not. too complex. ignoring."); + return 0; + } + } + if (!bgp_pbr_extract(api->port, api->match_port_num, NULL)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match port operations:too complex. ignoring."); + return 0; + } + if (api->match_packet_length_num) { + bool ret; + + ret = bgp_pbr_extract(api->packet_length, + api->match_packet_length_num, NULL); + if (!ret) + ret = bgp_pbr_extract_enumerate(api->packet_length, + api->match_packet_length_num, + OPERATOR_UNARY_OR + | OPERATOR_UNARY_AND, + NULL, FLOWSPEC_PKT_LEN); + if (!ret) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match packet length operations:too complex. ignoring."); + return 0; + } + } + if (api->match_dscp_num) { + if (!bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num, + OPERATOR_UNARY_OR | OPERATOR_UNARY_AND, + NULL, FLOWSPEC_DSCP)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match DSCP operations:too complex. ignoring."); + return 0; + } + } + if (api->match_flowlabel_num) { + if (api->afi == AFI_IP) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match Flow Label operations:" + "Not for IPv4."); + return 0; + } + if (!bgp_pbr_extract_enumerate(api->flow_label, + api->match_flowlabel_num, + OPERATOR_UNARY_OR | OPERATOR_UNARY_AND, + NULL, FLOWSPEC_FLOW_LABEL)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match FlowLabel operations:" + "too complex. ignoring."); + return 0; + } + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match FlowLabel operations " + "not supported. ignoring."); + return 0; + } + if (api->match_fragment_num) { + char fail_str[64]; + bool success; + + success = bgp_pbr_extract_enumerate(api->fragment, + api->match_fragment_num, + OPERATOR_UNARY_OR + | OPERATOR_UNARY_AND, + NULL, FLOWSPEC_FRAGMENT); + if (success) { + int i; + + for (i = 0; i < api->match_fragment_num; i++) { + if (api->fragment[i].value != 1 && + api->fragment[i].value != 2 && + api->fragment[i].value != 4 && + api->fragment[i].value != 8) { + success = false; + snprintf( + fail_str, sizeof(fail_str), + "Value not valid (%d) for this implementation", + api->fragment[i].value); + } + if (api->afi == AFI_IP6 && + api->fragment[i].value == 1) { + success = false; + snprintf(fail_str, sizeof(fail_str), + "IPv6 dont fragment match invalid (%d)", + api->fragment[i].value); + } + } + if (api->afi == AFI_IP6) { + success = false; + snprintf(fail_str, sizeof(fail_str), + "%s", IPV6_FRAGMENT_INVALID); + } + } else + snprintf(fail_str, sizeof(fail_str), + "too complex. ignoring"); + if (!success) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match fragment operation (%d) %s", + api->match_fragment_num, + fail_str); + return 0; + } + } + + /* no combinations with both src_port and dst_port + * or port with src_port and dst_port + */ + if (api->match_src_port_num + api->match_dst_port_num + + api->match_port_num > 3) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match multiple port operations: too complex. ignoring."); + return 0; + } + if ((api->match_src_port_num || api->match_dst_port_num + || api->match_port_num) && (api->match_icmp_type_num + || api->match_icmp_code_num)) { + if (BGP_DEBUG(pbr, PBR)) + zlog_debug("BGP: match multiple port/imcp operations: too complex. ignoring."); + return 0; + } + /* iprule only supports redirect IP */ + if (api->type == BGP_PBR_IPRULE) { + int i; + + for (i = 0; i < api->action_num; i++) { + if (api->actions[i].action == ACTION_TRAFFICRATE && + api->actions[i].u.r.rate == 0) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_debug("BGP: iprule match actions drop not supported"); + } + return 0; + } + if (api->actions[i].action == ACTION_MARKING) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_warn("PBR: iprule set DSCP/Flow Label %u not supported", + api->actions[i].u.marking_dscp); + } + } + if (api->actions[i].action == ACTION_REDIRECT) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_warn("PBR: iprule redirect VRF %u not supported", + api->actions[i].u.redirect_vrf); + } + } + } + + } else if (!(api->match_bitmask & PREFIX_SRC_PRESENT) && + !(api->match_bitmask & PREFIX_DST_PRESENT)) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_debug("BGP: match actions without src or dst address can not operate. ignoring."); + } + return 0; + } + return 1; +} + +/* return -1 if build or validation failed */ + +int bgp_pbr_build_and_validate_entry(const struct prefix *p, + struct bgp_path_info *path, + struct bgp_pbr_entry_main *api) +{ + int ret; + uint32_t i, action_count = 0; + struct ecommunity *ecom; + struct ecommunity_val *ecom_eval; + struct bgp_pbr_entry_action *api_action; + struct prefix *src = NULL, *dst = NULL; + int valid_prefix = 0; + struct bgp_pbr_entry_action *api_action_redirect_ip = NULL; + bool discard_action_found = false; + afi_t afi = family2afi(p->u.prefix_flowspec.family); + + /* extract match from flowspec entries */ + ret = bgp_flowspec_match_rules_fill((uint8_t *)p->u.prefix_flowspec.ptr, + p->u.prefix_flowspec.prefixlen, api, afi); + if (ret < 0) + return -1; + /* extract actiosn from flowspec ecom list */ + if (path && bgp_attr_get_ecommunity(path->attr)) { + ecom = bgp_attr_get_ecommunity(path->attr); + for (i = 0; i < ecom->size; i++) { + ecom_eval = (struct ecommunity_val *) + (ecom->val + (i * ECOMMUNITY_SIZE)); + action_count++; + if (action_count > ACTIONS_MAX_NUM) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "%s: %s (max %u)", + __func__, + FSPEC_ACTION_EXCEED_LIMIT, + action_count); + break; + } + api_action = &api->actions[action_count - 1]; + + if ((ecom_eval->val[1] == + (char)ECOMMUNITY_REDIRECT_VRF) && + (ecom_eval->val[0] == + (char)ECOMMUNITY_ENCODE_TRANS_EXP || + ecom_eval->val[0] == + (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 || + ecom_eval->val[0] == + (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_3)) { + struct ecommunity *eckey = ecommunity_new(); + struct ecommunity_val ecom_copy; + + memcpy(&ecom_copy, ecom_eval, + sizeof(struct ecommunity_val)); + ecom_copy.val[0] &= + ~ECOMMUNITY_ENCODE_TRANS_EXP; + ecom_copy.val[1] = ECOMMUNITY_ROUTE_TARGET; + ecommunity_add_val(eckey, &ecom_copy, + false, false); + + api_action->action = ACTION_REDIRECT; + api_action->u.redirect_vrf = + get_first_vrf_for_redirect_with_rt( + eckey); + ecommunity_free(&eckey); + } else if ((ecom_eval->val[0] == + (char)ECOMMUNITY_ENCODE_REDIRECT_IP_NH) && + (ecom_eval->val[1] == + (char)ECOMMUNITY_REDIRECT_IP_NH)) { + /* in case the 2 ecom present, + * do not overwrite + * draft-ietf-idr-flowspec-redirect + */ + if (api_action_redirect_ip && + p->u.prefix_flowspec.family == AF_INET) { + if (api_action_redirect_ip->u + .zr.redirect_ip_v4.s_addr + != INADDR_ANY) + continue; + if (path->attr->nexthop.s_addr + == INADDR_ANY) + continue; + api_action_redirect_ip->u.zr + .redirect_ip_v4.s_addr = + path->attr->nexthop.s_addr; + api_action_redirect_ip->u.zr.duplicate + = ecom_eval->val[7]; + continue; + } else if (api_action_redirect_ip && + p->u.prefix_flowspec.family == AF_INET6) { + if (memcmp(&api_action_redirect_ip->u + .zr.redirect_ip_v6, + &in6addr_any, + sizeof(struct in6_addr))) + continue; + if (path->attr->mp_nexthop_len == 0 || + path->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV4 || + path->attr->mp_nexthop_len == + BGP_ATTR_NHLEN_VPNV4) + continue; + memcpy(&api_action_redirect_ip->u + .zr.redirect_ip_v6, + &path->attr->mp_nexthop_global, + sizeof(struct in6_addr)); + api_action_redirect_ip->u.zr.duplicate + = ecom_eval->val[7]; + continue; + } else if (p->u.prefix_flowspec.family == + AF_INET) { + api_action->action = ACTION_REDIRECT_IP; + api_action->u.zr.redirect_ip_v4.s_addr = + path->attr->nexthop.s_addr; + api_action->u.zr.duplicate = + ecom_eval->val[7]; + api_action_redirect_ip = api_action; + } else if (p->u.prefix_flowspec.family == + AF_INET6) { + api_action->action = ACTION_REDIRECT_IP; + memcpy(&api_action->u + .zr.redirect_ip_v6, + &path->attr->mp_nexthop_global, + sizeof(struct in6_addr)); + api_action->u.zr.duplicate + = ecom_eval->val[7]; + api_action_redirect_ip = api_action; + } + } else if ((ecom_eval->val[0] == + (char)ECOMMUNITY_ENCODE_IP) && + (ecom_eval->val[1] == + (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4)) { + /* in case the 2 ecom present, + * overwrite simpson draft + * update redirect ip fields + */ + if (api_action_redirect_ip) { + memcpy(&(api_action_redirect_ip->u + .zr.redirect_ip_v4.s_addr), + (ecom_eval->val+2), 4); + api_action_redirect_ip->u + .zr.duplicate = + ecom_eval->val[7]; + continue; + } else { + api_action->action = ACTION_REDIRECT_IP; + memcpy(&(api_action->u + .zr.redirect_ip_v4.s_addr), + (ecom_eval->val+2), 4); + api_action->u.zr.duplicate = + ecom_eval->val[7]; + api_action_redirect_ip = api_action; + } + } else { + if (ecom_eval->val[0] != + (char)ECOMMUNITY_ENCODE_TRANS_EXP) + continue; + ret = ecommunity_fill_pbr_action(ecom_eval, + api_action, + afi); + if (ret != 0) + continue; + if ((api_action->action == ACTION_TRAFFICRATE) + && api->actions[i].u.r.rate == 0) + discard_action_found = true; + } + api->action_num++; + } + } + if (path && path->attr && bgp_attr_get_ipv6_ecommunity(path->attr)) { + struct ecommunity_val_ipv6 *ipv6_ecom_eval; + + ecom = bgp_attr_get_ipv6_ecommunity(path->attr); + for (i = 0; i < ecom->size; i++) { + ipv6_ecom_eval = (struct ecommunity_val_ipv6 *) + (ecom->val + (i * ecom->unit_size)); + action_count++; + if (action_count > ACTIONS_MAX_NUM) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + flog_err( + EC_BGP_FLOWSPEC_PACKET, + "%s: flowspec actions exceeds limit (max %u)", + __func__, action_count); + break; + } + api_action = &api->actions[action_count - 1]; + if ((ipv6_ecom_eval->val[1] == + (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) && + (ipv6_ecom_eval->val[0] == + (char)ECOMMUNITY_ENCODE_TRANS_EXP)) { + struct ecommunity *eckey = ecommunity_new(); + struct ecommunity_val_ipv6 ecom_copy; + + eckey->unit_size = IPV6_ECOMMUNITY_SIZE; + memcpy(&ecom_copy, ipv6_ecom_eval, + sizeof(struct ecommunity_val_ipv6)); + ecom_copy.val[1] = ECOMMUNITY_ROUTE_TARGET; + ecommunity_add_val_ipv6(eckey, &ecom_copy, + false, false); + api_action->action = ACTION_REDIRECT; + api_action->u.redirect_vrf = + get_first_vrf_for_redirect_with_rt( + eckey); + ecommunity_free(&eckey); + api->action_num++; + } + } + } + /* if ECOMMUNITY_TRAFFIC_RATE = 0 as action + * then reduce the API action list to that action + */ + if (api->action_num > 1 && discard_action_found) { + api->action_num = 1; + memset(&api->actions[0], 0, + sizeof(struct bgp_pbr_entry_action)); + api->actions[0].action = ACTION_TRAFFICRATE; + } + + /* validate if incoming matc/action is compatible + * with our policy routing engine + */ + if (!bgp_pbr_validate_policy_route(api)) + return -1; + + /* check inconsistency in the match rule */ + if (api->match_bitmask & PREFIX_SRC_PRESENT) { + src = &api->src_prefix; + afi = family2afi(src->family); + valid_prefix = 1; + } + if (api->match_bitmask & PREFIX_DST_PRESENT) { + dst = &api->dst_prefix; + if (valid_prefix && afi != family2afi(dst->family)) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_debug("%s: inconsistency: no match for afi src and dst (%u/%u)", + __func__, afi, family2afi(dst->family)); + } + return -1; + } + } + return 0; +} + +static void bgp_pbr_match_entry_free(void *arg) +{ + struct bgp_pbr_match_entry *bpme; + + bpme = (struct bgp_pbr_match_entry *)arg; + + if (bpme->installed) { + bgp_send_pbr_ipset_entry_match(bpme, false); + bpme->installed = false; + bpme->backpointer = NULL; + } + XFREE(MTYPE_PBR_MATCH_ENTRY, bpme); +} + +static void bgp_pbr_match_free(void *arg) +{ + struct bgp_pbr_match *bpm; + + bpm = (struct bgp_pbr_match *)arg; + + hash_clean(bpm->entry_hash, bgp_pbr_match_entry_free); + + if (hashcount(bpm->entry_hash) == 0) { + /* delete iptable entry first */ + /* then delete ipset match */ + if (bpm->installed) { + if (bpm->installed_in_iptable) { + bgp_send_pbr_iptable(bpm->action, + bpm, false); + bpm->installed_in_iptable = false; + bpm->action->refcnt--; + } + bgp_send_pbr_ipset_match(bpm, false); + bpm->installed = false; + bpm->action = NULL; + } + } + hash_clean_and_free(&bpm->entry_hash, NULL); + + XFREE(MTYPE_PBR_MATCH, bpm); +} + +static void *bgp_pbr_match_alloc_intern(void *arg) +{ + struct bgp_pbr_match *bpm, *new; + + bpm = (struct bgp_pbr_match *)arg; + + new = XCALLOC(MTYPE_PBR_MATCH, sizeof(*new)); + memcpy(new, bpm, sizeof(*bpm)); + + return new; +} + +static void bgp_pbr_rule_free(void *arg) +{ + struct bgp_pbr_rule *bpr; + + bpr = (struct bgp_pbr_rule *)arg; + + /* delete iprule */ + if (bpr->installed) { + bgp_send_pbr_rule_action(bpr->action, bpr, false); + bpr->installed = false; + bpr->action->refcnt--; + bpr->action = NULL; + } + XFREE(MTYPE_PBR_RULE, bpr); +} + +static void *bgp_pbr_rule_alloc_intern(void *arg) +{ + struct bgp_pbr_rule *bpr, *new; + + bpr = (struct bgp_pbr_rule *)arg; + + new = XCALLOC(MTYPE_PBR_RULE, sizeof(*new)); + memcpy(new, bpr, sizeof(*bpr)); + + return new; +} + +static void bgp_pbr_bpa_remove(struct bgp_pbr_action *bpa) +{ + if ((bpa->refcnt == 0) && bpa->installed && bpa->table_id != 0) { + bgp_send_pbr_rule_action(bpa, NULL, false); + bgp_zebra_announce_default(bpa->bgp, &bpa->nh, bpa->afi, + bpa->table_id, false); + bpa->installed = false; + } +} + +static void bgp_pbr_bpa_add(struct bgp_pbr_action *bpa) +{ + if (!bpa->installed && !bpa->install_in_progress) { + bgp_send_pbr_rule_action(bpa, NULL, true); + bgp_zebra_announce_default(bpa->bgp, &bpa->nh, bpa->afi, + bpa->table_id, true); + } +} + +static void bgp_pbr_action_free(void *arg) +{ + struct bgp_pbr_action *bpa = arg; + + bgp_pbr_bpa_remove(bpa); + + XFREE(MTYPE_PBR_ACTION, bpa); +} + +static void *bgp_pbr_action_alloc_intern(void *arg) +{ + struct bgp_pbr_action *bpa, *new; + + bpa = (struct bgp_pbr_action *)arg; + + new = XCALLOC(MTYPE_PBR_ACTION, sizeof(*new)); + + memcpy(new, bpa, sizeof(*bpa)); + + return new; +} + +static void *bgp_pbr_match_entry_alloc_intern(void *arg) +{ + struct bgp_pbr_match_entry *bpme, *new; + + bpme = (struct bgp_pbr_match_entry *)arg; + + new = XCALLOC(MTYPE_PBR_MATCH_ENTRY, sizeof(*new)); + + memcpy(new, bpme, sizeof(*bpme)); + + return new; +} + +uint32_t bgp_pbr_match_hash_key(const void *arg) +{ + const struct bgp_pbr_match *pbm = arg; + uint32_t key; + + key = jhash_1word(pbm->vrf_id, 0x4312abde); + key = jhash_1word(pbm->flags, key); + key = jhash_1word(pbm->family, key); + key = jhash(&pbm->pkt_len_min, 2, key); + key = jhash(&pbm->pkt_len_max, 2, key); + key = jhash(&pbm->tcp_flags, 2, key); + key = jhash(&pbm->tcp_mask_flags, 2, key); + key = jhash(&pbm->dscp_value, 1, key); + key = jhash(&pbm->flow_label, 2, key); + key = jhash(&pbm->fragment, 1, key); + key = jhash(&pbm->protocol, 1, key); + return jhash_1word(pbm->type, key); +} + +bool bgp_pbr_match_hash_equal(const void *arg1, const void *arg2) +{ + const struct bgp_pbr_match *r1, *r2; + + r1 = (const struct bgp_pbr_match *)arg1; + r2 = (const struct bgp_pbr_match *)arg2; + + if (r1->vrf_id != r2->vrf_id) + return false; + + if (r1->family != r2->family) + return false; + + if (r1->type != r2->type) + return false; + + if (r1->flags != r2->flags) + return false; + + if (r1->action != r2->action) + return false; + + if (r1->pkt_len_min != r2->pkt_len_min) + return false; + + if (r1->pkt_len_max != r2->pkt_len_max) + return false; + + if (r1->tcp_flags != r2->tcp_flags) + return false; + + if (r1->tcp_mask_flags != r2->tcp_mask_flags) + return false; + + if (r1->dscp_value != r2->dscp_value) + return false; + + if (r1->flow_label != r2->flow_label) + return false; + + if (r1->fragment != r2->fragment) + return false; + + if (r1->protocol != r2->protocol) + return false; + return true; +} + +uint32_t bgp_pbr_rule_hash_key(const void *arg) +{ + const struct bgp_pbr_rule *pbr = arg; + uint32_t key; + + key = prefix_hash_key(&pbr->src); + key = jhash_1word(pbr->vrf_id, key); + key = jhash_1word(pbr->flags, key); + return jhash_1word(prefix_hash_key(&pbr->dst), key); +} + +bool bgp_pbr_rule_hash_equal(const void *arg1, const void *arg2) +{ + const struct bgp_pbr_rule *r1, *r2; + + r1 = (const struct bgp_pbr_rule *)arg1; + r2 = (const struct bgp_pbr_rule *)arg2; + + if (r1->vrf_id != r2->vrf_id) + return false; + + if (r1->flags != r2->flags) + return false; + + if (r1->action != r2->action) + return false; + + if ((r1->flags & MATCH_IP_SRC_SET) && + !prefix_same(&r1->src, &r2->src)) + return false; + + if ((r1->flags & MATCH_IP_DST_SET) && + !prefix_same(&r1->dst, &r2->dst)) + return false; + + return true; +} + +uint32_t bgp_pbr_match_entry_hash_key(const void *arg) +{ + const struct bgp_pbr_match_entry *pbme; + uint32_t key; + + pbme = arg; + key = prefix_hash_key(&pbme->src); + key = jhash_1word(prefix_hash_key(&pbme->dst), key); + key = jhash(&pbme->dst_port_min, 2, key); + key = jhash(&pbme->src_port_min, 2, key); + key = jhash(&pbme->dst_port_max, 2, key); + key = jhash(&pbme->src_port_max, 2, key); + key = jhash(&pbme->proto, 1, key); + + return key; +} + +bool bgp_pbr_match_entry_hash_equal(const void *arg1, const void *arg2) +{ + const struct bgp_pbr_match_entry *r1, *r2; + + r1 = (const struct bgp_pbr_match_entry *)arg1; + r2 = (const struct bgp_pbr_match_entry *)arg2; + + /* + * on updates, comparing backpointer is not necessary + * unique value is self calculated + * rate is ignored for now + */ + + if (!prefix_same(&r1->src, &r2->src)) + return false; + + if (!prefix_same(&r1->dst, &r2->dst)) + return false; + + if (r1->src_port_min != r2->src_port_min) + return false; + + if (r1->dst_port_min != r2->dst_port_min) + return false; + + if (r1->src_port_max != r2->src_port_max) + return false; + + if (r1->dst_port_max != r2->dst_port_max) + return false; + + if (r1->proto != r2->proto) + return false; + + return true; +} + +uint32_t bgp_pbr_action_hash_key(const void *arg) +{ + const struct bgp_pbr_action *pbra; + uint32_t key; + + pbra = arg; + key = jhash_1word(pbra->table_id, 0x4312abde); + key = jhash_1word(pbra->fwmark, key); + key = jhash_1word(pbra->afi, key); + return key; +} + +bool bgp_pbr_action_hash_equal(const void *arg1, const void *arg2) +{ + const struct bgp_pbr_action *r1, *r2; + + r1 = (const struct bgp_pbr_action *)arg1; + r2 = (const struct bgp_pbr_action *)arg2; + + /* unique value is self calculated + * table and fwmark is self calculated + * rate is ignored + */ + if (r1->vrf_id != r2->vrf_id) + return false; + + if (r1->afi != r2->afi) + return false; + + return nexthop_same(&r1->nh, &r2->nh); +} + +struct bgp_pbr_rule *bgp_pbr_rule_lookup(vrf_id_t vrf_id, + uint32_t unique) +{ + struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); + struct bgp_pbr_rule_unique bpru; + + if (!bgp || unique == 0) + return NULL; + bpru.unique = unique; + bpru.bpr_found = NULL; + hash_walk(bgp->pbr_rule_hash, bgp_pbr_rule_walkcb, &bpru); + return bpru.bpr_found; +} + +struct bgp_pbr_action *bgp_pbr_action_rule_lookup(vrf_id_t vrf_id, + uint32_t unique) +{ + struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); + struct bgp_pbr_action_unique bpau; + + if (!bgp || unique == 0) + return NULL; + bpau.unique = unique; + bpau.bpa_found = NULL; + hash_walk(bgp->pbr_action_hash, bgp_pbr_action_walkcb, &bpau); + return bpau.bpa_found; +} + +struct bgp_pbr_match *bgp_pbr_match_ipset_lookup(vrf_id_t vrf_id, + uint32_t unique) +{ + struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); + struct bgp_pbr_match_unique bpmu; + + if (!bgp || unique == 0) + return NULL; + bpmu.unique = unique; + bpmu.bpm_found = NULL; + hash_walk(bgp->pbr_match_hash, bgp_pbr_match_walkcb, &bpmu); + return bpmu.bpm_found; +} + +struct bgp_pbr_match_entry *bgp_pbr_match_ipset_entry_lookup(vrf_id_t vrf_id, + char *ipset_name, + uint32_t unique) +{ + struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); + struct bgp_pbr_match_entry_unique bpmeu; + struct bgp_pbr_match_ipsetname bpmi; + + if (!bgp || unique == 0) + return NULL; + bpmi.ipsetname = XCALLOC(MTYPE_TMP, ZEBRA_IPSET_NAME_SIZE); + snprintf(bpmi.ipsetname, ZEBRA_IPSET_NAME_SIZE, "%s", ipset_name); + bpmi.bpm_found = NULL; + hash_walk(bgp->pbr_match_hash, bgp_pbr_match_pername_walkcb, &bpmi); + XFREE(MTYPE_TMP, bpmi.ipsetname); + if (!bpmi.bpm_found) + return NULL; + bpmeu.bpme_found = NULL; + bpmeu.unique = unique; + hash_walk(bpmi.bpm_found->entry_hash, + bgp_pbr_match_entry_walkcb, &bpmeu); + return bpmeu.bpme_found; +} + +struct bgp_pbr_match *bgp_pbr_match_iptable_lookup(vrf_id_t vrf_id, + uint32_t unique) +{ + struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id); + struct bgp_pbr_match_iptable_unique bpmiu; + + if (!bgp || unique == 0) + return NULL; + bpmiu.unique = unique; + bpmiu.bpm_found = NULL; + hash_walk(bgp->pbr_match_hash, bgp_pbr_match_iptable_walkcb, &bpmiu); + return bpmiu.bpm_found; +} + +void bgp_pbr_cleanup(struct bgp *bgp) +{ + hash_clean_and_free(&bgp->pbr_match_hash, bgp_pbr_match_free); + hash_clean_and_free(&bgp->pbr_rule_hash, bgp_pbr_rule_free); + hash_clean_and_free(&bgp->pbr_action_hash, bgp_pbr_action_free); + + if (bgp->bgp_pbr_cfg == NULL) + return; + + bgp_pbr_reset(bgp, AFI_IP); + bgp_pbr_reset(bgp, AFI_IP6); + XFREE(MTYPE_PBR, bgp->bgp_pbr_cfg); +} + +void bgp_pbr_init(struct bgp *bgp) +{ + bgp->pbr_match_hash = + hash_create_size(8, bgp_pbr_match_hash_key, + bgp_pbr_match_hash_equal, + "Match Hash"); + bgp->pbr_action_hash = + hash_create_size(8, bgp_pbr_action_hash_key, + bgp_pbr_action_hash_equal, + "Match Hash Entry"); + + bgp->pbr_rule_hash = + hash_create_size(8, bgp_pbr_rule_hash_key, + bgp_pbr_rule_hash_equal, + "Match Rule"); + + bgp->bgp_pbr_cfg = XCALLOC(MTYPE_PBR, sizeof(struct bgp_pbr_config)); + bgp->bgp_pbr_cfg->pbr_interface_any_ipv4 = true; +} + +void bgp_pbr_print_policy_route(struct bgp_pbr_entry_main *api) +{ + int i = 0; + char return_string[512]; + char *ptr = return_string; + int nb_items = 0; + int delta, len = sizeof(return_string); + + delta = snprintf(ptr, sizeof(return_string), "MATCH : "); + len -= delta; + ptr += delta; + if (api->match_bitmask & PREFIX_SRC_PRESENT) { + struct prefix *p = &(api->src_prefix); + + if (api->src_prefix_offset) + delta = snprintfrr(ptr, len, "@src %pFX/off%u", p, + api->src_prefix_offset); + else + delta = snprintfrr(ptr, len, "@src %pFX", p); + len -= delta; + ptr += delta; + INCREMENT_DISPLAY(ptr, nb_items, len); + } + if (api->match_bitmask & PREFIX_DST_PRESENT) { + struct prefix *p = &(api->dst_prefix); + + INCREMENT_DISPLAY(ptr, nb_items, len); + if (api->dst_prefix_offset) + delta = snprintfrr(ptr, len, "@dst %pFX/off%u", p, + api->dst_prefix_offset); + else + delta = snprintfrr(ptr, len, "@dst %pFX", p); + len -= delta; + ptr += delta; + } + + if (api->match_protocol_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_protocol_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->protocol[i], + i > 0 ? NULL : "@proto "); + len -= delta; + ptr += delta; + } + + if (api->match_src_port_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_src_port_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->src_port[i], + i > 0 ? NULL : "@srcport "); + len -= delta; + ptr += delta; + } + + if (api->match_dst_port_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_dst_port_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->dst_port[i], + i > 0 ? NULL : "@dstport "); + len -= delta; + ptr += delta; + } + + if (api->match_port_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_port_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->port[i], + i > 0 ? NULL : "@port "); + len -= delta; + ptr += delta; + } + + if (api->match_icmp_type_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_icmp_type_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->icmp_type[i], + i > 0 ? NULL : "@icmptype "); + len -= delta; + ptr += delta; + } + + if (api->match_icmp_code_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_icmp_code_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->icmp_code[i], + i > 0 ? NULL : "@icmpcode "); + len -= delta; + ptr += delta; + } + + if (api->match_packet_length_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_packet_length_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, + &api->packet_length[i], + i > 0 ? NULL : "@plen "); + len -= delta; + ptr += delta; + } + + if (api->match_dscp_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_dscp_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->dscp[i], + i > 0 ? NULL : "@dscp "); + len -= delta; + ptr += delta; + } + + if (api->match_flowlabel_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_flowlabel_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, + &api->flow_label[i], + i > 0 ? NULL : "@flowlabel "); + len -= delta; + ptr += delta; + } + + if (api->match_tcpflags_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_tcpflags_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->tcpflags[i], + i > 0 ? NULL : "@tcpflags "); + len -= delta; + ptr += delta; + } + + if (api->match_fragment_num) + INCREMENT_DISPLAY(ptr, nb_items, len); + for (i = 0; i < api->match_fragment_num; i++) { + delta = snprintf_bgp_pbr_match_val(ptr, len, &api->fragment[i], + i > 0 ? NULL : "@fragment "); + len -= delta; + ptr += delta; + } + + len = sizeof(return_string); + if (!nb_items) { + ptr = return_string; + } else { + len -= (ptr - return_string); + delta = snprintf(ptr, len, "; "); + len -= delta; + ptr += delta; + } + if (api->action_num) { + delta = snprintf(ptr, len, "SET : "); + len -= delta; + ptr += delta; + } + nb_items = 0; + for (i = 0; i < api->action_num; i++) { + switch (api->actions[i].action) { + case ACTION_TRAFFICRATE: + INCREMENT_DISPLAY(ptr, nb_items, len); + delta = snprintf(ptr, len, "@set rate %f", + api->actions[i].u.r.rate); + len -= delta; + ptr += delta; + break; + case ACTION_TRAFFIC_ACTION: + INCREMENT_DISPLAY(ptr, nb_items, len); + delta = snprintf(ptr, len, "@action "); + len -= delta; + ptr += delta; + if (api->actions[i].u.za.filter + & TRAFFIC_ACTION_TERMINATE) { + delta = snprintf(ptr, len, + " terminate (apply filter(s))"); + len -= delta; + ptr += delta; + } + if (api->actions[i].u.za.filter + & TRAFFIC_ACTION_DISTRIBUTE) { + delta = snprintf(ptr, len, " distribute"); + len -= delta; + ptr += delta; + } + if (api->actions[i].u.za.filter + & TRAFFIC_ACTION_SAMPLE) { + delta = snprintf(ptr, len, " sample"); + len -= delta; + ptr += delta; + } + break; + case ACTION_REDIRECT_IP: { + char local_buff[INET6_ADDRSTRLEN]; + void *ptr_ip; + + INCREMENT_DISPLAY(ptr, nb_items, len); + if (api->afi == AF_INET) + ptr_ip = &api->actions[i].u.zr.redirect_ip_v4; + else + ptr_ip = &api->actions[i].u.zr.redirect_ip_v6; + if (inet_ntop(afi2family(api->afi), ptr_ip, local_buff, + sizeof(local_buff)) != NULL) { + delta = snprintf(ptr, len, + "@redirect ip nh %s", local_buff); + len -= delta; + ptr += delta; + } + break; + } + case ACTION_REDIRECT: { + struct vrf *vrf; + + vrf = vrf_lookup_by_id(api->actions[i].u.redirect_vrf); + INCREMENT_DISPLAY(ptr, nb_items, len); + delta = snprintf(ptr, len, "@redirect vrf %s(%u)", + VRF_LOGNAME(vrf), + api->actions[i].u.redirect_vrf); + len -= delta; + ptr += delta; + break; + } + case ACTION_MARKING: + INCREMENT_DISPLAY(ptr, nb_items, len); + delta = snprintf(ptr, len, "@set dscp/flowlabel %u", + api->actions[i].u.marking_dscp); + len -= delta; + ptr += delta; + break; + default: + break; + } + } + zlog_info("%s", return_string); +} + +static void bgp_pbr_flush_iprule(struct bgp *bgp, struct bgp_pbr_action *bpa, + struct bgp_pbr_rule *bpr) +{ + /* if bpr is null, do nothing + */ + if (bpr == NULL) + return; + if (bpr->installed) { + bgp_send_pbr_rule_action(bpa, bpr, false); + bpr->installed = false; + bpr->action->refcnt--; + bpr->action = NULL; + if (bpr->path) { + struct bgp_path_info *path; + struct bgp_path_info_extra *extra; + + /* unlink path to bpme */ + path = (struct bgp_path_info *)bpr->path; + extra = bgp_path_info_extra_get(path); + if (extra->flowspec && extra->flowspec->bgp_fs_iprule) + listnode_delete(extra->flowspec->bgp_fs_iprule, bpr); + bpr->path = NULL; + } + } + hash_release(bgp->pbr_rule_hash, bpr); + bgp_pbr_bpa_remove(bpa); +} + +static void bgp_pbr_flush_entry(struct bgp *bgp, struct bgp_pbr_action *bpa, + struct bgp_pbr_match *bpm, + struct bgp_pbr_match_entry *bpme) +{ + /* if bpme is null, bpm is also null + */ + if (bpme == NULL) + return; + /* ipset del entry */ + if (bpme->installed) { + bgp_send_pbr_ipset_entry_match(bpme, false); + bpme->installed = false; + bpme->backpointer = NULL; + if (bpme->path) { + struct bgp_path_info *path; + struct bgp_path_info_extra *extra; + + /* unlink path to bpme */ + path = (struct bgp_path_info *)bpme->path; + extra = bgp_path_info_extra_get(path); + if (extra->flowspec && extra->flowspec->bgp_fs_pbr) + listnode_delete(extra->flowspec->bgp_fs_pbr, bpme); + bpme->path = NULL; + } + } + hash_release(bpm->entry_hash, bpme); + if (hashcount(bpm->entry_hash) == 0) { + /* delete iptable entry first */ + /* then delete ipset match */ + if (bpm->installed) { + if (bpm->installed_in_iptable) { + bgp_send_pbr_iptable(bpm->action, + bpm, false); + bpm->installed_in_iptable = false; + bpm->action->refcnt--; + } + bgp_send_pbr_ipset_match(bpm, false); + bpm->installed = false; + bpm->action = NULL; + } + hash_release(bgp->pbr_match_hash, bpm); + /* XXX release pbr_match_action if not used + * note that drop does not need to call send_pbr_action + */ + } + bgp_pbr_bpa_remove(bpa); +} + +struct bgp_pbr_match_entry_remain { + struct bgp_pbr_match_entry *bpme_to_match; + struct bgp_pbr_match_entry *bpme_found; +}; + +struct bgp_pbr_rule_remain { + struct bgp_pbr_rule *bpr_to_match; + struct bgp_pbr_rule *bpr_found; +}; + +static int bgp_pbr_get_same_rule(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_rule *r1 = (struct bgp_pbr_rule *)bucket->data; + struct bgp_pbr_rule_remain *ctxt = + (struct bgp_pbr_rule_remain *)arg; + struct bgp_pbr_rule *r2; + + r2 = ctxt->bpr_to_match; + + if (r1->vrf_id != r2->vrf_id) + return HASHWALK_CONTINUE; + + if (r1->flags != r2->flags) + return HASHWALK_CONTINUE; + + if ((r1->flags & MATCH_IP_SRC_SET) && + !prefix_same(&r1->src, &r2->src)) + return HASHWALK_CONTINUE; + + if ((r1->flags & MATCH_IP_DST_SET) && + !prefix_same(&r1->dst, &r2->dst)) + return HASHWALK_CONTINUE; + + /* this function is used for two cases: + * - remove an entry upon withdraw request + * (case r2->action is null) + * - replace an old iprule with different action + * (case r2->action is != null) + * the old one is removed after the new one + * this is to avoid disruption in traffic + */ + if (r2->action == NULL || + r1->action != r2->action) { + ctxt->bpr_found = r1; + return HASHWALK_ABORT; + } + return HASHWALK_CONTINUE; +} + +static int bgp_pbr_get_remaining_entry(struct hash_bucket *bucket, void *arg) +{ + struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data; + struct bgp_pbr_match_entry_remain *bpmer = + (struct bgp_pbr_match_entry_remain *)arg; + struct bgp_pbr_match *bpm_temp; + struct bgp_pbr_match_entry *bpme = bpmer->bpme_to_match; + + if (!bpme->backpointer || + bpm == bpme->backpointer || + bpme->backpointer->action == bpm->action) + return HASHWALK_CONTINUE; + /* ensure bpm other characteristics are equal */ + bpm_temp = bpme->backpointer; + if (bpm_temp->vrf_id != bpm->vrf_id || + bpm_temp->type != bpm->type || + bpm_temp->flags != bpm->flags || + bpm_temp->tcp_flags != bpm->tcp_flags || + bpm_temp->tcp_mask_flags != bpm->tcp_mask_flags || + bpm_temp->pkt_len_min != bpm->pkt_len_min || + bpm_temp->pkt_len_max != bpm->pkt_len_max || + bpm_temp->dscp_value != bpm->dscp_value || + bpm_temp->flow_label != bpm->flow_label || + bpm_temp->family != bpm->family || + bpm_temp->fragment != bpm->fragment) + return HASHWALK_CONTINUE; + + /* look for remaining bpme */ + bpmer->bpme_found = hash_lookup(bpm->entry_hash, bpme); + if (!bpmer->bpme_found) + return HASHWALK_CONTINUE; + return HASHWALK_ABORT; +} + +static void bgp_pbr_policyroute_remove_from_zebra_unit( + struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf) +{ + struct bgp_pbr_match temp; + struct bgp_pbr_match_entry temp2; + struct bgp_pbr_rule pbr_rule; + struct bgp_pbr_rule *bpr; + struct bgp_pbr_match *bpm; + struct bgp_pbr_match_entry *bpme; + struct bgp_pbr_match_entry_remain bpmer; + struct bgp_pbr_range_port *src_port; + struct bgp_pbr_range_port *dst_port; + struct bgp_pbr_range_port *pkt_len; + struct bgp_pbr_rule_remain bprr; + + if (!bpf) + return; + src_port = bpf->src_port; + dst_port = bpf->dst_port; + pkt_len = bpf->pkt_len; + + if (BGP_DEBUG(zebra, ZEBRA)) + bgp_pbr_dump_entry(bpf, false); + + /* as we don't know information from EC + * look for bpm that have the bpm + * with vrf_id characteristics + */ + memset(&temp2, 0, sizeof(temp2)); + memset(&temp, 0, sizeof(temp)); + + if (bpf->type == BGP_PBR_IPRULE) { + memset(&pbr_rule, 0, sizeof(pbr_rule)); + pbr_rule.vrf_id = bpf->vrf_id; + if (bpf->src) { + prefix_copy(&pbr_rule.src, bpf->src); + pbr_rule.flags |= MATCH_IP_SRC_SET; + } + if (bpf->dst) { + prefix_copy(&pbr_rule.dst, bpf->dst); + pbr_rule.flags |= MATCH_IP_DST_SET; + } + bpr = &pbr_rule; + /* A previous entry may already exist + * flush previous entry if necessary + */ + bprr.bpr_to_match = bpr; + bprr.bpr_found = NULL; + hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr); + if (bprr.bpr_found) { + static struct bgp_pbr_rule *local_bpr; + static struct bgp_pbr_action *local_bpa; + + local_bpr = bprr.bpr_found; + local_bpa = local_bpr->action; + bgp_pbr_flush_iprule(bgp, local_bpa, + local_bpr); + } + return; + } + + temp.family = bpf->family; + if (bpf->src) { + temp.flags |= MATCH_IP_SRC_SET; + prefix_copy(&temp2.src, bpf->src); + } else + temp2.src.family = bpf->family; + if (bpf->dst) { + temp.flags |= MATCH_IP_DST_SET; + prefix_copy(&temp2.dst, bpf->dst); + } else + temp2.dst.family = bpf->family; + if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) { + if (bpf->protocol == IPPROTO_ICMP) + temp.flags |= MATCH_ICMP_SET; + temp.flags |= MATCH_PORT_SRC_SET; + temp2.src_port_min = src_port->min_port; + if (src_port->max_port) { + temp.flags |= MATCH_PORT_SRC_RANGE_SET; + temp2.src_port_max = src_port->max_port; + } + } + if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) { + if (bpf->protocol == IPPROTO_ICMP) + temp.flags |= MATCH_ICMP_SET; + temp.flags |= MATCH_PORT_DST_SET; + temp2.dst_port_min = dst_port->min_port; + if (dst_port->max_port) { + temp.flags |= MATCH_PORT_DST_RANGE_SET; + temp2.dst_port_max = dst_port->max_port; + } + } + temp2.proto = bpf->protocol; + + if (pkt_len) { + temp.pkt_len_min = pkt_len->min_port; + if (pkt_len->max_port) + temp.pkt_len_max = pkt_len->max_port; + } else if (bpf->pkt_len_val) { + if (bpf->pkt_len_val->mask) + temp.flags |= MATCH_PKT_LEN_INVERSE_SET; + temp.pkt_len_min = bpf->pkt_len_val->val; + } + if (bpf->tcp_flags) { + temp.tcp_flags = bpf->tcp_flags->val; + temp.tcp_mask_flags = bpf->tcp_flags->mask; + } + if (bpf->dscp) { + if (bpf->dscp->mask) + temp.flags |= MATCH_DSCP_INVERSE_SET; + else + temp.flags |= MATCH_DSCP_SET; + temp.dscp_value = bpf->dscp->val; + } + if (bpf->flow_label) { + if (bpf->flow_label->mask) + temp.flags |= MATCH_FLOW_LABEL_INVERSE_SET; + else + temp.flags |= MATCH_FLOW_LABEL_SET; + temp.flow_label = bpf->flow_label->val; + } + + if (bpf->fragment) { + if (bpf->fragment->mask) + temp.flags |= MATCH_FRAGMENT_INVERSE_SET; + temp.fragment = bpf->fragment->val; + } + + if (bpf->src == NULL || bpf->dst == NULL) { + if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) + temp.type = IPSET_NET_PORT; + else + temp.type = IPSET_NET; + } else { + if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) + temp.type = IPSET_NET_PORT_NET; + else + temp.type = IPSET_NET_NET; + } + if (bpf->vrf_id == VRF_UNKNOWN) /* XXX case BGP destroy */ + temp.vrf_id = VRF_DEFAULT; + else + temp.vrf_id = bpf->vrf_id; + bpme = &temp2; + bpm = &temp; + bpme->backpointer = bpm; + /* right now, a previous entry may already exist + * flush previous entry if necessary + */ + bpmer.bpme_to_match = bpme; + bpmer.bpme_found = NULL; + hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer); + if (bpmer.bpme_found) { + static struct bgp_pbr_match *local_bpm; + static struct bgp_pbr_action *local_bpa; + + local_bpm = bpmer.bpme_found->backpointer; + local_bpa = local_bpm->action; + bgp_pbr_flush_entry(bgp, local_bpa, + local_bpm, bpmer.bpme_found); + } +} + +static uint8_t bgp_pbr_next_type_entry(uint8_t type_entry) +{ + if (type_entry == FLOWSPEC_TCP_FLAGS) + return FLOWSPEC_DSCP; + if (type_entry == FLOWSPEC_DSCP) + return FLOWSPEC_FLOW_LABEL; + if (type_entry == FLOWSPEC_FLOW_LABEL) + return FLOWSPEC_PKT_LEN; + if (type_entry == FLOWSPEC_PKT_LEN) + return FLOWSPEC_FRAGMENT; + if (type_entry == FLOWSPEC_FRAGMENT) + return FLOWSPEC_ICMP_TYPE; + return 0; +} + +static void bgp_pbr_icmp_action(struct bgp *bgp, struct bgp_path_info *path, + struct bgp_pbr_filter *bpf, + struct bgp_pbr_or_filter *bpof, bool add, + struct nexthop *nh, float *rate) +{ + struct bgp_pbr_range_port srcp, dstp; + struct bgp_pbr_val_mask *icmp_type, *icmp_code; + struct listnode *tnode, *cnode; + + if (!bpf) + return; + if (bpf->protocol != IPPROTO_ICMP) + return; + + memset(&srcp, 0, sizeof(srcp)); + memset(&dstp, 0, sizeof(dstp)); + bpf->src_port = &srcp; + bpf->dst_port = &dstp; + /* parse icmp type and lookup appropriate icmp code + * if no icmp code found, create as many entryes as + * there are listed icmp codes for that icmp type + */ + if (!bpof->icmp_type) { + srcp.min_port = 0; + srcp.max_port = 255; + for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) { + dstp.min_port = icmp_code->val; + if (add) + bgp_pbr_policyroute_add_to_zebra_unit( + bgp, path, bpf, nh, rate); + else + bgp_pbr_policyroute_remove_from_zebra_unit( + bgp, path, bpf); + } + return; + } + for (ALL_LIST_ELEMENTS_RO(bpof->icmp_type, tnode, icmp_type)) { + srcp.min_port = icmp_type->val; + srcp.max_port = 0; + dstp.max_port = 0; + /* only icmp type. create an entry only with icmp type */ + if (!bpof->icmp_code) { + /* icmp type is not one of the above + * forge an entry only based on the icmp type + */ + dstp.min_port = 0; + dstp.max_port = 255; + if (add) + bgp_pbr_policyroute_add_to_zebra_unit( + bgp, path, bpf, nh, rate); + else + bgp_pbr_policyroute_remove_from_zebra_unit( + bgp, path, bpf); + continue; + } + for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) { + dstp.min_port = icmp_code->val; + if (add) + bgp_pbr_policyroute_add_to_zebra_unit( + bgp, path, bpf, nh, rate); + else + bgp_pbr_policyroute_remove_from_zebra_unit( + bgp, path, bpf); + } + } + + bpf->src_port = NULL; + bpf->dst_port = NULL; +} + +static void bgp_pbr_policyroute_remove_from_zebra_recursive( + struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, + struct bgp_pbr_or_filter *bpof, uint8_t type_entry) +{ + struct listnode *node, *nnode; + struct bgp_pbr_val_mask *valmask; + uint8_t next_type_entry; + struct list *orig_list; + struct bgp_pbr_val_mask **target_val; + + if (type_entry == 0) { + bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); + return; + } + next_type_entry = bgp_pbr_next_type_entry(type_entry); + if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) { + orig_list = bpof->tcpflags; + target_val = &bpf->tcp_flags; + } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) { + orig_list = bpof->dscp; + target_val = &bpf->dscp; + } else if (type_entry == FLOWSPEC_FLOW_LABEL && bpof->flowlabel) { + orig_list = bpof->flowlabel; + target_val = &bpf->flow_label; + } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) { + orig_list = bpof->pkt_len; + target_val = &bpf->pkt_len_val; + } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) { + orig_list = bpof->fragment; + target_val = &bpf->fragment; + } else if (type_entry == FLOWSPEC_ICMP_TYPE && + (bpof->icmp_type || bpof->icmp_code)) { + /* enumerate list for icmp - must be last one */ + bgp_pbr_icmp_action(bgp, path, bpf, bpof, false, NULL, NULL); + return; + } else { + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, next_type_entry); + return; + } + for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) { + *target_val = valmask; + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, next_type_entry); + } +} + +static void bgp_pbr_policyroute_remove_from_zebra( + struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, + struct bgp_pbr_or_filter *bpof) +{ + if (!bpof) { + bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); + return; + } + if (bpof->tcpflags) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_TCP_FLAGS); + else if (bpof->dscp) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_DSCP); + else if (bpof->flowlabel) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_FLOW_LABEL); + else if (bpof->pkt_len) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_PKT_LEN); + else if (bpof->fragment) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_FRAGMENT); + else if (bpof->icmp_type || bpof->icmp_code) + bgp_pbr_policyroute_remove_from_zebra_recursive( + bgp, path, bpf, bpof, FLOWSPEC_ICMP_TYPE); + else + bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf); + /* flush bpof */ + if (bpof->tcpflags) + list_delete_all_node(bpof->tcpflags); + if (bpof->dscp) + list_delete_all_node(bpof->dscp); + if (bpof->flowlabel) + list_delete_all_node(bpof->flowlabel); + if (bpof->pkt_len) + list_delete_all_node(bpof->pkt_len); + if (bpof->fragment) + list_delete_all_node(bpof->fragment); +} + +static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add) +{ + struct bgp_pbr_range_port *src_port; + struct bgp_pbr_range_port *dst_port; + struct bgp_pbr_range_port *pkt_len; + char bufsrc[64], bufdst[64]; + char buffer[64]; + int remaining_len = 0; + char protocol_str[16]; + + if (!bpf) + return; + src_port = bpf->src_port; + dst_port = bpf->dst_port; + pkt_len = bpf->pkt_len; + + protocol_str[0] = '\0'; + if (bpf->tcp_flags && bpf->tcp_flags->mask) + bpf->protocol = IPPROTO_TCP; + if (bpf->protocol) + snprintf(protocol_str, sizeof(protocol_str), + "proto %d", bpf->protocol); + buffer[0] = '\0'; + if (bpf->protocol == IPPROTO_ICMP && src_port && dst_port) + remaining_len += snprintf(buffer, sizeof(buffer), + "type %d, code %d", + src_port->min_port, + dst_port->min_port); + else if (bpf->protocol == IPPROTO_UDP || + bpf->protocol == IPPROTO_TCP) { + + if (src_port && src_port->min_port) + remaining_len += snprintf(buffer, + sizeof(buffer), + "from [%u:%u]", + src_port->min_port, + src_port->max_port ? + src_port->max_port : + src_port->min_port); + if (dst_port && dst_port->min_port) + remaining_len += snprintf(buffer + + remaining_len, + sizeof(buffer) + - remaining_len, + "to [%u:%u]", + dst_port->min_port, + dst_port->max_port ? + dst_port->max_port : + dst_port->min_port); + } + if (pkt_len && (pkt_len->min_port || pkt_len->max_port)) { + remaining_len += snprintf(buffer + remaining_len, + sizeof(buffer) + - remaining_len, + " len [%u:%u]", + pkt_len->min_port, + pkt_len->max_port ? + pkt_len->max_port : + pkt_len->min_port); + } else if (bpf->pkt_len_val) { + remaining_len += snprintf(buffer + remaining_len, + sizeof(buffer) + - remaining_len, + " %s len %u", + bpf->pkt_len_val->mask + ? "!" : "", + bpf->pkt_len_val->val); + } + if (bpf->tcp_flags) { + remaining_len += snprintf(buffer + remaining_len, + sizeof(buffer) + - remaining_len, + "tcpflags %x/%x", + bpf->tcp_flags->val, + bpf->tcp_flags->mask); + } + if (bpf->dscp) { + snprintf(buffer + remaining_len, + sizeof(buffer) + - remaining_len, + "%s dscp %d", + bpf->dscp->mask + ? "!" : "", + bpf->dscp->val); + } + if (bpf->flow_label) { + snprintf(buffer + remaining_len, + sizeof(buffer) + - remaining_len, + "%s flow_label %d", + bpf->flow_label->mask + ? "!" : "", + bpf->flow_label->val); + } + zlog_debug("BGP: %s FS PBR from %s to %s, %s %s", + add ? "adding" : "removing", + bpf->src == NULL ? "" : + prefix2str(bpf->src, bufsrc, sizeof(bufsrc)), + bpf->dst == NULL ? "" : + prefix2str(bpf->dst, bufdst, sizeof(bufdst)), + protocol_str, buffer); + +} + +static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp, + struct bgp_path_info *path, + struct bgp_pbr_filter *bpf, + struct nexthop *nh, + float *rate) +{ + struct bgp_pbr_match temp; + struct bgp_pbr_match_entry temp2; + struct bgp_pbr_match *bpm; + struct bgp_pbr_match_entry *bpme = NULL; + struct bgp_pbr_action temp3; + struct bgp_pbr_action *bpa = NULL; + struct bgp_pbr_match_entry_remain bpmer; + struct bgp_pbr_rule_remain bprr; + struct bgp_pbr_range_port *src_port; + struct bgp_pbr_range_port *dst_port; + struct bgp_pbr_range_port *pkt_len; + struct bgp_pbr_rule pbr_rule; + struct bgp_pbr_rule *bpr; + bool bpr_found = false; + bool bpme_found = false; + struct vrf *vrf = NULL; + + if (!bpf) + return; + src_port = bpf->src_port; + dst_port = bpf->dst_port; + pkt_len = bpf->pkt_len; + + if (BGP_DEBUG(zebra, ZEBRA)) + bgp_pbr_dump_entry(bpf, true); + + /* look for bpa first */ + memset(&temp3, 0, sizeof(temp3)); + if (rate) + temp3.rate = *rate; + if (nh) + memcpy(&temp3.nh, nh, sizeof(struct nexthop)); + temp3.vrf_id = bpf->vrf_id; + temp3.afi = family2afi(bpf->family); + bpa = hash_get(bgp->pbr_action_hash, &temp3, + bgp_pbr_action_alloc_intern); + + if (nh) + vrf = vrf_lookup_by_id(nh->vrf_id); + if (bpa->fwmark == 0) { + /* drop is handled by iptable */ + if (nh && nh->type == NEXTHOP_TYPE_BLACKHOLE) { + bpa->table_id = 0; + bpa->installed = true; + } else { + bpa->fwmark = bgp_zebra_tm_get_id(); + /* if action is redirect-vrf, then + * use directly table_id of vrf + */ + if (nh && vrf && !vrf_is_backend_netns() + && bpf->vrf_id != vrf->vrf_id) + bpa->table_id = vrf->data.l.table_id; + else + bpa->table_id = bpa->fwmark; + bpa->installed = false; + } + bpa->bgp = bgp; + bpa->unique = ++bgp_pbr_action_counter_unique; + /* 0 value is forbidden */ + bpa->install_in_progress = false; + } + if (bpf->type == BGP_PBR_IPRULE) { + memset(&pbr_rule, 0, sizeof(pbr_rule)); + pbr_rule.vrf_id = bpf->vrf_id; + pbr_rule.priority = 20; + if (bpf->src) { + pbr_rule.flags |= MATCH_IP_SRC_SET; + prefix_copy(&pbr_rule.src, bpf->src); + } + if (bpf->dst) { + pbr_rule.flags |= MATCH_IP_DST_SET; + prefix_copy(&pbr_rule.dst, bpf->dst); + } + pbr_rule.action = bpa; + bpr = hash_get(bgp->pbr_rule_hash, &pbr_rule, + bgp_pbr_rule_alloc_intern); + if (bpr->unique == 0) { + bpr->unique = ++bgp_pbr_action_counter_unique; + bpr->installed = false; + bpr->install_in_progress = false; + /* link bgp info to bpr */ + bpr->path = (void *)path; + } else + bpr_found = true; + /* already installed */ + if (bpr_found) { + struct bgp_path_info_extra *extra = + bgp_path_info_extra_get(path); + + if (extra && extra->flowspec && + listnode_lookup_nocheck(extra->flowspec->bgp_fs_iprule, + bpr)) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + zlog_err("%s: entry %p/%p already installed in bgp pbr iprule", + __func__, path, bpr); + return; + } + } + + bgp_pbr_bpa_add(bpa); + + /* ip rule add */ + if (!bpr->installed) + bgp_send_pbr_rule_action(bpa, bpr, true); + + /* A previous entry may already exist + * flush previous entry if necessary + */ + bprr.bpr_to_match = bpr; + bprr.bpr_found = NULL; + hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr); + if (bprr.bpr_found) { + static struct bgp_pbr_rule *local_bpr; + static struct bgp_pbr_action *local_bpa; + + local_bpr = bprr.bpr_found; + local_bpa = local_bpr->action; + bgp_pbr_flush_iprule(bgp, local_bpa, + local_bpr); + } + return; + } + /* then look for bpm */ + memset(&temp, 0, sizeof(temp)); + temp.vrf_id = bpf->vrf_id; + temp.family = bpf->family; + if (bpf->src) + temp.flags |= MATCH_IP_SRC_SET; + if (bpf->dst) + temp.flags |= MATCH_IP_DST_SET; + + if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) { + if (bpf->protocol == IPPROTO_ICMP) + temp.flags |= MATCH_ICMP_SET; + temp.flags |= MATCH_PORT_SRC_SET; + } + if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) { + if (bpf->protocol == IPPROTO_ICMP) + temp.flags |= MATCH_ICMP_SET; + temp.flags |= MATCH_PORT_DST_SET; + } + if (src_port && src_port->max_port) + temp.flags |= MATCH_PORT_SRC_RANGE_SET; + if (dst_port && dst_port->max_port) + temp.flags |= MATCH_PORT_DST_RANGE_SET; + + if (bpf->src == NULL || bpf->dst == NULL) { + if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) + temp.type = IPSET_NET_PORT; + else + temp.type = IPSET_NET; + } else { + if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET)) + temp.type = IPSET_NET_PORT_NET; + else + temp.type = IPSET_NET_NET; + } + if (pkt_len) { + temp.pkt_len_min = pkt_len->min_port; + if (pkt_len->max_port) + temp.pkt_len_max = pkt_len->max_port; + } else if (bpf->pkt_len_val) { + if (bpf->pkt_len_val->mask) + temp.flags |= MATCH_PKT_LEN_INVERSE_SET; + temp.pkt_len_min = bpf->pkt_len_val->val; + } + if (bpf->tcp_flags) { + temp.tcp_flags = bpf->tcp_flags->val; + temp.tcp_mask_flags = bpf->tcp_flags->mask; + } + if (bpf->dscp) { + if (bpf->dscp->mask) + temp.flags |= MATCH_DSCP_INVERSE_SET; + else + temp.flags |= MATCH_DSCP_SET; + temp.dscp_value = bpf->dscp->val; + } + if (bpf->flow_label) { + if (bpf->flow_label->mask) + temp.flags |= MATCH_FLOW_LABEL_INVERSE_SET; + else + temp.flags |= MATCH_FLOW_LABEL_SET; + temp.flow_label = bpf->flow_label->val; + } + if (bpf->fragment) { + if (bpf->fragment->mask) + temp.flags |= MATCH_FRAGMENT_INVERSE_SET; + temp.fragment = bpf->fragment->val; + } + if (bpf->protocol) { + temp.protocol = bpf->protocol; + temp.flags |= MATCH_PROTOCOL_SET; + } + temp.action = bpa; + bpm = hash_get(bgp->pbr_match_hash, &temp, + bgp_pbr_match_alloc_intern); + + /* new, then self allocate ipset_name and unique */ + if (bpm->unique == 0) { + bpm->unique = ++bgp_pbr_match_counter_unique; + /* 0 value is forbidden */ + snprintf(bpm->ipset_name, sizeof(bpm->ipset_name), + "match%p", bpm); + bpm->entry_hash = hash_create_size(8, + bgp_pbr_match_entry_hash_key, + bgp_pbr_match_entry_hash_equal, + "Match Entry Hash"); + bpm->installed = false; + + /* unique2 should be updated too */ + bpm->unique2 = ++bgp_pbr_match_iptable_counter_unique; + bpm->installed_in_iptable = false; + bpm->install_in_progress = false; + bpm->install_iptable_in_progress = false; + } + + memset(&temp2, 0, sizeof(temp2)); + if (bpf->src) + prefix_copy(&temp2.src, bpf->src); + else + temp2.src.family = bpf->family; + if (bpf->dst) + prefix_copy(&temp2.dst, bpf->dst); + else + temp2.dst.family = bpf->family; + temp2.src_port_min = src_port ? src_port->min_port : 0; + temp2.dst_port_min = dst_port ? dst_port->min_port : 0; + temp2.src_port_max = src_port ? src_port->max_port : 0; + temp2.dst_port_max = dst_port ? dst_port->max_port : 0; + temp2.proto = bpf->protocol; + bpme = hash_get(bpm->entry_hash, &temp2, + bgp_pbr_match_entry_alloc_intern); + if (bpme->unique == 0) { + bpme->unique = ++bgp_pbr_match_entry_counter_unique; + /* 0 value is forbidden */ + bpme->backpointer = bpm; + bpme->installed = false; + bpme->install_in_progress = false; + /* link bgp info to bpme */ + bpme->path = (void *)path; + } else + bpme_found = true; + + /* already installed */ + if (bpme_found) { + struct bgp_path_info_extra *extra = + bgp_path_info_extra_get(path); + + if (extra && extra->flowspec && + listnode_lookup_nocheck(extra->flowspec->bgp_fs_pbr, bpme)) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + zlog_err( + "%s: entry %p/%p already installed in bgp pbr", + __func__, path, bpme); + return; + } + } + /* BGP FS: append entry to zebra + * - policies are not routing entries and as such + * route replace semantics don't necessarily follow + * through to policy entries + * - because of that, not all policing information will be stored + * into zebra. and non selected policies will be suppressed from zebra + * - as consequence, in order to bring consistency + * a policy will be added, then ifan ecmp policy exists, + * it will be suppressed subsequently + */ + /* ip rule add */ + bgp_pbr_bpa_add(bpa); + + /* ipset create */ + if (!bpm->installed) + bgp_send_pbr_ipset_match(bpm, true); + /* ipset add */ + if (!bpme->installed) + bgp_send_pbr_ipset_entry_match(bpme, true); + + /* iptables */ + if (!bpm->installed_in_iptable) + bgp_send_pbr_iptable(bpa, bpm, true); + + /* A previous entry may already exist + * flush previous entry if necessary + */ + bpmer.bpme_to_match = bpme; + bpmer.bpme_found = NULL; + hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer); + if (bpmer.bpme_found) { + static struct bgp_pbr_match *local_bpm; + static struct bgp_pbr_action *local_bpa; + + local_bpm = bpmer.bpme_found->backpointer; + local_bpa = local_bpm->action; + bgp_pbr_flush_entry(bgp, local_bpa, + local_bpm, bpmer.bpme_found); + } + + +} + +static void bgp_pbr_policyroute_add_to_zebra_recursive( + struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf, + struct bgp_pbr_or_filter *bpof, struct nexthop *nh, float *rate, + uint8_t type_entry) +{ + struct listnode *node, *nnode; + struct bgp_pbr_val_mask *valmask; + uint8_t next_type_entry; + struct list *orig_list; + struct bgp_pbr_val_mask **target_val; + + if (type_entry == 0) { + bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); + return; + } + next_type_entry = bgp_pbr_next_type_entry(type_entry); + if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) { + orig_list = bpof->tcpflags; + target_val = &bpf->tcp_flags; + } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) { + orig_list = bpof->dscp; + target_val = &bpf->dscp; + } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) { + orig_list = bpof->pkt_len; + target_val = &bpf->pkt_len_val; + } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) { + orig_list = bpof->fragment; + target_val = &bpf->fragment; + } else if (type_entry == FLOWSPEC_ICMP_TYPE && + (bpof->icmp_type || bpof->icmp_code)) { + /* enumerate list for icmp - must be last one */ + bgp_pbr_icmp_action(bgp, path, bpf, bpof, true, nh, rate); + return; + } else { + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, next_type_entry); + return; + } + for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) { + *target_val = valmask; + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, next_type_entry); + } +} + +static void bgp_pbr_policyroute_add_to_zebra(struct bgp *bgp, + struct bgp_path_info *path, + struct bgp_pbr_filter *bpf, + struct bgp_pbr_or_filter *bpof, + struct nexthop *nh, float *rate) +{ + if (!bpof) { + bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); + return; + } + if (bpof->tcpflags) + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, FLOWSPEC_TCP_FLAGS); + else if (bpof->dscp) + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, FLOWSPEC_DSCP); + else if (bpof->pkt_len) + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, FLOWSPEC_PKT_LEN); + else if (bpof->fragment) + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, FLOWSPEC_FRAGMENT); + else if (bpof->icmp_type || bpof->icmp_code) + bgp_pbr_policyroute_add_to_zebra_recursive( + bgp, path, bpf, bpof, nh, rate, FLOWSPEC_ICMP_TYPE); + else + bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate); + /* flush bpof */ + if (bpof->tcpflags) + list_delete_all_node(bpof->tcpflags); + if (bpof->dscp) + list_delete_all_node(bpof->dscp); + if (bpof->pkt_len) + list_delete_all_node(bpof->pkt_len); + if (bpof->fragment) + list_delete_all_node(bpof->fragment); + if (bpof->icmp_type) + list_delete_all_node(bpof->icmp_type); + if (bpof->icmp_code) + list_delete_all_node(bpof->icmp_code); +} + +static void bgp_pbr_handle_entry(struct bgp *bgp, struct bgp_path_info *path, + struct bgp_pbr_entry_main *api, bool add) +{ + struct nexthop nh; + int i = 0; + int continue_loop = 1; + float rate = 0; + struct prefix *src = NULL, *dst = NULL; + uint8_t proto = 0; + struct bgp_pbr_range_port *srcp = NULL, *dstp = NULL; + struct bgp_pbr_range_port range, range_icmp_code; + struct bgp_pbr_range_port pkt_len; + struct bgp_pbr_filter bpf; + uint8_t kind_enum; + struct bgp_pbr_or_filter bpof; + struct bgp_pbr_val_mask bpvm; + + memset(&range, 0, sizeof(range)); + memset(&nh, 0, sizeof(nh)); + memset(&bpf, 0, sizeof(bpf)); + memset(&bpof, 0, sizeof(bpof)); + if (api->match_bitmask & PREFIX_SRC_PRESENT || + (api->type == BGP_PBR_IPRULE && + api->match_bitmask_iprule & PREFIX_SRC_PRESENT)) + src = &api->src_prefix; + if (api->match_bitmask & PREFIX_DST_PRESENT || + (api->type == BGP_PBR_IPRULE && + api->match_bitmask_iprule & PREFIX_DST_PRESENT)) + dst = &api->dst_prefix; + if (api->type == BGP_PBR_IPRULE) + bpf.type = api->type; + memset(&nh, 0, sizeof(nh)); + nh.vrf_id = VRF_UNKNOWN; + if (api->match_protocol_num) { + proto = (uint8_t)api->protocol[0].value; + if (api->afi == AF_INET6 && proto == IPPROTO_ICMPV6) + proto = IPPROTO_ICMP; + } + /* if match_port is selected, then either src or dst port will be parsed + * but not both at the same time + */ + if (api->match_port_num >= 1) { + bgp_pbr_extract(api->port, + api->match_port_num, + &range); + srcp = dstp = ⦥ + } else if (api->match_src_port_num >= 1) { + bgp_pbr_extract(api->src_port, + api->match_src_port_num, + &range); + srcp = ⦥ + dstp = NULL; + } else if (api->match_dst_port_num >= 1) { + bgp_pbr_extract(api->dst_port, + api->match_dst_port_num, + &range); + dstp = ⦥ + srcp = NULL; + } + if (api->match_icmp_type_num >= 1) { + proto = IPPROTO_ICMP; + if (bgp_pbr_extract(api->icmp_type, + api->match_icmp_type_num, + &range)) + srcp = ⦥ + else { + bpof.icmp_type = list_new(); + bgp_pbr_extract_enumerate(api->icmp_type, + api->match_icmp_type_num, + OPERATOR_UNARY_OR, + bpof.icmp_type, + FLOWSPEC_ICMP_TYPE); + } + } + if (api->match_icmp_code_num >= 1) { + proto = IPPROTO_ICMP; + if (bgp_pbr_extract(api->icmp_code, + api->match_icmp_code_num, + &range_icmp_code)) + dstp = &range_icmp_code; + else { + bpof.icmp_code = list_new(); + bgp_pbr_extract_enumerate(api->icmp_code, + api->match_icmp_code_num, + OPERATOR_UNARY_OR, + bpof.icmp_code, + FLOWSPEC_ICMP_CODE); + } + } + + if (api->match_tcpflags_num) { + kind_enum = bgp_pbr_match_val_get_operator(api->tcpflags, + api->match_tcpflags_num); + if (kind_enum == OPERATOR_UNARY_AND) { + bpf.tcp_flags = &bpvm; + bgp_pbr_extract_enumerate(api->tcpflags, + api->match_tcpflags_num, + OPERATOR_UNARY_AND, + bpf.tcp_flags, + FLOWSPEC_TCP_FLAGS); + } else if (kind_enum == OPERATOR_UNARY_OR) { + bpof.tcpflags = list_new(); + bgp_pbr_extract_enumerate(api->tcpflags, + api->match_tcpflags_num, + OPERATOR_UNARY_OR, + bpof.tcpflags, + FLOWSPEC_TCP_FLAGS); + } + } + if (api->match_packet_length_num) { + bool ret; + + ret = bgp_pbr_extract(api->packet_length, + api->match_packet_length_num, + &pkt_len); + if (ret) + bpf.pkt_len = &pkt_len; + else { + bpof.pkt_len = list_new(); + bgp_pbr_extract_enumerate(api->packet_length, + api->match_packet_length_num, + OPERATOR_UNARY_OR, + bpof.pkt_len, + FLOWSPEC_PKT_LEN); + } + } + if (api->match_dscp_num >= 1) { + bpof.dscp = list_new(); + bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num, + OPERATOR_UNARY_OR, + bpof.dscp, FLOWSPEC_DSCP); + } + if (api->match_fragment_num) { + bpof.fragment = list_new(); + bgp_pbr_extract_enumerate(api->fragment, + api->match_fragment_num, + OPERATOR_UNARY_OR, + bpof.fragment, + FLOWSPEC_FRAGMENT); + } + bpf.vrf_id = api->vrf_id; + bpf.src = src; + bpf.dst = dst; + bpf.protocol = proto; + bpf.src_port = srcp; + bpf.dst_port = dstp; + bpf.family = afi2family(api->afi); + if (!add) { + bgp_pbr_policyroute_remove_from_zebra(bgp, path, &bpf, &bpof); + return; + } + /* no action for add = true */ + for (i = 0; i < api->action_num; i++) { + switch (api->actions[i].action) { + case ACTION_TRAFFICRATE: + /* drop packet */ + if (api->actions[i].u.r.rate == 0) { + nh.vrf_id = api->vrf_id; + nh.type = NEXTHOP_TYPE_BLACKHOLE; + bgp_pbr_policyroute_add_to_zebra( + bgp, path, &bpf, &bpof, &nh, &rate); + } else { + /* update rate. can be reentrant */ + rate = api->actions[i].u.r.rate; + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_warn("PBR: ignoring Set action rate %f", + api->actions[i].u.r.rate); + } + } + break; + case ACTION_TRAFFIC_ACTION: + if (api->actions[i].u.za.filter + & TRAFFIC_ACTION_SAMPLE) { + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_warn("PBR: Sample action Ignored"); + } + } + /* terminate action: run other filters + */ + break; + case ACTION_REDIRECT_IP: + nh.vrf_id = api->vrf_id; + if (api->afi == AFI_IP) { + nh.type = NEXTHOP_TYPE_IPV4; + nh.gate.ipv4.s_addr = + api->actions[i].u.zr. + redirect_ip_v4.s_addr; + } else { + nh.type = NEXTHOP_TYPE_IPV6; + memcpy(&nh.gate.ipv6, + &api->actions[i].u.zr.redirect_ip_v6, + sizeof(struct in6_addr)); + } + bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof, + &nh, &rate); + /* XXX combination with REDIRECT_VRF + * + REDIRECT_NH_IP not done + */ + continue_loop = 0; + break; + case ACTION_REDIRECT: + if (api->afi == AFI_IP) + nh.type = NEXTHOP_TYPE_IPV4; + else + nh.type = NEXTHOP_TYPE_IPV6; + nh.vrf_id = api->actions[i].u.redirect_vrf; + bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof, + &nh, &rate); + continue_loop = 0; + break; + case ACTION_MARKING: + if (BGP_DEBUG(pbr, PBR)) { + bgp_pbr_print_policy_route(api); + zlog_warn("PBR: Set DSCP/FlowLabel %u Ignored", + api->actions[i].u.marking_dscp); + } + break; + default: + break; + } + if (continue_loop == 0) + break; + } +} + +void bgp_pbr_update_entry(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *info, afi_t afi, safi_t safi, + bool nlri_update) +{ + struct bgp_pbr_entry_main api; + + if (safi != SAFI_FLOWSPEC) + return; /* not supported */ + /* Make Zebra API structure. */ + memset(&api, 0, sizeof(api)); + api.vrf_id = bgp->vrf_id; + api.afi = afi; + + if (!bgp_zebra_tm_chunk_obtained()) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + flog_err(EC_BGP_TABLE_CHUNK, + "%s: table chunk not obtained yet", __func__); + return; + } + + if (bgp_pbr_build_and_validate_entry(p, info, &api) < 0) { + if (BGP_DEBUG(pbr, PBR_ERROR)) + flog_err(EC_BGP_FLOWSPEC_INSTALLATION, + "%s: cancel updating entry %p in bgp pbr", + __func__, info); + return; + } + bgp_pbr_handle_entry(bgp, info, &api, nlri_update); +} + +int bgp_pbr_interface_compare(const struct bgp_pbr_interface *a, + const struct bgp_pbr_interface *b) +{ + return strcmp(a->name, b->name); +} + +struct bgp_pbr_interface *bgp_pbr_interface_lookup(const char *name, + struct bgp_pbr_interface_head *head) +{ + struct bgp_pbr_interface pbr_if; + + strlcpy(pbr_if.name, name, sizeof(pbr_if.name)); + return (RB_FIND(bgp_pbr_interface_head, + head, &pbr_if)); +} + +/* this function resets to the default policy routing + * go back to default status + */ +void bgp_pbr_reset(struct bgp *bgp, afi_t afi) +{ + struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; + struct bgp_pbr_interface_head *head; + struct bgp_pbr_interface *pbr_if; + + if (!bgp_pbr_cfg) + return; + if (afi == AFI_IP) + head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); + else + head = &(bgp_pbr_cfg->ifaces_by_name_ipv6); + while (!RB_EMPTY(bgp_pbr_interface_head, head)) { + pbr_if = RB_ROOT(bgp_pbr_interface_head, head); + RB_REMOVE(bgp_pbr_interface_head, head, pbr_if); + XFREE(MTYPE_TMP, pbr_if); + } +} diff --git a/bgpd/bgp_pbr.h b/bgpd/bgp_pbr.h new file mode 100644 index 0000000..cb16c4d --- /dev/null +++ b/bgpd/bgp_pbr.h @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP pbr + * Copyright (C) 6WIND + */ +#ifndef __BGP_PBR_H__ +#define __BGP_PBR_H__ + +#include "nexthop.h" +#include "zclient.h" + +/* flowspec case: 0 to 3 actions maximum: + * 1 redirect + * 1 set dscp + * 1 set traffic rate + */ +#define ACTIONS_MAX_NUM 4 +enum bgp_pbr_action_enum { + ACTION_TRAFFICRATE = 1, + ACTION_TRAFFIC_ACTION = 2, + ACTION_REDIRECT = 3, + ACTION_MARKING = 4, + ACTION_REDIRECT_IP = 5 +}; + +#define TRAFFIC_ACTION_SAMPLE (1 << 0) +#define TRAFFIC_ACTION_TERMINATE (1 << 1) +#define TRAFFIC_ACTION_DISTRIBUTE (1 << 2) + +#define OPERATOR_COMPARE_LESS_THAN (1<<1) +#define OPERATOR_COMPARE_GREATER_THAN (1<<2) +#define OPERATOR_COMPARE_EQUAL_TO (1<<3) +#define OPERATOR_COMPARE_EXACT_MATCH (1<<4) + +#define OPERATOR_UNARY_OR (1<<1) +#define OPERATOR_UNARY_AND (1<<2) + +/* struct used to store values [0;65535] + * this can be used for port number of protocol + */ +#define BGP_PBR_MATCH_VAL_MAX 5 + +struct bgp_pbr_match_val { + uint16_t value; + uint8_t compare_operator; + uint8_t unary_operator; +}; + +#define FRAGMENT_DONT 1 +#define FRAGMENT_IS 2 +#define FRAGMENT_FIRST 4 +#define FRAGMENT_LAST 8 + +struct bgp_pbr_entry_action { + /* used to store enum bgp_pbr_action_enum enumerate */ + uint8_t action; + union { + union { + uint8_t rate_info[4]; /* IEEE.754.1985 */ + float rate; + } r __attribute__((aligned(8))); + struct _pbr_action { + uint8_t do_sample; + uint8_t filter; + } za; + vrf_id_t redirect_vrf; + struct _pbr_redirect_ip { + struct in_addr redirect_ip_v4; + struct in6_addr redirect_ip_v6; + uint8_t duplicate; + } zr; + uint8_t marking_dscp; + } u __attribute__((aligned(8))); +}; + +/* BGP Policy Route structure */ +struct bgp_pbr_entry_main { +#define BGP_PBR_UNDEFINED 0 +#define BGP_PBR_IPSET 1 +#define BGP_PBR_IPRULE 2 + uint8_t type; + + /* + * This is an enum but we are going to treat it as a uint8_t + * for purpose of encoding/decoding + */ + afi_t afi; + safi_t safi; + +#define PREFIX_SRC_PRESENT (1 << 0) +#define PREFIX_DST_PRESENT (1 << 1) + uint8_t match_bitmask_iprule; + uint8_t match_bitmask; + + uint8_t match_src_port_num; + uint8_t match_dst_port_num; + uint8_t match_port_num; + uint8_t match_protocol_num; + uint8_t match_icmp_type_num; + uint8_t match_icmp_code_num; + uint8_t match_packet_length_num; + uint8_t match_dscp_num; + uint8_t match_tcpflags_num; + uint8_t match_fragment_num; + uint8_t match_flowlabel_num; + + struct prefix src_prefix; + struct prefix dst_prefix; + uint8_t src_prefix_offset; + uint8_t dst_prefix_offset; + +#define PROTOCOL_UDP 17 +#define PROTOCOL_TCP 6 +#define PROTOCOL_ICMP 1 +#define PROTOCOL_ICMPV6 58 + struct bgp_pbr_match_val protocol[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val src_port[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val dst_port[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val port[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val icmp_type[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val icmp_code[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val packet_length[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val dscp[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val flow_label[BGP_PBR_MATCH_VAL_MAX]; + + struct bgp_pbr_match_val tcpflags[BGP_PBR_MATCH_VAL_MAX]; + struct bgp_pbr_match_val fragment[BGP_PBR_MATCH_VAL_MAX]; + + uint16_t action_num; + struct bgp_pbr_entry_action actions[ACTIONS_MAX_NUM]; + + vrf_id_t vrf_id; +}; + +struct bgp_pbr_interface { + RB_ENTRY(bgp_pbr_interface) id_entry; + char name[IFNAMSIZ]; +}; + +RB_HEAD(bgp_pbr_interface_head, bgp_pbr_interface); +RB_PROTOTYPE(bgp_pbr_interface_head, bgp_pbr_interface, id_entry, + bgp_pbr_interface_compare); + +extern int bgp_pbr_interface_compare(const struct bgp_pbr_interface *a, + const struct bgp_pbr_interface *b); + +struct bgp_pbr_config { + struct bgp_pbr_interface_head ifaces_by_name_ipv4; + bool pbr_interface_any_ipv4; + struct bgp_pbr_interface_head ifaces_by_name_ipv6; + bool pbr_interface_any_ipv6; +}; + +extern struct bgp_pbr_config *bgp_pbr_cfg; + +struct bgp_pbr_rule { + uint32_t flags; + struct prefix src; + struct prefix dst; + struct bgp_pbr_action *action; + vrf_id_t vrf_id; + uint32_t unique; + uint32_t priority; + bool installed; + bool install_in_progress; + void *path; +}; + +struct bgp_pbr_match { + char ipset_name[ZEBRA_IPSET_NAME_SIZE]; + + /* mapped on enum ipset_type + */ + uint32_t type; + + uint32_t flags; + uint8_t family; + + uint16_t pkt_len_min; + uint16_t pkt_len_max; + uint16_t tcp_flags; + uint16_t tcp_mask_flags; + uint8_t dscp_value; + uint8_t fragment; + uint8_t protocol; + uint16_t flow_label; + + vrf_id_t vrf_id; + + /* unique identifier for ipset create transaction + */ + uint32_t unique; + + /* unique identifier for iptable add transaction + */ + uint32_t unique2; + + bool installed; + bool install_in_progress; + + bool installed_in_iptable; + bool install_iptable_in_progress; + + struct hash *entry_hash; + + struct bgp_pbr_action *action; + +}; + +struct bgp_pbr_match_entry { + struct bgp_pbr_match *backpointer; + + uint32_t unique; + + struct prefix src; + struct prefix dst; + + uint16_t src_port_min; + uint16_t src_port_max; + uint16_t dst_port_min; + uint16_t dst_port_max; + uint8_t proto; + + void *path; + + bool installed; + bool install_in_progress; +}; + +struct bgp_pbr_action { + + /* + * The Unique identifier of this specific pbrms + */ + uint32_t unique; + + uint32_t fwmark; + + uint32_t table_id; + + float rate; + + /* + * nexthop information, or drop information + * contains src vrf_id and nh contains dest vrf_id + */ + vrf_id_t vrf_id; + struct nexthop nh; + + bool installed; + bool install_in_progress; + uint32_t refcnt; + struct bgp *bgp; + afi_t afi; +}; + +extern struct bgp_pbr_rule *bgp_pbr_rule_lookup(vrf_id_t vrf_id, + uint32_t unique); + +extern struct bgp_pbr_action *bgp_pbr_action_rule_lookup(vrf_id_t vrf_id, + uint32_t unique); + +extern struct bgp_pbr_match *bgp_pbr_match_ipset_lookup(vrf_id_t vrf_id, + uint32_t unique); + +extern struct bgp_pbr_match_entry *bgp_pbr_match_ipset_entry_lookup( + vrf_id_t vrf_id, char *name, + uint32_t unique); +extern struct bgp_pbr_match *bgp_pbr_match_iptable_lookup(vrf_id_t vrf_id, + uint32_t unique); + +extern void bgp_pbr_cleanup(struct bgp *bgp); +extern void bgp_pbr_init(struct bgp *bgp); + +extern uint32_t bgp_pbr_rule_hash_key(const void *arg); +extern bool bgp_pbr_rule_hash_equal(const void *arg1, + const void *arg2); +extern uint32_t bgp_pbr_action_hash_key(const void *arg); +extern bool bgp_pbr_action_hash_equal(const void *arg1, + const void *arg2); +extern uint32_t bgp_pbr_match_entry_hash_key(const void *arg); +extern bool bgp_pbr_match_entry_hash_equal(const void *arg1, + const void *arg2); +extern uint32_t bgp_pbr_match_hash_key(const void *arg); +extern bool bgp_pbr_match_hash_equal(const void *arg1, + const void *arg2); + +void bgp_pbr_print_policy_route(struct bgp_pbr_entry_main *api); + +struct bgp_path_info; +extern void bgp_pbr_update_entry(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *new_select, afi_t afi, + safi_t safi, bool nlri_update); + +/* bgp pbr utilities */ +extern struct bgp_pbr_interface *pbr_interface_lookup(const char *name); +extern void bgp_pbr_reset(struct bgp *bgp, afi_t afi); +extern struct bgp_pbr_interface *bgp_pbr_interface_lookup(const char *name, + struct bgp_pbr_interface_head *head); + +extern int bgp_pbr_build_and_validate_entry(const struct prefix *p, + struct bgp_path_info *path, + struct bgp_pbr_entry_main *api); +#endif /* __BGP_PBR_H__ */ diff --git a/bgpd/bgp_rd.c b/bgpd/bgp_rd.c new file mode 100644 index 0000000..bb9f76b --- /dev/null +++ b/bgpd/bgp_rd.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP RD definitions for BGP-based VPNs (IP/EVPN) + * -- brought over from bgpd/bgp_mplsvpn.c + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include +#include "command.h" +#include "log.h" +#include "prefix.h" +#include "memory.h" +#include "stream.h" +#include "filter.h" +#include "frrstr.h" + +#include "lib/printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_rd.h" +#include "bgpd/bgp_attr.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#endif + +uint16_t decode_rd_type(const uint8_t *pnt) +{ + uint16_t v; + + v = ((uint16_t)*pnt++ << 8); +#ifdef ENABLE_BGP_VNC + /* + * VNC L2 stores LHI in lower byte, so omit it + */ + if (v != RD_TYPE_VNC_ETH) + v |= (uint16_t)*pnt; +#else /* duplicate code for clarity */ + v |= (uint16_t)*pnt; +#endif + return v; +} + +void encode_rd_type(uint16_t v, uint8_t *pnt) +{ + *((uint16_t *)pnt) = htons(v); +} + +/* type == RD_TYPE_AS */ +void decode_rd_as(const uint8_t *pnt, struct rd_as *rd_as) +{ + rd_as->as = (uint16_t)*pnt++ << 8; + rd_as->as |= (uint16_t)*pnt++; + ptr_get_be32(pnt, &rd_as->val); +} + +/* type == RD_TYPE_AS4 */ +void decode_rd_as4(const uint8_t *pnt, struct rd_as *rd_as) +{ + pnt = ptr_get_be32(pnt, &rd_as->as); + rd_as->val = ((uint16_t)*pnt++ << 8); + rd_as->val |= (uint16_t)*pnt; +} + +/* type == RD_TYPE_IP */ +void decode_rd_ip(const uint8_t *pnt, struct rd_ip *rd_ip) +{ + memcpy(&rd_ip->ip, pnt, 4); + pnt += 4; + + rd_ip->val = ((uint16_t)*pnt++ << 8); + rd_ip->val |= (uint16_t)*pnt; +} + +#ifdef ENABLE_BGP_VNC +/* type == RD_TYPE_VNC_ETH */ +void decode_rd_vnc_eth(const uint8_t *pnt, struct rd_vnc_eth *rd_vnc_eth) +{ + rd_vnc_eth->type = RD_TYPE_VNC_ETH; + rd_vnc_eth->local_nve_id = pnt[1]; + memcpy(rd_vnc_eth->macaddr.octet, pnt + 2, ETH_ALEN); +} +#endif + +int str2prefix_rd(const char *str, struct prefix_rd *prd) +{ + int ret = 0, type = RD_TYPE_UNDEFINED; + char *p, *p2; + struct stream *s = NULL; + char *half = NULL; + struct in_addr addr; + as_t as_val; + + prd->family = AF_UNSPEC; + prd->prefixlen = 64; + + p = strchr(str, ':'); + if (!p) + goto out; + + /* a second ':' is accepted */ + p2 = strchr(p + 1, ':'); + if (p2) { + /* type is in first part */ + half = XMALLOC(MTYPE_TMP, (p - str) + 1); + memcpy(half, str, (p - str)); + half[p - str] = '\0'; + type = atoi(half); + if (type != RD_TYPE_AS && type != RD_TYPE_IP && + type != RD_TYPE_AS4) + goto out; + XFREE(MTYPE_TMP, half); + half = XMALLOC(MTYPE_TMP, (p2 - p)); + memcpy(half, p + 1, (p2 - p - 1)); + half[p2 - p - 1] = '\0'; + p = p2 + 1; + } else { + half = XMALLOC(MTYPE_TMP, (p - str) + 1); + memcpy(half, str, (p - str)); + half[p - str] = '\0'; + } + if (!all_digit(p + 1)) + goto out; + + s = stream_new(RD_BYTES); + + /* if it is an AS format or an IP */ + if (asn_str2asn(half, &as_val)) { + if (as_val > UINT16_MAX) { + stream_putw(s, RD_TYPE_AS4); + stream_putl(s, as_val); + stream_putw(s, atol(p + 1)); + if (type != RD_TYPE_UNDEFINED && type != RD_TYPE_AS4) + goto out; + } else { + stream_putw(s, RD_TYPE_AS); + stream_putw(s, as_val); + stream_putl(s, atol(p + 1)); + if (type != RD_TYPE_UNDEFINED && type != RD_TYPE_AS) + goto out; + } + } else if (inet_aton(half, &addr)) { + stream_putw(s, RD_TYPE_IP); + stream_put_in_addr(s, &addr); + stream_putw(s, atol(p + 1)); + if (type != RD_TYPE_UNDEFINED && type != RD_TYPE_IP) + goto out; + } else + goto out; + memcpy(prd->val, s->data, 8); + ret = 1; + +out: + if (s) + stream_free(s); + XFREE(MTYPE_TMP, half); + return ret; +} + +char *prefix_rd2str(const struct prefix_rd *prd, char *buf, size_t size, + enum asnotation_mode asnotation) +{ + const uint8_t *pnt; + uint16_t type; + struct rd_as rd_as; + struct rd_ip rd_ip; + int len = 0; + + assert(size >= RD_ADDRSTRLEN); + + pnt = prd->val; + + type = decode_rd_type(pnt); + + if (type == RD_TYPE_AS) { + decode_rd_as(pnt + 2, &rd_as); + len += snprintfrr(buf + len, size - len, ASN_FORMAT(asnotation), + &rd_as.as); + snprintfrr(buf + len, size - len, ":%u", rd_as.val); + return buf; + } else if (type == RD_TYPE_AS4) { + decode_rd_as4(pnt + 2, &rd_as); + len += snprintfrr(buf + len, size - len, ASN_FORMAT(asnotation), + &rd_as.as); + snprintfrr(buf + len, size - len, ":%u", rd_as.val); + return buf; + } else if (type == RD_TYPE_IP) { + decode_rd_ip(pnt + 2, &rd_ip); + snprintfrr(buf, size, "%pI4:%hu", &rd_ip.ip, rd_ip.val); + return buf; + } +#ifdef ENABLE_BGP_VNC + else if (type == RD_TYPE_VNC_ETH) { + snprintf(buf, size, "LHI:%d, %02x:%02x:%02x:%02x:%02x:%02x", + *(pnt + 1), /* LHI */ + *(pnt + 2), /* MAC[0] */ + *(pnt + 3), *(pnt + 4), *(pnt + 5), *(pnt + 6), + *(pnt + 7)); + + return buf; + } +#endif + + snprintf(buf, size, "Unknown Type: %d", type); + return buf; +} + +void form_auto_rd(struct in_addr router_id, + uint16_t rd_id, + struct prefix_rd *prd) +{ + char buf[100]; + + prd->family = AF_UNSPEC; + prd->prefixlen = 64; + snprintfrr(buf, sizeof(buf), "%pI4:%hu", &router_id, rd_id); + (void)str2prefix_rd(buf, prd); +} + +static ssize_t printfrr_prd_asnotation(struct fbuf *buf, + struct printfrr_eargs *ea, + const void *ptr, + enum asnotation_mode asnotation) +{ + char rd_buf[RD_ADDRSTRLEN]; + + if (!ptr) + return bputs(buf, "(null)"); + + prefix_rd2str(ptr, rd_buf, sizeof(rd_buf), asnotation); + + return bputs(buf, rd_buf); +} + +printfrr_ext_autoreg_p("RDP", printfrr_prd); +static ssize_t printfrr_prd(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_prd_asnotation(buf, ea, ptr, ASNOTATION_PLAIN); +} + +printfrr_ext_autoreg_p("RDD", printfrr_prd_dot); +static ssize_t printfrr_prd_dot(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_prd_asnotation(buf, ea, ptr, ASNOTATION_DOT); +} + +printfrr_ext_autoreg_p("RDE", printfrr_prd_dotplus); +static ssize_t printfrr_prd_dotplus(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_prd_asnotation(buf, ea, ptr, ASNOTATION_DOTPLUS); +} diff --git a/bgpd/bgp_rd.h b/bgpd/bgp_rd.h new file mode 100644 index 0000000..93dda17 --- /dev/null +++ b/bgpd/bgp_rd.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP RD definitions for BGP-based VPNs (IP/EVPN) + * -- brought over from bgpd/bgp_mplsvpn.h + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_RD_H +#define _QUAGGA_BGP_RD_H + +#include "asn.h" +#include "prefix.h" + +/* RD types */ +#define RD_TYPE_UNDEFINED (-1) +#define RD_TYPE_AS 0 +#define RD_TYPE_IP 1 +#define RD_TYPE_AS4 2 + +#ifdef ENABLE_BGP_VNC +#define RD_TYPE_VNC_ETH 0xff00 /* VNC L2VPN */ +#endif + +#define RD_ADDRSTRLEN 28 +#define RD_BYTES 8 + +#define BGP_RD_AS_FORMAT(mode) \ + ((mode == ASNOTATION_DOT) \ + ? "%pRDD" \ + : ((mode == ASNOTATION_DOTPLUS) ? "%pRDE" : "%pRDP")) + +#define BGP_RD_AS_FORMAT_SPACE(mode) \ + ((mode == ASNOTATION_DOT) \ + ? "%-21pRDD" \ + : ((mode == ASNOTATION_DOTPLUS) ? "%-21pRDE" : "%-21pRDP")) + +struct rd_as { + uint16_t type; + as_t as; + uint32_t val; +}; + +struct rd_ip { + uint16_t type; + uint16_t val; + struct in_addr ip; +}; + +#ifdef ENABLE_BGP_VNC +struct rd_vnc_eth { + uint16_t type; + uint8_t local_nve_id; + struct ethaddr macaddr; +}; +#endif + +extern uint16_t decode_rd_type(const uint8_t *pnt); +extern void encode_rd_type(uint16_t, uint8_t *); + +extern void decode_rd_as(const uint8_t *pnt, struct rd_as *rd_as); +extern void decode_rd_as4(const uint8_t *pnt, struct rd_as *rd_as); +extern void decode_rd_ip(const uint8_t *pnt, struct rd_ip *rd_ip); +#ifdef ENABLE_BGP_VNC +extern void decode_rd_vnc_eth(const uint8_t *pnt, + struct rd_vnc_eth *rd_vnc_eth); +#endif + +extern int str2prefix_rd(const char *, struct prefix_rd *); +extern char *prefix_rd2str(const struct prefix_rd *prd, char *buf, size_t size, + enum asnotation_mode asnotation); +extern void form_auto_rd(struct in_addr router_id, uint16_t rd_id, + struct prefix_rd *prd); + +#endif /* _QUAGGA_BGP_RD_H */ diff --git a/bgpd/bgp_regex.c b/bgpd/bgp_regex.c new file mode 100644 index 0000000..68137a5 --- /dev/null +++ b/bgpd/bgp_regex.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS regular expression routine + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "log.h" +#include "command.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd.h" +#include "bgp_aspath.h" +#include "bgp_regex.h" + +/* Character `_' has special mean. It represents [,{}() ] and the + beginning of the line(^) and the end of the line ($). + + (^|[,{}() ]|$) */ + +regex_t *bgp_regcomp(const char *regstr) +{ + /* Convert _ character to generic regular expression. */ + int i, j; + int len; + int magic = 0; + char *magic_str; + char magic_regexp[] = "(^|[,{}() ]|$)"; + int ret; + regex_t *regex; + + len = strlen(regstr); + for (i = 0; i < len; i++) + if (regstr[i] == '_') + magic++; + + magic_str = XMALLOC(MTYPE_TMP, len + (14 * magic) + 1); + + for (i = 0, j = 0; i < len; i++) { + if (regstr[i] == '_') { + memcpy(magic_str + j, magic_regexp, + strlen(magic_regexp)); + j += strlen(magic_regexp); + } else + magic_str[j++] = regstr[i]; + } + magic_str[j] = '\0'; + + regex = XMALLOC(MTYPE_BGP_REGEXP, sizeof(regex_t)); + + ret = regcomp(regex, magic_str, REG_EXTENDED | REG_NOSUB); + + XFREE(MTYPE_TMP, magic_str); + + if (ret != 0) { + XFREE(MTYPE_BGP_REGEXP, regex); + return NULL; + } + + return regex; +} + +int bgp_regexec(regex_t *regex, struct aspath *aspath) +{ + return regexec(regex, aspath->str, 0, NULL, 0); +} + +void bgp_regex_free(regex_t *regex) +{ + regfree(regex); + XFREE(MTYPE_BGP_REGEXP, regex); +} diff --git a/bgpd/bgp_regex.h b/bgpd/bgp_regex.h new file mode 100644 index 0000000..c2607bc --- /dev/null +++ b/bgpd/bgp_regex.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* AS regular expression routine + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _FRR_BGP_REGEX_H +#define _FRR_BGP_REGEX_H + +#include + +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ + +extern void bgp_regex_free(regex_t *regex); +extern regex_t *bgp_regcomp(const char *str); +extern int bgp_regexec(regex_t *regex, struct aspath *aspath); + +#endif /* _FRR_BGP_REGEX_H */ diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c new file mode 100644 index 0000000..666adc4 --- /dev/null +++ b/bgpd/bgp_route.c @@ -0,0 +1,16363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP routing information + * Copyright (C) 1996, 97, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2016 Job Snijders + */ + +#include +#include + +#include "printfrr.h" +#include "frrstr.h" +#include "prefix.h" +#include "linklist.h" +#include "memory.h" +#include "command.h" +#include "stream.h" +#include "filter.h" +#include "log.h" +#include "routemap.h" +#include "buffer.h" +#include "sockunion.h" +#include "plist.h" +#include "frrevent.h" +#include "workqueue.h" +#include "queue.h" +#include "memory.h" +#include "srv6.h" +#include "lib/json.h" +#include "lib_errors.h" +#include "zclient.h" +#include "frrdistance.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_trace.h" +#include "bgpd/bgp_rpki.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#endif +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_encap_tlv.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_pbr.h" + +#include "bgpd/bgp_route_clippy.c" + +DEFINE_HOOK(bgp_snmp_update_stats, + (struct bgp_dest *rn, struct bgp_path_info *pi, bool added), + (rn, pi, added)); + +DEFINE_HOOK(bgp_rpki_prefix_status, + (struct peer *peer, struct attr *attr, + const struct prefix *prefix), + (peer, attr, prefix)); + +DEFINE_HOOK(bgp_route_update, + (struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, + struct bgp_path_info *old_route, struct bgp_path_info *new_route), + (bgp, afi, safi, bn, old_route, new_route)); + +/* Extern from bgp_dump.c */ +extern const char *bgp_origin_str[]; +extern const char *bgp_origin_long_str[]; + +/* PMSI strings. */ +#define PMSI_TNLTYPE_STR_NO_INFO "No info" +#define PMSI_TNLTYPE_STR_DEFAULT PMSI_TNLTYPE_STR_NO_INFO +static const struct message bgp_pmsi_tnltype_str[] = { + {PMSI_TNLTYPE_NO_INFO, PMSI_TNLTYPE_STR_NO_INFO}, + {PMSI_TNLTYPE_RSVP_TE_P2MP, "RSVP-TE P2MP"}, + {PMSI_TNLTYPE_MLDP_P2MP, "mLDP P2MP"}, + {PMSI_TNLTYPE_PIM_SSM, "PIM-SSM"}, + {PMSI_TNLTYPE_PIM_SM, "PIM-SM"}, + {PMSI_TNLTYPE_PIM_BIDIR, "PIM-BIDIR"}, + {PMSI_TNLTYPE_INGR_REPL, "Ingress Replication"}, + {PMSI_TNLTYPE_MLDP_MP2MP, "mLDP MP2MP"}, + {0} +}; + +#define VRFID_NONE_STR "-" +#define SOFT_RECONFIG_TASK_MAX_PREFIX 25000 + +static inline char *bgp_route_dump_path_info_flags(struct bgp_path_info *pi, + char *buf, size_t len) +{ + uint32_t flags = pi->flags; + + if (flags == 0) { + snprintfrr(buf, len, "None "); + return buf; + } + + snprintfrr(buf, len, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + CHECK_FLAG(flags, BGP_PATH_IGP_CHANGED) ? "IGP Changed " : "", + CHECK_FLAG(flags, BGP_PATH_DAMPED) ? "Damped" : "", + CHECK_FLAG(flags, BGP_PATH_HISTORY) ? "History " : "", + CHECK_FLAG(flags, BGP_PATH_SELECTED) ? "Selected " : "", + CHECK_FLAG(flags, BGP_PATH_VALID) ? "Valid " : "", + CHECK_FLAG(flags, BGP_PATH_ATTR_CHANGED) ? "Attr Changed " + : "", + CHECK_FLAG(flags, BGP_PATH_DMED_CHECK) ? "Dmed Check " : "", + CHECK_FLAG(flags, BGP_PATH_DMED_SELECTED) ? "Dmed Selected " + : "", + CHECK_FLAG(flags, BGP_PATH_STALE) ? "Stale " : "", + CHECK_FLAG(flags, BGP_PATH_REMOVED) ? "Removed " : "", + CHECK_FLAG(flags, BGP_PATH_COUNTED) ? "Counted " : "", + CHECK_FLAG(flags, BGP_PATH_MULTIPATH) ? "Mpath " : "", + CHECK_FLAG(flags, BGP_PATH_MULTIPATH_CHG) ? "Mpath Chg " : "", + CHECK_FLAG(flags, BGP_PATH_RIB_ATTR_CHG) ? "Rib Chg " : "", + CHECK_FLAG(flags, BGP_PATH_ANNC_NH_SELF) ? "NH Self " : "", + CHECK_FLAG(flags, BGP_PATH_LINK_BW_CHG) ? "LinkBW Chg " : "", + CHECK_FLAG(flags, BGP_PATH_ACCEPT_OWN) ? "Accept Own " : "", + CHECK_FLAG(flags, BGP_PATH_MPLSVPN_LABEL_NH) ? "MPLS Label " + : "", + CHECK_FLAG(flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND) + ? "MPLS Label Bind " + : "", + CHECK_FLAG(flags, BGP_PATH_UNSORTED) ? "Unsorted " : ""); + + return buf; +} + +DEFINE_HOOK(bgp_process, + (struct bgp * bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, + struct peer *peer, bool withdraw), + (bgp, afi, safi, bn, peer, withdraw)); + +/** Test if path is suppressed. */ +bool bgp_path_suppressed(struct bgp_path_info *pi) +{ + if (pi->extra == NULL || pi->extra->aggr_suppressors == NULL) + return false; + + return listcount(pi->extra->aggr_suppressors) > 0; +} + +struct bgp_dest *bgp_afi_node_get(struct bgp_table *table, afi_t afi, + safi_t safi, const struct prefix *p, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_dest *pdest = NULL; + + assert(table); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + pdest = bgp_node_get(table, (struct prefix *)prd); + + if (!bgp_dest_has_bgp_path_info_data(pdest)) + bgp_dest_set_bgp_table_info( + pdest, bgp_table_init(table->bgp, afi, safi)); + else + pdest = bgp_dest_unlock_node(pdest); + + assert(pdest); + table = bgp_dest_get_bgp_table_info(pdest); + } + + dest = bgp_node_get(table, p); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) + dest->pdest = pdest; + + return dest; +} + +struct bgp_dest *bgp_safi_node_lookup(struct bgp_table *table, safi_t safi, + const struct prefix *p, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_dest *pdest = NULL; + + if (!table) + return NULL; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + pdest = bgp_node_lookup(table, (struct prefix *)prd); + if (!pdest) + return NULL; + + if (!bgp_dest_has_bgp_path_info_data(pdest)) { + bgp_dest_unlock_node(pdest); + return NULL; + } + + table = bgp_dest_get_bgp_table_info(pdest); + } + + dest = bgp_node_lookup(table, p); + + return dest; +} + +/* Allocate bgp_path_info_extra */ +static struct bgp_path_info_extra *bgp_path_info_extra_new(void) +{ + struct bgp_path_info_extra *new; + new = XCALLOC(MTYPE_BGP_ROUTE_EXTRA, + sizeof(struct bgp_path_info_extra)); + new->flowspec = NULL; + return new; +} + +void bgp_path_info_extra_free(struct bgp_path_info_extra **extra) +{ + struct bgp_path_info_extra *e; + + if (!extra || !*extra) + return; + + e = *extra; + + if (e->damp_info) + bgp_damp_info_free(e->damp_info, NULL, 0); + e->damp_info = NULL; + if (e->vrfleak && e->vrfleak->parent) { + struct bgp_path_info *bpi = + (struct bgp_path_info *)e->vrfleak->parent; + + if (bpi->net) { + /* FIXME: since multiple e may have the same e->parent + * and e->parent->net is holding a refcount for each + * of them, we need to do some fudging here. + * + * WARNING: if bpi->net->lock drops to 0, bpi may be + * freed as well (because bpi->net was holding the + * last reference to bpi) => write after free! + */ + unsigned refcount; + + bpi = bgp_path_info_lock(bpi); + refcount = bgp_dest_get_lock_count(bpi->net) - 1; + bgp_dest_unlock_node((struct bgp_dest *)bpi->net); + if (!refcount) + bpi->net = NULL; + bgp_path_info_unlock(bpi); + } + bgp_path_info_unlock(e->vrfleak->parent); + e->vrfleak->parent = NULL; + } + + if (e->vrfleak && e->vrfleak->bgp_orig) + bgp_unlock(e->vrfleak->bgp_orig); + + if (e->vrfleak && e->vrfleak->peer_orig) + peer_unlock(e->vrfleak->peer_orig); + + if (e->aggr_suppressors) + list_delete(&e->aggr_suppressors); + + if (e->evpn && e->evpn->mh_info) + bgp_evpn_path_mh_info_free(e->evpn->mh_info); + + if ((*extra)->flowspec && (*extra)->flowspec->bgp_fs_iprule) + list_delete(&((*extra)->flowspec->bgp_fs_iprule)); + if ((*extra)->flowspec && (*extra)->flowspec->bgp_fs_pbr) + list_delete(&((*extra)->flowspec->bgp_fs_pbr)); + + if (e->evpn) + XFREE(MTYPE_BGP_ROUTE_EXTRA_EVPN, e->evpn); + if (e->flowspec) + XFREE(MTYPE_BGP_ROUTE_EXTRA_FS, e->flowspec); + if (e->vrfleak) + XFREE(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK, e->vrfleak); +#ifdef ENABLE_BGP_VNC + if (e->vnc) + XFREE(MTYPE_BGP_ROUTE_EXTRA_VNC, e->vnc); +#endif + + if (e->labels) + bgp_labels_unintern(&e->labels); + + XFREE(MTYPE_BGP_ROUTE_EXTRA, *extra); +} + +/* Get bgp_path_info extra information for the given bgp_path_info, lazy + * allocated if required. + */ +struct bgp_path_info_extra *bgp_path_info_extra_get(struct bgp_path_info *pi) +{ + if (!pi->extra) + pi->extra = bgp_path_info_extra_new(); + if (!pi->extra->evpn && pi->net && pi->net->rn->p.family == AF_EVPN) + pi->extra->evpn = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_EVPN, + sizeof(struct bgp_path_info_extra_evpn)); + return pi->extra; +} + +bool bgp_path_info_has_valid_label(const struct bgp_path_info *path) +{ + if (!BGP_PATH_INFO_NUM_LABELS(path)) + return false; + + return bgp_is_valid_label(&path->extra->labels->label[0]); +} + +bool bgp_path_info_labels_same(const struct bgp_path_info *bpi, + const mpls_label_t *label, uint32_t n) +{ + uint8_t bpi_num_labels; + const mpls_label_t *bpi_label; + + bpi_num_labels = BGP_PATH_INFO_NUM_LABELS(bpi); + bpi_label = bpi_num_labels ? bpi->extra->labels->label : NULL; + + return bgp_labels_same(bpi_label, bpi_num_labels, + (const mpls_label_t *)label, n); +} + +/* Free bgp route information. */ +void bgp_path_info_free_with_caller(const char *name, + struct bgp_path_info *path) +{ + frrtrace(2, frr_bgp, bgp_path_info_free, path, name); + bgp_attr_unintern(&path->attr); + + bgp_unlink_nexthop(path); + bgp_path_info_extra_free(&path->extra); + bgp_path_info_mpath_free(&path->mpath); + if (path->net) + bgp_addpath_free_info_data(&path->tx_addpath, + &path->net->tx_addpath); + + peer_unlock(path->peer); /* bgp_path_info peer reference */ + + XFREE(MTYPE_BGP_ROUTE, path); +} + +struct bgp_path_info *bgp_path_info_lock(struct bgp_path_info *path) +{ + path->lock++; + return path; +} + +struct bgp_path_info *bgp_path_info_unlock(struct bgp_path_info *path) +{ + assert(path && path->lock > 0); + path->lock--; + + if (path->lock == 0) { + bgp_path_info_free(path); + return NULL; + } + + return path; +} + +bool bgp_path_info_nexthop_changed(struct bgp_path_info *pi, struct peer *to, + afi_t afi) +{ + if (pi->peer->sort == BGP_PEER_IBGP && to->sort == BGP_PEER_IBGP && + !CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_FORCE_NEXTHOP_SELF)) + /* IBGP RR with no nexthop self force configured */ + return false; + + if (to->sort == BGP_PEER_IBGP && + !CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_NEXTHOP_SELF)) + /* IBGP RR with no nexthop self configured */ + return false; + + if (CHECK_FLAG(to->af_flags[afi][SAFI_MPLS_VPN], + PEER_FLAG_NEXTHOP_UNCHANGED)) + /* IBGP or EBGP with nexthop attribute unchanged */ + return false; + + return true; +} + +/* This function sets flag BGP_NODE_SELECT_DEFER based on condition */ +static int bgp_dest_set_defer_flag(struct bgp_dest *dest, bool delete) +{ + struct peer *peer; + struct bgp_path_info *old_pi, *nextpi; + bool set_flag = false; + struct bgp *bgp = NULL; + struct bgp_table *table = NULL; + afi_t afi = 0; + safi_t safi = 0; + + /* If the flag BGP_NODE_SELECT_DEFER is set and new path is added + * then the route selection is deferred + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER) && (!delete)) + return 0; + + if (CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED)) { + if (BGP_DEBUG(update, UPDATE_OUT)) { + table = bgp_dest_table(dest); + if (table) + bgp = table->bgp; + + zlog_debug( + "Route %pBD(%s) is in workqueue and being processed, not deferred.", + dest, bgp ? bgp->name_pretty : "(Unknown)"); + } + + return 0; + } + + table = bgp_dest_table(dest); + if (table) { + bgp = table->bgp; + afi = table->afi; + safi = table->safi; + } + + for (old_pi = bgp_dest_get_bgp_path_info(dest); + (old_pi != NULL) && (nextpi = old_pi->next, 1); old_pi = nextpi) { + if (CHECK_FLAG(old_pi->flags, BGP_PATH_SELECTED)) + continue; + + /* Route selection is deferred if there is a stale path which + * which indicates peer is in restart mode + */ + if (CHECK_FLAG(old_pi->flags, BGP_PATH_STALE) + && (old_pi->sub_type == BGP_ROUTE_NORMAL)) { + set_flag = true; + } else { + /* If the peer is graceful restart capable and peer is + * restarting mode, set the flag BGP_NODE_SELECT_DEFER + */ + peer = old_pi->peer; + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer) + && BGP_PEER_RESTARTING_MODE(peer) + && (old_pi + && old_pi->sub_type == BGP_ROUTE_NORMAL)) { + set_flag = true; + } + } + if (set_flag) + break; + } + + /* Set the flag BGP_NODE_SELECT_DEFER if route selection deferral timer + * is active + */ + if (set_flag && table) { + if (bgp && (bgp->gr_info[afi][safi].t_select_deferral)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) + bgp->gr_info[afi][safi].gr_deferred++; + SET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("DEFER route %pBD(%s), dest %p", + dest, bgp->name_pretty, dest); + return 0; + } + } + return -1; +} + +void bgp_path_info_add_with_caller(const char *name, struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + frrtrace(3, frr_bgp, bgp_path_info_add, dest, pi, name); + struct bgp_path_info *top; + + top = bgp_dest_get_bgp_path_info(dest); + + pi->next = top; + pi->prev = NULL; + if (top) + top->prev = pi; + bgp_dest_set_bgp_path_info(dest, pi); + + SET_FLAG(pi->flags, BGP_PATH_UNSORTED); + bgp_path_info_lock(pi); + bgp_dest_lock_node(dest); + peer_lock(pi->peer); /* bgp_path_info peer reference */ + bgp_dest_set_defer_flag(dest, false); + if (pi->peer) + pi->peer->stat_pfx_loc_rib++; + hook_call(bgp_snmp_update_stats, dest, pi, true); +} + +/* Do the actual removal of info from RIB, for use by bgp_process + completion callback *only* */ +struct bgp_dest *bgp_path_info_reap(struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + if (pi->next) + pi->next->prev = pi->prev; + if (pi->prev) + pi->prev->next = pi->next; + else + bgp_dest_set_bgp_path_info(dest, pi->next); + + bgp_path_info_mpath_dequeue(pi); + + pi->next = NULL; + pi->prev = NULL; + + if (pi->peer) + pi->peer->stat_pfx_loc_rib--; + hook_call(bgp_snmp_update_stats, dest, pi, false); + + bgp_path_info_unlock(pi); + return bgp_dest_unlock_node(dest); +} + +static struct bgp_dest *bgp_path_info_reap_unsorted(struct bgp_dest *dest, + struct bgp_path_info *pi) +{ + bgp_path_info_mpath_dequeue(pi); + + pi->next = NULL; + pi->prev = NULL; + + if (pi->peer) + pi->peer->stat_pfx_loc_rib--; + hook_call(bgp_snmp_update_stats, dest, pi, false); + bgp_path_info_unlock(pi); + + return bgp_dest_unlock_node(dest); +} + +void bgp_path_info_delete(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + bgp_path_info_set_flag(dest, pi, BGP_PATH_REMOVED); + /* set of previous already took care of pcount */ + UNSET_FLAG(pi->flags, BGP_PATH_VALID); +} + +/* undo the effects of a previous call to bgp_path_info_delete; typically + called when a route is deleted and then quickly re-added before the + deletion has been processed */ +void bgp_path_info_restore(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + bgp_path_info_unset_flag(dest, pi, BGP_PATH_REMOVED); + /* unset of previous already took care of pcount */ + SET_FLAG(pi->flags, BGP_PATH_VALID); +} + +/* Adjust pcount as required */ +static void bgp_pcount_adjust(struct bgp_dest *dest, struct bgp_path_info *pi) +{ + struct bgp_table *table; + + assert(dest && bgp_dest_table(dest)); + assert(pi && pi->peer && pi->peer->bgp); + + table = bgp_dest_table(dest); + + if (pi->peer == pi->peer->bgp->peer_self) + return; + + if (!BGP_PATH_COUNTABLE(pi) + && CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + + UNSET_FLAG(pi->flags, BGP_PATH_COUNTED); + + /* slight hack, but more robust against errors. */ + if (pi->peer->pcount[table->afi][table->safi]) + pi->peer->pcount[table->afi][table->safi]--; + else + flog_err(EC_LIB_DEVELOPMENT, + "Asked to decrement 0 prefix count for peer"); + } else if (BGP_PATH_COUNTABLE(pi) + && !CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + SET_FLAG(pi->flags, BGP_PATH_COUNTED); + pi->peer->pcount[table->afi][table->safi]++; + } +} + +static int bgp_label_index_differs(struct bgp_path_info *pi1, + struct bgp_path_info *pi2) +{ + return (!(pi1->attr->label_index == pi2->attr->label_index)); +} + +/* Set/unset bgp_path_info flags, adjusting any other state as needed. + * This is here primarily to keep prefix-count in check. + */ +void bgp_path_info_set_flag(struct bgp_dest *dest, struct bgp_path_info *pi, + uint32_t flag) +{ + SET_FLAG(pi->flags, flag); + + /* early bath if we know it's not a flag that changes countability state + */ + if (!CHECK_FLAG(flag, + BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) + return; + + bgp_pcount_adjust(dest, pi); +} + +void bgp_path_info_unset_flag(struct bgp_dest *dest, struct bgp_path_info *pi, + uint32_t flag) +{ + UNSET_FLAG(pi->flags, flag); + + /* early bath if we know it's not a flag that changes countability state + */ + if (!CHECK_FLAG(flag, + BGP_PATH_VALID | BGP_PATH_HISTORY | BGP_PATH_REMOVED)) + return; + + bgp_pcount_adjust(dest, pi); +} + +static bool use_bgp_med_value(struct attr *attr, struct bgp *bgp) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC) || + CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) + return true; + + return false; +} + +/* Get MED value. If MED value is missing and "bgp bestpath + missing-as-worst" is specified, treat it as the worst value. */ +static uint32_t bgp_med_value(struct attr *attr, struct bgp *bgp) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + return attr->med; + else { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) + return BGP_MED_MAX; + else + return 0; + } +} + +void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, char *buf, + size_t buf_len) +{ + struct peer *peer; + + if (!pi) { + snprintf(buf, buf_len, "NONE"); + return; + } + + if (pi->sub_type == BGP_ROUTE_IMPORTED && + bgp_get_imported_bpi_ultimate(pi)) + peer = bgp_get_imported_bpi_ultimate(pi)->peer; + else + peer = pi->peer; + + if (pi->addpath_rx_id) + snprintf(buf, buf_len, "path %s (addpath rxid %d)", peer->host, + pi->addpath_rx_id); + else + snprintf(buf, buf_len, "path %s", peer->host); +} + + +/* + * Get the ultimate path info. + */ +struct bgp_path_info *bgp_get_imported_bpi_ultimate(struct bgp_path_info *info) +{ + struct bgp_path_info *bpi_ultimate; + + if (info->sub_type != BGP_ROUTE_IMPORTED) + return info; + + for (bpi_ultimate = info; + bpi_ultimate->extra && bpi_ultimate->extra->vrfleak && + bpi_ultimate->extra->vrfleak->parent; + bpi_ultimate = bpi_ultimate->extra->vrfleak->parent) + ; + + return bpi_ultimate; +} + +/* Compare two bgp route entity. If 'new' is preferable over 'exist' return 1. + */ +int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + struct bgp_maxpaths_cfg *mpath_cfg, bool debug, + char *pfx_buf, afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason) +{ + const struct prefix *new_p; + struct attr *newattr, *existattr; + enum bgp_peer_sort new_sort; + enum bgp_peer_sort exist_sort; + enum bgp_peer_sub_sort new_sub_sort; + enum bgp_peer_sub_sort exist_sub_sort; + uint32_t new_pref; + uint32_t exist_pref; + uint32_t new_med; + uint32_t exist_med; + uint32_t new_weight; + uint32_t exist_weight; + uint32_t newm, existm; + struct in_addr new_id; + struct in_addr exist_id; + int new_cluster; + int exist_cluster; + int internal_as_route; + int confed_as_route; + int ret = 0; + int igp_metric_ret = 0; + int peer_sort_ret = -1; + char new_buf[PATH_ADDPATH_STR_BUFFER]; + char exist_buf[PATH_ADDPATH_STR_BUFFER]; + uint32_t new_mm_seq; + uint32_t exist_mm_seq; + int nh_cmp; + esi_t *exist_esi; + esi_t *new_esi; + bool same_esi; + bool old_proxy; + bool new_proxy; + bool new_origin, exist_origin; + struct bgp_path_info *bpi_ultimate; + struct peer *peer_new, *peer_exist; + + *paths_eq = 0; + + /* 0. Null check. */ + if (new == NULL) { + *reason = bgp_path_selection_none; + if (debug) + zlog_debug("%s: new is NULL", pfx_buf); + return 0; + } + + if (debug) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(new); + bgp_path_info_path_with_addpath_rx_str(bpi_ultimate, new_buf, + sizeof(new_buf)); + } + + if (exist == NULL) { + *reason = bgp_path_selection_first; + if (debug) + zlog_debug("%s(%s): %s is the initial bestpath", + pfx_buf, bgp->name_pretty, new_buf); + return 1; + } + + if (debug) { + char buf1[256], buf2[256]; + + bpi_ultimate = bgp_get_imported_bpi_ultimate(exist); + bgp_path_info_path_with_addpath_rx_str(bpi_ultimate, exist_buf, + sizeof(exist_buf)); + zlog_debug("%s(%s): Comparing %s flags %s with %s flags %s", + pfx_buf, bgp->name_pretty, new_buf, + bgp_route_dump_path_info_flags(new, buf1, + sizeof(buf1)), + exist_buf, + bgp_route_dump_path_info_flags(exist, buf2, + sizeof(buf2))); + } + + newattr = new->attr; + existattr = exist->attr; + + /* A BGP speaker that has advertised the "Long-lived Graceful Restart + * Capability" to a neighbor MUST perform the following upon receiving + * a route from that neighbor with the "LLGR_STALE" community, or upon + * attaching the "LLGR_STALE" community itself per Section 4.2: + * + * Treat the route as the least-preferred in route selection (see + * below). See the Risks of Depreferencing Routes section (Section 5.2) + * for a discussion of potential risks inherent in doing this. + */ + if (bgp_attr_get_community(newattr) && + community_include(bgp_attr_get_community(newattr), + COMMUNITY_LLGR_STALE)) { + if (debug) + zlog_debug( + "%s: %s wins over %s due to LLGR_STALE community", + pfx_buf, new_buf, exist_buf); + return 0; + } + + if (bgp_attr_get_community(existattr) && + community_include(bgp_attr_get_community(existattr), + COMMUNITY_LLGR_STALE)) { + if (debug) + zlog_debug( + "%s: %s loses to %s due to LLGR_STALE community", + pfx_buf, new_buf, exist_buf); + return 1; + } + + new_p = bgp_dest_get_prefix(new->net); + + /* For EVPN routes, we cannot just go by local vs remote, we have to + * look at the MAC mobility sequence number, if present. + */ + if ((safi == SAFI_EVPN) + && (new_p->u.prefix_evpn.route_type == BGP_EVPN_MAC_IP_ROUTE)) { + /* This is an error condition described in RFC 7432 Section + * 15.2. The RFC + * states that in this scenario "the PE MUST alert the operator" + * but it + * does not state what other action to take. In order to provide + * some + * consistency in this scenario we are going to prefer the path + * with the + * sticky flag. + */ + if (newattr->sticky != existattr->sticky) { + if (newattr->sticky && !existattr->sticky) { + *reason = bgp_path_selection_evpn_sticky_mac; + if (debug) + zlog_debug( + "%s: %s wins over %s due to sticky MAC flag", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!newattr->sticky && existattr->sticky) { + *reason = bgp_path_selection_evpn_sticky_mac; + if (debug) + zlog_debug( + "%s: %s loses to %s due to sticky MAC flag", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + new_esi = bgp_evpn_attr_get_esi(newattr); + exist_esi = bgp_evpn_attr_get_esi(existattr); + if (bgp_evpn_is_esi_valid(new_esi) && + !memcmp(new_esi, exist_esi, sizeof(esi_t))) { + same_esi = true; + } else { + same_esi = false; + } + + /* If both paths have the same non-zero ES and + * one path is local it wins. + * PS: Note the local path wins even if the remote + * has the higher MM seq. The local path's + * MM seq will be fixed up to match the highest + * rem seq, subsequently. + */ + if (same_esi) { + char esi_buf[ESI_STR_LEN]; + + if (bgp_evpn_is_path_local(bgp, new)) { + *reason = bgp_path_selection_evpn_local_path; + if (debug) + zlog_debug( + "%s: %s wins over %s as ES %s is same and local", + pfx_buf, new_buf, exist_buf, + esi_to_str(new_esi, esi_buf, + sizeof(esi_buf))); + return 1; + } + if (bgp_evpn_is_path_local(bgp, exist)) { + *reason = bgp_path_selection_evpn_local_path; + if (debug) + zlog_debug( + "%s: %s loses to %s as ES %s is same and local", + pfx_buf, new_buf, exist_buf, + esi_to_str(new_esi, esi_buf, + sizeof(esi_buf))); + return 0; + } + } + + new_mm_seq = mac_mobility_seqnum(newattr); + exist_mm_seq = mac_mobility_seqnum(existattr); + + if (new_mm_seq > exist_mm_seq) { + *reason = bgp_path_selection_evpn_seq; + if (debug) + zlog_debug( + "%s: %s wins over %s due to MM seq %u > %u", + pfx_buf, new_buf, exist_buf, new_mm_seq, + exist_mm_seq); + return 1; + } + + if (new_mm_seq < exist_mm_seq) { + *reason = bgp_path_selection_evpn_seq; + if (debug) + zlog_debug( + "%s: %s loses to %s due to MM seq %u < %u", + pfx_buf, new_buf, exist_buf, new_mm_seq, + exist_mm_seq); + return 0; + } + + /* if the sequence numbers and ESI are the same and one path + * is non-proxy it wins (over proxy) + */ + new_proxy = bgp_evpn_attr_is_proxy(newattr); + old_proxy = bgp_evpn_attr_is_proxy(existattr); + if (same_esi && bgp_evpn_attr_is_local_es(newattr) && + old_proxy != new_proxy) { + if (!new_proxy) { + *reason = bgp_path_selection_evpn_non_proxy; + if (debug) + zlog_debug( + "%s: %s wins over %s, same seq/es and non-proxy", + pfx_buf, new_buf, exist_buf); + return 1; + } + + *reason = bgp_path_selection_evpn_non_proxy; + if (debug) + zlog_debug( + "%s: %s loses to %s, same seq/es and non-proxy", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* + * if sequence numbers are the same path with the lowest IP + * wins + */ + nh_cmp = bgp_path_info_nexthop_cmp(new, exist); + if (nh_cmp < 0) { + *reason = bgp_path_selection_evpn_lower_ip; + if (debug) + zlog_debug( + "%s: %s wins over %s due to same MM seq %u and lower IP %pI4", + pfx_buf, new_buf, exist_buf, new_mm_seq, + &new->attr->nexthop); + return 1; + } + if (nh_cmp > 0) { + *reason = bgp_path_selection_evpn_lower_ip; + if (debug) + zlog_debug( + "%s: %s loses to %s due to same MM seq %u and higher IP %pI4", + pfx_buf, new_buf, exist_buf, new_mm_seq, + &new->attr->nexthop); + return 0; + } + } + + /* 1. Weight check. */ + new_weight = newattr->weight; + exist_weight = existattr->weight; + + if (new_weight > exist_weight) { + *reason = bgp_path_selection_weight; + if (debug) + zlog_debug("%s: %s wins over %s due to weight %d > %d", + pfx_buf, new_buf, exist_buf, new_weight, + exist_weight); + return 1; + } + + if (new_weight < exist_weight) { + *reason = bgp_path_selection_weight; + if (debug) + zlog_debug("%s: %s loses to %s due to weight %d < %d", + pfx_buf, new_buf, exist_buf, new_weight, + exist_weight); + return 0; + } + + /* 2. Local preference check. */ + new_pref = exist_pref = bgp->default_local_pref; + + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + new_pref = newattr->local_pref; + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + exist_pref = existattr->local_pref; + + if (new_pref > exist_pref) { + *reason = bgp_path_selection_local_pref; + if (debug) + zlog_debug( + "%s: %s wins over %s due to localpref %d > %d", + pfx_buf, new_buf, exist_buf, new_pref, + exist_pref); + return 1; + } + + if (new_pref < exist_pref) { + *reason = bgp_path_selection_local_pref; + if (debug) + zlog_debug( + "%s: %s loses to %s due to localpref %d < %d", + pfx_buf, new_buf, exist_buf, new_pref, + exist_pref); + return 0; + } + + /* If a BGP speaker supports ACCEPT_OWN and is configured for the + * extensions defined in this document, the following step is inserted + * after the LOCAL_PREF comparison step in the BGP decision process: + * When comparing a pair of routes for a BGP destination, the + * route with the ACCEPT_OWN community attached is preferred over + * the route that does not have the community. + * This extra step MUST only be invoked during the best path selection + * process of VPN-IP routes. + */ + if (safi == SAFI_MPLS_VPN && + (CHECK_FLAG(new->peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN) || + CHECK_FLAG(exist->peer->af_flags[afi][safi], + PEER_FLAG_ACCEPT_OWN))) { + bool new_accept_own = false; + bool exist_accept_own = false; + uint32_t accept_own = COMMUNITY_ACCEPT_OWN; + + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + new_accept_own = community_include( + bgp_attr_get_community(newattr), accept_own); + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + exist_accept_own = community_include( + bgp_attr_get_community(existattr), accept_own); + + if (new_accept_own && !exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s wins over %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!new_accept_own && exist_accept_own) { + *reason = bgp_path_selection_accept_own; + if (debug) + zlog_debug( + "%s: %s loses to %s due to accept-own", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + /* Tie-breaker - AIGP (Metric TLV) attribute */ + if (CHECK_FLAG(newattr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP)) && + CHECK_FLAG(existattr->flag, ATTR_FLAG_BIT(BGP_ATTR_AIGP)) && + CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_AIGP)) { + uint64_t new_aigp = bgp_attr_get_aigp_metric(newattr); + uint64_t exist_aigp = bgp_attr_get_aigp_metric(existattr); + + if (new_aigp < exist_aigp) { + *reason = bgp_path_selection_aigp; + if (debug) + zlog_debug( + "%s: %s wins over %s due to AIGP %" PRIu64 + " < %" PRIu64, + pfx_buf, new_buf, exist_buf, new_aigp, + exist_aigp); + return 1; + } + + if (new_aigp > exist_aigp) { + *reason = bgp_path_selection_aigp; + if (debug) + zlog_debug( + "%s: %s loses to %s due to AIGP %" PRIu64 + " > %" PRIu64, + pfx_buf, new_buf, exist_buf, new_aigp, + exist_aigp); + return 0; + } + } + + /* 3. Local route check. We prefer: + * - BGP_ROUTE_STATIC + * - BGP_ROUTE_AGGREGATE + * - BGP_ROUTE_REDISTRIBUTE + */ + new_origin = !(new->sub_type == BGP_ROUTE_NORMAL || + new->sub_type == BGP_ROUTE_IMPORTED); + exist_origin = !(exist->sub_type == BGP_ROUTE_NORMAL || + exist->sub_type == BGP_ROUTE_IMPORTED); + + if (new_origin && !exist_origin) { + *reason = bgp_path_selection_local_route; + if (debug) + zlog_debug( + "%s: %s wins over %s due to preferred BGP_ROUTE type", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (!new_origin && exist_origin) { + *reason = bgp_path_selection_local_route; + if (debug) + zlog_debug( + "%s: %s loses to %s due to preferred BGP_ROUTE type", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* Here if these are imported routes then get ultimate pi for + * path compare. + */ + new = bgp_get_imported_bpi_ultimate(new); + exist = bgp_get_imported_bpi_ultimate(exist); + newattr = new->attr; + existattr = exist->attr; + + /* 4. AS path length check. */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE)) { + int exist_hops = aspath_count_hops(existattr->aspath); + int exist_confeds = aspath_count_confeds(existattr->aspath); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED)) { + int aspath_hops; + + aspath_hops = aspath_count_hops(newattr->aspath); + aspath_hops += aspath_count_confeds(newattr->aspath); + + if (aspath_hops < (exist_hops + exist_confeds)) { + *reason = bgp_path_selection_confed_as_path; + if (debug) + zlog_debug( + "%s: %s wins over %s due to aspath (with confeds) hopcount %d < %d", + pfx_buf, new_buf, exist_buf, + aspath_hops, + (exist_hops + exist_confeds)); + return 1; + } + + if (aspath_hops > (exist_hops + exist_confeds)) { + *reason = bgp_path_selection_confed_as_path; + if (debug) + zlog_debug( + "%s: %s loses to %s due to aspath (with confeds) hopcount %d > %d", + pfx_buf, new_buf, exist_buf, + aspath_hops, + (exist_hops + exist_confeds)); + return 0; + } + } else { + int newhops = aspath_count_hops(newattr->aspath); + + if (newhops < exist_hops) { + *reason = bgp_path_selection_as_path; + if (debug) + zlog_debug( + "%s: %s wins over %s due to aspath hopcount %d < %d", + pfx_buf, new_buf, exist_buf, + newhops, exist_hops); + return 1; + } + + if (newhops > exist_hops) { + *reason = bgp_path_selection_as_path; + if (debug) + zlog_debug( + "%s: %s loses to %s due to aspath hopcount %d > %d", + pfx_buf, new_buf, exist_buf, + newhops, exist_hops); + return 0; + } + } + } + + /* 5. Origin check. */ + if (newattr->origin < existattr->origin) { + *reason = bgp_path_selection_origin; + if (debug) + zlog_debug("%s: %s wins over %s due to ORIGIN %s < %s", + pfx_buf, new_buf, exist_buf, + bgp_origin_long_str[newattr->origin], + bgp_origin_long_str[existattr->origin]); + return 1; + } + + if (newattr->origin > existattr->origin) { + *reason = bgp_path_selection_origin; + if (debug) + zlog_debug("%s: %s loses to %s due to ORIGIN %s > %s", + pfx_buf, new_buf, exist_buf, + bgp_origin_long_str[newattr->origin], + bgp_origin_long_str[existattr->origin]); + return 0; + } + + /* 6. MED check. */ + internal_as_route = (aspath_count_hops(newattr->aspath) == 0 + && aspath_count_hops(existattr->aspath) == 0); + confed_as_route = (aspath_count_confeds(newattr->aspath) > 0 + && aspath_count_confeds(existattr->aspath) > 0 + && aspath_count_hops(newattr->aspath) == 0 + && aspath_count_hops(existattr->aspath) == 0); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ALWAYS_COMPARE_MED) + || (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED) && confed_as_route) + || aspath_cmp_left(newattr->aspath, existattr->aspath) + || aspath_cmp_left_confed(newattr->aspath, existattr->aspath) + || internal_as_route) { + new_med = bgp_med_value(new->attr, bgp); + exist_med = bgp_med_value(exist->attr, bgp); + + if (new_med < exist_med) { + *reason = bgp_path_selection_med; + if (debug) + zlog_debug( + "%s: %s wins over %s due to MED %d < %d", + pfx_buf, new_buf, exist_buf, new_med, + exist_med); + return 1; + } + + if (new_med > exist_med) { + *reason = bgp_path_selection_med; + if (debug) + zlog_debug( + "%s: %s loses to %s due to MED %d > %d", + pfx_buf, new_buf, exist_buf, new_med, + exist_med); + return 0; + } + } + + if (exist->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(exist); + peer_exist = bpi_ultimate->peer; + } else + peer_exist = exist->peer; + + if (new->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(new); + peer_new = bpi_ultimate->peer; + } else + peer_new = new->peer; + + /* 7. Peer type check. */ + new_sort = peer_new->sort; + exist_sort = peer_exist->sort; + new_sub_sort = peer_new->sub_sort; + exist_sub_sort = peer_exist->sub_sort; + + if (new_sort == BGP_PEER_EBGP && + (exist_sort == BGP_PEER_IBGP || exist_sort == BGP_PEER_CONFED || + exist_sub_sort == BGP_PEER_EBGP_OAD)) { + *reason = bgp_path_selection_peer; + if (debug) + zlog_debug("%s: %s wins over %s due to eBGP peer > %s peer", + pfx_buf, new_buf, exist_buf, + (exist_sub_sort == BGP_PEER_EBGP_OAD) + ? "eBGP-OAD" + : "iBGP"); + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 1; + peer_sort_ret = 1; + } + + if (exist_sort == BGP_PEER_EBGP && + (new_sort == BGP_PEER_IBGP || new_sort == BGP_PEER_CONFED || + new_sub_sort == BGP_PEER_EBGP_OAD)) { + *reason = bgp_path_selection_peer; + if (debug) + zlog_debug("%s: %s loses to %s due to %s peer < eBGP peer", + pfx_buf, new_buf, exist_buf, + (exist_sub_sort == BGP_PEER_EBGP_OAD) + ? "eBGP-OAD" + : "iBGP"); + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 0; + peer_sort_ret = 0; + } + + /* 8. IGP metric check. */ + newm = existm = 0; + + if (new->extra) + newm = new->extra->igpmetric; + if (exist->extra) + existm = exist->extra->igpmetric; + + if (newm < existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s wins over %s due to IGP metric %u < %u", + pfx_buf, new_buf, exist_buf, newm, existm); + igp_metric_ret = 1; + } + + if (newm > existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s loses to %s due to IGP metric %u > %u", + pfx_buf, new_buf, exist_buf, newm, existm); + igp_metric_ret = 0; + } + + /* 9. Same IGP metric. Compare the cluster list length as + representative of IGP hops metric. Rewrite the metric value + pair (newm, existm) with the cluster list length. Prefer the + path with smaller cluster list length. */ + if (newm == existm) { + if (peer_sort_lookup(peer_new) == BGP_PEER_IBGP && + peer_sort_lookup(peer_exist) == BGP_PEER_IBGP && + (mpath_cfg == NULL || mpath_cfg->same_clusterlen)) { + newm = BGP_CLUSTER_LIST_LENGTH(new->attr); + existm = BGP_CLUSTER_LIST_LENGTH(exist->attr); + + if (newm < existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s wins over %s due to CLUSTER_LIST length %u < %u", + pfx_buf, new_buf, exist_buf, + newm, existm); + igp_metric_ret = 1; + } + + if (newm > existm) { + if (debug && peer_sort_ret < 0) + zlog_debug( + "%s: %s loses to %s due to CLUSTER_LIST length %u > %u", + pfx_buf, new_buf, exist_buf, + newm, existm); + igp_metric_ret = 0; + } + } + } + + /* 10. confed-external vs. confed-internal */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (new_sort == BGP_PEER_CONFED + && exist_sort == BGP_PEER_IBGP) { + *reason = bgp_path_selection_confed; + if (debug) + zlog_debug( + "%s: %s wins over %s due to confed-external peer > confed-internal peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 1; + peer_sort_ret = 1; + } + + if (exist_sort == BGP_PEER_CONFED + && new_sort == BGP_PEER_IBGP) { + *reason = bgp_path_selection_confed; + if (debug) + zlog_debug( + "%s: %s loses to %s due to confed-internal peer < confed-external peer", + pfx_buf, new_buf, exist_buf); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + return 0; + peer_sort_ret = 0; + } + } + + /* 11. Maximum path check. */ + if (newm == existm) { + /* If one path has a label but the other does not, do not treat + * them as equals for multipath + */ + bool new_label_valid, exist_label_valid; + + new_label_valid = bgp_path_info_has_valid_label(new); + exist_label_valid = bgp_path_info_has_valid_label(exist); + + if (new_label_valid != exist_label_valid) { + if (debug) + zlog_debug( + "%s: %s and %s cannot be multipath, one has a label while the other does not", + pfx_buf, new_buf, exist_buf); + } else if (CHECK_FLAG(bgp->flags, + BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { + /* + * For the two paths, all comparison steps till IGP + * metric + * have succeeded - including AS_PATH hop count. Since + * 'bgp + * bestpath as-path multipath-relax' knob is on, we + * don't need + * an exact match of AS_PATH. Thus, mark the paths are + * equal. + * That will trigger both these paths to get into the + * multipath + * array. + */ + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via multipath-relax", + pfx_buf, new_buf, exist_buf); + } else if (peer_new->sort == BGP_PEER_IBGP) { + if (aspath_cmp(new->attr->aspath, + exist->attr->aspath)) { + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via matching aspaths", + pfx_buf, new_buf, exist_buf); + } + } else if (peer_new->as == peer_exist->as) { + *paths_eq = 1; + + if (debug) + zlog_debug( + "%s: %s and %s are equal via same remote-as", + pfx_buf, new_buf, exist_buf); + } + } else { + /* + * TODO: If unequal cost ibgp multipath is enabled we can + * mark the paths as equal here instead of returning + */ + + /* Prior to the addition of BGP_FLAG_PEERTYPE_MULTIPATH_RELAX, + * if either step 7 or 10 (peer type checks) yielded a winner, + * that result was returned immediately. Returning from step 10 + * ignored the return value computed in steps 8 and 9 (IGP + * metric checks). In order to preserve that behavior, if + * peer_sort_ret is set, return that rather than igp_metric_ret. + */ + ret = peer_sort_ret; + if (peer_sort_ret < 0) { + ret = igp_metric_ret; + if (debug) { + if (ret == 1) + zlog_debug( + "%s: %s wins over %s after IGP metric comparison", + pfx_buf, new_buf, exist_buf); + else + zlog_debug( + "%s: %s loses to %s after IGP metric comparison", + pfx_buf, new_buf, exist_buf); + } + *reason = bgp_path_selection_igp_metric; + } + return ret; + } + + /* + * At this point, the decision whether to set *paths_eq = 1 has been + * completed. If we deferred returning because of bestpath peer-type + * relax configuration, return now. + */ + if (peer_sort_ret >= 0) + return peer_sort_ret; + + /* 12. If both paths are external, prefer the path that was received + first (the oldest one). This step minimizes route-flap, since a + newer path won't displace an older one, even if it was the + preferred route based on the additional decision criteria below. */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID) + && new_sort == BGP_PEER_EBGP && exist_sort == BGP_PEER_EBGP) { + if (CHECK_FLAG(new->flags, BGP_PATH_SELECTED)) { + *reason = bgp_path_selection_older; + if (debug) + zlog_debug( + "%s: %s wins over %s due to oldest external", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (CHECK_FLAG(exist->flags, BGP_PATH_SELECTED)) { + *reason = bgp_path_selection_older; + if (debug) + zlog_debug( + "%s: %s loses to %s due to oldest external", + pfx_buf, new_buf, exist_buf); + return 0; + } + } + + /* 13. Router-ID comparison. */ + /* If one of the paths is "stale", the corresponding peer router-id will + * be 0 and would always win over the other path. If originator id is + * used for the comparison, it will decide which path is better. + */ + if (newattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + new_id.s_addr = newattr->originator_id.s_addr; + else + new_id.s_addr = peer_new->remote_id.s_addr; + if (existattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + exist_id.s_addr = existattr->originator_id.s_addr; + else + exist_id.s_addr = peer_exist->remote_id.s_addr; + + if (ntohl(new_id.s_addr) < ntohl(exist_id.s_addr)) { + *reason = bgp_path_selection_router_id; + if (debug) + zlog_debug( + "%s: %s wins over %s due to Router-ID comparison", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (ntohl(new_id.s_addr) > ntohl(exist_id.s_addr)) { + *reason = bgp_path_selection_router_id; + if (debug) + zlog_debug( + "%s: %s loses to %s due to Router-ID comparison", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* 14. Cluster length comparison. */ + new_cluster = BGP_CLUSTER_LIST_LENGTH(new->attr); + exist_cluster = BGP_CLUSTER_LIST_LENGTH(exist->attr); + + if (new_cluster < exist_cluster) { + *reason = bgp_path_selection_cluster_length; + if (debug) + zlog_debug( + "%s: %s wins over %s due to CLUSTER_LIST length %d < %d", + pfx_buf, new_buf, exist_buf, new_cluster, + exist_cluster); + return 1; + } + + if (new_cluster > exist_cluster) { + *reason = bgp_path_selection_cluster_length; + if (debug) + zlog_debug( + "%s: %s loses to %s due to CLUSTER_LIST length %d > %d", + pfx_buf, new_buf, exist_buf, new_cluster, + exist_cluster); + return 0; + } + + /* 15. Neighbor address comparison. */ + /* Do this only if neither path is "stale" as stale paths do not have + * valid peer information (as the connection may or may not be up). + */ + if (CHECK_FLAG(exist->flags, BGP_PATH_STALE)) { + *reason = bgp_path_selection_stale; + if (debug) + zlog_debug( + "%s: %s wins over %s due to latter path being STALE", + pfx_buf, new_buf, exist_buf); + return 1; + } + + if (CHECK_FLAG(new->flags, BGP_PATH_STALE)) { + *reason = bgp_path_selection_stale; + if (debug) + zlog_debug( + "%s: %s loses to %s due to former path being STALE", + pfx_buf, new_buf, exist_buf); + return 0; + } + + /* locally configured routes to advertise do not have su_remote */ + if (peer_new->su_remote == NULL) { + *reason = bgp_path_selection_local_configured; + return 0; + } + + if (peer_exist->su_remote == NULL) { + *reason = bgp_path_selection_local_configured; + return 1; + } + + ret = sockunion_cmp(peer_new->su_remote, peer_exist->su_remote); + + if (ret == 1) { + *reason = bgp_path_selection_neighbor_ip; + if (debug) + zlog_debug( + "%s: %s loses to %s due to Neighor IP comparison", + pfx_buf, new_buf, exist_buf); + return 0; + } + + if (ret == -1) { + *reason = bgp_path_selection_neighbor_ip; + if (debug) + zlog_debug( + "%s: %s wins over %s due to Neighor IP comparison", + pfx_buf, new_buf, exist_buf); + return 1; + } + + *reason = bgp_path_selection_default; + if (debug) + zlog_debug("%s: %s wins over %s due to nothing left to compare", + pfx_buf, new_buf, exist_buf); + + return 1; +} + + +int bgp_evpn_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + bool debug) +{ + enum bgp_path_selection_reason reason; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + + if (debug) + prefix2str(bgp_dest_get_prefix(new->net), pfx_buf, + sizeof(pfx_buf)); + + return bgp_path_info_cmp(bgp, new, exist, paths_eq, NULL, debug, + pfx_buf, AFI_L2VPN, SAFI_EVPN, &reason); +} + +/* Compare two bgp route entity. Return -1 if new is preferred, 1 if exist + * is preferred, or 0 if they are the same (usually will only occur if + * multipath is enabled + * This version is compatible with */ +int bgp_path_info_cmp_compatible(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, char *pfx_buf, + afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason) +{ + int paths_eq; + int ret; + bool debug = false; + + ret = bgp_path_info_cmp(bgp, new, exist, &paths_eq, NULL, debug, + pfx_buf, afi, safi, reason); + + if (paths_eq) + ret = 0; + else { + if (ret == 1) + ret = -1; + else + ret = 1; + } + return ret; +} + +static enum filter_type bgp_input_filter(struct peer *peer, + const struct prefix *p, + struct attr *attr, afi_t afi, + safi_t safi) +{ + struct bgp_filter *filter; + enum filter_type ret = FILTER_PERMIT; + + filter = &peer->filter[afi][safi]; + +#define FILTER_EXIST_WARN(F, f, filter) \ + if (BGP_DEBUG(update, UPDATE_IN) && !(F##_IN(filter))) \ + zlog_debug("%s: Could not find configured input %s-list %s!", \ + peer->host, #f, F##_IN_NAME(filter)); + + if (DISTRIBUTE_IN_NAME(filter)) { + FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); + + if (access_list_apply(DISTRIBUTE_IN(filter), p) + == FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (PREFIX_LIST_IN_NAME(filter)) { + FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); + + if (prefix_list_apply(PREFIX_LIST_IN(filter), p) + == PREFIX_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (FILTER_LIST_IN_NAME(filter)) { + FILTER_EXIST_WARN(FILTER_LIST, as, filter); + + if (as_list_apply(FILTER_LIST_IN(filter), attr->aspath) + == AS_FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + +done: + if (frrtrace_enabled(frr_bgp, input_filter)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(5, frr_bgp, input_filter, peer, pfxprint, afi, safi, + ret == FILTER_PERMIT ? "permit" : "deny"); + } + + return ret; +#undef FILTER_EXIST_WARN +} + +static enum filter_type bgp_output_filter(struct peer *peer, + const struct prefix *p, + struct attr *attr, afi_t afi, + safi_t safi) +{ + struct bgp_filter *filter; + enum filter_type ret = FILTER_PERMIT; + + filter = &peer->filter[afi][safi]; + +#define FILTER_EXIST_WARN(F, f, filter) \ + if (BGP_DEBUG(update, UPDATE_OUT) && !(F##_OUT(filter))) \ + zlog_debug("%s: Could not find configured output %s-list %s!", \ + peer->host, #f, F##_OUT_NAME(filter)); + + if (DISTRIBUTE_OUT_NAME(filter)) { + FILTER_EXIST_WARN(DISTRIBUTE, distribute, filter); + + if (access_list_apply(DISTRIBUTE_OUT(filter), p) + == FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (PREFIX_LIST_OUT_NAME(filter)) { + FILTER_EXIST_WARN(PREFIX_LIST, prefix, filter); + + if (prefix_list_apply(PREFIX_LIST_OUT(filter), p) + == PREFIX_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (FILTER_LIST_OUT_NAME(filter)) { + FILTER_EXIST_WARN(FILTER_LIST, as, filter); + + if (as_list_apply(FILTER_LIST_OUT(filter), attr->aspath) + == AS_FILTER_DENY) { + ret = FILTER_DENY; + goto done; + } + } + + if (frrtrace_enabled(frr_bgp, output_filter)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(5, frr_bgp, output_filter, peer, pfxprint, afi, safi, + ret == FILTER_PERMIT ? "permit" : "deny"); + } + +done: + return ret; +#undef FILTER_EXIST_WARN +} + +/* If community attribute includes no_export then return 1. */ +static bool bgp_community_filter(struct peer *peer, struct attr *attr) +{ + if (bgp_attr_get_community(attr)) { + /* NO_ADVERTISE check. */ + if (community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_ADVERTISE)) + return true; + + /* NO_EXPORT check. */ + if (peer->sort == BGP_PEER_EBGP && + community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_EXPORT)) + return true; + + /* NO_EXPORT_SUBCONFED check. */ + if (peer->sort == BGP_PEER_EBGP + || peer->sort == BGP_PEER_CONFED) + if (community_include(bgp_attr_get_community(attr), + COMMUNITY_NO_EXPORT_SUBCONFED)) + return true; + } + return false; +} + +/* Route reflection loop check. */ +static bool bgp_cluster_filter(struct peer *peer, struct attr *attr) +{ + struct in_addr cluster_id; + struct cluster_list *cluster = bgp_attr_get_cluster(attr); + + if (cluster) { + if (peer->bgp->config & BGP_CONFIG_CLUSTER_ID) + cluster_id = peer->bgp->cluster_id; + else + cluster_id = peer->bgp->router_id; + + if (cluster_loop_check(cluster, cluster_id)) + return true; + } + return false; +} + +static bool bgp_otc_filter(struct peer *peer, struct attr *attr) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (peer->local_role == ROLE_PROVIDER || + peer->local_role == ROLE_RS_SERVER) + return true; + if (peer->local_role == ROLE_PEER && attr->otc != peer->as) + return true; + return false; + } + if (peer->local_role == ROLE_CUSTOMER || + peer->local_role == ROLE_PEER || + peer->local_role == ROLE_RS_CLIENT) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC); + attr->otc = peer->as; + } + return false; +} + +static bool bgp_otc_egress(struct peer *peer, struct attr *attr) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (peer->local_role == ROLE_CUSTOMER || + peer->local_role == ROLE_RS_CLIENT || + peer->local_role == ROLE_PEER) + return true; + return false; + } + if (peer->local_role == ROLE_PROVIDER || + peer->local_role == ROLE_PEER || + peer->local_role == ROLE_RS_SERVER) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_OTC); + attr->otc = peer->bgp->as; + } + return false; +} + +static bool bgp_check_role_applicability(afi_t afi, safi_t safi) +{ + return ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST); +} + +static int bgp_input_modifier(struct peer *peer, const struct prefix *p, + struct attr *attr, afi_t afi, safi_t safi, + const char *rmap_name, mpls_label_t *label, + uint8_t num_labels, struct bgp_dest *dest) +{ + struct bgp_filter *filter; + struct bgp_path_info rmap_path = { 0 }; + struct bgp_path_info_extra extra = { 0 }; + struct bgp_labels bgp_labels = {}; + route_map_result_t ret; + struct route_map *rmap = NULL; + + filter = &peer->filter[afi][safi]; + + /* Apply default weight value. */ + if (peer->weight[afi][safi]) + attr->weight = peer->weight[afi][safi]; + + if (rmap_name) { + rmap = route_map_lookup_by_name(rmap_name); + + if (rmap == NULL) + return RMAP_DENY; + } else { + if (ROUTE_MAP_IN_NAME(filter)) { + rmap = ROUTE_MAP_IN(filter); + + if (rmap == NULL) + return RMAP_DENY; + } + } + + /* Route map apply. */ + if (rmap) { + memset(&rmap_path, 0, sizeof(rmap_path)); + /* Duplicate current value to new structure for modification. */ + rmap_path.peer = peer; + rmap_path.attr = attr; + rmap_path.extra = &extra; + rmap_path.net = dest; + extra.labels = &bgp_labels; + + bgp_labels.num_labels = num_labels; + if (label && num_labels && num_labels <= BGP_MAX_LABELS) + memcpy(bgp_labels.label, label, + num_labels * sizeof(mpls_label_t)); + + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN); + + /* Apply BGP route map to the attribute. */ + ret = route_map_apply(rmap, p, &rmap_path); + + peer->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) + return RMAP_DENY; + } + return RMAP_PERMIT; +} + +static int bgp_output_modifier(struct peer *peer, const struct prefix *p, + struct attr *attr, afi_t afi, safi_t safi, + const char *rmap_name) +{ + struct bgp_path_info rmap_path; + route_map_result_t ret; + struct route_map *rmap = NULL; + uint8_t rmap_type; + + /* + * So if we get to this point and have no rmap_name + * we want to just show the output as it currently + * exists. + */ + if (!rmap_name) + return RMAP_PERMIT; + + /* Apply default weight value. */ + if (peer->weight[afi][safi]) + attr->weight = peer->weight[afi][safi]; + + rmap = route_map_lookup_by_name(rmap_name); + + /* + * If we have a route map name and we do not find + * the routemap that means we have an implicit + * deny. + */ + if (rmap == NULL) + return RMAP_DENY; + + memset(&rmap_path, 0, sizeof(rmap_path)); + /* Route map apply. */ + /* Duplicate current value to new structure for modification. */ + rmap_path.peer = peer; + rmap_path.attr = attr; + + rmap_type = peer->rmap_type; + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); + + /* Apply BGP route map to the attribute. */ + ret = route_map_apply(rmap, p, &rmap_path); + + peer->rmap_type = rmap_type; + + if (ret == RMAP_DENYMATCH) + /* + * caller has multiple error paths with bgp_attr_flush() + */ + return RMAP_DENY; + + return RMAP_PERMIT; +} + +/* If this is an EBGP peer with remove-private-AS */ +static void bgp_peer_remove_private_as(struct bgp *bgp, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr) +{ + if (peer->sort == BGP_PEER_EBGP + && (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS))) { + // Take action on the entire aspath + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE) + || peer_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) { + if (peer_af_flag_check( + peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) + attr->aspath = aspath_replace_private_asns( + attr->aspath, bgp->as, peer->as); + + /* + * Even if the aspath consists of just private ASNs we + * need to walk the AS-Path to maintain all instances + * of the peer's ASN to break possible loops. + */ + else + attr->aspath = aspath_remove_private_asns( + attr->aspath, peer->as); + } + + // 'all' was not specified so the entire aspath must be private + // ASNs + // for us to do anything + else if (aspath_private_as_check(attr->aspath)) { + if (peer_af_flag_check( + peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) + attr->aspath = aspath_replace_private_asns( + attr->aspath, bgp->as, peer->as); + else + /* + * Walk the aspath to retain any instances of + * the peer_asn + */ + attr->aspath = aspath_remove_private_asns( + attr->aspath, peer->as); + } + } +} + +/* If this is an EBGP peer with as-override */ +static void bgp_peer_as_override(struct bgp *bgp, afi_t afi, safi_t safi, + struct peer *peer, struct attr *attr) +{ + struct aspath *aspath; + + if (peer->sort == BGP_PEER_EBGP && + peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_OVERRIDE)) { + if (attr->aspath->refcnt) + aspath = aspath_dup(attr->aspath); + else + aspath = attr->aspath; + + attr->aspath = aspath_intern( + aspath_replace_specific_asn(aspath, peer->as, bgp->as)); + + aspath_free(aspath); + } +} + +void bgp_attr_add_llgr_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *llgr; + + old = bgp_attr_get_community(attr); + llgr = community_str2com("llgr-stale"); + + assert(llgr); + + if (old) { + merge = community_merge(community_dup(old), llgr); + + if (old->refcnt == 0) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(llgr); + } + + community_free(&llgr); + + bgp_attr_set_community(attr, new); +} + +void bgp_attr_add_gshut_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *gshut; + + old = bgp_attr_get_community(attr); + gshut = community_str2com("graceful-shutdown"); + + assert(gshut); + + if (old) { + merge = community_merge(community_dup(old), gshut); + + if (old->refcnt == 0) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(gshut); + } + + community_free(&gshut); + bgp_attr_set_community(attr, new); + + /* When we add the graceful-shutdown community we must also + * lower the local-preference */ + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = BGP_GSHUT_LOCAL_PREF; +} + + +/* Notify BGP Conditional advertisement scanner process. */ +void bgp_notify_conditional_adv_scanner(struct update_subgroup *subgrp) +{ + struct peer *peer = SUBGRP_PEER(subgrp); + afi_t afi = SUBGRP_AFI(subgrp); + safi_t safi = SUBGRP_SAFI(subgrp); + struct bgp_filter *filter = &peer->filter[afi][safi]; + + if (!ADVERTISE_MAP_NAME(filter)) + return; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + return; + + peer->advmap_table_change = true; +} + + +void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr) +{ + if (family == AF_INET) { + attr->nexthop.s_addr = INADDR_ANY; + attr->mp_nexthop_global_in.s_addr = INADDR_ANY; + } + if (family == AF_INET6) + memset(&attr->mp_nexthop_global, 0, IPV6_MAX_BYTELEN); + if (family == AF_EVPN) + memset(&attr->mp_nexthop_global_in, 0, BGP_ATTR_NHLEN_IPV4); +} + +bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, + struct update_subgroup *subgrp, + const struct prefix *p, struct attr *attr, + struct attr *post_attr) +{ + struct bgp_filter *filter; + struct peer *from; + struct peer *peer; + struct peer *onlypeer; + struct bgp *bgp; + struct attr *piattr; + route_map_result_t ret; + int transparent; + int reflect; + afi_t afi; + safi_t safi; + int samepeer_safe = 0; /* for synthetic mplsvpns routes */ + bool nh_reset = false; + uint64_t cum_bw; + mpls_label_t label; + + if (DISABLE_BGP_ANNOUNCE) + return false; + + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + peer = SUBGRP_PEER(subgrp); + onlypeer = NULL; + if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL)) + onlypeer = SUBGRP_PFIRST(subgrp)->peer; + + from = pi->peer; + filter = &peer->filter[afi][safi]; + bgp = SUBGRP_INST(subgrp); + piattr = bgp_path_info_mpath_count(pi) ? bgp_path_info_mpath_attr(pi) + : pi->attr; + + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT) && + peer->pmax_out[afi][safi] != 0 && + subgrp->pscount >= peer->pmax_out[afi][safi]) { + if (BGP_DEBUG(update, UPDATE_OUT) || + BGP_DEBUG(update, UPDATE_PREFIX)) { + zlog_debug("%s reached maximum prefix to be send (%u)", + peer->host, peer->pmax_out[afi][safi]); + } + return false; + } + +#ifdef ENABLE_BGP_VNC + if (((afi == AFI_IP) || (afi == AFI_IP6)) && (safi == SAFI_MPLS_VPN) + && ((pi->type == ZEBRA_ROUTE_BGP_DIRECT) + || (pi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT))) { + + /* + * direct and direct_ext type routes originate internally even + * though they can have peer pointers that reference other + * systems + */ + zlog_debug("%s: pfx %pFX bgp_direct->vpn route peer safe", + __func__, p); + samepeer_safe = 1; + } +#endif + + if (((afi == AFI_IP) || (afi == AFI_IP6)) + && ((safi == SAFI_MPLS_VPN) || (safi == SAFI_UNICAST)) + && (pi->type == ZEBRA_ROUTE_BGP) + && (pi->sub_type == BGP_ROUTE_IMPORTED)) { + + /* Applies to routes leaked vpn->vrf and vrf->vpn */ + + samepeer_safe = 1; + } + + /* With addpath we may be asked to TX all kinds of paths so make sure + * pi is valid */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID) + || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY) + || CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + return false; + } + + /* If this is not the bestpath then check to see if there is an enabled + * addpath + * feature that requires us to advertise it */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + if (!bgp_addpath_capable(pi, peer, afi, safi)) + return false; + + /* Aggregate-address suppress check. */ + if (bgp_path_suppressed(pi) && !UNSUPPRESS_MAP_NAME(filter)) + return false; + + /* + * If we are doing VRF 2 VRF leaking via the import + * statement, we want to prevent the route going + * off box as that the RT and RD created are localy + * significant and globaly useless. + */ + if (safi == SAFI_MPLS_VPN && BGP_PATH_INFO_NUM_LABELS(pi) && + pi->extra->labels->label[0] == BGP_PREVENT_VRF_2_VRF_LEAK) + return false; + + /* If it's labeled safi, make sure the route has a valid label. */ + if (safi == SAFI_LABELED_UNICAST) { + label = bgp_adv_label(dest, pi, peer, afi, safi); + if (!bgp_is_valid_label(&label)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %pFX is filtered - no label (%p)", + subgrp->update_group->id, subgrp->id, + p, &label); + return false; + } + } else if (safi == SAFI_MPLS_VPN && + CHECK_FLAG(pi->flags, BGP_PATH_MPLSVPN_NH_LABEL_BIND) && + pi->mplsvpn.bmnc.nh_label_bind_cache && peer && + pi->peer != peer && pi->sub_type != BGP_ROUTE_IMPORTED && + pi->sub_type != BGP_ROUTE_STATIC && + bgp_mplsvpn_path_uses_valid_mpls_label(pi) && + bgp_path_info_nexthop_changed(pi, peer, afi)) { + /* Redistributed mpls vpn route between distinct + * peers from 'pi->peer' to 'to', + * and an mpls label is used in this path, + * and there is a nh label bind entry, + * then get appropriate mpls local label + * and check its validity + */ + label = bgp_mplsvpn_nh_label_bind_get_label(pi); + if (!bgp_is_valid_label(&label)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %pFX is filtered - no valid label", + subgrp->update_group->id, subgrp->id, + p); + return false; + } + } + + /* Do not send back route to sender. */ + if (onlypeer && from == onlypeer) { + return false; + } + + /* Do not send the default route in the BGP table if the neighbor is + * configured for default-originate */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) { + if ((p->family == AF_INET || p->family == AF_INET6) && p->prefixlen == 0) + return false; + } + + /* Transparency check. */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT) + && CHECK_FLAG(from->af_flags[afi][safi], PEER_FLAG_RSERVER_CLIENT)) + transparent = 1; + else + transparent = 0; + + /* If community is not disabled check the no-export and local. */ + if (!transparent && bgp_community_filter(peer, piattr)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("%s: community filter check fail for %pFX", + __func__, p); + return false; + } + + /* If the attribute has originator-id and it is same as remote + peer's id. */ + if (onlypeer && piattr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) + && (IPV4_ADDR_SAME(&onlypeer->remote_id, &piattr->originator_id))) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX originator-id is same as remote router-id", + onlypeer, p); + return false; + } + + /* ORF prefix-list filter check */ + if (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_ADV) && + CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV)) + if (peer->orf_plist[afi][safi]) { + if (prefix_list_apply(peer->orf_plist[afi][safi], p) + == PREFIX_DENY) { + if (bgp_debug_update(NULL, p, + subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered via ORF", + peer, p); + return false; + } + } + + /* Output filter check. */ + if (bgp_output_filter(peer, p, piattr, afi, safi) == FILTER_DENY) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug("%pBP [Update:SEND] %pFX is filtered", peer, + p); + return false; + } + + /* AS path loop check. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_AS_LOOP_DETECTION) && + aspath_loop_check(piattr->aspath, peer->as)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] suppress announcement to peer AS %u that is part of AS path.", + peer, peer->as); + return false; + } + + /* If we're a CONFED we need to loop check the CONFED ID too */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (aspath_loop_check_confed(piattr->aspath, bgp->confed_id)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] suppress announcement to peer AS %u is AS path.", + peer, bgp->confed_id); + return false; + } + } + + /* Route-Reflect check. */ + if (from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) + reflect = 1; + else + reflect = 0; + + /* IBGP reflection check. */ + if (reflect && !samepeer_safe) { + /* A route from a Client peer. */ + if (CHECK_FLAG(from->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) { + /* Reflect to all the Non-Client peers and also to the + Client peers other than the originator. Originator + check + is already done. So there is noting to do. */ + /* no bgp client-to-client reflection check. */ + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_NO_CLIENT_TO_CLIENT)) + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + return false; + } else { + /* A route from a Non-client peer. Reflect to all other + clients. */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + return false; + } + } + + /* For modify attribute, copy it to temporary structure. + * post_attr comes from BGP conditional advertisements, where + * attributes are already processed by advertise-map route-map, + * and this needs to be saved instead of overwriting from the + * path attributes. + */ + if (post_attr) + *attr = *post_attr; + else + *attr = *piattr; + + /* don't confuse inbound and outbound setting */ + RESET_FLAG(attr->rmap_change_flags); + + /* If local-preference is not set. */ + if ((peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED) + && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)))) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = bgp->default_local_pref; + } + + /* If originator-id is not set and the route is to be reflected, + set the originator id */ + if (reflect + && (!(attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)))) { + IPV4_ADDR_COPY(&(attr->originator_id), &(from->remote_id)); + SET_FLAG(attr->flag, BGP_ATTR_ORIGINATOR_ID); + } + + /* Remove MED if its an EBGP peer - will get overwritten by route-maps + */ + if (peer->sort == BGP_PEER_EBGP && peer->sub_sort != BGP_PEER_EBGP_OAD && + attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { + if (from != bgp->peer_self && !transparent + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MED_UNCHANGED)) + attr->flag &= + ~(ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)); + } + + /* Since the nexthop attribute can vary per peer, it is not explicitly + * set + * in announce check, only certain flags and length (or number of + * nexthops + * -- for IPv6/MP_REACH) are set here in order to guide the update + * formation + * code in setting the nexthop(s) on a per peer basis in + * reformat_peer(). + * Typically, the source nexthop in the attribute is preserved but in + * the + * scenarios where we know it will always be overwritten, we reset the + * nexthop to "0" in an attempt to achieve better Update packing. An + * example of this is when a prefix from each of 2 IBGP peers needs to + * be + * announced to an EBGP peer (and they have the same attributes barring + * their nexthop). + */ + if (reflect) + SET_FLAG(attr->rmap_change_flags, BATTR_REFLECTED); + +#define NEXTHOP_IS_V6 \ + ((safi != SAFI_ENCAP && safi != SAFI_MPLS_VPN \ + && (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi))) \ + || ((safi == SAFI_ENCAP || safi == SAFI_MPLS_VPN) \ + && attr->mp_nexthop_len >= IPV6_MAX_BYTELEN)) + + /* IPv6/MP starts with 1 nexthop. The link-local address is passed only + * if + * the peer (group) is configured to receive link-local nexthop + * unchanged + * and it is available in the prefix OR we're not reflecting the route, + * link-local nexthop address is valid and + * the peer (group) to whom we're going to announce is on a shared + * network + * and this is either a self-originated route or the peer is EBGP. + * By checking if nexthop LL address is valid we are sure that + * we do not announce LL address as `::`. + */ + if (NEXTHOP_IS_V6) { + attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + if ((CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED) + && IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_local)) + || (!reflect && !transparent + && IN6_IS_ADDR_LINKLOCAL(&peer->nexthop.v6_local) + && peer->shared_network + && (from == bgp->peer_self + || peer->sort == BGP_PEER_EBGP))) { + if (safi == SAFI_MPLS_VPN) + attr->mp_nexthop_len = + BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL; + else + attr->mp_nexthop_len = + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; + } + + /* Clear off link-local nexthop in source, whenever it is not + * needed to + * ensure more prefixes share the same attribute for + * announcement. + */ + if (!(CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED))) + memset(&attr->mp_nexthop_local, 0, IPV6_MAX_BYTELEN); + } + + if (bgp_check_role_applicability(afi, safi) && + bgp_otc_egress(peer, attr)) + return false; + + if (filter->advmap.update_type == UPDATE_TYPE_WITHDRAW && + filter->advmap.aname && + route_map_lookup_by_name(filter->advmap.aname)) { + struct bgp_path_info rmap_path = {0}; + struct bgp_path_info_extra dummy_rmap_path_extra = {0}; + struct attr dummy_attr = *attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &dummy_rmap_path_extra, dest, + pi, peer, &dummy_attr); + + struct route_map *amap = + route_map_lookup_by_name(filter->advmap.aname); + + ret = route_map_apply(amap, p, &rmap_path); + + bgp_attr_flush(&dummy_attr); + + /* + * The conditional advertisement mode is Withdraw and this + * prefix is a conditional prefix. Don't advertise it + */ + if (ret == RMAP_PERMITMATCH) + return false; + } + + /* Route map & unsuppress-map apply. */ + if (!post_attr && + (ROUTE_MAP_OUT_NAME(filter) || bgp_path_suppressed(pi))) { + struct bgp_path_info rmap_path = {0}; + struct bgp_path_info_extra dummy_rmap_path_extra = {0}; + struct attr dummy_attr = {0}; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &dummy_rmap_path_extra, dest, + pi, peer, attr); + /* + * The route reflector is not allowed to modify the attributes + * of the reflected IBGP routes unless explicitly allowed. + */ + if ((from->sort == BGP_PEER_IBGP && peer->sort == BGP_PEER_IBGP) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { + dummy_attr = *attr; + rmap_path.attr = &dummy_attr; + } + + SET_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT); + + if (bgp_path_suppressed(pi)) + ret = route_map_apply(UNSUPPRESS_MAP(filter), p, + &rmap_path); + else + ret = route_map_apply(ROUTE_MAP_OUT(filter), p, + &rmap_path); + + bgp_attr_flush(&dummy_attr); + peer->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered by route-map '%s'", + peer, p, + bgp_path_suppressed(pi) + ? UNSUPPRESS_MAP_NAME(filter) + : ROUTE_MAP_OUT_NAME(filter)); + bgp_attr_flush(rmap_path.attr); + return false; + } + } + + bgp_peer_remove_private_as(bgp, afi, safi, peer, attr); + bgp_peer_as_override(bgp, afi, safi, peer, attr); + + /* RFC 8212 to prevent route leaks. + * This specification intends to improve this situation by requiring the + * explicit configuration of both BGP Import and Export Policies for any + * External BGP (EBGP) session such as customers, peers, or + * confederation boundaries for all enabled address families. Through + * codification of the aforementioned requirement, operators will + * benefit from consistent behavior across different BGP + * implementations. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY)) + if (!bgp_outbound_policy_exists(peer, filter)) { + if (monotime_since(&bgp->ebgprequirespolicywarning, + NULL) > FIFTEENMINUTE2USEC || + bgp->ebgprequirespolicywarning.tv_sec == 0) { + zlog_warn( + "EBGP inbound/outbound policy not properly setup, please configure in order for your peering to work correctly"); + monotime(&bgp->ebgprequirespolicywarning); + } + return false; + } + + /* draft-ietf-idr-deprecate-as-set-confed-set + * Filter routes having AS_SET or AS_CONFED_SET in the path. + * Eventually, This document (if approved) updates RFC 4271 + * and RFC 5065 by eliminating AS_SET and AS_CONFED_SET types, + * and obsoletes RFC 6472. + */ + if (peer->bgp->reject_as_sets) + if (aspath_check_as_sets(attr->aspath)) + return false; + + /* If neighbor soo is configured, then check if the route has + * SoO extended community and validate against the configured + * one. If they match, do not announce, to prevent routing + * loops. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) && + peer->soo[afi][safi]) { + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if ((ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_AS4, + ECOMMUNITY_SITE_ORIGIN) || + ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_SITE_ORIGIN)) && + ecommunity_include(ecomm, ecomm_soo)) { + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%pBP [Update:SEND] %pFX is filtered by SoO extcommunity '%s'", + peer, p, ecommunity_str(ecomm_soo)); + return false; + } + } + + /* Codification of AS 0 Processing */ + if (aspath_check_as_zero(attr->aspath)) + return false; + + if (bgp_in_graceful_shutdown(bgp)) { + if (peer->sort == BGP_PEER_IBGP || + peer->sort == BGP_PEER_CONFED || + peer->sub_sort == BGP_PEER_EBGP_OAD) { + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + attr->local_pref = BGP_GSHUT_LOCAL_PREF; + } else { + bgp_attr_add_gshut_community(attr); + } + } + + /* A BGP speaker that has advertised the "Long-lived Graceful Restart + * Capability" to a neighbor MUST perform the following upon receiving + * a route from that neighbor with the "LLGR_STALE" community, or upon + * attaching the "LLGR_STALE" community itself per Section 4.2: + * + * The route SHOULD NOT be advertised to any neighbor from which the + * Long-lived Graceful Restart Capability has not been received. + */ + if (bgp_attr_get_community(attr) && + community_include(bgp_attr_get_community(attr), + COMMUNITY_LLGR_STALE) && + !CHECK_FLAG(peer->cap, PEER_CAP_LLGR_RCV) && + !CHECK_FLAG(peer->cap, PEER_CAP_LLGR_ADV)) + return false; + + /* After route-map has been applied, we check to see if the nexthop to + * be carried in the attribute (that is used for the announcement) can + * be cleared off or not. We do this in all cases where we would be + * setting the nexthop to "ourselves". For IPv6, we only need to + * consider + * the global nexthop here; the link-local nexthop would have been + * cleared + * already, and if not, it is required by the update formation code. + * Also see earlier comments in this function. + */ + /* + * If route-map has performed some operation on the nexthop or the peer + * configuration says to pass it unchanged, we cannot reset the nexthop + * here, so only attempt to do it if these aren't true. Note that the + * route-map handler itself might have cleared the nexthop, if for + * example, + * it is configured as 'peer-address'. + */ + if (!bgp_rmap_nhop_changed(attr->rmap_change_flags, + piattr->rmap_change_flags) + && !transparent + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED)) { + /* We can reset the nexthop, if setting (or forcing) it to + * 'self' */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_SELF) + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) { + if (!reflect + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + } else if (peer->sort == BGP_PEER_EBGP) { + /* Can also reset the nexthop if announcing to EBGP, but + * only if + * no peer in the subgroup is on a shared subnet. + * Note: 3rd party nexthop currently implemented for + * IPv4 only. + */ + if ((p->family == AF_INET) && + (!bgp_subgrp_multiaccess_check_v4( + piattr->nexthop, + subgrp, from))) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + + if ((p->family == AF_INET6) && + (!bgp_subgrp_multiaccess_check_v6( + piattr->mp_nexthop_global, + subgrp, from))) { + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : p->family), + attr); + nh_reset = true; + } + + + + } else if (CHECK_FLAG(pi->flags, BGP_PATH_ANNC_NH_SELF)) { + /* + * This flag is used for leaked vpn-vrf routes + */ + int family = p->family; + + if (peer_cap_enhe(peer, afi, safi)) + family = AF_INET6; + + if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) + zlog_debug( + "%s: %pFX BGP_PATH_ANNC_NH_SELF, family=%s", + __func__, p, family2str(family)); + subgroup_announce_reset_nhop(family, attr); + nh_reset = true; + } + } + + /* If IPv6/MP and nexthop does not have any override and happens + * to + * be a link-local address, reset it so that we don't pass along + * the + * source's link-local IPv6 address to recipients who may not be + * on + * the same interface. + */ + if (p->family == AF_INET6 || peer_cap_enhe(peer, afi, safi)) { + if (IN6_IS_ADDR_LINKLOCAL(&attr->mp_nexthop_global)) { + subgroup_announce_reset_nhop(AF_INET6, attr); + nh_reset = true; + } + } + + /* If this is an iBGP, send Origin Validation State (OVS) + * extended community (rfc8097). + * draft-uttaro-idr-bgp-oad states: + * For example, the Origin Validation State Extended Community, + * defined as non-transitive in [RFC8097], can be advertised to + * peers in the same OAD. + */ + if ((peer->sort == BGP_PEER_IBGP || + peer->sub_sort == BGP_PEER_EBGP_OAD) && + peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI)) { + enum rpki_states rpki_state = RPKI_NOT_BEING_USED; + + rpki_state = hook_call(bgp_rpki_prefix_status, peer, attr, p); + + if (rpki_state != RPKI_NOT_BEING_USED) + bgp_attr_set_ecommunity(attr, + ecommunity_add_origin_validation_state( + rpki_state, + bgp_attr_get_ecommunity( + attr))); + } + + /* + * When the next hop is set to ourselves, if all multipaths have + * link-bandwidth announce the cumulative bandwidth as that makes + * the most sense. However, don't modify if the link-bandwidth has + * been explicitly set by user policy. + */ + if (nh_reset && bgp_path_info_mpath_chkwtd(bgp, pi) && + (cum_bw = bgp_path_info_mpath_cumbw(pi)) != 0 && + !CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_LINK_BW_SET)) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_EXTENDED_LINK_BANDWIDTH)) + bgp_attr_set_ipv6_ecommunity( + attr, + ecommunity_replace_linkbw(bgp->as, + bgp_attr_get_ipv6_ecommunity( + attr), + cum_bw, false, true)); + else + bgp_attr_set_ecommunity( + attr, + ecommunity_replace_linkbw( + bgp->as, bgp_attr_get_ecommunity(attr), + cum_bw, + CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE), + false)); + } + + return true; +} + +static void bgp_route_select_timer_expire(struct event *thread) +{ + struct afi_safi_info *info; + afi_t afi; + safi_t safi; + struct bgp *bgp; + + info = EVENT_ARG(thread); + afi = info->afi; + safi = info->safi; + bgp = info->bgp; + + bgp->gr_info[afi][safi].t_route_select = NULL; + XFREE(MTYPE_TMP, info); + + /* Best path selection */ + bgp_best_path_select_defer(bgp, afi, safi); +} + +void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_maxpaths_cfg *mpath_cfg, + struct bgp_path_info_pair *result, afi_t afi, + safi_t safi) +{ + struct bgp_path_info *new_select, *look_thru; + struct bgp_path_info *old_select, *worse, *first; + struct bgp_path_info *pi; + struct bgp_path_info *pi1; + struct bgp_path_info *pi2; + int paths_eq, do_mpath; + bool debug, any_comparisons; + struct list mp_list; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + char path_buf[PATH_ADDPATH_STR_BUFFER]; + enum bgp_path_selection_reason reason = bgp_path_selection_none; + bool unsorted_items = true; + + bgp_mp_list_init(&mp_list); + do_mpath = + (mpath_cfg->maxpaths_ebgp > 1 || mpath_cfg->maxpaths_ibgp > 1); + + debug = bgp_debug_bestpath(dest); + + if (debug) + prefix2str(bgp_dest_get_prefix(dest), pfx_buf, sizeof(pfx_buf)); + + /* bgp deterministic-med */ + new_select = NULL; + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED)) { + /* Clear BGP_PATH_DMED_SELECTED for all paths */ + for (pi1 = bgp_dest_get_bgp_path_info(dest); pi1; + pi1 = pi1->next) { + bgp_path_info_unset_flag(dest, pi1, + BGP_PATH_DMED_SELECTED); + UNSET_FLAG(pi1->flags, BGP_PATH_DMED_CHECK); + } + + for (pi1 = bgp_dest_get_bgp_path_info(dest); pi1; + pi1 = pi1->next) { + if (CHECK_FLAG(pi1->flags, BGP_PATH_DMED_CHECK)) + continue; + if (BGP_PATH_HOLDDOWN(pi1)) + continue; + if (pi1->peer != bgp->peer_self && + !CHECK_FLAG(pi1->peer->sflags, + PEER_STATUS_NSF_WAIT)) { + if (!peer_established(pi1->peer->connection)) + continue; + } + + new_select = pi1; + for (pi2 = pi1->next; pi2; pi2 = pi2->next) { + if (CHECK_FLAG(pi2->flags, BGP_PATH_DMED_CHECK)) + continue; + if (BGP_PATH_HOLDDOWN(pi2)) + continue; + if (pi2->peer != bgp->peer_self && + !CHECK_FLAG(pi2->peer->sflags, + PEER_STATUS_NSF_WAIT) && + !peer_established(pi2->peer->connection)) + continue; + + if (!aspath_cmp_left(pi1->attr->aspath, + pi2->attr->aspath) && + !aspath_cmp_left_confed(pi1->attr->aspath, + pi2->attr->aspath)) + continue; + + if (bgp_path_info_cmp(bgp, pi2, new_select, + &paths_eq, mpath_cfg, + debug, pfx_buf, afi, safi, + &dest->reason)) { + bgp_path_info_unset_flag(dest, + new_select, + BGP_PATH_DMED_SELECTED); + new_select = pi2; + } + + bgp_path_info_set_flag(dest, pi2, + BGP_PATH_DMED_CHECK); + } + bgp_path_info_set_flag(dest, new_select, + BGP_PATH_DMED_CHECK); + bgp_path_info_set_flag(dest, new_select, + BGP_PATH_DMED_SELECTED); + + if (debug) { + bgp_path_info_path_with_addpath_rx_str( + new_select, path_buf, sizeof(path_buf)); + zlog_debug( + "%pBD(%s): %s is the bestpath from AS %u", + dest, bgp->name_pretty, path_buf, + aspath_get_first_as( + new_select->attr->aspath)); + } + } + } + + /* + * Let's grab the unsorted items from the list + */ + struct bgp_path_info *unsorted_list = NULL; + struct bgp_path_info *unsorted_list_spot = NULL; + struct bgp_path_info *unsorted_holddown = NULL; + + old_select = NULL; + pi = bgp_dest_get_bgp_path_info(dest); + while (pi && CHECK_FLAG(pi->flags, BGP_PATH_UNSORTED)) { + struct bgp_path_info *next = pi->next; + + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + old_select = pi; + + /* + * Pull off pi off the list + */ + if (pi->next) + pi->next->prev = NULL; + + bgp_dest_set_bgp_path_info(dest, pi->next); + pi->next = NULL; + pi->prev = NULL; + + /* + * Place it on the unsorted list + */ + if (unsorted_list_spot) { + unsorted_list_spot->next = pi; + pi->prev = unsorted_list_spot; + pi->next = NULL; + } else { + unsorted_list = pi; + + pi->next = NULL; + pi->prev = NULL; + } + + unsorted_list_spot = pi; + pi = next; + } + + if (!old_select) { + old_select = bgp_dest_get_bgp_path_info(dest); + if (old_select && + !CHECK_FLAG(old_select->flags, BGP_PATH_SELECTED)) + old_select = NULL; + } + + if (!unsorted_list) + unsorted_items = true; + else + unsorted_items = false; + + any_comparisons = false; + worse = NULL; + while (unsorted_list) { + first = unsorted_list; + unsorted_list = unsorted_list->next; + + if (unsorted_list) + unsorted_list->prev = NULL; + first->next = NULL; + first->prev = NULL; + + /* + * It's not likely that the just received unsorted entry + * is in holddown and scheduled for removal but we should + * check + */ + if (BGP_PATH_HOLDDOWN(first)) { + /* + * reap REMOVED routes, if needs be + * selected route must stay for a while longer though + */ + if (debug) + zlog_debug("%s: %pBD(%s) pi %p from %s in holddown", + __func__, dest, bgp->name_pretty, + first, first->peer->host); + + if (old_select != first && + CHECK_FLAG(first->flags, BGP_PATH_REMOVED)) { + dest = bgp_path_info_reap_unsorted(dest, first); + assert(dest); + } else { + /* + * We are in hold down, so we cannot sort this + * item yet. Let's wait, so hold the unsorted + * to the side + */ + if (unsorted_holddown) { + first->next = unsorted_holddown; + unsorted_holddown->prev = first; + unsorted_holddown = first; + } else + unsorted_holddown = first; + + UNSET_FLAG(first->flags, BGP_PATH_UNSORTED); + } + continue; + } + + bgp_path_info_unset_flag(dest, first, BGP_PATH_DMED_CHECK); + + worse = NULL; + + struct bgp_path_info *look_thru_next; + + for (look_thru = bgp_dest_get_bgp_path_info(dest); look_thru; + look_thru = look_thru_next) { + /* look thru can be reaped save the next pointer */ + look_thru_next = look_thru->next; + + /* + * Now we have the first unsorted and the best selected + * Let's do best path comparison + */ + if (BGP_PATH_HOLDDOWN(look_thru)) { + /* reap REMOVED routes, if needs be + * selected route must stay for a while longer though + */ + if (debug) + zlog_debug("%s: %pBD(%s) pi from %s %p in holddown", + __func__, dest, + bgp->name_pretty, + look_thru->peer->host, + look_thru); + + if (CHECK_FLAG(look_thru->flags, + BGP_PATH_REMOVED) && + (look_thru != old_select)) { + dest = bgp_path_info_reap(dest, + look_thru); + assert(dest); + } + + continue; + } + + if (look_thru->peer && + look_thru->peer != bgp->peer_self && + !CHECK_FLAG(look_thru->peer->sflags, + PEER_STATUS_NSF_WAIT)) + if (!peer_established( + look_thru->peer->connection)) { + if (debug) + zlog_debug("%s: %pBD(%s) non self peer %s not estab state", + __func__, dest, + bgp->name_pretty, + look_thru->peer->host); + + continue; + } + + bgp_path_info_unset_flag(dest, look_thru, + BGP_PATH_DMED_CHECK); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) && + (!CHECK_FLAG(look_thru->flags, + BGP_PATH_DMED_SELECTED))) { + bgp_path_info_unset_flag(dest, look_thru, + BGP_PATH_DMED_CHECK); + if (debug) + zlog_debug("%s: %pBD(%s) pi %s dmed", + __func__, dest, + bgp->name_pretty, + look_thru->peer->host); + + worse = look_thru; + continue; + } + + reason = dest->reason; + any_comparisons = true; + if (bgp_path_info_cmp(bgp, first, look_thru, &paths_eq, + mpath_cfg, debug, pfx_buf, afi, + safi, &reason)) { + first->reason = reason; + worse = look_thru; + /* + * We can stop looking + */ + break; + } + + look_thru->reason = reason; + } + + if (!any_comparisons) + first->reason = bgp_path_selection_first; + + /* + * At this point worse if NON-NULL is where the first + * pointer should be before. if worse is NULL then + * first is bestpath too. Let's remove first from the + * list and place it in the right spot + */ + + if (!worse) { + struct bgp_path_info *end = + bgp_dest_get_bgp_path_info(dest); + + for (; end && end->next != NULL; end = end->next) + ; + + if (end) + end->next = first; + else + bgp_dest_set_bgp_path_info(dest, first); + first->prev = end; + first->next = NULL; + + dest->reason = first->reason; + } else { + if (worse->prev) + worse->prev->next = first; + first->next = worse; + if (worse) { + first->prev = worse->prev; + worse->prev = first; + } else + first->prev = NULL; + + if (dest->info == worse) { + bgp_dest_set_bgp_path_info(dest, first); + dest->reason = first->reason; + } + } + UNSET_FLAG(first->flags, BGP_PATH_UNSORTED); + } + + if (!unsorted_items) { + new_select = bgp_dest_get_bgp_path_info(dest); + while (new_select && BGP_PATH_HOLDDOWN(new_select)) + new_select = new_select->next; + + if (new_select) { + if (new_select->reason == bgp_path_selection_none) + new_select->reason = bgp_path_selection_first; + else if (new_select == bgp_dest_get_bgp_path_info(dest) && + new_select->next == NULL) + new_select->reason = bgp_path_selection_first; + dest->reason = new_select->reason; + } else + dest->reason = bgp_path_selection_none; + } else + new_select = old_select; + + + /* + * Reinsert all the unsorted_holddown items for future processing + * at the end of the list. + */ + if (unsorted_holddown) { + struct bgp_path_info *top = bgp_dest_get_bgp_path_info(dest); + struct bgp_path_info *prev = NULL; + + while (top != NULL) { + prev = top; + top = top->next; + } + + if (prev) { + prev->next = unsorted_holddown; + unsorted_holddown->prev = prev; + } else + bgp_dest_set_bgp_path_info(dest, unsorted_holddown); + } + + /* Now that we know which path is the bestpath see if any of the other + * paths + * qualify as multipaths + */ + if (debug) { + bgp_path_info_path_with_addpath_rx_str(new_select, path_buf, + sizeof(path_buf)); + zlog_debug( + "%pBD(%s): After path selection, newbest is %s oldbest was %s", + dest, bgp->name_pretty, path_buf, + old_select ? old_select->peer->host : "NONE"); + } + + if (do_mpath && new_select) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (debug) + bgp_path_info_path_with_addpath_rx_str( + pi, path_buf, sizeof(path_buf)); + + if (pi == new_select) { + if (debug) + zlog_debug( + "%pBD(%s): %s is the bestpath, add to the multipath list", + dest, bgp->name_pretty, + path_buf); + bgp_mp_list_add(&mp_list, pi); + continue; + } + + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->peer && pi->peer != bgp->peer_self + && !CHECK_FLAG(pi->peer->sflags, + PEER_STATUS_NSF_WAIT)) + if (!peer_established(pi->peer->connection)) + continue; + + if (!bgp_path_info_nexthop_cmp(pi, new_select)) { + if (debug) + zlog_debug( + "%pBD(%s): %s has the same nexthop as the bestpath, skip it", + dest, bgp->name_pretty, + path_buf); + continue; + } + + bgp_path_info_cmp(bgp, pi, new_select, &paths_eq, + mpath_cfg, debug, pfx_buf, afi, safi, + &dest->reason); + + if (paths_eq) { + if (debug) + zlog_debug( + "%pBD(%s): %s is equivalent to the bestpath, add to the multipath list", + dest, bgp->name_pretty, + path_buf); + bgp_mp_list_add(&mp_list, pi); + } + } + } + + bgp_path_info_mpath_update(bgp, dest, new_select, old_select, &mp_list, + mpath_cfg); + bgp_path_info_mpath_aggregate_update(new_select, old_select); + bgp_mp_list_clear(&mp_list); + + bgp_addpath_update_ids(bgp, dest, afi, safi); + + result->old = old_select; + result->new = new_select; + + return; +} + +/* + * A new route/change in bestpath of an existing route. Evaluate the path + * for advertisement to the subgroup. + */ +void subgroup_process_announce_selected(struct update_subgroup *subgrp, + struct bgp_path_info *selected, + struct bgp_dest *dest, afi_t afi, + safi_t safi, uint32_t addpath_tx_id) +{ + const struct prefix *p; + struct peer *onlypeer; + struct attr attr = { 0 }, *pattr = &attr; + struct bgp *bgp; + bool advertise; + + p = bgp_dest_get_prefix(dest); + bgp = SUBGRP_INST(subgrp); + onlypeer = ((SUBGRP_PCOUNT(subgrp) == 1) ? (SUBGRP_PFIRST(subgrp))->peer + : NULL); + + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("%s: p=%pFX, selected=%p", __func__, p, selected); + + /* First update is deferred until ORF or ROUTE-REFRESH is received */ + if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH)) + return; + + memset(&attr, 0, sizeof(attr)); + /* It's initialized in bgp_announce_check() */ + + /* Announcement to the subgroup. If the route is filtered withdraw it. + * If BGP_NODE_FIB_INSTALL_PENDING is set and data plane install status + * is pending (BGP_NODE_FIB_INSTALL_PENDING), do not advertise the + * route + */ + advertise = bgp_check_advertise(bgp, dest, safi); + + if (selected) { + if (subgroup_announce_check(dest, selected, subgrp, p, pattr, + NULL)) { + /* Route is selected, if the route is already installed + * in FIB, then it is advertised + */ + if (advertise) { + if (!bgp_check_withdrawal(bgp, dest, safi)) { + if (!bgp_adj_out_set_subgroup(dest, + subgrp, + pattr, + selected)) + bgp_attr_flush(pattr); + } else { + bgp_adj_out_unset_subgroup( + dest, subgrp, 1, addpath_tx_id); + bgp_attr_flush(pattr); + } + } else + bgp_attr_flush(pattr); + } else { + bgp_adj_out_unset_subgroup(dest, subgrp, 1, + addpath_tx_id); + bgp_attr_flush(pattr); + } + } + + /* If selected is NULL we must withdraw the path using addpath_tx_id */ + else { + bgp_adj_out_unset_subgroup(dest, subgrp, 1, addpath_tx_id); + } +} + +/* + * Clear IGP changed flag and attribute changed flag for a route (all paths). + * This is called at the end of route processing. + */ +void bgp_zebra_clear_route_change_flags(struct bgp_dest *dest) +{ + struct bgp_path_info *pi; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + UNSET_FLAG(pi->flags, BGP_PATH_IGP_CHANGED); + UNSET_FLAG(pi->flags, BGP_PATH_ATTR_CHANGED); + } +} + +/* + * Has the route changed from the RIB's perspective? This is invoked only + * if the route selection returns the same best route as earlier - to + * determine if we need to update zebra or not. + */ +bool bgp_zebra_has_route_changed(struct bgp_path_info *selected) +{ + struct bgp_path_info *mpinfo; + + /* If this is multipath, check all selected paths for any nexthop + * change or attribute change. Some attribute changes (e.g., community) + * aren't of relevance to the RIB, but we'll update zebra to ensure + * we handle the case of BGP nexthop change. This is the behavior + * when the best path has an attribute change anyway. + */ + if (CHECK_FLAG(selected->flags, BGP_PATH_IGP_CHANGED) + || CHECK_FLAG(selected->flags, BGP_PATH_MULTIPATH_CHG) + || CHECK_FLAG(selected->flags, BGP_PATH_LINK_BW_CHG)) + return true; + + /* + * If this is multipath, check all selected paths for any nexthop change + */ + for (mpinfo = bgp_path_info_mpath_first(selected); mpinfo; + mpinfo = bgp_path_info_mpath_next(mpinfo)) { + if (CHECK_FLAG(mpinfo->flags, BGP_PATH_IGP_CHANGED) + || CHECK_FLAG(mpinfo->flags, BGP_PATH_ATTR_CHANGED)) + return true; + } + + /* Nothing has changed from the RIB's perspective. */ + return false; +} + +struct bgp_process_queue { + struct bgp *bgp; + STAILQ_HEAD(, bgp_dest) pqueue; +#define BGP_PROCESS_QUEUE_EOIU_MARKER (1 << 0) + unsigned int flags; + unsigned int queued; +}; + +static void bgp_process_evpn_route_injection(struct bgp *bgp, afi_t afi, + safi_t safi, struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select) +{ + const struct prefix *p = bgp_dest_get_prefix(dest); + + if ((afi != AFI_IP && afi != AFI_IP6) || (safi != SAFI_UNICAST)) + return; + + if (advertise_type5_routes(bgp, afi) && new_select + && is_route_injectable_into_evpn(new_select)) { + + /* apply the route-map */ + if (bgp->adv_cmd_rmap[afi][safi].map) { + route_map_result_t ret; + struct bgp_path_info rmap_path; + struct bgp_path_info_extra rmap_path_extra; + struct attr dummy_attr; + + dummy_attr = *new_select->attr; + + /* Fill temp path_info */ + prep_for_rmap_apply(&rmap_path, &rmap_path_extra, dest, + new_select, new_select->peer, + &dummy_attr); + + RESET_FLAG(dummy_attr.rmap_change_flags); + + ret = route_map_apply(bgp->adv_cmd_rmap[afi][safi].map, + p, &rmap_path); + + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&dummy_attr); + bgp_evpn_withdraw_type5_route(bgp, p, afi, + safi); + } else + bgp_evpn_advertise_type5_route( + bgp, p, &dummy_attr, afi, safi); + } else { + bgp_evpn_advertise_type5_route(bgp, p, new_select->attr, + afi, safi); + } + } else if (advertise_type5_routes(bgp, afi) && old_select + && is_route_injectable_into_evpn(old_select)) + bgp_evpn_withdraw_type5_route(bgp, p, afi, safi); +} + +/* + * Utility to determine whether a particular path_info should use + * the IMPLICIT_NULL label. This is pretty specialized: it's only called + * in a path where we basically _know_ this is a BGP-LU route. + */ +static bool bgp_lu_need_null_label(struct bgp *bgp, + const struct bgp_path_info *new_select, + afi_t afi, mpls_label_t *label) +{ + /* Certain types get imp null; so do paths where the nexthop is + * not labeled. + */ + if (new_select->sub_type == BGP_ROUTE_STATIC + || new_select->sub_type == BGP_ROUTE_AGGREGATE + || new_select->sub_type == BGP_ROUTE_REDISTRIBUTE) + goto need_null_label; + else if (bgp_path_info_has_valid_label(new_select)) + return false; +need_null_label: + if (label == NULL) + return true; + /* Disable PHP : explicit-null */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV4_EXPLICIT_NULL) && + afi == AFI_IP) + *label = MPLS_LABEL_IPV4_EXPLICIT_NULL; + else if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV6_EXPLICIT_NULL) && + afi == AFI_IP6) + *label = MPLS_LABEL_IPV6_EXPLICIT_NULL; + else + /* Enforced PHP popping: implicit-null */ + *label = MPLS_LABEL_IMPLICIT_NULL; + + return true; +} + +/* Right now, since we only deal with per-prefix labels, it is not + * necessary to do this upon changes to best path. Exceptions: + * - label index has changed -> recalculate resulting label + * - path_info sub_type changed -> switch to/from null label value + * - no valid label (due to removed static label binding) -> get new one + */ +static void bgp_lu_handle_label_allocation(struct bgp *bgp, + struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select, + afi_t afi) +{ + mpls_label_t mpls_label_null; + + if (bgp->allocate_mpls_labels[afi][SAFI_UNICAST]) { + if (new_select) { + if (!old_select || + bgp_label_index_differs(new_select, old_select) || + new_select->sub_type != old_select->sub_type || + !bgp_is_valid_label(&dest->local_label)) { + /* control label imposition for local + * routes, aggregate and redistributed + * routes + */ + mpls_label_null = MPLS_LABEL_IMPLICIT_NULL; + if (bgp_lu_need_null_label(bgp, new_select, afi, + &mpls_label_null)) { + if (CHECK_FLAG( + dest->flags, + BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG( + dest->flags, + BGP_NODE_LABEL_REQUESTED)) + bgp_unregister_for_label(dest); + dest->local_label = mpls_lse_encode( + mpls_label_null, 0, 0, 1); + bgp_set_valid_label(&dest->local_label); + } else + bgp_register_for_label(dest, + new_select); + } + } else if (CHECK_FLAG(dest->flags, + BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) { + bgp_unregister_for_label(dest); + } + } else if (CHECK_FLAG(dest->flags, BGP_NODE_REGISTERED_FOR_LABEL) || + CHECK_FLAG(dest->flags, BGP_NODE_LABEL_REQUESTED)) { + bgp_unregister_for_label(dest); + } +} + +static struct interface * +bgp_label_get_resolved_nh_iface(const struct bgp_path_info *pi) +{ + struct nexthop *nh; + + if (pi->nexthop == NULL || pi->nexthop->nexthop == NULL || + !CHECK_FLAG(pi->nexthop->flags, BGP_NEXTHOP_VALID)) + /* next-hop is not valid */ + return NULL; + + nh = pi->nexthop->nexthop; + if (nh->ifindex == IFINDEX_INTERNAL && + nh->type != NEXTHOP_TYPE_IPV4_IFINDEX && + nh->type != NEXTHOP_TYPE_IPV6_IFINDEX) + /* next-hop does not contain valid interface */ + return NULL; + + return if_lookup_by_index(nh->ifindex, nh->vrf_id); +} + +static void +bgp_mplsvpn_handle_label_allocation(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *new_select, + struct bgp_path_info *old_select, afi_t afi) +{ + struct interface *ifp; + struct bgp_interface *bgp_ifp; + + if (bgp->allocate_mpls_labels[afi][SAFI_MPLS_VPN] && new_select) { + ifp = bgp_label_get_resolved_nh_iface(new_select); + if (ifp) + bgp_ifp = (struct bgp_interface *)(ifp->info); + else + bgp_ifp = NULL; + if (bgp_ifp && + CHECK_FLAG(bgp_ifp->flags, + BGP_INTERFACE_MPLS_L3VPN_SWITCHING) && + bgp_mplsvpn_path_uses_valid_mpls_label(new_select) && + new_select->sub_type != BGP_ROUTE_IMPORTED && + new_select->sub_type != BGP_ROUTE_STATIC) + bgp_mplsvpn_nh_label_bind_register_local_label( + bgp, dest, new_select); + else + bgp_mplsvpn_path_nh_label_bind_unlink(new_select); + } else { + if (new_select) + /* no mpls vpn allocation */ + bgp_mplsvpn_path_nh_label_bind_unlink(new_select); + else if (old_select) + /* unlink old selection if any */ + bgp_mplsvpn_path_nh_label_bind_unlink(old_select); + } +} + +/* + * old_select = The old best path + * new_select = the new best path + * + * if (!old_select && new_select) + * We are sending new information on. + * + * if (old_select && new_select) { + * if (new_select != old_select) + * We have a new best path send a change + * else + * We've received a update with new attributes that needs + * to be passed on. + * } + * + * if (old_select && !new_select) + * We have no eligible route that we can announce or the rn + * is being removed. + */ +static void bgp_process_main_one(struct bgp *bgp, struct bgp_dest *dest, + afi_t afi, safi_t safi) +{ + struct bgp_path_info *new_select; + struct bgp_path_info *old_select; + struct bgp_path_info_pair old_and_new; + int debug = 0; + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) { + if (dest) + debug = bgp_debug_bestpath(dest); + if (debug) + zlog_debug( + "%s: bgp delete in progress, ignoring event, p=%pBD(%s)", + __func__, dest, bgp->name_pretty); + return; + } + /* Is it end of initial update? (after startup) */ + if (!dest) { + frr_timestamp(3, bgp->update_delay_zebra_resume_time, + sizeof(bgp->update_delay_zebra_resume_time)); + + bgp->main_zebra_update_hold = 0; + FOREACH_AFI_SAFI (afi, safi) { + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + } + bgp->main_peers_update_hold = 0; + + bgp_start_routeadv(bgp); + return; + } + +#ifdef ENABLE_BGP_VNC + const struct prefix *p = bgp_dest_get_prefix(dest); +#endif + + debug = bgp_debug_bestpath(dest); + if (debug) + zlog_debug("%s: p=%pBD(%s) afi=%s, safi=%s start", __func__, + dest, bgp->name_pretty, afi2str(afi), + safi2str(safi)); + + /* The best path calculation for the route is deferred if + * BGP_NODE_SELECT_DEFER is set + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("SELECT_DEFER flag set for route %p(%s)", + dest, bgp->name_pretty); + return; + } + + /* Best path selection. */ + bgp_best_selection(bgp, dest, &bgp->maxpaths[afi][safi], &old_and_new, + afi, safi); + old_select = old_and_new.old; + new_select = old_and_new.new; + + if (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST) + /* label unicast path : + * Do we need to allocate or free labels? + */ + bgp_lu_handle_label_allocation(bgp, dest, new_select, + old_select, afi); + else if (safi == SAFI_MPLS_VPN) + /* mpls vpn path: + * Do we need to allocate or free labels? + */ + bgp_mplsvpn_handle_label_allocation(bgp, dest, new_select, + old_select, afi); + + if (debug) + zlog_debug( + "%s: p=%pBD(%s) afi=%s, safi=%s, old_select=%p, new_select=%p", + __func__, dest, bgp->name_pretty, afi2str(afi), + safi2str(safi), old_select, new_select); + + /* If best route remains the same and this is not due to user-initiated + * clear, see exactly what needs to be done. + */ + if (old_select && old_select == new_select && + !CHECK_FLAG(dest->flags, BGP_NODE_USER_CLEAR) && + !CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_CLEAR) && + !CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) && + !bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + if (bgp_zebra_has_route_changed(old_select)) { +#ifdef ENABLE_BGP_VNC + vnc_import_bgp_add_route(bgp, p, old_select); + vnc_import_bgp_exterior_add_route(bgp, p, old_select); +#endif + if (bgp_fibupd_safi(safi) + && !bgp_option_check(BGP_OPT_NO_FIB)) { + + if (new_select->type == ZEBRA_ROUTE_BGP + && (new_select->sub_type == BGP_ROUTE_NORMAL + || new_select->sub_type + == BGP_ROUTE_IMPORTED)) + bgp_zebra_route_install(dest, old_select, + bgp, true, NULL, + false); + } + } + + /* If there is a change of interest to peers, reannounce the + * route. */ + if (CHECK_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED) + || CHECK_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG) + || CHECK_FLAG(dest->flags, BGP_NODE_LABEL_CHANGED)) { + group_announce_route(bgp, afi, safi, dest, new_select); + + /* unicast routes must also be annouced to + * labeled-unicast update-groups */ + if (safi == SAFI_UNICAST) + group_announce_route(bgp, afi, + SAFI_LABELED_UNICAST, dest, + new_select); + + UNSET_FLAG(old_select->flags, BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(dest->flags, BGP_NODE_LABEL_CHANGED); + } + + /* advertise/withdraw type-5 routes */ + if (CHECK_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG) + || CHECK_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG)) + bgp_process_evpn_route_injection( + bgp, afi, safi, dest, old_select, old_select); + + UNSET_FLAG(old_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(old_select->flags, BGP_PATH_LINK_BW_CHG); + bgp_zebra_clear_route_change_flags(dest); + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + return; + } + + /* If the user did "clear ip bgp prefix x.x.x.x" this flag will be set + */ + UNSET_FLAG(dest->flags, BGP_NODE_USER_CLEAR); + + /* If the process wants to force deletion this flag will be set + */ + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_CLEAR); + + /* bestpath has changed; bump version */ + if (old_select || new_select) { + bgp_bump_version(dest); + + if (!bgp->t_rmap_def_originate_eval) + event_add_timer( + bm->master, + update_group_refresh_default_originate_route_map, + bgp, bgp->rmap_def_originate_eval_timer, + &bgp->t_rmap_def_originate_eval); + } + + /* TODO BMP insert rib update hook */ + if (old_select) + bgp_path_info_unset_flag(dest, old_select, BGP_PATH_SELECTED); + if (new_select) { + if (debug) + zlog_debug("%s: %pBD setting SELECTED flag", __func__, + dest); + bgp_path_info_set_flag(dest, new_select, BGP_PATH_SELECTED); + bgp_path_info_unset_flag(dest, new_select, + BGP_PATH_ATTR_CHANGED); + UNSET_FLAG(new_select->flags, BGP_PATH_MULTIPATH_CHG); + UNSET_FLAG(new_select->flags, BGP_PATH_LINK_BW_CHG); + } + + /* call bmp hook for loc-rib route update / withdraw after flags were + * set + */ + if (old_select || new_select) { + hook_call(bgp_route_update, bgp, afi, safi, dest, old_select, + new_select); + } + + +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { + if (old_select != new_select) { + if (old_select) { + vnc_import_bgp_exterior_del_route(bgp, p, + old_select); + vnc_import_bgp_del_route(bgp, p, old_select); + } + if (new_select) { + vnc_import_bgp_exterior_add_route(bgp, p, + new_select); + vnc_import_bgp_add_route(bgp, p, new_select); + } + } + } +#endif + + /* FIB update. */ + if (bgp_fibupd_safi(safi) && (bgp->inst_type != BGP_INSTANCE_TYPE_VIEW) + && !bgp_option_check(BGP_OPT_NO_FIB)) { + + if (new_select && new_select->type == ZEBRA_ROUTE_BGP + && (new_select->sub_type == BGP_ROUTE_NORMAL + || new_select->sub_type == BGP_ROUTE_AGGREGATE + || new_select->sub_type == BGP_ROUTE_IMPORTED)) { + + /* if this is an evpn imported type-5 prefix, + * we need to withdraw the route first to clear + * the nh neigh and the RMAC entry. + */ + if (old_select && + is_route_parent_evpn(old_select)) + bgp_zebra_withdraw_actual(dest, old_select, bgp); + + bgp_zebra_route_install(dest, new_select, bgp, true, + NULL, false); + } else { + /* Withdraw the route from the kernel. */ + if (old_select && old_select->type == ZEBRA_ROUTE_BGP + && (old_select->sub_type == BGP_ROUTE_NORMAL + || old_select->sub_type == BGP_ROUTE_AGGREGATE + || old_select->sub_type == BGP_ROUTE_IMPORTED)) + + bgp_zebra_route_install(dest, old_select, bgp, + false, NULL, false); + } + } + + group_announce_route(bgp, afi, safi, dest, new_select); + + /* unicast routes must also be annouced to labeled-unicast update-groups + */ + if (safi == SAFI_UNICAST) + group_announce_route(bgp, afi, SAFI_LABELED_UNICAST, dest, + new_select); + + + bgp_process_evpn_route_injection(bgp, afi, safi, dest, new_select, + old_select); + + /* Clear any route change flags. */ + bgp_zebra_clear_route_change_flags(dest); + + UNSET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + + /* Reap old select bgp_path_info, if it has been removed */ + if (old_select && CHECK_FLAG(old_select->flags, BGP_PATH_REMOVED)) + bgp_path_info_reap(dest, old_select); + + return; +} + +/* Process the routes with the flag BGP_NODE_SELECT_DEFER set */ +void bgp_best_path_select_defer(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + int cnt = 0; + struct afi_safi_info *thread_info; + + if (bgp->gr_info[afi][safi].t_route_select) { + struct event *t = bgp->gr_info[afi][safi].t_route_select; + + thread_info = EVENT_ARG(t); + XFREE(MTYPE_TMP, thread_info); + EVENT_OFF(bgp->gr_info[afi][safi].t_route_select); + } + + if (BGP_DEBUG(update, UPDATE_OUT)) { + zlog_debug("%s: processing route for %s : cnt %d", __func__, + get_afi_safi_str(afi, safi, false), + bgp->gr_info[afi][safi].gr_deferred); + } + + /* Process the route list */ + for (dest = bgp_table_top(bgp->rib[afi][safi]); + dest && bgp->gr_info[afi][safi].gr_deferred != 0 && + cnt < BGP_MAX_BEST_ROUTE_SELECT; + dest = bgp_route_next(dest)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) + continue; + + UNSET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + bgp->gr_info[afi][safi].gr_deferred--; + bgp_process_main_one(bgp, dest, afi, safi); + cnt++; + } + /* If iteration stopped before the entire table was traversed then the + * node needs to be unlocked. + */ + if (dest) { + bgp_dest_unlock_node(dest); + dest = NULL; + } + + /* Send EOR message when all routes are processed */ + if (!bgp->gr_info[afi][safi].gr_deferred) { + bgp_send_delayed_eor(bgp); + /* Send route processing complete message to RIB */ + bgp_zebra_update(bgp, afi, safi, + ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE); + return; + } + + thread_info = XMALLOC(MTYPE_TMP, sizeof(struct afi_safi_info)); + + thread_info->afi = afi; + thread_info->safi = safi; + thread_info->bgp = bgp; + + /* If there are more routes to be processed, start the + * selection timer + */ + event_add_timer(bm->master, bgp_route_select_timer_expire, thread_info, + BGP_ROUTE_SELECT_DELAY, + &bgp->gr_info[afi][safi].t_route_select); +} + +static wq_item_status bgp_process_wq(struct work_queue *wq, void *data) +{ + struct bgp_process_queue *pqnode = data; + struct bgp *bgp = pqnode->bgp; + struct bgp_table *table; + struct bgp_dest *dest; + + /* eoiu marker */ + if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER)) { + bgp_process_main_one(bgp, NULL, 0, 0); + /* should always have dedicated wq call */ + assert(STAILQ_FIRST(&pqnode->pqueue) == NULL); + return WQ_SUCCESS; + } + + while (!STAILQ_EMPTY(&pqnode->pqueue)) { + dest = STAILQ_FIRST(&pqnode->pqueue); + STAILQ_REMOVE_HEAD(&pqnode->pqueue, pq); + STAILQ_NEXT(dest, pq) = NULL; /* complete unlink */ + table = bgp_dest_table(dest); + /* note, new DESTs may be added as part of processing */ + bgp_process_main_one(bgp, dest, table->afi, table->safi); + + bgp_dest_unlock_node(dest); + bgp_table_unlock(table); + } + + return WQ_SUCCESS; +} + +static void bgp_processq_del(struct work_queue *wq, void *data) +{ + struct bgp_process_queue *pqnode = data; + + bgp_unlock(pqnode->bgp); + + XFREE(MTYPE_BGP_PROCESS_QUEUE, pqnode); +} + +void bgp_process_queue_init(struct bgp *bgp) +{ + if (!bgp->process_queue) { + char name[BUFSIZ]; + + snprintf(name, BUFSIZ, "process_queue %s", bgp->name_pretty); + bgp->process_queue = work_queue_new(bm->master, name); + } + + bgp->process_queue->spec.workfunc = &bgp_process_wq; + bgp->process_queue->spec.del_item_data = &bgp_processq_del; + bgp->process_queue->spec.max_retries = 0; + bgp->process_queue->spec.hold = 50; + /* Use a higher yield value of 50ms for main queue processing */ + bgp->process_queue->spec.yield = 50 * 1000L; +} + +static struct bgp_process_queue *bgp_processq_alloc(struct bgp *bgp) +{ + struct bgp_process_queue *pqnode; + + pqnode = XCALLOC(MTYPE_BGP_PROCESS_QUEUE, + sizeof(struct bgp_process_queue)); + + /* unlocked in bgp_processq_del */ + pqnode->bgp = bgp_lock(bgp); + STAILQ_INIT(&pqnode->pqueue); + + return pqnode; +} + +void bgp_process(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *pi, afi_t afi, safi_t safi) +{ +#define ARBITRARY_PROCESS_QLEN 10000 + struct work_queue *wq = bgp->process_queue; + struct bgp_process_queue *pqnode; + int pqnode_reuse = 0; + + /* + * Indicate that *this* pi is in an unsorted + * situation, even if the node is already + * scheduled. + */ + if (pi) { + struct bgp_path_info *first = bgp_dest_get_bgp_path_info(dest); + + SET_FLAG(pi->flags, BGP_PATH_UNSORTED); + + if (pi != first) { + if (pi->next) + pi->next->prev = pi->prev; + if (pi->prev) + pi->prev->next = pi->next; + + if (first) + first->prev = pi; + pi->next = first; + pi->prev = NULL; + bgp_dest_set_bgp_path_info(dest, pi); + } + } + + /* already scheduled for processing? */ + if (CHECK_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED)) + return; + + /* If the flag BGP_NODE_SELECT_DEFER is set, do not add route to + * the workqueue + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("BGP_NODE_SELECT_DEFER set for route %p", + dest); + return; + } + + if (CHECK_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG)) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug( + "Soft reconfigure table in progress for route %p", + dest); + return; + } + + if (wq == NULL) + return; + + /* Add route nodes to an existing work queue item until reaching the + limit only if is from the same BGP view and it's not an EOIU marker + */ + if (work_queue_item_count(wq)) { + struct work_queue_item *item = work_queue_last_item(wq); + pqnode = item->data; + + if (CHECK_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER) + || pqnode->bgp != bgp + || pqnode->queued >= ARBITRARY_PROCESS_QLEN) + pqnode = bgp_processq_alloc(bgp); + else + pqnode_reuse = 1; + } else + pqnode = bgp_processq_alloc(bgp); + /* all unlocked in bgp_process_wq */ + bgp_table_lock(bgp_dest_table(dest)); + + SET_FLAG(dest->flags, BGP_NODE_PROCESS_SCHEDULED); + bgp_dest_lock_node(dest); + + /* can't be enqueued twice */ + assert(STAILQ_NEXT(dest, pq) == NULL); + STAILQ_INSERT_TAIL(&pqnode->pqueue, dest, pq); + pqnode->queued++; + + if (!pqnode_reuse) + work_queue_add(wq, pqnode); + + return; +} + +void bgp_add_eoiu_mark(struct bgp *bgp) +{ + struct bgp_process_queue *pqnode; + + if (bgp->process_queue == NULL) + return; + + pqnode = bgp_processq_alloc(bgp); + + SET_FLAG(pqnode->flags, BGP_PROCESS_QUEUE_EOIU_MARKER); + work_queue_add(bgp->process_queue, pqnode); +} + +static void bgp_maximum_prefix_restart_timer(struct event *thread) +{ + struct peer_connection *connection = EVENT_ARG(thread); + struct peer *peer = connection->peer; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%s Maximum-prefix restart timer expired, restore peering", + peer->host); + + if ((peer_clear(peer, NULL) < 0) && bgp_debug_neighbor_events(peer)) + zlog_debug("%s: %s peer_clear failed", __func__, peer->host); +} + +static uint32_t bgp_filtered_routes_count(struct peer *peer, afi_t afi, + safi_t safi) +{ + uint32_t count = 0; + bool filtered = false; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct attr attr = {}; + struct bgp_table *table = peer->bgp->rib[afi][safi]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (ain = dest->adj_in; ain; ain = ain->next) { + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + attr = *ain->attr; + + if (bgp_input_filter(peer, rn_p, &attr, afi, safi) + == FILTER_DENY) + filtered = true; + + if (bgp_input_modifier( + peer, rn_p, &attr, afi, safi, + ROUTE_MAP_IN_NAME(&peer->filter[afi][safi]), + NULL, 0, NULL) + == RMAP_DENY) + filtered = true; + + if (filtered) + count++; + + bgp_attr_flush(&attr); + } + } + + return count; +} + +bool bgp_maximum_prefix_overflow(struct peer *peer, afi_t afi, safi_t safi, + int always) +{ + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + uint32_t pcount = (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_FORCE)) + ? bgp_filtered_routes_count(peer, afi, safi) + + peer->pcount[afi][safi] + : peer->pcount[afi][safi]; + struct peer_connection *connection = peer->connection; + + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) + return false; + + if (pcount > peer->pmax[afi][safi]) { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_LIMIT) + && !always) + return false; + + zlog_info( + "%%MAXPFXEXCEED: No. of %s prefix received from %pBP %u exceed, limit %u", + get_afi_safi_str(afi, safi, false), peer, pcount, + peer->pmax[afi][safi]); + SET_FLAG(peer->af_sflags[afi][safi], PEER_STATUS_PREFIX_LIMIT); + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING)) + return false; + + /* Convert AFI, SAFI to values for packet. */ + pkt_afi = afi_int2iana(afi); + pkt_safi = safi_int2iana(safi); + { + uint8_t ndata[7]; + + ndata[0] = (pkt_afi >> 8); + ndata[1] = pkt_afi; + ndata[2] = pkt_safi; + ndata[3] = (peer->pmax[afi][safi] >> 24); + ndata[4] = (peer->pmax[afi][safi] >> 16); + ndata[5] = (peer->pmax[afi][safi] >> 8); + ndata[6] = (peer->pmax[afi][safi]); + + SET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); + bgp_notify_send_with_data(connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_MAX_PREFIX, + ndata, 7); + } + + /* Dynamic peers will just close their connection. */ + if (peer_dynamic_neighbor(peer)) + return true; + + /* restart timer start */ + if (peer->pmax_restart[afi][safi]) { + peer->v_pmax_restart = + peer->pmax_restart[afi][safi] * 60; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Maximum-prefix restart timer started for %d secs", + peer, peer->v_pmax_restart); + + BGP_TIMER_ON(connection->t_pmax_restart, + bgp_maximum_prefix_restart_timer, + peer->v_pmax_restart); + } + + return true; + } else + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_LIMIT); + + if (pcount + > (peer->pmax[afi][safi] * peer->pmax_threshold[afi][safi] / 100)) { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD) + && !always) + return false; + + zlog_info( + "%%MAXPFX: No. of %s prefix received from %pBP reaches %u, max %u", + get_afi_safi_str(afi, safi, false), peer, pcount, + peer->pmax[afi][safi]); + SET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD); + } else + UNSET_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_PREFIX_THRESHOLD); + return false; +} + +/* Unconditionally remove the route from the RIB, without taking + * damping into consideration (eg, because the session went down) + */ +void bgp_rib_remove(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *peer, afi_t afi, safi_t safi) +{ + + struct bgp *bgp = NULL; + bool delete_route = false; + + bgp_aggregate_decrement(peer->bgp, bgp_dest_get_prefix(dest), pi, afi, + safi); + + if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + bgp_path_info_delete(dest, pi); /* keep historical info */ + + /* If the selected path is removed, reset BGP_NODE_SELECT_DEFER + * flag + */ + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + delete_route = true; + else if (bgp_dest_set_defer_flag(dest, true) < 0) + delete_route = true; + if (delete_route) { + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + UNSET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + bgp = pi->peer->bgp; + bgp->gr_info[afi][safi].gr_deferred--; + } + } + } + + hook_call(bgp_process, peer->bgp, afi, safi, dest, peer, true); + bgp_process(peer->bgp, dest, pi, afi, safi); +} + +static void bgp_rib_withdraw(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *peer, afi_t afi, safi_t safi, + struct prefix_rd *prd) +{ + const struct prefix *p = bgp_dest_get_prefix(dest); + + /* apply dampening, if result is suppressed, we'll be retaining + * the bgp_path_info in the RIB for historical reference. + */ + if (peer->sort == BGP_PEER_EBGP) { + if (get_active_bdc_from_pi(pi, afi, safi)) { + if (bgp_damp_withdraw(pi, dest, afi, safi, 0) == + BGP_DAMP_SUPPRESSED) { + bgp_aggregate_decrement(peer->bgp, p, pi, afi, + safi); + return; + } + } + } + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(peer->bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + peer->bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } + if ((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST)) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + + vnc_import_bgp_del_route(peer->bgp, p, pi); + vnc_import_bgp_exterior_del_route(peer->bgp, p, pi); + } + } +#endif + + /* If this is an EVPN route, process for un-import. */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(peer->bgp, afi, safi, p, pi); + + bgp_rib_remove(dest, pi, peer, afi, safi); +} + +struct bgp_path_info *info_make(int type, int sub_type, unsigned short instance, + struct peer *peer, struct attr *attr, + struct bgp_dest *dest) +{ + struct bgp_path_info *new; + + /* Make new BGP info. */ + new = XCALLOC(MTYPE_BGP_ROUTE, sizeof(struct bgp_path_info)); + new->type = type; + new->instance = instance; + new->sub_type = sub_type; + new->peer = peer; + new->attr = attr; + new->uptime = monotime(NULL); + new->net = dest; + return new; +} + +/* Check if received nexthop is valid or not. */ +bool bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, + uint8_t type, uint8_t stype, struct attr *attr, + struct bgp_dest *dest) +{ + bool ret = false; + bool is_bgp_static_route = + (type == ZEBRA_ROUTE_BGP && stype == BGP_ROUTE_STATIC) ? true + : false; + + /* If `bgp allow-martian-nexthop` is turned on, return next-hop + * as good. + */ + if (bgp->allow_martian) + return false; + + /* + * Only validated for unicast and multicast currently. + * Also valid for EVPN where the nexthop is an IP address. + * If we are a bgp static route being checked then there is + * no need to check to see if the nexthop is martian as + * that it should be ok. + */ + if (is_bgp_static_route || + (safi != SAFI_UNICAST && safi != SAFI_MULTICAST && safi != SAFI_EVPN)) + return false; + + /* If NEXT_HOP is present, validate it. */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) { + if (attr->nexthop.s_addr == INADDR_ANY || + !ipv4_unicast_valid(&attr->nexthop) || + bgp_nexthop_self(bgp, afi, type, stype, attr, dest)) + return true; + } + + /* If MP_NEXTHOP is present, validate it. */ + /* Note: For IPv6 nexthops, we only validate the global (1st) nexthop; + * there is code in bgp_attr.c to ignore the link-local (2nd) nexthop if + * it is not an IPv6 link-local address. + * + * If we receive an UPDATE with nexthop length set to 32 bytes + * we shouldn't discard an UPDATE if it's set to (::). + * The link-local (2st) is validated along the code path later. + */ + if (attr->mp_nexthop_len) { + switch (attr->mp_nexthop_len) { + case BGP_ATTR_NHLEN_IPV4: + case BGP_ATTR_NHLEN_VPNV4: + ret = (attr->mp_nexthop_global_in.s_addr == + INADDR_ANY || + !ipv4_unicast_valid( + &attr->mp_nexthop_global_in) || + bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + case BGP_ATTR_NHLEN_VPNV6_GLOBAL: + ret = (IN6_IS_ADDR_UNSPECIFIED( + &attr->mp_nexthop_global) + || IN6_IS_ADDR_LOOPBACK(&attr->mp_nexthop_global) + || IN6_IS_ADDR_MULTICAST( + &attr->mp_nexthop_global) + || bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + ret = (IN6_IS_ADDR_LOOPBACK(&attr->mp_nexthop_global) + || IN6_IS_ADDR_MULTICAST( + &attr->mp_nexthop_global) + || bgp_nexthop_self(bgp, afi, type, stype, attr, + dest)); + break; + + default: + ret = true; + break; + } + } + + return ret; +} + +static void bgp_attr_add_no_export_community(struct attr *attr) +{ + struct community *old; + struct community *new; + struct community *merge; + struct community *no_export; + + old = bgp_attr_get_community(attr); + no_export = community_str2com("no-export"); + + assert(no_export); + + if (old) { + merge = community_merge(community_dup(old), no_export); + + if (!old->refcnt) + community_free(&old); + + new = community_uniq_sort(merge); + community_free(&merge); + } else { + new = community_dup(no_export); + } + + community_free(&no_export); + + bgp_attr_set_community(attr, new); +} + +static bool bgp_accept_own(struct peer *peer, afi_t afi, safi_t safi, + struct attr *attr, const struct prefix *prefix, + int *sub_type) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + bool accept_own_found = false; + + if (safi != SAFI_MPLS_VPN) + return false; + + /* Processing of the ACCEPT_OWN community is enabled by configuration */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ACCEPT_OWN)) + return false; + + /* The route in question carries the ACCEPT_OWN community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + struct community *comm = bgp_attr_get_community(attr); + + if (community_include(comm, COMMUNITY_ACCEPT_OWN)) + accept_own_found = true; + } + + /* The route in question is targeted to one or more destination VRFs + * on the router (as determined by inspecting the Route Target(s)). + */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (accept_own_found && + ecommunity_include( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN], + bgp_attr_get_ecommunity(attr))) { + if (bgp_debug_update(peer, prefix, NULL, 1)) + zlog_debug( + "%pBP prefix %pFX has ORIGINATOR_ID, but it's accepted due to ACCEPT_OWN", + peer, prefix); + + /* Treat this route as imported, because it's leaked + * already from another VRF, and we got an updated + * version from route-reflector with ACCEPT_OWN + * community. + */ + *sub_type = BGP_ROUTE_IMPORTED; + + return true; + } + } + + return false; +} + +void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, + struct attr *attr, afi_t afi, safi_t safi, int type, + int sub_type, struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, int soft_reconfig, + struct bgp_route_evpn *evpn) +{ + int ret; + int aspath_loop_count = 0; + struct bgp_dest *dest; + struct bgp *bgp; + struct attr new_attr = {}; + struct attr *attr_new; + struct bgp_path_info *pi; + struct bgp_path_info *new = NULL; + const char *reason; + char pfx_buf[BGP_PRD_PATH_STRLEN]; + int connected = 0; + int do_loop_check = 1; + afi_t nh_afi; + bool force_evpn_import = false; + safi_t orig_safi = safi; + int allowas_in = 0; + struct bgp_labels bgp_labels = {}; + uint8_t i; + + if (frrtrace_enabled(frr_bgp, process_update)) { + char pfxprint[PREFIX2STR_BUFFER]; + + prefix2str(p, pfxprint, sizeof(pfxprint)); + frrtrace(6, frr_bgp, process_update, peer, pfxprint, addpath_id, + afi, safi, attr); + } + +#ifdef ENABLE_BGP_VNC + int vnc_implicit_withdraw = 0; +#endif + int same_attr = 0; + const struct prefix *bgp_nht_param_prefix; + + /* Special case for BGP-LU - map LU safi to ordinary unicast safi */ + if (orig_safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + bgp = peer->bgp; + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + if ((afi == AFI_L2VPN && safi == SAFI_EVPN) || + bgp_is_valid_label(&label[0])) + bgp_labels.num_labels = num_labels; + for (i = 0; i < bgp_labels.num_labels; i++) + bgp_labels.label[i] = label[i]; + + /* When peer's soft reconfiguration enabled. Record input packet in + Adj-RIBs-In. */ + if (!soft_reconfig && + CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) && + peer != bgp->peer_self) { + /* + * If the trigger is not from soft_reconfig and if + * PEER_FLAG_SOFT_RECONFIG is enabled for the peer, then attr + * will not be interned. In which case, it is ok to update the + * attr->evpn_overlay, so that, this can be stored in adj_in. + */ + if ((afi == AFI_L2VPN) && evpn) { + memcpy(&attr->evpn_overlay, evpn, + sizeof(struct bgp_route_evpn)); + } + bgp_adj_in_set(dest, peer, attr, addpath_id, &bgp_labels); + } + + /* Update permitted loop count */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) + allowas_in = peer->allowas_in[afi][safi]; + + /* Check previously received route. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer && pi->type == type + && pi->sub_type == sub_type + && pi->addpath_rx_id == addpath_id) + break; + + /* AS path local-as loop check. */ + if (peer->change_local_as) { + if (allowas_in) + aspath_loop_count = allowas_in; + else if (!CHECK_FLAG(peer->flags, + PEER_FLAG_LOCAL_AS_NO_PREPEND)) + aspath_loop_count = 1; + + if (aspath_loop_check(attr->aspath, peer->change_local_as) + > aspath_loop_count) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own AS;"; + goto filtered; + } + } + + /* If the peer is configured for "allowas-in origin" and the last ASN in + * the + * as-path is our ASN then we do not need to call aspath_loop_check + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN_ORIGIN)) + if (aspath_get_last_as(attr->aspath) == bgp->as) + do_loop_check = 0; + + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) + bgp_nht_param_prefix = NULL; + else + bgp_nht_param_prefix = p; + + /* AS path loop check. */ + if (do_loop_check) { + if (aspath_loop_check(attr->aspath, bgp->as) > + peer->allowas_in[afi][safi]) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own AS;"; + goto filtered; + } + } + + /* If we're a CONFED we need to loop check the CONFED ID too */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) && do_loop_check) + if (aspath_loop_check_confed(attr->aspath, bgp->confed_id) > + peer->allowas_in[afi][safi]) { + peer->stat_pfx_aspath_loop++; + reason = "as-path contains our own confed AS;"; + goto filtered; + } + + /* Route reflector originator ID check. If ACCEPT_OWN mechanism is + * enabled, then take care of that too. + */ + bool accept_own = false; + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID) + && IPV4_ADDR_SAME(&bgp->router_id, &attr->originator_id)) { + accept_own = + bgp_accept_own(peer, afi, safi, attr, p, &sub_type); + if (!accept_own) { + peer->stat_pfx_originator_loop++; + reason = "originator is us;"; + goto filtered; + } + } + + /* Route reflector cluster ID check. */ + if (bgp_cluster_filter(peer, attr)) { + peer->stat_pfx_cluster_loop++; + reason = "reflected from the same cluster;"; + goto filtered; + } + + /* Apply incoming filter. */ + if (bgp_input_filter(peer, p, attr, afi, orig_safi) == FILTER_DENY) { + peer->stat_pfx_filter++; + reason = "filter;"; + goto filtered; + } + + if ((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_MPLS_VPN && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT && + !CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL) && + vpn_leak_to_vrf_no_retain_filter_check(bgp, attr, afi)) { + reason = + "no import. Filtered by no bgp retain route-target all"; + goto filtered; + } + + /* If the route has Node Target Extended Communities, check + * if it's allowed to be installed locally. + */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + if (ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_IP, + ECOMMUNITY_NODE_TARGET) && + !ecommunity_node_target_match(ecomm, &peer->local_id)) { + reason = + "Node-Target Extended Communities do not contain own BGP Identifier;"; + goto filtered; + } + } + + /* RFC 8212 to prevent route leaks. + * This specification intends to improve this situation by requiring the + * explicit configuration of both BGP Import and Export Policies for any + * External BGP (EBGP) session such as customers, peers, or + * confederation boundaries for all enabled address families. Through + * codification of the aforementioned requirement, operators will + * benefit from consistent behavior across different BGP + * implementations. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY)) + if (!bgp_inbound_policy_exists(peer, + &peer->filter[afi][safi])) { + reason = "inbound policy missing"; + if (monotime_since(&bgp->ebgprequirespolicywarning, + NULL) > FIFTEENMINUTE2USEC || + bgp->ebgprequirespolicywarning.tv_sec == 0) { + zlog_warn( + "EBGP inbound/outbound policy not properly setup, please configure in order for your peering to work correctly"); + monotime(&bgp->ebgprequirespolicywarning); + } + goto filtered; + } + + /* draft-ietf-idr-deprecate-as-set-confed-set + * Filter routes having AS_SET or AS_CONFED_SET in the path. + * Eventually, This document (if approved) updates RFC 4271 + * and RFC 5065 by eliminating AS_SET and AS_CONFED_SET types, + * and obsoletes RFC 6472. + */ + if (peer->bgp->reject_as_sets) + if (aspath_check_as_sets(attr->aspath)) { + reason = + "as-path contains AS_SET or AS_CONFED_SET type;"; + goto filtered; + } + + new_attr = *attr; + /* + * If bgp_update is called with soft_reconfig set then + * attr is interned. In this case, do not overwrite the + * attr->evpn_overlay with evpn directly. Instead memcpy + * evpn to new_atr.evpn_overlay before it is interned. + */ + if (soft_reconfig && (afi == AFI_L2VPN) && evpn) + memcpy(&new_attr.evpn_overlay, evpn, + sizeof(struct bgp_route_evpn)); + + /* Apply incoming route-map. + * NB: new_attr may now contain newly allocated values from route-map + * "set" + * commands, so we need bgp_attr_flush in the error paths, until we + * intern + * the attr (which takes over the memory references) */ + if (bgp_input_modifier(peer, p, &new_attr, afi, orig_safi, NULL, label, + num_labels, dest) + == RMAP_DENY) { + peer->stat_pfx_filter++; + reason = "route-map;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (pi && pi->attr->rmap_table_id != new_attr.rmap_table_id) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + /* remove from RIB previous entry */ + bgp_zebra_route_install(dest, pi, bgp, false, NULL, + false); + } + + if (peer->sort == BGP_PEER_EBGP) { + + /* rfc7999: + * A BGP speaker receiving an announcement tagged with the + * BLACKHOLE community SHOULD add the NO_ADVERTISE or + * NO_EXPORT community as defined in RFC1997, or a + * similar community, to prevent propagation of the + * prefix outside the local AS. The community to prevent + * propagation SHOULD be chosen according to the operator's + * routing policy. + */ + if (bgp_attr_get_community(&new_attr) && + community_include(bgp_attr_get_community(&new_attr), + COMMUNITY_BLACKHOLE)) + bgp_attr_add_no_export_community(&new_attr); + + /* If we receive the graceful-shutdown community from an eBGP + * peer we must lower local-preference */ + if (bgp_attr_get_community(&new_attr) && + community_include(bgp_attr_get_community(&new_attr), + COMMUNITY_GSHUT)) { + new_attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + new_attr.local_pref = BGP_GSHUT_LOCAL_PREF; + + /* If graceful-shutdown is configured globally or + * per neighbor, then add the GSHUT community to + * all paths received from eBGP peers. */ + } else if (bgp_in_graceful_shutdown(peer->bgp) || + CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_SHUTDOWN)) + bgp_attr_add_gshut_community(&new_attr); + } + + /* next hop check. */ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD) && + bgp_update_martian_nexthop(bgp, afi, safi, type, sub_type, + &new_attr, dest)) { + peer->stat_pfx_nh_invalid++; + reason = "martian or self next-hop;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (bgp_mac_entry_exists(p) || bgp_mac_exist(&attr->rmac)) { + peer->stat_pfx_nh_invalid++; + reason = "self mac;"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + if (bgp_check_role_applicability(afi, safi) && + bgp_otc_filter(peer, &new_attr)) { + reason = "failing otc validation"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + /* If neighbor soo is configured, tag all incoming routes with + * this SoO tag and then filter out advertisements in + * subgroup_announce_check() if it matches the configured SoO + * on the other peer. + */ + if (peer->soo[afi][safi]) { + struct ecommunity *old_ecomm = + bgp_attr_get_ecommunity(&new_attr); + struct ecommunity *ecomm_soo = peer->soo[afi][safi]; + struct ecommunity *new_ecomm; + + if (old_ecomm) { + new_ecomm = ecommunity_merge(ecommunity_dup(old_ecomm), + ecomm_soo); + + if (!old_ecomm->refcnt) + ecommunity_free(&old_ecomm); + } else { + new_ecomm = ecommunity_dup(ecomm_soo); + } + + bgp_attr_set_ecommunity(&new_attr, new_ecomm); + } + + attr_new = bgp_attr_intern(&new_attr); + + /* If the update is implicit withdraw. */ + if (pi) { + pi->uptime = monotime(NULL); + same_attr = attrhash_cmp(pi->attr, attr_new); + + hook_call(bgp_process, bgp, afi, safi, dest, peer, true); + + /* Same attribute comes in. */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) && same_attr && + (!bgp_labels.num_labels || + bgp_path_info_labels_same(pi, bgp_labels.label, + bgp_labels.num_labels))) { + if (get_active_bdc_from_pi(pi, afi, safi) && + peer->sort == BGP_PEER_EBGP && + CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, + pfx_buf); + } + + if (bgp_damp_update(pi, dest, afi, safi) + != BGP_DAMP_SUPPRESSED) { + bgp_aggregate_increment(bgp, p, pi, afi, + safi); + bgp_process(bgp, dest, pi, afi, safi); + } + } else /* Duplicate - odd */ + { + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug( + "%pBP rcvd UPDATE w/ attr: %s", + peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug( + "%pBP rcvd %s...duplicate ignored", + peer, pfx_buf); + } + + /* graceful restart STALE flag unset. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) { + bgp_path_info_unset_flag( + dest, pi, BGP_PATH_STALE); + bgp_dest_set_defer_flag(dest, false); + bgp_process(bgp, dest, pi, afi, safi); + } + } + + bgp_dest_unlock_node(dest); + bgp_attr_unintern(&attr_new); + + return; + } + + /* Withdraw/Announce before we fully processed the withdraw */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) { + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug( + "%pBP rcvd %s, flapped quicker than processing", + peer, pfx_buf); + } + + bgp_path_info_restore(dest, pi); + + /* + * If the BGP_PATH_REMOVED flag is set, then EVPN + * routes would have been unimported already when a + * prior BGP withdraw processing happened. Such routes + * need to be imported again, so flag accordingly. + */ + force_evpn_import = true; + } else { + /* implicit withdraw, decrement aggregate and pcount + * here. only if update is accepted, they'll increment + * below. + */ + bgp_aggregate_decrement(bgp, p, pi, afi, safi); + } + + /* Received Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, + num_labels, addpath_id ? 1 : 0, + addpath_id, evpn, pfx_buf, + sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, pfx_buf); + } + + /* graceful restart STALE flag unset. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) { + bgp_path_info_unset_flag(dest, pi, BGP_PATH_STALE); + bgp_dest_set_defer_flag(dest, false); + } + + /* The attribute is changed. */ + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + + /* Update bgp route dampening information. */ + if (get_active_bdc_from_pi(pi, afi, safi) && + peer->sort == BGP_PEER_EBGP) { + /* This is implicit withdraw so we should update + * dampening information. + */ + if (!CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + bgp_damp_withdraw(pi, dest, afi, safi, 1); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } + if ((afi == AFI_IP || afi == AFI_IP6) + && (safi == SAFI_UNICAST)) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + /* + * Implicit withdraw case. + */ + ++vnc_implicit_withdraw; + vnc_import_bgp_del_route(bgp, p, pi); + vnc_import_bgp_exterior_del_route(bgp, p, pi); + } + } +#endif + + /* Special handling for EVPN update of an existing route. If the + * extended community attribute has changed, we need to + * un-import + * the route using its existing extended community. It will be + * subsequently processed for import with the new extended + * community. + */ + if (((safi == SAFI_EVPN) || (safi == SAFI_MPLS_VPN)) + && !same_attr) { + if ((pi->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) + && (attr_new->flag + & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES))) { + int cmp; + + cmp = ecommunity_cmp( + bgp_attr_get_ecommunity(pi->attr), + bgp_attr_get_ecommunity(attr_new)); + if (!cmp) { + if (bgp_debug_update(peer, p, NULL, 1)) + zlog_debug( + "Change in EXT-COMM, existing %s new %s", + ecommunity_str( + bgp_attr_get_ecommunity( + pi->attr)), + ecommunity_str( + bgp_attr_get_ecommunity( + attr_new))); + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route( + bgp, afi, safi, p, pi); + else /* SAFI_MPLS_VPN */ + vpn_leak_to_vrf_withdraw(pi); + } + } + } + + /* Update to new attribute. */ + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + + /* Update MPLS label */ + if (!bgp_path_info_labels_same(pi, &bgp_labels.label[0], + bgp_labels.num_labels)) { + bgp_path_info_extra_get(pi); + bgp_labels_unintern(&pi->extra->labels); + pi->extra->labels = bgp_labels_intern(&bgp_labels); + } + +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) + && (safi == SAFI_UNICAST)) { + if (vnc_implicit_withdraw) { + /* + * Add back the route with its new attributes + * (e.g., nexthop). + * The route is still selected, until the route + * selection + * queued by bgp_process actually runs. We have + * to make this + * update to the VNC side immediately to avoid + * racing against + * configuration changes (e.g., route-map + * changes) which + * trigger re-importation of the entire RIB. + */ + vnc_import_bgp_add_route(bgp, p, pi); + vnc_import_bgp_exterior_add_route(bgp, p, pi); + } + } +#endif + + /* Update bgp route dampening information. */ + if (get_active_bdc_from_pi(pi, afi, safi) && + peer->sort == BGP_PEER_EBGP) { + /* Now we do normal update dampening. */ + ret = bgp_damp_update(pi, dest, afi, safi); + if (ret == BGP_DAMP_SUPPRESSED) { + bgp_dest_unlock_node(dest); + return; + } + } + + /* Nexthop reachability check - for unicast and + * labeled-unicast.. */ + if (((afi == AFI_IP || afi == AFI_IP6) && + (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || + (safi == SAFI_MPLS_VPN && + pi->sub_type != BGP_ROUTE_IMPORTED))) || + (safi == SAFI_EVPN && + bgp_evpn_is_prefix_nht_supported(p))) { + if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP + && peer->ttl == BGP_DEFAULT_TTL + && !CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_CONNECTED_CHECK) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + connected = 1; + else + connected = 0; + + struct bgp *bgp_nexthop = bgp; + + if (pi->extra && pi->extra->vrfleak && + pi->extra->vrfleak->bgp_orig) + bgp_nexthop = pi->extra->vrfleak->bgp_orig; + + nh_afi = BGP_ATTR_NH_AFI(afi, pi->attr); + + if (bgp_find_or_add_nexthop(bgp, bgp_nexthop, nh_afi, + safi, pi, NULL, connected, + bgp_nht_param_prefix) || + CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) { + if (accept_own) + bgp_path_info_set_flag( + dest, pi, BGP_PATH_ACCEPT_OWN); + + bgp_path_info_set_flag(dest, pi, + BGP_PATH_VALID); + } else { + if (BGP_DEBUG(nht, NHT)) { + zlog_debug("%s(%pI4): NH unresolved for existing %pFX pi %p flags 0x%x", + __func__, + (in_addr_t *)&attr_new->nexthop, + p, pi, pi->flags); + } + bgp_path_info_unset_flag(dest, pi, + BGP_PATH_VALID); + } + } else { + /* case mpls-vpn routes with accept-own community + * (which have the BGP_ROUTE_IMPORTED subtype) + * case other afi/safi not supporting nexthop tracking + */ + if (accept_own) + bgp_path_info_set_flag(dest, pi, + BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, pi, BGP_PATH_VALID); + } + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, pi); + } + bgp_dest_unlock_node(pdest); + } +#endif + + /* If this is an EVPN route and some attribute has changed, + * or we are explicitly told to perform a route import, process + * route for import. If the extended community has changed, we + * would + * have done the un-import earlier and the import would result + * in the + * route getting injected into appropriate L2 VNIs. If it is + * just + * some other attribute change, the import will result in + * updating + * the attributes for the route in the VNI(s). + */ + if (safi == SAFI_EVPN) { + if ((!same_attr || force_evpn_import) && + CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + bgp_evpn_import_route(bgp, afi, safi, p, pi); + + /* If existing path is marked invalid then unimport the + * path from EVPN prefix. This will ensure EVPN route + * has only valid paths and path refcount maintained in + * EVPN nexthop is decremented appropriately. + */ + else if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) { + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s unimport EVPN %pFX as pi %p is not VALID", + __func__, p, pi); + bgp_evpn_unimport_route(bgp, afi, safi, p, pi); + } + } + + /* Process change. */ + bgp_aggregate_increment(bgp, p, pi, afi, safi); + + bgp_process(bgp, dest, pi, afi, safi); + bgp_dest_unlock_node(dest); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_to_vrf_update(bgp, pi, prd); + } + +#ifdef ENABLE_BGP_VNC + if (SAFI_MPLS_VPN == safi) { + mpls_label_t label_decoded = decode_label(label); + + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, + type, sub_type, &label_decoded); + } + if (SAFI_ENCAP == safi) { + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, + type, sub_type, NULL); + } +#endif + return; + } // End of implicit withdraw + + /* Received Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug("%pBP rcvd UPDATE w/ attr: %s", peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd %s", peer, pfx_buf); + } + + /* Make new BGP info. */ + new = info_make(type, sub_type, 0, peer, attr_new, dest); + + /* Update MPLS label */ + bgp_path_info_extra_get(new); + new->extra->labels = bgp_labels_intern(&bgp_labels); + + /* Nexthop reachability check. */ + if (((afi == AFI_IP || afi == AFI_IP6) && + (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || + (safi == SAFI_MPLS_VPN && + new->sub_type != BGP_ROUTE_IMPORTED))) || + (safi == SAFI_EVPN && bgp_evpn_is_prefix_nht_supported(p))) { + if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP + && peer->ttl == BGP_DEFAULT_TTL + && !CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_CONNECTED_CHECK) + && !CHECK_FLAG(bgp->flags, + BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + connected = 1; + else + connected = 0; + + nh_afi = BGP_ATTR_NH_AFI(afi, new->attr); + + if (bgp_find_or_add_nexthop(bgp, bgp, nh_afi, safi, new, NULL, + connected, bgp_nht_param_prefix) || + CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) { + if (accept_own) + bgp_path_info_set_flag(dest, new, + BGP_PATH_ACCEPT_OWN); + + bgp_path_info_set_flag(dest, new, BGP_PATH_VALID); + } else { + if (BGP_DEBUG(nht, NHT)) + zlog_debug("%s(%pI4): NH unresolved", __func__, + &attr_new->nexthop); + bgp_path_info_unset_flag(dest, new, BGP_PATH_VALID); + } + } else { + /* case mpls-vpn routes with accept-own community + * (which have the BGP_ROUTE_IMPORTED subtype) + * case other afi/safi not supporting nexthop tracking + */ + if (accept_own) + bgp_path_info_set_flag(dest, new, BGP_PATH_ACCEPT_OWN); + bgp_path_info_set_flag(dest, new, BGP_PATH_VALID); + } + + /* If maximum prefix count is configured and current prefix + * count exeed it. + */ + if (bgp_maximum_prefix_overflow(peer, afi, safi, 0)) { + reason = "maximum-prefix overflow"; + bgp_attr_flush(&new_attr); + goto filtered; + } + + /* Addpath ID */ + new->addpath_rx_id = addpath_id; + + /* Increment prefix */ + bgp_aggregate_increment(bgp, p, new, afi, safi); + + /* Register new BGP information. */ + bgp_path_info_add(dest, new); + + /* route_node_get lock */ + bgp_dest_unlock_node(dest); + +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); + if (bgp_dest_has_bgp_path_info_data(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, new); + } + bgp_dest_unlock_node(pdest); + } +#endif + + /* If this is an EVPN route, process for import. */ + if (safi == SAFI_EVPN && CHECK_FLAG(new->flags, BGP_PATH_VALID)) + bgp_evpn_import_route(bgp, afi, safi, p, new); + + hook_call(bgp_process, bgp, afi, safi, dest, peer, false); + + /* Process change. */ + bgp_process(bgp, dest, new, afi, safi); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_to_vrf_update(bgp, new, prd); + } +#ifdef ENABLE_BGP_VNC + if (SAFI_MPLS_VPN == safi) { + mpls_label_t label_decoded = decode_label(label); + + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, + sub_type, &label_decoded); + } + if (SAFI_ENCAP == safi) { + rfapiProcessUpdate(peer, NULL, p, prd, attr, afi, safi, type, + sub_type, NULL); + } +#endif + + return; + +/* This BGP update is filtered. Log the reason then update BGP + entry. */ +filtered: + if (new) { + bgp_unlink_nexthop(new); + bgp_path_info_delete(dest, new); + bgp_path_info_extra_free(&new->extra); + XFREE(MTYPE_BGP_ROUTE, new); + } + + hook_call(bgp_process, bgp, afi, safi, dest, peer, true); + + if (bgp_debug_update(peer, p, NULL, 1)) { + if (!peer->rcvd_attr_printed) { + zlog_debug("%pBP rcvd UPDATE w/ attr: %s", peer, + peer->rcvd_attr_str); + peer->rcvd_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, evpn, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd UPDATE about %s -- DENIED due to: %s", + peer, pfx_buf, reason); + } + + if (pi) { + /* If this is an EVPN route, un-import it as it is now filtered. + */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(bgp, afi, safi, p, pi); + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_to_vrf_withdraw(pi); + } + + bgp_rib_remove(dest, pi, peer, afi, safi); + } + + bgp_dest_unlock_node(dest); + +#ifdef ENABLE_BGP_VNC + /* + * Filtered update is treated as an implicit withdrawal (see + * bgp_rib_remove() + * a few lines above) + */ + if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { + rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, + 0); + } +#endif + + return; +} + +void bgp_withdraw(struct peer *peer, const struct prefix *p, + uint32_t addpath_id, afi_t afi, safi_t safi, int type, + int sub_type, struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, struct bgp_route_evpn *evpn) +{ + struct bgp *bgp; + char pfx_buf[BGP_PRD_PATH_STRLEN]; + struct bgp_dest *dest; + struct bgp_path_info *pi; + +#ifdef ENABLE_BGP_VNC + if ((SAFI_MPLS_VPN == safi) || (SAFI_ENCAP == safi)) { + rfapiProcessWithdraw(peer, NULL, p, prd, NULL, afi, safi, type, + 0); + } +#endif + + bgp = peer->bgp; + + /* Lookup node. */ + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + /* If peer is soft reconfiguration enabled. Record input packet for + * further calculation. + * + * Cisco IOS 12.4(24)T4 on session establishment sends withdraws for all + * routes that are filtered. This tanks out Quagga RS pretty badly due + * to + * the iteration over all RS clients. + * Since we need to remove the entry from adj_in anyway, do that first + * and + * if there was no entry, we don't need to do anything more. + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG) + && peer != bgp->peer_self) + if (!bgp_adj_in_unset(&dest, peer, addpath_id)) { + assert(dest); + peer->stat_pfx_dup_withdraw++; + + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str( + afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug( + "%s withdrawing route %s not in adj-in", + peer->host, pfx_buf); + } + bgp_dest_unlock_node(dest); + return; + } + + /* Lookup withdrawn route. */ + assert(dest); + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer && pi->type == type + && pi->sub_type == sub_type + && pi->addpath_rx_id == addpath_id) + break; + + /* Logging. */ + if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%pBP rcvd UPDATE about %s -- withdrawn", peer, + pfx_buf); + } + + /* Withdraw specified route from routing table. */ + if (pi && !CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) { + bgp_rib_withdraw(dest, pi, peer, afi, safi, prd); + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if ((SAFI_MPLS_VPN == safi) + && (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_to_vrf_withdraw(pi); + } + } else if (bgp_debug_update(peer, p, NULL, 1)) { + bgp_debug_rdpfxpath2str(afi, safi, prd, p, label, num_labels, + addpath_id ? 1 : 0, addpath_id, NULL, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("%s Can't find the route %s", peer->host, pfx_buf); + } + + /* Unlock bgp_node_get() lock. */ + bgp_dest_unlock_node(dest); + + return; +} + +void bgp_default_originate(struct peer *peer, afi_t afi, safi_t safi, + bool withdraw) +{ + struct update_subgroup *subgrp; + subgrp = peer_subgroup(peer, afi, safi); + subgroup_default_originate(subgrp, withdraw); +} + + +/* + * bgp_stop_announce_route_timer + */ +void bgp_stop_announce_route_timer(struct peer_af *paf) +{ + if (!paf->t_announce_route) + return; + + EVENT_OFF(paf->t_announce_route); +} + +/* + * bgp_announce_route_timer_expired + * + * Callback that is invoked when the route announcement timer for a + * peer_af expires. + */ +static void bgp_announce_route_timer_expired(struct event *t) +{ + struct peer_af *paf; + struct peer *peer; + + paf = EVENT_ARG(t); + peer = paf->peer; + + if (!peer_established(peer->connection)) + return; + + if (!peer->afc_nego[paf->afi][paf->safi]) + return; + + peer_af_announce_route(paf, 1); + + /* Notify BGP conditional advertisement scanner percess */ + peer->advmap_config_change[paf->afi][paf->safi] = true; +} + +/* + * bgp_announce_route + * + * *Triggers* announcement of routes of a given AFI/SAFI to a peer. + * + * if force is true we will force an update even if the update + * limiting code is attempted to kick in. + */ +void bgp_announce_route(struct peer *peer, afi_t afi, safi_t safi, bool force) +{ + struct peer_af *paf; + struct update_subgroup *subgrp; + + paf = peer_af_find(peer, afi, safi); + if (!paf) + return; + subgrp = PAF_SUBGRP(paf); + + /* + * Ignore if subgroup doesn't exist (implies AF is not negotiated) + * or a refresh has already been triggered. + */ + if (!subgrp || paf->t_announce_route) + return; + + if (force) + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + + /* + * Start a timer to stagger/delay the announce. This serves + * two purposes - announcement can potentially be combined for + * multiple peers and the announcement doesn't happen in the + * vty context. + */ + event_add_timer_msec(bm->master, bgp_announce_route_timer_expired, paf, + (subgrp->peer_count == 1) + ? BGP_ANNOUNCE_ROUTE_SHORT_DELAY_MS + : BGP_ANNOUNCE_ROUTE_DELAY_MS, + &paf->t_announce_route); +} + +/* + * Announce routes from all AF tables to a peer. + * + * This should ONLY be called when there is a need to refresh the + * routes to the peer based on a policy change for this peer alone + * or a route refresh request received from the peer. + * The operation will result in splitting the peer from its existing + * subgroups and putting it in new subgroups. + */ +void bgp_announce_route_all(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_announce_route(peer, afi, safi, false); +} + +/* Flag or unflag bgp_dest to determine whether it should be treated by + * bgp_soft_reconfig_table_task. + * Flag if flag is true. Unflag if flag is false. + */ +static void bgp_soft_reconfig_table_flag(struct bgp_table *table, bool flag) +{ + struct bgp_dest *dest; + struct bgp_adj_in *ain; + + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != NULL) + break; + } + if (flag && ain != NULL && ain->peer != NULL) + SET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + else + UNSET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + } +} + +static void bgp_soft_reconfig_table_update(struct peer *peer, + struct bgp_dest *dest, + struct bgp_adj_in *ain, afi_t afi, + safi_t safi, struct prefix_rd *prd) +{ + struct bgp_path_info *pi; + uint8_t num_labels; + mpls_label_t *label_pnt; + struct bgp_route_evpn evpn; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == peer) + break; + + num_labels = ain->labels ? ain->labels->num_labels : 0; + label_pnt = num_labels ? &ain->labels->label[0] : NULL; + if (pi) + memcpy(&evpn, bgp_attr_get_evpn_overlay(pi->attr), + sizeof(evpn)); + else + memset(&evpn, 0, sizeof(evpn)); + + bgp_update(peer, bgp_dest_get_prefix(dest), ain->addpath_rx_id, + ain->attr, afi, safi, ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, prd, + label_pnt, num_labels, 1, &evpn); +} + +static void bgp_soft_reconfig_table(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_table *table, + struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_adj_in *ain; + + if (!table) + table = peer->bgp->rib[afi][safi]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != peer) + continue; + + bgp_soft_reconfig_table_update(peer, dest, ain, afi, + safi, prd); + } +} + +/* Do soft reconfig table per bgp table. + * Walk on SOFT_RECONFIG_TASK_MAX_PREFIX bgp_dest, + * when BGP_NODE_SOFT_RECONFIG is set, + * reconfig bgp_dest for list of table->soft_reconfig_peers peers. + * Schedule a new thread to continue the job. + * Without splitting the full job into several part, + * vtysh waits for the job to finish before responding to a BGP command + */ +static void bgp_soft_reconfig_table_task(struct event *thread) +{ + uint32_t iter, max_iter; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct peer *peer; + struct bgp_table *table; + struct prefix_rd *prd; + struct listnode *node, *nnode; + + table = EVENT_ARG(thread); + prd = NULL; + + max_iter = SOFT_RECONFIG_TASK_MAX_PREFIX; + if (table->soft_reconfig_init) { + /* first call of the function with a new srta structure. + * Don't do any treatment this time on nodes + * in order vtysh to respond quickly + */ + max_iter = 0; + } + + for (iter = 0, dest = bgp_table_top(table); (dest && iter < max_iter); + dest = bgp_route_next(dest)) { + if (!CHECK_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG)) + continue; + + UNSET_FLAG(dest->flags, BGP_NODE_SOFT_RECONFIG); + + for (ain = dest->adj_in; ain; ain = ain->next) { + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, + nnode, peer)) { + if (ain->peer != peer) + continue; + + bgp_soft_reconfig_table_update( + peer, dest, ain, table->afi, + table->safi, prd); + iter++; + } + } + } + + /* we're either starting the initial iteration, + * or we're going to continue an ongoing iteration + */ + if (dest || table->soft_reconfig_init) { + table->soft_reconfig_init = false; + event_add_event(bm->master, bgp_soft_reconfig_table_task, table, + 0, &table->soft_reconfig_thread); + return; + } + /* we're done, clean up the background iteration context info and + schedule route annoucement + */ + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, nnode, peer)) { + listnode_delete(table->soft_reconfig_peers, peer); + bgp_announce_route(peer, table->afi, table->safi, false); + } + + list_delete(&table->soft_reconfig_peers); +} + + +/* Cancel soft_reconfig_table task matching bgp instance, bgp_table + * and peer. + * - bgp cannot be NULL + * - if table and peer are NULL, cancel all threads within the bgp instance + * - if table is NULL and peer is not, + * remove peer in all threads within the bgp instance + * - if peer is NULL, cancel all threads matching table within the bgp instance + */ +void bgp_soft_reconfig_table_task_cancel(const struct bgp *bgp, + const struct bgp_table *table, + const struct peer *peer) +{ + struct peer *npeer; + struct listnode *node, *nnode; + int afi, safi; + struct bgp_table *ntable; + + if (!bgp) + return; + + FOREACH_AFI_SAFI (afi, safi) { + ntable = bgp->rib[afi][safi]; + if (!ntable) + continue; + if (table && table != ntable) + continue; + + for (ALL_LIST_ELEMENTS(ntable->soft_reconfig_peers, node, nnode, + npeer)) { + if (peer && peer != npeer) + continue; + listnode_delete(ntable->soft_reconfig_peers, npeer); + } + + if (!ntable->soft_reconfig_peers + || !list_isempty(ntable->soft_reconfig_peers)) + continue; + + list_delete(&ntable->soft_reconfig_peers); + bgp_soft_reconfig_table_flag(ntable, false); + EVENT_OFF(ntable->soft_reconfig_thread); + } +} + +/* + * Returns false if the peer is not configured for soft reconfig in + */ +bool bgp_soft_reconfig_in(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct listnode *node, *nnode; + struct peer *npeer; + struct peer_af *paf; + + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) + return false; + + if ((safi != SAFI_MPLS_VPN) && (safi != SAFI_ENCAP) + && (safi != SAFI_EVPN)) { + table = peer->bgp->rib[afi][safi]; + if (!table) + return true; + + table->soft_reconfig_init = true; + + if (!table->soft_reconfig_peers) + table->soft_reconfig_peers = list_new(); + npeer = NULL; + /* add peer to the table soft_reconfig_peers if not already + * there + */ + for (ALL_LIST_ELEMENTS(table->soft_reconfig_peers, node, nnode, + npeer)) { + if (peer == npeer) + break; + } + if (peer != npeer) + listnode_add(table->soft_reconfig_peers, peer); + + /* (re)flag all bgp_dest in table. Existing soft_reconfig_in job + * on table would start back at the beginning. + */ + bgp_soft_reconfig_table_flag(table, true); + + if (!table->soft_reconfig_thread) + event_add_event(bm->master, + bgp_soft_reconfig_table_task, table, 0, + &table->soft_reconfig_thread); + /* Cancel bgp_announce_route_timer_expired threads. + * bgp_announce_route_timer_expired threads have been scheduled + * to announce routes as soon as the soft_reconfigure process + * finishes. + * In this case, soft_reconfigure is also scheduled by using + * a thread but is planned after the + * bgp_announce_route_timer_expired threads. It means that, + * without cancelling the threads, the route announcement task + * would run before the soft reconfiguration one. That would + * useless and would block vtysh during several seconds. Route + * announcements are rescheduled as soon as the soft_reconfigure + * process finishes. + */ + paf = peer_af_find(peer, afi, safi); + if (paf) + bgp_stop_announce_route_timer(paf); + } else + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + + if (table == NULL) + continue; + + const struct prefix *p = bgp_dest_get_prefix(dest); + struct prefix_rd prd; + + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(&prd.val, p->u.val, 8); + + bgp_soft_reconfig_table(peer, afi, safi, table, &prd); + } + + return true; +} + + +struct bgp_clear_node_queue { + struct bgp_dest *dest; +}; + +static wq_item_status bgp_clear_route_node(struct work_queue *wq, void *data) +{ + struct bgp_clear_node_queue *cnq = data; + struct bgp_dest *dest = cnq->dest; + struct peer *peer = wq->spec.data; + struct bgp_path_info *pi, *next; + struct bgp *bgp; + afi_t afi = bgp_dest_table(dest)->afi; + safi_t safi = bgp_dest_table(dest)->safi; + + assert(dest && peer); + bgp = peer->bgp; + + /* It is possible that we have multiple paths for a prefix from a peer + * if that peer is using AddPath. + */ + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (next = pi->next, 1); pi = next) { + if (pi->peer != peer) + continue; + + /* graceful restart STALE flag set. */ + if (((CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT) + && peer->nsf[afi][safi]) + || CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, BGP_PATH_STALE) + && !CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + bgp_path_info_set_flag(dest, pi, BGP_PATH_STALE); + else { + /* If this is an EVPN route, process for + * un-import. */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route( + bgp, afi, safi, + bgp_dest_get_prefix(dest), pi); + /* Handle withdraw for VRF route-leaking and L3VPN */ + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_withdraw(pi); + } + + bgp_rib_remove(dest, pi, peer, afi, safi); + } + } + return WQ_SUCCESS; +} + +static void bgp_clear_node_queue_del(struct work_queue *wq, void *data) +{ + struct bgp_clear_node_queue *cnq = data; + struct bgp_dest *dest = cnq->dest; + struct bgp_table *table = bgp_dest_table(dest); + + bgp_dest_unlock_node(dest); + bgp_table_unlock(table); + XFREE(MTYPE_BGP_CLEAR_NODE_QUEUE, cnq); +} + +static void bgp_clear_node_complete(struct work_queue *wq) +{ + struct peer *peer = wq->spec.data; + + /* Tickle FSM to start moving again */ + BGP_EVENT_ADD(peer->connection, Clearing_Completed); + + peer_unlock(peer); /* bgp_clear_route */ +} + +static void bgp_clear_node_queue_init(struct peer *peer) +{ + char wname[sizeof("clear xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx")]; + + snprintf(wname, sizeof(wname), "clear %s", peer->host); +#undef CLEAR_QUEUE_NAME_LEN + + peer->clear_node_queue = work_queue_new(bm->master, wname); + peer->clear_node_queue->spec.hold = 10; + peer->clear_node_queue->spec.workfunc = &bgp_clear_route_node; + peer->clear_node_queue->spec.del_item_data = &bgp_clear_node_queue_del; + peer->clear_node_queue->spec.completion_func = &bgp_clear_node_complete; + peer->clear_node_queue->spec.max_retries = 0; + + /* we only 'lock' this peer reference when the queue is actually active + */ + peer->clear_node_queue->spec.data = peer; +} + +static void bgp_clear_route_table(struct peer *peer, afi_t afi, safi_t safi, + struct bgp_table *table) +{ + struct bgp_dest *dest; + int force = peer->bgp->process_queue ? 0 : 1; + + if (!table) + table = peer->bgp->rib[afi][safi]; + + /* If still no table => afi/safi isn't configured at all or smth. */ + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + struct bgp_path_info *pi, *next; + struct bgp_adj_in *ain; + struct bgp_adj_in *ain_next; + + /* XXX:TODO: This is suboptimal, every non-empty route_node is + * queued for every clearing peer, regardless of whether it is + * relevant to the peer at hand. + * + * Overview: There are 3 different indices which need to be + * scrubbed, potentially, when a peer is removed: + * + * 1 peer's routes visible via the RIB (ie accepted routes) + * 2 peer's routes visible by the (optional) peer's adj-in index + * 3 other routes visible by the peer's adj-out index + * + * 3 there is no hurry in scrubbing, once the struct peer is + * removed from bgp->peer, we could just GC such deleted peer's + * adj-outs at our leisure. + * + * 1 and 2 must be 'scrubbed' in some way, at least made + * invisible via RIB index before peer session is allowed to be + * brought back up. So one needs to know when such a 'search' is + * complete. + * + * Ideally: + * + * - there'd be a single global queue or a single RIB walker + * - rather than tracking which route_nodes still need to be + * examined on a peer basis, we'd track which peers still + * aren't cleared + * + * Given that our per-peer prefix-counts now should be reliable, + * this may actually be achievable. It doesn't seem to be a huge + * problem at this time, + * + * It is possible that we have multiple paths for a prefix from + * a peer + * if that peer is using AddPath. + */ + ain = dest->adj_in; + while (ain) { + ain_next = ain->next; + + if (ain->peer == peer) + bgp_adj_in_remove(&dest, ain); + + ain = ain_next; + + assert(dest); + } + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = next) { + next = pi->next; + if (pi->peer != peer) + continue; + + if (force) { + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } else { + struct bgp_clear_node_queue *cnq; + + /* both unlocked in bgp_clear_node_queue_del */ + bgp_table_lock(bgp_dest_table(dest)); + bgp_dest_lock_node(dest); + cnq = XCALLOC( + MTYPE_BGP_CLEAR_NODE_QUEUE, + sizeof(struct bgp_clear_node_queue)); + cnq->dest = dest; + work_queue_add(peer->clear_node_queue, cnq); + break; + } + } + } + return; +} + +void bgp_clear_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + + if (peer->clear_node_queue == NULL) + bgp_clear_node_queue_init(peer); + + /* bgp_fsm.c keeps sessions in state Clearing, not transitioning to + * Idle until it receives a Clearing_Completed event. This protects + * against peers which flap faster than we can we clear, which could + * lead to: + * + * a) race with routes from the new session being installed before + * clear_route_node visits the node (to delete the route of that + * peer) + * b) resource exhaustion, clear_route_node likely leads to an entry + * on the process_main queue. Fast-flapping could cause that queue + * to grow and grow. + */ + + /* lock peer in assumption that clear-node-queue will get nodes; if so, + * the unlock will happen upon work-queue completion; other wise, the + * unlock happens at the end of this function. + */ + if (!peer->clear_node_queue->thread) + peer_lock(peer); + + if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN) + bgp_clear_route_table(peer, afi, safi, NULL); + else + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + bgp_clear_route_table(peer, afi, safi, table); + } + + /* unlock if no nodes got added to the clear-node-queue. */ + if (!peer->clear_node_queue->thread) + peer_unlock(peer); +} + +void bgp_clear_route_all(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_clear_route(peer, afi, safi); + +#ifdef ENABLE_BGP_VNC + rfapiProcessPeerDown(peer); +#endif +} + +void bgp_clear_adj_in(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_adj_in *ain; + struct bgp_adj_in *ain_next; + + table = peer->bgp->rib[afi][safi]; + + /* It is possible that we have multiple paths for a prefix from a peer + * if that peer is using AddPath. + */ + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + ain = dest->adj_in; + + while (ain) { + ain_next = ain->next; + + if (ain->peer == peer) + bgp_adj_in_remove(&dest, ain); + + ain = ain_next; + + assert(dest); + } + } +} + +/* If any of the routes from the peer have been marked with the NO_LLGR + * community, either as sent by the peer, or as the result of a configured + * policy, they MUST NOT be retained, but MUST be removed as per the normal + * operation of [RFC4271]. + */ +void bgp_clear_stale_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi, *next; + struct bgp_table *table; + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + struct bgp_dest *rm; + + /* look for neighbor in tables */ + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) + for (pi = bgp_dest_get_bgp_path_info(rm); + (pi != NULL) && (next = pi->next, 1); + pi = next) { + if (pi->peer != peer) + continue; + if (CHECK_FLAG( + peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT) && + bgp_attr_get_community(pi->attr) && + !community_include( + bgp_attr_get_community( + pi->attr), + COMMUNITY_NO_LLGR)) + continue; + if (!CHECK_FLAG(pi->flags, + BGP_PATH_STALE)) + continue; + + /* + * If this is VRF leaked route + * process for withdraw. + */ + if (pi->sub_type == + BGP_ROUTE_IMPORTED && + peer->bgp->inst_type == + BGP_INSTANCE_TYPE_DEFAULT) + vpn_leak_to_vrf_withdraw(pi); + + bgp_rib_remove(rm, pi, peer, afi, safi); + break; + } + } + } else { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (next = pi->next, 1); pi = next) { + if (pi->peer != peer) + continue; + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_LLGR_WAIT) && + bgp_attr_get_community(pi->attr) && + !community_include( + bgp_attr_get_community(pi->attr), + COMMUNITY_NO_LLGR)) + continue; + if (!CHECK_FLAG(pi->flags, BGP_PATH_STALE)) + continue; + if (safi == SAFI_UNICAST && + (peer->bgp->inst_type == + BGP_INSTANCE_TYPE_VRF || + peer->bgp->inst_type == + BGP_INSTANCE_TYPE_DEFAULT)) + vpn_leak_from_vrf_withdraw( + bgp_get_default(), peer->bgp, + pi); + + bgp_rib_remove(dest, pi, peer, afi, safi); + break; + } + } +} + +void bgp_set_stale_route(struct peer *peer, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest, *ndest; + struct bgp_path_info *pi; + struct bgp_table *table; + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) { + for (pi = bgp_dest_get_bgp_path_info(ndest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if ((CHECK_FLAG( + peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, + BGP_PATH_STALE) + && !CHECK_FLAG( + pi->flags, + BGP_PATH_UNUSEABLE)) { + if (bgp_debug_neighbor_events( + peer)) + zlog_debug( + "%pBP route-refresh for %s/%s, marking prefix %pFX as stale", + peer, + afi2str(afi), + safi2str(safi), + bgp_dest_get_prefix( + ndest)); + + bgp_path_info_set_flag( + ndest, pi, + BGP_PATH_STALE); + } + } + } + } + } else { + for (dest = bgp_table_top(peer->bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if ((CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ENHANCED_REFRESH)) + && !CHECK_FLAG(pi->flags, BGP_PATH_STALE) + && !CHECK_FLAG(pi->flags, + BGP_PATH_UNUSEABLE)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP route-refresh for %s/%s, marking prefix %pFX as stale", + peer, afi2str(afi), + safi2str(safi), + bgp_dest_get_prefix( + dest)); + + bgp_path_info_set_flag(dest, pi, + BGP_PATH_STALE); + } + } + } + } +} + +bool bgp_outbound_policy_exists(struct peer *peer, struct bgp_filter *filter) +{ + if (peer->sort == BGP_PEER_CONFED || peer->sort == BGP_PEER_IBGP || + peer->sub_sort == BGP_PEER_EBGP_OAD) + return true; + + if (peer->sort == BGP_PEER_EBGP && + (ROUTE_MAP_OUT_NAME(filter) || PREFIX_LIST_OUT_NAME(filter) || + FILTER_LIST_OUT_NAME(filter) || DISTRIBUTE_OUT_NAME(filter) || + UNSUPPRESS_MAP_NAME(filter))) + return true; + return false; +} + +bool bgp_inbound_policy_exists(struct peer *peer, struct bgp_filter *filter) +{ + if (peer->sort == BGP_PEER_CONFED || peer->sort == BGP_PEER_IBGP || + peer->sub_sort == BGP_PEER_EBGP_OAD) + return true; + + if (peer->sort == BGP_PEER_EBGP + && (ROUTE_MAP_IN_NAME(filter) || PREFIX_LIST_IN_NAME(filter) + || FILTER_LIST_IN_NAME(filter) + || DISTRIBUTE_IN_NAME(filter))) + return true; + return false; +} + +static void bgp_cleanup_table(struct bgp *bgp, struct bgp_table *table, + afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info *next; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = next) { + const struct prefix *p = bgp_dest_get_prefix(dest); + + next = pi->next; + + /* Unimport EVPN routes from VRFs */ + if (safi == SAFI_EVPN) + bgp_evpn_unimport_route(bgp, AFI_L2VPN, + SAFI_EVPN, p, pi); + + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) + && pi->type == ZEBRA_ROUTE_BGP + && (pi->sub_type == BGP_ROUTE_NORMAL + || pi->sub_type == BGP_ROUTE_AGGREGATE + || pi->sub_type == BGP_ROUTE_IMPORTED)) { + + if (bgp_fibupd_safi(safi)) + bgp_zebra_withdraw_actual(dest, pi, bgp); + } + + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } +} + +/* Delete all kernel routes. */ +void bgp_cleanup_routes(struct bgp *bgp) +{ + afi_t afi; + struct bgp_dest *dest; + struct bgp_table *table; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + if (afi == AFI_L2VPN) + continue; + bgp_cleanup_table(bgp, bgp->rib[afi][SAFI_UNICAST], afi, + SAFI_UNICAST); + /* + * VPN and ENCAP and EVPN tables are two-level (RD is top level) + */ + if (afi != AFI_L2VPN) { + safi_t safi; + safi = SAFI_MPLS_VPN; + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, afi, safi); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } + safi = SAFI_ENCAP; + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, afi, safi); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } + } + } + for (dest = bgp_table_top(bgp->rib[AFI_L2VPN][SAFI_EVPN]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (table != NULL) { + bgp_cleanup_table(bgp, table, afi, SAFI_EVPN); + bgp_table_finish(&table); + bgp_dest_set_bgp_table_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + + assert(dest); + } + } +} + +void bgp_reset(void) +{ + vty_reset(); + bgp_zclient_reset(); + access_list_reset(); + prefix_list_reset(); +} + +bool bgp_addpath_encode_rx(struct peer *peer, afi_t afi, safi_t safi) +{ + return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_RX_ADV) + && CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV)); +} + +/* Parse NLRI stream. Withdraw NLRI is recognized by NULL attr + value. */ +int bgp_nlri_parse_ip(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet) +{ + uint8_t *pnt; + uint8_t *lim; + struct prefix p; + int psize; + afi_t afi; + safi_t safi; + bool addpath_capable; + uint32_t addpath_id; + + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + addpath_id = 0; + addpath_capable = bgp_addpath_encode_rx(peer, afi, safi); + + /* RFC4271 6.3 The NLRI field in the UPDATE message is checked for + syntactic validity. If the field is syntactically incorrect, + then the Error Subcode is set to Invalid Network Field. */ + for (; pnt < lim; pnt += psize) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + if (addpath_capable) { + + /* When packet overflow occurs return immediately. */ + if (pnt + BGP_ADDPATH_ID_LEN >= lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + memcpy(&addpath_id, pnt, BGP_ADDPATH_ID_LEN); + addpath_id = ntohl(addpath_id); + pnt += BGP_ADDPATH_ID_LEN; + } + + /* Fetch prefix length. */ + p.prefixlen = *pnt++; + /* afi/safi validity already verified by caller, + * bgp_update_receive */ + p.family = afi2family(afi); + + /* Prefix length check. */ + if (p.prefixlen > prefix_blen(&p) * 8) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (wrong prefix length %d for afi %u)", + peer->host, p.prefixlen, packet->afi); + return BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH; + } + + /* Packet size overflow check. */ + psize = PSIZE(p.prefixlen); + + /* When packet overflow occur return immediately. */ + if (pnt + psize > lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length %d overflows packet)", + peer->host, p.prefixlen); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + + /* Defensive coding, double-check the psize fits in a struct + * prefix for the v4 and v6 afi's and unicast/multicast */ + if (psize > (ssize_t)sizeof(p.u.val)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length %d too large for prefix storage %zu)", + peer->host, p.prefixlen, sizeof(p.u.val)); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + /* Fetch prefix from NLRI packet. */ + memcpy(p.u.val, pnt, psize); + + /* Check address. */ + if (afi == AFI_IP && safi == SAFI_UNICAST) { + if (IN_CLASSD(ntohl(p.u.prefix4.s_addr))) { + /* From RFC4271 Section 6.3: + * + * If a prefix in the NLRI field is semantically + * incorrect + * (e.g., an unexpected multicast IP address), + * an error SHOULD + * be logged locally, and the prefix SHOULD be + * ignored. + */ + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv4 unicast NLRI is multicast address %pI4, ignoring", + peer->host, &p.u.prefix4); + continue; + } + } + + /* Check address. */ + if (afi == AFI_IP6 && safi == SAFI_UNICAST) { + if (IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 unicast NLRI is link-local address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + if (IN6_IS_ADDR_MULTICAST(&p.u.prefix6)) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s: IPv6 unicast NLRI is multicast address %pI6, ignoring", + peer->host, &p.u.prefix6); + + continue; + } + } + + /* Normal process. */ + if (attr) + bgp_update(peer, &p, addpath_id, attr, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, 0, NULL); + else + bgp_withdraw(peer, &p, addpath_id, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, NULL); + + /* Do not send BGP notification twice when maximum-prefix count + * overflow. */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) + return BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW; + } + + /* Packet length consistency check. */ + if (pnt != lim) { + flog_err( + EC_BGP_UPDATE_RCV, + "%s [Error] Update packet error (prefix length mismatch with total length)", + peer->host); + return BGP_NLRI_PARSE_ERROR_PACKET_LENGTH; + } + + return BGP_NLRI_PARSE_OK; +} + +static void bgp_nexthop_reachability_check(afi_t afi, safi_t safi, + struct bgp_path_info *bpi, + const struct prefix *p, + struct bgp_dest *dest, + struct bgp *bgp, + struct bgp *bgp_nexthop) +{ + /* Nexthop reachability check. */ + if (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK)) { + if (bgp_find_or_add_nexthop(bgp, bgp_nexthop, afi, safi, + bpi, NULL, 0, p)) + bgp_path_info_set_flag(dest, bpi, + BGP_PATH_VALID); + else { + if (BGP_DEBUG(nht, NHT)) { + char buf1[INET6_ADDRSTRLEN]; + + inet_ntop(p->family, &p->u.prefix, buf1, + sizeof(buf1)); + zlog_debug("%s(%s): Route not in table, not advertising", + __func__, buf1); + } + bgp_path_info_unset_flag(dest, bpi, + BGP_PATH_VALID); + } + } else { + /* Delete the NHT structure if any, if we're toggling between + * enabling/disabling import check. We deregister the route + * from NHT to avoid overloading NHT and the process interaction + */ + bgp_unlink_nexthop(bpi); + + bgp_path_info_set_flag(dest, bpi, BGP_PATH_VALID); + } + } +} + +static struct bgp_static *bgp_static_new(void) +{ + return XCALLOC(MTYPE_BGP_STATIC, sizeof(struct bgp_static)); +} + +static void bgp_static_free(struct bgp_static *bgp_static) +{ + XFREE(MTYPE_ROUTE_MAP_NAME, bgp_static->rmap.name); + route_map_counter_decrement(bgp_static->rmap.map); + + if (bgp_static->prd_pretty) + XFREE(MTYPE_BGP_NAME, bgp_static->prd_pretty); + XFREE(MTYPE_ATTR, bgp_static->eth_s_id); + XFREE(MTYPE_BGP_STATIC, bgp_static); +} + +void bgp_static_update(struct bgp *bgp, const struct prefix *p, + struct bgp_static *bgp_static, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_path_info *new; + struct bgp_path_info rmap_path; + struct attr attr; + struct attr *attr_new; + route_map_result_t ret; +#ifdef ENABLE_BGP_VNC + int vnc_implicit_withdraw = 0; + mpls_label_t label = MPLS_INVALID_LABEL; +#endif + uint8_t num_labels = 0; + struct bgp *bgp_nexthop = bgp; + struct bgp_labels labels = {}; + + assert(bgp_static); + + if ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) && + bgp_static->label != MPLS_INVALID_LABEL) + num_labels = 1; + + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, + &bgp_static->prd); + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + + attr.nexthop = bgp_static->igpnexthop; + attr.med = bgp_static->igpmetric; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + if (afi == AFI_IP) + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + if (bgp_static->igpmetric) + bgp_attr_set_aigp_metric(&attr, bgp_static->igpmetric); + + if (bgp_static->atomic) + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); + + /* Store label index, if required. */ + if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) { + attr.label_index = bgp_static->label_index; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); + } + + if (safi == SAFI_EVPN || safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) { + if (afi == AFI_IP) { + attr.mp_nexthop_global_in = bgp_static->igpnexthop; + attr.mp_nexthop_len = IPV4_MAX_BYTELEN; + } + } + + if (afi == AFI_L2VPN) { + if (bgp_static->gatewayIp.family == AF_INET) { + SET_IPADDR_V4(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v4, + &bgp_static->gatewayIp.u.prefix4, + IPV4_MAX_BYTELEN); + } else if (bgp_static->gatewayIp.family == AF_INET6) { + SET_IPADDR_V6(&attr.evpn_overlay.gw_ip); + memcpy(&attr.evpn_overlay.gw_ip.ipaddr_v6, + &bgp_static->gatewayIp.u.prefix6, + IPV6_MAX_BYTELEN); + } + memcpy(&attr.esi, bgp_static->eth_s_id, sizeof(esi_t)); + if (bgp_static->encap_tunneltype == BGP_ENCAP_TYPE_VXLAN) { + struct bgp_encap_type_vxlan bet; + memset(&bet, 0, sizeof(bet)); + bet.vnid = p->u.prefix_evpn.prefix_addr.eth_tag; + bgp_encap_type_vxlan_to_tlv(&bet, &attr); + } + if (bgp_static->router_mac) { + bgp_add_routermac_ecom(&attr, bgp_static->router_mac); + } + } + + /* Apply route-map. */ + if (bgp_static->rmap.name) { + struct attr attr_tmp = attr; + + memset(&rmap_path, 0, sizeof(rmap_path)); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr_tmp; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_NETWORK); + + ret = route_map_apply(bgp_static->rmap.map, p, &rmap_path); + + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* Free uninterned attribute. */ + bgp_attr_flush(&attr_tmp); + + /* Unintern original. */ + aspath_unintern(&attr.aspath); + bgp_static_withdraw(bgp, p, afi, safi, &bgp_static->prd); + bgp_dest_unlock_node(dest); + return; + } + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr_tmp); + + attr_new = bgp_attr_intern(&attr_tmp); + } else { + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr); + + attr_new = bgp_attr_intern(&attr); + } + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + break; + + if (pi) { + if (attrhash_cmp(pi->attr, attr_new) + && !CHECK_FLAG(pi->flags, BGP_PATH_REMOVED) + && !CHECK_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS)) { + bgp_dest_unlock_node(dest); + bgp_attr_unintern(&attr_new); + aspath_unintern(&attr.aspath); + return; + } else { + /* The attribute is changed. */ + bgp_path_info_set_flag(dest, pi, BGP_PATH_ATTR_CHANGED); + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(dest, pi); + else + bgp_aggregate_decrement(bgp, p, pi, afi, safi); +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && + safi == SAFI_UNICAST) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + /* + * Implicit withdraw case. + * We have to do this before pi is + * changed + */ + ++vnc_implicit_withdraw; + vnc_import_bgp_del_route(bgp, p, pi); + vnc_import_bgp_exterior_del_route( + bgp, p, pi); + } + } +#endif + bgp_attr_unintern(&pi->attr); + pi->attr = attr_new; + pi->uptime = monotime(NULL); +#ifdef ENABLE_BGP_VNC + if ((afi == AFI_IP || afi == AFI_IP6) && + safi == SAFI_UNICAST) { + if (vnc_implicit_withdraw) { + vnc_import_bgp_add_route(bgp, p, pi); + vnc_import_bgp_exterior_add_route( + bgp, p, pi); + } + } else { + if (BGP_PATH_INFO_NUM_LABELS(pi)) + label = decode_label( + &pi->extra->labels->label[0]); + } +#endif + if (pi->extra && pi->extra->vrfleak->bgp_orig) + bgp_nexthop = pi->extra->vrfleak->bgp_orig; + + bgp_nexthop_reachability_check(afi, safi, pi, p, dest, + bgp, bgp_nexthop); + + /* Process change. */ + bgp_aggregate_increment(bgp, p, pi, afi, safi); + bgp_process(bgp, dest, pi, afi, safi); + + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_update(bgp, pi, + &bgp_static->prd); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + safi == SAFI_EVPN) + rfapiProcessUpdate(pi->peer, NULL, p, + &bgp_static->prd, pi->attr, + afi, safi, pi->type, + pi->sub_type, &label); +#endif + + if (SAFI_UNICAST == safi + && (bgp->inst_type == BGP_INSTANCE_TYPE_VRF + || bgp->inst_type + == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, + pi); + } + + bgp_dest_unlock_node(dest); + aspath_unintern(&attr.aspath); + return; + } + } + + /* Make new BGP info. */ + new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_STATIC, 0, bgp->peer_self, + attr_new, dest); + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) { + SET_FLAG(new->flags, BGP_PATH_VALID); + bgp_path_info_extra_get(new); + if (num_labels) { + labels.num_labels = num_labels; + labels.label[0] = bgp_static->label; + new->extra->labels = bgp_labels_intern(&labels); + } +#ifdef ENABLE_BGP_VNC + label = decode_label(&bgp_static->label); +#endif + } + + bgp_nexthop_reachability_check(afi, safi, new, p, dest, bgp, bgp); + + /* Aggregate address increment. */ + bgp_aggregate_increment(bgp, p, new, afi, safi); + + /* Register new BGP information. */ + bgp_path_info_add(dest, new); + + /* route_node_get lock */ + bgp_dest_unlock_node(dest); + + /* Process change. */ + bgp_process(bgp, dest, new, afi, safi); + + if (SAFI_UNICAST == safi && + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + + if (SAFI_MPLS_VPN == safi && + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd); + } +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN) + rfapiProcessUpdate(new->peer, NULL, p, &bgp_static->prd, + new->attr, afi, safi, new->type, + new->sub_type, &label); +#endif + + /* Unintern original. */ + aspath_unintern(&attr.aspath); +} + +void bgp_static_withdraw(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct prefix_rd *prd) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + /* Check selected route and self inserted route. */ + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + break; + + /* Withdraw static BGP route from routing table. */ + if (pi) { + SET_FLAG(pi->flags, BGP_PATH_UNSORTED); +#ifdef ENABLE_BGP_VNC + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) + rfapiProcessWithdraw(pi->peer, NULL, p, prd, pi->attr, + afi, safi, pi->type, + 1); /* Kill, since it is an administrative change */ +#endif + if (SAFI_UNICAST == safi && + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF || + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + vpn_leak_from_vrf_withdraw(bgp_get_default(), bgp, pi); + } + if (SAFI_MPLS_VPN == safi + && bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + vpn_leak_to_vrf_withdraw(pi); + } + bgp_aggregate_decrement(bgp, p, pi, afi, safi); + bgp_unlink_nexthop(pi); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, safi); + } + + /* Unlock bgp_node_lookup. */ + bgp_dest_unlock_node(dest); +} + +/* Configure static BGP network. When user don't run zebra, static + route should be installed as valid. */ +int bgp_static_set(struct vty *vty, bool negate, const char *ip_str, + const char *rd_str, const char *label_str, afi_t afi, + safi_t safi, const char *rmap, int backdoor, + uint32_t label_index, int evpn_type, const char *esi, + const char *gwip, const char *ethtag, const char *routermac) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_static *bgp_static; + struct prefix_rd prd = {}; + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + uint8_t need_update = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + struct prefix gw_ip; + + /* Convert IP prefix string to struct prefix. */ + ret = str2prefix(ip_str, &p); + if (!ret) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) { + vty_out(vty, "%% Malformed prefix (link-local address)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + apply_mask(&p); + + if (afi == AFI_L2VPN && + (bgp_build_evpn_prefix(evpn_type, ethtag != NULL ? atol(ethtag) : 0, + &p))) { + vty_out(vty, "%% L2VPN prefix could not be forged\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (safi == SAFI_MPLS_VPN || safi == SAFI_EVPN) { + ret = str2prefix_rd(rd_str, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (label_str) { + unsigned long label_val; + + label_val = strtoul(label_str, NULL, 10); + encode_label(label_val, &label); + } + } + + if (safi == SAFI_EVPN) { + if (esi && str2esi(esi, NULL) == 0) { + vty_out(vty, "%% Malformed ESI\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (routermac && prefix_str2mac(routermac, NULL) == 0) { + vty_out(vty, "%% Malformed Router MAC\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (gwip) { + memset(&gw_ip, 0, sizeof(gw_ip)); + ret = str2prefix(gwip, &gw_ip); + if (!ret) { + vty_out(vty, "%% Malformed GatewayIp\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if ((gw_ip.family == AF_INET && + is_evpn_prefix_ipaddr_v6((struct prefix_evpn *)&p)) || + (gw_ip.family == AF_INET6 && + is_evpn_prefix_ipaddr_v4( + (struct prefix_evpn *)&p))) { + vty_out(vty, + "%% GatewayIp family differs with IP prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + if (safi == SAFI_MPLS_VPN || safi == SAFI_EVPN) { + pdest = bgp_node_get(bgp->route[afi][safi], + (struct prefix *)&prd); + if (!bgp_dest_has_bgp_path_info_data(pdest)) + bgp_dest_set_bgp_table_info(pdest, + bgp_table_init(bgp, afi, + safi)); + table = bgp_dest_get_bgp_table_info(pdest); + } else { + table = bgp->route[afi][safi]; + } + + if (negate) { + /* Set BGP static route configuration. */ + dest = bgp_node_lookup(bgp->route[afi][safi], &p); + + if (!dest) { + vty_out(vty, "%% Can't find static route specified\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static) { + if ((label_index != BGP_INVALID_LABEL_INDEX) && + (label_index != bgp_static->label_index)) { + vty_out(vty, + "%% label-index doesn't match static route\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + if ((rmap && bgp_static->rmap.name) && + strcmp(rmap, bgp_static->rmap.name)) { + vty_out(vty, + "%% route-map name doesn't match static route\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Update BGP RIB. */ + if (!bgp_static->backdoor) + bgp_static_withdraw(bgp, &p, afi, safi, NULL); + + /* Clear configuration. */ + bgp_static_free(bgp_static); + } + + bgp_dest_set_bgp_static_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + } else { + dest = bgp_node_get(table, &p); + + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static) { + /* Configuration change. */ + /* Label index cannot be changed. */ + if (bgp_static->label_index != label_index) { + vty_out(vty, "%% cannot change label-index\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check previous routes are installed into BGP. */ + if (bgp_static->valid + && bgp_static->backdoor != backdoor) + need_update = 1; + + bgp_static->backdoor = backdoor; + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + bgp_static->rmap.map = + route_map_lookup_by_name(rmap); + route_map_counter_increment( + bgp_static->rmap.map); + } else { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.map = NULL; + bgp_static->valid = 0; + } + bgp_dest_unlock_node(dest); + } else { + /* New configuration. */ + bgp_static = bgp_static_new(); + bgp_static->backdoor = backdoor; + bgp_static->valid = 0; + bgp_static->igpmetric = 0; + bgp_static->igpnexthop.s_addr = INADDR_ANY; + bgp_static->label_index = label_index; + bgp_static->label = label; + bgp_static->prd = prd; + + if (rd_str) + bgp_static->prd_pretty = XSTRDUP(MTYPE_BGP_NAME, + rd_str); + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp_static->rmap.name); + route_map_counter_decrement( + bgp_static->rmap.map); + bgp_static->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + bgp_static->rmap.map = + route_map_lookup_by_name(rmap); + route_map_counter_increment( + bgp_static->rmap.map); + } + + if (safi == SAFI_EVPN) { + if (esi) { + bgp_static->eth_s_id = + XCALLOC(MTYPE_ATTR, + sizeof(esi_t)); + str2esi(esi, bgp_static->eth_s_id); + } + if (routermac) { + bgp_static->router_mac = + XCALLOC(MTYPE_ATTR, + ETH_ALEN + 1); + (void)prefix_str2mac(routermac, + bgp_static->router_mac); + } + if (gwip) + prefix_copy(&bgp_static->gatewayIp, + &gw_ip); + } + + bgp_dest_set_bgp_static_info(dest, bgp_static); + } + + bgp_static->valid = 1; + if (need_update) + bgp_static_withdraw(bgp, &p, afi, safi, NULL); + + if (!bgp_static->backdoor) + bgp_static_update(bgp, &p, bgp_static, afi, safi); + } + + return CMD_SUCCESS; +} + +void bgp_static_add(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + SET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); + FOREACH_AFI_SAFI (afi, safi) + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + bgp_static_update(bgp, + bgp_dest_get_prefix(rm), + bgp_static, afi, safi); + } + } else { + bgp_static_update( + bgp, bgp_dest_get_prefix(dest), + bgp_dest_get_bgp_static_info(dest), afi, + safi); + } + } + UNSET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); +} + +/* Called from bgp_delete(). Delete all static routes from the BGP + instance. */ +void bgp_static_delete(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + FOREACH_AFI_SAFI (afi, safi) + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + if (!bgp_static) + continue; + + bgp_static_withdraw(bgp, + bgp_dest_get_prefix( + rm), + AFI_IP, safi, + (struct prefix_rd *) + bgp_dest_get_prefix( + dest)); + bgp_static_free(bgp_static); + bgp_dest_set_bgp_static_info(rm, + NULL); + rm = bgp_dest_unlock_node(rm); + assert(rm); + } + } else { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_static_withdraw(bgp, + bgp_dest_get_prefix(dest), + afi, safi, NULL); + bgp_static_free(bgp_static); + bgp_dest_set_bgp_static_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + } + } +} + +void bgp_static_redo_import_check(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + struct bgp_static *bgp_static; + + /* Use this flag to force reprocessing of the route */ + SET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); + FOREACH_AFI_SAFI (afi, safi) { + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + table = bgp_dest_get_bgp_table_info(dest); + + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + bgp_static = + bgp_dest_get_bgp_static_info( + rm); + bgp_static_update(bgp, + bgp_dest_get_prefix(rm), + bgp_static, afi, safi); + } + } else { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_static_update(bgp, + bgp_dest_get_prefix(dest), + bgp_static, afi, safi); + } + } + } + UNSET_FLAG(bgp->flags, BGP_FLAG_FORCE_STATIC_PROCESS); +} + +static void bgp_purge_af_static_redist_routes(struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + /* Do not install the aggregate route if BGP is in the + * process of termination. + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + table = bgp->rib[afi][safi]; + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (pi->peer == bgp->peer_self + && ((pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_STATIC) + || (pi->type != ZEBRA_ROUTE_BGP + && pi->sub_type + == BGP_ROUTE_REDISTRIBUTE))) { + bgp_aggregate_decrement( + bgp, bgp_dest_get_prefix(dest), pi, afi, + safi); + bgp_unlink_nexthop(pi); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, safi); + } + } + } +} + +/* + * Purge all networks and redistributed routes from routing table. + * Invoked upon the instance going down. + */ +void bgp_purge_static_redist_routes(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_purge_af_static_redist_routes(bgp, afi, safi); +} + +static int bgp_table_map_set(struct vty *vty, afi_t afi, safi_t safi, + const char *rmap_name) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bgp_rmap *rmap; + + rmap = &bgp->table_map[afi][safi]; + if (rmap_name) { + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); + rmap->map = route_map_lookup_by_name(rmap_name); + route_map_counter_increment(rmap->map); + } else { + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->map = NULL; + } + + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + + return CMD_SUCCESS; +} + +static int bgp_table_map_unset(struct vty *vty, afi_t afi, safi_t safi, + const char *rmap_name) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct bgp_rmap *rmap; + + rmap = &bgp->table_map[afi][safi]; + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + route_map_counter_decrement(rmap->map); + rmap->map = NULL; + + if (bgp_fibupd_safi(safi)) + bgp_zebra_announce_table(bgp, afi, safi); + + return CMD_SUCCESS; +} + +void bgp_config_write_table_map(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + if (bgp->table_map[afi][safi].name) { + vty_out(vty, " table-map %s\n", + bgp->table_map[afi][safi].name); + } +} + +DEFUN (bgp_table_map, + bgp_table_map_cmd, + "table-map WORD", + "BGP table to RIB route download filter\n" + "Name of the route map\n") +{ + int idx_word = 1; + return bgp_table_map_set(vty, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg); +} +DEFUN (no_bgp_table_map, + no_bgp_table_map_cmd, + "no table-map WORD", + NO_STR + "BGP table to RIB route download filter\n" + "Name of the route map\n") +{ + int idx_word = 2; + return bgp_table_map_unset(vty, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg); +} + +DEFPY(bgp_network, + bgp_network_cmd, + "[no] network \ + \ + [{route-map RMAP_NAME$map_name|label-index (0-1048560)$label_index| \ + backdoor$backdoor}]", + NO_STR + "Specify a network to announce via BGP\n" + "IPv4 prefix\n" + "Network number\n" + "Network mask\n" + "Network mask\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "Label index to associate with the prefix\n" + "Label index value\n" + "Specify a BGP backdoor route\n") +{ + char addr_prefix_str[BUFSIZ]; + + if (address_str) { + int ret; + + ret = netmask_str2prefix_str(address_str, netmask_str, + addr_prefix_str, + sizeof(addr_prefix_str)); + if (!ret) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + return bgp_static_set(vty, no, + address_str ? addr_prefix_str : prefix_str, NULL, + NULL, AFI_IP, bgp_node_safi(vty), map_name, + backdoor ? 1 : 0, + label_index ? (uint32_t)label_index + : BGP_INVALID_LABEL_INDEX, + 0, NULL, NULL, NULL, NULL); +} + +DEFPY(ipv6_bgp_network, + ipv6_bgp_network_cmd, + "[no] network X:X::X:X/M$prefix \ + [{route-map RMAP_NAME$map_name|label-index (0-1048560)$label_index}]", + NO_STR + "Specify a network to announce via BGP\n" + "IPv6 prefix\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "Label index to associate with the prefix\n" + "Label index value\n") +{ + return bgp_static_set(vty, no, prefix_str, NULL, NULL, AFI_IP6, + bgp_node_safi(vty), map_name, 0, + label_index ? (uint32_t)label_index + : BGP_INVALID_LABEL_INDEX, + 0, NULL, NULL, NULL, NULL); +} + +static struct bgp_aggregate *bgp_aggregate_new(void) +{ + return XCALLOC(MTYPE_BGP_AGGREGATE, sizeof(struct bgp_aggregate)); +} + +void bgp_aggregate_free(struct bgp_aggregate *aggregate) +{ + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->suppress_map_name); + route_map_counter_decrement(aggregate->suppress_map); + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); + route_map_counter_decrement(aggregate->rmap.map); + XFREE(MTYPE_BGP_AGGREGATE, aggregate); +} + +/** + * Helper function to avoid repeated code: prepare variables for a + * `route_map_apply` call. + * + * \returns `true` on route map match, otherwise `false`. + */ +static bool aggr_suppress_map_test(struct bgp *bgp, + struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + const struct prefix *p = bgp_dest_get_prefix(pi->net); + route_map_result_t rmr = RMAP_DENYMATCH; + struct bgp_path_info rmap_path = {}; + struct attr attr = {}; + + /* No route map entries created, just don't match. */ + if (aggregate->suppress_map == NULL) + return false; + + /* Call route map matching and return result. */ + attr.aspath = aspath_empty(bgp->asnotation); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_AGGREGATE); + rmr = route_map_apply(aggregate->suppress_map, p, &rmap_path); + bgp->peer_self->rmap_type = 0; + + bgp_attr_flush(&attr); + aspath_unintern(&attr.aspath); + + return rmr == RMAP_PERMITMATCH; +} + +/** Test whether the aggregation has suppressed this path or not. */ +static bool aggr_suppress_exists(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + if (pi->extra == NULL || pi->extra->aggr_suppressors == NULL) + return false; + + return listnode_lookup(pi->extra->aggr_suppressors, aggregate) != NULL; +} + +/** + * Suppress this path and keep the reference. + * + * \returns `true` if needs processing otherwise `false`. + */ +static bool aggr_suppress_path(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + struct bgp_path_info_extra *pie; + + /* Path is already suppressed by this aggregation. */ + if (aggr_suppress_exists(aggregate, pi)) + return false; + + pie = bgp_path_info_extra_get(pi); + + /* This is the first suppression, allocate memory and list it. */ + if (pie->aggr_suppressors == NULL) + pie->aggr_suppressors = list_new(); + + listnode_add(pie->aggr_suppressors, aggregate); + + /* Only mark for processing if suppressed. */ + if (listcount(pie->aggr_suppressors) == 1) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("aggregate-address suppressing: %pFX", + bgp_dest_get_prefix(pi->net)); + + bgp_path_info_set_flag(pi->net, pi, BGP_PATH_ATTR_CHANGED); + return true; + } + + return false; +} + +/** + * Unsuppress this path and remove the reference. + * + * \returns `true` if needs processing otherwise `false`. + */ +static bool aggr_unsuppress_path(struct bgp_aggregate *aggregate, + struct bgp_path_info *pi) +{ + /* Path wasn't suppressed. */ + if (!aggr_suppress_exists(aggregate, pi)) + return false; + + listnode_delete(pi->extra->aggr_suppressors, aggregate); + + /* Unsuppress and free extra memory if last item. */ + if (listcount(pi->extra->aggr_suppressors) == 0) { + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("aggregate-address unsuppressing: %pFX", + bgp_dest_get_prefix(pi->net)); + + list_delete(&pi->extra->aggr_suppressors); + bgp_path_info_set_flag(pi->net, pi, BGP_PATH_ATTR_CHANGED); + return true; + } + + return false; +} + +static bool bgp_aggregate_info_same(struct bgp_path_info *pi, uint8_t origin, + struct aspath *aspath, + struct community *comm, + struct ecommunity *ecomm, + struct lcommunity *lcomm) +{ + static struct aspath *ae = NULL; + enum asnotation_mode asnotation; + + asnotation = bgp_get_asnotation(NULL); + + if (!aspath) + ae = aspath_empty(asnotation); + + if (!pi) + return false; + + if (origin != pi->attr->origin) + return false; + + if (!aspath_cmp(pi->attr->aspath, (aspath) ? aspath : ae)) + return false; + + if (!community_cmp(bgp_attr_get_community(pi->attr), comm)) + return false; + + if (!ecommunity_cmp(bgp_attr_get_ecommunity(pi->attr), ecomm)) + return false; + + if (!lcommunity_cmp(bgp_attr_get_lcommunity(pi->attr), lcomm)) + return false; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + return false; + + return true; +} + +static void bgp_aggregate_install( + struct bgp *bgp, afi_t afi, safi_t safi, const struct prefix *p, + uint8_t origin, struct aspath *aspath, struct community *community, + struct ecommunity *ecommunity, struct lcommunity *lcommunity, + uint8_t atomic_aggregate, struct bgp_aggregate *aggregate) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct bgp_path_info *pi, *orig, *new; + struct attr *attr; + + table = bgp->rib[afi][safi]; + + dest = bgp_node_get(table, p); + + for (orig = pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_AGGREGATE) + break; + + /* + * If we have paths with different MEDs, then don't install + * (or uninstall) the aggregate route. + */ + if (aggregate->match_med && aggregate->med_mismatched) + goto uninstall_aggregate_route; + + if (aggregate->count > 0) { + /* + * If the aggregate information has not changed + * no need to re-install it again. + */ + if (pi && (!aggregate->rmap.changed && + bgp_aggregate_info_same(pi, origin, aspath, community, + ecommunity, lcommunity))) { + bgp_dest_unlock_node(dest); + + if (aspath) + aspath_free(aspath); + if (community) + community_free(&community); + if (ecommunity) + ecommunity_free(&ecommunity); + if (lcommunity) + lcommunity_free(&lcommunity); + + return; + } + + /* + * Mark the old as unusable + */ + if (pi) { + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, safi); + } + + attr = bgp_attr_aggregate_intern( + bgp, origin, aspath, community, ecommunity, lcommunity, + aggregate, atomic_aggregate, p); + + if (!attr) { + aspath_free(aspath); + community_free(&community); + ecommunity_free(&ecommunity); + lcommunity_free(&lcommunity); + bgp_dest_unlock_node(dest); + bgp_aggregate_delete(bgp, p, afi, safi, aggregate); + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("%s: %pFX null attribute", __func__, + p); + return; + } + + new = info_make(ZEBRA_ROUTE_BGP, BGP_ROUTE_AGGREGATE, 0, + bgp->peer_self, attr, dest); + + SET_FLAG(new->flags, BGP_PATH_VALID); + + bgp_path_info_add(dest, new); + bgp_process(bgp, dest, new, afi, safi); + } else { + uninstall_aggregate_route: + for (pi = orig; pi; pi = pi->next) + if (pi->peer == bgp->peer_self + && pi->type == ZEBRA_ROUTE_BGP + && pi->sub_type == BGP_ROUTE_AGGREGATE) + break; + + /* Withdraw static BGP route from routing table. */ + if (pi) { + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, safi); + } + } + + bgp_dest_unlock_node(dest); +} + +/** + * Check if the current path has different MED than other known paths. + * + * \returns `true` if the MED matched the others else `false`. + */ +static bool bgp_aggregate_med_match(struct bgp_aggregate *aggregate, + struct bgp *bgp, struct bgp_path_info *pi) +{ + uint32_t cur_med = bgp_med_value(pi->attr, bgp); + + /* This is the first route being analyzed. */ + if (!aggregate->med_initialized) { + aggregate->med_initialized = true; + aggregate->med_mismatched = false; + aggregate->med_matched_value = cur_med; + } else { + /* Check if routes with different MED showed up. */ + if (cur_med != aggregate->med_matched_value) + aggregate->med_mismatched = true; + } + + return !aggregate->med_mismatched; +} + +/** + * Initializes and tests all routes in the aggregate address path for MED + * values. + * + * \returns `true` if all MEDs are the same otherwise `false`. + */ +static bool bgp_aggregate_test_all_med(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi) +{ + struct bgp_table *table = bgp->rib[afi][safi]; + const struct prefix *dest_p; + struct bgp_dest *dest, *top; + struct bgp_path_info *pi; + bool med_matched = true; + + aggregate->med_initialized = false; + + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + dest_p = bgp_dest_get_prefix(dest); + if (dest_p->prefixlen <= p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + if (!bgp_aggregate_med_match(aggregate, bgp, pi)) { + med_matched = false; + break; + } + } + if (!med_matched) + break; + } + bgp_dest_unlock_node(top); + + return med_matched; +} + +/** + * Toggles the route suppression status for this aggregate address + * configuration. + */ +void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, bool suppress) +{ + struct bgp_table *table = bgp->rib[afi][safi]; + const struct prefix *dest_p; + struct bgp_dest *dest, *top; + struct bgp_path_info *pi; + + /* We've found a different MED we must revert any suppressed routes. */ + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + dest_p = bgp_dest_get_prefix(dest); + if (dest_p->prefixlen <= p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* We are toggling suppression back. */ + if (suppress) { + /* Suppress route if not suppressed already. */ + if (aggr_suppress_path(aggregate, pi)) + bgp_process(bgp, dest, pi, afi, safi); + continue; + } + + /* Install route if there is no more suppression. */ + if (aggr_unsuppress_path(aggregate, pi)) + bgp_process(bgp, dest, pi, afi, safi); + } + } + bgp_dest_unlock_node(top); +} + +/** + * Aggregate address MED matching incremental test: this function is called + * when the initial aggregation occurred and we are only testing a single + * new path. + * + * In addition to testing and setting the MED validity it also installs back + * suppressed routes (if summary is configured). + * + * Must not be called in `bgp_aggregate_route`. + */ +static void bgp_aggregate_med_update(struct bgp_aggregate *aggregate, + struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, + struct bgp_path_info *pi) +{ + /* MED matching disabled. */ + if (!aggregate->match_med) + return; + + /* Aggregation with different MED, recheck if we have got equal MEDs + * now. + */ + if (aggregate->med_mismatched && + bgp_aggregate_test_all_med(aggregate, bgp, p, afi, safi) && + aggregate->summary_only) + bgp_aggregate_toggle_suppressed(aggregate, bgp, p, afi, safi, + true); + else + bgp_aggregate_med_match(aggregate, bgp, pi); + + /* No mismatches, just quit. */ + if (!aggregate->med_mismatched) + return; + + /* Route summarization is disabled. */ + if (!aggregate->summary_only) + return; + + bgp_aggregate_toggle_suppressed(aggregate, bgp, p, afi, safi, false); +} + +/* Update an aggregate as routes are added/removed from the BGP table */ +bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct bgp_aggregate *aggregate) +{ + struct bgp_table *table; + struct bgp_dest *top; + struct bgp_dest *dest; + uint8_t origin; + struct aspath *aspath = NULL; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + struct bgp_path_info *pi; + uint8_t atomic_aggregate = 0; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || + bgp->peer_self == NULL) + return false; + + /* Initialize and test routes for MED difference. */ + if (aggregate->match_med) + bgp_aggregate_test_all_med(aggregate, bgp, p, afi, safi); + + /* + * Reset aggregate count: we might've been called from route map + * update so in that case we must retest all more specific routes. + * + * \see `bgp_route_map_process_update`. + */ + aggregate->count = 0; + aggregate->incomplete_origin_count = 0; + aggregate->egp_origin_count = 0; + + /* ORIGIN attribute: If at least one route among routes that are + aggregated has ORIGIN with the value INCOMPLETE, then the + aggregated route must have the ORIGIN attribute with the value + INCOMPLETE. Otherwise, if at least one route among routes that + are aggregated has ORIGIN with the value EGP, then the aggregated + route must have the origin attribute with the value EGP. In all + other case the value of the ORIGIN attribute of the aggregated + route is INTERNAL. */ + origin = BGP_ORIGIN_IGP; + + table = bgp->rib[afi][safi]; + + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= p->prefixlen) + continue; + + /* If suppress fib is enabled and route not installed + * in FIB, skip the route + */ + if (!bgp_check_advertise(bgp, dest, safi)) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) + atomic_aggregate = 1; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* + * summary-only aggregate route suppress + * aggregated route announcements. + * + * MED matching: + * Don't create summaries if MED didn't match + * otherwise neither the specific routes and the + * aggregation will be announced. + */ + if (aggregate->summary_only + && AGGREGATE_MED_VALID(aggregate)) { + if (aggr_suppress_path(aggregate, pi)) + bgp_process(bgp, dest, pi, afi, safi); + } + + /* + * Suppress more specific routes that match the route + * map results. + * + * MED matching: + * Don't suppress routes if MED matching is enabled and + * it mismatched otherwise we might end up with no + * routes for this path. + */ + if (aggregate->suppress_map_name + && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pi)) { + if (aggr_suppress_path(aggregate, pi)) + bgp_process(bgp, dest, pi, afi, safi); + } + + aggregate->count++; + + /* + * If at least one route among routes that are + * aggregated has ORIGIN with the value INCOMPLETE, + * then the aggregated route MUST have the ORIGIN + * attribute with the value INCOMPLETE. Otherwise, if + * at least one route among routes that are aggregated + * has ORIGIN with the value EGP, then the aggregated + * route MUST have the ORIGIN attribute with the value + * EGP. + */ + switch (pi->attr->origin) { + case BGP_ORIGIN_INCOMPLETE: + aggregate->incomplete_origin_count++; + break; + case BGP_ORIGIN_EGP: + aggregate->egp_origin_count++; + break; + default: + /*Do nothing. + */ + break; + } + + if (!aggregate->as_set) + continue; + + /* + * as-set aggregate route generate origin, as path, + * and community aggregation. + */ + /* Compute aggregate route's as-path. + */ + bgp_compute_aggregate_aspath_hash(aggregate, + pi->attr->aspath); + + /* Compute aggregate route's community. + */ + if (bgp_attr_get_community(pi->attr)) + bgp_compute_aggregate_community_hash( + aggregate, + bgp_attr_get_community(pi->attr)); + + /* Compute aggregate route's extended community. + */ + if (bgp_attr_get_ecommunity(pi->attr)) + bgp_compute_aggregate_ecommunity_hash( + aggregate, + bgp_attr_get_ecommunity(pi->attr)); + + /* Compute aggregate route's large community. + */ + if (bgp_attr_get_lcommunity(pi->attr)) + bgp_compute_aggregate_lcommunity_hash( + aggregate, + bgp_attr_get_lcommunity(pi->attr)); + } + } + if (aggregate->as_set) { + bgp_compute_aggregate_aspath_val(aggregate); + bgp_compute_aggregate_community_val(aggregate); + bgp_compute_aggregate_ecommunity_val(aggregate); + bgp_compute_aggregate_lcommunity_val(aggregate); + } + + + bgp_dest_unlock_node(top); + + + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + if (aggregate->aspath) + /* Retrieve aggregate route's as-path. + */ + aspath = aspath_dup(aggregate->aspath); + + if (aggregate->community) + /* Retrieve aggregate route's community. + */ + community = community_dup(aggregate->community); + + if (aggregate->ecommunity) + /* Retrieve aggregate route's ecommunity. + */ + ecommunity = ecommunity_dup(aggregate->ecommunity); + + if (aggregate->lcommunity) + /* Retrieve aggregate route's lcommunity. + */ + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + /* Unimport suppressed routes from EVPN */ + bgp_aggr_supp_withdraw_from_evpn(bgp, afi, safi); + + bgp_aggregate_install(bgp, afi, safi, p, origin, aspath, community, + ecommunity, lcommunity, atomic_aggregate, + aggregate); + + return true; +} + +void bgp_aggregate_delete(struct bgp *bgp, const struct prefix *p, afi_t afi, + safi_t safi, struct bgp_aggregate *aggregate) +{ + struct bgp_table *table; + struct bgp_dest *top; + struct bgp_dest *dest; + struct bgp_path_info *pi; + + table = bgp->rib[afi][safi]; + + /* If routes exists below this node, generate aggregate routes. */ + top = bgp_node_get(table, p); + for (dest = bgp_node_get(table, p); dest; + dest = bgp_route_next_until(dest, top)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen <= p->prefixlen) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (BGP_PATH_HOLDDOWN(pi)) + continue; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + continue; + + /* + * This route is suppressed: attempt to unsuppress it. + * + * `aggr_unsuppress_path` will fail if this particular + * aggregate route was not the suppressor. + */ + if (pi->extra && pi->extra->aggr_suppressors && + listcount(pi->extra->aggr_suppressors)) { + if (aggr_unsuppress_path(aggregate, pi)) + bgp_process(bgp, dest, pi, afi, safi); + } + + if (aggregate->count > 0) + aggregate->count--; + + if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) + aggregate->incomplete_origin_count--; + else if (pi->attr->origin == BGP_ORIGIN_EGP) + aggregate->egp_origin_count--; + + if (aggregate->as_set) { + /* Remove as-path from aggregate. + */ + bgp_remove_aspath_from_aggregate_hash( + aggregate, + pi->attr->aspath); + + if (bgp_attr_get_community(pi->attr)) + /* Remove community from aggregate. + */ + bgp_remove_comm_from_aggregate_hash( + aggregate, + bgp_attr_get_community( + pi->attr)); + + if (bgp_attr_get_ecommunity(pi->attr)) + /* Remove ecommunity from aggregate. + */ + bgp_remove_ecomm_from_aggregate_hash( + aggregate, + bgp_attr_get_ecommunity( + pi->attr)); + + if (bgp_attr_get_lcommunity(pi->attr)) + /* Remove lcommunity from aggregate. + */ + bgp_remove_lcomm_from_aggregate_hash( + aggregate, + bgp_attr_get_lcommunity( + pi->attr)); + } + } + } + if (aggregate->as_set) { + aspath_free(aggregate->aspath); + aggregate->aspath = NULL; + if (aggregate->community) + community_free(&aggregate->community); + if (aggregate->ecommunity) + ecommunity_free(&aggregate->ecommunity); + if (aggregate->lcommunity) + lcommunity_free(&aggregate->lcommunity); + } + + bgp_dest_unlock_node(top); +} + +static void bgp_add_route_to_aggregate(struct bgp *bgp, + const struct prefix *aggr_p, + struct bgp_path_info *pinew, afi_t afi, + safi_t safi, + struct bgp_aggregate *aggregate) +{ + uint8_t origin; + struct aspath *aspath = NULL; + uint8_t atomic_aggregate = 0; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + /* ORIGIN attribute: If at least one route among routes that are + * aggregated has ORIGIN with the value INCOMPLETE, then the + * aggregated route must have the ORIGIN attribute with the value + * INCOMPLETE. Otherwise, if at least one route among routes that + * are aggregated has ORIGIN with the value EGP, then the aggregated + * route must have the origin attribute with the value EGP. In all + * other case the value of the ORIGIN attribute of the aggregated + * route is INTERNAL. + */ + origin = BGP_ORIGIN_IGP; + + aggregate->count++; + + /* + * This must be called before `summary` check to avoid + * "suppressing" twice. + */ + if (aggregate->match_med) + bgp_aggregate_med_update(aggregate, bgp, aggr_p, afi, safi, + pinew); + + if (aggregate->summary_only && AGGREGATE_MED_VALID(aggregate)) + aggr_suppress_path(aggregate, pinew); + + if (aggregate->suppress_map_name && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pinew)) + aggr_suppress_path(aggregate, pinew); + + switch (pinew->attr->origin) { + case BGP_ORIGIN_INCOMPLETE: + aggregate->incomplete_origin_count++; + break; + case BGP_ORIGIN_EGP: + aggregate->egp_origin_count++; + break; + default: + /* Do nothing. + */ + break; + } + + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + /* Compute aggregate route's as-path. + */ + bgp_compute_aggregate_aspath(aggregate, + pinew->attr->aspath); + + /* Compute aggregate route's community. + */ + if (bgp_attr_get_community(pinew->attr)) + bgp_compute_aggregate_community( + aggregate, bgp_attr_get_community(pinew->attr)); + + /* Compute aggregate route's extended community. + */ + if (bgp_attr_get_ecommunity(pinew->attr)) + bgp_compute_aggregate_ecommunity( + aggregate, + bgp_attr_get_ecommunity(pinew->attr)); + + /* Compute aggregate route's large community. + */ + if (bgp_attr_get_lcommunity(pinew->attr)) + bgp_compute_aggregate_lcommunity( + aggregate, + bgp_attr_get_lcommunity(pinew->attr)); + + /* Retrieve aggregate route's as-path. + */ + if (aggregate->aspath) + aspath = aspath_dup(aggregate->aspath); + + /* Retrieve aggregate route's community. + */ + if (aggregate->community) + community = community_dup(aggregate->community); + + /* Retrieve aggregate route's ecommunity. + */ + if (aggregate->ecommunity) + ecommunity = ecommunity_dup(aggregate->ecommunity); + + /* Retrieve aggregate route's lcommunity. + */ + if (aggregate->lcommunity) + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, + aspath, community, ecommunity, + lcommunity, atomic_aggregate, aggregate); +} + +static void bgp_remove_route_from_aggregate(struct bgp *bgp, afi_t afi, + safi_t safi, + struct bgp_path_info *pi, + struct bgp_aggregate *aggregate, + const struct prefix *aggr_p) +{ + uint8_t origin; + struct aspath *aspath = NULL; + uint8_t atomic_aggregate = 0; + struct community *community = NULL; + struct ecommunity *ecommunity = NULL; + struct lcommunity *lcommunity = NULL; + + /* If the bgp instance is being deleted or self peer is deleted + * then do not create aggregate route + */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) + || (bgp->peer_self == NULL)) + return; + + if (BGP_PATH_HOLDDOWN(pi)) + return; + + if (pi->sub_type == BGP_ROUTE_AGGREGATE) + return; + + if (aggregate->summary_only && AGGREGATE_MED_VALID(aggregate)) + if (aggr_unsuppress_path(aggregate, pi)) + bgp_process(bgp, pi->net, pi, afi, safi); + + if (aggregate->suppress_map_name && AGGREGATE_MED_VALID(aggregate) + && aggr_suppress_map_test(bgp, aggregate, pi)) + if (aggr_unsuppress_path(aggregate, pi)) + bgp_process(bgp, pi->net, pi, afi, safi); + + /* + * This must be called after `summary`, `suppress-map` check to avoid + * "unsuppressing" twice. + */ + if (aggregate->match_med) + bgp_aggregate_med_update(aggregate, bgp, aggr_p, afi, safi, pi); + + if (aggregate->count > 0) + aggregate->count--; + + if (pi->attr->origin == BGP_ORIGIN_INCOMPLETE) + aggregate->incomplete_origin_count--; + else if (pi->attr->origin == BGP_ORIGIN_EGP) + aggregate->egp_origin_count--; + + if (aggregate->as_set) { + /* Remove as-path from aggregate. + */ + bgp_remove_aspath_from_aggregate(aggregate, + pi->attr->aspath); + + if (bgp_attr_get_community(pi->attr)) + /* Remove community from aggregate. + */ + bgp_remove_community_from_aggregate( + aggregate, bgp_attr_get_community(pi->attr)); + + if (bgp_attr_get_ecommunity(pi->attr)) + /* Remove ecommunity from aggregate. + */ + bgp_remove_ecommunity_from_aggregate( + aggregate, bgp_attr_get_ecommunity(pi->attr)); + + if (bgp_attr_get_lcommunity(pi->attr)) + /* Remove lcommunity from aggregate. + */ + bgp_remove_lcommunity_from_aggregate( + aggregate, bgp_attr_get_lcommunity(pi->attr)); + } + + origin = BGP_ORIGIN_IGP; + if (aggregate->incomplete_origin_count > 0) + origin = BGP_ORIGIN_INCOMPLETE; + else if (aggregate->egp_origin_count > 0) + origin = BGP_ORIGIN_EGP; + + if (aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + origin = aggregate->origin; + + if (aggregate->as_set) { + /* Retrieve aggregate route's as-path. + */ + if (aggregate->aspath) + aspath = aspath_dup(aggregate->aspath); + + /* Retrieve aggregate route's community. + */ + if (aggregate->community) + community = community_dup(aggregate->community); + + /* Retrieve aggregate route's ecommunity. + */ + if (aggregate->ecommunity) + ecommunity = ecommunity_dup(aggregate->ecommunity); + + /* Retrieve aggregate route's lcommunity. + */ + if (aggregate->lcommunity) + lcommunity = lcommunity_dup(aggregate->lcommunity); + } + + bgp_aggregate_install(bgp, afi, safi, aggr_p, origin, + aspath, community, ecommunity, + lcommunity, atomic_aggregate, aggregate); +} + +void bgp_aggregate_increment(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *pi, afi_t afi, safi_t safi) +{ + struct bgp_dest *child; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + struct bgp_table *table; + + table = bgp->aggregate[afi][safi]; + + /* No aggregates configured. */ + if (bgp_table_top_nolock(table) == NULL) + return; + + if (p->prefixlen == 0) + return; + + if (BGP_PATH_HOLDDOWN(pi)) + return; + + /* If suppress fib is enabled and route not installed + * in FIB, do not update the aggregate route + */ + if (!bgp_check_advertise(bgp, pi->net, safi)) + return; + + child = bgp_node_get(table, p); + + /* Aggregate address configuration check. */ + for (dest = child; dest; dest = bgp_dest_parent_nolock(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (aggregate != NULL && dest_p->prefixlen < p->prefixlen) { + bgp_add_route_to_aggregate(bgp, dest_p, pi, afi, safi, + aggregate); + } + } + bgp_dest_unlock_node(child); +} + +void bgp_aggregate_decrement(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *del, afi_t afi, safi_t safi) +{ + struct bgp_dest *child; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + struct bgp_table *table; + + table = bgp->aggregate[afi][safi]; + + /* No aggregates configured. */ + if (bgp_table_top_nolock(table) == NULL) + return; + + if (p->prefixlen == 0) + return; + + child = bgp_node_get(table, p); + + /* Aggregate address configuration check. */ + for (dest = child; dest; dest = bgp_dest_parent_nolock(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (aggregate != NULL && dest_p->prefixlen < p->prefixlen) { + bgp_remove_route_from_aggregate(bgp, afi, safi, del, + aggregate, dest_p); + } + } + bgp_dest_unlock_node(child); +} + +/* Aggregate route attribute. */ +#define AGGREGATE_SUMMARY_ONLY 1 +#define AGGREGATE_AS_SET 1 +#define AGGREGATE_AS_UNSET 0 + +static const char *bgp_origin2str(uint8_t origin) +{ + switch (origin) { + case BGP_ORIGIN_IGP: + return "igp"; + case BGP_ORIGIN_EGP: + return "egp"; + case BGP_ORIGIN_INCOMPLETE: + return "incomplete"; + } + return "n/a"; +} + +static const char *bgp_rpki_validation2str(enum rpki_states v_state) +{ + switch (v_state) { + case RPKI_NOT_BEING_USED: + return "not used"; + case RPKI_VALID: + return "valid"; + case RPKI_NOTFOUND: + return "not found"; + case RPKI_INVALID: + return "invalid"; + } + + assert(!"We should never get here this is a dev escape"); + return "ERROR"; +} + +static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str, + afi_t afi, safi_t safi) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + + /* Convert string to prefix structure. */ + ret = str2prefix(prefix_str, &p); + if (!ret) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask(&p); + + /* Old configuration check. */ + dest = bgp_node_lookup(bgp->aggregate[afi][safi], &p); + if (!dest) { + vty_out(vty, + "%% There is no aggregate-address configuration.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + bgp_aggregate_delete(bgp, &p, afi, safi, aggregate); + bgp_aggregate_install(bgp, afi, safi, &p, 0, NULL, NULL, + NULL, NULL, 0, aggregate); + + /* Unlock aggregate address configuration. */ + bgp_dest_set_bgp_aggregate_info(dest, NULL); + + bgp_free_aggregate_info(aggregate); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + + return CMD_SUCCESS; +} + +static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi, + safi_t safi, const char *rmap, + uint8_t summary_only, uint8_t as_set, + uint8_t origin, bool match_med, + const char *suppress_map) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix p; + struct bgp_dest *dest; + struct bgp_aggregate *aggregate; + uint8_t as_set_new = as_set; + + if (suppress_map && summary_only) { + vty_out(vty, + "'summary-only' and 'suppress-map' can't be used at the same time\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Convert string to prefix structure. */ + ret = str2prefix(prefix_str, &p); + if (!ret) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask(&p); + + if ((afi == AFI_IP && p.prefixlen == IPV4_MAX_BITLEN) || + (afi == AFI_IP6 && p.prefixlen == IPV6_MAX_BITLEN)) { + vty_out(vty, "Specified prefix: %s will not result in any useful aggregation, disallowing\n", + prefix_str); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Old configuration check. */ + dest = bgp_node_get(bgp->aggregate[afi][safi], &p); + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + + if (aggregate) { + vty_out(vty, "There is already same aggregate network.\n"); + /* try to remove the old entry */ + ret = bgp_aggregate_unset(vty, prefix_str, afi, safi); + if (ret) { + vty_out(vty, "Error deleting aggregate.\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + } + + /* Make aggregate address structure. */ + aggregate = bgp_aggregate_new(); + aggregate->summary_only = summary_only; + aggregate->match_med = match_med; + + /* Network operators MUST NOT locally generate any new + * announcements containing AS_SET or AS_CONFED_SET. If they have + * announced routes with AS_SET or AS_CONFED_SET in them, then they + * SHOULD withdraw those routes and re-announce routes for the + * aggregate or component prefixes (i.e., the more-specific routes + * subsumed by the previously aggregated route) without AS_SET + * or AS_CONFED_SET in the updates. + */ + if (bgp->reject_as_sets) { + if (as_set == AGGREGATE_AS_SET) { + as_set_new = AGGREGATE_AS_UNSET; + zlog_warn( + "%s: Ignoring as-set because `bgp reject-as-sets` is enabled.", + __func__); + vty_out(vty, + "Ignoring as-set because `bgp reject-as-sets` is enabled.\n"); + } + } + + aggregate->as_set = as_set_new; + aggregate->safi = safi; + /* Override ORIGIN attribute if defined. + * E.g.: Cisco and Juniper set ORIGIN for aggregated address + * to IGP which is not what rfc4271 says. + * This enables the same behavior, optionally. + */ + aggregate->origin = origin; + + if (rmap) { + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->rmap.name); + route_map_counter_decrement(aggregate->rmap.map); + aggregate->rmap.name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + aggregate->rmap.map = route_map_lookup_by_name(rmap); + aggregate->rmap.changed = true; + route_map_counter_increment(aggregate->rmap.map); + } + + if (suppress_map) { + XFREE(MTYPE_ROUTE_MAP_NAME, aggregate->suppress_map_name); + route_map_counter_decrement(aggregate->suppress_map); + + aggregate->suppress_map_name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, suppress_map); + aggregate->suppress_map = + route_map_lookup_by_name(aggregate->suppress_map_name); + route_map_counter_increment(aggregate->suppress_map); + } + + bgp_dest_set_bgp_aggregate_info(dest, aggregate); + + /* Aggregate address insert into BGP routing table. */ + if (!bgp_aggregate_route(bgp, &p, afi, safi, aggregate)) { + bgp_aggregate_free(aggregate); + bgp_dest_unlock_node(dest); + } + + return CMD_SUCCESS; +} + +DEFPY(aggregate_addressv4, aggregate_addressv4_cmd, + "[no] aggregate-address [{" + "as-set$as_set_s" + "|summary-only$summary_only" + "|route-map RMAP_NAME$rmap_name" + "|origin $origin_s" + "|matching-MED-only$match_med" + "|suppress-map RMAP_NAME$suppress_map" + "}]", + NO_STR + "Configure BGP aggregate entries\n" + "Aggregate prefix\n" + "Aggregate address\n" + "Aggregate mask\n" + "Generate AS set path information\n" + "Filter more specific routes from updates\n" + "Apply route map to aggregate network\n" + "Route map name\n" + "BGP origin code\n" + "Remote EGP\n" + "Local IGP\n" + "Unknown heritage\n" + "Only aggregate routes with matching MED\n" + "Suppress the selected more specific routes\n" + "Route map with the route selectors\n") +{ + const char *prefix_s = NULL; + safi_t safi = bgp_node_safi(vty); + uint8_t origin = BGP_ORIGIN_UNSPECIFIED; + int as_set = AGGREGATE_AS_UNSET; + char prefix_buf[PREFIX2STR_BUFFER]; + + if (addr_str) { + if (netmask_str2prefix_str(addr_str, mask_str, prefix_buf, + sizeof(prefix_buf)) + == 0) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING_CONFIG_FAILED; + } + prefix_s = prefix_buf; + } else + prefix_s = prefix_str; + + if (origin_s) { + if (strcmp(origin_s, "egp") == 0) + origin = BGP_ORIGIN_EGP; + else if (strcmp(origin_s, "igp") == 0) + origin = BGP_ORIGIN_IGP; + else if (strcmp(origin_s, "incomplete") == 0) + origin = BGP_ORIGIN_INCOMPLETE; + } + + if (as_set_s) + as_set = AGGREGATE_AS_SET; + + /* Handle configuration removal, otherwise installation. */ + if (no) + return bgp_aggregate_unset(vty, prefix_s, AFI_IP, safi); + + return bgp_aggregate_set(vty, prefix_s, AFI_IP, safi, rmap_name, + summary_only != NULL, as_set, origin, + match_med != NULL, suppress_map); +} + +void bgp_free_aggregate_info(struct bgp_aggregate *aggregate) +{ + if (aggregate->community) + community_free(&aggregate->community); + + hash_clean_and_free(&aggregate->community_hash, + bgp_aggr_community_remove); + + if (aggregate->ecommunity) + ecommunity_free(&aggregate->ecommunity); + + hash_clean_and_free(&aggregate->ecommunity_hash, + bgp_aggr_ecommunity_remove); + + if (aggregate->lcommunity) + lcommunity_free(&aggregate->lcommunity); + + hash_clean_and_free(&aggregate->lcommunity_hash, + bgp_aggr_lcommunity_remove); + + if (aggregate->aspath) + aspath_free(aggregate->aspath); + + hash_clean_and_free(&aggregate->aspath_hash, bgp_aggr_aspath_remove); + + bgp_aggregate_free(aggregate); +} + +DEFPY(aggregate_addressv6, aggregate_addressv6_cmd, + "[no] aggregate-address X:X::X:X/M$prefix [{" + "as-set$as_set_s" + "|summary-only$summary_only" + "|route-map RMAP_NAME$rmap_name" + "|origin $origin_s" + "|matching-MED-only$match_med" + "|suppress-map RMAP_NAME$suppress_map" + "}]", + NO_STR + "Configure BGP aggregate entries\n" + "Aggregate prefix\n" + "Generate AS set path information\n" + "Filter more specific routes from updates\n" + "Apply route map to aggregate network\n" + "Route map name\n" + "BGP origin code\n" + "Remote EGP\n" + "Local IGP\n" + "Unknown heritage\n" + "Only aggregate routes with matching MED\n" + "Suppress the selected more specific routes\n" + "Route map with the route selectors\n") +{ + uint8_t origin = BGP_ORIGIN_UNSPECIFIED; + int as_set = AGGREGATE_AS_UNSET; + + if (origin_s) { + if (strcmp(origin_s, "egp") == 0) + origin = BGP_ORIGIN_EGP; + else if (strcmp(origin_s, "igp") == 0) + origin = BGP_ORIGIN_IGP; + else if (strcmp(origin_s, "incomplete") == 0) + origin = BGP_ORIGIN_INCOMPLETE; + } + + if (as_set_s) + as_set = AGGREGATE_AS_SET; + + /* Handle configuration removal, otherwise installation. */ + if (no) + return bgp_aggregate_unset(vty, prefix_str, AFI_IP6, + SAFI_UNICAST); + + return bgp_aggregate_set(vty, prefix_str, AFI_IP6, SAFI_UNICAST, + rmap_name, summary_only != NULL, as_set, + origin, match_med != NULL, suppress_map); +} + +/* Redistribute route treatment. */ +void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, + const union g_addr *nexthop, ifindex_t ifindex, + enum nexthop_types_t nhtype, uint8_t distance, + enum blackhole_type bhtype, uint32_t metric, + uint8_t type, unsigned short instance, + route_tag_t tag) +{ + struct bgp_path_info *new; + struct bgp_path_info *bpi; + struct bgp_path_info rmap_path; + struct bgp_dest *bn; + struct attr attr; + struct attr *new_attr; + afi_t afi; + route_map_result_t ret; + struct bgp_redist *red; + struct interface *ifp; + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS) || + bgp->peer_self == NULL) + return; + + /* Make default attribute. */ + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + /* + * This must not be NULL to satisfy Coverity SA + */ + assert(attr.aspath); + + if (p->family == AF_INET6) + UNSET_FLAG(attr.flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)); + + switch (nhtype) { + case NEXTHOP_TYPE_IFINDEX: + switch (p->family) { + case AF_INET: + attr.nexthop.s_addr = INADDR_ANY; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in.s_addr = INADDR_ANY; + break; + case AF_INET6: + memset(&attr.mp_nexthop_global, 0, + sizeof(attr.mp_nexthop_global)); + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + } + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + attr.nexthop = nexthop->ipv4; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in = nexthop->ipv4; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + attr.mp_nexthop_global = nexthop->ipv6; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + case NEXTHOP_TYPE_BLACKHOLE: + switch (p->family) { + case AF_INET: + attr.nexthop.s_addr = INADDR_ANY; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + attr.mp_nexthop_global_in.s_addr = INADDR_ANY; + break; + case AF_INET6: + memset(&attr.mp_nexthop_global, 0, + sizeof(attr.mp_nexthop_global)); + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + } + attr.bh_type = bhtype; + break; + } + attr.nh_type = nhtype; + attr.nh_ifindex = ifindex; + ifp = if_lookup_by_index(ifindex, bgp->vrf_id); + if (ifp && if_is_operative(ifp)) + SET_FLAG(attr.nh_flags, BGP_ATTR_NH_IF_OPERSTATE); + else + UNSET_FLAG(attr.nh_flags, BGP_ATTR_NH_IF_OPERSTATE); + + attr.med = metric; + attr.distance = distance; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + attr.tag = tag; + + if (metric) + bgp_attr_set_aigp_metric(&attr, metric); + + afi = family2afi(p->family); + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (red) { + struct attr attr_new; + + /* Copy attribute for modification. */ + attr_new = attr; + + if (red->redist_metric_flag) { + attr_new.med = red->redist_metric; + bgp_attr_set_aigp_metric(&attr_new, red->redist_metric); + } + + /* Apply route-map. */ + if (red->rmap.name) { + memset(&rmap_path, 0, sizeof(rmap_path)); + rmap_path.peer = bgp->peer_self; + rmap_path.attr = &attr_new; + + SET_FLAG(bgp->peer_self->rmap_type, + PEER_RMAP_TYPE_REDISTRIBUTE); + + ret = route_map_apply(red->rmap.map, p, &rmap_path); + + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* Free uninterned attribute. */ + bgp_attr_flush(&attr_new); + + /* Unintern original. */ + aspath_unintern(&attr.aspath); + bgp_redistribute_delete(bgp, p, type, instance); + return; + } + } + + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(&attr_new); + + bn = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, + SAFI_UNICAST, p, NULL); + + new_attr = bgp_attr_intern(&attr_new); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) + if (bpi->peer == bgp->peer_self + && bpi->sub_type == BGP_ROUTE_REDISTRIBUTE) + break; + + if (bpi) { + /* Ensure the (source route) type is updated. */ + bpi->type = type; + if (attrhash_cmp(bpi->attr, new_attr) + && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + bgp_attr_unintern(&new_attr); + aspath_unintern(&attr.aspath); + bgp_dest_unlock_node(bn); + return; + } else { + /* The attribute is changed. */ + bgp_path_info_set_flag(bn, bpi, + BGP_PATH_ATTR_CHANGED); + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(bn, bpi); + else + bgp_aggregate_decrement( + bgp, p, bpi, afi, SAFI_UNICAST); + bgp_attr_unintern(&bpi->attr); + bpi->attr = new_attr; + bpi->uptime = monotime(NULL); + + /* Process change. */ + bgp_aggregate_increment(bgp, p, bpi, afi, + SAFI_UNICAST); + bgp_process(bgp, bn, bpi, afi, SAFI_UNICAST); + bgp_dest_unlock_node(bn); + aspath_unintern(&attr.aspath); + + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type + == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update( + bgp_get_default(), bgp, bpi); + } + return; + } + } + + new = info_make(type, BGP_ROUTE_REDISTRIBUTE, instance, + bgp->peer_self, new_attr, bn); + SET_FLAG(new->flags, BGP_PATH_VALID); + + bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST); + bgp_path_info_add(bn, new); + bgp_dest_unlock_node(bn); + SET_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED); + bgp_process(bgp, bn, new, afi, SAFI_UNICAST); + + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_update(bgp_get_default(), bgp, new); + } + } + + /* Unintern original. */ + aspath_unintern(&attr.aspath); +} + +void bgp_redistribute_delete(struct bgp *bgp, struct prefix *p, uint8_t type, + unsigned short instance) +{ + afi_t afi; + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_redist *red; + + afi = family2afi(p->family); + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (red) { + dest = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, + SAFI_UNICAST, p, NULL); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == type) + break; + + if (pi) { + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + bgp_aggregate_decrement(bgp, p, pi, afi, SAFI_UNICAST); + bgp_path_info_delete(dest, pi); + bgp_process(bgp, dest, pi, afi, SAFI_UNICAST); + } + bgp_dest_unlock_node(dest); + } +} + +/* Withdraw specified route type's route. */ +void bgp_redistribute_withdraw(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct bgp_table *table; + + table = bgp->rib[afi][SAFI_UNICAST]; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (pi->peer == bgp->peer_self && pi->type == type + && pi->instance == instance) + break; + + if (pi) { + if ((bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + || (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) { + + vpn_leak_from_vrf_withdraw(bgp_get_default(), + bgp, pi); + } + bgp_aggregate_decrement(bgp, bgp_dest_get_prefix(dest), + pi, afi, SAFI_UNICAST); + bgp_path_info_delete(dest, pi); + if (!CHECK_FLAG(bgp->flags, + BGP_FLAG_DELETE_IN_PROGRESS)) + bgp_process(bgp, dest, pi, afi, SAFI_UNICAST); + else { + dest = bgp_path_info_reap(dest, pi); + assert(dest); + } + } + } +} + +/* Static function to display route. */ +static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, + struct vty *vty, json_object *json, bool wide) +{ + int len = 0; + char buf[INET6_ADDRSTRLEN]; + + if (p->family == AF_INET) { + if (!json) { + len = vty_out(vty, "%pFX", p); + } else { + json_object_string_add(json, "prefix", + inet_ntop(p->family, + &p->u.prefix, buf, + sizeof(buf))); + json_object_int_add(json, "prefixLen", p->prefixlen); + json_object_string_addf(json, "network", "%pFX", p); + json_object_int_add(json, "version", dest->version); + } + } else if (p->family == AF_ETHERNET) { + len = vty_out(vty, "%pFX", p); + } else if (p->family == AF_EVPN) { + if (!json) + len = vty_out(vty, "%pFX", (struct prefix_evpn *)p); + else + bgp_evpn_route2json((struct prefix_evpn *)p, json); + } else if (p->family == AF_FLOWSPEC) { + route_vty_out_flowspec(vty, p, NULL, + json ? + NLRI_STRING_FORMAT_JSON_SIMPLE : + NLRI_STRING_FORMAT_MIN, json); + } else { + if (!json) + len = vty_out(vty, "%pFX", p); + else { + json_object_string_add(json, "prefix", + inet_ntop(p->family, + &p->u.prefix, buf, + sizeof(buf))); + json_object_int_add(json, "prefixLen", p->prefixlen); + json_object_string_addf(json, "network", "%pFX", p); + json_object_int_add(json, "version", dest->version); + } + } + + if (!json) { + len = wide ? (45 - len) : (17 - len); + if (len < 1) + vty_out(vty, "\n%*s", 20, " "); + else + vty_out(vty, "%*s", len, " "); + } +} + +enum bgp_display_type { + normal_list, +}; + +const char *bgp_path_selection_reason2str(enum bgp_path_selection_reason reason) +{ + switch (reason) { + case bgp_path_selection_none: + return "Nothing to Select"; + case bgp_path_selection_first: + return "First path received"; + case bgp_path_selection_evpn_sticky_mac: + return "EVPN Sticky Mac"; + case bgp_path_selection_evpn_seq: + return "EVPN sequence number"; + case bgp_path_selection_evpn_lower_ip: + return "EVPN lower IP"; + case bgp_path_selection_evpn_local_path: + return "EVPN local ES path"; + case bgp_path_selection_evpn_non_proxy: + return "EVPN non proxy"; + case bgp_path_selection_weight: + return "Weight"; + case bgp_path_selection_local_pref: + return "Local Pref"; + case bgp_path_selection_accept_own: + return "Accept Own"; + case bgp_path_selection_local_route: + return "Local Route"; + case bgp_path_selection_aigp: + return "AIGP"; + case bgp_path_selection_confed_as_path: + return "Confederation based AS Path"; + case bgp_path_selection_as_path: + return "AS Path"; + case bgp_path_selection_origin: + return "Origin"; + case bgp_path_selection_med: + return "MED"; + case bgp_path_selection_peer: + return "Peer Type"; + case bgp_path_selection_confed: + return "Confed Peer Type"; + case bgp_path_selection_igp_metric: + return "IGP Metric"; + case bgp_path_selection_older: + return "Older Path"; + case bgp_path_selection_router_id: + return "Router ID"; + case bgp_path_selection_cluster_length: + return "Cluster length"; + case bgp_path_selection_stale: + return "Path Staleness"; + case bgp_path_selection_local_configured: + return "Locally configured route"; + case bgp_path_selection_neighbor_ip: + return "Neighbor IP"; + case bgp_path_selection_default: + return "Nothing left to compare"; + } + return "Invalid (internal error)"; +} + +/* Print the short form route status for a bgp_path_info */ +static void route_vty_short_status_out(struct vty *vty, + struct bgp_path_info *path, + const struct prefix *p, + json_object *json_path) +{ + enum rpki_states rpki_state = RPKI_NOT_BEING_USED; + + if (json_path) { + + /* Route status display. */ + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) + json_object_boolean_true_add(json_path, "removed"); + + if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) + json_object_boolean_true_add(json_path, "stale"); + + if (path->extra && bgp_path_suppressed(path)) + json_object_boolean_true_add(json_path, "suppressed"); + + if (CHECK_FLAG(path->flags, BGP_PATH_UNSORTED)) + json_object_boolean_true_add(json_path, "unsorted"); + + if (CHECK_FLAG(path->flags, BGP_PATH_VALID) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + json_object_boolean_true_add(json_path, "valid"); + + /* Selected */ + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + json_object_boolean_true_add(json_path, "history"); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) + json_object_boolean_true_add(json_path, "damped"); + + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) { + json_object_boolean_true_add(json_path, "bestpath"); + json_object_string_add(json_path, "selectionReason", + bgp_path_selection_reason2str( + path->net->reason)); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) + json_object_boolean_true_add(json_path, "multipath"); + + /* Internal route. */ + if ((path->peer->as) + && (path->peer->as == path->peer->local_as)) + json_object_string_add(json_path, "pathFrom", + "internal"); + else + json_object_string_add(json_path, "pathFrom", + "external"); + + return; + } + + /* RPKI validation state */ + rpki_state = + hook_call(bgp_rpki_prefix_status, path->peer, path->attr, p); + + if (rpki_state == RPKI_VALID) + vty_out(vty, "V"); + else if (rpki_state == RPKI_INVALID) + vty_out(vty, "I"); + else if (rpki_state == RPKI_NOTFOUND) + vty_out(vty, "N"); + else + vty_out(vty, " "); + + /* Route status display. */ + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) + vty_out(vty, "R"); + else if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) + vty_out(vty, "S"); + else if (bgp_path_suppressed(path)) + vty_out(vty, "s"); + else if (CHECK_FLAG(path->flags, BGP_PATH_VALID) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "*"); + else + vty_out(vty, " "); + + /* Selected */ + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "h"); + else if (CHECK_FLAG(path->flags, BGP_PATH_UNSORTED)) + vty_out(vty, "u"); + else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) + vty_out(vty, "d"); + else if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + vty_out(vty, ">"); + else if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH)) + vty_out(vty, "="); + else + vty_out(vty, " "); + + /* Internal route. */ + if (path->peer && (path->peer->as) + && (path->peer->as == path->peer->local_as)) + vty_out(vty, "i"); + else + vty_out(vty, " "); + + /* adding space between next column */ + vty_out(vty, " "); +} + +static char *bgp_nexthop_hostname(struct peer *peer, + struct bgp_nexthop_cache *bnc) +{ + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME)) + return peer->hostname; + return NULL; +} + +/* called from terminal list command */ +void route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, safi_t safi, + json_object *json_paths, bool wide) +{ + int len; + struct attr *attr = path->attr; + json_object *json_path = NULL; + json_object *json_nexthops = NULL; + json_object *json_nexthop_global = NULL; + json_object *json_nexthop_ll = NULL; + json_object *json_ext_community = NULL; + char vrf_id_str[VRF_NAMSIZ] = {0}; + bool nexthop_self = + CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; + bool nexthop_othervrf = false; + vrf_id_t nexthop_vrfid = VRF_DEFAULT; + const char *nexthop_vrfname = VRF_DEFAULT_NAME; + char *nexthop_hostname = + bgp_nexthop_hostname(path->peer, path->nexthop); + char esi_buf[ESI_STR_LEN]; + + if (json_paths) + json_path = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + if (!json_paths) { + /* print prefix and mask */ + if (!display) + route_vty_out_route(path->net, p, vty, json_path, wide); + else + vty_out(vty, "%*s", (wide ? 45 : 17), " "); + } else { + route_vty_out_route(path->net, p, vty, json_path, wide); + } + + /* + * If vrf id of nexthop is different from that of prefix, + * set up printable string to append + */ + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->bgp_orig) { + const char *self = ""; + + if (nexthop_self) + self = "<"; + + nexthop_othervrf = true; + nexthop_vrfid = path->extra->vrfleak->bgp_orig->vrf_id; + + if (path->extra->vrfleak->bgp_orig->vrf_id == VRF_UNKNOWN) + snprintf(vrf_id_str, sizeof(vrf_id_str), + "@%s%s", VRFID_NONE_STR, self); + else + snprintf(vrf_id_str, sizeof(vrf_id_str), "@%u%s", + path->extra->vrfleak->bgp_orig->vrf_id, self); + + if (path->extra->vrfleak->bgp_orig->inst_type != + BGP_INSTANCE_TYPE_DEFAULT) + + nexthop_vrfname = path->extra->vrfleak->bgp_orig->name; + } else { + const char *self = ""; + + if (nexthop_self) + self = "<"; + + snprintf(vrf_id_str, sizeof(vrf_id_str), "%s", self); + } + + /* + * For ENCAP and EVPN routes, nexthop address family is not + * neccessarily the same as the prefix address family. + * Both SAFI_MPLS_VPN and SAFI_ENCAP use the MP nexthop field + * EVPN routes are also exchanged with a MP nexthop. Currently, + * this + * is only IPv4, the value will be present in either + * attr->nexthop or + * attr->mp_nexthop_global_in + */ + if ((safi == SAFI_ENCAP) || (safi == SAFI_MPLS_VPN)) { + char nexthop[128]; + int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); + + switch (af) { + case AF_INET: + snprintfrr(nexthop, sizeof(nexthop), "%pI4", + &attr->mp_nexthop_global_in); + break; + case AF_INET6: + snprintfrr(nexthop, sizeof(nexthop), "%pI6", + &attr->mp_nexthop_global); + break; + default: + snprintf(nexthop, sizeof(nexthop), "?"); + break; + } + + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_add(json_nexthop_global, "ip", + nexthop); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + (af == AF_INET) ? "ipv4" + : "ipv6"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%s(%s)%s", nexthop, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%s%s", nexthop, vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else if (safi == SAFI_EVPN) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_addf(json_nexthop_global, "ip", + "%pI4", + &attr->mp_nexthop_global_in); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", + &attr->mp_nexthop_global_in, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%pI4%s", + &attr->mp_nexthop_global_in, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else if (safi == SAFI_FLOWSPEC) { + if (attr->nexthop.s_addr != INADDR_ANY) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_add(json_nexthop_global, + "afi", "ipv4"); + json_object_string_addf(json_nexthop_global, + "ip", "%pI4", + &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + + json_object_boolean_true_add( + json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", + &attr->nexthop, + nexthop_hostname, + vrf_id_str); + else + len = vty_out(vty, "%pI4%s", + &attr->nexthop, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + } else if (p->family == AF_INET && !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + + json_object_string_addf(json_nexthop_global, "ip", + "%pI4", &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + json_object_boolean_true_add(json_nexthop_global, + "used"); + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI4(%s)%s", &attr->nexthop, + nexthop_hostname, vrf_id_str); + else + len = vty_out(vty, "%pI4%s", &attr->nexthop, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + + /* IPv6 Next Hop */ + else if (p->family == AF_INET6 || BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + if (json_paths) { + json_nexthop_global = json_object_new_object(); + json_object_string_addf(json_nexthop_global, "ip", + "%pI6", + &attr->mp_nexthop_global); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv6"); + json_object_string_add(json_nexthop_global, "scope", + "global"); + + /* We display both LL & GL if both have been + * received */ + if ((attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + || (path->peer->conf_if)) { + json_nexthop_ll = json_object_new_object(); + json_object_string_addf( + json_nexthop_ll, "ip", "%pI6", + &attr->mp_nexthop_local); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_ll, "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_ll, "afi", + "ipv6"); + json_object_string_add(json_nexthop_ll, "scope", + "link-local"); + + if ((IPV6_ADDR_CMP(&attr->mp_nexthop_global, + &attr->mp_nexthop_local) != + 0) && + !CHECK_FLAG(attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + json_object_boolean_true_add( + json_nexthop_ll, "used"); + else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else { + /* Display LL if LL/Global both in table unless + * prefer-global is set */ + if (((attr->mp_nexthop_len == + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) && + !CHECK_FLAG(attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) || + (path->peer->conf_if)) { + if (path->peer->conf_if) { + len = vty_out(vty, "%s", + path->peer->conf_if); + /* len of IPv6 addr + max len of def + * ifname */ + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } else { + if (nexthop_hostname) + len = vty_out( + vty, "%pI6(%s)%s", + &attr->mp_nexthop_local, + nexthop_hostname, + vrf_id_str); + else + len = vty_out( + vty, "%pI6%s", + &attr->mp_nexthop_local, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } else { + if (nexthop_hostname) + len = vty_out(vty, "%pI6(%s)%s", + &attr->mp_nexthop_global, + nexthop_hostname, + vrf_id_str); + else + len = vty_out(vty, "%pI6%s", + &attr->mp_nexthop_global, + vrf_id_str); + + len = wide ? (41 - len) : (16 - len); + + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + } + } + + /* MED/Metric */ + if (use_bgp_med_value(attr, path->peer->bgp)) { + uint32_t value = bgp_med_value(attr, path->peer->bgp); + + if (json_paths) + json_object_int_add(json_path, "metric", value); + else if (wide) + vty_out(vty, "%7u", value); + else + vty_out(vty, "%10u", value); + } else if (!json_paths) { + if (wide) + vty_out(vty, "%*s", 7, " "); + else + vty_out(vty, "%*s", 10, " "); + } + + /* Local Pref */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + if (json_paths) + json_object_int_add(json_path, "locPrf", + attr->local_pref); + else + vty_out(vty, "%7u", attr->local_pref); + else if (!json_paths) + vty_out(vty, " "); + + if (json_paths) + json_object_int_add(json_path, "weight", attr->weight); + else + vty_out(vty, "%7u ", attr->weight); + + if (json_paths) + json_object_string_addf(json_path, "peerId", "%pSU", + &path->peer->connection->su); + + /* Print aspath */ + if (attr->aspath) { + if (json_paths) + json_object_string_add(json_path, "path", + attr->aspath->str); + else + aspath_print_vty(vty, attr->aspath); + } + + /* Print origin */ + if (json_paths) + json_object_string_add(json_path, "origin", + bgp_origin_long_str[attr->origin]); + else + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + if (json_paths) { + if (bgp_evpn_is_esi_valid(&attr->esi)) { + json_object_string_add(json_path, "esi", + esi_to_str(&attr->esi, + esi_buf, sizeof(esi_buf))); + } + if (safi == SAFI_EVPN && + attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + json_ext_community = json_object_new_object(); + json_object_string_add( + json_ext_community, "string", + bgp_attr_get_ecommunity(attr)->str); + json_object_object_add(json_path, + "extendedCommunity", + json_ext_community); + } + + if (nexthop_self) + json_object_boolean_true_add(json_path, + "announceNexthopSelf"); + if (nexthop_othervrf) { + json_object_string_add(json_path, "nhVrfName", + nexthop_vrfname); + + json_object_int_add(json_path, "nhVrfId", + ((nexthop_vrfid == VRF_UNKNOWN) + ? -1 + : (int)nexthop_vrfid)); + } + } + + if (json_paths) { + if (json_nexthop_global || json_nexthop_ll) { + json_nexthops = json_object_new_array(); + + if (json_nexthop_global) + json_object_array_add(json_nexthops, + json_nexthop_global); + + if (json_nexthop_ll) + json_object_array_add(json_nexthops, + json_nexthop_ll); + + json_object_object_add(json_path, "nexthops", + json_nexthops); + } + + json_object_array_add(json_paths, json_path); + } else { + vty_out(vty, "\n"); + + if (safi == SAFI_EVPN) { + if (bgp_evpn_is_esi_valid(&attr->esi)) { + /* XXX - add these params to the json out */ + vty_out(vty, "%*s", 20, " "); + vty_out(vty, "ESI:%s", + esi_to_str(&attr->esi, esi_buf, + sizeof(esi_buf))); + + vty_out(vty, "\n"); + } + if (attr->flag & + ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + vty_out(vty, "%*s", 20, " "); + vty_out(vty, "%s\n", + bgp_attr_get_ecommunity(attr)->str); + } + } + +#ifdef ENABLE_BGP_VNC + /* prints an additional line, indented, with VNC info, if + * present */ + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) + rfapi_vty_out_vncinfo(vty, p, path, safi); +#endif + } +} + +/* called from terminal list command */ +void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, struct bgp_dest *dest, + const struct prefix *p, struct attr *attr, safi_t safi, + bool use_json, json_object *json_ar, bool wide) +{ + json_object *json_net = NULL; + int len; + char buff[BUFSIZ]; + + /* Route status display. */ + if (use_json) { + json_net = json_object_new_object(); + } else { + vty_out(vty, " *"); + vty_out(vty, ">"); + vty_out(vty, " "); + } + + /* print prefix and mask */ + if (use_json) { + if (safi == SAFI_EVPN) + bgp_evpn_route2json((struct prefix_evpn *)p, json_net); + else if (p->family == AF_INET || p->family == AF_INET6) { + json_object_string_add( + json_net, "addrPrefix", + inet_ntop(p->family, &p->u.prefix, buff, + BUFSIZ)); + json_object_int_add(json_net, "prefixLen", + p->prefixlen); + json_object_string_addf(json_net, "network", "%pFX", p); + } + } else + route_vty_out_route(dest, p, vty, NULL, wide); + + /* Print attribute */ + if (attr) { + if (use_json) { + if (p->family == AF_INET && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->mp_nexthop_global_in); + else + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->nexthop); + } else if (p->family == AF_INET6 || + BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + json_object_string_addf( + json_net, "nextHopGlobal", "%pI6", + &attr->mp_nexthop_global); + } else if (p->family == AF_EVPN && + !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) { + json_object_string_addf( + json_net, "nextHop", "%pI4", + &attr->mp_nexthop_global_in); + } + + if (use_bgp_med_value(attr, bgp)) { + uint32_t value = bgp_med_value(attr, bgp); + + json_object_int_add(json_net, "metric", value); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + json_object_int_add(json_net, "locPrf", + attr->local_pref); + + json_object_int_add(json_net, "weight", attr->weight); + + /* Print aspath */ + if (attr->aspath) + json_object_string_add(json_net, "path", + attr->aspath->str); + + /* Print origin */ + json_object_string_add( + json_net, "origin", + bgp_origin_long_str[attr->origin]); + } else { + if (p->family == AF_INET && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || + safi == SAFI_EVPN || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) + vty_out(vty, "%-16pI4", + &attr->mp_nexthop_global_in); + else if (wide) + vty_out(vty, "%-41pI4", &attr->nexthop); + else + vty_out(vty, "%-16pI4", &attr->nexthop); + } else if (p->family == AF_INET6 || + BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) { + len = vty_out(vty, "%pI6", + &attr->mp_nexthop_global); + len = wide ? (41 - len) : (16 - len); + if (len < 1) + vty_out(vty, "\n%*s", 36, " "); + else + vty_out(vty, "%*s", len, " "); + } + + if (use_bgp_med_value(attr, bgp)) { + uint32_t value = bgp_med_value(attr, bgp); + + if (wide) + vty_out(vty, "%7u", value); + else + vty_out(vty, "%10u", value); + } else if (wide) + vty_out(vty, " "); + else + vty_out(vty, " "); + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + vty_out(vty, "%7u", attr->local_pref); + else + vty_out(vty, " "); + + vty_out(vty, "%7u ", attr->weight); + + /* Print aspath */ + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + /* Print origin */ + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + } + } + if (use_json) { + struct bgp_path_info *bpi = bgp_dest_get_bgp_path_info(dest); + + json_object_boolean_true_add(json_net, "valid"); + json_object_boolean_true_add(json_net, "best"); + + if (bpi && CHECK_FLAG(bpi->flags, BGP_PATH_MULTIPATH)) + json_object_boolean_true_add(json_net, "multipath"); + json_object_object_addf(json_ar, json_net, "%pFX", p); + } else + vty_out(vty, "\n"); +} + +void route_vty_out_tag(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, safi_t safi, + json_object *json) +{ + json_object *json_out = NULL; + struct attr *attr; + mpls_label_t label = MPLS_INVALID_LABEL; + + if (!path->extra) + return; + + if (json) + json_out = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_out); + + /* print prefix and mask */ + if (json == NULL) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + } + + /* Print attribute */ + attr = path->attr; + if (((p->family == AF_INET) && + ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || + (safi == SAFI_EVPN && !BGP_ATTR_NEXTHOP_AFI_IP6(attr)) || + (!BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + if (json) + json_object_string_addf( + json_out, "mpNexthopGlobalIn", "%pI4", + &attr->mp_nexthop_global_in); + else + vty_out(vty, "%-16pI4", + &attr->mp_nexthop_global_in); + } else { + if (json) + json_object_string_addf(json_out, "nexthop", + "%pI4", &attr->nexthop); + else + vty_out(vty, "%-16pI4", &attr->nexthop); + } + } else if (((p->family == AF_INET6) && + ((safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP))) || + (safi == SAFI_EVPN && BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) || + (BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + char buf_a[512]; + + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL) { + if (json) + json_object_string_addf( + json_out, "mpNexthopGlobalIn", "%pI6", + &attr->mp_nexthop_global); + else + vty_out(vty, "%s", + inet_ntop(AF_INET6, + &attr->mp_nexthop_global, + buf_a, sizeof(buf_a))); + } else if (attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + snprintfrr(buf_a, sizeof(buf_a), "%pI6(%pI6)", + &attr->mp_nexthop_global, + &attr->mp_nexthop_local); + if (json) + json_object_string_add(json_out, + "mpNexthopGlobalLocal", + buf_a); + else + vty_out(vty, "%s", buf_a); + } + } + + if (bgp_path_info_has_valid_label(path)) { + label = decode_label(&path->extra->labels->label[0]); + if (json) { + json_object_int_add(json_out, "notag", label); + json_object_array_add(json, json_out); + } else { + vty_out(vty, "notag/%d", label); + vty_out(vty, "\n"); + } + } else if (!json) + vty_out(vty, "\n"); +} + +void route_vty_out_overlay(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + json_object *json_paths) +{ + struct attr *attr; + json_object *json_path = NULL; + json_object *json_nexthop = NULL; + json_object *json_overlay = NULL; + + if (!path->extra) + return; + + if (json_paths) { + json_path = json_object_new_object(); + json_overlay = json_object_new_object(); + json_nexthop = json_object_new_object(); + } + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + /* print prefix and mask */ + if (!display) + route_vty_out_route(path->net, p, vty, json_path, false); + else + vty_out(vty, "%*s", 17, " "); + + /* Print attribute */ + attr = path->attr; + int af = NEXTHOP_FAMILY(attr->mp_nexthop_len); + + switch (af) { + case AF_INET: + if (!json_path) { + vty_out(vty, "%-16pI4", &attr->mp_nexthop_global_in); + } else { + json_object_string_addf(json_nexthop, "ip", "%pI4", + &attr->mp_nexthop_global_in); + + json_object_string_add(json_nexthop, "afi", "ipv4"); + + json_object_object_add(json_path, "nexthop", + json_nexthop); + } + break; + case AF_INET6: + if (!json_path) { + vty_out(vty, "%pI6(%pI6)", &attr->mp_nexthop_global, + &attr->mp_nexthop_local); + } else { + json_object_string_addf(json_nexthop, "ipv6Global", + "%pI6", + &attr->mp_nexthop_global); + + json_object_string_addf(json_nexthop, "ipv6LinkLocal", + "%pI6", + &attr->mp_nexthop_local); + + json_object_string_add(json_nexthop, "afi", "ipv6"); + + json_object_object_add(json_path, "nexthop", + json_nexthop); + } + break; + default: + if (!json_path) { + vty_out(vty, "?"); + } else { + json_object_string_add(json_nexthop, "error", + "Unsupported address-family"); + } + } + + const struct bgp_route_evpn *eo = bgp_attr_get_evpn_overlay(attr); + + if (!json_path) + vty_out(vty, "/%pIA", &eo->gw_ip); + else + json_object_string_addf(json_overlay, "gw", "%pIA", &eo->gw_ip); + + if (bgp_attr_get_ecommunity(attr)) { + char *mac = NULL; + struct ecommunity_val *routermac = ecommunity_lookup( + bgp_attr_get_ecommunity(attr), ECOMMUNITY_ENCODE_EVPN, + ECOMMUNITY_EVPN_SUBTYPE_ROUTERMAC); + + if (routermac) + mac = ecom_mac2str((char *)routermac->val); + if (mac) { + if (!json_path) { + vty_out(vty, "/%s", mac); + } else { + json_object_string_add(json_overlay, "rmac", + mac); + } + XFREE(MTYPE_TMP, mac); + } + } + + if (!json_path) { + vty_out(vty, "\n"); + } else { + json_object_object_add(json_path, "overlay", json_overlay); + + json_object_array_add(json_paths, json_path); + } +} + +/* dampening route */ +static void damp_route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + afi_t afi, safi_t safi, bool use_json, + json_object *json_paths) +{ + struct attr *attr = path->attr; + int len; + char timebuf[BGP_UPTIME_LEN] = {}; + json_object *json_path = NULL; + + if (use_json) + json_path = json_object_new_object(); + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + /* print prefix and mask */ + if (!use_json) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + + len = vty_out(vty, "%s", path->peer->host); + len = 17 - len; + + if (len < 1) + vty_out(vty, "\n%*s", 34, " "); + else + vty_out(vty, "%*s", len, " "); + + vty_out(vty, "%s ", + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, safi, + use_json, NULL)); + + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + vty_out(vty, "\n"); + } else { + bgp_damp_reuse_time_vty(vty, path, timebuf, BGP_UPTIME_LEN, afi, + safi, use_json, json_path); + + if (attr->aspath) + json_object_string_add(json_path, "asPath", + attr->aspath->str); + + json_object_string_add(json_path, "origin", + bgp_origin_str[attr->origin]); + json_object_string_add(json_path, "peerHost", path->peer->host); + + json_object_array_add(json_paths, json_path); + } +} + +/* flap route */ +static void flap_route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + afi_t afi, safi_t safi, bool use_json, + json_object *json_paths) +{ + struct attr *attr = path->attr; + struct bgp_damp_info *bdi; + char timebuf[BGP_UPTIME_LEN] = {}; + int len; + json_object *json_path = NULL; + + if (!path->extra) + return; + + if (use_json) + json_path = json_object_new_object(); + + bdi = path->extra->damp_info; + + /* short status lead text */ + route_vty_short_status_out(vty, path, p, json_path); + + if (!use_json) { + if (!display) + route_vty_out_route(path->net, p, vty, NULL, false); + else + vty_out(vty, "%*s", 17, " "); + + len = vty_out(vty, "%s", path->peer->host); + len = 16 - len; + if (len < 1) + vty_out(vty, "\n%*s", 33, " "); + else + vty_out(vty, "%*s", len, " "); + + len = vty_out(vty, "%d", bdi->flap); + len = 5 - len; + if (len < 1) + vty_out(vty, " "); + else + vty_out(vty, "%*s", len, " "); + + vty_out(vty, "%s ", peer_uptime(bdi->start_time, timebuf, + BGP_UPTIME_LEN, 0, NULL)); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + vty_out(vty, "%s ", + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, + safi, use_json, NULL)); + else + vty_out(vty, "%*s ", 8, " "); + + if (attr->aspath) + aspath_print_vty(vty, attr->aspath); + + vty_out(vty, "%s", bgp_origin_str[attr->origin]); + + vty_out(vty, "\n"); + } else { + json_object_string_add(json_path, "peerHost", path->peer->host); + json_object_int_add(json_path, "bdiFlap", bdi->flap); + + peer_uptime(bdi->start_time, timebuf, BGP_UPTIME_LEN, use_json, + json_path); + + if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED) + && !CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) + bgp_damp_reuse_time_vty(vty, path, timebuf, + BGP_UPTIME_LEN, afi, safi, + use_json, json_path); + + if (attr->aspath) + json_object_string_add(json_path, "asPath", + attr->aspath->str); + + json_object_string_add(json_path, "origin", + bgp_origin_str[attr->origin]); + + json_object_array_add(json_paths, json_path); + } +} + +static void route_vty_out_advertised_to(struct vty *vty, struct peer *peer, + int *first, const char *header, + json_object *json_adv_to) +{ + json_object *json_peer = NULL; + + if (json_adv_to) { + /* 'advertised-to' is a dictionary of peers we have advertised + * this + * prefix too. The key is the peer's IP or swpX, the value is + * the + * hostname if we know it and "" if not. + */ + json_peer = json_object_new_object(); + + if (peer->hostname) + json_object_string_add(json_peer, "hostname", + peer->hostname); + + if (peer->conf_if) + json_object_object_add(json_adv_to, peer->conf_if, + json_peer); + else + json_object_object_addf(json_adv_to, json_peer, "%pSU", + &peer->connection->su); + } else { + if (*first) { + vty_out(vty, "%s", header); + *first = 0; + } + + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_HOSTNAME)) { + if (peer->conf_if) + vty_out(vty, " %s(%s)", peer->hostname, + peer->conf_if); + else + vty_out(vty, " %s(%pSU)", peer->hostname, + &peer->connection->su); + } else { + if (peer->conf_if) + vty_out(vty, " %s", peer->conf_if); + else + vty_out(vty, " %pSU", &peer->connection->su); + } + } +} + +static void route_vty_out_tx_ids(struct vty *vty, + struct bgp_addpath_info_data *d) +{ + int i; + + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + vty_out(vty, "TX-%s %u%s", bgp_addpath_names(i)->human_name, + d->addpath_tx_id[i], + i < BGP_ADDPATH_MAX - 1 ? " " : "\n"); + } +} + +static void route_vty_out_detail_es_info(struct vty *vty, + struct bgp_path_info *pi, + struct attr *attr, + json_object *json_path) +{ + char esi_buf[ESI_STR_LEN]; + bool es_local = !!CHECK_FLAG(attr->es_flags, ATTR_ES_IS_LOCAL); + bool peer_router = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_ROUTER); + bool peer_active = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_ACTIVE); + bool peer_proxy = !!CHECK_FLAG(attr->es_flags, + ATTR_ES_PEER_PROXY); + esi_to_str(&attr->esi, esi_buf, sizeof(esi_buf)); + if (json_path) { + json_object *json_es_info = NULL; + + json_object_string_add( + json_path, "esi", + esi_buf); + if (es_local || bgp_evpn_attr_is_sync(attr)) { + json_es_info = json_object_new_object(); + if (es_local) + json_object_boolean_true_add( + json_es_info, "localEs"); + if (peer_active) + json_object_boolean_true_add( + json_es_info, "peerActive"); + if (peer_proxy) + json_object_boolean_true_add( + json_es_info, "peerProxy"); + if (peer_router) + json_object_boolean_true_add( + json_es_info, "peerRouter"); + if (attr->mm_sync_seqnum) + json_object_int_add( + json_es_info, "peerSeq", + attr->mm_sync_seqnum); + json_object_object_add( + json_path, "es_info", + json_es_info); + } + } else { + if (bgp_evpn_attr_is_sync(attr)) + vty_out(vty, + " ESI %s %s peer-info: (%s%s%sMM: %d)\n", + esi_buf, + es_local ? "local-es":"", + peer_proxy ? "proxy " : "", + peer_active ? "active ":"", + peer_router ? "router ":"", + attr->mm_sync_seqnum); + else + vty_out(vty, " ESI %s %s\n", + esi_buf, + es_local ? "local-es":""); + } +} + +void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, + const struct prefix *p, struct bgp_path_info *path, + afi_t afi, safi_t safi, + enum rpki_states rpki_curr_state, + json_object *json_paths) +{ + char buf[INET6_ADDRSTRLEN]; + char vni_buf[30] = {}; + struct attr *attr = path->attr; + time_t tbuf; + char timebuf[32]; + json_object *json_bestpath = NULL; + json_object *json_cluster_list = NULL; + json_object *json_cluster_list_list = NULL; + json_object *json_ext_community = NULL; + json_object *json_ext_ipv6_community = NULL; + json_object *json_last_update = NULL; + json_object *json_pmsi = NULL; + json_object *json_nexthop_global = NULL; + json_object *json_nexthop_ll = NULL; + json_object *json_nexthops = NULL; + json_object *json_path = NULL; + json_object *json_peer = NULL; + json_object *json_string = NULL; + json_object *json_adv_to = NULL; + int first = 0; + struct listnode *node, *nnode; + struct peer *peer; + bool addpath_capable; + int has_adj; + unsigned int first_as; + bool nexthop_self = + CHECK_FLAG(path->flags, BGP_PATH_ANNC_NH_SELF) ? true : false; + int i; + char *nexthop_hostname = + bgp_nexthop_hostname(path->peer, path->nexthop); + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + mpls_label_t label = MPLS_INVALID_LABEL; + struct bgp_path_info *bpi_ultimate = + bgp_get_imported_bpi_ultimate(path); + + if (json_paths) { + json_path = json_object_new_object(); + json_peer = json_object_new_object(); + json_nexthop_global = json_object_new_object(); + } + + if (BGP_PATH_INFO_NUM_LABELS(path)) { + bgp_evpn_label2str(path->extra->labels->label, + path->extra->labels->num_labels, vni_buf, + sizeof(vni_buf)); + } + + if (safi == SAFI_EVPN) { + if (!json_paths) + vty_out(vty, " Route %pFX", p); + + if (vni_buf[0]) { + if (json_paths) + json_object_string_add(json_path, "vni", + vni_buf); + else + vty_out(vty, " VNI %s", vni_buf); + } + } + + if (safi == SAFI_EVPN + && attr->evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) { + char gwip_buf[INET6_ADDRSTRLEN]; + + ipaddr2str(&attr->evpn_overlay.gw_ip, gwip_buf, + sizeof(gwip_buf)); + + if (json_paths) + json_object_string_add(json_path, "gatewayIP", + gwip_buf); + else + vty_out(vty, " Gateway IP %s", gwip_buf); + } + + if (safi == SAFI_EVPN && !json_path) + vty_out(vty, "\n"); + + + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->parent && !json_paths) { + struct bgp_path_info *parent_ri; + struct bgp_dest *dest, *pdest; + + parent_ri = + (struct bgp_path_info *)path->extra->vrfleak->parent; + dest = parent_ri->net; + if (dest && dest->pdest) { + pdest = dest->pdest; + if (is_pi_family_evpn(parent_ri)) { + vty_out(vty, " Imported from "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + pdest)); + vty_out(vty, ":%pFX, VNI %s", + (struct prefix_evpn *) + bgp_dest_get_prefix(dest), + vni_buf); + if (CHECK_FLAG(attr->es_flags, ATTR_ES_L3_NHG)) + vty_out(vty, ", L3NHG %s", + CHECK_FLAG( + attr->es_flags, + ATTR_ES_L3_NHG_ACTIVE) + ? "active" + : "inactive"); + vty_out(vty, "\n"); + + } else { + vty_out(vty, " Imported from "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + (struct prefix_rd *)bgp_dest_get_prefix( + pdest)); + vty_out(vty, ":%pFX\n", + (struct prefix_evpn *) + bgp_dest_get_prefix(dest)); + } + } + } + + /* Line1 display AS-path, Aggregator */ + if (attr->aspath) { + if (json_paths) { + if (!attr->aspath->json) + aspath_str_update(attr->aspath, true); + json_object_lock(attr->aspath->json); + json_object_object_add(json_path, "aspath", + attr->aspath->json); + } else { + if (attr->aspath->segments) + vty_out(vty, " %s", attr->aspath->str); + else + vty_out(vty, " Local"); + } + } + + if (CHECK_FLAG(path->flags, BGP_PATH_REMOVED)) { + if (json_paths) + json_object_boolean_true_add(json_path, "removed"); + else + vty_out(vty, ", (removed)"); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_STALE)) { + if (json_paths) + json_object_boolean_true_add(json_path, "stale"); + else + vty_out(vty, ", (stale)"); + } + + if (CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) { + if (json_paths) { + json_object_int_add(json_path, "aggregatorAs", + attr->aggregator_as); + json_object_string_addf(json_path, "aggregatorId", + "%pI4", &attr->aggregator_addr); + } else { + vty_out(vty, ", (aggregated by %u %pI4)", + attr->aggregator_as, &attr->aggregator_addr); + } + } + + if (CHECK_FLAG(path->peer->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "rxedFromRrClient"); + else + vty_out(vty, ", (Received from a RR-client)"); + } + + if (CHECK_FLAG(path->peer->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "rxedFromRsClient"); + else + vty_out(vty, ", (Received from a RS-client)"); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "dampeningHistoryEntry"); + else + vty_out(vty, ", (history entry)"); + } else if (CHECK_FLAG(path->flags, BGP_PATH_DAMPED)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "dampeningSuppressed"); + else + vty_out(vty, ", (suppressed due to dampening)"); + } + + if (!json_paths) + vty_out(vty, "\n"); + + /* Line2 display Next-hop, Neighbor, Router-id */ + /* Display the nexthop */ + + if ((p->family == AF_INET || p->family == AF_ETHERNET || + p->family == AF_EVPN) && + (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN || + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + if (json_paths) { + json_object_string_addf( + json_nexthop_global, "ip", "%pI4", + &attr->mp_nexthop_global_in); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI4(%s)", + &attr->mp_nexthop_global_in, + nexthop_hostname); + else + vty_out(vty, " %pI4", + &attr->mp_nexthop_global_in); + } + } else { + if (json_paths) { + json_object_string_addf(json_nexthop_global, + "ip", "%pI4", + &attr->nexthop); + + if (path->peer->hostname) + json_object_string_add( + json_nexthop_global, "hostname", + path->peer->hostname); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI4(%s)", + &attr->nexthop, + nexthop_hostname); + else + vty_out(vty, " %pI4", + &attr->nexthop); + } + } + + if (json_paths) + json_object_string_add(json_nexthop_global, "afi", + "ipv4"); + } else { + if (json_paths) { + json_object_string_addf(json_nexthop_global, "ip", + "%pI6", + &attr->mp_nexthop_global); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_global, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_global, "afi", + "ipv6"); + json_object_string_add(json_nexthop_global, "scope", + "global"); + } else { + if (nexthop_hostname) + vty_out(vty, " %pI6(%s)", + &attr->mp_nexthop_global, + nexthop_hostname); + else + vty_out(vty, " %pI6", + &attr->mp_nexthop_global); + } + } + + /* Display the IGP cost or 'inaccessible' */ + if (!CHECK_FLAG(bpi_ultimate->flags, BGP_PATH_VALID)) { + bool import = CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK); + + if (json_paths) { + json_object_boolean_false_add(json_nexthop_global, + "accessible"); + json_object_boolean_add(json_nexthop_global, + "importCheckEnabled", import); + } else { + vty_out(vty, " (inaccessible%s)", + import ? ", import-check enabled" : ""); + } + } else { + if (bpi_ultimate->extra && bpi_ultimate->extra->igpmetric) { + if (json_paths) + json_object_int_add( + json_nexthop_global, "metric", + bpi_ultimate->extra->igpmetric); + else + vty_out(vty, " (metric %u)", + bpi_ultimate->extra->igpmetric); + } + + /* IGP cost is 0, display this only for json */ + else { + if (json_paths) + json_object_int_add(json_nexthop_global, + "metric", 0); + } + + if (json_paths) + json_object_boolean_true_add(json_nexthop_global, + "accessible"); + } + + /* Display peer "from" output */ + /* This path was originated locally */ + if (path->peer == bgp->peer_self) { + + if (safi == SAFI_EVPN || (p->family == AF_INET && + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr))) { + if (json_paths) + json_object_string_add(json_peer, "peerId", + "0.0.0.0"); + else + vty_out(vty, " from 0.0.0.0 "); + } else { + if (json_paths) + json_object_string_add(json_peer, "peerId", + "::"); + else + vty_out(vty, " from :: "); + } + + if (json_paths) + json_object_string_addf(json_peer, "routerId", "%pI4", + &bgp->router_id); + else + vty_out(vty, "(%pI4)", &bgp->router_id); + } + + /* We RXed this path from one of our peers */ + else { + + if (json_paths) { + json_object_string_addf(json_peer, "peerId", "%pSU", + &path->peer->connection->su); + json_object_string_addf(json_peer, "routerId", "%pI4", + &path->peer->remote_id); + + if (path->peer->hostname) + json_object_string_add(json_peer, "hostname", + path->peer->hostname); + + if (path->peer->domainname) + json_object_string_add(json_peer, "domainname", + path->peer->domainname); + + if (path->peer->conf_if) + json_object_string_add(json_peer, "interface", + path->peer->conf_if); + } else { + if (path->peer->conf_if) { + if (path->peer->hostname + && CHECK_FLAG(path->peer->bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + vty_out(vty, " from %s(%s)", + path->peer->hostname, + path->peer->conf_if); + else + vty_out(vty, " from %s", + path->peer->conf_if); + } else { + if (path->peer->hostname + && CHECK_FLAG(path->peer->bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + vty_out(vty, " from %s(%s)", + path->peer->hostname, + path->peer->host); + else + vty_out(vty, " from %pSU", + &path->peer->connection->su); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + vty_out(vty, " (%pI4)", &attr->originator_id); + else + vty_out(vty, " (%pI4)", &path->peer->remote_id); + } + } + + /* + * Note when vrfid of nexthop is different from that of prefix + */ + if (path->extra && path->extra->vrfleak && + path->extra->vrfleak->bgp_orig) { + vrf_id_t nexthop_vrfid = path->extra->vrfleak->bgp_orig->vrf_id; + + if (json_paths) { + const char *vn; + + if (path->extra->vrfleak->bgp_orig->inst_type == + BGP_INSTANCE_TYPE_DEFAULT) + vn = VRF_DEFAULT_NAME; + else + vn = path->extra->vrfleak->bgp_orig->name; + + json_object_string_add(json_path, "nhVrfName", vn); + + if (nexthop_vrfid == VRF_UNKNOWN) { + json_object_int_add(json_path, "nhVrfId", -1); + } else { + json_object_int_add(json_path, "nhVrfId", + (int)nexthop_vrfid); + } + } else { + if (nexthop_vrfid == VRF_UNKNOWN) + vty_out(vty, " vrf ?"); + else { + struct vrf *vrf; + + vrf = vrf_lookup_by_id(nexthop_vrfid); + vty_out(vty, " vrf %s(%u)", + VRF_LOGNAME(vrf), nexthop_vrfid); + } + } + } + + if (nexthop_self) { + if (json_paths) { + json_object_boolean_true_add(json_path, + "announceNexthopSelf"); + } else { + vty_out(vty, " announce-nh-self"); + } + } + + if (!json_paths) + vty_out(vty, "\n"); + + /* display the link-local nexthop */ + if (attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + if (json_paths) { + json_nexthop_ll = json_object_new_object(); + json_object_string_addf(json_nexthop_ll, "ip", "%pI6", + &attr->mp_nexthop_local); + + if (path->peer->hostname) + json_object_string_add(json_nexthop_ll, + "hostname", + path->peer->hostname); + + json_object_string_add(json_nexthop_ll, "afi", "ipv6"); + json_object_string_add(json_nexthop_ll, "scope", + "link-local"); + + json_object_boolean_true_add(json_nexthop_ll, + "accessible"); + + if (!CHECK_FLAG(attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + json_object_boolean_true_add(json_nexthop_ll, + "used"); + else + json_object_boolean_true_add( + json_nexthop_global, "used"); + } else { + vty_out(vty, " (%s) %s\n", + inet_ntop(AF_INET6, &attr->mp_nexthop_local, + buf, INET6_ADDRSTRLEN), + CHECK_FLAG(attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL) + ? "(prefer-global)" + : "(used)"); + } + } + /* If we do not have a link-local nexthop then we must flag the + global as "used" */ + else { + if (json_paths) + json_object_boolean_true_add(json_nexthop_global, + "used"); + } + + if (safi == SAFI_EVPN && + bgp_evpn_is_esi_valid(&attr->esi)) { + route_vty_out_detail_es_info(vty, path, attr, json_path); + } + + /* Line 3 display Origin, Med, Locpref, Weight, Tag, valid, + * Int/Ext/Local, Atomic, best */ + if (json_paths) + json_object_string_add(json_path, "origin", + bgp_origin_long_str[attr->origin]); + else + vty_out(vty, " Origin %s", + bgp_origin_long_str[attr->origin]); + + if (use_bgp_med_value(attr, bgp)) { + uint32_t value = bgp_med_value(attr, bgp); + + if (json_paths) + json_object_int_add(json_path, "metric", value); + else + vty_out(vty, ", metric %u", value); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + if (json_paths) + json_object_int_add(json_path, "locPrf", + attr->local_pref); + else + vty_out(vty, ", localpref %u", attr->local_pref); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_AIGP)) { + if (json_paths) + json_object_int_add(json_path, "aigpMetric", + bgp_attr_get_aigp_metric(attr)); + else + vty_out(vty, ", aigp-metric %" PRIu64, + bgp_attr_get_aigp_metric(attr)); + } + + if (attr->weight != 0) { + if (json_paths) + json_object_int_add(json_path, "weight", attr->weight); + else + vty_out(vty, ", weight %u", attr->weight); + } + + if (attr->tag != 0) { + if (json_paths) + json_object_int_add(json_path, "tag", attr->tag); + else + vty_out(vty, ", tag %" ROUTE_TAG_PRI, attr->tag); + } + + if (!CHECK_FLAG(path->flags, BGP_PATH_VALID)) { + if (json_paths) + json_object_boolean_false_add(json_path, "valid"); + else + vty_out(vty, ", invalid"); + } else if (!CHECK_FLAG(path->flags, BGP_PATH_HISTORY)) { + if (json_paths) + json_object_boolean_true_add(json_path, "valid"); + else + vty_out(vty, ", valid"); + } + + if (json_paths) + json_object_int_add(json_path, "version", bn->version); + + if (path->peer != bgp->peer_self) { + if (path->peer->as == path->peer->local_as) { + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (json_paths) + json_object_string_add( + json_peer, "type", + "confed-internal"); + else + vty_out(vty, ", confed-internal"); + } else { + if (json_paths) + json_object_string_add( + json_peer, "type", "internal"); + else + vty_out(vty, ", internal"); + } + } else { + if (bgp_confederation_peers_check(bgp, + path->peer->as)) { + if (json_paths) + json_object_string_add( + json_peer, "type", + "confed-external"); + else + vty_out(vty, ", confed-external"); + } else { + if (json_paths) + json_object_string_add( + json_peer, "type", + (path->peer->sub_sort == + BGP_PEER_EBGP_OAD) + ? "external (oad)" + : "external"); + else + vty_out(vty, ", %s", + (path->peer->sub_sort == + BGP_PEER_EBGP_OAD) + ? "external (oad)" + : "external"); + } + } + } else if (path->sub_type == BGP_ROUTE_AGGREGATE) { + if (json_paths) { + json_object_boolean_true_add(json_path, "aggregated"); + json_object_boolean_true_add(json_path, "local"); + } else { + vty_out(vty, ", aggregated, local"); + } + } else if (path->type != ZEBRA_ROUTE_BGP) { + if (json_paths) + json_object_boolean_true_add(json_path, "sourced"); + else + vty_out(vty, ", sourced"); + } else { + if (json_paths) { + json_object_boolean_true_add(json_path, "sourced"); + json_object_boolean_true_add(json_path, "local"); + } else { + vty_out(vty, ", sourced, local"); + } + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE)) { + if (json_paths) + json_object_boolean_true_add(json_path, + "atomicAggregate"); + else + vty_out(vty, ", atomic-aggregate"); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_OTC)) { + if (json_paths) + json_object_int_add(json_path, "otc", attr->otc); + else + vty_out(vty, ", otc %u", attr->otc); + } + + if (CHECK_FLAG(path->flags, BGP_PATH_MULTIPATH) + || (CHECK_FLAG(path->flags, BGP_PATH_SELECTED) + && bgp_path_info_mpath_count(path))) { + if (json_paths) + json_object_boolean_true_add(json_path, "multipath"); + else + vty_out(vty, ", multipath"); + } + + // Mark the bestpath(s) + if (CHECK_FLAG(path->flags, BGP_PATH_DMED_SELECTED)) { + first_as = aspath_get_first_as(attr->aspath); + + if (json_paths) { + if (!json_bestpath) + json_bestpath = json_object_new_object(); + json_object_int_add(json_bestpath, "bestpathFromAs", + first_as); + } else { + if (first_as) + vty_out(vty, ", bestpath-from-AS %u", first_as); + else + vty_out(vty, ", bestpath-from-AS Local"); + } + } + + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) { + if (json_paths) { + if (!json_bestpath) + json_bestpath = json_object_new_object(); + json_object_boolean_true_add(json_bestpath, "overall"); + json_object_string_add( + json_bestpath, "selectionReason", + bgp_path_selection_reason2str(bn->reason)); + } else { + vty_out(vty, ", best"); + vty_out(vty, " (%s)", + bgp_path_selection_reason2str(bn->reason)); + } + } + + if (rpki_curr_state != RPKI_NOT_BEING_USED) { + if (json_paths) + json_object_string_add( + json_path, "rpkiValidationState", + bgp_rpki_validation2str(rpki_curr_state)); + else + vty_out(vty, ", rpki validation-state: %s", + bgp_rpki_validation2str(rpki_curr_state)); + } + + if (json_bestpath) + json_object_object_add(json_path, "bestpath", json_bestpath); + + if (!json_paths) + vty_out(vty, "\n"); + + /* Line 4 display Community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) { + if (json_paths) { + if (!bgp_attr_get_community(attr)->json) + community_str(bgp_attr_get_community(attr), + true, true); + json_object_lock(bgp_attr_get_community(attr)->json); + json_object_object_add( + json_path, "community", + bgp_attr_get_community(attr)->json); + } else { + vty_out(vty, " Community: %s\n", + bgp_attr_get_community(attr)->str); + } + } + + /* Line 5 display Extended-community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_EXT_COMMUNITIES)) { + if (json_paths) { + json_ext_community = json_object_new_object(); + json_object_string_add( + json_ext_community, "string", + bgp_attr_get_ecommunity(attr)->str); + json_object_object_add(json_path, "extendedCommunity", + json_ext_community); + } else { + vty_out(vty, " Extended Community: %s\n", + bgp_attr_get_ecommunity(attr)->str); + } + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_IPV6_EXT_COMMUNITIES)) { + if (json_paths) { + json_ext_ipv6_community = json_object_new_object(); + json_object_string_add(json_ext_ipv6_community, "string", + bgp_attr_get_ipv6_ecommunity(attr) + ->str); + json_object_object_add(json_path, + "extendedIpv6Community", + json_ext_ipv6_community); + } else { + vty_out(vty, " Extended IPv6 Community: %s\n", + bgp_attr_get_ipv6_ecommunity(attr)->str); + } + } + + /* Line 6 display Large community */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) { + if (json_paths) { + if (!bgp_attr_get_lcommunity(attr)->json) + lcommunity_str(bgp_attr_get_lcommunity(attr), + true, true); + json_object_lock(bgp_attr_get_lcommunity(attr)->json); + json_object_object_add( + json_path, "largeCommunity", + bgp_attr_get_lcommunity(attr)->json); + } else { + vty_out(vty, " Large Community: %s\n", + bgp_attr_get_lcommunity(attr)->str); + } + } + + /* Line 7 display Originator, Cluster-id */ + if ((attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) + || (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST))) { + char buf[BUFSIZ] = {0}; + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID)) { + if (json_paths) + json_object_string_addf(json_path, + "originatorId", "%pI4", + &attr->originator_id); + else + vty_out(vty, " Originator: %pI4", + &attr->originator_id); + } + + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_CLUSTER_LIST)) { + struct cluster_list *cluster = + bgp_attr_get_cluster(attr); + int i; + + if (json_paths) { + json_cluster_list = json_object_new_object(); + json_cluster_list_list = + json_object_new_array(); + + for (i = 0; i < cluster->length / 4; i++) { + json_string = json_object_new_string( + inet_ntop(AF_INET, + &cluster->list[i], + buf, sizeof(buf))); + json_object_array_add( + json_cluster_list_list, + json_string); + } + + /* + * struct cluster_list does not have + * "str" variable like aspath and community + * do. Add this someday if someone asks + * for it. + * json_object_string_add(json_cluster_list, + * "string", cluster->str); + */ + json_object_object_add(json_cluster_list, + "list", + json_cluster_list_list); + json_object_object_add(json_path, "clusterList", + json_cluster_list); + } else { + vty_out(vty, ", Cluster list: "); + + for (i = 0; i < cluster->length / 4; i++) { + vty_out(vty, "%pI4 ", + &cluster->list[i]); + } + } + } + + if (!json_paths) + vty_out(vty, "\n"); + } + + if (path->extra && path->extra->damp_info) + bgp_damp_info_vty(vty, bgp, path, afi, safi, json_path); + + /* Remote Label */ + if (bgp_path_info_has_valid_label(path) && + (safi != SAFI_EVPN && !is_route_parent_evpn(path))) { + mpls_lse_decode(path->extra->labels->label[0], &label, &ttl, + &exp, &bos); + + if (json_paths) + json_object_int_add(json_path, "remoteLabel", label); + else + vty_out(vty, " Remote label: %d\n", label); + } + + /* Remote SID */ + if ((path->attr->srv6_l3vpn || path->attr->srv6_vpn) && + safi != SAFI_EVPN) { + struct in6_addr *sid_tmp = + path->attr->srv6_l3vpn ? (&path->attr->srv6_l3vpn->sid) + : (&path->attr->srv6_vpn->sid); + if (json_paths) + json_object_string_addf(json_path, "remoteSid", "%pI6", + sid_tmp); + else + vty_out(vty, " Remote SID: %pI6\n", sid_tmp); + } + + /* Label Index */ + if (attr->label_index != BGP_INVALID_LABEL_INDEX) { + if (json_paths) + json_object_int_add(json_path, "labelIndex", + attr->label_index); + else + vty_out(vty, " Label Index: %d\n", + attr->label_index); + } + + /* Line 8 display Addpath IDs */ + if (path->addpath_rx_id + || bgp_addpath_info_has_ids(&path->tx_addpath)) { + if (json_paths) { + json_object_int_add(json_path, "addpathRxId", + path->addpath_rx_id); + + /* Keep backwards compatibility with the old API + * by putting TX All's ID in the old field + */ + json_object_int_add( + json_path, "addpathTxId", + path->tx_addpath + .addpath_tx_id[BGP_ADDPATH_ALL]); + + /* ... but create a specific field for each + * strategy + */ + for (i = 0; i < BGP_ADDPATH_MAX; i++) { + json_object_int_add( + json_path, + bgp_addpath_names(i)->id_json_name, + path->tx_addpath.addpath_tx_id[i]); + } + } else { + vty_out(vty, " AddPath ID: RX %u, ", + path->addpath_rx_id); + + route_vty_out_tx_ids(vty, &path->tx_addpath); + } + } + + /* If we used addpath to TX a non-bestpath we need to display + * "Advertised to" on a path-by-path basis + */ + if (bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + first = 1; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + addpath_capable = + bgp_addpath_encode_tx(peer, afi, safi); + has_adj = bgp_adj_out_lookup( + peer, path->net, + bgp_addpath_id_for_peer(peer, afi, safi, + &path->tx_addpath)); + + if ((addpath_capable && has_adj) + || (!addpath_capable && has_adj + && CHECK_FLAG(path->flags, + BGP_PATH_SELECTED))) { + if (json_path && !json_adv_to) + json_adv_to = json_object_new_object(); + + route_vty_out_advertised_to( + vty, peer, &first, + " Advertised to:", json_adv_to); + } + } + + if (json_path) { + if (json_adv_to) { + json_object_object_add( + json_path, "advertisedTo", json_adv_to); + } + } else { + if (!first) { + vty_out(vty, "\n"); + } + } + } + + /* Line 9 display Uptime */ + tbuf = time(NULL) - (monotime(NULL) - path->uptime); + if (json_paths) { + json_last_update = json_object_new_object(); + json_object_int_add(json_last_update, "epoch", tbuf); + json_object_string_add(json_last_update, "string", + ctime_r(&tbuf, timebuf)); + json_object_object_add(json_path, "lastUpdate", + json_last_update); + } else + vty_out(vty, " Last update: %s", ctime_r(&tbuf, timebuf)); + + /* Line 10 display PMSI tunnel attribute, if present */ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { + const char *str = lookup_msg(bgp_pmsi_tnltype_str, + bgp_attr_get_pmsi_tnl_type(attr), + PMSI_TNLTYPE_STR_DEFAULT); + + if (json_paths) { + json_pmsi = json_object_new_object(); + json_object_string_add(json_pmsi, "tunnelType", str); + json_object_int_add(json_pmsi, "label", + label2vni(&attr->label)); + json_object_object_add(json_path, "pmsi", json_pmsi); + } else + vty_out(vty, " PMSI Tunnel Type: %s, label: %d\n", + str, label2vni(&attr->label)); + } + + if (path->peer->connection->t_gr_restart && + CHECK_FLAG(path->flags, BGP_PATH_STALE)) { + unsigned long gr_remaining = event_timer_remain_second( + path->peer->connection->t_gr_restart); + + if (json_paths) { + json_object_int_add(json_path, + "gracefulRestartSecondsRemaining", + gr_remaining); + } else + vty_out(vty, + " Time until Graceful Restart stale route deleted: %lu\n", + gr_remaining); + } + + if (path->peer->t_llgr_stale[afi][safi] && + bgp_attr_get_community(attr) && + community_include(bgp_attr_get_community(attr), + COMMUNITY_LLGR_STALE)) { + unsigned long llgr_remaining = event_timer_remain_second( + path->peer->t_llgr_stale[afi][safi]); + + if (json_paths) { + json_object_int_add(json_path, "llgrSecondsRemaining", + llgr_remaining); + } else + vty_out(vty, + " Time until Long-lived stale route deleted: %lu\n", + llgr_remaining); + } + + /* Output some debug about internal state of the dest flags */ + if (json_paths) { + if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED)) + json_object_boolean_true_add(json_path, "processScheduled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_USER_CLEAR)) + json_object_boolean_true_add(json_path, "userCleared"); + if (CHECK_FLAG(bn->flags, BGP_NODE_LABEL_CHANGED)) + json_object_boolean_true_add(json_path, "labelChanged"); + if (CHECK_FLAG(bn->flags, BGP_NODE_REGISTERED_FOR_LABEL)) + json_object_boolean_true_add(json_path, "registeredForLabel"); + if (CHECK_FLAG(bn->flags, BGP_NODE_SELECT_DEFER)) + json_object_boolean_true_add(json_path, "selectDefered"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED)) + json_object_boolean_true_add(json_path, "fibInstalled"); + if (CHECK_FLAG(bn->flags, BGP_NODE_FIB_INSTALL_PENDING)) + json_object_boolean_true_add(json_path, "fibPending"); + + if (json_nexthop_global || json_nexthop_ll) { + json_nexthops = json_object_new_array(); + + if (json_nexthop_global) + json_object_array_add(json_nexthops, + json_nexthop_global); + + if (json_nexthop_ll) + json_object_array_add(json_nexthops, + json_nexthop_ll); + + json_object_object_add(json_path, "nexthops", + json_nexthops); + } + + json_object_object_add(json_path, "peer", json_peer); + json_object_array_add(json_paths, json_path); + } +} + +#define BGP_SHOW_HEADER_CSV "Flags, Network, Next Hop, Metric, LocPrf, Weight, Path" +#define BGP_SHOW_DAMP_HEADER " Network From Reuse Path\n" +#define BGP_SHOW_FLAP_HEADER " Network From Flaps Duration Reuse Path\n" + +static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, + afi_t afi, safi_t safi, enum bgp_show_type type, + bool use_json); +static int bgp_show_community(struct vty *vty, struct bgp *bgp, + const char *comstr, int exact, afi_t afi, + safi_t safi, uint16_t show_flags); + +static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_table *table, enum bgp_show_type type, + void *output_arg, const char *rd, int is_last, + unsigned long *output_cum, unsigned long *total_cum, + unsigned long *json_header_depth, uint16_t show_flags, + enum rpki_states rpki_target_state) +{ + struct bgp_path_info *pi; + struct bgp_dest *dest; + bool header = true; + bool json_detail_header = false; + int display; + unsigned long output_count = 0; + unsigned long total_count = 0; + struct prefix *p; + json_object *json_paths = NULL; + int first = 1; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + bool wide = CHECK_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + bool all = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + bool detail_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON_DETAIL); + bool detail_routes = CHECK_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (output_cum && *output_cum != 0) + header = false; + + if (use_json && !*json_header_depth) { + if (all) + *json_header_depth = 1; + else { + vty_out(vty, "{\n"); + *json_header_depth = 2; + } + vty_out(vty, + " \"vrfId\": %d,\n \"vrfName\": \"%s\",\n \"tableVersion\": %" PRId64 + ",\n \"routerId\": \"%pI4\",\n \"defaultLocPrf\": %u,\n" + " \"localAS\": ", + bgp->vrf_id == VRF_UNKNOWN ? -1 : (int)bgp->vrf_id, + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT + ? VRF_DEFAULT_NAME + : bgp->name, + table->version, &bgp->router_id, + bgp->default_local_pref); + if ((bgp->asnotation == ASNOTATION_PLAIN) || + ((bgp->asnotation == ASNOTATION_DOT) && + (bgp->as < UINT16_MAX))) + vty_out(vty, "%u", bgp->as); + else { + vty_out(vty, "\""); + vty_out(vty, ASN_FORMAT(bgp->asnotation), &bgp->as); + vty_out(vty, "\""); + } + vty_out(vty, ",\n \"routes\": { "); + if (rd) { + vty_out(vty, " \"routeDistinguishers\" : {"); + ++*json_header_depth; + } + } + + if (use_json && rd) { + vty_out(vty, " \"%s\" : { ", rd); + } + + /* Check for 'json detail', where we need header output once per dest */ + if (use_json && detail_json && type != bgp_show_type_dampend_paths && + type != bgp_show_type_damp_neighbor && + type != bgp_show_type_flap_statistics && + type != bgp_show_type_flap_neighbor) + json_detail_header = true; + + /* Start processing of routes. */ + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + enum rpki_states rpki_curr_state = RPKI_NOT_BEING_USED; + bool json_detail_header_used = false; + + pi = bgp_dest_get_bgp_path_info(dest); + if (pi == NULL) + continue; + + display = 0; + if (use_json) + json_paths = json_object_new_array(); + else + json_paths = NULL; + + for (; pi; pi = pi->next) { + struct community *picomm = NULL; + + picomm = bgp_attr_get_community(pi->attr); + + total_count++; + + if (type == bgp_show_type_prefix_version) { + uint32_t version = + strtoul(output_arg, NULL, 10); + if (dest->version < version) + continue; + } + + if (type == bgp_show_type_community_alias) { + char *alias = output_arg; + char **communities; + int num; + bool found = false; + + if (picomm) { + frrstr_split(picomm->str, " ", + &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias( + communities[i]); + if (!found + && strcmp(alias, com2alias) + == 0) + found = true; + XFREE(MTYPE_TMP, + communities[i]); + } + XFREE(MTYPE_TMP, communities); + } + + if (!found && + bgp_attr_get_lcommunity(pi->attr)) { + frrstr_split(bgp_attr_get_lcommunity( + pi->attr) + ->str, + " ", &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias( + communities[i]); + if (!found + && strcmp(alias, com2alias) + == 0) + found = true; + XFREE(MTYPE_TMP, + communities[i]); + } + XFREE(MTYPE_TMP, communities); + } + + if (!found) + continue; + } + + if (type == bgp_show_type_rpki) { + if (dest_p->family == AF_INET + || dest_p->family == AF_INET6) + rpki_curr_state = hook_call( + bgp_rpki_prefix_status, + pi->peer, pi->attr, dest_p); + if (rpki_target_state != RPKI_NOT_BEING_USED + && rpki_curr_state != rpki_target_state) + continue; + } + + if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor + || type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) { + if (!(pi->extra && pi->extra->damp_info)) + continue; + } + if (type == bgp_show_type_regexp) { + regex_t *regex = output_arg; + + if (bgp_regexec(regex, pi->attr->aspath) + == REG_NOMATCH) + continue; + } + if (type == bgp_show_type_prefix_list) { + struct prefix_list *plist = output_arg; + + if (prefix_list_apply(plist, dest_p) + != PREFIX_PERMIT) + continue; + } + if (type == bgp_show_type_access_list) { + struct access_list *alist = output_arg; + + if (access_list_apply(alist, dest_p) != + FILTER_PERMIT) + continue; + } + if (type == bgp_show_type_filter_list) { + struct as_list *as_list = output_arg; + + if (as_list_apply(as_list, pi->attr->aspath) + != AS_FILTER_PERMIT) + continue; + } + if (type == bgp_show_type_route_map) { + struct route_map *rmap = output_arg; + struct bgp_path_info path; + struct bgp_path_info_extra extra; + struct attr dummy_attr = {}; + route_map_result_t ret; + + dummy_attr = *pi->attr; + + prep_for_rmap_apply(&path, &extra, dest, pi, + pi->peer, &dummy_attr); + + ret = route_map_apply(rmap, dest_p, &path); + bgp_attr_flush(&dummy_attr); + if (ret == RMAP_DENYMATCH) + continue; + } + if (type == bgp_show_type_neighbor + || type == bgp_show_type_flap_neighbor + || type == bgp_show_type_damp_neighbor) { + union sockunion *su = output_arg; + + if (pi->peer == NULL + || pi->peer->su_remote == NULL + || !sockunion_same(pi->peer->su_remote, su)) + continue; + } + if (type == bgp_show_type_cidr_only) { + uint32_t destination; + + destination = ntohl(dest_p->u.prefix4.s_addr); + if (IN_CLASSC(destination) + && dest_p->prefixlen == 24) + continue; + if (IN_CLASSB(destination) + && dest_p->prefixlen == 16) + continue; + if (IN_CLASSA(destination) + && dest_p->prefixlen == 8) + continue; + } + if (type == bgp_show_type_prefix_longer) { + p = output_arg; + if (!prefix_match(p, dest_p)) + continue; + } + if (type == bgp_show_type_community_all) { + if (!picomm) + continue; + } + if (type == bgp_show_type_community) { + struct community *com = output_arg; + + if (!picomm || !community_match(picomm, com)) + continue; + } + if (type == bgp_show_type_community_exact) { + struct community *com = output_arg; + + if (!picomm || !community_cmp(picomm, com)) + continue; + } + if (type == bgp_show_type_community_list) { + struct community_list *list = output_arg; + + if (!community_list_match(picomm, list)) + continue; + } + if (type == bgp_show_type_community_list_exact) { + struct community_list *list = output_arg; + + if (!community_list_exact_match(picomm, list)) + continue; + } + if (type == bgp_show_type_lcommunity) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity(pi->attr) || + !lcommunity_match( + bgp_attr_get_lcommunity(pi->attr), + lcom)) + continue; + } + + if (type == bgp_show_type_lcommunity_exact) { + struct lcommunity *lcom = output_arg; + + if (!bgp_attr_get_lcommunity(pi->attr) || + !lcommunity_cmp( + bgp_attr_get_lcommunity(pi->attr), + lcom)) + continue; + } + if (type == bgp_show_type_lcommunity_list) { + struct community_list *list = output_arg; + + if (!lcommunity_list_match( + bgp_attr_get_lcommunity(pi->attr), + list)) + continue; + } + if (type + == bgp_show_type_lcommunity_list_exact) { + struct community_list *list = output_arg; + + if (!lcommunity_list_exact_match( + bgp_attr_get_lcommunity(pi->attr), + list)) + continue; + } + if (type == bgp_show_type_lcommunity_all) { + if (!bgp_attr_get_lcommunity(pi->attr)) + continue; + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) { + if (!CHECK_FLAG(pi->flags, BGP_PATH_DAMPED) + || CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + continue; + } + if (type == bgp_show_type_self_originated) { + if (pi->peer != bgp->peer_self) + continue; + } + + if (!use_json && header) { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + table->version, &bgp->router_id); + if (bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + bgp->default_local_pref); + vty_out(vty, "local AS "); + vty_out(vty, ASN_FORMAT(bgp->asnotation), + &bgp->as); + vty_out(vty, "\n"); + if (!detail_routes) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) + vty_out(vty, BGP_SHOW_DAMP_HEADER); + else if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor) + vty_out(vty, BGP_SHOW_FLAP_HEADER); + else if (!detail_routes) + vty_out(vty, (wide ? BGP_SHOW_HEADER_WIDE + : BGP_SHOW_HEADER)); + header = false; + + } + if (rd != NULL && !display && !output_count) { + if (!use_json) + vty_out(vty, + "Route Distinguisher: %s\n", + rd); + } + if (type == bgp_show_type_dampend_paths + || type == bgp_show_type_damp_neighbor) + damp_route_vty_out(vty, dest_p, pi, display, + afi, safi, use_json, + json_paths); + else if (type == bgp_show_type_flap_statistics + || type == bgp_show_type_flap_neighbor) + flap_route_vty_out(vty, dest_p, pi, display, + afi, safi, use_json, + json_paths); + else { + if (detail_routes || detail_json) { + const struct prefix_rd *prd = NULL; + + if (dest->pdest) + prd = bgp_rd_from_dest( + dest->pdest, safi); + + if (!use_json) + route_vty_out_detail_header( + vty, bgp, dest, + bgp_dest_get_prefix( + dest), + prd, table->afi, safi, + NULL, false); + + route_vty_out_detail( + vty, bgp, dest, dest_p, pi, + family2afi(dest_p->family), + safi, RPKI_NOT_BEING_USED, + json_paths); + } else { + route_vty_out(vty, dest_p, pi, display, + safi, json_paths, wide); + } + } + display++; + } + + if (display) { + output_count++; + if (!use_json) + continue; + + /* encode prefix */ + if (dest_p->family == AF_FLOWSPEC) { + char retstr[BGP_FLOWSPEC_STRING_DISPLAY_MAX]; + + + bgp_fs_nlri_get_string( + (unsigned char *) + dest_p->u.prefix_flowspec.ptr, + dest_p->u.prefix_flowspec.prefixlen, + retstr, NLRI_STRING_FORMAT_MIN, NULL, + family2afi(dest_p->u + .prefix_flowspec.family)); + if (first) + vty_out(vty, "\"%s/%d\": ", retstr, + dest_p->u.prefix_flowspec + .prefixlen); + else + vty_out(vty, ",\"%s/%d\": ", retstr, + dest_p->u.prefix_flowspec + .prefixlen); + } else { + if (first) + vty_out(vty, "\"%pFX\": ", dest_p); + else + vty_out(vty, ",\"%pFX\": ", dest_p); + } + + /* This is used for 'json detail' vty keywords. + * + * In plain 'json' the per-prefix header is encoded + * as a standalone dictionary in the first json_paths + * array element: + * "": [{header}, {path-1}, {path-N}] + * (which is confusing and borderline broken) + * + * For 'json detail' this changes the value + * of each prefix-key to be a dictionary where each + * header item has its own key, and json_paths is + * tucked under the "paths" key: + * "": { + * "": , + * "": , + * "paths": [{path-1}, {path-N}] + * } + */ + if (json_detail_header && json_paths != NULL) { + const struct prefix_rd *prd; + + /* Start per-prefix dictionary */ + vty_out(vty, "{\n"); + + prd = bgp_rd_from_dest(dest, safi); + + route_vty_out_detail_header( + vty, bgp, dest, + bgp_dest_get_prefix(dest), prd, + table->afi, safi, json_paths, true); + + vty_out(vty, "\"paths\": "); + json_detail_header_used = true; + } + + /* + * We are using no_pretty here because under + * extremely high settings( say lots and lots of + * routes with lots and lots of ways to reach + * that route via different paths ) this can + * save several minutes of output when FRR + * is run on older cpu's or more underperforming + * routers out there + */ + vty_json_no_pretty(vty, json_paths); + + /* End per-prefix dictionary */ + if (json_detail_header_used) + vty_out(vty, "} "); + + json_paths = NULL; + first = 0; + } else + json_object_free(json_paths); + } + + if (output_cum) { + output_count += *output_cum; + *output_cum = output_count; + } + if (total_cum) { + total_count += *total_cum; + *total_cum = total_count; + } + if (use_json) { + if (rd) { + vty_out(vty, " }%s ", (is_last ? "" : ",")); + } + if (is_last) { + unsigned long i; + for (i = 0; i < *json_header_depth; ++i) + vty_out(vty, " } "); + if (!all) + vty_out(vty, "\n"); + } + } else { + if (is_last) { + /* No route is displayed */ + if (output_count == 0) { + if (type == bgp_show_type_normal) + vty_out(vty, + "No BGP prefixes displayed, %ld exist\n", + total_count); + } else + vty_out(vty, + "\nDisplayed %ld routes and %ld total paths\n", + output_count, total_count); + } + } + + return CMD_SUCCESS; +} + +int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_table *table, struct prefix_rd *prd_match, + enum bgp_show_type type, void *output_arg, + uint16_t show_flags) +{ + struct bgp_dest *dest, *next; + unsigned long output_cum = 0; + unsigned long total_cum = 0; + unsigned long json_header_depth = 0; + struct bgp_table *itable; + bool show_msg; + bool use_json = !!CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + show_msg = (!use_json && type == bgp_show_type_normal); + + for (dest = bgp_table_top(table); dest; dest = next) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + next = bgp_route_next(dest); + if (prd_match && memcmp(dest_p->u.val, prd_match->val, 8) != 0) + continue; + + itable = bgp_dest_get_bgp_table_info(dest); + if (itable != NULL) { + struct prefix_rd prd; + char rd[RD_ADDRSTRLEN]; + + memcpy(&prd, dest_p, sizeof(struct prefix_rd)); + prefix_rd2str(&prd, rd, sizeof(rd), bgp->asnotation); + bgp_show_table(vty, bgp, afi, safi, itable, type, output_arg, + rd, next == NULL, &output_cum, + &total_cum, &json_header_depth, + show_flags, RPKI_NOT_BEING_USED); + if (next == NULL) + show_msg = false; + } + } + if (show_msg) { + if (output_cum == 0) + vty_out(vty, "No BGP prefixes displayed, %ld exist\n", + total_cum); + else + vty_out(vty, + "\nDisplayed %ld routes and %ld total paths\n", + output_cum, total_cum); + } else { + if (use_json && output_cum == 0 && json_header_depth == 0) + vty_out(vty, "{}\n"); + } + return CMD_SUCCESS; +} + +static int bgp_show(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + enum bgp_show_type type, void *output_arg, + uint16_t show_flags, enum rpki_states rpki_target_state) +{ + struct bgp_table *table; + unsigned long json_header_depth = 0; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (bgp == NULL) { + bgp = bgp_get_default(); + } + + if (bgp == NULL) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + + /* Labeled-unicast routes live in the unicast table. */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + table = bgp->rib[afi][safi]; + /* use MPLS and ENCAP specific shows until they are merged */ + if (safi == SAFI_MPLS_VPN) { + return bgp_show_table_rd(vty, bgp, afi, safi, table, NULL, type, + output_arg, show_flags); + } + + if (safi == SAFI_FLOWSPEC && type == bgp_show_type_detail) { + return bgp_show_table_flowspec(vty, bgp, afi, table, type, + output_arg, use_json, + 1, NULL, NULL); + } + + if (safi == SAFI_EVPN) + return bgp_evpn_show_all_routes(vty, bgp, type, use_json, 0); + + return bgp_show_table(vty, bgp, afi, safi, table, type, output_arg, NULL, 1, + NULL, NULL, &json_header_depth, show_flags, + rpki_target_state); +} + +static void bgp_show_all_instances_routes_vty(struct vty *vty, afi_t afi, + safi_t safi, uint16_t show_flags) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + int is_first = 1; + bool route_output = false; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (use_json) + vty_out(vty, "{\n"); + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + route_output = true; + if (use_json) { + if (!is_first) + vty_out(vty, ",\n"); + else + is_first = 0; + + vty_out(vty, "\"%s\":", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } else { + vty_out(vty, "\nInstance %s:\n", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } + bgp_show(vty, bgp, afi, safi, bgp_show_type_normal, NULL, + show_flags, RPKI_NOT_BEING_USED); + } + + if (use_json) + vty_out(vty, "}\n"); + else if (!route_output) + vty_out(vty, "%% BGP instance not found\n"); +} + +/* Header of detailed BGP route information */ +void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, + struct bgp_dest *dest, const struct prefix *p, + const struct prefix_rd *prd, afi_t afi, + safi_t safi, json_object *json, + bool incremental_print) +{ + struct bgp_path_info *pi; + struct peer *peer; + struct listnode *node, *nnode; + char buf1[RD_ADDRSTRLEN]; + int count = 0; + int best = 0; + int suppress = 0; + int accept_own = 0; + int route_filter_translated_v4 = 0; + int route_filter_v4 = 0; + int route_filter_translated_v6 = 0; + int route_filter_v6 = 0; + int llgr_stale = 0; + int no_llgr = 0; + int accept_own_nexthop = 0; + int blackhole = 0; + int no_export = 0; + int no_advertise = 0; + int local_as = 0; + int no_peer = 0; + int first = 1; + int has_valid_label = 0; + mpls_label_t label = 0; + json_object *json_adv_to = NULL; + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + + mpls_lse_decode(dest->local_label, &label, &ttl, &exp, &bos); + + has_valid_label = bgp_is_valid_label(&label); + + if (safi == SAFI_EVPN) { + if (!json) { + vty_out(vty, "BGP routing table entry for %s%s%pFX\n", + prd ? prefix_rd2str(prd, buf1, sizeof(buf1), + bgp->asnotation) + : "", + prd ? ":" : "", (struct prefix_evpn *)p); + } else { + json_object_string_add( + json, "rd", + prd ? prefix_rd2str(prd, buf1, sizeof(buf1), + bgp->asnotation) + : ""); + bgp_evpn_route2json((struct prefix_evpn *)p, json); + } + } else { + if (!json) { + vty_out(vty, + "BGP routing table entry for %s%s%pFX, version %" PRIu64 + "\n", + (((safi == SAFI_MPLS_VPN || + safi == SAFI_ENCAP) && + prd) + ? prefix_rd2str(prd, buf1, + sizeof(buf1), + bgp->asnotation) + : ""), + safi == SAFI_MPLS_VPN && prd ? ":" : "", p, + dest->version); + + } else { + if (incremental_print) { + vty_out(vty, "\"prefix\": \"%pFX\",\n", p); + vty_out(vty, "\"version\": \"%" PRIu64 "\",", + dest->version); + } else { + json_object_string_addf(json, "prefix", "%pFX", + p); + json_object_int_add(json, "version", + dest->version); + } + } + } + + if (has_valid_label) { + if (json) { + if (incremental_print) + vty_out(vty, "\"localLabel\": \"%u\",\n", + label); + else + json_object_int_add(json, "localLabel", label); + } else + vty_out(vty, "Local label: %d\n", label); + } + + if (!json) + if (bgp_labeled_safi(safi) && safi != SAFI_EVPN) + vty_out(vty, "not allocated\n"); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + struct community *picomm = NULL; + + picomm = bgp_attr_get_community(pi->attr); + + count++; + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + best = count; + if (bgp_path_suppressed(pi)) + suppress = 1; + + if (!picomm) + continue; + + no_advertise += community_include( + picomm, COMMUNITY_NO_ADVERTISE); + no_export += + community_include(picomm, COMMUNITY_NO_EXPORT); + local_as += + community_include(picomm, COMMUNITY_LOCAL_AS); + accept_own += + community_include(picomm, COMMUNITY_ACCEPT_OWN); + route_filter_translated_v4 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_TRANSLATED_v4); + route_filter_translated_v6 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_TRANSLATED_v6); + route_filter_v4 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_v4); + route_filter_v6 += community_include( + picomm, COMMUNITY_ROUTE_FILTER_v6); + llgr_stale += + community_include(picomm, COMMUNITY_LLGR_STALE); + no_llgr += community_include(picomm, COMMUNITY_NO_LLGR); + accept_own_nexthop += community_include( + picomm, COMMUNITY_ACCEPT_OWN_NEXTHOP); + blackhole += + community_include(picomm, COMMUNITY_BLACKHOLE); + no_peer += community_include(picomm, COMMUNITY_NO_PEER); + } + } + + if (!json) { + vty_out(vty, "Paths: (%d available", count); + if (best) { + vty_out(vty, ", best #%d", best); + if (safi == SAFI_UNICAST) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + vty_out(vty, ", table %s", + VRF_DEFAULT_NAME); + else + vty_out(vty, ", vrf %s", + bgp->name); + } + } else + vty_out(vty, ", no best path"); + + if (accept_own) + vty_out(vty, + ", accept own local route exported and imported in different VRF"); + else if (route_filter_translated_v4) + vty_out(vty, + ", mark translated RTs for VPNv4 route filtering"); + else if (route_filter_v4) + vty_out(vty, + ", attach RT as-is for VPNv4 route filtering"); + else if (route_filter_translated_v6) + vty_out(vty, + ", mark translated RTs for VPNv6 route filtering"); + else if (route_filter_v6) + vty_out(vty, + ", attach RT as-is for VPNv6 route filtering"); + else if (llgr_stale) + vty_out(vty, + ", mark routes to be retained for a longer time. Requires support for Long-lived BGP Graceful Restart"); + else if (no_llgr) + vty_out(vty, + ", mark routes to not be treated according to Long-lived BGP Graceful Restart operations"); + else if (accept_own_nexthop) + vty_out(vty, + ", accept local nexthop"); + else if (blackhole) + vty_out(vty, ", inform peer to blackhole prefix"); + else if (no_export) + vty_out(vty, ", not advertised to EBGP peer"); + else if (no_advertise) + vty_out(vty, ", not advertised to any peer"); + else if (local_as) + vty_out(vty, ", not advertised outside local AS"); + else if (no_peer) + vty_out(vty, + ", inform EBGP peer not to advertise to their EBGP peers"); + + if (suppress) + vty_out(vty, + ", Advertisements suppressed by an aggregate."); + vty_out(vty, ")\n"); + } + + /* If we are not using addpath then we can display Advertised to and + * that will + * show what peers we advertised the bestpath to. If we are using + * addpath + * though then we must display Advertised to on a path-by-path basis. */ + if (!bgp_addpath_is_addpath_used(&bgp->tx_addpath, afi, safi)) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (bgp_adj_out_lookup(peer, dest, 0)) { + if (json && !json_adv_to) + json_adv_to = json_object_new_object(); + + route_vty_out_advertised_to( + vty, peer, &first, + " Advertised to non peer-group peers:\n ", + json_adv_to); + } + } + + if (json && json_adv_to) { + if (incremental_print) { + vty_out(vty, "\"advertisedTo\": "); + vty_json(vty, json_adv_to); + vty_out(vty, ","); + } else + json_object_object_add(json, "advertisedTo", + json_adv_to); + } else { + if (!json && first) + vty_out(vty, " Not advertised to any peer"); + vty_out(vty, "\n"); + } + } +} + +static void bgp_show_path_info(const struct prefix_rd *pfx_rd, + struct bgp_dest *bgp_node, struct vty *vty, + struct bgp *bgp, afi_t afi, safi_t safi, + json_object *json, enum bgp_path_type pathtype, + int *display, enum rpki_states rpki_target_state) +{ + struct bgp_path_info *pi; + int header = 1; + json_object *json_header = NULL; + json_object *json_paths = NULL; + const struct prefix *p = bgp_dest_get_prefix(bgp_node); + + for (pi = bgp_dest_get_bgp_path_info(bgp_node); pi; pi = pi->next) { + enum rpki_states rpki_curr_state = RPKI_NOT_BEING_USED; + + if (p->family == AF_INET || p->family == AF_INET6) + rpki_curr_state = hook_call(bgp_rpki_prefix_status, + pi->peer, pi->attr, p); + + if (rpki_target_state != RPKI_NOT_BEING_USED + && rpki_curr_state != rpki_target_state) + continue; + + if (json && !json_paths) { + /* Instantiate json_paths only if path is valid */ + json_paths = json_object_new_array(); + if (pfx_rd) + json_header = json_object_new_object(); + else + json_header = json; + } + + if (header) { + route_vty_out_detail_header( + vty, bgp, bgp_node, + bgp_dest_get_prefix(bgp_node), pfx_rd, AFI_IP, + safi, json_header, false); + header = 0; + } + (*display)++; + + if (pathtype == BGP_PATH_SHOW_ALL + || (pathtype == BGP_PATH_SHOW_BESTPATH + && CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + || (pathtype == BGP_PATH_SHOW_MULTIPATH + && (CHECK_FLAG(pi->flags, BGP_PATH_MULTIPATH) + || CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)))) + route_vty_out_detail(vty, bgp, bgp_node, + bgp_dest_get_prefix(bgp_node), pi, + afi, safi, rpki_curr_state, + json_paths); + } + + if (json && json_paths) { + json_object_object_add(json_header, "paths", json_paths); + + if (pfx_rd) + json_object_object_addf( + json, json_header, + BGP_RD_AS_FORMAT(bgp->asnotation), pfx_rd); + } +} + +/* + * Return rd based on safi + */ +const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, + safi_t safi) +{ + switch (safi) { + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_EVPN: + return (struct prefix_rd *)(bgp_dest_get_prefix(dest)); + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + return NULL; + } + + assert(!"Reached end of function when we were not expecting it"); +} + +/* Display specified route of BGP table. */ +static int bgp_show_route_in_table(struct vty *vty, struct bgp *bgp, + struct bgp_table *rib, const char *ip_str, + afi_t afi, safi_t safi, + enum rpki_states rpki_target_state, + struct prefix_rd *prd, int prefix_check, + enum bgp_path_type pathtype, bool use_json) +{ + int ret; + int display = 0; + struct prefix match; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_table *table; + json_object *json = NULL; + json_object *json_paths = NULL; + + /* Check IP address argument. */ + ret = str2prefix(ip_str, &match); + if (!ret) { + vty_out(vty, "address is malformed\n"); + return CMD_WARNING; + } + + match.family = afi2family(afi); + + if (use_json) + json = json_object_new_object(); + + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP) { + for (dest = bgp_table_top(rib); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + rm = bgp_node_match(table, &match); + if (rm == NULL) + continue; + + const struct prefix *rm_p = bgp_dest_get_prefix(rm); + if (prefix_check + && rm_p->prefixlen != match.prefixlen) { + bgp_dest_unlock_node(rm); + continue; + } + + bgp_show_path_info((struct prefix_rd *)dest_p, rm, vty, + bgp, afi, safi, json, pathtype, + &display, rpki_target_state); + + bgp_dest_unlock_node(rm); + } + } else if (safi == SAFI_EVPN) { + struct bgp_dest *longest_pfx; + bool is_exact_pfxlen_match = false; + + for (dest = bgp_table_top(rib); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(&dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + longest_pfx = NULL; + is_exact_pfxlen_match = false; + /* + * Search through all the prefixes for a match. The + * pfx's are enumerated in ascending order of pfxlens. + * So, the last pfx match is the longest match. Set + * is_exact_pfxlen_match when we get exact pfxlen match + */ + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) { + const struct prefix *rm_p = + bgp_dest_get_prefix(rm); + /* + * Get prefixlen of the ip-prefix within type5 + * evpn route + */ + if (evpn_type5_prefix_match(rm_p, &match) + && rm->info) { + longest_pfx = rm; + int type5_pfxlen = + bgp_evpn_get_type5_prefixlen( + rm_p); + if (type5_pfxlen == match.prefixlen) { + is_exact_pfxlen_match = true; + rm = bgp_dest_unlock_node(rm); + + assert(rm); + break; + } + } + } + + if (!longest_pfx) + continue; + + if (prefix_check && !is_exact_pfxlen_match) + continue; + + rm = longest_pfx; + bgp_dest_lock_node(rm); + + bgp_show_path_info((struct prefix_rd *)dest_p, rm, vty, + bgp, afi, safi, json, pathtype, + &display, rpki_target_state); + + bgp_dest_unlock_node(rm); + } + } else if (safi == SAFI_FLOWSPEC) { + if (use_json) + json_paths = json_object_new_array(); + + display = bgp_flowspec_display_match_per_ip(afi, rib, + &match, prefix_check, + vty, + use_json, + json_paths); + if (use_json) { + if (display) + json_object_object_add(json, "paths", + json_paths); + else + json_object_free(json_paths); + } + } else { + dest = bgp_node_match(rib, &match); + if (dest != NULL) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + if (!prefix_check + || dest_p->prefixlen == match.prefixlen) { + bgp_show_path_info(NULL, dest, vty, bgp, afi, + safi, json, pathtype, + &display, rpki_target_state); + } + + bgp_dest_unlock_node(dest); + } + } + + if (use_json) { + vty_json(vty, json); + } else { + if (!display) { + vty_out(vty, "%% Network not in table\n"); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +/* Display specified route of Main RIB */ +static int bgp_show_route(struct vty *vty, struct bgp *bgp, const char *ip_str, + afi_t afi, safi_t safi, struct prefix_rd *prd, + int prefix_check, enum bgp_path_type pathtype, + enum rpki_states rpki_target_state, bool use_json) +{ + if (!bgp) { + bgp = bgp_get_default(); + if (!bgp) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + } + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + return bgp_show_route_in_table(vty, bgp, bgp->rib[afi][safi], ip_str, + afi, safi, rpki_target_state, prd, + prefix_check, pathtype, use_json); +} + +static int bgp_show_lcommunity(struct vty *vty, struct bgp *bgp, int argc, + struct cmd_token **argv, bool exact, afi_t afi, + safi_t safi, bool uj) +{ + struct lcommunity *lcom; + struct buffer *b; + int i; + char *str; + int first = 0; + uint16_t show_flags = 0; + int ret; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + b = buffer_new(1024); + for (i = 0; i < argc; i++) { + if (first) + buffer_putc(b, ' '); + else { + if (strmatch(argv[i]->text, "AA:BB:CC")) { + first = 1; + buffer_putstr(b, argv[i]->arg); + } + } + } + buffer_putc(b, '\0'); + + str = buffer_getstr(b); + buffer_free(b); + + lcom = lcommunity_str2com(str); + XFREE(MTYPE_TMP, str); + if (!lcom) { + vty_out(vty, "%% Large-community malformed\n"); + return CMD_WARNING; + } + + ret = bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_lcommunity_exact + : bgp_show_type_lcommunity), + lcom, show_flags, RPKI_NOT_BEING_USED); + + lcommunity_free(&lcom); + return ret; +} + +static int bgp_show_lcommunity_list(struct vty *vty, struct bgp *bgp, + const char *lcom, bool exact, afi_t afi, + safi_t safi, bool uj) +{ + struct community_list *list; + uint16_t show_flags = 0; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + + list = community_list_lookup(bgp_clist, lcom, 0, + LARGE_COMMUNITY_LIST_MASTER); + if (list == NULL) { + vty_out(vty, "%% %s is not a valid large-community-list name\n", + lcom); + return CMD_WARNING; + } + + return bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_lcommunity_list_exact + : bgp_show_type_lcommunity_list), + list, show_flags, RPKI_NOT_BEING_USED); +} + +DEFUN (show_ip_bgp_large_community_list, + show_ip_bgp_large_community_list_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community-list <(1-500)|LCOMMUNITY_LIST_NAME> [exact-match] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the large-community-list\n" + "large-community-list number\n" + "large-community-list name\n" + "Exact match of the large-communities\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + int idx = 0; + bool exact_match = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "large-community-list", &idx); + + const char *clist_number_or_name = argv[++idx]->arg; + + if (++idx < argc && strmatch(argv[idx]->text, "exact-match")) + exact_match = 1; + + return bgp_show_lcommunity_list(vty, bgp, clist_number_or_name, + exact_match, afi, safi, uj); +} +DEFUN (show_ip_bgp_large_community, + show_ip_bgp_large_community_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] large-community [ [exact-match]] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the large-communities\n" + "List of large-community numbers\n" + "Exact match of the large-communities\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + int idx = 0; + bool exact_match = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + uint16_t show_flags = 0; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (argv_find(argv, argc, "AA:BB:CC", &idx)) { + if (argv_find(argv, argc, "exact-match", &idx)) { + argc--; + exact_match = 1; + } + return bgp_show_lcommunity(vty, bgp, argc, argv, + exact_match, afi, safi, uj); + } else + return bgp_show(vty, bgp, afi, safi, + bgp_show_type_lcommunity_all, NULL, show_flags, + RPKI_NOT_BEING_USED); +} + +static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array); +static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json); + + +DEFUN(show_ip_bgp_statistics_all, show_ip_bgp_statistics_all_cmd, + "show [ip] bgp [ VIEWVRFNAME] statistics-all [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR + "Display number of prefixes for all afi/safi\n" JSON_STR) +{ + bool uj = use_json(argc, argv); + struct bgp *bgp = NULL; + safi_t safi = SAFI_UNICAST; + afi_t afi = AFI_IP6; + int idx = 0; + struct json_object *json_all = NULL; + struct json_object *json_afi_safi = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_all = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + /* + * So limit output to those afi/safi pairs that + * actually have something interesting in them + */ + if (strmatch(get_afi_safi_str(afi, safi, true), + "Unknown")) { + continue; + } + if (uj) { + json_afi_safi = json_object_new_array(); + json_object_object_add( + json_all, + get_afi_safi_str(afi, safi, true), + json_afi_safi); + } else { + json_afi_safi = NULL; + } + + bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + } + + if (uj) + vty_json(vty, json_all); + + return CMD_SUCCESS; +} + +/* BGP route print out function without JSON */ +DEFUN (show_ip_bgp_l2vpn_evpn_statistics, + show_ip_bgp_l2vpn_evpn_statistics_cmd, + "show [ip] bgp [ VIEWVRFNAME] l2vpn evpn statistics [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "BGP RIB advertisement statistics\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0, ret; + bool uj = use_json(argc, argv); + struct json_object *json_afi_safi = NULL, *json = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_afi_safi = json_object_new_array(); + else + json_afi_safi = NULL; + + ret = bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + + if (uj) { + json = json_object_new_object(); + json_object_object_add(json, get_afi_safi_str(afi, safi, true), + json_afi_safi); + vty_json(vty, json); + } + return ret; +} + +/* BGP route print out function without JSON */ +DEFUN(show_ip_bgp_afi_safi_statistics, show_ip_bgp_afi_safi_statistics_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]]\ + statistics [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "BGP RIB advertisement statistics\n" JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0, ret; + bool uj = use_json(argc, argv); + struct json_object *json_afi_safi = NULL, *json = NULL; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + if (uj) + json_afi_safi = json_object_new_array(); + else + json_afi_safi = NULL; + + ret = bgp_table_stats(vty, bgp, afi, safi, json_afi_safi); + + if (uj) { + json = json_object_new_object(); + json_object_object_add(json, get_afi_safi_str(afi, safi, true), + json_afi_safi); + vty_json(vty, json); + } + return ret; +} + +DEFPY(show_ip_bgp_dampening_params, show_ip_bgp_dampening_params_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]] [all$all] dampening parameters [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Display detailed information about dampening\n" + "Display detail of configured dampening parameters\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = 0; + bool uj = use_json(argc, argv); + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + /* [ [all]] */ + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + return bgp_show_dampening_parameters(vty, afi, safi, show_flags); +} + +/* BGP route print out function */ +DEFPY(show_ip_bgp, show_ip_bgp_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]]\ + [all$all]\ + [cidr-only\ + |dampening \ + |community [AA:NN|local-AS|no-advertise|no-export\ + |graceful-shutdown|no-peer|blackhole|llgr-stale|no-llgr\ + |accept-own|accept-own-nexthop|route-filter-v6\ + |route-filter-v4|route-filter-translated-v6\ + |route-filter-translated-v4] [exact-match]\ + |community-list <(1-500)|COMMUNITY_LIST_NAME> [exact-match]\ + |filter-list AS_PATH_FILTER_NAME\ + |prefix-list WORD\ + |access-list ACCESSLIST_NAME\ + |route-map RMAP_NAME\ + |rpki \ + |version (1-4294967295)\ + |alias ALIAS_NAME\ + |A.B.C.D/M longer-prefixes\ + |X:X::X:X/M longer-prefixes\ + |"BGP_SELF_ORIG_CMD_STR"\ + |detail-routes$detail_routes\ + ] [json$uj [detail$detail_json] | wide$wide]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Display only routes with non-natural netmasks\n" + "Display detailed information about dampening\n" + "Display flap statistics of routes\n" + "Display paths suppressed due to dampening\n" + "Display routes matching the communities\n" COMMUNITY_AANN_STR + "Do not send outside local AS (well-known community)\n" + "Do not advertise to any peer (well-known community)\n" + "Do not export to next AS (well-known community)\n" + "Graceful shutdown (well-known community)\n" + "Do not export to any peer (well-known community)\n" + "Inform EBGP peers to blackhole traffic to prefix (well-known community)\n" + "Staled Long-lived Graceful Restart VPN route (well-known community)\n" + "Removed because Long-lived Graceful Restart was not enabled for VPN route (well-known community)\n" + "Should accept local VPN route if exported and imported into different VRF (well-known community)\n" + "Should accept VPN route with local nexthop (well-known community)\n" + "RT VPNv6 route filtering (well-known community)\n" + "RT VPNv4 route filtering (well-known community)\n" + "RT translated VPNv6 route filtering (well-known community)\n" + "RT translated VPNv4 route filtering (well-known community)\n" + "Exact match of the communities\n" + "Community-list number\n" + "Community-list name\n" + "Display routes matching the community-list\n" + "Exact match of the communities\n" + "Display routes conforming to the filter-list\n" + "Regular expression access list name\n" + "Display routes conforming to the prefix-list\n" + "Prefix-list name\n" + "Display routes conforming to the access-list\n" + "Access-list name\n" + "Display routes matching the route-map\n" + "A route-map to match on\n" + "RPKI route types\n" + "A valid path as determined by rpki\n" + "A invalid path as determined by rpki\n" + "A path that has no rpki data\n" + "Display prefixes with matching version numbers\n" + "Version number and above\n" + "Display prefixes with matching BGP community alias\n" + "BGP community alias\n" + "IPv4 prefix\n" + "Display route and more specific routes\n" + "IPv6 prefix\n" + "Display route and more specific routes\n" + BGP_SELF_ORIG_HELP_STR + "Display detailed version of all routes\n" + JSON_STR + "Display detailed version of JSON output\n" + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + enum bgp_show_type sh_type = bgp_show_type_normal; + void *output_arg = NULL; + struct bgp *bgp = NULL; + int idx = 0; + int exact_match = 0; + char *community = NULL; + bool first = true; + uint16_t show_flags = 0; + enum rpki_states rpki_target_state = RPKI_NOT_BEING_USED; + struct prefix p; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (detail_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON_DETAIL); + + if (detail_routes) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + /* [ [all]] */ + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (argv_find(argv, argc, "cidr-only", &idx)) + sh_type = bgp_show_type_cidr_only; + + if (argv_find(argv, argc, "dampening", &idx)) { + if (argv_find(argv, argc, "dampened-paths", &idx)) + sh_type = bgp_show_type_dampend_paths; + else if (argv_find(argv, argc, "flap-statistics", &idx)) + sh_type = bgp_show_type_flap_statistics; + } + + if (argv_find(argv, argc, "community", &idx)) { + char *maybecomm = NULL; + + if (idx + 1 < argc) { + if (argv[idx + 1]->type == VARIABLE_TKN) + maybecomm = argv[idx + 1]->arg; + else + maybecomm = argv[idx + 1]->text; + } + + if (maybecomm && !strmatch(maybecomm, "json") + && !strmatch(maybecomm, "exact-match")) + community = maybecomm; + + if (argv_find(argv, argc, "exact-match", &idx)) + exact_match = 1; + + if (!community) + sh_type = bgp_show_type_community_all; + } + + if (argv_find(argv, argc, "community-list", &idx)) { + const char *clist_number_or_name = argv[++idx]->arg; + struct community_list *list; + + if (argv_find(argv, argc, "exact-match", &idx)) + exact_match = 1; + + list = community_list_lookup(bgp_clist, clist_number_or_name, 0, + COMMUNITY_LIST_MASTER); + if (list == NULL) { + vty_out(vty, "%% %s community-list not found\n", + clist_number_or_name); + return CMD_WARNING; + } + + if (exact_match) + sh_type = bgp_show_type_community_list_exact; + else + sh_type = bgp_show_type_community_list; + output_arg = list; + } + + if (argv_find(argv, argc, "filter-list", &idx)) { + const char *filter = argv[++idx]->arg; + struct as_list *as_list; + + as_list = as_list_lookup(filter); + if (as_list == NULL) { + vty_out(vty, "%% %s AS-path access-list not found\n", + filter); + return CMD_WARNING; + } + + sh_type = bgp_show_type_filter_list; + output_arg = as_list; + } + + if (argv_find(argv, argc, "prefix-list", &idx)) { + const char *prefix_list_str = argv[++idx]->arg; + struct prefix_list *plist; + + plist = prefix_list_lookup(afi, prefix_list_str); + if (plist == NULL) { + vty_out(vty, "%% %s prefix-list not found\n", + prefix_list_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_prefix_list; + output_arg = plist; + } + + if (argv_find(argv, argc, "access-list", &idx)) { + const char *access_list_str = argv[++idx]->arg; + struct access_list *alist; + + alist = access_list_lookup(afi, access_list_str); + if (!alist) { + vty_out(vty, "%% %s access-list not found\n", + access_list_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_access_list; + output_arg = alist; + } + + if (argv_find(argv, argc, "route-map", &idx)) { + const char *rmap_str = argv[++idx]->arg; + struct route_map *rmap; + + rmap = route_map_lookup_by_name(rmap_str); + if (!rmap) { + vty_out(vty, "%% %s route-map not found\n", rmap_str); + return CMD_WARNING; + } + + sh_type = bgp_show_type_route_map; + output_arg = rmap; + } + + if (argv_find(argv, argc, "rpki", &idx)) { + sh_type = bgp_show_type_rpki; + if (argv_find(argv, argc, "valid", &idx)) + rpki_target_state = RPKI_VALID; + else if (argv_find(argv, argc, "invalid", &idx)) + rpki_target_state = RPKI_INVALID; + else if (argv_find(argv, argc, "notfound", &idx)) + rpki_target_state = RPKI_NOTFOUND; + } + + /* Display prefixes with matching version numbers */ + if (argv_find(argv, argc, "version", &idx)) { + sh_type = bgp_show_type_prefix_version; + output_arg = argv[idx + 1]->arg; + } + + /* Display prefixes with matching BGP community alias */ + if (argv_find(argv, argc, "alias", &idx)) { + sh_type = bgp_show_type_community_alias; + output_arg = argv[idx + 1]->arg; + } + + /* prefix-longer */ + if (argv_find(argv, argc, "A.B.C.D/M", &idx) + || argv_find(argv, argc, "X:X::X:X/M", &idx)) { + const char *prefix_str = argv[idx]->arg; + + if (!str2prefix(prefix_str, &p)) { + vty_out(vty, "%% Malformed Prefix\n"); + return CMD_WARNING; + } + + sh_type = bgp_show_type_prefix_longer; + output_arg = &p; + } + + /* self originated only */ + if (argv_find(argv, argc, BGP_SELF_ORIG_CMD_STR, &idx)) + sh_type = bgp_show_type_self_originated; + + if (!all) { + /* show bgp: AFI_IP6, show ip bgp: AFI_IP */ + if (community) + return bgp_show_community(vty, bgp, community, + exact_match, afi, safi, + show_flags); + else + return bgp_show(vty, bgp, afi, safi, sh_type, + output_arg, show_flags, + rpki_target_state); + } else { + struct listnode *node; + struct bgp *abgp; + /* show bgp ipv4 all: AFI_IP, show bgp ipv6 all: + * AFI_IP6 */ + + if (uj) + vty_out(vty, "{\n"); + + if (CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + || CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6)) { + afi = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + ? AFI_IP + : AFI_IP6; + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_SAFI (safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, + safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":{\n", + get_afi_safi_str(afi, + safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str( + afi, safi, + false)); + + if (community) + bgp_show_community( + vty, abgp, community, + exact_match, afi, safi, + show_flags); + else + bgp_show(vty, abgp, afi, safi, + sh_type, output_arg, + show_flags, + rpki_target_state); + if (uj) + vty_out(vty, "}\n"); + } + } + } else { + /* show bgp all: for each AFI and SAFI*/ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, + safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + + vty_out(vty, "\"%s\":{\n", + get_afi_safi_str(afi, + safi, + true)); + + /* Adding 'routes' key to make + * the json output format valid + * for evpn + */ + if (safi == SAFI_EVPN) + vty_out(vty, + "\"routes\":"); + + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str( + afi, safi, + false)); + + if (community) + bgp_show_community( + vty, abgp, community, + exact_match, afi, safi, + show_flags); + else + bgp_show(vty, abgp, afi, safi, + sh_type, output_arg, + show_flags, + rpki_target_state); + if (uj) + vty_out(vty, "}\n"); + } + } + } + if (uj) + vty_out(vty, "}\n"); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_route, + show_ip_bgp_route_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] [] [rpki ] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Network in the BGP routing table to display\n" + "IPv4 prefix\n" + "Network in the BGP routing table to display\n" + "IPv6 prefix\n" + "Display only the bestpath\n" + "Display only multipaths\n" + "Display only paths that match the specified rpki state\n" + "A valid path as determined by rpki\n" + "A invalid path as determined by rpki\n" + "A path that has no rpki data\n" + JSON_STR) +{ + int prefix_check = 0; + + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *prefix = NULL; + struct bgp *bgp = NULL; + enum bgp_path_type path_type; + bool uj = use_json(argc, argv); + + int idx = 0; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + if (!bgp) { + vty_out(vty, + "Specified 'all' vrf's but this command currently only works per view/vrf\n"); + return CMD_WARNING; + } + + /* */ + if (argv_find(argv, argc, "A.B.C.D", &idx) + || argv_find(argv, argc, "X:X::X:X", &idx)) + prefix_check = 0; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx) + || argv_find(argv, argc, "X:X::X:X/M", &idx)) + prefix_check = 1; + + if ((argv[idx]->type == IPV6_TKN || argv[idx]->type == IPV6_PREFIX_TKN) + && afi != AFI_IP6) { + vty_out(vty, + "%% Cannot specify IPv6 address or prefix with IPv4 AFI\n"); + return CMD_WARNING; + } + if ((argv[idx]->type == IPV4_TKN || argv[idx]->type == IPV4_PREFIX_TKN) + && afi != AFI_IP) { + vty_out(vty, + "%% Cannot specify IPv4 address or prefix with IPv6 AFI\n"); + return CMD_WARNING; + } + + prefix = argv[idx]->arg; + + /* [] */ + if (argv_find(argv, argc, "bestpath", &idx)) + path_type = BGP_PATH_SHOW_BESTPATH; + else if (argv_find(argv, argc, "multipath", &idx)) + path_type = BGP_PATH_SHOW_MULTIPATH; + else + path_type = BGP_PATH_SHOW_ALL; + + return bgp_show_route(vty, bgp, prefix, afi, safi, NULL, prefix_check, + path_type, RPKI_NOT_BEING_USED, uj); +} + +DEFUN (show_ip_bgp_regexp, + show_ip_bgp_regexp_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] regexp REGEX [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display routes matching the AS path regular expression\n" + "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + char *regstr = NULL; + + int idx = 0; + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, false); + if (!idx) + return CMD_WARNING; + + // get index of regex + if (argv_find(argv, argc, "REGEX", &idx)) + regstr = argv[idx]->arg; + + assert(regstr); + return bgp_show_regexp(vty, bgp, (const char *)regstr, afi, safi, + bgp_show_type_regexp, uj); +} + +DEFPY (show_ip_bgp_instance_all, + show_ip_bgp_instance_all_cmd, + "show [ip] bgp all ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_ALL_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = 0; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + bgp_show_all_instances_routes_vty(vty, afi, safi, show_flags); + return CMD_SUCCESS; +} + +static int bgp_show_regexp(struct vty *vty, struct bgp *bgp, const char *regstr, + afi_t afi, safi_t safi, enum bgp_show_type type, + bool use_json) +{ + regex_t *regex; + int rc; + uint16_t show_flags = 0; + + if (use_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (!config_bgp_aspath_validate(regstr)) { + vty_out(vty, "Invalid character in REGEX %s\n", + regstr); + return CMD_WARNING_CONFIG_FAILED; + } + + regex = bgp_regcomp(regstr); + if (!regex) { + vty_out(vty, "Can't compile regexp %s\n", regstr); + return CMD_WARNING; + } + + rc = bgp_show(vty, bgp, afi, safi, type, regex, show_flags, + RPKI_NOT_BEING_USED); + bgp_regex_free(regex); + return rc; +} + +static int bgp_show_community(struct vty *vty, struct bgp *bgp, + const char *comstr, int exact, afi_t afi, + safi_t safi, uint16_t show_flags) +{ + struct community *com; + int ret = 0; + + com = community_str2com(comstr); + if (!com) { + vty_out(vty, "%% Community malformed: %s\n", comstr); + return CMD_WARNING; + } + + ret = bgp_show(vty, bgp, afi, safi, + (exact ? bgp_show_type_community_exact + : bgp_show_type_community), + com, show_flags, RPKI_NOT_BEING_USED); + community_free(&com); + + return ret; +} + +enum bgp_stats { + BGP_STATS_MAXBITLEN = 0, + BGP_STATS_RIB, + BGP_STATS_PREFIXES, + BGP_STATS_TOTPLEN, + BGP_STATS_UNAGGREGATEABLE, + BGP_STATS_MAX_AGGREGATEABLE, + BGP_STATS_AGGREGATES, + BGP_STATS_SPACE, + BGP_STATS_ASPATH_COUNT, + BGP_STATS_ASPATH_MAXHOPS, + BGP_STATS_ASPATH_TOTHOPS, + BGP_STATS_ASPATH_MAXSIZE, + BGP_STATS_ASPATH_TOTSIZE, + BGP_STATS_ASN_HIGHEST, + BGP_STATS_MAX, +}; + +#define TABLE_STATS_IDX_VTY 0 +#define TABLE_STATS_IDX_JSON 1 + +static const char *table_stats_strs[][2] = { + [BGP_STATS_PREFIXES] = {"Total Prefixes", "totalPrefixes"}, + [BGP_STATS_TOTPLEN] = {"Average prefix length", "averagePrefixLength"}, + [BGP_STATS_RIB] = {"Total Advertisements", "totalAdvertisements"}, + [BGP_STATS_UNAGGREGATEABLE] = {"Unaggregateable prefixes", + "unaggregateablePrefixes"}, + [BGP_STATS_MAX_AGGREGATEABLE] = {"Maximum aggregateable prefixes", + "maximumAggregateablePrefixes"}, + [BGP_STATS_AGGREGATES] = {"BGP Aggregate advertisements", + "bgpAggregateAdvertisements"}, + [BGP_STATS_SPACE] = {"Address space advertised", + "addressSpaceAdvertised"}, + [BGP_STATS_ASPATH_COUNT] = {"Advertisements with paths", + "advertisementsWithPaths"}, + [BGP_STATS_ASPATH_MAXHOPS] = {"Longest AS-Path (hops)", + "longestAsPath"}, + [BGP_STATS_ASPATH_MAXSIZE] = {"Largest AS-Path (bytes)", + "largestAsPath"}, + [BGP_STATS_ASPATH_TOTHOPS] = {"Average AS-Path length (hops)", + "averageAsPathLengthHops"}, + [BGP_STATS_ASPATH_TOTSIZE] = {"Average AS-Path size (bytes)", + "averageAsPathSizeBytes"}, + [BGP_STATS_ASN_HIGHEST] = {"Highest public ASN", "highestPublicAsn"}, + [BGP_STATS_MAX] = {NULL, NULL} +}; + +struct bgp_table_stats { + struct bgp_table *table; + unsigned long long counts[BGP_STATS_MAX]; + + unsigned long long + prefix_len_count[MAX(EVPN_ROUTE_PREFIXLEN, IPV6_MAX_BITLEN) + + 1]; + + double total_space; +}; + +static void bgp_table_stats_rn(struct bgp_dest *dest, struct bgp_dest *top, + struct bgp_table_stats *ts, unsigned int space) +{ + struct bgp_dest *pdest = bgp_dest_parent_nolock(dest); + struct bgp_path_info *pi; + const struct prefix *rn_p; + + if (!bgp_dest_has_bgp_path_info_data(dest)) + return; + + rn_p = bgp_dest_get_prefix(dest); + ts->counts[BGP_STATS_PREFIXES]++; + ts->counts[BGP_STATS_TOTPLEN] += rn_p->prefixlen; + + ts->prefix_len_count[rn_p->prefixlen]++; + /* check if the prefix is included by any other announcements */ + while (pdest && !bgp_dest_has_bgp_path_info_data(pdest)) + pdest = bgp_dest_parent_nolock(pdest); + + if (pdest == NULL || pdest == top) { + ts->counts[BGP_STATS_UNAGGREGATEABLE]++; + /* announced address space */ + if (space) + ts->total_space += pow(2.0, space - rn_p->prefixlen); + } else if (bgp_dest_has_bgp_path_info_data(pdest)) + ts->counts[BGP_STATS_MAX_AGGREGATEABLE]++; + + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + ts->counts[BGP_STATS_RIB]++; + + if (CHECK_FLAG(pi->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))) + ts->counts[BGP_STATS_AGGREGATES]++; + + /* as-path stats */ + if (pi->attr->aspath) { + unsigned int hops = aspath_count_hops(pi->attr->aspath); + unsigned int size = aspath_size(pi->attr->aspath); + as_t highest = aspath_highest(pi->attr->aspath); + + ts->counts[BGP_STATS_ASPATH_COUNT]++; + + if (hops > ts->counts[BGP_STATS_ASPATH_MAXHOPS]) + ts->counts[BGP_STATS_ASPATH_MAXHOPS] = hops; + + if (size > ts->counts[BGP_STATS_ASPATH_MAXSIZE]) + ts->counts[BGP_STATS_ASPATH_MAXSIZE] = size; + + ts->counts[BGP_STATS_ASPATH_TOTHOPS] += hops; + ts->counts[BGP_STATS_ASPATH_TOTSIZE] += size; + if (highest > ts->counts[BGP_STATS_ASN_HIGHEST]) + ts->counts[BGP_STATS_ASN_HIGHEST] = highest; + } + } +} + +static void bgp_table_stats_walker(struct event *t) +{ + struct bgp_dest *dest, *ndest; + struct bgp_dest *top; + struct bgp_table_stats *ts = EVENT_ARG(t); + unsigned int space = 0; + + if (!(top = bgp_table_top(ts->table))) + return; + + switch (ts->table->afi) { + case AFI_IP: + space = IPV4_MAX_BITLEN; + break; + case AFI_IP6: + space = IPV6_MAX_BITLEN; + break; + case AFI_L2VPN: + space = EVPN_ROUTE_PREFIXLEN; + break; + case AFI_UNSPEC: + case AFI_MAX: + return; + } + + ts->counts[BGP_STATS_MAXBITLEN] = space; + + for (dest = top; dest; dest = bgp_route_next(dest)) { + if (ts->table->safi == SAFI_MPLS_VPN + || ts->table->safi == SAFI_ENCAP + || ts->table->safi == SAFI_EVPN) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + top = bgp_table_top(table); + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) + bgp_table_stats_rn(ndest, top, ts, space); + } else { + bgp_table_stats_rn(dest, top, ts, space); + } + } +} + +static void bgp_table_stats_all(struct vty *vty, afi_t afi, safi_t safi, + struct json_object *json_array) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + bgp_table_stats_single(vty, bgp, afi, safi, json_array); +} + +static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array) +{ + struct bgp_table_stats ts; + unsigned int i; + int ret = CMD_SUCCESS; + char temp_buf[20]; + struct json_object *json = NULL; + uint32_t bitlen = 0; + struct json_object *json_bitlen; + + if (json_array) + json = json_object_new_object(); + + if (!bgp->rib[afi][safi]) { + char warning_msg[50]; + + snprintf(warning_msg, sizeof(warning_msg), + "%% No RIB exist's for the AFI(%d)/SAFI(%d)", afi, + safi); + + if (!json) + vty_out(vty, "%s\n", warning_msg); + else + json_object_string_add(json, "warning", warning_msg); + + ret = CMD_WARNING; + goto end_table_stats; + } + + if (!json) + vty_out(vty, "BGP %s RIB statistics (%s)\n", + get_afi_safi_str(afi, safi, false), bgp->name_pretty); + else + json_object_string_add(json, "instance", bgp->name_pretty); + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + memset(&ts, 0, sizeof(ts)); + ts.table = bgp->rib[afi][safi]; + event_execute(bm->master, bgp_table_stats_walker, &ts, 0, NULL); + + for (i = 0; i < BGP_STATS_MAX; i++) { + if ((!json && !table_stats_strs[i][TABLE_STATS_IDX_VTY]) + || (json && !table_stats_strs[i][TABLE_STATS_IDX_JSON])) + continue; + + switch (i) { + case BGP_STATS_ASPATH_TOTHOPS: + case BGP_STATS_ASPATH_TOTSIZE: + if (!json) { + snprintf( + temp_buf, sizeof(temp_buf), "%12.2f", + ts.counts[i] + ? (float)ts.counts[i] + / (float)ts.counts + [BGP_STATS_ASPATH_COUNT] + : 0); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i] + ? (double)ts.counts[i] + / (double)ts.counts + [BGP_STATS_ASPATH_COUNT] + : 0); + } + break; + case BGP_STATS_TOTPLEN: + if (!json) { + snprintf( + temp_buf, sizeof(temp_buf), "%12.2f", + ts.counts[i] + ? (float)ts.counts[i] + / (float)ts.counts + [BGP_STATS_PREFIXES] + : 0); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i] + ? (double)ts.counts[i] + / (double)ts.counts + [BGP_STATS_PREFIXES] + : 0); + } + break; + case BGP_STATS_SPACE: + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), "%12g", + ts.total_space); + vty_out(vty, "%-30s: %s\n", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_double_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + (double)ts.total_space); + } + if (afi == AFI_IP6) { + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12g", + ts.total_space + * pow(2.0, -128 + 32)); + vty_out(vty, "%30s: %s\n", + "/32 equivalent %s\n", + temp_buf); + } else { + json_object_double_add( + json, "/32equivalent", + (double)(ts.total_space + * pow(2.0, + -128 + 32))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12g", + ts.total_space + * pow(2.0, -128 + 48)); + vty_out(vty, "%30s: %s\n", + "/48 equivalent %s\n", + temp_buf); + } else { + json_object_double_add( + json, "/48equivalent", + (double)(ts.total_space + * pow(2.0, + -128 + 48))); + } + } else { + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space * 100. + * pow(2.0, -32)); + vty_out(vty, "%30s: %s\n", + "% announced ", temp_buf); + } else { + json_object_double_add( + json, "%announced", + (double)(ts.total_space * 100. + * pow(2.0, -32))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space + * pow(2.0, -32 + 8)); + vty_out(vty, "%30s: %s\n", + "/8 equivalent ", temp_buf); + } else { + json_object_double_add( + json, "/8equivalent", + (double)(ts.total_space + * pow(2.0, -32 + 8))); + } + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), + "%12.2f", + ts.total_space + * pow(2.0, -32 + 24)); + vty_out(vty, "%30s: %s\n", + "/24 equivalent ", temp_buf); + } else { + json_object_double_add( + json, "/24equivalent", + (double)(ts.total_space + * pow(2.0, -32 + 24))); + } + } + break; + default: + if (!json) { + snprintf(temp_buf, sizeof(temp_buf), "%12llu", + ts.counts[i]); + vty_out(vty, "%-30s: %s", + table_stats_strs[i] + [TABLE_STATS_IDX_VTY], + temp_buf); + } else { + json_object_int_add( + json, + table_stats_strs[i] + [TABLE_STATS_IDX_JSON], + ts.counts[i]); + } + } + if (!json) + vty_out(vty, "\n"); + } + + switch (afi) { + case AFI_IP: + bitlen = IPV4_MAX_BITLEN; + break; + case AFI_IP6: + bitlen = IPV6_MAX_BITLEN; + break; + case AFI_L2VPN: + bitlen = EVPN_ROUTE_PREFIXLEN; + break; + case AFI_UNSPEC: + case AFI_MAX: + break; + } + + if (json) { + json_bitlen = json_object_new_array(); + + for (i = 0; i <= bitlen; i++) { + if (!ts.prefix_len_count[i]) + continue; + + struct json_object *ind_bit = json_object_new_object(); + + snprintf(temp_buf, sizeof(temp_buf), "%u", i); + json_object_int_add(ind_bit, temp_buf, + ts.prefix_len_count[i]); + json_object_array_add(json_bitlen, ind_bit); + } + json_object_object_add(json, "prefixLength", json_bitlen); + } + +end_table_stats: + if (json) + json_object_array_add(json_array, json); + return ret; +} + +static int bgp_table_stats(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi, struct json_object *json_array) +{ + if (!bgp) { + bgp_table_stats_all(vty, afi, safi, json_array); + return CMD_SUCCESS; + } + + return bgp_table_stats_single(vty, bgp, afi, safi, json_array); +} + +enum bgp_pcounts { + PCOUNT_ADJ_IN = 0, + PCOUNT_DAMPED, + PCOUNT_REMOVED, + PCOUNT_HISTORY, + PCOUNT_STALE, + PCOUNT_VALID, + PCOUNT_ALL, + PCOUNT_COUNTED, + PCOUNT_BPATH_SELECTED, + PCOUNT_PFCNT, /* the figure we display to users */ + PCOUNT_UNSORTED, + PCOUNT_MAX, +}; + +static const char *const pcount_strs[] = { + [PCOUNT_ADJ_IN] = "Adj-in", + [PCOUNT_DAMPED] = "Damped", + [PCOUNT_REMOVED] = "Removed", + [PCOUNT_HISTORY] = "History", + [PCOUNT_STALE] = "Stale", + [PCOUNT_VALID] = "Valid", + [PCOUNT_ALL] = "All RIB", + [PCOUNT_COUNTED] = "PfxCt counted", + [PCOUNT_BPATH_SELECTED] = "PfxCt Best Selected", + [PCOUNT_PFCNT] = "Useable", + [PCOUNT_UNSORTED] = "Unsorted", + [PCOUNT_MAX] = NULL, +}; + +struct peer_pcounts { + unsigned int count[PCOUNT_MAX]; + const struct peer *peer; + const struct bgp_table *table; + safi_t safi; +}; + +static void bgp_peer_count_proc(struct bgp_dest *rn, struct peer_pcounts *pc) +{ + const struct bgp_adj_in *ain; + const struct bgp_path_info *pi; + const struct peer *peer = pc->peer; + + for (ain = rn->adj_in; ain; ain = ain->next) + if (ain->peer == peer) + pc->count[PCOUNT_ADJ_IN]++; + + for (pi = bgp_dest_get_bgp_path_info(rn); pi; pi = pi->next) { + + if (pi->peer != peer) + continue; + + pc->count[PCOUNT_ALL]++; + + if (CHECK_FLAG(pi->flags, BGP_PATH_DAMPED)) + pc->count[PCOUNT_DAMPED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_HISTORY)) + pc->count[PCOUNT_HISTORY]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_REMOVED)) + pc->count[PCOUNT_REMOVED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_STALE)) + pc->count[PCOUNT_STALE]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_VALID)) + pc->count[PCOUNT_VALID]++; + if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + pc->count[PCOUNT_PFCNT]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + pc->count[PCOUNT_BPATH_SELECTED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_UNSORTED)) + pc->count[PCOUNT_UNSORTED]++; + + if (CHECK_FLAG(pi->flags, BGP_PATH_COUNTED)) { + pc->count[PCOUNT_COUNTED]++; + if (CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + flog_err( + EC_LIB_DEVELOPMENT, + "Attempting to count but flags say it is unusable"); + } else { + if (!CHECK_FLAG(pi->flags, BGP_PATH_UNUSEABLE)) + flog_err( + EC_LIB_DEVELOPMENT, + "Not counted but flags say we should"); + } + } +} + +static void bgp_peer_count_walker(struct event *t) +{ + struct bgp_dest *rn, *rm; + const struct bgp_table *table; + struct peer_pcounts *pc = EVENT_ARG(t); + + if (pc->safi == SAFI_MPLS_VPN || pc->safi == SAFI_ENCAP + || pc->safi == SAFI_EVPN) { + /* Special handling for 2-level routing tables. */ + for (rn = bgp_table_top(pc->table); rn; + rn = bgp_route_next(rn)) { + table = bgp_dest_get_bgp_table_info(rn); + if (table != NULL) + for (rm = bgp_table_top(table); rm; + rm = bgp_route_next(rm)) + bgp_peer_count_proc(rm, pc); + } + } else + for (rn = bgp_table_top(pc->table); rn; rn = bgp_route_next(rn)) + bgp_peer_count_proc(rn, pc); +} + +static int bgp_peer_counts(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi, bool use_json) +{ + struct peer_pcounts pcounts = {.peer = peer}; + unsigned int i; + json_object *json = NULL; + json_object *json_loop = NULL; + + if (use_json) { + json = json_object_new_object(); + json_loop = json_object_new_object(); + } + + if (!peer || !peer->bgp || !peer->afc[afi][safi] + || !peer->bgp->rib[afi][safi]) { + if (use_json) { + json_object_string_add( + json, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_loop); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + + return CMD_WARNING; + } + + memset(&pcounts, 0, sizeof(pcounts)); + pcounts.peer = peer; + pcounts.table = peer->bgp->rib[afi][safi]; + pcounts.safi = safi; + + /* in-place call via thread subsystem so as to record execution time + * stats for the thread-walk (i.e. ensure this can't be blamed on + * on just vty_read()). + */ + event_execute(bm->master, bgp_peer_count_walker, &pcounts, 0, NULL); + + if (use_json) { + json_object_string_add(json, "prefixCountsFor", peer->host); + json_object_string_add(json, "multiProtocol", + get_afi_safi_str(afi, safi, true)); + json_object_int_add(json, "pfxCounter", + peer->pcount[afi][safi]); + + for (i = 0; i < PCOUNT_MAX; i++) + json_object_int_add(json_loop, pcount_strs[i], + pcounts.count[i]); + + json_object_object_add(json, "ribTableWalkCounters", json_loop); + + if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { + json_object_string_add(json, "pfxctDriftFor", + peer->host); + json_object_string_add( + json, "recommended", + "Please report this bug, with the above command output"); + } + vty_json(vty, json); + } else { + + if (peer->hostname + && CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHOW_HOSTNAME)) { + vty_out(vty, "Prefix counts for %s/%s, %s\n", + peer->hostname, peer->host, + get_afi_safi_str(afi, safi, false)); + } else { + vty_out(vty, "Prefix counts for %s, %s\n", peer->host, + get_afi_safi_str(afi, safi, false)); + } + + vty_out(vty, "PfxCt: %u\n", peer->pcount[afi][safi]); + vty_out(vty, "\nCounts from RIB table walk:\n\n"); + + for (i = 0; i < PCOUNT_MAX; i++) + vty_out(vty, "%20s: %-10d\n", pcount_strs[i], + pcounts.count[i]); + + if (pcounts.count[PCOUNT_PFCNT] != peer->pcount[afi][safi]) { + vty_out(vty, "%s [pcount] PfxCt drift!\n", peer->host); + vty_out(vty, + "Please report this bug, with the above command output\n"); + } + } + + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_instance_neighbor_prefix_counts, + show_ip_bgp_instance_neighbor_prefix_counts_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] neighbors prefix-counts [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display detailed prefix count information\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct peer *peer; + int idx = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "neighbors", &idx); + peer = peer_lookup_in_view(vty, bgp, argv[idx + 1]->arg, uj); + if (!peer) + return CMD_WARNING; + + return bgp_peer_counts(vty, peer, afi, safi, uj); +} + +#ifdef KEEP_OLD_VPN_COMMANDS +DEFUN (show_ip_bgp_vpn_neighbor_prefix_counts, + show_ip_bgp_vpn_neighbor_prefix_counts_cmd, + "show [ip] bgp all neighbors prefix-counts [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4 NLRIs\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display detailed prefix count information\n" + JSON_STR) +{ + int idx_peer = 6; + struct peer *peer; + bool uj = use_json(argc, argv); + + peer = peer_lookup_in_view(vty, NULL, argv[idx_peer]->arg, uj); + if (!peer) + return CMD_WARNING; + + return bgp_peer_counts(vty, peer, AFI_IP, SAFI_MPLS_VPN, uj); +} + +DEFUN (show_ip_bgp_vpn_all_route_prefix, + show_ip_bgp_vpn_all_route_prefix_cmd, + "show [ip] bgp all [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_VPNVX_HELP_STR + "Display information about all VPNv4 NLRIs\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int idx = 0; + char *network = NULL; + struct bgp *bgp = bgp_get_default(); + if (!bgp) { + vty_out(vty, "Can't find default instance\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "A.B.C.D", &idx)) + network = argv[idx]->arg; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx)) + network = argv[idx]->arg; + else { + vty_out(vty, "Unable to figure out Network\n"); + return CMD_WARNING; + } + + return bgp_show_route(vty, bgp, network, AFI_IP, SAFI_MPLS_VPN, NULL, 0, + BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, + use_json(argc, argv)); +} +#endif /* KEEP_OLD_VPN_COMMANDS */ + +DEFUN (show_bgp_l2vpn_evpn_route_prefix, + show_bgp_l2vpn_evpn_route_prefix_cmd, + "show bgp l2vpn evpn [json]", + SHOW_STR + BGP_STR + L2VPN_HELP_STR + EVPN_HELP_STR + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int idx = 0; + char *network = NULL; + int prefix_check = 0; + + if (argv_find(argv, argc, "A.B.C.D", &idx) || + argv_find(argv, argc, "X:X::X:X", &idx)) + network = argv[idx]->arg; + else if (argv_find(argv, argc, "A.B.C.D/M", &idx) || + argv_find(argv, argc, "X:X::X:X/M", &idx)) { + network = argv[idx]->arg; + prefix_check = 1; + } else { + vty_out(vty, "Unable to figure out Network\n"); + return CMD_WARNING; + } + return bgp_show_route(vty, NULL, network, AFI_L2VPN, SAFI_EVPN, NULL, + prefix_check, BGP_PATH_SHOW_ALL, + RPKI_NOT_BEING_USED, use_json(argc, argv)); +} + +static void show_adj_route_header(struct vty *vty, struct peer *peer, + struct bgp_table *table, int *header1, + int *header2, json_object *json, bool wide, + bool detail) +{ + uint64_t version = table ? table->version : 0; + + if (*header1) { + if (json) { + json_object_int_add(json, "bgpTableVersion", version); + json_object_string_addf(json, "bgpLocalRouterId", + "%pI4", &peer->bgp->router_id); + json_object_int_add(json, "defaultLocPrf", + peer->bgp->default_local_pref); + json_object_int_add(json, "localAS", + peer->change_local_as + ? peer->change_local_as + : peer->local_as); + } else { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + version, &peer->bgp->router_id); + if (peer->bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", peer->bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + peer->bgp->default_local_pref); + vty_out(vty, "local AS %u\n", + peer->change_local_as ? peer->change_local_as + : peer->local_as); + if (!detail) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + } + *header1 = 0; + } + if (*header2) { + if (!json && !detail) + vty_out(vty, (wide ? BGP_SHOW_HEADER_WIDE + : BGP_SHOW_HEADER)); + *header2 = 0; + } +} + +static void +show_adj_route(struct vty *vty, struct peer *peer, struct bgp_table *table, + afi_t afi, safi_t safi, enum bgp_show_adj_route_type type, + const char *rmap_name, json_object *json, json_object *json_ar, + uint16_t show_flags, int *header1, int *header2, char *rd_str, + const struct prefix *match, unsigned long *output_count, + unsigned long *filtered_count) +{ + struct bgp_adj_in *ain = NULL; + struct bgp_adj_out *adj = NULL; + struct bgp_dest *dest; + struct bgp *bgp; + struct attr attr; + int ret; + struct update_subgroup *subgrp; + struct peer_af *paf = NULL; + bool route_filtered; + bool detail = CHECK_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + bool wide = CHECK_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + bool show_rd = ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) + ? true + : false; + int display = 0; + json_object *json_net = NULL; + + bgp = peer->bgp; + + /* If the user supplied a prefix, look for a matching route instead + * of walking the whole table. + */ + if (match) { + dest = bgp_node_match(table, match); + if (!dest) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + return; + } + + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + if (rn_p->prefixlen != match->prefixlen) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + + if (type == bgp_show_adj_route_received || + type == bgp_show_adj_route_filtered) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer == peer) { + attr = *ain->attr; + break; + } + } + /* bail out if if adj_out is empty, or + * if the prefix isn't in this peer's + * adj_in + */ + if (!ain || ain->peer != peer) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + } else if (type == bgp_show_adj_route_advertised) { + bool peer_found = false; + + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) { + SUBGRP_FOREACH_PEER (adj->subgroup, paf) { + if (paf->peer == peer && adj->attr) { + attr = *adj->attr; + peer_found = true; + break; + } + } + if (peer_found) + break; + } + /* bail out if if adj_out is empty, or + * if the prefix isn't in this peer's + * adj_out + */ + if (!paf || !peer_found) { + if (!use_json) + vty_out(vty, "Network not in table\n"); + bgp_dest_unlock_node(dest); + return; + } + } + + ret = bgp_output_modifier(peer, rn_p, &attr, afi, safi, + rmap_name); + + if (ret != RMAP_DENY) { + show_adj_route_header(vty, peer, table, header1, + header2, json, wide, detail); + + if (use_json) + json_net = json_object_new_object(); + + bgp_show_path_info(NULL /* prefix_rd */, dest, vty, bgp, + afi, safi, json_net, + BGP_PATH_SHOW_ALL, &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf(json_ar, json_net, + "%pFX", rn_p); + (*output_count)++; + } else + (*filtered_count)++; + + bgp_attr_flush(&attr); + bgp_dest_unlock_node(dest); + return; + } + + + subgrp = peer_subgroup(peer, afi, safi); + + if (type == bgp_show_adj_route_advertised && subgrp + && CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) { + if (use_json) { + json_object_int_add(json, "bgpTableVersion", + table->version); + json_object_string_addf(json, "bgpLocalRouterId", + "%pI4", &bgp->router_id); + json_object_int_add(json, "defaultLocPrf", + bgp->default_local_pref); + json_object_int_add(json, "localAS", + peer->change_local_as + ? peer->change_local_as + : peer->local_as); + json_object_string_add( + json, "bgpOriginatingDefaultNetwork", + (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); + } else { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4, vrf id ", + table->version, &bgp->router_id); + if (bgp->vrf_id == VRF_UNKNOWN) + vty_out(vty, "%s", VRFID_NONE_STR); + else + vty_out(vty, "%u", bgp->vrf_id); + vty_out(vty, "\n"); + vty_out(vty, "Default local pref %u, ", + bgp->default_local_pref); + vty_out(vty, "local AS %u\n", + peer->change_local_as ? peer->change_local_as + : peer->local_as); + if (!detail) { + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_NCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + vty_out(vty, BGP_SHOW_RPKI_HEADER); + } + + vty_out(vty, "Originating default network %s\n\n", + (afi == AFI_IP) ? "0.0.0.0/0" : "::/0"); + } + (*output_count)++; + *header1 = 0; + } + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + if (type == bgp_show_adj_route_received + || type == bgp_show_adj_route_filtered) { + for (ain = dest->adj_in; ain; ain = ain->next) { + if (ain->peer != peer) + continue; + show_adj_route_header(vty, peer, table, header1, + header2, json, wide, + detail); + + if ((safi == SAFI_MPLS_VPN) + || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + if (use_json) + json_object_string_add( + json_ar, "rd", rd_str); + else if (show_rd && rd_str) { + vty_out(vty, + "Route Distinguisher: %s\n", + rd_str); + show_rd = false; + } + } + + attr = *ain->attr; + route_filtered = false; + + /* Filter prefix using distribute list, + * filter list or prefix list + */ + const struct prefix *rn_p = + bgp_dest_get_prefix(dest); + if ((bgp_input_filter(peer, rn_p, &attr, afi, + safi)) + == FILTER_DENY) + route_filtered = true; + + /* Filter prefix using route-map */ + ret = bgp_input_modifier(peer, rn_p, &attr, afi, + safi, rmap_name, NULL, + 0, NULL); + + if (type == bgp_show_adj_route_filtered && + !route_filtered && ret != RMAP_DENY) { + bgp_attr_flush(&attr); + continue; + } + + if (type == bgp_show_adj_route_received + && (route_filtered || ret == RMAP_DENY)) + (*filtered_count)++; + + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + + struct bgp_path_info bpi; + struct bgp_dest buildit = *dest; + struct bgp_dest *pass_in; + + if (route_filtered || + ret == RMAP_DENY) { + bpi.attr = &attr; + bpi.peer = peer; + buildit.info = &bpi; + + pass_in = &buildit; + } else + pass_in = dest; + bgp_show_path_info( + NULL, pass_in, vty, bgp, afi, + safi, json_net, + BGP_PATH_SHOW_ALL, &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, json_net, + "%pFX", rn_p); + } else + route_vty_out_tmp(vty, bgp, dest, rn_p, + &attr, safi, use_json, + json_ar, wide); + bgp_attr_flush(&attr); + (*output_count)++; + } + } else if (type == bgp_show_adj_route_advertised) { + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) + SUBGRP_FOREACH_PEER (adj->subgroup, paf) { + if (paf->peer != peer || !adj->attr) + continue; + + show_adj_route_header(vty, peer, table, + header1, header2, + json, wide, + detail); + + const struct prefix *rn_p = + bgp_dest_get_prefix(dest); + + attr = *adj->attr; + ret = bgp_output_modifier( + peer, rn_p, &attr, afi, safi, + rmap_name); + + if (ret != RMAP_DENY) { + if ((safi == SAFI_MPLS_VPN) + || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + if (use_json) + json_object_string_add( + json_ar, + "rd", + rd_str); + else if (show_rd + && rd_str) { + vty_out(vty, + "Route Distinguisher: %s\n", + rd_str); + show_rd = false; + } + } + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + bgp_show_path_info( + NULL /* prefix_rd + */ + , + dest, vty, bgp, + afi, safi, + json_net, + BGP_PATH_SHOW_ALL, + &display, + RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, + json_net, + "%pFX", + rn_p); + } else + route_vty_out_tmp(vty, + bgp, + dest, + rn_p, + &attr, + safi, + use_json, + json_ar, + wide); + (*output_count)++; + } else { + (*filtered_count)++; + } + + bgp_attr_flush(&attr); + } + } else if (type == bgp_show_adj_route_bestpath) { + struct bgp_path_info *pi; + + show_adj_route_header(vty, peer, table, header1, + header2, json, wide, detail); + + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (pi->peer != peer) + continue; + + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + continue; + + if (detail) { + if (use_json) + json_net = + json_object_new_object(); + bgp_show_path_info( + NULL /* prefix_rd */, dest, vty, + bgp, afi, safi, json_net, + BGP_PATH_SHOW_BESTPATH, + &display, RPKI_NOT_BEING_USED); + if (use_json) + json_object_object_addf( + json_ar, json_net, + "%pFX", rn_p); + } else + route_vty_out_tmp(vty, bgp, dest, rn_p, + pi->attr, safi, + use_json, json_ar, + wide); + (*output_count)++; + } + } + } +} + +static int peer_adj_routes(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi, enum bgp_show_adj_route_type type, + const char *rmap_name, const struct prefix *match, + uint16_t show_flags) +{ + struct bgp *bgp; + struct bgp_table *table; + json_object *json = NULL; + json_object *json_ar = NULL; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + /* Init BGP headers here so they're only displayed once + * even if 'table' is 2-tier (MPLS_VPN, ENCAP, EVPN). + */ + int header1 = 1; + int header2 = 1; + + /* + * Initialize variables for each RD + * All prefixes under an RD is aggregated within "json_routes" + */ + char rd_str[BUFSIZ] = {0}; + json_object *json_routes = NULL; + + + /* For 2-tier tables, prefix counts need to be + * maintained across multiple runs of show_adj_route() + */ + unsigned long output_count_per_rd; + unsigned long filtered_count_per_rd; + unsigned long output_count = 0; + unsigned long filtered_count = 0; + + if (use_json) { + json = json_object_new_object(); + json_ar = json_object_new_object(); + } + + if (!peer || !peer->afc[afi][safi]) { + if (use_json) { + json_object_string_add( + json, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_ar); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + + return CMD_WARNING; + } + + if ((type == bgp_show_adj_route_received + || type == bgp_show_adj_route_filtered) + && !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SOFT_RECONFIG)) { + if (use_json) { + json_object_string_add( + json, "warning", + "Inbound soft reconfiguration not enabled"); + vty_out(vty, "%s\n", json_object_to_json_string(json)); + json_object_free(json); + json_object_free(json_ar); + } else + vty_out(vty, + "%% Inbound soft reconfiguration not enabled\n"); + + return CMD_WARNING; + } + + bgp = peer->bgp; + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + table = bgp->rib[afi][SAFI_UNICAST]; + else + table = bgp->rib[afi][safi]; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + + struct bgp_dest *dest; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + + output_count_per_rd = 0; + filtered_count_per_rd = 0; + + if (use_json) + json_routes = json_object_new_object(); + + const struct prefix_rd *prd; + prd = (const struct prefix_rd *)bgp_dest_get_prefix( + dest); + + prefix_rd2str(prd, rd_str, sizeof(rd_str), + bgp->asnotation); + + show_adj_route(vty, peer, table, afi, safi, type, + rmap_name, json, json_routes, show_flags, + &header1, &header2, rd_str, match, + &output_count_per_rd, + &filtered_count_per_rd); + + /* Don't include an empty RD in the output! */ + if (json_routes && (output_count_per_rd > 0)) + json_object_object_add(json_ar, rd_str, + json_routes); + + output_count += output_count_per_rd; + filtered_count += filtered_count_per_rd; + } + } else + show_adj_route(vty, peer, table, afi, safi, type, rmap_name, + json, json_ar, show_flags, &header1, &header2, + rd_str, match, &output_count, &filtered_count); + + if (use_json) { + if (type == bgp_show_adj_route_advertised) + json_object_object_add(json, "advertisedRoutes", + json_ar); + else + json_object_object_add(json, "receivedRoutes", json_ar); + json_object_int_add(json, "totalPrefixCounter", output_count); + json_object_int_add(json, "filteredPrefixCounter", + filtered_count); + + /* + * This is an extremely expensive operation at scale + * and non-pretty reduces memory footprint significantly. + */ + vty_json_no_pretty(vty, json); + } else if (output_count > 0) { + if (!match && filtered_count > 0) + vty_out(vty, + "\nTotal number of prefixes %ld (%ld filtered)\n", + output_count, filtered_count); + else + vty_out(vty, "\nTotal number of prefixes %ld\n", + output_count); + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_bgp_instance_neighbor_bestpath_route, + show_ip_bgp_instance_neighbor_bestpath_route_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR " [" BGP_SAFI_WITH_LABEL_CMD_STR "]] neighbors bestpath-routes [detail$detail] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes selected by best path\n" + "Display detailed version of routes\n" + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *rmap_name = NULL; + char *peerstr = NULL; + struct bgp *bgp = NULL; + struct peer *peer; + enum bgp_show_adj_route_type type = bgp_show_adj_route_bestpath; + int idx = 0; + uint16_t show_flags = 0; + + if (detail) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + + if (!idx) + return CMD_WARNING; + + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + return peer_adj_routes(vty, peer, afi, safi, type, rmap_name, NULL, + show_flags); +} + +DEFPY(show_ip_bgp_instance_neighbor_advertised_route, + show_ip_bgp_instance_neighbor_advertised_route_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR " [" BGP_SAFI_WITH_LABEL_CMD_STR "]] [all$all] neighbors [route-map RMAP_NAME$route_map] [$prefix | detail$detail] [json$uj | wide$wide]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display the routes advertised to a BGP neighbor\n" + "Display the received routes from neighbor\n" + "Display the filtered routes received from neighbor\n" + "Route-map to modify the attributes\n" + "Name of the route map\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Display detailed version of routes\n" + JSON_STR + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *peerstr = NULL; + struct bgp *bgp = NULL; + struct peer *peer; + enum bgp_show_adj_route_type type = bgp_show_adj_route_advertised; + int idx = 0; + bool first = true; + uint16_t show_flags = 0; + struct listnode *node; + struct bgp *abgp; + + if (detail || prefix_str) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (all) { + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_ALL); + if (argv_find(argv, argc, "ipv4", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP); + + if (argv_find(argv, argc, "ipv6", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6); + } + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + if (argv_find(argv, argc, "advertised-routes", &idx)) + type = bgp_show_adj_route_advertised; + else if (argv_find(argv, argc, "received-routes", &idx)) + type = bgp_show_adj_route_received; + else if (argv_find(argv, argc, "filtered-routes", &idx)) + type = bgp_show_adj_route_filtered; + + if (!all) + return peer_adj_routes(vty, peer, afi, safi, type, route_map, + prefix_str ? prefix : NULL, show_flags); + if (uj) + vty_out(vty, "{\n"); + + if (CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) + || CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP6)) { + afi = CHECK_FLAG(show_flags, BGP_SHOW_OPT_AFI_IP) ? AFI_IP + : AFI_IP6; + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_SAFI (safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":", + get_afi_safi_str(afi, safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, + false)); + + peer_adj_routes(vty, peer, afi, safi, type, + route_map, prefix, show_flags); + } + } + } else { + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, abgp)) { + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_afi_safi_peer_exists(abgp, afi, safi)) + continue; + + if (uj) { + if (first) + first = false; + else + vty_out(vty, ",\n"); + vty_out(vty, "\"%s\":", + get_afi_safi_str(afi, safi, + true)); + } else + vty_out(vty, + "\nFor address family: %s\n", + get_afi_safi_str(afi, safi, + false)); + + peer_adj_routes(vty, peer, afi, safi, type, + route_map, prefix, show_flags); + } + } + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_neighbor_received_prefix_filter, + show_ip_bgp_neighbor_received_prefix_filter_cmd, + "show [ip] bgp [ VIEWVRFNAME] [ [unicast]] neighbors received prefix-filter [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AF_STR + BGP_AF_STR + BGP_AF_MODIFIER_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display information received from a BGP neighbor\n" + "Display the prefixlist filter\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + char *peerstr = NULL; + char name[BUFSIZ]; + struct peer *peer; + int count; + int idx = 0; + struct bgp *bgp = NULL; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + snprintf(name, sizeof(name), "%s.%d.%d", peer->host, afi, safi); + count = prefix_bgp_show_prefix_list(NULL, afi, name, uj); + if (count) { + if (!uj) + vty_out(vty, "Address Family: %s\n", + get_afi_safi_str(afi, safi, false)); + prefix_bgp_show_prefix_list(vty, afi, name, uj); + } else { + if (uj) + vty_out(vty, "{}\n"); + else + vty_out(vty, "No functional output\n"); + } + + return CMD_SUCCESS; +} + +static int bgp_show_neighbor_route(struct vty *vty, struct peer *peer, + afi_t afi, safi_t safi, + enum bgp_show_type type, bool use_json) +{ + uint16_t show_flags = 0; + + if (use_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (!peer || !peer->afc[afi][safi]) { + if (use_json) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, "warning", + "No such neighbor or address family"); + vty_out(vty, "%s\n", + json_object_to_json_string(json_no)); + json_object_free(json_no); + } else + vty_out(vty, "%% No such neighbor or address family\n"); + return CMD_WARNING; + } + + /* labeled-unicast routes live in the unicast table */ + if (safi == SAFI_LABELED_UNICAST) + safi = SAFI_UNICAST; + + return bgp_show(vty, peer->bgp, afi, safi, type, &peer->connection->su, + show_flags, RPKI_NOT_BEING_USED); +} + +/* + * Used for "detailed" output for cmds like show bgp (or) + * show bgp (or) show bgp + */ +DEFPY(show_ip_bgp_vrf_afi_safi_routes_detailed, + show_ip_bgp_vrf_afi_safi_routes_detailed_cmd, + "show [ip] bgp [ VIEWVRFNAME$vrf_name] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] detail [json$uj]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information\n" + JSON_STR) +{ + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = BGP_SHOW_OPT_ROUTES_DETAIL; + + if (uj) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + /* 'vrf all' case to iterate all vrfs & show output per vrf instance */ + if (vrf_name && strmatch(vrf_name, "all")) { + bgp_show_all_instances_routes_vty(vty, afi, safi, show_flags); + return CMD_SUCCESS; + } + + /* All other cases except vrf all */ + return bgp_show(vty, bgp, afi, safi, bgp_show_type_detail, NULL, + show_flags, RPKI_NOT_BEING_USED); +} + +DEFUN (show_ip_bgp_neighbor_routes, + show_ip_bgp_neighbor_routes_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] neighbors [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Display flap statistics of the routes learned from neighbor\n" + "Display the dampened routes received from neighbor\n" + "Display routes learned from neighbor\n" + JSON_STR) +{ + char *peerstr = NULL; + struct bgp *bgp = NULL; + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + struct peer *peer; + enum bgp_show_type sh_type = bgp_show_type_neighbor; + int idx = 0; + bool uj = use_json(argc, argv); + + if (uj) + argc--; + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* neighbors */ + argv_find(argv, argc, "neighbors", &idx); + peerstr = argv[++idx]->arg; + + peer = peer_lookup_in_view(vty, bgp, peerstr, uj); + if (!peer) + return CMD_WARNING; + + if (argv_find(argv, argc, "flap-statistics", &idx)) + sh_type = bgp_show_type_flap_neighbor; + else if (argv_find(argv, argc, "dampened-routes", &idx)) + sh_type = bgp_show_type_damp_neighbor; + else if (argv_find(argv, argc, "routes", &idx)) + sh_type = bgp_show_type_neighbor; + + return bgp_show_neighbor_route(vty, peer, afi, safi, sh_type, uj); +} + +struct bgp_table *bgp_distance_table[AFI_MAX][SAFI_MAX]; + +struct bgp_distance { + /* Distance value for the IP source prefix. */ + uint8_t distance; + + /* Name of the access-list to be matched. */ + char *access_list; +}; + +DEFUN (show_bgp_afi_vpn_rd_route, + show_bgp_afi_vpn_rd_route_cmd, + "show bgp "BGP_AFI_CMD_STR" vpn rd [json]", + SHOW_STR + BGP_STR + BGP_AFI_HELP_STR + BGP_AF_MODIFIER_STR + "Display information for a route distinguisher\n" + "Route Distinguisher\n" + "All Route Distinguishers\n" + "Network in the BGP routing table to display\n" + "Network in the BGP routing table to display\n" + JSON_STR) +{ + int ret; + struct prefix_rd prd; + afi_t afi = AFI_MAX; + int idx = 0; + + if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + vty_out(vty, "%% Malformed Address Family\n"); + return CMD_WARNING; + } + + if (!strcmp(argv[5]->arg, "all")) + return bgp_show_route(vty, NULL, argv[6]->arg, afi, + SAFI_MPLS_VPN, NULL, 0, BGP_PATH_SHOW_ALL, + RPKI_NOT_BEING_USED, + use_json(argc, argv)); + + ret = str2prefix_rd(argv[5]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed Route Distinguisher\n"); + return CMD_WARNING; + } + + return bgp_show_route(vty, NULL, argv[6]->arg, afi, SAFI_MPLS_VPN, &prd, + 0, BGP_PATH_SHOW_ALL, RPKI_NOT_BEING_USED, + use_json(argc, argv)); +} + +static struct bgp_distance *bgp_distance_new(void) +{ + return XCALLOC(MTYPE_BGP_DISTANCE, sizeof(struct bgp_distance)); +} + +static void bgp_distance_free(struct bgp_distance *bdistance) +{ + XFREE(MTYPE_BGP_DISTANCE, bdistance); +} + +static int bgp_distance_set(struct vty *vty, const char *distance_str, + const char *ip_str, const char *access_list_str) +{ + int ret; + afi_t afi; + safi_t safi; + struct prefix p; + uint8_t distance; + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + ret = str2prefix(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + distance = atoi(distance_str); + + /* Get BGP distance node. */ + dest = bgp_node_get(bgp_distance_table[afi][safi], &p); + bdistance = bgp_dest_get_bgp_distance_info(dest); + if (bdistance) + bgp_dest_unlock_node(dest); + else { + bdistance = bgp_distance_new(); + bgp_dest_set_bgp_distance_info(dest, bdistance); + } + + /* Set distance value. */ + bdistance->distance = distance; + + /* Reset access-list configuration. */ + XFREE(MTYPE_AS_LIST, bdistance->access_list); + if (access_list_str) + bdistance->access_list = + XSTRDUP(MTYPE_AS_LIST, access_list_str); + + return CMD_SUCCESS; +} + +static int bgp_distance_unset(struct vty *vty, const char *distance_str, + const char *ip_str, const char *access_list_str) +{ + int ret; + afi_t afi; + safi_t safi; + struct prefix p; + int distance; + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + ret = str2prefix(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + dest = bgp_node_lookup(bgp_distance_table[afi][safi], &p); + if (!dest) { + vty_out(vty, "Can't find specified prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bdistance = bgp_dest_get_bgp_distance_info(dest); + distance = atoi(distance_str); + + if (bdistance->distance != distance) { + vty_out(vty, "Distance does not match configured\n"); + bgp_dest_unlock_node(dest); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_AS_LIST, bdistance->access_list); + bgp_distance_free(bdistance); + + bgp_dest_set_bgp_path_info(dest, NULL); + dest = bgp_dest_unlock_node(dest); + assert(dest); + bgp_dest_unlock_node(dest); + + return CMD_SUCCESS; +} + +/* Apply BGP information to distance method. */ +uint8_t bgp_distance_apply(const struct prefix *p, struct bgp_path_info *pinfo, + afi_t afi, safi_t safi, struct bgp *bgp) +{ + struct bgp_dest *dest; + struct prefix q = {0}; + struct peer *peer; + struct bgp_distance *bdistance; + struct access_list *alist; + struct bgp_static *bgp_static; + struct bgp_path_info *bpi_ultimate; + + if (!bgp) + return 0; + + peer = pinfo->peer; + + if (pinfo->attr->distance) + return pinfo->attr->distance; + + /* get peer origin to calculate appropriate distance */ + if (pinfo->sub_type == BGP_ROUTE_IMPORTED) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(pinfo); + peer = bpi_ultimate->peer; + } + + /* Check source address. + * Note: for aggregate route, peer can have unspec af type. + */ + if (pinfo->sub_type != BGP_ROUTE_AGGREGATE && + !sockunion2hostprefix(&peer->connection->su, &q)) + return 0; + + dest = bgp_node_match(bgp_distance_table[afi][safi], &q); + if (dest) { + bdistance = bgp_dest_get_bgp_distance_info(dest); + bgp_dest_unlock_node(dest); + + if (bdistance->access_list) { + alist = access_list_lookup(afi, bdistance->access_list); + if (alist + && access_list_apply(alist, p) == FILTER_PERMIT) + return bdistance->distance; + } else + return bdistance->distance; + } + + /* Backdoor check. */ + dest = bgp_node_lookup(bgp->route[afi][safi], p); + if (dest) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + bgp_dest_unlock_node(dest); + + if (bgp_static->backdoor) { + if (bgp->distance_local[afi][safi]) + return bgp->distance_local[afi][safi]; + else + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } + } + + if (peer->sort == BGP_PEER_EBGP) { + if (bgp->distance_ebgp[afi][safi]) + return bgp->distance_ebgp[afi][safi]; + return ZEBRA_EBGP_DISTANCE_DEFAULT; + } else if (peer->sort == BGP_PEER_IBGP) { + if (bgp->distance_ibgp[afi][safi]) + return bgp->distance_ibgp[afi][safi]; + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } else { + if (bgp->distance_local[afi][safi]) + return bgp->distance_local[afi][safi]; + return ZEBRA_IBGP_DISTANCE_DEFAULT; + } +} + +/* If we enter `distance bgp (1-255) (1-255) (1-255)`, + * we should tell ZEBRA update the routes for a specific + * AFI/SAFI to reflect changes in RIB. + */ +static void bgp_announce_routes_distance_update(struct bgp *bgp, + afi_t update_afi, + safi_t update_safi) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_fibupd_safi(safi)) + continue; + + if (afi != update_afi && safi != update_safi) + continue; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: Announcing routes due to distance change afi/safi (%d/%d)", + __func__, afi, safi); + bgp_zebra_announce_table(bgp, afi, safi); + } +} + +DEFUN (bgp_distance, + bgp_distance_cmd, + "distance bgp (1-255) (1-255) (1-255)", + "Define an administrative distance\n" + "BGP distance\n" + "Distance for routes external to the AS\n" + "Distance for routes internal to the AS\n" + "Distance for local routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 2; + int idx_number_2 = 3; + int idx_number_3 = 4; + int distance_ebgp = atoi(argv[idx_number]->arg); + int distance_ibgp = atoi(argv[idx_number_2]->arg); + int distance_local = atoi(argv[idx_number_3]->arg); + afi_t afi; + safi_t safi; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + if (bgp->distance_ebgp[afi][safi] != distance_ebgp + || bgp->distance_ibgp[afi][safi] != distance_ibgp + || bgp->distance_local[afi][safi] != distance_local) { + bgp->distance_ebgp[afi][safi] = distance_ebgp; + bgp->distance_ibgp[afi][safi] = distance_ibgp; + bgp->distance_local[afi][safi] = distance_local; + bgp_announce_routes_distance_update(bgp, afi, safi); + } + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance, + no_bgp_distance_cmd, + "no distance bgp [(1-255) (1-255) (1-255)]", + NO_STR + "Define an administrative distance\n" + "BGP distance\n" + "Distance for routes external to the AS\n" + "Distance for routes internal to the AS\n" + "Distance for local routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + safi_t safi; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + if (bgp->distance_ebgp[afi][safi] != 0 + || bgp->distance_ibgp[afi][safi] != 0 + || bgp->distance_local[afi][safi] != 0) { + bgp->distance_ebgp[afi][safi] = 0; + bgp->distance_ibgp[afi][safi] = 0; + bgp->distance_local[afi][safi] = 0; + bgp_announce_routes_distance_update(bgp, afi, safi); + } + return CMD_SUCCESS; +} + + +DEFUN (bgp_distance_source, + bgp_distance_source_cmd, + "distance (1-255) A.B.C.D/M", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + int idx_number = 1; + int idx_ipv4_prefixlen = 2; + bgp_distance_set(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance_source, + no_bgp_distance_source_cmd, + "no distance (1-255) A.B.C.D/M", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + int idx_number = 2; + int idx_ipv4_prefixlen = 3; + bgp_distance_unset(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (bgp_distance_source_access_list, + bgp_distance_source_access_list_cmd, + "distance (1-255) A.B.C.D/M WORD", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + int idx_number = 1; + int idx_ipv4_prefixlen = 2; + int idx_word = 3; + bgp_distance_set(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_distance_source_access_list, + no_bgp_distance_source_access_list_cmd, + "no distance (1-255) A.B.C.D/M WORD", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + int idx_number = 2; + int idx_ipv4_prefixlen = 3; + int idx_word = 4; + bgp_distance_unset(vty, argv[idx_number]->arg, + argv[idx_ipv4_prefixlen]->arg, argv[idx_word]->arg); + return CMD_SUCCESS; +} + +DEFUN (ipv6_bgp_distance_source, + ipv6_bgp_distance_source_cmd, + "distance (1-255) X:X::X:X/M", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_bgp_distance_source, + no_ipv6_bgp_distance_source_cmd, + "no distance (1-255) X:X::X:X/M", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n") +{ + bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, NULL); + return CMD_SUCCESS; +} + +DEFUN (ipv6_bgp_distance_source_access_list, + ipv6_bgp_distance_source_access_list_cmd, + "distance (1-255) X:X::X:X/M WORD", + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + bgp_distance_set(vty, argv[1]->arg, argv[2]->arg, argv[3]->arg); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_bgp_distance_source_access_list, + no_ipv6_bgp_distance_source_access_list_cmd, + "no distance (1-255) X:X::X:X/M WORD", + NO_STR + "Define an administrative distance\n" + "Administrative distance\n" + "IP source prefix\n" + "Access list name\n") +{ + bgp_distance_unset(vty, argv[2]->arg, argv[3]->arg, argv[4]->arg); + return CMD_SUCCESS; +} + +DEFUN (bgp_damp_set, + bgp_damp_set_cmd, + "bgp dampening [(1-45) [(1-20000) (1-50000) (1-255)]]", + "BGP Specific commands\n" + "Enable route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_half_life = 2; + int idx_reuse = 3; + int idx_suppress = 4; + int idx_max_suppress = 5; + int half = DEFAULT_HALF_LIFE * 60; + int reuse = DEFAULT_REUSE; + int suppress = DEFAULT_SUPPRESS; + int max = 4 * half; + + if (argc == 6) { + half = atoi(argv[idx_half_life]->arg) * 60; + reuse = atoi(argv[idx_reuse]->arg); + suppress = atoi(argv[idx_suppress]->arg); + max = atoi(argv[idx_max_suppress]->arg) * 60; + } else if (argc == 3) { + half = atoi(argv[idx_half_life]->arg) * 60; + max = 4 * half; + } + + /* + * These can't be 0 but our SA doesn't understand the + * way our cli is constructed + */ + assert(reuse); + assert(half); + if (suppress < reuse) { + vty_out(vty, + "Suppress value cannot be less than reuse value \n"); + return 0; + } + + return bgp_damp_enable(bgp, bgp_node_afi(vty), bgp_node_safi(vty), half, + reuse, suppress, max); +} + +DEFUN (bgp_damp_unset, + bgp_damp_unset_cmd, + "no bgp dampening [(1-45) [(1-20000) (1-50000) (1-255)]]", + NO_STR + "BGP Specific commands\n" + "Enable route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + return bgp_damp_disable(bgp, bgp_node_afi(vty), bgp_node_safi(vty)); +} + +/* Display specified route of BGP table. */ +static int bgp_clear_damp_route(struct vty *vty, const char *view_name, + const char *ip_str, afi_t afi, safi_t safi, + struct prefix_rd *prd, int prefix_check) +{ + int ret; + struct prefix match; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp_path_info *pi; + struct bgp_path_info *pi_temp; + struct bgp *bgp; + struct bgp_table *table; + + /* BGP structure lookup. */ + if (view_name) { + bgp = bgp_lookup_by_name(view_name); + if (bgp == NULL) { + vty_out(vty, "%% Can't find BGP instance %s\n", + view_name); + return CMD_WARNING; + } + } else { + bgp = bgp_get_default(); + if (bgp == NULL) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING; + } + } + + /* Check IP address argument. */ + ret = str2prefix(ip_str, &match); + if (!ret) { + vty_out(vty, "%% address is malformed\n"); + return CMD_WARNING; + } + + match.family = afi2family(afi); + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP) + || (safi == SAFI_EVPN)) { + for (dest = bgp_table_top(bgp->rib[AFI_IP][safi]); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + rm = bgp_node_match(table, &match); + if (rm == NULL) + continue; + + const struct prefix *rm_p = bgp_dest_get_prefix(dest); + + if (!prefix_check + || rm_p->prefixlen == match.prefixlen) { + pi = bgp_dest_get_bgp_path_info(rm); + while (pi) { + if (pi->extra && pi->extra->damp_info) { + pi_temp = pi->next; + bgp_damp_info_free(pi->extra->damp_info, + NULL, 1); + pi = pi_temp; + } else + pi = pi->next; + } + } + + bgp_dest_unlock_node(rm); + } + } else { + dest = bgp_node_match(bgp->rib[afi][safi], &match); + if (!dest) + return CMD_SUCCESS; + + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prefix_check || dest_p->prefixlen != match.prefixlen) + return CMD_SUCCESS; + + pi = bgp_dest_get_bgp_path_info(dest); + while (pi) { + if (!(pi->extra && pi->extra->damp_info)) { + pi = pi->next; + continue; + } + + pi_temp = pi->next; + struct bgp_damp_info *bdi = pi->extra->damp_info; + + if (bdi->lastrecord != BGP_RECORD_UPDATE) + continue; + + bgp_aggregate_increment(bgp, + bgp_dest_get_prefix(bdi->dest), + bdi->path, bdi->afi, bdi->safi); + bgp_process(bgp, bdi->dest, bdi->path, bdi->afi, + bdi->safi); + + bgp_damp_info_free(pi->extra->damp_info, NULL, 1); + pi = pi_temp; + } + + bgp_dest_unlock_node(dest); + } + + return CMD_SUCCESS; +} + +DEFUN (clear_ip_bgp_dampening, + clear_ip_bgp_dampening_cmd, + "clear ip bgp dampening", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_damp_info_clean(bgp, &bgp->damp[AFI_IP][SAFI_UNICAST], AFI_IP, + SAFI_UNICAST); + return CMD_SUCCESS; +} + +DEFUN (clear_ip_bgp_dampening_prefix, + clear_ip_bgp_dampening_prefix_cmd, + "clear ip bgp dampening A.B.C.D/M", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "IPv4 prefix\n") +{ + int idx_ipv4_prefixlen = 4; + return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4_prefixlen]->arg, + AFI_IP, SAFI_UNICAST, NULL, 1); +} + +DEFUN (clear_ip_bgp_dampening_address, + clear_ip_bgp_dampening_address_cmd, + "clear ip bgp dampening A.B.C.D", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "Network to clear damping information\n") +{ + int idx_ipv4 = 4; + return bgp_clear_damp_route(vty, NULL, argv[idx_ipv4]->arg, AFI_IP, + SAFI_UNICAST, NULL, 0); +} + +DEFUN (clear_ip_bgp_dampening_address_mask, + clear_ip_bgp_dampening_address_mask_cmd, + "clear ip bgp dampening A.B.C.D A.B.C.D", + CLEAR_STR + IP_STR + BGP_STR + "Clear route flap dampening information\n" + "Network to clear damping information\n" + "Network mask\n") +{ + int idx_ipv4 = 4; + int idx_ipv4_2 = 5; + int ret; + char prefix_str[BUFSIZ]; + + ret = netmask_str2prefix_str(argv[idx_ipv4]->arg, argv[idx_ipv4_2]->arg, + prefix_str, sizeof(prefix_str)); + if (!ret) { + vty_out(vty, "%% Inconsistent address and mask\n"); + return CMD_WARNING; + } + + return bgp_clear_damp_route(vty, NULL, prefix_str, AFI_IP, SAFI_UNICAST, + NULL, 0); +} + +static void show_bgp_peerhash_entry(struct hash_bucket *bucket, void *arg) +{ + struct vty *vty = arg; + struct peer *peer = bucket->data; + + vty_out(vty, "\tPeer: %s %pSU\n", peer->host, &peer->connection->su); +} + +DEFUN (show_bgp_listeners, + show_bgp_listeners_cmd, + "show bgp listeners", + SHOW_STR + BGP_STR + "Display Listen Sockets and who created them\n") +{ + bgp_dump_listener_info(vty); + + return CMD_SUCCESS; +} + +DEFUN (show_bgp_peerhash, + show_bgp_peerhash_cmd, + "show bgp peerhash", + SHOW_STR + BGP_STR + "Display information about the BGP peerhash\n") +{ + struct list *instances = bm->bgp; + struct listnode *node; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS_RO(instances, node, bgp)) { + vty_out(vty, "BGP: %s\n", bgp->name_pretty); + hash_iterate(bgp->peerhash, show_bgp_peerhash_entry, + vty); + } + + return CMD_SUCCESS; +} + +/* also used for encap safi */ +static void bgp_config_write_network_vpn(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + const struct prefix *p; + struct bgp_static *bgp_static; + mpls_label_t label; + + /* Network configuration. */ + for (pdest = bgp_table_top(bgp->route[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + /* "network" configuration display. */ + label = decode_label(&bgp_static->label); + + vty_out(vty, " network %pFX rd %s", p, + bgp_static->prd_pretty); + if (safi == SAFI_MPLS_VPN) + vty_out(vty, " label %u", label); + + if (bgp_static->rmap.name) + vty_out(vty, " route-map %s", + bgp_static->rmap.name); + + if (bgp_static->backdoor) + vty_out(vty, " backdoor"); + + vty_out(vty, "\n"); + } + } +} + +static void bgp_config_write_network_evpn(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + struct bgp_dest *pdest; + struct bgp_dest *dest; + struct bgp_table *table; + const struct prefix *p; + struct bgp_static *bgp_static; + char buf[PREFIX_STRLEN * 2]; + char buf2[SU_ADDRSTRLEN]; + char esi_buf[ESI_STR_LEN]; + + /* Network configuration. */ + for (pdest = bgp_table_top(bgp->route[afi][safi]); pdest; + pdest = bgp_route_next(pdest)) { + table = bgp_dest_get_bgp_table_info(pdest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + char *macrouter = NULL; + + if (bgp_static->router_mac) + macrouter = prefix_mac2str( + bgp_static->router_mac, NULL, 0); + if (bgp_static->eth_s_id) + esi_to_str(bgp_static->eth_s_id, + esi_buf, sizeof(esi_buf)); + p = bgp_dest_get_prefix(dest); + + /* "network" configuration display. */ + if (p->u.prefix_evpn.route_type == 5) { + char local_buf[PREFIX_STRLEN]; + + uint8_t family = is_evpn_prefix_ipaddr_v4(( + struct prefix_evpn *)p) + ? AF_INET + : AF_INET6; + inet_ntop(family, + &p->u.prefix_evpn.prefix_addr.ip.ip + .addr, + local_buf, sizeof(local_buf)); + snprintf(buf, sizeof(buf), "%s/%u", local_buf, + p->u.prefix_evpn.prefix_addr + .ip_prefix_length); + } else { + prefix2str(p, buf, sizeof(buf)); + } + + if (bgp_static->gatewayIp.family == AF_INET + || bgp_static->gatewayIp.family == AF_INET6) + inet_ntop(bgp_static->gatewayIp.family, + &bgp_static->gatewayIp.u.prefix, buf2, + sizeof(buf2)); + vty_out(vty, + " network %s rd %s ethtag %u label %u esi %s gwip %s routermac %s\n", + buf, bgp_static->prd_pretty, + p->u.prefix_evpn.prefix_addr.eth_tag, + decode_label(&bgp_static->label), esi_buf, buf2, + macrouter); + + XFREE(MTYPE_TMP, macrouter); + } + } +} + +/* Configuration of static route announcement and aggregate + information. */ +void bgp_config_write_network(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_dest *dest; + const struct prefix *p; + struct bgp_static *bgp_static; + struct bgp_aggregate *bgp_aggregate; + + if ((safi == SAFI_MPLS_VPN) || (safi == SAFI_ENCAP)) { + bgp_config_write_network_vpn(vty, bgp, afi, safi); + return; + } + + if (afi == AFI_L2VPN && safi == SAFI_EVPN) { + bgp_config_write_network_evpn(vty, bgp, afi, safi); + return; + } + + /* Network configuration. */ + for (dest = bgp_table_top(bgp->route[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bgp_static = bgp_dest_get_bgp_static_info(dest); + if (bgp_static == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + vty_out(vty, " network %pFX", p); + + if (bgp_static->label_index != BGP_INVALID_LABEL_INDEX) + vty_out(vty, " label-index %u", + bgp_static->label_index); + + if (bgp_static->rmap.name) + vty_out(vty, " route-map %s", bgp_static->rmap.name); + + if (bgp_static->backdoor) + vty_out(vty, " backdoor"); + + vty_out(vty, "\n"); + } + + /* Aggregate-address configuration. */ + for (dest = bgp_table_top(bgp->aggregate[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bgp_aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (bgp_aggregate == NULL) + continue; + + p = bgp_dest_get_prefix(dest); + + vty_out(vty, " aggregate-address %pFX", p); + + if (bgp_aggregate->as_set) + vty_out(vty, " as-set"); + + if (bgp_aggregate->summary_only) + vty_out(vty, " summary-only"); + + if (bgp_aggregate->rmap.name) + vty_out(vty, " route-map %s", bgp_aggregate->rmap.name); + + if (bgp_aggregate->origin != BGP_ORIGIN_UNSPECIFIED) + vty_out(vty, " origin %s", + bgp_origin2str(bgp_aggregate->origin)); + + if (bgp_aggregate->match_med) + vty_out(vty, " matching-MED-only"); + + if (bgp_aggregate->suppress_map_name) + vty_out(vty, " suppress-map %s", + bgp_aggregate->suppress_map_name); + + vty_out(vty, "\n"); + } +} + +void bgp_config_write_distance(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_distance *bdistance; + + /* Distance configuration. */ + if (bgp->distance_ebgp[afi][safi] && bgp->distance_ibgp[afi][safi] + && bgp->distance_local[afi][safi] + && (bgp->distance_ebgp[afi][safi] != ZEBRA_EBGP_DISTANCE_DEFAULT + || bgp->distance_ibgp[afi][safi] != ZEBRA_IBGP_DISTANCE_DEFAULT + || bgp->distance_local[afi][safi] + != ZEBRA_IBGP_DISTANCE_DEFAULT)) { + vty_out(vty, " distance bgp %d %d %d\n", + bgp->distance_ebgp[afi][safi], + bgp->distance_ibgp[afi][safi], + bgp->distance_local[afi][safi]); + } + + for (dest = bgp_table_top(bgp_distance_table[afi][safi]); dest; + dest = bgp_route_next(dest)) { + bdistance = bgp_dest_get_bgp_distance_info(dest); + if (bdistance != NULL) + vty_out(vty, " distance %d %pBD %s\n", + bdistance->distance, dest, + bdistance->access_list ? bdistance->access_list + : ""); + } +} + +/* Allocate routing table structure and install commands. */ +void bgp_route_init(void) +{ + afi_t afi; + safi_t safi; + + /* Init BGP distance table. */ + FOREACH_AFI_SAFI (afi, safi) + bgp_distance_table[afi][safi] = bgp_table_init(NULL, afi, safi); + + /* IPv4 BGP commands. */ + install_element(BGP_NODE, &bgp_table_map_cmd); + install_element(BGP_NODE, &bgp_network_cmd); + install_element(BGP_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_NODE, &aggregate_addressv4_cmd); + + /* IPv4 unicast configuration. */ + install_element(BGP_IPV4_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV4_NODE, &bgp_network_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_IPV4_NODE, &aggregate_addressv4_cmd); + + /* IPv4 multicast configuration. */ + install_element(BGP_IPV4M_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV4M_NODE, &bgp_network_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_table_map_cmd); + install_element(BGP_IPV4M_NODE, &aggregate_addressv4_cmd); + + /* IPv4 labeled-unicast configuration. */ + install_element(BGP_IPV4L_NODE, &bgp_network_cmd); + install_element(BGP_IPV4L_NODE, &aggregate_addressv4_cmd); + + install_element(VIEW_NODE, &show_ip_bgp_instance_all_cmd); + install_element(VIEW_NODE, &show_ip_bgp_afi_safi_statistics_cmd); + install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_statistics_cmd); + install_element(VIEW_NODE, &show_ip_bgp_dampening_params_cmd); + install_element(VIEW_NODE, &show_ip_bgp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_route_cmd); + install_element(VIEW_NODE, &show_ip_bgp_regexp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_statistics_all_cmd); + + install_element(VIEW_NODE, + &show_ip_bgp_instance_neighbor_advertised_route_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_instance_neighbor_bestpath_route_cmd); + install_element(VIEW_NODE, &show_ip_bgp_neighbor_routes_cmd); + install_element(VIEW_NODE, + &show_ip_bgp_neighbor_received_prefix_filter_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(VIEW_NODE, &show_ip_bgp_vpn_all_route_prefix_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + install_element(VIEW_NODE, &show_bgp_afi_vpn_rd_route_cmd); + install_element(VIEW_NODE, + &show_bgp_l2vpn_evpn_route_prefix_cmd); + + /* BGP dampening clear commands */ + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_cmd); + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_prefix_cmd); + + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_cmd); + install_element(ENABLE_NODE, &clear_ip_bgp_dampening_address_mask_cmd); + + /* prefix count */ + install_element(ENABLE_NODE, + &show_ip_bgp_instance_neighbor_prefix_counts_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(ENABLE_NODE, + &show_ip_bgp_vpn_neighbor_prefix_counts_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + + /* New config IPv6 BGP commands. */ + install_element(BGP_IPV6_NODE, &bgp_table_map_cmd); + install_element(BGP_IPV6_NODE, &ipv6_bgp_network_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_table_map_cmd); + + install_element(BGP_IPV6_NODE, &aggregate_addressv6_cmd); + + install_element(BGP_IPV6M_NODE, &ipv6_bgp_network_cmd); + + /* IPv6 labeled unicast address family. */ + install_element(BGP_IPV6L_NODE, &ipv6_bgp_network_cmd); + install_element(BGP_IPV6L_NODE, &aggregate_addressv6_cmd); + + install_element(BGP_NODE, &bgp_distance_cmd); + install_element(BGP_NODE, &no_bgp_distance_cmd); + install_element(BGP_NODE, &bgp_distance_source_cmd); + install_element(BGP_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_NODE, &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_source_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_IPV4_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_source_cmd); + install_element(BGP_IPV4M_NODE, &no_bgp_distance_source_cmd); + install_element(BGP_IPV4M_NODE, &bgp_distance_source_access_list_cmd); + install_element(BGP_IPV4M_NODE, + &no_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6_NODE, &bgp_distance_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV6_NODE, &ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6_NODE, &no_ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6_NODE, + &ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6_NODE, + &no_ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6M_NODE, &bgp_distance_cmd); + install_element(BGP_IPV6M_NODE, &no_bgp_distance_cmd); + install_element(BGP_IPV6M_NODE, &ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6M_NODE, &no_ipv6_bgp_distance_source_cmd); + install_element(BGP_IPV6M_NODE, + &ipv6_bgp_distance_source_access_list_cmd); + install_element(BGP_IPV6M_NODE, + &no_ipv6_bgp_distance_source_access_list_cmd); + + /* BGP dampening */ + install_element(BGP_NODE, &bgp_damp_set_cmd); + install_element(BGP_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4M_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4M_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV4L_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV4L_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6M_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6M_NODE, &bgp_damp_unset_cmd); + install_element(BGP_IPV6L_NODE, &bgp_damp_set_cmd); + install_element(BGP_IPV6L_NODE, &bgp_damp_unset_cmd); + + /* Large Communities */ + install_element(VIEW_NODE, &show_ip_bgp_large_community_list_cmd); + install_element(VIEW_NODE, &show_ip_bgp_large_community_cmd); + + /* show bgp vrf detailed */ + install_element(VIEW_NODE, + &show_ip_bgp_vrf_afi_safi_routes_detailed_cmd); + + install_element(VIEW_NODE, &show_bgp_listeners_cmd); + install_element(VIEW_NODE, &show_bgp_peerhash_cmd); +} + +void bgp_route_finish(void) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + bgp_table_unlock(bgp_distance_table[afi][safi]); + bgp_distance_table[afi][safi] = NULL; + } +} diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h new file mode 100644 index 0000000..efabbc7 --- /dev/null +++ b/bgpd/bgp_route.h @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP routing information base + * Copyright (C) 1996, 97, 98, 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ROUTE_H +#define _QUAGGA_BGP_ROUTE_H + +#include + +#include "hook.h" +#include "queue.h" +#include "nexthop.h" +#include "bgp_table.h" +#include "bgp_addpath_types.h" +#include "bgp_rpki.h" + +struct bgp_nexthop_cache; +struct bgp_route_evpn; + +enum bgp_show_type { + bgp_show_type_normal, + bgp_show_type_regexp, + bgp_show_type_prefix_list, + bgp_show_type_access_list, + bgp_show_type_filter_list, + bgp_show_type_route_map, + bgp_show_type_neighbor, + bgp_show_type_cidr_only, + bgp_show_type_prefix_longer, + bgp_show_type_community_alias, + bgp_show_type_community_all, + bgp_show_type_community, + bgp_show_type_community_exact, + bgp_show_type_community_list, + bgp_show_type_community_list_exact, + bgp_show_type_lcommunity_all, + bgp_show_type_lcommunity, + bgp_show_type_lcommunity_exact, + bgp_show_type_lcommunity_list, + bgp_show_type_lcommunity_list_exact, + bgp_show_type_flap_statistics, + bgp_show_type_flap_neighbor, + bgp_show_type_dampend_paths, + bgp_show_type_damp_neighbor, + bgp_show_type_detail, + bgp_show_type_rpki, + bgp_show_type_prefix_version, + bgp_show_type_self_originated, +}; + +enum bgp_show_adj_route_type { + bgp_show_adj_route_advertised, + bgp_show_adj_route_received, + bgp_show_adj_route_filtered, + bgp_show_adj_route_bestpath, +}; + + +#define BGP_SHOW_SCODE_HEADER \ + "Status codes: s suppressed, d damped, " \ + "h history, u unsorted, * valid, > best, = multipath,\n" \ + " i internal, r RIB-failure, S Stale, R Removed\n" +#define BGP_SHOW_OCODE_HEADER \ + "Origin codes: i - IGP, e - EGP, ? - incomplete\n" +#define BGP_SHOW_NCODE_HEADER "Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self\n" +#define BGP_SHOW_RPKI_HEADER \ + "RPKI validation codes: V valid, I invalid, N Not found\n\n" +#define BGP_SHOW_HEADER " Network Next Hop Metric LocPrf Weight Path\n" +#define BGP_SHOW_HEADER_WIDE " Network Next Hop Metric LocPrf Weight Path\n" + +/* Maximum number of sids we can process or send with a prefix. */ +#define BGP_MAX_SIDS 6 + +/* Maximum buffer length for storing BGP best path selection reason */ +#define BGP_MAX_SELECTION_REASON_STR_BUF 32 + +/* Error codes for handling NLRI */ +#define BGP_NLRI_PARSE_OK 0 +#define BGP_NLRI_PARSE_ERROR_PREFIX_OVERFLOW -1 +#define BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW -2 +#define BGP_NLRI_PARSE_ERROR_PREFIX_LENGTH -3 +#define BGP_NLRI_PARSE_ERROR_PACKET_LENGTH -4 +#define BGP_NLRI_PARSE_ERROR_LABEL_LENGTH -5 +#define BGP_NLRI_PARSE_ERROR_EVPN_MISSING_TYPE -6 +#define BGP_NLRI_PARSE_ERROR_EVPN_TYPE2_SIZE -7 +#define BGP_NLRI_PARSE_ERROR_EVPN_TYPE3_SIZE -8 +#define BGP_NLRI_PARSE_ERROR_EVPN_TYPE4_SIZE -9 +#define BGP_NLRI_PARSE_ERROR_EVPN_TYPE5_SIZE -10 +#define BGP_NLRI_PARSE_ERROR_FLOWSPEC_IPV6_NOT_SUPPORTED -11 +#define BGP_NLRI_PARSE_ERROR_FLOWSPEC_NLRI_SIZELIMIT -12 +#define BGP_NLRI_PARSE_ERROR_FLOWSPEC_BAD_FORMAT -13 +#define BGP_NLRI_PARSE_ERROR_ADDRESS_FAMILY -14 +#define BGP_NLRI_PARSE_ERROR_EVPN_TYPE1_SIZE -15 +#define BGP_NLRI_PARSE_ERROR -32 + +/* 1. local MAC-IP/type-2 paths in the VNI routing table are linked to the + * destination ES + * 2. remote MAC-IP paths in the global routing table are linked to the + * destination ES + */ +struct bgp_path_es_info { + /* back pointer to the route */ + struct bgp_path_info *pi; + vni_t vni; + /* destination ES */ + struct bgp_evpn_es *es; + /* memory used for linking the path to the destination ES */ + struct listnode es_listnode; + uint8_t flags; +/* Path is linked to the VNI list */ +#define BGP_EVPN_PATH_ES_INFO_VNI_LIST (1 << 0) +/* Path is linked to the global list */ +#define BGP_EVPN_PATH_ES_INFO_GLOBAL_LIST (1 << 1) +}; + +/* IP paths imported into the VRF from an EVPN route source + * are linked to the nexthop/VTEP IP + */ +struct bgp_path_evpn_nh_info { + /* back pointer to the route */ + struct bgp_path_info *pi; + struct bgp_evpn_nh *nh; + /* memory used for linking the path to the nexthop */ + struct listnode nh_listnode; +}; + +struct bgp_path_mh_info { + struct bgp_path_es_info *es_info; + struct bgp_path_evpn_nh_info *nh_info; +}; + +struct bgp_sid_info { + struct in6_addr sid; + uint8_t loc_block_len; + uint8_t loc_node_len; + uint8_t func_len; + uint8_t arg_len; + uint8_t transposition_len; + uint8_t transposition_offset; +}; + +/* new structure for EVPN */ +struct bgp_path_info_extra_evpn { +#define BGP_EVPN_MACIP_TYPE_SVI_IP (1 << 0) + /* af specific flags */ + uint16_t af_flags; + union { + struct ethaddr mac; /* MAC set here for VNI IP table */ + struct ipaddr ip; /* IP set here for VNI MAC table */ + } vni_info; + /* Destination Ethernet Segment links for EVPN MH */ + struct bgp_path_mh_info *mh_info; +}; + +/* new structure for flowspec*/ +struct bgp_path_info_extra_fs { + /* presence of FS pbr firewall based entry */ + struct list *bgp_fs_pbr; + /* presence of FS pbr iprule based entry */ + struct list *bgp_fs_iprule; +}; + +/* new structure for vrfleak*/ +struct bgp_path_info_extra_vrfleak { + void *parent; /* parent from global table */ + /* + * Original bgp instance for imported routes. Needed for: + * 1. Find all routes from a specific vrf for deletion + * 2. vrf context of original nexthop + * + * Store pointer to bgp instance rather than bgp->vrf_id because + * bgp->vrf_id is not always valid (or may change?). + * + * Set to NULL if route is not imported from another bgp instance. + */ + struct bgp *bgp_orig; + /* + * Original bgp session to know if the session is a + * connected EBGP session or not + */ + struct peer *peer_orig; + /* + * Nexthop in context of original bgp instance. Needed + * for label resolution of core mpls routes exported to a vrf. + * Set nexthop_orig.family to 0 if not valid. + */ + struct prefix nexthop_orig; +}; + +#ifdef ENABLE_BGP_VNC +struct bgp_path_info_extra_vnc { + union { + struct { + void *rfapi_handle; /* export: NVE advertising this + route */ + struct list *local_nexthops; /* optional, for static + routes */ + } export; + + struct { + struct event *timer; + void *hme; /* encap monitor, if this is a VPN route */ + struct prefix_rd + rd; /* import: route's route-distinguisher */ + uint8_t un_family; /* family of cached un address, 0 if + unset */ + union { + struct in_addr addr4; + struct in6_addr addr6; + } un; /* cached un address */ + time_t create_time; + struct prefix aux_prefix; /* AFI_L2VPN: the IP addr, + if family set */ + } import; + } vnc; +}; +#endif + +/* Ancillary information to struct bgp_path_info, + * used for uncommonly used data (aggregation, MPLS, etc.) + * and lazily allocated to save memory. + */ +struct bgp_path_info_extra { + /* Pointer to dampening structure. */ + struct bgp_damp_info *damp_info; + + /** List of aggregations that suppress this path. */ + struct list *aggr_suppressors; + + /* Nexthop reachability check. */ + uint32_t igpmetric; + + /* MPLS label(s) - VNI(s) for EVPN-VxLAN */ + struct bgp_labels *labels; + + /* timestamp of the rib installation */ + time_t bgp_rib_uptime; + + /*For EVPN*/ + struct bgp_path_info_extra_evpn *evpn; + +#ifdef ENABLE_BGP_VNC + struct bgp_path_info_extra_vnc *vnc; +#endif + + /* For flowspec*/ + struct bgp_path_info_extra_fs *flowspec; + + /* For vrf leaking*/ + struct bgp_path_info_extra_vrfleak *vrfleak; +}; + +struct bgp_mplsvpn_label_nh { + /* For nexthop per label linked list */ + LIST_ENTRY(bgp_path_info) label_nh_thread; + + /* Back pointer to the bgp label per nexthop structure */ + struct bgp_label_per_nexthop_cache *label_nexthop_cache; +}; + +struct bgp_mplsvpn_nh_label_bind { + /* For mplsvpn nexthop label bind linked list */ + LIST_ENTRY(bgp_path_info) nh_label_bind_thread; + + /* Back pointer to the bgp mplsvpn nexthop label bind structure */ + struct bgp_mplsvpn_nh_label_bind_cache *nh_label_bind_cache; +}; + +struct bgp_path_info { + /* For linked list. */ + struct bgp_path_info *next; + struct bgp_path_info *prev; + + /* For nexthop linked list */ + LIST_ENTRY(bgp_path_info) nh_thread; + + /* Back pointer to the prefix node */ + struct bgp_dest *net; + + /* Back pointer to the nexthop structure */ + struct bgp_nexthop_cache *nexthop; + + /* Peer structure. */ + struct peer *peer; + + /* Attribute structure. */ + struct attr *attr; + + /* Extra information */ + struct bgp_path_info_extra *extra; + + + /* Multipath information */ + struct bgp_path_info_mpath *mpath; + + /* Uptime. */ + time_t uptime; + + /* reference count */ + int lock; + + /* BGP information status. */ + uint32_t flags; +#define BGP_PATH_IGP_CHANGED (1 << 0) +#define BGP_PATH_DAMPED (1 << 1) +#define BGP_PATH_HISTORY (1 << 2) +#define BGP_PATH_SELECTED (1 << 3) +#define BGP_PATH_VALID (1 << 4) +#define BGP_PATH_ATTR_CHANGED (1 << 5) +#define BGP_PATH_DMED_CHECK (1 << 6) +#define BGP_PATH_DMED_SELECTED (1 << 7) +#define BGP_PATH_STALE (1 << 8) +#define BGP_PATH_REMOVED (1 << 9) +#define BGP_PATH_COUNTED (1 << 10) +#define BGP_PATH_MULTIPATH (1 << 11) +#define BGP_PATH_MULTIPATH_CHG (1 << 12) +#define BGP_PATH_RIB_ATTR_CHG (1 << 13) +#define BGP_PATH_ANNC_NH_SELF (1 << 14) +#define BGP_PATH_LINK_BW_CHG (1 << 15) +#define BGP_PATH_ACCEPT_OWN (1 << 16) +#define BGP_PATH_MPLSVPN_LABEL_NH (1 << 17) +#define BGP_PATH_MPLSVPN_NH_LABEL_BIND (1 << 18) +#define BGP_PATH_UNSORTED (1 << 19) + + /* BGP route type. This can be static, RIP, OSPF, BGP etc. */ + uint8_t type; + + /* When above type is BGP. This sub type specify BGP sub type + information. */ + uint8_t sub_type; +#define BGP_ROUTE_NORMAL 0 +#define BGP_ROUTE_STATIC 1 +#define BGP_ROUTE_AGGREGATE 2 +#define BGP_ROUTE_REDISTRIBUTE 3 +#ifdef ENABLE_BGP_VNC +# define BGP_ROUTE_RFP 4 +#endif +#define BGP_ROUTE_IMPORTED 5 /* from another bgp instance/safi */ + + unsigned short instance; + + enum bgp_path_selection_reason reason; + + /* Addpath identifiers */ + uint32_t addpath_rx_id; + struct bgp_addpath_info_data tx_addpath; + + union { + struct bgp_mplsvpn_label_nh blnc; + struct bgp_mplsvpn_nh_label_bind bmnc; + } mplsvpn; +}; + +/* Structure used in BGP path selection */ +struct bgp_path_info_pair { + struct bgp_path_info *old; + struct bgp_path_info *new; +}; + +/* BGP static route configuration. */ +struct bgp_static { + /* Backdoor configuration. */ + int backdoor; + + /* Label index configuration; applies to LU prefixes. */ + uint32_t label_index; +#define BGP_INVALID_LABEL_INDEX 0xFFFFFFFF + + /* Import check status. */ + uint8_t valid; + + uint16_t encap_tunneltype; + + /* IGP metric. */ + uint32_t igpmetric; + + /* IGP nexthop. */ + struct in_addr igpnexthop; + + /* Atomic set reference count (ie cause of pathlimit) */ + uint32_t atomic; + + /* BGP redistribute route-map. */ + struct { + char *name; + struct route_map *map; + } rmap; + + /* Route Distinguisher */ + struct prefix_rd prd; + char *prd_pretty; + + /* MPLS label. */ + mpls_label_t label; + + /* EVPN */ + esi_t *eth_s_id; + struct ethaddr *router_mac; + struct prefix gatewayIp; +}; + +/* Aggreagete address: + * + * advertise-map Set condition to advertise attribute + * as-set Generate AS set path information + * attribute-map Set attributes of aggregate + * route-map Set parameters of aggregate + * summary-only Filter more specific routes from updates + * suppress-map Conditionally filter more specific routes from updates + * + */ +struct bgp_aggregate { + /* Summary-only flag. */ + uint8_t summary_only; + + /* AS set generation. */ + uint8_t as_set; + + /* Optional modify flag to override ORIGIN */ + uint8_t origin; + + /** Are there MED mismatches? */ + bool med_mismatched; + /* MED matching state. */ + /** Did we get the first MED value? */ + bool med_initialized; + /** Match only equal MED. */ + bool match_med; + + /* Route-map for aggregated route. */ + struct { + char *name; + struct route_map *map; + bool changed; + } rmap; + + /* Suppress-count. */ + unsigned long count; + + /* Count of routes of origin type incomplete under this aggregate. */ + unsigned long incomplete_origin_count; + + /* Count of routes of origin type egp under this aggregate. */ + unsigned long egp_origin_count; + + /* Hash containing the communities of all the + * routes under this aggregate. + */ + struct hash *community_hash; + + /* Hash containing the extended communities of all the + * routes under this aggregate. + */ + struct hash *ecommunity_hash; + + /* Hash containing the large communities of all the + * routes under this aggregate. + */ + struct hash *lcommunity_hash; + + /* Hash containing the AS-Path of all the + * routes under this aggregate. + */ + struct hash *aspath_hash; + + /* Aggregate route's community. */ + struct community *community; + + /* Aggregate route's extended community. */ + struct ecommunity *ecommunity; + + /* Aggregate route's large community. */ + struct lcommunity *lcommunity; + + /* Aggregate route's as-path. */ + struct aspath *aspath; + + /* SAFI configuration. */ + safi_t safi; + + /** MED value found in current group. */ + uint32_t med_matched_value; + + /** + * Test if aggregated address MED of all route match, otherwise + * returns `false`. This macro will also return `true` if MED + * matching is disabled. + */ +#define AGGREGATE_MED_VALID(aggregate) \ + (((aggregate)->match_med && !(aggregate)->med_mismatched) \ + || !(aggregate)->match_med) + + /** Suppress map route map name (`NULL` when disabled). */ + char *suppress_map_name; + /** Suppress map route map pointer. */ + struct route_map *suppress_map; +}; + +#define BGP_NEXTHOP_AFI_FROM_NHLEN(nhlen) \ + ((nhlen) < IPV4_MAX_BYTELEN \ + ? 0 \ + : ((nhlen) < IPV6_MAX_BYTELEN ? AFI_IP : AFI_IP6)) + +#define BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr) \ + ((attr)->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL || \ + (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL || \ + (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL || \ + (attr)->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) + +#define BGP_ATTR_NEXTHOP_AFI_IP6(attr) \ + (!CHECK_FLAG(attr->flag, ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP)) && \ + BGP_ATTR_MP_NEXTHOP_LEN_IP6(attr)) + +#define BGP_PATH_COUNTABLE(BI) \ + (!CHECK_FLAG((BI)->flags, BGP_PATH_HISTORY) \ + && !CHECK_FLAG((BI)->flags, BGP_PATH_REMOVED)) + +/* Flags which indicate a route is unuseable in some form */ +#define BGP_PATH_UNUSEABLE \ + (BGP_PATH_HISTORY | BGP_PATH_DAMPED | BGP_PATH_REMOVED) +/* Macro to check BGP information is alive or not. Sadly, + * not equivalent to just checking previous, because of the + * sense of the additional VALID flag. + */ +#define BGP_PATH_HOLDDOWN(BI) \ + (!CHECK_FLAG((BI)->flags, BGP_PATH_VALID) \ + || CHECK_FLAG((BI)->flags, BGP_PATH_UNUSEABLE)) + +#define DISTRIBUTE_IN_NAME(F) ((F)->dlist[FILTER_IN].name) +#define DISTRIBUTE_IN(F) ((F)->dlist[FILTER_IN].alist) +#define DISTRIBUTE_OUT_NAME(F) ((F)->dlist[FILTER_OUT].name) +#define DISTRIBUTE_OUT(F) ((F)->dlist[FILTER_OUT].alist) + +#define PREFIX_LIST_IN_NAME(F) ((F)->plist[FILTER_IN].name) +#define PREFIX_LIST_IN(F) ((F)->plist[FILTER_IN].plist) +#define PREFIX_LIST_OUT_NAME(F) ((F)->plist[FILTER_OUT].name) +#define PREFIX_LIST_OUT(F) ((F)->plist[FILTER_OUT].plist) + +#define FILTER_LIST_IN_NAME(F) ((F)->aslist[FILTER_IN].name) +#define FILTER_LIST_IN(F) ((F)->aslist[FILTER_IN].aslist) +#define FILTER_LIST_OUT_NAME(F) ((F)->aslist[FILTER_OUT].name) +#define FILTER_LIST_OUT(F) ((F)->aslist[FILTER_OUT].aslist) + +#define ROUTE_MAP_IN_NAME(F) ((F)->map[RMAP_IN].name) +#define ROUTE_MAP_IN(F) ((F)->map[RMAP_IN].map) +#define ROUTE_MAP_OUT_NAME(F) ((F)->map[RMAP_OUT].name) +#define ROUTE_MAP_OUT(F) ((F)->map[RMAP_OUT].map) + +#define UNSUPPRESS_MAP_NAME(F) ((F)->usmap.name) +#define UNSUPPRESS_MAP(F) ((F)->usmap.map) + +#define ADVERTISE_MAP_NAME(F) ((F)->advmap.aname) +#define ADVERTISE_MAP(F) ((F)->advmap.amap) + +#define ADVERTISE_CONDITION(F) ((F)->advmap.condition) + +#define CONDITION_MAP_NAME(F) ((F)->advmap.cname) +#define CONDITION_MAP(F) ((F)->advmap.cmap) + +/* path PREFIX (addpath rxid NUMBER) */ +#define PATH_ADDPATH_STR_BUFFER PREFIX2STR_BUFFER + 32 + +#define BGP_PATH_INFO_NUM_LABELS(pi) \ + ((pi) && (pi)->extra && (pi)->extra->labels \ + ? (pi)->extra->labels->num_labels \ + : 0) + +enum bgp_path_type { + BGP_PATH_SHOW_ALL, + BGP_PATH_SHOW_BESTPATH, + BGP_PATH_SHOW_MULTIPATH +}; + +static inline void bgp_bump_version(struct bgp_dest *dest) +{ + dest->version = bgp_table_next_version(bgp_dest_table(dest)); +} + +static inline int bgp_fibupd_safi(safi_t safi) +{ + if (safi == SAFI_UNICAST || safi == SAFI_MULTICAST + || safi == SAFI_LABELED_UNICAST + || safi == SAFI_FLOWSPEC) + return 1; + return 0; +} + +/* Flag if the route path's family matches params. */ +static inline bool is_pi_family_matching(struct bgp_path_info *pi, + afi_t afi, safi_t safi) +{ + struct bgp_table *table; + struct bgp_dest *dest; + + dest = pi->net; + if (!dest) + return false; + table = bgp_dest_table(dest); + if (table && + table->afi == afi && + table->safi == safi) + return true; + return false; +} + +static inline void prep_for_rmap_apply(struct bgp_path_info *dst_pi, + struct bgp_path_info_extra *dst_pie, + struct bgp_dest *dest, + struct bgp_path_info *src_pi, + struct peer *peer, struct attr *attr) +{ + memset(dst_pi, 0, sizeof(struct bgp_path_info)); + dst_pi->peer = peer; + dst_pi->attr = attr; + dst_pi->net = dest; + dst_pi->flags = src_pi->flags; + dst_pi->type = src_pi->type; + dst_pi->sub_type = src_pi->sub_type; + dst_pi->mpath = src_pi->mpath; + if (src_pi->extra) { + memcpy(dst_pie, src_pi->extra, + sizeof(struct bgp_path_info_extra)); + dst_pi->extra = dst_pie; + } +} + +static inline bool bgp_check_advertise(struct bgp *bgp, struct bgp_dest *dest, + safi_t safi) +{ + if (!bgp_fibupd_safi(safi)) + return true; + + return (!(BGP_SUPPRESS_FIB_ENABLED(bgp) && + CHECK_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING) && + (!bgp_option_check(BGP_OPT_NO_FIB)))); +} + +/* + * If we have a fib result and it failed to install( or was withdrawn due + * to better admin distance we need to send down the wire a withdrawal. + * This function assumes that bgp_check_advertise was already returned + * as good to go. + */ +static inline bool bgp_check_withdrawal(struct bgp *bgp, struct bgp_dest *dest, + safi_t safi) +{ + struct bgp_path_info *pi, *selected = NULL; + + if (!bgp_fibupd_safi(safi) || !BGP_SUPPRESS_FIB_ENABLED(bgp)) + return false; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) { + selected = pi; + continue; + } + + if (pi->sub_type != BGP_ROUTE_NORMAL) + return true; + } + + /* + * pi is selected and bgp is dealing with a static route + * ( ie a network statement of some sort ). FIB installed + * is irrelevant + * + * I am not sure what the above for loop is wanted in this + * manner at this point. But I do know that if I have + * a static route that is selected and it's the one + * being checked for should I withdrawal we do not + * want to withdraw the route on installation :) + */ + if (selected && selected->sub_type == BGP_ROUTE_STATIC) + return false; + + if (CHECK_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED)) + return false; + + return true; +} + +/* called before bgp_process() */ +DECLARE_HOOK(bgp_process, + (struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, + struct peer *peer, bool withdraw), + (bgp, afi, safi, bn, peer, withdraw)); + +/* called when a route is updated in the rib */ +DECLARE_HOOK(bgp_route_update, + (struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_dest *bn, + struct bgp_path_info *old_route, struct bgp_path_info *new_route), + (bgp, afi, safi, bn, old_route, new_route)); + +/* BGP show options */ +#define BGP_SHOW_OPT_JSON (1 << 0) +#define BGP_SHOW_OPT_WIDE (1 << 1) +#define BGP_SHOW_OPT_AFI_ALL (1 << 2) +#define BGP_SHOW_OPT_AFI_IP (1 << 3) +#define BGP_SHOW_OPT_AFI_IP6 (1 << 4) +#define BGP_SHOW_OPT_ESTABLISHED (1 << 5) +#define BGP_SHOW_OPT_FAILED (1 << 6) +#define BGP_SHOW_OPT_JSON_DETAIL (1 << 7) +#define BGP_SHOW_OPT_TERSE (1 << 8) +#define BGP_SHOW_OPT_ROUTES_DETAIL (1 << 9) + +/* Prototypes. */ +extern void bgp_rib_remove(struct bgp_dest *dest, struct bgp_path_info *pi, + struct peer *peer, afi_t afi, safi_t safi); +extern void bgp_process_queue_init(struct bgp *bgp); +extern void bgp_route_init(void); +extern void bgp_route_finish(void); +extern void bgp_cleanup_routes(struct bgp *); +extern void bgp_free_aggregate_info(struct bgp_aggregate *aggregate); +extern void bgp_announce_route(struct peer *peer, afi_t afi, safi_t safi, + bool force); +extern void bgp_stop_announce_route_timer(struct peer_af *paf); +extern void bgp_announce_route_all(struct peer *); +extern void bgp_default_originate(struct peer *peer, afi_t afi, safi_t safi, + bool withdraw); +extern void bgp_soft_reconfig_table_task_cancel(const struct bgp *bgp, + const struct bgp_table *table, + const struct peer *peer); + +/* + * If this peer is configured for soft reconfig in then do the work + * and return true. If it is not return false; and do nothing + */ +extern bool bgp_soft_reconfig_in(struct peer *peer, afi_t afi, safi_t safi); +extern void bgp_clear_route(struct peer *, afi_t, safi_t); +extern void bgp_clear_route_all(struct peer *); +extern void bgp_clear_adj_in(struct peer *, afi_t, safi_t); +extern void bgp_clear_stale_route(struct peer *, afi_t, safi_t); +extern void bgp_set_stale_route(struct peer *peer, afi_t afi, safi_t safi); +extern bool bgp_outbound_policy_exists(struct peer *, struct bgp_filter *); +extern bool bgp_inbound_policy_exists(struct peer *, struct bgp_filter *); + +extern struct bgp_dest *bgp_afi_node_get(struct bgp_table *table, afi_t afi, + safi_t safi, const struct prefix *p, + struct prefix_rd *prd); +extern struct bgp_path_info *bgp_path_info_lock(struct bgp_path_info *path); +extern struct bgp_path_info *bgp_path_info_unlock(struct bgp_path_info *path); +extern bool bgp_path_info_nexthop_changed(struct bgp_path_info *pi, + struct peer *to, afi_t afi); +extern struct bgp_path_info * +bgp_get_imported_bpi_ultimate(struct bgp_path_info *info); +extern void bgp_path_info_add(struct bgp_dest *dest, struct bgp_path_info *pi); +extern void bgp_path_info_extra_free(struct bgp_path_info_extra **extra); +extern struct bgp_dest *bgp_path_info_reap(struct bgp_dest *dest, + struct bgp_path_info *pi); +extern void bgp_path_info_delete(struct bgp_dest *dest, + struct bgp_path_info *pi); +extern struct bgp_path_info_extra * +bgp_path_info_extra_get(struct bgp_path_info *path); +extern bool bgp_path_info_has_valid_label(const struct bgp_path_info *path); +extern void bgp_path_info_set_flag(struct bgp_dest *dest, + struct bgp_path_info *path, uint32_t flag); +extern void bgp_path_info_unset_flag(struct bgp_dest *dest, + struct bgp_path_info *path, uint32_t flag); +extern void bgp_path_info_path_with_addpath_rx_str(struct bgp_path_info *pi, + char *buf, size_t buf_len); +extern bool bgp_path_info_labels_same(const struct bgp_path_info *bpi, + const mpls_label_t *label, uint32_t n); + +extern int bgp_nlri_parse_ip(struct peer *, struct attr *, struct bgp_nlri *); + +extern bool bgp_maximum_prefix_overflow(struct peer *, afi_t, safi_t, int); + +extern void bgp_redistribute_add(struct bgp *bgp, struct prefix *p, + const union g_addr *nexthop, ifindex_t ifindex, + enum nexthop_types_t nhtype, uint8_t distance, + enum blackhole_type bhtype, uint32_t metric, + uint8_t type, unsigned short instance, + route_tag_t tag); +extern void bgp_redistribute_delete(struct bgp *, struct prefix *, uint8_t, + unsigned short); +extern void bgp_redistribute_withdraw(struct bgp *, afi_t, int, unsigned short); + +extern void bgp_static_add(struct bgp *); +extern void bgp_static_delete(struct bgp *); +extern void bgp_static_redo_import_check(struct bgp *); +extern void bgp_purge_static_redist_routes(struct bgp *bgp); +extern void bgp_static_update(struct bgp *bgp, const struct prefix *p, + struct bgp_static *s, afi_t afi, safi_t safi); +extern void bgp_static_withdraw(struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, struct prefix_rd *prd); + +extern int bgp_static_set(struct vty *vty, bool negate, const char *ip_str, + const char *rd_str, const char *label_str, afi_t afi, + safi_t safi, const char *rmap, int backdoor, + uint32_t label_index, int evpn_type, const char *esi, + const char *gwip, const char *ethtag, + const char *routermac); + +/* this is primarily for MPLS-VPN */ +extern void bgp_update(struct peer *peer, const struct prefix *p, + uint32_t addpath_id, struct attr *attr, afi_t afi, + safi_t safi, int type, int sub_type, + struct prefix_rd *prd, mpls_label_t *label, + uint8_t num_labels, int soft_reconfig, + struct bgp_route_evpn *evpn); +extern void bgp_withdraw(struct peer *peer, const struct prefix *p, + uint32_t addpath_id, afi_t afi, safi_t safi, int type, + int sub_type, struct prefix_rd *prd, + mpls_label_t *label, uint8_t num_labels, + struct bgp_route_evpn *evpn); + +/* for bgp_nexthop and bgp_damp */ +extern void bgp_process(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_path_info *pi, afi_t afi, safi_t safi); + +/* + * Add an end-of-initial-update marker to the process queue. This is just a + * queue element with NULL bgp node. + */ +extern void bgp_add_eoiu_mark(struct bgp *); +extern void bgp_config_write_table_map(struct vty *, struct bgp *, afi_t, + safi_t); +extern void bgp_config_write_network(struct vty *, struct bgp *, afi_t, safi_t); +extern void bgp_config_write_distance(struct vty *, struct bgp *, afi_t, + safi_t); + +extern void bgp_aggregate_delete(struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, + struct bgp_aggregate *aggregate); +extern bool bgp_aggregate_route(struct bgp *bgp, const struct prefix *p, + afi_t afi, safi_t safi, + struct bgp_aggregate *aggregate); +extern void bgp_aggregate_increment(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *path, afi_t afi, + safi_t safi); +extern void bgp_aggregate_decrement(struct bgp *bgp, const struct prefix *p, + struct bgp_path_info *path, afi_t afi, + safi_t safi); + +extern uint8_t bgp_distance_apply(const struct prefix *p, + struct bgp_path_info *path, afi_t afi, + safi_t safi, struct bgp *bgp); + +extern afi_t bgp_node_afi(struct vty *); +extern safi_t bgp_node_safi(struct vty *); + +extern struct bgp_path_info *info_make(int type, int sub_type, + unsigned short instance, + struct peer *peer, struct attr *attr, + struct bgp_dest *dest); + +extern void route_vty_out(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, safi_t safi, + json_object *json_paths, bool wide); +extern void route_vty_out_tag(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + safi_t safi, json_object *json); +extern void route_vty_out_tmp(struct vty *vty, struct bgp *bgp, + struct bgp_dest *dest, const struct prefix *p, + struct attr *attr, safi_t safi, bool use_json, + json_object *json_ar, bool wide); +extern void route_vty_out_overlay(struct vty *vty, const struct prefix *p, + struct bgp_path_info *path, int display, + json_object *json); + +extern void bgp_notify_conditional_adv_scanner(struct update_subgroup *subgrp); + +extern void subgroup_process_announce_selected(struct update_subgroup *subgrp, + struct bgp_path_info *selected, + struct bgp_dest *dest, afi_t afi, + safi_t safi, + uint32_t addpath_tx_id); + +extern bool subgroup_announce_check(struct bgp_dest *dest, + struct bgp_path_info *pi, + struct update_subgroup *subgrp, + const struct prefix *p, struct attr *attr, + struct attr *post_attr); + +extern void bgp_peer_clear_node_queue_drain_immediate(struct peer *peer); +extern void bgp_process_queues_drain_immediate(void); + +/* for encap/vpn */ +extern struct bgp_dest *bgp_safi_node_lookup(struct bgp_table *table, + safi_t safi, + const struct prefix *p, + struct prefix_rd *prd); +extern void bgp_path_info_restore(struct bgp_dest *dest, + struct bgp_path_info *path); + +extern int bgp_path_info_cmp_compatible(struct bgp *bgp, + struct bgp_path_info *new, + struct bgp_path_info *exist, + char *pfx_buf, afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason); +extern void bgp_attr_add_llgr_community(struct attr *attr); +extern void bgp_attr_add_gshut_community(struct attr *attr); + +extern void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, + struct bgp_maxpaths_cfg *mpath_cfg, + struct bgp_path_info_pair *result, afi_t afi, + safi_t safi); +extern void bgp_zebra_clear_route_change_flags(struct bgp_dest *dest); +extern bool bgp_zebra_has_route_changed(struct bgp_path_info *selected); + +extern void route_vty_out_detail_header(struct vty *vty, struct bgp *bgp, + struct bgp_dest *dest, + const struct prefix *p, + const struct prefix_rd *prd, afi_t afi, + safi_t safi, json_object *json, + bool incremental_print); +extern void route_vty_out_detail(struct vty *vty, struct bgp *bgp, + struct bgp_dest *bn, const struct prefix *p, + struct bgp_path_info *path, afi_t afi, + safi_t safi, enum rpki_states, + json_object *json_paths); +extern int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_table *table, struct prefix_rd *prd, + enum bgp_show_type type, void *output_arg, + uint16_t show_flags); +extern void bgp_best_path_select_defer(struct bgp *bgp, afi_t afi, safi_t safi); +extern bool bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, + uint8_t type, uint8_t stype, + struct attr *attr, struct bgp_dest *dest); +extern int bgp_evpn_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + bool debug); +extern void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, + struct bgp *bgp, + const struct prefix *p, afi_t afi, + safi_t safi, bool suppress); +extern void subgroup_announce_reset_nhop(uint8_t family, struct attr *attr); +const char * +bgp_path_selection_reason2str(enum bgp_path_selection_reason reason); +extern bool bgp_path_suppressed(struct bgp_path_info *pi); +extern bool bgp_addpath_encode_rx(struct peer *peer, afi_t afi, safi_t safi); +extern const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, + safi_t safi); +extern void bgp_path_info_free_with_caller(const char *caller, + struct bgp_path_info *path); +extern void bgp_path_info_add_with_caller(const char *caller, + struct bgp_dest *dest, + struct bgp_path_info *pi); +extern void bgp_aggregate_free(struct bgp_aggregate *aggregate); +extern int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, + struct bgp_path_info *exist, int *paths_eq, + struct bgp_maxpaths_cfg *mpath_cfg, bool debug, + char *pfx_buf, afi_t afi, safi_t safi, + enum bgp_path_selection_reason *reason); +#define bgp_path_info_add(A, B) \ + bgp_path_info_add_with_caller(__func__, (A), (B)) +#define bgp_path_info_free(B) bgp_path_info_free_with_caller(__func__, (B)) +#endif /* _QUAGGA_BGP_ROUTE_H */ diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c new file mode 100644 index 0000000..4895ddb --- /dev/null +++ b/bgpd/bgp_routemap.c @@ -0,0 +1,8072 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Route map function of bgpd. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "filter.h" +#include "routemap.h" +#include "command.h" +#include "linklist.h" +#include "plist.h" +#include "memory.h" +#include "log.h" +#include "frrlua.h" +#include "frrscript.h" +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ +#include "buffer.h" +#include "sockunion.h" +#include "hash.h" +#include "queue.h" +#include "frrstr.h" +#include "network.h" +#include "lib/northbound_cli.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_flowspec_util.h" +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_script.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#endif + +#include "bgpd/bgp_routemap_clippy.c" + +/* Memo of route-map commands. + +o Cisco route-map + + match as-path : Done + community : Done + interface : Done + ip address : Done + ip next-hop : Done + ip route-source : Done + ip prefix-list : Done + ipv6 address : Done + ipv6 next-hop : Done + ipv6 route-source: (This will not be implemented by bgpd) + ipv6 prefix-list : Done + length : (This will not be implemented by bgpd) + metric : Done + route-type : (This will not be implemented by bgpd) + tag : Done + local-preference : Done + + set as-path prepend : Done + as-path tag : Not yet + automatic-tag : (This will not be implemented by bgpd) + community : Done + large-community : Done + large-comm-list : Done + comm-list : Not yet + dampning : Not yet + default : (This will not be implemented by bgpd) + interface : (This will not be implemented by bgpd) + ip default : (This will not be implemented by bgpd) + ip next-hop : Done + ip precedence : (This will not be implemented by bgpd) + ip tos : (This will not be implemented by bgpd) + level : (This will not be implemented by bgpd) + local-preference : Done + metric : Done + metric-type : Not yet + origin : Done + tag : Done + weight : Done + table : Done + +o Local extensions + + set ipv6 next-hop global: Done + set ipv6 next-hop prefer-global: Done + set ipv6 next-hop local : Done + set as-path exclude : Done + +*/ + +/* generic value manipulation to be shared in multiple rules */ + +#define RMAP_VALUE_SET 0 +#define RMAP_VALUE_ADD 1 +#define RMAP_VALUE_SUB 2 + +struct rmap_value { + uint8_t action; + uint8_t variable; + uint32_t value; +}; + +static int route_value_match(struct rmap_value *rv, uint32_t value) +{ + if (rv->variable == 0 && value == rv->value) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static uint32_t route_value_adjust(struct rmap_value *rv, uint32_t current, + struct peer *peer) +{ + uint32_t value; + + switch (rv->variable) { + case 1: + value = peer->rtt; + break; + default: + value = rv->value; + break; + } + + switch (rv->action) { + case RMAP_VALUE_ADD: + if (current > UINT32_MAX - value) + return UINT32_MAX; + return current + value; + case RMAP_VALUE_SUB: + if (current <= value) + return 0; + return current - value; + default: + return value; + } +} + +static void *route_value_compile(const char *arg) +{ + uint8_t action = RMAP_VALUE_SET, var = 0; + unsigned long larg = 0; + char *endptr = NULL; + struct rmap_value *rv; + + if (arg[0] == '+') { + action = RMAP_VALUE_ADD; + arg++; + } else if (arg[0] == '-') { + action = RMAP_VALUE_SUB; + arg++; + } + + if (all_digit(arg)) { + errno = 0; + larg = strtoul(arg, &endptr, 10); + if (*arg == 0 || *endptr != 0 || errno || larg > UINT32_MAX) + return NULL; + } else { + if (strcmp(arg, "rtt") == 0) + var = 1; + else + return NULL; + } + + rv = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_value)); + + rv->action = action; + rv->variable = var; + rv->value = larg; + return rv; +} + +static void route_value_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* generic as path object to be shared in multiple rules */ + +static void *route_aspath_compile(const char *arg) +{ + struct aspath *aspath; + + aspath = aspath_str2aspath(arg, bgp_get_asnotation(NULL)); + if (!aspath) + return NULL; + return aspath; +} + +static void route_aspath_free(void *rule) +{ + struct aspath *aspath = rule; + aspath_free(aspath); +} + +struct bgp_match_peer_compiled { + char *interface; + union sockunion su; +}; + +/* 'match peer (A.B.C.D|X:X::X:X|WORD)' */ + +/* Compares the peer specified in the 'match peer' clause with the peer + received in bgp_path_info->peer. If it is the same, or if the peer structure + received is a peer_group containing it, returns RMAP_MATCH. */ +static enum route_map_cmd_result_t +route_match_peer(void *rule, const struct prefix *prefix, void *object) +{ + struct bgp_match_peer_compiled *pc; + union sockunion *su; + union sockunion su_def = { + .sin = {.sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY}}; + struct peer_group *group; + struct peer *peer; + struct listnode *node, *nnode; + + pc = rule; + su = &pc->su; + peer = ((struct bgp_path_info *)object)->peer; + + if (pc->interface) { + if (!peer->conf_if || !peer->group) + return RMAP_NOMATCH; + + if (peer->conf_if && strcmp(peer->conf_if, pc->interface) == 0) + return RMAP_MATCH; + + if (peer->group && + strcmp(peer->group->name, pc->interface) == 0) + return RMAP_MATCH; + + return RMAP_NOMATCH; + } + + /* If su='0.0.0.0' (command 'match peer local'), and it's a + NETWORK, + REDISTRIBUTE, AGGREGATE-ADDRESS or DEFAULT_GENERATED route + => return RMAP_MATCH + */ + if (sockunion_same(su, &su_def)) { + int ret; + if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_NETWORK) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_REDISTRIBUTE) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_AGGREGATE) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_DEFAULT)) + ret = RMAP_MATCH; + else + ret = RMAP_NOMATCH; + return ret; + } + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (sockunion_same(su, &peer->connection->su)) + return RMAP_MATCH; + + return RMAP_NOMATCH; + } else { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (sockunion_same(su, &peer->connection->su)) + return RMAP_MATCH; + } + return RMAP_NOMATCH; + } + + return RMAP_NOMATCH; +} + +static void *route_match_peer_compile(const char *arg) +{ + struct bgp_match_peer_compiled *pc; + int ret; + + pc = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct bgp_match_peer_compiled)); + + ret = str2sockunion(strcmp(arg, "local") ? arg : "0.0.0.0", &pc->su); + if (ret < 0) { + pc->interface = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + return pc; + } + + return pc; +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_peer_free(void *rule) +{ + struct bgp_match_peer_compiled *pc = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, pc->interface); + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_peer_cmd = { + "peer", + route_match_peer, + route_match_peer_compile, + route_match_peer_free +}; + +#ifdef HAVE_SCRIPTING + +enum frrlua_rm_status { + /* + * Script function run failure. This will translate into a deny + */ + LUA_RM_FAILURE = 0, + /* + * No Match was found for the route map function + */ + LUA_RM_NOMATCH, + /* + * Match was found but no changes were made to the incoming data. + */ + LUA_RM_MATCH, + /* + * Match was found and data was modified, so figure out what changed + */ + LUA_RM_MATCH_AND_CHANGE, +}; + +static enum route_map_cmd_result_t +route_match_script(void *rule, const struct prefix *prefix, void *object) +{ + const char *scriptname = rule; + const char *routematch_function = "route_match"; + struct bgp_path_info *path = (struct bgp_path_info *)object; + + struct frrscript *fs = frrscript_new(scriptname); + + if (frrscript_load(fs, routematch_function, NULL)) { + zlog_err( + "Issue loading script or function; defaulting to no match"); + return RMAP_NOMATCH; + } + + struct attr newattr = *path->attr; + + int result = frrscript_call( + fs, routematch_function, ("prefix", prefix), + ("attributes", &newattr), ("peer", path->peer), + ("RM_FAILURE", LUA_RM_FAILURE), ("RM_NOMATCH", LUA_RM_NOMATCH), + ("RM_MATCH", LUA_RM_MATCH), + ("RM_MATCH_AND_CHANGE", LUA_RM_MATCH_AND_CHANGE)); + + if (result) { + zlog_err("Issue running script rule; defaulting to no match"); + return RMAP_NOMATCH; + } + + long long *action = frrscript_get_result(fs, routematch_function, + "action", lua_tointegerp); + + int status = RMAP_NOMATCH; + + switch (*action) { + case LUA_RM_FAILURE: + zlog_err( + "Executing route-map match script '%s' failed; defaulting to no match", + scriptname); + status = RMAP_NOMATCH; + break; + case LUA_RM_NOMATCH: + status = RMAP_NOMATCH; + break; + case LUA_RM_MATCH_AND_CHANGE: + status = RMAP_MATCH; + zlog_debug("Updating attribute based on script's values"); + + uint32_t locpref = 0; + + path->attr->med = newattr.med; + + if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + locpref = path->attr->local_pref; + if (locpref != newattr.local_pref) { + SET_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)); + path->attr->local_pref = newattr.local_pref; + } + break; + case LUA_RM_MATCH: + status = RMAP_MATCH; + break; + } + + XFREE(MTYPE_SCRIPT_RES, action); + + frrscript_delete(fs); + + return status; +} + +static void *route_match_script_compile(const char *arg) +{ + char *scriptname; + + scriptname = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + + return scriptname; +} + +static void route_match_script_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_script_cmd = { + "script", + route_match_script, + route_match_script_compile, + route_match_script_free +}; + +#endif /* HAVE_SCRIPTING */ + +/* `match ip address IP_ACCESS_LIST' */ + +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_ip_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + if (prefix->family == AF_INET) { + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, prefix) == FILTER_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +/* Route map `ip address' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_address_compile(const char *arg) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP, arg); + if (!alist) + zlog_warn( + "Access List specified %s does not exist yet, default will be NO_MATCH until it is created", + arg); + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_ip_address_cmd = { + "ip address", + route_match_ip_address, + route_match_ip_address_compile, + route_match_ip_address_free +}; + +/* `match ip next-hop ' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_ip_next_hop(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + struct bgp_path_info *path; + struct prefix_ipv4 p; + + if (prefix->family == AF_INET) { + path = object; + p.family = AF_INET; + p.prefix = path->attr->nexthop; + p.prefixlen = IPV4_MAX_BITLEN; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, &p) == FILTER_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +/* Route map `ip next-hop' match statement. `arg' is + access-list name. */ +static void *route_match_ip_next_hop_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_next_hop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip next-hop matching. */ +static const struct route_map_rule_cmd route_match_ip_next_hop_cmd = { + "ip next-hop", + route_match_ip_next_hop, + route_match_ip_next_hop_compile, + route_match_ip_next_hop_free +}; + +/* `match ip route-source ACCESS-LIST' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_ip_route_source(void *rule, const struct prefix *pfx, void *object) +{ + struct access_list *alist; + struct bgp_path_info *path; + struct peer *peer; + struct prefix_ipv4 p; + + if (pfx->family == AF_INET) { + path = object; + peer = path->peer; + + if (!peer || sockunion_family(&peer->connection->su) != AF_INET) + return RMAP_NOMATCH; + + p.family = AF_INET; + p.prefix = peer->connection->su.sin.sin_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, &p) == FILTER_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +/* Route map `ip route-source' match statement. `arg' is + access-list name. */ +static void *route_match_ip_route_source_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_route_source_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip route-source matching. */ +static const struct route_map_rule_cmd route_match_ip_route_source_cmd = { + "ip route-source", + route_match_ip_route_source, + route_match_ip_route_source_compile, + route_match_ip_route_source_free +}; + +static enum route_map_cmd_result_t +route_match_prefix_list_flowspec(afi_t afi, struct prefix_list *plist, + const struct prefix *p) +{ + int ret; + struct bgp_pbr_entry_main api; + + memset(&api, 0, sizeof(api)); + + if (family2afi(p->u.prefix_flowspec.family) != afi) + return RMAP_NOMATCH; + + /* extract match from flowspec entries */ + ret = bgp_flowspec_match_rules_fill( + (uint8_t *)p->u.prefix_flowspec.ptr, + p->u.prefix_flowspec.prefixlen, &api, + afi); + if (ret < 0) + return RMAP_NOMATCH; + if (api.match_bitmask & PREFIX_DST_PRESENT || + api.match_bitmask_iprule & PREFIX_DST_PRESENT) { + if (family2afi((&api.dst_prefix)->family) != afi) + return RMAP_NOMATCH; + return prefix_list_apply(plist, &api.dst_prefix) == PREFIX_DENY + ? RMAP_NOMATCH + : RMAP_MATCH; + } else if (api.match_bitmask & PREFIX_SRC_PRESENT || + api.match_bitmask_iprule & PREFIX_SRC_PRESENT) { + if (family2afi((&api.src_prefix)->family) != afi) + return RMAP_NOMATCH; + return (prefix_list_apply(plist, &api.src_prefix) == PREFIX_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +static enum route_map_cmd_result_t +route_match_prefix_list_evpn(afi_t afi, struct prefix_list *plist, + const struct prefix *p) +{ + /* Convert to match a general plist */ + struct prefix new; + + if (evpn_prefix2prefix(p, &new)) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, &new) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static enum route_map_cmd_result_t +route_match_address_prefix_list(void *rule, afi_t afi, + const struct prefix *prefix, void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(afi, (char *)rule); + if (plist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + if (prefix->family == AF_FLOWSPEC) + return route_match_prefix_list_flowspec(afi, plist, + prefix); + + else if (prefix->family == AF_EVPN) + return route_match_prefix_list_evpn(afi, plist, prefix); + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static enum route_map_cmd_result_t +route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + return route_match_address_prefix_list(rule, AFI_IP, prefix, object); +} + +static void *route_match_ip_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_address_prefix_list_cmd = { + "ip address prefix-list", + route_match_ip_address_prefix_list, + route_match_ip_address_prefix_list_compile, + route_match_ip_address_prefix_list_free +}; + +/* `match ip next-hop prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + struct bgp_path_info *path; + struct prefix_ipv4 p; + + if (prefix->family == AF_INET) { + path = object; + p.family = AF_INET; + p.prefix = path->attr->nexthop; + p.prefixlen = IPV4_MAX_BITLEN; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (prefix_list_apply(plist, &p) == PREFIX_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_prefix_list_cmd = { + "ip next-hop prefix-list", + route_match_ip_next_hop_prefix_list, + route_match_ip_next_hop_prefix_list_compile, + route_match_ip_next_hop_prefix_list_free +}; + +/* `match ipv6 next-hop prefix-list PREFIXLIST_NAME' */ +static enum route_map_cmd_result_t +route_match_ipv6_next_hop_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + struct bgp_path_info *path; + struct prefix_ipv6 p; + + if (prefix->family == AF_INET6) { + path = object; + p.family = AF_INET6; + p.prefix = path->attr->mp_nexthop_global; + p.prefixlen = IPV6_MAX_BITLEN; + + plist = prefix_list_lookup(AFI_IP6, (char *)rule); + if (!plist) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + if (prefix_list_apply(plist, &p) == PREFIX_PERMIT) + return RMAP_MATCH; + + if (path->attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + p.prefix = path->attr->mp_nexthop_local; + if (prefix_list_apply(plist, &p) == PREFIX_PERMIT) + return RMAP_MATCH; + } + } + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_next_hop_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_next_hop_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ipv6_next_hop_prefix_list_cmd = { + "ipv6 next-hop prefix-list", + route_match_ipv6_next_hop_prefix_list, + route_match_ipv6_next_hop_prefix_list_compile, + route_match_ipv6_next_hop_prefix_list_free +}; + +/* `match ip next-hop type ' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path; + + if (prefix->family == AF_INET) { + path = (struct bgp_path_info *)object; + if (!path) + return RMAP_NOMATCH; + + /* If nexthop interface's index can't be resolved and nexthop is + set to any address then mark it as type `blackhole`. + This logic works for matching kernel/static routes like: + `ip route add blackhole 10.0.0.1`. */ + if (path->attr->nexthop.s_addr == INADDR_ANY + && !path->attr->nh_ifindex) + return RMAP_MATCH; + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_next_hop_type_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_type_cmd = { + "ip next-hop type", + route_match_ip_next_hop_type, + route_match_ip_next_hop_type_compile, + route_match_ip_next_hop_type_free +}; + +/* `match source-protocol` */ +static enum route_map_cmd_result_t +route_match_source_protocol(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path = object; + int *protocol = rule; + + if (!path) + return RMAP_NOMATCH; + + if (path->type == *protocol) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_source_protocol_compile(const char *arg) +{ + int *protocol; + + protocol = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*protocol)); + *protocol = proto_name2num(arg); + + return protocol; +} + +static void route_match_source_protocol_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_source_protocol_cmd = { + "source-protocol", + route_match_source_protocol, + route_match_source_protocol_compile, + route_match_source_protocol_free +}; + + +/* `match ip route-source prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_route_source_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + struct bgp_path_info *path; + struct peer *peer; + struct prefix_ipv4 p; + + if (prefix->family == AF_INET) { + path = object; + peer = path->peer; + + if (!peer || sockunion_family(&peer->connection->su) != AF_INET) + return RMAP_NOMATCH; + + p.family = AF_INET; + p.prefix = peer->connection->su.sin.sin_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (prefix_list_apply(plist, &p) == PREFIX_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_route_source_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_route_source_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_route_source_prefix_list_cmd = { + "ip route-source prefix-list", + route_match_ip_route_source_prefix_list, + route_match_ip_route_source_prefix_list_compile, + route_match_ip_route_source_prefix_list_free +}; + +/* `match evpn default-route' */ + +/* Match function should return 1 if match is success else 0 */ +static enum route_map_cmd_result_t +route_match_evpn_default_route(void *rule, const struct prefix *p, void *object) +{ + if (is_evpn_prefix_default(p)) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Route map commands for default-route matching. */ +static const struct route_map_rule_cmd + route_match_evpn_default_route_cmd = { + "evpn default-route", + route_match_evpn_default_route, + NULL, + NULL +}; + +/* `match mac address MAC_ACCESS_LIST' */ + +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_mac_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + struct prefix p; + + alist = access_list_lookup(AFI_L2VPN, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + if (prefix->u.prefix_evpn.route_type != BGP_EVPN_MAC_IP_ROUTE) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix %pFX is not a EVPN MAC IP ROUTE defaulting to NO_MATCH", + __func__, prefix); + return RMAP_NOMATCH; + } + + p.family = AF_ETHERNET; + p.prefixlen = ETH_ALEN * 8; + p.u.prefix_eth = prefix->u.prefix_evpn.macip_addr.mac; + + return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Route map `mac address' match statement. `arg' should be + access-list name. */ +static void *route_match_mac_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_mac_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for mac address matching. */ +static const struct route_map_rule_cmd route_match_mac_address_cmd = { + "mac address", + route_match_mac_address, + route_match_mac_address_compile, + route_match_mac_address_free +}; + +/* + * Match function returns: + * ...RMAP_MATCH if match is found. + * ...RMAP_NOMATCH if match is not found. + * ...RMAP_NOOP to ignore this match check. + */ +static enum route_map_cmd_result_t +route_match_vni(void *rule, const struct prefix *prefix, void *object) +{ + vni_t vni = 0; + unsigned int label_cnt; + struct bgp_path_info *path = NULL; + struct prefix_evpn *evp = (struct prefix_evpn *) prefix; + + vni = *((vni_t *)rule); + path = (struct bgp_path_info *)object; + + /* + * This rmap filter is valid for vxlan tunnel type only. + * For any other tunnel type, return noop to ignore + * this check. + */ + if (path->attr->encap_tunneltype != BGP_ENCAP_TYPE_VXLAN) + return RMAP_NOOP; + + /* + * Apply filter to type 1, 2, 5 routes only. + * Other route types do not have vni label. + */ + if (evp + && (evp->prefix.route_type != BGP_EVPN_AD_ROUTE + && evp->prefix.route_type != BGP_EVPN_MAC_IP_ROUTE + && evp->prefix.route_type != BGP_EVPN_IP_PREFIX_ROUTE)) + return RMAP_NOOP; + + for (label_cnt = 0; label_cnt < BGP_MAX_LABELS && + label_cnt < BGP_PATH_INFO_NUM_LABELS(path); + label_cnt++) { + if (vni == label2vni(&path->extra->labels->label[label_cnt])) + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +/* Route map `vni' match statement. */ +static void *route_match_vni_compile(const char *arg) +{ + vni_t *vni = NULL; + char *end = NULL; + + vni = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(vni_t)); + + *vni = strtoul(arg, &end, 10); + if (*end != '\0') { + XFREE(MTYPE_ROUTE_MAP_COMPILED, vni); + return NULL; + } + + return vni; +} + +/* Free route map's compiled `vni' value. */ +static void route_match_vni_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for vni matching. */ +static const struct route_map_rule_cmd route_match_evpn_vni_cmd = { + "evpn vni", + route_match_vni, + route_match_vni_compile, + route_match_vni_free +}; + +/* `match evpn route-type' */ + +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_evpn_route_type(void *rule, const struct prefix *pfx, void *object) +{ + uint8_t route_type = 0; + + route_type = *((uint8_t *)rule); + + if (route_type == pfx->u.prefix_evpn.route_type) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Route map `route-type' match statement. */ +static void *route_match_evpn_route_type_compile(const char *arg) +{ + uint8_t *route_type = NULL; + + route_type = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); + + if (strncmp(arg, "ea", 2) == 0) + *route_type = BGP_EVPN_AD_ROUTE; + else if (strncmp(arg, "ma", 2) == 0) + *route_type = BGP_EVPN_MAC_IP_ROUTE; + else if (strncmp(arg, "mu", 2) == 0) + *route_type = BGP_EVPN_IMET_ROUTE; + else if (strncmp(arg, "es", 2) == 0) + *route_type = BGP_EVPN_ES_ROUTE; + else + *route_type = BGP_EVPN_IP_PREFIX_ROUTE; + + return route_type; +} + +/* Free route map's compiled `route-type' value. */ +static void route_match_evpn_route_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for evpn route-type matching. */ +static const struct route_map_rule_cmd route_match_evpn_route_type_cmd = { + "evpn route-type", + route_match_evpn_route_type, + route_match_evpn_route_type_compile, + route_match_evpn_route_type_free +}; + +/* `match rd' */ + +/* Match function should return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_rd(void *rule, const struct prefix *prefix, void *object) +{ + struct prefix_rd *prd_rule = NULL; + const struct prefix_rd *prd_route = NULL; + struct bgp_path_info *path = NULL; + + if (prefix->family != AF_EVPN) + return RMAP_NOMATCH; + + prd_rule = (struct prefix_rd *)rule; + path = (struct bgp_path_info *)object; + + if (path->net == NULL || path->net->pdest == NULL) + return RMAP_NOMATCH; + + prd_route = (struct prefix_rd *)bgp_dest_get_prefix(path->net->pdest); + if (memcmp(prd_route->val, prd_rule->val, ECOMMUNITY_SIZE) == 0) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Route map `rd' match statement. */ +static void *route_match_rd_compile(const char *arg) +{ + struct prefix_rd *prd; + int ret; + + prd = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct prefix_rd)); + + ret = str2prefix_rd(arg, prd); + if (!ret) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, prd); + return NULL; + } + + return prd; +} + +/* Free route map's compiled `rd' value. */ +static void route_match_rd_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for rd matching. */ +static const struct route_map_rule_cmd route_match_evpn_rd_cmd = { + "evpn rd", + route_match_rd, + route_match_rd_compile, + route_match_rd_free +}; + +static enum route_map_cmd_result_t +route_set_evpn_gateway_ip(void *rule, const struct prefix *prefix, void *object) +{ + struct ipaddr *gw_ip = rule; + struct bgp_path_info *path; + struct prefix_evpn *evp; + + if (prefix->family != AF_EVPN) + return RMAP_OKAY; + + evp = (struct prefix_evpn *)prefix; + if (evp->prefix.route_type != BGP_EVPN_IP_PREFIX_ROUTE) + return RMAP_OKAY; + + if ((is_evpn_prefix_ipaddr_v4(evp) && IPADDRSZ(gw_ip) != 4) + || (is_evpn_prefix_ipaddr_v6(evp) && IPADDRSZ(gw_ip) != 16)) + return RMAP_OKAY; + + path = object; + + /* Set gateway-ip value. */ + path->attr->evpn_overlay.type = OVERLAY_INDEX_GATEWAY_IP; + memcpy(&path->attr->evpn_overlay.gw_ip, &gw_ip->ip.addr, + IPADDRSZ(gw_ip)); + + return RMAP_OKAY; +} + +/* + * Route map `evpn gateway-ip' compile function. + * Given string is converted to struct ipaddr structure + */ +static void *route_set_evpn_gateway_ip_compile(const char *arg) +{ + struct ipaddr *gw_ip = NULL; + int ret; + + gw_ip = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct ipaddr)); + + ret = str2ipaddr(arg, gw_ip); + if (ret < 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, gw_ip); + return NULL; + } + return gw_ip; +} + +/* Free route map's compiled `evpn gateway_ip' value. */ +static void route_set_evpn_gateway_ip_free(void *rule) +{ + struct ipaddr *gw_ip = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, gw_ip); +} + +/* Route map commands for set evpn gateway-ip ipv4. */ +struct route_map_rule_cmd route_set_evpn_gateway_ip_ipv4_cmd = { + "evpn gateway-ip ipv4", route_set_evpn_gateway_ip, + route_set_evpn_gateway_ip_compile, route_set_evpn_gateway_ip_free}; + +/* Route map commands for set evpn gateway-ip ipv6. */ +struct route_map_rule_cmd route_set_evpn_gateway_ip_ipv6_cmd = { + "evpn gateway-ip ipv6", route_set_evpn_gateway_ip, + route_set_evpn_gateway_ip_compile, route_set_evpn_gateway_ip_free}; + +/* Route map commands for VRF route leak with source vrf matching */ +static enum route_map_cmd_result_t +route_match_vrl_source_vrf(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path; + char *vrf_name; + + vrf_name = rule; + path = (struct bgp_path_info *)object; + + if (strncmp(vrf_name, "n/a", VRF_NAMSIZ) == 0) + return RMAP_NOMATCH; + + if (path->extra == NULL || path->extra->vrfleak == NULL || + path->extra->vrfleak->bgp_orig == NULL) + return RMAP_NOMATCH; + + if (strncmp(vrf_name, + vrf_id_to_name(path->extra->vrfleak->bgp_orig->vrf_id), + VRF_NAMSIZ) == 0) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_vrl_source_vrf_compile(const char *arg) +{ + uint8_t *vrf_name = NULL; + + vrf_name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + + return vrf_name; +} + +/* Free route map's compiled `route-type' value. */ +static void route_match_vrl_source_vrf_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_vrl_source_vrf_cmd = { + "source-vrf", + route_match_vrl_source_vrf, + route_match_vrl_source_vrf_compile, + route_match_vrl_source_vrf_free +}; + +/* `match alias` */ +static enum route_map_cmd_result_t +route_match_alias(void *rule, const struct prefix *prefix, void *object) +{ + char *alias = rule; + struct bgp_path_info *path = object; + char **communities; + int num; + bool found; + + if (bgp_attr_get_community(path->attr)) { + found = false; + frrstr_split(bgp_attr_get_community(path->attr)->str, " ", + &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias(communities[i]); + if (!found && strcmp(alias, com2alias) == 0) + found = true; + XFREE(MTYPE_TMP, communities[i]); + } + XFREE(MTYPE_TMP, communities); + if (found) + return RMAP_MATCH; + } + + if (bgp_attr_get_lcommunity(path->attr)) { + found = false; + frrstr_split(bgp_attr_get_lcommunity(path->attr)->str, " ", + &communities, &num); + for (int i = 0; i < num; i++) { + const char *com2alias = + bgp_community2alias(communities[i]); + if (!found && strcmp(alias, com2alias) == 0) + found = true; + XFREE(MTYPE_TMP, communities[i]); + } + XFREE(MTYPE_TMP, communities); + if (found) + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +static void *route_match_alias_compile(const char *arg) +{ + + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_alias_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_alias_cmd = { + "alias", route_match_alias, route_match_alias_compile, + route_match_alias_free}; + +/* `match local-preference LOCAL-PREF' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_local_pref(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *local_pref; + struct bgp_path_info *path; + + local_pref = rule; + path = object; + + if (path->attr->local_pref == *local_pref) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + +/* + * Route map `match local-preference' match statement. + * `arg' is local-pref value + */ +static void *route_match_local_pref_compile(const char *arg) +{ + uint32_t *local_pref; + char *endptr = NULL; + unsigned long tmpval; + + errno = 0; + tmpval = strtoul(arg, &endptr, 10); + if (*endptr != '\0' || errno || tmpval > UINT32_MAX) + return NULL; + + local_pref = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + + *local_pref = tmpval; + return local_pref; +} + +/* Free route map's compiled `match local-preference' value. */ +static void route_match_local_pref_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_local_pref_cmd = { + "local-preference", + route_match_local_pref, + route_match_local_pref_compile, + route_match_local_pref_free +}; + +/* `match metric METRIC' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_metric(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + + rv = rule; + path = object; + return route_value_match(rv, path->attr->med); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_metric_cmd = { + "metric", + route_match_metric, + route_value_compile, + route_value_free, +}; + +/* `match as-path ASPATH' */ + +/* Match function for as-path match. I assume given object is */ +static enum route_map_cmd_result_t +route_match_aspath(void *rule, const struct prefix *prefix, void *object) +{ + + struct as_list *as_list; + struct bgp_path_info *path; + + as_list = as_list_lookup((char *)rule); + if (as_list == NULL) + return RMAP_NOMATCH; + + path = object; + + /* Perform match. */ + return ((as_list_apply(as_list, path->attr->aspath) == AS_FILTER_DENY) + ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Compile function for as-path match. */ +static void *route_match_aspath_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Compile function for as-path match. */ +static void route_match_aspath_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for aspath matching. */ +static const struct route_map_rule_cmd route_match_aspath_cmd = { + "as-path", + route_match_aspath, + route_match_aspath_compile, + route_match_aspath_free +}; + +/* `match community COMMUNIY' */ +struct rmap_community { + char *name; + uint32_t name_hash; + bool exact; + bool any; +}; + +/* Match function for community match. */ +static enum route_map_cmd_result_t +route_match_community(void *rule, const struct prefix *prefix, void *object) +{ + struct community_list *list; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + path = object; + rcom = rule; + + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + COMMUNITY_LIST_MASTER); + if (!list) + return RMAP_NOMATCH; + + if (rcom->exact) { + if (community_list_exact_match( + bgp_attr_get_community(path->attr), list)) + return RMAP_MATCH; + } else if (rcom->any) { + if (!bgp_attr_get_community(path->attr)) + return RMAP_OKAY; + if (community_list_any_match(bgp_attr_get_community(path->attr), + list)) + return RMAP_MATCH; + } else { + if (community_list_match(bgp_attr_get_community(path->attr), + list)) + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +/* Compile function for community match. */ +static void *route_match_community_compile(const char *arg) +{ + struct rmap_community *rcom; + int len; + char *p; + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + + p = strchr(arg, ' '); + if (p) { + len = p - arg; + rcom->name = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, len + 1); + memcpy(rcom->name, arg, len); + p++; + if (*p == 'e') + rcom->exact = true; + else + rcom->any = true; + } else { + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + rcom->exact = false; + rcom->any = false; + } + + rcom->name_hash = bgp_clist_hash_key(rcom->name); + return rcom; +} + +/* Compile function for community match. */ +static void route_match_community_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +/* + * In routemap processing there is a need to add the + * name as a rule_key in the dependency table. Routemap + * lib is unaware of rule_key when exact-match clause + * is in use. routemap lib uses the compiled output to + * get the rule_key value. + */ +static void *route_match_get_community_key(void *rule) +{ + struct rmap_community *rcom; + + rcom = rule; + return rcom->name; +} + + +/* Route map commands for community matching. */ +static const struct route_map_rule_cmd route_match_community_cmd = { + "community", + route_match_community, + route_match_community_compile, + route_match_community_free, + route_match_get_community_key +}; + +/* Match function for lcommunity match. */ +static enum route_map_cmd_result_t +route_match_lcommunity(void *rule, const struct prefix *prefix, void *object) +{ + struct community_list *list; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + path = object; + + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + LARGE_COMMUNITY_LIST_MASTER); + if (!list) + return RMAP_NOMATCH; + + if (rcom->exact) { + if (lcommunity_list_exact_match( + bgp_attr_get_lcommunity(path->attr), list)) + return RMAP_MATCH; + } else if (rcom->any) { + if (!bgp_attr_get_lcommunity(path->attr)) + return RMAP_OKAY; + if (lcommunity_list_any_match(bgp_attr_get_lcommunity(path->attr), + list)) + return RMAP_MATCH; + } else { + if (lcommunity_list_match(bgp_attr_get_lcommunity(path->attr), + list)) + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +/* Compile function for community match. */ +static void *route_match_lcommunity_compile(const char *arg) +{ + struct rmap_community *rcom; + int len; + char *p; + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + + p = strchr(arg, ' '); + if (p) { + len = p - arg; + rcom->name = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, len + 1); + memcpy(rcom->name, arg, len); + p++; + if (*p == 'e') + rcom->exact = true; + else + rcom->any = true; + } else { + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + rcom->exact = false; + rcom->any = false; + } + + rcom->name_hash = bgp_clist_hash_key(rcom->name); + return rcom; +} + +/* Compile function for community match. */ +static void route_match_lcommunity_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +/* Route map commands for community matching. */ +static const struct route_map_rule_cmd route_match_lcommunity_cmd = { + "large-community", + route_match_lcommunity, + route_match_lcommunity_compile, + route_match_lcommunity_free, + route_match_get_community_key +}; + + +/* Match function for extcommunity match. */ +static enum route_map_cmd_result_t +route_match_ecommunity(void *rule, const struct prefix *prefix, void *object) +{ + struct community_list *list; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + path = object; + + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + EXTCOMMUNITY_LIST_MASTER); + if (!list) + return RMAP_NOMATCH; + + if (ecommunity_list_match(bgp_attr_get_ecommunity(path->attr), list)) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Compile function for extcommunity match. */ +static void *route_match_ecommunity_compile(const char *arg) +{ + struct rmap_community *rcom; + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); + rcom->name_hash = bgp_clist_hash_key(rcom->name); + + return rcom; +} + +/* Compile function for extcommunity match. */ +static void route_match_ecommunity_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +/* Route map commands for community matching. */ +static const struct route_map_rule_cmd route_match_ecommunity_cmd = { + "extcommunity", + route_match_ecommunity, + route_match_ecommunity_compile, + route_match_ecommunity_free +}; + +/* `match nlri` and `set nlri` are replaced by `address-family ipv4` + and `address-family vpnv4'. */ + +/* `match origin' */ +static enum route_map_cmd_result_t +route_match_origin(void *rule, const struct prefix *prefix, void *object) +{ + uint8_t *origin; + struct bgp_path_info *path; + + origin = rule; + path = object; + + if (path->attr->origin == *origin) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_origin_compile(const char *arg) +{ + uint8_t *origin; + + origin = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); + + if (strcmp(arg, "igp") == 0) + *origin = 0; + else if (strcmp(arg, "egp") == 0) + *origin = 1; + else + *origin = 2; + + return origin; +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_origin_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for origin matching. */ +static const struct route_map_rule_cmd route_match_origin_cmd = { + "origin", + route_match_origin, + route_match_origin_compile, + route_match_origin_free +}; + +/* match probability { */ + +static enum route_map_cmd_result_t +route_match_probability(void *rule, const struct prefix *prefix, void *object) +{ + long r = frr_weak_random(); + + switch (*(long *)rule) { + case 0: + break; + case RAND_MAX: + return RMAP_MATCH; + default: + if (r < *(long *)rule) { + return RMAP_MATCH; + } + } + + return RMAP_NOMATCH; +} + +static void *route_match_probability_compile(const char *arg) +{ + long *lobule; + unsigned perc; + + perc = atoi(arg); + lobule = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(long)); + + switch (perc) { + case 0: + *lobule = 0; + break; + case 100: + *lobule = RAND_MAX; + break; + default: + *lobule = RAND_MAX / 100 * perc; + } + + return lobule; +} + +static void route_match_probability_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_probability_cmd = { + "probability", + route_match_probability, + route_match_probability_compile, + route_match_probability_free +}; + +/* `match interface IFNAME' */ +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_interface(void *rule, const struct prefix *prefix, void *object) +{ + struct interface *ifp; + struct bgp_path_info *path; + + path = object; + + if (!path || !path->peer || !path->peer->bgp) + return RMAP_NOMATCH; + + ifp = if_lookup_by_name((char *)rule, path->peer->bgp->vrf_id); + + if (ifp == NULL || ifp->ifindex != path->attr->nh_ifindex) + return RMAP_NOMATCH; + + return RMAP_MATCH; +} + +/* Route map `interface' match statement. `arg' should be + interface name. */ +static void *route_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `interface' value. */ +static void route_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_interface_cmd = { + "interface", + route_match_interface, + route_match_interface_compile, + route_match_interface_free +}; + +/* } */ + +/* `set ip next-hop IP_ADDRESS' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct bgp_path_info *path; + + tag = rule; + path = object; + + return ((path->attr->tag == *tag) ? RMAP_MATCH : RMAP_NOMATCH); +} + + +/* Route map commands for tag matching. */ +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +static enum route_map_cmd_result_t +route_set_srte_color(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *srte_color = rule; + struct bgp_path_info *path; + + path = object; + + path->attr->srte_color = *srte_color; + + return RMAP_OKAY; +} + +/* Route map `sr-te color' compile function */ +static void *route_set_srte_color_compile(const char *arg) +{ + uint32_t *color; + + color = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + *color = atoi(arg); + + return color; +} + +/* Free route map's compiled `sr-te color' value. */ +static void route_set_srte_color_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for sr-te color set. */ +struct route_map_rule_cmd route_set_srte_color_cmd = { + "sr-te color", route_set_srte_color, route_set_srte_color_compile, + route_set_srte_color_free}; + +/* Set nexthop to object. object must be pointer to struct attr. */ +struct rmap_ip_nexthop_set { + struct in_addr *address; + int peer_address; + int unchanged; +}; + +static enum route_map_cmd_result_t +route_set_ip_nexthop(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_ip_nexthop_set *rins = rule; + struct bgp_path_info *path; + struct peer *peer; + + if (prefix->family == AF_INET6) + return RMAP_OKAY; + + path = object; + peer = path->peer; + + if (rins->unchanged) { + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_NEXTHOP_UNCHANGED); + } else if (rins->peer_address) { + if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) + && peer->su_remote + && sockunion_family(peer->su_remote) == AF_INET) { + path->attr->nexthop.s_addr = + sockunion2ip(peer->su_remote); + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + } else if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT)) { + /* The next hop value will be set as part of + * packet rewrite. Set the flags here to indicate + * that rewrite needs to be done. + * Also, clear the value. + */ + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_NEXTHOP_PEER_ADDRESS); + path->attr->nexthop.s_addr = INADDR_ANY; + } + } else { + /* Set next hop value. */ + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + path->attr->nexthop = *rins->address; + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_IPV4_NHOP_CHANGED); + /* case for MP-BGP : MPLS VPN */ + path->attr->mp_nexthop_global_in = *rins->address; + path->attr->mp_nexthop_len = sizeof(*rins->address); + } + + return RMAP_OKAY; +} + +/* Route map `ip nexthop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ip_nexthop_compile(const char *arg) +{ + struct rmap_ip_nexthop_set *rins; + struct in_addr *address = NULL; + int peer_address = 0; + int unchanged = 0; + int ret; + + if (strcmp(arg, "peer-address") == 0) + peer_address = 1; + else if (strcmp(arg, "unchanged") == 0) + unchanged = 1; + else { + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct in_addr)); + ret = inet_aton(arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + } + + rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct rmap_ip_nexthop_set)); + + rins->address = address; + rins->peer_address = peer_address; + rins->unchanged = unchanged; + + return rins; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_ip_nexthop_free(void *rule) +{ + struct rmap_ip_nexthop_set *rins = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rins->address); + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rins); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd route_set_ip_nexthop_cmd = { + "ip next-hop", + route_set_ip_nexthop, + route_set_ip_nexthop_compile, + route_set_ip_nexthop_free +}; + +/* `set l3vpn next-hop encapsulation l3vpn gre' */ + +/* Set nexthop to object */ +struct rmap_l3vpn_nexthop_encapsulation_set { + uint8_t protocol; +}; + +static enum route_map_cmd_result_t +route_set_l3vpn_nexthop_encapsulation(void *rule, const struct prefix *prefix, + void *object) +{ + struct rmap_l3vpn_nexthop_encapsulation_set *rins = rule; + struct bgp_path_info *path; + + path = object; + + if (rins->protocol != IPPROTO_GRE) + return RMAP_OKAY; + + SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_L3VPN_ACCEPT_GRE); + return RMAP_OKAY; +} + +/* Route map `l3vpn nexthop encapsulation' compile function. */ +static void *route_set_l3vpn_nexthop_encapsulation_compile(const char *arg) +{ + struct rmap_l3vpn_nexthop_encapsulation_set *rins; + + rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct rmap_l3vpn_nexthop_encapsulation_set)); + + /* XXX ALL GRE modes are accepted for now: gre or ip6gre */ + rins->protocol = IPPROTO_GRE; + + return rins; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_l3vpn_nexthop_encapsulation_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for l3vpn next-hop encapsulation set. */ +static const struct route_map_rule_cmd + route_set_l3vpn_nexthop_encapsulation_cmd = { + "l3vpn next-hop encapsulation", + route_set_l3vpn_nexthop_encapsulation, + route_set_l3vpn_nexthop_encapsulation_compile, + route_set_l3vpn_nexthop_encapsulation_free}; + +/* `set local-preference LOCAL_PREF' */ + +/* Set local preference. */ +static enum route_map_cmd_result_t +route_set_local_pref(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + uint32_t locpref = 0; + + /* Fetch routemap's rule information. */ + rv = rule; + path = object; + + /* Set local preference value. */ + if (path->attr->local_pref) + locpref = path->attr->local_pref; + + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + path->attr->local_pref = route_value_adjust(rv, locpref, path->peer); + + return RMAP_OKAY; +} + +/* Set local preference rule structure. */ +static const struct route_map_rule_cmd route_set_local_pref_cmd = { + "local-preference", + route_set_local_pref, + route_value_compile, + route_value_free, +}; + +/* `set weight WEIGHT' */ + +/* Set weight. */ +static enum route_map_cmd_result_t +route_set_weight(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + + /* Fetch routemap's rule information. */ + rv = rule; + path = object; + + /* Set weight value. */ + path->attr->weight = route_value_adjust(rv, 0, path->peer); + + return RMAP_OKAY; +} + +/* Set local preference rule structure. */ +static const struct route_map_rule_cmd route_set_weight_cmd = { + "weight", + route_set_weight, + route_value_compile, + route_value_free, +}; + +/* `set distance DISTANCE */ +static enum route_map_cmd_result_t +route_set_distance(void *rule, const struct prefix *prefix, void *object) +{ + struct bgp_path_info *path = object; + struct rmap_value *rv = rule; + + path->attr->distance = rv->value; + + return RMAP_OKAY; +} + +/* set distance rule structure */ +static const struct route_map_rule_cmd route_set_distance_cmd = { + "distance", + route_set_distance, + route_value_compile, + route_value_free, +}; + +/* `set metric METRIC' */ + +/* Set metric to attribute. */ +static enum route_map_cmd_result_t +route_set_metric(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + uint32_t med = 0; + + /* Fetch routemap's rule information. */ + rv = rule; + path = object; + + if (path->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + med = path->attr->med; + + path->attr->med = route_value_adjust(rv, med, path->peer); + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + return RMAP_OKAY; +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_value_compile, + route_value_free, +}; + +/* `set table (1-4294967295)' */ + +static enum route_map_cmd_result_t +route_set_table_id(void *rule, const struct prefix *prefix, + + void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + + /* Fetch routemap's rule information. */ + rv = rule; + path = object; + + path->attr->rmap_table_id = rv->value; + + return RMAP_OKAY; +} + +/* Set table_id rule structure. */ +static const struct route_map_rule_cmd route_set_table_id_cmd = { + "table", + route_set_table_id, + route_value_compile, + route_value_free +}; + +/* `set as-path prepend ASPATH' */ + +/* For AS path prepend mechanism. */ +static enum route_map_cmd_result_t +route_set_aspath_prepend(void *rule, const struct prefix *prefix, void *object) +{ + struct aspath *aspath; + struct aspath *new; + struct bgp_path_info *path; + + path = object; + + if (path->attr->aspath->refcnt) + new = aspath_dup(path->attr->aspath); + else + new = path->attr->aspath; + + if ((uintptr_t)rule > 10) { + aspath = rule; + aspath_prepend(aspath, new); + } else { + as_t as = aspath_leftmost(new); + if (as) + new = aspath_add_seq_n(new, as, (uintptr_t)rule); + } + + path->attr->aspath = new; + + return RMAP_OKAY; +} + +static void *route_set_aspath_prepend_compile(const char *arg) +{ + unsigned int num; + + if (sscanf(arg, "last-as %u", &num) == 1 && num > 0 && num <= 10) + return (void *)(uintptr_t)num; + + return route_aspath_compile(arg); +} + +static void route_set_aspath_prepend_free(void *rule) +{ + if ((uintptr_t)rule > 10) + route_aspath_free(rule); +} + + +/* Set as-path prepend rule structure. */ +static const struct route_map_rule_cmd route_set_aspath_prepend_cmd = { + "as-path prepend", + route_set_aspath_prepend, + route_set_aspath_prepend_compile, + route_set_aspath_prepend_free, +}; + +static void *route_aspath_exclude_compile(const char *arg) +{ + struct aspath_exclude *ase; + struct as_list *aux_aslist; + const char *str = arg; + static const char asp_acl[] = "as-path-access-list"; + + ase = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct aspath_exclude)); + if (strmatch(str, "all")) + ase->exclude_all = true; + else if (!strncmp(str, asp_acl, strlen(asp_acl))) { + str += strlen(asp_acl); + while (*str == ' ') + str++; + ase->exclude_aspath_acl_name = XSTRDUP(MTYPE_TMP, str); + aux_aslist = as_list_lookup(str); + if (!aux_aslist) + /* new orphan filter */ + as_exclude_set_orphan(ase); + else + as_list_list_add_head(&aux_aslist->exclude_rule, ase); + + ase->exclude_aspath_acl = aux_aslist; + } else + ase->aspath = aspath_str2aspath(str, bgp_get_asnotation(NULL)); + + return ase; +} + +static void route_aspath_exclude_free(void *rule) +{ + struct aspath_exclude *ase = rule; + struct as_list *acl; + + /* manage references to that rule*/ + if (ase->exclude_aspath_acl) { + acl = ase->exclude_aspath_acl; + as_list_list_del(&acl->exclude_rule, ase); + } else if (ase->exclude_aspath_acl_name) { + /* no ref to acl, this aspath exclude is orphan */ + as_exclude_remove_orphan(ase); + } + + aspath_free(ase->aspath); + if (ase->exclude_aspath_acl_name) + XFREE(MTYPE_TMP, ase->exclude_aspath_acl_name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, ase); +} + +/* For ASN exclude mechanism. + * Iterate over ASns requested and filter them from the given AS_PATH one by + * one. + * Make a deep copy of existing AS_PATH, but for the first ASn only. + */ +static enum route_map_cmd_result_t +route_set_aspath_exclude(void *rule, const struct prefix *dummy, void *object) +{ + struct aspath *new_path; + struct bgp_path_info *path; + struct aspath_exclude *ase = rule; + + path = object; + + if (path->peer->sort != BGP_PEER_EBGP) { + zlog_warn( + "`set as-path exclude` is supported only for EBGP peers"); + return RMAP_NOOP; + } + + if (path->attr->aspath->refcnt) + new_path = aspath_dup(path->attr->aspath); + else + new_path = path->attr->aspath; + + if (ase->aspath) + path->attr->aspath = + aspath_filter_exclude(new_path, ase->aspath); + else if (ase->exclude_all) + path->attr->aspath = aspath_filter_exclude_all(new_path); + + else if (ase->exclude_aspath_acl) + path->attr->aspath = + aspath_filter_exclude_acl(new_path, + ase->exclude_aspath_acl); + return RMAP_OKAY; +} + +/* Set ASn exclude rule structure. */ +static const struct route_map_rule_cmd route_set_aspath_exclude_cmd = { + "as-path exclude", + route_set_aspath_exclude, + route_aspath_exclude_compile, + route_aspath_exclude_free, +}; + +/* `set as-path replace AS-PATH` */ +static void *route_aspath_replace_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_aspath_replace_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static enum route_map_cmd_result_t +route_set_aspath_replace(void *rule, const struct prefix *dummy, void *object) +{ + struct aspath *aspath_new; + const char *replace = rule; + struct bgp_path_info *path = object; + as_t replace_asn = 0; + as_t configured_asn; + char *buf; + char src_asn[ASN_STRING_MAX_SIZE]; + char *acl_list_name = NULL; + uint32_t acl_list_name_len = 0; + char *buf_acl_name = NULL; + static const char asp_acl[] = "as-path-access-list"; + struct as_list *aspath_acl = NULL; + + if (path->peer->sort != BGP_PEER_EBGP) { + zlog_warn( + "`set as-path replace` is supported only for EBGP peers"); + goto end_ko; + } + + buf = strchr(replace, ' '); + if (!buf) { + configured_asn = path->peer->change_local_as + ? path->peer->change_local_as + : path->peer->local_as; + } else if (!strncmp(replace, asp_acl, strlen(asp_acl))) { + /* its as-path-acl-list command get the access list name */ + while (*buf == ' ') + buf++; + buf_acl_name = buf; + buf = strchr(buf_acl_name, ' '); + if (buf) + acl_list_name_len = buf - buf_acl_name; + else + acl_list_name_len = strlen(buf_acl_name); + + buf_acl_name[acl_list_name_len] = 0; + /* get the acl-list */ + aspath_acl = as_list_lookup(buf_acl_name); + if (!aspath_acl) { + zlog_warn("`set as-path replace`, invalid as-path-access-list name: %s", + buf_acl_name); + goto end_ko; + } + acl_list_name = XSTRDUP(MTYPE_TMP, buf_acl_name); + buf_acl_name[acl_list_name_len] = ' '; + + if (!buf) { + configured_asn = path->peer->change_local_as + ? path->peer->change_local_as + : path->peer->local_as; + } else { + while (*buf == ' ') + buf++; + /* get the configured asn */ + if (!asn_str2asn(buf, &configured_asn)) { + zlog_warn( + "`set as-path replace`, invalid configured AS %s", + buf); + goto end_ko; + } + } + + replace = buf; + + } else { + memcpy(src_asn, replace, (size_t)(buf - replace)); + src_asn[(size_t)(buf - replace)] = '\0'; + replace = src_asn; + buf++; + if (!asn_str2asn(buf, &configured_asn)) { + zlog_warn( + "`set as-path replace`, invalid configured AS %s", + buf); + goto end_ko; + } + } + + if (replace && !strmatch(replace, "any") && + !asn_str2asn(replace, &replace_asn)) { + zlog_warn("`set as-path replace`, invalid AS %s", replace); + goto end_ko; + } + + if (path->attr->aspath->refcnt) + aspath_new = aspath_dup(path->attr->aspath); + else + aspath_new = path->attr->aspath; + + if (aspath_acl) { + path->attr->aspath = aspath_replace_regex_asn(aspath_new, + aspath_acl, + configured_asn); + } else if (strmatch(replace, "any")) { + path->attr->aspath = + aspath_replace_all_asn(aspath_new, configured_asn); + } else { + path->attr->aspath = aspath_replace_specific_asn( + aspath_new, replace_asn, configured_asn); + } + aspath_free(aspath_new); + + + if (acl_list_name) + XFREE(MTYPE_TMP, acl_list_name); + return RMAP_OKAY; + +end_ko: + if (acl_list_name) + XFREE(MTYPE_TMP, acl_list_name); + return RMAP_NOOP; + +} + +static const struct route_map_rule_cmd route_set_aspath_replace_cmd = { + "as-path replace", + route_set_aspath_replace, + route_aspath_replace_compile, + route_aspath_replace_free, +}; + +/* `set community COMMUNITY' */ +struct rmap_com_set { + struct community *com; + int additive; + int none; +}; + +/* For community set mechanism. */ +static enum route_map_cmd_result_t +route_set_community(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_com_set *rcs; + struct bgp_path_info *path; + struct attr *attr; + struct community *new = NULL; + struct community *old; + struct community *merge; + + rcs = rule; + path = object; + attr = path->attr; + old = bgp_attr_get_community(attr); + + /* "none" case. */ + if (rcs->none) { + bgp_attr_set_community(attr, NULL); + /* See the longer comment down below. */ + if (old && old->refcnt == 0) + community_free(&old); + return RMAP_OKAY; + } + + /* "additive" case. */ + if (rcs->additive && old) { + merge = community_merge(community_dup(old), rcs->com); + + new = community_uniq_sort(merge); + community_free(&merge); + } else + new = community_dup(rcs->com); + + /* HACK: if the old community is not intern'd, + * we should free it here, or all reference to it may be + * lost. + * Really need to cleanup attribute caching sometime. + */ + if (old && old->refcnt == 0) + community_free(&old); + + /* will be interned by caller if required */ + bgp_attr_set_community(attr, new); + + return RMAP_OKAY; +} + +/* Compile function for set community. */ +static void *route_set_community_compile(const char *arg) +{ + struct rmap_com_set *rcs; + struct community *com = NULL; + char *sp; + int additive = 0; + int none = 0; + + if (strcmp(arg, "none") == 0) + none = 1; + else { + sp = strstr(arg, "additive"); + + if (sp && sp > arg) { + /* "additive" keyword is included. */ + additive = 1; + *(sp - 1) = '\0'; + } + + com = community_str2com(arg); + + if (additive) + *(sp - 1) = ' '; + + if (!com) + return NULL; + } + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_com_set)); + rcs->com = com; + rcs->additive = additive; + rcs->none = none; + + return rcs; +} + +/* Free function for set community. */ +static void route_set_community_free(void *rule) +{ + struct rmap_com_set *rcs = rule; + + if (rcs->com) + community_free(&rcs->com); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcs); +} + +/* Set community rule structure. */ +static const struct route_map_rule_cmd route_set_community_cmd = { + "community", + route_set_community, + route_set_community_compile, + route_set_community_free, +}; + +/* `set community COMMUNITY' */ +struct rmap_lcom_set { + struct lcommunity *lcom; + int additive; + int none; +}; + + +/* For lcommunity set mechanism. */ +static enum route_map_cmd_result_t +route_set_lcommunity(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_lcom_set *rcs; + struct bgp_path_info *path; + struct attr *attr; + struct lcommunity *new = NULL; + struct lcommunity *old; + struct lcommunity *merge; + + rcs = rule; + path = object; + attr = path->attr; + old = bgp_attr_get_lcommunity(attr); + + /* "none" case. */ + if (rcs->none) { + bgp_attr_set_lcommunity(attr, NULL); + + /* See the longer comment down below. */ + if (old && old->refcnt == 0) + lcommunity_free(&old); + return RMAP_OKAY; + } + + if (rcs->additive && old) { + merge = lcommunity_merge(lcommunity_dup(old), rcs->lcom); + + new = lcommunity_uniq_sort(merge); + lcommunity_free(&merge); + } else + new = lcommunity_dup(rcs->lcom); + + /* HACK: if the old large-community is not intern'd, + * we should free it here, or all reference to it may be + * lost. + * Really need to cleanup attribute caching sometime. + */ + if (old && old->refcnt == 0) + lcommunity_free(&old); + + /* will be intern()'d or attr_flush()'d by bgp_update_main() */ + bgp_attr_set_lcommunity(attr, new); + + return RMAP_OKAY; +} + +/* Compile function for set community. */ +static void *route_set_lcommunity_compile(const char *arg) +{ + struct rmap_lcom_set *rcs; + struct lcommunity *lcom = NULL; + char *sp; + int additive = 0; + int none = 0; + + if (strcmp(arg, "none") == 0) + none = 1; + else { + sp = strstr(arg, "additive"); + + if (sp && sp > arg) { + /* "additive" keyworkd is included. */ + additive = 1; + *(sp - 1) = '\0'; + } + + lcom = lcommunity_str2com(arg); + + if (additive) + *(sp - 1) = ' '; + + if (!lcom) + return NULL; + } + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_com_set)); + rcs->lcom = lcom; + rcs->additive = additive; + rcs->none = none; + + return rcs; +} + +/* Free function for set lcommunity. */ +static void route_set_lcommunity_free(void *rule) +{ + struct rmap_lcom_set *rcs = rule; + + if (rcs->lcom) { + lcommunity_free(&rcs->lcom); + } + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcs); +} + +/* Set community rule structure. */ +static const struct route_map_rule_cmd route_set_lcommunity_cmd = { + "large-community", + route_set_lcommunity, + route_set_lcommunity_compile, + route_set_lcommunity_free, +}; + +/* `set large-comm-list (<1-99>|<100-500>|WORD) delete' */ + +/* For large community set mechanism. */ +static enum route_map_cmd_result_t +route_set_lcommunity_delete(void *rule, const struct prefix *pfx, void *object) +{ + struct community_list *list; + struct lcommunity *merge; + struct lcommunity *new; + struct lcommunity *old; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + if (!rcom) + return RMAP_OKAY; + + path = object; + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + LARGE_COMMUNITY_LIST_MASTER); + old = bgp_attr_get_lcommunity(path->attr); + + if (list && old) { + merge = lcommunity_list_match_delete(lcommunity_dup(old), list); + new = lcommunity_uniq_sort(merge); + lcommunity_free(&merge); + + /* HACK: if the old community is not intern'd, + * we should free it here, or all reference to it may be + * lost. + * Really need to cleanup attribute caching sometime. + */ + if (old->refcnt == 0) + lcommunity_free(&old); + + if (new->size == 0) { + bgp_attr_set_lcommunity(path->attr, NULL); + lcommunity_free(&new); + } else { + bgp_attr_set_lcommunity(path->attr, new); + } + } + + return RMAP_OKAY; +} + +/* Compile function for set lcommunity. */ +static void *route_set_lcommunity_delete_compile(const char *arg) +{ + struct rmap_community *rcom; + char **splits; + int num; + + frrstr_split(arg, " ", &splits, &num); + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, splits[0]); + rcom->name_hash = bgp_clist_hash_key(rcom->name); + + for (int i = 0; i < num; i++) + XFREE(MTYPE_TMP, splits[i]); + XFREE(MTYPE_TMP, splits); + + return rcom; +} + +/* Free function for set lcommunity. */ +static void route_set_lcommunity_delete_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +/* Set lcommunity rule structure. */ +static const struct route_map_rule_cmd route_set_lcommunity_delete_cmd = { + "large-comm-list", + route_set_lcommunity_delete, + route_set_lcommunity_delete_compile, + route_set_lcommunity_delete_free, +}; + + +/* `set comm-list (<1-99>|<100-500>|WORD) delete' */ + +/* For community set mechanism. */ +static enum route_map_cmd_result_t +route_set_community_delete(void *rule, const struct prefix *prefix, + void *object) +{ + struct community_list *list; + struct community *merge; + struct community *new; + struct community *old; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + if (!rcom) + return RMAP_OKAY; + + path = object; + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + COMMUNITY_LIST_MASTER); + old = bgp_attr_get_community(path->attr); + + if (list && old) { + merge = community_list_match_delete(community_dup(old), list); + new = community_uniq_sort(merge); + community_free(&merge); + + /* HACK: if the old community is not intern'd, + * we should free it here, or all reference to it may be + * lost. + * Really need to cleanup attribute caching sometime. + */ + if (old->refcnt == 0) + community_free(&old); + + if (new->size == 0) { + bgp_attr_set_community(path->attr, NULL); + community_free(&new); + } else { + bgp_attr_set_community(path->attr, new); + } + } + + return RMAP_OKAY; +} + +/* Compile function for set community. */ +static void *route_set_community_delete_compile(const char *arg) +{ + struct rmap_community *rcom; + char **splits; + int num; + + frrstr_split(arg, " ", &splits, &num); + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, splits[0]); + rcom->name_hash = bgp_clist_hash_key(rcom->name); + + for (int i = 0; i < num; i++) + XFREE(MTYPE_TMP, splits[i]); + XFREE(MTYPE_TMP, splits); + + return rcom; +} + +/* Free function for set community. */ +static void route_set_community_delete_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +/* Set community rule structure. */ +static const struct route_map_rule_cmd route_set_community_delete_cmd = { + "comm-list", + route_set_community_delete, + route_set_community_delete_compile, + route_set_community_delete_free, +}; + +/* `set extcomm-list (<1-99>|<100-500>|WORD) delete' */ +static enum route_map_cmd_result_t +route_set_ecommunity_delete(void *rule, const struct prefix *prefix, + void *object) +{ + struct community_list *list; + struct ecommunity *merge; + struct ecommunity *new; + struct ecommunity *old; + struct bgp_path_info *path; + struct rmap_community *rcom = rule; + + if (!rcom) + return RMAP_OKAY; + + path = object; + list = community_list_lookup(bgp_clist, rcom->name, rcom->name_hash, + EXTCOMMUNITY_LIST_MASTER); + old = bgp_attr_get_ecommunity(path->attr); + if (list && old) { + merge = ecommunity_list_match_delete(ecommunity_dup(old), list); + new = ecommunity_uniq_sort(merge); + ecommunity_free(&merge); + + /* HACK: if the old community is not intern'd, + * we should free it here, or all reference to it may be + * lost. + * Really need to cleanup attribute caching sometime. + */ + if (old->refcnt == 0) + ecommunity_free(&old); + + if (new->size == 0) { + bgp_attr_set_ecommunity(path->attr, NULL); + ecommunity_free(&new); + } else { + bgp_attr_set_ecommunity(path->attr, new); + } + } + + return RMAP_OKAY; +} + +static void *route_set_ecommunity_delete_compile(const char *arg) +{ + struct rmap_community *rcom; + char **splits; + int num; + + frrstr_split(arg, " ", &splits, &num); + + rcom = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_community)); + rcom->name = XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, splits[0]); + rcom->name_hash = bgp_clist_hash_key(rcom->name); + + for (int i = 0; i < num; i++) + XFREE(MTYPE_TMP, splits[i]); + XFREE(MTYPE_TMP, splits); + + return rcom; +} + +static void route_set_ecommunity_delete_free(void *rule) +{ + struct rmap_community *rcom = rule; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom->name); + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcom); +} + +static const struct route_map_rule_cmd route_set_ecommunity_delete_cmd = { + "extended-comm-list", + route_set_ecommunity_delete, + route_set_ecommunity_delete_compile, + route_set_ecommunity_delete_free, +}; + +/* `set extcommunity rt COMMUNITY' */ + +struct rmap_ecom_set { + struct ecommunity *ecom; + bool none; +}; + +/* For community set mechanism. Used by _rt and _soo. */ +static enum route_map_cmd_result_t +route_set_ecommunity(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *new_ecom; + struct ecommunity *old_ecom; + struct bgp_path_info *path; + struct attr *attr; + + rcs = rule; + path = object; + attr = path->attr; + + if (rcs->none) { + bgp_attr_set_ecommunity(attr, NULL); + return RMAP_OKAY; + } + + if (!rcs->ecom) + return RMAP_OKAY; + + /* We assume additive for Extended Community. */ + old_ecom = bgp_attr_get_ecommunity(path->attr); + + if (old_ecom) { + new_ecom = + ecommunity_merge(ecommunity_dup(old_ecom), rcs->ecom); + + /* old_ecom->refcnt = 1 => owned elsewhere, e.g. + * bgp_update_receive() + * ->refcnt = 0 => set by a previous route-map + * statement */ + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } else + new_ecom = ecommunity_dup(rcs->ecom); + + /* will be intern()'d or attr_flush()'d by bgp_update_main() */ + bgp_attr_set_ecommunity(path->attr, new_ecom); + + return RMAP_OKAY; +} + +static void *route_set_ecommunity_none_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + bool none = false; + + if (strncmp(arg, "none", 4) == 0) + none = true; + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set)); + rcs->ecom = NULL; + rcs->none = none; + + return rcs; +} + +static void *route_set_ecommunity_rt_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *ecom; + + ecom = ecommunity_str2com(arg, ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecom) + return NULL; + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set)); + rcs->ecom = ecommunity_intern(ecom); + rcs->none = false; + + return rcs; +} + +/* Free function for set community. Used by _rt and _soo */ +static void route_set_ecommunity_free(void *rule) +{ + struct rmap_ecom_set *rcs = rule; + + if (rcs->ecom) + ecommunity_unintern(&rcs->ecom); + + XFREE(MTYPE_ROUTE_MAP_COMPILED, rcs); +} + +static const struct route_map_rule_cmd route_set_ecommunity_none_cmd = { + "extcommunity", + route_set_ecommunity, + route_set_ecommunity_none_compile, + route_set_ecommunity_free, +}; + +/* Set community rule structure. */ +static const struct route_map_rule_cmd route_set_ecommunity_rt_cmd = { + "extcommunity rt", + route_set_ecommunity, + route_set_ecommunity_rt_compile, + route_set_ecommunity_free, +}; + +/* `set extcommunity soo COMMUNITY' */ + +/* Compile function for set community. */ +static void *route_set_ecommunity_soo_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *ecom; + + ecom = ecommunity_str2com(arg, ECOMMUNITY_SITE_ORIGIN, 0); + if (!ecom) + return NULL; + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set)); + rcs->ecom = ecommunity_intern(ecom); + rcs->none = false; + + return rcs; +} + +/* Set community rule structure. */ +static const struct route_map_rule_cmd route_set_ecommunity_soo_cmd = { + "extcommunity soo", + route_set_ecommunity, + route_set_ecommunity_soo_compile, + route_set_ecommunity_free, +}; + +static void *route_set_ecommunity_nt_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *ecom; + + ecom = ecommunity_str2com(arg, ECOMMUNITY_NODE_TARGET, 0); + if (!ecom) + return NULL; + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set)); + rcs->ecom = ecommunity_intern(ecom); + rcs->none = false; + + return rcs; +} + +static const struct route_map_rule_cmd route_set_ecommunity_nt_cmd = { + "extcommunity nt", + route_set_ecommunity, + route_set_ecommunity_nt_compile, + route_set_ecommunity_free, +}; + +/* `set extcommunity bandwidth' */ + +struct rmap_ecomm_lb_set { + uint8_t lb_type; +#define RMAP_ECOMM_LB_SET_VALUE 1 +#define RMAP_ECOMM_LB_SET_CUMUL 2 +#define RMAP_ECOMM_LB_SET_NUM_MPATH 3 + bool non_trans; + uint64_t bw; +}; + +static enum route_map_cmd_result_t +route_set_ecommunity_lb(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_ecomm_lb_set *rels = rule; + struct bgp_path_info *path; + struct peer *peer; + struct ecommunity ecom_lb = {0}; + uint64_t bw_bytes = 0; + uint16_t mpath_count = 0; + struct ecommunity *new_ecom; + struct ecommunity *old_ecom; + as_t as; + + path = object; + peer = path->peer; + if (!peer || !peer->bgp) + return RMAP_ERROR; + + /* Build link bandwidth extended community */ + as = (peer->bgp->as > BGP_AS_MAX) ? BGP_AS_TRANS : peer->bgp->as; + if (rels->lb_type == RMAP_ECOMM_LB_SET_VALUE) { + bw_bytes = (rels->bw * 1000 * 1000) / 8; + } else if (rels->lb_type == RMAP_ECOMM_LB_SET_CUMUL) { + /* process this only for the best path. */ + if (!CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + return RMAP_OKAY; + + bw_bytes = bgp_path_info_mpath_cumbw(path); + if (!bw_bytes) + return RMAP_OKAY; + + } else if (rels->lb_type == RMAP_ECOMM_LB_SET_NUM_MPATH) { + + /* process this only for the best path. */ + if (!CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + return RMAP_OKAY; + + bw_bytes = (peer->bgp->lb_ref_bw * 1000 * 1000) / 8; + mpath_count = bgp_path_info_mpath_count(path) + 1; + bw_bytes *= mpath_count; + } + + if (CHECK_FLAG(peer->flags, PEER_FLAG_EXTENDED_LINK_BANDWIDTH)) { + struct ecommunity_val_ipv6 lb_eval; + + encode_lb_extended_extcomm(as, bw_bytes, rels->non_trans, + &lb_eval); + + old_ecom = bgp_attr_get_ipv6_ecommunity(path->attr); + if (old_ecom) { + new_ecom = ecommunity_dup(old_ecom); + ecommunity_add_val_ipv6(new_ecom, &lb_eval, true, true); + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } else { + ecom_lb.size = 1; + ecom_lb.unit_size = IPV6_ECOMMUNITY_SIZE; + ecom_lb.val = (uint8_t *)lb_eval.val; + new_ecom = ecommunity_dup(&ecom_lb); + } + + bgp_attr_set_ipv6_ecommunity(path->attr, new_ecom); + } else { + struct ecommunity_val lb_eval; + + encode_lb_extcomm(as, bw_bytes, rels->non_trans, &lb_eval, + CHECK_FLAG(peer->flags, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE)); + + old_ecom = bgp_attr_get_ecommunity(path->attr); + if (old_ecom) { + new_ecom = ecommunity_dup(old_ecom); + ecommunity_add_val(new_ecom, &lb_eval, true, true); + if (!old_ecom->refcnt) + ecommunity_free(&old_ecom); + } else { + ecom_lb.size = 1; + ecom_lb.unit_size = ECOMMUNITY_SIZE; + ecom_lb.val = (uint8_t *)lb_eval.val; + new_ecom = ecommunity_dup(&ecom_lb); + } + + bgp_attr_set_ecommunity(path->attr, new_ecom); + } + + /* Mark that route-map has set link bandwidth; used in attribute + * setting decisions. + */ + SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_LINK_BW_SET); + + return RMAP_OKAY; +} + +static void *route_set_ecommunity_lb_compile(const char *arg) +{ + struct rmap_ecomm_lb_set *rels; + uint8_t lb_type; + uint64_t bw = 0; + char bw_str[40] = {0}; + char *p, *str; + bool non_trans = false; + + str = (char *)arg; + p = strchr(arg, ' '); + if (p) { + int len; + + len = p - arg; + memcpy(bw_str, arg, len); + non_trans = true; + str = bw_str; + } + + if (strcmp(str, "cumulative") == 0) + lb_type = RMAP_ECOMM_LB_SET_CUMUL; + else if (strcmp(str, "num-multipaths") == 0) + lb_type = RMAP_ECOMM_LB_SET_NUM_MPATH; + else { + char *end = NULL; + + bw = strtoul(str, &end, 10); + if (*end != '\0') + return NULL; + lb_type = RMAP_ECOMM_LB_SET_VALUE; + } + + rels = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct rmap_ecomm_lb_set)); + rels->lb_type = lb_type; + rels->bw = bw; + rels->non_trans = non_trans; + + return rels; +} + +static enum route_map_cmd_result_t +route_set_ecommunity_color(void *rule, const struct prefix *prefix, + void *object) +{ + route_set_ecommunity(rule, prefix, object); + + return RMAP_OKAY; +} + +static void *route_set_ecommunity_color_compile(const char *arg) +{ + struct rmap_ecom_set *rcs; + struct ecommunity *ecom; + + ecom = ecommunity_str2com(arg, ECOMMUNITY_COLOR, 0); + if (!ecom) + return NULL; + + rcs = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct rmap_ecom_set)); + rcs->ecom = ecommunity_intern(ecom); + rcs->none = false; + + return rcs; +} + +static const struct route_map_rule_cmd route_set_ecommunity_color_cmd = { + "extcommunity color", + route_set_ecommunity_color, + route_set_ecommunity_color_compile, + route_set_ecommunity_free, +}; + + +static void route_set_ecommunity_lb_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set community rule structure. */ +struct route_map_rule_cmd route_set_ecommunity_lb_cmd = { + "extcommunity bandwidth", + route_set_ecommunity_lb, + route_set_ecommunity_lb_compile, + route_set_ecommunity_lb_free, +}; + +/* `set origin ORIGIN' */ + +/* For origin set. */ +static enum route_map_cmd_result_t +route_set_origin(void *rule, const struct prefix *prefix, void *object) +{ + uint8_t *origin; + struct bgp_path_info *path; + + origin = rule; + path = object; + + path->attr->origin = *origin; + + return RMAP_OKAY; +} + +/* Compile function for origin set. */ +static void *route_set_origin_compile(const char *arg) +{ + uint8_t *origin; + + origin = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint8_t)); + + if (strcmp(arg, "igp") == 0) + *origin = BGP_ORIGIN_IGP; + else if (strcmp(arg, "egp") == 0) + *origin = BGP_ORIGIN_EGP; + else + *origin = BGP_ORIGIN_INCOMPLETE; + + return origin; +} + +/* Compile function for origin set. */ +static void route_set_origin_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set origin rule structure. */ +static const struct route_map_rule_cmd route_set_origin_cmd = { + "origin", + route_set_origin, + route_set_origin_compile, + route_set_origin_free, +}; + +/* `set atomic-aggregate' */ + +/* For atomic aggregate set. */ +static enum route_map_cmd_result_t +route_set_atomic_aggregate(void *rule, const struct prefix *pfx, void *object) +{ + struct bgp_path_info *path; + + path = object; + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE); + + return RMAP_OKAY; +} + +/* Compile function for atomic aggregate. */ +static void *route_set_atomic_aggregate_compile(const char *arg) +{ + return (void *)1; +} + +/* Compile function for atomic aggregate. */ +static void route_set_atomic_aggregate_free(void *rule) +{ + return; +} + +/* Set atomic aggregate rule structure. */ +static const struct route_map_rule_cmd route_set_atomic_aggregate_cmd = { + "atomic-aggregate", + route_set_atomic_aggregate, + route_set_atomic_aggregate_compile, + route_set_atomic_aggregate_free, +}; + +/* AIGP TLV Metric */ +static enum route_map_cmd_result_t +route_set_aigp_metric(void *rule, const struct prefix *pfx, void *object) +{ + const char *aigp_metric = rule; + struct bgp_path_info *path = object; + uint32_t aigp = 0; + + if (strmatch(aigp_metric, "igp-metric")) { + if (!path->nexthop) + return RMAP_NOMATCH; + + bgp_attr_set_aigp_metric(path->attr, path->nexthop->metric); + } else { + aigp = atoi(aigp_metric); + bgp_attr_set_aigp_metric(path->attr, aigp); + } + + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AIGP); + + return RMAP_OKAY; +} + +static void *route_set_aigp_metric_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_set_aigp_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_set_aigp_metric_cmd = { + "aigp-metric", + route_set_aigp_metric, + route_set_aigp_metric_compile, + route_set_aigp_metric_free, +}; + +/* `set aggregator as AS A.B.C.D' */ +struct aggregator { + as_t as; + struct in_addr address; +}; + +static enum route_map_cmd_result_t +route_set_aggregator_as(void *rule, const struct prefix *prefix, void *object) +{ + struct bgp_path_info *path; + struct aggregator *aggregator; + + path = object; + aggregator = rule; + + path->attr->aggregator_as = aggregator->as; + path->attr->aggregator_addr = aggregator->address; + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR); + + return RMAP_OKAY; +} + +static void *route_set_aggregator_as_compile(const char *arg) +{ + struct aggregator *aggregator; + char as[10]; + char address[20]; + int ret; + + aggregator = + XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct aggregator)); + if (sscanf(arg, "%s %s", as, address) != 2) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, aggregator); + return NULL; + } + + aggregator->as = strtoul(as, NULL, 10); + ret = inet_aton(address, &aggregator->address); + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, aggregator); + return NULL; + } + return aggregator; +} + +static void route_set_aggregator_as_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_set_aggregator_as_cmd = { + "aggregator as", + route_set_aggregator_as, + route_set_aggregator_as_compile, + route_set_aggregator_as_free, +}; + +/* Set tag to object. object must be pointer to struct bgp_path_info */ +static enum route_map_cmd_result_t +route_set_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct bgp_path_info *path; + + tag = rule; + path = object; + + /* Set tag value */ + path->attr->tag = *tag; + + return RMAP_OKAY; +} + +/* Route map commands for tag set. */ +static const struct route_map_rule_cmd route_set_tag_cmd = { + "tag", + route_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* Set label-index to object. object must be pointer to struct bgp_path_info */ +static enum route_map_cmd_result_t +route_set_label_index(void *rule, const struct prefix *prefix, void *object) +{ + struct rmap_value *rv; + struct bgp_path_info *path; + uint32_t label_index; + + /* Fetch routemap's rule information. */ + rv = rule; + path = object; + + /* Set label-index value. */ + label_index = rv->value; + if (label_index) { + path->attr->label_index = label_index; + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_PREFIX_SID); + } + + return RMAP_OKAY; +} + +/* Route map commands for label-index set. */ +static const struct route_map_rule_cmd route_set_label_index_cmd = { + "label-index", + route_set_label_index, + route_value_compile, + route_value_free, +}; + +/* `match ipv6 address IP_ACCESS_LIST' */ + +static enum route_map_cmd_result_t +route_match_ipv6_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + if (prefix->family == AF_INET6) { + alist = access_list_lookup(AFI_IP6, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, prefix) == FILTER_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_address_compile(const char *arg) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP6, arg); + if (!alist) + zlog_warn( + "Access List specified %s does not exist yet, default will be NO_MATCH until it is created", + arg); + + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_ipv6_address_cmd = { + "ipv6 address", + route_match_ipv6_address, + route_match_ipv6_address_compile, + route_match_ipv6_address_free +}; + +/* `match ipv6 next-hop ACCESSLIST6_NAME' */ +static enum route_map_cmd_result_t +route_match_ipv6_next_hop(void *rule, const struct prefix *prefix, void *object) +{ + struct bgp_path_info *path; + struct access_list *alist; + struct prefix_ipv6 p; + + if (prefix->family == AF_INET6) { + path = object; + p.family = AF_INET6; + p.prefix = path->attr->mp_nexthop_global; + p.prefixlen = IPV6_MAX_BITLEN; + + alist = access_list_lookup(AFI_IP6, (char *)rule); + if (!alist) { + if (unlikely(CHECK_FLAG(rmap_debug, + DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + + if (access_list_apply(alist, &p) == FILTER_PERMIT) + return RMAP_MATCH; + + if (path->attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) { + p.prefix = path->attr->mp_nexthop_local; + if (access_list_apply(alist, &p) == FILTER_PERMIT) + return RMAP_MATCH; + } + } + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_next_hop_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_next_hop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ipv6_next_hop_cmd = { + "ipv6 next-hop", + route_match_ipv6_next_hop, + route_match_ipv6_next_hop_compile, + route_match_ipv6_next_hop_free +}; + +/* `match ipv6 next-hop IP_ADDRESS' */ + +static enum route_map_cmd_result_t +route_match_ipv6_next_hop_address(void *rule, const struct prefix *prefix, + void *object) +{ + struct in6_addr *addr = rule; + struct bgp_path_info *path; + + path = object; + + if (IPV6_ADDR_SAME(&path->attr->mp_nexthop_global, addr)) + return RMAP_MATCH; + + if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL + && IPV6_ADDR_SAME(&path->attr->mp_nexthop_local, rule)) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_next_hop_address_compile(const char *arg) +{ + struct in6_addr *address; + int ret; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + + ret = inet_pton(AF_INET6, arg, address); + if (!ret) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +static void route_match_ipv6_next_hop_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ipv6_next_hop_address_cmd = { + "ipv6 next-hop address", + route_match_ipv6_next_hop_address, + route_match_ipv6_next_hop_address_compile, + route_match_ipv6_next_hop_address_free +}; + +/* `match ip next-hop address IP_ADDRESS' */ + +static enum route_map_cmd_result_t +route_match_ipv4_next_hop(void *rule, const struct prefix *prefix, void *object) +{ + struct in_addr *addr = rule; + struct bgp_path_info *path; + + path = object; + + if (path->attr->nexthop.s_addr == addr->s_addr + || (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV4 + && IPV4_ADDR_SAME(&path->attr->mp_nexthop_global_in, addr))) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv4_next_hop_compile(const char *arg) +{ + struct in_addr *address; + int ret; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); + + ret = inet_pton(AF_INET, arg, address); + if (!ret) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +static void route_match_ipv4_next_hop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ipv4_next_hop_cmd = { + "ip next-hop address", + route_match_ipv4_next_hop, + route_match_ipv4_next_hop_compile, + route_match_ipv4_next_hop_free +}; + +/* `match ipv6 address prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + return route_match_address_prefix_list(rule, AFI_IP6, prefix, object); +} + +static void *route_match_ipv6_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ipv6_address_prefix_list_cmd = { + "ipv6 address prefix-list", + route_match_ipv6_address_prefix_list, + route_match_ipv6_address_prefix_list_compile, + route_match_ipv6_address_prefix_list_free +}; + +/* `match ipv6 next-hop type ' */ + +static enum route_map_cmd_result_t +route_match_ipv6_next_hop_type(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path; + struct in6_addr *addr = rule; + + if (prefix->family == AF_INET6) { + path = (struct bgp_path_info *)object; + if (!path) + return RMAP_NOMATCH; + + if (IPV6_ADDR_SAME(&path->attr->mp_nexthop_global, addr) + && !path->attr->nh_ifindex) + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_next_hop_type_compile(const char *arg) +{ + struct in6_addr *address; + int ret; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + + ret = inet_pton(AF_INET6, "::0", address); + if (!ret) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +static void route_match_ipv6_next_hop_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ipv6_next_hop_type_cmd = { + "ipv6 next-hop type", + route_match_ipv6_next_hop_type, + route_match_ipv6_next_hop_type_compile, + route_match_ipv6_next_hop_type_free +}; + +/* `set ipv6 nexthop global IP_ADDRESS' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ipv6_nexthop_global(void *rule, const struct prefix *p, void *object) +{ + struct in6_addr *address; + struct bgp_path_info *path; + + /* Fetch routemap's rule information. */ + address = rule; + path = object; + + /* Set next hop value. */ + path->attr->mp_nexthop_global = *address; + + /* Set nexthop length. */ + if (path->attr->mp_nexthop_len == 0) + path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED); + + return RMAP_OKAY; +} + +/* Route map `ip next-hop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ipv6_nexthop_global_compile(const char *arg) +{ + int ret; + struct in6_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + + ret = inet_pton(AF_INET6, arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* Free route map's compiled `ip next-hop' value. */ +static void route_set_ipv6_nexthop_global_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd + route_set_ipv6_nexthop_global_cmd = { + "ipv6 next-hop global", + route_set_ipv6_nexthop_global, + route_set_ipv6_nexthop_global_compile, + route_set_ipv6_nexthop_global_free +}; + +/* Set next-hop preference value. */ +static enum route_map_cmd_result_t +route_set_ipv6_nexthop_prefer_global(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path; + struct peer *peer; + + /* Fetch routemap's rule information. */ + path = object; + peer = path->peer; + + if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) { + /* Set next hop preference to global */ + SET_FLAG(path->attr->nh_flags, BGP_ATTR_NH_MP_PREFER_GLOBAL); + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED); + } else { + UNSET_FLAG(path->attr->nh_flags, BGP_ATTR_NH_MP_PREFER_GLOBAL); + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_IPV6_PREFER_GLOBAL_CHANGED); + } + + return RMAP_OKAY; +} + +static void *route_set_ipv6_nexthop_prefer_global_compile(const char *arg) +{ + int *rins = NULL; + + rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); + *rins = 1; + + return rins; +} + +/* Free route map's compiled `ip next-hop' value. */ +static void route_set_ipv6_nexthop_prefer_global_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set preferred. */ +static const struct route_map_rule_cmd + route_set_ipv6_nexthop_prefer_global_cmd = { + "ipv6 next-hop prefer-global", + route_set_ipv6_nexthop_prefer_global, + route_set_ipv6_nexthop_prefer_global_compile, + route_set_ipv6_nexthop_prefer_global_free +}; + +/* `set ipv6 nexthop local IP_ADDRESS' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ipv6_nexthop_local(void *rule, const struct prefix *p, void *object) +{ + struct in6_addr *address; + struct bgp_path_info *path; + struct bgp_dest *dest; + struct bgp_table *table = NULL; + + /* Fetch routemap's rule information. */ + address = rule; + path = object; + dest = path->net; + + if (!dest) + return RMAP_OKAY; + + table = bgp_dest_table(dest); + if (!table) + return RMAP_OKAY; + + /* Set next hop value. */ + path->attr->mp_nexthop_local = *address; + + /* Set nexthop length. */ + if (table->safi == SAFI_MPLS_VPN || table->safi == SAFI_ENCAP || + table->safi == SAFI_EVPN) + path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL; + else + path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; + + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_IPV6_LL_NHOP_CHANGED); + + return RMAP_OKAY; +} + +/* Route map `ip nexthop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ipv6_nexthop_local_compile(const char *arg) +{ + int ret; + struct in6_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + + ret = inet_pton(AF_INET6, arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_ipv6_nexthop_local_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd + route_set_ipv6_nexthop_local_cmd = { + "ipv6 next-hop local", + route_set_ipv6_nexthop_local, + route_set_ipv6_nexthop_local_compile, + route_set_ipv6_nexthop_local_free +}; + +/* `set ipv6 nexthop peer-address' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ipv6_nexthop_peer(void *rule, const struct prefix *pfx, void *object) +{ + struct in6_addr peer_address; + struct bgp_path_info *path; + struct peer *peer; + + /* Fetch routemap's rule information. */ + path = object; + peer = path->peer; + + if ((CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IN) + || CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_IMPORT)) + && peer->su_remote + && sockunion_family(peer->su_remote) == AF_INET6) { + peer_address = peer->su_remote->sin6.sin6_addr; + /* Set next hop value and length in attribute. */ + if (IN6_IS_ADDR_LINKLOCAL(&peer_address)) { + path->attr->mp_nexthop_local = peer_address; + if (path->attr->mp_nexthop_len + != BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + path->attr->mp_nexthop_len = + BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; + } else { + path->attr->mp_nexthop_global = peer_address; + if (path->attr->mp_nexthop_len == 0) + path->attr->mp_nexthop_len = + BGP_ATTR_NHLEN_IPV6_GLOBAL; + } + + } else if (CHECK_FLAG(peer->rmap_type, PEER_RMAP_TYPE_OUT)) { + /* The next hop value will be set as part of packet + * rewrite. + * Set the flags here to indicate that rewrite needs to + * be done. + * Also, clear the value - we clear both global and + * link-local + * nexthops, whether we send one or both is determined + * elsewhere. + */ + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_NEXTHOP_PEER_ADDRESS); + /* clear next hop value. */ + memset(&(path->attr->mp_nexthop_global), 0, + sizeof(struct in6_addr)); + memset(&(path->attr->mp_nexthop_local), 0, + sizeof(struct in6_addr)); + } + + return RMAP_OKAY; +} + +/* Route map `ip next-hop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ipv6_nexthop_peer_compile(const char *arg) +{ + int *rins = NULL; + + rins = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); + *rins = 1; + + return rins; +} + +/* Free route map's compiled `ip next-hop' value. */ +static void route_set_ipv6_nexthop_peer_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd route_set_ipv6_nexthop_peer_cmd = { + "ipv6 next-hop peer-address", + route_set_ipv6_nexthop_peer, + route_set_ipv6_nexthop_peer_compile, + route_set_ipv6_nexthop_peer_free +}; + +/* `set ipv4 vpn next-hop A.B.C.D' */ + +static enum route_map_cmd_result_t +route_set_vpnv4_nexthop(void *rule, const struct prefix *prefix, void *object) +{ + struct in_addr *address; + struct bgp_path_info *path; + + /* Fetch routemap's rule information. */ + address = rule; + path = object; + + /* Set next hop value. */ + path->attr->mp_nexthop_global_in = *address; + path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + + SET_FLAG(path->attr->rmap_change_flags, BATTR_RMAP_VPNV4_NHOP_CHANGED); + + return RMAP_OKAY; +} + +static void *route_set_vpnv4_nexthop_compile(const char *arg) +{ + int ret; + struct in_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); + + ret = inet_aton(arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* `set ipv6 vpn next-hop A.B.C.D' */ + +static enum route_map_cmd_result_t +route_set_vpnv6_nexthop(void *rule, const struct prefix *prefix, void *object) +{ + struct in6_addr *address; + struct bgp_path_info *path; + + /* Fetch routemap's rule information. */ + address = rule; + path = object; + + /* Set next hop value. */ + memcpy(&path->attr->mp_nexthop_global, address, + sizeof(struct in6_addr)); + path->attr->mp_nexthop_len = BGP_ATTR_NHLEN_VPNV6_GLOBAL; + + SET_FLAG(path->attr->rmap_change_flags, + BATTR_RMAP_VPNV6_GLOBAL_NHOP_CHANGED); + + return RMAP_OKAY; +} + +static void *route_set_vpnv6_nexthop_compile(const char *arg) +{ + int ret; + struct in6_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + ret = inet_pton(AF_INET6, arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +static void route_set_vpn_nexthop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ipv4 next-hop set. */ +static const struct route_map_rule_cmd route_set_vpnv4_nexthop_cmd = { + "ipv4 vpn next-hop", + route_set_vpnv4_nexthop, + route_set_vpnv4_nexthop_compile, + route_set_vpn_nexthop_free +}; + +/* Route map commands for ipv6 next-hop set. */ +static const struct route_map_rule_cmd route_set_vpnv6_nexthop_cmd = { + "ipv6 vpn next-hop", + route_set_vpnv6_nexthop, + route_set_vpnv6_nexthop_compile, + route_set_vpn_nexthop_free +}; + +/* `set originator-id' */ + +/* For origin set. */ +static enum route_map_cmd_result_t +route_set_originator_id(void *rule, const struct prefix *prefix, void *object) +{ + struct in_addr *address; + struct bgp_path_info *path; + + address = rule; + path = object; + + path->attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); + path->attr->originator_id = *address; + + return RMAP_OKAY; +} + +/* Compile function for originator-id set. */ +static void *route_set_originator_id_compile(const char *arg) +{ + int ret; + struct in_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); + + ret = inet_aton(arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* Compile function for originator_id set. */ +static void route_set_originator_id_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set originator-id rule structure. */ +static const struct route_map_rule_cmd route_set_originator_id_cmd = { + "originator-id", + route_set_originator_id, + route_set_originator_id_compile, + route_set_originator_id_free, +}; + +static enum route_map_cmd_result_t +route_match_rpki_extcommunity(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path; + struct ecommunity *ecomm; + struct ecommunity_val *ecomm_val; + enum rpki_states *rpki_status = rule; + enum rpki_states ecomm_rpki_status = RPKI_NOT_BEING_USED; + + path = object; + + ecomm = bgp_attr_get_ecommunity(path->attr); + if (!ecomm) + return RMAP_NOMATCH; + + ecomm_val = ecommunity_lookup(ecomm, ECOMMUNITY_ENCODE_OPAQUE_NON_TRANS, + ECOMMUNITY_ORIGIN_VALIDATION_STATE); + if (!ecomm_val) + return RMAP_NOMATCH; + + /* The Origin Validation State is encoded in the last octet of + * the extended community. + */ + switch (ecomm_val->val[7]) { + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_VALID: + ecomm_rpki_status = RPKI_VALID; + break; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTFOUND: + ecomm_rpki_status = RPKI_NOTFOUND; + break; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_INVALID: + ecomm_rpki_status = RPKI_INVALID; + break; + case ECOMMUNITY_ORIGIN_VALIDATION_STATE_NOTUSED: + break; + } + + if (ecomm_rpki_status == *rpki_status) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_extcommunity_compile(const char *arg) +{ + int *rpki_status; + + rpki_status = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); + + if (strcmp(arg, "valid") == 0) + *rpki_status = RPKI_VALID; + else if (strcmp(arg, "invalid") == 0) + *rpki_status = RPKI_INVALID; + else + *rpki_status = RPKI_NOTFOUND; + + return rpki_status; +} + +static const struct route_map_rule_cmd route_match_rpki_extcommunity_cmd = { + "rpki-extcommunity", + route_match_rpki_extcommunity, + route_match_extcommunity_compile, + route_value_free +}; + +/* + * This is the workhorse routine for processing in/out routemap + * modifications. + */ +static void bgp_route_map_process_peer(const char *rmap_name, + struct route_map *map, struct peer *peer, + int afi, int safi, int route_update) +{ + struct bgp_filter *filter; + + if (!peer || !rmap_name) + return; + + filter = &peer->filter[afi][safi]; + /* + * in is for non-route-server clients, + * out is for all peers + */ + if (filter->map[RMAP_IN].name + && (strcmp(rmap_name, filter->map[RMAP_IN].name) == 0)) { + filter->map[RMAP_IN].map = map; + + if (route_update && peer_established(peer->connection)) { + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SOFT_RECONFIG)) { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "Processing route_map %s(%s:%s) update on peer %s (inbound, soft-reconfig)", + rmap_name, afi2str(afi), + safi2str(safi), peer->host); + + bgp_soft_reconfig_in(peer, afi, safi); + } else if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_RCV)) { + if (bgp_debug_update(peer, NULL, NULL, 1)) + zlog_debug( + "Processing route_map %s(%s:%s) update on peer %s (inbound, route-refresh)", + rmap_name, afi2str(afi), + safi2str(safi), peer->host); + bgp_route_refresh_send( + peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_NORMAL); + } + } + } + + /* + * For outbound, unsuppress and default-originate map change (content or + * map created), merely update the "config" here, the actual route + * announcement happens at the group level. + */ + if (filter->map[RMAP_OUT].name + && (strcmp(rmap_name, filter->map[RMAP_OUT].name) == 0)) + filter->map[RMAP_OUT].map = map; + + if (filter->usmap.name && (strcmp(rmap_name, filter->usmap.name) == 0)) + filter->usmap.map = map; + + if (filter->advmap.aname + && (strcmp(rmap_name, filter->advmap.aname) == 0)) { + filter->advmap.amap = map; + } + + if (filter->advmap.cname + && (strcmp(rmap_name, filter->advmap.cname) == 0)) { + filter->advmap.cmap = map; + } + + if (peer->default_rmap[afi][safi].name + && (strcmp(rmap_name, peer->default_rmap[afi][safi].name) == 0)) + peer->default_rmap[afi][safi].map = map; + + /* Notify BGP conditional advertisement scanner percess */ + peer->advmap_config_change[afi][safi] = true; +} + +static void bgp_route_map_update_peer_group(const char *rmap_name, + struct route_map *map, + struct bgp *bgp) +{ + struct peer_group *group; + struct listnode *node, *nnode; + struct bgp_filter *filter; + int afi, safi; + int direct; + + if (!bgp) + return; + + /* All the peers have been updated correctly already. This is + * just updating the placeholder data. No real update required. + */ + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &group->conf->filter[afi][safi]; + + for (direct = RMAP_IN; direct < RMAP_MAX; direct++) { + if ((filter->map[direct].name) + && (strcmp(rmap_name, + filter->map[direct].name) + == 0)) + filter->map[direct].map = map; + + if (group->conf->default_rmap[afi][safi].name && + strmatch(group->conf->default_rmap[afi][safi] + .name, + rmap_name)) + group->conf->default_rmap[afi][safi].map = + map; + } + + if (filter->usmap.name + && (strcmp(rmap_name, filter->usmap.name) == 0)) + filter->usmap.map = map; + + if (filter->advmap.aname && + (strcmp(rmap_name, filter->advmap.aname) == 0)) + filter->advmap.amap = map; + + if (filter->advmap.cname && + (strcmp(rmap_name, filter->advmap.cname) == 0)) + filter->advmap.cmap = map; + } + } +} + +/* + * Note that if an extreme number (tens of thousands) of route-maps are in use + * and if bgp has an extreme number of peers, network statements, etc then this + * function can consume a lot of cycles. This is due to this function being + * called for each route-map and within this function we walk the list of peers, + * network statements, etc looking to see if they use this route-map. + */ +static void bgp_route_map_process_update(struct bgp *bgp, const char *rmap_name, + bool route_update) +{ + int i; + bool matched; + afi_t afi; + safi_t safi; + struct peer *peer; + struct bgp_dest *bn; + struct bgp_static *bgp_static; + struct bgp_aggregate *aggregate; + struct listnode *node, *nnode; + struct route_map *map; + char buf[INET6_ADDRSTRLEN]; + + map = route_map_lookup_by_name(rmap_name); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + + /* Ignore dummy peer-group structure */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + continue; + + FOREACH_AFI_SAFI (afi, safi) { + /* process in/out/import/export/default-orig + * route-maps */ + bgp_route_map_process_peer(rmap_name, map, peer, afi, + safi, route_update); + } + } + + /* for outbound/default-orig route-maps, process for groups */ + update_group_policy_update(bgp, BGP_POLICY_ROUTE_MAP, rmap_name, + route_update, 0); + + /* update peer-group config (template) */ + bgp_route_map_update_peer_group(rmap_name, map, bgp); + + FOREACH_AFI_SAFI (afi, safi) { + /* For table route-map updates. */ + if (!bgp_fibupd_safi(safi)) + continue; + + if (bgp->table_map[afi][safi].name + && (strcmp(rmap_name, bgp->table_map[afi][safi].name) + == 0)) { + + /* bgp->table_map[afi][safi].map is NULL. + * i.e Route map creation event. + * So update applied_counter. + * If it is not NULL, i.e It may be routemap updation or + * deletion. so no need to update the counter. + */ + if (!bgp->table_map[afi][safi].map) + route_map_counter_increment(map); + bgp->table_map[afi][safi].map = map; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Processing route_map %s(%s:%s) update on table map", + rmap_name, afi2str(afi), + safi2str(safi)); + if (route_update) + bgp_zebra_announce_table(bgp, afi, safi); + } + + /* For network route-map updates. */ + for (bn = bgp_table_top(bgp->route[afi][safi]); bn; + bn = bgp_route_next(bn)) { + bgp_static = bgp_dest_get_bgp_static_info(bn); + if (!bgp_static) + continue; + + if (!bgp_static->rmap.name + || (strcmp(rmap_name, bgp_static->rmap.name) != 0)) + continue; + + if (!bgp_static->rmap.map) + route_map_counter_increment(map); + + bgp_static->rmap.map = map; + + if (route_update && !bgp_static->backdoor) { + const struct prefix *bn_p = + bgp_dest_get_prefix(bn); + + if (bgp_debug_zebra(bn_p)) + zlog_debug( + "Processing route_map %s(%s:%s) update on static route %s", + rmap_name, afi2str(afi), + safi2str(safi), + inet_ntop(bn_p->family, + &bn_p->u.prefix, buf, + sizeof(buf))); + bgp_static_update(bgp, bn_p, bgp_static, afi, + safi); + } + } + + /* For aggregate-address route-map updates. */ + for (bn = bgp_table_top(bgp->aggregate[afi][safi]); bn; + bn = bgp_route_next(bn)) { + aggregate = bgp_dest_get_bgp_aggregate_info(bn); + if (!aggregate) + continue; + + matched = false; + + /* Update suppress map pointer. */ + if (aggregate->suppress_map_name + && strmatch(aggregate->suppress_map_name, + rmap_name)) { + if (aggregate->rmap.map == NULL) + route_map_counter_increment(map); + + aggregate->suppress_map = map; + + bgp_aggregate_toggle_suppressed( + aggregate, bgp, bgp_dest_get_prefix(bn), + afi, safi, false); + + matched = true; + } + + if (aggregate->rmap.name + && strmatch(rmap_name, aggregate->rmap.name)) { + if (aggregate->rmap.map == NULL) + route_map_counter_increment(map); + + aggregate->rmap.map = map; + aggregate->rmap.changed = true; + + matched = true; + } + + if (matched && route_update) { + const struct prefix *bn_p = + bgp_dest_get_prefix(bn); + + if (bgp_debug_zebra(bn_p)) + zlog_debug( + "Processing route_map %s(%s:%s) update on aggregate-address route %s", + rmap_name, afi2str(afi), + safi2str(safi), + inet_ntop(bn_p->family, + &bn_p->u.prefix, buf, + sizeof(buf))); + (void)bgp_aggregate_route(bgp, bn_p, afi, safi, + aggregate); + } + } + } + + /* For redistribute route-map updates. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + struct list *red_list; + struct bgp_redist *red; + + red_list = bgp->redist[afi][i]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + if (!red->rmap.name + || (strcmp(rmap_name, red->rmap.name) != 0)) + continue; + + if (!red->rmap.map) + route_map_counter_increment(map); + + red->rmap.map = map; + + if (!route_update) + continue; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Processing route_map %s(%s:%s) update on redistributed routes", + rmap_name, afi2str(afi), + safi2str(safi)); + + bgp_redistribute_resend(bgp, afi, i, + red->instance); + } + } + + /* for type5 command route-maps */ + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp->adv_cmd_rmap[afi][safi].name + || strcmp(rmap_name, bgp->adv_cmd_rmap[afi][safi].name) + != 0) + continue; + + /* Make sure the route-map is populated here if not already done */ + bgp->adv_cmd_rmap[afi][safi].map = map; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Processing route_map %s(%s:%s) update on advertise type5 route command", + rmap_name, afi2str(afi), safi2str(safi)); + + if (route_update && advertise_type5_routes(bgp, afi)) { + bgp_evpn_withdraw_type5_routes(bgp, afi, safi); + bgp_evpn_advertise_type5_routes(bgp, afi, safi); + } + } +} + +static void bgp_route_map_process_update_cb(char *rmap_name) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + bgp_route_map_process_update(bgp, rmap_name, true); + +#ifdef ENABLE_BGP_VNC + vnc_routemap_update(bgp, __func__); +#endif + } + + vpn_policy_routemap_event(rmap_name); +} + +void bgp_route_map_update_timer(struct event *thread) +{ + route_map_walk_update_list(bgp_route_map_process_update_cb); +} + +static void bgp_route_map_mark_update(const char *rmap_name) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + /* If new update is received before the current timer timed out, + * turn it off and start a new timer. + */ + EVENT_OFF(bm->t_rmap_update); + + /* rmap_update_timer of 0 means don't do route updates */ + if (bm->rmap_update_timer) { + event_add_timer(bm->master, bgp_route_map_update_timer, NULL, + bm->rmap_update_timer, &bm->t_rmap_update); + + /* Signal the groups that a route-map update event has + * started */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + update_group_policy_update(bgp, BGP_POLICY_ROUTE_MAP, + rmap_name, true, 1); + } else { + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + bgp_route_map_process_update(bgp, rmap_name, false); +#ifdef ENABLE_BGP_VNC + vnc_routemap_update(bgp, __func__); +#endif + } + + vpn_policy_routemap_event(rmap_name); + } +} + +static void bgp_route_map_add(const char *rmap_name) +{ + if (route_map_mark_updated(rmap_name) == 0) + bgp_route_map_mark_update(rmap_name); + + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); +} + +static void bgp_route_map_delete(const char *rmap_name) +{ + if (route_map_mark_updated(rmap_name) == 0) + bgp_route_map_mark_update(rmap_name); + + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED); +} + +bool bgp_route_map_has_extcommunity_rt(const struct route_map *map) +{ + struct route_map_index *index = NULL; + struct route_map_rule *set = NULL; + + assert(map); + + for (index = map->head; index; index = index->next) { + for (set = index->set_list.head; set; set = set->next) { + if (set->cmd && set->cmd->str && + (strmatch(set->cmd->str, "extcommunity rt") || + strmatch(set->cmd->str, "extended-comm-list"))) + return true; + } + } + return false; +} + +static void bgp_route_map_event(const char *rmap_name) +{ + if (route_map_mark_updated(rmap_name) == 0) + bgp_route_map_mark_update(rmap_name); + + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); +} + +DEFUN_YANG (match_mac_address, + match_mac_address_cmd, + "match mac address ACCESSLIST_MAC_NAME", + MATCH_STR + "mac address\n" + "Match address of route\n" + "MAC Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:mac-address-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[3]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_mac_address, + no_match_mac_address_cmd, + "no match mac address ACCESSLIST_MAC_NAME", + NO_STR + MATCH_STR + "mac\n" + "Match address of route\n" + "MAC acess-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:mac-address-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* + * Helper to handle the case of the user passing in a number or type string + */ +static const char *parse_evpn_rt_type(const char *num_rt_type) +{ + switch (num_rt_type[0]) { + case '1': + return "ead"; + case '2': + return "macip"; + case '3': + return "multicast"; + case '4': + return "es"; + case '5': + return "prefix"; + default: + break; + } + + /* Was already full type string */ + return num_rt_type; +} + +DEFUN_YANG (match_evpn_route_type, + match_evpn_route_type_cmd, + "match evpn route-type ", + MATCH_STR + EVPN_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_4_HELP_STR + EVPN_TYPE_4_HELP_STR + EVPN_TYPE_5_HELP_STR + EVPN_TYPE_5_HELP_STR) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-route-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:evpn-route-type", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + parse_evpn_rt_type(argv[3]->arg)); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_evpn_route_type, + no_match_evpn_route_type_cmd, + "no match evpn route-type ", + NO_STR + MATCH_STR + EVPN_HELP_STR + EVPN_TYPE_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_1_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_2_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_3_HELP_STR + EVPN_TYPE_4_HELP_STR + EVPN_TYPE_4_HELP_STR + EVPN_TYPE_5_HELP_STR + EVPN_TYPE_5_HELP_STR) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-route-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (match_evpn_vni, + match_evpn_vni_cmd, + "match evpn vni " CMD_VNI_RANGE, + MATCH_STR + EVPN_HELP_STR + "Match VNI\n" + "VNI ID\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-vni']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:evpn-vni", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[3]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_evpn_vni, + no_match_evpn_vni_cmd, + "no match evpn vni " CMD_VNI_RANGE, + NO_STR + MATCH_STR + EVPN_HELP_STR + "Match VNI\n" + "VNI ID\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-vni']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:evpn-vni", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, argv[3]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_evpn_default_route, + match_evpn_default_route_cmd, + "match evpn default-route", + MATCH_STR + EVPN_HELP_STR + "default EVPN type-5 route\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-default-route']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:evpn-default-route", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_evpn_default_route, + no_match_evpn_default_route_cmd, + "no match evpn default-route", + NO_STR + MATCH_STR + EVPN_HELP_STR + "default EVPN type-5 route\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-default-route']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_evpn_rd, + match_evpn_rd_cmd, + "match evpn rd ASN:NN_OR_IP-ADDRESS:NN", + MATCH_STR + EVPN_HELP_STR + "Route Distinguisher\n" + "ASN:XX or A.B.C.D:XX\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-rd']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:route-distinguisher", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[3]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_evpn_rd, + no_match_evpn_rd_cmd, + "no match evpn rd ASN:NN_OR_IP-ADDRESS:NN", + NO_STR + MATCH_STR + EVPN_HELP_STR + "Route Distinguisher\n" + "ASN:XX or A.B.C.D:XX\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:evpn-rd']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_evpn_gw_ip_ipv4, + set_evpn_gw_ip_ipv4_cmd, + "set evpn gateway-ip ipv4 A.B.C.D", + SET_STR + EVPN_HELP_STR + "Set gateway IP for prefix advertisement route\n" + "IPv4 address\n" + "Gateway IP address in IPv4 format\n") +{ + int ret; + union sockunion su; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-evpn-gateway-ip-ipv4']"; + char xpath_value[XPATH_MAXLEN]; + + ret = str2sockunion(argv[4]->arg, &su); + if (ret < 0) { + vty_out(vty, "%% Malformed gateway IP\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (su.sin.sin_addr.s_addr == 0 || + !ipv4_unicast_valid(&su.sin.sin_addr)) { + vty_out(vty, + "%% Gateway IP cannot be 0.0.0.0, multicast or reserved\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv4", + xpath); + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[4]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_evpn_gw_ip_ipv4, + no_set_evpn_gw_ip_ipv4_cmd, + "no set evpn gateway-ip ipv4 A.B.C.D", + NO_STR + SET_STR + EVPN_HELP_STR + "Set gateway IP for prefix advertisement route\n" + "IPv4 address\n" + "Gateway IP address in IPv4 format\n") +{ + int ret; + union sockunion su; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-evpn-gateway-ip-ipv4']"; + + ret = str2sockunion(argv[5]->arg, &su); + if (ret < 0) { + vty_out(vty, "%% Malformed gateway IP\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (su.sin.sin_addr.s_addr == 0 || + !ipv4_unicast_valid(&su.sin.sin_addr)) { + vty_out(vty, + "%% Gateway IP cannot be 0.0.0.0, multicast or reserved\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_evpn_gw_ip_ipv6, + set_evpn_gw_ip_ipv6_cmd, + "set evpn gateway-ip ipv6 X:X::X:X", + SET_STR + EVPN_HELP_STR + "Set gateway IP for prefix advertisement route\n" + "IPv6 address\n" + "Gateway IP address in IPv6 format\n") +{ + int ret; + union sockunion su; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-evpn-gateway-ip-ipv6']"; + char xpath_value[XPATH_MAXLEN]; + + ret = str2sockunion(argv[4]->arg, &su); + if (ret < 0) { + vty_out(vty, "%% Malformed gateway IP\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) + || IN6_IS_ADDR_MULTICAST(&su.sin6.sin6_addr)) { + vty_out(vty, + "%% Gateway IP cannot be a linklocal or multicast address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv6", + xpath); + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[4]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_evpn_gw_ip_ipv6, + no_set_evpn_gw_ip_ipv6_cmd, + "no set evpn gateway-ip ipv6 X:X::X:X", + NO_STR + SET_STR + EVPN_HELP_STR + "Set gateway IP for prefix advertisement route\n" + "IPv4 address\n" + "Gateway IP address in IPv4 format\n") +{ + int ret; + union sockunion su; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-evpn-gateway-ip-ipv6']"; + + ret = str2sockunion(argv[5]->arg, &su); + if (ret < 0) { + vty_out(vty, "%% Malformed gateway IP\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) + || IN6_IS_ADDR_MULTICAST(&su.sin6.sin6_addr)) { + vty_out(vty, + "%% Gateway IP cannot be a linklocal or multicast address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(match_vrl_source_vrf, + match_vrl_source_vrf_cmd, + "match source-vrf NAME$vrf_name", + MATCH_STR + "source vrf\n" + "The VRF name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-vrf']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:source-vrf", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, vrf_name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_match_vrl_source_vrf, + no_match_vrl_source_vrf_cmd, + "no match source-vrf NAME$vrf_name", + NO_STR MATCH_STR + "source vrf\n" + "The VRF name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-vrf']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (match_peer, + match_peer_cmd, + "match peer ", + MATCH_STR + "Match peer address\n" + "IP address of peer\n" + "IPv6 address of peer\n" + "Interface name of peer or peer group name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:peer']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:peer-ipv4-address", + xpath); + nb_cli_enqueue_change(vty, xpath_value, + addrv4_str ? NB_OP_MODIFY : NB_OP_DESTROY, + addrv4_str); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:peer-ipv6-address", + xpath); + nb_cli_enqueue_change(vty, xpath_value, + addrv6_str ? NB_OP_MODIFY : NB_OP_DESTROY, + addrv6_str); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:peer-interface", + xpath); + nb_cli_enqueue_change(vty, xpath_value, + intf ? NB_OP_MODIFY : NB_OP_DESTROY, intf); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_peer_local, + match_peer_local_cmd, + "match peer local", + MATCH_STR + "Match peer address\n" + "Static or Redistributed routes\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:peer']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:peer-local", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_peer, + no_match_peer_cmd, + "no match peer []", + NO_STR + MATCH_STR + "Match peer address\n" + "Static or Redistributed routes\n" + "IP address of peer\n" + "IPv6 address of peer\n" + "Interface name of peer\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:peer']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +#ifdef HAVE_SCRIPTING +DEFUN_YANG (match_script, + match_script_cmd, + "[no] match script WORD", + NO_STR + MATCH_STR + "Execute script to determine match\n" + "The script name to run, without .lua; e.g. 'myroutemap' to run myroutemap.lua\n") +{ + bool no = strmatch(argv[0]->text, "no"); + int i = 0; + argv_find(argv, argc, "WORD", &i); + const char *script = argv[i]->arg; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-script']"; + char xpath_value[XPATH_MAXLEN]; + + if (no) { + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:script", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, + script); + + return nb_cli_apply_changes(vty, NULL); + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:script", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + script); + + return nb_cli_apply_changes(vty, NULL); +} +#endif /* HAVE_SCRIPTING */ + +/* match probability */ +DEFUN_YANG (match_probability, + match_probability_cmd, + "match probability (0-100)", + MATCH_STR + "Match portion of routes defined by percentage value\n" + "Percentage of routes\n") +{ + int idx_number = 2; + + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:probability']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:probability", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_probability, + no_match_probability_cmd, + "no match probability [(1-99)]", + NO_STR + MATCH_STR + "Match portion of routes defined by percentage value\n" + "Percentage of routes\n") +{ + int idx_number = 3; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:probability']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + if (argc <= idx_number) + return nb_cli_apply_changes(vty, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:probability", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, + argv[idx_number]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFPY_YANG (match_ip_route_source, + match_ip_route_source_cmd, + "match ip route-source ACCESSLIST4_NAME", + MATCH_STR + IP_STR + "Match advertising source address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ip-route-source']"; + char xpath_value[XPATH_MAXLEN + 32]; + int idx_acl = 3; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_acl]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_ip_route_source, + no_match_ip_route_source_cmd, + "no match ip route-source [ACCESSLIST4_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match advertising source address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ip-route-source']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_ip_route_source_prefix_list, + match_ip_route_source_prefix_list_cmd, + "match ip route-source prefix-list PREFIXLIST_NAME", + MATCH_STR + IP_STR + "Match advertising source address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + int idx_word = 4; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ip-route-source-prefix-list']"; + char xpath_value[XPATH_MAXLEN + 32]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_word]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_ip_route_source_prefix_list, + no_match_ip_route_source_prefix_list_cmd, + "no match ip route-source prefix-list [PREFIXLIST_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match advertising source address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ip-route-source-prefix-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_local_pref, + match_local_pref_cmd, + "match local-preference (0-4294967295)", + MATCH_STR + "Match local-preference of route\n" + "Metric value\n") +{ + int idx_number = 2; + + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-local-preference']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:local-preference", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_local_pref, + no_match_local_pref_cmd, + "no match local-preference [(0-4294967295)]", + NO_STR + MATCH_STR + "Match local preference of route\n" + "Local preference value\n") +{ + int idx_localpref = 3; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-local-preference']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + if (argc <= idx_localpref) + return nb_cli_apply_changes(vty, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:local-preference", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, + argv[idx_localpref]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG(match_alias, match_alias_cmd, "match alias ALIAS_NAME", + MATCH_STR + "Match BGP community alias name\n" + "BGP community alias name\n") +{ + const char *alias = argv[2]->arg; + struct community_alias ca1; + struct community_alias *lookup_alias; + + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-alias']"; + char xpath_value[XPATH_MAXLEN]; + + memset(&ca1, 0, sizeof(ca1)); + strlcpy(ca1.alias, alias, sizeof(ca1.alias)); + lookup_alias = bgp_ca_alias_lookup(&ca1); + if (!lookup_alias) { + vty_out(vty, "%% BGP alias name '%s' does not exist\n", alias); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:alias", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, alias); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG(no_match_alias, no_match_alias_cmd, "no match alias [ALIAS_NAME]", + NO_STR MATCH_STR + "Match BGP community alias name\n" + "BGP community alias name\n") +{ + int idx_alias = 3; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-alias']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + if (argc <= idx_alias) + return nb_cli_apply_changes(vty, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:alias", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, + argv[idx_alias]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_community, match_community_cmd, + "match community <(1-99)|(100-500)|COMMUNITY_LIST_NAME> []", + MATCH_STR "Match BGP community list\n" + "Community-list number (standard)\n" + "Community-list number (expanded)\n" + "Community-list name\n" + "Do exact matching of communities\n" + "Do matching of any community\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-community']"; + char xpath_value[XPATH_MAXLEN]; + char xpath_match[XPATH_MAXLEN]; + int idx_comm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[idx_comm_list]->arg); + + snprintf(xpath_match, sizeof(xpath_match), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match", + xpath); + if (exact) + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, + "true"); + else + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + + snprintf(xpath_match, sizeof(xpath_match), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any", + xpath); + if (any) + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "true"); + else + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG( + no_match_community, no_match_community_cmd, + "no match community [<(1-99)|(100-500)|COMMUNITY_LIST_NAME> []]", + NO_STR MATCH_STR "Match BGP community list\n" + "Community-list number (standard)\n" + "Community-list number (expanded)\n" + "Community-list name\n" + "Do exact matching of communities\n" + "Do matching of any community\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-community']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_lcommunity, match_lcommunity_cmd, + "match large-community <(1-99)|(100-500)|LCOMMUNITY_LIST_NAME> []", + MATCH_STR "Match BGP large community list\n" + "Large Community-list number (standard)\n" + "Large Community-list number (expanded)\n" + "Large Community-list name\n" + "Do exact matching of communities\n" + "Do matching of any community\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-large-community']"; + char xpath_value[XPATH_MAXLEN]; + char xpath_match[XPATH_MAXLEN]; + int idx_lcomm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[idx_lcomm_list]->arg); + + snprintf(xpath_match, sizeof(xpath_match), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match", + xpath); + if (exact) + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, + "true"); + else + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + + snprintf(xpath_match, sizeof(xpath_match), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any", + xpath); + if (any) + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "true"); + else + nb_cli_enqueue_change(vty, xpath_match, NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG( + no_match_lcommunity, no_match_lcommunity_cmd, + "no match large-community [<(1-99)|(100-500)|LCOMMUNITY_LIST_NAME> []]", + NO_STR MATCH_STR "Match BGP large community list\n" + "Large Community-list number (standard)\n" + "Large Community-list number (expanded)\n" + "Large Community-list name\n" + "Do exact matching of communities\n" + "Do matching of any community\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-large-community']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (match_ecommunity, + match_ecommunity_cmd, + "match extcommunity <(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME>", + MATCH_STR + "Match BGP/VPN extended community list\n" + "Extended community-list number (standard)\n" + "Extended community-list number (expanded)\n" + "Extended community-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-extcommunity']"; + char xpath_value[XPATH_MAXLEN]; + int idx_comm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[idx_comm_list]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_ecommunity, + no_match_ecommunity_cmd, + "no match extcommunity [<(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME>]", + NO_STR + MATCH_STR + "Match BGP/VPN extended community list\n" + "Extended community-list number (standard)\n" + "Extended community-list number (expanded)\n" + "Extended community-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-extcommunity']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + + +DEFPY_YANG (set_ecommunity_delete, + set_ecommunity_delete_cmd, + "set extended-comm-list " EXTCOMM_LIST_CMD_STR " delete", + SET_STR + "set BGP extended community list (for deletion)\n" + EXTCOMM_STD_LIST_NUM_STR + EXTCOMM_EXP_LIST_NUM_STR + EXTCOMM_LIST_NAME_STR + "Delete matching extended communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:extended-comm-list-delete']"; + char xpath_value[XPATH_MAXLEN]; + int idx_comm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_comm_list]->arg); + return nb_cli_apply_changes(vty, NULL); +} + + +DEFPY_YANG (no_set_ecommunity_delete, + no_set_ecommunity_delete_cmd, + "no set extended-comm-list [" EXTCOMM_LIST_CMD_STR "] delete", + NO_STR + SET_STR + "set BGP extended community list (for deletion)\n" + EXTCOMM_STD_LIST_NUM_STR + EXTCOMM_EXP_LIST_NUM_STR + EXTCOMM_LIST_NAME_STR + "Delete matching extended communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:extended-comm-list-delete']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (match_aspath, + match_aspath_cmd, + "match as-path AS_PATH_FILTER_NAME", + MATCH_STR + "Match BGP AS path list\n" + "AS path access-list name\n") +{ + int idx_word = 2; + + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:as-path-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_word]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_aspath, + no_match_aspath_cmd, + "no match as-path [AS_PATH_FILTER_NAME]", + NO_STR + MATCH_STR + "Match BGP AS path list\n" + "AS path access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:as-path-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_origin, + match_origin_cmd, + "match origin ", + MATCH_STR + "BGP origin code\n" + "remote EGP\n" + "local IGP\n" + "unknown heritage\n") +{ + int idx_origin = 2; + const char *origin_type; + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-origin']"; + char xpath_value[XPATH_MAXLEN]; + + if (strncmp(argv[idx_origin]->arg, "igp", 2) == 0) + origin_type = "igp"; + else if (strncmp(argv[idx_origin]->arg, "egp", 1) == 0) + origin_type = "egp"; + else if (strncmp(argv[idx_origin]->arg, "incomplete", 2) == 0) + origin_type = "incomplete"; + else { + vty_out(vty, "%% Invalid match origin type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:origin", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, origin_type); + + return nb_cli_apply_changes(vty, NULL); +} + + +DEFUN_YANG (no_match_origin, + no_match_origin_cmd, + "no match origin []", + NO_STR + MATCH_STR + "BGP origin code\n" + "remote EGP\n" + "local IGP\n" + "unknown heritage\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:match-origin']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_table_id, + set_table_id_cmd, + "set table (1-4294967295)", + SET_STR + "export route to non-main kernel table\n" + "Kernel routing table id\n") +{ + int idx_number = 2; + const char *xpath = "./set-action[action='frr-bgp-route-map:table']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:table", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_table_id, + no_set_table_id_cmd, + "no set table [(1-4294967295)]", + NO_STR + SET_STR + "export route to non-main kernel table\n" + "Kernel routing table id\n") +{ + const char *xpath = "./set-action[action='frr-bgp-route-map:table']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ip_nexthop_peer, + set_ip_nexthop_peer_cmd, + "[no] set ip next-hop peer-address", + NO_STR + SET_STR + IP_STR + "Next hop address\n" + "Use peer address (for BGP only)\n") +{ + char xpath_value[XPATH_MAXLEN]; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-ipv4-nexthop']"; + + if (strmatch(argv[0]->text, "no")) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv4-nexthop", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + "peer-address"); + } + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ip_nexthop_unchanged, + set_ip_nexthop_unchanged_cmd, + "[no] set ip next-hop unchanged", + NO_STR + SET_STR + IP_STR + "Next hop address\n" + "Don't modify existing Next hop address\n") +{ + char xpath_value[XPATH_MAXLEN]; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-ipv4-nexthop']"; + + if (strmatch(argv[0]->text, "no")) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv4-nexthop", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + "unchanged"); + } + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_distance, + set_distance_cmd, + "set distance (1-255)", + SET_STR + "BGP Administrative Distance to use\n" + "Distance value\n") +{ + int idx_number = 2; + const char *xpath = "./set-action[action='frr-bgp-route-map:distance']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:distance", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_distance, + no_set_distance_cmd, + "no set distance [(1-255)]", + NO_STR SET_STR + "BGP Administrative Distance to use\n" + "Distance value\n") +{ + const char *xpath = "./set-action[action='frr-bgp-route-map:distance']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_l3vpn_nexthop_encapsulation, set_l3vpn_nexthop_encapsulation_cmd, + "[no] set l3vpn next-hop encapsulation gre", + NO_STR SET_STR + "L3VPN operations\n" + "Next hop Information\n" + "Encapsulation options (for BGP only)\n" + "Accept L3VPN traffic over GRE encapsulation\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-l3vpn-nexthop-encapsulation']"; + const char *xpath_value = + "./set-action[action='frr-bgp-route-map:set-l3vpn-nexthop-encapsulation']/rmap-set-action/frr-bgp-route-map:l3vpn-nexthop-encapsulation"; + enum nb_operation operation; + + if (no) + operation = NB_OP_DESTROY; + else + operation = NB_OP_CREATE; + + nb_cli_enqueue_change(vty, xpath, operation, NULL); + if (operation == NB_OP_DESTROY) + return nb_cli_apply_changes(vty, NULL); + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "gre"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_local_pref, + set_local_pref_cmd, + "set local-preference WORD", + SET_STR + "BGP local preference path attribute\n" + "Preference value (0-4294967295)\n") +{ + int idx_number = 2; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-local-preference']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:local-pref", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_local_pref, + no_set_local_pref_cmd, + "no set local-preference [WORD]", + NO_STR + SET_STR + "BGP local preference path attribute\n" + "Preference value (0-4294967295)\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-local-preference']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_weight, + set_weight_cmd, + "set weight (0-4294967295)", + SET_STR + "BGP weight for routing table\n" + "Weight value\n") +{ + int idx_number = 2; + const char *xpath = "./set-action[action='frr-bgp-route-map:weight']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:weight", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_weight, + no_set_weight_cmd, + "no set weight [(0-4294967295)]", + NO_STR + SET_STR + "BGP weight for routing table\n" + "Weight value\n") +{ + const char *xpath = "./set-action[action='frr-bgp-route-map:weight']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_label_index, + set_label_index_cmd, + "set label-index (0-1048560)", + SET_STR + "Label index to associate with the prefix\n" + "Label index value\n") +{ + int idx_number = 2; + const char *xpath = + "./set-action[action='frr-bgp-route-map:label-index']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:label-index", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_number]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_label_index, + no_set_label_index_cmd, + "no set label-index [(0-1048560)]", + NO_STR + SET_STR + "Label index to associate with the prefix\n" + "Label index value\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:label-index']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_aspath_prepend_asn, + set_aspath_prepend_asn_cmd, + "set as-path prepend ASNUM...", + SET_STR + "Transform BGP AS_PATH attribute\n" + "Prepend to the as-path\n" + AS_STR) +{ + int idx_asn = 3; + int ret; + char *str; + struct aspath *aspath; + + str = argv_concat(argv, argc, idx_asn); + + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-prepend']"; + char xpath_value[XPATH_MAXLEN]; + + aspath = route_aspath_compile(str); + if (!aspath) { + vty_out(vty, "%% Invalid AS path value %s\n", str); + return CMD_WARNING_CONFIG_FAILED; + } + route_aspath_free(aspath); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:prepend-as-path", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFUN_YANG (set_aspath_prepend_lastas, + set_aspath_prepend_lastas_cmd, + "set as-path prepend last-as (1-10)", + SET_STR + "Transform BGP AS_PATH attribute\n" + "Prepend to the as-path\n" + "Use the last AS-number in the as-path\n" + "Number of times to insert\n") +{ + int idx_num = 4; + + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-prepend']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:last-as", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_num]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_aspath_replace_asn, set_aspath_replace_asn_cmd, + "set as-path replace $replace [$configured_asn]", + SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Replace any AS number to local or configured AS number\n" + "Replace a specific AS number to local or configured AS number\n" + "Define the configured AS number\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + char xpath_value[XPATH_MAXLEN]; + as_t as_value, as_configured_value; + char replace_value[ASN_STRING_MAX_SIZE * 2]; + + if (!strmatch(replace, "any") && !asn_str2asn(replace, &as_value)) { + vty_out(vty, "%% Invalid AS value %s\n", replace); + return CMD_WARNING_CONFIG_FAILED; + } + if (configured_asn_str && + !asn_str2asn(configured_asn_str, &as_configured_value)) { + vty_out(vty, "%% Invalid AS configured value %s\n", + configured_asn_str); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:replace-as-path", xpath); + snprintf(replace_value, sizeof(replace_value), "%s%s%s", replace, + configured_asn_str ? " " : "", + configured_asn_str ? configured_asn_str : ""); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, replace_value); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_aspath_replace_asn, no_set_aspath_replace_asn_cmd, + "no set as-path replace [] [$configured_asn]", + NO_STR SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Replace any AS number to local or configured AS number\n" + "Replace a specific AS number to local or configured AS number\n" + "Define the configured AS number\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + set_aspath_replace_access_list, set_aspath_replace_access_list_cmd, + "set as-path replace as-path-access-list AS_PATH_FILTER_NAME$aspath_filter_name [$configured_asn]", + SET_STR + "Transform BGP AS-path attribute\n" + "Replace AS number to local or configured AS number\n" + "Specify an as path access list name\n" + "AS path access list name\n" + "Define the configured AS number\n") +{ + char *str; + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + char xpath_value[XPATH_MAXLEN]; + as_t as_configured_value; + char replace_value[ASN_STRING_MAX_SIZE * 2]; + int ret; + + if (configured_asn_str && + !asn_str2asn(configured_asn_str, &as_configured_value)) { + vty_out(vty, "%% Invalid AS configured value %s\n", + configured_asn_str); + return CMD_WARNING_CONFIG_FAILED; + } + + str = argv_concat(argv, argc, 3); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(replace_value, sizeof(replace_value), "%s %s", aspath_filter_name, str); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:replace-as-path", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFPY_YANG( + no_set_aspath_replace_access_list, no_set_aspath_replace_access_list_cmd, + "no set as-path replace as-path-access-list [AS_PATH_FILTER_NAME] [$configured_asn]", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Replace AS number to local or configured AS number\n" + "Specify an as path access list name\n" + "AS path access list name\n" + "Define the configured AS number\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-replace']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_aspath_prepend, + no_set_aspath_prepend_last_as_cmd, + "no set as-path prepend [last-as [(1-10)]]", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Prepend to the as-path\n" + "Use the peers AS-number\n" + "Number of times to insert\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-prepend']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_aspath_prepend, + no_set_aspath_prepend_as_cmd, + "no set as-path prepend ASNUM...", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Prepend to the as-path\n" + AS_STR) + +DEFUN_YANG (set_aspath_exclude, + set_aspath_exclude_cmd, + "set as-path exclude ASNUM...", + SET_STR + "Transform BGP AS-path attribute\n" + "Exclude from the as-path\n" + AS_STR) +{ + int idx_asn = 3; + int ret; + char *str; + struct aspath *aspath; + + str = argv_concat(argv, argc, idx_asn); + + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-exclude']"; + char xpath_value[XPATH_MAXLEN]; + + aspath = route_aspath_compile(str); + if (!aspath) { + vty_out(vty, "%% Invalid AS path value %s\n", str); + return CMD_WARNING_CONFIG_FAILED; + } + route_aspath_free(aspath); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:exclude-as-path", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFPY_YANG(set_aspath_exclude_all, set_aspath_exclude_all_cmd, + "[no$no] set as-path exclude all$all", + NO_STR SET_STR + "Transform BGP AS-path attribute\n" + "Exclude from the as-path\n" + "Exclude all AS numbers from the as-path\n") +{ + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-exclude']"; + char xpath_value[XPATH_MAXLEN]; + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:exclude-as-path", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, all); + } + ret = nb_cli_apply_changes(vty, NULL); + + return ret; +} + +DEFUN_YANG (no_set_aspath_exclude, + no_set_aspath_exclude_cmd, + "no set as-path exclude ASNUM...", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Exclude from the as-path\n" + "AS number\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-exclude']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_aspath_exclude_access_list, set_aspath_exclude_access_list_cmd, + "set as-path exclude as-path-access-list AS_PATH_FILTER_NAME", + SET_STR + "Transform BGP AS-path attribute\n" + "Exclude from the as-path\n" + "Specify an as path access list name\n" + "AS path access list name\n") +{ + char *str; + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-exclude']"; + char xpath_value[XPATH_MAXLEN]; + int ret; + + str = argv_concat(argv, argc, 3); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:exclude-as-path", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFPY_YANG(no_set_aspath_exclude_access_list, no_set_aspath_exclude_access_list_cmd, + "no set as-path exclude as-path-access-list [AS_PATH_FILTER_NAME]", + NO_STR + SET_STR + "Transform BGP AS_PATH attribute\n" + "Exclude from the as-path\n" + "Specify an as path access list name\n" + "AS path access list name\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:as-path-exclude']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_aspath_exclude, no_set_aspath_exclude_all_cmd, + "no set as-path exclude", + NO_STR SET_STR + "Transform BGP AS_PATH attribute\n" + "Exclude from the as-path\n") + +DEFUN_YANG (set_community, + set_community_cmd, + "set community AA:NN...", + SET_STR + "BGP community attribute\n" + COMMUNITY_VAL_STR) +{ + int idx_aa_nn = 2; + int i; + int first = 0; + int additive = 0; + struct buffer *b; + struct community *com = NULL; + char *str; + char *argstr = NULL; + int ret; + + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-community']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:community-string", + xpath); + + b = buffer_new(1024); + + for (i = idx_aa_nn; i < argc; i++) { + if (strncmp(argv[i]->arg, "additive", strlen(argv[i]->arg)) + == 0) { + additive = 1; + continue; + } + + if (first) + buffer_putc(b, ' '); + else + first = 1; + + if (strncmp(argv[i]->arg, "local-AS", strlen(argv[i]->arg)) + == 0) { + buffer_putstr(b, "local-AS"); + continue; + } + if (strncmp(argv[i]->arg, "no-a", strlen("no-a")) == 0 + && strncmp(argv[i]->arg, "no-advertise", + strlen(argv[i]->arg)) + == 0) { + buffer_putstr(b, "no-advertise"); + continue; + } + if (strncmp(argv[i]->arg, "no-e", strlen("no-e")) == 0 + && strncmp(argv[i]->arg, "no-export", strlen(argv[i]->arg)) + == 0) { + buffer_putstr(b, "no-export"); + continue; + } + if (strncmp(argv[i]->arg, "blackhole", strlen(argv[i]->arg)) + == 0) { + buffer_putstr(b, "blackhole"); + continue; + } + if (strncmp(argv[i]->arg, "graceful-shutdown", + strlen(argv[i]->arg)) + == 0) { + buffer_putstr(b, "graceful-shutdown"); + continue; + } + buffer_putstr(b, argv[i]->arg); + } + buffer_putc(b, '\0'); + + /* Fetch result string then compile it to communities attribute. */ + str = buffer_getstr(b); + buffer_free(b); + + if (str) + com = community_str2com(str); + + /* Can't compile user input into communities attribute. */ + if (!com) { + vty_out(vty, "%% Malformed communities attribute '%s'\n", str); + XFREE(MTYPE_TMP, str); + return CMD_WARNING_CONFIG_FAILED; + } + XFREE(MTYPE_TMP, str); + + /* Set communites attribute string. */ + str = community_str(com, false, false); + + if (additive) { + size_t argstr_sz = strlen(str) + strlen(" additive") + 1; + argstr = XCALLOC(MTYPE_TMP, argstr_sz); + strlcpy(argstr, str, argstr_sz); + strlcat(argstr, " additive", argstr_sz); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argstr); + } else + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + + ret = nb_cli_apply_changes(vty, NULL); + + if (argstr) + XFREE(MTYPE_TMP, argstr); + community_free(&com); + + return ret; +} + +DEFUN_YANG (set_community_none, + set_community_none_cmd, + "set community none", + SET_STR + "BGP community attribute\n" + "No community attribute\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-community']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:community-none", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_community, + no_set_community_cmd, + "no set community AA:NN...", + NO_STR + SET_STR + "BGP community attribute\n" + COMMUNITY_VAL_STR) +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-community']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_community, + no_set_community_short_cmd, + "no set community", + NO_STR + SET_STR + "BGP community attribute\n") + +DEFPY_YANG (set_community_delete, + set_community_delete_cmd, + "set comm-list <(1-99)|(100-500)|COMMUNITY_LIST_NAME> delete", + SET_STR + "set BGP community list (for deletion)\n" + "Community-list number (standard)\n" + "Community-list number (expanded)\n" + "Community-list name\n" + "Delete matching communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:comm-list-delete']"; + char xpath_value[XPATH_MAXLEN]; + int idx_comm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_comm_list]->arg); + + return nb_cli_apply_changes(vty, NULL); + +} + +DEFUN_YANG (no_set_community_delete, + no_set_community_delete_cmd, + "no set comm-list [<(1-99)|(100-500)|COMMUNITY_LIST_NAME> delete]", + NO_STR + SET_STR + "set BGP community list (for deletion)\n" + "Community-list number (standard)\n" + "Community-list number (expanded)\n" + "Community-list name\n" + "Delete matching communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:comm-list-delete']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_lcommunity, + set_lcommunity_cmd, + "set large-community AA:BB:CC...", + SET_STR + "BGP large community attribute\n" + "Large Community number in aa:bb:cc format or additive\n") +{ + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-large-community']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:large-community-string", + xpath); + str = argv_concat(argv, argc, 2); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFUN_YANG (set_lcommunity_none, + set_lcommunity_none_cmd, + "set large-community none", + SET_STR + "BGP large community attribute\n" + "No large community attribute\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-large-community']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:large-community-none", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_lcommunity, + no_set_lcommunity_cmd, + "no set large-community none", + NO_STR + SET_STR + "BGP large community attribute\n" + "No community attribute\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-large-community']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_lcommunity1, + no_set_lcommunity1_cmd, + "no set large-community AA:BB:CC...", + NO_STR + SET_STR + "BGP large community attribute\n" + "Large community in AA:BB:CC... format or additive\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-large-community']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_lcommunity1, + no_set_lcommunity1_short_cmd, + "no set large-community", + NO_STR + SET_STR + "BGP large community attribute\n") + +DEFPY_YANG (set_lcommunity_delete, + set_lcommunity_delete_cmd, + "set large-comm-list <(1-99)|(100-500)|LCOMMUNITY_LIST_NAME> delete", + SET_STR + "set BGP large community list (for deletion)\n" + "Large Community-list number (standard)\n" + "Large Communitly-list number (expanded)\n" + "Large Community-list name\n" + "Delete matching large communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:large-comm-list-delete']"; + char xpath_value[XPATH_MAXLEN]; + int idx_lcomm_list = 2; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:comm-list-name", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_lcomm_list]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_lcommunity_delete, + no_set_lcommunity_delete_cmd, + "no set large-comm-list <(1-99)|(100-500)|LCOMMUNITY_LIST_NAME> [delete]", + NO_STR + SET_STR + "set BGP large community list (for deletion)\n" + "Large Community-list number (standard)\n" + "Large Communitly-list number (expanded)\n" + "Large Community-list name\n" + "Delete matching large communities\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:large-comm-list-delete']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_lcommunity_delete, + no_set_lcommunity_delete_short_cmd, + "no set large-comm-list", + NO_STR + SET_STR + "set BGP large community list (for deletion)\n") + +DEFUN_YANG (set_ecommunity_rt, + set_ecommunity_rt_cmd, + "set extcommunity rt ASN:NN_OR_IP-ADDRESS:NN...", + SET_STR + "BGP extended community attribute\n" + "Route Target extended community\n" + "VPN extended community\n") +{ + int idx_asn_nn = 3; + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-rt']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-rt", xpath); + str = argv_concat(argv, argc, idx_asn_nn); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFUN_YANG (no_set_ecommunity_rt, + no_set_ecommunity_rt_cmd, + "no set extcommunity rt ASN:NN_OR_IP-ADDRESS:NN...", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Route Target extended community\n" + "VPN extended community\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-rt']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_ecommunity_rt, + no_set_ecommunity_rt_short_cmd, + "no set extcommunity rt", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Route Target extended community\n") + +DEFUN_YANG (set_ecommunity_soo, + set_ecommunity_soo_cmd, + "set extcommunity soo ASN:NN_OR_IP-ADDRESS:NN...", + SET_STR + "BGP extended community attribute\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + int idx_asn_nn = 3; + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-soo']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-soo", + xpath); + str = argv_concat(argv, argc, idx_asn_nn); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFUN_YANG (no_set_ecommunity_soo, + no_set_ecommunity_soo_cmd, + "no set extcommunity soo ASN:NN_OR_IP-ADDRESS:NN...", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Site-of-Origin extended community\n" + "VPN extended community\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-soo']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_ecommunity_soo, + no_set_ecommunity_soo_short_cmd, + "no set extcommunity soo", + NO_STR + SET_STR + "GP extended community attribute\n" + "Site-of-Origin extended community\n") + +DEFUN_YANG(set_ecommunity_none, set_ecommunity_none_cmd, + "set extcommunity none", + SET_STR + "BGP extended community attribute\n" + "No extended community attribute\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-none']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-none", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG(no_set_ecommunity_none, no_set_ecommunity_none_cmd, + "no set extcommunity none", + NO_STR SET_STR + "BGP extended community attribute\n" + "No extended community attribute\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-none']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ecommunity_lb, + set_ecommunity_lb_cmd, + "set extcommunity bandwidth <(1-4294967295)|cumulative|num-multipaths> [non-transitive]", + SET_STR + "BGP extended community attribute\n" + "Link bandwidth extended community\n" + "Bandwidth value in Mbps\n" + "Cumulative bandwidth of all multipaths (outbound-only)\n" + "Internally computed bandwidth based on number of multipaths (outbound-only)\n" + "Attribute is set as non-transitive\n") +{ + int idx_lb = 3; + int idx_non_transitive = 0; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-lb']"; + char xpath_lb_type[XPATH_MAXLEN]; + char xpath_bandwidth[XPATH_MAXLEN]; + char xpath_non_transitive[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_lb_type, sizeof(xpath_lb_type), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-lb/lb-type", + xpath); + snprintf(xpath_bandwidth, sizeof(xpath_bandwidth), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-lb/bandwidth", + xpath); + snprintf(xpath_non_transitive, sizeof(xpath_non_transitive), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific", + xpath); + + if ((strcmp(argv[idx_lb]->arg, "cumulative")) == 0) + nb_cli_enqueue_change(vty, xpath_lb_type, NB_OP_MODIFY, + "cumulative-bandwidth"); + else if ((strcmp(argv[idx_lb]->arg, "num-multipaths")) == 0) + nb_cli_enqueue_change(vty, xpath_lb_type, NB_OP_MODIFY, + "computed-bandwidth"); + else { + nb_cli_enqueue_change(vty, xpath_lb_type, NB_OP_MODIFY, + "explicit-bandwidth"); + nb_cli_enqueue_change(vty, xpath_bandwidth, NB_OP_MODIFY, + argv[idx_lb]->arg); + } + + if (argv_find(argv, argc, "non-transitive", &idx_non_transitive)) + nb_cli_enqueue_change(vty, xpath_non_transitive, NB_OP_MODIFY, + "true"); + else + nb_cli_enqueue_change(vty, xpath_non_transitive, NB_OP_MODIFY, + "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_ecommunity_lb, + no_set_ecommunity_lb_cmd, + "no set extcommunity bandwidth <(1-4294967295)|cumulative|num-multipaths> [non-transitive]", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Link bandwidth extended community\n" + "Bandwidth value in Mbps\n" + "Cumulative bandwidth of all multipaths (outbound-only)\n" + "Internally computed bandwidth based on number of multipaths (outbound-only)\n" + "Attribute is set as non-transitive\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-lb']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_ecommunity_lb, + no_set_ecommunity_lb_short_cmd, + "no set extcommunity bandwidth", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Link bandwidth extended community\n") + +DEFPY_YANG (set_ecommunity_nt, + set_ecommunity_nt_cmd, + "set extcommunity nt RTLIST...", + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n" + "Node Target ID\n") +{ + int idx_nt = 3; + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-nt']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-nt", xpath); + str = argv_concat(argv, argc, idx_nt); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFPY_YANG (no_set_ecommunity_nt, + no_set_ecommunity_nt_cmd, + "no set extcommunity nt RTLIST...", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n" + "Node Target ID\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-nt']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_ecommunity_color, set_ecommunity_color_cmd, + "set extcommunity color RTLIST...", + SET_STR + "BGP extended community attribute\n" + "Color extended community\n" + "Color ID\n") +{ + int idx_color = 3; + char *str; + int ret; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-color']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:extcommunity-color", + xpath); + str = argv_concat(argv, argc, idx_color); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, str); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, str); + return ret; +} + +DEFPY_YANG(no_set_ecommunity_color_all, no_set_ecommunity_color_all_cmd, + "no set extcommunity color", + NO_STR SET_STR + "BGP extended community attribute\n" + "Color extended community\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-color']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_ecommunity_color, no_set_ecommunity_color_cmd, + "no set extcommunity color RTLIST...", + NO_STR SET_STR + "BGP extended community attribute\n" + "Color extended community\n" + "Color ID\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-extcommunity-color']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_YANG (no_set_ecommunity_nt, + no_set_ecommunity_nt_short_cmd, + "no set extcommunity nt", + NO_STR + SET_STR + "BGP extended community attribute\n" + "Node Target extended community\n") + +DEFUN_YANG (set_origin, + set_origin_cmd, + "set origin ", + SET_STR + "BGP origin code\n" + "remote EGP\n" + "local IGP\n" + "unknown heritage\n") +{ + int idx_origin = 2; + const char *origin_type; + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-origin']"; + char xpath_value[XPATH_MAXLEN]; + + if (strncmp(argv[idx_origin]->arg, "igp", 2) == 0) + origin_type = "igp"; + else if (strncmp(argv[idx_origin]->arg, "egp", 1) == 0) + origin_type = "egp"; + else if (strncmp(argv[idx_origin]->arg, "incomplete", 2) == 0) + origin_type = "incomplete"; + else { + vty_out(vty, "%% Invalid match origin type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:origin", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, origin_type); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_origin, + no_set_origin_cmd, + "no set origin []", + NO_STR + SET_STR + "BGP origin code\n" + "remote EGP\n" + "local IGP\n" + "unknown heritage\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:set-origin']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_atomic_aggregate, + set_atomic_aggregate_cmd, + "set atomic-aggregate", + SET_STR + "BGP atomic aggregate attribute\n" ) +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:atomic-aggregate']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:atomic-aggregate", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_atomic_aggregate, + no_set_atomic_aggregate_cmd, + "no set atomic-aggregate", + NO_STR + SET_STR + "BGP atomic aggregate attribute\n" ) +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:atomic-aggregate']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (set_aigp_metric, + set_aigp_metric_cmd, + "set aigp-metric $aigp_metric", + SET_STR + "BGP AIGP attribute (AIGP Metric TLV)\n" + "AIGP Metric value from IGP protocol\n" + "Manual AIGP Metric value\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:aigp-metric']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:aigp-metric", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, aigp_metric); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_set_aigp_metric, + no_set_aigp_metric_cmd, + "no set aigp-metric []", + NO_STR + SET_STR + "BGP AIGP attribute (AIGP Metric TLV)\n" + "AIGP Metric value from IGP protocol\n" + "Manual AIGP Metric value\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:aigp-metric']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_aggregator_as, + set_aggregator_as_cmd, + "set aggregator as ASNUM A.B.C.D", + SET_STR + "BGP aggregator attribute\n" + "AS number of aggregator\n" + AS_STR + "IP address of aggregator\n") +{ + int idx_number = 3; + int idx_ipv4 = 4; + char xpath_asn[XPATH_MAXLEN]; + char xpath_addr[XPATH_MAXLEN]; + const char *xpath = + "./set-action[action='frr-bgp-route-map:aggregator']"; + as_t as_value; + + if (!asn_str2asn(argv[idx_number]->arg, &as_value)) { + vty_out(vty, "%% Invalid AS value %s\n", argv[idx_number]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf( + xpath_asn, sizeof(xpath_asn), + "%s/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-asn", + xpath); + nb_cli_enqueue_change(vty, xpath_asn, NB_OP_MODIFY, + argv[idx_number]->arg); + + snprintf( + xpath_addr, sizeof(xpath_addr), + "%s/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-address", + xpath); + nb_cli_enqueue_change(vty, xpath_addr, NB_OP_MODIFY, + argv[idx_ipv4]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_aggregator_as, + no_set_aggregator_as_cmd, + "no set aggregator as [ASNUM A.B.C.D]", + NO_STR + SET_STR + "BGP aggregator attribute\n" + "AS number of aggregator\n" + AS_STR + "IP address of aggregator\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:aggregator']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_ipv6_next_hop, + match_ipv6_next_hop_cmd, + "match ipv6 next-hop ACCESSLIST6_NAME", + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[argc - 1]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_ipv6_next_hop, + no_match_ipv6_next_hop_cmd, + "no match ipv6 next-hop [ACCESSLIST6_NAME]", + NO_STR + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (match_ipv6_next_hop_address, + match_ipv6_next_hop_address_cmd, + "match ipv6 next-hop address X:X::X:X", + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 address\n" + "IPv6 address of next hop\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ipv6-nexthop']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:ipv6-address", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[argc - 1]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_ipv6_next_hop_address, + no_match_ipv6_next_hop_address_cmd, + "no match ipv6 next-hop address X:X::X:X", + NO_STR + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 address\n" + "IPv6 address of next hop\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ipv6-nexthop']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_HIDDEN (match_ipv6_next_hop_address, + match_ipv6_next_hop_old_cmd, + "match ipv6 next-hop X:X::X:X", + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 address of next hop\n") + +ALIAS_HIDDEN (no_match_ipv6_next_hop_address, + no_match_ipv6_next_hop_old_cmd, + "no match ipv6 next-hop X:X::X:X", + NO_STR + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "IPv6 address of next hop\n") + +DEFUN_YANG (match_ipv6_next_hop_prefix_list, + match_ipv6_next_hop_prefix_list_cmd, + "match ipv6 next-hop prefix-list PREFIXLIST_NAME", + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "Match entries by prefix-list\n" + "IPv6 prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-prefix-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[argc - 1]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_ipv6_next_hop_prefix_list, + no_match_ipv6_next_hop_prefix_list_cmd, + "no match ipv6 next-hop prefix-list [PREFIXLIST_NAME]", + NO_STR + MATCH_STR + IPV6_STR + "Match IPv6 next-hop address of route\n" + "Match entries by prefix-list\n" + "IPv6 prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-prefix-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (match_ipv4_next_hop, + match_ipv4_next_hop_cmd, + "match ip next-hop address A.B.C.D", + MATCH_STR + IP_STR + "Match IP next-hop address of route\n" + "IP address\n" + "IP address of next-hop\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ipv4-nexthop']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:ipv4-address", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[4]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_match_ipv4_next_hop, + no_match_ipv4_next_hop_cmd, + "no match ip next-hop address [A.B.C.D]", + NO_STR + MATCH_STR + IP_STR + "Match IP next-hop address of route\n" + "IP address\n" + "IP address of next-hop\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:ipv4-nexthop']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ipv6_nexthop_peer, + set_ipv6_nexthop_peer_cmd, + "set ipv6 next-hop peer-address", + SET_STR + IPV6_STR + "Next hop address\n" + "Use peer address (for BGP only)\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-peer-address']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:preference", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_ipv6_nexthop_peer, + no_set_ipv6_nexthop_peer_cmd, + "no set ipv6 next-hop peer-address", + NO_STR + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "Use peer address (for BGP only)\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-peer-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ipv6_nexthop_prefer_global, + set_ipv6_nexthop_prefer_global_cmd, + "set ipv6 next-hop prefer-global", + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "Prefer global over link-local if both exist\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-prefer-global']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:preference", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_ipv6_nexthop_prefer_global, + no_set_ipv6_nexthop_prefer_global_cmd, + "no set ipv6 next-hop prefer-global", + NO_STR + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "Prefer global over link-local if both exist\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-prefer-global']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_ipv6_nexthop_global, + set_ipv6_nexthop_global_cmd, + "set ipv6 next-hop global X:X::X:X", + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "IPv6 global address\n" + "IPv6 address of next hop\n") +{ + int idx_ipv6 = 4; + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-nexthop-global']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv6-address", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ipv6]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_ipv6_nexthop_global, + no_set_ipv6_nexthop_global_cmd, + "no set ipv6 next-hop global X:X::X:X", + NO_STR + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "IPv6 global address\n" + "IPv6 address of next hop\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-nexthop-global']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +#ifdef KEEP_OLD_VPN_COMMANDS +DEFUN_YANG (set_vpn_nexthop, + set_vpn_nexthop_cmd, + "set ", + SET_STR + "VPNv4 information\n" + "VPN next-hop address\n" + "IP address of next hop\n" + "VPNv6 information\n" + "VPN next-hop address\n" + "IPv6 address of next hop\n") +{ + int idx_ip = 3; + afi_t afi; + int idx = 0; + char xpath_value[XPATH_MAXLEN]; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + if (afi == AFI_IP) { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv4-vpn-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv4-address", + xpath); + } else { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-vpn-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv6-address", + xpath); + } + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ip]->arg); + + return nb_cli_apply_changes(vty, NULL); + } + + return CMD_SUCCESS; +} + +DEFUN_YANG (no_set_vpn_nexthop, + no_set_vpn_nexthop_cmd, + "no set ", + NO_STR + SET_STR + "VPNv4 information\n" + "VPN next-hop address\n" + "IP address of next hop\n" + "VPNv6 information\n" + "VPN next-hop address\n" + "IPv6 address of next hop\n") +{ + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_vpnvx(argv, argc, &idx, &afi)) { + if (afi == AFI_IP) { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv4-vpn-address']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } else { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-vpn-address']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + return nb_cli_apply_changes(vty, NULL); + } + return CMD_SUCCESS; +} +#endif /* KEEP_OLD_VPN_COMMANDS */ + +DEFPY_YANG (set_ipx_vpn_nexthop, + set_ipx_vpn_nexthop_cmd, + "set vpn next-hop ", + SET_STR + "IPv4 information\n" + "IPv6 information\n" + "VPN information\n" + "VPN next-hop address\n" + "IP address of next hop\n" + "IPv6 address of next hop\n") +{ + int idx_ip = 4; + afi_t afi; + int idx = 0; + char xpath_value[XPATH_MAXLEN]; + + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + if (afi == AFI_IP) { + if (addrv6_str) { + vty_out(vty, "%% IPv4 next-hop expected\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv4-vpn-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv4-address", + xpath); + } else { + if (addrv4_str) { + vty_out(vty, "%% IPv6 next-hop expected\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-vpn-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:ipv6-address", + xpath); + } + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ip]->arg); + return nb_cli_apply_changes(vty, NULL); + } + return CMD_SUCCESS; +} + +DEFUN_YANG (no_set_ipx_vpn_nexthop, + no_set_ipx_vpn_nexthop_cmd, + "no set vpn next-hop []", + NO_STR + SET_STR + "IPv4 information\n" + "IPv6 information\n" + "VPN information\n" + "VPN next-hop address\n" + "IP address of next hop\n" + "IPv6 address of next hop\n") +{ + afi_t afi; + int idx = 0; + + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + if (afi == AFI_IP) { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv4-vpn-address']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } else { + const char *xpath = + "./set-action[action='frr-bgp-route-map:ipv6-vpn-address']"; + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + return nb_cli_apply_changes(vty, NULL); + } + return CMD_SUCCESS; +} + +DEFUN_YANG (set_originator_id, + set_originator_id_cmd, + "set originator-id A.B.C.D", + SET_STR + "BGP originator ID attribute\n" + "IP address of originator\n") +{ + int idx_ipv4 = 2; + const char *xpath = + "./set-action[action='frr-bgp-route-map:originator-id']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-bgp-route-map:originator-id", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ipv4]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_originator_id, + no_set_originator_id_cmd, + "no set originator-id [A.B.C.D]", + NO_STR + SET_STR + "BGP originator ID attribute\n" + "IP address of originator\n") +{ + const char *xpath = + "./set-action[action='frr-bgp-route-map:originator-id']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (match_rpki_extcommunity, + match_rpki_extcommunity_cmd, + "[no$no] match rpki-extcommunity ", + NO_STR + MATCH_STR + "BGP RPKI (Origin Validation State) extended community attribute\n" + "Valid prefix\n" + "Invalid prefix\n" + "Prefix not found\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:rpki-extcommunity']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + if (!no) { + snprintf( + xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:rpki-extcommunity", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[2]->arg); + } + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (match_source_protocol, + match_source_protocol_cmd, + "match source-protocol " FRR_REDIST_STR_ZEBRA "$proto", + MATCH_STR + "Match protocol via which the route was learnt\n" + FRR_REDIST_HELP_STR_ZEBRA) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-protocol']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:source-protocol", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, proto); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_match_source_protocol, + no_match_source_protocol_cmd, + "no match source-protocol [" FRR_REDIST_STR_ZEBRA "]", + NO_STR + MATCH_STR + "Match protocol via which the route was learnt\n" + FRR_REDIST_HELP_STR_ZEBRA) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-protocol']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +/* Initialization of route map. */ +void bgp_route_map_init(void) +{ + route_map_init(); + + route_map_add_hook(bgp_route_map_add); + route_map_delete_hook(bgp_route_map_delete); + route_map_event_hook(bgp_route_map_event); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_match_ip_address_hook(generic_match_add); + route_map_no_match_ip_address_hook(generic_match_delete); + + route_map_match_ip_address_prefix_list_hook(generic_match_add); + route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_hook(generic_match_add); + route_map_no_match_ip_next_hop_hook(generic_match_delete); + + route_map_match_ipv6_next_hop_hook(generic_match_add); + route_map_no_match_ipv6_next_hop_hook(generic_match_delete); + + route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); + route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_type_hook(generic_match_add); + route_map_no_match_ip_next_hop_type_hook(generic_match_delete); + + route_map_match_ipv6_address_hook(generic_match_add); + route_map_no_match_ipv6_address_hook(generic_match_delete); + + route_map_match_ipv6_address_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); + + route_map_match_ipv6_next_hop_type_hook(generic_match_add); + route_map_no_match_ipv6_next_hop_type_hook(generic_match_delete); + + route_map_match_ipv6_next_hop_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_next_hop_prefix_list_hook(generic_match_delete); + + route_map_match_metric_hook(generic_match_add); + route_map_no_match_metric_hook(generic_match_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_set_srte_color_hook(generic_set_add); + route_map_no_set_srte_color_hook(generic_set_delete); + + route_map_set_ip_nexthop_hook(generic_set_add); + route_map_no_set_ip_nexthop_hook(generic_set_delete); + + route_map_set_ipv6_nexthop_local_hook(generic_set_add); + route_map_no_set_ipv6_nexthop_local_hook(generic_set_delete); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_install_match(&route_match_peer_cmd); + route_map_install_match(&route_match_alias_cmd); + route_map_install_match(&route_match_local_pref_cmd); +#ifdef HAVE_SCRIPTING + route_map_install_match(&route_match_script_cmd); +#endif + route_map_install_match(&route_match_ip_address_cmd); + route_map_install_match(&route_match_ip_next_hop_cmd); + route_map_install_match(&route_match_ip_route_source_cmd); + route_map_install_match(&route_match_ip_address_prefix_list_cmd); + route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); + route_map_install_match(&route_match_ip_next_hop_type_cmd); + route_map_install_match(&route_match_source_protocol_cmd); + route_map_install_match(&route_match_ip_route_source_prefix_list_cmd); + route_map_install_match(&route_match_aspath_cmd); + route_map_install_match(&route_match_community_cmd); + route_map_install_match(&route_match_lcommunity_cmd); + route_map_install_match(&route_match_ecommunity_cmd); + route_map_install_match(&route_match_local_pref_cmd); + route_map_install_match(&route_match_metric_cmd); + route_map_install_match(&route_match_origin_cmd); + route_map_install_match(&route_match_probability_cmd); + route_map_install_match(&route_match_interface_cmd); + route_map_install_match(&route_match_tag_cmd); + route_map_install_match(&route_match_mac_address_cmd); + route_map_install_match(&route_match_evpn_vni_cmd); + route_map_install_match(&route_match_evpn_route_type_cmd); + route_map_install_match(&route_match_evpn_rd_cmd); + route_map_install_match(&route_match_evpn_default_route_cmd); + route_map_install_match(&route_match_vrl_source_vrf_cmd); + + route_map_install_set(&route_set_evpn_gateway_ip_ipv4_cmd); + route_map_install_set(&route_set_evpn_gateway_ip_ipv6_cmd); + route_map_install_set(&route_set_table_id_cmd); + route_map_install_set(&route_set_srte_color_cmd); + route_map_install_set(&route_set_ip_nexthop_cmd); + route_map_install_set(&route_set_local_pref_cmd); + route_map_install_set(&route_set_weight_cmd); + route_map_install_set(&route_set_label_index_cmd); + route_map_install_set(&route_set_metric_cmd); + route_map_install_set(&route_set_distance_cmd); + route_map_install_set(&route_set_aspath_prepend_cmd); + route_map_install_set(&route_set_aspath_exclude_cmd); + route_map_install_set(&route_set_aspath_replace_cmd); + route_map_install_set(&route_set_origin_cmd); + route_map_install_set(&route_set_atomic_aggregate_cmd); + route_map_install_set(&route_set_aigp_metric_cmd); + route_map_install_set(&route_set_aggregator_as_cmd); + route_map_install_set(&route_set_community_cmd); + route_map_install_set(&route_set_community_delete_cmd); + route_map_install_set(&route_set_ecommunity_delete_cmd); + route_map_install_set(&route_set_lcommunity_cmd); + route_map_install_set(&route_set_lcommunity_delete_cmd); + route_map_install_set(&route_set_vpnv4_nexthop_cmd); + route_map_install_set(&route_set_vpnv6_nexthop_cmd); + route_map_install_set(&route_set_originator_id_cmd); + route_map_install_set(&route_set_ecommunity_rt_cmd); + route_map_install_set(&route_set_ecommunity_nt_cmd); + route_map_install_set(&route_set_ecommunity_soo_cmd); + route_map_install_set(&route_set_ecommunity_lb_cmd); + route_map_install_set(&route_set_ecommunity_color_cmd); + route_map_install_set(&route_set_ecommunity_none_cmd); + route_map_install_set(&route_set_tag_cmd); + route_map_install_set(&route_set_label_index_cmd); + route_map_install_set(&route_set_l3vpn_nexthop_encapsulation_cmd); + + install_element(RMAP_NODE, &match_peer_cmd); + install_element(RMAP_NODE, &match_peer_local_cmd); + install_element(RMAP_NODE, &no_match_peer_cmd); + install_element(RMAP_NODE, &match_ip_route_source_cmd); + install_element(RMAP_NODE, &no_match_ip_route_source_cmd); + install_element(RMAP_NODE, &match_ip_route_source_prefix_list_cmd); + install_element(RMAP_NODE, &no_match_ip_route_source_prefix_list_cmd); + install_element(RMAP_NODE, &match_mac_address_cmd); + install_element(RMAP_NODE, &no_match_mac_address_cmd); + install_element(RMAP_NODE, &match_evpn_vni_cmd); + install_element(RMAP_NODE, &no_match_evpn_vni_cmd); + install_element(RMAP_NODE, &match_evpn_route_type_cmd); + install_element(RMAP_NODE, &no_match_evpn_route_type_cmd); + install_element(RMAP_NODE, &match_evpn_rd_cmd); + install_element(RMAP_NODE, &no_match_evpn_rd_cmd); + install_element(RMAP_NODE, &match_evpn_default_route_cmd); + install_element(RMAP_NODE, &no_match_evpn_default_route_cmd); + install_element(RMAP_NODE, &set_evpn_gw_ip_ipv4_cmd); + install_element(RMAP_NODE, &no_set_evpn_gw_ip_ipv4_cmd); + install_element(RMAP_NODE, &set_evpn_gw_ip_ipv6_cmd); + install_element(RMAP_NODE, &no_set_evpn_gw_ip_ipv6_cmd); + install_element(RMAP_NODE, &match_vrl_source_vrf_cmd); + install_element(RMAP_NODE, &no_match_vrl_source_vrf_cmd); + + install_element(RMAP_NODE, &match_aspath_cmd); + install_element(RMAP_NODE, &no_match_aspath_cmd); + install_element(RMAP_NODE, &match_local_pref_cmd); + install_element(RMAP_NODE, &no_match_local_pref_cmd); + install_element(RMAP_NODE, &match_alias_cmd); + install_element(RMAP_NODE, &no_match_alias_cmd); + install_element(RMAP_NODE, &match_community_cmd); + install_element(RMAP_NODE, &no_match_community_cmd); + install_element(RMAP_NODE, &match_lcommunity_cmd); + install_element(RMAP_NODE, &no_match_lcommunity_cmd); + install_element(RMAP_NODE, &match_ecommunity_cmd); + install_element(RMAP_NODE, &no_match_ecommunity_cmd); + install_element(RMAP_NODE, &match_origin_cmd); + install_element(RMAP_NODE, &no_match_origin_cmd); + install_element(RMAP_NODE, &match_probability_cmd); + install_element(RMAP_NODE, &no_match_probability_cmd); + + install_element(RMAP_NODE, &no_set_table_id_cmd); + install_element(RMAP_NODE, &set_table_id_cmd); + install_element(RMAP_NODE, &set_ip_nexthop_peer_cmd); + install_element(RMAP_NODE, &set_ip_nexthop_unchanged_cmd); + install_element(RMAP_NODE, &set_local_pref_cmd); + install_element(RMAP_NODE, &set_distance_cmd); + install_element(RMAP_NODE, &no_set_distance_cmd); + install_element(RMAP_NODE, &no_set_local_pref_cmd); + install_element(RMAP_NODE, &set_weight_cmd); + install_element(RMAP_NODE, &set_label_index_cmd); + install_element(RMAP_NODE, &no_set_weight_cmd); + install_element(RMAP_NODE, &no_set_label_index_cmd); + install_element(RMAP_NODE, &set_aspath_prepend_asn_cmd); + install_element(RMAP_NODE, &set_aspath_prepend_lastas_cmd); + install_element(RMAP_NODE, &set_aspath_exclude_cmd); + install_element(RMAP_NODE, &set_aspath_exclude_all_cmd); + install_element(RMAP_NODE, &set_aspath_exclude_access_list_cmd); + install_element(RMAP_NODE, &set_aspath_replace_asn_cmd); + install_element(RMAP_NODE, &set_aspath_replace_access_list_cmd); + install_element(RMAP_NODE, &no_set_aspath_prepend_last_as_cmd); + install_element(RMAP_NODE, &no_set_aspath_prepend_as_cmd); + install_element(RMAP_NODE, &no_set_aspath_exclude_cmd); + install_element(RMAP_NODE, &no_set_aspath_exclude_all_cmd); + install_element(RMAP_NODE, &no_set_aspath_exclude_access_list_cmd); + install_element(RMAP_NODE, &no_set_aspath_replace_asn_cmd); + install_element(RMAP_NODE, &no_set_aspath_replace_access_list_cmd); + install_element(RMAP_NODE, &set_origin_cmd); + install_element(RMAP_NODE, &no_set_origin_cmd); + install_element(RMAP_NODE, &set_atomic_aggregate_cmd); + install_element(RMAP_NODE, &no_set_atomic_aggregate_cmd); + install_element(RMAP_NODE, &set_aigp_metric_cmd); + install_element(RMAP_NODE, &no_set_aigp_metric_cmd); + install_element(RMAP_NODE, &set_aggregator_as_cmd); + install_element(RMAP_NODE, &no_set_aggregator_as_cmd); + install_element(RMAP_NODE, &set_community_cmd); + install_element(RMAP_NODE, &set_community_none_cmd); + install_element(RMAP_NODE, &no_set_community_cmd); + install_element(RMAP_NODE, &no_set_community_short_cmd); + install_element(RMAP_NODE, &set_community_delete_cmd); + install_element(RMAP_NODE, &no_set_community_delete_cmd); + install_element(RMAP_NODE, &set_lcommunity_cmd); + install_element(RMAP_NODE, &set_lcommunity_none_cmd); + install_element(RMAP_NODE, &no_set_lcommunity_cmd); + install_element(RMAP_NODE, &no_set_lcommunity1_cmd); + install_element(RMAP_NODE, &no_set_lcommunity1_short_cmd); + install_element(RMAP_NODE, &set_lcommunity_delete_cmd); + install_element(RMAP_NODE, &no_set_lcommunity_delete_cmd); + install_element(RMAP_NODE, &no_set_lcommunity_delete_short_cmd); + install_element(RMAP_NODE, &set_ecommunity_rt_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_rt_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_rt_short_cmd); + install_element(RMAP_NODE, &set_ecommunity_soo_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_soo_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_soo_short_cmd); + install_element(RMAP_NODE, &set_ecommunity_lb_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_lb_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_lb_short_cmd); + install_element(RMAP_NODE, &set_ecommunity_none_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_none_cmd); + install_element(RMAP_NODE, &set_ecommunity_nt_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_nt_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_nt_short_cmd); + install_element(RMAP_NODE, &set_ecommunity_color_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_color_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_color_all_cmd); + install_element(RMAP_NODE, &set_ecommunity_delete_cmd); + install_element(RMAP_NODE, &no_set_ecommunity_delete_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(RMAP_NODE, &set_vpn_nexthop_cmd); + install_element(RMAP_NODE, &no_set_vpn_nexthop_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + install_element(RMAP_NODE, &set_ipx_vpn_nexthop_cmd); + install_element(RMAP_NODE, &no_set_ipx_vpn_nexthop_cmd); + install_element(RMAP_NODE, &set_originator_id_cmd); + install_element(RMAP_NODE, &no_set_originator_id_cmd); + install_element(RMAP_NODE, &set_l3vpn_nexthop_encapsulation_cmd); + + route_map_install_match(&route_match_ipv6_address_cmd); + route_map_install_match(&route_match_ipv6_next_hop_cmd); + route_map_install_match(&route_match_ipv6_next_hop_address_cmd); + route_map_install_match(&route_match_ipv6_next_hop_prefix_list_cmd); + route_map_install_match(&route_match_ipv4_next_hop_cmd); + route_map_install_match(&route_match_ipv6_address_prefix_list_cmd); + route_map_install_match(&route_match_ipv6_next_hop_type_cmd); + route_map_install_set(&route_set_ipv6_nexthop_global_cmd); + route_map_install_set(&route_set_ipv6_nexthop_prefer_global_cmd); + route_map_install_set(&route_set_ipv6_nexthop_local_cmd); + route_map_install_set(&route_set_ipv6_nexthop_peer_cmd); + route_map_install_match(&route_match_rpki_extcommunity_cmd); + + install_element(RMAP_NODE, &match_ipv6_next_hop_cmd); + install_element(RMAP_NODE, &match_ipv6_next_hop_address_cmd); + install_element(RMAP_NODE, &match_ipv6_next_hop_prefix_list_cmd); + install_element(RMAP_NODE, &no_match_ipv6_next_hop_cmd); + install_element(RMAP_NODE, &no_match_ipv6_next_hop_address_cmd); + install_element(RMAP_NODE, &no_match_ipv6_next_hop_prefix_list_cmd); + install_element(RMAP_NODE, &match_ipv6_next_hop_old_cmd); + install_element(RMAP_NODE, &no_match_ipv6_next_hop_old_cmd); + install_element(RMAP_NODE, &match_ipv4_next_hop_cmd); + install_element(RMAP_NODE, &no_match_ipv4_next_hop_cmd); + install_element(RMAP_NODE, &set_ipv6_nexthop_global_cmd); + install_element(RMAP_NODE, &no_set_ipv6_nexthop_global_cmd); + install_element(RMAP_NODE, &set_ipv6_nexthop_prefer_global_cmd); + install_element(RMAP_NODE, &no_set_ipv6_nexthop_prefer_global_cmd); + install_element(RMAP_NODE, &set_ipv6_nexthop_peer_cmd); + install_element(RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd); + install_element(RMAP_NODE, &match_rpki_extcommunity_cmd); + install_element(RMAP_NODE, &match_source_protocol_cmd); + install_element(RMAP_NODE, &no_match_source_protocol_cmd); +#ifdef HAVE_SCRIPTING + install_element(RMAP_NODE, &match_script_cmd); +#endif +} + +void bgp_route_map_terminate(void) +{ + /* ToDo: Cleanup all the used memory */ + route_map_finish(); +} diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c new file mode 100644 index 0000000..096502a --- /dev/null +++ b/bgpd/bgp_routemap_nb.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_routemap_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_bgp_route_map_info = { + .name = "frr-bgp-route-map", + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:local-preference", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_local_preference_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_local_preference_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:alias", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_alias_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_alias_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:script", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_script_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_script_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:origin", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_origin_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_origin_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_rpki_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_rpki_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki-extcommunity", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:probability", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_probability_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_probability_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-vrf", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv4-address", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-interface", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv6-address", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-local", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_peer_local_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_peer_local_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:list-name", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_list_name_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_list_name_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-default-route", + .cbs = { + .create = lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_create, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-vni", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-route-type", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:route-distinguisher", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list", + .cbs = { + .create = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_create, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_destroy, + .apply_finish = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_finish, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:ipv4-address", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:ipv6-address", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:distance", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_distance_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_distance_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-rt", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-nt", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-soo", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv4-address", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_ipv4_address_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_ipv4_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv4-nexthop", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv6-address", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:preference", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_preference_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_preference_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:label-index", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_label_index_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_label_index_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:local-pref", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_local_pref_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_local_pref_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:weight", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_weight_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_weight_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:origin", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_origin_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_origin_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:originator-id", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_originator_id_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_originator_id_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:table", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_table_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_table_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:atomic-aggregate", + .cbs = { + .create = lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_create, + .destroy = lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aigp-metric", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_aigp_metric_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_aigp_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:prepend-as-path", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:last-as", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_last_as_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_last_as_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:exclude-as-path", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:replace-as-path", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_replace_as_path_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_replace_as_path_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:community-none", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_community_none_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_community_none_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:community-string", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_community_string_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_community_string_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:large-community-none", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_large_community_none_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_large_community_none_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:large-community-string", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_large_community_string_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_large_community_string_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator", + .cbs = { + .create = lib_route_map_entry_set_action_rmap_set_action_aggregator_create, + .destroy = lib_route_map_entry_set_action_rmap_set_action_aggregator_destroy, + .apply_finish = lib_route_map_entry_set_action_rmap_set_action_aggregator_finish, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-asn", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_asn_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-address", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_address_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:comm-list-name", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_comm_list_name_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_comm_list_name_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-none", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb", + .cbs = { + .create = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_create, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_destroy, + .apply_finish = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_finish, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/lb-type", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_lb_type_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/bandwidth", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-color", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_two_octet_as_specific_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv4", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv6", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:l3vpn-nexthop-encapsulation", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h new file mode 100644 index 0000000..d7f0cea --- /dev/null +++ b/bgpd/bgp_routemap_nb.h @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#ifndef _FRR_BGP_ROUTEMAP_NB_H_ +#define _FRR_BGP_ROUTEMAP_NB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct frr_yang_module_info frr_bgp_route_map_info; + +/* prototypes */ +int lib_route_map_entry_match_condition_rmap_match_condition_local_preference_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_local_preference_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_alias_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_alias_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_script_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_script_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_origin_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_origin_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_probability_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_probability_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_local_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_peer_local_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_access_list_num_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_access_list_num_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_access_list_num_extended_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_access_list_num_extended_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_list_name_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_list_name_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_create(struct nb_cb_create_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_create( + struct nb_cb_create_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_destroy( + struct nb_cb_destroy_args *args); +void lib_route_map_entry_match_condition_rmap_match_condition_comm_list_finish(struct nb_cb_apply_finish_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_distance_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_distance_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv4_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv4_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_preference_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_preference_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_label_index_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_label_index_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_local_pref_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_local_pref_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_weight_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_weight_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_origin_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_origin_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_originator_id_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_originator_id_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_table_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_table_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_create(struct nb_cb_create_args *args); +int lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aigp_metric_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aigp_metric_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_last_as_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_last_as_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_replace_as_path_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_replace_as_path_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_community_none_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_community_none_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_community_string_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_community_string_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_large_community_none_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_large_community_none_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_large_community_string_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_large_community_string_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aggregator_create( + struct nb_cb_create_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aggregator_destroy( + struct nb_cb_destroy_args *args); +void lib_route_map_entry_set_action_rmap_set_action_aggregator_finish(struct nb_cb_apply_finish_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_asn_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_num_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_num_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_num_extended_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_num_extended_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_name_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_comm_list_name_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_create( + struct nb_cb_create_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_destroy( + struct nb_cb_destroy_args *args); +void lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_finish(struct nb_cb_apply_finish_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_lb_type_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_two_octet_as_specific_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy( + struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_destroy( + struct nb_cb_destroy_args *args); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c new file mode 100644 index 0000000..15c32ea --- /dev/null +++ b/bgpd/bgp_routemap_nb_config.c @@ -0,0 +1,3282 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_routemap_nb.h" + +/* Add bgp route map rule. */ +static int bgp_route_match_add(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len) +{ + int retval = CMD_SUCCESS; + enum rmap_compile_rets ret; + + ret = route_map_add_match(index, command, arg, type); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, "%% BGP Can't find rule."); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, "%% BGP Argument is malformed."); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_SUCCESS: + /* + * Intentionally doing nothing here. + */ + break; + } + + return retval; +} + +/* Delete bgp route map rule. */ +static int bgp_route_match_delete(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len) +{ + enum rmap_compile_rets ret; + int retval = CMD_SUCCESS; + char *dep_name = NULL; + const char *tmpstr; + char *rmap_name = NULL; + + if (type != RMAP_EVENT_MATCH_DELETED) { + /* ignore the mundane, the types without any dependency */ + if (arg == NULL) { + tmpstr = route_map_get_match_arg(index, command); + if (tmpstr != NULL) + dep_name = + XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr); + } else { + dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg); + } + rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name); + } + + ret = route_map_delete_match(index, command, dep_name, type); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, "%% BGP Can't find rule."); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, + "%% BGP Argument is malformed."); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_SUCCESS: + /* + * Nothing to do here + */ + break; + } + + XFREE(MTYPE_ROUTE_MAP_RULE, dep_name); + XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name); + + return retval; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:local-preference + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_local_preference_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *local_pref; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + local_pref = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "local-preference"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "local-preference", + local_pref, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_local_preference_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:alias + */ +int lib_route_map_entry_match_condition_rmap_match_condition_alias_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *alias; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + alias = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "alias"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "alias", alias, + RMAP_EVENT_MATCH_ADDED, args->errmsg, + args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_VALIDATION; + } + + break; + } + + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_alias_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:script + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_script_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *script; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + script = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "script"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "script", + script, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_script_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:origin + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_origin_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *origin; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + origin = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "origin"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "origin", origin, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_origin_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_rpki_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *rpki; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + rpki = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "rpki"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "rpki", rpki, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_rpki_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol + */ +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + enum rmap_compile_rets ret; + const char *proto; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "source-protocol"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "source-protocol", + proto, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki-extcommunity + */ +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *rpki; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + rpki = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "rpki-extcommunity"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "rpki-extcommunity", + rpki, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:probability + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_probability_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *probability; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + probability = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "probability"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "probability", + probability, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_probability_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-vrf + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *vrf; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + vrf = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "source-vrf"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "source-vrf", vrf, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv4-address + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *peer; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + peer = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "peer"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "peer", peer, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv4_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-interface + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *peer; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + peer = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "peer"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "peer", peer, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_interface_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv6-address + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *peer; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + peer = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "peer"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "peer", peer, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_ipv6_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-local + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_local_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + bool value; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + value = yang_dnode_get_bool(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "peer"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + if (value) { + ret = bgp_route_match_add(rhc->rhc_rmi, "peer", + "local", + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_peer_local_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:list-name + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *list_name; + enum rmap_compile_rets ret = RMAP_COMPILE_SUCCESS; + const char *condition; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + list_name = yang_dnode_get_string(args->dnode, NULL); + condition = yang_dnode_get_string(args->dnode, + "../../frr-route-map:condition"); + + if (IS_MATCH_AS_LIST(condition)) { + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "as-path"; + rhc->rhc_event = RMAP_EVENT_ASLIST_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "as-path", + list_name, RMAP_EVENT_ASLIST_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_MAC_LIST(condition)) { + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "mac address"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, + "mac address", + list_name, + RMAP_EVENT_FILTER_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_ROUTE_SRC(condition)) { + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "ip route-source"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, + "ip route-source", + list_name, RMAP_EVENT_FILTER_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_ROUTE_SRC_PL(condition)) { + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "ip route-source prefix-list"; + rhc->rhc_event = RMAP_EVENT_PLIST_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, + "ip route-source prefix-list", + list_name, RMAP_EVENT_PLIST_ADDED, + args->errmsg, args->errmsg_len); + } + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-default-route + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_create( + struct nb_cb_create_args *args) +{ + struct routemap_hook_context *rhc; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "evpn default-route"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "evpn default-route", + NULL, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_default_route_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-vni + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *vni; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + vni = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "evpn vni"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "evpn vni", vni, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_vni_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:evpn-route-type + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "evpn route-type"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "evpn route-type", + type, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_evpn_route_type_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:route-distinguisher + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *rd; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + rd = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "evpn rd"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "evpn rd", rd, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_route_distinguisher_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath = /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list + */ +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_create( + struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +void +lib_route_map_entry_match_condition_rmap_match_condition_comm_list_finish( + struct nb_cb_apply_finish_args *args) +{ + struct routemap_hook_context *rhc; + const char *value; + bool exact_match = false; + bool any = false; + char *argstr; + const char *condition; + route_map_event_t event; + int ret; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + value = yang_dnode_get_string(args->dnode, "comm-list-name"); + + if (yang_dnode_exists(args->dnode, "comm-list-name-exact-match")) + exact_match = yang_dnode_get_bool( + args->dnode, "./comm-list-name-exact-match"); + + if (yang_dnode_exists(args->dnode, "comm-list-name-any")) + any = yang_dnode_get_bool(args->dnode, "comm-list-name-any"); + + if (exact_match) { + argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + strlen(value) + strlen("exact-match") + 2); + + snprintf(argstr, (strlen(value) + strlen("exact-match") + 2), + "%s exact-match", value); + } else if (any) { + argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + strlen(value) + strlen("any") + 2); + + snprintf(argstr, (strlen(value) + strlen("any") + 2), "%s any", + value); + } else + argstr = (char *)value; + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + + condition = yang_dnode_get_string(args->dnode, + "../../frr-route-map:condition"); + if (IS_MATCH_COMMUNITY(condition)) { + rhc->rhc_rule = "community"; + event = RMAP_EVENT_CLIST_ADDED; + rhc->rhc_event = RMAP_EVENT_CLIST_DELETED; + } else if (IS_MATCH_LCOMMUNITY(condition)) { + rhc->rhc_rule = "large-community"; + event = RMAP_EVENT_LLIST_ADDED; + rhc->rhc_event = RMAP_EVENT_LLIST_DELETED; + } else { + rhc->rhc_rule = "extcommunity"; + event = RMAP_EVENT_ECLIST_ADDED; + rhc->rhc_event = RMAP_EVENT_ECLIST_DELETED; + } + + ret = bgp_route_match_add(rhc->rhc_rmi, rhc->rhc_rule, argstr, event, + args->errmsg, args->errmsg_len); + /* + * At this point if this is not a successful operation + * bgpd is about to crash. Let's just cut to the + * chase and do it. + */ + assert(ret == RMAP_COMPILE_SUCCESS); + + if (argstr != value) + XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any + */ +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_any_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_comm_list_comm_list_name_exact_match_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:ipv4-address + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *peer; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + peer = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "ip next-hop address"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, rhc->rhc_rule, + peer, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_ipv4_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:ipv6-address + */ +int +lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *peer; + enum rmap_compile_rets ret; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + peer = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "ipv6 next-hop address"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, rhc->rhc_rule, + peer, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_match_condition_rmap_match_condition_ipv6_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:distance + */ +int lib_route_map_entry_set_action_rmap_set_action_distance_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "distance"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "distance", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_distance_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-rt + */ +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity rt"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "extcommunity rt", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_rt_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-nt + */ +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *str; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + str = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity nt"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "extcommunity nt", str, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_nt_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-soo + */ +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity soo"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "extcommunity soo", + type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_soo_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv4-address + */ +int lib_route_map_entry_set_action_rmap_set_action_ipv4_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *addr; + int rv = CMD_SUCCESS; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + addr = yang_dnode_get_string(args->dnode, NULL); + + rhc->rhc_shook = generic_set_delete; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + rhc->rhc_rule = "ipv4 vpn next-hop"; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, addr, + args->errmsg, args->errmsg_len); + + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_ipv4_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv4-nexthop + */ +int lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "ip next-hop"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, type, + args->errmsg, args->errmsg_len); + + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_ipv4_nexthop_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:ipv6-address + */ +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *addr; + int rv = CMD_SUCCESS; + const char *action = NULL; + struct in6_addr i6a; + + action = yang_dnode_get_string(args->dnode, + "../../frr-route-map:action"); + switch (args->event) { + case NB_EV_VALIDATE: + if (action && IS_SET_IPV6_NH_GLOBAL(action)) { + yang_dnode_get_ipv6(&i6a, args->dnode, NULL); + if (IN6_IS_ADDR_UNSPECIFIED(&i6a) + || IN6_IS_ADDR_LOOPBACK(&i6a) + || IN6_IS_ADDR_MULTICAST(&i6a) + || IN6_IS_ADDR_LINKLOCAL(&i6a)) + return NB_ERR_VALIDATION; + } + return NB_OK; + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + break; + } + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + addr = yang_dnode_get_string(args->dnode, NULL); + + rhc->rhc_shook = generic_set_delete; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + if (IS_SET_IPV6_NH_GLOBAL(action)) + /* Set destroy information. */ + rhc->rhc_rule = "ipv6 next-hop global"; + else + rhc->rhc_rule = "ipv6 vpn next-hop"; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, addr, + args->errmsg, args->errmsg_len); + + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:preference + */ +int lib_route_map_entry_set_action_rmap_set_action_preference_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + int rv = CMD_SUCCESS; + const char *action = NULL; + bool value; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + value = yang_dnode_get_bool(args->dnode, NULL); + + rhc->rhc_shook = generic_set_delete; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + action = yang_dnode_get_string(args->dnode, + "../../frr-route-map:action"); + + if (value) { + if (IS_SET_IPV6_PEER_ADDR(action)) + /* Set destroy information. */ + rhc->rhc_rule = "ipv6 next-hop peer-address"; + else + rhc->rhc_rule = "ipv6 next-hop prefer-global"; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, + NULL, + args->errmsg, args->errmsg_len); + } + + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_preference_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:label-index + */ +int lib_route_map_entry_set_action_rmap_set_action_label_index_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "label-index"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "label-index", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_label_index_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:local-pref + */ +int lib_route_map_entry_set_action_rmap_set_action_local_pref_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "local-preference"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "local-preference", + type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_local_pref_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:weight + */ +int lib_route_map_entry_set_action_rmap_set_action_weight_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "weight"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "weight", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_weight_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:origin + */ +int lib_route_map_entry_set_action_rmap_set_action_origin_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "origin"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "origin", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_origin_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:originator-id + */ +int lib_route_map_entry_set_action_rmap_set_action_originator_id_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "originator-id"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "originator-id", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_originator_id_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:table + */ +int lib_route_map_entry_set_action_rmap_set_action_table_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "table"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "table", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_table_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:atomic-aggregate + */ +int +lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_create( + struct nb_cb_create_args *args) +{ + struct routemap_hook_context *rhc; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "atomic-aggregate"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, NULL, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_atomic_aggregate_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aigp-metric + */ +int lib_route_map_entry_set_action_rmap_set_action_aigp_metric_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *aigp; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + aigp = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "aigp-metric"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, aigp, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_aigp_metric_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:prepend-as-path + */ +int +lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "as-path prepend"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "as-path prepend", + type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_prepend_as_path_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:last-as + */ +int lib_route_map_entry_set_action_rmap_set_action_last_as_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *value; + char *argstr; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + value = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "as-path prepend"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + strlen(value) + strlen("last-as") + 2); + + snprintf(argstr, (strlen(value) + strlen("last-as") + 2), + "last-as %s", value); + + rv = generic_set_add(rhc->rhc_rmi, "as-path prepend", + argstr, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); + return NB_ERR_INCONSISTENCY; + } + + XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_last_as_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:exclude-as-path + */ +int +lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "as-path exclude"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "as-path exclude", + type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_exclude_as_path_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:replace-as-path + */ +int lib_route_map_entry_set_action_rmap_set_action_replace_as_path_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "as-path replace"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "as-path replace", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_replace_as_path_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:community-none + */ +int lib_route_map_entry_set_action_rmap_set_action_community_none_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + bool none = false; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + none = yang_dnode_get_bool(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "community"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + if (none) { + rv = generic_set_add(rhc->rhc_rmi, "community", + "none", + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + return NB_OK; + } + + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_community_none_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:community-string + */ +int +lib_route_map_entry_set_action_rmap_set_action_community_string_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "community"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "community", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_community_string_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:large-community-none + */ +int +lib_route_map_entry_set_action_rmap_set_action_large_community_none_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + bool none = false; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + none = yang_dnode_get_bool(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "large-community"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + if (none) { + rv = generic_set_add(rhc->rhc_rmi, + "large-community", + "none", + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + return NB_OK; + } + + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_large_community_none_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:large-community-string + */ +int +lib_route_map_entry_set_action_rmap_set_action_large_community_string_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "large-community"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "large-community", + type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_large_community_string_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * xpath = + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator + */ +int lib_route_map_entry_set_action_rmap_set_action_aggregator_create( + struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_aggregator_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +void lib_route_map_entry_set_action_rmap_set_action_aggregator_finish( + struct nb_cb_apply_finish_args *args) +{ + struct routemap_hook_context *rhc; + const char *asn; + const char *addr; + char *argstr; + int ret; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + asn = yang_dnode_get_string(args->dnode, "aggregator-asn"); + addr = yang_dnode_get_string(args->dnode, "aggregator-address"); + + argstr = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + strlen(asn) + strlen(addr) + 2); + + snprintf(argstr, (strlen(asn) + strlen(addr) + 2), "%s %s", asn, addr); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "aggregator as"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + ret = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, argstr, args->errmsg, + args->errmsg_len); + /* + * At this point if this is not a successful operation + * bgpd is about to crash. Let's just cut to the + * chase and do it. + */ + assert(ret == CMD_SUCCESS); + + XFREE(MTYPE_ROUTE_MAP_COMPILED, argstr); +} +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-asn + */ +int +lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_asn_modify( + struct nb_cb_modify_args *args) +{ + const char *asn; + enum match_type match; + + switch (args->event) { + case NB_EV_VALIDATE: + asn = yang_dnode_get_string(args->dnode, NULL); + if (!asn) + return NB_ERR_VALIDATION; + match = asn_str2asn_match(asn); + if (match == exact_match) + return NB_OK; + return NB_ERR_VALIDATION; + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:aggregator/aggregator-address + */ +int +lib_route_map_entry_set_action_rmap_set_action_aggregator_aggregator_address_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:comm-list-name + */ +int lib_route_map_entry_set_action_rmap_set_action_comm_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *value; + const char *action; + int rv = CMD_SUCCESS; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + value = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + + action = yang_dnode_get_string(args->dnode, + "../../frr-route-map:action"); + if (IS_SET_COMM_LIST_DEL(action)) + rhc->rhc_rule = "comm-list"; + else if (IS_SET_EXTCOMM_LIST_DEL(action)) + rhc->rhc_rule = "extended-comm-list"; + else + rhc->rhc_rule = "large-comm-list"; + + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, rhc->rhc_rule, value, + args->errmsg, args->errmsg_len); + + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_comm_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb + */ +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_create( + struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +void +lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_finish( + struct nb_cb_apply_finish_args *args) +{ + struct routemap_hook_context *rhc; + enum ecommunity_lb_type lb_type; + char str[VTY_BUFSIZ]; + uint32_t bandwidth; + int ret; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + lb_type = yang_dnode_get_enum(args->dnode, "lb-type"); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity bandwidth"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + switch (lb_type) { + case EXPLICIT_BANDWIDTH: + bandwidth = yang_dnode_get_uint32(args->dnode, "bandwidth"); + snprintf(str, sizeof(str), "%u", bandwidth); + break; + case CUMULATIVE_BANDWIDTH: + snprintf(str, sizeof(str), "%s", "cumulative"); + break; + case COMPUTED_BANDWIDTH: + snprintf(str, sizeof(str), "%s", "num-multipaths"); + } + + if (yang_dnode_get_bool(args->dnode, "two-octet-as-specific")) + strlcat(str, " non-transitive", sizeof(str)); + + ret = generic_set_add(rhc->rhc_rmi, "extcommunity bandwidth", str, + args->errmsg, args->errmsg_len); + /* + * At this point if this is not a successful operation + * bgpd is about to crash. Let's just cut to the + * chase and do it. + */ + assert(ret == CMD_SUCCESS); +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/lb-type + */ +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_lb_type_modify( + struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/bandwidth + */ +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_modify( + struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_bandwidth_destroy( + struct nb_cb_destroy_args *args) +{ + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-color + */ +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *str; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + str = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity color"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "extcommunity color", str, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_color_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific + */ +int +lib_route_map_entry_set_action_rmap_set_action_extcommunity_lb_two_octet_as_specific_modify( + struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:extcommunity-none + */ +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + bool none = false; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + none = yang_dnode_get_bool(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "extcommunity"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + if (none) { + rv = generic_set_add(rhc->rhc_rmi, "extcommunity", + "none", args->errmsg, + args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + return NB_OK; + } + + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_extcommunity_none_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv4 + */ +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "evpn gateway-ip ipv4"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "evpn gateway-ip ipv4", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv4_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv6 + */ +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "evpn gateway-ip ipv6"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "evpn gateway-ip ipv6", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_evpn_gateway_ip_ipv6_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/l3vpn-nexthop-encapsulation + */ +int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "l3vpn next-hop encapsulation"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, + "l3vpn next-hop encapsulation", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_l3vpn_nexthop_encapsulation_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} diff --git a/bgpd/bgp_rpki.c b/bgpd/bgp_rpki.c new file mode 100644 index 0000000..f9cbf24 --- /dev/null +++ b/bgpd/bgp_rpki.c @@ -0,0 +1,2873 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP RPKI + * Copyright (C) 2013 Michael Mester (m.mester@fu-berlin.de), for FU Berlin + * Copyright (C) 2014-2017 Andreas Reuter (andreas.reuter@fu-berlin.de), for FU + * Berlin + * Copyright (C) 2016-2017 Colin Sames (colin.sames@haw-hamburg.de), for HAW + * Hamburg + * Copyright (C) 2017-2018 Marcel Röthke (marcel.roethke@haw-hamburg.de), + * for HAW Hamburg + */ + +/* If rtrlib compiled with ssh support, don`t fail build */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include +#include "prefix.h" +#include "log.h" +#include "command.h" +#include "linklist.h" +#include "memory.h" +#include "frrevent.h" +#include "filter.h" +#include "lib_errors.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgp_advertise.h" +#include "bgp_label.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_rpki.h" +#include "bgpd/bgp_debug.h" +#include "northbound_cli.h" + +#include "lib/network.h" +#include "rtrlib/rtrlib.h" +#include "hook.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "bgpd/bgp_rpki_clippy.c" + +DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_TEMP, "BGP RPKI Intermediate Buffer"); +DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_CACHE, "BGP RPKI Cache server"); +DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_CACHE_GROUP, "BGP RPKI Cache server group"); +DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_RTRLIB, "BGP RPKI RTRLib"); +DEFINE_MTYPE_STATIC(BGPD, BGP_RPKI_REVALIDATE, "BGP RPKI Revalidation"); + +#define STR_SEPARATOR 10 + +#define POLLING_PERIOD_DEFAULT 3600 +#define EXPIRE_INTERVAL_DEFAULT 7200 +#define RETRY_INTERVAL_DEFAULT 600 +#define BGP_RPKI_CACHE_SERVER_SYNC_RETRY_TIMEOUT 3 + +#define RPKI_DEBUG(...) \ + if (rpki_debug_conf || rpki_debug_term) { \ + zlog_debug("RPKI: " __VA_ARGS__); \ + } + +#define RPKI_OUTPUT_STRING "Control rpki specific settings\n" + +struct cache { + enum { + TCP, +#if defined(FOUND_SSH) + SSH +#endif + } type; + struct tr_socket *tr_socket; + union { + struct tr_tcp_config *tcp_config; + struct tr_ssh_config *ssh_config; + } tr_config; + struct rtr_socket *rtr_socket; + uint8_t preference; + struct rpki_vrf *rpki_vrf; +}; + +enum return_values { SUCCESS = 0, ERROR = -1 }; + +struct rpki_for_each_record_arg { + struct vty *vty; + unsigned int *prefix_amount; + as_t as; + json_object *json; + enum asnotation_mode asnotation; +}; + +struct rpki_vrf { + struct rtr_mgr_config *rtr_config; + struct list *cache_list; + bool rtr_is_running; + bool rtr_is_stopping; + bool rtr_is_synced; + _Atomic int rtr_update_overflow; + unsigned int polling_period; + unsigned int expire_interval; + unsigned int retry_interval; + int rpki_sync_socket_rtr; + int rpki_sync_socket_bgpd; + char *vrfname; + struct event *t_rpki_sync; + + QOBJ_FIELDS; +}; + +static pthread_key_t rpki_pthread; + +static struct rpki_vrf *find_rpki_vrf(const char *vrfname); +static int bgp_rpki_vrf_update(struct vrf *vrf, bool enabled); +static int bgp_rpki_write_vrf(struct vty *vty, struct vrf *vrf); +static int bgp_rpki_hook_write_vrf(struct vty *vty, struct vrf *vrf); +static int bgp_rpki_write_debug(struct vty *vty, bool running); +static int start(struct rpki_vrf *rpki_vrf); +static void stop(struct rpki_vrf *rpki_vrf); +static int reset(bool force, struct rpki_vrf *rpki_vrf); +static struct rtr_mgr_group *get_connected_group(struct rpki_vrf *rpki_vrf); +static void print_prefix_table(struct vty *vty, struct rpki_vrf *rpki_vrf, + json_object *json, bool count_only); +static void install_cli_commands(void); +static int config_write(struct vty *vty); +static int config_on_exit(struct vty *vty); +static void free_cache(struct cache *cache); +static struct rtr_mgr_group *get_groups(struct list *cache_list); +#if defined(FOUND_SSH) +static int add_ssh_cache(struct rpki_vrf *rpki_vrf, const char *host, + const unsigned int port, const char *username, + const char *client_privkey_path, + const char *server_pubkey_path, + const uint8_t preference, const char *bindaddr); +#endif +static struct rtr_socket *create_rtr_socket(struct tr_socket *tr_socket); +static struct cache *find_cache(const uint8_t preference, + struct list *cache_list); +static void rpki_delete_all_cache_nodes(struct rpki_vrf *rpki_vrf); +static int add_tcp_cache(struct rpki_vrf *rpki_vrf, const char *host, + const char *port, const uint8_t preference, + const char *bindaddr); +static void print_record(const struct pfx_record *record, struct vty *vty, + json_object *json, enum asnotation_mode asnotation); +static bool is_synchronized(struct rpki_vrf *rpki_vrf); +static bool is_running(struct rpki_vrf *rpki_vrf); +static bool is_stopping(struct rpki_vrf *rpki_vrf); +static void route_match_free(void *rule); +static enum route_map_cmd_result_t route_match(void *rule, + const struct prefix *prefix, + + void *object); +static void *route_match_compile(const char *arg); +static void revalidate_bgp_node(struct bgp_dest *dest, afi_t afi, safi_t safi); +static void revalidate_all_routes(struct rpki_vrf *rpki_vrf); + +static bool rpki_debug_conf, rpki_debug_term; + +DECLARE_QOBJ_TYPE(rpki_vrf); +DEFINE_QOBJ_TYPE(rpki_vrf); + +struct list *rpki_vrf_list; + +static struct cmd_node rpki_node = { + .name = "rpki", + .node = RPKI_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-rpki)# ", + .config_write = config_write, + .node_exit = config_on_exit, +}; + +static struct cmd_node rpki_vrf_node = { + .name = "rpki", + .node = RPKI_VRF_NODE, + .parent_node = VRF_NODE, + .prompt = "%s(config-vrf-rpki)# ", + .config_write = NULL, + .node_exit = config_on_exit, +}; + +static const struct route_map_rule_cmd route_match_rpki_cmd = { + "rpki", route_match, route_match_compile, route_match_free}; + +static void *malloc_wrapper(size_t size) +{ + return XMALLOC(MTYPE_BGP_RPKI_RTRLIB, size); +} + +static void *realloc_wrapper(void *ptr, size_t size) +{ + return XREALLOC(MTYPE_BGP_RPKI_RTRLIB, ptr, size); +} + +static void free_wrapper(void *ptr) +{ + XFREE(MTYPE_BGP_RPKI_RTRLIB, ptr); +} + +static void init_tr_socket(struct cache *cache) +{ + if (cache->type == TCP) + tr_tcp_init(cache->tr_config.tcp_config, + cache->tr_socket); +#if defined(FOUND_SSH) + else + tr_ssh_init(cache->tr_config.ssh_config, + cache->tr_socket); +#endif +} + +static void free_tr_socket(struct cache *cache) +{ + if (cache->type == TCP) + tr_tcp_init(cache->tr_config.tcp_config, + cache->tr_socket); +#if defined(FOUND_SSH) + else + tr_ssh_init(cache->tr_config.ssh_config, + cache->tr_socket); +#endif +} + +static int rpki_validate_prefix(struct peer *peer, struct attr *attr, + const struct prefix *prefix); + +static void ipv6_addr_to_network_byte_order(const uint32_t *src, uint32_t *dest) +{ + int i; + + for (i = 0; i < 4; i++) + dest[i] = htonl(src[i]); +} + +static void ipv6_addr_to_host_byte_order(const uint32_t *src, uint32_t *dest) +{ + int i; + + for (i = 0; i < 4; i++) + dest[i] = ntohl(src[i]); +} + +static enum route_map_cmd_result_t route_match(void *rule, + const struct prefix *prefix, + void *object) +{ + int *rpki_status = rule; + struct bgp_path_info *path; + + path = object; + + if (rpki_validate_prefix(path->peer, path->attr, prefix) + == *rpki_status) { + return RMAP_MATCH; + } + + return RMAP_NOMATCH; +} + +static void *route_match_compile(const char *arg) +{ + int *rpki_status; + + rpki_status = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(int)); + + if (strcmp(arg, "valid") == 0) + *rpki_status = RPKI_VALID; + else if (strcmp(arg, "invalid") == 0) + *rpki_status = RPKI_INVALID; + else + *rpki_status = RPKI_NOTFOUND; + + return rpki_status; +} + +static void route_match_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static struct rtr_socket *create_rtr_socket(struct tr_socket *tr_socket) +{ + struct rtr_socket *rtr_socket = + XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct rtr_socket)); + rtr_socket->tr_socket = tr_socket; + return rtr_socket; +} + +static int bgp_rpki_vrf_update(struct vrf *vrf, bool enabled) +{ + struct rpki_vrf *rpki; + + if (vrf->vrf_id == VRF_DEFAULT) + rpki = find_rpki_vrf(NULL); + else + rpki = find_rpki_vrf(vrf->name); + if (!rpki) + return 0; + + if (enabled) + start(rpki); + else + stop(rpki); + return 1; +} + +/* tcp identifier : : + * ssh identifier : @: + */ +static struct rpki_vrf *find_rpki_vrf_from_ident(const char *ident) +{ +#if defined(FOUND_SSH) + struct tr_ssh_config *ssh_config; +#endif + struct tr_tcp_config *tcp_config; + struct listnode *rpki_vrf_nnode; + unsigned int cache_port, port; + struct listnode *cache_node; + struct rpki_vrf *rpki_vrf; + struct cache *cache; + bool is_tcp = true; + size_t host_len; + char *endptr; + char *host; + char *ptr; + char *buf; + + /* extract the */ + ptr = strrchr(ident, ':'); + if (!ptr) + return NULL; + + ptr++; + /* extract port */ + port = atoi(ptr); + if (port == 0) + /* not ours */ + return NULL; + + /* extract host */ + ptr--; + host_len = (size_t)(ptr - ident); + buf = XCALLOC(MTYPE_BGP_RPKI_TEMP, host_len + 1); + memcpy(buf, ident, host_len); + buf[host_len] = '\0'; + endptr = strrchr(buf, '@'); + + /* ssh session */ + if (endptr) { + host = XCALLOC(MTYPE_BGP_RPKI_TEMP, + (size_t)(buf + host_len - endptr) + 1); + memcpy(host, endptr + 1, (size_t)(buf + host_len - endptr) + 1); + is_tcp = false; + } else { + host = buf; + buf = NULL; + } + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf_list, rpki_vrf_nnode, rpki_vrf)) { + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, + cache)) { + if ((cache->type == TCP && !is_tcp) +#if defined(FOUND_SSH) + || (cache->type == SSH && is_tcp) +#endif + ) + continue; + + if (is_tcp) { + tcp_config = cache->tr_config.tcp_config; + cache_port = atoi(tcp_config->port); + if (cache_port != port) + continue; + if (strlen(tcp_config->host) != strlen(host)) + continue; + if (memcmp(tcp_config->host, host, host_len) == + 0) + break; + } +#if defined(FOUND_SSH) + else { + ssh_config = cache->tr_config.ssh_config; + if (port != ssh_config->port) + continue; + if (strmatch(ssh_config->host, host)) + break; + } +#endif + } + if (cache) + break; + } + if (host) + XFREE(MTYPE_BGP_RPKI_TEMP, host); + if (buf) + XFREE(MTYPE_BGP_RPKI_TEMP, buf); + return rpki_vrf; +} + +static struct rpki_vrf *find_rpki_vrf(const char *vrfname) +{ + struct listnode *rpki_vrf_nnode; + struct rpki_vrf *rpki_vrf; + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf_list, rpki_vrf_nnode, rpki_vrf)) { + if (!vrfname && !rpki_vrf->vrfname) + /* rpki_vrf struct of the default VRF */ + return rpki_vrf; + if (vrfname && rpki_vrf->vrfname && + strmatch(vrfname, rpki_vrf->vrfname)) + return rpki_vrf; + } + return NULL; +} + +static struct cache *find_cache(const uint8_t preference, + struct list *cache_list) +{ + struct listnode *cache_node; + struct cache *cache; + + if (!cache_list) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { + if (cache->preference == preference) + return cache; + } + return NULL; +} + +static void rpki_delete_all_cache_nodes(struct rpki_vrf *rpki_vrf) +{ + struct listnode *cache_node, *cache_next; + struct cache *cache; + + for (ALL_LIST_ELEMENTS(rpki_vrf->cache_list, cache_node, cache_next, + cache)) { + if (is_running(rpki_vrf)) + rtr_mgr_remove_group(rpki_vrf->rtr_config, + cache->preference); + listnode_delete(rpki_vrf->cache_list, cache); + } +} + +static void print_record(const struct pfx_record *record, struct vty *vty, + json_object *json, enum asnotation_mode asnotation) +{ + char ip[INET6_ADDRSTRLEN]; + json_object *json_record = NULL; + + lrtr_ip_addr_to_str(&record->prefix, ip, sizeof(ip)); + + if (!json) { + vty_out(vty, "%-40s %3u - %3u ", ip, record->min_len, + record->max_len); + vty_out(vty, ASN_FORMAT(asnotation), (as_t *)&record->asn); + vty_out(vty, "\n"); + } else { + json_record = json_object_new_object(); + json_object_string_add(json_record, "prefix", ip); + json_object_int_add(json_record, "prefixLenMin", + record->min_len); + json_object_int_add(json_record, "prefixLenMax", + record->max_len); + asn_asn2json(json_record, "asn", record->asn, asnotation); + json_object_array_add(json, json_record); + } +} + +static void print_record_by_asn(const struct pfx_record *record, void *data) +{ + struct rpki_for_each_record_arg *arg = data; + struct vty *vty = arg->vty; + + if (record->asn == arg->as) { + (*arg->prefix_amount)++; + print_record(record, vty, arg->json, arg->asnotation); + } +} + +static void print_record_cb(const struct pfx_record *record, void *data) +{ + struct rpki_for_each_record_arg *arg = data; + struct vty *vty = arg->vty; + + (*arg->prefix_amount)++; + + print_record(record, vty, arg->json, arg->asnotation); +} + +static void count_record_cb(const struct pfx_record *record, void *data) +{ + struct rpki_for_each_record_arg *arg = data; + + (*arg->prefix_amount)++; +} + +static struct rtr_mgr_group *get_groups(struct list *cache_list) +{ + struct listnode *cache_node; + struct rtr_mgr_group *rtr_mgr_groups; + struct cache *cache; + + int group_count = listcount(cache_list); + + if (group_count == 0) + return NULL; + + rtr_mgr_groups = XMALLOC(MTYPE_BGP_RPKI_CACHE_GROUP, + group_count * sizeof(struct rtr_mgr_group)); + + size_t i = 0; + + for (ALL_LIST_ELEMENTS_RO(cache_list, cache_node, cache)) { + rtr_mgr_groups[i].sockets = &cache->rtr_socket; + rtr_mgr_groups[i].sockets_len = 1; + rtr_mgr_groups[i].preference = cache->preference; + + init_tr_socket(cache); + + i++; + } + + return rtr_mgr_groups; +} + +inline bool is_synchronized(struct rpki_vrf *rpki_vrf) +{ + return rpki_vrf->rtr_is_synced; +} + +inline bool is_running(struct rpki_vrf *rpki_vrf) +{ + return rpki_vrf->rtr_is_running; +} + +inline bool is_stopping(struct rpki_vrf *rpki_vrf) +{ + return rpki_vrf->rtr_is_stopping; +} + +static void pfx_record_to_prefix(struct pfx_record *record, + struct prefix *prefix) +{ + prefix->prefixlen = record->min_len; + + if (record->prefix.ver == LRTR_IPV4) { + prefix->family = AF_INET; + prefix->u.prefix4.s_addr = htonl(record->prefix.u.addr4.addr); + } else { + prefix->family = AF_INET6; + ipv6_addr_to_network_byte_order(record->prefix.u.addr6.addr, + prefix->u.prefix6.s6_addr32); + } +} + +struct rpki_revalidate_prefix { + struct bgp *bgp; + struct prefix prefix; + afi_t afi; + safi_t safi; +}; + +static void rpki_revalidate_prefix(struct event *thread) +{ + struct rpki_revalidate_prefix *rrp = EVENT_ARG(thread); + struct bgp_dest *match, *node; + + match = bgp_table_subtree_lookup(rrp->bgp->rib[rrp->afi][rrp->safi], + &rrp->prefix); + + node = match; + + while (node) { + if (bgp_dest_has_bgp_path_info_data(node)) { + revalidate_bgp_node(node, rrp->afi, rrp->safi); + } + + node = bgp_route_next_until(node, match); + } + + XFREE(MTYPE_BGP_RPKI_REVALIDATE, rrp); +} + +static void bgpd_sync_callback(struct event *thread) +{ + struct bgp *bgp; + struct listnode *node; + struct prefix prefix; + struct pfx_record rec; + struct rpki_vrf *rpki_vrf = EVENT_ARG(thread); + struct vrf *vrf = NULL; + + event_add_read(bm->master, bgpd_sync_callback, rpki_vrf, + rpki_vrf->rpki_sync_socket_bgpd, NULL); + + if (atomic_load_explicit(&rpki_vrf->rtr_update_overflow, + memory_order_seq_cst)) { + while (read(rpki_vrf->rpki_sync_socket_bgpd, &rec, + sizeof(struct pfx_record)) != -1) + ; + + atomic_store_explicit(&rpki_vrf->rtr_update_overflow, 0, + memory_order_seq_cst); + revalidate_all_routes(rpki_vrf); + return; + } + + int retval = read(rpki_vrf->rpki_sync_socket_bgpd, &rec, + sizeof(struct pfx_record)); + if (retval != sizeof(struct pfx_record)) { + RPKI_DEBUG("Could not read from rpki_sync_socket_bgpd"); + return; + } + pfx_record_to_prefix(&rec, &prefix); + + afi_t afi = (rec.prefix.ver == LRTR_IPV4) ? AFI_IP : AFI_IP6; + + if (rpki_vrf->vrfname) { + vrf = vrf_lookup_by_name(rpki_vrf->vrfname); + if (!vrf) { + zlog_err("%s(): vrf for rpki %s not found", __func__, + rpki_vrf->vrfname); + return; + } + } + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + safi_t safi; + + if (!vrf && bgp->vrf_id != VRF_DEFAULT) + continue; + if (vrf && bgp->vrf_id != vrf->vrf_id) + continue; + + for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) { + struct bgp_table *table = bgp->rib[afi][safi]; + struct rpki_revalidate_prefix *rrp; + + if (!table) + continue; + + rrp = XCALLOC(MTYPE_BGP_RPKI_REVALIDATE, sizeof(*rrp)); + rrp->bgp = bgp; + rrp->prefix = prefix; + rrp->afi = afi; + rrp->safi = safi; + event_add_event(bm->master, rpki_revalidate_prefix, rrp, + 0, &bgp->t_revalidate[afi][safi]); + } + } +} + +static void revalidate_bgp_node(struct bgp_dest *bgp_dest, afi_t afi, + safi_t safi) +{ + struct bgp_adj_in *ain; + mpls_label_t *label; + uint8_t num_labels; + + for (ain = bgp_dest->adj_in; ain; ain = ain->next) { + struct bgp_path_info *path = + bgp_dest_get_bgp_path_info(bgp_dest); + + num_labels = BGP_PATH_INFO_NUM_LABELS(path); + label = num_labels ? path->extra->labels->label : NULL; + + (void)bgp_update(ain->peer, bgp_dest_get_prefix(bgp_dest), + ain->addpath_rx_id, ain->attr, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, label, + num_labels, 1, NULL); + } +} + +/* + * The act of a soft reconfig in revalidation is really expensive + * coupled with the fact that the download of a full rpki state + * from a rpki server can be expensive, let's break up the revalidation + * to a point in time in the future to allow other bgp events + * to take place too. + */ +struct rpki_revalidate_peer { + afi_t afi; + safi_t safi; + struct peer *peer; +}; + +static void bgp_rpki_revalidate_peer(struct event *thread) +{ + struct rpki_revalidate_peer *rvp = EVENT_ARG(thread); + + /* + * Here's the expensive bit of gnomish deviousness + */ + bgp_soft_reconfig_in(rvp->peer, rvp->afi, rvp->safi); + + XFREE(MTYPE_BGP_RPKI_REVALIDATE, rvp); +} + +static void revalidate_all_routes(struct rpki_vrf *rpki_vrf) +{ + struct bgp *bgp; + struct listnode *node; + struct vrf *vrf = NULL; + + if (rpki_vrf->vrfname) { + vrf = vrf_lookup_by_name(rpki_vrf->vrfname); + if (!vrf) { + zlog_err("%s(): vrf for rpki %s not found", __func__, + rpki_vrf->vrfname); + return; + } + } + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + struct peer *peer; + struct listnode *peer_listnode; + + if (!vrf && bgp->vrf_id != VRF_DEFAULT) + continue; + if (vrf && bgp->vrf_id != vrf->vrf_id) + continue; + + for (ALL_LIST_ELEMENTS_RO(bgp->peer, peer_listnode, peer)) { + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + struct rpki_revalidate_peer *rvp; + + if (!bgp->rib[afi][safi]) + continue; + + if (!peer_established(peer->connection)) + continue; + + rvp = XCALLOC(MTYPE_BGP_RPKI_REVALIDATE, + sizeof(*rvp)); + rvp->peer = peer; + rvp->afi = afi; + rvp->safi = safi; + + event_add_event( + bm->master, bgp_rpki_revalidate_peer, + rvp, 0, + &peer->t_revalidate_all[afi][safi]); + } + } + } +} + +static void rpki_update_cb_sync_rtr(struct pfx_table *p __attribute__((unused)), + const struct pfx_record rec, + const bool added __attribute__((unused))) +{ + struct rpki_vrf *rpki_vrf; + const char *msg; + const struct rtr_socket *rtr = rec.socket; + const char *ident; + + if (!rtr) { + msg = "could not find rtr_socket from cb_sync_rtr"; + goto err; + } + if (!rtr->tr_socket) { + msg = "could not find tr_socket from cb_sync_rtr"; + goto err; + } + ident = rtr->tr_socket->ident_fp(rtr->tr_socket->socket); + if (!ident) { + msg = "could not find ident from cb_sync_rtr"; + goto err; + } + rpki_vrf = find_rpki_vrf_from_ident(ident); + if (!rpki_vrf) { + msg = "could not find rpki_vrf"; + goto err; + } + + if (is_stopping(rpki_vrf) || + atomic_load_explicit(&rpki_vrf->rtr_update_overflow, + memory_order_seq_cst)) + return; + + int retval = write(rpki_vrf->rpki_sync_socket_rtr, &rec, + sizeof(struct pfx_record)); + if (retval == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) + atomic_store_explicit(&rpki_vrf->rtr_update_overflow, 1, + memory_order_seq_cst); + + else if (retval != sizeof(struct pfx_record)) + RPKI_DEBUG("Could not write to rpki_sync_socket_rtr"); + return; +err: + zlog_err("RPKI: %s", msg); +} + +static void rpki_init_sync_socket(struct rpki_vrf *rpki_vrf) +{ + int fds[2]; + const char *msg; + + RPKI_DEBUG("initializing sync socket"); + if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, fds) != 0) { + msg = "could not open rpki sync socketpair"; + goto err; + } + rpki_vrf->rpki_sync_socket_rtr = fds[0]; + rpki_vrf->rpki_sync_socket_bgpd = fds[1]; + + if (set_nonblocking(rpki_vrf->rpki_sync_socket_rtr) != 0) { + msg = "could not set rpki_sync_socket_rtr to non blocking"; + goto err; + } + + if (set_nonblocking(rpki_vrf->rpki_sync_socket_bgpd) != 0) { + msg = "could not set rpki_sync_socket_bgpd to non blocking"; + goto err; + } + + + event_add_read(bm->master, bgpd_sync_callback, rpki_vrf, + rpki_vrf->rpki_sync_socket_bgpd, NULL); + + return; + +err: + zlog_err("RPKI: %s", msg); + abort(); + +} + +static struct rpki_vrf *bgp_rpki_allocate(const char *vrfname) +{ + struct rpki_vrf *rpki_vrf; + + /* initialise default vrf cache list */ + rpki_vrf = XCALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct rpki_vrf)); + + rpki_vrf->cache_list = list_new(); + rpki_vrf->cache_list->del = (void (*)(void *)) & free_cache; + rpki_vrf->polling_period = POLLING_PERIOD_DEFAULT; + rpki_vrf->expire_interval = EXPIRE_INTERVAL_DEFAULT; + rpki_vrf->retry_interval = RETRY_INTERVAL_DEFAULT; + + if (vrfname && !strmatch(vrfname, VRF_DEFAULT_NAME)) + rpki_vrf->vrfname = XSTRDUP(MTYPE_BGP_RPKI_CACHE, vrfname); + QOBJ_REG(rpki_vrf, rpki_vrf); + listnode_add(rpki_vrf_list, rpki_vrf); + + return rpki_vrf; +} + +static int bgp_rpki_init(struct event_loop *master) +{ + rpki_debug_conf = false; + rpki_debug_term = false; + + rpki_vrf_list = list_new(); + + install_cli_commands(); + + return 0; +} + +static int bgp_rpki_fini(void) +{ + struct listnode *node, *nnode; + struct rpki_vrf *rpki_vrf; + + for (ALL_LIST_ELEMENTS(rpki_vrf_list, node, nnode, rpki_vrf)) { + stop(rpki_vrf); + list_delete(&rpki_vrf->cache_list); + + close(rpki_vrf->rpki_sync_socket_rtr); + close(rpki_vrf->rpki_sync_socket_bgpd); + + listnode_delete(rpki_vrf_list, rpki_vrf); + QOBJ_UNREG(rpki_vrf); + if (rpki_vrf->vrfname) + XFREE(MTYPE_BGP_RPKI_CACHE, rpki_vrf->vrfname); + XFREE(MTYPE_BGP_RPKI_CACHE, rpki_vrf); + } + + return 0; +} + +static int bgp_rpki_module_init(void) +{ + pthread_key_create(&rpki_pthread, NULL); + + lrtr_set_alloc_functions(malloc_wrapper, realloc_wrapper, free_wrapper); + + hook_register(bgp_rpki_prefix_status, rpki_validate_prefix); + hook_register(frr_late_init, bgp_rpki_init); + hook_register(frr_early_fini, bgp_rpki_fini); + hook_register(bgp_hook_config_write_debug, &bgp_rpki_write_debug); + hook_register(bgp_hook_vrf_update, &bgp_rpki_vrf_update); + hook_register(bgp_hook_config_write_vrf, &bgp_rpki_hook_write_vrf); + + return 0; +} + +static void sync_expired(struct event *thread) +{ + struct rpki_vrf *rpki_vrf = EVENT_ARG(thread); + + if (!rtr_mgr_conf_in_sync(rpki_vrf->rtr_config)) { + RPKI_DEBUG("rtr_mgr is not synced, retrying."); + event_add_timer(bm->master, sync_expired, rpki_vrf, + BGP_RPKI_CACHE_SERVER_SYNC_RETRY_TIMEOUT, + &rpki_vrf->t_rpki_sync); + return; + } + + RPKI_DEBUG("rtr_mgr sync is done."); + + rpki_vrf->rtr_is_synced = true; +} + +static int start(struct rpki_vrf *rpki_vrf) +{ + struct list *cache_list = NULL; + struct vrf *vrf; + int ret; + + rpki_vrf->rtr_is_stopping = false; + rpki_vrf->rtr_is_synced = false; + rpki_vrf->rtr_update_overflow = 0; + cache_list = rpki_vrf->cache_list; + rpki_vrf->rtr_update_overflow = 0; + + if (!cache_list || list_isempty(cache_list)) { + RPKI_DEBUG( + "No caches were found in config. Prefix validation is off."); + return ERROR; + } + + if (rpki_vrf->vrfname) + vrf = vrf_lookup_by_name(rpki_vrf->vrfname); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + if (!vrf || !CHECK_FLAG(vrf->status, VRF_ACTIVE)) { + RPKI_DEBUG("VRF %s not present or disabled", rpki_vrf->vrfname); + return ERROR; + } + + RPKI_DEBUG("Init rtr_mgr (%s).", vrf->name); + int groups_len = listcount(cache_list); + struct rtr_mgr_group *groups = get_groups(rpki_vrf->cache_list); + + RPKI_DEBUG("Polling period: %d", rpki_vrf->polling_period); + ret = rtr_mgr_init(&rpki_vrf->rtr_config, groups, groups_len, + rpki_vrf->polling_period, rpki_vrf->expire_interval, + rpki_vrf->retry_interval, rpki_update_cb_sync_rtr, + NULL, NULL, NULL); + if (ret == RTR_ERROR) { + RPKI_DEBUG("Init rtr_mgr failed (%s).", vrf->name); + return ERROR; + } + + RPKI_DEBUG("Starting rtr_mgr (%s).", vrf->name); + ret = rtr_mgr_start(rpki_vrf->rtr_config); + if (ret == RTR_ERROR) { + RPKI_DEBUG("Starting rtr_mgr failed (%s).", vrf->name); + rtr_mgr_free(rpki_vrf->rtr_config); + return ERROR; + } + + event_add_timer(bm->master, sync_expired, rpki_vrf, 0, + &rpki_vrf->t_rpki_sync); + + XFREE(MTYPE_BGP_RPKI_CACHE_GROUP, groups); + + rpki_vrf->rtr_is_running = true; + + return SUCCESS; +} + +static void stop(struct rpki_vrf *rpki_vrf) +{ + rpki_vrf->rtr_is_stopping = true; + if (is_running(rpki_vrf)) { + EVENT_OFF(rpki_vrf->t_rpki_sync); + rtr_mgr_stop(rpki_vrf->rtr_config); + rtr_mgr_free(rpki_vrf->rtr_config); + rpki_vrf->rtr_is_running = false; + } +} + +static int reset(bool force, struct rpki_vrf *rpki_vrf) +{ + if (is_running(rpki_vrf) && !force) + return SUCCESS; + + RPKI_DEBUG("Resetting RPKI Session"); + stop(rpki_vrf); + return start(rpki_vrf); +} + +static struct rtr_mgr_group *get_connected_group(struct rpki_vrf *rpki_vrf) +{ + struct list *cache_list; + + if (!rpki_vrf) + return NULL; + + cache_list = rpki_vrf->cache_list; + if (!cache_list || list_isempty(cache_list)) + return NULL; + + return rtr_mgr_get_first_group(rpki_vrf->rtr_config); +} + +static void print_prefix_table_by_asn(struct vty *vty, as_t as, + struct rpki_vrf *rpki_vrf, + json_object *json) +{ + unsigned int number_of_ipv4_prefixes = 0; + unsigned int number_of_ipv6_prefixes = 0; + struct rtr_mgr_group *group = get_connected_group(rpki_vrf); + struct rpki_for_each_record_arg arg; + json_object *json_records = NULL; + + arg.vty = vty; + arg.as = as; + arg.json = NULL; + arg.asnotation = bgp_get_asnotation(bgp_lookup_by_vrf_id(VRF_DEFAULT)); + + if (!rpki_vrf) + return; + + if (!group) { + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); + vty_json(vty, json); + } else + vty_out(vty, "Cannot find a connected group.\n"); + return; + } + + struct pfx_table *pfx_table = group->sockets[0]->pfx_table; + + if (!json) { + vty_out(vty, "RPKI/RTR prefix table\n"); + vty_out(vty, "%-40s %s %s\n", "Prefix", "Prefix Length", + "Origin-AS"); + } else { + json_records = json_object_new_array(); + json_object_object_add(json, "prefixes", json_records); + arg.json = json_records; + } + + arg.prefix_amount = &number_of_ipv4_prefixes; + pfx_table_for_each_ipv4_record(pfx_table, print_record_by_asn, &arg); + + arg.prefix_amount = &number_of_ipv6_prefixes; + pfx_table_for_each_ipv6_record(pfx_table, print_record_by_asn, &arg); + + if (!json) { + vty_out(vty, "Number of IPv4 Prefixes: %u\n", + number_of_ipv4_prefixes); + vty_out(vty, "Number of IPv6 Prefixes: %u\n", + number_of_ipv6_prefixes); + } else { + json_object_int_add(json, "ipv4PrefixCount", + number_of_ipv4_prefixes); + json_object_int_add(json, "ipv6PrefixCount", + number_of_ipv6_prefixes); + } + + if (json) + vty_json(vty, json); +} + +static void print_prefix_table(struct vty *vty, struct rpki_vrf *rpki_vrf, + json_object *json, bool count_only) +{ + struct rpki_for_each_record_arg arg; + + unsigned int number_of_ipv4_prefixes = 0; + unsigned int number_of_ipv6_prefixes = 0; + struct rtr_mgr_group *group; + json_object *json_records = NULL; + + if (!rpki_vrf) + return; + + group = get_connected_group(rpki_vrf); + arg.vty = vty; + arg.json = NULL; + arg.asnotation = bgp_get_asnotation(bgp_lookup_by_vrf_id(VRF_DEFAULT)); + + if (!group) { + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); + vty_json(vty, json); + } else + vty_out(vty, "Cannot find a connected group.\n"); + return; + } + + struct pfx_table *pfx_table = group->sockets[0]->pfx_table; + + if (!count_only) { + if (!json) { + vty_out(vty, "RPKI/RTR prefix table\n"); + vty_out(vty, "%-40s %s %s\n", "Prefix", + "Prefix Length", "Origin-AS"); + } else { + json_records = json_object_new_array(); + json_object_object_add(json, "prefixes", json_records); + arg.json = json_records; + } + } + + arg.prefix_amount = &number_of_ipv4_prefixes; + if (count_only) + pfx_table_for_each_ipv4_record(pfx_table, count_record_cb, &arg); + else + pfx_table_for_each_ipv4_record(pfx_table, print_record_cb, &arg); + + arg.prefix_amount = &number_of_ipv6_prefixes; + if (count_only) + pfx_table_for_each_ipv6_record(pfx_table, count_record_cb, &arg); + else + pfx_table_for_each_ipv6_record(pfx_table, print_record_cb, &arg); + + if (!json) { + vty_out(vty, "Number of IPv4 Prefixes: %u\n", + number_of_ipv4_prefixes); + vty_out(vty, "Number of IPv6 Prefixes: %u\n", + number_of_ipv6_prefixes); + } else { + json_object_int_add(json, "ipv4PrefixCount", + number_of_ipv4_prefixes); + json_object_int_add(json, "ipv6PrefixCount", + number_of_ipv6_prefixes); + } + + if (json) + vty_json(vty, json); +} + +static int rpki_validate_prefix(struct peer *peer, struct attr *attr, + const struct prefix *prefix) +{ + struct assegment *as_segment; + as_t as_number = 0; + struct lrtr_ip_addr ip_addr_prefix; + enum pfxv_state result; + struct bgp *bgp = peer->bgp; + struct vrf *vrf; + struct rpki_vrf *rpki_vrf; + + if (!bgp) + return 0; + + vrf = vrf_lookup_by_id(bgp->vrf_id); + if (!vrf) + return 0; + + if (vrf->vrf_id == VRF_DEFAULT) + rpki_vrf = find_rpki_vrf(NULL); + else + rpki_vrf = find_rpki_vrf(vrf->name); + if (!rpki_vrf || !is_synchronized(rpki_vrf)) + return 0; + + if (!is_synchronized(rpki_vrf)) + return RPKI_NOT_BEING_USED; + + // No aspath means route comes from iBGP + if (!attr->aspath || !attr->aspath->segments) { + // Set own as number + as_number = peer->bgp->as; + } else { + as_segment = attr->aspath->segments; + // Find last AsSegment + while (as_segment->next) + as_segment = as_segment->next; + + if (as_segment->type == AS_SEQUENCE) { + // Get rightmost asn + as_number = as_segment->as[as_segment->length - 1]; + } else if (as_segment->type == AS_CONFED_SEQUENCE + || as_segment->type == AS_CONFED_SET) { + // Set own as number + as_number = peer->bgp->as; + } else { + // RFC says: "Take distinguished value NONE as asn" + // which means state is unknown + return RPKI_NOTFOUND; + } + } + + // Get the prefix in requested format + switch (prefix->family) { + case AF_INET: + ip_addr_prefix.ver = LRTR_IPV4; + ip_addr_prefix.u.addr4.addr = ntohl(prefix->u.prefix4.s_addr); + break; + + case AF_INET6: + ip_addr_prefix.ver = LRTR_IPV6; + ipv6_addr_to_host_byte_order(prefix->u.prefix6.s6_addr32, + ip_addr_prefix.u.addr6.addr); + break; + + default: + return RPKI_NOT_BEING_USED; + } + + // Do the actual validation + rtr_mgr_validate(rpki_vrf->rtr_config, as_number, &ip_addr_prefix, + prefix->prefixlen, &result); + + // Print Debug output + switch (result) { + case BGP_PFXV_STATE_VALID: + RPKI_DEBUG( + "Validating Prefix %pFX from asn %u Result: VALID", + prefix, as_number); + return RPKI_VALID; + case BGP_PFXV_STATE_NOT_FOUND: + RPKI_DEBUG( + "Validating Prefix %pFX from asn %u Result: NOT FOUND", + prefix, as_number); + return RPKI_NOTFOUND; + case BGP_PFXV_STATE_INVALID: + RPKI_DEBUG( + "Validating Prefix %pFX from asn %u Result: INVALID", + prefix, as_number); + return RPKI_INVALID; + default: + RPKI_DEBUG( + "Validating Prefix %pFX from asn %u Result: CANNOT VALIDATE", + prefix, as_number); + break; + } + return RPKI_NOT_BEING_USED; +} + +static int add_cache(struct cache *cache) +{ + uint8_t preference = cache->preference; + struct rtr_mgr_group group; + struct list *cache_list; + struct rpki_vrf *rpki_vrf; + + rpki_vrf = cache->rpki_vrf; + if (!rpki_vrf) + return ERROR; + + group.preference = preference; + group.sockets_len = 1; + group.sockets = &cache->rtr_socket; + + cache_list = rpki_vrf->cache_list; + if (!cache_list) + return ERROR; + + if (is_running(rpki_vrf)) { + init_tr_socket(cache); + + if (rtr_mgr_add_group(rpki_vrf->rtr_config, &group) != + RTR_SUCCESS) { + free_tr_socket(cache); + return ERROR; + } + } + + listnode_add(cache_list, cache); + + return SUCCESS; +} + +static int rpki_create_socket(void *_cache) +{ + struct timeval prev_snd_tmout, prev_rcv_tmout, timeout; + struct cache *cache = (struct cache *)_cache; + struct rpki_vrf *rpki_vrf; + struct tr_tcp_config *tcp_config; + struct addrinfo *res = NULL; + struct addrinfo hints = {}; + socklen_t optlen; + char *host, *port; + struct vrf *vrf; + int cancel_state; + int socket; + int ret; +#if defined(FOUND_SSH) + struct tr_ssh_config *ssh_config; + char s_port[10]; +#endif + + if (!cache) + return -1; + + rpki_vrf = cache->rpki_vrf; + + /* + * the rpki infrastructure can call this function + * multiple times per pthread. Why? I have absolutely + * no idea, and I am not sure I care a whole bunch. + * Why does this matter? Well when we attempt to + * hook this pthread into the rcu structure multiple + * times the rcu code asserts on shutdown. Clearly + * upset that you have rcu data associated with a pthread + * that has not been cleaned up. And frankly this is rightly so. + * + * At this point we know that this function is not + * called a million bajillion times so let's just + * add a bit of insurance by looking to see if + * some thread specific code has been set for this + * pthread. If not, hook into the rcu code and + * make things happy. + * + * IF YOU PUT A ZLOG_XXXX prior to the call into + * frr_pthread_non_controlled_startup in this function + * BGP WILL CRASH. You have been warned. + */ + if (!pthread_getspecific(rpki_pthread) && + frr_pthread_non_controlled_startup(cache->rtr_socket->thread_id, + "RPKI RTRLIB socket", + "rpki_create_socket") < 0) + return -1; + + pthread_setspecific(rpki_pthread, &rpki_pthread); + + if (rpki_vrf->vrfname == NULL) + vrf = vrf_lookup_by_id(VRF_DEFAULT); + else + vrf = vrf_lookup_by_name(rpki_vrf->vrfname); + if (!vrf) + return -1; + + if (!CHECK_FLAG(vrf->status, VRF_ACTIVE) || vrf->vrf_id == VRF_UNKNOWN) + return -1; + + if (cache->type == TCP) { + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + + tcp_config = cache->tr_config.tcp_config; + host = tcp_config->host; + port = tcp_config->port; + } +#if defined(FOUND_SSH) + else { + ssh_config = cache->tr_config.ssh_config; + host = ssh_config->host; + snprintf(s_port, sizeof(s_port), "%u", ssh_config->port); + port = s_port; + + hints.ai_flags |= AI_NUMERICHOST; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + } +#endif + + frr_with_privs (&bgpd_privs) { + ret = vrf_getaddrinfo(host, port, &hints, &res, vrf->vrf_id); + } + if (ret != 0) { + flog_err_sys(EC_LIB_SOCKET, "getaddrinfo: %s", + gai_strerror(ret)); + return -1; + } + + frr_with_privs (&bgpd_privs) { + socket = vrf_socket(res->ai_family, res->ai_socktype, + res->ai_protocol, vrf->vrf_id, NULL); + } + if (socket < 0) { + freeaddrinfo(res); + return -1; + } + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &cancel_state); + timeout.tv_sec = 30; + timeout.tv_usec = 0; + + optlen = sizeof(prev_rcv_tmout); + ret = getsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &prev_rcv_tmout, + &optlen); + if (ret < 0) + zlog_warn("%s: failed to getsockopt SO_RCVTIMEO for socket %d", + __func__, socket); + ret = getsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &prev_snd_tmout, + &optlen); + if (ret < 0) + zlog_warn("%s: failed to getsockopt SO_SNDTIMEO for socket %d", + __func__, socket); + ret = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, + sizeof(timeout)); + if (ret < 0) + zlog_warn("%s: failed to setsockopt SO_RCVTIMEO for socket %d", + __func__, socket); + + ret = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, + sizeof(timeout)); + if (ret < 0) + zlog_warn("%s: failed to setsockopt SO_SNDTIMEO for socket %d", + __func__, socket); + + if (connect(socket, res->ai_addr, res->ai_addrlen) == -1) { + freeaddrinfo(res); + close(socket); + pthread_setcancelstate(cancel_state, NULL); + return -1; + } + + freeaddrinfo(res); + pthread_setcancelstate(cancel_state, NULL); + + ret = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &prev_rcv_tmout, + sizeof(prev_rcv_tmout)); + if (ret < 0) + zlog_warn("%s: failed to setsockopt SO_RCVTIMEO for socket %d", + __func__, socket); + + ret = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &prev_snd_tmout, + sizeof(prev_snd_tmout)); + if (ret < 0) + zlog_warn("%s: failed to setsockopt SO_SNDTIMEO for socket %d", + __func__, socket); + + return socket; +} + +static int add_tcp_cache(struct rpki_vrf *rpki_vrf, const char *host, + const char *port, const uint8_t preference, + const char *bindaddr) +{ + struct rtr_socket *rtr_socket; + struct tr_tcp_config *tcp_config = + XCALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_tcp_config)); + struct tr_socket *tr_socket = + XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_socket)); + struct cache *cache = + XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct cache)); + + tcp_config->host = XSTRDUP(MTYPE_BGP_RPKI_CACHE, host); + tcp_config->port = XSTRDUP(MTYPE_BGP_RPKI_CACHE, port); + if (bindaddr) + tcp_config->bindaddr = XSTRDUP(MTYPE_BGP_RPKI_CACHE, bindaddr); + else + tcp_config->bindaddr = NULL; + + tcp_config->data = cache; + tcp_config->new_socket = rpki_create_socket; + rtr_socket = create_rtr_socket(tr_socket); + + cache->rpki_vrf = rpki_vrf; + cache->type = TCP; + cache->tr_socket = tr_socket; + cache->tr_config.tcp_config = tcp_config; + cache->rtr_socket = rtr_socket; + cache->preference = preference; + + int ret = add_cache(cache); + if (ret != SUCCESS) { + tcp_config->data = NULL; + free_cache(cache); + } + return ret; +} + +#if defined(FOUND_SSH) +static int add_ssh_cache(struct rpki_vrf *rpki_vrf, const char *host, + const unsigned int port, const char *username, + const char *client_privkey_path, + const char *server_pubkey_path, + const uint8_t preference, const char *bindaddr) +{ + struct tr_ssh_config *ssh_config = + XCALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_ssh_config)); + struct cache *cache = + XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct cache)); + struct tr_socket *tr_socket = + XMALLOC(MTYPE_BGP_RPKI_CACHE, sizeof(struct tr_socket)); + struct rtr_socket *rtr_socket; + + ssh_config->port = port; + ssh_config->host = XSTRDUP(MTYPE_BGP_RPKI_CACHE, host); + if (bindaddr) + ssh_config->bindaddr = XSTRDUP(MTYPE_BGP_RPKI_CACHE, bindaddr); + else + ssh_config->bindaddr = NULL; + ssh_config->data = cache; + ssh_config->new_socket = rpki_create_socket; + + ssh_config->username = XSTRDUP(MTYPE_BGP_RPKI_CACHE, username); + ssh_config->client_privkey_path = + XSTRDUP(MTYPE_BGP_RPKI_CACHE, client_privkey_path); + ssh_config->server_hostkey_path = + XSTRDUP(MTYPE_BGP_RPKI_CACHE, server_pubkey_path); + + rtr_socket = create_rtr_socket(tr_socket); + + cache->rpki_vrf = rpki_vrf; + cache->type = SSH; + cache->tr_socket = tr_socket; + cache->tr_config.ssh_config = ssh_config; + cache->rtr_socket = rtr_socket; + cache->preference = preference; + + int ret = add_cache(cache); + if (ret != SUCCESS) { + ssh_config->data = NULL; + free_cache(cache); + } + + return ret; +} +#endif + +static void free_cache(struct cache *cache) +{ + if (cache->type == TCP) { + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config->host); + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config->port); + XFREE(MTYPE_BGP_RPKI_CACHE, + cache->tr_config.tcp_config->bindaddr); + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.tcp_config); + } +#if defined(FOUND_SSH) + else { + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config->host); + XFREE(MTYPE_BGP_RPKI_CACHE, + cache->tr_config.ssh_config->username); + XFREE(MTYPE_BGP_RPKI_CACHE, + cache->tr_config.ssh_config->client_privkey_path); + XFREE(MTYPE_BGP_RPKI_CACHE, + cache->tr_config.ssh_config->server_hostkey_path); + XFREE(MTYPE_BGP_RPKI_CACHE, + cache->tr_config.ssh_config->bindaddr); + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_config.ssh_config); + } +#endif + XFREE(MTYPE_BGP_RPKI_CACHE, cache->tr_socket); + XFREE(MTYPE_BGP_RPKI_CACHE, cache->rtr_socket); + XFREE(MTYPE_BGP_RPKI_CACHE, cache); +} + +static int bgp_rpki_write_debug(struct vty *vty, bool running) +{ + if (rpki_debug_conf && running) { + vty_out(vty, "debug rpki\n"); + return 1; + } + if ((rpki_debug_conf || rpki_debug_term) && !running) { + vty_out(vty, " BGP RPKI debugging is on\n"); + return 1; + } + return 0; +} + +static int bgp_rpki_hook_write_vrf(struct vty *vty, struct vrf *vrf) +{ + int ret; + + ret = bgp_rpki_write_vrf(vty, vrf); + if (ret == ERROR) + return 0; + return ret; +} + +static int bgp_rpki_write_vrf(struct vty *vty, struct vrf *vrf) +{ + struct listnode *cache_node; + struct cache *cache; + struct rpki_vrf *rpki_vrf = NULL; + char sep[STR_SEPARATOR]; + vrf_id_t vrf_id = VRF_DEFAULT; + + if (!vrf) { + rpki_vrf = find_rpki_vrf(NULL); + snprintf(sep, sizeof(sep), "%s", ""); + } else if (vrf->vrf_id != VRF_DEFAULT) { + rpki_vrf = find_rpki_vrf(vrf->name); + snprintf(sep, sizeof(sep), "%s", " "); + vrf_id = vrf->vrf_id; + } else + return ERROR; + + if (!rpki_vrf) + return ERROR; + + if (rpki_vrf->cache_list && list_isempty(rpki_vrf->cache_list) && + rpki_vrf->polling_period == POLLING_PERIOD_DEFAULT && + rpki_vrf->retry_interval == RETRY_INTERVAL_DEFAULT && + rpki_vrf->expire_interval == EXPIRE_INTERVAL_DEFAULT) + /* do not display the default config values */ + return 0; + + if (vrf_id == VRF_DEFAULT) + vty_out(vty, "%s!\n", sep); + vty_out(vty, "%srpki\n", sep); + + if (rpki_vrf->polling_period != POLLING_PERIOD_DEFAULT) + vty_out(vty, "%s rpki polling_period %d\n", sep, + rpki_vrf->polling_period); + if (rpki_vrf->retry_interval != RETRY_INTERVAL_DEFAULT) + vty_out(vty, "%s rpki retry_interval %d\n", sep, + rpki_vrf->retry_interval); + if (rpki_vrf->expire_interval != EXPIRE_INTERVAL_DEFAULT) + vty_out(vty, "%s rpki expire_interval %d\n", sep, + rpki_vrf->expire_interval); + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, cache)) { + switch (cache->type) { + struct tr_tcp_config *tcp_config; +#if defined(FOUND_SSH) + struct tr_ssh_config *ssh_config; +#endif + case TCP: + tcp_config = cache->tr_config.tcp_config; + vty_out(vty, "%s rpki cache tcp %s %s ", sep, + tcp_config->host, tcp_config->port); + if (tcp_config->bindaddr) + vty_out(vty, "source %s ", + tcp_config->bindaddr); + break; +#if defined(FOUND_SSH) + case SSH: + ssh_config = cache->tr_config.ssh_config; + vty_out(vty, "%s rpki cache ssh %s %u %s %s %s ", sep, + ssh_config->host, ssh_config->port, + ssh_config->username, + ssh_config->client_privkey_path, + ssh_config->server_hostkey_path != NULL + ? ssh_config->server_hostkey_path + : ""); + if (ssh_config->bindaddr) + vty_out(vty, "source %s ", + ssh_config->bindaddr); + break; +#endif + default: + break; + } + + vty_out(vty, "preference %hhu\n", cache->preference); + } + + vty_out(vty, "%sexit\n%s", sep, vrf_id == VRF_DEFAULT ? "!\n" : ""); + + return 1; +} + +static int config_write(struct vty *vty) +{ + return bgp_rpki_write_vrf(vty, NULL); +} + +static struct rpki_vrf *get_rpki_vrf(const char *vrfname) +{ + struct rpki_vrf *rpki_vrf = NULL; + struct vrf *vrf = NULL; + + if (vrfname && !strmatch(vrfname, VRF_DEFAULT_NAME)) { + vrf = vrf_lookup_by_name(vrfname); + if (!vrf) + return NULL; + rpki_vrf = find_rpki_vrf(vrf->name); + } else + /* default VRF */ + rpki_vrf = find_rpki_vrf(NULL); + + return rpki_vrf; +} + +DEFUN_NOSH (rpki, + rpki_cmd, + "rpki", + "Enable rpki and enter rpki configuration mode\n") +{ + struct rpki_vrf *rpki_vrf; + char *vrfname = NULL; + struct vrf *vrf; + + if (vty->node == CONFIG_NODE) + vty->node = RPKI_NODE; + else { + vrf = VTY_GET_CONTEXT(vrf); + + if (!vrf) + return CMD_WARNING; + + vty->node = RPKI_VRF_NODE; + if (vrf->vrf_id != VRF_DEFAULT) + vrfname = vrf->name; + } + + rpki_vrf = find_rpki_vrf(vrfname); + if (!rpki_vrf) { + rpki_vrf = bgp_rpki_allocate(vrfname); + + rpki_init_sync_socket(rpki_vrf); + } + if (vty->node == RPKI_VRF_NODE) + VTY_PUSH_CONTEXT_SUB(vty->node, rpki_vrf); + else + VTY_PUSH_CONTEXT(vty->node, rpki_vrf); + return CMD_SUCCESS; +} + +DEFPY (no_rpki, + no_rpki_cmd, + "no rpki", + NO_STR + "Enable rpki and enter rpki configuration mode\n") +{ + struct rpki_vrf *rpki_vrf; + char *vrfname = NULL; + + if (vty->node == VRF_NODE) { + VTY_DECLVAR_CONTEXT(vrf, vrf); + + if (vrf->vrf_id != VRF_DEFAULT) + vrfname = vrf->name; + } + + rpki_vrf = find_rpki_vrf(vrfname); + + rpki_delete_all_cache_nodes(rpki_vrf); + stop(rpki_vrf); + rpki_vrf->polling_period = POLLING_PERIOD_DEFAULT; + rpki_vrf->expire_interval = EXPIRE_INTERVAL_DEFAULT; + rpki_vrf->retry_interval = RETRY_INTERVAL_DEFAULT; + + return CMD_SUCCESS; +} + +DEFPY (bgp_rpki_start, + bgp_rpki_start_cmd, + "rpki start [vrf NAME$vrfname]", + RPKI_OUTPUT_STRING + "start rpki support\n" + VRF_CMD_HELP_STR) +{ + struct list *cache_list = NULL; + struct rpki_vrf *rpki_vrf; + + rpki_vrf = get_rpki_vrf(vrfname); + + if (!rpki_vrf) + return CMD_WARNING; + + cache_list = rpki_vrf->cache_list; + if (!cache_list || listcount(cache_list) == 0) + vty_out(vty, + "Could not start rpki because no caches are configured\n"); + + if (!is_running(rpki_vrf)) { + if (start(rpki_vrf) == ERROR) { + RPKI_DEBUG("RPKI failed to start"); + return CMD_WARNING; + } + } + return CMD_SUCCESS; +} + +DEFPY (bgp_rpki_stop, + bgp_rpki_stop_cmd, + "rpki stop [vrf NAME$vrfname]", + RPKI_OUTPUT_STRING + "start rpki support\n" + VRF_CMD_HELP_STR) +{ + struct rpki_vrf *rpki_vrf; + + rpki_vrf = get_rpki_vrf(vrfname); + + if (rpki_vrf && is_running(rpki_vrf)) + stop(rpki_vrf); + + return CMD_SUCCESS; +} + +DEFPY (rpki_polling_period, + rpki_polling_period_cmd, + "rpki polling_period (1-86400)$pp", + RPKI_OUTPUT_STRING + "Set polling period\n" + "Polling period value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + rpki_vrf->polling_period = pp; + return CMD_SUCCESS; +} + +DEFUN (no_rpki_polling_period, + no_rpki_polling_period_cmd, + "no rpki polling_period [(1-86400)]", + NO_STR + RPKI_OUTPUT_STRING + "Set polling period back to default\n" + "Polling period value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + rpki_vrf->polling_period = POLLING_PERIOD_DEFAULT; + return CMD_SUCCESS; +} + +DEFPY (rpki_expire_interval, + rpki_expire_interval_cmd, + "rpki expire_interval (600-172800)$tmp", + RPKI_OUTPUT_STRING + "Set expire interval\n" + "Expire interval value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + if ((unsigned int)tmp >= rpki_vrf->polling_period) { + rpki_vrf->expire_interval = tmp; + return CMD_SUCCESS; + } + + vty_out(vty, "%% Expiry interval must be polling period or larger\n"); + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (no_rpki_expire_interval, + no_rpki_expire_interval_cmd, + "no rpki expire_interval [(600-172800)]", + NO_STR + RPKI_OUTPUT_STRING + "Set expire interval back to default\n" + "Expire interval value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + rpki_vrf->expire_interval = rpki_vrf->polling_period * 2; + return CMD_SUCCESS; +} + +DEFPY (rpki_retry_interval, + rpki_retry_interval_cmd, + "rpki retry_interval (1-7200)$tmp", + RPKI_OUTPUT_STRING + "Set retry interval\n" + "retry interval value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + rpki_vrf->retry_interval = tmp; + return CMD_SUCCESS; +} + +DEFUN (no_rpki_retry_interval, + no_rpki_retry_interval_cmd, + "no rpki retry_interval [(1-7200)]", + NO_STR + RPKI_OUTPUT_STRING + "Set retry interval back to default\n" + "retry interval value\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + rpki_vrf->retry_interval = RETRY_INTERVAL_DEFAULT; + return CMD_SUCCESS; +} + +#if CONFDATE > 20240916 +CPP_NOTICE("Remove rpki_cache_cmd") +#endif +DEFPY(rpki_cache, rpki_cache_cmd, + "rpki cache [source $bindaddr] preference (1-255)", + RPKI_OUTPUT_STRING + "Install a cache server to current group\n" + "IP address of cache server\n" + "Hostname of cache server\n" + "TCP port number\n" + "SSH port number\n" + "SSH user name\n" + "Path to own SSH private key\n" + "Path to the known hosts file\n" + "Configure source IP address of RPKI connection\n" + "Define a Source IP Address\n" + "Preference of the cache server\n" + "Preference value\n") +{ + int return_value; + struct listnode *cache_node; + struct cache *current_cache; + struct rpki_vrf *rpki_vrf; + bool init; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + if (!rpki_vrf || !rpki_vrf->cache_list) + return CMD_WARNING; + + init = !!list_isempty(rpki_vrf->cache_list); + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, + current_cache)) { + if (current_cache->preference == preference) { + vty_out(vty, + "Cache with preference %ld is already configured\n", + preference); + return CMD_WARNING; + } + } + + // use ssh connection + if (ssh_uname) { +#if defined(FOUND_SSH) + return_value = add_ssh_cache(rpki_vrf, cache, sshport, ssh_uname, + ssh_privkey, known_hosts_path, + preference, bindaddr_str); +#else + return_value = SUCCESS; + vty_out(vty, + "ssh sockets are not supported. Please recompile rtrlib and frr with ssh support. If you want to use it\n"); +#endif + } else { // use tcp connection + return_value = add_tcp_cache(rpki_vrf, cache, tcpport, + preference, bindaddr_str); + } + + if (return_value == ERROR) { + vty_out(vty, "Could not create new rpki cache\n"); + return CMD_WARNING; + } + + if (init) + start(rpki_vrf); + + return CMD_SUCCESS; +} + +DEFPY(rpki_cache_tcp, rpki_cache_tcp_cmd, + "rpki cache tcp $cache TCPPORT [source $bindaddr] preference (1-255)", + RPKI_OUTPUT_STRING + "Install a cache server to current group\n" + "Use TCP\n" + "IP address of cache server\n" + "Hostname of cache server\n" + "TCP port number\n" + "Configure source IP address of RPKI connection\n" + "Define a Source IP Address\n" + "Preference of the cache server\n" + "Preference value\n") +{ + int return_value; + struct listnode *cache_node; + struct cache *current_cache; + struct rpki_vrf *rpki_vrf; + bool init; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + if (!rpki_vrf || !rpki_vrf->cache_list) + return CMD_WARNING; + + init = !!list_isempty(rpki_vrf->cache_list); + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, + current_cache)) { + if (current_cache->preference == preference) { + vty_out(vty, + "Cache with preference %ld is already configured\n", + preference); + return CMD_WARNING; + } + } + + return_value = add_tcp_cache(rpki_vrf, cache, tcpport, preference, + bindaddr_str); + + if (return_value == ERROR) { + vty_out(vty, "Could not create new rpki cache\n"); + return CMD_WARNING; + } + + if (init) + start(rpki_vrf); + + return CMD_SUCCESS; +} + +DEFPY(rpki_cache_ssh, rpki_cache_ssh_cmd, + "rpki cache ssh $cache (1-65535)$sshport SSH_UNAME SSH_PRIVKEY [KNOWN_HOSTS_PATH] [source $bindaddr] preference (1-255)", + RPKI_OUTPUT_STRING + "Install a cache server to current group\n" + "Use SSH\n" + "IP address of cache server\n" + "Hostname of cache server\n" + "SSH port number\n" + "SSH user name\n" + "Path to own SSH private key\n" + "Path to the known hosts file\n" + "Configure source IP address of RPKI connection\n" + "Define a Source IP Address\n" + "Preference of the cache server\n" + "Preference value\n") +{ + int return_value; + struct listnode *cache_node; + struct cache *current_cache; + struct rpki_vrf *rpki_vrf; + bool init; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + if (!rpki_vrf || !rpki_vrf->cache_list) + return CMD_WARNING; + + init = !!list_isempty(rpki_vrf->cache_list); + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, + current_cache)) { + if (current_cache->preference == preference) { + vty_out(vty, + "Cache with preference %ld is already configured\n", + preference); + return CMD_WARNING; + } + } + +#if defined(FOUND_SSH) + return_value = add_ssh_cache(rpki_vrf, cache, sshport, ssh_uname, + ssh_privkey, known_hosts_path, preference, + bindaddr_str); +#else + return_value = SUCCESS; + vty_out(vty, + "ssh sockets are not supported. Please recompile rtrlib and frr with ssh support. If you want to use it\n"); +#endif + + if (return_value == ERROR) { + vty_out(vty, "Could not create new rpki cache\n"); + return CMD_WARNING; + } + + if (init) + start(rpki_vrf); + + return CMD_SUCCESS; +} + +DEFPY (no_rpki_cache, + no_rpki_cache_cmd, + "no rpki cache [source $bindaddr] preference (1-255)", + NO_STR + RPKI_OUTPUT_STRING + "Install a cache server to current group\n" + "Use TCP\n" + "Use SSH\n" + "IP address of cache server\n" + "Hostname of cache server\n" + "TCP port number\n" + "SSH port number\n" + "SSH user name\n" + "Path to own SSH private key\n" + "Path to the known hosts file\n" + "Configure source IP address of RPKI connection\n" + "Define a Source IP Address\n" + "Preference of the cache server\n" + "Preference value\n") +{ + struct cache *cache_p; + struct list *cache_list = NULL; + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + cache_list = rpki_vrf->cache_list; + cache_p = find_cache(preference, cache_list); + if (!rpki_vrf || !cache_p) { + vty_out(vty, "Could not find cache with preference %ld\n", + preference); + return CMD_WARNING; + } + + if (is_running(rpki_vrf) && listcount(cache_list) == 1) { + stop(rpki_vrf); + } else if (is_running(rpki_vrf)) { + if (rtr_mgr_remove_group(rpki_vrf->rtr_config, preference) == + RTR_ERROR) { + vty_out(vty, + "Could not remove cache with preference %ld\n", + preference); + return CMD_WARNING; + } + } + + listnode_delete(cache_list, cache_p); + free_cache(cache_p); + + return CMD_SUCCESS; +} + +DEFPY (show_rpki_prefix_table, + show_rpki_prefix_table_cmd, + "show rpki $prefixkind [vrf NAME$vrfname] [json$uj]", + SHOW_STR + RPKI_OUTPUT_STRING + "Show validated prefixes which were received from RPKI Cache\n" + "Show prefixes count which were received from RPKI Cache\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct json_object *json = NULL; + struct rpki_vrf *rpki_vrf; + + if (uj) + json = json_object_new_object(); + + rpki_vrf = get_rpki_vrf(vrfname); + if (!rpki_vrf) { + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (is_synchronized(rpki_vrf)) { + if (strmatch(prefixkind, "prefix-count")) + print_prefix_table(vty, rpki_vrf, json, true); + else + print_prefix_table(vty, rpki_vrf, json, false); + } else { + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else + vty_out(vty, "No connection to RPKI cache server.\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFPY (show_rpki_as_number, + show_rpki_as_number_cmd, + "show rpki as-number <0$zero|ASNUM$by_asn> [vrf NAME$vrfname] [json$uj]", + SHOW_STR + RPKI_OUTPUT_STRING + "Lookup by ASN in prefix table\n" + "AS Number of 0, see RFC-7607\n" + "AS Number\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct json_object *json = NULL; + struct rpki_vrf *rpki_vrf; + as_t as; + + if (uj) + json = json_object_new_object(); + + rpki_vrf = get_rpki_vrf(vrfname); + if (!rpki_vrf) { + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (!is_synchronized(rpki_vrf)) { + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else + vty_out(vty, "No Connection to RPKI cache server.\n"); + return CMD_WARNING; + } + + if (zero) + as = 0; + else + as = by_asn; + + print_prefix_table_by_asn(vty, as, rpki_vrf, json); + return CMD_SUCCESS; +} + +DEFPY (show_rpki_prefix, + show_rpki_prefix_cmd, + "show rpki prefix [0$zero|ASNUM$asn] [vrf NAME$vrfname] [json$uj]", + SHOW_STR + RPKI_OUTPUT_STRING + "Lookup IP prefix and optionally ASN in prefix table\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "AS Number of 0, see RFC-7607\n" + "AS Number\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + json_object *json = NULL; + json_object *json_records = NULL; + enum asnotation_mode asnotation; + struct rpki_vrf *rpki_vrf; + as_t as; + + if (uj) + json = json_object_new_object(); + + rpki_vrf = get_rpki_vrf(vrfname); + + if (!rpki_vrf || !is_synchronized(rpki_vrf)) { + if (json) { + json_object_string_add(json, "error", "No Connection to RPKI cache server."); + vty_json(vty, json); + } else + vty_out(vty, "No Connection to RPKI cache server.\n"); + return CMD_WARNING; + } + + if (zero) + as = 0; + else + as = asn; + + struct lrtr_ip_addr addr; + char addr_str[INET6_ADDRSTRLEN]; + size_t addr_len = strchr(prefix_str, '/') - prefix_str; + + memset(addr_str, 0, sizeof(addr_str)); + memcpy(addr_str, prefix_str, addr_len); + + if (lrtr_ip_str_to_addr(addr_str, &addr) != 0) { + if (json) { + json_object_string_add(json, "error", "Invalid IP prefix."); + vty_json(vty, json); + } else + vty_out(vty, "Invalid IP prefix\n"); + return CMD_WARNING; + } + + struct pfx_record *matches = NULL; + unsigned int match_count = 0; + enum pfxv_state result; + + if (pfx_table_validate_r(rpki_vrf->rtr_config->pfx_table, &matches, + &match_count, as, &addr, prefix->prefixlen, + &result) != PFX_SUCCESS) { + if (json) { + json_object_string_add(json, "error", "Prefix lookup failed."); + vty_json(vty, json); + } else + vty_out(vty, "Prefix lookup failed\n"); + return CMD_WARNING; + } + + + if (!json) { + vty_out(vty, "%-40s %s %s\n", "Prefix", "Prefix Length", + "Origin-AS"); + } else { + json_records = json_object_new_array(); + json_object_object_add(json, "prefixes", json_records); + } + + asnotation = bgp_get_asnotation(bgp_lookup_by_vrf_id(VRF_DEFAULT)); + for (size_t i = 0; i < match_count; ++i) { + const struct pfx_record *record = &matches[i]; + + if (record->max_len >= prefix->prefixlen && + ((as != 0 && (uint32_t)as == record->asn) || asn == 0)) { + print_record(&matches[i], vty, json_records, + asnotation); + } + } + + if (json) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY (show_rpki_cache_server, + show_rpki_cache_server_cmd, + "show rpki cache-server [vrf NAME$vrfname] [json$uj]", + SHOW_STR + RPKI_OUTPUT_STRING + "Show configured cache server\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct json_object *json = NULL; + struct json_object *json_server = NULL; + struct json_object *json_servers = NULL; + struct listnode *cache_node; + struct cache *cache; + struct rpki_vrf *rpki_vrf; + + if (uj) { + json = json_object_new_object(); + json_servers = json_object_new_array(); + json_object_object_add(json, "servers", json_servers); + } + + rpki_vrf = get_rpki_vrf(vrfname); + if (!rpki_vrf) { + if (json) + vty_json(vty, json); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, cache)) { + if (cache->type == TCP) { + if (!json) { + vty_out(vty, + "host: %s port: %s, preference: %hhu, protocol: tcp", + cache->tr_config.tcp_config->host, + cache->tr_config.tcp_config->port, + cache->preference); + if (cache->tr_config.tcp_config->bindaddr) + vty_out(vty, ", source: %s\n", + cache->tr_config.tcp_config + ->bindaddr); + else + vty_out(vty, "\n"); + } else { + json_server = json_object_new_object(); + json_object_string_add(json_server, "mode", + "tcp"); + json_object_string_add( + json_server, "host", + cache->tr_config.tcp_config->host); + json_object_string_add( + json_server, "port", + cache->tr_config.tcp_config->port); + json_object_int_add(json_server, "preference", + cache->preference); + if (cache->tr_config.tcp_config->bindaddr) + json_object_string_add(json_server, + "source", + cache->tr_config + .tcp_config + ->bindaddr); + json_object_array_add(json_servers, + json_server); + } + +#if defined(FOUND_SSH) + } else if (cache->type == SSH) { + if (!json) { + vty_out(vty, + "host: %s, port: %d, username: %s, server_hostkey_path: %s, client_privkey_path: %s, preference: %hhu, protocol: ssh", + cache->tr_config.ssh_config->host, + cache->tr_config.ssh_config->port, + cache->tr_config.ssh_config->username, + cache->tr_config.ssh_config + ->server_hostkey_path, + cache->tr_config.ssh_config + ->client_privkey_path, + cache->preference); + if (cache->tr_config.ssh_config->bindaddr) + vty_out(vty, ", source: %s\n", + cache->tr_config.ssh_config + ->bindaddr); + else + vty_out(vty, "\n"); + } else { + json_server = json_object_new_object(); + json_object_string_add(json_server, "mode", + "ssh"); + json_object_string_add( + json_server, "host", + cache->tr_config.ssh_config->host); + json_object_int_add( + json_server, "port", + cache->tr_config.ssh_config->port); + json_object_string_add( + json_server, "username", + cache->tr_config.ssh_config->username); + json_object_string_add( + json_server, "serverHostkeyPath", + cache->tr_config.ssh_config + ->server_hostkey_path); + json_object_string_add( + json_server, "clientPrivkeyPath", + cache->tr_config.ssh_config + ->client_privkey_path); + json_object_int_add(json_server, "preference", + cache->preference); + if (cache->tr_config.ssh_config->bindaddr) + json_object_string_add(json_server, + "source", + cache->tr_config + .ssh_config + ->bindaddr); + json_object_array_add(json_servers, + json_server); + } +#endif + } + } + + if (json) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY (show_rpki_cache_connection, + show_rpki_cache_connection_cmd, + "show rpki cache-connection [vrf NAME$vrfname] [json$uj]", + SHOW_STR + RPKI_OUTPUT_STRING + "Show to which RPKI Cache Servers we have a connection\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct json_object *json = NULL; + struct json_object *json_conn = NULL; + struct json_object *json_conns = NULL; + struct listnode *cache_node; + struct cache *cache; + struct rtr_mgr_group *group; + struct rpki_vrf *rpki_vrf; + + if (uj) + json = json_object_new_object(); + + rpki_vrf = get_rpki_vrf(vrfname); + if (!rpki_vrf) { + if (json) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (!is_synchronized(rpki_vrf)) { + if (json) { + json_object_string_add(json, "error", "No connection to RPKI cache server."); + vty_json(vty, json); + } else + vty_out(vty, "No connection to RPKI cache server.\n"); + + return CMD_SUCCESS; + } + + group = get_connected_group(rpki_vrf); + if (!group) { + if (json) { + json_object_string_add(json, "error", "Cannot find a connected group."); + vty_json(vty, json); + } else + vty_out(vty, "Cannot find a connected group.\n"); + + return CMD_SUCCESS; + } + + if (!json) { + vty_out(vty, "Connected to group %d\n", group->preference); + } else { + json_conns = json_object_new_array(); + json_object_int_add(json, "connectedGroup", group->preference); + json_object_object_add(json, "connections", json_conns); + } + + for (ALL_LIST_ELEMENTS_RO(rpki_vrf->cache_list, cache_node, cache)) { + struct tr_tcp_config *tcp_config; +#if defined(FOUND_SSH) + struct tr_ssh_config *ssh_config; +#endif + switch (cache->type) { + case TCP: + tcp_config = cache->tr_config.tcp_config; + + if (!json) { + vty_out(vty, + "rpki tcp cache %s %s pref %hhu%s\n", + tcp_config->host, tcp_config->port, + cache->preference, + cache->rtr_socket->state == + RTR_ESTABLISHED + ? " (connected)" + : ""); + } else { + json_conn = json_object_new_object(); + json_object_string_add(json_conn, "mode", + "tcp"); + json_object_string_add(json_conn, "host", + tcp_config->host); + json_object_string_add(json_conn, "port", + tcp_config->port); + json_object_int_add(json_conn, "preference", + cache->preference); + json_object_string_add( + json_conn, "state", + cache->rtr_socket->state == + RTR_ESTABLISHED + ? "connected" + : "disconnected"); + json_object_array_add(json_conns, json_conn); + } + break; +#if defined(FOUND_SSH) + case SSH: + ssh_config = cache->tr_config.ssh_config; + + if (!json) { + vty_out(vty, + "rpki ssh cache %s %u pref %hhu%s\n", + ssh_config->host, ssh_config->port, + cache->preference, + cache->rtr_socket->state == + RTR_ESTABLISHED + ? " (connected)" + : ""); + } else { + json_conn = json_object_new_object(); + json_object_string_add(json_conn, "mode", + "ssh"); + json_object_string_add(json_conn, "host", + ssh_config->host); + json_object_int_add(json_conn, "port", + ssh_config->port); + json_object_int_add(json_conn, "preference", + cache->preference); + json_object_string_add( + json_conn, "state", + cache->rtr_socket->state == + RTR_ESTABLISHED + ? "connected" + : "disconnected"); + json_object_array_add(json_conns, json_conn); + } + break; +#endif + default: + break; + } + } + + if (json) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY(show_rpki_configuration, show_rpki_configuration_cmd, + "show rpki configuration [vrf NAME$vrfname] [json$uj]", + SHOW_STR RPKI_OUTPUT_STRING + "Show RPKI configuration\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + struct json_object *json = NULL; + struct rpki_vrf *rpki_vrf; + + if (uj) + json = json_object_new_object(); + + rpki_vrf = find_rpki_vrf(vrfname); + if (!rpki_vrf) { + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (uj) { + json_object_boolean_add(json, "enabled", + !!listcount(rpki_vrf->cache_list)); + json_object_int_add(json, "serversCount", + listcount(rpki_vrf->cache_list)); + json_object_int_add(json, "pollingPeriodSeconds", + rpki_vrf->polling_period); + json_object_int_add(json, "retryIntervalSeconds", + rpki_vrf->retry_interval); + json_object_int_add(json, "expireIntervalSeconds", + rpki_vrf->expire_interval); + + vty_json(vty, json); + + return CMD_SUCCESS; + } + + vty_out(vty, "rpki is %s", + listcount(rpki_vrf->cache_list) ? "Enabled" : "Disabled"); + + if (list_isempty(rpki_vrf->cache_list)) { + vty_out(vty, "\n"); + return CMD_SUCCESS; + } + + vty_out(vty, " (%d cache servers configured)", + listcount(rpki_vrf->cache_list)); + vty_out(vty, "\n"); + vty_out(vty, "\tpolling period %d\n", rpki_vrf->polling_period); + vty_out(vty, "\tretry interval %d\n", rpki_vrf->retry_interval); + vty_out(vty, "\texpire interval %d\n", rpki_vrf->expire_interval); + + return CMD_SUCCESS; +} + +static int config_on_exit(struct vty *vty) +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + reset(false, rpki_vrf); + return 1; +} + +DEFPY(rpki_reset, + rpki_reset_cmd, + "rpki reset [vrf NAME$vrfname]", + RPKI_OUTPUT_STRING + "reset rpki\n" + VRF_CMD_HELP_STR) +{ + struct rpki_vrf *rpki_vrf; + + rpki_vrf = find_rpki_vrf(vrfname); + if (!rpki_vrf) + return CMD_WARNING; + + return reset(true, rpki_vrf) == SUCCESS ? CMD_SUCCESS : CMD_WARNING; +} + +DEFPY (rpki_reset_config_mode, + rpki_reset_config_mode_cmd, + "rpki reset", + RPKI_OUTPUT_STRING + "reset rpki\n") +{ + struct rpki_vrf *rpki_vrf; + + if (vty->node == RPKI_VRF_NODE) + rpki_vrf = VTY_GET_CONTEXT_SUB(rpki_vrf); + else + rpki_vrf = VTY_GET_CONTEXT(rpki_vrf); + + if (!rpki_vrf) + return CMD_WARNING_CONFIG_FAILED; + + return reset(true, rpki_vrf) == SUCCESS ? CMD_SUCCESS : CMD_WARNING; +} + +DEFUN (debug_rpki, + debug_rpki_cmd, + "debug rpki", + DEBUG_STR + "Enable debugging for rpki\n") +{ + if (vty->node == CONFIG_NODE) + rpki_debug_conf = true; + else + rpki_debug_term = true; + return CMD_SUCCESS; +} + +DEFUN (no_debug_rpki, + no_debug_rpki_cmd, + "no debug rpki", + NO_STR + DEBUG_STR + "Disable debugging for rpki\n") +{ + if (vty->node == CONFIG_NODE) + rpki_debug_conf = false; + else + rpki_debug_term = false; + return CMD_SUCCESS; +} + +DEFUN_YANG (match_rpki, + match_rpki_cmd, + "match rpki ", + MATCH_STR + RPKI_OUTPUT_STRING + "Valid prefix\n" + "Invalid prefix\n" + "Prefix not found\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:rpki']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:rpki", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, argv[2]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_match_rpki, + no_match_rpki_cmd, + "no match rpki ", + NO_STR + MATCH_STR + RPKI_OUTPUT_STRING + "Valid prefix\n" + "Invalid prefix\n" + "Prefix not found\n") +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:rpki']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static void install_cli_commands(void) +{ + // TODO: make config write work + install_node(&rpki_node); + install_default(RPKI_NODE); + install_node(&rpki_vrf_node); + install_default(RPKI_VRF_NODE); + install_element(CONFIG_NODE, &rpki_cmd); + install_element(CONFIG_NODE, &no_rpki_cmd); + + + install_element(ENABLE_NODE, &bgp_rpki_start_cmd); + install_element(ENABLE_NODE, &bgp_rpki_stop_cmd); + + /* Install rpki reset command */ + install_element(ENABLE_NODE, &rpki_reset_cmd); + install_element(RPKI_NODE, &rpki_reset_config_mode_cmd); + + /* Install rpki polling period commands */ + install_element(RPKI_NODE, &rpki_polling_period_cmd); + install_element(RPKI_NODE, &no_rpki_polling_period_cmd); + + /* Install rpki expire interval commands */ + install_element(RPKI_NODE, &rpki_expire_interval_cmd); + install_element(RPKI_NODE, &no_rpki_expire_interval_cmd); + + /* Install rpki retry interval commands */ + install_element(RPKI_NODE, &rpki_retry_interval_cmd); + install_element(RPKI_NODE, &no_rpki_retry_interval_cmd); + + /* Install rpki cache commands */ + install_element(RPKI_NODE, &rpki_cache_tcp_cmd); + install_element(RPKI_NODE, &rpki_cache_ssh_cmd); + install_element(RPKI_NODE, &rpki_cache_cmd); + install_element(RPKI_NODE, &no_rpki_cache_cmd); + + /* RPKI_VRF_NODE commands */ + install_element(VRF_NODE, &rpki_cmd); + install_element(VRF_NODE, &no_rpki_cmd); + /* Install rpki reset command */ + install_element(RPKI_VRF_NODE, &rpki_reset_config_mode_cmd); + + /* Install rpki polling period commands */ + install_element(RPKI_VRF_NODE, &rpki_polling_period_cmd); + install_element(RPKI_VRF_NODE, &no_rpki_polling_period_cmd); + + /* Install rpki expire interval commands */ + install_element(RPKI_VRF_NODE, &rpki_expire_interval_cmd); + install_element(RPKI_VRF_NODE, &no_rpki_expire_interval_cmd); + + /* Install rpki retry interval commands */ + install_element(RPKI_VRF_NODE, &rpki_retry_interval_cmd); + install_element(RPKI_VRF_NODE, &no_rpki_retry_interval_cmd); + + /* Install rpki cache commands */ + install_element(RPKI_VRF_NODE, &rpki_cache_tcp_cmd); + install_element(RPKI_VRF_NODE, &rpki_cache_ssh_cmd); + install_element(RPKI_VRF_NODE, &rpki_cache_cmd); + install_element(RPKI_VRF_NODE, &no_rpki_cache_cmd); + + /* Install show commands */ + install_element(VIEW_NODE, &show_rpki_prefix_table_cmd); + install_element(VIEW_NODE, &show_rpki_cache_connection_cmd); + install_element(VIEW_NODE, &show_rpki_cache_server_cmd); + install_element(VIEW_NODE, &show_rpki_prefix_cmd); + install_element(VIEW_NODE, &show_rpki_as_number_cmd); + install_element(VIEW_NODE, &show_rpki_configuration_cmd); + + /* Install debug commands */ + install_element(CONFIG_NODE, &debug_rpki_cmd); + install_element(ENABLE_NODE, &debug_rpki_cmd); + install_element(CONFIG_NODE, &no_debug_rpki_cmd); + install_element(ENABLE_NODE, &no_debug_rpki_cmd); + + /* Install route match */ + route_map_install_match(&route_match_rpki_cmd); + install_element(RMAP_NODE, &match_rpki_cmd); + install_element(RMAP_NODE, &no_match_rpki_cmd); +} + +FRR_MODULE_SETUP(.name = "bgpd_rpki", .version = "0.3.6", + .description = "Enable RPKI support for FRR.", + .init = bgp_rpki_module_init, +); diff --git a/bgpd/bgp_rpki.h b/bgpd/bgp_rpki.h new file mode 100644 index 0000000..4f2f87d --- /dev/null +++ b/bgpd/bgp_rpki.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * bgp_rpki code + * Copyright (C) 2021 NVIDIA Corporation and Mellanox Technologies, LTD + * All Rights Reserved + * Donald Sharp + */ +#ifndef __BGP_RPKI_H__ +#define __BGP_RPKI_H__ + +extern struct zebra_privs_t bgpd_privs; + +enum rpki_states { + RPKI_NOT_BEING_USED, + RPKI_VALID, + RPKI_NOTFOUND, + RPKI_INVALID +}; + +#endif diff --git a/bgpd/bgp_script.c b/bgpd/bgp_script.c new file mode 100644 index 0000000..b373858 --- /dev/null +++ b/bgpd/bgp_script.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP scripting foo + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ + +#include + +#ifdef HAVE_SCRIPTING + +#include "bgpd.h" +#include "bgp_script.h" +#include "bgp_debug.h" +#include "bgp_aspath.h" +#include "frratomic.h" +#include "frrscript.h" + +void lua_pushpeer(lua_State *L, const struct peer *peer) +{ + lua_newtable(L); + lua_pushinteger(L, peer->as); + lua_setfield(L, -2, "remote_as"); + lua_pushinteger(L, peer->local_as); + lua_setfield(L, -2, "local_as"); + lua_pushinaddr(L, &peer->remote_id); + lua_setfield(L, -2, "remote_id"); + lua_pushinaddr(L, &peer->local_id); + lua_setfield(L, -2, "local_id"); + lua_pushstring(L, lookup_msg(bgp_status_msg, peer->connection->status, + NULL)); + lua_setfield(L, -2, "state"); + lua_pushstring(L, peer->desc ? peer->desc : ""); + lua_setfield(L, -2, "description"); + lua_pushinteger(L, peer->uptime); + lua_setfield(L, -2, "uptime"); + lua_pushinteger(L, peer->readtime); + lua_setfield(L, -2, "last_readtime"); + lua_pushinteger(L, peer->resettime); + lua_setfield(L, -2, "last_resettime"); + lua_pushsockunion(L, peer->su_local); + lua_setfield(L, -2, "local_address"); + lua_pushsockunion(L, peer->su_remote); + lua_setfield(L, -2, "remote_address"); + lua_pushinteger(L, peer->cap); + lua_setfield(L, -2, "capabilities"); + lua_pushinteger(L, peer->flags); + lua_setfield(L, -2, "flags"); + lua_pushstring(L, peer->password ? peer->password : ""); + lua_setfield(L, -2, "password"); + + /* Nested tables here */ + lua_newtable(L); + { + lua_newtable(L); + { + lua_pushinteger(L, peer->holdtime); + lua_setfield(L, -2, "hold"); + lua_pushinteger(L, peer->keepalive); + lua_setfield(L, -2, "keepalive"); + lua_pushinteger(L, peer->connect); + lua_setfield(L, -2, "connect"); + lua_pushinteger(L, peer->routeadv); + lua_setfield(L, -2, "route_advertisement"); + } + lua_setfield(L, -2, "configured"); + + lua_newtable(L); + { + lua_pushinteger(L, peer->v_holdtime); + lua_setfield(L, -2, "hold"); + lua_pushinteger(L, peer->v_keepalive); + lua_setfield(L, -2, "keepalive"); + lua_pushinteger(L, peer->v_connect); + lua_setfield(L, -2, "connect"); + lua_pushinteger(L, peer->v_routeadv); + lua_setfield(L, -2, "route_advertisement"); + } + lua_setfield(L, -2, "negotiated"); + } + lua_setfield(L, -2, "timers"); + + lua_newtable(L); + { + lua_pushinteger(L, atomic_load_explicit(&peer->open_in, + memory_order_relaxed)); + lua_setfield(L, -2, "open_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->open_out, + memory_order_relaxed)); + lua_setfield(L, -2, "open_out"); + lua_pushinteger(L, atomic_load_explicit(&peer->update_in, + memory_order_relaxed)); + lua_setfield(L, -2, "update_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->update_out, + memory_order_relaxed)); + lua_setfield(L, -2, "update_out"); + lua_pushinteger(L, atomic_load_explicit(&peer->update_time, + memory_order_relaxed)); + lua_setfield(L, -2, "update_time"); + lua_pushinteger(L, atomic_load_explicit(&peer->keepalive_in, + memory_order_relaxed)); + lua_setfield(L, -2, "keepalive_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->keepalive_out, + memory_order_relaxed)); + lua_setfield(L, -2, "keepalive_out"); + lua_pushinteger(L, atomic_load_explicit(&peer->notify_in, + memory_order_relaxed)); + lua_setfield(L, -2, "notify_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->notify_out, + memory_order_relaxed)); + lua_setfield(L, -2, "notify_out"); + lua_pushinteger(L, atomic_load_explicit(&peer->refresh_in, + memory_order_relaxed)); + lua_setfield(L, -2, "refresh_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->refresh_out, + memory_order_relaxed)); + lua_setfield(L, -2, "refresh_out"); + lua_pushinteger(L, atomic_load_explicit(&peer->dynamic_cap_in, + memory_order_relaxed)); + lua_setfield(L, -2, "dynamic_cap_in"); + lua_pushinteger(L, atomic_load_explicit(&peer->dynamic_cap_out, + memory_order_relaxed)); + lua_setfield(L, -2, "dynamic_cap_out"); + lua_pushinteger(L, peer->established); + lua_setfield(L, -2, "times_established"); + lua_pushinteger(L, peer->dropped); + lua_setfield(L, -2, "times_dropped"); + } + lua_setfield(L, -2, "stats"); +} + +void lua_pushattr(lua_State *L, const struct attr *attr) +{ + lua_newtable(L); + lua_pushinteger(L, attr->med); + lua_setfield(L, -2, "metric"); + lua_pushinteger(L, attr->nh_ifindex); + lua_setfield(L, -2, "ifindex"); + lua_pushstring(L, attr->aspath->str); + lua_setfield(L, -2, "aspath"); + lua_pushinteger(L, attr->local_pref); + lua_setfield(L, -2, "localpref"); +} + +void lua_decode_attr(lua_State *L, int idx, struct attr *attr) +{ + lua_getfield(L, idx, "metric"); + attr->med = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "ifindex"); + attr->nh_ifindex = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "aspath"); + attr->aspath = aspath_str2aspath(lua_tostring(L, -1), + bgp_get_asnotation(NULL)); + lua_pop(L, 1); + lua_getfield(L, idx, "localpref"); + attr->local_pref = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_pop(L, 1); +} + +void *lua_toattr(lua_State *L, int idx) +{ + struct attr *attr = XCALLOC(MTYPE_TMP, sizeof(struct attr)); + + lua_decode_attr(L, idx, attr); + return attr; +} + +struct frrscript_codec frrscript_codecs_bgpd[] = { + {.typename = "peer", + .encoder = (encoder_func)lua_pushpeer, + .decoder = NULL}, + {.typename = "attr", + .encoder = (encoder_func)lua_pushattr, + .decoder = lua_toattr}, + {}}; + +void bgp_script_init(void) +{ + frrscript_register_type_codecs(frrscript_codecs_bgpd); +} + +#endif /* HAVE_SCRIPTING */ diff --git a/bgpd/bgp_script.h b/bgpd/bgp_script.h new file mode 100644 index 0000000..f2f47e9 --- /dev/null +++ b/bgpd/bgp_script.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP scripting foo + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ +#ifndef __BGP_SCRIPT__ +#define __BGP_SCRIPT__ + +#include +#include "bgpd.h" + +#ifdef HAVE_SCRIPTING + +#include "frrlua.h" + +/* + * Initialize scripting stuff. + */ +void bgp_script_init(void); + +void lua_pushpeer(lua_State *L, const struct peer *peer); + +void lua_pushattr(lua_State *L, const struct attr *attr); + +void lua_decode_attr(lua_State *L, int idx, struct attr *attr); + +void *lua_toattr(lua_State *L, int idx); + +#endif /* HAVE_SCRIPTING */ + +#endif /* __BGP_SCRIPT__ */ diff --git a/bgpd/bgp_snmp.c b/bgpd/bgp_snmp.c new file mode 100644 index 0000000..065ea76 --- /dev/null +++ b/bgpd/bgp_snmp.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP4 SNMP support + * Copyright (C) 1999, 2000 Kunihiro Ishiguro + */ + +#include + +#include +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "frrevent.h" +#include "smux.h" +#include "filter.h" +#include "hook.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_snmp.h" +#include "bgpd/bgp_snmp_bgp4.h" +#include "bgpd/bgp_snmp_bgp4v2.h" +#include "bgpd/bgp_mplsvpn_snmp.h" +#include "bgpd/bgp_snmp_clippy.c" + + + +static int bgp_cli_snmp_traps_config_write(struct vty *vty); + +DEFPY(bgp_snmp_traps_rfc4273, bgp_snmp_traps_rfc4273_cmd, + "[no$no] bgp snmp traps rfc4273", + NO_STR BGP_STR + "Configure BGP SNMP\n" + "Configure SNMP traps for BGP\n" + "Configure use of rfc4273 SNMP traps for BGP\n") +{ + if (no) { + UNSET_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273); + return CMD_SUCCESS; + } + SET_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273); + return CMD_SUCCESS; +} + +DEFPY(bgp_snmp_traps_bgp4_mibv2, bgp_snmp_traps_bgp4_mibv2_cmd, + "[no$no] bgp snmp traps bgp4-mibv2", + NO_STR BGP_STR + "Configure BGP SNMP\n" + "Configure SNMP traps for BGP\n" + "Configure use of BGP4-MIBv2 SNMP traps for BGP\n") +{ + if (no) { + UNSET_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2); + return CMD_SUCCESS; + } + SET_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2); + return CMD_SUCCESS; +} + +static void bgp_snmp_traps_init(void) +{ + install_element(CONFIG_NODE, &bgp_snmp_traps_rfc4273_cmd); + install_element(CONFIG_NODE, &bgp_snmp_traps_bgp4_mibv2_cmd); + + SET_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273); + /* BGP4MIBv2 traps are disabled by default */ +} + +int bgp_cli_snmp_traps_config_write(struct vty *vty) +{ + int write = 0; + + if (!CHECK_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273)) { + vty_out(vty, "no bgp snmp traps rfc4273\n"); + write++; + } + if (CHECK_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2)) { + vty_out(vty, "bgp snmp traps bgp4-mibv2\n"); + write++; + } + + return write; +} + +int bgpTrapEstablished(struct peer *peer) +{ + if (CHECK_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273)) + bgp4TrapEstablished(peer); + + if (CHECK_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2)) + bgpv2TrapEstablished(peer); + + return 0; +} + +int bgpTrapBackwardTransition(struct peer *peer) +{ + if (CHECK_FLAG(bm->options, BGP_OPT_TRAPS_RFC4273)) + bgp4TrapBackwardTransition(peer); + + if (CHECK_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2)) + bgpv2TrapBackwardTransition(peer); + + return 0; +} + +static int bgp_snmp_init(struct event_loop *tm) +{ + smux_init(tm); + bgp_snmp_traps_init(); + bgp_snmp_bgp4_init(tm); + bgp_snmp_bgp4v2_init(tm); + bgp_mpls_l3vpn_module_init(); + return 0; +} + +static int bgp_snmp_module_init(void) +{ + hook_register(peer_status_changed, bgpTrapEstablished); + hook_register(peer_backward_transition, bgpTrapBackwardTransition); + hook_register(frr_late_init, bgp_snmp_init); + hook_register(bgp_snmp_traps_config_write, + bgp_cli_snmp_traps_config_write); + return 0; +} + +FRR_MODULE_SETUP(.name = "bgpd_snmp", .version = FRR_VERSION, + .description = "bgpd AgentX SNMP module", + .init = bgp_snmp_module_init, +); diff --git a/bgpd/bgp_snmp.h b/bgpd/bgp_snmp.h new file mode 100644 index 0000000..12ec652 --- /dev/null +++ b/bgpd/bgp_snmp.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Common header file for BGP SNMP implementation. + * + * Copyright (C) 2022 Donatas Abraitis + */ + +#ifndef _FRR_BGP_SNMP_H_ +#define _FRR_BGP_SNMP_H_ + +/* SNMP value hack. */ +#define INTEGER ASN_INTEGER +#define INTEGER32 ASN_INTEGER +#define COUNTER32 ASN_COUNTER +#define OCTET_STRING ASN_OCTET_STR +#define IPADDRESS ASN_IPADDRESS +#define GAUGE32 ASN_UNSIGNED + +extern int bgpTrapEstablished(struct peer *peer); +extern int bgpTrapBackwardTransition(struct peer *peer); + +#endif /* _FRR_BGP_SNMP_H_ */ diff --git a/bgpd/bgp_snmp_bgp4.c b/bgpd/bgp_snmp_bgp4.c new file mode 100644 index 0000000..755777c --- /dev/null +++ b/bgpd/bgp_snmp_bgp4.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP4 SNMP support + * Copyright (C) 1999, 2000 Kunihiro Ishiguro + */ + +#include + +#include +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "frrevent.h" +#include "smux.h" +#include "filter.h" +#include "hook.h" +#include "libfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_snmp.h" +#include "bgpd/bgp_snmp_bgp4.h" +#include "bgpd/bgp_mplsvpn_snmp.h" + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +/* BGP-MIB instances. */ +static oid bgp_oid[] = {BGP4MIB}; +static oid bgp_trap_oid[] = {BGP4MIB, 0}; + +/* IP address 0.0.0.0. */ +static struct in_addr bgp_empty_addr = {.s_addr = 0}; + +static uint8_t *bgpVersion(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + static uint8_t version; + + if (smux_header_generic(v, name, length, exact, var_len, + write_method) == MATCH_FAILED) + return NULL; + + /* Return BGP version. Zebra bgpd only support version 4. */ + version = (0x80 >> (BGP_VERSION_4 - 1)); + + /* Return octet string length 1. */ + *var_len = 1; + return &version; +} + +static uint8_t *bgpLocalAs(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct bgp *bgp; + + if (smux_header_generic(v, name, length, exact, var_len, + write_method) == MATCH_FAILED) + return NULL; + + /* Get BGP structure. */ + bgp = bgp_get_default(); + if (!bgp) + return NULL; + + return SNMP_INTEGER(bgp->as); +} + +static struct peer *peer_lookup_addr_ipv4(struct in_addr *src) +{ + struct bgp *bgp; + struct peer *peer; + struct listnode *node; + struct listnode *bgpnode; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, bgpnode, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (sockunion_family(&peer->connection->su) != AF_INET) + continue; + + if (sockunion2ip(&peer->connection->su) == src->s_addr) + return peer; + } + } + + return NULL; +} + +static struct peer *bgp_peer_lookup_next(struct in_addr *src) +{ + struct bgp *bgp; + struct peer *peer; + struct peer *next_peer = NULL; + struct listnode *node; + struct listnode *bgpnode; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, bgpnode, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (sockunion_family(&peer->connection->su) != AF_INET) + continue; + if (ntohl(sockunion2ip(&peer->connection->su)) <= + ntohl(src->s_addr)) + continue; + + if (!next_peer || + ntohl(sockunion2ip(&next_peer->connection->su)) > + ntohl(sockunion2ip(&peer->connection->su))) { + next_peer = peer; + } + } + } + + if (next_peer) { + src->s_addr = sockunion2ip(&next_peer->connection->su); + return next_peer; + } + + return NULL; +} + +/* 1.3.6.1.2.1.15.3.1.x = 10 */ +#define PEERTAB_NAMELEN 10 + +static struct peer *bgpPeerTable_lookup(struct variable *v, oid name[], + size_t *length, struct in_addr *addr, + int exact) +{ + struct peer *peer = NULL; + size_t namelen = v ? v->namelen : PEERTAB_NAMELEN; + int len; + + if (exact) { + /* Check the length. */ + if (*length - namelen != sizeof(struct in_addr)) + return NULL; + + oid2in_addr(name + namelen, IN_ADDR_SIZE, addr); + + peer = peer_lookup_addr_ipv4(addr); + return peer; + } else { + len = *length - namelen; + if (len > 4) + len = 4; + + oid2in_addr(name + namelen, len, addr); + + peer = bgp_peer_lookup_next(addr); + + if (peer == NULL) + return NULL; + + oid_copy_in_addr(name + namelen, addr); + *length = sizeof(struct in_addr) + namelen; + + return peer; + } + return NULL; +} + +/* BGP write methods. */ +static int write_bgpPeerTable(int action, uint8_t *var_val, + uint8_t var_val_type, size_t var_val_len, + uint8_t *statP, oid *name, size_t length) +{ + struct in_addr addr; + struct peer *peer; + struct peer_connection *connection; + long intval; + + if (var_val_type != ASN_INTEGER) { + return SNMP_ERR_WRONGTYPE; + } + if (var_val_len != sizeof(long)) { + return SNMP_ERR_WRONGLENGTH; + } + + intval = *(long *)var_val; + + memset(&addr, 0, sizeof(addr)); + + peer = bgpPeerTable_lookup(NULL, name, &length, &addr, 1); + if (!peer) + return SNMP_ERR_NOSUCHNAME; + + connection = peer->connection; + + if (action != SNMP_MSG_INTERNAL_SET_COMMIT) + return SNMP_ERR_NOERROR; + + zlog_info("%s: SNMP write .%ld = %ld", peer->host, + (long)name[PEERTAB_NAMELEN - 1], intval); + + switch (name[PEERTAB_NAMELEN - 1]) { + case BGPPEERADMINSTATUS: +#define BGP_PeerAdmin_stop 1 +#define BGP_PeerAdmin_start 2 + /* When the peer is established, */ + if (intval == BGP_PeerAdmin_stop) + BGP_EVENT_ADD(connection, BGP_Stop); + else if (intval == BGP_PeerAdmin_start) + ; /* Do nothing. */ + else + return SNMP_ERR_NOSUCHNAME; + break; + case BGPPEERCONNECTRETRYINTERVAL: + peer_flag_set(peer, PEER_FLAG_TIMER_CONNECT); + peer->connect = intval; + peer->v_connect = intval; + break; + case BGPPEERHOLDTIMECONFIGURED: + peer_flag_set(peer, PEER_FLAG_TIMER); + peer->holdtime = intval; + peer->v_holdtime = intval; + break; + case BGPPEERKEEPALIVECONFIGURED: + peer_flag_set(peer, PEER_FLAG_TIMER); + peer->keepalive = intval; + peer->v_keepalive = intval; + break; + case BGPPEERMINROUTEADVERTISEMENTINTERVAL: + peer->v_routeadv = intval; + break; + } + return SNMP_ERR_NOERROR; +} + +static uint8_t *bgpPeerTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + static struct in_addr addr; + struct peer *peer; + uint32_t ui, uo; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + memset(&addr, 0, sizeof(addr)); + + peer = bgpPeerTable_lookup(v, name, length, &addr, exact); + if (!peer) + return NULL; + + switch (v->magic) { + case BGPPEERIDENTIFIER: + return SNMP_IPADDRESS(peer->remote_id); + case BGPPEERSTATE: + return SNMP_INTEGER(peer->connection->status); + case BGPPEERADMINSTATUS: + *write_method = write_bgpPeerTable; +#define BGP_PeerAdmin_stop 1 +#define BGP_PeerAdmin_start 2 + if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) + return SNMP_INTEGER(BGP_PeerAdmin_stop); + else + return SNMP_INTEGER(BGP_PeerAdmin_start); + case BGPPEERNEGOTIATEDVERSION: + return SNMP_INTEGER(BGP_VERSION_4); + case BGPPEERLOCALADDR: + if (peer->su_local) + return SNMP_IPADDRESS(peer->su_local->sin.sin_addr); + else + return SNMP_IPADDRESS(bgp_empty_addr); + case BGPPEERLOCALPORT: + if (peer->su_local) + return SNMP_INTEGER( + ntohs(peer->su_local->sin.sin_port)); + else + return SNMP_INTEGER(0); + case BGPPEERREMOTEADDR: + if (peer->su_remote) + return SNMP_IPADDRESS(peer->su_remote->sin.sin_addr); + else + return SNMP_IPADDRESS(bgp_empty_addr); + case BGPPEERREMOTEPORT: + if (peer->su_remote) + return SNMP_INTEGER( + ntohs(peer->su_remote->sin.sin_port)); + else + return SNMP_INTEGER(0); + case BGPPEERREMOTEAS: + return SNMP_INTEGER(peer->as); + case BGPPEERINUPDATES: + ui = atomic_load_explicit(&peer->update_in, + memory_order_relaxed); + return SNMP_INTEGER(ui); + case BGPPEEROUTUPDATES: + uo = atomic_load_explicit(&peer->update_out, + memory_order_relaxed); + return SNMP_INTEGER(uo); + case BGPPEERINTOTALMESSAGES: + return SNMP_INTEGER(PEER_TOTAL_RX(peer)); + case BGPPEEROUTTOTALMESSAGES: + return SNMP_INTEGER(PEER_TOTAL_TX(peer)); + case BGPPEERLASTERROR: { + static uint8_t lasterror[2]; + lasterror[0] = peer->notify.code; + lasterror[1] = peer->notify.subcode; + *var_len = 2; + return (uint8_t *)&lasterror; + } + case BGPPEERFSMESTABLISHEDTRANSITIONS: + return SNMP_INTEGER(peer->established); + case BGPPEERFSMESTABLISHEDTIME: + if (peer->uptime == 0) + return SNMP_INTEGER(0); + else + return SNMP_INTEGER(monotime(NULL) - peer->uptime); + case BGPPEERCONNECTRETRYINTERVAL: + *write_method = write_bgpPeerTable; + return SNMP_INTEGER(peer->v_connect); + case BGPPEERHOLDTIME: + return SNMP_INTEGER(peer->v_holdtime); + case BGPPEERKEEPALIVE: + return SNMP_INTEGER(peer->v_keepalive); + case BGPPEERHOLDTIMECONFIGURED: + *write_method = write_bgpPeerTable; + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) + return SNMP_INTEGER(peer->holdtime); + else + return SNMP_INTEGER(peer->v_holdtime); + case BGPPEERKEEPALIVECONFIGURED: + *write_method = write_bgpPeerTable; + if (CHECK_FLAG(peer->flags, PEER_FLAG_TIMER)) + return SNMP_INTEGER(peer->keepalive); + else + return SNMP_INTEGER(peer->v_keepalive); + case BGPPEERMINROUTEADVERTISEMENTINTERVAL: + *write_method = write_bgpPeerTable; + return SNMP_INTEGER(peer->v_routeadv); + case BGPPEERINUPDATEELAPSEDTIME: + if (peer->update_time == 0) + return SNMP_INTEGER(0); + else + return SNMP_INTEGER(monotime(NULL) - peer->update_time); + default: + return NULL; + } + return NULL; +} + +static uint8_t *bgpIdentifier(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct bgp *bgp; + + if (smux_header_generic(v, name, length, exact, var_len, + write_method) == MATCH_FAILED) + return NULL; + + bgp = bgp_get_default(); + if (!bgp) + return NULL; + + return SNMP_IPADDRESS(bgp->router_id); +} + +static uint8_t *bgpRcvdPathAttrTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + /* Received Path Attribute Table. This table contains, one entry + per path to a network, path attributes received from all peers + running BGP version 3 or less. This table is obsolete, having + been replaced in functionality with the bgp4PathAttrTable. */ + return NULL; +} + +static struct bgp_path_info *bgp4PathAttrLookup(struct variable *v, oid name[], + size_t *length, struct bgp *bgp, + struct prefix_ipv4 *addr, + int exact) +{ + oid *offset; + int offsetlen; + struct bgp_path_info *path; + struct bgp_path_info *min; + struct bgp_dest *dest; + union sockunion su; + unsigned int len; + struct in_addr paddr; + + sockunion_init(&su); + +#define BGP_PATHATTR_ENTRY_OFFSET (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE) + + if (exact) { + if (*length - v->namelen != BGP_PATHATTR_ENTRY_OFFSET) + return NULL; + + /* Set OID offset for prefix. */ + offset = name + v->namelen; + oid2in_addr(offset, IN_ADDR_SIZE, &addr->prefix); + offset += IN_ADDR_SIZE; + + /* Prefix length. */ + addr->prefixlen = *offset; + offset++; + + /* Peer address. */ + su.sin.sin_family = AF_INET; + oid2in_addr(offset, IN_ADDR_SIZE, &su.sin.sin_addr); + + /* Lookup node. */ + dest = bgp_node_lookup(bgp->rib[AFI_IP][SAFI_UNICAST], + (struct prefix *)addr); + if (dest) { + for (path = bgp_dest_get_bgp_path_info(dest); path; + path = path->next) + if (sockunion_same(&path->peer->connection->su, + &su)) + return path; + + bgp_dest_unlock_node(dest); + } + } else { + offset = name + v->namelen; + offsetlen = *length - v->namelen; + len = offsetlen; + + if (offsetlen == 0) + dest = bgp_table_top(bgp->rib[AFI_IP][SAFI_UNICAST]); + else { + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, &addr->prefix); + + offset += IN_ADDR_SIZE; + offsetlen -= IN_ADDR_SIZE; + + if (offsetlen > 0) + addr->prefixlen = *offset; + else + addr->prefixlen = len * 8; + + dest = bgp_node_get(bgp->rib[AFI_IP][SAFI_UNICAST], + (struct prefix *)addr); + + offset++; + offsetlen--; + } + + if (offsetlen > 0) { + len = offsetlen; + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, &paddr); + } else + paddr.s_addr = INADDR_ANY; + + if (!dest) + return NULL; + + do { + min = NULL; + + for (path = bgp_dest_get_bgp_path_info(dest); path; + path = path->next) { + if (path->peer->connection->su.sin.sin_family == + AF_INET && + ntohl(paddr.s_addr) < + ntohl(path->peer->connection->su.sin + .sin_addr.s_addr)) { + if (min) { + if (ntohl(path->peer->connection + ->su.sin + .sin_addr + .s_addr) < + ntohl(min->peer->connection + ->su.sin + .sin_addr + .s_addr)) + min = path; + } else + min = path; + } + } + + if (min) { + const struct prefix *rn_p = + bgp_dest_get_prefix(dest); + + *length = + v->namelen + BGP_PATHATTR_ENTRY_OFFSET; + + offset = name + v->namelen; + oid_copy_in_addr(offset, &rn_p->u.prefix4); + offset += IN_ADDR_SIZE; + *offset = rn_p->prefixlen; + offset++; + oid_copy_in_addr(offset, + &min->peer->connection->su.sin + .sin_addr); + addr->prefix = rn_p->u.prefix4; + addr->prefixlen = rn_p->prefixlen; + + bgp_dest_unlock_node(dest); + + return min; + } + + paddr.s_addr = INADDR_ANY; + } while ((dest = bgp_route_next(dest)) != NULL); + } + return NULL; +} + +static uint8_t *bgp4PathAttrTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct bgp *bgp; + struct bgp_path_info *path; + struct prefix_ipv4 addr; + + bgp = bgp_get_default(); + if (!bgp) + return NULL; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + memset(&addr, 0, sizeof(addr)); + + path = bgp4PathAttrLookup(v, name, length, bgp, &addr, exact); + if (!path) + return NULL; + + switch (v->magic) { + case BGP4PATHATTRPEER: /* 1 */ + return SNMP_IPADDRESS(path->peer->connection->su.sin.sin_addr); + case BGP4PATHATTRIPADDRPREFIXLEN: /* 2 */ + return SNMP_INTEGER(addr.prefixlen); + case BGP4PATHATTRIPADDRPREFIX: /* 3 */ + return SNMP_IPADDRESS(addr.prefix); + case BGP4PATHATTRORIGIN: /* 4 */ + return SNMP_INTEGER(path->attr->origin); + case BGP4PATHATTRASPATHSEGMENT: /* 5 */ + return aspath_snmp_pathseg(path->attr->aspath, var_len); + case BGP4PATHATTRNEXTHOP: /* 6 */ + return SNMP_IPADDRESS(path->attr->nexthop); + case BGP4PATHATTRMULTIEXITDISC: /* 7 */ + return SNMP_INTEGER(path->attr->med); + case BGP4PATHATTRLOCALPREF: /* 8 */ + return SNMP_INTEGER(path->attr->local_pref); + case BGP4PATHATTRATOMICAGGREGATE: /* 9 */ + return SNMP_INTEGER(1); + case BGP4PATHATTRAGGREGATORAS: /* 10 */ + return SNMP_INTEGER(path->attr->aggregator_as); + case BGP4PATHATTRAGGREGATORADDR: /* 11 */ + return SNMP_IPADDRESS(path->attr->aggregator_addr); + case BGP4PATHATTRCALCLOCALPREF: /* 12 */ + return SNMP_INTEGER(-1); + case BGP4PATHATTRBEST: /* 13 */ +#define BGP4_PathAttrBest_false 1 +#define BGP4_PathAttrBest_true 2 + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + return SNMP_INTEGER(BGP4_PathAttrBest_true); + else + return SNMP_INTEGER(BGP4_PathAttrBest_false); + case BGP4PATHATTRUNKNOWN: /* 14 */ + *var_len = 0; + return NULL; + } + return NULL; +} + +/* BGP Traps. */ +static struct trap_object bgpTrapList[] = {{3, {3, 1, BGPPEERREMOTEADDR}}, + {3, {3, 1, BGPPEERLASTERROR}}, + {3, {3, 1, BGPPEERSTATE}}}; + +static struct variable bgp_variables[] = { + /* BGP version. */ + {BGPVERSION, OCTET_STRING, RONLY, bgpVersion, 1, {1}}, + /* BGP local AS. */ + {BGPLOCALAS, INTEGER, RONLY, bgpLocalAs, 1, {2}}, + /* BGP peer table. */ + {BGPPEERIDENTIFIER, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 1}}, + {BGPPEERSTATE, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 2}}, + {BGPPEERADMINSTATUS, INTEGER, RWRITE, bgpPeerTable, 3, {3, 1, 3}}, + {BGPPEERNEGOTIATEDVERSION, + INTEGER32, + RONLY, + bgpPeerTable, + 3, + {3, 1, 4}}, + {BGPPEERLOCALADDR, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 5}}, + {BGPPEERLOCALPORT, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 6}}, + {BGPPEERREMOTEADDR, IPADDRESS, RONLY, bgpPeerTable, 3, {3, 1, 7}}, + {BGPPEERREMOTEPORT, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 8}}, + {BGPPEERREMOTEAS, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 9}}, + {BGPPEERINUPDATES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 10}}, + {BGPPEEROUTUPDATES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 11}}, + {BGPPEERINTOTALMESSAGES, COUNTER32, RONLY, bgpPeerTable, 3, {3, 1, 12}}, + {BGPPEEROUTTOTALMESSAGES, + COUNTER32, + RONLY, + bgpPeerTable, + 3, + {3, 1, 13}}, + {BGPPEERLASTERROR, OCTET_STRING, RONLY, bgpPeerTable, 3, {3, 1, 14}}, + {BGPPEERFSMESTABLISHEDTRANSITIONS, + COUNTER32, + RONLY, + bgpPeerTable, + 3, + {3, 1, 15}}, + {BGPPEERFSMESTABLISHEDTIME, + GAUGE32, + RONLY, + bgpPeerTable, + 3, + {3, 1, 16}}, + {BGPPEERCONNECTRETRYINTERVAL, + INTEGER, + RWRITE, + bgpPeerTable, + 3, + {3, 1, 17}}, + {BGPPEERHOLDTIME, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 18}}, + {BGPPEERKEEPALIVE, INTEGER, RONLY, bgpPeerTable, 3, {3, 1, 19}}, + {BGPPEERHOLDTIMECONFIGURED, + INTEGER, + RWRITE, + bgpPeerTable, + 3, + {3, 1, 20}}, + {BGPPEERKEEPALIVECONFIGURED, + INTEGER, + RWRITE, + bgpPeerTable, + 3, + {3, 1, 21}}, + {BGPPEERMINROUTEADVERTISEMENTINTERVAL, + INTEGER, + RWRITE, + bgpPeerTable, + 3, + {3, 1, 23}}, + {BGPPEERINUPDATEELAPSEDTIME, + GAUGE32, + RONLY, + bgpPeerTable, + 3, + {3, 1, 24}}, + /* BGP identifier. */ + {BGPIDENTIFIER, IPADDRESS, RONLY, bgpIdentifier, 1, {4}}, + /* BGP received path attribute table. */ + {BGPPATHATTRPEER, IPADDRESS, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 1}}, + {BGPPATHATTRDESTNETWORK, + IPADDRESS, + RONLY, + bgpRcvdPathAttrTable, + 3, + {5, 1, 2}}, + {BGPPATHATTRORIGIN, INTEGER, RONLY, bgpRcvdPathAttrTable, 3, {5, 1, 3}}, + {BGPPATHATTRASPATH, + OCTET_STRING, + RONLY, + bgpRcvdPathAttrTable, + 3, + {5, 1, 4}}, + {BGPPATHATTRNEXTHOP, + IPADDRESS, + RONLY, + bgpRcvdPathAttrTable, + 3, + {5, 1, 5}}, + {BGPPATHATTRINTERASMETRIC, + INTEGER32, + RONLY, + bgpRcvdPathAttrTable, + 3, + {5, 1, 6}}, + /* BGP-4 received path attribute table. */ + {BGP4PATHATTRPEER, IPADDRESS, RONLY, bgp4PathAttrTable, 3, {6, 1, 1}}, + {BGP4PATHATTRIPADDRPREFIXLEN, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 2}}, + {BGP4PATHATTRIPADDRPREFIX, + IPADDRESS, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 3}}, + {BGP4PATHATTRORIGIN, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 4}}, + {BGP4PATHATTRASPATHSEGMENT, + OCTET_STRING, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 5}}, + {BGP4PATHATTRNEXTHOP, + IPADDRESS, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 6}}, + {BGP4PATHATTRMULTIEXITDISC, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 7}}, + {BGP4PATHATTRLOCALPREF, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 8}}, + {BGP4PATHATTRATOMICAGGREGATE, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 9}}, + {BGP4PATHATTRAGGREGATORAS, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 10}}, + {BGP4PATHATTRAGGREGATORADDR, + IPADDRESS, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 11}}, + {BGP4PATHATTRCALCLOCALPREF, + INTEGER, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 12}}, + {BGP4PATHATTRBEST, INTEGER, RONLY, bgp4PathAttrTable, 3, {6, 1, 13}}, + {BGP4PATHATTRUNKNOWN, + OCTET_STRING, + RONLY, + bgp4PathAttrTable, + 3, + {6, 1, 14}}, +}; + +int bgp4TrapEstablished(struct peer *peer) +{ + int ret; + struct in_addr addr; + oid index[sizeof(oid) * IN_ADDR_SIZE]; + struct peer_connection *connection = peer->connection; + + /* Check if this peer just went to Established */ + if ((connection->ostatus != OpenConfirm) || + !(peer_established(connection))) + return 0; + + ret = inet_aton(peer->host, &addr); + if (ret == 0) + return 0; + + oid_copy_in_addr(index, &addr); + + smux_trap(bgp_variables, array_size(bgp_variables), bgp_trap_oid, + array_size(bgp_trap_oid), bgp_oid, + sizeof(bgp_oid) / sizeof(oid), index, IN_ADDR_SIZE, + bgpTrapList, array_size(bgpTrapList), BGPESTABLISHED); + return 0; +} + +int bgp4TrapBackwardTransition(struct peer *peer) +{ + int ret; + struct in_addr addr; + oid index[sizeof(oid) * IN_ADDR_SIZE]; + + ret = inet_aton(peer->host, &addr); + if (ret == 0) + return 0; + + oid_copy_in_addr(index, &addr); + + smux_trap(bgp_variables, array_size(bgp_variables), bgp_trap_oid, + array_size(bgp_trap_oid), bgp_oid, + sizeof(bgp_oid) / sizeof(oid), index, IN_ADDR_SIZE, + bgpTrapList, array_size(bgpTrapList), BGPBACKWARDTRANSITION); + return 0; +} + +int bgp_snmp_bgp4_init(struct event_loop *tm) +{ + REGISTER_MIB("mibII/bgp", bgp_variables, variable, bgp_oid); + return 0; +} diff --git a/bgpd/bgp_snmp_bgp4.h b/bgpd/bgp_snmp_bgp4.h new file mode 100644 index 0000000..67f7cc6 --- /dev/null +++ b/bgpd/bgp_snmp_bgp4.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP4-MIB SNMP support + * + * Using: http://www.circitor.fr/Mibs/Html/B/BGP4-MIB.php + * + * Copyright (C) 2022 Donatas Abraitis + */ + +#ifndef _FRR_BGP_SNMP_BGP4_H_ +#define _FRR_BGP_SNMP_BGP4_H_ + +#define BGPVERSION 0 +#define BGPLOCALAS 0 +#define BGPIDENTIFIER 0 + +/* bgp */ +#define BGP4MIB 1, 3, 6, 1, 2, 1, 15 + +/* bgpTraps */ +#define BGPESTABLISHED 1 +#define BGPBACKWARDTRANSITION 2 + +/* bgpPeerTable */ +#define BGPPEERIDENTIFIER 1 +#define BGPPEERSTATE 2 +#define BGPPEERADMINSTATUS 3 +#define BGPPEERNEGOTIATEDVERSION 4 +#define BGPPEERLOCALADDR 5 +#define BGPPEERLOCALPORT 6 +#define BGPPEERREMOTEADDR 7 +#define BGPPEERREMOTEPORT 8 +#define BGPPEERREMOTEAS 9 +#define BGPPEERINUPDATES 10 +#define BGPPEEROUTUPDATES 11 +#define BGPPEERINTOTALMESSAGES 12 +#define BGPPEEROUTTOTALMESSAGES 13 +#define BGPPEERLASTERROR 14 +#define BGPPEERFSMESTABLISHEDTRANSITIONS 15 +#define BGPPEERFSMESTABLISHEDTIME 16 +#define BGPPEERCONNECTRETRYINTERVAL 17 +#define BGPPEERHOLDTIME 18 +#define BGPPEERKEEPALIVE 19 +#define BGPPEERHOLDTIMECONFIGURED 20 +#define BGPPEERKEEPALIVECONFIGURED 21 +#define BGPPEERMINROUTEADVERTISEMENTINTERVAL 22 +#define BGPPEERINUPDATEELAPSEDTIME 23 + +/* bgpPathAttrEntry */ +#define BGPPATHATTRPEER 1 +#define BGPPATHATTRDESTNETWORK 2 +#define BGPPATHATTRORIGIN 3 +#define BGPPATHATTRASPATH 4 +#define BGPPATHATTRNEXTHOP 5 +#define BGPPATHATTRINTERASMETRIC 6 + +/* bgp4PathAttrEntry */ +#define BGP4PATHATTRPEER 1 +#define BGP4PATHATTRIPADDRPREFIXLEN 2 +#define BGP4PATHATTRIPADDRPREFIX 3 +#define BGP4PATHATTRORIGIN 4 +#define BGP4PATHATTRASPATHSEGMENT 5 +#define BGP4PATHATTRNEXTHOP 6 +#define BGP4PATHATTRMULTIEXITDISC 7 +#define BGP4PATHATTRLOCALPREF 8 +#define BGP4PATHATTRATOMICAGGREGATE 9 +#define BGP4PATHATTRAGGREGATORAS 10 +#define BGP4PATHATTRAGGREGATORADDR 11 +#define BGP4PATHATTRCALCLOCALPREF 12 +#define BGP4PATHATTRBEST 13 +#define BGP4PATHATTRUNKNOWN 14 + +extern int bgp4TrapEstablished(struct peer *peer); +extern int bgp4TrapBackwardTransition(struct peer *peer); +extern int bgp_snmp_bgp4_init(struct event_loop *tm); + +#endif /* _FRR_BGP_SNMP_BGP4_H_ */ diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c new file mode 100644 index 0000000..e8c3e65 --- /dev/null +++ b/bgpd/bgp_snmp_bgp4v2.c @@ -0,0 +1,1673 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP4V2-MIB SNMP support + * + * Copyright (C) 2022 Donatas Abraitis + */ + +#include + +#include +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "frrevent.h" +#include "smux.h" +#include "filter.h" +#include "hook.h" +#include "libfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_snmp.h" +#include "bgpd/bgp_snmp_bgp4v2.h" + +SNMP_LOCAL_VARIABLES + +static oid bgpv2_oid[] = {BGP4V2MIB}; +static oid bgpv2_trap_oid[] = { BGP4V2MIB, 0 }; +static struct in_addr bgp_empty_addr = {}; + +static struct peer *peer_lookup_all_vrf(struct ipaddr *addr) +{ + struct bgp *bgp; + struct peer *peer; + struct listnode *node; + struct listnode *bgpnode; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, bgpnode, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + switch (sockunion_family(&peer->connection->su)) { + case AF_INET: + if (IPV4_ADDR_SAME(&peer->connection->su.sin + .sin_addr, + &addr->ip._v4_addr)) + return peer; + break; + case AF_INET6: + if (IPV6_ADDR_SAME(&peer->connection->su.sin6 + .sin6_addr, + &addr->ip._v6_addr)) + return peer; + break; + default: + break; + } + } + } + + return NULL; +} + +static struct peer *peer_lookup_all_vrf_next(struct ipaddr *addr, oid *offset, + sa_family_t family) +{ + struct bgp *bgp; + struct peer *peer; + struct peer *next_peer = NULL; + struct listnode *node; + struct listnode *bgpnode; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, bgpnode, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + sa_family_t peer_family = + sockunion_family(&peer->connection->su); + + if (peer_family != family) + continue; + + switch (peer_family) { + case AF_INET: + oid2in_addr(offset, IN_ADDR_SIZE, + &addr->ip._v4_addr); + if (IPV4_ADDR_CMP(&peer->connection->su.sin + .sin_addr, + &addr->ip._v4_addr) < 0 || + IPV4_ADDR_SAME(&peer->connection->su.sin + .sin_addr, + &addr->ip._v4_addr)) + continue; + + if (!next_peer || + IPV4_ADDR_CMP(&next_peer->connection->su.sin + .sin_addr, + &peer->connection->su.sin + .sin_addr) > 0) + next_peer = peer; + + break; + case AF_INET6: + oid2in6_addr(offset, &addr->ip._v6_addr); + if (IPV6_ADDR_CMP(&peer->connection->su.sin6 + .sin6_addr, + &addr->ip._v6_addr) < 0 || + IPV6_ADDR_SAME(&peer->connection->su.sin6 + .sin6_addr, + &addr->ip._v6_addr)) + continue; + + if (!next_peer || + IPV6_ADDR_CMP(&next_peer->connection->su + .sin6.sin6_addr, + &peer->connection->su.sin6 + .sin6_addr) > 0) + next_peer = peer; + + break; + default: + break; + } + } + } + + if (next_peer) + return next_peer; + + return NULL; +} + +static struct peer *bgpv2PeerTable_lookup(struct variable *v, oid name[], + size_t *length, int exact, + struct ipaddr *addr) +{ + struct peer *peer = NULL; + size_t namelen = v ? v->namelen : BGP4V2_PEER_ENTRY_OFFSET; + oid *offset = name + namelen; + sa_family_t family = name[namelen - 1] == 1 ? AF_INET : AF_INET6; + int afi_len = IN_ADDR_SIZE; + size_t offsetlen = *length - namelen; + + if (family == AF_INET6) + afi_len = IN6_ADDR_SIZE; + + /* Somehow with net-snmp 5.7.3, every OID item in an array + * is uninitialized and has a max random value, let's zero it. + * With 5.8, 5.9, it works fine even without this hack. + */ + if (!offsetlen) { + for (int i = 0; i < afi_len; i++) + *(offset + i) = 0; + } + + if (exact) { + if (family == AF_INET) { + oid2in_addr(offset, afi_len, &addr->ip._v4_addr); + peer = peer_lookup_all_vrf(addr); + return peer; + } else if (family == AF_INET6) { + oid2in6_addr(offset, &addr->ip._v6_addr); + return peer_lookup_all_vrf(addr); + } + } else { + peer = peer_lookup_all_vrf_next(addr, offset, family); + if (peer == NULL) + return NULL; + + switch (sockunion_family(&peer->connection->su)) { + case AF_INET: + oid_copy_in_addr(offset, + &peer->connection->su.sin.sin_addr); + *length = afi_len + namelen; + return peer; + case AF_INET6: + oid_copy_in6_addr(offset, + &peer->connection->su.sin6.sin6_addr); + *length = afi_len + namelen; + return peer; + default: + break; + } + } + + return NULL; +} + +static uint8_t *bgpv2PeerTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct peer *peer; + struct ipaddr addr = {}; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + + peer = bgpv2PeerTable_lookup(v, name, length, exact, &addr); + if (!peer) + return NULL; + + switch (v->magic) { + case BGP4V2_PEER_INSTANCE: + return SNMP_INTEGER(peer->bgp->vrf_id); + case BGP4V2_PEER_LOCAL_ADDR_TYPE: + if (peer->su_local) + return SNMP_INTEGER(peer->su_local->sa.sa_family == + AF_INET + ? AFI_IP + : AFI_IP6); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LOCAL_ADDR: + if (peer->su_local) + if (peer->su_local->sa.sa_family == AF_INET) + return SNMP_IPADDRESS( + peer->su_local->sin.sin_addr); + else + return SNMP_IP6ADDRESS( + peer->su_local->sin6.sin6_addr); + else + return SNMP_IPADDRESS(bgp_empty_addr); + case BGP4V2_PEER_REMOTE_ADDR_TYPE: + if (peer->su_remote) + return SNMP_INTEGER(peer->su_remote->sa.sa_family == + AF_INET + ? AFI_IP + : AFI_IP6); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_REMOTE_ADDR: + if (peer->su_remote) + if (peer->su_remote->sa.sa_family == AF_INET) + return SNMP_IPADDRESS( + peer->su_remote->sin.sin_addr); + else + return SNMP_IP6ADDRESS( + peer->su_remote->sin6.sin6_addr); + else + return SNMP_IPADDRESS(bgp_empty_addr); + case BGP4V2_PEER_LOCAL_PORT: + if (peer->su_local) + if (peer->su_local->sa.sa_family == AF_INET) + return SNMP_INTEGER( + ntohs(peer->su_local->sin.sin_port)); + else + return SNMP_INTEGER( + ntohs(peer->su_local->sin6.sin6_port)); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LOCAL_AS: + return SNMP_INTEGER(peer->local_as); + case BGP4V2_PEER_LOCAL_IDENTIFIER: + return SNMP_IPADDRESS(peer->local_id); + case BGP4V2_PEER_REMOTE_PORT: + if (peer->su_remote) + if (peer->su_remote->sa.sa_family == AF_INET) + return SNMP_INTEGER( + ntohs(peer->su_remote->sin.sin_port)); + else + return SNMP_INTEGER( + ntohs(peer->su_remote->sin6.sin6_port)); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_REMOTE_AS: + return SNMP_INTEGER(peer->as); + case BGP4V2_PEER_REMOTE_IDENTIFIER: + return SNMP_IPADDRESS(peer->remote_id); + case BGP4V2_PEER_ADMIN_STATUS: +#define BGP_PEER_ADMIN_STATUS_HALTED 1 +#define BGP_PEER_ADMIN_STATUS_RUNNING 2 + if (BGP_PEER_START_SUPPRESSED(peer)) + return SNMP_INTEGER(BGP_PEER_ADMIN_STATUS_HALTED); + else + return SNMP_INTEGER(BGP_PEER_ADMIN_STATUS_RUNNING); + case BGP4V2_PEER_STATE: + return SNMP_INTEGER(peer->connection->status); + case BGP4V2_PEER_DESCRIPTION: + if (peer->desc) + return SNMP_STRING(peer->desc); + break; + default: + break; + } + + return NULL; +} + +static uint8_t *bgpv2PeerErrorsTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct peer *peer; + struct ipaddr addr = {}; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + + peer = bgpv2PeerTable_lookup(v, name, length, exact, &addr); + if (!peer) + return NULL; + + switch (v->magic) { + case BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED: + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->notify.code); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED: + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->notify.subcode); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME: + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->resettime); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT: + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) { + struct bgp_notify notify = peer->notify; + char msg_buf[255]; + const char *msg_str = NULL; + + if (notify.code == BGP_NOTIFY_CEASE && + (notify.subcode == + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN || + notify.subcode == BGP_NOTIFY_CEASE_ADMIN_RESET)) { + msg_str = bgp_notify_admin_message( + msg_buf, sizeof(msg_buf), + (uint8_t *)notify.data, notify.length); + return SNMP_STRING(msg_str); + } + } + return SNMP_STRING(""); + case BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA: + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED && + peer->notify.data) + return SNMP_STRING(peer->notify.data); + else + return SNMP_STRING(""); + case BGP4V2_PEER_LAST_ERROR_CODE_SENT: + if (peer->last_reset != PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->notify.code); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT: + if (peer->last_reset != PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->notify.subcode); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_SENT_TIME: + if (peer->last_reset != PEER_DOWN_NOTIFY_RECEIVED) + return SNMP_INTEGER(peer->resettime); + else + return SNMP_INTEGER(0); + case BGP4V2_PEER_LAST_ERROR_SENT_TEXT: + if (peer->last_reset == PEER_DOWN_NOTIFY_SEND || + peer->last_reset == PEER_DOWN_RTT_SHUTDOWN || + peer->last_reset == PEER_DOWN_USER_SHUTDOWN) { + struct bgp_notify notify = peer->notify; + char msg_buf[255]; + const char *msg_str = NULL; + + if (notify.code == BGP_NOTIFY_CEASE && + (notify.subcode == + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN || + notify.subcode == BGP_NOTIFY_CEASE_ADMIN_RESET)) { + msg_str = bgp_notify_admin_message( + msg_buf, sizeof(msg_buf), + (uint8_t *)notify.data, notify.length); + return SNMP_STRING(msg_str); + } + } + return SNMP_STRING(""); + case BGP4V2_PEER_LAST_ERROR_SENT_DATA: + if ((peer->last_reset == PEER_DOWN_NOTIFY_SEND || + peer->last_reset == PEER_DOWN_RTT_SHUTDOWN || + peer->last_reset == PEER_DOWN_USER_SHUTDOWN) && + peer->notify.data) + return SNMP_STRING(peer->notify.data); + else + return SNMP_STRING(""); + default: + break; + } + + return NULL; +} + +static uint8_t *bgpv2PeerEventTimesTable(struct variable *v, oid name[], + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + struct peer *peer; + struct ipaddr addr = {}; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + + peer = bgpv2PeerTable_lookup(v, name, length, exact, &addr); + if (!peer) + return NULL; + + switch (v->magic) { + case BGP4V2_PEER_FSM_ESTABLISHED_TIME: + if (!peer->uptime) + return SNMP_INTEGER(0); + else + return SNMP_INTEGER(monotime(NULL) - peer->uptime); + case BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME: + if (!peer->update_time) + return SNMP_INTEGER(0); + else + return SNMP_INTEGER(monotime(NULL) - peer->update_time); + default: + break; + } + + return NULL; +} + +static struct bgp_path_info * +bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, + struct bgp *bgp, struct prefix *addr, int exact) +{ + oid *offset; + int offsetlen; + struct bgp_path_info *path, *min; + struct bgp_dest *dest; + union sockunion su; + struct ipaddr paddr = {}; + size_t namelen = v ? v->namelen : BGP4V2_NLRI_ENTRY_OFFSET; + sa_family_t family; + sa_family_t min_family = 0; /* family of the selected min path */ + afi_t afi; + safi_t safi; + size_t afi_len; + long prefix_type = 0; + long peer_addr_type = 0; + long nrli_index = 1; + long cur_index = 0; + + /* Bgp4V2AddressFamilyIdentifierTC limited to IPv6 */ + if (name[namelen - 1] > IANA_AFI_IPV6) + return NULL; + afi = afi_iana2int(name[namelen - 1]); + afi_len = afi == AFI_IP ? IN_ADDR_SIZE : IN6_ADDR_SIZE; + assert(IS_VALID_AFI(afi)); + +#define BGP_NLRI_ENTRY_OFFSET namelen +#define BGP4V2_NLRI_V4_V4_OFFSET IN_ADDR_SIZE + IN_ADDR_SIZE + 5 +#define BGP4V2_NLRI_V4_V6_OFFSET IN_ADDR_SIZE + IN6_ADDR_SIZE + 5 +#define BGP4V2_NLRI_V6_V6_OFFSET IN6_ADDR_SIZE + IN6_ADDR_SIZE + 5 + + + sockunion_init(&su); + + if (exact) { + if (*length - namelen != BGP4V2_NLRI_V4_V4_OFFSET && + *length - namelen != BGP4V2_NLRI_V4_V6_OFFSET && + *length - namelen != BGP4V2_NLRI_V6_V6_OFFSET) + return NULL; + + /* Set OID offset for prefix type */ + offset = name + namelen; + + /* Bgp4V2SubsequentAddressFamilyIdentifierTC */ + /* limited to Labeled unicast */ + if (*offset > IANA_SAFI_LABELED_UNICAST) + return NULL; + safi = safi_iana2int(*offset); + offset++; + + /* get bgp4V2NlriPrefixType */ + prefix_type = *offset; + offset++; + + /* get bgp4V2NlriPrefix */ + if (prefix_type == IANA_AFI_IPV4) { + oid2in_addr(offset, IN_ADDR_SIZE, &addr->u.prefix4); + addr->family = AF_INET; + offset += IN_ADDR_SIZE; + } else if (prefix_type == IANA_AFI_IPV6) { + oid2in6_addr(offset, &addr->u.prefix6); + addr->family = AF_INET6; + offset += IN6_ADDR_SIZE; + } + + /* get bgp4V2NlriPrefixLen */ + addr->prefixlen = *offset; + offset++; + + /* get bgp4V2PeerRemoteAddrType */ + peer_addr_type = *offset; + if (peer_addr_type == IANA_AFI_IPV4) + family = AF_INET; + else + family = AF_INET6; + offset++; + + /* Peer address */ + su.sin.sin_family = family; + + /* get bgp4V2PeerRemoteAddr*/ + if (family == AF_INET) { + oid2in_addr(offset, IN_ADDR_SIZE, &su.sin.sin_addr); + offset += IN_ADDR_SIZE; + } else { + oid2in6_addr(offset, &su.sin6.sin6_addr); + offset += IN6_ADDR_SIZE; + } + + /* bgp4V2NlriIndex */ + nrli_index = *offset; + offset++; + + /* Lookup node */ + dest = bgp_node_lookup(bgp->rib[afi][safi], addr); + if (dest) { + for (path = bgp_dest_get_bgp_path_info(dest); path; + path = path->next) + if (sockunion_same(&path->peer->connection->su, + &su)) { + cur_index++; + if (cur_index == nrli_index) + return path; + } + + bgp_dest_unlock_node(dest); + } + + return NULL; + } + + /* Set OID offset for prefix type */ + offset = name + namelen; + offsetlen = *length - namelen; + + if (offsetlen == 0) { + dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); + } else { + + /* bgp4V2NlriAfi is already get */ + /* it is comming from the name parameter */ + + /* get bgp4V2NlriSafi */ + /* Bgp4V2SubsequentAddressFamilyIdentifierTC */ + /* limited to Labeled unicast */ + if (*offset > IANA_SAFI_LABELED_UNICAST) + return NULL; + safi = safi_iana2int(*offset); + offset++; + + /* get bgp4V2NlriPrefixType */ + prefix_type = *offset; + offset++; + /* get bgp4V2NlriPrefix */ + if (prefix_type == IANA_AFI_IPV4) { + oid2in_addr(offset, IN_ADDR_SIZE, &addr->u.prefix4); + addr->family = AF_INET; + offset += IN_ADDR_SIZE; + offsetlen -= IN_ADDR_SIZE; + } else if (prefix_type == IANA_AFI_IPV6) { + oid2in6_addr(offset, &addr->u.prefix6); + addr->family = AF_INET6; + offset += IN6_ADDR_SIZE; + offsetlen -= IN6_ADDR_SIZE; + } + + /* get bgp4V2NlriPrefixLen */ + if (offsetlen > 0) + addr->prefixlen = *offset; + else + addr->prefixlen = afi_len * 8; + + offset++; + offsetlen--; + + /* get node */ + dest = bgp_node_lookup(bgp->rib[afi][safi], addr); + } + + if (!dest) + return NULL; + + if (offsetlen > 0) { + /* get bgp4V2PeerRemoteAddrType */ + peer_addr_type = *offset; + if (peer_addr_type == IANA_AFI_IPV4) + family = AF_INET; + else + family = AF_INET6; + offset++; + + if (family == AF_INET) { + oid2in_addr(offset, IN_ADDR_SIZE, &paddr.ip._v4_addr); + offset += IN_ADDR_SIZE; + } else { + oid2in6_addr(offset, &paddr.ip._v6_addr); + offset += IN6_ADDR_SIZE; + } + /* get bgp4V2NlriIndex */ + nrli_index = *offset; + offset++; + + } else { + /* default case start with ipv4*/ + if (afi == AFI_IP) + family = AF_INET; + else + family = AF_INET6; + memset(&paddr.ip, 0, sizeof(paddr.ip)); + nrli_index = 1; + } + + do { + min = NULL; + min_family = 0; + cur_index = 0; + + for (path = bgp_dest_get_bgp_path_info(dest); path; + path = path->next) { + sa_family_t path_family = + sockunion_family(&path->peer->connection->su); + /* the next addr must be > to the current */ + if (path_family < family) + continue; + + if (family == AF_INET && + IPV4_ADDR_CMP(&paddr.ip._v4_addr, + &path->peer->connection->su.sin + .sin_addr) > 0) + continue; + else if (family == AF_INET6 && + IPV6_ADDR_CMP(&paddr.ip._v6_addr, + &path->peer->connection->su.sin6 + .sin6_addr) > 0) + continue; + + if (family == AF_INET && + IPV4_ADDR_CMP(&paddr.ip._v4_addr, + &path->peer->connection->su.sin + .sin_addr) == 0) { + if (cur_index == nrli_index) { + min = path; + min_family = family; + nrli_index++; + break; + } + cur_index++; + continue; + } else if (family == AF_INET6 && + IPV6_ADDR_CMP(&paddr.ip._v6_addr, + &path->peer->connection->su + .sin6.sin6_addr) == 0) { + if (cur_index == nrli_index) { + min = path; + min_family = family; + nrli_index++; + break; + } + cur_index++; + continue; + } + + /* first valid path its the min peer addr*/ + if (!min) { + min = path; + min_family = path_family; + continue; + } + + /* consider path < min */ + if (path_family < min_family) { + min = path; + min_family = path_family; + continue; + } + + if (path_family == AF_INET + && IPV4_ADDR_CMP(&path->peer->connection->su.sin.sin_addr, + &min->peer->connection->su.sin.sin_addr) + < 0) { + min = path; + min_family = path_family; + + } else if (path_family == AF_INET6 + && IPV6_ADDR_CMP( + &path->peer->connection->su.sin6.sin6_addr, + &min->peer->connection->su.sin6.sin6_addr) + < 0) { + min = path; + min_family = path_family; + } + } + + if (min) { + const struct prefix *rn_p = bgp_dest_get_prefix(dest); + + offset = name + namelen; + + /* encode bgp4V2NlriSafi*/ + *offset = SAFI_UNICAST; + offset++; + /* encode bgp4V2NlriPrefixType into index*/ + /* encode bgp4V2NlriPrefix into index */ + if (rn_p->family == AF_INET) { + *offset = IANA_AFI_IPV4; + offset++; + oid_copy_in_addr(offset, &rn_p->u.prefix4); + offset += IN_ADDR_SIZE; + } else { + *offset = IANA_AFI_IPV6; + offset++; + oid_copy_in6_addr(offset, &rn_p->u.prefix6); + offset += IN6_ADDR_SIZE; + } + /* encode bgp4V2NlriPrefixLen into index*/ + *offset = rn_p->prefixlen; + offset++; + + /* Encode bgp4V2PeerRemoteAddrType */ + /* Encode bgp4V2PeerRemoteAddr */ + if (min_family == AF_INET) { + *offset = IANA_AFI_IPV4; + offset++; + oid_copy_in_addr(offset, + &min->peer->connection->su.sin.sin_addr); + offset += IN_ADDR_SIZE; + addr->u.prefix4 = rn_p->u.prefix4; + } else { + *offset = IANA_AFI_IPV6; + offset++; + oid_copy_in6_addr( + offset, &min->peer->connection->su.sin6.sin6_addr); + offset += IN6_ADDR_SIZE; + addr->u.prefix6 = rn_p->u.prefix6; + } + + /* Encode bgp4V2NlriIndex*/ + + *offset = nrli_index; + offset++; + + *length = offset - name; + + addr->prefixlen = rn_p->prefixlen; + addr->family = rn_p->family; + + bgp_dest_unlock_node(dest); + + return min; + } + + memset(&paddr.ip, 0, sizeof(paddr.ip)); + nrli_index = 1; + + } while ((dest = bgp_route_next(dest))); + + return NULL; +} + +static uint8_t *bgp4v2PathAttrTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct bgp *bgp; + struct bgp_path_info *path; + struct peer_af *paf = NULL; + struct prefix addr = {}; + const struct prefix *prefix = NULL; + enum bgp_af_index index; + + bgp = bgp_get_default(); + if (!bgp) + return NULL; + + if (smux_header_table(v, name, length, exact, var_len, write_method) == + MATCH_FAILED) + return NULL; + + path = bgp4v2PathAttrLookup(v, name, length, bgp, &addr, exact); + if (!path) + return NULL; + + prefix = bgp_dest_get_prefix(path->net); + + AF_FOREACH (index) { + paf = path->peer->peer_af_array[index]; + if (paf) + break; + } + + switch (v->magic) { + case BGP4V2_NLRI_INDEX: + return SNMP_INTEGER(0); + case BGP4V2_NLRI_AFI: + if (paf) + return SNMP_INTEGER(paf->afi); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_SAFI: + if (paf) + return SNMP_INTEGER(paf->safi); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_PREFIX_TYPE: + if (paf) + return SNMP_INTEGER(paf->afi); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_PREFIX: + if (prefix->family == AF_INET6) + return SNMP_IP6ADDRESS(prefix->u.prefix6); + else + return SNMP_IPADDRESS(prefix->u.prefix4); + case BGP4V2_NLRI_PREFIX_LEN: + return SNMP_INTEGER(prefix->prefixlen); + case BGP4V2_NLRI_BEST: + if (CHECK_FLAG(path->flags, BGP_PATH_SELECTED)) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_CALC_LOCAL_PREF: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) + return SNMP_INTEGER(path->attr->local_pref); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_ORIGIN: + switch (path->attr->origin) { + case BGP_ORIGIN_IGP: + return SNMP_INTEGER(1); + case BGP_ORIGIN_EGP: + return SNMP_INTEGER(2); + case BGP_ORIGIN_INCOMPLETE: + return SNMP_INTEGER(3); + default: + return SNMP_INTEGER(0); + } + case BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE: + switch (path->attr->mp_nexthop_len) { + case BGP_ATTR_NHLEN_IPV4: + return SNMP_INTEGER(1); + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + return SNMP_INTEGER(2); + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + if (CHECK_FLAG(path->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + return SNMP_INTEGER(2); + else + return SNMP_INTEGER(4); + default: + return SNMP_INTEGER(1); + } + case BGP4V2_NLRI_NEXT_HOP_ADDR: + switch (path->attr->mp_nexthop_len) { + case BGP_ATTR_NHLEN_IPV4: + return SNMP_IPADDRESS(path->attr->mp_nexthop_global_in); + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + return SNMP_IP6ADDRESS(path->attr->mp_nexthop_global); + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + if (CHECK_FLAG(path->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) + return SNMP_IP6ADDRESS( + path->attr->mp_nexthop_global); + else + return SNMP_IP6ADDRESS( + path->attr->mp_nexthop_local); + default: + return SNMP_IPADDRESS(path->attr->nexthop); + } + break; + case BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE: + case BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR: + /* Not properly defined in specification what should be here. */ + break; + case BGP4V2_NLRI_LOCAL_PREF_PRESENT: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_LOCAL_PREF: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) + return SNMP_INTEGER(path->attr->local_pref); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_MED_PRESENT: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_MED: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) + return SNMP_INTEGER(path->attr->med); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_ATOMIC_AGGREGATE: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_ATOMIC_AGGREGATE))) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_AGGREGATOR_PRESENT: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_AGGREGATOR_AS: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) + return SNMP_INTEGER(path->attr->aggregator_as); + else + return SNMP_INTEGER(0); + case BGP4V2_NLRI_AGGREGATOR_ADDR: + if (CHECK_FLAG(path->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_AGGREGATOR))) + return SNMP_IPADDRESS(path->attr->aggregator_addr); + else + return SNMP_IPADDRESS(bgp_empty_addr); + case BGP4V2_NLRI_AS_PATH_CALC_LENGTH: + return SNMP_INTEGER(path->attr->aspath->segments->length); + case BGP4V2_NLRI_AS_PATH: + return aspath_snmp_pathseg(path->attr->aspath, var_len); + case BGP4V2_NLRI_PATH_ATTR_UNKNOWN: + *var_len = 0; + return NULL; + } + return NULL; +} + +/* BGP V2 Traps. */ +static struct trap_object bgpv2TrapEstListv4[] = { + { 6, { 1, 2, 1, BGP4V2_PEER_STATE, 1, 1 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 1 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 1 } } +}; + +static struct trap_object bgpv2TrapEstListv6[] = { + { 6, { 1, 2, 1, BGP4V2_PEER_STATE, 1, 2 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 2 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 2 } } +}; + +static struct trap_object bgpv2TrapBackListv4[] = { + { 6, { 1, 2, 1, BGP4V2_PEER_STATE, 1, 1 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 1 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 1 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 1 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 1 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 1 } } +}; + +static struct trap_object bgpv2TrapBackListv6[] = { + { 6, { 1, 2, 1, BGP4V2_PEER_STATE, 1, 2 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 2 } }, + { 6, { 1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 2 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 2 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 2 } }, + { 6, { 1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 2 } } +}; + +static struct variable bgpv2_variables[] = { + /* bgp4V2PeerEntry */ + {BGP4V2_PEER_INSTANCE, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_INSTANCE, 1, 1}}, + {BGP4V2_PEER_INSTANCE, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_INSTANCE, 1, 2}}, + {BGP4V2_PEER_LOCAL_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 1, 1}}, + {BGP4V2_PEER_LOCAL_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 1, 2}}, + {BGP4V2_PEER_LOCAL_ADDR, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 1, 1}}, + {BGP4V2_PEER_LOCAL_ADDR, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 1, 2}}, + {BGP4V2_PEER_REMOTE_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 1, 1}}, + {BGP4V2_PEER_REMOTE_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 1, 2}}, + {BGP4V2_PEER_REMOTE_ADDR, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 1, 1}}, + {BGP4V2_PEER_REMOTE_ADDR, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 1, 2}}, + {BGP4V2_PEER_LOCAL_PORT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 1}}, + {BGP4V2_PEER_LOCAL_PORT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 2}}, + {BGP4V2_PEER_LOCAL_AS, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 1, 1}}, + {BGP4V2_PEER_LOCAL_AS, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 1, 2}}, + {BGP4V2_PEER_LOCAL_IDENTIFIER, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 1, 1}}, + {BGP4V2_PEER_LOCAL_IDENTIFIER, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 1, 2}}, + {BGP4V2_PEER_REMOTE_PORT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 1}}, + {BGP4V2_PEER_REMOTE_PORT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 2}}, + {BGP4V2_PEER_REMOTE_AS, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 1, 1}}, + {BGP4V2_PEER_REMOTE_AS, + ASN_UNSIGNED, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 1, 2}}, + {BGP4V2_PEER_REMOTE_IDENTIFIER, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 1, 1}}, + {BGP4V2_PEER_REMOTE_IDENTIFIER, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 1, 2}}, + {BGP4V2_PEER_ADMIN_STATUS, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 1, 1}}, + {BGP4V2_PEER_ADMIN_STATUS, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 1, 2}}, + {BGP4V2_PEER_STATE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_STATE, 1, 1}}, + {BGP4V2_PEER_STATE, + ASN_INTEGER, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_STATE, 1, 2}}, + {BGP4V2_PEER_DESCRIPTION, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 1, 1}}, + {BGP4V2_PEER_DESCRIPTION, + ASN_OCTET_STR, + RONLY, + bgpv2PeerTable, + 6, + {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 1, 2}}, + /* bgp4V2PeerErrorsEntry */ + {BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, + ASN_TIMETICKS, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, + ASN_TIMETICKS, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_CODE_SENT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_CODE_SENT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, + ASN_UNSIGNED, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_SENT_TIME, + ASN_TIMETICKS, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_SENT_TIME, + ASN_TIMETICKS, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_SENT_TEXT, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_SENT_TEXT, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 1, 2}}, + {BGP4V2_PEER_LAST_ERROR_SENT_DATA, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 1, 1}}, + {BGP4V2_PEER_LAST_ERROR_SENT_DATA, + ASN_OCTET_STR, + RONLY, + bgpv2PeerErrorsTable, + 6, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 1, 2}}, + /* bgp4V2PeerEventTimesEntry */ + {BGP4V2_PEER_FSM_ESTABLISHED_TIME, + ASN_UNSIGNED, + RONLY, + bgpv2PeerEventTimesTable, + 6, + {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 1, 1}}, + {BGP4V2_PEER_FSM_ESTABLISHED_TIME, + ASN_UNSIGNED, + RONLY, + bgpv2PeerEventTimesTable, + 6, + {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 1, 2}}, + {BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, + ASN_UNSIGNED, + RONLY, + bgpv2PeerEventTimesTable, + 6, + {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 1, 1}}, + {BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, + ASN_UNSIGNED, + RONLY, + bgpv2PeerEventTimesTable, + 6, + {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 1, 2}}, + /* bgp4V2NlriTable */ + {BGP4V2_NLRI_INDEX, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_INDEX, 1, 1}}, + {BGP4V2_NLRI_INDEX, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_INDEX, 1, 2}}, + {BGP4V2_NLRI_AFI, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AFI, 1, 1}}, + {BGP4V2_NLRI_AFI, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AFI, 1, 2}}, + {BGP4V2_NLRI_SAFI, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_SAFI, 1, 1}}, + {BGP4V2_NLRI_SAFI, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_SAFI, 1, 2}}, + {BGP4V2_NLRI_PREFIX_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 1, 1}}, + {BGP4V2_NLRI_PREFIX_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 1, 2}}, + {BGP4V2_NLRI_PREFIX, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX, 1, 1}}, + {BGP4V2_NLRI_PREFIX, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX, 1, 2}}, + {BGP4V2_NLRI_PREFIX_LEN, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 1, 1}}, + {BGP4V2_NLRI_PREFIX_LEN, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 1, 2}}, + {BGP4V2_NLRI_BEST, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_BEST, 1, 1}}, + {BGP4V2_NLRI_BEST, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_BEST, 1, 2}}, + {BGP4V2_NLRI_CALC_LOCAL_PREF, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 1, 1}}, + {BGP4V2_NLRI_CALC_LOCAL_PREF, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 1, 2}}, + {BGP4V2_NLRI_ORIGIN, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_ORIGIN, 1, 1}}, + {BGP4V2_NLRI_ORIGIN, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_ORIGIN, 1, 2}}, + {BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 1, 1}}, + {BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 1, 2}}, + {BGP4V2_NLRI_NEXT_HOP_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 1, 1}}, + {BGP4V2_NLRI_NEXT_HOP_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 1, 2}}, + {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 1, 1}}, + {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 1, 2}}, + {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 1, 1}}, + {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 1, 2}}, + {BGP4V2_NLRI_LOCAL_PREF_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 1, 1}}, + {BGP4V2_NLRI_LOCAL_PREF_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 1, 2}}, + {BGP4V2_NLRI_LOCAL_PREF, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 1, 1}}, + {BGP4V2_NLRI_LOCAL_PREF, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 1, 2}}, + {BGP4V2_NLRI_MED_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 1, 1}}, + {BGP4V2_NLRI_MED_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 1, 2}}, + {BGP4V2_NLRI_MED, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_MED, 1, 1}}, + {BGP4V2_NLRI_MED, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_MED, 1, 2}}, + {BGP4V2_NLRI_ATOMIC_AGGREGATE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 1, 1}}, + {BGP4V2_NLRI_ATOMIC_AGGREGATE, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 1, 2}}, + {BGP4V2_NLRI_AGGREGATOR_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 1, 1}}, + {BGP4V2_NLRI_AGGREGATOR_PRESENT, + ASN_INTEGER, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 1, 2}}, + {BGP4V2_NLRI_AGGREGATOR_AS, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 1, 1}}, + {BGP4V2_NLRI_AGGREGATOR_AS, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 1, 2}}, + {BGP4V2_NLRI_AGGREGATOR_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 1, 1}}, + {BGP4V2_NLRI_AGGREGATOR_ADDR, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 1, 2}}, + {BGP4V2_NLRI_AS_PATH_CALC_LENGTH, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 1, 1}}, + {BGP4V2_NLRI_AS_PATH_CALC_LENGTH, + ASN_UNSIGNED, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 1, 2}}, + {BGP4V2_NLRI_AS_PATH_STRING, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 1, 1}}, + {BGP4V2_NLRI_AS_PATH_STRING, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 1, 2}}, + {BGP4V2_NLRI_AS_PATH, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH, 1, 1}}, + {BGP4V2_NLRI_AS_PATH, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_AS_PATH, 1, 2}}, + {BGP4V2_NLRI_PATH_ATTR_UNKNOWN, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 1, 1}}, + {BGP4V2_NLRI_PATH_ATTR_UNKNOWN, + ASN_OCTET_STR, + RONLY, + bgp4v2PathAttrTable, + 6, + {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 1, 2}}, +}; + +int bgpv2TrapEstablished(struct peer *peer) +{ + oid index[sizeof(oid) * IN6_ADDR_SIZE]; + size_t length; + + if (!CHECK_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2)) + return 0; + + /* Check if this peer just went to Established */ + if ((peer->connection->ostatus != OpenConfirm) || + !(peer_established(peer->connection))) + return 0; + + switch (sockunion_family(&peer->connection->su)) { + case AF_INET: + oid_copy_in_addr(index, &peer->connection->su.sin.sin_addr); + length = IN_ADDR_SIZE; + smux_trap(bgpv2_variables, array_size(bgpv2_variables), + bgpv2_trap_oid, array_size(bgpv2_trap_oid), bgpv2_oid, + sizeof(bgpv2_oid) / sizeof(oid), index, length, + bgpv2TrapEstListv4, array_size(bgpv2TrapEstListv4), + BGP4V2ESTABLISHED); + break; + case AF_INET6: + oid_copy_in6_addr(index, &peer->connection->su.sin6.sin6_addr); + length = IN6_ADDR_SIZE; + smux_trap(bgpv2_variables, array_size(bgpv2_variables), + bgpv2_trap_oid, array_size(bgpv2_trap_oid), bgpv2_oid, + sizeof(bgpv2_oid) / sizeof(oid), index, length, + bgpv2TrapEstListv6, array_size(bgpv2TrapEstListv6), + BGP4V2ESTABLISHED); + break; + default: + break; + } + + return 0; +} + +int bgpv2TrapBackwardTransition(struct peer *peer) +{ + oid index[sizeof(oid) * IN6_ADDR_SIZE]; + size_t length; + + if (!CHECK_FLAG(bm->options, BGP_OPT_TRAPS_BGP4MIBV2)) + return 0; + + switch (sockunion_family(&peer->connection->su)) { + case AF_INET: + oid_copy_in_addr(index, &peer->connection->su.sin.sin_addr); + length = IN_ADDR_SIZE; + smux_trap(bgpv2_variables, array_size(bgpv2_variables), + bgpv2_trap_oid, array_size(bgpv2_trap_oid), bgpv2_oid, + sizeof(bgpv2_oid) / sizeof(oid), index, length, + bgpv2TrapBackListv4, array_size(bgpv2TrapBackListv4), + BGP4V2BACKWARDTRANSITION); + break; + case AF_INET6: + oid_copy_in6_addr(index, &peer->connection->su.sin6.sin6_addr); + length = IN6_ADDR_SIZE; + smux_trap(bgpv2_variables, array_size(bgpv2_variables), + bgpv2_trap_oid, array_size(bgpv2_trap_oid), bgpv2_oid, + sizeof(bgpv2_oid) / sizeof(oid), index, length, + bgpv2TrapBackListv6, array_size(bgpv2TrapBackListv6), + BGP4V2BACKWARDTRANSITION); + break; + default: + break; + } + + return 0; +} + +int bgp_snmp_bgp4v2_init(struct event_loop *tm) +{ + REGISTER_MIB("mibII/bgpv2", bgpv2_variables, variable, bgpv2_oid); + return 0; +} diff --git a/bgpd/bgp_snmp_bgp4v2.h b/bgpd/bgp_snmp_bgp4v2.h new file mode 100644 index 0000000..ca35533 --- /dev/null +++ b/bgpd/bgp_snmp_bgp4v2.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP4V2-MIB SNMP support + * + * Using: http://www.circitor.fr/Mibs/Html/B/BGP4V2-MIB.php + * + * Copyright (C) 2022 Donatas Abraitis + */ + +#ifndef _FRR_BGP_SNMP_BGP4V2_H_ +#define _FRR_BGP_SNMP_BGP4V2_H_ + +/* bgp4V2 */ +#define BGP4V2MIB 1, 3, 6, 1, 3, 5, 1 + +/* bgp4V2PeerEntry: + * offset 1.3.6.1.3.5.1.1.2.1.x.(1|2).(4|16) = 13 + * offset 1.3.6.1.4.1.7336.3.2.1.1.2.1.x.1.(1|2) = 16 + */ + + +/* bgpTraps */ +#define BGP4V2ESTABLISHED 1 +#define BGP4V2BACKWARDTRANSITION 2 + +/* bgpPeerTable */ + +#define BGP4V2_PEER_ENTRY_OFFSET 13 +#define BGP4V2_PEER_INSTANCE 1 +#define BGP4V2_PEER_LOCAL_ADDR_TYPE 2 +#define BGP4V2_PEER_LOCAL_ADDR 3 +#define BGP4V2_PEER_REMOTE_ADDR_TYPE 4 +#define BGP4V2_PEER_REMOTE_ADDR 5 +#define BGP4V2_PEER_LOCAL_PORT 6 +#define BGP4V2_PEER_LOCAL_AS 7 +#define BGP4V2_PEER_LOCAL_IDENTIFIER 8 +#define BGP4V2_PEER_REMOTE_PORT 9 +#define BGP4V2_PEER_REMOTE_AS 10 +#define BGP4V2_PEER_REMOTE_IDENTIFIER 11 +#define BGP4V2_PEER_ADMIN_STATUS 12 +#define BGP4V2_PEER_STATE 13 +#define BGP4V2_PEER_DESCRIPTION 14 + +/* bgp4V2PeerErrorsEntry */ +#define BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED 1 +#define BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED 2 +#define BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME 3 +#define BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT 4 +#define BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA 5 +#define BGP4V2_PEER_LAST_ERROR_CODE_SENT 6 +#define BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT 7 +#define BGP4V2_PEER_LAST_ERROR_SENT_TIME 8 +#define BGP4V2_PEER_LAST_ERROR_SENT_TEXT 9 +#define BGP4V2_PEER_LAST_ERROR_SENT_DATA 10 + +/* bgp4V2PeerEventTimesEntry */ +#define BGP4V2_PEER_FSM_ESTABLISHED_TIME 1 +#define BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME 2 + +/* bgp4V2NlriEntry + * offset 1.3.6.1.3.5.1.1.9.1.x.(1|2).(4|16) = 13 + * offset 1.3.6.1.4.1.7336.3.2.1.1.9.1.x.1.(1|2) = 16 + */ +#define BGP4V2_NLRI_ENTRY_OFFSET 13 +#define BGP4V2_NLRI_INDEX 1 +#define BGP4V2_NLRI_AFI 2 +#define BGP4V2_NLRI_SAFI 3 +#define BGP4V2_NLRI_PREFIX_TYPE 4 +#define BGP4V2_NLRI_PREFIX 5 +#define BGP4V2_NLRI_PREFIX_LEN 6 +#define BGP4V2_NLRI_BEST 7 +#define BGP4V2_NLRI_CALC_LOCAL_PREF 8 +#define BGP4V2_NLRI_ORIGIN 9 +#define BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE 10 +#define BGP4V2_NLRI_NEXT_HOP_ADDR 11 +#define BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE 12 +#define BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR 13 +#define BGP4V2_NLRI_LOCAL_PREF_PRESENT 14 +#define BGP4V2_NLRI_LOCAL_PREF 15 +#define BGP4V2_NLRI_MED_PRESENT 16 +#define BGP4V2_NLRI_MED 17 +#define BGP4V2_NLRI_ATOMIC_AGGREGATE 18 +#define BGP4V2_NLRI_AGGREGATOR_PRESENT 19 +#define BGP4V2_NLRI_AGGREGATOR_AS 20 +#define BGP4V2_NLRI_AGGREGATOR_ADDR 21 +#define BGP4V2_NLRI_AS_PATH_CALC_LENGTH 22 +#define BGP4V2_NLRI_AS_PATH_STRING 23 +#define BGP4V2_NLRI_AS_PATH 24 +#define BGP4V2_NLRI_PATH_ATTR_UNKNOWN 25 + +/* bgp4V2Notifications */ +#define BGP4V2_ESTABLISHED_NOTIFICATION 1 +#define BGP4V2_BACKWARD_TRANSITION_NOTIFICATION 2 + +extern int bgp_snmp_bgp4v2_init(struct event_loop *tm); +extern int bgpv2TrapEstablished(struct peer *peer); +extern int bgpv2TrapBackwardTransition(struct peer *peer); + +#endif /* _FRR_BGP_SNMP_BGP4V2_H_ */ diff --git a/bgpd/bgp_table.c b/bgpd/bgp_table.c new file mode 100644 index 0000000..781909b --- /dev/null +++ b/bgpd/bgp_table.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP routing table + * Copyright (C) 1998, 2001 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "memory.h" +#include "sockunion.h" +#include "queue.h" +#include "filter.h" +#include "command.h" +#include "printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgp_addpath.h" +#include "bgp_trace.h" + +void bgp_table_lock(struct bgp_table *rt) +{ + rt->lock++; +} + +void bgp_table_unlock(struct bgp_table *rt) +{ + assert(rt->lock > 0); + rt->lock--; + + if (rt->lock != 0) { + return; + } + + route_table_finish(rt->route_table); + rt->route_table = NULL; + + XFREE(MTYPE_BGP_TABLE, rt); +} + +void bgp_table_finish(struct bgp_table **rt) +{ + if (*rt != NULL) { + bgp_table_unlock(*rt); + *rt = NULL; + } +} + +/* + * bgp_dest_lock_node + */ +struct bgp_dest *bgp_dest_lock_node(struct bgp_dest *dest) +{ + frrtrace(1, frr_bgp, bgp_dest_lock, dest); + struct route_node *rn = route_lock_node(bgp_dest_to_rnode(dest)); + + return bgp_dest_from_rnode(rn); +} + +/* + * bgp_dest_get_prefix_str + */ +const char *bgp_dest_get_prefix_str(struct bgp_dest *dest) +{ + const struct prefix *p = NULL; + static char str[PREFIX_STRLEN] = {0}; + + p = bgp_dest_get_prefix(dest); + if (p) + return prefix2str(p, str, sizeof(str)); + + return NULL; +} + +/* + * bgp_dest_unlock_node + */ +inline struct bgp_dest *bgp_dest_unlock_node(struct bgp_dest *dest) +{ + frrtrace(1, frr_bgp, bgp_dest_unlock, dest); + bgp_delete_listnode(dest); + struct route_node *rn = bgp_dest_to_rnode(dest); + + if (rn->lock == 1) { + struct bgp_table *rt = bgp_dest_table(dest); + if (rt->bgp) { + bgp_addpath_free_node_data(&rt->bgp->tx_addpath, + &dest->tx_addpath, rt->afi, + rt->safi); + } + XFREE(MTYPE_BGP_NODE, dest); + dest = NULL; + rn->info = NULL; + } + route_unlock_node(rn); + + return dest; +} + +/* + * bgp_node_destroy + */ +static void bgp_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, struct route_node *node) +{ + struct bgp_dest *dest; + struct bgp_table *rt; + dest = bgp_dest_from_rnode(node); + rt = table->info; + if (dest) { + if (rt->bgp) { + bgp_addpath_free_node_data(&rt->bgp->tx_addpath, + &dest->tx_addpath, + rt->afi, rt->safi); + } + XFREE(MTYPE_BGP_NODE, dest); + node->info = NULL; + } + + XFREE(MTYPE_ROUTE_NODE, node); +} + +/* + * Function vector to customize the behavior of the route table + * library for BGP route tables. + */ +route_table_delegate_t bgp_table_delegate = { .create_node = route_node_create, + .destroy_node = bgp_node_destroy }; + +/* + * bgp_table_init + */ +struct bgp_table *bgp_table_init(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_table *rt; + + rt = XCALLOC(MTYPE_BGP_TABLE, sizeof(struct bgp_table)); + + rt->route_table = route_table_init_with_delegate(&bgp_table_delegate); + + /* + * Set up back pointer to bgp_table. + */ + route_table_set_info(rt->route_table, rt); + + /* + * pointer to bgp instance allows working back from bgp_path_info to bgp + */ + rt->bgp = bgp; + + bgp_table_lock(rt); + rt->afi = afi; + rt->safi = safi; + + return rt; +} + +/* Delete the route node from the selection deferral route list */ +void bgp_delete_listnode(struct bgp_dest *dest) +{ + const struct route_node *rn = NULL; + struct bgp_table *table = NULL; + struct bgp *bgp = NULL; + afi_t afi; + safi_t safi; + + /* If the route to be deleted is selection pending, update the + * route node in gr_info + */ + if (CHECK_FLAG(dest->flags, BGP_NODE_SELECT_DEFER)) { + table = bgp_dest_table(dest); + + if (table) { + bgp = table->bgp; + afi = table->afi; + safi = table->safi; + } else + return; + + rn = bgp_dest_to_rnode(dest); + + if (bgp && rn && rn->lock == 1) { + /* Delete the route from the selection pending list */ + bgp->gr_info[afi][safi].gr_deferred--; + UNSET_FLAG(dest->flags, BGP_NODE_SELECT_DEFER); + } + } +} + +struct bgp_dest *bgp_table_subtree_lookup(const struct bgp_table *table, + const struct prefix *p) +{ + struct bgp_dest *dest = bgp_dest_from_rnode(table->route_table->top); + struct bgp_dest *matched = NULL; + + if (dest == NULL) + return NULL; + + + while (dest) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + struct route_node *node = dest->rn; + + if (dest_p->prefixlen >= p->prefixlen) { + if (!prefix_match(p, dest_p)) + return NULL; + + matched = dest; + break; + } + + if (!prefix_match(dest_p, p)) + return NULL; + + if (dest_p->prefixlen == p->prefixlen) { + matched = dest; + break; + } + + dest = bgp_dest_from_rnode( + node->link[prefix_bit(&p->u.prefix, dest_p->prefixlen)]); + } + + if (!matched) + return NULL; + + bgp_dest_lock_node(matched); + return matched; +} + +printfrr_ext_autoreg_p("BD", printfrr_bd); +static ssize_t printfrr_bd(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct bgp_dest *dest = ptr; + const struct prefix *p = bgp_dest_get_prefix(dest); + char cbuf[PREFIX_STRLEN]; + + if (!dest) + return bputs(buf, "(null)"); + +#if !defined(DEV_BUILD) + /* need to get the real length even if buffer too small */ + prefix2str(p, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +#else + return bprintfrr(buf, "%s(%p)", prefix2str(p, cbuf, sizeof(cbuf)), dest); +#endif +} diff --git a/bgpd/bgp_table.h b/bgpd/bgp_table.h new file mode 100644 index 0000000..130f5ca --- /dev/null +++ b/bgpd/bgp_table.h @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP routing table + * Copyright (C) 1998, 2001 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_TABLE_H +#define _QUAGGA_BGP_TABLE_H + +#include "mpls.h" +#include "table.h" +#include "queue.h" +#include "linklist.h" +#include "bgpd.h" +#include "bgp_advertise.h" + +struct bgp_table { + /* table belongs to this instance */ + struct bgp *bgp; + + /* afi/safi of this table */ + afi_t afi; + safi_t safi; + + int lock; + + /* soft_reconfig_table in progress */ + bool soft_reconfig_init; + struct event *soft_reconfig_thread; + + /* list of peers on which soft_reconfig_table has to run */ + struct list *soft_reconfig_peers; + + struct route_table *route_table; + uint64_t version; +}; + +enum bgp_path_selection_reason { + bgp_path_selection_none, + bgp_path_selection_first, + bgp_path_selection_evpn_sticky_mac, + bgp_path_selection_evpn_seq, + bgp_path_selection_evpn_local_path, + bgp_path_selection_evpn_non_proxy, + bgp_path_selection_evpn_lower_ip, + bgp_path_selection_weight, + bgp_path_selection_local_pref, + bgp_path_selection_accept_own, + bgp_path_selection_local_route, + bgp_path_selection_aigp, + bgp_path_selection_confed_as_path, + bgp_path_selection_as_path, + bgp_path_selection_origin, + bgp_path_selection_med, + bgp_path_selection_peer, + bgp_path_selection_confed, + bgp_path_selection_igp_metric, + bgp_path_selection_older, + bgp_path_selection_router_id, + bgp_path_selection_cluster_length, + bgp_path_selection_stale, + bgp_path_selection_local_configured, + bgp_path_selection_neighbor_ip, + bgp_path_selection_default, +}; + +struct bgp_dest { + struct route_node *rn; + + void *info; + + struct bgp_adj_out_rb adj_out; + + struct bgp_adj_in *adj_in; + + struct bgp_dest *pdest; + + STAILQ_ENTRY(bgp_dest) pq; + + struct zebra_announce_item zai; + struct bgp_path_info *za_bgp_pi; + struct bgpevpn *za_vpn; + bool za_is_sync; + + uint64_t version; + + mpls_label_t local_label; + + uint16_t flags; +#define BGP_NODE_PROCESS_SCHEDULED (1 << 0) +#define BGP_NODE_USER_CLEAR (1 << 1) +#define BGP_NODE_LABEL_CHANGED (1 << 2) +#define BGP_NODE_REGISTERED_FOR_LABEL (1 << 3) +#define BGP_NODE_SELECT_DEFER (1 << 4) +#define BGP_NODE_FIB_INSTALL_PENDING (1 << 5) +#define BGP_NODE_FIB_INSTALLED (1 << 6) +#define BGP_NODE_LABEL_REQUESTED (1 << 7) +#define BGP_NODE_SOFT_RECONFIG (1 << 8) +#define BGP_NODE_PROCESS_CLEAR (1 << 9) +#define BGP_NODE_SCHEDULE_FOR_INSTALL (1 << 10) +#define BGP_NODE_SCHEDULE_FOR_DELETE (1 << 11) + + struct bgp_addpath_node_data tx_addpath; + + enum bgp_path_selection_reason reason; +}; + +DECLARE_LIST(zebra_announce, struct bgp_dest, zai); + +extern void bgp_delete_listnode(struct bgp_dest *dest); +/* + * bgp_table_iter_t + * + * Structure that holds state for iterating over a bgp table. + */ +typedef struct bgp_table_iter_t_ { + struct bgp_table *table; + route_table_iter_t rt_iter; +} bgp_table_iter_t; + +extern struct bgp_table *bgp_table_init(struct bgp *bgp, afi_t, safi_t); +extern void bgp_table_lock(struct bgp_table *); +extern void bgp_table_unlock(struct bgp_table *); +extern void bgp_table_finish(struct bgp_table **); +extern struct bgp_dest *bgp_dest_unlock_node(struct bgp_dest *dest); +extern struct bgp_dest *bgp_dest_lock_node(struct bgp_dest *dest); +extern const char *bgp_dest_get_prefix_str(struct bgp_dest *dest); + + +/* + * bgp_dest_from_rnode + * + * Returns the bgp_dest structure corresponding to a route_node. + */ +static inline struct bgp_dest *bgp_dest_from_rnode(struct route_node *rnode) +{ + return (rnode && rnode->info) ? (struct bgp_dest *)rnode->info : NULL; +} + +/* + * bgp_dest_to_rnode + * + * Returns the route_node structure corresponding to a bgp_dest. + */ +static inline struct route_node *bgp_dest_to_rnode(const struct bgp_dest *dest) +{ + return dest ? dest->rn : NULL; +} + +/* + * bgp_dest_table + * + * Returns the bgp_table that the given dest is in. + */ +static inline struct bgp_table *bgp_dest_table(struct bgp_dest *dest) +{ + return route_table_get_info(bgp_dest_to_rnode(dest)->table); +} + +/* + * bgp_dest_parent_nolock + * + * Gets the parent dest of the given node without locking it. + */ +static inline struct bgp_dest *bgp_dest_parent_nolock(struct bgp_dest *dest) +{ + struct route_node *rn = bgp_dest_to_rnode(dest)->parent; + + while (rn && !rn->info) + rn = rn->parent; + + return bgp_dest_from_rnode(rn); +} + +/* + * bgp_table_top_nolock + * + * Gets the top dest in the table without locking it. + * + * @see bgp_table_top + */ +static inline struct bgp_dest * +bgp_table_top_nolock(const struct bgp_table *const table) +{ + struct route_node *top; + struct route_node *rn = top = table->route_table->top; + + while (rn && !rn->info) { + if (rn == top) + route_lock_node(rn); + rn = route_next(rn); + } + if (rn && rn != top) + route_unlock_node(rn); + return rn ? rn->info : NULL; +} + +/* + * bgp_table_top + */ +static inline struct bgp_dest * +bgp_table_top(const struct bgp_table *const table) +{ + struct route_node *rn = route_top(table->route_table); + + while (rn && !rn->info) + rn = route_next(rn); + return rn ? rn->info : NULL; +} + +/* + * bgp_route_next + */ +static inline struct bgp_dest *bgp_route_next(struct bgp_dest *dest) +{ + struct route_node *rn = route_next(bgp_dest_to_rnode(dest)); + + while (rn && !rn->info) + rn = route_next(rn); + return bgp_dest_from_rnode(rn); +} + +/* + * bgp_route_next_until + */ +static inline struct bgp_dest *bgp_route_next_until(struct bgp_dest *dest, + struct bgp_dest *limit) +{ + struct route_node *rnode; + + rnode = route_next_until(bgp_dest_to_rnode(dest), + bgp_dest_to_rnode(limit)); + + while (rnode && !rnode->info) + rnode = route_next_until(rnode, bgp_dest_to_rnode(limit)); + + return bgp_dest_from_rnode(rnode); +} + +/* + * bgp_node_get + */ +static inline struct bgp_dest *bgp_node_get(struct bgp_table *const table, + const struct prefix *p) +{ + struct route_node *rn = route_node_get(table->route_table, p); + + if (!rn->info) { + struct bgp_dest *dest = XCALLOC(MTYPE_BGP_NODE, + sizeof(struct bgp_dest)); + + RB_INIT(bgp_adj_out_rb, &dest->adj_out); + rn->info = dest; + dest->rn = rn; + } + return rn->info; +} + +/* + * bgp_node_lookup + */ +static inline struct bgp_dest * +bgp_node_lookup(const struct bgp_table *const table, const struct prefix *p) +{ + struct route_node *rn = route_node_lookup(table->route_table, p); + + return bgp_dest_from_rnode(rn); +} + +/* + * bgp_node_match + */ +static inline struct bgp_dest *bgp_node_match(const struct bgp_table *table, + const struct prefix *p) +{ + struct route_node *rn = route_node_match(table->route_table, p); + + return bgp_dest_from_rnode(rn); +} + +static inline unsigned long bgp_table_count(const struct bgp_table *const table) +{ + return route_table_count(table->route_table); +} + +/* + * bgp_table_get_next + */ +static inline struct bgp_dest *bgp_table_get_next(const struct bgp_table *table, + const struct prefix *p) +{ + struct route_node *rn = route_table_get_next(table->route_table, p); + + while (rn && !rn->info) + rn = route_next(rn); + return bgp_dest_from_rnode(rn); +} + +/* This would benefit from a real atomic operation... + * until then. */ +static inline uint64_t bgp_table_next_version(struct bgp_table *table) +{ + return ++table->version; +} + +static inline uint64_t bgp_table_version(struct bgp_table *table) +{ + return table->version; +} + +/* Find the subtree of the prefix p + * + * This will return the first node that belongs the the subtree of p. Including + * p itself, if it is in the tree. + * + * If the subtree is not present in the table, NULL is returned. + */ +struct bgp_dest *bgp_table_subtree_lookup(const struct bgp_table *table, + const struct prefix *p); + +static inline struct bgp_aggregate * +bgp_dest_get_bgp_aggregate_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void +bgp_dest_set_bgp_aggregate_info(struct bgp_dest *dest, + struct bgp_aggregate *aggregate) +{ + dest->info = aggregate; +} + +static inline struct bgp_distance * +bgp_dest_get_bgp_distance_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void bgp_dest_set_bgp_distance_info(struct bgp_dest *dest, + struct bgp_distance *distance) +{ + dest->info = distance; +} + +static inline struct bgp_static * +bgp_dest_get_bgp_static_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void bgp_dest_set_bgp_static_info(struct bgp_dest *dest, + struct bgp_static *bgp_static) +{ + dest->info = bgp_static; +} + +static inline struct bgp_connected_ref * +bgp_dest_get_bgp_connected_ref_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void +bgp_dest_set_bgp_connected_ref_info(struct bgp_dest *dest, + struct bgp_connected_ref *bc) +{ + dest->info = bc; +} + +static inline struct bgp_nexthop_cache * +bgp_dest_get_bgp_nexthop_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void bgp_dest_set_bgp_nexthop_info(struct bgp_dest *dest, + struct bgp_nexthop_cache *bnc) +{ + dest->info = bnc; +} + +static inline struct bgp_path_info * +bgp_dest_get_bgp_path_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void bgp_dest_set_bgp_path_info(struct bgp_dest *dest, + struct bgp_path_info *bi) +{ + dest->info = bi; +} + +static inline struct bgp_table * +bgp_dest_get_bgp_table_info(struct bgp_dest *dest) +{ + return dest ? dest->info : NULL; +} + +static inline void bgp_dest_set_bgp_table_info(struct bgp_dest *dest, + struct bgp_table *table) +{ + dest->info = table; +} + +static inline bool bgp_dest_has_bgp_path_info_data(struct bgp_dest *dest) +{ + return dest ? !!dest->info : false; +} + +static inline const struct prefix *bgp_dest_get_prefix(const struct bgp_dest *dest) +{ + return dest ? &dest->rn->p : NULL; +} + +static inline unsigned int bgp_dest_get_lock_count(const struct bgp_dest *dest) +{ + return dest ? dest->rn->lock : 0; +} + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pRN" (struct bgp_node *) +#pragma FRR printfrr_ext "%pBD" (struct bgp_dest *) +#endif + +#endif /* _QUAGGA_BGP_TABLE_H */ diff --git a/bgpd/bgp_trace.c b/bgpd/bgp_trace.c new file mode 100644 index 0000000..af45e7a --- /dev/null +++ b/bgpd/bgp_trace.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define TRACEPOINT_CREATE_PROBES +#define TRACEPOINT_DEFINE + +#include + +#include "bgp_trace.h" diff --git a/bgpd/bgp_trace.h b/bgpd/bgp_trace.h new file mode 100644 index 0000000..a77a25e --- /dev/null +++ b/bgpd/bgp_trace.h @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Tracing for BGP + * + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ + +#if !defined(_BGP_TRACE_H) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _BGP_TRACE_H + +#include "lib/trace.h" + +#ifdef HAVE_LTTNG + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER frr_bgp + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "bgpd/bgp_trace.h" + +#include + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "lib/stream.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" + + +/* clang-format off */ + +TRACEPOINT_EVENT_CLASS( + frr_bgp, + packet_process, + TP_ARGS(struct peer *, peer, bgp_size_t, size), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ) +) + +#define PKT_PROCESS_TRACEPOINT_INSTANCE(name) \ + TRACEPOINT_EVENT_INSTANCE( \ + frr_bgp, packet_process, name, \ + TP_ARGS(struct peer *, peer, bgp_size_t, size)) \ + TRACEPOINT_LOGLEVEL(frr_bgp, name, TRACE_INFO) + +PKT_PROCESS_TRACEPOINT_INSTANCE(open_process) +PKT_PROCESS_TRACEPOINT_INSTANCE(keepalive_process) +PKT_PROCESS_TRACEPOINT_INSTANCE(update_process) +PKT_PROCESS_TRACEPOINT_INSTANCE(notification_process) +PKT_PROCESS_TRACEPOINT_INSTANCE(capability_process) +PKT_PROCESS_TRACEPOINT_INSTANCE(refresh_process) + +TRACEPOINT_EVENT( + frr_bgp, + packet_read, + TP_ARGS(struct peer_connection *, connection, struct stream *, pkt), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(connection->peer)) + ctf_sequence_hex(uint8_t, packet, pkt->data, size_t, + STREAM_READABLE(pkt)) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, packet_read, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + process_update, + TP_ARGS(struct peer *, peer, char *, pfx, uint32_t, addpath_id, afi_t, + afi, safi_t, safi, struct attr *, attr), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_string(prefix, pfx) + ctf_integer(uint32_t, addpath_id, addpath_id) + ctf_integer(afi_t, afi, afi) + ctf_integer(safi_t, safi, safi) + ctf_integer_hex(intptr_t, attribute_ptr, attr) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, process_update, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + input_filter, + TP_ARGS(struct peer *, peer, char *, pfx, afi_t, afi, safi_t, safi, + const char *, result), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_string(prefix, pfx) + ctf_integer(afi_t, afi, afi) + ctf_integer(safi_t, safi, safi) + ctf_string(action, result) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, input_filter, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + output_filter, + TP_ARGS(struct peer *, peer, char *, pfx, afi_t, afi, safi_t, safi, + const char *, result), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_string(prefix, pfx) + ctf_integer(afi_t, afi, afi) + ctf_integer(safi_t, safi, safi) + ctf_string(action, result) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, output_filter, TRACE_INFO) + +/* BMP tracepoints */ + +/* BMP mirrors a packet to all mirror-enabled targets */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_mirror_packet, + TP_ARGS(struct peer *, peer, uint8_t, type, struct stream *, pkt), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_integer(uint8_t, type, type) + ctf_sequence_hex(uint8_t, packet, pkt->data, size_t, + STREAM_READABLE(pkt)) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_mirror_packet, TRACE_INFO) + + +/* BMP sends an EOR */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_eor, + TP_ARGS(afi_t, afi, safi_t, safi, uint8_t, flags, uint8_t, peer_type_flag), + TP_FIELDS( + ctf_integer(afi_t, afi, afi) + ctf_integer(safi_t, safi, safi) + ctf_integer(uint8_t, flags, flags) + ctf_integer(uint8_t, peer_type_flag, peer_type_flag) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_eor, TRACE_INFO) + + +/* BMP updates its copy of the last OPEN a peer sent */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_update_saved_open, + TP_ARGS(struct peer *, peer, struct stream *, pkt), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_sequence_hex(uint8_t, packet, pkt->data, size_t, + STREAM_READABLE(pkt)) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_update_saved_open, TRACE_DEBUG) + + +/* BMP is notified of a peer status change internally */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_peer_status_changed, + TP_ARGS(struct peer *, peer), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_peer_status_changed, TRACE_DEBUG) + + +/* + * BMP is notified that a peer has transitioned in the opposite direction of + * Established internally + */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_peer_backward_transition, + TP_ARGS(struct peer *, peer), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_peer_backward, TRACE_DEBUG) + + +/* + * BMP is hooked for a route process + */ +TRACEPOINT_EVENT( + frr_bgp, + bmp_process, + TP_ARGS(struct peer *, peer, char *, pfx, afi_t, + afi, safi_t, safi, bool, withdraw), + TP_FIELDS( + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_string(prefix, pfx) + ctf_integer(afi_t, afi, afi) + ctf_integer(safi_t, safi, safi) + ctf_integer(bool, withdraw, withdraw) + ) +) + +TRACEPOINT_LOGLEVEL(frr_bgp, bmp_process, TRACE_DEBUG) + +/* + * bgp_dest_lock/bgp_dest_unlock + */ +TRACEPOINT_EVENT( + frr_bgp, + bgp_dest_lock, + TP_ARGS(struct bgp_dest *, dest), + TP_FIELDS( + ctf_string(prefix, bgp_dest_get_prefix_str(dest)) + ctf_integer(unsigned int, count, bgp_dest_get_lock_count(dest)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_dest_lock, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + bgp_dest_unlock, + TP_ARGS(struct bgp_dest *, dest), + TP_FIELDS( + ctf_string(prefix, bgp_dest_get_prefix_str(dest)) + ctf_integer(unsigned int, count, bgp_dest_get_lock_count(dest)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_dest_unlock, TRACE_INFO) + +/* + * peer_lock/peer_unlock + */ +TRACEPOINT_EVENT( + frr_bgp, + bgp_peer_lock, + TP_ARGS(struct peer *, peer, + const char *, name), + TP_FIELDS( + ctf_string(caller, name) + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_integer(unsigned int, count, peer->lock) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_peer_lock, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + bgp_peer_unlock, + TP_ARGS(struct peer *, peer, + const char *, name), + TP_FIELDS( + ctf_string(caller, name) + ctf_string(peer, PEER_HOSTNAME(peer)) + ctf_integer(unsigned int, count, peer->lock) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_peer_unlock, TRACE_INFO) + +/* + * bgp_path_info_add/bgp_path_info_free + */ +TRACEPOINT_EVENT( + frr_bgp, + bgp_path_info_add, + TP_ARGS(struct bgp_dest *, dest, + struct bgp_path_info *, bpi, + const char *, name), + TP_FIELDS( + ctf_string(caller, name) + ctf_string(prefix, bgp_dest_get_prefix_str(dest)) + ctf_string(peer, PEER_HOSTNAME(bpi->peer)) + ctf_integer(unsigned int, dest_lock, + bgp_dest_get_lock_count(dest)) + ctf_integer(unsigned int, peer_lock, bpi->peer->lock) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_path_info_add, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + bgp_path_info_free, + TP_ARGS(struct bgp_path_info *, bpi, + const char *, name), + TP_FIELDS( + ctf_string(caller, name) + ctf_string(prefix, bgp_dest_get_prefix_str(bpi->net)) + ctf_string(peer, PEER_HOSTNAME(bpi->peer)) + ctf_integer(unsigned int, dest_lock, + bgp_dest_get_lock_count(bpi->net)) + ctf_integer(unsigned int, peer_lock, bpi->peer->lock) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, bgp_path_info_free, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mac_ip_zsend, + TP_ARGS(int, add, struct bgpevpn *, vpn, + const struct prefix_evpn *, pfx, + struct in_addr, vtep, esi_t *, esi), + TP_FIELDS( + ctf_string(action, add ? "add" : "del") + ctf_integer(vni_t, vni, (vpn ? vpn->vni : 0)) + ctf_integer(uint32_t, eth_tag, &pfx->prefix.macip_addr.eth_tag) + ctf_array(unsigned char, mac, &pfx->prefix.macip_addr.mac, + sizeof(struct ethaddr)) + ctf_array(unsigned char, ip, &pfx->prefix.macip_addr.ip, + sizeof(struct ipaddr)) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mac_ip_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_bum_vtep_zsend, + TP_ARGS(int, add, struct bgpevpn *, vpn, + const struct prefix_evpn *, pfx), + TP_FIELDS( + ctf_string(action, add ? "add" : "del") + ctf_integer(vni_t, vni, (vpn ? vpn->vni : 0)) + ctf_integer_network_hex(unsigned int, vtep, + pfx->prefix.imet_addr.ip.ipaddr_v4.s_addr) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_bum_vtep_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_vtep_zsend, + TP_ARGS(bool, add, struct bgp_evpn_es *, es, + struct bgp_evpn_es_vtep *, es_vtep), + TP_FIELDS( + ctf_string(action, add ? "add" : "del") + ctf_string(esi, es->esi_str) + ctf_string(vtep, es_vtep->vtep_str) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_vtep_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_nhg_zsend, + TP_ARGS(bool, add, bool, type_v4, uint32_t, nhg_id, + struct bgp_evpn_es_vrf *, es_vrf), + TP_FIELDS( + ctf_string(action, add ? "add" : "del") + ctf_string(type, type_v4 ? "v4" : "v6") + ctf_integer(unsigned int, nhg, nhg_id) + ctf_string(esi, es_vrf->es->esi_str) + ctf_integer(int, vrf, es_vrf->bgp_vrf->vrf_id) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_nhg_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_nh_zsend, + TP_ARGS(uint32_t, nhg_id, struct bgp_evpn_es_vtep *, vtep, + struct bgp_evpn_es_vrf *, es_vrf), + TP_FIELDS( + ctf_integer(unsigned int, nhg, nhg_id) + ctf_string(vtep, vtep->vtep_str) + ctf_integer(int, svi, es_vrf->bgp_vrf->l3vni_svi_ifindex) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_nh_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_nh_rmac_zsend, + TP_ARGS(bool, add, struct bgp_evpn_nh *, nh), + TP_FIELDS( + ctf_string(action, add ? "add" : "del") + ctf_integer(int, vrf, nh->bgp_vrf->vrf_id) + ctf_string(nh, nh->nh_str) + ctf_array(unsigned char, rmac, &nh->rmac, + sizeof(struct ethaddr)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_nh_rmac_zsend, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_es_add_zrecv, + TP_ARGS(esi_t *, esi, struct in_addr, vtep, + uint8_t, active, uint8_t, bypass, uint16_t, df_pref), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_integer(uint8_t, active, active) + ctf_integer(uint8_t, bypass, bypass) + ctf_integer(uint16_t, df_pref, df_pref) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_es_add_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_es_del_zrecv, + TP_ARGS(esi_t *, esi), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_es_del_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_es_evi_add_zrecv, + TP_ARGS(esi_t *, esi, vni_t, vni), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_es_evi_add_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_es_evi_del_zrecv, + TP_ARGS(esi_t *, esi, vni_t, vni), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_es_evi_del_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_es_evi_vtep_add, + TP_ARGS(esi_t *, esi, vni_t, vni, struct in_addr, vtep, + uint8_t, ead_es), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_integer(uint8_t, ead_es, ead_es) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_es_evi_vtep_add, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_es_evi_vtep_del, + TP_ARGS(esi_t *, esi, vni_t, vni, struct in_addr, vtep, + uint8_t, ead_es), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_integer(uint8_t, ead_es, ead_es) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_es_evi_vtep_del, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_ead_es_evi_route_upd, + TP_ARGS(esi_t *, esi, vni_t, vni, + uint8_t, route_type, + struct in_addr, vtep), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ctf_integer(uint8_t, route_type, route_type) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_ead_es_evi_route_upd, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_mh_local_ead_es_evi_route_del, + TP_ARGS(esi_t *, esi, vni_t, vni, + uint8_t, route_type, + struct in_addr, vtep), + TP_FIELDS( + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ctf_integer(vni_t, vni, vni) + ctf_integer(uint8_t, route_type, route_type) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_mh_local_ead_es_evi_route_del, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_vni_add_zrecv, + TP_ARGS(vni_t, vni, struct in_addr, vtep, vrf_id_t, vrf, + struct in_addr, mc_grp), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_integer_network_hex(unsigned int, mc_grp, + mc_grp.s_addr) + ctf_integer(int, vrf, vrf) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_vni_add_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_vni_del_zrecv, + TP_ARGS(vni_t, vni), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_vni_del_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_macip_add_zrecv, + TP_ARGS(vni_t, vni, struct ethaddr *, mac, + struct ipaddr *, ip, uint32_t, flags, + uint32_t, seqnum, esi_t *, esi), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ctf_array(unsigned char, mac, mac, + sizeof(struct ethaddr)) + ctf_array(unsigned char, ip, ip, + sizeof(struct ipaddr)) + ctf_integer(uint32_t, flags, flags) + ctf_integer(uint32_t, seq, seqnum) + ctf_array(unsigned char, esi, esi, sizeof(esi_t)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_macip_add_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_macip_del_zrecv, + TP_ARGS(vni_t, vni, struct ethaddr *, mac, struct ipaddr *, ip, + int, state), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ctf_array(unsigned char, mac, mac, + sizeof(struct ethaddr)) + ctf_array(unsigned char, ip, ip, + sizeof(struct ipaddr)) + ctf_integer(int, state, state) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_macip_del_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_advertise_type5, + TP_ARGS(vrf_id_t, vrf, const struct prefix_evpn *, pfx, + struct ethaddr *, rmac, struct in_addr, vtep), + TP_FIELDS( + ctf_integer(int, vrf_id, vrf) + ctf_array(unsigned char, ip, &pfx->prefix.prefix_addr.ip, + sizeof(struct ipaddr)) + ctf_array(unsigned char, rmac, rmac, + sizeof(struct ethaddr)) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_advertise_type5, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_withdraw_type5, + TP_ARGS(vrf_id_t, vrf, const struct prefix_evpn *, pfx), + TP_FIELDS( + ctf_integer(int, vrf_id, vrf) + ctf_array(unsigned char, ip, &pfx->prefix.prefix_addr.ip, + sizeof(struct ipaddr)) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_withdraw_type5, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_l3vni_add_zrecv, + TP_ARGS(vni_t, vni, vrf_id_t, vrf, + struct ethaddr *, svi_rmac, + struct ethaddr *, vrr_rmac, int, filter, + struct in_addr, vtep, int, svi_ifindex, + bool, anycast_mac), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ctf_integer(int, vrf, vrf) + ctf_array(unsigned char, svi_rmac, svi_rmac, + sizeof(struct ethaddr)) + ctf_array(unsigned char, vrr_rmac, vrr_rmac, + sizeof(struct ethaddr)) + ctf_integer_network_hex(unsigned int, vtep, vtep.s_addr) + ctf_integer(int, filter, filter) + ctf_integer(int, svi_ifindex, svi_ifindex) + ctf_string(anycast_mac, anycast_mac ? "y" : "n") + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_l3vni_add_zrecv, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_bgp, + evpn_local_l3vni_del_zrecv, + TP_ARGS(vni_t, vni, vrf_id_t, vrf), + TP_FIELDS( + ctf_integer(vni_t, vni, vni) + ctf_integer(int, vrf, vrf) + ) +) +TRACEPOINT_LOGLEVEL(frr_bgp, evpn_local_l3vni_del_zrecv, TRACE_INFO) +/* clang-format on */ + +#include + +#endif /* HAVE_LTTNG */ + +#endif /* _BGP_TRACE_H */ diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c new file mode 100644 index 0000000..124e7a3 --- /dev/null +++ b/bgpd/bgp_updgrp.c @@ -0,0 +1,2246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_updgrp.c: BGP update group structures + * + * @copyright Copyright (C) 2014 Cumulus Networks, Inc. + * + * @author Avneesh Sachdev + * @author Rajesh Varadarajan + * @author Pradosh Mohapatra + */ + +#include + +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "command.h" +#include "sockunion.h" +#include "network.h" +#include "memory.h" +#include "filter.h" +#include "routemap.h" +#include "log.h" +#include "plist.h" +#include "linklist.h" +#include "workqueue.h" +#include "hash.h" +#include "jhash.h" +#include "queue.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_io.h" + +/******************** + * PRIVATE FUNCTIONS + ********************/ + +/** + * assign a unique ID to update group and subgroup. Mostly for display/ + * debugging purposes. It's a 64-bit space - used leisurely without a + * worry about its wrapping and about filling gaps. While at it, timestamp + * the creation. + */ +static void update_group_checkin(struct update_group *updgrp) +{ + updgrp->id = ++bm->updgrp_idspace; + updgrp->uptime = monotime(NULL); +} + +static void update_subgroup_checkin(struct update_subgroup *subgrp, + struct update_group *updgrp) +{ + subgrp->id = ++bm->subgrp_idspace; + subgrp->uptime = monotime(NULL); +} + +static void sync_init(struct update_subgroup *subgrp, + struct update_group *updgrp) +{ + struct peer *peer = UPDGRP_PEER(updgrp); + + subgrp->sync = + XCALLOC(MTYPE_BGP_SYNCHRONISE, sizeof(struct bgp_synchronize)); + bgp_adv_fifo_init(&subgrp->sync->update); + bgp_adv_fifo_init(&subgrp->sync->withdraw); + + subgrp->hash = + hash_create(bgp_advertise_attr_hash_key, + bgp_advertise_attr_hash_cmp, "BGP SubGroup Hash"); + + /* We use a larger buffer for subgrp->work in the event that: + * - We RX a BGP_UPDATE where the attributes alone are just + * under 4096 or 65535 (if Extended Message capability negotiated). + * - The user configures an outbound route-map that does many as-path + * prepends or adds many communities. At most they can have + * CMD_ARGC_MAX + * args in a route-map so there is a finite limit on how large they + * can + * make the attributes. + * + * Having a buffer with BGP_MAX_PACKET_SIZE_OVERFLOW allows us to avoid + * bounds + * checking for every single attribute as we construct an UPDATE. + */ + subgrp->work = stream_new(peer->max_packet_size + + BGP_MAX_PACKET_SIZE_OVERFLOW); + subgrp->scratch = stream_new(peer->max_packet_size); +} + +static void sync_delete(struct update_subgroup *subgrp) +{ + XFREE(MTYPE_BGP_SYNCHRONISE, subgrp->sync); + hash_clean_and_free(&subgrp->hash, + (void (*)(void *))bgp_advertise_attr_free); + + if (subgrp->work) + stream_free(subgrp->work); + subgrp->work = NULL; + if (subgrp->scratch) + stream_free(subgrp->scratch); + subgrp->scratch = NULL; +} + +/** + * conf_copy + * + * copy only those fields that are relevant to update group match + */ +static void conf_copy(struct peer *dst, struct peer *src, afi_t afi, + safi_t safi) +{ + struct bgp_filter *srcfilter; + struct bgp_filter *dstfilter; + + srcfilter = &src->filter[afi][safi]; + dstfilter = &dst->filter[afi][safi]; + + dst->bgp = src->bgp; + dst->sort = src->sort; + dst->sub_sort = src->sub_sort; + dst->as = src->as; + dst->v_routeadv = src->v_routeadv; + dst->flags = src->flags; + dst->af_flags[afi][safi] = src->af_flags[afi][safi]; + dst->pmax_out[afi][safi] = src->pmax_out[afi][safi]; + dst->max_packet_size = src->max_packet_size; + XFREE(MTYPE_BGP_PEER_HOST, dst->host); + + dst->host = XSTRDUP(MTYPE_BGP_PEER_HOST, src->host); + dst->cap = src->cap; + dst->af_cap[afi][safi] = src->af_cap[afi][safi]; + dst->afc_nego[afi][safi] = src->afc_nego[afi][safi]; + dst->orf_plist[afi][safi] = src->orf_plist[afi][safi]; + dst->addpath_type[afi][safi] = src->addpath_type[afi][safi]; + dst->addpath_best_selected[afi][safi] = + src->addpath_best_selected[afi][safi]; + dst->addpath_paths_limit[afi][safi] = + src->addpath_paths_limit[afi][safi]; + dst->local_as = src->local_as; + dst->change_local_as = src->change_local_as; + dst->shared_network = src->shared_network; + dst->local_role = src->local_role; + + if (src->soo[afi][safi]) { + ecommunity_free(&dst->soo[afi][safi]); + dst->soo[afi][safi] = ecommunity_dup(src->soo[afi][safi]); + } + + memcpy(&(dst->nexthop), &(src->nexthop), sizeof(struct bgp_nexthop)); + + dst->group = src->group; + + if (src->default_rmap[afi][safi].name) { + dst->default_rmap[afi][safi].name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, + src->default_rmap[afi][safi].name); + dst->default_rmap[afi][safi].map = + src->default_rmap[afi][safi].map; + } + + if (DISTRIBUTE_OUT_NAME(srcfilter)) { + DISTRIBUTE_OUT_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, DISTRIBUTE_OUT_NAME(srcfilter)); + DISTRIBUTE_OUT(dstfilter) = DISTRIBUTE_OUT(srcfilter); + } + + if (PREFIX_LIST_OUT_NAME(srcfilter)) { + PREFIX_LIST_OUT_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, PREFIX_LIST_OUT_NAME(srcfilter)); + PREFIX_LIST_OUT(dstfilter) = PREFIX_LIST_OUT(srcfilter); + } + + if (FILTER_LIST_OUT_NAME(srcfilter)) { + FILTER_LIST_OUT_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, FILTER_LIST_OUT_NAME(srcfilter)); + FILTER_LIST_OUT(dstfilter) = FILTER_LIST_OUT(srcfilter); + } + + if (ROUTE_MAP_OUT_NAME(srcfilter)) { + ROUTE_MAP_OUT_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, ROUTE_MAP_OUT_NAME(srcfilter)); + ROUTE_MAP_OUT(dstfilter) = ROUTE_MAP_OUT(srcfilter); + } + + if (UNSUPPRESS_MAP_NAME(srcfilter)) { + UNSUPPRESS_MAP_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, UNSUPPRESS_MAP_NAME(srcfilter)); + UNSUPPRESS_MAP(dstfilter) = UNSUPPRESS_MAP(srcfilter); + } + + if (ADVERTISE_MAP_NAME(srcfilter)) { + ADVERTISE_MAP_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, ADVERTISE_MAP_NAME(srcfilter)); + ADVERTISE_MAP(dstfilter) = ADVERTISE_MAP(srcfilter); + ADVERTISE_CONDITION(dstfilter) = ADVERTISE_CONDITION(srcfilter); + } + + if (CONDITION_MAP_NAME(srcfilter)) { + CONDITION_MAP_NAME(dstfilter) = XSTRDUP( + MTYPE_BGP_FILTER_NAME, CONDITION_MAP_NAME(srcfilter)); + CONDITION_MAP(dstfilter) = CONDITION_MAP(srcfilter); + } + + dstfilter->advmap.update_type = srcfilter->advmap.update_type; +} + +/** + * since we did a bunch of XSTRDUP's in conf_copy, time to free them up + */ +static void conf_release(struct peer *src, afi_t afi, safi_t safi) +{ + struct bgp_filter *srcfilter; + + srcfilter = &src->filter[afi][safi]; + + XFREE(MTYPE_ROUTE_MAP_NAME, src->default_rmap[afi][safi].name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->dlist[FILTER_OUT].name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->plist[FILTER_OUT].name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->aslist[FILTER_OUT].name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->map[RMAP_OUT].name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->usmap.name); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->advmap.aname); + + XFREE(MTYPE_BGP_FILTER_NAME, srcfilter->advmap.cname); + + XFREE(MTYPE_BGP_PEER_HOST, src->host); + + ecommunity_free(&src->soo[afi][safi]); +} + +static void peer2_updgrp_copy(struct update_group *updgrp, struct peer_af *paf) +{ + struct peer *src; + struct peer *dst; + + if (!updgrp || !paf) + return; + + src = paf->peer; + dst = updgrp->conf; + if (!src || !dst) + return; + + updgrp->afi = paf->afi; + updgrp->safi = paf->safi; + updgrp->afid = paf->afid; + updgrp->bgp = src->bgp; + + conf_copy(dst, src, paf->afi, paf->safi); +} + +/** + * auxiliary functions to maintain the hash table. + * - updgrp_hash_alloc - to create a new entry, passed to hash_get + * - updgrp_hash_key_make - makes the key for update group search + * - updgrp_hash_cmp - compare two update groups. + */ +static void *updgrp_hash_alloc(void *p) +{ + struct update_group *updgrp; + const struct update_group *in; + + in = (const struct update_group *)p; + updgrp = XCALLOC(MTYPE_BGP_UPDGRP, sizeof(struct update_group)); + memcpy(updgrp, in, sizeof(struct update_group)); + updgrp->conf = XCALLOC(MTYPE_BGP_PEER, sizeof(struct peer)); + updgrp->conf->connection = XCALLOC(MTYPE_BGP_PEER_CONNECTION, + sizeof(struct peer_connection)); + conf_copy(updgrp->conf, in->conf, in->afi, in->safi); + return updgrp; +} + +/** + * The hash value for a peer is computed from the following variables: + * v = f( + * 1. IBGP (1) or EBGP (2) + * 2. FLAGS based on configuration: + * LOCAL_AS_NO_PREPEND + * LOCAL_AS_REPLACE_AS + * 3. AF_FLAGS based on configuration: + * Refer to definition in bgp_updgrp.h + * 4. (AF-independent) Capability flags: + * AS4_RCV capability + * 5. (AF-dependent) Capability flags: + * ORF_PREFIX_SM_RCV (peer can send prefix ORF) + * 6. MRAI + * 7. peer-group name + * 8. Outbound route-map name (neighbor route-map <> out) + * 9. Outbound distribute-list name (neighbor distribute-list <> out) + * 10. Outbound prefix-list name (neighbor prefix-list <> out) + * 11. Outbound as-list name (neighbor filter-list <> out) + * 12. Unsuppress map name (neighbor unsuppress-map <>) + * 13. default rmap name (neighbor default-originate route-map <>) + * 14. encoding both global and link-local nexthop? + * 15. If peer is configured to be a lonesoul, peer ip address + * 16. Local-as should match, if configured. + * 17. maximum-prefix-out + * 18. Local-role should also match, if configured. + * 19. Add-Path best selected paths count should match as well + * ) + */ +static unsigned int updgrp_hash_key_make(const void *p) +{ + const struct update_group *updgrp; + const struct peer *peer; + const struct bgp_filter *filter; + uint64_t flags; + uint32_t key; + afi_t afi; + safi_t safi; + + /* + * IF YOU ADD AN ADDITION TO THE HASH KEY TO ENSURE + * THAT THE UPDATE GROUP CALCULATION IS CORRECT THEN + * PLEASE ADD IT TO THE DEBUG OUTPUT TOO AT THE BOTTOM + */ +#define SEED1 999331 +#define SEED2 2147483647 + + updgrp = p; + peer = updgrp->conf; + afi = updgrp->afi; + safi = updgrp->safi; + flags = peer->af_flags[afi][safi]; + filter = &peer->filter[afi][safi]; + + key = 0; + + key = jhash_1word(peer->sort, key); /* EBGP or IBGP */ + key = jhash_1word(peer->sub_sort, key); /* OAD */ + key = jhash_1word((peer->flags & PEER_UPDGRP_FLAGS), key); + key = jhash_1word((flags & PEER_UPDGRP_AF_FLAGS), key); + key = jhash_1word((uint32_t)peer->addpath_type[afi][safi], key); + key = jhash_1word(peer->addpath_best_selected[afi][safi], key); + key = jhash_1word(peer->addpath_paths_limit[afi][safi].receive, key); + key = jhash_1word(peer->addpath_paths_limit[afi][safi].send, key); + key = jhash_1word((peer->cap & PEER_UPDGRP_CAP_FLAGS), key); + key = jhash_1word((peer->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS), + key); + key = jhash_1word(peer->v_routeadv, key); + key = jhash_1word(peer->change_local_as, key); + key = jhash_1word(peer->max_packet_size, key); + key = jhash_1word(peer->pmax_out[afi][safi], key); + + + if (CHECK_FLAG(peer->flags, PEER_FLAG_AS_LOOP_DETECTION)) + key = jhash_2words(peer->as, + CHECK_FLAG(peer->flags, + PEER_FLAG_AS_LOOP_DETECTION), + key); + if (peer->group) + key = jhash_1word(jhash(peer->group->name, + strlen(peer->group->name), SEED1), + key); + + if (filter->map[RMAP_OUT].name) + key = jhash_1word(jhash(filter->map[RMAP_OUT].name, + strlen(filter->map[RMAP_OUT].name), + SEED1), + key); + + if (filter->dlist[FILTER_OUT].name) + key = jhash_1word(jhash(filter->dlist[FILTER_OUT].name, + strlen(filter->dlist[FILTER_OUT].name), + SEED1), + key); + + if (filter->plist[FILTER_OUT].name) + key = jhash_1word(jhash(filter->plist[FILTER_OUT].name, + strlen(filter->plist[FILTER_OUT].name), + SEED1), + key); + + if (filter->aslist[FILTER_OUT].name) + key = jhash_1word(jhash(filter->aslist[FILTER_OUT].name, + strlen(filter->aslist[FILTER_OUT].name), + SEED1), + key); + + if (filter->usmap.name) + key = jhash_1word(jhash(filter->usmap.name, + strlen(filter->usmap.name), SEED1), + key); + + if (filter->advmap.aname) + key = jhash_1word(jhash(filter->advmap.aname, + strlen(filter->advmap.aname), SEED1), + key); + + if (filter->advmap.update_type) + key = jhash_1word(filter->advmap.update_type, key); + + if (peer->default_rmap[afi][safi].name) + key = jhash_1word( + jhash(peer->default_rmap[afi][safi].name, + strlen(peer->default_rmap[afi][safi].name), + SEED1), + key); + + /* If peer is on a shared network and is exchanging IPv6 prefixes, + * it needs to include link-local address. That's different from + * non-shared-network peers (nexthop encoded with 32 bytes vs 16 + * bytes). We create different update groups to take care of that. + */ + key = jhash_1word( + (peer->shared_network && peer_afi_active_nego(peer, AFI_IP6)), + key); + /* + * There are certain peers that must get their own update-group: + * - lonesoul peers + * - peers that negotiated ORF + * - maximum-prefix-out is set + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL) + || CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV) + || CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT)) + key = jhash_1word(jhash(peer->host, strlen(peer->host), SEED2), + key); + /* + * Multiple sessions with the same neighbor should get their own + * update-group if they have different roles. + */ + key = jhash_1word(peer->local_role, key); + + /* Neighbors configured with the AIGP attribute are put in a separate + * update group from other neighbors. + */ + key = jhash_1word((peer->flags & PEER_FLAG_AIGP), key); + + if (peer->soo[afi][safi]) { + const char *soo_str = ecommunity_str(peer->soo[afi][safi]); + + key = jhash_1word(jhash(soo_str, strlen(soo_str), SEED1), key); + } + + /* + * ANY NEW ITEMS THAT ARE ADDED TO THE key, ENSURE DEBUG + * STATEMENT STAYS UP TO DATE + */ + if (bgp_debug_neighbor_events(peer)) { + zlog_debug("%pBP Update Group Hash: sort: %d sub_sort: %d UpdGrpFlags: %ju UpdGrpAFFlags: %ju", + peer, peer->sort, peer->sub_sort, + (intmax_t)CHECK_FLAG(peer->flags, PEER_UPDGRP_FLAGS), + (intmax_t)CHECK_FLAG(flags, PEER_UPDGRP_AF_FLAGS)); + zlog_debug("%pBP Update Group Hash: addpath: %u UpdGrpCapFlag: %ju UpdGrpCapAFFlag: %u route_adv: %u change local as: %u, as_path_loop_detection: %d", + peer, (uint32_t)peer->addpath_type[afi][safi], + (intmax_t)CHECK_FLAG(peer->cap, + PEER_UPDGRP_CAP_FLAGS), + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_UPDGRP_AF_CAP_FLAGS), + peer->v_routeadv, peer->change_local_as, + !!CHECK_FLAG(peer->flags, + PEER_FLAG_AS_LOOP_DETECTION)); + zlog_debug("%pBP Update Group Hash: addpath paths-limit: (send %u, receive %u)", + peer, peer->addpath_paths_limit[afi][safi].send, + peer->addpath_paths_limit[afi][safi].receive); + zlog_debug( + "%pBP Update Group Hash: max packet size: %u pmax_out: %u Peer Group: %s rmap out: %s", + peer, peer->max_packet_size, peer->pmax_out[afi][safi], + peer->group ? peer->group->name : "(NONE)", + ROUTE_MAP_OUT_NAME(filter) ? ROUTE_MAP_OUT_NAME(filter) + : "(NONE)"); + zlog_debug( + "%pBP Update Group Hash: dlist out: %s plist out: %s aslist out: %s usmap out: %s advmap: %s %d", + peer, + DISTRIBUTE_OUT_NAME(filter) + ? DISTRIBUTE_OUT_NAME(filter) + : "(NONE)", + PREFIX_LIST_OUT_NAME(filter) + ? PREFIX_LIST_OUT_NAME(filter) + : "(NONE)", + FILTER_LIST_OUT_NAME(filter) + ? FILTER_LIST_OUT_NAME(filter) + : "(NONE)", + UNSUPPRESS_MAP_NAME(filter) + ? UNSUPPRESS_MAP_NAME(filter) + : "(NONE)", + ADVERTISE_MAP_NAME(filter) ? ADVERTISE_MAP_NAME(filter) + : "(NONE)", + filter->advmap.update_type); + zlog_debug( + "%pBP Update Group Hash: default rmap: %s shared network and afi active network: %d", + peer, + peer->default_rmap[afi][safi].name + ? peer->default_rmap[afi][safi].name + : "(NONE)", + peer->shared_network && + peer_afi_active_nego(peer, AFI_IP6)); + zlog_debug("%pBP Update Group Hash: Lonesoul: %d ORF prefix: %u max prefix out: %ju", + peer, !!CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL), + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV), + (intmax_t)CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_OUT)); + zlog_debug( + "%pBP Update Group Hash: local role: %u AIGP: %d SOO: %s", + peer, peer->local_role, + !!CHECK_FLAG(peer->flags, PEER_FLAG_AIGP), + peer->soo[afi][safi] + ? ecommunity_str(peer->soo[afi][safi]) + : "(NONE)"); + zlog_debug("%pBP Update Group Hash key: %u", peer, key); + } + return key; +} + +static bool updgrp_hash_cmp(const void *p1, const void *p2) +{ + const struct update_group *grp1; + const struct update_group *grp2; + const struct peer *pe1; + const struct peer *pe2; + uint64_t flags1; + uint64_t flags2; + const struct bgp_filter *fl1; + const struct bgp_filter *fl2; + afi_t afi; + safi_t safi; + + if (!p1 || !p2) + return false; + + grp1 = p1; + grp2 = p2; + pe1 = grp1->conf; + pe2 = grp2->conf; + afi = grp1->afi; + safi = grp1->safi; + flags1 = pe1->af_flags[afi][safi]; + flags2 = pe2->af_flags[afi][safi]; + fl1 = &pe1->filter[afi][safi]; + fl2 = &pe2->filter[afi][safi]; + + /* put EBGP and IBGP peers in different update groups */ + if (pe1->sort != pe2->sort) + return false; + + /* check peer flags */ + if ((pe1->flags & PEER_UPDGRP_FLAGS) + != (pe2->flags & PEER_UPDGRP_FLAGS)) + return false; + + /* If there is 'local-as' configured, it should match. */ + if (pe1->change_local_as != pe2->change_local_as) + return false; + + if (pe1->pmax_out[afi][safi] != pe2->pmax_out[afi][safi]) + return false; + + /* flags like route reflector client */ + if ((flags1 & PEER_UPDGRP_AF_FLAGS) != (flags2 & PEER_UPDGRP_AF_FLAGS)) + return false; + + if (pe1->addpath_type[afi][safi] != pe2->addpath_type[afi][safi]) + return false; + + if ((pe1->cap & PEER_UPDGRP_CAP_FLAGS) + != (pe2->cap & PEER_UPDGRP_CAP_FLAGS)) + return false; + + if ((pe1->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS) + != (pe2->af_cap[afi][safi] & PEER_UPDGRP_AF_CAP_FLAGS)) + return false; + + if (pe1->v_routeadv != pe2->v_routeadv) + return false; + + if (pe1->group != pe2->group) + return false; + + /* Roles can affect filtering */ + if (pe1->local_role != pe2->local_role) + return false; + + /* route-map names should be the same */ + if ((fl1->map[RMAP_OUT].name && !fl2->map[RMAP_OUT].name) + || (!fl1->map[RMAP_OUT].name && fl2->map[RMAP_OUT].name) + || (fl1->map[RMAP_OUT].name && fl2->map[RMAP_OUT].name + && strcmp(fl1->map[RMAP_OUT].name, fl2->map[RMAP_OUT].name))) + return false; + + if ((fl1->dlist[FILTER_OUT].name && !fl2->dlist[FILTER_OUT].name) + || (!fl1->dlist[FILTER_OUT].name && fl2->dlist[FILTER_OUT].name) + || (fl1->dlist[FILTER_OUT].name && fl2->dlist[FILTER_OUT].name + && strcmp(fl1->dlist[FILTER_OUT].name, + fl2->dlist[FILTER_OUT].name))) + return false; + + if ((fl1->plist[FILTER_OUT].name && !fl2->plist[FILTER_OUT].name) + || (!fl1->plist[FILTER_OUT].name && fl2->plist[FILTER_OUT].name) + || (fl1->plist[FILTER_OUT].name && fl2->plist[FILTER_OUT].name + && strcmp(fl1->plist[FILTER_OUT].name, + fl2->plist[FILTER_OUT].name))) + return false; + + if ((fl1->aslist[FILTER_OUT].name && !fl2->aslist[FILTER_OUT].name) + || (!fl1->aslist[FILTER_OUT].name && fl2->aslist[FILTER_OUT].name) + || (fl1->aslist[FILTER_OUT].name && fl2->aslist[FILTER_OUT].name + && strcmp(fl1->aslist[FILTER_OUT].name, + fl2->aslist[FILTER_OUT].name))) + return false; + + if ((fl1->usmap.name && !fl2->usmap.name) + || (!fl1->usmap.name && fl2->usmap.name) + || (fl1->usmap.name && fl2->usmap.name + && strcmp(fl1->usmap.name, fl2->usmap.name))) + return false; + + if ((fl1->advmap.aname && !fl2->advmap.aname) + || (!fl1->advmap.aname && fl2->advmap.aname) + || (fl1->advmap.aname && fl2->advmap.aname + && strcmp(fl1->advmap.aname, fl2->advmap.aname))) + return false; + + if (fl1->advmap.update_type != fl2->advmap.update_type) + return false; + + if ((pe1->default_rmap[afi][safi].name + && !pe2->default_rmap[afi][safi].name) + || (!pe1->default_rmap[afi][safi].name + && pe2->default_rmap[afi][safi].name) + || (pe1->default_rmap[afi][safi].name + && pe2->default_rmap[afi][safi].name + && strcmp(pe1->default_rmap[afi][safi].name, + pe2->default_rmap[afi][safi].name))) + return false; + + if ((afi == AFI_IP6) && (pe1->shared_network != pe2->shared_network)) + return false; + + if ((CHECK_FLAG(pe1->flags, PEER_FLAG_LONESOUL) || + CHECK_FLAG(pe1->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_RCV)) && + !sockunion_same(&pe1->connection->su, &pe2->connection->su)) + return false; + + return true; +} + +static void peer_lonesoul_or_not(struct peer *peer, int set) +{ + /* no change in status? */ + if (set == (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL) > 0)) + return; + + if (set) + SET_FLAG(peer->flags, PEER_FLAG_LONESOUL); + else + UNSET_FLAG(peer->flags, PEER_FLAG_LONESOUL); + + update_group_adjust_peer_afs(peer); +} + +/* + * subgroup_total_packets_enqueued + * + * Returns the total number of packets enqueued to a subgroup. + */ +static unsigned int +subgroup_total_packets_enqueued(struct update_subgroup *subgrp) +{ + struct bpacket *pkt; + + pkt = bpacket_queue_last(SUBGRP_PKTQ(subgrp)); + + return pkt->ver - 1; +} + +static int update_group_show_walkcb(struct update_group *updgrp, void *arg) +{ + struct updwalk_context *ctx = arg; + struct vty *vty; + struct update_subgroup *subgrp; + struct peer_af *paf; + struct bgp_filter *filter; + struct peer *peer = UPDGRP_PEER(updgrp); + int match = 0; + json_object *json_updgrp = NULL; + json_object *json_subgrps = NULL; + json_object *json_subgrp = NULL; + json_object *json_time = NULL; + json_object *json_subgrp_time = NULL; + json_object *json_subgrp_event = NULL; + json_object *json_peers = NULL; + json_object *json_pkt_info = NULL; + time_t epoch_tbuf, tbuf; + char timebuf[32]; + + if (!ctx) + return CMD_SUCCESS; + + if (ctx->subgrp_id) { + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) + continue; + else { + match = 1; + break; + } + } + } else { + match = 1; + } + + if (!match) { + /* Since this routine is invoked from a walk, we cannot signal + * any */ + /* error here, can only return. */ + return CMD_SUCCESS; + } + + vty = ctx->vty; + + if (ctx->uj) { + json_updgrp = json_object_new_object(); + /* Display json o/p */ + tbuf = monotime(NULL); + tbuf -= updgrp->uptime; + epoch_tbuf = time(NULL) - tbuf; + json_time = json_object_new_object(); + json_object_int_add(json_time, "epoch", epoch_tbuf); + json_object_string_add(json_time, "epochString", + ctime_r(&epoch_tbuf, timebuf)); + json_object_object_add(json_updgrp, "groupCreateTime", + json_time); + json_object_string_add(json_updgrp, "afi", + afi2str(updgrp->afi)); + json_object_string_add(json_updgrp, "safi", + safi2str(updgrp->safi)); + } else { + vty_out(vty, "Update-group %" PRIu64 ":\n", updgrp->id); + vty_out(vty, " Created: %s", + timestamp_string(updgrp->uptime, timebuf)); + } + + filter = &updgrp->conf->filter[updgrp->afi][updgrp->safi]; + if (filter->map[RMAP_OUT].name) { + if (ctx->uj) + json_object_string_add(json_updgrp, "outRouteMap", + filter->map[RMAP_OUT].name); + else + vty_out(vty, " Outgoing route map: %s\n", + filter->map[RMAP_OUT].name); + } + + if (ctx->uj) + json_object_int_add(json_updgrp, "minRouteAdvInt", + updgrp->conf->v_routeadv); + else + vty_out(vty, " MRAI value (seconds): %d\n", + updgrp->conf->v_routeadv); + + if (updgrp->conf->change_local_as) { + if (ctx->uj) { + json_object_int_add(json_updgrp, "localAs", + updgrp->conf->change_local_as); + json_object_boolean_add( + json_updgrp, "noPrepend", + CHECK_FLAG(updgrp->conf->flags, + PEER_FLAG_LOCAL_AS_NO_PREPEND)); + json_object_boolean_add( + json_updgrp, "replaceLocalAs", + CHECK_FLAG(updgrp->conf->flags, + PEER_FLAG_LOCAL_AS_REPLACE_AS)); + } else { + vty_out(vty, " Local AS %u%s%s\n", + updgrp->conf->change_local_as, + CHECK_FLAG(updgrp->conf->flags, + PEER_FLAG_LOCAL_AS_NO_PREPEND) + ? " no-prepend" + : "", + CHECK_FLAG(updgrp->conf->flags, + PEER_FLAG_LOCAL_AS_REPLACE_AS) + ? " replace-as" + : ""); + } + } + if (ctx->uj) + json_subgrps = json_object_new_array(); + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) + continue; + if (ctx->uj) { + json_subgrp = json_object_new_object(); + json_object_int_add(json_subgrp, "subGroupId", + subgrp->id); + tbuf = monotime(NULL); + tbuf -= subgrp->uptime; + epoch_tbuf = time(NULL) - tbuf; + json_subgrp_time = json_object_new_object(); + json_object_int_add(json_subgrp_time, "epoch", + epoch_tbuf); + json_object_string_add(json_subgrp_time, "epochString", + ctime_r(&epoch_tbuf, timebuf)); + json_object_object_add(json_subgrp, "groupCreateTime", + json_subgrp_time); + } else { + vty_out(vty, "\n"); + vty_out(vty, " Update-subgroup %" PRIu64 ":\n", + subgrp->id); + vty_out(vty, " Created: %s", + timestamp_string(subgrp->uptime, timebuf)); + } + + if (subgrp->split_from.update_group_id + || subgrp->split_from.subgroup_id) { + if (ctx->uj) { + json_object_int_add( + json_subgrp, "splitGroupId", + subgrp->split_from.update_group_id); + json_object_int_add( + json_subgrp, "splitSubGroupId", + subgrp->split_from.subgroup_id); + } else { + vty_out(vty, + " Split from group id: %" PRIu64 + "\n", + subgrp->split_from.update_group_id); + vty_out(vty, + " Split from subgroup id: %" PRIu64 + "\n", + subgrp->split_from.subgroup_id); + } + } + + if (ctx->uj) { + json_subgrp_event = json_object_new_object(); + json_object_int_add(json_subgrp_event, "joinEvents", + subgrp->join_events); + json_object_int_add(json_subgrp_event, "pruneEvents", + subgrp->prune_events); + json_object_int_add(json_subgrp_event, "mergeEvents", + subgrp->merge_events); + json_object_int_add(json_subgrp_event, "splitEvents", + subgrp->split_events); + json_object_int_add(json_subgrp_event, "switchEvents", + subgrp->updgrp_switch_events); + json_object_int_add(json_subgrp_event, + "peerRefreshEvents", + subgrp->peer_refreshes_combined); + json_object_int_add(json_subgrp_event, + "mergeCheckEvents", + subgrp->merge_checks_triggered); + json_object_object_add(json_subgrp, "statistics", + json_subgrp_event); + json_object_int_add(json_subgrp, "coalesceTime", + (UPDGRP_INST(subgrp->update_group)) + ->coalesce_time); + json_object_int_add(json_subgrp, "version", + subgrp->version); + json_pkt_info = json_object_new_object(); + json_object_int_add( + json_pkt_info, "qeueueLen", + bpacket_queue_length(SUBGRP_PKTQ(subgrp))); + json_object_int_add( + json_pkt_info, "queuedTotal", + subgroup_total_packets_enqueued(subgrp)); + json_object_int_add( + json_pkt_info, "queueHwmLen", + bpacket_queue_hwm_length(SUBGRP_PKTQ(subgrp))); + json_object_int_add( + json_pkt_info, "totalEnqueued", + subgroup_total_packets_enqueued(subgrp)); + json_object_object_add(json_subgrp, "packetQueueInfo", + json_pkt_info); + json_object_int_add(json_subgrp, "adjListCount", + subgrp->adj_count); + json_object_boolean_add( + json_subgrp, "needsRefresh", + CHECK_FLAG(subgrp->flags, + SUBGRP_FLAG_NEEDS_REFRESH)); + } else { + vty_out(vty, " Join events: %u\n", + subgrp->join_events); + vty_out(vty, " Prune events: %u\n", + subgrp->prune_events); + vty_out(vty, " Merge events: %u\n", + subgrp->merge_events); + vty_out(vty, " Split events: %u\n", + subgrp->split_events); + vty_out(vty, " Update group switch events: %u\n", + subgrp->updgrp_switch_events); + vty_out(vty, " Peer refreshes combined: %u\n", + subgrp->peer_refreshes_combined); + vty_out(vty, " Merge checks triggered: %u\n", + subgrp->merge_checks_triggered); + vty_out(vty, " Coalesce Time: %u%s\n", + (UPDGRP_INST(subgrp->update_group)) + ->coalesce_time, + subgrp->t_coalesce ? "(Running)" : ""); + vty_out(vty, " Version: %" PRIu64 "\n", + subgrp->version); + vty_out(vty, " Packet queue length: %d\n", + bpacket_queue_length(SUBGRP_PKTQ(subgrp))); + vty_out(vty, " Total packets enqueued: %u\n", + subgroup_total_packets_enqueued(subgrp)); + vty_out(vty, " Packet queue high watermark: %d\n", + bpacket_queue_hwm_length(SUBGRP_PKTQ(subgrp))); + vty_out(vty, " Adj-out list count: %u\n", + subgrp->adj_count); + vty_out(vty, " Advertise list: %s\n", + advertise_list_is_empty(subgrp) ? "empty" + : "not empty"); + vty_out(vty, " Flags: %s\n", + CHECK_FLAG(subgrp->flags, + SUBGRP_FLAG_NEEDS_REFRESH) + ? "R" + : ""); + if (peer) + vty_out(vty, " Max packet size: %d\n", + peer->max_packet_size); + } + if (subgrp->peer_count > 0) { + if (ctx->uj) { + json_peers = json_object_new_array(); + SUBGRP_FOREACH_PEER (subgrp, paf) { + json_object *peer = + json_object_new_string( + paf->peer->host); + json_object_array_add(json_peers, peer); + } + json_object_object_add(json_subgrp, "peers", + json_peers); + } else { + vty_out(vty, " Peers:\n"); + SUBGRP_FOREACH_PEER (subgrp, paf) + vty_out(vty, " - %s\n", + paf->peer->host); + } + } + + if (ctx->uj) + json_object_array_add(json_subgrps, json_subgrp); + } + + if (ctx->uj) { + json_object_object_add(json_updgrp, "subGroup", json_subgrps); + json_object_object_addf(ctx->json_updategrps, json_updgrp, + "%" PRIu64, updgrp->id); + } + + return UPDWALK_CONTINUE; +} + +/* + * Helper function to show the packet queue for each subgroup of update group. + * Will be constrained to a particular subgroup id if id !=0 + */ +static int updgrp_show_packet_queue_walkcb(struct update_group *updgrp, + void *arg) +{ + struct updwalk_context *ctx = arg; + struct update_subgroup *subgrp; + struct vty *vty; + + vty = ctx->vty; + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) + continue; + vty_out(vty, "update group %" PRIu64 ", subgroup %" PRIu64 "\n", + updgrp->id, subgrp->id); + bpacket_queue_show_vty(SUBGRP_PKTQ(subgrp), vty); + } + return UPDWALK_CONTINUE; +} + +/* + * Show the packet queue for each subgroup of update group. Will be + * constrained to a particular subgroup id if id !=0 + */ +void update_group_show_packet_queue(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t id) +{ + struct updwalk_context ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.vty = vty; + ctx.subgrp_id = id; + ctx.flags = 0; + update_group_af_walk(bgp, afi, safi, updgrp_show_packet_queue_walkcb, + &ctx); +} + +static struct update_group *update_group_find(struct peer_af *paf) +{ + struct update_group *updgrp; + struct update_group tmp; + struct peer tmp_conf; + struct peer_connection tmp_connection; + + if (!peer_established((PAF_PEER(paf))->connection)) + return NULL; + + memset(&tmp, 0, sizeof(tmp)); + memset(&tmp_conf, 0, sizeof(tmp_conf)); + memset(&tmp_connection, 0, sizeof(struct peer_connection)); + + tmp.conf = &tmp_conf; + tmp_conf.connection = &tmp_connection; + + peer2_updgrp_copy(&tmp, paf); + + updgrp = hash_lookup(paf->peer->bgp->update_groups[paf->afid], &tmp); + conf_release(&tmp_conf, paf->afi, paf->safi); + return updgrp; +} + +static struct update_group *update_group_create(struct peer_af *paf) +{ + struct update_group *updgrp; + struct update_group tmp; + struct peer tmp_conf; + struct peer_connection tmp_connection; + + memset(&tmp, 0, sizeof(tmp)); + memset(&tmp_conf, 0, sizeof(tmp_conf)); + memset(&tmp_connection, 0, sizeof(tmp_connection)); + + tmp.conf = &tmp_conf; + tmp_conf.connection = &tmp_connection; + peer2_updgrp_copy(&tmp, paf); + + updgrp = hash_get(paf->peer->bgp->update_groups[paf->afid], &tmp, + updgrp_hash_alloc); + update_group_checkin(updgrp); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("create update group %" PRIu64, updgrp->id); + + UPDGRP_GLOBAL_STAT(updgrp, updgrps_created) += 1; + + conf_release(&tmp_conf, paf->afi, paf->safi); + return updgrp; +} + +static void update_group_delete(struct update_group *updgrp) +{ + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("delete update group %" PRIu64, updgrp->id); + + UPDGRP_GLOBAL_STAT(updgrp, updgrps_deleted) += 1; + + hash_release(updgrp->bgp->update_groups[updgrp->afid], updgrp); + conf_release(updgrp->conf, updgrp->afi, updgrp->safi); + + XFREE(MTYPE_BGP_PEER_HOST, updgrp->conf->host); + + XFREE(MTYPE_BGP_PEER_IFNAME, updgrp->conf->ifname); + + XFREE(MTYPE_BGP_PEER_CONNECTION, updgrp->conf->connection); + XFREE(MTYPE_BGP_PEER, updgrp->conf); + XFREE(MTYPE_BGP_UPDGRP, updgrp); +} + +static void update_group_add_subgroup(struct update_group *updgrp, + struct update_subgroup *subgrp) +{ + if (!updgrp || !subgrp) + return; + + LIST_INSERT_HEAD(&(updgrp->subgrps), subgrp, updgrp_train); + subgrp->update_group = updgrp; +} + +static void update_group_remove_subgroup(struct update_group *updgrp, + struct update_subgroup *subgrp) +{ + if (!updgrp || !subgrp) + return; + + LIST_REMOVE(subgrp, updgrp_train); + subgrp->update_group = NULL; + if (LIST_EMPTY(&(updgrp->subgrps))) + update_group_delete(updgrp); +} + +static struct update_subgroup * +update_subgroup_create(struct update_group *updgrp) +{ + struct update_subgroup *subgrp; + + subgrp = XCALLOC(MTYPE_BGP_UPD_SUBGRP, sizeof(struct update_subgroup)); + update_subgroup_checkin(subgrp, updgrp); + subgrp->v_coalesce = (UPDGRP_INST(updgrp))->coalesce_time; + sync_init(subgrp, updgrp); + bpacket_queue_init(SUBGRP_PKTQ(subgrp)); + bpacket_queue_add(SUBGRP_PKTQ(subgrp), NULL, NULL); + TAILQ_INIT(&(subgrp->adjq)); + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("create subgroup u%" PRIu64 ":s%" PRIu64, updgrp->id, + subgrp->id); + + update_group_add_subgroup(updgrp, subgrp); + + UPDGRP_INCR_STAT(updgrp, subgrps_created); + + return subgrp; +} + +static void update_subgroup_delete(struct update_subgroup *subgrp) +{ + if (!subgrp) + return; + + if (subgrp->update_group) + UPDGRP_INCR_STAT(subgrp->update_group, subgrps_deleted); + + EVENT_OFF(subgrp->t_merge_check); + EVENT_OFF(subgrp->t_coalesce); + + bpacket_queue_cleanup(SUBGRP_PKTQ(subgrp)); + subgroup_clear_table(subgrp); + + sync_delete(subgrp); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS) && subgrp->update_group) + zlog_debug("delete subgroup u%" PRIu64 ":s%" PRIu64, + subgrp->update_group->id, subgrp->id); + + update_group_remove_subgroup(subgrp->update_group, subgrp); + + XFREE(MTYPE_BGP_UPD_SUBGRP, subgrp); +} + +void update_subgroup_inherit_info(struct update_subgroup *to, + struct update_subgroup *from) +{ + if (!to || !from) + return; + + to->sflags = from->sflags; +} + +/* + * update_subgroup_check_delete + * + * Delete a subgroup if it is ready to be deleted. + * + * Returns true if the subgroup was deleted. + */ +static bool update_subgroup_check_delete(struct update_subgroup *subgrp) +{ + if (!subgrp) + return false; + + if (!LIST_EMPTY(&(subgrp->peers))) + return false; + + update_subgroup_delete(subgrp); + + return true; +} + +/* + * update_subgroup_add_peer + * + * @param send_enqueued_packets If true all currently enqueued packets will + * also be sent to the peer. + */ +static void update_subgroup_add_peer(struct update_subgroup *subgrp, + struct peer_af *paf, + int send_enqueued_pkts) +{ + struct bpacket *pkt; + + if (!subgrp || !paf) + return; + + LIST_INSERT_HEAD(&(subgrp->peers), paf, subgrp_train); + paf->subgroup = subgrp; + subgrp->peer_count++; + + if (bgp_debug_peer_updout_enabled(paf->peer->host)) { + UPDGRP_PEER_DBG_EN(subgrp->update_group); + } + + SUBGRP_INCR_STAT(subgrp, join_events); + + if (send_enqueued_pkts) { + pkt = bpacket_queue_first(SUBGRP_PKTQ(subgrp)); + } else { + + /* + * Hang the peer off of the last, placeholder, packet in the + * queue. This means it won't see any of the packets that are + * currently the queue. + */ + pkt = bpacket_queue_last(SUBGRP_PKTQ(subgrp)); + assert(pkt->buffer == NULL); + } + + bpacket_add_peer(pkt, paf); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("peer %s added to subgroup s%" PRIu64, + paf->peer->host, subgrp->id); +} + +/* + * update_subgroup_remove_peer_internal + * + * Internal function that removes a peer from a subgroup, but does not + * delete the subgroup. A call to this function must almost always be + * followed by a call to update_subgroup_check_delete(). + * + * @see update_subgroup_remove_peer + */ +static void update_subgroup_remove_peer_internal(struct update_subgroup *subgrp, + struct peer_af *paf) +{ + assert(subgrp && paf && subgrp->update_group); + + if (bgp_debug_peer_updout_enabled(paf->peer->host)) { + UPDGRP_PEER_DBG_DIS(subgrp->update_group); + } + + bpacket_queue_remove_peer(paf); + LIST_REMOVE(paf, subgrp_train); + paf->subgroup = NULL; + subgrp->peer_count--; + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("peer %s deleted from subgroup s%" + PRIu64 " peer cnt %d", + paf->peer->host, subgrp->id, subgrp->peer_count); + SUBGRP_INCR_STAT(subgrp, prune_events); +} + +/* + * update_subgroup_remove_peer + */ +void update_subgroup_remove_peer(struct update_subgroup *subgrp, + struct peer_af *paf) +{ + if (!subgrp || !paf) + return; + + update_subgroup_remove_peer_internal(subgrp, paf); + + if (update_subgroup_check_delete(subgrp)) + return; + + /* + * The deletion of the peer may have caused some packets to be + * deleted from the subgroup packet queue. Check if the subgroup can + * be merged now. + */ + update_subgroup_check_merge(subgrp, "removed peer from subgroup"); +} + +static struct update_subgroup *update_subgroup_find(struct update_group *updgrp, + struct peer_af *paf) +{ + struct update_subgroup *subgrp = NULL; + uint64_t version; + + if (paf->subgroup) { + assert(0); + return NULL; + } else + version = 0; + + if (!peer_established(PAF_PEER(paf)->connection)) + return NULL; + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + if (subgrp->version != version + || CHECK_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) + continue; + + /* + * The version number is not meaningful on a subgroup that needs + * a refresh. + */ + if (update_subgroup_needs_refresh(subgrp)) + continue; + + break; + } + + return subgrp; +} + +/* + * update_subgroup_ready_for_merge + * + * Returns true if this subgroup is in a state that allows it to be + * merged into another subgroup. + */ +static bool update_subgroup_ready_for_merge(struct update_subgroup *subgrp) +{ + + /* + * Not ready if there are any encoded packets waiting to be written + * out to peers. + */ + if (!bpacket_queue_is_empty(SUBGRP_PKTQ(subgrp))) + return false; + + /* + * Not ready if there enqueued updates waiting to be encoded. + */ + if (!advertise_list_is_empty(subgrp)) + return false; + + /* + * Don't attempt to merge a subgroup that needs a refresh. For one, + * we can't determine if the adj_out of such a group matches that of + * another group. + */ + if (update_subgroup_needs_refresh(subgrp)) + return false; + + return true; +} + +/* + * update_subgrp_can_merge_into + * + * Returns true if the first subgroup can merge into the second + * subgroup. + */ +static int update_subgroup_can_merge_into(struct update_subgroup *subgrp, + struct update_subgroup *target) +{ + + if (subgrp == target) + return 0; + + /* + * Both must have processed the BRIB to the same point in order to + * be merged. + */ + if (subgrp->version != target->version) + return 0; + + if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE) + != CHECK_FLAG(target->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) + return 0; + + if (subgrp->adj_count != target->adj_count) + return 0; + + return update_subgroup_ready_for_merge(target); +} + +/* + * update_subgroup_merge + * + * Merge the first subgroup into the second one. + */ +static void update_subgroup_merge(struct update_subgroup *subgrp, + struct update_subgroup *target, + const char *reason) +{ + struct peer_af *paf; + int result; + int peer_count; + + assert(subgrp->adj_count == target->adj_count); + + peer_count = subgrp->peer_count; + + while (1) { + paf = LIST_FIRST(&subgrp->peers); + if (!paf) + break; + + update_subgroup_remove_peer_internal(subgrp, paf); + + /* + * Add the peer to the target subgroup, while making sure that + * any currently enqueued packets won't be sent to it. Enqueued + * packets could, for example, result in an unnecessary withdraw + * followed by an advertise. + */ + update_subgroup_add_peer(target, paf, 0); + } + + SUBGRP_INCR_STAT(target, merge_events); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" (%d peers) merged into u%" PRIu64 ":s%" PRIu64", trigger: %s", + subgrp->update_group->id, subgrp->id, peer_count, + target->update_group->id, target->id, + reason ? reason : "unknown"); + + result = update_subgroup_check_delete(subgrp); + assert(result); +} + +/* + * update_subgroup_check_merge + * + * Merge this subgroup into another subgroup if possible. + * + * Returns true if the subgroup has been merged. The subgroup pointer + * should not be accessed in this case. + */ +bool update_subgroup_check_merge(struct update_subgroup *subgrp, + const char *reason) +{ + struct update_subgroup *target; + + if (!update_subgroup_ready_for_merge(subgrp)) + return false; + + /* + * Look for a subgroup to merge into. + */ + UPDGRP_FOREACH_SUBGRP (subgrp->update_group, target) { + if (update_subgroup_can_merge_into(subgrp, target)) + break; + } + + if (!target) + return false; + + update_subgroup_merge(subgrp, target, reason); + return true; +} + +/* +* update_subgroup_merge_check_thread_cb +*/ +static void update_subgroup_merge_check_thread_cb(struct event *thread) +{ + struct update_subgroup *subgrp; + + subgrp = EVENT_ARG(thread); + + subgrp->t_merge_check = NULL; + + update_subgroup_check_merge(subgrp, "triggered merge check"); +} + +/* + * update_subgroup_trigger_merge_check + * + * Triggers a call to update_subgroup_check_merge() on a clean context. + * + * @param force If true, the merge check will be triggered even if the + * subgroup doesn't currently look ready for a merge. + * + * Returns true if a merge check will be performed shortly. + */ +bool update_subgroup_trigger_merge_check(struct update_subgroup *subgrp, + int force) +{ + if (subgrp->t_merge_check) + return true; + + if (!force && !update_subgroup_ready_for_merge(subgrp)) + return false; + + subgrp->t_merge_check = NULL; + event_add_timer_msec(bm->master, update_subgroup_merge_check_thread_cb, + subgrp, 0, &subgrp->t_merge_check); + + SUBGRP_INCR_STAT(subgrp, merge_checks_triggered); + + return true; +} + +/* + * update_subgroup_copy_adj_out + * + * Helper function that clones the adj out (state about advertised + * routes) from one subgroup to another. It assumes that the adj out + * of the target subgroup is empty. + */ +static void update_subgroup_copy_adj_out(struct update_subgroup *source, + struct update_subgroup *dest) +{ + struct bgp_adj_out *aout, *aout_copy; + + SUBGRP_FOREACH_ADJ (source, aout) { + /* + * Copy the adj out. + */ + aout_copy = bgp_adj_out_alloc(dest, aout->dest, + aout->addpath_tx_id); + aout_copy->attr = + aout->attr ? bgp_attr_intern(aout->attr) : NULL; + } + + dest->scount = source->scount; +} + +/* + * update_subgroup_copy_packets + * + * Copy packets after and including the given packet to the subgroup + * 'dest'. + * + * Returns the number of packets copied. + */ +static int update_subgroup_copy_packets(struct update_subgroup *dest, + struct bpacket *pkt) +{ + int count; + + count = 0; + while (pkt && pkt->buffer) { + bpacket_queue_add(SUBGRP_PKTQ(dest), stream_dup(pkt->buffer), + &pkt->arr); + count++; + pkt = bpacket_next(pkt); + } + + return count; +} + +static bool updgrp_prefix_list_update(struct update_group *updgrp, + const char *name) +{ + struct peer *peer; + struct bgp_filter *filter; + + peer = UPDGRP_PEER(updgrp); + filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; + + if (PREFIX_LIST_OUT_NAME(filter) + && (strcmp(name, PREFIX_LIST_OUT_NAME(filter)) == 0)) { + PREFIX_LIST_OUT(filter) = prefix_list_lookup( + UPDGRP_AFI(updgrp), PREFIX_LIST_OUT_NAME(filter)); + return true; + } + return false; +} + +static bool updgrp_filter_list_update(struct update_group *updgrp, + const char *name) +{ + struct peer *peer; + struct bgp_filter *filter; + + peer = UPDGRP_PEER(updgrp); + filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; + + if (FILTER_LIST_OUT_NAME(filter) + && (strcmp(name, FILTER_LIST_OUT_NAME(filter)) == 0)) { + FILTER_LIST_OUT(filter) = + as_list_lookup(FILTER_LIST_OUT_NAME(filter)); + return true; + } + return false; +} + +static bool updgrp_distribute_list_update(struct update_group *updgrp, + const char *name) +{ + struct peer *peer; + struct bgp_filter *filter; + + peer = UPDGRP_PEER(updgrp); + filter = &peer->filter[UPDGRP_AFI(updgrp)][UPDGRP_SAFI(updgrp)]; + + if (DISTRIBUTE_OUT_NAME(filter) + && (strcmp(name, DISTRIBUTE_OUT_NAME(filter)) == 0)) { + DISTRIBUTE_OUT(filter) = access_list_lookup( + UPDGRP_AFI(updgrp), DISTRIBUTE_OUT_NAME(filter)); + return true; + } + return false; +} + +static int updgrp_route_map_update(struct update_group *updgrp, + const char *name, int *def_rmap_changed) +{ + struct peer *peer; + struct bgp_filter *filter; + int changed = 0; + afi_t afi; + safi_t safi; + + peer = UPDGRP_PEER(updgrp); + afi = UPDGRP_AFI(updgrp); + safi = UPDGRP_SAFI(updgrp); + filter = &peer->filter[afi][safi]; + + if (ROUTE_MAP_OUT_NAME(filter) + && (strcmp(name, ROUTE_MAP_OUT_NAME(filter)) == 0)) { + ROUTE_MAP_OUT(filter) = route_map_lookup_by_name(name); + + changed = 1; + } + + if (UNSUPPRESS_MAP_NAME(filter) + && (strcmp(name, UNSUPPRESS_MAP_NAME(filter)) == 0)) { + UNSUPPRESS_MAP(filter) = route_map_lookup_by_name(name); + changed = 1; + } + + /* process default-originate route-map */ + if (peer->default_rmap[afi][safi].name + && (strcmp(name, peer->default_rmap[afi][safi].name) == 0)) { + peer->default_rmap[afi][safi].map = + route_map_lookup_by_name(name); + if (def_rmap_changed) + *def_rmap_changed = 1; + } + return changed; +} + +/* + * hash iteration callback function to process a policy change for an + * update group. Check if the changed policy matches the updgrp's + * outbound route-map or unsuppress-map or default-originate map or + * filter-list or prefix-list or distribute-list. + * Trigger update generation accordingly. + */ +static int updgrp_policy_update_walkcb(struct update_group *updgrp, void *arg) +{ + struct updwalk_context *ctx = arg; + struct update_subgroup *subgrp; + int changed = 0; + int def_changed = 0; + + if (!updgrp || !ctx || !ctx->policy_name) + return UPDWALK_CONTINUE; + + switch (ctx->policy_type) { + case BGP_POLICY_ROUTE_MAP: + changed = updgrp_route_map_update(updgrp, ctx->policy_name, + &def_changed); + break; + case BGP_POLICY_FILTER_LIST: + changed = updgrp_filter_list_update(updgrp, ctx->policy_name); + break; + case BGP_POLICY_PREFIX_LIST: + changed = updgrp_prefix_list_update(updgrp, ctx->policy_name); + break; + case BGP_POLICY_DISTRIBUTE_LIST: + changed = + updgrp_distribute_list_update(updgrp, ctx->policy_name); + break; + default: + break; + } + + /* If not doing route update, return after updating "config" */ + if (!ctx->policy_route_update) + return UPDWALK_CONTINUE; + + /* If nothing has changed, return after updating "config" */ + if (!changed && !def_changed) + return UPDWALK_CONTINUE; + + /* + * If something has changed, at the beginning of a route-map + * modification + * event, mark each subgroup's needs-refresh bit. For one, it signals to + * whoever that the subgroup needs a refresh. Second, it prevents + * premature + * merge of this subgroup with another before a complete (outbound) + * refresh. + */ + if (ctx->policy_event_start_flag) { + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + update_subgroup_set_needs_refresh(subgrp, 1); + } + return UPDWALK_CONTINUE; + } + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + /* Avoid supressing duplicate routes later + * when processing in subgroup_announce_table(). + */ + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + + if (changed) { + if (bgp_debug_update(NULL, NULL, updgrp, 0)) + zlog_debug( + "u%" PRIu64 ":s%" PRIu64" announcing routes upon policy %s (type %d) change", + updgrp->id, subgrp->id, + ctx->policy_name, ctx->policy_type); + subgroup_announce_route(subgrp); + } + if (def_changed) { + if (bgp_debug_update(NULL, NULL, updgrp, 0)) + zlog_debug( + "u%" PRIu64 ":s%" PRIu64" announcing default upon default routemap %s change", + updgrp->id, subgrp->id, + ctx->policy_name); + if (route_map_lookup_by_name(ctx->policy_name)) { + /* + * When there is change in routemap, this flow + * is triggered. the routemap is still present + * in lib, hence its a update flow. The flag + * needs to be unset. + */ + UNSET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + subgroup_default_originate(subgrp, false); + } else { + /* + * This is a explicit withdraw, since the + * routemap is not present in routemap lib. need + * to pass `true` for withdraw arg. + */ + subgroup_default_originate(subgrp, true); + } + } + update_subgroup_set_needs_refresh(subgrp, 0); + } + return UPDWALK_CONTINUE; +} + +static int update_group_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct update_group *updgrp = bucket->data; + struct updwalk_context *wctx = arg; + int ret = (*wctx->cb)(updgrp, wctx->context); + return ret; +} + +/******************** + * PUBLIC FUNCTIONS + ********************/ + +/* + * trigger function when a policy (route-map/filter-list/prefix-list/ + * distribute-list etc.) content changes. Go through all the + * update groups and process the change. + * + * bgp: the bgp instance + * ptype: the type of policy that got modified, see bgpd.h + * pname: name of the policy + * route_update: flag to control if an automatic update generation should + * occur + * start_event: flag that indicates if it's the beginning of the change. + * Esp. when the user is changing the content interactively + * over multiple statements. Useful to set dirty flag on + * update groups. + */ +void update_group_policy_update(struct bgp *bgp, enum bgp_policy_type ptype, + const char *pname, bool route_update, + int start_event) +{ + struct updwalk_context ctx; + + memset(&ctx, 0, sizeof(ctx)); + ctx.policy_type = ptype; + ctx.policy_name = pname; + ctx.policy_route_update = route_update; + ctx.policy_event_start_flag = start_event; + ctx.flags = 0; + + update_group_walk(bgp, updgrp_policy_update_walkcb, &ctx); +} + +/* + * update_subgroup_split_peer + * + * Ensure that the given peer is in a subgroup of its own in the + * specified update group. + */ +void update_subgroup_split_peer(struct peer_af *paf, + struct update_group *updgrp) +{ + struct update_subgroup *old_subgrp, *subgrp; + uint64_t old_id; + + + old_subgrp = paf->subgroup; + + if (!updgrp) + updgrp = old_subgrp->update_group; + + /* + * If the peer is alone in its subgroup, reuse the existing + * subgroup. + */ + if (old_subgrp->peer_count == 1) { + if (updgrp == old_subgrp->update_group) + return; + + subgrp = old_subgrp; + old_id = old_subgrp->update_group->id; + + if (bgp_debug_peer_updout_enabled(paf->peer->host)) { + UPDGRP_PEER_DBG_DIS(old_subgrp->update_group); + } + + update_group_remove_subgroup(old_subgrp->update_group, + old_subgrp); + update_group_add_subgroup(updgrp, subgrp); + + if (bgp_debug_peer_updout_enabled(paf->peer->host)) { + UPDGRP_PEER_DBG_EN(updgrp); + } + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" peer %s moved to u%" PRIu64 ":s%" PRIu64, + old_id, subgrp->id, paf->peer->host, + updgrp->id, subgrp->id); + + /* + * The state of the subgroup (adj_out, advs, packet queue etc) + * is consistent internally, but may not be identical to other + * subgroups in the new update group even if the version number + * matches up. Make sure a full refresh is done before the + * subgroup is merged with another. + */ + update_subgroup_set_needs_refresh(subgrp, 1); + + SUBGRP_INCR_STAT(subgrp, updgrp_switch_events); + return; + } + + /* + * Create a new subgroup under the specified update group, and copy + * over relevant state to it. + */ + subgrp = update_subgroup_create(updgrp); + update_subgroup_inherit_info(subgrp, old_subgrp); + + subgrp->split_from.update_group_id = old_subgrp->update_group->id; + subgrp->split_from.subgroup_id = old_subgrp->id; + + /* + * Copy out relevant state from the old subgroup. + */ + update_subgroup_copy_adj_out(paf->subgroup, subgrp); + update_subgroup_copy_packets(subgrp, paf->next_pkt_to_send); + + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" peer %s split and moved into u%" PRIu64":s%" PRIu64, + paf->subgroup->update_group->id, paf->subgroup->id, + paf->peer->host, updgrp->id, subgrp->id); + + SUBGRP_INCR_STAT(paf->subgroup, split_events); + + /* + * Since queued advs were left behind, this new subgroup needs a + * refresh. + */ + update_subgroup_set_needs_refresh(subgrp, 1); + + /* + * Remove peer from old subgroup, and add it to the new one. + */ + update_subgroup_remove_peer(paf->subgroup, paf); + + update_subgroup_add_peer(subgrp, paf, 1); +} + +void update_bgp_group_init(struct bgp *bgp) +{ + int afid; + + AF_FOREACH (afid) + bgp->update_groups[afid] = + hash_create(updgrp_hash_key_make, updgrp_hash_cmp, + "BGP Update Group Hash"); +} + +void update_bgp_group_free(struct bgp *bgp) +{ + int afid; + + AF_FOREACH (afid) { + if (bgp->update_groups[afid]) { + hash_free(bgp->update_groups[afid]); + bgp->update_groups[afid] = NULL; + } + } +} + +void update_group_show(struct bgp *bgp, afi_t afi, safi_t safi, struct vty *vty, + uint64_t subgrp_id, bool uj) +{ + struct updwalk_context ctx; + json_object *json_vrf_obj = NULL; + + memset(&ctx, 0, sizeof(ctx)); + ctx.vty = vty; + ctx.subgrp_id = subgrp_id; + ctx.uj = uj; + + if (uj) { + ctx.json_updategrps = json_object_new_object(); + json_vrf_obj = json_object_new_object(); + } + + update_group_af_walk(bgp, afi, safi, update_group_show_walkcb, &ctx); + + if (uj) { + const char *vname; + + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + vname = VRF_DEFAULT_NAME; + else + vname = bgp->name; + json_object_object_add(json_vrf_obj, vname, + ctx.json_updategrps); + vty_json(vty, json_vrf_obj); + } +} + +/* + * update_group_show_stats + * + * Show global statistics about update groups. + */ +void update_group_show_stats(struct bgp *bgp, struct vty *vty) +{ + vty_out(vty, "Update groups created: %u\n", + bgp->update_group_stats.updgrps_created); + vty_out(vty, "Update groups deleted: %u\n", + bgp->update_group_stats.updgrps_deleted); + vty_out(vty, "Update subgroups created: %u\n", + bgp->update_group_stats.subgrps_created); + vty_out(vty, "Update subgroups deleted: %u\n", + bgp->update_group_stats.subgrps_deleted); + vty_out(vty, "Join events: %u\n", bgp->update_group_stats.join_events); + vty_out(vty, "Prune events: %u\n", + bgp->update_group_stats.prune_events); + vty_out(vty, "Merge events: %u\n", + bgp->update_group_stats.merge_events); + vty_out(vty, "Split events: %u\n", + bgp->update_group_stats.split_events); + vty_out(vty, "Update group switch events: %u\n", + bgp->update_group_stats.updgrp_switch_events); + vty_out(vty, "Peer route refreshes combined: %u\n", + bgp->update_group_stats.peer_refreshes_combined); + vty_out(vty, "Merge checks triggered: %u\n", + bgp->update_group_stats.merge_checks_triggered); +} + +/* + * update_group_adjust_peer + */ +void update_group_adjust_peer(struct peer_af *paf) +{ + struct update_group *updgrp; + struct update_subgroup *subgrp, *old_subgrp; + struct peer *peer; + + if (!paf) + return; + + peer = PAF_PEER(paf); + if (!peer_established(peer->connection)) { + return; + } + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) { + return; + } + + if (!peer->afc_nego[paf->afi][paf->safi]) { + return; + } + + updgrp = update_group_find(paf); + if (!updgrp) + updgrp = update_group_create(paf); + + old_subgrp = paf->subgroup; + + if (old_subgrp) { + + /* + * If the update group of the peer is unchanged, the peer can + * stay + * in its existing subgroup and we're done. + */ + if (old_subgrp->update_group == updgrp) + return; + + /* + * The peer is switching between update groups. Put it in its + * own subgroup under the new update group. + */ + update_subgroup_split_peer(paf, updgrp); + return; + } + + subgrp = update_subgroup_find(updgrp, paf); + if (!subgrp) + subgrp = update_subgroup_create(updgrp); + + update_subgroup_add_peer(subgrp, paf, 1); + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 " add peer %s", updgrp->id, + subgrp->id, paf->peer->host); + + return; +} + +int update_group_adjust_soloness(struct peer *peer, int set) +{ + struct peer_group *group; + struct listnode *node, *nnode; + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + peer_lonesoul_or_not(peer, set); + if (peer_established(peer->connection)) + bgp_announce_route_all(peer); + } else { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + peer_lonesoul_or_not(peer, set); + if (peer_established(peer->connection)) + bgp_announce_route_all(peer); + } + } + return 0; +} + +/* + * update_subgroup_rib + */ +struct bgp_table *update_subgroup_rib(struct update_subgroup *subgrp) +{ + struct bgp *bgp; + + bgp = SUBGRP_INST(subgrp); + if (!bgp) + return NULL; + + return bgp->rib[SUBGRP_AFI(subgrp)][SUBGRP_SAFI(subgrp)]; +} + +void update_group_af_walk(struct bgp *bgp, afi_t afi, safi_t safi, + updgrp_walkcb cb, void *ctx) +{ + struct updwalk_context wctx; + int afid; + + if (!bgp) + return; + afid = afindex(afi, safi); + if (afid >= BGP_AF_MAX) + return; + + memset(&wctx, 0, sizeof(wctx)); + wctx.cb = cb; + wctx.context = ctx; + + if (bgp->update_groups[afid]) + hash_walk(bgp->update_groups[afid], update_group_walkcb, &wctx); +} + +void update_group_walk(struct bgp *bgp, updgrp_walkcb cb, void *ctx) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + update_group_af_walk(bgp, afi, safi, cb, ctx); + } +} + +static int +update_group_default_originate_route_map_walkcb(struct update_group *updgrp, + void *arg) +{ + struct update_subgroup *subgrp; + struct peer *peer; + afi_t afi; + safi_t safi; + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + + if (peer->default_rmap[afi][safi].name) { + /* + * When there is change in routemap this flow will + * be triggered. We need to unset the Flag to ensure + * the update flow gets triggered. + */ + UNSET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + subgroup_default_originate(subgrp, false); + } + } + + return UPDWALK_CONTINUE; +} + +void update_group_refresh_default_originate_route_map(struct event *thread) +{ + struct bgp *bgp; + char reason[] = "refresh default-originate route-map"; + + bgp = EVENT_ARG(thread); + update_group_walk(bgp, update_group_default_originate_route_map_walkcb, + reason); + EVENT_OFF(bgp->t_rmap_def_originate_eval); +} + +/* + * peer_af_announce_route + * + * Refreshes routes out to a peer_af immediately. + * + * If the combine parameter is true, then this function will try to + * gather other peers in the subgroup for which a route announcement + * is pending and efficently announce routes to all of them. + * + * For now, the 'combine' option has an effect only if all peers in + * the subgroup have a route announcement pending. + */ +void peer_af_announce_route(struct peer_af *paf, int combine) +{ + struct update_subgroup *subgrp; + struct peer_af *cur_paf; + int all_pending; + + subgrp = paf->subgroup; + all_pending = 0; + + if (combine) { + /* + * If there are other peers in the old subgroup that also need + * routes to be announced, pull them into the peer's new + * subgroup. + * Combine route announcement with other peers if possible. + * + * For now, we combine only if all peers in the subgroup have an + * announcement pending. + */ + all_pending = 1; + + SUBGRP_FOREACH_PEER (subgrp, cur_paf) { + if (cur_paf == paf) + continue; + + if (cur_paf->t_announce_route) + continue; + + all_pending = 0; + break; + } + } + /* + * Announce to the peer alone if we were not asked to combine peers, + * or if some peers don't have a route annoucement pending. + */ + if (!combine || !all_pending) { + update_subgroup_split_peer(paf, NULL); + subgrp = paf->subgroup; + + assert(subgrp && subgrp->update_group); + if (bgp_debug_update(paf->peer, NULL, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" %s announcing routes", + subgrp->update_group->id, subgrp->id, + paf->peer->host); + + subgroup_announce_route(paf->subgroup); + return; + } + + /* + * We will announce routes the entire subgroup. + * + * First stop refresh timers on all the other peers. + */ + SUBGRP_FOREACH_PEER (subgrp, cur_paf) { + if (cur_paf == paf) + continue; + + bgp_stop_announce_route_timer(cur_paf); + } + + if (bgp_debug_update(paf->peer, NULL, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" announcing routes to %s, combined into %d peers", + subgrp->update_group->id, subgrp->id, + paf->peer->host, subgrp->peer_count); + + subgroup_announce_route(subgrp); + + SUBGRP_INCR_STAT_BY(subgrp, peer_refreshes_combined, + subgrp->peer_count - 1); +} + +void subgroup_trigger_write(struct update_subgroup *subgrp) +{ + struct peer_af *paf; + + /* + * For each peer in the subgroup, schedule a job to pull packets from + * the subgroup output queue into their own output queue. This action + * will trigger a write job on the I/O thread. + */ + SUBGRP_FOREACH_PEER (subgrp, paf) { + struct peer_connection *connection = paf->peer->connection; + + if (peer_established(connection)) + event_add_timer_msec(bm->master, + bgp_generate_updgrp_packets, + connection, 0, + &connection->t_generate_updgrp_packets); + } +} + +int update_group_clear_update_dbg(struct update_group *updgrp, void *arg) +{ + UPDGRP_PEER_DBG_OFF(updgrp); + return UPDWALK_CONTINUE; +} + +/* Return true if we should addpath encode NLRI to this peer */ +bool bgp_addpath_encode_tx(struct peer *peer, afi_t afi, safi_t safi) +{ + return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ADDPATH_AF_TX_ADV) + && CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV)); +} + +bool bgp_addpath_capable(struct bgp_path_info *bpi, struct peer *peer, + afi_t afi, safi_t safi) +{ + return (bgp_addpath_tx_path(peer->addpath_type[afi][safi], bpi) || + (safi == SAFI_LABELED_UNICAST && + bgp_addpath_tx_path(peer->addpath_type[afi][SAFI_UNICAST], + bpi))); +} + +bool bgp_check_selected(struct bgp_path_info *bpi, struct peer *peer, + bool addpath_capable, afi_t afi, safi_t safi) +{ + return (CHECK_FLAG(bpi->flags, BGP_PATH_SELECTED) || + (addpath_capable && bgp_addpath_capable(bpi, peer, afi, safi))); +} diff --git a/bgpd/bgp_updgrp.h b/bgpd/bgp_updgrp.h new file mode 100644 index 0000000..d0fd226 --- /dev/null +++ b/bgpd/bgp_updgrp.h @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_updgrp.c: BGP update group structures + * + * @copyright Copyright (C) 2014 Cumulus Networks, Inc. + * + * @author Avneesh Sachdev + * @author Rajesh Varadarajan + * @author Pradosh Mohapatra + */ + +#ifndef _QUAGGA_BGP_UPDGRP_H +#define _QUAGGA_BGP_UPDGRP_H + +#include "bgp_advertise.h" + +/* + * The following three heuristic constants determine how long advertisement to + * a subgroup will be delayed after it is created. The intent is to allow + * transient changes in peer state (primarily session establishment) to settle, + * so that more peers can be grouped together and benefit from sharing + * advertisement computations with the subgroup. + * + * These values have a very large impact on initial convergence time; any + * changes should be accompanied by careful performance testing at all scales. + * + * The coalesce time 'C' for a new subgroup within a particular BGP instance + * 'B' with total number of known peers 'P', established or not, is computed as + * follows: + * + * C = MIN(BGP_MAX_SUBGROUP_COALESCE_TIME, + * BGP_DEFAULT_SUBGROUP_COALESCE_TIME + + * (P*BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME)) + */ +#define BGP_DEFAULT_SUBGROUP_COALESCE_TIME 1000 +#define BGP_MAX_SUBGROUP_COALESCE_TIME 10000 +#define BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME 50 + +#define PEER_UPDGRP_FLAGS \ + (PEER_FLAG_LOCAL_AS_NO_PREPEND | PEER_FLAG_LOCAL_AS_REPLACE_AS) + +#define PEER_UPDGRP_AF_FLAGS \ + (PEER_FLAG_SEND_COMMUNITY | PEER_FLAG_SEND_EXT_COMMUNITY | \ + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI | PEER_FLAG_SEND_LARGE_COMMUNITY | \ + PEER_FLAG_DEFAULT_ORIGINATE | PEER_FLAG_REFLECTOR_CLIENT | \ + PEER_FLAG_RSERVER_CLIENT | PEER_FLAG_NEXTHOP_SELF | \ + PEER_FLAG_NEXTHOP_UNCHANGED | PEER_FLAG_FORCE_NEXTHOP_SELF | \ + PEER_FLAG_AS_PATH_UNCHANGED | PEER_FLAG_MED_UNCHANGED | \ + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED | PEER_FLAG_REMOVE_PRIVATE_AS | \ + PEER_FLAG_REMOVE_PRIVATE_AS_ALL | \ + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE | \ + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE | PEER_FLAG_AS_OVERRIDE) + +#define PEER_UPDGRP_CAP_FLAGS (PEER_CAP_AS4_RCV) + +#define PEER_UPDGRP_AF_CAP_FLAGS \ + (PEER_CAP_ORF_PREFIX_SM_RCV | PEER_CAP_ADDPATH_AF_TX_ADV | \ + PEER_CAP_ADDPATH_AF_RX_RCV | PEER_CAP_ENHE_AF_NEGO) + +enum bpacket_attr_vec_type { BGP_ATTR_VEC_NH = 0, BGP_ATTR_VEC_MAX }; + +typedef struct { + uint32_t flags; + unsigned long offset; +} bpacket_attr_vec; + +#define BPKT_ATTRVEC_FLAGS_UPDATED (1 << 0) +#define BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS (1 << 1) +#define BPKT_ATTRVEC_FLAGS_REFLECTED (1 << 2) +#define BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED (1 << 3) +#define BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED (1 << 4) +#define BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED (1 << 5) +#define BPKT_ATTRVEC_FLAGS_RMAP_IPV6_LNH_CHANGED (1 << 6) +#define BPKT_ATTRVEC_FLAGS_RMAP_VPNV4_NH_CHANGED (1 << 7) +#define BPKT_ATTRVEC_FLAGS_RMAP_VPNV6_GNH_CHANGED (1 << 8) + +typedef struct bpacket_attr_vec_arr { + bpacket_attr_vec entries[BGP_ATTR_VEC_MAX]; +} bpacket_attr_vec_arr; + +struct bpacket { + /* for being part of an update subgroup's message list */ + TAILQ_ENTRY(bpacket) pkt_train; + + /* list of peers (well, peer_afs) that the packet needs to be sent to */ + LIST_HEAD(pkt_peer_list, peer_af) peers; + + struct stream *buffer; + bpacket_attr_vec_arr arr; + + unsigned int ver; +}; + +struct bpacket_queue { + TAILQ_HEAD(pkt_queue, bpacket) pkts; + + unsigned int conf_max_count; + unsigned int curr_count; + unsigned int hwm_count; + unsigned int max_count_reached_count; +}; + +struct update_group { + /* back pointer to the BGP instance */ + struct bgp *bgp; + + /* list of subgroups that belong to the update group */ + LIST_HEAD(subgrp_list, update_subgroup) subgrps; + + /* lazy way to store configuration common to all peers + hash function will compute from this data */ + struct peer *conf; + + afi_t afi; + safi_t safi; + int afid; + + uint64_t id; + time_t uptime; + + uint32_t join_events; + uint32_t prune_events; + uint32_t merge_events; + uint32_t updgrp_switch_events; + uint32_t peer_refreshes_combined; + uint32_t adj_count; + uint32_t split_events; + uint32_t merge_checks_triggered; + + uint32_t subgrps_created; + uint32_t subgrps_deleted; + + uint32_t num_dbg_en_peers; +}; + +/* + * Shorthand for a global statistics counter. + */ +#define UPDGRP_GLOBAL_STAT(updgrp, stat) \ + ((updgrp)->bgp->update_group_stats.stat) + +/* + * Add the given value to a counter on an update group and the bgp + * instance. + */ +#define UPDGRP_INCR_STAT_BY(updgrp, stat, value) \ + do { \ + (updgrp)->stat += (value); \ + UPDGRP_GLOBAL_STAT(updgrp, stat) += (value); \ + } while (0) + +/* + * Increment a counter on a update group and its parent structures. + */ +#define UPDGRP_INCR_STAT(subgrp, stat) UPDGRP_INCR_STAT_BY(subgrp, stat, 1) + +struct update_subgroup { + /* back pointer to the parent update group */ + struct update_group *update_group; + + /* list of peers that belong to the subgroup */ + LIST_HEAD(peer_list, peer_af) peers; + int peer_count; + + /* for being part of an update group's subgroup list */ + LIST_ENTRY(update_subgroup) updgrp_train; + + struct bpacket_queue pkt_queue; + + /* + * List of adj-out structures for this subgroup. + * It essentially represents the snapshot of every prefix that + * has been advertised to the members of the subgroup + */ + TAILQ_HEAD(adjout_queue, bgp_adj_out) adjq; + + /* packet buffer for update generation */ + struct stream *work; + + /* We use a separate stream to encode MP_REACH_NLRI for efficient + * NLRI packing. peer->obuf_work stores all the other attributes. The + * actual packet is then constructed by concatenating the two. + */ + struct stream *scratch; + + /* synchronization list and time */ + struct bgp_synchronize *sync; + + /* send prefix count */ + uint32_t scount; + + /* send prefix count prior to packet update */ + uint32_t pscount; + + /* announcement attribute hash */ + struct hash *hash; + + struct event *t_coalesce; + uint32_t v_coalesce; + + struct event *t_merge_check; + + /* table version that the subgroup has caught up to. */ + uint64_t version; + + /* version maintained to record adj changes */ + uint64_t adj_version; + + time_t uptime; + + /* + * Identifying information about the subgroup that this subgroup was + * split + * from, if any. + */ + struct { + uint64_t update_group_id; + uint64_t subgroup_id; + } split_from; + + uint32_t join_events; + uint32_t prune_events; + + /* + * This is bumped up when another subgroup merges into this one. + */ + uint32_t merge_events; + uint32_t updgrp_switch_events; + uint32_t peer_refreshes_combined; + uint32_t adj_count; + uint32_t split_events; + uint32_t merge_checks_triggered; + + uint64_t id; + + uint16_t sflags; +#define SUBGRP_STATUS_DEFAULT_ORIGINATE (1 << 0) +#define SUBGRP_STATUS_FORCE_UPDATES (1 << 1) +#define SUBGRP_STATUS_TABLE_REPARSING (1 << 2) +/* + * This flag has been added to ensure that the SNT counters + * gets incremented and decremented only during the creation + * and deletion workflows of default originate, + * not during the update workflow. + */ +#define SUBGRP_STATUS_PEER_DEFAULT_ORIGINATED (1 << 3) + + uint16_t flags; +#define SUBGRP_FLAG_NEEDS_REFRESH (1 << 0) +}; + +/* + * Add the given value to the specified counter on a subgroup and its + * parent structures. + */ +#define SUBGRP_INCR_STAT_BY(subgrp, stat, value) \ + do { \ + (subgrp)->stat += (value); \ + if ((subgrp)->update_group) \ + UPDGRP_INCR_STAT_BY((subgrp)->update_group, stat, \ + value); \ + } while (0) + +/* + * Increment a counter on a subgroup and its parent structures. + */ +#define SUBGRP_INCR_STAT(subgrp, stat) SUBGRP_INCR_STAT_BY(subgrp, stat, 1) + +/* + * Decrement a counter on a subgroup and its parent structures. + */ +#define SUBGRP_DECR_STAT(subgrp, stat) SUBGRP_INCR_STAT_BY(subgrp, stat, -1) + +typedef int (*updgrp_walkcb)(struct update_group *updgrp, void *ctx); + +/* really a private structure */ +struct updwalk_context { + struct vty *vty; + struct bgp_dest *dest; + struct bgp_path_info *pi; + uint64_t updgrp_id; + uint64_t subgrp_id; + enum bgp_policy_type policy_type; + const char *policy_name; + int policy_event_start_flag; + bool policy_route_update; + updgrp_walkcb cb; + void *context; + uint8_t flags; + bool uj; + json_object *json_updategrps; + +#define UPDWALK_FLAGS_ADVQUEUE (1 << 0) +#define UPDWALK_FLAGS_ADVERTISED (1 << 1) +}; + +#define UPDWALK_CONTINUE HASHWALK_CONTINUE +#define UPDWALK_ABORT HASHWALK_ABORT + +#define PAF_PEER(p) ((p)->peer) +#define PAF_SUBGRP(p) ((p)->subgroup) +#define PAF_UPDGRP(p) ((p)->subgroup->update_group) +#define PAF_PKTQ(f) SUBGRP_PKTQ((f)->subgroup) + +#define UPDGRP_PEER(u) ((u)->conf) +#define UPDGRP_AFI(u) ((u)->afi) +#define UPDGRP_SAFI(u) ((u)->safi) +#define UPDGRP_INST(u) ((u)->bgp) +#define UPDGRP_AFFLAGS(u) ((u)->conf->af_flags[UPDGRP_AFI(u)][UPDGRP_SAFI(u)]) +#define UPDGRP_DBG_ON(u) ((u)->num_dbg_en_peers) +#define UPDGRP_PEER_DBG_EN(u) (((u)->num_dbg_en_peers)++) +#define UPDGRP_PEER_DBG_DIS(u) (((u)->num_dbg_en_peers)--) +#define UPDGRP_PEER_DBG_OFF(u) (u)->num_dbg_en_peers = 0 + +#define SUBGRP_AFI(s) UPDGRP_AFI((s)->update_group) +#define SUBGRP_SAFI(s) UPDGRP_SAFI((s)->update_group) +#define SUBGRP_PEER(s) UPDGRP_PEER((s)->update_group) +#define SUBGRP_PCOUNT(s) ((s)->peer_count) +#define SUBGRP_PFIRST(s) LIST_FIRST(&((s)->peers)) +#define SUBGRP_PKTQ(s) &((s)->pkt_queue) +#define SUBGRP_INST(s) UPDGRP_INST((s)->update_group) +#define SUBGRP_AFFLAGS(s) UPDGRP_AFFLAGS((s)->update_group) +#define SUBGRP_UPDGRP(s) ((s)->update_group) + +/* + * Walk all subgroups in an update group. + */ +#define UPDGRP_FOREACH_SUBGRP(updgrp, subgrp) \ + LIST_FOREACH (subgrp, &((updgrp)->subgrps), updgrp_train) + +#define UPDGRP_FOREACH_SUBGRP_SAFE(updgrp, subgrp, tmp_subgrp) \ + LIST_FOREACH_SAFE (subgrp, &((updgrp)->subgrps), updgrp_train, \ + tmp_subgrp) + +#define SUBGRP_FOREACH_PEER(subgrp, paf) \ + LIST_FOREACH (paf, &(subgrp->peers), subgrp_train) + +#define SUBGRP_FOREACH_PEER_SAFE(subgrp, paf, temp_paf) \ + LIST_FOREACH_SAFE (paf, &(subgrp->peers), subgrp_train, temp_paf) + +#define SUBGRP_FOREACH_ADJ(subgrp, adj) \ + TAILQ_FOREACH (adj, &(subgrp->adjq), subgrp_adj_train) + +#define SUBGRP_FOREACH_ADJ_SAFE(subgrp, adj, adj_temp) \ + TAILQ_FOREACH_SAFE (adj, &(subgrp->adjq), subgrp_adj_train, adj_temp) + +/* Prototypes. */ +/* bgp_updgrp.c */ +extern void update_bgp_group_init(struct bgp *); +extern void udpate_bgp_group_free(struct bgp *); + +extern void update_group_show(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t subgrp_id, bool uj); +extern void update_group_show_stats(struct bgp *bgp, struct vty *vty); +extern void update_group_adjust_peer(struct peer_af *paf); +extern int update_group_adjust_soloness(struct peer *peer, int set); + +extern void update_subgroup_remove_peer(struct update_subgroup *, + struct peer_af *); +extern struct bgp_table *update_subgroup_rib(struct update_subgroup *); +extern void update_subgroup_split_peer(struct peer_af *, struct update_group *); +extern bool update_subgroup_check_merge(struct update_subgroup *, const char *); +extern bool update_subgroup_trigger_merge_check(struct update_subgroup *, + int force); +extern void update_group_policy_update(struct bgp *bgp, + enum bgp_policy_type ptype, + const char *pname, bool route_update, + int start_event); +extern void update_group_af_walk(struct bgp *bgp, afi_t afi, safi_t safi, + updgrp_walkcb cb, void *ctx); +extern void update_group_walk(struct bgp *bgp, updgrp_walkcb cb, void *ctx); +extern void +update_group_refresh_default_originate_route_map(struct event *thread); +extern void update_group_start_advtimer(struct bgp *bgp); + +extern void update_subgroup_inherit_info(struct update_subgroup *to, + struct update_subgroup *from); + +/* bgp_updgrp_packet.c */ +extern struct bpacket *bpacket_alloc(void); +extern void bpacket_free(struct bpacket *pkt); +extern void bpacket_queue_init(struct bpacket_queue *q); +extern void bpacket_queue_cleanup(struct bpacket_queue *q); +extern void bpacket_queue_sanity_check(struct bpacket_queue *q); +extern struct bpacket *bpacket_queue_add(struct bpacket_queue *q, + struct stream *s, + struct bpacket_attr_vec_arr *vecarr); +struct bpacket *bpacket_queue_remove(struct bpacket_queue *q); +extern struct bpacket *bpacket_queue_first(struct bpacket_queue *q); +struct bpacket *bpacket_queue_last(struct bpacket_queue *q); +unsigned int bpacket_queue_length(struct bpacket_queue *q); +unsigned int bpacket_queue_hwm_length(struct bpacket_queue *q); +bool bpacket_queue_is_full(struct bgp *bgp, struct bpacket_queue *q); +extern void bpacket_queue_advance_peer(struct peer_af *paf); +extern void bpacket_queue_remove_peer(struct peer_af *paf); +extern void bpacket_add_peer(struct bpacket *pkt, struct peer_af *paf); +unsigned int bpacket_queue_virtual_length(struct peer_af *paf); +extern void bpacket_queue_show_vty(struct bpacket_queue *q, struct vty *vty); +bool subgroup_packets_to_build(struct update_subgroup *subgrp); +extern struct bpacket *subgroup_update_packet(struct update_subgroup *s); +extern struct bpacket *subgroup_withdraw_packet(struct update_subgroup *s); +extern struct stream *bpacket_reformat_for_peer(struct bpacket *pkt, + struct peer_af *paf); +extern void bpacket_attr_vec_arr_reset(struct bpacket_attr_vec_arr *vecarr); +extern void bpacket_attr_vec_arr_set_vec(struct bpacket_attr_vec_arr *vecarr, + enum bpacket_attr_vec_type type, + struct stream *s, struct attr *attr); +extern void subgroup_default_update_packet(struct update_subgroup *subgrp, + struct attr *attr, + struct peer *from); +extern void subgroup_default_withdraw_packet(struct update_subgroup *subgrp); + +/* bgp_updgrp_adv.c */ +extern struct bgp_advertise * +bgp_advertise_clean_subgroup(struct update_subgroup *subgrp, + struct bgp_adj_out *adj); +extern void update_group_show_adj_queue(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t id); +extern void update_group_show_advertised(struct bgp *bgp, afi_t afi, + safi_t safi, struct vty *vty, + uint64_t id); +extern void update_group_show_packet_queue(struct bgp *bgp, afi_t afi, + safi_t safi, struct vty *vty, + uint64_t id); +extern void subgroup_announce_route(struct update_subgroup *subgrp); +extern void subgroup_announce_all(struct update_subgroup *subgrp); + +extern void subgroup_default_originate(struct update_subgroup *subgrp, + bool withdraw); +extern void group_announce_route(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *dest, + struct bgp_path_info *pi); +extern void subgroup_clear_table(struct update_subgroup *subgrp); +extern void update_group_announce(struct bgp *bgp); +extern void update_group_announce_rrclients(struct bgp *bgp); +extern void peer_af_announce_route(struct peer_af *paf, int combine); +extern struct bgp_adj_out *bgp_adj_out_alloc(struct update_subgroup *subgrp, + struct bgp_dest *dest, + uint32_t addpath_tx_id); +extern void bgp_adj_out_remove_subgroup(struct bgp_dest *dest, + struct bgp_adj_out *adj, + struct update_subgroup *subgrp); +extern bool bgp_adj_out_set_subgroup(struct bgp_dest *dest, + struct update_subgroup *subgrp, + struct attr *attr, + struct bgp_path_info *path); +extern void bgp_adj_out_unset_subgroup(struct bgp_dest *dest, + struct update_subgroup *subgrp, + char withdraw, uint32_t addpath_tx_id); +void subgroup_announce_table(struct update_subgroup *subgrp, + struct bgp_table *table); +extern void subgroup_trigger_write(struct update_subgroup *subgrp); + +extern int update_group_clear_update_dbg(struct update_group *updgrp, + void *arg); + +extern void update_bgp_group_free(struct bgp *bgp); +extern bool bgp_addpath_encode_tx(struct peer *peer, afi_t afi, safi_t safi); +extern bool bgp_check_selected(struct bgp_path_info *bpi, struct peer *peer, + bool addpath_capable, afi_t afi, safi_t safi); +extern bool bgp_addpath_capable(struct bgp_path_info *bpi, struct peer *peer, + afi_t afi, safi_t safi); + +/* + * Inline functions + */ + +/* + * bpacket_queue_is_empty + */ +static inline int bpacket_queue_is_empty(struct bpacket_queue *queue) +{ + + /* + * The packet queue is empty if it only contains a sentinel. + */ + if (queue->curr_count != 1) + return 0; + + assert(bpacket_queue_first(queue)->buffer == NULL); + return 1; +} + +/* + * bpacket_next + * + * Returns the packet after the given packet in a bpacket queue. + */ +static inline struct bpacket *bpacket_next(struct bpacket *pkt) +{ + return TAILQ_NEXT(pkt, pkt_train); +} + +/* + * update_group_adjust_peer_afs + * + * Adjust all peer_af structures for the given peer. + */ +static inline void update_group_adjust_peer_afs(struct peer *peer) +{ + struct peer_af *paf; + int afidx; + + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { + paf = peer->peer_af_array[afidx]; + if (paf != NULL) + update_group_adjust_peer(paf); + } +} + +/* + * update_group_remove_peer_afs + * + * Remove all peer_af structures for the given peer from their subgroups. + */ +static inline void update_group_remove_peer_afs(struct peer *peer) +{ + struct peer_af *paf; + int afidx; + + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { + paf = peer->peer_af_array[afidx]; + if (paf != NULL) + update_subgroup_remove_peer(PAF_SUBGRP(paf), paf); + } +} + +/* + * update_subgroup_needs_refresh + */ +static inline int +update_subgroup_needs_refresh(const struct update_subgroup *subgrp) +{ + if (CHECK_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH)) + return 1; + else + return 0; +} + +/* + * update_subgroup_set_needs_refresh + */ +static inline void +update_subgroup_set_needs_refresh(struct update_subgroup *subgrp, int value) +{ + if (value) + SET_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH); + else + UNSET_FLAG(subgrp->flags, SUBGRP_FLAG_NEEDS_REFRESH); +} + +static inline struct update_subgroup *peer_subgroup(struct peer *peer, + afi_t afi, safi_t safi) +{ + struct peer_af *paf; + + paf = peer_af_find(peer, afi, safi); + if (paf) + return PAF_SUBGRP(paf); + return NULL; +} + +/* + * update_group_adjust_peer_afs + * + * Adjust all peer_af structures for the given peer. + */ +static inline void bgp_announce_peer(struct peer *peer) +{ + struct peer_af *paf; + int afidx; + + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { + paf = peer->peer_af_array[afidx]; + if (paf != NULL) + subgroup_announce_all(PAF_SUBGRP(paf)); + } +} + +/** + * advertise_list_is_empty + */ +static inline int advertise_list_is_empty(struct update_subgroup *subgrp) +{ + if (bgp_adv_fifo_count(&subgrp->sync->update) || + bgp_adv_fifo_count(&subgrp->sync->withdraw)) + return 0; + + return 1; +} + +#endif /* _QUAGGA_BGP_UPDGRP_H */ diff --git a/bgpd/bgp_updgrp_adv.c b/bgpd/bgp_updgrp_adv.c new file mode 100644 index 0000000..250378a --- /dev/null +++ b/bgpd/bgp_updgrp_adv.c @@ -0,0 +1,1142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_updgrp_adv.c: BGP update group advertisement and adjacency + * maintenance + * + * + * @copyright Copyright (C) 2014 Cumulus Networks, Inc. + * + * @author Avneesh Sachdev + * @author Rajesh Varadarajan + * @author Pradosh Mohapatra + */ + +#include + +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "hash.h" +#include "frrevent.h" +#include "queue.h" +#include "routemap.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_addpath.h" + + +/******************** + * PRIVATE FUNCTIONS + ********************/ +static int bgp_adj_out_compare(const struct bgp_adj_out *o1, + const struct bgp_adj_out *o2) +{ + if (o1->subgroup < o2->subgroup) + return -1; + + if (o1->subgroup > o2->subgroup) + return 1; + + if (o1->addpath_tx_id < o2->addpath_tx_id) + return -1; + + if (o1->addpath_tx_id > o2->addpath_tx_id) + return 1; + + return 0; +} +RB_GENERATE(bgp_adj_out_rb, bgp_adj_out, adj_entry, bgp_adj_out_compare); + +static inline struct bgp_adj_out *adj_lookup(struct bgp_dest *dest, + struct update_subgroup *subgrp, + uint32_t addpath_tx_id) +{ + struct bgp_adj_out lookup; + + if (!dest || !subgrp) + return NULL; + + /* update-groups that do not support addpath will pass 0 for + * addpath_tx_id. */ + lookup.subgroup = subgrp; + lookup.addpath_tx_id = addpath_tx_id; + + return RB_FIND(bgp_adj_out_rb, &dest->adj_out, &lookup); +} + +static void adj_free(struct bgp_adj_out *adj) +{ + bgp_labels_unintern(&adj->labels); + + TAILQ_REMOVE(&(adj->subgroup->adjq), adj, subgrp_adj_train); + SUBGRP_DECR_STAT(adj->subgroup, adj_count); + + RB_REMOVE(bgp_adj_out_rb, &adj->dest->adj_out, adj); + bgp_dest_unlock_node(adj->dest); + + XFREE(MTYPE_BGP_ADJ_OUT, adj); +} + +static void +subgrp_announce_addpath_best_selected(struct bgp_dest *dest, + struct update_subgroup *subgrp) +{ + afi_t afi = SUBGRP_AFI(subgrp); + safi_t safi = SUBGRP_SAFI(subgrp); + struct peer *peer = SUBGRP_PEER(subgrp); + enum bgp_path_selection_reason reason; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + int paths_eq = 0; + struct list *list = list_new(); + struct bgp_path_info *pi = NULL; + uint16_t paths_count = 0; + uint16_t paths_limit = peer->addpath_paths_limit[afi][safi].receive; + + if (peer->addpath_type[afi][safi] == BGP_ADDPATH_BEST_SELECTED) { + paths_limit = + paths_limit + ? MIN(paths_limit, + peer->addpath_best_selected[afi][safi]) + : peer->addpath_best_selected[afi][safi]; + + while (paths_count++ < paths_limit) { + struct bgp_path_info *exist = NULL; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (listnode_lookup(list, pi)) + continue; + + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + continue; + + if (bgp_path_info_cmp(peer->bgp, pi, exist, + &paths_eq, NULL, 0, + pfx_buf, afi, safi, + &reason)) + exist = pi; + } + + if (exist) + listnode_add(list, exist); + } + } + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + uint32_t id = bgp_addpath_id_for_peer(peer, afi, safi, + &pi->tx_addpath); + + if (peer->addpath_type[afi][safi] == + BGP_ADDPATH_BEST_SELECTED) { + if (listnode_lookup(list, pi)) + subgroup_process_announce_selected( + subgrp, pi, dest, afi, safi, id); + else + subgroup_process_announce_selected( + subgrp, NULL, dest, afi, safi, id); + } else { + /* No Paths-Limit involved */ + if (!paths_limit) { + subgroup_process_announce_selected(subgrp, pi, + dest, afi, + safi, id); + continue; + } + + /* If we have Paths-Limit capability, we MUST + * not send more than the number of paths expected + * by the peer. + */ + if (paths_count++ < paths_limit) + subgroup_process_announce_selected(subgrp, pi, + dest, afi, + safi, id); + else + subgroup_process_announce_selected(subgrp, NULL, + dest, afi, + safi, id); + } + } + + if (list) + list_delete(&list); +} + +static void subgrp_withdraw_stale_addpath(struct updwalk_context *ctx, + struct update_subgroup *subgrp) +{ + struct bgp_adj_out *adj, *adj_next; + uint32_t id; + struct bgp_path_info *pi; + afi_t afi = SUBGRP_AFI(subgrp); + safi_t safi = SUBGRP_SAFI(subgrp); + struct peer *peer = SUBGRP_PEER(subgrp); + + /* Look through all of the paths we have advertised for this rn and send + * a withdraw for the ones that are no longer present */ + RB_FOREACH_SAFE (adj, bgp_adj_out_rb, &ctx->dest->adj_out, adj_next) { + if (adj->subgroup != subgrp) + continue; + + for (pi = bgp_dest_get_bgp_path_info(ctx->dest); pi; + pi = pi->next) { + id = bgp_addpath_id_for_peer(peer, afi, safi, + &pi->tx_addpath); + + if (id == adj->addpath_tx_id) { + break; + } + } + + if (!pi) { + subgroup_process_announce_selected(subgrp, NULL, + ctx->dest, afi, safi, + adj->addpath_tx_id); + } + } +} + +static int group_announce_route_walkcb(struct update_group *updgrp, void *arg) +{ + struct updwalk_context *ctx = arg; + struct update_subgroup *subgrp; + afi_t afi; + safi_t safi; + struct peer *peer; + struct bgp_adj_out *adj, *adj_next; + bool addpath_capable; + + afi = UPDGRP_AFI(updgrp); + safi = UPDGRP_SAFI(updgrp); + peer = UPDGRP_PEER(updgrp); + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + + if (BGP_DEBUG(update, UPDATE_OUT)) + zlog_debug("%s: afi=%s, safi=%s, p=%pBD", __func__, + afi2str(afi), safi2str(safi), ctx->dest); + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + /* + * Skip the subgroups that have coalesce timer running. We will + * walk the entire prefix table for those subgroups when the + * coalesce timer fires. + */ + if (!subgrp->t_coalesce) { + + /* An update-group that uses addpath */ + if (addpath_capable) { + subgrp_withdraw_stale_addpath(ctx, subgrp); + + subgrp_announce_addpath_best_selected(ctx->dest, + subgrp); + + /* Process the bestpath last so the "show [ip] + * bgp neighbor x.x.x.x advertised" + * output shows the attributes from the bestpath + */ + if (ctx->pi) + subgroup_process_announce_selected( + subgrp, ctx->pi, ctx->dest, afi, + safi, + bgp_addpath_id_for_peer( + peer, afi, safi, + &ctx->pi->tx_addpath)); + } + /* An update-group that does not use addpath */ + else { + if (ctx->pi) { + subgroup_process_announce_selected( + subgrp, ctx->pi, ctx->dest, afi, + safi, + bgp_addpath_id_for_peer( + peer, afi, safi, + &ctx->pi->tx_addpath)); + } else { + /* Find the addpath_tx_id of the path we + * had advertised and + * send a withdraw */ + RB_FOREACH_SAFE (adj, bgp_adj_out_rb, + &ctx->dest->adj_out, + adj_next) { + if (adj->subgroup == subgrp) { + subgroup_process_announce_selected( + subgrp, NULL, + ctx->dest, afi, + safi, + adj->addpath_tx_id); + } + } + } + } + } + + /* Notify BGP Conditional advertisement */ + bgp_notify_conditional_adv_scanner(subgrp); + } + + return UPDWALK_CONTINUE; +} + +static void subgrp_show_adjq_vty(struct update_subgroup *subgrp, + struct vty *vty, uint8_t flags) +{ + struct bgp_table *table; + struct bgp_adj_out *adj; + unsigned long output_count; + struct bgp_dest *dest; + int header1 = 1; + struct bgp *bgp; + int header2 = 1; + + bgp = SUBGRP_INST(subgrp); + if (!bgp) + return; + + table = bgp->rib[SUBGRP_AFI(subgrp)][SUBGRP_SAFI(subgrp)]; + + output_count = 0; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + RB_FOREACH (adj, bgp_adj_out_rb, &dest->adj_out) { + if (adj->subgroup != subgrp) + continue; + + if (header1) { + vty_out(vty, + "BGP table version is %" PRIu64 + ", local router ID is %pI4\n", + table->version, &bgp->router_id); + vty_out(vty, BGP_SHOW_SCODE_HEADER); + vty_out(vty, BGP_SHOW_OCODE_HEADER); + header1 = 0; + } + if (header2) { + vty_out(vty, BGP_SHOW_HEADER); + header2 = 0; + } + if ((flags & UPDWALK_FLAGS_ADVQUEUE) && adj->adv && + adj->adv->baa) { + route_vty_out_tmp(vty, bgp, dest, dest_p, + adj->adv->baa->attr, + SUBGRP_SAFI(subgrp), 0, NULL, + false); + output_count++; + } + if ((flags & UPDWALK_FLAGS_ADVERTISED) && adj->attr) { + route_vty_out_tmp(vty, bgp, dest, dest_p, + adj->attr, SUBGRP_SAFI(subgrp), + 0, NULL, false); + output_count++; + } + } + } + if (output_count != 0) + vty_out(vty, "\nTotal number of prefixes %ld\n", output_count); +} + +static int updgrp_show_adj_walkcb(struct update_group *updgrp, void *arg) +{ + struct updwalk_context *ctx = arg; + struct update_subgroup *subgrp; + struct vty *vty; + + vty = ctx->vty; + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + if (ctx->subgrp_id && (ctx->subgrp_id != subgrp->id)) + continue; + vty_out(vty, "update group %" PRIu64 ", subgroup %" PRIu64 "\n", + updgrp->id, subgrp->id); + subgrp_show_adjq_vty(subgrp, vty, ctx->flags); + } + return UPDWALK_CONTINUE; +} + +static void updgrp_show_adj(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t id, uint8_t flags) +{ + struct updwalk_context ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.vty = vty; + ctx.subgrp_id = id; + ctx.flags = flags; + + update_group_af_walk(bgp, afi, safi, updgrp_show_adj_walkcb, &ctx); +} + +static void subgroup_coalesce_timer(struct event *thread) +{ + struct update_subgroup *subgrp; + struct bgp *bgp; + safi_t safi; + + subgrp = EVENT_ARG(thread); + if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" announcing routes upon coalesce timer expiry(%u ms)", + (SUBGRP_UPDGRP(subgrp))->id, subgrp->id, + subgrp->v_coalesce); + subgrp->t_coalesce = NULL; + subgrp->v_coalesce = 0; + bgp = SUBGRP_INST(subgrp); + subgroup_announce_route(subgrp); + safi = SUBGRP_SAFI(subgrp); + + /* While the announce_route() may kick off the route advertisement timer + * for + * the members of the subgroup, we'd like to send the initial updates + * much + * faster (i.e., without enforcing MRAI). Also, if there were no routes + * to + * announce, this is the method currently employed to trigger the EOR. + */ + if (!bgp_update_delay_active(SUBGRP_INST(subgrp)) && + !(bgp_fibupd_safi(safi) && BGP_SUPPRESS_FIB_ENABLED(bgp))) { + + struct peer_af *paf; + struct peer *peer; + + SUBGRP_FOREACH_PEER (subgrp, paf) { + peer = PAF_PEER(paf); + struct peer_connection *connection = peer->connection; + + EVENT_OFF(connection->t_routeadv); + BGP_TIMER_ON(connection->t_routeadv, bgp_routeadv_timer, + 0); + } + } +} + +static int update_group_announce_walkcb(struct update_group *updgrp, void *arg) +{ + struct update_subgroup *subgrp; + + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + /* Avoid supressing duplicate routes later + * when processing in subgroup_announce_table(). + */ + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES); + + subgroup_announce_all(subgrp); + } + + return UPDWALK_CONTINUE; +} + +static int update_group_announce_rrc_walkcb(struct update_group *updgrp, + void *arg) +{ + struct update_subgroup *subgrp; + afi_t afi; + safi_t safi; + struct peer *peer; + + afi = UPDGRP_AFI(updgrp); + safi = UPDGRP_SAFI(updgrp); + peer = UPDGRP_PEER(updgrp); + + /* Only announce if this is a group of route-reflector-clients */ + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_REFLECTOR_CLIENT)) { + UPDGRP_FOREACH_SUBGRP (updgrp, subgrp) { + subgroup_announce_all(subgrp); + } + } + + return UPDWALK_CONTINUE; +} + +/******************** + * PUBLIC FUNCTIONS + ********************/ + +/** + * Allocate an adj-out object. Do proper initialization of its fields, + * primarily its association with the subgroup and the prefix. + */ +struct bgp_adj_out *bgp_adj_out_alloc(struct update_subgroup *subgrp, + struct bgp_dest *dest, + uint32_t addpath_tx_id) +{ + struct bgp_adj_out *adj; + + adj = XCALLOC(MTYPE_BGP_ADJ_OUT, sizeof(struct bgp_adj_out)); + adj->subgroup = subgrp; + adj->addpath_tx_id = addpath_tx_id; + + RB_INSERT(bgp_adj_out_rb, &dest->adj_out, adj); + bgp_dest_lock_node(dest); + adj->dest = dest; + + TAILQ_INSERT_TAIL(&(subgrp->adjq), adj, subgrp_adj_train); + SUBGRP_INCR_STAT(subgrp, adj_count); + return adj; +} + + +struct bgp_advertise * +bgp_advertise_clean_subgroup(struct update_subgroup *subgrp, + struct bgp_adj_out *adj) +{ + struct bgp_advertise *adv; + struct bgp_advertise_attr *baa; + struct bgp_advertise *next; + struct bgp_adv_fifo_head *fhead; + + adv = adj->adv; + baa = adv->baa; + next = NULL; + + if (baa) { + fhead = &subgrp->sync->update; + + /* Unlink myself from advertise attribute FIFO. */ + bgp_advertise_delete(baa, adv); + + /* Fetch next advertise candidate. */ + next = bgp_advertise_attr_fifo_first(&baa->fifo); + + /* Unintern BGP advertise attribute. */ + bgp_advertise_attr_unintern(subgrp->hash, baa); + } else + fhead = &subgrp->sync->withdraw; + + + /* Unlink myself from advertisement FIFO. */ + bgp_adv_fifo_del(fhead, adv); + + /* Free memory. */ + bgp_advertise_free(adj->adv); + adj->adv = NULL; + + return next; +} + +bool bgp_adj_out_set_subgroup(struct bgp_dest *dest, + struct update_subgroup *subgrp, struct attr *attr, + struct bgp_path_info *path) +{ + struct bgp_adj_out *adj = NULL; + struct bgp_advertise *adv; + struct peer *peer; + afi_t afi; + safi_t safi; + struct peer *adv_peer; + struct peer_af *paf; + struct bgp *bgp; + uint32_t attr_hash = 0; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + bgp = SUBGRP_INST(subgrp); + + if (DISABLE_BGP_ANNOUNCE) + return false; + + /* Look for adjacency information. */ + adj = adj_lookup( + dest, subgrp, + bgp_addpath_id_for_peer(peer, afi, safi, &path->tx_addpath)); + + if (adj) { + if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING)) + subgrp->pscount++; + } else { + adj = bgp_adj_out_alloc( + subgrp, dest, + bgp_addpath_id_for_peer(peer, afi, safi, + &path->tx_addpath)); + if (!adj) + return false; + + subgrp->pscount++; + } + + /* Check if we are sending the same route. This is needed to + * avoid duplicate UPDATES. For instance, filtering communities + * at egress, neighbors will see duplicate UPDATES despite + * the route wasn't changed actually. + * Do not suppress BGP UPDATES for route-refresh. + */ + if (likely(CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES))) + attr_hash = attrhash_key_make(attr); + + if (!CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES) && + attr_hash && adj->attr_hash == attr_hash && + bgp_labels_cmp(path->extra ? path->extra->labels : NULL, + adj->labels)) { + if (BGP_DEBUG(update, UPDATE_OUT)) { + char attr_str[BUFSIZ] = {0}; + + bgp_dump_attr(attr, attr_str, sizeof(attr_str)); + + zlog_debug("%s suppress UPDATE w/ attr: %s", peer->host, + attr_str); + } + + /* + * If BGP is skipping sending this value to it's peers + * the version number should be updated just like it + * would if it sent the data. Why? Because update + * groups will not be coalesced until such time that + * the version numbers are the same. + * + * Imagine a scenario with say 2 peers and they come + * up and are placed in the same update group. Then + * a new peer comes up a bit later. Then a prefix is + * flapped that we decide for the first 2 peers are + * mapped to and we decide not to send the data to + * it. Then unless more network changes happen we + * will never be able to coalesce the 3rd peer down + */ + subgrp->version = MAX(subgrp->version, dest->version); + return false; + } + + if (adj->adv) + bgp_advertise_clean_subgroup(subgrp, adj); + adj->adv = bgp_advertise_new(); + + adv = adj->adv; + adv->dest = dest; + assert(adv->pathi == NULL); + /* bgp_path_info adj_out reference */ + adv->pathi = bgp_path_info_lock(path); + + adv->baa = bgp_advertise_attr_intern(subgrp->hash, attr); + adv->adj = adj; + adj->attr_hash = attr_hash; + if (path->extra) + adj->labels = bgp_labels_intern(path->extra->labels); + else + adj->labels = NULL; + + /* Add new advertisement to advertisement attribute list. */ + bgp_advertise_add(adv->baa, adv); + + /* + * If the update adv list is empty, trigger the member peers' + * mrai timers so the socket writes can happen. + */ + if (!bgp_adv_fifo_count(&subgrp->sync->update)) { + SUBGRP_FOREACH_PEER (subgrp, paf) { + /* If there are no routes in the withdraw list, set + * the flag PEER_STATUS_ADV_DELAY which will allow + * more routes to be sent in the update message + */ + if (bgp_fibupd_safi(safi) && + BGP_SUPPRESS_FIB_ENABLED(bgp)) { + adv_peer = PAF_PEER(paf); + if (!bgp_adv_fifo_count( + &subgrp->sync->withdraw)) + SET_FLAG(adv_peer->thread_flags, + PEER_THREAD_SUBGRP_ADV_DELAY); + else + UNSET_FLAG(adv_peer->thread_flags, + PEER_THREAD_SUBGRP_ADV_DELAY); + } + bgp_adjust_routeadv(PAF_PEER(paf)); + } + } + + bgp_adv_fifo_add_tail(&subgrp->sync->update, adv); + + subgrp->version = MAX(subgrp->version, dest->version); + + return true; +} + +/* The only time 'withdraw' will be false is if we are sending + * the "neighbor x.x.x.x default-originate" default and need to clear + * bgp_adj_out for the 0.0.0.0/0 route in the BGP table. + */ +void bgp_adj_out_unset_subgroup(struct bgp_dest *dest, + struct update_subgroup *subgrp, char withdraw, + uint32_t addpath_tx_id) +{ + struct bgp_adj_out *adj; + struct bgp_advertise *adv; + bool trigger_write; + + if (DISABLE_BGP_ANNOUNCE) + return; + + /* Lookup existing adjacency */ + adj = adj_lookup(dest, subgrp, addpath_tx_id); + if (adj != NULL) { + /* Clean up previous advertisement. */ + if (adj->adv) + bgp_advertise_clean_subgroup(subgrp, adj); + + /* If default originate is enabled and the route is default + * route, do not send withdraw. This will prevent deletion of + * the default route at the peer. + */ + if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE) + && is_default_prefix(bgp_dest_get_prefix(dest))) + return; + + if (adj->attr && withdraw) { + /* We need advertisement structure. */ + adj->adv = bgp_advertise_new(); + adv = adj->adv; + adv->dest = dest; + adv->adj = adj; + + /* Note if we need to trigger a packet write */ + trigger_write = + !bgp_adv_fifo_count(&subgrp->sync->withdraw); + + /* Add to synchronization entry for withdraw + * announcement. */ + bgp_adv_fifo_add_tail(&subgrp->sync->withdraw, adv); + + if (trigger_write) + subgroup_trigger_write(subgrp); + } else { + /* Free allocated information. */ + adj_free(adj); + } + if (!CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING)) + subgrp->pscount--; + } + + subgrp->version = MAX(subgrp->version, dest->version); +} + +void bgp_adj_out_remove_subgroup(struct bgp_dest *dest, struct bgp_adj_out *adj, + struct update_subgroup *subgrp) +{ + if (adj->attr) + bgp_attr_unintern(&adj->attr); + + if (adj->adv) + bgp_advertise_clean_subgroup(subgrp, adj); + + adj_free(adj); +} + +/* + * Go through all the routes and clean up the adj/adv structures corresponding + * to the subgroup. + */ +void subgroup_clear_table(struct update_subgroup *subgrp) +{ + struct bgp_adj_out *aout, *taout; + + SUBGRP_FOREACH_ADJ_SAFE (subgrp, aout, taout) + bgp_adj_out_remove_subgroup(aout->dest, aout, subgrp); +} + +/* + * subgroup_announce_table + */ +void subgroup_announce_table(struct update_subgroup *subgrp, + struct bgp_table *table) +{ + struct bgp_dest *dest; + struct bgp_path_info *ri; + struct peer *peer; + afi_t afi; + safi_t safi; + safi_t safi_rib; + bool addpath_capable; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + + if (safi == SAFI_LABELED_UNICAST) + safi_rib = SAFI_UNICAST; + else + safi_rib = safi; + + if (!table) + table = peer->bgp->rib[afi][safi_rib]; + + if (safi != SAFI_MPLS_VPN && safi != SAFI_ENCAP && safi != SAFI_EVPN + && CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) + subgroup_default_originate(subgrp, false); + + subgrp->pscount = 0; + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING); + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + + if (addpath_capable) + subgrp_announce_addpath_best_selected(dest, subgrp); + + for (ri = bgp_dest_get_bgp_path_info(dest); ri; ri = ri->next) { + + if (!bgp_check_selected(ri, peer, addpath_capable, afi, + safi_rib)) + continue; + + /* If default originate is enabled for + * the peer, do not send explicit + * withdraw. This will prevent deletion + * of default route advertised through + * default originate + */ + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE) && + is_default_prefix(bgp_dest_get_prefix(dest))) + break; + + if (CHECK_FLAG(ri->flags, BGP_PATH_SELECTED)) + subgroup_process_announce_selected( + subgrp, ri, dest, afi, safi_rib, + bgp_addpath_id_for_peer( + peer, afi, safi_rib, + &ri->tx_addpath)); + } + } + UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING); + + /* + * We walked through the whole table -- make sure our version number + * is consistent with the one on the table. This should allow + * subgroups to merge sooner if a peer comes up when the route node + * with the largest version is no longer in the table. This also + * covers the pathological case where all routes in the table have + * now been deleted. + */ + subgrp->version = MAX(subgrp->version, table->version); + + /* + * Start a task to merge the subgroup if necessary. + */ + update_subgroup_trigger_merge_check(subgrp, 0); +} + +/* + * subgroup_announce_route + * + * Refresh all routes out to a subgroup. + */ +void subgroup_announce_route(struct update_subgroup *subgrp) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct peer *onlypeer; + + if (update_subgroup_needs_refresh(subgrp)) { + update_subgroup_set_needs_refresh(subgrp, 0); + } + + /* + * First update is deferred until ORF or ROUTE-REFRESH is received + */ + onlypeer = ((SUBGRP_PCOUNT(subgrp) == 1) ? (SUBGRP_PFIRST(subgrp))->peer + : NULL); + if (onlypeer && CHECK_FLAG(onlypeer->af_sflags[SUBGRP_AFI(subgrp)] + [SUBGRP_SAFI(subgrp)], + PEER_STATUS_ORF_WAIT_REFRESH)) + return; + + if (SUBGRP_SAFI(subgrp) != SAFI_MPLS_VPN + && SUBGRP_SAFI(subgrp) != SAFI_ENCAP + && SUBGRP_SAFI(subgrp) != SAFI_EVPN) + subgroup_announce_table(subgrp, NULL); + else + for (dest = bgp_table_top(update_subgroup_rib(subgrp)); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + if (!table) + continue; + subgroup_announce_table(subgrp, table); + } +} + +void subgroup_default_originate(struct update_subgroup *subgrp, bool withdraw) +{ + struct bgp *bgp; + struct attr attr = { 0 }; + struct attr *new_attr = &attr; + struct aspath *aspath; + struct prefix p; + struct peer *from; + struct bgp_dest *dest; + struct bgp_path_info *pi; + struct peer *peer; + struct bgp_adj_out *adj; + route_map_result_t ret = RMAP_DENYMATCH; + route_map_result_t new_ret = RMAP_DENYMATCH; + afi_t afi; + safi_t safi; + safi_t safi_rib; + int pref = 65536; + int new_pref = 0; + + if (!subgrp) + return; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + + if (!(afi == AFI_IP || afi == AFI_IP6)) + return; + + if (safi == SAFI_LABELED_UNICAST) + safi_rib = SAFI_UNICAST; + else + safi_rib = safi; + + bgp = peer->bgp; + from = bgp->peer_self; + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_IGP); + + /* make coverity happy */ + assert(attr.aspath); + + aspath = attr.aspath; + attr.med = 0; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + + if ((afi == AFI_IP6) || peer_cap_enhe(peer, afi, safi)) { + /* IPv6 global nexthop must be included. */ + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + + /* If the peer is on shared nextwork and we have link-local + nexthop set it. */ + if (peer->shared_network + && !IN6_IS_ADDR_UNSPECIFIED(&peer->nexthop.v6_local)) + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL; + } + + if (peer->default_rmap[afi][safi].name) { + struct bgp_path_info tmp_pi = {0}; + + tmp_pi.peer = bgp->peer_self; + + SET_FLAG(bgp->peer_self->rmap_type, PEER_RMAP_TYPE_DEFAULT); + + /* Iterate over the RIB to see if we can announce + * the default route. We announce the default + * route only if route-map has a match. + */ + for (dest = bgp_table_top(bgp->rib[afi][safi_rib]); dest; + dest = bgp_route_next(dest)) { + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + struct attr tmp_attr = attr; + + tmp_pi.attr = &tmp_attr; + + new_ret = route_map_apply_ext( + peer->default_rmap[afi][safi].map, + bgp_dest_get_prefix(dest), pi, &tmp_pi, + &new_pref); + + if (new_ret == RMAP_PERMITMATCH) { + if (new_pref < pref) { + pref = new_pref; + bgp_attr_flush(new_attr); + new_attr = bgp_attr_intern( + tmp_pi.attr); + } + bgp_attr_flush(tmp_pi.attr); + subgroup_announce_reset_nhop( + (peer_cap_enhe(peer, afi, safi) + ? AF_INET6 + : AF_INET), + new_attr); + ret = new_ret; + } else + bgp_attr_flush(&tmp_attr); + } + } + bgp->peer_self->rmap_type = 0; + + if (ret == RMAP_DENYMATCH) { + /* + * If its a implicit withdraw due to routemap + * deny operation need to set the flag back. + * This is a convertion of update flow to + * withdraw flow. + */ + if (!withdraw && + (!CHECK_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE))) + SET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + withdraw = true; + } + } + + /* Check if the default route is in local BGP RIB which is + * installed through redistribute or network command + */ + memset(&p, 0, sizeof(p)); + p.family = afi2family(afi); + p.prefixlen = 0; + dest = bgp_safi_node_lookup(bgp->rib[afi][safi_rib], safi_rib, &p, + NULL); + + if (withdraw) { + /* Withdraw the default route advertised using default + * originate + */ + if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE)) + subgroup_default_withdraw_packet(subgrp); + UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_DEFAULT_ORIGINATE); + + /* If default route is present in the local RIB, advertise the + * route + */ + if (dest) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) { + if (!CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + continue; + + if (subgroup_announce_check(dest, pi, subgrp, + bgp_dest_get_prefix( + dest), + &attr, NULL)) { + if (!bgp_adj_out_set_subgroup(dest, + subgrp, + &attr, pi)) + bgp_attr_flush(&attr); + } else + bgp_attr_flush(&attr); + } + bgp_dest_unlock_node(dest); + } + } else { + if (!CHECK_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) { + + /* The 'neighbor x.x.x.x default-originate' default will + * act as an + * implicit withdraw for any previous UPDATEs sent for + * 0.0.0.0/0 so + * clear adj_out for the 0.0.0.0/0 prefix in the BGP + * table. + */ + if (dest) { + /* Remove the adjacency for the previously + * advertised default route + */ + adj = adj_lookup( + dest, subgrp, + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + if (adj != NULL) { + /* Clean up previous advertisement. */ + if (adj->adv) + bgp_advertise_clean_subgroup( + subgrp, adj); + + /* Free allocated information. */ + adj_free(adj); + } + bgp_dest_unlock_node(dest); + } + + /* Advertise the default route */ + if (bgp_in_graceful_shutdown(bgp)) + bgp_attr_add_gshut_community(new_attr); + + SET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + subgroup_default_update_packet(subgrp, new_attr, from); + } + } + + aspath_unintern(&aspath); +} + +/* + * Announce the BGP table to a subgroup. + * + * At startup, we try to optimize route announcement by coalescing the + * peer-up events. This is done only the first time - from then on, + * subgrp->v_coalesce will be set to zero and the normal logic + * prevails. + */ +void subgroup_announce_all(struct update_subgroup *subgrp) +{ + if (!subgrp) + return; + + /* + * If coalesce timer value is not set, announce routes immediately. + */ + if (!subgrp->v_coalesce) { + if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" announcing all routes", + subgrp->update_group->id, subgrp->id); + subgroup_announce_route(subgrp); + return; + } + + /* + * We should wait for the coalesce timer. Arm the timer if not done. + */ + if (!subgrp->t_coalesce) { + event_add_timer_msec(bm->master, subgroup_coalesce_timer, + subgrp, subgrp->v_coalesce, + &subgrp->t_coalesce); + } +} + +/* + * Go through all update subgroups and set up the adv queue for the + * input route. + */ +void group_announce_route(struct bgp *bgp, afi_t afi, safi_t safi, + struct bgp_dest *dest, struct bgp_path_info *pi) +{ + struct updwalk_context ctx; + ctx.pi = pi; + ctx.dest = dest; + + /* If suppress fib is enabled, the route will be advertised when + * FIB status is received + */ + if (!bgp_check_advertise(bgp, dest, safi)) + return; + + update_group_af_walk(bgp, afi, safi, group_announce_route_walkcb, &ctx); +} + +void update_group_show_adj_queue(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t id) +{ + updgrp_show_adj(bgp, afi, safi, vty, id, UPDWALK_FLAGS_ADVQUEUE); +} + +void update_group_show_advertised(struct bgp *bgp, afi_t afi, safi_t safi, + struct vty *vty, uint64_t id) +{ + updgrp_show_adj(bgp, afi, safi, vty, id, UPDWALK_FLAGS_ADVERTISED); +} + +void update_group_announce(struct bgp *bgp) +{ + update_group_walk(bgp, update_group_announce_walkcb, NULL); +} + +void update_group_announce_rrclients(struct bgp *bgp) +{ + update_group_walk(bgp, update_group_announce_rrc_walkcb, NULL); +} diff --git a/bgpd/bgp_updgrp_packet.c b/bgpd/bgp_updgrp_packet.c new file mode 100644 index 0000000..534bb75 --- /dev/null +++ b/bgpd/bgp_updgrp_packet.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bgp_updgrp_packet.c: BGP update group packet handling routines + * + * @copyright Copyright (C) 2014 Cumulus Networks, Inc. + * + * @author Avneesh Sachdev + * @author Rajesh Varadarajan + * @author Pradosh Mohapatra + */ + +#include + +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "command.h" +#include "sockunion.h" +#include "network.h" +#include "memory.h" +#include "filter.h" +#include "routemap.h" +#include "log.h" +#include "plist.h" +#include "linklist.h" +#include "workqueue.h" +#include "hash.h" +#include "queue.h" +#include "mpls.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_label.h" +#include "bgpd/bgp_addpath.h" + +/******************** + * PRIVATE FUNCTIONS + ********************/ + +/******************** + * PUBLIC FUNCTIONS + ********************/ +struct bpacket *bpacket_alloc(void) +{ + struct bpacket *pkt; + + pkt = XCALLOC(MTYPE_BGP_PACKET, sizeof(struct bpacket)); + + return pkt; +} + +void bpacket_free(struct bpacket *pkt) +{ + if (pkt->buffer) + stream_free(pkt->buffer); + pkt->buffer = NULL; + XFREE(MTYPE_BGP_PACKET, pkt); +} + +void bpacket_queue_init(struct bpacket_queue *q) +{ + TAILQ_INIT(&(q->pkts)); +} + +/* + * bpacket_queue_add_packet + * + * Internal function of bpacket_queue - and adds a + * packet entry to the end of the list. + * + * Users of bpacket_queue should use bpacket_queue_add instead. + */ +static void bpacket_queue_add_packet(struct bpacket_queue *q, + struct bpacket *pkt) +{ + struct bpacket *last_pkt; + + if (TAILQ_EMPTY(&(q->pkts))) + TAILQ_INSERT_TAIL(&(q->pkts), pkt, pkt_train); + else { + last_pkt = bpacket_queue_last(q); + TAILQ_INSERT_AFTER(&(q->pkts), last_pkt, pkt, pkt_train); + } + q->curr_count++; + if (q->hwm_count < q->curr_count) + q->hwm_count = q->curr_count; +} + +/* + * Adds a packet to the bpacket_queue. + * + * The stream passed is consumed by this function. So, the caller should + * not free or use the stream after + * invoking this function. + */ +struct bpacket *bpacket_queue_add(struct bpacket_queue *q, struct stream *s, + struct bpacket_attr_vec_arr *vecarrp) +{ + struct bpacket *pkt; + struct bpacket *last_pkt; + + + pkt = bpacket_alloc(); + if (TAILQ_EMPTY(&(q->pkts))) { + pkt->ver = 1; + pkt->buffer = s; + if (vecarrp) + memcpy(&pkt->arr, vecarrp, + sizeof(struct bpacket_attr_vec_arr)); + else + bpacket_attr_vec_arr_reset(&pkt->arr); + bpacket_queue_add_packet(q, pkt); + return pkt; + } + + /* + * Fill in the new information into the current sentinel and create a + * new sentinel. + */ + last_pkt = bpacket_queue_last(q); + assert(last_pkt->buffer == NULL); + last_pkt->buffer = s; + if (vecarrp) + memcpy(&last_pkt->arr, vecarrp, + sizeof(struct bpacket_attr_vec_arr)); + else + bpacket_attr_vec_arr_reset(&last_pkt->arr); + + pkt->ver = last_pkt->ver; + pkt->ver++; + bpacket_queue_add_packet(q, pkt); + + return last_pkt; +} + +struct bpacket *bpacket_queue_first(struct bpacket_queue *q) +{ + return (TAILQ_FIRST(&(q->pkts))); +} + +struct bpacket *bpacket_queue_last(struct bpacket_queue *q) +{ + return TAILQ_LAST(&(q->pkts), pkt_queue); +} + +struct bpacket *bpacket_queue_remove(struct bpacket_queue *q) +{ + struct bpacket *first; + + first = bpacket_queue_first(q); + if (first) { + TAILQ_REMOVE(&(q->pkts), first, pkt_train); + q->curr_count--; + } + return first; +} + +unsigned int bpacket_queue_length(struct bpacket_queue *q) +{ + return q->curr_count - 1; +} + +unsigned int bpacket_queue_hwm_length(struct bpacket_queue *q) +{ + return q->hwm_count - 1; +} + +bool bpacket_queue_is_full(struct bgp *bgp, struct bpacket_queue *q) +{ + if (q->curr_count >= bgp->default_subgroup_pkt_queue_max) + return true; + return false; +} + +void bpacket_add_peer(struct bpacket *pkt, struct peer_af *paf) +{ + if (!pkt || !paf) + return; + + LIST_INSERT_HEAD(&(pkt->peers), paf, pkt_train); + paf->next_pkt_to_send = pkt; +} + +/* + * bpacket_queue_cleanup + */ +void bpacket_queue_cleanup(struct bpacket_queue *q) +{ + struct bpacket *pkt; + + while ((pkt = bpacket_queue_remove(q))) { + bpacket_free(pkt); + } +} + +/* + * bpacket_queue_compact + * + * Delete packets that do not need to be transmitted to any peer from + * the queue. + * + * @return the number of packets deleted. + */ +static int bpacket_queue_compact(struct bpacket_queue *q) +{ + int num_deleted; + struct bpacket *pkt, *removed_pkt; + + num_deleted = 0; + + while (1) { + pkt = bpacket_queue_first(q); + if (!pkt) + break; + + /* + * Don't delete the sentinel. + */ + if (!pkt->buffer) + break; + + if (!LIST_EMPTY(&(pkt->peers))) + break; + + removed_pkt = bpacket_queue_remove(q); + assert(pkt == removed_pkt); + bpacket_free(removed_pkt); + + num_deleted++; + } + + return num_deleted; +} + +void bpacket_queue_advance_peer(struct peer_af *paf) +{ + struct bpacket *pkt; + struct bpacket *old_pkt; + + old_pkt = paf->next_pkt_to_send; + if (old_pkt->buffer == NULL) + /* Already at end of list */ + return; + + LIST_REMOVE(paf, pkt_train); + pkt = TAILQ_NEXT(old_pkt, pkt_train); + bpacket_add_peer(pkt, paf); + + if (!bpacket_queue_compact(PAF_PKTQ(paf))) + return; + + /* + * Deleted one or more packets. Check if we can now merge this + * peer's subgroup into another subgroup. + */ + update_subgroup_check_merge(paf->subgroup, "advanced peer in queue"); +} + +/* + * bpacket_queue_remove_peer + * + * Remove the peer from the packet queue of the subgroup it belongs + * to. + */ +void bpacket_queue_remove_peer(struct peer_af *paf) +{ + struct bpacket_queue *q; + + q = PAF_PKTQ(paf); + assert(q); + + LIST_REMOVE(paf, pkt_train); + paf->next_pkt_to_send = NULL; + + bpacket_queue_compact(q); +} + +unsigned int bpacket_queue_virtual_length(struct peer_af *paf) +{ + struct bpacket *pkt; + struct bpacket *last; + struct bpacket_queue *q; + + pkt = paf->next_pkt_to_send; + if (!pkt || (pkt->buffer == NULL)) + /* Already at end of list */ + return 0; + + q = PAF_PKTQ(paf); + if (TAILQ_EMPTY(&(q->pkts))) + return 0; + + last = TAILQ_LAST(&(q->pkts), pkt_queue); + if (last->ver >= pkt->ver) + return last->ver - pkt->ver; + + /* sequence # rolled over */ + return (UINT_MAX - pkt->ver + 1) + last->ver; +} + +/* + * Dump the bpacket queue + */ +void bpacket_queue_show_vty(struct bpacket_queue *q, struct vty *vty) +{ + struct bpacket *pkt; + struct peer_af *paf; + + pkt = bpacket_queue_first(q); + while (pkt) { + vty_out(vty, " Packet %p ver %u buffer %p\n", pkt, pkt->ver, + pkt->buffer); + + LIST_FOREACH (paf, &(pkt->peers), pkt_train) { + vty_out(vty, " - %s\n", paf->peer->host); + } + pkt = bpacket_next(pkt); + } + return; +} + +struct stream *bpacket_reformat_for_peer(struct bpacket *pkt, + struct peer_af *paf) +{ + struct stream *s = NULL; + bpacket_attr_vec *vec; + struct peer *peer; + struct bgp_filter *filter; + + s = stream_dup(pkt->buffer); + peer = PAF_PEER(paf); + + vec = &pkt->arr.entries[BGP_ATTR_VEC_NH]; + + if (!CHECK_FLAG(vec->flags, BPKT_ATTRVEC_FLAGS_UPDATED)) + return s; + + uint8_t nhlen; + afi_t nhafi; + int route_map_sets_nh; + + nhlen = stream_getc_from(s, vec->offset); + filter = &peer->filter[paf->afi][paf->safi]; + + if (peer_cap_enhe(peer, paf->afi, paf->safi)) + nhafi = AFI_IP6; + else + nhafi = BGP_NEXTHOP_AFI_FROM_NHLEN(nhlen); + + if (nhafi == AFI_IP) { + struct in_addr v4nh, *mod_v4nh; + int nh_modified = 0; + size_t offset_nh = vec->offset + 1; + + route_map_sets_nh = + (CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED) || + CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_VPNV4_NH_CHANGED) || + CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)); + + switch (nhlen) { + case BGP_ATTR_NHLEN_IPV4: + break; + case BGP_ATTR_NHLEN_VPNV4: + offset_nh += 8; + break; + default: + /* TODO: handle IPv6 nexthops */ + flog_warn( + EC_BGP_INVALID_NEXTHOP_LENGTH, + "%s: %s: invalid MP nexthop length (AFI IP): %u", + __func__, peer->host, nhlen); + stream_free(s); + return NULL; + } + + stream_get_from(&v4nh, s, offset_nh, IPV4_MAX_BYTELEN); + mod_v4nh = &v4nh; + + /* + * If route-map has set the nexthop, that is normally + * used; if it is specified as peer-address, the peering + * address is picked up. Otherwise, if NH is unavailable + * from attribute, the peering addr is picked up; the + * "NH unavailable" case also covers next-hop-self and + * some other scenarios - see subgroup_announce_check(). + * In all other cases, use the nexthop carried in the + * attribute unless it is EBGP non-multiaccess and there + * is no next-hop-unchanged setting or the peer is EBGP + * and the route-map that changed the next-hop value + * was applied inbound rather than outbound. Updates to + * an EBGP peer should only modify the next-hop if it + * was set in an outbound route-map to that peer. + * Note: It is assumed route-map cannot set the nexthop + * to an invalid value. + */ + if (route_map_sets_nh + && ((peer->sort != BGP_PEER_EBGP) + || ROUTE_MAP_OUT(filter))) { + if (CHECK_FLAG( + vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)) { + mod_v4nh = &peer->nexthop.v4; + nh_modified = 1; + } + } else if (v4nh.s_addr == INADDR_ANY) { + mod_v4nh = &peer->nexthop.v4; + nh_modified = 1; + } else if (peer->sort == BGP_PEER_EBGP + && (bgp_multiaccess_check_v4(v4nh, peer) == 0) + && !CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED) + && !peer_af_flag_check( + peer, paf->afi, paf->safi, + PEER_FLAG_NEXTHOP_UNCHANGED)) { + /* NOTE: not handling case where NH has new AFI + */ + mod_v4nh = &peer->nexthop.v4; + nh_modified = 1; + } + + if (nh_modified) /* allow for VPN RD */ + stream_put_in_addr_at(s, offset_nh, mod_v4nh); + + if (bgp_debug_update(peer, NULL, NULL, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %s send UPDATE w/ nexthop %pI4%s", + PAF_SUBGRP(paf)->update_group->id, + PAF_SUBGRP(paf)->id, peer->host, mod_v4nh, + (nhlen == BGP_ATTR_NHLEN_VPNV4 ? " and RD" + : "")); + } else if (nhafi == AFI_IP6) { + struct in6_addr v6nhglobal, *mod_v6nhg; + struct in6_addr v6nhlocal, *mod_v6nhl; + int gnh_modified, lnh_modified; + size_t offset_nhglobal = vec->offset + 1; + size_t offset_nhlocal = vec->offset + 1; + + gnh_modified = lnh_modified = 0; + mod_v6nhg = &v6nhglobal; + mod_v6nhl = &v6nhlocal; + + route_map_sets_nh = + (CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED) || + CHECK_FLAG( + vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_VPNV6_GNH_CHANGED) || + CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)); + + /* + * The logic here is rather similar to that for IPv4, the + * additional work being to handle 1 or 2 nexthops. + * Also, 3rd party nexthop is not propagated for EBGP + * right now. + */ + switch (nhlen) { + case BGP_ATTR_NHLEN_IPV6_GLOBAL: + break; + case BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL: + offset_nhlocal += IPV6_MAX_BYTELEN; + break; + case BGP_ATTR_NHLEN_VPNV6_GLOBAL: + offset_nhglobal += 8; + break; + case BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL: + offset_nhglobal += 8; + offset_nhlocal += 8 * 2 + IPV6_MAX_BYTELEN; + break; + default: + /* TODO: handle IPv4 nexthops */ + flog_warn( + EC_BGP_INVALID_NEXTHOP_LENGTH, + "%s: %s: invalid MP nexthop length (AFI IP6): %u", + __func__, peer->host, nhlen); + stream_free(s); + return NULL; + } + + stream_get_from(&v6nhglobal, s, offset_nhglobal, + IPV6_MAX_BYTELEN); + + /* + * Updates to an EBGP peer should only modify the + * next-hop if it was set in an outbound route-map + * to that peer. + */ + if (route_map_sets_nh + && ((peer->sort != BGP_PEER_EBGP) + || ROUTE_MAP_OUT(filter))) { + if (CHECK_FLAG( + vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS)) { + mod_v6nhg = &peer->nexthop.v6_global; + gnh_modified = 1; + } + } else if (IN6_IS_ADDR_UNSPECIFIED(&v6nhglobal)) { + mod_v6nhg = &peer->nexthop.v6_global; + gnh_modified = 1; + } else if ((peer->sort == BGP_PEER_EBGP) + && (!bgp_multiaccess_check_v6(v6nhglobal, peer)) + && !CHECK_FLAG(vec->flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED) + && !peer_af_flag_check( + peer, paf->afi, paf->safi, + PEER_FLAG_NEXTHOP_UNCHANGED)) { + /* NOTE: not handling case where NH has new AFI + */ + mod_v6nhg = &peer->nexthop.v6_global; + gnh_modified = 1; + } + + if (IN6_IS_ADDR_UNSPECIFIED(mod_v6nhg)) { + if (peer->nexthop.v4.s_addr != INADDR_ANY) { + ipv4_to_ipv4_mapped_ipv6(mod_v6nhg, + peer->nexthop.v4); + } + } + + if (IS_MAPPED_IPV6(&peer->nexthop.v6_global)) { + mod_v6nhg = &peer->nexthop.v6_global; + gnh_modified = 1; + } + + if (nhlen == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL + || nhlen == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { + stream_get_from(&v6nhlocal, s, offset_nhlocal, + IPV6_MAX_BYTELEN); + if (IN6_IS_ADDR_UNSPECIFIED(&v6nhlocal)) { + mod_v6nhl = &peer->nexthop.v6_local; + lnh_modified = 1; + } + } + + if (gnh_modified) + stream_put_in6_addr_at(s, offset_nhglobal, mod_v6nhg); + if (lnh_modified) + stream_put_in6_addr_at(s, offset_nhlocal, mod_v6nhl); + + if (bgp_debug_update(peer, NULL, NULL, 0)) { + if (nhlen == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL + || nhlen == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) + zlog_debug( + "u%" PRIu64 ":s%" PRIu64 + " %s send UPDATE w/ mp_nexthops %pI6, %pI6%s", + PAF_SUBGRP(paf)->update_group->id, + PAF_SUBGRP(paf)->id, peer->host, + mod_v6nhg, mod_v6nhl, + (nhlen == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL + ? " and RD" + : "")); + else + zlog_debug( + "u%" PRIu64 ":s%" PRIu64 + " %s send UPDATE w/ mp_nexthop %pI6%s", + PAF_SUBGRP(paf)->update_group->id, + PAF_SUBGRP(paf)->id, peer->host, + mod_v6nhg, + (nhlen == BGP_ATTR_NHLEN_VPNV6_GLOBAL + ? " and RD" + : "")); + } + } else if (paf->afi == AFI_L2VPN) { + struct in_addr v4nh, *mod_v4nh; + int nh_modified = 0; + + stream_get_from(&v4nh, s, vec->offset + 1, 4); + mod_v4nh = &v4nh; + + /* No route-map changes allowed for EVPN nexthops. */ + if (v4nh.s_addr == INADDR_ANY) { + mod_v4nh = &peer->nexthop.v4; + nh_modified = 1; + } + + if (nh_modified) + stream_put_in_addr_at(s, vec->offset + 1, mod_v4nh); + + if (bgp_debug_update(peer, NULL, NULL, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " %s send UPDATE w/ nexthop %pI4", + PAF_SUBGRP(paf)->update_group->id, + PAF_SUBGRP(paf)->id, peer->host, mod_v4nh); + } + + return s; +} + +/* + * Update the vecarr offsets to go beyond 'pos' bytes, i.e. add 'pos' + * to each offset. + */ +static void bpacket_attr_vec_arr_update(struct bpacket_attr_vec_arr *vecarr, + size_t pos) +{ + int i; + + if (!vecarr) + return; + + for (i = 0; i < BGP_ATTR_VEC_MAX; i++) + vecarr->entries[i].offset += pos; +} + +/* + * Return if there are packets to build for this subgroup. + */ +bool subgroup_packets_to_build(struct update_subgroup *subgrp) +{ + struct bgp_advertise *adv; + + if (!subgrp) + return false; + + adv = bgp_adv_fifo_first(&subgrp->sync->withdraw); + if (adv) + return true; + + adv = bgp_adv_fifo_first(&subgrp->sync->update); + if (adv) + return true; + + return false; +} + +/* Make BGP update packet. */ +struct bpacket *subgroup_update_packet(struct update_subgroup *subgrp) +{ + struct bpacket_attr_vec_arr vecarr; + struct bpacket *pkt; + struct peer *peer; + struct stream *s; + struct stream *snlri; + struct stream *packet; + struct bgp_adj_out *adj; + struct bgp_advertise *adv; + struct bgp_dest *dest = NULL; + struct bgp_path_info *path = NULL; + bgp_size_t total_attr_len = 0; + unsigned long attrlen_pos = 0; + size_t mpattrlen_pos = 0; + size_t mpattr_pos = 0; + afi_t afi; + safi_t safi; + int space_remaining = 0; + int space_needed = 0; + char send_attr_str[BUFSIZ]; + int send_attr_printed = 0; + int num_pfx = 0; + bool addpath_capable = false; + int addpath_overhead = 0; + uint32_t addpath_tx_id = 0; + struct prefix_rd *prd = NULL; + mpls_label_t label = MPLS_INVALID_LABEL, *label_pnt = NULL; + uint8_t num_labels = 0; + + if (!subgrp) + return NULL; + + if (bpacket_queue_is_full(SUBGRP_INST(subgrp), SUBGRP_PKTQ(subgrp))) + return NULL; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + s = subgrp->work; + stream_reset(s); + snlri = subgrp->scratch; + stream_reset(snlri); + + bpacket_attr_vec_arr_reset(&vecarr); + + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + addpath_overhead = addpath_capable ? BGP_ADDPATH_ID_LEN : 0; + + adv = bgp_adv_fifo_first(&subgrp->sync->update); + while (adv) { + const struct prefix *dest_p; + + assert(adv->dest); + dest = adv->dest; + dest_p = bgp_dest_get_prefix(dest); + adj = adv->adj; + addpath_tx_id = adj->addpath_tx_id; + path = adv->pathi; + + space_remaining = STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) + - BGP_MAX_PACKET_SIZE_OVERFLOW; + space_needed = + BGP_NLRI_LENGTH + addpath_overhead + + bgp_packet_mpattr_prefix_size(afi, safi, dest_p); + + /* When remaining space can't include NLRI and it's length. */ + if (space_remaining < space_needed) + break; + + /* If packet is empty, set attribute. */ + if (stream_empty(s)) { + struct peer *from = NULL; + + if (path) + from = path->peer; + + /* 1: Write the BGP message header - 16 bytes marker, 2 + * bytes length, + * one byte message type. + */ + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* 2: withdrawn routes length */ + stream_putw(s, 0); + + /* 3: total attributes length - attrlen_pos stores the + * position */ + attrlen_pos = stream_get_endp(s); + stream_putw(s, 0); + + /* 4: if there is MP_REACH_NLRI attribute, that should + * be the first + * attribute, according to + * draft-ietf-idr-error-handling. Save the + * position. + */ + mpattr_pos = stream_get_endp(s); + + /* 5: Encode all the attributes, except MP_REACH_NLRI + * attr. */ + total_attr_len = bgp_packet_attribute( + NULL, peer, s, adv->baa->attr, &vecarr, NULL, + afi, safi, from, NULL, NULL, 0, 0, 0, path); + + space_remaining = + STREAM_CONCAT_REMAIN(s, snlri, STREAM_SIZE(s)) + - BGP_MAX_PACKET_SIZE_OVERFLOW; + space_needed = BGP_NLRI_LENGTH + addpath_overhead + + bgp_packet_mpattr_prefix_size( + afi, safi, dest_p); + + /* If the attributes alone do not leave any room for + * NLRI then + * return */ + if (space_remaining < space_needed) { + flog_err( + EC_BGP_UPDGRP_ATTR_LEN, + "u%" PRIu64 ":s%" PRIu64" attributes too long, cannot send UPDATE", + subgrp->update_group->id, subgrp->id); + + /* Flush the FIFO update queue */ + while (adv) + adv = bgp_advertise_clean_subgroup( + subgrp, adj); + return NULL; + } + + if (BGP_DEBUG(update, UPDATE_OUT) + || BGP_DEBUG(update, UPDATE_PREFIX)) { + memset(send_attr_str, 0, BUFSIZ); + send_attr_printed = 0; + bgp_dump_attr(adv->baa->attr, send_attr_str, + sizeof(send_attr_str)); + } + } + + if ((afi == AFI_IP && safi == SAFI_UNICAST) + && !peer_cap_enhe(peer, afi, safi)) + stream_put_prefix_addpath(s, dest_p, addpath_capable, + addpath_tx_id); + else { + /* Encode the prefix in MP_REACH_NLRI attribute */ + if (dest->pdest) + prd = (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest); + + if (safi == SAFI_LABELED_UNICAST) { + label = bgp_adv_label(dest, path, peer, afi, + safi); + label_pnt = &label; + num_labels = 1; + } else if (safi == SAFI_MPLS_VPN && path && + CHECK_FLAG(path->flags, + BGP_PATH_MPLSVPN_NH_LABEL_BIND) && + path->mplsvpn.bmnc.nh_label_bind_cache && + path->peer && path->peer != peer && + path->sub_type != BGP_ROUTE_IMPORTED && + path->sub_type != BGP_ROUTE_STATIC && + bgp_mplsvpn_path_uses_valid_mpls_label( + path) && + bgp_path_info_nexthop_changed(path, peer, + afi)) { + /* Redistributed mpls vpn route between distinct + * peers from 'pi->peer' to 'to', + * and an mpls label is used in this path, + * and there is a nh label bind entry, + * then get appropriate mpls local label. When + * called here, 'get_label()' returns a valid + * label. + */ + label = bgp_mplsvpn_nh_label_bind_get_label( + path); + label_pnt = &label; + num_labels = 1; + } else { + num_labels = BGP_PATH_INFO_NUM_LABELS(path); + label_pnt = + num_labels + ? &path->extra->labels->label[0] + : NULL; + } + + if (stream_empty(snlri)) + mpattrlen_pos = bgp_packet_mpattr_start( + snlri, peer, afi, safi, &vecarr, + adv->baa->attr); + + bgp_packet_mpattr_prefix(snlri, afi, safi, dest_p, prd, + label_pnt, num_labels, + addpath_capable, addpath_tx_id, + adv->baa->attr); + } + + num_pfx++; + + if (bgp_debug_update(NULL, dest_p, subgrp->update_group, 0)) { + char pfx_buf[BGP_PRD_PATH_STRLEN]; + + if (!send_attr_printed) { + zlog_debug("u%" PRIu64 ":s%" PRIu64" send UPDATE w/ attr: %s", + subgrp->update_group->id, subgrp->id, + send_attr_str); + if (!stream_empty(snlri)) { + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + + pkt_afi = afi_int2iana(afi); + pkt_safi = safi_int2iana(safi); + zlog_debug( + "u%" PRIu64 ":s%" PRIu64 + " send MP_REACH for afi/safi %s/%s", + subgrp->update_group->id, + subgrp->id, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } + + send_attr_printed = 1; + } + + bgp_debug_rdpfxpath2str(afi, safi, prd, dest_p, + label_pnt, num_labels, + addpath_capable, addpath_tx_id, + &adv->baa->attr->evpn_overlay, + pfx_buf, sizeof(pfx_buf)); + zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %s", + subgrp->update_group->id, subgrp->id, + pfx_buf); + } + + /* Synchnorize attribute. */ + if (adj->attr) + bgp_attr_unintern(&adj->attr); + else + subgrp->scount++; + + adj->attr = bgp_attr_intern(adv->baa->attr); + adv = bgp_advertise_clean_subgroup(subgrp, adj); + } + + if (!stream_empty(s)) { + if (!stream_empty(snlri)) { + bgp_packet_mpattr_end(snlri, mpattrlen_pos); + total_attr_len += stream_get_endp(snlri); + } + + /* set the total attribute length correctly */ + stream_putw_at(s, attrlen_pos, total_attr_len); + + if (!stream_empty(snlri)) { + packet = stream_dupcat(s, snlri, mpattr_pos); + bpacket_attr_vec_arr_update(&vecarr, mpattr_pos); + } else + packet = stream_dup(s); + bgp_packet_set_size(packet); + if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) + zlog_debug( + "u%" PRIu64 ":s%" PRIu64 + " send UPDATE len %zd (max message len: %hu) numpfx %d", + subgrp->update_group->id, subgrp->id, + (stream_get_endp(packet) + - stream_get_getp(packet)), + peer->max_packet_size, num_pfx); + pkt = bpacket_queue_add(SUBGRP_PKTQ(subgrp), packet, &vecarr); + stream_reset(s); + stream_reset(snlri); + return pkt; + } + return NULL; +} + +/* Make BGP withdraw packet. */ +/* For ipv4 unicast: + 16-octet marker | 2-octet length | 1-octet type | + 2-octet withdrawn route length | withdrawn prefixes | 2-octet attrlen (=0) +*/ +/* For other afi/safis: + 16-octet marker | 2-octet length | 1-octet type | + 2-octet withdrawn route length (=0) | 2-octet attrlen | + mp_unreach attr type | attr len | afi | safi | withdrawn prefixes +*/ +struct bpacket *subgroup_withdraw_packet(struct update_subgroup *subgrp) +{ + struct bpacket *pkt; + struct stream *s; + struct bgp_adj_out *adj; + struct bgp_advertise *adv; + struct peer *peer; + struct bgp_dest *dest; + bgp_size_t unfeasible_len; + bgp_size_t total_attr_len; + size_t mp_start = 0; + size_t attrlen_pos = 0; + size_t mplen_pos = 0; + uint8_t first_time = 1; + afi_t afi; + safi_t safi; + int space_remaining = 0; + int space_needed = 0; + int num_pfx = 0; + bool addpath_capable = false; + int addpath_overhead = 0; + uint32_t addpath_tx_id = 0; + const struct prefix_rd *prd = NULL; + + + if (!subgrp) + return NULL; + + if (bpacket_queue_is_full(SUBGRP_INST(subgrp), SUBGRP_PKTQ(subgrp))) + return NULL; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + s = subgrp->work; + stream_reset(s); + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + addpath_overhead = addpath_capable ? BGP_ADDPATH_ID_LEN : 0; + + while ((adv = bgp_adv_fifo_first(&subgrp->sync->withdraw)) != NULL) { + const struct prefix *dest_p; + + assert(adv->dest); + adj = adv->adj; + dest = adv->dest; + dest_p = bgp_dest_get_prefix(dest); + addpath_tx_id = adj->addpath_tx_id; + + space_remaining = + STREAM_WRITEABLE(s) - BGP_MAX_PACKET_SIZE_OVERFLOW; + space_needed = + BGP_NLRI_LENGTH + addpath_overhead + BGP_TOTAL_ATTR_LEN + + bgp_packet_mpattr_prefix_size(afi, safi, dest_p); + + if (space_remaining < space_needed) + break; + + if (stream_empty(s)) { + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + stream_putw(s, 0); /* unfeasible routes length */ + } else + first_time = 0; + + if (afi == AFI_IP && safi == SAFI_UNICAST + && !peer_cap_enhe(peer, afi, safi)) + stream_put_prefix_addpath(s, dest_p, addpath_capable, + addpath_tx_id); + else { + if (dest->pdest) + prd = (struct prefix_rd *)bgp_dest_get_prefix( + dest->pdest); + + /* If first time, format the MP_UNREACH header + */ + if (first_time) { + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + + pkt_afi = afi_int2iana(afi); + pkt_safi = safi_int2iana(safi); + + attrlen_pos = stream_get_endp(s); + /* total attr length = 0 for now. + * reevaluate later */ + stream_putw(s, 0); + mp_start = stream_get_endp(s); + mplen_pos = bgp_packet_mpunreach_start(s, afi, + safi); + if (bgp_debug_update(NULL, NULL, + subgrp->update_group, 0)) + zlog_debug( + "u%" PRIu64 ":s%" PRIu64 + " send MP_UNREACH for afi/safi %s/%s", + subgrp->update_group->id, + subgrp->id, + iana_afi2str(pkt_afi), + iana_safi2str(pkt_safi)); + } + + bgp_packet_mpunreach_prefix(s, dest_p, afi, safi, prd, + NULL, 0, addpath_capable, + addpath_tx_id, NULL); + } + + num_pfx++; + + if (bgp_debug_update(NULL, dest_p, subgrp->update_group, 0)) { + char pfx_buf[BGP_PRD_PATH_STRLEN]; + + bgp_debug_rdpfxpath2str(afi, safi, prd, dest_p, NULL, 0, + addpath_capable, addpath_tx_id, + NULL, pfx_buf, sizeof(pfx_buf)); + zlog_debug("u%" PRIu64 ":s%" PRIu64" send UPDATE %s -- unreachable", + subgrp->update_group->id, subgrp->id, + pfx_buf); + } + + subgrp->scount--; + + bgp_adj_out_remove_subgroup(dest, adj, subgrp); + } + + if (!stream_empty(s)) { + if (afi == AFI_IP && safi == SAFI_UNICAST + && !peer_cap_enhe(peer, afi, safi)) { + unfeasible_len = stream_get_endp(s) - BGP_HEADER_SIZE + - BGP_UNFEASIBLE_LEN; + stream_putw_at(s, BGP_HEADER_SIZE, unfeasible_len); + stream_putw(s, 0); + } else { + /* Set the mp_unreach attr's length */ + bgp_packet_mpunreach_end(s, mplen_pos); + + /* Set total path attribute length. */ + total_attr_len = stream_get_endp(s) - mp_start; + stream_putw_at(s, attrlen_pos, total_attr_len); + } + bgp_packet_set_size(s); + if (bgp_debug_update(NULL, NULL, subgrp->update_group, 0)) + zlog_debug("u%" PRIu64 ":s%" PRIu64" send UPDATE (withdraw) len %zd numpfx %d", + subgrp->update_group->id, subgrp->id, + (stream_get_endp(s) - stream_get_getp(s)), + num_pfx); + pkt = bpacket_queue_add(SUBGRP_PKTQ(subgrp), stream_dup(s), + NULL); + stream_reset(s); + return pkt; + } + + return NULL; +} + +void subgroup_default_update_packet(struct update_subgroup *subgrp, + struct attr *attr, struct peer *from) +{ + struct stream *s; + struct peer *peer; + struct prefix p; + unsigned long pos; + bgp_size_t total_attr_len; + afi_t afi; + safi_t safi; + struct bpacket_attr_vec_arr vecarr; + bool addpath_capable = false; + mpls_label_t label = MPLS_LABEL_IMPLICIT_NULL; + uint8_t num_labels = 0; + + if (DISABLE_BGP_ANNOUNCE) + return; + + if (!subgrp) + return; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + bpacket_attr_vec_arr_reset(&vecarr); + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + + if (safi == SAFI_LABELED_UNICAST) { + label = mpls_lse_encode((afi == AFI_IP) + ? MPLS_LABEL_IPV4_EXPLICIT_NULL + : MPLS_LABEL_IPV6_EXPLICIT_NULL, + 0, 0, 1); + bgp_set_valid_label(&label); + num_labels = 1; + } + + memset(&p, 0, sizeof(p)); + p.family = afi2family(afi); + p.prefixlen = 0; + + /* Logging the attribute. */ + if (bgp_debug_update(NULL, &p, subgrp->update_group, 0)) { + char attrstr[BUFSIZ]; + /* ' with addpath ID ' 17 + * max strlen of uint32 + 10 + * +/- (just in case) + 1 + * null terminator + 1 + * ============================ 29 */ + char tx_id_buf[30]; + + attrstr[0] = '\0'; + + bgp_dump_attr(attr, attrstr, sizeof(attrstr)); + + if (addpath_capable) + snprintf(tx_id_buf, sizeof(tx_id_buf), + " with addpath ID %u", + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + else + tx_id_buf[0] = '\0'; + + zlog_debug("u%" PRIu64 ":s%" PRIu64 " send UPDATE %pFX%s %s", + (SUBGRP_UPDGRP(subgrp))->id, subgrp->id, &p, + tx_id_buf, attrstr); + } + + s = stream_new(peer->max_packet_size); + + /* Make BGP update packet. */ + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* Unfeasible Routes Length. */ + stream_putw(s, 0); + + /* Make place for total attribute length. */ + pos = stream_get_endp(s); + stream_putw(s, 0); + total_attr_len = + bgp_packet_attribute(NULL, peer, s, attr, &vecarr, &p, afi, + safi, from, NULL, &label, num_labels, + addpath_capable, + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, + NULL); + + /* Set Total Path Attribute Length. */ + stream_putw_at(s, pos, total_attr_len); + + /* NLRI set. */ + if (p.family == AF_INET && safi == SAFI_UNICAST + && !peer_cap_enhe(peer, afi, safi)) + stream_put_prefix_addpath( + s, &p, addpath_capable, + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + + /* Set size. */ + bgp_packet_set_size(s); + + (void)bpacket_queue_add(SUBGRP_PKTQ(subgrp), s, &vecarr); + subgroup_trigger_write(subgrp); + + if (!CHECK_FLAG(subgrp->sflags, + SUBGRP_STATUS_PEER_DEFAULT_ORIGINATED)) { + subgrp->scount++; + SET_FLAG(subgrp->sflags, SUBGRP_STATUS_PEER_DEFAULT_ORIGINATED); + } +} + +void subgroup_default_withdraw_packet(struct update_subgroup *subgrp) +{ + struct peer *peer; + struct stream *s; + struct prefix p; + unsigned long attrlen_pos = 0; + unsigned long cp; + bgp_size_t unfeasible_len; + bgp_size_t total_attr_len = 0; + size_t mp_start = 0; + size_t mplen_pos = 0; + afi_t afi; + safi_t safi; + bool addpath_capable = false; + + if (DISABLE_BGP_ANNOUNCE) + return; + + peer = SUBGRP_PEER(subgrp); + afi = SUBGRP_AFI(subgrp); + safi = SUBGRP_SAFI(subgrp); + addpath_capable = bgp_addpath_encode_tx(peer, afi, safi); + + memset(&p, 0, sizeof(p)); + p.family = afi2family(afi); + p.prefixlen = 0; + + if (bgp_debug_update(NULL, &p, subgrp->update_group, 0)) { + /* ' with addpath ID ' 17 + * max strlen of uint32 + 10 + * +/- (just in case) + 1 + * null terminator + 1 + * ============================ 29 */ + char tx_id_buf[30]; + + if (addpath_capable) + snprintf(tx_id_buf, sizeof(tx_id_buf), + " with addpath ID %u", + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + + zlog_debug("u%" PRIu64 ":s%" PRIu64 + " send UPDATE %pFX%s -- unreachable", + (SUBGRP_UPDGRP(subgrp))->id, subgrp->id, &p, + tx_id_buf); + } + + s = stream_new(peer->max_packet_size); + + /* Make BGP update packet. */ + bgp_packet_set_marker(s, BGP_MSG_UPDATE); + + /* Unfeasible Routes Length. */; + cp = stream_get_endp(s); + stream_putw(s, 0); + + /* Withdrawn Routes. */ + if (p.family == AF_INET && safi == SAFI_UNICAST + && !peer_cap_enhe(peer, afi, safi)) { + stream_put_prefix_addpath( + s, &p, addpath_capable, + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE); + + unfeasible_len = stream_get_endp(s) - cp - 2; + + /* Set unfeasible len. */ + stream_putw_at(s, cp, unfeasible_len); + + /* Set total path attribute length. */ + stream_putw(s, 0); + } else { + attrlen_pos = stream_get_endp(s); + stream_putw(s, 0); + mp_start = stream_get_endp(s); + mplen_pos = bgp_packet_mpunreach_start(s, afi, safi); + bgp_packet_mpunreach_prefix( + s, &p, afi, safi, NULL, NULL, 0, addpath_capable, + BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE, NULL); + + /* Set the mp_unreach attr's length */ + bgp_packet_mpunreach_end(s, mplen_pos); + + /* Set total path attribute length. */ + total_attr_len = stream_get_endp(s) - mp_start; + stream_putw_at(s, attrlen_pos, total_attr_len); + } + + bgp_packet_set_size(s); + + (void)bpacket_queue_add(SUBGRP_PKTQ(subgrp), s, NULL); + subgroup_trigger_write(subgrp); + + if (CHECK_FLAG(subgrp->sflags, SUBGRP_STATUS_PEER_DEFAULT_ORIGINATED)) { + subgrp->scount--; + UNSET_FLAG(subgrp->sflags, + SUBGRP_STATUS_PEER_DEFAULT_ORIGINATED); + } +} + +static void +bpacket_vec_arr_inherit_attr_flags(struct bpacket_attr_vec_arr *vecarr, + enum bpacket_attr_vec_type type, + struct attr *attr) +{ + if (CHECK_FLAG(attr->rmap_change_flags, + BATTR_RMAP_NEXTHOP_PEER_ADDRESS)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_PEER_ADDRESS); + + if (CHECK_FLAG(attr->rmap_change_flags, BATTR_REFLECTED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_REFLECTED); + + if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_NEXTHOP_UNCHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_NH_UNCHANGED); + + if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_IPV4_NHOP_CHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_IPV4_NH_CHANGED); + + if (CHECK_FLAG(attr->rmap_change_flags, + BATTR_RMAP_IPV6_GLOBAL_NHOP_CHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_IPV6_GNH_CHANGED); + + if (CHECK_FLAG(attr->rmap_change_flags, BATTR_RMAP_VPNV4_NHOP_CHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_VPNV4_NH_CHANGED); + + if (CHECK_FLAG(attr->rmap_change_flags, + BATTR_RMAP_VPNV6_GLOBAL_NHOP_CHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_VPNV6_GNH_CHANGED); + + if (CHECK_FLAG(attr->rmap_change_flags, + BATTR_RMAP_IPV6_LL_NHOP_CHANGED)) + SET_FLAG(vecarr->entries[BGP_ATTR_VEC_NH].flags, + BPKT_ATTRVEC_FLAGS_RMAP_IPV6_LNH_CHANGED); +} + +/* Reset the Attributes vector array. The vector array is used to override + * certain output parameters in the packet for a particular peer + */ +void bpacket_attr_vec_arr_reset(struct bpacket_attr_vec_arr *vecarr) +{ + int i; + + if (!vecarr) + return; + + i = 0; + while (i < BGP_ATTR_VEC_MAX) { + vecarr->entries[i].flags = 0; + vecarr->entries[i].offset = 0; + i++; + } +} + +/* Setup a particular node entry in the vecarr */ +void bpacket_attr_vec_arr_set_vec(struct bpacket_attr_vec_arr *vecarr, + enum bpacket_attr_vec_type type, + struct stream *s, struct attr *attr) +{ + if (!vecarr) + return; + assert(type < BGP_ATTR_VEC_MAX); + + SET_FLAG(vecarr->entries[type].flags, BPKT_ATTRVEC_FLAGS_UPDATED); + vecarr->entries[type].offset = stream_get_endp(s); + if (attr) + bpacket_vec_arr_inherit_attr_flags(vecarr, type, attr); +} diff --git a/bgpd/bgp_vnc_types.h b/bgpd/bgp_vnc_types.h new file mode 100644 index 0000000..b08af9f --- /dev/null +++ b/bgpd/bgp_vnc_types.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015-2016, LabN Consulting, L.L.C. + */ + +#ifndef _QUAGGA_BGP_VNC_TYPES_H +#define _QUAGGA_BGP_VNC_TYPES_H + +#ifdef ENABLE_BGP_VNC +typedef enum { + BGP_VNC_SUBTLV_TYPE_LIFETIME = 1, + BGP_VNC_SUBTLV_TYPE_RFPOPTION = 2, /* deprecated */ +} bgp_vnc_subtlv_types; + +#endif /* ENABLE_BGP_VNC */ +#endif /* _QUAGGA_BGP_VNC_TYPES_H */ diff --git a/bgpd/bgp_vpn.c b/bgpd/bgp_vpn.c new file mode 100644 index 0000000..2470fb8 --- /dev/null +++ b/bgpd/bgp_vpn.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* VPN Related functions + * Copyright (C) 2017 6WIND + * + * This file is part of FRRouting + */ + +#include +#include "command.h" +#include "prefix.h" +#include "lib/json.h" +#include "lib/printfrr.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_vpn.h" +#include "bgpd/bgp_updgrp.h" + +int show_adj_route_vpn(struct vty *vty, struct peer *peer, + struct prefix_rd *prd, afi_t afi, safi_t safi, + bool use_json) +{ + struct bgp *bgp; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_dest *rm; + int rd_header; + int header = 1; + json_object *json = NULL; + json_object *json_adv = NULL; + json_object *json_routes = NULL; + char rd_str[BUFSIZ]; + unsigned long output_count = 0; + + bgp = bgp_get_default(); + if (bgp == NULL) { + if (!use_json) + vty_out(vty, "No BGP process is configured\n"); + else + vty_out(vty, "{}\n"); + return CMD_WARNING; + } + + if (use_json) { + json = json_object_new_object(); + json_adv = json_object_new_object(); + } + + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + + table = bgp_dest_get_bgp_table_info(dest); + if (table == NULL) + continue; + + /* + * Initialize variables for each RD + * All prefixes under an RD is aggregated within "json_routes" + */ + rd_header = 1; + memset(rd_str, 0, sizeof(rd_str)); + json_routes = NULL; + + for (rm = bgp_table_top(table); rm; rm = bgp_route_next(rm)) { + struct bgp_adj_out *adj = NULL; + struct attr *attr = NULL; + struct peer_af *paf = NULL; + + RB_FOREACH (adj, bgp_adj_out_rb, &rm->adj_out) + SUBGRP_FOREACH_PEER (adj->subgroup, paf) { + if (paf->peer != peer || !adj->attr) + continue; + + attr = adj->attr; + break; + } + + if (bgp_dest_get_bgp_path_info(rm) == NULL) + continue; + + if (!attr) + continue; + + if (header) { + if (use_json) { + json_object_int_add( + json, "bgpTableVersion", 0); + json_object_string_addf( + json, "bgpLocalRouterId", + "%pI4", &bgp->router_id); + json_object_int_add( + json, + "defaultLocPrf", + bgp->default_local_pref); + json_object_int_add( + json, "localAS", + bgp->as); + } else { + vty_out(vty, + "BGP table version is 0, local router ID is %pI4\n", + &bgp->router_id); + vty_out(vty, "Default local pref %u, ", + bgp->default_local_pref); + vty_out(vty, "local AS %u\n", bgp->as); + vty_out(vty, + "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal\n"); + vty_out(vty, + "Origin codes: i - IGP, e - EGP, ? - incomplete\n\n"); + vty_out(vty, V4_HEADER); + } + header = 0; + } + + if (rd_header) { + uint16_t type; + struct rd_as rd_as = {0}; + struct rd_ip rd_ip = {0}; +#ifdef ENABLE_BGP_VNC + struct rd_vnc_eth rd_vnc_eth = {0}; +#endif + const uint8_t *pnt; + + pnt = dest_p->u.val; + + /* Decode RD type. */ + type = decode_rd_type(pnt); + /* Decode RD value. */ + if (type == RD_TYPE_AS) + decode_rd_as(pnt + 2, &rd_as); + else if (type == RD_TYPE_AS4) + decode_rd_as4(pnt + 2, &rd_as); + else if (type == RD_TYPE_IP) + decode_rd_ip(pnt + 2, &rd_ip); +#ifdef ENABLE_BGP_VNC + else if (type == RD_TYPE_VNC_ETH) + decode_rd_vnc_eth(pnt, &rd_vnc_eth); +#endif + if (use_json) { + json_routes = json_object_new_object(); + + if (type == RD_TYPE_AS + || type == RD_TYPE_AS4) + snprintf(rd_str, sizeof(rd_str), + "%u:%d", rd_as.as, + rd_as.val); + else if (type == RD_TYPE_IP) + snprintfrr(rd_str, + sizeof(rd_str), + "%pI4:%d", &rd_ip.ip, + rd_ip.val); + json_object_string_add( + json_routes, + "rd", rd_str); + } else { + vty_out(vty, "Route Distinguisher: "); + + if (type == RD_TYPE_AS + || type == RD_TYPE_AS4) + vty_out(vty, "%u:%d", rd_as.as, + rd_as.val); + else if (type == RD_TYPE_IP) + vty_out(vty, "%pI4:%d", + &rd_ip.ip, rd_ip.val); +#ifdef ENABLE_BGP_VNC + else if (type == RD_TYPE_VNC_ETH) + vty_out(vty, + "%u:%02x:%02x:%02x:%02x:%02x:%02x", + rd_vnc_eth.local_nve_id, + rd_vnc_eth.macaddr + .octet[0], + rd_vnc_eth.macaddr + .octet[1], + rd_vnc_eth.macaddr + .octet[2], + rd_vnc_eth.macaddr + .octet[3], + rd_vnc_eth.macaddr + .octet[4], + rd_vnc_eth.macaddr + .octet[5]); +#endif + + vty_out(vty, "\n"); + } + rd_header = 0; + } + route_vty_out_tmp(vty, bgp, rm, bgp_dest_get_prefix(rm), + attr, safi, use_json, json_routes, + false); + output_count++; + } + + if (use_json && json_routes) + json_object_object_add(json_adv, rd_str, json_routes); + } + + if (use_json) { + json_object_object_add(json, "advertisedRoutes", json_adv); + json_object_int_add(json, + "totalPrefixCounter", output_count); + vty_json(vty, json); + } else + vty_out(vty, "\nTotal number of prefixes %ld\n", output_count); + + return CMD_SUCCESS; +} diff --git a/bgpd/bgp_vpn.h b/bgpd/bgp_vpn.h new file mode 100644 index 0000000..dedd0d6 --- /dev/null +++ b/bgpd/bgp_vpn.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* VPN common functions to MP-BGP + * Copyright (C) 2017 6WIND + */ + +#ifndef _FRR_BGP_VPN_H +#define _FRR_BGP_VPN_H + +#include + +extern int show_adj_route_vpn(struct vty *vty, struct peer *peer, + struct prefix_rd *prd, afi_t afi, safi_t safi, + bool use_json); + +#endif /* _QUAGGA_BGP_VPN_H */ diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c new file mode 100644 index 0000000..1a87799 --- /dev/null +++ b/bgpd/bgp_vty.c @@ -0,0 +1,22859 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP VTY interface. + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#include + +#ifdef GNU_LINUX +#include //RT_TABLE_XXX +#endif + +#include "command.h" +#include "lib/json.h" +#include "lib/sockopt.h" +#include "lib_errors.h" +#include "lib/zclient.h" +#include "lib/printfrr.h" +#include "prefix.h" +#include "plist.h" +#include "buffer.h" +#include "linklist.h" +#include "stream.h" +#include "frrevent.h" +#include "log.h" +#include "memory.h" +#include "lib_vty.h" +#include "hash.h" +#include "queue.h" +#include "filter.h" +#include "frrstr.h" +#include "asn.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr_evpn.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_lcommunity.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_io.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_conditional_adv.h" +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#endif + +FRR_CFG_DEFAULT_BOOL(BGP_IMPORT_CHECK, + { + .val_bool = false, + .match_profile = "traditional", + .match_version = "< 7.4", + }, + { .val_bool = true }, +); +FRR_CFG_DEFAULT_BOOL(BGP_SHOW_HOSTNAME, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_BOOL(BGP_SHOW_NEXTHOP_HOSTNAME, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_BOOL(BGP_LOG_NEIGHBOR_CHANGES, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_BOOL(BGP_DETERMINISTIC_MED, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_ULONG(BGP_CONNECT_RETRY, + { .val_ulong = 10, .match_profile = "datacenter", }, + { .val_ulong = 120 }, +); +FRR_CFG_DEFAULT_ULONG(BGP_HOLDTIME, + { .val_ulong = 9, .match_profile = "datacenter", }, + { .val_ulong = 180 }, +); +FRR_CFG_DEFAULT_ULONG(BGP_KEEPALIVE, + { .val_ulong = 3, .match_profile = "datacenter", }, + { .val_ulong = 60 }, +); +FRR_CFG_DEFAULT_BOOL(BGP_EBGP_REQUIRES_POLICY, + { .val_bool = false, .match_profile = "datacenter", }, + { .val_bool = false, .match_version = "< 7.4", }, + { .val_bool = true }, +); +FRR_CFG_DEFAULT_BOOL(BGP_SUPPRESS_DUPLICATES, + { .val_bool = false, .match_version = "< 7.6", }, + { .val_bool = true }, +); +FRR_CFG_DEFAULT_BOOL(BGP_GRACEFUL_NOTIFICATION, + { .val_bool = false, .match_version = "< 8.3", }, + { .val_bool = true }, +); +FRR_CFG_DEFAULT_BOOL(BGP_HARD_ADMIN_RESET, + { .val_bool = false, .match_version = "< 8.3", }, + { .val_bool = true }, +); +FRR_CFG_DEFAULT_BOOL(BGP_SOFT_VERSION_CAPABILITY, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_BOOL(BGP_DYNAMIC_CAPABILITY, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); +FRR_CFG_DEFAULT_BOOL(BGP_ENFORCE_FIRST_AS, + { .val_bool = false, .match_version = "< 9.1", }, + { .val_bool = true }, +); + +DEFINE_HOOK(bgp_inst_config_write, + (struct bgp *bgp, struct vty *vty), + (bgp, vty)); +DEFINE_HOOK(bgp_snmp_update_last_changed, (struct bgp *bgp), (bgp)); +DEFINE_HOOK(bgp_snmp_init_stats, (struct bgp *bgp), (bgp)); +DEFINE_HOOK(bgp_snmp_traps_config_write, (struct vty * vty), (vty)); + +static struct peer_group *listen_range_exists(struct bgp *bgp, + struct prefix *range, int exact); + +/* Show BGP peer's information. */ +enum show_type { + show_all, + show_peer, + show_ipv4_all, + show_ipv6_all, + show_ipv4_peer, + show_ipv6_peer +}; + +static struct peer_group *listen_range_exists(struct bgp *bgp, + struct prefix *range, int exact); + +static void bgp_show_global_graceful_restart_mode_vty(struct vty *vty, + struct bgp *bgp); + +static int bgp_show_neighbor_graceful_restart_afi_all(struct vty *vty, + enum show_type type, + const char *ip_str, + afi_t afi, bool use_json); + +static enum node_type bgp_node_type(afi_t afi, safi_t safi) +{ + switch (afi) { + case AFI_IP: + switch (safi) { + case SAFI_UNICAST: + return BGP_IPV4_NODE; + case SAFI_MULTICAST: + return BGP_IPV4M_NODE; + case SAFI_LABELED_UNICAST: + return BGP_IPV4L_NODE; + case SAFI_MPLS_VPN: + return BGP_VPNV4_NODE; + case SAFI_FLOWSPEC: + return BGP_FLOWSPECV4_NODE; + case SAFI_UNSPEC: + case SAFI_ENCAP: + case SAFI_EVPN: + case SAFI_MAX: + /* not expected */ + return BGP_IPV4_NODE; + } + break; + case AFI_IP6: + switch (safi) { + case SAFI_UNICAST: + return BGP_IPV6_NODE; + case SAFI_MULTICAST: + return BGP_IPV6M_NODE; + case SAFI_LABELED_UNICAST: + return BGP_IPV6L_NODE; + case SAFI_MPLS_VPN: + return BGP_VPNV6_NODE; + case SAFI_FLOWSPEC: + return BGP_FLOWSPECV6_NODE; + case SAFI_UNSPEC: + case SAFI_ENCAP: + case SAFI_EVPN: + case SAFI_MAX: + /* not expected and the return value seems wrong */ + return BGP_IPV4_NODE; + } + break; + case AFI_L2VPN: + return BGP_EVPN_NODE; + case AFI_UNSPEC: + case AFI_MAX: + // We should never be here but to clarify the switch statement.. + return BGP_IPV4_NODE; + } + + // Impossible to happen + return BGP_IPV4_NODE; +} + +static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) +{ + if (afi == AFI_IP) { + if (safi == SAFI_UNICAST) + return "IPv4 Unicast"; + if (safi == SAFI_MULTICAST) + return "IPv4 Multicast"; + if (safi == SAFI_LABELED_UNICAST) + return "IPv4 Labeled Unicast"; + if (safi == SAFI_MPLS_VPN) + return "IPv4 VPN"; + if (safi == SAFI_ENCAP) + return "IPv4 Encap"; + if (safi == SAFI_FLOWSPEC) + return "IPv4 Flowspec"; + } else if (afi == AFI_IP6) { + if (safi == SAFI_UNICAST) + return "IPv6 Unicast"; + if (safi == SAFI_MULTICAST) + return "IPv6 Multicast"; + if (safi == SAFI_LABELED_UNICAST) + return "IPv6 Labeled Unicast"; + if (safi == SAFI_MPLS_VPN) + return "IPv6 VPN"; + if (safi == SAFI_ENCAP) + return "IPv6 Encap"; + if (safi == SAFI_FLOWSPEC) + return "IPv6 Flowspec"; + } else if (afi == AFI_L2VPN) { + if (safi == SAFI_EVPN) + return "L2VPN EVPN"; + } + + return "Unknown"; +} + +/* + * Please note that we have intentionally camelCased + * the return strings here. So if you want + * to use this function, please ensure you + * are doing this within json output + */ +static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) +{ + if (afi == AFI_IP) { + if (safi == SAFI_UNICAST) + return "ipv4Unicast"; + if (safi == SAFI_MULTICAST) + return "ipv4Multicast"; + if (safi == SAFI_LABELED_UNICAST) + return "ipv4LabeledUnicast"; + if (safi == SAFI_MPLS_VPN) + return "ipv4Vpn"; + if (safi == SAFI_ENCAP) + return "ipv4Encap"; + if (safi == SAFI_FLOWSPEC) + return "ipv4Flowspec"; + } else if (afi == AFI_IP6) { + if (safi == SAFI_UNICAST) + return "ipv6Unicast"; + if (safi == SAFI_MULTICAST) + return "ipv6Multicast"; + if (safi == SAFI_LABELED_UNICAST) + return "ipv6LabeledUnicast"; + if (safi == SAFI_MPLS_VPN) + return "ipv6Vpn"; + if (safi == SAFI_ENCAP) + return "ipv6Encap"; + if (safi == SAFI_FLOWSPEC) + return "ipv6Flowspec"; + } else if (afi == AFI_L2VPN) { + if (safi == SAFI_EVPN) + return "l2VpnEvpn"; + } + + return "Unknown"; +} + +/* unset srv6 locator */ +static int bgp_srv6_locator_unset(struct bgp *bgp) +{ + int ret; + struct listnode *node, *nnode; + struct srv6_locator_chunk *chunk; + struct bgp_srv6_function *func; + struct bgp *bgp_vrf; + + /* release chunk notification via ZAPI */ + ret = bgp_zebra_srv6_manager_release_locator_chunk( + bgp->srv6_locator_name); + if (ret < 0) + return -1; + + /* refresh chunks */ + for (ALL_LIST_ELEMENTS(bgp->srv6_locator_chunks, node, nnode, chunk)) { + listnode_delete(bgp->srv6_locator_chunks, chunk); + srv6_locator_chunk_free(&chunk); + } + + /* refresh functions */ + for (ALL_LIST_ELEMENTS(bgp->srv6_functions, node, nnode, func)) { + listnode_delete(bgp->srv6_functions, func); + srv6_function_free(func); + } + + /* refresh tovpn_sid */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + /* refresh vpnv4 tovpn_sid */ + XFREE(MTYPE_BGP_SRV6_SID, + bgp_vrf->vpn_policy[AFI_IP].tovpn_sid); + + /* refresh vpnv6 tovpn_sid */ + XFREE(MTYPE_BGP_SRV6_SID, + bgp_vrf->vpn_policy[AFI_IP6].tovpn_sid); + + /* refresh per-vrf tovpn_sid */ + XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->tovpn_sid); + } + + /* update vpn bgp processes */ + vpn_leak_postchange_all(); + + /* refresh tovpn_sid_locator */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + /* refresh vpnv4 tovpn_sid_locator */ + srv6_locator_chunk_free( + &bgp_vrf->vpn_policy[AFI_IP].tovpn_sid_locator); + + /* refresh vpnv6 tovpn_sid_locator */ + srv6_locator_chunk_free( + &bgp_vrf->vpn_policy[AFI_IP6].tovpn_sid_locator); + + /* refresh per-vrf tovpn_sid_locator */ + srv6_locator_chunk_free(&bgp_vrf->tovpn_sid_locator); + } + + /* clear locator name */ + memset(bgp->srv6_locator_name, 0, sizeof(bgp->srv6_locator_name)); + + return 0; +} + +/* Utility function to get address family from current node. */ +afi_t bgp_node_afi(struct vty *vty) +{ + afi_t afi; + switch (vty->node) { + case BGP_IPV6_NODE: + case BGP_IPV6M_NODE: + case BGP_IPV6L_NODE: + case BGP_VPNV6_NODE: + case BGP_FLOWSPECV6_NODE: + afi = AFI_IP6; + break; + case BGP_EVPN_NODE: + afi = AFI_L2VPN; + break; + default: + afi = AFI_IP; + break; + } + return afi; +} + +/* Utility function to get subsequent address family from current + node. */ +safi_t bgp_node_safi(struct vty *vty) +{ + safi_t safi; + switch (vty->node) { + case BGP_VPNV4_NODE: + case BGP_VPNV6_NODE: + safi = SAFI_MPLS_VPN; + break; + case BGP_IPV4M_NODE: + case BGP_IPV6M_NODE: + safi = SAFI_MULTICAST; + break; + case BGP_EVPN_NODE: + safi = SAFI_EVPN; + break; + case BGP_IPV4L_NODE: + case BGP_IPV6L_NODE: + safi = SAFI_LABELED_UNICAST; + break; + case BGP_FLOWSPECV4_NODE: + case BGP_FLOWSPECV6_NODE: + safi = SAFI_FLOWSPEC; + break; + default: + safi = SAFI_UNICAST; + break; + } + return safi; +} + +/** + * Converts an AFI in string form to afi_t + * + * @param afi string, one of + * - "ipv4" + * - "ipv6" + * - "l2vpn" + * @return the corresponding afi_t + */ +afi_t bgp_vty_afi_from_str(const char *afi_str) +{ + afi_t afi = AFI_MAX; /* unknown */ + if (strmatch(afi_str, "ipv4")) + afi = AFI_IP; + else if (strmatch(afi_str, "ipv6")) + afi = AFI_IP6; + else if (strmatch(afi_str, "l2vpn")) + afi = AFI_L2VPN; + return afi; +} + +int argv_find_and_parse_afi(struct cmd_token **argv, int argc, int *index, + afi_t *afi) +{ + int ret = 0; + if (argv_find(argv, argc, "ipv4", index)) { + ret = 1; + if (afi) + *afi = AFI_IP; + } else if (argv_find(argv, argc, "ipv6", index)) { + ret = 1; + if (afi) + *afi = AFI_IP6; + } else if (argv_find(argv, argc, "l2vpn", index)) { + ret = 1; + if (afi) + *afi = AFI_L2VPN; + } + return ret; +} + +/* supports */ +safi_t bgp_vty_safi_from_str(const char *safi_str) +{ + safi_t safi = SAFI_MAX; /* unknown */ + if (strmatch(safi_str, "multicast")) + safi = SAFI_MULTICAST; + else if (strmatch(safi_str, "unicast")) + safi = SAFI_UNICAST; + else if (strmatch(safi_str, "vpn")) + safi = SAFI_MPLS_VPN; + else if (strmatch(safi_str, "evpn")) + safi = SAFI_EVPN; + else if (strmatch(safi_str, "labeled-unicast")) + safi = SAFI_LABELED_UNICAST; + else if (strmatch(safi_str, "flowspec")) + safi = SAFI_FLOWSPEC; + return safi; +} + +int argv_find_and_parse_safi(struct cmd_token **argv, int argc, int *index, + safi_t *safi) +{ + int ret = 0; + if (argv_find(argv, argc, "unicast", index)) { + ret = 1; + if (safi) + *safi = SAFI_UNICAST; + } else if (argv_find(argv, argc, "multicast", index)) { + ret = 1; + if (safi) + *safi = SAFI_MULTICAST; + } else if (argv_find(argv, argc, "labeled-unicast", index)) { + ret = 1; + if (safi) + *safi = SAFI_LABELED_UNICAST; + } else if (argv_find(argv, argc, "vpn", index)) { + ret = 1; + if (safi) + *safi = SAFI_MPLS_VPN; + } else if (argv_find(argv, argc, "evpn", index)) { + ret = 1; + if (safi) + *safi = SAFI_EVPN; + } else if (argv_find(argv, argc, "flowspec", index)) { + ret = 1; + if (safi) + *safi = SAFI_FLOWSPEC; + } + return ret; +} + +/* + * Convert an afi_t/safi_t pair to matching BGP_DEFAULT_AF* flag. + * + * afi + * address-family identifier + * + * safi + * subsequent address-family identifier + * + * Returns: + * default_af string corresponding to the supplied afi/safi pair. + * If afi/safi is invalid or if flag for afi/safi doesn't exist, + * return -1. + */ +static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) +{ + switch (afi) { + case AFI_IP: + switch (safi) { + case SAFI_UNICAST: + return "ipv4-unicast"; + case SAFI_MULTICAST: + return "ipv4-multicast"; + case SAFI_MPLS_VPN: + return "ipv4-vpn"; + case SAFI_ENCAP: + return "ipv4-encap"; + case SAFI_LABELED_UNICAST: + return "ipv4-labeled-unicast"; + case SAFI_FLOWSPEC: + return "ipv4-flowspec"; + case SAFI_UNSPEC: + case SAFI_EVPN: + case SAFI_MAX: + return "unknown-afi/safi"; + } + break; + case AFI_IP6: + switch (safi) { + case SAFI_UNICAST: + return "ipv6-unicast"; + case SAFI_MULTICAST: + return "ipv6-multicast"; + case SAFI_MPLS_VPN: + return "ipv6-vpn"; + case SAFI_ENCAP: + return "ipv6-encap"; + case SAFI_LABELED_UNICAST: + return "ipv6-labeled-unicast"; + case SAFI_FLOWSPEC: + return "ipv6-flowspec"; + case SAFI_UNSPEC: + case SAFI_EVPN: + case SAFI_MAX: + return "unknown-afi/safi"; + } + break; + case AFI_L2VPN: + switch (safi) { + case SAFI_EVPN: + return "l2vpn-evpn"; + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_MAX: + return "unknown-afi/safi"; + } + break; + case AFI_UNSPEC: + case AFI_MAX: + return "unknown-afi/safi"; + } + /* all AFIs are accounted for above, so this shouldn't happen */ + + assert(!"Reached end of function where we did not expect to"); +} + +int bgp_get_vty(struct bgp **bgp, as_t *as, const char *name, + enum bgp_instance_type inst_type, const char *as_pretty, + enum asnotation_mode asnotation) +{ + int ret = bgp_get(bgp, as, name, inst_type, as_pretty, asnotation); + + if (ret == BGP_CREATED) { + bgp_timers_set(NULL, *bgp, DFLT_BGP_KEEPALIVE, DFLT_BGP_HOLDTIME, + DFLT_BGP_CONNECT_RETRY, BGP_DEFAULT_DELAYOPEN); + + if (DFLT_BGP_IMPORT_CHECK) + SET_FLAG((*bgp)->flags, BGP_FLAG_IMPORT_CHECK); + if (DFLT_BGP_SHOW_HOSTNAME) + SET_FLAG((*bgp)->flags, BGP_FLAG_SHOW_HOSTNAME); + if (DFLT_BGP_SHOW_NEXTHOP_HOSTNAME) + SET_FLAG((*bgp)->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME); + if (DFLT_BGP_LOG_NEIGHBOR_CHANGES) + SET_FLAG((*bgp)->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES); + if (DFLT_BGP_DETERMINISTIC_MED) + SET_FLAG((*bgp)->flags, BGP_FLAG_DETERMINISTIC_MED); + if (DFLT_BGP_EBGP_REQUIRES_POLICY) + SET_FLAG((*bgp)->flags, BGP_FLAG_EBGP_REQUIRES_POLICY); + if (DFLT_BGP_SUPPRESS_DUPLICATES) + SET_FLAG((*bgp)->flags, BGP_FLAG_SUPPRESS_DUPLICATES); + if (DFLT_BGP_GRACEFUL_NOTIFICATION) + SET_FLAG((*bgp)->flags, BGP_FLAG_GRACEFUL_NOTIFICATION); + if (DFLT_BGP_HARD_ADMIN_RESET) + SET_FLAG((*bgp)->flags, BGP_FLAG_HARD_ADMIN_RESET); + if (DFLT_BGP_SOFT_VERSION_CAPABILITY) + SET_FLAG((*bgp)->flags, + BGP_FLAG_SOFT_VERSION_CAPABILITY); + if (DFLT_BGP_DYNAMIC_CAPABILITY) + SET_FLAG((*bgp)->flags, + BGP_FLAG_DYNAMIC_CAPABILITY); + if (DFLT_BGP_ENFORCE_FIRST_AS) + SET_FLAG((*bgp)->flags, BGP_FLAG_ENFORCE_FIRST_AS); + + ret = BGP_SUCCESS; + } + return ret; +} + +/* + * bgp_vty_find_and_parse_afi_safi_bgp + * + * For a given 'show ...' command, correctly parse the afi/safi/bgp out from it + * This function *assumes* that the calling function pre-sets the afi/safi/bgp + * to appropriate values for the calling function. This is to allow the + * calling function to make decisions appropriate for the show command + * that is being parsed. + * + * The show commands are generally of the form: + * "show [ip] bgp [ VIEWVRFNAME] [ + * []] ..." + * + * Since we use argv_find if the show command in particular doesn't have: + * [ip] + * [ VIEWVRFNAME] + * [ []] + * The command parsing should still be ok. + * + * vty -> The vty for the command so we can output some useful data in + * the event of a parse error in the vrf. + * argv -> The command tokens + * argc -> How many command tokens we have + * idx -> The current place in the command, generally should be 0 for this + * function + * afi -> The parsed afi if it was included in the show command, returned here + * safi -> The parsed safi if it was included in the show command, returned here + * bgp -> Pointer to the bgp data structure we need to fill in. + * use_json -> json is configured or not + * + * The function returns the correct location in the parse tree for the + * last token found. + * + * Returns 0 for failure to parse correctly, else the idx position of where + * it found the last token. + */ +int bgp_vty_find_and_parse_afi_safi_bgp(struct vty *vty, + struct cmd_token **argv, int argc, + int *idx, afi_t *afi, safi_t *safi, + struct bgp **bgp, bool use_json) +{ + char *vrf_name = NULL; + + assert(afi); + assert(safi); + assert(bgp); + + if (argv_find(argv, argc, "ip", idx)) + *afi = AFI_IP; + + if (argv_find(argv, argc, "view", idx)) + vrf_name = argv[*idx + 1]->arg; + else if (argv_find(argv, argc, "vrf", idx)) { + vrf_name = argv[*idx + 1]->arg; + if (strmatch(vrf_name, VRF_DEFAULT_NAME)) + vrf_name = NULL; + } + if (vrf_name) { + if (strmatch(vrf_name, "all")) + *bgp = NULL; + else { + *bgp = bgp_lookup_by_name(vrf_name); + if (!*bgp) { + if (use_json) { + json_object *json = NULL; + json = json_object_new_object(); + json_object_string_add( + json, "warning", + "View/Vrf is unknown"); + vty_json(vty, json); + } + else + vty_out(vty, "View/Vrf %s is unknown\n", + vrf_name); + *idx = 0; + return 0; + } + } + } else { + *bgp = bgp_get_default(); + if (!*bgp) { + if (use_json) { + json_object *json = NULL; + json = json_object_new_object(); + json_object_string_add( + json, "warning", + "Default BGP instance not found"); + vty_json(vty, json); + } + else + vty_out(vty, + "Default BGP instance not found\n"); + *idx = 0; + return 0; + } + } + + if (argv_find_and_parse_afi(argv, argc, idx, afi)) + argv_find_and_parse_safi(argv, argc, idx, safi); + + *idx += 1; + return *idx; +} + +static bool peer_address_self_check(struct bgp *bgp, union sockunion *su) +{ + struct interface *ifp = NULL; + struct listnode *node; + struct bgp_listener *listener; + union sockunion all_su; + + if (su->sa.sa_family == AF_INET) { + (void)str2sockunion("0.0.0.0", &all_su); + ifp = if_lookup_by_ipv4_exact(&su->sin.sin_addr, bgp->vrf_id); + } else if (su->sa.sa_family == AF_INET6) { + (void)str2sockunion("::", &all_su); + ifp = if_lookup_by_ipv6_exact(&su->sin6.sin6_addr, + su->sin6.sin6_scope_id, + bgp->vrf_id); + } + + if (ifp) { + for (ALL_LIST_ELEMENTS_RO(bm->listen_sockets, node, listener)) { + if (sockunion_family(su) != + sockunion_family(&listener->su)) + continue; + + /* If 0.0.0.0/:: is a listener, then treat as self and + * reject. + */ + if (!sockunion_cmp(&listener->su, su) || + !sockunion_cmp(&listener->su, &all_su)) + return true; + } + } + + return false; +} + +/* Utility function for looking up peer from VTY. */ +/* This is used only for configuration, so disallow if attempted on + * a dynamic neighbor. + */ +static struct peer *peer_lookup_vty(struct vty *vty, const char *ip_str) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int ret; + union sockunion su; + struct peer *peer; + + if (!bgp) { + return NULL; + } + + ret = str2sockunion(ip_str, &su); + if (ret < 0) { + peer = peer_lookup_by_conf_if(bgp, ip_str); + if (!peer) { + if ((peer = peer_lookup_by_hostname(bgp, ip_str)) + == NULL) { + vty_out(vty, + "%% Malformed address or name: %s\n", + ip_str); + return NULL; + } + } + } else { + peer = peer_lookup(bgp, &su); + if (!peer) { + vty_out(vty, + "%% Specify remote-as or peer-group commands first\n"); + return NULL; + } + if (peer_dynamic_neighbor(peer)) { + vty_out(vty, + "%% Operation not allowed on a dynamic neighbor\n"); + return NULL; + } + } + return peer; +} + +/* Utility function for looking up peer or peer group. */ +/* This is used only for configuration, so disallow if attempted on + * a dynamic neighbor. + */ +struct peer *peer_and_group_lookup_vty(struct vty *vty, const char *peer_str) +{ + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + int ret; + union sockunion su; + struct peer *peer = NULL; + struct peer_group *group = NULL; + + if (!bgp) { + return NULL; + } + + ret = str2sockunion(peer_str, &su); + if (ret == 0) { + /* IP address, locate peer. */ + peer = peer_lookup(bgp, &su); + } else { + /* Not IP, could match either peer configured on interface or a + * group. */ + peer = peer_lookup_by_conf_if(bgp, peer_str); + if (!peer) + group = peer_group_lookup(bgp, peer_str); + } + + if (peer) { + if (peer_dynamic_neighbor(peer)) { + zlog_warn( + "%pBP: Operation not allowed on a dynamic neighbor", + peer); + vty_out(vty, + "%% Operation not allowed on a dynamic neighbor\n"); + return NULL; + } + + return peer; + } + + if (group) + return group->conf; + + zlog_warn("Specify remote-as or peer-group commands first before: %s", + vty->buf); + vty_out(vty, "%% Specify remote-as or peer-group commands first\n"); + + return NULL; +} + +int bgp_vty_return(struct vty *vty, enum bgp_create_error_code ret) +{ + const char *str = NULL; + + switch (ret) { + case BGP_SUCCESS: + case BGP_CREATED: + case BGP_GR_NO_OPERATION: + break; + case BGP_ERR_INVALID_VALUE: + str = "Invalid value"; + break; + case BGP_ERR_INVALID_FLAG: + str = "Invalid flag"; + break; + case BGP_ERR_PEER_GROUP_SHUTDOWN: + str = "Peer-group has been shutdown. Activate the peer-group first"; + break; + case BGP_ERR_PEER_FLAG_CONFLICT: + str = "Can't set override-capability and strict-capability-match at the same time"; + break; + case BGP_ERR_PEER_GROUP_NO_REMOTE_AS: + str = "Specify remote-as or peer-group remote AS first"; + break; + case BGP_ERR_PEER_GROUP_CANT_CHANGE: + str = "Cannot change the peer-group. Deconfigure first"; + break; + case BGP_ERR_PEER_GROUP_MISMATCH: + str = "Peer is not a member of this peer-group"; + break; + case BGP_ERR_PEER_FILTER_CONFLICT: + str = "Prefix/distribute list can not co-exist"; + break; + case BGP_ERR_NOT_INTERNAL_PEER: + str = "Invalid command. Not an internal neighbor"; + break; + case BGP_ERR_REMOVE_PRIVATE_AS: + str = "remove-private-AS cannot be configured for IBGP peers"; + break; + case BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS: + str = "Cannot have local-as same as BGP AS number"; + break; + case BGP_ERR_TCPSIG_FAILED: + str = "Error while applying TCP-Sig to session(s)"; + break; + case BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK: + str = "ebgp-multihop and ttl-security cannot be configured together"; + break; + case BGP_ERR_NO_IBGP_WITH_TTLHACK: + str = "ttl-security only allowed for EBGP peers"; + break; + case BGP_ERR_AS_OVERRIDE: + str = "as-override cannot be configured for IBGP peers"; + break; + case BGP_ERR_INVALID_DYNAMIC_NEIGHBORS_LIMIT: + str = "Invalid limit for number of dynamic neighbors"; + break; + case BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_EXISTS: + str = "Dynamic neighbor listen range already exists"; + break; + case BGP_ERR_INVALID_FOR_DYNAMIC_PEER: + str = "Operation not allowed on a dynamic neighbor"; + break; + case BGP_ERR_INVALID_FOR_DIRECT_PEER: + str = "Operation not allowed on a directly connected neighbor"; + break; + case BGP_ERR_PEER_SAFI_CONFLICT: + str = "Cannot activate peer for both 'ipv4 unicast' and 'ipv4 labeled-unicast'"; + break; + case BGP_ERR_GR_INVALID_CMD: + str = "The Graceful Restart command used is not valid at this moment."; + break; + case BGP_ERR_GR_OPERATION_FAILED: + str = "The Graceful Restart Operation failed due to an err."; + break; + case BGP_ERR_PEER_GROUP_MEMBER: + str = "Peer-group member cannot override remote-as of peer-group."; + break; + case BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT: + str = "Peer-group members must be all internal or all external."; + break; + case BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_NOT_FOUND: + str = "Range specified cannot be deleted because it is not part of current config."; + break; + case BGP_ERR_INSTANCE_MISMATCH: + str = "Instance specified does not match the current instance."; + break; + case BGP_ERR_NO_INTERFACE_CONFIG: + str = "Interface specified is not being used for interface based peer."; + break; + case BGP_ERR_SOFT_RECONFIG_UNCONFIGURED: + str = "No configuration already specified for soft reconfiguration."; + break; + case BGP_ERR_AS_MISMATCH: + str = "BGP is already running."; + break; + case BGP_ERR_AF_UNCONFIGURED: + str = "AFI/SAFI specified is not currently configured."; + break; + case BGP_ERR_INVALID_AS: + str = "Confederation AS specified is the same AS as our AS."; + break; + case BGP_ERR_INVALID_ROLE_NAME: + str = "Invalid role name"; + break; + case BGP_ERR_INVALID_INTERNAL_ROLE: + str = "External roles can be set only on eBGP session"; + break; + } + if (str) { + vty_out(vty, "%% %s\n", str); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +/* BGP clear sort. */ +enum clear_sort { + clear_all, + clear_peer, + clear_group, + clear_external, + clear_as +}; + +static void bgp_clear_vty_error(struct vty *vty, struct peer *peer, afi_t afi, + safi_t safi, int error) +{ + switch (error) { + case BGP_ERR_AF_UNCONFIGURED: + if (vty) + vty_out(vty, + "%% BGP: Enable %s address family for the neighbor %s\n", + get_afi_safi_str(afi, safi, false), peer->host); + else + zlog_warn( + "%% BGP: Enable %s address family for the neighbor %s", + get_afi_safi_str(afi, safi, false), peer->host); + break; + case BGP_ERR_SOFT_RECONFIG_UNCONFIGURED: + if (vty) + vty_out(vty, + "%% BGP: Inbound soft reconfig for %s not possible as it\n has neither refresh capability, nor inbound soft reconfig\n", + peer->host); + else + zlog_warn( + "%% BGP: Inbound soft reconfig for %s not possible as it has neither refresh capability, nor inbound soft reconfig", + peer->host); + break; + default: + break; + } +} + +static int bgp_peer_clear(struct peer *peer, afi_t afi, safi_t safi, + struct listnode **nnode, enum bgp_clear_type stype) +{ + int ret = 0; + struct peer_af *paf; + + /* if afi/.safi not specified, spin thru all of them */ + if ((afi == AFI_UNSPEC) && (safi == SAFI_UNSPEC)) { + afi_t tmp_afi; + safi_t tmp_safi; + enum bgp_af_index index; + + for (index = BGP_AF_START; index < BGP_AF_MAX; index++) { + paf = peer->peer_af_array[index]; + if (!paf) + continue; + + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + tmp_afi = paf->afi; + tmp_safi = paf->safi; + if (!peer->afc[tmp_afi][tmp_safi]) + continue; + + if (stype == BGP_CLEAR_SOFT_NONE) + ret = peer_clear(peer, nnode); + else + ret = peer_clear_soft(peer, tmp_afi, tmp_safi, + stype); + } + /* if afi specified and safi not, spin thru safis on this afi */ + } else if (safi == SAFI_UNSPEC) { + safi_t tmp_safi; + + for (tmp_safi = SAFI_UNICAST; + tmp_safi < SAFI_MAX; tmp_safi++) { + if (!peer->afc[afi][tmp_safi]) + continue; + + paf = peer_af_find(peer, afi, tmp_safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + if (stype == BGP_CLEAR_SOFT_NONE) + ret = peer_clear(peer, nnode); + else + ret = peer_clear_soft(peer, afi, + tmp_safi, stype); + } + /* both afi/safi specified, let the caller know if not defined */ + } else { + if (!peer->afc[afi][safi]) + return 1; + + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + if (stype == BGP_CLEAR_SOFT_NONE) + ret = peer_clear(peer, nnode); + else + ret = peer_clear_soft(peer, afi, safi, stype); + } + + return ret; +} + +/* `clear ip bgp' functions. */ +static int bgp_clear(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, + enum clear_sort sort, enum bgp_clear_type stype, + const char *arg) +{ + int ret = 0; + bool found = false; + struct peer *peer; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + /* Clear all neighbors. */ + /* + * Pass along pointer to next node to peer_clear() when walking all + * nodes on the BGP instance as that may get freed if it is a + * doppelganger + */ + if (sort == clear_all) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + + bgp_peer_gr_flags_update(peer); + + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) + gr_router_detected = true; + + ret = bgp_peer_clear(peer, afi, safi, &nnode, + stype); + + if (ret < 0) + bgp_clear_vty_error(vty, peer, afi, safi, ret); + } + + if (gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { + bgp_zebra_send_capabilities(bgp, false); + } else if (!gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_ENABLE) { + bgp_zebra_send_capabilities(bgp, true); + } + + /* This is to apply read-only mode on this clear. */ + if (stype == BGP_CLEAR_SOFT_NONE) + bgp->update_delay_over = 0; + + return CMD_SUCCESS; + } + + /* Clear specified neighbor. */ + if (sort == clear_peer) { + union sockunion su; + + /* Make sockunion for lookup. */ + ret = str2sockunion(arg, &su); + if (ret < 0) { + peer = peer_lookup_by_conf_if(bgp, arg); + if (!peer) { + peer = peer_lookup_by_hostname(bgp, arg); + if (!peer) { + vty_out(vty, + "Malformed address or name: %s\n", + arg); + return CMD_WARNING; + } + } + } else { + peer = peer_lookup(bgp, &su); + if (!peer) { + vty_out(vty, + "%% BGP: Unknown neighbor - \"%s\"\n", + arg); + return CMD_WARNING; + } + } + + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + + ret = bgp_peer_clear(peer, afi, safi, NULL, stype); + + /* if afi/safi not defined for this peer, let caller know */ + if (ret == 1) + ret = BGP_ERR_AF_UNCONFIGURED; + + if (ret < 0) + bgp_clear_vty_error(vty, peer, afi, safi, ret); + + return CMD_SUCCESS; + } + + /* Clear all neighbors belonging to a specific peer-group. */ + if (sort == clear_group) { + struct peer_group *group; + + group = peer_group_lookup(bgp, arg); + if (!group) { + vty_out(vty, "%% BGP: No such peer-group %s\n", arg); + return CMD_WARNING; + } + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + ret = bgp_peer_clear(peer, afi, safi, &nnode, stype); + + if (ret < 0) + bgp_clear_vty_error(vty, peer, afi, safi, ret); + else + found = true; + } + + if (!found) + vty_out(vty, + "%% BGP: No %s peer belonging to peer-group %s is configured\n", + get_afi_safi_str(afi, safi, false), arg); + + return CMD_SUCCESS; + } + + /* Clear all external (eBGP) neighbors. */ + if (sort == clear_external) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->sort == BGP_PEER_IBGP) + continue; + + bgp_peer_gr_flags_update(peer); + + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) + gr_router_detected = true; + + ret = bgp_peer_clear(peer, afi, safi, &nnode, stype); + + if (ret < 0) + bgp_clear_vty_error(vty, peer, afi, safi, ret); + else + found = true; + } + + if (gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { + bgp_zebra_send_capabilities(bgp, false); + } else if (!gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_ENABLE) { + bgp_zebra_send_capabilities(bgp, true); + } + + if (!found) + vty_out(vty, + "%% BGP: No external %s peer is configured\n", + get_afi_safi_str(afi, safi, false)); + + return CMD_SUCCESS; + } + + /* Clear all neighbors belonging to a specific AS. */ + if (sort == clear_as) { + as_t as; + + if (!asn_str2asn(arg, &as)) { + vty_out(vty, "%% BGP: No such AS %s\n", arg); + return CMD_WARNING; + } + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->as != as) + continue; + + bgp_peer_gr_flags_update(peer); + + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) + gr_router_detected = true; + + ret = bgp_peer_clear(peer, afi, safi, &nnode, stype); + + if (ret < 0) + bgp_clear_vty_error(vty, peer, afi, safi, ret); + else + found = true; + } + + if (gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { + bgp_zebra_send_capabilities(bgp, false); + } else if (!gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_ENABLE) { + bgp_zebra_send_capabilities(bgp, true); + } + + if (!found) + vty_out(vty, + "%% BGP: No %s peer is configured with AS %s\n", + get_afi_safi_str(afi, safi, false), arg); + + return CMD_SUCCESS; + } + + return CMD_SUCCESS; +} + +static int bgp_clear_vty(struct vty *vty, const char *name, afi_t afi, + safi_t safi, enum clear_sort sort, + enum bgp_clear_type stype, const char *arg) +{ + struct bgp *bgp; + + /* BGP structure lookup. */ + if (name) { + bgp = bgp_lookup_by_name(name); + if (bgp == NULL) { + vty_out(vty, "Can't find BGP instance %s\n", name); + return CMD_WARNING; + } + } else { + bgp = bgp_get_default(); + if (bgp == NULL) { + vty_out(vty, "No BGP process is configured\n"); + return CMD_WARNING; + } + } + + return bgp_clear(vty, bgp, afi, safi, sort, stype, arg); +} + +/* clear soft inbound */ +static void bgp_clear_star_soft_in(struct vty *vty, const char *name) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_clear_vty(vty, name, afi, safi, clear_all, + BGP_CLEAR_SOFT_IN, NULL); +} + +/* clear soft outbound */ +static void bgp_clear_star_soft_out(struct vty *vty, const char *name) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + bgp_clear_vty(vty, name, afi, safi, clear_all, + BGP_CLEAR_SOFT_OUT, NULL); +} + + +void bgp_clear_soft_in(struct bgp *bgp, afi_t afi, safi_t safi) +{ + bgp_clear(NULL, bgp, afi, safi, clear_all, BGP_CLEAR_SOFT_IN, NULL); +} + +static int peer_flag_modify_vty(struct vty *vty, const char *ip_str, + uint64_t flag, int set) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* + * If 'neighbor ', then this is for directly connected peers, + * we should not accept disable-connected-check. + */ + if (peer->conf_if && (flag == PEER_FLAG_DISABLE_CONNECTED_CHECK)) { + vty_out(vty, + "%s is directly connected peer, cannot accept disable-connected-check\n", + ip_str); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!set && flag == PEER_FLAG_SHUTDOWN) + peer_tx_shutdown_message_unset(peer); + + if (set) + ret = peer_flag_set(peer, flag); + else + ret = peer_flag_unset(peer, flag); + + return bgp_vty_return(vty, ret); +} + +static int peer_flag_set_vty(struct vty *vty, const char *ip_str, uint64_t flag) +{ + return peer_flag_modify_vty(vty, ip_str, flag, 1); +} + +static int peer_flag_unset_vty(struct vty *vty, const char *ip_str, + uint64_t flag) +{ + return peer_flag_modify_vty(vty, ip_str, flag, 0); +} + +#include "bgpd/bgp_vty_clippy.c" + +DEFUN_HIDDEN (bgp_local_mac, + bgp_local_mac_cmd, + "bgp local-mac vni " CMD_VNI_RANGE " mac WORD seq (0-4294967295)", + BGP_STR + "Local MAC config\n" + "VxLAN Network Identifier\n" + "VNI number\n" + "local mac\n" + "mac address\n" + "mac-mobility sequence\n" + "seq number\n") +{ + int rv; + vni_t vni; + struct ethaddr mac; + struct ipaddr ip; + uint32_t seq; + struct bgp *bgp; + + vni = strtoul(argv[3]->arg, NULL, 10); + if (!prefix_str2mac(argv[5]->arg, &mac)) { + vty_out(vty, "%% Malformed MAC address\n"); + return CMD_WARNING; + } + memset(&ip, 0, sizeof(ip)); + seq = strtoul(argv[7]->arg, NULL, 10); + + bgp = bgp_get_default(); + if (!bgp) { + vty_out(vty, "Default BGP instance is not there\n"); + return CMD_WARNING; + } + + rv = bgp_evpn_local_macip_add(bgp, vni, &mac, &ip, 0 /* flags */, seq, + zero_esi); + if (rv < 0) { + vty_out(vty, "Internal error\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_bgp_local_mac, + no_bgp_local_mac_cmd, + "no bgp local-mac vni " CMD_VNI_RANGE " mac WORD", + NO_STR + BGP_STR + "Local MAC config\n" + "VxLAN Network Identifier\n" + "VNI number\n" + "local mac\n" + "mac address\n") +{ + int rv; + vni_t vni; + struct ethaddr mac; + struct ipaddr ip; + struct bgp *bgp; + + vni = strtoul(argv[4]->arg, NULL, 10); + if (!prefix_str2mac(argv[6]->arg, &mac)) { + vty_out(vty, "%% Malformed MAC address\n"); + return CMD_WARNING; + } + memset(&ip, 0, sizeof(ip)); + + bgp = bgp_get_default(); + if (!bgp) { + vty_out(vty, "Default BGP instance is not there\n"); + return CMD_WARNING; + } + + rv = bgp_evpn_local_macip_del(bgp, vni, &mac, &ip, ZEBRA_NEIGH_ACTIVE); + if (rv < 0) { + vty_out(vty, "Internal error\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN (no_synchronization, + no_synchronization_cmd, + "no synchronization", + NO_STR + "Perform IGP synchronization\n") +{ + return CMD_SUCCESS; +} + +DEFUN (no_auto_summary, + no_auto_summary_cmd, + "no auto-summary", + NO_STR + "Enable automatic network number summarization\n") +{ + return CMD_SUCCESS; +} + +/* "router bgp" commands. */ +DEFUN_NOSH (router_bgp, + router_bgp_cmd, + "router bgp [ASNUM$instasn [ VIEWVRFNAME] [as-notation ]]", + ROUTER_STR + BGP_STR + AS_STR + BGP_INSTANCE_HELP_STR + "Force the AS notation output\n" + "use 'AA.BB' format for AS 4 byte values\n" + "use 'AA.BB' format for all AS values\n" + "use plain format for all AS values\n") +{ + int idx_asn = 2; + int idx_view_vrf = 3; + int idx_vrf = 4; + int is_new_bgp = 0; + int idx_asnotation = 3; + int idx_asnotation_kind = 4; + enum asnotation_mode asnotation = ASNOTATION_UNDEFINED; + int ret; + as_t as; + struct bgp *bgp; + const char *name = NULL; + enum bgp_instance_type inst_type; + + // "router bgp" without an ASN + if (argc == 2) { + // Pending: Make VRF option available for ASN less config + bgp = bgp_get_default(); + + if (bgp == NULL) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (listcount(bm->bgp) > 1) { + vty_out(vty, "%% Please specify ASN and VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + // "router bgp X" + else { + if (!asn_str2asn(argv[idx_asn]->arg, &as)) { + vty_out(vty, "%% BGP: No such AS %s\n", + argv[idx_asn]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (as == BGP_PRIVATE_AS_MAX || as == BGP_AS4_MAX) + vty_out(vty, "Reserved AS used (%u|%u); AS is %u\n", + BGP_PRIVATE_AS_MAX, BGP_AS4_MAX, as); + + inst_type = BGP_INSTANCE_TYPE_DEFAULT; + + if (argv_find(argv, argc, "VIEWVRFNAME", &idx_vrf)) { + idx_view_vrf = idx_vrf - 1; + if (argv[idx_view_vrf]->text) { + name = argv[idx_vrf]->arg; + + if (!strcmp(argv[idx_view_vrf]->text, "vrf")) { + if (strmatch(name, VRF_DEFAULT_NAME)) + name = NULL; + else + inst_type = + BGP_INSTANCE_TYPE_VRF; + } else if (!strcmp(argv[idx_view_vrf]->text, + "view")) + inst_type = BGP_INSTANCE_TYPE_VIEW; + } + } + if (argv_find(argv, argc, "as-notation", &idx_asnotation)) { + idx_asnotation_kind = idx_asnotation + 1; + if (strmatch(argv[idx_asnotation_kind]->text, "dot+")) + asnotation = ASNOTATION_DOTPLUS; + else if (strmatch(argv[idx_asnotation_kind]->text, + "dot")) + asnotation = ASNOTATION_DOT; + else if (strmatch(argv[idx_asnotation_kind]->text, + "plain")) + asnotation = ASNOTATION_PLAIN; + } + + if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) + is_new_bgp = (bgp_lookup(as, name) == NULL); + + ret = bgp_get_vty(&bgp, &as, name, inst_type, + argv[idx_asn]->arg, asnotation); + switch (ret) { + case BGP_ERR_AS_MISMATCH: + vty_out(vty, "BGP is already running; AS is %s\n", + bgp->as_pretty); + return CMD_WARNING_CONFIG_FAILED; + case BGP_ERR_INSTANCE_MISMATCH: + vty_out(vty, + "BGP instance name and AS number mismatch\n"); + vty_out(vty, + "BGP instance is already running; AS is %s\n", + bgp->as_pretty); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * If we just instantiated the default instance, complete + * any pending VRF-VPN leaking that was configured via + * earlier "router bgp X vrf FOO" blocks. + */ + if (is_new_bgp && inst_type == BGP_INSTANCE_TYPE_DEFAULT) + vpn_leak_postchange_all(); + + if (inst_type == BGP_INSTANCE_TYPE_VRF) + bgp_vpn_leak_export(bgp); + /* Pending: handle when user tries to change a view to vrf n vv. + */ + /* for pre-existing bgp instance, + * - update as_pretty + * - update asnotation if explicitly mentioned + */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) { + XFREE(MTYPE_BGP_NAME, bgp->as_pretty); + bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, + argv[idx_asn]->arg); + if (!CHECK_FLAG(bgp->config, BGP_CONFIG_ASNOTATION) && + asnotation != ASNOTATION_UNDEFINED) { + SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION); + bgp->asnotation = asnotation; + } + } + } + + /* unset the auto created flag as the user config is now present */ + UNSET_FLAG(bgp->vrf_flags, BGP_VRF_AUTO); + VTY_PUSH_CONTEXT(BGP_NODE, bgp); + + return CMD_SUCCESS; +} + +/* "no router bgp" commands. */ +DEFUN (no_router_bgp, + no_router_bgp_cmd, + "no router bgp [ASNUM$instasn [ VIEWVRFNAME] [as-notation ]]", + NO_STR + ROUTER_STR + BGP_STR + AS_STR + BGP_INSTANCE_HELP_STR + "Force the AS notation output\n" + "use 'AA.BB' format for AS 4 byte values\n" + "use 'AA.BB' format for all AS values\n" + "use plain format for all AS values\n") +{ + int idx_asn = 3; + int idx_vrf = 5; + as_t as; + struct bgp *bgp; + const char *name = NULL; + + // "no router bgp" without an ASN + if (argc == 3) { + // Pending: Make VRF option available for ASN less config + bgp = bgp_get_default(); + + if (bgp == NULL) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (listcount(bm->bgp) > 1) { + vty_out(vty, "%% Please specify ASN and VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->l3vni) { + vty_out(vty, "%% Please unconfigure l3vni %u\n", + bgp->l3vni); + return CMD_WARNING_CONFIG_FAILED; + } + } else { + if (!asn_str2asn(argv[idx_asn]->arg, &as)) { + vty_out(vty, "%% BGP: No such AS %s\n", + argv[idx_asn]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (argc > 4) { + name = argv[idx_vrf]->arg; + if (strmatch(argv[idx_vrf - 1]->text, "vrf") + && strmatch(name, VRF_DEFAULT_NAME)) + name = NULL; + } + + /* Lookup bgp structure. */ + bgp = bgp_lookup(as, name); + if (!bgp) { + vty_out(vty, "%% Can't find BGP instance\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->l3vni) { + vty_out(vty, "%% Please unconfigure l3vni %u\n", + bgp->l3vni); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Cannot delete default instance if vrf instances exist */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + struct listnode *node; + struct bgp *tmp_bgp; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, tmp_bgp)) { + if (tmp_bgp->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + if (CHECK_FLAG(tmp_bgp->vrf_flags, BGP_VRF_AUTO)) + bgp_delete(tmp_bgp); + + if (CHECK_FLAG( + tmp_bgp->af_flags[AFI_IP] + [SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_IP6] + [SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_IP] + [SAFI_UNICAST], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_IP6] + [SAFI_UNICAST], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) || + CHECK_FLAG(tmp_bgp->af_flags[AFI_IP] + [SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT) || + CHECK_FLAG(tmp_bgp->af_flags[AFI_IP6] + [SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT) || + (bgp == bgp_get_evpn() && + (CHECK_FLAG( + tmp_bgp->af_flags[AFI_L2VPN] + [SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_L2VPN] + [SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_L2VPN] + [SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST) || + CHECK_FLAG( + tmp_bgp->af_flags[AFI_L2VPN] + [SAFI_EVPN], + BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP))) || + (tmp_bgp->l3vni)) { + vty_out(vty, + "%% Cannot delete default BGP instance. Dependent VRF instances exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + } + + bgp_delete(bgp); + + return CMD_SUCCESS; +} + +/* bgp session-dscp */ + +DEFPY (bgp_session_dscp, + bgp_session_dscp_cmd, + "bgp session-dscp (0-63)$dscp", + BGP_STR + "Override default (CS6) DSCP for BGP connections\n" + "Manually configured DSCP value\n") +{ + bm->ip_tos = dscp << 2; + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_session_dscp, + no_bgp_session_dscp_cmd, + "no bgp session-dscp [(0-63)]", + NO_STR + BGP_STR + "Override default (CS6) DSCP for BGP connections\n" + "Manually configured DSCP value\n") +{ + bm->ip_tos = IPTOS_PREC_INTERNETCONTROL; + + return CMD_SUCCESS; +} + +/* BGP router-id. */ + +DEFPY (bgp_router_id, + bgp_router_id_cmd, + "bgp router-id A.B.C.D", + BGP_STR + "Override configured router identifier\n" + "Manually configured router identifier\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_router_id_static_set(bgp, router_id); + return CMD_SUCCESS; +} + +DEFPY (no_bgp_router_id, + no_bgp_router_id_cmd, + "no bgp router-id [A.B.C.D]", + NO_STR + BGP_STR + "Override configured router identifier\n" + "Manually configured router identifier\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (router_id_str) { + if (!IPV4_ADDR_SAME(&bgp->router_id_static, &router_id)) { + vty_out(vty, "%% BGP router-id doesn't match\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + router_id.s_addr = 0; + bgp_router_id_static_set(bgp, router_id); + + return CMD_SUCCESS; +} + +DEFPY(bgp_community_alias, bgp_community_alias_cmd, + "[no$no] bgp community alias WORD$community ALIAS_NAME$alias_name", + NO_STR BGP_STR + "Add community specific parameters\n" + "Create an alias for a community\n" + "Community (AA:BB or AA:BB:CC)\n" + "Alias name\n") +{ + struct community_alias ca = {}; + struct community_alias *lookup_community; + struct community_alias *lookup_alias; + struct community *comm; + struct lcommunity *lcomm; + uint8_t invalid = 0; + + comm = community_str2com(community); + if (!comm) + invalid++; + community_free(&comm); + + lcomm = lcommunity_str2com(community); + if (!lcomm) + invalid++; + lcommunity_free(&lcomm); + + if (invalid > 1) { + vty_out(vty, "Invalid community format\n"); + return CMD_WARNING; + } + + strlcpy(ca.community, community, sizeof(ca.community)); + strlcpy(ca.alias, alias_name, sizeof(ca.alias)); + + lookup_community = bgp_ca_community_lookup(&ca); + lookup_alias = bgp_ca_alias_lookup(&ca); + + if (no) { + bgp_ca_alias_delete(&ca); + bgp_ca_community_delete(&ca); + } else { + if (lookup_alias) { + /* Lookup if community hash table has an item + * with the same alias name. + */ + strlcpy(ca.community, lookup_alias->community, + sizeof(ca.community)); + if (bgp_ca_community_lookup(&ca)) { + vty_out(vty, + "community (%s) already has this alias (%s)\n", + lookup_alias->community, + lookup_alias->alias); + return CMD_WARNING; + } + bgp_ca_alias_delete(&ca); + } + + if (lookup_community) { + /* Lookup if alias hash table has an item + * with the same community. + */ + strlcpy(ca.alias, lookup_community->alias, + sizeof(ca.alias)); + if (bgp_ca_alias_lookup(&ca)) { + vty_out(vty, + "alias (%s) already has this community (%s)\n", + lookup_community->alias, + lookup_community->community); + return CMD_WARNING; + } + bgp_ca_community_delete(&ca); + } + + bgp_ca_alias_insert(&ca); + bgp_ca_community_insert(&ca); + } + + return CMD_SUCCESS; +} + +DEFPY (bgp_global_suppress_fib_pending, + bgp_global_suppress_fib_pending_cmd, + "[no] bgp suppress-fib-pending", + NO_STR + BGP_STR + "Advertise only routes that are programmed in kernel to peers globally\n") +{ + bm_wait_for_fib_set(!no); + + return CMD_SUCCESS; +} + +DEFPY (bgp_suppress_fib_pending, + bgp_suppress_fib_pending_cmd, + "[no] bgp suppress-fib-pending", + NO_STR + BGP_STR + "Advertise only routes that are programmed in kernel to peers\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp_suppress_fib_pending_set(bgp, !no); + return CMD_SUCCESS; +} + +/* BGP Cluster ID. */ +DEFUN (bgp_cluster_id, + bgp_cluster_id_cmd, + "bgp cluster-id ", + BGP_STR + "Configure Route-Reflector Cluster-id\n" + "Route-Reflector Cluster-id in IP address format\n" + "Route-Reflector Cluster-id as 32 bit quantity\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ipv4 = 2; + int ret; + struct in_addr cluster; + + ret = inet_aton(argv[idx_ipv4]->arg, &cluster); + if (!ret) { + vty_out(vty, "%% Malformed bgp cluster identifier\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_cluster_id_set(bgp, &cluster); + bgp_clear_star_soft_out(vty, bgp->name); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_cluster_id, + no_bgp_cluster_id_cmd, + "no bgp cluster-id []", + NO_STR + BGP_STR + "Configure Route-Reflector Cluster-id\n" + "Route-Reflector Cluster-id in IP address format\n" + "Route-Reflector Cluster-id as 32 bit quantity\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_cluster_id_unset(bgp); + bgp_clear_star_soft_out(vty, bgp->name); + + return CMD_SUCCESS; +} + +DEFPY (bgp_norib, + bgp_norib_cmd, + "bgp no-rib", + BGP_STR + "Disable BGP route installation to RIB (Zebra)\n") +{ + if (bgp_option_check(BGP_OPT_NO_FIB)) { + vty_out(vty, + "%% No-RIB option is already set, nothing to do here.\n"); + return CMD_SUCCESS; + } + + bgp_option_norib_set_runtime(); + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_norib, + no_bgp_norib_cmd, + "no bgp no-rib", + NO_STR + BGP_STR + "Disable BGP route installation to RIB (Zebra)\n") +{ + if (!bgp_option_check(BGP_OPT_NO_FIB)) { + vty_out(vty, + "%% No-RIB option is not set, nothing to do here.\n"); + return CMD_SUCCESS; + } + + bgp_option_norib_unset_runtime(); + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_send_extra_data, + no_bgp_send_extra_data_cmd, + "[no] bgp send-extra-data zebra", + NO_STR + BGP_STR + "Extra data to Zebra for display/use\n" + "To zebra\n") +{ + if (no) + UNSET_FLAG(bm->flags, BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA); + else + SET_FLAG(bm->flags, BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA); + + return CMD_SUCCESS; +} + +DEFUN (bgp_confederation_identifier, + bgp_confederation_identifier_cmd, + "bgp confederation identifier ASNUM", + BGP_STR + "AS confederation parameters\n" + "Set routing domain confederation AS\n" + AS_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + as_t as; + + if (!asn_str2asn(argv[idx_number]->arg, &as)) { + vty_out(vty, "%% BGP: No such AS %s\n", argv[idx_number]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_confederation_id_set(bgp, as, argv[idx_number]->arg); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_confederation_identifier, + no_bgp_confederation_identifier_cmd, + "no bgp confederation identifier [ASNUM]", + NO_STR + BGP_STR + "AS confederation parameters\n" + "Set routing domain confederation AS\n" + AS_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_confederation_id_unset(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_confederation_peers, + bgp_confederation_peers_cmd, + "bgp confederation peers ASNUM...", + BGP_STR + "AS confederation parameters\n" + "Peer ASs in BGP confederation\n" + AS_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_asn = 3; + as_t as; + int i; + + for (i = idx_asn; i < argc; i++) { + if (!asn_str2asn(argv[i]->arg, &as)) { + vty_out(vty, "%% Invalid confed peer AS value: %s\n", + argv[i]->arg); + continue; + } + + bgp_confederation_peers_add(bgp, as, argv[i]->arg); + } + return CMD_SUCCESS; +} + +DEFUN (no_bgp_confederation_peers, + no_bgp_confederation_peers_cmd, + "no bgp confederation peers ASNUM...", + NO_STR + BGP_STR + "AS confederation parameters\n" + "Peer ASs in BGP confederation\n" + AS_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_asn = 4; + as_t as; + int i; + + for (i = idx_asn; i < argc; i++) { + if (!asn_str2asn(argv[i]->arg, &as)) { + vty_out(vty, "%% Invalid confed peer AS value: %s\n", + argv[i]->arg); + continue; + } + bgp_confederation_peers_remove(bgp, as); + } + return CMD_SUCCESS; +} + +/** + * Central routine for maximum-paths configuration. + * @peer_type: BGP_PEER_EBGP or BGP_PEER_IBGP + * @set: 1 for setting values, 0 for removing the max-paths config. + */ +static int bgp_maxpaths_config_vty(struct vty *vty, int peer_type, + const char *mpaths, uint16_t options, + int set) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint16_t maxpaths = 0; + int ret; + afi_t afi; + safi_t safi; + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + + if (set) { + maxpaths = strtol(mpaths, NULL, 10); + if (maxpaths > multipath_num) { + vty_out(vty, + "%% Maxpaths Specified: %d is > than multipath num specified on bgp command line %d", + maxpaths, multipath_num); + return CMD_WARNING_CONFIG_FAILED; + } + ret = bgp_maximum_paths_set(bgp, afi, safi, peer_type, maxpaths, + options); + } else + ret = bgp_maximum_paths_unset(bgp, afi, safi, peer_type); + + if (ret < 0) { + vty_out(vty, + "%% Failed to %sset maximum-paths %s %u for afi %u, safi %u\n", + (set == 1) ? "" : "un", + (peer_type == BGP_PEER_EBGP) ? "ebgp" : "ibgp", + maxpaths, afi, safi); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_maxmed_admin, + bgp_maxmed_admin_cmd, + "bgp max-med administrative ", + BGP_STR + "Advertise routes with max-med\n" + "Administratively applied, for an indefinite period\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->v_maxmed_admin = 1; + bgp->maxmed_admin_value = BGP_MAXMED_VALUE_DEFAULT; + + bgp_maxmed_update(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_maxmed_admin_medv, + bgp_maxmed_admin_medv_cmd, + "bgp max-med administrative (0-4294967295)", + BGP_STR + "Advertise routes with max-med\n" + "Administratively applied, for an indefinite period\n" + "Max MED value to be used\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + + bgp->v_maxmed_admin = 1; + bgp->maxmed_admin_value = strtoul(argv[idx_number]->arg, NULL, 10); + + bgp_maxmed_update(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_maxmed_admin, + no_bgp_maxmed_admin_cmd, + "no bgp max-med administrative [(0-4294967295)]", + NO_STR + BGP_STR + "Advertise routes with max-med\n" + "Administratively applied, for an indefinite period\n" + "Max MED value to be used\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->v_maxmed_admin = BGP_MAXMED_ADMIN_UNCONFIGURED; + bgp->maxmed_admin_value = BGP_MAXMED_VALUE_DEFAULT; + bgp_maxmed_update(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_maxmed_onstartup, + bgp_maxmed_onstartup_cmd, + "bgp max-med on-startup (5-86400) [(0-4294967295)]", + BGP_STR + "Advertise routes with max-med\n" + "Effective on a startup\n" + "Time (seconds) period for max-med\n" + "Max MED value to be used\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx = 0; + + if (argv_find(argv, argc, "(5-86400)", &idx)) + bgp->v_maxmed_onstartup = strtoul(argv[idx]->arg, NULL, 10); + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + bgp->maxmed_onstartup_value = strtoul(argv[idx]->arg, NULL, 10); + else + bgp->maxmed_onstartup_value = BGP_MAXMED_VALUE_DEFAULT; + + bgp_maxmed_update(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_maxmed_onstartup, + no_bgp_maxmed_onstartup_cmd, + "no bgp max-med on-startup [(5-86400) [(0-4294967295)]]", + NO_STR + BGP_STR + "Advertise routes with max-med\n" + "Effective on a startup\n" + "Time (seconds) period for max-med\n" + "Max MED value to be used\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* Cancel max-med onstartup if its on */ + if (bgp->t_maxmed_onstartup) { + EVENT_OFF(bgp->t_maxmed_onstartup); + bgp->maxmed_onstartup_over = 1; + } + + bgp->v_maxmed_onstartup = BGP_MAXMED_ONSTARTUP_UNCONFIGURED; + bgp->maxmed_onstartup_value = BGP_MAXMED_VALUE_DEFAULT; + + bgp_maxmed_update(bgp); + + return CMD_SUCCESS; +} + +static int bgp_global_update_delay_config_vty(struct vty *vty, + uint16_t update_delay, + uint16_t establish_wait) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + bool vrf_cfg = false; + + /* + * See if update-delay is set per-vrf and warn user to delete it + * Note that we only need to check this if this is the first time + * setting the global config. + */ + if (bm->v_update_delay == BGP_UPDATE_DELAY_DEF) { + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->v_update_delay != BGP_UPDATE_DELAY_DEF) { + vty_out(vty, + "%% update-delay configuration found in vrf %s\n", + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT + ? VRF_DEFAULT_NAME + : bgp->name); + vrf_cfg = true; + } + } + } + + if (vrf_cfg) { + vty_out(vty, + "%%Failed: global update-delay config not permitted\n"); + return CMD_WARNING; + } + + if (!establish_wait) { /* update-delay */ + bm->v_update_delay = update_delay; + bm->v_establish_wait = bm->v_update_delay; + } else { + /* update-delay */ + if (update_delay < establish_wait) { + vty_out(vty, + "%%Failed: update-delay less than the establish-wait!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bm->v_update_delay = update_delay; + bm->v_establish_wait = establish_wait; + } + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + bgp->v_update_delay = bm->v_update_delay; + bgp->v_establish_wait = bm->v_establish_wait; + } + + return CMD_SUCCESS; +} + +static int bgp_global_update_delay_deconfig_vty(struct vty *vty) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + bm->v_update_delay = BGP_UPDATE_DELAY_DEF; + bm->v_establish_wait = bm->v_update_delay; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + bgp->v_update_delay = bm->v_update_delay; + bgp->v_establish_wait = bm->v_establish_wait; + } + + return CMD_SUCCESS; +} + +static int bgp_update_delay_config_vty(struct vty *vty, uint16_t update_delay, + uint16_t establish_wait) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* if configured globally, per-instance config is not allowed */ + if (bm->v_update_delay) { + vty_out(vty, + "%%Failed: per-vrf update-delay config not permitted with global update-delay\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + + if (!establish_wait) /* update-delay */ + { + bgp->v_update_delay = update_delay; + bgp->v_establish_wait = bgp->v_update_delay; + return CMD_SUCCESS; + } + + /* update-delay */ + if (update_delay < establish_wait) { + vty_out(vty, + "%%Failed: update-delay less than the establish-wait!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp->v_update_delay = update_delay; + bgp->v_establish_wait = establish_wait; + + return CMD_SUCCESS; +} + +static int bgp_update_delay_deconfig_vty(struct vty *vty) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* If configured globally, cannot remove from one bgp instance */ + if (bm->v_update_delay) { + vty_out(vty, + "%%Failed: bgp update-delay configured globally. Delete per-vrf not permitted\n"); + return CMD_WARNING_CONFIG_FAILED; + } + bgp->v_update_delay = BGP_UPDATE_DELAY_DEF; + bgp->v_establish_wait = bgp->v_update_delay; + + return CMD_SUCCESS; +} + +void bgp_config_write_update_delay(struct vty *vty, struct bgp *bgp) +{ + /* If configured globally, no need to display per-instance value */ + if (bgp->v_update_delay != bm->v_update_delay) { + vty_out(vty, " update-delay %d", bgp->v_update_delay); + if (bgp->v_update_delay != bgp->v_establish_wait) + vty_out(vty, " %d", bgp->v_establish_wait); + vty_out(vty, "\n"); + } +} + +/* Global update-delay configuration */ +DEFPY (bgp_global_update_delay, + bgp_global_update_delay_cmd, + "bgp update-delay (0-3600)$delay [(1-3600)$wait]", + BGP_STR + "Force initial delay for best-path and updates for all bgp instances\n" + "Max delay in seconds\n" + "Establish wait in seconds\n") +{ + return bgp_global_update_delay_config_vty(vty, delay, wait); +} + +/* Global update-delay deconfiguration */ +DEFPY (no_bgp_global_update_delay, + no_bgp_global_update_delay_cmd, + "no bgp update-delay [(0-3600) [(1-3600)]]", + NO_STR + BGP_STR + "Force initial delay for best-path and updates\n" + "Max delay in seconds\n" + "Establish wait in seconds\n") +{ + return bgp_global_update_delay_deconfig_vty(vty); +} + +/* Update-delay configuration */ + +DEFPY (bgp_update_delay, + bgp_update_delay_cmd, + "update-delay (0-3600)$delay [(1-3600)$wait]", + "Force initial delay for best-path and updates\n" + "Max delay in seconds\n" + "Establish wait in seconds\n") +{ + return bgp_update_delay_config_vty(vty, delay, wait); +} + +/* Update-delay deconfiguration */ +DEFPY (no_bgp_update_delay, + no_bgp_update_delay_cmd, + "no update-delay [(0-3600) [(1-3600)]]", + NO_STR + "Force initial delay for best-path and updates\n" + "Max delay in seconds\n" + "Establish wait in seconds\n") +{ + return bgp_update_delay_deconfig_vty(vty); +} + + +static int bgp_wpkt_quanta_config_vty(struct vty *vty, uint32_t quanta, + bool set) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + quanta = set ? quanta : BGP_WRITE_PACKET_MAX; + atomic_store_explicit(&bgp->wpkt_quanta, quanta, memory_order_relaxed); + + return CMD_SUCCESS; +} + +static int bgp_rpkt_quanta_config_vty(struct vty *vty, uint32_t quanta, + bool set) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + quanta = set ? quanta : BGP_READ_PACKET_MAX; + atomic_store_explicit(&bgp->rpkt_quanta, quanta, memory_order_relaxed); + + return CMD_SUCCESS; +} + +void bgp_config_write_wpkt_quanta(struct vty *vty, struct bgp *bgp) +{ + uint32_t quanta = + atomic_load_explicit(&bgp->wpkt_quanta, memory_order_relaxed); + if (quanta != BGP_WRITE_PACKET_MAX) + vty_out(vty, " write-quanta %d\n", quanta); +} + +void bgp_config_write_rpkt_quanta(struct vty *vty, struct bgp *bgp) +{ + uint32_t quanta = + atomic_load_explicit(&bgp->rpkt_quanta, memory_order_relaxed); + if (quanta != BGP_READ_PACKET_MAX) + vty_out(vty, " read-quanta %d\n", quanta); +} + +/* Packet quanta configuration + * + * XXX: The value set here controls the size of a stack buffer in the IO + * thread. When changing these limits be careful to prevent stack overflow. + * + * Furthermore, the maximums used here should correspond to + * BGP_WRITE_PACKET_MAX and BGP_READ_PACKET_MAX. + */ +DEFPY (bgp_wpkt_quanta, + bgp_wpkt_quanta_cmd, + "[no] write-quanta (1-64)$quanta", + NO_STR + "How many packets to write to peer socket per run\n" + "Number of packets\n") +{ + return bgp_wpkt_quanta_config_vty(vty, quanta, !no); +} + +DEFPY (bgp_rpkt_quanta, + bgp_rpkt_quanta_cmd, + "[no] read-quanta (1-10)$quanta", + NO_STR + "How many packets to read from peer socket per I/O cycle\n" + "Number of packets\n") +{ + return bgp_rpkt_quanta_config_vty(vty, quanta, !no); +} + +void bgp_config_write_coalesce_time(struct vty *vty, struct bgp *bgp) +{ + if (!bgp->heuristic_coalesce) + vty_out(vty, " coalesce-time %u\n", bgp->coalesce_time); +} + +/* BGP TCP keepalive */ +static void bgp_config_tcp_keepalive(struct vty *vty, struct bgp *bgp) +{ + if (bgp->tcp_keepalive_idle) { + vty_out(vty, " bgp tcp-keepalive %u %u %u\n", + bgp->tcp_keepalive_idle, bgp->tcp_keepalive_intvl, + bgp->tcp_keepalive_probes); + } +} + +DEFUN (bgp_coalesce_time, + bgp_coalesce_time_cmd, + "coalesce-time (0-4294967295)", + "Subgroup coalesce timer\n" + "Subgroup coalesce timer value (in ms)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + int idx = 0; + + bgp->heuristic_coalesce = false; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + bgp->coalesce_time = strtoul(argv[idx]->arg, NULL, 10); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_coalesce_time, + no_bgp_coalesce_time_cmd, + "no coalesce-time (0-4294967295)", + NO_STR + "Subgroup coalesce timer\n" + "Subgroup coalesce timer value (in ms)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->heuristic_coalesce = true; + bgp->coalesce_time = BGP_DEFAULT_SUBGROUP_COALESCE_TIME; + return CMD_SUCCESS; +} + +/* Maximum-paths configuration */ +DEFUN (bgp_maxpaths, + bgp_maxpaths_cmd, + "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Forward packets over multiple paths\n" + "Number of paths\n") +{ + int idx_number = 1; + return bgp_maxpaths_config_vty(vty, BGP_PEER_EBGP, + argv[idx_number]->arg, 0, 1); +} + +ALIAS_HIDDEN(bgp_maxpaths, bgp_maxpaths_hidden_cmd, + "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Forward packets over multiple paths\n" + "Number of paths\n") + +DEFUN (bgp_maxpaths_ibgp, + bgp_maxpaths_ibgp_cmd, + "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n") +{ + int idx_number = 2; + return bgp_maxpaths_config_vty(vty, BGP_PEER_IBGP, + argv[idx_number]->arg, 0, 1); +} + +ALIAS_HIDDEN(bgp_maxpaths_ibgp, bgp_maxpaths_ibgp_hidden_cmd, + "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n") + +DEFUN (bgp_maxpaths_ibgp_cluster, + bgp_maxpaths_ibgp_cluster_cmd, + "maximum-paths ibgp " CMD_RANGE_STR(1, MULTIPATH_NUM) " equal-cluster-length", + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n" + "Match the cluster length\n") +{ + int idx_number = 2; + return bgp_maxpaths_config_vty(vty, BGP_PEER_IBGP, + argv[idx_number]->arg, true, 1); +} + +ALIAS_HIDDEN(bgp_maxpaths_ibgp_cluster, bgp_maxpaths_ibgp_cluster_hidden_cmd, + "maximum-paths ibgp " CMD_RANGE_STR( + 1, MULTIPATH_NUM) " equal-cluster-length", + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n" + "Match the cluster length\n") + +DEFUN (no_bgp_maxpaths, + no_bgp_maxpaths_cmd, + "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", + NO_STR + "Forward packets over multiple paths\n" + "Number of paths\n") +{ + return bgp_maxpaths_config_vty(vty, BGP_PEER_EBGP, NULL, 0, 0); +} + +ALIAS_HIDDEN(no_bgp_maxpaths, no_bgp_maxpaths_hidden_cmd, + "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR + "Forward packets over multiple paths\n" + "Number of paths\n") + +DEFUN (no_bgp_maxpaths_ibgp, + no_bgp_maxpaths_ibgp_cmd, + "no maximum-paths ibgp [" CMD_RANGE_STR(1, MULTIPATH_NUM) " [equal-cluster-length]]", + NO_STR + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n" + "Match the cluster length\n") +{ + return bgp_maxpaths_config_vty(vty, BGP_PEER_IBGP, NULL, 0, 0); +} + +ALIAS_HIDDEN(no_bgp_maxpaths_ibgp, no_bgp_maxpaths_ibgp_hidden_cmd, + "no maximum-paths ibgp [" CMD_RANGE_STR( + 1, MULTIPATH_NUM) " [equal-cluster-length]]", + NO_STR + "Forward packets over multiple paths\n" + "iBGP-multipath\n" + "Number of paths\n" + "Match the cluster length\n") + +static void bgp_config_write_maxpaths(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + if (bgp->maxpaths[afi][safi].maxpaths_ebgp != multipath_num) { + vty_out(vty, " maximum-paths %d\n", + bgp->maxpaths[afi][safi].maxpaths_ebgp); + } + + if (bgp->maxpaths[afi][safi].maxpaths_ibgp != multipath_num) { + vty_out(vty, " maximum-paths ibgp %d", + bgp->maxpaths[afi][safi].maxpaths_ibgp); + if (bgp->maxpaths[afi][safi].same_clusterlen) + vty_out(vty, " equal-cluster-length"); + vty_out(vty, "\n"); + } +} + +/* BGP timers. */ + +DEFUN (bgp_timers, + bgp_timers_cmd, + "timers bgp (0-65535) (0-65535)", + "Adjust routing timers\n" + "BGP timers\n" + "Keepalive interval\n" + "Holdtime\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 2; + int idx_number_2 = 3; + unsigned long keepalive = 0; + unsigned long holdtime = 0; + + keepalive = strtoul(argv[idx_number]->arg, NULL, 10); + holdtime = strtoul(argv[idx_number_2]->arg, NULL, 10); + + /* Holdtime value check. */ + if (holdtime < 3 && holdtime != 0) { + vty_out(vty, + "%% hold time value must be either 0 or greater than 3\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_timers_set(vty, bgp, keepalive, holdtime, DFLT_BGP_CONNECT_RETRY, + BGP_DEFAULT_DELAYOPEN); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_timers, + no_bgp_timers_cmd, + "no timers bgp [(0-65535) (0-65535)]", + NO_STR + "Adjust routing timers\n" + "BGP timers\n" + "Keepalive interval\n" + "Holdtime\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_timers_set(vty, bgp, DFLT_BGP_KEEPALIVE, DFLT_BGP_HOLDTIME, + DFLT_BGP_CONNECT_RETRY, BGP_DEFAULT_DELAYOPEN); + + return CMD_SUCCESS; +} + +/* BGP minimum holdtime. */ + +DEFUN(bgp_minimum_holdtime, bgp_minimum_holdtime_cmd, + "bgp minimum-holdtime (1-65535)", + "BGP specific commands\n" + "BGP minimum holdtime\n" + "Seconds\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 2; + unsigned long min_holdtime; + + min_holdtime = strtoul(argv[idx_number]->arg, NULL, 10); + + bgp->default_min_holdtime = min_holdtime; + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_minimum_holdtime, no_bgp_minimum_holdtime_cmd, + "no bgp minimum-holdtime [(1-65535)]", + NO_STR + "BGP specific commands\n" + "BGP minimum holdtime\n" + "Seconds\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->default_min_holdtime = 0; + + return CMD_SUCCESS; +} + +DEFPY(bgp_tcp_keepalive, bgp_tcp_keepalive_cmd, + "bgp tcp-keepalive (1-65535)$idle (1-65535)$intvl (1-30)$probes", + BGP_STR + "TCP keepalive parameters\n" + "TCP keepalive idle time (seconds)\n" + "TCP keepalive interval (seconds)\n" + "TCP keepalive maximum probes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp_tcp_keepalive_set(bgp, (uint16_t)idle, (uint16_t)intvl, + (uint16_t)probes); + + return CMD_SUCCESS; +} + +DEFPY(no_bgp_tcp_keepalive, no_bgp_tcp_keepalive_cmd, + "no bgp tcp-keepalive [(1-65535) (1-65535) (1-30)]", + NO_STR + BGP_STR + "TCP keepalive parameters\n" + "TCP keepalive idle time (seconds)\n" + "TCP keepalive interval (seconds)\n" + "TCP keepalive maximum probes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp_tcp_keepalive_unset(bgp); + + return CMD_SUCCESS; +} + +DEFUN (bgp_client_to_client_reflection, + bgp_client_to_client_reflection_cmd, + "bgp client-to-client reflection", + BGP_STR + "Configure client to client route reflection\n" + "reflection of routes allowed\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_NO_CLIENT_TO_CLIENT); + bgp_clear_star_soft_out(vty, bgp->name); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_client_to_client_reflection, + no_bgp_client_to_client_reflection_cmd, + "no bgp client-to-client reflection", + NO_STR + BGP_STR + "Configure client to client route reflection\n" + "reflection of routes allowed\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_NO_CLIENT_TO_CLIENT); + bgp_clear_star_soft_out(vty, bgp->name); + + return CMD_SUCCESS; +} + +/* "bgp always-compare-med" configuration. */ +DEFUN (bgp_always_compare_med, + bgp_always_compare_med_cmd, + "bgp always-compare-med", + BGP_STR + "Allow comparing MED from different neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_ALWAYS_COMPARE_MED); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_always_compare_med, + no_bgp_always_compare_med_cmd, + "no bgp always-compare-med", + NO_STR + BGP_STR + "Allow comparing MED from different neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_ALWAYS_COMPARE_MED); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + + +DEFUN(bgp_ebgp_requires_policy, bgp_ebgp_requires_policy_cmd, + "bgp ebgp-requires-policy", + BGP_STR + "Require in and out policy for eBGP peers (RFC8212)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY); + return CMD_SUCCESS; +} + +DEFUN(no_bgp_ebgp_requires_policy, no_bgp_ebgp_requires_policy_cmd, + "no bgp ebgp-requires-policy", + NO_STR + BGP_STR + "Require in and out policy for eBGP peers (RFC8212)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY); + return CMD_SUCCESS; +} + +DEFPY(bgp_enforce_first_as, + bgp_enforce_first_as_cmd, + "[no] bgp enforce-first-as", + NO_STR + BGP_STR + "Enforce the first AS for EBGP routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_ENFORCE_FIRST_AS); + else + SET_FLAG(bgp->flags, BGP_FLAG_ENFORCE_FIRST_AS); + + return CMD_SUCCESS; +} + +DEFPY(bgp_lu_uses_explicit_null, bgp_lu_uses_explicit_null_cmd, + "[no] bgp labeled-unicast $value", + NO_STR BGP_STR + "BGP Labeled-unicast options\n" + "Use explicit-null label values for all local prefixes\n" + "Use the IPv4 explicit-null label value for IPv4 local prefixes\n" + "Use the IPv6 explicit-null label value for IPv6 local prefixes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint64_t label_mode; + + if (strmatch(value, "ipv4-explicit-null")) + label_mode = BGP_FLAG_LU_IPV4_EXPLICIT_NULL; + else if (strmatch(value, "ipv6-explicit-null")) + label_mode = BGP_FLAG_LU_IPV6_EXPLICIT_NULL; + else + label_mode = BGP_FLAG_LU_IPV4_EXPLICIT_NULL | + BGP_FLAG_LU_IPV6_EXPLICIT_NULL; + if (no) + UNSET_FLAG(bgp->flags, label_mode); + else + SET_FLAG(bgp->flags, label_mode); + return CMD_SUCCESS; +} + +DEFUN(bgp_suppress_duplicates, bgp_suppress_duplicates_cmd, + "bgp suppress-duplicates", + BGP_STR + "Suppress duplicate updates if the route actually not changed\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES); + return CMD_SUCCESS; +} + +DEFUN(no_bgp_suppress_duplicates, no_bgp_suppress_duplicates_cmd, + "no bgp suppress-duplicates", + NO_STR + BGP_STR + "Suppress duplicate updates if the route actually not changed\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES); + return CMD_SUCCESS; +} + +DEFUN(bgp_reject_as_sets, bgp_reject_as_sets_cmd, + "bgp reject-as-sets", + BGP_STR + "Reject routes with AS_SET or AS_CONFED_SET flag\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct peer *peer; + + bgp->reject_as_sets = true; + + /* Reset existing BGP sessions to reject routes + * with aspath containing AS_SET or AS_CONFED_SET. + */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_AS_SETS_REJECT; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_reject_as_sets, no_bgp_reject_as_sets_cmd, + "no bgp reject-as-sets", + NO_STR + BGP_STR + "Reject routes with AS_SET or AS_CONFED_SET flag\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct peer *peer; + + bgp->reject_as_sets = false; + + /* Reset existing BGP sessions to reject routes + * with aspath containing AS_SET or AS_CONFED_SET. + */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_AS_SETS_REJECT; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + + return CMD_SUCCESS; +} + +/* "bgp deterministic-med" configuration. */ +DEFUN (bgp_deterministic_med, + bgp_deterministic_med_cmd, + "bgp deterministic-med", + BGP_STR + "Pick the best-MED path among paths advertised from the neighboring AS\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED)) { + SET_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED); + bgp_recalculate_all_bestpaths(bgp); + } + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_deterministic_med, + no_bgp_deterministic_med_cmd, + "no bgp deterministic-med", + NO_STR + BGP_STR + "Pick the best-MED path among paths advertised from the neighboring AS\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int bestpath_per_as_used; + afi_t afi; + safi_t safi; + struct peer *peer; + struct listnode *node, *nnode; + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED)) { + bestpath_per_as_used = 0; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + FOREACH_AFI_SAFI (afi, safi) + if (bgp_addpath_dmed_required( + peer->addpath_type[afi][safi])) { + bestpath_per_as_used = 1; + break; + } + + if (bestpath_per_as_used) + break; + } + + if (bestpath_per_as_used) { + vty_out(vty, + "bgp deterministic-med cannot be disabled while addpath-tx-bestpath-per-AS is in use\n"); + return CMD_WARNING_CONFIG_FAILED; + } else { + UNSET_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED); + bgp_recalculate_all_bestpaths(bgp); + } + } + + return CMD_SUCCESS; +} + +/* "bgp graceful-restart mode" configuration. */ +DEFUN (bgp_graceful_restart, + bgp_graceful_restart_cmd, + "bgp graceful-restart", + BGP_STR + GR_CMD + ) +{ + int ret = BGP_GR_FAILURE; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] bgp_graceful_restart_cmd : START "); + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + ret = bgp_gr_update_all(bgp, GLOBAL_GR_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, + bgp->peer, + ret); + vty_out(vty, + "Graceful restart configuration changed, reset all peers to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] bgp_graceful_restart_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (no_bgp_graceful_restart, + no_bgp_graceful_restart_cmd, + "no bgp graceful-restart", + NO_STR + BGP_STR + NO_GR_CMD + ) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] no_bgp_graceful_restart_cmd : START "); + + int ret = BGP_GR_FAILURE; + + ret = bgp_gr_update_all(bgp, NO_GLOBAL_GR_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, + bgp->peer, + ret); + vty_out(vty, + "Graceful restart configuration changed, reset all peers to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] no_bgp_graceful_restart_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (bgp_graceful_restart_stalepath_time, + bgp_graceful_restart_stalepath_time_cmd, + "bgp graceful-restart stalepath-time (1-4095)", + BGP_STR + "Graceful restart capability parameters\n" + "Set the max time to hold onto restarting peer's stale paths\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t stalepath; + + stalepath = strtoul(argv[idx_number]->arg, NULL, 10); + bgp->stalepath_time = stalepath; + return CMD_SUCCESS; +} + +DEFUN (bgp_graceful_restart_restart_time, + bgp_graceful_restart_restart_time_cmd, + "bgp graceful-restart restart-time (0-4095)", + BGP_STR + "Graceful restart capability parameters\n" + "Set the time to wait to delete stale routes before a BGP open message is received\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t restart; + struct listnode *node, *nnode; + struct peer *peer; + + restart = strtoul(argv[idx_number]->arg, NULL, 10); + bgp->restart_time = restart; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_RESTART, + CAPABILITY_ACTION_SET); + + return CMD_SUCCESS; +} + +DEFUN (bgp_graceful_restart_select_defer_time, + bgp_graceful_restart_select_defer_time_cmd, + "bgp graceful-restart select-defer-time (0-3600)", + BGP_STR + "Graceful restart capability parameters\n" + "Set the time to defer the BGP route selection after restart\n" + "Delay value (seconds, 0 - disable)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t defer_time; + + defer_time = strtoul(argv[idx_number]->arg, NULL, 10); + bgp->select_defer_time = defer_time; + if (defer_time == 0) + SET_FLAG(bgp->flags, BGP_FLAG_SELECT_DEFER_DISABLE); + else + UNSET_FLAG(bgp->flags, BGP_FLAG_SELECT_DEFER_DISABLE); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_restart_stalepath_time, + no_bgp_graceful_restart_stalepath_time_cmd, + "no bgp graceful-restart stalepath-time [(1-4095)]", + NO_STR + BGP_STR + "Graceful restart capability parameters\n" + "Set the max time to hold onto restarting peer's stale paths\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_restart_restart_time, + no_bgp_graceful_restart_restart_time_cmd, + "no bgp graceful-restart restart-time [(0-4095)]", + NO_STR + BGP_STR + "Graceful restart capability parameters\n" + "Set the time to wait to delete stale routes before a BGP open message is received\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct peer *peer; + + bgp->restart_time = BGP_DEFAULT_RESTART_TIME; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_RESTART, + CAPABILITY_ACTION_UNSET); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_restart_select_defer_time, + no_bgp_graceful_restart_select_defer_time_cmd, + "no bgp graceful-restart select-defer-time [(0-3600)]", + NO_STR + BGP_STR + "Graceful restart capability parameters\n" + "Set the time to defer the BGP route selection after restart\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->select_defer_time = BGP_DEFAULT_SELECT_DEFERRAL_TIME; + UNSET_FLAG(bgp->flags, BGP_FLAG_SELECT_DEFER_DISABLE); + + return CMD_SUCCESS; +} + +DEFUN (bgp_graceful_restart_preserve_fw, + bgp_graceful_restart_preserve_fw_cmd, + "bgp graceful-restart preserve-fw-state", + BGP_STR + "Graceful restart capability parameters\n" + "Sets F-bit indication that fib is preserved while doing Graceful Restart\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_GR_PRESERVE_FWD); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_restart_preserve_fw, + no_bgp_graceful_restart_preserve_fw_cmd, + "no bgp graceful-restart preserve-fw-state", + NO_STR + BGP_STR + "Graceful restart capability parameters\n" + "Unsets F-bit indication that fib is preserved while doing Graceful Restart\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_GR_PRESERVE_FWD); + return CMD_SUCCESS; +} + +DEFPY (bgp_graceful_restart_notification, + bgp_graceful_restart_notification_cmd, + "[no$no] bgp graceful-restart notification", + NO_STR + BGP_STR + "Graceful restart capability parameters\n" + "Indicate Graceful Restart support for BGP NOTIFICATION messages\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct peer *peer; + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_NOTIFICATION); + else + SET_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_NOTIFICATION); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_RESTART, + CAPABILITY_ACTION_SET); + + return CMD_SUCCESS; +} + +DEFPY (bgp_administrative_reset, + bgp_administrative_reset_cmd, + "[no$no] bgp hard-administrative-reset", + NO_STR + BGP_STR + "Send Hard Reset CEASE Notification for 'Administrative Reset'\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_HARD_ADMIN_RESET); + else + SET_FLAG(bgp->flags, BGP_FLAG_HARD_ADMIN_RESET); + + return CMD_SUCCESS; +} + +DEFUN (bgp_graceful_restart_disable, + bgp_graceful_restart_disable_cmd, + "bgp graceful-restart-disable", + BGP_STR + GR_DISABLE) +{ + int ret = BGP_GR_FAILURE; + struct listnode *node, *nnode; + struct peer *peer; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_graceful_restart_disable_cmd : START "); + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + ret = bgp_gr_update_all(bgp, GLOBAL_DISABLE_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, + bgp->peer, + ret); + vty_out(vty, + "Graceful restart configuration changed, reset all peers to take effect\n"); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_RESTART, + CAPABILITY_ACTION_UNSET); + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_LLGR, + CAPABILITY_ACTION_UNSET); + } + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] bgp_graceful_restart_disable_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (no_bgp_graceful_restart_disable, + no_bgp_graceful_restart_disable_cmd, + "no bgp graceful-restart-disable", + NO_STR + BGP_STR + NO_GR_DISABLE + ) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_graceful_restart_disable_cmd : START "); + + int ret = BGP_GR_FAILURE; + + ret = bgp_gr_update_all(bgp, NO_GLOBAL_DISABLE_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, + bgp->peer, + ret); + vty_out(vty, + "Graceful restart configuration changed, reset all peers to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_graceful_restart_disable_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (bgp_neighbor_graceful_restart_set, + bgp_neighbor_graceful_restart_set_cmd, + "neighbor graceful-restart", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + GR_NEIGHBOR_CMD + ) +{ + int idx_peer = 1; + struct peer *peer; + int result = BGP_GR_FAILURE, ret = BGP_GR_SUCCESS; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_neighbor_graceful_restart_set_cmd : START "); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + result = bgp_neighbor_graceful_restart(peer, PEER_GR_CMD); + if (result == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_neighbor_graceful_restart_set_cmd : END "); + + if (ret != BGP_GR_SUCCESS) + vty_out(vty, + "As part of configuring graceful-restart, capability send to zebra failed\n"); + + return bgp_vty_return(vty, result); +} + +DEFUN (no_bgp_neighbor_graceful_restart, + no_bgp_neighbor_graceful_restart_set_cmd, + "no neighbor graceful-restart", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + NO_GR_NEIGHBOR_CMD + ) +{ + int idx_peer = 2; + int result = BGP_GR_FAILURE, ret = BGP_GR_SUCCESS; + struct peer *peer; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_set_cmd : START "); + + result = bgp_neighbor_graceful_restart(peer, NO_PEER_GR_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_set_cmd : END "); + + if (ret != BGP_GR_SUCCESS) + vty_out(vty, + "As part of configuring graceful-restart, capability send to zebra failed\n"); + + return bgp_vty_return(vty, result); +} + +DEFUN (bgp_neighbor_graceful_restart_helper_set, + bgp_neighbor_graceful_restart_helper_set_cmd, + "neighbor graceful-restart-helper", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + GR_NEIGHBOR_HELPER_CMD + ) +{ + int idx_peer = 1; + struct peer *peer; + int ret = BGP_GR_FAILURE; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_neighbor_graceful_restart_helper_set_cmd : START "); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + + ret = bgp_neighbor_graceful_restart(peer, PEER_HELPER_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_neighbor_graceful_restart_helper_set_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (no_bgp_neighbor_graceful_restart_helper, + no_bgp_neighbor_graceful_restart_helper_set_cmd, + "no neighbor graceful-restart-helper", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + NO_GR_NEIGHBOR_HELPER_CMD + ) +{ + int idx_peer = 2; + int ret = BGP_GR_FAILURE; + struct peer *peer; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_helper_set_cmd : START "); + + ret = bgp_neighbor_graceful_restart(peer, NO_PEER_HELPER_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_helper_set_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (bgp_neighbor_graceful_restart_disable_set, + bgp_neighbor_graceful_restart_disable_set_cmd, + "neighbor graceful-restart-disable", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + GR_NEIGHBOR_DISABLE_CMD + ) +{ + int idx_peer = 1; + struct peer *peer; + int ret = BGP_GR_FAILURE; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] bgp_neighbor_graceful_restart_disable_set_cmd : START "); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = bgp_neighbor_graceful_restart(peer, PEER_DISABLE_CMD); + if (ret == BGP_GR_SUCCESS) { + if (peer->bgp->t_startup) + bgp_peer_gr_flags_update(peer); + + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR]bgp_neighbor_graceful_restart_disable_set_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFUN (no_bgp_neighbor_graceful_restart_disable, + no_bgp_neighbor_graceful_restart_disable_set_cmd, + "no neighbor graceful-restart-disable", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + NO_GR_NEIGHBOR_DISABLE_CMD + ) +{ + int idx_peer = 2; + int ret = BGP_GR_FAILURE; + struct peer *peer; + + VTY_BGP_GR_DEFINE_LOOP_VARIABLE; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_disable_set_cmd : START "); + + ret = bgp_neighbor_graceful_restart(peer, NO_PEER_DISABLE_CMD); + if (ret == BGP_GR_SUCCESS) { + VTY_BGP_GR_ROUTER_DETECT(bgp, peer, peer->bgp->peer); + VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(peer->bgp, ret); + vty_out(vty, + "Graceful restart configuration changed, reset this peer to take effect\n"); + } + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug( + "[BGP_GR] no_bgp_neighbor_graceful_restart_disable_set_cmd : END "); + + return bgp_vty_return(vty, ret); +} + +DEFPY (neighbor_graceful_shutdown, + neighbor_graceful_shutdown_cmd, + "[no$no] neighbor $neighbor graceful-shutdown", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Graceful shutdown\n") +{ + afi_t afi; + safi_t safi; + struct peer *peer; + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + ret = peer_flag_unset_vty(vty, neighbor, + PEER_FLAG_GRACEFUL_SHUTDOWN); + else + ret = peer_flag_set_vty(vty, neighbor, + PEER_FLAG_GRACEFUL_SHUTDOWN); + + FOREACH_AFI_SAFI (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + bgp_clear(vty, bgp, afi, safi, clear_peer, BGP_CLEAR_SOFT_IN, + neighbor); + } + + return ret; +} + +DEFUN_HIDDEN (bgp_graceful_restart_disable_eor, + bgp_graceful_restart_disable_eor_cmd, + "bgp graceful-restart disable-eor", + BGP_STR + "Graceful restart configuration parameters\n" + "Disable EOR Check\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_GR_DISABLE_EOR); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_bgp_graceful_restart_disable_eor, + no_bgp_graceful_restart_disable_eor_cmd, + "no bgp graceful-restart disable-eor", + NO_STR + BGP_STR + "Graceful restart configuration parameters\n" + "Disable EOR Check\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_GR_DISABLE_EOR); + + return CMD_SUCCESS; +} + +DEFUN (bgp_graceful_restart_rib_stale_time, + bgp_graceful_restart_rib_stale_time_cmd, + "bgp graceful-restart rib-stale-time (1-3600)", + BGP_STR + "Graceful restart configuration parameters\n" + "Specify the stale route removal timer in rib\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t stale_time; + + stale_time = strtoul(argv[idx_number]->arg, NULL, 10); + bgp->rib_stale_time = stale_time; + /* Send the stale timer update message to RIB */ + if (bgp_zebra_stale_timer_update(bgp)) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_restart_rib_stale_time, + no_bgp_graceful_restart_rib_stale_time_cmd, + "no bgp graceful-restart rib-stale-time [(1-3600)]", + NO_STR + BGP_STR + "Graceful restart configuration parameters\n" + "Specify the stale route removal timer in rib\n" + "Delay value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->rib_stale_time = BGP_DEFAULT_RIB_STALE_TIME; + /* Send the stale timer update message to RIB */ + if (bgp_zebra_stale_timer_update(bgp)) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(bgp_llgr_stalepath_time, bgp_llgr_stalepath_time_cmd, + "bgp long-lived-graceful-restart stale-time (1-16777215)", + BGP_STR + "Enable Long-lived Graceful Restart\n" + "Specifies maximum time to wait before purging long-lived stale routes\n" + "Stale time value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + uint32_t llgr_stale_time; + struct listnode *node, *nnode; + struct peer *peer; + + llgr_stale_time = strtoul(argv[3]->arg, NULL, 10); + bgp->llgr_stale_time = llgr_stale_time; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_LLGR, CAPABILITY_ACTION_SET); + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_llgr_stalepath_time, no_bgp_llgr_stalepath_time_cmd, + "no bgp long-lived-graceful-restart stale-time [(1-16777215)]", + NO_STR BGP_STR + "Enable Long-lived Graceful Restart\n" + "Specifies maximum time to wait before purging long-lived stale routes\n" + "Stale time value (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct peer *peer; + + bgp->llgr_stale_time = BGP_DEFAULT_LLGR_STALE_TIME; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_LLGR, + CAPABILITY_ACTION_UNSET); + + return CMD_SUCCESS; +} + +static inline void bgp_initiate_graceful_shut_unshut(struct vty *vty, + struct bgp *bgp) +{ + bgp_static_redo_import_check(bgp); + bgp_redistribute_redo(bgp); + bgp_clear_star_soft_out(vty, bgp->name); + bgp_clear_star_soft_in(vty, bgp->name); +} + +static int bgp_global_graceful_shutdown_config_vty(struct vty *vty) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + bool vrf_cfg = false; + + if (CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) + return CMD_SUCCESS; + + /* See if graceful-shutdown is set per-vrf and warn user to delete */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN)) { + vty_out(vty, + "%% graceful-shutdown configuration found in vrf %s\n", + bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT ? + VRF_DEFAULT_NAME : bgp->name); + vrf_cfg = true; + } + } + + if (vrf_cfg) { + vty_out(vty, + "%%Failed: global graceful-shutdown not permitted\n"); + return CMD_WARNING; + } + + /* Set flag globally */ + SET_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN); + + /* Initiate processing for all BGP instances. */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + bgp_initiate_graceful_shut_unshut(vty, bgp); + + return CMD_SUCCESS; +} + +static int bgp_global_graceful_shutdown_deconfig_vty(struct vty *vty) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + if (!CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) + return CMD_SUCCESS; + + /* Unset flag globally */ + UNSET_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN); + + /* Initiate processing for all BGP instances. */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + bgp_initiate_graceful_shut_unshut(vty, bgp); + + return CMD_SUCCESS; +} + +/* "bgp graceful-shutdown" configuration */ +DEFUN (bgp_graceful_shutdown, + bgp_graceful_shutdown_cmd, + "bgp graceful-shutdown", + BGP_STR + "Graceful shutdown parameters\n") +{ + if (vty->node == CONFIG_NODE) + return bgp_global_graceful_shutdown_config_vty(vty); + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* if configured globally, per-instance config is not allowed */ + if (CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) { + vty_out(vty, + "%%Failed: per-vrf graceful-shutdown config not permitted with global graceful-shutdown\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN)) { + SET_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN); + bgp_initiate_graceful_shut_unshut(vty, bgp); + } + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_graceful_shutdown, + no_bgp_graceful_shutdown_cmd, + "no bgp graceful-shutdown", + NO_STR + BGP_STR + "Graceful shutdown parameters\n") +{ + if (vty->node == CONFIG_NODE) + return bgp_global_graceful_shutdown_deconfig_vty(vty); + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* If configured globally, cannot remove from one bgp instance */ + if (CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) { + vty_out(vty, + "%%Failed: bgp graceful-shutdown configured globally. Delete per-vrf not permitted\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN)) { + UNSET_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN); + bgp_initiate_graceful_shut_unshut(vty, bgp); + } + + return CMD_SUCCESS; +} + +/* "bgp fast-external-failover" configuration. */ +DEFUN (bgp_fast_external_failover, + bgp_fast_external_failover_cmd, + "bgp fast-external-failover", + BGP_STR + "Immediately reset session if a link to a directly connected external peer goes down\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_fast_external_failover, + no_bgp_fast_external_failover_cmd, + "no bgp fast-external-failover", + NO_STR + BGP_STR + "Immediately reset session if a link to a directly connected external peer goes down\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER); + return CMD_SUCCESS; +} + +DEFPY (bgp_bestpath_aigp, + bgp_bestpath_aigp_cmd, + "[no$no] bgp bestpath aigp", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "Evaluate the AIGP attribute during the best path selection process\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_COMPARE_AIGP); + else + SET_FLAG(bgp->flags, BGP_FLAG_COMPARE_AIGP); + + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath compare-routerid" configuration. */ +DEFUN (bgp_bestpath_compare_router_id, + bgp_bestpath_compare_router_id_cmd, + "bgp bestpath compare-routerid", + BGP_STR + "Change the default bestpath selection\n" + "Compare router-id for identical EBGP paths\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_bestpath_compare_router_id, + no_bgp_bestpath_compare_router_id_cmd, + "no bgp bestpath compare-routerid", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "Compare router-id for identical EBGP paths\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath as-path ignore" configuration. */ +DEFUN (bgp_bestpath_aspath_ignore, + bgp_bestpath_aspath_ignore_cmd, + "bgp bestpath as-path ignore", + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Ignore as-path length in selecting a route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_bestpath_aspath_ignore, + no_bgp_bestpath_aspath_ignore_cmd, + "no bgp bestpath as-path ignore", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Ignore as-path length in selecting a route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath as-path confed" configuration. */ +DEFUN (bgp_bestpath_aspath_confed, + bgp_bestpath_aspath_confed_cmd, + "bgp bestpath as-path confed", + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Compare path lengths including confederation sets & sequences in selecting a route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_bestpath_aspath_confed, + no_bgp_bestpath_aspath_confed_cmd, + "no bgp bestpath as-path confed", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Compare path lengths including confederation sets & sequences in selecting a route\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath as-path multipath-relax" configuration. */ +DEFUN (bgp_bestpath_aspath_multipath_relax, + bgp_bestpath_aspath_multipath_relax_cmd, + "bgp bestpath as-path multipath-relax []", + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Allow load sharing across routes that have different AS paths (but same length)\n" + "Generate an AS_SET\n" + "Do not generate an AS_SET\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx = 0; + SET_FLAG(bgp->flags, BGP_FLAG_ASPATH_MULTIPATH_RELAX); + + /* no-as-set is now the default behavior so we can silently + * ignore it */ + if (argv_find(argv, argc, "as-set", &idx)) + SET_FLAG(bgp->flags, BGP_FLAG_MULTIPATH_RELAX_AS_SET); + else + UNSET_FLAG(bgp->flags, BGP_FLAG_MULTIPATH_RELAX_AS_SET); + + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_bestpath_aspath_multipath_relax, + no_bgp_bestpath_aspath_multipath_relax_cmd, + "no bgp bestpath as-path multipath-relax []", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "AS-path attribute\n" + "Allow load sharing across routes that have different AS paths (but same length)\n" + "Generate an AS_SET\n" + "Do not generate an AS_SET\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_ASPATH_MULTIPATH_RELAX); + UNSET_FLAG(bgp->flags, BGP_FLAG_MULTIPATH_RELAX_AS_SET); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath peer-type multipath-relax" configuration. */ +DEFUN(bgp_bestpath_peer_type_multipath_relax, + bgp_bestpath_peer_type_multipath_relax_cmd, + "bgp bestpath peer-type multipath-relax", + BGP_STR + "Change the default bestpath selection\n" + "Peer type\n" + "Allow load sharing across routes learned from different peer types\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_bestpath_peer_type_multipath_relax, + no_bgp_bestpath_peer_type_multipath_relax_cmd, + "no bgp bestpath peer-type multipath-relax", + NO_STR BGP_STR + "Change the default bestpath selection\n" + "Peer type\n" + "Allow load sharing across routes learned from different peer types\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX); + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp log-neighbor-changes" configuration. */ +DEFUN (bgp_log_neighbor_changes, + bgp_log_neighbor_changes_cmd, + "bgp log-neighbor-changes", + BGP_STR + "Log neighbor up/down and reset reason\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_log_neighbor_changes, + no_bgp_log_neighbor_changes_cmd, + "no bgp log-neighbor-changes", + NO_STR + BGP_STR + "Log neighbor up/down and reset reason\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES); + return CMD_SUCCESS; +} + +/* "bgp bestpath med" configuration. */ +DEFUN (bgp_bestpath_med, + bgp_bestpath_med_cmd, + "bgp bestpath med ", + BGP_STR + "Change the default bestpath selection\n" + "MED attribute\n" + "Compare MED among confederation paths\n" + "Treat missing MED as the least preferred one\n" + "Treat missing MED as the least preferred one\n" + "Compare MED among confederation paths\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + int idx = 0; + if (argv_find(argv, argc, "confed", &idx)) + SET_FLAG(bgp->flags, BGP_FLAG_MED_CONFED); + idx = 0; + if (argv_find(argv, argc, "missing-as-worst", &idx)) + SET_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST); + + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_bestpath_med, + no_bgp_bestpath_med_cmd, + "no bgp bestpath med ", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "MED attribute\n" + "Compare MED among confederation paths\n" + "Treat missing MED as the least preferred one\n" + "Treat missing MED as the least preferred one\n" + "Compare MED among confederation paths\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + int idx = 0; + if (argv_find(argv, argc, "confed", &idx)) + UNSET_FLAG(bgp->flags, BGP_FLAG_MED_CONFED); + idx = 0; + if (argv_find(argv, argc, "missing-as-worst", &idx)) + UNSET_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST); + + bgp_recalculate_all_bestpaths(bgp); + + return CMD_SUCCESS; +} + +/* "bgp bestpath bandwidth" configuration. */ +DEFPY (bgp_bestpath_bw, + bgp_bestpath_bw_cmd, + "bgp bestpath bandwidth $bw_cfg", + BGP_STR + "Change the default bestpath selection\n" + "Link Bandwidth attribute\n" + "Ignore link bandwidth (i.e., do regular ECMP, not weighted)\n" + "Ignore paths without link bandwidth for ECMP (if other paths have it)\n" + "Assign a low default weight (value 1) to paths not having link bandwidth\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + safi_t safi; + + if (!bw_cfg) { + vty_out(vty, "%% Bandwidth configuration must be specified\n"); + return CMD_ERR_INCOMPLETE; + } + if (!strcmp(bw_cfg, "ignore")) + bgp->lb_handling = BGP_LINK_BW_IGNORE_BW; + else if (!strcmp(bw_cfg, "skip-missing")) + bgp->lb_handling = BGP_LINK_BW_SKIP_MISSING; + else if (!strcmp(bw_cfg, "default-weight-for-missing")) + bgp->lb_handling = BGP_LINK_BW_DEFWT_4_MISSING; + else + return CMD_ERR_NO_MATCH; + + /* This config is used in route install, so redo that. */ + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_fibupd_safi(safi)) + continue; + bgp_zebra_announce_table(bgp, afi, safi); + } + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_bestpath_bw, + no_bgp_bestpath_bw_cmd, + "no bgp bestpath bandwidth [$bw_cfg]", + NO_STR + BGP_STR + "Change the default bestpath selection\n" + "Link Bandwidth attribute\n" + "Ignore link bandwidth (i.e., do regular ECMP, not weighted)\n" + "Ignore paths without link bandwidth for ECMP (if other paths have it)\n" + "Assign a low default weight (value 1) to paths not having link bandwidth\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + safi_t safi; + + bgp->lb_handling = BGP_LINK_BW_ECMP; + + /* This config is used in route install, so redo that. */ + FOREACH_AFI_SAFI (afi, safi) { + if (!bgp_fibupd_safi(safi)) + continue; + bgp_zebra_announce_table(bgp, afi, safi); + } + return CMD_SUCCESS; +} + +DEFPY(bgp_default_afi_safi, bgp_default_afi_safi_cmd, + "[no] bgp default $afi_safi", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Activate ipv4-unicast for a peer by default\n" + "Activate ipv4-multicast for a peer by default\n" + "Activate ipv4-vpn for a peer by default\n" + "Activate ipv4-labeled-unicast for a peer by default\n" + "Activate ipv4-flowspec for a peer by default\n" + "Activate ipv6-unicast for a peer by default\n" + "Activate ipv6-multicast for a peer by default\n" + "Activate ipv6-vpn for a peer by default\n" + "Activate ipv6-labeled-unicast for a peer by default\n" + "Activate ipv6-flowspec for a peer by default\n" + "Activate l2vpn-evpn for a peer by default\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + char afi_safi_str[strlen(afi_safi) + 1]; + char *afi_safi_str_tok; + + strlcpy(afi_safi_str, afi_safi, sizeof(afi_safi_str)); + char *afi_str = strtok_r(afi_safi_str, "-", &afi_safi_str_tok); + char *safi_str = strtok_r(NULL, "-", &afi_safi_str_tok); + afi_t afi = bgp_vty_afi_from_str(afi_str); + safi_t safi; + + /* + * Impossible situation but making coverity happy + */ + assert(afi != AFI_MAX); + + if (strmatch(safi_str, "labeled")) + safi = bgp_vty_safi_from_str("labeled-unicast"); + else + safi = bgp_vty_safi_from_str(safi_str); + + assert(safi != SAFI_MAX); + if (no) + bgp->default_af[afi][safi] = false; + else { + if ((safi == SAFI_LABELED_UNICAST + && bgp->default_af[afi][SAFI_UNICAST]) + || (safi == SAFI_UNICAST + && bgp->default_af[afi][SAFI_LABELED_UNICAST])) + bgp_vty_return(vty, BGP_ERR_PEER_SAFI_CONFLICT); + else + bgp->default_af[afi][safi] = true; + } + + return CMD_SUCCESS; +} + +/* Display hostname in certain command outputs */ +DEFUN (bgp_default_show_hostname, + bgp_default_show_hostname_cmd, + "bgp default show-hostname", + BGP_STR + "Configure BGP defaults\n" + "Show hostname in certain command outputs\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_SHOW_HOSTNAME); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_default_show_hostname, + no_bgp_default_show_hostname_cmd, + "no bgp default show-hostname", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Show hostname in certain command outputs\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_SHOW_HOSTNAME); + return CMD_SUCCESS; +} + +/* Display hostname in certain command outputs */ +DEFUN (bgp_default_show_nexthop_hostname, + bgp_default_show_nexthop_hostname_cmd, + "bgp default show-nexthop-hostname", + BGP_STR + "Configure BGP defaults\n" + "Show hostname for nexthop in certain command outputs\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME); + return CMD_SUCCESS; +} + +DEFUN (no_bgp_default_show_nexthop_hostname, + no_bgp_default_show_nexthop_hostname_cmd, + "no bgp default show-nexthop-hostname", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Show hostname for nexthop in certain command outputs\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME); + return CMD_SUCCESS; +} + +DEFPY (bgp_default_software_version_capability, + bgp_default_software_version_capability_cmd, + "[no] bgp default software-version-capability", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Advertise software version capability for all neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY); + else + SET_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY); + + return CMD_SUCCESS; +} + +DEFPY (bgp_default_dynamic_capability, + bgp_default_dynamic_capability_cmd, + "[no] bgp default dynamic-capability", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Advertise dynamic capability for all neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY); + else + SET_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY); + + return CMD_SUCCESS; +} + +/* "bgp network import-check" configuration. */ +DEFUN (bgp_network_import_check, + bgp_network_import_check_cmd, + "bgp network import-check", + BGP_STR + "BGP network command\n" + "Check BGP network route exists in IGP\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK)) { + SET_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK); + bgp_static_redo_import_check(bgp); + } + + return CMD_SUCCESS; +} + +#if CONFDATE > 20241013 +CPP_NOTICE("Drop `bgp network import-check exact` command") +#endif +ALIAS_HIDDEN(bgp_network_import_check, bgp_network_import_check_exact_cmd, + "bgp network import-check exact", + BGP_STR + "BGP network command\n" + "Check BGP network route exists in IGP\n" + "Match route precisely\n") + +DEFUN (no_bgp_network_import_check, + no_bgp_network_import_check_cmd, + "no bgp network import-check", + NO_STR + BGP_STR + "BGP network command\n" + "Check BGP network route exists in IGP\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK)) { + UNSET_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK); + bgp_static_redo_import_check(bgp); + } + + return CMD_SUCCESS; +} + +DEFUN (bgp_default_local_preference, + bgp_default_local_preference_cmd, + "bgp default local-preference (0-4294967295)", + BGP_STR + "Configure BGP defaults\n" + "local preference (higher=more preferred)\n" + "Configure default local preference value\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t local_pref; + + local_pref = strtoul(argv[idx_number]->arg, NULL, 10); + + bgp_default_local_preference_set(bgp, local_pref); + bgp_clear_star_soft_in(vty, bgp->name); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_default_local_preference, + no_bgp_default_local_preference_cmd, + "no bgp default local-preference [(0-4294967295)]", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "local preference (higher=more preferred)\n" + "Configure default local preference value\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_default_local_preference_unset(bgp); + bgp_clear_star_soft_in(vty, bgp->name); + + return CMD_SUCCESS; +} + + +DEFUN (bgp_default_subgroup_pkt_queue_max, + bgp_default_subgroup_pkt_queue_max_cmd, + "bgp default subgroup-pkt-queue-max (20-100)", + BGP_STR + "Configure BGP defaults\n" + "subgroup-pkt-queue-max\n" + "Configure subgroup packet queue max\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + uint32_t max_size; + + max_size = strtoul(argv[idx_number]->arg, NULL, 10); + + bgp_default_subgroup_pkt_queue_max_set(bgp, max_size); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_default_subgroup_pkt_queue_max, + no_bgp_default_subgroup_pkt_queue_max_cmd, + "no bgp default subgroup-pkt-queue-max [(20-100)]", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "subgroup-pkt-queue-max\n" + "Configure subgroup packet queue max\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_default_subgroup_pkt_queue_max_unset(bgp); + return CMD_SUCCESS; +} + + +DEFUN (bgp_rr_allow_outbound_policy, + bgp_rr_allow_outbound_policy_cmd, + "bgp route-reflector allow-outbound-policy", + BGP_STR + "Allow modifications made by out route-map\n" + "on ibgp neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { + SET_FLAG(bgp->flags, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY); + update_group_announce_rrclients(bgp); + bgp_clear_star_soft_out(vty, bgp->name); + } + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_rr_allow_outbound_policy, + no_bgp_rr_allow_outbound_policy_cmd, + "no bgp route-reflector allow-outbound-policy", + NO_STR + BGP_STR + "Allow modifications made by out route-map\n" + "on ibgp neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { + UNSET_FLAG(bgp->flags, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY); + update_group_announce_rrclients(bgp); + bgp_clear_star_soft_out(vty, bgp->name); + } + + return CMD_SUCCESS; +} + +DEFUN (bgp_listen_limit, + bgp_listen_limit_cmd, + "bgp listen limit (1-65535)", + BGP_STR + "BGP Dynamic Neighbors listen commands\n" + "Maximum number of BGP Dynamic Neighbors that can be created\n" + "Configure Dynamic Neighbors listen limit value\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_number = 3; + int listen_limit; + + listen_limit = strtoul(argv[idx_number]->arg, NULL, 10); + + bgp_listen_limit_set(bgp, listen_limit); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_listen_limit, + no_bgp_listen_limit_cmd, + "no bgp listen limit [(1-65535)]", + NO_STR + BGP_STR + "BGP Dynamic Neighbors listen commands\n" + "Maximum number of BGP Dynamic Neighbors that can be created\n" + "Configure Dynamic Neighbors listen limit value\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp_listen_limit_unset(bgp); + return CMD_SUCCESS; +} + + +/* + * Check if this listen range is already configured. Check for exact + * match or overlap based on input. + */ +static struct peer_group *listen_range_exists(struct bgp *bgp, + struct prefix *range, int exact) +{ + struct listnode *node, *nnode; + struct listnode *node1, *nnode1; + struct peer_group *group; + struct prefix *lr; + afi_t afi; + int match; + + afi = family2afi(range->family); + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node1, nnode1, + lr)) { + if (exact) + match = prefix_same(range, lr); + else + match = (prefix_match(range, lr) + || prefix_match(lr, range)); + if (match) + return group; + } + } + + return NULL; +} + +DEFUN (bgp_listen_range, + bgp_listen_range_cmd, + "bgp listen range peer-group PGNAME", + BGP_STR + "Configure BGP dynamic neighbors listen range\n" + "Configure BGP dynamic neighbors listen range\n" + NEIGHBOR_ADDR_STR + "Member of the peer-group\n" + "Peer-group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct prefix range; + struct peer_group *group, *existing_group; + afi_t afi; + int ret; + int idx = 0; + + argv_find(argv, argc, "A.B.C.D/M", &idx); + argv_find(argv, argc, "X:X::X:X/M", &idx); + char *prefix = argv[idx]->arg; + argv_find(argv, argc, "PGNAME", &idx); + char *peergroup = argv[idx]->arg; + + /* Convert IP prefix string to struct prefix. */ + ret = str2prefix(prefix, &range); + if (!ret) { + vty_out(vty, "%% Malformed listen range\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + afi = family2afi(range.family); + + if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&range.u.prefix6)) { + vty_out(vty, + "%% Malformed listen range (link-local address)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + apply_mask(&range); + + /* Check if same listen range is already configured. */ + existing_group = listen_range_exists(bgp, &range, 1); + if (existing_group) { + if (strcmp(existing_group->name, peergroup) == 0) + return CMD_SUCCESS; + else { + vty_out(vty, + "%% Same listen range is attached to peer-group %s\n", + existing_group->name); + return CMD_WARNING_CONFIG_FAILED; + } + } + + /* Check if an overlapping listen range exists. */ + if (listen_range_exists(bgp, &range, 0)) { + vty_out(vty, + "%% Listen range overlaps with existing listen range\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + group = peer_group_lookup(bgp, peergroup); + if (!group) { + vty_out(vty, "%% Configure the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_group_listen_range_add(group, &range); + return bgp_vty_return(vty, ret); +} + +DEFUN (no_bgp_listen_range, + no_bgp_listen_range_cmd, + "no bgp listen range peer-group PGNAME", + NO_STR + BGP_STR + "Unconfigure BGP dynamic neighbors listen range\n" + "Unconfigure BGP dynamic neighbors listen range\n" + NEIGHBOR_ADDR_STR + "Member of the peer-group\n" + "Peer-group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct prefix range; + struct peer_group *group; + afi_t afi; + int ret; + int idx = 0; + + argv_find(argv, argc, "A.B.C.D/M", &idx); + argv_find(argv, argc, "X:X::X:X/M", &idx); + char *prefix = argv[idx]->arg; + argv_find(argv, argc, "PGNAME", &idx); + char *peergroup = argv[idx]->arg; + + /* Convert IP prefix string to struct prefix. */ + ret = str2prefix(prefix, &range); + if (!ret) { + vty_out(vty, "%% Malformed listen range\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + afi = family2afi(range.family); + + if (afi == AFI_IP6 && IN6_IS_ADDR_LINKLOCAL(&range.u.prefix6)) { + vty_out(vty, + "%% Malformed listen range (link-local address)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + apply_mask(&range); + + group = peer_group_lookup(bgp, peergroup); + if (!group) { + vty_out(vty, "%% Peer-group does not exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_group_listen_range_del(group, &range); + return bgp_vty_return(vty, ret); +} + +void bgp_config_write_listen(struct vty *vty, struct bgp *bgp) +{ + struct peer_group *group; + struct listnode *node, *nnode, *rnode, *nrnode; + struct prefix *range; + afi_t afi; + + if (bgp->dynamic_neighbors_limit != BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT) + vty_out(vty, " bgp listen limit %d\n", + bgp->dynamic_neighbors_limit); + + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (ALL_LIST_ELEMENTS(group->listen_range[afi], rnode, + nrnode, range)) { + vty_out(vty, + " bgp listen range %pFX peer-group %s\n", + range, group->name); + } + } + } +} + + +DEFUN (bgp_disable_connected_route_check, + bgp_disable_connected_route_check_cmd, + "bgp disable-ebgp-connected-route-check", + BGP_STR + "Disable checking if nexthop is connected on ebgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + SET_FLAG(bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK); + bgp_clear_star_soft_in(vty, bgp->name); + + return CMD_SUCCESS; +} + +DEFUN (no_bgp_disable_connected_route_check, + no_bgp_disable_connected_route_check_cmd, + "no bgp disable-ebgp-connected-route-check", + NO_STR + BGP_STR + "Disable checking if nexthop is connected on ebgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + UNSET_FLAG(bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK); + bgp_clear_star_soft_in(vty, bgp->name); + + return CMD_SUCCESS; +} + + +static int peer_remote_as_vty(struct vty *vty, const char *peer_str, + const char *as_str) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + as_t as; + int as_type = AS_SPECIFIED; + union sockunion su; + + if (as_str[0] == 'i') { + as = 0; + as_type = AS_INTERNAL; + } else if (as_str[0] == 'e') { + as = 0; + as_type = AS_EXTERNAL; + } else if (!asn_str2asn(as_str, &as)) + as_type = AS_UNSPECIFIED; + + if (as_type == AS_UNSPECIFIED) { + vty_out(vty, "%% Invalid peer AS: %s\n", as_str); + return CMD_WARNING_CONFIG_FAILED; + } + /* If peer is peer group or interface peer, call proper function. */ + ret = str2sockunion(peer_str, &su); + if (ret < 0) { + struct peer *peer; + + /* Check if existing interface peer */ + peer = peer_lookup_by_conf_if(bgp, peer_str); + + ret = peer_remote_as(bgp, NULL, peer_str, &as, as_type, as_str); + + /* if not interface peer, check peer-group settings */ + if (ret < 0 && !peer) { + ret = peer_group_remote_as(bgp, peer_str, &as, as_type, + as_str); + if (ret < 0) { + vty_out(vty, + "%% Create the peer-group or interface first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; + } + } else { + if (peer_address_self_check(bgp, &su)) { + vty_out(vty, + "%% Can not configure the local system as neighbor\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ret = peer_remote_as(bgp, &su, NULL, &as, as_type, as_str); + } + + return bgp_vty_return(vty, ret); +} + +DEFUN (bgp_default_shutdown, + bgp_default_shutdown_cmd, + "[no] bgp default shutdown", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Apply administrative shutdown to newly configured peers\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->autoshutdown = !strmatch(argv[0]->text, "no"); + return CMD_SUCCESS; +} + +DEFPY(bgp_shutdown_msg, bgp_shutdown_msg_cmd, "bgp shutdown message MSG...", + BGP_STR + "Administrative shutdown of the BGP instance\n" + "Add a shutdown message (RFC 8203)\n" + "Shutdown message\n") +{ + char *msgstr = NULL; + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (argc > 3) + msgstr = argv_concat(argv, argc, 3); + + if (msgstr && strlen(msgstr) > BGP_ADMIN_SHUTDOWN_MSG_LEN) { + vty_out(vty, "%% Shutdown message size exceeded %d\n", + BGP_ADMIN_SHUTDOWN_MSG_LEN); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_shutdown_enable(bgp, msgstr); + XFREE(MTYPE_TMP, msgstr); + + return CMD_SUCCESS; +} + +DEFPY(bgp_shutdown, bgp_shutdown_cmd, "bgp shutdown", + BGP_STR "Administrative shutdown of the BGP instance\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp_shutdown_enable(bgp, NULL); + + return CMD_SUCCESS; +} + +DEFPY(no_bgp_shutdown, no_bgp_shutdown_cmd, "no bgp shutdown", + NO_STR BGP_STR "Administrative shutdown of the BGP instance\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp_shutdown_disable(bgp); + + return CMD_SUCCESS; +} + +ALIAS(no_bgp_shutdown, no_bgp_shutdown_msg_cmd, + "no bgp shutdown message MSG...", NO_STR BGP_STR + "Administrative shutdown of the BGP instance\n" + "Add a shutdown message (RFC 8203)\n" "Shutdown message\n") + +DEFUN (neighbor_remote_as, + neighbor_remote_as_cmd, + "neighbor remote-as ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + int idx_peer = 1; + int idx_remote_as = 3; + return peer_remote_as_vty(vty, argv[idx_peer]->arg, + argv[idx_remote_as]->arg); +} + +DEFPY (bgp_allow_martian, + bgp_allow_martian_cmd, + "[no]$no bgp allow-martian-nexthop", + NO_STR + BGP_STR + "Allow Martian nexthops to be received in the NLRI from a peer\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + bgp->allow_martian = false; + else + bgp->allow_martian = true; + + return CMD_SUCCESS; +} + +/* Enable fast convergence of bgp sessions. If this is enabled, bgp + * sessions do not wait for hold timer expiry to bring down the sessions + * when nexthop becomes unreachable + */ +DEFUN(bgp_fast_convergence, bgp_fast_convergence_cmd, "bgp fast-convergence", + BGP_STR "Fast convergence for bgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->fast_convergence = true; + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_fast_convergence, no_bgp_fast_convergence_cmd, + "no bgp fast-convergence", + NO_STR BGP_STR "Fast convergence for bgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->fast_convergence = false; + + return CMD_SUCCESS; +} + +static int peer_conf_interface_get(struct vty *vty, const char *conf_if, + int v6only, + const char *peer_group_name, + const char *as_str) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + as_t as = 0; + int as_type = AS_UNSPECIFIED; + struct peer *peer; + struct peer_group *group; + int ret = 0; + + group = peer_group_lookup(bgp, conf_if); + + if (group) { + vty_out(vty, "%% Name conflict with peer-group \n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (as_str) { + if (as_str[0] == 'i') { + as_type = AS_INTERNAL; + } else if (as_str[0] == 'e') { + as_type = AS_EXTERNAL; + } else { + /* Get AS number. */ + if (asn_str2asn(as_str, &as)) + as_type = AS_SPECIFIED; + } + } + + peer = peer_lookup_by_conf_if(bgp, conf_if); + if (peer) { + if (as_str) + ret = peer_remote_as(bgp, NULL, conf_if, &as, as_type, + as_str); + } else { + peer = peer_create(NULL, conf_if, bgp, bgp->as, as, as_type, + NULL, true, as_str); + + if (!peer) { + vty_out(vty, "%% BGP failed to create peer\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (v6only) + peer_flag_set(peer, PEER_FLAG_IFPEER_V6ONLY); + + /* Request zebra to initiate IPv6 RAs on this interface. We do + * this + * any unnumbered peer in order to not worry about run-time + * transitions + * (e.g., peering is initially IPv4, but the IPv4 /30 or /31 + * address + * gets deleted later etc.) + */ + if (peer->ifp) + bgp_zebra_initiate_radv(bgp, peer); + } + + if ((v6only && !CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) + || (!v6only && CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY))) { + if (v6only) + peer_flag_set(peer, PEER_FLAG_IFPEER_V6ONLY); + else + peer_flag_unset(peer, PEER_FLAG_IFPEER_V6ONLY); + + /* v6only flag changed. Reset bgp seesion */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_V6ONLY_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); + } + + if (!CHECK_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE)) { + SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE); + SET_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE); + SET_FLAG(peer->flags_override, PEER_FLAG_CAPABILITY_ENHE); + } + + if (peer_group_name) { + group = peer_group_lookup(bgp, peer_group_name); + if (!group) { + vty_out(vty, "%% Configure the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_group_bind(bgp, NULL, peer, group, &as); + } + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_interface_config, + neighbor_interface_config_cmd, + "neighbor WORD interface [peer-group PGNAME]", + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Enable BGP on interface\n" + "Member of the peer-group\n" + "Peer-group name\n") +{ + int idx_word = 1; + int idx_peer_group_word = 4; + + if (argc > idx_peer_group_word) + return peer_conf_interface_get( + vty, argv[idx_word]->arg, 0, + argv[idx_peer_group_word]->arg, NULL); + else + return peer_conf_interface_get(vty, argv[idx_word]->arg, 0, + NULL, NULL); +} + +DEFUN (neighbor_interface_config_v6only, + neighbor_interface_config_v6only_cmd, + "neighbor WORD interface v6only [peer-group PGNAME]", + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Enable BGP on interface\n" + "Enable BGP with v6 link-local only\n" + "Member of the peer-group\n" + "Peer-group name\n") +{ + int idx_word = 1; + int idx_peer_group_word = 5; + + if (argc > idx_peer_group_word) + return peer_conf_interface_get( + vty, argv[idx_word]->arg, 1, + argv[idx_peer_group_word]->arg, NULL); + + return peer_conf_interface_get(vty, argv[idx_word]->arg, 1, NULL, NULL); +} + + +DEFUN (neighbor_interface_config_remote_as, + neighbor_interface_config_remote_as_cmd, + "neighbor WORD interface remote-as ", + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Enable BGP on interface\n" + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + int idx_word = 1; + int idx_remote_as = 4; + return peer_conf_interface_get(vty, argv[idx_word]->arg, 0, NULL, + argv[idx_remote_as]->arg); +} + +DEFUN (neighbor_interface_v6only_config_remote_as, + neighbor_interface_v6only_config_remote_as_cmd, + "neighbor WORD interface v6only remote-as ", + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Enable BGP with v6 link-local only\n" + "Enable BGP on interface\n" + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + int idx_word = 1; + int idx_remote_as = 5; + return peer_conf_interface_get(vty, argv[idx_word]->arg, 1, NULL, + argv[idx_remote_as]->arg); +} + +DEFUN (neighbor_peer_group, + neighbor_peer_group_cmd, + "neighbor WORD peer-group", + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Configure peer-group\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_word = 1; + struct peer *peer; + struct peer_group *group; + + peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); + if (peer) { + vty_out(vty, "%% Name conflict with interface: \n"); + return CMD_WARNING_CONFIG_FAILED; + } + + group = peer_group_get(bgp, argv[idx_word]->arg); + if (!group) { + vty_out(vty, "%% BGP failed to find or create peer-group\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (no_neighbor, + no_neighbor_cmd, + "no neighbor [remote-as <(1-4294967295)|internal|external>]>", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_peer = 2; + int ret; + union sockunion su; + struct peer_group *group; + struct peer *peer; + struct peer *other; + afi_t afi; + int lr_count; + + ret = str2sockunion(argv[idx_peer]->arg, &su); + if (ret < 0) { + /* look up for neighbor by interface name config. */ + peer = peer_lookup_by_conf_if(bgp, argv[idx_peer]->arg); + if (peer) { + /* Request zebra to terminate IPv6 RAs on this + * interface. */ + if (peer->ifp) + bgp_zebra_terminate_radv(peer->bgp, peer); + peer_notify_unconfig(peer); + peer_delete(peer); + return CMD_SUCCESS; + } + + group = peer_group_lookup(bgp, argv[idx_peer]->arg); + if (group) { + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + lr_count = listcount(group->listen_range[afi]); + if (lr_count) { + vty_out(vty, + "%%Peer-group %s is attached to %d listen-range(s), delete them first\n", + group->name, lr_count); + return CMD_WARNING_CONFIG_FAILED; + } + } + peer_group_notify_unconfig(group); + peer_group_delete(group); + } else { + vty_out(vty, "%% Create the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } else { + peer = peer_lookup(bgp, &su); + if (peer) { + if (peer_dynamic_neighbor(peer)) { + vty_out(vty, + "%% Operation not allowed on a dynamic neighbor\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + other = peer->doppelganger; + + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE)) + bgp_zebra_terminate_radv(peer->bgp, peer); + + peer_notify_unconfig(peer); + peer_delete(peer); + if (other && other->connection->status != Deleted) { + peer_notify_unconfig(other); + peer_delete(other); + } + } + } + + return CMD_SUCCESS; +} + +DEFUN (no_neighbor_interface_config, + no_neighbor_interface_config_cmd, + "no neighbor WORD interface [v6only] [peer-group PGNAME] [remote-as <(1-4294967295)|internal|external>]", + NO_STR + NEIGHBOR_STR + "Interface name\n" + "Configure BGP on interface\n" + "Enable BGP with v6 link-local only\n" + "Member of the peer-group\n" + "Peer-group name\n" + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_word = 2; + struct peer *peer; + + /* look up for neighbor by interface name config. */ + peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); + if (peer) { + /* Request zebra to terminate IPv6 RAs on this interface. */ + if (peer->ifp) + bgp_zebra_terminate_radv(peer->bgp, peer); + peer_notify_unconfig(peer); + peer_delete(peer); + } else { + vty_out(vty, "%% Create the bgp interface first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +DEFUN (no_neighbor_peer_group, + no_neighbor_peer_group_cmd, + "no neighbor WORD peer-group", + NO_STR + NEIGHBOR_STR + "Neighbor tag\n" + "Configure peer-group\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_word = 2; + struct peer_group *group; + afi_t afi; + int lr_count; + + group = peer_group_lookup(bgp, argv[idx_word]->arg); + if (group) { + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + lr_count = listcount(group->listen_range[afi]); + if (lr_count) { + vty_out(vty, + "%%Peer-group %s is attached to %d listen-range(s), delete them first\n", + group->name, lr_count); + return CMD_WARNING_CONFIG_FAILED; + } + } + peer_group_notify_unconfig(group); + peer_group_delete(group); + } else { + vty_out(vty, "%% Create the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +DEFUN (no_neighbor_interface_peer_group_remote_as, + no_neighbor_interface_peer_group_remote_as_cmd, + "no neighbor WORD remote-as ", + NO_STR + NEIGHBOR_STR + "Interface name or neighbor tag\n" + "Specify a BGP neighbor\n" + AS_STR + "Internal BGP peer\n" + "External BGP peer\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_word = 2; + struct peer_group *group; + struct peer *peer; + + /* look up for neighbor by interface name config. */ + peer = peer_lookup_by_conf_if(bgp, argv[idx_word]->arg); + if (peer) { + peer_as_change(peer, 0, AS_UNSPECIFIED, NULL); + return CMD_SUCCESS; + } + + group = peer_group_lookup(bgp, argv[idx_word]->arg); + if (group) + peer_group_remote_as_delete(group); + else { + vty_out(vty, "%% Create the peer-group or interface first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +DEFUN (neighbor_local_as, + neighbor_local_as_cmd, + "neighbor local-as ASNUM", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a local-as number\n" + "AS number expressed in dotted or plain format used as local AS\n") +{ + int idx_peer = 1; + int idx_number = 3; + struct peer *peer; + int ret; + as_t as; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!asn_str2asn(argv[idx_number]->arg, &as)) { + vty_out(vty, "%% Invalid neighbor local-as value: %s\n", + argv[idx_number]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_local_as_set(peer, as, 0, 0, argv[idx_number]->arg); + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_local_as_no_prepend, + neighbor_local_as_no_prepend_cmd, + "neighbor local-as ASNUM no-prepend", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a local-as number\n" + "AS number expressed in dotted or plain format used as local AS\n" + "Do not prepend local-as to updates from ebgp peers\n") +{ + int idx_peer = 1; + int idx_number = 3; + struct peer *peer; + int ret; + as_t as; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!asn_str2asn(argv[idx_number]->arg, &as)) { + vty_out(vty, "%% Invalid neighbor local-as value: %s\n", + argv[idx_number]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_local_as_set(peer, as, 1, 0, argv[idx_number]->arg); + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_local_as_no_prepend_replace_as, + neighbor_local_as_no_prepend_replace_as_cmd, + "neighbor local-as ASNUM no-prepend replace-as", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a local-as number\n" + "AS number expressed in dotted or plain format used as local AS\n" + "Do not prepend local-as to updates from ebgp peers\n" + "Do not prepend local-as to updates from ibgp peers\n") +{ + int idx_peer = 1; + int idx_number = 3; + struct peer *peer; + int ret; + as_t as; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!asn_str2asn(argv[idx_number]->arg, &as)) { + vty_out(vty, "%% Invalid neighbor local-as value: %s\n", + argv[idx_number]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_local_as_set(peer, as, 1, 1, argv[idx_number]->arg); + return bgp_vty_return(vty, ret); +} + +DEFUN (no_neighbor_local_as, + no_neighbor_local_as_cmd, + "no neighbor local-as [ASNUM [no-prepend [replace-as]]]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Specify a local-as number\n" + "AS number expressed in dotted or plain format used as local AS\n" + "Do not prepend local-as to updates from ebgp peers\n" + "Do not prepend local-as to updates from ibgp peers\n") +{ + int idx_peer = 2; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_local_as_unset(peer); + return bgp_vty_return(vty, ret); +} + + +DEFUN (neighbor_solo, + neighbor_solo_cmd, + "neighbor solo", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Solo peer - part of its own update group\n") +{ + int idx_peer = 1; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = update_group_adjust_soloness(peer, 1); + return bgp_vty_return(vty, ret); +} + +DEFUN (no_neighbor_solo, + no_neighbor_solo_cmd, + "no neighbor solo", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Solo peer - part of its own update group\n") +{ + int idx_peer = 2; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = update_group_adjust_soloness(peer, 0); + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_password, + neighbor_password_cmd, + "neighbor password LINE", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set a password\n" + "The password\n") +{ + int idx_peer = 1; + int idx_line = 3; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_password_set(peer, argv[idx_line]->arg); + return bgp_vty_return(vty, ret); +} + +DEFUN (no_neighbor_password, + no_neighbor_password_cmd, + "no neighbor password [LINE]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set a password\n" + "The password\n") +{ + int idx_peer = 2; + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_password_unset(peer); + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_activate, + neighbor_activate_cmd, + "neighbor activate", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable the Address Family for this Neighbor\n") +{ + int idx_peer = 1; + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_activate(peer, bgp_node_afi(vty), bgp_node_safi(vty)); + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN(neighbor_activate, neighbor_activate_hidden_cmd, + "neighbor activate", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Enable the Address Family for this Neighbor\n") + +DEFUN (no_neighbor_activate, + no_neighbor_activate_cmd, + "no neighbor activate", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable the Address Family for this Neighbor\n") +{ + int idx_peer = 2; + int ret; + struct peer *peer; + + /* Lookup peer. */ + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_deactivate(peer, bgp_node_afi(vty), bgp_node_safi(vty)); + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN(no_neighbor_activate, no_neighbor_activate_hidden_cmd, + "no neighbor activate", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Enable the Address Family for this Neighbor\n") + +DEFUN (neighbor_set_peer_group, + neighbor_set_peer_group_cmd, + "neighbor peer-group PGNAME", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Member of the peer-group\n" + "Peer-group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_peer = 1; + int idx_word = 3; + int ret; + as_t as; + union sockunion su; + struct peer *peer; + struct peer_group *group; + + ret = str2sockunion(argv[idx_peer]->arg, &su); + if (ret < 0) { + peer = peer_lookup_by_conf_if(bgp, argv[idx_peer]->arg); + if (!peer) { + vty_out(vty, "%% Malformed address or name: %s\n", + argv[idx_peer]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + } else { + if (peer_address_self_check(bgp, &su)) { + vty_out(vty, + "%% Can not configure the local system as neighbor\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Disallow for dynamic neighbor. */ + peer = peer_lookup(bgp, &su); + if (peer && peer_dynamic_neighbor(peer)) { + vty_out(vty, + "%% Operation not allowed on a dynamic neighbor\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + group = peer_group_lookup(bgp, argv[idx_word]->arg); + if (!group) { + vty_out(vty, "%% Configure the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = peer_group_bind(bgp, &su, peer, group, &as); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN(neighbor_set_peer_group, neighbor_set_peer_group_hidden_cmd, + "neighbor peer-group PGNAME", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Member of the peer-group\n" + "Peer-group name\n") + +DEFUN (no_neighbor_set_peer_group, + no_neighbor_set_peer_group_cmd, + "no neighbor peer-group PGNAME", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Member of the peer-group\n" + "Peer-group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_peer = 2; + int idx_word = 4; + int ret; + struct peer *peer; + struct peer_group *group; + + peer = peer_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + group = peer_group_lookup(bgp, argv[idx_word]->arg); + if (!group) { + vty_out(vty, "%% Configure the peer-group first\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE)) + bgp_zebra_terminate_radv(peer->bgp, peer); + + peer_notify_unconfig(peer); + ret = peer_delete(peer); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN(no_neighbor_set_peer_group, no_neighbor_set_peer_group_hidden_cmd, + "no neighbor peer-group PGNAME", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Member of the peer-group\n" + "Peer-group name\n") + +/* neighbor passive. */ +DEFUN (neighbor_passive, + neighbor_passive_cmd, + "neighbor passive", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Don't send open messages to this neighbor\n") +{ + int idx_peer = 1; + return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_PASSIVE); +} + +DEFUN (no_neighbor_passive, + no_neighbor_passive_cmd, + "no neighbor passive", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Don't send open messages to this neighbor\n") +{ + int idx_peer = 2; + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, PEER_FLAG_PASSIVE); +} + +/* neighbor shutdown. */ +DEFUN (neighbor_shutdown_msg, + neighbor_shutdown_msg_cmd, + "neighbor shutdown message MSG...", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n" + "Add a shutdown message (RFC 8203)\n" + "Shutdown message\n") +{ + int idx_peer = 1; + + if (argc >= 5) { + struct peer *peer = + peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + char *message; + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + message = argv_concat(argv, argc, 4); + peer_tx_shutdown_message_set(peer, message); + XFREE(MTYPE_TMP, message); + } + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, PEER_FLAG_SHUTDOWN); +} + +ALIAS(neighbor_shutdown_msg, neighbor_shutdown_cmd, + "neighbor shutdown", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n") + +DEFUN (no_neighbor_shutdown_msg, + no_neighbor_shutdown_msg_cmd, + "no neighbor shutdown message MSG...", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n" + "Remove a shutdown message (RFC 8203)\n" + "Shutdown message\n") +{ + int idx_peer = 2; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_SHUTDOWN); +} + +ALIAS(no_neighbor_shutdown_msg, no_neighbor_shutdown_cmd, + "no neighbor shutdown", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n") + +DEFUN(neighbor_shutdown_rtt, + neighbor_shutdown_rtt_cmd, + "neighbor shutdown rtt (1-65535) [count (1-255)]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n" + "Shutdown if round-trip-time is higher than expected\n" + "Round-trip-time in milliseconds\n" + "Specify the number of keepalives before shutdown\n" + "The number of keepalives with higher RTT to shutdown\n") +{ + int idx_peer = 1; + int idx_rtt = 4; + int idx_count = 0; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + peer->rtt_expected = strtol(argv[idx_rtt]->arg, NULL, 10); + + if (argv_find(argv, argc, "count", &idx_count)) + peer->rtt_keepalive_conf = + strtol(argv[idx_count + 1]->arg, NULL, 10); + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_RTT_SHUTDOWN); +} + +DEFUN(no_neighbor_shutdown_rtt, + no_neighbor_shutdown_rtt_cmd, + "no neighbor shutdown rtt [(1-65535) [count (1-255)]]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Administratively shut down this neighbor\n" + "Shutdown if round-trip-time is higher than expected\n" + "Round-trip-time in milliseconds\n" + "Specify the number of keepalives before shutdown\n" + "The number of keepalives with higher RTT to shutdown\n") +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + peer->rtt_expected = 0; + peer->rtt_keepalive_conf = 1; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_RTT_SHUTDOWN); +} + +/* neighbor capability dynamic. */ +DEFUN (neighbor_capability_dynamic, + neighbor_capability_dynamic_cmd, + "neighbor capability dynamic", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise dynamic capability to this neighbor\n") +{ + int idx_peer = 1; + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DYNAMIC_CAPABILITY); +} + +DEFUN (no_neighbor_capability_dynamic, + no_neighbor_capability_dynamic_cmd, + "no neighbor capability dynamic", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise dynamic capability to this neighbor\n") +{ + int idx_peer = 2; + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DYNAMIC_CAPABILITY); +} + +/* neighbor dont-capability-negotiate */ +DEFUN (neighbor_dont_capability_negotiate, + neighbor_dont_capability_negotiate_cmd, + "neighbor dont-capability-negotiate", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Do not perform capability negotiation\n") +{ + int idx_peer = 1; + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DONT_CAPABILITY); +} + +DEFUN (no_neighbor_dont_capability_negotiate, + no_neighbor_dont_capability_negotiate_cmd, + "no neighbor dont-capability-negotiate", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Do not perform capability negotiation\n") +{ + int idx_peer = 2; + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DONT_CAPABILITY); +} + +/* neighbor capability fqdn */ +DEFPY (neighbor_capability_fqdn, + neighbor_capability_fqdn_cmd, + "[no$no] neighbor $neighbor capability fqdn", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise fqdn capability to the peer\n") +{ + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + ret = peer_flag_unset_vty(vty, neighbor, + PEER_FLAG_CAPABILITY_FQDN); + else + ret = peer_flag_set_vty(vty, neighbor, + PEER_FLAG_CAPABILITY_FQDN); + + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_FQDN, + no ? CAPABILITY_ACTION_UNSET + : CAPABILITY_ACTION_SET); + + return ret; +} + +/* neighbor capability extended next hop encoding */ +DEFUN (neighbor_capability_enhe, + neighbor_capability_enhe_cmd, + "neighbor capability extended-nexthop", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise extended next-hop capability to the peer\n") +{ + int idx_peer = 1; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (peer && peer->conf_if) + return CMD_SUCCESS; + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_CAPABILITY_ENHE); +} + +DEFUN (no_neighbor_capability_enhe, + no_neighbor_capability_enhe_cmd, + "no neighbor capability extended-nexthop", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise extended next-hop capability to the peer\n") +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (peer && peer->conf_if) { + vty_out(vty, + "Peer %s cannot have capability extended-nexthop turned off\n", + argv[idx_peer]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_CAPABILITY_ENHE); +} + +/* neighbor capability software-version */ +DEFPY(neighbor_capability_software_version, + neighbor_capability_software_version_cmd, + "[no$no] neighbor $neighbor capability software-version", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise Software Version capability to the peer\n") +{ + struct peer *peer; + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + ret = peer_flag_unset_vty(vty, neighbor, + PEER_FLAG_CAPABILITY_SOFT_VERSION); + else + ret = peer_flag_set_vty(vty, neighbor, + PEER_FLAG_CAPABILITY_SOFT_VERSION); + + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, + CAPABILITY_CODE_SOFT_VERSION, + no ? CAPABILITY_ACTION_UNSET + : CAPABILITY_ACTION_SET); + + return ret; +} + +static int peer_af_flag_modify_vty(struct vty *vty, const char *peer_str, + afi_t afi, safi_t safi, uint64_t flag, + int set) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (set) + ret = peer_af_flag_set(peer, afi, safi, flag); + else + ret = peer_af_flag_unset(peer, afi, safi, flag); + + return bgp_vty_return(vty, ret); +} + +static int peer_af_flag_set_vty(struct vty *vty, const char *peer_str, + afi_t afi, safi_t safi, uint64_t flag) +{ + return peer_af_flag_modify_vty(vty, peer_str, afi, safi, flag, 1); +} + +static int peer_af_flag_unset_vty(struct vty *vty, const char *peer_str, + afi_t afi, safi_t safi, uint64_t flag) +{ + return peer_af_flag_modify_vty(vty, peer_str, afi, safi, flag, 0); +} + +/* neighbor capability orf prefix-list. */ +DEFUN (neighbor_capability_orf_prefix, + neighbor_capability_orf_prefix_cmd, + "neighbor capability orf prefix-list ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise ORF capability to the peer\n" + "Advertise prefixlist ORF capability to this neighbor\n" + "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" + "Capability to RECEIVE the ORF from this neighbor\n" + "Capability to SEND the ORF to this neighbor\n") +{ + int idx_send_recv = 5; + char *peer_str = argv[1]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(argv[idx_send_recv]->text, "send")) { + ret = peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_SM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_SET); + return ret; + } + + if (strmatch(argv[idx_send_recv]->text, "receive")) { + ret = peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_RM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_SET); + return ret; + } + + if (strmatch(argv[idx_send_recv]->text, "both")) { + ret = peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_SM) | + peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_RM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_SET); + return ret; + } + + return CMD_WARNING_CONFIG_FAILED; +} + +ALIAS_HIDDEN( + neighbor_capability_orf_prefix, + neighbor_capability_orf_prefix_hidden_cmd, + "neighbor capability orf prefix-list ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise ORF capability to the peer\n" + "Advertise prefixlist ORF capability to this neighbor\n" + "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" + "Capability to RECEIVE the ORF from this neighbor\n" + "Capability to SEND the ORF to this neighbor\n") + +DEFUN (no_neighbor_capability_orf_prefix, + no_neighbor_capability_orf_prefix_cmd, + "no neighbor capability orf prefix-list ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise ORF capability to the peer\n" + "Advertise prefixlist ORF capability to this neighbor\n" + "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" + "Capability to RECEIVE the ORF from this neighbor\n" + "Capability to SEND the ORF to this neighbor\n") +{ + int idx_send_recv = 6; + char *peer_str = argv[2]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(argv[idx_send_recv]->text, "send")) { + ret = peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_SM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_UNSET); + return ret; + } + + if (strmatch(argv[idx_send_recv]->text, "receive")) { + ret = peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_RM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_UNSET); + return ret; + } + + if (strmatch(argv[idx_send_recv]->text, "both")) { + ret = peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_SM) | + peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_ORF_PREFIX_RM); + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ORF, + CAPABILITY_ACTION_UNSET); + return ret; + } + + return CMD_WARNING_CONFIG_FAILED; +} + +ALIAS_HIDDEN( + no_neighbor_capability_orf_prefix, + no_neighbor_capability_orf_prefix_hidden_cmd, + "no neighbor capability orf prefix-list ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Advertise capability to the peer\n" + "Advertise ORF capability to the peer\n" + "Advertise prefixlist ORF capability to this neighbor\n" + "Capability to SEND and RECEIVE the ORF to/from this neighbor\n" + "Capability to RECEIVE the ORF from this neighbor\n" + "Capability to SEND the ORF to this neighbor\n") + +/* neighbor next-hop-self. */ +DEFUN (neighbor_nexthop_self, + neighbor_nexthop_self_cmd, + "neighbor next-hop-self", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), PEER_FLAG_NEXTHOP_SELF); +} + +ALIAS_HIDDEN(neighbor_nexthop_self, neighbor_nexthop_self_hidden_cmd, + "neighbor next-hop-self", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n") + +/* neighbor next-hop-self. */ +DEFUN (neighbor_nexthop_self_force, + neighbor_nexthop_self_force_cmd, + "neighbor next-hop-self force", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_FORCE_NEXTHOP_SELF); +} + +ALIAS_HIDDEN(neighbor_nexthop_self_force, + neighbor_nexthop_self_force_hidden_cmd, + "neighbor next-hop-self force", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") + +ALIAS_HIDDEN(neighbor_nexthop_self_force, + neighbor_nexthop_self_all_hidden_cmd, + "neighbor next-hop-self all", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") + +DEFUN (no_neighbor_nexthop_self, + no_neighbor_nexthop_self_cmd, + "no neighbor next-hop-self", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_NEXTHOP_SELF); +} + +ALIAS_HIDDEN(no_neighbor_nexthop_self, no_neighbor_nexthop_self_hidden_cmd, + "no neighbor next-hop-self", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n") + +DEFUN (no_neighbor_nexthop_self_force, + no_neighbor_nexthop_self_force_cmd, + "no neighbor next-hop-self force", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_FORCE_NEXTHOP_SELF); +} + +ALIAS_HIDDEN(no_neighbor_nexthop_self_force, + no_neighbor_nexthop_self_force_hidden_cmd, + "no neighbor next-hop-self force", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") + +ALIAS_HIDDEN(no_neighbor_nexthop_self_force, + no_neighbor_nexthop_self_all_hidden_cmd, + "no neighbor next-hop-self all", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable the next hop calculation for this neighbor\n" + "Set the next hop to self for reflected routes\n") + +/* neighbor as-override */ +DEFUN (neighbor_as_override, + neighbor_as_override_cmd, + "neighbor as-override", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Override ASNs in outbound updates if aspath equals remote-as\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), PEER_FLAG_AS_OVERRIDE); +} + +ALIAS_HIDDEN(neighbor_as_override, neighbor_as_override_hidden_cmd, + "neighbor as-override", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Override ASNs in outbound updates if aspath equals remote-as\n") + +DEFUN (no_neighbor_as_override, + no_neighbor_as_override_cmd, + "no neighbor as-override", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Override ASNs in outbound updates if aspath equals remote-as\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_AS_OVERRIDE); +} + +ALIAS_HIDDEN(no_neighbor_as_override, no_neighbor_as_override_hidden_cmd, + "no neighbor as-override", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Override ASNs in outbound updates if aspath equals remote-as\n") + +/* neighbor remove-private-AS. */ +DEFUN (neighbor_remove_private_as, + neighbor_remove_private_as_cmd, + "neighbor remove-private-AS", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS); +} + +ALIAS_HIDDEN(neighbor_remove_private_as, neighbor_remove_private_as_hidden_cmd, + "neighbor remove-private-AS", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n") + +DEFUN (neighbor_remove_private_as_all, + neighbor_remove_private_as_all_cmd, + "neighbor remove-private-AS all", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_ALL); +} + +ALIAS_HIDDEN(neighbor_remove_private_as_all, + neighbor_remove_private_as_all_hidden_cmd, + "neighbor remove-private-AS all", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n") + +DEFUN (neighbor_remove_private_as_replace_as, + neighbor_remove_private_as_replace_as_cmd, + "neighbor remove-private-AS replace-AS", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Replace private ASNs with our ASN in outbound updates\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE); +} + +ALIAS_HIDDEN(neighbor_remove_private_as_replace_as, + neighbor_remove_private_as_replace_as_hidden_cmd, + "neighbor remove-private-AS replace-AS", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Replace private ASNs with our ASN in outbound updates\n") + +DEFUN (neighbor_remove_private_as_all_replace_as, + neighbor_remove_private_as_all_replace_as_cmd, + "neighbor remove-private-AS all replace-AS", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n" + "Replace private ASNs with our ASN in outbound updates\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE); +} + +ALIAS_HIDDEN( + neighbor_remove_private_as_all_replace_as, + neighbor_remove_private_as_all_replace_as_hidden_cmd, + "neighbor remove-private-AS all replace-AS", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n" + "Replace private ASNs with our ASN in outbound updates\n") + +DEFUN (no_neighbor_remove_private_as, + no_neighbor_remove_private_as_cmd, + "no neighbor remove-private-AS", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS); +} + +ALIAS_HIDDEN(no_neighbor_remove_private_as, + no_neighbor_remove_private_as_hidden_cmd, + "no neighbor remove-private-AS", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n") + +DEFUN (no_neighbor_remove_private_as_all, + no_neighbor_remove_private_as_all_cmd, + "no neighbor remove-private-AS all", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_ALL); +} + +ALIAS_HIDDEN(no_neighbor_remove_private_as_all, + no_neighbor_remove_private_as_all_hidden_cmd, + "no neighbor remove-private-AS all", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n") + +DEFUN (no_neighbor_remove_private_as_replace_as, + no_neighbor_remove_private_as_replace_as_cmd, + "no neighbor remove-private-AS replace-AS", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Replace private ASNs with our ASN in outbound updates\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE); +} + +ALIAS_HIDDEN(no_neighbor_remove_private_as_replace_as, + no_neighbor_remove_private_as_replace_as_hidden_cmd, + "no neighbor remove-private-AS replace-AS", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Replace private ASNs with our ASN in outbound updates\n") + +DEFUN (no_neighbor_remove_private_as_all_replace_as, + no_neighbor_remove_private_as_all_replace_as_cmd, + "no neighbor remove-private-AS all replace-AS", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n" + "Replace private ASNs with our ASN in outbound updates\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE); +} + +ALIAS_HIDDEN( + no_neighbor_remove_private_as_all_replace_as, + no_neighbor_remove_private_as_all_replace_as_hidden_cmd, + "no neighbor remove-private-AS all replace-AS", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Remove private ASNs in outbound updates\n" + "Apply to all AS numbers\n" + "Replace private ASNs with our ASN in outbound updates\n") + + +/* neighbor send-community. */ +DEFUN (neighbor_send_community, + neighbor_send_community_cmd, + "neighbor send-community", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n") +{ + int idx_peer = 1; + + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_SEND_COMMUNITY); +} + +ALIAS_HIDDEN(neighbor_send_community, neighbor_send_community_hidden_cmd, + "neighbor send-community", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n") + +DEFUN (no_neighbor_send_community, + no_neighbor_send_community_cmd, + "no neighbor send-community", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n") +{ + int idx_peer = 2; + + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_SEND_COMMUNITY); +} + +ALIAS_HIDDEN(no_neighbor_send_community, no_neighbor_send_community_hidden_cmd, + "no neighbor send-community", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n") + +/* neighbor send-community extended. */ +DEFUN (neighbor_send_community_type, + neighbor_send_community_type_cmd, + "neighbor send-community ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n" + "Send Standard and Extended Community attributes\n" + "Send Standard, Large and Extended Community attributes\n" + "Send Extended Community attributes\n" + "Send Standard Community attributes\n" + "Send Large Community attributes\n") +{ + const char *type = argv[argc - 1]->text; + char *peer_str = argv[1]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(type, "standard")) + return peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY); + + if (strmatch(type, "extended")) + return peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY); + + if (strmatch(type, "large")) + return peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_LARGE_COMMUNITY); + + if (strmatch(type, "both")) { + return peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY) + | peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY); + } + return peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY) + | peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY) + | peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_LARGE_COMMUNITY); +} + +ALIAS_HIDDEN( + neighbor_send_community_type, neighbor_send_community_type_hidden_cmd, + "neighbor send-community ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n" + "Send Standard and Extended Community attributes\n" + "Send Standard, Large and Extended Community attributes\n" + "Send Extended Community attributes\n" + "Send Standard Community attributes\n" + "Send Large Community attributes\n") + +DEFUN (no_neighbor_send_community_type, + no_neighbor_send_community_type_cmd, + "no neighbor send-community ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n" + "Send Standard and Extended Community attributes\n" + "Send Standard, Large and Extended Community attributes\n" + "Send Extended Community attributes\n" + "Send Standard Community attributes\n" + "Send Large Community attributes\n") +{ + const char *type = argv[argc - 1]->text; + char *peer_str = argv[2]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(type, "standard")) + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY); + + if (strmatch(type, "extended")) + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY); + + if (strmatch(type, "large")) + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_LARGE_COMMUNITY); + + if (strmatch(type, "both")) { + + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY) + | peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY); + } + + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_COMMUNITY) + | peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY) + | peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_SEND_LARGE_COMMUNITY); +} + +ALIAS_HIDDEN( + no_neighbor_send_community_type, + no_neighbor_send_community_type_hidden_cmd, + "no neighbor send-community ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n" + "Send Standard and Extended Community attributes\n" + "Send Standard, Large and Extended Community attributes\n" + "Send Extended Community attributes\n" + "Send Standard Community attributes\n" + "Send Large Community attributes\n") + +DEFPY (neighbor_ecommunity_rpki, + neighbor_ecommunity_rpki_cmd, + "[no$no] neighbor $neighbor send-community extended rpki", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Community attribute to this neighbor\n" + "Send Extended Community attributes\n" + "Send RPKI Extended Community attributes\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + return peer_af_flag_unset_vty(vty, neighbor, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI); + else + return peer_af_flag_set_vty(vty, neighbor, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI); +} + +/* neighbor soft-reconfig. */ +DEFUN (neighbor_soft_reconfiguration, + neighbor_soft_reconfiguration_cmd, + "neighbor soft-reconfiguration inbound", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Per neighbor soft reconfiguration\n" + "Allow inbound soft reconfiguration for this neighbor\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_SOFT_RECONFIG); +} + +ALIAS_HIDDEN(neighbor_soft_reconfiguration, + neighbor_soft_reconfiguration_hidden_cmd, + "neighbor soft-reconfiguration inbound", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Per neighbor soft reconfiguration\n" + "Allow inbound soft reconfiguration for this neighbor\n") + +DEFUN (no_neighbor_soft_reconfiguration, + no_neighbor_soft_reconfiguration_cmd, + "no neighbor soft-reconfiguration inbound", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Per neighbor soft reconfiguration\n" + "Allow inbound soft reconfiguration for this neighbor\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_SOFT_RECONFIG); +} + +ALIAS_HIDDEN(no_neighbor_soft_reconfiguration, + no_neighbor_soft_reconfiguration_hidden_cmd, + "no neighbor soft-reconfiguration inbound", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Per neighbor soft reconfiguration\n" + "Allow inbound soft reconfiguration for this neighbor\n") + +DEFUN (neighbor_route_reflector_client, + neighbor_route_reflector_client_cmd, + "neighbor route-reflector-client", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Reflector client\n") +{ + int idx_peer = 1; + struct peer *peer; + + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_REFLECTOR_CLIENT); +} + +ALIAS_HIDDEN(neighbor_route_reflector_client, + neighbor_route_reflector_client_hidden_cmd, + "neighbor route-reflector-client", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Reflector client\n") + +DEFUN (no_neighbor_route_reflector_client, + no_neighbor_route_reflector_client_cmd, + "no neighbor route-reflector-client", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Reflector client\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_REFLECTOR_CLIENT); +} + +ALIAS_HIDDEN(no_neighbor_route_reflector_client, + no_neighbor_route_reflector_client_hidden_cmd, + "no neighbor route-reflector-client", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Reflector client\n") + +/* neighbor route-server-client. */ +DEFUN (neighbor_route_server_client, + neighbor_route_server_client_cmd, + "neighbor route-server-client", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Server client\n") +{ + int idx_peer = 1; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_RSERVER_CLIENT); +} + +ALIAS_HIDDEN(neighbor_route_server_client, + neighbor_route_server_client_hidden_cmd, + "neighbor route-server-client", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Server client\n") + +DEFUN (no_neighbor_route_server_client, + no_neighbor_route_server_client_cmd, + "no neighbor route-server-client", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Server client\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_RSERVER_CLIENT); +} + +ALIAS_HIDDEN(no_neighbor_route_server_client, + no_neighbor_route_server_client_hidden_cmd, + "no neighbor route-server-client", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Configure a neighbor as Route Server client\n") + +DEFUN (neighbor_nexthop_local_unchanged, + neighbor_nexthop_local_unchanged_cmd, + "neighbor nexthop-local unchanged", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure treatment of outgoing link-local nexthop attribute\n" + "Leave link-local nexthop unchanged for this peer\n") +{ + int idx_peer = 1; + return peer_af_flag_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED); +} + +DEFUN (no_neighbor_nexthop_local_unchanged, + no_neighbor_nexthop_local_unchanged_cmd, + "no neighbor nexthop-local unchanged", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Configure treatment of outgoing link-local-nexthop attribute\n" + "Leave link-local nexthop unchanged for this peer\n") +{ + int idx_peer = 2; + return peer_af_flag_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED); +} + +DEFUN (neighbor_attr_unchanged, + neighbor_attr_unchanged_cmd, + "neighbor attribute-unchanged [{as-path|next-hop|med}]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP attribute is propagated unchanged to this neighbor\n" + "As-path attribute\n" + "Nexthop attribute\n" + "Med attribute\n") +{ + int idx = 0; + char *peer_str = argv[1]->arg; + struct peer *peer; + bool aspath = false; + bool nexthop = false; + bool med = false; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret = 0; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (argv_find(argv, argc, "as-path", &idx)) + aspath = true; + + idx = 0; + if (argv_find(argv, argc, "next-hop", &idx)) + nexthop = true; + + idx = 0; + if (argv_find(argv, argc, "med", &idx)) + med = true; + + /* no flags means all of them! */ + if (!aspath && !nexthop && !med) { + ret = peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED); + ret |= peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED); + ret |= peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_MED_UNCHANGED); + } else { + if (!aspath) { + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED)) { + ret |= peer_af_flag_unset_vty( + vty, peer_str, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED); + } + } else + ret |= peer_af_flag_set_vty( + vty, peer_str, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED); + + if (!nexthop) { + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED)) { + ret |= peer_af_flag_unset_vty( + vty, peer_str, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED); + } + } else + ret |= peer_af_flag_set_vty( + vty, peer_str, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED); + + if (!med) { + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_MED_UNCHANGED)) { + ret |= peer_af_flag_unset_vty( + vty, peer_str, afi, safi, + PEER_FLAG_MED_UNCHANGED); + } + } else + ret |= peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_MED_UNCHANGED); + } + + return ret; +} + +ALIAS_HIDDEN( + neighbor_attr_unchanged, neighbor_attr_unchanged_hidden_cmd, + "neighbor attribute-unchanged [{as-path|next-hop|med}]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "BGP attribute is propagated unchanged to this neighbor\n" + "As-path attribute\n" + "Nexthop attribute\n" + "Med attribute\n") + +DEFUN (no_neighbor_attr_unchanged, + no_neighbor_attr_unchanged_cmd, + "no neighbor attribute-unchanged [{as-path|next-hop|med}]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP attribute is propagated unchanged to this neighbor\n" + "As-path attribute\n" + "Nexthop attribute\n" + "Med attribute\n") +{ + int idx = 0; + char *peer_str = argv[2]->arg; + struct peer *peer; + bool aspath = false; + bool nexthop = false; + bool med = false; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret = 0; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (argv_find(argv, argc, "as-path", &idx)) + aspath = true; + + idx = 0; + if (argv_find(argv, argc, "next-hop", &idx)) + nexthop = true; + + idx = 0; + if (argv_find(argv, argc, "med", &idx)) + med = true; + + if (!aspath && !nexthop && !med) // no flags means all of them! + return peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED) + | peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED) + | peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_MED_UNCHANGED); + + if (aspath) + ret |= peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED); + + if (nexthop) + ret |= peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED); + + if (med) + ret |= peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_MED_UNCHANGED); + + return ret; +} + +ALIAS_HIDDEN( + no_neighbor_attr_unchanged, no_neighbor_attr_unchanged_hidden_cmd, + "no neighbor attribute-unchanged [{as-path|next-hop|med}]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "BGP attribute is propagated unchanged to this neighbor\n" + "As-path attribute\n" + "Nexthop attribute\n" + "Med attribute\n") + +/* EBGP multihop configuration. */ +static int peer_ebgp_multihop_set_vty(struct vty *vty, const char *ip_str, + const char *ttl_str) +{ + struct peer *peer; + unsigned int ttl; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (peer->conf_if) + return bgp_vty_return(vty, BGP_ERR_INVALID_FOR_DIRECT_PEER); + + if (!ttl_str) + ttl = MAXTTL; + else + ttl = strtoul(ttl_str, NULL, 10); + + return bgp_vty_return(vty, peer_ebgp_multihop_set(peer, ttl)); +} + +static int peer_ebgp_multihop_unset_vty(struct vty *vty, const char *ip_str) +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + return bgp_vty_return(vty, peer_ebgp_multihop_unset(peer)); +} + +/* neighbor ebgp-multihop. */ +DEFUN (neighbor_ebgp_multihop, + neighbor_ebgp_multihop_cmd, + "neighbor ebgp-multihop", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Allow EBGP neighbors not on directly connected networks\n") +{ + int idx_peer = 1; + return peer_ebgp_multihop_set_vty(vty, argv[idx_peer]->arg, NULL); +} + +DEFUN (neighbor_ebgp_multihop_ttl, + neighbor_ebgp_multihop_ttl_cmd, + "neighbor ebgp-multihop (1-255)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Allow EBGP neighbors not on directly connected networks\n" + "maximum hop count\n") +{ + int idx_peer = 1; + int idx_number = 3; + return peer_ebgp_multihop_set_vty(vty, argv[idx_peer]->arg, + argv[idx_number]->arg); +} + +DEFUN (no_neighbor_ebgp_multihop, + no_neighbor_ebgp_multihop_cmd, + "no neighbor ebgp-multihop [(1-255)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Allow EBGP neighbors not on directly connected networks\n" + "maximum hop count\n") +{ + int idx_peer = 2; + return peer_ebgp_multihop_unset_vty(vty, argv[idx_peer]->arg); +} + +DEFPY (neighbor_aigp, + neighbor_aigp_cmd, + "[no$no] neighbor $neighbor aigp", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable send and receive of the AIGP attribute per neighbor\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + return peer_flag_unset_vty(vty, neighbor, PEER_FLAG_AIGP); + else + return peer_flag_set_vty(vty, neighbor, PEER_FLAG_AIGP); +} + +static uint8_t get_role_by_name(const char *role_str) +{ + if (strncmp(role_str, "peer", 2) == 0) + return ROLE_PEER; + if (strncmp(role_str, "provider", 2) == 0) + return ROLE_PROVIDER; + if (strncmp(role_str, "customer", 2) == 0) + return ROLE_CUSTOMER; + if (strncmp(role_str, "rs-server", 4) == 0) + return ROLE_RS_SERVER; + if (strncmp(role_str, "rs-client", 4) == 0) + return ROLE_RS_CLIENT; + return ROLE_UNDEFINED; +} + +static int peer_role_set_vty(struct vty *vty, struct peer *peer, + const char *role_str, bool strict_mode) +{ + uint8_t role = get_role_by_name(role_str); + + if (role == ROLE_UNDEFINED) + return bgp_vty_return(vty, BGP_ERR_INVALID_ROLE_NAME); + return bgp_vty_return(vty, peer_role_set(peer, role, strict_mode)); +} + +DEFPY(neighbor_role, + neighbor_role_cmd, + "neighbor $neighbor local-role $role", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set session role\n" + ROLE_STR) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_role_set_vty(vty, peer, role, false); + + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ROLE, + CAPABILITY_ACTION_SET); + + return ret; +} + +DEFPY(neighbor_role_strict, + neighbor_role_strict_cmd, + "neighbor $neighbor local-role $role strict-mode", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set session role\n" + ROLE_STR + "Use additional restriction on peer\n") +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_role_set_vty(vty, peer, role, true); + + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ROLE, + CAPABILITY_ACTION_SET); + + return ret; +} + +DEFPY(no_neighbor_role, + no_neighbor_role_cmd, + "no neighbor $neighbor local-role [strict-mode]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set session role\n" + ROLE_STR + "Use additional restriction on peer\n") +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = bgp_vty_return(vty, peer_role_unset(peer)); + + bgp_capability_send(peer, AFI_IP, SAFI_UNICAST, CAPABILITY_CODE_ROLE, + CAPABILITY_ACTION_UNSET); + + return ret; +} + +DEFPY (neighbor_oad, + neighbor_oad_cmd, + "[no$no] neighbor $neighbor oad", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set peering session type to EBGP-OAD\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + peer->sub_sort = 0; + else if (peer->sort == BGP_PEER_EBGP) + peer->sub_sort = BGP_PEER_EBGP_OAD; + + return CMD_SUCCESS; +} + +/* disable-connected-check */ +DEFUN (neighbor_disable_connected_check, + neighbor_disable_connected_check_cmd, + "neighbor ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "one-hop away EBGP peer using loopback address\n" + "Enforce EBGP neighbors perform multihop\n") +{ + int idx_peer = 1; + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DISABLE_CONNECTED_CHECK); +} + +DEFUN (no_neighbor_disable_connected_check, + no_neighbor_disable_connected_check_cmd, + "no neighbor ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "one-hop away EBGP peer using loopback address\n" + "Enforce EBGP neighbors perform multihop\n") +{ + int idx_peer = 2; + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DISABLE_CONNECTED_CHECK); +} + +DEFPY(neighbor_extended_link_bw, + neighbor_extended_link_bw_cmd, + "[no] neighbor $neighbor extended-link-bandwidth", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Send Extended (64-bit) version of encoding for Link-Bandwidth\n") +{ + int ret; + + if (no) + ret = peer_flag_unset_vty(vty, neighbor, + PEER_FLAG_EXTENDED_LINK_BANDWIDTH); + else + ret = peer_flag_set_vty(vty, neighbor, + PEER_FLAG_EXTENDED_LINK_BANDWIDTH); + + return ret; +} + +/* disable-link-bw-encoding-ieee */ +DEFUN(neighbor_disable_link_bw_encoding_ieee, + neighbor_disable_link_bw_encoding_ieee_cmd, + "neighbor disable-link-bw-encoding-ieee", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable IEEE floating-point encoding for extended community bandwidth\n") +{ + int idx_peer = 1; + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE); +} + +DEFUN(no_neighbor_disable_link_bw_encoding_ieee, + no_neighbor_disable_link_bw_encoding_ieee_cmd, + "no neighbor disable-link-bw-encoding-ieee", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Disable IEEE floating-point encoding for extended community bandwidth\n") +{ + int idx_peer = 2; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE); +} + +/* extended-optional-parameters */ +DEFUN(neighbor_extended_optional_parameters, + neighbor_extended_optional_parameters_cmd, + "neighbor extended-optional-parameters", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Force the extended optional parameters format for OPEN messages\n") +{ + int idx_peer = 1; + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_EXTENDED_OPT_PARAMS); +} + +DEFUN(no_neighbor_extended_optional_parameters, + no_neighbor_extended_optional_parameters_cmd, + "no neighbor extended-optional-parameters", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Force the extended optional parameters format for OPEN messages\n") +{ + int idx_peer = 2; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_EXTENDED_OPT_PARAMS); +} + +/* enforce-first-as */ +DEFUN (neighbor_enforce_first_as, + neighbor_enforce_first_as_cmd, + "neighbor enforce-first-as", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enforce the first AS for EBGP routes\n") +{ + int idx_peer = 1; + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_ENFORCE_FIRST_AS); +} + +DEFUN (no_neighbor_enforce_first_as, + no_neighbor_enforce_first_as_cmd, + "no neighbor enforce-first-as", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enforce the first AS for EBGP routes\n") +{ + int idx_peer = 2; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_ENFORCE_FIRST_AS); +} + + +DEFUN (neighbor_description, + neighbor_description_cmd, + "neighbor description LINE...", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Neighbor specific description\n" + "Up to 80 characters describing this neighbor\n") +{ + int idx_peer = 1; + int idx_line = 3; + struct peer *peer; + char *str; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + str = argv_concat(argv, argc, idx_line); + + peer_description_set(peer, str); + + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +DEFUN (no_neighbor_description, + no_neighbor_description_cmd, + "no neighbor description", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Neighbor specific description\n") +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + peer_description_unset(peer); + + return CMD_SUCCESS; +} + +ALIAS(no_neighbor_description, no_neighbor_description_comment_cmd, + "no neighbor description LINE...", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Neighbor specific description\n" + "Up to 80 characters describing this neighbor\n") + +/* Neighbor update-source. */ +static int peer_update_source_vty(struct vty *vty, const char *peer_str, + const char *source_str) +{ + struct peer *peer; + struct prefix p; + union sockunion su; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (peer->conf_if) + return CMD_WARNING; + + if (source_str) { + if (str2sockunion(source_str, &su) == 0) + peer_update_source_addr_set(peer, &su); + else { + if (str2prefix(source_str, &p)) { + vty_out(vty, + "%% Invalid update-source, remove prefix length \n"); + return CMD_WARNING_CONFIG_FAILED; + } else + peer_update_source_if_set(peer, source_str); + } + } else + peer_update_source_unset(peer); + + return CMD_SUCCESS; +} + +#define BGP_UPDATE_SOURCE_HELP_STR \ + "IPv4 address\n" \ + "IPv6 address\n" \ + "Interface name (requires zebra to be running)\n" + +DEFUN (neighbor_update_source, + neighbor_update_source_cmd, + "neighbor update-source ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Source of routing updates\n" + BGP_UPDATE_SOURCE_HELP_STR) +{ + int idx_peer = 1; + int idx_peer_2 = 3; + return peer_update_source_vty(vty, argv[idx_peer]->arg, + argv[idx_peer_2]->arg); +} + +DEFUN (no_neighbor_update_source, + no_neighbor_update_source_cmd, + "no neighbor update-source []", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Source of routing updates\n" + BGP_UPDATE_SOURCE_HELP_STR) +{ + int idx_peer = 2; + return peer_update_source_vty(vty, argv[idx_peer]->arg, NULL); +} + +static int peer_default_originate_set_vty(struct vty *vty, const char *peer_str, + afi_t afi, safi_t safi, + const char *rmap, int set) +{ + int ret; + struct peer *peer; + struct route_map *route_map = NULL; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (set) { + if (rmap) + route_map = route_map_lookup_warn_noexist(vty, rmap); + ret = peer_default_originate_set(peer, afi, safi, + rmap, route_map); + } else + ret = peer_default_originate_unset(peer, afi, safi); + + return bgp_vty_return(vty, ret); +} + +/* neighbor default-originate. */ +DEFUN (neighbor_default_originate, + neighbor_default_originate_cmd, + "neighbor default-originate", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n") +{ + int idx_peer = 1; + return peer_default_originate_set_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), + bgp_node_safi(vty), NULL, 1); +} + +ALIAS_HIDDEN(neighbor_default_originate, neighbor_default_originate_hidden_cmd, + "neighbor default-originate", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n") + +DEFUN (neighbor_default_originate_rmap, + neighbor_default_originate_rmap_cmd, + "neighbor default-originate route-map RMAP_NAME", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n" + "Route-map to specify criteria to originate default\n" + "route-map name\n") +{ + int idx_peer = 1; + int idx_word = 4; + return peer_default_originate_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg, 1); +} + +ALIAS_HIDDEN( + neighbor_default_originate_rmap, + neighbor_default_originate_rmap_hidden_cmd, + "neighbor default-originate route-map RMAP_NAME", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n" + "Route-map to specify criteria to originate default\n" + "route-map name\n") + +DEFUN (no_neighbor_default_originate, + no_neighbor_default_originate_cmd, + "no neighbor default-originate [route-map RMAP_NAME]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n" + "Route-map to specify criteria to originate default\n" + "route-map name\n") +{ + int idx_peer = 2; + return peer_default_originate_set_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), + bgp_node_safi(vty), NULL, 0); +} + +ALIAS_HIDDEN( + no_neighbor_default_originate, no_neighbor_default_originate_hidden_cmd, + "no neighbor default-originate [route-map RMAP_NAME]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Originate default route to this neighbor\n" + "Route-map to specify criteria to originate default\n" + "route-map name\n") + + +/* Set neighbor's BGP port. */ +static int peer_port_vty(struct vty *vty, const char *ip_str, int afi, + const char *port_str) +{ + struct peer *peer; + uint16_t port; + struct servent *sp; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!port_str) { + sp = getservbyname("bgp", "tcp"); + port = (sp == NULL) ? BGP_PORT_DEFAULT : ntohs(sp->s_port); + } else { + port = strtoul(port_str, NULL, 10); + } + + peer_port_set(peer, port); + + return CMD_SUCCESS; +} + +/* Set specified peer's BGP port. */ +DEFUN (neighbor_port, + neighbor_port_cmd, + "neighbor port (0-65535)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Neighbor's BGP port\n" + "TCP port number\n") +{ + int idx_ip = 1; + int idx_number = 3; + return peer_port_vty(vty, argv[idx_ip]->arg, AFI_IP, + argv[idx_number]->arg); +} + +DEFUN (no_neighbor_port, + no_neighbor_port_cmd, + "no neighbor port [(0-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Neighbor's BGP port\n" + "TCP port number\n") +{ + int idx_ip = 2; + return peer_port_vty(vty, argv[idx_ip]->arg, AFI_IP, NULL); +} + + +/* neighbor weight. */ +static int peer_weight_set_vty(struct vty *vty, const char *ip_str, afi_t afi, + safi_t safi, const char *weight_str) +{ + int ret; + struct peer *peer; + unsigned long weight; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + weight = strtoul(weight_str, NULL, 10); + + ret = peer_weight_set(peer, afi, safi, weight); + return bgp_vty_return(vty, ret); +} + +static int peer_weight_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, + safi_t safi) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_weight_unset(peer, afi, safi); + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_weight, + neighbor_weight_cmd, + "neighbor weight (0-65535)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set default weight for routes from this neighbor\n" + "default weight\n") +{ + int idx_peer = 1; + int idx_number = 3; + return peer_weight_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), argv[idx_number]->arg); +} + +ALIAS_HIDDEN(neighbor_weight, neighbor_weight_hidden_cmd, + "neighbor weight (0-65535)", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Set default weight for routes from this neighbor\n" + "default weight\n") + +DEFUN (no_neighbor_weight, + no_neighbor_weight_cmd, + "no neighbor weight [(0-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set default weight for routes from this neighbor\n" + "default weight\n") +{ + int idx_peer = 2; + return peer_weight_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty)); +} + +ALIAS_HIDDEN(no_neighbor_weight, no_neighbor_weight_hidden_cmd, + "no neighbor weight [(0-65535)]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Set default weight for routes from this neighbor\n" + "default weight\n") + + +/* Override capability negotiation. */ +DEFUN (neighbor_override_capability, + neighbor_override_capability_cmd, + "neighbor override-capability", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Override capability negotiation result\n") +{ + int idx_peer = 1; + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_OVERRIDE_CAPABILITY); +} + +DEFUN (no_neighbor_override_capability, + no_neighbor_override_capability_cmd, + "no neighbor override-capability", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Override capability negotiation result\n") +{ + int idx_peer = 2; + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_OVERRIDE_CAPABILITY); +} + +DEFUN (neighbor_strict_capability, + neighbor_strict_capability_cmd, + "neighbor strict-capability-match", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Strict capability negotiation match\n") +{ + int idx_peer = 1; + + return peer_flag_set_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_STRICT_CAP_MATCH); +} + +DEFUN (no_neighbor_strict_capability, + no_neighbor_strict_capability_cmd, + "no neighbor strict-capability-match", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Strict capability negotiation match\n") +{ + int idx_peer = 2; + + return peer_flag_unset_vty(vty, argv[idx_peer]->arg, + PEER_FLAG_STRICT_CAP_MATCH); +} + +static int peer_timers_set_vty(struct vty *vty, const char *ip_str, + const char *keep_str, const char *hold_str) +{ + int ret; + struct peer *peer; + uint32_t keepalive; + uint32_t holdtime; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + keepalive = strtoul(keep_str, NULL, 10); + holdtime = strtoul(hold_str, NULL, 10); + + ret = peer_timers_set(peer, keepalive, holdtime); + + return bgp_vty_return(vty, ret); +} + +static int peer_timers_unset_vty(struct vty *vty, const char *ip_str) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_timers_unset(peer); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_timers, + neighbor_timers_cmd, + "neighbor timers (0-65535) (0-65535)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "Keepalive interval\n" + "Holdtime\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_number_2 = 4; + return peer_timers_set_vty(vty, argv[idx_peer]->arg, + argv[idx_number]->arg, + argv[idx_number_2]->arg); +} + +DEFUN (no_neighbor_timers, + no_neighbor_timers_cmd, + "no neighbor timers [(0-65535) (0-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "Keepalive interval\n" + "Holdtime\n") +{ + int idx_peer = 2; + return peer_timers_unset_vty(vty, argv[idx_peer]->arg); +} + + +static int peer_timers_connect_set_vty(struct vty *vty, const char *ip_str, + const char *time_str) +{ + int ret; + struct peer *peer; + uint32_t connect; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + connect = strtoul(time_str, NULL, 10); + + ret = peer_timers_connect_set(peer, connect); + + return bgp_vty_return(vty, ret); +} + +static int peer_timers_connect_unset_vty(struct vty *vty, const char *ip_str) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_timers_connect_unset(peer); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_timers_connect, + neighbor_timers_connect_cmd, + "neighbor timers connect (1-65535)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "BGP connect timer\n" + "Connect timer\n") +{ + int idx_peer = 1; + int idx_number = 4; + return peer_timers_connect_set_vty(vty, argv[idx_peer]->arg, + argv[idx_number]->arg); +} + +DEFUN (no_neighbor_timers_connect, + no_neighbor_timers_connect_cmd, + "no neighbor timers connect [(1-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "BGP connect timer\n" + "Connect timer\n") +{ + int idx_peer = 2; + return peer_timers_connect_unset_vty(vty, argv[idx_peer]->arg); +} + +DEFPY (neighbor_timers_delayopen, + neighbor_timers_delayopen_cmd, + "neighbor $neighbor timers delayopen (1-240)$interval", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "RFC 4271 DelayOpenTimer\n" + "DelayOpenTime timer interval\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (!interval) { + if (peer_timers_delayopen_unset(peer)) + return CMD_WARNING_CONFIG_FAILED; + } else { + if (peer_timers_delayopen_set(peer, interval)) + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFPY (no_neighbor_timers_delayopen, + no_neighbor_timers_delayopen_cmd, + "no neighbor $neighbor timers delayopen [(0-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP per neighbor timers\n" + "RFC 4271 DelayOpenTimer\n" + "DelayOpenTime timer interval\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (peer_timers_delayopen_unset(peer)) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +static int peer_advertise_interval_vty(struct vty *vty, const char *ip_str, + const char *time_str, int set) +{ + int ret; + struct peer *peer; + uint32_t routeadv = 0; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (time_str) + routeadv = strtoul(time_str, NULL, 10); + + if (set) + ret = peer_advertise_interval_set(peer, routeadv); + else + ret = peer_advertise_interval_unset(peer); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_advertise_interval, + neighbor_advertise_interval_cmd, + "neighbor advertisement-interval (0-600)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Minimum interval between sending BGP routing updates\n" + "time in seconds\n") +{ + int idx_peer = 1; + int idx_number = 3; + return peer_advertise_interval_vty(vty, argv[idx_peer]->arg, + argv[idx_number]->arg, 1); +} + +DEFUN (no_neighbor_advertise_interval, + no_neighbor_advertise_interval_cmd, + "no neighbor advertisement-interval [(0-600)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Minimum interval between sending BGP routing updates\n" + "time in seconds\n") +{ + int idx_peer = 2; + return peer_advertise_interval_vty(vty, argv[idx_peer]->arg, NULL, 0); +} + + +/* Time to wait before processing route-map updates */ +DEFUN (bgp_set_route_map_delay_timer, + bgp_set_route_map_delay_timer_cmd, + "bgp route-map delay-timer (0-600)", + SET_STR + "BGP route-map delay timer\n" + "Time in secs to wait before processing route-map changes\n" + "0 disables the timer, no route updates happen when route-maps change\n") +{ + int idx_number = 3; + uint32_t rmap_delay_timer; + + if (argv[idx_number]->arg) { + rmap_delay_timer = strtoul(argv[idx_number]->arg, NULL, 10); + bm->rmap_update_timer = rmap_delay_timer; + + /* if the dynamic update handling is being disabled, and a timer + * is + * running, stop the timer and act as if the timer has already + * fired. + */ + if (!rmap_delay_timer && bm->t_rmap_update) { + EVENT_OFF(bm->t_rmap_update); + event_execute(bm->master, bgp_route_map_update_timer, + NULL, 0, NULL); + } + return CMD_SUCCESS; + } else { + vty_out(vty, "%% BGP invalid route-map delay-timer\n"); + return CMD_WARNING_CONFIG_FAILED; + } +} + +DEFUN (no_bgp_set_route_map_delay_timer, + no_bgp_set_route_map_delay_timer_cmd, + "no bgp route-map delay-timer [(0-600)]", + NO_STR + BGP_STR + "Default BGP route-map delay timer\n" + "Reset to default time to wait for processing route-map changes\n" + "0 disables the timer, no route updates happen when route-maps change\n") +{ + + bm->rmap_update_timer = RMAP_DEFAULT_UPDATE_TIMER; + + return CMD_SUCCESS; +} + +/* neighbor interface */ +static int peer_interface_vty(struct vty *vty, const char *ip_str, + const char *str) +{ + struct peer *peer; + + peer = peer_lookup_vty(vty, ip_str); + if (!peer || peer->conf_if) { + vty_out(vty, "%% BGP invalid peer %s\n", ip_str); + return CMD_WARNING_CONFIG_FAILED; + } + + if (str) + peer_interface_set(peer, str); + else + peer_interface_unset(peer); + + return CMD_SUCCESS; +} + +DEFUN (neighbor_interface, + neighbor_interface_cmd, + "neighbor interface WORD", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR + "Interface\n" + "Interface name\n") +{ + int idx_ip = 1; + int idx_word = 3; + + return peer_interface_vty(vty, argv[idx_ip]->arg, argv[idx_word]->arg); +} + +DEFUN (no_neighbor_interface, + no_neighbor_interface_cmd, + "no neighbor interface WORD", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR + "Interface\n" + "Interface name\n") +{ + int idx_peer = 2; + + return peer_interface_vty(vty, argv[idx_peer]->arg, NULL); +} + +DEFUN (neighbor_distribute_list, + neighbor_distribute_list_cmd, + "neighbor distribute-list ACCESSLIST_NAME ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "IP Access-list name\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") +{ + int idx_peer = 1; + int idx_acl = 3; + int direct, ret; + struct peer *peer; + + const char *pstr = argv[idx_peer]->arg; + const char *acl = argv[idx_acl]->arg; + const char *inout = argv[argc - 1]->text; + + peer = peer_and_group_lookup_vty(vty, pstr); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + direct = strmatch(inout, "in") ? FILTER_IN : FILTER_OUT; + ret = peer_distribute_set(peer, bgp_node_afi(vty), bgp_node_safi(vty), + direct, acl); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN( + neighbor_distribute_list, neighbor_distribute_list_hidden_cmd, + "neighbor distribute-list ACCESSLIST_NAME ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "IP Access-list name\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") + +DEFUN (no_neighbor_distribute_list, + no_neighbor_distribute_list_cmd, + "no neighbor distribute-list ACCESSLIST_NAME ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "IP Access-list name\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") +{ + int idx_peer = 2; + int direct, ret; + struct peer *peer; + + const char *pstr = argv[idx_peer]->arg; + const char *inout = argv[argc - 1]->text; + + peer = peer_and_group_lookup_vty(vty, pstr); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + direct = strmatch(inout, "in") ? FILTER_IN : FILTER_OUT; + ret = peer_distribute_unset(peer, bgp_node_afi(vty), bgp_node_safi(vty), + direct); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN( + no_neighbor_distribute_list, no_neighbor_distribute_list_hidden_cmd, + "no neighbor distribute-list ACCESSLIST_NAME ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "IP Access-list name\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") + +/* Set prefix list to the peer. */ +static int peer_prefix_list_set_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *name_str, + const char *direct_str) +{ + int ret; + int direct = FILTER_IN; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "i", 1) == 0) + direct = FILTER_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = FILTER_OUT; + + ret = peer_prefix_list_set(peer, afi, safi, direct, name_str); + + return bgp_vty_return(vty, ret); +} + +static int peer_prefix_list_unset_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *direct_str) +{ + int ret; + struct peer *peer; + int direct = FILTER_IN; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "i", 1) == 0) + direct = FILTER_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = FILTER_OUT; + + ret = peer_prefix_list_unset(peer, afi, safi, direct); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_prefix_list, + neighbor_prefix_list_cmd, + "neighbor prefix-list WORD ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "Name of a prefix list\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") +{ + int idx_peer = 1; + int idx_word = 3; + int idx_in_out = 4; + return peer_prefix_list_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg, argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(neighbor_prefix_list, neighbor_prefix_list_hidden_cmd, + "neighbor prefix-list WORD ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "Name of a prefix list\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") + +DEFUN (no_neighbor_prefix_list, + no_neighbor_prefix_list_cmd, + "no neighbor prefix-list WORD ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "Name of a prefix list\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") +{ + int idx_peer = 2; + int idx_in_out = 5; + return peer_prefix_list_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(no_neighbor_prefix_list, no_neighbor_prefix_list_hidden_cmd, + "no neighbor prefix-list WORD ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Filter updates to/from this neighbor\n" + "Name of a prefix list\n" + "Filter incoming updates\n" + "Filter outgoing updates\n") + +static int peer_aslist_set_vty(struct vty *vty, const char *ip_str, afi_t afi, + safi_t safi, const char *name_str, + const char *direct_str) +{ + int ret; + struct peer *peer; + int direct = FILTER_IN; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "i", 1) == 0) + direct = FILTER_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = FILTER_OUT; + + ret = peer_aslist_set(peer, afi, safi, direct, name_str); + + return bgp_vty_return(vty, ret); +} + +static int peer_aslist_unset_vty(struct vty *vty, const char *ip_str, afi_t afi, + safi_t safi, const char *direct_str) +{ + int ret; + struct peer *peer; + int direct = FILTER_IN; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "i", 1) == 0) + direct = FILTER_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = FILTER_OUT; + + ret = peer_aslist_unset(peer, afi, safi, direct); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_filter_list, + neighbor_filter_list_cmd, + "neighbor filter-list AS_PATH_FILTER_NAME ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Establish BGP filters\n" + "AS path access-list name\n" + "Filter incoming routes\n" + "Filter outgoing routes\n") +{ + int idx_peer = 1; + int idx_word = 3; + int idx_in_out = 4; + return peer_aslist_set_vty(vty, argv[idx_peer]->arg, bgp_node_afi(vty), + bgp_node_safi(vty), argv[idx_word]->arg, + argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(neighbor_filter_list, neighbor_filter_list_hidden_cmd, + "neighbor filter-list AS_PATH_FILTER_NAME ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Establish BGP filters\n" + "AS path access-list name\n" + "Filter incoming routes\n" + "Filter outgoing routes\n") + +DEFUN (no_neighbor_filter_list, + no_neighbor_filter_list_cmd, + "no neighbor filter-list AS_PATH_FILTER_NAME ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Establish BGP filters\n" + "AS path access-list name\n" + "Filter incoming routes\n" + "Filter outgoing routes\n") +{ + int idx_peer = 2; + int idx_in_out = 5; + return peer_aslist_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(no_neighbor_filter_list, no_neighbor_filter_list_hidden_cmd, + "no neighbor filter-list AS_PATH_FILTER_NAME ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Establish BGP filters\n" + "AS path access-list name\n" + "Filter incoming routes\n" + "Filter outgoing routes\n") + +/* Set advertise-map to the peer. */ +static int peer_advertise_map_set_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *advertise_str, + const char *condition_str, bool condition, + bool set) +{ + int ret = CMD_WARNING_CONFIG_FAILED; + struct peer *peer; + struct route_map *advertise_map; + struct route_map *condition_map; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return ret; + + condition_map = route_map_lookup_warn_noexist(vty, condition_str); + advertise_map = route_map_lookup_warn_noexist(vty, advertise_str); + + if (set) + ret = peer_advertise_map_set(peer, afi, safi, advertise_str, + advertise_map, condition_str, + condition_map, condition); + else + ret = peer_advertise_map_unset(peer, afi, safi, advertise_str, + advertise_map, condition_str, + condition_map, condition); + + return bgp_vty_return(vty, ret); +} + +DEFPY (bgp_condadv_period, + bgp_condadv_period_cmd, + "[no$no] bgp conditional-advertisement timer (5-240)$period", + NO_STR + BGP_STR + "Conditional advertisement settings\n" + "Set period to rescan BGP table to check if condition is met\n" + "Period between BGP table scans, in seconds; default 60\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->condition_check_period = + no ? DEFAULT_CONDITIONAL_ROUTES_POLL_TIME : period; + + return CMD_SUCCESS; +} + +DEFPY (bgp_def_originate_eval, + bgp_def_originate_eval_cmd, + "[no$no] bgp default-originate timer (0-3600)$timer", + NO_STR + BGP_STR + "Control default-originate\n" + "Set period to rescan BGP table to check if default-originate condition is met\n" + "Period between BGP table scans, in seconds; default 5\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->rmap_def_originate_eval_timer = + no ? RMAP_DEFAULT_ORIGINATE_EVAL_TIMER : timer; + + if (bgp->t_rmap_def_originate_eval) + EVENT_OFF(bgp->t_rmap_def_originate_eval); + + return CMD_SUCCESS; +} + +DEFPY (neighbor_advertise_map, + neighbor_advertise_map_cmd, + "[no$no] neighbor $neighbor advertise-map RMAP_NAME$advertise_str $exist RMAP_NAME$condition_str", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Route-map to conditionally advertise routes\n" + "Name of advertise map\n" + "Advertise routes only if prefixes in exist-map are installed in BGP table\n" + "Advertise routes only if prefixes in non-exist-map are not installed in BGP table\n" + "Name of the exist or non exist map\n") +{ + bool condition = CONDITION_EXIST; + + if (!strcmp(exist, "non-exist-map")) + condition = CONDITION_NON_EXIST; + + return peer_advertise_map_set_vty(vty, neighbor, bgp_node_afi(vty), + bgp_node_safi(vty), advertise_str, + condition_str, condition, !no); +} + +ALIAS_HIDDEN(neighbor_advertise_map, neighbor_advertise_map_hidden_cmd, + "[no$no] neighbor $neighbor advertise-map RMAP_NAME$advertise_str $exist RMAP_NAME$condition_str", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Route-map to conditionally advertise routes\n" + "Name of advertise map\n" + "Advertise routes only if prefixes in exist-map are installed in BGP table\n" + "Advertise routes only if prefixes in non-exist-map are not installed in BGP table\n" + "Name of the exist or non exist map\n") + +/* Set route-map to the peer. */ +static int peer_route_map_set_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, const char *name_str, + const char *direct_str) +{ + int ret; + struct peer *peer; + int direct = RMAP_IN; + struct route_map *route_map; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "in", 2) == 0) + direct = RMAP_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = RMAP_OUT; + + route_map = route_map_lookup_warn_noexist(vty, name_str); + ret = peer_route_map_set(peer, afi, safi, direct, name_str, route_map); + + return bgp_vty_return(vty, ret); +} + +static int peer_route_map_unset_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *direct_str) +{ + int ret; + struct peer *peer; + int direct = RMAP_IN; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + /* Check filter direction. */ + if (strncmp(direct_str, "in", 2) == 0) + direct = RMAP_IN; + else if (strncmp(direct_str, "o", 1) == 0) + direct = RMAP_OUT; + + ret = peer_route_map_unset(peer, afi, safi, direct); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_route_map, + neighbor_route_map_cmd, + "neighbor route-map RMAP_NAME ", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Apply route map to neighbor\n" + "Name of route map\n" + "Apply map to incoming routes\n" + "Apply map to outbound routes\n") +{ + int idx_peer = 1; + int idx_word = 3; + int idx_in_out = 4; + return peer_route_map_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg, argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(neighbor_route_map, neighbor_route_map_hidden_cmd, + "neighbor route-map RMAP_NAME ", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Apply route map to neighbor\n" + "Name of route map\n" + "Apply map to incoming routes\n" + "Apply map to outbound routes\n") + +DEFUN (no_neighbor_route_map, + no_neighbor_route_map_cmd, + "no neighbor route-map RMAP_NAME ", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Apply route map to neighbor\n" + "Name of route map\n" + "Apply map to incoming routes\n" + "Apply map to outbound routes\n") +{ + int idx_peer = 2; + int idx_in_out = 5; + return peer_route_map_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_in_out]->arg); +} + +ALIAS_HIDDEN(no_neighbor_route_map, no_neighbor_route_map_hidden_cmd, + "no neighbor route-map RMAP_NAME ", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Apply route map to neighbor\n" + "Name of route map\n" + "Apply map to incoming routes\n" + "Apply map to outbound routes\n") + +/* Set unsuppress-map to the peer. */ +static int peer_unsuppress_map_set_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *name_str) +{ + int ret; + struct peer *peer; + struct route_map *route_map; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + route_map = route_map_lookup_warn_noexist(vty, name_str); + ret = peer_unsuppress_map_set(peer, afi, safi, name_str, route_map); + + return bgp_vty_return(vty, ret); +} + +/* Unset route-map from the peer. */ +static int peer_unsuppress_map_unset_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_unsuppress_map_unset(peer, afi, safi); + + return bgp_vty_return(vty, ret); +} + +DEFUN (neighbor_unsuppress_map, + neighbor_unsuppress_map_cmd, + "neighbor unsuppress-map WORD", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Route-map to selectively unsuppress suppressed routes\n" + "Name of route map\n") +{ + int idx_peer = 1; + int idx_word = 3; + return peer_unsuppress_map_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_word]->arg); +} + +ALIAS_HIDDEN(neighbor_unsuppress_map, neighbor_unsuppress_map_hidden_cmd, + "neighbor unsuppress-map WORD", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Route-map to selectively unsuppress suppressed routes\n" + "Name of route map\n") + +DEFUN (no_neighbor_unsuppress_map, + no_neighbor_unsuppress_map_cmd, + "no neighbor unsuppress-map WORD", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Route-map to selectively unsuppress suppressed routes\n" + "Name of route map\n") +{ + int idx_peer = 2; + return peer_unsuppress_map_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), + bgp_node_safi(vty)); +} + +ALIAS_HIDDEN(no_neighbor_unsuppress_map, no_neighbor_unsuppress_map_hidden_cmd, + "no neighbor unsuppress-map WORD", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Route-map to selectively unsuppress suppressed routes\n" + "Name of route map\n") + +static int peer_maximum_prefix_set_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi, + const char *num_str, + const char *threshold_str, int warning, + const char *restart_str, + const char *force_str) +{ + int ret; + struct peer *peer; + uint32_t max; + uint8_t threshold; + uint16_t restart; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + max = strtoul(num_str, NULL, 10); + if (threshold_str) + threshold = atoi(threshold_str); + else + threshold = MAXIMUM_PREFIX_THRESHOLD_DEFAULT; + + if (restart_str) + restart = atoi(restart_str); + else + restart = 0; + + ret = peer_maximum_prefix_set(peer, afi, safi, max, threshold, warning, + restart, force_str ? true : false); + + return bgp_vty_return(vty, ret); +} + +static int peer_maximum_prefix_unset_vty(struct vty *vty, const char *ip_str, + afi_t afi, safi_t safi) +{ + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, ip_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_maximum_prefix_unset(peer, afi, safi); + + return bgp_vty_return(vty, ret); +} + +/* Maximum number of prefix to be sent to the neighbor. */ +DEFUN(neighbor_maximum_prefix_out, + neighbor_maximum_prefix_out_cmd, + "neighbor maximum-prefix-out (1-4294967295)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to be sent to this peer\n" + "Maximum no. of prefix limit\n") +{ + int ret; + int idx_peer = 1; + int idx_number = 3; + struct peer *peer; + uint32_t max; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + max = strtoul(argv[idx_number]->arg, NULL, 10); + + ret = peer_maximum_prefix_out_set(peer, afi, safi, max); + + return bgp_vty_return(vty, ret); +} + +DEFUN(no_neighbor_maximum_prefix_out, + no_neighbor_maximum_prefix_out_cmd, + "no neighbor maximum-prefix-out [(1-4294967295)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to be sent to this peer\n" + "Maximum no. of prefix limit\n") +{ + int ret; + int idx_peer = 2; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_maximum_prefix_out_unset(peer, afi, safi); + + return bgp_vty_return(vty, ret); +} + +/* Maximum number of prefix configuration. Prefix count is different + for each peer configuration. So this configuration can be set for + each peer configuration. */ +DEFUN (neighbor_maximum_prefix, + neighbor_maximum_prefix_cmd, + "neighbor maximum-prefix (1-4294967295) [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, NULL, 0, NULL, force); +} + +ALIAS_HIDDEN(neighbor_maximum_prefix, neighbor_maximum_prefix_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Force checking all received routes not only accepted\n") + +DEFUN (neighbor_maximum_prefix_threshold, + neighbor_maximum_prefix_threshold_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_number_2 = 4; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, argv[idx_number_2]->arg, 0, NULL, force); +} + +ALIAS_HIDDEN( + neighbor_maximum_prefix_threshold, + neighbor_maximum_prefix_threshold_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Force checking all received routes not only accepted\n") + +DEFUN (neighbor_maximum_prefix_warning, + neighbor_maximum_prefix_warning_cmd, + "neighbor maximum-prefix (1-4294967295) warning-only [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, NULL, 1, NULL, force); +} + +ALIAS_HIDDEN( + neighbor_maximum_prefix_warning, + neighbor_maximum_prefix_warning_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) warning-only [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") + +DEFUN (neighbor_maximum_prefix_threshold_warning, + neighbor_maximum_prefix_threshold_warning_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) warning-only [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_number_2 = 4; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, argv[idx_number_2]->arg, 1, NULL, force); +} + +ALIAS_HIDDEN( + neighbor_maximum_prefix_threshold_warning, + neighbor_maximum_prefix_threshold_warning_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) warning-only [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") + +DEFUN (neighbor_maximum_prefix_restart, + neighbor_maximum_prefix_restart_cmd, + "neighbor maximum-prefix (1-4294967295) restart (1-65535) [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_number_2 = 5; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, NULL, 0, argv[idx_number_2]->arg, force); +} + +ALIAS_HIDDEN( + neighbor_maximum_prefix_restart, + neighbor_maximum_prefix_restart_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) restart (1-65535) [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefix accept from this peer\n" + "maximum no. of prefix limit\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Force checking all received routes not only accepted\n") + +DEFUN (neighbor_maximum_prefix_threshold_restart, + neighbor_maximum_prefix_threshold_restart_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) restart (1-65535) [force]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 1; + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 6; + int idx_force = 0; + char *force = NULL; + + if (argv_find(argv, argc, "force", &idx_force)) + force = argv[idx_force]->arg; + + return peer_maximum_prefix_set_vty( + vty, argv[idx_peer]->arg, bgp_node_afi(vty), bgp_node_safi(vty), + argv[idx_number]->arg, argv[idx_number_2]->arg, 0, + argv[idx_number_3]->arg, force); +} + +ALIAS_HIDDEN( + neighbor_maximum_prefix_threshold_restart, + neighbor_maximum_prefix_threshold_restart_hidden_cmd, + "neighbor maximum-prefix (1-4294967295) (1-100) restart (1-65535) [force]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Force checking all received routes not only accepted\n") + +DEFUN (no_neighbor_maximum_prefix, + no_neighbor_maximum_prefix_cmd, + "no neighbor maximum-prefix [(1-4294967295) [(1-100)] [restart (1-65535)] [warning-only] [force]]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") +{ + int idx_peer = 2; + return peer_maximum_prefix_unset_vty(vty, argv[idx_peer]->arg, + bgp_node_afi(vty), + bgp_node_safi(vty)); +} + +ALIAS_HIDDEN( + no_neighbor_maximum_prefix, no_neighbor_maximum_prefix_hidden_cmd, + "no neighbor maximum-prefix [(1-4294967295) [(1-100)] [restart (1-65535)] [warning-only] [force]]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Maximum number of prefixes to accept from this peer\n" + "maximum no. of prefix limit\n" + "Threshold value (%) at which to generate a warning msg\n" + "Restart bgp connection after limit is exceeded\n" + "Restart interval in minutes\n" + "Only give warning message when limit is exceeded\n" + "Force checking all received routes not only accepted\n") + +/* "neighbor accept-own" */ +DEFPY (neighbor_accept_own, + neighbor_accept_own_cmd, + "[no$no] neighbor $neighbor accept-own", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable handling of self-originated VPN routes containing ACCEPT_OWN community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + ret = peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ACCEPT_OWN); + else + ret = peer_af_flag_set(peer, afi, safi, PEER_FLAG_ACCEPT_OWN); + + return bgp_vty_return(vty, ret); +} + +/* "neighbor soo" */ +DEFPY (neighbor_soo, + neighbor_soo_cmd, + "neighbor $neighbor soo ASN:NN_OR_IP-ADDRESS:NN$soo", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set the Site-of-Origin (SoO) extended community\n" + "VPN extended community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + struct ecommunity *ecomm_soo; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ecomm_soo = ecommunity_str2com(soo, ECOMMUNITY_SITE_ORIGIN, 0); + if (!ecomm_soo) { + vty_out(vty, "%% Malformed SoO extended community\n"); + return CMD_WARNING; + } + ecommunity_str(ecomm_soo); + + if (!ecommunity_match(peer->soo[afi][safi], ecomm_soo)) { + ecommunity_free(&peer->soo[afi][safi]); + peer->soo[afi][safi] = ecomm_soo; + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_SOO); + } else { + ecommunity_free(&ecomm_soo); + } + + return bgp_vty_return(vty, + peer_af_flag_set(peer, afi, safi, PEER_FLAG_SOO)); +} + +DEFPY (no_neighbor_soo, + no_neighbor_soo_cmd, + "no neighbor $neighbor soo [ASN:NN_OR_IP-ADDRESS:NN$soo]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Set the Site-of-Origin (SoO) extended community\n" + "VPN extended community\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ecommunity_free(&peer->soo[afi][safi]); + + return bgp_vty_return( + vty, peer_af_flag_unset(peer, afi, safi, PEER_FLAG_SOO)); +} + +/* "neighbor allowas-in" */ +DEFUN (neighbor_allowas_in, + neighbor_allowas_in_cmd, + "neighbor allowas-in [<(1-10)|origin>]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Accept as-path with my AS present in it\n" + "Number of occurrences of AS number\n" + "Only accept my AS in the as-path if the route was originated in my AS\n") +{ + int idx_peer = 1; + int idx_number_origin = 3; + int ret; + int origin = 0; + struct peer *peer; + int allow_num = 0; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (argc <= idx_number_origin) + allow_num = 3; + else { + if (argv[idx_number_origin]->type == WORD_TKN) + origin = 1; + else + allow_num = atoi(argv[idx_number_origin]->arg); + } + + ret = peer_allowas_in_set(peer, bgp_node_afi(vty), bgp_node_safi(vty), + allow_num, origin); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN( + neighbor_allowas_in, neighbor_allowas_in_hidden_cmd, + "neighbor allowas-in [<(1-10)|origin>]", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Accept as-path with my AS present in it\n" + "Number of occurrences of AS number\n" + "Only accept my AS in the as-path if the route was originated in my AS\n") + +DEFUN (no_neighbor_allowas_in, + no_neighbor_allowas_in_cmd, + "no neighbor allowas-in [<(1-10)|origin>]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "allow local ASN appears in aspath attribute\n" + "Number of occurrences of AS number\n" + "Only accept my AS in the as-path if the route was originated in my AS\n") +{ + int idx_peer = 2; + int ret; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_allowas_in_unset(peer, bgp_node_afi(vty), + bgp_node_safi(vty)); + + return bgp_vty_return(vty, ret); +} + +ALIAS_HIDDEN( + no_neighbor_allowas_in, no_neighbor_allowas_in_hidden_cmd, + "no neighbor allowas-in [<(1-10)|origin>]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "allow local ASN appears in aspath attribute\n" + "Number of occurrences of AS number\n" + "Only accept my AS in the as-path if the route was originated in my AS\n") + +DEFUN (neighbor_ttl_security, + neighbor_ttl_security_cmd, + "neighbor ttl-security hops (1-254)", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP ttl-security parameters\n" + "Specify the maximum number of hops to the BGP peer\n" + "Number of hops to BGP peer\n") +{ + int idx_peer = 1; + int idx_number = 4; + struct peer *peer; + int gtsm_hops; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + gtsm_hops = strtoul(argv[idx_number]->arg, NULL, 10); + + /* + * If 'neighbor swpX', then this is for directly connected peers, + * we should not accept a ttl-security hops value greater than 1. + */ + if (peer->conf_if && (gtsm_hops > BGP_GTSM_HOPS_CONNECTED)) { + vty_out(vty, + "%s is directly connected peer, hops cannot exceed 1\n", + argv[idx_peer]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + return bgp_vty_return(vty, peer_ttl_security_hops_set(peer, gtsm_hops)); +} + +DEFUN (no_neighbor_ttl_security, + no_neighbor_ttl_security_cmd, + "no neighbor ttl-security hops (1-254)", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "BGP ttl-security parameters\n" + "Specify the maximum number of hops to the BGP peer\n" + "Number of hops to BGP peer\n") +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + return bgp_vty_return(vty, peer_ttl_security_hops_unset(peer)); +} + +/* disable-addpath-rx */ +DEFUN(neighbor_disable_addpath_rx, + neighbor_disable_addpath_rx_cmd, + "neighbor disable-addpath-rx", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Do not accept additional paths\n") +{ + char *peer_str = argv[1]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + int action; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + action = bgp_addpath_capability_action(peer->addpath_type[afi][safi], 0); + + ret = peer_af_flag_set_vty(vty, peer_str, afi, safi, + PEER_FLAG_DISABLE_ADDPATH_RX); + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ADDPATH, action); + + return ret; +} + +DEFUN(no_neighbor_disable_addpath_rx, + no_neighbor_disable_addpath_rx_cmd, + "no neighbor disable-addpath-rx", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Do not accept additional paths\n") +{ + char *peer_str = argv[2]->arg; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + int action; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + action = bgp_addpath_capability_action(peer->addpath_type[afi][safi], 0); + + ret = peer_af_flag_unset_vty(vty, peer_str, afi, safi, + PEER_FLAG_DISABLE_ADDPATH_RX); + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_ADDPATH, action); + + return ret; +} + +DEFUN (neighbor_addpath_tx_all_paths, + neighbor_addpath_tx_all_paths_cmd, + "neighbor addpath-tx-all-paths", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise all paths to a neighbor\n") +{ + int idx_peer = 1; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + bgp_addpath_set_peer_type(peer, afi, safi, BGP_ADDPATH_ALL, 0); + + return CMD_SUCCESS; +} + +ALIAS_HIDDEN(neighbor_addpath_tx_all_paths, + neighbor_addpath_tx_all_paths_hidden_cmd, + "neighbor addpath-tx-all-paths", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Use addpath to advertise all paths to a neighbor\n") + +DEFUN (no_neighbor_addpath_tx_all_paths, + no_neighbor_addpath_tx_all_paths_cmd, + "no neighbor addpath-tx-all-paths", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise all paths to a neighbor\n") +{ + int idx_peer = 2; + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (peer->addpath_type[afi][safi] != BGP_ADDPATH_ALL) { + vty_out(vty, + "%% Peer not currently configured to transmit all paths."); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_addpath_set_peer_type(peer, afi, safi, BGP_ADDPATH_NONE, 0); + + return CMD_SUCCESS; +} + +ALIAS_HIDDEN(no_neighbor_addpath_tx_all_paths, + no_neighbor_addpath_tx_all_paths_hidden_cmd, + "no neighbor addpath-tx-all-paths", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Use addpath to advertise all paths to a neighbor\n") + +DEFPY (neighbor_addpath_tx_best_selected_paths, + neighbor_addpath_tx_best_selected_paths_cmd, + "neighbor $neighbor addpath-tx-best-selected (1-6)$paths", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise best selected paths to a neighbor\n" + "The number of best paths\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), + BGP_ADDPATH_BEST_SELECTED, paths); + return CMD_SUCCESS; +} + +DEFPY (no_neighbor_addpath_tx_best_selected_paths, + no_neighbor_addpath_tx_best_selected_paths_cmd, + "no neighbor $neighbor addpath-tx-best-selected [(1-6)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise best selected paths to a neighbor\n" + "The number of best paths\n") +{ + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), + BGP_ADDPATH_BEST_SELECTED, 0); + return CMD_SUCCESS; +} + +DEFUN (neighbor_addpath_tx_bestpath_per_as, + neighbor_addpath_tx_bestpath_per_as_cmd, + "neighbor addpath-tx-bestpath-per-AS", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise the bestpath per each neighboring AS\n") +{ + int idx_peer = 1; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), + BGP_ADDPATH_BEST_PER_AS, 0); + + return CMD_SUCCESS; +} + +ALIAS_HIDDEN(neighbor_addpath_tx_bestpath_per_as, + neighbor_addpath_tx_bestpath_per_as_hidden_cmd, + "neighbor addpath-tx-bestpath-per-AS", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Use addpath to advertise the bestpath per each neighboring AS\n") + +DEFUN (no_neighbor_addpath_tx_bestpath_per_as, + no_neighbor_addpath_tx_bestpath_per_as_cmd, + "no neighbor addpath-tx-bestpath-per-AS", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Use addpath to advertise the bestpath per each neighboring AS\n") +{ + int idx_peer = 2; + struct peer *peer; + + peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (peer->addpath_type[bgp_node_afi(vty)][bgp_node_safi(vty)] + != BGP_ADDPATH_BEST_PER_AS) { + vty_out(vty, + "%% Peer not currently configured to transmit all best path per as."); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_addpath_set_peer_type(peer, bgp_node_afi(vty), bgp_node_safi(vty), + BGP_ADDPATH_NONE, 0); + + return CMD_SUCCESS; +} + +ALIAS_HIDDEN(no_neighbor_addpath_tx_bestpath_per_as, + no_neighbor_addpath_tx_bestpath_per_as_hidden_cmd, + "no neighbor addpath-tx-bestpath-per-AS", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "Use addpath to advertise the bestpath per each neighboring AS\n") + +DEFPY( + neighbor_aspath_loop_detection, neighbor_aspath_loop_detection_cmd, + "neighbor $neighbor sender-as-path-loop-detection", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Detect AS loops before sending to neighbor\n") +{ + return peer_flag_set_vty(vty, neighbor, PEER_FLAG_AS_LOOP_DETECTION); +} + +DEFPY (neighbor_addpath_paths_limit, + neighbor_addpath_paths_limit_cmd, + "neighbor $neighbor addpath-rx-paths-limit (1-65535)$paths_limit", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Paths Limit for Addpath to receive from the peer\n" + "Maximum number of paths\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_af_flag_set_vty(vty, neighbor, afi, safi, + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT); + + peer->addpath_paths_limit[afi][safi].send = paths_limit; + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_PATHS_LIMIT, + CAPABILITY_ACTION_SET); + + return ret; +} + +DEFPY (no_neighbor_addpath_paths_limit, + no_neighbor_addpath_paths_limit_cmd, + "no neighbor $neighbor addpath-rx-paths-limit [(1-65535)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Paths Limit for Addpath to receive from the peer\n" + "Maximum number of paths\n") +{ + struct peer *peer; + afi_t afi = bgp_node_afi(vty); + safi_t safi = bgp_node_safi(vty); + int ret; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + ret = peer_af_flag_unset_vty(vty, neighbor, afi, safi, + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT); + + peer->addpath_paths_limit[afi][safi].send = 0; + + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_PATHS_LIMIT, + CAPABILITY_ACTION_SET); + + return ret; +} + +DEFPY( + no_neighbor_aspath_loop_detection, + no_neighbor_aspath_loop_detection_cmd, + "no neighbor $neighbor sender-as-path-loop-detection", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Detect AS loops before sending to neighbor\n") +{ + return peer_flag_unset_vty(vty, neighbor, PEER_FLAG_AS_LOOP_DETECTION); +} + +DEFPY(neighbor_path_attribute_discard, + neighbor_path_attribute_discard_cmd, + "neighbor $neighbor path-attribute discard (1-255)...", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Manipulate path attributes from incoming UPDATE messages\n" + "Drop specified attributes from incoming UPDATE messages\n" + "Attribute number\n") +{ + struct peer *peer; + int idx = 0; + char *discard_attrs = NULL; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + argv_find(argv, argc, "(1-255)", &idx); + if (idx) + discard_attrs = argv_concat(argv, argc, idx); + + bgp_path_attribute_discard_vty(vty, peer, discard_attrs, true); + + XFREE(MTYPE_TMP, discard_attrs); + + return CMD_SUCCESS; +} + +DEFPY(no_neighbor_path_attribute_discard, + no_neighbor_path_attribute_discard_cmd, + "no neighbor $neighbor path-attribute discard [(1-255)]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Manipulate path attributes from incoming UPDATE messages\n" + "Drop specified attributes from incoming UPDATE messages\n" + "Attribute number\n") +{ + struct peer *peer; + int idx = 0; + char *discard_attrs = NULL; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + argv_find(argv, argc, "(1-255)", &idx); + if (idx) + discard_attrs = argv[idx]->arg; + + bgp_path_attribute_discard_vty(vty, peer, discard_attrs, false); + + XFREE(MTYPE_TMP, discard_attrs); + + return CMD_SUCCESS; +} + +DEFPY(neighbor_path_attribute_treat_as_withdraw, + neighbor_path_attribute_treat_as_withdraw_cmd, + "neighbor $neighbor path-attribute treat-as-withdraw (1-255)...", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Manipulate path attributes from incoming UPDATE messages\n" + "Treat-as-withdraw any incoming BGP UPDATE messages that contain the specified attribute\n" + "Attribute number\n") +{ + struct peer *peer; + int idx = 0; + char *withdraw_attrs = NULL; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + argv_find(argv, argc, "(1-255)", &idx); + if (idx) + withdraw_attrs = argv_concat(argv, argc, idx); + + bgp_path_attribute_withdraw_vty(vty, peer, withdraw_attrs, true); + + XFREE(MTYPE_TMP, withdraw_attrs); + + return CMD_SUCCESS; +} + +DEFPY(no_neighbor_path_attribute_treat_as_withdraw, + no_neighbor_path_attribute_treat_as_withdraw_cmd, + "no neighbor $neighbor path-attribute treat-as-withdraw (1-255)...", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Manipulate path attributes from incoming UPDATE messages\n" + "Treat-as-withdraw any incoming BGP UPDATE messages that contain the specified attribute\n" + "Attribute number\n") +{ + struct peer *peer; + int idx = 0; + char *withdraw_attrs = NULL; + + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + argv_find(argv, argc, "(1-255)", &idx); + if (idx) + withdraw_attrs = argv_concat(argv, argc, idx); + + bgp_path_attribute_withdraw_vty(vty, peer, withdraw_attrs, false); + + XFREE(MTYPE_TMP, withdraw_attrs); + + return CMD_SUCCESS; +} + +DEFPY(neighbor_damp, + neighbor_damp_cmd, + "neighbor $neighbor dampening [(1-45)$half [(1-20000)$reuse (1-20000)$suppress (1-255)$max]]", + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable neighbor route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + struct peer *peer = peer_and_group_lookup_vty(vty, neighbor); + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + if (!half) + half = DEFAULT_HALF_LIFE; + if (!reuse) { + reuse = DEFAULT_REUSE; + suppress = DEFAULT_SUPPRESS; + max = half * 4; + } + if (suppress < reuse) { + vty_out(vty, "Suppress value cannot be less than reuse value\n"); + return CMD_WARNING_CONFIG_FAILED; + } + bgp_peer_damp_enable(peer, bgp_node_afi(vty), bgp_node_safi(vty), + half * 60, reuse, suppress, max * 60); + return CMD_SUCCESS; +} + +DEFPY(no_neighbor_damp, + no_neighbor_damp_cmd, + "no neighbor $neighbor dampening [HALF [REUSE SUPPRESS MAX]]", + NO_STR + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Enable neighbor route-flap dampening\n" + "Half-life time for the penalty\n" + "Value to start reusing a route\n" + "Value to start suppressing a route\n" + "Maximum duration to suppress a stable route\n") +{ + struct peer *peer = peer_and_group_lookup_vty(vty, neighbor); + + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + bgp_peer_damp_disable(peer, bgp_node_afi(vty), bgp_node_safi(vty)); + return CMD_SUCCESS; +} + +DEFPY (show_ip_bgp_neighbor_damp_param, + show_ip_bgp_neighbor_damp_param_cmd, + "show [ip] bgp [ [unicast]] neighbors $neighbor dampening parameters [json]$json", + SHOW_STR + IP_STR + BGP_STR + BGP_AFI_HELP_STR + "Address Family modifier\n" + NEIGHBOR_STR + NEIGHBOR_ADDR_STR2 + "Neighbor route-flap dampening information\n" + "Display detail of configured dampening parameters\n" + JSON_STR) +{ + bool use_json = false; + int idx = 0; + afi_t afi = AFI_IP; + safi_t safi = SAFI_UNICAST; + struct peer *peer; + + if (argv_find(argv, argc, "ip", &idx)) + afi = AFI_IP; + if (argv_find(argv, argc, "ipv4", &idx)) + afi = AFI_IP; + if (argv_find(argv, argc, "ipv6", &idx)) + afi = AFI_IP6; + peer = peer_and_group_lookup_vty(vty, neighbor); + if (!peer) + return CMD_WARNING; + if (json) + use_json = true; + bgp_show_peer_dampening_parameters(vty, peer, afi, safi, use_json); + return CMD_SUCCESS; +} + +static int set_ecom_list(struct vty *vty, int argc, struct cmd_token **argv, + struct ecommunity **list, bool is_rt6) +{ + struct ecommunity *ecom = NULL; + struct ecommunity *ecomadd; + + for (; argc; --argc, ++argv) { + if (is_rt6) + ecomadd = ecommunity_str2com_ipv6(argv[0]->arg, + ECOMMUNITY_ROUTE_TARGET, + 0); + else + ecomadd = ecommunity_str2com(argv[0]->arg, + ECOMMUNITY_ROUTE_TARGET, + 0); + if (!ecomadd) { + vty_out(vty, "Malformed community-list value\n"); + if (ecom) + ecommunity_free(&ecom); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ecom) { + ecommunity_merge(ecom, ecomadd); + ecommunity_free(&ecomadd); + } else { + ecom = ecomadd; + } + } + + if (*list) { + ecommunity_free(&*list); + } + *list = ecom; + + return CMD_SUCCESS; +} + +/* + * v2vimport is true if we are handling a `import vrf ...` command + */ +static afi_t vpn_policy_getafi(struct vty *vty, struct bgp *bgp, bool v2vimport) +{ + afi_t afi; + + switch (vty->node) { + case BGP_IPV4_NODE: + afi = AFI_IP; + break; + case BGP_IPV6_NODE: + afi = AFI_IP6; + break; + default: + vty_out(vty, + "%% context error: valid only in address-family unicast block\n"); + return AFI_MAX; + } + + if (!v2vimport) { + if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT) + || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) { + vty_out(vty, + "%% error: Please unconfigure import vrf commands before using vpn commands\n"); + return AFI_MAX; + } + } else { + if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT) + || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)) { + vty_out(vty, + "%% error: Please unconfigure vpn to vrf commands before using import vrf commands\n"); + return AFI_MAX; + } + } + return afi; +} + +DEFPY (af_rd_vpn_export, + af_rd_vpn_export_cmd, + "[no] rd vpn export ASN:NN_OR_IP-ADDRESS:NN$rd_str", + NO_STR + "Specify route distinguisher\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n" + "Route Distinguisher (: | :)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct prefix_rd prd; + int ret; + afi_t afi; + int idx = 0; + bool yes = true; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + if (yes) { + ret = str2prefix_rd(rd_str, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + /* + * pre-change: un-export vpn routes (vpn->vrf routes unaffected) + */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + if (yes) { + bgp->vpn_policy[afi].tovpn_rd_pretty = XSTRDUP(MTYPE_BGP_NAME, + rd_str); + bgp->vpn_policy[afi].tovpn_rd = prd; + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET); + } else { + XFREE(MTYPE_BGP_NAME, bgp->vpn_policy[afi].tovpn_rd_pretty); + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_RD_SET); + } + + /* post-change: re-export vpn routes */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + return CMD_SUCCESS; +} + +ALIAS (af_rd_vpn_export, + af_no_rd_vpn_export_cmd, + "no rd vpn export", + NO_STR + "Specify route distinguisher\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n") + +DEFPY(af_label_vpn_export_allocation_mode, + af_label_vpn_export_allocation_mode_cmd, + "[no$no] label vpn export allocation-mode ", + NO_STR + "label value for VRF\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n" + "Label allocation mode\n" + "Allocate one label for all BGP updates of the VRF\n" + "Allocate a label per connected next-hop in the VRF\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + bool old_per_nexthop, new_per_nexthop; + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + old_per_nexthop = !!CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP); + if (no) { + if (old_per_nexthop == false && label_per_nh) + return CMD_ERR_NO_MATCH; + if (old_per_nexthop == true && label_per_vrf) + return CMD_ERR_NO_MATCH; + new_per_nexthop = false; + } else { + if (label_per_nh) + new_per_nexthop = true; + else + new_per_nexthop = false; + } + + /* no change */ + if (old_per_nexthop == new_per_nexthop) + return CMD_SUCCESS; + + /* + * pre-change: un-export vpn routes (vpn->vrf routes unaffected) + */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), + bgp); + + if (new_per_nexthop) + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP); + else + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP); + + /* post-change: re-export vpn routes */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, bgp_get_default(), + bgp); + + hook_call(bgp_snmp_update_last_changed, bgp); + return CMD_SUCCESS; +} + +DEFPY (af_label_vpn_export, + af_label_vpn_export_cmd, + "[no] label vpn export <(0-1048575)$label_val|auto$label_auto>", + NO_STR + "label value for VRF\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n" + "Label Value <0-1048575>\n" + "Automatically assign a label\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + mpls_label_t label = (mpls_label_t)label_val; + afi_t afi; + int idx = 0; + bool yes = true; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + if (yes) { + if (label_auto && CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) + /* no change */ + return CMD_SUCCESS; + if (!label_auto && label == bgp->vpn_policy[afi].tovpn_label) + /* no change */ + return CMD_SUCCESS; + } else { + if (label_auto && !CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) + /* no match */ + return CMD_WARNING_CONFIG_FAILED; + if (!label_auto && label_val && + label != bgp->vpn_policy[afi].tovpn_label) + /* no change */ + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * pre-change: un-export vpn routes (vpn->vrf routes unaffected) + */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG)) { + bgp_zebra_release_label_range(bgp->vpn_policy[afi].tovpn_label, + bgp->vpn_policy[afi].tovpn_label); + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG); + + } else if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { + /* release any previous auto label */ + if (bgp->vpn_policy[afi].tovpn_label != MPLS_LABEL_NONE) { + + /* + * label has previously been automatically + * assigned by labelpool: release it + * + * NB if tovpn_label == MPLS_LABEL_NONE it + * means the automatic assignment is in flight + * and therefore the labelpool callback must + * detect that the auto label is not needed. + */ + + bgp_lp_release(LP_TYPE_VRF, + &bgp->vpn_policy[afi], + bgp->vpn_policy[afi].tovpn_label); + } + } + + if (yes) { + if (label_auto) { + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO); + /* fetch a label */ + bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; + } else { + bgp->vpn_policy[afi].tovpn_label = label; + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO); + if (bgp->vpn_policy[afi].tovpn_label >= + MPLS_LABEL_UNRESERVED_MIN && + bgp_zebra_request_label_range(bgp->vpn_policy[afi] + .tovpn_label, + 1, false)) + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG); + } + } else { + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO); + bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; + } + + /* post-change: re-export vpn routes */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + hook_call(bgp_snmp_update_last_changed, bgp); + return CMD_SUCCESS; +} + +DEFPY (af_sid_vpn_export, + af_sid_vpn_export_cmd, + "[no] sid vpn export <(1-1048575)$sid_idx|auto$sid_auto>", + NO_STR + "sid value for VRF\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n" + "Sid allocation index\n" + "Automatically assign a label\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + int debug = 0; + int idx = 0; + bool yes = true; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + if (!yes) { + /* when SID is not set, do nothing */ + if ((bgp->vpn_policy[afi].tovpn_sid_index == 0) && + !CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO)) + return CMD_SUCCESS; + + /* pre-change */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + bgp->vpn_policy[afi].tovpn_sid_index = 0; + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO); + + /* post-change */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + return CMD_SUCCESS; + } + + if (bgp->tovpn_sid_index != 0 || + CHECK_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO)) { + vty_out(vty, + "per-vrf sid and per-af sid are mutually exclusive\n" + "Failed: per-vrf sid is configured. Remove per-vrf sid before configuring per-af sid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* skip when it's already configured */ + if ((sid_idx != 0 && bgp->vpn_policy[afi].tovpn_sid_index != 0) + || (sid_auto && CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO))) + return CMD_SUCCESS; + + /* + * mode change between sid_idx and sid_auto isn't supported. + * user must negate sid vpn export when they want to change the mode + */ + if ((sid_auto && bgp->vpn_policy[afi].tovpn_sid_index != 0) + || (sid_idx != 0 && CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO))) { + vty_out(vty, "it's already configured as %s.\n", + sid_auto ? "auto-mode" : "idx-mode"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* pre-change */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + if (sid_auto) { + /* SID allocation auto-mode */ + if (debug) + zlog_debug("%s: auto sid alloc.", __func__); + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO); + } else if (sid_idx != 0) { + /* SID allocation index-mode */ + if (debug) + zlog_debug("%s: idx %ld sid alloc.", __func__, sid_idx); + bgp->vpn_policy[afi].tovpn_sid_index = sid_idx; + } + + /* post-change */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + return CMD_SUCCESS; +} + +DEFPY (bgp_sid_vpn_export, + bgp_sid_vpn_export_cmd, + "[no] sid vpn per-vrf export <(1-1048575)$sid_idx|auto$sid_auto>", + NO_STR + "sid value for VRF\n" + "Between current vrf and vpn\n" + "sid per-VRF (both IPv4 and IPv6 address families)\n" + "For routes leaked from current vrf to vpn\n" + "Sid allocation index\n" + "Automatically assign a label\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int debug; + + debug = (BGP_DEBUG(vpn, VPN_LEAK_TO_VRF) | + BGP_DEBUG(vpn, VPN_LEAK_FROM_VRF)); + + if (no) { + /* when per-VRF SID is not set, do nothing */ + if (bgp->tovpn_sid_index == 0 && + !CHECK_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO)) + return CMD_SUCCESS; + + sid_idx = 0; + sid_auto = false; + bgp->tovpn_sid_index = 0; + UNSET_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO); + } + + if (bgp->vpn_policy[AFI_IP].tovpn_sid_index != 0 || + CHECK_FLAG(bgp->vpn_policy[AFI_IP].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO) || + bgp->vpn_policy[AFI_IP6].tovpn_sid_index != 0 || + CHECK_FLAG(bgp->vpn_policy[AFI_IP6].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO)) { + vty_out(vty, + "per-vrf sid and per-af sid are mutually exclusive\n" + "Failed: per-af sid is configured. Remove per-af sid before configuring per-vrf sid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* skip when it's already configured */ + if ((sid_idx != 0 && bgp->tovpn_sid_index != 0) || + (sid_auto && CHECK_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO))) + return CMD_SUCCESS; + + /* + * mode change between sid_idx and sid_auto isn't supported. + * user must negate sid vpn export when they want to change the mode + */ + if ((sid_auto && bgp->tovpn_sid_index != 0) || + (sid_idx != 0 && + CHECK_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO))) { + vty_out(vty, "it's already configured as %s.\n", + sid_auto ? "auto-mode" : "idx-mode"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* pre-change */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_get_default(), + bgp); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, bgp_get_default(), + bgp); + + if (sid_auto) { + /* SID allocation auto-mode */ + if (debug) + zlog_debug("%s: auto per-vrf sid alloc.", __func__); + SET_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO); + } else if (sid_idx != 0) { + /* SID allocation index-mode */ + if (debug) + zlog_debug("%s: idx %ld per-vrf sid alloc.", __func__, + sid_idx); + bgp->tovpn_sid_index = sid_idx; + } + + /* post-change */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_get_default(), + bgp); + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, + bgp_get_default(), bgp); + + return CMD_SUCCESS; +} + +ALIAS (af_label_vpn_export, + af_no_label_vpn_export_cmd, + "no label vpn export", + NO_STR + "label value for VRF\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n") + +ALIAS (bgp_sid_vpn_export, + no_bgp_sid_vpn_export_cmd, + "no$no sid vpn per-vrf export", + NO_STR + "sid value for VRF\n" + "Between current vrf and vpn\n" + "sid per-VRF (both IPv4 and IPv6 address families)\n" + "For routes leaked from current vrf to vpn\n") + +DEFPY (af_nexthop_vpn_export, + af_nexthop_vpn_export_cmd, + "[no] nexthop vpn export [$nexthop_su]", + NO_STR + "Specify next hop to use for VRF advertised prefixes\n" + "Between current address-family and vpn\n" + "For routes leaked from current address-family to vpn\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + struct prefix p; + + if (!no) { + if (!nexthop_su) { + vty_out(vty, "%% Nexthop required\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (!sockunion2hostprefix(nexthop_su, &p)) + return CMD_WARNING_CONFIG_FAILED; + } + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + /* + * pre-change: un-export vpn routes (vpn->vrf routes unaffected) + */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + if (!no) { + bgp->vpn_policy[afi].tovpn_nexthop = p; + SET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_NEXTHOP_SET); + } else { + UNSET_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_NEXTHOP_SET); + } + + /* post-change: re-export vpn routes */ + vpn_leak_postchange(BGP_VPN_POLICY_DIR_TOVPN, afi, + bgp_get_default(), bgp); + + return CMD_SUCCESS; +} + +static int vpn_policy_getdirs(struct vty *vty, const char *dstr, int *dodir) +{ + if (!strcmp(dstr, "import")) { + dodir[BGP_VPN_POLICY_DIR_FROMVPN] = 1; + } else if (!strcmp(dstr, "export")) { + dodir[BGP_VPN_POLICY_DIR_TOVPN] = 1; + } else if (!strcmp(dstr, "both")) { + dodir[BGP_VPN_POLICY_DIR_FROMVPN] = 1; + dodir[BGP_VPN_POLICY_DIR_TOVPN] = 1; + } else { + vty_out(vty, "%% direction parse error\n"); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +DEFPY (af_rt_vpn_imexport, + af_rt_vpn_imexport_cmd, + "[no] vpn $direction_str RTLIST...", + NO_STR + "Specify route target list\n" + "Specify route target list\n" + "Between current address-family and vpn\n" + "For routes leaked from vpn to current address-family: match any\n" + "For routes leaked from current address-family to vpn: set\n" + "both import: match any and export: set\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct ecommunity *ecom = NULL; + int dodir[BGP_VPN_POLICY_DIR_MAX] = {0}; + enum vpn_policy_direction dir; + afi_t afi; + int idx = 0; + bool yes = true; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + ret = vpn_policy_getdirs(vty, direction_str, dodir); + if (ret != CMD_SUCCESS) + return ret; + + if (yes) { + if (!argv_find(argv, argc, "RTLIST", &idx)) { + vty_out(vty, "%% Missing RTLIST\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ret = set_ecom_list(vty, argc - idx, argv + idx, &ecom, false); + if (ret != CMD_SUCCESS) { + return ret; + } + } + + for (dir = 0; dir < BGP_VPN_POLICY_DIR_MAX; ++dir) { + if (!dodir[dir]) + continue; + + vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); + + if (yes) { + if (bgp->vpn_policy[afi].rtlist[dir]) + ecommunity_free( + &bgp->vpn_policy[afi].rtlist[dir]); + bgp->vpn_policy[afi].rtlist[dir] = + ecommunity_dup(ecom); + } else { + if (bgp->vpn_policy[afi].rtlist[dir]) + ecommunity_free( + &bgp->vpn_policy[afi].rtlist[dir]); + bgp->vpn_policy[afi].rtlist[dir] = NULL; + } + + vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); + } + + if (ecom) + ecommunity_free(&ecom); + + return CMD_SUCCESS; +} + +ALIAS (af_rt_vpn_imexport, + af_no_rt_vpn_imexport_cmd, + "no vpn $direction_str", + NO_STR + "Specify route target list\n" + "Specify route target list\n" + "Between current address-family and vpn\n" + "For routes leaked from vpn to current address-family\n" + "For routes leaked from current address-family to vpn\n" + "both import and export\n") + +DEFPY (af_route_map_vpn_imexport, + af_route_map_vpn_imexport_cmd, +/* future: "route-map RMAP" */ + "[no] route-map vpn $direction_str RMAP$rmap_str", + NO_STR + "Specify route map\n" + "Between current address-family and vpn\n" + "For routes leaked from vpn to current address-family\n" + "For routes leaked from current address-family to vpn\n" + "name of route-map\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + int dodir[BGP_VPN_POLICY_DIR_MAX] = {0}; + enum vpn_policy_direction dir; + afi_t afi; + int idx = 0; + bool yes = true; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + ret = vpn_policy_getdirs(vty, direction_str, dodir); + if (ret != CMD_SUCCESS) + return ret; + + for (dir = 0; dir < BGP_VPN_POLICY_DIR_MAX; ++dir) { + if (!dodir[dir]) + continue; + + vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); + + if (yes) { + if (bgp->vpn_policy[afi].rmap_name[dir]) + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp->vpn_policy[afi].rmap_name[dir]); + bgp->vpn_policy[afi].rmap_name[dir] = XSTRDUP( + MTYPE_ROUTE_MAP_NAME, rmap_str); + bgp->vpn_policy[afi].rmap[dir] = + route_map_lookup_warn_noexist(vty, rmap_str); + if (!bgp->vpn_policy[afi].rmap[dir]) + return CMD_SUCCESS; + } else { + if (bgp->vpn_policy[afi].rmap_name[dir]) + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp->vpn_policy[afi].rmap_name[dir]); + bgp->vpn_policy[afi].rmap_name[dir] = NULL; + bgp->vpn_policy[afi].rmap[dir] = NULL; + } + + vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); + } + + return CMD_SUCCESS; +} + +ALIAS (af_route_map_vpn_imexport, + af_no_route_map_vpn_imexport_cmd, + "no route-map vpn $direction_str", + NO_STR + "Specify route map\n" + "Between current address-family and vpn\n" + "For routes leaked from vpn to current address-family\n" + "For routes leaked from current address-family to vpn\n") + +DEFPY(af_import_vrf_route_map, af_import_vrf_route_map_cmd, + "import vrf route-map RMAP$rmap_str", + "Import routes from another VRF\n" + "Vrf routes being filtered\n" + "Specify route map\n" + "name of route-map\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + enum vpn_policy_direction dir = BGP_VPN_POLICY_DIR_FROMVPN; + afi_t afi; + struct bgp *bgp_default; + + afi = vpn_policy_getafi(vty, bgp, true); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + bgp_default = bgp_get_default(); + if (!bgp_default) { + int32_t ret; + as_t as = bgp->as; + + /* Auto-create assuming the same AS */ + ret = bgp_get_vty(&bgp_default, &as, NULL, + BGP_INSTANCE_TYPE_DEFAULT, NULL, + ASNOTATION_UNDEFINED); + + if (ret) { + vty_out(vty, + "VRF default is not configured as a bgp instance\n"); + return CMD_WARNING; + } + } + + vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); + + if (bgp->vpn_policy[afi].rmap_name[dir]) + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp->vpn_policy[afi].rmap_name[dir]); + bgp->vpn_policy[afi].rmap_name[dir] = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_str); + bgp->vpn_policy[afi].rmap[dir] = + route_map_lookup_warn_noexist(vty, rmap_str); + if (!bgp->vpn_policy[afi].rmap[dir]) + return CMD_SUCCESS; + + SET_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT); + + vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); + + return CMD_SUCCESS; +} + +DEFPY(af_no_import_vrf_route_map, af_no_import_vrf_route_map_cmd, + "no import vrf route-map [RMAP$rmap_str]", + NO_STR + "Import routes from another VRF\n" + "Vrf routes being filtered\n" + "Specify route map\n" + "name of route-map\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + enum vpn_policy_direction dir = BGP_VPN_POLICY_DIR_FROMVPN; + afi_t afi; + + afi = vpn_policy_getafi(vty, bgp, true); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + vpn_leak_prechange(dir, afi, bgp_get_default(), bgp); + + if (bgp->vpn_policy[afi].rmap_name[dir]) + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp->vpn_policy[afi].rmap_name[dir]); + bgp->vpn_policy[afi].rmap_name[dir] = NULL; + bgp->vpn_policy[afi].rmap[dir] = NULL; + + if (bgp->vpn_policy[afi].import_vrf->count == 0) + UNSET_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT); + + vpn_leak_postchange(dir, afi, bgp_get_default(), bgp); + + return CMD_SUCCESS; +} + +DEFPY(bgp_imexport_vrf, bgp_imexport_vrf_cmd, + "[no] import vrf VIEWVRFNAME$import_name", + NO_STR + "Import routes from another VRF\n" + "VRF to import from\n" + "The name of the VRF\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node; + struct bgp *vrf_bgp, *bgp_default; + int32_t ret = 0; + as_t as = bgp->as; + bool remove = false; + int32_t idx = 0; + char *vname; + enum bgp_instance_type bgp_type = BGP_INSTANCE_TYPE_VRF; + safi_t safi; + afi_t afi; + + if (import_name == NULL) { + vty_out(vty, "%% Missing import name\n"); + return CMD_WARNING; + } + + if (strcmp(import_name, "route-map") == 0) { + vty_out(vty, "%% Must include route-map name\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "no", &idx)) + remove = true; + + afi = vpn_policy_getafi(vty, bgp, true); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + safi = bgp_node_safi(vty); + + if (((BGP_INSTANCE_TYPE_DEFAULT == bgp->inst_type) + && (strcmp(import_name, VRF_DEFAULT_NAME) == 0)) + || (bgp->name && (strcmp(import_name, bgp->name) == 0))) { + vty_out(vty, "%% Cannot %s vrf %s into itself\n", + remove ? "unimport" : "import", import_name); + return CMD_WARNING; + } + + bgp_default = bgp_get_default(); + if (!bgp_default) { + /* Auto-create assuming the same AS */ + ret = bgp_get_vty(&bgp_default, &as, NULL, + BGP_INSTANCE_TYPE_DEFAULT, NULL, + ASNOTATION_UNDEFINED); + + if (ret) { + vty_out(vty, + "VRF default is not configured as a bgp instance\n"); + return CMD_WARNING; + } + } + + vrf_bgp = bgp_lookup_by_name(import_name); + if (!vrf_bgp) { + if (strcmp(import_name, VRF_DEFAULT_NAME) == 0) { + vrf_bgp = bgp_default; + } else { + /* Auto-create assuming the same AS */ + ret = bgp_get_vty(&vrf_bgp, &as, import_name, bgp_type, + NULL, ASNOTATION_UNDEFINED); + + /* Auto created VRF instances should be marked + * properly, otherwise we have a state after bgpd + * restart where VRF instance has default VRF's ASN. + */ + SET_FLAG(vrf_bgp->vrf_flags, BGP_VRF_AUTO); + } + + if (ret) { + vty_out(vty, + "VRF %s is not configured as a bgp instance\n", + import_name); + return CMD_WARNING; + } + } + + if (remove) { + vrf_unimport_from_vrf(bgp, vrf_bgp, afi, safi); + } else { + /* Already importing from "import_vrf"? */ + for (ALL_LIST_ELEMENTS_RO(bgp->vpn_policy[afi].import_vrf, node, + vname)) { + if (strcmp(vname, import_name) == 0) + return CMD_WARNING; + } + + vrf_import_from_vrf(bgp, vrf_bgp, afi, safi); + } + + return CMD_SUCCESS; +} + +/* This command is valid only in a bgp vrf instance or the default instance */ +DEFPY (bgp_imexport_vpn, + bgp_imexport_vpn_cmd, + "[no] $direction_str vpn", + NO_STR + "Import routes to this address-family\n" + "Export routes from this address-family\n" + "to/from default instance VPN RIB\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int previous_state; + afi_t afi; + safi_t safi; + int idx = 0; + bool yes = true; + int flag; + enum vpn_policy_direction dir; + struct bgp *bgp_default = bgp_get_default(); + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + if (BGP_INSTANCE_TYPE_VRF != bgp->inst_type && + BGP_INSTANCE_TYPE_DEFAULT != bgp->inst_type) { + + vty_out(vty, "%% import|export vpn valid only for bgp vrf or default instance\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + afi = bgp_node_afi(vty); + safi = bgp_node_safi(vty); + if ((SAFI_UNICAST != safi) || ((AFI_IP != afi) && (AFI_IP6 != afi))) { + vty_out(vty, "%% import|export vpn valid only for unicast ipv4|ipv6\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!strcmp(direction_str, "import")) { + flag = BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT; + dir = BGP_VPN_POLICY_DIR_FROMVPN; + } else if (!strcmp(direction_str, "export")) { + flag = BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT; + dir = BGP_VPN_POLICY_DIR_TOVPN; + } else { + vty_out(vty, "%% unknown direction %s\n", direction_str); + return CMD_WARNING_CONFIG_FAILED; + } + + previous_state = CHECK_FLAG(bgp->af_flags[afi][safi], flag); + + if (yes) { + SET_FLAG(bgp->af_flags[afi][safi], flag); + if (!previous_state) { + /* trigger export current vrf */ + vpn_leak_postchange(dir, afi, bgp_default, bgp); + } + } else { + if (previous_state) { + /* trigger un-export current vrf */ + vpn_leak_prechange(dir, afi, bgp_default, bgp); + } + UNSET_FLAG(bgp->af_flags[afi][safi], flag); + if (previous_state && bgp_default && + !CHECK_FLAG(bgp_default->af_flags[afi][SAFI_MPLS_VPN], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL)) + vpn_leak_no_retain(bgp, bgp_default, afi); + } + + hook_call(bgp_snmp_init_stats, bgp); + + return CMD_SUCCESS; +} + +DEFPY (af_routetarget_import, + af_routetarget_import_cmd, + "[no] redirect import RTLIST...", + NO_STR + "Specify route target list\n" + "Specify route target list\n" + "Specify route target list\n" + "Specify route target list\n" + "Flow-spec redirect type route target\n" + "Import routes to this address-family\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN|IPV6:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct ecommunity *ecom = NULL; + afi_t afi; + int idx = 0, idx_unused = 0; + bool yes = true; + bool rt6 = false; + + if (argv_find(argv, argc, "no", &idx)) + yes = false; + + if (argv_find(argv, argc, "rt6", &idx_unused) || + argv_find(argv, argc, "route-target6", &idx_unused)) + rt6 = true; + + afi = vpn_policy_getafi(vty, bgp, false); + if (afi == AFI_MAX) + return CMD_WARNING_CONFIG_FAILED; + + if (rt6 && afi != AFI_IP6) + return CMD_WARNING_CONFIG_FAILED; + + if (yes) { + if (!argv_find(argv, argc, "RTLIST", &idx)) { + vty_out(vty, "%% Missing RTLIST\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ret = set_ecom_list(vty, argc - idx, argv + idx, &ecom, rt6); + if (ret != CMD_SUCCESS) + return ret; + } + + if (yes) { + if (bgp->vpn_policy[afi].import_redirect_rtlist) + ecommunity_free(&bgp->vpn_policy[afi] + .import_redirect_rtlist); + bgp->vpn_policy[afi].import_redirect_rtlist = + ecommunity_dup(ecom); + } else { + if (bgp->vpn_policy[afi].import_redirect_rtlist) + ecommunity_free(&bgp->vpn_policy[afi] + .import_redirect_rtlist); + bgp->vpn_policy[afi].import_redirect_rtlist = NULL; + } + + if (ecom) + ecommunity_free(&ecom); + + return CMD_SUCCESS; +} + +DEFUN_NOSH (address_family_ipv4_safi, + address_family_ipv4_safi_cmd, + "address-family ipv4 []", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_SAFI_WITH_LABEL_HELP_STR) +{ + + if (argc == 3) { + VTY_DECLVAR_CONTEXT(bgp, bgp); + safi_t safi = bgp_vty_safi_from_str(argv[2]->text); + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT + && safi != SAFI_UNICAST && safi != SAFI_MULTICAST + && safi != SAFI_EVPN) { + vty_out(vty, + "Only Unicast/Multicast/EVPN SAFIs supported in non-core instances.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + vty->node = bgp_node_type(AFI_IP, safi); + } else + vty->node = BGP_IPV4_NODE; + + return CMD_SUCCESS; +} + +DEFUN_NOSH (address_family_ipv6_safi, + address_family_ipv6_safi_cmd, + "address-family ipv6 []", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_SAFI_WITH_LABEL_HELP_STR) +{ + if (argc == 3) { + VTY_DECLVAR_CONTEXT(bgp, bgp); + safi_t safi = bgp_vty_safi_from_str(argv[2]->text); + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT + && safi != SAFI_UNICAST && safi != SAFI_MULTICAST + && safi != SAFI_EVPN) { + vty_out(vty, + "Only Unicast/Multicast/EVPN SAFIs supported in non-core instances.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + vty->node = bgp_node_type(AFI_IP6, safi); + } else + vty->node = BGP_IPV6_NODE; + + return CMD_SUCCESS; +} + +#ifdef KEEP_OLD_VPN_COMMANDS +DEFUN_NOSH (address_family_vpnv4, + address_family_vpnv4_cmd, + "address-family vpnv4 [unicast]", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_AF_MODIFIER_STR) +{ + vty->node = BGP_VPNV4_NODE; + return CMD_SUCCESS; +} + +DEFUN_NOSH (address_family_vpnv6, + address_family_vpnv6_cmd, + "address-family vpnv6 [unicast]", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_AF_MODIFIER_STR) +{ + vty->node = BGP_VPNV6_NODE; + return CMD_SUCCESS; +} +#endif /* KEEP_OLD_VPN_COMMANDS */ + +DEFUN_NOSH (address_family_evpn, + address_family_evpn_cmd, + "address-family l2vpn evpn", + "Enter Address Family command mode\n" + BGP_AF_STR + BGP_AF_MODIFIER_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + vty->node = BGP_EVPN_NODE; + return CMD_SUCCESS; +} + +DEFUN_NOSH (bgp_segment_routing_srv6, + bgp_segment_routing_srv6_cmd, + "segment-routing srv6", + "Segment-Routing configuration\n" + "Segment-Routing SRv6 configuration\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->srv6_enabled = true; + vty->node = BGP_SRV6_NODE; + return CMD_SUCCESS; +} + +DEFUN (no_bgp_segment_routing_srv6, + no_bgp_segment_routing_srv6_cmd, + "no segment-routing srv6", + NO_STR + "Segment-Routing configuration\n" + "Segment-Routing SRv6 configuration\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (strlen(bgp->srv6_locator_name) > 0) + if (bgp_srv6_locator_unset(bgp) < 0) + return CMD_WARNING_CONFIG_FAILED; + + bgp->srv6_enabled = false; + return CMD_SUCCESS; +} + +DEFPY (bgp_srv6_locator, + bgp_srv6_locator_cmd, + "locator NAME$name", + "Specify SRv6 locator\n" + "Specify SRv6 locator\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + + if (strlen(bgp->srv6_locator_name) > 0 + && strcmp(name, bgp->srv6_locator_name) != 0) { + vty_out(vty, "srv6 locator is already configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + snprintf(bgp->srv6_locator_name, + sizeof(bgp->srv6_locator_name), "%s", name); + + ret = bgp_zebra_srv6_manager_get_locator_chunk(name); + if (ret < 0) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_srv6_locator, + no_bgp_srv6_locator_cmd, + "no locator NAME$name", + NO_STR + "Specify SRv6 locator\n" + "Specify SRv6 locator\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* when locator isn't configured, do nothing */ + if (strlen(bgp->srv6_locator_name) < 1) + return CMD_SUCCESS; + + /* name validation */ + if (strcmp(name, bgp->srv6_locator_name) != 0) { + vty_out(vty, "%% No srv6 locator is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* unset locator */ + if (bgp_srv6_locator_unset(bgp) < 0) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +DEFPY (show_bgp_srv6, + show_bgp_srv6_cmd, + "show bgp segment-routing srv6", + SHOW_STR + BGP_STR + "BGP Segment Routing\n" + "BGP Segment Routing SRv6\n") +{ + struct bgp *bgp; + struct listnode *node; + struct srv6_locator_chunk *chunk; + struct bgp_srv6_function *func; + + bgp = bgp_get_default(); + if (!bgp) + return CMD_SUCCESS; + + vty_out(vty, "locator_name: %s\n", bgp->srv6_locator_name); + vty_out(vty, "locator_chunks:\n"); + for (ALL_LIST_ELEMENTS_RO(bgp->srv6_locator_chunks, node, chunk)) { + vty_out(vty, "- %pFX\n", &chunk->prefix); + vty_out(vty, " block-length: %d\n", chunk->block_bits_length); + vty_out(vty, " node-length: %d\n", chunk->node_bits_length); + vty_out(vty, " func-length: %d\n", + chunk->function_bits_length); + vty_out(vty, " arg-length: %d\n", chunk->argument_bits_length); + } + + vty_out(vty, "functions:\n"); + for (ALL_LIST_ELEMENTS_RO(bgp->srv6_functions, node, func)) { + vty_out(vty, "- sid: %pI6\n", &func->sid); + vty_out(vty, " locator: %s\n", func->locator_name); + } + + vty_out(vty, "bgps:\n"); + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + vty_out(vty, "- name: %s\n", + bgp->name ? bgp->name : "default"); + + vty_out(vty, " vpn_policy[AFI_IP].tovpn_sid: %pI6\n", + bgp->vpn_policy[AFI_IP].tovpn_sid); + vty_out(vty, " vpn_policy[AFI_IP6].tovpn_sid: %pI6\n", + bgp->vpn_policy[AFI_IP6].tovpn_sid); + vty_out(vty, " per-vrf tovpn_sid: %pI6\n", bgp->tovpn_sid); + } + + return CMD_SUCCESS; +} + +DEFUN_NOSH (exit_address_family, + exit_address_family_cmd, + "exit-address-family", + "Exit from Address Family configuration mode\n") +{ + if (vty->node == BGP_IPV4_NODE || vty->node == BGP_IPV4M_NODE + || vty->node == BGP_IPV4L_NODE || vty->node == BGP_VPNV4_NODE + || vty->node == BGP_IPV6_NODE || vty->node == BGP_IPV6M_NODE + || vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE + || vty->node == BGP_EVPN_NODE + || vty->node == BGP_FLOWSPECV4_NODE + || vty->node == BGP_FLOWSPECV6_NODE) + vty->node = BGP_NODE; + return CMD_SUCCESS; +} + +/* Recalculate bestpath and re-advertise a prefix */ +static int bgp_clear_prefix(struct vty *vty, const char *view_name, + const char *ip_str, afi_t afi, safi_t safi, + struct prefix_rd *prd) +{ + int ret; + struct prefix match; + struct bgp_dest *dest; + struct bgp_dest *rm; + struct bgp *bgp; + struct bgp_table *table; + struct bgp_table *rib; + + /* BGP structure lookup. */ + if (view_name) { + bgp = bgp_lookup_by_name(view_name); + if (bgp == NULL) { + vty_out(vty, "%% Can't find BGP instance %s\n", + view_name); + return CMD_WARNING; + } + } else { + bgp = bgp_get_default(); + if (bgp == NULL) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING; + } + } + + /* Check IP address argument. */ + ret = str2prefix(ip_str, &match); + if (!ret) { + vty_out(vty, "%% address is malformed\n"); + return CMD_WARNING; + } + + match.family = afi2family(afi); + rib = bgp->rib[afi][safi]; + + if (safi == SAFI_MPLS_VPN) { + for (dest = bgp_table_top(rib); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (prd && memcmp(dest_p->u.val, prd->val, 8) != 0) + continue; + + table = bgp_dest_get_bgp_table_info(dest); + if (table == NULL) + continue; + + rm = bgp_node_match(table, &match); + if (rm != NULL) { + const struct prefix *rm_p = + bgp_dest_get_prefix(rm); + + if (rm_p->prefixlen == match.prefixlen) { + SET_FLAG(rm->flags, + BGP_NODE_USER_CLEAR); + bgp_process(bgp, rm, + bgp_dest_get_bgp_path_info( + rm), + afi, safi); + } + bgp_dest_unlock_node(rm); + } + } + } else { + dest = bgp_node_match(rib, &match); + if (dest != NULL) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (dest_p->prefixlen == match.prefixlen) { + SET_FLAG(dest->flags, BGP_NODE_USER_CLEAR); + bgp_process(bgp, dest, + bgp_dest_get_bgp_path_info(dest), + afi, safi); + } + bgp_dest_unlock_node(dest); + } + } + + return CMD_SUCCESS; +} + +/* one clear bgp command to rule them all */ +DEFUN (clear_ip_bgp_all, + clear_ip_bgp_all_cmd, + "clear [ip] bgp [ VIEWVRFNAME] [ []] <*|A.B.C.D$neighbor|X:X::X:X$neighbor|WORD$neighbor|ASNUM|external|peer-group PGNAME> []|in [prefix-filter]|out|message-stats|capabilities>]", + CLEAR_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_AF_STR + BGP_SAFI_WITH_LABEL_HELP_STR + BGP_AF_MODIFIER_STR + "Clear all peers\n" + "BGP IPv4 neighbor to clear\n" + "BGP IPv6 neighbor to clear\n" + "BGP neighbor on interface to clear\n" + "Clear peers with the AS number in plain or dotted format\n" + "Clear all external peers\n" + "Clear all members of peer-group\n" + "BGP peer-group name\n" + BGP_SOFT_STR + BGP_SOFT_IN_STR + BGP_SOFT_OUT_STR + BGP_SOFT_IN_STR + "Push out prefix-list ORF and do inbound soft reconfig\n" + BGP_SOFT_OUT_STR + "Reset message statistics\n" + "Resend capabilities\n") +{ + char *vrf = NULL; + + afi_t afi = AFI_UNSPEC; + safi_t safi = SAFI_UNSPEC; + enum clear_sort clr_sort = clear_peer; + enum bgp_clear_type clr_type; + char *clr_arg = NULL; + + int idx = 0; + + /* clear [ip] bgp */ + if (argv_find(argv, argc, "ip", &idx)) + afi = AFI_IP; + + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + idx += 2; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } else if (argv_find(argv, argc, "view", &idx)) { + /* [ VIEWVRFNAME] */ + vrf = argv[idx + 1]->arg; + idx += 2; + } + /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) + argv_find_and_parse_safi(argv, argc, &idx, &safi); + + /* <*|A.B.C.D|X:X::X:X|WORD|ASNUM|external|peer-group PGNAME> */ + if (argv_find(argv, argc, "*", &idx)) { + clr_sort = clear_all; + } else if (argv_find(argv, argc, "A.B.C.D", &idx)) { + clr_sort = clear_peer; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "X:X::X:X", &idx)) { + clr_sort = clear_peer; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "peer-group", &idx)) { + clr_sort = clear_group; + idx++; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "PGNAME", &idx)) { + clr_sort = clear_peer; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "WORD", &idx)) { + clr_sort = clear_peer; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "ASNUM", &idx)) { + clr_sort = clear_as; + clr_arg = argv[idx]->arg; + } else if (argv_find(argv, argc, "external", &idx)) { + clr_sort = clear_external; + } + + /* []|in [prefix-filter]|out|message-stats|capabilities>] */ + if (argv_find(argv, argc, "soft", &idx)) { + if (argv_find(argv, argc, "in", &idx) + || argv_find(argv, argc, "out", &idx)) + clr_type = strmatch(argv[idx]->text, "in") + ? BGP_CLEAR_SOFT_IN + : BGP_CLEAR_SOFT_OUT; + else + clr_type = BGP_CLEAR_SOFT_BOTH; + } else if (argv_find(argv, argc, "in", &idx)) { + clr_type = argv_find(argv, argc, "prefix-filter", &idx) + ? BGP_CLEAR_SOFT_IN_ORF_PREFIX + : BGP_CLEAR_SOFT_IN; + } else if (argv_find(argv, argc, "out", &idx)) { + clr_type = BGP_CLEAR_SOFT_OUT; + } else if (argv_find(argv, argc, "message-stats", &idx)) { + clr_type = BGP_CLEAR_MESSAGE_STATS; + } else if (argv_find(argv, argc, "capabilities", &idx)) { + clr_type = BGP_CLEAR_CAPABILITIES; + } else + clr_type = BGP_CLEAR_SOFT_NONE; + + return bgp_clear_vty(vty, vrf, afi, safi, clr_sort, clr_type, clr_arg); +} + +DEFUN (clear_ip_bgp_prefix, + clear_ip_bgp_prefix_cmd, + "clear [ip] bgp [ VIEWVRFNAME] prefix A.B.C.D/M", + CLEAR_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + "Clear bestpath and re-advertise\n" + "IPv4 prefix\n") +{ + char *vrf = NULL; + char *prefix = NULL; + + int idx = 0; + + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + idx += 2; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } else if (argv_find(argv, argc, "view", &idx)) { + /* [ VIEWVRFNAME] */ + vrf = argv[idx + 1]->arg; + idx += 2; + } + + prefix = argv[argc - 1]->arg; + + return bgp_clear_prefix(vty, vrf, prefix, AFI_IP, SAFI_UNICAST, NULL); +} + +DEFUN (clear_bgp_ipv6_safi_prefix, + clear_bgp_ipv6_safi_prefix_cmd, + "clear [ip] bgp ipv6 "BGP_SAFI_CMD_STR" prefix X:X::X:X/M", + CLEAR_STR + IP_STR + BGP_STR + BGP_AF_STR + BGP_SAFI_HELP_STR + "Clear bestpath and re-advertise\n" + "IPv6 prefix\n") +{ + int idx_safi = 0; + int idx_ipv6_prefix = 0; + safi_t safi = SAFI_UNICAST; + char *prefix = argv_find(argv, argc, "X:X::X:X/M", &idx_ipv6_prefix) ? + argv[idx_ipv6_prefix]->arg : NULL; + + argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); + return bgp_clear_prefix( + vty, NULL, prefix, AFI_IP6, + safi, NULL); +} + +DEFUN (clear_bgp_instance_ipv6_safi_prefix, + clear_bgp_instance_ipv6_safi_prefix_cmd, + "clear [ip] bgp VIEWVRFNAME ipv6 "BGP_SAFI_CMD_STR" prefix X:X::X:X/M", + CLEAR_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AF_STR + BGP_SAFI_HELP_STR + "Clear bestpath and re-advertise\n" + "IPv6 prefix\n") +{ + int idx_safi = 0; + int idx_vrfview = 0; + int idx_ipv6_prefix = 0; + safi_t safi = SAFI_UNICAST; + char *prefix = argv_find(argv, argc, "X:X::X:X/M", &idx_ipv6_prefix) ? + argv[idx_ipv6_prefix]->arg : NULL; + char *vrfview = NULL; + + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx_vrfview)) { + vrfview = argv[idx_vrfview + 1]->arg; + if (vrfview && strmatch(vrfview, VRF_DEFAULT_NAME)) + vrfview = NULL; + } else if (argv_find(argv, argc, "view", &idx_vrfview)) { + /* [ VIEWVRFNAME] */ + vrfview = argv[idx_vrfview + 1]->arg; + } + argv_find_and_parse_safi(argv, argc, &idx_safi, &safi); + + return bgp_clear_prefix( + vty, vrfview, prefix, + AFI_IP6, safi, NULL); +} + +DEFUN (show_bgp_views, + show_bgp_views_cmd, + "show [ip] bgp views", + SHOW_STR + IP_STR + BGP_STR + "Show the defined BGP views\n") +{ + struct list *inst = bm->bgp; + struct listnode *node; + struct bgp *bgp; + + vty_out(vty, "Defined BGP views:\n"); + for (ALL_LIST_ELEMENTS_RO(inst, node, bgp)) { + /* Skip VRFs. */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + continue; + vty_out(vty, "\t%s (AS%s)\n", bgp->name ? bgp->name : "(null)", + bgp->as_pretty); + } + + return CMD_SUCCESS; +} + +static inline void calc_peers_cfgd_estbd(struct bgp *bgp, int *peers_cfgd, + int *peers_estbd) +{ + struct peer *peer; + struct listnode *node; + + *peers_cfgd = *peers_estbd = 0; + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + (*peers_cfgd)++; + if (peer_established(peer->connection)) + (*peers_estbd)++; + } +} + +static void print_bgp_vrfs(struct bgp *bgp, struct vty *vty, json_object *json, + const char *type) +{ + int peers_cfg, peers_estb; + + calc_peers_cfgd_estbd(bgp, &peers_cfg, &peers_estb); + + if (json) { + int64_t vrf_id_ui = (bgp->vrf_id == VRF_UNKNOWN) + ? -1 + : (int64_t)bgp->vrf_id; + json_object_string_add(json, "type", type); + json_object_int_add(json, "vrfId", vrf_id_ui); + json_object_string_addf(json, "routerId", "%pI4", + &bgp->router_id); + json_object_int_add(json, "numConfiguredPeers", peers_cfg); + json_object_int_add(json, "numEstablishedPeers", peers_estb); + json_object_int_add(json, "l3vni", bgp->l3vni); + json_object_string_addf(json, "rmac", "%pEA", &bgp->rmac); + json_object_string_add( + json, "interface", + ifindex2ifname(bgp->l3vni_svi_ifindex, bgp->vrf_id)); + } +} + +static int show_bgp_vrfs_detail_common(struct vty *vty, struct bgp *bgp, + json_object *json, const char *name, + const char *type, bool use_vrf) +{ + int peers_cfg, peers_estb; + + calc_peers_cfgd_estbd(bgp, &peers_cfg, &peers_estb); + + if (use_vrf) { + if (json) { + print_bgp_vrfs(bgp, vty, json, type); + } else { + vty_out(vty, "BGP instance %s VRF id %d\n", + bgp->name_pretty, + bgp->vrf_id == VRF_UNKNOWN ? -1 + : (int)bgp->vrf_id); + vty_out(vty, "Router Id %pI4\n", &bgp->router_id); + vty_out(vty, + "Num Configured Peers %d, Established %d\n", + peers_cfg, peers_estb); + if (bgp->l3vni) { + vty_out(vty, + "L3VNI %u, L3VNI-SVI %s, Router MAC %pEA\n", + bgp->l3vni, + ifindex2ifname(bgp->l3vni_svi_ifindex, + bgp->vrf_id), + &bgp->rmac); + } + } + } else { + if (json) { + print_bgp_vrfs(bgp, vty, json, type); + } else { + vty_out(vty, "%4s %-5d %-16pI4 %-9u %-10u %-37s\n", + type, + bgp->vrf_id == VRF_UNKNOWN ? -1 + : (int)bgp->vrf_id, + &bgp->router_id, peers_cfg, peers_estb, name); + vty_out(vty, "%11s %-16u %-21pEA %-20s\n", " ", + bgp->l3vni, &bgp->rmac, + ifindex2ifname(bgp->l3vni_svi_ifindex, + bgp->vrf_id)); + } + } + + return CMD_SUCCESS; +} + +DEFPY (show_bgp_vrfs, + show_bgp_vrfs_cmd, + "show [ip] bgp vrfs [] [json]", + SHOW_STR + IP_STR + BGP_STR + "Show BGP VRFs\n" + "Specific VRF name\n" + JSON_STR) +{ + struct list *inst = bm->bgp; + struct listnode *node; + struct bgp *bgp; + bool uj = use_json(argc, argv); + json_object *json = NULL; + json_object *json_vrfs = NULL; + json_object *json_vrf = NULL; + int count = 0; + const char *name = vrf_name; + const char *type; + + if (uj) + json = json_object_new_object(); + + if (name) { + if (strmatch(name, VRF_DEFAULT_NAME)) { + bgp = bgp_get_default(); + type = "DFLT"; + } else { + bgp = bgp_lookup_by_name(name); + type = "VRF"; + } + if (!bgp) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% Specified BGP instance not found\n"); + + return CMD_WARNING; + } + } + + if (vrf_name) { + if (uj) + json_vrf = json_object_new_object(); + + show_bgp_vrfs_detail_common(vty, bgp, json_vrf, name, type, + true); + + if (uj) { + json_object_object_add(json, name, json_vrf); + vty_json(vty, json); + } + + return CMD_SUCCESS; + } + + if (uj) + json_vrfs = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(inst, node, bgp)) { + const char *name; + + /* Skip Views. */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + continue; + + count++; + if (!uj && count == 1) { + vty_out(vty, + "%4s %-5s %-16s %9s %10s %-37s\n", + "Type", "Id", "routerId", "#PeersCfg", + "#PeersEstb", "Name"); + vty_out(vty, "%11s %-16s %-21s %-6s\n", " ", + "L3-VNI", "RouterMAC", "Interface"); + } + if (uj) + json_vrf = json_object_new_object(); + + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + name = VRF_DEFAULT_NAME; + type = "DFLT"; + } else { + name = bgp->name; + type = "VRF"; + } + + show_bgp_vrfs_detail_common(vty, bgp, json_vrf, name, type, + false); + + if (uj) + json_object_object_add(json_vrfs, name, json_vrf); + } + + if (uj) { + json_object_object_add(json, "vrfs", json_vrfs); + json_object_int_add(json, "totalVrfs", count); + vty_json(vty, json); + } else { + if (count) + vty_out(vty, + "\nTotal number of VRFs (including default): %d\n", + count); + } + + return CMD_SUCCESS; +} + +DEFUN (show_bgp_mac_hash, + show_bgp_mac_hash_cmd, + "show bgp mac hash", + SHOW_STR + BGP_STR + "Mac Address\n" + "Mac Address database\n") +{ + bgp_mac_dump_table(vty); + + return CMD_SUCCESS; +} + +static void show_tip_entry(struct hash_bucket *bucket, void *args) +{ + struct vty *vty = (struct vty *)args; + struct tip_addr *tip = (struct tip_addr *)bucket->data; + + vty_out(vty, "addr: %pI4, count: %d\n", &tip->addr, tip->refcnt); +} + +static void bgp_show_martian_nexthops(struct vty *vty, struct bgp *bgp) +{ + vty_out(vty, "self nexthop database:\n"); + bgp_nexthop_show_address_hash(vty, bgp); + + vty_out(vty, "Tunnel-ip database:\n"); + hash_iterate(bgp->tip_hash, + (void (*)(struct hash_bucket *, void *))show_tip_entry, + vty); +} + +DEFUN(show_bgp_martian_nexthop_db, show_bgp_martian_nexthop_db_cmd, + "show bgp [ VIEWVRFNAME] martian next-hop", + SHOW_STR BGP_STR BGP_INSTANCE_HELP_STR + "martian next-hops\n" + "martian next-hop database\n") +{ + struct bgp *bgp = NULL; + int idx = 0; + char *name = NULL; + + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + name = argv[idx + 1]->arg; + if (name && strmatch(name, VRF_DEFAULT_NAME)) + name = NULL; + } else if (argv_find(argv, argc, "view", &idx)) + /* [ VIEWVRFNAME] */ + name = argv[idx + 1]->arg; + if (name) + bgp = bgp_lookup_by_name(name); + else + bgp = bgp_get_default(); + + if (!bgp) { + vty_out(vty, "%% No BGP process is configured\n"); + return CMD_WARNING; + } + bgp_show_martian_nexthops(vty, bgp); + + return CMD_SUCCESS; +} + +DEFUN (show_bgp_memory, + show_bgp_memory_cmd, + "show [ip] bgp memory", + SHOW_STR + IP_STR + BGP_STR + "Global BGP memory statistics\n") +{ + char memstrbuf[MTYPE_MEMSTR_LEN]; + unsigned long count; + + /* RIB related usage stats */ + count = mtype_stats_alloc(MTYPE_BGP_NODE); + vty_out(vty, "%ld RIB nodes, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_dest))); + + count = mtype_stats_alloc(MTYPE_BGP_ROUTE); + vty_out(vty, "%ld BGP routes, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_path_info))); + if ((count = mtype_stats_alloc(MTYPE_BGP_ROUTE_EXTRA))) + vty_out(vty, "%ld BGP route ancillaries, using %s of memory\n", + count, + mtype_memstr( + memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_path_info_extra))); + + count = mtype_stats_alloc(MTYPE_BGP_ROUTE_EXTRA_EVPN); + if (count) + vty_out(vty, "%ld BGP extra info for EVPN, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_path_info_extra_evpn))); + + count = mtype_stats_alloc(MTYPE_BGP_ROUTE_EXTRA_FS); + if (count) + vty_out(vty, + "%ld BGP extra info for flowspec, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_path_info_extra_fs))); + + count = mtype_stats_alloc(MTYPE_BGP_ROUTE_EXTRA_VRFLEAK); + if (count) + vty_out(vty, + "%ld BGP extra info for vrf leaking, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_path_info_extra_vrfleak))); + + if ((count = mtype_stats_alloc(MTYPE_BGP_STATIC))) + vty_out(vty, "%ld Static routes, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_static))); + + if ((count = mtype_stats_alloc(MTYPE_BGP_PACKET))) + vty_out(vty, "%ld Packets, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bpacket))); + + /* Adj-In/Out */ + if ((count = mtype_stats_alloc(MTYPE_BGP_ADJ_IN))) + vty_out(vty, "%ld Adj-In entries, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_adj_in))); + if ((count = mtype_stats_alloc(MTYPE_BGP_ADJ_OUT))) + vty_out(vty, "%ld Adj-Out entries, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_adj_out))); + + if ((count = mtype_stats_alloc(MTYPE_BGP_NEXTHOP_CACHE))) + vty_out(vty, "%ld Nexthop cache entries, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_nexthop_cache))); + + if ((count = mtype_stats_alloc(MTYPE_BGP_DAMP_INFO))) + vty_out(vty, "%ld Dampening entries, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct bgp_damp_info))); + + /* Attributes */ + count = attr_count(); + vty_out(vty, "%ld BGP attributes, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct attr))); + + if ((count = attr_unknown_count())) + vty_out(vty, "%ld unknown attributes\n", count); + + /* AS_PATH attributes */ + count = aspath_count(); + vty_out(vty, "%ld BGP AS-PATH entries, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct aspath))); + + count = mtype_stats_alloc(MTYPE_AS_SEG); + vty_out(vty, "%ld BGP AS-PATH segments, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct assegment))); + + /* Other attributes */ + if ((count = community_count())) + vty_out(vty, "%ld BGP community entries, using %s of memory\n", + count, mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct community))); + if ((count = mtype_stats_alloc(MTYPE_ECOMMUNITY))) + vty_out(vty, + "%ld BGP ext-community entries, using %s of memory\n", + count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct ecommunity))); + if ((count = mtype_stats_alloc(MTYPE_LCOMMUNITY))) + vty_out(vty, + "%ld BGP large-community entries, using %s of memory\n", + count, mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct lcommunity))); + + if ((count = mtype_stats_alloc(MTYPE_CLUSTER))) + vty_out(vty, "%ld Cluster lists, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct cluster_list))); + + /* Peer related usage */ + count = mtype_stats_alloc(MTYPE_BGP_PEER); + vty_out(vty, "%ld peers, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct peer))); + + if ((count = mtype_stats_alloc(MTYPE_PEER_GROUP))) + vty_out(vty, "%ld peer groups, using %s of memory\n", count, + mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(struct peer_group))); + + /* Other */ + if ((count = mtype_stats_alloc(MTYPE_BGP_REGEXP))) + vty_out(vty, "%ld compiled regexes, using %s of memory\n", + count, mtype_memstr(memstrbuf, sizeof(memstrbuf), + count * sizeof(regex_t))); + return CMD_SUCCESS; +} + +static void bgp_show_bestpath_json(struct bgp *bgp, json_object *json) +{ + json_object *bestpath = json_object_new_object(); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE)) + json_object_string_add(bestpath, "asPath", "ignore"); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED)) + json_object_string_add(bestpath, "asPath", "confed"); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MULTIPATH_RELAX_AS_SET)) + json_object_string_add(bestpath, "multiPathRelax", + "as-set"); + else + json_object_string_add(bestpath, "multiPathRelax", + "true"); + } else + json_object_string_add(bestpath, "multiPathRelax", "false"); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + json_object_boolean_true_add(bestpath, "peerTypeRelax"); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID)) + json_object_string_add(bestpath, "compareRouterId", "true"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED) + || CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) { + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED)) + json_object_string_add(bestpath, "med", "confed"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) + json_object_string_add(bestpath, "med", + "missing-as-worst"); + else + json_object_string_add(bestpath, "med", "true"); + } + + json_object_object_add(json, "bestPath", bestpath); +} + +/* Print the error code/subcode for why the peer is down */ +static void bgp_show_peer_reset(struct vty * vty, struct peer *peer, + json_object *json_peer, bool use_json) +{ + const char *code_str; + const char *subcode_str; + + if (use_json) { + if (peer->last_reset == PEER_DOWN_NOTIFY_SEND + || peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) { + char errorcodesubcode_hexstr[5]; + char errorcodesubcode_str[256]; + + code_str = bgp_notify_code_str(peer->notify.code); + subcode_str = bgp_notify_subcode_str( + peer->notify.code, + peer->notify.subcode); + + snprintf(errorcodesubcode_hexstr, + sizeof(errorcodesubcode_hexstr), "%02X%02X", + peer->notify.code, peer->notify.subcode); + json_object_string_add(json_peer, + "lastErrorCodeSubcode", + errorcodesubcode_hexstr); + snprintf(errorcodesubcode_str, 255, "%s%s", + code_str, subcode_str); + json_object_string_add(json_peer, + "lastNotificationReason", + errorcodesubcode_str); + json_object_boolean_add(json_peer, + "lastNotificationHardReset", + peer->notify.hard_reset); + if (peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED + && peer->notify.code == BGP_NOTIFY_CEASE + && (peer->notify.subcode + == BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN + || peer->notify.subcode + == BGP_NOTIFY_CEASE_ADMIN_RESET) + && peer->notify.length) { + char msgbuf[1024]; + const char *msg_str; + + msg_str = bgp_notify_admin_message( + msgbuf, sizeof(msgbuf), + (uint8_t *)peer->notify.data, + peer->notify.length); + json_object_string_add(json_peer, + "lastShutdownDescription", + msg_str); + } + + } + json_object_string_add(json_peer, "lastResetDueTo", + peer_down_str[(int)peer->last_reset]); + json_object_int_add(json_peer, "lastResetCode", + peer->last_reset); + json_object_string_add(json_peer, "softwareVersion", + peer->soft_version ? peer->soft_version + : "n/a"); + } else { + if (peer->last_reset == PEER_DOWN_NOTIFY_SEND + || peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) { + code_str = bgp_notify_code_str(peer->notify.code); + subcode_str = + bgp_notify_subcode_str(peer->notify.code, + peer->notify.subcode); + vty_out(vty, " Notification %s (%s%s%s)\n", + peer->last_reset == PEER_DOWN_NOTIFY_SEND + ? "sent" + : "received", + code_str, subcode_str, + peer->notify.hard_reset + ? bgp_notify_subcode_str( + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_HARD_RESET) + : ""); + } else { + vty_out(vty, " %s (%s)\n", + peer_down_str[(int)peer->last_reset], + peer->soft_version ? peer->soft_version : "n/a"); + } + } +} + +static inline bool bgp_has_peer_failed(struct peer *peer, afi_t afi, + safi_t safi) +{ + return ((!peer_established(peer->connection)) || + !peer->afc_recv[afi][safi]); +} + +static void bgp_show_failed_summary(struct vty *vty, struct bgp *bgp, + struct peer *peer, json_object *json_peer, + int max_neighbor_width, bool use_json) +{ + char timebuf[BGP_UPTIME_LEN], dn_flag[2]; + int len; + + if (use_json) { + if (peer_dynamic_neighbor(peer)) + json_object_boolean_true_add(json_peer, + "dynamicPeer"); + if (peer->hostname) + json_object_string_add(json_peer, "hostname", + peer->hostname); + + if (peer->domainname) + json_object_string_add(json_peer, "domainname", + peer->domainname); + json_object_int_add(json_peer, "connectionsEstablished", + peer->established); + json_object_int_add(json_peer, "connectionsDropped", + peer->dropped); + peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, + use_json, json_peer); + if (peer_established(peer->connection)) + json_object_string_add(json_peer, "lastResetDueTo", + "AFI/SAFI Not Negotiated"); + else + bgp_show_peer_reset(NULL, peer, json_peer, true); + } else { + dn_flag[1] = '\0'; + dn_flag[0] = peer_dynamic_neighbor(peer) ? '*' : '\0'; + if (peer->hostname + && CHECK_FLAG(bgp->flags, BGP_FLAG_SHOW_HOSTNAME)) + len = vty_out(vty, "%s%s(%s)", dn_flag, + peer->hostname, peer->host); + else + len = vty_out(vty, "%s%s", dn_flag, peer->host); + + /* pad the neighbor column with spaces */ + if (len < max_neighbor_width) + vty_out(vty, "%*s", max_neighbor_width - len, + " "); + vty_out(vty, "%7d %7d %9s", peer->established, + peer->dropped, + peer_uptime(peer->uptime, timebuf, + BGP_UPTIME_LEN, 0, NULL)); + if (peer_established(peer->connection)) + vty_out(vty, " AFI/SAFI Not Negotiated\n"); + else + bgp_show_peer_reset(vty, peer, NULL, + false); + } +} + +/* Strip peer's description to the given size. */ +static char *bgp_peer_description_stripped(char *desc, uint32_t size) +{ + static char stripped[BUFSIZ]; + uint32_t i = 0; + uint32_t last_space = size; + + while (i < size) { + if (*(desc + i) == '\0') { + stripped[i] = '\0'; + return stripped; + } + if (i != 0 && *(desc + i) == ' ' && last_space != i - 1) + last_space = i; + stripped[i] = *(desc + i); + i++; + } + + stripped[last_space] = '\0'; + + return stripped; +} + +/* Determine whether var peer should be filtered out of the summary. */ +static bool bgp_show_summary_is_peer_filtered(struct peer *peer, + struct peer *fpeer, int as_type, + as_t as) +{ + + /* filter neighbor XXXX */ + if (fpeer && fpeer != peer) + return true; + + /* filter remote-as (internal|external) */ + if (as_type != AS_UNSPECIFIED) { + if (peer->as_type == AS_SPECIFIED) { + if (as_type == AS_INTERNAL) { + if (peer->as != peer->local_as) + return true; + } else if (peer->as == peer->local_as) + return true; + } else if (as_type != peer->as_type) + return true; + } else if (as && as != peer->as) /* filter remote-as XXX */ + return true; + + return false; +} + +/* Show BGP peer's summary information. + * + * Peer's description is stripped according to if `wide` option is given + * or not. + * + * When adding new columns to `show bgp summary` output, please make + * sure `Desc` is the lastest column to show because it can contain + * whitespaces and the whole output will be tricky. + */ +static int bgp_show_summary(struct vty *vty, struct bgp *bgp, int afi, int safi, + struct peer *fpeer, int as_type, as_t as, + uint16_t show_flags) +{ + struct peer *peer; + struct listnode *node, *nnode; + unsigned int count = 0, dn_count = 0; + char timebuf[BGP_UPTIME_LEN], dn_flag[2]; + char neighbor_buf[VTY_BUFSIZ]; + int neighbor_col_default_width = 16; + int len, failed_count = 0; + unsigned int filtered_count = 0; + int max_neighbor_width = 0; + int pfx_rcd_safi; + json_object *json = NULL; + json_object *json_peer = NULL; + json_object *json_peers = NULL; + struct peer_af *paf; + struct bgp_filter *filter; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + bool show_failed = CHECK_FLAG(show_flags, BGP_SHOW_OPT_FAILED); + bool show_established = + CHECK_FLAG(show_flags, BGP_SHOW_OPT_ESTABLISHED); + bool show_wide = CHECK_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + bool show_terse = CHECK_FLAG(show_flags, BGP_SHOW_OPT_TERSE); + + /* labeled-unicast routes are installed in the unicast table so in order + * to + * display the correct PfxRcd value we must look at SAFI_UNICAST + */ + + if (safi == SAFI_LABELED_UNICAST) + pfx_rcd_safi = SAFI_UNICAST; + else + pfx_rcd_safi = safi; + + if (use_json) { + json = json_object_new_object(); + json_peers = json_object_new_object(); + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (bgp_show_summary_is_peer_filtered(peer, fpeer, + as_type, as)) { + filtered_count++; + count++; + continue; + } + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if (peer->afc[afi][safi]) { + /* See if we have at least a single failed peer */ + if (bgp_has_peer_failed(peer, afi, safi)) + failed_count++; + count++; + } + if (peer_dynamic_neighbor(peer)) + dn_count++; + } + + } else { + /* Loop over all neighbors that will be displayed to determine + * how many + * characters are needed for the Neighbor column + */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (bgp_show_summary_is_peer_filtered(peer, fpeer, + as_type, as)) { + filtered_count++; + count++; + continue; + } + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if (peer->afc[afi][safi]) { + memset(dn_flag, '\0', sizeof(dn_flag)); + if (peer_dynamic_neighbor(peer)) + dn_flag[0] = '*'; + + if (peer->hostname + && CHECK_FLAG(bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + snprintf(neighbor_buf, + sizeof(neighbor_buf), + "%s%s(%s) ", dn_flag, + peer->hostname, peer->host); + else + snprintf(neighbor_buf, + sizeof(neighbor_buf), "%s%s ", + dn_flag, peer->host); + + len = strlen(neighbor_buf); + + if (len > max_neighbor_width) + max_neighbor_width = len; + + /* See if we have at least a single failed peer */ + if (bgp_has_peer_failed(peer, afi, safi)) + failed_count++; + count++; + } + } + + /* Originally we displayed the Neighbor column as 16 + * characters wide so make that the default + */ + if (max_neighbor_width < neighbor_col_default_width) + max_neighbor_width = neighbor_col_default_width; + } + + if (show_failed && !failed_count) { + if (use_json) { + json_object_int_add(json, "failedPeersCount", 0); + json_object_int_add(json, "dynamicPeers", dn_count); + json_object_int_add(json, "totalPeers", count); + + vty_json(vty, json); + } else { + vty_out(vty, "%% No failed BGP neighbors found\n"); + } + return CMD_SUCCESS; + } + + count = 0; /* Reset the value as its used again */ + filtered_count = 0; + dn_count = 0; + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if (!peer->afc[afi][safi]) + continue; + + if (!count) { + unsigned long ents; + char memstrbuf[MTYPE_MEMSTR_LEN]; + int64_t vrf_id_ui; + + vrf_id_ui = (bgp->vrf_id == VRF_UNKNOWN) + ? -1 + : (int64_t)bgp->vrf_id; + + /* Usage summary and header */ + if (use_json) { + json_object_string_addf(json, "routerId", + "%pI4", + &bgp->router_id); + asn_asn2json(json, "as", bgp->as, + bgp->asnotation); + json_object_int_add(json, "vrfId", vrf_id_ui); + json_object_string_add( + json, "vrfName", + (bgp->inst_type + == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } else { + vty_out(vty, + "BGP router identifier %pI4, local AS number %s %s vrf-id %d", + &bgp->router_id, bgp->as_pretty, + bgp->name_pretty, + bgp->vrf_id == VRF_UNKNOWN + ? -1 + : (int)bgp->vrf_id); + vty_out(vty, "\n"); + } + + if (bgp_update_delay_configured(bgp)) { + if (use_json) { + json_object_int_add( + json, "updateDelayLimit", + bgp->v_update_delay); + + if (bgp->v_update_delay + != bgp->v_establish_wait) + json_object_int_add( + json, + "updateDelayEstablishWait", + bgp->v_establish_wait); + + if (bgp_update_delay_active(bgp)) { + json_object_string_add( + json, + "updateDelayFirstNeighbor", + bgp->update_delay_begin_time); + json_object_boolean_true_add( + json, + "updateDelayInProgress"); + } else { + if (bgp->update_delay_over) { + json_object_string_add( + json, + "updateDelayFirstNeighbor", + bgp->update_delay_begin_time); + json_object_string_add( + json, + "updateDelayBestpathResumed", + bgp->update_delay_end_time); + json_object_string_add( + json, + "updateDelayZebraUpdateResume", + bgp->update_delay_zebra_resume_time); + json_object_string_add( + json, + "updateDelayPeerUpdateResume", + bgp->update_delay_peers_resume_time); + } + } + } else { + vty_out(vty, + "Read-only mode update-delay limit: %d seconds\n", + bgp->v_update_delay); + if (bgp->v_update_delay + != bgp->v_establish_wait) + vty_out(vty, + " Establish wait: %d seconds\n", + bgp->v_establish_wait); + + if (bgp_update_delay_active(bgp)) { + vty_out(vty, + " First neighbor established: %s\n", + bgp->update_delay_begin_time); + vty_out(vty, + " Delay in progress\n"); + } else { + if (bgp->update_delay_over) { + vty_out(vty, + " First neighbor established: %s\n", + bgp->update_delay_begin_time); + vty_out(vty, + " Best-paths resumed: %s\n", + bgp->update_delay_end_time); + vty_out(vty, + " zebra update resumed: %s\n", + bgp->update_delay_zebra_resume_time); + vty_out(vty, + " peers update resumed: %s\n", + bgp->update_delay_peers_resume_time); + } + } + } + } + + if (use_json) { + if (bgp_maxmed_onstartup_configured(bgp) + && bgp->maxmed_active) + json_object_boolean_true_add( + json, "maxMedOnStartup"); + if (bgp->v_maxmed_admin) + json_object_boolean_true_add( + json, "maxMedAdministrative"); + + json_object_int_add( + json, "tableVersion", + bgp_table_version(bgp->rib[afi][safi])); + + ents = bgp_table_count(bgp->rib[afi][safi]); + json_object_int_add(json, "ribCount", ents); + json_object_int_add( + json, "ribMemory", + ents * sizeof(struct bgp_dest)); + + ents = bgp->af_peer_count[afi][safi]; + json_object_int_add(json, "peerCount", ents); + json_object_int_add(json, "peerMemory", + ents * sizeof(struct peer)); + + if ((ents = listcount(bgp->group))) { + json_object_int_add( + json, "peerGroupCount", ents); + json_object_int_add( + json, "peerGroupMemory", + ents * sizeof(struct + peer_group)); + } + + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING)) + json_object_boolean_true_add( + json, "dampeningEnabled"); + } else { + if (!show_terse) { + if (bgp_maxmed_onstartup_configured(bgp) + && bgp->maxmed_active) + vty_out(vty, + "Max-med on-startup active\n"); + if (bgp->v_maxmed_admin) + vty_out(vty, + "Max-med administrative active\n"); + + vty_out(vty, + "BGP table version %" PRIu64 + "\n", + bgp_table_version( + bgp->rib[afi][safi])); + + ents = bgp_table_count( + bgp->rib[afi][safi]); + vty_out(vty, + "RIB entries %ld, using %s of memory\n", + ents, + mtype_memstr( + memstrbuf, + sizeof(memstrbuf), + ents + * sizeof( + struct + bgp_dest))); + + /* Peer related usage */ + ents = bgp->af_peer_count[afi][safi]; + vty_out(vty, + "Peers %ld, using %s of memory\n", + ents, + mtype_memstr( + memstrbuf, + sizeof(memstrbuf), + ents + * sizeof( + struct + peer))); + + if ((ents = listcount(bgp->group))) + vty_out(vty, + "Peer groups %ld, using %s of memory\n", + ents, + mtype_memstr( + memstrbuf, + sizeof(memstrbuf), + ents + * sizeof( + struct + peer_group))); + + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING)) + vty_out(vty, + "Dampening enabled.\n"); + } + if (show_failed) { + vty_out(vty, "\n"); + + /* Subtract 8 here because 'Neighbor' is + * 8 characters */ + vty_out(vty, "Neighbor"); + vty_out(vty, "%*s", + max_neighbor_width - 8, " "); + vty_out(vty, + BGP_SHOW_SUMMARY_HEADER_FAILED); + } + } + } + + paf = peer_af_find(peer, afi, safi); + filter = &peer->filter[afi][safi]; + + count++; + /* Works for both failed & successful cases */ + if (peer_dynamic_neighbor(peer)) + dn_count++; + + if (use_json) { + json_peer = NULL; + if (bgp_show_summary_is_peer_filtered(peer, fpeer, + as_type, as)) { + filtered_count++; + continue; + } + if (show_failed && + bgp_has_peer_failed(peer, afi, safi)) { + json_peer = json_object_new_object(); + bgp_show_failed_summary(vty, bgp, peer, + json_peer, 0, use_json); + } else if (!show_failed) { + if (show_established + && bgp_has_peer_failed(peer, afi, safi)) { + filtered_count++; + continue; + } + + json_peer = json_object_new_object(); + if (peer_dynamic_neighbor(peer)) { + json_object_boolean_true_add(json_peer, + "dynamicPeer"); + } + + if (peer->hostname) + json_object_string_add(json_peer, "hostname", + peer->hostname); + + if (peer->domainname) + json_object_string_add(json_peer, "domainname", + peer->domainname); + + asn_asn2json(json_peer, "remoteAs", peer->as, + bgp->asnotation); + asn_asn2json(json_peer, "localAs", + peer->change_local_as + ? peer->change_local_as + : peer->local_as, + bgp->asnotation); + json_object_int_add(json_peer, "version", 4); + json_object_int_add(json_peer, "msgRcvd", + PEER_TOTAL_RX(peer)); + json_object_int_add(json_peer, "msgSent", + PEER_TOTAL_TX(peer)); + + atomic_size_t outq_count, inq_count; + outq_count = + atomic_load_explicit(&peer->connection + ->obuf + ->count, + memory_order_relaxed); + inq_count = + atomic_load_explicit(&peer->connection + ->ibuf + ->count, + memory_order_relaxed); + + json_object_int_add( + json_peer, "tableVersion", + (paf && PAF_SUBGRP(paf)) + ? paf->subgroup->version + : 0); + json_object_int_add(json_peer, "outq", + outq_count); + json_object_int_add(json_peer, "inq", + inq_count); + peer_uptime(peer->uptime, timebuf, BGP_UPTIME_LEN, + use_json, json_peer); + + json_object_int_add(json_peer, "pfxRcd", + peer->pcount[afi][pfx_rcd_safi]); + + if (paf && PAF_SUBGRP(paf)) + json_object_int_add( + json_peer, "pfxSnt", + (PAF_SUBGRP(paf))->scount); + else + json_object_int_add(json_peer, "pfxSnt", + 0); + + /* BGP FSM state */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_SHUTDOWN)) + json_object_string_add(json_peer, + "state", + "Idle (Admin)"); + else if (peer->afc_recv[afi][safi]) + json_object_string_add( + json_peer, "state", + lookup_msg(bgp_status_msg, + peer->connection->status, + NULL)); + else if (CHECK_FLAG( + peer->sflags, + PEER_STATUS_PREFIX_OVERFLOW)) + json_object_string_add(json_peer, + "state", + "Idle (PfxCt)"); + else + json_object_string_add( + json_peer, "state", + lookup_msg(bgp_status_msg, + peer->connection->status, + NULL)); + + /* BGP peer state */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_SHUTDOWN)) + json_object_string_add(json_peer, + "peerState", + "Admin"); + else if (CHECK_FLAG( + peer->sflags, + PEER_STATUS_PREFIX_OVERFLOW)) + json_object_string_add(json_peer, + "peerState", + "PfxCt"); + else if (CHECK_FLAG(peer->flags, + PEER_FLAG_PASSIVE)) + json_object_string_add(json_peer, + "peerState", + "Passive"); + else if (CHECK_FLAG(peer->sflags, + PEER_STATUS_NSF_WAIT)) + json_object_string_add(json_peer, + "peerState", + "NSF passive"); + else if (CHECK_FLAG( + peer->bgp->flags, + BGP_FLAG_EBGP_REQUIRES_POLICY) + && (!bgp_inbound_policy_exists(peer, + filter) + || !bgp_outbound_policy_exists( + peer, filter))) + json_object_string_add(json_peer, + "peerState", + "Policy"); + else + json_object_string_add( + json_peer, "peerState", "OK"); + + json_object_int_add(json_peer, "connectionsEstablished", + peer->established); + json_object_int_add(json_peer, "connectionsDropped", + peer->dropped); + if (peer->desc) + json_object_string_add( + json_peer, "desc", peer->desc); + } + /* Avoid creating empty peer dicts in JSON */ + if (json_peer == NULL) + continue; + + if (peer->conf_if) + json_object_string_add(json_peer, "idType", + "interface"); + else if (peer->connection->su.sa.sa_family == AF_INET) + json_object_string_add(json_peer, "idType", + "ipv4"); + else if (peer->connection->su.sa.sa_family == AF_INET6) + json_object_string_add(json_peer, "idType", + "ipv6"); + json_object_object_add(json_peers, peer->host, + json_peer); + } else { + if (bgp_show_summary_is_peer_filtered(peer, fpeer, + as_type, as)) { + filtered_count++; + continue; + } + if (show_failed && + bgp_has_peer_failed(peer, afi, safi)) { + bgp_show_failed_summary(vty, bgp, peer, NULL, + max_neighbor_width, + use_json); + } else if (!show_failed) { + if (show_established + && bgp_has_peer_failed(peer, afi, safi)) { + filtered_count++; + continue; + } + + if ((count - filtered_count) == 1) { + /* display headline before the first + * neighbor line */ + vty_out(vty, "\n"); + + /* Subtract 8 here because 'Neighbor' is + * 8 characters */ + vty_out(vty, "Neighbor"); + vty_out(vty, "%*s", + max_neighbor_width - 8, " "); + vty_out(vty, + show_wide + ? BGP_SHOW_SUMMARY_HEADER_ALL_WIDE + : BGP_SHOW_SUMMARY_HEADER_ALL); + } + + memset(dn_flag, '\0', sizeof(dn_flag)); + if (peer_dynamic_neighbor(peer)) { + dn_flag[0] = '*'; + } + + if (peer->hostname + && CHECK_FLAG(bgp->flags, + BGP_FLAG_SHOW_HOSTNAME)) + len = vty_out(vty, "%s%s(%s)", dn_flag, + peer->hostname, + peer->host); + else + len = vty_out(vty, "%s%s", dn_flag, peer->host); + + /* pad the neighbor column with spaces */ + if (len < max_neighbor_width) + vty_out(vty, "%*s", max_neighbor_width - len, + " "); + + atomic_size_t outq_count, inq_count; + outq_count = + atomic_load_explicit(&peer->connection + ->obuf + ->count, + memory_order_relaxed); + inq_count = + atomic_load_explicit(&peer->connection + ->ibuf + ->count, + memory_order_relaxed); + + vty_out(vty, "4"); + vty_out(vty, ASN_FORMAT_SPACE(bgp->asnotation), + &peer->as); + if (show_wide) + vty_out(vty, + ASN_FORMAT_SPACE( + bgp->asnotation), + peer->change_local_as + ? &peer->change_local_as + : &peer->local_as); + vty_out(vty, + " %9u %9u %8" PRIu64 " %4zu %4zu %8s", + PEER_TOTAL_RX(peer), + PEER_TOTAL_TX(peer), + (paf && PAF_SUBGRP(paf)) + ? paf->subgroup->version + : 0, + inq_count, outq_count, + peer_uptime(peer->uptime, timebuf, + BGP_UPTIME_LEN, 0, NULL)); + + if (peer_established(peer->connection)) { + if (peer->afc_recv[afi][safi]) { + if (CHECK_FLAG( + bgp->flags, + BGP_FLAG_EBGP_REQUIRES_POLICY) + && !bgp_inbound_policy_exists( + peer, filter)) + vty_out(vty, " %12s", + "(Policy)"); + else + vty_out(vty, + " %12u", + peer->pcount + [afi] + [pfx_rcd_safi]); + } else { + vty_out(vty, " NoNeg"); + } + + if (paf && PAF_SUBGRP(paf)) { + if (CHECK_FLAG( + bgp->flags, + BGP_FLAG_EBGP_REQUIRES_POLICY) + && !bgp_outbound_policy_exists( + peer, filter)) + vty_out(vty, " %8s", + "(Policy)"); + else + vty_out(vty, + " %8u", + (PAF_SUBGRP( + paf)) + ->scount); + } else { + vty_out(vty, " NoNeg"); + } + } else { + if (CHECK_FLAG(peer->flags, + PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(peer->bgp->flags, + BGP_FLAG_SHUTDOWN)) + vty_out(vty, " Idle (Admin)"); + else if (CHECK_FLAG( + peer->sflags, + PEER_STATUS_PREFIX_OVERFLOW)) + vty_out(vty, " Idle (PfxCt)"); + else + vty_out(vty, " %12s", + lookup_msg(bgp_status_msg, + peer->connection + ->status, + NULL)); + + vty_out(vty, " %8u", 0); + } + /* Make sure `Desc` column is the lastest in + * the output. + * If the description is not set, try + * to print the software version if the + * capability is enabled and received. + */ + if (peer->desc) + vty_out(vty, " %s", + bgp_peer_description_stripped( + peer->desc, + show_wide ? 64 : 20)); + else if (peer->soft_version) { + vty_out(vty, " %s", + bgp_peer_description_stripped( + peer->soft_version, + show_wide ? 64 : 20)); + } else { + vty_out(vty, " N/A"); + } + vty_out(vty, "\n"); + } + + } + } + + if (use_json) { + json_object_object_add(json, "peers", json_peers); + json_object_int_add(json, "failedPeers", failed_count); + json_object_int_add(json, "displayedPeers", + count - filtered_count); + json_object_int_add(json, "totalPeers", count); + json_object_int_add(json, "dynamicPeers", dn_count); + + if (!show_failed) + bgp_show_bestpath_json(bgp, json); + + vty_json(vty, json); + } else { + if (count) { + if (filtered_count == count) + vty_out(vty, "\n%% No matching neighbor\n"); + else { + if (show_failed) + vty_out(vty, "\nDisplayed neighbors %d", + failed_count); + else if (as_type != AS_UNSPECIFIED || as + || fpeer || show_established) + vty_out(vty, "\nDisplayed neighbors %d", + count - filtered_count); + + vty_out(vty, "\nTotal number of neighbors %d\n", + count); + } + } else { + vty_out(vty, "No %s neighbor is configured\n", + get_afi_safi_str(afi, safi, false)); + } + + if (dn_count) { + vty_out(vty, "* - dynamic neighbor\n"); + vty_out(vty, "%d dynamic neighbor(s), limit %d\n", + dn_count, bgp->dynamic_neighbors_limit); + } + } + + return CMD_SUCCESS; +} + +static void bgp_show_summary_afi_safi(struct vty *vty, struct bgp *bgp, int afi, + int safi, struct peer *fpeer, int as_type, + as_t as, uint16_t show_flags) +{ + int is_first = 1; + int afi_wildcard = (afi == AFI_MAX); + int safi_wildcard = (safi == SAFI_MAX); + int is_wildcard = (afi_wildcard || safi_wildcard); + bool nbr_output = false; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (use_json && is_wildcard) + vty_out(vty, "{\n"); + if (afi_wildcard) + afi = 1; /* AFI_IP */ + while (afi < AFI_MAX) { + if (safi_wildcard) + safi = 1; /* SAFI_UNICAST */ + while (safi < SAFI_MAX) { + if (bgp_afi_safi_peer_exists(bgp, afi, safi)) { + nbr_output = true; + + if (is_wildcard) { + /* + * So limit output to those afi/safi + * pairs that + * actualy have something interesting in + * them + */ + if (use_json) { + if (!is_first) + vty_out(vty, ",\n"); + else + is_first = 0; + + vty_out(vty, "\"%s\":", + get_afi_safi_str(afi, + safi, + true)); + } else { + vty_out(vty, "\n%s Summary:\n", + get_afi_safi_str(afi, + safi, + false)); + } + } + bgp_show_summary(vty, bgp, afi, safi, fpeer, + as_type, as, show_flags); + } + safi++; + if (!safi_wildcard) + safi = SAFI_MAX; + } + afi++; + if (!afi_wildcard) + afi = AFI_MAX; + } + + if (use_json && is_wildcard) + vty_out(vty, "}\n"); + else if (!nbr_output) { + if (use_json) + vty_out(vty, "{}\n"); + else + vty_out(vty, "%% No BGP neighbors found in %s\n", + bgp->name_pretty); + } +} + +static void bgp_show_all_instances_summary_vty(struct vty *vty, afi_t afi, + safi_t safi, + const char *neighbor, + int as_type, as_t as, + uint16_t show_flags) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + struct peer *fpeer = NULL; + int is_first = 1; + bool nbr_output = false; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + if (use_json) + vty_out(vty, "{\n"); + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + nbr_output = true; + if (use_json) { + if (!is_first) + vty_out(vty, ",\n"); + else + is_first = 0; + + vty_out(vty, "\"%s\":", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } + if (neighbor) { + fpeer = peer_lookup_in_view(vty, bgp, neighbor, + use_json); + if (!fpeer) + continue; + } + bgp_show_summary_afi_safi(vty, bgp, afi, safi, fpeer, as_type, + as, show_flags); + } + + if (use_json) + vty_out(vty, "}\n"); + else if (!nbr_output) + vty_out(vty, "%% BGP instance not found\n"); +} + +int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, + safi_t safi, const char *neighbor, int as_type, + as_t as, uint16_t show_flags) +{ + struct bgp *bgp; + bool use_json = CHECK_FLAG(show_flags, BGP_SHOW_OPT_JSON); + struct peer *fpeer = NULL; + + if (name) { + if (strmatch(name, "all")) { + bgp_show_all_instances_summary_vty(vty, afi, safi, + neighbor, as_type, + as, show_flags); + return CMD_SUCCESS; + } else { + bgp = bgp_lookup_by_name(name); + + if (!bgp) { + if (use_json) + vty_out(vty, "{}\n"); + else + vty_out(vty, + "%% BGP instance not found\n"); + return CMD_WARNING; + } + + if (neighbor) { + fpeer = peer_lookup_in_view(vty, bgp, neighbor, + use_json); + if (!fpeer) + return CMD_WARNING; + } + bgp_show_summary_afi_safi(vty, bgp, afi, safi, fpeer, + as_type, as, show_flags); + return CMD_SUCCESS; + } + } + + bgp = bgp_get_default(); + + if (bgp) { + if (neighbor) { + fpeer = peer_lookup_in_view(vty, bgp, neighbor, + use_json); + if (!fpeer) + return CMD_WARNING; + } + bgp_show_summary_afi_safi(vty, bgp, afi, safi, fpeer, as_type, + as, show_flags); + } else { + if (use_json) + vty_out(vty, "{}\n"); + else + vty_out(vty, "%% BGP instance not found\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* `show [ip] bgp summary' commands. */ +DEFPY(show_ip_bgp_summary, show_ip_bgp_summary_cmd, + "show [ip] bgp [ VIEWVRFNAME] [" BGP_AFI_CMD_STR + " [" BGP_SAFI_WITH_LABEL_CMD_STR + "]] [all$all] summary [established|failed] [|remote-as >] [terse] [wide] [json$uj]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Display the entries for all address families\n" + "Summary of BGP neighbor status\n" + "Show only sessions in Established state\n" + "Show only sessions not in Established state\n" + "Show only the specified neighbor session\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + "Show only the specified remote AS sessions\n" AS_STR + "Internal (iBGP) AS sessions\n" + "External (eBGP) AS sessions\n" + "Shorten the information on BGP instances\n" + "Increase table width for longer output\n" JSON_STR) +{ + char *vrf = NULL; + afi_t afi = AFI_MAX; + safi_t safi = SAFI_MAX; + as_t as = 0; /* 0 means AS filter not set */ + int as_type = AS_UNSPECIFIED; + uint16_t show_flags = 0; + + int idx = 0; + + /* show [ip] bgp */ + if (!all && argv_find(argv, argc, "ip", &idx)) + afi = AFI_IP; + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } else if (argv_find(argv, argc, "view", &idx)) + /* [ VIEWVRFNAME] */ + vrf = argv[idx + 1]->arg; + /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + argv_find_and_parse_safi(argv, argc, &idx, &safi); + } + + if (argv_find(argv, argc, "failed", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_FAILED); + + if (argv_find(argv, argc, "established", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_ESTABLISHED); + + if (argv_find(argv, argc, "remote-as", &idx)) { + if (argv[idx + 1]->arg[0] == 'i') + as_type = AS_INTERNAL; + else if (argv[idx + 1]->arg[0] == 'e') + as_type = AS_EXTERNAL; + else if (!asn_str2asn(argv[idx + 1]->arg, &as)) { + vty_out(vty, + "%% Invalid neighbor remote-as value: %s\n", + argv[idx + 1]->arg); + return CMD_SUCCESS; + } + } + + if (argv_find(argv, argc, "terse", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_TERSE); + + if (argv_find(argv, argc, "wide", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + if (argv_find(argv, argc, "json", &idx)) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + + return bgp_show_summary_vty(vty, vrf, afi, safi, neighbor, as_type, as, + show_flags); +} + +const char *get_afi_safi_str(afi_t afi, safi_t safi, bool for_json) +{ + if (for_json) + return get_afi_safi_json_str(afi, safi); + else + return get_afi_safi_vty_str(afi, safi); +} + + +static void bgp_show_peer_afi_orf_cap(struct vty *vty, struct peer *p, + afi_t afi, safi_t safi, + uint16_t adv_smcap, uint16_t adv_rmcap, + uint16_t rcv_smcap, uint16_t rcv_rmcap, + bool use_json, json_object *json_pref) +{ + /* Send-Mode */ + if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap) + || CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) { + if (use_json) { + if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap) + && CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) + json_object_string_add(json_pref, "sendMode", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap)) + json_object_string_add(json_pref, "sendMode", + "advertised"); + else if (CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) + json_object_string_add(json_pref, "sendMode", + "received"); + } else { + vty_out(vty, " Send-mode: "); + if (CHECK_FLAG(p->af_cap[afi][safi], adv_smcap)) + vty_out(vty, "advertised"); + if (CHECK_FLAG(p->af_cap[afi][safi], rcv_smcap)) + vty_out(vty, "%sreceived", + CHECK_FLAG(p->af_cap[afi][safi], + adv_smcap) + ? ", " + : ""); + vty_out(vty, "\n"); + } + } + + /* Receive-Mode */ + if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap) + || CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) { + if (use_json) { + if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap) + && CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) + json_object_string_add(json_pref, "recvMode", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap)) + json_object_string_add(json_pref, "recvMode", + "advertised"); + else if (CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) + json_object_string_add(json_pref, "recvMode", + "received"); + } else { + vty_out(vty, " Receive-mode: "); + if (CHECK_FLAG(p->af_cap[afi][safi], adv_rmcap)) + vty_out(vty, "advertised"); + if (CHECK_FLAG(p->af_cap[afi][safi], rcv_rmcap)) + vty_out(vty, "%sreceived", + CHECK_FLAG(p->af_cap[afi][safi], + adv_rmcap) + ? ", " + : ""); + vty_out(vty, "\n"); + } + } +} + +static void bgp_show_neighnor_graceful_restart_flags(struct vty *vty, + struct peer *p, + json_object *json) +{ + bool rbit = false; + bool nbit = false; + + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV) && + (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) && + (peer_established(p->connection))) { + rbit = CHECK_FLAG(p->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + nbit = CHECK_FLAG(p->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); + } + + if (json) { + json_object_boolean_add(json, "rBit", rbit); + json_object_boolean_add(json, "nBit", nbit); + } else { + vty_out(vty, "\n R bit: %s", rbit ? "True" : "False"); + vty_out(vty, "\n N bit: %s\n", nbit ? "True" : "False"); + } +} + +static void bgp_show_neighbor_graceful_restart_remote_mode(struct vty *vty, + struct peer *peer, + json_object *json) +{ + const char *mode = "NotApplicable"; + + if (!json) + vty_out(vty, "\n Remote GR Mode: "); + + if (CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV) && + (peer_established(peer->connection))) { + if ((peer->nsf_af_count == 0) + && !CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + + mode = "Disable"; + + } else if (peer->nsf_af_count == 0 + && CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + + mode = "Helper"; + + } else if (peer->nsf_af_count != 0 + && CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) { + + mode = "Restart"; + } + } + + if (json) + json_object_string_add(json, "remoteGrMode", mode); + else + vty_out(vty, "%s", mode); +} + +static void bgp_show_neighbor_graceful_restart_local_mode(struct vty *vty, + struct peer *p, + json_object *json) +{ + const char *mode = "Invalid"; + + if (!json) + vty_out(vty, " Local GR Mode: "); + + if (bgp_peer_gr_mode_get(p) == PEER_HELPER) + mode = "Helper"; + else if (bgp_peer_gr_mode_get(p) == PEER_GR) + mode = "Restart"; + else if (bgp_peer_gr_mode_get(p) == PEER_DISABLE) + mode = "Disable"; + else if (bgp_peer_gr_mode_get(p) == PEER_GLOBAL_INHERIT) { + if (bgp_global_gr_mode_get(p->bgp) == GLOBAL_HELPER) + mode = "Helper*"; + else if (bgp_global_gr_mode_get(p->bgp) == GLOBAL_GR) + mode = "Restart*"; + else if (bgp_global_gr_mode_get(p->bgp) == GLOBAL_DISABLE) + mode = "Disable*"; + else + mode = "Invalid*"; + } + + if (json) + json_object_string_add(json, "localGrMode", mode); + else + vty_out(vty, "%s", mode); +} + +static void bgp_show_neighbor_graceful_restart_capability_per_afi_safi( + struct vty *vty, struct peer *peer, json_object *json) +{ + afi_t afi; + safi_t safi; + json_object *json_afi_safi = NULL; + json_object *json_timer = NULL; + json_object *json_endofrib_status = NULL; + bool eor_flag = false; + + FOREACH_AFI_SAFI_NSF (afi, safi) { + if (!peer->afc[afi][safi]) + continue; + + if (!CHECK_FLAG(peer->cap, PEER_CAP_RESTART_ADV) || + !CHECK_FLAG(peer->cap, PEER_CAP_RESTART_RCV)) + continue; + + if (json) { + json_afi_safi = json_object_new_object(); + json_endofrib_status = json_object_new_object(); + json_timer = json_object_new_object(); + } + + if (peer->eor_stime[afi][safi] >= peer->pkt_stime[afi][safi]) + eor_flag = true; + else + eor_flag = false; + + if (!json) { + vty_out(vty, " %s:\n", + get_afi_safi_str(afi, safi, false)); + + vty_out(vty, " F bit: "); + } + + if (peer->nsf[afi][safi] && + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV)) { + + if (json) { + json_object_boolean_true_add(json_afi_safi, + "fBit"); + } else + vty_out(vty, "True\n"); + } else { + if (json) + json_object_boolean_false_add(json_afi_safi, + "fBit"); + else + vty_out(vty, "False\n"); + } + + if (!json) + vty_out(vty, " End-of-RIB sent: "); + + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND)) { + if (json) { + json_object_boolean_true_add( + json_endofrib_status, "endOfRibSend"); + + PRINT_EOR_JSON(eor_flag); + } else { + vty_out(vty, "Yes\n"); + vty_out(vty, + " End-of-RIB sent after update: "); + + PRINT_EOR(eor_flag); + } + } else { + if (json) { + json_object_boolean_false_add( + json_endofrib_status, "endOfRibSend"); + json_object_boolean_false_add( + json_endofrib_status, + "endOfRibSentAfterUpdate"); + } else { + vty_out(vty, "No\n"); + vty_out(vty, + " End-of-RIB sent after update: "); + vty_out(vty, "No\n"); + } + } + + if (!json) + vty_out(vty, " End-of-RIB received: "); + + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + if (json) + json_object_boolean_true_add( + json_endofrib_status, "endOfRibRecv"); + else + vty_out(vty, "Yes\n"); + } else { + if (json) + json_object_boolean_false_add( + json_endofrib_status, "endOfRibRecv"); + else + vty_out(vty, "No\n"); + } + + if (json) { + json_object_int_add(json_timer, "stalePathTimer", + peer->bgp->stalepath_time); + json_object_int_add(json_timer, "llgrStaleTime", + peer->llgr[afi][safi].stale_time); + + if (peer->connection->t_gr_stale != NULL) { + json_object_int_add(json_timer, + "stalePathTimerRemaining", + event_timer_remain_second( + peer->connection + ->t_gr_stale)); + } + + /* Display Configured Selection + * Deferral only when when + * Gr mode is enabled. + */ + if (CHECK_FLAG(peer->flags, + PEER_FLAG_GRACEFUL_RESTART)) { + json_object_int_add(json_timer, + "selectionDeferralTimer", + peer->bgp->stalepath_time); + } + + if (peer->bgp->gr_info[afi][safi].t_select_deferral != + NULL) { + + json_object_int_add( + json_timer, + "selectionDeferralTimerRemaining", + event_timer_remain_second( + peer->bgp->gr_info[afi][safi] + .t_select_deferral)); + } + } else { + vty_out(vty, " Timers:\n"); + vty_out(vty, + " Configured Stale Path Time(sec): %u\n", + peer->bgp->stalepath_time); + + if (peer->connection->t_gr_stale != NULL) + vty_out(vty, + " Stale Path Remaining(sec): %ld\n", + event_timer_remain_second( + peer->connection->t_gr_stale)); + /* Display Configured Selection + * Deferral only when when + * Gr mode is enabled. + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) + vty_out(vty, + " Configured Selection Deferral Time(sec): %u\n", + peer->bgp->select_defer_time); + + vty_out(vty, " LLGR Stale Path Time(sec): %u\n", + peer->llgr[afi][safi].stale_time); + + if (peer->bgp->gr_info[afi][safi].t_select_deferral != + NULL) + vty_out(vty, + " Selection Deferral Time Remaining(sec): %ld\n", + event_timer_remain_second( + peer->bgp->gr_info[afi][safi] + .t_select_deferral)); + } + if (json) { + json_object_object_add(json_afi_safi, "endOfRibStatus", + json_endofrib_status); + json_object_object_add(json_afi_safi, "timers", + json_timer); + json_object_object_add( + json, get_afi_safi_str(afi, safi, true), + json_afi_safi); + } + } +} + +static void bgp_show_neighbor_graceful_restart_time(struct vty *vty, + struct peer *p, + json_object *json) +{ + if (json) { + json_object *json_timer = NULL; + + json_timer = json_object_new_object(); + + json_object_int_add(json_timer, "configuredRestartTimer", + p->bgp->restart_time); + json_object_int_add(json_timer, "configuredLlgrStaleTime", + p->bgp->llgr_stale_time); + + json_object_int_add(json_timer, "receivedRestartTimer", + p->v_gr_restart); + + if (p->connection->t_gr_restart != NULL) + json_object_int_add(json_timer, "restartTimerRemaining", + event_timer_remain_second( + p->connection->t_gr_restart)); + + json_object_object_add(json, "timers", json_timer); + } else { + + vty_out(vty, " Timers:\n"); + vty_out(vty, " Configured Restart Time(sec): %u\n", + p->bgp->restart_time); + + vty_out(vty, " Received Restart Time(sec): %u\n", + p->v_gr_restart); + vty_out(vty, " Configured LLGR Stale Path Time(sec): %u\n", + p->bgp->llgr_stale_time); + if (p->connection->t_gr_restart != NULL) + vty_out(vty, " Restart Time Remaining(sec): %ld\n", + event_timer_remain_second( + p->connection->t_gr_restart)); + if (p->connection->t_gr_restart != NULL) { + vty_out(vty, " Restart Time Remaining(sec): %ld\n", + event_timer_remain_second( + p->connection->t_gr_restart)); + } + } +} + +static void bgp_show_peer_gr_status(struct vty *vty, struct peer *p, + json_object *json) +{ + char dn_flag[2] = {0}; + /* '*' + v6 address of neighbor */ + char neighborAddr[INET6_ADDRSTRLEN + 1] = {0}; + + if (!p->conf_if && peer_dynamic_neighbor(p)) + dn_flag[0] = '*'; + + if (p->conf_if) { + if (json) + json_object_string_addf(json, "neighborAddr", "%pSU", + &p->connection->su); + else + vty_out(vty, "BGP neighbor on %s: %pSU\n", p->conf_if, + &p->connection->su); + } else { + snprintf(neighborAddr, sizeof(neighborAddr), "%s%s", dn_flag, + p->host); + + if (json) + json_object_string_add(json, "neighborAddr", + neighborAddr); + else + vty_out(vty, "BGP neighbor is %s\n", neighborAddr); + } + + /* more gr info in new format */ + BGP_SHOW_PEER_GR_CAPABILITY(vty, p, json); +} + +static void bgp_show_peer_afi(struct vty *vty, struct peer *p, afi_t afi, + safi_t safi, bool use_json, + json_object *json_neigh) +{ + struct bgp_filter *filter; + struct peer_af *paf; + char orf_pfx_name[BUFSIZ]; + int orf_pfx_count; + json_object *json_af = NULL; + json_object *json_prefA = NULL; + json_object *json_addr = NULL; + json_object *json_advmap = NULL; + + if (use_json) { + json_addr = json_object_new_object(); + json_af = json_object_new_object(); + filter = &p->filter[afi][safi]; + + if (peer_group_active(p)) + json_object_string_add(json_addr, "peerGroupMember", + p->group->name); + + paf = peer_af_find(p, afi, safi); + if (paf && PAF_SUBGRP(paf)) { + json_object_int_add(json_addr, "updateGroupId", + PAF_UPDGRP(paf)->id); + json_object_int_add(json_addr, "subGroupId", + PAF_SUBGRP(paf)->id); + json_object_int_add(json_addr, "packetQueueLength", + bpacket_queue_virtual_length(paf)); + } + + if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) { + json_object_int_add(json_af, "orfType", + ORF_TYPE_PREFIX); + json_prefA = json_object_new_object(); + bgp_show_peer_afi_orf_cap(vty, p, afi, safi, + PEER_CAP_ORF_PREFIX_SM_ADV, + PEER_CAP_ORF_PREFIX_RM_ADV, + PEER_CAP_ORF_PREFIX_SM_RCV, + PEER_CAP_ORF_PREFIX_RM_RCV, + use_json, json_prefA); + json_object_object_add(json_af, "orfPrefixList", + json_prefA); + } + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) + json_object_object_add(json_addr, "afDependentCap", + json_af); + else + json_object_free(json_af); + + snprintf(orf_pfx_name, sizeof(orf_pfx_name), "%s.%d.%d", + p->host, afi, safi); + orf_pfx_count = prefix_bgp_show_prefix_list( + NULL, afi, orf_pfx_name, use_json); + + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND) + || orf_pfx_count) { + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND)) + json_object_boolean_true_add(json_neigh, + "orfSent"); + if (orf_pfx_count) + json_object_int_add(json_addr, "orfRecvCounter", + orf_pfx_count); + } + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH)) + json_object_string_add( + json_addr, "orfFirstUpdate", + "deferredUntilORFOrRouteRefreshRecvd"); + + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + json_object_boolean_true_add(json_addr, + "routeReflectorClient"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT)) + json_object_boolean_true_add(json_addr, + "routeServerClient"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) + json_object_boolean_true_add(json_addr, + "inboundSoftConfigPermit"); + + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) + json_object_boolean_true_add( + json_addr, + "privateAsNumsAllReplacedInUpdatesToNbr"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) + json_object_boolean_true_add( + json_addr, + "privateAsNumsReplacedInUpdatesToNbr"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) + json_object_boolean_true_add( + json_addr, + "privateAsNumsAllRemovedInUpdatesToNbr"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS)) + json_object_boolean_true_add( + json_addr, + "privateAsNumsRemovedInUpdatesToNbr"); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) { + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) + json_object_boolean_true_add(json_addr, + "allowAsInOrigin"); + else + json_object_int_add(json_addr, "allowAsInCount", + p->allowas_in[afi][safi]); + } + + if (p->addpath_type[afi][safi] != BGP_ADDPATH_NONE) + json_object_boolean_true_add( + json_addr, + bgp_addpath_names(p->addpath_type[afi][safi]) + ->type_json_name); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_OVERRIDE)) + json_object_string_add(json_addr, + "overrideASNsInOutboundUpdates", + "ifAspathEqualRemoteAs"); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_SELF) + || CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) + json_object_boolean_true_add(json_addr, + "routerAlwaysNextHop"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_AS_PATH_UNCHANGED)) + json_object_boolean_true_add( + json_addr, "unchangedAsPathPropogatedToNbr"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED)) + json_object_boolean_true_add( + json_addr, "unchangedNextHopPropogatedToNbr"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MED_UNCHANGED)) + json_object_boolean_true_add( + json_addr, "unchangedMedPropogatedToNbr"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) + || CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY)) { + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_COMMUNITY) + && CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY)) + json_object_string_add(json_addr, + "commAttriSentToNbr", + "extendedAndStandard"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY)) + json_object_string_add(json_addr, + "commAttriSentToNbr", + "extended"); + else + json_object_string_add(json_addr, + "commAttriSentToNbr", + "standard"); + } + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) { + if (p->default_rmap[afi][safi].name) + json_object_string_add( + json_addr, "defaultRouteMap", + p->default_rmap[afi][safi].name); + + if (paf && PAF_SUBGRP(paf) + && CHECK_FLAG(PAF_SUBGRP(paf)->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) + json_object_boolean_true_add(json_addr, + "defaultSent"); + else + json_object_boolean_true_add(json_addr, + "defaultNotSent"); + } + + if (afi == AFI_L2VPN && safi == SAFI_EVPN) { + if (is_evpn_enabled()) + json_object_boolean_true_add( + json_addr, "advertiseAllVnis"); + } + + if (filter->plist[FILTER_IN].name + || filter->dlist[FILTER_IN].name + || filter->aslist[FILTER_IN].name + || filter->map[RMAP_IN].name) + json_object_boolean_true_add(json_addr, + "inboundPathPolicyConfig"); + if (filter->plist[FILTER_OUT].name + || filter->dlist[FILTER_OUT].name + || filter->aslist[FILTER_OUT].name + || filter->map[RMAP_OUT].name || filter->usmap.name) + json_object_boolean_true_add( + json_addr, "outboundPathPolicyConfig"); + + /* prefix-list */ + if (filter->plist[FILTER_IN].name) + json_object_string_add(json_addr, + "incomingUpdatePrefixFilterList", + filter->plist[FILTER_IN].name); + if (filter->plist[FILTER_OUT].name) + json_object_string_add(json_addr, + "outgoingUpdatePrefixFilterList", + filter->plist[FILTER_OUT].name); + + /* distribute-list */ + if (filter->dlist[FILTER_IN].name) + json_object_string_add( + json_addr, "incomingUpdateNetworkFilterList", + filter->dlist[FILTER_IN].name); + if (filter->dlist[FILTER_OUT].name) + json_object_string_add( + json_addr, "outgoingUpdateNetworkFilterList", + filter->dlist[FILTER_OUT].name); + + /* filter-list. */ + if (filter->aslist[FILTER_IN].name) + json_object_string_add(json_addr, + "incomingUpdateAsPathFilterList", + filter->aslist[FILTER_IN].name); + if (filter->aslist[FILTER_OUT].name) + json_object_string_add(json_addr, + "outgoingUpdateAsPathFilterList", + filter->aslist[FILTER_OUT].name); + + /* route-map. */ + if (filter->map[RMAP_IN].name) + json_object_string_add( + json_addr, "routeMapForIncomingAdvertisements", + filter->map[RMAP_IN].name); + if (filter->map[RMAP_OUT].name) + json_object_string_add( + json_addr, "routeMapForOutgoingAdvertisements", + filter->map[RMAP_OUT].name); + + /* ebgp-requires-policy (inbound) */ + if (CHECK_FLAG(p->bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY) + && !bgp_inbound_policy_exists(p, filter)) + json_object_string_add( + json_addr, "inboundEbgpRequiresPolicy", + "Inbound updates discarded due to missing policy"); + + /* ebgp-requires-policy (outbound) */ + if (CHECK_FLAG(p->bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY) + && (!bgp_outbound_policy_exists(p, filter))) + json_object_string_add( + json_addr, "outboundEbgpRequiresPolicy", + "Outbound updates discarded due to missing policy"); + + /* unsuppress-map */ + if (filter->usmap.name) + json_object_string_add(json_addr, + "selectiveUnsuppressRouteMap", + filter->usmap.name); + + /* advertise-map */ + if (filter->advmap.aname) { + json_advmap = json_object_new_object(); + json_object_string_add(json_advmap, "condition", + filter->advmap.condition + ? "EXIST" + : "NON_EXIST"); + json_object_string_add(json_advmap, "conditionMap", + filter->advmap.cname); + json_object_string_add(json_advmap, "advertiseMap", + filter->advmap.aname); + json_object_string_add( + json_advmap, "advertiseStatus", + filter->advmap.update_type == + UPDATE_TYPE_ADVERTISE + ? "Advertise" + : "Withdraw"); + json_object_object_add(json_addr, "advertiseMap", + json_advmap); + } + + /* Receive prefix count */ + json_object_int_add(json_addr, "acceptedPrefixCounter", + p->pcount[afi][safi]); + if (paf && PAF_SUBGRP(paf)) + json_object_int_add(json_addr, "sentPrefixCounter", + (PAF_SUBGRP(paf))->scount); + + /* Maximum prefix */ + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX_OUT)) + json_object_int_add(json_addr, "prefixOutAllowedMax", + p->pmax_out[afi][safi]); + + /* Maximum prefix */ + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) { + json_object_int_add(json_addr, "prefixAllowedMax", + p->pmax[afi][safi]); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING)) + json_object_boolean_true_add( + json_addr, "prefixAllowedMaxWarning"); + json_object_int_add(json_addr, + "prefixAllowedWarningThresh", + p->pmax_threshold[afi][safi]); + if (p->pmax_restart[afi][safi]) + json_object_int_add( + json_addr, + "prefixAllowedRestartIntervalMsecs", + p->pmax_restart[afi][safi] * 60000); + } + json_object_object_add(json_neigh, + get_afi_safi_str(afi, safi, true), + json_addr); + + } else { + filter = &p->filter[afi][safi]; + + vty_out(vty, " For address family: %s\n", + get_afi_safi_str(afi, safi, false)); + + if (peer_group_active(p)) + vty_out(vty, " %s peer-group member\n", + p->group->name); + + paf = peer_af_find(p, afi, safi); + if (paf && PAF_SUBGRP(paf)) { + vty_out(vty, " Update group %" PRIu64", subgroup %" PRIu64 "\n", + PAF_UPDGRP(paf)->id, PAF_SUBGRP(paf)->id); + vty_out(vty, " Packet Queue length %d\n", + bpacket_queue_virtual_length(paf)); + } else { + vty_out(vty, " Not part of any update group\n"); + } + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_RM_RCV)) + vty_out(vty, " AF-dependant capabilities:\n"); + + if (CHECK_FLAG(p->af_cap[afi][safi], PEER_CAP_ORF_PREFIX_SM_ADV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_RCV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_ADV) + || CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) { + vty_out(vty, + " Outbound Route Filter (ORF) type (%d) Prefix-list:\n", + ORF_TYPE_PREFIX); + bgp_show_peer_afi_orf_cap( + vty, p, afi, safi, PEER_CAP_ORF_PREFIX_SM_ADV, + PEER_CAP_ORF_PREFIX_RM_ADV, + PEER_CAP_ORF_PREFIX_SM_RCV, + PEER_CAP_ORF_PREFIX_RM_RCV, use_json, NULL); + } + + snprintf(orf_pfx_name, sizeof(orf_pfx_name), "%s.%d.%d", + p->host, afi, safi); + orf_pfx_count = prefix_bgp_show_prefix_list( + NULL, afi, orf_pfx_name, use_json); + + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND) + || orf_pfx_count) { + vty_out(vty, " Outbound Route Filter (ORF):"); + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND)) + vty_out(vty, " sent;"); + if (orf_pfx_count) + vty_out(vty, " received (%d entries)", + orf_pfx_count); + vty_out(vty, "\n"); + } + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_ORF_WAIT_REFRESH)) + vty_out(vty, + " First update is deferred until ORF or ROUTE-REFRESH is received\n"); + + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REFLECTOR_CLIENT)) + vty_out(vty, " Route-Reflector Client\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_RSERVER_CLIENT)) + vty_out(vty, " Route-Server Client\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SOFT_RECONFIG)) + vty_out(vty, + " Inbound soft reconfiguration allowed\n"); + + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) + vty_out(vty, + " Private AS numbers (all) replaced in updates to this neighbor\n"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) + vty_out(vty, + " Private AS numbers replaced in updates to this neighbor\n"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) + vty_out(vty, + " Private AS numbers (all) removed in updates to this neighbor\n"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_REMOVE_PRIVATE_AS)) + vty_out(vty, + " Private AS numbers removed in updates to this neighbor\n"); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) { + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) + vty_out(vty, + " Local AS allowed as path origin\n"); + else + vty_out(vty, + " Local AS allowed in path, %d occurrences\n", + p->allowas_in[afi][safi]); + } + + if (p->addpath_type[afi][safi] != BGP_ADDPATH_NONE) + vty_out(vty, " %s\n", + bgp_addpath_names(p->addpath_type[afi][safi]) + ->human_description); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_AS_OVERRIDE)) + vty_out(vty, + " Override ASNs in outbound updates if aspath equals remote-as\n"); + + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_NEXTHOP_SELF) + || CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_FORCE_NEXTHOP_SELF)) + vty_out(vty, " NEXT_HOP is always this router\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_AS_PATH_UNCHANGED)) + vty_out(vty, + " AS_PATH is propagated unchanged to this neighbor\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED)) + vty_out(vty, + " NEXT_HOP is propagated unchanged to this neighbor\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MED_UNCHANGED)) + vty_out(vty, + " MED is propagated unchanged to this neighbor\n"); + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY) + || CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY) + || CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY)) { + vty_out(vty, + " Community attribute sent to this neighbor"); + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_COMMUNITY) + && CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY) + && CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY)) + vty_out(vty, "(all)\n"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY)) + vty_out(vty, "(large)\n"); + else if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY)) + vty_out(vty, "(extended)\n"); + else + vty_out(vty, "(standard)\n"); + } + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) { + vty_out(vty, " Default information originate,"); + + if (p->default_rmap[afi][safi].name) + vty_out(vty, " default route-map %s%s,", + p->default_rmap[afi][safi].map ? "*" + : "", + p->default_rmap[afi][safi].name); + if (paf && PAF_SUBGRP(paf) + && CHECK_FLAG(PAF_SUBGRP(paf)->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) + vty_out(vty, " default sent\n"); + else + vty_out(vty, " default not sent\n"); + } + + /* advertise-vni-all */ + if (afi == AFI_L2VPN && safi == SAFI_EVPN) { + if (is_evpn_enabled()) + vty_out(vty, " advertise-all-vni\n"); + } + + if (filter->plist[FILTER_IN].name + || filter->dlist[FILTER_IN].name + || filter->aslist[FILTER_IN].name + || filter->map[RMAP_IN].name) + vty_out(vty, " Inbound path policy configured\n"); + if (filter->plist[FILTER_OUT].name + || filter->dlist[FILTER_OUT].name + || filter->aslist[FILTER_OUT].name + || filter->map[RMAP_OUT].name || filter->usmap.name) + vty_out(vty, " Outbound path policy configured\n"); + + /* prefix-list */ + if (filter->plist[FILTER_IN].name) + vty_out(vty, + " Incoming update prefix filter list is %s%s\n", + filter->plist[FILTER_IN].plist ? "*" : "", + filter->plist[FILTER_IN].name); + if (filter->plist[FILTER_OUT].name) + vty_out(vty, + " Outgoing update prefix filter list is %s%s\n", + filter->plist[FILTER_OUT].plist ? "*" : "", + filter->plist[FILTER_OUT].name); + + /* distribute-list */ + if (filter->dlist[FILTER_IN].name) + vty_out(vty, + " Incoming update network filter list is %s%s\n", + filter->dlist[FILTER_IN].alist ? "*" : "", + filter->dlist[FILTER_IN].name); + if (filter->dlist[FILTER_OUT].name) + vty_out(vty, + " Outgoing update network filter list is %s%s\n", + filter->dlist[FILTER_OUT].alist ? "*" : "", + filter->dlist[FILTER_OUT].name); + + /* filter-list. */ + if (filter->aslist[FILTER_IN].name) + vty_out(vty, + " Incoming update AS path filter list is %s%s\n", + filter->aslist[FILTER_IN].aslist ? "*" : "", + filter->aslist[FILTER_IN].name); + if (filter->aslist[FILTER_OUT].name) + vty_out(vty, + " Outgoing update AS path filter list is %s%s\n", + filter->aslist[FILTER_OUT].aslist ? "*" : "", + filter->aslist[FILTER_OUT].name); + + /* route-map. */ + if (filter->map[RMAP_IN].name) + vty_out(vty, + " Route map for incoming advertisements is %s%s\n", + filter->map[RMAP_IN].map ? "*" : "", + filter->map[RMAP_IN].name); + if (filter->map[RMAP_OUT].name) + vty_out(vty, + " Route map for outgoing advertisements is %s%s\n", + filter->map[RMAP_OUT].map ? "*" : "", + filter->map[RMAP_OUT].name); + + /* ebgp-requires-policy (inbound) */ + if (CHECK_FLAG(p->bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY) + && !bgp_inbound_policy_exists(p, filter)) + vty_out(vty, + " Inbound updates discarded due to missing policy\n"); + + /* ebgp-requires-policy (outbound) */ + if (CHECK_FLAG(p->bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY) + && !bgp_outbound_policy_exists(p, filter)) + vty_out(vty, + " Outbound updates discarded due to missing policy\n"); + + /* unsuppress-map */ + if (filter->usmap.name) + vty_out(vty, + " Route map for selective unsuppress is %s%s\n", + filter->usmap.map ? "*" : "", + filter->usmap.name); + + /* advertise-map */ + if (filter->advmap.aname && filter->advmap.cname) + vty_out(vty, + " Condition %s, Condition-map %s%s, Advertise-map %s%s, status: %s\n", + filter->advmap.condition ? "EXIST" + : "NON_EXIST", + filter->advmap.cmap ? "*" : "", + filter->advmap.cname, + filter->advmap.amap ? "*" : "", + filter->advmap.aname, + filter->advmap.update_type == + UPDATE_TYPE_ADVERTISE + ? "Advertise" + : "Withdraw"); + + /* Receive prefix count */ + vty_out(vty, " %u accepted prefixes\n", + p->pcount[afi][safi]); + + /* maximum-prefix-out */ + if (CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_OUT)) + vty_out(vty, + " Maximum allowed prefixes sent %u\n", + p->pmax_out[afi][safi]); + + /* Maximum prefix */ + if (CHECK_FLAG(p->af_flags[afi][safi], PEER_FLAG_MAX_PREFIX)) { + vty_out(vty, + " Maximum prefixes allowed %u%s\n", + p->pmax[afi][safi], + CHECK_FLAG(p->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING) + ? " (warning-only)" + : ""); + vty_out(vty, " Threshold for warning message %d%%", + p->pmax_threshold[afi][safi]); + if (p->pmax_restart[afi][safi]) + vty_out(vty, ", restart interval %d min", + p->pmax_restart[afi][safi]); + vty_out(vty, "\n"); + } + + vty_out(vty, "\n"); + } +} + +static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json, + json_object *json) +{ + struct bgp *bgp; + char timebuf[BGP_UPTIME_LEN]; + char dn_flag[2]; + afi_t afi; + safi_t safi; + uint16_t i; + uint8_t *msg; + json_object *json_neigh = NULL; + time_t epoch_tbuf; + uint32_t sync_tcp_mss; + + bgp = p->bgp; + + if (use_json) + json_neigh = json_object_new_object(); + + memset(dn_flag, '\0', sizeof(dn_flag)); + if (!p->conf_if && peer_dynamic_neighbor(p)) + dn_flag[0] = '*'; + + if (!use_json) { + if (p->conf_if) /* Configured interface name. */ + vty_out(vty, "BGP neighbor on %s: %pSU, ", p->conf_if, + &p->connection->su); + else /* Configured IP address. */ + vty_out(vty, "BGP neighbor is %s%s, ", dn_flag, + p->host); + } + + if (use_json) { + if (p->conf_if && BGP_CONNECTION_SU_UNSPEC(p->connection)) + json_object_string_add(json_neigh, "bgpNeighborAddr", + "none"); + else if (p->conf_if && !BGP_CONNECTION_SU_UNSPEC(p->connection)) + json_object_string_addf(json_neigh, "bgpNeighborAddr", + "%pSU", &p->connection->su); + + asn_asn2json(json_neigh, "remoteAs", p->as, bgp->asnotation); + + if (p->change_local_as) + asn_asn2json(json_neigh, "localAs", p->change_local_as, + bgp->asnotation); + else + asn_asn2json(json_neigh, "localAs", p->local_as, + bgp->asnotation); + + if (CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND)) + json_object_boolean_true_add(json_neigh, + "localAsNoPrepend"); + + if (CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS)) + json_object_boolean_true_add(json_neigh, + "localAsReplaceAs"); + } else { + if ((p->as_type == AS_SPECIFIED) || + (p->as_type == AS_EXTERNAL) || + (p->as_type == AS_INTERNAL)) { + vty_out(vty, "remote AS "); + vty_out(vty, ASN_FORMAT(bgp->asnotation), &p->as); + vty_out(vty, ", "); + } else + vty_out(vty, "remote AS Unspecified, "); + vty_out(vty, "local AS "); + vty_out(vty, ASN_FORMAT(bgp->asnotation), + p->change_local_as ? &p->change_local_as + : &p->local_as); + vty_out(vty, "%s%s, ", + CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND) + ? " no-prepend" + : "", + CHECK_FLAG(p->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS) + ? " replace-as" + : ""); + } + /* peer type internal or confed-internal */ + if ((p->as == p->local_as) || (p->as_type == AS_INTERNAL)) { + if (use_json) { + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) + json_object_boolean_true_add( + json_neigh, "nbrConfedInternalLink"); + else + json_object_boolean_true_add(json_neigh, + "nbrInternalLink"); + } else { + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) + vty_out(vty, "confed-internal link\n"); + else + vty_out(vty, "internal link\n"); + } + /* peer type external or confed-external */ + } else if (p->as || (p->as_type == AS_EXTERNAL)) { + if (use_json) { + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) + json_object_boolean_true_add( + json_neigh, "nbrConfedExternalLink"); + else + json_object_boolean_true_add(json_neigh, + "nbrExternalLink"); + } else { + if (bgp_confederation_peers_check(bgp, p->as)) + vty_out(vty, "confed-external link\n"); + else + vty_out(vty, "external link\n"); + } + } else { + if (use_json) + json_object_boolean_true_add(json_neigh, + "nbrUnspecifiedLink"); + else + vty_out(vty, "unspecified link\n"); + } + + /* Roles */ + if (use_json) { + json_object_string_add(json_neigh, "localRole", + bgp_get_name_by_role(p->local_role)); + json_object_string_add(json_neigh, "remoteRole", + bgp_get_name_by_role(p->remote_role)); + } else { + vty_out(vty, " Local Role: %s\n", + bgp_get_name_by_role(p->local_role)); + vty_out(vty, " Remote Role: %s\n", + bgp_get_name_by_role(p->remote_role)); + } + + + /* Description. */ + if (p->desc) { + if (use_json) + json_object_string_add(json_neigh, "nbrDesc", p->desc); + else + vty_out(vty, " Description: %s\n", p->desc); + } + + if (p->hostname) { + if (use_json) { + json_object_string_add(json_neigh, "hostname", + p->hostname); + + if (p->domainname) + json_object_string_add(json_neigh, "domainname", + p->domainname); + } else { + if (p->domainname && (p->domainname[0] != '\0')) + vty_out(vty, "Hostname: %s.%s\n", p->hostname, + p->domainname); + else + vty_out(vty, "Hostname: %s\n", p->hostname); + } + } else { + if (use_json) + json_object_string_add(json_neigh, "hostname", + "Unknown"); + } + + /* Peer-group */ + if (p->group) { + if (use_json) { + json_object_string_add(json_neigh, "peerGroup", + p->group->name); + + if (dn_flag[0]) { + struct prefix prefix, *range = NULL; + + if (sockunion2hostprefix(&p->connection->su, + &prefix)) + range = peer_group_lookup_dynamic_neighbor_range( + p->group, &prefix); + + if (range) { + json_object_string_addf( + json_neigh, + "peerSubnetRangeGroup", "%pFX", + range); + } + } + } else { + vty_out(vty, + " Member of peer-group %s for session parameters\n", + p->group->name); + + if (dn_flag[0]) { + struct prefix prefix, *range = NULL; + + if (sockunion2hostprefix(&p->connection->su, + &prefix)) + range = peer_group_lookup_dynamic_neighbor_range( + p->group, &prefix); + + if (range) { + vty_out(vty, + " Belongs to the subnet range group: %pFX\n", + range); + } + } + } + } + + if (use_json) { + /* Administrative shutdown. */ + if (CHECK_FLAG(p->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(p->bgp->flags, BGP_FLAG_SHUTDOWN)) + json_object_boolean_true_add(json_neigh, + "adminShutDown"); + + /* BGP Version. */ + json_object_int_add(json_neigh, "bgpVersion", 4); + json_object_string_addf(json_neigh, "remoteRouterId", "%pI4", + &p->remote_id); + json_object_string_addf(json_neigh, "localRouterId", "%pI4", + &bgp->router_id); + + /* Confederation */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) + && bgp_confederation_peers_check(bgp, p->as)) + json_object_boolean_true_add(json_neigh, + "nbrCommonAdmin"); + + /* Status. */ + json_object_string_add(json_neigh, "bgpState", + lookup_msg(bgp_status_msg, + p->connection->status, NULL)); + + if (peer_established(p->connection)) { + time_t uptime; + + uptime = monotime(NULL); + uptime -= p->uptime; + epoch_tbuf = time(NULL) - uptime; + + json_object_int_add(json_neigh, "bgpTimerUpMsec", + uptime * 1000); + json_object_string_add(json_neigh, "bgpTimerUpString", + peer_uptime(p->uptime, timebuf, + BGP_UPTIME_LEN, 0, + NULL)); + json_object_int_add(json_neigh, + "bgpTimerUpEstablishedEpoch", + epoch_tbuf); + } else if (p->connection->status == Active) { + if (CHECK_FLAG(p->flags, PEER_FLAG_PASSIVE)) + json_object_string_add(json_neigh, "bgpStateIs", + "passive"); + else if (CHECK_FLAG(p->sflags, PEER_STATUS_NSF_WAIT)) + json_object_string_add(json_neigh, "bgpStateIs", + "passiveNSF"); + } + + /* read timer */ + time_t uptime; + struct tm tm; + + uptime = monotime(NULL); + uptime -= p->readtime; + gmtime_r(&uptime, &tm); + + json_object_int_add(json_neigh, "bgpTimerLastRead", + (tm.tm_sec * 1000) + (tm.tm_min * 60000) + + (tm.tm_hour * 3600000)); + + uptime = monotime(NULL); + uptime -= p->last_write; + gmtime_r(&uptime, &tm); + + json_object_int_add(json_neigh, "bgpTimerLastWrite", + (tm.tm_sec * 1000) + (tm.tm_min * 60000) + + (tm.tm_hour * 3600000)); + + uptime = monotime(NULL); + uptime -= p->update_time; + gmtime_r(&uptime, &tm); + + json_object_int_add(json_neigh, "bgpInUpdateElapsedTimeMsecs", + (tm.tm_sec * 1000) + (tm.tm_min * 60000) + + (tm.tm_hour * 3600000)); + + /* Configured timer values. */ + json_object_int_add(json_neigh, + "bgpTimerConfiguredHoldTimeMsecs", + CHECK_FLAG(p->flags, PEER_FLAG_TIMER) + ? p->holdtime * 1000 + : bgp->default_holdtime * 1000); + json_object_int_add(json_neigh, + "bgpTimerConfiguredKeepAliveIntervalMsecs", + CHECK_FLAG(p->flags, PEER_FLAG_TIMER) + ? p->keepalive * 1000 + : bgp->default_keepalive * 1000); + json_object_int_add(json_neigh, "bgpTimerHoldTimeMsecs", + p->v_holdtime * 1000); + json_object_int_add(json_neigh, + "bgpTimerKeepAliveIntervalMsecs", + p->v_keepalive * 1000); + if (CHECK_FLAG(p->flags, PEER_FLAG_TIMER_DELAYOPEN)) { + json_object_int_add(json_neigh, + "bgpTimerDelayOpenTimeMsecs", + p->v_delayopen * 1000); + } + + /* Configured and Synced tcp-mss value for peer */ + sync_tcp_mss = sockopt_tcp_mss_get(p->connection->fd); + json_object_int_add(json_neigh, "bgpTcpMssConfigured", + p->tcp_mss); + json_object_int_add(json_neigh, "bgpTcpMssSynced", sync_tcp_mss); + + /* Extended Optional Parameters Length for BGP OPEN Message */ + if (BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(p)) + json_object_boolean_true_add( + json_neigh, "extendedOptionalParametersLength"); + else + json_object_boolean_false_add( + json_neigh, "extendedOptionalParametersLength"); + + /* Conditional advertisements */ + json_object_int_add( + json_neigh, + "bgpTimerConfiguredConditionalAdvertisementsSec", + bgp->condition_check_period); + if (event_is_scheduled(bgp->t_condition_check)) + json_object_int_add( + json_neigh, + "bgpTimerUntilConditionalAdvertisementsSec", + event_timer_remain_second( + bgp->t_condition_check)); + } else { + /* Administrative shutdown. */ + if (CHECK_FLAG(p->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(p->bgp->flags, BGP_FLAG_SHUTDOWN)) + vty_out(vty, " Administratively shut down\n"); + + /* BGP Version. */ + vty_out(vty, " BGP version 4"); + vty_out(vty, ", remote router ID %pI4", &p->remote_id); + vty_out(vty, ", local router ID %pI4\n", &bgp->router_id); + + /* Confederation */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION) + && bgp_confederation_peers_check(bgp, p->as)) + vty_out(vty, + " Neighbor under common administration\n"); + + /* Status. */ + vty_out(vty, " BGP state = %s", + lookup_msg(bgp_status_msg, p->connection->status, NULL)); + + if (peer_established(p->connection)) + vty_out(vty, ", up for %8s", + peer_uptime(p->uptime, timebuf, BGP_UPTIME_LEN, + 0, NULL)); + else if (p->connection->status == Active) { + if (CHECK_FLAG(p->flags, PEER_FLAG_PASSIVE)) + vty_out(vty, " (passive)"); + else if (CHECK_FLAG(p->sflags, PEER_STATUS_NSF_WAIT)) + vty_out(vty, " (NSF passive)"); + } + vty_out(vty, "\n"); + + /* read timer */ + vty_out(vty, " Last read %s", + peer_uptime(p->readtime, timebuf, BGP_UPTIME_LEN, 0, + NULL)); + vty_out(vty, ", Last write %s\n", + peer_uptime(p->last_write, timebuf, BGP_UPTIME_LEN, 0, + NULL)); + + /* Configured timer values. */ + vty_out(vty, + " Hold time is %d seconds, keepalive interval is %d seconds\n", + p->v_holdtime, p->v_keepalive); + vty_out(vty, " Configured hold time is %d seconds", + CHECK_FLAG(p->flags, PEER_FLAG_TIMER) + ? p->holdtime + : bgp->default_holdtime); + vty_out(vty, ", keepalive interval is %d seconds\n", + CHECK_FLAG(p->flags, PEER_FLAG_TIMER) + ? p->keepalive + : bgp->default_keepalive); + if (CHECK_FLAG(p->flags, PEER_FLAG_TIMER_DELAYOPEN)) + vty_out(vty, + " Configured DelayOpenTime is %d seconds\n", + p->delayopen); + + /* Configured and synced tcp-mss value for peer */ + sync_tcp_mss = sockopt_tcp_mss_get(p->connection->fd); + vty_out(vty, " Configured tcp-mss is %d", p->tcp_mss); + vty_out(vty, ", synced tcp-mss is %d\n", sync_tcp_mss); + + /* Extended Optional Parameters Length for BGP OPEN Message */ + if (BGP_OPEN_EXT_OPT_PARAMS_CAPABLE(p)) + vty_out(vty, + " Extended Optional Parameters Length is enabled\n"); + + /* Conditional advertisements */ + vty_out(vty, + " Configured conditional advertisements interval is %d seconds\n", + bgp->condition_check_period); + if (event_is_scheduled(bgp->t_condition_check)) + vty_out(vty, + " Time until conditional advertisements begin is %lu seconds\n", + event_timer_remain_second( + bgp->t_condition_check)); + } + /* Capability. */ + if (peer_established(p->connection) && + (p->cap || peer_afc_advertised(p) || peer_afc_received(p))) { + if (use_json) { + json_object *json_cap = NULL; + + json_cap = json_object_new_object(); + + /* AS4 */ + if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) { + if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) + json_object_string_add( + json_cap, "4byteAs", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) + json_object_string_add(json_cap, + "4byteAs", + "advertised"); + else if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) + json_object_string_add(json_cap, + "4byteAs", + "received"); + } + + /* Extended Message Support */ + if (CHECK_FLAG(p->cap, PEER_CAP_EXTENDED_MESSAGE_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_EXTENDED_MESSAGE_RCV)) + json_object_string_add(json_cap, + "extendedMessage", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_EXTENDED_MESSAGE_ADV)) + json_object_string_add(json_cap, + "extendedMessage", + "advertised"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_EXTENDED_MESSAGE_RCV)) + json_object_string_add(json_cap, + "extendedMessage", + "received"); + + /* AddPath */ + if (CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_ADV)) { + json_object *json_add = NULL; + const char *print_store; + + json_add = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + json_object *json_sub = NULL; + json_sub = json_object_new_object(); + print_store = get_afi_safi_str( + afi, safi, true); + + if (CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV)) { + json_object_boolean_add( + json_sub, + "txAdvertisedAndReceived", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_ADV) && + CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_RCV)); + + json_object_boolean_add( + json_sub, "txAdvertised", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_ADV)); + + json_object_boolean_add( + json_sub, "txReceived", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_RCV)); + } + + if (CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV)) { + json_object_boolean_add( + json_sub, + "rxAdvertisedAndReceived", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_ADV) && + CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_RCV)); + + json_object_boolean_add( + json_sub, "rxAdvertised", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_ADV)); + + json_object_boolean_add( + json_sub, "rxReceived", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_RCV)); + } + + if (CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV)) + json_object_object_add( + json_add, print_store, + json_sub); + else + json_object_free(json_sub); + } + + json_object_object_add(json_cap, "addPath", + json_add); + } + + /* Paths-Limit */ + if (CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_ADV)) { + json_object *json_add = NULL; + const char *print_store; + + json_add = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + json_object *json_sub = NULL; + + json_sub = json_object_new_object(); + print_store = get_afi_safi_str(afi, safi, + true); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) && + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + json_object_boolean_true_add( + json_sub, + "advertisedAndReceived"); + json_object_int_add( + json_sub, + "advertisedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .send); + json_object_int_add( + json_sub, + "receivedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .receive); + } else if (CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_ADV)) { + json_object_boolean_true_add( + json_sub, + "advertised"); + json_object_int_add( + json_sub, + "advertisedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .send); + } else if (CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + json_object_boolean_true_add( + json_sub, + "received"); + json_object_int_add( + json_sub, + "receivedPathsLimit", + p->addpath_paths_limit + [afi][safi] + .receive); + } + } + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) + json_object_object_add(json_add, + print_store, + json_sub); + else + json_object_free(json_sub); + } + + json_object_object_add(json_cap, "pathsLimit", + json_add); + } + + /* Dynamic */ + if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { + if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV)) + json_object_string_add( + json_cap, "dynamic", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_DYNAMIC_ADV)) + json_object_string_add(json_cap, + "dynamic", + "advertised"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_DYNAMIC_RCV)) + json_object_string_add(json_cap, + "dynamic", + "received"); + } + + /* Role */ + if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) { + if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV)) + json_object_string_add( + json_cap, "role", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) + json_object_string_add(json_cap, "role", + "advertised"); + else if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV)) + json_object_string_add(json_cap, "role", + "received"); + } + + /* Extended nexthop */ + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) { + json_object *json_nxt = NULL; + const char *print_store; + + + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) + json_object_string_add( + json_cap, "extendedNexthop", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) + json_object_string_add( + json_cap, "extendedNexthop", + "advertised"); + else if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) + json_object_string_add( + json_cap, "extendedNexthop", + "received"); + + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) { + json_nxt = json_object_new_object(); + + for (safi = SAFI_UNICAST; + safi < SAFI_MAX; safi++) { + if (CHECK_FLAG( + p->af_cap[AFI_IP] + [safi], + PEER_CAP_ENHE_AF_RCV)) { + print_store = + get_afi_safi_str( + AFI_IP, + safi, + true); + json_object_string_add( + json_nxt, + print_store, + "recieved"); /* misspelled for compatibility */ + } + } + json_object_object_add( + json_cap, + "extendedNexthopFamililesByPeer", + json_nxt); + } + } + + /* Long-lived Graceful Restart */ + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_LLGR_ADV)) { + json_object *json_llgr = NULL; + const char *afi_safi_str; + + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV)) + json_object_string_add( + json_cap, + "longLivedGracefulRestart", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_ADV)) + json_object_string_add( + json_cap, + "longLivedGracefulRestart", + "advertised"); + else if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV)) + json_object_string_add( + json_cap, + "longLivedGracefulRestart", + "received"); + + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV)) { + json_llgr = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ENHE_AF_RCV)) { + afi_safi_str = + get_afi_safi_str( + afi, + safi, + true); + json_object_string_add( + json_llgr, + afi_safi_str, + "received"); + } + } + json_object_object_add( + json_cap, + "longLivedGracefulRestartByPeer", + json_llgr); + } + } + + /* Route Refresh */ + if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) || + CHECK_FLAG(p->cap, PEER_CAP_REFRESH_RCV)) { + if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_REFRESH_RCV)) + json_object_string_add(json_cap, + "routeRefresh", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_REFRESH_ADV)) + json_object_string_add(json_cap, + "routeRefresh", + "advertised"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_REFRESH_RCV)) + json_object_string_add(json_cap, + "routeRefresh", + "received"); + } + + /* Enhanced Route Refresh */ + if (CHECK_FLAG(p->cap, PEER_CAP_ENHANCED_RR_ADV) || + CHECK_FLAG(p->cap, PEER_CAP_ENHANCED_RR_RCV)) { + if (CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_ADV) && + CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_RCV)) + json_object_string_add( + json_cap, + "enhancedRouteRefresh", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_ADV)) + json_object_string_add( + json_cap, + "enhancedRouteRefresh", + "advertised"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_RCV)) + json_object_string_add( + json_cap, + "enhancedRouteRefresh", + "received"); + } + + /* Multiprotocol Extensions */ + json_object *json_multi = NULL; + + json_multi = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) { + if (p->afc_adv[afi][safi] || + p->afc_recv[afi][safi]) { + json_object *json_exten = NULL; + json_exten = json_object_new_object(); + + if (p->afc_adv[afi][safi] && + p->afc_recv[afi][safi]) + json_object_boolean_true_add( + json_exten, + "advertisedAndReceived"); + else if (p->afc_adv[afi][safi]) + json_object_boolean_true_add( + json_exten, + "advertised"); + else if (p->afc_recv[afi][safi]) + json_object_boolean_true_add( + json_exten, "received"); + + json_object_object_add( + json_multi, + get_afi_safi_str(afi, safi, + true), + json_exten); + } + } + json_object_object_add(json_cap, + "multiprotocolExtensions", + json_multi); + + /* Hostname capabilities */ + json_object *json_hname = NULL; + + json_hname = json_object_new_object(); + + if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_ADV)) { + json_object_string_add( + json_hname, "advHostName", + bgp->peer_self->hostname + ? bgp->peer_self->hostname + : "n/a"); + json_object_string_add( + json_hname, "advDomainName", + bgp->peer_self->domainname + ? bgp->peer_self->domainname + : "n/a"); + } + + + if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_RCV)) { + json_object_string_add( + json_hname, "rcvHostName", + p->hostname ? p->hostname : "n/a"); + json_object_string_add( + json_hname, "rcvDomainName", + p->domainname ? p->domainname : "n/a"); + } + + json_object_object_add(json_cap, "hostName", + json_hname); + + /* Software Version capability */ + json_object *json_soft_version = NULL; + + json_soft_version = json_object_new_object(); + + if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_ADV)) + json_object_string_add( + json_soft_version, + "advertisedSoftwareVersion", + cmd_software_version_get()); + + if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_RCV)) + json_object_string_add( + json_soft_version, + "receivedSoftwareVersion", + p->soft_version ? p->soft_version + : "n/a"); + + json_object_object_add(json_cap, "softwareVersion", + json_soft_version); + + /* Graceful Restart */ + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) { + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV) && + CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) + json_object_string_add( + json_cap, "gracefulRestart", + "advertisedAndReceived"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_RESTART_ADV)) + json_object_string_add( + json_cap, + "gracefulRestartCapability", + "advertised"); + else if (CHECK_FLAG(p->cap, + PEER_CAP_RESTART_RCV)) + json_object_string_add( + json_cap, + "gracefulRestartCapability", + "received"); + + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { + int restart_af_count = 0; + json_object *json_restart = NULL; + json_restart = json_object_new_object(); + + json_object_int_add( + json_cap, + "gracefulRestartRemoteTimerMsecs", + p->v_gr_restart * 1000); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_RESTART_AF_RCV)) { + json_object *json_sub = + NULL; + json_sub = + json_object_new_object(); + + if (CHECK_FLAG( + p->af_cap + [afi] + [safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV)) + json_object_boolean_true_add( + json_sub, + "preserved"); + restart_af_count++; + json_object_object_add( + json_restart, + get_afi_safi_str( + afi, + safi, + true), + json_sub); + } + } + if (!restart_af_count) { + json_object_string_add( + json_cap, + "addressFamiliesByPeer", + "none"); + json_object_free(json_restart); + } else + json_object_object_add( + json_cap, + "addressFamiliesByPeer", + json_restart); + } + } + json_object_object_add( + json_neigh, "neighborCapabilities", json_cap); + } else { + vty_out(vty, " Neighbor capabilities:\n"); + + /* AS4 */ + if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) { + vty_out(vty, " 4 Byte AS:"); + if (CHECK_FLAG(p->cap, PEER_CAP_AS4_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_AS4_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_AS4_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Extended Message Support */ + if (CHECK_FLAG(p->cap, PEER_CAP_EXTENDED_MESSAGE_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_EXTENDED_MESSAGE_ADV)) { + vty_out(vty, " Extended Message:"); + if (CHECK_FLAG(p->cap, + PEER_CAP_EXTENDED_MESSAGE_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, + PEER_CAP_EXTENDED_MESSAGE_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG( + p->cap, + PEER_CAP_EXTENDED_MESSAGE_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* AddPath */ + if (CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ADDPATH_ADV)) { + vty_out(vty, " AddPath:\n"); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_TX_RCV)) { + vty_out(vty, " %s: TX ", + get_afi_safi_str( + afi, safi, + false)); + + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_ADV)) + vty_out(vty, + "advertised"); + + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_RCV)) + vty_out(vty, + "%sreceived", + CHECK_FLAG( + p->af_cap + [afi] + [safi], + PEER_CAP_ADDPATH_AF_TX_ADV) + ? " and " + : ""); + + vty_out(vty, "\n"); + } + + if (CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_ADV) || + CHECK_FLAG( + p->af_cap[afi][safi], + PEER_CAP_ADDPATH_AF_RX_RCV)) { + vty_out(vty, " %s: RX ", + get_afi_safi_str( + afi, safi, + false)); + + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_ADV)) + vty_out(vty, + "advertised"); + + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_RCV)) + vty_out(vty, + "%sreceived", + CHECK_FLAG( + p->af_cap + [afi] + [safi], + PEER_CAP_ADDPATH_AF_RX_ADV) + ? " and " + : ""); + + vty_out(vty, "\n"); + } + } + } + + /* Paths-Limit */ + if (CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_PATHS_LIMIT_ADV)) { + vty_out(vty, " Paths-Limit:\n"); + + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) || + CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) { + vty_out(vty, " %s: ", + get_afi_safi_str(afi, + safi, + false)); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_ADV)) + vty_out(vty, + "advertised (%u)", + p->addpath_paths_limit + [afi][safi] + .send); + + if (CHECK_FLAG(p->af_cap[afi][safi], + PEER_CAP_PATHS_LIMIT_AF_RCV)) + vty_out(vty, + "%sreceived (%u)", + CHECK_FLAG(p->af_cap[afi] + [safi], + PEER_CAP_PATHS_LIMIT_AF_ADV) + ? " and " + : "", + p->addpath_paths_limit + [afi][safi] + .receive); + + vty_out(vty, "\n"); + } + } + } + + /* Dynamic */ + if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) { + vty_out(vty, " Dynamic:"); + if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_DYNAMIC_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_DYNAMIC_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Role */ + if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) { + vty_out(vty, " Role:"); + if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_ROLE_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_ROLE_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Extended nexthop */ + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) { + vty_out(vty, " Extended nexthop:"); + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_ENHE_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + + if (CHECK_FLAG(p->cap, PEER_CAP_ENHE_RCV)) { + vty_out(vty, + " Address families by peer:\n "); + for (safi = SAFI_UNICAST; + safi < SAFI_MAX; safi++) + if (CHECK_FLAG( + p->af_cap[AFI_IP] + [safi], + PEER_CAP_ENHE_AF_RCV)) + vty_out(vty, + " %s\n", + get_afi_safi_str( + AFI_IP, + safi, + false)); + } + } + + /* Long-lived Graceful Restart */ + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_LLGR_ADV)) { + vty_out(vty, + " Long-lived Graceful Restart:"); + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_LLGR_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + + if (CHECK_FLAG(p->cap, PEER_CAP_LLGR_RCV)) { + vty_out(vty, + " Address families by peer:\n"); + FOREACH_AFI_SAFI (afi, safi) + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_LLGR_AF_RCV)) + vty_out(vty, + " %s\n", + get_afi_safi_str( + afi, + safi, + false)); + } + } + + /* Route Refresh */ + if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV) || + CHECK_FLAG(p->cap, PEER_CAP_REFRESH_RCV)) { + vty_out(vty, " Route refresh:"); + if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_REFRESH_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_REFRESH_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Enhanced Route Refresh */ + if (CHECK_FLAG(p->cap, PEER_CAP_ENHANCED_RR_ADV) || + CHECK_FLAG(p->cap, PEER_CAP_ENHANCED_RR_RCV)) { + vty_out(vty, " Enhanced Route Refresh:"); + if (CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, + PEER_CAP_ENHANCED_RR_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_REFRESH_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Multiprotocol Extensions */ + FOREACH_AFI_SAFI (afi, safi) + if (p->afc_adv[afi][safi] || + p->afc_recv[afi][safi]) { + vty_out(vty, " Address Family %s:", + get_afi_safi_str(afi, safi, + false)); + if (p->afc_adv[afi][safi]) + vty_out(vty, " advertised"); + if (p->afc_recv[afi][safi]) + vty_out(vty, " %sreceived", + p->afc_adv[afi][safi] + ? "and " + : ""); + vty_out(vty, "\n"); + } + + /* Hostname capability */ + vty_out(vty, " Hostname Capability:"); + + if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_ADV)) { + vty_out(vty, + " advertised (name: %s,domain name: %s)", + bgp->peer_self->hostname + ? bgp->peer_self->hostname + : "n/a", + bgp->peer_self->domainname + ? bgp->peer_self->domainname + : "n/a"); + } else { + vty_out(vty, " not advertised"); + } + + if (CHECK_FLAG(p->cap, PEER_CAP_HOSTNAME_RCV)) { + vty_out(vty, + " received (name: %s,domain name: %s)", + p->hostname ? p->hostname : "n/a", + p->domainname ? p->domainname : "n/a"); + } else { + vty_out(vty, " not received"); + } + + vty_out(vty, "\n"); + + /* Software Version capability */ + vty_out(vty, " Version Capability:"); + + if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_ADV)) { + vty_out(vty, + " advertised software version (%s)", + cmd_software_version_get()); + } else + vty_out(vty, " not advertised"); + + if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_RCV)) { + vty_out(vty, " received software version (%s)", + p->soft_version ? p->soft_version + : "n/a"); + } else + vty_out(vty, " not received"); + + vty_out(vty, "\n"); + + /* Graceful Restart */ + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) || + CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) { + vty_out(vty, + " Graceful Restart Capability:"); + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) + vty_out(vty, " advertised"); + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) + vty_out(vty, " %sreceived", + CHECK_FLAG(p->cap, + PEER_CAP_RESTART_ADV) + ? "and " + : ""); + vty_out(vty, "\n"); + + if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { + int restart_af_count = 0; + + vty_out(vty, + " Remote Restart timer is %d seconds\n", + p->v_gr_restart); + vty_out(vty, + " Address families by peer:\n "); + + FOREACH_AFI_SAFI (afi, safi) + if (CHECK_FLAG( + p->af_cap[afi] + [safi], + PEER_CAP_RESTART_AF_RCV)) { + vty_out(vty, "%s%s(%s)", + restart_af_count + ? ", " + : "", + get_afi_safi_str( + afi, + safi, + false), + CHECK_FLAG( + p->af_cap + [afi] + [safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV) + ? "preserved" + : "not preserved"); + restart_af_count++; + } + if (!restart_af_count) + vty_out(vty, "none"); + vty_out(vty, "\n"); + } + } /* Graceful Restart */ + } + } + + /* graceful restart information */ + json_object *json_grace = NULL; + json_object *json_grace_send = NULL; + json_object *json_grace_recv = NULL; + int eor_send_af_count = 0; + int eor_receive_af_count = 0; + + if (use_json) { + json_grace = json_object_new_object(); + json_grace_send = json_object_new_object(); + json_grace_recv = json_object_new_object(); + + if ((peer_established(p->connection)) && + CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND)) { + json_object_boolean_true_add( + json_grace_send, + get_afi_safi_str(afi, safi, + true)); + eor_send_af_count++; + } + } + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + json_object_boolean_true_add( + json_grace_recv, + get_afi_safi_str(afi, safi, + true)); + eor_receive_af_count++; + } + } + } + json_object_object_add(json_grace, "endOfRibSend", + json_grace_send); + json_object_object_add(json_grace, "endOfRibRecv", + json_grace_recv); + + + if (p->connection->t_gr_restart) + json_object_int_add(json_grace, + "gracefulRestartTimerMsecs", + event_timer_remain_second( + p->connection->t_gr_restart) * + 1000); + + if (p->connection->t_gr_stale) + json_object_int_add(json_grace, + "gracefulStalepathTimerMsecs", + event_timer_remain_second( + p->connection->t_gr_stale) * + 1000); + /* more gr info in new format */ + BGP_SHOW_PEER_GR_CAPABILITY(vty, p, json_grace); + json_object_object_add(json_neigh, "gracefulRestartInfo", + json_grace); + } else { + vty_out(vty, " Graceful restart information:\n"); + if ((peer_established(p->connection)) && + CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV)) { + vty_out(vty, " End-of-RIB send: "); + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_EOR_SEND)) { + vty_out(vty, "%s%s", + eor_send_af_count ? ", " : "", + get_afi_safi_str(afi, safi, + false)); + eor_send_af_count++; + } + } + vty_out(vty, "\n"); + vty_out(vty, " End-of-RIB received: "); + FOREACH_AFI_SAFI (afi, safi) { + if (CHECK_FLAG(p->af_sflags[afi][safi], + PEER_STATUS_EOR_RECEIVED)) { + vty_out(vty, "%s%s", + eor_receive_af_count ? ", " + : "", + get_afi_safi_str(afi, safi, + false)); + eor_receive_af_count++; + } + } + vty_out(vty, "\n"); + } + + if (p->connection->t_gr_restart) + vty_out(vty, + " The remaining time of restart timer is %ld\n", + event_timer_remain_second( + p->connection->t_gr_restart)); + + if (p->connection->t_gr_stale) + vty_out(vty, + " The remaining time of stalepath timer is %ld\n", + event_timer_remain_second( + p->connection->t_gr_stale)); + + /* more gr info in new format */ + BGP_SHOW_PEER_GR_CAPABILITY(vty, p, NULL); + } + + if (use_json) { + json_object *json_stat = NULL; + json_stat = json_object_new_object(); + /* Packet counts. */ + + atomic_size_t outq_count, inq_count; + outq_count = atomic_load_explicit(&p->connection->obuf->count, + memory_order_relaxed); + inq_count = atomic_load_explicit(&p->connection->ibuf->count, + memory_order_relaxed); + + json_object_int_add(json_stat, "depthInq", + (unsigned long)inq_count); + json_object_int_add(json_stat, "depthOutq", + (unsigned long)outq_count); + json_object_int_add(json_stat, "opensSent", + atomic_load_explicit(&p->open_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "opensRecv", + atomic_load_explicit(&p->open_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "notificationsSent", + atomic_load_explicit(&p->notify_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "notificationsRecv", + atomic_load_explicit(&p->notify_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "updatesSent", + atomic_load_explicit(&p->update_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "updatesRecv", + atomic_load_explicit(&p->update_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "keepalivesSent", + atomic_load_explicit(&p->keepalive_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "keepalivesRecv", + atomic_load_explicit(&p->keepalive_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "routeRefreshSent", + atomic_load_explicit(&p->refresh_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "routeRefreshRecv", + atomic_load_explicit(&p->refresh_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "capabilitySent", + atomic_load_explicit(&p->dynamic_cap_out, + memory_order_relaxed)); + json_object_int_add(json_stat, "capabilityRecv", + atomic_load_explicit(&p->dynamic_cap_in, + memory_order_relaxed)); + json_object_int_add(json_stat, "totalSent", PEER_TOTAL_TX(p)); + json_object_int_add(json_stat, "totalRecv", PEER_TOTAL_RX(p)); + json_object_object_add(json_neigh, "messageStats", json_stat); + } else { + atomic_size_t outq_count, inq_count, open_out, open_in, + notify_out, notify_in, update_out, update_in, + keepalive_out, keepalive_in, refresh_out, refresh_in, + dynamic_cap_out, dynamic_cap_in; + outq_count = atomic_load_explicit(&p->connection->obuf->count, + memory_order_relaxed); + inq_count = atomic_load_explicit(&p->connection->ibuf->count, + memory_order_relaxed); + open_out = atomic_load_explicit(&p->open_out, + memory_order_relaxed); + open_in = + atomic_load_explicit(&p->open_in, memory_order_relaxed); + notify_out = atomic_load_explicit(&p->notify_out, + memory_order_relaxed); + notify_in = atomic_load_explicit(&p->notify_in, + memory_order_relaxed); + update_out = atomic_load_explicit(&p->update_out, + memory_order_relaxed); + update_in = atomic_load_explicit(&p->update_in, + memory_order_relaxed); + keepalive_out = atomic_load_explicit(&p->keepalive_out, + memory_order_relaxed); + keepalive_in = atomic_load_explicit(&p->keepalive_in, + memory_order_relaxed); + refresh_out = atomic_load_explicit(&p->refresh_out, + memory_order_relaxed); + refresh_in = atomic_load_explicit(&p->refresh_in, + memory_order_relaxed); + dynamic_cap_out = atomic_load_explicit(&p->dynamic_cap_out, + memory_order_relaxed); + dynamic_cap_in = atomic_load_explicit(&p->dynamic_cap_in, + memory_order_relaxed); + + /* Packet counts. */ + vty_out(vty, " Message statistics:\n"); + vty_out(vty, " Inq depth is %zu\n", inq_count); + vty_out(vty, " Outq depth is %zu\n", outq_count); + vty_out(vty, " Sent Rcvd\n"); + vty_out(vty, " Opens: %10zu %10zu\n", open_out, + open_in); + vty_out(vty, " Notifications: %10zu %10zu\n", notify_out, + notify_in); + vty_out(vty, " Updates: %10zu %10zu\n", update_out, + update_in); + vty_out(vty, " Keepalives: %10zu %10zu\n", keepalive_out, + keepalive_in); + vty_out(vty, " Route Refresh: %10zu %10zu\n", refresh_out, + refresh_in); + vty_out(vty, " Capability: %10zu %10zu\n", + dynamic_cap_out, dynamic_cap_in); + vty_out(vty, " Total: %10u %10u\n", + (uint32_t)PEER_TOTAL_TX(p), (uint32_t)PEER_TOTAL_RX(p)); + } + + if (use_json) { + /* advertisement-interval */ + json_object_int_add(json_neigh, + "minBtwnAdvertisementRunsTimerMsecs", + p->v_routeadv * 1000); + + /* Update-source. */ + if (p->update_if || p->update_source) { + if (p->update_if) + json_object_string_add(json_neigh, + "updateSource", + p->update_if); + else if (p->update_source) + json_object_string_addf(json_neigh, + "updateSource", "%pSU", + p->update_source); + } + } else { + /* advertisement-interval */ + vty_out(vty, + " Minimum time between advertisement runs is %d seconds\n", + p->v_routeadv); + + /* Update-source. */ + if (p->update_if || p->update_source) { + vty_out(vty, " Update source is "); + if (p->update_if) + vty_out(vty, "%s", p->update_if); + else if (p->update_source) + vty_out(vty, "%pSU", p->update_source); + vty_out(vty, "\n"); + } + + vty_out(vty, "\n"); + } + + /* Address Family Information */ + json_object *json_hold = NULL; + + if (use_json) + json_hold = json_object_new_object(); + + FOREACH_AFI_SAFI (afi, safi) + if (p->afc[afi][safi]) + bgp_show_peer_afi(vty, p, afi, safi, use_json, + json_hold); + + if (use_json) { + json_object_object_add(json_neigh, "addressFamilyInfo", + json_hold); + json_object_int_add(json_neigh, "connectionsEstablished", + p->established); + json_object_int_add(json_neigh, "connectionsDropped", + p->dropped); + } else + vty_out(vty, " Connections established %d; dropped %d\n", + p->established, p->dropped); + + if (!p->last_reset) { + if (use_json) + json_object_string_add(json_neigh, "lastReset", + "never"); + else + vty_out(vty, " Last reset never\n"); + } else { + if (use_json) { + time_t uptime; + struct tm tm; + + uptime = monotime(NULL); + uptime -= p->resettime; + gmtime_r(&uptime, &tm); + + json_object_int_add(json_neigh, "lastResetTimerMsecs", + (tm.tm_sec * 1000) + + (tm.tm_min * 60000) + + (tm.tm_hour * 3600000)); + bgp_show_peer_reset(NULL, p, json_neigh, true); + } else { + vty_out(vty, " Last reset %s, ", + peer_uptime(p->resettime, timebuf, + BGP_UPTIME_LEN, 0, NULL)); + + bgp_show_peer_reset(vty, p, NULL, false); + if (p->last_reset_cause) { + msg = p->last_reset_cause->data; + vty_out(vty, + " Message received that caused BGP to send a NOTIFICATION:\n "); + for (i = 1; i <= p->last_reset_cause->size; + i++) { + vty_out(vty, "%02X", *msg++); + + if (i != p->last_reset_cause->size) { + if (i % 16 == 0) { + vty_out(vty, "\n "); + } else if (i % 4 == 0) { + vty_out(vty, " "); + } + } + } + vty_out(vty, "\n"); + } + } + } + + if (CHECK_FLAG(p->sflags, PEER_STATUS_PREFIX_OVERFLOW)) { + if (use_json) + json_object_boolean_true_add(json_neigh, + "prefixesConfigExceedMax"); + else + vty_out(vty, + " Peer had exceeded the max. no. of prefixes configured.\n"); + + if (p->connection->t_pmax_restart) { + if (use_json) { + json_object_boolean_true_add( + json_neigh, "reducePrefixNumFrom"); + json_object_int_add(json_neigh, + "restartInTimerMsec", + event_timer_remain_second( + p->connection + ->t_pmax_restart) * + 1000); + } else + vty_out(vty, + " Reduce the no. of prefix from %s, will restart in %ld seconds\n", + p->host, + event_timer_remain_second( + p->connection->t_pmax_restart)); + } else { + if (use_json) + json_object_boolean_true_add( + json_neigh, + "reducePrefixNumAndClearIpBgp"); + else + vty_out(vty, + " Reduce the no. of prefix and clear ip bgp %s to restore peering\n", + p->host); + } + } + + /* EBGP Multihop and GTSM */ + if (p->sort != BGP_PEER_IBGP) { + if (use_json) { + if (p->gtsm_hops > BGP_GTSM_HOPS_DISABLED) + json_object_int_add(json_neigh, + "externalBgpNbrMaxHopsAway", + p->gtsm_hops); + else + json_object_int_add(json_neigh, + "externalBgpNbrMaxHopsAway", + p->ttl); + } else { + if (p->gtsm_hops > BGP_GTSM_HOPS_DISABLED) + vty_out(vty, + " External BGP neighbor may be up to %d hops away.\n", + p->gtsm_hops); + else + vty_out(vty, + " External BGP neighbor may be up to %d hops away.\n", + p->ttl); + } + } else { + if (use_json) { + if (p->gtsm_hops > BGP_GTSM_HOPS_DISABLED) + json_object_int_add(json_neigh, + "internalBgpNbrMaxHopsAway", + p->gtsm_hops); + else + json_object_int_add(json_neigh, + "internalBgpNbrMaxHopsAway", + p->ttl); + } else { + if (p->gtsm_hops > BGP_GTSM_HOPS_DISABLED) + vty_out(vty, + " Internal BGP neighbor may be up to %d hops away.\n", + p->gtsm_hops); + else + vty_out(vty, + " Internal BGP neighbor may be up to %d hops away.\n", + p->ttl); + } + } + + /* Local address. */ + if (p->su_local) { + if (use_json) { + json_object_string_addf(json_neigh, "hostLocal", "%pSU", + p->su_local); + json_object_int_add(json_neigh, "portLocal", + ntohs(p->su_local->sin.sin_port)); + } else + vty_out(vty, "Local host: %pSU, Local port: %d\n", + p->su_local, ntohs(p->su_local->sin.sin_port)); + } else { + if (use_json) { + json_object_string_add(json_neigh, "hostLocal", + "Unknown"); + json_object_int_add(json_neigh, "portLocal", -1); + } + } + + /* Remote address. */ + if (p->su_remote) { + if (use_json) { + json_object_string_addf(json_neigh, "hostForeign", + "%pSU", p->su_remote); + json_object_int_add(json_neigh, "portForeign", + ntohs(p->su_remote->sin.sin_port)); + } else + vty_out(vty, "Foreign host: %pSU, Foreign port: %d\n", + p->su_remote, + ntohs(p->su_remote->sin.sin_port)); + } else { + if (use_json) { + json_object_string_add(json_neigh, "hostForeign", + "Unknown"); + json_object_int_add(json_neigh, "portForeign", -1); + } + } + + /* Nexthop display. */ + if (p->su_local) { + if (use_json) { + json_object_string_addf(json_neigh, "nexthop", "%pI4", + &p->nexthop.v4); + json_object_string_addf(json_neigh, "nexthopGlobal", + "%pI6", &p->nexthop.v6_global); + json_object_string_addf(json_neigh, "nexthopLocal", + "%pI6", &p->nexthop.v6_local); + if (p->shared_network) + json_object_string_add(json_neigh, + "bgpConnection", + "sharedNetwork"); + else + json_object_string_add(json_neigh, + "bgpConnection", + "nonSharedNetwork"); + } else { + vty_out(vty, "Nexthop: %pI4\n", &p->nexthop.v4); + vty_out(vty, "Nexthop global: %pI6\n", + &p->nexthop.v6_global); + vty_out(vty, "Nexthop local: %pI6\n", + &p->nexthop.v6_local); + vty_out(vty, "BGP connection: %s\n", + p->shared_network ? "shared network" + : "non shared network"); + } + } else { + if (use_json) { + json_object_string_add(json_neigh, "nexthop", + "Unknown"); + json_object_string_add(json_neigh, "nexthopGlobal", + "Unknown"); + json_object_string_add(json_neigh, "nexthopLocal", + "Unknown"); + json_object_string_add(json_neigh, "bgpConnection", + "Unknown"); + } + } + + /* Timer information. */ + if (use_json) { + json_object_int_add(json_neigh, "connectRetryTimer", + p->v_connect); + if (peer_established(p->connection)) { + json_object_int_add(json_neigh, "estimatedRttInMsecs", + p->rtt); + if (CHECK_FLAG(p->flags, PEER_FLAG_RTT_SHUTDOWN)) { + json_object_int_add(json_neigh, + "shutdownRttInMsecs", + p->rtt_expected); + json_object_int_add(json_neigh, + "shutdownRttAfterCount", + p->rtt_keepalive_rcv); + } + } + if (p->connection->t_start) + json_object_int_add(json_neigh, + "nextStartTimerDueInMsecs", + event_timer_remain_second( + p->connection->t_start) * + 1000); + if (p->connection->t_connect) + json_object_int_add(json_neigh, + "nextConnectTimerDueInMsecs", + event_timer_remain_second( + p->connection->t_connect) * + 1000); + if (p->connection->t_routeadv) { + json_object_int_add(json_neigh, "mraiInterval", + p->v_routeadv); + json_object_int_add(json_neigh, "mraiTimerExpireInMsecs", + event_timer_remain_second( + p->connection->t_routeadv) * + 1000); + } + if (p->password) + json_object_int_add(json_neigh, "authenticationEnabled", + 1); + + if (p->connection->t_read) + json_object_string_add(json_neigh, "readThread", "on"); + else + json_object_string_add(json_neigh, "readThread", "off"); + + if (CHECK_FLAG(p->connection->thread_flags, + PEER_THREAD_WRITES_ON)) + json_object_string_add(json_neigh, "writeThread", "on"); + else + json_object_string_add(json_neigh, "writeThread", + "off"); + } else { + vty_out(vty, "BGP Connect Retry Timer in Seconds: %d\n", + p->v_connect); + if (peer_established(p->connection)) { + vty_out(vty, "Estimated round trip time: %d ms\n", + p->rtt); + if (CHECK_FLAG(p->flags, PEER_FLAG_RTT_SHUTDOWN)) + vty_out(vty, + "Shutdown when RTT > %dms, count > %u\n", + p->rtt_expected, p->rtt_keepalive_rcv); + } + if (p->connection->t_start) + vty_out(vty, "Next start timer due in %ld seconds\n", + event_timer_remain_second( + p->connection->t_start)); + if (p->connection->t_connect) + vty_out(vty, "Next connect timer due in %ld seconds\n", + event_timer_remain_second( + p->connection->t_connect)); + if (p->connection->t_routeadv) + vty_out(vty, + "MRAI (interval %u) timer expires in %ld seconds\n", + p->v_routeadv, + event_timer_remain_second( + p->connection->t_routeadv)); + if (p->password) + vty_out(vty, "Peer Authentication Enabled\n"); + + vty_out(vty, "Read thread: %s Write thread: %s FD used: %d\n", + p->connection->t_read ? "on" : "off", + CHECK_FLAG(p->connection->thread_flags, + PEER_THREAD_WRITES_ON) + ? "on" + : "off", + p->connection->fd); + } + + if (p->notify.code == BGP_NOTIFY_OPEN_ERR + && p->notify.subcode == BGP_NOTIFY_OPEN_UNSUP_CAPBL) + bgp_capability_vty_out(vty, p, use_json, json_neigh); + + if (!use_json) + vty_out(vty, "\n"); + + /* BFD information. */ + if (p->bfd_config) + bgp_bfd_show_info(vty, p, json_neigh); + + if (use_json) { + if (p->conf_if) /* Configured interface name. */ + json_object_object_add(json, p->conf_if, json_neigh); + else /* Configured IP address. */ + json_object_object_add(json, p->host, json_neigh); + } +} + +static int bgp_show_neighbor_graceful_restart(struct vty *vty, struct bgp *bgp, + enum show_type type, + union sockunion *su, + const char *conf_if, afi_t afi, + json_object *json) +{ + struct listnode *node, *nnode; + struct peer *peer; + bool found = false; + safi_t safi = SAFI_UNICAST; + json_object *json_neighbor = NULL; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if ((peer->afc[afi][safi]) == 0) + continue; + + if (json) + json_neighbor = json_object_new_object(); + + if (type == show_all) { + bgp_show_peer_gr_status(vty, peer, json_neighbor); + + if (json) + json_object_object_add(json, peer->host, + json_neighbor); + + } else if (type == show_peer) { + if (conf_if) { + if ((peer->conf_if + && !strcmp(peer->conf_if, conf_if)) + || (peer->hostname + && !strcmp(peer->hostname, conf_if))) { + found = true; + bgp_show_peer_gr_status(vty, peer, + json_neighbor); + } + } else { + if (sockunion_same(&peer->connection->su, su)) { + found = true; + bgp_show_peer_gr_status(vty, peer, + json_neighbor); + } + } + if (json) { + if (found) + json_object_object_add(json, peer->host, + json_neighbor); + else + json_object_free(json_neighbor); + } + } + + if (found) + break; + } + + if (type == show_peer && !found) { + if (json) + json_object_boolean_true_add(json, "bgpNoSuchNeighbor"); + else + vty_out(vty, "%% No such neighbor\n"); + } + + if (!json) + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +static int bgp_show_neighbor(struct vty *vty, struct bgp *bgp, + enum show_type type, union sockunion *su, + const char *conf_if, bool use_json, + json_object *json) +{ + struct listnode *node, *nnode; + struct peer *peer; + int find = 0; + bool nbr_output = false; + afi_t afi = AFI_MAX; + safi_t safi = SAFI_MAX; + + if (type == show_ipv4_peer || type == show_ipv4_all) { + afi = AFI_IP; + } else if (type == show_ipv6_peer || type == show_ipv6_all) { + afi = AFI_IP6; + } + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + switch (type) { + case show_all: + bgp_show_peer(vty, peer, use_json, json); + nbr_output = true; + break; + case show_peer: + if (conf_if) { + if ((peer->conf_if + && !strcmp(peer->conf_if, conf_if)) + || (peer->hostname + && !strcmp(peer->hostname, conf_if))) { + find = 1; + bgp_show_peer(vty, peer, use_json, + json); + } + } else { + if (sockunion_same(&peer->connection->su, su)) { + find = 1; + bgp_show_peer(vty, peer, use_json, + json); + } + } + break; + case show_ipv4_peer: + case show_ipv6_peer: + FOREACH_SAFI (safi) { + if (peer->afc[afi][safi]) { + if (conf_if) { + if ((peer->conf_if + && !strcmp(peer->conf_if, conf_if)) + || (peer->hostname + && !strcmp(peer->hostname, conf_if))) { + find = 1; + bgp_show_peer(vty, peer, use_json, + json); + break; + } + } else { + if (sockunion_same(&peer->connection + ->su, + su)) { + find = 1; + bgp_show_peer(vty, peer, use_json, + json); + break; + } + } + } + } + break; + case show_ipv4_all: + case show_ipv6_all: + FOREACH_SAFI (safi) { + if (peer->afc[afi][safi]) { + bgp_show_peer(vty, peer, use_json, json); + nbr_output = true; + break; + } + } + break; + } + } + + if ((type == show_peer || type == show_ipv4_peer || + type == show_ipv6_peer) && !find) { + if (use_json) + json_object_boolean_true_add(json, "bgpNoSuchNeighbor"); + else + vty_out(vty, "%% No such neighbor in this view/vrf\n"); + } + + if (type != show_peer && type != show_ipv4_peer && + type != show_ipv6_peer && !nbr_output && !use_json) + vty_out(vty, "%% No BGP neighbors found\n"); + + if (use_json) { + vty_out(vty, "%s\n", json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + } else { + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +static void bgp_show_neighbor_graceful_restart_vty(struct vty *vty, + enum show_type type, + const char *ip_str, + afi_t afi, json_object *json) +{ + + int ret; + struct bgp *bgp; + union sockunion su; + + bgp = bgp_get_default(); + + if (!bgp) + return; + + if (!json) + bgp_show_global_graceful_restart_mode_vty(vty, bgp); + + if (ip_str) { + ret = str2sockunion(ip_str, &su); + if (ret < 0) + bgp_show_neighbor_graceful_restart(vty, bgp, type, NULL, + ip_str, afi, json); + else + bgp_show_neighbor_graceful_restart(vty, bgp, type, &su, + NULL, afi, json); + } else + bgp_show_neighbor_graceful_restart(vty, bgp, type, NULL, NULL, + afi, json); +} + +static void bgp_show_all_instances_neighbors_vty(struct vty *vty, + enum show_type type, + const char *ip_str, + bool use_json) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + union sockunion su; + json_object *json = NULL; + int ret, is_first = 1; + bool nbr_output = false; + + if (use_json) + vty_out(vty, "{\n"); + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + nbr_output = true; + if (use_json) { + if (!(json = json_object_new_object())) { + flog_err( + EC_BGP_JSON_MEM_ERROR, + "Unable to allocate memory for JSON object"); + vty_out(vty, + "{\"error\": {\"message:\": \"Unable to allocate memory for JSON object\"}}}\n"); + return; + } + + json_object_int_add(json, "vrfId", + (bgp->vrf_id == VRF_UNKNOWN) + ? -1 + : (int64_t)bgp->vrf_id); + json_object_string_add( + json, "vrfName", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + + if (!is_first) + vty_out(vty, ",\n"); + else + is_first = 0; + + vty_out(vty, "\"%s\":", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } else { + vty_out(vty, "\nInstance %s:\n", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + } + + if (type == show_peer || type == show_ipv4_peer || + type == show_ipv6_peer) { + ret = str2sockunion(ip_str, &su); + if (ret < 0) + bgp_show_neighbor(vty, bgp, type, NULL, ip_str, + use_json, json); + else + bgp_show_neighbor(vty, bgp, type, &su, NULL, + use_json, json); + } else { + bgp_show_neighbor(vty, bgp, type, NULL, NULL, + use_json, json); + } + json_object_free(json); + json = NULL; + } + + if (use_json) + vty_out(vty, "}\n"); + else if (!nbr_output) + vty_out(vty, "%% BGP instance not found\n"); +} + +static int bgp_show_neighbor_vty(struct vty *vty, const char *name, + enum show_type type, const char *ip_str, + bool use_json) +{ + int ret; + struct bgp *bgp; + union sockunion su; + json_object *json = NULL; + + if (name) { + if (strmatch(name, "all")) { + bgp_show_all_instances_neighbors_vty(vty, type, ip_str, + use_json); + return CMD_SUCCESS; + } else { + bgp = bgp_lookup_by_name(name); + if (!bgp) { + if (use_json) { + json = json_object_new_object(); + vty_json(vty, json); + } else + vty_out(vty, + "%% BGP instance not found\n"); + + return CMD_WARNING; + } + } + } else { + bgp = bgp_get_default(); + } + + if (bgp) { + json = json_object_new_object(); + if (ip_str) { + ret = str2sockunion(ip_str, &su); + if (ret < 0) + bgp_show_neighbor(vty, bgp, type, NULL, ip_str, + use_json, json); + else + bgp_show_neighbor(vty, bgp, type, &su, NULL, + use_json, json); + } else { + bgp_show_neighbor(vty, bgp, type, NULL, NULL, use_json, + json); + } + json_object_free(json); + } else { + if (use_json) + vty_out(vty, "{}\n"); + else + vty_out(vty, "%% BGP instance not found\n"); + } + + return CMD_SUCCESS; +} + + + +/* "show [ip] bgp neighbors graceful-restart" commands. */ +DEFUN (show_ip_bgp_neighbors_graceful_restart, + show_ip_bgp_neighbors_graceful_restart_cmd, + "show bgp [] neighbors [] graceful-restart [json]", + SHOW_STR + BGP_STR + IP_STR + IPV6_STR + NEIGHBOR_STR + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + GR_SHOW + JSON_STR) +{ + char *sh_arg = NULL; + enum show_type sh_type; + int idx = 0; + afi_t afi = AFI_MAX; + bool uj = use_json(argc, argv); + + if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) + afi = AFI_MAX; + + idx++; + + if (argv_find(argv, argc, "A.B.C.D", &idx) + || argv_find(argv, argc, "X:X::X:X", &idx) + || argv_find(argv, argc, "WORD", &idx)) { + sh_type = show_peer; + sh_arg = argv[idx]->arg; + } else + sh_type = show_all; + + if (!argv_find(argv, argc, "graceful-restart", &idx)) + return CMD_SUCCESS; + + + return bgp_show_neighbor_graceful_restart_afi_all(vty, sh_type, sh_arg, + afi, uj); +} + +/* "show [ip] bgp neighbors" commands. */ +DEFUN (show_ip_bgp_neighbors, + show_ip_bgp_neighbors_cmd, + "show [ip] bgp [ VIEWVRFNAME] [] neighbors [] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AF_STR + BGP_AF_STR + "Detailed information on TCP and BGP neighbor connections\n" + "Neighbor to display information about\n" + "Neighbor to display information about\n" + "Neighbor on BGP configured interface\n" + JSON_STR) +{ + char *vrf = NULL; + char *sh_arg = NULL; + enum show_type sh_type; + afi_t afi = AFI_MAX; + + bool uj = use_json(argc, argv); + + int idx = 0; + + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } else if (argv_find(argv, argc, "view", &idx)) + /* [ VIEWVRFNAME] */ + vrf = argv[idx + 1]->arg; + + idx++; + + if (argv_find(argv, argc, "ipv4", &idx)) { + sh_type = show_ipv4_all; + afi = AFI_IP; + } else if (argv_find(argv, argc, "ipv6", &idx)) { + sh_type = show_ipv6_all; + afi = AFI_IP6; + } else { + sh_type = show_all; + } + + if (argv_find(argv, argc, "A.B.C.D", &idx) + || argv_find(argv, argc, "X:X::X:X", &idx) + || argv_find(argv, argc, "WORD", &idx)) { + sh_type = show_peer; + sh_arg = argv[idx]->arg; + } + + if (sh_type == show_peer && afi == AFI_IP) { + sh_type = show_ipv4_peer; + } else if (sh_type == show_peer && afi == AFI_IP6) { + sh_type = show_ipv6_peer; + } + + return bgp_show_neighbor_vty(vty, vrf, sh_type, sh_arg, uj); +} + +/* Show BGP's AS paths internal data. There are both `show [ip] bgp + paths' and `show ip mbgp paths'. Those functions results are the + same.*/ +DEFUN (show_ip_bgp_paths, + show_ip_bgp_paths_cmd, + "show [ip] bgp ["BGP_SAFI_CMD_STR"] paths", + SHOW_STR + IP_STR + BGP_STR + BGP_SAFI_HELP_STR + "Path information\n") +{ + vty_out(vty, "Address Refcnt Path\n"); + aspath_print_all_vty(vty); + return CMD_SUCCESS; +} + +#include "hash.h" + +static void community_show_all_iterator(struct hash_bucket *bucket, + struct vty *vty) +{ + struct community *com; + + com = (struct community *)bucket->data; + vty_out(vty, "[%p] (%ld) %s\n", (void *)com, com->refcnt, + community_str(com, false, false)); +} + +/* Show BGP's community internal data. */ +DEFUN (show_ip_bgp_community_info, + show_ip_bgp_community_info_cmd, + "show [ip] bgp community-info", + SHOW_STR + IP_STR + BGP_STR + "List all bgp community information\n") +{ + vty_out(vty, "Address Refcnt Community\n"); + + hash_iterate(community_hash(), + (void (*)(struct hash_bucket *, + void *))community_show_all_iterator, + vty); + + return CMD_SUCCESS; +} + +static void lcommunity_show_all_iterator(struct hash_bucket *bucket, + struct vty *vty) +{ + struct lcommunity *lcom; + + lcom = (struct lcommunity *)bucket->data; + vty_out(vty, "[%p] (%ld) %s\n", (void *)lcom, lcom->refcnt, + lcommunity_str(lcom, false, false)); +} + +/* Show BGP's community internal data. */ +DEFUN (show_ip_bgp_lcommunity_info, + show_ip_bgp_lcommunity_info_cmd, + "show ip bgp large-community-info", + SHOW_STR + IP_STR + BGP_STR + "List all bgp large-community information\n") +{ + vty_out(vty, "Address Refcnt Large-community\n"); + + hash_iterate(lcommunity_hash(), + (void (*)(struct hash_bucket *, + void *))lcommunity_show_all_iterator, + vty); + + return CMD_SUCCESS; +} +/* Graceful Restart */ + +static void bgp_show_global_graceful_restart_mode_vty(struct vty *vty, + struct bgp *bgp) +{ + + + vty_out(vty, "\n%s", SHOW_GR_HEADER); + + enum global_mode bgp_global_gr_mode = bgp_global_gr_mode_get(bgp); + + switch (bgp_global_gr_mode) { + + case GLOBAL_HELPER: + vty_out(vty, "Global BGP GR Mode : Helper\n"); + break; + + case GLOBAL_GR: + vty_out(vty, "Global BGP GR Mode : Restart\n"); + break; + + case GLOBAL_DISABLE: + vty_out(vty, "Global BGP GR Mode : Disable\n"); + break; + + case GLOBAL_INVALID: + vty_out(vty, + "Global BGP GR Mode Invalid\n"); + break; + } + vty_out(vty, "\n"); +} + +static int bgp_show_neighbor_graceful_restart_afi_all(struct vty *vty, + enum show_type type, + const char *ip_str, + afi_t afi, bool use_json) +{ + json_object *json = NULL; + + if (use_json) + json = json_object_new_object(); + + if ((afi == AFI_MAX) && (ip_str == NULL)) { + afi = AFI_IP; + + while ((afi != AFI_L2VPN) && (afi < AFI_MAX)) { + + bgp_show_neighbor_graceful_restart_vty( + vty, type, ip_str, afi, json); + afi++; + } + } else if (afi != AFI_MAX) { + bgp_show_neighbor_graceful_restart_vty(vty, type, ip_str, afi, + json); + } else { + if (json) + json_object_free(json); + return CMD_ERR_INCOMPLETE; + } + + if (json) + vty_json(vty, json); + + return CMD_SUCCESS; +} +/* Graceful Restart */ + +DEFUN (show_ip_bgp_attr_info, + show_ip_bgp_attr_info_cmd, + "show [ip] bgp attribute-info", + SHOW_STR + IP_STR + BGP_STR + "List all bgp attribute information\n") +{ + attr_show_all(vty); + return CMD_SUCCESS; +} + +static int bgp_show_route_leak_vty(struct vty *vty, const char *name, + afi_t afi, safi_t safi, + bool use_json, json_object *json) +{ + struct bgp *bgp; + struct listnode *node; + char *vname; + char *ecom_str; + enum vpn_policy_direction dir; + + if (json) { + json_object *json_import_vrfs = NULL; + json_object *json_export_vrfs = NULL; + + bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); + + if (!bgp) { + vty_json(vty, json); + + return CMD_WARNING; + } + + /* Provide context for the block */ + json_object_string_add(json, "vrf", name ? name : "default"); + json_object_string_add(json, "afiSafi", + get_afi_safi_str(afi, safi, true)); + + if (!CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + json_object_string_add(json, "importFromVrfs", "none"); + json_object_string_add(json, "importRts", "none"); + } else { + json_import_vrfs = json_object_new_array(); + + for (ALL_LIST_ELEMENTS_RO( + bgp->vpn_policy[afi].import_vrf, + node, vname)) + json_object_array_add(json_import_vrfs, + json_object_new_string(vname)); + + json_object_object_add(json, "importFromVrfs", + json_import_vrfs); + dir = BGP_VPN_POLICY_DIR_FROMVPN; + if (bgp->vpn_policy[afi].rtlist[dir]) { + ecom_str = ecommunity_ecom2str( + bgp->vpn_policy[afi].rtlist[dir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "importRts", + ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } else + json_object_string_add(json, "importRts", + "none"); + } + + if (!CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) { + json_object_string_add(json, "exportToVrfs", "none"); + json_object_string_add(json, "routeDistinguisher", + "none"); + json_object_string_add(json, "exportRts", "none"); + } else { + json_export_vrfs = json_object_new_array(); + + for (ALL_LIST_ELEMENTS_RO( + bgp->vpn_policy[afi].export_vrf, + node, vname)) + json_object_array_add(json_export_vrfs, + json_object_new_string(vname)); + json_object_object_add(json, "exportToVrfs", + json_export_vrfs); + json_object_string_addf( + json, "routeDistinguisher", "%s", + bgp->vpn_policy[afi].tovpn_rd_pretty); + dir = BGP_VPN_POLICY_DIR_TOVPN; + if (bgp->vpn_policy[afi].rtlist[dir]) { + ecom_str = ecommunity_ecom2str( + bgp->vpn_policy[afi].rtlist[dir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + json_object_string_add(json, "exportRts", + ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } else + json_object_string_add(json, "exportRts", + "none"); + } + + if (use_json) { + vty_json(vty, json); + } + } else { + bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); + + if (!bgp) { + vty_out(vty, "%% No such BGP instance exist\n"); + return CMD_WARNING; + } + + if (!CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) + vty_out(vty, + "This VRF is not importing %s routes from any other VRF\n", + get_afi_safi_str(afi, safi, false)); + else { + vty_out(vty, + "This VRF is importing %s routes from the following VRFs:\n", + get_afi_safi_str(afi, safi, false)); + + for (ALL_LIST_ELEMENTS_RO( + bgp->vpn_policy[afi].import_vrf, + node, vname)) + vty_out(vty, " %s\n", vname); + + dir = BGP_VPN_POLICY_DIR_FROMVPN; + ecom_str = NULL; + if (bgp->vpn_policy[afi].rtlist[dir]) { + ecom_str = ecommunity_ecom2str( + bgp->vpn_policy[afi].rtlist[dir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, "Import RT(s): %s\n", ecom_str); + + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } else + vty_out(vty, "Import RT(s):\n"); + } + + if (!CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) + vty_out(vty, + "This VRF is not exporting %s routes to any other VRF\n", + get_afi_safi_str(afi, safi, false)); + else { + vty_out(vty, + "This VRF is exporting %s routes to the following VRFs:\n", + get_afi_safi_str(afi, safi, false)); + + for (ALL_LIST_ELEMENTS_RO( + bgp->vpn_policy[afi].export_vrf, + node, vname)) + vty_out(vty, " %s\n", vname); + + vty_out(vty, "RD: "); + vty_out(vty, BGP_RD_AS_FORMAT(bgp->asnotation), + &bgp->vpn_policy[afi].tovpn_rd); + vty_out(vty, "\n"); + + dir = BGP_VPN_POLICY_DIR_TOVPN; + if (bgp->vpn_policy[afi].rtlist[dir]) { + ecom_str = ecommunity_ecom2str( + bgp->vpn_policy[afi].rtlist[dir], + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, "Export RT: %s\n", ecom_str); + XFREE(MTYPE_ECOMMUNITY_STR, ecom_str); + } else + vty_out(vty, "Import RT(s):\n"); + } + } + + return CMD_SUCCESS; +} + +static int bgp_show_all_instance_route_leak_vty(struct vty *vty, afi_t afi, + safi_t safi, bool use_json) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + char *vrf_name = NULL; + json_object *json = NULL; + json_object *json_vrf = NULL; + json_object *json_vrfs = NULL; + + if (use_json) { + json = json_object_new_object(); + json_vrfs = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) + vrf_name = bgp->name; + + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + if (use_json) { + json_vrf = json_object_new_object(); + } else { + vty_out(vty, "\nInstance %s:\n", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME : bgp->name); + } + bgp_show_route_leak_vty(vty, vrf_name, afi, safi, 0, json_vrf); + if (use_json) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + json_object_object_add(json_vrfs, + VRF_DEFAULT_NAME, json_vrf); + else + json_object_object_add(json_vrfs, vrf_name, + json_vrf); + } + } + + if (use_json) { + json_object_object_add(json, "vrfs", json_vrfs); + vty_json(vty, json); + } + + return CMD_SUCCESS; +} + +/* "show [ip] bgp route-leak" command. */ +DEFUN (show_ip_bgp_route_leak, + show_ip_bgp_route_leak_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] route-leak [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "Route leaking information\n" + JSON_STR) +{ + char *vrf = NULL; + afi_t afi = AFI_MAX; + safi_t safi = SAFI_MAX; + + bool uj = use_json(argc, argv); + int idx = 0; + json_object *json = NULL; + + /* show [ip] bgp */ + if (argv_find(argv, argc, "ip", &idx)) { + afi = AFI_IP; + safi = SAFI_UNICAST; + } + /* [vrf VIEWVRFNAME] */ + if (argv_find(argv, argc, "view", &idx)) { + vty_out(vty, + "%% This command is not applicable to BGP views\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } + /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) + argv_find_and_parse_safi(argv, argc, &idx, &safi); + + if (!((afi == AFI_IP || afi == AFI_IP6) && safi == SAFI_UNICAST)) { + vty_out(vty, + "%% This command is applicable only for unicast ipv4|ipv6\n"); + return CMD_WARNING; + } + + if (vrf && strmatch(vrf, "all")) + return bgp_show_all_instance_route_leak_vty(vty, afi, safi, uj); + + if (uj) + json = json_object_new_object(); + + return bgp_show_route_leak_vty(vty, vrf, afi, safi, uj, json); +} + +static void bgp_show_all_instances_updgrps_vty(struct vty *vty, afi_t afi, + safi_t safi, bool uj) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + if (!uj) + vty_out(vty, "\nInstance %s:\n", + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + ? VRF_DEFAULT_NAME + : bgp->name); + + update_group_show(bgp, afi, safi, vty, 0, uj); + } +} + +static int bgp_show_update_groups(struct vty *vty, const char *name, int afi, + int safi, uint64_t subgrp_id, bool uj) +{ + struct bgp *bgp; + + if (name) { + if (strmatch(name, "all")) { + bgp_show_all_instances_updgrps_vty(vty, afi, safi, uj); + return CMD_SUCCESS; + } else { + bgp = bgp_lookup_by_name(name); + } + } else { + bgp = bgp_get_default(); + } + + if (bgp) + update_group_show(bgp, afi, safi, vty, subgrp_id, uj); + return CMD_SUCCESS; +} + +DEFUN (show_ip_bgp_updgrps, + show_ip_bgp_updgrps_cmd, + "show [ip] bgp [ VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] update-groups [SUBGROUP-ID] [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + BGP_AFI_HELP_STR + BGP_SAFI_WITH_LABEL_HELP_STR + "Detailed info about dynamic update groups\n" + "Specific subgroup to display detailed info for\n" + JSON_STR) +{ + char *vrf = NULL; + afi_t afi = AFI_IP6; + safi_t safi = SAFI_UNICAST; + uint64_t subgrp_id = 0; + + int idx = 0; + + bool uj = use_json(argc, argv); + + /* show [ip] bgp */ + if (argv_find(argv, argc, "ip", &idx)) + afi = AFI_IP; + /* [ VIEWVRFNAME] */ + if (argv_find(argv, argc, "vrf", &idx)) { + vrf = argv[idx + 1]->arg; + if (vrf && strmatch(vrf, VRF_DEFAULT_NAME)) + vrf = NULL; + } else if (argv_find(argv, argc, "view", &idx)) + /* [ VIEWVRFNAME] */ + vrf = argv[idx + 1]->arg; + /* ["BGP_AFI_CMD_STR" ["BGP_SAFI_CMD_STR"]] */ + if (argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + argv_find_and_parse_safi(argv, argc, &idx, &safi); + } + + /* get subgroup id, if provided */ + idx = argc - 1; + if (argv[idx]->type == VARIABLE_TKN) + subgrp_id = strtoull(argv[idx]->arg, NULL, 10); + + return (bgp_show_update_groups(vty, vrf, afi, safi, subgrp_id, uj)); +} + +DEFUN (show_bgp_instance_all_ipv6_updgrps, + show_bgp_instance_all_ipv6_updgrps_cmd, + "show [ip] bgp all update-groups [json]", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_ALL_HELP_STR + "Detailed info about dynamic update groups\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + + bgp_show_all_instances_updgrps_vty(vty, AFI_IP6, SAFI_UNICAST, uj); + return CMD_SUCCESS; +} + +DEFUN (show_bgp_l2vpn_evpn_updgrps, + show_bgp_l2vpn_evpn_updgrps_cmd, + "show [ip] bgp l2vpn evpn update-groups", + SHOW_STR + IP_STR + BGP_STR + "l2vpn address family\n" + "evpn sub-address family\n" + "Detailed info about dynamic update groups\n") +{ + char *vrf = NULL; + uint64_t subgrp_id = 0; + + bgp_show_update_groups(vty, vrf, AFI_L2VPN, SAFI_EVPN, subgrp_id, 0); + return CMD_SUCCESS; +} + +DEFUN (show_bgp_updgrps_stats, + show_bgp_updgrps_stats_cmd, + "show [ip] bgp update-groups statistics", + SHOW_STR + IP_STR + BGP_STR + "Detailed info about dynamic update groups\n" + "Statistics\n") +{ + struct bgp *bgp; + + bgp = bgp_get_default(); + if (bgp) + update_group_show_stats(bgp, vty); + + return CMD_SUCCESS; +} + +DEFUN (show_bgp_instance_updgrps_stats, + show_bgp_instance_updgrps_stats_cmd, + "show [ip] bgp VIEWVRFNAME update-groups statistics", + SHOW_STR + IP_STR + BGP_STR + BGP_INSTANCE_HELP_STR + "Detailed info about dynamic update groups\n" + "Statistics\n") +{ + int idx_word = 3; + struct bgp *bgp; + + bgp = bgp_lookup_by_name(argv[idx_word]->arg); + if (bgp) + update_group_show_stats(bgp, vty); + + return CMD_SUCCESS; +} + +static void show_bgp_updgrps_adj_info_aux(struct vty *vty, const char *name, + afi_t afi, safi_t safi, + const char *what, uint64_t subgrp_id) +{ + struct bgp *bgp; + + if (name) + bgp = bgp_lookup_by_name(name); + else + bgp = bgp_get_default(); + + if (bgp) { + if (!strcmp(what, "advertise-queue")) + update_group_show_adj_queue(bgp, afi, safi, vty, + subgrp_id); + else if (!strcmp(what, "advertised-routes")) + update_group_show_advertised(bgp, afi, safi, vty, + subgrp_id); + else if (!strcmp(what, "packet-queue")) + update_group_show_packet_queue(bgp, afi, safi, vty, + subgrp_id); + } +} + +DEFPY(show_ip_bgp_instance_updgrps_adj_s, + show_ip_bgp_instance_updgrps_adj_s_cmd, + "show [ip]$ip bgp [ VIEWVRFNAME$vrf] [$afi $safi] update-groups [SUBGROUP-ID]$sgid $rtq", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR + BGP_SAFI_HELP_STR + "Detailed info about dynamic update groups\n" + "Specific subgroup to display info for\n" + "Advertisement queue\n" + "Announced routes\n" + "Packet queue\n") +{ + uint64_t subgrp_id = 0; + afi_t afiz; + safi_t safiz; + if (sgid) + subgrp_id = strtoull(sgid, NULL, 10); + + if (!ip && !afi) + afiz = AFI_IP6; + if (!ip && afi) + afiz = bgp_vty_afi_from_str(afi); + if (ip && !afi) + afiz = AFI_IP; + if (ip && afi) { + afiz = bgp_vty_afi_from_str(afi); + if (afiz != AFI_IP) + vty_out(vty, + "%% Cannot specify both 'ip' and 'ipv6'\n"); + return CMD_WARNING; + } + + safiz = safi ? bgp_vty_safi_from_str(safi) : SAFI_UNICAST; + + show_bgp_updgrps_adj_info_aux(vty, vrf, afiz, safiz, rtq, subgrp_id); + return CMD_SUCCESS; +} + +static int bgp_show_one_peer_group(struct vty *vty, struct peer_group *group, + json_object *json) +{ + struct listnode *node, *nnode; + struct prefix *range; + struct peer *conf; + struct peer *peer; + afi_t afi; + safi_t safi; + const char *peer_status; + int lr_count; + int dynamic; + bool af_cfgd; + json_object *json_peer_group = NULL; + json_object *json_peer_group_afc = NULL; + json_object *json_peer_group_members = NULL; + json_object *json_peer_group_dynamic = NULL; + json_object *json_peer_group_dynamic_af = NULL; + json_object *json_peer_group_ranges = NULL; + + conf = group->conf; + + if (json) { + json_peer_group = json_object_new_object(); + json_peer_group_afc = json_object_new_array(); + } + + if (conf->as_type == AS_SPECIFIED || conf->as_type == AS_EXTERNAL) { + if (json) + asn_asn2json(json_peer_group, "remoteAs", conf->as, + bgp_get_asnotation(conf->bgp)); + else { + vty_out(vty, "\nBGP peer-group %s, remote AS ", + group->name); + vty_out(vty, ASN_FORMAT(bgp_get_asnotation(conf->bgp)), + &conf->as); + vty_out(vty, "\n"); + } + } else if (conf->as_type == AS_INTERNAL) { + if (json) + asn_asn2json(json, "remoteAs", group->bgp->as, + group->bgp->asnotation); + else + vty_out(vty, "\nBGP peer-group %s, remote AS %s\n", + group->name, group->bgp->as_pretty); + } else { + if (!json) + vty_out(vty, "\nBGP peer-group %s\n", group->name); + } + + if ((group->bgp->as == conf->as) || (conf->as_type == AS_INTERNAL)) { + if (json) + json_object_string_add(json_peer_group, "type", + "internal"); + else + vty_out(vty, " Peer-group type is internal\n"); + } else { + if (json) + json_object_string_add(json_peer_group, "type", + "external"); + else + vty_out(vty, " Peer-group type is external\n"); + } + + /* Display AFs configured. */ + if (!json) + vty_out(vty, " Configured address-families:"); + + FOREACH_AFI_SAFI (afi, safi) { + if (conf->afc[afi][safi]) { + af_cfgd = true; + if (json) + json_object_array_add( + json_peer_group_afc, + json_object_new_string(get_afi_safi_str( + afi, safi, false))); + else + vty_out(vty, " %s;", + get_afi_safi_str(afi, safi, false)); + } + } + + if (json) { + json_object_object_add(json_peer_group, + "addressFamiliesConfigured", + json_peer_group_afc); + } else { + if (!af_cfgd) + vty_out(vty, " none\n"); + else + vty_out(vty, "\n"); + } + + /* Display listen ranges (for dynamic neighbors), if any */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + lr_count = listcount(group->listen_range[afi]); + if (lr_count) { + if (json) { + if (!json_peer_group_dynamic) + json_peer_group_dynamic = + json_object_new_object(); + + json_peer_group_dynamic_af = + json_object_new_object(); + json_peer_group_ranges = + json_object_new_array(); + json_object_int_add(json_peer_group_dynamic_af, + "count", lr_count); + } else { + vty_out(vty, " %d %s listen range(s)\n", + lr_count, afi2str(afi)); + } + + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, + nnode, range)) { + if (json) { + char buf[BUFSIZ]; + + snprintfrr(buf, sizeof(buf), "%pFX", + range); + + json_object_array_add( + json_peer_group_ranges, + json_object_new_string(buf)); + } else { + vty_out(vty, " %pFX\n", range); + } + } + + if (json) { + json_object_object_add( + json_peer_group_dynamic_af, "ranges", + json_peer_group_ranges); + + json_object_object_add( + json_peer_group_dynamic, afi2str(afi), + json_peer_group_dynamic_af); + } + } + } + + if (json_peer_group_dynamic) + json_object_object_add(json_peer_group, "dynamicRanges", + json_peer_group_dynamic); + + /* Display group members and their status */ + if (listcount(group->peer)) { + if (json) + json_peer_group_members = json_object_new_object(); + else + vty_out(vty, " Peer-group members:\n"); + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN) + || CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHUTDOWN)) + peer_status = "Idle (Admin)"; + else if (CHECK_FLAG(peer->sflags, + PEER_STATUS_PREFIX_OVERFLOW)) + peer_status = "Idle (PfxCt)"; + else + peer_status = + lookup_msg(bgp_status_msg, + peer->connection->status, + NULL); + + dynamic = peer_dynamic_neighbor(peer); + + if (json) { + json_object *json_peer_group_member = + json_object_new_object(); + + json_object_string_add(json_peer_group_member, + "status", peer_status); + + if (dynamic) + json_object_boolean_true_add( + json_peer_group_member, + "dynamic"); + + json_object_object_add(json_peer_group_members, + peer->host, + json_peer_group_member); + } else { + vty_out(vty, " %s %s %s \n", peer->host, + dynamic ? "(dynamic)" : "", + peer_status); + } + } + if (json) + json_object_object_add(json_peer_group, "members", + json_peer_group_members); + } + + if (json) + json_object_object_add(json, group->name, json_peer_group); + + return CMD_SUCCESS; +} + +static int bgp_show_peer_group_vty(struct vty *vty, const char *name, + const char *group_name, bool uj) +{ + struct bgp *bgp; + struct listnode *node, *nnode; + struct peer_group *group; + bool found = false; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + bgp = name ? bgp_lookup_by_name(name) : bgp_get_default(); + + if (!bgp) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, "%% BGP instance not found\n"); + + return CMD_WARNING; + } + + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + if (group_name) { + if (strmatch(group->name, group_name)) { + bgp_show_one_peer_group(vty, group, json); + found = true; + break; + } + } else { + bgp_show_one_peer_group(vty, group, json); + } + } + + if (group_name && !found && !uj) + vty_out(vty, "%% No such peer-group\n"); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFUN(show_ip_bgp_peer_groups, show_ip_bgp_peer_groups_cmd, + "show [ip] bgp [ VIEWVRFNAME] peer-group [PGNAME] [json]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR + "Detailed information on BGP peer groups\n" + "Peer group name\n" JSON_STR) +{ + char *vrf, *pg; + int idx = 0; + bool uj = use_json(argc, argv); + + vrf = argv_find(argv, argc, "VIEWVRFNAME", &idx) ? argv[idx]->arg + : NULL; + pg = argv_find(argv, argc, "PGNAME", &idx) ? argv[idx]->arg : NULL; + + return bgp_show_peer_group_vty(vty, vrf, pg, uj); +} + + +/* Redistribute VTY commands. */ + +DEFUN (bgp_redistribute_ipv4, + bgp_redistribute_ipv4_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD, + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int type; + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_redist_add(bgp, AFI_IP, type, 0); + return bgp_redistribute_set(bgp, AFI_IP, type, 0, false); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4, bgp_redistribute_ipv4_hidden_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD, + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD) + +DEFUN (bgp_redistribute_ipv4_rmap, + bgp_redistribute_ipv4_rmap_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_word = 3; + int type; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = route_map_lookup_warn_noexist( + vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + red = bgp_redist_add(bgp, AFI_IP, type, 0); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_rmap, bgp_redistribute_ipv4_rmap_hidden_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (bgp_redistribute_ipv4_metric, + bgp_redistribute_ipv4_metric_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_number = 3; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, type, 0); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); + return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_metric, bgp_redistribute_ipv4_metric_hidden_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295)", + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n") + +DEFUN (bgp_redistribute_ipv4_rmap_metric, + bgp_redistribute_ipv4_rmap_metric_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " route-map RMAP_NAME metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n" + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_word = 3; + int idx_number = 5; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, type, 0); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); + return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_rmap_metric, + bgp_redistribute_ipv4_rmap_metric_hidden_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD + " route-map RMAP_NAME metric (0-4294967295)", + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n" + "Metric for redistributed routes\n" + "Default metric\n") + +DEFUN (bgp_redistribute_ipv4_metric_rmap, + bgp_redistribute_ipv4_metric_rmap_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD " metric (0-4294967295) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_number = 3; + int idx_word = 5; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, type, 0); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, type, metric); + changed |= + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP, type, 0, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_metric_rmap, + bgp_redistribute_ipv4_metric_rmap_hidden_cmd, + "redistribute " FRR_IP_REDIST_STR_BGPD + " metric (0-4294967295) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (bgp_redistribute_ipv4_ospf, + bgp_redistribute_ipv4_ospf_cmd, + "redistribute (1-65535)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 1; + int idx_number = 2; + unsigned short instance; + unsigned short protocol; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + bgp_redist_add(bgp, AFI_IP, protocol, instance); + return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, false); +} + +ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf, bgp_redistribute_ipv4_ospf_hidden_cmd, + "redistribute (1-65535)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n") + +DEFUN (bgp_redistribute_ipv4_ospf_rmap, + bgp_redistribute_ipv4_ospf_rmap_cmd, + "redistribute (1-65535) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 1; + int idx_number = 2; + int idx_word = 4; + struct bgp_redist *red; + unsigned short instance; + int protocol; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + red = bgp_redist_add(bgp, AFI_IP, protocol, instance); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); +} + +ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf_rmap, + bgp_redistribute_ipv4_ospf_rmap_hidden_cmd, + "redistribute (1-65535) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (bgp_redistribute_ipv4_ospf_metric, + bgp_redistribute_ipv4_ospf_metric_cmd, + "redistribute (1-65535) metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 1; + int idx_number = 2; + int idx_number_2 = 4; + uint32_t metric; + struct bgp_redist *red; + unsigned short instance; + int protocol; + bool changed; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + metric = strtoul(argv[idx_number_2]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, protocol, instance); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, + metric); + return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); +} + +ALIAS_HIDDEN(bgp_redistribute_ipv4_ospf_metric, + bgp_redistribute_ipv4_ospf_metric_hidden_cmd, + "redistribute (1-65535) metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n") + +DEFUN (bgp_redistribute_ipv4_ospf_rmap_metric, + bgp_redistribute_ipv4_ospf_rmap_metric_cmd, + "redistribute (1-65535) route-map RMAP_NAME metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Route map reference\n" + "Pointer to route-map entries\n" + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 1; + int idx_number = 2; + int idx_word = 4; + int idx_number_2 = 6; + uint32_t metric; + struct bgp_redist *red; + unsigned short instance; + int protocol; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + metric = strtoul(argv[idx_number_2]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, protocol, instance); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, + metric); + return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_ospf_rmap_metric, + bgp_redistribute_ipv4_ospf_rmap_metric_hidden_cmd, + "redistribute (1-65535) route-map RMAP_NAME metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Route map reference\n" + "Pointer to route-map entries\n" + "Metric for redistributed routes\n" + "Default metric\n") + +DEFUN (bgp_redistribute_ipv4_ospf_metric_rmap, + bgp_redistribute_ipv4_ospf_metric_rmap_cmd, + "redistribute (1-65535) metric (0-4294967295) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 1; + int idx_number = 2; + int idx_number_2 = 4; + int idx_word = 6; + uint32_t metric; + struct bgp_redist *red; + unsigned short instance; + int protocol; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } else if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + metric = strtoul(argv[idx_number_2]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP, protocol, instance); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP, protocol, + metric); + changed |= + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP, protocol, instance, changed); +} + +ALIAS_HIDDEN( + bgp_redistribute_ipv4_ospf_metric_rmap, + bgp_redistribute_ipv4_ospf_metric_rmap_hidden_cmd, + "redistribute (1-65535) metric (0-4294967295) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (no_bgp_redistribute_ipv4_ospf, + no_bgp_redistribute_ipv4_ospf_cmd, + "no redistribute (1-65535) [{metric (0-4294967295)|route-map RMAP_NAME}]", + NO_STR + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_ospf_table = 2; + int idx_number = 3; + unsigned short instance; + int protocol; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + protocol = ZEBRA_ROUTE_OSPF; + else { + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use '%s'\n", + argv[idx_ospf_table]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (strncmp(argv[idx_ospf_table]->arg, "table-direct", + strlen("table-direct")) == 0) { + protocol = ZEBRA_ROUTE_TABLE_DIRECT; + if (instance == RT_TABLE_MAIN || + instance == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %u routing table\n", + instance); + return CMD_WARNING_CONFIG_FAILED; + } + } else + protocol = ZEBRA_ROUTE_TABLE; + } + + bgp_redistribute_unset(bgp, AFI_IP, protocol, instance); + return CMD_SUCCESS; +} + +ALIAS_HIDDEN( + no_bgp_redistribute_ipv4_ospf, no_bgp_redistribute_ipv4_ospf_hidden_cmd, + "no redistribute (1-65535) [{metric (0-4294967295)|route-map RMAP_NAME}]", + NO_STR + "Redistribute information from another routing protocol\n" + "Open Shortest Path First (OSPFv2)\n" + "Non-main Kernel Routing Table\n" + "Non-main Kernel Routing Table - Direct\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (no_bgp_redistribute_ipv4, + no_bgp_redistribute_ipv4_cmd, + "no redistribute " FRR_IP_REDIST_STR_BGPD " [{metric (0-4294967295)|route-map RMAP_NAME}]", + NO_STR + "Redistribute information from another routing protocol\n" + FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 2; + int type; + + type = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + bgp_redistribute_unset(bgp, AFI_IP, type, 0); + return CMD_SUCCESS; +} + +ALIAS_HIDDEN( + no_bgp_redistribute_ipv4, no_bgp_redistribute_ipv4_hidden_cmd, + "no redistribute " FRR_IP_REDIST_STR_BGPD + " [{metric (0-4294967295)|route-map RMAP_NAME}]", + NO_STR + "Redistribute information from another routing protocol\n" FRR_IP_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") + +DEFUN (bgp_redistribute_ipv6, + bgp_redistribute_ipv6_cmd, + "redistribute " FRR_IP6_REDIST_STR_BGPD, + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int type; + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_redist_add(bgp, AFI_IP6, type, 0); + return bgp_redistribute_set(bgp, AFI_IP6, type, 0, false); +} + +DEFUN (bgp_redistribute_ipv6_rmap, + bgp_redistribute_ipv6_rmap_cmd, + "redistribute " FRR_IP6_REDIST_STR_BGPD " route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_word = 3; + int type; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + red = bgp_redist_add(bgp, AFI_IP6, type, 0); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); +} + +DEFUN (bgp_redistribute_ipv6_metric, + bgp_redistribute_ipv6_metric_cmd, + "redistribute " FRR_IP6_REDIST_STR_BGPD " metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_number = 3; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP6, type, 0); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP6, type, metric); + return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); +} + +DEFUN (bgp_redistribute_ipv6_rmap_metric, + bgp_redistribute_ipv6_rmap_metric_cmd, + "redistribute " FRR_IP6_REDIST_STR_BGPD " route-map RMAP_NAME metric (0-4294967295)", + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD + "Route map reference\n" + "Pointer to route-map entries\n" + "Metric for redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_word = 3; + int idx_number = 5; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP6, type, 0); + changed = + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP6, type, + metric); + return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); +} + +DEFPY(bgp_redistribute_ipv6_table, bgp_redistribute_ipv6_table_cmd, + "redistribute table-direct (1-65535)$table_id [{metric$metric (0-4294967295)$metric_val|route-map WORD$rmap}]", + "Redistribute information from another routing protocol\n" + "Non-main Kernel Routing Table - Direct\n" + "Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bool changed = false; + struct route_map *route_map = NULL; + struct bgp_redist *red; + + if (rmap) + route_map = route_map_lookup_warn_noexist(vty, rmap); + + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use 'table-direct'\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (table_id == RT_TABLE_MAIN || table_id == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %lu routing table\n", + table_id); + return CMD_WARNING_CONFIG_FAILED; + } + + red = bgp_redist_add(bgp, AFI_IP6, ZEBRA_ROUTE_TABLE_DIRECT, table_id); + if (rmap) + changed = bgp_redistribute_rmap_set(red, rmap, route_map); + if (metric) + changed |= bgp_redistribute_metric_set(bgp, red, AFI_IP6, + ZEBRA_ROUTE_TABLE_DIRECT, + metric_val); + return bgp_redistribute_set(bgp, AFI_IP6, ZEBRA_ROUTE_TABLE_DIRECT, + table_id, changed); +} + +DEFPY(no_bgp_redistribute_ipv6_table, no_bgp_redistribute_ipv6_table_cmd, + "no redistribute table-direct (1-65535)$table_id [{metric (0-4294967295)|route-map WORD}]", + NO_STR + "Redistribute information from another routing protocol\n" + "Non-main Kernel Routing Table - Direct\n" + "Table ID\n" + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (bgp->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "%% Only default BGP instance can use 'table-direct'\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (table_id == RT_TABLE_MAIN || table_id == RT_TABLE_LOCAL) { + vty_out(vty, + "%% 'table-direct', can not use %lu routing table\n", + table_id); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_redistribute_unset(bgp, AFI_IP6, ZEBRA_ROUTE_TABLE_DIRECT, table_id); + return CMD_SUCCESS; +} + +DEFUN (bgp_redistribute_ipv6_metric_rmap, + bgp_redistribute_ipv6_metric_rmap_cmd, + "redistribute " FRR_IP6_REDIST_STR_BGPD " metric (0-4294967295) route-map RMAP_NAME", + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 1; + int idx_number = 3; + int idx_word = 5; + int type; + uint32_t metric; + struct bgp_redist *red; + bool changed; + struct route_map *route_map = + route_map_lookup_warn_noexist(vty, argv[idx_word]->arg); + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + metric = strtoul(argv[idx_number]->arg, NULL, 10); + + red = bgp_redist_add(bgp, AFI_IP6, type, 0); + changed = bgp_redistribute_metric_set(bgp, red, AFI_IP6, SAFI_UNICAST, + metric); + changed |= + bgp_redistribute_rmap_set(red, argv[idx_word]->arg, route_map); + return bgp_redistribute_set(bgp, AFI_IP6, type, 0, changed); +} + +DEFUN (no_bgp_redistribute_ipv6, + no_bgp_redistribute_ipv6_cmd, + "no redistribute " FRR_IP6_REDIST_STR_BGPD " [{metric (0-4294967295)|route-map RMAP_NAME}]", + NO_STR + "Redistribute information from another routing protocol\n" + FRR_IP6_REDIST_HELP_STR_BGPD + "Metric for redistributed routes\n" + "Default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int idx_protocol = 2; + int type; + + type = proto_redistnum(AFI_IP6, argv[idx_protocol]->text); + if (type < 0) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bgp_redistribute_unset(bgp, AFI_IP6, type, 0); + return CMD_SUCCESS; +} + +/* Neighbor update tcp-mss. */ +static int peer_tcp_mss_vty(struct vty *vty, const char *peer_str, + const char *tcp_mss_str) +{ + struct peer *peer; + uint32_t tcp_mss_val = 0; + + peer = peer_and_group_lookup_vty(vty, peer_str); + if (!peer) + return CMD_WARNING_CONFIG_FAILED; + + if (tcp_mss_str) { + tcp_mss_val = strtoul(tcp_mss_str, NULL, 10); + peer_tcp_mss_set(peer, tcp_mss_val); + } else { + peer_tcp_mss_unset(peer); + } + + return CMD_SUCCESS; +} + +DEFUN(neighbor_tcp_mss, neighbor_tcp_mss_cmd, + "neighbor tcp-mss (1-65535)", + NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "TCP max segment size\n" + "TCP MSS value\n") +{ + int peer_index = 1; + int mss_index = 3; + + vty_out(vty, + " Warning: Reset BGP session for tcp-mss value to take effect\n"); + return peer_tcp_mss_vty(vty, argv[peer_index]->arg, + argv[mss_index]->arg); +} + +DEFUN(no_neighbor_tcp_mss, no_neighbor_tcp_mss_cmd, + "no neighbor tcp-mss [(1-65535)]", + NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 + "TCP max segment size\n" + "TCP MSS value\n") +{ + int peer_index = 2; + + vty_out(vty, + " Warning: Reset BGP session for tcp-mss value to take effect\n"); + return peer_tcp_mss_vty(vty, argv[peer_index]->arg, NULL); +} + +DEFPY(bgp_retain_route_target, bgp_retain_route_target_cmd, + "[no$no] bgp retain route-target all", + NO_STR BGP_STR + "Retain BGP updates\n" + "Retain BGP updates based on route-target values\n" + "Retain all BGP updates\n") +{ + bool check; + struct bgp *bgp = VTY_GET_CONTEXT(bgp); + + check = CHECK_FLAG(bgp->af_flags[bgp_node_afi(vty)][bgp_node_safi(vty)], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL); + if (check != !no) { + if (!no) + SET_FLAG(bgp->af_flags[bgp_node_afi(vty)] + [bgp_node_safi(vty)], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL); + else + UNSET_FLAG(bgp->af_flags[bgp_node_afi(vty)] + [bgp_node_safi(vty)], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL); + /* trigger a flush to re-sync with ADJ-RIB-in */ + bgp_clear(vty, bgp, bgp_node_afi(vty), bgp_node_safi(vty), + clear_all, BGP_CLEAR_SOFT_IN, NULL); + } + return CMD_SUCCESS; +} + +static void bgp_config_write_redistribute(struct vty *vty, struct bgp *bgp, + afi_t afi, safi_t safi) +{ + int i; + + /* Unicast redistribution only. */ + if (safi != SAFI_UNICAST) + return; + + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + /* Redistribute BGP does not make sense. */ + if (i != ZEBRA_ROUTE_BGP) { + struct list *red_list; + struct listnode *node; + struct bgp_redist *red; + + red_list = bgp->redist[afi][i]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + /* "redistribute" configuration. */ + vty_out(vty, " redistribute %s", + zebra_route_string(i)); + if (red->instance) + vty_out(vty, " %d", red->instance); + if (red->redist_metric_flag) + vty_out(vty, " metric %u", + red->redist_metric); + if (red->rmap.name) + vty_out(vty, " route-map %s", + red->rmap.name); + vty_out(vty, "\n"); + } + } + } +} + +/* peer-group helpers for config-write */ + +bool peergroup_flag_check(struct peer *peer, uint64_t flag) +{ + if (!peer_group_active(peer)) { + if (CHECK_FLAG(peer->flags_invert, flag)) + return !CHECK_FLAG(peer->flags, flag); + else + return !!CHECK_FLAG(peer->flags, flag); + } + + return !!CHECK_FLAG(peer->flags_override, flag); +} + +bool peergroup_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag) +{ + if (!peer_group_active(peer)) { + if (CHECK_FLAG(peer->af_flags_invert[afi][safi], flag)) + return !peer_af_flag_check(peer, afi, safi, flag); + else + return peer_af_flag_check(peer, afi, safi, flag); + } + + return !!CHECK_FLAG(peer->af_flags_override[afi][safi], flag); +} + +static bool peergroup_filter_check(struct peer *peer, afi_t afi, safi_t safi, + uint8_t type, int direct) +{ + struct bgp_filter *filter; + + if (peer_group_active(peer)) + return !!CHECK_FLAG(peer->filter_override[afi][safi][direct], + type); + + filter = &peer->filter[afi][safi]; + switch (type) { + case PEER_FT_DISTRIBUTE_LIST: + return !!(filter->dlist[direct].name); + case PEER_FT_FILTER_LIST: + return !!(filter->aslist[direct].name); + case PEER_FT_PREFIX_LIST: + return !!(filter->plist[direct].name); + case PEER_FT_ROUTE_MAP: + return !!(filter->map[direct].name); + case PEER_FT_UNSUPPRESS_MAP: + return !!(filter->usmap.name); + case PEER_FT_ADVERTISE_MAP: + return !!(filter->advmap.aname + && ((filter->advmap.condition == direct) + && filter->advmap.cname)); + default: + return false; + } +} + +/* Return true if the addpath type is set for peer and different from + * peer-group. + */ +static bool peergroup_af_addpath_check(struct peer *peer, afi_t afi, + safi_t safi) +{ + enum bgp_addpath_strat type, g_type; + + type = peer->addpath_type[afi][safi]; + + if (type != BGP_ADDPATH_NONE) { + if (peer_group_active(peer)) { + g_type = peer->group->conf->addpath_type[afi][safi]; + + if (type != g_type) + return true; + else + return false; + } + + return true; + } + + return false; +} + +/* This is part of the address-family block (unicast only) */ +static void bgp_vpn_policy_config_write_afi(struct vty *vty, struct bgp *bgp, + afi_t afi) +{ + int indent = 2; + uint32_t tovpn_sid_index = 0; + + if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]) { + if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) + vty_out(vty, "%*simport vrf route-map %s\n", indent, "", + bgp->vpn_policy[afi] + .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]); + else + vty_out(vty, "%*sroute-map vpn import %s\n", indent, "", + bgp->vpn_policy[afi] + .rmap_name[BGP_VPN_POLICY_DIR_FROMVPN]); + } + if (CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_IMPORT) + || CHECK_FLAG(bgp->af_flags[afi][SAFI_UNICAST], + BGP_CONFIG_VRF_TO_VRF_EXPORT)) + return; + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_AUTO)) { + + vty_out(vty, "%*slabel vpn export %s\n", indent, "", "auto"); + + } else { + if (bgp->vpn_policy[afi].tovpn_label != MPLS_LABEL_NONE) { + vty_out(vty, "%*slabel vpn export %u\n", indent, "", + bgp->vpn_policy[afi].tovpn_label); + } + } + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP)) + vty_out(vty, + "%*slabel vpn export allocation-mode per-nexthop\n", + indent, ""); + + tovpn_sid_index = bgp->vpn_policy[afi].tovpn_sid_index; + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_SID_AUTO)) { + vty_out(vty, "%*ssid vpn export %s\n", indent, "", "auto"); + } else if (tovpn_sid_index != 0) { + vty_out(vty, "%*ssid vpn export %d\n", indent, "", + tovpn_sid_index); + } + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, BGP_VPN_POLICY_TOVPN_RD_SET)) + vty_out(vty, "%*srd vpn export %s\n", indent, "", + bgp->vpn_policy[afi].tovpn_rd_pretty); + + if (CHECK_FLAG(bgp->vpn_policy[afi].flags, + BGP_VPN_POLICY_TOVPN_NEXTHOP_SET)) { + + char buf[PREFIX_STRLEN]; + if (inet_ntop(bgp->vpn_policy[afi].tovpn_nexthop.family, + &bgp->vpn_policy[afi].tovpn_nexthop.u.prefix, buf, + sizeof(buf))) { + + vty_out(vty, "%*snexthop vpn export %s\n", + indent, "", buf); + } + } + if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN] + && bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN] + && ecommunity_cmp( + bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN])) { + + char *b = ecommunity_ecom2str( + bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN], + ECOMMUNITY_FORMAT_ROUTE_MAP, ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, "%*srt vpn both %s\n", indent, "", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } else { + if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_FROMVPN]) { + char *b = ecommunity_ecom2str( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_FROMVPN], + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, "%*srt vpn import %s\n", indent, "", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + if (bgp->vpn_policy[afi].rtlist[BGP_VPN_POLICY_DIR_TOVPN]) { + char *b = ecommunity_ecom2str( + bgp->vpn_policy[afi] + .rtlist[BGP_VPN_POLICY_DIR_TOVPN], + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, "%*srt vpn export %s\n", indent, "", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + } + + if (bgp->vpn_policy[afi].rmap_name[BGP_VPN_POLICY_DIR_TOVPN]) + vty_out(vty, "%*sroute-map vpn export %s\n", indent, "", + bgp->vpn_policy[afi] + .rmap_name[BGP_VPN_POLICY_DIR_TOVPN]); + + if (bgp->vpn_policy[afi].import_redirect_rtlist) { + char *b = ecommunity_ecom2str( + bgp->vpn_policy[afi] + .import_redirect_rtlist, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + + if (bgp->vpn_policy[afi].import_redirect_rtlist->unit_size + != ECOMMUNITY_SIZE) + vty_out(vty, "%*srt6 redirect import %s\n", + indent, "", b); + else + vty_out(vty, "%*srt redirect import %s\n", + indent, "", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } +} + +static void bgp_config_write_filter(struct vty *vty, struct peer *peer, + afi_t afi, safi_t safi) +{ + struct bgp_filter *filter; + char *addr; + + addr = peer->host; + filter = &peer->filter[afi][safi]; + + /* distribute-list. */ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_DISTRIBUTE_LIST, + FILTER_IN)) + vty_out(vty, " neighbor %s distribute-list %s in\n", addr, + filter->dlist[FILTER_IN].name); + + if (peergroup_filter_check(peer, afi, safi, PEER_FT_DISTRIBUTE_LIST, + FILTER_OUT)) + vty_out(vty, " neighbor %s distribute-list %s out\n", addr, + filter->dlist[FILTER_OUT].name); + + /* prefix-list. */ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_PREFIX_LIST, + FILTER_IN)) + vty_out(vty, " neighbor %s prefix-list %s in\n", addr, + filter->plist[FILTER_IN].name); + + if (peergroup_filter_check(peer, afi, safi, PEER_FT_PREFIX_LIST, + FILTER_OUT)) + vty_out(vty, " neighbor %s prefix-list %s out\n", addr, + filter->plist[FILTER_OUT].name); + + /* route-map. */ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_ROUTE_MAP, RMAP_IN)) + vty_out(vty, " neighbor %s route-map %s in\n", addr, + filter->map[RMAP_IN].name); + + if (peergroup_filter_check(peer, afi, safi, PEER_FT_ROUTE_MAP, + RMAP_OUT)) + vty_out(vty, " neighbor %s route-map %s out\n", addr, + filter->map[RMAP_OUT].name); + + /* unsuppress-map */ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_UNSUPPRESS_MAP, 0)) + vty_out(vty, " neighbor %s unsuppress-map %s\n", addr, + filter->usmap.name); + + /* advertise-map : always applied in OUT direction*/ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_ADVERTISE_MAP, + CONDITION_NON_EXIST)) + vty_out(vty, + " neighbor %s advertise-map %s non-exist-map %s\n", + addr, filter->advmap.aname, filter->advmap.cname); + + if (peergroup_filter_check(peer, afi, safi, PEER_FT_ADVERTISE_MAP, + CONDITION_EXIST)) + vty_out(vty, " neighbor %s advertise-map %s exist-map %s\n", + addr, filter->advmap.aname, filter->advmap.cname); + + /* filter-list. */ + if (peergroup_filter_check(peer, afi, safi, PEER_FT_FILTER_LIST, + FILTER_IN)) + vty_out(vty, " neighbor %s filter-list %s in\n", addr, + filter->aslist[FILTER_IN].name); + + if (peergroup_filter_check(peer, afi, safi, PEER_FT_FILTER_LIST, + FILTER_OUT)) + vty_out(vty, " neighbor %s filter-list %s out\n", addr, + filter->aslist[FILTER_OUT].name); +} + +/* BGP peer configuration display function. */ +static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp, + struct peer *peer) +{ + struct peer *g_peer = NULL; + char *addr; + int if_pg_printed = false; + int if_ras_printed = false; + + /* Skip dynamic neighbors. */ + if (peer_dynamic_neighbor(peer)) + return; + + if (peer->conf_if) + addr = peer->conf_if; + else + addr = peer->host; + + /************************************ + ****** Global to the neighbor ****** + ************************************/ + if (peer->conf_if) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) + vty_out(vty, " neighbor %s interface v6only", addr); + else + vty_out(vty, " neighbor %s interface", addr); + + if (peer_group_active(peer)) { + vty_out(vty, " peer-group %s", peer->group->name); + if_pg_printed = true; + } else if (peer->as_type == AS_SPECIFIED) { + vty_out(vty, " remote-as %s", peer->as_pretty); + if_ras_printed = true; + } else if (peer->as_type == AS_INTERNAL) { + vty_out(vty, " remote-as internal"); + if_ras_printed = true; + } else if (peer->as_type == AS_EXTERNAL) { + vty_out(vty, " remote-as external"); + if_ras_printed = true; + } + + vty_out(vty, "\n"); + } + + /* remote-as and peer-group */ + /* peer is a member of a peer-group */ + if (peer_group_active(peer)) { + g_peer = peer->group->conf; + + if (g_peer->as_type == AS_UNSPECIFIED && !if_ras_printed) { + if (peer->as_type == AS_SPECIFIED) { + vty_out(vty, " neighbor %s remote-as %s\n", + addr, peer->as_pretty); + } else if (peer->as_type == AS_INTERNAL) { + vty_out(vty, + " neighbor %s remote-as internal\n", + addr); + } else if (peer->as_type == AS_EXTERNAL) { + vty_out(vty, + " neighbor %s remote-as external\n", + addr); + } + } + + /* For swpX peers we displayed the peer-group + * via 'neighbor swpX interface peer-group PGNAME' */ + if (!if_pg_printed) + vty_out(vty, " neighbor %s peer-group %s\n", addr, + peer->group->name); + } + + /* peer is NOT a member of a peer-group */ + else { + /* peer is a peer-group, declare the peer-group */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + vty_out(vty, " neighbor %s peer-group\n", addr); + } + + if (!if_ras_printed) { + if (peer->as_type == AS_SPECIFIED) { + vty_out(vty, " neighbor %s remote-as %s\n", + addr, peer->as_pretty); + } else if (peer->as_type == AS_INTERNAL) { + vty_out(vty, + " neighbor %s remote-as internal\n", + addr); + } else if (peer->as_type == AS_EXTERNAL) { + vty_out(vty, + " neighbor %s remote-as external\n", + addr); + } + } + } + + /* local-as */ + if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS)) { + vty_out(vty, " neighbor %s local-as %s", addr, + peer->change_local_as_pretty); + if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND)) + vty_out(vty, " no-prepend"); + if (peergroup_flag_check(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS)) + vty_out(vty, " replace-as"); + vty_out(vty, "\n"); + } + + /* description */ + if (peer->desc) { + vty_out(vty, " neighbor %s description %s\n", addr, peer->desc); + } + + /* shutdown */ + if (peergroup_flag_check(peer, PEER_FLAG_SHUTDOWN)) { + if (peer->tx_shutdown_message) + vty_out(vty, " neighbor %s shutdown message %s\n", addr, + peer->tx_shutdown_message); + else + vty_out(vty, " neighbor %s shutdown\n", addr); + } + + if (peergroup_flag_check(peer, PEER_FLAG_RTT_SHUTDOWN)) + vty_out(vty, " neighbor %s shutdown rtt %u count %u\n", addr, + peer->rtt_expected, peer->rtt_keepalive_conf); + + /* bfd */ + if (peer->bfd_config) + bgp_bfd_peer_config_write(vty, peer, addr); + + /* password */ + if (peergroup_flag_check(peer, PEER_FLAG_PASSWORD)) + vty_out(vty, " neighbor %s password %s\n", addr, + peer->password); + + /* neighbor solo */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_LONESOUL)) { + if (!peer_group_active(peer)) { + vty_out(vty, " neighbor %s solo\n", addr); + } + } + + /* BGP port */ + if (peer->port != BGP_PORT_DEFAULT) { + vty_out(vty, " neighbor %s port %d\n", addr, peer->port); + } + + /* Local interface name */ + if (peer->ifname) { + vty_out(vty, " neighbor %s interface %s\n", addr, peer->ifname); + } + + /* TCP max segment size */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_TCP_MSS)) + vty_out(vty, " neighbor %s tcp-mss %d\n", addr, peer->tcp_mss); + + /* passive */ + if (peergroup_flag_check(peer, PEER_FLAG_PASSIVE)) + vty_out(vty, " neighbor %s passive\n", addr); + + /* ebgp-multihop */ + if (peer->sort != BGP_PEER_IBGP && peer->ttl != BGP_DEFAULT_TTL + && !(peer->gtsm_hops != BGP_GTSM_HOPS_DISABLED + && peer->ttl == MAXTTL)) { + if (!peer_group_active(peer) || g_peer->ttl != peer->ttl) { + if (peer->ttl != MAXTTL) + vty_out(vty, " neighbor %s ebgp-multihop %d\n", + addr, peer->ttl); + else + vty_out(vty, " neighbor %s ebgp-multihop\n", + addr); + } + } + + /* aigp */ + if (peergroup_flag_check(peer, PEER_FLAG_AIGP)) + vty_out(vty, " neighbor %s aigp\n", addr); + + /* graceful-shutdown */ + if (peergroup_flag_check(peer, PEER_FLAG_GRACEFUL_SHUTDOWN)) + vty_out(vty, " neighbor %s graceful-shutdown\n", addr); + + /* role */ + if (peergroup_flag_check(peer, PEER_FLAG_ROLE) && + peer->local_role != ROLE_UNDEFINED) + vty_out(vty, " neighbor %s local-role %s%s\n", addr, + bgp_get_name_by_role(peer->local_role), + CHECK_FLAG(peer->flags, PEER_FLAG_ROLE_STRICT_MODE) + ? " strict-mode" + : ""); + + if (peer->sub_sort == BGP_PEER_EBGP_OAD) + vty_out(vty, " neighbor %s oad\n", addr); + + /* ttl-security hops */ + if (peer->gtsm_hops != BGP_GTSM_HOPS_DISABLED) { + if (!peer_group_active(peer) + || g_peer->gtsm_hops != peer->gtsm_hops) { + vty_out(vty, " neighbor %s ttl-security hops %d\n", + addr, peer->gtsm_hops); + } + } + + /* disable-connected-check */ + if (peergroup_flag_check(peer, PEER_FLAG_DISABLE_CONNECTED_CHECK)) + vty_out(vty, " neighbor %s disable-connected-check\n", addr); + + /* link-bw-encoding-ieee */ + if (peergroup_flag_check(peer, PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE)) + vty_out(vty, " neighbor %s disable-link-bw-encoding-ieee\n", + addr); + + if (peergroup_flag_check(peer, PEER_FLAG_EXTENDED_LINK_BANDWIDTH)) + vty_out(vty, " neighbor %s extended-link-bandwidth\n", addr); + + /* extended-optional-parameters */ + if (peergroup_flag_check(peer, PEER_FLAG_EXTENDED_OPT_PARAMS)) + vty_out(vty, " neighbor %s extended-optional-parameters\n", + addr); + + /* enforce-first-as */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ENFORCE_FIRST_AS)) { + if (!peergroup_flag_check(peer, PEER_FLAG_ENFORCE_FIRST_AS)) + vty_out(vty, " no neighbor %s enforce-first-as\n", addr); + } else { + if (peergroup_flag_check(peer, PEER_FLAG_ENFORCE_FIRST_AS)) + vty_out(vty, " neighbor %s enforce-first-as\n", addr); + } + + /* update-source */ + if (peergroup_flag_check(peer, PEER_FLAG_UPDATE_SOURCE)) { + if (peer->update_source) + vty_out(vty, " neighbor %s update-source %pSU\n", addr, + peer->update_source); + else if (peer->update_if) + vty_out(vty, " neighbor %s update-source %s\n", addr, + peer->update_if); + } + + /* advertisement-interval */ + if (peergroup_flag_check(peer, PEER_FLAG_ROUTEADV)) + vty_out(vty, " neighbor %s advertisement-interval %u\n", addr, + peer->routeadv); + + /* timers */ + if (peergroup_flag_check(peer, PEER_FLAG_TIMER)) + vty_out(vty, " neighbor %s timers %u %u\n", addr, + peer->keepalive, peer->holdtime); + + /* timers connect */ + if (peergroup_flag_check(peer, PEER_FLAG_TIMER_CONNECT)) + vty_out(vty, " neighbor %s timers connect %u\n", addr, + peer->connect); + /* need special-case handling for changed default values due to + * config profile / version (because there is no "timers bgp connect" + * command, we need to save this per-peer :/) + */ + else if (!peer_group_active(peer) && !peer->connect && + peer->bgp->default_connect_retry != SAVE_BGP_CONNECT_RETRY) + vty_out(vty, " neighbor %s timers connect %u\n", addr, + peer->bgp->default_connect_retry); + + /* timers delayopen */ + if (peergroup_flag_check(peer, PEER_FLAG_TIMER_DELAYOPEN)) + vty_out(vty, " neighbor %s timers delayopen %u\n", addr, + peer->delayopen); + /* Save config even though flag is not set if default values have been + * changed + */ + else if (!peer_group_active(peer) && !peer->delayopen + && peer->bgp->default_delayopen != BGP_DEFAULT_DELAYOPEN) + vty_out(vty, " neighbor %s timers delayopen %u\n", addr, + peer->bgp->default_delayopen); + + /* capability software-version */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY)) { + if (!peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) + vty_out(vty, " no neighbor %s capability dynamic\n", + addr); + } else { + if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) + vty_out(vty, " neighbor %s capability dynamic\n", addr); + } + + /* capability extended-nexthop */ + if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_ENHE)) { + if (CHECK_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_ENHE) && + !peer->conf_if) + vty_out(vty, + " no neighbor %s capability extended-nexthop\n", + addr); + else if (!peer->conf_if) + vty_out(vty, + " neighbor %s capability extended-nexthop\n", + addr); + } + + /* capability software-version */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY)) { + if (!peergroup_flag_check(peer, + PEER_FLAG_CAPABILITY_SOFT_VERSION)) + vty_out(vty, + " no neighbor %s capability software-version\n", + addr); + } else { + if (peergroup_flag_check(peer, + PEER_FLAG_CAPABILITY_SOFT_VERSION)) + vty_out(vty, + " neighbor %s capability software-version\n", + addr); + } + + /* dont-capability-negotiation */ + if (peergroup_flag_check(peer, PEER_FLAG_DONT_CAPABILITY)) + vty_out(vty, " neighbor %s dont-capability-negotiate\n", addr); + + /* capability fqdn */ + if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_FQDN)) + vty_out(vty, + " no neighbor %s capability fqdn\n", + addr); + + /* override-capability */ + if (peergroup_flag_check(peer, PEER_FLAG_OVERRIDE_CAPABILITY)) + vty_out(vty, " neighbor %s override-capability\n", addr); + + /* strict-capability-match */ + if (peergroup_flag_check(peer, PEER_FLAG_STRICT_CAP_MATCH)) + vty_out(vty, " neighbor %s strict-capability-match\n", addr); + + /* Sender side AS path loop detection. */ + if (peergroup_flag_check(peer, PEER_FLAG_AS_LOOP_DETECTION)) + vty_out(vty, " neighbor %s sender-as-path-loop-detection\n", + addr); + + /* path-attribute discard */ + char discard_attrs_str[BUFSIZ] = {0}; + bool discard_attrs = bgp_path_attribute_discard( + peer, discard_attrs_str, sizeof(discard_attrs_str)); + + if (discard_attrs) + vty_out(vty, " neighbor %s path-attribute discard %s\n", addr, + discard_attrs_str); + + /* path-attribute treat-as-withdraw */ + char withdraw_attrs_str[BUFSIZ] = {0}; + bool withdraw_attrs = bgp_path_attribute_treat_as_withdraw( + peer, withdraw_attrs_str, sizeof(withdraw_attrs_str)); + + if (withdraw_attrs) + vty_out(vty, + " neighbor %s path-attribute treat-as-withdraw %s\n", + addr, withdraw_attrs_str); + + if (!CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT)) { + + if (CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) { + vty_out(vty, + " neighbor %s graceful-restart-helper\n", addr); + } else if (CHECK_FLAG( + peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)) { + vty_out(vty, + " neighbor %s graceful-restart\n", addr); + } else if ( + (!(CHECK_FLAG(peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_HELPER)) + && !(CHECK_FLAG( + peer->peer_gr_new_status_flag, + PEER_GRACEFUL_RESTART_NEW_STATE_RESTART)))) { + vty_out(vty, " neighbor %s graceful-restart-disable\n", + addr); + } + } +} + +/* BGP peer configuration display function. */ +static void bgp_config_write_peer_af(struct vty *vty, struct bgp *bgp, + struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *g_peer = NULL; + char *addr; + bool flag_scomm, flag_secomm, flag_slcomm; + + /* Skip dynamic neighbors. */ + if (peer_dynamic_neighbor(peer)) + return; + + if (peer->conf_if) + addr = peer->conf_if; + else + addr = peer->host; + + /************************************ + ****** Per AF to the neighbor ****** + ************************************/ + if (peer_group_active(peer)) { + g_peer = peer->group->conf; + + /* If the peer-group is active but peer is not, print a 'no + * activate' */ + if (g_peer->afc[afi][safi] && !peer->afc[afi][safi]) { + vty_out(vty, " no neighbor %s activate\n", addr); + } + + /* If the peer-group is not active but peer is, print an + 'activate' */ + else if (!g_peer->afc[afi][safi] && peer->afc[afi][safi]) { + vty_out(vty, " neighbor %s activate\n", addr); + } + } else { + if (peer->afc[afi][safi]) { + if (safi == SAFI_ENCAP) + vty_out(vty, " neighbor %s activate\n", addr); + else if (!bgp->default_af[afi][safi]) + vty_out(vty, " neighbor %s activate\n", addr); + } else { + if (bgp->default_af[afi][safi]) + vty_out(vty, " no neighbor %s activate\n", + addr); + } + } + + /* addpath TX knobs */ + if (peergroup_af_addpath_check(peer, afi, safi)) { + switch (peer->addpath_type[afi][safi]) { + case BGP_ADDPATH_ALL: + vty_out(vty, " neighbor %s addpath-tx-all-paths\n", + addr); + break; + case BGP_ADDPATH_BEST_PER_AS: + vty_out(vty, + " neighbor %s addpath-tx-bestpath-per-AS\n", + addr); + break; + case BGP_ADDPATH_BEST_SELECTED: + if (peer->addpath_best_selected[afi][safi]) + vty_out(vty, + " neighbor %s addpath-tx-best-selected %u\n", + addr, + peer->addpath_best_selected[afi][safi]); + break; + case BGP_ADDPATH_MAX: + case BGP_ADDPATH_NONE: + break; + } + } + + if (CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_DISABLE_ADDPATH_RX)) + vty_out(vty, " neighbor %s disable-addpath-rx\n", addr); + + if (CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ADDPATH_RX_PATHS_LIMIT)) + vty_out(vty, " neighbor %s addpath-rx-paths-limit %u\n", addr, + peer->addpath_paths_limit[afi][safi].send); + + /* ORF capability. */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ORF_PREFIX_SM) + || peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_ORF_PREFIX_RM)) { + vty_out(vty, " neighbor %s capability orf prefix-list", addr); + + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_ORF_PREFIX_SM) + && peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_ORF_PREFIX_RM)) + vty_out(vty, " both"); + else if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_ORF_PREFIX_SM)) + vty_out(vty, " send"); + else + vty_out(vty, " receive"); + vty_out(vty, "\n"); + } + + /* Route reflector client. */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_REFLECTOR_CLIENT)) { + vty_out(vty, " neighbor %s route-reflector-client\n", addr); + } + + /* next-hop-self force */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_FORCE_NEXTHOP_SELF)) { + vty_out(vty, " neighbor %s next-hop-self force\n", addr); + } + + /* next-hop-self */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_NEXTHOP_SELF)) { + vty_out(vty, " neighbor %s next-hop-self\n", addr); + } + + /* remove-private-AS */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE)) { + vty_out(vty, " neighbor %s remove-private-AS all replace-AS\n", + addr); + } + + else if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE)) { + vty_out(vty, " neighbor %s remove-private-AS replace-AS\n", + addr); + } + + else if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS_ALL)) { + vty_out(vty, " neighbor %s remove-private-AS all\n", addr); + } + + else if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_REMOVE_PRIVATE_AS)) { + vty_out(vty, " neighbor %s remove-private-AS\n", addr); + } + + /* as-override */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_AS_OVERRIDE)) { + vty_out(vty, " neighbor %s as-override\n", addr); + } + + /* send-community print. */ + flag_scomm = peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_SEND_COMMUNITY); + flag_secomm = peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY); + flag_slcomm = peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_SEND_LARGE_COMMUNITY); + + if (flag_scomm && flag_secomm && flag_slcomm) { + vty_out(vty, " no neighbor %s send-community all\n", addr); + } else { + if (flag_scomm) + vty_out(vty, " no neighbor %s send-community\n", addr); + if (flag_secomm) + vty_out(vty, + " no neighbor %s send-community extended\n", + addr); + + if (flag_slcomm) + vty_out(vty, " no neighbor %s send-community large\n", + addr); + + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI)) + vty_out(vty, + " no neighbor %s send-community extended rpki\n", + addr); + } + + /* Default information */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_DEFAULT_ORIGINATE)) { + vty_out(vty, " neighbor %s default-originate", addr); + + if (peer->default_rmap[afi][safi].name) + vty_out(vty, " route-map %s", + peer->default_rmap[afi][safi].name); + + vty_out(vty, "\n"); + } + + /* Soft reconfiguration inbound. */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SOFT_RECONFIG)) { + vty_out(vty, " neighbor %s soft-reconfiguration inbound\n", + addr); + } + + /* maximum-prefix. */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_MAX_PREFIX)) { + vty_out(vty, " neighbor %s maximum-prefix %u", addr, + peer->pmax[afi][safi]); + + if (peer->pmax_threshold[afi][safi] + != MAXIMUM_PREFIX_THRESHOLD_DEFAULT) + vty_out(vty, " %u", peer->pmax_threshold[afi][safi]); + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_MAX_PREFIX_WARNING)) + vty_out(vty, " warning-only"); + if (peer->pmax_restart[afi][safi]) + vty_out(vty, " restart %u", + peer->pmax_restart[afi][safi]); + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_MAX_PREFIX_FORCE)) + vty_out(vty, " force"); + + vty_out(vty, "\n"); + } + + /* maximum-prefix-out */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_MAX_PREFIX_OUT)) + vty_out(vty, " neighbor %s maximum-prefix-out %u\n", + addr, peer->pmax_out[afi][safi]); + + /* Route server client. */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_RSERVER_CLIENT)) { + vty_out(vty, " neighbor %s route-server-client\n", addr); + } + + /* Nexthop-local unchanged. */ + if (peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED)) { + vty_out(vty, " neighbor %s nexthop-local unchanged\n", addr); + } + + /* allowas-in <1-10> */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ALLOWAS_IN)) { + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_ALLOWAS_IN_ORIGIN)) { + vty_out(vty, " neighbor %s allowas-in origin\n", addr); + } else if (peer->allowas_in[afi][safi] == 3) { + vty_out(vty, " neighbor %s allowas-in\n", addr); + } else { + vty_out(vty, " neighbor %s allowas-in %d\n", addr, + peer->allowas_in[afi][safi]); + } + } + + /* accept-own */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_ACCEPT_OWN)) + vty_out(vty, " neighbor %s accept-own\n", addr); + + /* soo */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_SOO)) { + char *soo_str = ecommunity_ecom2str( + peer->soo[afi][safi], ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + vty_out(vty, " neighbor %s soo %s\n", addr, soo_str); + XFREE(MTYPE_ECOMMUNITY_STR, soo_str); + } + + /* weight */ + if (peergroup_af_flag_check(peer, afi, safi, PEER_FLAG_WEIGHT)) + vty_out(vty, " neighbor %s weight %lu\n", addr, + peer->weight[afi][safi]); + + /* Filter. */ + bgp_config_write_filter(vty, peer, afi, safi); + + /* atribute-unchanged. */ + if (peer_af_flag_check(peer, afi, safi, PEER_FLAG_AS_PATH_UNCHANGED) + || (safi != SAFI_EVPN + && peer_af_flag_check(peer, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED)) + || peer_af_flag_check(peer, afi, safi, PEER_FLAG_MED_UNCHANGED)) { + + if (!peer_group_active(peer) + || peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED) + || peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED) + || peergroup_af_flag_check(peer, afi, safi, + PEER_FLAG_MED_UNCHANGED)) { + + vty_out(vty, + " neighbor %s attribute-unchanged%s%s%s\n", + addr, + peer_af_flag_check(peer, afi, safi, + PEER_FLAG_AS_PATH_UNCHANGED) + ? " as-path" + : "", + peer_af_flag_check(peer, afi, safi, + PEER_FLAG_NEXTHOP_UNCHANGED) + ? " next-hop" + : "", + peer_af_flag_check(peer, afi, safi, + PEER_FLAG_MED_UNCHANGED) + ? " med" + : ""); + } + } +} + +static void bgp_vpn_config_write(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + if (!CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL)) + vty_out(vty, " no bgp retain route-target all\n"); +} + +/* Address family based peer configuration display. */ +static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct peer *peer; + struct peer_group *group; + struct listnode *node, *nnode; + + + vty_frame(vty, " !\n address-family "); + if (afi == AFI_IP) { + if (safi == SAFI_UNICAST) + vty_frame(vty, "ipv4 unicast"); + else if (safi == SAFI_LABELED_UNICAST) + vty_frame(vty, "ipv4 labeled-unicast"); + else if (safi == SAFI_MULTICAST) + vty_frame(vty, "ipv4 multicast"); + else if (safi == SAFI_MPLS_VPN) + vty_frame(vty, "ipv4 vpn"); + else if (safi == SAFI_ENCAP) + vty_frame(vty, "ipv4 encap"); + else if (safi == SAFI_FLOWSPEC) + vty_frame(vty, "ipv4 flowspec"); + } else if (afi == AFI_IP6) { + if (safi == SAFI_UNICAST) + vty_frame(vty, "ipv6 unicast"); + else if (safi == SAFI_LABELED_UNICAST) + vty_frame(vty, "ipv6 labeled-unicast"); + else if (safi == SAFI_MULTICAST) + vty_frame(vty, "ipv6 multicast"); + else if (safi == SAFI_MPLS_VPN) + vty_frame(vty, "ipv6 vpn"); + else if (safi == SAFI_ENCAP) + vty_frame(vty, "ipv6 encap"); + else if (safi == SAFI_FLOWSPEC) + vty_frame(vty, "ipv6 flowspec"); + } else if (afi == AFI_L2VPN) { + if (safi == SAFI_EVPN) + vty_frame(vty, "l2vpn evpn"); + } + vty_frame(vty, "\n"); + + bgp_config_write_distance(vty, bgp, afi, safi); + + bgp_config_write_network(vty, bgp, afi, safi); + + bgp_config_write_redistribute(vty, bgp, afi, safi); + + /* BGP flag dampening. */ + if (CHECK_FLAG(bgp->af_flags[afi][safi], BGP_CONFIG_DAMPENING)) + bgp_config_write_damp(vty, bgp, afi, safi); + for (ALL_LIST_ELEMENTS_RO(bgp->group, node, group)) + if (peer_af_flag_check(group->conf, afi, safi, + PEER_FLAG_CONFIG_DAMPENING)) + bgp_config_write_peer_damp(vty, group->conf, afi, safi); + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) && + peer_af_flag_check(peer, afi, safi, + PEER_FLAG_CONFIG_DAMPENING)) + bgp_config_write_peer_damp(vty, peer, afi, safi); + + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) + bgp_config_write_peer_af(vty, bgp, group->conf, afi, safi); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + /* Do not display doppelganger peers */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + bgp_config_write_peer_af(vty, bgp, peer, afi, safi); + } + + bgp_config_write_maxpaths(vty, bgp, afi, safi); + bgp_config_write_table_map(vty, bgp, afi, safi); + + if (safi == SAFI_EVPN) + bgp_config_write_evpn_info(vty, bgp, afi, safi); + + if (safi == SAFI_FLOWSPEC) + bgp_fs_config_write_pbr(vty, bgp, afi, safi); + + if (safi == SAFI_MPLS_VPN) + bgp_vpn_config_write(vty, bgp, afi, safi); + + if (safi == SAFI_UNICAST) { + bgp_vpn_policy_config_write_afi(vty, bgp, afi); + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT)) { + + vty_out(vty, " export vpn\n"); + } + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT)) { + + vty_out(vty, " import vpn\n"); + } + if (CHECK_FLAG(bgp->af_flags[afi][safi], + BGP_CONFIG_VRF_TO_VRF_IMPORT)) { + char *name; + + for (ALL_LIST_ELEMENTS_RO( + bgp->vpn_policy[afi].import_vrf, node, + name)) + vty_out(vty, " import vrf %s\n", name); + } + } + + vty_endframe(vty, " exit-address-family\n"); +} + +int bgp_config_write(struct vty *vty) +{ + struct bgp *bgp; + struct peer_group *group; + struct peer *peer; + struct listnode *node, *nnode; + struct listnode *mnode, *mnnode; + afi_t afi; + safi_t safi; + uint32_t tovpn_sid_index = 0; + + hook_call(bgp_snmp_traps_config_write, vty); + + if (bm->rmap_update_timer != RMAP_DEFAULT_UPDATE_TIMER) + vty_out(vty, "bgp route-map delay-timer %u\n", + bm->rmap_update_timer); + + if (bm->v_update_delay != BGP_UPDATE_DELAY_DEF) { + vty_out(vty, "bgp update-delay %d", bm->v_update_delay); + if (bm->v_update_delay != bm->v_establish_wait) + vty_out(vty, " %d", bm->v_establish_wait); + vty_out(vty, "\n"); + } + + if (bm->wait_for_fib) + vty_out(vty, "bgp suppress-fib-pending\n"); + + if (CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) + vty_out(vty, "bgp graceful-shutdown\n"); + + /* No-RIB (Zebra) option flag configuration */ + if (bgp_option_check(BGP_OPT_NO_FIB)) + vty_out(vty, "bgp no-rib\n"); + + if (CHECK_FLAG(bm->flags, BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA)) + vty_out(vty, "bgp send-extra-data zebra\n"); + + /* DSCP value for outgoing packets in BGP connections */ + if (bm->ip_tos != IPTOS_PREC_INTERNETCONTROL) + vty_out(vty, "bgp session-dscp %u\n", bm->ip_tos >> 2); + + /* BGP InQ limit */ + if (bm->inq_limit != BM_DEFAULT_Q_LIMIT) + vty_out(vty, "bgp input-queue-limit %u\n", bm->inq_limit); + + if (bm->outq_limit != BM_DEFAULT_Q_LIMIT) + vty_out(vty, "bgp output-queue-limit %u\n", bm->outq_limit); + + /* BGP configuration. */ + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + + /* skip all auto created vrf as they dont have user config */ + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + + /* Router bgp ASN */ + vty_out(vty, "router bgp %s", bgp->as_pretty); + + if (bgp->name) + vty_out(vty, " %s %s", + (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + ? "view" : "vrf", bgp->name); + if (CHECK_FLAG(bgp->config, BGP_CONFIG_ASNOTATION)) + vty_out(vty, " as-notation %s", + asn_mode2str(bgp->asnotation)); + + vty_out(vty, "\n"); + + /* BGP fast-external-failover. */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER)) + vty_out(vty, " no bgp fast-external-failover\n"); + + /* BGP router ID. */ + if (bgp->router_id_static.s_addr != INADDR_ANY) + vty_out(vty, " bgp router-id %pI4\n", + &bgp->router_id_static); + + /* Suppress fib pending */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_FIB_PENDING)) + vty_out(vty, " bgp suppress-fib-pending\n"); + + /* BGP log-neighbor-changes. */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LOG_NEIGHBOR_CHANGES) + != SAVE_BGP_LOG_NEIGHBOR_CHANGES) + vty_out(vty, " %sbgp log-neighbor-changes\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_LOG_NEIGHBOR_CHANGES) + ? "" + : "no "); + + /* BGP configuration. */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ALWAYS_COMPARE_MED)) + vty_out(vty, " bgp always-compare-med\n"); + + /* RFC8212 default eBGP policy. */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_EBGP_REQUIRES_POLICY) + != SAVE_BGP_EBGP_REQUIRES_POLICY) + vty_out(vty, " %sbgp ebgp-requires-policy\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_EBGP_REQUIRES_POLICY) + ? "" + : "no "); + + /* bgp enforce-first-as */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_ENFORCE_FIRST_AS) != + SAVE_BGP_ENFORCE_FIRST_AS) + vty_out(vty, " %sbgp enforce-first-as\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_ENFORCE_FIRST_AS) + ? "" + : "no "); + + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV4_EXPLICIT_NULL) && + !!CHECK_FLAG(bgp->flags, BGP_FLAG_LU_IPV6_EXPLICIT_NULL)) + vty_out(vty, " bgp labeled-unicast explicit-null\n"); + else if (!!CHECK_FLAG(bgp->flags, + BGP_FLAG_LU_IPV4_EXPLICIT_NULL)) + vty_out(vty, + " bgp labeled-unicast ipv4-explicit-null\n"); + else if (!!CHECK_FLAG(bgp->flags, + BGP_FLAG_LU_IPV6_EXPLICIT_NULL)) + vty_out(vty, + " bgp labeled-unicast ipv6-explicit-null\n"); + + /* draft-ietf-idr-deprecate-as-set-confed-set */ + if (bgp->reject_as_sets) + vty_out(vty, " bgp reject-as-sets\n"); + + /* Suppress duplicate updates if the route actually not changed + */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_DUPLICATES) + != SAVE_BGP_SUPPRESS_DUPLICATES) + vty_out(vty, " %sbgp suppress-duplicates\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_SUPPRESS_DUPLICATES) + ? "" + : "no "); + + /* Send Hard Reset CEASE Notification for 'Administrative Reset' + */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_HARD_ADMIN_RESET) != + SAVE_BGP_HARD_ADMIN_RESET) + vty_out(vty, " %sbgp hard-administrative-reset\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_HARD_ADMIN_RESET) + ? "" + : "no "); + + /* BGP default - */ + FOREACH_AFI_SAFI (afi, safi) { + if (afi == AFI_IP && safi == SAFI_UNICAST) { + if (!bgp->default_af[afi][safi]) + vty_out(vty, " no bgp default %s\n", + get_bgp_default_af_flag(afi, + safi)); + } else if (bgp->default_af[afi][safi]) + vty_out(vty, " bgp default %s\n", + get_bgp_default_af_flag(afi, safi)); + } + + /* BGP default local-preference. */ + if (bgp->default_local_pref != BGP_DEFAULT_LOCAL_PREF) + vty_out(vty, " bgp default local-preference %u\n", + bgp->default_local_pref); + + /* BGP default show-hostname */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_SHOW_HOSTNAME) + != SAVE_BGP_SHOW_HOSTNAME) + vty_out(vty, " %sbgp default show-hostname\n", + CHECK_FLAG(bgp->flags, BGP_FLAG_SHOW_HOSTNAME) + ? "" + : "no "); + + /* BGP default show-nexthop-hostname */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_SHOW_NEXTHOP_HOSTNAME) + != SAVE_BGP_SHOW_HOSTNAME) + vty_out(vty, " %sbgp default show-nexthop-hostname\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_SHOW_NEXTHOP_HOSTNAME) + ? "" + : "no "); + + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY) != + SAVE_BGP_SOFT_VERSION_CAPABILITY) + vty_out(vty, + " %sbgp default software-version-capability\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_SOFT_VERSION_CAPABILITY) + ? "" + : "no "); + + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY) != + SAVE_BGP_DYNAMIC_CAPABILITY) + vty_out(vty, + " %sbgp default dynamic-capability\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_DYNAMIC_CAPABILITY) + ? "" + : "no "); + + /* BGP default subgroup-pkt-queue-max. */ + if (bgp->default_subgroup_pkt_queue_max + != BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX) + vty_out(vty, " bgp default subgroup-pkt-queue-max %u\n", + bgp->default_subgroup_pkt_queue_max); + + /* BGP client-to-client reflection. */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_NO_CLIENT_TO_CLIENT)) + vty_out(vty, " no bgp client-to-client reflection\n"); + + /* BGP cluster ID. */ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CLUSTER_ID)) + vty_out(vty, " bgp cluster-id %pI4\n", + &bgp->cluster_id); + + /* Disable ebgp connected nexthop check */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + vty_out(vty, + " bgp disable-ebgp-connected-route-check\n"); + + /* Confederation identifier*/ + if (CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) + vty_out(vty, " bgp confederation identifier %s\n", + bgp->confed_id_pretty); + + /* Confederation peer */ + if (bgp->confed_peers_cnt > 0) { + int i; + + vty_out(vty, " bgp confederation peers"); + + for (i = 0; i < bgp->confed_peers_cnt; i++) + vty_out(vty, " %s", + bgp->confed_peers[i].as_pretty); + + vty_out(vty, "\n"); + } + + /* BGP deterministic-med. */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) + != SAVE_BGP_DETERMINISTIC_MED) + vty_out(vty, " %sbgp deterministic-med\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_DETERMINISTIC_MED) + ? "" + : "no "); + + /* BGP update-delay. */ + bgp_config_write_update_delay(vty, bgp); + + if (bgp->v_maxmed_onstartup + != BGP_MAXMED_ONSTARTUP_UNCONFIGURED) { + vty_out(vty, " bgp max-med on-startup %u", + bgp->v_maxmed_onstartup); + if (bgp->maxmed_onstartup_value + != BGP_MAXMED_VALUE_DEFAULT) + vty_out(vty, " %u", + bgp->maxmed_onstartup_value); + vty_out(vty, "\n"); + } + if (bgp->v_maxmed_admin != BGP_MAXMED_ADMIN_UNCONFIGURED) { + vty_out(vty, " bgp max-med administrative"); + if (bgp->maxmed_admin_value != BGP_MAXMED_VALUE_DEFAULT) + vty_out(vty, " %u", bgp->maxmed_admin_value); + vty_out(vty, "\n"); + } + + /* write quanta */ + bgp_config_write_wpkt_quanta(vty, bgp); + /* read quanta */ + bgp_config_write_rpkt_quanta(vty, bgp); + + /* coalesce time */ + bgp_config_write_coalesce_time(vty, bgp); + + /* BGP per-instance graceful-shutdown */ + /* BGP-wide settings and per-instance settings are mutually + * exclusive. + */ + if (!CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)) + if (CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN)) + vty_out(vty, " bgp graceful-shutdown\n"); + + /* Long-lived Graceful Restart */ + if (bgp->llgr_stale_time != BGP_DEFAULT_LLGR_STALE_TIME) + vty_out(vty, + " bgp long-lived-graceful-restart stale-time %u\n", + bgp->llgr_stale_time); + + /* BGP graceful-restart. */ + if (bgp->stalepath_time != BGP_DEFAULT_STALEPATH_TIME) + vty_out(vty, + " bgp graceful-restart stalepath-time %u\n", + bgp->stalepath_time); + + if (bgp->restart_time != BGP_DEFAULT_RESTART_TIME) + vty_out(vty, " bgp graceful-restart restart-time %u\n", + bgp->restart_time); + + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_NOTIFICATION) != + SAVE_BGP_GRACEFUL_NOTIFICATION) + vty_out(vty, " %sbgp graceful-restart notification\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_GRACEFUL_NOTIFICATION) + ? "" + : "no "); + + if (bgp->select_defer_time != BGP_DEFAULT_SELECT_DEFERRAL_TIME) + vty_out(vty, + " bgp graceful-restart select-defer-time %u\n", + bgp->select_defer_time); + + if (bgp_global_gr_mode_get(bgp) == GLOBAL_GR) + vty_out(vty, " bgp graceful-restart\n"); + + if (bgp_global_gr_mode_get(bgp) == GLOBAL_DISABLE) + vty_out(vty, " bgp graceful-restart-disable\n"); + + /* BGP graceful-restart Preserve State F bit. */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_GR_PRESERVE_FWD)) + vty_out(vty, + " bgp graceful-restart preserve-fw-state\n"); + + /* BGP TCP keepalive */ + bgp_config_tcp_keepalive(vty, bgp); + + /* Stale timer for RIB */ + if (bgp->rib_stale_time != BGP_DEFAULT_RIB_STALE_TIME) + vty_out(vty, + " bgp graceful-restart rib-stale-time %u\n", + bgp->rib_stale_time); + + /* BGP bestpath method. */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_IGNORE)) + vty_out(vty, " bgp bestpath as-path ignore\n"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_CONFED)) + vty_out(vty, " bgp bestpath as-path confed\n"); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ASPATH_MULTIPATH_RELAX)) { + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_MULTIPATH_RELAX_AS_SET)) { + vty_out(vty, + " bgp bestpath as-path multipath-relax as-set\n"); + } else { + vty_out(vty, + " bgp bestpath as-path multipath-relax\n"); + } + } + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY)) { + vty_out(vty, + " bgp route-reflector allow-outbound-policy\n"); + } + if (CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_ROUTER_ID)) + vty_out(vty, " bgp bestpath compare-routerid\n"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_COMPARE_AIGP)) + vty_out(vty, " bgp bestpath aigp\n"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED) + || CHECK_FLAG(bgp->flags, BGP_FLAG_MED_MISSING_AS_WORST)) { + vty_out(vty, " bgp bestpath med"); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_MED_CONFED)) + vty_out(vty, " confed"); + if (CHECK_FLAG(bgp->flags, + BGP_FLAG_MED_MISSING_AS_WORST)) + vty_out(vty, " missing-as-worst"); + vty_out(vty, "\n"); + } + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX)) + vty_out(vty, + " bgp bestpath peer-type multipath-relax\n"); + + /* Link bandwidth handling. */ + if (bgp->lb_handling == BGP_LINK_BW_IGNORE_BW) + vty_out(vty, " bgp bestpath bandwidth ignore\n"); + else if (bgp->lb_handling == BGP_LINK_BW_SKIP_MISSING) + vty_out(vty, " bgp bestpath bandwidth skip-missing\n"); + else if (bgp->lb_handling == BGP_LINK_BW_DEFWT_4_MISSING) + vty_out(vty, " bgp bestpath bandwidth default-weight-for-missing\n"); + + /* BGP network import check. */ + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK) + != SAVE_BGP_IMPORT_CHECK) + vty_out(vty, " %sbgp network import-check\n", + CHECK_FLAG(bgp->flags, BGP_FLAG_IMPORT_CHECK) + ? "" + : "no "); + + /* BGP timers configuration. */ + if (bgp->default_keepalive != SAVE_BGP_KEEPALIVE + || bgp->default_holdtime != SAVE_BGP_HOLDTIME) + vty_out(vty, " timers bgp %u %u\n", + bgp->default_keepalive, bgp->default_holdtime); + + /* BGP minimum holdtime configuration. */ + if (bgp->default_min_holdtime != SAVE_BGP_HOLDTIME + && bgp->default_min_holdtime != 0) + vty_out(vty, " bgp minimum-holdtime %u\n", + bgp->default_min_holdtime); + + /* Conditional advertisement timer configuration */ + if (bgp->condition_check_period + != DEFAULT_CONDITIONAL_ROUTES_POLL_TIME) + vty_out(vty, + " bgp conditional-advertisement timer %u\n", + bgp->condition_check_period); + + /* default-originate timer configuration */ + if (bgp->rmap_def_originate_eval_timer != + RMAP_DEFAULT_ORIGINATE_EVAL_TIMER) + vty_out(vty, " bgp default-originate timer %u\n", + bgp->rmap_def_originate_eval_timer); + + /* peer-group */ + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + bgp_config_write_peer_global(vty, bgp, group->conf); + } + + /* Normal neighbor configuration. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + bgp_config_write_peer_global(vty, bgp, peer); + } + + /* listen range and limit for dynamic BGP neighbors */ + bgp_config_write_listen(vty, bgp); + + /* + * BGP default autoshutdown neighbors + * + * This must be placed after any peer and peer-group + * configuration, to avoid setting all peers to shutdown after + * a daemon restart, which is undesired behavior. (see #2286) + */ + if (bgp->autoshutdown) + vty_out(vty, " bgp default shutdown\n"); + + /* BGP instance administrative shutdown */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN)) + vty_out(vty, " bgp shutdown\n"); + + if (bgp->allow_martian) + vty_out(vty, " bgp allow-martian-nexthop\n"); + + if (bgp->fast_convergence) + vty_out(vty, " bgp fast-convergence\n"); + + if (bgp->srv6_enabled) { + vty_frame(vty, " !\n segment-routing srv6\n"); + if (strlen(bgp->srv6_locator_name)) + vty_out(vty, " locator %s\n", + bgp->srv6_locator_name); + vty_endframe(vty, " exit\n"); + } + + tovpn_sid_index = bgp->tovpn_sid_index; + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_TOVPN_SID_AUTO)) { + vty_out(vty, " sid vpn per-vrf export auto\n"); + } else if (tovpn_sid_index != 0) { + vty_out(vty, " sid vpn per-vrf export %d\n", + tovpn_sid_index); + } + + /* IPv4 unicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_UNICAST); + + /* IPv4 multicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_MULTICAST); + + /* IPv4 labeled-unicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_LABELED_UNICAST); + + /* IPv4 VPN configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_MPLS_VPN); + + /* ENCAPv4 configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_ENCAP); + + /* FLOWSPEC v4 configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP, SAFI_FLOWSPEC); + + /* IPv6 unicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_UNICAST); + + /* IPv6 multicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_MULTICAST); + + /* IPv6 labeled-unicast configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, + SAFI_LABELED_UNICAST); + + /* IPv6 VPN configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_MPLS_VPN); + + /* ENCAPv6 configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_ENCAP); + + /* FLOWSPEC v6 configuration. */ + bgp_config_write_family(vty, bgp, AFI_IP6, SAFI_FLOWSPEC); + + /* EVPN configuration. */ + bgp_config_write_family(vty, bgp, AFI_L2VPN, SAFI_EVPN); + + hook_call(bgp_inst_config_write, bgp, vty); + +#ifdef ENABLE_BGP_VNC + bgp_rfapi_cfg_write(vty, bgp); +#endif + + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); + } + return 0; +} + + +/* BGP node structure. */ +static struct cmd_node bgp_node = { + .name = "bgp", + .node = BGP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = bgp_config_write, +}; + +static struct cmd_node bgp_ipv4_unicast_node = { + .name = "bgp ipv4 unicast", + .node = BGP_IPV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_ipv4_multicast_node = { + .name = "bgp ipv4 multicast", + .node = BGP_IPV4M_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_ipv4_labeled_unicast_node = { + .name = "bgp ipv4 labeled unicast", + .node = BGP_IPV4L_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_ipv6_unicast_node = { + .name = "bgp ipv6 unicast", + .node = BGP_IPV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_ipv6_multicast_node = { + .name = "bgp ipv6 multicast", + .node = BGP_IPV6M_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_ipv6_labeled_unicast_node = { + .name = "bgp ipv6 labeled unicast", + .node = BGP_IPV6L_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_vpnv4_node = { + .name = "bgp vpnv4", + .node = BGP_VPNV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_vpnv6_node = { + .name = "bgp vpnv6", + .node = BGP_VPNV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af-vpnv6)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_evpn_node = { + .name = "bgp evpn", + .node = BGP_EVPN_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-evpn)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_evpn_vni_node = { + .name = "bgp evpn vni", + .node = BGP_EVPN_VNI_NODE, + .parent_node = BGP_EVPN_NODE, + .prompt = "%s(config-router-af-vni)# ", +}; + +static struct cmd_node bgp_flowspecv4_node = { + .name = "bgp ipv4 flowspec", + .node = BGP_FLOWSPECV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_flowspecv6_node = { + .name = "bgp ipv6 flowspec", + .node = BGP_FLOWSPECV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af-vpnv6)# ", + .no_xpath = true, +}; + +static struct cmd_node bgp_srv6_node = { + .name = "bgp srv6", + .node = BGP_SRV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-srv6)# ", +}; + +static void community_list_vty(void); + +static void bgp_ac_peergroup(vector comps, struct cmd_token *token) +{ + struct bgp *bgp; + struct peer_group *group; + struct listnode *lnbgp, *lnpeer; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, lnbgp, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->group, lnpeer, group)) + vector_set(comps, + XSTRDUP(MTYPE_COMPLETION, group->name)); + } +} + +static void bgp_ac_peer(vector comps, struct cmd_token *token) +{ + struct bgp *bgp; + struct peer *peer; + struct listnode *lnbgp, *lnpeer; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, lnbgp, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, lnpeer, peer)) { + /* only provide suggestions on the appropriate input + * token type, + * they'll otherwise show up multiple times */ + enum cmd_token_type match_type; + char *name = peer->host; + + if (peer->conf_if) { + match_type = VARIABLE_TKN; + name = peer->conf_if; + } else if (strchr(peer->host, ':')) + match_type = IPV6_TKN; + else + match_type = IPV4_TKN; + + if (token->type != match_type) + continue; + + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, name)); + } + } +} + +static void bgp_ac_neighbor(vector comps, struct cmd_token *token) +{ + bgp_ac_peer(comps, token); + + if (token->type == VARIABLE_TKN) + bgp_ac_peergroup(comps, token); +} + +static const struct cmd_variable_handler bgp_var_neighbor[] = { + {.varname = "neighbor", .completions = bgp_ac_neighbor}, + {.varname = "neighbors", .completions = bgp_ac_neighbor}, + {.varname = "peer", .completions = bgp_ac_neighbor}, + {.completions = NULL}}; + +static const struct cmd_variable_handler bgp_var_peergroup[] = { + {.tokenname = "PGNAME", .completions = bgp_ac_peergroup}, + {.completions = NULL} }; + +DEFINE_HOOK(bgp_config_end, (struct bgp *bgp), (bgp)); + +static struct event *t_bgp_cfg; + +bool bgp_config_inprocess(void) +{ + return event_is_scheduled(t_bgp_cfg); +} + +/* Max wait time for config to load before post-config processing */ +#define BGP_PRE_CONFIG_MAX_WAIT_SECONDS 600 + +static void bgp_config_finish(struct event *t) +{ + struct listnode *node; + struct bgp *bgp; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) + hook_call(bgp_config_end, bgp); +} + +static void bgp_config_end_timeout(struct event *t) +{ + zlog_err("BGP configuration end timer expired after %d seconds.", + BGP_PRE_CONFIG_MAX_WAIT_SECONDS); + bgp_config_finish(t); +} + +static void bgp_config_start(void) +{ + EVENT_OFF(t_bgp_cfg); + event_add_timer(bm->master, bgp_config_end_timeout, NULL, + BGP_PRE_CONFIG_MAX_WAIT_SECONDS, &t_bgp_cfg); +} + +/* When we receive a hook the configuration is read, + * we start a timer to make sure we postpone sending + * EoR before route-maps are processed. + * This is especially valid if using `bgp route-map delay-timer`. + */ +static void bgp_config_end(void) +{ +#define BGP_POST_CONFIG_DELAY_SECONDS 1 + uint32_t bgp_post_config_delay = + event_is_scheduled(bm->t_rmap_update) + ? event_timer_remain_second(bm->t_rmap_update) + : BGP_POST_CONFIG_DELAY_SECONDS; + + /* If BGP config processing thread isn't running, then + * we can return and rely it's properly handled. + */ + if (!bgp_config_inprocess()) + return; + + EVENT_OFF(t_bgp_cfg); + + /* Start a new timer to make sure we don't send EoR + * before route-maps are processed. + */ + event_add_timer(bm->master, bgp_config_finish, NULL, + bgp_post_config_delay, &t_bgp_cfg); +} + +static int config_write_interface_one(struct vty *vty, struct vrf *vrf) +{ + int write = 0; + struct interface *ifp; + struct bgp_interface *iifp; + + FOR_ALL_INTERFACES (vrf, ifp) { + iifp = ifp->info; + if (!iifp) + continue; + + if_vty_config_start(vty, ifp); + + if (CHECK_FLAG(iifp->flags, + BGP_INTERFACE_MPLS_BGP_FORWARDING)) { + vty_out(vty, " mpls bgp forwarding\n"); + write++; + } + if (CHECK_FLAG(iifp->flags, + BGP_INTERFACE_MPLS_L3VPN_SWITCHING)) { + vty_out(vty, + " mpls bgp l3vpn-multi-domain-switching\n"); + write++; + } + + if_vty_config_end(vty); + } + + return write; +} + +/* Configuration write function for bgpd. */ +static int config_write_interface(struct vty *vty) +{ + int write = 0; + struct vrf *vrf = NULL; + + /* Display all VRF aware OSPF interface configuration */ + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + write += config_write_interface_one(vty, vrf); + } + + return write; +} + +DEFPY(mpls_bgp_forwarding, mpls_bgp_forwarding_cmd, + "[no$no] mpls bgp forwarding", + NO_STR MPLS_STR BGP_STR + "Enable MPLS forwarding for eBGP directly connected peers\n") +{ + bool check; + struct bgp_interface *iifp; + + VTY_DECLVAR_CONTEXT(interface, ifp); + iifp = ifp->info; + if (!iifp) { + vty_out(vty, "Interface %s not available\n", ifp->name); + return CMD_WARNING_CONFIG_FAILED; + } + check = CHECK_FLAG(iifp->flags, BGP_INTERFACE_MPLS_BGP_FORWARDING); + if (check != !no) { + if (no) + UNSET_FLAG(iifp->flags, + BGP_INTERFACE_MPLS_BGP_FORWARDING); + else + SET_FLAG(iifp->flags, + BGP_INTERFACE_MPLS_BGP_FORWARDING); + /* trigger a nht update on eBGP sessions */ + if (if_is_operative(ifp)) + bgp_nht_ifp_up(ifp); + } + return CMD_SUCCESS; +} + +DEFPY(mpls_bgp_l3vpn_multi_domain_switching, + mpls_bgp_l3vpn_multi_domain_switching_cmd, + "[no$no] mpls bgp l3vpn-multi-domain-switching", + NO_STR MPLS_STR BGP_STR + "Bind a local MPLS label to incoming L3VPN updates\n") +{ + bool check; + struct bgp_interface *iifp; + + VTY_DECLVAR_CONTEXT(interface, ifp); + iifp = ifp->info; + if (!iifp) { + vty_out(vty, "Interface %s not available\n", ifp->name); + return CMD_WARNING_CONFIG_FAILED; + } + check = CHECK_FLAG(iifp->flags, BGP_INTERFACE_MPLS_L3VPN_SWITCHING); + if (check == !no) + return CMD_SUCCESS; + if (no) + UNSET_FLAG(iifp->flags, BGP_INTERFACE_MPLS_L3VPN_SWITCHING); + else + SET_FLAG(iifp->flags, BGP_INTERFACE_MPLS_L3VPN_SWITCHING); + /* trigger a nht update on eBGP sessions */ + if (if_is_operative(ifp)) + bgp_nht_ifp_up(ifp); + + return CMD_SUCCESS; +} + +DEFPY (bgp_inq_limit, + bgp_inq_limit_cmd, + "bgp input-queue-limit (1-4294967295)$limit", + BGP_STR + "Set the BGP Input Queue limit for all peers when message parsing\n" + "Input-Queue limit\n") +{ + bm->inq_limit = limit; + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_inq_limit, + no_bgp_inq_limit_cmd, + "no bgp input-queue-limit [(1-4294967295)$limit]", + NO_STR + BGP_STR + "Set the BGP Input Queue limit for all peers when message parsing\n" + "Input-Queue limit\n") +{ + bm->inq_limit = BM_DEFAULT_Q_LIMIT; + + return CMD_SUCCESS; +} + +DEFPY (bgp_outq_limit, + bgp_outq_limit_cmd, + "bgp output-queue-limit (1-4294967295)$limit", + BGP_STR + "Set the BGP Output Queue limit for all peers when message parsing\n" + "Output-Queue limit\n") +{ + bm->outq_limit = limit; + + return CMD_SUCCESS; +} + +DEFPY (no_bgp_outq_limit, + no_bgp_outq_limit_cmd, + "no bgp output-queue-limit [(1-4294967295)$limit]", + NO_STR + BGP_STR + "Set the BGP Output Queue limit for all peers when message parsing\n" + "Output-Queue limit\n") +{ + bm->outq_limit = BM_DEFAULT_Q_LIMIT; + + return CMD_SUCCESS; +} + + +/* Initialization of BGP interface. */ +static void bgp_vty_if_init(void) +{ + /* Install interface node. */ + if_cmd_init(config_write_interface); + + /* "mpls bgp forwarding" commands. */ + install_element(INTERFACE_NODE, &mpls_bgp_forwarding_cmd); + install_element(INTERFACE_NODE, + &mpls_bgp_l3vpn_multi_domain_switching_cmd); +} + +void bgp_vty_init(void) +{ + cmd_variable_handler_register(bgp_var_neighbor); + cmd_variable_handler_register(bgp_var_peergroup); + + cmd_init_config_callbacks(bgp_config_start, bgp_config_end); + + /* Install bgp top node. */ + install_node(&bgp_node); + install_node(&bgp_ipv4_unicast_node); + install_node(&bgp_ipv4_multicast_node); + install_node(&bgp_ipv4_labeled_unicast_node); + install_node(&bgp_ipv6_unicast_node); + install_node(&bgp_ipv6_multicast_node); + install_node(&bgp_ipv6_labeled_unicast_node); + install_node(&bgp_vpnv4_node); + install_node(&bgp_vpnv6_node); + install_node(&bgp_evpn_node); + install_node(&bgp_evpn_vni_node); + install_node(&bgp_flowspecv4_node); + install_node(&bgp_flowspecv6_node); + install_node(&bgp_srv6_node); + + /* Install default VTY commands to new nodes. */ + install_default(BGP_NODE); + install_default(BGP_IPV4_NODE); + install_default(BGP_IPV4M_NODE); + install_default(BGP_IPV4L_NODE); + install_default(BGP_IPV6_NODE); + install_default(BGP_IPV6M_NODE); + install_default(BGP_IPV6L_NODE); + install_default(BGP_VPNV4_NODE); + install_default(BGP_VPNV6_NODE); + install_default(BGP_FLOWSPECV4_NODE); + install_default(BGP_FLOWSPECV6_NODE); + install_default(BGP_EVPN_NODE); + install_default(BGP_EVPN_VNI_NODE); + install_default(BGP_SRV6_NODE); + + /* "global bgp inq-limit command */ + install_element(CONFIG_NODE, &bgp_inq_limit_cmd); + install_element(CONFIG_NODE, &no_bgp_inq_limit_cmd); + install_element(CONFIG_NODE, &bgp_outq_limit_cmd); + install_element(CONFIG_NODE, &no_bgp_outq_limit_cmd); + + /* "bgp local-mac" hidden commands. */ + install_element(CONFIG_NODE, &bgp_local_mac_cmd); + install_element(CONFIG_NODE, &no_bgp_local_mac_cmd); + + /* "bgp suppress-fib-pending" global */ + install_element(CONFIG_NODE, &bgp_global_suppress_fib_pending_cmd); + + /* bgp route-map delay-timer commands. */ + install_element(CONFIG_NODE, &bgp_set_route_map_delay_timer_cmd); + install_element(CONFIG_NODE, &no_bgp_set_route_map_delay_timer_cmd); + + install_element(BGP_NODE, &bgp_allow_martian_cmd); + + /* bgp fast-convergence command */ + install_element(BGP_NODE, &bgp_fast_convergence_cmd); + install_element(BGP_NODE, &no_bgp_fast_convergence_cmd); + + /* global bgp update-delay command */ + install_element(CONFIG_NODE, &bgp_global_update_delay_cmd); + install_element(CONFIG_NODE, &no_bgp_global_update_delay_cmd); + + /* global bgp graceful-shutdown command */ + install_element(CONFIG_NODE, &bgp_graceful_shutdown_cmd); + install_element(CONFIG_NODE, &no_bgp_graceful_shutdown_cmd); + + /* Dummy commands (Currently not supported) */ + install_element(BGP_NODE, &no_synchronization_cmd); + install_element(BGP_NODE, &no_auto_summary_cmd); + + /* "router bgp" commands. */ + install_element(CONFIG_NODE, &router_bgp_cmd); + + /* "no router bgp" commands. */ + install_element(CONFIG_NODE, &no_router_bgp_cmd); + + /* "bgp session-dscp command */ + install_element(CONFIG_NODE, &bgp_session_dscp_cmd); + install_element(CONFIG_NODE, &no_bgp_session_dscp_cmd); + + /* "bgp router-id" commands. */ + install_element(BGP_NODE, &bgp_router_id_cmd); + install_element(BGP_NODE, &no_bgp_router_id_cmd); + + /* "bgp suppress-fib-pending" command */ + install_element(BGP_NODE, &bgp_suppress_fib_pending_cmd); + + /* "bgp cluster-id" commands. */ + install_element(BGP_NODE, &bgp_cluster_id_cmd); + install_element(BGP_NODE, &no_bgp_cluster_id_cmd); + + /* "bgp no-rib" commands. */ + install_element(CONFIG_NODE, &bgp_norib_cmd); + install_element(CONFIG_NODE, &no_bgp_norib_cmd); + + install_element(CONFIG_NODE, &no_bgp_send_extra_data_cmd); + + /* "bgp confederation" commands. */ + install_element(BGP_NODE, &bgp_confederation_identifier_cmd); + install_element(BGP_NODE, &no_bgp_confederation_identifier_cmd); + + /* "bgp confederation peers" commands. */ + install_element(BGP_NODE, &bgp_confederation_peers_cmd); + install_element(BGP_NODE, &no_bgp_confederation_peers_cmd); + + /* bgp max-med command */ + install_element(BGP_NODE, &bgp_maxmed_admin_cmd); + install_element(BGP_NODE, &no_bgp_maxmed_admin_cmd); + install_element(BGP_NODE, &bgp_maxmed_admin_medv_cmd); + install_element(BGP_NODE, &bgp_maxmed_onstartup_cmd); + install_element(BGP_NODE, &no_bgp_maxmed_onstartup_cmd); + + /* "neighbor role" commands. */ + install_element(BGP_NODE, &neighbor_role_cmd); + install_element(BGP_NODE, &neighbor_role_strict_cmd); + install_element(BGP_NODE, &no_neighbor_role_cmd); + + /* "neighbor oad" commands. */ + install_element(BGP_NODE, &neighbor_oad_cmd); + + /* "neighbor aigp" commands. */ + install_element(BGP_NODE, &neighbor_aigp_cmd); + + /* "neighbor graceful-shutdown" command */ + install_element(BGP_NODE, &neighbor_graceful_shutdown_cmd); + + /* bgp disable-ebgp-connected-nh-check */ + install_element(BGP_NODE, &bgp_disable_connected_route_check_cmd); + install_element(BGP_NODE, &no_bgp_disable_connected_route_check_cmd); + + /* bgp update-delay command */ + install_element(BGP_NODE, &bgp_update_delay_cmd); + install_element(BGP_NODE, &no_bgp_update_delay_cmd); + + install_element(BGP_NODE, &bgp_wpkt_quanta_cmd); + install_element(BGP_NODE, &bgp_rpkt_quanta_cmd); + + install_element(BGP_NODE, &bgp_coalesce_time_cmd); + install_element(BGP_NODE, &no_bgp_coalesce_time_cmd); + + /* "maximum-paths" commands. */ + install_element(BGP_NODE, &bgp_maxpaths_hidden_cmd); + install_element(BGP_NODE, &no_bgp_maxpaths_hidden_cmd); + install_element(BGP_IPV4_NODE, &bgp_maxpaths_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_maxpaths_cmd); + install_element(BGP_IPV6_NODE, &bgp_maxpaths_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_maxpaths_cmd); + install_element(BGP_NODE, &bgp_maxpaths_ibgp_hidden_cmd); + install_element(BGP_NODE, &bgp_maxpaths_ibgp_cluster_hidden_cmd); + install_element(BGP_NODE, &no_bgp_maxpaths_ibgp_hidden_cmd); + install_element(BGP_IPV4_NODE, &bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV4_NODE, &bgp_maxpaths_ibgp_cluster_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV6_NODE, &bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV6_NODE, &bgp_maxpaths_ibgp_cluster_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_maxpaths_ibgp_cmd); + + install_element(BGP_IPV4L_NODE, &bgp_maxpaths_cmd); + install_element(BGP_IPV4L_NODE, &no_bgp_maxpaths_cmd); + install_element(BGP_IPV4L_NODE, &bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV4L_NODE, &bgp_maxpaths_ibgp_cluster_cmd); + install_element(BGP_IPV4L_NODE, &no_bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV6L_NODE, &bgp_maxpaths_cmd); + install_element(BGP_IPV6L_NODE, &no_bgp_maxpaths_cmd); + install_element(BGP_IPV6L_NODE, &bgp_maxpaths_ibgp_cmd); + install_element(BGP_IPV6L_NODE, &bgp_maxpaths_ibgp_cluster_cmd); + install_element(BGP_IPV6L_NODE, &no_bgp_maxpaths_ibgp_cmd); + + /* "timers bgp" commands. */ + install_element(BGP_NODE, &bgp_timers_cmd); + install_element(BGP_NODE, &no_bgp_timers_cmd); + + /* "minimum-holdtime" commands. */ + install_element(BGP_NODE, &bgp_minimum_holdtime_cmd); + install_element(BGP_NODE, &no_bgp_minimum_holdtime_cmd); + + /* route-map delay-timer commands - per instance for backwards compat. + */ + install_element(BGP_NODE, &bgp_set_route_map_delay_timer_cmd); + install_element(BGP_NODE, &no_bgp_set_route_map_delay_timer_cmd); + + /* "bgp client-to-client reflection" commands */ + install_element(BGP_NODE, &no_bgp_client_to_client_reflection_cmd); + install_element(BGP_NODE, &bgp_client_to_client_reflection_cmd); + + /* "bgp always-compare-med" commands */ + install_element(BGP_NODE, &bgp_always_compare_med_cmd); + install_element(BGP_NODE, &no_bgp_always_compare_med_cmd); + + /* bgp ebgp-requires-policy */ + install_element(BGP_NODE, &bgp_ebgp_requires_policy_cmd); + install_element(BGP_NODE, &no_bgp_ebgp_requires_policy_cmd); + + /* bgp enforce-first-as */ + install_element(BGP_NODE, &bgp_enforce_first_as_cmd); + + /* bgp labeled-unicast explicit-null */ + install_element(BGP_NODE, &bgp_lu_uses_explicit_null_cmd); + + /* bgp suppress-duplicates */ + install_element(BGP_NODE, &bgp_suppress_duplicates_cmd); + install_element(BGP_NODE, &no_bgp_suppress_duplicates_cmd); + + /* bgp reject-as-sets */ + install_element(BGP_NODE, &bgp_reject_as_sets_cmd); + install_element(BGP_NODE, &no_bgp_reject_as_sets_cmd); + + /* "bgp deterministic-med" commands */ + install_element(BGP_NODE, &bgp_deterministic_med_cmd); + install_element(BGP_NODE, &no_bgp_deterministic_med_cmd); + + /* "bgp graceful-restart" command */ + install_element(BGP_NODE, &bgp_graceful_restart_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_cmd); + + /* "bgp graceful-restart-disable" command */ + install_element(BGP_NODE, &bgp_graceful_restart_disable_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_disable_cmd); + + /* "neighbor a:b:c:d graceful-restart" command */ + install_element(BGP_NODE, &bgp_neighbor_graceful_restart_set_cmd); + install_element(BGP_NODE, &no_bgp_neighbor_graceful_restart_set_cmd); + + /* "neighbor a:b:c:d graceful-restart-disable" command */ + install_element(BGP_NODE, + &bgp_neighbor_graceful_restart_disable_set_cmd); + install_element(BGP_NODE, + &no_bgp_neighbor_graceful_restart_disable_set_cmd); + + /* "neighbor a:b:c:d graceful-restart-helper" command */ + install_element(BGP_NODE, + &bgp_neighbor_graceful_restart_helper_set_cmd); + install_element(BGP_NODE, + &no_bgp_neighbor_graceful_restart_helper_set_cmd); + + install_element(BGP_NODE, &bgp_graceful_restart_stalepath_time_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_stalepath_time_cmd); + install_element(BGP_NODE, &bgp_graceful_restart_restart_time_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_restart_time_cmd); + install_element(BGP_NODE, &bgp_graceful_restart_select_defer_time_cmd); + install_element(BGP_NODE, + &no_bgp_graceful_restart_select_defer_time_cmd); + install_element(BGP_NODE, &bgp_graceful_restart_preserve_fw_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_preserve_fw_cmd); + install_element(BGP_NODE, &bgp_graceful_restart_notification_cmd); + + install_element(BGP_NODE, &bgp_graceful_restart_disable_eor_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_disable_eor_cmd); + install_element(BGP_NODE, &bgp_graceful_restart_rib_stale_time_cmd); + install_element(BGP_NODE, &no_bgp_graceful_restart_rib_stale_time_cmd); + + /* "bgp graceful-shutdown" commands */ + install_element(BGP_NODE, &bgp_graceful_shutdown_cmd); + install_element(BGP_NODE, &no_bgp_graceful_shutdown_cmd); + + /* "bgp hard-administrative-reset" commands */ + install_element(BGP_NODE, &bgp_administrative_reset_cmd); + + /* "bgp long-lived-graceful-restart" commands */ + install_element(BGP_NODE, &bgp_llgr_stalepath_time_cmd); + install_element(BGP_NODE, &no_bgp_llgr_stalepath_time_cmd); + + /* "bgp fast-external-failover" commands */ + install_element(BGP_NODE, &bgp_fast_external_failover_cmd); + install_element(BGP_NODE, &no_bgp_fast_external_failover_cmd); + + /* "bgp bestpath aigp" commands */ + install_element(BGP_NODE, &bgp_bestpath_aigp_cmd); + + /* "bgp bestpath compare-routerid" commands */ + install_element(BGP_NODE, &bgp_bestpath_compare_router_id_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_compare_router_id_cmd); + + /* "bgp bestpath as-path ignore" commands */ + install_element(BGP_NODE, &bgp_bestpath_aspath_ignore_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_aspath_ignore_cmd); + + /* "bgp bestpath as-path confed" commands */ + install_element(BGP_NODE, &bgp_bestpath_aspath_confed_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_aspath_confed_cmd); + + /* "bgp bestpath as-path multipath-relax" commands */ + install_element(BGP_NODE, &bgp_bestpath_aspath_multipath_relax_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_aspath_multipath_relax_cmd); + + /* "bgp bestpath peer-type multipath-relax" commands */ + install_element(BGP_NODE, &bgp_bestpath_peer_type_multipath_relax_cmd); + install_element(BGP_NODE, + &no_bgp_bestpath_peer_type_multipath_relax_cmd); + + /* "bgp log-neighbor-changes" commands */ + install_element(BGP_NODE, &bgp_log_neighbor_changes_cmd); + install_element(BGP_NODE, &no_bgp_log_neighbor_changes_cmd); + + /* "bgp bestpath med" commands */ + install_element(BGP_NODE, &bgp_bestpath_med_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_med_cmd); + + /* "bgp bestpath bandwidth" commands */ + install_element(BGP_NODE, &bgp_bestpath_bw_cmd); + install_element(BGP_NODE, &no_bgp_bestpath_bw_cmd); + + /* "no bgp default -" commands. */ + install_element(BGP_NODE, &bgp_default_afi_safi_cmd); + + /* "bgp network import-check" commands. */ + install_element(BGP_NODE, &bgp_network_import_check_cmd); + install_element(BGP_NODE, &bgp_network_import_check_exact_cmd); + install_element(BGP_NODE, &no_bgp_network_import_check_cmd); + + /* "bgp default local-preference" commands. */ + install_element(BGP_NODE, &bgp_default_local_preference_cmd); + install_element(BGP_NODE, &no_bgp_default_local_preference_cmd); + + /* bgp default show-hostname */ + install_element(BGP_NODE, &bgp_default_show_hostname_cmd); + install_element(BGP_NODE, &no_bgp_default_show_hostname_cmd); + + /* bgp default show-nexthop-hostname */ + install_element(BGP_NODE, &bgp_default_show_nexthop_hostname_cmd); + install_element(BGP_NODE, &no_bgp_default_show_nexthop_hostname_cmd); + + /* bgp default software-version-capability */ + install_element(BGP_NODE, &bgp_default_software_version_capability_cmd); + + /* bgp default dynamic-capability */ + install_element(BGP_NODE, &bgp_default_dynamic_capability_cmd); + + /* "bgp default subgroup-pkt-queue-max" commands. */ + install_element(BGP_NODE, &bgp_default_subgroup_pkt_queue_max_cmd); + install_element(BGP_NODE, &no_bgp_default_subgroup_pkt_queue_max_cmd); + + /* bgp ibgp-allow-policy-mods command */ + install_element(BGP_NODE, &bgp_rr_allow_outbound_policy_cmd); + install_element(BGP_NODE, &no_bgp_rr_allow_outbound_policy_cmd); + + /* "bgp listen limit" commands. */ + install_element(BGP_NODE, &bgp_listen_limit_cmd); + install_element(BGP_NODE, &no_bgp_listen_limit_cmd); + + /* "bgp listen range" commands. */ + install_element(BGP_NODE, &bgp_listen_range_cmd); + install_element(BGP_NODE, &no_bgp_listen_range_cmd); + + /* "bgp default shutdown" command */ + install_element(BGP_NODE, &bgp_default_shutdown_cmd); + + /* "bgp shutdown" commands */ + install_element(BGP_NODE, &bgp_shutdown_cmd); + install_element(BGP_NODE, &bgp_shutdown_msg_cmd); + install_element(BGP_NODE, &no_bgp_shutdown_cmd); + install_element(BGP_NODE, &no_bgp_shutdown_msg_cmd); + + /* "neighbor remote-as" commands. */ + install_element(BGP_NODE, &neighbor_remote_as_cmd); + install_element(BGP_NODE, &neighbor_interface_config_cmd); + install_element(BGP_NODE, &neighbor_interface_config_v6only_cmd); + install_element(BGP_NODE, &neighbor_interface_config_remote_as_cmd); + install_element(BGP_NODE, + &neighbor_interface_v6only_config_remote_as_cmd); + install_element(BGP_NODE, &no_neighbor_cmd); + install_element(BGP_NODE, &no_neighbor_interface_config_cmd); + + /* "neighbor peer-group" commands. */ + install_element(BGP_NODE, &neighbor_peer_group_cmd); + install_element(BGP_NODE, &no_neighbor_peer_group_cmd); + install_element(BGP_NODE, + &no_neighbor_interface_peer_group_remote_as_cmd); + + /* "neighbor local-as" commands. */ + install_element(BGP_NODE, &neighbor_local_as_cmd); + install_element(BGP_NODE, &neighbor_local_as_no_prepend_cmd); + install_element(BGP_NODE, &neighbor_local_as_no_prepend_replace_as_cmd); + install_element(BGP_NODE, &no_neighbor_local_as_cmd); + + /* "neighbor solo" commands. */ + install_element(BGP_NODE, &neighbor_solo_cmd); + install_element(BGP_NODE, &no_neighbor_solo_cmd); + + /* "neighbor password" commands. */ + install_element(BGP_NODE, &neighbor_password_cmd); + install_element(BGP_NODE, &no_neighbor_password_cmd); + + /* "neighbor activate" commands. */ + install_element(BGP_NODE, &neighbor_activate_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_activate_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_activate_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_activate_cmd); + install_element(BGP_IPV6_NODE, &neighbor_activate_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_activate_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_activate_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_activate_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_activate_cmd); + install_element(BGP_FLOWSPECV4_NODE, &neighbor_activate_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_activate_cmd); + install_element(BGP_EVPN_NODE, &neighbor_activate_cmd); + + /* "no neighbor activate" commands. */ + install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_activate_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_activate_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_activate_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_activate_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_activate_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_activate_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_activate_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_activate_cmd); + install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_activate_cmd); + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_activate_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_activate_cmd); + + /* "neighbor peer-group" set commands. */ + install_element(BGP_NODE, &neighbor_set_peer_group_cmd); + install_element(BGP_IPV4_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &neighbor_set_peer_group_hidden_cmd); + + /* "no neighbor peer-group unset" commands. */ + install_element(BGP_NODE, &no_neighbor_set_peer_group_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &no_neighbor_set_peer_group_hidden_cmd); + + /* "neighbor softreconfiguration inbound" commands.*/ + install_element(BGP_NODE, &neighbor_soft_reconfiguration_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_soft_reconfiguration_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &neighbor_soft_reconfiguration_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &neighbor_soft_reconfiguration_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_EVPN_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_soft_reconfiguration_cmd); + + /* "neighbor attribute-unchanged" commands. */ + install_element(BGP_NODE, &neighbor_attr_unchanged_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_attr_unchanged_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_attr_unchanged_cmd); + + install_element(BGP_EVPN_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_attr_unchanged_cmd); + + install_element(BGP_FLOWSPECV4_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_attr_unchanged_cmd); + + /* "nexthop-local unchanged" commands */ + install_element(BGP_IPV6_NODE, &neighbor_nexthop_local_unchanged_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_nexthop_local_unchanged_cmd); + + /* "neighbor next-hop-self" commands. */ + install_element(BGP_NODE, &neighbor_nexthop_self_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_nexthop_self_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_cmd); + + /* "neighbor next-hop-self force" commands. */ + install_element(BGP_NODE, &neighbor_nexthop_self_force_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_nexthop_self_force_hidden_cmd); + install_element(BGP_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_nexthop_self_all_hidden_cmd); + install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_force_cmd); + + /* "neighbor as-override" commands. */ + install_element(BGP_NODE, &neighbor_as_override_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_as_override_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_IPV6_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_as_override_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_as_override_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_as_override_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_as_override_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_as_override_cmd); + + /* "neighbor remove-private-AS" commands. */ + install_element(BGP_NODE, &neighbor_remove_private_as_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_remove_private_as_hidden_cmd); + install_element(BGP_NODE, &neighbor_remove_private_as_all_hidden_cmd); + install_element(BGP_NODE, + &no_neighbor_remove_private_as_all_hidden_cmd); + install_element(BGP_NODE, + &neighbor_remove_private_as_replace_as_hidden_cmd); + install_element(BGP_NODE, + &no_neighbor_remove_private_as_replace_as_hidden_cmd); + install_element(BGP_NODE, + &neighbor_remove_private_as_all_replace_as_hidden_cmd); + install_element( + BGP_NODE, + &no_neighbor_remove_private_as_all_replace_as_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV4_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV4_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV6_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); + + /* "neighbor send-community" commands.*/ + install_element(BGP_NODE, &neighbor_send_community_hidden_cmd); + install_element(BGP_NODE, &neighbor_send_community_type_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_send_community_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_send_community_type_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV4_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_IPV6_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV6_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_send_community_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_send_community_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_send_community_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV4_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV6_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_ecommunity_rpki_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_ecommunity_rpki_cmd); + + /* "neighbor route-reflector" commands.*/ + install_element(BGP_NODE, &neighbor_route_reflector_client_hidden_cmd); + install_element(BGP_NODE, + &no_neighbor_route_reflector_client_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_route_reflector_client_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &neighbor_route_reflector_client_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &neighbor_route_reflector_client_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &no_neighbor_route_reflector_client_cmd); + install_element(BGP_EVPN_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_route_reflector_client_cmd); + + /* "neighbor route-server" commands.*/ + install_element(BGP_NODE, &neighbor_route_server_client_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_route_server_client_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_IPV6_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_EVPN_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_FLOWSPECV4_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_FLOWSPECV4_NODE, + &no_neighbor_route_server_client_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_FLOWSPECV6_NODE, + &no_neighbor_route_server_client_cmd); + + /* "neighbor disable-addpath-rx" commands. */ + install_element(BGP_IPV4_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_disable_addpath_rx_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_disable_addpath_rx_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_disable_addpath_rx_cmd); + + /* "neighbor addpath-tx-all-paths" commands.*/ + install_element(BGP_NODE, &neighbor_addpath_tx_all_paths_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_addpath_tx_all_paths_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_addpath_tx_all_paths_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_tx_all_paths_cmd); + + /* "neighbor addpath-tx-best-selected" commands.*/ + install_element(BGP_IPV4_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV4_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_addpath_tx_best_selected_paths_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_addpath_tx_best_selected_paths_cmd); + + /* "neighbor addpath-tx-bestpath-per-AS" commands.*/ + install_element(BGP_NODE, + &neighbor_addpath_tx_bestpath_per_as_hidden_cmd); + install_element(BGP_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_hidden_cmd); + install_element(BGP_IPV4_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV4_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV4M_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV4L_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6M_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_IPV6L_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_VPNV4_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_addpath_tx_bestpath_per_as_cmd); + install_element(BGP_VPNV6_NODE, + &no_neighbor_addpath_tx_bestpath_per_as_cmd); + + /* "neighbor addpath-rx-paths-limit" commands.*/ + install_element(BGP_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_addpath_paths_limit_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_addpath_paths_limit_cmd); + + /* "neighbor sender-as-path-loop-detection" commands. */ + install_element(BGP_NODE, &neighbor_aspath_loop_detection_cmd); + install_element(BGP_NODE, &no_neighbor_aspath_loop_detection_cmd); + + /* "neighbor path-attribute discard" commands. */ + install_element(BGP_NODE, &neighbor_path_attribute_discard_cmd); + install_element(BGP_NODE, &no_neighbor_path_attribute_discard_cmd); + + /* "neighbor path-attribute treat-as-withdraw" commands. */ + install_element(BGP_NODE, + &neighbor_path_attribute_treat_as_withdraw_cmd); + install_element(BGP_NODE, + &no_neighbor_path_attribute_treat_as_withdraw_cmd); + + /* "neighbor passive" commands. */ + install_element(BGP_NODE, &neighbor_passive_cmd); + install_element(BGP_NODE, &no_neighbor_passive_cmd); + + + /* "neighbor shutdown" commands. */ + install_element(BGP_NODE, &neighbor_shutdown_cmd); + install_element(BGP_NODE, &no_neighbor_shutdown_cmd); + install_element(BGP_NODE, &neighbor_shutdown_msg_cmd); + install_element(BGP_NODE, &no_neighbor_shutdown_msg_cmd); + install_element(BGP_NODE, &neighbor_shutdown_rtt_cmd); + install_element(BGP_NODE, &no_neighbor_shutdown_rtt_cmd); + + /* "neighbor capability extended-nexthop" commands.*/ + install_element(BGP_NODE, &neighbor_capability_enhe_cmd); + install_element(BGP_NODE, &no_neighbor_capability_enhe_cmd); + + /* "neighbor capability software-version" commands.*/ + install_element(BGP_NODE, &neighbor_capability_software_version_cmd); + + /* "neighbor capability orf prefix-list" commands.*/ + install_element(BGP_NODE, &neighbor_capability_orf_prefix_hidden_cmd); + install_element(BGP_NODE, + &no_neighbor_capability_orf_prefix_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_capability_orf_prefix_cmd); + + /* "neighbor capability dynamic" commands.*/ + install_element(BGP_NODE, &neighbor_capability_dynamic_cmd); + install_element(BGP_NODE, &no_neighbor_capability_dynamic_cmd); + + /* "neighbor dont-capability-negotiate" commands. */ + install_element(BGP_NODE, &neighbor_dont_capability_negotiate_cmd); + install_element(BGP_NODE, &no_neighbor_dont_capability_negotiate_cmd); + + /* "neighbor capability fqdn" command. */ + install_element(BGP_NODE, &neighbor_capability_fqdn_cmd); + + /* "neighbor ebgp-multihop" commands. */ + install_element(BGP_NODE, &neighbor_ebgp_multihop_cmd); + install_element(BGP_NODE, &neighbor_ebgp_multihop_ttl_cmd); + install_element(BGP_NODE, &no_neighbor_ebgp_multihop_cmd); + + /* "neighbor disable-connected-check" commands. */ + install_element(BGP_NODE, &neighbor_disable_connected_check_cmd); + install_element(BGP_NODE, &no_neighbor_disable_connected_check_cmd); + + /* "neighbor disable-link-bw-encoding-ieee" commands. */ + install_element(BGP_NODE, &neighbor_disable_link_bw_encoding_ieee_cmd); + install_element(BGP_NODE, + &no_neighbor_disable_link_bw_encoding_ieee_cmd); + + + install_element(BGP_NODE, &neighbor_extended_link_bw_cmd); + + /* "neighbor extended-optional-parameters" commands. */ + install_element(BGP_NODE, &neighbor_extended_optional_parameters_cmd); + install_element(BGP_NODE, + &no_neighbor_extended_optional_parameters_cmd); + + /* "neighbor enforce-first-as" commands. */ + install_element(BGP_NODE, &neighbor_enforce_first_as_cmd); + install_element(BGP_NODE, &no_neighbor_enforce_first_as_cmd); + + /* "neighbor description" commands. */ + install_element(BGP_NODE, &neighbor_description_cmd); + install_element(BGP_NODE, &no_neighbor_description_cmd); + install_element(BGP_NODE, &no_neighbor_description_comment_cmd); + + /* "neighbor update-source" commands. "*/ + install_element(BGP_NODE, &neighbor_update_source_cmd); + install_element(BGP_NODE, &no_neighbor_update_source_cmd); + + /* "neighbor default-originate" commands. */ + install_element(BGP_NODE, &neighbor_default_originate_hidden_cmd); + install_element(BGP_NODE, &neighbor_default_originate_rmap_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_default_originate_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV4_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_default_originate_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_default_originate_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_default_originate_cmd); + install_element(BGP_IPV6_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV6_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_default_originate_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_default_originate_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_default_originate_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_default_originate_rmap_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_default_originate_cmd); + + /* "neighbor port" commands. */ + install_element(BGP_NODE, &neighbor_port_cmd); + install_element(BGP_NODE, &no_neighbor_port_cmd); + + /* "neighbor weight" commands. */ + install_element(BGP_NODE, &neighbor_weight_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_weight_hidden_cmd); + + install_element(BGP_IPV4_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_weight_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_weight_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_weight_cmd); + install_element(BGP_IPV6_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_weight_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_weight_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_weight_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_weight_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_weight_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_weight_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_weight_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_weight_cmd); + + /* "neighbor override-capability" commands. */ + install_element(BGP_NODE, &neighbor_override_capability_cmd); + install_element(BGP_NODE, &no_neighbor_override_capability_cmd); + + /* "neighbor strict-capability-match" commands. */ + install_element(BGP_NODE, &neighbor_strict_capability_cmd); + install_element(BGP_NODE, &no_neighbor_strict_capability_cmd); + + /* "neighbor timers" commands. */ + install_element(BGP_NODE, &neighbor_timers_cmd); + install_element(BGP_NODE, &no_neighbor_timers_cmd); + + /* "neighbor timers connect" commands. */ + install_element(BGP_NODE, &neighbor_timers_connect_cmd); + install_element(BGP_NODE, &no_neighbor_timers_connect_cmd); + + /* "neighbor timers delayopen" commands. */ + install_element(BGP_NODE, &neighbor_timers_delayopen_cmd); + install_element(BGP_NODE, &no_neighbor_timers_delayopen_cmd); + + /* "neighbor advertisement-interval" commands. */ + install_element(BGP_NODE, &neighbor_advertise_interval_cmd); + install_element(BGP_NODE, &no_neighbor_advertise_interval_cmd); + + /* "neighbor interface" commands. */ + install_element(BGP_NODE, &neighbor_interface_cmd); + install_element(BGP_NODE, &no_neighbor_interface_cmd); + + /* "neighbor distribute" commands. */ + install_element(BGP_NODE, &neighbor_distribute_list_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_distribute_list_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_IPV6_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_distribute_list_cmd); + + /* "neighbor prefix-list" commands. */ + install_element(BGP_NODE, &neighbor_prefix_list_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_prefix_list_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_IPV6_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_FLOWSPECV4_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_prefix_list_cmd); + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_prefix_list_cmd); + + /* "neighbor filter-list" commands. */ + install_element(BGP_NODE, &neighbor_filter_list_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_filter_list_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_IPV6_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_filter_list_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_filter_list_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_filter_list_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_FLOWSPECV4_NODE, &neighbor_filter_list_cmd); + install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_filter_list_cmd); + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_filter_list_cmd); + + /* "neighbor route-map" commands. */ + install_element(BGP_NODE, &neighbor_route_map_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_route_map_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_IPV6_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_route_map_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_route_map_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_route_map_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_FLOWSPECV4_NODE, &neighbor_route_map_cmd); + install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_map_cmd); + install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_EVPN_NODE, &neighbor_route_map_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_route_map_cmd); + + /* "neighbor unsuppress-map" commands. */ + install_element(BGP_NODE, &neighbor_unsuppress_map_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_unsuppress_map_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_unsuppress_map_cmd); + + /* "neighbor advertise-map" commands. */ + install_element(BGP_NODE, &bgp_condadv_period_cmd); + install_element(BGP_NODE, &neighbor_advertise_map_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_IPV6_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_advertise_map_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_advertise_map_cmd); + + /* bgp default-originate timer */ + install_element(BGP_NODE, &bgp_def_originate_eval_cmd); + + /* neighbor maximum-prefix-out commands. */ + install_element(BGP_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_maximum_prefix_out_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_out_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_maximum_prefix_out_cmd); + + /* "neighbor maximum-prefix" commands. */ + install_element(BGP_NODE, &neighbor_maximum_prefix_hidden_cmd); + install_element(BGP_NODE, + &neighbor_maximum_prefix_threshold_hidden_cmd); + install_element(BGP_NODE, &neighbor_maximum_prefix_warning_hidden_cmd); + install_element(BGP_NODE, + &neighbor_maximum_prefix_threshold_warning_hidden_cmd); + install_element(BGP_NODE, &neighbor_maximum_prefix_restart_hidden_cmd); + install_element(BGP_NODE, + &neighbor_maximum_prefix_threshold_restart_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_maximum_prefix_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV4_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV4_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV4_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV4M_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV4L_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV6_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV6_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV6M_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_IPV6L_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_VPNV4_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_VPNV6_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_EVPN_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_EVPN_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_EVPN_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_EVPN_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_EVPN_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_EVPN_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_maximum_prefix_cmd); + + /* "neighbor allowas-in" */ + install_element(BGP_NODE, &neighbor_allowas_in_hidden_cmd); + install_element(BGP_NODE, &no_neighbor_allowas_in_hidden_cmd); + install_element(BGP_IPV4_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_IPV6_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_EVPN_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_allowas_in_cmd); + + /* neighbor accept-own */ + install_element(BGP_VPNV4_NODE, &neighbor_accept_own_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_accept_own_cmd); + + /* "neighbor soo" */ + install_element(BGP_IPV4_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_soo_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_soo_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_soo_cmd); + install_element(BGP_VPNV4_NODE, &neighbor_soo_cmd); + install_element(BGP_VPNV4_NODE, &no_neighbor_soo_cmd); + install_element(BGP_VPNV6_NODE, &neighbor_soo_cmd); + install_element(BGP_VPNV6_NODE, &no_neighbor_soo_cmd); + install_element(BGP_EVPN_NODE, &neighbor_soo_cmd); + install_element(BGP_EVPN_NODE, &no_neighbor_soo_cmd); + + /* "neighbor dampening" commands. */ + install_element(BGP_NODE, &neighbor_damp_cmd); + install_element(BGP_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV4_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV4_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV4M_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV4M_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV4L_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV4L_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV6_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV6_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV6M_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV6M_NODE, &no_neighbor_damp_cmd); + install_element(BGP_IPV6L_NODE, &neighbor_damp_cmd); + install_element(BGP_IPV6L_NODE, &no_neighbor_damp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_neighbor_damp_param_cmd); + + /* address-family commands. */ + install_element(BGP_NODE, &address_family_ipv4_safi_cmd); + install_element(BGP_NODE, &address_family_ipv6_safi_cmd); +#ifdef KEEP_OLD_VPN_COMMANDS + install_element(BGP_NODE, &address_family_vpnv4_cmd); + install_element(BGP_NODE, &address_family_vpnv6_cmd); +#endif /* KEEP_OLD_VPN_COMMANDS */ + + install_element(BGP_NODE, &address_family_evpn_cmd); + + /* "exit-address-family" command. */ + install_element(BGP_IPV4_NODE, &exit_address_family_cmd); + install_element(BGP_IPV4M_NODE, &exit_address_family_cmd); + install_element(BGP_IPV4L_NODE, &exit_address_family_cmd); + install_element(BGP_IPV6_NODE, &exit_address_family_cmd); + install_element(BGP_IPV6M_NODE, &exit_address_family_cmd); + install_element(BGP_IPV6L_NODE, &exit_address_family_cmd); + install_element(BGP_VPNV4_NODE, &exit_address_family_cmd); + install_element(BGP_VPNV6_NODE, &exit_address_family_cmd); + install_element(BGP_FLOWSPECV4_NODE, &exit_address_family_cmd); + install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); + install_element(BGP_EVPN_NODE, &exit_address_family_cmd); + + /* BGP retain all route-target */ + install_element(BGP_VPNV4_NODE, &bgp_retain_route_target_cmd); + install_element(BGP_VPNV6_NODE, &bgp_retain_route_target_cmd); + + /* "clear ip bgp commands" */ + install_element(ENABLE_NODE, &clear_ip_bgp_all_cmd); + + /* clear ip bgp prefix */ + install_element(ENABLE_NODE, &clear_ip_bgp_prefix_cmd); + install_element(ENABLE_NODE, &clear_bgp_ipv6_safi_prefix_cmd); + install_element(ENABLE_NODE, &clear_bgp_instance_ipv6_safi_prefix_cmd); + + /* "show [ip] bgp summary" commands. */ + install_element(VIEW_NODE, &show_bgp_instance_all_ipv6_updgrps_cmd); + install_element(VIEW_NODE, &show_bgp_l2vpn_evpn_updgrps_cmd); + install_element(VIEW_NODE, &show_bgp_instance_updgrps_stats_cmd); + install_element(VIEW_NODE, &show_bgp_updgrps_stats_cmd); + install_element(VIEW_NODE, &show_ip_bgp_instance_updgrps_adj_s_cmd); + install_element(VIEW_NODE, &show_ip_bgp_summary_cmd); + install_element(VIEW_NODE, &show_ip_bgp_updgrps_cmd); + + /* "show [ip] bgp neighbors" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_neighbors_cmd); + + install_element(VIEW_NODE, &show_ip_bgp_neighbors_graceful_restart_cmd); + + /* "show [ip] bgp peer-group" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_peer_groups_cmd); + + /* "show [ip] bgp paths" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_paths_cmd); + + /* "show [ip] bgp community" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_community_info_cmd); + + /* "show ip bgp large-community" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_lcommunity_info_cmd); + /* "show [ip] bgp attribute-info" commands. */ + install_element(VIEW_NODE, &show_ip_bgp_attr_info_cmd); + /* "show [ip] bgp route-leak" command */ + install_element(VIEW_NODE, &show_ip_bgp_route_leak_cmd); + + /* "redistribute" commands. */ + install_element(BGP_NODE, &bgp_redistribute_ipv4_hidden_cmd); + install_element(BGP_NODE, &no_bgp_redistribute_ipv4_hidden_cmd); + install_element(BGP_NODE, &bgp_redistribute_ipv4_rmap_hidden_cmd); + install_element(BGP_NODE, &bgp_redistribute_ipv4_metric_hidden_cmd); + install_element(BGP_NODE, + &bgp_redistribute_ipv4_rmap_metric_hidden_cmd); + install_element(BGP_NODE, + &bgp_redistribute_ipv4_metric_rmap_hidden_cmd); + install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_hidden_cmd); + install_element(BGP_NODE, &no_bgp_redistribute_ipv4_ospf_hidden_cmd); + install_element(BGP_NODE, &bgp_redistribute_ipv4_ospf_rmap_hidden_cmd); + install_element(BGP_NODE, + &bgp_redistribute_ipv4_ospf_metric_hidden_cmd); + install_element(BGP_NODE, + &bgp_redistribute_ipv4_ospf_rmap_metric_hidden_cmd); + install_element(BGP_NODE, + &bgp_redistribute_ipv4_ospf_metric_rmap_hidden_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_redistribute_ipv4_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_rmap_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_metric_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_rmap_metric_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_metric_rmap_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_cmd); + install_element(BGP_IPV4_NODE, &no_bgp_redistribute_ipv4_ospf_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_rmap_cmd); + install_element(BGP_IPV4_NODE, &bgp_redistribute_ipv4_ospf_metric_cmd); + install_element(BGP_IPV4_NODE, + &bgp_redistribute_ipv4_ospf_rmap_metric_cmd); + install_element(BGP_IPV4_NODE, + &bgp_redistribute_ipv4_ospf_metric_rmap_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_redistribute_ipv6_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_rmap_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_metric_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_rmap_metric_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_metric_rmap_cmd); + install_element(BGP_IPV6_NODE, &bgp_redistribute_ipv6_table_cmd); + install_element(BGP_IPV6_NODE, &no_bgp_redistribute_ipv6_table_cmd); + + /* import|export vpn [route-map RMAP_NAME] */ + install_element(BGP_IPV4_NODE, &bgp_imexport_vpn_cmd); + install_element(BGP_IPV6_NODE, &bgp_imexport_vpn_cmd); + + install_element(BGP_IPV4_NODE, &bgp_imexport_vrf_cmd); + install_element(BGP_IPV6_NODE, &bgp_imexport_vrf_cmd); + + /* ttl_security commands */ + install_element(BGP_NODE, &neighbor_ttl_security_cmd); + install_element(BGP_NODE, &no_neighbor_ttl_security_cmd); + + /* "bgp tcp-keepalive" commands */ + install_element(BGP_NODE, &bgp_tcp_keepalive_cmd); + install_element(BGP_NODE, &no_bgp_tcp_keepalive_cmd); + + /* "show [ip] bgp memory" commands. */ + install_element(VIEW_NODE, &show_bgp_memory_cmd); + + /* "show bgp martian next-hop" */ + install_element(VIEW_NODE, &show_bgp_martian_nexthop_db_cmd); + + install_element(VIEW_NODE, &show_bgp_mac_hash_cmd); + + /* "show [ip] bgp views" commands. */ + install_element(VIEW_NODE, &show_bgp_views_cmd); + + /* "show [ip] bgp vrfs" commands. */ + install_element(VIEW_NODE, &show_bgp_vrfs_cmd); + + /* Community-list. */ + community_list_vty(); + + community_alias_vty(); + + /* vpn-policy commands */ + install_element(BGP_IPV4_NODE, &af_rd_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_rd_vpn_export_cmd); + install_element(BGP_IPV4_NODE, &af_label_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_label_vpn_export_cmd); + install_element(BGP_IPV4_NODE, + &af_label_vpn_export_allocation_mode_cmd); + install_element(BGP_IPV6_NODE, + &af_label_vpn_export_allocation_mode_cmd); + install_element(BGP_IPV4_NODE, &af_nexthop_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_nexthop_vpn_export_cmd); + install_element(BGP_IPV4_NODE, &af_rt_vpn_imexport_cmd); + install_element(BGP_IPV6_NODE, &af_rt_vpn_imexport_cmd); + install_element(BGP_IPV4_NODE, &af_route_map_vpn_imexport_cmd); + install_element(BGP_IPV6_NODE, &af_route_map_vpn_imexport_cmd); + install_element(BGP_IPV4_NODE, &af_import_vrf_route_map_cmd); + install_element(BGP_IPV6_NODE, &af_import_vrf_route_map_cmd); + + install_element(BGP_IPV4_NODE, &af_routetarget_import_cmd); + install_element(BGP_IPV6_NODE, &af_routetarget_import_cmd); + + install_element(BGP_IPV4_NODE, &af_no_rd_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_no_rd_vpn_export_cmd); + install_element(BGP_IPV4_NODE, &af_no_label_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_no_label_vpn_export_cmd); + install_element(BGP_IPV4_NODE, &af_no_rt_vpn_imexport_cmd); + install_element(BGP_IPV6_NODE, &af_no_rt_vpn_imexport_cmd); + install_element(BGP_IPV4_NODE, &af_no_route_map_vpn_imexport_cmd); + install_element(BGP_IPV6_NODE, &af_no_route_map_vpn_imexport_cmd); + install_element(BGP_IPV4_NODE, &af_no_import_vrf_route_map_cmd); + install_element(BGP_IPV6_NODE, &af_no_import_vrf_route_map_cmd); + + /* tcp-mss command */ + install_element(BGP_NODE, &neighbor_tcp_mss_cmd); + install_element(BGP_NODE, &no_neighbor_tcp_mss_cmd); + + /* srv6 commands */ + install_element(VIEW_NODE, &show_bgp_srv6_cmd); + install_element(BGP_NODE, &bgp_segment_routing_srv6_cmd); + install_element(BGP_NODE, &no_bgp_segment_routing_srv6_cmd); + install_element(BGP_SRV6_NODE, &bgp_srv6_locator_cmd); + install_element(BGP_SRV6_NODE, &no_bgp_srv6_locator_cmd); + install_element(BGP_IPV4_NODE, &af_sid_vpn_export_cmd); + install_element(BGP_IPV6_NODE, &af_sid_vpn_export_cmd); + install_element(BGP_NODE, &bgp_sid_vpn_export_cmd); + install_element(BGP_NODE, &no_bgp_sid_vpn_export_cmd); + + bgp_vty_if_init(); +} + +#include "memory.h" +#include "bgp_regex.h" +#include "bgp_clist.h" +#include "bgp_ecommunity.h" + +/* VTY functions. */ + +/* Direction value to string conversion. */ +static const char *community_direct_str(int direct) +{ + switch (direct) { + case COMMUNITY_DENY: + return "deny"; + case COMMUNITY_PERMIT: + return "permit"; + default: + return "unknown"; + } +} + +/* Display error string. */ +static void community_list_perror(struct vty *vty, int ret) +{ + switch (ret) { + case COMMUNITY_LIST_ERR_MALFORMED_VAL: + vty_out(vty, "%% Malformed community-list value\n"); + break; + case COMMUNITY_LIST_ERR_STANDARD_CONFLICT: + vty_out(vty, + "%% Community name conflict, previously defined as standard community\n"); + break; + case COMMUNITY_LIST_ERR_EXPANDED_CONFLICT: + vty_out(vty, + "%% Community name conflict, previously defined as expanded community\n"); + break; + } +} + +/* "community-list" keyword help string. */ +#define COMMUNITY_LIST_STR "Add a community list entry\n" + +/*community-list standard */ +DEFUN (community_list_standard, + bgp_community_list_standard_cmd, + "bgp community-list <(1-99)|standard COMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + BGP_STR + COMMUNITY_LIST_STR + "Community list number (standard)\n" + "Add an standard community-list entry\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + COMMUNITY_VAL_STR) +{ + char *cl_name_or_number = NULL; + char *seq = NULL; + int direct = 0; + int style = COMMUNITY_LIST_STANDARD; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "COMMUNITY_LIST_NAME", &idx); + cl_name_or_number = argv[idx]->arg; + direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + argv_find(argv, argc, "AA:NN", &idx); + char *str = argv_concat(argv, argc, idx); + + assert(str); + int ret = community_list_set(bgp_clist, cl_name_or_number, str, seq, + direct, style); + + XFREE(MTYPE_TMP, str); + + if (ret < 0) { + /* Display error string. */ + community_list_perror(vty, ret); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (no_community_list_standard_all, + no_bgp_community_list_standard_all_cmd, + "no bgp community-list <(1-99)|standard COMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + NO_STR + BGP_STR + COMMUNITY_LIST_STR + "Community list number (standard)\n" + "Add an standard community-list entry\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + COMMUNITY_VAL_STR) +{ + char *cl_name_or_number = NULL; + char *str = NULL; + int direct = 0; + int style = COMMUNITY_LIST_STANDARD; + char *seq = NULL; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "permit", &idx); + argv_find(argv, argc, "deny", &idx); + + if (idx) { + direct = argv_find(argv, argc, "permit", &idx) + ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + + idx = 0; + argv_find(argv, argc, "AA:NN", &idx); + str = argv_concat(argv, argc, idx); + } + + idx = 0; + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "COMMUNITY_LIST_NAME", &idx); + cl_name_or_number = argv[idx]->arg; + + community_list_unset(bgp_clist, cl_name_or_number, str, seq, direct, + style); + + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +ALIAS(no_community_list_standard_all, no_bgp_community_list_standard_all_list_cmd, + "no bgp community-list <(1-99)|standard COMMUNITY_LIST_NAME>", + NO_STR BGP_STR COMMUNITY_LIST_STR + "Community list number (standard)\n" + "Add an standard community-list entry\n" + "Community list name\n") + +/*community-list expanded */ +DEFUN (community_list_expanded_all, + bgp_community_list_expanded_all_cmd, + "bgp community-list <(100-500)|expanded COMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + BGP_STR + COMMUNITY_LIST_STR + "Community list number (expanded)\n" + "Add an expanded community-list entry\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + COMMUNITY_VAL_STR) +{ + char *cl_name_or_number = NULL; + char *seq = NULL; + int direct = 0; + int style = COMMUNITY_LIST_EXPANDED; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + + argv_find(argv, argc, "(100-500)", &idx); + argv_find(argv, argc, "COMMUNITY_LIST_NAME", &idx); + cl_name_or_number = argv[idx]->arg; + direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + argv_find(argv, argc, "AA:NN", &idx); + char *str = argv_concat(argv, argc, idx); + + assert(str); + int ret = community_list_set(bgp_clist, cl_name_or_number, str, seq, + direct, style); + + XFREE(MTYPE_TMP, str); + + if (ret < 0) { + /* Display error string. */ + community_list_perror(vty, ret); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (no_community_list_expanded_all, + no_bgp_community_list_expanded_all_cmd, + "no bgp community-list <(100-500)|expanded COMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + NO_STR + BGP_STR + COMMUNITY_LIST_STR + "Community list number (expanded)\n" + "Add an expanded community-list entry\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + COMMUNITY_VAL_STR) +{ + char *cl_name_or_number = NULL; + char *seq = NULL; + char *str = NULL; + int direct = 0; + int style = COMMUNITY_LIST_EXPANDED; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "permit", &idx); + argv_find(argv, argc, "deny", &idx); + + if (idx) { + direct = argv_find(argv, argc, "permit", &idx) + ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + + idx = 0; + argv_find(argv, argc, "AA:NN", &idx); + str = argv_concat(argv, argc, idx); + } + + idx = 0; + argv_find(argv, argc, "(100-500)", &idx); + argv_find(argv, argc, "COMMUNITY_LIST_NAME", &idx); + cl_name_or_number = argv[idx]->arg; + + community_list_unset(bgp_clist, cl_name_or_number, str, seq, direct, + style); + + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +ALIAS(no_community_list_expanded_all, + no_bgp_community_list_expanded_all_list_cmd, + "no bgp community-list <(100-500)|expanded COMMUNITY_LIST_NAME>", + NO_STR BGP_STR COMMUNITY_LIST_STR + "Community list number (expanded)\n" + "Add an expanded community-list entry\n" + "Community list name\n") + +/* Return configuration string of community-list entry. */ +static const char *community_list_config_str(struct community_entry *entry) +{ + const char *str; + + if (entry->style == COMMUNITY_LIST_STANDARD) + str = community_str(entry->u.com, false, false); + else if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) + str = lcommunity_str(entry->u.lcom, false, false); + else + str = entry->config; + + return str; +} + +static void community_list_show(struct vty *vty, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry == list->head) { + if (all_digit(list->name)) + vty_out(vty, "Community %s list %s\n", + entry->style == COMMUNITY_LIST_STANDARD + ? "standard" + : "(expanded) access", + list->name); + else + vty_out(vty, "Named Community %s list %s\n", + entry->style == COMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name); + } + vty_out(vty, " %s %s\n", community_direct_str(entry->direct), + community_list_config_str(entry)); + } +} + +DEFUN (show_community_list, + show_bgp_community_list_cmd, + "show bgp community-list", + SHOW_STR + BGP_STR + "List community-list\n") +{ + struct community_list *list; + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, COMMUNITY_LIST_MASTER); + if (!cm) + return CMD_SUCCESS; + + for (list = cm->num.head; list; list = list->next) + community_list_show(vty, list); + + for (list = cm->str.head; list; list = list->next) + community_list_show(vty, list); + + return CMD_SUCCESS; +} + +DEFUN (show_community_list_arg, + show_bgp_community_list_arg_cmd, + "show bgp community-list <(1-500)|COMMUNITY_LIST_NAME> detail", + SHOW_STR + BGP_STR + "List community-list\n" + "Community-list number\n" + "Community-list name\n" + "Detailed information on community-list\n") +{ + int idx_comm_list = 3; + struct community_list *list; + + list = community_list_lookup(bgp_clist, argv[idx_comm_list]->arg, 0, + COMMUNITY_LIST_MASTER); + if (!list) { + vty_out(vty, "%% Can't find community-list\n"); + return CMD_WARNING; + } + + community_list_show(vty, list); + + return CMD_SUCCESS; +} + +/* + * Large Community code. + */ +static int lcommunity_list_set_vty(struct vty *vty, int argc, + struct cmd_token **argv, int style, + int reject_all_digit_name) +{ + int ret; + int direct; + char *str; + int idx = 0; + char *cl_name; + char *seq = NULL; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + + /* All digit name check. */ + idx = 0; + argv_find(argv, argc, "LCOMMUNITY_LIST_NAME", &idx); + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "(100-500)", &idx); + cl_name = argv[idx]->arg; + if (reject_all_digit_name && all_digit(cl_name)) { + vty_out(vty, "%% Community name cannot have all digits\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + idx = 0; + argv_find(argv, argc, "AA:BB:CC", &idx); + argv_find(argv, argc, "LINE", &idx); + /* Concat community string argument. */ + if (idx) + str = argv_concat(argv, argc, idx); + else + str = NULL; + + ret = lcommunity_list_set(bgp_clist, cl_name, str, seq, direct, style); + + /* Free temporary community list string allocated by + argv_concat(). */ + XFREE(MTYPE_TMP, str); + + if (ret < 0) { + community_list_perror(vty, ret); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +static int lcommunity_list_unset_vty(struct vty *vty, int argc, + struct cmd_token **argv, int style) +{ + int direct = 0; + char *str = NULL; + int idx = 0; + char *seq = NULL; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "permit", &idx); + argv_find(argv, argc, "deny", &idx); + + if (idx) { + /* Check the list direct. */ + if (strncmp(argv[idx]->arg, "p", 1) == 0) + direct = COMMUNITY_PERMIT; + else + direct = COMMUNITY_DENY; + + idx = 0; + argv_find(argv, argc, "LINE", &idx); + argv_find(argv, argc, "AA:AA:NN", &idx); + /* Concat community string argument. */ + str = argv_concat(argv, argc, idx); + } + + idx = 0; + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "(100-500)", &idx); + argv_find(argv, argc, "LCOMMUNITY_LIST_NAME", &idx); + + /* Unset community list. */ + lcommunity_list_unset(bgp_clist, argv[idx]->arg, str, seq, direct, + style); + + /* Free temporary community list string allocated by + argv_concat(). */ + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +/* "large-community-list" keyword help string. */ +#define LCOMMUNITY_LIST_STR "Add a large community list entry\n" +#define LCOMMUNITY_VAL_STR "large community in 'aa:bb:cc' format\n" + +DEFUN (lcommunity_list_standard, + bgp_lcommunity_list_standard_cmd, + "bgp large-community-list (1-99) [seq (0-4294967295)] AA:BB:CC...", + BGP_STR + LCOMMUNITY_LIST_STR + "Large Community list number (standard)\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + LCOMMUNITY_VAL_STR) +{ + return lcommunity_list_set_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD, 0); +} + +DEFUN (lcommunity_list_expanded, + bgp_lcommunity_list_expanded_cmd, + "bgp large-community-list (100-500) [seq (0-4294967295)] LINE...", + BGP_STR + LCOMMUNITY_LIST_STR + "Large Community list number (expanded)\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + "An ordered list as a regular-expression\n") +{ + return lcommunity_list_set_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_EXPANDED, 0); +} + +DEFUN (lcommunity_list_name_standard, + bgp_lcommunity_list_name_standard_cmd, + "bgp large-community-list standard LCOMMUNITY_LIST_NAME [seq (0-4294967295)] AA:BB:CC...", + BGP_STR + LCOMMUNITY_LIST_STR + "Specify standard large-community-list\n" + "Large Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + LCOMMUNITY_VAL_STR) +{ + return lcommunity_list_set_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD, 1); +} + +DEFUN (lcommunity_list_name_expanded, + bgp_lcommunity_list_name_expanded_cmd, + "bgp large-community-list expanded LCOMMUNITY_LIST_NAME [seq (0-4294967295)] LINE...", + BGP_STR + LCOMMUNITY_LIST_STR + "Specify expanded large-community-list\n" + "Large Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + "An ordered list as a regular-expression\n") +{ + return lcommunity_list_set_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_EXPANDED, 1); +} + +DEFUN (no_lcommunity_list_all, + no_bgp_lcommunity_list_all_cmd, + "no bgp large-community-list <(1-99)|(100-500)|LCOMMUNITY_LIST_NAME>", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Large Community list number (standard)\n" + "Large Community list number (expanded)\n" + "Large Community list name\n") +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD); +} + +DEFUN (no_lcommunity_list_name_standard_all, + no_bgp_lcommunity_list_name_standard_all_cmd, + "no bgp large-community-list standard LCOMMUNITY_LIST_NAME", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Specify standard large-community-list\n" + "Large Community list name\n") +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD); +} + +DEFUN (no_lcommunity_list_name_expanded_all, + no_bgp_lcommunity_list_name_expanded_all_cmd, + "no bgp large-community-list expanded LCOMMUNITY_LIST_NAME", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Specify expanded large-community-list\n" + "Large Community list name\n") +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_EXPANDED); +} + +DEFUN (no_lcommunity_list_standard, + no_bgp_lcommunity_list_standard_cmd, + "no bgp large-community-list (1-99) [seq (0-4294967295)] AA:AA:NN...", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Large Community list number (standard)\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + LCOMMUNITY_VAL_STR) +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD); +} + +DEFUN (no_lcommunity_list_expanded, + no_bgp_lcommunity_list_expanded_cmd, + "no bgp large-community-list (100-500) [seq (0-4294967295)] LINE...", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Large Community list number (expanded)\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + "An ordered list as a regular-expression\n") +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_EXPANDED); +} + +DEFUN (no_lcommunity_list_name_standard, + no_bgp_lcommunity_list_name_standard_cmd, + "no bgp large-community-list standard LCOMMUNITY_LIST_NAME [seq (0-4294967295)] AA:AA:NN...", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Specify standard large-community-list\n" + "Large Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + LCOMMUNITY_VAL_STR) +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_STANDARD); +} + +DEFUN (no_lcommunity_list_name_expanded, + no_bgp_lcommunity_list_name_expanded_cmd, + "no bgp large-community-list expanded LCOMMUNITY_LIST_NAME [seq (0-4294967295)] LINE...", + NO_STR + BGP_STR + LCOMMUNITY_LIST_STR + "Specify expanded large-community-list\n" + "Large community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify large community to reject\n" + "Specify large community to accept\n" + "An ordered list as a regular-expression\n") +{ + return lcommunity_list_unset_vty(vty, argc, argv, + LARGE_COMMUNITY_LIST_EXPANDED); +} + +static void lcommunity_list_show(struct vty *vty, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry == list->head) { + if (all_digit(list->name)) + vty_out(vty, "Large community %s list %s\n", + entry->style == + LARGE_COMMUNITY_LIST_STANDARD + ? "standard" + : "(expanded) access", + list->name); + else + vty_out(vty, + "Named large community %s list %s\n", + entry->style == + LARGE_COMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name); + } + vty_out(vty, " %s %s\n", community_direct_str(entry->direct), + community_list_config_str(entry)); + } +} + +DEFUN (show_lcommunity_list, + show_bgp_lcommunity_list_cmd, + "show bgp large-community-list", + SHOW_STR + BGP_STR + "List large-community list\n") +{ + struct community_list *list; + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, + LARGE_COMMUNITY_LIST_MASTER); + if (!cm) + return CMD_SUCCESS; + + for (list = cm->num.head; list; list = list->next) + lcommunity_list_show(vty, list); + + for (list = cm->str.head; list; list = list->next) + lcommunity_list_show(vty, list); + + return CMD_SUCCESS; +} + +DEFUN (show_lcommunity_list_arg, + show_bgp_lcommunity_list_arg_cmd, + "show bgp large-community-list <(1-500)|LCOMMUNITY_LIST_NAME> detail", + SHOW_STR + BGP_STR + "List large-community list\n" + "Large-community-list number\n" + "Large-community-list name\n" + "Detailed information on large-community-list\n") +{ + struct community_list *list; + + list = community_list_lookup(bgp_clist, argv[3]->arg, 0, + LARGE_COMMUNITY_LIST_MASTER); + if (!list) { + vty_out(vty, "%% Can't find large-community-list\n"); + return CMD_WARNING; + } + + lcommunity_list_show(vty, list); + + return CMD_SUCCESS; +} + +/* "extcommunity-list" keyword help string. */ +#define EXTCOMMUNITY_LIST_STR "Add a extended community list entry\n" +#define EXTCOMMUNITY_VAL_STR "Extended community attribute in 'rt aa:nn_or_IPaddr:nn' OR 'soo aa:nn_or_IPaddr:nn' format\n" + +DEFUN (extcommunity_list_standard, + bgp_extcommunity_list_standard_cmd, + "bgp extcommunity-list <(1-99)|standard EXTCOMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + BGP_STR + EXTCOMMUNITY_LIST_STR + "Extended Community list number (standard)\n" + "Specify standard extcommunity-list\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + EXTCOMMUNITY_VAL_STR) +{ + int style = EXTCOMMUNITY_LIST_STANDARD; + int direct = 0; + char *cl_number_or_name = NULL; + char *seq = NULL; + + int idx = 0; + + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "EXTCOMMUNITY_LIST_NAME", &idx); + cl_number_or_name = argv[idx]->arg; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + argv_find(argv, argc, "AA:NN", &idx); + char *str = argv_concat(argv, argc, idx); + + int ret = extcommunity_list_set(bgp_clist, cl_number_or_name, str, seq, + direct, style); + + XFREE(MTYPE_TMP, str); + + if (ret < 0) { + community_list_perror(vty, ret); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (extcommunity_list_name_expanded, + bgp_extcommunity_list_name_expanded_cmd, + "bgp extcommunity-list <(100-500)|expanded EXTCOMMUNITY_LIST_NAME> [seq (0-4294967295)] LINE...", + BGP_STR + EXTCOMMUNITY_LIST_STR + "Extended Community list number (expanded)\n" + "Specify expanded extcommunity-list\n" + "Extended Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + "An ordered list as a regular-expression\n") +{ + int style = EXTCOMMUNITY_LIST_EXPANDED; + int direct = 0; + char *cl_number_or_name = NULL; + char *seq = NULL; + int idx = 0; + + argv_find(argv, argc, "(100-500)", &idx); + argv_find(argv, argc, "EXTCOMMUNITY_LIST_NAME", &idx); + cl_number_or_name = argv[idx]->arg; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + direct = argv_find(argv, argc, "permit", &idx) ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + argv_find(argv, argc, "LINE", &idx); + char *str = argv_concat(argv, argc, idx); + + int ret = extcommunity_list_set(bgp_clist, cl_number_or_name, str, seq, + direct, style); + + XFREE(MTYPE_TMP, str); + + if (ret < 0) { + community_list_perror(vty, ret); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (no_extcommunity_list_standard_all, + no_bgp_extcommunity_list_standard_all_cmd, + "no bgp extcommunity-list <(1-99)|standard EXTCOMMUNITY_LIST_NAME> [seq (0-4294967295)] AA:NN...", + NO_STR + BGP_STR + EXTCOMMUNITY_LIST_STR + "Extended Community list number (standard)\n" + "Specify standard extcommunity-list\n" + "Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + EXTCOMMUNITY_VAL_STR) +{ + int style = EXTCOMMUNITY_LIST_STANDARD; + int direct = 0; + char *cl_number_or_name = NULL; + char *str = NULL; + char *seq = NULL; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "permit", &idx); + argv_find(argv, argc, "deny", &idx); + if (idx) { + direct = argv_find(argv, argc, "permit", &idx) + ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + + idx = 0; + argv_find(argv, argc, "AA:NN", &idx); + str = argv_concat(argv, argc, idx); + } + + idx = 0; + argv_find(argv, argc, "(1-99)", &idx); + argv_find(argv, argc, "EXTCOMMUNITY_LIST_NAME", &idx); + cl_number_or_name = argv[idx]->arg; + + extcommunity_list_unset(bgp_clist, cl_number_or_name, str, seq, direct, + style); + + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +ALIAS(no_extcommunity_list_standard_all, + no_bgp_extcommunity_list_standard_all_list_cmd, + "no bgp extcommunity-list <(1-99)|standard EXTCOMMUNITY_LIST_NAME>", + NO_STR BGP_STR EXTCOMMUNITY_LIST_STR + "Extended Community list number (standard)\n" + "Specify standard extcommunity-list\n" + "Community list name\n") + +DEFUN (no_extcommunity_list_expanded_all, + no_bgp_extcommunity_list_expanded_all_cmd, + "no bgp extcommunity-list <(100-500)|expanded EXTCOMMUNITY_LIST_NAME> [seq (0-4294967295)] LINE...", + NO_STR + BGP_STR + EXTCOMMUNITY_LIST_STR + "Extended Community list number (expanded)\n" + "Specify expanded extcommunity-list\n" + "Extended Community list name\n" + "Sequence number of an entry\n" + "Sequence number\n" + "Specify community to reject\n" + "Specify community to accept\n" + "An ordered list as a regular-expression\n") +{ + int style = EXTCOMMUNITY_LIST_EXPANDED; + int direct = 0; + char *cl_number_or_name = NULL; + char *str = NULL; + char *seq = NULL; + int idx = 0; + + if (argv_find(argv, argc, "(0-4294967295)", &idx)) + seq = argv[idx]->arg; + + idx = 0; + argv_find(argv, argc, "permit", &idx); + argv_find(argv, argc, "deny", &idx); + + if (idx) { + direct = argv_find(argv, argc, "permit", &idx) + ? COMMUNITY_PERMIT + : COMMUNITY_DENY; + + idx = 0; + argv_find(argv, argc, "LINE", &idx); + str = argv_concat(argv, argc, idx); + } + + idx = 0; + argv_find(argv, argc, "(100-500)", &idx); + argv_find(argv, argc, "EXTCOMMUNITY_LIST_NAME", &idx); + cl_number_or_name = argv[idx]->arg; + + extcommunity_list_unset(bgp_clist, cl_number_or_name, str, seq, direct, + style); + + XFREE(MTYPE_TMP, str); + + return CMD_SUCCESS; +} + +ALIAS(no_extcommunity_list_expanded_all, + no_bgp_extcommunity_list_expanded_all_list_cmd, + "no bgp extcommunity-list <(100-500)|expanded EXTCOMMUNITY_LIST_NAME>", + NO_STR BGP_STR EXTCOMMUNITY_LIST_STR + "Extended Community list number (expanded)\n" + "Specify expanded extcommunity-list\n" + "Extended Community list name\n") + +static void extcommunity_list_show(struct vty *vty, struct community_list *list) +{ + struct community_entry *entry; + + for (entry = list->head; entry; entry = entry->next) { + if (entry == list->head) { + if (all_digit(list->name)) + vty_out(vty, "Extended community %s list %s\n", + entry->style == EXTCOMMUNITY_LIST_STANDARD + ? "standard" + : "(expanded) access", + list->name); + else + vty_out(vty, + "Named extended community %s list %s\n", + entry->style == EXTCOMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name); + } + vty_out(vty, " %s %s\n", community_direct_str(entry->direct), + community_list_config_str(entry)); + } +} + +DEFUN (show_extcommunity_list, + show_bgp_extcommunity_list_cmd, + "show bgp extcommunity-list", + SHOW_STR + BGP_STR + "List extended-community list\n") +{ + struct community_list *list; + struct community_list_master *cm; + + cm = community_list_master_lookup(bgp_clist, EXTCOMMUNITY_LIST_MASTER); + if (!cm) + return CMD_SUCCESS; + + for (list = cm->num.head; list; list = list->next) + extcommunity_list_show(vty, list); + + for (list = cm->str.head; list; list = list->next) + extcommunity_list_show(vty, list); + + return CMD_SUCCESS; +} + +DEFUN (show_extcommunity_list_arg, + show_bgp_extcommunity_list_arg_cmd, + "show bgp extcommunity-list <(1-500)|EXTCOMMUNITY_LIST_NAME> detail", + SHOW_STR + BGP_STR + "List extended-community list\n" + "Extcommunity-list number\n" + "Extcommunity-list name\n" + "Detailed information on extcommunity-list\n") +{ + int idx_comm_list = 3; + struct community_list *list; + + list = community_list_lookup(bgp_clist, argv[idx_comm_list]->arg, 0, + EXTCOMMUNITY_LIST_MASTER); + if (!list) { + vty_out(vty, "%% Can't find extcommunity-list\n"); + return CMD_WARNING; + } + + extcommunity_list_show(vty, list); + + return CMD_SUCCESS; +} + +/* Display community-list and extcommunity-list configuration. */ +static int community_list_config_write(struct vty *vty) +{ + struct community_list *list; + struct community_entry *entry; + struct community_list_master *cm; + int write = 0; + + /* Community-list. */ + cm = community_list_master_lookup(bgp_clist, COMMUNITY_LIST_MASTER); + + for (list = cm->num.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp community-list %s seq %" PRId64 " %s %s\n", + list->name, entry->seq, + community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + for (list = cm->str.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp community-list %s %s seq %" PRId64 " %s %s\n", + entry->style == COMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name, entry->seq, + community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + + /* Extcommunity-list. */ + cm = community_list_master_lookup(bgp_clist, EXTCOMMUNITY_LIST_MASTER); + + for (list = cm->num.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp extcommunity-list %s seq %" PRId64 " %s %s\n", + list->name, entry->seq, + community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + for (list = cm->str.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp extcommunity-list %s %s seq %" PRId64" %s %s\n", + entry->style == EXTCOMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name, entry->seq, + community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + + + /* lcommunity-list. */ + cm = community_list_master_lookup(bgp_clist, + LARGE_COMMUNITY_LIST_MASTER); + + for (list = cm->num.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp large-community-list %s seq %" PRId64" %s %s\n", + list->name, entry->seq, + community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + for (list = cm->str.head; list; list = list->next) + for (entry = list->head; entry; entry = entry->next) { + vty_out(vty, + "bgp large-community-list %s %s seq %" PRId64" %s %s\n", + + entry->style == LARGE_COMMUNITY_LIST_STANDARD + ? "standard" + : "expanded", + list->name, entry->seq, community_direct_str(entry->direct), + community_list_config_str(entry)); + write++; + } + + return write; +} + +static int community_list_config_write(struct vty *vty); +static struct cmd_node community_list_node = { + .name = "community list", + .node = COMMUNITY_LIST_NODE, + .prompt = "", + .config_write = community_list_config_write, +}; + +static void community_list_vty(void) +{ + install_node(&community_list_node); + + /* Community-list. */ + install_element(CONFIG_NODE, &bgp_community_list_standard_cmd); + install_element(CONFIG_NODE, &bgp_community_list_expanded_all_cmd); + install_element(CONFIG_NODE, &no_bgp_community_list_standard_all_cmd); + install_element(CONFIG_NODE, &no_bgp_community_list_standard_all_list_cmd); + install_element(CONFIG_NODE, &no_bgp_community_list_expanded_all_cmd); + install_element(CONFIG_NODE, &no_bgp_community_list_expanded_all_list_cmd); + install_element(VIEW_NODE, &show_bgp_community_list_cmd); + install_element(VIEW_NODE, &show_bgp_community_list_arg_cmd); + + /* Extcommunity-list. */ + install_element(CONFIG_NODE, &bgp_extcommunity_list_standard_cmd); + install_element(CONFIG_NODE, &bgp_extcommunity_list_name_expanded_cmd); + install_element(CONFIG_NODE, &no_bgp_extcommunity_list_standard_all_cmd); + install_element(CONFIG_NODE, + &no_bgp_extcommunity_list_standard_all_list_cmd); + install_element(CONFIG_NODE, &no_bgp_extcommunity_list_expanded_all_cmd); + install_element(CONFIG_NODE, + &no_bgp_extcommunity_list_expanded_all_list_cmd); + install_element(VIEW_NODE, &show_bgp_extcommunity_list_cmd); + install_element(VIEW_NODE, &show_bgp_extcommunity_list_arg_cmd); + + /* Large Community List */ + install_element(CONFIG_NODE, &bgp_lcommunity_list_standard_cmd); + install_element(CONFIG_NODE, &bgp_lcommunity_list_expanded_cmd); + install_element(CONFIG_NODE, &bgp_lcommunity_list_name_standard_cmd); + install_element(CONFIG_NODE, &bgp_lcommunity_list_name_expanded_cmd); + install_element(CONFIG_NODE, &no_bgp_lcommunity_list_all_cmd); + install_element(CONFIG_NODE, + &no_bgp_lcommunity_list_name_standard_all_cmd); + install_element(CONFIG_NODE, + &no_bgp_lcommunity_list_name_expanded_all_cmd); + install_element(CONFIG_NODE, &no_bgp_lcommunity_list_standard_cmd); + install_element(CONFIG_NODE, &no_bgp_lcommunity_list_expanded_cmd); + install_element(CONFIG_NODE, &no_bgp_lcommunity_list_name_standard_cmd); + install_element(CONFIG_NODE, &no_bgp_lcommunity_list_name_expanded_cmd); + install_element(VIEW_NODE, &show_bgp_lcommunity_list_cmd); + install_element(VIEW_NODE, &show_bgp_lcommunity_list_arg_cmd); + + bgp_community_list_command_completion_setup(); +} + +static struct cmd_node community_alias_node = { + .name = "community alias", + .node = COMMUNITY_ALIAS_NODE, + .prompt = "", + .config_write = bgp_community_alias_write, +}; + +void community_alias_vty(void) +{ + install_node(&community_alias_node); + + /* Community-list. */ + install_element(CONFIG_NODE, &bgp_community_alias_cmd); + + bgp_community_alias_command_completion_setup(); +} diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h new file mode 100644 index 0000000..addd717 --- /dev/null +++ b/bgpd/bgp_vty.h @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP VTY interface. + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_VTY_H +#define _QUAGGA_BGP_VTY_H + +#include "bgpd/bgpd.h" +#include "stream.h" +struct bgp; + +#define BGP_INSTANCE_HELP_STR "BGP view\nBGP VRF\nView/VRF name\n" +#define BGP_INSTANCE_ALL_HELP_STR "BGP view\nBGP VRF\nAll Views/VRFs\n" + +#define BGP_AFI_CMD_STR "" +#define BGP_AFI_HELP_STR BGP_AF_STR BGP_AF_STR +#define BGP_SAFI_CMD_STR "" +#define BGP_SAFI_HELP_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR +#define BGP_AFI_SAFI_CMD_STR BGP_AFI_CMD_STR" "BGP_SAFI_CMD_STR +#define BGP_AFI_SAFI_HELP_STR BGP_AFI_HELP_STR BGP_SAFI_HELP_STR + +#define BGP_SAFI_WITH_LABEL_CMD_STR "" +#define BGP_SAFI_WITH_LABEL_HELP_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR + +#define BGP_SELF_ORIG_CMD_STR "self-originate" +#define BGP_SELF_ORIG_HELP_STR "Display only self-originated routes\n" + +#define SHOW_GR_HEADER \ + "Codes: GR - Graceful Restart," \ + " * - Inheriting Global GR Config,\n" \ + " Restart - GR Mode-Restarting," \ + " Helper - GR Mode-Helper,\n" \ + " Disable - GR Mode-Disable.\n\n" + +#define BGP_SHOW_SUMMARY_HEADER_ALL \ + "V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc\n" +#define BGP_SHOW_SUMMARY_HEADER_ALL_WIDE \ + "V AS LocalAS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc\n" +#define BGP_SHOW_SUMMARY_HEADER_FAILED "EstdCnt DropCnt ResetTime Reason\n" + +#define BGP_SHOW_PEER_GR_CAPABILITY(vty, p, json) \ + do { \ + bgp_show_neighbor_graceful_restart_local_mode(vty, p, json); \ + bgp_show_neighbor_graceful_restart_remote_mode(vty, p, json); \ + bgp_show_neighnor_graceful_restart_flags(vty, p, json); \ + bgp_show_neighbor_graceful_restart_time(vty, p, json); \ + bgp_show_neighbor_graceful_restart_capability_per_afi_safi( \ + vty, p, json); \ + } while (0) + +#define VTY_BGP_GR_DEFINE_LOOP_VARIABLE \ + struct peer *peer_loop = NULL; \ + struct listnode *node = NULL; \ + struct listnode *nnode = NULL; \ + bool gr_router_detected = false + +#define VTY_BGP_GR_ROUTER_DETECT(_bgp, _peer, _peer_list) \ + do { \ + if (_peer->bgp->t_startup) \ + bgp_peer_gr_flags_update(_peer); \ + for (ALL_LIST_ELEMENTS(_peer_list, node, nnode, peer_loop)) { \ + if (CHECK_FLAG(peer_loop->flags, \ + PEER_FLAG_GRACEFUL_RESTART)) \ + gr_router_detected = true; \ + } \ + } while (0) + + +#define VTY_SEND_BGP_GR_CAPABILITY_TO_ZEBRA(_bgp, _ret) \ + do { \ + if (gr_router_detected \ + && _bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { \ + if (bgp_zebra_send_capabilities(_bgp, false)) \ + _ret = BGP_ERR_INVALID_VALUE; \ + } else if (!gr_router_detected \ + && _bgp->present_zebra_gr_state \ + == ZEBRA_GR_ENABLE) { \ + if (bgp_zebra_send_capabilities(_bgp, true)) \ + _ret = BGP_ERR_INVALID_VALUE; \ + } \ + } while (0) + +#define VTY_BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA( \ + _bgp, _peer_list, _ret) \ + do { \ + struct peer *peer_loop; \ + bool gr_router_detected = false; \ + struct listnode *node = {0}; \ + struct listnode *nnode = {0}; \ + for (ALL_LIST_ELEMENTS(_peer_list, node, nnode, peer_loop)) { \ + if (peer_loop->bgp->t_startup) \ + bgp_peer_gr_flags_update(peer_loop); \ + if (CHECK_FLAG(peer_loop->flags, \ + PEER_FLAG_GRACEFUL_RESTART)) \ + gr_router_detected = true; \ + } \ + if (gr_router_detected \ + && _bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { \ + if (bgp_zebra_send_capabilities(_bgp, false)) \ + _ret = BGP_ERR_INVALID_VALUE; \ + } else if (!gr_router_detected \ + && _bgp->present_zebra_gr_state \ + == ZEBRA_GR_ENABLE) { \ + if (bgp_zebra_send_capabilities(_bgp, true)) \ + _ret = BGP_ERR_INVALID_VALUE; \ + } \ + } while (0) + + +#define PRINT_EOR(_eor_flag) \ + do { \ + if (eor_flag) \ + vty_out(vty, "Yes\n"); \ + else \ + vty_out(vty, "No\n"); \ + } while (0) + +#define PRINT_EOR_JSON(_eor_flag) \ + do { \ + if (eor_flag) \ + json_object_boolean_true_add( \ + json_endofrib_status, \ + "endOfRibSentAfterUpdate"); \ + else \ + json_object_boolean_false_add( \ + json_endofrib_status, \ + "endOfRibSentAfterUpdate"); \ + } while (0) + +extern void bgp_clear_soft_in(struct bgp *bgp, afi_t afi, safi_t safi); +extern void bgp_vty_init(void); +extern void community_alias_vty(void); +extern const char *get_afi_safi_str(afi_t afi, safi_t safi, bool for_json); +extern int bgp_get_vty(struct bgp **bgp, as_t *as, const char *name, + enum bgp_instance_type inst_type, const char *as_pretty, + enum asnotation_mode asnotation); +extern void bgp_config_write_update_delay(struct vty *vty, struct bgp *bgp); +extern void bgp_config_write_wpkt_quanta(struct vty *vty, struct bgp *bgp); +extern void bgp_config_write_rpkt_quanta(struct vty *vty, struct bgp *bgp); +extern void bgp_config_write_listen(struct vty *vty, struct bgp *bgp); +extern void bgp_config_write_coalesce_time(struct vty *vty, struct bgp *bgp); +extern int bgp_vty_return(struct vty *vty, enum bgp_create_error_code ret); +extern bool bgp_config_inprocess(void); +extern struct peer *peer_and_group_lookup_vty(struct vty *vty, + const char *peer_str); + +extern afi_t bgp_vty_afi_from_str(const char *afi_str); + +extern safi_t bgp_vty_safi_from_str(const char *safi_str); + +extern int argv_find_and_parse_afi(struct cmd_token **argv, int argc, + int *index, afi_t *afi); + +extern int argv_find_and_parse_safi(struct cmd_token **argv, int argc, + int *index, safi_t *safi); + +extern int bgp_vty_find_and_parse_afi_safi_bgp(struct vty *vty, + struct cmd_token **argv, + int argc, int *idx, afi_t *afi, + safi_t *safi, struct bgp **bgp, + bool use_json); +int bgp_vty_find_and_parse_bgp(struct vty *vty, struct cmd_token **argv, + int argc, struct bgp **bgp, bool use_json); +extern int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, + safi_t safi, const char *neighbor, int as_type, + as_t as, uint16_t show_flags); +extern bool peergroup_flag_check(struct peer *peer, uint64_t flag); +extern bool peergroup_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag); + +#endif /* _QUAGGA_BGP_VTY_H */ diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c new file mode 100644 index 0000000..7d1e983 --- /dev/null +++ b/bgpd/bgp_zebra.c @@ -0,0 +1,4173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* zebra client + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + * Copyright (c) 2023 LabN Consulting, L.L.C. + */ + +#include + +#include "command.h" +#include "stream.h" +#include "network.h" +#include "prefix.h" +#include "log.h" +#include "sockunion.h" +#include "zclient.h" +#include "routemap.h" +#include "frrevent.h" +#include "queue.h" +#include "memory.h" +#include "lib/json.h" +#include "lib/bfd.h" +#include "lib/route_opaque.h" +#include "filter.h" +#include "mpls.h" +#include "vxlan.h" +#include "pbr.h" +#include "frrdistance.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_label.h" +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#endif +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_labelpool.h" +#include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_mac.h" +#include "bgpd/bgp_trace.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_lcommunity.h" + +/* All information about zebra. */ +struct zclient *zclient = NULL; +struct zclient *zclient_sync; +static bool bgp_zebra_label_manager_connect(void); + +/* hook to indicate vrf status change for SNMP */ +DEFINE_HOOK(bgp_vrf_status_changed, (struct bgp *bgp, struct interface *ifp), + (bgp, ifp)); + +DEFINE_MTYPE_STATIC(BGPD, BGP_IF_INFO, "BGP interface context"); + +/* Can we install into zebra? */ +static inline bool bgp_install_info_to_zebra(struct bgp *bgp) +{ + if (zclient->sock <= 0) + return false; + + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing information", + __func__); + return false; + } + + return true; +} + +int zclient_num_connects; + +/* Router-id update message from zebra. */ +static int bgp_router_id_update(ZAPI_CALLBACK_ARGS) +{ + struct prefix router_id; + + zebra_router_id_update_read(zclient->ibuf, &router_id); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx Router Id update VRF %u Id %pFX", vrf_id, + &router_id); + + bgp_router_id_zebra_bump(vrf_id, &router_id); + return 0; +} + +/* Set or clear interface on which unnumbered neighbor is configured. This + * would in turn cause BGP to initiate or turn off IPv6 RAs on this + * interface. + */ +static void bgp_update_interface_nbrs(struct bgp *bgp, struct interface *ifp, + struct interface *upd_ifp) +{ + struct listnode *node, *nnode; + struct peer *peer; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->conf_if && (strcmp(peer->conf_if, ifp->name) == 0)) { + if (upd_ifp) { + peer->ifp = upd_ifp; + bgp_zebra_initiate_radv(bgp, peer); + } else { + bgp_zebra_terminate_radv(bgp, peer); + peer->ifp = upd_ifp; + } + } + } +} + +static int bgp_read_fec_update(ZAPI_CALLBACK_ARGS) +{ + bgp_parse_fec_update(); + return 0; +} + +static void bgp_start_interface_nbrs(struct bgp *bgp, struct interface *ifp) +{ + struct listnode *node, *nnode; + struct peer *peer; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->conf_if && (strcmp(peer->conf_if, ifp->name) == 0) && + !peer_established(peer->connection)) { + if (peer_active(peer)) + BGP_EVENT_ADD(peer->connection, BGP_Stop); + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + } +} + +static void bgp_nbr_connected_add(struct bgp *bgp, struct nbr_connected *ifc) +{ + struct connected *connected; + struct interface *ifp; + struct prefix *p; + + /* Kick-off the FSM for any relevant peers only if there is a + * valid local address on the interface. + */ + ifp = ifc->ifp; + frr_each (if_connected, ifp->connected, connected) { + p = connected->address; + if (p->family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6)) + break; + } + if (!connected) + return; + + bgp_start_interface_nbrs(bgp, ifp); +} + +static void bgp_nbr_connected_delete(struct bgp *bgp, struct nbr_connected *ifc, + int del) +{ + struct listnode *node, *nnode; + struct peer *peer; + struct interface *ifp; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->conf_if + && (strcmp(peer->conf_if, ifc->ifp->name) == 0)) { + peer->last_reset = PEER_DOWN_NBR_ADDR_DEL; + BGP_EVENT_ADD(peer->connection, BGP_Stop); + } + } + /* Free neighbor also, if we're asked to. */ + if (del) { + ifp = ifc->ifp; + listnode_delete(ifp->nbr_connected, ifc); + nbr_connected_free(ifc); + } +} + +static int bgp_ifp_destroy(struct interface *ifp) +{ + struct bgp *bgp; + + bgp = ifp->vrf->info; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx Intf del VRF %u IF %s", ifp->vrf->vrf_id, + ifp->name); + + if (bgp) { + bgp_update_interface_nbrs(bgp, ifp, NULL); + hook_call(bgp_vrf_status_changed, bgp, ifp); + } + + bgp_mac_del_mac_entry(ifp); + + return 0; +} + +static int bgp_ifp_up(struct interface *ifp) +{ + struct connected *c; + struct nbr_connected *nc; + struct listnode *node, *nnode; + struct bgp *bgp; + + bgp = ifp->vrf->info; + + bgp_mac_add_mac_entry(ifp); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx Intf up VRF %u IF %s", ifp->vrf->vrf_id, + ifp->name); + + if (!bgp) + return 0; + + frr_each (if_connected, ifp->connected, c) + bgp_connected_add(bgp, c); + + for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) + bgp_nbr_connected_add(bgp, nc); + + hook_call(bgp_vrf_status_changed, bgp, ifp); + bgp_nht_ifp_up(ifp); + + if (bgp_get_default() && if_is_loopback(ifp)) { + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP6); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP6); + vpn_leak_postchange_all(); + } + + return 0; +} + +static int bgp_ifp_down(struct interface *ifp) +{ + struct connected *c; + struct nbr_connected *nc; + struct listnode *node, *nnode; + struct bgp *bgp; + struct peer *peer; + + bgp = ifp->vrf->info; + + bgp_mac_del_mac_entry(ifp); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx Intf down VRF %u IF %s", ifp->vrf->vrf_id, + ifp->name); + + if (!bgp) + return 0; + + frr_each (if_connected, ifp->connected, c) + bgp_connected_delete(bgp, c); + + for (ALL_LIST_ELEMENTS(ifp->nbr_connected, node, nnode, nc)) + bgp_nbr_connected_delete(bgp, nc, 1); + + /* Fast external-failover */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_NO_FAST_EXT_FAILOVER)) { + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + /* Take down directly connected peers. */ + if ((peer->ttl != BGP_DEFAULT_TTL) + && (peer->gtsm_hops != BGP_GTSM_HOPS_CONNECTED)) + continue; + + if (ifp == peer->nexthop.ifp) { + BGP_EVENT_ADD(peer->connection, BGP_Stop); + peer->last_reset = PEER_DOWN_IF_DOWN; + } + } + } + + hook_call(bgp_vrf_status_changed, bgp, ifp); + bgp_nht_ifp_down(ifp); + + if (bgp_get_default() && if_is_loopback(ifp)) { + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP6); + vpn_leak_zebra_vrf_sid_withdraw(bgp, AFI_IP); + vpn_leak_zebra_vrf_sid_withdraw(bgp, AFI_IP6); + vpn_leak_postchange_all(); + } + + return 0; +} + +static int bgp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct bgp *bgp; + struct peer *peer; + struct prefix *addr; + struct listnode *node, *nnode; + afi_t afi; + safi_t safi; + + bgp = bgp_lookup_by_vrf_id(vrf_id); + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + if (bgp_debug_zebra(ifc->address)) + zlog_debug("Rx Intf address add VRF %u IF %s addr %pFX", vrf_id, + ifc->ifp->name, ifc->address); + + if (!bgp) + return 0; + + if (if_is_operative(ifc->ifp)) { + bgp_connected_add(bgp, ifc); + + /* If we have learnt of any neighbors on this interface, + * check to kick off any BGP interface-based neighbors, + * but only if this is a link-local address. + */ + if (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6) + && !list_isempty(ifc->ifp->nbr_connected)) + bgp_start_interface_nbrs(bgp, ifc->ifp); + else { + addr = ifc->address; + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (addr->family == AF_INET) + continue; + + /* + * If the Peer's interface name matches the + * interface name for which BGP received the + * update and if the received interface address + * is a globalV6 and if the peer is currently + * using a v4-mapped-v6 addr or a link local + * address, then copy the Rxed global v6 addr + * into peer's v6_global and send updates out + * with new nexthop addr. + */ + if ((peer->conf_if && + (strcmp(peer->conf_if, ifc->ifp->name) == + 0)) && + !IN6_IS_ADDR_LINKLOCAL(&addr->u.prefix6) && + ((IS_MAPPED_IPV6( + &peer->nexthop.v6_global)) || + IN6_IS_ADDR_LINKLOCAL( + &peer->nexthop.v6_global))) { + + if (bgp_debug_zebra(ifc->address)) { + zlog_debug( + "Update peer %pBP's current intf addr %pI6 and send updates", + peer, + &peer->nexthop + .v6_global); + } + memcpy(&peer->nexthop.v6_global, + &addr->u.prefix6, + IPV6_MAX_BYTELEN); + FOREACH_AFI_SAFI (afi, safi) + bgp_announce_route(peer, afi, + safi, true); + } + } + } + } + + return 0; +} + +static int bgp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct listnode *node, *nnode; + struct connected *ifc; + struct peer *peer; + struct bgp *bgp; + struct prefix *addr; + + bgp = bgp_lookup_by_vrf_id(vrf_id); + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + if (bgp_debug_zebra(ifc->address)) + zlog_debug("Rx Intf address del VRF %u IF %s addr %pFX", vrf_id, + ifc->ifp->name, ifc->address); + + if (bgp && if_is_operative(ifc->ifp)) { + bgp_connected_delete(bgp, ifc); + } + + addr = ifc->address; + + if (bgp) { + /* + * When we are using the v6 global as part of the peering + * nexthops and we are removing it, then we need to + * clear the peer data saved for that nexthop and + * cause a re-announcement of the route. Since + * we do not want the peering to bounce. + */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + afi_t afi; + safi_t safi; + + if (addr->family == AF_INET) + continue; + + if (!IN6_IS_ADDR_LINKLOCAL(&addr->u.prefix6) + && memcmp(&peer->nexthop.v6_global, + &addr->u.prefix6, 16) + == 0) { + memset(&peer->nexthop.v6_global, 0, 16); + FOREACH_AFI_SAFI (afi, safi) + bgp_announce_route(peer, afi, safi, + true); + } + } + } + + connected_free(&ifc); + + return 0; +} + +static int bgp_interface_nbr_address_add(ZAPI_CALLBACK_ARGS) +{ + struct nbr_connected *ifc = NULL; + struct bgp *bgp; + + ifc = zebra_interface_nbr_address_read(cmd, zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + if (bgp_debug_zebra(ifc->address)) + zlog_debug("Rx Intf neighbor add VRF %u IF %s addr %pFX", + vrf_id, ifc->ifp->name, ifc->address); + + if (if_is_operative(ifc->ifp)) { + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (bgp) + bgp_nbr_connected_add(bgp, ifc); + } + + return 0; +} + +static int bgp_interface_nbr_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct nbr_connected *ifc = NULL; + struct bgp *bgp; + + ifc = zebra_interface_nbr_address_read(cmd, zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + if (bgp_debug_zebra(ifc->address)) + zlog_debug("Rx Intf neighbor del VRF %u IF %s addr %pFX", + vrf_id, ifc->ifp->name, ifc->address); + + if (if_is_operative(ifc->ifp)) { + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (bgp) + bgp_nbr_connected_delete(bgp, ifc, 0); + } + + nbr_connected_free(ifc); + + return 0; +} + +/* Zebra route add and delete treatment. */ +static int zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + enum nexthop_types_t nhtype; + enum blackhole_type bhtype = BLACKHOLE_UNSPEC; + struct zapi_route api; + union g_addr nexthop = {}; + ifindex_t ifindex; + int add, i; + struct bgp *bgp; + + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + /* ignore link-local address. */ + if (api.prefix.family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) + return 0; + + ifindex = api.nexthops[0].ifindex; + nhtype = api.nexthops[0].type; + + /* api_nh structure has union of gate and bh_type */ + if (nhtype == NEXTHOP_TYPE_BLACKHOLE) { + /* bh_type is only applicable if NEXTHOP_TYPE_BLACKHOLE*/ + bhtype = api.nexthops[0].bh_type; + } else + nexthop = api.nexthops[0].gate; + + add = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); + if (add) { + /* + * The ADD message is actually an UPDATE and there is no + * explicit DEL + * for a prior redistributed route, if any. So, perform an + * implicit + * DEL processing for the same redistributed route from any + * other + * source type. + */ + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (i != api.type) + bgp_redistribute_delete(bgp, &api.prefix, i, + api.instance); + } + + /* Now perform the add/update. */ + bgp_redistribute_add(bgp, &api.prefix, &nexthop, ifindex, + nhtype, bhtype, api.distance, api.metric, + api.type, api.instance, api.tag); + } else { + bgp_redistribute_delete(bgp, &api.prefix, api.type, + api.instance); + } + + if (bgp_debug_zebra(&api.prefix)) { + char buf[PREFIX_STRLEN]; + + if (add) { + inet_ntop(api.prefix.family, &nexthop, buf, + sizeof(buf)); + zlog_debug( + "Rx route ADD VRF %u %s[%d] %pFX nexthop %s (type %d if %u) metric %u distance %u tag %" ROUTE_TAG_PRI, + vrf_id, zebra_route_string(api.type), + api.instance, &api.prefix, buf, nhtype, ifindex, + api.metric, api.distance, api.tag); + } else { + zlog_debug("Rx route DEL VRF %u %s[%d] %pFX", vrf_id, + zebra_route_string(api.type), api.instance, + &api.prefix); + } + } + + return 0; +} + +struct interface *if_lookup_by_ipv4(struct in_addr *addr, vrf_id_t vrf_id) +{ + struct vrf *vrf; + struct interface *ifp; + struct connected *connected; + struct prefix_ipv4 p; + struct prefix *cp; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + p.family = AF_INET; + p.prefix = *addr; + p.prefixlen = IPV4_MAX_BITLEN; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET) + if (prefix_match(cp, (struct prefix *)&p)) + return ifp; + } + } + return NULL; +} + +struct interface *if_lookup_by_ipv4_exact(struct in_addr *addr, vrf_id_t vrf_id) +{ + struct vrf *vrf; + struct interface *ifp; + struct connected *connected; + struct prefix *cp; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET) + if (IPV4_ADDR_SAME(&cp->u.prefix4, addr)) + return ifp; + } + } + return NULL; +} + +struct interface *if_lookup_by_ipv6(struct in6_addr *addr, ifindex_t ifindex, + vrf_id_t vrf_id) +{ + struct vrf *vrf; + struct interface *ifp; + struct connected *connected; + struct prefix_ipv6 p; + struct prefix *cp; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + p.family = AF_INET6; + p.prefix = *addr; + p.prefixlen = IPV6_MAX_BITLEN; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET6) + if (prefix_match(cp, (struct prefix *)&p)) { + if (IN6_IS_ADDR_LINKLOCAL( + &cp->u.prefix6)) { + if (ifindex == ifp->ifindex) + return ifp; + } else + return ifp; + } + } + } + return NULL; +} + +struct interface *if_lookup_by_ipv6_exact(struct in6_addr *addr, + ifindex_t ifindex, vrf_id_t vrf_id) +{ + struct vrf *vrf; + struct interface *ifp; + struct connected *connected; + struct prefix *cp; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET6) + if (IPV6_ADDR_SAME(&cp->u.prefix6, addr)) { + if (IN6_IS_ADDR_LINKLOCAL( + &cp->u.prefix6)) { + if (ifindex == ifp->ifindex) + return ifp; + } else + return ifp; + } + } + } + return NULL; +} + +static int if_get_ipv6_global(struct interface *ifp, struct in6_addr *addr) +{ + struct connected *connected; + struct prefix *cp; + + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET6) + if (!IN6_IS_ADDR_LINKLOCAL(&cp->u.prefix6)) { + memcpy(addr, &cp->u.prefix6, IPV6_MAX_BYTELEN); + return 1; + } + } + return 0; +} + +static bool if_get_ipv6_local(struct interface *ifp, struct in6_addr *addr) +{ + struct connected *connected; + struct prefix *cp; + + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + + if (cp->family == AF_INET6) + if (IN6_IS_ADDR_LINKLOCAL(&cp->u.prefix6)) { + memcpy(addr, &cp->u.prefix6, IPV6_MAX_BYTELEN); + return true; + } + } + return false; +} + +static int if_get_ipv4_address(struct interface *ifp, struct in_addr *addr) +{ + struct connected *connected; + struct prefix *cp; + + frr_each (if_connected, ifp->connected, connected) { + cp = connected->address; + if ((cp->family == AF_INET) + && !ipv4_martian(&(cp->u.prefix4))) { + *addr = cp->u.prefix4; + return 1; + } + } + return 0; +} + + +bool bgp_zebra_nexthop_set(union sockunion *local, union sockunion *remote, + struct bgp_nexthop *nexthop, struct peer *peer) +{ + int ret = 0; + struct interface *ifp = NULL; + bool v6_ll_avail = true; + + memset(nexthop, 0, sizeof(struct bgp_nexthop)); + + if (!local) + return false; + if (!remote) + return false; + + if (local->sa.sa_family == AF_INET) { + nexthop->v4 = local->sin.sin_addr; + if (peer->update_if) + ifp = if_lookup_by_name(peer->update_if, + peer->bgp->vrf_id); + else + ifp = if_lookup_by_ipv4_exact(&local->sin.sin_addr, + peer->bgp->vrf_id); + } + if (local->sa.sa_family == AF_INET6) { + memcpy(&nexthop->v6_global, &local->sin6.sin6_addr, IPV6_MAX_BYTELEN); + if (IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr)) { + if (peer->conf_if || peer->ifname) + ifp = if_lookup_by_name(peer->conf_if + ? peer->conf_if + : peer->ifname, + peer->bgp->vrf_id); + else if (peer->update_if) + ifp = if_lookup_by_name(peer->update_if, + peer->bgp->vrf_id); + } else if (peer->update_if) + ifp = if_lookup_by_name(peer->update_if, + peer->bgp->vrf_id); + else + ifp = if_lookup_by_ipv6_exact(&local->sin6.sin6_addr, + local->sin6.sin6_scope_id, + peer->bgp->vrf_id); + } + + /* Handle peerings via loopbacks. For instance, peer between + * 127.0.0.1 and 127.0.0.2. In short, allow peering with self + * via 127.0.0.0/8. + */ + if (!ifp && cmd_allow_reserved_ranges_get()) + ifp = if_get_vrf_loopback(peer->bgp->vrf_id); + + if (!ifp) { + /* + * BGP views do not currently get proper data + * from zebra( when attached ) to be able to + * properly resolve nexthops, so give this + * instance type a pass. + */ + if (peer->bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + return true; + /* + * If we have no interface data but we have established + * some connection w/ zebra than something has gone + * terribly terribly wrong here, so say this failed + * If we do not any zebra connection then not + * having a ifp pointer is ok. + */ + return zclient_num_connects ? false : true; + } + + nexthop->ifp = ifp; + + /* IPv4 connection, fetch and store IPv6 local address(es) if any. */ + if (local->sa.sa_family == AF_INET) { + /* IPv6 nexthop*/ + ret = if_get_ipv6_global(ifp, &nexthop->v6_global); + + if (!ret) { + /* There is no global nexthop. Use link-local address as + * both the + * global and link-local nexthop. In this scenario, the + * expectation + * for interop is that the network admin would use a + * route-map to + * specify the global IPv6 nexthop. + */ + v6_ll_avail = + if_get_ipv6_local(ifp, &nexthop->v6_global); + memcpy(&nexthop->v6_local, &nexthop->v6_global, + IPV6_MAX_BYTELEN); + } else + v6_ll_avail = + if_get_ipv6_local(ifp, &nexthop->v6_local); + + /* + * If we are a v4 connection and we are not doing unnumbered + * not having a v6 LL address is ok + */ + if (!v6_ll_avail && !peer->conf_if) + v6_ll_avail = true; + if (if_lookup_by_ipv4(&remote->sin.sin_addr, peer->bgp->vrf_id)) + peer->shared_network = 1; + else + peer->shared_network = 0; + } + + /* IPv6 connection, fetch and store IPv4 local address if any. */ + if (local->sa.sa_family == AF_INET6) { + struct interface *direct = NULL; + + /* IPv4 nexthop. */ + ret = if_get_ipv4_address(ifp, &nexthop->v4); + if (!ret && peer->local_id.s_addr != INADDR_ANY) + nexthop->v4 = peer->local_id; + + /* Global address*/ + if (!IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr)) { + memcpy(&nexthop->v6_global, &local->sin6.sin6_addr, + IPV6_MAX_BYTELEN); + + /* If directly connected set link-local address. */ + direct = if_lookup_by_ipv6(&remote->sin6.sin6_addr, + remote->sin6.sin6_scope_id, + peer->bgp->vrf_id); + if (direct) + v6_ll_avail = if_get_ipv6_local( + ifp, &nexthop->v6_local); + /* + * It's fine to not have a v6 LL when using + * update-source loopback/vrf + */ + if (!v6_ll_avail && if_is_loopback(ifp)) + v6_ll_avail = true; + else if (!v6_ll_avail) { + flog_warn( + EC_BGP_NO_LL_ADDRESS_AVAILABLE, + "Interface: %s does not have a v6 LL address associated with it, waiting until one is created for it", + ifp->name); + } + } else + /* Link-local address. */ + { + ret = if_get_ipv6_global(ifp, &nexthop->v6_global); + + /* If there is no global address. Set link-local + address as + global. I know this break RFC specification... */ + /* In this scenario, the expectation for interop is that + * the + * network admin would use a route-map to specify the + * global + * IPv6 nexthop. + */ + if (!ret) + memcpy(&nexthop->v6_global, + &local->sin6.sin6_addr, + IPV6_MAX_BYTELEN); + /* Always set the link-local address */ + memcpy(&nexthop->v6_local, &local->sin6.sin6_addr, + IPV6_MAX_BYTELEN); + } + + if (IN6_IS_ADDR_LINKLOCAL(&local->sin6.sin6_addr) + || if_lookup_by_ipv6(&remote->sin6.sin6_addr, + remote->sin6.sin6_scope_id, + peer->bgp->vrf_id)) + peer->shared_network = 1; + else + peer->shared_network = 0; + } + +/* KAME stack specific treatment. */ +#ifdef KAME + if (IN6_IS_ADDR_LINKLOCAL(&nexthop->v6_global) + && IN6_LINKLOCAL_IFINDEX(nexthop->v6_global)) { + SET_IN6_LINKLOCAL_IFINDEX(nexthop->v6_global, 0); + } + if (IN6_IS_ADDR_LINKLOCAL(&nexthop->v6_local) + && IN6_LINKLOCAL_IFINDEX(nexthop->v6_local)) { + SET_IN6_LINKLOCAL_IFINDEX(nexthop->v6_local, 0); + } +#endif /* KAME */ + + /* If we have identified the local interface, there is no error for now. + */ + return v6_ll_avail; +} + +static struct in6_addr * +bgp_path_info_to_ipv6_nexthop(struct bgp_path_info *path, ifindex_t *ifindex) +{ + struct in6_addr *nexthop = NULL; + + /* Only global address nexthop exists. */ + if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL + || path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_VPNV6_GLOBAL) { + nexthop = &path->attr->mp_nexthop_global; + if (IN6_IS_ADDR_LINKLOCAL(nexthop)) + *ifindex = path->attr->nh_ifindex; + } + + /* If both global and link-local address present. */ + if (path->attr->mp_nexthop_len == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL + || path->attr->mp_nexthop_len + == BGP_ATTR_NHLEN_VPNV6_GLOBAL_AND_LL) { + /* Check if route-map is set to prefer global over link-local */ + if (CHECK_FLAG(path->attr->nh_flags, + BGP_ATTR_NH_MP_PREFER_GLOBAL)) { + nexthop = &path->attr->mp_nexthop_global; + if (IN6_IS_ADDR_LINKLOCAL(nexthop)) + *ifindex = path->attr->nh_ifindex; + } else { + /* Workaround for Cisco's nexthop bug. */ + if (IN6_IS_ADDR_UNSPECIFIED( + &path->attr->mp_nexthop_global) + && path->peer->su_remote + && path->peer->su_remote->sa.sa_family + == AF_INET6) { + nexthop = + &path->peer->su_remote->sin6.sin6_addr; + if (IN6_IS_ADDR_LINKLOCAL(nexthop)) + *ifindex = path->peer->nexthop.ifp + ->ifindex; + } else { + nexthop = &path->attr->mp_nexthop_local; + if (IN6_IS_ADDR_LINKLOCAL(nexthop)) + *ifindex = path->attr->nh_lla_ifindex; + } + } + } + + return nexthop; +} + +static bool bgp_table_map_apply(struct route_map *map, const struct prefix *p, + struct bgp_path_info *path) +{ + route_map_result_t ret; + + ret = route_map_apply(map, p, path); + bgp_attr_flush(path->attr); + + if (ret != RMAP_DENYMATCH) + return true; + + if (bgp_debug_zebra(p)) { + if (p->family == AF_INET) { + zlog_debug( + "Zebra rmap deny: IPv4 route %pFX nexthop %pI4", + p, &path->attr->nexthop); + } + if (p->family == AF_INET6) { + ifindex_t ifindex; + struct in6_addr *nexthop; + + nexthop = bgp_path_info_to_ipv6_nexthop(path, &ifindex); + zlog_debug( + "Zebra rmap deny: IPv6 route %pFX nexthop %pI6", + p, nexthop); + } + } + return false; +} + +static struct event *bgp_tm_thread_connect; +static bool bgp_tm_status_connected; +static bool bgp_tm_chunk_obtained; +#define BGP_FLOWSPEC_TABLE_CHUNK 100000 +static uint32_t bgp_tm_min, bgp_tm_max, bgp_tm_chunk_size; +struct bgp *bgp_tm_bgp; + +static void bgp_zebra_tm_connect(struct event *t) +{ + struct zclient *zclient; + int delay = 10, ret = 0; + + zclient = EVENT_ARG(t); + if (bgp_tm_status_connected && zclient->sock > 0) + delay = 60; + else { + bgp_tm_status_connected = false; + ret = tm_table_manager_connect(zclient); + } + if (ret < 0) { + zlog_err("Error connecting to table manager!"); + bgp_tm_status_connected = false; + } else { + if (!bgp_tm_status_connected) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Connecting to table manager. Success"); + } + bgp_tm_status_connected = true; + if (!bgp_tm_chunk_obtained) { + if (bgp_zebra_get_table_range(zclient, bgp_tm_chunk_size, + &bgp_tm_min, + &bgp_tm_max) >= 0) { + bgp_tm_chunk_obtained = true; + /* parse non installed entries */ + bgp_zebra_announce_table(bgp_tm_bgp, AFI_IP, SAFI_FLOWSPEC); + } + } + } + event_add_timer(bm->master, bgp_zebra_tm_connect, zclient, delay, + &bgp_tm_thread_connect); +} + +bool bgp_zebra_tm_chunk_obtained(void) +{ + return bgp_tm_chunk_obtained; +} + +uint32_t bgp_zebra_tm_get_id(void) +{ + static int table_id; + + if (!bgp_tm_chunk_obtained) + return ++table_id; + return bgp_tm_min++; +} + +void bgp_zebra_init_tm_connect(struct bgp *bgp) +{ + int delay = 1; + + /* if already set, do nothing + */ + if (bgp_tm_thread_connect != NULL) + return; + bgp_tm_status_connected = false; + bgp_tm_chunk_obtained = false; + bgp_tm_min = bgp_tm_max = 0; + bgp_tm_chunk_size = BGP_FLOWSPEC_TABLE_CHUNK; + bgp_tm_bgp = bgp; + event_add_timer(bm->master, bgp_zebra_tm_connect, zclient_sync, delay, + &bgp_tm_thread_connect); +} + +int bgp_zebra_get_table_range(struct zclient *zc, uint32_t chunk_size, + uint32_t *start, uint32_t *end) +{ + int ret; + + if (!bgp_tm_status_connected) + return -1; + ret = tm_get_table_chunk(zc, chunk_size, start, end); + if (ret < 0) { + flog_err(EC_BGP_TABLE_CHUNK, + "BGP: Error getting table chunk %u", chunk_size); + return -1; + } + zlog_info("BGP: Table Manager returns range from chunk %u is [%u %u]", + chunk_size, *start, *end); + return 0; +} + +static bool update_ipv4nh_for_route_install(int nh_othervrf, struct bgp *nh_bgp, + struct in_addr *nexthop, + struct attr *attr, bool is_evpn, + struct zapi_nexthop *api_nh) +{ + api_nh->gate.ipv4 = *nexthop; + api_nh->vrf_id = nh_bgp->vrf_id; + + /* Need to set fields appropriately for EVPN routes imported into + * a VRF (which are programmed as onlink on l3-vni SVI) as well as + * connected routes leaked into a VRF. + */ + if (attr->nh_type == NEXTHOP_TYPE_BLACKHOLE) { + api_nh->type = attr->nh_type; + api_nh->bh_type = attr->bh_type; + } else if (is_evpn) { + /* + * If the nexthop is EVPN overlay index gateway IP, + * treat the nexthop as NEXTHOP_TYPE_IPV4 + * Else, mark the nexthop as onlink. + */ + if (attr->evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) + api_nh->type = NEXTHOP_TYPE_IPV4; + else { + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN); + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); + api_nh->ifindex = nh_bgp->l3vni_svi_ifindex; + } + } else if (nh_othervrf && api_nh->gate.ipv4.s_addr == INADDR_ANY) { + api_nh->type = NEXTHOP_TYPE_IFINDEX; + api_nh->ifindex = attr->nh_ifindex; + } else + api_nh->type = NEXTHOP_TYPE_IPV4; + + return true; +} + +static bool update_ipv6nh_for_route_install(int nh_othervrf, struct bgp *nh_bgp, + struct in6_addr *nexthop, + ifindex_t ifindex, + struct bgp_path_info *pi, + struct bgp_path_info *best_pi, + bool is_evpn, + struct zapi_nexthop *api_nh) +{ + struct attr *attr; + + attr = pi->attr; + api_nh->vrf_id = nh_bgp->vrf_id; + + if (attr->nh_type == NEXTHOP_TYPE_BLACKHOLE) { + api_nh->type = attr->nh_type; + api_nh->bh_type = attr->bh_type; + } else if (is_evpn) { + /* + * If the nexthop is EVPN overlay index gateway IP, + * treat the nexthop as NEXTHOP_TYPE_IPV4 + * Else, mark the nexthop as onlink. + */ + if (attr->evpn_overlay.type == OVERLAY_INDEX_GATEWAY_IP) + api_nh->type = NEXTHOP_TYPE_IPV6; + else { + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN); + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); + api_nh->ifindex = nh_bgp->l3vni_svi_ifindex; + } + } else if (nh_othervrf) { + if (IN6_IS_ADDR_UNSPECIFIED(nexthop)) { + api_nh->type = NEXTHOP_TYPE_IFINDEX; + api_nh->ifindex = attr->nh_ifindex; + } else if (IN6_IS_ADDR_LINKLOCAL(nexthop)) { + if (ifindex == 0) + return false; + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + api_nh->ifindex = ifindex; + } else { + api_nh->type = NEXTHOP_TYPE_IPV6; + api_nh->ifindex = 0; + } + } else { + if (IN6_IS_ADDR_LINKLOCAL(nexthop)) { + if (pi == best_pi + && attr->mp_nexthop_len + == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL) + if (pi->peer->nexthop.ifp) + ifindex = + pi->peer->nexthop.ifp->ifindex; + if (!ifindex) { + if (pi->peer->conf_if) + ifindex = pi->peer->ifp->ifindex; + else if (pi->peer->ifname) + ifindex = ifname2ifindex( + pi->peer->ifname, + pi->peer->bgp->vrf_id); + else if (pi->peer->nexthop.ifp) + ifindex = + pi->peer->nexthop.ifp->ifindex; + } + + if (ifindex == 0) + return false; + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + api_nh->ifindex = ifindex; + } else { + api_nh->type = NEXTHOP_TYPE_IPV6; + api_nh->ifindex = 0; + } + } + /* api_nh structure has union of gate and bh_type */ + if (nexthop && api_nh->type != NEXTHOP_TYPE_BLACKHOLE) + api_nh->gate.ipv6 = *nexthop; + + return true; +} + +static bool bgp_zebra_use_nhop_weighted(struct bgp *bgp, struct attr *attr, + uint64_t *nh_weight) +{ + /* zero link-bandwidth and link-bandwidth not present are treated + * as the same situation. + */ + if (!attr->link_bw) { + /* the only situations should be if we're either told + * to skip or use default weight. + */ + if (bgp->lb_handling == BGP_LINK_BW_SKIP_MISSING) + return false; + *nh_weight = BGP_ZEBRA_DEFAULT_NHOP_WEIGHT; + } else + *nh_weight = attr->link_bw; + + return true; +} + +static void bgp_zebra_announce_parse_nexthop( + struct bgp_path_info *info, const struct prefix *p, struct bgp *bgp, + struct zapi_route *api, unsigned int *valid_nh_count, afi_t afi, + safi_t safi, uint32_t *nhg_id, uint32_t *metric, route_tag_t *tag, + bool *allow_recursion) +{ + struct zapi_nexthop *api_nh; + int nh_family; + struct bgp_path_info *mpinfo; + struct bgp *bgp_orig; + struct attr local_attr; + struct bgp_path_info local_info; + struct bgp_path_info *mpinfo_cp = &local_info; + mpls_label_t *labels; + uint8_t num_labels = 0; + mpls_label_t nh_label; + int nh_othervrf = 0; + bool nh_updated = false; + bool do_wt_ecmp; + uint32_t ttl = 0; + uint32_t bos = 0; + uint32_t exp = 0; + + /* Determine if we're doing weighted ECMP or not */ + do_wt_ecmp = bgp_path_info_mpath_chkwtd(bgp, info); + + /* + * vrf leaking support (will have only one nexthop) + */ + if (info->extra && info->extra->vrfleak && + info->extra->vrfleak->bgp_orig) + nh_othervrf = 1; + + /* EVPN MAC-IP routes are installed with a L3 NHG id */ + if (nhg_id && bgp_evpn_path_es_use_nhg(bgp, info, nhg_id)) { + mpinfo = NULL; + zapi_route_set_nhg_id(api, nhg_id); + } else { + mpinfo = info; + } + + for (; mpinfo; mpinfo = bgp_path_info_mpath_next(mpinfo)) { + uint64_t nh_weight; + bool is_evpn; + bool is_parent_evpn; + + if (*valid_nh_count >= multipath_num) + break; + + *mpinfo_cp = *mpinfo; + nh_weight = 0; + + /* Get nexthop address-family */ + if (p->family == AF_INET && + !BGP_ATTR_MP_NEXTHOP_LEN_IP6(mpinfo_cp->attr)) + nh_family = AF_INET; + else if (p->family == AF_INET6 || + (p->family == AF_INET && + BGP_ATTR_MP_NEXTHOP_LEN_IP6(mpinfo_cp->attr))) + nh_family = AF_INET6; + else + continue; + + /* If processing for weighted ECMP, determine the next hop's + * weight. Based on user setting, we may skip the next hop + * in some situations. + */ + if (do_wt_ecmp) { + if (!bgp_zebra_use_nhop_weighted(bgp, mpinfo->attr, + &nh_weight)) + continue; + } + api_nh = &api->nexthops[*valid_nh_count]; + + api_nh->srte_color = bgp_attr_get_color(info->attr); + + if (bgp_debug_zebra(&api->prefix)) { + if (BGP_PATH_INFO_NUM_LABELS(mpinfo)) { + zlog_debug("%s: p=%pFX, bgp_is_valid_label: %d", + __func__, p, + bgp_is_valid_label( + &mpinfo->extra->labels + ->label[0])); + } else { + zlog_debug("%s: p=%pFX, no label", __func__, p); + } + } + + if (bgp->table_map[afi][safi].name) { + /* Copy info and attributes, so the route-map + apply doesn't modify the BGP route info. */ + local_attr = *mpinfo->attr; + mpinfo_cp->attr = &local_attr; + if (!bgp_table_map_apply(bgp->table_map[afi][safi].map, + p, mpinfo_cp)) + continue; + + /* metric/tag is only allowed to be + * overridden on 1st nexthop */ + if (mpinfo == info) { + if (metric) + *metric = mpinfo_cp->attr->med; + if (tag) + *tag = mpinfo_cp->attr->tag; + } + } + + BGP_ORIGINAL_UPDATE(bgp_orig, mpinfo, bgp); + + is_parent_evpn = is_route_parent_evpn(mpinfo); + + if (nh_family == AF_INET) { + nh_updated = update_ipv4nh_for_route_install( + nh_othervrf, bgp_orig, + &mpinfo_cp->attr->nexthop, mpinfo_cp->attr, + is_parent_evpn, api_nh); + } else { + ifindex_t ifindex = IFINDEX_INTERNAL; + struct in6_addr *nexthop; + + nexthop = bgp_path_info_to_ipv6_nexthop(mpinfo_cp, + &ifindex); + + if (!nexthop) + nh_updated = update_ipv4nh_for_route_install( + nh_othervrf, bgp_orig, + &mpinfo_cp->attr->nexthop, + mpinfo_cp->attr, is_parent_evpn, + api_nh); + else + nh_updated = update_ipv6nh_for_route_install( + nh_othervrf, bgp_orig, nexthop, ifindex, + mpinfo, info, is_parent_evpn, api_nh); + } + + is_evpn = !!CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN); + + /* Did we get proper nexthop info to update zebra? */ + if (!nh_updated) + continue; + + /* Allow recursion if it is a multipath group with both + * eBGP and iBGP paths. + */ + if (allow_recursion && !*allow_recursion && + CHECK_FLAG(bgp->flags, BGP_FLAG_PEERTYPE_MULTIPATH_RELAX) && + (mpinfo->peer->sort == BGP_PEER_IBGP || + mpinfo->peer->sort == BGP_PEER_CONFED)) + *allow_recursion = true; + + num_labels = BGP_PATH_INFO_NUM_LABELS(mpinfo); + labels = num_labels ? mpinfo->extra->labels->label : NULL; + + if (num_labels && (is_evpn || bgp_is_valid_label(&labels[0]))) { + enum lsp_types_t nh_label_type = ZEBRA_LSP_NONE; + + if (is_evpn) { + nh_label = *bgp_evpn_path_info_labels_get_l3vni( + labels, num_labels); + nh_label_type = ZEBRA_LSP_EVPN; + } else { + mpls_lse_decode(labels[0], &nh_label, &ttl, + &exp, &bos); + } + + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL); + api_nh->label_num = 1; + api_nh->label_type = nh_label_type; + api_nh->labels[0] = nh_label; + } + + if (is_evpn + && mpinfo->attr->evpn_overlay.type + != OVERLAY_INDEX_GATEWAY_IP) + memcpy(&api_nh->rmac, &(mpinfo->attr->rmac), + sizeof(struct ethaddr)); + + api_nh->weight = nh_weight; + + if (((mpinfo->attr->srv6_l3vpn && + !sid_zero_ipv6(&mpinfo->attr->srv6_l3vpn->sid)) || + (mpinfo->attr->srv6_vpn && + !sid_zero_ipv6(&mpinfo->attr->srv6_vpn->sid))) && + !is_evpn && bgp_is_valid_label(&labels[0])) { + struct in6_addr *sid_tmp = + mpinfo->attr->srv6_l3vpn + ? (&mpinfo->attr->srv6_l3vpn->sid) + : (&mpinfo->attr->srv6_vpn->sid); + + memcpy(&api_nh->seg6_segs[0], sid_tmp, + sizeof(api_nh->seg6_segs[0])); + + if (mpinfo->attr->srv6_l3vpn && + mpinfo->attr->srv6_l3vpn->transposition_len != 0) { + mpls_lse_decode(labels[0], &nh_label, &ttl, + &exp, &bos); + + if (nh_label < MPLS_LABEL_UNRESERVED_MIN) { + if (bgp_debug_zebra(&api->prefix)) + zlog_debug( + "skip invalid SRv6 routes: transposition scheme is used, but label is too small"); + continue; + } + + transpose_sid(&api_nh->seg6_segs[0], nh_label, + mpinfo->attr->srv6_l3vpn + ->transposition_offset, + mpinfo->attr->srv6_l3vpn + ->transposition_len); + } + + api_nh->seg_num = 1; + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); + } + + (*valid_nh_count)++; + } +} + +static void bgp_debug_zebra_nh(struct zapi_route *api) +{ + int i; + int nh_family; + char nh_buf[INET6_ADDRSTRLEN]; + char eth_buf[ETHER_ADDR_STRLEN + 7] = { '\0' }; + char buf1[ETHER_ADDR_STRLEN]; + char label_buf[20]; + char sid_buf[20]; + char segs_buf[256]; + struct zapi_nexthop *api_nh; + int count; + + count = api->nexthop_num; + for (i = 0; i < count; i++) { + api_nh = &api->nexthops[i]; + switch (api_nh->type) { + case NEXTHOP_TYPE_IFINDEX: + nh_buf[0] = '\0'; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + nh_family = AF_INET; + inet_ntop(nh_family, &api_nh->gate, nh_buf, + sizeof(nh_buf)); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + nh_family = AF_INET6; + inet_ntop(nh_family, &api_nh->gate, nh_buf, + sizeof(nh_buf)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + strlcpy(nh_buf, "blackhole", sizeof(nh_buf)); + break; + default: + /* Note: add new nexthop case */ + assert(0); + break; + } + + label_buf[0] = '\0'; + eth_buf[0] = '\0'; + segs_buf[0] = '\0'; + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) && + !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) + snprintf(label_buf, sizeof(label_buf), "label %u", + api_nh->labels[0]); + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6) && + !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { + inet_ntop(AF_INET6, &api_nh->seg6_segs[0], sid_buf, + sizeof(sid_buf)); + snprintf(segs_buf, sizeof(segs_buf), "segs %s", sid_buf); + } + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN) && + !is_zero_mac(&api_nh->rmac)) + snprintf(eth_buf, sizeof(eth_buf), " RMAC %s", + prefix_mac2str(&api_nh->rmac, buf1, + sizeof(buf1))); + zlog_debug(" nhop [%d]: %s if %u VRF %u wt %" PRIu64 + " %s %s %s", + i + 1, nh_buf, api_nh->ifindex, api_nh->vrf_id, + api_nh->weight, label_buf, segs_buf, eth_buf); + } +} + +static enum zclient_send_status +bgp_zebra_announce_actual(struct bgp_dest *dest, struct bgp_path_info *info, + struct bgp *bgp) +{ + struct bgp_path_info *bpi_ultimate; + struct zapi_route api = { 0 }; + unsigned int valid_nh_count = 0; + bool allow_recursion = false; + uint8_t distance; + struct peer *peer; + uint32_t metric; + route_tag_t tag; + bool is_add; + uint32_t nhg_id = 0; + struct bgp_table *table = bgp_dest_table(dest); + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (table->safi == SAFI_FLOWSPEC) { + bgp_pbr_update_entry(bgp, p, info, table->afi, table->safi, + true); + return ZCLIENT_SEND_SUCCESS; + } + + /* Make Zebra API structure. */ + api.vrf_id = bgp->vrf_id; + api.type = ZEBRA_ROUTE_BGP; + api.safi = table->safi; + api.prefix = *p; + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + + peer = info->peer; + + if (info->type == ZEBRA_ROUTE_BGP) { + bpi_ultimate = bgp_get_imported_bpi_ultimate(info); + peer = bpi_ultimate->peer; + } + + tag = info->attr->tag; + + if (peer->sort == BGP_PEER_IBGP || peer->sort == BGP_PEER_CONFED + || info->sub_type == BGP_ROUTE_AGGREGATE) { + SET_FLAG(api.flags, ZEBRA_FLAG_IBGP); + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + } + + if ((peer->sort == BGP_PEER_EBGP && peer->ttl != BGP_DEFAULT_TTL) + || CHECK_FLAG(peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK) + || CHECK_FLAG(bgp->flags, BGP_FLAG_DISABLE_NH_CONNECTED_CHK)) + + allow_recursion = true; + + if (info->attr->rmap_table_id) { + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + api.tableid = info->attr->rmap_table_id; + } + + if (info->attr->srte_color) + SET_FLAG(api.message, ZAPI_MESSAGE_SRTE); + + /* Metric is currently based on the best-path only */ + metric = info->attr->med; + + bgp_zebra_announce_parse_nexthop(info, p, bgp, &api, &valid_nh_count, + table->afi, table->safi, &nhg_id, + &metric, &tag, &allow_recursion); + + is_add = (valid_nh_count || nhg_id) ? true : false; + + if (is_add && CHECK_FLAG(bm->flags, BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA)) { + struct bgp_zebra_opaque bzo = {}; + const char *reason = + bgp_path_selection_reason2str(dest->reason); + + strlcpy(bzo.aspath, info->attr->aspath->str, + sizeof(bzo.aspath)); + + if (info->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES)) + strlcpy(bzo.community, + bgp_attr_get_community(info->attr)->str, + sizeof(bzo.community)); + + if (info->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LARGE_COMMUNITIES)) + strlcpy(bzo.lcommunity, + bgp_attr_get_lcommunity(info->attr)->str, + sizeof(bzo.lcommunity)); + + strlcpy(bzo.selection_reason, reason, + sizeof(bzo.selection_reason)); + + SET_FLAG(api.message, ZAPI_MESSAGE_OPAQUE); + api.opaque.length = MIN(sizeof(struct bgp_zebra_opaque), + ZAPI_MESSAGE_OPAQUE_LENGTH); + memcpy(api.opaque.data, &bzo, api.opaque.length); + } + + if (allow_recursion) + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + + /* + * When we create an aggregate route we must also + * install a Null0 route in the RIB, so overwrite + * what was written into api with a blackhole route + */ + if (info->sub_type == BGP_ROUTE_AGGREGATE) + zapi_route_set_blackhole(&api, BLACKHOLE_NULL); + else + api.nexthop_num = valid_nh_count; + + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = metric; + + if (tag) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = tag; + } + + distance = bgp_distance_apply(p, info, table->afi, table->safi, bgp); + if (distance) { + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + api.distance = distance; + } + + if (bgp_debug_zebra(p)) { + zlog_debug( + "Tx route %s VRF %u %pFX metric %u tag %" ROUTE_TAG_PRI + " count %d nhg %d", + is_add ? "add" : "delete", bgp->vrf_id, &api.prefix, + api.metric, api.tag, api.nexthop_num, nhg_id); + bgp_debug_zebra_nh(&api); + + zlog_debug("%s: %pFX: announcing to zebra (recursion %sset)", + __func__, p, (allow_recursion ? "" : "NOT ")); + } + return zclient_route_send(is_add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, + zclient, &api); +} + + +/* Announce all routes of a table to zebra */ +void bgp_zebra_announce_table(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + /* Don't try to install if we're not connected to Zebra or Zebra doesn't + * know of this instance. + */ + if (!bgp_install_info_to_zebra(bgp)) + return; + + table = bgp->rib[afi][safi]; + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && + (pi->type == ZEBRA_ROUTE_BGP + && (pi->sub_type == BGP_ROUTE_NORMAL + || pi->sub_type == BGP_ROUTE_IMPORTED))) + bgp_zebra_route_install(dest, pi, bgp, true, + NULL, false); +} + +/* Announce routes of any bgp subtype of a table to zebra */ +void bgp_zebra_announce_table_all_subtypes(struct bgp *bgp, afi_t afi, + safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + if (!bgp_install_info_to_zebra(bgp)) + return; + + table = bgp->rib[afi][safi]; + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) && + pi->type == ZEBRA_ROUTE_BGP) + bgp_zebra_route_install(dest, pi, bgp, true, + NULL, false); +} + +enum zclient_send_status bgp_zebra_withdraw_actual(struct bgp_dest *dest, + struct bgp_path_info *info, + struct bgp *bgp) +{ + struct zapi_route api; + struct peer *peer; + struct bgp_table *table = bgp_dest_table(dest); + const struct prefix *p = bgp_dest_get_prefix(dest); + + if (table->safi == SAFI_FLOWSPEC) { + peer = info->peer; + bgp_pbr_update_entry(peer->bgp, p, info, table->afi, + table->safi, false); + return ZCLIENT_SEND_SUCCESS; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = bgp->vrf_id; + api.type = ZEBRA_ROUTE_BGP; + api.safi = table->safi; + api.prefix = *p; + + if (info->attr->rmap_table_id) { + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + api.tableid = info->attr->rmap_table_id; + } + + if (bgp_debug_zebra(p)) + zlog_debug("Tx route delete VRF %u %pFX", bgp->vrf_id, + &api.prefix); + + return zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); +} + +/* + * Walk the new Fifo list one by one and invoke bgp_zebra_announce/withdraw + * to install/withdraw the routes to zebra. + * + * If status = ZCLIENT_SEND_SUCCESS (Buffer empt)y i.e. Zebra is free to + * receive more incoming data, then pick the next item on the list and + * continue processing. + * + * If status = ZCLIENT_SEND_BUFFERED (Buffer pending) i.e. Zebra is busy, + * break and bail out of the function because once at some point when zebra + * is free, a callback is triggered which inturn call this same function and + * continue processing items on list. + */ +#define ZEBRA_ANNOUNCEMENTS_LIMIT 1000 +static void bgp_handle_route_announcements_to_zebra(struct event *e) +{ + bool is_evpn = false; + uint32_t count = 0; + struct bgp_dest *dest = NULL; + struct bgp_table *table = NULL; + enum zclient_send_status status = ZCLIENT_SEND_SUCCESS; + bool install; + const struct prefix_evpn *evp = NULL; + + while (count < ZEBRA_ANNOUNCEMENTS_LIMIT) { + is_evpn = false; + + dest = zebra_announce_pop(&bm->zebra_announce_head); + + if (!dest) + break; + + table = bgp_dest_table(dest); + install = CHECK_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL); + if (table->afi == AFI_L2VPN && table->safi == SAFI_EVPN) { + is_evpn = true; + evp = (const struct prefix_evpn *)bgp_dest_get_prefix( + dest); + } + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("BGP %s route %pBD(%s) with dest %p and flags 0x%x to zebra", + install ? "announcing" : "withdrawing", dest, + table->bgp->name_pretty, dest, dest->flags); + + if (install) { + if (is_evpn) + status = + evpn_zebra_install(table->bgp, + dest->za_vpn, + (const struct prefix_evpn + *) + bgp_dest_get_prefix( + dest), + dest->za_bgp_pi); + else + status = bgp_zebra_announce_actual(dest, + dest->za_bgp_pi, + table->bgp); + UNSET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL); + } else { + if (is_evpn) + status = evpn_zebra_uninstall( + table->bgp, dest->za_vpn, + (const struct prefix_evpn *) + bgp_dest_get_prefix(dest), + dest->za_bgp_pi, false); + else + status = bgp_zebra_withdraw_actual(dest, + dest->za_bgp_pi, + table->bgp); + + UNSET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_DELETE); + } + + if (is_evpn && status == ZCLIENT_SEND_FAILURE) + flog_err(EC_BGP_EVPN_FAIL, + "%s (%u): Failed to %s EVPN %pFX %s route in VNI %u", + vrf_id_to_name(table->bgp->vrf_id), + table->bgp->vrf_id, + install ? "install" : "uninstall", evp, + evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE + ? "MACIP" + : "IMET", + dest->za_vpn->vni); + + bgp_path_info_unlock(dest->za_bgp_pi); + dest->za_bgp_pi = NULL; + dest->za_vpn = NULL; + bgp_dest_unlock_node(dest); + + if (status == ZCLIENT_SEND_BUFFERED) + break; + + count++; + } + + if (status != ZCLIENT_SEND_BUFFERED && + zebra_announce_count(&bm->zebra_announce_head)) + event_add_event(bm->master, + bgp_handle_route_announcements_to_zebra, NULL, + 0, &bm->t_bgp_zebra_route); +} + +/* + * Callback function invoked when zclient_flush_data() receives a BUFFER_EMPTY + * i.e. zebra is free to receive more incoming data. + */ +static void bgp_zebra_buffer_write_ready(void) +{ + bgp_handle_route_announcements_to_zebra(NULL); +} + +/* + * BGP is now keeping a list of dests with the dest having a pointer + * to the bgp_path_info that it will be working on. + * Here is the sequence of events that should happen: + * + * Current State New State Action + * ------------- --------- ------ + * ---- Install Place dest on list, save pi, mark + * as going to be installed + * ---- Withdrawal Place dest on list, save pi, mark + * as going to be deleted + * + * Install Install Leave dest on list, release old pi, + * save new pi, mark as going to be + * Installed + * Install Withdrawal Leave dest on list, release old pi, + * save new pi, mark as going to be + * withdrawan, remove install flag + * + * Withdrawal Install Leave dest on list, release old pi, + * save new pi, mark as going to be + * installed. + * Withdrawal Withdrawal Leave dest on list, release old pi, + * save new pi, mark as going to be + * withdrawn. + */ +void bgp_zebra_route_install(struct bgp_dest *dest, struct bgp_path_info *info, + struct bgp *bgp, bool install, struct bgpevpn *vpn, + bool is_sync) +{ + bool is_evpn = false; + struct bgp_table *table = NULL; + + table = bgp_dest_table(dest); + if (table && table->afi == AFI_L2VPN && table->safi == SAFI_EVPN) + is_evpn = true; + + /* + * BGP is installing this route and bgp has been configured + * to suppress announcements until the route has been installed + * let's set the fact that we expect this route to be installed + */ + if (install) { + if (BGP_SUPPRESS_FIB_ENABLED(bgp)) + SET_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING); + + if (bgp->main_zebra_update_hold && !is_evpn) + return; + } else { + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING); + } + + /* + * Don't try to install if we're not connected to Zebra or Zebra doesn't + * know of this instance. + */ + if (!bgp_install_info_to_zebra(bgp) && !is_evpn) + return; + + if (!CHECK_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL) && + !CHECK_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_DELETE)) { + zebra_announce_add_tail(&bm->zebra_announce_head, dest); + /* + * If neither flag is set and za_bgp_pi is not set then it is a bug + */ + assert(!dest->za_bgp_pi); + bgp_path_info_lock(info); + bgp_dest_lock_node(dest); + dest->za_bgp_pi = info; + } else if (CHECK_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL)) { + assert(dest->za_bgp_pi); + bgp_path_info_unlock(dest->za_bgp_pi); + bgp_path_info_lock(info); + dest->za_bgp_pi = info; + } else if (CHECK_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_DELETE)) { + assert(dest->za_bgp_pi); + bgp_path_info_unlock(dest->za_bgp_pi); + bgp_path_info_lock(info); + dest->za_bgp_pi = info; + } + + if (is_evpn) { + dest->za_vpn = vpn; + dest->za_is_sync = is_sync; + } + + if (install) { + UNSET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_DELETE); + SET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL); + } else { + UNSET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_INSTALL); + SET_FLAG(dest->flags, BGP_NODE_SCHEDULE_FOR_DELETE); + } + + event_add_event(bm->master, bgp_handle_route_announcements_to_zebra, + NULL, 0, &bm->t_bgp_zebra_route); +} + +/* Withdraw all entries in a BGP instances RIB table from Zebra */ +void bgp_zebra_withdraw_table_all_subtypes(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest; + struct bgp_table *table; + struct bgp_path_info *pi; + + if (!bgp_install_info_to_zebra(bgp)) + return; + + table = bgp->rib[afi][safi]; + if (!table) + return; + + for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED) + && (pi->type == ZEBRA_ROUTE_BGP)) + bgp_zebra_route_install(dest, pi, bgp, false, + NULL, false); + } + } +} + +struct bgp_redist *bgp_redist_lookup(struct bgp *bgp, afi_t afi, uint8_t type, + unsigned short instance) +{ + struct list *red_list; + struct listnode *node; + struct bgp_redist *red; + + red_list = bgp->redist[afi][type]; + if (!red_list) + return (NULL); + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + if (red->instance == instance) + return red; + + return NULL; +} + +struct bgp_redist *bgp_redist_add(struct bgp *bgp, afi_t afi, uint8_t type, + unsigned short instance) +{ + struct list *red_list; + struct bgp_redist *red; + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (red) + return red; + + if (!bgp->redist[afi][type]) + bgp->redist[afi][type] = list_new(); + + red_list = bgp->redist[afi][type]; + red = XCALLOC(MTYPE_BGP_REDIST, sizeof(struct bgp_redist)); + red->instance = instance; + + listnode_add(red_list, red); + + return red; +} + +static void bgp_redist_del(struct bgp *bgp, afi_t afi, uint8_t type, + unsigned short instance) +{ + struct bgp_redist *red; + + red = bgp_redist_lookup(bgp, afi, type, instance); + + if (red) { + listnode_delete(bgp->redist[afi][type], red); + XFREE(MTYPE_BGP_REDIST, red); + if (!bgp->redist[afi][type]->count) + list_delete(&bgp->redist[afi][type]); + } +} + +/* Other routes redistribution into BGP. */ +int bgp_redistribute_set(struct bgp *bgp, afi_t afi, int type, + unsigned short instance, bool changed) +{ + /* If redistribute options are changed call + * bgp_redistribute_unreg() to reset the option and withdraw + * the routes + */ + if (changed) + bgp_redistribute_unreg(bgp, afi, type, instance); + + /* Return if already redistribute flag is set. */ + if (instance) { + if (redist_check_instance(&zclient->mi_redist[afi][type], + instance)) + return CMD_WARNING; + + redist_add_instance(&zclient->mi_redist[afi][type], instance); + } else { + if (vrf_bitmap_check(&zclient->redist[afi][type], bgp->vrf_id)) + return CMD_WARNING; + +#ifdef ENABLE_BGP_VNC + if (EVPN_ENABLED(bgp) && type == ZEBRA_ROUTE_VNC_DIRECT) { + vnc_export_bgp_enable( + bgp, afi); /* only enables if mode bits cfg'd */ + } +#endif + + vrf_bitmap_set(&zclient->redist[afi][type], bgp->vrf_id); + } + + /* + * Don't try to register if we're not connected to Zebra or Zebra + * doesn't know of this instance. + * + * When we come up later well resend if needed. + */ + if (!bgp_install_info_to_zebra(bgp)) + return CMD_SUCCESS; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Tx redistribute add VRF %u afi %d %s %d", + bgp->vrf_id, afi, zebra_route_string(type), + instance); + + /* Send distribute add message to zebra. */ + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, + instance, bgp->vrf_id); + + return CMD_SUCCESS; +} + +int bgp_redistribute_resend(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + /* Don't try to send if we're not connected to Zebra or Zebra doesn't + * know of this instance. + */ + if (!bgp_install_info_to_zebra(bgp)) + return -1; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Tx redistribute del/add VRF %u afi %d %s %d", + bgp->vrf_id, afi, zebra_route_string(type), + instance); + + /* Send distribute add message to zebra. */ + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, type, + instance, bgp->vrf_id); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, + instance, bgp->vrf_id); + + return 0; +} + +/* Redistribute with route-map specification. */ +bool bgp_redistribute_rmap_set(struct bgp_redist *red, const char *name, + struct route_map *route_map) +{ + if (red->rmap.name && (strcmp(red->rmap.name, name) == 0)) + return false; + + XFREE(MTYPE_ROUTE_MAP_NAME, red->rmap.name); + /* Decrement the count for existing routemap and + * increment the count for new route map. + */ + route_map_counter_decrement(red->rmap.map); + red->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); + red->rmap.map = route_map; + route_map_counter_increment(red->rmap.map); + + return true; +} + +/* Redistribute with metric specification. */ +bool bgp_redistribute_metric_set(struct bgp *bgp, struct bgp_redist *red, + afi_t afi, int type, uint32_t metric) +{ + struct bgp_dest *dest; + struct bgp_path_info *pi; + + if (red->redist_metric_flag && red->redist_metric == metric) + return false; + + red->redist_metric_flag = 1; + red->redist_metric = metric; + + for (dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (pi->sub_type == BGP_ROUTE_REDISTRIBUTE + && pi->type == type + && pi->instance == red->instance) { + struct attr *old_attr; + struct attr new_attr; + + new_attr = *pi->attr; + new_attr.med = red->redist_metric; + old_attr = pi->attr; + pi->attr = bgp_attr_intern(&new_attr); + bgp_attr_unintern(&old_attr); + + bgp_path_info_set_flag(dest, pi, + BGP_PATH_ATTR_CHANGED); + bgp_process(bgp, dest, pi, afi, SAFI_UNICAST); + } + } + } + + return true; +} + +/* Unset redistribution. */ +int bgp_redistribute_unreg(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + struct bgp_redist *red; + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (!red) + return CMD_SUCCESS; + + /* Return if zebra connection is disabled. */ + if (instance) { + if (!redist_check_instance(&zclient->mi_redist[afi][type], + instance)) + return CMD_WARNING; + redist_del_instance(&zclient->mi_redist[afi][type], instance); + } else { + if (!vrf_bitmap_check(&zclient->redist[afi][type], bgp->vrf_id)) + return CMD_WARNING; + vrf_bitmap_unset(&zclient->redist[afi][type], bgp->vrf_id); + } + + if (bgp_install_info_to_zebra(bgp)) { + /* Send distribute delete message to zebra. */ + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Tx redistribute del VRF %u afi %d %s %d", + bgp->vrf_id, afi, zebra_route_string(type), + instance); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, + type, instance, bgp->vrf_id); + } + + /* Withdraw redistributed routes from current BGP's routing table. */ + bgp_redistribute_withdraw(bgp, afi, type, instance); + + return CMD_SUCCESS; +} + +/* Unset redistribution. */ +static void _bgp_redistribute_unset(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + struct bgp_redist *red; + +/* + * vnc and vpn->vrf checks must be before red check because + * they operate within bgpd irrespective of zebra connection + * status. red lookup fails if there is no zebra connection. + */ +#ifdef ENABLE_BGP_VNC + if (EVPN_ENABLED(bgp) && type == ZEBRA_ROUTE_VNC_DIRECT) { + vnc_export_bgp_disable(bgp, afi); + } +#endif + + red = bgp_redist_lookup(bgp, afi, type, instance); + if (!red) + return; + + bgp_redistribute_unreg(bgp, afi, type, instance); + + /* Unset route-map. */ + XFREE(MTYPE_ROUTE_MAP_NAME, red->rmap.name); + route_map_counter_decrement(red->rmap.map); + red->rmap.map = NULL; + + /* Unset metric. */ + red->redist_metric_flag = 0; + red->redist_metric = 0; + + bgp_redist_del(bgp, afi, type, instance); +} + +void bgp_redistribute_unset(struct bgp *bgp, afi_t afi, int type, + unsigned short instance) +{ + struct listnode *node, *nnode; + struct bgp_redist *red; + + if ((type != ZEBRA_ROUTE_TABLE && type != ZEBRA_ROUTE_TABLE_DIRECT) || + instance != 0) + return _bgp_redistribute_unset(bgp, afi, type, instance); + + /* walk over instance */ + if (!bgp->redist[afi][type]) + return; + + for (ALL_LIST_ELEMENTS(bgp->redist[afi][type], node, nnode, red)) + _bgp_redistribute_unset(bgp, afi, type, red->instance); +} + +void bgp_redistribute_redo(struct bgp *bgp) +{ + afi_t afi; + int i; + struct list *red_list; + struct listnode *node; + struct bgp_redist *red; + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + + red_list = bgp->redist[afi][i]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + bgp_redistribute_resend(bgp, afi, i, + red->instance); + } + } + } +} + +void bgp_zclient_reset(void) +{ + zclient_reset(zclient); +} + +/* Register this instance with Zebra. Invoked upon connect (for + * default instance) and when other VRFs are learnt (or created and + * already learnt). + */ +void bgp_zebra_instance_register(struct bgp *bgp) +{ + /* Don't try to register if we're not connected to Zebra */ + if (!zclient || zclient->sock < 0) + return; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Registering VRF %u", bgp->vrf_id); + + /* Register for router-id, interfaces, redistributed routes. */ + zclient_send_reg_requests(zclient, bgp->vrf_id); + + /* For EVPN instance, register to learn about VNIs, if appropriate. */ + if (bgp->advertise_all_vni) + bgp_zebra_advertise_all_vni(bgp, 1); + + bgp_nht_register_nexthops(bgp); +} + +/* Deregister this instance with Zebra. Invoked upon the instance + * being deleted (default or VRF) and it is already registered. + */ +void bgp_zebra_instance_deregister(struct bgp *bgp) +{ + /* Don't try to deregister if we're not connected to Zebra */ + if (zclient->sock < 0) + return; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Deregistering VRF %u", bgp->vrf_id); + + /* For EVPN instance, unregister learning about VNIs, if appropriate. */ + if (bgp->advertise_all_vni) + bgp_zebra_advertise_all_vni(bgp, 0); + + /* Deregister for router-id, interfaces, redistributed routes. */ + zclient_send_dereg_requests(zclient, bgp->vrf_id); +} + +void bgp_zebra_initiate_radv(struct bgp *bgp, struct peer *peer) +{ + uint32_t ra_interval = BGP_UNNUM_DEFAULT_RA_INTERVAL; + + /* Don't try to initiate if we're not connected to Zebra */ + if (zclient->sock < 0) + return; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%u: Initiating RA for peer %s", bgp->vrf_id, + peer->host); + + /* + * If unnumbered peer (peer->ifp) call thru zapi to start RAs. + * If we don't have an ifp pointer, call function to find the + * ifps for a numbered enhe peer to turn RAs on. + */ + peer->ifp ? zclient_send_interface_radv_req(zclient, bgp->vrf_id, + peer->ifp, 1, ra_interval) + : bgp_nht_reg_enhe_cap_intfs(peer); +} + +void bgp_zebra_terminate_radv(struct bgp *bgp, struct peer *peer) +{ + /* Don't try to terminate if we're not connected to Zebra */ + if (zclient->sock < 0) + return; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%u: Terminating RA for peer %s", bgp->vrf_id, + peer->host); + + /* + * If unnumbered peer (peer->ifp) call thru zapi to stop RAs. + * If we don't have an ifp pointer, call function to find the + * ifps for a numbered enhe peer to turn RAs off. + */ + peer->ifp ? zclient_send_interface_radv_req(zclient, bgp->vrf_id, + peer->ifp, 0, 0) + : bgp_nht_dereg_enhe_cap_intfs(peer); +} + +int bgp_zebra_advertise_subnet(struct bgp *bgp, int advertise, vni_t vni) +{ + struct stream *s = NULL; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, cannot advertise subnet", + __func__); + return 0; + } + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_ADVERTISE_SUBNET, bgp->vrf_id); + stream_putc(s, advertise); + stream_put3(s, vni); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +int bgp_zebra_advertise_svi_macip(struct bgp *bgp, int advertise, vni_t vni) +{ + struct stream *s = NULL; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) + return 0; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_ADVERTISE_SVI_MACIP, bgp->vrf_id); + stream_putc(s, advertise); + stream_putl(s, vni); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +int bgp_zebra_advertise_gw_macip(struct bgp *bgp, int advertise, vni_t vni) +{ + struct stream *s = NULL; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing gw_macip", + __func__); + return 0; + } + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_ADVERTISE_DEFAULT_GW, bgp->vrf_id); + stream_putc(s, advertise); + stream_putl(s, vni); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +int bgp_zebra_vxlan_flood_control(struct bgp *bgp, + enum vxlan_flood_control flood_ctrl) +{ + struct stream *s; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: No zebra instance to talk to, not installing all vni", + __func__); + return 0; + } + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_VXLAN_FLOOD_CONTROL, bgp->vrf_id); + stream_putc(s, flood_ctrl); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +int bgp_zebra_advertise_all_vni(struct bgp *bgp, int advertise) +{ + struct stream *s; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) + return 0; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_ADVERTISE_ALL_VNI, bgp->vrf_id); + stream_putc(s, advertise); + /* Also inform current BUM handling setting. This is really + * relevant only when 'advertise' is set. + */ + stream_putc(s, bgp->vxlan_flood_ctrl); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +int bgp_zebra_dup_addr_detection(struct bgp *bgp) +{ + struct stream *s; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return 0; + + /* Don't try to register if Zebra doesn't know of this instance. */ + if (!IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) + return 0; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("dup addr detect %s max_moves %u time %u freeze %s freeze_time %u", + bgp->evpn_info->dup_addr_detect ? + "enable" : "disable", + bgp->evpn_info->dad_max_moves, + bgp->evpn_info->dad_time, + bgp->evpn_info->dad_freeze ? + "enable" : "disable", + bgp->evpn_info->dad_freeze_time); + + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_DUPLICATE_ADDR_DETECTION, + bgp->vrf_id); + stream_putl(s, bgp->evpn_info->dup_addr_detect); + stream_putl(s, bgp->evpn_info->dad_time); + stream_putl(s, bgp->evpn_info->dad_max_moves); + stream_putl(s, bgp->evpn_info->dad_freeze); + stream_putl(s, bgp->evpn_info->dad_freeze_time); + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +static int rule_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t seqno, priority, unique; + enum zapi_rule_notify_owner note; + struct bgp_pbr_action *bgp_pbra; + struct bgp_pbr_rule *bgp_pbr = NULL; + char ifname[IFNAMSIZ + 1]; + + if (!zapi_rule_notify_decode(zclient->ibuf, &seqno, &priority, &unique, + ifname, ¬e)) + return -1; + + bgp_pbra = bgp_pbr_action_rule_lookup(vrf_id, unique); + if (!bgp_pbra) { + /* look in bgp pbr rule */ + bgp_pbr = bgp_pbr_rule_lookup(vrf_id, unique); + if (!bgp_pbr && note != ZAPI_RULE_REMOVED) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Fail to look BGP rule (%u)", + __func__, unique); + return 0; + } + } + + switch (note) { + case ZAPI_RULE_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received RULE_FAIL_INSTALL", __func__); + if (bgp_pbra) { + bgp_pbra->installed = false; + bgp_pbra->install_in_progress = false; + } else { + bgp_pbr->installed = false; + bgp_pbr->install_in_progress = false; + } + break; + case ZAPI_RULE_INSTALLED: + if (bgp_pbra) { + bgp_pbra->installed = true; + bgp_pbra->install_in_progress = false; + } else { + struct bgp_path_info *path; + struct bgp_path_info_extra *extra; + + bgp_pbr->installed = true; + bgp_pbr->install_in_progress = false; + bgp_pbr->action->refcnt++; + /* link bgp_info to bgp_pbr */ + path = (struct bgp_path_info *)bgp_pbr->path; + extra = bgp_path_info_extra_get(path); + if (!extra->flowspec) { + extra->flowspec = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_FS, + sizeof(struct bgp_path_info_extra_fs)); + extra->flowspec->bgp_fs_iprule = NULL; + extra->flowspec->bgp_fs_pbr = NULL; + } + listnode_add_force(&extra->flowspec->bgp_fs_iprule, bgp_pbr); + } + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received RULE_INSTALLED", __func__); + break; + case ZAPI_RULE_FAIL_REMOVE: + case ZAPI_RULE_REMOVED: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received RULE REMOVED", __func__); + break; + } + + return 0; +} + +static int ipset_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t unique; + enum zapi_ipset_notify_owner note; + struct bgp_pbr_match *bgp_pbim; + + if (!zapi_ipset_notify_decode(zclient->ibuf, + &unique, + ¬e)) + return -1; + + bgp_pbim = bgp_pbr_match_ipset_lookup(vrf_id, unique); + if (!bgp_pbim) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Fail to look BGP match ( %u, ID %u)", + __func__, note, unique); + return 0; + } + + switch (note) { + case ZAPI_IPSET_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET_FAIL_INSTALL", __func__); + bgp_pbim->installed = false; + bgp_pbim->install_in_progress = false; + break; + case ZAPI_IPSET_INSTALLED: + bgp_pbim->installed = true; + bgp_pbim->install_in_progress = false; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET_INSTALLED", __func__); + break; + case ZAPI_IPSET_FAIL_REMOVE: + case ZAPI_IPSET_REMOVED: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET REMOVED", __func__); + break; + } + + return 0; +} + +static int ipset_entry_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t unique; + char ipset_name[ZEBRA_IPSET_NAME_SIZE]; + enum zapi_ipset_entry_notify_owner note; + struct bgp_pbr_match_entry *bgp_pbime; + + if (!zapi_ipset_entry_notify_decode( + zclient->ibuf, + &unique, + ipset_name, + ¬e)) + return -1; + bgp_pbime = bgp_pbr_match_ipset_entry_lookup(vrf_id, + ipset_name, + unique); + if (!bgp_pbime) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: Fail to look BGP match entry (%u, ID %u)", + __func__, note, unique); + return 0; + } + + switch (note) { + case ZAPI_IPSET_ENTRY_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET_ENTRY_FAIL_INSTALL", + __func__); + bgp_pbime->installed = false; + bgp_pbime->install_in_progress = false; + break; + case ZAPI_IPSET_ENTRY_INSTALLED: + { + struct bgp_path_info *path; + struct bgp_path_info_extra *extra; + + bgp_pbime->installed = true; + bgp_pbime->install_in_progress = false; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET_ENTRY_INSTALLED", + __func__); + /* link bgp_path_info to bpme */ + path = (struct bgp_path_info *)bgp_pbime->path; + extra = bgp_path_info_extra_get(path); + if (!extra->flowspec) { + extra->flowspec = + XCALLOC(MTYPE_BGP_ROUTE_EXTRA_FS, + sizeof(struct bgp_path_info_extra_fs)); + extra->flowspec->bgp_fs_iprule = NULL; + extra->flowspec->bgp_fs_pbr = NULL; + } + listnode_add_force(&extra->flowspec->bgp_fs_pbr, bgp_pbime); + } + break; + case ZAPI_IPSET_ENTRY_FAIL_REMOVE: + case ZAPI_IPSET_ENTRY_REMOVED: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPSET_ENTRY_REMOVED", + __func__); + break; + } + return 0; +} + +static int iptable_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t unique; + enum zapi_iptable_notify_owner note; + struct bgp_pbr_match *bgpm; + + if (!zapi_iptable_notify_decode( + zclient->ibuf, + &unique, + ¬e)) + return -1; + bgpm = bgp_pbr_match_iptable_lookup(vrf_id, unique); + if (!bgpm) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Fail to look BGP iptable (%u %u)", + __func__, note, unique); + return 0; + } + switch (note) { + case ZAPI_IPTABLE_FAIL_INSTALL: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPTABLE_FAIL_INSTALL", + __func__); + bgpm->installed_in_iptable = false; + bgpm->install_iptable_in_progress = false; + break; + case ZAPI_IPTABLE_INSTALLED: + bgpm->installed_in_iptable = true; + bgpm->install_iptable_in_progress = false; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPTABLE_INSTALLED", __func__); + bgpm->action->refcnt++; + break; + case ZAPI_IPTABLE_FAIL_REMOVE: + case ZAPI_IPTABLE_REMOVED: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Received IPTABLE REMOVED", __func__); + break; + } + return 0; +} + +/* Process route notification messages from RIB */ +static int bgp_zebra_route_notify_owner(int command, struct zclient *zclient, + zebra_size_t length, vrf_id_t vrf_id) +{ + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table_id; + afi_t afi; + safi_t safi; + struct bgp_dest *dest; + struct bgp *bgp; + struct bgp_path_info *pi, *new_select; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e, + &afi, &safi)) { + zlog_err("%s : error in msg decode", __func__); + return -1; + } + + /* Get the bgp instance */ + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) { + flog_err(EC_BGP_INVALID_BGP_INSTANCE, + "%s : bgp instance not found vrf %d", __func__, + vrf_id); + return -1; + } + + /* Find the bgp route node */ + dest = bgp_safi_node_lookup(bgp->rib[afi][safi], safi, &p, + &bgp->vrf_prd); + if (!dest) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %pFX does not exist in the BGP table, nothing to do for %u", + __func__, &p, note); + return -1; + } + + switch (note) { + case ZAPI_ROUTE_INSTALLED: + new_select = NULL; + /* Clear the flags so that route can be processed */ + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING); + SET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route %pBD : INSTALLED", dest); + /* Find the best route */ + for (pi = dest->info; pi; pi = pi->next) { + /* Process aggregate route */ + bgp_aggregate_increment(bgp, &p, pi, afi, safi); + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + new_select = pi; + } + /* Advertise the route */ + if (new_select) + group_announce_route(bgp, afi, safi, dest, new_select); + else { + flog_err(EC_BGP_INVALID_ROUTE, + "selected route %pBD not found", dest); + + bgp_dest_unlock_node(dest); + return -1; + } + break; + case ZAPI_ROUTE_REMOVED: + /* Route deleted from dataplane, reset the installed flag + * so that route can be reinstalled when client sends + * route add later + */ + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route %pBD: Removed from Fib", dest); + break; + case ZAPI_ROUTE_FAIL_INSTALL: + new_select = NULL; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route: %pBD Failed to Install into Fib", + dest); + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING); + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + new_select = pi; + } + if (new_select) + group_announce_route(bgp, afi, safi, dest, new_select); + /* Error will be logged by zebra module */ + break; + case ZAPI_ROUTE_BETTER_ADMIN_WON: + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("route: %pBD removed due to better admin won", + dest); + new_select = NULL; + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALL_PENDING); + UNSET_FLAG(dest->flags, BGP_NODE_FIB_INSTALLED); + for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) { + bgp_aggregate_decrement(bgp, &p, pi, afi, safi); + if (CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)) + new_select = pi; + } + if (new_select) + group_announce_route(bgp, afi, safi, dest, new_select); + /* No action required */ + break; + case ZAPI_ROUTE_REMOVE_FAIL: + zlog_warn("%s: Route %pBD failure to remove", __func__, dest); + break; + } + + bgp_dest_unlock_node(dest); + return 0; +} + +/* this function is used to forge ip rule, + * - either for iptable/ipset using fwmark id + * - or for sample ip rule cmd + */ +static void bgp_encode_pbr_rule_action(struct stream *s, + struct bgp_pbr_action *pbra, + struct bgp_pbr_rule *pbr) +{ + uint8_t fam = AF_INET; + struct pbr_rule r; + + if (pbra->nh.type == NEXTHOP_TYPE_IPV6) + fam = AF_INET6; + + /* + * Convert to canonical form + */ + memset(&r, 0, sizeof(r)); + /* r.seq unused */ + if (pbr) + r.priority = pbr->priority; + + /* ruleno unused - priority change + * ruleno permits distinguishing various FS PBR entries + * - FS PBR entries based on ipset/iptables + * - FS PBR entries based on iprule + * the latter may contain default routing information injected by FS + */ + if (pbr) + r.unique = pbr->unique; + else + r.unique = pbra->unique; + + r.family = fam; + + /* filter */ + + if (pbr && pbr->flags & MATCH_IP_SRC_SET) { + SET_FLAG(r.filter.filter_bm, PBR_FILTER_SRC_IP); + r.filter.src_ip = pbr->src; + } else { + /* ??? */ + r.filter.src_ip.family = fam; + } + if (pbr && pbr->flags & MATCH_IP_DST_SET) { + SET_FLAG(r.filter.filter_bm, PBR_FILTER_DST_IP); + r.filter.dst_ip = pbr->dst; + } else { + /* ??? */ + r.filter.dst_ip.family = fam; + } + /* src_port, dst_port, pcp, dsfield not used */ + if (!pbr) { + SET_FLAG(r.filter.filter_bm, PBR_FILTER_FWMARK); + r.filter.fwmark = pbra->fwmark; + } + + SET_FLAG(r.action.flags, PBR_ACTION_TABLE); /* always valid */ + r.action.table = pbra->table_id; + + zapi_pbr_rule_encode(s, &r); +} + +static void bgp_encode_pbr_ipset_match(struct stream *s, + struct bgp_pbr_match *pbim) +{ + stream_putl(s, pbim->unique); + stream_putl(s, pbim->type); + stream_putc(s, pbim->family); + stream_put(s, pbim->ipset_name, + ZEBRA_IPSET_NAME_SIZE); +} + +static void bgp_encode_pbr_ipset_entry_match(struct stream *s, + struct bgp_pbr_match_entry *pbime) +{ + stream_putl(s, pbime->unique); + /* check that back pointer is not null */ + stream_put(s, pbime->backpointer->ipset_name, + ZEBRA_IPSET_NAME_SIZE); + + stream_putc(s, pbime->src.family); + stream_putc(s, pbime->src.prefixlen); + stream_put(s, &pbime->src.u.prefix, prefix_blen(&pbime->src)); + + stream_putc(s, pbime->dst.family); + stream_putc(s, pbime->dst.prefixlen); + stream_put(s, &pbime->dst.u.prefix, prefix_blen(&pbime->dst)); + + stream_putw(s, pbime->src_port_min); + stream_putw(s, pbime->src_port_max); + stream_putw(s, pbime->dst_port_min); + stream_putw(s, pbime->dst_port_max); + stream_putc(s, pbime->proto); +} + +static void bgp_encode_pbr_iptable_match(struct stream *s, + struct bgp_pbr_action *bpa, + struct bgp_pbr_match *pbm) +{ + stream_putl(s, pbm->unique2); + + stream_putl(s, pbm->type); + + stream_putl(s, pbm->flags); + + /* TODO: correlate with what is contained + * into bgp_pbr_action. + * currently only forward supported + */ + if (bpa->nh.type == NEXTHOP_TYPE_BLACKHOLE) + stream_putl(s, ZEBRA_IPTABLES_DROP); + else + stream_putl(s, ZEBRA_IPTABLES_FORWARD); + stream_putl(s, bpa->fwmark); + stream_put(s, pbm->ipset_name, + ZEBRA_IPSET_NAME_SIZE); + stream_putc(s, pbm->family); + stream_putw(s, pbm->pkt_len_min); + stream_putw(s, pbm->pkt_len_max); + stream_putw(s, pbm->tcp_flags); + stream_putw(s, pbm->tcp_mask_flags); + stream_putc(s, pbm->dscp_value); + stream_putc(s, pbm->fragment); + stream_putc(s, pbm->protocol); + stream_putw(s, pbm->flow_label); +} + +/* BGP has established connection with Zebra. */ +static void bgp_zebra_connected(struct zclient *zclient) +{ + struct bgp *bgp; + + zclient_num_connects++; /* increment even if not responding */ + + /* Send the client registration */ + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); + + /* At this point, we may or may not have BGP instances configured, but + * we're only interested in the default VRF (others wouldn't have learnt + * the VRF from Zebra yet.) + */ + bgp = bgp_get_default(); + if (!bgp) + return; + + bgp_zebra_instance_register(bgp); + + /* TODO - What if we have peers and networks configured, do we have to + * kick-start them? + */ + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, bgp->peer); +} + +static int bgp_zebra_process_local_es_add(ZAPI_CALLBACK_ARGS) +{ + esi_t esi; + struct bgp *bgp = NULL; + struct stream *s = NULL; + char buf[ESI_STR_LEN]; + struct in_addr originator_ip; + uint8_t active; + uint8_t bypass; + uint16_t df_pref; + + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + s = zclient->ibuf; + stream_get(&esi, s, sizeof(esi_t)); + originator_ip.s_addr = stream_get_ipv4(s); + active = stream_getc(s); + df_pref = stream_getw(s); + bypass = stream_getc(s); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Rx add ESI %s originator-ip %pI4 active %u df_pref %u %s", + esi_to_str(&esi, buf, sizeof(buf)), &originator_ip, + active, df_pref, bypass ? "bypass" : ""); + + frrtrace(5, frr_bgp, evpn_mh_local_es_add_zrecv, &esi, originator_ip, + active, bypass, df_pref); + + bgp_evpn_local_es_add(bgp, &esi, originator_ip, active, df_pref, + !!bypass); + + return 0; +} + +static int bgp_zebra_process_local_es_del(ZAPI_CALLBACK_ARGS) +{ + esi_t esi; + struct bgp *bgp = NULL; + struct stream *s = NULL; + char buf[ESI_STR_LEN]; + + memset(&esi, 0, sizeof(esi_t)); + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + s = zclient->ibuf; + stream_get(&esi, s, sizeof(esi_t)); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx del ESI %s", + esi_to_str(&esi, buf, sizeof(buf))); + + frrtrace(1, frr_bgp, evpn_mh_local_es_del_zrecv, &esi); + + bgp_evpn_local_es_del(bgp, &esi); + + return 0; +} + +static int bgp_zebra_process_local_es_evi(ZAPI_CALLBACK_ARGS) +{ + esi_t esi; + vni_t vni; + struct bgp *bgp; + struct stream *s; + char buf[ESI_STR_LEN]; + + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + s = zclient->ibuf; + stream_get(&esi, s, sizeof(esi_t)); + vni = stream_getl(s); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx %s ESI %s VNI %u", + (cmd == ZEBRA_VNI_ADD) ? "add" : "del", + esi_to_str(&esi, buf, sizeof(buf)), vni); + + if (cmd == ZEBRA_LOCAL_ES_EVI_ADD) { + frrtrace(2, frr_bgp, evpn_mh_local_es_evi_add_zrecv, &esi, vni); + + bgp_evpn_local_es_evi_add(bgp, &esi, vni); + } else { + frrtrace(2, frr_bgp, evpn_mh_local_es_evi_del_zrecv, &esi, vni); + + bgp_evpn_local_es_evi_del(bgp, &esi, vni); + } + + return 0; +} + +static int bgp_zebra_process_local_l3vni(ZAPI_CALLBACK_ARGS) +{ + int filter = 0; + vni_t l3vni = 0; + struct ethaddr svi_rmac, vrr_rmac = {.octet = {0} }; + struct in_addr originator_ip; + struct stream *s; + ifindex_t svi_ifindex; + bool is_anycast_mac = false; + + memset(&svi_rmac, 0, sizeof(svi_rmac)); + memset(&originator_ip, 0, sizeof(originator_ip)); + s = zclient->ibuf; + l3vni = stream_getl(s); + if (cmd == ZEBRA_L3VNI_ADD) { + stream_get(&svi_rmac, s, sizeof(struct ethaddr)); + originator_ip.s_addr = stream_get_ipv4(s); + stream_get(&filter, s, sizeof(int)); + svi_ifindex = stream_getl(s); + stream_get(&vrr_rmac, s, sizeof(struct ethaddr)); + is_anycast_mac = stream_getl(s); + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx L3VNI ADD VRF %s VNI %u Originator-IP %pI4 RMAC svi-mac %pEA vrr-mac %pEA filter %s svi-if %u", + vrf_id_to_name(vrf_id), l3vni, + &originator_ip, &svi_rmac, &vrr_rmac, + filter ? "prefix-routes-only" : "none", + svi_ifindex); + + frrtrace(8, frr_bgp, evpn_local_l3vni_add_zrecv, l3vni, vrf_id, + &svi_rmac, &vrr_rmac, filter, originator_ip, + svi_ifindex, is_anycast_mac); + + bgp_evpn_local_l3vni_add(l3vni, vrf_id, &svi_rmac, &vrr_rmac, + originator_ip, filter, svi_ifindex, + is_anycast_mac); + } else { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx L3VNI DEL VRF %s VNI %u", + vrf_id_to_name(vrf_id), l3vni); + + frrtrace(2, frr_bgp, evpn_local_l3vni_del_zrecv, l3vni, vrf_id); + + bgp_evpn_local_l3vni_del(l3vni, vrf_id); + } + + return 0; +} + +static int bgp_zebra_process_local_vni(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + vni_t vni; + struct bgp *bgp; + struct in_addr vtep_ip = {INADDR_ANY}; + vrf_id_t tenant_vrf_id = VRF_DEFAULT; + struct in_addr mcast_grp = {INADDR_ANY}; + ifindex_t svi_ifindex = 0; + + s = zclient->ibuf; + vni = stream_getl(s); + if (cmd == ZEBRA_VNI_ADD) { + vtep_ip.s_addr = stream_get_ipv4(s); + stream_get(&tenant_vrf_id, s, sizeof(vrf_id_t)); + mcast_grp.s_addr = stream_get_ipv4(s); + stream_get(&svi_ifindex, s, sizeof(ifindex_t)); + } + + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "Rx VNI %s VRF %s VNI %u tenant-vrf %s SVI ifindex %u", + (cmd == ZEBRA_VNI_ADD) ? "add" : "del", + vrf_id_to_name(vrf_id), vni, + vrf_id_to_name(tenant_vrf_id), svi_ifindex); + + if (cmd == ZEBRA_VNI_ADD) { + frrtrace(4, frr_bgp, evpn_local_vni_add_zrecv, vni, vtep_ip, + tenant_vrf_id, mcast_grp); + + return bgp_evpn_local_vni_add( + bgp, vni, + vtep_ip.s_addr != INADDR_ANY ? vtep_ip : bgp->router_id, + tenant_vrf_id, mcast_grp, svi_ifindex); + } else { + frrtrace(1, frr_bgp, evpn_local_vni_del_zrecv, vni); + + return bgp_evpn_local_vni_del(bgp, vni); + } +} + +static int bgp_zebra_process_local_macip(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + vni_t vni; + struct bgp *bgp; + struct ethaddr mac; + struct ipaddr ip; + int ipa_len; + uint8_t flags = 0; + uint32_t seqnum = 0; + int state = 0; + char buf2[ESI_STR_LEN]; + esi_t esi; + + memset(&ip, 0, sizeof(ip)); + s = zclient->ibuf; + vni = stream_getl(s); + stream_get(&mac.octet, s, ETH_ALEN); + ipa_len = stream_getl(s); + if (ipa_len != 0 && ipa_len != IPV4_MAX_BYTELEN + && ipa_len != IPV6_MAX_BYTELEN) { + flog_err(EC_BGP_MACIP_LEN, + "%u:Recv MACIP %s with invalid IP addr length %d", + vrf_id, (cmd == ZEBRA_MACIP_ADD) ? "Add" : "Del", + ipa_len); + return -1; + } + + if (ipa_len) { + ip.ipa_type = + (ipa_len == IPV4_MAX_BYTELEN) ? IPADDR_V4 : IPADDR_V6; + stream_get(&ip.ip.addr, s, ipa_len); + } + if (cmd == ZEBRA_MACIP_ADD) { + flags = stream_getc(s); + seqnum = stream_getl(s); + stream_get(&esi, s, sizeof(esi_t)); + } else { + state = stream_getl(s); + memset(&esi, 0, sizeof(esi_t)); + } + + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp) + return 0; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%u:Recv MACIP %s f 0x%x MAC %pEA IP %pIA VNI %u seq %u state %d ESI %s", + vrf_id, (cmd == ZEBRA_MACIP_ADD) ? "Add" : "Del", flags, + &mac, &ip, vni, seqnum, state, + esi_to_str(&esi, buf2, sizeof(buf2))); + + if (cmd == ZEBRA_MACIP_ADD) { + frrtrace(6, frr_bgp, evpn_local_macip_add_zrecv, vni, &mac, &ip, + flags, seqnum, &esi); + + return bgp_evpn_local_macip_add(bgp, vni, &mac, &ip, + flags, seqnum, &esi); + } else { + frrtrace(4, frr_bgp, evpn_local_macip_del_zrecv, vni, &mac, &ip, + state); + + return bgp_evpn_local_macip_del(bgp, vni, &mac, &ip, state); + } +} + +static int bgp_zebra_process_local_ip_prefix(ZAPI_CALLBACK_ARGS) +{ + struct stream *s = NULL; + struct bgp *bgp_vrf = NULL; + struct prefix p; + + memset(&p, 0, sizeof(p)); + s = zclient->ibuf; + stream_get(&p, s, sizeof(struct prefix)); + + bgp_vrf = bgp_lookup_by_vrf_id(vrf_id); + if (!bgp_vrf) + return 0; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Recv prefix %pFX %s on vrf %s", &p, + (cmd == ZEBRA_IP_PREFIX_ROUTE_ADD) ? "ADD" : "DEL", + vrf_id_to_name(vrf_id)); + + if (cmd == ZEBRA_IP_PREFIX_ROUTE_ADD) { + + if (p.family == AF_INET) + bgp_evpn_advertise_type5_route(bgp_vrf, &p, NULL, + AFI_IP, SAFI_UNICAST); + else + bgp_evpn_advertise_type5_route(bgp_vrf, &p, NULL, + AFI_IP6, SAFI_UNICAST); + + } else { + if (p.family == AF_INET) + bgp_evpn_withdraw_type5_route(bgp_vrf, &p, AFI_IP, + SAFI_UNICAST); + else + bgp_evpn_withdraw_type5_route(bgp_vrf, &p, AFI_IP6, + SAFI_UNICAST); + } + return 0; +} + +extern struct zebra_privs_t bgpd_privs; + +static int bgp_ifp_create(struct interface *ifp) +{ + struct bgp *bgp; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Rx Intf add VRF %u IF %s", ifp->vrf->vrf_id, + ifp->name); + + bgp = ifp->vrf->info; + if (!bgp) + return 0; + + bgp_mac_add_mac_entry(ifp); + + bgp_update_interface_nbrs(bgp, ifp, ifp); + hook_call(bgp_vrf_status_changed, bgp, ifp); + + if (bgp_get_default() && if_is_loopback(ifp)) { + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_update(bgp, AFI_IP6); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP); + vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP6); + vpn_leak_postchange_all(); + } + + return 0; +} + +static int bgp_zebra_process_srv6_locator_chunk(ZAPI_CALLBACK_ARGS) +{ + struct stream *s = NULL; + struct bgp *bgp = bgp_get_default(); + struct listnode *node; + struct srv6_locator_chunk *c; + struct srv6_locator_chunk *chunk = srv6_locator_chunk_alloc(); + + s = zclient->ibuf; + zapi_srv6_locator_chunk_decode(s, chunk); + + if (strcmp(bgp->srv6_locator_name, chunk->locator_name) != 0) { + zlog_err("%s: Locator name unmatch %s:%s", __func__, + bgp->srv6_locator_name, chunk->locator_name); + srv6_locator_chunk_free(&chunk); + return 0; + } + + for (ALL_LIST_ELEMENTS_RO(bgp->srv6_locator_chunks, node, c)) { + if (!prefix_cmp(&c->prefix, &chunk->prefix)) { + srv6_locator_chunk_free(&chunk); + return 0; + } + } + + listnode_add(bgp->srv6_locator_chunks, chunk); + vpn_leak_postchange_all(); + return 0; +} + +static int bgp_zebra_process_srv6_locator_add(ZAPI_CALLBACK_ARGS) +{ + struct srv6_locator loc = {}; + struct bgp *bgp = bgp_get_default(); + const char *loc_name = bgp->srv6_locator_name; + + if (!bgp || !bgp->srv6_enabled) + return 0; + + if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) + return -1; + + if (bgp_zebra_srv6_manager_get_locator_chunk(loc_name) < 0) + return -1; + + return 0; +} + +static int bgp_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) +{ + struct srv6_locator loc = {}; + struct bgp *bgp = bgp_get_default(); + struct listnode *node, *nnode; + struct srv6_locator_chunk *chunk, *tovpn_sid_locator; + struct bgp_srv6_function *func; + struct bgp *bgp_vrf; + struct in6_addr *tovpn_sid; + struct prefix_ipv6 tmp_prefi; + + if (!bgp) + return 0; + + if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) + return -1; + + // refresh chunks + for (ALL_LIST_ELEMENTS(bgp->srv6_locator_chunks, node, nnode, chunk)) + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&chunk->prefix)) { + listnode_delete(bgp->srv6_locator_chunks, chunk); + srv6_locator_chunk_free(&chunk); + } + + // refresh functions + for (ALL_LIST_ELEMENTS(bgp->srv6_functions, node, nnode, func)) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = 128; + tmp_prefi.prefix = func->sid; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) { + listnode_delete(bgp->srv6_functions, func); + srv6_function_free(func); + } + } + + // refresh tovpn_sid + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + // refresh vpnv4 tovpn_sid + tovpn_sid = bgp_vrf->vpn_policy[AFI_IP].tovpn_sid; + if (tovpn_sid) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = 128; + tmp_prefi.prefix = *tovpn_sid; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + XFREE(MTYPE_BGP_SRV6_SID, + bgp_vrf->vpn_policy[AFI_IP].tovpn_sid); + } + + // refresh vpnv6 tovpn_sid + tovpn_sid = bgp_vrf->vpn_policy[AFI_IP6].tovpn_sid; + if (tovpn_sid) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = 128; + tmp_prefi.prefix = *tovpn_sid; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + XFREE(MTYPE_BGP_SRV6_SID, + bgp_vrf->vpn_policy[AFI_IP6].tovpn_sid); + } + + /* refresh per-vrf tovpn_sid */ + tovpn_sid = bgp_vrf->tovpn_sid; + if (tovpn_sid) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = IPV6_MAX_BITLEN; + tmp_prefi.prefix = *tovpn_sid; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + XFREE(MTYPE_BGP_SRV6_SID, bgp_vrf->tovpn_sid); + } + } + + vpn_leak_postchange_all(); + + /* refresh tovpn_sid_locator */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp_vrf)) { + if (bgp_vrf->inst_type != BGP_INSTANCE_TYPE_VRF) + continue; + + /* refresh vpnv4 tovpn_sid_locator */ + tovpn_sid_locator = + bgp_vrf->vpn_policy[AFI_IP].tovpn_sid_locator; + if (tovpn_sid_locator) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = IPV6_MAX_BITLEN; + tmp_prefi.prefix = tovpn_sid_locator->prefix.prefix; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + srv6_locator_chunk_free( + &bgp_vrf->vpn_policy[AFI_IP] + .tovpn_sid_locator); + } + + /* refresh vpnv6 tovpn_sid_locator */ + tovpn_sid_locator = + bgp_vrf->vpn_policy[AFI_IP6].tovpn_sid_locator; + if (tovpn_sid_locator) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = IPV6_MAX_BITLEN; + tmp_prefi.prefix = tovpn_sid_locator->prefix.prefix; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + srv6_locator_chunk_free( + &bgp_vrf->vpn_policy[AFI_IP6] + .tovpn_sid_locator); + } + + /* refresh per-vrf tovpn_sid_locator */ + tovpn_sid_locator = bgp_vrf->tovpn_sid_locator; + if (tovpn_sid_locator) { + tmp_prefi.family = AF_INET6; + tmp_prefi.prefixlen = IPV6_MAX_BITLEN; + tmp_prefi.prefix = tovpn_sid_locator->prefix.prefix; + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&tmp_prefi)) + srv6_locator_chunk_free( + &bgp_vrf->tovpn_sid_locator); + } + } + + return 0; +} + +static zclient_handler *const bgp_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = bgp_router_id_update, + [ZEBRA_INTERFACE_ADDRESS_ADD] = bgp_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = bgp_interface_address_delete, + [ZEBRA_INTERFACE_NBR_ADDRESS_ADD] = bgp_interface_nbr_address_add, + [ZEBRA_INTERFACE_NBR_ADDRESS_DELETE] = bgp_interface_nbr_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = zebra_read_route, + [ZEBRA_FEC_UPDATE] = bgp_read_fec_update, + [ZEBRA_LOCAL_ES_ADD] = bgp_zebra_process_local_es_add, + [ZEBRA_LOCAL_ES_DEL] = bgp_zebra_process_local_es_del, + [ZEBRA_VNI_ADD] = bgp_zebra_process_local_vni, + [ZEBRA_LOCAL_ES_EVI_ADD] = bgp_zebra_process_local_es_evi, + [ZEBRA_LOCAL_ES_EVI_DEL] = bgp_zebra_process_local_es_evi, + [ZEBRA_VNI_DEL] = bgp_zebra_process_local_vni, + [ZEBRA_MACIP_ADD] = bgp_zebra_process_local_macip, + [ZEBRA_MACIP_DEL] = bgp_zebra_process_local_macip, + [ZEBRA_L3VNI_ADD] = bgp_zebra_process_local_l3vni, + [ZEBRA_L3VNI_DEL] = bgp_zebra_process_local_l3vni, + [ZEBRA_IP_PREFIX_ROUTE_ADD] = bgp_zebra_process_local_ip_prefix, + [ZEBRA_IP_PREFIX_ROUTE_DEL] = bgp_zebra_process_local_ip_prefix, + [ZEBRA_RULE_NOTIFY_OWNER] = rule_notify_owner, + [ZEBRA_IPSET_NOTIFY_OWNER] = ipset_notify_owner, + [ZEBRA_IPSET_ENTRY_NOTIFY_OWNER] = ipset_entry_notify_owner, + [ZEBRA_IPTABLE_NOTIFY_OWNER] = iptable_notify_owner, + [ZEBRA_ROUTE_NOTIFY_OWNER] = bgp_zebra_route_notify_owner, + [ZEBRA_SRV6_LOCATOR_ADD] = bgp_zebra_process_srv6_locator_add, + [ZEBRA_SRV6_LOCATOR_DELETE] = bgp_zebra_process_srv6_locator_delete, + [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = + bgp_zebra_process_srv6_locator_chunk, +}; + +static int bgp_if_new_hook(struct interface *ifp) +{ + struct bgp_interface *iifp; + + if (ifp->info) + return 0; + iifp = XCALLOC(MTYPE_BGP_IF_INFO, sizeof(struct bgp_interface)); + ifp->info = iifp; + + return 0; +} + +static int bgp_if_delete_hook(struct interface *ifp) +{ + XFREE(MTYPE_BGP_IF_INFO, ifp->info); + return 0; +} + +void bgp_if_init(void) +{ + /* Initialize Zebra interface data structure. */ + hook_register_prio(if_add, 0, bgp_if_new_hook); + hook_register_prio(if_del, 0, bgp_if_delete_hook); +} + +static void bgp_start_label_manager(struct event *start) +{ + bgp_zebra_label_manager_connect(); +} + +static bool bgp_zebra_label_manager_ready(void) +{ + return (zclient_sync->sock > 0); +} + +static bool bgp_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + if (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: failed connecting synchronous zclient!", + __func__); + return false; + } + /* make socket non-blocking */ + set_nonblocking(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: failed sending hello for synchronous zclient!", + __func__); + close(zclient_sync->sock); + zclient_sync->sock = -1; + return false; + } + + /* Connect to label manager */ + if (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: failed connecting to label manager!", __func__); + if (zclient_sync->sock > 0) { + close(zclient_sync->sock); + zclient_sync->sock = -1; + } + return false; + } + + /* tell label pool that zebra is connected */ + bgp_lp_event_zebra_up(); + + /* tell BGP L3VPN that label manager is available */ + if (bgp_get_default()) + vpn_leak_postchange_all(); + return true; +} + +static void bgp_zebra_capabilities(struct zclient_capabilities *cap) +{ + bm->v6_with_v4_nexthops = cap->v6_with_v4_nexthop; +} + +void bgp_zebra_init(struct event_loop *master, unsigned short instance) +{ + zclient_num_connects = 0; + + hook_register_prio(if_real, 0, bgp_ifp_create); + hook_register_prio(if_up, 0, bgp_ifp_up); + hook_register_prio(if_down, 0, bgp_ifp_down); + hook_register_prio(if_unreal, 0, bgp_ifp_destroy); + + /* Set default values. */ + zclient = zclient_new(master, &zclient_options_default, bgp_handlers, + array_size(bgp_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_BGP, 0, &bgpd_privs); + zclient->zebra_buffer_write_ready = bgp_zebra_buffer_write_ready; + zclient->zebra_connected = bgp_zebra_connected; + zclient->zebra_capabilities = bgp_zebra_capabilities; + zclient->nexthop_update = bgp_nexthop_update; + zclient->instance = instance; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_sync, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_BGP; + zclient_sync->instance = instance; + zclient_sync->session_id = 1; + zclient_sync->privs = &bgpd_privs; + + if (!bgp_zebra_label_manager_ready()) + event_add_timer(master, bgp_start_label_manager, NULL, 1, + &bm->t_bgp_start_label_manager); +} + +void bgp_zebra_destroy(void) +{ + if (zclient == NULL) + return; + zclient_stop(zclient); + zclient_free(zclient); + zclient = NULL; + + if (zclient_sync == NULL) + return; + zclient_stop(zclient_sync); + zclient_free(zclient_sync); + zclient_sync = NULL; +} + +int bgp_zebra_num_connects(void) +{ + return zclient_num_connects; +} + +void bgp_send_pbr_rule_action(struct bgp_pbr_action *pbra, + struct bgp_pbr_rule *pbr, + bool install) +{ + struct stream *s; + + if (pbra->install_in_progress && !pbr) + return; + if (pbr && pbr->install_in_progress) + return; + if (BGP_DEBUG(zebra, ZEBRA)) { + if (pbr) + zlog_debug("%s: table %d (ip rule) %d", __func__, + pbra->table_id, install); + else + zlog_debug("%s: table %d fwmark %d %d", __func__, + pbra->table_id, pbra->fwmark, install); + } + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_RULE_ADD : ZEBRA_RULE_DELETE, + VRF_DEFAULT); + + bgp_encode_pbr_rule_action(s, pbra, pbr); + + if ((zclient_send_message(zclient) != ZCLIENT_SEND_FAILURE) + && install) { + if (!pbr) + pbra->install_in_progress = true; + else + pbr->install_in_progress = true; + } +} + +void bgp_send_pbr_ipset_match(struct bgp_pbr_match *pbrim, bool install) +{ + struct stream *s; + + if (pbrim->install_in_progress) + return; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: name %s type %d %d, ID %u", __func__, + pbrim->ipset_name, pbrim->type, install, + pbrim->unique); + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_IPSET_CREATE : + ZEBRA_IPSET_DESTROY, + VRF_DEFAULT); + + stream_putl(s, 1); /* send one pbr action */ + + bgp_encode_pbr_ipset_match(s, pbrim); + + stream_putw_at(s, 0, stream_get_endp(s)); + if ((zclient_send_message(zclient) != ZCLIENT_SEND_FAILURE) && install) + pbrim->install_in_progress = true; +} + +void bgp_send_pbr_ipset_entry_match(struct bgp_pbr_match_entry *pbrime, + bool install) +{ + struct stream *s; + + if (pbrime->install_in_progress) + return; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: name %s %d %d, ID %u", __func__, + pbrime->backpointer->ipset_name, pbrime->unique, + install, pbrime->unique); + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_IPSET_ENTRY_ADD : + ZEBRA_IPSET_ENTRY_DELETE, + VRF_DEFAULT); + + stream_putl(s, 1); /* send one pbr action */ + + bgp_encode_pbr_ipset_entry_match(s, pbrime); + + stream_putw_at(s, 0, stream_get_endp(s)); + if ((zclient_send_message(zclient) != ZCLIENT_SEND_FAILURE) && install) + pbrime->install_in_progress = true; +} + +static void bgp_encode_pbr_interface_list(struct bgp *bgp, struct stream *s, + uint8_t family) +{ + struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; + struct bgp_pbr_interface_head *head; + struct bgp_pbr_interface *pbr_if; + struct interface *ifp; + + if (!bgp_pbr_cfg) + return; + if (family == AF_INET) + head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); + else + head = &(bgp_pbr_cfg->ifaces_by_name_ipv6); + RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { + ifp = if_lookup_by_name(pbr_if->name, bgp->vrf_id); + if (ifp) + stream_putl(s, ifp->ifindex); + } +} + +static int bgp_pbr_get_ifnumber(struct bgp *bgp, uint8_t family) +{ + struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg; + struct bgp_pbr_interface_head *head; + struct bgp_pbr_interface *pbr_if; + int cnt = 0; + + if (!bgp_pbr_cfg) + return 0; + if (family == AF_INET) + head = &(bgp_pbr_cfg->ifaces_by_name_ipv4); + else + head = &(bgp_pbr_cfg->ifaces_by_name_ipv6); + RB_FOREACH (pbr_if, bgp_pbr_interface_head, head) { + if (if_lookup_by_name(pbr_if->name, bgp->vrf_id)) + cnt++; + } + return cnt; +} + +void bgp_send_pbr_iptable(struct bgp_pbr_action *pba, + struct bgp_pbr_match *pbm, + bool install) +{ + struct stream *s; + int ret = 0; + int nb_interface; + + if (pbm->install_iptable_in_progress) + return; + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: name %s type %d mark %d %d, ID %u", __func__, + pbm->ipset_name, pbm->type, pba->fwmark, install, + pbm->unique2); + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_IPTABLE_ADD : + ZEBRA_IPTABLE_DELETE, + VRF_DEFAULT); + + bgp_encode_pbr_iptable_match(s, pba, pbm); + nb_interface = bgp_pbr_get_ifnumber(pba->bgp, pbm->family); + stream_putl(s, nb_interface); + if (nb_interface) + bgp_encode_pbr_interface_list(pba->bgp, s, pbm->family); + stream_putw_at(s, 0, stream_get_endp(s)); + ret = zclient_send_message(zclient); + if (install) { + if (ret != ZCLIENT_SEND_FAILURE) + pba->refcnt++; + else + pbm->install_iptable_in_progress = true; + } +} + +/* inject in table a default route to: + * - if nexthop IP is present : to this nexthop + * - if vrf is different from local : to the matching VRF + */ +void bgp_zebra_announce_default(struct bgp *bgp, struct nexthop *nh, + afi_t afi, uint32_t table_id, bool announce) +{ + struct zapi_nexthop *api_nh; + struct zapi_route api; + struct prefix p; + + if (!nh || (nh->type != NEXTHOP_TYPE_IPV4 + && nh->type != NEXTHOP_TYPE_IPV6) + || nh->vrf_id == VRF_UNKNOWN) + return; + + /* in vrf-lite, no default route has to be announced + * the table id of vrf is directly used to divert traffic + */ + if (!vrf_is_backend_netns() && bgp->vrf_id != nh->vrf_id) + return; + + memset(&p, 0, sizeof(p)); + if (afi != AFI_IP && afi != AFI_IP6) + return; + p.family = afi2family(afi); + memset(&api, 0, sizeof(api)); + api.vrf_id = bgp->vrf_id; + api.type = ZEBRA_ROUTE_BGP; + api.safi = SAFI_UNICAST; + api.prefix = p; + api.tableid = table_id; + api.nexthop_num = 1; + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api_nh = &api.nexthops[0]; + + api.distance = ZEBRA_EBGP_DISTANCE_DEFAULT; + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + + api_nh->vrf_id = nh->vrf_id; + + if (BGP_DEBUG(zebra, ZEBRA)) { + struct vrf *vrf; + + vrf = vrf_lookup_by_id(nh->vrf_id); + zlog_debug("%s: %s default route to %pNHvv(%s) table %d", + bgp->name_pretty, announce ? "adding" : "withdrawing", + nh, VRF_LOGNAME(vrf), table_id); + } + + /* redirect IP */ + if (afi == AFI_IP && nh->gate.ipv4.s_addr != INADDR_ANY) { + api_nh->gate.ipv4 = nh->gate.ipv4; + api_nh->type = NEXTHOP_TYPE_IPV4; + } else if (afi == AFI_IP6 && memcmp(&nh->gate.ipv6, &in6addr_any, + sizeof(struct in6_addr))) { + memcpy(&api_nh->gate.ipv6, &nh->gate.ipv6, + sizeof(struct in6_addr)); + api_nh->type = NEXTHOP_TYPE_IPV6; + } else if (nh->vrf_id != bgp->vrf_id) { + struct vrf *vrf; + struct interface *ifp; + + vrf = vrf_lookup_by_id(nh->vrf_id); + if (!vrf) + return; + /* create default route with interface + * with nexthop-vrf + */ + ifp = if_lookup_by_name_vrf(vrf->name, vrf); + if (!ifp) + return; + api_nh->type = NEXTHOP_TYPE_IFINDEX; + api_nh->ifindex = ifp->ifindex; + } + + zclient_route_send(announce ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, + zclient, &api); +} + +/* Send capabilities to RIB */ +int bgp_zebra_send_capabilities(struct bgp *bgp, bool disable) +{ + struct zapi_cap api; + int ret = BGP_GR_SUCCESS; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Sending %sable for %s", __func__, + disable ? "dis" : "en", bgp->name_pretty); + + if (zclient == NULL) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s zclient invalid", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + /* Check if the client is connected */ + if ((zclient->sock < 0) || (zclient->t_connect)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s client not connected", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + /* Check if capability is already sent. If the flag force is set + * send the capability since this can be initial bgp configuration + */ + memset(&api, 0, sizeof(api)); + if (disable) { + api.cap = ZEBRA_CLIENT_GR_DISABLE; + api.vrf_id = bgp->vrf_id; + } else { + api.cap = ZEBRA_CLIENT_GR_CAPABILITIES; + api.stale_removal_time = bgp->rib_stale_time; + api.vrf_id = bgp->vrf_id; + } + + if (zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, &api) + == ZCLIENT_SEND_FAILURE) { + zlog_err("%s: %s error sending capability", __func__, + bgp->name_pretty); + ret = BGP_GR_FAILURE; + } else { + if (disable) + bgp->present_zebra_gr_state = ZEBRA_GR_DISABLE; + else + bgp->present_zebra_gr_state = ZEBRA_GR_ENABLE; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s send capabilty success", __func__, + bgp->name_pretty); + ret = BGP_GR_SUCCESS; + } + return ret; +} + +/* Send route update pesding or completed status to RIB for the + * specific AFI, SAFI + */ +int bgp_zebra_update(struct bgp *bgp, afi_t afi, safi_t safi, + enum zserv_client_capabilities type) +{ + struct zapi_cap api = {0}; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s afi: %u safi: %u Command %s", __func__, + bgp->name_pretty, afi, safi, + zserv_gr_client_cap_string(type)); + + if (zclient == NULL) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s zclient == NULL, invalid", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + /* Check if the client is connected */ + if ((zclient->sock < 0) || (zclient->t_connect)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s client not connected", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + api.afi = afi; + api.safi = safi; + api.vrf_id = bgp->vrf_id; + api.cap = type; + + if (zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, &api) + == ZCLIENT_SEND_FAILURE) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s error sending capability", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + return BGP_GR_SUCCESS; +} + + +/* Send RIB stale timer update */ +int bgp_zebra_stale_timer_update(struct bgp *bgp) +{ + struct zapi_cap api; + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s Timer Update to %u", __func__, + bgp->name_pretty, bgp->rib_stale_time); + + if (zclient == NULL) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("zclient invalid"); + return BGP_GR_FAILURE; + } + + /* Check if the client is connected */ + if ((zclient->sock < 0) || (zclient->t_connect)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s client not connected", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + memset(&api, 0, sizeof(api)); + api.cap = ZEBRA_CLIENT_RIB_STALE_TIME; + api.stale_removal_time = bgp->rib_stale_time; + api.vrf_id = bgp->vrf_id; + if (zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, &api) + == ZCLIENT_SEND_FAILURE) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: %s error sending capability", __func__, + bgp->name_pretty); + return BGP_GR_FAILURE; + } + + return BGP_GR_SUCCESS; +} + +int bgp_zebra_srv6_manager_get_locator_chunk(const char *name) +{ + return srv6_manager_get_locator_chunk(zclient, name); +} + +int bgp_zebra_srv6_manager_release_locator_chunk(const char *name) +{ + return srv6_manager_release_locator_chunk(zclient, name); +} + +void bgp_zebra_send_nexthop_label(int cmd, mpls_label_t label, + ifindex_t ifindex, vrf_id_t vrf_id, + enum lsp_types_t ltype, struct prefix *p, + uint8_t num_labels, mpls_label_t out_labels[]) +{ + struct zapi_labels zl = {}; + struct zapi_nexthop *znh; + int i = 0; + + zl.type = ltype; + zl.local_label = label; + zl.nexthop_num = 1; + znh = &zl.nexthops[0]; + if (p->family == AF_INET) + IPV4_ADDR_COPY(&znh->gate.ipv4, &p->u.prefix4); + else + IPV6_ADDR_COPY(&znh->gate.ipv6, &p->u.prefix6); + if (ifindex == IFINDEX_INTERNAL) + znh->type = (p->family == AF_INET) ? NEXTHOP_TYPE_IPV4 + : NEXTHOP_TYPE_IPV6; + else + znh->type = (p->family == AF_INET) ? NEXTHOP_TYPE_IPV4_IFINDEX + : NEXTHOP_TYPE_IPV6_IFINDEX; + znh->ifindex = ifindex; + znh->vrf_id = vrf_id; + if (num_labels == 0) + znh->label_num = 0; + else { + if (num_labels > MPLS_MAX_LABELS) + znh->label_num = MPLS_MAX_LABELS; + else + znh->label_num = num_labels; + for (i = 0; i < znh->label_num; i++) + znh->labels[i] = out_labels[i]; + } + /* vrf_id is DEFAULT_VRF */ + zebra_send_mpls_labels(zclient, cmd, &zl); +} + +bool bgp_zebra_request_label_range(uint32_t base, uint32_t chunk_size, + bool label_auto) +{ + int ret; + uint32_t start, end; + + if (!zclient_sync || !bgp_zebra_label_manager_ready()) + return false; + + ret = lm_get_label_chunk(zclient_sync, 0, base, chunk_size, &start, + &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return false; + } + + if (start > end || start < MPLS_LABEL_UNRESERVED_MIN || + end > MPLS_LABEL_UNRESERVED_MAX) { + flog_err(EC_BGP_LM_ERROR, "%s: Invalid Label chunk: %u - %u", + __func__, start, end); + return false; + } + + if (label_auto) + /* label automatic is serviced by the bgp label pool + * manager, which allocates label chunks in + * pre-pools, and which needs to be notified about + * new chunks availability + */ + bgp_lp_event_chunk(start, end); + + return true; +} + +void bgp_zebra_release_label_range(uint32_t start, uint32_t end) +{ + int ret; + + if (!zclient_sync || !bgp_zebra_label_manager_ready()) + return; + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) + zlog_warn("%s: error releasing label range!", __func__); +} diff --git a/bgpd/bgp_zebra.h b/bgpd/bgp_zebra.h new file mode 100644 index 0000000..55a4185 --- /dev/null +++ b/bgpd/bgp_zebra.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* zebra connection and redistribute fucntions. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGP_ZEBRA_H +#define _QUAGGA_BGP_ZEBRA_H + +#include "vxlan.h" + +/* Macro to update bgp_original based on bpg_path_info */ +#define BGP_ORIGINAL_UPDATE(_bgp_orig, _mpinfo, _bgp) \ + ((_mpinfo->extra && _mpinfo->extra->vrfleak && \ + _mpinfo->extra->vrfleak->bgp_orig && \ + _mpinfo->sub_type == BGP_ROUTE_IMPORTED) \ + ? (_bgp_orig = _mpinfo->extra->vrfleak->bgp_orig) \ + : (_bgp_orig = _bgp)) + +/* Default weight for next hop, if doing weighted ECMP. */ +#define BGP_ZEBRA_DEFAULT_NHOP_WEIGHT 1 + +extern void bgp_zebra_init(struct event_loop *master, unsigned short instance); +extern void bgp_if_init(void); +extern void bgp_zebra_init_tm_connect(struct bgp *bgp); +extern uint32_t bgp_zebra_tm_get_id(void); +extern bool bgp_zebra_tm_chunk_obtained(void); +extern void bgp_zebra_destroy(void); +extern int bgp_zebra_get_table_range(struct zclient *zc, uint32_t chunk_size, + uint32_t *start, uint32_t *end); +extern int bgp_if_update_all(void); +extern void bgp_zebra_route_install(struct bgp_dest *dest, + struct bgp_path_info *path, struct bgp *bgp, + bool install, struct bgpevpn *vpn, + bool is_sync); +extern void bgp_zebra_announce_table(struct bgp *bgp, afi_t afi, safi_t safi); + +/* Announce routes of any bgp subtype of a table to zebra */ +extern void bgp_zebra_announce_table_all_subtypes(struct bgp *bgp, afi_t afi, + safi_t safi); + +/* Withdraw all entries of any subtype in a BGP instances RIB table from Zebra */ +extern void bgp_zebra_withdraw_table_all_subtypes(struct bgp *bgp, afi_t afi, + safi_t safi); + +extern void bgp_zebra_initiate_radv(struct bgp *bgp, struct peer *peer); +extern void bgp_zebra_terminate_radv(struct bgp *bgp, struct peer *peer); + +extern void bgp_zebra_instance_register(struct bgp *bgp); +extern void bgp_zebra_instance_deregister(struct bgp *bgp); + +extern void bgp_redistribute_redo(struct bgp *bgp); +extern struct bgp_redist *bgp_redist_lookup(struct bgp *bgp, afi_t afi, + uint8_t type, + unsigned short instance); +extern struct bgp_redist *bgp_redist_add(struct bgp *bgp, afi_t afi, + uint8_t type, unsigned short instance); +extern int bgp_redistribute_set(struct bgp *bgp, afi_t afi, int type, + unsigned short instance, bool changed); +extern int bgp_redistribute_resend(struct bgp *bgp, afi_t afi, int type, + unsigned short instance); +extern bool bgp_redistribute_rmap_set(struct bgp_redist *red, const char *name, + struct route_map *route_map); +extern bool bgp_redistribute_metric_set(struct bgp *bgp, struct bgp_redist *red, + afi_t afi, int type, uint32_t metric); +extern void bgp_redistribute_unset(struct bgp *bgp, afi_t afi, int type, + unsigned short instance); +extern int bgp_redistribute_unreg(struct bgp *bgp, afi_t afi, int type, + unsigned short instance); + +extern struct interface *if_lookup_by_ipv4(struct in_addr *addr, + vrf_id_t vrf_id); +extern struct interface *if_lookup_by_ipv4_exact(struct in_addr *addr, + vrf_id_t vrf_id); +extern struct interface *if_lookup_by_ipv6(struct in6_addr *addr, + ifindex_t ifindex, vrf_id_t vrf_id); +extern struct interface *if_lookup_by_ipv6_exact(struct in6_addr *addr, + ifindex_t ifindex, + vrf_id_t vrf_id); +extern int bgp_zebra_advertise_subnet(struct bgp *bgp, int advertise, + vni_t vni); +extern int bgp_zebra_advertise_gw_macip(struct bgp *bgp, int advertise, + vni_t vni); +extern int bgp_zebra_advertise_svi_macip(struct bgp *bgp, int advertise, + vni_t vni); +extern int bgp_zebra_advertise_all_vni(struct bgp *bgp, int advertise); +extern int bgp_zebra_dup_addr_detection(struct bgp *bgp); +extern int bgp_zebra_vxlan_flood_control(struct bgp *bgp, + enum vxlan_flood_control flood_ctrl); + +extern int bgp_zebra_num_connects(void); + +extern bool bgp_zebra_nexthop_set(union sockunion *local, + union sockunion *remote, + struct bgp_nexthop *nexthop, + struct peer *peer); +struct bgp_pbr_action; +struct bgp_pbr_match; +struct bgp_pbr_rule; +struct bgp_pbr_match_entry; + +extern void bgp_send_pbr_rule_action(struct bgp_pbr_action *pbra, + struct bgp_pbr_rule *pbr, + bool install); +extern void bgp_send_pbr_ipset_match(struct bgp_pbr_match *pbrim, + bool install); +extern void bgp_send_pbr_ipset_entry_match(struct bgp_pbr_match_entry *pbrime, + bool install); +extern void bgp_send_pbr_iptable(struct bgp_pbr_action *pba, + struct bgp_pbr_match *pbm, + bool install); + +extern void bgp_zebra_announce_default(struct bgp *bgp, struct nexthop *nh, + afi_t afi, uint32_t table_id, bool announce); +extern int bgp_zebra_send_capabilities(struct bgp *bgp, bool disable); +extern int bgp_zebra_update(struct bgp *bgp, afi_t afi, safi_t safi, + enum zserv_client_capabilities); +extern int bgp_zebra_stale_timer_update(struct bgp *bgp); +extern int bgp_zebra_srv6_manager_get_locator_chunk(const char *name); +extern int bgp_zebra_srv6_manager_release_locator_chunk(const char *name); +extern void bgp_zebra_send_nexthop_label(int cmd, mpls_label_t label, + ifindex_t index, vrf_id_t vrfid, + enum lsp_types_t ltype, + struct prefix *p, uint8_t num_labels, + mpls_label_t out_labels[]); +extern bool bgp_zebra_request_label_range(uint32_t base, uint32_t chunk_size, + bool label_auto); +extern void bgp_zebra_release_label_range(uint32_t start, uint32_t end); +extern enum zclient_send_status +bgp_zebra_withdraw_actual(struct bgp_dest *dest, struct bgp_path_info *info, + struct bgp *bgp); +#endif /* _QUAGGA_BGP_ZEBRA_H */ diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c new file mode 100644 index 0000000..894226a --- /dev/null +++ b/bgpd/bgpd.c @@ -0,0 +1,8781 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP-4, BGP-4+ daemon program + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "ringbuf.h" +#include "command.h" +#include "sockunion.h" +#include "sockopt.h" +#include "network.h" +#include "memory.h" +#include "filter.h" +#include "routemap.h" +#include "log.h" +#include "plist.h" +#include "linklist.h" +#include "workqueue.h" +#include "queue.h" +#include "zclient.h" +#include "bfd.h" +#include "hash.h" +#include "jhash.h" +#include "table.h" +#include "lib/json.h" +#include "lib/sockopt.h" +#include "frr_pthread.h" +#include "bitfield.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_community_alias.h" +#include "bgpd/bgp_conditional_adv.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_fsm.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_damp.h" +#include "bgpd/bgp_mplsvpn.h" +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi_backend.h" +#endif +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_nht.h" +#include "bgpd/bgp_nhg.h" +#include "bgpd/bgp_updgrp.h" +#include "bgpd/bgp_bfd.h" +#include "bgpd/bgp_memory.h" +#include "bgpd/bgp_evpn_vty.h" +#include "bgpd/bgp_keepalives.h" +#include "bgpd/bgp_io.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_labelpool.h" +#include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_addpath.h" +#include "bgpd/bgp_evpn_private.h" +#include "bgpd/bgp_evpn_mh.h" +#include "bgpd/bgp_mac.h" +#include "bgp_trace.h" + +DEFINE_MTYPE_STATIC(BGPD, PEER_TX_SHUTDOWN_MSG, "Peer shutdown message (TX)"); +DEFINE_QOBJ_TYPE(bgp_master); +DEFINE_QOBJ_TYPE(bgp); +DEFINE_QOBJ_TYPE(peer); +DEFINE_HOOK(bgp_inst_delete, (struct bgp *bgp), (bgp)); + +/* BGP process wide configuration. */ +static struct bgp_master bgp_master; + +/* BGP process wide configuration pointer to export. */ +struct bgp_master *bm; + +/* BGP community-list. */ +struct community_list_handler *bgp_clist; + +unsigned int multipath_num = MULTIPATH_NUM; + +/* Number of bgp instances configured for suppress fib config */ +unsigned int bgp_suppress_fib_count; + +static void bgp_if_finish(struct bgp *bgp); +static void peer_drop_dynamic_neighbor(struct peer *peer); + +extern struct zclient *zclient; + +/* handle main socket creation or deletion */ +static int bgp_check_main_socket(bool create, struct bgp *bgp) +{ + static int bgp_server_main_created; + struct listnode *node; + char *address; + + if (create) { + if (bgp_server_main_created) + return 0; + if (list_isempty(bm->addresses)) { + if (bgp_socket(bgp, bm->port, NULL) < 0) + return BGP_ERR_INVALID_VALUE; + } else { + for (ALL_LIST_ELEMENTS_RO(bm->addresses, node, address)) + if (bgp_socket(bgp, bm->port, address) < 0) + return BGP_ERR_INVALID_VALUE; + } + bgp_server_main_created = 1; + return 0; + } + if (!bgp_server_main_created) + return 0; + bgp_close(); + bgp_server_main_created = 0; + return 0; +} + +void bgp_session_reset(struct peer *peer) +{ + if (peer->doppelganger && + (peer->doppelganger->connection->status != Deleted) && + !(CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) + peer_delete(peer->doppelganger); + + BGP_EVENT_ADD(peer->connection, BGP_Stop); +} + +/* + * During session reset, we may delete the doppelganger peer, which would + * be the next node to the current node. If the session reset was invoked + * during walk of peer list, we would end up accessing the freed next + * node. This function moves the next node along. + */ +static void bgp_session_reset_safe(struct peer *peer, struct listnode **nnode) +{ + struct listnode *n; + struct peer *npeer; + + n = (nnode) ? *nnode : NULL; + npeer = (n) ? listgetdata(n) : NULL; + + if (peer->doppelganger && + (peer->doppelganger->connection->status != Deleted) && + !(CHECK_FLAG(peer->doppelganger->flags, PEER_FLAG_CONFIG_NODE))) { + if (peer->doppelganger == npeer) + /* nnode and *nnode are confirmed to be non-NULL here */ + *nnode = (*nnode)->next; + peer_delete(peer->doppelganger); + } + + BGP_EVENT_ADD(peer->connection, BGP_Stop); +} + +/* BGP global flag manipulation. */ +int bgp_option_set(int flag) +{ + switch (flag) { + case BGP_OPT_NO_FIB: + case BGP_OPT_NO_LISTEN: + case BGP_OPT_NO_ZEBRA: + SET_FLAG(bm->options, flag); + break; + default: + return BGP_ERR_INVALID_FLAG; + } + return 0; +} + +int bgp_option_unset(int flag) +{ + switch (flag) { + case BGP_OPT_NO_ZEBRA: + case BGP_OPT_NO_FIB: + UNSET_FLAG(bm->options, flag); + break; + default: + return BGP_ERR_INVALID_FLAG; + } + return 0; +} + +int bgp_option_check(int flag) +{ + return CHECK_FLAG(bm->options, flag); +} + +/* set the bgp no-rib option during runtime and remove installed routes */ +void bgp_option_norib_set_runtime(void) +{ + struct bgp *bgp; + struct listnode *node; + afi_t afi; + safi_t safi; + + if (bgp_option_check(BGP_OPT_NO_FIB)) + return; + + bgp_option_set(BGP_OPT_NO_FIB); + + zlog_info("Disabled BGP route installation to RIB (Zebra)"); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + FOREACH_AFI_SAFI (afi, safi) { + /* + * Stop a crash, more work is needed + * here to properly add/remove these types of + * routes from zebra. + */ + if (!bgp_fibupd_safi(safi)) + continue; + + bgp_zebra_withdraw_table_all_subtypes(bgp, afi, safi); + } + } + + zlog_info("All routes have been withdrawn from RIB (Zebra)"); +} + +/* unset the bgp no-rib option during runtime and announce routes to Zebra */ +void bgp_option_norib_unset_runtime(void) +{ + struct bgp *bgp; + struct listnode *node; + afi_t afi; + safi_t safi; + + if (!bgp_option_check(BGP_OPT_NO_FIB)) + return; + + bgp_option_unset(BGP_OPT_NO_FIB); + + zlog_info("Enabled BGP route installation to RIB (Zebra)"); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, node, bgp)) { + FOREACH_AFI_SAFI (afi, safi) { + /* + * Stop a crash, more work is needed + * here to properly add/remove these types + * of routes from zebra + */ + if (!bgp_fibupd_safi(safi)) + continue; + + bgp_zebra_announce_table_all_subtypes(bgp, afi, safi); + } + } + + zlog_info("All routes have been installed in RIB (Zebra)"); +} + +/* Internal function to set BGP structure configureation flag. */ +static void bgp_config_set(struct bgp *bgp, int config) +{ + SET_FLAG(bgp->config, config); +} + +static void bgp_config_unset(struct bgp *bgp, int config) +{ + UNSET_FLAG(bgp->config, config); +} + +static int bgp_config_check(struct bgp *bgp, int config) +{ + return CHECK_FLAG(bgp->config, config); +} + +/* Set BGP router identifier; distinguish between explicit config and other + * cases. + */ +static int bgp_router_id_set(struct bgp *bgp, const struct in_addr *id, + bool is_config) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (IPV4_ADDR_SAME(&bgp->router_id, id)) + return 0; + + /* EVPN uses router id in RD, withdraw them */ + if (is_evpn_enabled()) + bgp_evpn_handle_router_id_update(bgp, true); + + vpn_handle_router_id_update(bgp, true, is_config); + + IPV4_ADDR_COPY(&bgp->router_id, id); + + /* Set all peer's local identifier with this value. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + IPV4_ADDR_COPY(&peer->local_id, id); + + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_RID_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + + /* EVPN uses router id in RD, update them */ + if (is_evpn_enabled()) + bgp_evpn_handle_router_id_update(bgp, false); + + vpn_handle_router_id_update(bgp, false, is_config); + + return 0; +} + +void bgp_router_id_zebra_bump(vrf_id_t vrf_id, const struct prefix *router_id) +{ + struct listnode *node, *nnode; + struct bgp *bgp; + struct in_addr *addr = NULL; + + if (router_id != NULL) + addr = (struct in_addr *)&(router_id->u.prefix4); + + if (vrf_id == VRF_DEFAULT) { + /* Router-id change for default VRF has to also update all + * views. */ + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + continue; + + if (addr) + bgp->router_id_zebra = *addr; + else + addr = &bgp->router_id_zebra; + + if (!bgp->router_id_static.s_addr) { + /* Router ID is updated if there are no active + * peer sessions + */ + if (bgp->established_peers == 0) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "RID change : vrf %s(%u), RTR ID %pI4", + bgp->name_pretty, + bgp->vrf_id, addr); + /* + * if old router-id was 0x0, set flag + * to use this new value + */ + bgp_router_id_set(bgp, addr, + (bgp->router_id.s_addr + == INADDR_ANY) + ? true + : false); + } + } + } + } else { + bgp = bgp_lookup_by_vrf_id(vrf_id); + if (bgp) { + if (addr) + bgp->router_id_zebra = *addr; + else + addr = &bgp->router_id_zebra; + + if (!bgp->router_id_static.s_addr) { + /* Router ID is updated if there are no active + * peer sessions + */ + if (bgp->established_peers == 0) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "RID change : vrf %s(%u), RTR ID %pI4", + bgp->name_pretty, + bgp->vrf_id, addr); + /* + * if old router-id was 0x0, set flag + * to use this new value + */ + bgp_router_id_set(bgp, addr, + (bgp->router_id.s_addr + == INADDR_ANY) + ? true + : false); + } + } + + } + } +} + +void bgp_router_id_static_set(struct bgp *bgp, struct in_addr id) +{ + bgp->router_id_static = id; + bgp_router_id_set(bgp, + id.s_addr != INADDR_ANY ? &id : &bgp->router_id_zebra, + true /* is config */); +} + +void bm_wait_for_fib_set(bool set) +{ + bool send_msg = false; + struct bgp *bgp; + struct peer *peer; + struct listnode *next, *node; + + if (bm->wait_for_fib == set) + return; + + bm->wait_for_fib = set; + if (set) { + if (bgp_suppress_fib_count == 0) + send_msg = true; + bgp_suppress_fib_count++; + } else { + bgp_suppress_fib_count--; + if (bgp_suppress_fib_count == 0) + send_msg = true; + } + + if (send_msg && zclient) + zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, + zclient, set); + + /* + * If this is configed at a time when peers are already set + * FRR needs to reset the connection(s) as that some installs + * may have already happened in some shape fashion or form + * let's just start over + */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (!BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) + continue; + + peer->last_reset = PEER_DOWN_SUPPRESS_FIB_PENDING; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } +} + +/* Set the suppress fib pending for the bgp configuration */ +void bgp_suppress_fib_pending_set(struct bgp *bgp, bool set) +{ + bool send_msg = false; + struct peer *peer; + struct listnode *node; + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW) + return; + + if (set) { + SET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_FIB_PENDING); + /* Send msg to zebra for the first instance of bgp enabled + * with suppress fib + */ + if (bgp_suppress_fib_count == 0) + send_msg = true; + bgp_suppress_fib_count++; + } else { + UNSET_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_FIB_PENDING); + bgp_suppress_fib_count--; + + /* Send msg to zebra if there are no instances enabled + * with suppress fib + */ + if (bgp_suppress_fib_count == 0) + send_msg = true; + } + /* Send route notify request to RIB */ + if (send_msg) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Sending ZEBRA_ROUTE_NOTIFY_REQUEST"); + + if (zclient) + zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, + zclient, set); + } + + /* + * If this is configed at a time when peers are already set + * FRR needs to reset the connection as that some installs + * may have already happened in some shape fashion or form + * let's just start over + */ + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (!BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + continue; + + peer->last_reset = PEER_DOWN_SUPPRESS_FIB_PENDING; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } +} + +/* BGP's cluster-id control. */ +void bgp_cluster_id_set(struct bgp *bgp, struct in_addr *cluster_id) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (bgp_config_check(bgp, BGP_CONFIG_CLUSTER_ID) + && IPV4_ADDR_SAME(&bgp->cluster_id, cluster_id)) + return; + + IPV4_ADDR_COPY(&bgp->cluster_id, cluster_id); + bgp_config_set(bgp, BGP_CONFIG_CLUSTER_ID); + + /* Clear all IBGP peer. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->sort != BGP_PEER_IBGP) + continue; + + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_CLID_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } +} + +void bgp_cluster_id_unset(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (!bgp_config_check(bgp, BGP_CONFIG_CLUSTER_ID)) + return; + + bgp->cluster_id.s_addr = 0; + bgp_config_unset(bgp, BGP_CONFIG_CLUSTER_ID); + + /* Clear all IBGP peer. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->sort != BGP_PEER_IBGP) + continue; + + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_CLID_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } +} + +/* BGP timer configuration. */ +void bgp_timers_set(struct vty *vty, struct bgp *bgp, uint32_t keepalive, + uint32_t holdtime, uint32_t connect_retry, + uint32_t delayopen) +{ + uint32_t default_keepalive = holdtime / 3; + + if (keepalive > default_keepalive) { + if (vty) + vty_out(vty, + "%% keepalive value %u is larger than 1/3 of the holdtime, setting to %u\n", + keepalive, default_keepalive); + } else { + default_keepalive = keepalive; + } + + bgp->default_keepalive = default_keepalive; + bgp->default_holdtime = holdtime; + bgp->default_connect_retry = connect_retry; + bgp->default_delayopen = delayopen; +} + +/* mostly for completeness - CLI uses its own defaults */ +void bgp_timers_unset(struct bgp *bgp) +{ + bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE; + bgp->default_holdtime = BGP_DEFAULT_HOLDTIME; + bgp->default_connect_retry = BGP_DEFAULT_CONNECT_RETRY; + bgp->default_delayopen = BGP_DEFAULT_DELAYOPEN; +} + +void bgp_tcp_keepalive_set(struct bgp *bgp, uint16_t keepalive_idle, + uint16_t keepalive_intvl, uint16_t keepalive_probes) +{ + bgp->tcp_keepalive_idle = keepalive_idle; + bgp->tcp_keepalive_intvl = keepalive_intvl; + bgp->tcp_keepalive_probes = keepalive_probes; +} + +void bgp_tcp_keepalive_unset(struct bgp *bgp) +{ + bgp->tcp_keepalive_idle = 0; + bgp->tcp_keepalive_intvl = 0; + bgp->tcp_keepalive_probes = 0; +} + +/* BGP confederation configuration. */ +void bgp_confederation_id_set(struct bgp *bgp, as_t as, const char *as_str) +{ + struct peer *peer; + struct listnode *node, *nnode; + int already_confed; + + if (as == 0) + return; + + /* Remember - were we doing confederation before? */ + already_confed = bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION); + bgp->confed_id = as; + if (bgp->confed_id_pretty) + XFREE(MTYPE_BGP_NAME, bgp->confed_id_pretty); + bgp->confed_id_pretty = XSTRDUP(MTYPE_BGP_NAME, as_str); + bgp_config_set(bgp, BGP_CONFIG_CONFEDERATION); + + /* If we were doing confederation already, this is just an external + AS change. Just Reset EBGP sessions, not CONFED sessions. If we + were not doing confederation before, reset all EBGP sessions. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + enum bgp_peer_sort ptype = peer_sort(peer); + + /* We're looking for peers who's AS is not local or part of our + confederation. */ + if (already_confed) { + if (ptype == BGP_PEER_EBGP) { + peer->local_as = as; + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + peer->last_reset = + PEER_DOWN_CONFED_ID_CHANGE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset_safe(peer, &nnode); + } + } else { + /* Not doign confederation before, so reset every + non-local + session */ + if (ptype != BGP_PEER_IBGP) { + /* Reset the local_as to be our EBGP one */ + if (ptype == BGP_PEER_EBGP) + peer->local_as = as; + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + peer->last_reset = + PEER_DOWN_CONFED_ID_CHANGE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset_safe(peer, &nnode); + } + } + } + return; +} + +void bgp_confederation_id_unset(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node, *nnode; + + bgp->confed_id = 0; + XFREE(MTYPE_BGP_NAME, bgp->confed_id_pretty); + bgp_config_unset(bgp, BGP_CONFIG_CONFEDERATION); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + /* We're looking for peers who's AS is not local */ + if (peer_sort(peer) != BGP_PEER_IBGP) { + peer->local_as = bgp->as; + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + peer->last_reset = PEER_DOWN_CONFED_ID_CHANGE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + + else + bgp_session_reset_safe(peer, &nnode); + } + } +} + +/* Is an AS part of the confed or not? */ +bool bgp_confederation_peers_check(struct bgp *bgp, as_t as) +{ + int i; + + if (!bgp) + return false; + + for (i = 0; i < bgp->confed_peers_cnt; i++) + if (bgp->confed_peers[i].as == as) + return true; + + return false; +} + +/* Add an AS to the confederation set. */ +void bgp_confederation_peers_add(struct bgp *bgp, as_t as, const char *as_str) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (!bgp) + return; + + if (bgp_confederation_peers_check(bgp, as)) + return; + + bgp->confed_peers = XREALLOC(MTYPE_BGP_CONFED_LIST, bgp->confed_peers, + (bgp->confed_peers_cnt + 1) * + sizeof(struct as_confed)); + + bgp->confed_peers[bgp->confed_peers_cnt].as = as; + bgp->confed_peers[bgp->confed_peers_cnt].as_pretty = + XSTRDUP(MTYPE_BGP_NAME, as_str); + bgp->confed_peers_cnt++; + + if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION)) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->as == as) { + peer->local_as = bgp->as; + (void)peer_sort(peer); + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + peer->last_reset = + PEER_DOWN_CONFED_PEER_CHANGE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset_safe(peer, &nnode); + } + } + } +} + +/* Delete an AS from the confederation set. */ +void bgp_confederation_peers_remove(struct bgp *bgp, as_t as) +{ + int i; + int j; + struct peer *peer; + struct listnode *node, *nnode; + + if (!bgp) + return; + + if (!bgp_confederation_peers_check(bgp, as)) + return; + + for (i = 0; i < bgp->confed_peers_cnt; i++) + if (bgp->confed_peers[i].as == as) { + XFREE(MTYPE_BGP_NAME, bgp->confed_peers[i].as_pretty); + for (j = i + 1; j < bgp->confed_peers_cnt; j++) { + bgp->confed_peers[j - 1].as = + bgp->confed_peers[j].as; + bgp->confed_peers[j - 1].as_pretty = + bgp->confed_peers[j].as_pretty; + } + } + + bgp->confed_peers_cnt--; + + if (bgp->confed_peers_cnt == 0) { + if (bgp->confed_peers) + XFREE(MTYPE_BGP_CONFED_LIST, bgp->confed_peers); + bgp->confed_peers = NULL; + } else + bgp->confed_peers = XREALLOC( + MTYPE_BGP_CONFED_LIST, bgp->confed_peers, + bgp->confed_peers_cnt * sizeof(struct as_confed)); + + /* Now reset any peer who's remote AS has just been removed from the + CONFED */ + if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION)) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (peer->as == as) { + peer->local_as = bgp->confed_id; + (void)peer_sort(peer); + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + peer->last_reset = + PEER_DOWN_CONFED_PEER_CHANGE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset_safe(peer, &nnode); + } + } + } +} + +/* Local preference configuration. */ +void bgp_default_local_preference_set(struct bgp *bgp, uint32_t local_pref) +{ + if (!bgp) + return; + + bgp->default_local_pref = local_pref; +} + +void bgp_default_local_preference_unset(struct bgp *bgp) +{ + if (!bgp) + return; + + bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; +} + +/* Local preference configuration. */ +void bgp_default_subgroup_pkt_queue_max_set(struct bgp *bgp, + uint32_t queue_size) +{ + if (!bgp) + return; + + bgp->default_subgroup_pkt_queue_max = queue_size; +} + +void bgp_default_subgroup_pkt_queue_max_unset(struct bgp *bgp) +{ + if (!bgp) + return; + bgp->default_subgroup_pkt_queue_max = + BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX; +} + +/* Listen limit configuration. */ +void bgp_listen_limit_set(struct bgp *bgp, int listen_limit) +{ + if (!bgp) + return; + + bgp->dynamic_neighbors_limit = listen_limit; +} + +void bgp_listen_limit_unset(struct bgp *bgp) +{ + if (!bgp) + return; + + bgp->dynamic_neighbors_limit = BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT; +} + +int bgp_map_afi_safi_iana2int(iana_afi_t pkt_afi, iana_safi_t pkt_safi, + afi_t *afi, safi_t *safi) +{ + /* Map from IANA values to internal values, return error if + * values are unrecognized. + */ + *afi = afi_iana2int(pkt_afi); + *safi = safi_iana2int(pkt_safi); + if (*afi == AFI_MAX || *safi == SAFI_MAX) + return -1; + + return 0; +} + +int bgp_map_afi_safi_int2iana(afi_t afi, safi_t safi, iana_afi_t *pkt_afi, + iana_safi_t *pkt_safi) +{ + /* Map from internal values to IANA values, return error if + * internal values are bad (unexpected). + */ + if (afi == AFI_MAX || safi == SAFI_MAX) + return -1; + *pkt_afi = afi_int2iana(afi); + *pkt_safi = safi_int2iana(safi); + return 0; +} + +struct peer_af *peer_af_create(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer_af *af; + int afid; + struct bgp *bgp; + + if (!peer) + return NULL; + + afid = afindex(afi, safi); + if (afid >= BGP_AF_MAX) + return NULL; + + bgp = peer->bgp; + assert(peer->peer_af_array[afid] == NULL); + + /* Allocate new peer af */ + af = XCALLOC(MTYPE_BGP_PEER_AF, sizeof(struct peer_af)); + + peer->peer_af_array[afid] = af; + af->afi = afi; + af->safi = safi; + af->afid = afid; + af->peer = peer; + bgp->af_peer_count[afi][safi]++; + + return af; +} + +struct peer_af *peer_af_find(struct peer *peer, afi_t afi, safi_t safi) +{ + int afid; + + if (!peer) + return NULL; + + afid = afindex(afi, safi); + if (afid >= BGP_AF_MAX) + return NULL; + + return peer->peer_af_array[afid]; +} + +int peer_af_delete(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer_af *af; + int afid; + struct bgp *bgp; + + if (!peer) + return -1; + + afid = afindex(afi, safi); + if (afid >= BGP_AF_MAX) + return -1; + + af = peer->peer_af_array[afid]; + if (!af) + return -1; + + bgp = peer->bgp; + bgp_soft_reconfig_table_task_cancel(bgp, bgp->rib[afi][safi], peer); + + bgp_stop_announce_route_timer(af); + + if (PAF_SUBGRP(af)) { + if (BGP_DEBUG(update_groups, UPDATE_GROUPS)) + zlog_debug("u%" PRIu64 ":s%" PRIu64 " remove peer %s", + af->subgroup->update_group->id, + af->subgroup->id, peer->host); + } + + + update_subgroup_remove_peer(af->subgroup, af); + + if (bgp->af_peer_count[afi][safi]) + bgp->af_peer_count[afi][safi]--; + + peer->peer_af_array[afid] = NULL; + XFREE(MTYPE_BGP_PEER_AF, af); + return 0; +} + +/* Peer comparison function for sorting. */ +int peer_cmp(struct peer *p1, struct peer *p2) +{ + if (p1->group && !p2->group) + return -1; + + if (!p1->group && p2->group) + return 1; + + if (p1->group == p2->group) { + if (p1->conf_if && !p2->conf_if) + return -1; + + if (!p1->conf_if && p2->conf_if) + return 1; + + if (p1->conf_if && p2->conf_if) + return if_cmp_name_func(p1->conf_if, p2->conf_if); + } else + return strcmp(p1->group->name, p2->group->name); + + return sockunion_cmp(&p1->connection->su, &p2->connection->su); +} + +static unsigned int peer_hash_key_make(const void *p) +{ + const struct peer *peer = p; + return sockunion_hash(&peer->connection->su); +} + +static bool peer_hash_same(const void *p1, const void *p2) +{ + const struct peer *peer1 = p1; + const struct peer *peer2 = p2; + + return (sockunion_same(&peer1->connection->su, &peer2->connection->su) && + CHECK_FLAG(peer1->flags, PEER_FLAG_CONFIG_NODE) == + CHECK_FLAG(peer2->flags, PEER_FLAG_CONFIG_NODE)); +} + +void peer_flag_inherit(struct peer *peer, uint64_t flag) +{ + bool group_val; + + /* Skip if peer is not a peer-group member. */ + if (!peer_group_active(peer)) + return; + + /* Unset override flag to signal inheritance from peer-group. */ + UNSET_FLAG(peer->flags_override, flag); + + /* + * Inherit flag state from peer-group. If the flag of the peer-group is + * not being inverted, the peer must inherit the inverse of the current + * peer-group flag state. + */ + group_val = CHECK_FLAG(peer->group->conf->flags, flag); + if (!CHECK_FLAG(peer->group->conf->flags_invert, flag) + && CHECK_FLAG(peer->flags_invert, flag)) + COND_FLAG(peer->flags, flag, !group_val); + else + COND_FLAG(peer->flags, flag, group_val); +} + +bool peer_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag) +{ + return !!CHECK_FLAG(peer->af_flags[afi][safi], flag); +} + +void peer_af_flag_inherit(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag) +{ + bool group_val; + + /* Skip if peer is not a peer-group member. */ + if (!peer_group_active(peer)) + return; + + /* Unset override flag to signal inheritance from peer-group. */ + UNSET_FLAG(peer->af_flags_override[afi][safi], flag); + + /* + * Inherit flag state from peer-group. If the flag of the peer-group is + * not being inverted, the peer must inherit the inverse of the current + * peer-group flag state. + */ + group_val = CHECK_FLAG(peer->group->conf->af_flags[afi][safi], flag); + if (!CHECK_FLAG(peer->group->conf->af_flags_invert[afi][safi], flag) + && CHECK_FLAG(peer->af_flags_invert[afi][safi], flag)) + COND_FLAG(peer->af_flags[afi][safi], flag, !group_val); + else + COND_FLAG(peer->af_flags[afi][safi], flag, group_val); +} + +/* Check peer's AS number and determines if this peer is IBGP or EBGP */ +static inline enum bgp_peer_sort peer_calc_sort(struct peer *peer) +{ + struct bgp *bgp; + as_t local_as; + + bgp = peer->bgp; + + if (peer->change_local_as) + local_as = peer->change_local_as; + else + local_as = peer->local_as; + + /* Peer-group */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (peer->as_type == AS_INTERNAL) + return BGP_PEER_IBGP; + + else if (peer->as_type == AS_EXTERNAL) + return BGP_PEER_EBGP; + + else if (peer->as_type == AS_SPECIFIED && peer->as) { + assert(bgp); + return (local_as == peer->as ? BGP_PEER_IBGP + : BGP_PEER_EBGP); + } + + else { + struct peer *peer1; + + assert(peer->group); + peer1 = listnode_head(peer->group->peer); + + if (peer1) + return peer1->sort; + } + return BGP_PEER_INTERNAL; + } + + /* Normal peer */ + if (bgp && CHECK_FLAG(bgp->config, BGP_CONFIG_CONFEDERATION)) { + if (local_as == 0) + return BGP_PEER_INTERNAL; + + if (local_as == peer->as) { + if (bgp->as == bgp->confed_id) { + if (local_as == bgp->as) + return BGP_PEER_IBGP; + else + return BGP_PEER_EBGP; + } else { + if (local_as == bgp->confed_id) + return BGP_PEER_EBGP; + else + return BGP_PEER_IBGP; + } + } + + if (bgp_confederation_peers_check(bgp, peer->as)) + return BGP_PEER_CONFED; + + return BGP_PEER_EBGP; + } else { + if (peer->as_type == AS_UNSPECIFIED) { + /* check if in peer-group with AS information */ + if (peer->group + && (peer->group->conf->as_type != AS_UNSPECIFIED)) { + if (peer->group->conf->as_type + == AS_SPECIFIED) { + if (local_as == peer->group->conf->as) + return BGP_PEER_IBGP; + else + return BGP_PEER_EBGP; + } else if (peer->group->conf->as_type + == AS_INTERNAL) + return BGP_PEER_IBGP; + else + return BGP_PEER_EBGP; + } + /* no AS information anywhere, let caller know */ + return BGP_PEER_UNSPECIFIED; + } else if (peer->as_type != AS_SPECIFIED) + return (peer->as_type == AS_INTERNAL ? BGP_PEER_IBGP + : BGP_PEER_EBGP); + + return (local_as == 0 ? BGP_PEER_INTERNAL + : local_as == peer->as ? BGP_PEER_IBGP + : BGP_PEER_EBGP); + } +} + +/* Calculate and cache the peer "sort" */ +enum bgp_peer_sort peer_sort(struct peer *peer) +{ + peer->sort = peer_calc_sort(peer); + return peer->sort; +} + +enum bgp_peer_sort peer_sort_lookup(struct peer *peer) +{ + return peer->sort; +} + +/* + * Mutex will be freed in peer_connection_free + * this is a convenience function to reduce cut-n-paste + */ +void bgp_peer_connection_buffers_free(struct peer_connection *connection) +{ + frr_with_mutex (&connection->io_mtx) { + if (connection->ibuf) { + stream_fifo_free(connection->ibuf); + connection->ibuf = NULL; + } + + if (connection->obuf) { + stream_fifo_free(connection->obuf); + connection->obuf = NULL; + } + + if (connection->ibuf_work) { + ringbuf_del(connection->ibuf_work); + connection->ibuf_work = NULL; + } + } +} + +void bgp_peer_connection_free(struct peer_connection **connection) +{ + bgp_peer_connection_buffers_free(*connection); + pthread_mutex_destroy(&(*connection)->io_mtx); + + memset(*connection, 0, sizeof(struct peer_connection)); + XFREE(MTYPE_BGP_PEER_CONNECTION, *connection); + + connection = NULL; +} + +struct peer_connection *bgp_peer_connection_new(struct peer *peer) +{ + struct peer_connection *connection; + + connection = XCALLOC(MTYPE_BGP_PEER_CONNECTION, + sizeof(struct peer_connection)); + + connection->peer = peer; + connection->fd = -1; + + connection->ibuf = stream_fifo_new(); + connection->obuf = stream_fifo_new(); + pthread_mutex_init(&connection->io_mtx, NULL); + + /* We use a larger buffer for peer->obuf_work in the event that: + * - We RX a BGP_UPDATE where the attributes alone are just + * under BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE. + * - The user configures an outbound route-map that does many as-path + * prepends or adds many communities. At most they can have + * CMD_ARGC_MAX args in a route-map so there is a finite limit on how + * large they can make the attributes. + * + * Having a buffer with BGP_MAX_PACKET_SIZE_OVERFLOW allows us to avoid + * bounds checking for every single attribute as we construct an + * UPDATE. + */ + connection->ibuf_work = + ringbuf_new(BGP_MAX_PACKET_SIZE * BGP_READ_PACKET_MAX); + + connection->status = Idle; + connection->ostatus = Idle; + + return connection; +} + +static void peer_free(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + assert(peer->connection->status == Deleted); + + QOBJ_UNREG(peer); + + /* this /ought/ to have been done already through bgp_stop earlier, + * but just to be sure.. + */ + bgp_timer_set(peer->connection); + bgp_reads_off(peer->connection); + bgp_writes_off(peer->connection); + event_cancel_event_ready(bm->master, peer->connection); + FOREACH_AFI_SAFI (afi, safi) + EVENT_OFF(peer->t_revalidate_all[afi][safi]); + assert(!peer->connection->t_write); + assert(!peer->connection->t_read); + event_cancel_event_ready(bm->master, peer->connection); + + /* Free connected nexthop, if present */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE) + && !peer_dynamic_neighbor(peer)) + bgp_delete_connected_nexthop(family2afi(peer->connection->su.sa + .sa_family), + peer); + + FOREACH_AFI_SAFI (afi, safi) { + if (peer->filter[afi][safi].advmap.aname) + XFREE(MTYPE_BGP_FILTER_NAME, + peer->filter[afi][safi].advmap.aname); + if (peer->filter[afi][safi].advmap.cname) + XFREE(MTYPE_BGP_FILTER_NAME, + peer->filter[afi][safi].advmap.cname); + } + + XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); + + XFREE(MTYPE_PEER_DESC, peer->desc); + XFREE(MTYPE_BGP_PEER_HOST, peer->host); + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); + + /* Update source configuration. */ + if (peer->update_source) { + sockunion_free(peer->update_source); + peer->update_source = NULL; + } + + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); + + XFREE(MTYPE_BGP_NOTIFICATION, peer->notify.data); + memset(&peer->notify, 0, sizeof(struct bgp_notify)); + + if (peer->clear_node_queue) + work_queue_free_and_null(&peer->clear_node_queue); + + XFREE(MTYPE_PEER_CONF_IF, peer->conf_if); + + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + + /* Remove BFD configuration. */ + if (peer->bfd_config) + bgp_peer_remove_bfd_config(peer); + + FOREACH_AFI_SAFI (afi, safi) + bgp_addpath_set_peer_type(peer, afi, safi, BGP_ADDPATH_NONE, 0); + + if (peer->change_local_as_pretty) + XFREE(MTYPE_BGP_NAME, peer->change_local_as_pretty); + if (peer->as_pretty) + XFREE(MTYPE_BGP_NAME, peer->as_pretty); + + bgp_peer_connection_free(&peer->connection); + + bgp_unlock(peer->bgp); + + stream_free(peer->last_reset_cause); + + memset(peer, 0, sizeof(struct peer)); + + XFREE(MTYPE_BGP_PEER, peer); +} + +/* increase reference count on a struct peer */ +struct peer *peer_lock_with_caller(const char *name, struct peer *peer) +{ + frrtrace(2, frr_bgp, bgp_peer_lock, peer, name); + assert(peer && (peer->lock >= 0)); + + peer->lock++; + + return peer; +} + +/* decrease reference count on a struct peer + * struct peer is freed and NULL returned if last reference + */ +struct peer *peer_unlock_with_caller(const char *name, struct peer *peer) +{ + frrtrace(2, frr_bgp, bgp_peer_unlock, peer, name); + assert(peer && (peer->lock > 0)); + + peer->lock--; + + if (peer->lock == 0) { + peer_free(peer); + return NULL; + } + + return peer; +} +/* BGP GR changes */ + +int bgp_global_gr_init(struct bgp *bgp) +{ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s called ..", __func__); + + int local_GLOBAL_GR_FSM[BGP_GLOBAL_GR_MODE][BGP_GLOBAL_GR_EVENT_CMD] = { + /* GLOBAL_HELPER Mode */ + { + /*Event -> */ + /*GLOBAL_GR_cmd*/ /*no_Global_GR_cmd*/ + GLOBAL_GR, GLOBAL_INVALID, + /*GLOBAL_DISABLE_cmd*/ /*no_Global_Disable_cmd*/ + GLOBAL_DISABLE, GLOBAL_INVALID + }, + /* GLOBAL_GR Mode */ + { + /*Event -> */ + /*GLOBAL_GR_cmd*/ /*no_Global_GR_cmd*/ + GLOBAL_GR, GLOBAL_HELPER, + /*GLOBAL_DISABLE_cmd*/ /*no_Global_Disable_cmd*/ + GLOBAL_DISABLE, GLOBAL_INVALID + }, + /* GLOBAL_DISABLE Mode */ + { + /*Event -> */ + /*GLOBAL_GR_cmd */ /*no_Global_GR_cmd*/ + GLOBAL_GR, GLOBAL_INVALID, + /*GLOBAL_DISABLE_cmd*//*no_Global_Disable_cmd*/ + GLOBAL_DISABLE, GLOBAL_HELPER + }, + /* GLOBAL_INVALID Mode */ + { + /*Event -> */ + /*GLOBAL_GR_cmd*/ /*no_Global_GR_cmd*/ + GLOBAL_INVALID, GLOBAL_INVALID, + /*GLOBAL_DISABLE_cmd*/ /*no_Global_Disable_cmd*/ + GLOBAL_INVALID, GLOBAL_INVALID + } + }; + memcpy(bgp->GLOBAL_GR_FSM, local_GLOBAL_GR_FSM, + sizeof(local_GLOBAL_GR_FSM)); + + bgp->global_gr_present_state = GLOBAL_HELPER; + bgp->present_zebra_gr_state = ZEBRA_GR_DISABLE; + + return BGP_GR_SUCCESS; +} + +int bgp_peer_gr_init(struct peer *peer) +{ + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("%s called ..", __func__); + + struct bgp_peer_gr local_Peer_GR_FSM[BGP_PEER_GR_MODE] + [BGP_PEER_GR_EVENT_CMD] = { + { + /* PEER_HELPER Mode */ + /* Event-> */ /* PEER_GR_CMD */ /* NO_PEER_GR_CMD */ + { PEER_GR, bgp_peer_gr_action }, {PEER_INVALID, NULL }, + /* Event-> */ /* PEER_DISABLE_CMD */ /* NO_PEER_DISABLE_CMD */ + {PEER_DISABLE, bgp_peer_gr_action }, {PEER_INVALID, NULL }, + /* Event-> */ /* PEER_HELPER_cmd */ /* NO_PEER_HELPER_CMD */ + { PEER_HELPER, NULL }, {PEER_GLOBAL_INHERIT, + bgp_peer_gr_action } + }, + { + /* PEER_GR Mode */ + /* Event-> */ /* PEER_GR_CMD */ /* NO_PEER_GR_CMD */ + { PEER_GR, NULL }, { PEER_GLOBAL_INHERIT, + bgp_peer_gr_action }, + /* Event-> */ /* PEER_DISABLE_CMD */ /* NO_PEER_DISABLE_CMD */ + {PEER_DISABLE, bgp_peer_gr_action }, { PEER_INVALID, NULL }, + /* Event-> */ /* PEER_HELPER_cmd */ /* NO_PEER_HELPER_CMD */ + { PEER_HELPER, bgp_peer_gr_action }, { PEER_INVALID, NULL } + }, + { + /* PEER_DISABLE Mode */ + /* Event-> */ /* PEER_GR_CMD */ /* NO_PEER_GR_CMD */ + { PEER_GR, bgp_peer_gr_action }, { PEER_INVALID, NULL }, + /* Event-> */ /* PEER_DISABLE_CMD */ /* NO_PEER_DISABLE_CMD */ + { PEER_DISABLE, NULL }, { PEER_GLOBAL_INHERIT, + bgp_peer_gr_action }, + /* Event-> */ /* PEER_HELPER_cmd */ /* NO_PEER_HELPER_CMD */ + { PEER_HELPER, bgp_peer_gr_action }, { PEER_INVALID, NULL } + }, + { + /* PEER_INVALID Mode */ + /* Event-> */ /* PEER_GR_CMD */ /* NO_PEER_GR_CMD */ + { PEER_INVALID, NULL }, { PEER_INVALID, NULL }, + /* Event-> */ /* PEER_DISABLE_CMD */ /* NO_PEER_DISABLE_CMD */ + { PEER_INVALID, NULL }, { PEER_INVALID, NULL }, + /* Event-> */ /* PEER_HELPER_cmd */ /* NO_PEER_HELPER_CMD */ + { PEER_INVALID, NULL }, { PEER_INVALID, NULL }, + }, + { + /* PEER_GLOBAL_INHERIT Mode */ + /* Event-> */ /* PEER_GR_CMD */ /* NO_PEER_GR_CMD */ + { PEER_GR, bgp_peer_gr_action }, { PEER_GLOBAL_INHERIT, NULL }, + /* Event-> */ /* PEER_DISABLE_CMD */ /* NO_PEER_DISABLE_CMD */ + { PEER_DISABLE, bgp_peer_gr_action }, { PEER_GLOBAL_INHERIT, NULL }, + /* Event-> */ /* PEER_HELPER_cmd */ /* NO_PEER_HELPER_CMD */ + { PEER_HELPER, bgp_peer_gr_action }, { PEER_GLOBAL_INHERIT, NULL } + } + }; + memcpy(&peer->PEER_GR_FSM, local_Peer_GR_FSM, + sizeof(local_Peer_GR_FSM)); + peer->peer_gr_present_state = PEER_GLOBAL_INHERIT; + bgp_peer_move_to_gr_mode(peer, PEER_GLOBAL_INHERIT); + + return BGP_GR_SUCCESS; +} + +static void bgp_srv6_init(struct bgp *bgp) +{ + bgp->srv6_enabled = false; + memset(bgp->srv6_locator_name, 0, sizeof(bgp->srv6_locator_name)); + bgp->srv6_locator_chunks = list_new(); + bgp->srv6_locator_chunks->del = srv6_locator_chunk_list_free; + bgp->srv6_functions = list_new(); + bgp->srv6_functions->del = (void (*)(void *))srv6_function_free; +} + +static void bgp_srv6_cleanup(struct bgp *bgp) +{ + for (afi_t afi = AFI_IP; afi < AFI_MAX; afi++) { + if (bgp->vpn_policy[afi].tovpn_sid_locator != NULL) + srv6_locator_chunk_free( + &bgp->vpn_policy[afi].tovpn_sid_locator); + if (bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent != NULL) + XFREE(MTYPE_BGP_SRV6_SID, + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent); + if (bgp->vpn_policy[afi].tovpn_sid != NULL) { + sid_unregister(bgp, bgp->vpn_policy[afi].tovpn_sid); + XFREE(MTYPE_BGP_SRV6_SID, + bgp->vpn_policy[afi].tovpn_sid); + } + } + + if (bgp->tovpn_sid_locator != NULL) + srv6_locator_chunk_free(&bgp->tovpn_sid_locator); + if (bgp->tovpn_zebra_vrf_sid_last_sent != NULL) + XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_zebra_vrf_sid_last_sent); + if (bgp->tovpn_sid != NULL) { + sid_unregister(bgp, bgp->tovpn_sid); + XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_sid); + } + + if (bgp->srv6_locator_chunks) + list_delete(&bgp->srv6_locator_chunks); + if (bgp->srv6_functions) + list_delete(&bgp->srv6_functions); +} + +/* Allocate new peer object, implicitely locked. */ +struct peer *peer_new(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct peer *peer; + struct servent *sp; + + /* bgp argument is absolutely required */ + assert(bgp); + + /* Allocate new peer. */ + peer = XCALLOC(MTYPE_BGP_PEER, sizeof(struct peer)); + + /* Create buffers. */ + peer->connection = bgp_peer_connection_new(peer); + + /* Set default value. */ + peer->v_start = BGP_INIT_START_TIMER; + peer->v_connect = bgp->default_connect_retry; + peer->cur_event = peer->last_event = peer->last_major_event = 0; + peer->bgp = bgp_lock(bgp); + peer = peer_lock(peer); /* initial reference */ + peer->local_role = ROLE_UNDEFINED; + peer->remote_role = ROLE_UNDEFINED; + peer->password = NULL; + peer->max_packet_size = BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE; + + /* Set default flags. */ + FOREACH_AFI_SAFI (afi, safi) { + SET_FLAG(peer->af_flags[afi][safi], PEER_FLAG_SEND_COMMUNITY); + SET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY); + SET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI); + SET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY); + + SET_FLAG(peer->af_flags_invert[afi][safi], + PEER_FLAG_SEND_COMMUNITY); + SET_FLAG(peer->af_flags_invert[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY); + SET_FLAG(peer->af_flags_invert[afi][safi], + PEER_FLAG_SEND_EXT_COMMUNITY_RPKI); + SET_FLAG(peer->af_flags_invert[afi][safi], + PEER_FLAG_SEND_LARGE_COMMUNITY); + peer->addpath_type[afi][safi] = BGP_ADDPATH_NONE; + peer->addpath_best_selected[afi][safi] = 0; + peer->addpath_paths_limit[afi][safi].receive = 0; + peer->addpath_paths_limit[afi][safi].send = 0; + peer->soo[afi][safi] = NULL; + } + + /* set nexthop-unchanged for l2vpn evpn by default */ + SET_FLAG(peer->af_flags[AFI_L2VPN][SAFI_EVPN], + PEER_FLAG_NEXTHOP_UNCHANGED); + + SET_FLAG(peer->sflags, PEER_STATUS_CAPABILITY_OPEN); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_ENFORCE_FIRST_AS)) + peer_flag_set(peer, PEER_FLAG_ENFORCE_FIRST_AS); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY)) + peer_flag_set(peer, PEER_FLAG_CAPABILITY_SOFT_VERSION); + + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY)) + peer_flag_set(peer, PEER_FLAG_DYNAMIC_CAPABILITY); + + SET_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_FQDN); + SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_FQDN); + + /* Initialize per peer bgp GR FSM */ + bgp_peer_gr_init(peer); + + /* Get service port number. */ + sp = getservbyname("bgp", "tcp"); + peer->port = (sp == NULL) ? BGP_PORT_DEFAULT : ntohs(sp->s_port); + + QOBJ_REG(peer, peer); + return peer; +} + +/* + * This function is invoked when a duplicate peer structure associated with + * a neighbor is being deleted. If this about-to-be-deleted structure is + * the one with all the config, then we have to copy over the info. + */ +void peer_xfer_config(struct peer *peer_dst, struct peer *peer_src) +{ + struct peer_af *paf; + afi_t afi; + safi_t safi; + int afidx; + + assert(peer_src); + assert(peer_dst); + + /* The following function is used by both peer group config copy to + * individual peer and when we transfer config + */ + if (peer_src->change_local_as) + peer_dst->change_local_as = peer_src->change_local_as; + + /* peer flags apply */ + peer_dst->flags = peer_src->flags; + /* + * The doppelganger *must* not have a config node stored + */ + UNSET_FLAG(peer_dst->flags, PEER_FLAG_CONFIG_NODE); + peer_dst->peer_gr_present_state = peer_src->peer_gr_present_state; + peer_dst->peer_gr_new_status_flag = peer_src->peer_gr_new_status_flag; + + peer_dst->local_as = peer_src->local_as; + peer_dst->port = peer_src->port; + /* copy tcp_mss value */ + peer_dst->tcp_mss = peer_src->tcp_mss; + (void)peer_sort(peer_dst); + peer_dst->sub_sort = peer_src->sub_sort; + peer_dst->rmap_type = peer_src->rmap_type; + peer_dst->local_role = peer_src->local_role; + + peer_dst->max_packet_size = peer_src->max_packet_size; + + /* Timers */ + peer_dst->holdtime = peer_src->holdtime; + peer_dst->keepalive = peer_src->keepalive; + peer_dst->connect = peer_src->connect; + peer_dst->delayopen = peer_src->delayopen; + peer_dst->v_holdtime = peer_src->v_holdtime; + peer_dst->v_keepalive = peer_src->v_keepalive; + peer_dst->routeadv = peer_src->routeadv; + peer_dst->v_routeadv = peer_src->v_routeadv; + peer_dst->v_delayopen = peer_src->v_delayopen; + + /* password apply */ + if (peer_src->password) { + XFREE(MTYPE_PEER_PASSWORD, peer_dst->password); + peer_dst->password = + XSTRDUP(MTYPE_PEER_PASSWORD, peer_src->password); + } + + FOREACH_AFI_SAFI (afi, safi) { + peer_dst->afc[afi][safi] = peer_src->afc[afi][safi]; + peer_dst->af_flags[afi][safi] = peer_src->af_flags[afi][safi]; + peer_dst->allowas_in[afi][safi] = + peer_src->allowas_in[afi][safi]; + peer_dst->weight[afi][safi] = peer_src->weight[afi][safi]; + peer_dst->addpath_type[afi][safi] = + peer_src->addpath_type[afi][safi]; + peer_dst->addpath_paths_limit[afi][safi] = + peer_src->addpath_paths_limit[afi][safi]; + } + + for (afidx = BGP_AF_START; afidx < BGP_AF_MAX; afidx++) { + paf = peer_src->peer_af_array[afidx]; + if (paf != NULL) { + if (!peer_af_find(peer_dst, paf->afi, paf->safi)) + peer_af_create(peer_dst, paf->afi, paf->safi); + } + } + + /* update-source apply */ + if (peer_src->update_source) { + if (peer_dst->update_source) + sockunion_free(peer_dst->update_source); + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer_dst->update_if); + peer_dst->update_source = + sockunion_dup(peer_src->update_source); + } else if (peer_src->update_if) { + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer_dst->update_if); + if (peer_dst->update_source) { + sockunion_free(peer_dst->update_source); + peer_dst->update_source = NULL; + } + peer_dst->update_if = + XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, peer_src->update_if); + } + + if (peer_src->ifname) { + XFREE(MTYPE_BGP_PEER_IFNAME, peer_dst->ifname); + + peer_dst->ifname = + XSTRDUP(MTYPE_BGP_PEER_IFNAME, peer_src->ifname); + } + peer_dst->ttl = peer_src->ttl; + peer_dst->gtsm_hops = peer_src->gtsm_hops; +} + +static int bgp_peer_conf_if_to_su_update_v4(struct peer_connection *connection, + struct interface *ifp) +{ + struct connected *ifc; + struct prefix p; + uint32_t addr; + + /* If our IPv4 address on the interface is /30 or /31, we can derive the + * IPv4 address of the other end. + */ + frr_each (if_connected, ifp->connected, ifc) { + if (ifc->address && (ifc->address->family == AF_INET)) { + prefix_copy(&p, CONNECTED_PREFIX(ifc)); + if (p.prefixlen == 30) { + connection->su.sa.sa_family = AF_INET; + addr = ntohl(p.u.prefix4.s_addr); + if (addr % 4 == 1) + connection->su.sin.sin_addr.s_addr = + htonl(addr + 1); + else if (addr % 4 == 2) + connection->su.sin.sin_addr.s_addr = + htonl(addr - 1); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + connection->su.sin.sin_len = + sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + return 1; + } else if (p.prefixlen == 31) { + connection->su.sa.sa_family = AF_INET; + addr = ntohl(p.u.prefix4.s_addr); + if (addr % 2 == 0) + connection->su.sin.sin_addr.s_addr = + htonl(addr + 1); + else + connection->su.sin.sin_addr.s_addr = + htonl(addr - 1); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + connection->su.sin.sin_len = + sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + return 1; + } else if (bgp_debug_neighbor_events(connection->peer)) + zlog_debug("%s: IPv4 interface address is not /30 or /31, v4 session not started", + connection->peer->conf_if); + } + } + + return 0; +} + +static bool bgp_peer_conf_if_to_su_update_v6(struct peer_connection *connection, + struct interface *ifp) +{ + struct nbr_connected *ifc_nbr; + + /* Have we learnt the peer's IPv6 link-local address? */ + if (ifp->nbr_connected + && (ifc_nbr = listnode_head(ifp->nbr_connected))) { + connection->su.sa.sa_family = AF_INET6; + memcpy(&connection->su.sin6.sin6_addr, + &ifc_nbr->address->u.prefix, sizeof(struct in6_addr)); +#ifdef SIN6_LEN + connection->su.sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif + connection->su.sin6.sin6_scope_id = ifp->ifindex; + return true; + } + + return false; +} + +/* + * Set or reset the peer address socketunion structure based on the + * learnt/derived peer address. If the address has changed, update the + * password on the listen socket, if needed. + */ +void bgp_peer_conf_if_to_su_update(struct peer_connection *connection) +{ + struct interface *ifp; + int prev_family; + int peer_addr_updated = 0; + struct listnode *node; + union sockunion old_su; + struct peer *peer = connection->peer; + + /* + * This function is only ever needed when FRR an interface + * based peering, so this simple test will tell us if + * we are in an interface based configuration or not + */ + if (!peer->conf_if) + return; + + old_su = connection->su; + + prev_family = connection->su.sa.sa_family; + if ((ifp = if_lookup_by_name(peer->conf_if, peer->bgp->vrf_id))) { + peer->ifp = ifp; + /* If BGP unnumbered is not "v6only", we first see if we can + * derive the + * peer's IPv4 address. + */ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_IFPEER_V6ONLY)) + peer_addr_updated = + bgp_peer_conf_if_to_su_update_v4(connection, + ifp); + + /* If "v6only" or we can't derive peer's IPv4 address, see if + * we've + * learnt the peer's IPv6 link-local address. This is from the + * source + * IPv6 address in router advertisement. + */ + if (!peer_addr_updated) + peer_addr_updated = + bgp_peer_conf_if_to_su_update_v6(connection, + ifp); + } + /* If we could derive the peer address, we may need to install the + * password + * configured for the peer, if any, on the listen socket. Otherwise, + * mark + * that peer's address is not available and uninstall the password, if + * needed. + */ + if (peer_addr_updated) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD) + && prev_family == AF_UNSPEC) + bgp_md5_set(connection); + } else { + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD) + && prev_family != AF_UNSPEC) + bgp_md5_unset(connection); + connection->su.sa.sa_family = AF_UNSPEC; + memset(&connection->su.sin6.sin6_addr, 0, + sizeof(struct in6_addr)); + } + + /* + * If they are the same, nothing to do here, move along + */ + if (!sockunion_same(&old_su, &connection->su)) { + union sockunion new_su = connection->su; + struct bgp *bgp = peer->bgp; + + /* + * Our peer structure is stored in the bgp->peerhash + * release it before we modify anything in both the + * hash and the list. But *only* if the peer + * is in the bgp->peerhash as that on deletion + * we call bgp_stop which calls this function :( + * so on deletion let's remove from the list first + * and then do the deletion preventing this from + * being added back on the list below when we + * fail to remove it up here. + */ + + /* + * listnode_lookup just scans the list + * for the peer structure so it's safe + * to use without modifying the su + */ + node = listnode_lookup(bgp->peer, peer); + if (node) { + /* + * Let's reset the peer->su release and + * reset it and put it back. We have to + * do this because hash_release will + * scan through looking for a matching + * su if needed. + */ + connection->su = old_su; + hash_release(peer->bgp->peerhash, peer); + listnode_delete(peer->bgp->peer, peer); + + connection->su = new_su; + (void)hash_get(peer->bgp->peerhash, peer, + hash_alloc_intern); + listnode_add_sort(peer->bgp->peer, peer); + } + } +} + +void bgp_recalculate_afi_safi_bestpaths(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct bgp_dest *dest, *ndest; + struct bgp_path_info *pi, *next; + struct bgp_table *table; + + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + + if (!table) + continue; + + /* Special handling for 2-level routing + * tables. */ + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + for (ndest = bgp_table_top(table); ndest; + ndest = bgp_route_next(ndest)) { + for (pi = bgp_dest_get_bgp_path_info(ndest); + (pi != NULL) && (next = pi->next, 1); + pi = next) + bgp_process(bgp, ndest, pi, afi, safi); + } + } else { + for (pi = bgp_dest_get_bgp_path_info(dest); + (pi != NULL) && (next = pi->next, 1); pi = next) + bgp_process(bgp, dest, pi, afi, safi); + } + } +} + +/* Force a bestpath recalculation for all prefixes. This is used + * when 'bgp bestpath' commands are entered. + */ +void bgp_recalculate_all_bestpaths(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) { + bgp_recalculate_afi_safi_bestpaths(bgp, afi, safi); + } +} + +/* + * Create new BGP peer. + * + * conf_if and su are mutually exclusive if configuring from the cli. + * If we are handing a doppelganger, then we *must* pass in both + * the original peer's su and conf_if, so that we can appropriately + * track the bgp->peerhash( ie we don't want to remove the current + * one from the config ). + */ +struct peer *peer_create(union sockunion *su, const char *conf_if, + struct bgp *bgp, as_t local_as, as_t remote_as, + int as_type, struct peer_group *group, + bool config_node, const char *as_str) +{ + int active; + struct peer *peer; + char buf[SU_ADDRSTRLEN]; + afi_t afi; + safi_t safi; + + peer = peer_new(bgp); + if (conf_if) { + peer->conf_if = XSTRDUP(MTYPE_PEER_CONF_IF, conf_if); + if (su) + peer->connection->su = *su; + else + bgp_peer_conf_if_to_su_update(peer->connection); + XFREE(MTYPE_BGP_PEER_HOST, peer->host); + peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, conf_if); + } else if (su) { + peer->connection->su = *su; + sockunion2str(su, buf, SU_ADDRSTRLEN); + XFREE(MTYPE_BGP_PEER_HOST, peer->host); + peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, buf); + } + peer->local_as = local_as; + peer->as = remote_as; + /* internal and external values do not use as_pretty */ + if (as_str && asn_str2asn(as_str, NULL)) + peer->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_str); + peer->as_type = as_type; + peer->local_id = bgp->router_id; + peer->v_holdtime = bgp->default_holdtime; + peer->v_keepalive = bgp->default_keepalive; + peer->v_routeadv = (peer_sort(peer) == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + if (bgp_config_inprocess()) + peer->shut_during_cfg = true; + + peer = peer_lock(peer); /* bgp peer list reference */ + peer->group = group; + listnode_add_sort(bgp->peer, peer); + + if (config_node) + SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); + + (void)hash_get(bgp->peerhash, peer, hash_alloc_intern); + + /* Adjust update-group coalesce timer heuristics for # peers. */ + if (bgp->heuristic_coalesce) { + long ct = BGP_DEFAULT_SUBGROUP_COALESCE_TIME + + (bgp->peer->count + * BGP_PEER_ADJUST_SUBGROUP_COALESCE_TIME); + bgp->coalesce_time = MIN(BGP_MAX_SUBGROUP_COALESCE_TIME, ct); + } + + active = peer_active(peer); + if (!active) { + if (peer->connection->su.sa.sa_family == AF_UNSPEC) + peer->last_reset = PEER_DOWN_NBR_ADDR; + else + peer->last_reset = PEER_DOWN_NOAFI_ACTIVATED; + } + + /* Last read and reset time set */ + peer->readtime = peer->resettime = monotime(NULL); + + /* Default TTL set. */ + peer->ttl = (peer->sort == BGP_PEER_IBGP) ? MAXTTL : BGP_DEFAULT_TTL; + + /* Default configured keepalives count for shutdown rtt command */ + peer->rtt_keepalive_conf = 1; + + /* If 'bgp default -' is configured, then activate the + * neighbor for the corresponding address family. IPv4 Unicast is + * the only address family enabled by default without expliict + * configuration. + */ + FOREACH_AFI_SAFI (afi, safi) { + if (bgp->default_af[afi][safi]) { + peer->afc[afi][safi] = 1; + peer_af_create(peer, afi, safi); + } + } + + /* auto shutdown if configured */ + if (bgp->autoshutdown) + peer_flag_set(peer, PEER_FLAG_SHUTDOWN); + /* Set up peer's events and timers. */ + else if (!active && peer_active(peer)) + bgp_timer_set(peer->connection); + + bgp_peer_gr_flags_update(peer); + BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(bgp, bgp->peer); + + return peer; +} + +/* Make accept BGP peer. This function is only called from the test code */ +struct peer *peer_create_accept(struct bgp *bgp) +{ + struct peer *peer; + + peer = peer_new(bgp); + + peer = peer_lock(peer); /* bgp peer list reference */ + listnode_add_sort(bgp->peer, peer); + (void)hash_get(bgp->peerhash, peer, hash_alloc_intern); + + return peer; +} + +/* + * Return true if we have a peer configured to use this afi/safi + */ +bool bgp_afi_safi_peer_exists(struct bgp *bgp, afi_t afi, safi_t safi) +{ + struct listnode *node; + struct peer *peer; + + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + continue; + + if (peer->afc[afi][safi]) + return true; + } + + return false; +} + +/* Change peer's AS number. */ +void peer_as_change(struct peer *peer, as_t as, int as_specified, + const char *as_str) +{ + enum bgp_peer_sort origtype, newtype; + + /* Stop peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_REMOTE_AS_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); + } + origtype = peer_sort_lookup(peer); + peer->as = as; + if (as_specified == AS_SPECIFIED && as_str) { + if (peer->as_pretty) + XFREE(MTYPE_BGP_NAME, peer->as_pretty); + peer->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_str); + } else if (peer->as_type == AS_UNSPECIFIED && peer->as_pretty) + XFREE(MTYPE_BGP_NAME, peer->as_pretty); + peer->as_type = as_specified; + + if (bgp_config_check(peer->bgp, BGP_CONFIG_CONFEDERATION) + && !bgp_confederation_peers_check(peer->bgp, as) + && peer->bgp->as != as) + peer->local_as = peer->bgp->confed_id; + else + peer->local_as = peer->bgp->as; + + newtype = peer_sort(peer); + /* Advertisement-interval reset */ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_ROUTEADV)) { + peer->v_routeadv = (newtype == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + } + + /* TTL reset */ + if (newtype == BGP_PEER_IBGP) + peer->ttl = MAXTTL; + else if (origtype == BGP_PEER_IBGP) + peer->ttl = BGP_DEFAULT_TTL; + + /* reflector-client reset */ + if (newtype != BGP_PEER_IBGP) { + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_UNICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_MULTICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_LABELED_UNICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_MPLS_VPN], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_ENCAP], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP][SAFI_FLOWSPEC], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_UNICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_MULTICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_LABELED_UNICAST], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_MPLS_VPN], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_ENCAP], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_IP6][SAFI_FLOWSPEC], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_L2VPN][SAFI_EVPN], + PEER_FLAG_REFLECTOR_CLIENT); + } +} + +/* If peer does not exist, create new one. If peer already exists, + set AS number to the peer. */ +int peer_remote_as(struct bgp *bgp, union sockunion *su, const char *conf_if, + as_t *as, int as_type, const char *as_str) +{ + struct peer *peer; + as_t local_as; + + if (conf_if) + peer = peer_lookup_by_conf_if(bgp, conf_if); + else + peer = peer_lookup(bgp, su); + + if (peer) { + /* Not allowed for a dynamic peer. */ + if (peer_dynamic_neighbor(peer)) { + *as = peer->as; + return BGP_ERR_INVALID_FOR_DYNAMIC_PEER; + } + + /* When this peer is a member of peer-group. */ + if (peer->group) { + /* peer-group already has AS number/internal/external */ + if (peer->group->conf->as + || peer->group->conf->as_type) { + /* Return peer group's AS number. */ + *as = peer->group->conf->as; + return BGP_ERR_PEER_GROUP_MEMBER; + } + + enum bgp_peer_sort peer_sort_type = + peer_sort(peer->group->conf); + + /* Explicit AS numbers used, compare AS numbers */ + if (as_type == AS_SPECIFIED) { + if (((peer_sort_type == BGP_PEER_IBGP) + && (bgp->as != *as)) + || ((peer_sort_type == BGP_PEER_EBGP) + && (bgp->as == *as))) { + *as = peer->as; + return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; + } + } else { + /* internal/external used, compare as-types */ + if (((peer_sort_type == BGP_PEER_IBGP) + && (as_type != AS_INTERNAL)) + || ((peer_sort_type == BGP_PEER_EBGP) + && (as_type != AS_EXTERNAL))) { + *as = peer->as; + return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; + } + } + } + + /* Existing peer's AS number change. */ + if (((peer->as_type == AS_SPECIFIED) && peer->as != *as) + || (peer->as_type != as_type)) + peer_as_change(peer, *as, as_type, as_str); + } else { + if (conf_if) + return BGP_ERR_NO_INTERFACE_CONFIG; + + /* If the peer is not part of our confederation, and its not an + iBGP peer then spoof the source AS */ + if (bgp_config_check(bgp, BGP_CONFIG_CONFEDERATION) && + !bgp_confederation_peers_check(bgp, *as) && *as && + bgp->as != *as) + local_as = bgp->confed_id; + else + local_as = bgp->as; + + peer_create(su, conf_if, bgp, local_as, *as, as_type, NULL, + true, as_str); + } + + return 0; +} + +const char *bgp_get_name_by_role(uint8_t role) +{ + switch (role) { + case ROLE_PROVIDER: + return "provider"; + case ROLE_RS_SERVER: + return "rs-server"; + case ROLE_RS_CLIENT: + return "rs-client"; + case ROLE_CUSTOMER: + return "customer"; + case ROLE_PEER: + return "peer"; + case ROLE_UNDEFINED: + return "undefined"; + } + return "unknown"; +} + +enum asnotation_mode bgp_get_asnotation(struct bgp *bgp) +{ + if (!bgp) + return ASNOTATION_PLAIN; + return bgp->asnotation; +} + +static void peer_group2peer_config_copy_af(struct peer_group *group, + struct peer *peer, afi_t afi, + safi_t safi) +{ + int in = FILTER_IN; + int out = FILTER_OUT; + uint64_t flags_tmp; + uint64_t pflags_ovrd; + uint8_t *pfilter_ovrd; + struct peer *conf; + + conf = group->conf; + pflags_ovrd = peer->af_flags_override[afi][safi]; + pfilter_ovrd = &peer->filter_override[afi][safi][in]; + + /* peer af_flags apply */ + flags_tmp = conf->af_flags[afi][safi] & ~pflags_ovrd; + flags_tmp ^= conf->af_flags_invert[afi][safi] + ^ peer->af_flags_invert[afi][safi]; + flags_tmp &= ~pflags_ovrd; + + UNSET_FLAG(peer->af_flags[afi][safi], ~pflags_ovrd); + SET_FLAG(peer->af_flags[afi][safi], flags_tmp); + SET_FLAG(peer->af_flags_invert[afi][safi], + conf->af_flags_invert[afi][safi]); + + /* maximum-prefix */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_MAX_PREFIX)) { + PEER_ATTR_INHERIT(peer, group, pmax[afi][safi]); + PEER_ATTR_INHERIT(peer, group, pmax_threshold[afi][safi]); + PEER_ATTR_INHERIT(peer, group, pmax_restart[afi][safi]); + } + + /* maximum-prefix-out */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_MAX_PREFIX_OUT)) + PEER_ATTR_INHERIT(peer, group, pmax_out[afi][safi]); + + /* allowas-in */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_ALLOWAS_IN)) + PEER_ATTR_INHERIT(peer, group, allowas_in[afi][safi]); + + /* soo */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_SOO)) + PEER_ATTR_INHERIT(peer, group, soo[afi][safi]); + + /* weight */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_WEIGHT)) + PEER_ATTR_INHERIT(peer, group, weight[afi][safi]); + + /* default-originate route-map */ + if (!CHECK_FLAG(pflags_ovrd, PEER_FLAG_DEFAULT_ORIGINATE)) { + PEER_STR_ATTR_INHERIT(peer, group, default_rmap[afi][safi].name, + MTYPE_ROUTE_MAP_NAME); + PEER_ATTR_INHERIT(peer, group, default_rmap[afi][safi].map); + } + + /* inbound filter apply */ + if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_DISTRIBUTE_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].dlist[in].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].dlist[in].alist); + } + + if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_PREFIX_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].plist[in].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].plist[in].plist); + } + + if (!CHECK_FLAG(pfilter_ovrd[in], PEER_FT_FILTER_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].aslist[in].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].aslist[in].aslist); + } + + if (!CHECK_FLAG(pfilter_ovrd[RMAP_IN], PEER_FT_ROUTE_MAP)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].map[in].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].map[RMAP_IN].map); + } + + /* outbound filter apply */ + if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_DISTRIBUTE_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].dlist[out].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].dlist[out].alist); + } + + if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_PREFIX_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].plist[out].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].plist[out].plist); + } + + if (!CHECK_FLAG(pfilter_ovrd[out], PEER_FT_FILTER_LIST)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].aslist[out].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].aslist[out].aslist); + } + + if (!CHECK_FLAG(pfilter_ovrd[RMAP_OUT], PEER_FT_ROUTE_MAP)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].map[RMAP_OUT].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].map[RMAP_OUT].map); + } + + /* nondirectional filter apply */ + if (!CHECK_FLAG(pfilter_ovrd[0], PEER_FT_UNSUPPRESS_MAP)) { + PEER_STR_ATTR_INHERIT(peer, group, filter[afi][safi].usmap.name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, filter[afi][safi].usmap.map); + } + + /* Conditional Advertisements */ + if (!CHECK_FLAG(pfilter_ovrd[RMAP_OUT], PEER_FT_ADVERTISE_MAP)) { + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].advmap.aname, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, filter[afi][safi].advmap.amap); + PEER_STR_ATTR_INHERIT(peer, group, + filter[afi][safi].advmap.cname, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, group, filter[afi][safi].advmap.cmap); + PEER_ATTR_INHERIT(peer, group, + filter[afi][safi].advmap.condition); + } + + if (peer->addpath_type[afi][safi] == BGP_ADDPATH_NONE) { + peer->addpath_type[afi][safi] = conf->addpath_type[afi][safi]; + bgp_addpath_type_changed(conf->bgp); + } +} + +static int peer_activate_af(struct peer *peer, afi_t afi, safi_t safi) +{ + int active; + struct peer *other; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + flog_err(EC_BGP_PEER_GROUP, "%s was called for peer-group %s", + __func__, peer->host); + return 1; + } + + /* Do not activate a peer for both SAFI_UNICAST and SAFI_LABELED_UNICAST + */ + if ((safi == SAFI_UNICAST && peer->afc[afi][SAFI_LABELED_UNICAST]) + || (safi == SAFI_LABELED_UNICAST && peer->afc[afi][SAFI_UNICAST])) + return BGP_ERR_PEER_SAFI_CONFLICT; + + /* Nothing to do if we've already activated this peer */ + if (peer->afc[afi][safi]) + return 0; + + if (peer_af_create(peer, afi, safi) == NULL) + return 1; + + active = peer_active(peer); + peer->afc[afi][safi] = 1; + + if (peer->group) + peer_group2peer_config_copy_af(peer->group, peer, afi, safi); + + if (!active && peer_active(peer)) { + bgp_timer_set(peer->connection); + } else { + if (peer_established(peer->connection)) { + if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV)) { + peer->afc_adv[afi][safi] = 1; + bgp_capability_send(peer, afi, safi, + CAPABILITY_CODE_MP, + CAPABILITY_ACTION_SET); + if (peer->afc_recv[afi][safi]) { + peer->afc_nego[afi][safi] = 1; + bgp_announce_route(peer, afi, safi, + false); + } + } else { + peer->last_reset = PEER_DOWN_AF_ACTIVATE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + if (peer->connection->status == OpenSent || + peer->connection->status == OpenConfirm) { + peer->last_reset = PEER_DOWN_AF_ACTIVATE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + /* + * If we are turning on a AFI/SAFI locally and we've + * started bringing a peer up, we need to tell + * the other peer to restart because we might loose + * configuration here because when the doppelganger + * gets to a established state due to how + * we resolve we could just overwrite the afi/safi + * activation. + */ + other = peer->doppelganger; + if (other && (other->connection->status == OpenSent || + other->connection->status == OpenConfirm)) { + other->last_reset = PEER_DOWN_AF_ACTIVATE; + bgp_notify_send(other->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + + return 0; +} + +/* Activate the peer or peer group for specified AFI and SAFI. */ +int peer_activate(struct peer *peer, afi_t afi, safi_t safi) +{ + int ret = 0; + struct peer_group *group; + struct listnode *node, *nnode; + struct peer *tmp_peer; + struct bgp *bgp; + safi_t safi_check; + + /* Nothing to do if we've already activated this peer */ + if (peer->afc[afi][safi]) + return ret; + + bgp = peer->bgp; + + /* This is a peer-group so activate all of the members of the + * peer-group as well */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + + /* Do not activate a peer for both SAFI_UNICAST and + * SAFI_LABELED_UNICAST */ + if ((safi == SAFI_UNICAST + && peer->afc[afi][SAFI_LABELED_UNICAST]) + || (safi == SAFI_LABELED_UNICAST + && peer->afc[afi][SAFI_UNICAST])) + return BGP_ERR_PEER_SAFI_CONFLICT; + + peer->afc[afi][safi] = 1; + group = peer->group; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, tmp_peer)) { + ret |= peer_activate_af(tmp_peer, afi, safi); + } + } else { + ret |= peer_activate_af(peer, afi, safi); + } + + /* If this is the first peer to be activated for this + * afi/labeled-unicast or afi/mpls-vpn, recalc bestpaths to trigger + * label allocation */ + if (safi == SAFI_LABELED_UNICAST) + safi_check = SAFI_UNICAST; + else + safi_check = safi; + if (ret != BGP_ERR_PEER_SAFI_CONFLICT && + (safi == SAFI_LABELED_UNICAST || safi == SAFI_MPLS_VPN) && + !bgp->allocate_mpls_labels[afi][safi_check]) { + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "peer(s) are now active for %s, allocate MPLS labels", + safi2str(safi)); + bgp->allocate_mpls_labels[afi][safi_check] = 1; + bgp_recalculate_afi_safi_bestpaths(bgp, afi, safi_check); + } + + if (safi == SAFI_FLOWSPEC) { + /* connect to table manager */ + bgp_zebra_init_tm_connect(bgp); + } + return ret; +} + +static bool non_peergroup_deactivate_af(struct peer *peer, afi_t afi, + safi_t safi) +{ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + flog_err(EC_BGP_PEER_GROUP, "%s was called for peer-group %s", + __func__, peer->host); + return true; + } + + /* Nothing to do if we've already deactivated this peer */ + if (!peer->afc[afi][safi]) + return false; + + /* De-activate the address family configuration. */ + peer->afc[afi][safi] = 0; + + if (peer_af_delete(peer, afi, safi) != 0) { + flog_err(EC_BGP_PEER_DELETE, + "couldn't delete af structure for peer %s(%s, %s)", + peer->host, afi2str(afi), safi2str(safi)); + return true; + } + + if (peer_established(peer->connection)) { + if (CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV)) { + peer->afc_adv[afi][safi] = 0; + peer->afc_nego[afi][safi] = 0; + + if (peer_active_nego(peer)) { + bgp_capability_send(peer, afi, safi, + CAPABILITY_CODE_MP, + CAPABILITY_ACTION_UNSET); + bgp_clear_route(peer, afi, safi); + peer->pcount[afi][safi] = 0; + } else { + peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } else { + peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } + + return false; +} + +int peer_deactivate(struct peer *peer, afi_t afi, safi_t safi) +{ + int ret = 0; + struct peer_group *group; + struct peer *tmp_peer; + struct listnode *node, *nnode; + struct bgp *bgp; + safi_t safi_check; + + /* Nothing to do if we've already de-activated this peer */ + if (!peer->afc[afi][safi]) + return ret; + + /* This is a peer-group so de-activate all of the members of the + * peer-group as well */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + peer->afc[afi][safi] = 0; + group = peer->group; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, tmp_peer)) { + ret |= non_peergroup_deactivate_af(tmp_peer, afi, safi); + } + } else { + ret |= non_peergroup_deactivate_af(peer, afi, safi); + } + + bgp = peer->bgp; + + /* If this is the last peer to be deactivated for this + * afi/labeled-unicast or afi/mpls-vpn, recalc bestpaths to trigger + * label deallocation */ + if (safi == SAFI_LABELED_UNICAST) + safi_check = SAFI_UNICAST; + else + safi_check = safi; + if ((safi == SAFI_LABELED_UNICAST || safi == SAFI_MPLS_VPN) && + bgp->allocate_mpls_labels[afi][safi_check] && + !bgp_afi_safi_peer_exists(bgp, afi, safi)) { + + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "peer(s) are no longer active for %s, deallocate MPLS labels", + safi2str(safi)); + bgp->allocate_mpls_labels[afi][safi_check] = 0; + bgp_recalculate_afi_safi_bestpaths(bgp, afi, safi_check); + } + return ret; +} + +void peer_nsf_stop(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT); + UNSET_FLAG(peer->sflags, PEER_STATUS_NSF_MODE); + + FOREACH_AFI_SAFI_NSF (afi, safi) { + peer->nsf[afi][safi] = 0; + EVENT_OFF(peer->t_llgr_stale[afi][safi]); + } + + if (peer->connection->t_gr_restart) { + EVENT_OFF(peer->connection->t_gr_restart); + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%pBP graceful restart timer stopped", peer); + } + if (peer->connection->t_gr_stale) { + EVENT_OFF(peer->connection->t_gr_stale); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP graceful restart stalepath timer stopped", + peer); + } + bgp_clear_route_all(peer); +} + +/* Delete peer from confguration. + * + * The peer is moved to a dead-end "Deleted" neighbour-state, to allow + * it to "cool off" and refcounts to hit 0, at which state it is freed. + * + * This function /should/ take care to be idempotent, to guard against + * it being called multiple times through stray events that come in + * that happen to result in this function being called again. That + * said, getting here for a "Deleted" peer is a bug in the neighbour + * FSM. + */ +int peer_delete(struct peer *peer) +{ + int i; + afi_t afi; + safi_t safi; + struct bgp *bgp; + struct bgp_filter *filter; + struct listnode *pn; + int accept_peer; + + assert(peer->connection->status != Deleted); + + bgp = peer->bgp; + accept_peer = CHECK_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); + + bgp_soft_reconfig_table_task_cancel(bgp, NULL, peer); + + bgp_keepalives_off(peer->connection); + bgp_reads_off(peer->connection); + bgp_writes_off(peer->connection); + event_cancel_event_ready(bm->master, peer->connection); + FOREACH_AFI_SAFI (afi, safi) + EVENT_OFF(peer->t_revalidate_all[afi][safi]); + assert(!CHECK_FLAG(peer->connection->thread_flags, + PEER_THREAD_WRITES_ON)); + assert(!CHECK_FLAG(peer->connection->thread_flags, + PEER_THREAD_READS_ON)); + assert(!CHECK_FLAG(peer->thread_flags, PEER_THREAD_KEEPALIVES_ON)); + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) + peer_nsf_stop(peer); + + SET_FLAG(peer->flags, PEER_FLAG_DELETE); + + /* Remove BFD settings. */ + if (peer->bfd_config) + bgp_peer_remove_bfd_config(peer); + + /* Delete peer route flap dampening configuration. This needs to happen + * before removing the peer from peer groups. + */ + FOREACH_AFI_SAFI (afi, safi) + if (peer_af_flag_check(peer, afi, safi, + PEER_FLAG_CONFIG_DAMPENING)) + bgp_peer_damp_disable(peer, afi, safi); + + /* If this peer belongs to peer group, clear up the + relationship. */ + if (peer->group) { + if (peer_dynamic_neighbor(peer)) + peer_drop_dynamic_neighbor(peer); + + if ((pn = listnode_lookup(peer->group->peer, peer))) { + peer = peer_unlock( + peer); /* group->peer list reference */ + list_delete_node(peer->group->peer, pn); + } + peer->group = NULL; + } + + /* Withdraw all information from routing table. We can not use + * BGP_EVENT_ADD (peer, BGP_Stop) at here. Because the event is + * executed after peer structure is deleted. + */ + peer->last_reset = PEER_DOWN_NEIGHBOR_DELETE; + bgp_stop(peer->connection); + UNSET_FLAG(peer->flags, PEER_FLAG_DELETE); + + if (peer->doppelganger) { + peer->doppelganger->doppelganger = NULL; + peer->doppelganger = NULL; + } + + UNSET_FLAG(peer->sflags, PEER_STATUS_ACCEPT_PEER); + bgp_fsm_change_status(peer->connection, Deleted); + + /* Remove from NHT */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE)) + bgp_unlink_nexthop_by_peer(peer); + + /* Password configuration */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD)) { + XFREE(MTYPE_PEER_PASSWORD, peer->password); + if (!accept_peer && + !BGP_CONNECTION_SU_UNSPEC(peer->connection) && + !CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && + !CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_NEIGHBOR)) + bgp_md5_unset(peer->connection); + } + + bgp_timer_set(peer->connection); /* stops all timers for Deleted */ + + /* Delete from all peer list. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) + && (pn = listnode_lookup(bgp->peer, peer))) { + /* + * Removing from the list node first because + * peer_unlock *can* call peer_delete( I know, + * I know ). So let's remove it and in + * the su recalculate function we'll ensure + * it's in there or not. + */ + list_delete_node(bgp->peer, pn); + hash_release(bgp->peerhash, peer); + peer_unlock(peer); /* bgp peer list reference */ + } + + /* Local and remote addresses. */ + if (peer->su_local) { + sockunion_free(peer->su_local); + peer->su_local = NULL; + } + + if (peer->su_remote) { + sockunion_free(peer->su_remote); + peer->su_remote = NULL; + } + + /* Free filter related memory. */ + FOREACH_AFI_SAFI (afi, safi) { + filter = &peer->filter[afi][safi]; + + for (i = FILTER_IN; i < FILTER_MAX; i++) { + XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[i].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[i].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[i].name); + } + + for (i = RMAP_IN; i < RMAP_MAX; i++) { + XFREE(MTYPE_BGP_FILTER_NAME, filter->map[i].name); + } + + XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); + XFREE(MTYPE_ROUTE_MAP_NAME, peer->default_rmap[afi][safi].name); + ecommunity_free(&peer->soo[afi][safi]); + } + + FOREACH_AFI_SAFI (afi, safi) + peer_af_delete(peer, afi, safi); + + XFREE(MTYPE_BGP_PEER_HOST, peer->hostname); + XFREE(MTYPE_BGP_PEER_HOST, peer->domainname); + XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version); + + peer_unlock(peer); /* initial reference */ + + return 0; +} + +static int peer_group_cmp(struct peer_group *g1, struct peer_group *g2) +{ + return strcmp(g1->name, g2->name); +} + +/* Peer group cofiguration. */ +static struct peer_group *peer_group_new(void) +{ + return XCALLOC(MTYPE_PEER_GROUP, sizeof(struct peer_group)); +} + +static void peer_group_free(struct peer_group *group) +{ + XFREE(MTYPE_PEER_GROUP, group); +} + +struct peer_group *peer_group_lookup(struct bgp *bgp, const char *name) +{ + struct peer_group *group; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + if (strcmp(group->name, name) == 0) + return group; + } + return NULL; +} + +struct peer_group *peer_group_get(struct bgp *bgp, const char *name) +{ + struct peer_group *group; + afi_t afi; + safi_t safi; + + group = peer_group_lookup(bgp, name); + if (group) + return group; + + group = peer_group_new(); + group->bgp = bgp; + XFREE(MTYPE_PEER_GROUP_HOST, group->name); + group->name = XSTRDUP(MTYPE_PEER_GROUP_HOST, name); + group->peer = list_new(); + for (afi = AFI_IP; afi < AFI_MAX; afi++) + group->listen_range[afi] = list_new(); + group->conf = peer_new(bgp); + FOREACH_AFI_SAFI (afi, safi) { + if (bgp->default_af[afi][safi]) + group->conf->afc[afi][safi] = 1; + } + XFREE(MTYPE_BGP_PEER_HOST, group->conf->host); + group->conf->host = XSTRDUP(MTYPE_BGP_PEER_HOST, name); + group->conf->group = group; + group->conf->as = 0; + group->conf->ttl = BGP_DEFAULT_TTL; + group->conf->gtsm_hops = BGP_GTSM_HOPS_DISABLED; + group->conf->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV; + SET_FLAG(group->conf->sflags, PEER_STATUS_GROUP); + listnode_add_sort(bgp->group, group); + + return group; +} + +static void peer_group2peer_config_copy(struct peer_group *group, + struct peer *peer) +{ + uint64_t flags_tmp; + struct peer *conf; + bool config_node = !!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); + + conf = group->conf; + + /* remote-as */ + if (conf->as) + peer->as = conf->as; + + /* local-as */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_LOCAL_AS)) + peer->change_local_as = conf->change_local_as; + + /* If peer-group has configured TTL then override it */ + if (conf->ttl != BGP_DEFAULT_TTL) + peer->ttl = conf->ttl; + + /* GTSM hops */ + peer->gtsm_hops = conf->gtsm_hops; + + /* peer flags apply */ + flags_tmp = conf->flags & ~peer->flags_override; + flags_tmp ^= conf->flags_invert ^ peer->flags_invert; + flags_tmp &= ~peer->flags_override; + + UNSET_FLAG(peer->flags, ~peer->flags_override); + SET_FLAG(peer->flags, flags_tmp); + SET_FLAG(peer->flags_invert, conf->flags_invert); + + if (config_node) + SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); + + /* peer timers apply */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_TIMER)) { + PEER_ATTR_INHERIT(peer, group, holdtime); + PEER_ATTR_INHERIT(peer, group, keepalive); + } + + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_TIMER_CONNECT)) { + PEER_ATTR_INHERIT(peer, group, connect); + if (CHECK_FLAG(conf->flags, PEER_FLAG_TIMER_CONNECT)) + peer->v_connect = conf->connect; + else + peer->v_connect = peer->bgp->default_connect_retry; + } + + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_TIMER_DELAYOPEN)) { + PEER_ATTR_INHERIT(peer, group, delayopen); + if (CHECK_FLAG(conf->flags, PEER_FLAG_TIMER_DELAYOPEN)) + peer->v_delayopen = conf->delayopen; + else + peer->v_delayopen = peer->bgp->default_delayopen; + } + + /* advertisement-interval apply */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_ROUTEADV)) { + PEER_ATTR_INHERIT(peer, group, routeadv); + if (CHECK_FLAG(conf->flags, PEER_FLAG_ROUTEADV)) + peer->v_routeadv = conf->routeadv; + else + peer->v_routeadv = (peer_sort(peer) == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + } + + /* capability extended-nexthop apply */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_CAPABILITY_ENHE)) + if (CHECK_FLAG(conf->flags, PEER_FLAG_CAPABILITY_ENHE)) + SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE); + + /* capability software-version apply */ + if (!CHECK_FLAG(peer->flags_override, + PEER_FLAG_CAPABILITY_SOFT_VERSION)) + if (CHECK_FLAG(conf->flags, PEER_FLAG_CAPABILITY_SOFT_VERSION)) + SET_FLAG(peer->flags, + PEER_FLAG_CAPABILITY_SOFT_VERSION); + + /* capability dynamic apply */ + if (!CHECK_FLAG(peer->flags_override, + PEER_FLAG_DYNAMIC_CAPABILITY)) + if (CHECK_FLAG(conf->flags, PEER_FLAG_DYNAMIC_CAPABILITY)) + SET_FLAG(peer->flags, + PEER_FLAG_DYNAMIC_CAPABILITY); + + /* password apply */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_PASSWORD)) + PEER_STR_ATTR_INHERIT(peer, group, password, + MTYPE_PEER_PASSWORD); + + if (!BGP_CONNECTION_SU_UNSPEC(peer->connection)) + bgp_md5_set(peer->connection); + + /* update-source apply */ + if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_UPDATE_SOURCE)) { + if (conf->update_source) { + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); + PEER_SU_ATTR_INHERIT(peer, group, update_source); + } else if (conf->update_if) { + sockunion_free(peer->update_source); + PEER_STR_ATTR_INHERIT(peer, group, update_if, + MTYPE_PEER_UPDATE_SOURCE); + } + } + + /* role */ + PEER_ATTR_INHERIT(peer, group, local_role); + + /* Update GR flags for the peer. */ + bgp_peer_gr_flags_update(peer); + + /* Apply BFD settings from group to peer if it exists. */ + if (conf->bfd_config) { + bgp_peer_configure_bfd(peer, false); + bgp_peer_config_apply(peer, group); + } +} + +/* Peer group's remote AS configuration. */ +int peer_group_remote_as(struct bgp *bgp, const char *group_name, as_t *as, + int as_type, const char *as_str) +{ + struct peer_group *group; + struct peer *peer; + struct listnode *node, *nnode; + + group = peer_group_lookup(bgp, group_name); + if (!group) + return -1; + + if ((as_type == group->conf->as_type) && (group->conf->as == *as)) + return 0; + + + /* When we setup peer-group AS number all peer group member's AS + number must be updated to same number. */ + peer_as_change(group->conf, *as, as_type, as_str); + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (((peer->as_type == AS_SPECIFIED) && peer->as != *as) || + (peer->as_type != as_type)) { + peer_as_change(peer, *as, as_type, as_str); + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s peer %s set to as_type %u curr status %s trigger BGP_Start", + __func__, peer->host, peer->as_type, + lookup_msg(bgp_status_msg, + peer->connection->status, NULL)); + /* Start Peer FSM to form neighbor using new as, + * NOTE: the connection is triggered upon start + * timer expiry. + */ + if (!BGP_PEER_START_SUPPRESSED(peer)) + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + } + + return 0; +} + +void peer_notify_unconfig(struct peer *peer) +{ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_PEER_UNCONFIG); +} + +static void peer_notify_shutdown(struct peer *peer) +{ + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP configured Graceful-Restart, skipping shutdown notification", + peer); + return; + } + + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); +} + +void peer_group_notify_unconfig(struct peer_group *group) +{ + struct peer *peer, *other; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + other = peer->doppelganger; + if (other && other->connection->status != Deleted) { + other->group = NULL; + peer_notify_unconfig(other); + } else + peer_notify_unconfig(peer); + } +} + +int peer_group_delete(struct peer_group *group) +{ + struct bgp *bgp; + struct peer *peer; + struct prefix *prefix; + struct peer *other; + struct listnode *node, *nnode; + afi_t afi; + + bgp = group->bgp; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + other = peer->doppelganger; + + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE)) + bgp_zebra_terminate_radv(bgp, peer); + + peer_delete(peer); + if (other && other->connection->status != Deleted) { + other->group = NULL; + peer_delete(other); + } + } + list_delete(&group->peer); + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, + prefix)) { + prefix_free(&prefix); + } + list_delete(&group->listen_range[afi]); + } + + XFREE(MTYPE_PEER_GROUP_HOST, group->name); + group->name = NULL; + + if (group->conf->bfd_config) + bgp_peer_remove_bfd_config(group->conf); + + group->conf->group = NULL; + peer_delete(group->conf); + + /* Delete from all peer_group list. */ + listnode_delete(bgp->group, group); + + peer_group_free(group); + + return 0; +} + +int peer_group_remote_as_delete(struct peer_group *group) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if ((group->conf->as_type == AS_UNSPECIFIED) + || ((!group->conf->as) && (group->conf->as_type == AS_SPECIFIED))) + return 0; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (CHECK_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE)) + bgp_zebra_terminate_radv(peer->bgp, peer); + + /* reset existing peer connection */ + peer_as_change(peer, 0, AS_UNSPECIFIED, NULL); + } + + group->conf->as = 0; + group->conf->as_type = AS_UNSPECIFIED; + + return 0; +} + +int peer_group_listen_range_add(struct peer_group *group, struct prefix *range) +{ + struct prefix *prefix; + struct listnode *node, *nnode; + afi_t afi; + + afi = family2afi(range->family); + + /* Group needs remote AS configured. */ + if (group->conf->as_type == AS_UNSPECIFIED) + return BGP_ERR_PEER_GROUP_NO_REMOTE_AS; + + /* Ensure no duplicates. Currently we don't care about overlaps. */ + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, prefix)) { + if (prefix_same(range, prefix)) + return 0; + } + + prefix = prefix_new(); + prefix_copy(prefix, range); + listnode_add(group->listen_range[afi], prefix); + + /* Update passwords for new ranges */ + if (group->conf->password) + bgp_md5_set_prefix(group->bgp, prefix, group->conf->password); + + return 0; +} + +int peer_group_listen_range_del(struct peer_group *group, struct prefix *range) +{ + struct prefix *prefix, prefix2; + struct listnode *node, *nnode; + struct peer *peer; + afi_t afi; + + afi = family2afi(range->family); + + /* Identify the listen range. */ + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, prefix)) { + if (prefix_same(range, prefix)) + break; + } + + if (!prefix) + return BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_NOT_FOUND; + + /* Dispose off any dynamic neighbors that exist due to this listen range + */ + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (!peer_dynamic_neighbor(peer)) + continue; + + if (sockunion2hostprefix(&peer->connection->su, &prefix2) && + prefix_match(prefix, &prefix2)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "Deleting dynamic neighbor %s group %s upon delete of listen range %pFX", + peer->host, group->name, prefix); + peer_delete(peer); + } + } + + /* Get rid of the listen range */ + listnode_delete(group->listen_range[afi], prefix); + + /* Remove passwords for deleted ranges */ + if (group->conf->password) + bgp_md5_unset_prefix(group->bgp, prefix); + + return 0; +} + +/* Bind specified peer to peer group. */ +int peer_group_bind(struct bgp *bgp, union sockunion *su, struct peer *peer, + struct peer_group *group, as_t *as) +{ + int first_member = 0; + afi_t afi; + safi_t safi; + enum bgp_peer_sort ptype, gtype; + + /* Lookup the peer. */ + if (!peer) + peer = peer_lookup(bgp, su); + + /* The peer exist, bind it to the peer-group */ + if (peer) { + /* When the peer already belongs to a peer-group, check the + * consistency. */ + if (peer_group_active(peer)) { + + /* The peer is already bound to the peer-group, + * nothing to do + */ + if (strcmp(peer->group->name, group->name) == 0) + return 0; + else + return BGP_ERR_PEER_GROUP_CANT_CHANGE; + } + + /* The peer has not specified a remote-as, inherit it from the + * peer-group */ + if (peer->as_type == AS_UNSPECIFIED) { + peer->as_type = group->conf->as_type; + peer->as = group->conf->as; + peer->sort = group->conf->sort; + peer->sub_sort = group->conf->sub_sort; + } + + ptype = peer_sort(peer); + if (!group->conf->as && ptype != BGP_PEER_UNSPECIFIED) { + gtype = peer_sort(group->conf); + if ((gtype != BGP_PEER_INTERNAL) && (gtype != ptype)) { + if (as) + *as = peer->as; + return BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT; + } + + if (gtype == BGP_PEER_INTERNAL) + first_member = 1; + } + + peer_group2peer_config_copy(group, peer); + + FOREACH_AFI_SAFI (afi, safi) { + if (group->conf->afc[afi][safi]) { + peer->afc[afi][safi] = 1; + + if (peer_af_find(peer, afi, safi) + || peer_af_create(peer, afi, safi)) { + peer_group2peer_config_copy_af( + group, peer, afi, safi); + } + } else if (peer->afc[afi][safi]) + peer_deactivate(peer, afi, safi); + } + + if (peer->group) { + assert(group && peer->group == group); + } else { + listnode_delete(bgp->peer, peer); + + peer->group = group; + listnode_add_sort(bgp->peer, peer); + + peer = peer_lock(peer); /* group->peer list reference */ + listnode_add(group->peer, peer); + } + + if (first_member) { + gtype = peer_sort(group->conf); + /* Advertisement-interval reset */ + if (!CHECK_FLAG(group->conf->flags, + PEER_FLAG_ROUTEADV)) { + group->conf->v_routeadv = + (gtype == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + } + + /* ebgp-multihop reset */ + if (gtype == BGP_PEER_IBGP) + group->conf->ttl = MAXTTL; + } + + SET_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE); + + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_RMAP_BIND; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else { + bgp_session_reset(peer); + } + } + + /* Create a new peer. */ + else { + if ((group->conf->as_type == AS_SPECIFIED) + && (!group->conf->as)) { + return BGP_ERR_PEER_GROUP_NO_REMOTE_AS; + } + + peer = peer_create(su, NULL, bgp, bgp->as, group->conf->as, + group->conf->as_type, group, true, NULL); + + peer = peer_lock(peer); /* group->peer list reference */ + listnode_add(group->peer, peer); + + peer_group2peer_config_copy(group, peer); + + /* If the peer-group is active for this afi/safi then activate + * for this peer */ + FOREACH_AFI_SAFI (afi, safi) { + if (group->conf->afc[afi][safi]) { + peer->afc[afi][safi] = 1; + + if (!peer_af_find(peer, afi, safi)) + peer_af_create(peer, afi, safi); + + peer_group2peer_config_copy_af(group, peer, afi, + safi); + } else if (peer->afc[afi][safi]) + peer_deactivate(peer, afi, safi); + } + + /* Set up peer's events and timers. */ + if (peer_active(peer)) + bgp_timer_set(peer->connection); + } + + return 0; +} + +static void bgp_startup_timer_expire(struct event *thread) +{ + struct bgp *bgp; + + bgp = EVENT_ARG(thread); + bgp->t_startup = NULL; +} + +/* + * On shutdown we call the cleanup function which + * does a free of the link list nodes, free up + * the data we are pointing at too. + */ +static void bgp_vrf_string_name_delete(void *data) +{ + char *vname = data; + + XFREE(MTYPE_TMP, vname); +} + +/* BGP instance creation by `router bgp' commands. */ +static struct bgp *bgp_create(as_t *as, const char *name, + enum bgp_instance_type inst_type, + const char *as_pretty, + enum asnotation_mode asnotation) +{ + struct bgp *bgp; + afi_t afi; + safi_t safi; + + bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp)); + bgp->as = *as; + if (as_pretty) + bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_pretty); + else + bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, asn_asn2asplain(*as)); + + if (asnotation != ASNOTATION_UNDEFINED) { + bgp->asnotation = asnotation; + SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION); + } else + asn_str2asn_notation(bgp->as_pretty, NULL, &bgp->asnotation); + + if (BGP_DEBUG(zebra, ZEBRA)) { + if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) + zlog_debug("Creating Default VRF, AS %s", + bgp->as_pretty); + else + zlog_debug("Creating %s %s, AS %s", + (inst_type == BGP_INSTANCE_TYPE_VRF) + ? "VRF" + : "VIEW", + name, bgp->as_pretty); + } + + /* Default the EVPN VRF to the default one */ + if (inst_type == BGP_INSTANCE_TYPE_DEFAULT && !bgp_master.bgp_evpn) { + bgp_lock(bgp); + bm->bgp_evpn = bgp; + } + + bgp_lock(bgp); + + bgp->allow_martian = false; + bgp_process_queue_init(bgp); + bgp->heuristic_coalesce = true; + bgp->inst_type = inst_type; + bgp->vrf_id = (inst_type == BGP_INSTANCE_TYPE_DEFAULT) ? VRF_DEFAULT + : VRF_UNKNOWN; + bgp->peer_self = peer_new(bgp); + XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->host); + bgp->peer_self->host = + XSTRDUP(MTYPE_BGP_PEER_HOST, "Static announcement"); + XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->hostname); + if (cmd_hostname_get()) + bgp->peer_self->hostname = + XSTRDUP(MTYPE_BGP_PEER_HOST, cmd_hostname_get()); + + XFREE(MTYPE_BGP_PEER_HOST, bgp->peer_self->domainname); + if (cmd_domainname_get()) + bgp->peer_self->domainname = + XSTRDUP(MTYPE_BGP_PEER_HOST, cmd_domainname_get()); + bgp->peer = list_new(); + bgp->peer->cmp = (int (*)(void *, void *))peer_cmp; + bgp->peerhash = hash_create(peer_hash_key_make, peer_hash_same, + "BGP Peer Hash"); + bgp->peerhash->max_size = BGP_PEER_MAX_HASH_SIZE; + + bgp->group = list_new(); + bgp->group->cmp = (int (*)(void *, void *))peer_group_cmp; + + FOREACH_AFI_SAFI (afi, safi) { + bgp->route[afi][safi] = bgp_table_init(bgp, afi, safi); + bgp->aggregate[afi][safi] = bgp_table_init(bgp, afi, safi); + bgp->rib[afi][safi] = bgp_table_init(bgp, afi, safi); + + /* Enable maximum-paths */ + bgp_maximum_paths_set(bgp, afi, safi, BGP_PEER_EBGP, + multipath_num, 0); + bgp_maximum_paths_set(bgp, afi, safi, BGP_PEER_IBGP, + multipath_num, 0); + /* Initialize graceful restart info */ + bgp->gr_info[afi][safi].eor_required = 0; + bgp->gr_info[afi][safi].eor_received = 0; + bgp->gr_info[afi][safi].t_select_deferral = NULL; + bgp->gr_info[afi][safi].t_route_select = NULL; + bgp->gr_info[afi][safi].gr_deferred = 0; + } + + bgp->v_update_delay = bm->v_update_delay; + bgp->v_establish_wait = bm->v_establish_wait; + bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; + bgp->default_subgroup_pkt_queue_max = + BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX; + bgp_tcp_keepalive_unset(bgp); + bgp_timers_unset(bgp); + bgp->default_min_holdtime = 0; + bgp->restart_time = BGP_DEFAULT_RESTART_TIME; + bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; + bgp->select_defer_time = BGP_DEFAULT_SELECT_DEFERRAL_TIME; + bgp->rib_stale_time = BGP_DEFAULT_RIB_STALE_TIME; + bgp->dynamic_neighbors_limit = BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT; + bgp->dynamic_neighbors_count = 0; + bgp->lb_ref_bw = BGP_LINK_BW_REF_BW; + bgp->lb_handling = BGP_LINK_BW_ECMP; + bgp->reject_as_sets = false; + bgp->condition_check_period = DEFAULT_CONDITIONAL_ROUTES_POLL_TIME; + bgp_addpath_init_bgp_data(&bgp->tx_addpath); + bgp->fast_convergence = false; + bgp->llgr_stale_time = BGP_DEFAULT_LLGR_STALE_TIME; + bgp->rmap_def_originate_eval_timer = RMAP_DEFAULT_ORIGINATE_EVAL_TIMER; + +#ifdef ENABLE_BGP_VNC + if (inst_type != BGP_INSTANCE_TYPE_VRF) { + bgp->rfapi = bgp_rfapi_new(bgp); + assert(bgp->rfapi); + assert(bgp->rfapi_cfg); + } +#endif /* ENABLE_BGP_VNC */ + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + bgp->vpn_policy[afi].bgp = bgp; + bgp->vpn_policy[afi].afi = afi; + bgp->vpn_policy[afi].tovpn_label = MPLS_LABEL_NONE; + bgp->vpn_policy[afi].tovpn_zebra_vrf_label_last_sent = + MPLS_LABEL_NONE; + + bgp->vpn_policy[afi].import_vrf = list_new(); + bgp->vpn_policy[afi].import_vrf->del = + bgp_vrf_string_name_delete; + bgp->vpn_policy[afi].export_vrf = list_new(); + bgp->vpn_policy[afi].export_vrf->del = + bgp_vrf_string_name_delete; + SET_FLAG(bgp->af_flags[afi][SAFI_MPLS_VPN], + BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL); + } + + for (afi = AFI_IP; afi < AFI_MAX; afi++) + bgp_label_per_nexthop_cache_init( + &bgp->mpls_labels_per_nexthop[afi]); + + bgp_mplsvpn_nh_label_bind_cache_init(&bgp->mplsvpn_nh_label_bind); + + if (name) + bgp->name = XSTRDUP(MTYPE_BGP_NAME, name); + + event_add_timer(bm->master, bgp_startup_timer_expire, bgp, + bgp->restart_time, &bgp->t_startup); + + /* printable name we can use in debug messages */ + if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) { + bgp->name_pretty = XSTRDUP(MTYPE_BGP_NAME, "VRF default"); + } else { + const char *n; + int len; + + if (bgp->name) + n = bgp->name; + else + n = "?"; + + len = 4 + 1 + strlen(n) + 1; /* "view foo\0" */ + + bgp->name_pretty = XCALLOC(MTYPE_BGP_NAME, len); + snprintf(bgp->name_pretty, len, "%s %s", + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + ? "VRF" + : "VIEW", + n); + } + + atomic_store_explicit(&bgp->wpkt_quanta, BGP_WRITE_PACKET_MAX, + memory_order_relaxed); + atomic_store_explicit(&bgp->rpkt_quanta, BGP_READ_PACKET_MAX, + memory_order_relaxed); + bgp->coalesce_time = BGP_DEFAULT_SUBGROUP_COALESCE_TIME; + bgp->default_af[AFI_IP][SAFI_UNICAST] = true; + + QOBJ_REG(bgp, bgp); + + update_bgp_group_init(bgp); + + /* assign a unique rd id for auto derivation of vrf's RD */ + bf_assign_index(bm->rd_idspace, bgp->vrf_rd_id); + + bgp_evpn_init(bgp); + bgp_evpn_vrf_es_init(bgp); + bgp_pbr_init(bgp); + bgp_srv6_init(bgp); + + /*initilize global GR FSM */ + bgp_global_gr_init(bgp); + + memset(&bgp->ebgprequirespolicywarning, 0, + sizeof(bgp->ebgprequirespolicywarning)); + + return bgp; +} + +/* Return the "default VRF" instance of BGP. */ +struct bgp *bgp_get_default(void) +{ + struct bgp *bgp; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + return bgp; + return NULL; +} + +/* Lookup BGP entry. */ +struct bgp *bgp_lookup(as_t as, const char *name) +{ + struct bgp *bgp; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + if (bgp->as == as + && ((bgp->name == NULL && name == NULL) + || (bgp->name && name && strcmp(bgp->name, name) == 0))) + return bgp; + return NULL; +} + +/* Lookup BGP structure by view name. */ +struct bgp *bgp_lookup_by_name(const char *name) +{ + struct bgp *bgp; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + if (CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) + continue; + if ((bgp->name == NULL && name == NULL) + || (bgp->name && name && strcmp(bgp->name, name) == 0)) + return bgp; + } + return NULL; +} + +/* Lookup BGP instance based on VRF id. */ +/* Note: Only to be used for incoming messages from Zebra. */ +struct bgp *bgp_lookup_by_vrf_id(vrf_id_t vrf_id) +{ + struct vrf *vrf; + + /* Lookup VRF (in tree) and follow link. */ + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + return (vrf->info) ? (struct bgp *)vrf->info : NULL; +} + +/* Sets the BGP instance where EVPN is enabled */ +void bgp_set_evpn(struct bgp *bgp) +{ + if (bm->bgp_evpn == bgp) + return; + + /* First, release the reference count we hold on the instance */ + if (bm->bgp_evpn) + bgp_unlock(bm->bgp_evpn); + + bm->bgp_evpn = bgp; + + /* Increase the reference count on this new VRF */ + if (bm->bgp_evpn) + bgp_lock(bm->bgp_evpn); +} + +/* Returns the BGP instance where EVPN is enabled, if any */ +struct bgp *bgp_get_evpn(void) +{ + return bm->bgp_evpn; +} + +/* handle socket creation or deletion, if necessary + * this is called for all new BGP instances + */ +int bgp_handle_socket(struct bgp *bgp, struct vrf *vrf, vrf_id_t old_vrf_id, + bool create) +{ + struct listnode *node; + char *address; + + /* Create BGP server socket, if listen mode not disabled */ + if (!bgp || bgp_option_check(BGP_OPT_NO_LISTEN)) + return 0; + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { + /* + * suppress vrf socket + */ + if (!create) { + bgp_close_vrf_socket(bgp); + return 0; + } + if (vrf == NULL) + return BGP_ERR_INVALID_VALUE; + /* do nothing + * if vrf_id did not change + */ + if (vrf->vrf_id == old_vrf_id) + return 0; + if (old_vrf_id != VRF_UNKNOWN) { + /* look for old socket. close it. */ + bgp_close_vrf_socket(bgp); + } + /* if backend is not yet identified ( VRF_UNKNOWN) then + * creation will be done later + */ + if (vrf->vrf_id == VRF_UNKNOWN) + return 0; + if (list_isempty(bm->addresses)) { + if (bgp_socket(bgp, bm->port, NULL) < 0) + return BGP_ERR_INVALID_VALUE; + } else { + for (ALL_LIST_ELEMENTS_RO(bm->addresses, node, address)) + if (bgp_socket(bgp, bm->port, address) < 0) + return BGP_ERR_INVALID_VALUE; + } + return 0; + } else + return bgp_check_main_socket(create, bgp); +} + +int bgp_lookup_by_as_name_type(struct bgp **bgp_val, as_t *as, const char *name, + enum bgp_instance_type inst_type) +{ + struct bgp *bgp; + + /* Multiple instance check. */ + if (name) + bgp = bgp_lookup_by_name(name); + else + bgp = bgp_get_default(); + + if (bgp) { + *bgp_val = bgp; + if (bgp->as != *as) { + *as = bgp->as; + return BGP_ERR_AS_MISMATCH; + } + if (bgp->inst_type != inst_type) + return BGP_ERR_INSTANCE_MISMATCH; + return BGP_SUCCESS; + } + *bgp_val = NULL; + + return BGP_SUCCESS; +} + +/* Called from VTY commands. */ +int bgp_get(struct bgp **bgp_val, as_t *as, const char *name, + enum bgp_instance_type inst_type, const char *as_pretty, + enum asnotation_mode asnotation) +{ + struct bgp *bgp; + struct vrf *vrf = NULL; + int ret = 0; + + ret = bgp_lookup_by_as_name_type(bgp_val, as, name, inst_type); + if (ret || *bgp_val) + return ret; + + bgp = bgp_create(as, name, inst_type, as_pretty, asnotation); + + /* + * view instances will never work inside of a vrf + * as such they must always be in the VRF_DEFAULT + * Also we must set this to something useful because + * of the vrf socket code needing an actual useful + * default value to send to the underlying OS. + * + * This code is currently ignoring vrf based + * code using the -Z option( and that is probably + * best addressed elsewhere in the code ) + */ + if (inst_type == BGP_INSTANCE_TYPE_VIEW) + bgp->vrf_id = VRF_DEFAULT; + + bgp_router_id_set(bgp, &bgp->router_id_zebra, true); + bgp_address_init(bgp); + bgp_tip_hash_init(bgp); + bgp_scan_init(bgp); + *bgp_val = bgp; + + bgp->t_rmap_def_originate_eval = NULL; + + /* If Default instance or VRF, link to the VRF structure, if present. */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT + || bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { + vrf = bgp_vrf_lookup_by_instance_type(bgp); + if (vrf) + bgp_vrf_link(bgp, vrf); + } + /* BGP server socket already processed if BGP instance + * already part of the list + */ + bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, true); + listnode_add(bm->bgp, bgp); + + if (IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("%s: Registering BGP instance %s to zebra", + __func__, bgp->name_pretty); + bgp_zebra_instance_register(bgp); + } + + return BGP_CREATED; +} + +static void bgp_zclient_set_redist(afi_t afi, int type, unsigned short instance, + vrf_id_t vrf_id, bool set) +{ + if (instance) { + if (set) + redist_add_instance(&zclient->mi_redist[afi][type], + instance); + else + redist_del_instance(&zclient->mi_redist[afi][type], + instance); + } else { + if (set) + vrf_bitmap_set(&zclient->redist[afi][type], vrf_id); + else + vrf_bitmap_unset(&zclient->redist[afi][type], vrf_id); + } +} + +static void bgp_set_redist_vrf_bitmaps(struct bgp *bgp, bool set) +{ + afi_t afi; + int i; + struct list *red_list; + struct listnode *node; + struct bgp_redist *red; + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + + red_list = bgp->redist[afi][i]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + bgp_zclient_set_redist(afi, i, red->instance, + bgp->vrf_id, set); + } + } +} + +/* + * Make BGP instance "up". Applies only to VRFs (non-default) and + * implies the VRF has been learnt from Zebra. + */ +void bgp_instance_up(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node, *next; + + bgp_set_redist_vrf_bitmaps(bgp, true); + + /* Register with zebra. */ + bgp_zebra_instance_register(bgp); + + /* Kick off any peers that may have been configured. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) { + if (!BGP_PEER_START_SUPPRESSED(peer)) + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + + /* Process any networks that have been configured. */ + bgp_static_add(bgp); +} + +/* + * Make BGP instance "down". Applies only to VRFs (non-default) and + * implies the VRF has been deleted by Zebra. + */ +void bgp_instance_down(struct bgp *bgp) +{ + struct peer *peer; + struct listnode *node; + struct listnode *next; + + /* Cleanup evpn instance state */ + bgp_evpn_instance_down(bgp); + + /* Stop timers. */ + if (bgp->t_rmap_def_originate_eval) + EVENT_OFF(bgp->t_rmap_def_originate_eval); + + /* Bring down peers, so corresponding routes are purged. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) { + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); + else + bgp_session_reset(peer); + } + + /* Purge network and redistributed routes. */ + bgp_purge_static_redist_routes(bgp); + + /* Cleanup registered nexthops (flags) */ + bgp_cleanup_nexthops(bgp); + + bgp_zebra_instance_deregister(bgp); + + bgp_set_redist_vrf_bitmaps(bgp, false); +} + +/* Delete BGP instance. */ +int bgp_delete(struct bgp *bgp) +{ + struct peer *peer; + struct peer_group *group; + struct listnode *node, *next; + struct vrf *vrf; + afi_t afi; + safi_t safi; + int i; + struct bgp_dest *dest = NULL; + struct bgp_dest *dest_next = NULL; + struct bgp_table *dest_table = NULL; + struct graceful_restart_info *gr_info; + uint32_t cnt_before, cnt_after; + + assert(bgp); + + /* + * Iterate the pending dest list and remove all the dest pertaininig to + * the bgp under delete. + */ + cnt_before = zebra_announce_count(&bm->zebra_announce_head); + for (dest = zebra_announce_first(&bm->zebra_announce_head); dest; + dest = dest_next) { + dest_next = zebra_announce_next(&bm->zebra_announce_head, dest); + dest_table = bgp_dest_table(dest); + if (dest_table->bgp == bgp) { + zebra_announce_del(&bm->zebra_announce_head, dest); + bgp_path_info_unlock(dest->za_bgp_pi); + bgp_dest_unlock_node(dest); + } + } + + cnt_after = zebra_announce_count(&bm->zebra_announce_head); + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug("Zebra Announce Fifo cleanup count before %u and after %u during BGP %s deletion", + cnt_before, cnt_after, bgp->name_pretty); + + bgp_soft_reconfig_table_task_cancel(bgp, NULL, NULL); + + /* make sure we withdraw any exported routes */ + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP, bgp_get_default(), + bgp); + vpn_leak_prechange(BGP_VPN_POLICY_DIR_TOVPN, AFI_IP6, bgp_get_default(), + bgp); + + bgp_vpn_leak_unimport(bgp); + + hook_call(bgp_inst_delete, bgp); + + FOREACH_AFI_SAFI (afi, safi) + EVENT_OFF(bgp->t_revalidate[afi][safi]); + + EVENT_OFF(bgp->t_condition_check); + EVENT_OFF(bgp->t_startup); + EVENT_OFF(bgp->t_maxmed_onstartup); + EVENT_OFF(bgp->t_update_delay); + EVENT_OFF(bgp->t_establish_wait); + + /* Set flag indicating bgp instance delete in progress */ + SET_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS); + + /* Delete the graceful restart info */ + FOREACH_AFI_SAFI (afi, safi) { + struct event *t; + + gr_info = &bgp->gr_info[afi][safi]; + if (!gr_info) + continue; + t = gr_info->t_select_deferral; + if (t) { + void *info = EVENT_ARG(t); + + XFREE(MTYPE_TMP, info); + } + EVENT_OFF(gr_info->t_select_deferral); + + t = gr_info->t_route_select; + if (t) { + void *info = EVENT_ARG(t); + + XFREE(MTYPE_TMP, info); + } + EVENT_OFF(gr_info->t_route_select); + } + + /* Delete route flap dampening configuration */ + FOREACH_AFI_SAFI (afi, safi) { + bgp_damp_disable(bgp, afi, safi); + } + + if (BGP_DEBUG(zebra, ZEBRA)) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + zlog_debug("Deleting Default VRF"); + else + zlog_debug("Deleting %s %s", + (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + ? "VRF" + : "VIEW", + bgp->name); + } + + /* unmap from RT list */ + bgp_evpn_vrf_delete(bgp); + + /* unmap bgp vrf label */ + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP); + vpn_leak_zebra_vrf_label_withdraw(bgp, AFI_IP6); + + /* Stop timers. */ + if (bgp->t_rmap_def_originate_eval) + EVENT_OFF(bgp->t_rmap_def_originate_eval); + + /* Inform peers we're going down. */ + for (ALL_LIST_ELEMENTS(bgp->peer, node, next, peer)) + peer_notify_shutdown(peer); + + /* Delete static routes (networks). */ + bgp_static_delete(bgp); + + /* Unset redistribution. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) + if (i != ZEBRA_ROUTE_BGP) + bgp_redistribute_unset(bgp, afi, i, 0); + + /* Free peers and peer-groups. */ + for (ALL_LIST_ELEMENTS(bgp->group, node, next, group)) + peer_group_delete(group); + + while (listcount(bgp->peer)) { + peer = listnode_head(bgp->peer); + peer_delete(peer); + } + + if (bgp->peer_self) { + peer_delete(bgp->peer_self); + bgp->peer_self = NULL; + } + + update_bgp_group_free(bgp); + +/* TODO - Other memory may need to be freed - e.g., NHT */ + +#ifdef ENABLE_BGP_VNC + rfapi_delete(bgp); +#endif + + /* Free memory allocated with aggregate address configuration. */ + FOREACH_AFI_SAFI (afi, safi) { + struct bgp_aggregate *aggregate = NULL; + + for (struct bgp_dest *dest = + bgp_table_top(bgp->aggregate[afi][safi]); + dest; dest = bgp_route_next(dest)) { + aggregate = bgp_dest_get_bgp_aggregate_info(dest); + if (aggregate == NULL) + continue; + + bgp_dest_set_bgp_aggregate_info(dest, NULL); + bgp_free_aggregate_info(aggregate); + } + } + + bgp_cleanup_routes(bgp); + + for (afi = 0; afi < AFI_MAX; ++afi) { + if (!bgp->vpn_policy[afi].import_redirect_rtlist) + continue; + ecommunity_free( + &bgp->vpn_policy[afi] + .import_redirect_rtlist); + bgp->vpn_policy[afi].import_redirect_rtlist = NULL; + } + + /* Free any memory allocated to holding routemap references */ + for (afi = 0; afi < AFI_MAX; ++afi) { + for (enum vpn_policy_direction dir = 0; + dir < BGP_VPN_POLICY_DIR_MAX; ++dir) { + if (bgp->vpn_policy[afi].rmap_name[dir]) + XFREE(MTYPE_ROUTE_MAP_NAME, + bgp->vpn_policy[afi].rmap_name[dir]); + bgp->vpn_policy[afi].rmap[dir] = NULL; + } + } + + /* Deregister from Zebra, if needed */ + if (IS_BGP_INST_KNOWN_TO_ZEBRA(bgp)) { + if (BGP_DEBUG(zebra, ZEBRA)) + zlog_debug( + "%s: deregistering this bgp %s instance from zebra", + __func__, bgp->name); + bgp_zebra_instance_deregister(bgp); + } + + /* Remove visibility via the master list - there may however still be + * routes to be processed still referencing the struct bgp. + */ + listnode_delete(bm->bgp, bgp); + + /* Free interfaces in this instance. */ + bgp_if_finish(bgp); + + vrf = bgp_vrf_lookup_by_instance_type(bgp); + bgp_handle_socket(bgp, vrf, VRF_UNKNOWN, false); + if (vrf) + bgp_vrf_unlink(bgp, vrf); + + /* Update EVPN VRF pointer */ + if (bm->bgp_evpn == bgp) { + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + bgp_set_evpn(NULL); + else + bgp_set_evpn(bgp_get_default()); + } + + if (bgp->process_queue) + work_queue_free_and_null(&bgp->process_queue); + + event_master_free_unused(bm->master); + bgp_unlock(bgp); /* initial reference */ + + return 0; +} + +void bgp_free(struct bgp *bgp) +{ + afi_t afi; + safi_t safi; + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_rmap *rmap; + + QOBJ_UNREG(bgp); + + list_delete(&bgp->group); + list_delete(&bgp->peer); + + if (bgp->peerhash) { + hash_free(bgp->peerhash); + bgp->peerhash = NULL; + } + + FOREACH_AFI_SAFI (afi, safi) { + /* Special handling for 2-level routing tables. */ + if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP + || safi == SAFI_EVPN) { + for (dest = bgp_table_top(bgp->rib[afi][safi]); dest; + dest = bgp_route_next(dest)) { + table = bgp_dest_get_bgp_table_info(dest); + bgp_table_finish(&table); + } + } + if (bgp->route[afi][safi]) + bgp_table_finish(&bgp->route[afi][safi]); + if (bgp->aggregate[afi][safi]) + bgp_table_finish(&bgp->aggregate[afi][safi]); + if (bgp->rib[afi][safi]) + bgp_table_finish(&bgp->rib[afi][safi]); + rmap = &bgp->table_map[afi][safi]; + XFREE(MTYPE_ROUTE_MAP_NAME, rmap->name); + } + + bgp_scan_finish(bgp); + bgp_address_destroy(bgp); + bgp_tip_hash_destroy(bgp); + + /* release the auto RD id */ + bf_release_index(bm->rd_idspace, bgp->vrf_rd_id); + + bgp_evpn_cleanup(bgp); + bgp_pbr_cleanup(bgp); + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + enum vpn_policy_direction dir; + + if (bgp->vpn_policy[afi].import_vrf) + list_delete(&bgp->vpn_policy[afi].import_vrf); + if (bgp->vpn_policy[afi].export_vrf) + list_delete(&bgp->vpn_policy[afi].export_vrf); + + dir = BGP_VPN_POLICY_DIR_FROMVPN; + if (bgp->vpn_policy[afi].rtlist[dir]) + ecommunity_free(&bgp->vpn_policy[afi].rtlist[dir]); + dir = BGP_VPN_POLICY_DIR_TOVPN; + if (bgp->vpn_policy[afi].rtlist[dir]) + ecommunity_free(&bgp->vpn_policy[afi].rtlist[dir]); + if (bgp->vpn_policy[afi].tovpn_rd_pretty) + XFREE(MTYPE_BGP_NAME, + bgp->vpn_policy[afi].tovpn_rd_pretty); + } + bgp_srv6_cleanup(bgp); + bgp_confederation_id_unset(bgp); + + for (int i = 0; i < bgp->confed_peers_cnt; i++) + XFREE(MTYPE_BGP_NAME, bgp->confed_peers[i].as_pretty); + + XFREE(MTYPE_BGP_NAME, bgp->as_pretty); + XFREE(MTYPE_BGP_NAME, bgp->name); + XFREE(MTYPE_BGP_NAME, bgp->name_pretty); + XFREE(MTYPE_BGP_NAME, bgp->snmp_stats); + XFREE(MTYPE_BGP_CONFED_LIST, bgp->confed_peers); + + XFREE(MTYPE_BGP, bgp); +} + +struct peer *peer_lookup_by_conf_if(struct bgp *bgp, const char *conf_if) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (!conf_if) + return NULL; + + if (bgp != NULL) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + if (peer->conf_if && !strcmp(peer->conf_if, conf_if) + && !CHECK_FLAG(peer->sflags, + PEER_STATUS_ACCEPT_PEER)) + return peer; + } else if (bm->bgp != NULL) { + struct listnode *bgpnode, *nbgpnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + if (peer->conf_if + && !strcmp(peer->conf_if, conf_if) + && !CHECK_FLAG(peer->sflags, + PEER_STATUS_ACCEPT_PEER)) + return peer; + } + return NULL; +} + +struct peer *peer_lookup_by_hostname(struct bgp *bgp, const char *hostname) +{ + struct peer *peer; + struct listnode *node, *nnode; + + if (!hostname) + return NULL; + + if (bgp != NULL) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + if (peer->hostname && !strcmp(peer->hostname, hostname) + && !CHECK_FLAG(peer->sflags, + PEER_STATUS_ACCEPT_PEER)) + return peer; + } else if (bm->bgp != NULL) { + struct listnode *bgpnode, *nbgpnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) + if (peer->hostname + && !strcmp(peer->hostname, hostname) + && !CHECK_FLAG(peer->sflags, + PEER_STATUS_ACCEPT_PEER)) + return peer; + } + return NULL; +} + +struct peer *peer_lookup(struct bgp *bgp, union sockunion *su) +{ + struct peer *peer = NULL; + struct peer tmp_peer; + struct peer_connection connection; + + memset(&connection, 0, sizeof(struct peer_connection)); + memset(&tmp_peer, 0, sizeof(struct peer)); + tmp_peer.connection = &connection; + + /* + * We do not want to find the doppelganger peer so search for the peer + * in + * the hash that has PEER_FLAG_CONFIG_NODE + */ + SET_FLAG(tmp_peer.flags, PEER_FLAG_CONFIG_NODE); + + connection.su = *su; + + if (bgp != NULL) { + peer = hash_lookup(bgp->peerhash, &tmp_peer); + } else if (bm->bgp != NULL) { + struct listnode *bgpnode, *nbgpnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) { + peer = hash_lookup(bgp->peerhash, &tmp_peer); + if (peer) + break; + } + } + + return peer; +} + +struct peer *peer_create_bind_dynamic_neighbor(struct bgp *bgp, + union sockunion *su, + struct peer_group *group) +{ + struct peer *peer; + afi_t afi; + safi_t safi; + + /* Create peer first; we've already checked group config is valid. */ + peer = peer_create(su, NULL, bgp, bgp->as, group->conf->as, + group->conf->as_type, group, true, NULL); + if (!peer) + return NULL; + + /* Link to group */ + peer = peer_lock(peer); + listnode_add(group->peer, peer); + + peer_group2peer_config_copy(group, peer); + + /* + * Bind peer for all AFs configured for the group. We don't call + * peer_group_bind as that is sub-optimal and does some stuff we don't + * want. + */ + FOREACH_AFI_SAFI (afi, safi) { + if (!group->conf->afc[afi][safi]) + continue; + peer->afc[afi][safi] = 1; + + if (!peer_af_find(peer, afi, safi)) + peer_af_create(peer, afi, safi); + + peer_group2peer_config_copy_af(group, peer, afi, safi); + } + + /* Mark as dynamic, but also as a "config node" for other things to + * work. */ + SET_FLAG(peer->flags, PEER_FLAG_DYNAMIC_NEIGHBOR); + + return peer; +} + +struct prefix * +peer_group_lookup_dynamic_neighbor_range(struct peer_group *group, + struct prefix *prefix) +{ + struct listnode *node, *nnode; + struct prefix *range; + afi_t afi; + + afi = family2afi(prefix->family); + + if (group->listen_range[afi]) + for (ALL_LIST_ELEMENTS(group->listen_range[afi], node, nnode, + range)) + if (prefix_match(range, prefix)) + return range; + + return NULL; +} + +struct peer_group * +peer_group_lookup_dynamic_neighbor(struct bgp *bgp, struct prefix *prefix, + struct prefix **listen_range) +{ + struct prefix *range = NULL; + struct peer_group *group = NULL; + struct listnode *node, *nnode; + + *listen_range = NULL; + if (bgp != NULL) { + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) + if ((range = peer_group_lookup_dynamic_neighbor_range( + group, prefix))) + break; + } else if (bm->bgp != NULL) { + struct listnode *bgpnode, *nbgpnode; + + for (ALL_LIST_ELEMENTS(bm->bgp, bgpnode, nbgpnode, bgp)) + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) + if ((range = peer_group_lookup_dynamic_neighbor_range( + group, prefix))) + goto found_range; + } + +found_range: + *listen_range = range; + return (group && range) ? group : NULL; +} + +struct peer *peer_lookup_dynamic_neighbor(struct bgp *bgp, union sockunion *su) +{ + struct peer_group *group; + struct bgp *gbgp; + struct peer *peer; + struct prefix prefix; + struct prefix *listen_range; + int dncount; + + if (!sockunion2hostprefix(su, &prefix)) + return NULL; + + /* See if incoming connection matches a configured listen range. */ + group = peer_group_lookup_dynamic_neighbor(bgp, &prefix, &listen_range); + + if (!group) + return NULL; + + + gbgp = group->bgp; + + if (!gbgp) + return NULL; + + if (bgp_debug_neighbor_events(NULL)) + zlog_debug( + "Dynamic Neighbor %pFX matches group %s listen range %pFX", + &prefix, group->name, listen_range); + + /* Are we within the listen limit? */ + dncount = gbgp->dynamic_neighbors_count; + + if (dncount >= gbgp->dynamic_neighbors_limit) { + if (bgp_debug_neighbor_events(NULL)) + zlog_debug( + "Dynamic Neighbor %pFX rejected - at limit %d", + &prefix, gbgp->dynamic_neighbors_limit); + return NULL; + } + + /* Ensure group is not disabled. */ + if (CHECK_FLAG(group->conf->flags, PEER_FLAG_SHUTDOWN)) { + if (bgp_debug_neighbor_events(NULL)) + zlog_debug( + "Dynamic Neighbor %pFX rejected - group %s disabled", + &prefix, group->name); + return NULL; + } + + /* Check that at least one AF is activated for the group. */ + if (!peer_group_af_configured(group)) { + if (bgp_debug_neighbor_events(NULL)) + zlog_debug( + "Dynamic Neighbor %pFX rejected - no AF activated for group %s", + &prefix, group->name); + return NULL; + } + + /* Create dynamic peer and bind to associated group. */ + peer = peer_create_bind_dynamic_neighbor(gbgp, su, group); + assert(peer); + + gbgp->dynamic_neighbors_count = ++dncount; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s Dynamic Neighbor added, group %s count %d", + peer->host, group->name, dncount); + + if (dncount == gbgp->dynamic_neighbors_limit) { + zlog_warn("Dynamic Neighbor %s added as last connection. Peer-group %s reached maximum listen limit %d", + peer->host, group->name, + gbgp->dynamic_neighbors_limit); + } + return peer; +} + +static void peer_drop_dynamic_neighbor(struct peer *peer) +{ + int dncount = -1; + if (peer->group->bgp) { + dncount = peer->group->bgp->dynamic_neighbors_count; + if (dncount) + peer->group->bgp->dynamic_neighbors_count = --dncount; + } + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s dropped from group %s, count %d", peer->host, + peer->group->name, dncount); +} + +bool bgp_path_attribute_discard(struct peer *peer, char *buf, size_t size) +{ + if (!buf) + return false; + + buf[0] = '\0'; + + for (unsigned int i = 1; i <= BGP_ATTR_MAX; i++) { + if (peer->discard_attrs[i]) + snprintf(buf + strlen(buf), size - strlen(buf), "%s%d", + (strlen(buf) > 0) ? " " : "", i); + } + + if (strlen(buf) > 0) + return true; + + return false; +} + +bool bgp_path_attribute_treat_as_withdraw(struct peer *peer, char *buf, + size_t size) +{ + if (!buf) + return false; + + buf[0] = '\0'; + + for (unsigned int i = 1; i <= BGP_ATTR_MAX; i++) { + if (peer->withdraw_attrs[i]) + snprintf(buf + strlen(buf), size - strlen(buf), "%s%d", + (strlen(buf) > 0) ? " " : "", i); + } + + if (strlen(buf) > 0) + return true; + + return false; +} + +/* If peer is configured at least one address family return 1. */ +bool peer_active(struct peer *peer) +{ + if (BGP_CONNECTION_SU_UNSPEC(peer->connection)) + return false; + if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] + || peer->afc[AFI_IP][SAFI_LABELED_UNICAST] + || peer->afc[AFI_IP][SAFI_MPLS_VPN] || peer->afc[AFI_IP][SAFI_ENCAP] + || peer->afc[AFI_IP][SAFI_FLOWSPEC] + || peer->afc[AFI_IP6][SAFI_UNICAST] + || peer->afc[AFI_IP6][SAFI_MULTICAST] + || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] + || peer->afc[AFI_IP6][SAFI_MPLS_VPN] + || peer->afc[AFI_IP6][SAFI_ENCAP] + || peer->afc[AFI_IP6][SAFI_FLOWSPEC] + || peer->afc[AFI_L2VPN][SAFI_EVPN]) + return true; + return false; +} + +/* If peer is negotiated at least one address family return 1. */ +bool peer_active_nego(struct peer *peer) +{ + if (peer->afc_nego[AFI_IP][SAFI_UNICAST] + || peer->afc_nego[AFI_IP][SAFI_MULTICAST] + || peer->afc_nego[AFI_IP][SAFI_LABELED_UNICAST] + || peer->afc_nego[AFI_IP][SAFI_MPLS_VPN] + || peer->afc_nego[AFI_IP][SAFI_ENCAP] + || peer->afc_nego[AFI_IP][SAFI_FLOWSPEC] + || peer->afc_nego[AFI_IP6][SAFI_UNICAST] + || peer->afc_nego[AFI_IP6][SAFI_MULTICAST] + || peer->afc_nego[AFI_IP6][SAFI_LABELED_UNICAST] + || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] + || peer->afc_nego[AFI_IP6][SAFI_ENCAP] + || peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] + || peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) + return true; + return false; +} + +/* If peer received at least one address family MP, return true */ +bool peer_afc_received(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + if (peer->afc_recv[afi][safi]) + return true; + + return false; +} + +/* If peer advertised at least one address family MP, return true */ +bool peer_afc_advertised(struct peer *peer) +{ + afi_t afi; + safi_t safi; + + FOREACH_AFI_SAFI (afi, safi) + if (peer->afc_adv[afi][safi]) + return true; + + return false; +} + +void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, + enum peer_change_type type) +{ + struct peer_af *paf; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return; + + if (!peer_established(peer->connection)) + return; + + if (type == peer_change_reset) { + /* If we're resetting session, we've to delete both peer struct + */ + if ((peer->doppelganger) && + (peer->doppelganger->connection->status != Deleted) && + (!CHECK_FLAG(peer->doppelganger->flags, + PEER_FLAG_CONFIG_NODE))) + peer_delete(peer->doppelganger); + + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else if (type == peer_change_reset_in) { + if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_RCV)) + bgp_route_refresh_send(peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_NORMAL); + else { + if ((peer->doppelganger) && + (peer->doppelganger->connection->status != Deleted) && + (!CHECK_FLAG(peer->doppelganger->flags, + PEER_FLAG_CONFIG_NODE))) + peer_delete(peer->doppelganger); + + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } + } else if (type == peer_change_reset_out) { + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) + SET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_FORCE_UPDATES); + + update_group_adjust_peer(paf); + bgp_announce_route(peer, afi, safi, false); + } +} + +struct peer_flag_action { + /* Peer's flag. */ + uint64_t flag; + + /* This flag can be set for peer-group member. */ + uint8_t not_for_member; + + /* Action when the flag is changed. */ + enum peer_change_type type; +}; + +static const struct peer_flag_action peer_flag_action_list[] = { + {PEER_FLAG_PASSIVE, 0, peer_change_reset}, + {PEER_FLAG_SHUTDOWN, 0, peer_change_reset}, + {PEER_FLAG_RTT_SHUTDOWN, 0, peer_change_none}, + {PEER_FLAG_DONT_CAPABILITY, 0, peer_change_none}, + {PEER_FLAG_OVERRIDE_CAPABILITY, 0, peer_change_none}, + {PEER_FLAG_STRICT_CAP_MATCH, 0, peer_change_none}, + {PEER_FLAG_DYNAMIC_CAPABILITY, 0, peer_change_none}, + {PEER_FLAG_DISABLE_CONNECTED_CHECK, 0, peer_change_reset}, + {PEER_FLAG_CAPABILITY_ENHE, 0, peer_change_reset}, + {PEER_FLAG_ENFORCE_FIRST_AS, 0, peer_change_reset_in}, + {PEER_FLAG_IFPEER_V6ONLY, 0, peer_change_reset}, + {PEER_FLAG_ROUTEADV, 0, peer_change_none}, + {PEER_FLAG_TIMER, 0, peer_change_none}, + {PEER_FLAG_TIMER_CONNECT, 0, peer_change_none}, + {PEER_FLAG_TIMER_DELAYOPEN, 0, peer_change_none}, + {PEER_FLAG_PASSWORD, 0, peer_change_none}, + {PEER_FLAG_LOCAL_AS, 0, peer_change_reset}, + {PEER_FLAG_LOCAL_AS_NO_PREPEND, 0, peer_change_reset}, + {PEER_FLAG_LOCAL_AS_REPLACE_AS, 0, peer_change_reset}, + {PEER_FLAG_UPDATE_SOURCE, 0, peer_change_none}, + {PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE, 0, peer_change_none}, + {PEER_FLAG_EXTENDED_OPT_PARAMS, 0, peer_change_reset}, + {PEER_FLAG_ROLE_STRICT_MODE, 0, peer_change_none}, + {PEER_FLAG_ROLE, 0, peer_change_none}, + {PEER_FLAG_PORT, 0, peer_change_reset}, + {PEER_FLAG_AIGP, 0, peer_change_none}, + {PEER_FLAG_GRACEFUL_SHUTDOWN, 0, peer_change_none}, + {PEER_FLAG_CAPABILITY_SOFT_VERSION, 0, peer_change_none}, + {PEER_FLAG_CAPABILITY_FQDN, 0, peer_change_none}, + {PEER_FLAG_AS_LOOP_DETECTION, 0, peer_change_none}, + {PEER_FLAG_EXTENDED_LINK_BANDWIDTH, 0, peer_change_none}, + {0, 0, 0}}; + +static const struct peer_flag_action peer_af_flag_action_list[] = { + {PEER_FLAG_SEND_COMMUNITY, 1, peer_change_reset_out}, + {PEER_FLAG_SEND_EXT_COMMUNITY, 1, peer_change_reset_out}, + {PEER_FLAG_SEND_LARGE_COMMUNITY, 1, peer_change_reset_out}, + {PEER_FLAG_NEXTHOP_SELF, 1, peer_change_reset_out}, + {PEER_FLAG_REFLECTOR_CLIENT, 1, peer_change_reset}, + {PEER_FLAG_RSERVER_CLIENT, 1, peer_change_reset}, + {PEER_FLAG_SOFT_RECONFIG, 0, peer_change_reset_in}, + {PEER_FLAG_AS_PATH_UNCHANGED, 1, peer_change_reset_out}, + {PEER_FLAG_NEXTHOP_UNCHANGED, 1, peer_change_reset_out}, + {PEER_FLAG_MED_UNCHANGED, 1, peer_change_reset_out}, + {PEER_FLAG_DEFAULT_ORIGINATE, 0, peer_change_none}, + {PEER_FLAG_REMOVE_PRIVATE_AS, 1, peer_change_reset_out}, + {PEER_FLAG_ALLOWAS_IN, 0, peer_change_reset_in}, + {PEER_FLAG_ALLOWAS_IN_ORIGIN, 0, peer_change_reset_in}, + {PEER_FLAG_ORF_PREFIX_SM, 1, peer_change_reset}, + {PEER_FLAG_ORF_PREFIX_RM, 1, peer_change_reset}, + {PEER_FLAG_MAX_PREFIX, 0, peer_change_none}, + {PEER_FLAG_MAX_PREFIX_WARNING, 0, peer_change_none}, + {PEER_FLAG_MAX_PREFIX_FORCE, 0, peer_change_none}, + {PEER_FLAG_MAX_PREFIX_OUT, 0, peer_change_none}, + {PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED, 0, peer_change_reset_out}, + {PEER_FLAG_FORCE_NEXTHOP_SELF, 1, peer_change_reset_out}, + {PEER_FLAG_REMOVE_PRIVATE_AS_ALL, 1, peer_change_reset_out}, + {PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE, 1, peer_change_reset_out}, + {PEER_FLAG_AS_OVERRIDE, 1, peer_change_reset_out}, + {PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, 1, peer_change_reset_out}, + {PEER_FLAG_WEIGHT, 0, peer_change_reset_in}, + {PEER_FLAG_DISABLE_ADDPATH_RX, 0, peer_change_none}, + {PEER_FLAG_SOO, 0, peer_change_reset}, + {PEER_FLAG_ACCEPT_OWN, 0, peer_change_reset}, + {PEER_FLAG_SEND_EXT_COMMUNITY_RPKI, 1, peer_change_reset_out}, + {PEER_FLAG_ADDPATH_RX_PATHS_LIMIT, 0, peer_change_none}, + {0, 0, 0}}; + +/* Proper action set. */ +static int peer_flag_action_set(const struct peer_flag_action *action_list, + int size, struct peer_flag_action *action, + uint64_t flag) +{ + int i; + int found = 0; + int reset_in = 0; + int reset_out = 0; + const struct peer_flag_action *match = NULL; + + /* Check peer's frag action. */ + for (i = 0; i < size; i++) { + match = &action_list[i]; + + if (match->flag == 0) + break; + + if (match->flag & flag) { + found = 1; + + if (match->type == peer_change_reset_in) + reset_in = 1; + if (match->type == peer_change_reset_out) + reset_out = 1; + if (match->type == peer_change_reset) { + reset_in = 1; + reset_out = 1; + } + if (match->not_for_member) + action->not_for_member = 1; + } + } + + /* Set peer clear type. */ + if (reset_in && reset_out) + action->type = peer_change_reset; + else if (reset_in) + action->type = peer_change_reset_in; + else if (reset_out) + action->type = peer_change_reset_out; + else + action->type = peer_change_none; + + return found; +} + +static void peer_flag_modify_action(struct peer *peer, uint64_t flag) +{ + if (flag == PEER_FLAG_SHUTDOWN) { + if (CHECK_FLAG(peer->flags, flag)) { + if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)) + peer_nsf_stop(peer); + + UNSET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); + + if (peer->connection->t_pmax_restart) { + EVENT_OFF(peer->connection->t_pmax_restart); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Maximum-prefix restart timer canceled", + peer); + } + + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) { + char *msg = peer->tx_shutdown_message; + size_t msglen; + uint8_t msgbuf[BGP_ADMIN_SHUTDOWN_MSG_LEN + 1]; + + if (!msg && peer_group_active(peer)) + msg = peer->group->conf + ->tx_shutdown_message; + msglen = msg ? strlen(msg) : 0; + if (msglen > BGP_ADMIN_SHUTDOWN_MSG_LEN) + msglen = BGP_ADMIN_SHUTDOWN_MSG_LEN; + + if (msglen) { + msgbuf[0] = msglen; + memcpy(msgbuf + 1, msg, msglen); + + bgp_notify_send_with_data( + peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN, + msgbuf, msglen + 1); + } else + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); + } else + bgp_session_reset(peer); + } else { + peer->v_start = BGP_INIT_START_TIMER; + BGP_EVENT_ADD(peer->connection, BGP_Stop); + } + } else if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + if (flag == PEER_FLAG_DYNAMIC_CAPABILITY) + peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; + else if (flag == PEER_FLAG_PASSIVE) + peer->last_reset = PEER_DOWN_PASSIVE_CHANGE; + else if (flag == PEER_FLAG_DISABLE_CONNECTED_CHECK) + peer->last_reset = PEER_DOWN_MULTIHOP_CHANGE; + + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); +} + +/* Enable global administrative shutdown of all peers of BGP instance */ +void bgp_shutdown_enable(struct bgp *bgp, const char *msg) +{ + struct peer *peer; + struct listnode *node; + /* length(1) + message(N) */ + uint8_t data[BGP_ADMIN_SHUTDOWN_MSG_LEN + 1]; + + /* do nothing if already shut down */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN)) + return; + + /* informational log message */ + zlog_info("Enabled administrative shutdown on BGP instance AS %u", + bgp->as); + + /* iterate through peers of BGP instance */ + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + peer->last_reset = PEER_DOWN_USER_SHUTDOWN; + + /* continue, if peer is already in administrative shutdown. */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN)) + continue; + + /* send a RFC 4486 notification message if necessary */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + if (msg) { + size_t datalen = strlen(msg); + + if (datalen > BGP_ADMIN_SHUTDOWN_MSG_LEN) + datalen = BGP_ADMIN_SHUTDOWN_MSG_LEN; + + data[0] = datalen; + memcpy(data + 1, msg, datalen); + + bgp_notify_send_with_data(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN, + data, datalen + 1); + } else { + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN); + } + } + + /* reset start timer to initial value */ + peer->v_start = BGP_INIT_START_TIMER; + + /* trigger a RFC 4271 ManualStop event */ + BGP_EVENT_ADD(peer->connection, BGP_Stop); + } + + /* set the BGP instances shutdown flag */ + SET_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN); +} + +/* Disable global administrative shutdown of all peers of BGP instance */ +void bgp_shutdown_disable(struct bgp *bgp) +{ + const struct listnode *node; + struct peer *peer; + + /* do nothing if not shut down. */ + if (!CHECK_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN)) + return; + + /* informational log message */ + zlog_info("Disabled administrative shutdown on BGP instance AS %u", + bgp->as); + + /* clear the BGP instances shutdown flag */ + UNSET_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN); + + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + bgp_timer_set(peer->connection); + peer->last_reset = PEER_DOWN_WAITING_OPEN; + } +} + +/* Change specified peer flag. */ +static int peer_flag_modify(struct peer *peer, uint64_t flag, int set) +{ + int found; + int size; + bool invert, member_invert; + struct peer *member; + struct listnode *node, *nnode; + struct peer_flag_action action; + + memset(&action, 0, sizeof(struct peer_flag_action)); + size = sizeof(peer_flag_action_list) / sizeof(struct peer_flag_action); + + invert = CHECK_FLAG(peer->flags_invert, flag); + found = peer_flag_action_set(peer_flag_action_list, size, &action, + flag); + + /* Abort if no flag action exists. */ + if (!found) + return BGP_ERR_INVALID_FLAG; + + /* Check for flag conflict: STRICT_CAP_MATCH && OVERRIDE_CAPABILITY */ + if (set && CHECK_FLAG(peer->flags | flag, PEER_FLAG_STRICT_CAP_MATCH) + && CHECK_FLAG(peer->flags | flag, PEER_FLAG_OVERRIDE_CAPABILITY)) + return BGP_ERR_PEER_FLAG_CONFLICT; + + /* Handle flag updates where desired state matches current state. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (set && CHECK_FLAG(peer->flags, flag)) { + COND_FLAG(peer->flags_override, flag, !invert); + return 0; + } + + if (!set && !CHECK_FLAG(peer->flags, flag)) { + COND_FLAG(peer->flags_override, flag, invert); + return 0; + } + } + + /* Inherit from peer-group or set/unset flags accordingly. */ + if (peer_group_active(peer) && set == invert) + peer_flag_inherit(peer, flag); + else + COND_FLAG(peer->flags, flag, set); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Update flag override state accordingly. */ + COND_FLAG(peer->flags_override, flag, set != invert); + + /* + * For the extended next-hop encoding flag we need to turn RAs + * on if flag is being set, but only turn RAs off if the flag + * is being unset on this peer and if this peer is a member of a + * peer-group, the peer-group also doesn't have the flag set. + */ + if (flag == PEER_FLAG_CAPABILITY_ENHE) { + if (set) { + bgp_zebra_initiate_radv(peer->bgp, peer); + } else if (peer_group_active(peer)) { + if (!CHECK_FLAG(peer->group->conf->flags, + flag) && + !peer->conf_if) + bgp_zebra_terminate_radv(peer->bgp, + peer); + } else + bgp_zebra_terminate_radv(peer->bgp, peer); + } + + if (flag == PEER_FLAG_SHUTDOWN) + peer->last_reset = set ? PEER_DOWN_USER_SHUTDOWN + : PEER_DOWN_WAITING_OPEN; + + /* Execute flag action on peer. */ + if (action.type == peer_change_reset) + peer_flag_modify_action(peer, flag); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Update peer-group members, unless they are explicitly overriding + * peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, flag)) + continue; + + /* Check if only member without group is inverted. */ + member_invert = + CHECK_FLAG(member->flags_invert, flag) && !invert; + + /* Skip peers with equivalent configuration. */ + if (set != member_invert && CHECK_FLAG(member->flags, flag)) + continue; + + if (set == member_invert && !CHECK_FLAG(member->flags, flag)) + continue; + + /* Update flag on peer-group member. */ + COND_FLAG(member->flags, flag, set != member_invert); + + if (flag == PEER_FLAG_CAPABILITY_ENHE && !member->conf_if) + set ? bgp_zebra_initiate_radv(member->bgp, member) + : bgp_zebra_terminate_radv(member->bgp, member); + + if (flag == PEER_FLAG_SHUTDOWN) + member->last_reset = set ? PEER_DOWN_USER_SHUTDOWN + : PEER_DOWN_WAITING_OPEN; + + /* Execute flag action on peer-group member. */ + if (action.type == peer_change_reset) + peer_flag_modify_action(member, flag); + } + + return 0; +} + +int peer_flag_set(struct peer *peer, uint64_t flag) +{ + return peer_flag_modify(peer, flag, 1); +} + +int peer_flag_unset(struct peer *peer, uint64_t flag) +{ + return peer_flag_modify(peer, flag, 0); +} + +static int peer_af_flag_modify(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag, bool set) +{ + int found; + int size; + bool invert, member_invert; + struct peer *member; + struct listnode *node, *nnode; + struct peer_flag_action action; + enum bgp_peer_sort ptype; + + memset(&action, 0, sizeof(struct peer_flag_action)); + size = sizeof(peer_af_flag_action_list) + / sizeof(struct peer_flag_action); + + invert = CHECK_FLAG(peer->af_flags_invert[afi][safi], flag); + found = peer_flag_action_set(peer_af_flag_action_list, size, &action, + flag); + + /* Abort if flag action exists. */ + if (!found) + return BGP_ERR_INVALID_FLAG; + + ptype = peer_sort(peer); + /* Special check for reflector client. */ + if (flag & PEER_FLAG_REFLECTOR_CLIENT && ptype != BGP_PEER_IBGP) + return BGP_ERR_NOT_INTERNAL_PEER; + + /* Special check for remove-private-AS. */ + if (flag & PEER_FLAG_REMOVE_PRIVATE_AS && ptype == BGP_PEER_IBGP) + return BGP_ERR_REMOVE_PRIVATE_AS; + + /* as-override is not allowed for IBGP peers */ + if (flag & PEER_FLAG_AS_OVERRIDE && ptype == BGP_PEER_IBGP) + return BGP_ERR_AS_OVERRIDE; + + /* Handle flag updates where desired state matches current state. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (set && CHECK_FLAG(peer->af_flags[afi][safi], flag)) { + COND_FLAG(peer->af_flags_override[afi][safi], flag, + !invert); + return 0; + } + + if (!set && !CHECK_FLAG(peer->af_flags[afi][safi], flag)) { + COND_FLAG(peer->af_flags_override[afi][safi], flag, + invert); + return 0; + } + } + + /* + * For EVPN we implicitly set the NEXTHOP_UNCHANGED flag, + * if we are setting/unsetting flags which conflict with this flag + * handle accordingly + */ + if (afi == AFI_L2VPN && safi == SAFI_EVPN) { + if (set) { + + /* + * if we are setting NEXTHOP_SELF, we need to unset the + * NEXTHOP_UNCHANGED flag + */ + if (CHECK_FLAG(flag, PEER_FLAG_NEXTHOP_SELF) || + CHECK_FLAG(flag, PEER_FLAG_FORCE_NEXTHOP_SELF)) + UNSET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED); + } else { + + /* + * if we are unsetting NEXTHOP_SELF, we need to set the + * NEXTHOP_UNCHANGED flag to reset the defaults for EVPN + */ + if (CHECK_FLAG(flag, PEER_FLAG_NEXTHOP_SELF) || + CHECK_FLAG(flag, PEER_FLAG_FORCE_NEXTHOP_SELF)) + SET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED); + } + } + + /* + * If the peer is a route server client let's not + * muck with the nexthop on the way out the door + */ + if (flag & PEER_FLAG_RSERVER_CLIENT) { + if (set) + SET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED); + else + UNSET_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_NEXTHOP_UNCHANGED); + } + + /* Inherit from peer-group or set/unset flags accordingly. */ + if (peer_group_active(peer) && set == invert) + peer_af_flag_inherit(peer, afi, safi, flag); + else + COND_FLAG(peer->af_flags[afi][safi], flag, set); + + /* Execute action when peer is established. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && + peer_established(peer->connection)) { + if (!set && flag == PEER_FLAG_SOFT_RECONFIG) + bgp_clear_adj_in(peer, afi, safi); + else { + if (flag == PEER_FLAG_REFLECTOR_CLIENT) + peer->last_reset = PEER_DOWN_RR_CLIENT_CHANGE; + else if (flag == PEER_FLAG_RSERVER_CLIENT) + peer->last_reset = PEER_DOWN_RS_CLIENT_CHANGE; + else if (flag == PEER_FLAG_ORF_PREFIX_SM) + peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; + else if (flag == PEER_FLAG_ORF_PREFIX_RM) + peer->last_reset = PEER_DOWN_CAPABILITY_CHANGE; + + /* We should not reset the session if + * dynamic capability is enabled and we + * are changing the ORF prefix flags. + */ + if ((CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_RCV) && + CHECK_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV)) && + (flag == PEER_FLAG_ORF_PREFIX_RM || + flag == PEER_FLAG_ORF_PREFIX_SM)) + action.type = peer_change_none; + + peer_change_action(peer, afi, safi, action.type); + } + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + COND_FLAG(peer->af_flags_override[afi][safi], flag, + set != invert); + } else { + /* + * Update peer-group members, unless they are explicitly + * overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, + member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + flag)) + continue; + + /* Check if only member without group is inverted. */ + member_invert = + CHECK_FLAG(member->af_flags_invert[afi][safi], + flag) + && !invert; + + /* Skip peers with equivalent configuration. */ + if (set != member_invert + && CHECK_FLAG(member->af_flags[afi][safi], flag)) + continue; + + if (set == member_invert + && !CHECK_FLAG(member->af_flags[afi][safi], flag)) + continue; + + /* Update flag on peer-group member. */ + COND_FLAG(member->af_flags[afi][safi], flag, + set != member_invert); + + /* Execute flag action on peer-group member. */ + if (peer_established(member->connection)) { + if (!set && flag == PEER_FLAG_SOFT_RECONFIG) + bgp_clear_adj_in(member, afi, safi); + else { + if (flag == PEER_FLAG_REFLECTOR_CLIENT) + member->last_reset = + PEER_DOWN_RR_CLIENT_CHANGE; + else if (flag + == PEER_FLAG_RSERVER_CLIENT) + member->last_reset = + PEER_DOWN_RS_CLIENT_CHANGE; + else if (flag + == PEER_FLAG_ORF_PREFIX_SM) + member->last_reset = + PEER_DOWN_CAPABILITY_CHANGE; + else if (flag + == PEER_FLAG_ORF_PREFIX_RM) + member->last_reset = + PEER_DOWN_CAPABILITY_CHANGE; + + /* We should not reset the session if + * dynamic capability is enabled and we + * are changing the ORF prefix flags. + */ + if ((CHECK_FLAG(peer->cap, + PEER_CAP_DYNAMIC_RCV) && + CHECK_FLAG(peer->cap, + PEER_CAP_DYNAMIC_ADV)) && + (flag == PEER_FLAG_ORF_PREFIX_RM || + flag == PEER_FLAG_ORF_PREFIX_SM)) + action.type = peer_change_none; + + peer_change_action(member, afi, safi, + action.type); + } + } + } + } + + return 0; +} + +int peer_af_flag_set(struct peer *peer, afi_t afi, safi_t safi, uint64_t flag) +{ + return peer_af_flag_modify(peer, afi, safi, flag, 1); +} + +int peer_af_flag_unset(struct peer *peer, afi_t afi, safi_t safi, uint64_t flag) +{ + return peer_af_flag_modify(peer, afi, safi, flag, 0); +} + + +void peer_tx_shutdown_message_set(struct peer *peer, const char *msg) +{ + XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); + peer->tx_shutdown_message = + msg ? XSTRDUP(MTYPE_PEER_TX_SHUTDOWN_MSG, msg) : NULL; +} + +void peer_tx_shutdown_message_unset(struct peer *peer) +{ + XFREE(MTYPE_PEER_TX_SHUTDOWN_MSG, peer->tx_shutdown_message); +} + + +/* EBGP multihop configuration. */ +int peer_ebgp_multihop_set(struct peer *peer, int ttl) +{ + struct peer_group *group; + struct listnode *node, *nnode; + struct peer *peer1; + + if (peer->sort == BGP_PEER_IBGP || peer->conf_if) + return 0; + + /* is there anything to do? */ + if (peer->ttl == ttl) + return 0; + + /* see comment in peer_ttl_security_hops_set() */ + if (ttl != MAXTTL) { + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + group = peer->group; + if (group->conf->gtsm_hops != BGP_GTSM_HOPS_DISABLED) + return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, + peer1)) { + if (peer1->sort == BGP_PEER_IBGP) + continue; + + if (peer1->gtsm_hops != BGP_GTSM_HOPS_DISABLED) + return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; + } + } else { + if (peer->gtsm_hops != BGP_GTSM_HOPS_DISABLED) + return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; + } + } + + peer->ttl = ttl; + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (peer->sort != BGP_PEER_IBGP) { + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + + /* Reconfigure BFD peer with new TTL. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + } + } else { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (peer->sort == BGP_PEER_IBGP) + continue; + + peer->ttl = group->conf->ttl; + + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + + /* Reconfigure BFD peer with new TTL. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + } + } + return 0; +} + +int peer_ebgp_multihop_unset(struct peer *peer) +{ + struct peer_group *group; + struct listnode *node, *nnode; + int ttl; + + if (peer->sort == BGP_PEER_IBGP) + return 0; + + if (peer->gtsm_hops != BGP_GTSM_HOPS_DISABLED && peer->ttl != MAXTTL) + return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; + + if (peer_group_active(peer)) + ttl = peer->group->conf->ttl; + else + ttl = BGP_DEFAULT_TTL; + + if (ttl == peer->ttl) + return 0; + + peer->ttl = ttl; + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + + /* Reconfigure BFD peer with new TTL. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + } else { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + if (peer->sort == BGP_PEER_IBGP) + continue; + + peer->ttl = BGP_DEFAULT_TTL; + + if (peer->connection->fd >= 0) { + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + } + + /* Reconfigure BFD peer with new TTL. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + } + } + return 0; +} + +/* Set Open Policy Role and check its correctness */ +int peer_role_set(struct peer *peer, uint8_t role, bool strict_mode) +{ + struct peer *member; + struct listnode *node, *nnode; + + peer_flag_set(peer, PEER_FLAG_ROLE); + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (peer->sort != BGP_PEER_EBGP) + return BGP_ERR_INVALID_INTERNAL_ROLE; + + if (peer->local_role == role) { + if (CHECK_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE) && + !strict_mode) + /* TODO: Is session restart needed if it was + * down? + */ + UNSET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + if (!CHECK_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE) && + strict_mode) { + SET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + /* Restart session to throw Role Mismatch + * Notification + */ + if (peer->remote_role == ROLE_UNDEFINED) + bgp_session_reset(peer); + } + } else { + peer->local_role = role; + if (strict_mode) + SET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + else + UNSET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + } + + return CMD_SUCCESS; + } + + peer->local_role = role; + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + if (member->sort != BGP_PEER_EBGP) + return BGP_ERR_INVALID_INTERNAL_ROLE; + + if (member->local_role == role) { + if (CHECK_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE) && + !strict_mode) + /* TODO: Is session restart needed if it was + * down? + */ + UNSET_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE); + if (!CHECK_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE) && + strict_mode) { + SET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + SET_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE); + /* Restart session to throw Role Mismatch + * Notification + */ + if (member->remote_role == ROLE_UNDEFINED) + bgp_session_reset(member); + } + } else { + member->local_role = role; + + if (strict_mode) { + SET_FLAG(peer->flags, + PEER_FLAG_ROLE_STRICT_MODE); + SET_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE); + } else { + UNSET_FLAG(member->flags, + PEER_FLAG_ROLE_STRICT_MODE); + } + } + } + + return CMD_SUCCESS; +} + +int peer_role_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + peer_flag_unset(peer, PEER_FLAG_ROLE); + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return peer_role_set(peer, ROLE_UNDEFINED, 0); + + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) + peer_role_set(member, ROLE_UNDEFINED, 0); + + return CMD_SUCCESS; +} + +/* Neighbor description. */ +void peer_description_set(struct peer *peer, const char *desc) +{ + XFREE(MTYPE_PEER_DESC, peer->desc); + + peer->desc = XSTRDUP(MTYPE_PEER_DESC, desc); +} + +void peer_description_unset(struct peer *peer) +{ + XFREE(MTYPE_PEER_DESC, peer->desc); +} + +/* Neighbor update-source. */ +int peer_update_source_if_set(struct peer *peer, const char *ifname) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_UPDATE_SOURCE); + if (peer->update_if) { + if (strcmp(peer->update_if, ifname) == 0) + return 0; + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); + } + peer->update_if = XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, ifname); + sockunion_free(peer->update_source); + peer->update_source = NULL; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); + + /* Apply new source configuration to BFD session. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) + continue; + + /* Skip peers with the same configuration. */ + if (member->update_if) { + if (strcmp(member->update_if, ifname) == 0) + continue; + XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); + } + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); + member->update_if = XSTRDUP(MTYPE_PEER_UPDATE_SOURCE, ifname); + sockunion_free(member->update_source); + member->update_source = NULL; + + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) { + member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(member); + + /* Apply new source configuration to BFD session. */ + if (member->bfd_config) + bgp_peer_bfd_update_source(member); + } + + return 0; +} + +void peer_update_source_addr_set(struct peer *peer, const union sockunion *su) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_UPDATE_SOURCE); + if (peer->update_source) { + if (sockunion_cmp(peer->update_source, su) == 0) + return; + sockunion_free(peer->update_source); + } + peer->update_source = sockunion_dup(su); + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); + + /* Apply new source configuration to BFD session. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + + /* Skip peer-group mechanics for regular peers. */ + return; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) + continue; + + /* Skip peers with the same configuration. */ + if (member->update_source) { + if (sockunion_cmp(member->update_source, su) == 0) + continue; + sockunion_free(member->update_source); + } + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); + member->update_source = sockunion_dup(su); + XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); + + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) { + member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(member); + + /* Apply new source configuration to BFD session. */ + if (member->bfd_config) + bgp_peer_bfd_update_source(member); + } +} + +void peer_update_source_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + bool src_unchanged = false; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_UPDATE_SOURCE)) + return; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + /* Don't reset peer if the update_source we'll inherit from + * the peer-group matches the peer's existing update_source + */ + src_unchanged = + (peer->update_source && + peer->group->conf->update_source && + sockunion_cmp(peer->update_source, + peer->group->conf->update_source) == 0); + + peer_flag_inherit(peer, PEER_FLAG_UPDATE_SOURCE); + PEER_SU_ATTR_INHERIT(peer, peer->group, update_source); + PEER_STR_ATTR_INHERIT(peer, peer->group, update_if, + MTYPE_PEER_UPDATE_SOURCE); + + if (src_unchanged) + return; + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_UPDATE_SOURCE); + sockunion_free(peer->update_source); + peer->update_source = NULL; + XFREE(MTYPE_PEER_UPDATE_SOURCE, peer->update_if); + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(peer); + + /* Apply new source configuration to BFD session. */ + if (peer->bfd_config) + bgp_peer_bfd_update_source(peer); + + /* Skip peer-group mechanics for regular peers. */ + return; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_UPDATE_SOURCE)) + continue; + + /* Skip peers with the same configuration. */ + if (!CHECK_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE) + && !member->update_source && !member->update_if) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_UPDATE_SOURCE); + sockunion_free(member->update_source); + member->update_source = NULL; + XFREE(MTYPE_PEER_UPDATE_SOURCE, member->update_if); + + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) { + member->last_reset = PEER_DOWN_UPDATE_SOURCE_CHANGE; + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(member); + + /* Apply new source configuration to BFD session. */ + if (member->bfd_config) + bgp_peer_bfd_update_source(member); + } +} + +int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi, + const char *rmap, struct route_map *route_map) +{ + struct peer *member; + struct listnode *node, *nnode; + struct update_subgroup *subgrp; + + /* Set flag and configuration on peer. */ + peer_af_flag_set(peer, afi, safi, PEER_FLAG_DEFAULT_ORIGINATE); + + subgrp = peer_subgroup(peer, afi, safi); + + if (rmap) { + if (!peer->default_rmap[afi][safi].name + || strcmp(rmap, peer->default_rmap[afi][safi].name) != 0) { + struct route_map *map = NULL; + + if (peer->default_rmap[afi][safi].name) { + map = route_map_lookup_by_name( + peer->default_rmap[afi][safi].name); + XFREE(MTYPE_ROUTE_MAP_NAME, + peer->default_rmap[afi][safi].name); + } + + /* + * When there is a change in route-map policy, + * this flow gets triggered. Since, the default + * route is already originated, the flag is set. + * The flag should be unset here, + * to trigger the flow of sending update message. + */ + if (subgrp) + UNSET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + + route_map_counter_decrement(map); + peer->default_rmap[afi][safi].name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + peer->default_rmap[afi][safi].map = route_map; + route_map_counter_increment(route_map); + } + } else if (!rmap) { + struct route_map *map = NULL; + + if (peer->default_rmap[afi][safi].name) { + map = route_map_lookup_by_name( + peer->default_rmap[afi][safi].name); + XFREE(MTYPE_ROUTE_MAP_NAME, + peer->default_rmap[afi][safi].name); + } + + /* + * This is triggered in case of route-map deletion. + * The flag needs to be unset, to trigger the flow + * of sending an update message. + */ + if (subgrp) + UNSET_FLAG(subgrp->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + + route_map_counter_decrement(map); + peer->default_rmap[afi][safi].name = NULL; + peer->default_rmap[afi][safi].map = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Update peer route announcements. */ + if (peer_established(peer->connection) && + peer->afc_nego[afi][safi]) { + update_group_adjust_peer(peer_af_find(peer, afi, safi)); + bgp_default_originate(peer, afi, safi, false); + bgp_announce_route(peer, afi, safi, false); + } + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE); + if (rmap) { + struct route_map *map = NULL; + + if (member->default_rmap[afi][safi].name) { + map = route_map_lookup_by_name( + member->default_rmap[afi][safi].name); + XFREE(MTYPE_ROUTE_MAP_NAME, + member->default_rmap[afi][safi].name); + } + + route_map_counter_decrement(map); + member->default_rmap[afi][safi].name = + XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); + member->default_rmap[afi][safi].map = route_map; + route_map_counter_increment(route_map); + } + + /* Update peer route announcements. */ + if (peer_established(member->connection) && + member->afc_nego[afi][safi]) { + update_group_adjust_peer( + peer_af_find(member, afi, safi)); + bgp_default_originate(member, afi, safi, false); + bgp_announce_route(member, afi, safi, false); + } + } + + return 0; +} + +int peer_default_originate_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_af_flag_inherit(peer, afi, safi, + PEER_FLAG_DEFAULT_ORIGINATE); + PEER_STR_ATTR_INHERIT(peer, peer->group, + default_rmap[afi][safi].name, + MTYPE_ROUTE_MAP_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + default_rmap[afi][safi].map); + } else { + struct route_map *map = NULL; + + /* Otherwise remove flag and configuration from peer. */ + peer_af_flag_unset(peer, afi, safi, + PEER_FLAG_DEFAULT_ORIGINATE); + if (peer->default_rmap[afi][safi].name) { + map = route_map_lookup_by_name( + peer->default_rmap[afi][safi].name); + XFREE(MTYPE_ROUTE_MAP_NAME, + peer->default_rmap[afi][safi].name); + } + route_map_counter_decrement(map); + peer->default_rmap[afi][safi].name = NULL; + peer->default_rmap[afi][safi].map = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Update peer route announcements. */ + if (peer_established(peer->connection) && + peer->afc_nego[afi][safi]) { + update_group_adjust_peer(peer_af_find(peer, afi, safi)); + bgp_default_originate(peer, afi, safi, true); + bgp_announce_route(peer, afi, safi, false); + } + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + struct route_map *map; + + map = NULL; + + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_DEFAULT_ORIGINATE); + if (member->default_rmap[afi][safi].name) { + map = route_map_lookup_by_name( + member->default_rmap[afi][safi].name); + XFREE(MTYPE_ROUTE_MAP_NAME, + member->default_rmap[afi][safi].name); + } + route_map_counter_decrement(map); + member->default_rmap[afi][safi].name = NULL; + member->default_rmap[afi][safi].map = NULL; + + /* Update peer route announcements. */ + if (peer_established(member->connection) && + member->afc_nego[afi][safi]) { + update_group_adjust_peer(peer_af_find(member, afi, safi)); + bgp_default_originate(member, afi, safi, true); + bgp_announce_route(member, afi, safi, false); + } + } + + return 0; +} + +void peer_port_set(struct peer *peer, uint16_t port) +{ + peer->port = port; + peer_flag_set(peer, PEER_FLAG_PORT); +} + +void peer_port_unset(struct peer *peer) +{ + peer->port = BGP_PORT_DEFAULT; + peer_flag_unset(peer, PEER_FLAG_PORT); +} + +/* Set the TCP-MSS value in the peer structure, + * This gets applied only after connection reset + * So this value will be used in bgp_connect. + */ +void peer_tcp_mss_set(struct peer *peer, uint32_t tcp_mss) +{ + peer->tcp_mss = tcp_mss; + SET_FLAG(peer->flags, PEER_FLAG_TCP_MSS); + bgp_tcp_mss_set(peer); +} + +/* Reset the TCP-MSS value in the peer structure, + * This gets applied only after connection reset + * So this value will be used in bgp_connect. + */ +void peer_tcp_mss_unset(struct peer *peer) +{ + UNSET_FLAG(peer->flags, PEER_FLAG_TCP_MSS); + peer->tcp_mss = 0; + bgp_tcp_mss_set(peer); +} + +/* + * Helper function that is called after the name of the policy + * being used by a peer has changed (AF specific). Automatically + * initiates inbound or outbound processing as needed. + */ +void peer_on_policy_change(struct peer *peer, afi_t afi, safi_t safi, + int outbound) +{ + if (outbound) { + update_group_adjust_peer(peer_af_find(peer, afi, safi)); + if (peer_established(peer->connection)) + bgp_announce_route(peer, afi, safi, false); + } else { + if (!peer_established(peer->connection)) + return; + + if (bgp_soft_reconfig_in(peer, afi, safi)) + return; + + if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_RCV)) + bgp_route_refresh_send(peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_NORMAL); + } +} + + +/* neighbor weight. */ +int peer_weight_set(struct peer *peer, afi_t afi, safi_t safi, uint16_t weight) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set flag and configuration on peer. */ + peer_af_flag_set(peer, afi, safi, PEER_FLAG_WEIGHT); + if (peer->weight[afi][safi] != weight) { + peer->weight[afi][safi] = weight; + peer_on_policy_change(peer, afi, safi, 0); + } + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_WEIGHT)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT); + if (member->weight[afi][safi] != weight) { + member->weight[afi][safi] = weight; + peer_on_policy_change(member, afi, safi, 0); + } + } + + return 0; +} + +int peer_weight_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_WEIGHT)) + return 0; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_WEIGHT); + PEER_ATTR_INHERIT(peer, peer->group, weight[afi][safi]); + + peer_on_policy_change(peer, afi, safi, 0); + return 0; + } + + /* Remove flag and configuration from peer. */ + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_WEIGHT); + peer->weight[afi][safi] = 0; + peer_on_policy_change(peer, afi, safi, 0); + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_WEIGHT)) + continue; + + /* Skip peers where flag is already disabled. */ + if (!CHECK_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_WEIGHT); + member->weight[afi][safi] = 0; + peer_on_policy_change(member, afi, safi, 0); + } + + return 0; +} + +int peer_timers_set(struct peer *peer, uint32_t keepalive, uint32_t holdtime) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (keepalive > UINT16_MAX) + return BGP_ERR_INVALID_VALUE; + + if (holdtime > UINT16_MAX) + return BGP_ERR_INVALID_VALUE; + + if (holdtime < 3 && holdtime != 0) + return BGP_ERR_INVALID_VALUE; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_TIMER); + peer->holdtime = holdtime; + peer->keepalive = (keepalive < holdtime / 3 ? keepalive : holdtime / 3); + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_TIMER); + PEER_ATTR_INHERIT(member, peer->group, holdtime); + PEER_ATTR_INHERIT(member, peer->group, keepalive); + } + + return 0; +} + +int peer_timers_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_TIMER); + PEER_ATTR_INHERIT(peer, peer->group, holdtime); + PEER_ATTR_INHERIT(peer, peer->group, keepalive); + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_TIMER); + peer->holdtime = 0; + peer->keepalive = 0; + } + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_TIMER); + member->holdtime = 0; + member->keepalive = 0; + } + + return 0; +} + +int peer_timers_connect_set(struct peer *peer, uint32_t connect) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (connect > UINT16_MAX) + return BGP_ERR_INVALID_VALUE; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_TIMER_CONNECT); + peer->connect = connect; + peer->v_connect = connect; + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (!peer_established(peer->connection)) { + if (peer_active(peer)) + BGP_EVENT_ADD(peer->connection, BGP_Stop); + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + return 0; + } + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER_CONNECT)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_TIMER_CONNECT); + member->connect = connect; + member->v_connect = connect; + + if (!peer_established(member->connection)) { + if (peer_active(member)) + BGP_EVENT_ADD(member->connection, BGP_Stop); + BGP_EVENT_ADD(member->connection, BGP_Start); + } + } + + return 0; +} + +int peer_timers_connect_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_TIMER_CONNECT); + PEER_ATTR_INHERIT(peer, peer->group, connect); + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_TIMER_CONNECT); + peer->connect = 0; + } + + /* Set timer with fallback to default value. */ + if (peer->connect) + peer->v_connect = peer->connect; + else + peer->v_connect = peer->bgp->default_connect_retry; + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + if (!peer_established(peer->connection)) { + if (peer_active(peer)) + BGP_EVENT_ADD(peer->connection, BGP_Stop); + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + return 0; + } + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_TIMER_CONNECT)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_TIMER_CONNECT); + member->connect = 0; + member->v_connect = peer->bgp->default_connect_retry; + + if (!peer_established(member->connection)) { + if (peer_active(member)) + BGP_EVENT_ADD(member->connection, BGP_Stop); + BGP_EVENT_ADD(member->connection, BGP_Start); + } + } + + return 0; +} + +int peer_advertise_interval_set(struct peer *peer, uint32_t routeadv) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (routeadv > 600) + return BGP_ERR_INVALID_VALUE; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_ROUTEADV); + peer->routeadv = routeadv; + peer->v_routeadv = routeadv; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Update peer route announcements. */ + update_group_adjust_peer_afs(peer); + if (peer_established(peer->connection)) + bgp_announce_route_all(peer); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_ROUTEADV)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_ROUTEADV); + member->routeadv = routeadv; + member->v_routeadv = routeadv; + + /* Update peer route announcements. */ + update_group_adjust_peer_afs(member); + if (peer_established(member->connection)) + bgp_announce_route_all(member); + } + + return 0; +} + +int peer_advertise_interval_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_ROUTEADV); + PEER_ATTR_INHERIT(peer, peer->group, routeadv); + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_ROUTEADV); + peer->routeadv = 0; + } + + /* Set timer with fallback to default value. */ + if (peer->routeadv) + peer->v_routeadv = peer->routeadv; + else + peer->v_routeadv = (peer->sort == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Update peer route announcements. */ + update_group_adjust_peer_afs(peer); + if (peer_established(peer->connection)) + bgp_announce_route_all(peer); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_ROUTEADV)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_ROUTEADV); + member->routeadv = 0; + member->v_routeadv = (member->sort == BGP_PEER_IBGP) + ? BGP_DEFAULT_IBGP_ROUTEADV + : BGP_DEFAULT_EBGP_ROUTEADV; + + /* Update peer route announcements. */ + update_group_adjust_peer_afs(member); + if (peer_established(member->connection)) + bgp_announce_route_all(member); + } + + return 0; +} + +/* set the peers RFC 4271 DelayOpen session attribute flag and DelayOpenTimer + * interval + */ +int peer_timers_delayopen_set(struct peer *peer, uint32_t delayopen) +{ + struct peer *member; + struct listnode *node; + + /* Set peers session attribute flag and timer interval. */ + peer_flag_set(peer, PEER_FLAG_TIMER_DELAYOPEN); + peer->delayopen = delayopen; + peer->v_delayopen = delayopen; + + /* Skip group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS_RO(peer->group->peer, node, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, + PEER_FLAG_TIMER_DELAYOPEN)) + continue; + + /* Set session attribute flag and timer intervals on peer-group + * member. + */ + SET_FLAG(member->flags, PEER_FLAG_TIMER_DELAYOPEN); + member->delayopen = delayopen; + member->v_delayopen = delayopen; + } + + return 0; +} + +/* unset the peers RFC 4271 DelayOpen session attribute flag and reset the + * DelayOpenTimer interval to the default value. + */ +int peer_timers_delayopen_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_TIMER_DELAYOPEN); + PEER_ATTR_INHERIT(peer, peer->group, delayopen); + } else { + /* Otherwise remove session attribute flag and set timer + * interval to default value. + */ + peer_flag_unset(peer, PEER_FLAG_TIMER_DELAYOPEN); + peer->delayopen = peer->bgp->default_delayopen; + } + + /* Set timer value to zero */ + peer->v_delayopen = 0; + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS_RO(peer->group->peer, node, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, + PEER_FLAG_TIMER_DELAYOPEN)) + continue; + + /* Remove session attribute flag, reset the timer interval to + * the default value and set the timer value to zero. + */ + UNSET_FLAG(member->flags, PEER_FLAG_TIMER_DELAYOPEN); + member->delayopen = peer->bgp->default_delayopen; + member->v_delayopen = 0; + } + + return 0; +} + +/* neighbor interface */ +void peer_interface_set(struct peer *peer, const char *str) +{ + XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); + peer->ifname = XSTRDUP(MTYPE_BGP_PEER_IFNAME, str); +} + +void peer_interface_unset(struct peer *peer) +{ + XFREE(MTYPE_BGP_PEER_IFNAME, peer->ifname); +} + +/* Allow-as in. */ +int peer_allowas_in_set(struct peer *peer, afi_t afi, safi_t safi, + int allow_num, int origin) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (!origin && (allow_num < 1 || allow_num > 10)) + return BGP_ERR_INVALID_VALUE; + + /* Set flag and configuration on peer. */ + peer_af_flag_set(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); + if (origin) { + if (peer->allowas_in[afi][safi] != 0 + || !CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) { + peer_af_flag_set(peer, afi, safi, + PEER_FLAG_ALLOWAS_IN_ORIGIN); + peer->allowas_in[afi][safi] = 0; + peer_on_policy_change(peer, afi, safi, 0); + } + } else { + if (peer->allowas_in[afi][safi] != allow_num + || CHECK_FLAG(peer->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) { + + peer_af_flag_unset(peer, afi, safi, + PEER_FLAG_ALLOWAS_IN_ORIGIN); + peer->allowas_in[afi][safi] = allow_num; + peer_on_policy_change(peer, afi, safi, 0); + } + } + + /* Skip peer-group mechanics for regular peers. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Set flag and configuration on all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_ALLOWAS_IN)) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN); + if (origin) { + if (member->allowas_in[afi][safi] != 0 + || !CHECK_FLAG(member->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) { + SET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN); + member->allowas_in[afi][safi] = 0; + peer_on_policy_change(peer, afi, safi, 0); + } + } else { + if (member->allowas_in[afi][safi] != allow_num + || CHECK_FLAG(member->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN)) { + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN); + member->allowas_in[afi][safi] = allow_num; + peer_on_policy_change(peer, afi, safi, 0); + } + } + } + + return 0; +} + +int peer_allowas_in_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Skip peer if flag is already disabled. */ + if (!CHECK_FLAG(peer->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN)) + return 0; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); + peer_af_flag_inherit(peer, afi, safi, + PEER_FLAG_ALLOWAS_IN_ORIGIN); + PEER_ATTR_INHERIT(peer, peer->group, allowas_in[afi][safi]); + peer_on_policy_change(peer, afi, safi, 0); + + return 0; + } + + /* Remove flag and configuration from peer. */ + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ALLOWAS_IN); + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_ALLOWAS_IN_ORIGIN); + peer->allowas_in[afi][safi] = 0; + peer_on_policy_change(peer, afi, safi, 0); + + /* Skip peer-group mechanics if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Remove flags and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_ALLOWAS_IN)) + continue; + + /* Remove flags and configuration on peer-group member. */ + UNSET_FLAG(member->af_flags[afi][safi], PEER_FLAG_ALLOWAS_IN); + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_ALLOWAS_IN_ORIGIN); + member->allowas_in[afi][safi] = 0; + peer_on_policy_change(member, afi, safi, 0); + } + + return 0; +} + +int peer_local_as_set(struct peer *peer, as_t as, bool no_prepend, + bool replace_as, const char *as_str) +{ + bool old_no_prepend, old_replace_as; + struct bgp *bgp = peer->bgp; + struct peer *member; + struct listnode *node, *nnode; + + if (bgp->as == as) + return BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS; + + /* Save previous flag states. */ + old_no_prepend = + !!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND); + old_replace_as = + !!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS); + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_LOCAL_AS); + peer_flag_modify(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND, no_prepend); + peer_flag_modify(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS, replace_as); + + if (peer->change_local_as == as && old_no_prepend == no_prepend + && old_replace_as == replace_as) + return 0; + peer->change_local_as = as; + if (as_str) { + if (peer->change_local_as_pretty) + XFREE(MTYPE_BGP_NAME, peer->change_local_as_pretty); + peer->change_local_as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_str); + } + + (void)peer_sort(peer); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + return 0; + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_LOCAL_AS)) + continue; + + /* Skip peers with the same configuration. */ + old_no_prepend = CHECK_FLAG(member->flags, + PEER_FLAG_LOCAL_AS_NO_PREPEND); + old_replace_as = CHECK_FLAG(member->flags, + PEER_FLAG_LOCAL_AS_REPLACE_AS); + if (member->change_local_as == as + && CHECK_FLAG(member->flags, PEER_FLAG_LOCAL_AS) + && old_no_prepend == no_prepend + && old_replace_as == replace_as) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_LOCAL_AS); + COND_FLAG(member->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND, + no_prepend); + COND_FLAG(member->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS, + replace_as); + member->change_local_as = as; + if (as_str) + member->change_local_as_pretty = XSTRDUP(MTYPE_BGP_NAME, + as_str); + } + + return 0; +} + +int peer_local_as_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_LOCAL_AS)) + return 0; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS); + peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND); + peer_flag_inherit(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS); + PEER_ATTR_INHERIT(peer, peer->group, change_local_as); + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_LOCAL_AS); + peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_NO_PREPEND); + peer_flag_unset(peer, PEER_FLAG_LOCAL_AS_REPLACE_AS); + peer->change_local_as = 0; + XFREE(MTYPE_BGP_NAME, peer->change_local_as_pretty); + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or stop peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) { + peer->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + BGP_EVENT_ADD(peer->connection, BGP_Stop); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_LOCAL_AS)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS); + UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS_NO_PREPEND); + UNSET_FLAG(member->flags, PEER_FLAG_LOCAL_AS_REPLACE_AS); + member->change_local_as = 0; + XFREE(MTYPE_BGP_NAME, member->change_local_as_pretty); + + /* Send notification or stop peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) { + member->last_reset = PEER_DOWN_LOCAL_AS_CHANGE; + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + } else + bgp_session_reset(member); + } + + return 0; +} + +/* Set password for authenticating with the peer. */ +int peer_password_set(struct peer *peer, const char *password) +{ + struct peer *member; + struct listnode *node, *nnode; + int len = password ? strlen(password) : 0; + int ret = BGP_SUCCESS; + + if ((len < PEER_PASSWORD_MINLEN) || (len > PEER_PASSWORD_MAXLEN)) + return BGP_ERR_INVALID_VALUE; + + /* Set flag and configuration on peer. */ + peer_flag_set(peer, PEER_FLAG_PASSWORD); + if (peer->password && strcmp(peer->password, password) == 0) + return 0; + XFREE(MTYPE_PEER_PASSWORD, peer->password); + peer->password = XSTRDUP(MTYPE_PEER_PASSWORD, password); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + + /* + * Attempt to install password on socket and skip peer-group + * mechanics. + */ + if (BGP_CONNECTION_SU_UNSPEC(peer->connection)) + return BGP_SUCCESS; + return (bgp_md5_set(peer->connection) >= 0) + ? BGP_SUCCESS + : BGP_ERR_TCPSIG_FAILED; + } + + /* + * Set flag and configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_PASSWORD)) + continue; + + /* Skip peers with the same password. */ + if (member->password && strcmp(member->password, password) == 0) + continue; + + /* Set flag and configuration on peer-group member. */ + SET_FLAG(member->flags, PEER_FLAG_PASSWORD); + if (member->password) + XFREE(MTYPE_PEER_PASSWORD, member->password); + member->password = XSTRDUP(MTYPE_PEER_PASSWORD, password); + + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(member); + + /* Attempt to install password on socket. */ + if (!BGP_CONNECTION_SU_UNSPEC(member->connection) && + bgp_md5_set(member->connection) < 0) + ret = BGP_ERR_TCPSIG_FAILED; + } + + /* Set flag and configuration on all peer-group listen ranges */ + struct listnode *ln; + struct prefix *lr; + + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) + bgp_md5_set_prefix(peer->bgp, lr, password); + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) + bgp_md5_set_prefix(peer->bgp, lr, password); + + return ret; +} + +int peer_password_unset(struct peer *peer) +{ + struct peer *member; + struct listnode *node, *nnode; + + if (!CHECK_FLAG(peer->flags, PEER_FLAG_PASSWORD)) + return 0; + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_flag_inherit(peer, PEER_FLAG_PASSWORD); + PEER_STR_ATTR_INHERIT(peer, peer->group, password, + MTYPE_PEER_PASSWORD); + } else { + /* Otherwise remove flag and configuration from peer. */ + peer_flag_unset(peer, PEER_FLAG_PASSWORD); + XFREE(MTYPE_PEER_PASSWORD, peer->password); + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(peer); + + /* Attempt to uninstall password on socket. */ + if (!BGP_CONNECTION_SU_UNSPEC(peer->connection)) + bgp_md5_unset(peer->connection); + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->flags_override, PEER_FLAG_PASSWORD)) + continue; + + /* Remove flag and configuration on peer-group member. */ + UNSET_FLAG(member->flags, PEER_FLAG_PASSWORD); + XFREE(MTYPE_PEER_PASSWORD, member->password); + + /* Send notification or reset peer depending on state. */ + if (BGP_IS_VALID_STATE_FOR_NOTIF(member->connection->status)) + bgp_notify_send(member->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_CONFIG_CHANGE); + else + bgp_session_reset(member); + + /* Attempt to uninstall password on socket. */ + if (!BGP_CONNECTION_SU_UNSPEC(member->connection)) + bgp_md5_unset(member->connection); + } + + /* Set flag and configuration on all peer-group listen ranges */ + struct listnode *ln; + struct prefix *lr; + + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP], ln, lr)) + bgp_md5_unset_prefix(peer->bgp, lr); + for (ALL_LIST_ELEMENTS_RO(peer->group->listen_range[AFI_IP6], ln, lr)) + bgp_md5_unset_prefix(peer->bgp, lr); + + return 0; +} + + +/* Set distribute list to the peer. */ +int peer_distribute_set(struct peer *peer, afi_t afi, safi_t safi, int direct, + const char *name) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Set configuration on peer. */ + filter = &peer->filter[afi][safi]; + if (filter->plist[direct].name) + return BGP_ERR_PEER_FILTER_CONFLICT; + if (filter->dlist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, filter->dlist[direct].name); + filter->dlist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->dlist[direct].alist = access_list_lookup(afi, name); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_DISTRIBUTE_LIST); + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set configuration on all peer-group members, un less they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_DISTRIBUTE_LIST)) + continue; + + /* Set configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->dlist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->dlist[direct].name); + filter->dlist[direct].name = + XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->dlist[direct].alist = access_list_lookup(afi, name); + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +int peer_distribute_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_DISTRIBUTE_LIST); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].dlist[direct].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].dlist[direct].alist); + } else { + /* Otherwise remove configuration from peer. */ + filter = &peer->filter[afi][safi]; + if (filter->dlist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->dlist[direct].name); + filter->dlist[direct].name = NULL; + filter->dlist[direct].alist = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_DISTRIBUTE_LIST)) + continue; + + /* Remove configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->dlist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->dlist[direct].name); + filter->dlist[direct].name = NULL; + filter->dlist[direct].alist = NULL; + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +/* Update distribute list. */ +static void peer_distribute_update(struct access_list *access) +{ + afi_t afi; + safi_t safi; + int direct; + struct listnode *mnode, *mnnode; + struct listnode *node, *nnode; + struct bgp *bgp; + struct peer *peer; + struct peer_group *group; + struct bgp_filter *filter; + + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + if (access->name) + update_group_policy_update(bgp, + BGP_POLICY_DISTRIBUTE_LIST, + access->name, true, 0); + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &peer->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->dlist[direct].name) + filter->dlist[direct] + .alist = access_list_lookup( + afi, + filter->dlist[direct] + .name); + else + filter->dlist[direct].alist = + NULL; + } + } + } + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &group->conf->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->dlist[direct].name) + filter->dlist[direct] + .alist = access_list_lookup( + afi, + filter->dlist[direct] + .name); + else + filter->dlist[direct].alist = + NULL; + } + } + } +#ifdef ENABLE_BGP_VNC + vnc_prefix_list_update(bgp); +#endif + } +} + +/* Set prefix list to the peer. */ +int peer_prefix_list_set(struct peer *peer, afi_t afi, safi_t safi, int direct, + const char *name) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Set configuration on peer. */ + filter = &peer->filter[afi][safi]; + if (filter->dlist[direct].name) + return BGP_ERR_PEER_FILTER_CONFLICT; + if (filter->plist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, filter->plist[direct].name); + filter->plist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->plist[direct].plist = prefix_list_lookup(afi, name); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_PREFIX_LIST); + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_PREFIX_LIST)) + continue; + + /* Set configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->plist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->plist[direct].name); + filter->plist[direct].name = + XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->plist[direct].plist = prefix_list_lookup(afi, name); + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +int peer_prefix_list_unset(struct peer *peer, afi_t afi, safi_t safi, + int direct) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_PREFIX_LIST); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].plist[direct].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].plist[direct].plist); + } else { + /* Otherwise remove configuration from peer. */ + filter = &peer->filter[afi][safi]; + if (filter->plist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->plist[direct].name); + filter->plist[direct].name = NULL; + filter->plist[direct].plist = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_PREFIX_LIST)) + continue; + + /* Remove configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->plist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->plist[direct].name); + filter->plist[direct].name = NULL; + filter->plist[direct].plist = NULL; + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +/* Update prefix-list list. */ +static void peer_prefix_list_update(struct prefix_list *plist) +{ + struct listnode *mnode, *mnnode; + struct listnode *node, *nnode; + struct bgp *bgp; + struct peer *peer; + struct peer_group *group; + struct bgp_filter *filter; + afi_t afi; + safi_t safi; + int direct; + + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + + /* + * Update the prefix-list on update groups. + */ + update_group_policy_update( + bgp, BGP_POLICY_PREFIX_LIST, + plist ? prefix_list_name(plist) : NULL, true, 0); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &peer->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->plist[direct].name) + filter->plist[direct] + .plist = prefix_list_lookup( + afi, + filter->plist[direct] + .name); + else + filter->plist[direct].plist = + NULL; + } + + /* If we touch prefix-list, we need to process + * new updates. This is important for ORF to + * work correctly. + */ + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV) && + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) + peer_clear_soft( + peer, afi, safi, + BGP_CLEAR_SOFT_IN_ORF_PREFIX); + } + } + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &group->conf->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->plist[direct].name) + filter->plist[direct] + .plist = prefix_list_lookup( + afi, + filter->plist[direct] + .name); + else + filter->plist[direct].plist = + NULL; + } + } + } + } +} + +int peer_aslist_set(struct peer *peer, afi_t afi, safi_t safi, int direct, + const char *name) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Set configuration on peer. */ + filter = &peer->filter[afi][safi]; + if (filter->aslist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, filter->aslist[direct].name); + filter->aslist[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->aslist[direct].aslist = as_list_lookup(name); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_FILTER_LIST); + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_FILTER_LIST)) + continue; + + /* Set configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->aslist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->aslist[direct].name); + filter->aslist[direct].name = + XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->aslist[direct].aslist = as_list_lookup(name); + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +int peer_aslist_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != FILTER_IN && direct != FILTER_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_FILTER_LIST); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].aslist[direct].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].aslist[direct].aslist); + } else { + /* Otherwise remove configuration from peer. */ + filter = &peer->filter[afi][safi]; + if (filter->aslist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->aslist[direct].name); + filter->aslist[direct].name = NULL; + filter->aslist[direct].aslist = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_FILTER_LIST)) + continue; + + /* Remove configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->aslist[direct].name) + XFREE(MTYPE_BGP_FILTER_NAME, + filter->aslist[direct].name); + filter->aslist[direct].name = NULL; + filter->aslist[direct].aslist = NULL; + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == FILTER_OUT) ? 1 : 0); + } + + return 0; +} + +static void peer_aslist_update(const char *aslist_name) +{ + afi_t afi; + safi_t safi; + int direct; + struct listnode *mnode, *mnnode; + struct listnode *node, *nnode; + struct bgp *bgp; + struct peer *peer; + struct peer_group *group; + struct bgp_filter *filter; + + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + update_group_policy_update(bgp, BGP_POLICY_FILTER_LIST, + aslist_name, true, 0); + + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &peer->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->aslist[direct].name) + filter->aslist[direct] + .aslist = as_list_lookup( + filter->aslist[direct] + .name); + else + filter->aslist[direct].aslist = + NULL; + } + } + } + for (ALL_LIST_ELEMENTS(bgp->group, node, nnode, group)) { + FOREACH_AFI_SAFI (afi, safi) { + filter = &group->conf->filter[afi][safi]; + + for (direct = FILTER_IN; direct < FILTER_MAX; + direct++) { + if (filter->aslist[direct].name) + filter->aslist[direct] + .aslist = as_list_lookup( + filter->aslist[direct] + .name); + else + filter->aslist[direct].aslist = + NULL; + } + } + } + } +} + +static void peer_aslist_add(char *aslist_name) +{ + peer_aslist_update(aslist_name); + route_map_notify_dependencies(aslist_name, RMAP_EVENT_ASLIST_ADDED); +} + +static void peer_aslist_del(const char *aslist_name) +{ + peer_aslist_update(aslist_name); + route_map_notify_dependencies(aslist_name, RMAP_EVENT_ASLIST_DELETED); +} + + +int peer_route_map_set(struct peer *peer, afi_t afi, safi_t safi, int direct, + const char *name, struct route_map *route_map) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + struct route_map *map = NULL; + + if (direct != RMAP_IN && direct != RMAP_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Set configuration on peer. */ + filter = &peer->filter[afi][safi]; + if (filter->map[direct].name) { + /* If the neighbor is configured with the same route-map + * again then, ignore the duplicate configuration. + */ + if (strcmp(filter->map[direct].name, name) == 0) + return 0; + + map = route_map_lookup_by_name(filter->map[direct].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); + } + route_map_counter_decrement(map); + filter->map[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->map[direct].map = route_map; + route_map_counter_increment(route_map); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][direct], + PEER_FT_ROUTE_MAP); + peer_on_policy_change(peer, afi, safi, + (direct == RMAP_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + map = NULL; + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_ROUTE_MAP)) + continue; + + /* Set configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->map[direct].name) { + map = route_map_lookup_by_name(filter->map[direct].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); + } + route_map_counter_decrement(map); + filter->map[direct].name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->map[direct].map = route_map; + route_map_counter_increment(route_map); + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == RMAP_OUT) ? 1 : 0); + } + return 0; +} + +/* Unset route-map from the peer. */ +int peer_route_map_unset(struct peer *peer, afi_t afi, safi_t safi, int direct) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + if (direct != RMAP_IN && direct != RMAP_OUT) + return BGP_ERR_INVALID_VALUE; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][direct], PEER_FT_ROUTE_MAP); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].map[direct].name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].map[direct].map); + } else { + struct route_map *map = NULL; + + /* Otherwise remove configuration from peer. */ + filter = &peer->filter[afi][safi]; + + if (filter->map[direct].name) { + map = route_map_lookup_by_name(filter->map[direct].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); + } + route_map_counter_decrement(map); + filter->map[direct].name = NULL; + filter->map[direct].map = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, + (direct == RMAP_OUT) ? 1 : 0); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + struct route_map *map; + + map = NULL; + + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][direct], + PEER_FT_ROUTE_MAP)) + continue; + + /* Remove configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->map[direct].name) { + map = route_map_lookup_by_name(filter->map[direct].name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->map[direct].name); + } + route_map_counter_decrement(map); + filter->map[direct].name = NULL; + filter->map[direct].map = NULL; + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, + (direct == RMAP_OUT) ? 1 : 0); + } + + return 0; +} + +/* Set unsuppress-map to the peer. */ +int peer_unsuppress_map_set(struct peer *peer, afi_t afi, safi_t safi, + const char *name, struct route_map *route_map) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + /* Set configuration on peer. */ + filter = &peer->filter[afi][safi]; + if (filter->usmap.name) + XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); + route_map_counter_decrement(filter->usmap.map); + filter->usmap.name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->usmap.map = route_map; + route_map_counter_increment(route_map); + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Set override-flag and process peer route updates. */ + SET_FLAG(peer->filter_override[afi][safi][0], + PEER_FT_UNSUPPRESS_MAP); + peer_on_policy_change(peer, afi, safi, 1); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + struct route_map *map; + + map = NULL; + + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][0], + PEER_FT_UNSUPPRESS_MAP)) + continue; + + /* Set configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->usmap.name) { + map = route_map_lookup_by_name(filter->usmap.name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); + } + route_map_counter_decrement(map); + filter->usmap.name = XSTRDUP(MTYPE_BGP_FILTER_NAME, name); + filter->usmap.map = route_map; + route_map_counter_increment(route_map); + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, 1); + } + + return 0; +} + +/* Unset route-map from the peer. */ +int peer_unsuppress_map_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *member; + struct bgp_filter *filter; + struct listnode *node, *nnode; + + /* Unset override-flag unconditionally. */ + UNSET_FLAG(peer->filter_override[afi][safi][0], PEER_FT_UNSUPPRESS_MAP); + + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + PEER_STR_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].usmap.name, + MTYPE_BGP_FILTER_NAME); + PEER_ATTR_INHERIT(peer, peer->group, + filter[afi][safi].usmap.map); + } else { + struct route_map *map = NULL; + + /* Otherwise remove configuration from peer. */ + filter = &peer->filter[afi][safi]; + if (filter->usmap.name) { + map = route_map_lookup_by_name(filter->usmap.name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); + } + route_map_counter_decrement(map); + filter->usmap.name = NULL; + filter->usmap.map = NULL; + } + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Process peer route updates. */ + peer_on_policy_change(peer, afi, safi, 1); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Remove configuration on all peer-group members, unless they are + * explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + struct route_map *map; + + map = NULL; + + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->filter_override[afi][safi][0], + PEER_FT_UNSUPPRESS_MAP)) + continue; + + /* Remove configuration on peer-group member. */ + filter = &member->filter[afi][safi]; + if (filter->usmap.name) { + map = route_map_lookup_by_name(filter->usmap.name); + XFREE(MTYPE_BGP_FILTER_NAME, filter->usmap.name); + } + route_map_counter_decrement(map); + filter->usmap.name = NULL; + filter->usmap.map = NULL; + + /* Process peer route updates. */ + peer_on_policy_change(member, afi, safi, 1); + } + + return 0; +} + +static bool peer_maximum_prefix_clear_overflow(struct peer *peer) +{ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW)) + return false; + + UNSET_FLAG(peer->sflags, PEER_STATUS_PREFIX_OVERFLOW); + if (peer->connection->t_pmax_restart) { + EVENT_OFF(peer->connection->t_pmax_restart); + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP Maximum-prefix restart timer cancelled", + peer); + } + BGP_EVENT_ADD(peer->connection, BGP_Start); + return true; +} + +int peer_maximum_prefix_set(struct peer *peer, afi_t afi, safi_t safi, + uint32_t max, uint8_t threshold, int warning, + uint16_t restart, bool force) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set flags and configuration on peer. */ + peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX); + + if (force) + peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX_FORCE); + else + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_FORCE); + + if (warning) + peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); + else + peer_af_flag_unset(peer, afi, safi, + PEER_FLAG_MAX_PREFIX_WARNING); + + peer->pmax[afi][safi] = max; + peer->pmax_threshold[afi][safi] = threshold; + peer->pmax_restart[afi][safi] = restart; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Re-check if peer violates maximum-prefix. */ + if ((peer_established(peer->connection)) && + (peer->afc[afi][safi])) + bgp_maximum_prefix_overflow(peer, afi, safi, 1); + + /* Skip peer-group mechanics for regular peers. */ + return 0; + } + + /* + * Set flags and configuration on all peer-group members, unless they + * are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_MAX_PREFIX)) + continue; + + /* Set flag and configuration on peer-group member. */ + member->pmax[afi][safi] = max; + member->pmax_threshold[afi][safi] = threshold; + member->pmax_restart[afi][safi] = restart; + + if (force) + SET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_FORCE); + else + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_FORCE); + + if (warning) + SET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING); + else + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING); + + /* Re-check if peer violates maximum-prefix. */ + if ((peer_established(member->connection)) && + (member->afc[afi][safi])) + bgp_maximum_prefix_overflow(member, afi, safi, 1); + } + + return 0; +} + +int peer_maximum_prefix_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_MAX_PREFIX); + peer_af_flag_inherit(peer, afi, safi, + PEER_FLAG_MAX_PREFIX_FORCE); + peer_af_flag_inherit(peer, afi, safi, + PEER_FLAG_MAX_PREFIX_WARNING); + PEER_ATTR_INHERIT(peer, peer->group, pmax[afi][safi]); + PEER_ATTR_INHERIT(peer, peer->group, pmax_threshold[afi][safi]); + PEER_ATTR_INHERIT(peer, peer->group, pmax_restart[afi][safi]); + + return 0; + } + + /* Remove flags and configuration from peer. */ + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX); + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_FORCE); + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_WARNING); + peer->pmax[afi][safi] = 0; + peer->pmax_threshold[afi][safi] = 0; + peer->pmax_restart[afi][safi] = 0; + + /* + * Remove flags and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + struct peer *member; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(peer->group->peer, node, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_MAX_PREFIX)) + continue; + + /* Remove flag and configuration on peer-group member. + */ + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX); + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_FORCE); + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_WARNING); + member->pmax[afi][safi] = 0; + member->pmax_threshold[afi][safi] = 0; + member->pmax_restart[afi][safi] = 0; + + peer_maximum_prefix_clear_overflow(member); + } + } else { + peer_maximum_prefix_clear_overflow(peer); + } + + return 0; +} + +void peer_maximum_prefix_out_refresh_routes(struct peer *peer, afi_t afi, + safi_t safi) +{ + update_group_adjust_peer(peer_af_find(peer, afi, safi)); + + if (peer_established(peer->connection)) + bgp_announce_route(peer, afi, safi, false); +} + +int peer_maximum_prefix_out_set(struct peer *peer, afi_t afi, safi_t safi, + uint32_t max) +{ + struct peer *member; + struct listnode *node, *nnode; + + /* Set flag on peer and peer-group member if any */ + peer_af_flag_set(peer, afi, safi, PEER_FLAG_MAX_PREFIX_OUT); + /* Set configuration on peer. */ + peer->pmax_out[afi][safi] = max; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Skip peer-group mechanics for regular peers. */ + peer_maximum_prefix_out_refresh_routes(peer, afi, safi); + return 0; + } + + /* + * Set flag and configuration on all peer-group members, unless they + * are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_MAX_PREFIX_OUT)) + continue; + + /* Set configuration on peer-group member. */ + member->pmax_out[afi][safi] = max; + + peer_maximum_prefix_out_refresh_routes(member, afi, safi); + } + return 0; +} + +int peer_maximum_prefix_out_unset(struct peer *peer, afi_t afi, safi_t safi) +{ + struct peer *member; + struct listnode *node; + /* Inherit configuration from peer-group if peer is member. */ + if (peer_group_active(peer)) { + peer_af_flag_inherit(peer, afi, safi, PEER_FLAG_MAX_PREFIX_OUT); + PEER_ATTR_INHERIT(peer, peer->group, pmax_out[afi][safi]); + + peer_maximum_prefix_out_refresh_routes(peer, afi, safi); + return 0; + } + + /* Remove flag and configuration from peer. */ + peer_af_flag_unset(peer, afi, safi, PEER_FLAG_MAX_PREFIX_OUT); + peer->pmax_out[afi][safi] = 0; + + /* Check if handling a regular peer. */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Skip peer-group mechanics for regular peers. */ + peer_maximum_prefix_out_refresh_routes(peer, afi, safi); + return 0; + } + + /* + * Remove flag and configuration from all peer-group members, unless + * they are explicitly overriding peer-group configuration. + */ + for (ALL_LIST_ELEMENTS_RO(peer->group->peer, node, member)) { + /* Skip peers with overridden configuration. */ + if (CHECK_FLAG(member->af_flags_override[afi][safi], + PEER_FLAG_MAX_PREFIX_OUT)) + continue; + + /* Remove flag and configuration on peer-group member. + */ + UNSET_FLAG(member->af_flags[afi][safi], + PEER_FLAG_MAX_PREFIX_OUT); + member->pmax_out[afi][safi] = 0; + + peer_maximum_prefix_out_refresh_routes(member, afi, safi); + } + return 0; +} + +int is_ebgp_multihop_configured(struct peer *peer) +{ + struct peer_group *group; + struct listnode *node, *nnode; + struct peer *peer1; + + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + group = peer->group; + if ((peer_sort(peer) != BGP_PEER_IBGP) + && (group->conf->ttl != BGP_DEFAULT_TTL)) + return 1; + + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer1)) { + if ((peer_sort(peer1) != BGP_PEER_IBGP) + && (peer1->ttl != BGP_DEFAULT_TTL)) + return 1; + } + } else { + if ((peer_sort(peer) != BGP_PEER_IBGP) + && (peer->ttl != BGP_DEFAULT_TTL)) + return 1; + } + return 0; +} + +/* Set # of hops between us and BGP peer. */ +int peer_ttl_security_hops_set(struct peer *peer, int gtsm_hops) +{ + struct peer_group *group; + struct peer *gpeer; + struct listnode *node, *nnode; + int ret; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: set gtsm_hops to %d for %s", __func__, + gtsm_hops, peer->host); + + /* We cannot configure ttl-security hops when ebgp-multihop is already + set. For non peer-groups, the check is simple. For peer-groups, + it's + slightly messy, because we need to check both the peer-group + structure + and all peer-group members for any trace of ebgp-multihop + configuration + before actually applying the ttl-security rules. Cisco really made a + mess of this configuration parameter, and OpenBGPD got it right. + */ + + if ((peer->gtsm_hops == BGP_GTSM_HOPS_DISABLED) + && (peer->sort != BGP_PEER_IBGP)) { + if (is_ebgp_multihop_configured(peer)) + return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK; + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + peer->gtsm_hops = gtsm_hops; + + /* Calling ebgp multihop also resets the session. + * On restart, NHT will get setup correctly as will the + * min & max ttls on the socket. The return value is + * irrelevant. + */ + ret = peer_ebgp_multihop_set(peer, MAXTTL); + + if (ret != 0) + return ret; + } else { + group = peer->group; + group->conf->gtsm_hops = gtsm_hops; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, + gpeer)) { + gpeer->gtsm_hops = group->conf->gtsm_hops; + + /* Calling ebgp multihop also resets the + * session. + * On restart, NHT will get setup correctly as + * will the + * min & max ttls on the socket. The return + * value is + * irrelevant. + */ + peer_ebgp_multihop_set(gpeer, MAXTTL); + } + } + } else { + /* Post the first gtsm setup or if its ibgp, maxttl setting + * isn't + * necessary, just set the minttl. + */ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + peer->gtsm_hops = gtsm_hops; + + if (peer->connection->fd >= 0) + sockopt_minttl(peer->connection->su.sa.sa_family, + peer->connection->fd, + MAXTTL + 1 - gtsm_hops); + if ((peer->connection->status < Established) && + peer->doppelganger && + (peer->doppelganger->connection->fd >= 0)) + sockopt_minttl(peer->connection->su.sa.sa_family, + peer->doppelganger->connection->fd, + MAXTTL + 1 - gtsm_hops); + } else { + group = peer->group; + group->conf->gtsm_hops = gtsm_hops; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, + gpeer)) { + struct peer_connection *connection = + gpeer->connection; + gpeer->gtsm_hops = group->conf->gtsm_hops; + + /* Change setting of existing peer + * established then change value (may break + * connectivity) + * not established yet (teardown session and + * restart) + * no session then do nothing (will get + * handled by next connection) + */ + if (connection->fd >= 0 && + gpeer->gtsm_hops != BGP_GTSM_HOPS_DISABLED) + sockopt_minttl(connection->su.sa.sa_family, + connection->fd, + MAXTTL + 1 - + gpeer->gtsm_hops); + if ((connection->status < Established) && + gpeer->doppelganger && + (gpeer->doppelganger->connection->fd >= 0)) + sockopt_minttl(connection->su.sa.sa_family, + gpeer->doppelganger + ->connection->fd, + MAXTTL + 1 - gtsm_hops); + } + } + } + + return 0; +} + +int peer_ttl_security_hops_unset(struct peer *peer) +{ + struct peer_group *group; + struct listnode *node, *nnode; + int ret = 0; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: set gtsm_hops to zero for %s", __func__, + peer->host); + + /* if a peer-group member, then reset to peer-group default rather than + * 0 */ + if (peer_group_active(peer)) + peer->gtsm_hops = peer->group->conf->gtsm_hops; + else + peer->gtsm_hops = BGP_GTSM_HOPS_DISABLED; + + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) { + /* Invoking ebgp_multihop_set will set the TTL back to the + * original + * value as well as restting the NHT and such. The session is + * reset. + */ + if (peer->sort == BGP_PEER_EBGP) + ret = peer_ebgp_multihop_unset(peer); + else { + if (peer->connection->fd >= 0) + sockopt_minttl(peer->connection->su.sa.sa_family, + peer->connection->fd, 0); + + if ((peer->connection->status < Established) && + peer->doppelganger && + (peer->doppelganger->connection->fd >= 0)) + sockopt_minttl(peer->connection->su.sa.sa_family, + peer->doppelganger->connection->fd, + 0); + } + } else { + group = peer->group; + for (ALL_LIST_ELEMENTS(group->peer, node, nnode, peer)) { + peer->gtsm_hops = BGP_GTSM_HOPS_DISABLED; + if (peer->sort == BGP_PEER_EBGP) + ret = peer_ebgp_multihop_unset(peer); + else { + if (peer->connection->fd >= 0) + sockopt_minttl(peer->connection->su.sa + .sa_family, + peer->connection->fd, 0); + + if ((peer->connection->status < Established) && + peer->doppelganger && + (peer->doppelganger->connection->fd >= 0)) + sockopt_minttl(peer->connection->su.sa + .sa_family, + peer->doppelganger + ->connection->fd, + 0); + } + } + } + + return ret; +} + +static void peer_reset_message_stats(struct peer *peer) +{ + if (peer) { + atomic_store_explicit(&peer->open_in, 0, memory_order_relaxed); + atomic_store_explicit(&peer->open_out, 0, memory_order_relaxed); + atomic_store_explicit(&peer->update_in, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->update_out, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->keepalive_in, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->keepalive_out, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->notify_in, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->notify_out, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->refresh_in, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->refresh_out, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->dynamic_cap_in, 0, + memory_order_relaxed); + atomic_store_explicit(&peer->dynamic_cap_out, 0, + memory_order_relaxed); + } +} + +/* Helper function to resend some BGP capabilities that are uncontrolled. + * For instance, FQDN capability, that can't be turned off, but let's say + * we changed the hostname, we need to resend it. + */ +static void peer_clear_capabilities(struct peer *peer, afi_t afi, safi_t safi) +{ + bgp_capability_send(peer, afi, safi, CAPABILITY_CODE_FQDN, + CAPABILITY_ACTION_SET); +} + +/* + * If peer clear is invoked in a loop for all peers on the BGP instance, + * it may end up freeing the doppelganger, and if this was the next node + * to the current node, we would end up accessing the freed next node. + * Pass along additional parameter which can be updated if next node + * is freed; only required when walking the peer list on BGP instance. + */ +int peer_clear(struct peer *peer, struct listnode **nnode) +{ + if (!CHECK_FLAG(peer->flags, PEER_FLAG_SHUTDOWN) + || !CHECK_FLAG(peer->bgp->flags, BGP_FLAG_SHUTDOWN)) { + if (peer_maximum_prefix_clear_overflow(peer)) + return 0; + + peer->v_start = BGP_INIT_START_TIMER; + if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status)) + bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_ADMIN_RESET); + else + bgp_session_reset_safe(peer, nnode); + } + return 0; +} + +int peer_clear_soft(struct peer *peer, afi_t afi, safi_t safi, + enum bgp_clear_type stype) +{ + struct peer_af *paf; + + if (!peer_established(peer->connection)) + return 0; + + if (!peer->afc[afi][safi]) + return BGP_ERR_AF_UNCONFIGURED; + + peer->rtt = sockopt_tcp_rtt(peer->connection->fd); + + if (stype == BGP_CLEAR_SOFT_OUT || stype == BGP_CLEAR_SOFT_BOTH) { + /* Clear the "neighbor x.x.x.x default-originate" flag */ + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup + && CHECK_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE)) + UNSET_FLAG(paf->subgroup->sflags, + SUBGRP_STATUS_DEFAULT_ORIGINATE); + + bgp_announce_route(peer, afi, safi, false); + } + + if (stype == BGP_CLEAR_SOFT_IN_ORF_PREFIX) { + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_SM_ADV) && + CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) { + struct bgp_filter *filter = &peer->filter[afi][safi]; + uint8_t prefix_type; + + if (CHECK_FLAG(peer->af_cap[afi][safi], + PEER_CAP_ORF_PREFIX_RM_RCV)) + prefix_type = ORF_TYPE_PREFIX; + + if (filter->plist[FILTER_IN].plist) { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND)) + bgp_route_refresh_send( + peer, afi, safi, prefix_type, + REFRESH_DEFER, 1, + BGP_ROUTE_REFRESH_NORMAL); + bgp_route_refresh_send( + peer, afi, safi, prefix_type, + REFRESH_IMMEDIATE, 0, + BGP_ROUTE_REFRESH_NORMAL); + } else { + if (CHECK_FLAG(peer->af_sflags[afi][safi], + PEER_STATUS_ORF_PREFIX_SEND)) + bgp_route_refresh_send( + peer, afi, safi, prefix_type, + REFRESH_IMMEDIATE, 1, + BGP_ROUTE_REFRESH_NORMAL); + else + bgp_route_refresh_send( + peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_NORMAL); + } + return 0; + } + } + + if (stype == BGP_CLEAR_SOFT_IN || stype == BGP_CLEAR_SOFT_BOTH + || stype == BGP_CLEAR_SOFT_IN_ORF_PREFIX) { + /* If neighbor has soft reconfiguration inbound flag. + Use Adj-RIB-In database. */ + if (!bgp_soft_reconfig_in(peer, afi, safi)) { + /* If neighbor has route refresh capability, send route + refresh + message to the peer. */ + if (CHECK_FLAG(peer->cap, PEER_CAP_REFRESH_RCV)) + bgp_route_refresh_send( + peer, afi, safi, 0, 0, 0, + BGP_ROUTE_REFRESH_NORMAL); + else + return BGP_ERR_SOFT_RECONFIG_UNCONFIGURED; + } + } + + if (stype == BGP_CLEAR_MESSAGE_STATS) + peer_reset_message_stats(peer); + + if (stype == BGP_CLEAR_CAPABILITIES) + peer_clear_capabilities(peer, afi, safi); + + return 0; +} + +/* Display peer uptime.*/ +char *peer_uptime(time_t uptime2, char *buf, size_t len, bool use_json, + json_object *json) +{ + time_t uptime1, epoch_tbuf; + struct tm tm; + + /* If there is no connection has been done before print `never'. */ + if (uptime2 == 0) { + if (use_json) { + json_object_string_add(json, "peerUptime", "never"); + json_object_int_add(json, "peerUptimeMsec", 0); + } else + snprintf(buf, len, "never"); + return buf; + } + + /* Get current time. */ + uptime1 = monotime(NULL); + uptime1 -= uptime2; + gmtime_r(&uptime1, &tm); + + if (uptime1 < ONE_DAY_SECOND) + snprintf(buf, len, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, + tm.tm_sec); + else if (uptime1 < ONE_WEEK_SECOND) + snprintf(buf, len, "%dd%02dh%02dm", tm.tm_yday, tm.tm_hour, + tm.tm_min); + else if (uptime1 < ONE_YEAR_SECOND) + snprintf(buf, len, "%02dw%dd%02dh", tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7), tm.tm_hour); + else + snprintf(buf, len, "%02dy%02dw%dd", tm.tm_year - 70, + tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7)); + + if (use_json) { + epoch_tbuf = time(NULL) - uptime1; + json_object_string_add(json, "peerUptime", buf); + json_object_int_add(json, "peerUptimeMsec", uptime1 * 1000); + json_object_int_add(json, "peerUptimeEstablishedEpoch", + epoch_tbuf); + } + + return buf; +} + +void bgp_master_init(struct event_loop *master, const int buffer_size, + struct list *addresses) +{ + qobj_init(); + + memset(&bgp_master, 0, sizeof(bgp_master)); + + bm = &bgp_master; + + zebra_announce_init(&bm->zebra_announce_head); + bm->bgp = list_new(); + bm->listen_sockets = list_new(); + bm->port = BGP_PORT_DEFAULT; + bm->addresses = addresses; + bm->master = master; + bm->start_time = monotime(NULL); + bm->t_rmap_update = NULL; + bm->rmap_update_timer = RMAP_DEFAULT_UPDATE_TIMER; + bm->v_update_delay = BGP_UPDATE_DELAY_DEF; + bm->v_establish_wait = BGP_UPDATE_DELAY_DEF; + bm->terminating = false; + bm->socket_buffer = buffer_size; + bm->wait_for_fib = false; + bm->ip_tos = IPTOS_PREC_INTERNETCONTROL; + bm->inq_limit = BM_DEFAULT_Q_LIMIT; + bm->outq_limit = BM_DEFAULT_Q_LIMIT; + bm->t_bgp_sync_label_manager = NULL; + bm->t_bgp_start_label_manager = NULL; + bm->t_bgp_zebra_route = NULL; + + bgp_mac_init(); + /* init the rd id space. + assign 0th index in the bitfield, + so that we start with id 1 + */ + bf_init(bm->rd_idspace, UINT16_MAX); + bf_assign_zero_index(bm->rd_idspace); + + /* mpls label dynamic allocation pool */ + bgp_lp_init(bm->master, &bm->labelpool); + + bgp_nhg_init(); + bgp_evpn_mh_init(); + QOBJ_REG(bm, bgp_master); +} + +/* + * Free up connected routes and interfaces for a BGP instance. Invoked upon + * instance delete (non-default only) or BGP exit. + */ +static void bgp_if_finish(struct bgp *bgp) +{ + struct vrf *vrf; + struct interface *ifp; + + vrf = bgp_vrf_lookup_by_instance_type(bgp); + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VIEW || !vrf) + return; + + FOR_ALL_INTERFACES (vrf, ifp) { + struct connected *c; + + frr_each_safe (if_connected, ifp->connected, c) + bgp_connected_delete(bgp, c); + } +} + +static void bgp_viewvrf_autocomplete(vector comps, struct cmd_token *token) +{ + struct vrf *vrf = NULL; + struct listnode *next; + struct bgp *bgp; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, vrf->name)); + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { + if (bgp->inst_type != BGP_INSTANCE_TYPE_VIEW) + continue; + + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, bgp->name)); + } +} + +static void bgp_instasn_autocomplete(vector comps, struct cmd_token *token) +{ + struct listnode *next, *next2; + struct bgp *bgp, *bgp2; + char buf[ASN_STRING_MAX_SIZE]; + + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next, bgp)) { + /* deduplicate */ + for (ALL_LIST_ELEMENTS_RO(bm->bgp, next2, bgp2)) { + if (bgp2->as == bgp->as) + break; + if (bgp2 == bgp) + break; + } + if (bgp2 != bgp) + continue; + + snprintf(buf, sizeof(buf), "%s", bgp->as_pretty); + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, buf)); + } +} + +static const struct cmd_variable_handler bgp_viewvrf_var_handlers[] = { + {.tokenname = "VIEWVRFNAME", .completions = bgp_viewvrf_autocomplete}, + {.varname = "instasn", .completions = bgp_instasn_autocomplete}, + {.completions = NULL}, +}; + +struct frr_pthread *bgp_pth_io; +struct frr_pthread *bgp_pth_ka; + +static void bgp_pthreads_init(void) +{ + assert(!bgp_pth_io); + assert(!bgp_pth_ka); + + struct frr_pthread_attr io = { + .start = frr_pthread_attr_default.start, + .stop = frr_pthread_attr_default.stop, + }; + struct frr_pthread_attr ka = { + .start = bgp_keepalives_start, + .stop = bgp_keepalives_stop, + }; + bgp_pth_io = frr_pthread_new(&io, "BGP I/O thread", "bgpd_io"); + bgp_pth_ka = frr_pthread_new(&ka, "BGP Keepalives thread", "bgpd_ka"); +} + +void bgp_pthreads_run(void) +{ + frr_pthread_run(bgp_pth_io, NULL); + frr_pthread_run(bgp_pth_ka, NULL); + + /* Wait until threads are ready. */ + frr_pthread_wait_running(bgp_pth_io); + frr_pthread_wait_running(bgp_pth_ka); +} + +void bgp_pthreads_finish(void) +{ + frr_pthread_stop_all(); +} + +static int peer_unshut_after_cfg(struct bgp *bgp) +{ + struct listnode *node; + struct peer *peer; + + for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) { + if (!peer->shut_during_cfg) + continue; + + if (bgp_debug_neighbor_events(peer)) + zlog_debug("%s: released from config-pending hold", + peer->host); + + peer->shut_during_cfg = false; + if (peer_active(peer) && + peer->connection->status != Established) { + if (peer->connection->status != Idle) + BGP_EVENT_ADD(peer->connection, BGP_Stop); + BGP_EVENT_ADD(peer->connection, BGP_Start); + } + } + + return 0; +} + +void bgp_init(unsigned short instance) +{ + hook_register(bgp_config_end, peer_unshut_after_cfg); + + /* allocates some vital data structures used by peer commands in + * vty_init */ + + /* pre-init pthreads */ + bgp_pthreads_init(); + + /* Init zebra. */ + bgp_zebra_init(bm->master, instance); + +#ifdef ENABLE_BGP_VNC + vnc_zebra_init(bm->master); +#endif + + /* BGP VTY commands installation. */ + bgp_vty_init(); + + /* BGP inits. */ + bgp_attr_init(); + bgp_labels_init(); + bgp_debug_init(); + bgp_community_alias_init(); + bgp_dump_init(); + bgp_route_init(); + bgp_route_map_init(); + bgp_scan_vty_init(); + bgp_mplsvpn_init(); +#ifdef ENABLE_BGP_VNC + rfapi_init(); +#endif + bgp_ethernetvpn_init(); + bgp_flowspec_vty_init(); + + /* Access list initialize. */ + access_list_init(); + access_list_add_hook(peer_distribute_update); + access_list_delete_hook(peer_distribute_update); + + /* Filter list initialize. */ + bgp_filter_init(); + as_list_add_hook(peer_aslist_add); + as_list_delete_hook(peer_aslist_del); + + /* Prefix list initialize.*/ + prefix_list_init(); + prefix_list_add_hook(peer_prefix_list_update); + prefix_list_delete_hook(peer_prefix_list_update); + + /* Community list initialize. */ + bgp_clist = community_list_init(); + + /* BFD init */ + bgp_bfd_init(bm->master); + + bgp_lp_vty_init(); + + bgp_label_per_nexthop_init(); + bgp_mplsvpn_nexthop_init(); + + cmd_variable_handler_register(bgp_viewvrf_var_handlers); +} + +void bgp_terminate(void) +{ + struct bgp *bgp; + struct peer *peer; + struct listnode *node, *nnode; + struct listnode *mnode, *mnnode; + + QOBJ_UNREG(bm); + + /* Close the listener sockets first as this prevents peers from + * attempting + * to reconnect on receiving the peer unconfig message. In the presence + * of a large number of peers this will ensure that no peer is left with + * a dangling connection + */ + + bgp_close(); + /* reverse bgp_master_init */ + for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) { + bgp_close_vrf_socket(bgp); + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + if (BGP_PEER_GRACEFUL_RESTART_CAPABLE(peer)) { + if (bgp_debug_neighbor_events(peer)) + zlog_debug( + "%pBP configured Graceful-Restart, skipping unconfig notification", + peer); + continue; + } + if (BGP_IS_VALID_STATE_FOR_NOTIF( + peer->connection->status)) + bgp_notify_send(peer->connection, + BGP_NOTIFY_CEASE, + BGP_NOTIFY_CEASE_PEER_UNCONFIG); + } + } + + if (bm->listen_sockets) + list_delete(&bm->listen_sockets); + + EVENT_OFF(bm->t_rmap_update); + EVENT_OFF(bm->t_bgp_sync_label_manager); + EVENT_OFF(bm->t_bgp_start_label_manager); + EVENT_OFF(bm->t_bgp_zebra_route); + + bgp_mac_finish(); +} + +struct peer *peer_lookup_in_view(struct vty *vty, struct bgp *bgp, + const char *ip_str, bool use_json) +{ + int ret; + struct peer *peer; + union sockunion su; + struct peer_group *group; + + /* Get peer sockunion. */ + ret = str2sockunion(ip_str, &su); + if (ret < 0) { + peer = peer_lookup_by_conf_if(bgp, ip_str); + if (!peer) { + peer = peer_lookup_by_hostname(bgp, ip_str); + + if (!peer) { + group = peer_group_lookup(bgp, ip_str); + if (group) + peer = listnode_head(group->peer); + } + + if (!peer) { + if (use_json) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add( + json_no, + "malformedAddressOrName", + ip_str); + vty_json(vty, json_no); + } else + vty_out(vty, + "%% Malformed address or name: %s\n", + ip_str); + return NULL; + } + } + return peer; + } + + /* Peer structure lookup. */ + peer = peer_lookup(bgp, &su); + if (!peer) { + if (use_json) { + json_object *json_no = NULL; + json_no = json_object_new_object(); + json_object_string_add(json_no, "warning", + "No such neighbor in this view/vrf"); + vty_json(vty, json_no); + } else + vty_out(vty, "No such neighbor in this view/vrf\n"); + return NULL; + } + + return peer; +} + +void bgp_gr_apply_running_config(void) +{ + struct peer *peer = NULL; + struct bgp *bgp = NULL; + struct listnode *node, *nnode; + bool gr_router_detected = false; + + if (BGP_DEBUG(graceful_restart, GRACEFUL_RESTART)) + zlog_debug("[BGP_GR] %s called !", __func__); + + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) { + for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) { + bgp_peer_gr_flags_update(peer); + if (CHECK_FLAG(peer->flags, PEER_FLAG_GRACEFUL_RESTART)) + gr_router_detected = true; + } + + if (gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { + bgp_zebra_send_capabilities(bgp, true); + } else if (!gr_router_detected + && bgp->present_zebra_gr_state == ZEBRA_GR_ENABLE) { + bgp_zebra_send_capabilities(bgp, false); + } + + gr_router_detected = false; + } +} + +printfrr_ext_autoreg_p("BP", printfrr_bp); +static ssize_t printfrr_bp(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct peer *peer = ptr; + + if (!peer) + return bputs(buf, "(null)"); + + return bprintfrr(buf, "%s(%s)", peer->host, + peer->hostname ? peer->hostname : "Unknown"); +} + +const struct message bgp_martian_type_str[] = { + {BGP_MARTIAN_IF_IP, "Self Interface IP"}, + {BGP_MARTIAN_TUN_IP, "Self Tunnel IP"}, + {BGP_MARTIAN_IF_MAC, "Self Interface MAC"}, + {BGP_MARTIAN_RMAC, "Self RMAC"}, + {BGP_MARTIAN_SOO, "Self Site-of-Origin"}, + {0}}; + +const char *bgp_martian_type2str(enum bgp_martian_type mt) +{ + return lookup_msg(bgp_martian_type_str, mt, "Unknown Martian Type"); +} diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h new file mode 100644 index 0000000..1f8cc53 --- /dev/null +++ b/bgpd/bgpd.h @@ -0,0 +1,2790 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP message definition header. + * Copyright (C) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_BGPD_H +#define _QUAGGA_BGPD_H + +#include "qobj.h" +#include + +#include "hook.h" +#include "frr_pthread.h" +#include "lib/json.h" +#include "vrf.h" +#include "vty.h" +#include "srv6.h" +#include "iana_afi.h" +#include "asn.h" + +PREDECL_LIST(zebra_announce); + +/* For union sockunion. */ +#include "queue.h" +#include "sockunion.h" +#include "routemap.h" +#include "linklist.h" +#include "defaults.h" +#include "bgp_memory.h" +#include "bitfield.h" +#include "vxlan.h" +#include "bgp_labelpool.h" +#include "bgp_addpath_types.h" +#include "bgp_nexthop.h" +#include "bgp_io.h" +#include "bgp_damp.h" + +#include "lib/bfd.h" + +DECLARE_HOOK(bgp_hook_config_write_vrf, (struct vty *vty, struct vrf *vrf), + (vty, vrf)); + +#define BGP_MAX_HOSTNAME 64 /* Linux max, is larger than most other sys */ +#define BGP_PEER_MAX_HASH_SIZE 16384 + +/* Default interval for IPv6 RAs when triggered by BGP unnumbered neighbor. */ +#define BGP_UNNUM_DEFAULT_RA_INTERVAL 10 + +struct update_subgroup; +struct bpacket; +struct bgp_pbr_config; + +/* + * Allow the neighbor XXXX remote-as to take internal or external + * AS_SPECIFIED is zero to auto-inherit original non-feature/enhancement + * behavior + * in the system. + */ +enum { AS_UNSPECIFIED = 0, + AS_SPECIFIED, + AS_INTERNAL, + AS_EXTERNAL, +}; + +/* Zebra Gracaful Restart states */ +enum zebra_gr_mode { + ZEBRA_GR_DISABLE = 0, + ZEBRA_GR_ENABLE +}; + +/* Typedef BGP specific types. */ +typedef uint16_t as16_t; /* we may still encounter 16 Bit asnums */ +typedef uint16_t bgp_size_t; + +enum bgp_af_index { + BGP_AF_START, + BGP_AF_IPV4_UNICAST = BGP_AF_START, + BGP_AF_IPV4_MULTICAST, + BGP_AF_IPV4_VPN, + BGP_AF_IPV6_UNICAST, + BGP_AF_IPV6_MULTICAST, + BGP_AF_IPV6_VPN, + BGP_AF_IPV4_ENCAP, + BGP_AF_IPV6_ENCAP, + BGP_AF_L2VPN_EVPN, + BGP_AF_IPV4_LBL_UNICAST, + BGP_AF_IPV6_LBL_UNICAST, + BGP_AF_IPV4_FLOWSPEC, + BGP_AF_IPV6_FLOWSPEC, + BGP_AF_MAX +}; + +#define AF_FOREACH(af) for ((af) = BGP_AF_START; (af) < BGP_AF_MAX; (af)++) + +#define FOREACH_SAFI(safi) \ + for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) + +extern struct frr_pthread *bgp_pth_io; +extern struct frr_pthread *bgp_pth_ka; + +/* BGP master for system wide configurations and variables. */ +struct bgp_master { + /* BGP instance list. */ + struct list *bgp; + + /* BGP thread master. */ + struct event_loop *master; + + /* Listening sockets */ + struct list *listen_sockets; + + /* BGP port number. */ + uint16_t port; + + /* Listener addresses */ + struct list *addresses; + + /* The Mac table */ + struct hash *self_mac_hash; + + /* BGP start time. */ + time_t start_time; + + /* Various BGP global configuration. */ + uint8_t options; + +#define BGP_OPT_NO_FIB (1 << 0) +#define BGP_OPT_NO_LISTEN (1 << 1) +#define BGP_OPT_NO_ZEBRA (1 << 2) +#define BGP_OPT_TRAPS_RFC4273 (1 << 3) +#define BGP_OPT_TRAPS_BGP4MIBV2 (1 << 4) + + uint64_t updgrp_idspace; + uint64_t subgrp_idspace; + + /* timer to dampen route map changes */ + struct event *t_rmap_update; /* Handle route map updates */ + uint32_t rmap_update_timer; /* Route map update timer */ +#define RMAP_DEFAULT_UPDATE_TIMER 5 /* disabled by default */ + + /* Id space for automatic RD derivation for an EVI/VRF */ + bitfield_t rd_idspace; + + /* dynamic mpls label allocation pool */ + struct labelpool labelpool; + + /* BGP-EVPN VRF ID. Defaults to default VRF (if any) */ + struct bgp* bgp_evpn; + + /* How big should we set the socket buffer size */ + uint32_t socket_buffer; + + /* Should we do wait for fib install globally? */ + bool wait_for_fib; + + /* EVPN multihoming */ + struct bgp_evpn_mh_info *mh_info; + + /* global update-delay timer values */ + uint16_t v_update_delay; + uint16_t v_establish_wait; + + uint32_t flags; +#define BM_FLAG_GRACEFUL_SHUTDOWN (1 << 0) +#define BM_FLAG_SEND_EXTRA_DATA_TO_ZEBRA (1 << 1) + + bool terminating; /* global flag that sigint terminate seen */ + + /* TOS value for outgoing packets in BGP connections */ + uint8_t ip_tos; + +#define BM_DEFAULT_Q_LIMIT 10000 + uint32_t inq_limit; + uint32_t outq_limit; + + struct event *t_bgp_sync_label_manager; + struct event *t_bgp_start_label_manager; + + struct event *t_bgp_zebra_route; + + bool v6_with_v4_nexthops; + + /* To preserve ordering of installations into zebra across all Vrfs */ + struct zebra_announce_head zebra_announce_head; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bgp_master); + +/* BGP route-map structure. */ +struct bgp_rmap { + char *name; + struct route_map *map; +}; + +struct bgp_redist { + unsigned short instance; + + /* BGP redistribute metric configuration. */ + uint8_t redist_metric_flag; + uint32_t redist_metric; + + /* BGP redistribute route-map. */ + struct bgp_rmap rmap; +}; + +enum vpn_policy_direction { + BGP_VPN_POLICY_DIR_FROMVPN = 0, + BGP_VPN_POLICY_DIR_TOVPN = 1, + BGP_VPN_POLICY_DIR_MAX = 2 +}; + +struct vpn_policy { + struct bgp *bgp; /* parent */ + afi_t afi; + struct ecommunity *rtlist[BGP_VPN_POLICY_DIR_MAX]; + struct ecommunity *import_redirect_rtlist; + char *rmap_name[BGP_VPN_POLICY_DIR_MAX]; + struct route_map *rmap[BGP_VPN_POLICY_DIR_MAX]; + + /* should be mpls_label_t? */ + uint32_t tovpn_label; /* may be MPLS_LABEL_NONE */ + uint32_t tovpn_zebra_vrf_label_last_sent; + char *tovpn_rd_pretty; + struct prefix_rd tovpn_rd; + struct prefix tovpn_nexthop; /* unset => set to 0 */ + uint32_t flags; +#define BGP_VPN_POLICY_TOVPN_LABEL_AUTO (1 << 0) +#define BGP_VPN_POLICY_TOVPN_RD_SET (1 << 1) +#define BGP_VPN_POLICY_TOVPN_NEXTHOP_SET (1 << 2) +#define BGP_VPN_POLICY_TOVPN_SID_AUTO (1 << 3) +#define BGP_VPN_POLICY_TOVPN_LABEL_PER_NEXTHOP (1 << 4) +/* Manual label is registered with zebra label manager */ +#define BGP_VPN_POLICY_TOVPN_LABEL_MANUAL_REG (1 << 5) + + /* + * If we are importing another vrf into us keep a list of + * vrf names that are being imported into us. + */ + struct list *import_vrf; + + /* + * if we are being exported to another vrf keep a list of + * vrf names that we are being exported to. + */ + struct list *export_vrf; + + /* + * Segment-Routing SRv6 Mode + */ + uint32_t tovpn_sid_index; /* unset => set to 0 */ + struct in6_addr *tovpn_sid; + struct srv6_locator_chunk *tovpn_sid_locator; + uint32_t tovpn_sid_transpose_label; + struct in6_addr *tovpn_zebra_vrf_sid_last_sent; +}; + +/* + * Type of 'struct bgp'. + * - Default: The default instance + * - VRF: A specific (non-default) VRF + * - View: An instance used for route exchange + * The "default" instance is treated separately to simplify the code. Note + * that if deployed in a Multi-VRF environment, it may not exist. + */ +enum bgp_instance_type { + BGP_INSTANCE_TYPE_DEFAULT, + BGP_INSTANCE_TYPE_VRF, + BGP_INSTANCE_TYPE_VIEW +}; + +#define BGP_SEND_EOR(bgp, afi, safi) \ + (!CHECK_FLAG(bgp->flags, BGP_FLAG_GR_DISABLE_EOR) \ + && ((bgp->gr_info[afi][safi].t_select_deferral == NULL) \ + || (bgp->gr_info[afi][safi].eor_required \ + == bgp->gr_info[afi][safi].eor_received))) + +/* BGP GR Global ds */ + +#define BGP_GLOBAL_GR_MODE 4 +#define BGP_GLOBAL_GR_EVENT_CMD 4 + +/* Graceful restart selection deferral timer info */ +struct graceful_restart_info { + /* Count of EOR message expected */ + uint32_t eor_required; + /* Count of EOR received */ + uint32_t eor_received; + /* Deferral Timer */ + struct event *t_select_deferral; + /* Routes Deferred */ + uint32_t gr_deferred; + /* Best route select */ + struct event *t_route_select; + /* AFI, SAFI enabled */ + bool af_enabled[AFI_MAX][SAFI_MAX]; + /* Route update completed */ + bool route_sync[AFI_MAX][SAFI_MAX]; +}; + +enum global_mode { + GLOBAL_HELPER = 0, /* This is the default mode */ + GLOBAL_GR, + GLOBAL_DISABLE, + GLOBAL_INVALID +}; + +enum global_gr_command { + GLOBAL_GR_CMD = 0, + NO_GLOBAL_GR_CMD, + GLOBAL_DISABLE_CMD, + NO_GLOBAL_DISABLE_CMD +}; + +#define BGP_GR_SUCCESS 0 +#define BGP_GR_FAILURE 1 + +/* Handling of BGP link bandwidth (LB) on receiver - whether and how to + * do weighted ECMP. Note: This applies after multipath computation. + */ +enum bgp_link_bw_handling { + /* Do ECMP if some paths don't have LB - default */ + BGP_LINK_BW_ECMP, + /* Completely ignore LB, just do regular ECMP */ + BGP_LINK_BW_IGNORE_BW, + /* Skip paths without LB, do wECMP on others */ + BGP_LINK_BW_SKIP_MISSING, + /* Do wECMP with default weight for paths not having LB */ + BGP_LINK_BW_DEFWT_4_MISSING +}; + +RB_HEAD(bgp_es_vrf_rb_head, bgp_evpn_es_vrf); +RB_PROTOTYPE(bgp_es_vrf_rb_head, bgp_evpn_es_vrf, rb_node, bgp_es_vrf_rb_cmp); + +struct bgp_snmp_stats { + /* SNMP variables for mplsL3Vpn*/ + time_t creation_time; + time_t modify_time; + bool active; + uint32_t routes_added; + uint32_t routes_deleted; +}; + +struct bgp_srv6_function { + struct in6_addr sid; + char locator_name[SRV6_LOCNAME_SIZE]; +}; + +struct as_confed { + as_t as; + char *as_pretty; +}; + +struct bgp_mplsvpn_nh_label_bind_cache; +PREDECL_RBTREE_UNIQ(bgp_mplsvpn_nh_label_bind_cache); + +/* BGP instance structure. */ +struct bgp { + /* AS number of this BGP instance. */ + as_t as; + char *as_pretty; + + /* Name of this BGP instance. */ + char *name; + char *name_pretty; /* printable "VRF|VIEW name|default" */ + + /* Type of instance and VRF id. */ + enum bgp_instance_type inst_type; + vrf_id_t vrf_id; + + /* Reference count to allow peer_delete to finish after bgp_delete */ + int lock; + + /* Self peer. */ + struct peer *peer_self; + + /* BGP peer. */ + struct list *peer; + struct hash *peerhash; + + /* BGP peer group. */ + struct list *group; + + /* The maximum number of BGP dynamic neighbors that can be created */ + int dynamic_neighbors_limit; + + /* The current number of BGP dynamic neighbors */ + int dynamic_neighbors_count; + + struct hash *update_groups[BGP_AF_MAX]; + + /* + * Global statistics for update groups. + */ + struct { + uint32_t join_events; + uint32_t prune_events; + uint32_t merge_events; + uint32_t split_events; + uint32_t updgrp_switch_events; + uint32_t peer_refreshes_combined; + uint32_t adj_count; + uint32_t merge_checks_triggered; + + uint32_t updgrps_created; + uint32_t updgrps_deleted; + uint32_t subgrps_created; + uint32_t subgrps_deleted; + } update_group_stats; + + struct bgp_snmp_stats *snmp_stats; + + /* BGP configuration. */ + uint16_t config; +#define BGP_CONFIG_CLUSTER_ID (1 << 0) +#define BGP_CONFIG_CONFEDERATION (1 << 1) +#define BGP_CONFIG_ASNOTATION (1 << 2) + + /* BGP router identifier. */ + struct in_addr router_id; + struct in_addr router_id_static; + struct in_addr router_id_zebra; + + /* BGP route reflector cluster ID. */ + struct in_addr cluster_id; + + /* BGP confederation information. */ + as_t confed_id; + char *confed_id_pretty; + struct as_confed *confed_peers; + int confed_peers_cnt; + + /* start-up timer on only once at the beginning */ + struct event *t_startup; + + uint32_t v_maxmed_onstartup; /* Duration of max-med on start-up */ +#define BGP_MAXMED_ONSTARTUP_UNCONFIGURED 0 /* 0 means off, its the default */ + uint32_t maxmed_onstartup_value; /* Max-med value when active on + start-up */ + + /* non-null when max-med onstartup is on */ + struct event *t_maxmed_onstartup; + uint8_t maxmed_onstartup_over; /* Flag to make it effective only once */ + + bool v_maxmed_admin; /* true/false if max-med administrative is on/off + */ +#define BGP_MAXMED_ADMIN_UNCONFIGURED false /* Off by default */ + uint32_t maxmed_admin_value; /* Max-med value when administrative in on + */ +#define BGP_MAXMED_VALUE_DEFAULT 4294967294 /* Maximum by default */ + + uint8_t maxmed_active; /* 1/0 if max-med is active or not */ + uint32_t maxmed_value; /* Max-med value when its active */ + + /* BGP update delay on startup */ + struct event *t_update_delay; + struct event *t_establish_wait; + struct event *t_revalidate[AFI_MAX][SAFI_MAX]; + + uint8_t update_delay_over; + uint8_t main_zebra_update_hold; + uint8_t main_peers_update_hold; + uint16_t v_update_delay; + uint16_t v_establish_wait; + char update_delay_begin_time[64]; + char update_delay_end_time[64]; + char update_delay_zebra_resume_time[64]; + char update_delay_peers_resume_time[64]; + uint32_t established; + uint32_t restarted_peers; + uint32_t implicit_eors; + uint32_t explicit_eors; +#define BGP_UPDATE_DELAY_DEF 0 +#define BGP_UPDATE_DELAY_MIN 0 +#define BGP_UPDATE_DELAY_MAX 3600 + + /* Reference bandwidth for BGP link-bandwidth. Used when + * the LB value has to be computed based on some other + * factor (e.g., number of multipaths for the prefix) + * Value is in Mbps + */ + uint64_t lb_ref_bw; +#define BGP_LINK_BW_REF_BW 1 + + /* BGP flags. */ + uint64_t flags; +#define BGP_FLAG_ALWAYS_COMPARE_MED (1ULL << 0) +#define BGP_FLAG_DETERMINISTIC_MED (1ULL << 1) +#define BGP_FLAG_MED_MISSING_AS_WORST (1ULL << 2) +#define BGP_FLAG_MED_CONFED (1ULL << 3) +#define BGP_FLAG_NO_CLIENT_TO_CLIENT (1ULL << 4) +#define BGP_FLAG_COMPARE_ROUTER_ID (1ULL << 5) +#define BGP_FLAG_ASPATH_IGNORE (1ULL << 6) +#define BGP_FLAG_IMPORT_CHECK (1ULL << 7) +#define BGP_FLAG_NO_FAST_EXT_FAILOVER (1ULL << 8) +#define BGP_FLAG_LOG_NEIGHBOR_CHANGES (1ULL << 9) + +/* This flag is set when we have full BGP Graceful-Restart mode enable */ +#define BGP_FLAG_GRACEFUL_RESTART (1ULL << 10) + +#define BGP_FLAG_ASPATH_CONFED (1ULL << 11) +#define BGP_FLAG_ASPATH_MULTIPATH_RELAX (1ULL << 12) +#define BGP_FLAG_RR_ALLOW_OUTBOUND_POLICY (1ULL << 13) +#define BGP_FLAG_DISABLE_NH_CONNECTED_CHK (1ULL << 14) +#define BGP_FLAG_MULTIPATH_RELAX_AS_SET (1ULL << 15) +#define BGP_FLAG_FORCE_STATIC_PROCESS (1ULL << 16) +#define BGP_FLAG_SHOW_HOSTNAME (1ULL << 17) +#define BGP_FLAG_GR_PRESERVE_FWD (1ULL << 18) +#define BGP_FLAG_GRACEFUL_SHUTDOWN (1ULL << 19) +#define BGP_FLAG_DELETE_IN_PROGRESS (1ULL << 20) +#define BGP_FLAG_SELECT_DEFER_DISABLE (1ULL << 21) +#define BGP_FLAG_GR_DISABLE_EOR (1ULL << 22) +#define BGP_FLAG_EBGP_REQUIRES_POLICY (1ULL << 23) +#define BGP_FLAG_SHOW_NEXTHOP_HOSTNAME (1ULL << 24) + +/* This flag is set if the instance is in administrative shutdown */ +#define BGP_FLAG_SHUTDOWN (1ULL << 25) +#define BGP_FLAG_SUPPRESS_FIB_PENDING (1ULL << 26) +#define BGP_FLAG_SUPPRESS_DUPLICATES (1ULL << 27) +#define BGP_FLAG_PEERTYPE_MULTIPATH_RELAX (1ULL << 29) +/* Indicate Graceful Restart support for BGP NOTIFICATION messages */ +#define BGP_FLAG_GRACEFUL_NOTIFICATION (1ULL << 30) +/* Send Hard Reset CEASE Notification for 'Administrative Reset' */ +#define BGP_FLAG_HARD_ADMIN_RESET (1ULL << 31) +/* Evaluate the AIGP attribute during the best path selection process */ +#define BGP_FLAG_COMPARE_AIGP (1ULL << 32) +/* For BGP-LU, force IPv4 local prefixes to use ipv4-explicit-null label */ +#define BGP_FLAG_LU_IPV4_EXPLICIT_NULL (1ULL << 33) +/* For BGP-LU, force IPv6 local prefixes to use ipv6-explicit-null label */ +#define BGP_FLAG_LU_IPV6_EXPLICIT_NULL (1ULL << 34) +#define BGP_FLAG_SOFT_VERSION_CAPABILITY (1ULL << 35) +#define BGP_FLAG_ENFORCE_FIRST_AS (1ULL << 36) +#define BGP_FLAG_DYNAMIC_CAPABILITY (1ULL << 37) +#define BGP_FLAG_VNI_DOWN (1ULL << 38) + + /* BGP default address-families. + * New peers inherit enabled afi/safis from bgp instance. + */ + uint16_t default_af[AFI_MAX][SAFI_MAX]; + + enum global_mode GLOBAL_GR_FSM[BGP_GLOBAL_GR_MODE] + [BGP_GLOBAL_GR_EVENT_CMD]; + enum global_mode global_gr_present_state; + + /* This variable stores the current Graceful Restart state of Zebra + * - ZEBRA_GR_ENABLE / ZEBRA_GR_DISABLE + */ + enum zebra_gr_mode present_zebra_gr_state; + + /* BGP Per AF flags */ + uint16_t af_flags[AFI_MAX][SAFI_MAX]; +#define BGP_CONFIG_DAMPENING (1 << 0) +/* l2vpn evpn flags - 1 << 0 is used for DAMPENNG */ +#define BGP_L2VPN_EVPN_ADV_IPV4_UNICAST (1 << 1) +#define BGP_L2VPN_EVPN_ADV_IPV4_UNICAST_GW_IP (1 << 2) +#define BGP_L2VPN_EVPN_ADV_IPV6_UNICAST (1 << 3) +#define BGP_L2VPN_EVPN_ADV_IPV6_UNICAST_GW_IP (1 << 4) +#define BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV4 (1 << 5) +#define BGP_L2VPN_EVPN_DEFAULT_ORIGINATE_IPV6 (1 << 6) +/* import/export between address families */ +#define BGP_CONFIG_VRF_TO_MPLSVPN_EXPORT (1 << 7) +#define BGP_CONFIG_MPLSVPN_TO_VRF_IMPORT (1 << 8) +/* vrf-route leaking flags */ +#define BGP_CONFIG_VRF_TO_VRF_IMPORT (1 << 9) +#define BGP_CONFIG_VRF_TO_VRF_EXPORT (1 << 10) +/* vpnvx retain flag */ +#define BGP_VPNVX_RETAIN_ROUTE_TARGET_ALL (1 << 11) + + /* BGP per AF peer count */ + uint32_t af_peer_count[AFI_MAX][SAFI_MAX]; + + /* Tree for next-hop lookup cache. */ + struct bgp_nexthop_cache_head nexthop_cache_table[AFI_MAX]; + + /* Tree for import-check */ + struct bgp_nexthop_cache_head import_check_table[AFI_MAX]; + + struct bgp_table *connected_table[AFI_MAX]; + + struct hash *address_hash; + + /* DB for all local tunnel-ips - used mainly for martian checks + Currently it only has all VxLan tunnel IPs*/ + struct hash *tip_hash; + + /* Static route configuration. */ + struct bgp_table *route[AFI_MAX][SAFI_MAX]; + + /* Aggregate address configuration. */ + struct bgp_table *aggregate[AFI_MAX][SAFI_MAX]; + + /* BGP routing information base. */ + struct bgp_table *rib[AFI_MAX][SAFI_MAX]; + + /* BGP table route-map. */ + struct bgp_rmap table_map[AFI_MAX][SAFI_MAX]; + + /* BGP redistribute configuration. */ + struct list *redist[AFI_MAX][ZEBRA_ROUTE_MAX]; + + /* Allocate MPLS labels */ + uint8_t allocate_mpls_labels[AFI_MAX][SAFI_MAX]; + + /* Tree for next-hop lookup cache. */ + struct bgp_label_per_nexthop_cache_head + mpls_labels_per_nexthop[AFI_MAX]; + + /* Tree for mplsvpn next-hop label bind cache */ + struct bgp_mplsvpn_nh_label_bind_cache_head mplsvpn_nh_label_bind; + + /* Allocate hash entries to store policy routing information + * The hash are used to host pbr rules somewhere. + * Actually, pbr will only be used by flowspec + * those hash elements will have relationship together as + * illustrated in below diagram: + * + * pbr_action a <----- pbr_match i <--- pbr_match_entry 1..n + * <----- pbr_match j <--- pbr_match_entry 1..m + * <----- pbr_rule k + * + * - here in BGP structure, the list of match and actions will + * stand for the list of ipset sets, and table_ids in the kernel + * - the arrow above between pbr_match and pbr_action indicate + * that a backpointer permits match to find the action + * - the arrow betwen match_entry and match is a hash list + * contained in match, that lists the whole set of entries + */ + struct hash *pbr_match_hash; + struct hash *pbr_rule_hash; + struct hash *pbr_action_hash; + + /* timer to re-evaluate neighbor default-originate route-maps */ + struct event *t_rmap_def_originate_eval; + uint16_t rmap_def_originate_eval_timer; +#define RMAP_DEFAULT_ORIGINATE_EVAL_TIMER 5 + + /* BGP distance configuration. */ + uint8_t distance_ebgp[AFI_MAX][SAFI_MAX]; + uint8_t distance_ibgp[AFI_MAX][SAFI_MAX]; + uint8_t distance_local[AFI_MAX][SAFI_MAX]; + + /* BGP default local-preference. */ + uint32_t default_local_pref; + + /* BGP default subgroup pkt queue max */ + uint32_t default_subgroup_pkt_queue_max; + + /* BGP default timer. */ + uint32_t default_holdtime; + uint32_t default_keepalive; + uint32_t default_connect_retry; + uint32_t default_delayopen; + + /* BGP minimum holdtime. */ + uint16_t default_min_holdtime; + + /* BGP graceful restart */ + uint32_t restart_time; + uint32_t stalepath_time; + uint32_t select_defer_time; + struct graceful_restart_info gr_info[AFI_MAX][SAFI_MAX]; + uint32_t rib_stale_time; + + /* BGP Long-lived Graceful Restart */ + uint32_t llgr_stale_time; + +#define BGP_ROUTE_SELECT_DELAY 1 +#define BGP_MAX_BEST_ROUTE_SELECT 10000 + /* Maximum-paths configuration */ + struct bgp_maxpaths_cfg { + uint16_t maxpaths_ebgp; + uint16_t maxpaths_ibgp; + bool same_clusterlen; + } maxpaths[AFI_MAX][SAFI_MAX]; + + _Atomic uint32_t wpkt_quanta; // max # packets to write per i/o cycle + _Atomic uint32_t rpkt_quanta; // max # packets to read per i/o cycle + + /* Automatic coalesce adjust on/off */ + bool heuristic_coalesce; + /* Actual coalesce time */ + uint32_t coalesce_time; + + /* Auto-shutdown new peers */ + bool autoshutdown; + + struct bgp_addpath_bgp_data tx_addpath; + +#ifdef ENABLE_BGP_VNC + struct rfapi_cfg *rfapi_cfg; + struct rfapi *rfapi; +#endif + + /* EVPN related information */ + + /* EVI hash table */ + struct hash *vnihash; + + /* + * VNI hash table based on SVI ifindex as its key. + * We use SVI ifindex as key to lookup a VNI table for gateway IP + * overlay index recursive lookup. + * For this purpose, a hashtable is added which optimizes this lookup. + */ + struct hash *vni_svi_hash; + + /* EVPN enable - advertise gateway macip routes */ + int advertise_gw_macip; + + /* EVPN enable - advertise local VNIs and their MACs etc. */ + int advertise_all_vni; + + /* draft-ietf-idr-deprecate-as-set-confed-set + * Reject aspaths with AS_SET and/or AS_CONFED_SET. + */ + bool reject_as_sets; + + struct bgp_evpn_info *evpn_info; + + /* EVPN - use RFC 8365 to auto-derive RT */ + int advertise_autort_rfc8365; + + /* + * Flooding mechanism for BUM packets for VxLAN-EVPN. + */ + enum vxlan_flood_control vxlan_flood_ctrl; + + /* Hash table of Import RTs to EVIs */ + struct hash *import_rt_hash; + + /* Hash table of VRF import RTs to VRFs */ + struct hash *vrf_import_rt_hash; + + /* L3-VNI corresponding to this vrf */ + vni_t l3vni; + + /* router-mac to be used in mac-ip routes for this vrf */ + struct ethaddr rmac; + + /* originator ip - to be used as NH for type-5 routes */ + struct in_addr originator_ip; + + /* SVI associated with the L3-VNI corresponding to this vrf */ + ifindex_t l3vni_svi_ifindex; + + /* RB tree of ES-VRFs */ + struct bgp_es_vrf_rb_head es_vrf_rb_tree; + + /* Hash table of EVPN nexthops maintained per-tenant-VRF */ + struct hash *evpn_nh_table; + + /* + * Flag resolve_overlay_index is used for recursive resolution + * procedures for EVPN type-5 route's gateway IP overlay index. + * When this flag is set, we build remote-ip-hash for + * all L2VNIs and resolve overlay index nexthops using this hash. + * Overlay index nexthops remain unresolved if this flag is not set. + */ + bool resolve_overlay_index; + + /* vrf flags */ + uint32_t vrf_flags; +#define BGP_VRF_AUTO (1 << 0) +#define BGP_VRF_IMPORT_RT_CFGD (1 << 1) +#define BGP_VRF_EXPORT_RT_CFGD (1 << 2) +#define BGP_VRF_IMPORT_AUTO_RT_CFGD (1 << 3) /* retain auto when cfgd */ +#define BGP_VRF_EXPORT_AUTO_RT_CFGD (1 << 4) /* retain auto when cfgd */ +#define BGP_VRF_RD_CFGD (1 << 5) +#define BGP_VRF_L3VNI_PREFIX_ROUTES_ONLY (1 << 6) +/* per-VRF toVPN SID */ +#define BGP_VRF_TOVPN_SID_AUTO (1 << 7) + + /* unique ID for auto derivation of RD for this vrf */ + uint16_t vrf_rd_id; + + /* Automatically derived RD for this VRF */ + struct prefix_rd vrf_prd_auto; + + /* RD for this VRF */ + struct prefix_rd vrf_prd; + char *vrf_prd_pretty; + + /* import rt list for the vrf instance */ + struct list *vrf_import_rtl; + + /* export rt list for the vrf instance */ + struct list *vrf_export_rtl; + + /* list of corresponding l2vnis (struct bgpevpn) */ + struct list *l2vnis; + + /* route map for advertise ipv4/ipv6 unicast (type-5 routes) */ + struct bgp_rmap adv_cmd_rmap[AFI_MAX][SAFI_MAX]; + + struct vpn_policy vpn_policy[AFI_MAX]; + + struct bgp_pbr_config *bgp_pbr_cfg; + + /* Count of peers in established state */ + uint32_t established_peers; + + /* Weighted ECMP related config. */ + enum bgp_link_bw_handling lb_handling; + + /* Process Queue for handling routes */ + struct work_queue *process_queue; + + bool fast_convergence; + + /* BGP Conditional advertisement */ + uint32_t condition_check_period; + uint32_t condition_filter_count; + struct event *t_condition_check; + + /* BGP VPN SRv6 backend */ + bool srv6_enabled; + char srv6_locator_name[SRV6_LOCNAME_SIZE]; + struct list *srv6_locator_chunks; + struct list *srv6_functions; + uint32_t tovpn_sid_index; /* unset => set to 0 */ + struct in6_addr *tovpn_sid; + struct srv6_locator_chunk *tovpn_sid_locator; + uint32_t tovpn_sid_transpose_label; + struct in6_addr *tovpn_zebra_vrf_sid_last_sent; + + /* TCP keepalive parameters for BGP connection */ + uint16_t tcp_keepalive_idle; + uint16_t tcp_keepalive_intvl; + uint16_t tcp_keepalive_probes; + + struct timeval ebgprequirespolicywarning; +#define FIFTEENMINUTE2USEC (int64_t)15 * 60 * 1000000 + + bool allow_martian; + + enum asnotation_mode asnotation; + + /* BGP route flap dampening configuration */ + struct bgp_damp_config damp[AFI_MAX][SAFI_MAX]; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bgp); + +struct bgp_interface { +#define BGP_INTERFACE_MPLS_BGP_FORWARDING (1 << 0) +/* L3VPN multi domain switching */ +#define BGP_INTERFACE_MPLS_L3VPN_SWITCHING (1 << 1) + uint32_t flags; +}; + +DECLARE_HOOK(bgp_inst_delete, (struct bgp *bgp), (bgp)); +DECLARE_HOOK(bgp_inst_config_write, + (struct bgp *bgp, struct vty *vty), + (bgp, vty)); +DECLARE_HOOK(bgp_snmp_traps_config_write, (struct vty *vty), (vty)); +DECLARE_HOOK(bgp_config_end, (struct bgp *bgp), (bgp)); +DECLARE_HOOK(bgp_hook_vrf_update, (struct vrf *vrf, bool enabled), + (vrf, enabled)); + +/* Thread callback information */ +struct afi_safi_info { + afi_t afi; + safi_t safi; + struct bgp *bgp; +}; + +#define BGP_ROUTE_ADV_HOLD(bgp) (bgp->main_peers_update_hold) + +#define IS_BGP_INST_KNOWN_TO_ZEBRA(bgp) \ + (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT \ + || (bgp->inst_type == BGP_INSTANCE_TYPE_VRF \ + && bgp->vrf_id != VRF_UNKNOWN)) + +#define BGP_SELECT_DEFER_DISABLE(bgp) \ + (CHECK_FLAG(bgp->flags, BGP_FLAG_SELECT_DEFER_DISABLE)) + +#define BGP_SUPPRESS_FIB_ENABLED(bgp) \ + (CHECK_FLAG(bgp->flags, BGP_FLAG_SUPPRESS_FIB_PENDING) \ + || bm->wait_for_fib) + +/* BGP peer-group support. */ +struct peer_group { + /* Name of the peer-group. */ + char *name; + + /* Pointer to BGP. */ + struct bgp *bgp; + + /* Peer-group client list. */ + struct list *peer; + + /** Dynamic neighbor listening ranges */ + struct list *listen_range[AFI_MAX]; + + /* Peer-group config */ + struct peer *conf; +}; + +/* BGP Notify message format. */ +struct bgp_notify { + uint8_t code; + uint8_t subcode; + bgp_size_t length; + bool hard_reset; + char *data; + uint8_t *raw_data; +}; + +/* Next hop self address. */ +struct bgp_nexthop { + struct interface *ifp; + struct in_addr v4; + struct in6_addr v6_global; + struct in6_addr v6_local; +}; + +/* BGP addpath values */ +#define BGP_ADDPATH_RX 1 +#define BGP_ADDPATH_TX 2 +#define BGP_ADDPATH_ID_LEN 4 + +#define BGP_ADDPATH_TX_ID_FOR_DEFAULT_ORIGINATE 1 + +/* Route map direction */ +#define RMAP_IN 0 +#define RMAP_OUT 1 +#define RMAP_MAX 2 + +#define BGP_DEFAULT_TTL 1 +#define BGP_GTSM_HOPS_DISABLED 0 +#define BGP_GTSM_HOPS_CONNECTED 1 + +/* Advertise map */ +#define CONDITION_NON_EXIST false +#define CONDITION_EXIST true + +enum update_type { UPDATE_TYPE_WITHDRAW, UPDATE_TYPE_ADVERTISE }; + +#include "filter.h" + +/* BGP filter structure. */ +struct bgp_filter { + /* Distribute-list. */ + struct { + char *name; + struct access_list *alist; + } dlist[FILTER_MAX]; + + /* Prefix-list. */ + struct { + char *name; + struct prefix_list *plist; + } plist[FILTER_MAX]; + + /* Filter-list. */ + struct { + char *name; + struct as_list *aslist; + } aslist[FILTER_MAX]; + + /* Route-map. */ + struct { + char *name; + struct route_map *map; + } map[RMAP_MAX]; + + /* Unsuppress-map. */ + struct { + char *name; + struct route_map *map; + } usmap; + + /* Advertise-map */ + struct { + char *aname; + struct route_map *amap; + + bool condition; + + char *cname; + struct route_map *cmap; + + enum update_type update_type; + } advmap; +}; + +/* IBGP/EBGP identifier. We also have a CONFED peer, which is to say, + a peer who's AS is part of our Confederation. */ +enum bgp_peer_sort { + BGP_PEER_UNSPECIFIED, + BGP_PEER_IBGP, + BGP_PEER_EBGP, + BGP_PEER_INTERNAL, + BGP_PEER_CONFED, +}; + +/* BGP peering sub-types + * E.g.: + * EBGP-OAD - https://datatracker.ietf.org/doc/html/draft-uttaro-idr-bgp-oad + */ +enum bgp_peer_sub_sort { + BGP_PEER_EBGP_OAD = 1, +}; + +/* BGP message header and packet size. */ +#define BGP_MARKER_SIZE 16 +#define BGP_HEADER_SIZE 19 +#define BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE 4096 +#define BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE 65535 +#define BGP_MAX_PACKET_SIZE BGP_EXTENDED_MESSAGE_MAX_PACKET_SIZE +#define BGP_MAX_PACKET_SIZE_OVERFLOW 1024 + +/* + * Trigger delay for bgp_announce_route(). + */ +#define BGP_ANNOUNCE_ROUTE_SHORT_DELAY_MS 100 +#define BGP_ANNOUNCE_ROUTE_DELAY_MS 500 + +struct peer_af { + /* back pointer to the peer */ + struct peer *peer; + + /* which subgroup the peer_af belongs to */ + struct update_subgroup *subgroup; + + /* for being part of an update subgroup's peer list */ + LIST_ENTRY(peer_af) subgrp_train; + + /* for being part of a packet's peer list */ + LIST_ENTRY(peer_af) pkt_train; + + struct bpacket *next_pkt_to_send; + + /* + * Trigger timer for bgp_announce_route(). + */ + struct event *t_announce_route; + + afi_t afi; + safi_t safi; + int afid; +}; +/* BGP GR per peer ds */ + +#define BGP_PEER_GR_MODE 5 +#define BGP_PEER_GR_EVENT_CMD 6 + +enum peer_mode { + PEER_HELPER = 0, + PEER_GR, + PEER_DISABLE, + PEER_INVALID, + PEER_GLOBAL_INHERIT /* This is the default mode */ + +}; + +enum peer_gr_command { + PEER_GR_CMD = 0, + NO_PEER_GR_CMD, + PEER_DISABLE_CMD, + NO_PEER_DISABLE_CMD, + PEER_HELPER_CMD, + NO_PEER_HELPER_CMD +}; + +typedef unsigned int (*bgp_peer_gr_action_ptr)(struct peer *, enum peer_mode, + enum peer_mode); + +struct bgp_peer_gr { + enum peer_mode next_state; + bgp_peer_gr_action_ptr action_fun; +}; + +/* + * BGP FSM event codes, per RFC 4271 ss. 8.1 + */ +enum bgp_fsm_rfc_codes { + BGP_FSM_ManualStart = 1, + BGP_FSM_ManualStop = 2, + BGP_FSM_AutomaticStart = 3, + BGP_FSM_ManualStart_with_PassiveTcpEstablishment = 4, + BGP_FSM_AutomaticStart_with_PassiveTcpEstablishment = 5, + BGP_FSM_AutomaticStart_with_DampPeerOscillations = 6, + BGP_FSM_AutomaticStart_with_DampPeerOscillations_and_PassiveTcpEstablishment = + 7, + BGP_FSM_AutomaticStop = 8, + BGP_FSM_ConnectRetryTimer_Expires = 9, + BGP_FSM_HoldTimer_Expires = 10, + BGP_FSM_KeepaliveTimer_Expires = 11, + BGP_FSM_DelayOpenTimer_Expires = 12, + BGP_FSM_IdleHoldTimer_Expires = 13, + BGP_FSM_TcpConnection_Valid = 14, + BGP_FSM_Tcp_CR_Invalid = 15, + BGP_FSM_Tcp_CR_Acked = 16, + BGP_FSM_TcpConnectionConfirmed = 17, + BGP_FSM_TcpConnectionFails = 18, + BGP_FSM_BGPOpen = 19, + BGP_FSM_BGPOpen_with_DelayOpenTimer_running = 20, + BGP_FSM_BGPHeaderErr = 21, + BGP_FSM_BGPOpenMsgErr = 22, + BGP_FSM_OpenCollisionDump = 23, + BGP_FSM_NotifMsgVerErr = 24, + BGP_FSM_NotifMsg = 25, + BGP_FSM_KeepAliveMsg = 26, + BGP_FSM_UpdateMsg = 27, + BGP_FSM_UpdateMsgErr = 28 +}; + +/* + * BGP finite state machine events + * + * Note: these do not correspond to RFC-defined event codes. Those are + * defined elsewhere. + */ +enum bgp_fsm_events { + BGP_Start = 1, + BGP_Stop, + TCP_connection_open, + TCP_connection_open_w_delay, + TCP_connection_closed, + TCP_connection_open_failed, + TCP_fatal_error, + ConnectRetry_timer_expired, + Hold_Timer_expired, + KeepAlive_timer_expired, + DelayOpen_timer_expired, + Receive_OPEN_message, + Receive_KEEPALIVE_message, + Receive_UPDATE_message, + Receive_NOTIFICATION_message, + Clearing_Completed, + BGP_EVENTS_MAX, +}; + +/* BGP finite state machine status. */ +enum bgp_fsm_status { + Idle = 1, + Connect, + Active, + OpenSent, + OpenConfirm, + Established, + Clearing, + Deleted, + BGP_STATUS_MAX, +}; + +#define PEER_HOSTNAME(peer) ((peer)->host ? (peer)->host : "(unknown peer)") + +struct llgr_info { + uint32_t stale_time; + uint8_t flags; +}; + +struct addpath_paths_limit { + uint16_t send; + uint16_t receive; +}; + +struct peer_connection { + struct peer *peer; + + /* Status of the peer connection. */ + enum bgp_fsm_status status; + enum bgp_fsm_status ostatus; + + int fd; + + /* Thread flags */ + _Atomic uint32_t thread_flags; +#define PEER_THREAD_WRITES_ON (1U << 0) +#define PEER_THREAD_READS_ON (1U << 1) + + /* Packet receive and send buffer. */ + pthread_mutex_t io_mtx; // guards ibuf, obuf + struct stream_fifo *ibuf; // packets waiting to be processed + struct stream_fifo *obuf; // packets waiting to be written + + struct ringbuf *ibuf_work; // WiP buffer used by bgp_read() only + + struct event *t_read; + struct event *t_write; + struct event *t_connect; + struct event *t_delayopen; + struct event *t_start; + struct event *t_holdtime; + + struct event *t_connect_check_r; + struct event *t_connect_check_w; + + struct event *t_gr_restart; + struct event *t_gr_stale; + + struct event *t_generate_updgrp_packets; + struct event *t_pmax_restart; + + struct event *t_routeadv; + struct event *t_process_packet; + struct event *t_process_packet_error; + + union sockunion su; +#define BGP_CONNECTION_SU_UNSPEC(connection) \ + (connection->su.sa.sa_family == AF_UNSPEC) +}; +extern struct peer_connection *bgp_peer_connection_new(struct peer *peer); +extern void bgp_peer_connection_free(struct peer_connection **connection); +extern void bgp_peer_connection_buffers_free(struct peer_connection *connection); + +/* BGP neighbor structure. */ +struct peer { + /* BGP structure. */ + struct bgp *bgp; + + /* reference count, primarily to allow bgp_process'ing of route_node's + * to be done after a struct peer is deleted. + * + * named 'lock' for hysterical reasons within Quagga. + */ + int lock; + + /* BGP peer group. */ + struct peer_group *group; + + /* BGP peer_af structures, per configured AF on this peer */ + struct peer_af *peer_af_array[BGP_AF_MAX]; + + /* Peer's remote AS number. */ + int as_type; + as_t as; + /* for vty as format */ + char *as_pretty; + + /* Peer's local AS number. */ + as_t local_as; + + enum bgp_peer_sort sort; + enum bgp_peer_sub_sort sub_sort; + + /* Peer's Change local AS number. */ + as_t change_local_as; + /* for vty as format */ + char *change_local_as_pretty; + + /* Remote router ID. */ + struct in_addr remote_id; + + /* Local router ID. */ + struct in_addr local_id; + + struct stream *curr; // the current packet being parsed + + /* the doppelganger peer structure, due to dual TCP conn setup */ + struct peer *doppelganger; + + /* FSM events, stored for debug purposes. + * Note: uchar used for reduced memory usage. + */ + enum bgp_fsm_events cur_event; + enum bgp_fsm_events last_event; + enum bgp_fsm_events last_major_event; + + /* Peer index, used for dumping TABLE_DUMP_V2 format */ + uint16_t table_dump_index; + + /* Peer information */ + + /* + * We will have 2 `struct peer_connection` data structures + * connection is our attempt to talk to our peer. incoming + * is the peer attempting to talk to us. When it is + * time to consolidate between the two, we'll solidify + * into the connection variable being used. + */ + struct peer_connection *connection; + + int ttl; /* TTL of TCP connection to the peer. */ + int rtt; /* Estimated round-trip-time from TCP_INFO */ + int rtt_expected; /* Expected round-trip-time for a peer */ + uint8_t rtt_keepalive_rcv; /* Received count for RTT shutdown */ + uint8_t rtt_keepalive_conf; /* Configured count for RTT shutdown */ + int gtsm_hops; /* minimum hopcount to peer */ + char *desc; /* Description of the peer. */ + unsigned short port; /* Destination port for peer */ + char *host; /* Printable address of the peer. */ + + time_t uptime; /* Last Up/Down time */ + time_t readtime; /* Last read time */ + time_t resettime; /* Last reset time */ + + char *conf_if; /* neighbor interface config name. */ + struct interface *ifp; /* corresponding interface */ + char *ifname; /* bind interface name. */ + char *update_if; + union sockunion *update_source; + + union sockunion *su_local; /* Sockunion of local address. */ + union sockunion *su_remote; /* Sockunion of remote address. */ + int shared_network; /* Is this peer shared same network. */ + struct bgp_nexthop nexthop; /* Nexthop */ + + /* Roles in bgp session */ + uint8_t local_role; + uint8_t remote_role; +#define ROLE_PROVIDER 0 +#define ROLE_RS_SERVER 1 +#define ROLE_RS_CLIENT 2 +#define ROLE_CUSTOMER 3 +#define ROLE_PEER 4 +#define ROLE_UNDEFINED 255 + +#define ROLE_NAME_MAX_LEN 20 + + /* Peer address family configuration. */ + uint8_t afc[AFI_MAX][SAFI_MAX]; + uint8_t afc_nego[AFI_MAX][SAFI_MAX]; + uint8_t afc_adv[AFI_MAX][SAFI_MAX]; + uint8_t afc_recv[AFI_MAX][SAFI_MAX]; + + /* Capability flags (reset in bgp_stop) */ + uint64_t cap; +#define PEER_CAP_REFRESH_ADV (1ULL << 0) /* refresh advertised */ +#define PEER_CAP_REFRESH_RCV (1ULL << 2) /* refresh rfc received */ +#define PEER_CAP_DYNAMIC_ADV (1ULL << 3) /* dynamic advertised */ +#define PEER_CAP_DYNAMIC_RCV (1ULL << 4) /* dynamic received */ +#define PEER_CAP_RESTART_ADV (1ULL << 5) /* restart advertised */ +#define PEER_CAP_RESTART_RCV (1ULL << 6) /* restart received */ +#define PEER_CAP_AS4_ADV (1ULL << 7) /* as4 advertised */ +#define PEER_CAP_AS4_RCV (1ULL << 8) /* as4 received */ +/* sent graceful-restart restart (R) bit */ +#define PEER_CAP_GRACEFUL_RESTART_R_BIT_ADV (1ULL << 9) +/* received graceful-restart restart (R) bit */ +#define PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV (1ULL << 10) +#define PEER_CAP_ADDPATH_ADV (1ULL << 11) /* addpath advertised */ +#define PEER_CAP_ADDPATH_RCV (1ULL << 12) /* addpath received */ +#define PEER_CAP_ENHE_ADV (1ULL << 13) /* Extended nexthop advertised */ +#define PEER_CAP_ENHE_RCV (1ULL << 14) /* Extended nexthop received */ +#define PEER_CAP_HOSTNAME_ADV (1ULL << 15) /* hostname advertised */ +#define PEER_CAP_HOSTNAME_RCV (1ULL << 16) /* hostname received */ +#define PEER_CAP_ENHANCED_RR_ADV (1ULL << 17) /* enhanced rr advertised */ +#define PEER_CAP_ENHANCED_RR_RCV (1ULL << 18) /* enhanced rr received */ +#define PEER_CAP_EXTENDED_MESSAGE_ADV (1ULL << 19) +#define PEER_CAP_EXTENDED_MESSAGE_RCV (1ULL << 20) +#define PEER_CAP_LLGR_ADV (1ULL << 21) +#define PEER_CAP_LLGR_RCV (1ULL << 22) +/* sent graceful-restart notification (N) bit */ +#define PEER_CAP_GRACEFUL_RESTART_N_BIT_ADV (1ULL << 23) +/* received graceful-restart notification (N) bit */ +#define PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV (1ULL << 24) +#define PEER_CAP_ROLE_ADV (1ULL << 25) /* role advertised */ +#define PEER_CAP_ROLE_RCV (1ULL << 26) /* role received */ +#define PEER_CAP_SOFT_VERSION_ADV (1ULL << 27) +#define PEER_CAP_SOFT_VERSION_RCV (1ULL << 28) +#define PEER_CAP_PATHS_LIMIT_ADV (1U << 29) +#define PEER_CAP_PATHS_LIMIT_RCV (1U << 30) + + /* Capability flags (reset in bgp_stop) */ + uint32_t af_cap[AFI_MAX][SAFI_MAX]; +#define PEER_CAP_ORF_PREFIX_SM_ADV (1U << 0) /* send-mode advertised */ +#define PEER_CAP_ORF_PREFIX_RM_ADV (1U << 1) /* receive-mode advertised */ +#define PEER_CAP_ORF_PREFIX_SM_RCV (1U << 2) /* send-mode received */ +#define PEER_CAP_ORF_PREFIX_RM_RCV (1U << 3) /* receive-mode received */ +#define PEER_CAP_RESTART_AF_RCV (1U << 6) /* graceful restart afi/safi received */ +#define PEER_CAP_RESTART_AF_PRESERVE_RCV (1U << 7) /* graceful restart afi/safi F-bit received */ +#define PEER_CAP_ADDPATH_AF_TX_ADV (1U << 8) /* addpath tx advertised */ +#define PEER_CAP_ADDPATH_AF_TX_RCV (1U << 9) /* addpath tx received */ +#define PEER_CAP_ADDPATH_AF_RX_ADV (1U << 10) /* addpath rx advertised */ +#define PEER_CAP_ADDPATH_AF_RX_RCV (1U << 11) /* addpath rx received */ +#define PEER_CAP_ENHE_AF_ADV (1U << 12) /* Extended nexthopi afi/safi advertised */ +#define PEER_CAP_ENHE_AF_RCV (1U << 13) /* Extended nexthop afi/safi received */ +#define PEER_CAP_ENHE_AF_NEGO (1U << 14) /* Extended nexthop afi/safi negotiated */ +#define PEER_CAP_LLGR_AF_ADV (1U << 15) +#define PEER_CAP_LLGR_AF_RCV (1U << 16) +#define PEER_CAP_PATHS_LIMIT_AF_ADV (1U << 17) +#define PEER_CAP_PATHS_LIMIT_AF_RCV (1U << 18) + + /* Global configuration flags. */ + /* + * Parallel array to flags that indicates whether each flag originates + * from a peer-group or if it is config that is specific to this + * individual peer. If a flag is set independent of the peer-group, the + * same bit should be set here. If this peer is a peer-group, this + * memory region should be all zeros. + * + * The assumption is that the default state for all flags is unset, + * so if a flag is unset, the corresponding override flag is unset too. + * However if a flag is set, the corresponding override flag is set. + */ + uint64_t flags_override; + /* + * Parallel array to flags that indicates whether the default behavior + * of *flags_override* should be inverted. If a flag is unset and the + * corresponding invert flag is set, the corresponding override flag + * would be set. However if a flag is set and the corresponding invert + * flag is unset, the corresponding override flag would be unset. + * + * This can be used for attributes like *send-community*, which are + * implicitely enabled and have to be disabled explicitely, compared to + * 'normal' attributes like *next-hop-self* which are implicitely set. + * + * All operations dealing with flags should apply the following boolean + * logic to keep the internal flag system in a sane state: + * + * value=0 invert=0 Inherit flag if member, otherwise unset flag + * value=0 invert=1 Unset flag unconditionally + * value=1 invert=0 Set flag unconditionally + * value=1 invert=1 Inherit flag if member, otherwise set flag + * + * Contrary to the implementation of *flags_override*, the flag + * inversion state can be set either on the peer OR the peer *and* the + * peer-group. This was done on purpose, as the inversion state of a + * flag can be determined on either the peer or the peer-group. + * + * Example: Enabling the cisco configuration mode inverts all flags + * related to *send-community* unconditionally for both peer-groups and + * peers. + * + * This behavior is different for interface peers though, which enable + * the *extended-nexthop* flag by default, which regular peers do not. + * As the peer-group can contain both regular and interface peers, the + * flag inversion state must be set on the peer only. + * + * When a peer inherits the configuration from a peer-group and the + * inversion state of the flag differs between peer and peer-group, the + * newly set value must equal to the inverted state of the peer-group. + */ + uint64_t flags_invert; + /* + * Effective array for storing the peer/peer-group flags. In case of a + * peer-group, the peer-specific overrides (see flags_override and + * flags_invert) must be respected. + * When changing the structure of flags/af_flags, do not forget to + * change flags_invert/flags_override too. + */ + uint64_t flags; +#define PEER_FLAG_PASSIVE (1ULL << 0) /* passive mode */ +#define PEER_FLAG_SHUTDOWN (1ULL << 1) /* shutdown */ +#define PEER_FLAG_DONT_CAPABILITY (1ULL << 2) /* dont-capability */ +#define PEER_FLAG_OVERRIDE_CAPABILITY (1ULL << 3) /* override-capability */ +#define PEER_FLAG_STRICT_CAP_MATCH (1ULL << 4) /* strict-match */ +#define PEER_FLAG_DYNAMIC_CAPABILITY (1ULL << 5) /* dynamic capability */ +#define PEER_FLAG_DISABLE_CONNECTED_CHECK (1ULL << 6) /* disable-connected-check */ +#define PEER_FLAG_LOCAL_AS_NO_PREPEND (1ULL << 7) /* local-as no-prepend */ +#define PEER_FLAG_LOCAL_AS_REPLACE_AS (1ULL << 8) /* local-as no-prepend replace-as */ +#define PEER_FLAG_DELETE (1ULL << 9) /* mark the peer for deleting */ +#define PEER_FLAG_CONFIG_NODE (1ULL << 10) /* the node to update configs on */ +#define PEER_FLAG_LONESOUL (1ULL << 11) +#define PEER_FLAG_DYNAMIC_NEIGHBOR (1ULL << 12) /* dynamic neighbor */ +#define PEER_FLAG_CAPABILITY_ENHE (1ULL << 13) /* Extended next-hop (rfc 5549)*/ +#define PEER_FLAG_IFPEER_V6ONLY (1ULL << 14) /* if-based peer is v6 only */ +#define PEER_FLAG_IS_RFAPI_HD (1ULL << 15) /* attached to rfapi HD */ +#define PEER_FLAG_ENFORCE_FIRST_AS (1ULL << 16) /* enforce-first-as */ +#define PEER_FLAG_ROUTEADV (1ULL << 17) /* route advertise */ +#define PEER_FLAG_TIMER (1ULL << 18) /* keepalive & holdtime */ +#define PEER_FLAG_TIMER_CONNECT (1ULL << 19) /* connect timer */ +#define PEER_FLAG_PASSWORD (1ULL << 20) /* password */ +#define PEER_FLAG_LOCAL_AS (1ULL << 21) /* local-as */ +#define PEER_FLAG_UPDATE_SOURCE (1ULL << 22) /* update-source */ + + /* BGP-GR Peer related flags */ +#define PEER_FLAG_GRACEFUL_RESTART_HELPER (1ULL << 23) /* Helper */ +#define PEER_FLAG_GRACEFUL_RESTART (1ULL << 24) /* Graceful Restart */ +#define PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT (1ULL << 25) /* Global-Inherit */ +#define PEER_FLAG_RTT_SHUTDOWN (1ULL << 26) /* shutdown rtt */ +#define PEER_FLAG_TIMER_DELAYOPEN (1ULL << 27) /* delayopen timer */ +#define PEER_FLAG_TCP_MSS (1ULL << 28) /* tcp-mss */ +/* Disable IEEE floating-point link bandwidth encoding in + * extended communities. + */ +#define PEER_FLAG_DISABLE_LINK_BW_ENCODING_IEEE (1ULL << 29) +/* force the extended format for Optional Parameters in OPEN message */ +#define PEER_FLAG_EXTENDED_OPT_PARAMS (1ULL << 30) + + /* BGP Open Policy flags. + * Enforce using roles on both sides: + * `local-role ROLE strict-mode` configured. + */ +#define PEER_FLAG_ROLE_STRICT_MODE (1ULL << 31) + /* `local-role` configured */ +#define PEER_FLAG_ROLE (1ULL << 32) +#define PEER_FLAG_PORT (1ULL << 33) +#define PEER_FLAG_AIGP (1ULL << 34) +#define PEER_FLAG_GRACEFUL_SHUTDOWN (1ULL << 35) +#define PEER_FLAG_CAPABILITY_SOFT_VERSION (1ULL << 36) +#define PEER_FLAG_CAPABILITY_FQDN (1ULL << 37) /* fqdn capability */ +#define PEER_FLAG_AS_LOOP_DETECTION (1ULL << 38) /* as path loop detection */ +#define PEER_FLAG_EXTENDED_LINK_BANDWIDTH (1ULL << 39) + + /* + *GR-Disabled mode means unset PEER_FLAG_GRACEFUL_RESTART + *& PEER_FLAG_GRACEFUL_RESTART_HELPER + *and PEER_FLAG_GRACEFUL_RESTART_GLOBAL_INHERIT + */ + + struct bgp_peer_gr PEER_GR_FSM[BGP_PEER_GR_MODE][BGP_PEER_GR_EVENT_CMD]; + enum peer_mode peer_gr_present_state; + /* Non stop forwarding afi-safi count for BGP gr feature*/ + uint8_t nsf_af_count; + + uint8_t peer_gr_new_status_flag; +#define PEER_GRACEFUL_RESTART_NEW_STATE_HELPER (1U << 0) +#define PEER_GRACEFUL_RESTART_NEW_STATE_RESTART (1U << 1) +#define PEER_GRACEFUL_RESTART_NEW_STATE_INHERIT (1U << 2) + + /* outgoing message sent in CEASE_ADMIN_SHUTDOWN notify */ + char *tx_shutdown_message; + + /* NSF mode (graceful restart) */ + uint8_t nsf[AFI_MAX][SAFI_MAX]; + /* EOR Send time */ + time_t eor_stime[AFI_MAX][SAFI_MAX]; + /* Last update packet sent time */ + time_t pkt_stime[AFI_MAX][SAFI_MAX]; + + /* Peer / peer group route flap dampening configuration */ + struct bgp_damp_config damp[AFI_MAX][SAFI_MAX]; + + /* Peer Per AF flags */ + /* + * Please consult the comments for *flags_override*, *flags_invert* and + * *flags* to understand what these three arrays do. The address-family + * specific attributes are being treated the exact same way as global + * peer attributes. + */ + uint64_t af_flags_override[AFI_MAX][SAFI_MAX]; + uint64_t af_flags_invert[AFI_MAX][SAFI_MAX]; + uint64_t af_flags[AFI_MAX][SAFI_MAX]; +#define PEER_FLAG_SEND_COMMUNITY (1ULL << 0) +#define PEER_FLAG_SEND_EXT_COMMUNITY (1ULL << 1) +#define PEER_FLAG_NEXTHOP_SELF (1ULL << 2) +#define PEER_FLAG_REFLECTOR_CLIENT (1ULL << 3) +#define PEER_FLAG_RSERVER_CLIENT (1ULL << 4) +#define PEER_FLAG_SOFT_RECONFIG (1ULL << 5) +#define PEER_FLAG_AS_PATH_UNCHANGED (1ULL << 6) +#define PEER_FLAG_NEXTHOP_UNCHANGED (1ULL << 7) +#define PEER_FLAG_MED_UNCHANGED (1ULL << 8) +#define PEER_FLAG_DEFAULT_ORIGINATE (1ULL << 9) +#define PEER_FLAG_REMOVE_PRIVATE_AS (1ULL << 10) +#define PEER_FLAG_ALLOWAS_IN (1ULL << 11) +#define PEER_FLAG_ORF_PREFIX_SM (1ULL << 12) +#define PEER_FLAG_ORF_PREFIX_RM (1ULL << 13) +#define PEER_FLAG_MAX_PREFIX (1ULL << 14) +#define PEER_FLAG_MAX_PREFIX_WARNING (1ULL << 15) +#define PEER_FLAG_NEXTHOP_LOCAL_UNCHANGED (1ULL << 16) +#define PEER_FLAG_FORCE_NEXTHOP_SELF (1ULL << 17) +#define PEER_FLAG_REMOVE_PRIVATE_AS_ALL (1ULL << 18) +#define PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE (1ULL << 19) +#define PEER_FLAG_AS_OVERRIDE (1ULL << 20) +#define PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE (1ULL << 21) +#define PEER_FLAG_WEIGHT (1ULL << 22) +#define PEER_FLAG_ALLOWAS_IN_ORIGIN (1ULL << 23) +#define PEER_FLAG_SEND_LARGE_COMMUNITY (1ULL << 24) +#define PEER_FLAG_MAX_PREFIX_OUT (1ULL << 25) +#define PEER_FLAG_MAX_PREFIX_FORCE (1ULL << 26) +#define PEER_FLAG_DISABLE_ADDPATH_RX (1ULL << 27) +#define PEER_FLAG_SOO (1ULL << 28) +#define PEER_FLAG_SEND_EXT_COMMUNITY_RPKI (1ULL << 29) +#define PEER_FLAG_ADDPATH_RX_PATHS_LIMIT (1ULL << 30) +#define PEER_FLAG_CONFIG_DAMPENING (1U << 31) +#define PEER_FLAG_ACCEPT_OWN (1ULL << 63) + + enum bgp_addpath_strat addpath_type[AFI_MAX][SAFI_MAX]; + + /* MD5 password */ + char *password; + + /* default-originate route-map. */ + struct { + char *name; + struct route_map *map; + } default_rmap[AFI_MAX][SAFI_MAX]; + + /* Peer status flags. */ + uint16_t sflags; +#define PEER_STATUS_ACCEPT_PEER (1U << 0) /* accept peer */ +#define PEER_STATUS_PREFIX_OVERFLOW (1U << 1) /* prefix-overflow */ +#define PEER_STATUS_CAPABILITY_OPEN (1U << 2) /* capability open send */ +#define PEER_STATUS_HAVE_ACCEPT (1U << 3) /* accept peer's parent */ +#define PEER_STATUS_GROUP (1U << 4) /* peer-group conf */ +#define PEER_STATUS_NSF_MODE (1U << 5) /* NSF aware peer */ +#define PEER_STATUS_NSF_WAIT (1U << 6) /* wait comeback peer */ +/* received extended format encoding for OPEN message */ +#define PEER_STATUS_EXT_OPT_PARAMS_LENGTH (1U << 7) + + /* Peer status af flags (reset in bgp_stop) */ + uint16_t af_sflags[AFI_MAX][SAFI_MAX]; +#define PEER_STATUS_ORF_PREFIX_SEND (1U << 0) /* prefix-list send peer */ +#define PEER_STATUS_ORF_WAIT_REFRESH (1U << 1) /* wait refresh received peer */ +#define PEER_STATUS_PREFIX_THRESHOLD (1U << 2) /* exceed prefix-threshold */ +#define PEER_STATUS_PREFIX_LIMIT (1U << 3) /* exceed prefix-limit */ +#define PEER_STATUS_EOR_SEND (1U << 4) /* end-of-rib send to peer */ +#define PEER_STATUS_EOR_RECEIVED (1U << 5) /* end-of-rib received from peer */ +#define PEER_STATUS_ENHANCED_REFRESH (1U << 6) /* Enhanced Route Refresh */ +#define PEER_STATUS_BORR_SEND (1U << 7) /* BoRR send to peer */ +#define PEER_STATUS_BORR_RECEIVED (1U << 8) /* BoRR received from peer */ +#define PEER_STATUS_EORR_SEND (1U << 9) /* EoRR send to peer */ +#define PEER_STATUS_EORR_RECEIVED (1U << 10) /* EoRR received from peer */ +/* LLGR aware peer */ +#define PEER_STATUS_LLGR_WAIT (1U << 11) +#define PEER_STATUS_REFRESH_PENDING (1U << 12) /* refresh request from peer */ +#define PEER_STATUS_RTT_SHUTDOWN (1U << 13) /* In shutdown state due to RTT */ + + /* Configured timer values. */ + _Atomic uint32_t holdtime; + _Atomic uint32_t keepalive; + _Atomic uint32_t connect; + _Atomic uint32_t routeadv; + _Atomic uint32_t delayopen; + + /* Timer values. */ + _Atomic uint32_t v_start; + _Atomic uint32_t v_connect; + _Atomic uint32_t v_holdtime; + _Atomic uint32_t v_keepalive; + _Atomic uint32_t v_routeadv; + _Atomic uint32_t v_delayopen; + _Atomic uint32_t v_pmax_restart; + _Atomic uint32_t v_gr_restart; + + /* Threads. */ + struct event *t_llgr_stale[AFI_MAX][SAFI_MAX]; + struct event *t_revalidate_all[AFI_MAX][SAFI_MAX]; + struct event *t_refresh_stalepath; + + /* Thread flags. */ + _Atomic uint32_t thread_flags; +#define PEER_THREAD_KEEPALIVES_ON (1U << 0) +#define PEER_THREAD_SUBGRP_ADV_DELAY (1U << 1) + + /* workqueues */ + struct work_queue *clear_node_queue; + +#define PEER_TOTAL_RX(peer) \ + atomic_load_explicit(&peer->open_in, memory_order_relaxed) \ + + atomic_load_explicit(&peer->update_in, memory_order_relaxed) \ + + atomic_load_explicit(&peer->notify_in, memory_order_relaxed) \ + + atomic_load_explicit(&peer->refresh_in, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->keepalive_in, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->dynamic_cap_in, \ + memory_order_relaxed) + +#define PEER_TOTAL_TX(peer) \ + atomic_load_explicit(&peer->open_out, memory_order_relaxed) \ + + atomic_load_explicit(&peer->update_out, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->notify_out, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->refresh_out, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->keepalive_out, \ + memory_order_relaxed) \ + + atomic_load_explicit(&peer->dynamic_cap_out, \ + memory_order_relaxed) + + /* Statistics field */ + _Atomic uint32_t open_in; /* Open message input count */ + _Atomic uint32_t open_out; /* Open message output count */ + _Atomic uint32_t update_in; /* Update message input count */ + _Atomic uint32_t update_out; /* Update message ouput count */ + _Atomic time_t update_time; /* Update message received time. */ + _Atomic uint32_t keepalive_in; /* Keepalive input count */ + _Atomic uint32_t keepalive_out; /* Keepalive output count */ + _Atomic uint32_t notify_in; /* Notify input count */ + _Atomic uint32_t notify_out; /* Notify output count */ + _Atomic uint32_t refresh_in; /* Route Refresh input count */ + _Atomic uint32_t refresh_out; /* Route Refresh output count */ + _Atomic uint32_t dynamic_cap_in; /* Dynamic Capability input count. */ + _Atomic uint32_t dynamic_cap_out; /* Dynamic Capability output count. */ + + uint32_t stat_pfx_filter; + uint32_t stat_pfx_aspath_loop; + uint32_t stat_pfx_originator_loop; + uint32_t stat_pfx_cluster_loop; + uint32_t stat_pfx_nh_invalid; + uint32_t stat_pfx_dup_withdraw; + uint32_t stat_upd_7606; /* RFC7606: treat-as-withdraw */ + uint64_t stat_pfx_loc_rib; /* RFC7854 : Number of routes in Loc-RIB */ + uint64_t stat_pfx_adj_rib_in; /* RFC7854 : Number of routes in Adj-RIBs-In */ + + /* BGP state count */ + uint32_t established; /* Established */ + uint32_t dropped; /* Dropped */ + + /* Update delay related fields */ + uint8_t update_delay_over; /* When this is set, BGP is no more waiting + for EOR */ + + time_t synctime; + /* timestamp when the last UPDATE msg was written */ + _Atomic time_t last_write; + /* timestamp when the last msg was written */ + _Atomic time_t last_update; + + /* only updated under io_mtx. + * last_sendq_warn is only for ratelimiting log warning messages. + */ + time_t last_sendq_ok, last_sendq_warn; + + /* Notify data. */ + struct bgp_notify notify; + + /* Filter structure. */ + struct bgp_filter filter[AFI_MAX][SAFI_MAX]; + + /* + * Parallel array to filter that indicates whether each filter + * originates from a peer-group or if it is config that is specific to + * this individual peer. If a filter is set independent of the + * peer-group the appropriate bit should be set here. If this peer is a + * peer-group, this memory region should be all zeros. The assumption + * is that the default state for all flags is unset. Due to filters + * having a direction (e.g. in/out/...), this array has a third + * dimension for storing the overrides independently per direction. + * + * Notes: + * - if a filter for an individual peer is unset, the corresponding + * override flag is unset and the peer is considered to be back in + * sync with the peer-group. + * - This does *not* contain the filter values, rather it contains + * whether the filter in filter (struct bgp_filter) is peer-specific. + */ + uint8_t filter_override[AFI_MAX][SAFI_MAX][FILTER_MAX]; +#define PEER_FT_DISTRIBUTE_LIST (1U << 0) /* distribute-list */ +#define PEER_FT_FILTER_LIST (1U << 1) /* filter-list */ +#define PEER_FT_PREFIX_LIST (1U << 2) /* prefix-list */ +#define PEER_FT_ROUTE_MAP (1U << 3) /* route-map */ +#define PEER_FT_UNSUPPRESS_MAP (1U << 4) /* unsuppress-map */ +#define PEER_FT_ADVERTISE_MAP (1U << 5) /* advertise-map */ + + /* ORF Prefix-list */ + struct prefix_list *orf_plist[AFI_MAX][SAFI_MAX]; + + /* Text description of last attribute rcvd */ + char rcvd_attr_str[BUFSIZ]; + + /* Track if we printed the attribute in debugs */ + int rcvd_attr_printed; + + /* Accepted prefix count */ + uint32_t pcount[AFI_MAX][SAFI_MAX]; + + /* Max prefix count. */ + uint32_t pmax[AFI_MAX][SAFI_MAX]; + uint8_t pmax_threshold[AFI_MAX][SAFI_MAX]; + uint16_t pmax_restart[AFI_MAX][SAFI_MAX]; +#define MAXIMUM_PREFIX_THRESHOLD_DEFAULT 75 + + /* Send prefix count. */ + uint32_t pmax_out[AFI_MAX][SAFI_MAX]; + + /* allowas-in. */ + char allowas_in[AFI_MAX][SAFI_MAX]; + + /* soo */ + struct ecommunity *soo[AFI_MAX][SAFI_MAX]; + + /* weight */ + unsigned long weight[AFI_MAX][SAFI_MAX]; + + /* peer reset cause */ + uint8_t last_reset; +#define PEER_DOWN_RID_CHANGE 1U /* bgp router-id command */ +#define PEER_DOWN_REMOTE_AS_CHANGE 2U /* neighbor remote-as command */ +#define PEER_DOWN_LOCAL_AS_CHANGE 3U /* neighbor local-as command */ +#define PEER_DOWN_CLID_CHANGE 4U /* bgp cluster-id command */ +#define PEER_DOWN_CONFED_ID_CHANGE 5U /* bgp confederation id command */ +#define PEER_DOWN_CONFED_PEER_CHANGE 6U /* bgp confederation peer command */ +#define PEER_DOWN_RR_CLIENT_CHANGE 7U /* neighbor rr-client command */ +#define PEER_DOWN_RS_CLIENT_CHANGE 8U /* neighbor rs-client command */ +#define PEER_DOWN_UPDATE_SOURCE_CHANGE 9U /* neighbor update-source command */ +#define PEER_DOWN_AF_ACTIVATE 10U /* neighbor activate command */ +#define PEER_DOWN_USER_SHUTDOWN 11U /* neighbor shutdown command */ +#define PEER_DOWN_USER_RESET 12U /* clear ip bgp command */ +#define PEER_DOWN_NOTIFY_RECEIVED 13U /* notification received */ +#define PEER_DOWN_NOTIFY_SEND 14U /* notification send */ +#define PEER_DOWN_CLOSE_SESSION 15U /* tcp session close */ +#define PEER_DOWN_NEIGHBOR_DELETE 16U /* neghbor delete */ +#define PEER_DOWN_RMAP_BIND 17U /* neghbor peer-group command */ +#define PEER_DOWN_RMAP_UNBIND 18U /* no neighbor peer-group command */ +#define PEER_DOWN_CAPABILITY_CHANGE 19U /* neighbor capability command */ +#define PEER_DOWN_PASSIVE_CHANGE 20U /* neighbor passive command */ +#define PEER_DOWN_MULTIHOP_CHANGE 21U /* neighbor multihop command */ +#define PEER_DOWN_NSF_CLOSE_SESSION 22U /* NSF tcp session close */ +#define PEER_DOWN_V6ONLY_CHANGE 23U /* if-based peering v6only toggled */ +#define PEER_DOWN_BFD_DOWN 24U /* BFD down */ +#define PEER_DOWN_IF_DOWN 25U /* Interface down */ +#define PEER_DOWN_NBR_ADDR_DEL 26U /* Peer address lost */ +#define PEER_DOWN_WAITING_NHT 27U /* Waiting for NHT to resolve */ +#define PEER_DOWN_NBR_ADDR 28U /* Waiting for peer IPv6 IP Addr */ +#define PEER_DOWN_VRF_UNINIT 29U /* Associated VRF is not init yet */ +#define PEER_DOWN_NOAFI_ACTIVATED 30U /* No AFI/SAFI activated for peer */ +#define PEER_DOWN_AS_SETS_REJECT 31U /* Reject routes with AS_SET */ +#define PEER_DOWN_WAITING_OPEN 32U /* Waiting for open to succeed */ +#define PEER_DOWN_PFX_COUNT 33U /* Reached received prefix count */ +#define PEER_DOWN_SOCKET_ERROR 34U /* Some socket error happened */ +#define PEER_DOWN_RTT_SHUTDOWN 35U /* Automatically shutdown due to RTT */ +#define PEER_DOWN_SUPPRESS_FIB_PENDING 36U /* Suppress fib pending changed */ + /* + * Remember to update peer_down_str in bgp_fsm.c when you add + * a new value to the last_reset reason + */ + + struct stream *last_reset_cause; + + /* The kind of route-map Flags.*/ + uint16_t rmap_type; +#define PEER_RMAP_TYPE_IN (1U << 0) /* neighbor route-map in */ +#define PEER_RMAP_TYPE_OUT (1U << 1) /* neighbor route-map out */ +#define PEER_RMAP_TYPE_NETWORK (1U << 2) /* network route-map */ +#define PEER_RMAP_TYPE_REDISTRIBUTE (1U << 3) /* redistribute route-map */ +#define PEER_RMAP_TYPE_DEFAULT (1U << 4) /* default-originate route-map */ +#define PEER_RMAP_TYPE_NOSET (1U << 5) /* not allow to set commands */ +#define PEER_RMAP_TYPE_IMPORT (1U << 6) /* neighbor route-map import */ +#define PEER_RMAP_TYPE_EXPORT (1U << 7) /* neighbor route-map export */ +#define PEER_RMAP_TYPE_AGGREGATE (1U << 8) /* aggregate-address route-map */ + + /** Peer overwrite configuration. */ + struct bfd_session_config { + /** + * Manual configuration bit. + * + * This flag only makes sense for real peers (and not groups), + * it keeps track if the user explicitly configured BFD for a + * peer. + */ + bool manual; + /** Control Plane Independent. */ + bool cbit; + /** Detection multiplier. */ + uint8_t detection_multiplier; + /** Minimum required RX interval. */ + uint32_t min_rx; + /** Minimum required TX interval. */ + uint32_t min_tx; + /** Profile name. */ + char profile[BFD_PROFILE_NAME_LEN]; + /** Peer BFD session */ + struct bfd_session_params *session; + } * bfd_config; + + /* hostname and domainname advertised by host */ + char *hostname; + char *domainname; + + /* Extended Message Support */ + uint16_t max_packet_size; + + /* Conditional advertisement */ + bool advmap_config_change[AFI_MAX][SAFI_MAX]; + bool advmap_table_change; + + /* set TCP max segment size */ + uint32_t tcp_mss; + + /* Long-lived Graceful Restart */ + struct llgr_info llgr[AFI_MAX][SAFI_MAX]; + + bool shut_during_cfg; + +#define BGP_ATTR_MAX 255 + /* Path attributes discard */ + bool discard_attrs[BGP_ATTR_MAX + 1]; + + /* Path attributes treat-as-withdraw */ + bool withdraw_attrs[BGP_ATTR_MAX + 1]; + + /* BGP Software Version Capability */ +#define BGP_MAX_SOFT_VERSION 64 + char *soft_version; + + /* Add-Path Best selected paths number to advertise */ + uint8_t addpath_best_selected[AFI_MAX][SAFI_MAX]; + + /* Add-Path Paths-Limit */ + struct addpath_paths_limit addpath_paths_limit[AFI_MAX][SAFI_MAX]; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(peer); + +/* Inherit peer attribute from peer-group. */ +#define PEER_ATTR_INHERIT(peer, group, attr) \ + ((peer)->attr = (group)->conf->attr) +#define PEER_STR_ATTR_INHERIT(peer, group, attr, mt) \ + do { \ + XFREE(mt, (peer)->attr); \ + if ((group)->conf->attr) \ + (peer)->attr = XSTRDUP(mt, (group)->conf->attr); \ + else \ + (peer)->attr = NULL; \ + } while (0) +#define PEER_SU_ATTR_INHERIT(peer, group, attr) \ + do { \ + if ((peer)->attr) \ + sockunion_free((peer)->attr); \ + if ((group)->conf->attr) \ + (peer)->attr = sockunion_dup((group)->conf->attr); \ + else \ + (peer)->attr = NULL; \ + } while (0) + +/* Check if suppress start/restart of sessions to peer. */ +#define BGP_PEER_START_SUPPRESSED(P) \ + (CHECK_FLAG((P)->flags, PEER_FLAG_SHUTDOWN) || \ + CHECK_FLAG((P)->sflags, PEER_STATUS_PREFIX_OVERFLOW) || \ + CHECK_FLAG((P)->bgp->flags, BGP_FLAG_SHUTDOWN) || \ + (P)->shut_during_cfg) + +#define PEER_ROUTE_ADV_DELAY(peer) \ + (CHECK_FLAG(peer->thread_flags, PEER_THREAD_SUBGRP_ADV_DELAY)) + +#define PEER_PASSWORD_MINLEN (1) +#define PEER_PASSWORD_MAXLEN (80) + +/* This structure's member directly points incoming packet data + stream. */ +struct bgp_nlri { + /* AFI. */ + uint16_t afi; /* iana_afi_t */ + + /* SAFI. */ + uint8_t safi; /* iana_safi_t */ + + /* Length of whole NLRI. */ + bgp_size_t length; + + /* Pointer to NLRI byte stream. */ + uint8_t *nlri; +}; + +/* BGP versions. */ +#define BGP_VERSION_4 4 + +/* Default BGP port number. */ +#define BGP_PORT_DEFAULT 179 + +/* Extended BGP Administrative Shutdown Communication */ +#define BGP_ADMIN_SHUTDOWN_MSG_LEN 255 + +/* BGP minimum message size. */ +#define BGP_MSG_OPEN_MIN_SIZE (BGP_HEADER_SIZE + 10) +#define BGP_MSG_UPDATE_MIN_SIZE (BGP_HEADER_SIZE + 4) +#define BGP_MSG_NOTIFY_MIN_SIZE (BGP_HEADER_SIZE + 2) +#define BGP_MSG_KEEPALIVE_MIN_SIZE (BGP_HEADER_SIZE + 0) +#define BGP_MSG_ROUTE_REFRESH_MIN_SIZE (BGP_HEADER_SIZE + 4) +#define BGP_MSG_CAPABILITY_MIN_SIZE (BGP_HEADER_SIZE + 3) + +/* BGP message types. */ +#define BGP_MSG_OPEN 1 +#define BGP_MSG_UPDATE 2 +#define BGP_MSG_NOTIFY 3 +#define BGP_MSG_KEEPALIVE 4 +#define BGP_MSG_ROUTE_REFRESH_NEW 5 +#define BGP_MSG_CAPABILITY 6 +#define BGP_MSG_ROUTE_REFRESH_OLD 128 + +/* BGP open optional parameter. */ +#define BGP_OPEN_OPT_CAP 2 + +/* BGP4 attribute type codes. */ +#define BGP_ATTR_ORIGIN 1 +#define BGP_ATTR_AS_PATH 2 +#define BGP_ATTR_NEXT_HOP 3 +#define BGP_ATTR_MULTI_EXIT_DISC 4 +#define BGP_ATTR_LOCAL_PREF 5 +#define BGP_ATTR_ATOMIC_AGGREGATE 6 +#define BGP_ATTR_AGGREGATOR 7 +#define BGP_ATTR_COMMUNITIES 8 +#define BGP_ATTR_ORIGINATOR_ID 9 +#define BGP_ATTR_CLUSTER_LIST 10 +#define BGP_ATTR_MP_REACH_NLRI 14 +#define BGP_ATTR_MP_UNREACH_NLRI 15 +#define BGP_ATTR_EXT_COMMUNITIES 16 +#define BGP_ATTR_AS4_PATH 17 +#define BGP_ATTR_AS4_AGGREGATOR 18 +#define BGP_ATTR_PMSI_TUNNEL 22 +#define BGP_ATTR_ENCAP 23 +#define BGP_ATTR_IPV6_EXT_COMMUNITIES 25 +#define BGP_ATTR_AIGP 26 +#define BGP_ATTR_LARGE_COMMUNITIES 32 +#define BGP_ATTR_OTC 35 +#define BGP_ATTR_PREFIX_SID 40 +#ifdef ENABLE_BGP_VNC_ATTR +#define BGP_ATTR_VNC 255 +#endif + +/* BGP update origin. */ +#define BGP_ORIGIN_IGP 0 +#define BGP_ORIGIN_EGP 1 +#define BGP_ORIGIN_INCOMPLETE 2 +#define BGP_ORIGIN_UNSPECIFIED 255 + +/* BGP notify message codes. */ +#define BGP_NOTIFY_HEADER_ERR 1 +#define BGP_NOTIFY_OPEN_ERR 2 +#define BGP_NOTIFY_UPDATE_ERR 3 +#define BGP_NOTIFY_HOLD_ERR 4 +#define BGP_NOTIFY_FSM_ERR 5 +#define BGP_NOTIFY_CEASE 6 +#define BGP_NOTIFY_ROUTE_REFRESH_ERR 7 +#define BGP_NOTIFY_SEND_HOLD_ERR 8 + +/* Subcodes for BGP Finite State Machine Error */ +#define BGP_NOTIFY_FSM_ERR_SUBCODE_UNSPECIFIC 0 +#define BGP_NOTIFY_FSM_ERR_SUBCODE_OPENSENT 1 +#define BGP_NOTIFY_FSM_ERR_SUBCODE_OPENCONFIRM 2 +#define BGP_NOTIFY_FSM_ERR_SUBCODE_ESTABLISHED 3 + +#define BGP_NOTIFY_SUBCODE_UNSPECIFIC 0 + +/* BGP_NOTIFY_HEADER_ERR sub codes. */ +#define BGP_NOTIFY_HEADER_NOT_SYNC 1 +#define BGP_NOTIFY_HEADER_BAD_MESLEN 2 +#define BGP_NOTIFY_HEADER_BAD_MESTYPE 3 + +/* BGP_NOTIFY_OPEN_ERR sub codes. */ +#define BGP_NOTIFY_OPEN_MALFORMED_ATTR 0 +#define BGP_NOTIFY_OPEN_UNSUP_VERSION 1 +#define BGP_NOTIFY_OPEN_BAD_PEER_AS 2 +#define BGP_NOTIFY_OPEN_BAD_BGP_IDENT 3 +#define BGP_NOTIFY_OPEN_UNSUP_PARAM 4 +#define BGP_NOTIFY_OPEN_UNACEP_HOLDTIME 6 +#define BGP_NOTIFY_OPEN_UNSUP_CAPBL 7 +#define BGP_NOTIFY_OPEN_ROLE_MISMATCH 11 + +/* BGP_NOTIFY_UPDATE_ERR sub codes. */ +#define BGP_NOTIFY_UPDATE_MAL_ATTR 1 +#define BGP_NOTIFY_UPDATE_UNREC_ATTR 2 +#define BGP_NOTIFY_UPDATE_MISS_ATTR 3 +#define BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR 4 +#define BGP_NOTIFY_UPDATE_ATTR_LENG_ERR 5 +#define BGP_NOTIFY_UPDATE_INVAL_ORIGIN 6 +#define BGP_NOTIFY_UPDATE_INVAL_NEXT_HOP 8 +#define BGP_NOTIFY_UPDATE_OPT_ATTR_ERR 9 +#define BGP_NOTIFY_UPDATE_INVAL_NETWORK 10 +#define BGP_NOTIFY_UPDATE_MAL_AS_PATH 11 + +/* BGP_NOTIFY_CEASE sub codes (RFC 4486). */ +#define BGP_NOTIFY_CEASE_MAX_PREFIX 1 +#define BGP_NOTIFY_CEASE_ADMIN_SHUTDOWN 2 +#define BGP_NOTIFY_CEASE_PEER_UNCONFIG 3 +#define BGP_NOTIFY_CEASE_ADMIN_RESET 4 +#define BGP_NOTIFY_CEASE_CONNECT_REJECT 5 +#define BGP_NOTIFY_CEASE_CONFIG_CHANGE 6 +#define BGP_NOTIFY_CEASE_COLLISION_RESOLUTION 7 +#define BGP_NOTIFY_CEASE_OUT_OF_RESOURCE 8 +#define BGP_NOTIFY_CEASE_HARD_RESET 9 +#define BGP_NOTIFY_CEASE_BFD_DOWN 10 + +/* BGP_NOTIFY_ROUTE_REFRESH_ERR sub codes (RFC 7313). */ +#define BGP_NOTIFY_ROUTE_REFRESH_INVALID_MSG_LEN 1 + +/* BGP route refresh optional subtypes. */ +#define BGP_ROUTE_REFRESH_NORMAL 0 +#define BGP_ROUTE_REFRESH_BORR 1 +#define BGP_ROUTE_REFRESH_EORR 2 + +/* BGP timers default value. */ +#define BGP_INIT_START_TIMER 1 +/* The following 3 are RFC defaults that are overridden in bgp_vty.c with + * version-/profile-specific values. The values here do not matter, they only + * exist to provide a clear layering separation between core and CLI. + */ +#define BGP_DEFAULT_HOLDTIME 180 +#define BGP_DEFAULT_KEEPALIVE 60 +#define BGP_DEFAULT_CONNECT_RETRY 120 + +#define BGP_DEFAULT_EBGP_ROUTEADV 0 +#define BGP_DEFAULT_IBGP_ROUTEADV 0 + +/* BGP RFC 4271 DelayOpenTime default value */ +#define BGP_DEFAULT_DELAYOPEN 120 + +/* BGP default local preference. */ +#define BGP_DEFAULT_LOCAL_PREF 100 + +/* BGP local-preference to send when 'bgp graceful-shutdown' + * is configured */ +#define BGP_GSHUT_LOCAL_PREF 0 + +/* BGP default subgroup packet queue max . */ +#define BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX 40 + +/* BGP graceful restart */ +#define BGP_DEFAULT_RESTART_TIME 120 +#define BGP_DEFAULT_STALEPATH_TIME 360 +#define BGP_DEFAULT_SELECT_DEFERRAL_TIME 360 +#define BGP_DEFAULT_RIB_STALE_TIME 500 +#define BGP_DEFAULT_UPDATE_ADVERTISEMENT_TIME 1 + +/* BGP Long-lived Graceful Restart */ +#define BGP_DEFAULT_LLGR_STALE_TIME 0 + +/* BGP uptime string length. */ +#define BGP_UPTIME_LEN 25 + +/* Default configuration settings for bgpd. */ +#define BGP_DEFAULT_CONFIG "bgpd.conf" + +/* BGP Dynamic Neighbors feature */ +#define BGP_DYNAMIC_NEIGHBORS_LIMIT_DEFAULT 100 +#define BGP_DYNAMIC_NEIGHBORS_LIMIT_MIN 1 +#define BGP_DYNAMIC_NEIGHBORS_LIMIT_MAX 65535 + +/* BGP AIGP */ +#define BGP_AIGP_TLV_RESERVED 0 /* AIGP Reserved */ +#define BGP_AIGP_TLV_METRIC 1 /* AIGP Metric */ +#define BGP_AIGP_TLV_METRIC_LEN 11 +#define BGP_AIGP_TLV_METRIC_MAX 0xffffffffffffffffULL +#define BGP_AIGP_TLV_METRIC_DESC "Accumulated IGP Metric" + +/* Flag for peer_clear_soft(). */ +enum bgp_clear_type { + BGP_CLEAR_SOFT_NONE, + BGP_CLEAR_SOFT_OUT, + BGP_CLEAR_SOFT_IN, + BGP_CLEAR_SOFT_BOTH, + BGP_CLEAR_SOFT_IN_ORF_PREFIX, + BGP_CLEAR_MESSAGE_STATS, + BGP_CLEAR_CAPABILITIES, +}; + +/* Macros. */ +#define BGP_INPUT(P) ((P)->curr) +#define BGP_INPUT_PNT(P) (stream_pnt(BGP_INPUT(P))) +#define BGP_IS_VALID_STATE_FOR_NOTIF(S) \ + (((S) == OpenSent) || ((S) == OpenConfirm) || ((S) == Established)) + +/* BGP error codes. */ +enum bgp_create_error_code { + BGP_SUCCESS = 0, + BGP_CREATED = 1, + BGP_ERR_INVALID_VALUE = -1, + BGP_ERR_INVALID_FLAG = -2, + BGP_ERR_INVALID_AS = -3, + BGP_ERR_PEER_GROUP_MEMBER = -4, + BGP_ERR_PEER_GROUP_NO_REMOTE_AS = -5, + BGP_ERR_PEER_GROUP_CANT_CHANGE = -6, + BGP_ERR_PEER_GROUP_MISMATCH = -7, + BGP_ERR_PEER_GROUP_PEER_TYPE_DIFFERENT = -8, + BGP_ERR_AS_MISMATCH = -9, + BGP_ERR_PEER_FLAG_CONFLICT = -10, + BGP_ERR_PEER_GROUP_SHUTDOWN = -11, + BGP_ERR_PEER_FILTER_CONFLICT = -12, + BGP_ERR_NOT_INTERNAL_PEER = -13, + BGP_ERR_REMOVE_PRIVATE_AS = -14, + BGP_ERR_AF_UNCONFIGURED = -15, + BGP_ERR_SOFT_RECONFIG_UNCONFIGURED = -16, + BGP_ERR_INSTANCE_MISMATCH = -17, + BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS = -19, + BGP_ERR_TCPSIG_FAILED = -20, + BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK = -21, + BGP_ERR_NO_IBGP_WITH_TTLHACK = -22, + BGP_ERR_NO_INTERFACE_CONFIG = -23, + BGP_ERR_AS_OVERRIDE = -25, + BGP_ERR_INVALID_DYNAMIC_NEIGHBORS_LIMIT = -26, + BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_EXISTS = -27, + BGP_ERR_DYNAMIC_NEIGHBORS_RANGE_NOT_FOUND = -28, + BGP_ERR_INVALID_FOR_DYNAMIC_PEER = -29, + BGP_ERR_INVALID_FOR_DIRECT_PEER = -30, + BGP_ERR_PEER_SAFI_CONFLICT = -31, + + /* BGP GR ERRORS */ + BGP_ERR_GR_INVALID_CMD = -32, + BGP_ERR_GR_OPERATION_FAILED = -33, + BGP_GR_NO_OPERATION = -34, + + /*BGP Open Policy ERRORS */ + BGP_ERR_INVALID_ROLE_NAME = -35, + BGP_ERR_INVALID_INTERNAL_ROLE = -36 +}; + +/* + * Enumeration of different policy kinds a peer can be configured with. + */ +enum bgp_policy_type { + BGP_POLICY_ROUTE_MAP, + BGP_POLICY_FILTER_LIST, + BGP_POLICY_PREFIX_LIST, + BGP_POLICY_DISTRIBUTE_LIST, +}; + +/* peer_flag_change_type. */ +enum peer_change_type { + peer_change_none, + peer_change_reset, + peer_change_reset_in, + peer_change_reset_out, +}; + +/* Enumeration of martian ("self") entry types. + * Routes carrying fields that match a self entry are considered martians + * and should be handled accordingly, i.e. dropped or import-filtered. + * Note: + * These "martians" are separate from routes optionally allowed via + * 'bgp allow-martian-nexthop'. The optionally allowed martians are + * simply prefixes caught by ipv4_martian(), i.e. routes outside + * the non-reserved IPv4 Unicast address space. + */ +enum bgp_martian_type { + BGP_MARTIAN_IF_IP, /* bgp->address_hash */ + BGP_MARTIAN_TUN_IP, /* bgp->tip_hash */ + BGP_MARTIAN_IF_MAC, /* bgp->self_mac_hash */ + BGP_MARTIAN_RMAC, /* bgp->rmac */ + BGP_MARTIAN_SOO, /* bgp->evpn_info->macvrf_soo */ +}; + +extern const struct message bgp_martian_type_str[]; +extern const char *bgp_martian_type2str(enum bgp_martian_type mt); + +extern struct bgp_master *bm; +extern unsigned int multipath_num; + +/* Prototypes. */ +extern void bgp_terminate(void); +extern void bgp_reset(void); +extern void bgp_zclient_reset(void); +extern struct bgp *bgp_get_default(void); +extern struct bgp *bgp_lookup(as_t, const char *); +extern struct bgp *bgp_lookup_by_name(const char *); +extern struct bgp *bgp_lookup_by_vrf_id(vrf_id_t); +extern struct bgp *bgp_get_evpn(void); +extern void bgp_set_evpn(struct bgp *bgp); +extern struct peer *peer_lookup(struct bgp *, union sockunion *); +extern struct peer *peer_lookup_by_conf_if(struct bgp *, const char *); +extern struct peer *peer_lookup_by_hostname(struct bgp *, const char *); +extern void bgp_peer_conf_if_to_su_update(struct peer_connection *connection); +extern int peer_group_listen_range_del(struct peer_group *, struct prefix *); +extern struct peer_group *peer_group_lookup(struct bgp *, const char *); +extern struct peer_group *peer_group_get(struct bgp *, const char *); +extern struct peer *peer_create_bind_dynamic_neighbor(struct bgp *, + union sockunion *, + struct peer_group *); +extern struct prefix * +peer_group_lookup_dynamic_neighbor_range(struct peer_group *, struct prefix *); +extern struct peer_group *peer_group_lookup_dynamic_neighbor(struct bgp *, + struct prefix *, + struct prefix **); +extern struct peer *peer_lookup_dynamic_neighbor(struct bgp *, + union sockunion *); + +/* + * Peers are incredibly easy to memory leak + * due to the various ways that they are actually used + * Provide some functionality to debug locks and unlocks + */ +extern struct peer *peer_lock_with_caller(const char *, struct peer *); +extern struct peer *peer_unlock_with_caller(const char *, struct peer *); +#define peer_unlock(A) peer_unlock_with_caller(__FUNCTION__, (A)) +#define peer_lock(B) peer_lock_with_caller(__FUNCTION__, (B)) + +extern enum bgp_peer_sort peer_sort(struct peer *peer); +extern enum bgp_peer_sort peer_sort_lookup(struct peer *peer); + +extern bool peer_active(struct peer *); +extern bool peer_active_nego(struct peer *); +extern bool peer_afc_received(struct peer *peer); +extern bool peer_afc_advertised(struct peer *peer); +extern void bgp_recalculate_all_bestpaths(struct bgp *bgp); +extern struct peer *peer_create(union sockunion *su, const char *conf_if, + struct bgp *bgp, as_t local_as, as_t remote_as, + int as_type, struct peer_group *group, + bool config_node, const char *as_str); +extern struct peer *peer_create_accept(struct bgp *); +extern void peer_xfer_config(struct peer *dst, struct peer *src); +extern char *peer_uptime(time_t uptime2, char *buf, size_t len, bool use_json, + json_object *json); + +extern int bgp_config_write(struct vty *); + +extern void bgp_master_init(struct event_loop *master, const int buffer_size, + struct list *addresses); + +extern void bgp_init(unsigned short instance); +extern void bgp_pthreads_run(void); +extern void bgp_pthreads_finish(void); +extern void bgp_route_map_init(void); +extern void bgp_session_reset(struct peer *); + +extern int bgp_option_set(int); +extern int bgp_option_unset(int); +extern int bgp_option_check(int); + +/* set the bgp no-rib option during runtime and remove installed routes */ +extern void bgp_option_norib_set_runtime(void); + +/* unset the bgp no-rib option during runtime and reset all peers */ +extern void bgp_option_norib_unset_runtime(void); + +extern int bgp_get(struct bgp **bgp, as_t *as, const char *name, + enum bgp_instance_type kind, const char *as_pretty, + enum asnotation_mode asnotation); +extern void bgp_instance_up(struct bgp *); +extern void bgp_instance_down(struct bgp *); +extern int bgp_delete(struct bgp *); + +extern int bgp_handle_socket(struct bgp *bgp, struct vrf *vrf, + vrf_id_t old_vrf_id, bool create); + +extern void bgp_router_id_zebra_bump(vrf_id_t, const struct prefix *); +extern void bgp_router_id_static_set(struct bgp *, struct in_addr); + +extern void bm_wait_for_fib_set(bool set); +extern void bgp_suppress_fib_pending_set(struct bgp *bgp, bool set); +extern void bgp_cluster_id_set(struct bgp *bgp, struct in_addr *cluster_id); +extern void bgp_cluster_id_unset(struct bgp *bgp); + +extern void bgp_confederation_id_set(struct bgp *bgp, as_t as, + const char *as_str); +extern void bgp_confederation_id_unset(struct bgp *bgp); +extern bool bgp_confederation_peers_check(struct bgp *, as_t); + +extern void bgp_confederation_peers_add(struct bgp *bgp, as_t as, + const char *as_str); +extern void bgp_confederation_peers_remove(struct bgp *bgp, as_t as); + +extern void bgp_timers_set(struct vty *vty, struct bgp *, uint32_t keepalive, + uint32_t holdtime, uint32_t connect_retry, + uint32_t delayopen); +extern void bgp_timers_unset(struct bgp *); + +extern void bgp_default_local_preference_set(struct bgp *bgp, + uint32_t local_pref); +extern void bgp_default_local_preference_unset(struct bgp *bgp); + +extern void bgp_default_subgroup_pkt_queue_max_set(struct bgp *bgp, + uint32_t queue_size); +extern void bgp_default_subgroup_pkt_queue_max_unset(struct bgp *bgp); + +extern void bgp_listen_limit_set(struct bgp *bgp, int listen_limit); +extern void bgp_listen_limit_unset(struct bgp *bgp); + +extern bool bgp_update_delay_active(struct bgp *); +extern bool bgp_update_delay_configured(struct bgp *); +extern bool bgp_afi_safi_peer_exists(struct bgp *bgp, afi_t afi, safi_t safi); +extern void peer_as_change(struct peer *peer, as_t as, int as_type, + const char *as_str); +extern int peer_remote_as(struct bgp *bgp, union sockunion *su, + const char *conf_if, as_t *as, int as_type, + const char *as_str); +extern int peer_group_remote_as(struct bgp *bgp, const char *peer_str, as_t *as, + int as_type, const char *as_str); +extern int peer_delete(struct peer *peer); +extern void peer_notify_unconfig(struct peer *peer); +extern int peer_group_delete(struct peer_group *); +extern int peer_group_remote_as_delete(struct peer_group *); +extern int peer_group_listen_range_add(struct peer_group *, struct prefix *); +extern void peer_group_notify_unconfig(struct peer_group *group); + +extern int peer_activate(struct peer *, afi_t, safi_t); +extern int peer_deactivate(struct peer *, afi_t, safi_t); + +extern int peer_group_bind(struct bgp *, union sockunion *, struct peer *, + struct peer_group *, as_t *); + +extern int peer_flag_set(struct peer *peer, uint64_t flag); +extern int peer_flag_unset(struct peer *peer, uint64_t flag); +extern void peer_flag_inherit(struct peer *peer, uint64_t flag); + +extern int peer_af_flag_set(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag); +extern int peer_af_flag_unset(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag); +extern bool peer_af_flag_check(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag); +extern void peer_af_flag_inherit(struct peer *peer, afi_t afi, safi_t safi, + uint64_t flag); +extern void peer_change_action(struct peer *peer, afi_t afi, safi_t safi, + enum peer_change_type type); + +extern int peer_ebgp_multihop_set(struct peer *, int); +extern int peer_ebgp_multihop_unset(struct peer *); +extern int is_ebgp_multihop_configured(struct peer *peer); + +extern int peer_role_set(struct peer *peer, uint8_t role, bool strict_mode); +extern int peer_role_unset(struct peer *peer); + +extern void peer_description_set(struct peer *, const char *); +extern void peer_description_unset(struct peer *); + +extern int peer_update_source_if_set(struct peer *, const char *); +extern void peer_update_source_addr_set(struct peer *peer, + const union sockunion *su); +extern void peer_update_source_unset(struct peer *peer); + +extern int peer_default_originate_set(struct peer *peer, afi_t afi, safi_t safi, + const char *rmap, + struct route_map *route_map); +extern int peer_default_originate_unset(struct peer *, afi_t, safi_t); +extern void bgp_tcp_keepalive_set(struct bgp *bgp, uint16_t idle, + uint16_t interval, uint16_t probes); +extern void bgp_tcp_keepalive_unset(struct bgp *bgp); + +extern void peer_port_set(struct peer *, uint16_t); +extern void peer_port_unset(struct peer *); + +extern int peer_weight_set(struct peer *, afi_t, safi_t, uint16_t); +extern int peer_weight_unset(struct peer *, afi_t, safi_t); + +extern int peer_timers_set(struct peer *, uint32_t keepalive, + uint32_t holdtime); +extern int peer_timers_unset(struct peer *); + +extern int peer_timers_connect_set(struct peer *, uint32_t); +extern int peer_timers_connect_unset(struct peer *); + +extern int peer_advertise_interval_set(struct peer *, uint32_t); +extern int peer_advertise_interval_unset(struct peer *); + +extern int peer_timers_delayopen_set(struct peer *peer, uint32_t delayopen); +extern int peer_timers_delayopen_unset(struct peer *peer); + +extern void peer_interface_set(struct peer *, const char *); +extern void peer_interface_unset(struct peer *); + +extern int peer_distribute_set(struct peer *, afi_t, safi_t, int, const char *); +extern int peer_distribute_unset(struct peer *, afi_t, safi_t, int); + +extern int peer_allowas_in_set(struct peer *, afi_t, safi_t, int, int); +extern int peer_allowas_in_unset(struct peer *, afi_t, safi_t); + +extern int peer_local_as_set(struct peer *peer, as_t as, bool no_prepend, + bool replace_as, const char *as_str); +extern int peer_local_as_unset(struct peer *); + +extern int peer_prefix_list_set(struct peer *, afi_t, safi_t, int, + const char *); +extern int peer_prefix_list_unset(struct peer *, afi_t, safi_t, int); + +extern int peer_aslist_set(struct peer *, afi_t, safi_t, int, const char *); +extern int peer_aslist_unset(struct peer *, afi_t, safi_t, int); + +extern int peer_route_map_set(struct peer *peer, afi_t afi, safi_t safi, int, + const char *name, struct route_map *route_map); +extern int peer_route_map_unset(struct peer *, afi_t, safi_t, int); + +extern int peer_unsuppress_map_set(struct peer *peer, afi_t afi, safi_t safi, + const char *name, + struct route_map *route_map); + +extern int peer_advertise_map_set(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, + bool condition); + +extern int peer_password_set(struct peer *, const char *); +extern int peer_password_unset(struct peer *); + +extern int peer_unsuppress_map_unset(struct peer *, afi_t, safi_t); + +extern int peer_advertise_map_unset(struct peer *peer, afi_t afi, safi_t safi, + const char *advertise_name, + struct route_map *advertise_map, + const char *condition_name, + struct route_map *condition_map, + bool condition); + +extern int peer_maximum_prefix_set(struct peer *, afi_t, safi_t, uint32_t, + uint8_t, int, uint16_t, bool force); +extern int peer_maximum_prefix_unset(struct peer *, afi_t, safi_t); + +extern void peer_maximum_prefix_out_refresh_routes(struct peer *peer, afi_t afi, + safi_t safi); +extern int peer_maximum_prefix_out_set(struct peer *peer, afi_t afi, + safi_t safi, uint32_t max); +extern int peer_maximum_prefix_out_unset(struct peer *peer, afi_t afi, + safi_t safi); + +extern int peer_clear(struct peer *, struct listnode **); +extern int peer_clear_soft(struct peer *, afi_t, safi_t, enum bgp_clear_type); + +extern int peer_ttl_security_hops_set(struct peer *, int); +extern int peer_ttl_security_hops_unset(struct peer *); + +extern void peer_tx_shutdown_message_set(struct peer *, const char *msg); +extern void peer_tx_shutdown_message_unset(struct peer *); + +extern void bgp_route_map_update_timer(struct event *thread); +extern const char *bgp_get_name_by_role(uint8_t role); +extern enum asnotation_mode bgp_get_asnotation(struct bgp *bgp); + +extern void bgp_route_map_terminate(void); + +extern bool bgp_route_map_has_extcommunity_rt(const struct route_map *map); + +extern int peer_cmp(struct peer *p1, struct peer *p2); + +extern int bgp_map_afi_safi_iana2int(iana_afi_t pkt_afi, iana_safi_t pkt_safi, + afi_t *afi, safi_t *safi); +extern int bgp_map_afi_safi_int2iana(afi_t afi, safi_t safi, + iana_afi_t *pkt_afi, + iana_safi_t *pkt_safi); + +extern struct peer_af *peer_af_create(struct peer *, afi_t, safi_t); +extern struct peer_af *peer_af_find(struct peer *, afi_t, safi_t); +extern int peer_af_delete(struct peer *, afi_t, safi_t); + +extern void bgp_shutdown_enable(struct bgp *bgp, const char *msg); +extern void bgp_shutdown_disable(struct bgp *bgp); + +extern void bgp_close(void); +extern void bgp_free(struct bgp *); +void bgp_gr_apply_running_config(void); + +/* BGP GR */ +int bgp_global_gr_init(struct bgp *bgp); +int bgp_peer_gr_init(struct peer *peer); + + +#define BGP_GR_ROUTER_DETECT_AND_SEND_CAPABILITY_TO_ZEBRA(_bgp, _peer_list) \ + do { \ + struct peer *peer_loop; \ + bool gr_router_detected = false; \ + struct listnode *node = {0}; \ + for (ALL_LIST_ELEMENTS_RO(_peer_list, node, peer_loop)) { \ + if (CHECK_FLAG(peer_loop->flags, \ + PEER_FLAG_GRACEFUL_RESTART)) \ + gr_router_detected = true; \ + } \ + if (gr_router_detected \ + && _bgp->present_zebra_gr_state == ZEBRA_GR_DISABLE) { \ + bgp_zebra_send_capabilities(_bgp, false); \ + } else if (!gr_router_detected \ + && _bgp->present_zebra_gr_state \ + == ZEBRA_GR_ENABLE) { \ + bgp_zebra_send_capabilities(_bgp, true); \ + } \ + } while (0) + +static inline struct bgp *bgp_lock(struct bgp *bgp) +{ + bgp->lock++; + return bgp; +} + +static inline void bgp_unlock(struct bgp *bgp) +{ + assert(bgp->lock > 0); + if (--bgp->lock == 0) + bgp_free(bgp); +} + +static inline int afindex(afi_t afi, safi_t safi) +{ + switch (afi) { + case AFI_IP: + switch (safi) { + case SAFI_UNICAST: + return BGP_AF_IPV4_UNICAST; + case SAFI_MULTICAST: + return BGP_AF_IPV4_MULTICAST; + case SAFI_LABELED_UNICAST: + return BGP_AF_IPV4_LBL_UNICAST; + case SAFI_MPLS_VPN: + return BGP_AF_IPV4_VPN; + case SAFI_ENCAP: + return BGP_AF_IPV4_ENCAP; + case SAFI_FLOWSPEC: + return BGP_AF_IPV4_FLOWSPEC; + case SAFI_EVPN: + case SAFI_UNSPEC: + case SAFI_MAX: + return BGP_AF_MAX; + } + break; + case AFI_IP6: + switch (safi) { + case SAFI_UNICAST: + return BGP_AF_IPV6_UNICAST; + case SAFI_MULTICAST: + return BGP_AF_IPV6_MULTICAST; + case SAFI_LABELED_UNICAST: + return BGP_AF_IPV6_LBL_UNICAST; + case SAFI_MPLS_VPN: + return BGP_AF_IPV6_VPN; + case SAFI_ENCAP: + return BGP_AF_IPV6_ENCAP; + case SAFI_FLOWSPEC: + return BGP_AF_IPV6_FLOWSPEC; + case SAFI_EVPN: + case SAFI_UNSPEC: + case SAFI_MAX: + return BGP_AF_MAX; + } + break; + case AFI_L2VPN: + switch (safi) { + case SAFI_EVPN: + return BGP_AF_L2VPN_EVPN; + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_MAX: + return BGP_AF_MAX; + } + break; + case AFI_UNSPEC: + case AFI_MAX: + return BGP_AF_MAX; + } + + assert(!"Reached end of function we should never hit"); +} + +/* If the peer is not a peer-group but is bound to a peer-group return 1 */ +static inline int peer_group_active(struct peer *peer) +{ + if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP) && peer->group) + return 1; + return 0; +} + +/* If peer is negotiated at least one address family return 1. */ +static inline int peer_afi_active_nego(const struct peer *peer, afi_t afi) +{ + if (peer->afc_nego[afi][SAFI_UNICAST] + || peer->afc_nego[afi][SAFI_MULTICAST] + || peer->afc_nego[afi][SAFI_LABELED_UNICAST] + || peer->afc_nego[afi][SAFI_MPLS_VPN] + || peer->afc_nego[afi][SAFI_ENCAP] + || peer->afc_nego[afi][SAFI_FLOWSPEC] + || peer->afc_nego[afi][SAFI_EVPN]) + return 1; + return 0; +} + +/* If at least one address family activated for group, return 1. */ +static inline int peer_group_af_configured(struct peer_group *group) +{ + struct peer *peer = group->conf; + + if (peer->afc[AFI_IP][SAFI_UNICAST] || peer->afc[AFI_IP][SAFI_MULTICAST] + || peer->afc[AFI_IP][SAFI_LABELED_UNICAST] + || peer->afc[AFI_IP][SAFI_FLOWSPEC] + || peer->afc[AFI_IP][SAFI_MPLS_VPN] || peer->afc[AFI_IP][SAFI_ENCAP] + || peer->afc[AFI_IP6][SAFI_UNICAST] + || peer->afc[AFI_IP6][SAFI_MULTICAST] + || peer->afc[AFI_IP6][SAFI_LABELED_UNICAST] + || peer->afc[AFI_IP6][SAFI_MPLS_VPN] + || peer->afc[AFI_IP6][SAFI_ENCAP] + || peer->afc[AFI_IP6][SAFI_FLOWSPEC] + || peer->afc[AFI_L2VPN][SAFI_EVPN]) + return 1; + return 0; +} + +static inline char *timestamp_string(time_t ts, char *timebuf) +{ + time_t tbuf; + + tbuf = time(NULL) - (monotime(NULL) - ts); + return ctime_r(&tbuf, timebuf); +} + +static inline bool peer_established(struct peer_connection *connection) +{ + return connection->status == Established; +} + +static inline bool peer_dynamic_neighbor(struct peer *peer) +{ + return CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_NEIGHBOR); +} + +static inline bool peer_dynamic_neighbor_no_nsf(struct peer *peer) +{ + return (peer_dynamic_neighbor(peer) && + !CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_WAIT)); +} + +static inline int peer_cap_enhe(struct peer *peer, afi_t afi, safi_t safi) +{ + return (CHECK_FLAG(peer->af_cap[afi][safi], PEER_CAP_ENHE_AF_NEGO)); +} + +/* Lookup VRF for BGP instance based on its type. */ +static inline struct vrf *bgp_vrf_lookup_by_instance_type(struct bgp *bgp) +{ + struct vrf *vrf; + + if (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) + vrf = vrf_lookup_by_id(VRF_DEFAULT); + else if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + vrf = vrf_lookup_by_name(bgp->name); + else + vrf = NULL; + + return vrf; +} + +static inline uint32_t bgp_vrf_interfaces(struct bgp *bgp, bool active) +{ + struct vrf *vrf; + struct interface *ifp; + uint32_t count = 0; + + /* if there is one interface in the vrf which is up then it is deemed + * active + */ + vrf = bgp_vrf_lookup_by_instance_type(bgp); + if (vrf == NULL) + return 0; + RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) { + if (strcmp(ifp->name, bgp->name) == 0) + continue; + if (!active || if_is_up(ifp)) + count++; + } + return count; +} + +/* Link BGP instance to VRF. */ +static inline void bgp_vrf_link(struct bgp *bgp, struct vrf *vrf) +{ + bgp->vrf_id = vrf->vrf_id; + if (vrf->info != (void *)bgp) + vrf->info = (void *)bgp_lock(bgp); +} + +/* Unlink BGP instance from VRF. */ +static inline void bgp_vrf_unlink(struct bgp *bgp, struct vrf *vrf) +{ + if (vrf->info == (void *)bgp) { + vrf->info = NULL; + bgp_unlock(bgp); + } + bgp->vrf_id = VRF_UNKNOWN; +} + +static inline bool bgp_in_graceful_shutdown(struct bgp *bgp) +{ + /* True if either set for this instance or globally */ + return (!!CHECK_FLAG(bgp->flags, BGP_FLAG_GRACEFUL_SHUTDOWN) || + !!CHECK_FLAG(bm->flags, BM_FLAG_GRACEFUL_SHUTDOWN)); +} + +/* For benefit of rfapi */ +extern struct peer *peer_new(struct bgp *bgp); + +extern struct peer *peer_lookup_in_view(struct vty *vty, struct bgp *bgp, + const char *ip_str, bool use_json); +extern int bgp_lookup_by_as_name_type(struct bgp **bgp_val, as_t *as, + const char *name, + enum bgp_instance_type inst_type); + +/* Hooks */ +DECLARE_HOOK(bgp_vrf_status_changed, (struct bgp *bgp, struct interface *ifp), + (bgp, ifp)); +DECLARE_HOOK(peer_status_changed, (struct peer *peer), (peer)); +DECLARE_HOOK(bgp_snmp_init_stats, (struct bgp *bgp), (bgp)); +DECLARE_HOOK(bgp_snmp_update_last_changed, (struct bgp *bgp), (bgp)); +DECLARE_HOOK(bgp_snmp_update_stats, + (struct bgp_dest *rn, struct bgp_path_info *pi, bool added), + (rn, pi, added)); +DECLARE_HOOK(bgp_rpki_prefix_status, + (struct peer * peer, struct attr *attr, + const struct prefix *prefix), + (peer, attr, prefix)); + +void peer_nsf_stop(struct peer *peer); + +void peer_tcp_mss_set(struct peer *peer, uint32_t tcp_mss); +void peer_tcp_mss_unset(struct peer *peer); + +extern void bgp_recalculate_afi_safi_bestpaths(struct bgp *bgp, afi_t afi, + safi_t safi); +extern void peer_on_policy_change(struct peer *peer, afi_t afi, safi_t safi, + int outbound); +extern bool bgp_path_attribute_discard(struct peer *peer, char *buf, + size_t size); +extern bool bgp_path_attribute_treat_as_withdraw(struct peer *peer, char *buf, + size_t size); + +extern void srv6_function_free(struct bgp_srv6_function *func); + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +/* clang-format off */ +#pragma FRR printfrr_ext "%pBP" (struct peer *) +/* clang-format on */ +#endif + +#endif /* _QUAGGA_BGPD_H */ diff --git a/bgpd/rfapi/bgp_rfapi_cfg.c b/bgpd/rfapi/bgp_rfapi_cfg.c new file mode 100644 index 0000000..a452ebe --- /dev/null +++ b/bgpd/rfapi/bgp_rfapi_cfg.c @@ -0,0 +1,4615 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ +#include "lib/zebra.h" + +#include "lib/command.h" +#include "lib/prefix.h" +#include "lib/memory.h" +#include "lib/linklist.h" +#include "lib/agg_table.h" +#include "lib/plist.h" +#include "lib/routemap.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" + +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/vnc_zebra.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp_p.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/vnc_debug.h" + +#ifdef ENABLE_BGP_VNC + +#undef BGP_VNC_DEBUG_MATCH_GROUP + + +DEFINE_MGROUP(RFAPI, "rfapi"); +DEFINE_MTYPE(RFAPI, RFAPI_CFG, "NVE Configuration"); +DEFINE_MTYPE(RFAPI, RFAPI_GROUP_CFG, "NVE Group Configuration"); +DEFINE_MTYPE(RFAPI, RFAPI_L2_CFG, "RFAPI L2 Group Configuration"); +DEFINE_MTYPE(RFAPI, RFAPI_RFP_GROUP_CFG, "RFAPI RFP Group Configuration"); +DEFINE_MTYPE(RFAPI, RFAPI, "RFAPI Generic"); +DEFINE_MTYPE(RFAPI, RFAPI_DESC, "RFAPI Descriptor"); +DEFINE_MTYPE(RFAPI, RFAPI_IMPORTTABLE, "RFAPI Import Table"); +DEFINE_MTYPE(RFAPI, RFAPI_MONITOR, "RFAPI Monitor VPN"); +DEFINE_MTYPE(RFAPI, RFAPI_MONITOR_ENCAP, "RFAPI Monitor Encap"); +DEFINE_MTYPE(RFAPI, RFAPI_NEXTHOP, "RFAPI Next Hop"); +DEFINE_MTYPE(RFAPI, RFAPI_VN_OPTION, "RFAPI VN Option"); +DEFINE_MTYPE(RFAPI, RFAPI_UN_OPTION, "RFAPI UN Option"); +DEFINE_MTYPE(RFAPI, RFAPI_WITHDRAW, "RFAPI Withdraw"); +DEFINE_MTYPE(RFAPI, RFAPI_RFG_NAME, "RFAPI RFGName"); +DEFINE_MTYPE(RFAPI, RFAPI_ADB, "RFAPI Advertisement Data"); +DEFINE_MTYPE(RFAPI, RFAPI_ETI, "RFAPI Export Table Info"); +DEFINE_MTYPE(RFAPI, RFAPI_NVE_ADDR, "RFAPI NVE Address"); +DEFINE_MTYPE(RFAPI, RFAPI_PREFIX_BAG, "RFAPI Prefix Bag"); +DEFINE_MTYPE(RFAPI, RFAPI_IT_EXTRA, "RFAPI IT Extra"); +DEFINE_MTYPE(RFAPI, RFAPI_INFO, "RFAPI Info"); +DEFINE_MTYPE(RFAPI, RFAPI_ADDR, "RFAPI Addr"); +DEFINE_MTYPE(RFAPI, RFAPI_UPDATED_RESPONSE_QUEUE, "RFAPI Updated Rsp Queue"); +DEFINE_MTYPE(RFAPI, RFAPI_RECENT_DELETE, "RFAPI Recently Deleted Route"); +DEFINE_MTYPE(RFAPI, RFAPI_L2ADDR_OPT, "RFAPI L2 Address Option"); +DEFINE_MTYPE(RFAPI, RFAPI_AP, "RFAPI Advertised Prefix"); +DEFINE_MTYPE(RFAPI, RFAPI_MONITOR_ETH, "RFAPI Monitor Ethernet"); + +DEFINE_QOBJ_TYPE(rfapi_nve_group_cfg); +DEFINE_QOBJ_TYPE(rfapi_l2_group_cfg); +/*********************************************************************** + * RFAPI Support + ***********************************************************************/ + + +/* + * compaitibility to old quagga_time call + * time_t value in terms of stabilised absolute time. + * replacement for POSIX time() + */ +time_t rfapi_time(time_t *t) +{ + time_t clock = monotime(NULL); + if (t) + *t = clock; + return clock; +} + +void nve_group_to_nve_list(struct rfapi_nve_group_cfg *rfg, struct list **nves, + uint8_t family) /* AF_INET, AF_INET6 */ +{ + struct listnode *hln; + struct rfapi_descriptor *rfd; + + /* + * loop over nves in this grp, add to list + */ + for (ALL_LIST_ELEMENTS_RO(rfg->nves, hln, rfd)) { + if (rfd->vn_addr.addr_family == family) { + if (!*nves) + *nves = list_new(); + listnode_add(*nves, rfd); + } + } +} + + +struct rfapi_nve_group_cfg *bgp_rfapi_cfg_match_group(struct rfapi_cfg *hc, + struct prefix *vn, + struct prefix *un) +{ + struct rfapi_nve_group_cfg *rfg_vn = NULL; + struct rfapi_nve_group_cfg *rfg_un = NULL; + + struct agg_table *rt_vn; + struct agg_table *rt_un; + struct agg_node *rn_vn; + struct agg_node *rn_un; + + struct rfapi_nve_group_cfg *rfg; + struct listnode *node, *nnode; + + switch (vn->family) { + case AF_INET: + rt_vn = hc->nve_groups_vn[AFI_IP]; + break; + case AF_INET6: + rt_vn = hc->nve_groups_vn[AFI_IP6]; + break; + default: + return NULL; + } + + switch (un->family) { + case AF_INET: + rt_un = hc->nve_groups_un[AFI_IP]; + break; + case AF_INET6: + rt_un = hc->nve_groups_un[AFI_IP6]; + break; + default: + return NULL; + } + + rn_vn = agg_node_match(rt_vn, vn); /* NB locks node */ + if (rn_vn) { + rfg_vn = rn_vn->info; + agg_unlock_node(rn_vn); + } + + rn_un = agg_node_match(rt_un, un); /* NB locks node */ + if (rn_un) { + rfg_un = rn_un->info; + agg_unlock_node(rn_un); + } + +#ifdef BGP_VNC_DEBUG_MATCH_GROUP + { + vnc_zlog_debug_verbose("%s: vn prefix: %pFX", __func__, vn); + vnc_zlog_debug_verbose("%s: un prefix: %pFX", __func__, un); + vnc_zlog_debug_verbose( + "%s: rn_vn=%p, rn_un=%p, rfg_vn=%p, rfg_un=%p", + __func__, rn_vn, rn_un, rfg_vn, rfg_un); + } +#endif + + + if (rfg_un == rfg_vn) /* same group */ + return rfg_un; + if (!rfg_un) /* un doesn't match, return vn-matched grp */ + return rfg_vn; + if (!rfg_vn) /* vn doesn't match, return un-matched grp */ + return rfg_un; + + /* + * Two different nve groups match: the group configured earlier wins. + * For now, just walk the sequential list and pick the first one. + * If this approach is too slow, then store serial numbers in the + * nve group structures as they are defined and just compare + * serial numbers. + */ + for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, rfg)) { + if ((rfg == rfg_un) || (rfg == rfg_vn)) { + return rfg; + } + } + vnc_zlog_debug_verbose( + "%s: shouldn't happen, returning NULL when un and vn match", + __func__); + return NULL; /* shouldn't happen */ +} + +/*------------------------------------------ + * rfapi_get_rfp_start_val + * + * Returns value passed to rfapi on rfp_start + * + * input: + * void * bgp structure + * + * returns: + * void * + *------------------------------------------*/ +void *rfapi_get_rfp_start_val(void *bgpv) +{ + struct bgp *bgp = bgpv; + if (bgp == NULL || bgp->rfapi == NULL) + return NULL; + return bgp->rfapi->rfp; +} + +/*------------------------------------------ + * bgp_rfapi_is_vnc_configured + * + * Returns if VNC is configured + * + * input: + * bgp NULL (=use default instance) + * + * output: + * + * return value: If VNC is configured for the bgpd instance + * 0 Success + * EPERM Not Default instance (VNC operations not allowed) + * ENXIO VNC not configured + --------------------------------------------*/ +int bgp_rfapi_is_vnc_configured(struct bgp *bgp) +{ + if (bgp == NULL) + bgp = bgp_get_default(); + + if (bgp && bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) + return EPERM; + + if (bgp && bgp->rfapi_cfg) + return 0; + return ENXIO; +} + +/*********************************************************************** + * VNC Configuration/CLI + ***********************************************************************/ +#define VNC_VTY_CONFIG_CHECK(bgp) \ + { \ + switch (bgp_rfapi_is_vnc_configured(bgp)) { \ + case EPERM: \ + vty_out(vty, \ + "VNC operations only permitted on default BGP instance.\n"); \ + return CMD_WARNING_CONFIG_FAILED; \ + break; \ + case ENXIO: \ + vty_out(vty, "VNC not configured.\n"); \ + return CMD_WARNING_CONFIG_FAILED; \ + break; \ + default: \ + break; \ + } \ + } + +DEFUN (vnc_advertise_un_method, + vnc_advertise_un_method_cmd, + "vnc advertise-un-method encap-attr", + VNC_CONFIG_STR + "Method of advertising UN addresses\n" + "Via Tunnel Encap attribute (in VPN SAFI)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VNC_VTY_CONFIG_CHECK(bgp); + + if (!strncmp(argv[2]->arg, "encap-safi", 7)) { + bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP; + } else { + bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP; + } + + return CMD_SUCCESS; +} + +/*------------------------------------------------------------------------- + * RFG defaults + *-----------------------------------------------------------------------*/ + + +DEFUN_NOSH (vnc_defaults, + vnc_defaults_cmd, + "vnc defaults", VNC_CONFIG_STR "Configure default NVE group\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VNC_VTY_CONFIG_CHECK(bgp); + if (bgp->inst_type != BGP_INSTANCE_TYPE_DEFAULT) { + vty_out(vty, "Malformed community-list value\n"); + return CMD_WARNING_CONFIG_FAILED; + } + vty->node = BGP_VNC_DEFAULTS_NODE; + return CMD_SUCCESS; +} + +static int set_ecom_list(struct vty *vty, int argc, struct cmd_token **argv, + struct ecommunity **list) +{ + struct ecommunity *ecom = NULL; + struct ecommunity *ecomadd; + + for (; argc; --argc, ++argv) { + + ecomadd = ecommunity_str2com(argv[0]->arg, + ECOMMUNITY_ROUTE_TARGET, 0); + if (!ecomadd) { + vty_out(vty, "Malformed community-list value\n"); + if (ecom) + ecommunity_free(&ecom); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ecom) { + ecommunity_merge(ecom, ecomadd); + ecommunity_free(&ecomadd); + } else { + ecom = ecomadd; + } + } + + if (*list) { + ecommunity_free(&*list); + } + *list = ecom; + + return CMD_SUCCESS; +} + +DEFUN (vnc_defaults_rt_import, + vnc_defaults_rt_import_cmd, + "rt import RTLIST...", + "Specify default route targets\n" + "Import filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + return set_ecom_list(vty, argc - 2, argv + 2, + &bgp->rfapi_cfg->default_rt_import_list); +} + +DEFUN (vnc_defaults_rt_export, + vnc_defaults_rt_export_cmd, + "rt export RTLIST...", + "Configure default route targets\n" + "Export filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + return set_ecom_list(vty, argc - 2, argv + 2, + &bgp->rfapi_cfg->default_rt_export_list); +} + +DEFUN (vnc_defaults_rt_both, + vnc_defaults_rt_both_cmd, + "rt both RTLIST...", + "Configure default route targets\n" + "Export+import filters\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int rc; + + rc = set_ecom_list(vty, argc - 2, argv + 2, + &bgp->rfapi_cfg->default_rt_import_list); + if (rc != CMD_SUCCESS) + return rc; + return set_ecom_list(vty, argc - 2, argv + 2, + &bgp->rfapi_cfg->default_rt_export_list); +} + +DEFUN (vnc_defaults_rd, + vnc_defaults_rd_cmd, + "rd ASN:NN_OR_IP-ADDRESS:NN", + "Specify default route distinguisher\n" + "Route Distinguisher (: | : | auto:vn: )\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix_rd prd; + + if (!strncmp(argv[1]->arg, "auto:vn:", 8)) { + /* + * use AF_UNIX to designate automatically-assigned RD + * auto:vn:nn where nn is a 2-octet quantity + */ + char *end = NULL; + uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); + uint16_t value = value32 & 0xffff; + + if (!argv[1]->arg[8] || *end) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (value32 > 0xffff) { + vty_out(vty, "%% Malformed rd (must be less than %u\n", + 0x0ffff); + return CMD_WARNING_CONFIG_FAILED; + } + + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNIX; + prd.prefixlen = 64; + prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; + prd.val[1] = RD_TYPE_IP & 0x0ff; + prd.val[6] = (value >> 8) & 0x0ff; + prd.val[7] = value & 0x0ff; + + } else { + + /* TODO: save RD format */ + ret = str2prefix_rd(argv[1]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + bgp->rfapi_cfg->default_rd = prd; + return CMD_SUCCESS; +} + +DEFUN (vnc_defaults_l2rd, + vnc_defaults_l2rd_cmd, + "l2rd <(1-255)|auto-vn>", + "Specify default Local Nve ID value to use in RD for L2 routes\n" + "Fixed value 1-255\n" + "use the low-order octet of the NVE's VN address\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint8_t value = 0; + + if (strmatch(argv[1]->text, "auto-vn")) { + value = 0; + } else { + char *end = NULL; + unsigned long value_l = strtoul(argv[1]->arg, &end, 10); + + value = value_l & 0xff; + if (!argv[1]->arg[0] || *end) { + vty_out(vty, "%% Malformed l2 nve ID \"%s\"\n", + argv[1]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if ((value_l < 1) || (value_l > 0xff)) { + vty_out(vty, + "%% Malformed l2 nve id (must be greater than 0 and less than %u\n", + 0x100); + return CMD_WARNING_CONFIG_FAILED; + } + } + bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_L2RD; + bgp->rfapi_cfg->default_l2rd = value; + + return CMD_SUCCESS; +} + +DEFUN (vnc_defaults_no_l2rd, + vnc_defaults_no_l2rd_cmd, + "no l2rd", + NO_STR + "Specify default Local Nve ID value to use in RD for L2 routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + bgp->rfapi_cfg->default_l2rd = 0; + bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_L2RD; + + return CMD_SUCCESS; +} + +DEFUN (vnc_defaults_responselifetime, + vnc_defaults_responselifetime_cmd, + "response-lifetime ", + "Specify default response lifetime\n" + "Response lifetime in seconds\n" "Infinite response lifetime\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint32_t rspint; + struct rfapi *h = NULL; + struct listnode *hdnode; + struct rfapi_descriptor *rfd; + + h = bgp->rfapi; + if (!h) + return CMD_WARNING_CONFIG_FAILED; + + if (strmatch(argv[1]->text, "infinite")) { + rspint = RFAPI_INFINITE_LIFETIME; + } else { + rspint = strtoul(argv[1]->arg, NULL, 10); + if (rspint > INT32_MAX) + rspint = INT32_MAX; /* is really an int, not an unsigned + int */ + } + + bgp->rfapi_cfg->default_response_lifetime = rspint; + + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, hdnode, rfd)) + if (rfd->rfg + && !(rfd->rfg->flags & RFAPI_RFG_RESPONSE_LIFETIME)) + rfd->response_lifetime = rfd->rfg->response_lifetime = + rspint; + + return CMD_SUCCESS; +} + +struct rfapi_nve_group_cfg * +bgp_rfapi_cfg_match_byname(struct bgp *bgp, const char *name, + rfapi_group_cfg_type_t type) /* _MAX = any */ +{ + struct rfapi_nve_group_cfg *rfg; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->nve_groups_sequential, node, + nnode, rfg)) { + if ((type == RFAPI_GROUP_CFG_MAX || type == rfg->type) + && !strcmp(rfg->name, name)) + return rfg; + } + return NULL; +} + +static struct rfapi_nve_group_cfg * +rfapi_group_new(struct bgp *bgp, rfapi_group_cfg_type_t type, const char *name) +{ + struct rfapi_nve_group_cfg *rfg; + + rfg = XCALLOC(MTYPE_RFAPI_GROUP_CFG, + sizeof(struct rfapi_nve_group_cfg)); + rfg->type = type; + rfg->name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, name); + /* add to tail of list */ + listnode_add(bgp->rfapi_cfg->nve_groups_sequential, rfg); + rfg->label = MPLS_LABEL_NONE; + + QOBJ_REG(rfg, rfapi_nve_group_cfg); + + return rfg; +} + +static struct rfapi_l2_group_cfg *rfapi_l2_group_lookup_byname(struct bgp *bgp, + const char *name) +{ + struct rfapi_l2_group_cfg *rfg; + struct listnode *node, *nnode; + + if (bgp->rfapi_cfg->l2_groups == NULL) /* not the best place for this */ + bgp->rfapi_cfg->l2_groups = list_new(); + + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->l2_groups, node, nnode, rfg)) { + if (!strcmp(rfg->name, name)) + return rfg; + } + return NULL; +} + +static struct rfapi_l2_group_cfg *rfapi_l2_group_new(void) +{ + struct rfapi_l2_group_cfg *rfg; + + rfg = XCALLOC(MTYPE_RFAPI_L2_CFG, sizeof(struct rfapi_l2_group_cfg)); + QOBJ_REG(rfg, rfapi_l2_group_cfg); + + return rfg; +} + +static void rfapi_l2_group_del(struct rfapi_l2_group_cfg *rfg) +{ + QOBJ_UNREG(rfg); + XFREE(MTYPE_RFAPI_L2_CFG, rfg); +} + +static int rfapi_str2route_type(const char *l3str, const char *pstr, afi_t *afi, + int *type) +{ + if (!l3str || !pstr) + return EINVAL; + + if (!strcmp(l3str, "ipv4")) { + *afi = AFI_IP; + } else { + if (!strcmp(l3str, "ipv6")) + *afi = AFI_IP6; + else + return ENOENT; + } + + if (!strcmp(pstr, "connected")) + *type = ZEBRA_ROUTE_CONNECT; + if (!strcmp(pstr, "kernel")) + *type = ZEBRA_ROUTE_KERNEL; + if (!strcmp(pstr, "static")) + *type = ZEBRA_ROUTE_STATIC; + if (!strcmp(pstr, "bgp")) + *type = ZEBRA_ROUTE_BGP; + if (!strcmp(pstr, "bgp-direct")) + *type = ZEBRA_ROUTE_BGP_DIRECT; + if (!strcmp(pstr, "bgp-direct-to-nve-groups")) + *type = ZEBRA_ROUTE_BGP_DIRECT_EXT; + + if (!strcmp(pstr, "rip")) { + if (*afi == AFI_IP) + *type = ZEBRA_ROUTE_RIP; + else + *type = ZEBRA_ROUTE_RIPNG; + } + + if (!strcmp(pstr, "ripng")) { + if (*afi == AFI_IP) + return EAFNOSUPPORT; + *type = ZEBRA_ROUTE_RIPNG; + } + + if (!strcmp(pstr, "ospf")) { + if (*afi == AFI_IP) + *type = ZEBRA_ROUTE_OSPF; + else + *type = ZEBRA_ROUTE_OSPF6; + } + + if (!strcmp(pstr, "ospf6")) { + if (*afi == AFI_IP) + return EAFNOSUPPORT; + *type = ZEBRA_ROUTE_OSPF6; + } + + return 0; +} + +/*------------------------------------------------------------------------- + * redistribute + *-----------------------------------------------------------------------*/ + +#define VNC_REDIST_ENABLE(bgp, afi, type) \ + do { \ + switch (type) { \ + case ZEBRA_ROUTE_BGP_DIRECT: \ + vnc_import_bgp_redist_enable((bgp), (afi)); \ + break; \ + case ZEBRA_ROUTE_BGP_DIRECT_EXT: \ + vnc_import_bgp_exterior_redist_enable((bgp), (afi)); \ + break; \ + default: \ + if ((type) < ZEBRA_ROUTE_MAX) \ + vnc_redistribute_set((bgp), (afi), (type)); \ + break; \ + } \ + } while (0) + +#define VNC_REDIST_DISABLE(bgp, afi, type) \ + do { \ + switch (type) { \ + case ZEBRA_ROUTE_BGP_DIRECT: \ + vnc_import_bgp_redist_disable((bgp), (afi)); \ + break; \ + case ZEBRA_ROUTE_BGP_DIRECT_EXT: \ + vnc_import_bgp_exterior_redist_disable((bgp), (afi)); \ + break; \ + default: \ + if ((type) < ZEBRA_ROUTE_MAX) \ + vnc_redistribute_unset((bgp), (afi), (type)); \ + break; \ + } \ + } while (0) + +static uint8_t redist_was_enabled[AFI_MAX][ZEBRA_ROUTE_MAX]; + +static void vnc_redistribute_prechange(struct bgp *bgp) +{ + afi_t afi; + int type; + + vnc_zlog_debug_verbose("%s: entry", __func__); + memset(redist_was_enabled, 0, sizeof(redist_was_enabled)); + + /* + * Look to see if we have any redistribution enabled. If so, flush + * the corresponding routes and turn off redistribution temporarily. + * We need to do it because the RD's used for the redistributed + * routes depend on the nve group. + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { + if (bgp->rfapi_cfg->redist[afi][type]) { + redist_was_enabled[afi][type] = 1; + VNC_REDIST_DISABLE(bgp, afi, type); + } + } + } + vnc_zlog_debug_verbose("%s: return", __func__); +} + +static void vnc_redistribute_postchange(struct bgp *bgp) +{ + afi_t afi; + int type; + + vnc_zlog_debug_verbose("%s: entry", __func__); + /* + * If we turned off redistribution above, turn it back on. Doing so + * will tell zebra to resend the routes to us + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { + if (redist_was_enabled[afi][type]) { + VNC_REDIST_ENABLE(bgp, afi, type); + } + } + } + vnc_zlog_debug_verbose("%s: return", __func__); +} + +DEFUN (vnc_redistribute_rh_roo_localadmin, + vnc_redistribute_rh_roo_localadmin_cmd, + "vnc redistribute resolve-nve roo-ec-local-admin (0-65535)", + VNC_CONFIG_STR + "Redistribute routes into VNC\n" + "Resolve-NVE mode\n" + "Route Origin Extended Community Local Admin Field\n" "Field value\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint32_t localadmin; + char *endptr; + + VNC_VTY_CONFIG_CHECK(bgp); + + localadmin = strtoul(argv[4]->arg, &endptr, 0); + if (!argv[4]->arg[0] || *endptr) { + vty_out(vty, "%% Malformed value\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (localadmin > 0xffff) { + vty_out(vty, "%% Value out of range (0-%d)\n", 0xffff); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->rfapi_cfg->resolve_nve_roo_local_admin == localadmin) + return CMD_SUCCESS; + + if ((bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) + == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) { + + vnc_export_bgp_prechange(bgp); + } + vnc_redistribute_prechange(bgp); + + bgp->rfapi_cfg->resolve_nve_roo_local_admin = localadmin; + + if ((bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) + == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) { + + vnc_export_bgp_postchange(bgp); + } + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + + +DEFUN (vnc_redistribute_mode, + vnc_redistribute_mode_cmd, + "vnc redistribute mode ", + VNC_CONFIG_STR + "Redistribute routes into VNC\n" + "Redistribution mode\n" + "Based on redistribute nve-group\n" + "Unmodified\n" "Resolve each nexthop to connected NVEs\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + vnc_redist_mode_t newmode; + + VNC_VTY_CONFIG_CHECK(bgp); + + switch (argv[3]->arg[0]) { + case 'n': + newmode = VNC_REDIST_MODE_RFG; + break; + + case 'p': + newmode = VNC_REDIST_MODE_PLAIN; + break; + + case 'r': + newmode = VNC_REDIST_MODE_RESOLVE_NVE; + break; + + default: + vty_out(vty, "unknown redistribute mode\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (newmode != bgp->rfapi_cfg->redist_mode) { + vnc_redistribute_prechange(bgp); + bgp->rfapi_cfg->redist_mode = newmode; + vnc_redistribute_postchange(bgp); + } + + return CMD_SUCCESS; +} + +DEFUN (vnc_redistribute_protocol, + vnc_redistribute_protocol_cmd, + "vnc redistribute ", + VNC_CONFIG_STR + "Redistribute routes into VNC\n" + "IPv4 routes\n" + "IPv6 routes\n" + "From BGP\n" + "From BGP without Zebra\n" + "From BGP without Zebra, only to configured NVE groups\n" + "Connected interfaces\n" + "From kernel routes\n" + "From Open Shortest Path First (OSPF)\n" + "From Routing Information Protocol (RIP)\n" "From Static routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int type = ZEBRA_ROUTE_MAX; /* init to bogus value */ + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + if (rfapi_str2route_type(argv[2]->arg, argv[3]->arg, &afi, &type)) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { + if (bgp->rfapi_cfg->redist_bgp_exterior_view_name) { + VNC_REDIST_DISABLE(bgp, afi, + type); /* disabled view implicitly */ + XFREE(MTYPE_RFAPI_GROUP_CFG, + bgp->rfapi_cfg->redist_bgp_exterior_view_name); + } + bgp->rfapi_cfg->redist_bgp_exterior_view = bgp; + } + + VNC_REDIST_ENABLE(bgp, afi, type); + + return CMD_SUCCESS; +} + +DEFUN (vnc_no_redistribute_protocol, + vnc_no_redistribute_protocol_cmd, + "no vnc redistribute ", + NO_STR + VNC_CONFIG_STR + "Redistribute from other protocol\n" + "IPv4 routes\n" + "IPv6 routes\n" + "From BGP\n" + "From BGP without Zebra\n" + "From BGP without Zebra, only to configured NVE groups\n" + "Connected interfaces\n" + "From kernel routes\n" + "From Open Shortest Path First (OSPF)\n" + "From Routing Information Protocol (RIP)\n" "From Static routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int type; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + if (rfapi_str2route_type(argv[3]->arg, argv[4]->arg, &afi, &type)) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + VNC_REDIST_DISABLE(bgp, afi, type); + + if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + bgp->rfapi_cfg->redist_bgp_exterior_view_name); + bgp->rfapi_cfg->redist_bgp_exterior_view = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN (vnc_redistribute_bgp_exterior, + vnc_redistribute_bgp_exterior_cmd, + "vnc redistribute bgp-direct-to-nve-groups view NAME", + VNC_CONFIG_STR + "Redistribute routes into VNC\n" + "IPv4 routes\n" + "IPv6 routes\n" + "From BGP without Zebra, only to configured NVE groups\n" + "From BGP view\n" "BGP view name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int type; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + if (rfapi_str2route_type(argv[2]->arg, "bgp-direct-to-nve-groups", &afi, + &type)) { + vty_out(vty, "%% Invalid route type\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_RFAPI_GROUP_CFG, + bgp->rfapi_cfg->redist_bgp_exterior_view_name); + bgp->rfapi_cfg->redist_bgp_exterior_view_name = + XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[5]->arg); + /* could be NULL if name is not defined yet */ + bgp->rfapi_cfg->redist_bgp_exterior_view = + bgp_lookup_by_name(argv[5]->arg); + + VNC_REDIST_ENABLE(bgp, afi, type); + + return CMD_SUCCESS; +} + +DEFUN (vnc_redistribute_nvegroup, + vnc_redistribute_nvegroup_cmd, + "vnc redistribute nve-group NAME", + VNC_CONFIG_STR + "Assign a NVE group to routes redistributed from another routing protocol\n" + "NVE group\n" "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VNC_VTY_CONFIG_CHECK(bgp); + + vnc_redistribute_prechange(bgp); + + /* + * OK if nve group doesn't exist yet; we'll set the pointer + * when the group is defined later + */ + bgp->rfapi_cfg->rfg_redist = bgp_rfapi_cfg_match_byname( + bgp, argv[3]->arg, RFAPI_GROUP_CFG_NVE); + XFREE(MTYPE_RFAPI_GROUP_CFG, bgp->rfapi_cfg->rfg_redist_name); + bgp->rfapi_cfg->rfg_redist_name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[3]->arg); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_redistribute_no_nvegroup, + vnc_redistribute_no_nvegroup_cmd, + "no vnc redistribute nve-group", + NO_STR + VNC_CONFIG_STR + "Redistribute from other protocol\n" + "Assign a NVE group to routes redistributed from another routing protocol\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + VNC_VTY_CONFIG_CHECK(bgp); + + vnc_redistribute_prechange(bgp); + + bgp->rfapi_cfg->rfg_redist = NULL; + XFREE(MTYPE_RFAPI_GROUP_CFG, bgp->rfapi_cfg->rfg_redist_name); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + + +DEFUN (vnc_redistribute_lifetime, + vnc_redistribute_lifetime_cmd, + "vnc redistribute lifetime ", + VNC_CONFIG_STR + "Redistribute\n" + "Assign a lifetime to routes redistributed from another routing protocol\n" + "lifetime value (32 bit)\n" + "Allow lifetime to never expire\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VNC_VTY_CONFIG_CHECK(bgp); + + vnc_redistribute_prechange(bgp); + + if (strmatch(argv[3]->text, "infinite")) { + bgp->rfapi_cfg->redist_lifetime = RFAPI_INFINITE_LIFETIME; + } else { + bgp->rfapi_cfg->redist_lifetime = + strtoul(argv[3]->arg, NULL, 10); + } + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +/*-- redist policy, non-nvegroup start --*/ + +DEFUN (vnc_redist_bgpdirect_no_prefixlist, + vnc_redist_bgpdirect_no_prefixlist_cmd, + "no vnc redistribute prefix-list", + NO_STR + VNC_CONFIG_STR + "Redistribute from other protocol\n" + "Redistribute from BGP directly\n" + "Redistribute from BGP without Zebra, only to configured NVE groups\n" + "IPv4 routes\n" + "IPv6 routes\n" "Prefix-list for filtering redistributed routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + afi_t afi; + struct rfapi_cfg *hc; + uint8_t route_type = 0; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[3]->text, "bgp-direct")) { + route_type = ZEBRA_ROUTE_BGP_DIRECT; + } else { + route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; + } + + if (strmatch(argv[4]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->plist_redist_name[route_type][afi]); + hc->plist_redist[route_type][afi] = NULL; + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_redist_bgpdirect_prefixlist, + vnc_redist_bgpdirect_prefixlist_cmd, + "vnc redistribute prefix-list NAME", + VNC_CONFIG_STR + "Redistribute from other protocol\n" + "Redistribute from BGP directly\n" + "Redistribute from BGP without Zebra, only to configured NVE groups\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering redistributed routes\n" + "prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + afi_t afi; + uint8_t route_type = 0; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[2]->text, "bgp-direct")) { + route_type = ZEBRA_ROUTE_BGP_DIRECT; + } else { + route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; + } + + if (strmatch(argv[3]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->plist_redist_name[route_type][afi]); + hc->plist_redist_name[route_type][afi] = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[5]->arg); + hc->plist_redist[route_type][afi] = + prefix_list_lookup(afi, argv[5]->arg); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_redist_bgpdirect_no_routemap, + vnc_redist_bgpdirect_no_routemap_cmd, + "no vnc redistribute route-map", + NO_STR + VNC_CONFIG_STR + "Redistribute from other protocols\n" + "Redistribute from BGP directly\n" + "Redistribute from BGP without Zebra, only to configured NVE groups\n" + "Route-map for filtering redistributed routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + uint8_t route_type = 0; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[3]->text, "bgp-direct")) { + route_type = ZEBRA_ROUTE_BGP_DIRECT; + } else { + route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->routemap_redist_name[route_type]); + hc->routemap_redist[route_type] = NULL; + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_redist_bgpdirect_routemap, + vnc_redist_bgpdirect_routemap_cmd, + "vnc redistribute route-map NAME", + VNC_CONFIG_STR + "Redistribute from other protocols\n" + "Redistribute from BGP directly\n" + "Redistribute from BGP without Zebra, only to configured NVE groups\n" + "Route-map for filtering exported routes\n" "route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + uint8_t route_type = 0; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[2]->text, "bgp-direct")) { + route_type = ZEBRA_ROUTE_BGP_DIRECT; + } else { + route_type = ZEBRA_ROUTE_BGP_DIRECT_EXT; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->routemap_redist_name[route_type]); + + /* If the old route map config overwrite with new + * route map config , old routemap counter have to be + * reduced. + */ + route_map_counter_decrement(hc->routemap_redist[route_type]); + hc->routemap_redist_name[route_type] = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[4]->arg); + + hc->routemap_redist[route_type] = + route_map_lookup_by_name(argv[4]->arg); + route_map_counter_increment(hc->routemap_redist[route_type]); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +/*-- redist policy, non-nvegroup end --*/ + +/*-- redist policy, nvegroup start --*/ + +DEFUN (vnc_nve_group_redist_bgpdirect_no_prefixlist, + vnc_nve_group_redist_bgpdirect_no_prefixlist_cmd, + "no redistribute bgp-direct prefix-list", + NO_STR + "Redistribute from other protocol\n" + "Redistribute from BGP directly\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering redistributed routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg) + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[3]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]); + rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi] = NULL; + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_redist_bgpdirect_prefixlist, + vnc_nve_group_redist_bgpdirect_prefixlist_cmd, + "redistribute bgp-direct prefix-list NAME", + "Redistribute from other protocol\n" + "Redistribute from BGP directly\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering redistributed routes\n" + "prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[2]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]); + rfg->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi] = + XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[4]->arg); + rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi] = + prefix_list_lookup(afi, argv[4]->arg); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_redist_bgpdirect_no_routemap, + vnc_nve_group_redist_bgpdirect_no_routemap_cmd, + "no redistribute bgp-direct route-map", + NO_STR + "Redistribute from other protocols\n" + "Redistribute from BGP directly\n" + "Route-map for filtering redistributed routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); + route_map_counter_decrement( + rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); + rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT] = NULL; + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_redist_bgpdirect_routemap, + vnc_nve_group_redist_bgpdirect_routemap_cmd, + "redistribute bgp-direct route-map NAME", + "Redistribute from other protocols\n" + "Redistribute from BGP directly\n" + "Route-map for filtering exported routes\n" "route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vnc_redistribute_prechange(bgp); + + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); + route_map_counter_decrement( + rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); + rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT] = + XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[3]->arg); + rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT] = + route_map_lookup_by_name(argv[3]->arg); + route_map_counter_increment( + rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]); + + vnc_redistribute_postchange(bgp); + + return CMD_SUCCESS; +} + +/*-- redist policy, nvegroup end --*/ + +/*------------------------------------------------------------------------- + * export + *-----------------------------------------------------------------------*/ + +DEFUN (vnc_export_mode, + vnc_export_mode_cmd, + "vnc export mode ", + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "Select export mode\n" + "Export routes with nve-group next-hops\n" + "Export routes with NVE connected router next-hops\n" + "Disable export\n" "Export routes with registering NVE as next-hop\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + uint32_t oldmode = 0; + uint32_t newmode = 0; + + VNC_VTY_CONFIG_CHECK(bgp); + + if (argv[2]->arg[0] == 'b') { + oldmode = bgp->rfapi_cfg->flags + & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS; + switch (argv[4]->arg[0]) { + case 'g': + newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP; + break; + case 'c': + newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE; + break; + case 'n': + newmode = 0; + break; + case 'r': + newmode = BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH; + break; + default: + vty_out(vty, "Invalid mode specified\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (newmode == oldmode) { + vty_out(vty, "Mode unchanged\n"); + return CMD_SUCCESS; + } + + vnc_export_bgp_prechange(bgp); + + bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS; + bgp->rfapi_cfg->flags |= newmode; + + vnc_export_bgp_postchange(bgp); + + + } else { + /* + * export to zebra with RH mode is not yet implemented + */ + vty_out(vty, + "Changing modes for zebra export not implemented yet\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +static struct rfapi_rfg_name *rfgn_new(void) +{ + return XCALLOC(MTYPE_RFAPI_RFG_NAME, sizeof(struct rfapi_rfg_name)); +} + +static void rfgn_free(struct rfapi_rfg_name *rfgn) +{ + XFREE(MTYPE_RFAPI_RFG_NAME, rfgn); +} + +DEFUN (vnc_export_nvegroup, + vnc_export_nvegroup_cmd, + "vnc export group-nve group NAME", + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "NVE group, used in 'group-nve' export mode\n" + "NVE group\n" "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_nve_group_cfg *rfg_new; + + VNC_VTY_CONFIG_CHECK(bgp); + + rfg_new = bgp_rfapi_cfg_match_byname(bgp, argv[5]->arg, + RFAPI_GROUP_CFG_NVE); + if (rfg_new == NULL) { + rfg_new = bgp_rfapi_cfg_match_byname(bgp, argv[5]->arg, + RFAPI_GROUP_CFG_VRF); + if (rfg_new) + vnc_add_vrf_opener(bgp, rfg_new); + } + + if (rfg_new == NULL) { + vty_out(vty, "Can't find group named \"%s\".\n", argv[5]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[2]->arg[0] == 'b') { + + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* + * Set group for export to BGP Direct + */ + + /* see if group is already included in export list */ + for (ALL_LIST_ELEMENTS_RO( + bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (!strcmp(rfgn->name, argv[5]->arg)) { + /* already in the list: we're done */ + return CMD_SUCCESS; + } + } + + rfgn = rfgn_new(); + rfgn->name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[5]->arg); + rfgn->rfg = rfg_new; /* OK if not set yet */ + + listnode_add(bgp->rfapi_cfg->rfg_export_direct_bgp_l, rfgn); + + vnc_zlog_debug_verbose("%s: testing rfg_new", __func__); + if (rfg_new) { + vnc_zlog_debug_verbose( + "%s: testing bgp grp mode enabled", __func__); + if (VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) + vnc_zlog_debug_verbose( + "%s: calling vnc_direct_bgp_add_group", + __func__); + vnc_direct_bgp_add_group(bgp, rfg_new); + } + + } else { + + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* + * Set group for export to Zebra + */ + + /* see if group is already included in export list */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, + node, rfgn)) { + + if (!strcmp(rfgn->name, argv[5]->arg)) { + /* already in the list: we're done */ + return CMD_SUCCESS; + } + } + + rfgn = rfgn_new(); + rfgn->name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[5]->arg); + rfgn->rfg = rfg_new; /* OK if not set yet */ + + listnode_add(bgp->rfapi_cfg->rfg_export_zebra_l, rfgn); + + if (rfg_new) { + if (VNC_EXPORT_ZEBRA_GRP_ENABLED(bgp->rfapi_cfg)) + vnc_zebra_add_group(bgp, rfg_new); + } + } + + return CMD_SUCCESS; +} + +/* + * This command applies to routes exported from VNC to BGP directly + * without going though zebra + */ +DEFUN (vnc_no_export_nvegroup, + vnc_no_export_nvegroup_cmd, + "vnc export group-nve no group NAME", + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "NVE group, used in 'group-nve' export mode\n" + "Disable export of VNC routes\n" "NVE group\n" "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + + VNC_VTY_CONFIG_CHECK(bgp); + + if (argv[2]->arg[0] == 'b') { + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, + node, nnode, rfgn)) { + + if (rfgn->name && !strcmp(rfgn->name, argv[6]->arg)) { + vnc_zlog_debug_verbose("%s: matched \"%s\"", + __func__, rfgn->name); + if (rfgn->rfg) + vnc_direct_bgp_del_group(bgp, + rfgn->rfg); + XFREE(MTYPE_RFAPI_GROUP_CFG, rfgn->name); + list_delete_node( + bgp->rfapi_cfg->rfg_export_direct_bgp_l, + node); + rfgn_free(rfgn); + break; + } + } + } else { + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_zebra_l, node, + nnode, rfgn)) { + + vnc_zlog_debug_verbose("does rfg \"%s\" match?", + rfgn->name); + if (rfgn->name && !strcmp(rfgn->name, argv[6]->arg)) { + if (rfgn->rfg) + vnc_zebra_del_group(bgp, rfgn->rfg); + XFREE(MTYPE_RFAPI_GROUP_CFG, rfgn->name); + list_delete_node( + bgp->rfapi_cfg->rfg_export_zebra_l, + node); + rfgn_free(rfgn); + break; + } + } + } + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_export_no_prefixlist, + vnc_nve_group_export_no_prefixlist_cmd, + "no export prefix-list [NAME]", + NO_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering exported routes\n" "prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int idx = 0; + int is_bgp = 1; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + vty_out(vty, "%% Malformed Address Family\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[idx - 1]->text[0] == 'z') + is_bgp = 0; + idx += 2; /* skip afi and keyword */ + + if (is_bgp) { + if (idx == argc + || (rfg->plist_export_bgp_name[afi] + && strmatch(argv[idx]->arg, + rfg->plist_export_bgp_name[afi]))) { + if (rfg->plist_export_bgp_name[afi]) + free(rfg->plist_export_bgp_name[afi]); + rfg->plist_export_bgp_name[afi] = NULL; + rfg->plist_export_bgp[afi] = NULL; + + vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); + } + } else { + if (idx == argc + || (rfg->plist_export_zebra_name[afi] + && strmatch(argv[idx]->arg, + rfg->plist_export_zebra_name[afi]))) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->plist_export_zebra_name[afi]); + rfg->plist_export_zebra[afi] = NULL; + + vnc_zebra_reexport_group_afi(bgp, rfg, afi); + } + } + return CMD_SUCCESS; +} + +ALIAS (vnc_nve_group_export_no_prefixlist, + vnc_vrf_policy_export_no_prefixlist_cmd, + "no export prefix-list [NAME]", + NO_STR + "Export to VRF\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering exported routes\n" "prefix list name\n") + +DEFUN (vnc_nve_group_export_prefixlist, + vnc_nve_group_export_prefixlist_cmd, + "export prefix-list NAME", + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering exported routes\n" "prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int idx = 0; + int is_bgp = 1; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!argv_find_and_parse_afi(argv, argc, &idx, &afi)) { + vty_out(vty, "%% Malformed Address Family\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[idx - 1]->text[0] == 'z') + is_bgp = 0; + idx = argc - 1; + + if (is_bgp) { + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->plist_export_bgp_name[afi]); + rfg->plist_export_bgp_name[afi] = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[idx]->arg); + rfg->plist_export_bgp[afi] = + prefix_list_lookup(afi, argv[idx]->arg); + + vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); + + } else { + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->plist_export_zebra_name[afi]); + rfg->plist_export_zebra_name[afi] = + XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[idx]->arg); + rfg->plist_export_zebra[afi] = + prefix_list_lookup(afi, argv[idx]->arg); + + vnc_zebra_reexport_group_afi(bgp, rfg, afi); + } + return CMD_SUCCESS; +} + +ALIAS (vnc_nve_group_export_prefixlist, + vnc_vrf_policy_export_prefixlist_cmd, + "export prefix-list NAME", + "Export to VRF\n" + "IPv4 routes\n" + "IPv6 routes\n" + "Prefix-list for filtering exported routes\n" "prefix list name\n") + +DEFUN (vnc_nve_group_export_no_routemap, + vnc_nve_group_export_no_routemap_cmd, + "no export route-map [NAME]", + NO_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "Route-map for filtering exported routes\n" "route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int idx = 2; + int is_bgp = 1; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + switch (argv[idx]->text[0]) { + case 'z': + is_bgp = 0; + idx += 2; + break; + case 'b': + idx += 2; + break; + default: /* route-map */ + idx++; + break; + } + + if (is_bgp) { + if (idx == argc + || (rfg->routemap_export_bgp_name + && strmatch(argv[idx]->arg, + rfg->routemap_export_bgp_name))) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + rfg->routemap_export_bgp_name); + route_map_counter_decrement(rfg->routemap_export_bgp); + rfg->routemap_export_bgp = NULL; + + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); + } + } else { + if (idx == argc + || (rfg->routemap_export_zebra_name + && strmatch(argv[idx]->arg, + rfg->routemap_export_zebra_name))) { + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->routemap_export_zebra_name); + route_map_counter_decrement(rfg->routemap_export_zebra); + rfg->routemap_export_zebra = NULL; + + vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP); + vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP6); + } + } + return CMD_SUCCESS; +} + +ALIAS (vnc_nve_group_export_no_routemap, + vnc_vrf_policy_export_no_routemap_cmd, + "no export route-map [NAME]", + NO_STR + "Export to VRF\n" + "Route-map for filtering exported routes\n" "route map name\n") + +DEFUN (vnc_nve_group_export_routemap, + vnc_nve_group_export_routemap_cmd, + "export route-map NAME", + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "Route-map for filtering exported routes\n" "route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int idx = 0; + int is_bgp = 1; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[1]->text[0] == 'z') + is_bgp = 0; + idx = argc - 1; + + if (is_bgp) { + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->routemap_export_bgp_name); + route_map_counter_decrement(rfg->routemap_export_bgp); + rfg->routemap_export_bgp_name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[idx]->arg); + rfg->routemap_export_bgp = + route_map_lookup_by_name(argv[idx]->arg); + route_map_counter_increment(rfg->routemap_export_bgp); + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); + } else { + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->routemap_export_zebra_name); + route_map_counter_decrement(rfg->routemap_export_zebra); + rfg->routemap_export_zebra_name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[idx]->arg); + rfg->routemap_export_zebra = + route_map_lookup_by_name(argv[idx]->arg); + route_map_counter_increment(rfg->routemap_export_zebra); + vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP); + vnc_zebra_reexport_group_afi(bgp, rfg, AFI_IP6); + } + return CMD_SUCCESS; +} + +ALIAS (vnc_nve_group_export_routemap, + vnc_vrf_policy_export_routemap_cmd, + "export route-map NAME", + "Export to VRF\n" + "Route-map for filtering exported routes\n" "route map name\n") + +DEFUN (vnc_nve_export_no_prefixlist, + vnc_nve_export_no_prefixlist_cmd, + "no vnc export prefix-list [NAME]", + NO_STR + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "IPv4 prefixes\n" + "IPv6 prefixes\n" + "Prefix-list for filtering exported routes\n" "Prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[4]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + if (argv[3]->arg[0] == 'b') { + if (((argc > 6) && hc->plist_export_bgp_name[afi] + && strmatch(argv[6]->text, hc->plist_export_bgp_name[afi])) + || (argc <= 6)) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + hc->plist_export_bgp_name[afi]); + hc->plist_export_bgp[afi] = NULL; + vnc_direct_bgp_reexport(bgp, afi); + } + } else { + if (((argc > 6) && hc->plist_export_zebra_name[afi] + && strmatch(argv[6]->text, + hc->plist_export_zebra_name[afi])) + || (argc <= 6)) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + hc->plist_export_zebra_name[afi]); + hc->plist_export_zebra[afi] = NULL; + /* TBD vnc_zebra_rh_reexport(bgp, afi); */ + } + } + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_export_prefixlist, + vnc_nve_export_prefixlist_cmd, + "vnc export prefix-list NAME", + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "IPv4 prefixes\n" + "IPv6 prefixes\n" + "Prefix-list for filtering exported routes\n" "Prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + afi_t afi; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (strmatch(argv[3]->text, "ipv4")) { + afi = AFI_IP; + } else { + afi = AFI_IP6; + } + + if (argv[2]->arg[0] == 'b') { + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->plist_export_bgp_name[afi]); + hc->plist_export_bgp_name[afi] = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[5]->arg); + hc->plist_export_bgp[afi] = + prefix_list_lookup(afi, argv[5]->arg); + vnc_direct_bgp_reexport(bgp, afi); + } else { + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->plist_export_zebra_name[afi]); + hc->plist_export_zebra_name[afi] = + XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[5]->arg); + hc->plist_export_zebra[afi] = + prefix_list_lookup(afi, argv[5]->arg); + /* TBD vnc_zebra_rh_reexport(bgp, afi); */ + } + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_export_no_routemap, + vnc_nve_export_no_routemap_cmd, + "no vnc export route-map [NAME]", + NO_STR + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "Route-map for filtering exported routes\n" "Route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (argv[3]->arg[0] == 'b') { + if (((argc > 5) && hc->routemap_export_bgp_name + && strmatch(argv[5]->text, hc->routemap_export_bgp_name)) + || (argc <= 5)) { + XFREE(MTYPE_RFAPI_GROUP_CFG, + hc->routemap_export_bgp_name); + route_map_counter_decrement(hc->routemap_export_bgp); + hc->routemap_export_bgp = NULL; + vnc_direct_bgp_reexport(bgp, AFI_IP); + vnc_direct_bgp_reexport(bgp, AFI_IP6); + } + } else { + if (((argc > 5) && hc->routemap_export_zebra_name + && strmatch(argv[5]->text, hc->routemap_export_zebra_name)) + || (argc <= 5)) { + + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->routemap_export_zebra_name); + route_map_counter_decrement(hc->routemap_export_zebra); + hc->routemap_export_zebra = NULL; + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ + } + } + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_export_routemap, + vnc_nve_export_routemap_cmd, + "vnc export route-map NAME", + VNC_CONFIG_STR + "Export to other protocols\n" + "Export to BGP\n" + "Export to Zebra (experimental)\n" + "Route-map for filtering exported routes\n" "Route map name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_cfg *hc; + + VNC_VTY_CONFIG_CHECK(bgp); + hc = bgp->rfapi_cfg; + + if (argv[2]->arg[0] == 'b') { + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->routemap_export_bgp_name); + route_map_counter_decrement(hc->routemap_export_bgp); + hc->routemap_export_bgp_name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[4]->arg); + hc->routemap_export_bgp = + route_map_lookup_by_name(argv[4]->arg); + route_map_counter_increment(hc->routemap_export_bgp); + vnc_direct_bgp_reexport(bgp, AFI_IP); + vnc_direct_bgp_reexport(bgp, AFI_IP6); + } else { + XFREE(MTYPE_RFAPI_GROUP_CFG, hc->routemap_export_zebra_name); + route_map_counter_decrement(hc->routemap_export_zebra); + hc->routemap_export_zebra_name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, + argv[4]->arg); + hc->routemap_export_zebra = + route_map_lookup_by_name(argv[4]->arg); + route_map_counter_increment(hc->routemap_export_zebra); + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ + } + return CMD_SUCCESS; +} + + +/* + * respond to changes in the global prefix list configuration + */ +void vnc_prefix_list_update(struct bgp *bgp) +{ + afi_t afi; + struct listnode *n; + struct rfapi_nve_group_cfg *rfg; + struct rfapi_cfg *hc; + int i; + + if (!bgp) { + vnc_zlog_debug_verbose("%s: No BGP process is configured", + __func__); + return; + } + + if (!(hc = bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: rfapi not configured", __func__); + return; + } + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + /* + * Loop over nve groups + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, + n, rfg)) { + + if (rfg->plist_export_bgp_name[afi]) { + rfg->plist_export_bgp[afi] = prefix_list_lookup( + afi, rfg->plist_export_bgp_name[afi]); + } + if (rfg->plist_export_zebra_name[afi]) { + rfg->plist_export_zebra + [afi] = prefix_list_lookup( + afi, rfg->plist_export_zebra_name[afi]); + } + for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { + if (rfg->plist_redist_name[i][afi]) { + rfg->plist_redist + [i][afi] = prefix_list_lookup( + afi, + rfg->plist_redist_name[i][afi]); + } + } + + vnc_direct_bgp_reexport_group_afi(bgp, rfg, afi); + /* TBD vnc_zebra_reexport_group_afi(bgp, rfg, afi); */ + } + + /* + * RH config, too + */ + if (hc->plist_export_bgp_name[afi]) { + hc->plist_export_bgp[afi] = prefix_list_lookup( + afi, hc->plist_export_bgp_name[afi]); + } + if (hc->plist_export_zebra_name[afi]) { + hc->plist_export_zebra[afi] = prefix_list_lookup( + afi, hc->plist_export_zebra_name[afi]); + } + + for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { + if (hc->plist_redist_name[i][afi]) { + hc->plist_redist[i][afi] = prefix_list_lookup( + afi, hc->plist_redist_name[i][afi]); + } + } + } + + vnc_direct_bgp_reexport(bgp, AFI_IP); + vnc_direct_bgp_reexport(bgp, AFI_IP6); + + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ + + vnc_redistribute_prechange(bgp); + vnc_redistribute_postchange(bgp); +} + +/* + * respond to changes in the global route map configuration + */ +void vnc_routemap_update(struct bgp *bgp, const char *unused) +{ + struct listnode *n; + struct rfapi_nve_group_cfg *rfg; + struct rfapi_cfg *hc; + int i; + struct route_map *old = NULL; + + vnc_zlog_debug_verbose("%s(arg=%s)", __func__, unused); + + if (!bgp) { + vnc_zlog_debug_verbose("%s: No BGP process is configured", + __func__); + return; + } + + if (!(hc = bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: rfapi not configured", __func__); + return; + } + + /* + * Loop over nve groups + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, n, + rfg)) { + + if (rfg->routemap_export_bgp_name) { + old = rfg->routemap_export_bgp; + rfg->routemap_export_bgp = route_map_lookup_by_name( + rfg->routemap_export_bgp_name); + /* old is NULL. i.e Route map creation event. + * So update applied_counter. + * If Old is not NULL, i.e It may be routemap + * updation or deletion. + * So no need to update the counter. + */ + if (!old) + route_map_counter_increment( + rfg->routemap_export_bgp); + } + if (rfg->routemap_export_zebra_name) { + old = rfg->routemap_export_bgp; + rfg->routemap_export_bgp = route_map_lookup_by_name( + rfg->routemap_export_zebra_name); + if (!old) + route_map_counter_increment( + rfg->routemap_export_bgp); + } + for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { + if (rfg->routemap_redist_name[i]) { + old = rfg->routemap_redist[i]; + rfg->routemap_redist[i] = + route_map_lookup_by_name( + rfg->routemap_redist_name[i]); + if (!old) + route_map_counter_increment( + rfg->routemap_redist[i]); + } + } + + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP); + vnc_direct_bgp_reexport_group_afi(bgp, rfg, AFI_IP6); + /* TBD vnc_zebra_reexport_group_afi(bgp, rfg, afi); */ + } + + /* + * RH config, too + */ + if (hc->routemap_export_bgp_name) { + old = hc->routemap_export_bgp; + hc->routemap_export_bgp = + route_map_lookup_by_name(hc->routemap_export_bgp_name); + if (!old) + route_map_counter_increment(hc->routemap_export_bgp); + } + if (hc->routemap_export_zebra_name) { + old = hc->routemap_export_bgp; + hc->routemap_export_bgp = route_map_lookup_by_name( + hc->routemap_export_zebra_name); + if (!old) + route_map_counter_increment(hc->routemap_export_bgp); + } + for (i = 0; i < ZEBRA_ROUTE_MAX; ++i) { + if (hc->routemap_redist_name[i]) { + old = hc->routemap_redist[i]; + hc->routemap_redist[i] = route_map_lookup_by_name( + hc->routemap_redist_name[i]); + if (!old) + route_map_counter_increment( + hc->routemap_redist[i]); + } + } + + vnc_direct_bgp_reexport(bgp, AFI_IP); + vnc_direct_bgp_reexport(bgp, AFI_IP6); + + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP); */ + /* TBD vnc_zebra_rh_reexport(bgp, AFI_IP6); */ + + vnc_redistribute_prechange(bgp); + vnc_redistribute_postchange(bgp); + + vnc_zlog_debug_verbose("%s done", __func__); +} + +/*------------------------------------------------------------------------- + * nve-group + *-----------------------------------------------------------------------*/ + + +DEFUN_NOSH (vnc_nve_group, + vnc_nve_group_cmd, + "vnc nve-group NAME", + VNC_CONFIG_STR "Configure a NVE group\n" "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct rfapi_nve_group_cfg *rfg; + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + + VNC_VTY_CONFIG_CHECK(bgp); + + /* Search for name */ + rfg = bgp_rfapi_cfg_match_byname(bgp, argv[2]->arg, + RFAPI_GROUP_CFG_NVE); + + if (!rfg) { + rfg = rfapi_group_new(bgp, RFAPI_GROUP_CFG_NVE, argv[2]->arg); + if (!rfg) { + /* Error out of memory */ + vty_out(vty, "Can't allocate memory for NVE group\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Copy defaults from struct rfapi_cfg */ + rfg->rd = bgp->rfapi_cfg->default_rd; + if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_L2RD) { + rfg->l2rd = bgp->rfapi_cfg->default_l2rd; + rfg->flags |= RFAPI_RFG_L2RD; + } + rfg->rd = bgp->rfapi_cfg->default_rd; + rfg->response_lifetime = + bgp->rfapi_cfg->default_response_lifetime; + + if (bgp->rfapi_cfg->default_rt_export_list) { + rfg->rt_export_list = ecommunity_dup( + bgp->rfapi_cfg->default_rt_export_list); + } + + if (bgp->rfapi_cfg->default_rt_import_list) { + rfg->rt_import_list = ecommunity_dup( + bgp->rfapi_cfg->default_rt_import_list); + rfg->rfapi_import_table = rfapiImportTableRefAdd( + bgp, rfg->rt_import_list, rfg); + } + + /* + * If a redist nve group was named but the group was not + * defined, + * make the linkage now + */ + if (!bgp->rfapi_cfg->rfg_redist) { + if (bgp->rfapi_cfg->rfg_redist_name + && !strcmp(bgp->rfapi_cfg->rfg_redist_name, + rfg->name)) { + + vnc_redistribute_prechange(bgp); + bgp->rfapi_cfg->rfg_redist = rfg; + vnc_redistribute_postchange(bgp); + } + } + + /* + * Same treatment for bgp-direct export group + */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, + node, nnode, rfgn)) { + + if (!strcmp(rfgn->name, rfg->name)) { + rfgn->rfg = rfg; + vnc_direct_bgp_add_group(bgp, rfg); + break; + } + } + + /* + * Same treatment for zebra export group + */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_zebra_l, node, + nnode, rfgn)) { + + vnc_zlog_debug_verbose( + "%s: ezport zebra: checking if \"%s\" == \"%s\"", + __func__, rfgn->name, rfg->name); + if (!strcmp(rfgn->name, rfg->name)) { + rfgn->rfg = rfg; + vnc_zebra_add_group(bgp, rfg); + break; + } + } + } + + /* + * XXX subsequent calls will need to make sure this item is still + * in the linked list and has the same name + */ + VTY_PUSH_CONTEXT_SUB(BGP_VNC_NVE_GROUP_NODE, rfg); + + return CMD_SUCCESS; +} + +static void bgp_rfapi_delete_nve_group(struct vty *vty, /* NULL = no output */ + struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg) +{ + struct list *orphaned_nves = NULL; + struct listnode *node, *nnode; + + /* + * If there are currently-open NVEs that belong to this group, + * zero out their references to this group structure. + */ + if (rfg->nves) { + struct rfapi_descriptor *rfd; + orphaned_nves = list_new(); + while ((rfd = listnode_head(rfg->nves))) { + rfd->rfg = NULL; + listnode_delete(rfg->nves, rfd); + listnode_add(orphaned_nves, rfd); + } + list_delete(&rfg->nves); + } + + /* delete it */ + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->name); + if (rfg->rfapi_import_table) + rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); + if (rfg->rt_import_list) + ecommunity_free(&rfg->rt_import_list); + if (rfg->rt_export_list) + ecommunity_free(&rfg->rt_export_list); + + if (rfg->vn_node) { + rfg->vn_node->info = NULL; + agg_unlock_node(rfg->vn_node); /* frees */ + } + if (rfg->un_node) { + rfg->un_node->info = NULL; + agg_unlock_node(rfg->un_node); /* frees */ + } + if (rfg->rfp_cfg) + XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, rfg->rfp_cfg); + listnode_delete(bgp->rfapi_cfg->nve_groups_sequential, rfg); + + QOBJ_UNREG(rfg); + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg); + + /* + * Attempt to reassign the orphaned nves to a new group. If + * a NVE can not be reassigned, its rfd->rfg will remain NULL + * and it will become a zombie until released by rfapi_close(). + */ + if (orphaned_nves) { + struct rfapi_descriptor *rfd; + + for (ALL_LIST_ELEMENTS(orphaned_nves, node, nnode, rfd)) { + /* + * 1. rfapi_close() equivalent except: + * a. don't free original descriptor + * b. remember query list + * c. remember advertised route list + * 2. rfapi_open() equivalent except: + * a. reuse original descriptor + * 3. rfapi_register() on remembered advertised route + * list + * 4. rfapi_query on rememebred query list + */ + + int rc; + + rc = rfapi_reopen(rfd, bgp); + + if (!rc) { + list_delete_node(orphaned_nves, node); + if (vty) + vty_out(vty, + "WARNING: reassigned NVE vn="); + rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); + if (vty) + vty_out(vty, " un="); + rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); + if (vty) + vty_out(vty, " to new group \"%s\"\n", + rfd->rfg->name); + } + } + + for (ALL_LIST_ELEMENTS_RO(orphaned_nves, node, rfd)) { + if (vty) + vty_out(vty, "WARNING: orphaned NVE vn="); + rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); + if (vty) + vty_out(vty, " un="); + rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); + if (vty) + vty_out(vty, "\n"); + } + list_delete(&orphaned_nves); + } +} + +static int +bgp_rfapi_delete_named_nve_group(struct vty *vty, /* NULL = no output */ + struct bgp *bgp, + const char *rfg_name, /* NULL = any */ + rfapi_group_cfg_type_t type) /* _MAX = any */ +{ + struct rfapi_nve_group_cfg *rfg = NULL; + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + + /* Search for name */ + if (rfg_name) { + rfg = bgp_rfapi_cfg_match_byname(bgp, rfg_name, type); + if (!rfg) { + if (vty) + vty_out(vty, "No NVE group named \"%s\"\n", + rfg_name); + return CMD_WARNING_CONFIG_FAILED; + } + } + + /* + * If this group is the redist nve group, unlink it + */ + if (rfg_name == NULL || bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + bgp->rfapi_cfg->rfg_redist = NULL; + vnc_redistribute_postchange(bgp); + } + + + /* + * remove reference from bgp direct export list + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + if (rfgn->rfg == rfg) { + rfgn->rfg = NULL; + /* remove exported routes from this group */ + vnc_direct_bgp_del_group(bgp, rfg); + break; + } + } + + /* + * remove reference from zebra export list + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + if (rfgn->rfg == rfg) { + rfgn->rfg = NULL; + /* remove exported routes from this group */ + vnc_zebra_del_group(bgp, rfg); + break; + } + } + if (rfg) { + if (rfg->rfd) + clear_vnc_vrf_closer(rfg); + bgp_rfapi_delete_nve_group(vty, bgp, rfg); + } else /* must be delete all */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->nve_groups_sequential, + node, nnode, rfg)) { + if (rfg->rfd) + clear_vnc_vrf_closer(rfg); + bgp_rfapi_delete_nve_group(vty, bgp, rfg); + } + return CMD_SUCCESS; +} + +DEFUN (vnc_no_nve_group, + vnc_no_nve_group_cmd, + "no vnc nve-group NAME", + NO_STR + VNC_CONFIG_STR + "Configure a NVE group\n" + "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + return bgp_rfapi_delete_named_nve_group(vty, bgp, argv[3]->arg, + RFAPI_GROUP_CFG_NVE); +} + +DEFUN (vnc_nve_group_prefix, + vnc_nve_group_prefix_cmd, + "prefix ", + "Specify prefixes matching NVE VN or UN interfaces\n" + "VN prefix\n" + "UN prefix\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + struct prefix p; + afi_t afi; + struct agg_table *rt; + struct agg_node *rn; + int is_un_prefix = 0; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!str2prefix(argv[2]->arg, &p)) { + vty_out(vty, "Malformed prefix \"%s\"\n", argv[2]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + afi = family2afi(p.family); + if (!afi) { + vty_out(vty, "Unsupported address family\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[1]->arg[0] == 'u') { + rt = bgp->rfapi_cfg->nve_groups_un[afi]; + is_un_prefix = 1; + } else { + rt = bgp->rfapi_cfg->nve_groups_vn[afi]; + } + + rn = agg_node_get(rt, &p); /* NB locks node */ + if (rn->info) { + /* + * There is already a group with this prefix + */ + agg_unlock_node(rn); + if (rn->info != rfg) { + /* + * different group name: fail + */ + vty_out(vty, + "nve group \"%s\" already has \"%s\" prefix %s\n", + ((struct rfapi_nve_group_cfg *)(rn->info)) + ->name, + argv[1]->arg, argv[2]->arg); + return CMD_WARNING_CONFIG_FAILED; + } else { + /* + * same group name: it's already in the correct place + * in the table, so we're done. + * + * Implies rfg->(vn|un)_prefix is already correct. + */ + return CMD_SUCCESS; + } + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + /* New prefix, new node */ + + if (is_un_prefix) { + + /* detach rfg from previous route table location */ + if (rfg->un_node) { + rfg->un_node->info = NULL; + agg_unlock_node(rfg->un_node); /* frees */ + } + rfg->un_node = rn; /* back ref */ + rfg->un_prefix = p; + + } else { + + /* detach rfg from previous route table location */ + if (rfg->vn_node) { + rfg->vn_node->info = NULL; + agg_unlock_node(rfg->vn_node); /* frees */ + } + rfg->vn_node = rn; /* back ref */ + rfg->vn_prefix = p; + } + + /* attach */ + rn->info = rfg; + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_rt_import, + vnc_nve_group_rt_import_cmd, + "rt import RTLIST...", + "Specify route targets\n" + "Import filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int rc; + struct listnode *node; + struct rfapi_rfg_name *rfgn; + int is_export_bgp = 0; + int is_export_zebra = 0; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); + if (rc != CMD_SUCCESS) + return rc; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_bgp = 1; + break; + } + } + + if (is_export_bgp) + vnc_direct_bgp_del_group(bgp, rfg); + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_zebra = 1; + break; + } + } + + if (is_export_zebra) + vnc_zebra_del_group(bgp, rfg); + + /* + * stop referencing old import table, now reference new one + */ + if (rfg->rfapi_import_table) + rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); + rfg->rfapi_import_table = + rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); + + if (is_export_bgp) + vnc_direct_bgp_add_group(bgp, rfg); + + if (is_export_zebra) + vnc_zebra_add_group(bgp, rfg); + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_rt_export, + vnc_nve_group_rt_export_cmd, + "rt export RTLIST...", + "Specify route targets\n" + "Export filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int rc; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return rc; +} + +DEFUN (vnc_nve_group_rt_both, + vnc_nve_group_rt_both_cmd, + "rt both RTLIST...", + "Specify route targets\n" + "Export+import filters\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + int rc; + int is_export_bgp = 0; + int is_export_zebra = 0; + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); + if (rc != CMD_SUCCESS) + return rc; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_bgp = 1; + break; + } + } + + if (is_export_bgp) + vnc_direct_bgp_del_group(bgp, rfg); + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_zebra = 1; + break; + } + } + + if (is_export_zebra) { + vnc_zlog_debug_verbose("%s: is_export_zebra", __func__); + vnc_zebra_del_group(bgp, rfg); + } + + /* + * stop referencing old import table, now reference new one + */ + if (rfg->rfapi_import_table) + rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); + rfg->rfapi_import_table = + rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); + + if (is_export_bgp) + vnc_direct_bgp_add_group(bgp, rfg); + + if (is_export_zebra) + vnc_zebra_add_group(bgp, rfg); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return rc; +} + +DEFUN (vnc_nve_group_l2rd, + vnc_nve_group_l2rd_cmd, + "l2rd <(1-255)|auto-vn>", + "Specify default Local Nve ID value to use in RD for L2 routes\n" + "Fixed value 1-255\n" + "use the low-order octet of the NVE's VN address\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[1]->text, "auto:vn")) { + rfg->l2rd = 0; + } else { + char *end = NULL; + unsigned long value_l = strtoul(argv[1]->arg, &end, 10); + uint8_t value = value_l & 0xff; + + if (!argv[1]->arg[0] || *end) { + vty_out(vty, "%% Malformed l2 nve ID \"%s\"\n", + argv[1]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if ((value_l < 1) || (value_l > 0xff)) { + vty_out(vty, + "%% Malformed l2 nve id (must be greater than 0 and less than %u\n", + 0x100); + return CMD_WARNING_CONFIG_FAILED; + } + + rfg->l2rd = value; + } + rfg->flags |= RFAPI_RFG_L2RD; + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_no_l2rd, + vnc_nve_group_no_l2rd_cmd, + "no l2rd", + NO_STR + "Specify default Local Nve ID value to use in RD for L2 routes\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rfg->l2rd = 0; + rfg->flags &= ~RFAPI_RFG_L2RD; + + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_rd, + vnc_nve_group_rd_cmd, + "rd ASN:NN_OR_IP-ADDRESS:NN", + "Specify route distinguisher\n" + "Route Distinguisher (: | : | auto:vn: )\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + int ret; + struct prefix_rd prd; + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!strncmp(argv[1]->arg, "auto:vn:", 8)) { + /* + * use AF_UNIX to designate automatically-assigned RD + * auto:vn:nn where nn is a 2-octet quantity + */ + char *end = NULL; + uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); + uint16_t value = value32 & 0xffff; + + if (!argv[1]->arg[8] || *end) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (value32 > 0xffff) { + vty_out(vty, "%% Malformed rd (must be less than %u\n", + 0x0ffff); + return CMD_WARNING_CONFIG_FAILED; + } + + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNIX; + prd.prefixlen = 64; + prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; + prd.val[1] = RD_TYPE_IP & 0x0ff; + prd.val[6] = (value >> 8) & 0x0ff; + prd.val[7] = value & 0x0ff; + + } else { + + /* TODO: save RD format */ + ret = str2prefix_rd(argv[1]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rfg->rd = prd; + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + return CMD_SUCCESS; +} + +DEFUN (vnc_nve_group_responselifetime, + vnc_nve_group_responselifetime_cmd, + "response-lifetime ", + "Specify response lifetime\n" + "Response lifetime in seconds\n" "Infinite response lifetime\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + unsigned int rspint; + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + struct rfapi_descriptor *rfd; + struct listnode *hdnode; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[1]->text, "infinite")) { + rspint = RFAPI_INFINITE_LIFETIME; + } else { + rspint = strtoul(argv[1]->arg, NULL, 10); + } + + rfg->response_lifetime = rspint; + rfg->flags |= RFAPI_RFG_RESPONSE_LIFETIME; + if (rfg->nves) + for (ALL_LIST_ELEMENTS_RO(rfg->nves, hdnode, rfd)) + rfd->response_lifetime = rspint; + return CMD_SUCCESS; +} + +/* + * Sigh. This command, like exit-address-family, is a hack to deal + * with the lack of rigorous level control in the command handler. + * TBD fix command handler. + */ +DEFUN_NOSH (exit_vnc, + exit_vnc_cmd, + "exit-vnc", + "Exit VNC configuration mode\n") +{ + if (vty->node == BGP_VNC_DEFAULTS_NODE + || vty->node == BGP_VNC_NVE_GROUP_NODE + || vty->node == BGP_VNC_L2_GROUP_NODE) { + + vty->node = BGP_NODE; + } + return CMD_SUCCESS; +} + +static struct cmd_node bgp_vnc_defaults_node = { + .name = "bgp vnc defaults", + .node = BGP_VNC_DEFAULTS_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-vnc-defaults)# ", +}; + +static struct cmd_node bgp_vnc_nve_group_node = { + .name = "bgp vnc nve", + .node = BGP_VNC_NVE_GROUP_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-vnc-nve-group)# ", +}; + +/*------------------------------------------------------------------------- + * VNC nve-group + * Note there are two types of NVEs, one for VPNs one for RFP NVEs + *-----------------------------------------------------------------------*/ + +DEFUN_NOSH (vnc_vrf_policy, + vnc_vrf_policy_cmd, + "vrf-policy NAME", + "Configure a VRF policy group\n" + "VRF name\n") +{ + struct rfapi_nve_group_cfg *rfg; + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) { + vty_out(vty, + "Can't configure vrf-policy within a BGP VRF instance\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Search for name */ + rfg = bgp_rfapi_cfg_match_byname(bgp, argv[1]->arg, + RFAPI_GROUP_CFG_VRF); + + if (!rfg) { + rfg = rfapi_group_new(bgp, RFAPI_GROUP_CFG_VRF, argv[1]->arg); + if (!rfg) { + /* Error out of memory */ + vty_out(vty, "Can't allocate memory for NVE group\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + /* + * XXX subsequent calls will need to make sure this item is still + * in the linked list and has the same name + */ + VTY_PUSH_CONTEXT_SUB(BGP_VRF_POLICY_NODE, rfg); + + return CMD_SUCCESS; +} + +DEFUN (vnc_no_vrf_policy, + vnc_no_vrf_policy_cmd, + "no vrf-policy NAME", + NO_STR + "Remove a VRF policy group\n" + "VRF name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* silently return */ + if (bgp->inst_type == BGP_INSTANCE_TYPE_VRF) + return CMD_SUCCESS; + + return bgp_rfapi_delete_named_nve_group(vty, bgp, argv[2]->arg, + RFAPI_GROUP_CFG_VRF); +} + +DEFUN (vnc_vrf_policy_label, + vnc_vrf_policy_label_cmd, + "label (0-1048575)", + "Default label value for VRF\n" + "Label Value <0-1048575>\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + + uint32_t label; + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + label = strtoul(argv[1]->arg, NULL, 10); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rfg->label = label; + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + return CMD_SUCCESS; +} + +DEFUN (vnc_vrf_policy_no_label, + vnc_vrf_policy_no_label_cmd, + "no label", + NO_STR + "Remove VRF default label\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current VRF group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rfg->label = MPLS_LABEL_NONE; + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + return CMD_SUCCESS; +} + +DEFUN (vnc_vrf_policy_nexthop, + vnc_vrf_policy_nexthop_cmd, + "nexthop ", + "Specify next hop to use for VRF advertised prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Use configured router-id (default)\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + struct prefix p; + + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current VRF no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + if (!str2prefix(argv[1]->arg, &p) && p.family) { + // vty_out (vty, "Nexthop set to self\n"); + SET_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF); + memset(&rfg->vn_prefix, 0, sizeof(struct prefix)); + } else { + UNSET_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF); + rfg->vn_prefix = p; + rfg->un_prefix = p; + } + + /* TBD handle router-id/ nexthop changes when have advertised prefixes + */ + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return CMD_SUCCESS; +} + +/* The RT code should be refactored/simplified with above... */ +DEFUN (vnc_vrf_policy_rt_import, + vnc_vrf_policy_rt_import_cmd, + "rt import RTLIST...", + "Specify route targets\n" + "Import filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + int rc; + struct listnode *node; + struct rfapi_rfg_name *rfgn; + int is_export_bgp = 0; + int is_export_zebra = 0; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); + if (rc != CMD_SUCCESS) + return rc; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_bgp = 1; + break; + } + } + + if (is_export_bgp) + vnc_direct_bgp_del_group(bgp, rfg); + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_zebra = 1; + break; + } + } + + if (is_export_zebra) + vnc_zebra_del_group(bgp, rfg); + + /* + * stop referencing old import table, now reference new one + */ + if (rfg->rfapi_import_table) + rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); + rfg->rfapi_import_table = + rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); + + if (is_export_bgp) + vnc_direct_bgp_add_group(bgp, rfg); + + if (is_export_zebra) + vnc_zebra_add_group(bgp, rfg); + + return CMD_SUCCESS; +} + +DEFUN (vnc_vrf_policy_rt_export, + vnc_vrf_policy_rt_export_cmd, + "rt export RTLIST...", + "Specify route targets\n" + "Export filter\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + int rc; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return rc; +} + +DEFUN (vnc_vrf_policy_rt_both, + vnc_vrf_policy_rt_both_cmd, + "rt both RTLIST...", + "Specify route targets\n" + "Export+import filters\n" + "Space separated route target list (A.B.C.D:MN|EF:OPQR|GHJK:MN)\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + int rc; + int is_export_bgp = 0; + int is_export_zebra = 0; + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_import_list); + if (rc != CMD_SUCCESS) + return rc; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_bgp = 1; + break; + } + } + + if (is_export_bgp) + vnc_direct_bgp_del_group(bgp, rfg); + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + is_export_zebra = 1; + break; + } + } + + if (is_export_zebra) { + vnc_zlog_debug_verbose("%s: is_export_zebra", __func__); + vnc_zebra_del_group(bgp, rfg); + } + + /* + * stop referencing old import table, now reference new one + */ + if (rfg->rfapi_import_table) + rfapiImportTableRefDelByIt(bgp, rfg->rfapi_import_table); + rfg->rfapi_import_table = + rfapiImportTableRefAdd(bgp, rfg->rt_import_list, rfg); + + if (is_export_bgp) + vnc_direct_bgp_add_group(bgp, rfg); + + if (is_export_zebra) + vnc_zebra_add_group(bgp, rfg); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rc = set_ecom_list(vty, argc - 2, argv + 2, &rfg->rt_export_list); + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + + return rc; +} + +DEFUN (vnc_vrf_policy_rd, + vnc_vrf_policy_rd_cmd, + "rd ASN:NN_OR_IP-ADDRESS:NN", + "Specify default VRF route distinguisher\n" + "Route Distinguisher (: | : | auto:nh: )\n") +{ + int ret; + struct prefix_rd prd; + VTY_DECLVAR_CONTEXT_SUB(rfapi_nve_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!strncmp(argv[1]->arg, "auto:nh:", 8)) { + /* + * use AF_UNIX to designate automatically-assigned RD + * auto:vn:nn where nn is a 2-octet quantity + */ + char *end = NULL; + uint32_t value32 = strtoul(argv[1]->arg + 8, &end, 10); + uint16_t value = value32 & 0xffff; + + if (!*(argv[1]->arg + 5) || *end) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (value32 > 0xffff) { + vty_out(vty, "%% Malformed rd (must be less than %u\n", + 0x0ffff); + return CMD_WARNING_CONFIG_FAILED; + } + + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNIX; + prd.prefixlen = 64; + prd.val[0] = (RD_TYPE_IP >> 8) & 0x0ff; + prd.val[1] = RD_TYPE_IP & 0x0ff; + prd.val[6] = (value >> 8) & 0x0ff; + prd.val[7] = value & 0x0ff; + + } else { + + /* TODO: save RD format */ + ret = str2prefix_rd(argv[1]->arg, &prd); + if (!ret) { + vty_out(vty, "%% Malformed rd\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_prechange(bgp); + } + + rfg->rd = prd; + + if (bgp->rfapi_cfg->rfg_redist == rfg) { + vnc_redistribute_postchange(bgp); + } + return CMD_SUCCESS; +} + +DEFUN_NOSH (exit_vrf_policy, + exit_vrf_policy_cmd, + "exit-vrf-policy", + "Exit VRF policy configuration mode\n") +{ + if (vty->node == BGP_VRF_POLICY_NODE) { + vty->node = BGP_NODE; + } + return CMD_SUCCESS; +} + +static struct cmd_node bgp_vrf_policy_node = { + .name = "bgp vrf policy", + .node = BGP_VRF_POLICY_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-vrf-policy)# ", +}; + +/*------------------------------------------------------------------------- + * vnc-l2-group + *-----------------------------------------------------------------------*/ + + +DEFUN_NOSH (vnc_l2_group, + vnc_l2_group_cmd, + "vnc l2-group NAME", + VNC_CONFIG_STR "Configure a L2 group\n" "Group name\n") +{ + struct rfapi_l2_group_cfg *rfg; + VTY_DECLVAR_CONTEXT(bgp, bgp); + VNC_VTY_CONFIG_CHECK(bgp); + + /* Search for name */ + rfg = rfapi_l2_group_lookup_byname(bgp, argv[2]->arg); + + if (!rfg) { + rfg = rfapi_l2_group_new(); + if (!rfg) { + /* Error out of memory */ + vty_out(vty, "Can't allocate memory for L2 group\n"); + return CMD_WARNING_CONFIG_FAILED; + } + rfg->name = XSTRDUP(MTYPE_RFAPI_GROUP_CFG, argv[2]->arg); + /* add to tail of list */ + listnode_add(bgp->rfapi_cfg->l2_groups, rfg); + } + + /* + * XXX subsequent calls will need to make sure this item is still + * in the linked list and has the same name + */ + VTY_PUSH_CONTEXT_SUB(BGP_VNC_L2_GROUP_NODE, rfg); + return CMD_SUCCESS; +} + +static void bgp_rfapi_delete_l2_group(struct vty *vty, /* NULL = no output */ + struct bgp *bgp, + struct rfapi_l2_group_cfg *rfg) +{ + /* delete it */ + XFREE(MTYPE_RFAPI_GROUP_CFG, rfg->name); + if (rfg->rt_import_list) + ecommunity_free(&rfg->rt_import_list); + if (rfg->rt_export_list) + ecommunity_free(&rfg->rt_export_list); + if (rfg->labels) + list_delete(&rfg->labels); + XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, rfg->rfp_cfg); + listnode_delete(bgp->rfapi_cfg->l2_groups, rfg); + + rfapi_l2_group_del(rfg); +} + +static int +bgp_rfapi_delete_named_l2_group(struct vty *vty, /* NULL = no output */ + struct bgp *bgp, + const char *rfg_name) /* NULL = any */ +{ + struct rfapi_l2_group_cfg *rfg = NULL; + struct listnode *node, *nnode; + + /* Search for name */ + if (rfg_name) { + rfg = rfapi_l2_group_lookup_byname(bgp, rfg_name); + if (!rfg) { + if (vty) + vty_out(vty, "No L2 group named \"%s\"\n", + rfg_name); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (rfg) + bgp_rfapi_delete_l2_group(vty, bgp, rfg); + else /* must be delete all */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->l2_groups, node, nnode, + rfg)) + bgp_rfapi_delete_l2_group(vty, bgp, rfg); + return CMD_SUCCESS; +} + +DEFUN (vnc_no_l2_group, + vnc_no_l2_group_cmd, + "no vnc l2-group NAME", + NO_STR + VNC_CONFIG_STR + "Configure a L2 group\n" + "Group name\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + return bgp_rfapi_delete_named_l2_group(vty, bgp, argv[3]->arg); +} + + +DEFUN (vnc_l2_group_lni, + vnc_l2_group_lni_cmd, + "logical-network-id (0-4294967295)", + "Specify Logical Network ID associated with group\n" + "value\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current L2 group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rfg->logical_net_id = strtoul(argv[1]->arg, NULL, 10); + + return CMD_SUCCESS; +} + +DEFUN (vnc_l2_group_labels, + vnc_l2_group_labels_cmd, + "labels (0-1048575)...", + "Specify label values associated with group\n" + "Space separated list of label values <0-1048575>\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct list *ll; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current L2 group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ll = rfg->labels; + if (ll == NULL) { + ll = list_new(); + rfg->labels = ll; + } + argc--; + argv++; + for (; argc; --argc, ++argv) { + uint32_t label; + label = strtoul(argv[0]->arg, NULL, 10); + if (!listnode_lookup(ll, (void *)(uintptr_t)label)) + listnode_add(ll, (void *)(uintptr_t)label); + } + + return CMD_SUCCESS; +} + +DEFUN (vnc_l2_group_no_labels, + vnc_l2_group_no_labels_cmd, + "no labels (0-1048575)...", + NO_STR + "Specify label values associated with L2 group\n" + "Space separated list of label values <0-1048575>\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + struct list *ll; + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current L2 group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ll = rfg->labels; + if (ll == NULL) { + vty_out(vty, "Label no longer associated with group\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + argc -= 2; + argv += 2; + for (; argc; --argc, ++argv) { + uint32_t label; + label = strtoul(argv[0]->arg, NULL, 10); + listnode_delete(ll, (void *)(uintptr_t)label); + } + + return CMD_SUCCESS; +} + +DEFUN (vnc_l2_group_rt, + vnc_l2_group_rt_cmd, + "rt ASN:NN_OR_IP-ADDRESS:NN", + "Specify route targets\n" + "Export+import filters\n" + "Export filters\n" + "Import filters\n" + "A route target\n") +{ + VTY_DECLVAR_CONTEXT_SUB(rfapi_l2_group_cfg, rfg); + VTY_DECLVAR_CONTEXT(bgp, bgp); + int rc = CMD_SUCCESS; + int do_import = 0; + int do_export = 0; + + switch (argv[1]->arg[0]) { + case 'b': + do_export = 1; + do_import = 1; + break; + case 'i': + do_import = 1; + break; + case 'e': + do_export = 1; + break; + default: + vty_out(vty, "Unknown option, %s\n", argv[1]->arg); + return CMD_ERR_NO_MATCH; + } + + /* make sure it's still in list */ + if (!listnode_lookup(bgp->rfapi_cfg->l2_groups, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current L2 group no longer exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (do_import) + rc = set_ecom_list(vty, argc - 2, argv + 2, + &rfg->rt_import_list); + if (rc == CMD_SUCCESS && do_export) + rc = set_ecom_list(vty, argc - 2, argv + 2, + &rfg->rt_export_list); + return rc; +} + + +static struct cmd_node bgp_vnc_l2_group_node = { + .name = "bgp vnc l2", + .node = BGP_VNC_L2_GROUP_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-vnc-l2-group)# ", +}; + +struct rfapi_l2_group_cfg * +bgp_rfapi_get_group_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, + uint32_t label) +{ + struct rfapi_l2_group_cfg *rfg; + struct listnode *node; + + if (bgp->rfapi_cfg->l2_groups == NULL) /* not the best place for this */ + return NULL; + + label = label & 0xfffff; /* label is 20 bits! */ + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->l2_groups, node, rfg)) { + if (rfg->logical_net_id == logical_net_id) { + struct listnode *lnode; + void *data; + for (ALL_LIST_ELEMENTS_RO(rfg->labels, lnode, data)) + if (((uint32_t)((uintptr_t)data)) + == label) { /* match! */ + return rfg; + } + } + } + return NULL; +} + +struct list *bgp_rfapi_get_labellist_by_lni_label(struct bgp *bgp, + uint32_t logical_net_id, + uint32_t label) +{ + struct rfapi_l2_group_cfg *rfg; + rfg = bgp_rfapi_get_group_by_lni_label(bgp, logical_net_id, label); + if (rfg) { + return rfg->labels; + } + return NULL; +} + +struct ecommunity * +bgp_rfapi_get_ecommunity_by_lni_label(struct bgp *bgp, uint32_t is_import, + uint32_t logical_net_id, uint32_t label) +{ + struct rfapi_l2_group_cfg *rfg; + rfg = bgp_rfapi_get_group_by_lni_label(bgp, logical_net_id, label); + if (rfg) { + if (is_import) + return rfg->rt_import_list; + else + return rfg->rt_export_list; + } + return NULL; +} + +void bgp_rfapi_cfg_init(void) +{ + install_node(&bgp_vnc_defaults_node); + install_node(&bgp_vnc_nve_group_node); + install_node(&bgp_vrf_policy_node); + install_node(&bgp_vnc_l2_group_node); + install_default(BGP_VRF_POLICY_NODE); + install_default(BGP_VNC_DEFAULTS_NODE); + install_default(BGP_VNC_NVE_GROUP_NODE); + install_default(BGP_VNC_L2_GROUP_NODE); + + /* + * Add commands + */ + install_element(BGP_NODE, &vnc_defaults_cmd); + install_element(BGP_NODE, &vnc_nve_group_cmd); + install_element(BGP_NODE, &vnc_no_nve_group_cmd); + install_element(BGP_NODE, &vnc_vrf_policy_cmd); + install_element(BGP_NODE, &vnc_no_vrf_policy_cmd); + install_element(BGP_NODE, &vnc_l2_group_cmd); + install_element(BGP_NODE, &vnc_no_l2_group_cmd); + install_element(BGP_NODE, &vnc_advertise_un_method_cmd); + install_element(BGP_NODE, &vnc_export_mode_cmd); + + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_import_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_export_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rt_both_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_rd_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_l2rd_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &vnc_defaults_no_l2rd_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, + &vnc_defaults_responselifetime_cmd); + install_element(BGP_VNC_DEFAULTS_NODE, &exit_vnc_cmd); + + install_element(BGP_NODE, &vnc_redistribute_protocol_cmd); + install_element(BGP_NODE, &vnc_no_redistribute_protocol_cmd); + install_element(BGP_NODE, &vnc_redistribute_nvegroup_cmd); + install_element(BGP_NODE, &vnc_redistribute_no_nvegroup_cmd); + install_element(BGP_NODE, &vnc_redistribute_lifetime_cmd); + install_element(BGP_NODE, &vnc_redistribute_rh_roo_localadmin_cmd); + install_element(BGP_NODE, &vnc_redistribute_mode_cmd); + install_element(BGP_NODE, &vnc_redistribute_bgp_exterior_cmd); + + install_element(BGP_NODE, &vnc_redist_bgpdirect_no_prefixlist_cmd); + install_element(BGP_NODE, &vnc_redist_bgpdirect_prefixlist_cmd); + install_element(BGP_NODE, &vnc_redist_bgpdirect_no_routemap_cmd); + install_element(BGP_NODE, &vnc_redist_bgpdirect_routemap_cmd); + + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_redist_bgpdirect_no_prefixlist_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_redist_bgpdirect_prefixlist_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_redist_bgpdirect_no_routemap_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_redist_bgpdirect_routemap_cmd); + + install_element(BGP_NODE, &vnc_export_nvegroup_cmd); + install_element(BGP_NODE, &vnc_no_export_nvegroup_cmd); + install_element(BGP_NODE, &vnc_nve_export_prefixlist_cmd); + install_element(BGP_NODE, &vnc_nve_export_routemap_cmd); + install_element(BGP_NODE, &vnc_nve_export_no_prefixlist_cmd); + install_element(BGP_NODE, &vnc_nve_export_no_routemap_cmd); + + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_l2rd_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_no_l2rd_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_prefix_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_import_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_export_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rt_both_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &vnc_nve_group_rd_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_responselifetime_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_export_prefixlist_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_export_routemap_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_export_no_prefixlist_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, + &vnc_nve_group_export_no_routemap_cmd); + install_element(BGP_VNC_NVE_GROUP_NODE, &exit_vnc_cmd); + + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_label_cmd); + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_no_label_cmd); + // Reenable to support VRF controller use case and testing + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_nexthop_cmd); + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_import_cmd); + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_export_cmd); + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rt_both_cmd); + install_element(BGP_VRF_POLICY_NODE, &vnc_vrf_policy_rd_cmd); + install_element(BGP_VRF_POLICY_NODE, + &vnc_vrf_policy_export_prefixlist_cmd); + install_element(BGP_VRF_POLICY_NODE, + &vnc_vrf_policy_export_routemap_cmd); + install_element(BGP_VRF_POLICY_NODE, + &vnc_vrf_policy_export_no_prefixlist_cmd); + install_element(BGP_VRF_POLICY_NODE, + &vnc_vrf_policy_export_no_routemap_cmd); + install_element(BGP_VRF_POLICY_NODE, &exit_vrf_policy_cmd); + + install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_lni_cmd); + install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_labels_cmd); + install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_no_labels_cmd); + install_element(BGP_VNC_L2_GROUP_NODE, &vnc_l2_group_rt_cmd); + install_element(BGP_VNC_L2_GROUP_NODE, &exit_vnc_cmd); +} + +struct rfapi_cfg *bgp_rfapi_cfg_new(struct rfapi_rfp_cfg *cfg) +{ + struct rfapi_cfg *h; + afi_t afi; + + h = XCALLOC(MTYPE_RFAPI_CFG, sizeof(struct rfapi_cfg)); + assert(h); + + h->nve_groups_sequential = list_new(); + assert(h->nve_groups_sequential); + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + h->nve_groups_vn[afi] = agg_table_init(); + h->nve_groups_un[afi] = agg_table_init(); + } + h->default_response_lifetime = + BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT; + h->rfg_export_direct_bgp_l = list_new(); + h->rfg_export_zebra_l = list_new(); + h->resolve_nve_roo_local_admin = + BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT; + + SET_FLAG(h->flags, BGP_VNC_CONFIG_FLAGS_DEFAULT); + + if (cfg == NULL) { + h->rfp_cfg.download_type = RFAPI_RFP_DOWNLOAD_PARTIAL; + h->rfp_cfg.ftd_advertisement_interval = + RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL; + h->rfp_cfg.holddown_factor = + RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR; + h->rfp_cfg.use_updated_response = 0; + h->rfp_cfg.use_removes = 0; + } else { + h->rfp_cfg.download_type = cfg->download_type; + h->rfp_cfg.ftd_advertisement_interval = + cfg->ftd_advertisement_interval; + h->rfp_cfg.holddown_factor = cfg->holddown_factor; + h->rfp_cfg.use_updated_response = cfg->use_updated_response; + h->rfp_cfg.use_removes = cfg->use_removes; + if (cfg->use_updated_response) + h->flags &= ~BGP_VNC_CONFIG_CALLBACK_DISABLE; + else + h->flags |= BGP_VNC_CONFIG_CALLBACK_DISABLE; + if (cfg->use_removes) + h->flags &= ~BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; + else + h->flags |= BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; + } + return h; +} + +static void bgp_rfapi_rfgn_list_delete(void *data) +{ + struct rfapi_rfg_name *rfgn = data; + + XFREE(MTYPE_RFAPI_GROUP_CFG, rfgn->name); + rfgn_free(rfgn); +} + +void bgp_rfapi_cfg_destroy(struct bgp *bgp, struct rfapi_cfg *h) +{ + afi_t afi; + if (h == NULL) + return; + + bgp_rfapi_delete_named_nve_group(NULL, bgp, NULL, RFAPI_GROUP_CFG_MAX); + bgp_rfapi_delete_named_l2_group(NULL, bgp, NULL); + if (h->l2_groups != NULL) + list_delete(&h->l2_groups); + list_delete(&h->nve_groups_sequential); + + h->rfg_export_direct_bgp_l->del = bgp_rfapi_rfgn_list_delete; + list_delete(&h->rfg_export_direct_bgp_l); + + h->rfg_export_zebra_l->del = bgp_rfapi_rfgn_list_delete; + list_delete(&h->rfg_export_zebra_l); + + if (h->default_rt_export_list) + ecommunity_free(&h->default_rt_export_list); + if (h->default_rt_import_list) + ecommunity_free(&h->default_rt_import_list); + XFREE(MTYPE_RFAPI_RFP_GROUP_CFG, h->default_rfp_cfg); + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + agg_table_finish(h->nve_groups_vn[afi]); + agg_table_finish(h->nve_groups_un[afi]); + } + XFREE(MTYPE_RFAPI_CFG, h); +} + +int bgp_rfapi_cfg_write(struct vty *vty, struct bgp *bgp) +{ + struct listnode *node, *nnode; + struct rfapi_nve_group_cfg *rfg; + struct rfapi_cfg *hc = bgp->rfapi_cfg; + struct rfapi_rfg_name *rfgn; + int write = 0; + afi_t afi; + int type; + if (bgp->rfapi == NULL || hc == NULL) + return write; + + vty_out(vty, "!\n"); + for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, rfg)) + if (rfg->type == RFAPI_GROUP_CFG_VRF) { + ++write; + vty_out(vty, " vrf-policy %s\n", rfg->name); + if (rfg->label <= MPLS_LABEL_MAX) { + vty_out(vty, " label %u\n", rfg->label); + } + if (CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { + vty_out(vty, " nexthop self\n"); + + } else { + if (rfg->vn_prefix.family) { + char buf[BUFSIZ]; + buf[0] = buf[BUFSIZ - 1] = 0; + inet_ntop(rfg->vn_prefix.family, + &rfg->vn_prefix.u.prefix, buf, + sizeof(buf)); + if (!buf[0] || buf[BUFSIZ - 1]) { + // vty_out (vty, "nexthop + // self\n"); + } else { + vty_out(vty, " nexthop %s\n", + buf); + } + } + } + + if (rfg->rd.prefixlen) { + if (AF_UNIX == rfg->rd.family) { + + uint16_t value = 0; + + value = ((rfg->rd.val[6] << 8) + & 0x0ff00) + | (rfg->rd.val[7] & 0x0ff); + + vty_out(vty, " rd auto:nh:%d\n", + value); + + } else + vty_out(vty, " rd %pRDP\n", &rfg->rd); + } + + if (rfg->rt_import_list && rfg->rt_export_list + && ecommunity_cmp(rfg->rt_import_list, + rfg->rt_export_list)) { + char *b = ecommunity_ecom2str( + rfg->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt both %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } else { + if (rfg->rt_import_list) { + char *b = ecommunity_ecom2str( + rfg->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt import %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + if (rfg->rt_export_list) { + char *b = ecommunity_ecom2str( + rfg->rt_export_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt export %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + } + + /* + * route filtering: prefix-lists and route-maps + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + const char *afistr = + (afi == AFI_IP) ? "ipv4" : "ipv6"; + + if (rfg->plist_export_bgp_name[afi]) { + vty_out(vty, + " export %s%s prefix-list %s\n", + (rfg->type == RFAPI_GROUP_CFG_VRF + ? "" + : "bgp "), + afistr, + rfg->plist_export_bgp_name + [afi]); + } + if (rfg->plist_export_zebra_name[afi]) { + vty_out(vty, + " export %s%s prefix-list %s\n", + (rfg->type == RFAPI_GROUP_CFG_VRF + ? "" + : "zebra "), + afistr, + rfg->plist_export_zebra_name + [afi]); + } + /* + * currently we only support redist plists for + * bgp-direct. + * If we later add plist support for + * redistributing other + * protocols, we'll need to loop over protocols + * here + */ + if (rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT][afi]) { + vty_out(vty, + " redistribute bgp-direct %s prefix-list %s\n", + afistr, + rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT] + [afi]); + } + if (rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT][afi]) { + vty_out(vty, + " redistribute bgp-direct-to-nve-groups %s prefix-list %s\n", + afistr, + rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT] + [afi]); + } + } + + if (rfg->routemap_export_bgp_name) { + vty_out(vty, " export %sroute-map %s\n", + (rfg->type == RFAPI_GROUP_CFG_VRF + ? "" + : "bgp "), + rfg->routemap_export_bgp_name); + } + if (rfg->routemap_export_zebra_name) { + vty_out(vty, " export %sroute-map %s\n", + (rfg->type == RFAPI_GROUP_CFG_VRF + ? "" + : "zebra "), + rfg->routemap_export_zebra_name); + } + if (rfg->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) { + vty_out(vty, + " redistribute bgp-direct route-map %s\n", + rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT]); + } + if (rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vty_out(vty, + " redistribute bgp-direct-to-nve-groups route-map %s\n", + rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT]); + } + vty_out(vty, " exit-vrf-policy\n"); + vty_out(vty, "!\n"); + } + if (hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP) { + vty_out(vty, " vnc advertise-un-method encap-safi\n"); + write++; + } + + { /* was based on listen ports */ + /* for now allow both old and new */ + if (bgp->rfapi->rfp_methods.cfg_cb) + write += (bgp->rfapi->rfp_methods.cfg_cb)( + vty, bgp->rfapi->rfp); + + if (write) + vty_out(vty, "!\n"); + + if (hc->l2_groups) { + struct rfapi_l2_group_cfg *rfgc = NULL; + struct listnode *gnode; + for (ALL_LIST_ELEMENTS_RO(hc->l2_groups, gnode, rfgc)) { + struct listnode *lnode; + void *data; + ++write; + vty_out(vty, " vnc l2-group %s\n", rfgc->name); + if (rfgc->logical_net_id != 0) + vty_out(vty, + " logical-network-id %u\n", + rfgc->logical_net_id); + if (rfgc->labels != NULL + && listhead(rfgc->labels) != NULL) { + vty_out(vty, " labels "); + for (ALL_LIST_ELEMENTS_RO(rfgc->labels, + lnode, + data)) { + vty_out(vty, "%hu ", + (uint16_t)( + (uintptr_t) + data)); + } + vty_out(vty, "\n"); + } + + if (rfgc->rt_import_list && rfgc->rt_export_list + && ecommunity_cmp(rfgc->rt_import_list, + rfgc->rt_export_list)) { + char *b = ecommunity_ecom2str( + rfgc->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt both %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } else { + if (rfgc->rt_import_list) { + char *b = ecommunity_ecom2str( + rfgc->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt import %s\n", + b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + if (rfgc->rt_export_list) { + char *b = ecommunity_ecom2str( + rfgc->rt_export_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt export %s\n", + b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + } + if (bgp->rfapi->rfp_methods.cfg_group_cb) + write += (bgp->rfapi->rfp_methods + .cfg_group_cb)( + vty, bgp->rfapi->rfp, + RFAPI_RFP_CFG_GROUP_L2, + rfgc->name, rfgc->rfp_cfg); + vty_out(vty, " exit-vnc\n"); + vty_out(vty, "!\n"); + } + } + + if (hc->default_rd.prefixlen + || hc->default_response_lifetime + != BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT + || hc->default_rt_import_list || hc->default_rt_export_list + || hc->nve_groups_sequential->count) { + + + ++write; + vty_out(vty, " vnc defaults\n"); + + if (hc->default_rd.prefixlen) { + if (AF_UNIX == hc->default_rd.family) { + uint16_t value = 0; + + value = ((hc->default_rd.val[6] << 8) + & 0x0ff00) + | (hc->default_rd.val[7] + & 0x0ff); + + vty_out(vty, " rd auto:vn:%d\n", + value); + + } else + vty_out(vty, " rd %pRDP\n", + &hc->default_rd); + } + if (hc->default_response_lifetime + != BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT) { + vty_out(vty, " response-lifetime "); + if (hc->default_response_lifetime != UINT32_MAX) + vty_out(vty, "%d", + hc->default_response_lifetime); + else + vty_out(vty, "infinite"); + vty_out(vty, "\n"); + } + if (hc->default_rt_import_list + && hc->default_rt_export_list + && ecommunity_cmp(hc->default_rt_import_list, + hc->default_rt_export_list)) { + char *b = ecommunity_ecom2str( + hc->default_rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt both %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } else { + if (hc->default_rt_import_list) { + char *b = ecommunity_ecom2str( + hc->default_rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt import %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + if (hc->default_rt_export_list) { + char *b = ecommunity_ecom2str( + hc->default_rt_export_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt export %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + } + if (bgp->rfapi->rfp_methods.cfg_group_cb) + write += (bgp->rfapi->rfp_methods.cfg_group_cb)( + vty, bgp->rfapi->rfp, + RFAPI_RFP_CFG_GROUP_DEFAULT, NULL, + bgp->rfapi_cfg->default_rfp_cfg); + vty_out(vty, " exit-vnc\n"); + vty_out(vty, "!\n"); + } + + for (ALL_LIST_ELEMENTS(hc->nve_groups_sequential, node, nnode, + rfg)) + if (rfg->type == RFAPI_GROUP_CFG_NVE) { + ++write; + vty_out(vty, " vnc nve-group %s\n", rfg->name); + + if (rfg->vn_prefix.family && rfg->vn_node) + vty_out(vty, " prefix %s %pFX\n", "vn", + &rfg->vn_prefix); + + if (rfg->un_prefix.family && rfg->un_node) + vty_out(vty, " prefix %s %pFX\n", "un", + &rfg->un_prefix); + + + if (rfg->rd.prefixlen) { + if (AF_UNIX == rfg->rd.family) { + + uint16_t value = 0; + + value = ((rfg->rd.val[6] << 8) + & 0x0ff00) + | (rfg->rd.val[7] + & 0x0ff); + + vty_out(vty, + " rd auto:vn:%d\n", + value); + + } else + vty_out(vty, " rd %pRDP\n", + &rfg->rd); + } + if (rfg->flags & RFAPI_RFG_RESPONSE_LIFETIME) { + vty_out(vty, " response-lifetime "); + if (rfg->response_lifetime + != UINT32_MAX) + vty_out(vty, "%d", + rfg->response_lifetime); + else + vty_out(vty, "infinite"); + vty_out(vty, "\n"); + } + + if (rfg->rt_import_list && rfg->rt_export_list + && ecommunity_cmp(rfg->rt_import_list, + rfg->rt_export_list)) { + char *b = ecommunity_ecom2str( + rfg->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt both %s\n", b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } else { + if (rfg->rt_import_list) { + char *b = ecommunity_ecom2str( + rfg->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt import %s\n", + b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + if (rfg->rt_export_list) { + char *b = ecommunity_ecom2str( + rfg->rt_export_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, + ECOMMUNITY_ROUTE_TARGET); + vty_out(vty, " rt export %s\n", + b); + XFREE(MTYPE_ECOMMUNITY_STR, b); + } + } + + /* + * route filtering: prefix-lists and route-maps + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + const char *afistr = (afi == AFI_IP) + ? "ipv4" + : "ipv6"; + + if (rfg->plist_export_bgp_name[afi]) { + vty_out(vty, + " export bgp %s prefix-list %s\n", + afistr, + rfg->plist_export_bgp_name + [afi]); + } + if (rfg->plist_export_zebra_name[afi]) { + vty_out(vty, + " export zebra %s prefix-list %s\n", + afistr, + rfg->plist_export_zebra_name + [afi]); + } + /* + * currently we only support redist + * plists for bgp-direct. + * If we later add plist support for + * redistributing other + * protocols, we'll need to loop over + * protocols here + */ + if (rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT] + [afi]) { + vty_out(vty, + " redistribute bgp-direct %s prefix-list %s\n", + afistr, + rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT] + [afi]); + } + if (rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT] + [afi]) { + vty_out(vty, + " redistribute bgp-direct-to-nve-groups %s prefix-list %s\n", + afistr, + rfg->plist_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT] + [afi]); + } + } + + if (rfg->routemap_export_bgp_name) { + vty_out(vty, + " export bgp route-map %s\n", + rfg->routemap_export_bgp_name); + } + if (rfg->routemap_export_zebra_name) { + vty_out(vty, + " export zebra route-map %s\n", + rfg->routemap_export_zebra_name); + } + if (rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT]) { + vty_out(vty, + " redistribute bgp-direct route-map %s\n", + rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT]); + } + if (rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vty_out(vty, + " redistribute bgp-direct-to-nve-groups route-map %s\n", + rfg->routemap_redist_name + [ZEBRA_ROUTE_BGP_DIRECT_EXT]); + } + if (bgp->rfapi->rfp_methods.cfg_group_cb) + write += (bgp->rfapi->rfp_methods + .cfg_group_cb)( + vty, bgp->rfapi->rfp, + RFAPI_RFP_CFG_GROUP_NVE, + rfg->name, rfg->rfp_cfg); + vty_out(vty, " exit-vnc\n"); + vty_out(vty, "!\n"); + } + } /* have listen ports */ + + /* + * route export to other protocols + */ + if (VNC_EXPORT_BGP_GRP_ENABLED(hc)) { + vty_out(vty, " vnc export bgp mode group-nve\n"); + } else if (VNC_EXPORT_BGP_RH_ENABLED(hc)) { + vty_out(vty, " vnc export bgp mode registering-nve\n"); + } else if (VNC_EXPORT_BGP_CE_ENABLED(hc)) { + vty_out(vty, " vnc export bgp mode ce\n"); + } + + if (VNC_EXPORT_ZEBRA_GRP_ENABLED(hc)) { + vty_out(vty, " vnc export zebra mode group-nve\n"); + } else if (VNC_EXPORT_ZEBRA_RH_ENABLED(hc)) { + vty_out(vty, " vnc export zebra mode registering-nve\n"); + } + + if (hc->rfg_export_direct_bgp_l) { + for (ALL_LIST_ELEMENTS(hc->rfg_export_direct_bgp_l, node, nnode, + rfgn)) { + + vty_out(vty, " vnc export bgp group-nve group %s\n", + rfgn->name); + } + } + + if (hc->rfg_export_zebra_l) { + for (ALL_LIST_ELEMENTS(hc->rfg_export_zebra_l, node, nnode, + rfgn)) { + + vty_out(vty, " vnc export zebra group-nve group %s\n", + rfgn->name); + } + } + + + if (hc->rfg_redist_name) { + vty_out(vty, " vnc redistribute nve-group %s\n", + hc->rfg_redist_name); + } + if (hc->redist_lifetime) { + vty_out(vty, " vnc redistribute lifetime %d\n", + hc->redist_lifetime); + } + if (hc->resolve_nve_roo_local_admin + != BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT) { + + vty_out(vty, + " vnc redistribute resolve-nve roo-ec-local-admin %d\n", + hc->resolve_nve_roo_local_admin); + } + + if (hc->redist_mode) /* ! default */ + { + const char *s = ""; + + switch (hc->redist_mode) { + case VNC_REDIST_MODE_PLAIN: + s = "plain"; + break; + case VNC_REDIST_MODE_RFG: + s = "nve-group"; + break; + case VNC_REDIST_MODE_RESOLVE_NVE: + s = "resolve-nve"; + break; + } + if (s) { + vty_out(vty, " vnc redistribute mode %s\n", s); + } + } + + /* + * route filtering: prefix-lists and route-maps + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + const char *afistr = (afi == AFI_IP) ? "ipv4" : "ipv6"; + + if (hc->plist_export_bgp_name[afi]) { + vty_out(vty, " vnc export bgp %s prefix-list %s\n", + afistr, hc->plist_export_bgp_name[afi]); + } + if (hc->plist_export_zebra_name[afi]) { + vty_out(vty, " vnc export zebra %s prefix-list %s\n", + afistr, hc->plist_export_zebra_name[afi]); + } + if (hc->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT][afi]) { + vty_out(vty, + " vnc redistribute bgp-direct %s prefix-list %s\n", + afistr, + hc->plist_redist_name[ZEBRA_ROUTE_BGP_DIRECT] + [afi]); + } + } + + if (hc->routemap_export_bgp_name) { + vty_out(vty, " vnc export bgp route-map %s\n", + hc->routemap_export_bgp_name); + } + if (hc->routemap_export_zebra_name) { + vty_out(vty, " vnc export zebra route-map %s\n", + hc->routemap_export_zebra_name); + } + if (hc->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]) { + vty_out(vty, " vnc redistribute bgp-direct route-map %s\n", + hc->routemap_redist_name[ZEBRA_ROUTE_BGP_DIRECT]); + } + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { + if (hc->redist[afi][type]) { + if (type == ZEBRA_ROUTE_BGP_DIRECT_EXT + && hc->redist_bgp_exterior_view_name) { + vty_out(vty, + " vnc redistribute %s %s view %s\n", + ((afi == AFI_IP) ? "ipv4" + : "ipv6"), + zebra_route_string(type), + hc->redist_bgp_exterior_view_name); + } else { + vty_out(vty, + " vnc redistribute %s %s\n", + ((afi == AFI_IP) ? "ipv4" + : "ipv6"), + zebra_route_string(type)); + } + } + } + } + return write; +} + +void bgp_rfapi_show_summary(struct bgp *bgp, struct vty *vty) +{ + struct rfapi_cfg *hc = bgp->rfapi_cfg; + afi_t afi; + int type, redist = 0; + char tmp[40]; + if (hc == NULL) + return; + + vty_out(vty, "%-39s %-19s %s\n", "VNC Advertise method:", + (hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP + ? "Encapsulation SAFI" + : "Tunnel Encap attribute"), + ((hc->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP) + == (BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP + & BGP_VNC_CONFIG_FLAGS_DEFAULT) + ? "(default)" + : "")); + /* export */ + vty_out(vty, "%-39s ", "Export from VNC:"); + /* + * route export to other protocols + */ + if (VNC_EXPORT_BGP_GRP_ENABLED(hc)) { + redist++; + vty_out(vty, "ToBGP Groups={"); + if (hc->rfg_export_direct_bgp_l) { + int cnt = 0; + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + for (ALL_LIST_ELEMENTS(hc->rfg_export_direct_bgp_l, + node, nnode, rfgn)) { + if (cnt++ != 0) + vty_out(vty, ","); + + vty_out(vty, "%s", rfgn->name); + } + } + vty_out(vty, "}"); + } else if (VNC_EXPORT_BGP_RH_ENABLED(hc)) { + redist++; + vty_out(vty, "ToBGP {Registering NVE}"); + /* note filters, route-maps not shown */ + } else if (VNC_EXPORT_BGP_CE_ENABLED(hc)) { + redist++; + vty_out(vty, "ToBGP {NVE connected router:%d}", + hc->resolve_nve_roo_local_admin); + /* note filters, route-maps not shown */ + } + + if (VNC_EXPORT_ZEBRA_GRP_ENABLED(hc)) { + redist++; + vty_out(vty, "%sToZebra Groups={", (redist == 1 ? "" : " ")); + if (hc->rfg_export_zebra_l) { + int cnt = 0; + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + for (ALL_LIST_ELEMENTS(hc->rfg_export_zebra_l, node, + nnode, rfgn)) { + if (cnt++ != 0) + vty_out(vty, ","); + vty_out(vty, "%s", rfgn->name); + } + } + vty_out(vty, "}"); + } else if (VNC_EXPORT_ZEBRA_RH_ENABLED(hc)) { + redist++; + vty_out(vty, "%sToZebra {Registering NVE}", + (redist == 1 ? "" : " ")); + /* note filters, route-maps not shown */ + } + vty_out(vty, "%-19s %s\n", (redist ? "" : "Off"), + (redist ? "" : "(default)")); + + /* Redistribution */ + redist = 0; + vty_out(vty, "%-39s ", "Redistribution into VNC:"); + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + for (type = 0; type < ZEBRA_ROUTE_MAX; ++type) { + if (hc->redist[afi][type]) { + vty_out(vty, "{%s,%s} ", + ((afi == AFI_IP) ? "ipv4" : "ipv6"), + zebra_route_string(type)); + redist++; + } + } + } + vty_out(vty, "%-19s %s\n", (redist ? "" : "Off"), + (redist ? "" : "(default)")); + + vty_out(vty, "%-39s %3u%-16s %s\n", + "RFP Registration Hold-Down Factor:", + hc->rfp_cfg.holddown_factor, "%", + (hc->rfp_cfg.holddown_factor + == RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR + ? "(default)" + : "")); + vty_out(vty, "%-39s %-19s %s\n", "RFP Updated responses:", + (hc->rfp_cfg.use_updated_response == 0 ? "Off" : "On"), + (hc->rfp_cfg.use_updated_response == 0 ? "(default)" : "")); + vty_out(vty, "%-39s %-19s %s\n", "RFP Removal responses:", + (hc->rfp_cfg.use_removes == 0 ? "Off" : "On"), + (hc->rfp_cfg.use_removes == 0 ? "(default)" : "")); + vty_out(vty, "%-39s %-19s %s\n", "RFP Full table download:", + (hc->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_FULL ? "On" + : "Off"), + (hc->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_PARTIAL + ? "(default)" + : "")); + snprintf(tmp, sizeof(tmp), "%u seconds", + hc->rfp_cfg.ftd_advertisement_interval); + vty_out(vty, "%-39s %-19s %s\n", " Advertisement Interval:", tmp, + (hc->rfp_cfg.ftd_advertisement_interval + == RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL + ? "(default)" + : "")); + vty_out(vty, "%-39s %d seconds\n", "Default RFP response lifetime:", + hc->default_response_lifetime); + vty_out(vty, "\n"); + return; +} + +struct rfapi_cfg *bgp_rfapi_get_config(struct bgp *bgp) +{ + struct rfapi_cfg *hc = NULL; + if (bgp == NULL) + bgp = bgp_get_default(); + if (bgp != NULL) + hc = bgp->rfapi_cfg; + return hc; +} + +#endif /* ENABLE_BGP_VNC */ diff --git a/bgpd/rfapi/bgp_rfapi_cfg.h b/bgpd/rfapi/bgp_rfapi_cfg.h new file mode 100644 index 0000000..77549ad --- /dev/null +++ b/bgpd/rfapi/bgp_rfapi_cfg.h @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_BGP_RFAPI_CFG_H +#define _QUAGGA_BGP_RFAPI_CFG_H + +#include "lib/table.h" +#include "lib/routemap.h" + +#ifdef ENABLE_BGP_VNC +#include "rfapi.h" + +struct rfapi_l2_group_cfg { + char *name; + uint32_t logical_net_id; + struct list *labels; /* list of uint32_t */ + struct ecommunity *rt_import_list; + struct ecommunity *rt_export_list; + void *rfp_cfg; /* rfp owned group config */ + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(rfapi_l2_group_cfg); + +typedef enum { + RFAPI_GROUP_CFG_NVE = 1, + RFAPI_GROUP_CFG_VRF, + RFAPI_GROUP_CFG_L2, + RFAPI_GROUP_CFG_MAX +} rfapi_group_cfg_type_t; + +struct rfapi_nve_group_cfg { + struct agg_node *vn_node; /* backref */ + struct agg_node *un_node; /* backref */ + + rfapi_group_cfg_type_t type; /* NVE|VPN */ + char *name; /* unique by type! */ + struct prefix vn_prefix; + struct prefix un_prefix; + + struct prefix_rd rd; + uint8_t l2rd; /* 0 = VN addr LSB */ + uint32_t response_lifetime; + uint32_t flags; +#define RFAPI_RFG_RESPONSE_LIFETIME 0x01 /* bits */ +#define RFAPI_RFG_L2RD 0x02 +#define RFAPI_RFG_VPN_NH_SELF 0x04 + struct ecommunity *rt_import_list; + struct ecommunity *rt_export_list; + struct rfapi_import_table *rfapi_import_table; + + void *rfp_cfg; /* rfp owned group config */ + /* + * List of NVE descriptors that are assigned to this NVE group + * + * Currently (Mar 2010) this list is used only by the route + * export code to generate per-NVE nexthops for each route. + * + * The nve descriptors listed here have pointers back to + * this nve group config structure to enable them to delete + * their own list entries when they are closed. Consequently, + * if an instance of this nve group config structure is deleted, + * we must first set the nve descriptor references to it to NULL. + */ + struct list *nves; + + /* + * Route filtering + * + * Prefix lists are segregated by afi (part of the base plist code) + * Route-maps are not segregated + */ + char *plist_export_bgp_name[AFI_MAX]; + struct prefix_list *plist_export_bgp[AFI_MAX]; + + char *plist_export_zebra_name[AFI_MAX]; + struct prefix_list *plist_export_zebra[AFI_MAX]; + + char *plist_redist_name[ZEBRA_ROUTE_MAX][AFI_MAX]; + struct prefix_list *plist_redist[ZEBRA_ROUTE_MAX][AFI_MAX]; + + char *routemap_export_bgp_name; + struct route_map *routemap_export_bgp; + + char *routemap_export_zebra_name; + struct route_map *routemap_export_zebra; + + char *routemap_redist_name[ZEBRA_ROUTE_MAX]; + struct route_map *routemap_redist[ZEBRA_ROUTE_MAX]; + + /* for VRF type groups */ + uint32_t label; + struct rfapi_descriptor *rfd; + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(rfapi_nve_group_cfg); + +struct rfapi_rfg_name { + struct rfapi_nve_group_cfg *rfg; + char *name; +}; + +typedef enum { + VNC_REDIST_MODE_PLAIN = 0, /* 0 = default */ + VNC_REDIST_MODE_RFG, + VNC_REDIST_MODE_RESOLVE_NVE +} vnc_redist_mode_t; + +struct rfapi_cfg { + struct prefix_rd default_rd; + uint8_t default_l2rd; + struct ecommunity *default_rt_import_list; + struct ecommunity *default_rt_export_list; + uint32_t default_response_lifetime; +#define BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT 3600 + void *default_rfp_cfg; /* rfp owned group config */ + + struct list *l2_groups; /* rfapi_l2_group_cfg list */ + /* three views into the same collection of rfapi_nve_group_cfg */ + struct list *nve_groups_sequential; + struct agg_table *nve_groups_vn[AFI_MAX]; + struct agg_table *nve_groups_un[AFI_MAX]; + + /* + * For Single VRF export to ordinary routing protocols. This is + * the nve-group that the ordinary protocols belong to. We use it + * to set the RD when sending unicast Zebra routes to VNC + */ + uint8_t redist[AFI_MAX][ZEBRA_ROUTE_MAX]; + uint32_t redist_lifetime; + vnc_redist_mode_t redist_mode; + + /* + * view name of BGP unicast instance that holds + * exterior routes + */ + char *redist_bgp_exterior_view_name; + struct bgp *redist_bgp_exterior_view; + + /* + * nve group for redistribution of routes from zebra to VNC + * (which is probably not useful for production networks) + */ + char *rfg_redist_name; + struct rfapi_nve_group_cfg *rfg_redist; + + /* + * List of NVE groups on whose behalf we will export VNC + * routes to zebra. ((NB: it's actually a list of ) + * This list is used when BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS is + * BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP + */ + struct list *rfg_export_zebra_l; + + /* + * List of NVE groups on whose behalf we will export VNC + * routes directly to the bgp unicast RIB. (NB: it's actually + * a list of ) + * This list is used when BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS is + * BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP + */ + struct list *rfg_export_direct_bgp_l; + + /* + * Exported Route filtering + * + * Prefix lists are segregated by afi (part of the base plist code) + * Route-maps are not segregated + */ + char *plist_export_bgp_name[AFI_MAX]; + struct prefix_list *plist_export_bgp[AFI_MAX]; + + char *plist_export_zebra_name[AFI_MAX]; + struct prefix_list *plist_export_zebra[AFI_MAX]; + + char *routemap_export_bgp_name; + struct route_map *routemap_export_bgp; + + char *routemap_export_zebra_name; + struct route_map *routemap_export_zebra; + + /* + * Redistributed route filtering (routes from other + * protocols into VNC) + */ + char *plist_redist_name[ZEBRA_ROUTE_MAX][AFI_MAX]; + struct prefix_list *plist_redist[ZEBRA_ROUTE_MAX][AFI_MAX]; + + char *routemap_redist_name[ZEBRA_ROUTE_MAX]; + struct route_map *routemap_redist[ZEBRA_ROUTE_MAX]; + + /* + * For importing bgp unicast routes to VNC, we encode the CE + * (route nexthop) in a Route Origin extended community. The + * local part (16-bit) is user-configurable. + */ + uint16_t resolve_nve_roo_local_admin; +#define BGP_VNC_CONFIG_RESOLVE_NVE_ROO_LOCAL_ADMIN_DEFAULT 5226 + + uint32_t flags; +#define BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP 0x00000001 +#define BGP_VNC_CONFIG_CALLBACK_DISABLE 0x00000002 +#define BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE 0x00000004 + +#define BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS 0x000000f0 +#define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS 0x00000f00 + +#define BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE 0x00000000 +#define BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP 0x00000010 +#define BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH 0x00000020 /* registerd nve */ +#define BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE 0x00000040 + +#define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_NONE 0x00000000 +#define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP 0x00000100 +#define BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_RH 0x00000200 + +#define BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP 0x00001000 +#define BGP_VNC_CONFIG_L2RD 0x00002000 + +/* Use new NVE RIB to filter callback routes */ +/* Filter querying NVE's registrations from responses */ +/* Default to updated-responses off */ +/* Default to removal-responses off */ +#define BGP_VNC_CONFIG_FLAGS_DEFAULT \ + (BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP | BGP_VNC_CONFIG_CALLBACK_DISABLE \ + | BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE) + + struct rfapi_rfp_cfg rfp_cfg; /* rfp related configuration */ +}; + +#define VNC_EXPORT_ZEBRA_GRP_ENABLED(hc) \ + (((hc)->flags & BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS) \ + == BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_GRP) + +#define VNC_EXPORT_ZEBRA_RH_ENABLED(hc) \ + (((hc)->flags & BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_BITS) \ + == BGP_VNC_CONFIG_EXPORT_ZEBRA_MODE_RH) + +#define VNC_EXPORT_BGP_GRP_ENABLED(hc) \ + (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ + == BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP) + +#define VNC_EXPORT_BGP_RH_ENABLED(hc) \ + (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ + == BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH) + +#define VNC_EXPORT_BGP_CE_ENABLED(hc) \ + (((hc)->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) \ + == BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE) + + +void bgp_rfapi_cfg_init(void); + +struct rfapi_cfg *bgp_rfapi_cfg_new(struct rfapi_rfp_cfg *cfg); + +void bgp_rfapi_cfg_destroy(struct bgp *bgp, struct rfapi_cfg *h); + +int bgp_rfapi_cfg_write(struct vty *vty, struct bgp *bgp); + +extern int bgp_rfapi_is_vnc_configured(struct bgp *bgp); + +extern void nve_group_to_nve_list(struct rfapi_nve_group_cfg *rfg, + struct list **nves, + uint8_t family); /* AF_INET, AF_INET6 */ + +struct rfapi_nve_group_cfg *bgp_rfapi_cfg_match_group(struct rfapi_cfg *hc, + struct prefix *vn, + struct prefix *un); + +struct rfapi_nve_group_cfg * +bgp_rfapi_cfg_match_byname(struct bgp *bgp, const char *name, + rfapi_group_cfg_type_t type); /* _MAX = any */ + +extern void vnc_prefix_list_update(struct bgp *bgp); + +extern void vnc_routemap_update(struct bgp *bgp, const char *unused); + +extern void bgp_rfapi_show_summary(struct bgp *bgp, struct vty *vty); + +extern struct rfapi_cfg *bgp_rfapi_get_config(struct bgp *bgp); + +extern struct rfapi_l2_group_cfg * +bgp_rfapi_get_group_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, + uint32_t label); + +extern struct ecommunity * +bgp_rfapi_get_ecommunity_by_lni_label(struct bgp *bgp, uint32_t is_import, + uint32_t logical_net_id, + uint32_t label); /* note, 20bit label! */ + +extern struct list * +bgp_rfapi_get_labellist_by_lni_label(struct bgp *bgp, uint32_t logical_net_id, + uint32_t label); /* note, 20bit label! */ + +#endif /* ENABLE_BGP_VNC */ + +#endif /* _QUAGGA_BGP_RFAPI_CFG_H */ diff --git a/bgpd/rfapi/rfapi.c b/bgpd/rfapi/rfapi.c new file mode 100644 index 0000000..23e3eb4 --- /dev/null +++ b/bgpd/rfapi/rfapi.c @@ -0,0 +1,4041 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/routemap.h" +#include "lib/log.h" +#include "lib/linklist.h" +#include "lib/command.h" +#include "lib/stream.h" +#include "lib/ringbuf.h" +#include "lib/lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_backend.h" + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_advertise.h" +#include "bgpd/bgp_vnc_types.h" +#include "bgpd/bgp_zebra.h" + +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp_p.h" +#include "bgpd/rfapi/vnc_zebra.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/rfapi_rib.h" +#include "bgpd/rfapi/rfapi_ap.h" +#include "bgpd/rfapi/rfapi_encap_tlv.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define DEBUG_CLEANUP 0 + +struct ethaddr rfapi_ethaddr0 = {{0}}; + +#define DEBUG_RFAPI_STR "RF API debugging/testing command\n" + +const char *rfapi_error_str(int code) +{ + switch (code) { + case 0: + return "Success"; + case ENXIO: + return "BGP or VNC not configured"; + case ENOENT: + return "No match"; + case EEXIST: + return "Handle already open"; + case ENOMSG: + return "Incomplete configuration"; + case EAFNOSUPPORT: + return "Invalid address family"; + case EDEADLK: + return "Called from within a callback procedure"; + case EBADF: + return "Invalid handle"; + case EINVAL: + return "Invalid argument"; + case ESTALE: + return "Stale descriptor"; + default: + return "Unknown error"; + } +} + +/*------------------------------------------ + * rfapi_get_response_lifetime_default + * + * Returns the default lifetime for a response. + * rfp_start_val value returned by rfp_start or + * NULL (=use default instance) + * + * input: + * None + * + * output: + * + * return value: The bgp instance default lifetime for a response. + --------------------------------------------*/ +int rfapi_get_response_lifetime_default(void *rfp_start_val) +{ + struct bgp *bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (bgp) + return bgp->rfapi_cfg->default_response_lifetime; + return BGP_VNC_DEFAULT_RESPONSE_LIFETIME_DEFAULT; +} + +/*------------------------------------------ + * rfapi_is_vnc_configured + * + * Returns if VNC is configured + * + * input: + * rfp_start_val value returned by rfp_start or + * NULL (=use default instance) + * + * output: + * + * return value: If VNC is configured for the bgpd instance + * 0 Success + * ENXIO VNC not configured + --------------------------------------------*/ +int rfapi_is_vnc_configured(void *rfp_start_val) +{ + struct bgp *bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (bgp_rfapi_is_vnc_configured(bgp) == 0) + return 0; + return ENXIO; +} + + +/*------------------------------------------ + * rfapi_get_vn_addr + * + * Get the virtual network address used by an NVE based on it's RFD + * + * input: + * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * vn NVE virtual network address + *------------------------------------------*/ +struct rfapi_ip_addr *rfapi_get_vn_addr(void *rfd) +{ + struct rfapi_descriptor *rrfd = (struct rfapi_descriptor *)rfd; + return &rrfd->vn_addr; +} + +/*------------------------------------------ + * rfapi_get_un_addr + * + * Get the underlay network address used by an NVE based on it's RFD + * + * input: + * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * un NVE underlay network address + *------------------------------------------*/ +struct rfapi_ip_addr *rfapi_get_un_addr(void *rfd) +{ + struct rfapi_descriptor *rrfd = (struct rfapi_descriptor *)rfd; + return &rrfd->un_addr; +} + +int rfapi_ip_addr_cmp(struct rfapi_ip_addr *a1, struct rfapi_ip_addr *a2) +{ + if (a1->addr_family != a2->addr_family) + return a1->addr_family - a2->addr_family; + + if (a1->addr_family == AF_INET) { + return IPV4_ADDR_CMP(&a1->addr.v4, &a2->addr.v4); + } + + if (a1->addr_family == AF_INET6) { + return IPV6_ADDR_CMP(&a1->addr.v6, &a2->addr.v6); + } + + assert(1); + /* NOTREACHED */ + return 1; +} + +static int rfapi_find_node(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_addr *un_addr, + struct agg_node **node) +{ + struct rfapi *h; + struct prefix p; + struct agg_node *rn; + int rc; + afi_t afi; + + if (!bgp) { + return ENXIO; + } + + h = bgp->rfapi; + if (!h) { + return ENXIO; + } + + afi = family2afi(un_addr->addr_family); + if (!afi) { + return EAFNOSUPPORT; + } + + if ((rc = rfapiRaddr2Qprefix(un_addr, &p))) + return rc; + + rn = agg_node_lookup(h->un[afi], &p); + + if (!rn) + return ENOENT; + + agg_unlock_node(rn); + + *node = rn; + + return 0; +} + + +int rfapi_find_rfd(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_addr *un_addr, struct rfapi_descriptor **rfd) +{ + struct agg_node *rn; + int rc; + + rc = rfapi_find_node(bgp, vn_addr, un_addr, &rn); + + if (rc) + return rc; + + for (*rfd = (struct rfapi_descriptor *)(rn->info); *rfd; + *rfd = (*rfd)->next) { + if (!rfapi_ip_addr_cmp(&(*rfd)->vn_addr, vn_addr)) + break; + } + + if (!*rfd) + return ENOENT; + + return 0; +} + +/*------------------------------------------ + * rfapi_find_handle + * + * input: + * un underlay network address + * vn virtual network address + * + * output: + * pHandle pointer to location to store handle + * + * return value: + * 0 Success + * ENOENT no matching handle + * ENXIO BGP or VNC not configured + *------------------------------------------*/ +static int rfapi_find_handle(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_addr *un_addr, + rfapi_handle *handle) +{ + struct rfapi_descriptor **rfd; + + rfd = (struct rfapi_descriptor **)handle; + + return rfapi_find_rfd(bgp, vn_addr, un_addr, rfd); +} + +static int rfapi_find_handle_vty(struct vty *vty, struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_addr *un_addr, + rfapi_handle *handle) +{ + struct bgp *bgp; + struct rfapi_descriptor **rfd; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + + rfd = (struct rfapi_descriptor **)handle; + + return rfapi_find_rfd(bgp, vn_addr, un_addr, rfd); +} + +static int is_valid_rfd(struct rfapi_descriptor *rfd) +{ + rfapi_handle hh; + + if (!rfd || rfd->bgp == NULL) + return 0; + + if (CHECK_FLAG( + rfd->flags, + RFAPI_HD_FLAG_IS_VRF)) /* assume VRF/internal are valid */ + return 1; + + if (rfapi_find_handle(rfd->bgp, &rfd->vn_addr, &rfd->un_addr, &hh)) + return 0; + + if (rfd != hh) + return 0; + + return 1; +} + +/* + * check status of descriptor + */ +int rfapi_check(void *handle) +{ + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + rfapi_handle hh; + int rc; + + if (!rfd || rfd->bgp == NULL) + return EINVAL; + + if (CHECK_FLAG( + rfd->flags, + RFAPI_HD_FLAG_IS_VRF)) /* assume VRF/internal are valid */ + return 0; + + if ((rc = rfapi_find_handle(rfd->bgp, &rfd->vn_addr, &rfd->un_addr, + &hh))) + return rc; + + if (rfd != hh) + return ENOENT; + + if (!rfd->rfg) + return ESTALE; + + return 0; +} + + +void del_vnc_route(struct rfapi_descriptor *rfd, + struct peer *peer, /* rfd->peer for RFP regs */ + struct bgp *bgp, safi_t safi, const struct prefix *p, + struct prefix_rd *prd, uint8_t type, uint8_t sub_type, + struct rfapi_nexthop *lnh, int kill) +{ + afi_t afi; /* of the VN address */ + struct bgp_dest *bn; + struct bgp_path_info *bpi; + struct prefix_rd prd0; + + afi = family2afi(p->family); + assert(afi == AFI_IP || afi == AFI_IP6); + + if (safi == SAFI_ENCAP) { + memset(&prd0, 0, sizeof(prd0)); + prd0.family = AF_UNSPEC; + prd0.prefixlen = 64; + prd = &prd0; + } + bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + vnc_zlog_debug_verbose( + "%s: peer=%p, prefix=%pFX, prd=%pRDP afi=%d, safi=%d bn=%p, bn->info=%p", + __func__, peer, p, prd, afi, safi, bn, + (bn ? bgp_dest_get_bgp_path_info(bn) : NULL)); + + for (bpi = (bn ? bgp_dest_get_bgp_path_info(bn) : NULL); bpi; + bpi = bpi->next) { + vnc_zlog_debug_verbose( + "%s: trying bpi=%p, bpi->peer=%p, bpi->type=%d, bpi->sub_type=%d, bpi->extra->vnc.export.rfapi_handle=%p, local_pref=%" PRIu64, + __func__, bpi, bpi->peer, bpi->type, bpi->sub_type, + (bpi->extra ? bpi->extra->vnc->vnc.export.rfapi_handle + : NULL), + CHECK_FLAG(bpi->attr->flag, + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF) + ? bpi->attr->local_pref + : 0)); + + if (bpi->peer == peer && bpi->type == type && + bpi->sub_type == sub_type && bpi->extra && + bpi->extra->vnc->vnc.export.rfapi_handle == (void *)rfd) { + vnc_zlog_debug_verbose("%s: matched it", __func__); + + break; + } + } + + if (lnh) { + /* + * lnh set means to JUST delete the local nexthop from this + * route. Leave the route itself in place. + * TBD add return code reporting of success/failure + */ + if (!bpi || !bpi->extra || + !bpi->extra->vnc->vnc.export.local_nexthops) { + /* + * no local nexthops + */ + vnc_zlog_debug_verbose( + "%s: lnh list already empty at prefix %pFX", + __func__, p); + goto done; + } + + /* + * look for it + */ + struct listnode *node; + struct rfapi_nexthop *pLnh = NULL; + + for (ALL_LIST_ELEMENTS_RO(bpi->extra->vnc->vnc.export + .local_nexthops, + node, pLnh)) { + if (prefix_same(&pLnh->addr, &lnh->addr)) { + break; + } + } + + if (pLnh) { + listnode_delete(bpi->extra->vnc->vnc.export.local_nexthops, + pLnh); + + /* silly rabbit, listnode_delete doesn't invoke + * list->del on data */ + rfapi_nexthop_free(pLnh); + } else { + vnc_zlog_debug_verbose("%s: desired lnh not found %pFX", + __func__, p); + } + goto done; + } + + /* + * loop back to import tables + * Do this before removing from BGP RIB because rfapiProcessWithdraw + * might refer to it + */ + rfapiProcessWithdraw(peer, rfd, p, prd, NULL, afi, safi, type, kill); + + if (bpi) { + vnc_zlog_debug_verbose( + "%s: Found route (safi=%d) to delete at prefix %pFX", + __func__, safi, p); + + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + table = bgp_dest_get_bgp_table_info(pdest); + if (table) + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, bpi); + bgp_dest_unlock_node(pdest); + } + + /* + * Delete local_nexthops list + */ + if (bpi->extra && bpi->extra->vnc->vnc.export.local_nexthops) + list_delete(&bpi->extra->vnc->vnc.export.local_nexthops); + + bgp_aggregate_decrement(bgp, p, bpi, afi, safi); + bgp_path_info_delete(bn, bpi); + bgp_process(bgp, bn, bpi, afi, safi); + } else { + vnc_zlog_debug_verbose( + "%s: Couldn't find route (safi=%d) at prefix %pFX", + __func__, safi, p); + } +done: + bgp_dest_unlock_node(bn); +} + +struct rfapi_nexthop *rfapi_nexthop_new(struct rfapi_nexthop *copyme) +{ + struct rfapi_nexthop *new = + XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_nexthop)); + if (copyme) + *new = *copyme; + return new; +} + +void rfapi_nexthop_free(void *p) +{ + struct rfapi_nexthop *goner = p; + XFREE(MTYPE_RFAPI_NEXTHOP, goner); +} + +struct rfapi_vn_option *rfapi_vn_options_dup(struct rfapi_vn_option *existing) +{ + struct rfapi_vn_option *p; + struct rfapi_vn_option *head = NULL; + struct rfapi_vn_option *tail = NULL; + + for (p = existing; p; p = p->next) { + struct rfapi_vn_option *new; + + new = XCALLOC(MTYPE_RFAPI_VN_OPTION, + sizeof(struct rfapi_vn_option)); + *new = *p; + new->next = NULL; + if (tail) + (tail)->next = new; + tail = new; + if (!head) { + head = new; + } + } + return head; +} + +void rfapi_un_options_free(struct rfapi_un_option *p) +{ + struct rfapi_un_option *next; + + while (p) { + next = p->next; + XFREE(MTYPE_RFAPI_UN_OPTION, p); + p = next; + } +} + +void rfapi_vn_options_free(struct rfapi_vn_option *p) +{ + struct rfapi_vn_option *next; + + while (p) { + next = p->next; + XFREE(MTYPE_RFAPI_VN_OPTION, p); + p = next; + } +} + +/* Based on bgp_redistribute_add() */ +void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie, VPN UN addr, peer */ + struct bgp *bgp, int safi, const struct prefix *p, + struct prefix_rd *prd, struct rfapi_ip_addr *nexthop, + uint32_t *local_pref, + uint32_t *lifetime, /* NULL => dont send lifetime */ + struct bgp_tea_options *rfp_options, + struct rfapi_un_option *options_un, + struct rfapi_vn_option *options_vn, + struct ecommunity *rt_export_list, /* Copied, not consumed */ + uint32_t *med, /* NULL => don't set med */ + uint32_t *label, /* low order 3 bytes */ + uint8_t type, uint8_t sub_type, /* RFP, NORMAL or REDIST */ + int flags) +{ + afi_t afi; /* of the VN address */ + struct bgp_labels bgp_labels = {}; + struct bgp_path_info *new; + struct bgp_path_info *bpi; + struct bgp_dest *bn; + + struct attr attr = {0}; + struct attr *new_attr; + uint32_t label_val; + + struct bgp_attr_encap_subtlv *encaptlv; + char buf[PREFIX_STRLEN]; + + struct rfapi_nexthop *lnh = NULL; /* local nexthop */ + struct rfapi_vn_option *vo; + struct rfapi_l2address_option *l2o = NULL; + struct rfapi_ip_addr *un_addr = &rfd->un_addr; + + bgp_encap_types TunnelType = BGP_ENCAP_TYPE_RESERVED; + struct bgp_redist *red; + + if (safi == SAFI_ENCAP + && !(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP)) { + + /* + * Encap mode not enabled. UN addresses will be communicated + * via VNC Tunnel subtlv instead. + */ + vnc_zlog_debug_verbose( + "%s: encap mode not enabled, not adding SAFI_ENCAP route", + __func__); + return; + } + + for (vo = options_vn; vo; vo = vo->next) { + if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { + l2o = &vo->v.l2addr; + if (RFAPI_0_ETHERADDR(&l2o->macaddr)) + l2o = NULL; /* not MAC resolution */ + } + if (RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP == vo->type) { + lnh = &vo->v.local_nexthop; + } + } + + if (label && *label != MPLS_INVALID_LABEL) + label_val = *label; + else + label_val = MPLS_LABEL_IMPLICIT_NULL; + + afi = family2afi(p->family); + assert(afi == AFI_IP || afi == AFI_IP6); + + vnc_zlog_debug_verbose("%s: afi=%s, safi=%s", __func__, afi2str(afi), + safi2str(safi)); + + /* Make default attribute. Produces already-interned attr.aspath */ + /* Cripes, the memory management of attributes is byzantine */ + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + + /* + * At this point: + * attr: static + * extra: dynamically allocated, owned by attr + * aspath: points to interned hash from aspath hash table + */ + + + /* + * Route-specific un_options get added to the VPN SAFI + * advertisement tunnel encap attribute. (the per-NVE + * "default" un_options are put into the 1-per-NVE ENCAP + * SAFI advertisement). The VPN SAFI also gets the + * default un_options if there are no route-specific options. + */ + if (options_un) { + struct rfapi_un_option *uo; + + for (uo = options_un; uo; uo = uo->next) { + if (RFAPI_UN_OPTION_TYPE_TUNNELTYPE == uo->type) { + TunnelType = rfapi_tunneltype_option_to_tlv( + bgp, un_addr, &uo->v.tunnel, &attr, + l2o != NULL); + } + } + } else { + /* + * Add encap attr + * These are the NVE-specific "default" un_options which are + * put into the 1-per-NVE ENCAP advertisement. + */ + if (rfd->default_tunneltype_option.type) { + TunnelType = rfapi_tunneltype_option_to_tlv( + bgp, un_addr, &rfd->default_tunneltype_option, + &attr, l2o != NULL); + } else /* create default for local addse */ + if (type == ZEBRA_ROUTE_BGP + && sub_type == BGP_ROUTE_RFP) + TunnelType = rfapi_tunneltype_option_to_tlv( + bgp, un_addr, NULL, &attr, l2o != NULL); + } + + if (TunnelType == BGP_ENCAP_TYPE_MPLS) { + if (safi == SAFI_ENCAP) { + /* Encap SAFI not used with MPLS */ + vnc_zlog_debug_verbose( + "%s: mpls tunnel type, encap safi omitted", + __func__); + aspath_unintern(&attr.aspath); /* Unintern original. */ + return; + } + } + + if (local_pref) { + attr.local_pref = *local_pref; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + } + + if (med) { + attr.med = *med; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + } + + /* override default weight assigned by bgp_attr_default_set() */ + attr.weight = rfd->peer ? rfd->peer->weight[afi][safi] : 0; + + /* + * NB: ticket 81: do not reset attr.aspath here because it would + * cause iBGP peers to drop route + */ + + /* + * Set originator ID for routes imported from BGP directly. + * These routes could be synthetic, and therefore could + * reuse the peer pointers of the routes they are derived + * from. Setting the originator ID to "us" prevents the + * wrong originator ID from being sent when this route is + * sent from a route reflector. + */ + if (type == ZEBRA_ROUTE_BGP_DIRECT + || type == ZEBRA_ROUTE_BGP_DIRECT_EXT) { + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_ORIGINATOR_ID); + attr.originator_id = bgp->router_id; + } + + + /* Set up vnc attribute (sub-tlv for Prefix Lifetime) */ + if (lifetime && *lifetime != RFAPI_INFINITE_LIFETIME) { + uint32_t lt; + + encaptlv = XCALLOC(MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + 4); + encaptlv->type = + BGP_VNC_SUBTLV_TYPE_LIFETIME; /* prefix lifetime */ + encaptlv->length = 4; + lt = htonl(*lifetime); + memcpy(encaptlv->value, <, 4); + bgp_attr_set_vnc_subtlvs(&attr, encaptlv); + vnc_zlog_debug_verbose( + "%s: set Encap Attr Prefix Lifetime to %d", __func__, + *lifetime); + } + + /* add rfp options to vnc attr */ + if (rfp_options) { + + if (flags & RFAPI_AHR_RFPOPT_IS_VNCTLV) { + struct bgp_attr_encap_subtlv *vnc_subtlvs = + bgp_attr_get_vnc_subtlvs(&attr); + /* + * this flag means we're passing a pointer to an + * existing encap tlv chain which we should copy. + * It's a hack to avoid adding yet another argument + * to add_vnc_route() + */ + encaptlv = encap_tlv_dup( + (struct bgp_attr_encap_subtlv *)rfp_options); + if (vnc_subtlvs) + vnc_subtlvs->next = encaptlv; + else + bgp_attr_set_vnc_subtlvs(&attr, encaptlv); + } else { + struct bgp_tea_options *hop; + /* XXX max of one tlv present so far from above code */ + struct bgp_attr_encap_subtlv *tail = + bgp_attr_get_vnc_subtlvs(&attr); + + for (hop = rfp_options; hop; hop = hop->next) { + + /* + * Construct subtlv + */ + encaptlv = XCALLOC( + MTYPE_ENCAP_TLV, + sizeof(struct bgp_attr_encap_subtlv) + 2 + + hop->length); + encaptlv->type = + BGP_VNC_SUBTLV_TYPE_RFPOPTION; /* RFP + option + */ + encaptlv->length = 2 + hop->length; + *((uint8_t *)(encaptlv->value) + 0) = hop->type; + *((uint8_t *)(encaptlv->value) + 1) = + hop->length; + memcpy(((uint8_t *)encaptlv->value) + 2, + hop->value, hop->length); + + /* + * add to end of subtlv chain + */ + if (tail) + tail->next = encaptlv; + else + bgp_attr_set_vnc_subtlvs(&attr, + encaptlv); + tail = encaptlv; + } + } + } + + /* + * At this point: + * attr: static + * extra: dynamically allocated, owned by attr + * vnc_subtlvs: dynamic chain, length 1 + * aspath: points to interned hash from aspath hash table + */ + + + bgp_attr_set_ecommunity(&attr, ecommunity_new()); + assert(bgp_attr_get_ecommunity(&attr)); + + if (TunnelType != BGP_ENCAP_TYPE_MPLS + && TunnelType != BGP_ENCAP_TYPE_RESERVED) { + /* + * Add BGP Encapsulation Extended Community. Format described in + * section 4.5 of RFC 5512. + * Always include when not MPLS type, to disambiguate this case. + */ + struct ecommunity_val beec; + + memset(&beec, 0, sizeof(beec)); + beec.val[0] = ECOMMUNITY_ENCODE_OPAQUE; + beec.val[1] = ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP; + beec.val[6] = ((TunnelType) >> 8) & 0xff; + beec.val[7] = (TunnelType)&0xff; + ecommunity_add_val(bgp_attr_get_ecommunity(&attr), &beec, false, + false); + } + + /* + * Add extended community attributes to match rt export list + */ + if (rt_export_list) { + bgp_attr_set_ecommunity( + &attr, ecommunity_merge(bgp_attr_get_ecommunity(&attr), + rt_export_list)); + } + + struct ecommunity *ecomm = bgp_attr_get_ecommunity(&attr); + + if (!ecomm->size) { + ecommunity_free(&ecomm); + bgp_attr_set_ecommunity(&attr, NULL); + } + vnc_zlog_debug_verbose("%s: attr.ecommunity=%p", __func__, ecomm); + + + /* + * At this point: + * attr: static + * extra: dynamically allocated, owned by attr + * vnc_subtlvs: dynamic chain, length 1 + * ecommunity: dynamic 2-part + * aspath: points to interned hash from aspath hash table + */ + + /* stuff nexthop in attr_extra; which field depends on IPv4 or IPv6 */ + switch (nexthop->addr_family) { + case AF_INET: + /* + * set this field to prevent bgp_route.c code from setting + * mp_nexthop_global_in to self + */ + attr.nexthop.s_addr = nexthop->addr.v4.s_addr; + + attr.mp_nexthop_global_in = nexthop->addr.v4; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; + break; + + case AF_INET6: + attr.mp_nexthop_global = nexthop->addr.v6; + attr.mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; + break; + + default: + assert(0); + } + + + prefix2str(p, buf, sizeof(buf)); + + /* + * At this point: + * + * attr: static + * extra: dynamically allocated, owned by attr + * vnc_subtlvs: dynamic chain, length 1 + * ecommunity: dynamic 2-part + * aspath: points to interned hash from aspath hash table + */ + + red = bgp_redist_lookup(bgp, afi, type, 0); + + if (red && red->redist_metric_flag) { + attr.med = red->redist_metric; + attr.flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + } + + bn = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + /* + * bgp_attr_intern creates a new reference to a cached + * attribute, but leaves the following bits of trash: + * - old attr + * - old attr->extra (free via bgp_attr_extra_free(attr)) + * + * Note that it frees the original attr->extra->ecommunity + * but leaves the new attribute pointing to the ORIGINAL + * vnc options (which therefore we needn't free from the + * static attr) + */ + new_attr = bgp_attr_intern(&attr); + + aspath_unintern(&attr.aspath); /* Unintern original. */ + + /* + * At this point: + * + * attr: static + * extra: dynamically allocated, owned by attr + * vnc_subtlvs: dynamic chain, length 1 + * ecommunity: POINTS TO INTERNED ecom, THIS REF NOT COUNTED + * + * new_attr: an attr that is part of the hash table, distinct + * from attr which is static. + * extra: dynamically allocated, owned by new_attr (in hash table) + * vnc_subtlvs: POINTS TO SAME dynamic chain AS attr + * ecommunity: POINTS TO interned/refcounted dynamic 2-part AS attr + * aspath: POINTS TO interned/refcounted hashed block + */ + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + /* probably only need to check + * bpi->extra->vnc->vnc.export.rfapi_handle */ + if (bpi->peer == rfd->peer && bpi->type == type && + bpi->sub_type == sub_type && bpi->extra && + bpi->extra->vnc->vnc.export.rfapi_handle == (void *)rfd) { + break; + } + } + + if (bpi) { + + /* + * Adding new local_nexthop, which does not by itself change + * what is advertised via BGP + */ + if (lnh) { + if (!bpi->extra->vnc->vnc.export.local_nexthops) { + /* TBD make arrangements to free when needed */ + bpi->extra->vnc->vnc.export.local_nexthops = + list_new(); + bpi->extra->vnc->vnc.export.local_nexthops->del = + rfapi_nexthop_free; + } + + /* + * already present? + */ + struct listnode *node; + struct rfapi_nexthop *pLnh = NULL; + + for (ALL_LIST_ELEMENTS_RO(bpi->extra->vnc->vnc.export + .local_nexthops, + node, pLnh)) { + if (prefix_same(&pLnh->addr, &lnh->addr)) { + break; + } + } + + /* + * Not present, add new one + */ + if (!pLnh) { + pLnh = rfapi_nexthop_new(lnh); + listnode_add(bpi->extra->vnc->vnc.export + .local_nexthops, + pLnh); + } + } + + if (attrhash_cmp(bpi->attr, new_attr) + && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + bgp_attr_unintern(&new_attr); + bgp_dest_unlock_node(bn); + + vnc_zlog_debug_any( + "%s: Found route (safi=%d) at prefix %s, no change", + __func__, safi, buf); + + goto done; + } else { + /* The attribute is changed. */ + bgp_path_info_set_flag(bn, bpi, BGP_PATH_ATTR_CHANGED); + + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + table = bgp_dest_get_bgp_table_info(pdest); + if (table) + vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, bpi); + bgp_dest_unlock_node(pdest); + } + + /* Rewrite BGP route information. */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + bgp_path_info_restore(bn, bpi); + else + bgp_aggregate_decrement(bgp, p, bpi, afi, safi); + bgp_attr_unintern(&bpi->attr); + bpi->attr = new_attr; + bpi->uptime = monotime(NULL); + + + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], + (struct prefix *)prd); + table = bgp_dest_get_bgp_table_info(pdest); + if (table) + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, bpi); + bgp_dest_unlock_node(pdest); + } + + /* Process change. */ + bgp_aggregate_increment(bgp, p, bpi, afi, safi); + bgp_process(bgp, bn, bpi, afi, safi); + bgp_dest_unlock_node(bn); + + vnc_zlog_debug_any( + "%s: Found route (safi=%d) at prefix %s, changed attr", + __func__, safi, buf); + + goto done; + } + } + + new = info_make(type, sub_type, 0, rfd->peer, new_attr, NULL); + SET_FLAG(new->flags, BGP_PATH_VALID); + + /* save backref to rfapi handle */ + bgp_path_info_extra_get(new); + new->extra->vnc = XCALLOC(MTYPE_BGP_ROUTE_EXTRA_VNC, + sizeof(struct bgp_path_info_extra_vnc)); + new->extra->vnc->vnc.export.rfapi_handle = (void *)rfd; + + encode_label(label_val, &bgp_labels.label[0]); + bgp_labels.num_labels = 1; + new->extra->labels = bgp_labels_intern(&bgp_labels); + + /* debug */ + + if (VNC_DEBUG(VERBOSE)) { + vnc_zlog_debug_verbose("%s: printing BPI", __func__); + rfapiPrintBi(NULL, new); + } + + bgp_aggregate_increment(bgp, p, new, afi, safi); + bgp_path_info_add(bn, new); + + if (safi == SAFI_MPLS_VPN) { + struct bgp_dest *pdest = NULL; + struct bgp_table *table = NULL; + + pdest = bgp_node_get(bgp->rib[afi][safi], (struct prefix *)prd); + table = bgp_dest_get_bgp_table_info(pdest); + if (table) + vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + bgp, prd, table, p, new); + bgp_dest_unlock_node(pdest); + encode_label(label_val, &bn->local_label); + } + + bgp_dest_unlock_node(bn); + bgp_process(bgp, bn, new, afi, safi); + + vnc_zlog_debug_any( + "%s: Added route (safi=%s) at prefix %s (bn=%p, prd=%pRDP)", + __func__, safi2str(safi), buf, bn, prd); + +done: + /* Loop back to import tables */ + rfapiProcessUpdate(rfd->peer, rfd, p, prd, new_attr, afi, safi, type, + sub_type, &label_val); + vnc_zlog_debug_verbose("%s: looped back import route (safi=%d)", + __func__, safi); +} + +uint32_t rfp_cost_to_localpref(uint8_t cost) +{ + return 255 - cost; +} + +static void rfapiTunnelRouteAnnounce(struct bgp *bgp, + struct rfapi_descriptor *rfd, + uint32_t *pLifetime) +{ + struct prefix_rd prd; + struct prefix pfx_vn; + int rc; + uint32_t local_pref = rfp_cost_to_localpref(0); + + rc = rfapiRaddr2Qprefix(&(rfd->vn_addr), &pfx_vn); + assert(!rc); + + /* + * Construct route distinguisher = 0 + */ + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + add_vnc_route(rfd, /* rfapi descr, for export list & backref */ + bgp, /* which bgp instance */ + SAFI_ENCAP, /* which SAFI */ + &pfx_vn, /* prefix to advertise */ + &prd, /* route distinguisher to use */ + &rfd->un_addr, /* nexthop */ + &local_pref, + pLifetime, /* max lifetime of child VPN routes */ + NULL, /* no rfp options for ENCAP safi */ + NULL, /* rfp un options */ + NULL, /* rfp vn options */ + rfd->rt_export_list, NULL, /* med */ + NULL, /* label: default */ + ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); +} + + +/*********************************************************************** + * RFP processing behavior configuration + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_rfp_set_configuration + * + * This is used to change rfapi's processing behavior based on + * RFP requirements. + * + * input: + * rfp_start_val value returned by rfp_start + * rfapi_rfp_cfg Pointer to configuration structure + * + * output: + * none + * + * return value: + * 0 Success + * ENXIO Unabled to locate configured BGP/VNC +--------------------------------------------*/ +int rfapi_rfp_set_configuration(void *rfp_start_val, struct rfapi_rfp_cfg *new) +{ + struct rfapi_rfp_cfg *rcfg; + struct bgp *bgp; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + + if (!new || !bgp || !bgp->rfapi_cfg) + return ENXIO; + + rcfg = &bgp->rfapi_cfg->rfp_cfg; + rcfg->download_type = new->download_type; + rcfg->ftd_advertisement_interval = new->ftd_advertisement_interval; + rcfg->holddown_factor = new->holddown_factor; + + if (rcfg->use_updated_response != new->use_updated_response) { + rcfg->use_updated_response = new->use_updated_response; + if (rcfg->use_updated_response) + rfapiMonitorCallbacksOn(bgp); + else + rfapiMonitorCallbacksOff(bgp); + } + if (rcfg->use_removes != new->use_removes) { + rcfg->use_removes = new->use_removes; + if (rcfg->use_removes) + rfapiMonitorResponseRemovalOn(bgp); + else + rfapiMonitorResponseRemovalOff(bgp); + } + return 0; +} + +/*------------------------------------------ + * rfapi_rfp_set_cb_methods + * + * Change registered callback functions for asynchronous notifications + * from RFAPI to the RFP client. + * + * input: + * rfp_start_val value returned by rfp_start + * methods Pointer to struct rfapi_rfp_cb_methods containing + * pointers to callback methods as described above + * + * return value: + * 0 Success + * ENXIO BGP or VNC not configured + *------------------------------------------*/ +int rfapi_rfp_set_cb_methods(void *rfp_start_val, + struct rfapi_rfp_cb_methods *methods) +{ + struct rfapi *h; + struct bgp *bgp; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (!bgp) + return ENXIO; + + h = bgp->rfapi; + if (!h) + return ENXIO; + + h->rfp_methods = *methods; + + return 0; +} + +/*********************************************************************** + * NVE Sessions + ***********************************************************************/ +/* + * Caller must supply an already-allocated rfd with the "caller" + * fields already set (vn_addr, un_addr, callback, cookie) + * The advertised_prefixes[] array elements should be NULL to + * have this function set them to newly-allocated radix trees. + */ +static int rfapi_open_inner(struct rfapi_descriptor *rfd, struct bgp *bgp, + struct rfapi *h, struct rfapi_nve_group_cfg *rfg) +{ + int ret; + + if (h->flags & RFAPI_INCALLBACK) + return EDEADLK; + + /* + * Fill in configured fields + */ + + /* + * If group's RD is specified as "auto", then fill in based + * on NVE's VN address + */ + rfd->rd = rfg->rd; + + if (rfd->rd.family == AF_UNIX) { + ret = rfapi_set_autord_from_vn(&rfd->rd, &rfd->vn_addr); + if (ret != 0) + return ret; + } + rfd->rt_export_list = (rfg->rt_export_list) + ? ecommunity_dup(rfg->rt_export_list) + : NULL; + rfd->response_lifetime = rfg->response_lifetime; + rfd->rfg = rfg; + + /* + * Fill in BGP peer structure + */ + rfd->peer = peer_new(bgp); + rfd->peer->connection->status = Established; /* keep bgp core happy */ + + bgp_peer_connection_buffers_free(rfd->peer->connection); + + { /* base code assumes have valid host pointer */ + char buf[INET6_ADDRSTRLEN]; + buf[0] = 0; + + if (rfd->vn_addr.addr_family == AF_INET) { + inet_ntop(AF_INET, &rfd->vn_addr.addr.v4, buf, + sizeof(buf)); + } else if (rfd->vn_addr.addr_family == AF_INET6) { + inet_ntop(AF_INET6, &rfd->vn_addr.addr.v6, buf, + sizeof(buf)); + } + rfd->peer->host = XSTRDUP(MTYPE_BGP_PEER_HOST, buf); + } + /* Mark peer as belonging to HD */ + SET_FLAG(rfd->peer->flags, PEER_FLAG_IS_RFAPI_HD); + + /* + * Set min prefix lifetime to max value so it will get set + * upon first rfapi_register() + */ + rfd->min_prefix_lifetime = UINT32_MAX; + +/* + * Allocate response tables if needed + */ +#define RFD_RTINIT_AFI(rh, ary, afi) \ + do { \ + if (!ary[afi]) { \ + ary[afi] = agg_table_init(); \ + agg_set_table_info(ary[afi], rh); \ + } \ + } while (0) + +#define RFD_RTINIT(rh, ary) \ + do { \ + RFD_RTINIT_AFI(rh, ary, AFI_IP); \ + RFD_RTINIT_AFI(rh, ary, AFI_IP6); \ + RFD_RTINIT_AFI(rh, ary, AFI_L2VPN); \ + } while (0) + + RFD_RTINIT(rfd, rfd->rib); + RFD_RTINIT(rfd, rfd->rib_pending); + RFD_RTINIT(rfd, rfd->rsp_times); + + /* + * Link to Import Table + */ + rfd->import_table = rfg->rfapi_import_table; + rfd->import_table->refcount += 1; + + rfapiApInit(&rfd->advertised); + + /* + * add this NVE descriptor to the list of NVEs in the NVE group + */ + if (!rfg->nves) { + rfg->nves = list_new(); + } + listnode_add(rfg->nves, rfd); + + vnc_direct_bgp_add_nve(bgp, rfd); + vnc_zebra_add_nve(bgp, rfd); + + return 0; +} + +/* moved from rfapi_register */ +int rfapi_init_and_open(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_nve_group_cfg *rfg) +{ + struct rfapi *h = bgp->rfapi; + char buf_vn[BUFSIZ]; + char buf_un[BUFSIZ]; + afi_t afi_vn, afi_un; + struct prefix pfx_un; + struct agg_node *rn; + + rfd->open_time = monotime(NULL); + + if (rfg->type == RFAPI_GROUP_CFG_VRF) + SET_FLAG(rfd->flags, RFAPI_HD_FLAG_IS_VRF); + + rfapiRfapiIpAddr2Str(&rfd->vn_addr, buf_vn, BUFSIZ); + rfapiRfapiIpAddr2Str(&rfd->un_addr, buf_un, BUFSIZ); + + vnc_zlog_debug_verbose("%s: new RFD with VN=%s UN=%s cookie=%p", + __func__, buf_vn, buf_un, rfd->cookie); + + if (rfg->type != RFAPI_GROUP_CFG_VRF) /* unclear if needed for VRF */ + { + listnode_add(&h->descriptors, rfd); + if (h->descriptors.count > h->stat.max_descriptors) { + h->stat.max_descriptors = h->descriptors.count; + } + + /* + * attach to UN radix tree + */ + afi_vn = family2afi(rfd->vn_addr.addr_family); + afi_un = family2afi(rfd->un_addr.addr_family); + assert(afi_vn && afi_un); + assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx_un)); + + rn = agg_node_get(h->un[afi_un], &pfx_un); + assert(rn); + rfd->next = rn->info; + rn->info = rfd; + rfd->un_node = rn; + } + return rfapi_open_inner(rfd, bgp, h, rfg); +} + +struct rfapi_vn_option *rfapiVnOptionsDup(struct rfapi_vn_option *orig) +{ + struct rfapi_vn_option *head = NULL; + struct rfapi_vn_option *tail = NULL; + struct rfapi_vn_option *vo = NULL; + + for (vo = orig; vo; vo = vo->next) { + struct rfapi_vn_option *new; + + new = XCALLOC(MTYPE_RFAPI_VN_OPTION, + sizeof(struct rfapi_vn_option)); + memcpy(new, vo, sizeof(struct rfapi_vn_option)); + new->next = NULL; + + if (tail) { + tail->next = new; + } else { + head = tail = new; + } + } + return head; +} + +struct rfapi_un_option *rfapiUnOptionsDup(struct rfapi_un_option *orig) +{ + struct rfapi_un_option *head = NULL; + struct rfapi_un_option *tail = NULL; + struct rfapi_un_option *uo = NULL; + + for (uo = orig; uo; uo = uo->next) { + struct rfapi_un_option *new; + + new = XCALLOC(MTYPE_RFAPI_UN_OPTION, + sizeof(struct rfapi_un_option)); + memcpy(new, uo, sizeof(struct rfapi_un_option)); + new->next = NULL; + + if (tail) { + tail->next = new; + } else { + head = tail = new; + } + } + return head; +} + +struct bgp_tea_options *rfapiOptionsDup(struct bgp_tea_options *orig) +{ + struct bgp_tea_options *head = NULL; + struct bgp_tea_options *tail = NULL; + struct bgp_tea_options *hop = NULL; + + for (hop = orig; hop; hop = hop->next) { + struct bgp_tea_options *new; + + new = XCALLOC(MTYPE_BGP_TEA_OPTIONS, + sizeof(struct bgp_tea_options)); + memcpy(new, hop, sizeof(struct bgp_tea_options)); + new->next = NULL; + if (hop->value) { + new->value = XCALLOC(MTYPE_BGP_TEA_OPTIONS_VALUE, + hop->length); + memcpy(new->value, hop->value, hop->length); + } + if (tail) { + tail->next = new; + } else { + head = tail = new; + } + } + return head; +} + +void rfapiFreeBgpTeaOptionChain(struct bgp_tea_options *p) +{ + struct bgp_tea_options *next; + + while (p) { + next = p->next; + + XFREE(MTYPE_BGP_TEA_OPTIONS_VALUE, p->value); + XFREE(MTYPE_BGP_TEA_OPTIONS, p); + + p = next; + } +} + +void rfapiAdbFree(struct rfapi_adb *adb) +{ + XFREE(MTYPE_RFAPI_ADB, adb); +} + +static int +rfapi_query_inner(void *handle, struct rfapi_ip_addr *target, + struct rfapi_l2address_option *l2o, /* may be NULL */ + struct rfapi_next_hop_entry **ppNextHopEntry) +{ + afi_t afi; + struct prefix p; + struct prefix p_original; + struct agg_node *rn; + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + struct bgp *bgp = rfd->bgp; + struct rfapi_next_hop_entry *pNHE = NULL; + struct rfapi_ip_addr *self_vn_addr = NULL; + int eth_is_0 = 0; + int use_eth_resolution = 0; + struct rfapi_next_hop_entry *i_nhe; + + /* preemptive */ + if (!bgp) { + vnc_zlog_debug_verbose("%s: No BGP instance, returning ENXIO", + __func__); + return ENXIO; + } + if (!bgp->rfapi) { + vnc_zlog_debug_verbose("%s: No RFAPI instance, returning ENXIO", + __func__); + return ENXIO; + } + if (bgp->rfapi->flags & RFAPI_INCALLBACK) { + vnc_zlog_debug_verbose( + "%s: Called during calback, returning EDEADLK", + __func__); + return EDEADLK; + } + + if (!is_valid_rfd(rfd)) { + vnc_zlog_debug_verbose("%s: invalid handle, returning EBADF", + __func__); + return EBADF; + } + + rfd->rsp_counter++; /* dedup: identify this generation */ + rfd->rsp_time = monotime(NULL); /* response content dedup */ + rfd->ftd_last_allowed_time = + monotime(NULL) - + bgp->rfapi_cfg->rfp_cfg.ftd_advertisement_interval; + + if (l2o) { + if (!memcmp(l2o->macaddr.octet, rfapi_ethaddr0.octet, + ETH_ALEN)) { + eth_is_0 = 1; + } + /* per t/c Paul/Lou 151022 */ + if (!eth_is_0 || l2o->logical_net_id) { + use_eth_resolution = 1; + } + } + + if (ppNextHopEntry) + *ppNextHopEntry = NULL; + + /* + * Save original target in prefix form. In case of L2-based queries, + * p_original will be modified to reflect the L2 target + */ + assert(!rfapiRaddr2Qprefix(target, &p_original)); + + if (bgp->rfapi_cfg->rfp_cfg.download_type == RFAPI_RFP_DOWNLOAD_FULL) { + /* convert query to 0/0 when full-table download is enabled */ + memset((char *)&p, 0, sizeof(p)); + p.family = target->addr_family; + } else { + p = p_original; + } + + { + char *s; + + vnc_zlog_debug_verbose("%s(rfd=%p, target=%pFX, ppNextHop=%p)", + __func__, rfd, &p, ppNextHopEntry); + + s = ecommunity_ecom2str(rfd->import_table->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vnc_zlog_debug_verbose( + "%s rfd->import_table=%p, rfd->import_table->rt_import_list: %s", + __func__, rfd->import_table, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + afi = family2afi(p.family); + assert(afi); + + if (CHECK_FLAG(bgp->rfapi_cfg->flags, + BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP)) { + self_vn_addr = &rfd->vn_addr; + } + + if (use_eth_resolution) { + uint32_t logical_net_id = l2o->logical_net_id; + struct ecommunity *l2com; + + /* + * fix up p_original to contain L2 address + */ + rfapiL2o2Qprefix(l2o, &p_original); + + l2com = bgp_rfapi_get_ecommunity_by_lni_label( + bgp, 1, logical_net_id, l2o->label); + if (l2com) { + uint8_t *v = l2com->val; + logical_net_id = (v[5] << 16) + (v[6] << 8) + (v[7]); + } + /* + * Ethernet/L2-based lookup + * + * Always returns IT node corresponding to route + */ + + if (RFAPI_RFP_DOWNLOAD_FULL + == bgp->rfapi_cfg->rfp_cfg.download_type) { + eth_is_0 = 1; + } + + rn = rfapiMonitorEthAdd( + bgp, rfd, (eth_is_0 ? &rfapi_ethaddr0 : &l2o->macaddr), + logical_net_id); + + if (eth_is_0) { + struct rfapi_ip_prefix rprefix; + + memset(&rprefix, 0, sizeof(rprefix)); + rprefix.prefix.addr_family = target->addr_family; + if (target->addr_family == AF_INET) { + rprefix.length = IPV4_MAX_BITLEN; + } else { + rprefix.length = IPV6_MAX_BITLEN; + } + + pNHE = rfapiEthRouteTable2NextHopList( + logical_net_id, &rprefix, + rfd->response_lifetime, self_vn_addr, + rfd->rib[afi], &p_original); + goto done; + } + + } else { + + /* + * IP-based lookup + */ + + rn = rfapiMonitorAdd(bgp, rfd, &p); + + /* + * If target address is 0, this request is special: means to + * return ALL routes in the table + * + * Monitors for All-Routes queries get put on a special list, + * not in the VPN tree + */ + if (RFAPI_0_PREFIX(&p)) { + + vnc_zlog_debug_verbose("%s: 0-prefix", __func__); + + /* + * Generate nexthop list for caller + */ + pNHE = rfapiRouteTable2NextHopList( + rfd->import_table->imported_vpn[afi], + rfd->response_lifetime, self_vn_addr, + rfd->rib[afi], &p_original); + goto done; + } + + if (rn) { + agg_lock_node(rn); /* so we can unlock below */ + } else { + /* + * returns locked node. Don't unlock yet because the + * unlock + * might free it before we're done with it. This + * situation + * could occur when rfapiMonitorGetAttachNode() returns + * a + * newly-created default node. + */ + rn = rfapiMonitorGetAttachNode(rfd, &p); + } + } + + assert(rn); + if (!rn->info) { + agg_unlock_node(rn); + vnc_zlog_debug_verbose( + "%s: VPN route not found, returning ENOENT", __func__); + return ENOENT; + } + + if (VNC_DEBUG(RFAPI_QUERY)) { + rfapiShowImportTable(NULL, "query", + rfd->import_table->imported_vpn[afi], 1); + } + + if (use_eth_resolution) { + + struct rfapi_ip_prefix rprefix; + + memset(&rprefix, 0, sizeof(rprefix)); + rprefix.prefix.addr_family = target->addr_family; + if (target->addr_family == AF_INET) { + rprefix.length = IPV4_MAX_BITLEN; + } else { + rprefix.length = IPV6_MAX_BITLEN; + } + + pNHE = rfapiEthRouteNode2NextHopList( + rn, &rprefix, rfd->response_lifetime, self_vn_addr, + rfd->rib[afi], &p_original); + + + } else { + /* + * Generate answer to query + */ + pNHE = rfapiRouteNode2NextHopList(rn, rfd->response_lifetime, + self_vn_addr, rfd->rib[afi], + &p_original); + } + + agg_unlock_node(rn); + +done: + if (ppNextHopEntry) { + /* only count if caller gets it */ + ++bgp->rfapi->response_immediate_count; + } + + if (!pNHE) { + vnc_zlog_debug_verbose("%s: NO NHEs, returning ENOENT", + __func__); + return ENOENT; + } + + /* + * count nexthops for statistics + */ + for (i_nhe = pNHE; i_nhe; i_nhe = i_nhe->next) { + ++rfd->stat_count_nh_reachable; + } + + if (ppNextHopEntry) { + *ppNextHopEntry = pNHE; + } else { + rfapi_free_next_hop_list(pNHE); + } + + vnc_zlog_debug_verbose("%s: success", __func__); + return 0; +} + +/* + * support on-the-fly reassignment of an already-open nve to a new + * nve-group in the event that its original nve-group is + * administratively deleted. + */ +static int rfapi_open_rfd(struct rfapi_descriptor *rfd, struct bgp *bgp) +{ + struct prefix pfx_vn; + struct prefix pfx_un; + struct rfapi_nve_group_cfg *rfg; + struct rfapi *h; + struct rfapi_cfg *hc; + int rc; + + h = bgp->rfapi; + if (!h) + return ENXIO; + + hc = bgp->rfapi_cfg; + if (!hc) + return ENXIO; + + rc = rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn); + assert(!rc); + + rc = rfapiRaddr2Qprefix(&rfd->un_addr, &pfx_un); + assert(!rc); + + /* + * Find the matching nve group config block + */ + rfg = bgp_rfapi_cfg_match_group(hc, &pfx_vn, &pfx_un); + if (!rfg) { + return ENOENT; + } + + /* + * check nve group config block for required values + */ + if (!rfg->rt_export_list || !rfg->rfapi_import_table) { + + return ENOMSG; + } + + rc = rfapi_open_inner(rfd, bgp, h, rfg); + if (rc) { + return rc; + } + + /* + * re-advertise registered routes, this time as part of new NVE-group + */ + rfapiApReadvertiseAll(bgp, rfd); + + /* + * re-attach callbacks to import table + */ + if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + rfapiMonitorAttachImportHd(rfd); + } + + return 0; +} + +/*------------------------------------------ + * rfapi_open + * + * This function initializes a NVE record and associates it with + * the specified VN and underlay network addresses + * + * input: + * rfp_start_val value returned by rfp_start + * vn NVE virtual network address + * + * un NVE underlay network address + * + * default_options Default options to use on registrations. + * For now only tunnel type is supported. + * May be overridden per-prefix in rfapi_register(). + * Caller owns (rfapi_open() does not free) + * + * response_cb Pointer to next hop list update callback function or + * NULL when no callbacks are desired. + * + * userdata Passed to subsequent response_cb invocations. + * + * output: + * response_lifetime The length of time that responses sent to this + * NVE are valid. + * + * pHandle pointer to location to store rfapi handle. The + * handle must be passed on subsequent rfapi_ calls. + * + * + * return value: + * 0 Success + * EEXIST NVE with this {vn,un} already open + * ENOENT No matching nve group config + * ENOMSG Matched nve group config was incomplete + * ENXIO BGP or VNC not configured + * EAFNOSUPPORT Matched nve group specifies auto-assignment of RD, + * but underlay network address is not IPv4 + * EDEADLK Called from within a callback procedure + *------------------------------------------*/ +int rfapi_open(void *rfp_start_val, struct rfapi_ip_addr *vn, + struct rfapi_ip_addr *un, + struct rfapi_un_option *default_options, + uint32_t *response_lifetime, + void *userdata, /* callback cookie */ + rfapi_handle *pHandle) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_descriptor *rfd; + struct rfapi_cfg *hc; + struct rfapi_nve_group_cfg *rfg; + + struct prefix pfx_vn; + struct prefix pfx_un; + + int rc; + rfapi_handle hh = NULL; + int reusing_provisional = 0; + + { + char buf[2][INET_ADDRSTRLEN]; + vnc_zlog_debug_verbose( + "%s: VN=%s UN=%s", __func__, + rfapiRfapiIpAddr2Str(vn, buf[0], INET_ADDRSTRLEN), + rfapiRfapiIpAddr2Str(un, buf[1], INET_ADDRSTRLEN)); + } + + assert(pHandle); + *pHandle = NULL; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (!bgp) + return ENXIO; + + h = bgp->rfapi; + if (!h) + return ENXIO; + + hc = bgp->rfapi_cfg; + if (!hc) + return ENXIO; + + if (h->flags & RFAPI_INCALLBACK) + return EDEADLK; + + rc = rfapiRaddr2Qprefix(vn, &pfx_vn); + assert(!rc); + + rc = rfapiRaddr2Qprefix(un, &pfx_un); + assert(!rc); + + /* + * already have a descriptor with VN and UN? + */ + if (!rfapi_find_handle(bgp, vn, un, &hh)) { + /* + * we might have set up a handle for static routes before + * this NVE was opened. In that case, reuse the handle + */ + rfd = hh; + if (!CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_PROVISIONAL)) { + return EEXIST; + } + + /* + * reuse provisional descriptor + * hh is not NULL + */ + reusing_provisional = 1; + } + + /* + * Find the matching nve group config block + */ + rfg = bgp_rfapi_cfg_match_group(hc, &pfx_vn, &pfx_un); + if (!rfg) { + ++h->stat.count_unknown_nves; + { + char buf[2][INET_ADDRSTRLEN]; + zlog_notice("%s: no matching group VN=%s UN=%s", + __func__, + rfapiRfapiIpAddr2Str(vn, buf[0], + INET_ADDRSTRLEN), + rfapiRfapiIpAddr2Str(un, buf[1], + INET_ADDRSTRLEN)); + } + return ENOENT; + } + + /* + * check nve group config block for required values + */ + if (!rfg->rt_export_list || !rfg->rfapi_import_table) { + + ++h->stat.count_unknown_nves; + return ENOMSG; + } + + /* + * If group config specifies auto-rd assignment, check that + * VN address is IPv4|v6 so we don't fail in rfapi_open_inner(). + * Check here so we don't need to unwind memory allocations, &c. + */ + if ((rfg->rd.family == AF_UNIX) && (vn->addr_family != AF_INET) + && (vn->addr_family != AF_INET6)) { + return EAFNOSUPPORT; + } + + if (hh) { + /* + * reusing provisional rfd + */ + rfd = hh; + } else { + rfd = XCALLOC(MTYPE_RFAPI_DESC, + sizeof(struct rfapi_descriptor)); + } + + rfd->bgp = bgp; + if (default_options) { + struct rfapi_un_option *p; + + for (p = default_options; p; p = p->next) { + if ((RFAPI_UN_OPTION_TYPE_PROVISIONAL == p->type)) { + rfd->flags |= RFAPI_HD_FLAG_PROVISIONAL; + } + if ((RFAPI_UN_OPTION_TYPE_TUNNELTYPE == p->type)) { + rfd->default_tunneltype_option = p->v.tunnel; + } + } + } + + /* + * Fill in caller fields + */ + rfd->vn_addr = *vn; + rfd->un_addr = *un; + rfd->cookie = userdata; + + if (!reusing_provisional) { + rc = rfapi_init_and_open(bgp, rfd, rfg); + /* + * This can fail only if the VN address is IPv6 and the group + * specified auto-assignment of RDs, which only works for v4, + * and the check above should catch it. + * + * Another failure possibility is that we were called + * during an rfapi callback. Also checked above. + */ + assert(!rc); + } + + if (response_lifetime) + *response_lifetime = rfd->response_lifetime; + *pHandle = rfd; + return 0; +} + +/* + * For use with debug functions + */ +static int rfapi_set_response_cb(struct rfapi_descriptor *rfd, + rfapi_response_cb_t *response_cb) +{ + if (!is_valid_rfd(rfd)) + return EBADF; + rfd->response_cb = response_cb; + return 0; +} + +/* + * rfapi_close_inner + * + * Does almost all the work of rfapi_close, except: + * 1. preserves the descriptor (doesn't free it) + * 2. preserves the prefix query list (i.e., rfd->mon list) + * 3. preserves the advertised prefix list (rfd->advertised) + * 4. preserves the rib and rib_pending tables + * + * The purpose of organizing it this way is to support on-the-fly + * reassignment of an already-open nve to a new nve-group in the + * event that its original nve-group is administratively deleted. + */ +static int rfapi_close_inner(struct rfapi_descriptor *rfd, struct bgp *bgp) +{ + int rc; + struct prefix pfx_vn; + struct prefix_rd prd; /* currently always 0 for VN->UN */ + + if (!is_valid_rfd(rfd)) + return EBADF; + + rc = rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn); + assert(!rc); /* should never have bad AF in stored vn address */ + + /* + * update exported routes to reflect disappearance of this NVE as + * nexthop + */ + vnc_direct_bgp_del_nve(bgp, rfd); + vnc_zebra_del_nve(bgp, rfd); + + /* + * unlink this HD's monitors from import table + */ + rfapiMonitorDetachImportHd(rfd); + + /* + * Unlink from Import Table + * NB rfd->import_table will be NULL if we are closing a stale + * descriptor + */ + if (rfd->import_table) + rfapiImportTableRefDelByIt(bgp, rfd->import_table); + rfd->import_table = NULL; + + /* + * Construct route distinguisher + */ + memset(&prd, 0, sizeof(prd)); + prd = rfd->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + /* + * withdraw tunnel + */ + del_vnc_route(rfd, rfd->peer, bgp, SAFI_ENCAP, + &pfx_vn, /* prefix being advertised */ + &prd, /* route distinguisher to use (0 for ENCAP) */ + ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0); /* no kill */ + + /* + * Construct route distinguisher for VPN routes + */ + prd = rfd->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + /* + * find all VPN routes associated with this rfd and delete them, too + */ + rfapiApWithdrawAll(bgp, rfd); + + /* + * remove this nve descriptor from the list of nves + * associated with the nve group + */ + if (rfd->rfg) { + listnode_delete(rfd->rfg->nves, rfd); + rfd->rfg = NULL; /* XXX mark as orphaned/stale */ + } + + if (rfd->rt_export_list) + ecommunity_free(&rfd->rt_export_list); + rfd->rt_export_list = NULL; + + /* + * free peer structure (possibly delayed until its + * refcount reaches zero) + */ + if (rfd->peer) { + vnc_zlog_debug_verbose("%s: calling peer_delete(%p), #%d", + __func__, rfd->peer, rfd->peer->lock); + peer_delete(rfd->peer); + } + rfd->peer = NULL; + + return 0; +} + +int rfapi_close(void *handle) +{ + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + int rc; + struct agg_node *node; + struct bgp *bgp; + struct rfapi *h; + + vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); + +#ifdef RFAPI_WHO_IS_CALLING_ME + zlog_backtrace(LOG_INFO); +#endif + + bgp = rfd->bgp; + if (!bgp) + return ENXIO; + + h = bgp->rfapi; + if (!h) + return ENXIO; + + if (!is_valid_rfd(rfd)) + return EBADF; + + if (h->flags & RFAPI_INCALLBACK) { + /* + * Queue these close requests for processing after callback + * is finished + */ + if (!CHECK_FLAG(rfd->flags, + RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY)) { + work_queue_add(h->deferred_close_q, handle); + vnc_zlog_debug_verbose( + "%s: added handle %p to deferred close queue", + __func__, handle); + } + return 0; + } + + if (CHECK_FLAG(rfd->flags, RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY)) { + + vnc_zlog_debug_verbose("%s administrative close rfd=%p", + __func__, rfd); + + if (h->rfp_methods.close_cb) { + vnc_zlog_debug_verbose( + "%s calling close callback rfd=%p", __func__, + rfd); + + /* + * call the callback fairly early so that it can still + * lookup un/vn + * from handle, etc. + * + * NB RFAPI_INCALLBACK is tested above, so if we reach + * this point + * we are not already in the context of a callback. + */ + h->flags |= RFAPI_INCALLBACK; + (*h->rfp_methods.close_cb)(handle, EIDRM); + h->flags &= ~RFAPI_INCALLBACK; + } + } + + if (rfd->rfg) { + /* + * Orphaned descriptors have already done this part, so do + * only for non-orphaned descriptors. + */ + if ((rc = rfapi_close_inner(rfd, bgp))) + return rc; + } + + /* + * Remove descriptor from UN index + * (remove from chain at node) + */ + rc = rfapi_find_node(bgp, &rfd->vn_addr, &rfd->un_addr, &node); + if (!rc) { + struct rfapi_descriptor *hh; + + if (node->info == rfd) { + node->info = rfd->next; + } else { + + for (hh = node->info; hh; hh = hh->next) { + if (hh->next == rfd) { + hh->next = rfd->next; + break; + } + } + } + agg_unlock_node(node); + } + + /* + * remove from descriptor list + */ + listnode_delete(&h->descriptors, rfd); + + /* + * Delete monitor list items and free monitor structures + */ + (void)rfapiMonitorDelHd(rfd); + + /* + * release advertised prefix data + */ + rfapiApRelease(&rfd->advertised); + + /* + * Release RFP callback RIB + */ + rfapiRibFree(rfd); + + /* + * free descriptor + */ + memset(rfd, 0, sizeof(struct rfapi_descriptor)); + XFREE(MTYPE_RFAPI_DESC, rfd); + + return 0; +} + +/* + * Reopen a nve descriptor. If the descriptor's NVE-group + * does not exist (e.g., if it has been administratively removed), + * reassignment to a new NVE-group is attempted. + * + * If NVE-group reassignment fails, the descriptor becomes "stale" + * (rfd->rfg == NULL implies "stale:). The only permissible API operation + * on a stale descriptor is rfapi_close(). Any other rfapi_* API operation + * on the descriptor will return ESTALE. + * + * Reopening a descriptor is a potentially expensive operation, because + * it involves withdrawing any routes advertised by the NVE, withdrawing + * the NVE's route queries, and then re-adding them all after a new + * NVE-group is assigned. There are also possible route-export affects + * caused by deleting and then adding the NVE: advertised prefixes + * and nexthop lists for exported routes can turn over. + */ +int rfapi_reopen(struct rfapi_descriptor *rfd, struct bgp *bgp) +{ + struct rfapi *h; + int rc; + + if ((rc = rfapi_close_inner(rfd, bgp))) { + return rc; + } + if ((rc = rfapi_open_rfd(rfd, bgp))) { + + h = bgp->rfapi; + + assert(h != NULL && !CHECK_FLAG(h->flags, RFAPI_INCALLBACK)); + + if (CHECK_FLAG(rfd->flags, + RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY) + && h && h->rfp_methods.close_cb) { + + /* + * NB RFAPI_INCALLBACK is tested above, so if we reach + * this point + * we are not already in the context of a callback. + */ + h->flags |= RFAPI_INCALLBACK; + (*h->rfp_methods.close_cb)((rfapi_handle)rfd, ESTALE); + h->flags &= ~RFAPI_INCALLBACK; + } + return rc; + } + return 0; +} + +/*********************************************************************** + * NVE Routes + ***********************************************************************/ +/* + * Announce reachability to this prefix via the NVE + */ +int rfapi_register(void *handle, struct rfapi_ip_prefix *prefix, + uint32_t lifetime, /* host byte order */ + struct rfapi_un_option *options_un, + struct rfapi_vn_option *options_vn, + rfapi_register_action action) +{ + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + struct bgp *bgp; + struct prefix p; + struct prefix *pfx_ip = NULL; + struct prefix_rd prd; + afi_t afi; + struct prefix pfx_mac_buf; + struct prefix *pfx_mac = NULL; + struct prefix pfx_vn_buf; + const char *action_str = NULL; + uint32_t *label = NULL; + struct rfapi_vn_option *vo; + struct rfapi_l2address_option *l2o = NULL; + struct prefix_rd *prd_override = NULL; + + switch (action) { + case RFAPI_REGISTER_ADD: + action_str = "add"; + break; + case RFAPI_REGISTER_WITHDRAW: + action_str = "withdraw"; + break; + case RFAPI_REGISTER_KILL: + action_str = "kill"; + break; + default: + assert(0); + break; + } + + /* + * Inspect VN options + */ + for (vo = options_vn; vo; vo = vo->next) { + if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { + l2o = &vo->v.l2addr; + } + if (RFAPI_VN_OPTION_TYPE_INTERNAL_RD == vo->type) { + prd_override = &vo->v.internal_rd; + } + } + + /********************************************************************* + * advertise prefix + *********************************************************************/ + + /* + * set

based on + */ + assert(!rfapiRprefix2Qprefix(prefix, &p)); + + afi = family2afi(prefix->prefix.addr_family); + assert(afi); + + vnc_zlog_debug_verbose( + "%s(rfd=%p, pfx=%pFX, lifetime=%d, opts_un=%p, opts_vn=%p, action=%s)", + __func__, rfd, &p, lifetime, options_un, options_vn, + action_str); + + /* + * These tests come after the prefix conversion so that we can + * print the prefix in a debug message before failing + */ + + bgp = rfd->bgp; + if (!bgp) { + vnc_zlog_debug_verbose("%s: no BGP instance: returning ENXIO", + __func__); + return ENXIO; + } + if (!bgp->rfapi) { + vnc_zlog_debug_verbose("%s: no RFAPI instance: returning ENXIO", + __func__); + return ENXIO; + } + if (!rfd->rfg) { + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations_failed; + } + vnc_zlog_debug_verbose( + "%s: rfd=%p, no RF GRP instance: returning ESTALE", + __func__, rfd); + return ESTALE; + } + + if (bgp->rfapi->flags & RFAPI_INCALLBACK) { + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations_failed; + } + vnc_zlog_debug_verbose("%s: in callback: returning EDEADLK", + __func__); + return EDEADLK; + } + + if (!is_valid_rfd(rfd)) { + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations_failed; + } + vnc_zlog_debug_verbose("%s: invalid handle: returning EBADF", + __func__); + return EBADF; + } + + /* + * Is there a MAC address in this registration? + */ + if (l2o && !RFAPI_0_ETHERADDR(&l2o->macaddr)) { + rfapiL2o2Qprefix(l2o, &pfx_mac_buf); + pfx_mac = &pfx_mac_buf; + } + + /* + * Is there an IP prefix in this registration? + */ + if (!(RFAPI_0_PREFIX(&p) && RFAPI_HOST_PREFIX(&p))) { + pfx_ip = &p; + } else { + if (!pfx_mac) { + vnc_zlog_debug_verbose( + "%s: missing mac addr that is required for host 0 pfx", + __func__); + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations_failed; + } + return EINVAL; + } + if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) { + vnc_zlog_debug_verbose( + "%s: handle has bad vn_addr: returning EBADF", + __func__); + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations_failed; + } + return EBADF; + } + } + + if (RFAPI_REGISTER_ADD == action) { + ++bgp->rfapi->stat.count_registrations; + } + + /* + * Figure out if this registration is missing an IP address + * + * MAC-addr based: + * + * In RFAPI, we use prefixes in family AF_LINK to store + * the MAC addresses. These prefixes are used for the + * list of advertised prefixes and in the RFAPI import + * tables. + * + * In BGP proper, we use the prefix matching the NVE's + * VN address with a host prefix-length (i.e., 32 or 128). + * + */ + if (l2o && l2o->logical_net_id && RFAPI_0_PREFIX(&p) + && RFAPI_HOST_PREFIX(&p)) { + + rfapiL2o2Qprefix(l2o, &pfx_mac_buf); + pfx_mac = &pfx_mac_buf; + } + + /* + * Construct route distinguisher + */ + if (prd_override) { + prd = *prd_override; + } else { + memset(&prd, 0, sizeof(prd)); + if (pfx_mac) { + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + encode_rd_type(RD_TYPE_VNC_ETH, prd.val); + if (l2o->local_nve_id + || !(rfd->rfg->flags & RFAPI_RFG_L2RD)) { + /* + * If Local NVE ID is specified in message, use + * it. + * (if no local default configured, also use it + * even if 0) + */ + prd.val[1] = l2o->local_nve_id; + } else { + if (rfd->rfg->l2rd) { + /* + * locally-configured literal value + */ + prd.val[1] = rfd->rfg->l2rd; + } else { + /* + * 0 means auto:vn, which means use LSB + * of VN addr + */ + if (rfd->vn_addr.addr_family + == AF_INET) { + prd.val[1] = + *(((char *)&rfd->vn_addr + .addr.v4 + .s_addr) + + 3); + } else { + prd.val[1] = + *(((char *)&rfd->vn_addr + .addr.v6 + .s6_addr) + + 15); + } + } + } + memcpy(prd.val + 2, pfx_mac->u.prefix_eth.octet, 6); + } else { + prd = rfd->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + } + } + + + if (action == RFAPI_REGISTER_WITHDRAW + || action == RFAPI_REGISTER_KILL) { + + int adv_tunnel = 0; + + /* + * withdraw previous advertisement + */ + del_vnc_route( + rfd, rfd->peer, bgp, SAFI_MPLS_VPN, + pfx_ip ? pfx_ip + : &pfx_vn_buf, /* prefix being advertised */ + &prd, /* route distinguisher (0 for ENCAP) */ + ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, + action == RFAPI_REGISTER_KILL); + + if (0 == rfapiApDelete(bgp, rfd, &p, pfx_mac, &prd, + &adv_tunnel)) { + if (adv_tunnel) + rfapiTunnelRouteAnnounce( + bgp, rfd, &rfd->max_prefix_lifetime); + } + + } else { + + int adv_tunnel = 0; + uint32_t local_pref; + struct ecommunity *rtlist = NULL; + struct ecommunity_val ecom_value; + + if (!rfapiApCount(rfd)) { + /* + * make sure we advertise tunnel route upon adding the + * first VPN route + */ + adv_tunnel = 1; + } + + if (rfapiApAdd(bgp, rfd, &p, pfx_mac, &prd, lifetime, + prefix->cost, l2o)) { + adv_tunnel = 1; + } + + vnc_zlog_debug_verbose("%s: adv_tunnel = %d", __func__, + adv_tunnel); + if (adv_tunnel) { + vnc_zlog_debug_verbose("%s: announcing tunnel route", + __func__); + rfapiTunnelRouteAnnounce(bgp, rfd, + &rfd->max_prefix_lifetime); + } + + vnc_zlog_debug_verbose("%s: calling add_vnc_route", __func__); + + local_pref = rfp_cost_to_localpref(prefix->cost); + + if (l2o && l2o->label) + label = &l2o->label; + + if (pfx_mac) { + struct ecommunity *l2com = NULL; + + if (label) { + l2com = bgp_rfapi_get_ecommunity_by_lni_label( + bgp, 1, l2o->logical_net_id, *label); + } + if (l2com) { + rtlist = ecommunity_dup(l2com); + } else { + /* + * If mac address is set, add an RT based on the + * registered LNI + */ + memset((char *)&ecom_value, 0, + sizeof(ecom_value)); + ecom_value.val[1] = ECOMMUNITY_ROUTE_TARGET; + ecom_value.val[5] = + (l2o->logical_net_id >> 16) & 0xff; + ecom_value.val[6] = + (l2o->logical_net_id >> 8) & 0xff; + ecom_value.val[7] = + (l2o->logical_net_id >> 0) & 0xff; + rtlist = ecommunity_new(); + ecommunity_add_val(rtlist, &ecom_value, + false, false); + } + if (l2o->tag_id) { + as_t as = bgp->as; + uint16_t val = l2o->tag_id; + memset((char *)&ecom_value, 0, + sizeof(ecom_value)); + ecom_value.val[1] = ECOMMUNITY_ROUTE_TARGET; + if (as > BGP_AS_MAX) { + ecom_value.val[0] = + ECOMMUNITY_ENCODE_AS4; + ecom_value.val[2] = (as >> 24) & 0xff; + ecom_value.val[3] = (as >> 16) & 0xff; + ecom_value.val[4] = (as >> 8) & 0xff; + ecom_value.val[5] = as & 0xff; + } else { + ecom_value.val[0] = + ECOMMUNITY_ENCODE_AS; + ecom_value.val[2] = (as >> 8) & 0xff; + ecom_value.val[3] = as & 0xff; + } + ecom_value.val[6] = (val >> 8) & 0xff; + ecom_value.val[7] = val & 0xff; + if (rtlist == NULL) + rtlist = ecommunity_new(); + ecommunity_add_val(rtlist, &ecom_value, + false, false); + } + } + + /* + * advertise prefix via tunnel endpoint + */ + add_vnc_route( + rfd, /* rfapi descr, for export list & backref */ + bgp, /* which bgp instance */ + SAFI_MPLS_VPN, /* which SAFI */ + (pfx_ip ? pfx_ip + : &pfx_vn_buf), /* prefix being advertised */ + &prd, /* route distinguisher to use (0 for ENCAP) */ + &rfd->vn_addr, /* nexthop */ + &local_pref, + &lifetime, /* prefix lifetime -> Tunnel Encap attr */ + NULL, options_un, /* rfapi un options */ + options_vn, /* rfapi vn options */ + (rtlist ? rtlist : rfd->rt_export_list), NULL, /* med */ + label, /* label: default */ + ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); + + if (rtlist) + ecommunity_free(&rtlist); /* sets rtlist = NULL */ + } + + vnc_zlog_debug_verbose("%s: success", __func__); + return 0; +} + +int rfapi_query(void *handle, struct rfapi_ip_addr *target, + struct rfapi_l2address_option *l2o, /* may be NULL */ + struct rfapi_next_hop_entry **ppNextHopEntry) +{ + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + struct bgp *bgp = rfd->bgp; + int rc; + + assert(ppNextHopEntry); + *ppNextHopEntry = NULL; + + if (bgp && bgp->rfapi) { + bgp->rfapi->stat.count_queries++; + } + + if (!rfd->rfg) { + if (bgp && bgp->rfapi) + ++bgp->rfapi->stat.count_queries_failed; + return ESTALE; + } + + if ((rc = rfapi_query_inner(handle, target, l2o, ppNextHopEntry))) { + if (bgp && bgp->rfapi) + ++bgp->rfapi->stat.count_queries_failed; + } + return rc; +} + +int rfapi_query_done(rfapi_handle handle, struct rfapi_ip_addr *target) +{ + struct prefix p; + int rc; + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + struct bgp *bgp = rfd->bgp; + + if (!rfd->rfg) + return ESTALE; + + assert(target); + rc = rfapiRaddr2Qprefix(target, &p); + assert(!rc); + + if (!is_valid_rfd(rfd)) + return EBADF; + + /* preemptive */ + if (!bgp || !bgp->rfapi) + return ENXIO; + + if (bgp->rfapi->flags & RFAPI_INCALLBACK) + return EDEADLK; + + rfapiMonitorDel(bgp, rfd, &p); + + return 0; +} + +int rfapi_query_done_all(rfapi_handle handle, int *count) +{ + struct rfapi_descriptor *rfd = (struct rfapi_descriptor *)handle; + struct bgp *bgp = rfd->bgp; + ; + int num; + + if (!rfd->rfg) + return ESTALE; + + if (!is_valid_rfd(rfd)) + return EBADF; + + /* preemptive */ + if (!bgp || !bgp->rfapi) + return ENXIO; + + if (bgp->rfapi->flags & RFAPI_INCALLBACK) + return EDEADLK; + + num = rfapiMonitorDelHd(rfd); + + if (count) + *count = num; + + return 0; +} + +void rfapi_free_next_hop_list(struct rfapi_next_hop_entry *list) +{ + struct rfapi_next_hop_entry *nh; + struct rfapi_next_hop_entry *next; + + for (nh = list; nh; nh = next) { + next = nh->next; + rfapi_un_options_free(nh->un_options); + nh->un_options = NULL; + rfapi_vn_options_free(nh->vn_options); + nh->vn_options = NULL; + XFREE(MTYPE_RFAPI_NEXTHOP, nh); + } +} + +/* + * NULL handle => return total count across all nves + */ +uint32_t rfapi_monitor_count(void *handle) +{ + struct bgp *bgp = bgp_get_default(); + uint32_t count; + + if (handle) { + struct rfapi_descriptor *rfd = + (struct rfapi_descriptor *)handle; + count = rfd->monitor_count; + } else { + + if (!bgp || !bgp->rfapi) + return 0; + + count = bgp->rfapi->monitor_count; + } + + return count; +} + +/*********************************************************************** + * CLI/CONFIG + ***********************************************************************/ + +DEFUN (debug_rfapi_show_nves, + debug_rfapi_show_nves_cmd, + "debug rfapi-dev show nves", + DEBUG_STR + DEBUG_RFAPI_STR + SHOW_STR + "NVE Information\n") +{ + rfapiPrintMatchingDescriptors(vty, NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN ( + debug_rfapi_show_nves_vn_un, + debug_rfapi_show_nves_vn_un_cmd, + "debug rfapi-dev show nves ", /* prefix also ok */ + DEBUG_STR + DEBUG_RFAPI_STR + SHOW_STR + "NVE Information\n" + "Specify virtual network\n" + "Specify underlay network interface\n" + "IPv4 address\n" + "IPv6 address\n") +{ + struct prefix pfx; + + if (!str2prefix(argv[5]->arg, &pfx)) { + vty_out(vty, "Malformed address \"%s\"\n", argv[5]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "Invalid address \"%s\"\n", argv[5]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv[4]->arg[0] == 'u') { + rfapiPrintMatchingDescriptors(vty, NULL, &pfx); + } else { + rfapiPrintMatchingDescriptors(vty, &pfx, NULL); + } + return CMD_SUCCESS; +} + +/* + * Note: this function does not flush vty output, so if it is called + * with a stream pointing to a vty, the user will have to type something + * before the callback output shows up + */ +static void test_nexthops_callback( + // struct rfapi_ip_addr *target, + struct rfapi_next_hop_entry *next_hops, void *userdata) +{ + void *stream = userdata; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + fp(out, "Nexthops Callback, Target=("); + // rfapiPrintRfapiIpAddr(stream, target); + fp(out, ")\n"); + + rfapiPrintNhl(stream, next_hops); + + fp(out, "\n"); + + rfapi_free_next_hop_list(next_hops); +} + +DEFUN (debug_rfapi_open, + debug_rfapi_open_cmd, + "debug rfapi-dev open vn un ", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_open\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate xt addr follows\n" + "underlay network interface IPv4 address\n" + "underlay network interface IPv6 address\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + uint32_t lifetime = 0; + int rc; + rfapi_handle handle; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + rc = rfapi_open(rfapi_get_rfp_start_val_by_bgp(bgp_get_default()), &vn, + &un, /*&uo */ NULL, &lifetime, NULL, &handle); + + vty_out(vty, "rfapi_open: status %d, handle %p, lifetime %d\n", rc, + handle, lifetime); + + rc = rfapi_set_response_cb(handle, test_nexthops_callback); + + vty_out(vty, "rfapi_set_response_cb: status %d\n", rc); + + return CMD_SUCCESS; +} + + +DEFUN (debug_rfapi_close_vn_un, + debug_rfapi_close_vn_un_cmd, + "debug rfapi-dev close vn un ", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_close\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate xt addr follows\n" + "underlay network interface IPv4 address\n" + "underlay network interface IPv6 address\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + rfapi_handle handle; + int rc; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[4]->arg, argv[6]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = rfapi_close(handle); + + vty_out(vty, "rfapi_close(handle=%p): status %d\n", handle, rc); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_close_rfd, + debug_rfapi_close_rfd_cmd, + "debug rfapi-dev close rfd HANDLE", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_close\n" + "indicate handle follows\n" "rfapi handle in hexadecimal\n") +{ + rfapi_handle handle; + int rc; + char *endptr = NULL; + + handle = (rfapi_handle)(uintptr_t)(strtoull(argv[4]->arg, &endptr, 16)); + + if (*endptr != '\0' || (uintptr_t)handle == UINTPTR_MAX) { + vty_out(vty, "Invalid value: %s\n", argv[4]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + rc = rfapi_close(handle); + + vty_out(vty, "rfapi_close(handle=%p): status %d\n", handle, rc); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_register_vn_un, + debug_rfapi_register_vn_un_cmd, + "debug rfapi-dev register vn un prefix lifetime SECONDS [cost (0-255)]", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_register\n" + "indicate vn addr follows\n" + "virtual network IPv4 interface address\n" + "virtual network IPv6 interface address\n" + "indicate un addr follows\n" + "underlay network IPv4 interface address\n" + "underlay network IPv6 interface address\n" + "indicate prefix follows\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "indicate lifetime follows\n" + "lifetime\n" + "Cost (localpref = 255-cost)\n" + "0-255\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + rfapi_handle handle; + struct prefix pfx; + uint32_t lifetime; + struct rfapi_ip_prefix hpfx; + int rc; + uint8_t cost = 100; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[4]->arg, argv[6]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * Get prefix to advertise + */ + if (!str2prefix(argv[8]->arg, &pfx)) { + vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + rfapiQprefix2Rprefix(&pfx, &hpfx); + + if (strmatch(argv[10]->text, "infinite")) { + lifetime = RFAPI_INFINITE_LIFETIME; + } else { + lifetime = strtoul(argv[10]->arg, NULL, 10); + } + + if (argc >= 13) + cost = (uint8_t) strtoul(argv[12]->arg, NULL, 10); + hpfx.cost = cost; + + rc = rfapi_register(handle, &hpfx, lifetime, NULL, NULL, + RFAPI_REGISTER_ADD); + if (rc) { + vty_out(vty, "rfapi_register failed with rc=%d (%s)\n", rc, + strerror(rc)); + } + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_register_vn_un_l2o, + debug_rfapi_register_vn_un_l2o_cmd, + "debug rfapi-dev register vn un prefix lifetime SECONDS macaddr X:X:X:X:X:X lni (0-16777215)", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_register\n" + "indicate vn addr follows\n" + "virtual network IPv4 interface address\n" + "virtual network IPv6 interface address\n" + "indicate un addr follows\n" + "underlay network IPv4 interface address\n" + "underlay network IPv6 interface address\n" + "indicate prefix follows\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "indicate lifetime follows\n" + "Seconds of lifetime\n" + "indicate MAC address follows\n" + "MAC address\n" + "indicate lni follows\n" + "lni value range\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + rfapi_handle handle; + struct prefix pfx; + uint32_t lifetime; + struct rfapi_ip_prefix hpfx; + int rc; + struct rfapi_vn_option optary[10]; /* XXX must be big enough */ + struct rfapi_vn_option *opt = NULL; + int opt_next = 0; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[4]->arg, argv[6]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * Get prefix to advertise + */ + if (!str2prefix(argv[8]->arg, &pfx)) { + vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + rfapiQprefix2Rprefix(&pfx, &hpfx); + + if (strmatch(argv[10]->text, "infinite")) { + lifetime = RFAPI_INFINITE_LIFETIME; + } else { + lifetime = strtoul(argv[10]->arg, NULL, 10); + } + + /* L2 option parsing START */ + memset(optary, 0, sizeof(optary)); + optary[opt_next].v.l2addr.logical_net_id = + strtoul(argv[14]->arg, NULL, 10); + if (rfapiStr2EthAddr(argv[12]->arg, + &optary[opt_next].v.l2addr.macaddr)) { + vty_out(vty, "Bad mac address \"%s\"\n", argv[12]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + optary[opt_next].type = RFAPI_VN_OPTION_TYPE_L2ADDR; + opt = optary; + + /* L2 option parsing END */ + + /* TBD fixme */ + rc = rfapi_register(handle, &hpfx, lifetime, NULL /* &uo */, opt, + RFAPI_REGISTER_ADD); + if (rc) { + vty_out(vty, "rfapi_register failed with rc=%d (%s)\n", rc, + strerror(rc)); + } + + return CMD_SUCCESS; +} + + +DEFUN (debug_rfapi_unregister_vn_un, + debug_rfapi_unregister_vn_un_cmd, + "debug rfapi-dev unregister vn un prefix [kill]", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_unregister\n" + "indicate vn addr follows\n" + "virtual network interface address\n" + "virtual network interface address\n" + "indicate xt addr follows\n" + "underlay network interface address\n" + "underlay network interface address\n" + "prefix to remove\n" + "prefix to remove\n" + "prefix to remove\n" + "Remove without holddown\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + rfapi_handle handle; + struct prefix pfx; + struct rfapi_ip_prefix hpfx; + int rc; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[4]->arg, argv[6]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * Get prefix to advertise + */ + if (!str2prefix(argv[8]->arg, &pfx)) { + vty_out(vty, "Malformed prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "Bad family for prefix \"%s\"\n", argv[8]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + rfapiQprefix2Rprefix(&pfx, &hpfx); + + rfapi_register(handle, &hpfx, 0, NULL, NULL, + (argc == 10 ? + RFAPI_REGISTER_KILL : RFAPI_REGISTER_WITHDRAW)); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_query_vn_un, + debug_rfapi_query_vn_un_cmd, + "debug rfapi-dev query vn un target ", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_query\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate un addr follows\n" + "IPv4 un address\n" + "IPv6 un address\n" + "indicate target follows\n" + "target IPv4 address\n" + "target IPv6 address\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + struct rfapi_ip_addr target; + rfapi_handle handle; + int rc; + struct rfapi_next_hop_entry *pNextHopEntry; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + + /* + * Get target addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[8]->arg, &target))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[4]->arg, argv[6]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * options parameter not used? Set to NULL for now + */ + rc = rfapi_query(handle, &target, NULL, &pNextHopEntry); + + if (rc) { + vty_out(vty, "rfapi_query failed with rc=%d (%s)\n", rc, + strerror(rc)); + } else { + /* + * print nexthop list + */ + test_nexthops_callback(/*&target, */ pNextHopEntry, + vty); /* frees nh list! */ + } + + return CMD_SUCCESS; +} + + +DEFUN (debug_rfapi_query_vn_un_l2o, + debug_rfapi_query_vn_un_l2o_cmd, + "debug rfapi-dev query vn un lni LNI target X:X:X:X:X:X", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_query\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate xt addr follows\n" + "underlay network interface IPv4 address\n" + "underlay network interface IPv6 address\n" + "logical network ID follows\n" + "logical network ID\n" + "indicate target MAC addr follows\n" + "target MAC addr\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + int rc; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[4]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[6]->arg, &un))) + return rc; + + vty_out(vty, "%% This command is broken.\n"); + return CMD_WARNING_CONFIG_FAILED; +} + + +DEFUN (debug_rfapi_query_done_vn_un, + debug_rfapi_query_vn_un_done_cmd, + "debug rfapi-dev query done vn un target ", + DEBUG_STR + DEBUG_RFAPI_STR + "rfapi_query_done\n" + "rfapi_query_done\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate xt addr follows\n" + "underlay network interface IPv4 address\n" + "underlay network interface IPv6 address\n" + "indicate target follows\n" + "Target IPv4 address\n" + "Target IPv6 address\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + struct rfapi_ip_addr target; + rfapi_handle handle; + int rc; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[5]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[7]->arg, &un))) + return rc; + + + /* + * Get target addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[9]->arg, &target))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[5]->arg, argv[7]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * options parameter not used? Set to NULL for now + */ + rc = rfapi_query_done(handle, &target); + + vty_out(vty, "rfapi_query_done returned %d\n", rc); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_show_import, + debug_rfapi_show_import_cmd, + "debug rfapi-dev show import", + DEBUG_STR + DEBUG_RFAPI_STR + SHOW_STR + "import\n") +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + char *s; + int first_l2 = 1; + + /* + * Show all import tables + */ + + bgp = bgp_get_default(); /* assume 1 instance for now */ + if (!bgp) { + vty_out(vty, "No BGP instance\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + h = bgp->rfapi; + if (!h) { + vty_out(vty, "No RFAPI instance\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* + * Iterate over all import tables; do a filtered import + * for the afi/safi combination + */ + + + for (it = h->imports; it; it = it->next) { + s = ecommunity_ecom2str(it->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, "Import Table %p, RTs: %s\n", it, s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + + rfapiShowImportTable(vty, "IP VPN", it->imported_vpn[AFI_IP], + 1); + rfapiShowImportTable(vty, "IP ENCAP", + it->imported_encap[AFI_IP], 0); + rfapiShowImportTable(vty, "IP6 VPN", it->imported_vpn[AFI_IP6], + 1); + rfapiShowImportTable(vty, "IP6 ENCAP", + it->imported_encap[AFI_IP6], 0); + } + + if (h->import_mac) { + void *cursor = NULL; + uint32_t lni; + uintptr_t lni_as_ptr; + int rc; + char buf[BUFSIZ]; + + for (rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, + (void **)&it, &cursor); + !rc; + rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, + (void **)&it, &cursor)) { + + if (it->imported_vpn[AFI_L2VPN]) { + lni = lni_as_ptr; + if (first_l2) { + vty_out(vty, + "\nLNI-based Ethernet Tables:\n"); + first_l2 = 0; + } + snprintf(buf, sizeof(buf), "L2VPN LNI=%u", lni); + rfapiShowImportTable( + vty, buf, it->imported_vpn[AFI_L2VPN], + 1); + } + } + } + + rfapiShowImportTable(vty, "CE IT - IP VPN", + h->it_ce->imported_vpn[AFI_IP], 1); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_show_import_vn_un, + debug_rfapi_show_import_vn_un_cmd, + "debug rfapi-dev show import vn un ", + DEBUG_STR + DEBUG_RFAPI_STR + SHOW_STR + "import\n" + "indicate vn addr follows\n" + "virtual network interface IPv4 address\n" + "virtual network interface IPv6 address\n" + "indicate xt addr follows\n" + "underlay network interface IPv4 address\n" + "underlay network interface IPv6 address\n") +{ + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + rfapi_handle handle; + int rc; + struct rfapi_descriptor *rfd; + + /* + * Get VN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[5]->arg, &vn))) + return rc; + + + /* + * Get UN addr + */ + if ((rc = rfapiCliGetRfapiIpAddr(vty, argv[7]->arg, &un))) + return rc; + + + if (rfapi_find_handle_vty(vty, &vn, &un, &handle)) { + vty_out(vty, "can't locate handle matching vn=%s, un=%s\n", + argv[5]->arg, argv[7]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + rfd = (struct rfapi_descriptor *)handle; + + rfapiShowImportTable(vty, "IP VPN", + rfd->import_table->imported_vpn[AFI_IP], 1); + rfapiShowImportTable(vty, "IP ENCAP", + rfd->import_table->imported_encap[AFI_IP], 0); + rfapiShowImportTable(vty, "IP6 VPN", + rfd->import_table->imported_vpn[AFI_IP6], 1); + rfapiShowImportTable(vty, "IP6 ENCAP", + rfd->import_table->imported_encap[AFI_IP6], 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_rfapi_response_omit_self, + debug_rfapi_response_omit_self_cmd, + "debug rfapi-dev response-omit-self ", + DEBUG_STR + DEBUG_RFAPI_STR + "Omit self in RFP responses\n" + "filter out self from responses\n" "leave self in responses\n") +{ + struct bgp *bgp = bgp_get_default(); + + if (!bgp) { + vty_out(vty, "No BGP process is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (!bgp->rfapi_cfg) { + vty_out(vty, "VNC not configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(argv[3]->text, "on")) + SET_FLAG(bgp->rfapi_cfg->flags, + BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP); + else + UNSET_FLAG(bgp->rfapi_cfg->flags, + BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP); + + return CMD_SUCCESS; +} + + +#ifdef RFAPI_DEBUG_SKIPLIST_CLI + +#include "lib/skiplist.h" +DEFUN (skiplist_test_cli, + skiplist_test_cli_cmd, + "skiplist test", + "skiplist command\n" + "test\n") +{ + skiplist_test(vty); + + return CMD_SUCCESS; +} + +DEFUN (skiplist_debug_cli, + skiplist_debug_cli_cmd, + "skiplist debug", + "skiplist command\n" + "debug\n") +{ + skiplist_debug(vty, NULL); + return CMD_SUCCESS; +} + +#endif /* RFAPI_DEBUG_SKIPLIST_CLI */ + +void rfapi_init(void) +{ + bgp_rfapi_cfg_init(); + vnc_debug_init(); + + install_element(ENABLE_NODE, &debug_rfapi_show_import_cmd); + install_element(ENABLE_NODE, &debug_rfapi_show_import_vn_un_cmd); + + install_element(ENABLE_NODE, &debug_rfapi_open_cmd); + install_element(ENABLE_NODE, &debug_rfapi_close_vn_un_cmd); + install_element(ENABLE_NODE, &debug_rfapi_close_rfd_cmd); + install_element(ENABLE_NODE, &debug_rfapi_register_vn_un_cmd); + install_element(ENABLE_NODE, &debug_rfapi_unregister_vn_un_cmd); + install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_cmd); + install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_done_cmd); + install_element(ENABLE_NODE, &debug_rfapi_query_vn_un_l2o_cmd); + + install_element(ENABLE_NODE, &debug_rfapi_response_omit_self_cmd); + + /* Need the following show commands for gpz test scripts */ + install_element(ENABLE_NODE, &debug_rfapi_show_nves_cmd); + install_element(ENABLE_NODE, &debug_rfapi_show_nves_vn_un_cmd); + install_element(ENABLE_NODE, &debug_rfapi_register_vn_un_l2o_cmd); + +#ifdef RFAPI_DEBUG_SKIPLIST_CLI + install_element(ENABLE_NODE, &skiplist_test_cli_cmd); + install_element(ENABLE_NODE, &skiplist_debug_cli_cmd); +#endif + + rfapi_vty_init(); +} + +#ifdef DEBUG_RFAPI +static void rfapi_print_exported(struct bgp *bgp) +{ + struct bgp_dest *destn; + struct bgp_dest *dest; + struct bgp_path_info *bpi; + + if (!bgp) + return; + + for (destn = bgp_table_top(bgp->rib[AFI_IP][SAFI_MPLS_VPN]); destn; + destn = bgp_route_next(destn)) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(destn); + if (!table) + continue; + fprintf(stderr, "%s: vpn destn=%p\n", __func__, destn); + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bpi = bgp_dest_get_bgp_path_info(dest); + + if (!bpi) + continue; + fprintf(stderr, "%s: dest=%p\n", __func__, dest); + for (; bpi; bpi = bpi->next) { + rfapiPrintBi((void *)2, bpi); /* 2 => stderr */ + } + } + } + for (destn = bgp_table_top(bgp->rib[AFI_IP][SAFI_ENCAP]); destn; + destn = bgp_route_next(destn)) { + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(destn); + if (!table) + continue; + fprintf(stderr, "%s: encap destn=%p\n", __func__, destn); + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + bpi = bgp_dest_get_bgp_path_info(dest); + if (!bpi) + continue; + fprintf(stderr, "%s: dest=%p\n", __func__, dest); + for (; bpi; bpi = bpi->next) { + rfapiPrintBi((void *)2, bpi); /* 2 => stderr */ + } + } + } +} +#endif /* defined(DEBUG_RFAPI) */ + +/* + * Free all memory to prepare for clean exit as seen by valgrind memcheck + */ +void rfapi_delete(struct bgp *bgp) +{ + extern void rfp_clear_vnc_nve_all(void); /* can't fix correctly yet */ + +#if DEBUG_CLEANUP + zlog_debug("%s: bgp %p", __func__, bgp); +#endif + + /* + * This clears queries and registered routes, and closes nves + */ + if (bgp->rfapi) + rfp_clear_vnc_nve_all(); + + /* + * close any remaining descriptors + */ + struct rfapi *h = bgp->rfapi; + + if (h && h->descriptors.count) { + struct listnode *node, *nnode; + struct rfapi_descriptor *rfd; +#if DEBUG_CLEANUP + zlog_debug("%s: descriptor count %u", __func__, + h->descriptors.count); +#endif + for (ALL_LIST_ELEMENTS(&h->descriptors, node, nnode, rfd)) { +#if DEBUG_CLEANUP + zlog_debug("%s: closing rfd %p", __func__, rfd); +#endif + (void)rfapi_close(rfd); + } + } + + bgp_rfapi_cfg_destroy(bgp, bgp->rfapi_cfg); + bgp->rfapi_cfg = NULL; + bgp_rfapi_destroy(bgp, bgp->rfapi); + bgp->rfapi = NULL; +#ifdef DEBUG_RFAPI + /* + * show what's left in the BGP MPLSVPN RIB + */ + rfapi_print_exported(bgp); +#endif +} + +int rfapi_set_autord_from_vn(struct prefix_rd *rd, struct rfapi_ip_addr *vn) +{ + vnc_zlog_debug_verbose("%s: auto-assigning RD", __func__); + if (vn->addr_family != AF_INET && vn->addr_family != AF_INET6) { + vnc_zlog_debug_verbose( + "%s: can't auto-assign RD, VN addr family is not IPv4|v6", + __func__); + return EAFNOSUPPORT; + } + rd->family = AF_UNSPEC; + rd->prefixlen = 64; + rd->val[1] = RD_TYPE_IP; + if (vn->addr_family == AF_INET) { + memcpy(rd->val + 2, &vn->addr.v4.s_addr, 4); + } else { /* is v6 */ + memcpy(rd->val + 2, &vn->addr.v6.s6_addr32[3], + 4); /* low order 4 bytes */ + } + vnc_zlog_debug_verbose("%s: auto-RD is set to %pRDP", __func__, rd); + return 0; +} + +/*------------------------------------------ + * rfapi_bgp_lookup_by_rfp + * + * Find bgp instance pointer based on value returned by rfp_start + * + * input: + * rfp_start_val value returned by rfp_startor + * NULL (=get default instance) + * + * output: + * none + * + * return value: + * bgp bgp instance pointer + * NULL = not found + * + --------------------------------------------*/ +struct bgp *rfapi_bgp_lookup_by_rfp(void *rfp_start_val) +{ + struct bgp *bgp = NULL; + struct listnode *node, *nnode; + + if (rfp_start_val == NULL) + bgp = bgp_get_default(); + else + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + if (bgp->rfapi != NULL + && bgp->rfapi->rfp == rfp_start_val) + return bgp; + return bgp; +} + +/*------------------------------------------ + * rfapi_get_rfp_start_val_by_bgp + * + * Find bgp instance pointer based on value returned by rfp_start + * + * input: + * bgp bgp instance pointer + * + * output: + * none + * + * return value: + * rfp_start_val + * NULL = not found + * + --------------------------------------------*/ +void *rfapi_get_rfp_start_val_by_bgp(struct bgp *bgp) +{ + if (!bgp || !bgp->rfapi) + return NULL; + return bgp->rfapi->rfp; +} + +/*********************************************************************** + * RFP group specific configuration + ***********************************************************************/ +static void *rfapi_rfp_get_or_init_group_config_default(struct rfapi_cfg *rfc, + struct vty *vty, + uint32_t size) +{ + if (rfc->default_rfp_cfg == NULL && size > 0) { + rfc->default_rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); + vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, + size); + } + return rfc->default_rfp_cfg; +} + +static void *rfapi_rfp_get_or_init_group_config_nve(struct rfapi_cfg *rfc, + struct vty *vty, + uint32_t size) +{ + struct rfapi_nve_group_cfg *rfg = + VTY_GET_CONTEXT_SUB(rfapi_nve_group_cfg); + + /* make sure group is still in list */ + if (!rfg || !listnode_lookup(rfc->nve_groups_sequential, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current NVE group no longer exists\n"); + return NULL; + } + + if (rfg->rfp_cfg == NULL && size > 0) { + rfg->rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); + vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, + size); + } + return rfg->rfp_cfg; +} + +static void *rfapi_rfp_get_or_init_group_config_l2(struct rfapi_cfg *rfc, + struct vty *vty, + uint32_t size) +{ + struct rfapi_l2_group_cfg *rfg = + VTY_GET_CONTEXT_SUB(rfapi_l2_group_cfg); + + /* make sure group is still in list */ + if (!rfg || !listnode_lookup(rfc->l2_groups, rfg)) { + /* Not in list anymore */ + vty_out(vty, "Current L2 group no longer exists\n"); + return NULL; + } + if (rfg->rfp_cfg == NULL && size > 0) { + rfg->rfp_cfg = XCALLOC(MTYPE_RFAPI_RFP_GROUP_CFG, size); + vnc_zlog_debug_verbose("%s: allocated, size=%d", __func__, + size); + } + return rfg->rfp_cfg; +} + +/*------------------------------------------ + * rfapi_rfp_init_group_config_ptr_vty + * + * This is used to init or return a previously init'ed group specific + * configuration pointer. Group is identified by vty context. + * NOTE: size is ignored when a previously init'ed value is returned. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * vty quagga vty context + * size number of bytes to allocation + * + * output: + * none + * + * return value: + * rfp_cfg_group NULL or Pointer to configuration structure +--------------------------------------------*/ +void *rfapi_rfp_init_group_config_ptr_vty(void *rfp_start_val, + rfapi_rfp_cfg_group_type type, + struct vty *vty, uint32_t size) +{ + struct bgp *bgp; + void *ret = NULL; + + if (rfp_start_val == NULL || vty == NULL) + return NULL; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (!bgp || !bgp->rfapi_cfg) + return NULL; + + switch (type) { + case RFAPI_RFP_CFG_GROUP_DEFAULT: + ret = rfapi_rfp_get_or_init_group_config_default(bgp->rfapi_cfg, + vty, size); + break; + case RFAPI_RFP_CFG_GROUP_NVE: + ret = rfapi_rfp_get_or_init_group_config_nve(bgp->rfapi_cfg, + vty, size); + break; + case RFAPI_RFP_CFG_GROUP_L2: + ret = rfapi_rfp_get_or_init_group_config_l2(bgp->rfapi_cfg, vty, + size); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: Unknown group type=%d", + __func__, type); + /* should never happen */ + assert("Unknown type" == NULL); + break; + } + return ret; +} + +/*------------------------------------------ + * rfapi_rfp_get_group_config_ptr_vty + * + * This is used to get group specific configuration pointer. + * Group is identified by type and vty context. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * vty quagga vty context + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +void *rfapi_rfp_get_group_config_ptr_vty(void *rfp_start_val, + rfapi_rfp_cfg_group_type type, + struct vty *vty) +{ + return rfapi_rfp_init_group_config_ptr_vty(rfp_start_val, type, vty, 0); +} + +static void * +rfapi_rfp_get_group_config_name_nve(struct rfapi_cfg *rfc, const char *name, + void *criteria, + rfp_group_config_search_cb_t *search_cb) +{ + struct rfapi_nve_group_cfg *rfg; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(rfc->nve_groups_sequential, node, rfg)) { + if (!strcmp(rfg->name, name) && /* name match */ + (search_cb == NULL || !search_cb(criteria, rfg->rfp_cfg))) + return rfg->rfp_cfg; + } + return NULL; +} + +static void * +rfapi_rfp_get_group_config_name_l2(struct rfapi_cfg *rfc, const char *name, + void *criteria, + rfp_group_config_search_cb_t *search_cb) +{ + struct rfapi_l2_group_cfg *rfg; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(rfc->l2_groups, node, rfg)) { + if (!strcmp(rfg->name, name) && /* name match */ + (search_cb == NULL || !search_cb(criteria, rfg->rfp_cfg))) + return rfg->rfp_cfg; + } + return NULL; +} + +/*------------------------------------------ + * rfapi_rfp_get_group_config_ptr_name + * + * This is used to get group specific configuration pointer. + * Group is identified by type and name context. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * name group name + * criteria RFAPI caller provided search criteria + * search_cb optional rfp_group_config_search_cb_t + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +void *rfapi_rfp_get_group_config_ptr_name( + void *rfp_start_val, rfapi_rfp_cfg_group_type type, const char *name, + void *criteria, rfp_group_config_search_cb_t *search_cb) +{ + struct bgp *bgp; + void *ret = NULL; + + if (rfp_start_val == NULL || name == NULL) + return NULL; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (!bgp || !bgp->rfapi_cfg) + return NULL; + + switch (type) { + case RFAPI_RFP_CFG_GROUP_DEFAULT: + ret = bgp->rfapi_cfg->default_rfp_cfg; + break; + case RFAPI_RFP_CFG_GROUP_NVE: + ret = rfapi_rfp_get_group_config_name_nve(bgp->rfapi_cfg, name, + criteria, search_cb); + break; + case RFAPI_RFP_CFG_GROUP_L2: + ret = rfapi_rfp_get_group_config_name_l2(bgp->rfapi_cfg, name, + criteria, search_cb); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: Unknown group type=%d", + __func__, type); + /* should never happen */ + assert("Unknown type" == NULL); + break; + } + return ret; +} + +/*------------------------------------------ + * rfapi_rfp_get_l2_group_config_ptr_lni + * + * This is used to get group specific configuration pointer. + * Group is identified by type and logical network identifier. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * logical_net_id group logical network identifier + * criteria RFAPI caller provided search criteria + * search_cb optional rfp_group_config_search_cb_t + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +void * +rfapi_rfp_get_l2_group_config_ptr_lni(void *rfp_start_val, + uint32_t logical_net_id, void *criteria, + rfp_group_config_search_cb_t *search_cb) +{ + struct bgp *bgp; + struct rfapi_l2_group_cfg *rfg; + struct listnode *node; + + if (rfp_start_val == NULL) + return NULL; + + bgp = rfapi_bgp_lookup_by_rfp(rfp_start_val); + if (!bgp || !bgp->rfapi_cfg) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->l2_groups, node, rfg)) { + if (rfg->logical_net_id == logical_net_id + && (search_cb == NULL + || !search_cb(criteria, rfg->rfp_cfg))) { + if (rfg->rfp_cfg == NULL) + vnc_zlog_debug_verbose( + "%s: returning rfp group config for lni=0", + __func__); + return rfg->rfp_cfg; + } + } + return NULL; +} diff --git a/bgpd/rfapi/rfapi.h b/bgpd/rfapi/rfapi.h new file mode 100644 index 0000000..3f61d5b --- /dev/null +++ b/bgpd/rfapi/rfapi.h @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_BGP_RFAPI_H +#define _QUAGGA_BGP_RFAPI_H + +#ifdef ENABLE_BGP_VNC + +#include +#include +#include "lib/zebra.h" +#include "lib/vty.h" +#include "lib/prefix.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_encap_types.h" + +/* probably ought to have a field-specific define in config.h */ +#ifndef s6_addr32 /* for solaris/bsd */ +# define s6_addr32 __u6_addr.__u6_addr32 +#endif + +#define RFAPI_V4_ADDR 0x04 +#define RFAPI_V6_ADDR 0x06 +#define RFAPI_SHOW_STR "VNC information\n" + +struct rfapi_ip_addr { + uint8_t addr_family; /* AF_INET | AF_INET6 */ + union { + struct in_addr v4; /* in network order */ + struct in6_addr v6; /* in network order */ + } addr; +}; + +struct rfapi_ip_prefix { + uint8_t length; + uint8_t cost; /* bgp local pref = 255 - cost */ + struct rfapi_ip_addr prefix; +}; + +struct rfapi_nexthop { + struct prefix addr; + uint8_t cost; +}; + +struct rfapi_next_hop_entry { + struct rfapi_next_hop_entry *next; + struct rfapi_ip_prefix prefix; + uint32_t lifetime; + struct rfapi_ip_addr un_address; + struct rfapi_ip_addr vn_address; + struct rfapi_vn_option *vn_options; + struct rfapi_un_option *un_options; +}; + +#define RFAPI_REMOVE_RESPONSE_LIFETIME 0 +#define RFAPI_INFINITE_LIFETIME 0xFFFFFFFF + +struct rfapi_l2address_option { + struct ethaddr macaddr; /* use 0 to assign label to IP prefix */ + uint32_t label; /* 20bit label in low bits, no TC, S, or TTL */ + uint32_t logical_net_id; /* ~= EVPN Ethernet Segment Id, + must not be zero for mac regis. */ + uint8_t local_nve_id; + uint16_t tag_id; /* EVPN Ethernet Tag ID, 0 = none */ +}; + +typedef enum { + RFAPI_UN_OPTION_TYPE_PROVISIONAL, /* internal use only */ + RFAPI_UN_OPTION_TYPE_TUNNELTYPE, +} rfapi_un_option_type; + +struct rfapi_tunneltype_option { + bgp_encap_types type; + union { + struct bgp_encap_type_reserved reserved; + struct bgp_encap_type_l2tpv3_over_ip l2tpv3_ip; + struct bgp_encap_type_gre gre; + struct bgp_encap_type_transmit_tunnel_endpoint + transmit_tunnel_endpoint; + struct bgp_encap_type_ipsec_in_tunnel_mode ipsec_tunnel; + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode + ip_ipsec; + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode + mpls_ipsec; + struct bgp_encap_type_ip_in_ip ip_ip; + struct bgp_encap_type_vxlan vxlan; + struct bgp_encap_type_nvgre nvgre; + struct bgp_encap_type_mpls mpls; + struct bgp_encap_type_mpls_in_gre mpls_gre; + struct bgp_encap_type_vxlan_gpe vxlan_gpe; + struct bgp_encap_type_mpls_in_udp mpls_udp; + struct bgp_encap_type_pbb pbb; + } bgpinfo; +}; + +struct rfapi_un_option { + struct rfapi_un_option *next; + rfapi_un_option_type type; + union { + struct rfapi_tunneltype_option tunnel; + } v; +}; + +typedef enum { + RFAPI_VN_OPTION_TYPE_L2ADDR = + 3, /* Layer 2 address, 3 for legacy compatibility */ + RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP, /* for static routes */ + RFAPI_VN_OPTION_TYPE_INTERNAL_RD, /* internal use only */ +} rfapi_vn_option_type; + +struct rfapi_vn_option { + struct rfapi_vn_option *next; + + rfapi_vn_option_type type; + + union { + struct rfapi_l2address_option l2addr; + + /* + * If this option is present, the next hop is local to the + * client NVE (i.e., not via a tunnel). + */ + struct rfapi_nexthop local_nexthop; + + /* + * For rfapi internal use only + */ + struct prefix_rd internal_rd; + } v; +}; + +struct rfapi_l2address_option_match { + struct rfapi_l2address_option o; + uint32_t flags; + +#define RFAPI_L2O_MACADDR 0x00000001 +#define RFAPI_L2O_LABEL 0x00000002 +#define RFAPI_L2O_LNI 0x00000004 +#define RFAPI_L2O_LHI 0x00000008 +}; + +#define VNC_CONFIG_STR "VNC/RFP related configuration\n" + +typedef void *rfapi_handle; + +/*********************************************************************** + * RFP Callbacks + ***********************************************************************/ +/*------------------------------------------ + * rfapi_response_cb_t (callback typedef) + * + * Callbacks of this type are used to provide asynchronous + * route updates from RFAPI to the RFP client. + * + * response_cb + * called to notify the rfp client that a next hop list + * that has previously been provided in response to an + * rfapi_query call has been updated. Deleted routes are indicated + * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. + * + * By default, the routes an NVE receives via this callback include + * its own routes (that it has registered). However, these may be + * filtered out if the global BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP + * flag is set. + * + * local_cb + * called to notify the rfp client that a local route + * has been added or deleted. Deleted routes are indicated + * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. + * + * input: + * next_hops a list of possible next hops. + * This is a linked list allocated within the + * rfapi. The response_cb callback function is responsible + * for freeing this memory via rfapi_free_next_hop_list() + * in order to avoid memory leaks. + * + * userdata value (cookie) originally specified in call to + * rfapi_open() + * + *------------------------------------------*/ +typedef void(rfapi_response_cb_t)(struct rfapi_next_hop_entry *next_hops, + void *userdata); + +/*------------------------------------------ + * rfapi_nve_close_cb_t (callback typedef) + * + * Callbacks of this type are used to provide asynchronous + * notification that an rfapi_handle was invalidated + * + * input: + * pHandle Firmerly valid rfapi_handle returned to + * client via rfapi_open(). + * + * reason EIDRM handle administratively closed (clear nve ...) + * ESTALE handle invalidated by configuration change + * + *------------------------------------------*/ +typedef void(rfapi_nve_close_cb_t)(rfapi_handle pHandle, int reason); + +/*------------------------------------------ + * rfp_cfg_write_cb_t (callback typedef) + * + * This callback is used to generate output for any config parameters + * that may supported by RFP via RFP defined vty commands at the bgp + * level. See loglevel as an example. + * + * input: + * vty -- quagga vty context + * rfp_start_val -- value returned by rfp_start + * + * output: + * to vty, rfp related configuration + * + * return value: + * lines written +--------------------------------------------*/ +typedef int(rfp_cfg_write_cb_t)(struct vty *vty, void *rfp_start_val); + +/*------------------------------------------ + * rfp_cfg_group_write_cb_t (callback typedef) + * + * This callback is used to generate output for any config parameters + * that may supported by RFP via RFP defined vty commands at the + * L2 or NVE level. See loglevel as an example. + * + * input: + * vty quagga vty context + * rfp_start_val value returned by rfp_start + * type group type + * name group name + * rfp_cfg_group Pointer to configuration structure + * + * output: + * to vty, rfp related configuration + * + * return value: + * lines written +--------------------------------------------*/ +typedef enum { + RFAPI_RFP_CFG_GROUP_DEFAULT, + RFAPI_RFP_CFG_GROUP_NVE, + RFAPI_RFP_CFG_GROUP_L2 +} rfapi_rfp_cfg_group_type; + +typedef int(rfp_cfg_group_write_cb_t)(struct vty *vty, void *rfp_start_val, + rfapi_rfp_cfg_group_type type, + const char *name, void *rfp_cfg_group); + +/*********************************************************************** + * Configuration related defines and structures + ***********************************************************************/ + +struct rfapi_rfp_cb_methods { + rfp_cfg_write_cb_t *cfg_cb; /* show top level config */ + rfp_cfg_group_write_cb_t *cfg_group_cb; /* show group level config */ + rfapi_response_cb_t *response_cb; /* unsolicited responses */ + rfapi_response_cb_t *local_cb; /* local route add/delete */ + rfapi_nve_close_cb_t *close_cb; /* handle closed */ +}; + +/* + * If a route with infinite lifetime is withdrawn, this is + * how long (in seconds) to wait before expiring it (because + * RFAPI_LIFETIME_MULTIPLIER_PCT * infinity is too long to wait) + */ +#define RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY (60*120) + +/* + * the factor that should be applied to a prefix's value + * before using it to expire a withdrawn prefix, expressed as a percent. + * Thus, a value of 100 means to use the exact value of , + * a value of 200 means to use twice the value of , etc. + */ +#define RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR 150 + +/* + * This is used by rfapi to determine if RFP is using/supports + * a partial (i.e., cache) or full table download approach for + * mapping information. When full table download approach is + * used all information is passed to RFP after an initial + * rfapi_query. When partial table download is used, only + * information matching a query is passed. + */ +typedef enum { + RFAPI_RFP_DOWNLOAD_PARTIAL = 0, + RFAPI_RFP_DOWNLOAD_FULL +} rfapi_rfp_download_type; + +#define RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL 1 + +struct rfapi_rfp_cfg { + /* partial or full table download */ + rfapi_rfp_download_type download_type; /* default=partial */ + /* + * When full-table-download is enabled, this is the minimum + * number of seconds between times a non-queried prefix will + * be updated to a particular NVE. + * default: RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL + */ + uint32_t ftd_advertisement_interval; + /* + * percentage of registration lifetime to continue to use information + * post soft-state refresh timeout + default: RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR + */ + uint32_t holddown_factor; + /* Control generation of updated RFP responses */ + uint8_t use_updated_response; /* default=0/no */ + /* when use_updated_response, also generate remove responses */ + uint8_t use_removes; /* default=0/no */ +}; + +/*********************************************************************** + * Process related functions -- MUST be provided by the RFAPI user <<=== + ***********************************************************************/ + +/*------------------------------------------ + * rfp_start + * + * This function will start the RFP code + * + * input: + * master quagga thread_master to tie into bgpd threads + * + * output: + * cfgp Pointer to rfapi_rfp_cfg (null = use defaults), + * copied by caller, updated via rfp_set_configuration + * cbmp Pointer to rfapi_rfp_cb_methods, may be null + * copied by caller, updated via rfapi_rfp_set_cb_methods + * return value: + * rfp_start_val rfp returned value passed on rfp_stop and other rfapi calls +--------------------------------------------*/ +extern void *rfp_start(struct event_loop *master, struct rfapi_rfp_cfg **cfgp, + struct rfapi_rfp_cb_methods **cbmp); + +/*------------------------------------------ + * rfp_stop + * + * This function is called on shutdown to trigger RFP cleanup + * + * input: + * rfp_start_val + * + * output: + * none + * + * return value: +--------------------------------------------*/ +extern void rfp_stop(void *rfp_start_val); + +/*********************************************************************** + * RFP processing behavior configuration + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_rfp_set_configuration + * + * This is used to change rfapi's processing behavior based on + * RFP requirements. + * + * input: + * rfp_start_val value returned by rfp_start + * rfapi_rfp_cfg Pointer to configuration structure + * + * output: + * none + * + * return value: + * 0 Success + * ENXIO Unabled to locate configured BGP/VNC +--------------------------------------------*/ +extern int rfapi_rfp_set_configuration(void *rfp_start_val, + struct rfapi_rfp_cfg *rfp_cfg); + +/*------------------------------------------ + * rfapi_rfp_set_cb_methods + * + * Change registered callback functions for asynchronous notifications + * from RFAPI to the RFP client. + * + * input: + * rfp_start_val value by rfp_start + * methods Pointer to struct rfapi_rfp_cb_methods containing + * pointers to callback methods as described above + * + * return value: + * 0 Success + * ENXIO BGP or VNC not configured + *------------------------------------------*/ +extern int rfapi_rfp_set_cb_methods(void *rfp_start_val, + struct rfapi_rfp_cb_methods *methods); + +/*********************************************************************** + * RFP group specific configuration + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_rfp_init_group_config_ptr_vty + * + * This is used to init or return a previously init'ed group specific + * configuration pointer. Group is identified by vty context. + * NOTE: size is ignored when a previously init'ed value is returned. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * vty quagga vty context + * size number of bytes to allocation + * + * output: + * none + * + * return value: + * rfp_cfg_group NULL or Pointer to configuration structure +--------------------------------------------*/ +extern void *rfapi_rfp_init_group_config_ptr_vty(void *rfp_start_val, + rfapi_rfp_cfg_group_type type, + struct vty *vty, + uint32_t size); + +/*------------------------------------------ + * rfapi_rfp_get_group_config_ptr_vty + * + * This is used to get group specific configuration pointer. + * Group is identified by type and vty context. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * vty quagga vty context + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +extern void *rfapi_rfp_get_group_config_ptr_vty(void *rfp_start_val, + rfapi_rfp_cfg_group_type type, + struct vty *vty); + +/*------------------------------------------ + * rfp_group_config_search_cb_t (callback typedef) + * + * This callback is used to called from within a + * rfapi_rfp_get_group_config_ptr to check if the rfp_cfg_group + * matches the search criteria + * + * input: + * criteria RFAPI caller provided serach criteria + * rfp_cfg_group Pointer to configuration structure | NULL + * + * output: + * + * return value: + * 0 Match/Success + * ENOENT No matching +--------------------------------------------*/ +typedef int(rfp_group_config_search_cb_t)(void *criteria, void *rfp_cfg_group); + +/*------------------------------------------ + * rfapi_rfp_get_group_config_ptr_name + * + * This is used to get group specific configuration pointer. + * Group is identified by type and name context. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * type group type + * name group name + * criteria RFAPI caller provided serach criteria + * search_cb optional rfp_group_config_search_cb_t + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +extern void *rfapi_rfp_get_group_config_ptr_name( + void *rfp_start_val, rfapi_rfp_cfg_group_type type, const char *name, + void *criteria, rfp_group_config_search_cb_t *search_cb); + +/*------------------------------------------ + * rfapi_rfp_get_l2_group_config_ptr_lni + * + * This is used to get group specific configuration pointer. + * Group is identified by type and logical network identifier. + * RFAPI frees rfp_cfg_group when group is deleted during reconfig, + * bgp restart or shutdown. + * + * input: + * rfp_start_val value returned by rfp_start + * logical_net_id group logical network identifier + * criteria RFAPI caller provided serach criteria + * search_cb optional rfp_group_config_search_cb_t + * + * output: + * none + * + * return value: + * rfp_cfg_group Pointer to configuration structure +--------------------------------------------*/ +extern void * +rfapi_rfp_get_l2_group_config_ptr_lni(void *rfp_start_val, + uint32_t logical_net_id, void *criteria, + rfp_group_config_search_cb_t *search_cb); + +/*********************************************************************** + * NVE Sessions + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_open + * + * This function initializes a NVE record and associates it with + * the specified VN and underlay network addresses + * + * input: + * rfp_start_val value returned by rfp_start + * vn NVE virtual network address + * + * un NVE underlay network address + * + * default_options Default options to use on registrations. + * For now only tunnel type is supported. + * May be overridden per-prefix in rfapi_register(). + * Caller owns (rfapi_open() does not free) + * + * response_cb Pointer to next hop list update callback function or + * NULL when no callbacks are desired. + * + * userdata Passed to subsequent response_cb invocations. + * + * output: + * response_lifetime The length of time that responses sent to this + * NVE are valid. + * + * pHandle pointer to location to store rfapi handle. The + * handle must be passed on subsequent rfapi_ calls. + * + * + * return value: + * 0 Success + * EEXIST NVE with this {vn,un} already open + * ENOENT No matching nve group config + * ENOMSG Matched nve group config was incomplete + * ENXIO BGP or VNC not configured + * EAFNOSUPPORT Matched nve group specifies auto-assignment of RD, + * but underlay network address is not IPv4 + * EDEADLK Called from within a callback procedure + *------------------------------------------*/ +extern int rfapi_open(void *rfp_start_val, struct rfapi_ip_addr *vn, + struct rfapi_ip_addr *un, + struct rfapi_un_option *default_options, + uint32_t *response_lifetime, void *userdata, + rfapi_handle *pHandle); + + +/*------------------------------------------ + * rfapi_close + * + * Shut down NVE session and release associated data. Calling + * from within a rfapi callback procedure is permitted (the close + * will be completed asynchronously after the callback finishes). + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * + * output: + * + * return value: + * 0 Success + * EBADF invalid handle + * ENXIO BGP or VNC not configured + *------------------------------------------*/ +extern int rfapi_close(rfapi_handle rfd); + +/*------------------------------------------ + * rfapi_check + * + * Test rfapi descriptor + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * + * output: + * + * return value: + * 0 Success: handle is valid and usable + * EINVAL null argument + * ESTALE formerly valid handle invalidated by config, needs close + * EBADF invalid handle + * ENXIO BGP or VNC not configured + * EAFNOSUPPORT Internal addressing error + *------------------------------------------*/ +extern int rfapi_check(rfapi_handle rfd); + +/*********************************************************************** + * NVE Routes + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_query + * + * This function queries the RIB for a + * particular route. Note that this call may result in subsequent + * callbacks to response_cb. Response callbacks can be cancelled + * by calling rfapi_query_done. A duplicate query using the same target + * will result in only one callback per change in next_hops. (i.e., + * cancel/replace the prior query results.) + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * target: the destination address + * l2o ptr to L2 Options struct, NULL if not present in query + * + * output: + * ppNextHopEntry pointer to a location to store a pointer + * to the returned list of nexthops. It is the + * caller's responsibility to free this list + * via rfapi_free_next_hop_list(). + * + * + * return value: + * 0 Success + * EBADF invalid handle + * ENOENT no valid route + * ENXIO BGP or VNC not configured + * ESTALE descriptor is no longer usable; should be closed + * EDEADLK Called from within a callback procedure +--------------------------------------------*/ +extern int rfapi_query(rfapi_handle rfd, struct rfapi_ip_addr *target, + struct rfapi_l2address_option *l2o, + struct rfapi_next_hop_entry **ppNextHopEntry); + +/*------------------------------------------ + * rfapi_query_done + * + * Notifies the rfapi that the user is no longer interested + * in the specified target. + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * target: the destination address + * + * output: + * + * return value: + * 0 Success + * EBADF invalid handle + * ENOENT no match found for target + * ENXIO BGP or VNC not configured + * ESTALE descriptor is no longer usable; should be closed + * EDEADLK Called from within a callback procedure +--------------------------------------------*/ +extern int rfapi_query_done(rfapi_handle rfd, struct rfapi_ip_addr *target); + +/*------------------------------------------ + * rfapi_query_done_all + * + * Notifies the rfapi that the user is no longer interested + * in any target. + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * + * output: + * count: number of queries cleared + * + * return value: + * 0 Success + * EBADF invalid handle + * ENXIO BGP or VNC not configured + * ESTALE descriptor is no longer usable; should be closed + * EDEADLK Called from within a callback procedure +--------------------------------------------*/ +extern int rfapi_query_done_all(rfapi_handle rfd, int *count); + +/*------------------------------------------ + * rfapi_register + * + * Requests that reachability to the indicated prefix via this NVE + * be advertised by BGP. If is non-zero, then the previously- + * advertised prefix should be withdrawn. + * + * (This function should NOT be called if the rfapi_open() function + * returns NULL) + * + * input: + * rfd: rfapi descriptor returned by rfapi_open + * prefix: A prefix to be registered or deregistered + * lifetime Prefix lifetime in seconds, host byte order + * options_un underlay netowrk options, may include tunnel-type + * Caller owns (rfapi_register() does not free). + * options_vn virtual network options, may include layer 2 address + * option and local-nexthop option + * Caller owns (rfapi_register() does not free). + * + * action: RFAPI_REGISTER_ADD add the route + * RFAPI_REGISTER_WITHDRAW withdraw route + * RFAPI_REGISTER_KILL withdraw without holddown + * + * return value: + * 0 Success + * EBADF invalid handle + * ENXIO BGP or VNC not configured + * ESTALE descriptor is no longer usable; should be closed + * EDEADLK Called from within a callback procedure + --------------------------------------------*/ + +typedef enum { + RFAPI_REGISTER_ADD, + RFAPI_REGISTER_WITHDRAW, + RFAPI_REGISTER_KILL +} rfapi_register_action; + +extern int rfapi_register(rfapi_handle rfd, struct rfapi_ip_prefix *prefix, + uint32_t lifetime, struct rfapi_un_option *options_un, + struct rfapi_vn_option *options_vn, + rfapi_register_action action); + +/*********************************************************************** + * Helper / Utility functions + ***********************************************************************/ + +/*------------------------------------------ + * rfapi_get_vn_addr + * + * Get the virtual network address used by an NVE based on it's RFD + * + * input: + * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * vn NVE virtual network address + *------------------------------------------*/ +extern struct rfapi_ip_addr *rfapi_get_vn_addr(void *); + +/*------------------------------------------ + * rfapi_get_un_addr + * + * Get the underlay network address used by an NVE based on it's RFD + * + * input: + * rfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * un NVE underlay network address + *------------------------------------------*/ +extern struct rfapi_ip_addr *rfapi_get_un_addr(void *); + +/*------------------------------------------ + * rfapi_error_str + * + * Returns a string describing the rfapi error code. + * + * input: + * + * code Error code returned by rfapi function + * + * returns: + * + * const char * String + *------------------------------------------*/ +extern const char *rfapi_error_str(int code); + +/*------------------------------------------ + * rfapi_get_rfp_start_val + * + * Returns value passed to rfapi on rfp_start + * + * input: + * void * bgp structure + * + * returns: + * void * + *------------------------------------------*/ +extern void *rfapi_get_rfp_start_val(void *bgpv); + +/*------------------------------------------ + * rfapi_compare_rfds + * + * Compare two generic rfapi descriptors. + * + * input: + * rfd1: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * rfd2: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * 0 Mismatch + * 1 Match + *------------------------------------------*/ +extern int rfapi_compare_rfds(void *rfd1, void *rfd2); + +/*------------------------------------------ + * rfapi_free_next_hop_list + * + * Frees a next_hop_list returned by a rfapi_query invocation + * + * input: + * list: a pointer to a response list (as a + * struct rfapi_next_hop_entry) to free. + * + * output: + * + * return value: None + --------------------------------------------*/ +extern void rfapi_free_next_hop_list(struct rfapi_next_hop_entry *list); + +/*------------------------------------------ + * rfapi_get_response_lifetime_default + * + * Returns the default lifetime for a response. + * rfp_start_val value returned by rfp_start or + * NULL (=use default instance) + * + * input: + * None + * + * output: + * + * return value: The bgp instance default lifetime for a response. + --------------------------------------------*/ +extern int rfapi_get_response_lifetime_default(void *rfp_start_val); + +/*------------------------------------------ + * rfapi_is_vnc_configured + * + * Returns if VNC is configured + * + * input: + * rfp_start_val value returned by rfp_start or + * NULL (=use default instance) + * + * output: + * + * return value: If VNC is configured for the bgpd instance + * 0 Success + * ENXIO VNC not configured + --------------------------------------------*/ +extern int rfapi_is_vnc_configured(void *rfp_start_val); + +/*------------------------------------------ + * rfapi_bgp_lookup_by_rfp + * + * Find bgp instance pointer based on value returned by rfp_start + * + * input: + * rfp_start_val value returned by rfp_startor + * NULL (=get default instance) + * + * output: + * none + * + * return value: + * bgp bgp instance pointer + * NULL = not found + * + --------------------------------------------*/ +extern struct bgp *rfapi_bgp_lookup_by_rfp(void *rfp_start_val); + +/*------------------------------------------ + * rfapi_get_rfp_start_val_by_bgp + * + * Find bgp instance pointer based on value returned by rfp_start + * + * input: + * bgp bgp instance pointer + * + * output: + * none + * + * return value: + * rfp_start_val + * NULL = not found + * + --------------------------------------------*/ +extern void *rfapi_get_rfp_start_val_by_bgp(struct bgp *bgp); + +#endif /* ENABLE_BGP_VNC */ + +#endif /* _QUAGGA_BGP_RFAPI_H */ diff --git a/bgpd/rfapi/rfapi_ap.c b/bgpd/rfapi/rfapi_ap.c new file mode 100644 index 0000000..fe344a5 --- /dev/null +++ b/bgpd/rfapi/rfapi_ap.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/routemap.h" +#include "lib/log.h" +#include "lib/linklist.h" +#include "lib/command.h" +#include "lib/stream.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_backend.h" + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_advertise.h" + +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp_p.h" +#include "bgpd/rfapi/vnc_zebra.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/rfapi_rib.h" + +#include "bgpd/rfapi/rfapi_ap.h" +#include "bgpd/rfapi/vnc_debug.h" + +/* + * Per-NVE Advertised prefixes + * + * We maintain a list of prefixes advertised by each NVE. + * There are two indices: by prefix and by lifetime. + * + * BY-PREFIX skiplist + * + * key: ptr to struct prefix (when storing, point to prefix that + * is part of rfapi_adb). + * + * value: ptr to struct rfapi_adb + * + * BY-LIFETIME skiplist + * + * key: ptr to struct rfapi_adb + * value: ptr to struct rfapi_adb + * + */ + +/* + * Skiplist sort function that sorts first according to lifetime + * and then according to adb pointer value. The adb pointer + * is used to spread out the sort for adbs with the same lifetime + * and thereby make the skip list operations more efficient. + */ +static int sl_adb_lifetime_cmp(const void *adb1, const void *adb2) +{ + const struct rfapi_adb *a1 = adb1; + const struct rfapi_adb *a2 = adb2; + + if (a1->lifetime < a2->lifetime) + return -1; + if (a1->lifetime > a2->lifetime) + return 1; + + if (a1 < a2) + return -1; + if (a1 > a2) + return 1; + + return 0; +} + +void rfapiApInit(struct rfapi_advertised_prefixes *ap) +{ + ap->ipN_by_prefix = skiplist_new(0, rfapi_rib_key_cmp, NULL); + ap->ip0_by_ether = skiplist_new(0, rfapi_rib_key_cmp, NULL); + ap->by_lifetime = skiplist_new(0, sl_adb_lifetime_cmp, NULL); +} + +void rfapiApRelease(struct rfapi_advertised_prefixes *ap) +{ + struct rfapi_adb *adb; + + /* Free ADBs and lifetime items */ + while (0 == skiplist_first(ap->by_lifetime, NULL, (void **)&adb)) { + rfapiAdbFree(adb); + skiplist_delete_first(ap->by_lifetime); + } + + while (0 == skiplist_delete_first(ap->ipN_by_prefix)) + ; + while (0 == skiplist_delete_first(ap->ip0_by_ether)) + ; + + /* Free lists */ + skiplist_free(ap->ipN_by_prefix); + skiplist_free(ap->ip0_by_ether); + skiplist_free(ap->by_lifetime); + + ap->ipN_by_prefix = NULL; + ap->ip0_by_ether = NULL; + ap->by_lifetime = NULL; +} + +int rfapiApCount(struct rfapi_descriptor *rfd) +{ + if (!rfd->advertised.by_lifetime) + return 0; + + return skiplist_count(rfd->advertised.by_lifetime); +} + +int rfapiApCountAll(struct bgp *bgp) +{ + struct rfapi *h; + struct listnode *node; + struct rfapi_descriptor *rfd; + int total = 0; + + h = bgp->rfapi; + if (h) { + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + total += rfapiApCount(rfd); + } + } + return total; +} + + +void rfapiApReadvertiseAll(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + struct rfapi_adb *adb; + void *cursor = NULL; + int rc; + + for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, + (void **)&adb, &cursor); + rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, + (void **)&adb, &cursor)) { + + struct prefix_rd prd; + uint32_t local_pref = rfp_cost_to_localpref(adb->cost); + + prd = rfd->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + /* + * TBD this is not quite right. When pfx_ip is 0/32 or 0/128, + * we need to substitute the VN address as the prefix + */ + add_vnc_route(rfd, bgp, SAFI_MPLS_VPN, &adb->u.s.prefix_ip, + &prd, /* RD to use (0 for ENCAP) */ + &rfd->vn_addr, /* nexthop */ + &local_pref, &adb->lifetime, NULL, + NULL, /* struct rfapi_un_option */ + NULL, /* struct rfapi_vn_option */ + rfd->rt_export_list, NULL, /* med */ + NULL, ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0); + } +} + +void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + struct rfapi_adb *adb; + void *cursor; + int rc; + + + cursor = NULL; + for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL, + (void **)&adb, &cursor); + rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL, + (void **)&adb, &cursor)) { + + struct prefix pfx_vn_buf; + struct prefix *pfx_ip; + + if (!(RFAPI_0_PREFIX(&adb->u.s.prefix_ip) + && RFAPI_HOST_PREFIX(&adb->u.s.prefix_ip))) { + + pfx_ip = &adb->u.s.prefix_ip; + + } else { + + pfx_ip = NULL; + + /* + * 0/32 or 0/128 => mac advertisement + */ + if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) { + /* + * Bad: it means we can't delete the route + */ + vnc_zlog_debug_verbose( + "%s: BAD: handle has bad vn_addr: skipping", + __func__); + continue; + } + } + + del_vnc_route(rfd, rfd->peer, bgp, SAFI_MPLS_VPN, + pfx_ip ? pfx_ip : &pfx_vn_buf, + &adb->u.s.prd, /* RD to use (0 for ENCAP) */ + ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0); + } +} + +/* + * returns nonzero if tunnel readvertisement is needed, 0 otherwise + */ +static int rfapiApAdjustLifetimeStats( + struct rfapi_descriptor *rfd, + uint32_t *old_lifetime, /* set if removing/replacing */ + uint32_t *new_lifetime) /* set if replacing/adding */ +{ + int advertise = 0; + int find_max = 0; + int find_min = 0; + + vnc_zlog_debug_verbose("%s: rfd=%p, pOldLife=%p, pNewLife=%p", __func__, + rfd, old_lifetime, new_lifetime); + if (old_lifetime) + vnc_zlog_debug_verbose("%s: OldLife=%d", __func__, + *old_lifetime); + if (new_lifetime) + vnc_zlog_debug_verbose("%s: NewLife=%d", __func__, + *new_lifetime); + + if (new_lifetime) { + /* + * Adding new lifetime + */ + if (old_lifetime) { + /* + * replacing existing lifetime + */ + + + /* old and new are same */ + if (*old_lifetime == *new_lifetime) + return 0; + + if (*old_lifetime == rfd->min_prefix_lifetime) { + find_min = 1; + } + if (*old_lifetime == rfd->max_prefix_lifetime) { + find_max = 1; + } + + /* no need to search if new value is at or equals + * min|max */ + if (*new_lifetime <= rfd->min_prefix_lifetime) { + rfd->min_prefix_lifetime = *new_lifetime; + find_min = 0; + } + if (*new_lifetime >= rfd->max_prefix_lifetime) { + rfd->max_prefix_lifetime = *new_lifetime; + advertise = 1; + find_max = 0; + } + + } else { + /* + * Just adding new lifetime + */ + if (*new_lifetime < rfd->min_prefix_lifetime) { + rfd->min_prefix_lifetime = *new_lifetime; + } + if (*new_lifetime > rfd->max_prefix_lifetime) { + advertise = 1; + rfd->max_prefix_lifetime = *new_lifetime; + } + } + } else { + /* + * Deleting + */ + + /* + * See if the max prefix lifetime for this NVE has decreased. + * The easy optimization: track min & max; walk the table only + * if they are different. + * The general optimization: index the advertised_prefixes + * table by lifetime. + * + * Note: for a given nve_descriptor, only one of the + * advertised_prefixes[] tables will be used: viz., the + * address family that matches the VN address. + * + */ + if (rfd->max_prefix_lifetime == rfd->min_prefix_lifetime) { + + /* + * Common case: all lifetimes are the same. Only + * thing we need to do here is check if there are + * no exported routes left. In that case, reinitialize + * the max and min values. + */ + if (!rfapiApCount(rfd)) { + rfd->max_prefix_lifetime = 0; + rfd->min_prefix_lifetime = UINT32_MAX; + } + + + } else { + if (old_lifetime) { + if (*old_lifetime == rfd->min_prefix_lifetime) { + find_min = 1; + } + if (*old_lifetime == rfd->max_prefix_lifetime) { + find_max = 1; + } + } + } + } + + if (find_min || find_max) { + uint32_t min = UINT32_MAX; + uint32_t max = 0; + + struct rfapi_adb *adb_min; + struct rfapi_adb *adb_max; + + if (!skiplist_first(rfd->advertised.by_lifetime, + (void **)&adb_min, NULL) + && !skiplist_last(rfd->advertised.by_lifetime, + (void **)&adb_max, NULL)) { + + /* + * This should always work + */ + min = adb_min->lifetime; + max = adb_max->lifetime; + + } else { + + void *cursor; + struct rfapi_rib_key rk; + struct rfapi_adb *adb; + int rc; + + vnc_zlog_debug_verbose( + "%s: walking to find new min/max", __func__); + + cursor = NULL; + for (rc = skiplist_next(rfd->advertised.ipN_by_prefix, + (void **)&rk, (void **)&adb, + &cursor); + !rc; + rc = skiplist_next(rfd->advertised.ipN_by_prefix, + (void **)&rk, (void **)&adb, + &cursor)) { + + uint32_t lt = adb->lifetime; + + if (lt > max) + max = lt; + if (lt < min) + min = lt; + } + cursor = NULL; + for (rc = skiplist_next(rfd->advertised.ip0_by_ether, + (void **)&rk, (void **)&adb, + &cursor); + !rc; + rc = skiplist_next(rfd->advertised.ip0_by_ether, + (void **)&rk, (void **)&adb, + &cursor)) { + + uint32_t lt = adb->lifetime; + + if (lt > max) + max = lt; + if (lt < min) + min = lt; + } + } + + /* + * trigger tunnel route update + * but only if we found a VPN route and it had + * a lifetime greater than 0 + */ + if (max && rfd->max_prefix_lifetime != max) + advertise = 1; + rfd->max_prefix_lifetime = max; + rfd->min_prefix_lifetime = min; + } + + vnc_zlog_debug_verbose("%s: returning advertise=%d, min=%d, max=%d", + __func__, advertise, rfd->min_prefix_lifetime, + rfd->max_prefix_lifetime); + + return (advertise != 0); +} + +/* + * Return Value + * + * 0 No need to advertise tunnel route + * non-0 advertise tunnel route + */ +int rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *pfx_ip, struct prefix *pfx_eth, + struct prefix_rd *prd, uint32_t lifetime, uint8_t cost, + struct rfapi_l2address_option *l2o) /* other options TBD */ +{ + int rc; + struct rfapi_adb *adb; + uint32_t old_lifetime = 0; + int use_ip0 = 0; + struct rfapi_rib_key rk; + + rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); + if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { + use_ip0 = 1; + assert(pfx_eth); + rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, + (void **)&adb); + + } else { + + /* find prefix in advertised prefixes list */ + rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, + (void **)&adb); + } + + + if (rc) { + /* Not found */ + adb = XCALLOC(MTYPE_RFAPI_ADB, sizeof(struct rfapi_adb)); + adb->lifetime = lifetime; + adb->u.key = rk; + + if (use_ip0) { + assert(pfx_eth); + skiplist_insert(rfd->advertised.ip0_by_ether, + &adb->u.key, adb); + } else { + skiplist_insert(rfd->advertised.ipN_by_prefix, + &adb->u.key, adb); + } + + skiplist_insert(rfd->advertised.by_lifetime, adb, adb); + } else { + old_lifetime = adb->lifetime; + if (old_lifetime != lifetime) { + assert(!skiplist_delete(rfd->advertised.by_lifetime, + adb, NULL)); + adb->lifetime = lifetime; + assert(!skiplist_insert(rfd->advertised.by_lifetime, + adb, adb)); + } + } + adb->cost = cost; + if (l2o) + adb->l2o = *l2o; + else + memset(&adb->l2o, 0, sizeof(struct rfapi_l2address_option)); + + if (rfapiApAdjustLifetimeStats(rfd, (rc ? NULL : &old_lifetime), + &lifetime)) + return 1; + + return 0; +} + +/* + * After this function returns successfully, caller should call + * rfapiAdjustLifetimeStats() and possibly rfapiTunnelRouteAnnounce() + */ +int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *pfx_ip, struct prefix *pfx_eth, + struct prefix_rd *prd, int *advertise_tunnel) /* out */ +{ + int rc; + struct rfapi_adb *adb; + uint32_t old_lifetime; + int use_ip0 = 0; + struct rfapi_rib_key rk; + + if (advertise_tunnel) + *advertise_tunnel = 0; + + rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk); + /* find prefix in advertised prefixes list */ + if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) { + use_ip0 = 1; + assert(pfx_eth); + + rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk, + (void **)&adb); + + } else { + + /* find prefix in advertised prefixes list */ + rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk, + (void **)&adb); + } + + if (rc) { + return ENOENT; + } + + old_lifetime = adb->lifetime; + + if (use_ip0) { + rc = skiplist_delete(rfd->advertised.ip0_by_ether, &rk, NULL); + } else { + rc = skiplist_delete(rfd->advertised.ipN_by_prefix, &rk, NULL); + } + assert(!rc); + + rc = skiplist_delete(rfd->advertised.by_lifetime, adb, NULL); + assert(!rc); + + rfapiAdbFree(adb); + + if (rfapiApAdjustLifetimeStats(rfd, &old_lifetime, NULL)) { + if (advertise_tunnel) + *advertise_tunnel = 1; + } + + return 0; +} diff --git a/bgpd/rfapi/rfapi_ap.h b/bgpd/rfapi/rfapi_ap.h new file mode 100644 index 0000000..7698fba --- /dev/null +++ b/bgpd/rfapi/rfapi_ap.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ +#ifndef _QUAGGA_BGP_RFAPI_AP_H +#define _QUAGGA_BGP_RFAPI_AP_H + +/* TBD delete some of these #includes */ + +#include + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/routemap.h" +#include "lib/log.h" +#include "lib/linklist.h" +#include "lib/command.h" +#include "lib/stream.h" + +#include "bgpd/bgpd.h" + +#include "bgp_rfapi_cfg.h" +#include "rfapi.h" +#include "rfapi_backend.h" + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_advertise.h" + +#include "rfapi_import.h" +#include "rfapi_private.h" +#include "rfapi_monitor.h" +#include "rfapi_vty.h" +#include "vnc_export_bgp.h" +#include "vnc_export_bgp_p.h" +#include "vnc_zebra.h" +#include "vnc_import_bgp.h" +#include "rfapi_rib.h" + + +extern void rfapiApInit(struct rfapi_advertised_prefixes *ap); + +extern void rfapiApRelease(struct rfapi_advertised_prefixes *ap); + +extern int rfapiApCount(struct rfapi_descriptor *rfd); + + +extern int rfapiApCountAll(struct bgp *bgp); + +extern void rfapiApReadvertiseAll(struct bgp *bgp, + struct rfapi_descriptor *rfd); + +extern void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd); + +extern int +rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *pfx_ip, + struct prefix *pfx_eth, struct prefix_rd *prd, uint32_t lifetime, + uint8_t cost, + struct rfapi_l2address_option *l2o); /* other options TBD */ + +extern int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *pfx_ip, struct prefix *pfx_eth, + struct prefix_rd *prd, + int *advertise_tunnel); /* out */ + + +#endif /* _QUAGGA_BGP_RFAPI_AP_H */ diff --git a/bgpd/rfapi/rfapi_backend.h b/bgpd/rfapi/rfapi_backend.h new file mode 100644 index 0000000..32ea0a2 --- /dev/null +++ b/bgpd/rfapi/rfapi_backend.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_BGP_RFAPI_BACKEND_H +#define _QUAGGA_BGP_RFAPI_BACKEND_H + +#ifdef ENABLE_BGP_VNC + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_nexthop.h" + +extern void rfapi_init(void); +extern void vnc_zebra_init(struct event_loop *master); +extern void vnc_zebra_destroy(void); + +extern void rfapi_delete(struct bgp *); + +struct rfapi *bgp_rfapi_new(struct bgp *bgp); +void bgp_rfapi_destroy(struct bgp *bgp, struct rfapi *h); + +extern void rfapiProcessUpdate(struct peer *peer, void *rfd, + const struct prefix *p, struct prefix_rd *prd, + struct attr *attr, afi_t afi, safi_t safi, + uint8_t type, uint8_t sub_type, uint32_t *label); + + +extern void rfapiProcessWithdraw(struct peer *peer, void *rfd, + const struct prefix *p, struct prefix_rd *prd, + struct attr *attr, afi_t afi, safi_t safi, + uint8_t type, int kill); + +extern void rfapiProcessPeerDown(struct peer *peer); + +extern void vnc_zebra_announce(struct prefix *p, + struct bgp_path_info *new_select, + struct bgp *bgp); + +extern void vnc_zebra_withdraw(struct prefix *p, + struct bgp_path_info *old_select); + + +extern void rfapi_vty_out_vncinfo(struct vty *vty, const struct prefix *p, + struct bgp_path_info *bpi, safi_t safi); + + +extern void vnc_direct_bgp_vpn_enable(struct bgp *bgp, afi_t afi); + +extern void vnc_direct_bgp_vpn_disable(struct bgp *bgp, afi_t afi); + +extern void vnc_direct_bgp_rh_vpn_enable(struct bgp *bgp, afi_t afi); + +extern void vnc_direct_bgp_rh_vpn_disable(struct bgp *bgp, afi_t afi); + +#endif /* ENABLE_BGP_VNC */ + +#endif /* _QUAGGA_BGP_RFAPI_BACKEND_H */ diff --git a/bgpd/rfapi/rfapi_descriptor_rfp_utils.c b/bgpd/rfapi/rfapi_descriptor_rfp_utils.c new file mode 100644 index 0000000..28326ab --- /dev/null +++ b/bgpd/rfapi/rfapi_descriptor_rfp_utils.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/log.h" + +#include "bgpd/bgpd.h" + +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_descriptor_rfp_utils.h" +#include "bgpd/rfapi/vnc_debug.h" + + +void *rfapi_create_generic(struct rfapi_ip_addr *vn, struct rfapi_ip_addr *un) +{ + struct rfapi_descriptor *rfd; + rfd = XCALLOC(MTYPE_RFAPI_DESC, sizeof(struct rfapi_descriptor)); + vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); + rfd->vn_addr = *vn; + rfd->un_addr = *un; + return (void *)rfd; +} + +/*------------------------------------------ + * rfapi_free_generic + * + * Compare two generic rfapi descriptors. + * + * input: + * grfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * + *------------------------------------------*/ +void rfapi_free_generic(void *grfd) +{ + struct rfapi_descriptor *rfd; + rfd = (struct rfapi_descriptor *)grfd; + XFREE(MTYPE_RFAPI_DESC, rfd); +} + + +/*------------------------------------------ + * rfapi_compare_rfds + * + * Compare two generic rfapi descriptors. + * + * input: + * rfd1: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * rfd2: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * 0 Mismatch + * 1 Match + *------------------------------------------*/ +int rfapi_compare_rfds(void *rfd1, void *rfd2) +{ + struct rfapi_descriptor *rrfd1, *rrfd2; + int match = 0; + + rrfd1 = (struct rfapi_descriptor *)rfd1; + rrfd2 = (struct rfapi_descriptor *)rfd2; + + if (rrfd1->vn_addr.addr_family == rrfd2->vn_addr.addr_family) { + if (rrfd1->vn_addr.addr_family == AF_INET) + match = IPV4_ADDR_SAME(&(rrfd1->vn_addr.addr.v4), + &(rrfd2->vn_addr.addr.v4)); + else + match = IPV6_ADDR_SAME(&(rrfd1->vn_addr.addr.v6), + &(rrfd2->vn_addr.addr.v6)); + } + + /* + * If the VN addresses don't match in all forms, + * give up. + */ + if (!match) + return 0; + + /* + * do the process again for the UN addresses. + */ + match = 0; + if (rrfd1->un_addr.addr_family == rrfd2->un_addr.addr_family) { + /* VN addresses match + * UN address families match + * now check the actual UN addresses + */ + if (rrfd1->un_addr.addr_family == AF_INET) + match = IPV4_ADDR_SAME(&(rrfd1->un_addr.addr.v4), + &(rrfd2->un_addr.addr.v4)); + else + match = IPV6_ADDR_SAME(&(rrfd1->un_addr.addr.v6), + &(rrfd2->un_addr.addr.v6)); + } + return match; +} diff --git a/bgpd/rfapi/rfapi_descriptor_rfp_utils.h b/bgpd/rfapi/rfapi_descriptor_rfp_utils.h new file mode 100644 index 0000000..9e65a70 --- /dev/null +++ b/bgpd/rfapi/rfapi_descriptor_rfp_utils.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + + +extern void *rfapi_create_generic(struct rfapi_ip_addr *vn, + struct rfapi_ip_addr *un); + +/*------------------------------------------ + * rfapi_free_generic + * + * Compare two generic rfapi descriptors. + * + * input: + * grfd: rfapi descriptor returned by rfapi_open or rfapi_create_generic + * + * output: + * + * return value: + * + *------------------------------------------*/ +extern void rfapi_free_generic(void *grfd); diff --git a/bgpd/rfapi/rfapi_encap_tlv.c b/bgpd/rfapi/rfapi_encap_tlv.c new file mode 100644 index 0000000..37a7326 --- /dev/null +++ b/bgpd/rfapi/rfapi_encap_tlv.c @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015-2016, LabN Consulting, L.L.C. + */ + +#include "lib/zebra.h" + +#include "lib/memory.h" +#include "lib/prefix.h" +#include "lib/table.h" +#include "lib/vty.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" + +#include "bgpd/bgp_encap_types.h" +#include "bgpd/bgp_encap_tlv.h" + +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_encap_tlv.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/vnc_debug.h" + +static void rfapi_add_endpoint_address_to_subtlv( + struct bgp *bgp, struct rfapi_ip_addr *ea, + struct bgp_tea_subtlv_remote_endpoint *subtlv) +{ + subtlv->family = ea->addr_family; + if (subtlv->family == AF_INET) + subtlv->ip_address.v4 = ea->addr.v4; + else + subtlv->ip_address.v6 = ea->addr.v6; + subtlv->as4 = htonl(bgp->as); +} + +bgp_encap_types +rfapi_tunneltype_option_to_tlv(struct bgp *bgp, struct rfapi_ip_addr *ea, + struct rfapi_tunneltype_option *tto, + struct attr *attr, int always_add) +{ + +#define _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ttype) \ + if ((always_add \ + || (bgp->rfapi_cfg \ + && !CHECK_FLAG(bgp->rfapi_cfg->flags, \ + BGP_VNC_CONFIG_ADV_UN_METHOD_ENCAP))) \ + && ea \ + && !CHECK_SUBTLV_FLAG(&tto->bgpinfo.ttype, \ + BGP_TEA_SUBTLV_REMOTE_ENDPOINT)) { \ + rfapi_add_endpoint_address_to_subtlv( \ + bgp, ea, &tto->bgpinfo.ttype.st_endpoint); \ + SET_SUBTLV_FLAG(&tto->bgpinfo.ttype, \ + BGP_TEA_SUBTLV_REMOTE_ENDPOINT); \ + } + + struct rfapi_tunneltype_option dto; + if (tto == NULL) { /* create default type */ + tto = &dto; + memset(tto, 0, sizeof(dto)); + tto->type = RFAPI_BGP_ENCAP_TYPE_DEFAULT; + } + switch (tto->type) { + case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(l2tpv3_ip); + bgp_encap_type_l2tpv3overip_to_tlv(&tto->bgpinfo.l2tpv3_ip, + attr); + break; + + case BGP_ENCAP_TYPE_GRE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(gre); + bgp_encap_type_gre_to_tlv(&tto->bgpinfo.gre, attr); + break; + + case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(transmit_tunnel_endpoint); + bgp_encap_type_transmit_tunnel_endpoint( + &tto->bgpinfo.transmit_tunnel_endpoint, attr); + break; + + case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ipsec_tunnel); + bgp_encap_type_ipsec_in_tunnel_mode_to_tlv( + &tto->bgpinfo.ipsec_tunnel, attr); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ip_ipsec); + bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + &tto->bgpinfo.ip_ipsec, attr); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_ipsec); + bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode_to_tlv( + &tto->bgpinfo.mpls_ipsec, attr); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(ip_ip); + bgp_encap_type_ip_in_ip_to_tlv(&tto->bgpinfo.ip_ip, attr); + break; + + case BGP_ENCAP_TYPE_VXLAN: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(vxlan); + bgp_encap_type_vxlan_to_tlv(&tto->bgpinfo.vxlan, attr); + break; + + case BGP_ENCAP_TYPE_NVGRE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(nvgre); + bgp_encap_type_nvgre_to_tlv(&tto->bgpinfo.nvgre, attr); + break; + + case BGP_ENCAP_TYPE_MPLS: + /* nothing to do for MPLS */ + break; + + case BGP_ENCAP_TYPE_MPLS_IN_GRE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_gre); + bgp_encap_type_mpls_in_gre_to_tlv(&tto->bgpinfo.mpls_gre, attr); + break; + + case BGP_ENCAP_TYPE_VXLAN_GPE: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(vxlan_gpe); + bgp_encap_type_vxlan_gpe_to_tlv(&tto->bgpinfo.vxlan_gpe, attr); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_UDP: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(mpls_udp); + bgp_encap_type_mpls_in_udp_to_tlv(&tto->bgpinfo.mpls_udp, attr); + break; + + case BGP_ENCAP_TYPE_PBB: + _RTTO_MAYBE_ADD_ENDPOINT_ADDRESS(pbb); + bgp_encap_type_pbb_to_tlv(&tto->bgpinfo.pbb, attr); + break; + + case BGP_ENCAP_TYPE_RESERVED: + assert(!"Cannot process BGP_ENCAP_TYPE_RESERVED"); + } + return tto->type; +} + +struct rfapi_un_option *rfapi_encap_tlv_to_un_option(struct attr *attr) +{ + struct rfapi_un_option *uo = NULL; + struct rfapi_tunneltype_option *tto; + int rc; + struct bgp_attr_encap_subtlv *stlv; + + /* no tunnel encap attr stored */ + if (!attr->encap_tunneltype) + return NULL; + + stlv = attr->encap_subtlvs; + + uo = XCALLOC(MTYPE_RFAPI_UN_OPTION, sizeof(struct rfapi_un_option)); + uo->type = RFAPI_UN_OPTION_TYPE_TUNNELTYPE; + uo->v.tunnel.type = attr->encap_tunneltype; + tto = &uo->v.tunnel; + + switch (attr->encap_tunneltype) { + case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: + rc = tlv_to_bgp_encap_type_l2tpv3overip( + stlv, &tto->bgpinfo.l2tpv3_ip); + break; + + case BGP_ENCAP_TYPE_GRE: + rc = tlv_to_bgp_encap_type_gre(stlv, &tto->bgpinfo.gre); + break; + + case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: + rc = tlv_to_bgp_encap_type_transmit_tunnel_endpoint( + stlv, &tto->bgpinfo.transmit_tunnel_endpoint); + break; + + case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: + rc = tlv_to_bgp_encap_type_ipsec_in_tunnel_mode( + stlv, &tto->bgpinfo.ipsec_tunnel); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + rc = tlv_to_bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( + stlv, &tto->bgpinfo.ip_ipsec); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + rc = tlv_to_bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( + stlv, &tto->bgpinfo.mpls_ipsec); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP: + rc = tlv_to_bgp_encap_type_ip_in_ip(stlv, &tto->bgpinfo.ip_ip); + break; + + case BGP_ENCAP_TYPE_VXLAN: + rc = tlv_to_bgp_encap_type_vxlan(stlv, &tto->bgpinfo.vxlan); + break; + + case BGP_ENCAP_TYPE_NVGRE: + rc = tlv_to_bgp_encap_type_nvgre(stlv, &tto->bgpinfo.nvgre); + break; + + case BGP_ENCAP_TYPE_MPLS: + rc = tlv_to_bgp_encap_type_mpls(stlv, &tto->bgpinfo.mpls); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_GRE: + rc = tlv_to_bgp_encap_type_mpls_in_gre(stlv, + &tto->bgpinfo.mpls_gre); + break; + + case BGP_ENCAP_TYPE_VXLAN_GPE: + rc = tlv_to_bgp_encap_type_vxlan_gpe(stlv, + &tto->bgpinfo.vxlan_gpe); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_UDP: + rc = tlv_to_bgp_encap_type_mpls_in_udp(stlv, + &tto->bgpinfo.mpls_udp); + break; + + case BGP_ENCAP_TYPE_PBB: + rc = tlv_to_bgp_encap_type_pbb(stlv, &tto->bgpinfo.pbb); + break; + + default: + vnc_zlog_debug_verbose("%s: unknown tunnel type %d", __func__, + attr->encap_tunneltype); + rc = -1; + break; + } + if (rc) { + XFREE(MTYPE_RFAPI_UN_OPTION, uo); + } + return uo; +} + +/*********************************************************************** + * SUBTLV PRINT + ***********************************************************************/ + +static void subtlv_print_encap_l2tpv3_over_ip( + void *stream, int column_offset, + struct bgp_tea_subtlv_encap_l2tpv3_over_ip *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(L2TPv3 over IP)", + vty_newline); + fp(out, "%*s SessionID: %d%s", column_offset, "", st->sessionid, + vty_newline); + fp(out, "%*s Cookie: (length %d)%s", column_offset, "", + st->cookie_length, vty_newline); +} + +static void subtlv_print_encap_gre(void *stream, int column_offset, + struct bgp_tea_subtlv_encap_gre_key *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(GRE)", + vty_newline); + fp(out, "%*s GRE key: %d (0x%x)%s", column_offset, "", st->gre_key, + st->gre_key, vty_newline); +} + +static void subtlv_print_encap_pbb(void *stream, int column_offset, + struct bgp_tea_subtlv_encap_pbb *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(PBB)", + vty_newline); + if (st->flag_isid) { + fp(out, "%*s ISID: %d (0x%x)%s", column_offset, "", st->isid, + st->isid, vty_newline); + } + if (st->flag_vid) { + fp(out, "%*s VID: %d (0x%x)%s", column_offset, "", st->vid, + st->vid, vty_newline); + } + fp(out, "%*s MACADDR %02x:%02x:%02x:%02x:%02x:%02x%s", column_offset, + "", st->macaddr[0], st->macaddr[1], st->macaddr[2], st->macaddr[3], + st->macaddr[4], st->macaddr[5], vty_newline); +} + +static void subtlv_print_proto_type(void *stream, int column_offset, + struct bgp_tea_subtlv_proto_type *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: Encap(Proto Type)", + vty_newline); + fp(out, "%*s Proto %d (0x%x)%s", column_offset, "", st->proto, + st->proto, vty_newline); +} + +static void subtlv_print_color(void *stream, int column_offset, + struct bgp_tea_subtlv_color *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: Color", vty_newline); + fp(out, "%*s Color: %d (0x%x)", column_offset, "", st->color, + st->color, vty_newline); +} + +static void subtlv_print_ipsec_ta(void *stream, int column_offset, + struct bgp_tea_subtlv_ipsec_ta *st) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!st) + return; + + fp(out, "%*s%s%s", column_offset, "", "SubTLV: IPSEC TA", vty_newline); + fp(out, "%*s Authenticator Type: %d (0x%x)", column_offset, "", + st->authenticator_type, st->authenticator_type, vty_newline); + fp(out, "%*s Authenticator: (length %d)", column_offset, "", + st->authenticator_length, vty_newline); +} + +/*********************************************************************** + * TLV PRINT + ***********************************************************************/ + +static void +print_encap_type_l2tpv3overip(void *stream, int column_offset, + struct bgp_encap_type_l2tpv3_over_ip *bet) +{ + const char *type = "L2TPv3 over IP"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_encap_l2tpv3_over_ip(stream, column_offset + 2, + &bet->st_encap); + subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); + subtlv_print_color(stream, column_offset + 2, &bet->st_color); +} + +static void print_encap_type_gre(void *stream, int column_offset, + struct bgp_encap_type_gre *bet) +{ + const char *type = "GRE"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_encap_gre(stream, column_offset + 2, &bet->st_encap); + subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); + subtlv_print_color(stream, column_offset + 2, &bet->st_color); +} + +static void print_encap_type_ip_in_ip(void *stream, int column_offset, + struct bgp_encap_type_ip_in_ip *bet) +{ + const char *type = "IP in IP"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_proto_type(stream, column_offset + 2, &bet->st_proto); + subtlv_print_color(stream, column_offset + 2, &bet->st_color); +} + +static void print_encap_type_transmit_tunnel_endpoint( + void *stream, int column_offset, + struct bgp_encap_type_transmit_tunnel_endpoint *bet) +{ + const char *type = "Transmit Tunnel Endpoint"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +static void print_encap_type_ipsec_in_tunnel_mode( + void *stream, int column_offset, + struct bgp_encap_type_ipsec_in_tunnel_mode *bet) +{ + const char *type = "IPSEC in Tunnel mode"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); +} + +static void print_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( + void *stream, int column_offset, + struct bgp_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode *bet) +{ + const char *type = "IP in IP Tunnel with IPSEC transport mode"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); +} + +static void print_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( + void *stream, int column_offset, + struct bgp_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode *bet) +{ + const char *type = "MPLS in IP Tunnel with IPSEC transport mode"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_ipsec_ta(stream, column_offset + 2, &bet->st_ipsec_ta); +} + + +static void print_encap_type_pbb(void *stream, int column_offset, + struct bgp_encap_type_pbb *bet) +{ + const char *type = "PBB"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + subtlv_print_encap_pbb(stream, column_offset + 2, &bet->st_encap); +} + + +static void print_encap_type_vxlan(void *stream, int column_offset, + struct bgp_encap_type_vxlan *bet) +{ + const char *type = "VXLAN"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + + +static void print_encap_type_nvgre(void *stream, int column_offset, + struct bgp_encap_type_nvgre *bet) +{ + const char *type = "NVGRE"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +static void print_encap_type_mpls(void *stream, int column_offset, + struct bgp_encap_type_mpls *bet) +{ + const char *type = "MPLS"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +static void print_encap_type_mpls_in_gre(void *stream, int column_offset, + struct bgp_encap_type_mpls_in_gre *bet) +{ + const char *type = "MPLS in GRE"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +static void print_encap_type_vxlan_gpe(void *stream, int column_offset, + struct bgp_encap_type_vxlan_gpe *bet) +{ + const char *type = "VXLAN GPE"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +static void print_encap_type_mpls_in_udp(void *stream, int column_offset, + struct bgp_encap_type_mpls_in_udp *bet) +{ + const char *type = "MPLS in UDP"; + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bet) + return; + + fp(out, "%*sTEA type %s%s", column_offset, "", type, vty_newline); + + /* no subtlvs for this type */ +} + +void rfapi_print_tunneltype_option(void *stream, int column_offset, + struct rfapi_tunneltype_option *tto) +{ + switch (tto->type) { + case BGP_ENCAP_TYPE_L2TPV3_OVER_IP: + print_encap_type_l2tpv3overip(stream, column_offset, + &tto->bgpinfo.l2tpv3_ip); + break; + + case BGP_ENCAP_TYPE_GRE: + print_encap_type_gre(stream, column_offset, &tto->bgpinfo.gre); + break; + + case BGP_ENCAP_TYPE_TRANSMIT_TUNNEL_ENDPOINT: + print_encap_type_transmit_tunnel_endpoint( + stream, column_offset, + &tto->bgpinfo.transmit_tunnel_endpoint); + break; + + case BGP_ENCAP_TYPE_IPSEC_IN_TUNNEL_MODE: + print_encap_type_ipsec_in_tunnel_mode( + stream, column_offset, &tto->bgpinfo.ipsec_tunnel); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + print_encap_type_ip_in_ip_tunnel_with_ipsec_transport_mode( + stream, column_offset, &tto->bgpinfo.ip_ipsec); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_IP_TUNNEL_WITH_IPSEC_TRANSPORT_MODE: + print_encap_type_mpls_in_ip_tunnel_with_ipsec_transport_mode( + stream, column_offset, &tto->bgpinfo.mpls_ipsec); + break; + + case BGP_ENCAP_TYPE_IP_IN_IP: + print_encap_type_ip_in_ip(stream, column_offset, + &tto->bgpinfo.ip_ip); + break; + + case BGP_ENCAP_TYPE_VXLAN: + print_encap_type_vxlan(stream, column_offset, + &tto->bgpinfo.vxlan); + break; + + case BGP_ENCAP_TYPE_NVGRE: + print_encap_type_nvgre(stream, column_offset, + &tto->bgpinfo.nvgre); + break; + + case BGP_ENCAP_TYPE_MPLS: + print_encap_type_mpls(stream, column_offset, + &tto->bgpinfo.mpls); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_GRE: + print_encap_type_mpls_in_gre(stream, column_offset, + &tto->bgpinfo.mpls_gre); + break; + + case BGP_ENCAP_TYPE_VXLAN_GPE: + print_encap_type_vxlan_gpe(stream, column_offset, + &tto->bgpinfo.vxlan_gpe); + break; + + case BGP_ENCAP_TYPE_MPLS_IN_UDP: + print_encap_type_mpls_in_udp(stream, column_offset, + &tto->bgpinfo.mpls_udp); + break; + + case BGP_ENCAP_TYPE_PBB: + print_encap_type_pbb(stream, column_offset, &tto->bgpinfo.pbb); + break; + + case BGP_ENCAP_TYPE_RESERVED: + assert(!"Cannot process BGP_ENCAP_TYPE_RESERVED"); + } +} diff --git a/bgpd/rfapi/rfapi_encap_tlv.h b/bgpd/rfapi/rfapi_encap_tlv.h new file mode 100644 index 0000000..56131d8 --- /dev/null +++ b/bgpd/rfapi/rfapi_encap_tlv.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2015-2016, LabN Consulting, L.L.C. + */ + +#ifndef _QUAGGA_BGP_RFAPI_ENCAP_TLV_H +#define _QUAGGA_BGP_RFAPI_ENCAP_TLV_H + +#define RFAPI_BGP_ENCAP_TYPE_DEFAULT BGP_ENCAP_TYPE_IP_IN_IP + +extern bgp_encap_types +rfapi_tunneltype_option_to_tlv(struct bgp *bgp, struct rfapi_ip_addr *ea, + struct rfapi_tunneltype_option *tto, + struct attr *attr, int always_add); + +extern struct rfapi_un_option *rfapi_encap_tlv_to_un_option(struct attr *attr); + +extern void rfapi_print_tunneltype_option(void *stream, int column_offset, + struct rfapi_tunneltype_option *tto); + + +#endif /* _QUAGGA_BGP_RFAPI_ENCAP_TLV_H */ diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c new file mode 100644 index 0000000..44dfc88 --- /dev/null +++ b/bgpd/rfapi/rfapi_import.c @@ -0,0 +1,4800 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2009-2016, LabN Consulting, L.L.C. + */ + +/* + * File: rfapi_import.c + * Purpose: Handle import of routes from BGP to RFAPI + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/log.h" +#include "lib/skiplist.h" +#include "frrevent.h" +#include "lib/stream.h" +#include "lib/lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" /* prefix_rd2str() */ +#include "bgpd/bgp_vnc_types.h" +#include "bgpd/bgp_rd.h" + +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_nve_addr.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp_p.h" +#include "bgpd/rfapi/vnc_zebra.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/vnc_import_bgp_p.h" +#include "bgpd/rfapi/rfapi_rib.h" +#include "bgpd/rfapi/rfapi_encap_tlv.h" +#include "bgpd/rfapi/vnc_debug.h" + +#undef DEBUG_MONITOR_MOVE_SHORTER +#undef DEBUG_RETURNED_NHL +#undef DEBUG_ROUTE_COUNTERS +#undef DEBUG_ENCAP_MONITOR +#undef DEBUG_L2_EXTRA +#undef DEBUG_IT_NODES +#undef DEBUG_BI_SEARCH + +/* + * Allocated for each withdraw timer instance; freed when the timer + * expires or is canceled + */ +struct rfapi_withdraw { + struct rfapi_import_table *import_table; + struct agg_node *node; + struct bgp_path_info *info; + safi_t safi; /* used only for bulk operations */ + /* + * For import table node reference count checking (i.e., debugging). + * Normally when a timer expires, lockoffset should be 0. However, if + * the timer expiration function is called directly (e.g., + * rfapiExpireVpnNow), the node could be locked by a preceding + * agg_route_top() or agg_route_next() in a loop, so we need to pass + * this value in. + */ + int lockoffset; +}; + +/* + * DEBUG FUNCTION + * Count remote routes and compare with actively-maintained values. + * Abort if they disagree. + */ +void rfapiCheckRouteCount(void) +{ + struct bgp *bgp = bgp_get_default(); + struct rfapi *h; + struct rfapi_import_table *it; + afi_t afi; + + assert(bgp); + + h = bgp->rfapi; + assert(h); + + for (it = h->imports; it; it = it->next) { + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_table *rt; + struct agg_node *rn; + + int holddown_count = 0; + int imported_count = 0; + int remote_count = 0; + + rt = it->imported_vpn[afi]; + + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + struct bgp_path_info *bpi; + struct bgp_path_info *next; + + for (bpi = rn->info; bpi; bpi = next) { + next = bpi->next; + + if (CHECK_FLAG(bpi->flags, + BGP_PATH_REMOVED)) { + ++holddown_count; + + } else { + if (!RFAPI_LOCAL_BI(bpi)) { + if (RFAPI_DIRECT_IMPORT_BI( + bpi)) { + ++imported_count; + } else { + ++remote_count; + } + } + } + } + } + + if (it->holddown_count[afi] != holddown_count) { + vnc_zlog_debug_verbose( + "%s: it->holddown_count %d != holddown_count %d", + __func__, it->holddown_count[afi], + holddown_count); + assert(0); + } + if (it->remote_count[afi] != remote_count) { + vnc_zlog_debug_verbose( + "%s: it->remote_count %d != remote_count %d", + __func__, it->remote_count[afi], + remote_count); + assert(0); + } + if (it->imported_count[afi] != imported_count) { + vnc_zlog_debug_verbose( + "%s: it->imported_count %d != imported_count %d", + __func__, it->imported_count[afi], + imported_count); + assert(0); + } + } + } +} + +#ifdef DEBUG_ROUTE_COUNTERS +#define VNC_ITRCCK do {rfapiCheckRouteCount();} while (0) +#else +#define VNC_ITRCCK +#endif + +/* + * Validate reference count for a node in an import table + * + * Normally lockoffset is 0 for nodes in quiescent state. However, + * agg_unlock_node will delete the node if it is called when + * node->lock == 1, and we have to validate the refcount before + * the node is deleted. In this case, we specify lockoffset 1. + */ +void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset) +{ + unsigned int count_bpi = 0; + unsigned int count_monitor = 0; + struct bgp_path_info *bpi; + struct rfapi_monitor_encap *hme; + struct rfapi_monitor_vpn *hmv; + + for (bpi = rn->info; bpi; bpi = bpi->next) + ++count_bpi; + + + if (rn->aggregate) { + ++count_monitor; /* rfapi_it_extra */ + + switch (safi) { + void *cursor; + int rc; + + case SAFI_ENCAP: + for (hme = RFAPI_MONITOR_ENCAP(rn); hme; + hme = hme->next) + ++count_monitor; + break; + + case SAFI_MPLS_VPN: + + for (hmv = RFAPI_MONITOR_VPN(rn); hmv; hmv = hmv->next) + ++count_monitor; + + if (RFAPI_MONITOR_EXTERIOR(rn)->source) { + ++count_monitor; /* sl */ + cursor = NULL; + for (rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn)->source, + NULL, NULL, &cursor); + !rc; + rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn)->source, + NULL, NULL, &cursor)) { + + ++count_monitor; /* sl entry */ + } + } + break; + + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_EVPN: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + assert(!"Passed in safi should be impossible"); + } + } + + if (count_bpi + count_monitor + lockoffset + != agg_node_get_lock_count(rn)) { + vnc_zlog_debug_verbose( + "%s: count_bpi=%d, count_monitor=%d, lockoffset=%d, rn->lock=%d", + __func__, count_bpi, count_monitor, lockoffset, + agg_node_get_lock_count(rn)); + assert(0); + } +} + +/* + * Perform deferred rfapi_close operations that were queued + * during callbacks. + */ +static wq_item_status rfapi_deferred_close_workfunc(struct work_queue *q, + void *data) +{ + struct rfapi_descriptor *rfd = data; + struct rfapi *h = q->spec.data; + + assert(!(h->flags & RFAPI_INCALLBACK)); + rfapi_close(rfd); + vnc_zlog_debug_verbose("%s: completed deferred close on handle %p", + __func__, rfd); + return WQ_SUCCESS; +} + +/* + * Extract layer 2 option from Encap TLVS in BGP attrs + */ +int rfapiGetL2o(struct attr *attr, struct rfapi_l2address_option *l2o) +{ + if (attr) { + struct bgp_attr_encap_subtlv *pEncap; + + for (pEncap = bgp_attr_get_vnc_subtlvs(attr); pEncap; + pEncap = pEncap->next) { + + if (pEncap->type == BGP_VNC_SUBTLV_TYPE_RFPOPTION) { + if (pEncap->value[0] + == RFAPI_VN_OPTION_TYPE_L2ADDR) { + + if (pEncap->value[1] == 14) { + memcpy(l2o->macaddr.octet, + pEncap->value + 2, + ETH_ALEN); + l2o->label = + ((pEncap->value[10] + >> 4) + & 0x0f) + + ((pEncap->value[9] + << 4) + & 0xff0) + + ((pEncap->value[8] + << 12) + & 0xff000); + + l2o->local_nve_id = + pEncap->value[12]; + + l2o->logical_net_id = + (pEncap->value[15] + & 0xff) + + ((pEncap->value[14] + << 8) + & 0xff00) + + ((pEncap->value[13] + << 16) + & 0xff0000); + } + + return 0; + } + } + } + } + + return ENOENT; +} + +/* + * Extract the lifetime from the Tunnel Encap attribute of a route in + * an import table + */ +int rfapiGetVncLifetime(struct attr *attr, uint32_t *lifetime) +{ + struct bgp_attr_encap_subtlv *pEncap; + + *lifetime = RFAPI_INFINITE_LIFETIME; /* default to infinite */ + + if (attr) { + + for (pEncap = bgp_attr_get_vnc_subtlvs(attr); pEncap; + pEncap = pEncap->next) { + + if (pEncap->type + == BGP_VNC_SUBTLV_TYPE_LIFETIME) { /* lifetime */ + if (pEncap->length == 4) { + memcpy(lifetime, pEncap->value, 4); + *lifetime = ntohl(*lifetime); + return 0; + } + } + } + } + + return ENOENT; +} + +/* + * Look for UN address in Encap attribute + */ +int rfapiGetVncTunnelUnAddr(struct attr *attr, struct prefix *p) +{ + struct bgp_attr_encap_subtlv *pEncap; + bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS;/*Default tunnel type*/ + + bgp_attr_extcom_tunnel_type(attr, &tun_type); + if (tun_type == BGP_ENCAP_TYPE_MPLS) { + if (!p) + return 0; + /* MPLS carries UN address in next hop */ + rfapiNexthop2Prefix(attr, p); + if (p->family != AF_UNSPEC) + return 0; + + return ENOENT; + } + if (attr) { + for (pEncap = attr->encap_subtlvs; pEncap; + pEncap = pEncap->next) { + + if (pEncap->type + == BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT) { /* un + addr + */ + switch (pEncap->length) { + case 8: + if (p) { + p->family = AF_INET; + p->prefixlen = IPV4_MAX_BITLEN; + memcpy(p->u.val, pEncap->value, + 4); + } + return 0; + + case 20: + if (p) { + p->family = AF_INET6; + p->prefixlen = IPV6_MAX_BITLEN; + memcpy(p->u.val, pEncap->value, + 16); + } + return 0; + } + } + } + } + + return ENOENT; +} + +/* + * Get UN address wherever it might be + */ +int rfapiGetUnAddrOfVpnBi(struct bgp_path_info *bpi, struct prefix *p) +{ + /* If it's in this route's VNC attribute, we're done */ + if (!rfapiGetVncTunnelUnAddr(bpi->attr, p)) + return 0; + /* + * Otherwise, see if it's cached from a corresponding ENCAP SAFI + * advertisement + */ + if (bpi->extra) { + switch (bpi->extra->vnc->vnc.import.un_family) { + case AF_INET: + if (p) { + p->family = + bpi->extra->vnc->vnc.import.un_family; + p->u.prefix4 = + bpi->extra->vnc->vnc.import.un.addr4; + p->prefixlen = IPV4_MAX_BITLEN; + } + return 0; + case AF_INET6: + if (p) { + p->family = + bpi->extra->vnc->vnc.import.un_family; + p->u.prefix6 = + bpi->extra->vnc->vnc.import.un.addr6; + p->prefixlen = IPV6_MAX_BITLEN; + } + return 0; + default: + if (p) + p->family = AF_UNSPEC; +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose( + "%s: bpi->extra->vnc.import.un_family is 0, no UN addr", + __func__); +#endif + break; + } + } + + return ENOENT; +} + + +/* + * Make a new bgp_path_info from gathered parameters + */ +static struct bgp_path_info *rfapiBgpInfoCreate(struct attr *attr, + struct peer *peer, void *rfd, + struct prefix_rd *prd, + uint8_t type, uint8_t sub_type, + uint32_t *label) +{ + struct bgp_path_info *new; + struct bgp_labels bgp_labels = {}; + + new = info_make(type, sub_type, 0, peer, attr, NULL); + + new->attr = bgp_attr_intern(attr); + + bgp_path_info_extra_get(new); + new->extra->vnc = XCALLOC(MTYPE_BGP_ROUTE_EXTRA_VNC, + sizeof(struct bgp_path_info_extra_vnc)); + if (prd) { + new->extra->vnc->vnc.import.rd = *prd; + new->extra->vnc->vnc.import.create_time = monotime(NULL); + } + if (label && *label != MPLS_INVALID_LABEL) { + encode_label(*label, &bgp_labels.label[0]); + bgp_labels.num_labels = 1; + new->extra->labels = bgp_labels_intern(&bgp_labels); + } + + peer_lock(peer); + + return new; +} + +/* + * Frees bgp_path_info as used in import tables (parts are not + * allocated exactly the way they are in the main RIBs) + */ +static void rfapiBgpInfoFree(struct bgp_path_info *goner) +{ + if (!goner) + return; + + if (goner->peer) { + vnc_zlog_debug_verbose("%s: calling peer_unlock(%p), #%d", + __func__, goner->peer, + goner->peer->lock); + peer_unlock(goner->peer); + } + + bgp_attr_unintern(&goner->attr); + + if (goner->extra) + bgp_path_info_extra_free(&goner->extra); + XFREE(MTYPE_BGP_ROUTE, goner); +} + +struct rfapi_import_table *rfapiMacImportTableGetNoAlloc(struct bgp *bgp, + uint32_t lni) +{ + struct rfapi *h; + struct rfapi_import_table *it = NULL; + uintptr_t lni_as_ptr = lni; + + h = bgp->rfapi; + if (!h) + return NULL; + + if (!h->import_mac) + return NULL; + + if (skiplist_search(h->import_mac, (void *)lni_as_ptr, (void **)&it)) + return NULL; + + return it; +} + +struct rfapi_import_table *rfapiMacImportTableGet(struct bgp *bgp, uint32_t lni) +{ + struct rfapi *h; + struct rfapi_import_table *it = NULL; + uintptr_t lni_as_ptr = lni; + + h = bgp->rfapi; + assert(h); + + if (!h->import_mac) { + /* default cmp is good enough for LNI */ + h->import_mac = skiplist_new(0, NULL, NULL); + } + + if (skiplist_search(h->import_mac, (void *)lni_as_ptr, (void **)&it)) { + + struct ecommunity *enew; + struct ecommunity_val eval; + afi_t afi; + + it = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, + sizeof(struct rfapi_import_table)); + /* set RT list of new import table based on LNI */ + memset((char *)&eval, 0, sizeof(eval)); + eval.val[0] = 0; /* VNC L2VPN */ + eval.val[1] = 2; /* VNC L2VPN */ + eval.val[5] = (lni >> 16) & 0xff; + eval.val[6] = (lni >> 8) & 0xff; + eval.val[7] = (lni >> 0) & 0xff; + + enew = ecommunity_new(); + ecommunity_add_val(enew, &eval, false, false); + it->rt_import_list = enew; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + it->imported_vpn[afi] = agg_table_init(); + it->imported_encap[afi] = agg_table_init(); + } + + it->l2_logical_net_id = lni; + + skiplist_insert(h->import_mac, (void *)lni_as_ptr, it); + } + + assert(it); + return it; +} + +/* + * Implement MONITOR_MOVE_SHORTER(original_node) from + * RFAPI-Import-Event-Handling.txt + * + * Returns pointer to the list of moved monitors + */ +static struct rfapi_monitor_vpn * +rfapiMonitorMoveShorter(struct agg_node *original_vpn_node, int lockoffset) +{ + struct bgp_path_info *bpi; + struct agg_node *par; + struct rfapi_monitor_vpn *m; + struct rfapi_monitor_vpn *mlast; + struct rfapi_monitor_vpn *moved; + int movecount = 0; + int parent_already_refcounted = 0; + + RFAPI_CHECK_REFCOUNT(original_vpn_node, SAFI_MPLS_VPN, lockoffset); + +#ifdef DEBUG_MONITOR_MOVE_SHORTER + { + vnc_zlog_debug_verbose("%s: called with node pfx=%pFX", + __func__, &original_vpn_node->p); + } +#endif + + /* + * 1. If there is at least one bpi (either regular route or + * route marked as withdrawn, with a pending timer) at + * original_node with a valid UN address, we're done. Return. + */ + for (bpi = original_vpn_node->info; bpi; bpi = bpi->next) { + struct prefix pfx; + + if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) { +#ifdef DEBUG_MONITOR_MOVE_SHORTER + vnc_zlog_debug_verbose( + "%s: have valid UN at original node, no change", + __func__); +#endif + return NULL; + } + } + + /* + * 2. Travel up the tree (toward less-specific prefixes) from + * original_node to find the first node that has at least + * one route (even if it is only a withdrawn route) with a + * valid UN address. Call this node "Node P." + */ + for (par = agg_node_parent(original_vpn_node); par; + par = agg_node_parent(par)) { + for (bpi = par->info; bpi; bpi = bpi->next) { + struct prefix pfx; + if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) { + break; + } + } + if (bpi) + break; + } + + if (par) { + RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, 0); + } + + /* + * If no less-specific routes, try to use the 0/0 node + */ + if (!par) { + const struct prefix *p; + /* this isn't necessarily 0/0 */ + par = agg_route_table_top(original_vpn_node); + + if (par) + p = agg_node_get_prefix(par); + /* + * If we got the top node but it wasn't 0/0, + * ignore it + */ + if (par && p->prefixlen) { + agg_unlock_node(par); /* maybe free */ + par = NULL; + } + + if (par) { + ++parent_already_refcounted; + } + } + + /* + * Create 0/0 node if it isn't there + */ + if (!par) { + struct prefix pfx_default; + const struct prefix *p = agg_node_get_prefix(original_vpn_node); + + memset(&pfx_default, 0, sizeof(pfx_default)); + pfx_default.family = p->family; + + /* creates default node if none exists */ + par = agg_node_get(agg_get_table(original_vpn_node), + &pfx_default); + ++parent_already_refcounted; + } + + /* + * 3. Move each of the monitors found at original_node to Node P. + * These are "Moved Monitors." + * + */ + + /* + * Attach at end so that the list pointer we return points + * only to the moved routes + */ + for (m = RFAPI_MONITOR_VPN(par), mlast = NULL; m; + mlast = m, m = m->next) + ; + + if (mlast) { + moved = mlast->next = RFAPI_MONITOR_VPN(original_vpn_node); + } else { + moved = RFAPI_MONITOR_VPN_W_ALLOC(par) = + RFAPI_MONITOR_VPN(original_vpn_node); + } + if (RFAPI_MONITOR_VPN( + original_vpn_node)) /* check agg, so not allocated */ + RFAPI_MONITOR_VPN_W_ALLOC(original_vpn_node) = NULL; + + /* + * update the node pointers on the monitors + */ + for (m = moved; m; m = m->next) { + ++movecount; + m->node = par; + } + + RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, + parent_already_refcounted - movecount); + while (movecount > parent_already_refcounted) { + agg_lock_node(par); + ++parent_already_refcounted; + } + while (movecount < parent_already_refcounted) { + /* unlikely, but code defensively */ + agg_unlock_node(par); + --parent_already_refcounted; + } + RFAPI_CHECK_REFCOUNT(original_vpn_node, SAFI_MPLS_VPN, + movecount + lockoffset); + while (movecount--) { + agg_unlock_node(original_vpn_node); + } + +#ifdef DEBUG_MONITOR_MOVE_SHORTER + { + vnc_zlog_debug_verbose("%s: moved to node pfx=%pFX", __func__, + &par->p); + } +#endif + + + return moved; +} + +/* + * Implement MONITOR_MOVE_LONGER(new_node) from + * RFAPI-Import-Event-Handling.txt + */ +static void rfapiMonitorMoveLonger(struct agg_node *new_vpn_node) +{ + struct rfapi_monitor_vpn *monitor; + struct rfapi_monitor_vpn *mlast; + struct bgp_path_info *bpi; + struct agg_node *par; + const struct prefix *new_vpn_node_p = agg_node_get_prefix(new_vpn_node); + + RFAPI_CHECK_REFCOUNT(new_vpn_node, SAFI_MPLS_VPN, 0); + + /* + * Make sure we have at least one valid route at the new node + */ + for (bpi = new_vpn_node->info; bpi; bpi = bpi->next) { + struct prefix pfx; + if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx)) + break; + } + + if (!bpi) { + vnc_zlog_debug_verbose( + "%s: no valid routes at node %p, so not attempting moves", + __func__, new_vpn_node); + return; + } + + /* + * Find first parent node that has monitors + */ + for (par = agg_node_parent(new_vpn_node); par; + par = agg_node_parent(par)) { + if (RFAPI_MONITOR_VPN(par)) + break; + } + + if (!par) { + vnc_zlog_debug_verbose( + "%s: no parent nodes with monitors, done", __func__); + return; + } + + /* + * Check each of these monitors to see of their longest-match + * is now the updated node. Move any such monitors to the more- + * specific updated node + */ + for (mlast = NULL, monitor = RFAPI_MONITOR_VPN(par); monitor;) { + /* + * If new longest match for monitor prefix is the new + * route's prefix, move monitor to new route's prefix + */ + if (prefix_match(new_vpn_node_p, &monitor->p)) { + /* detach */ + if (mlast) { + mlast->next = monitor->next; + } else { + RFAPI_MONITOR_VPN_W_ALLOC(par) = monitor->next; + } + + + /* attach */ + monitor->next = RFAPI_MONITOR_VPN(new_vpn_node); + RFAPI_MONITOR_VPN_W_ALLOC(new_vpn_node) = monitor; + monitor->node = new_vpn_node; + + agg_lock_node(new_vpn_node); /* incr refcount */ + + monitor = mlast ? mlast->next : RFAPI_MONITOR_VPN(par); + + RFAPI_CHECK_REFCOUNT(par, SAFI_MPLS_VPN, 1); + /* decr refcount after we're done with par as this might + * free it */ + agg_unlock_node(par); + + continue; + } + mlast = monitor; + monitor = monitor->next; + } + + RFAPI_CHECK_REFCOUNT(new_vpn_node, SAFI_MPLS_VPN, 0); +} + + +static void rfapiBgpInfoChainFree(struct bgp_path_info *bpi) +{ + struct bgp_path_info *next; + + while (bpi) { + + /* + * If there is a timer waiting to delete this bpi, cancel + * the timer and delete immediately + */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && + bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = + EVENT_ARG(bpi->extra->vnc->vnc.import.timer); + + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + EVENT_OFF(bpi->extra->vnc->vnc.import.timer); + } + + next = bpi->next; + bpi->next = NULL; + rfapiBgpInfoFree(bpi); + bpi = next; + } +} + +static void rfapiImportTableFlush(struct rfapi_import_table *it) +{ + afi_t afi; + + /* + * Free ecommunity + */ + ecommunity_free(&it->rt_import_list); + it->rt_import_list = NULL; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_node *rn; + struct agg_table *at; + + at = it->imported_vpn[afi]; + if (at) { + for (rn = agg_route_top(at); rn; + rn = agg_route_next(rn)) { + /* + * Each route_node has: + * aggregate: points to rfapi_it_extra with + * monitor chain(s) + * info: points to chain of bgp_path_info + */ + /* free bgp_path_info and its children */ + rfapiBgpInfoChainFree(rn->info); + rn->info = NULL; + + rfapiMonitorExtraFlush(SAFI_MPLS_VPN, rn); + } + agg_table_finish(at); + } + + if (at) { + at = it->imported_encap[afi]; + for (rn = agg_route_top(at); rn; + rn = agg_route_next(rn)) { + /* free bgp_path_info and its children */ + rfapiBgpInfoChainFree(rn->info); + rn->info = NULL; + + rfapiMonitorExtraFlush(SAFI_ENCAP, rn); + } + agg_table_finish(at); + } + } + if (it->monitor_exterior_orphans) { + skiplist_free(it->monitor_exterior_orphans); + } +} + +void rfapiImportTableRefDelByIt(struct bgp *bgp, + struct rfapi_import_table *it_target) +{ + struct rfapi *h; + struct rfapi_import_table *it; + struct rfapi_import_table *prev = NULL; + + assert(it_target); + + h = bgp->rfapi; + assert(h); + + for (it = h->imports; it; prev = it, it = it->next) { + if (it == it_target) + break; + } + + assert(it); + assert(it->refcount); + + it->refcount -= 1; + + if (!it->refcount) { + if (prev) { + prev->next = it->next; + } else { + h->imports = it->next; + } + rfapiImportTableFlush(it); + XFREE(MTYPE_RFAPI_IMPORTTABLE, it); + } +} + +#ifdef RFAPI_REQUIRE_ENCAP_BEEC +/* + * Look for magic BGP Encapsulation Extended Community value + * Format in RFC 5512 Sect. 4.5 + */ +static int rfapiEcommunitiesMatchBeec(struct ecommunity *ecom, + bgp_encap_types type) +{ + int i; + + if (!ecom) + return 0; + + for (i = 0; i < (ecom->size * ECOMMUNITY_SIZE); i += ECOMMUNITY_SIZE) { + + uint8_t *ep; + + ep = ecom->val + i; + + if (ep[0] == ECOMMUNITY_ENCODE_OPAQUE + && ep[1] == ECOMMUNITY_OPAQUE_SUBTYPE_ENCAP + && ep[6] == ((type && 0xff00) >> 8) + && ep[7] == (type & 0xff)) { + + return 1; + } + } + return 0; +} +#endif + +int rfapiEcommunitiesIntersect(struct ecommunity *e1, struct ecommunity *e2) +{ + uint32_t i, j; + + if (!e1 || !e2) + return 0; + + { + char *s1, *s2; + s1 = ecommunity_ecom2str(e1, ECOMMUNITY_FORMAT_DISPLAY, 0); + s2 = ecommunity_ecom2str(e2, ECOMMUNITY_FORMAT_DISPLAY, 0); + vnc_zlog_debug_verbose("%s: e1[%s], e2[%s]", __func__, s1, s2); + XFREE(MTYPE_ECOMMUNITY_STR, s1); + XFREE(MTYPE_ECOMMUNITY_STR, s2); + } + + for (i = 0; i < e1->size; ++i) { + for (j = 0; j < e2->size; ++j) { + if (!memcmp(e1->val + (i * ECOMMUNITY_SIZE), + e2->val + (j * ECOMMUNITY_SIZE), + ECOMMUNITY_SIZE)) { + + return 1; + } + } + } + return 0; +} + +int rfapiEcommunityGetLNI(struct ecommunity *ecom, uint32_t *lni) +{ + if (ecom) { + uint32_t i; + + for (i = 0; i < ecom->size; ++i) { + uint8_t *p = ecom->val + (i * ECOMMUNITY_SIZE); + + if ((*(p + 0) == 0x00) && (*(p + 1) == 0x02)) { + + *lni = (*(p + 5) << 16) | (*(p + 6) << 8) + | (*(p + 7)); + return 0; + } + } + } + return ENOENT; +} + +int rfapiEcommunityGetEthernetTag(struct ecommunity *ecom, uint16_t *tag_id) +{ + struct bgp *bgp = bgp_get_default(); + *tag_id = 0; /* default to untagged */ + if (ecom) { + uint32_t i; + + for (i = 0; i < ecom->size; ++i) { + as_t as = 0; + int encode = 0; + const uint8_t *p = ecom->val + (i * ECOMMUNITY_SIZE); + + /* High-order octet of type. */ + encode = *p++; + + if (*p++ == ECOMMUNITY_ROUTE_TARGET) { + if (encode == ECOMMUNITY_ENCODE_AS4) { + p = ptr_get_be32(p, &as); + } else if (encode == ECOMMUNITY_ENCODE_AS) { + as = (*p++ << 8); + as |= (*p++); + p += 2; /* skip next two, tag/vid + always in lowest bytes */ + } + if (as == bgp->as) { + *tag_id = *p++ << 8; + *tag_id |= (*p++); + return 0; + } + } + } + } + return ENOENT; +} + +static int rfapiVpnBiNhEqualsPt(struct bgp_path_info *bpi, + struct rfapi_ip_addr *hpt) +{ + uint8_t family; + + if (!hpt || !bpi) + return 0; + + family = BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len); + + if (hpt->addr_family != family) + return 0; + + switch (family) { + case AF_INET: + if (bpi->attr->mp_nexthop_global_in.s_addr + != hpt->addr.v4.s_addr) + return 0; + break; + + case AF_INET6: + if (IPV6_ADDR_CMP(&bpi->attr->mp_nexthop_global, &hpt->addr.v6)) + return 0; + break; + + default: + return 0; + } + + return 1; +} + + +/* + * Compare 2 VPN BIs. Return true if they have the same VN and UN addresses + */ +static int rfapiVpnBiSamePtUn(struct bgp_path_info *bpi1, + struct bgp_path_info *bpi2) +{ + struct prefix pfx_un1; + struct prefix pfx_un2; + + if (!bpi1 || !bpi2) + return 0; + + /* + * VN address comparisons + */ + + if (BGP_MP_NEXTHOP_FAMILY(bpi1->attr->mp_nexthop_len) + != BGP_MP_NEXTHOP_FAMILY(bpi2->attr->mp_nexthop_len)) { + return 0; + } + + switch (BGP_MP_NEXTHOP_FAMILY(bpi1->attr->mp_nexthop_len)) { + case AF_INET: + if (bpi1->attr->mp_nexthop_global_in.s_addr + != bpi2->attr->mp_nexthop_global_in.s_addr) + return 0; + break; + + case AF_INET6: + if (IPV6_ADDR_CMP(&bpi1->attr->mp_nexthop_global, + &bpi2->attr->mp_nexthop_global)) + return 0; + break; + + default: + return 0; + } + + memset(&pfx_un1, 0, sizeof(pfx_un1)); + memset(&pfx_un2, 0, sizeof(pfx_un2)); + + /* + * UN address comparisons + */ + if (rfapiGetVncTunnelUnAddr(bpi1->attr, &pfx_un1)) { + if (bpi1->extra) { + pfx_un1.family = bpi1->extra->vnc->vnc.import.un_family; + switch (bpi1->extra->vnc->vnc.import.un_family) { + case AF_INET: + pfx_un1.u.prefix4 = + bpi1->extra->vnc->vnc.import.un.addr4; + break; + case AF_INET6: + pfx_un1.u.prefix6 = + bpi1->extra->vnc->vnc.import.un.addr6; + break; + default: + pfx_un1.family = AF_UNSPEC; + break; + } + } + } + + if (rfapiGetVncTunnelUnAddr(bpi2->attr, &pfx_un2)) { + if (bpi2->extra) { + pfx_un2.family = bpi2->extra->vnc->vnc.import.un_family; + switch (bpi2->extra->vnc->vnc.import.un_family) { + case AF_INET: + pfx_un2.u.prefix4 = + bpi2->extra->vnc->vnc.import.un.addr4; + break; + case AF_INET6: + pfx_un2.u.prefix6 = + bpi2->extra->vnc->vnc.import.un.addr6; + break; + default: + pfx_un2.family = AF_UNSPEC; + break; + } + } + } + + if (pfx_un1.family == AF_UNSPEC || pfx_un2.family == AF_UNSPEC) + return 0; + + if (pfx_un1.family != pfx_un2.family) + return 0; + + switch (pfx_un1.family) { + case AF_INET: + if (!IPV4_ADDR_SAME(&pfx_un1.u.prefix4, &pfx_un2.u.prefix4)) + return 0; + break; + case AF_INET6: + if (!IPV6_ADDR_SAME(&pfx_un1.u.prefix6, &pfx_un2.u.prefix6)) + return 0; + break; + } + + + return 1; +} + +uint8_t rfapiRfpCost(struct attr *attr) +{ + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + if (attr->local_pref > 255) { + return 0; + } + return 255 - attr->local_pref; + } + + return 255; +} + +/*------------------------------------------ + * rfapi_extract_l2o + * + * Find Layer 2 options in an option chain + * + * input: + * pHop option chain + * + * output: + * l2o layer 2 options extracted + * + * return value: + * 0 OK + * 1 no options found + * + --------------------------------------------*/ +int rfapi_extract_l2o( + struct bgp_tea_options *pHop, /* chain of options */ + struct rfapi_l2address_option *l2o) /* return extracted value */ +{ + struct bgp_tea_options *p; + + for (p = pHop; p; p = p->next) { + if ((p->type == RFAPI_VN_OPTION_TYPE_L2ADDR) + && (p->length >= 8)) { + + char *v = p->value; + + memcpy(&l2o->macaddr, v, 6); + + l2o->label = ((v[6] << 12) & 0xff000) + + ((v[7] << 4) & 0xff0) + + ((v[8] >> 4) & 0xf); + + l2o->local_nve_id = (uint8_t)v[10]; + + l2o->logical_net_id = + (v[11] << 16) + (v[12] << 8) + (v[13] << 0); + + return 0; + } + } + return 1; +} + +static struct rfapi_next_hop_entry * +rfapiRouteInfo2NextHopEntry(struct rfapi_ip_prefix *rprefix, + struct bgp_path_info *bpi, /* route to encode */ + uint32_t lifetime, /* use this in nhe */ + struct agg_node *rn) /* req for L2 eth addr */ +{ + struct rfapi_next_hop_entry *new; + int have_vnc_tunnel_un = 0; + const struct prefix *p = agg_node_get_prefix(rn); + +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose("%s: entry, bpi %p, rn %p", __func__, bpi, rn); +#endif + + new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); + + new->prefix = *rprefix; + + if (bpi->extra && decode_rd_type(bpi->extra->vnc->vnc.import.rd.val) == + RD_TYPE_VNC_ETH) { + /* ethernet */ + + struct rfapi_vn_option *vo; + + vo = XCALLOC(MTYPE_RFAPI_VN_OPTION, + sizeof(struct rfapi_vn_option)); + + vo->type = RFAPI_VN_OPTION_TYPE_L2ADDR; + + memcpy(&vo->v.l2addr.macaddr, &p->u.prefix_eth.octet, ETH_ALEN); + /* only low 3 bytes of this are significant */ + (void)rfapiEcommunityGetLNI(bgp_attr_get_ecommunity(bpi->attr), + &vo->v.l2addr.logical_net_id); + (void)rfapiEcommunityGetEthernetTag( + bgp_attr_get_ecommunity(bpi->attr), + &vo->v.l2addr.tag_id); + + /* local_nve_id comes from lower byte of RD type */ + vo->v.l2addr.local_nve_id = + bpi->extra->vnc->vnc.import.rd.val[1]; + + /* label comes from MP_REACH_NLRI label */ + vo->v.l2addr.label = + BGP_PATH_INFO_NUM_LABELS(bpi) + ? decode_label(&bpi->extra->labels->label[0]) + : MPLS_INVALID_LABEL; + + new->vn_options = vo; + + /* + * If there is an auxiliary prefix (i.e., host IP address), + * use it as the nexthop prefix instead of the query prefix + */ + if (bpi->extra->vnc->vnc.import.aux_prefix.family) { + rfapiQprefix2Rprefix(&bpi->extra->vnc->vnc.import + .aux_prefix, + &new->prefix); + } + } + + bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS; /*Default*/ + new->prefix.cost = rfapiRfpCost(bpi->attr); + + struct bgp_attr_encap_subtlv *pEncap; + + switch (BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len)) { + case AF_INET: + new->vn_address.addr_family = AF_INET; + new->vn_address.addr.v4 = bpi->attr->mp_nexthop_global_in; + break; + + case AF_INET6: + new->vn_address.addr_family = AF_INET6; + new->vn_address.addr.v6 = bpi->attr->mp_nexthop_global; + break; + + default: + zlog_warn("%s: invalid vpn nexthop length: %d", __func__, + bpi->attr->mp_nexthop_len); + rfapi_free_next_hop_list(new); + return NULL; + } + + for (pEncap = bgp_attr_get_vnc_subtlvs(bpi->attr); pEncap; + pEncap = pEncap->next) { + switch (pEncap->type) { + case BGP_VNC_SUBTLV_TYPE_LIFETIME: + /* use configured lifetime, not attr lifetime */ + break; + + default: + zlog_warn("%s: unknown VNC option type %d", __func__, + pEncap->type); + + break; + } + } + + bgp_attr_extcom_tunnel_type(bpi->attr, &tun_type); + if (tun_type == BGP_ENCAP_TYPE_MPLS) { + struct prefix p; + /* MPLS carries UN address in next hop */ + rfapiNexthop2Prefix(bpi->attr, &p); + if (p.family != AF_UNSPEC) { + rfapiQprefix2Raddr(&p, &new->un_address); + have_vnc_tunnel_un = 1; + } + } + + for (pEncap = bpi->attr->encap_subtlvs; pEncap; pEncap = pEncap->next) { + switch (pEncap->type) { + case BGP_ENCAP_SUBTLV_TYPE_REMOTE_ENDPOINT: + /* + * Overrides ENCAP UN address, if any + */ + switch (pEncap->length) { + + case 8: + new->un_address.addr_family = AF_INET; + memcpy(&new->un_address.addr.v4, pEncap->value, + 4); + have_vnc_tunnel_un = 1; + break; + + case 20: + new->un_address.addr_family = AF_INET6; + memcpy(&new->un_address.addr.v6, pEncap->value, + 16); + have_vnc_tunnel_un = 1; + break; + + default: + zlog_warn( + "%s: invalid tunnel subtlv UN addr length (%d) for bpi %p", + __func__, pEncap->length, bpi); + } + break; + + default: + zlog_warn("%s: unknown Encap Attribute option type %d", + __func__, pEncap->type); + break; + } + } + + new->un_options = rfapi_encap_tlv_to_un_option(bpi->attr); + +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose("%s: line %d: have_vnc_tunnel_un=%d", __func__, + __LINE__, have_vnc_tunnel_un); +#endif + + if (!have_vnc_tunnel_un && bpi->extra) { + /* + * use cached UN address from ENCAP route + */ + new->un_address.addr_family = + bpi->extra->vnc->vnc.import.un_family; + switch (new->un_address.addr_family) { + case AF_INET: + new->un_address.addr.v4 = + bpi->extra->vnc->vnc.import.un.addr4; + break; + case AF_INET6: + new->un_address.addr.v6 = + bpi->extra->vnc->vnc.import.un.addr6; + break; + default: + zlog_warn("%s: invalid UN addr family (%d) for bpi %p", + __func__, new->un_address.addr_family, bpi); + rfapi_free_next_hop_list(new); + return NULL; + } + } + + new->lifetime = lifetime; + return new; +} + +int rfapiHasNonRemovedRoutes(struct agg_node *rn) +{ + struct bgp_path_info *bpi; + + for (bpi = rn->info; bpi; bpi = bpi->next) { + struct prefix pfx; + + if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) + && (bpi->extra && !rfapiGetUnAddrOfVpnBi(bpi, &pfx))) { + + return 1; + } + } + return 0; +} + +#ifdef DEBUG_IT_NODES +/* + * DEBUG FUNCTION + */ +void rfapiDumpNode(struct agg_node *rn) +{ + struct bgp_path_info *bpi; + + vnc_zlog_debug_verbose("%s: rn=%p", __func__, rn); + for (bpi = rn->info; bpi; bpi = bpi->next) { + struct prefix pfx; + int ctrc = rfapiGetUnAddrOfVpnBi(bpi, &pfx); + int nr; + + if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) + && (bpi->extra && !ctrc)) { + + nr = 1; + } else { + nr = 0; + } + + vnc_zlog_debug_verbose( + " bpi=%p, nr=%d, flags=0x%x, extra=%p, ctrc=%d", bpi, + nr, bpi->flags, bpi->extra, ctrc); + } +} +#endif + +static int rfapiNhlAddNodeRoutes( + struct agg_node *rn, /* in */ + struct rfapi_ip_prefix *rprefix, /* in */ + uint32_t lifetime, /* in */ + int removed, /* in */ + struct rfapi_next_hop_entry **head, /* in/out */ + struct rfapi_next_hop_entry **tail, /* in/out */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_node *rfd_rib_node, /* preload this NVE rib node */ + struct prefix *pfx_target_original) /* query target */ +{ + struct bgp_path_info *bpi; + struct rfapi_next_hop_entry *new; + struct prefix pfx_un; + struct skiplist *seen_nexthops; + int count = 0; + const struct prefix *p = agg_node_get_prefix(rn); + int is_l2 = (p->family == AF_ETHERNET); + + if (rfd_rib_node) { + struct agg_table *atable = agg_get_table(rfd_rib_node); + struct rfapi_descriptor *rfd; + + if (atable) { + rfd = agg_get_table_info(atable); + + if (rfapiRibFTDFilterRecentPrefix(rfd, rn, + pfx_target_original)) + return 0; + } + } + + seen_nexthops = + skiplist_new(0, vnc_prefix_cmp, prefix_free_lists); + + for (bpi = rn->info; bpi; bpi = bpi->next) { + + struct prefix pfx_vn; + struct prefix *newpfx; + + if (removed && !CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { +#ifdef DEBUG_RETURNED_NHL + vnc_zlog_debug_verbose( + "%s: want holddown, this route not holddown, skip", + __func__); +#endif + continue; + } + if (!removed && CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + continue; + } + + if (!bpi->extra) { + continue; + } + + /* + * Check for excluded VN address + */ + if (rfapiVpnBiNhEqualsPt(bpi, exclude_vnaddr)) + continue; + + /* + * Check for VN address (nexthop) copied already + */ + if (is_l2) { + /* L2 routes: semantic nexthop in aux_prefix; VN addr + * ain't it */ + pfx_vn = bpi->extra->vnc->vnc.import.aux_prefix; + } else { + rfapiNexthop2Prefix(bpi->attr, &pfx_vn); + } + if (!skiplist_search(seen_nexthops, &pfx_vn, NULL)) { +#ifdef DEBUG_RETURNED_NHL + vnc_zlog_debug_verbose( + "%s: already put VN/nexthop %pFX, skip", + __func__, &pfx_vn); +#endif + continue; + } + + if (rfapiGetUnAddrOfVpnBi(bpi, &pfx_un)) { +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose( + "%s: failed to get UN address of this VPN bpi", + __func__); +#endif + continue; + } + + newpfx = prefix_new(); + *newpfx = pfx_vn; + skiplist_insert(seen_nexthops, newpfx, newpfx); + + new = rfapiRouteInfo2NextHopEntry(rprefix, bpi, lifetime, rn); + if (new) { + if (rfapiRibPreloadBi(rfd_rib_node, &pfx_vn, &pfx_un, + lifetime, bpi)) { + /* duplicate filtered by RIB */ + rfapi_free_next_hop_list(new); + new = NULL; + } + } + + if (new) { + if (*tail) { + (*tail)->next = new; + } else { + *head = new; + } + *tail = new; + ++count; + } + } + + skiplist_free(seen_nexthops); + + return count; +} + + +/* + * Breadth-first + * + * omit_node is meant for the situation where we are adding a subtree + * of a parent of some original requested node. The response already + * contains the original requested node, and we don't want to duplicate + * its routes in the list, so we skip it if the right or left node + * matches (of course, we still travel down its child subtrees). + */ +static int rfapiNhlAddSubtree( + struct agg_node *rn, /* in */ + uint32_t lifetime, /* in */ + struct rfapi_next_hop_entry **head, /* in/out */ + struct rfapi_next_hop_entry **tail, /* in/out */ + struct agg_node *omit_node, /* in */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload here */ + struct prefix *pfx_target_original) /* query target */ +{ + struct rfapi_ip_prefix rprefix; + int rcount = 0; + + /* FIXME: need to find a better way here to work without sticking our + * hands in node->link */ + if (agg_node_left(rn) && agg_node_left(rn) != omit_node) { + if (agg_node_left(rn)->info) { + const struct prefix *p = + agg_node_get_prefix(agg_node_left(rn)); + int count = 0; + struct agg_node *rib_rn = NULL; + + rfapiQprefix2Rprefix(p, &rprefix); + if (rfd_rib_table) + rib_rn = agg_node_get(rfd_rib_table, p); + + count = rfapiNhlAddNodeRoutes( + agg_node_left(rn), &rprefix, lifetime, 0, head, + tail, exclude_vnaddr, rib_rn, + pfx_target_original); + if (!count) { + count = rfapiNhlAddNodeRoutes( + agg_node_left(rn), &rprefix, lifetime, + 1, head, tail, exclude_vnaddr, rib_rn, + pfx_target_original); + } + rcount += count; + if (rib_rn) + agg_unlock_node(rib_rn); + } + } + + if (agg_node_right(rn) && agg_node_right(rn) != omit_node) { + if (agg_node_right(rn)->info) { + const struct prefix *p = + agg_node_get_prefix(agg_node_right(rn)); + int count = 0; + struct agg_node *rib_rn = NULL; + + rfapiQprefix2Rprefix(p, &rprefix); + if (rfd_rib_table) + rib_rn = agg_node_get(rfd_rib_table, p); + + count = rfapiNhlAddNodeRoutes( + agg_node_right(rn), &rprefix, lifetime, 0, head, + tail, exclude_vnaddr, rib_rn, + pfx_target_original); + if (!count) { + count = rfapiNhlAddNodeRoutes( + agg_node_right(rn), &rprefix, lifetime, + 1, head, tail, exclude_vnaddr, rib_rn, + pfx_target_original); + } + rcount += count; + if (rib_rn) + agg_unlock_node(rib_rn); + } + } + + if (agg_node_left(rn)) { + rcount += rfapiNhlAddSubtree( + agg_node_left(rn), lifetime, head, tail, omit_node, + exclude_vnaddr, rfd_rib_table, pfx_target_original); + } + if (agg_node_right(rn)) { + rcount += rfapiNhlAddSubtree( + agg_node_right(rn), lifetime, head, tail, omit_node, + exclude_vnaddr, rfd_rib_table, pfx_target_original); + } + + return rcount; +} + +/* + * Implementation of ROUTE_LIST(node) from RFAPI-Import-Event-Handling.txt + * + * Construct an rfapi nexthop list based on the routes attached to + * the specified node. + * + * If there are any routes that do NOT have BGP_PATH_REMOVED set, + * return those only. If there are ONLY routes with BGP_PATH_REMOVED, + * then return those, and also include all the non-removed routes from the + * next less-specific node (i.e., this node's parent) at the end. + */ +struct rfapi_next_hop_entry *rfapiRouteNode2NextHopList( + struct agg_node *rn, uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload here */ + struct prefix *pfx_target_original) /* query target */ +{ + struct rfapi_ip_prefix rprefix; + struct rfapi_next_hop_entry *answer = NULL; + struct rfapi_next_hop_entry *last = NULL; + struct agg_node *parent; + const struct prefix *p = agg_node_get_prefix(rn); + int count = 0; + struct agg_node *rib_rn; + +#ifdef DEBUG_RETURNED_NHL + vnc_zlog_debug_verbose("%s: called with node pfx=%rRN", __func__, rn); + zlog_backtrace(LOG_INFO); +#endif + + rfapiQprefix2Rprefix(p, &rprefix); + + rib_rn = rfd_rib_table ? agg_node_get(rfd_rib_table, p) : NULL; + + /* + * Add non-withdrawn routes at this node + */ + count = rfapiNhlAddNodeRoutes(rn, &rprefix, lifetime, 0, &answer, &last, + exclude_vnaddr, rib_rn, + pfx_target_original); + + /* + * If the list has at least one entry, it's finished + */ + if (count) { + count += rfapiNhlAddSubtree(rn, lifetime, &answer, &last, NULL, + exclude_vnaddr, rfd_rib_table, + pfx_target_original); + vnc_zlog_debug_verbose("%s: %d nexthops, answer=%p", __func__, + count, answer); +#ifdef DEBUG_RETURNED_NHL + rfapiPrintNhl(NULL, answer); +#endif + if (rib_rn) + agg_unlock_node(rib_rn); + return answer; + } + + /* + * Add withdrawn routes at this node + */ + count = rfapiNhlAddNodeRoutes(rn, &rprefix, lifetime, 1, &answer, &last, + exclude_vnaddr, rib_rn, + pfx_target_original); + if (rib_rn) + agg_unlock_node(rib_rn); + + // rfapiPrintNhl(NULL, answer); + + /* + * walk up the tree until we find a node with non-deleted + * routes, then add them + */ + for (parent = agg_node_parent(rn); parent; + parent = agg_node_parent(parent)) { + if (rfapiHasNonRemovedRoutes(parent)) { + break; + } + } + + /* + * Add non-withdrawn routes from less-specific prefix + */ + if (parent) { + const struct prefix *p = agg_node_get_prefix(parent); + + rib_rn = rfd_rib_table ? agg_node_get(rfd_rib_table, p) : NULL; + rfapiQprefix2Rprefix(p, &rprefix); + count += rfapiNhlAddNodeRoutes(parent, &rprefix, lifetime, 0, + &answer, &last, exclude_vnaddr, + rib_rn, pfx_target_original); + count += rfapiNhlAddSubtree(parent, lifetime, &answer, &last, + rn, exclude_vnaddr, rfd_rib_table, + pfx_target_original); + if (rib_rn) + agg_unlock_node(rib_rn); + } else { + /* + * There is no parent with non-removed routes. Still need to + * add subtree of original node if it contributed routes to the + * answer. + */ + if (count) + count += rfapiNhlAddSubtree(rn, lifetime, &answer, + &last, rn, exclude_vnaddr, + rfd_rib_table, + pfx_target_original); + } + + vnc_zlog_debug_verbose("%s: %d nexthops, answer=%p", __func__, count, + answer); +#ifdef DEBUG_RETURNED_NHL + rfapiPrintNhl(NULL, answer); +#endif + return answer; +} + +/* + * Construct nexthop list of all routes in table + */ +struct rfapi_next_hop_entry *rfapiRouteTable2NextHopList( + struct agg_table *rt, uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload this NVE rib table */ + struct prefix *pfx_target_original) /* query target */ +{ + struct agg_node *rn; + struct rfapi_next_hop_entry *biglist = NULL; + struct rfapi_next_hop_entry *nhl; + struct rfapi_next_hop_entry *tail = NULL; + int count = 0; + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + + nhl = rfapiRouteNode2NextHopList(rn, lifetime, exclude_vnaddr, + rfd_rib_table, + pfx_target_original); + if (!tail) { + tail = biglist = nhl; + if (tail) + count = 1; + } else { + tail->next = nhl; + } + if (tail) { + while (tail->next) { + ++count; + tail = tail->next; + } + } + } + + vnc_zlog_debug_verbose("%s: returning %d routes", __func__, count); + return biglist; +} + +struct rfapi_next_hop_entry *rfapiEthRouteNode2NextHopList( + struct agg_node *rn, struct rfapi_ip_prefix *rprefix, + uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload NVE rib table */ + struct prefix *pfx_target_original) /* query target */ +{ + int count = 0; + struct rfapi_next_hop_entry *answer = NULL; + struct rfapi_next_hop_entry *last = NULL; + struct agg_node *rib_rn; + + rib_rn = rfd_rib_table + ? agg_node_get(rfd_rib_table, agg_node_get_prefix(rn)) + : NULL; + + count = rfapiNhlAddNodeRoutes(rn, rprefix, lifetime, 0, &answer, &last, + NULL, rib_rn, pfx_target_original); + +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose("%s: node %p: %d non-holddown routes", __func__, + rn, count); +#endif + + if (!count) { + count = rfapiNhlAddNodeRoutes(rn, rprefix, lifetime, 1, &answer, + &last, exclude_vnaddr, rib_rn, + pfx_target_original); + vnc_zlog_debug_verbose("%s: node %p: %d holddown routes", + __func__, rn, count); + } + + if (rib_rn) + agg_unlock_node(rib_rn); + +#ifdef DEBUG_RETURNED_NHL + rfapiPrintNhl(NULL, answer); +#endif + + return answer; +} + + +/* + * Construct nexthop list of all routes in table + */ +struct rfapi_next_hop_entry *rfapiEthRouteTable2NextHopList( + uint32_t logical_net_id, struct rfapi_ip_prefix *rprefix, + uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload NVE rib node */ + struct prefix *pfx_target_original) /* query target */ +{ + struct rfapi_import_table *it; + struct bgp *bgp = bgp_get_default(); + struct agg_table *rt; + struct agg_node *rn; + struct rfapi_next_hop_entry *biglist = NULL; + struct rfapi_next_hop_entry *nhl; + struct rfapi_next_hop_entry *tail = NULL; + int count = 0; + + + it = rfapiMacImportTableGet(bgp, logical_net_id); + rt = it->imported_vpn[AFI_L2VPN]; + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + + nhl = rfapiEthRouteNode2NextHopList( + rn, rprefix, lifetime, exclude_vnaddr, rfd_rib_table, + pfx_target_original); + if (!tail) { + tail = biglist = nhl; + if (tail) + count = 1; + } else { + tail->next = nhl; + } + if (tail) { + while (tail->next) { + ++count; + tail = tail->next; + } + } + } + + vnc_zlog_debug_verbose("%s: returning %d routes", __func__, count); + return biglist; +} + +/* + * Insert a new bpi to the imported route table node, + * keeping the list of BPIs sorted best route first + */ +static void rfapiBgpInfoAttachSorted(struct agg_node *rn, + struct bgp_path_info *info_new, afi_t afi, + safi_t safi) +{ + struct bgp *bgp; + struct bgp_path_info *prev; + struct bgp_path_info *next; + char pfx_buf[PREFIX2STR_BUFFER] = {}; + + + bgp = bgp_get_default(); /* assume 1 instance for now */ + + if (VNC_DEBUG(IMPORT_BI_ATTACH)) { + vnc_zlog_debug_verbose("%s: info_new->peer=%p", __func__, + info_new->peer); + vnc_zlog_debug_verbose("%s: info_new->peer->su_remote=%p", + __func__, info_new->peer->su_remote); + } + + for (prev = NULL, next = rn->info; next; + prev = next, next = next->next) { + enum bgp_path_selection_reason reason; + + if (!bgp + || (!CHECK_FLAG(info_new->flags, BGP_PATH_REMOVED) + && CHECK_FLAG(next->flags, BGP_PATH_REMOVED)) + || bgp_path_info_cmp_compatible(bgp, info_new, next, + pfx_buf, afi, safi, + &reason) + == -1) { /* -1 if 1st is better */ + break; + } + } + vnc_zlog_debug_verbose("%s: prev=%p, next=%p", __func__, prev, next); + if (prev) { + prev->next = info_new; + } else { + rn->info = info_new; + } + info_new->prev = prev; + info_new->next = next; + if (next) + next->prev = info_new; + bgp_attr_intern(info_new->attr); +} + +static void rfapiBgpInfoDetach(struct agg_node *rn, struct bgp_path_info *bpi) +{ + /* + * Remove the route (doubly-linked) + */ + // bgp_attr_unintern (&bpi->attr); + if (bpi->next) + bpi->next->prev = bpi->prev; + if (bpi->prev) + bpi->prev->next = bpi->next; + else + rn->info = bpi->next; +} + +/* + * For L3-indexed import tables + */ +static int rfapi_bi_peer_rd_cmp(const void *b1, const void *b2) +{ + const struct bgp_path_info *bpi1 = b1; + const struct bgp_path_info *bpi2 = b2; + + /* + * Compare peers + */ + if (bpi1->peer < bpi2->peer) + return -1; + if (bpi1->peer > bpi2->peer) + return 1; + + /* + * compare RDs + */ + return vnc_prefix_cmp((const struct prefix *)&bpi1->extra->vnc->vnc + .import.rd, + (const struct prefix *)&bpi2->extra->vnc->vnc + .import.rd); +} + +/* + * For L2-indexed import tables + * The BPIs in these tables should ALWAYS have an aux_prefix set because + * they arrive via IPv4 or IPv6 advertisements. + */ +static int rfapi_bi_peer_rd_aux_cmp(const void *b1, const void *b2) +{ + const struct bgp_path_info *bpi1 = b1; + const struct bgp_path_info *bpi2 = b2; + int rc; + + /* + * Compare peers + */ + if (bpi1->peer < bpi2->peer) + return -1; + if (bpi1->peer > bpi2->peer) + return 1; + + /* + * compare RDs + */ + rc = vnc_prefix_cmp((struct prefix *)&bpi1->extra->vnc->vnc.import.rd, + (struct prefix *)&bpi2->extra->vnc->vnc.import.rd); + if (rc) { + return rc; + } + + /* + * L2 import tables can have multiple entries with the + * same MAC address, same RD, but different L3 addresses. + * + * Use presence of aux_prefix with AF=ethernet and prefixlen=1 + * as magic value to signify explicit wildcarding of the aux_prefix. + * This magic value will not appear in bona fide bpi entries in + * the import table, but is allowed in the "fake" bpi used to + * probe the table when searching. (We have to test both b1 and b2 + * because there is no guarantee of the order the test key and + * the real key will be passed) + */ + if ((bpi1->extra->vnc->vnc.import.aux_prefix.family == AF_ETHERNET && + (bpi1->extra->vnc->vnc.import.aux_prefix.prefixlen == 1)) || + (bpi2->extra->vnc->vnc.import.aux_prefix.family == AF_ETHERNET && + (bpi2->extra->vnc->vnc.import.aux_prefix.prefixlen == 1))) { + /* + * wildcard aux address specified + */ + return 0; + } + + return vnc_prefix_cmp(&bpi1->extra->vnc->vnc.import.aux_prefix, + &bpi2->extra->vnc->vnc.import.aux_prefix); +} + + +/* + * Index on RD and Peer + */ +static void rfapiItBiIndexAdd(struct agg_node *rn, /* Import table VPN node */ + struct bgp_path_info *bpi) /* new BPI */ +{ + struct skiplist *sl; + const struct prefix *p; + + assert(rn); + assert(bpi); + assert(bpi->extra); + + vnc_zlog_debug_verbose("%s: bpi %p, peer %p, rd %pRDP", __func__, bpi, + bpi->peer, &bpi->extra->vnc->vnc.import.rd); + + sl = RFAPI_RDINDEX_W_ALLOC(rn); + if (!sl) { + p = agg_node_get_prefix(rn); + if (AF_ETHERNET == p->family) { + sl = skiplist_new(0, rfapi_bi_peer_rd_aux_cmp, NULL); + } else { + sl = skiplist_new(0, rfapi_bi_peer_rd_cmp, NULL); + } + RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd = sl; + agg_lock_node(rn); /* for skiplist */ + } + assert(!skiplist_insert(sl, (void *)bpi, (void *)bpi)); + agg_lock_node(rn); /* for skiplist entry */ + + /* NB: BPIs in import tables are not refcounted */ +} + +static void rfapiItBiIndexDump(struct agg_node *rn) +{ + struct skiplist *sl; + void *cursor = NULL; + struct bgp_path_info *k; + struct bgp_path_info *v; + int rc; + + sl = RFAPI_RDINDEX(rn); + if (!sl) + return; + + for (rc = skiplist_next(sl, (void **)&k, (void **)&v, &cursor); !rc; + rc = skiplist_next(sl, (void **)&k, (void **)&v, &cursor)) { + + char buf[RD_ADDRSTRLEN]; + char buf_aux_pfx[PREFIX_STRLEN]; + + prefix_rd2str(&k->extra->vnc->vnc.import.rd, buf, sizeof(buf), + bgp_get_asnotation(k->peer ? k->peer->bgp : NULL)); + if (k->extra->vnc->vnc.import.aux_prefix.family) { + prefix2str(&k->extra->vnc->vnc.import.aux_prefix, + buf_aux_pfx, sizeof(buf_aux_pfx)); + } else + strlcpy(buf_aux_pfx, "(none)", sizeof(buf_aux_pfx)); + + vnc_zlog_debug_verbose("bpi %p, peer %p, rd %s, aux_prefix %s", + k, k->peer, buf, buf_aux_pfx); + } +} + +static struct bgp_path_info *rfapiItBiIndexSearch( + struct agg_node *rn, /* Import table VPN node */ + struct prefix_rd *prd, struct peer *peer, + const struct prefix *aux_prefix) /* optional L3 addr for L2 ITs */ +{ + struct skiplist *sl; + int rc; + struct bgp_path_info bpi_fake = {0}; + struct bgp_path_info_extra bpi_extra = {0}; + struct bgp_path_info_extra_vnc bpi_extra_vnc = { 0 }; + struct bgp_path_info *bpi_result; + + sl = RFAPI_RDINDEX(rn); + if (!sl) + return NULL; + +#ifdef DEBUG_BI_SEARCH + { + char buf_aux_pfx[PREFIX_STRLEN]; + + if (aux_prefix) { + prefix2str(aux_prefix, buf_aux_pfx, + sizeof(buf_aux_pfx)); + } else + strlcpy(buf_aux_pfx, "(nil)", sizeof(buf_aux_pfx)); + + vnc_zlog_debug_verbose( + "%s want prd=%pRDP, peer=%p, aux_prefix=%s", __func__, + prd, peer, buf_aux_pfx); + rfapiItBiIndexDump(rn); + } +#endif + + /* threshold is a WAG */ + if (sl->count < 3) { +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose("%s: short list algorithm", __func__); +#endif + /* if short list, linear search might be faster */ + for (bpi_result = rn->info; bpi_result; + bpi_result = bpi_result->next) { +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose("%s: bpi has prd=%pRDP, peer=%p", + __func__, + &bpi_result->extra->vnc->vnc + .import.rd, + bpi_result->peer); +#endif + if (peer == bpi_result->peer && + !prefix_cmp((struct prefix *)&bpi_result->extra->vnc + ->vnc.import.rd, + (struct prefix *)prd)) { +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose( + "%s: peer and RD same, doing aux_prefix check", + __func__); +#endif + if (!aux_prefix || + !prefix_cmp(aux_prefix, + &bpi_result->extra->vnc->vnc + .import.aux_prefix)) { +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose("%s: match", + __func__); +#endif + break; + } + } + } + return bpi_result; + } + + bpi_fake.peer = peer; + bpi_fake.extra = &bpi_extra; + bpi_fake.extra->vnc = &bpi_extra_vnc; + bpi_fake.extra->vnc->vnc.import.rd = *prd; + if (aux_prefix) { + bpi_fake.extra->vnc->vnc.import.aux_prefix = *aux_prefix; + } else { + /* wildcard */ + bpi_fake.extra->vnc->vnc.import.aux_prefix.family = AF_ETHERNET; + bpi_fake.extra->vnc->vnc.import.aux_prefix.prefixlen = 1; + } + + rc = skiplist_search(sl, (void *)&bpi_fake, (void *)&bpi_result); + + if (rc) { +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose("%s: no match", __func__); +#endif + return NULL; + } + +#ifdef DEBUG_BI_SEARCH + vnc_zlog_debug_verbose("%s: matched bpi=%p", __func__, bpi_result); +#endif + + return bpi_result; +} + +static void rfapiItBiIndexDel(struct agg_node *rn, /* Import table VPN node */ + struct bgp_path_info *bpi) /* old BPI */ +{ + struct skiplist *sl; + int rc; + + vnc_zlog_debug_verbose("%s: bpi %p, peer %p, rd %pRDP", __func__, bpi, + bpi->peer, &bpi->extra->vnc->vnc.import.rd); + + sl = RFAPI_RDINDEX(rn); + assert(sl); + + rc = skiplist_delete(sl, (void *)(bpi), (void *)bpi); + if (rc) { + rfapiItBiIndexDump(rn); + } + assert(!rc); + + agg_unlock_node(rn); /* for skiplist entry */ + + /* NB: BPIs in import tables are not refcounted */ +} + +/* + * Add a backreference at the ENCAP node to the VPN route that + * refers to it + */ +static void +rfapiMonitorEncapAdd(struct rfapi_import_table *import_table, + struct prefix *p, /* VN address */ + struct agg_node *vpn_rn, /* VPN node */ + struct bgp_path_info *vpn_bpi) /* VPN bpi/route */ +{ + afi_t afi = family2afi(p->family); + struct agg_node *rn; + struct rfapi_monitor_encap *m; + + assert(afi); + rn = agg_node_get(import_table->imported_encap[afi], p); /* locks rn */ + assert(rn); + + m = XCALLOC(MTYPE_RFAPI_MONITOR_ENCAP, + sizeof(struct rfapi_monitor_encap)); + + m->node = vpn_rn; + m->bpi = vpn_bpi; + m->rn = rn; + + /* insert to encap node's list */ + m->next = RFAPI_MONITOR_ENCAP(rn); + if (m->next) + m->next->prev = m; + RFAPI_MONITOR_ENCAP_W_ALLOC(rn) = m; + + /* for easy lookup when deleting vpn route */ + vpn_bpi->extra->vnc->vnc.import.hme = m; + + vnc_zlog_debug_verbose("%s: it=%p, vpn_bpi=%p, afi=%d, encap rn=%p, setting vpn_bpi->extra->vnc->vnc.import.hme=%p", + __func__, import_table, vpn_bpi, afi, rn, m); + + RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 0); + bgp_attr_intern(vpn_bpi->attr); +} + +static void rfapiMonitorEncapDelete(struct bgp_path_info *vpn_bpi) +{ + /* + * Remove encap monitor + */ + vnc_zlog_debug_verbose("%s: vpn_bpi=%p", __func__, vpn_bpi); + if (vpn_bpi->extra) { + struct rfapi_monitor_encap *hme = + vpn_bpi->extra->vnc->vnc.import.hme; + + if (hme) { + + vnc_zlog_debug_verbose("%s: hme=%p", __func__, hme); + + /* Refcount checking takes too long here */ + // RFAPI_CHECK_REFCOUNT(hme->rn, SAFI_ENCAP, 0); + if (hme->next) + hme->next->prev = hme->prev; + if (hme->prev) + hme->prev->next = hme->next; + else + RFAPI_MONITOR_ENCAP_W_ALLOC(hme->rn) = + hme->next; + /* Refcount checking takes too long here */ + // RFAPI_CHECK_REFCOUNT(hme->rn, SAFI_ENCAP, 1); + + /* see if the struct rfapi_it_extra is empty and can be + * freed */ + rfapiMonitorExtraPrune(SAFI_ENCAP, hme->rn); + + agg_unlock_node(hme->rn); /* decr ref count */ + XFREE(MTYPE_RFAPI_MONITOR_ENCAP, hme); + vpn_bpi->extra->vnc->vnc.import.hme = NULL; + } + } +} + +/* + * Timer callback for withdraw + */ +static void rfapiWithdrawTimerVPN(struct event *t) +{ + struct rfapi_withdraw *wcb = EVENT_ARG(t); + struct bgp_path_info *bpi = wcb->info; + struct bgp *bgp = bgp_get_default(); + const struct prefix *p; + struct rfapi_monitor_vpn *moved; + afi_t afi; + bool early_exit = false; + + if (bgp == NULL) { + vnc_zlog_debug_verbose( + "%s: NULL BGP pointer, assume shutdown race condition!!!", + __func__); + early_exit = true; + } + if (bgp && CHECK_FLAG(bgp->flags, BGP_FLAG_DELETE_IN_PROGRESS)) { + vnc_zlog_debug_verbose( + "%s: BGP delete in progress, assume shutdown race condition!!!", + __func__); + early_exit = true; + } + + /* This callback is responsible for the withdraw object's memory */ + if (early_exit) { + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + return; + } + + assert(wcb->node); + assert(bpi); + assert(wcb->import_table); + assert(bpi->extra); + + RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_MPLS_VPN, wcb->lockoffset); + + vnc_zlog_debug_verbose("%s: removing bpi %p at prefix %pRN", __func__, + bpi, wcb->node); + + /* + * Remove the route (doubly-linked) + */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_VALID) + && VALID_INTERIOR_TYPE(bpi->type)) + RFAPI_MONITOR_EXTERIOR(wcb->node)->valid_interior_count--; + + p = agg_node_get_prefix(wcb->node); + afi = family2afi(p->family); + wcb->import_table->holddown_count[afi] -= 1; /* keep count consistent */ + rfapiItBiIndexDel(wcb->node, bpi); + rfapiBgpInfoDetach(wcb->node, bpi); /* with removed bpi */ + + vnc_import_bgp_exterior_del_route_interior(bgp, wcb->import_table, + wcb->node, bpi); + + + /* + * If VNC is configured to send response remove messages, AND + * if the removed route had a UN address, do response removal + * processing. + */ + if (!(bgp->rfapi_cfg->flags + & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { + + int has_valid_duplicate = 0; + struct bgp_path_info *bpii; + + /* + * First check if there are any OTHER routes at this node + * that have the same nexthop and a valid UN address. If + * there are (e.g., from other peers), then the route isn't + * really gone, so skip sending a response removal message. + */ + for (bpii = wcb->node->info; bpii; bpii = bpii->next) { + if (rfapiVpnBiSamePtUn(bpi, bpii)) { + has_valid_duplicate = 1; + break; + } + } + + vnc_zlog_debug_verbose("%s: has_valid_duplicate=%d", __func__, + has_valid_duplicate); + + if (!has_valid_duplicate) { + rfapiRibPendingDeleteRoute(bgp, wcb->import_table, afi, + wcb->node); + } + } + + rfapiMonitorEncapDelete(bpi); + + /* + * If there are no VPN monitors at this VPN Node A, + * we are done + */ + if (!RFAPI_MONITOR_VPN(wcb->node)) { + vnc_zlog_debug_verbose("%s: no VPN monitors at this node", + __func__); + goto done; + } + + /* + * rfapiMonitorMoveShorter only moves monitors if there are + * no remaining valid routes at the current node + */ + moved = rfapiMonitorMoveShorter(wcb->node, 1); + + if (moved) { + rfapiMonitorMovedUp(wcb->import_table, wcb->node, moved->node, + moved); + } + +done: + /* + * Free VPN bpi + */ + rfapiBgpInfoFree(bpi); + wcb->info = NULL; + + /* + * If route count at this node has gone to 0, withdraw exported prefix + */ + if (!wcb->node->info) { + /* see if the struct rfapi_it_extra is empty and can be freed */ + rfapiMonitorExtraPrune(SAFI_MPLS_VPN, wcb->node); + vnc_direct_bgp_del_prefix(bgp, wcb->import_table, wcb->node); + vnc_zebra_del_prefix(bgp, wcb->import_table, wcb->node); + } else { + /* + * nexthop change event + * vnc_direct_bgp_add_prefix() will recompute the VN addr + * ecommunity + */ + vnc_direct_bgp_add_prefix(bgp, wcb->import_table, wcb->node); + } + + RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_MPLS_VPN, 1 + wcb->lockoffset); + agg_unlock_node(wcb->node); /* decr ref count */ + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); +} + +/* + * This works for multiprotocol extension, but not for plain ol' + * unicast IPv4 because that nexthop is stored in attr->nexthop + */ +void rfapiNexthop2Prefix(struct attr *attr, struct prefix *p) +{ + assert(p); + assert(attr); + + memset(p, 0, sizeof(struct prefix)); + + switch (p->family = BGP_MP_NEXTHOP_FAMILY(attr->mp_nexthop_len)) { + case AF_INET: + p->u.prefix4 = attr->mp_nexthop_global_in; + p->prefixlen = IPV4_MAX_BITLEN; + break; + + case AF_INET6: + p->u.prefix6 = attr->mp_nexthop_global; + p->prefixlen = IPV6_MAX_BITLEN; + break; + + default: + vnc_zlog_debug_verbose("%s: Family is unknown = %d", __func__, + p->family); + } +} + +void rfapiUnicastNexthop2Prefix(afi_t afi, struct attr *attr, struct prefix *p) +{ + if (afi == AFI_IP) { + p->family = AF_INET; + p->prefixlen = IPV4_MAX_BITLEN; + p->u.prefix4 = attr->nexthop; + } else { + rfapiNexthop2Prefix(attr, p); + } +} + +static int rfapiAttrNexthopAddrDifferent(struct prefix *p1, struct prefix *p2) +{ + if (!p1 || !p2) { + vnc_zlog_debug_verbose("%s: p1 or p2 is NULL", __func__); + return 1; + } + + /* + * Are address families the same? + */ + if (p1->family != p2->family) { + return 1; + } + + switch (p1->family) { + case AF_INET: + if (IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4)) + return 0; + break; + + case AF_INET6: + if (IPV6_ADDR_SAME(&p1->u.prefix6, &p2->u.prefix6)) + return 0; + break; + + default: + assert(1); + } + + return 1; +} + +static void rfapiCopyUnEncap2VPN(struct bgp_path_info *encap_bpi, + struct bgp_path_info *vpn_bpi) +{ + if (!vpn_bpi || !vpn_bpi->extra) { + zlog_warn("%s: no vpn bpi attr/extra, can't copy UN address", + __func__); + return; + } + + switch (BGP_MP_NEXTHOP_FAMILY(encap_bpi->attr->mp_nexthop_len)) { + case AF_INET: + + /* + * instrumentation to debug segfault of 091127 + */ + vnc_zlog_debug_verbose("%s: vpn_bpi=%p", __func__, vpn_bpi); + vnc_zlog_debug_verbose("%s: vpn_bpi->extra=%p", __func__, + vpn_bpi->extra); + + vpn_bpi->extra->vnc->vnc.import.un_family = AF_INET; + vpn_bpi->extra->vnc->vnc.import.un.addr4 = + encap_bpi->attr->mp_nexthop_global_in; + break; + + case AF_INET6: + vpn_bpi->extra->vnc->vnc.import.un_family = AF_INET6; + vpn_bpi->extra->vnc->vnc.import.un.addr6 = + encap_bpi->attr->mp_nexthop_global; + break; + + default: + zlog_warn("%s: invalid encap nexthop length: %d", __func__, + encap_bpi->attr->mp_nexthop_len); + vpn_bpi->extra->vnc->vnc.import.un_family = AF_UNSPEC; + break; + } +} + +/* + * returns 0 on success, nonzero on error + */ +static int +rfapiWithdrawEncapUpdateCachedUn(struct rfapi_import_table *import_table, + struct bgp_path_info *encap_bpi, + struct agg_node *vpn_rn, + struct bgp_path_info *vpn_bpi) +{ + if (!encap_bpi) { + + /* + * clear cached UN address + */ + if (!vpn_bpi || !vpn_bpi->extra) { + zlog_warn( + "%s: missing VPN bpi/extra, can't clear UN addr", + __func__); + return 1; + } + vpn_bpi->extra->vnc->vnc.import.un_family = AF_UNSPEC; + memset(&vpn_bpi->extra->vnc->vnc.import.un, 0, + sizeof(vpn_bpi->extra->vnc->vnc.import.un)); + if (CHECK_FLAG(vpn_bpi->flags, BGP_PATH_VALID)) { + if (rfapiGetVncTunnelUnAddr(vpn_bpi->attr, NULL)) { + UNSET_FLAG(vpn_bpi->flags, BGP_PATH_VALID); + if (VALID_INTERIOR_TYPE(vpn_bpi->type)) + RFAPI_MONITOR_EXTERIOR(vpn_rn) + ->valid_interior_count--; + /* signal interior route withdrawal to + * import-exterior */ + vnc_import_bgp_exterior_del_route_interior( + bgp_get_default(), import_table, vpn_rn, + vpn_bpi); + } + } + + } else { + if (!vpn_bpi) { + zlog_warn("%s: missing VPN bpi, can't clear UN addr", + __func__); + return 1; + } + rfapiCopyUnEncap2VPN(encap_bpi, vpn_bpi); + if (!CHECK_FLAG(vpn_bpi->flags, BGP_PATH_VALID)) { + SET_FLAG(vpn_bpi->flags, BGP_PATH_VALID); + if (VALID_INTERIOR_TYPE(vpn_bpi->type)) + RFAPI_MONITOR_EXTERIOR(vpn_rn) + ->valid_interior_count++; + /* signal interior route withdrawal to import-exterior + */ + vnc_import_bgp_exterior_add_route_interior( + bgp_get_default(), import_table, vpn_rn, + vpn_bpi); + } + } + return 0; +} + +static void rfapiWithdrawTimerEncap(struct event *t) +{ + struct rfapi_withdraw *wcb = EVENT_ARG(t); + struct bgp_path_info *bpi = wcb->info; + int was_first_route = 0; + struct rfapi_monitor_encap *em; + struct skiplist *vpn_node_sl = skiplist_new(0, NULL, NULL); + + assert(wcb->node); + assert(bpi); + assert(wcb->import_table); + + RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_ENCAP, 0); + + if (wcb->node->info == bpi) + was_first_route = 1; + + /* + * Remove the route/bpi and free it + */ + rfapiBgpInfoDetach(wcb->node, bpi); + rfapiBgpInfoFree(bpi); + + if (!was_first_route) + goto done; + + for (em = RFAPI_MONITOR_ENCAP(wcb->node); em; em = em->next) { + + /* + * Update monitoring VPN BPIs with new encap info at the + * head of the encap bpi chain (which could be NULL after + * removing the expiring bpi above) + */ + if (rfapiWithdrawEncapUpdateCachedUn(wcb->import_table, + wcb->node->info, em->node, + em->bpi)) + continue; + + /* + * Build a list of unique VPN nodes referenced by these + * monitors. + * Use a skiplist for speed. + */ + skiplist_insert(vpn_node_sl, em->node, em->node); + } + + + /* + * for each VPN node referenced in the ENCAP monitors: + */ + struct agg_node *rn; + while (!skiplist_first(vpn_node_sl, (void **)&rn, NULL)) { + if (!wcb->node->info) { + struct rfapi_monitor_vpn *moved; + + moved = rfapiMonitorMoveShorter(rn, 0); + if (moved) { + // rfapiDoRouteCallback(wcb->import_table, + // moved->node, moved); + rfapiMonitorMovedUp(wcb->import_table, rn, + moved->node, moved); + } + } else { + // rfapiDoRouteCallback(wcb->import_table, rn, NULL); + rfapiMonitorItNodeChanged(wcb->import_table, rn, NULL); + } + skiplist_delete_first(vpn_node_sl); + } + +done: + RFAPI_CHECK_REFCOUNT(wcb->node, SAFI_ENCAP, 1); + agg_unlock_node(wcb->node); /* decr ref count */ + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + skiplist_free(vpn_node_sl); +} + + +/* + * Works for both VPN and ENCAP routes; timer_service_func is different + * in each case + */ +static void +rfapiBiStartWithdrawTimer(struct rfapi_import_table *import_table, + struct agg_node *rn, struct bgp_path_info *bpi, + afi_t afi, safi_t safi, + void (*timer_service_func)(struct event *)) +{ + uint32_t lifetime; + struct rfapi_withdraw *wcb; + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + /* + * Already on the path to being withdrawn, + * should already have a timer set up to + * delete it. + */ + vnc_zlog_debug_verbose( + "%s: already being withdrawn, do nothing", __func__); + return; + } + + rfapiGetVncLifetime(bpi->attr, &lifetime); + vnc_zlog_debug_verbose("%s: VNC lifetime is %u", __func__, lifetime); + + /* + * withdrawn routes get to hang around for a while + */ + SET_FLAG(bpi->flags, BGP_PATH_REMOVED); + + /* set timer to remove the route later */ + lifetime = rfapiGetHolddownFromLifetime(lifetime); + vnc_zlog_debug_verbose("%s: using timeout %u", __func__, lifetime); + + /* + * Stash import_table, node, and info for use by timer + * service routine, which is supposed to free the wcb. + */ + wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); + wcb->node = rn; + wcb->info = bpi; + wcb->import_table = import_table; + bgp_attr_intern(bpi->attr); + + if (VNC_DEBUG(VERBOSE)) { + vnc_zlog_debug_verbose( + "%s: wcb values: node=%p, info=%p, import_table=%p (bpi follows)", + __func__, wcb->node, wcb->info, wcb->import_table); + rfapiPrintBi(NULL, bpi); + } + + + assert(bpi->extra); + if (lifetime > UINT32_MAX / 1001) { + /* sub-optimal case, but will probably never happen */ + bpi->extra->vnc->vnc.import.timer = NULL; + event_add_timer(bm->master, timer_service_func, wcb, lifetime, + &bpi->extra->vnc->vnc.import.timer); + } else { + static uint32_t jitter; + uint32_t lifetime_msec; + + /* + * the goal here is to spread out the timers so they are + * sortable in the skip list + */ + if (++jitter >= 1000) + jitter = 0; + + lifetime_msec = (lifetime * 1000) + jitter; + + bpi->extra->vnc->vnc.import.timer = NULL; + event_add_timer_msec(bm->master, timer_service_func, wcb, + lifetime_msec, + &bpi->extra->vnc->vnc.import.timer); + } + + /* re-sort route list (BGP_PATH_REMOVED routes are last) */ + if (((struct bgp_path_info *)rn->info)->next) { + rfapiBgpInfoDetach(rn, bpi); + rfapiBgpInfoAttachSorted(rn, bpi, afi, safi); + } +} + + +typedef void(rfapi_bi_filtered_import_f)(struct rfapi_import_table *table, + int action, struct peer *peer, + void *rfd, const struct prefix *prefix, + const struct prefix *aux_prefix, + afi_t afi, struct prefix_rd *prd, + struct attr *attr, uint8_t type, + uint8_t sub_type, uint32_t *label); + + +static void rfapiExpireEncapNow(struct rfapi_import_table *it, + struct agg_node *rn, struct bgp_path_info *bpi) +{ + struct rfapi_withdraw *wcb; + struct event t; + + /* + * pretend we're an expiring timer + */ + wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); + wcb->info = bpi; + wcb->node = rn; + wcb->import_table = it; + memset(&t, 0, sizeof(t)); + t.arg = wcb; + rfapiWithdrawTimerEncap(&t); /* frees wcb */ +} + +static int rfapiGetNexthop(struct attr *attr, struct prefix *prefix) +{ + switch (BGP_MP_NEXTHOP_FAMILY(attr->mp_nexthop_len)) { + case AF_INET: + prefix->family = AF_INET; + prefix->prefixlen = IPV4_MAX_BITLEN; + prefix->u.prefix4 = attr->mp_nexthop_global_in; + break; + case AF_INET6: + prefix->family = AF_INET6; + prefix->prefixlen = IPV6_MAX_BITLEN; + prefix->u.prefix6 = attr->mp_nexthop_global; + break; + default: + vnc_zlog_debug_verbose("%s: unknown attr->mp_nexthop_len %d", + __func__, attr->mp_nexthop_len); + return EINVAL; + } + return 0; +} + +/* + * import a bgp_path_info if its route target list intersects with the + * import table's route target list + */ +static void rfapiBgpInfoFilteredImportEncap( + struct rfapi_import_table *import_table, int action, struct peer *peer, + void *rfd, /* set for looped back routes */ + const struct prefix *p, + const struct prefix *aux_prefix, /* Unused for encap routes */ + afi_t afi, struct prefix_rd *prd, + struct attr *attr, /* part of bgp_path_info */ + uint8_t type, /* part of bgp_path_info */ + uint8_t sub_type, /* part of bgp_path_info */ + uint32_t *label) /* part of bgp_path_info */ +{ + struct agg_table *rt = NULL; + struct agg_node *rn; + struct bgp_path_info *info_new; + struct bgp_path_info *bpi; + struct bgp_path_info *next; + char buf[BUFSIZ]; + + struct prefix p_firstbpi_old; + struct prefix p_firstbpi_new; + int replacing = 0; + const char *action_str = NULL; + struct prefix un_prefix; + + struct bgp *bgp; + bgp = bgp_get_default(); /* assume 1 instance for now */ + + switch (action) { + case FIF_ACTION_UPDATE: + action_str = "update"; + break; + case FIF_ACTION_WITHDRAW: + action_str = "withdraw"; + break; + case FIF_ACTION_KILL: + action_str = "kill"; + break; + default: + assert(0); + break; + } + + vnc_zlog_debug_verbose( + "%s: entry: %s: prefix %s/%d", __func__, action_str, + inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf)), + p->prefixlen); + + memset(&p_firstbpi_old, 0, sizeof(p_firstbpi_old)); + memset(&p_firstbpi_new, 0, sizeof(p_firstbpi_new)); + + if (action == FIF_ACTION_UPDATE) { + /* + * Compare rt lists. If no intersection, don't import this route + * On a withdraw, peer and RD are sufficient to determine if + * we should act. + */ + if (!attr || !bgp_attr_get_ecommunity(attr)) { + + vnc_zlog_debug_verbose( + "%s: attr, extra, or ecommunity missing, not importing", + __func__); + return; + } +#ifdef RFAPI_REQUIRE_ENCAP_BEEC + if (!rfapiEcommunitiesMatchBeec( + bgp_attr_get_ecommunity(attr))) { + vnc_zlog_debug_verbose( + "%s: it=%p: no match for BGP Encapsulation ecommunity", + __func__, import_table); + return; + } +#endif + if (!rfapiEcommunitiesIntersect( + import_table->rt_import_list, + bgp_attr_get_ecommunity(attr))) { + + vnc_zlog_debug_verbose( + "%s: it=%p: no ecommunity intersection", + __func__, import_table); + return; + } + + /* + * Updates must also have a nexthop address + */ + memset(&un_prefix, 0, + sizeof(un_prefix)); /* keep valgrind happy */ + if (rfapiGetNexthop(attr, &un_prefix)) { + vnc_zlog_debug_verbose("%s: missing nexthop address", + __func__); + return; + } + } + + /* + * Figure out which radix tree the route would go into + */ + switch (afi) { + case AFI_IP: + case AFI_IP6: + rt = import_table->imported_encap[afi]; + break; + + case AFI_UNSPEC: + case AFI_L2VPN: + case AFI_MAX: + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); + return; + } + + /* + * agg_node_lookup returns a node only if there is at least + * one route attached. + */ + rn = agg_node_lookup(rt, p); + +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose("%s: initial encap lookup(it=%p) rn=%p", + __func__, import_table, rn); +#endif + + if (rn) { + + RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 1); + agg_unlock_node(rn); /* undo lock in agg_node_lookup */ + + + /* + * capture nexthop of first bpi + */ + if (rn->info) { + rfapiNexthop2Prefix( + ((struct bgp_path_info *)(rn->info))->attr, + &p_firstbpi_old); + } + + for (bpi = rn->info; bpi; bpi = bpi->next) { + + /* + * Does this bgp_path_info refer to the same route + * as we are trying to add? + */ + vnc_zlog_debug_verbose("%s: comparing BPI %p", __func__, + bpi); + + + /* + * Compare RDs + * + * RD of import table bpi is in + * bpi->extra->vnc->vnc.import.rd RD of info_orig is in prd + */ + if (!bpi->extra) { + vnc_zlog_debug_verbose("%s: no bpi->extra", + __func__); + continue; + } + if (prefix_cmp((struct prefix *)&bpi->extra->vnc->vnc + .import.rd, + (struct prefix *)prd)) { + vnc_zlog_debug_verbose("%s: prd does not match", + __func__); + continue; + } + + /* + * Compare peers + */ + if (bpi->peer != peer) { + vnc_zlog_debug_verbose( + "%s: peer does not match", __func__); + continue; + } + + vnc_zlog_debug_verbose("%s: found matching bpi", + __func__); + + /* Same route. Delete this bpi, replace with new one */ + + if (action == FIF_ACTION_WITHDRAW) { + + vnc_zlog_debug_verbose( + "%s: withdrawing at prefix %pRN", + __func__, rn); + + rfapiBiStartWithdrawTimer( + import_table, rn, bpi, afi, SAFI_ENCAP, + rfapiWithdrawTimerEncap); + + } else { + vnc_zlog_debug_verbose( + "%s: %s at prefix %pRN", __func__, + ((action == FIF_ACTION_KILL) + ? "killing" + : "replacing"), + rn); + + /* + * If this route is waiting to be deleted + * because of + * a previous withdraw, we must cancel its + * timer. + */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && + bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = EVENT_ARG( + bpi->extra->vnc->vnc.import.timer); + + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + EVENT_OFF(bpi->extra->vnc->vnc.import + .timer); + } + + if (action == FIF_ACTION_UPDATE) { + rfapiBgpInfoDetach(rn, bpi); + rfapiBgpInfoFree(bpi); + replacing = 1; + } else { + /* + * Kill: do export stuff when removing + * bpi + */ + struct rfapi_withdraw *wcb; + struct event t; + + /* + * pretend we're an expiring timer + */ + wcb = XCALLOC( + MTYPE_RFAPI_WITHDRAW, + sizeof(struct rfapi_withdraw)); + wcb->info = bpi; + wcb->node = rn; + wcb->import_table = import_table; + memset(&t, 0, sizeof(t)); + t.arg = wcb; + rfapiWithdrawTimerEncap( + &t); /* frees wcb */ + } + } + + break; + } + } + + if (rn) + RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, replacing ? 1 : 0); + + if (action == FIF_ACTION_WITHDRAW || action == FIF_ACTION_KILL) + return; + + info_new = + rfapiBgpInfoCreate(attr, peer, rfd, prd, type, sub_type, NULL); + + if (rn) { + if (!replacing) + agg_lock_node(rn); /* incr ref count for new BPI */ + } else { + rn = agg_node_get(rt, p); + } + + vnc_zlog_debug_verbose("%s: (afi=%d, rn=%p) inserting at prefix %pRN", + __func__, afi, rn, rn); + + rfapiBgpInfoAttachSorted(rn, info_new, afi, SAFI_ENCAP); + + /* + * Delete holddown routes from same NVE. See details in + * rfapiBgpInfoFilteredImportVPN() + */ + for (bpi = info_new->next; bpi; bpi = next) { + + struct prefix pfx_un; + int un_match = 0; + + next = bpi->next; + if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + /* + * We already match the VN address (it is the prefix + * of the route node) + */ + + if (!rfapiGetNexthop(bpi->attr, &pfx_un) + && prefix_same(&pfx_un, &un_prefix)) { + + un_match = 1; + } + + if (!un_match) + continue; + + vnc_zlog_debug_verbose( + "%s: removing holddown bpi matching NVE of new route", + __func__); + if (bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = + EVENT_ARG(bpi->extra->vnc->vnc.import.timer); + + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + EVENT_OFF(bpi->extra->vnc->vnc.import.timer); + } + rfapiExpireEncapNow(import_table, rn, bpi); + } + + rfapiNexthop2Prefix(((struct bgp_path_info *)(rn->info))->attr, + &p_firstbpi_new); + + /* + * If the nexthop address of the selected Encap route (i.e., + * the UN address) has changed, then we must update the VPN + * routes that refer to this Encap route and possibly force + * rfapi callbacks. + */ + if (rfapiAttrNexthopAddrDifferent(&p_firstbpi_old, &p_firstbpi_new)) { + + struct rfapi_monitor_encap *m; + struct rfapi_monitor_encap *mnext; + + struct agg_node *referenced_vpn_prefix; + + /* + * Optimized approach: build radix tree on the fly to + * hold list of VPN nodes referenced by the ENCAP monitors + * + * The nodes in this table correspond to prefixes of VPN routes. + * The "info" pointer of the node points to a chain of + * struct rfapi_monitor_encap, each of which refers to a + * specific VPN node. + */ + struct agg_table *referenced_vpn_table; + + referenced_vpn_table = agg_table_init(); + +/* + * iterate over the set of monitors at this ENCAP node. + */ +#ifdef DEBUG_ENCAP_MONITOR + vnc_zlog_debug_verbose("%s: examining monitors at rn=%p", + __func__, rn); +#endif + for (m = RFAPI_MONITOR_ENCAP(rn); m; m = m->next) { + const struct prefix *p; + + /* + * For each referenced bpi/route, copy the ENCAP route's + * nexthop to the VPN route's cached UN address field + * and set + * the address family of the cached UN address field. + */ + rfapiCopyUnEncap2VPN(info_new, m->bpi); + if (!CHECK_FLAG(m->bpi->flags, BGP_PATH_VALID)) { + SET_FLAG(m->bpi->flags, BGP_PATH_VALID); + if (VALID_INTERIOR_TYPE(m->bpi->type)) + RFAPI_MONITOR_EXTERIOR(m->node) + ->valid_interior_count++; + vnc_import_bgp_exterior_add_route_interior( + bgp, import_table, m->node, m->bpi); + } + + /* + * Build a list of unique VPN nodes referenced by these + * monitors + * + * There could be more than one VPN node here with a + * given + * prefix. Those are currently in an unsorted linear + * list + * per prefix. + */ + p = agg_node_get_prefix(m->node); + referenced_vpn_prefix = + agg_node_get(referenced_vpn_table, p); + assert(referenced_vpn_prefix); + for (mnext = referenced_vpn_prefix->info; mnext; + mnext = mnext->next) { + + if (mnext->node == m->node) + break; + } + + if (mnext) { + /* + * already have an entry for this VPN node + */ + agg_unlock_node(referenced_vpn_prefix); + } else { + mnext = XCALLOC( + MTYPE_RFAPI_MONITOR_ENCAP, + sizeof(struct rfapi_monitor_encap)); + mnext->node = m->node; + mnext->next = referenced_vpn_prefix->info; + referenced_vpn_prefix->info = mnext; + } + } + + /* + * for each VPN node referenced in the ENCAP monitors: + */ + for (referenced_vpn_prefix = + agg_route_top(referenced_vpn_table); + referenced_vpn_prefix; + referenced_vpn_prefix = + agg_route_next(referenced_vpn_prefix)) { + + while ((m = referenced_vpn_prefix->info)) { + + struct agg_node *n; + + rfapiMonitorMoveLonger(m->node); + for (n = m->node; n; n = agg_node_parent(n)) { + // rfapiDoRouteCallback(import_table, n, + // NULL); + } + rfapiMonitorItNodeChanged(import_table, m->node, + NULL); + + referenced_vpn_prefix->info = m->next; + agg_unlock_node(referenced_vpn_prefix); + XFREE(MTYPE_RFAPI_MONITOR_ENCAP, m); + } + } + agg_table_finish(referenced_vpn_table); + } + + RFAPI_CHECK_REFCOUNT(rn, SAFI_ENCAP, 0); +} + +static void rfapiExpireVpnNow(struct rfapi_import_table *it, + struct agg_node *rn, struct bgp_path_info *bpi, + int lockoffset) +{ + struct rfapi_withdraw *wcb; + struct event t; + + /* + * pretend we're an expiring timer + */ + wcb = XCALLOC(MTYPE_RFAPI_WITHDRAW, sizeof(struct rfapi_withdraw)); + wcb->info = bpi; + wcb->node = rn; + wcb->import_table = it; + wcb->lockoffset = lockoffset; + memset(&t, 0, sizeof(t)); + t.arg = wcb; + rfapiWithdrawTimerVPN(&t); /* frees wcb */ +} + + +/* + * import a bgp_path_info if its route target list intersects with the + * import table's route target list + */ +void rfapiBgpInfoFilteredImportVPN( + struct rfapi_import_table *import_table, int action, struct peer *peer, + void *rfd, /* set for looped back routes */ + const struct prefix *p, + const struct prefix *aux_prefix, /* AFI_L2VPN: optional IP */ + afi_t afi, struct prefix_rd *prd, + struct attr *attr, /* part of bgp_path_info */ + uint8_t type, /* part of bgp_path_info */ + uint8_t sub_type, /* part of bgp_path_info */ + uint32_t *label) /* part of bgp_path_info */ +{ + struct agg_table *rt = NULL; + struct agg_node *rn; + struct agg_node *n; + struct bgp_path_info *info_new; + struct bgp_path_info *bpi; + struct bgp_path_info *next; + char buf[BUFSIZ]; + struct prefix vn_prefix; + struct prefix un_prefix; + int un_prefix_valid = 0; + struct agg_node *ern; + int replacing = 0; + int original_had_routes = 0; + struct prefix original_nexthop; + const char *action_str = NULL; + int is_it_ce = 0; + + struct bgp *bgp; + bgp = bgp_get_default(); /* assume 1 instance for now */ + + switch (action) { + case FIF_ACTION_UPDATE: + action_str = "update"; + break; + case FIF_ACTION_WITHDRAW: + action_str = "withdraw"; + break; + case FIF_ACTION_KILL: + action_str = "kill"; + break; + default: + assert(0); + break; + } + + if (import_table == bgp->rfapi->it_ce) + is_it_ce = 1; + + vnc_zlog_debug_verbose("%s: entry: %s%s: prefix %s/%d: it %p, afi %s", + __func__, (is_it_ce ? "CE-IT " : ""), action_str, + rfapi_ntop(p->family, &p->u.prefix, buf, BUFSIZ), + p->prefixlen, import_table, afi2str(afi)); + + VNC_ITRCCK; + + /* + * Compare rt lists. If no intersection, don't import this route + * On a withdraw, peer and RD are sufficient to determine if + * we should act. + */ + if (action == FIF_ACTION_UPDATE) { + if (!attr || !bgp_attr_get_ecommunity(attr)) { + + vnc_zlog_debug_verbose( + "%s: attr, extra, or ecommunity missing, not importing", + __func__); + return; + } + if ((import_table != bgp->rfapi->it_ce) && + !rfapiEcommunitiesIntersect( + import_table->rt_import_list, + bgp_attr_get_ecommunity(attr))) { + + vnc_zlog_debug_verbose( + "%s: it=%p: no ecommunity intersection", + __func__, import_table); + return; + } + + memset(&vn_prefix, 0, + sizeof(vn_prefix)); /* keep valgrind happy */ + if (rfapiGetNexthop(attr, &vn_prefix)) { + /* missing nexthop address would be a bad, bad thing */ + vnc_zlog_debug_verbose("%s: missing nexthop", __func__); + return; + } + } + + /* + * Figure out which radix tree the route would go into + */ + switch (afi) { + case AFI_IP: + case AFI_IP6: + case AFI_L2VPN: + rt = import_table->imported_vpn[afi]; + break; + + case AFI_UNSPEC: + case AFI_MAX: + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); + return; + } + + /* clear it */ + memset(&original_nexthop, 0, sizeof(original_nexthop)); + + /* + * agg_node_lookup returns a node only if there is at least + * one route attached. + */ + rn = agg_node_lookup(rt, p); + + vnc_zlog_debug_verbose("%s: rn=%p", __func__, rn); + + if (rn) { + + RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 1); + agg_unlock_node(rn); /* undo lock in agg_node_lookup */ + + if (rn->info) + original_had_routes = 1; + + if (VNC_DEBUG(VERBOSE)) { + vnc_zlog_debug_verbose("%s: showing IT node on entry", + __func__); + rfapiShowItNode(NULL, rn); /* debug */ + } + + /* + * Look for same route (will have same RD and peer) + */ + bpi = rfapiItBiIndexSearch(rn, prd, peer, aux_prefix); + + if (bpi) { + + /* + * This was an old test when we iterated over the + * BPIs linearly. Since we're now looking up with + * RD and peer, comparing types should not be + * needed. Changed to assertion. + * + * Compare types. Doing so prevents a RFP-originated + * route from matching an imported route, for example. + */ + if (VNC_DEBUG(VERBOSE) && bpi->type != type) + /* should be handled by RDs, but warn for now */ + zlog_warn("%s: type mismatch! (bpi=%d, arg=%d)", + __func__, bpi->type, type); + + vnc_zlog_debug_verbose("%s: found matching bpi", + __func__); + + /* + * In the special CE table, withdrawals occur without + * holddown + */ + if (import_table == bgp->rfapi->it_ce) { + vnc_direct_bgp_del_route_ce(bgp, rn, bpi); + if (action == FIF_ACTION_WITHDRAW) + action = FIF_ACTION_KILL; + } + + if (action == FIF_ACTION_WITHDRAW) { + + int washolddown = CHECK_FLAG(bpi->flags, + BGP_PATH_REMOVED); + + vnc_zlog_debug_verbose( + "%s: withdrawing at prefix %pRN%s", + __func__, rn, + (washolddown + ? " (already being withdrawn)" + : "")); + + VNC_ITRCCK; + if (!washolddown) { + rfapiBiStartWithdrawTimer( + import_table, rn, bpi, afi, + SAFI_MPLS_VPN, + rfapiWithdrawTimerVPN); + + RFAPI_UPDATE_ITABLE_COUNT( + bpi, import_table, afi, -1); + import_table->holddown_count[afi] += 1; + } + VNC_ITRCCK; + } else { + vnc_zlog_debug_verbose( + "%s: %s at prefix %pRN", __func__, + ((action == FIF_ACTION_KILL) + ? "killing" + : "replacing"), + rn); + + /* + * If this route is waiting to be deleted + * because of + * a previous withdraw, we must cancel its + * timer. + */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && + bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = EVENT_ARG( + bpi->extra->vnc->vnc.import.timer); + + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + EVENT_OFF(bpi->extra->vnc->vnc.import + .timer); + + import_table->holddown_count[afi] -= 1; + RFAPI_UPDATE_ITABLE_COUNT( + bpi, import_table, afi, 1); + } + /* + * decrement remote count (if route is remote) + * because + * we are going to remove it below + */ + RFAPI_UPDATE_ITABLE_COUNT(bpi, import_table, + afi, -1); + if (action == FIF_ACTION_UPDATE) { + replacing = 1; + + /* + * make copy of original nexthop so we + * can see if it changed + */ + rfapiGetNexthop(bpi->attr, + &original_nexthop); + + /* + * remove bpi without doing any export + * processing + */ + if (CHECK_FLAG(bpi->flags, + BGP_PATH_VALID) + && VALID_INTERIOR_TYPE(bpi->type)) + RFAPI_MONITOR_EXTERIOR(rn) + ->valid_interior_count--; + rfapiItBiIndexDel(rn, bpi); + rfapiBgpInfoDetach(rn, bpi); + rfapiMonitorEncapDelete(bpi); + vnc_import_bgp_exterior_del_route_interior( + bgp, import_table, rn, bpi); + rfapiBgpInfoFree(bpi); + } else { + /* Kill */ + /* + * remove bpi and do export processing + */ + import_table->holddown_count[afi] += 1; + rfapiExpireVpnNow(import_table, rn, bpi, + 0); + } + } + } + } + + if (rn) + RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, replacing ? 1 : 0); + + if (action == FIF_ACTION_WITHDRAW || action == FIF_ACTION_KILL) { + VNC_ITRCCK; + return; + } + + info_new = + rfapiBgpInfoCreate(attr, peer, rfd, prd, type, sub_type, label); + + /* + * lookup un address in encap table + */ + ern = agg_node_match(import_table->imported_encap[afi], &vn_prefix); + if (ern) { + rfapiCopyUnEncap2VPN(ern->info, info_new); + agg_unlock_node(ern); /* undo lock in route_note_match */ + } else { + /* Not a big deal, just means VPN route got here first */ + vnc_zlog_debug_verbose("%s: no encap route for vn addr %pFX", + __func__, &vn_prefix); + info_new->extra->vnc->vnc.import.un_family = AF_UNSPEC; + } + + if (rn) { + if (!replacing) + agg_lock_node(rn); + } else { + /* + * No need to increment reference count, so only "get" + * if the node is not there already + */ + rn = agg_node_get(rt, p); + } + + /* + * For ethernet routes, if there is an accompanying IP address, + * save it in the bpi + */ + if ((AFI_L2VPN == afi) && aux_prefix) { + + vnc_zlog_debug_verbose("%s: setting BPI's aux_prefix", + __func__); + info_new->extra->vnc->vnc.import.aux_prefix = *aux_prefix; + } + + vnc_zlog_debug_verbose("%s: inserting bpi %p at prefix %pRN #%d", + __func__, info_new, rn, + agg_node_get_lock_count(rn)); + + rfapiBgpInfoAttachSorted(rn, info_new, afi, SAFI_MPLS_VPN); + rfapiItBiIndexAdd(rn, info_new); + if (!rfapiGetUnAddrOfVpnBi(info_new, NULL)) { + if (VALID_INTERIOR_TYPE(info_new->type)) + RFAPI_MONITOR_EXTERIOR(rn)->valid_interior_count++; + SET_FLAG(info_new->flags, BGP_PATH_VALID); + } + RFAPI_UPDATE_ITABLE_COUNT(info_new, import_table, afi, 1); + vnc_import_bgp_exterior_add_route_interior(bgp, import_table, rn, + info_new); + + if (import_table == bgp->rfapi->it_ce) + vnc_direct_bgp_add_route_ce(bgp, rn, info_new); + + if (VNC_DEBUG(VERBOSE)) { + vnc_zlog_debug_verbose("%s: showing IT node", __func__); + rfapiShowItNode(NULL, rn); /* debug */ + } + + rfapiMonitorEncapAdd(import_table, &vn_prefix, rn, info_new); + + if (!rfapiGetUnAddrOfVpnBi(info_new, &un_prefix)) { + + /* + * if we have a valid UN address (either via Encap route + * or via tunnel attribute), then we should attempt + * to move any monitors at less-specific nodes to this node + */ + rfapiMonitorMoveLonger(rn); + + un_prefix_valid = 1; + } + + /* + * 101129 Enhancement: if we add a route (implication: it is not + * in holddown), delete all other routes from this nve at this + * node that are in holddown, regardless of peer. + * + * Reasons it's OK to do that: + * + * - if the holddown route being deleted originally came from BGP VPN, + * it is already gone from BGP (implication of holddown), so there + * won't be any added inconsistency with the BGP RIB. + * + * - once a fresh route is added at a prefix, any routes in holddown + * at that prefix will not show up in RFP responses, so deleting + * the holddown routes won't affect the contents of responses. + * + * - lifetimes are supposed to be consistent, so there should not + * be a case where the fresh route has a shorter lifetime than + * the holddown route, so we don't expect the fresh route to + * disappear and complete its holddown time before the existing + * holddown routes time out. Therefore, we won't have a situation + * where we expect the existing holddown routes to be hidden and + * then to reappear sometime later (as holddown routes) in a + * RFP response. + * + * Among other things, this would enable us to skirt the problem + * of local holddown routes that refer to NVE descriptors that + * have already been closed (if the same NVE triggers a subsequent + * rfapi_open(), the new peer is different and doesn't match the + * peer of the holddown route, so the stale holddown route still + * hangs around until it times out instead of just being replaced + * by the fresh route). + */ + /* + * We know that the new bpi will have been inserted before any routes + * in holddown, so we can skip any that came before it + */ + for (bpi = info_new->next; bpi; bpi = next) { + + struct prefix pfx_vn; + struct prefix pfx_un; + int un_match = 0; + int remote_peer_match = 0; + + next = bpi->next; + + /* + * Must be holddown + */ + if (!CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + /* + * Must match VN address (nexthop of VPN route) + */ + if (rfapiGetNexthop(bpi->attr, &pfx_vn)) + continue; + if (!prefix_same(&pfx_vn, &vn_prefix)) + continue; + + if (un_prefix_valid && /* new route UN addr */ + !rfapiGetUnAddrOfVpnBi(bpi, &pfx_un) + && /* old route UN addr */ + prefix_same(&pfx_un, &un_prefix)) { /* compare */ + un_match = 1; + } + if (!RFAPI_LOCAL_BI(bpi) && !RFAPI_LOCAL_BI(info_new) && + sockunion_same(&bpi->peer->connection->su, + &info_new->peer->connection->su)) { + /* old & new are both remote, same peer */ + remote_peer_match = 1; + } + + if (!un_match && !remote_peer_match) + continue; + + vnc_zlog_debug_verbose( + "%s: removing holddown bpi matching NVE of new route", + __func__); + if (bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = + EVENT_ARG(bpi->extra->vnc->vnc.import.timer); + + XFREE(MTYPE_RFAPI_WITHDRAW, wcb); + EVENT_OFF(bpi->extra->vnc->vnc.import.timer); + } + rfapiExpireVpnNow(import_table, rn, bpi, 0); + } + + if (!original_had_routes) { + /* + * We went from 0 usable routes to 1 usable route. Perform the + * "Adding a Route" export process. + */ + vnc_direct_bgp_add_prefix(bgp, import_table, rn); + vnc_zebra_add_prefix(bgp, import_table, rn); + } else { + /* + * Check for nexthop change event + * Note: the prefix_same() test below detects two situations: + * 1. route is replaced, new route has different nexthop + * 2. new route is added (original_nexthop is 0) + */ + struct prefix new_nexthop; + + rfapiGetNexthop(attr, &new_nexthop); + if (!prefix_same(&original_nexthop, &new_nexthop)) { + /* + * nexthop change event + * vnc_direct_bgp_add_prefix() will recompute VN addr + * ecommunity + */ + vnc_direct_bgp_add_prefix(bgp, import_table, rn); + } + } + + if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + for (n = rn; n; n = agg_node_parent(n)) { + // rfapiDoRouteCallback(import_table, n, NULL); + } + rfapiMonitorItNodeChanged(import_table, rn, NULL); + } + RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 0); + VNC_ITRCCK; +} + +static void rfapiBgpInfoFilteredImportBadSafi( + struct rfapi_import_table *import_table, int action, struct peer *peer, + void *rfd, /* set for looped back routes */ + const struct prefix *p, + const struct prefix *aux_prefix, /* AFI_L2VPN: optional IP */ + afi_t afi, struct prefix_rd *prd, + struct attr *attr, /* part of bgp_path_info */ + uint8_t type, /* part of bgp_path_info */ + uint8_t sub_type, /* part of bgp_path_info */ + uint32_t *label) /* part of bgp_path_info */ +{ + vnc_zlog_debug_verbose("%s: Error, bad safi", __func__); +} + +static rfapi_bi_filtered_import_f * +rfapiBgpInfoFilteredImportFunction(safi_t safi) +{ + switch (safi) { + case SAFI_MPLS_VPN: + return rfapiBgpInfoFilteredImportVPN; + + case SAFI_ENCAP: + return rfapiBgpInfoFilteredImportEncap; + + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_EVPN: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + /* not expected */ + flog_err(EC_LIB_DEVELOPMENT, "%s: bad safi %d", __func__, safi); + return rfapiBgpInfoFilteredImportBadSafi; + } + + assert(!"Reached end of function when we were not expecting to"); +} + +void rfapiProcessUpdate(struct peer *peer, + void *rfd, /* set when looped from RFP/RFAPI */ + const struct prefix *p, struct prefix_rd *prd, + struct attr *attr, afi_t afi, safi_t safi, uint8_t type, + uint8_t sub_type, uint32_t *label) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + int has_ip_route = 1; + uint32_t lni = 0; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + assert(bgp); + + h = bgp->rfapi; + assert(h); + + /* + * look at high-order byte of RD. FF means MAC + * address is present (VNC L2VPN) + */ + if ((safi == SAFI_MPLS_VPN) + && (decode_rd_type(prd->val) == RD_TYPE_VNC_ETH)) { + struct prefix pfx_mac_buf; + struct prefix pfx_nexthop_buf; + int rc; + + /* + * Set flag if prefix and nexthop are the same - don't + * add the route to normal IP-based import tables + */ + if (!rfapiGetNexthop(attr, &pfx_nexthop_buf)) { + if (!prefix_cmp(&pfx_nexthop_buf, p)) { + has_ip_route = 0; + } + } + + memset(&pfx_mac_buf, 0, sizeof(pfx_mac_buf)); + pfx_mac_buf.family = AF_ETHERNET; + pfx_mac_buf.prefixlen = 48; + memcpy(&pfx_mac_buf.u.prefix_eth.octet, prd->val + 2, 6); + + /* + * Find rt containing LNI (Logical Network ID), which + * _should_ always be present when mac address is present + */ + rc = rfapiEcommunityGetLNI(bgp_attr_get_ecommunity(attr), &lni); + + vnc_zlog_debug_verbose( + "%s: rfapiEcommunityGetLNI returned %d, lni=%d, attr=%p", + __func__, rc, lni, attr); + if (!rc) { + it = rfapiMacImportTableGet(bgp, lni); + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, peer, rfd, + &pfx_mac_buf, /* prefix */ + p, /* aux prefix: IP addr */ + AFI_L2VPN, prd, attr, type, sub_type, label); + } + } + + if (!has_ip_route) + return; + + /* + * Iterate over all import tables; do a filtered import + * for the afi/safi combination + */ + for (it = h->imports; it; it = it->next) { + (*rfapiBgpInfoFilteredImportFunction(safi))( + it, FIF_ACTION_UPDATE, peer, rfd, p, /* prefix */ + NULL, afi, prd, attr, type, sub_type, label); + } + + if (safi == SAFI_MPLS_VPN) { + vnc_direct_bgp_rh_add_route(bgp, afi, p, peer, attr); + rfapiBgpInfoFilteredImportVPN( + bgp->rfapi->it_ce, FIF_ACTION_UPDATE, peer, rfd, + p, /* prefix */ + NULL, afi, prd, attr, type, sub_type, label); + } +} + + +void rfapiProcessWithdraw(struct peer *peer, void *rfd, const struct prefix *p, + struct prefix_rd *prd, struct attr *attr, afi_t afi, + safi_t safi, uint8_t type, int kill) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + assert(bgp); + + h = bgp->rfapi; + assert(h); + + /* + * look at high-order byte of RD. FF means MAC + * address is present (VNC L2VPN) + */ + if (h->import_mac != NULL && safi == SAFI_MPLS_VPN + && decode_rd_type(prd->val) == RD_TYPE_VNC_ETH) { + struct prefix pfx_mac_buf; + void *cursor = NULL; + int rc; + + memset(&pfx_mac_buf, 0, sizeof(pfx_mac_buf)); + pfx_mac_buf.family = AF_ETHERNET; + pfx_mac_buf.prefixlen = 48; + memcpy(&pfx_mac_buf.u.prefix_eth, prd->val + 2, 6); + + /* + * withdraw does not contain attrs, so we don't have + * access to the route's LNI, which would ordinarily + * select the specific mac-based import table. Instead, + * we must iterate over all mac-based tables and rely + * on the RD to match. + * + * If this approach is too slow, add an index where + * key is {RD, peer} and value is the import table + */ + for (rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor); + rc == 0; rc = skiplist_next(h->import_mac, NULL, + (void **)&it, &cursor)) { + +#ifdef DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: calling rfapiBgpInfoFilteredImportVPN(it=%p, afi=AFI_L2VPN)", + __func__, it); +#endif + + rfapiBgpInfoFilteredImportVPN( + it, + (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), + peer, rfd, &pfx_mac_buf, /* prefix */ + p, /* aux_prefix: IP */ + AFI_L2VPN, prd, attr, type, 0, + NULL); /* sub_type & label unused for withdraw + */ + } + } + + /* + * XXX For the case where the withdraw involves an L2 + * route with no IP information, we rely on the lack + * of RT-list intersection to filter out the withdraw + * from the IP-based import tables below + */ + + /* + * Iterate over all import tables; do a filtered import + * for the afi/safi combination + */ + + for (it = h->imports; it; it = it->next) { + (*rfapiBgpInfoFilteredImportFunction(safi))( + it, (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), + peer, rfd, p, /* prefix */ + NULL, afi, prd, attr, type, 0, + NULL); /* sub_type & label unused for withdraw */ + } + + /* TBD the deletion should happen after the lifetime expires */ + if (safi == SAFI_MPLS_VPN) + vnc_direct_bgp_rh_del_route(bgp, afi, p, peer); + + if (safi == SAFI_MPLS_VPN) { + rfapiBgpInfoFilteredImportVPN( + bgp->rfapi->it_ce, + (kill ? FIF_ACTION_KILL : FIF_ACTION_WITHDRAW), peer, + rfd, p, /* prefix */ + NULL, afi, prd, attr, type, 0, + NULL); /* sub_type & label unused for withdraw */ + } +} + +/* + * TBD optimized withdraw timer algorithm for case of many + * routes expiring at the same time due to peer drop. + */ +/* + * 1. Visit all BPIs in all ENCAP import tables. + * + * a. If a bpi's peer is the failed peer, remove the bpi. + * b. If the removed ENCAP bpi was first in the list of + * BPIs at this ENCAP node, loop over all monitors + * at this node: + * + * (1) for each ENCAP monitor, loop over all its + * VPN node monitors and set their RFAPI_MON_FLAG_NEEDCALLBACK + * flags. + * + * 2. Visit all BPIs in all VPN import tables. + * a. If a bpi's peer is the failed peer, remove the bpi. + * b. loop over all the VPN node monitors and set their + * RFAPI_MON_FLAG_NEEDCALLBACK flags + * c. If there are no BPIs left at this VPN node, + * + */ + + +/* surprise, this gets called from peer_delete(), from rfapi_close() */ +static void rfapiProcessPeerDownRt(struct peer *peer, + struct rfapi_import_table *import_table, + afi_t afi, safi_t safi) +{ + struct agg_node *rn; + struct bgp_path_info *bpi; + struct agg_table *rt = NULL; + void (*timer_service_func)(struct event *) = NULL; + + assert(afi == AFI_IP || afi == AFI_IP6); + + VNC_ITRCCK; + + switch (safi) { + case SAFI_MPLS_VPN: + rt = import_table->imported_vpn[afi]; + timer_service_func = rfapiWithdrawTimerVPN; + break; + case SAFI_ENCAP: + rt = import_table->imported_encap[afi]; + timer_service_func = rfapiWithdrawTimerEncap; + break; + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_EVPN: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + /* Suppress uninitialized variable warning */ + rt = NULL; + timer_service_func = NULL; + assert(0); + } + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + for (bpi = rn->info; bpi; bpi = bpi->next) { + if (bpi->peer == peer) { + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + /* already in holddown, skip */ + continue; + } + + if (safi == SAFI_MPLS_VPN) { + RFAPI_UPDATE_ITABLE_COUNT( + bpi, import_table, afi, -1); + import_table->holddown_count[afi] += 1; + } + rfapiBiStartWithdrawTimer(import_table, rn, bpi, + afi, safi, + timer_service_func); + } + } + } + VNC_ITRCCK; +} + +/* + * This gets called when a peer connection drops. We have to remove + * all the routes from this peer. + * + * Current approach is crude. TBD Optimize by setting fewer timers and + * grouping withdrawn routes so we can generate callbacks more + * efficiently. + */ +void rfapiProcessPeerDown(struct peer *peer) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + + /* + * If this peer is a "dummy" peer structure atached to a RFAPI + * nve_descriptor, we don't need to walk the import tables + * because the routes are already withdrawn by rfapi_close() + */ + if (CHECK_FLAG(peer->flags, PEER_FLAG_IS_RFAPI_HD)) + return; + + /* + * 1. Visit all BPIs in all ENCAP import tables. + * Start withdraw timer on the BPIs that match peer. + * + * 2. Visit All BPIs in all VPN import tables. + * Start withdraw timer on the BPIs that match peer. + */ + + bgp = bgp_get_default(); /* assume 1 instance for now */ + if (!bgp) + return; + + h = bgp->rfapi; + assert(h); + + for (it = h->imports; it; it = it->next) { + rfapiProcessPeerDownRt(peer, it, AFI_IP, SAFI_ENCAP); + rfapiProcessPeerDownRt(peer, it, AFI_IP6, SAFI_ENCAP); + rfapiProcessPeerDownRt(peer, it, AFI_IP, SAFI_MPLS_VPN); + rfapiProcessPeerDownRt(peer, it, AFI_IP6, SAFI_MPLS_VPN); + } + + if (h->it_ce) { + rfapiProcessPeerDownRt(peer, h->it_ce, AFI_IP, SAFI_MPLS_VPN); + rfapiProcessPeerDownRt(peer, h->it_ce, AFI_IP6, SAFI_MPLS_VPN); + } +} + +/* + * Import an entire RIB (for an afi/safi) to an import table RIB, + * filtered according to the import table's RT list + * + * TBD: does this function need additions to match rfapiProcessUpdate() + * for, e.g., L2 handling? + */ +static void rfapiBgpTableFilteredImport(struct bgp *bgp, + struct rfapi_import_table *it, + afi_t afi, safi_t safi) +{ + struct bgp_dest *dest1; + struct bgp_dest *dest2; + + /* Only these SAFIs have 2-level RIBS */ + assert(safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP); + + /* + * Now visit all the rd nodes and the nodes of all the + * route tables attached to them, and import the routes + * if they have matching route targets + */ + for (dest1 = bgp_table_top(bgp->rib[afi][safi]); dest1; + dest1 = bgp_route_next(dest1)) { + + if (bgp_dest_has_bgp_path_info_data(dest1)) { + + for (dest2 = bgp_table_top( + bgp_dest_get_bgp_table_info(dest1)); + dest2; dest2 = bgp_route_next(dest2)) { + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest2); + bpi; bpi = bpi->next) { + uint32_t label = MPLS_INVALID_LABEL; + + if (CHECK_FLAG(bpi->flags, + BGP_PATH_REMOVED)) + continue; + + if (BGP_PATH_INFO_NUM_LABELS(bpi)) + label = decode_label( + &bpi->extra->labels + ->label[0]); + (*rfapiBgpInfoFilteredImportFunction( + safi))( + it, /* which import table */ + FIF_ACTION_UPDATE, bpi->peer, + NULL, + bgp_dest_get_prefix(dest2), + NULL, afi, + (struct prefix_rd *) + bgp_dest_get_prefix( + dest1), + bpi->attr, bpi->type, + bpi->sub_type, &label); + } + } + } + } +} + + +/* per-bgp-instance rfapi data */ +struct rfapi *bgp_rfapi_new(struct bgp *bgp) +{ + struct rfapi *h; + afi_t afi; + struct rfapi_rfp_cfg *cfg = NULL; + struct rfapi_rfp_cb_methods *cbm = NULL; + + assert(bgp->rfapi_cfg == NULL); + + h = XCALLOC(MTYPE_RFAPI, sizeof(struct rfapi)); + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + h->un[afi] = agg_table_init(); + } + + /* + * initialize the ce import table + */ + h->it_ce = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, + sizeof(struct rfapi_import_table)); + h->it_ce->imported_vpn[AFI_IP] = agg_table_init(); + h->it_ce->imported_vpn[AFI_IP6] = agg_table_init(); + h->it_ce->imported_encap[AFI_IP] = agg_table_init(); + h->it_ce->imported_encap[AFI_IP6] = agg_table_init(); + rfapiBgpTableFilteredImport(bgp, h->it_ce, AFI_IP, SAFI_MPLS_VPN); + rfapiBgpTableFilteredImport(bgp, h->it_ce, AFI_IP6, SAFI_MPLS_VPN); + + /* + * Set up work queue for deferred rfapi_close operations + */ + h->deferred_close_q = + work_queue_new(bm->master, "rfapi deferred close"); + h->deferred_close_q->spec.workfunc = rfapi_deferred_close_workfunc; + h->deferred_close_q->spec.data = h; + + h->rfp = rfp_start(bm->master, &cfg, &cbm); + bgp->rfapi_cfg = bgp_rfapi_cfg_new(cfg); + if (cbm != NULL) { + h->rfp_methods = *cbm; + } + return h; +} + +void bgp_rfapi_destroy(struct bgp *bgp, struct rfapi *h) +{ + afi_t afi; + + if (bgp == NULL || h == NULL) + return; + + if (h->resolve_nve_nexthop) { + skiplist_free(h->resolve_nve_nexthop); + h->resolve_nve_nexthop = NULL; + } + + rfapiImportTableFlush(h->it_ce); + + if (h->import_mac) { + struct rfapi_import_table *it; + void *cursor; + int rc; + + for (cursor = NULL, + rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor); + !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor)) { + + rfapiImportTableFlush(it); + XFREE(MTYPE_RFAPI_IMPORTTABLE, it); + } + skiplist_free(h->import_mac); + h->import_mac = NULL; + } + + work_queue_free_and_null(&h->deferred_close_q); + + if (h->rfp != NULL) + rfp_stop(h->rfp); + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + agg_table_finish(h->un[afi]); + } + + XFREE(MTYPE_RFAPI_IMPORTTABLE, h->it_ce); + XFREE(MTYPE_RFAPI, h); +} + +struct rfapi_import_table * +rfapiImportTableRefAdd(struct bgp *bgp, struct ecommunity *rt_import_list, + struct rfapi_nve_group_cfg *rfg) +{ + struct rfapi *h; + struct rfapi_import_table *it; + afi_t afi; + + h = bgp->rfapi; + assert(h); + + for (it = h->imports; it; it = it->next) { + if (ecommunity_cmp(it->rt_import_list, rt_import_list)) + break; + } + + vnc_zlog_debug_verbose("%s: matched it=%p", __func__, it); + + if (!it) { + it = XCALLOC(MTYPE_RFAPI_IMPORTTABLE, + sizeof(struct rfapi_import_table)); + it->next = h->imports; + h->imports = it; + + it->rt_import_list = ecommunity_dup(rt_import_list); + it->rfg = rfg; + it->monitor_exterior_orphans = + skiplist_new(0, NULL, prefix_free_lists); + + /* + * fill import route tables from RIBs + * + * Potential area for optimization. If this occurs when + * tables are large (e.g., the operator adds a nve group + * with a new RT list to a running system), it could take + * a while. + * + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + it->imported_vpn[afi] = agg_table_init(); + it->imported_encap[afi] = agg_table_init(); + + rfapiBgpTableFilteredImport(bgp, it, afi, + SAFI_MPLS_VPN); + rfapiBgpTableFilteredImport(bgp, it, afi, SAFI_ENCAP); + + vnc_import_bgp_exterior_redist_enable_it(bgp, afi, it); + } + } + + it->refcount += 1; + + return it; +} + +/* + * skiplist element free function + */ +static void delete_rem_pfx_na_free(void *na) +{ + uint32_t *pCounter = ((struct rfapi_nve_addr *)na)->info; + + *pCounter += 1; + XFREE(MTYPE_RFAPI_NVE_ADDR, na); +} + +/* + * Common deleter for IP and MAC import tables + */ +static void rfapiDeleteRemotePrefixesIt( + struct bgp *bgp, struct rfapi_import_table *it, struct prefix *un, + struct prefix *vn, struct prefix *p, int delete_active, + int delete_holddown, uint32_t *pARcount, uint32_t *pAHcount, + uint32_t *pHRcount, uint32_t *pHHcount, + struct skiplist *uniq_active_nves, struct skiplist *uniq_holddown_nves) +{ + afi_t afi; + +#ifdef DEBUG_L2_EXTRA + { + char buf_pfx[PREFIX_STRLEN]; + + if (p) { + prefix2str(p, buf_pfx, sizeof(buf_pfx)); + } else { + buf_pfx[0] = '*'; + buf_pfx[1] = 0; + } + + vnc_zlog_debug_verbose( + "%s: entry, p=%s, delete_active=%d, delete_holddown=%d", + __func__, buf_pfx, delete_active, delete_holddown); + } +#endif + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_table *rt; + struct agg_node *rn; + + if (p && (family2afi(p->family) != afi)) { + continue; + } + + rt = it->imported_vpn[afi]; + if (!rt) + continue; + + vnc_zlog_debug_verbose("%s: scanning rt for afi=%d", __func__, + afi); + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + struct bgp_path_info *bpi; + struct bgp_path_info *next; + const struct prefix *rn_p = agg_node_get_prefix(rn); + + if (p && VNC_DEBUG(IMPORT_DEL_REMOTE)) + vnc_zlog_debug_any("%s: want %pFX, have %pRN", + __func__, p, rn); + + if (p && prefix_cmp(p, rn_p)) + continue; + + vnc_zlog_debug_verbose("%s: rn pfx=%pRN", __func__, rn); + + /* TBD is this valid for afi == AFI_L2VPN? */ + RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 1); + + for (bpi = rn->info; bpi; bpi = next) { + next = bpi->next; + + struct prefix qpt; + struct prefix qct; + int qpt_valid = 0; + int qct_valid = 0; + int is_active = 0; + + vnc_zlog_debug_verbose("%s: examining bpi %p", + __func__, bpi); + + if (!rfapiGetNexthop(bpi->attr, &qpt)) + qpt_valid = 1; + + if (vn) { + if (!qpt_valid + || !prefix_match(vn, &qpt)) { +#ifdef DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: continue at vn && !qpt_valid || !prefix_match(vn, &qpt)", + __func__); +#endif + continue; + } + } + + if (!rfapiGetUnAddrOfVpnBi(bpi, &qct)) + qct_valid = 1; + + if (un) { + if (!qct_valid + || !prefix_match(un, &qct)) { +#ifdef DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: continue at un && !qct_valid || !prefix_match(un, &qct)", + __func__); +#endif + continue; + } + } + + + /* + * Blow bpi away + */ + /* + * If this route is waiting to be deleted + * because of + * a previous withdraw, we must cancel its + * timer. + */ + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + if (!delete_holddown) + continue; + if (bpi->extra->vnc->vnc.import.timer) { + struct rfapi_withdraw *wcb = EVENT_ARG( + bpi->extra->vnc->vnc + .import.timer); + + wcb->import_table + ->holddown_count[afi] -= + 1; + RFAPI_UPDATE_ITABLE_COUNT( + bpi, wcb->import_table, + afi, 1); + XFREE(MTYPE_RFAPI_WITHDRAW, + wcb); + EVENT_OFF(bpi->extra->vnc->vnc + .import.timer); + } + } else { + if (!delete_active) + continue; + is_active = 1; + } + + vnc_zlog_debug_verbose( + "%s: deleting bpi %p (qct_valid=%d, qpt_valid=%d, delete_holddown=%d, delete_active=%d)", + __func__, bpi, qct_valid, qpt_valid, + delete_holddown, delete_active); + + + /* + * add nve to list + */ + if (qct_valid && qpt_valid) { + + struct rfapi_nve_addr na; + struct rfapi_nve_addr *nap; + + memset(&na, 0, sizeof(na)); + assert(!rfapiQprefix2Raddr(&qct, + &na.un)); + assert(!rfapiQprefix2Raddr(&qpt, + &na.vn)); + + if (skiplist_search( + (is_active + ? uniq_active_nves + : uniq_holddown_nves), + &na, (void **)&nap)) { + char line[BUFSIZ]; + + nap = XCALLOC( + MTYPE_RFAPI_NVE_ADDR, + sizeof(struct + rfapi_nve_addr)); + *nap = na; + nap->info = is_active + ? pAHcount + : pHHcount; + skiplist_insert( + (is_active + ? uniq_active_nves + : uniq_holddown_nves), + nap, nap); + + rfapiNveAddr2Str(nap, line, + BUFSIZ); + } + } + + vnc_direct_bgp_rh_del_route(bgp, afi, rn_p, + bpi->peer); + + RFAPI_UPDATE_ITABLE_COUNT(bpi, it, afi, -1); + it->holddown_count[afi] += 1; + rfapiExpireVpnNow(it, rn, bpi, 1); + + vnc_zlog_debug_verbose( + "%s: incrementing count (is_active=%d)", + __func__, is_active); + + if (is_active) + ++*pARcount; + else + ++*pHRcount; + } + } + } +} + + +/* + * For use by the "clear vnc prefixes" command + */ +/*------------------------------------------ + * rfapiDeleteRemotePrefixes + * + * UI helper: For use by the "clear vnc prefixes" command + * + * input: + * un if set, tunnel must match this prefix + * vn if set, nexthop prefix must match this prefix + * p if set, prefix must match this prefix + * it if set, only look in this import table + * + * output + * pARcount number of active routes deleted + * pAHcount number of active nves deleted + * pHRcount number of holddown routes deleted + * pHHcount number of holddown nves deleted + * + * return value: + * void + --------------------------------------------*/ +void rfapiDeleteRemotePrefixes(struct prefix *un, struct prefix *vn, + struct prefix *p, + struct rfapi_import_table *arg_it, + int delete_active, int delete_holddown, + uint32_t *pARcount, uint32_t *pAHcount, + uint32_t *pHRcount, uint32_t *pHHcount) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + uint32_t deleted_holddown_route_count = 0; + uint32_t deleted_active_route_count = 0; + uint32_t deleted_holddown_nve_count = 0; + uint32_t deleted_active_nve_count = 0; + struct skiplist *uniq_holddown_nves; + struct skiplist *uniq_active_nves; + + VNC_ITRCCK; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + /* If no bgp instantiated yet, no vnc prefixes exist */ + if (!bgp) + return; + + h = bgp->rfapi; + assert(h); + + uniq_holddown_nves = + skiplist_new(0, rfapi_nve_addr_cmp, delete_rem_pfx_na_free); + uniq_active_nves = + skiplist_new(0, rfapi_nve_addr_cmp, delete_rem_pfx_na_free); + + /* + * Iterate over all import tables; do a filtered import + * for the afi/safi combination + */ + + if (arg_it) + it = arg_it; + else + it = h->imports; + for (; it;) { + + vnc_zlog_debug_verbose( + "%s: calling rfapiDeleteRemotePrefixesIt() on (IP) import %p", + __func__, it); + + rfapiDeleteRemotePrefixesIt( + bgp, it, un, vn, p, delete_active, delete_holddown, + &deleted_active_route_count, &deleted_active_nve_count, + &deleted_holddown_route_count, + &deleted_holddown_nve_count, uniq_active_nves, + uniq_holddown_nves); + + if (arg_it) + it = NULL; + else + it = it->next; + } + + /* + * Now iterate over L2 import tables + */ + if (h->import_mac && !(p && (p->family != AF_ETHERNET))) { + + void *cursor = NULL; + int rc; + + for (cursor = NULL, + rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor); + !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor)) { + + vnc_zlog_debug_verbose( + "%s: calling rfapiDeleteRemotePrefixesIt() on import_mac %p", + __func__, it); + + rfapiDeleteRemotePrefixesIt( + bgp, it, un, vn, p, delete_active, + delete_holddown, &deleted_active_route_count, + &deleted_active_nve_count, + &deleted_holddown_route_count, + &deleted_holddown_nve_count, uniq_active_nves, + uniq_holddown_nves); + } + } + + /* + * our custom element freeing function above counts as it deletes + */ + skiplist_free(uniq_holddown_nves); + skiplist_free(uniq_active_nves); + + if (pARcount) + *pARcount = deleted_active_route_count; + if (pAHcount) + *pAHcount = deleted_active_nve_count; + if (pHRcount) + *pHRcount = deleted_holddown_route_count; + if (pHHcount) + *pHHcount = deleted_holddown_nve_count; + + VNC_ITRCCK; +} + +/*------------------------------------------ + * rfapiCountRemoteRoutes + * + * UI helper: count VRF routes from BGP side + * + * input: + * + * output + * pALRcount count of active local routes + * pARRcount count of active remote routes + * pHRcount count of holddown routes + * pIRcount count of direct imported routes + * + * return value: + * void + --------------------------------------------*/ +void rfapiCountAllItRoutes(int *pALRcount, /* active local routes */ + int *pARRcount, /* active remote routes */ + int *pHRcount, /* holddown routes */ + int *pIRcount) /* imported routes */ +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + afi_t afi; + + int total_active_local = 0; + int total_active_remote = 0; + int total_holddown = 0; + int total_imported = 0; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + assert(bgp); + + h = bgp->rfapi; + assert(h); + + /* + * Iterate over all import tables; do a filtered import + * for the afi/safi combination + */ + + for (it = h->imports; it; it = it->next) { + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + total_active_local += it->local_count[afi]; + total_active_remote += it->remote_count[afi]; + total_holddown += it->holddown_count[afi]; + total_imported += it->imported_count[afi]; + } + } + + void *cursor; + int rc; + + if (h->import_mac) { + for (cursor = NULL, + rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor); + !rc; rc = skiplist_next(h->import_mac, NULL, (void **)&it, + &cursor)) { + + total_active_local += it->local_count[AFI_L2VPN]; + total_active_remote += it->remote_count[AFI_L2VPN]; + total_holddown += it->holddown_count[AFI_L2VPN]; + total_imported += it->imported_count[AFI_L2VPN]; + } + } + + + if (pALRcount) { + *pALRcount = total_active_local; + } + if (pARRcount) { + *pARRcount = total_active_remote; + } + if (pHRcount) { + *pHRcount = total_holddown; + } + if (pIRcount) { + *pIRcount = total_imported; + } +} + +/*------------------------------------------ + * rfapiGetHolddownFromLifetime + * + * calculate holddown value based on lifetime + * + * input: + * lifetime lifetime + * + * return value: + * Holddown value based on lifetime, holddown_factor, + * and RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY + * + --------------------------------------------*/ +/* hold down time maxes out at RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY */ +uint32_t rfapiGetHolddownFromLifetime(uint32_t lifetime) +{ + uint32_t factor; + struct bgp *bgp; + + bgp = bgp_get_default(); + if (bgp && bgp->rfapi_cfg) + factor = bgp->rfapi_cfg->rfp_cfg.holddown_factor; + else + factor = RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR; + + if (factor < 100 || lifetime < RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) + lifetime = lifetime * factor / 100; + if (lifetime < RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) + return lifetime; + else + return RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY; +} diff --git a/bgpd/rfapi/rfapi_import.h b/bgpd/rfapi/rfapi_import.h new file mode 100644 index 0000000..1a37e1c --- /dev/null +++ b/bgpd/rfapi/rfapi_import.h @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: rfapi_import.h + * Purpose: Handle import of routes from BGP to RFAPI + */ + +#ifndef QUAGGA_HGP_RFAPI_IMPORT_H +#define QUAGGA_HGP_RFAPI_IMPORT_H + +#include "frrevent.h" + +/* + * These are per-rt-import-list + * + * routes are not segregated by RD - the RD is stored in bgp_path_info_extra + * and is needed to determine if two prefixes are the same. + */ +struct rfapi_import_table { + struct rfapi_import_table *next; + struct rfapi_nve_group_cfg *rfg; + struct ecommunity *rt_import_list; /* copied from nve grp */ + int refcount; /* nve grps and nves */ + uint32_t l2_logical_net_id; /* L2 only: EVPN Eth Seg Id */ + struct agg_table *imported_vpn[AFI_MAX]; + struct rfapi_monitor_vpn *vpn0_queries[AFI_MAX]; + struct rfapi_monitor_eth *eth0_queries; + struct agg_table *imported_encap[AFI_MAX]; + struct skiplist *monitor_exterior_orphans; + int local_count[AFI_MAX]; + int remote_count[AFI_MAX]; + int holddown_count[AFI_MAX]; + int imported_count[AFI_MAX]; +}; + +#define RFAPI_LOCAL_BI(bpi) \ + (((bpi)->type == ZEBRA_ROUTE_BGP) && ((bpi)->sub_type == BGP_ROUTE_RFP)) + +#define RFAPI_DIRECT_IMPORT_BI(bpi) \ + (((bpi)->type == ZEBRA_ROUTE_BGP_DIRECT) \ + || ((bpi)->type == ZEBRA_ROUTE_BGP_DIRECT_EXT)) + +#define RFAPI_UPDATE_ITABLE_COUNT(bpi, itable, afi, cnt) \ + if (RFAPI_LOCAL_BI(bpi)) { \ + (itable)->local_count[(afi)] += (cnt); \ + } else { \ + if (RFAPI_DIRECT_IMPORT_BI(bpi)) \ + (itable)->imported_count[(afi)] += (cnt); \ + else \ + (itable)->remote_count[(afi)] += (cnt); \ + } + +extern uint8_t rfapiRfpCost(struct attr *attr); + +extern void rfapiCheckRouteCount(void); + +/* + * Print BPI in an Import Table + */ +extern void rfapiPrintBi(void *stream, struct bgp_path_info *bpi); + +extern void rfapiShowImportTable(void *stream, const char *label, + struct agg_table *rt, int isvpn); + +extern struct rfapi_import_table * +rfapiImportTableRefAdd(struct bgp *bgp, struct ecommunity *rt_import_list, + struct rfapi_nve_group_cfg *rfg); + +extern void rfapiImportTableRefDelByIt(struct bgp *bgp, + struct rfapi_import_table *it_target); + + +/* + * Construct an rfapi nexthop list based on the routes attached to + * the specified node. + * + * If there are any routes that do NOT have BGP_PATH_REMOVED set, + * return those only. If there are ONLY routes with BGP_INFO_REMOVED, + * then return those, and also include all the non-removed routes from the + * next less-specific node (i.e., this node's parent) at the end. + */ +extern struct rfapi_next_hop_entry *rfapiRouteNode2NextHopList( + struct agg_node *rn, uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload this NVE rib table */ + struct prefix *pfx_target_original); /* query target */ + +extern struct rfapi_next_hop_entry *rfapiRouteTable2NextHopList( + struct agg_table *rt, uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rfd_rib_table, /* preload this NVE rib table */ + struct prefix *pfx_target_original); /* query target */ + +extern struct rfapi_next_hop_entry *rfapiEthRouteTable2NextHopList( + uint32_t logical_net_id, struct rfapi_ip_prefix *rprefix, + uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rib_route_table, /* preload NVE rib node */ + struct prefix *pfx_target_original); /* query target */ + +extern int rfapiEcommunitiesIntersect(struct ecommunity *e1, + struct ecommunity *e2); + +extern void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, + int lockoffset); + +extern int rfapiHasNonRemovedRoutes(struct agg_node *rn); + +extern int rfapiGetUnAddrOfVpnBi(struct bgp_path_info *bpi, struct prefix *p); + +extern void rfapiNexthop2Prefix(struct attr *attr, struct prefix *p); + +extern void rfapiUnicastNexthop2Prefix(afi_t afi, struct attr *attr, + struct prefix *p); + +/* Filtered Import Function actions */ +#define FIF_ACTION_UPDATE 0 +#define FIF_ACTION_WITHDRAW 1 +#define FIF_ACTION_KILL 2 + +extern void rfapiBgpInfoFilteredImportVPN( + struct rfapi_import_table *import_table, int action, struct peer *peer, + void *rfd, /* set for looped back routes */ + const struct prefix *p, + const struct prefix *aux_prefix, /* AFI_ETHER: optional IP */ + afi_t afi, struct prefix_rd *prd, + struct attr *attr, /* part of bgp_path_info */ + uint8_t type, /* part of bgp_path_info */ + uint8_t sub_type, /* part of bgp_path_info */ + uint32_t *label); /* part of bgp_path_info */ + +extern struct rfapi_next_hop_entry *rfapiEthRouteNode2NextHopList( + struct agg_node *rn, struct rfapi_ip_prefix *rprefix, + uint32_t lifetime, /* put into nexthop entries */ + struct rfapi_ip_addr *exclude_vnaddr, /* omit routes to same NVE */ + struct agg_table *rib_route_table, /* preload NVE rib table */ + struct prefix *pfx_target_original); /* query target */ + +extern struct rfapi_import_table *rfapiMacImportTableGetNoAlloc(struct bgp *bgp, + uint32_t lni); + +extern struct rfapi_import_table *rfapiMacImportTableGet(struct bgp *bgp, + uint32_t lni); + +extern int rfapiGetL2o(struct attr *attr, struct rfapi_l2address_option *l2o); + +extern int rfapiEcommunityGetLNI(struct ecommunity *ecom, uint32_t *lni); + +extern int rfapiEcommunityGetEthernetTag(struct ecommunity *ecom, + uint16_t *tag_id); + +/* enable for debugging; disable for performance */ +#if 0 +#define RFAPI_CHECK_REFCOUNT(rn, safi, lo) rfapiCheckRefcount((rn),(safi),(lo)) +#else +#define RFAPI_CHECK_REFCOUNT(rn, safi, lo) {} +#endif + +/*------------------------------------------ + * rfapiDeleteRemotePrefixes + * + * UI helper: For use by the "clear vnc prefixes" command + * + * input: + * un if set, tunnel must match this prefix + * vn if set, nexthop prefix must match this prefix + * p if set, prefix must match this prefix + * it if set, only look in this import table + * + * output + * pARcount number of active routes deleted + * pAHcount number of active nves deleted + * pHRcount number of holddown routes deleted + * pHHcount number of holddown nves deleted + * + * return value: + * void + --------------------------------------------*/ +extern void rfapiDeleteRemotePrefixes(struct prefix *un, struct prefix *vn, + struct prefix *p, + struct rfapi_import_table *it, + int delete_active, int delete_holddown, + uint32_t *pARcount, /* active routes */ + uint32_t *pAHcount, /* active nves */ + uint32_t *pHRcount, /* holddown routes */ + uint32_t *pHHcount); /* holddown nves */ + +/*------------------------------------------ + * rfapiCountAllItRoutes + * + * UI helper: count VRF routes from BGP side + * + * input: + * + * output + * pARcount count of active routes + * pHRcount count of holddown routes + * pIRcount count of holddown routes + * + * return value: + * void + --------------------------------------------*/ +extern void rfapiCountAllItRoutes(int *pALRcount, /* active local routes */ + int *pARRcount, /* active remote routes */ + int *pHRcount, /* holddown routes */ + int *pIRcount); /* direct imported routes */ + +/*------------------------------------------ + * rfapiGetHolddownFromLifetime + * + * calculate holddown value based on lifetime + * + * input: + * lifetime lifetime + * + * return value: + * Holddown value based on lifetime, holddown_factor, + * and RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY + * + --------------------------------------------*/ +extern uint32_t rfapiGetHolddownFromLifetime(uint32_t lifetime); + +#endif /* QUAGGA_HGP_RFAPI_IMPORT_H */ diff --git a/bgpd/rfapi/rfapi_monitor.c b/bgpd/rfapi/rfapi_monitor.c new file mode 100644 index 0000000..146e0d1 --- /dev/null +++ b/bgpd/rfapi/rfapi_monitor.c @@ -0,0 +1,1557 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: rfapi_monitor.c + */ + +/* TBD remove unneeded includes */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/log.h" +#include "lib/table.h" +#include "lib/skiplist.h" + +#include "bgpd/bgpd.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_backend.h" + +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/rfapi_rib.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define DEBUG_L2_EXTRA 0 +#define DEBUG_DUP_CHECK 0 +#define DEBUG_ETH_SL 0 + +static void rfapiMonitorTimerRestart(struct rfapi_monitor_vpn *m); + +static void rfapiMonitorEthTimerRestart(struct rfapi_monitor_eth *m); + +/* + * Forward declarations + */ +static void rfapiMonitorEthDetachImport(struct bgp *bgp, + struct rfapi_monitor_eth *mon); + +#if DEBUG_ETH_SL +/* + * Debug function, special case + */ +void rfapiMonitorEthSlCheck(struct agg_node *rn, const char *tag1, + const char *tag2) +{ + struct agg_node *rn_saved = NULL; + static struct skiplist *sl_saved = NULL; + struct skiplist *sl; + + if (!rn) + return; + + if (rn_saved && (rn != rn_saved)) + return; + + if (!rn_saved) + rn_saved = rn; + + sl = RFAPI_MONITOR_ETH(rn); + if (sl || sl_saved) { + vnc_zlog_debug_verbose( + "%s[%s%s]: rn=%p, rn->lock=%d, old sl=%p, new sl=%p", + __func__, (tag1 ? tag1 : ""), (tag2 ? tag2 : ""), rn, + rn->lock, sl_saved, sl); + sl_saved = sl; + } +} +#endif + +/* + * Debugging function that aborts when it finds monitors whose + * "next" pointer * references themselves + */ +void rfapiMonitorLoopCheck(struct rfapi_monitor_vpn *mchain) +{ + struct rfapi_monitor_vpn *m; + + for (m = mchain; m; m = m->next) + assert(m != m->next); +} + +#if DEBUG_DUP_CHECK +/* + * Debugging code: see if a monitor is mentioned more than once + * in a HD's monitor list + */ +void rfapiMonitorDupCheck(struct bgp *bgp) +{ + struct listnode *hnode; + struct rfapi_descriptor *rfd; + + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { + struct agg_node *mrn; + + if (!rfd->mon) + continue; + + for (mrn = agg_route_top(rfd->mon); mrn; + mrn = agg_route_next(mrn)) { + struct rfapi_monitor_vpn *m; + for (m = (struct rfapi_monitor_vpn *)(mrn->info); m; + m = m->next) + m->dcount = 0; + } + } + + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { + struct agg_node *mrn; + + if (!rfd->mon) + continue; + + for (mrn = agg_route_top(rfd->mon); mrn; + mrn = agg_route_next(mrn)) { + struct rfapi_monitor_vpn *m; + + for (m = (struct rfapi_monitor_vpn *)(mrn->info); m; + m = m->next) + assert(++m->dcount == 1); + } + } +} +#endif + +/* debug */ +void rfapiMonitorCleanCheck(struct bgp *bgp) +{ + struct listnode *hnode; + struct rfapi_descriptor *rfd; + + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { + assert(!rfd->import_table->vpn0_queries[AFI_IP]); + assert(!rfd->import_table->vpn0_queries[AFI_IP6]); + + struct agg_node *rn; + + for (rn = agg_route_top( + rfd->import_table->imported_vpn[AFI_IP]); + rn; rn = agg_route_next(rn)) { + + assert(!RFAPI_MONITOR_VPN(rn)); + } + for (rn = agg_route_top( + rfd->import_table->imported_vpn[AFI_IP6]); + rn; rn = agg_route_next(rn)) { + + assert(!RFAPI_MONITOR_VPN(rn)); + } + } +} + +/* debug */ +void rfapiMonitorCheckAttachAllowed(void) +{ + struct bgp *bgp = bgp_get_default(); + assert(!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)); +} + +void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn) +{ + struct rfapi_it_extra *hie; + struct rfapi_monitor_vpn *v; + struct rfapi_monitor_vpn *v_next; + struct rfapi_monitor_encap *e = NULL; + struct rfapi_monitor_encap *e_next = NULL; + + if (!rn) + return; + + if (!rn->aggregate) + return; + + hie = (struct rfapi_it_extra *)(rn->aggregate); + + switch (safi) { + case SAFI_ENCAP: + for (e = hie->u.encap.e; e; e = e_next) { + e_next = e->next; + e->next = NULL; + XFREE(MTYPE_RFAPI_MONITOR_ENCAP, e); + agg_unlock_node(rn); + } + hie->u.encap.e = NULL; + break; + + case SAFI_MPLS_VPN: + for (v = hie->u.vpn.v; v; v = v_next) { + v_next = v->next; + v->next = NULL; + XFREE(MTYPE_RFAPI_MONITOR, e); + agg_unlock_node(rn); + } + hie->u.vpn.v = NULL; + if (hie->u.vpn.e.source) { + while (!skiplist_delete_first(hie->u.vpn.e.source)) { + agg_unlock_node(rn); + } + skiplist_free(hie->u.vpn.e.source); + hie->u.vpn.e.source = NULL; + agg_unlock_node(rn); + } + if (hie->u.vpn.idx_rd) { + /* looping through bpi->extra->vnc.import.rd is tbd */ + while (!skiplist_delete_first(hie->u.vpn.idx_rd)) { + agg_unlock_node(rn); + } + skiplist_free(hie->u.vpn.idx_rd); + hie->u.vpn.idx_rd = NULL; + agg_unlock_node(rn); + } + if (hie->u.vpn.mon_eth) { + while (!skiplist_delete_first(hie->u.vpn.mon_eth)) { + agg_unlock_node(rn); + } + skiplist_free(hie->u.vpn.mon_eth); + hie->u.vpn.mon_eth = NULL; + agg_unlock_node(rn); + } + break; + + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_EVPN: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + assert(0); + } + XFREE(MTYPE_RFAPI_IT_EXTRA, hie); + rn->aggregate = NULL; + agg_unlock_node(rn); +} + +/* + * If the child lists are empty, release the rfapi_it_extra struct + */ +void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn) +{ + struct rfapi_it_extra *hie; + + if (!rn) + return; + + if (!rn->aggregate) + return; + + hie = (struct rfapi_it_extra *)(rn->aggregate); + + switch (safi) { + case SAFI_ENCAP: + if (hie->u.encap.e) + return; + break; + + case SAFI_MPLS_VPN: + if (hie->u.vpn.v) + return; + if (hie->u.vpn.mon_eth) { + if (skiplist_count(hie->u.vpn.mon_eth)) + return; + skiplist_free(hie->u.vpn.mon_eth); + hie->u.vpn.mon_eth = NULL; + agg_unlock_node(rn); /* uncount skiplist */ + } + if (hie->u.vpn.e.source) { + if (skiplist_count(hie->u.vpn.e.source)) + return; + skiplist_free(hie->u.vpn.e.source); + hie->u.vpn.e.source = NULL; + agg_unlock_node(rn); + } + if (hie->u.vpn.idx_rd) { + if (skiplist_count(hie->u.vpn.idx_rd)) + return; + skiplist_free(hie->u.vpn.idx_rd); + hie->u.vpn.idx_rd = NULL; + agg_unlock_node(rn); + } + if (hie->u.vpn.mon_eth) { + if (skiplist_count(hie->u.vpn.mon_eth)) + return; + skiplist_free(hie->u.vpn.mon_eth); + hie->u.vpn.mon_eth = NULL; + agg_unlock_node(rn); + } + break; + + case SAFI_UNSPEC: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_EVPN: + case SAFI_LABELED_UNICAST: + case SAFI_FLOWSPEC: + case SAFI_MAX: + assert(0); + } + XFREE(MTYPE_RFAPI_IT_EXTRA, hie); + rn->aggregate = NULL; + agg_unlock_node(rn); +} + +/* + * returns locked node + */ +struct agg_node *rfapiMonitorGetAttachNode(struct rfapi_descriptor *rfd, + struct prefix *p) +{ + afi_t afi; + struct agg_node *rn; + + if (RFAPI_0_PREFIX(p)) { + assert(1); + } + + afi = family2afi(p->family); + assert(afi); + + /* + * It's possible that even though there is a route at this node, + * there are no routes with valid UN addresses (i.e,. with no + * valid tunnel routes). Check for that and walk back up the + * tree if necessary. + * + * When the outer loop completes, the matched node, if any, is + * locked (i.e., its reference count has been incremented) to + * account for the VPN monitor we are about to attach. + * + * if a monitor is moved to another node, there must be + * corresponding unlock/locks + */ + for (rn = agg_node_match(rfd->import_table->imported_vpn[afi], p); + rn;) { + + struct bgp_path_info *bpi; + struct prefix pfx_dummy; + + /* TBD update this code to use new valid_interior_count */ + for (bpi = rn->info; bpi; bpi = bpi->next) { + /* + * If there is a cached ENCAP UN address, it's a usable + * VPN route + */ + if (bpi->extra && bpi->extra->vnc->vnc.import.un_family) { + break; + } + + /* + * Or if there is a valid Encap Attribute tunnel subtlv + * address, + * it's a usable VPN route. + */ + if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_dummy)) { + break; + } + } + if (bpi) + break; + + agg_unlock_node(rn); + if ((rn = agg_node_parent(rn))) { + agg_lock_node(rn); + } + } + + if (!rn) { + struct prefix pfx_default; + + memset(&pfx_default, 0, sizeof(pfx_default)); + pfx_default.family = p->family; + + /* creates default node if none exists, and increments ref count + */ + rn = agg_node_get(rfd->import_table->imported_vpn[afi], + &pfx_default); + } + + return rn; +} + +/* + * If this function happens to attach the monitor to a radix tree + * node (as opposed to the 0-prefix list), the node pointer is + * returned (for the benefit of caller which might like to use it + * to generate an immediate query response). + */ +static struct agg_node *rfapiMonitorAttachImport(struct rfapi_descriptor *rfd, + struct rfapi_monitor_vpn *m) +{ + struct agg_node *rn; + + rfapiMonitorCheckAttachAllowed(); + + if (RFAPI_0_PREFIX(&m->p)) { + /* + * Add new monitor entry to vpn0 list + */ + afi_t afi; + + afi = family2afi(m->p.family); + assert(afi); + + m->next = rfd->import_table->vpn0_queries[afi]; + rfd->import_table->vpn0_queries[afi] = m; + vnc_zlog_debug_verbose("%s: attached monitor %p to vpn0 list", + __func__, m); + return NULL; + } + + /* + * Attach new monitor entry to import table node + */ + rn = rfapiMonitorGetAttachNode(rfd, &m->p); /* returns locked rn */ + m->node = rn; + m->next = RFAPI_MONITOR_VPN(rn); + RFAPI_MONITOR_VPN_W_ALLOC(rn) = m; + RFAPI_CHECK_REFCOUNT(rn, SAFI_MPLS_VPN, 0); + vnc_zlog_debug_verbose("%s: attached monitor %p to rn %p", __func__, m, + rn); + return rn; +} + + +/* + * reattach monitors for this HD to import table + */ +void rfapiMonitorAttachImportHd(struct rfapi_descriptor *rfd) +{ + struct agg_node *mrn; + + if (!rfd->mon) { + /* + * No monitors for this HD + */ + return; + } + + for (mrn = agg_route_top(rfd->mon); mrn; mrn = agg_route_next(mrn)) { + + if (!mrn->info) + continue; + + (void)rfapiMonitorAttachImport( + rfd, (struct rfapi_monitor_vpn *)(mrn->info)); + } +} + +/* + * Adds a monitor for a query to the NVE descriptor's list + * and, if callbacks are enabled, attaches it to the import table. + * + * If we happened to locate the import table radix tree attachment + * point, return it so the caller can use it to generate a query + * response without repeating the lookup. Note that when callbacks + * are disabled, this function will not perform a lookup, and the + * caller will have to do its own lookup. + */ +struct agg_node *rfapiMonitorAdd(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *p) +{ + struct rfapi_monitor_vpn *m; + struct agg_node *rn; + + /* + * Initialize nve's monitor list if needed + * NB use the same radix tree for IPv4 and IPv6 targets. + * The prefix will always have full-length mask (/32, /128) + * or be 0/0 so they won't get mixed up. + */ + if (!rfd->mon) { + rfd->mon = agg_table_init(); + } + rn = agg_node_get(rfd->mon, p); + if (rn->info) { + /* + * received this query before, no further action needed + */ + rfapiMonitorTimerRestart((struct rfapi_monitor_vpn *)rn->info); + agg_unlock_node(rn); + return NULL; + } + + /* + * New query for this nve, record it in the HD + */ + rn->info = + XCALLOC(MTYPE_RFAPI_MONITOR, sizeof(struct rfapi_monitor_vpn)); + m = (struct rfapi_monitor_vpn *)(rn->info); + m->rfd = rfd; + prefix_copy(&m->p, p); + + ++rfd->monitor_count; + ++bgp->rfapi->monitor_count; + + rfapiMonitorTimerRestart(m); + + if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { + /* + * callbacks turned off, so don't attach monitor to import table + */ + return NULL; + } + + + /* + * attach to import table + */ + return rfapiMonitorAttachImport(rfd, m); +} + +/* + * returns monitor pointer if found, NULL if not + */ +static struct rfapi_monitor_vpn * +rfapiMonitorDetachImport(struct rfapi_monitor_vpn *m) +{ + struct rfapi_monitor_vpn *prev; + struct rfapi_monitor_vpn *this = NULL; + + if (RFAPI_0_PREFIX(&m->p)) { + afi_t afi; + + /* + * 0-prefix monitors are stored in a special list and not + * in the import VPN tree + */ + + afi = family2afi(m->p.family); + assert(afi); + + if (m->rfd->import_table) { + for (prev = NULL, + this = m->rfd->import_table->vpn0_queries[afi]; + this; prev = this, this = this->next) { + + if (this == m) + break; + } + if (this) { + if (!prev) { + m->rfd->import_table + ->vpn0_queries[afi] = + this->next; + } else { + prev->next = this->next; + } + } + } + } else { + + if (m->node) { + for (prev = NULL, this = RFAPI_MONITOR_VPN(m->node); + this; prev = this, this = this->next) { + + if (this == m) + break; + } + if (this) { + if (prev) { + prev->next = this->next; + } else { + RFAPI_MONITOR_VPN_W_ALLOC(m->node) = + this->next; + } + RFAPI_CHECK_REFCOUNT(m->node, SAFI_MPLS_VPN, 1); + agg_unlock_node(m->node); + } + m->node = NULL; + } + } + return this; +} + + +void rfapiMonitorDetachImportHd(struct rfapi_descriptor *rfd) +{ + struct agg_node *rn; + + if (!rfd->mon) + return; + + for (rn = agg_route_top(rfd->mon); rn; rn = agg_route_next(rn)) { + if (rn->info) { + rfapiMonitorDetachImport( + (struct rfapi_monitor_vpn *)(rn->info)); + } + } +} + +void rfapiMonitorDel(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *p) +{ + struct agg_node *rn; + struct rfapi_monitor_vpn *m; + + assert(rfd->mon); + rn = agg_node_get(rfd->mon, p); /* locks node */ + m = rn->info; + + assert(m); + + /* + * remove from import table + */ + if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + rfapiMonitorDetachImport(m); + } + + EVENT_OFF(m->timer); + + /* + * remove from rfd list + */ + XFREE(MTYPE_RFAPI_MONITOR, m); + rn->info = NULL; + agg_unlock_node(rn); /* undo original lock when created */ + agg_unlock_node(rn); /* undo lock in agg_node_get */ + + --rfd->monitor_count; + --bgp->rfapi->monitor_count; +} + +/* + * returns count of monitors deleted + */ +int rfapiMonitorDelHd(struct rfapi_descriptor *rfd) +{ + struct agg_node *rn; + struct bgp *bgp; + int count = 0; + + vnc_zlog_debug_verbose("%s: entry rfd=%p", __func__, rfd); + + bgp = bgp_get_default(); + + if (rfd->mon) { + for (rn = agg_route_top(rfd->mon); rn; + rn = agg_route_next(rn)) { + struct rfapi_monitor_vpn *m; + if ((m = rn->info)) { + if (!(bgp->rfapi_cfg->flags + & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + rfapiMonitorDetachImport(m); + } + + EVENT_OFF(m->timer); + + XFREE(MTYPE_RFAPI_MONITOR, m); + rn->info = NULL; + agg_unlock_node(rn); /* undo original lock + when created */ + ++count; + --rfd->monitor_count; + --bgp->rfapi->monitor_count; + } + } + agg_table_finish(rfd->mon); + rfd->mon = NULL; + } + + if (rfd->mon_eth) { + + struct rfapi_monitor_eth *mon_eth; + + while (!skiplist_first(rfd->mon_eth, NULL, (void **)&mon_eth)) { + + int rc; + + if (!(bgp->rfapi_cfg->flags + & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + rfapiMonitorEthDetachImport(bgp, mon_eth); + } else { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: callbacks disabled, not attempting to detach mon_eth %p", + __func__, mon_eth); +#endif + } + + EVENT_OFF(mon_eth->timer); + + /* + * remove from rfd list + */ + rc = skiplist_delete(rfd->mon_eth, mon_eth, mon_eth); + assert(!rc); + + vnc_zlog_debug_verbose("%s: freeing mon_eth %p", + __func__, mon_eth); + XFREE(MTYPE_RFAPI_MONITOR_ETH, mon_eth); + + ++count; + --rfd->monitor_count; + --bgp->rfapi->monitor_count; + } + skiplist_free(rfd->mon_eth); + rfd->mon_eth = NULL; + } + + return count; +} + +void rfapiMonitorResponseRemovalOff(struct bgp *bgp) +{ + if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE) { + return; + } + bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; +} + +void rfapiMonitorResponseRemovalOn(struct bgp *bgp) +{ + if (!(bgp->rfapi_cfg->flags + & BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { + return; + } + bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE; +} + +static void rfapiMonitorTimerExpire(struct event *t) +{ + struct rfapi_monitor_vpn *m = EVENT_ARG(t); + + /* forget reference to thread, it's gone */ + m->timer = NULL; + + /* delete the monitor */ + rfapiMonitorDel(bgp_get_default(), m->rfd, &m->p); +} + +static void rfapiMonitorTimerRestart(struct rfapi_monitor_vpn *m) +{ + unsigned long remain = event_timer_remain_second(m->timer); + + /* unexpected case, but avoid wraparound problems below */ + if (remain > m->rfd->response_lifetime) + return; + + /* don't restart if we just restarted recently */ + if (m->rfd->response_lifetime - remain < 2) + return; + + EVENT_OFF(m->timer); + + { + char buf[BUFSIZ]; + + vnc_zlog_debug_verbose( + "%s: target %s life %u", __func__, + rfapi_ntop(m->p.family, m->p.u.val, buf, BUFSIZ), + m->rfd->response_lifetime); + } + + event_add_timer(bm->master, rfapiMonitorTimerExpire, m, + m->rfd->response_lifetime, &m->timer); +} + +/* + * called when an updated response is sent to the NVE. Per + * ticket 255, restart timers for any monitors that could have + * been responsible for the response, i.e., any monitors for + * the exact prefix or a parent of it. + */ +void rfapiMonitorTimersRestart(struct rfapi_descriptor *rfd, + const struct prefix *p) +{ + struct agg_node *rn; + + if (AF_ETHERNET == p->family) { + struct rfapi_monitor_eth *mon_eth; + int rc; + void *cursor; + + /* + * XXX match any LNI + */ + for (cursor = NULL, + rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon_eth, + &cursor); + rc == 0; rc = skiplist_next(rfd->mon_eth, NULL, + (void **)&mon_eth, &cursor)) { + + if (!memcmp(mon_eth->macaddr.octet, + p->u.prefix_eth.octet, ETH_ALEN)) { + + rfapiMonitorEthTimerRestart(mon_eth); + } + } + + } else { + for (rn = agg_route_top(rfd->mon); rn; + rn = agg_route_next(rn)) { + struct rfapi_monitor_vpn *m; + const struct prefix *p_node; + + if (!((m = rn->info))) + continue; + + p_node = agg_node_get_prefix(m->node); + /* NB order of test is significant ! */ + if (!m->node || prefix_match(p_node, p)) { + rfapiMonitorTimerRestart(m); + } + } + } +} + +/* + * Find monitors at this node and all its parents. Call + * rfapiRibUpdatePendingNode with this node and all corresponding NVEs. + */ +void rfapiMonitorItNodeChanged( + struct rfapi_import_table *import_table, struct agg_node *it_node, + struct rfapi_monitor_vpn *monitor_list) /* for base it node, NULL=all */ +{ + struct skiplist *nves_seen; + struct agg_node *rn = it_node; + struct bgp *bgp = bgp_get_default(); + const struct prefix *p = agg_node_get_prefix(rn); + afi_t afi = family2afi(p->family); + + assert(bgp); + assert(import_table); + + nves_seen = skiplist_new(0, NULL, NULL); + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: it=%p, it_node=%p, it_node->prefix=%pFX", + __func__, import_table, it_node, &it_node->p); +#endif + + if (AFI_L2VPN == afi) { + struct rfapi_monitor_eth *m; + struct skiplist *sl; + void *cursor; + int rc; + + if ((sl = RFAPI_MONITOR_ETH(rn))) { + + for (cursor = NULL, + rc = skiplist_next(sl, NULL, (void **)&m, &cursor); + !rc; rc = skiplist_next(sl, NULL, (void **)&m, + &cursor)) { + + if (skiplist_search(nves_seen, m->rfd, NULL)) { + /* + * Haven't done this NVE yet. Add to + * "seen" list. + */ + assert(!skiplist_insert(nves_seen, + m->rfd, NULL)); + + /* + * update its RIB + */ + rfapiRibUpdatePendingNode( + bgp, m->rfd, import_table, + it_node, + m->rfd->response_lifetime); + } + } + } + + } else { + + struct rfapi_monitor_vpn *m; + + if (monitor_list) { + m = monitor_list; + } else { + m = RFAPI_MONITOR_VPN(rn); + } + + do { + /* + * If we have reached the root node (parent==NULL) and + * there + * are no routes here (info==NULL), and the IT node that + * changed was not the root node (it_node->parent != + * NULL), + * then any monitors at this node are here because they + * had + * no match at all. Therefore, do not send route updates + * to them + * because we haven't sent them an initial route. + */ + if (!agg_node_parent(rn) && !rn->info + && it_node->parent) + break; + + for (; m; m = m->next) { + + if (RFAPI_0_PREFIX(&m->p)) { + /* shouldn't happen, but be safe */ + continue; + } + if (skiplist_search(nves_seen, m->rfd, NULL)) { + /* + * Haven't done this NVE yet. Add to + * "seen" list. + */ + assert(!skiplist_insert(nves_seen, + m->rfd, NULL)); + + vnc_zlog_debug_verbose( + "%s: update rfd %p attached to pfx %pRN (targ=%pFX)", + __func__, m->rfd, m->node, + &m->p); + + /* + * update its RIB + */ + rfapiRibUpdatePendingNode( + bgp, m->rfd, import_table, + it_node, + m->rfd->response_lifetime); + } + } + rn = agg_node_parent(rn); + if (rn) + m = RFAPI_MONITOR_VPN(rn); + } while (rn); + } + + /* + * All-routes L2 monitors + */ + if (AFI_L2VPN == afi) { + struct rfapi_monitor_eth *e; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: checking L2 all-routes monitors", + __func__); +#endif + + for (e = import_table->eth0_queries; e; e = e->next) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: checking eth0 mon=%p", + __func__, e); +#endif + if (skiplist_search(nves_seen, e->rfd, NULL)) { + /* + * Haven't done this NVE yet. Add to "seen" + * list. + */ + assert(!skiplist_insert(nves_seen, e->rfd, + NULL)); + +/* + * update its RIB + */ +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: found L2 all-routes monitor %p", + __func__, e); +#endif + rfapiRibUpdatePendingNode( + bgp, e->rfd, import_table, it_node, + e->rfd->response_lifetime); + } + } + } else { + struct rfapi_monitor_vpn *m; + + /* + * All-routes IPv4. IPv6 monitors + */ + for (m = import_table->vpn0_queries[afi]; m; m = m->next) { + if (skiplist_search(nves_seen, m->rfd, NULL)) { + /* + * Haven't done this NVE yet. Add to "seen" + * list. + */ + assert(!skiplist_insert(nves_seen, m->rfd, + NULL)); + + /* + * update its RIB + */ + rfapiRibUpdatePendingNode( + bgp, m->rfd, import_table, it_node, + m->rfd->response_lifetime); + } + } + } + + skiplist_free(nves_seen); +} + +/* + * For the listed monitors, update new node and its subtree, but + * omit old node and its subtree + */ +void rfapiMonitorMovedUp(struct rfapi_import_table *import_table, + struct agg_node *old_node, struct agg_node *new_node, + struct rfapi_monitor_vpn *monitor_list) +{ + struct bgp *bgp = bgp_get_default(); + struct rfapi_monitor_vpn *m; + + assert(new_node); + assert(old_node); + assert(new_node != old_node); + + /* + * If new node is 0/0 and there is no route there, don't + * generate an update because it will not contain any + * routes including the target. + */ + if (!new_node->parent && !new_node->info) { + vnc_zlog_debug_verbose( + "%s: new monitor at 0/0 and no routes, no updates", + __func__); + return; + } + + for (m = monitor_list; m; m = m->next) { + rfapiRibUpdatePendingNode(bgp, m->rfd, import_table, new_node, + m->rfd->response_lifetime); + rfapiRibUpdatePendingNodeSubtree(bgp, m->rfd, import_table, + new_node, old_node, + m->rfd->response_lifetime); + } +} + +static void rfapiMonitorEthTimerExpire(struct event *t) +{ + struct rfapi_monitor_eth *m = EVENT_ARG(t); + + /* forget reference to thread, it's gone */ + m->timer = NULL; + + /* delete the monitor */ + rfapiMonitorEthDel(bgp_get_default(), m->rfd, &m->macaddr, + m->logical_net_id); + +} + +static void rfapiMonitorEthTimerRestart(struct rfapi_monitor_eth *m) +{ + unsigned long remain = event_timer_remain_second(m->timer); + + /* unexpected case, but avoid wraparound problems below */ + if (remain > m->rfd->response_lifetime) + return; + + /* don't restart if we just restarted recently */ + if (m->rfd->response_lifetime - remain < 2) + return; + + EVENT_OFF(m->timer); + + { + char buf[BUFSIZ]; + + vnc_zlog_debug_verbose( + "%s: target %s life %u", __func__, + rfapiEthAddr2Str(&m->macaddr, buf, BUFSIZ), + m->rfd->response_lifetime); + } + + event_add_timer(bm->master, rfapiMonitorEthTimerExpire, m, + m->rfd->response_lifetime, &m->timer); +} + +static int mon_eth_cmp(const void *a, const void *b) +{ + const struct rfapi_monitor_eth *m1; + const struct rfapi_monitor_eth *m2; + + int i; + + m1 = (struct rfapi_monitor_eth *)a; + m2 = (struct rfapi_monitor_eth *)b; + + /* + * compare ethernet addresses + */ + for (i = 0; i < ETH_ALEN; ++i) { + if (m1->macaddr.octet[i] != m2->macaddr.octet[i]) + return (m1->macaddr.octet[i] - m2->macaddr.octet[i]); + } + + /* + * compare LNIs + */ + return (m1->logical_net_id - m2->logical_net_id); +} + +static void rfapiMonitorEthAttachImport( + struct rfapi_import_table *it, + struct agg_node *rn, /* it node attach point if non-0 */ + struct rfapi_monitor_eth *mon) /* monitor struct to attach */ +{ + struct skiplist *sl; + int rc; + + vnc_zlog_debug_verbose("%s: it=%p", __func__, it); + + rfapiMonitorCheckAttachAllowed(); + + if (RFAPI_0_ETHERADDR(&mon->macaddr)) { + /* + * These go on a different list + */ + mon->next = it->eth0_queries; + it->eth0_queries = mon; +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: attached monitor %p to eth0 list", + __func__, mon); +#endif + return; + } + + if (rn == NULL) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: rn is null!", __func__); +#endif + return; + } + + /* + * Get sl to attach to + */ + sl = RFAPI_MONITOR_ETH_W_ALLOC(rn); + if (!sl) { + sl = RFAPI_MONITOR_ETH_W_ALLOC(rn) = + skiplist_new(0, NULL, NULL); + agg_lock_node(rn); /* count skiplist mon_eth */ + } + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: rn=%p, rn->lock=%d, sl=%p, attaching eth mon %p", __func__, + rn, rn->lock, sl, mon); +#endif + + rc = skiplist_insert(sl, (void *)mon, (void *)mon); + assert(!rc); + + /* count eth monitor */ + agg_lock_node(rn); +} + +/* + * reattach monitors for this HD to import table + */ +static void rfapiMonitorEthAttachImportHd(struct bgp *bgp, + struct rfapi_descriptor *rfd) +{ + void *cursor; + struct rfapi_monitor_eth *mon; + int rc; + + if (!rfd->mon_eth) { + /* + * No monitors for this HD + */ + return; + } + + for (cursor = NULL, + rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon, &cursor); + rc == 0; + rc = skiplist_next(rfd->mon_eth, NULL, (void **)&mon, &cursor)) { + + struct rfapi_import_table *it; + struct prefix pfx_mac_buf; + struct agg_node *rn; + + it = rfapiMacImportTableGet(bgp, mon->logical_net_id); + assert(it); + + memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); + pfx_mac_buf.family = AF_ETHERNET; + pfx_mac_buf.prefixlen = 48; + pfx_mac_buf.u.prefix_eth = mon->macaddr; + + rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); + assert(rn); + + (void)rfapiMonitorEthAttachImport(it, rn, mon); + } +} + +static void rfapiMonitorEthDetachImport( + struct bgp *bgp, + struct rfapi_monitor_eth *mon) /* monitor struct to detach */ +{ + struct rfapi_import_table *it; + struct prefix pfx_mac_buf; + struct skiplist *sl; + struct agg_node *rn; + int rc; + + it = rfapiMacImportTableGet(bgp, mon->logical_net_id); + assert(it); + + if (RFAPI_0_ETHERADDR(&mon->macaddr)) { + struct rfapi_monitor_eth *prev; + struct rfapi_monitor_eth *this = NULL; + + for (prev = NULL, this = it->eth0_queries; this; + prev = this, this = this->next) { + + if (this == mon) + break; + } + if (this) { + if (!prev) { + it->eth0_queries = this->next; + } else { + prev->next = this->next; + } + } +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: it=%p, LNI=%d, detached eth0 mon %p", __func__, it, + mon->logical_net_id, mon); +#endif + return; + } + + memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); + pfx_mac_buf.family = AF_ETHERNET; + pfx_mac_buf.prefixlen = 48; + pfx_mac_buf.u.prefix_eth = mon->macaddr; + + rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); + assert(rn); + + /* + * Get sl to detach from + */ + sl = RFAPI_MONITOR_ETH(rn); +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: it=%p, rn=%p, rn->lock=%d, sl=%p, pfx=%pFX, LNI=%d, detaching eth mon %p", + __func__, it, rn, rn->lock, sl, agg_node_get_prefix(rn), + mon->logical_net_id, mon); +#endif + assert(sl); + + + rc = skiplist_delete(sl, (void *)mon, (void *)mon); + assert(!rc); + + /* uncount eth monitor */ + agg_unlock_node(rn); +} + +struct agg_node *rfapiMonitorEthAdd(struct bgp *bgp, + struct rfapi_descriptor *rfd, + struct ethaddr *macaddr, + uint32_t logical_net_id) +{ + int rc; + struct rfapi_monitor_eth mon_buf; + struct rfapi_monitor_eth *val; + struct rfapi_import_table *it; + struct agg_node *rn = NULL; + struct prefix pfx_mac_buf; + + if (!rfd->mon_eth) { + rfd->mon_eth = skiplist_new(0, mon_eth_cmp, NULL); + } + + it = rfapiMacImportTableGet(bgp, logical_net_id); + assert(it); + + /* + * Get route node in import table. Here is where we attach the + * monitor. + * + * Look it up now because we return it to caller regardless of + * whether we create a new monitor or not. + */ + memset((void *)&pfx_mac_buf, 0, sizeof(struct prefix)); + pfx_mac_buf.family = AF_ETHERNET; + pfx_mac_buf.prefixlen = 48; + pfx_mac_buf.u.prefix_eth = *macaddr; + + if (!RFAPI_0_ETHERADDR(macaddr)) { + rn = agg_node_get(it->imported_vpn[AFI_L2VPN], &pfx_mac_buf); + assert(rn); + } + + memset((void *)&mon_buf, 0, sizeof(mon_buf)); + mon_buf.rfd = rfd; + mon_buf.macaddr = *macaddr; + mon_buf.logical_net_id = logical_net_id; + + { + char buf[BUFSIZ]; + + vnc_zlog_debug_verbose( + "%s: LNI=%d: rfd=%p, pfx=%s", __func__, logical_net_id, + rfd, rfapi_ntop(pfx_mac_buf.family, pfx_mac_buf.u.val, + buf, BUFSIZ)); + } + + + /* + * look up query + */ + rc = skiplist_search(rfd->mon_eth, (void *)&mon_buf, (void **)&val); + if (!rc) { + /* + * Found monitor - we have seen this query before + * restart timer + */ + vnc_zlog_debug_verbose( + "%s: already present in rfd->mon_eth, not adding", + __func__); + rfapiMonitorEthTimerRestart(val); + return rn; + } + + /* + * New query + */ + val = XCALLOC(MTYPE_RFAPI_MONITOR_ETH, + sizeof(struct rfapi_monitor_eth)); + assert(val); + *val = mon_buf; + + ++rfd->monitor_count; + ++bgp->rfapi->monitor_count; + + rc = skiplist_insert(rfd->mon_eth, val, val); + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: inserted rfd=%p mon_eth=%p, rc=%d", + __func__, rfd, val, rc); +#else + (void)rc; +#endif + + /* + * start timer + */ + rfapiMonitorEthTimerRestart(val); + + if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { +/* + * callbacks turned off, so don't attach monitor to import table + */ +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: callbacks turned off, not attaching mon_eth %p to import table", + __func__, val); +#endif + return rn; + } + + /* + * attach to import table + */ + rfapiMonitorEthAttachImport(it, rn, val); + + return rn; +} + +void rfapiMonitorEthDel(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct ethaddr *macaddr, uint32_t logical_net_id) +{ + struct rfapi_monitor_eth *val; + struct rfapi_monitor_eth mon_buf; + int rc; + + vnc_zlog_debug_verbose("%s: entry rfd=%p", __func__, rfd); + + assert(rfd->mon_eth); + + memset((void *)&mon_buf, 0, sizeof(mon_buf)); + mon_buf.macaddr = *macaddr; + mon_buf.logical_net_id = logical_net_id; + + rc = skiplist_search(rfd->mon_eth, (void *)&mon_buf, (void **)&val); + assert(!rc); + + /* + * remove from import table + */ + if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + rfapiMonitorEthDetachImport(bgp, val); + } + + EVENT_OFF(val->timer); + + /* + * remove from rfd list + */ + rc = skiplist_delete(rfd->mon_eth, val, val); + assert(!rc); + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: freeing mon_eth %p", __func__, val); +#endif + XFREE(MTYPE_RFAPI_MONITOR_ETH, val); + + --rfd->monitor_count; + --bgp->rfapi->monitor_count; +} + + +void rfapiMonitorCallbacksOff(struct bgp *bgp) +{ + struct rfapi_import_table *it; + afi_t afi; + struct agg_table *rt; + struct agg_node *rn; + void *cursor; + int rc; + struct rfapi *h = bgp->rfapi; + + if (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE) { + /* + * Already off. + */ + return; + } + bgp->rfapi_cfg->flags |= BGP_VNC_CONFIG_CALLBACK_DISABLE; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: turned off callbacks", __func__); +#endif + + if (h == NULL) + return; + /* + * detach monitors from import VPN tables. The monitors + * will still be linked in per-nve monitor lists. + */ + for (it = h->imports; it; it = it->next) { + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct rfapi_monitor_vpn *m; + struct rfapi_monitor_vpn *next; + + rt = it->imported_vpn[afi]; + + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + m = RFAPI_MONITOR_VPN(rn); + if (RFAPI_MONITOR_VPN(rn)) + RFAPI_MONITOR_VPN_W_ALLOC(rn) = NULL; + for (; m; m = next) { + next = m->next; + m->next = + NULL; /* gratuitous safeness */ + m->node = NULL; + agg_unlock_node(rn); /* uncount */ + } + } + + for (m = it->vpn0_queries[afi]; m; m = next) { + next = m->next; + m->next = NULL; /* gratuitous safeness */ + m->node = NULL; + } + it->vpn0_queries[afi] = NULL; /* detach first monitor */ + } + } + + /* + * detach monitors from import Eth tables. The monitors + * will still be linked in per-nve monitor lists. + */ + + /* + * Loop over ethernet import tables + */ + for (cursor = NULL, + rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor); + !rc; + rc = skiplist_next(h->import_mac, NULL, (void **)&it, &cursor)) { + struct rfapi_monitor_eth *e; + struct rfapi_monitor_eth *enext; + + /* + * The actual route table + */ + rt = it->imported_vpn[AFI_L2VPN]; + + /* + * Find non-0 monitors (i.e., actual addresses, not FTD + * monitors) + */ + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + struct skiplist *sl; + + sl = RFAPI_MONITOR_ETH(rn); + while (!skiplist_delete_first(sl)) { + agg_unlock_node(rn); /* uncount monitor */ + } + } + + /* + * Find 0-monitors (FTD queries) + */ + for (e = it->eth0_queries; e; e = enext) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: detaching eth0 mon %p", + __func__, e); +#endif + enext = e->next; + e->next = NULL; /* gratuitous safeness */ + } + it->eth0_queries = NULL; /* detach first monitor */ + } +} + +void rfapiMonitorCallbacksOn(struct bgp *bgp) +{ + struct listnode *hnode; + struct rfapi_descriptor *rfd; + + if (!(bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_CALLBACK_DISABLE)) { + /* + * Already on. It's important that we don't try to reattach + * monitors that are already attached because, in the interest + * of performance, there is no checking at the lower level + * whether a monitor is already attached. It leads to + * corrupted chains (e.g., looped pointers) + */ + return; + } + bgp->rfapi_cfg->flags &= ~BGP_VNC_CONFIG_CALLBACK_DISABLE; +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: turned on callbacks", __func__); +#endif + if (bgp->rfapi == NULL) + return; + + /* + * reattach monitors + */ + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, hnode, rfd)) { + + rfapiMonitorAttachImportHd(rfd); + rfapiMonitorEthAttachImportHd(bgp, rfd); + } +} diff --git a/bgpd/rfapi/rfapi_monitor.h b/bgpd/rfapi/rfapi_monitor.h new file mode 100644 index 0000000..3200079 --- /dev/null +++ b/bgpd/rfapi/rfapi_monitor.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef QUAGGA_HGP_RFAPI_MONITOR_H +#define QUAGGA_HGP_RFAPI_MONITOR_H + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/table.h" + +/* + * These get attached to the nodes in an import table (using "aggregate" ptr) + * to indicate which nves are interested in a prefix/target + */ +struct rfapi_monitor_vpn { + struct rfapi_monitor_vpn *next; /* chain from struct agg_node */ + struct rfapi_descriptor *rfd; /* which NVE requested the route */ + struct prefix p; /* constant: pfx in original request */ + struct agg_node *node; /* node we're currently attached to */ + uint32_t flags; +#define RFAPI_MON_FLAG_NEEDCALLBACK 0x00000001 /* deferred callback */ + + // int dcount; /* debugging counter */ + struct event *timer; +}; + +struct rfapi_monitor_encap { + struct rfapi_monitor_encap *next; + struct rfapi_monitor_encap *prev; + struct agg_node *node; /* VPN node */ + struct bgp_path_info *bpi; /* VPN bpi */ + struct agg_node *rn; /* parent node */ +}; + +struct rfapi_monitor_eth { + struct rfapi_monitor_eth *next; /* for use in vpn0_queries list */ + struct rfapi_descriptor *rfd; /* which NVE requested the route */ + struct ethaddr macaddr; + uint32_t logical_net_id; + struct event *timer; +}; + +/* + * This is referenced by the "aggregate" field of a route node + * in an RFAPI import table. + * + * node lock/unlock: + * - one lock increment for this structure itself + * - one lock per chained struct rfapi_monitor_vpn + * - one lock for the mon_eth skiplist itself + * - one lock per mon_eth skiplist entry + * - one lock for the ext skiplist itself + * - one lock for each ext skiplist entry + * remember to free skiplist when freeing rfapi_it_extra + * - one lock per chained struct rfapi_monitor_encap + * + */ +struct rfapi_it_extra { + union { + struct { + struct rfapi_monitor_vpn *v; + struct skiplist *idx_rd; /* RD index */ + struct skiplist *mon_eth; /* ether queries */ + struct { + /* routes with UN addrs, either cached encap or + * Encap TLV */ + int valid_interior_count; + + /* unicast exterior routes, key=bpi, + * val=allocated prefix */ + struct skiplist *source; + } e; + } vpn; + struct { + struct rfapi_monitor_encap *e; + } encap; + } u; +}; + +#define RFAPI_IT_EXTRA_GET(rn) \ + ((struct rfapi_it_extra \ + *)((rn)->aggregate \ + ? (rn)->aggregate \ + : (agg_lock_node(rn), \ + (rn)->aggregate = XCALLOC( \ + MTYPE_RFAPI_IT_EXTRA, \ + sizeof(struct rfapi_it_extra))))) + +#define RFAPI_RDINDEX(rn) \ + ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd : NULL) + +#define RFAPI_RDINDEX_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.idx_rd) + +#define RFAPI_MONITOR_ETH(rn) \ + ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.mon_eth : NULL) + +#define RFAPI_MONITOR_ETH_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.mon_eth) + +#define RFAPI_MONITOR_VPN(rn) \ + ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.vpn.v : NULL) + +#define RFAPI_MONITOR_VPN_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.vpn.v) + +#define RFAPI_MONITOR_ENCAP(rn) \ + ((rn)->aggregate ? RFAPI_IT_EXTRA_GET(rn)->u.encap.e : NULL) + +#define RFAPI_MONITOR_ENCAP_W_ALLOC(rn) (RFAPI_IT_EXTRA_GET(rn)->u.encap.e) + +#define RFAPI_MONITOR_EXTERIOR(rn) (&(RFAPI_IT_EXTRA_GET(rn)->u.vpn.e)) + +#define RFAPI_HAS_MONITOR_EXTERIOR(rn) \ + (rn && rn->aggregate \ + && ((struct rfapi_it_extra *)(rn->aggregate))->u.vpn.e.source \ + && !skiplist_first(((struct rfapi_it_extra *)(rn->aggregate)) \ + ->u.vpn.e.source, \ + NULL, NULL)) + +extern void rfapiMonitorLoopCheck(struct rfapi_monitor_vpn *mchain); + +extern void rfapiMonitorCleanCheck(struct bgp *bgp); + +extern void rfapiMonitorCheckAttachAllowed(void); + +extern void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn); + +extern struct agg_node *rfapiMonitorGetAttachNode(struct rfapi_descriptor *rfd, + struct prefix *p); + +extern void rfapiMonitorAttachImportHd(struct rfapi_descriptor *rfd); + +extern struct agg_node *rfapiMonitorAdd(struct bgp *bgp, + struct rfapi_descriptor *rfd, + struct prefix *p); + +extern void rfapiMonitorDetachImportHd(struct rfapi_descriptor *rfd); + +extern void rfapiMonitorDel(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct prefix *p); + +extern int rfapiMonitorDelHd(struct rfapi_descriptor *rfd); + +extern void rfapiMonitorCallbacksOff(struct bgp *bgp); + +extern void rfapiMonitorCallbacksOn(struct bgp *bgp); + +extern void rfapiMonitorResponseRemovalOff(struct bgp *bgp); + +extern void rfapiMonitorResponseRemovalOn(struct bgp *bgp); + +extern void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn); + +extern void rfapiMonitorTimersRestart(struct rfapi_descriptor *rfd, + const struct prefix *p); + +extern void rfapiMonitorItNodeChanged(struct rfapi_import_table *import_table, + struct agg_node *it_node, + struct rfapi_monitor_vpn *monitor_list); + +extern void rfapiMonitorMovedUp(struct rfapi_import_table *import_table, + struct agg_node *old_node, + struct agg_node *new_node, + struct rfapi_monitor_vpn *monitor_list); + +extern struct agg_node *rfapiMonitorEthAdd(struct bgp *bgp, + struct rfapi_descriptor *rfd, + struct ethaddr *macaddr, + uint32_t logical_net_id); + +extern void rfapiMonitorEthDel(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct ethaddr *macaddr, + uint32_t logical_net_id); + +#endif /* QUAGGA_HGP_RFAPI_MONITOR_H */ diff --git a/bgpd/rfapi/rfapi_nve_addr.c b/bgpd/rfapi/rfapi_nve_addr.c new file mode 100644 index 0000000..eabec2f --- /dev/null +++ b/bgpd/rfapi/rfapi_nve_addr.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/skiplist.h" + + +#include "bgpd/bgpd.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_backend.h" + +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_nve_addr.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define DEBUG_NVE_ADDR 0 + +void rfapiNveAddr2Str(struct rfapi_nve_addr *, char *, int); + + +#if DEBUG_NVE_ADDR +static void logdifferent(const char *tag, struct rfapi_nve_addr *a, + struct rfapi_nve_addr *b) +{ + char a_str[BUFSIZ]; + char b_str[BUFSIZ]; + + rfapiNveAddr2Str(a, a_str, BUFSIZ); + rfapiNveAddr2Str(b, b_str, BUFSIZ); + vnc_zlog_debug_verbose("%s: [%s] [%s]", tag, a_str, b_str); +} +#endif + + +int rfapi_nve_addr_cmp(const void *k1, const void *k2) +{ + const struct rfapi_nve_addr *a = (struct rfapi_nve_addr *)k1; + const struct rfapi_nve_addr *b = (struct rfapi_nve_addr *)k2; + int ret = 0; + + if (!a || !b) { +#if DEBUG_NVE_ADDR + vnc_zlog_debug_verbose("%s: missing address a=%p b=%p", + __func__, a, b); +#endif + return (a - b); + } + if (a->un.addr_family != b->un.addr_family) { +#if DEBUG_NVE_ADDR + vnc_zlog_debug_verbose( + "diff: UN addr fam a->un.af=%d, b->un.af=%d", + a->un.addr_family, b->un.addr_family); +#endif + return (a->un.addr_family - b->un.addr_family); + } + if (a->un.addr_family == AF_INET) { + ret = IPV4_ADDR_CMP(&a->un.addr.v4, &b->un.addr.v4); + if (ret != 0) { +#if DEBUG_NVE_ADDR + logdifferent("diff: UN addr", a, b); +#endif + return ret; + } + } else if (a->un.addr_family == AF_INET6) { + ret = IPV6_ADDR_CMP(&a->un.addr.v6, &b->un.addr.v6); + if (ret == 0) { +#if DEBUG_NVE_ADDR + logdifferent("diff: UN addr", a, b); +#endif + return ret; + } + } else { + assert(0); + } + if (a->vn.addr_family != b->vn.addr_family) { +#if DEBUG_NVE_ADDR + vnc_zlog_debug_verbose( + "diff: pT addr fam a->vn.af=%d, b->vn.af=%d", + a->vn.addr_family, b->vn.addr_family); +#endif + return (a->vn.addr_family - b->vn.addr_family); + } + if (a->vn.addr_family == AF_INET) { + ret = IPV4_ADDR_CMP(&a->vn.addr.v4, &b->vn.addr.v4); + if (ret != 0) { +#if DEBUG_NVE_ADDR + logdifferent("diff: VN addr", a, b); +#endif + return ret; + } + } else if (a->vn.addr_family == AF_INET6) { + ret = IPV6_ADDR_CMP(&a->vn.addr.v6, &b->vn.addr.v6); + if (ret == 0) { +#if DEBUG_NVE_ADDR + logdifferent("diff: VN addr", a, b); +#endif + return ret; + } + } else { + assert(0); + } + return 0; +} + +void rfapiNveAddr2Str(struct rfapi_nve_addr *na, char *buf, int bufsize) +{ + char *p = buf; + int r; + +#define REMAIN (bufsize - (p-buf)) +#define INCP {p += (r > REMAIN)? REMAIN: r;} + + if (bufsize < 1) + return; + + r = snprintf(p, REMAIN, "VN="); + INCP; + + if (!rfapiRfapiIpAddr2Str(&na->vn, p, REMAIN)) + goto done; + + buf[bufsize - 1] = 0; + p = buf + strlen(buf); + + r = snprintf(p, REMAIN, ", UN="); + INCP; + + rfapiRfapiIpAddr2Str(&na->un, p, REMAIN); + +done: + buf[bufsize - 1] = 0; +} diff --git a/bgpd/rfapi/rfapi_nve_addr.h b/bgpd/rfapi/rfapi_nve_addr.h new file mode 100644 index 0000000..49e9fc5 --- /dev/null +++ b/bgpd/rfapi/rfapi_nve_addr.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_BGP_RFAPI_NVE_ADDR_H +#define _QUAGGA_BGP_RFAPI_NVE_ADDR_H + +#include "rfapi.h" + +struct rfapi_nve_addr { + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + void *info; +}; + + +extern int rfapi_nve_addr_cmp(const void *k1, const void *k2); + +extern void rfapiNveAddr2Str(struct rfapi_nve_addr *na, char *buf, int bufsize); + + +#endif /* _QUAGGA_BGP_RFAPI_NVE_ADDR_H */ diff --git a/bgpd/rfapi/rfapi_private.h b/bgpd/rfapi/rfapi_private.h new file mode 100644 index 0000000..2161d43 --- /dev/null +++ b/bgpd/rfapi/rfapi_private.h @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * Internal definitions for RFAPI. Not for use by other code + */ + +#ifndef _QUAGGA_BGP_RFAPI_PRIVATE_H +#define _QUAGGA_BGP_RFAPI_PRIVATE_H + +#include "lib/linklist.h" +#include "lib/skiplist.h" +#include "lib/workqueue.h" + +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" + +#include "rfapi.h" + +/* + * Lists of rfapi_adb. Each rfapi_adb is referenced twice: + * + * 1. each is referenced in by_lifetime + * 2. each is referenced by exactly one of: ipN_by_prefix, ip0_by_ether + */ +struct rfapi_advertised_prefixes { + struct skiplist *ipN_by_prefix; /* all except 0/32, 0/128 */ + struct skiplist *ip0_by_ether; /* ip prefix 0/32, 0/128 */ + struct skiplist *by_lifetime; /* all */ +}; + +struct rfapi_descriptor { + struct agg_node *un_node; /* backref to un table */ + + struct rfapi_descriptor *next; /* next vn_addr */ + + /* supplied by client */ + struct bgp *bgp; /* from rfp_start_val */ + struct rfapi_ip_addr vn_addr; + struct rfapi_ip_addr un_addr; + rfapi_response_cb_t *response_cb; /* override per-bgp response_cb */ + void *cookie; /* for callbacks */ + struct rfapi_tunneltype_option default_tunneltype_option; + + /* supplied by matched configuration */ + struct prefix_rd rd; + struct ecommunity *rt_export_list; + uint32_t response_lifetime; + + /* list of prefixes currently being advertised by this nve */ + struct rfapi_advertised_prefixes advertised; + + time_t open_time; + + uint32_t max_prefix_lifetime; + uint32_t min_prefix_lifetime; + + /* reference to this nve's import table */ + struct rfapi_import_table *import_table; + + uint32_t monitor_count; + struct agg_table *mon; /* rfapi_monitors */ + struct skiplist *mon_eth; /* ethernet monitors */ + + /* + * rib RIB as seen by NVE + * rib_pending RIB containing nodes with updated info chains + * rsp_times last time we sent response containing pfx + */ + uint32_t rib_prefix_count; /* pfxes with routes */ + struct agg_table *rib[AFI_MAX]; + struct agg_table *rib_pending[AFI_MAX]; + struct work_queue *updated_responses_queue; + struct agg_table *rsp_times[AFI_MAX]; + + uint32_t rsp_counter; /* dedup initial rsp */ + time_t rsp_time; /* dedup initial rsp */ + time_t ftd_last_allowed_time; /* FTD filter */ + + unsigned int stat_count_nh_reachable; + unsigned int stat_count_nh_removal; + + /* + * points to the original nve group structure that matched + * when this nve_descriptor was created. We use this pointer + * in rfapi_close() to find the nve group structure and + * delete its reference back to us. + * + * If the nve group structure is deleted (via configuration + * change) while this nve_descriptor exists, this rfg pointer + * will be set to NULL. + */ + struct rfapi_nve_group_cfg *rfg; + + /* + * This ~7kB structure is here to permit multiple routes for + * a prefix to be injected to BGP. There are at least two + * situations where such conditions obtain: + * + * When an VNC route is exported to BGP on behalf of the set of + * NVEs that belong to the export NVE group, it is replicated + * so that there is one route per NVE (and the route's nexthop + * is the NVE's VN address). + * + * Each of these routes being injected to BGP must have a distinct + * peer pointer (otherwise, if they have the same peer pointer, each + * route will be considered an implicit waithdraw of the previous + * route injected from that peer, and the new route will replace + * rather than augment the old one(s)). + */ + struct peer *peer; + + uint32_t flags; +#define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP 0x00000001 +#define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP6 0x00000002 +#define RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_L2VPN 0x00000004 +#define RFAPI_HD_FLAG_PROVISIONAL 0x00000008 +#define RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY 0x00000010 +#define RFAPI_HD_FLAG_IS_VRF 0x00000012 +}; + +#define RFAPI_QUEUED_FLAG(afi) \ + (((afi) == AFI_IP) \ + ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP \ + : (((afi) == AFI_IP6) \ + ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_IP6 \ + : (((afi) == AFI_L2VPN) \ + ? RFAPI_HD_FLAG_CALLBACK_SCHEDULED_AFI_L2VPN \ + : (assert(0), 0)))) + + +struct rfapi_global_stats { + time_t last_reset; + unsigned int max_descriptors; + + unsigned int count_unknown_nves; + + unsigned int count_queries; + unsigned int count_queries_failed; + + unsigned int max_responses; /* semantics? */ + + unsigned int count_registrations; + unsigned int count_registrations_failed; + + unsigned int count_updated_response_updates; + unsigned int count_updated_response_deletes; +}; + +/* + * There is one of these per BGP instance. + * + * Radix tree is indexed by un address; follow chain and + * check vn address to get exact match. + */ +struct rfapi { + struct agg_table *un[AFI_MAX]; + struct rfapi_import_table *imports; /* IPv4, IPv6 */ + struct list descriptors; /* debug & resolve-nve imports */ + + struct rfapi_global_stats stat; + + /* + * callbacks into RFP, set at startup time (bgp_rfapi_new() gets + * values from rfp_start()) or via rfapi_rfp_set_cb_methods() + * (otherwise NULL). Note that the response_cb method can also + * be overridden per-rfd (currently used only for debug/test scenarios) + */ + struct rfapi_rfp_cb_methods rfp_methods; + + /* + * Import tables for Ethernet over IPSEC + * + * The skiplist keys are LNIs. Values are pointers + * to struct rfapi_import_table. + */ + struct skiplist *import_mac; /* L2 */ + + /* + * when exporting plain routes ("registered-nve" mode) to + * bgp unicast or zebra, we need to keep track of information + * related to expiring the routes according to the VNC lifetime + */ + struct agg_table *rt_export_bgp[AFI_MAX]; + struct agg_table *rt_export_zebra[AFI_MAX]; + + /* + * For VNC->BGP unicast exports in CE mode, we need a + * routing table that collects all of the VPN routes + * in a single tree. The VPN rib is split up according + * to RD first, so we can't use that. This is an import + * table that matches all RTs. + */ + struct rfapi_import_table *it_ce; + + /* + * when importing bgp-direct routes in resolve-nve mode, + * this list maps unicast route nexthops to their bgp_path_infos + * in the unicast table + */ + struct skiplist *resolve_nve_nexthop; + + /* + * Descriptors for which rfapi_close() was called during a callback. + * They will be closed after the callback finishes. + */ + struct work_queue *deferred_close_q; + + /* + * For "show vnc responses" + */ + uint32_t response_immediate_count; + uint32_t response_updated_count; + uint32_t monitor_count; + + uint32_t rib_prefix_count_total; + uint32_t rib_prefix_count_total_max; + + uint32_t flags; +#define RFAPI_INCALLBACK 0x00000001 + void *rfp; /* from rfp_start */ +}; + +#define RFAPI_RIB_PREFIX_COUNT_INCR(rfd, rfapi) \ + do { \ + ++(rfd)->rib_prefix_count; \ + ++(rfapi)->rib_prefix_count_total; \ + if ((rfapi)->rib_prefix_count_total \ + > (rfapi)->rib_prefix_count_total_max) \ + ++(rfapi)->rib_prefix_count_total_max; \ + } while (0) + +#define RFAPI_RIB_PREFIX_COUNT_DECR(rfd, rfapi) \ + do { \ + --(rfd)->rib_prefix_count; \ + --(rfapi)->rib_prefix_count_total; \ + } while (0) + +#define RFAPI_0_PREFIX(prefix) \ + ((((prefix)->family == AF_INET) \ + ? (prefix)->u.prefix4.s_addr == INADDR_ANY \ + : (((prefix)->family == AF_INET6) \ + ? (IN6_IS_ADDR_UNSPECIFIED(&(prefix)->u.prefix6)) \ + : 0))) + +#define RFAPI_0_ETHERADDR(ea) \ + (((ea)->octet[0] | (ea)->octet[1] | (ea)->octet[2] | (ea)->octet[3] \ + | (ea)->octet[4] | (ea)->octet[5]) \ + == 0) + +#define RFAPI_HOST_PREFIX(prefix) \ + (((prefix)->family == AF_INET) \ + ? ((prefix)->prefixlen == IPV4_MAX_BITLEN) \ + : (((prefix)->family == AF_INET6) \ + ? ((prefix)->prefixlen == IPV6_MAX_BITLEN) \ + : 0)) + +extern int rfapi_find_rfd(struct bgp *bgp, struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_addr *un_addr, + struct rfapi_descriptor **rfd); + +extern void +add_vnc_route(struct rfapi_descriptor *rfd, /* cookie + UN addr for VPN */ + struct bgp *bgp, int safi, const struct prefix *p, + struct prefix_rd *prd, struct rfapi_ip_addr *nexthop, + uint32_t *local_pref, /* host byte order */ + uint32_t *lifetime, /* host byte order */ + struct bgp_tea_options *rfp_options, + struct rfapi_un_option *options_un, + struct rfapi_vn_option *options_vn, + struct ecommunity *rt_export_list, uint32_t *med, uint32_t *label, + uint8_t type, uint8_t sub_type, int flags); +#define RFAPI_AHR_NO_TUNNEL_SUBTLV 0x00000001 +#define RFAPI_AHR_RFPOPT_IS_VNCTLV 0x00000002 /* hack! */ + +extern void del_vnc_route(struct rfapi_descriptor *rfd, struct peer *peer, + struct bgp *bgp, safi_t safi, const struct prefix *p, + struct prefix_rd *prd, uint8_t type, uint8_t sub_type, + struct rfapi_nexthop *lnh, int kill); + +extern int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, + struct prefix *p); + +extern int rfapiGetVncLifetime(struct attr *attr, uint32_t *lifetime); + +extern int rfapiGetVncTunnelUnAddr(struct attr *attr, struct prefix *p); + +extern int rfapi_reopen(struct rfapi_descriptor *rfd, struct bgp *bgp); + +extern void vnc_import_bgp_add_rfp_host_route_mode_resolve_nve( + struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *prefix); + +extern void vnc_import_bgp_del_rfp_host_route_mode_resolve_nve( + struct bgp *bgp, struct rfapi_descriptor *rfd, struct prefix *prefix); + +extern void rfapiFreeBgpTeaOptionChain(struct bgp_tea_options *p); + +extern struct rfapi_vn_option *rfapiVnOptionsDup(struct rfapi_vn_option *orig); + +extern struct rfapi_un_option *rfapiUnOptionsDup(struct rfapi_un_option *orig); + +extern struct bgp_tea_options *rfapiOptionsDup(struct bgp_tea_options *orig); + +extern int rfapi_ip_addr_cmp(struct rfapi_ip_addr *a1, + struct rfapi_ip_addr *a2); + +extern uint32_t rfp_cost_to_localpref(uint8_t cost); + +extern int rfapi_set_autord_from_vn(struct prefix_rd *rd, + struct rfapi_ip_addr *vn); + +extern struct rfapi_nexthop *rfapi_nexthop_new(struct rfapi_nexthop *copyme); + +extern void rfapi_nexthop_free(void *goner); + +extern struct rfapi_vn_option * +rfapi_vn_options_dup(struct rfapi_vn_option *existing); + +extern void rfapi_un_options_free(struct rfapi_un_option *goner); + +extern void rfapi_vn_options_free(struct rfapi_vn_option *goner); + +extern void vnc_add_vrf_opener(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg); +extern void clear_vnc_vrf_closer(struct rfapi_nve_group_cfg *rfg); +/*------------------------------------------ + * rfapi_extract_l2o + * + * Find Layer 2 options in an option chain + * + * input: + * pHop option chain + * + * output: + * l2o layer 2 options extracted + * + * return value: + * 0 OK + * 1 no options found + * + --------------------------------------------*/ +extern int rfapi_extract_l2o( + struct bgp_tea_options *pHop, /* chain of options */ + struct rfapi_l2address_option *l2o); /* return extracted value */ + +/* + * compaitibility to old quagga_time call + * time_t value in terms of stabilised absolute time. + * replacement for POSIX time() + * + * Please do not use this. This is kept only for + * Lou's CI in that that CI compiles against some + * private bgp code and it will just fail to compile + * without this. Use monotime() + */ +extern time_t rfapi_time(time_t *t); + +DECLARE_MGROUP(RFAPI); +DECLARE_MTYPE(RFAPI_CFG); +DECLARE_MTYPE(RFAPI_GROUP_CFG); +DECLARE_MTYPE(RFAPI_L2_CFG); +DECLARE_MTYPE(RFAPI_RFP_GROUP_CFG); +DECLARE_MTYPE(RFAPI); +DECLARE_MTYPE(RFAPI_DESC); +DECLARE_MTYPE(RFAPI_IMPORTTABLE); +DECLARE_MTYPE(RFAPI_MONITOR); +DECLARE_MTYPE(RFAPI_MONITOR_ENCAP); +DECLARE_MTYPE(RFAPI_NEXTHOP); +DECLARE_MTYPE(RFAPI_VN_OPTION); +DECLARE_MTYPE(RFAPI_UN_OPTION); +DECLARE_MTYPE(RFAPI_WITHDRAW); +DECLARE_MTYPE(RFAPI_RFG_NAME); +DECLARE_MTYPE(RFAPI_ADB); +DECLARE_MTYPE(RFAPI_ETI); +DECLARE_MTYPE(RFAPI_NVE_ADDR); +DECLARE_MTYPE(RFAPI_PREFIX_BAG); +DECLARE_MTYPE(RFAPI_IT_EXTRA); +DECLARE_MTYPE(RFAPI_INFO); +DECLARE_MTYPE(RFAPI_ADDR); +DECLARE_MTYPE(RFAPI_UPDATED_RESPONSE_QUEUE); +DECLARE_MTYPE(RFAPI_RECENT_DELETE); +DECLARE_MTYPE(RFAPI_L2ADDR_OPT); +DECLARE_MTYPE(RFAPI_AP); +DECLARE_MTYPE(RFAPI_MONITOR_ETH); + + +/* + * Caller must supply an already-allocated rfd with the "caller" + * fields already set (vn_addr, un_addr, callback, cookie) + * The advertised_prefixes[] array elements should be NULL to + * have this function set them to newly-allocated radix trees. + */ +extern int rfapi_init_and_open(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_nve_group_cfg *rfg); + +#endif /* _QUAGGA_BGP_RFAPI_PRIVATE_H */ diff --git a/bgpd/rfapi/rfapi_rib.c b/bgpd/rfapi/rfapi_rib.c new file mode 100644 index 0000000..53e416b --- /dev/null +++ b/bgpd/rfapi/rfapi_rib.c @@ -0,0 +1,2428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: rfapi_rib.c + * Purpose: maintain per-nve ribs and generate change lists + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/log.h" +#include "lib/skiplist.h" +#include "lib/workqueue.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_vnc_types.h" + +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/rfapi_rib.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_encap_tlv.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define DEBUG_PROCESS_PENDING_NODE 0 +#define DEBUG_PENDING_DELETE_ROUTE 0 +#define DEBUG_NHL 0 +#define DEBUG_RIB_SL_RD 0 +#define DEBUG_CLEANUP 0 + +/* forward decl */ +#if DEBUG_NHL +static void rfapiRibShowRibSl(void *stream, struct prefix *pfx, + struct skiplist *sl); +#endif + +/* + * RIB + * --- + * Model of the set of routes currently in the NVE's RIB. + * + * node->info ptr to "struct skiplist". + * MUST be NULL if there are no routes. + * key = ptr to struct prefix {vn} + * val = ptr to struct rfapi_info + * skiplist.del = NULL + * skiplist.cmp = vnc_prefix_cmp + * + * node->aggregate ptr to "struct skiplist". + * key = ptr to struct prefix {vn} + * val = ptr to struct rfapi_info + * skiplist.del = rfapi_info_free + * skiplist.cmp = vnc_prefix_cmp + * + * This skiplist at "aggregate" + * contains the routes recently + * deleted + * + * + * Pending RIB + * ----------- + * Sparse list of prefixes that need to be updated. Each node + * will have the complete set of routes for the prefix. + * + * node->info ptr to "struct list" (lib/linklist.h) + * "Cost List" + * List of routes sorted lowest cost first. + * This list is how the new complete set + * of routes should look. + * Set if there are updates to the prefix; + * MUST be NULL if there are no updates. + * + * .data = ptr to struct rfapi_info + * list.cmp = NULL (sorted manually) + * list.del = rfapi_info_free + * + * Special case: if node->info is 1, it means + * "delete all routes at this prefix". + * + * node->aggregate ptr to struct skiplist + * key = ptr to struct prefix {vn} (part of ri) + * val = struct rfapi_info + * skiplist.cmp = vnc_prefix_cmp + * skiplist.del = NULL + * + * ptlist is rewritten anew each time + * rfapiRibUpdatePendingNode() is called + * + * THE ptlist VALUES ARE REFERENCES TO THE + * rfapi_info STRUCTS IN THE node->info LIST. + */ + +/* + * iterate over RIB to count responses, compare with running counters + */ +void rfapiRibCheckCounts( + int checkstats, /* validate rfd & global counts */ + unsigned int offset) /* number of ri's held separately */ +{ + struct rfapi_descriptor *rfd; + struct listnode *node; + + struct bgp *bgp = bgp_get_default(); + + uint32_t t_pfx_active = 0; + + uint32_t t_ri_active = 0; + uint32_t t_ri_deleted = 0; + uint32_t t_ri_pend = 0; + + unsigned int alloc_count; + + /* + * loop over NVEs + */ + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { + + afi_t afi; + uint32_t pfx_active = 0; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_node *rn; + + for (rn = agg_route_top(rfd->rib[afi]); rn; + rn = agg_route_next(rn)) { + + struct skiplist *sl = rn->info; + struct skiplist *dsl = rn->aggregate; + uint32_t ri_active = 0; + uint32_t ri_deleted = 0; + + if (sl) { + ri_active = skiplist_count(sl); + assert(ri_active); + t_ri_active += ri_active; + ++pfx_active; + ++t_pfx_active; + } + + if (dsl) { + ri_deleted = skiplist_count(dsl); + t_ri_deleted += ri_deleted; + } + } + for (rn = agg_route_top(rfd->rib_pending[afi]); rn; + rn = agg_route_next(rn)) { + + struct list *l = rn->info; /* sorted by cost */ + struct skiplist *sl = rn->aggregate; + uint32_t ri_pend_cost = 0; + uint32_t ri_pend_uniq = 0; + + if (sl) { + ri_pend_uniq = skiplist_count(sl); + } + + if (l && (l != (void *)1)) { + ri_pend_cost = l->count; + t_ri_pend += l->count; + } + + assert(ri_pend_uniq == ri_pend_cost); + } + } + + if (checkstats) { + if (pfx_active != rfd->rib_prefix_count) { + vnc_zlog_debug_verbose( + "%s: rfd %p actual pfx count %u != running %u", + __func__, rfd, pfx_active, + rfd->rib_prefix_count); + assert(0); + } + } + } + + if (checkstats && bgp->rfapi) { + if (t_pfx_active != bgp->rfapi->rib_prefix_count_total) { + vnc_zlog_debug_verbose( + "%s: actual total pfx count %u != running %u", + __func__, t_pfx_active, + bgp->rfapi->rib_prefix_count_total); + assert(0); + } + } + + /* + * Check against memory allocation count + */ + alloc_count = mtype_stats_alloc(MTYPE_RFAPI_INFO); + assert(t_ri_active + t_ri_deleted + t_ri_pend + offset == alloc_count); +} + +static struct rfapi_info *rfapi_info_new(void) +{ + return XCALLOC(MTYPE_RFAPI_INFO, sizeof(struct rfapi_info)); +} + +void rfapiFreeRfapiUnOptionChain(struct rfapi_un_option *p) +{ + while (p) { + struct rfapi_un_option *next; + + next = p->next; + XFREE(MTYPE_RFAPI_UN_OPTION, p); + p = next; + } +} + +void rfapiFreeRfapiVnOptionChain(struct rfapi_vn_option *p) +{ + while (p) { + struct rfapi_vn_option *next; + + next = p->next; + XFREE(MTYPE_RFAPI_VN_OPTION, p); + p = next; + } +} + + +static void rfapi_info_free(struct rfapi_info *goner) +{ + if (goner) { + if (goner->tea_options) { + rfapiFreeBgpTeaOptionChain(goner->tea_options); + goner->tea_options = NULL; + } + if (goner->un_options) { + rfapiFreeRfapiUnOptionChain(goner->un_options); + goner->un_options = NULL; + } + if (goner->vn_options) { + rfapiFreeRfapiVnOptionChain(goner->vn_options); + goner->vn_options = NULL; + } + if (goner->timer) { + struct rfapi_rib_tcb *tcb; + + tcb = EVENT_ARG(goner->timer); + EVENT_OFF(goner->timer); + XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); + } + XFREE(MTYPE_RFAPI_INFO, goner); + } +} + +/* + * Timer control block for recently-deleted and expired routes + */ +struct rfapi_rib_tcb { + struct rfapi_descriptor *rfd; + struct skiplist *sl; + struct rfapi_info *ri; + struct agg_node *rn; + int flags; +#define RFAPI_RIB_TCB_FLAG_DELETED 0x00000001 +}; + +/* + * remove route from rib + */ +static void rfapiRibExpireTimer(struct event *t) +{ + struct rfapi_rib_tcb *tcb = EVENT_ARG(t); + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + /* + * Forget reference to thread. Otherwise rfapi_info_free() will + * attempt to free thread pointer as an option chain + */ + tcb->ri->timer = NULL; + + /* "deleted" skiplist frees ri, "active" doesn't */ + assert(!skiplist_delete(tcb->sl, &tcb->ri->rk, NULL)); + if (!tcb->sl->del) { + /* + * XXX in this case, skiplist has no delete function: we must + * therefore delete rfapi_info explicitly. + */ + rfapi_info_free(tcb->ri); + } + + if (skiplist_empty(tcb->sl)) { + if (CHECK_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED)) + tcb->rn->aggregate = NULL; + else { + struct bgp *bgp = bgp_get_default(); + tcb->rn->info = NULL; + RFAPI_RIB_PREFIX_COUNT_DECR(tcb->rfd, bgp->rfapi); + } + skiplist_free(tcb->sl); + agg_unlock_node(tcb->rn); + } + + XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); + + RFAPI_RIB_CHECK_COUNTS(1, 0); +} + +static void rfapiRibStartTimer(struct rfapi_descriptor *rfd, + struct rfapi_info *ri, + struct agg_node *rn, /* route node attached to */ + int deleted) +{ + struct rfapi_rib_tcb *tcb = NULL; + + if (ri->timer) { + tcb = EVENT_ARG(ri->timer); + EVENT_OFF(ri->timer); + } else { + tcb = XCALLOC(MTYPE_RFAPI_RECENT_DELETE, + sizeof(struct rfapi_rib_tcb)); + } +#if DEBUG_CLEANUP + zlog_debug("%s: rfd %p, rn %p, ri %p, tcb %p", __func__, rfd, rn, ri, + tcb); +#endif + + tcb->rfd = rfd; + tcb->ri = ri; + tcb->rn = rn; + if (deleted) { + tcb->sl = (struct skiplist *)rn->aggregate; + SET_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED); + } else { + tcb->sl = (struct skiplist *)rn->info; + UNSET_FLAG(tcb->flags, RFAPI_RIB_TCB_FLAG_DELETED); + } + + vnc_zlog_debug_verbose("%s: rfd %p pfx %pRN life %u", __func__, rfd, rn, + ri->lifetime); + + event_add_timer(bm->master, rfapiRibExpireTimer, tcb, ri->lifetime, + &ri->timer); +} + +extern void rfapi_rib_key_init(struct prefix *prefix, /* may be NULL */ + struct prefix_rd *rd, /* may be NULL */ + struct prefix *aux, /* may be NULL */ + struct rfapi_rib_key *rk) + +{ + memset((void *)rk, 0, sizeof(struct rfapi_rib_key)); + if (prefix) + rk->vn = *prefix; + if (rd) + rk->rd = *rd; + if (aux) + rk->aux_prefix = *aux; +} + +/* + * Compares two s + */ +int rfapi_rib_key_cmp(const void *k1, const void *k2) +{ + const struct rfapi_rib_key *a = (struct rfapi_rib_key *)k1; + const struct rfapi_rib_key *b = (struct rfapi_rib_key *)k2; + int ret; + + if (!a || !b) + return (a - b); + + ret = vnc_prefix_cmp(&a->vn, &b->vn); + if (ret) + return ret; + + ret = vnc_prefix_cmp(&a->rd, &b->rd); + if (ret) + return ret; + + ret = vnc_prefix_cmp(&a->aux_prefix, &b->aux_prefix); + + return ret; +} + + +/* + * Note: this function will claim that two option chains are + * different unless their option items are in identical order. + * The consequence is that RFP updated responses can be sent + * unnecessarily, or that they might contain nexthop items + * that are not strictly needed. + * + * This function could be modified to compare option chains more + * thoroughly, but it's not clear that the extra compuation would + * be worth it. + */ +static int bgp_tea_options_cmp(struct bgp_tea_options *a, + struct bgp_tea_options *b) +{ + int rc; + + if (!a || !b) { + return (a - b); + } + + if (a->type != b->type) + return (a->type - b->type); + if (a->length != b->length) + return (a->length = b->length); + if ((rc = memcmp(a->value, b->value, a->length))) + return rc; + if (!a->next != !b->next) { /* logical xor */ + return (a->next - b->next); + } + if (a->next) + return bgp_tea_options_cmp(a->next, b->next); + return 0; +} + +static int rfapi_info_cmp(struct rfapi_info *a, struct rfapi_info *b) +{ + int rc; + + if (!a || !b) + return (a - b); + + if ((rc = rfapi_rib_key_cmp(&a->rk, &b->rk))) + return rc; + + if ((rc = vnc_prefix_cmp(&a->un, &b->un))) + return rc; + + if (a->cost != b->cost) + return (a->cost - b->cost); + + if (a->lifetime != b->lifetime) + return (a->lifetime - b->lifetime); + + if ((rc = bgp_tea_options_cmp(a->tea_options, b->tea_options))) + return rc; + + return 0; +} + +void rfapiRibClear(struct rfapi_descriptor *rfd) +{ + struct bgp *bgp; + afi_t afi; + + if (rfd->bgp) + bgp = rfd->bgp; + else + bgp = bgp_get_default(); +#ifdef DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); +#endif + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + struct agg_node *pn; + struct agg_node *rn; + + if (rfd->rib_pending[afi]) { + for (pn = agg_route_top(rfd->rib_pending[afi]); pn; + pn = agg_route_next(pn)) { + if (pn->aggregate) { + /* + * free references into the rfapi_info + * structures before + * freeing the structures themselves + */ + skiplist_free( + (struct skiplist + *)(pn->aggregate)); + pn->aggregate = NULL; + agg_unlock_node( + pn); /* skiplist deleted */ + } + /* + * free the rfapi_info structures + */ + if (pn->info) { + if (pn->info != (void *)1) { + list_delete( + (struct list * + *)(&pn->info)); + } + pn->info = NULL; + /* linklist or 1 deleted */ + agg_unlock_node(pn); + } + } + } + if (rfd->rib[afi]) { + for (rn = agg_route_top(rfd->rib[afi]); rn; + rn = agg_route_next(rn)) { + if (rn->info) { + + struct rfapi_info *ri; + + while (0 == skiplist_first( + (struct skiplist *) + rn->info, + NULL, + (void **)&ri)) { + + if (ri->timer) { + struct rfapi_rib_tcb + *tcb; + + tcb = EVENT_ARG( + ri->timer); + EVENT_OFF(ri->timer); + XFREE(MTYPE_RFAPI_RECENT_DELETE, + tcb); + } + rfapi_info_free(ri); + skiplist_delete_first( + (struct skiplist *) + rn->info); + } + skiplist_free( + (struct skiplist *)rn->info); + rn->info = NULL; + agg_unlock_node(rn); + RFAPI_RIB_PREFIX_COUNT_DECR(rfd, + bgp->rfapi); + } + if (rn->aggregate) { + + struct rfapi_info *ri_del; + + /* delete skiplist & contents */ + while (!skiplist_first( + (struct skiplist + *)(rn->aggregate), + NULL, (void **)&ri_del)) { + + /* sl->del takes care of ri_del + */ + skiplist_delete_first(( + struct skiplist + *)(rn->aggregate)); + } + skiplist_free( + (struct skiplist + *)(rn->aggregate)); + + rn->aggregate = NULL; + agg_unlock_node(rn); + } + } + } + } + if (rfd->updated_responses_queue) + work_queue_free_and_null(&rfd->updated_responses_queue); +} + +/* + * Release all dynamically-allocated memory that is part of an HD's RIB + */ +void rfapiRibFree(struct rfapi_descriptor *rfd) +{ + afi_t afi; + +#if DEBUG_CLEANUP + zlog_debug("%s: rfd %p", __func__, rfd); +#endif + + /* + * NB rfd is typically detached from master list, so is not included + * in the count performed by RFAPI_RIB_CHECK_COUNTS + */ + + /* + * Free routes attached to radix trees + */ + rfapiRibClear(rfd); + + /* Now the uncounted rfapi_info's are freed, so the check should succeed + */ + RFAPI_RIB_CHECK_COUNTS(1, 0); + + /* + * Free radix trees + */ + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + if (rfd->rib_pending[afi]) + agg_table_finish(rfd->rib_pending[afi]); + rfd->rib_pending[afi] = NULL; + + if (rfd->rib[afi]) + agg_table_finish(rfd->rib[afi]); + rfd->rib[afi] = NULL; + + /* NB agg_table_finish frees only prefix nodes, not chained + * info */ + if (rfd->rsp_times[afi]) + agg_table_finish(rfd->rsp_times[afi]); + rfd->rib[afi] = NULL; + } +} + +/* + * Copies struct bgp_path_info to struct rfapi_info, except for rk fields and un + */ +static void rfapiRibBi2Ri(struct bgp_path_info *bpi, struct rfapi_info *ri, + uint32_t lifetime) +{ + struct bgp_attr_encap_subtlv *pEncap; + + ri->cost = rfapiRfpCost(bpi->attr); + ri->lifetime = lifetime; + + /* This loop based on rfapiRouteInfo2NextHopEntry() */ + for (pEncap = bgp_attr_get_vnc_subtlvs(bpi->attr); pEncap; + pEncap = pEncap->next) { + struct bgp_tea_options *hop; + + switch (pEncap->type) { + case BGP_VNC_SUBTLV_TYPE_LIFETIME: + /* use configured lifetime, not attr lifetime */ + break; + + case BGP_VNC_SUBTLV_TYPE_RFPOPTION: + hop = XCALLOC(MTYPE_BGP_TEA_OPTIONS, + sizeof(struct bgp_tea_options)); + assert(hop); + hop->type = pEncap->value[0]; + hop->length = pEncap->value[1]; + hop->value = XCALLOC(MTYPE_BGP_TEA_OPTIONS_VALUE, + pEncap->length - 2); + assert(hop->value); + memcpy(hop->value, pEncap->value + 2, + pEncap->length - 2); + if (hop->length > pEncap->length - 2) { + zlog_warn( + "%s: VNC subtlv length mismatch: RFP option says %d, attr says %d (shrinking)", + __func__, hop->length, + pEncap->length - 2); + hop->length = pEncap->length - 2; + } + hop->next = ri->tea_options; + ri->tea_options = hop; + break; + + default: + break; + } + } + + rfapi_un_options_free(ri->un_options); /* maybe free old version */ + ri->un_options = rfapi_encap_tlv_to_un_option(bpi->attr); + + /* + * VN options + */ + if (bpi->extra && decode_rd_type(bpi->extra->vnc->vnc.import.rd.val) == + RD_TYPE_VNC_ETH) { + /* ethernet route */ + + struct rfapi_vn_option *vo; + + vo = XCALLOC(MTYPE_RFAPI_VN_OPTION, + sizeof(struct rfapi_vn_option)); + assert(vo); + + vo->type = RFAPI_VN_OPTION_TYPE_L2ADDR; + + /* copy from RD already stored in bpi, so we don't need it_node + */ + memcpy(&vo->v.l2addr.macaddr, + bpi->extra->vnc->vnc.import.rd.val + 2, ETH_ALEN); + + (void)rfapiEcommunityGetLNI(bgp_attr_get_ecommunity(bpi->attr), + &vo->v.l2addr.logical_net_id); + (void)rfapiEcommunityGetEthernetTag( + bgp_attr_get_ecommunity(bpi->attr), + &vo->v.l2addr.tag_id); + + /* local_nve_id comes from RD */ + vo->v.l2addr.local_nve_id = + bpi->extra->vnc->vnc.import.rd.val[1]; + + /* label comes from MP_REACH_NLRI label */ + vo->v.l2addr.label = + BGP_PATH_INFO_NUM_LABELS(bpi) + ? decode_label(&bpi->extra->labels->label[0]) + : MPLS_INVALID_LABEL; + + rfapi_vn_options_free( + ri->vn_options); /* maybe free old version */ + ri->vn_options = vo; + } + + /* + * If there is an auxiliary IP address (L2 can have it), copy it + */ + if (bpi->extra && bpi->extra->vnc->vnc.import.aux_prefix.family) { + ri->rk.aux_prefix = bpi->extra->vnc->vnc.import.aux_prefix; + } +} + +/* + * rfapiRibPreloadBi + * + * Install route into NVE RIB model so as to be consistent with + * caller's response to rfapi_query(). + * + * Also: return indication to caller whether this specific route + * should be included in the response to the NVE according to + * the following tests: + * + * 1. If there were prior duplicates of this route in this same + * query response, don't include the route. + * + * RETURN VALUE: + * + * 0 OK to include route in response + * !0 do not include route in response + */ +int rfapiRibPreloadBi( + struct agg_node *rfd_rib_node, /* NULL = don't preload or filter */ + struct prefix *pfx_vn, struct prefix *pfx_un, uint32_t lifetime, + struct bgp_path_info *bpi) +{ + struct rfapi_descriptor *rfd; + struct skiplist *slRibPt = NULL; + struct rfapi_info *ori = NULL; + struct rfapi_rib_key rk; + struct agg_node *trn; + afi_t afi; + const struct prefix *p = agg_node_get_prefix(rfd_rib_node); + + if (!rfd_rib_node) + return 0; + + afi = family2afi(p->family); + + rfd = agg_get_table_info(agg_get_table(rfd_rib_node)); + + memset((void *)&rk, 0, sizeof(rk)); + rk.vn = *pfx_vn; + rk.rd = bpi->extra->vnc->vnc.import.rd; + + /* + * If there is an auxiliary IP address (L2 can have it), copy it + */ + if (bpi->extra->vnc->vnc.import.aux_prefix.family) { + rk.aux_prefix = bpi->extra->vnc->vnc.import.aux_prefix; + } + + /* + * is this route already in NVE's RIB? + */ + slRibPt = (struct skiplist *)rfd_rib_node->info; + + if (slRibPt && !skiplist_search(slRibPt, &rk, (void **)&ori)) { + + if ((ori->rsp_counter == rfd->rsp_counter) + && (ori->last_sent_time == rfd->rsp_time)) { + return -1; /* duplicate in this response */ + } + + /* found: update contents of existing route in RIB */ + ori->un = *pfx_un; + rfapiRibBi2Ri(bpi, ori, lifetime); + } else { + /* not found: add new route to RIB */ + ori = rfapi_info_new(); + ori->rk = rk; + ori->un = *pfx_un; + rfapiRibBi2Ri(bpi, ori, lifetime); + + if (!slRibPt) { + slRibPt = skiplist_new(0, rfapi_rib_key_cmp, NULL); + rfd_rib_node->info = slRibPt; + agg_lock_node(rfd_rib_node); + RFAPI_RIB_PREFIX_COUNT_INCR(rfd, rfd->bgp->rfapi); + } + skiplist_insert(slRibPt, &ori->rk, ori); + } + + ori->last_sent_time = monotime(NULL); + + /* + * poke timer + */ + RFAPI_RIB_CHECK_COUNTS(0, 0); + rfapiRibStartTimer(rfd, ori, rfd_rib_node, 0); + RFAPI_RIB_CHECK_COUNTS(0, 0); + + /* + * Update last sent time for prefix + */ + trn = agg_node_get(rfd->rsp_times[afi], p); /* locks trn */ + trn->info = (void *)(uintptr_t)monotime(NULL); + if (agg_node_get_lock_count(trn) > 1) + agg_unlock_node(trn); + + return 0; +} + +/* + * Frees rfapi_info items at node + * + * Adjust 'rib' and 'rib_pending' as follows: + * + * If rib_pending node->info is 1 (magic value): + * callback: NHL = RIB NHL with lifetime = withdraw_lifetime_value + * RIB = remove all routes at the node + * DONE + * + * For each item at rib node: + * if not present in pending node, move RIB item to "delete list" + * + * For each item at pending rib node: + * if present (same vn/un) in rib node with same lifetime & options, drop + * matching item from pending node + * + * For each remaining item at pending rib node, add or replace item + * at rib node. + * + * Construct NHL as concatenation of pending list + delete list + * + * Clear pending node + */ +static void process_pending_node(struct bgp *bgp, struct rfapi_descriptor *rfd, + afi_t afi, + struct agg_node *pn, /* pending node */ + struct rfapi_next_hop_entry **head, + struct rfapi_next_hop_entry **tail) +{ + struct listnode *node = NULL; + struct listnode *nnode = NULL; + struct rfapi_info *ri = NULL; /* happy valgrind */ + struct rfapi_ip_prefix hp = {0}; /* pfx to put in NHE */ + struct agg_node *rn = NULL; + struct skiplist *slRibPt = NULL; /* rib list */ + struct skiplist *slPendPt = NULL; + struct list *lPendCost = NULL; + struct list *delete_list = NULL; + int printedprefix = 0; + int rib_node_started_nonempty = 0; + int sendingsomeroutes = 0; + const struct prefix *p; +#if DEBUG_PROCESS_PENDING_NODE + unsigned int count_rib_initial = 0; + unsigned int count_pend_vn_initial = 0; + unsigned int count_pend_cost_initial = 0; +#endif + + assert(pn); + p = agg_node_get_prefix(pn); + vnc_zlog_debug_verbose("%s: afi=%d, %pRN pn->info=%p", __func__, afi, + pn, pn->info); + + if (AFI_L2VPN != afi) { + rfapiQprefix2Rprefix(p, &hp); + } + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + /* + * Find corresponding RIB node + */ + rn = agg_node_get(rfd->rib[afi], p); /* locks rn */ + + /* + * RIB skiplist has key=rfapi_addr={vn,un}, val = rfapi_info, + * skiplist.del = NULL + */ + slRibPt = (struct skiplist *)rn->info; + if (slRibPt) + rib_node_started_nonempty = 1; + + slPendPt = (struct skiplist *)(pn->aggregate); + lPendCost = (struct list *)(pn->info); + +#if DEBUG_PROCESS_PENDING_NODE + /* debugging */ + if (slRibPt) + count_rib_initial = skiplist_count(slRibPt); + + if (slPendPt) + count_pend_vn_initial = skiplist_count(slPendPt); + + if (lPendCost && lPendCost != (struct list *)1) + count_pend_cost_initial = lPendCost->count; +#endif + + + /* + * Handle special case: delete all routes at prefix + */ + if (lPendCost == (struct list *)1) { + vnc_zlog_debug_verbose("%s: lPendCost=1 => delete all", + __func__); + if (slRibPt && !skiplist_empty(slRibPt)) { + delete_list = list_new(); + while (0 + == skiplist_first(slRibPt, NULL, (void **)&ri)) { + listnode_add(delete_list, ri); + vnc_zlog_debug_verbose( + "%s: after listnode_add, delete_list->count=%d", + __func__, delete_list->count); + rfapiFreeBgpTeaOptionChain(ri->tea_options); + ri->tea_options = NULL; + + if (ri->timer) { + struct rfapi_rib_tcb *tcb; + + tcb = EVENT_ARG(ri->timer); + EVENT_OFF(ri->timer); + XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); + } + + vnc_zlog_debug_verbose( + "%s: put dl pfx=%pRN vn=%pFX un=%pFX cost=%d life=%d vn_options=%p", + __func__, pn, &ri->rk.vn, &ri->un, + ri->cost, ri->lifetime, ri->vn_options); + + skiplist_delete_first(slRibPt); + } + + assert(skiplist_empty(slRibPt)); + + skiplist_free(slRibPt); + rn->info = slRibPt = NULL; + agg_unlock_node(rn); + + lPendCost = pn->info = NULL; + agg_unlock_node(pn); + + goto callback; + } + if (slRibPt) { + skiplist_free(slRibPt); + rn->info = NULL; + agg_unlock_node(rn); + } + + assert(!slPendPt); + if (slPendPt) { /* TBD I think we can toss this block */ + skiplist_free(slPendPt); + pn->aggregate = NULL; + agg_unlock_node(pn); + } + + pn->info = NULL; + agg_unlock_node(pn); + + agg_unlock_node(rn); /* agg_node_get() */ + + if (rib_node_started_nonempty) { + RFAPI_RIB_PREFIX_COUNT_DECR(rfd, bgp->rfapi); + } + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + return; + } + + vnc_zlog_debug_verbose("%s: lPendCost->count=%d, slRibPt->count=%d", + __func__, + (lPendCost ? (int)lPendCost->count : -1), + (slRibPt ? (int)slRibPt->count : -1)); + + /* + * Iterate over routes at RIB Node. + * If not found at Pending Node, delete from RIB Node and add to + * deletelist + * If found at Pending Node + * If identical rfapi_info, delete from Pending Node + */ + if (slRibPt) { + void *cursor = NULL; + struct rfapi_info *ori; + + /* + * Iterate over RIB List + * + */ + while (!skiplist_next(slRibPt, NULL, (void **)&ori, &cursor)) { + + if (skiplist_search(slPendPt, &ori->rk, (void **)&ri)) { + /* + * Not in Pending list, so it should be deleted + */ + if (!delete_list) + delete_list = list_new(); + listnode_add(delete_list, ori); + rfapiFreeBgpTeaOptionChain(ori->tea_options); + ori->tea_options = NULL; + if (ori->timer) { + struct rfapi_rib_tcb *tcb; + + tcb = EVENT_ARG(ori->timer); + EVENT_OFF(ori->timer); + XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); + } + +#if DEBUG_PROCESS_PENDING_NODE + /* deleted from slRibPt below, after we're done + * iterating */ + vnc_zlog_debug_verbose( + "%s: slRibPt ri %p not matched in pending list, delete", + __func__, ori); +#endif + + } else { + /* + * Found in pending list. If same lifetime, + * cost, options, + * then remove from pending list because the + * route + * hasn't changed. + */ + if (!rfapi_info_cmp(ori, ri)) { + skiplist_delete(slPendPt, &ri->rk, + NULL); + assert(lPendCost); + if (lPendCost) { + /* linear walk: might need + * optimization */ + listnode_delete(lPendCost, + ri); /* XXX + doesn't + free + data! + bug? */ + rfapi_info_free( + ri); /* grr... */ + } + } +#if DEBUG_PROCESS_PENDING_NODE + vnc_zlog_debug_verbose( + "%s: slRibPt ri %p matched in pending list, %s", + __func__, ori, + (same ? "same info" + : "different info")); +#endif + } + } + /* + * Go back and delete items from RIB + */ + if (delete_list) { + for (ALL_LIST_ELEMENTS_RO(delete_list, node, ri)) { + vnc_zlog_debug_verbose( + "%s: deleting ri %p from slRibPt", + __func__, ri); + assert(!skiplist_delete(slRibPt, &ri->rk, + NULL)); + } + if (skiplist_empty(slRibPt)) { + skiplist_free(slRibPt); + slRibPt = rn->info = NULL; + agg_unlock_node(rn); + } + } + } + + RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); + + /* + * Iterate over routes at Pending Node + * + * If {vn} found at RIB Node, update RIB Node route contents to match PN + * If {vn} NOT found at RIB Node, add copy to RIB Node + */ + if (lPendCost) { + for (ALL_LIST_ELEMENTS_RO(lPendCost, node, ri)) { + + struct rfapi_info *ori; + + if (slRibPt + && !skiplist_search(slRibPt, &ri->rk, + (void **)&ori)) { + + /* found: update contents of existing route in + * RIB */ + ori->un = ri->un; + ori->cost = ri->cost; + ori->lifetime = ri->lifetime; + rfapiFreeBgpTeaOptionChain(ori->tea_options); + ori->tea_options = + rfapiOptionsDup(ri->tea_options); + ori->last_sent_time = monotime(NULL); + + rfapiFreeRfapiVnOptionChain(ori->vn_options); + ori->vn_options = + rfapiVnOptionsDup(ri->vn_options); + + rfapiFreeRfapiUnOptionChain(ori->un_options); + ori->un_options = + rfapiUnOptionsDup(ri->un_options); + + vnc_zlog_debug_verbose( + "%s: matched lPendCost item %p in slRibPt, rewrote", + __func__, ri); + + } else { + /* not found: add new route to RIB */ + ori = rfapi_info_new(); + ori->rk = ri->rk; + ori->un = ri->un; + ori->cost = ri->cost; + ori->lifetime = ri->lifetime; + ori->tea_options = + rfapiOptionsDup(ri->tea_options); + ori->last_sent_time = monotime(NULL); + ori->vn_options = + rfapiVnOptionsDup(ri->vn_options); + ori->un_options = + rfapiUnOptionsDup(ri->un_options); + + if (!slRibPt) { + slRibPt = skiplist_new( + 0, rfapi_rib_key_cmp, NULL); + rn->info = slRibPt; + agg_lock_node(rn); + } + skiplist_insert(slRibPt, &ori->rk, ori); + + vnc_zlog_debug_verbose( + "%s: nomatch lPendCost item %p in slRibPt, added (rd=%pRDP)", + __func__, ri, &ori->rk.rd); + } + + /* + * poke timer + */ + RFAPI_RIB_CHECK_COUNTS( + 0, (delete_list ? delete_list->count : 0)); + rfapiRibStartTimer(rfd, ori, rn, 0); + RFAPI_RIB_CHECK_COUNTS( + 0, (delete_list ? delete_list->count : 0)); + } + } + + +callback: + /* + * Construct NHL as concatenation of pending list + delete list + */ + + + RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); + + if (lPendCost) { + + char buf[BUFSIZ]; + char buf2[BUFSIZ]; + + vnc_zlog_debug_verbose("%s: lPendCost->count now %d", __func__, + lPendCost->count); + vnc_zlog_debug_verbose("%s: For prefix %pRN (a)", __func__, pn); + printedprefix = 1; + + for (ALL_LIST_ELEMENTS(lPendCost, node, nnode, ri)) { + + struct rfapi_next_hop_entry *new; + struct agg_node *trn; + + new = XCALLOC(MTYPE_RFAPI_NEXTHOP, + sizeof(struct rfapi_next_hop_entry)); + + if (ri->rk.aux_prefix.family) { + rfapiQprefix2Rprefix(&ri->rk.aux_prefix, + &new->prefix); + } else { + new->prefix = hp; + if (AFI_L2VPN == afi) { + /* hp is 0; need to set length to match + * AF of vn */ + new->prefix.length = + (ri->rk.vn.family == AF_INET) + ? 32 + : 128; + } + } + new->prefix.cost = ri->cost; + new->lifetime = ri->lifetime; + rfapiQprefix2Raddr(&ri->rk.vn, &new->vn_address); + rfapiQprefix2Raddr(&ri->un, &new->un_address); + /* free option chain from ri */ + rfapiFreeBgpTeaOptionChain(ri->tea_options); + + ri->tea_options = + NULL; /* option chain was transferred to NHL */ + + new->vn_options = ri->vn_options; + ri->vn_options = + NULL; /* option chain was transferred to NHL */ + + new->un_options = ri->un_options; + ri->un_options = + NULL; /* option chain was transferred to NHL */ + + if (*tail) + (*tail)->next = new; + *tail = new; + if (!*head) { + *head = new; + } + sendingsomeroutes = 1; + + ++rfd->stat_count_nh_reachable; + ++bgp->rfapi->stat.count_updated_response_updates; + + /* + * update this NVE's timestamp for this prefix + */ + trn = agg_node_get(rfd->rsp_times[afi], + p); /* locks trn */ + trn->info = (void *)(uintptr_t)monotime(NULL); + if (agg_node_get_lock_count(trn) > 1) + agg_unlock_node(trn); + + rfapiRfapiIpAddr2Str(&new->vn_address, buf, BUFSIZ); + rfapiRfapiIpAddr2Str(&new->un_address, buf2, BUFSIZ); + vnc_zlog_debug_verbose( + "%s: add vn=%s un=%s cost=%d life=%d", + __func__, buf, buf2, new->prefix.cost, + new->lifetime); + } + } + + RFAPI_RIB_CHECK_COUNTS(0, (delete_list ? delete_list->count : 0)); + + if (delete_list) { + + char buf[BUFSIZ]; + char buf2[BUFSIZ]; + + if (!printedprefix) { + vnc_zlog_debug_verbose("%s: For prefix %pRN (d)", + __func__, pn); + } + vnc_zlog_debug_verbose("%s: delete_list has %d elements", + __func__, delete_list->count); + + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + if (!CHECK_FLAG(bgp->rfapi_cfg->flags, + BGP_VNC_CONFIG_RESPONSE_REMOVAL_DISABLE)) { + + for (ALL_LIST_ELEMENTS(delete_list, node, nnode, ri)) { + + struct rfapi_next_hop_entry *new; + struct rfapi_info *ri_del; + + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + new = XCALLOC( + MTYPE_RFAPI_NEXTHOP, + sizeof(struct rfapi_next_hop_entry)); + + if (ri->rk.aux_prefix.family) { + rfapiQprefix2Rprefix(&ri->rk.aux_prefix, + &new->prefix); + } else { + new->prefix = hp; + if (AFI_L2VPN == afi) { + /* hp is 0; need to set length + * to match AF of vn */ + new->prefix.length = + (ri->rk.vn.family + == AF_INET) + ? 32 + : 128; + } + } + + new->prefix.cost = ri->cost; + new->lifetime = RFAPI_REMOVE_RESPONSE_LIFETIME; + rfapiQprefix2Raddr(&ri->rk.vn, + &new->vn_address); + rfapiQprefix2Raddr(&ri->un, &new->un_address); + + new->vn_options = ri->vn_options; + ri->vn_options = NULL; /* option chain was + transferred to NHL */ + + new->un_options = ri->un_options; + ri->un_options = NULL; /* option chain was + transferred to NHL */ + + if (*tail) + (*tail)->next = new; + *tail = new; + if (!*head) { + *head = new; + } + ++rfd->stat_count_nh_removal; + ++bgp->rfapi->stat + .count_updated_response_deletes; + + rfapiRfapiIpAddr2Str(&new->vn_address, buf, + BUFSIZ); + rfapiRfapiIpAddr2Str(&new->un_address, buf2, + BUFSIZ); + vnc_zlog_debug_verbose( + "%s: DEL vn=%s un=%s cost=%d life=%d", + __func__, buf, buf2, new->prefix.cost, + new->lifetime); + + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + /* + * Update/add to list of recent deletions at + * this prefix + */ + if (!rn->aggregate) { + rn->aggregate = skiplist_new( + 0, rfapi_rib_key_cmp, + (void (*)(void *)) + rfapi_info_free); + agg_lock_node(rn); + } + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + + /* sanity check lifetime */ + if (ri->lifetime + > RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY) + ri->lifetime = + RFAPI_LIFETIME_INFINITE_WITHDRAW_DELAY; + + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + /* cancel normal expire timer */ + if (ri->timer) { + struct rfapi_rib_tcb *tcb; + + tcb = EVENT_ARG(ri->timer); + EVENT_OFF(ri->timer); + XFREE(MTYPE_RFAPI_RECENT_DELETE, tcb); + } + RFAPI_RIB_CHECK_COUNTS(0, delete_list->count); + + /* + * Look in "recently-deleted" list + */ + if (skiplist_search( + (struct skiplist *)(rn->aggregate), + &ri->rk, (void **)&ri_del)) { + + int rc; + + RFAPI_RIB_CHECK_COUNTS( + 0, delete_list->count); + /* + * NOT in "recently-deleted" list + */ + list_delete_node( + delete_list, + node); /* does not free ri */ + rc = skiplist_insert( + (struct skiplist + *)(rn->aggregate), + &ri->rk, ri); + assert(!rc); + + RFAPI_RIB_CHECK_COUNTS( + 0, delete_list->count); + rfapiRibStartTimer(rfd, ri, rn, 1); + RFAPI_RIB_CHECK_COUNTS( + 0, delete_list->count); + ri->last_sent_time = monotime(NULL); +#if DEBUG_RIB_SL_RD + vnc_zlog_debug_verbose( + "%s: move route to recently deleted list, rd=%pRDP", + __func__, &ri->rk.rd); +#endif + + } else { + /* + * IN "recently-deleted" list + */ + RFAPI_RIB_CHECK_COUNTS( + 0, delete_list->count); + rfapiRibStartTimer(rfd, ri_del, rn, 1); + RFAPI_RIB_CHECK_COUNTS( + 0, delete_list->count); + ri->last_sent_time = monotime(NULL); + } + } + } else { + vnc_zlog_debug_verbose( + "%s: response removal disabled, omitting removals", + __func__); + } + + delete_list->del = (void (*)(void *))rfapi_info_free; + list_delete(&delete_list); + } + + RFAPI_RIB_CHECK_COUNTS(0, 0); + + /* + * Reset pending lists. The final agg_unlock_node() will probably + * cause the pending node to be released. + */ + if (slPendPt) { + skiplist_free(slPendPt); + pn->aggregate = NULL; + agg_unlock_node(pn); + } + if (lPendCost) { + list_delete(&lPendCost); + pn->info = NULL; + agg_unlock_node(pn); + } + RFAPI_RIB_CHECK_COUNTS(0, 0); + + if (rib_node_started_nonempty) { + if (!rn->info) { + RFAPI_RIB_PREFIX_COUNT_DECR(rfd, bgp->rfapi); + } + } else { + if (rn->info) { + RFAPI_RIB_PREFIX_COUNT_INCR(rfd, bgp->rfapi); + } + } + + if (sendingsomeroutes) + rfapiMonitorTimersRestart(rfd, p); + + agg_unlock_node(rn); /* agg_node_get() */ + + RFAPI_RIB_CHECK_COUNTS(1, 0); +} + +/* + * regardless of targets, construct a single callback by doing + * only one traversal of the pending RIB + * + * + * Do callback + * + */ +static void rib_do_callback_onepass(struct rfapi_descriptor *rfd, afi_t afi) +{ + struct bgp *bgp = bgp_get_default(); + struct rfapi_next_hop_entry *head = NULL; + struct rfapi_next_hop_entry *tail = NULL; + struct agg_node *rn; + +#ifdef DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: rfd=%p, afi=%d", __func__, rfd, afi); +#endif + + if (!rfd->rib_pending[afi]) + return; + + assert(bgp->rfapi); + + for (rn = agg_route_top(rfd->rib_pending[afi]); rn; + rn = agg_route_next(rn)) { + process_pending_node(bgp, rfd, afi, rn, &head, &tail); + } + + if (head) { + rfapi_response_cb_t *f; + +#if DEBUG_NHL + vnc_zlog_debug_verbose("%s: response callback NHL follows:", + __func__); + rfapiPrintNhl(NULL, head); +#endif + + if (rfd->response_cb) + f = rfd->response_cb; + else + f = bgp->rfapi->rfp_methods.response_cb; + + bgp->rfapi->flags |= RFAPI_INCALLBACK; + vnc_zlog_debug_verbose("%s: invoking updated response callback", + __func__); + (*f)(head, rfd->cookie); + bgp->rfapi->flags &= ~RFAPI_INCALLBACK; + ++bgp->rfapi->response_updated_count; + } +} + +static wq_item_status rfapiRibDoQueuedCallback(struct work_queue *wq, + void *data) +{ + struct rfapi_descriptor *rfd; + afi_t afi; + uint32_t queued_flag; + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + rfd = ((struct rfapi_updated_responses_queue *)data)->rfd; + afi = ((struct rfapi_updated_responses_queue *)data)->afi; + + /* Make sure the HD wasn't closed after the work item was scheduled */ + if (rfapi_check(rfd)) + return WQ_SUCCESS; + + rib_do_callback_onepass(rfd, afi); + + queued_flag = RFAPI_QUEUED_FLAG(afi); + + UNSET_FLAG(rfd->flags, queued_flag); + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + return WQ_SUCCESS; +} + +static void rfapiRibQueueItemDelete(struct work_queue *wq, void *data) +{ + XFREE(MTYPE_RFAPI_UPDATED_RESPONSE_QUEUE, data); +} + +static void updated_responses_queue_init(struct rfapi_descriptor *rfd) +{ + if (rfd->updated_responses_queue) + return; + + rfd->updated_responses_queue = + work_queue_new(bm->master, "rfapi updated responses"); + assert(rfd->updated_responses_queue); + + rfd->updated_responses_queue->spec.workfunc = rfapiRibDoQueuedCallback; + rfd->updated_responses_queue->spec.del_item_data = + rfapiRibQueueItemDelete; + rfd->updated_responses_queue->spec.max_retries = 0; + rfd->updated_responses_queue->spec.hold = 1; +} + +/* + * Called when an import table node is modified. Construct a + * new complete nexthop list, sorted by cost (lowest first), + * based on the import table node. + * + * Filter out duplicate nexthops (vn address). There should be + * only one UN address per VN address from the point of view of + * a given import table, so we can probably ignore UN addresses + * while filtering. + * + * Based on rfapiNhlAddNodeRoutes() + */ +void rfapiRibUpdatePendingNode( + struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_import_table *it, /* needed for L2 */ + struct agg_node *it_node, uint32_t lifetime) +{ + const struct prefix *prefix; + struct bgp_path_info *bpi; + struct agg_node *pn; + afi_t afi; + uint32_t queued_flag; + int count = 0; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (CHECK_FLAG(bgp->rfapi_cfg->flags, BGP_VNC_CONFIG_CALLBACK_DISABLE)) + return; + + vnc_zlog_debug_verbose("%s: callbacks are not disabled", __func__); + + RFAPI_RIB_CHECK_COUNTS(1, 0); + + prefix = agg_node_get_prefix(it_node); + afi = family2afi(prefix->family); + vnc_zlog_debug_verbose("%s: prefix=%pFX", __func__, prefix); + + pn = agg_node_get(rfd->rib_pending[afi], prefix); + assert(pn); + + vnc_zlog_debug_verbose("%s: pn->info=%p, pn->aggregate=%p", __func__, + pn->info, pn->aggregate); + + if (pn->aggregate) { + /* + * free references into the rfapi_info structures before + * freeing the structures themselves + */ + skiplist_free((struct skiplist *)(pn->aggregate)); + pn->aggregate = NULL; + agg_unlock_node(pn); /* skiplist deleted */ + } + + + /* + * free the rfapi_info structures + */ + if (pn->info) { + if (pn->info != (void *)1) { + list_delete((struct list **)(&pn->info)); + } + pn->info = NULL; + agg_unlock_node(pn); /* linklist or 1 deleted */ + } + + /* + * The BPIs in the import table are already sorted by cost + */ + for (bpi = it_node->info; bpi; bpi = bpi->next) { + + struct rfapi_info *ri; + struct prefix pfx_nh; + + if (!bpi->extra) { + /* shouldn't happen */ + /* TBD increment error stats counter */ + continue; + } + + rfapiNexthop2Prefix(bpi->attr, &pfx_nh); + + /* + * Omit route if nexthop is self + */ + if (CHECK_FLAG(bgp->rfapi_cfg->flags, + BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP)) { + + struct prefix pfx_vn; + + assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn)); + if (prefix_same(&pfx_vn, &pfx_nh)) + continue; + } + + ri = rfapi_info_new(); + ri->rk.vn = pfx_nh; + ri->rk.rd = bpi->extra->vnc->vnc.import.rd; + /* + * If there is an auxiliary IP address (L2 can have it), copy it + */ + if (bpi->extra->vnc->vnc.import.aux_prefix.family) { + ri->rk.aux_prefix = + bpi->extra->vnc->vnc.import.aux_prefix; + } + + if (rfapiGetUnAddrOfVpnBi(bpi, &ri->un)) { + rfapi_info_free(ri); + continue; + } + + if (!pn->aggregate) { + pn->aggregate = + skiplist_new(0, rfapi_rib_key_cmp, NULL); + agg_lock_node(pn); + } + + /* + * If we have already added this nexthop, the insert will fail. + * Note that the skiplist key is a pointer INTO the rfapi_info + * structure which will be added to the "info" list. + * The skiplist entry VALUE is not used for anything but + * might be useful during debugging. + */ + if (skiplist_insert((struct skiplist *)pn->aggregate, &ri->rk, + ri)) { + + /* + * duplicate + */ + rfapi_info_free(ri); + continue; + } + + rfapiRibBi2Ri(bpi, ri, lifetime); + + if (!pn->info) { + pn->info = list_new(); + ((struct list *)(pn->info))->del = + (void (*)(void *))rfapi_info_free; + agg_lock_node(pn); + } + + listnode_add((struct list *)(pn->info), ri); + } + + if (pn->info) { + count = ((struct list *)(pn->info))->count; + } + + if (!count) { + assert(!pn->info); + assert(!pn->aggregate); + pn->info = (void *)1; /* magic value means this node has no + routes */ + agg_lock_node(pn); + } + + agg_unlock_node(pn); /* agg_node_get */ + + queued_flag = RFAPI_QUEUED_FLAG(afi); + + if (!CHECK_FLAG(rfd->flags, queued_flag)) { + + struct rfapi_updated_responses_queue *urq; + + urq = XCALLOC(MTYPE_RFAPI_UPDATED_RESPONSE_QUEUE, + sizeof(struct rfapi_updated_responses_queue)); + if (!rfd->updated_responses_queue) + updated_responses_queue_init(rfd); + + SET_FLAG(rfd->flags, queued_flag); + urq->rfd = rfd; + urq->afi = afi; + work_queue_add(rfd->updated_responses_queue, urq); + } + RFAPI_RIB_CHECK_COUNTS(1, 0); +} + +void rfapiRibUpdatePendingNodeSubtree( + struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_import_table *it, struct agg_node *it_node, + struct agg_node *omit_subtree, /* may be NULL */ + uint32_t lifetime) +{ + /* FIXME: need to find a better way here to work without sticking our + * hands in node->link */ + if (agg_node_left(it_node) + && (agg_node_left(it_node) != omit_subtree)) { + if (agg_node_left(it_node)->info) + rfapiRibUpdatePendingNode( + bgp, rfd, it, agg_node_left(it_node), lifetime); + rfapiRibUpdatePendingNodeSubtree(bgp, rfd, it, + agg_node_left(it_node), + omit_subtree, lifetime); + } + + if (agg_node_right(it_node) + && (agg_node_right(it_node) != omit_subtree)) { + if (agg_node_right(it_node)->info) + rfapiRibUpdatePendingNode(bgp, rfd, it, + agg_node_right(it_node), + lifetime); + rfapiRibUpdatePendingNodeSubtree(bgp, rfd, it, + agg_node_right(it_node), + omit_subtree, lifetime); + } +} + +/* + * RETURN VALUE + * + * 0 allow prefix to be included in response + * !0 don't allow prefix to be included in response + */ +int rfapiRibFTDFilterRecentPrefix( + struct rfapi_descriptor *rfd, + struct agg_node *it_rn, /* import table node */ + struct prefix *pfx_target_original) /* query target */ +{ + struct bgp *bgp = rfd->bgp; + const struct prefix *p = agg_node_get_prefix(it_rn); + afi_t afi = family2afi(p->family); + time_t prefix_time; + struct agg_node *trn; + + /* + * Not in FTD mode, so allow prefix + */ + if (bgp->rfapi_cfg->rfp_cfg.download_type != RFAPI_RFP_DOWNLOAD_FULL) + return 0; + + /* + * TBD + * This matches behavior of now-obsolete rfapiRibFTDFilterRecent(), + * but we need to decide if that is correct. + */ + if (p->family == AF_ETHERNET) + return 0; + +#ifdef DEBUG_FTD_FILTER_RECENT + { + vnc_zlog_debug_verbose("%s: prefix %pFX", __func__, + agg_node_get_prefix(it_rn)); + } +#endif + + /* + * prefix covers target address, so allow prefix + */ + if (prefix_match(p, pfx_target_original)) { +#ifdef DEBUG_FTD_FILTER_RECENT + vnc_zlog_debug_verbose("%s: prefix covers target, allowed", + __func__); +#endif + return 0; + } + + /* + * check this NVE's timestamp for this prefix + */ + trn = agg_node_get(rfd->rsp_times[afi], p); /* locks trn */ + prefix_time = (time_t)trn->info; + if (agg_node_get_lock_count(trn) > 1) + agg_unlock_node(trn); + +#ifdef DEBUG_FTD_FILTER_RECENT + vnc_zlog_debug_verbose("%s: last sent time %lu, last allowed time %lu", + __func__, prefix_time, + rfd->ftd_last_allowed_time); +#endif + + /* + * haven't sent this prefix, which doesn't cover target address, + * to NVE since ftd_advertisement_interval, so OK to send now. + */ + if (prefix_time <= rfd->ftd_last_allowed_time) + return 0; + + return 1; +} + +/* + * Call when rfapi returns from rfapi_query() so the RIB reflects + * the routes sent to the NVE before the first updated response + * + * Also: remove duplicates from response. Caller should use returned + * value of nexthop chain. + */ +struct rfapi_next_hop_entry * +rfapiRibPreload(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_next_hop_entry *response, int use_eth_resolution) +{ + struct rfapi_next_hop_entry *nhp; + struct rfapi_next_hop_entry *nhp_next; + struct rfapi_next_hop_entry *head = NULL; + struct rfapi_next_hop_entry *tail = NULL; + time_t new_last_sent_time; + + vnc_zlog_debug_verbose("%s: loading response=%p, use_eth_resolution=%d", + __func__, response, use_eth_resolution); + + new_last_sent_time = monotime(NULL); + + for (nhp = response; nhp; nhp = nhp_next) { + + struct prefix pfx; + struct rfapi_rib_key rk; + afi_t afi; + struct rfapi_info *ri; + int need_insert; + struct agg_node *rn; + int rib_node_started_nonempty = 0; + struct agg_node *trn; + int allowed = 0; + + /* save in case we delete nhp */ + nhp_next = nhp->next; + + if (nhp->lifetime == RFAPI_REMOVE_RESPONSE_LIFETIME) { + /* + * weird, shouldn't happen + */ + vnc_zlog_debug_verbose( + "%s: got nhp->lifetime == RFAPI_REMOVE_RESPONSE_LIFETIME", + __func__); + continue; + } + + + if (use_eth_resolution) { + /* get the prefix of the ethernet address in the L2 + * option */ + struct rfapi_l2address_option *pL2o; + struct rfapi_vn_option *vo; + + /* + * Look for VN option of type + * RFAPI_VN_OPTION_TYPE_L2ADDR + */ + for (pL2o = NULL, vo = nhp->vn_options; vo; + vo = vo->next) { + if (RFAPI_VN_OPTION_TYPE_L2ADDR == vo->type) { + pL2o = &vo->v.l2addr; + break; + } + } + + if (!pL2o) { + /* + * not supposed to happen + */ + vnc_zlog_debug_verbose("%s: missing L2 info", + __func__); + continue; + } + + afi = AFI_L2VPN; + rfapiL2o2Qprefix(pL2o, &pfx); + } else { + rfapiRprefix2Qprefix(&nhp->prefix, &pfx); + afi = family2afi(pfx.family); + } + + /* + * TBD for ethernet, rib must know the right way to distinguish + * duplicate routes + * + * Current approach: prefix is key to radix tree; then + * each prefix has a set of routes with unique VN addrs + */ + + /* + * Look up prefix in RIB + */ + rn = agg_node_get(rfd->rib[afi], &pfx); /* locks rn */ + + if (rn->info) { + rib_node_started_nonempty = 1; + } else { + rn->info = skiplist_new(0, rfapi_rib_key_cmp, NULL); + agg_lock_node(rn); + } + + /* + * Look up route at prefix + */ + need_insert = 0; + memset((void *)&rk, 0, sizeof(rk)); + assert(!rfapiRaddr2Qprefix(&nhp->vn_address, &rk.vn)); + + if (use_eth_resolution) { + /* copy what came from aux_prefix to rk.aux_prefix */ + rfapiRprefix2Qprefix(&nhp->prefix, &rk.aux_prefix); + if (RFAPI_0_PREFIX(&rk.aux_prefix) + && RFAPI_HOST_PREFIX(&rk.aux_prefix)) { + /* mark as "none" if nhp->prefix is 0/32 or + * 0/128 */ + rk.aux_prefix.family = AF_UNSPEC; + } + } + +#if DEBUG_NHL + { + char str_aux_prefix[PREFIX_STRLEN]; + + str_aux_prefix[0] = 0; + + prefix2str(&rk.aux_prefix, str_aux_prefix, + sizeof(str_aux_prefix)); + + if (!rk.aux_prefix.family) { + } + vnc_zlog_debug_verbose( + "%s: rk.vn=%pFX rk.aux_prefix=%s", __func__, + &rk.vn, + (rk.aux_prefix.family ? str_aux_prefix : "-")); + } + vnc_zlog_debug_verbose( + "%s: RIB skiplist for this prefix follows", __func__); + rfapiRibShowRibSl(NULL, agg_node_get_prefix(rn), + (struct skiplist *)rn->info); +#endif + + + if (!skiplist_search((struct skiplist *)rn->info, &rk, + (void **)&ri)) { + /* + * Already have this route; make values match + */ + rfapiFreeRfapiUnOptionChain(ri->un_options); + ri->un_options = NULL; + rfapiFreeRfapiVnOptionChain(ri->vn_options); + ri->vn_options = NULL; + +#if DEBUG_NHL + vnc_zlog_debug_verbose("%s: found in RIB", __func__); +#endif + + /* + * Filter duplicate routes from initial response. + * Check timestamps to avoid wraparound problems + */ + if ((ri->rsp_counter != rfd->rsp_counter) + || (ri->last_sent_time != new_last_sent_time)) { + +#if DEBUG_NHL + vnc_zlog_debug_verbose( + "%s: allowed due to counter/timestamp diff", + __func__); +#endif + allowed = 1; + } + + } else { + +#if DEBUG_NHL + vnc_zlog_debug_verbose( + "%s: allowed due to not yet in RIB", __func__); +#endif + /* not found: add new route to RIB */ + ri = rfapi_info_new(); + need_insert = 1; + allowed = 1; + } + + ri->rk = rk; + assert(!rfapiRaddr2Qprefix(&nhp->un_address, &ri->un)); + ri->cost = nhp->prefix.cost; + ri->lifetime = nhp->lifetime; + ri->vn_options = rfapiVnOptionsDup(nhp->vn_options); + ri->rsp_counter = rfd->rsp_counter; + ri->last_sent_time = monotime(NULL); + + if (need_insert) { + int rc; + rc = skiplist_insert((struct skiplist *)rn->info, + &ri->rk, ri); + assert(!rc); + } + + if (!rib_node_started_nonempty) { + RFAPI_RIB_PREFIX_COUNT_INCR(rfd, bgp->rfapi); + } + + RFAPI_RIB_CHECK_COUNTS(0, 0); + rfapiRibStartTimer(rfd, ri, rn, 0); + RFAPI_RIB_CHECK_COUNTS(0, 0); + + agg_unlock_node(rn); + + /* + * update this NVE's timestamp for this prefix + */ + trn = agg_node_get(rfd->rsp_times[afi], &pfx); /* locks trn */ + trn->info = (void *)(uintptr_t)monotime(NULL); + if (agg_node_get_lock_count(trn) > 1) + agg_unlock_node(trn); + + vnc_zlog_debug_verbose( + "%s: added pfx=%pFX nh[vn]=%pFX, cost=%u, lifetime=%u, allowed=%d", + __func__, &pfx, &rk.vn, nhp->prefix.cost, nhp->lifetime, + allowed); + + if (allowed) { + if (tail) + (tail)->next = nhp; + tail = nhp; + if (!head) { + head = nhp; + } + } else { + rfapi_un_options_free(nhp->un_options); + nhp->un_options = NULL; + rfapi_vn_options_free(nhp->vn_options); + nhp->vn_options = NULL; + + XFREE(MTYPE_RFAPI_NEXTHOP, nhp); + } + } + + if (tail) + tail->next = NULL; + return head; +} + +void rfapiRibPendingDeleteRoute(struct bgp *bgp, struct rfapi_import_table *it, + afi_t afi, struct agg_node *it_node) +{ + struct rfapi_descriptor *rfd; + struct listnode *node; + const struct prefix *p = agg_node_get_prefix(it_node); + + vnc_zlog_debug_verbose("%s: entry, it=%p, afi=%d, it_node=%p, pfx=%pRN", + __func__, it, afi, it_node, it_node); + + if (AFI_L2VPN == afi) { + /* + * ethernet import tables are per-LNI and each ethernet monitor + * identifies the rfd that owns it. + */ + struct rfapi_monitor_eth *m; + struct agg_node *rn; + struct skiplist *sl; + void *cursor; + int rc; + + /* + * route-specific monitors + */ + if ((sl = RFAPI_MONITOR_ETH(it_node))) { + + vnc_zlog_debug_verbose( + "%s: route-specific skiplist: %p", __func__, + sl); + + for (cursor = NULL, + rc = skiplist_next(sl, NULL, (void **)&m, &cursor); + !rc; rc = skiplist_next(sl, NULL, (void **)&m, + &cursor)) { + +#if DEBUG_PENDING_DELETE_ROUTE + vnc_zlog_debug_verbose("%s: eth monitor rfd=%p", + __func__, m->rfd); +#endif + /* + * If we have already sent a route with this + * prefix to this + * NVE, it's OK to send an update with the + * delete + */ + if ((rn = agg_node_lookup(m->rfd->rib[afi], + p))) { + rfapiRibUpdatePendingNode( + bgp, m->rfd, it, it_node, + m->rfd->response_lifetime); + agg_unlock_node(rn); + } + } + } + + /* + * all-routes/FTD monitors + */ + for (m = it->eth0_queries; m; m = m->next) { +#if DEBUG_PENDING_DELETE_ROUTE + vnc_zlog_debug_verbose("%s: eth0 monitor rfd=%p", + __func__, m->rfd); +#endif + /* + * If we have already sent a route with this prefix to + * this + * NVE, it's OK to send an update with the delete + */ + if ((rn = agg_node_lookup(m->rfd->rib[afi], p))) { + rfapiRibUpdatePendingNode( + bgp, m->rfd, it, it_node, + m->rfd->response_lifetime); + agg_unlock_node(rn); + } + } + + } else { + /* + * Find RFDs that reference this import table + */ + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, + rfd)) { + + struct agg_node *rn; + + vnc_zlog_debug_verbose( + "%s: comparing rfd(%p)->import_table=%p to it=%p", + __func__, rfd, rfd->import_table, it); + + if (rfd->import_table != it) + continue; + + vnc_zlog_debug_verbose("%s: matched rfd %p", __func__, + rfd); + + /* + * If we have sent a response to this NVE with this + * prefix + * previously, we should send an updated response. + */ + if ((rn = agg_node_lookup(rfd->rib[afi], p))) { + rfapiRibUpdatePendingNode( + bgp, rfd, it, it_node, + rfd->response_lifetime); + agg_unlock_node(rn); + } + } + } +} + +void rfapiRibShowResponsesSummary(void *stream) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + struct bgp *bgp = bgp_get_default(); + + int nves = 0; + int nves_with_nonempty_ribs = 0; + struct rfapi_descriptor *rfd; + struct listnode *node; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bgp) { + fp(out, "Unable to find default BGP instance\n"); + return; + } + + fp(out, "%-24s ", "Responses: (Prefixes)"); + fp(out, "%-8s %-8u ", "Active:", bgp->rfapi->rib_prefix_count_total); + fp(out, "%-8s %-8u", + "Maximum:", bgp->rfapi->rib_prefix_count_total_max); + fp(out, "\n"); + + fp(out, "%-24s ", " (Updated)"); + fp(out, "%-8s %-8u ", + "Update:", bgp->rfapi->stat.count_updated_response_updates); + fp(out, "%-8s %-8u", + "Remove:", bgp->rfapi->stat.count_updated_response_deletes); + fp(out, "%-8s %-8u", "Total:", + bgp->rfapi->stat.count_updated_response_updates + + bgp->rfapi->stat.count_updated_response_deletes); + fp(out, "\n"); + + fp(out, "%-24s ", " (NVEs)"); + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { + ++nves; + if (rfd->rib_prefix_count) + ++nves_with_nonempty_ribs; + } + fp(out, "%-8s %-8u ", "Active:", nves_with_nonempty_ribs); + fp(out, "%-8s %-8u", "Total:", nves); + fp(out, "\n"); +} + +void rfapiRibShowResponsesSummaryClear(void) +{ + struct bgp *bgp = bgp_get_default(); + + bgp->rfapi->rib_prefix_count_total_max = + bgp->rfapi->rib_prefix_count_total; +} + +static int print_rib_sl(int (*fp)(void *, const char *, ...), struct vty *vty, + void *out, struct skiplist *sl, int deleted, + char *str_pfx, int *printedprefix) +{ + struct rfapi_info *ri; + int rc; + void *cursor; + int routes_displayed = 0; + + cursor = NULL; + for (rc = skiplist_next(sl, NULL, (void **)&ri, &cursor); !rc; + rc = skiplist_next(sl, NULL, (void **)&ri, &cursor)) { + + char str_vn[PREFIX_STRLEN]; + char str_un[PREFIX_STRLEN]; + char str_lifetime[BUFSIZ]; + char str_age[BUFSIZ]; + char *p; + + ++routes_displayed; + + prefix2str(&ri->rk.vn, str_vn, sizeof(str_vn)); + p = index(str_vn, '/'); + if (p) + *p = 0; + + prefix2str(&ri->un, str_un, sizeof(str_un)); + p = index(str_un, '/'); + if (p) + *p = 0; + + rfapiFormatSeconds(ri->lifetime, str_lifetime, BUFSIZ); +#ifdef RFAPI_REGISTRATIONS_REPORT_AGE + rfapiFormatAge(ri->last_sent_time, str_age, BUFSIZ); +#else + { + time_t now = monotime(NULL); + time_t expire = + ri->last_sent_time + (time_t)ri->lifetime; + /* allow for delayed/async removal */ + rfapiFormatSeconds((expire > now ? expire - now : 1), + str_age, BUFSIZ); + } +#endif + + fp(out, " %c %-20s %-15s %-15s %-4u %-8s %-8s %pRDP\n", + deleted ? 'r' : ' ', *printedprefix ? "" : str_pfx, str_vn, + str_un, ri->cost, str_lifetime, str_age, &ri->rk.rd); + + if (!*printedprefix) + *printedprefix = 1; + } + return routes_displayed; +} + +#if DEBUG_NHL +/* + * This one is for debugging (set stream to NULL to send output to log) + */ +static void rfapiRibShowRibSl(void *stream, struct prefix *pfx, + struct skiplist *sl) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + int nhs_displayed = 0; + char str_pfx[PREFIX_STRLEN]; + int printedprefix = 0; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + prefix2str(pfx, str_pfx, sizeof(str_pfx)); + + nhs_displayed += + print_rib_sl(fp, vty, out, sl, 0, str_pfx, &printedprefix); +} +#endif + +void rfapiRibShowResponses(void *stream, struct prefix *pfx_match, + int show_removed) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + struct rfapi_descriptor *rfd; + struct listnode *node; + + struct bgp *bgp = bgp_get_default(); + int printedheader = 0; + int routes_total = 0; + int nhs_total = 0; + int nves_displayed = 0; + int routes_displayed = 0; + int nhs_displayed = 0; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + if (!bgp) { + fp(out, "Unable to find default BGP instance\n"); + return; + } + + /* + * loop over NVEs + */ + for (ALL_LIST_ELEMENTS_RO(&bgp->rfapi->descriptors, node, rfd)) { + + int printednve = 0; + afi_t afi; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_node *rn; + + if (!rfd->rib[afi]) + continue; + + for (rn = agg_route_top(rfd->rib[afi]); rn; + rn = agg_route_next(rn)) { + const struct prefix *p = + agg_node_get_prefix(rn); + struct skiplist *sl; + char str_pfx[PREFIX_STRLEN]; + int printedprefix = 0; + + if (!show_removed) + sl = rn->info; + else + sl = rn->aggregate; + + if (!sl) + continue; + + routes_total++; + nhs_total += skiplist_count(sl); + + if (pfx_match && !prefix_match(pfx_match, p) + && !prefix_match(p, pfx_match)) + continue; + + if (!printedheader) { + ++printedheader; + + fp(out, "\n[%s]\n", + show_removed ? "Removed" : "Active"); + fp(out, "%-15s %-15s\n", "Querying VN", + "Querying UN"); + fp(out, + " %-20s %-15s %-15s %4s %-8s %-8s\n", + "Prefix", "Registered VN", + "Registered UN", "Cost", "Lifetime", +#ifdef RFAPI_REGISTRATIONS_REPORT_AGE + "Age" +#else + "Remaining" +#endif + ); + } + if (!printednve) { + char str_vn[BUFSIZ]; + char str_un[BUFSIZ]; + + ++printednve; + ++nves_displayed; + + fp(out, "%-15s %-15s\n", + rfapiRfapiIpAddr2Str(&rfd->vn_addr, + str_vn, BUFSIZ), + rfapiRfapiIpAddr2Str(&rfd->un_addr, + str_un, + BUFSIZ)); + } + prefix2str(p, str_pfx, sizeof(str_pfx)); + // fp(out, " %s\n", buf); /* prefix */ + + routes_displayed++; + nhs_displayed += print_rib_sl( + fp, vty, out, sl, show_removed, str_pfx, + &printedprefix); + } + } + } + + if (routes_total) { + fp(out, "\n"); + fp(out, "Displayed %u NVEs, and %u out of %u %s prefixes", + nves_displayed, routes_displayed, routes_total, + show_removed ? "removed" : "active"); + if (nhs_displayed != routes_displayed + || nhs_total != routes_total) + fp(out, " with %u out of %u next hops", nhs_displayed, + nhs_total); + fp(out, "\n"); + } +} diff --git a/bgpd/rfapi/rfapi_rib.h b/bgpd/rfapi/rfapi_rib.h new file mode 100644 index 0000000..5fa838b --- /dev/null +++ b/bgpd/rfapi/rfapi_rib.h @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: rfapi_rib.h + * Purpose: per-nve rib + */ + +#ifndef QUAGGA_HGP_RFAPI_RIB_H +#define QUAGGA_HGP_RFAPI_RIB_H + +/* + * Key for indexing RIB and Pending RIB skiplists. For L3 RIBs, + * the VN address is sufficient because it represents the actual next hop. + * + * For L2 RIBs, it is possible to have multiple routes to a given L2 + * prefix via a given VN address, but each route having a unique aux_prefix. + */ +struct rfapi_rib_key { + struct prefix vn; + struct prefix_rd rd; + + /* + * for L2 routes: optional IP addr + * .family == 0 means "none" + */ + struct prefix aux_prefix; +}; +#include "rfapi.h" + +/* + * RFAPI Advertisement Data Block + * + * Holds NVE prefix advertisement information + */ +struct rfapi_adb { + union { + struct { + struct prefix prefix_ip; + struct prefix_rd prd; + struct prefix prefix_eth; + } s; /* mainly for legacy use */ + struct rfapi_rib_key key; + } u; + uint32_t lifetime; + uint8_t cost; + struct rfapi_l2address_option l2o; +}; + +struct rfapi_info { + struct rfapi_rib_key rk; /* NVE VN addr + aux addr */ + struct prefix un; + uint8_t cost; + uint32_t lifetime; + time_t last_sent_time; + uint32_t rsp_counter; /* dedup initial responses */ + struct bgp_tea_options *tea_options; + struct rfapi_un_option *un_options; + struct rfapi_vn_option *vn_options; + struct event *timer; +}; + +/* + * Work item for updated responses queue + */ +struct rfapi_updated_responses_queue { + struct rfapi_descriptor *rfd; + afi_t afi; +}; + + +extern void rfapiRibClear(struct rfapi_descriptor *rfd); + +extern void rfapiRibFree(struct rfapi_descriptor *rfd); + +extern void rfapiRibUpdatePendingNode(struct bgp *bgp, + struct rfapi_descriptor *rfd, + struct rfapi_import_table *it, + struct agg_node *it_node, + uint32_t lifetime); + +extern void rfapiRibUpdatePendingNodeSubtree(struct bgp *bgp, + struct rfapi_descriptor *rfd, + struct rfapi_import_table *it, + struct agg_node *it_node, + struct agg_node *omit_subtree, + uint32_t lifetime); + +extern int rfapiRibPreloadBi(struct agg_node *rfd_rib_node, + struct prefix *pfx_vn, struct prefix *pfx_un, + uint32_t lifetime, struct bgp_path_info *bpi); + +extern struct rfapi_next_hop_entry * +rfapiRibPreload(struct bgp *bgp, struct rfapi_descriptor *rfd, + struct rfapi_next_hop_entry *response, int use_eth_resolution); + +extern void rfapiRibPendingDeleteRoute(struct bgp *bgp, + struct rfapi_import_table *it, afi_t afi, + struct agg_node *it_node); + +extern void rfapiRibShowResponsesSummary(void *stream); + +extern void rfapiRibShowResponsesSummaryClear(void); + +extern void rfapiRibShowResponses(void *stream, struct prefix *pfx_match, + int show_removed); + +extern int rfapiRibFTDFilterRecentPrefix( + struct rfapi_descriptor *rfd, + struct agg_node *it_rn, /* import table node */ + struct prefix *pfx_target_original); /* query target */ + +extern void rfapiFreeRfapiUnOptionChain(struct rfapi_un_option *p); + +extern void rfapiFreeRfapiVnOptionChain(struct rfapi_vn_option *p); + +extern void +rfapiRibCheckCounts(int checkstats, /* validate rfd & global counts */ + unsigned int offset); /* number of ri's held separately */ + +/* enable for debugging; disable for performance */ +#if 0 +#define RFAPI_RIB_CHECK_COUNTS(checkstats, offset) rfapiRibCheckCounts(checkstats, offset) +#else +#define RFAPI_RIB_CHECK_COUNTS(checkstats, offset) +#endif + +extern void rfapi_rib_key_init(struct prefix *prefix, /* may be NULL */ + struct prefix_rd *rd, /* may be NULL */ + struct prefix *aux, /* may be NULL */ + struct rfapi_rib_key *rk); + +extern int rfapi_rib_key_cmp(const void *k1, const void *k2); + +extern void rfapiAdbFree(struct rfapi_adb *adb); + +#endif /* QUAGGA_HGP_RFAPI_RIB_H */ diff --git a/bgpd/rfapi/rfapi_vty.c b/bgpd/rfapi/rfapi_vty.c new file mode 100644 index 0000000..8f1f509 --- /dev/null +++ b/bgpd/rfapi/rfapi_vty.c @@ -0,0 +1,5008 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/memory.h" +#include "lib/routemap.h" +#include "lib/log.h" +#include "lib/linklist.h" +#include "lib/command.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_backend.h" + +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_community.h" +#include "bgpd/bgp_vnc_types.h" +#include "bgpd/bgp_label.h" + +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_rib.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/rfapi_ap.h" +#include "bgpd/rfapi/rfapi_encap_tlv.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define DEBUG_L2_EXTRA 0 +#define DEBUG_SHOW_EXTRA 0 + +#define VNC_SHOW_STR "VNC information\n" + +/* format related utilies */ + + +#define FMT_MIN 60 /* seconds */ +#define FMT_HOUR (60 * FMT_MIN) +#define FMT_DAY (24 * FMT_HOUR) +#define FMT_YEAR (365 * FMT_DAY) + +char *rfapiFormatSeconds(uint32_t seconds, char *buf, size_t len) +{ + int year, day, hour, min; + + if (seconds >= FMT_YEAR) { + year = seconds / FMT_YEAR; + seconds -= year * FMT_YEAR; + } else + year = 0; + + if (seconds >= FMT_DAY) { + day = seconds / FMT_DAY; + seconds -= day * FMT_DAY; + } else + day = 0; + + if (seconds >= FMT_HOUR) { + hour = seconds / FMT_HOUR; + seconds -= hour * FMT_HOUR; + } else + hour = 0; + + if (seconds >= FMT_MIN) { + min = seconds / FMT_MIN; + seconds -= min * FMT_MIN; + } else + min = 0; + + if (year > 0) { + snprintf(buf, len, "%dy%dd%dh", year, day, hour); + } else if (day > 0) { + snprintf(buf, len, "%dd%dh%dm", day, hour, min); + } else { + snprintf(buf, len, "%02d:%02d:%02d", hour, min, seconds); + } + + return buf; +} + +char *rfapiFormatAge(time_t age, char *buf, size_t len) +{ + time_t now, age_adjusted; + + now = monotime(NULL); + age_adjusted = now - age; + + return rfapiFormatSeconds(age_adjusted, buf, len); +} + + +/* + * Reimplementation of quagga/lib/prefix.c function, but + * for RFAPI-style prefixes + */ +void rfapiRprefixApplyMask(struct rfapi_ip_prefix *rprefix) +{ + uint8_t *pnt; + int index; + int offset; + + static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff}; + + switch (rprefix->prefix.addr_family) { + case AF_INET: + index = rprefix->length / 8; + if (index < 4) { + pnt = (uint8_t *)&rprefix->prefix.addr.v4; + offset = rprefix->length % 8; + pnt[index] &= maskbit[offset]; + index++; + while (index < 4) + pnt[index++] = 0; + } + break; + + case AF_INET6: + index = rprefix->length / 8; + if (index < 16) { + pnt = (uint8_t *)&rprefix->prefix.addr.v6; + offset = rprefix->length % 8; + pnt[index] &= maskbit[offset]; + index++; + while (index < 16) + pnt[index++] = 0; + } + break; + + default: + assert(0); + } +} + +/* + * translate a quagga prefix into a rfapi IP address. The + * prefix is REQUIRED to be 32 bits for IPv4 and 128 bits for IPv6 + * + * RETURNS: + * + * 0 Success + * <0 Error + */ +int rfapiQprefix2Raddr(struct prefix *qprefix, struct rfapi_ip_addr *raddr) +{ + memset(raddr, 0, sizeof(struct rfapi_ip_addr)); + raddr->addr_family = qprefix->family; + switch (qprefix->family) { + case AF_INET: + if (qprefix->prefixlen != IPV4_MAX_BITLEN) + return -1; + raddr->addr.v4 = qprefix->u.prefix4; + break; + case AF_INET6: + if (qprefix->prefixlen != IPV6_MAX_BITLEN) + return -1; + raddr->addr.v6 = qprefix->u.prefix6; + break; + default: + return -1; + } + return 0; +} + +/* + * Translate Quagga prefix to RFAPI prefix + */ +/* rprefix->cost set to 0 */ +void rfapiQprefix2Rprefix(const struct prefix *qprefix, + struct rfapi_ip_prefix *rprefix) +{ + memset(rprefix, 0, sizeof(struct rfapi_ip_prefix)); + rprefix->length = qprefix->prefixlen; + rprefix->prefix.addr_family = qprefix->family; + switch (qprefix->family) { + case AF_INET: + rprefix->prefix.addr.v4 = qprefix->u.prefix4; + break; + case AF_INET6: + rprefix->prefix.addr.v6 = qprefix->u.prefix6; + break; + default: + assert(0); + } +} + +int rfapiRprefix2Qprefix(struct rfapi_ip_prefix *rprefix, + struct prefix *qprefix) +{ + memset(qprefix, 0, sizeof(struct prefix)); + qprefix->prefixlen = rprefix->length; + qprefix->family = rprefix->prefix.addr_family; + + switch (rprefix->prefix.addr_family) { + case AF_INET: + qprefix->u.prefix4 = rprefix->prefix.addr.v4; + break; + case AF_INET6: + qprefix->u.prefix6 = rprefix->prefix.addr.v6; + break; + default: + return EAFNOSUPPORT; + } + return 0; +} + +/* + * returns 1 if prefixes have same addr family, prefix len, and address + * Note that host bits matter in this comparison! + * + * For paralellism with quagga/lib/prefix.c. if we need a comparison + * where host bits are ignored, call that function rfapiRprefixCmp. + */ +int rfapiRprefixSame(struct rfapi_ip_prefix *hp1, struct rfapi_ip_prefix *hp2) +{ + if (hp1->prefix.addr_family != hp2->prefix.addr_family) + return 0; + if (hp1->length != hp2->length) + return 0; + if (hp1->prefix.addr_family == AF_INET) + if (IPV4_ADDR_SAME(&hp1->prefix.addr.v4, &hp2->prefix.addr.v4)) + return 1; + if (hp1->prefix.addr_family == AF_INET6) + if (IPV6_ADDR_SAME(&hp1->prefix.addr.v6, &hp2->prefix.addr.v6)) + return 1; + return 0; +} + +int rfapiRaddr2Qprefix(struct rfapi_ip_addr *hia, struct prefix *pfx) +{ + memset(pfx, 0, sizeof(struct prefix)); + pfx->family = hia->addr_family; + + switch (hia->addr_family) { + case AF_INET: + pfx->prefixlen = IPV4_MAX_BITLEN; + pfx->u.prefix4 = hia->addr.v4; + break; + case AF_INET6: + pfx->prefixlen = IPV6_MAX_BITLEN; + pfx->u.prefix6 = hia->addr.v6; + break; + default: + return EAFNOSUPPORT; + } + return 0; +} + +void rfapiL2o2Qprefix(struct rfapi_l2address_option *l2o, struct prefix *pfx) +{ + memset(pfx, 0, sizeof(struct prefix)); + pfx->family = AF_ETHERNET; + pfx->prefixlen = 48; + pfx->u.prefix_eth = l2o->macaddr; +} + +char *rfapiEthAddr2Str(const struct ethaddr *ea, char *buf, int bufsize) +{ + return prefix_mac2str(ea, buf, bufsize); +} + +int rfapiStr2EthAddr(const char *str, struct ethaddr *ea) +{ + unsigned int a[6]; + int i; + + if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, + a + 4, a + 5) + != 6) { + + return EINVAL; + } + + for (i = 0; i < 6; ++i) + ea->octet[i] = a[i] & 0xff; + + return 0; +} + +const char *rfapi_ntop(int af, const void *src, char *buf, socklen_t size) +{ + if (af == AF_ETHERNET) { + return rfapiEthAddr2Str((const struct ethaddr *)src, buf, size); + } + + return inet_ntop(af, src, buf, size); +} + +int rfapiDebugPrintf(void *dummy, const char *format, ...) +{ + va_list args; + va_start(args, format); + vzlog(LOG_DEBUG, format, args); + va_end(args); + return 0; +} + +PRINTFRR(2, 3) +static int rfapiStdioPrintf(void *stream, const char *format, ...) +{ + FILE *file = NULL; + + va_list args; + va_start(args, format); + + switch ((uintptr_t)stream) { + case 1: + file = stdout; + break; + case 2: + file = stderr; + break; + default: + assert(0); + } + + vfprintf(file, format, args); + va_end(args); + return 0; +} + +/* Fake out for debug logging */ +static struct vty vty_dummy_zlog; +static struct vty vty_dummy_stdio; +#define HVTYNL ((vty == &vty_dummy_zlog)? "": "\n") + +static const char *str_vty_newline(struct vty *vty) +{ + if (vty == &vty_dummy_zlog) + return ""; + return "\n"; +} + +int rfapiStream2Vty(void *stream, /* input */ + int (**fp)(void *, const char *, ...), /* output */ + struct vty **vty, /* output */ + void **outstream, /* output */ + const char **vty_newline) /* output */ +{ + + if (!stream) { + vty_dummy_zlog.type = VTY_SHELL; /* for VTYNL */ + *vty = &vty_dummy_zlog; + *fp = (int (*)(void *, const char *, ...))rfapiDebugPrintf; + *outstream = NULL; + *vty_newline = str_vty_newline(*vty); + return 1; + } + + if (((uintptr_t)stream == (uintptr_t)1) + || ((uintptr_t)stream == (uintptr_t)2)) { + + vty_dummy_stdio.type = VTY_SHELL; /* for VTYNL */ + *vty = &vty_dummy_stdio; + *fp = (int (*)(void *, const char *, ...))rfapiStdioPrintf; + *outstream = stream; + *vty_newline = str_vty_newline(*vty); + return 1; + } + + *vty = stream; /* VTYNL requires vty to be legit */ + *fp = (int (*)(void *, const char *, ...))vty_out; + *outstream = stream; + *vty_newline = str_vty_newline(*vty); + return 1; +} + +/* called from bgpd/bgp_vty.c'route_vty_out() */ +void rfapi_vty_out_vncinfo(struct vty *vty, const struct prefix *p, + struct bgp_path_info *bpi, safi_t safi) +{ + char *s; + uint32_t lifetime; + + /* + * Print, on an indented line: + * UN address [if VPN route and VNC UN addr subtlv] + * EC list + * VNC lifetime + */ + vty_out(vty, " "); + + if (safi == SAFI_MPLS_VPN) { + struct prefix pfx_un; + + if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_un)) { + char buf[BUFSIZ]; + + vty_out(vty, "UN=%s", + inet_ntop(pfx_un.family, pfx_un.u.val, buf, + sizeof(buf))); + } + } + + if (bgp_attr_get_ecommunity(bpi->attr)) { + s = ecommunity_ecom2str(bgp_attr_get_ecommunity(bpi->attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " EC{%s}", s); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + if (BGP_PATH_INFO_NUM_LABELS(bpi)) { + if (bpi->extra->labels->label[0] == BGP_PREVENT_VRF_2_VRF_LEAK) + vty_out(vty, " label=VRF2VRF"); + else + vty_out(vty, " label=%u", + decode_label(&bpi->extra->labels->label[0])); + } + + if (bpi->attr->srv6_l3vpn || bpi->attr->srv6_vpn) { + struct in6_addr *sid_tmp = + bpi->attr->srv6_l3vpn ? (&bpi->attr->srv6_l3vpn->sid) + : (&bpi->attr->srv6_vpn->sid); + vty_out(vty, " sid=%pI6", sid_tmp); + + if (bpi->attr->srv6_l3vpn && + bpi->attr->srv6_l3vpn->loc_block_len != 0) { + vty_out(vty, " sid_structure=[%d,%d,%d,%d]", + bpi->attr->srv6_l3vpn->loc_block_len, + bpi->attr->srv6_l3vpn->loc_node_len, + bpi->attr->srv6_l3vpn->func_len, + bpi->attr->srv6_l3vpn->arg_len); + } + } + + if (!rfapiGetVncLifetime(bpi->attr, &lifetime)) { + vty_out(vty, " life=%d", lifetime); + } + + vty_out(vty, " type=%s, subtype=%d", zebra_route_string(bpi->type), + bpi->sub_type); + + vty_out(vty, "%s", HVTYNL); +} + +void rfapiPrintAttrPtrs(void *stream, struct attr *attr) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + struct transit *transit; + struct cluster_list *cluster; + struct ecommunity *ecomm; + struct community *comm; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + fp(out, "Attr[%p]:%s", attr, HVTYNL); + if (!attr) + return; + + /* IPv4 Nexthop */ + fp(out, " nexthop=%pI4%s", &attr->nexthop, HVTYNL); + + fp(out, " aspath=%p, refcnt=%d%s", attr->aspath, + (attr->aspath ? attr->aspath->refcnt : 0), HVTYNL); + + comm = bgp_attr_get_community(attr); + fp(out, " community=%p, refcnt=%d%s", comm, (comm ? comm->refcnt : 0), + HVTYNL); + + ecomm = bgp_attr_get_ecommunity(attr); + fp(out, " ecommunity=%p, refcnt=%d%s", ecomm, + (ecomm ? ecomm->refcnt : 0), HVTYNL); + + cluster = bgp_attr_get_cluster(attr); + fp(out, " cluster=%p, refcnt=%d%s", cluster, + (cluster ? cluster->refcnt : 0), HVTYNL); + + transit = bgp_attr_get_transit(attr); + fp(out, " transit=%p, refcnt=%d%s", transit, + (transit ? transit->refcnt : 0), HVTYNL); +} + +/* + * Print BPI in an Import Table + */ +void rfapiPrintBi(void *stream, struct bgp_path_info *bpi) +{ + char buf[BUFSIZ]; + char *s; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + char line[BUFSIZ]; + char *p = line; + int r; + int has_macaddr = 0; + struct ethaddr macaddr = {{0}}; + struct rfapi_l2address_option l2o_buf; + uint8_t l2hid = 0; /* valid if has_macaddr */ + +#define REMAIN (BUFSIZ - (p-line)) +#define INCP {p += (r > REMAIN)? REMAIN: r;} + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + if (!bpi) + return; + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra && + bpi->extra->vnc->vnc.import.timer) { + struct event *t = + (struct event *)bpi->extra->vnc->vnc.import.timer; + + r = snprintf(p, REMAIN, " [%4lu] ", + event_timer_remain_second(t)); + INCP; + + } else { + r = snprintf(p, REMAIN, " "); + INCP; + } + + if (bpi->extra) { + /* TBD This valid only for SAFI_MPLS_VPN, but not for encap */ + if (decode_rd_type(bpi->extra->vnc->vnc.import.rd.val) == + RD_TYPE_VNC_ETH) { + has_macaddr = 1; + memcpy(macaddr.octet, + bpi->extra->vnc->vnc.import.rd.val + 2, 6); + l2hid = bpi->extra->vnc->vnc.import.rd.val[1]; + } + } + + /* + * Print these items: + * type/subtype + * nexthop address + * lifetime + * RFP option sizes (they are opaque values) + * extended communities (RTs) + */ + uint32_t lifetime; + int printed_1st_gol = 0; + struct bgp_attr_encap_subtlv *pEncap; + struct prefix pfx_un; + int af = BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len); + + /* Nexthop */ + if (af == AF_INET) { + r = snprintfrr(p, REMAIN, "%pI4", + &bpi->attr->mp_nexthop_global_in); + INCP; + } else if (af == AF_INET6) { + r = snprintfrr(p, REMAIN, "%pI6", + &bpi->attr->mp_nexthop_global); + INCP; + } else { + r = snprintf(p, REMAIN, "?"); + INCP; + } + + /* + * VNC tunnel subtlv, if present, contains UN address + */ + if (!rfapiGetVncTunnelUnAddr(bpi->attr, &pfx_un)) { + r = snprintf(p, REMAIN, " un=%s", + inet_ntop(pfx_un.family, pfx_un.u.val, buf, + sizeof(buf))); + INCP; + } + + /* Lifetime */ + if (rfapiGetVncLifetime(bpi->attr, &lifetime)) { + r = snprintf(p, REMAIN, " nolife"); + INCP; + } else { + if (lifetime == 0xffffffff) + r = snprintf(p, REMAIN, " %6s", "infini"); + else + r = snprintf(p, REMAIN, " %6u", lifetime); + INCP; + } + + /* RFP option lengths */ + for (pEncap = bgp_attr_get_vnc_subtlvs(bpi->attr); pEncap; + pEncap = pEncap->next) { + + if (pEncap->type == BGP_VNC_SUBTLV_TYPE_RFPOPTION) { + if (printed_1st_gol) { + r = snprintf(p, REMAIN, ","); + INCP; + } else { + r = snprintf(p, REMAIN, + " "); /* leading space */ + INCP; + } + r = snprintf(p, REMAIN, "%d", pEncap->length); + INCP; + printed_1st_gol = 1; + } + } + + /* RT list */ + if (bgp_attr_get_ecommunity(bpi->attr)) { + s = ecommunity_ecom2str(bgp_attr_get_ecommunity(bpi->attr), + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + r = snprintf(p, REMAIN, " %s", s); + INCP; + XFREE(MTYPE_ECOMMUNITY_STR, s); + } + + r = snprintf(p, REMAIN, " bpi@%p", bpi); + INCP; + + r = snprintf(p, REMAIN, " p@%p", bpi->peer); + INCP; + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) { + r = snprintf(p, REMAIN, " HD=yes"); + INCP; + } else { + r = snprintf(p, REMAIN, " HD=no"); + INCP; + } + + if (bpi->attr->weight) { + r = snprintf(p, REMAIN, " W=%d", bpi->attr->weight); + INCP; + } + + if (bpi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + r = snprintf(p, REMAIN, " LP=%d", bpi->attr->local_pref); + INCP; + } else { + r = snprintf(p, REMAIN, " LP=unset"); + INCP; + } + + r = snprintf(p, REMAIN, " %c:%u", zebra_route_char(bpi->type), + bpi->sub_type); + INCP; + + fp(out, "%s%s", line, HVTYNL); + + if (has_macaddr) { + fp(out, " RD HID=%d ETH=%02x:%02x:%02x:%02x:%02x:%02x%s", + l2hid, macaddr.octet[0], macaddr.octet[1], macaddr.octet[2], + macaddr.octet[3], macaddr.octet[4], macaddr.octet[5], + HVTYNL); + } + + if (!rfapiGetL2o(bpi->attr, &l2o_buf)) { + fp(out, + " L2O ETH=%02x:%02x:%02x:%02x:%02x:%02x LBL=%d LNI=%d LHI=%hhu%s", + l2o_buf.macaddr.octet[0], l2o_buf.macaddr.octet[1], + l2o_buf.macaddr.octet[2], l2o_buf.macaddr.octet[3], + l2o_buf.macaddr.octet[4], l2o_buf.macaddr.octet[5], + l2o_buf.label, l2o_buf.logical_net_id, l2o_buf.local_nve_id, + HVTYNL); + } + if (bpi->extra && bpi->extra->vnc->vnc.import.aux_prefix.family) { + const char *sp; + + sp = rfapi_ntop(bpi->extra->vnc->vnc.import.aux_prefix.family, + &bpi->extra->vnc->vnc.import.aux_prefix.u.prefix, + buf, BUFSIZ); + buf[BUFSIZ - 1] = 0; + if (sp) { + fp(out, " IP: %s%s", sp, HVTYNL); + } + } + { + struct rfapi_un_option *uo = + rfapi_encap_tlv_to_un_option(bpi->attr); + if (uo) { + rfapi_print_tunneltype_option(stream, 8, &uo->v.tunnel); + rfapi_un_options_free(uo); + } + } +} + +char *rfapiMonitorVpn2Str(struct rfapi_monitor_vpn *m, char *buf, int size) +{ + char buf_pfx[BUFSIZ]; + char buf_vn[BUFSIZ]; + char buf_un[BUFSIZ]; + int rc; + + rfapiRfapiIpAddr2Str(&m->rfd->un_addr, buf_vn, BUFSIZ); + rfapiRfapiIpAddr2Str(&m->rfd->vn_addr, buf_un, BUFSIZ); + + rc = snprintf(buf, size, + "m=%p, next=%p, rfd=%p(vn=%s un=%s), p=%s/%d, node=%p", m, + m->next, m->rfd, buf_vn, buf_un, + inet_ntop(m->p.family, &m->p.u.prefix, buf_pfx, + sizeof(buf_pfx)), + m->p.prefixlen, m->node); + buf[size - 1] = 0; + if (rc >= size) + return NULL; + return buf; +} + +static void rfapiDebugPrintMonitorVpn(void *stream, struct rfapi_monitor_vpn *m) +{ + char buf[BUFSIZ]; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + rfapiMonitorVpn2Str(m, buf, BUFSIZ); + fp(out, " Mon %s%s", buf, HVTYNL); +} + +static void rfapiDebugPrintMonitorEncap(void *stream, + struct rfapi_monitor_encap *m) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out = NULL; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + fp(out, " Mon m=%p, next=%p, node=%p, bpi=%p%s", m, m->next, m->node, + m->bpi, HVTYNL); +} + +void rfapiShowItNode(void *stream, struct agg_node *rn) +{ + struct bgp_path_info *bpi; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + fp(out, "%pRN @%p #%d%s", rn, rn, agg_node_get_lock_count(rn), HVTYNL); + + for (bpi = rn->info; bpi; bpi = bpi->next) { + rfapiPrintBi(stream, bpi); + } + + /* doesn't show montors */ +} + +void rfapiShowImportTable(void *stream, const char *label, struct agg_table *rt, + int isvpn) +{ + struct agg_node *rn; + char buf[BUFSIZ]; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + fp(out, "Import Table [%s]%s", label, HVTYNL); + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + struct bgp_path_info *bpi; + const struct prefix *p = agg_node_get_prefix(rn); + + if (p->family == AF_ETHERNET) { + rfapiEthAddr2Str(&p->u.prefix_eth, buf, sizeof(buf)); + } else { + inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf)); + } + + fp(out, "%s/%d @%p #%d%s", buf, p->prefixlen, rn, + agg_node_get_lock_count(rn) + - 1, /* account for loop iterator locking */ + HVTYNL); + + for (bpi = rn->info; bpi; bpi = bpi->next) { + rfapiPrintBi(stream, bpi); + } + + if (isvpn) { + struct rfapi_monitor_vpn *m; + for (m = RFAPI_MONITOR_VPN(rn); m; m = m->next) { + rfapiDebugPrintMonitorVpn(stream, m); + } + } else { + struct rfapi_monitor_encap *m; + for (m = RFAPI_MONITOR_ENCAP(rn); m; m = m->next) { + rfapiDebugPrintMonitorEncap(stream, m); + } + } + } +} + +int rfapiShowVncQueries(void *stream, struct prefix *pfx_match) +{ + struct bgp *bgp; + struct rfapi *h; + struct listnode *node; + struct rfapi_descriptor *rfd; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + + int printedheader = 0; + int queries_total = 0; + int queries_displayed = 0; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return CMD_WARNING; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + if (!bgp) { + vty_out(vty, "No BGP instance\n"); + return CMD_WARNING; + } + + h = bgp->rfapi; + if (!h) { + vty_out(vty, "No RFAPI instance\n"); + return CMD_WARNING; + } + + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + + struct agg_node *rn; + int printedquerier = 0; + + if (!rfd->mon && + !(rfd->mon_eth && skiplist_count(rfd->mon_eth))) + continue; + + /* + * IP Queries + */ + if (rfd->mon) { + for (rn = agg_route_top(rfd->mon); rn; + rn = agg_route_next(rn)) { + const struct prefix *p = + agg_node_get_prefix(rn); + struct rfapi_monitor_vpn *m; + char buf_remain[BUFSIZ]; + char buf_pfx[BUFSIZ]; + + if (!rn->info) + continue; + + m = rn->info; + + ++queries_total; + + if (pfx_match && !prefix_match(pfx_match, p) + && !prefix_match(p, pfx_match)) + continue; + + ++queries_displayed; + + if (!printedheader) { + ++printedheader; + fp(out, "\n"); + fp(out, "%-15s %-15s %-15s %-10s\n", + "VN Address", "UN Address", "Target", + "Remaining"); + } + + if (!printedquerier) { + char buf_vn[BUFSIZ]; + char buf_un[BUFSIZ]; + + rfapiRfapiIpAddr2Str(&rfd->un_addr, + buf_un, BUFSIZ); + rfapiRfapiIpAddr2Str(&rfd->vn_addr, + buf_vn, BUFSIZ); + + fp(out, "%-15s %-15s", buf_vn, buf_un); + printedquerier = 1; + } else + fp(out, "%-15s %-15s", "", ""); + buf_remain[0] = 0; + rfapiFormatSeconds( + event_timer_remain_second(m->timer), + buf_remain, BUFSIZ); + fp(out, " %-15s %-10s\n", + inet_ntop(m->p.family, &m->p.u.prefix, + buf_pfx, sizeof(buf_pfx)), + buf_remain); + } + } + + /* + * Ethernet Queries + */ + if (rfd->mon_eth && skiplist_count(rfd->mon_eth)) { + + int rc; + void *cursor; + struct rfapi_monitor_eth *mon_eth; + + for (cursor = NULL, + rc = skiplist_next(rfd->mon_eth, NULL, + (void **)&mon_eth, &cursor); + rc == 0; + rc = skiplist_next(rfd->mon_eth, NULL, + (void **)&mon_eth, &cursor)) { + + char buf_remain[BUFSIZ]; + char buf_pfx[BUFSIZ]; + struct prefix pfx_mac; + + ++queries_total; + + vnc_zlog_debug_verbose( + "%s: checking rfd=%p mon_eth=%p", + __func__, rfd, mon_eth); + + memset((void *)&pfx_mac, 0, + sizeof(struct prefix)); + pfx_mac.family = AF_ETHERNET; + pfx_mac.prefixlen = 48; + pfx_mac.u.prefix_eth = mon_eth->macaddr; + + if (pfx_match + && !prefix_match(pfx_match, &pfx_mac) + && !prefix_match(&pfx_mac, pfx_match)) + continue; + + ++queries_displayed; + + if (!printedheader) { + ++printedheader; + fp(out, "\n"); + fp(out, + "%-15s %-15s %-17s %10s %-10s\n", + "VN Address", "UN Address", "Target", + "LNI", "Remaining"); + } + + if (!printedquerier) { + char buf_vn[BUFSIZ]; + char buf_un[BUFSIZ]; + + rfapiRfapiIpAddr2Str(&rfd->un_addr, + buf_un, BUFSIZ); + rfapiRfapiIpAddr2Str(&rfd->vn_addr, + buf_vn, BUFSIZ); + + fp(out, "%-15s %-15s", buf_vn, buf_un); + printedquerier = 1; + } else + fp(out, "%-15s %-15s", "", ""); + buf_remain[0] = 0; + rfapiFormatSeconds(event_timer_remain_second( + mon_eth->timer), + buf_remain, BUFSIZ); + fp(out, " %-17s %10d %-10s\n", + rfapi_ntop(pfx_mac.family, &pfx_mac.u.prefix, + buf_pfx, BUFSIZ), + mon_eth->logical_net_id, buf_remain); + } + } + } + + if (queries_total) { + fp(out, "\n"); + fp(out, "Displayed %d out of %d total queries\n", + queries_displayed, queries_total); + } + return CMD_SUCCESS; +} + +static int rfapiPrintRemoteRegBi(struct bgp *bgp, void *stream, + struct agg_node *rn, struct bgp_path_info *bpi) +{ + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + struct prefix pfx_un; + struct prefix pfx_vn; + uint8_t cost; + uint32_t lifetime; + bgp_encap_types tun_type = BGP_ENCAP_TYPE_MPLS;/*Default tunnel type*/ + + char buf_pfx[BUFSIZ]; + char buf_ntop[BUFSIZ]; + char buf_un[BUFSIZ]; + char buf_vn[BUFSIZ]; + char buf_lifetime[BUFSIZ]; + int nlines = 0; + const struct prefix *p = agg_node_get_prefix(rn); + + if (!stream) + return 0; /* for debug log, print into buf & call output once */ + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return 0; + + /* + * Prefix + */ + buf_pfx[0] = 0; + snprintf( + buf_pfx, sizeof(buf_pfx), "%s/%d", + rfapi_ntop(p->family, &p->u.prefix, buf_ntop, sizeof(buf_ntop)), + p->prefixlen); + buf_pfx[BUFSIZ - 1] = 0; + nlines++; + + /* + * UN addr + */ + buf_un[0] = 0; + if (!rfapiGetUnAddrOfVpnBi(bpi, &pfx_un)) { + snprintf(buf_un, sizeof(buf_un), "%s", + inet_ntop(pfx_un.family, &pfx_un.u.prefix, buf_ntop, + sizeof(buf_ntop))); + } + + bgp_attr_extcom_tunnel_type(bpi->attr, &tun_type); + /* + * VN addr + */ + buf_vn[0] = 0; + rfapiNexthop2Prefix(bpi->attr, &pfx_vn); + if (tun_type == BGP_ENCAP_TYPE_MPLS) { + /* MPLS carries un in nrli next hop (same as vn for IP tunnels) + */ + snprintf(buf_un, sizeof(buf_un), "%s", + inet_ntop(pfx_vn.family, &pfx_vn.u.prefix, buf_ntop, + sizeof(buf_ntop))); + if (BGP_PATH_INFO_NUM_LABELS(bpi)) { + uint32_t l = decode_label(&bpi->extra->labels->label[0]); + snprintf(buf_vn, sizeof(buf_vn), "Label: %d", l); + } else /* should never happen */ + { + snprintf(buf_vn, sizeof(buf_vn), "Label: N/A"); + } + } else { + snprintf(buf_vn, sizeof(buf_vn), "%s", + inet_ntop(pfx_vn.family, &pfx_vn.u.prefix, buf_ntop, + sizeof(buf_ntop))); + } + buf_vn[BUFSIZ - 1] = 0; + buf_un[BUFSIZ - 1] = 0; + + /* + * Cost is encoded in local_pref as (255-cost) + * See rfapi_import.c'rfapiRouteInfo2NextHopEntry() for conversion + * back to cost. + */ + uint32_t local_pref; + + if (bpi->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) + local_pref = bpi->attr->local_pref; + else + local_pref = 0; + cost = (local_pref > 255) ? 0 : 255 - local_pref; + + fp(out, "%-20s ", buf_pfx); + fp(out, "%-15s ", buf_vn); + fp(out, "%-15s ", buf_un); + fp(out, "%-4d ", cost); + + /* Lifetime */ + /* NB rfapiGetVncLifetime sets infinite value when returning !0 */ + if (rfapiGetVncLifetime(bpi->attr, &lifetime) + || (lifetime == RFAPI_INFINITE_LIFETIME)) { + + fp(out, "%-10s ", "infinite"); + } else { + time_t t_lifetime = lifetime; + rfapiFormatSeconds(t_lifetime, buf_lifetime, BUFSIZ); + fp(out, "%-10s ", buf_lifetime); + } + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED) && bpi->extra && + bpi->extra->vnc->vnc.import.timer) { + uint32_t remaining; + time_t age; + char buf_age[BUFSIZ]; + + struct event *t = + (struct event *)bpi->extra->vnc->vnc.import.timer; + remaining = event_timer_remain_second(t); + +#ifdef RFAPI_REGISTRATIONS_REPORT_AGE + /* + * Calculate when the timer started. Doing so here saves + * us a timestamp field in "struct bgp_path_info". + * + * See rfapi_import.c'rfapiBiStartWithdrawTimer() for the + * original calculation. + */ + age = rfapiGetHolddownFromLifetime(lifetime, factor) + - remaining; +#else /* report remaining time */ + age = remaining; +#endif + rfapiFormatSeconds(age, buf_age, BUFSIZ); + + fp(out, "%-10s ", buf_age); + + } else if (RFAPI_LOCAL_BI(bpi)) { + char buf_age[BUFSIZ]; + + if (bpi->extra && bpi->extra->vnc->vnc.import.create_time) { + rfapiFormatAge(bpi->extra->vnc->vnc.import.create_time, + buf_age, BUFSIZ); + } else { + buf_age[0] = '?'; + buf_age[1] = 0; + } + fp(out, "%-10s ", buf_age); + } + fp(out, "%s", HVTYNL); + + if (p->family == AF_ETHERNET) { + /* + * If there is a corresponding IP address && != VN address, + * print that on the next line + */ + + if (bpi->extra && bpi->extra->vnc->vnc.import.aux_prefix.family) { + const char *sp; + + sp = rfapi_ntop(bpi->extra->vnc->vnc.import.aux_prefix + .family, + &bpi->extra->vnc->vnc.import.aux_prefix + .u.prefix, + buf_ntop, BUFSIZ); + buf_ntop[BUFSIZ - 1] = 0; + + if (sp && strcmp(buf_vn, sp) != 0) { + fp(out, " IP: %s", sp); + if (nlines == 1) + nlines++; + } + } + } + if (tun_type != BGP_ENCAP_TYPE_MPLS && BGP_PATH_INFO_NUM_LABELS(bpi)) { + uint32_t l = decode_label(&bpi->extra->labels->label[0]); + + if (!MPLS_LABEL_IS_NULL(l)) { + fp(out, " Label: %d", l); + if (nlines == 1) + nlines++; + } + } + if (nlines > 1) + fp(out, "%s", HVTYNL); + + return 1; +} + +static int rfapiShowRemoteRegistrationsIt(struct bgp *bgp, void *stream, + struct rfapi_import_table *it, + struct prefix *prefix_only, + int show_expiring, /* either/or */ + int show_local, int show_remote, + int show_imported, /* either/or */ + uint32_t *pLni) /* AFI_L2VPN only */ +{ + afi_t afi; + int printed_rtlist_hdr = 0; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + int total = 0; + int printed = 0; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return printed; + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + + struct agg_node *rn; + + if (!it->imported_vpn[afi]) + continue; + + for (rn = agg_route_top(it->imported_vpn[afi]); rn; + rn = agg_route_next(rn)) { + const struct prefix *p = agg_node_get_prefix(rn); + struct bgp_path_info *bpi; + int count_only; + + /* allow for wider or more narrow mask from user */ + if (prefix_only && !prefix_match(prefix_only, p) + && !prefix_match(p, prefix_only)) + count_only = 1; + else + count_only = 0; + + for (bpi = rn->info; bpi; bpi = bpi->next) { + + if (!show_local && RFAPI_LOCAL_BI(bpi)) { + + /* local route from RFP */ + continue; + } + + if (!show_remote && !RFAPI_LOCAL_BI(bpi)) { + + /* remote route */ + continue; + } + + if (show_expiring + && !CHECK_FLAG(bpi->flags, + BGP_PATH_REMOVED)) + continue; + + if (!show_expiring + && CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + if (bpi->type == ZEBRA_ROUTE_BGP_DIRECT + || bpi->type + == ZEBRA_ROUTE_BGP_DIRECT_EXT) { + if (!show_imported) + continue; + } else { + if (show_imported) + continue; + } + + total++; + if (count_only == 1) + continue; + if (!printed_rtlist_hdr) { + const char *agetype = ""; + char *s; + const char *type = ""; + if (show_imported) { + type = "Imported"; + } else { + if (show_expiring) { + type = "Holddown"; + } else { + if (RFAPI_LOCAL_BI( + bpi)) { + type = "Local"; + } else { + type = "Remote"; + } + } + } + + s = ecommunity_ecom2str( + it->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + + if (pLni) { + fp(out, + "%s[%s] L2VPN Network 0x%x (%u) RT={%s}", + HVTYNL, type, *pLni, + (*pLni & 0xfff), s); + } else { + fp(out, "%s[%s] Prefix RT={%s}", + HVTYNL, type, s); + } + XFREE(MTYPE_ECOMMUNITY_STR, s); + + if (it->rfg && it->rfg->name) { + fp(out, " %s \"%s\"", + (it->rfg->type == RFAPI_GROUP_CFG_VRF + ? "VRF" + : "NVE group"), + it->rfg->name); + } + fp(out, "%s", HVTYNL); + if (show_expiring) { +#ifdef RFAPI_REGISTRATIONS_REPORT_AGE + agetype = "Age"; +#else + agetype = "Remaining"; +#endif + } else if (show_local) { + agetype = "Age"; + } + + printed_rtlist_hdr = 1; + + fp(out, + "%-20s %-15s %-15s %4s %-10s %-10s%s", + (pLni ? "L2 Address/IP" : "Prefix"), + "VN Address", "UN Address", "Cost", + "Lifetime", agetype, HVTYNL); + } + printed += rfapiPrintRemoteRegBi(bgp, stream, + rn, bpi); + } + } + } + + if (printed > 0) { + + const char *type = "prefixes"; + + if (show_imported) { + type = "imported prefixes"; + } else { + if (show_expiring) { + type = "prefixes in holddown"; + } else { + if (show_local && !show_remote) { + type = "locally registered prefixes"; + } else if (!show_local && show_remote) { + type = "remotely registered prefixes"; + } + } + } + + fp(out, "Displayed %d out of %d %s%s", printed, total, type, + HVTYNL); +#if DEBUG_SHOW_EXTRA + fp(out, "IT table above: it=%p%s", it, HVTYNL); +#endif + } + return printed; +} + + +/* + * rfapiShowRemoteRegistrations + * + * Similar to rfapiShowImportTable() above. This function + * is mean to produce the "remote" portion of the output + * of "show vnc registrations". + */ +int rfapiShowRemoteRegistrations(void *stream, struct prefix *prefix_only, + int show_expiring, int show_local, + int show_remote, int show_imported) +{ + struct bgp *bgp; + struct rfapi *h; + struct rfapi_import_table *it; + int printed = 0; + + bgp = bgp_get_default(); + if (!bgp) { + return printed; + } + + h = bgp->rfapi; + if (!h) { + return printed; + } + + for (it = h->imports; it; it = it->next) { + printed += rfapiShowRemoteRegistrationsIt( + bgp, stream, it, prefix_only, show_expiring, show_local, + show_remote, show_imported, NULL); + } + + if (h->import_mac) { + void *cursor = NULL; + int rc; + uintptr_t lni_as_ptr; + uint32_t lni; + uint32_t *pLni; + + for (rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, + (void **)&it, &cursor); + !rc; + rc = skiplist_next(h->import_mac, (void **)&lni_as_ptr, + (void **)&it, &cursor)) { + pLni = NULL; + if ((lni_as_ptr & 0xffffffff) == lni_as_ptr) { + lni = (uint32_t)(lni_as_ptr & 0xffffffff); + pLni = &lni; + } + + printed += rfapiShowRemoteRegistrationsIt( + bgp, stream, it, prefix_only, show_expiring, + show_local, show_remote, show_imported, pLni); + } + } + + return printed; +} + +/*------------------------------------------ + * rfapiRfapiIpAddr2Str + * + * UI helper: generate string from rfapi_ip_addr + * + * input: + * a IP v4/v6 address + * + * output + * buf put string here + * bufsize max space to write + * + * return value: + * NULL conversion failed + * non-NULL pointer to buf + --------------------------------------------*/ +const char *rfapiRfapiIpAddr2Str(struct rfapi_ip_addr *a, char *buf, + int bufsize) +{ + const char *rc = NULL; + + switch (a->addr_family) { + case AF_INET: + rc = inet_ntop(a->addr_family, &a->addr.v4, buf, bufsize); + break; + case AF_INET6: + rc = inet_ntop(a->addr_family, &a->addr.v6, buf, bufsize); + break; + } + return rc; +} + +void rfapiPrintRfapiIpAddr(void *stream, struct rfapi_ip_addr *a) +{ + char buf[BUFSIZ]; + const char *rc = NULL; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out = NULL; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + rc = rfapiRfapiIpAddr2Str(a, buf, BUFSIZ); + + if (rc) + fp(out, "%s", buf); +} + +const char *rfapiRfapiIpPrefix2Str(struct rfapi_ip_prefix *p, char *buf, + int bufsize) +{ + struct rfapi_ip_addr *a = &p->prefix; + const char *rc = NULL; + + switch (a->addr_family) { + case AF_INET: + rc = inet_ntop(a->addr_family, &a->addr.v4, buf, bufsize); + break; + case AF_INET6: + rc = inet_ntop(a->addr_family, &a->addr.v6, buf, bufsize); + break; + } + + if (rc) { + int alen = strlen(buf); + int remaining = bufsize - alen - 1; + int slen; + + if (remaining > 0) { + slen = snprintf(buf + alen, remaining, "/%u", + p->length); + if (slen < remaining) /* see man page for snprintf(3) */ + return rc; + } + } + + return NULL; +} + +void rfapiPrintRfapiIpPrefix(void *stream, struct rfapi_ip_prefix *p) +{ + char buf[BUFSIZ]; + const char *rc; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out = NULL; + const char *vty_newline; + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + rc = rfapiRfapiIpPrefix2Str(p, buf, BUFSIZ); + + if (rc) + fp(out, "%s:%u", buf, p->cost); + else + fp(out, "?/?:?"); +} + +void rfapiPrintAdvertisedInfo(struct vty *vty, struct rfapi_descriptor *rfd, + safi_t safi, struct prefix *p) +{ + afi_t afi; /* of the VN address */ + struct bgp_dest *bd; + struct bgp_path_info *bpi; + uint8_t type = ZEBRA_ROUTE_BGP; + struct bgp *bgp; + int printed = 0; + struct prefix_rd prd0; + struct prefix_rd *prd; + + /* + * Find the bgp_path in the RIB corresponding to this + * prefix and rfd + */ + + afi = family2afi(p->family); + assert(afi == AFI_IP || afi == AFI_IP6); + + bgp = bgp_get_default(); /* assume 1 instance for now */ + assert(bgp); + + if (safi == SAFI_ENCAP) { + memset(&prd0, 0, sizeof(prd0)); + prd0.family = AF_UNSPEC; + prd0.prefixlen = 64; + prd = &prd0; + } else { + prd = &rfd->rd; + } + bd = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, p, prd); + + vty_out(vty, " bd=%p%s", bd, HVTYNL); + + for (bpi = bgp_dest_get_bgp_path_info(bd); bpi; bpi = bpi->next) { + if (bpi->peer == rfd->peer && bpi->type == type && + bpi->sub_type == BGP_ROUTE_RFP && bpi->extra && + bpi->extra->vnc->vnc.export.rfapi_handle == (void *)rfd) { + rfapiPrintBi(vty, bpi); + printed = 1; + } + } + + if (!printed) { + vty_out(vty, " --?--%s", HVTYNL); + return; + } +} + +void rfapiPrintDescriptor(struct vty *vty, struct rfapi_descriptor *rfd) +{ + /* pHD un-addr vn-addr pCB cookie rd lifetime */ + /* RT export list */ + /* RT import list */ + /* list of advertised prefixes */ + /* dump import table */ + + char *s; + void *cursor; + int rc; + afi_t afi; + struct rfapi_adb *adb; + + vty_out(vty, "%-10p ", rfd); + rfapiPrintRfapiIpAddr(vty, &rfd->un_addr); + vty_out(vty, " "); + rfapiPrintRfapiIpAddr(vty, &rfd->vn_addr); + vty_out(vty, " %p %p ", rfd->response_cb, rfd->cookie); + vty_out(vty, "%pRDP", &rfd->rd); + vty_out(vty, " %d", rfd->response_lifetime); + vty_out(vty, " %s", (rfd->rfg ? rfd->rfg->name : "")); + vty_out(vty, "%s", HVTYNL); + + vty_out(vty, " Peer %p #%d%s", rfd->peer, rfd->peer->lock, HVTYNL); + + /* export RT list */ + if (rfd->rt_export_list) { + s = ecommunity_ecom2str(rfd->rt_export_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " Export %s%s", s, HVTYNL); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } else { + vty_out(vty, " Export (nil)%s", HVTYNL); + } + + /* import RT list */ + if (rfd->import_table) { + s = ecommunity_ecom2str(rfd->import_table->rt_import_list, + ECOMMUNITY_FORMAT_ROUTE_MAP, 0); + vty_out(vty, " Import %s%s", s, HVTYNL); + XFREE(MTYPE_ECOMMUNITY_STR, s); + } else { + vty_out(vty, " Import (nil)%s", HVTYNL); + } + + for (afi = AFI_IP; afi < AFI_MAX; ++afi) { + uint8_t family; + + family = afi2family(afi); + if (!family) + continue; + + cursor = NULL; + for (rc = skiplist_next(rfd->advertised.ipN_by_prefix, NULL, + (void **)&adb, &cursor); + rc == 0; + rc = skiplist_next(rfd->advertised.ipN_by_prefix, NULL, + (void **)&adb, &cursor)) { + + /* group like family prefixes together in output */ + if (family != adb->u.s.prefix_ip.family) + continue; + + vty_out(vty, " Adv Pfx: %pFX%s", &adb->u.s.prefix_ip, + HVTYNL); + rfapiPrintAdvertisedInfo(vty, rfd, SAFI_MPLS_VPN, + &adb->u.s.prefix_ip); + } + } + for (rc = skiplist_next(rfd->advertised.ip0_by_ether, NULL, + (void **)&adb, &cursor); + rc == 0; rc = skiplist_next(rfd->advertised.ip0_by_ether, NULL, + (void **)&adb, &cursor)) { + vty_out(vty, " Adv Pfx: %pFX%s", &adb->u.s.prefix_eth, HVTYNL); + + /* TBD update the following function to print ethernet info */ + /* Also need to pass/use rd */ + rfapiPrintAdvertisedInfo(vty, rfd, SAFI_MPLS_VPN, + &adb->u.s.prefix_ip); + } + vty_out(vty, "%s", HVTYNL); +} + +/* + * test scripts rely on first line for each nve starting in 1st column, + * leading whitespace for additional detail of that nve + */ +void rfapiPrintMatchingDescriptors(struct vty *vty, struct prefix *vn_prefix, + struct prefix *un_prefix) +{ + struct bgp *bgp; + struct rfapi *h; + struct listnode *ln; + struct rfapi_descriptor *rfd; + int printed = 0; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + if (!bgp) + return; + + h = bgp->rfapi; + assert(h); + + for (ln = listhead(&h->descriptors); ln; ln = listnextnode(ln)) { + rfd = listgetdata(ln); + + struct prefix pfx; + + if (vn_prefix) { + assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx)); + if (!prefix_match(vn_prefix, &pfx)) + continue; + } + + if (un_prefix) { + assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx)); + if (!prefix_match(un_prefix, &pfx)) + continue; + } + + if (!printed) { + /* print column header */ + vty_out(vty, "%s %s %s %s %s %s %s %s%s", "descriptor", + "un-addr", "vn-addr", "callback", "cookie", + "RD", "lifetime", "group", HVTYNL); + } + rfapiPrintDescriptor(vty, rfd); + printed = 1; + } +} + + +/* + * Parse an address and put into a struct prefix + */ +int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, struct prefix *p) +{ + if (!str2prefix(str, p)) { + vty_out(vty, "Malformed address \"%s\"%s", str ? str : "null", + HVTYNL); + return CMD_WARNING; + } + switch (p->family) { + case AF_INET: + if (p->prefixlen != IPV4_MAX_BITLEN) { + vty_out(vty, "Not a host address: \"%s\"%s", str, + HVTYNL); + return CMD_WARNING; + } + break; + case AF_INET6: + if (p->prefixlen != IPV6_MAX_BITLEN) { + vty_out(vty, "Not a host address: \"%s\"%s", str, + HVTYNL); + return CMD_WARNING; + } + break; + default: + vty_out(vty, "Invalid address \"%s\"%s", str, HVTYNL); + return CMD_WARNING; + } + return 0; +} + +int rfapiCliGetRfapiIpAddr(struct vty *vty, const char *str, + struct rfapi_ip_addr *hai) +{ + struct prefix pfx; + int rc; + + rc = rfapiCliGetPrefixAddr(vty, str, &pfx); + if (rc) + return rc; + + hai->addr_family = pfx.family; + if (pfx.family == AF_INET) + hai->addr.v4 = pfx.u.prefix4; + else + hai->addr.v6 = pfx.u.prefix6; + + return 0; +} + +/* + * Note: this function does not flush vty output, so if it is called + * with a stream pointing to a vty, the user will have to type something + * before the callback output shows up + */ +void rfapiPrintNhl(void *stream, struct rfapi_next_hop_entry *next_hops) +{ + struct rfapi_next_hop_entry *nh; + int count; + + int (*fp)(void *, const char *, ...); + struct vty *vty; + void *out; + const char *vty_newline; + +#define REMAIN (BUFSIZ - (p-line)) +#define INCP {p += (r > REMAIN)? REMAIN: r;} + + if (rfapiStream2Vty(stream, &fp, &vty, &out, &vty_newline) == 0) + return; + + for (nh = next_hops, count = 1; nh; nh = nh->next, ++count) { + + char line[BUFSIZ]; + char *p = line; + int r; + + r = snprintf(p, REMAIN, "%3d pfx=", count); + INCP; + + if (rfapiRfapiIpPrefix2Str(&nh->prefix, p, REMAIN)) { + /* it fit, so count length */ + r = strlen(p); + } else { + /* didn't fit */ + goto truncate; + } + INCP; + + r = snprintf(p, REMAIN, ", un="); + INCP; + + if (rfapiRfapiIpAddr2Str(&nh->un_address, p, REMAIN)) { + /* it fit, so count length */ + r = strlen(p); + } else { + /* didn't fit */ + goto truncate; + } + INCP; + + r = snprintf(p, REMAIN, ", vn="); + INCP; + + if (rfapiRfapiIpAddr2Str(&nh->vn_address, p, REMAIN)) { + /* it fit, so count length */ + r = strlen(p); + } else { + /* didn't fit */ + goto truncate; + } + INCP; + + truncate: + line[BUFSIZ - 1] = 0; + fp(out, "%s%s", line, HVTYNL); + + /* + * options + */ + if (nh->vn_options) { + struct rfapi_vn_option *vo; + char offset[] = " "; + + for (vo = nh->vn_options; vo; vo = vo->next) { + char pbuf[100]; + + switch (vo->type) { + case RFAPI_VN_OPTION_TYPE_L2ADDR: + rfapiEthAddr2Str(&vo->v.l2addr.macaddr, + pbuf, sizeof(pbuf)); + fp(out, + "%sL2 %s LBL=0x%06x NETID=0x%06x NVEID=%d%s", + offset, pbuf, + (vo->v.l2addr.label & 0x00ffffff), + (vo->v.l2addr.logical_net_id + & 0x00ffffff), + vo->v.l2addr.local_nve_id, HVTYNL); + break; + + case RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP: + fp(out, "%sLNH %pFX cost=%d%s", offset, + &vo->v.local_nexthop.addr, + vo->v.local_nexthop.cost, HVTYNL); + break; + + case RFAPI_VN_OPTION_TYPE_INTERNAL_RD: + fp(out, + "%svn option type %d (unknown)%s", + offset, vo->type, HVTYNL); + break; + } + } + } + if (nh->un_options) { + struct rfapi_un_option *uo; + char offset[] = " "; + + for (uo = nh->un_options; uo; uo = uo->next) { + switch (uo->type) { + case RFAPI_UN_OPTION_TYPE_TUNNELTYPE: + rfapi_print_tunneltype_option( + stream, 8, &uo->v.tunnel); + break; + case RFAPI_UN_OPTION_TYPE_PROVISIONAL: + fp(out, "%sUN Option type %d%s", offset, + uo->type, vty_newline); + break; + } + } + } + } +} + +/*********************************************************************** + * STATIC ROUTES + ***********************************************************************/ + +/* + * Add another nexthop to the NHL + */ +static void rfapiAddDeleteLocalRfpPrefix(struct rfapi_ip_addr *un_addr, + struct rfapi_ip_addr *vn_addr, + struct rfapi_ip_prefix *rprefix, + int is_add, + uint32_t lifetime, /* add only */ + struct rfapi_vn_option *vn_options, + struct rfapi_next_hop_entry **head, + struct rfapi_next_hop_entry **tail) +{ + struct rfapi_next_hop_entry *new; + + /* + * construct NHL + */ + + new = XCALLOC(MTYPE_RFAPI_NEXTHOP, sizeof(struct rfapi_next_hop_entry)); + new->prefix = *rprefix; + new->un_address = *un_addr; + new->vn_address = *vn_addr; + + new->vn_options = vn_options; + if (is_add) { + new->lifetime = lifetime; + } else { + new->lifetime = RFAPI_REMOVE_RESPONSE_LIFETIME; + } + + if (*tail) + (*tail)->next = new; + *tail = new; + if (!*head) { + *head = new; + } +} + + +static int +register_add(struct vty *vty, struct cmd_token *carg_prefix, + struct cmd_token *carg_vn, struct cmd_token *carg_un, + struct cmd_token *carg_cost, /* optional */ + struct cmd_token *carg_lifetime, /* optional */ + struct cmd_token *carg_macaddr, /* optional */ + struct cmd_token + *carg_vni, /* mac present=>mandatory Virtual Network ID */ + int argc, struct cmd_token **argv) +{ + const char *arg_prefix = carg_prefix ? carg_prefix->arg : NULL; + const char *arg_vn = carg_vn ? carg_vn->arg : NULL; + const char *arg_un = carg_un ? carg_un->arg : NULL; + const char *arg_cost = carg_cost ? carg_cost->arg : NULL; + const char *arg_lifetime = carg_lifetime ? carg_lifetime->arg : NULL; + const char *arg_macaddr = carg_macaddr ? carg_macaddr->arg : NULL; + const char *arg_vni = carg_vni ? carg_vni->arg : NULL; + struct rfapi_ip_addr vn_address; + struct rfapi_ip_addr un_address; + struct prefix pfx; + struct rfapi_ip_prefix rpfx; + uint32_t cost; + uint32_t lnh_cost; + uint32_t lifetime; + rfapi_handle rfd; + struct rfapi_vn_option optary[10]; /* XXX must be big enough */ + struct rfapi_vn_option *opt = NULL; + int opt_next = 0; + + int rc = CMD_WARNING_CONFIG_FAILED; + char *endptr; + struct bgp *bgp; + struct rfapi *h; + struct rfapi_cfg *rfapi_cfg; + + const char *arg_lnh = NULL; + const char *arg_lnh_cost = NULL; + + bgp = bgp_get_default(); /* assume 1 instance for now */ + if (!bgp) { + if (vty) + vty_out(vty, "BGP not configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + h = bgp->rfapi; + rfapi_cfg = bgp->rfapi_cfg; + if (!h || !rfapi_cfg) { + if (vty) + vty_out(vty, "RFAPI not configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + for (; argc; --argc, ++argv) { + if (strmatch(argv[0]->text, "local-next-hop")) { + if (arg_lnh) { + vty_out(vty, + "local-next-hop specified more than once\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (argc <= 1) { + vty_out(vty, + "Missing parameter for local-next-hop\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ++argv; + --argc; + arg_lnh = argv[0]->arg; + } + if (strmatch(argv[0]->text, "local-cost")) { + if (arg_lnh_cost) { + vty_out(vty, + "local-cost specified more than once\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (argc <= 1) { + vty_out(vty, + "Missing parameter for local-cost\n"); + return CMD_WARNING_CONFIG_FAILED; + } + ++argv; + --argc; + arg_lnh_cost = argv[0]->arg; + } + } + + if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_vn, &vn_address))) + goto fail; + if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_un, &un_address))) + goto fail; + + /* arg_prefix is optional if mac address is given */ + if (arg_macaddr && !arg_prefix) { + /* + * fake up a 0/32 or 0/128 prefix + */ + switch (vn_address.addr_family) { + case AF_INET: + arg_prefix = "0.0.0.0/32"; + break; + case AF_INET6: + arg_prefix = "0::0/128"; + break; + default: + vty_out(vty, + "Internal error, unknown VN address family\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (!str2prefix(arg_prefix, &pfx)) { + vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); + goto fail; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "prefix \"%s\" has invalid address family\n", + arg_prefix); + goto fail; + } + + + memset(optary, 0, sizeof(optary)); + + if (arg_cost) { + endptr = NULL; + cost = strtoul(arg_cost, &endptr, 10); + if (*endptr != '\0' || cost > 255) { + vty_out(vty, "%% Invalid %s value\n", "cost"); + goto fail; + } + } else { + cost = 255; + } + + if (arg_lifetime) { + if (!strcmp(arg_lifetime, "infinite")) { + lifetime = RFAPI_INFINITE_LIFETIME; + } else { + endptr = NULL; + lifetime = strtoul(arg_lifetime, &endptr, 10); + if (*endptr != '\0') { + vty_out(vty, "%% Invalid %s value\n", + "lifetime"); + goto fail; + } + } + } else { + lifetime = RFAPI_INFINITE_LIFETIME; /* default infinite */ + } + + if (arg_lnh_cost) { + if (!arg_lnh) { + vty_out(vty, + "%% %s may only be specified with local-next-hop\n", + "local-cost"); + goto fail; + } + endptr = NULL; + lnh_cost = strtoul(arg_lnh_cost, &endptr, 10); + if (*endptr != '\0' || lnh_cost > 255) { + vty_out(vty, "%% Invalid %s value\n", "local-cost"); + goto fail; + } + } else { + lnh_cost = 255; + } + + if (arg_lnh) { + if (!arg_prefix) { + vty_out(vty, + "%% %s may only be specified with prefix\n", + "local-next-hop"); + goto fail; + } + if ((rc = rfapiCliGetPrefixAddr( + vty, arg_lnh, + &optary[opt_next].v.local_nexthop.addr))) { + + goto fail; + } + + optary[opt_next].v.local_nexthop.cost = lnh_cost; + optary[opt_next].type = RFAPI_VN_OPTION_TYPE_LOCAL_NEXTHOP; + + if (opt_next) { + optary[opt_next - 1].next = optary + opt_next; + } else { + opt = optary; + } + ++opt_next; + } + + if (arg_vni && !arg_macaddr) { + vty_out(vty, "%% %s may only be specified with mac address\n", + "virtual-network-identifier"); + goto fail; + } + + if (arg_macaddr) { + if (!arg_vni) { + vty_out(vty, + "Missing \"vni\" parameter (mandatory with mac)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + optary[opt_next].v.l2addr.logical_net_id = + strtoul(arg_vni, NULL, 10); + + if ((rc = rfapiStr2EthAddr( + arg_macaddr, + &optary[opt_next].v.l2addr.macaddr))) { + vty_out(vty, "Invalid %s value\n", "mac address"); + goto fail; + } + /* TBD label, NVE ID */ + + optary[opt_next].type = RFAPI_VN_OPTION_TYPE_L2ADDR; + + if (opt_next) { + optary[opt_next - 1].next = optary + opt_next; + } else { + opt = optary; + } + ++opt_next; + } + + vnc_zlog_debug_verbose( + "%s: vn=%s, un=%s, prefix=%s, cost=%s, lifetime=%s, lnh=%s", + __func__, arg_vn, arg_un, arg_prefix, + (arg_cost ? arg_cost : "NULL"), + (arg_lifetime ? arg_lifetime : "NULL"), + (arg_lnh ? arg_lnh : "NULL")); + + rfapiQprefix2Rprefix(&pfx, &rpfx); + + rpfx.cost = cost & 255; + + /* look up rf descriptor, call open if it doesn't exist */ + rc = rfapi_find_rfd(bgp, &vn_address, &un_address, + (struct rfapi_descriptor **)&rfd); + if (rc) { + if (ENOENT == rc) { + struct rfapi_un_option uo; + + /* + * flag descriptor as provisionally opened for static + * route + * registration so that we can fix up the other + * parameters + * when the real open comes along + */ + memset(&uo, 0, sizeof(uo)); + uo.type = RFAPI_UN_OPTION_TYPE_PROVISIONAL; + + rc = rfapi_open(rfapi_get_rfp_start_val_by_bgp(bgp), + &vn_address, &un_address, + &uo, /* flags */ + NULL, NULL, /* no userdata */ + &rfd); + if (rc) { + vty_out(vty, + "Can't open session for this NVE: %s\n", + rfapi_error_str(rc)); + rc = CMD_WARNING_CONFIG_FAILED; + goto fail; + } + } else { + vty_out(vty, "Can't find session for this NVE: %s\n", + rfapi_error_str(rc)); + goto fail; + } + } + + rc = rfapi_register(rfd, &rpfx, lifetime, NULL, opt, + RFAPI_REGISTER_ADD); + if (!rc) { + struct rfapi_next_hop_entry *head = NULL; + struct rfapi_next_hop_entry *tail = NULL; + struct rfapi_vn_option *vn_opt_new; + + vnc_zlog_debug_verbose( + "%s: rfapi_register succeeded, returning 0", __func__); + + if (h->rfp_methods.local_cb) { + struct rfapi_descriptor *r = + (struct rfapi_descriptor *)rfd; + vn_opt_new = rfapi_vn_options_dup(opt); + + rfapiAddDeleteLocalRfpPrefix(&r->un_addr, &r->vn_addr, + &rpfx, 1, lifetime, + vn_opt_new, &head, &tail); + if (head) { + h->flags |= RFAPI_INCALLBACK; + (*h->rfp_methods.local_cb)(head, r->cookie); + h->flags &= ~RFAPI_INCALLBACK; + } + head = tail = NULL; + } + return 0; + } + + vnc_zlog_debug_verbose("%s: rfapi_register failed", __func__); + vty_out(vty, "\n"); + vty_out(vty, "Registration failed.\n"); + vty_out(vty, + "Confirm that either the VN or UN address matches a configured NVE group.\n"); + return CMD_WARNING_CONFIG_FAILED; + +fail: + vnc_zlog_debug_verbose("%s: fail, rc=%d", __func__, rc); + return rc; +} + +/************************************************************************ + * Add prefix With LNH_OPTIONS... + ************************************************************************/ +DEFUN (add_vnc_prefix_cost_life_lnh, + add_vnc_prefix_cost_life_lnh_cmd, + "add vnc prefix vn un cost (0-255) lifetime (1-4294967295) LNH_OPTIONS...", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n" + "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[9], argv[11], + /* mac vni */ + NULL, NULL, argc - 12, argv + 12); +} + +DEFUN (add_vnc_prefix_life_cost_lnh, + add_vnc_prefix_life_cost_lnh_cmd, + "add vnc prefix vn un lifetime (1-4294967295) cost (0-255) LNH_OPTIONS...", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[11], argv[9], + /* mac vni */ + NULL, NULL, argc - 12, argv + 12); +} + +DEFUN (add_vnc_prefix_cost_lnh, + add_vnc_prefix_cost_lnh_cmd, + "add vnc prefix vn un cost (0-255) LNH_OPTIONS...", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[9], NULL, + /* mac vni */ + NULL, NULL, argc - 10, argv + 10); +} + +DEFUN (add_vnc_prefix_life_lnh, + add_vnc_prefix_life_lnh_cmd, + "add vnc prefix vn un lifetime (1-4294967295) LNH_OPTIONS...", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n" + "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], NULL, argv[9], + /* mac vni */ + NULL, NULL, argc - 10, argv + 10); +} + +DEFUN (add_vnc_prefix_lnh, + add_vnc_prefix_lnh_cmd, + "add vnc prefix vn un LNH_OPTIONS...", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "[local-next-hop (A.B.C.D|X:X::X:X)] [local-cost <0-255>]\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], NULL, NULL, + /* mac vni */ + NULL, NULL, argc - 8, argv + 8); +} + +/************************************************************************ + * Add prefix Without LNH_OPTIONS... + ************************************************************************/ +DEFUN (add_vnc_prefix_cost_life, + add_vnc_prefix_cost_life_cmd, + "add vnc prefix vn un cost (0-255) lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[9], argv[11], + /* mac vni */ + NULL, NULL, 0, NULL); +} + +DEFUN (add_vnc_prefix_life_cost, + add_vnc_prefix_life_cost_cmd, + "add vnc prefix vn un lifetime (1-4294967295) cost (0-255)", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[11], argv[9], + /* mac vni */ + NULL, NULL, 0, NULL); +} + +DEFUN (add_vnc_prefix_cost, + add_vnc_prefix_cost_cmd, + "add vnc prefix vn un cost (0-255)", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], argv[9], NULL, + /* mac vni */ + NULL, NULL, 0, NULL); +} + +DEFUN (add_vnc_prefix_life, + add_vnc_prefix_life_cmd, + "add vnc prefix vn un lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], NULL, argv[9], + /* mac vni */ + NULL, NULL, 0, NULL); +} + +DEFUN (add_vnc_prefix, + add_vnc_prefix_cmd, + "add vnc prefix vn un ", + "Add registration\n" + "VNC Information\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[3], argv[5], argv[7], NULL, NULL, + /* mac vni */ + NULL, NULL, 0, NULL); +} + +/************************************************************************ + * Mac address registrations + ************************************************************************/ +DEFUN (add_vnc_mac_vni_prefix_cost_life, + add_vnc_mac_vni_prefix_cost_life_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un prefix cost (0-255) lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[11], argv[7], argv[9], argv[13], argv[15], + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + + +DEFUN (add_vnc_mac_vni_prefix_life, + add_vnc_mac_vni_prefix_life_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un prefix lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[11], argv[7], argv[9], NULL, argv[13], + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + +DEFUN (add_vnc_mac_vni_prefix_cost, + add_vnc_mac_vni_prefix_cost_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un prefix cost (0-255)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Administrative cost [default: 255]\n" "Administrative cost\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[11], argv[7], argv[9], argv[13], NULL, + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + +DEFUN (add_vnc_mac_vni_prefix, + add_vnc_mac_vni_prefix_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un prefix ", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" "IPv6 prefix\n") +{ + /* pfx vn un cost life */ + return register_add(vty, argv[11], argv[7], argv[9], NULL, NULL, + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + +DEFUN (add_vnc_mac_vni_cost_life, + add_vnc_mac_vni_cost_life_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un cost (0-255) lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" + "Administrative cost\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, NULL, argv[7], argv[9], argv[11], argv[13], + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + + +DEFUN (add_vnc_mac_vni_cost, + add_vnc_mac_vni_cost_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un cost (0-255)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Administrative cost [default: 255]\n" "Administrative cost\n") +{ + /* pfx vn un cost life */ + return register_add(vty, NULL, argv[7], argv[9], argv[11], NULL, + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + + +DEFUN (add_vnc_mac_vni_life, + add_vnc_mac_vni_life_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un lifetime (1-4294967295)", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Registration lifetime [default: infinite]\n" + "Lifetime value in seconds\n") +{ + /* pfx vn un cost life */ + return register_add(vty, NULL, argv[7], argv[9], NULL, argv[11], + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + + +DEFUN (add_vnc_mac_vni, + add_vnc_mac_vni_cmd, + "add vnc mac X:X:X:X:X:X virtual-network-identifier (1-4294967295) vn un ", + "Add registration\n" + "VNC Information\n" + "Add/modify mac address information\n" + "MAC address\n" + "Virtual Network Identifier follows\n" + "Virtual Network Identifier\n" + "VN address of NVE\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "UN IPv4 interface address\n" "UN IPv6 interface address\n") +{ + /* pfx vn un cost life */ + return register_add(vty, NULL, argv[7], argv[9], NULL, NULL, + /* mac vni */ + argv[3], argv[5], 0, NULL); +} + +/************************************************************************ + * Delete prefix + ************************************************************************/ + +struct rfapi_local_reg_delete_arg { + /* + * match parameters + */ + struct bgp *bgp; + struct rfapi_ip_addr un_address; /* AF==0: wildcard */ + struct rfapi_ip_addr vn_address; /* AF==0: wildcard */ + struct prefix prefix; /* AF==0: wildcard */ + struct prefix_rd rd; /* plen!=64: wildcard */ + struct rfapi_nve_group_cfg *rfg; /* NULL: wildcard */ + + struct rfapi_l2address_option_match l2o; + + /* + * result parameters + */ + struct vty *vty; + uint32_t reg_count; + uint32_t pfx_count; + uint32_t query_count; + + uint32_t failed_pfx_count; + + uint32_t nve_count; + struct skiplist *nves; + + uint32_t remote_active_nve_count; + uint32_t remote_active_pfx_count; + uint32_t remote_holddown_nve_count; + uint32_t remote_holddown_pfx_count; +}; + +struct nve_addr { + struct rfapi_ip_addr vn; + struct rfapi_ip_addr un; + struct rfapi_descriptor *rfd; + struct rfapi_local_reg_delete_arg *cda; +}; + +static void nve_addr_free(void *hap) +{ + ((struct nve_addr *)hap)->cda->nve_count += 1; + XFREE(MTYPE_RFAPI_NVE_ADDR, hap); +} + +static int nve_addr_cmp(const void *k1, const void *k2) +{ + const struct nve_addr *a = (struct nve_addr *)k1; + const struct nve_addr *b = (struct nve_addr *)k2; + int ret = 0; + + if (!a || !b) { + return (a - b); + } + if (a->un.addr_family != b->un.addr_family) { + return (a->un.addr_family - b->un.addr_family); + } + if (a->vn.addr_family != b->vn.addr_family) { + return (a->vn.addr_family - b->vn.addr_family); + } + if (a->un.addr_family == AF_INET) { + ret = IPV4_ADDR_CMP(&a->un.addr.v4, &b->un.addr.v4); + if (ret != 0) { + return ret; + } + } else if (a->un.addr_family == AF_INET6) { + ret = IPV6_ADDR_CMP(&a->un.addr.v6, &b->un.addr.v6); + if (ret != 0) { + return ret; + } + } else { + assert(0); + } + if (a->vn.addr_family == AF_INET) { + ret = IPV4_ADDR_CMP(&a->vn.addr.v4, &b->vn.addr.v4); + if (ret != 0) + return ret; + } else if (a->vn.addr_family == AF_INET6) { + ret = IPV6_ADDR_CMP(&a->vn.addr.v6, &b->vn.addr.v6); + if (ret == 0) { + return ret; + } + } else { + assert(0); + } + return 0; +} + +static int parse_deleter_args(struct vty *vty, struct bgp *bgp, + const char *arg_prefix, const char *arg_vn, + const char *arg_un, const char *arg_l2addr, + const char *arg_vni, const char *arg_rd, + struct rfapi_nve_group_cfg *arg_rfg, + struct rfapi_local_reg_delete_arg *rcdarg) +{ + int rc = CMD_WARNING; + + memset(rcdarg, 0, sizeof(struct rfapi_local_reg_delete_arg)); + + rcdarg->vty = vty; + if (bgp == NULL) + bgp = bgp_get_default(); + rcdarg->bgp = bgp; + rcdarg->rfg = arg_rfg; /* may be NULL */ + + if (arg_vn && strcmp(arg_vn, "*")) { + if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_vn, + &rcdarg->vn_address))) + return rc; + } + if (arg_un && strcmp(arg_un, "*")) { + if ((rc = rfapiCliGetRfapiIpAddr(vty, arg_un, + &rcdarg->un_address))) + return rc; + } + if (arg_prefix && strcmp(arg_prefix, "*")) { + + if (!str2prefix(arg_prefix, &rcdarg->prefix)) { + vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); + return rc; + } + } + + if (arg_l2addr) { + if (!arg_vni) { + vty_out(vty, "Missing VNI\n"); + return rc; + } + if (strcmp(arg_l2addr, "*")) { + if ((rc = rfapiStr2EthAddr(arg_l2addr, + &rcdarg->l2o.o.macaddr))) { + vty_out(vty, "Malformed L2 Address \"%s\"\n", + arg_l2addr); + return rc; + } + rcdarg->l2o.flags |= RFAPI_L2O_MACADDR; + } + if (strcmp(arg_vni, "*")) { + rcdarg->l2o.o.logical_net_id = + strtoul(arg_vni, NULL, 10); + rcdarg->l2o.flags |= RFAPI_L2O_LNI; + } + } + if (arg_rd) { + if (!str2prefix_rd(arg_rd, &rcdarg->rd)) { + vty_out(vty, "Malformed RD \"%s\"\n", arg_rd); + return rc; + } + } + + return CMD_SUCCESS; +} + +static int +parse_deleter_tokens(struct vty *vty, struct bgp *bgp, + struct cmd_token *carg_prefix, struct cmd_token *carg_vn, + struct cmd_token *carg_un, struct cmd_token *carg_l2addr, + struct cmd_token *carg_vni, struct cmd_token *carg_rd, + struct rfapi_nve_group_cfg *arg_rfg, + struct rfapi_local_reg_delete_arg *rcdarg) +{ + const char *arg_prefix = carg_prefix ? carg_prefix->arg : NULL; + const char *arg_vn = carg_vn ? carg_vn->arg : NULL; + const char *arg_un = carg_un ? carg_un->arg : NULL; + const char *arg_l2addr = carg_l2addr ? carg_l2addr->arg : NULL; + const char *arg_vni = carg_vni ? carg_vni->arg : NULL; + const char *arg_rd = carg_rd ? carg_rd->arg : NULL; + return parse_deleter_args(vty, bgp, arg_prefix, arg_vn, arg_un, + arg_l2addr, arg_vni, arg_rd, arg_rfg, rcdarg); +} + +static void record_nve_in_cda_list(struct rfapi_local_reg_delete_arg *cda, + struct rfapi_ip_addr *un_address, + struct rfapi_ip_addr *vn_address, + struct rfapi_descriptor *rfd) +{ + struct nve_addr ha; + struct nve_addr *hap; + + memset(&ha, 0, sizeof(ha)); + ha.un = *un_address; + ha.vn = *vn_address; + ha.rfd = rfd; + + if (!cda->nves) + cda->nves = skiplist_new(0, nve_addr_cmp, nve_addr_free); + + if (skiplist_search(cda->nves, &ha, (void *)&hap)) { + hap = XCALLOC(MTYPE_RFAPI_NVE_ADDR, sizeof(struct nve_addr)); + assert(hap); + ha.cda = cda; + *hap = ha; + skiplist_insert(cda->nves, hap, hap); + } +} + +static void clear_vnc_responses(struct rfapi_local_reg_delete_arg *cda) +{ + struct rfapi *h; + struct rfapi_descriptor *rfd; + int query_count = 0; + struct listnode *node; + struct bgp *bgp_default = bgp_get_default(); + + if (cda->vn_address.addr_family && cda->un_address.addr_family) { + /* + * Single nve case + */ + if (rfapi_find_rfd(bgp_default, &cda->vn_address, + &cda->un_address, &rfd)) + return; + + rfapiRibClear(rfd); + rfapi_query_done_all(rfd, &query_count); + cda->query_count += query_count; + + /* + * Track unique nves seen + */ + record_nve_in_cda_list(cda, &rfd->un_addr, &rfd->vn_addr, rfd); + return; + } + + /* + * wildcard case + */ + + if (!bgp_default) + return; /* ENXIO */ + + h = bgp_default->rfapi; + + if (!h) + return; /* ENXIO */ + + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + /* + * match un, vn addresses of NVEs + */ + if (cda->un_address.addr_family + && rfapi_ip_addr_cmp(&cda->un_address, &rfd->un_addr)) { + continue; + } + if (cda->vn_address.addr_family + && rfapi_ip_addr_cmp(&cda->vn_address, &rfd->vn_addr)) { + continue; + } + + rfapiRibClear(rfd); + + rfapi_query_done_all(rfd, &query_count); + cda->query_count += query_count; + + /* + * Track unique nves seen + */ + record_nve_in_cda_list(cda, &rfd->un_addr, &rfd->vn_addr, rfd); + } +} + +/* + * TBD need to count deleted prefixes and nves? + * + * ENXIO BGP or VNC not configured + */ +static int rfapiDeleteLocalPrefixesByRFD(struct rfapi_local_reg_delete_arg *cda, + struct rfapi_descriptor *rfd) +{ + struct rfapi_ip_addr *pUn; /* NULL = wildcard */ + struct rfapi_ip_addr *pVn; /* NULL = wildcard */ + struct prefix *pPrefix; /* NULL = wildcard */ + struct prefix_rd *pPrd; /* NULL = wildcard */ + + struct rfapi_ip_prefix rprefix; + struct rfapi_next_hop_entry *head = NULL; + struct rfapi_next_hop_entry *tail = NULL; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: entry", __func__); +#endif + + pUn = (cda->un_address.addr_family ? &cda->un_address : NULL); + pVn = (cda->vn_address.addr_family ? &cda->vn_address : NULL); + pPrefix = (cda->prefix.family ? &cda->prefix : NULL); + pPrd = (cda->rd.prefixlen == 64 ? &cda->rd : NULL); + + if (pPrefix) { + rfapiQprefix2Rprefix(pPrefix, &rprefix); + } + + do /* to preserve old code structure */ + { + struct rfapi *h = cda->bgp->rfapi; + ; + struct rfapi_adb *adb; + int rc; + int deleted_from_this_nve; + struct nve_addr ha; + struct nve_addr *hap; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: rfd=%p", __func__, rfd); +#endif + + /* + * match un, vn addresses of NVEs + */ + if (pUn && (rfapi_ip_addr_cmp(pUn, &rfd->un_addr))) + break; + if (pVn && (rfapi_ip_addr_cmp(pVn, &rfd->vn_addr))) + break; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose("%s: un, vn match", __func__); +#endif + + /* + * match prefix + */ + + deleted_from_this_nve = 0; + + { + struct skiplist *sl; + struct rfapi_ip_prefix rp; + void *cursor; + struct list *adb_delete_list; + + /* + * The advertisements are stored in a skiplist. + * Withdrawing + * the registration deletes the advertisement from the + * skiplist, which we can't do while iterating over that + * same skiplist using the current skiplist API. + * + * Strategy: iterate over the skiplist and build another + * list containing only the matching ADBs. Then delete + * _everything_ in that second list (which can be done + * using either skiplists or quagga linklists). + */ + adb_delete_list = list_new(); + + /* + * Advertised IP prefixes (not 0/32 or 0/128) + */ + sl = rfd->advertised.ipN_by_prefix; + + for (cursor = NULL, + rc = skiplist_next(sl, NULL, (void **)&adb, + &cursor); + !rc; rc = skiplist_next(sl, NULL, (void **)&adb, + &cursor)) { + + if (pPrefix) { + if (!prefix_same(pPrefix, + &adb->u.s.prefix_ip)) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: adb=%p, prefix doesn't match, skipping", + __func__, adb); +#endif + continue; + } + } + if (pPrd) { + if (memcmp(pPrd->val, adb->u.s.prd.val, + 8) + != 0) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: adb=%p, RD doesn't match, skipping", + __func__, adb); +#endif + continue; + } + } + if (CHECK_FLAG(cda->l2o.flags, + RFAPI_L2O_MACADDR)) { + if (memcmp(cda->l2o.o.macaddr.octet, + adb->u.s.prefix_eth.u + .prefix_eth.octet, + ETH_ALEN)) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: adb=%p, macaddr doesn't match, skipping", + __func__, adb); +#endif + continue; + } + } + + if (CHECK_FLAG(cda->l2o.flags, RFAPI_L2O_LNI)) { + if (cda->l2o.o.logical_net_id + != adb->l2o.logical_net_id) { +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: adb=%p, LNI doesn't match, skipping", + __func__, adb); +#endif + continue; + } + } + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: ipN adding adb %p to delete list", + __func__, adb); +#endif + + listnode_add(adb_delete_list, adb); + } + + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adb_delete_list, node, adb)) { + int this_advertisement_prefix_count; + struct rfapi_vn_option optary[3]; + struct rfapi_vn_option *opt = NULL; + int cur_opt = 0; + + this_advertisement_prefix_count = 1; + + rfapiQprefix2Rprefix(&adb->u.s.prefix_ip, &rp); + + memset(optary, 0, sizeof(optary)); + + /* if mac addr present in advert, make l2o vn + * option */ + if (adb->u.s.prefix_eth.family == AF_ETHERNET) { + if (opt != NULL) + opt->next = &optary[cur_opt]; + opt = &optary[cur_opt++]; + opt->type = RFAPI_VN_OPTION_TYPE_L2ADDR; + opt->v.l2addr.macaddr = + adb->u.s.prefix_eth.u + .prefix_eth; + ++this_advertisement_prefix_count; + } + /* + * use saved RD value instead of trying to + * invert + * complex RD computation in rfapi_register() + */ + if (opt != NULL) + opt->next = &optary[cur_opt]; + opt = &optary[cur_opt++]; + opt->type = RFAPI_VN_OPTION_TYPE_INTERNAL_RD; + opt->v.internal_rd = adb->u.s.prd; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: ipN killing reg from adb %p ", + __func__, adb); +#endif + + rc = rfapi_register(rfd, &rp, 0, NULL, + (cur_opt ? optary : NULL), + RFAPI_REGISTER_KILL); + if (!rc) { + cda->pfx_count += + this_advertisement_prefix_count; + cda->reg_count += 1; + deleted_from_this_nve = 1; + } + if (h->rfp_methods.local_cb) { + rfapiAddDeleteLocalRfpPrefix( + &rfd->un_addr, &rfd->vn_addr, + &rp, 0, 0, NULL, &head, &tail); + } + } + list_delete_all_node(adb_delete_list); + + if (!(pPrefix && !RFAPI_0_PREFIX(pPrefix))) { + /* + * Caller didn't specify a prefix, or specified + * (0/32 or 0/128) + */ + + /* + * Advertised 0/32 and 0/128 (indexed by + * ethernet address) + */ + sl = rfd->advertised.ip0_by_ether; + + for (cursor = NULL, + rc = skiplist_next(sl, NULL, (void **)&adb, + &cursor); + !rc; + rc = skiplist_next(sl, NULL, (void **)&adb, + &cursor)) { + + if (CHECK_FLAG(cda->l2o.flags, + RFAPI_L2O_MACADDR)) { + if (memcmp(cda->l2o.o.macaddr + .octet, + adb->u.s.prefix_eth.u + .prefix_eth + .octet, + ETH_ALEN)) { + + continue; + } + } + if (CHECK_FLAG(cda->l2o.flags, + RFAPI_L2O_LNI)) { + if (cda->l2o.o.logical_net_id + != adb->l2o.logical_net_id) { + continue; + } + } +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: ip0 adding adb %p to delete list", + __func__, adb); +#endif + listnode_add(adb_delete_list, adb); + } + + + for (ALL_LIST_ELEMENTS_RO(adb_delete_list, node, + adb)) { + + struct rfapi_vn_option vn; + + rfapiQprefix2Rprefix( + &adb->u.s.prefix_ip, &rp); + + memset(&vn, 0, sizeof(vn)); + vn.type = RFAPI_VN_OPTION_TYPE_L2ADDR; + vn.v.l2addr = adb->l2o; + +#if DEBUG_L2_EXTRA + vnc_zlog_debug_verbose( + "%s: ip0 killing reg from adb %p ", + __func__, adb); +#endif + + rc = rfapi_register( + rfd, &rp, 0, NULL, &vn, + RFAPI_REGISTER_KILL); + if (!rc) { + cda->pfx_count += 1; + cda->reg_count += 1; + deleted_from_this_nve = 1; + } + if (h->rfp_methods.local_cb) { + struct rfapi_vn_option + *vn_opt_new; + + vn_opt_new = + rfapi_vn_options_dup( + &vn); + rfapiAddDeleteLocalRfpPrefix( + &rfd->un_addr, + &rfd->vn_addr, &rp, 0, + 0, vn_opt_new, &head, + &tail); + } + } + list_delete_all_node(adb_delete_list); + } + list_delete(&adb_delete_list); + } + + + if (head) { /* should not be set if (NULL == + rfapi_cfg->local_cb) */ + h->flags |= RFAPI_INCALLBACK; + (*h->rfp_methods.local_cb)(head, rfd->cookie); + h->flags &= ~RFAPI_INCALLBACK; + head = tail = NULL; + } + + if (deleted_from_this_nve) { + /* + * track unique NVEs seen + */ + memset(&ha, 0, sizeof(ha)); + ha.un = rfd->un_addr; + ha.vn = rfd->vn_addr; + + if (!cda->nves) + cda->nves = skiplist_new(0, nve_addr_cmp, + nve_addr_free); + if (skiplist_search(cda->nves, &ha, (void **)&hap)) { + hap = XCALLOC(MTYPE_RFAPI_NVE_ADDR, + sizeof(struct nve_addr)); + assert(hap); + ha.cda = cda; + *hap = ha; + skiplist_insert(cda->nves, hap, hap); + } + } + } while (0); /* to preserve old code structure */ + + return 0; +} + +static int rfapiDeleteLocalPrefixes(struct rfapi_local_reg_delete_arg *cda) +{ + int rc = 0; + + if (cda->rfg) { + if (cda->rfg->rfd) /* if not open, nothing to delete */ + rc = rfapiDeleteLocalPrefixesByRFD(cda, cda->rfg->rfd); + } else { + struct bgp *bgp = cda->bgp; + struct rfapi *h; + struct rfapi_cfg *rfapi_cfg; + + struct listnode *node; + struct rfapi_descriptor *rfd; + if (!bgp) + return ENXIO; + h = bgp->rfapi; + rfapi_cfg = bgp->rfapi_cfg; + if (!h || !rfapi_cfg) + return ENXIO; + vnc_zlog_debug_verbose("%s: starting descriptor loop", + __func__); + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + rc = rfapiDeleteLocalPrefixesByRFD(cda, rfd); + } + } + return rc; +} + +/* + * clear_vnc_prefix + * + * Deletes local and remote prefixes that match + */ +static void clear_vnc_prefix(struct rfapi_local_reg_delete_arg *cda) +{ + struct prefix pfx_un; + struct prefix pfx_vn; + + struct prefix *pUN = NULL; + struct prefix *pVN = NULL; + struct prefix *pPrefix = NULL; + + struct rfapi_import_table *it = NULL; + + /* + * Delete matching remote prefixes in holddown + */ + if (cda->vn_address.addr_family) { + if (!rfapiRaddr2Qprefix(&cda->vn_address, &pfx_vn)) + pVN = &pfx_vn; + } + if (cda->un_address.addr_family) { + if (!rfapiRaddr2Qprefix(&cda->un_address, &pfx_un)) + pUN = &pfx_un; + } + if (cda->prefix.family) { + pPrefix = &cda->prefix; + } + if (cda->rfg) { + it = cda->rfg->rfapi_import_table; + } + rfapiDeleteRemotePrefixes( + pUN, pVN, pPrefix, it, 0, 1, &cda->remote_active_pfx_count, + &cda->remote_active_nve_count, &cda->remote_holddown_pfx_count, + &cda->remote_holddown_nve_count); + + /* + * Now do local prefixes + */ + rfapiDeleteLocalPrefixes(cda); +} + +static void print_cleared_stats(struct rfapi_local_reg_delete_arg *cda) +{ + struct vty *vty = cda->vty; /* for benefit of VTYNL */ + + /* Our special element-deleting function counts nves */ + if (cda->nves) { + skiplist_free(cda->nves); + cda->nves = NULL; + } + if (cda->failed_pfx_count) + vty_out(vty, "Failed to delete %d prefixes\n", + cda->failed_pfx_count); + + /* left as "prefixes" even in single case for ease of machine parsing */ + vty_out(vty, + "[Local] Cleared %u registrations, %u prefixes, %u responses from %d NVEs\n", + cda->reg_count, cda->pfx_count, cda->query_count, + cda->nve_count); + + /* + * We don't currently allow deletion of active remote prefixes from + * the command line + */ + + vty_out(vty, "[Holddown] Cleared %u prefixes from %u NVEs\n", + cda->remote_holddown_pfx_count, cda->remote_holddown_nve_count); +} + +/* + * Caller has already deleted registrations and queries for this/these + * NVEs. Now we just have to close their descriptors. + */ +static void clear_vnc_nve_closer(struct rfapi_local_reg_delete_arg *cda) +{ + struct skiplist *sl = cda->nves; /* contains affected NVEs */ + struct nve_addr *pKey; + struct nve_addr *pValue; + void *cursor = NULL; + int rc; + + if (!sl) + return; + + for (rc = skiplist_next(sl, (void **)&pKey, (void **)&pValue, &cursor); + !rc; rc = skiplist_next(sl, (void **)&pKey, (void **)&pValue, + &cursor)) { + + if (pValue->rfd) { + pValue->rfd->flags |= + RFAPI_HD_FLAG_CLOSING_ADMINISTRATIVELY; + rfapi_close(pValue->rfd); + } + } +} + +DEFUN (clear_vnc_nve_all, + clear_vnc_nve_all_cmd, + "clear vnc nve *", + "clear\n" + "VNC Information\n" + "Clear per NVE information\n" + "For all NVEs\n") +{ + + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_args(vty, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &cda))) + return rc; + + cda.vty = vty; + + clear_vnc_responses(&cda); + clear_vnc_prefix(&cda); + clear_vnc_nve_closer(&cda); + + print_cleared_stats(&cda); + + return 0; +} + +DEFUN (clear_vnc_nve_vn_un, + clear_vnc_nve_vn_un_cmd, + "clear vnc nve vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "VN address of NVE\n" + "For all NVEs\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "For all UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[4], argv[6], NULL, + NULL, NULL, NULL, &cda))) + return rc; + + cda.vty = vty; + + clear_vnc_responses(&cda); + clear_vnc_prefix(&cda); + clear_vnc_nve_closer(&cda); + + print_cleared_stats(&cda); + + return 0; +} + +DEFUN (clear_vnc_nve_un_vn, + clear_vnc_nve_un_vn_cmd, + "clear vnc nve un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "UN address of NVE\n" + "For all un NVEs\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "VN address of NVE\n" + "For all vn NVEs\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[6], argv[4], NULL, + NULL, NULL, NULL, &cda))) + return rc; + + cda.vty = vty; + + clear_vnc_responses(&cda); + clear_vnc_prefix(&cda); + clear_vnc_nve_closer(&cda); + + print_cleared_stats(&cda); + + return 0; +} + +DEFUN (clear_vnc_nve_vn, + clear_vnc_nve_vn_cmd, + "clear vnc nve vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "VN address of NVE\n" + "All addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[4], NULL, NULL, + NULL, NULL, NULL, &cda))) + return rc; + + cda.vty = vty; + + clear_vnc_responses(&cda); + clear_vnc_prefix(&cda); + clear_vnc_nve_closer(&cda); + + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_nve_un, + clear_vnc_nve_un_cmd, + "clear vnc nve un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "UN address of NVE\n" + "All un nves\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, argv[4], NULL, + NULL, NULL, NULL, &cda))) + return rc; + + cda.vty = vty; + + clear_vnc_responses(&cda); + clear_vnc_prefix(&cda); + clear_vnc_nve_closer(&cda); + + print_cleared_stats(&cda); + return 0; +} + +/*------------------------------------------------- + * Clear VNC Prefix + *-------------------------------------------------*/ + +/* + * This function is defined in this file (rather than in rfp_registration.c) + * because here we have access to all the task handles. + */ +DEFUN (clear_vnc_prefix_vn_un, + clear_vnc_prefix_vn_un_cmd, + "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[5], argv[7], + NULL, NULL, NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_prefix_un_vn, + clear_vnc_prefix_un_vn_cmd, + "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[7], argv[5], + NULL, NULL, NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_prefix_un, + clear_vnc_prefix_un_cmd, + "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, argv[3], NULL, argv[5], NULL, + NULL, NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_prefix_vn, + clear_vnc_prefix_vn_cmd, + "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "UN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, argv[3], argv[5], NULL, NULL, + NULL, NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_prefix_all, + clear_vnc_prefix_all_cmd, + "clear vnc prefix <*|A.B.C.D/M|X:X::X:X/M> *", + "clear\n" + "VNC Information\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "From any NVE\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + if ((rc = parse_deleter_tokens(vty, NULL, argv[3], NULL, NULL, NULL, + NULL, NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +/*------------------------------------------------- + * Clear VNC MAC + *-------------------------------------------------*/ + +/* + * This function is defined in this file (rather than in rfp_registration.c) + * because here we have access to all the task handles. + */ +DEFUN (clear_vnc_mac_vn_un, + clear_vnc_mac_vn_un_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[7], argv[9], + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_un_vn, + clear_vnc_mac_un_vn_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[9], argv[7], + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_un, + clear_vnc_mac_un_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, argv[7], argv[3], + argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_vn, + clear_vnc_mac_vn_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, NULL, argv[7], NULL, argv[3], + argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_all, + clear_vnc_mac_all_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> *", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "From any NVE\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, NULL, NULL, NULL, argv[3], + argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +/*------------------------------------------------- + * Clear VNC MAC PREFIX + *-------------------------------------------------*/ + +DEFUN (clear_vnc_mac_vn_un_prefix, + clear_vnc_mac_vn_un_prefix_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> un <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, argv[11], argv[7], argv[9], + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_un_vn_prefix, + clear_vnc_mac_un_vn_prefix_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> vn <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M> prefix <*|A.B.C.D/M|X:X::X:X/M>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "VN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 prefix\n" + "IPv6 prefix\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, argv[11], argv[9], argv[7], + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_un_prefix, + clear_vnc_mac_un_prefix_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> un <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All UN addresses\n" + "UN IPv4 interface address\n" + "UN IPv6 interface address\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, argv[9], NULL, argv[7], + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_vn_prefix, + clear_vnc_mac_vn_prefix_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> vn <*|A.B.C.D|X:X::X:X> prefix <*|A.B.C.D/M|X:X::X:X/M>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n" + "Clear prefix registration information\n" + "All prefixes\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, argv[9], argv[7], NULL, + argv[3], argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +DEFUN (clear_vnc_mac_all_prefix, + clear_vnc_mac_all_prefix_cmd, + "clear vnc mac <*|X:X:X:X:X:X> virtual-network-identifier <*|(1-4294967295)> prefix <*|A.B.C.D/M|X:X::X:X/M>", + "clear\n" + "VNC Information\n" + "Clear mac registration information\n" + "All macs\n" + "MAC address\n" + "VNI keyword\n" + "Any virtual network identifier\n" + "Virtual network identifier\n" + "UN address of NVE\n" + "All VN addresses\n" + "VN IPv4 interface address\n" + "VN IPv6 interface address\n") +{ + struct rfapi_local_reg_delete_arg cda; + int rc; + + /* pfx vn un L2 VNI */ + if ((rc = parse_deleter_tokens(vty, NULL, argv[7], NULL, NULL, argv[3], + argv[5], NULL, NULL, &cda))) + return rc; + cda.vty = vty; + clear_vnc_prefix(&cda); + print_cleared_stats(&cda); + return 0; +} + +/************************************************************************ + * Show commands + ************************************************************************/ + + +/* copied from rfp_vty.c */ +static int check_and_display_is_vnc_running(struct vty *vty) +{ + if (bgp_rfapi_is_vnc_configured(NULL) == 0) + return 1; /* is running */ + + if (vty) { + vty_out(vty, "VNC is not configured.\n"); + } + return 0; /* not running */ +} + +static int rfapi_vty_show_nve_summary(struct vty *vty, + show_nve_summary_t show_type) +{ + struct bgp *bgp_default = bgp_get_default(); + struct rfapi *h; + int is_vnc_running = (bgp_rfapi_is_vnc_configured(bgp_default) == 0); + + int active_local_routes; + int active_remote_routes; + int holddown_remote_routes; + int imported_remote_routes; + + if (!bgp_default) + goto notcfg; + + h = bgp_default->rfapi; + + if (!h) + goto notcfg; + + /* don't show local info if not running RFP */ + if (is_vnc_running || show_type == SHOW_NVE_SUMMARY_REGISTERED) { + + switch (show_type) { + + case SHOW_NVE_SUMMARY_ACTIVE_NVES: + vty_out(vty, "%-24s ", "NVEs:"); + vty_out(vty, "%-8s %-8u ", + "Active:", h->descriptors.count); + vty_out(vty, "%-8s %-8u ", + "Maximum:", h->stat.max_descriptors); + vty_out(vty, "%-8s %-8u", + "Unknown:", h->stat.count_unknown_nves); + break; + + case SHOW_NVE_SUMMARY_REGISTERED: + /* + * NB: With the introduction of L2 route support, we no + * longer have a one-to-one correspondence between + * locally-originated route advertisements and routes in + * the import tables that have local origin. This + * discrepancy arises because a single advertisement + * may contain both an IP prefix and a MAC address. + * Such an advertisement results in two import table + * entries: one indexed by IP prefix, the other indexed + * by MAC address. + * + * TBD: update computation and display of registration + * statistics to reflect the underlying semantics. + */ + if (is_vnc_running) { + vty_out(vty, "%-24s ", "Registrations:"); + vty_out(vty, "%-8s %-8u ", "Active:", + rfapiApCountAll(bgp_default)); + vty_out(vty, "%-8s %-8u ", "Failed:", + h->stat.count_registrations_failed); + vty_out(vty, "%-8s %-8u", + "Total:", h->stat.count_registrations); + vty_out(vty, "\n"); + } + vty_out(vty, "%-24s ", "Prefixes registered:"); + vty_out(vty, "\n"); + + rfapiCountAllItRoutes(&active_local_routes, + &active_remote_routes, + &holddown_remote_routes, + &imported_remote_routes); + + /* local */ + if (is_vnc_running) { + vty_out(vty, " %-20s ", "Locally:"); + vty_out(vty, "%-8s %-8u ", + "Active:", active_local_routes); + vty_out(vty, "\n"); + } + + + vty_out(vty, " %-20s ", "Remotely:"); + vty_out(vty, "%-8s %-8u", + "Active:", active_remote_routes); + vty_out(vty, "\n"); + vty_out(vty, " %-20s ", "In Holddown:"); + vty_out(vty, "%-8s %-8u", + "Active:", holddown_remote_routes); + vty_out(vty, "\n"); + vty_out(vty, " %-20s ", "Imported:"); + vty_out(vty, "%-8s %-8u", + "Active:", imported_remote_routes); + break; + + case SHOW_NVE_SUMMARY_QUERIES: + vty_out(vty, "%-24s ", "Queries:"); + vty_out(vty, "%-8s %-8u ", + "Active:", rfapi_monitor_count(NULL)); + vty_out(vty, "%-8s %-8u ", + "Failed:", h->stat.count_queries_failed); + vty_out(vty, "%-8s %-8u", + "Total:", h->stat.count_queries); + break; + + case SHOW_NVE_SUMMARY_RESPONSES: + rfapiRibShowResponsesSummary(vty); + break; + + case SHOW_NVE_SUMMARY_UNKNOWN_NVES: + case SHOW_NVE_SUMMARY_MAX: + break; + } + vty_out(vty, "\n"); + } + return 0; + +notcfg: + vty_out(vty, "VNC is not configured.\n"); + return CMD_WARNING; +} + +static int rfapi_show_nves(struct vty *vty, struct prefix *vn_prefix, + struct prefix *un_prefix) +{ + // struct hash *rfds; + // struct rfp_rfapi_descriptor_param param; + + struct bgp *bgp_default = bgp_get_default(); + struct rfapi *h; + struct listnode *node; + struct rfapi_descriptor *rfd; + + int total = 0; + int printed = 0; + int rc; + + if (!bgp_default) + goto notcfg; + + h = bgp_default->rfapi; + + if (!h) + goto notcfg; + + rc = rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_ACTIVE_NVES); + if (rc) + return rc; + + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + struct prefix pfx; + char vn_addr_buf[INET6_ADDRSTRLEN] = { + 0, + }; + char un_addr_buf[INET6_ADDRSTRLEN] = { + 0, + }; + char age[10]; + + ++total; + + if (vn_prefix) { + assert(!rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx)); + if (!prefix_match(vn_prefix, &pfx)) + continue; + } + + if (un_prefix) { + assert(!rfapiRaddr2Qprefix(&rfd->un_addr, &pfx)); + if (!prefix_match(un_prefix, &pfx)) + continue; + } + + rfapiRfapiIpAddr2Str(&rfd->vn_addr, vn_addr_buf, + INET6_ADDRSTRLEN); + rfapiRfapiIpAddr2Str(&rfd->un_addr, un_addr_buf, + INET6_ADDRSTRLEN); + + if (!printed) { + /* print out a header */ + vty_out(vty, + " Active Next Hops\n"); + vty_out(vty, "%-15s %-15s %-5s %-5s %-6s %-6s %s\n", + "VN Address", "UN Address", "Regis", "Resps", + "Reach", "Remove", "Age"); + } + + ++printed; + + vty_out(vty, "%-15s %-15s %-5u %-5u %-6u %-6u %s\n", + vn_addr_buf, un_addr_buf, rfapiApCount(rfd), + rfapi_monitor_count(rfd), rfd->stat_count_nh_reachable, + rfd->stat_count_nh_removal, + rfapiFormatAge(rfd->open_time, age, 10)); + } + + if (printed > 0 || vn_prefix || un_prefix) + vty_out(vty, "Displayed %d out of %d active NVEs\n", printed, + total); + + return 0; + +notcfg: + vty_out(vty, "VNC is not configured.\n"); + return CMD_WARNING; +} + + +DEFUN (vnc_show_summary, + vnc_show_summary_cmd, + "show vnc summary", + SHOW_STR + VNC_SHOW_STR + "Display VNC status summary\n") +{ + if (!check_and_display_is_vnc_running(vty)) + return CMD_SUCCESS; + bgp_rfapi_show_summary(bgp_get_default(), vty); + vty_out(vty, "\n"); + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_ACTIVE_NVES); + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_RESPONSES); + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_REGISTERED); + return CMD_SUCCESS; +} + +DEFUN (vnc_show_nves, + vnc_show_nves_cmd, + "show vnc nves", + SHOW_STR + VNC_SHOW_STR + "List known NVEs\n") +{ + rfapi_show_nves(vty, NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN (vnc_show_nves_ptct, + vnc_show_nves_ptct_cmd, + "show vnc nves ", + SHOW_STR + VNC_SHOW_STR + "List known NVEs\n" + "VN address of NVE\n" + "UN address of NVE\n" + "IPv4 interface address\n" + "IPv6 interface address\n") +{ + struct prefix pfx; + + if (!check_and_display_is_vnc_running(vty)) + return CMD_SUCCESS; + + if (!str2prefix(argv[4]->arg, &pfx)) { + vty_out(vty, "Malformed address \"%s\"\n", argv[4]->arg); + return CMD_WARNING; + } + if (pfx.family != AF_INET && pfx.family != AF_INET6) { + vty_out(vty, "Invalid address \"%s\"\n", argv[4]->arg); + return CMD_WARNING; + } + + if (argv[3]->arg[0] == 'u') { + rfapi_show_nves(vty, NULL, &pfx); + } else { + rfapi_show_nves(vty, &pfx, NULL); + } + + return CMD_SUCCESS; +} + +/* adapted from rfp_registration_cache_log() */ +static void rfapi_show_registrations(struct vty *vty, + struct prefix *restrict_to, int show_local, + int show_remote, int show_holddown, + int show_imported) +{ + int printed = 0; + + if (!vty) + return; + + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_REGISTERED); + + if (show_local) { + /* non-expiring, local */ + printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 1, + 0, 0); + } + if (show_remote) { + /* non-expiring, non-local */ + printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 0, + 1, 0); + } + if (show_holddown) { + /* expiring, including local */ + printed += rfapiShowRemoteRegistrations(vty, restrict_to, 1, 1, + 1, 0); + } + if (show_imported) { + /* non-expiring, non-local */ + printed += rfapiShowRemoteRegistrations(vty, restrict_to, 0, 0, + 1, 1); + } + if (!printed) { + vty_out(vty, "\n"); + } +} + +DEFUN (vnc_show_registrations_pfx, + vnc_show_registrations_pfx_cmd, + "show vnc registrations []", + SHOW_STR + VNC_SHOW_STR + "List active prefix registrations\n" + "Limit output to a particualr IPV4 address\n" + "Limit output to a particular IPv4 prefix\n" + "Limit output to a particualr IPV6 address\n" + "Limit output to a particular IPv6 prefix\n" + "Limit output to a particular MAC address\n") +{ + struct prefix p; + struct prefix *p_addr = NULL; + + if (argc > 3) { + if (!str2prefix(argv[3]->arg, &p)) { + vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); + return CMD_SUCCESS; + } else { + p_addr = &p; + } + } + + rfapi_show_registrations(vty, p_addr, 1, 1, 1, 1); + return CMD_SUCCESS; +} + +DEFUN (vnc_show_registrations_some_pfx, + vnc_show_registrations_some_pfx_cmd, + "show vnc registrations []", + SHOW_STR + VNC_SHOW_STR + "List active prefix registrations\n" + "show all registrations\n" + "show only registrations in holddown\n" + "show only imported prefixes\n" + "show only local registrations\n" + "show only remote registrations\n" + "Limit output to a particualr IPV4 address\n" + "Limit output to a particular IPv4 prefix\n" + "Limit output to a particualr IPV6 address\n" + "Limit output to a particular IPv6 prefix\n" + "Limit output to a particular MAC address\n") +{ + struct prefix p; + struct prefix *p_addr = NULL; + + int show_local = 0; + int show_remote = 0; + int show_holddown = 0; + int show_imported = 0; + + if (argc > 4) { + if (!str2prefix(argv[4]->arg, &p)) { + vty_out(vty, "Invalid prefix: %s\n", argv[4]->arg); + return CMD_SUCCESS; + } else { + p_addr = &p; + } + } + switch (argv[3]->arg[0]) { + case 'a': + show_local = 1; + show_remote = 1; + show_holddown = 1; + show_imported = 1; + break; + + case 'h': + show_holddown = 1; + break; + + case 'i': + show_imported = 1; + break; + + case 'l': + show_local = 1; + break; + + case 'r': + show_remote = 1; + break; + } + + rfapi_show_registrations(vty, p_addr, show_local, show_remote, + show_holddown, show_imported); + return CMD_SUCCESS; +} + +DEFUN (vnc_show_responses_pfx, + vnc_show_responses_pfx_cmd, + "show vnc responses []", + SHOW_STR + VNC_SHOW_STR + "List recent query responses\n" + "Limit output to a particualr IPV4 address\n" + "Limit output to a particular IPv4 prefix\n" + "Limit output to a particualr IPV6 address\n" + "Limit output to a particular IPv6 prefix\n" + "Limit output to a particular MAC address\n" ) +{ + struct prefix p; + struct prefix *p_addr = NULL; + + if (argc > 3) { + if (!str2prefix(argv[3]->arg, &p)) { + vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); + return CMD_SUCCESS; + } else { + p_addr = &p; + } + } + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); + + rfapiRibShowResponsesSummary(vty); + + rfapiRibShowResponses(vty, p_addr, 0); + rfapiRibShowResponses(vty, p_addr, 1); + + return CMD_SUCCESS; +} + +DEFUN (vnc_show_responses_some_pfx, + vnc_show_responses_some_pfx_cmd, + "show vnc responses []", + SHOW_STR + VNC_SHOW_STR + "List recent query responses\n" + "show only active query responses\n" + "show only removed query responses\n" + "Limit output to a particualr IPV4 address\n" + "Limit output to a particular IPv4 prefix\n" + "Limit output to a particualr IPV6 address\n" + "Limit output to a particular IPv6 prefix\n" + "Limit output to a particular MAC address\n") +{ + struct prefix p; + struct prefix *p_addr = NULL; + + int show_active = 0; + int show_removed = 0; + + if (!check_and_display_is_vnc_running(vty)) + return CMD_SUCCESS; + + if (argc > 4) { + if (!str2prefix(argv[4]->arg, &p)) { + vty_out(vty, "Invalid prefix: %s\n", argv[4]->arg); + return CMD_SUCCESS; + } else { + p_addr = &p; + } + } + + switch (argv[3]->arg[0]) { + case 'a': + show_active = 1; + break; + + case 'r': + show_removed = 1; + break; + } + + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); + + rfapiRibShowResponsesSummary(vty); + + if (show_active) + rfapiRibShowResponses(vty, p_addr, 0); + if (show_removed) + rfapiRibShowResponses(vty, p_addr, 1); + + return CMD_SUCCESS; +} + +DEFUN (show_vnc_queries_pfx, + show_vnc_queries_pfx_cmd, + "show vnc queries []", + SHOW_STR + VNC_SHOW_STR + "List active queries\n" + "Limit output to a particualr IPV4 address\n" + "Limit output to a particular IPv4 prefix\n" + "Limit output to a particualr IPV6 address\n" + "Limit output to a particular IPv6 prefix\n" + "Limit output to a particualr MAC address\n") +{ + struct prefix pfx; + struct prefix *p = NULL; + + if (argc > 3) { + if (!str2prefix(argv[3]->arg, &pfx)) { + vty_out(vty, "Invalid prefix: %s\n", argv[3]->arg); + return CMD_WARNING; + } + p = &pfx; + } + + rfapi_vty_show_nve_summary(vty, SHOW_NVE_SUMMARY_QUERIES); + + return rfapiShowVncQueries(vty, p); +} + +DEFUN (vnc_clear_counters, + vnc_clear_counters_cmd, + "clear vnc counters", + CLEAR_STR + VNC_SHOW_STR + "Reset VNC counters\n") +{ + struct bgp *bgp_default = bgp_get_default(); + struct rfapi *h; + struct listnode *node; + struct rfapi_descriptor *rfd; + + if (!bgp_default) + goto notcfg; + + h = bgp_default->rfapi; + + if (!h) + goto notcfg; + + /* per-rfd */ + for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) { + rfd->stat_count_nh_reachable = 0; + rfd->stat_count_nh_removal = 0; + } + + /* global */ + memset(&h->stat, 0, sizeof(h->stat)); + + /* + * 151122 per bug 103, set count_registrations = number active. + * Do same for queries + */ + h->stat.count_registrations = rfapiApCountAll(bgp_default); + h->stat.count_queries = rfapi_monitor_count(NULL); + + rfapiRibShowResponsesSummaryClear(); + + return CMD_SUCCESS; + +notcfg: + vty_out(vty, "VNC is not configured.\n"); + return CMD_WARNING; +} + +/************************************************************************ + * Add prefix with vrf + * + * add [vrf ] prefix + * [rd ] [label ] [local-preference <0-4294967295>] + ************************************************************************/ +void vnc_add_vrf_opener(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) +{ + if (rfg->rfd == NULL) { /* need new rfapi_handle */ + /* based on rfapi_open */ + struct rfapi_descriptor *rfd; + + rfd = XCALLOC(MTYPE_RFAPI_DESC, + sizeof(struct rfapi_descriptor)); + rfd->bgp = bgp; + rfg->rfd = rfd; + /* leave most fields empty as will get from (dynamic) config + * when needed */ + rfd->default_tunneltype_option.type = BGP_ENCAP_TYPE_MPLS; + rfd->cookie = rfg; + if (rfg->vn_prefix.family + && !CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { + rfapiQprefix2Raddr(&rfg->vn_prefix, &rfd->vn_addr); + } else { + memset(&rfd->vn_addr, 0, sizeof(struct rfapi_ip_addr)); + rfd->vn_addr.addr_family = AF_INET; + rfd->vn_addr.addr.v4 = bgp->router_id; + } + rfd->un_addr = rfd->vn_addr; /* sigh, need something in UN for + lookups */ + vnc_zlog_debug_verbose("%s: Opening RFD for VRF %s", __func__, + rfg->name); + rfapi_init_and_open(bgp, rfd, rfg); + } +} + +/* NOTE: this functions parallels vnc_direct_add_rn_group_rd */ +static int vnc_add_vrf_prefix(struct vty *vty, const char *arg_vrf, + const char *arg_prefix, + const char *arg_rd, /* optional */ + const char *arg_label, /* optional */ + const char *arg_pref) /* optional */ +{ + struct bgp *bgp; + struct rfapi_nve_group_cfg *rfg; + struct prefix pfx; + struct rfapi_ip_prefix rpfx; + uint32_t pref = 0; + struct rfapi_vn_option optary[3]; + struct rfapi_vn_option *opt = NULL; + int cur_opt = 0; + + bgp = bgp_get_default(); /* assume main instance for now */ + if (!bgp) { + vty_out(vty, "No BGP process is configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (!bgp->rfapi || !bgp->rfapi_cfg) { + vty_out(vty, "VRF support not configured\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rfg = bgp_rfapi_cfg_match_byname(bgp, arg_vrf, RFAPI_GROUP_CFG_VRF); + /* arg checks */ + if (!rfg) { + vty_out(vty, "VRF \"%s\" appears not to be configured.\n", + arg_vrf); + return CMD_WARNING_CONFIG_FAILED; + } + if (!rfg->rt_export_list || !rfg->rfapi_import_table) { + vty_out(vty, + "VRF \"%s\" is missing RT import/export RT configuration.\n", + arg_vrf); + return CMD_WARNING_CONFIG_FAILED; + } + if (!rfg->rd.prefixlen && !arg_rd) { + vty_out(vty, + "VRF \"%s\" isn't configured with an RD, so RD must be provided.\n", + arg_vrf); + return CMD_WARNING_CONFIG_FAILED; + } + if (rfg->label > MPLS_LABEL_MAX && !arg_label) { + vty_out(vty, + "VRF \"%s\" isn't configured with a default labels, so a label must be provided.\n", + arg_vrf); + return CMD_WARNING_CONFIG_FAILED; + } + if (!str2prefix(arg_prefix, &pfx)) { + vty_out(vty, "Malformed prefix \"%s\"\n", arg_prefix); + return CMD_WARNING_CONFIG_FAILED; + } + rfapiQprefix2Rprefix(&pfx, &rpfx); + memset(optary, 0, sizeof(optary)); + if (arg_rd) { + opt = &optary[cur_opt++]; + opt->type = RFAPI_VN_OPTION_TYPE_INTERNAL_RD; + /* TODO: save RD format */ + if (!str2prefix_rd(arg_rd, &opt->v.internal_rd)) { + vty_out(vty, "Malformed RD \"%s\"\n", arg_rd); + return CMD_WARNING_CONFIG_FAILED; + } + } + if (rfg->label <= MPLS_LABEL_MAX || arg_label) { + struct rfapi_l2address_option *l2o; + if (opt != NULL) + opt->next = &optary[cur_opt]; + opt = &optary[cur_opt++]; + opt->type = RFAPI_VN_OPTION_TYPE_L2ADDR; + l2o = &opt->v.l2addr; + if (arg_label) { + int32_t label; + label = strtoul(arg_label, NULL, 10); + l2o->label = label; + } else + l2o->label = rfg->label; + } + if (arg_pref) { + char *endptr = NULL; + pref = strtoul(arg_pref, &endptr, 10); + if (*endptr != '\0') { + vty_out(vty, + "%% Invalid local-preference value \"%s\"\n", + arg_pref); + return CMD_WARNING_CONFIG_FAILED; + } + } + rpfx.cost = 255 - (pref & 255); + vnc_add_vrf_opener(bgp, rfg); + + if (!rfapi_register(rfg->rfd, &rpfx, RFAPI_INFINITE_LIFETIME, NULL, + (cur_opt ? optary : NULL), RFAPI_REGISTER_ADD)) { + struct rfapi_next_hop_entry *head = NULL; + struct rfapi_next_hop_entry *tail = NULL; + struct rfapi_vn_option *vn_opt_new; + + vnc_zlog_debug_verbose("%s: rfapi_register succeeded", + __func__); + + if (bgp->rfapi->rfp_methods.local_cb) { + struct rfapi_descriptor *r = + (struct rfapi_descriptor *)rfg->rfd; + vn_opt_new = rfapi_vn_options_dup(opt); + + rfapiAddDeleteLocalRfpPrefix(&r->un_addr, &r->vn_addr, + &rpfx, 1, + RFAPI_INFINITE_LIFETIME, + vn_opt_new, &head, &tail); + if (head) { + bgp->rfapi->flags |= RFAPI_INCALLBACK; + (*bgp->rfapi->rfp_methods.local_cb)(head, + r->cookie); + bgp->rfapi->flags &= ~RFAPI_INCALLBACK; + } + head = tail = NULL; + } + vnc_zlog_debug_verbose( + "%s completed, count=%d/%d", __func__, + rfg->rfapi_import_table->local_count[AFI_IP], + rfg->rfapi_import_table->local_count[AFI_IP6]); + return CMD_SUCCESS; + } + + vnc_zlog_debug_verbose("%s: rfapi_register failed", __func__); + vty_out(vty, "Add failed.\n"); + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (add_vrf_prefix_rd_label_pref, + add_vrf_prefix_rd_label_pref_cmd, + "add vrf NAME prefix [{rd ASN:NN_OR_IP-ADDRESS|label (0-1048575)|preference (0-4294967295)}]", + "Add\n" + "To a VRF\n" + "VRF name\n" + "Add/modify prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Override configured VRF Route Distinguisher\n" + ": or :\n" + "Override configured VRF label\n" + "Label Value <0-1048575>\n" + "Set advertised local preference\n" + "local preference (higher=more preferred)\n") +{ + char *arg_vrf = argv[2]->arg; + char *arg_prefix = argv[4]->arg; + char *arg_rd = NULL; /* optional */ + char *arg_label = NULL; /* optional */ + char *arg_pref = NULL; /* optional */ + int pargc = 5; + argc--; /* don't parse argument */ + while (pargc < argc) { + switch (argv[pargc++]->arg[0]) { + case 'r': + arg_rd = argv[pargc]->arg; + break; + case 'l': + arg_label = argv[pargc]->arg; + break; + case 'p': + arg_pref = argv[pargc]->arg; + break; + default: + break; + } + pargc++; + } + + return vnc_add_vrf_prefix(vty, arg_vrf, arg_prefix, arg_rd, arg_label, + arg_pref); +} + +/************************************************************************ + * del prefix with vrf + * + * clear [vrf ] prefix [rd ] + ************************************************************************/ +static int rfapi_cfg_group_it_count(struct rfapi_nve_group_cfg *rfg) +{ + int count = 0; + + if (rfg->rfapi_import_table == NULL) + return 0; + + afi_t afi = AFI_MAX; + while (afi-- > 0) { + count += rfg->rfapi_import_table->local_count[afi]; + } + return count; +} + +void clear_vnc_vrf_closer(struct rfapi_nve_group_cfg *rfg) +{ + struct rfapi_descriptor *rfd = rfg->rfd; + afi_t afi; + + if (rfd == NULL) + return; + /* check if IT is empty */ + for (afi = 0; + afi < AFI_MAX && rfg->rfapi_import_table->local_count[afi] == 0; + afi++) + ; + + if (afi == AFI_MAX) { + vnc_zlog_debug_verbose("%s: closing RFD for VRF %s", __func__, + rfg->name); + rfg->rfd = NULL; + rfapi_close(rfd); + } else { + vnc_zlog_debug_verbose( + "%s: VRF %s afi=%d count=%d", __func__, rfg->name, afi, + rfg->rfapi_import_table->local_count[afi]); + } +} + +static int vnc_clear_vrf(struct vty *vty, struct bgp *bgp, const char *arg_vrf, + const char *arg_prefix, /* NULL = all */ + const char *arg_rd) /* optional */ +{ + struct rfapi_nve_group_cfg *rfg; + struct rfapi_local_reg_delete_arg cda; + int rc; + int start_count; + + if (bgp == NULL) + bgp = bgp_get_default(); /* assume main instance for now */ + if (!bgp) { + vty_out(vty, "No BGP process is configured\n"); + return CMD_WARNING; + } + if (!bgp->rfapi || !bgp->rfapi_cfg) { + vty_out(vty, "VRF support not configured\n"); + return CMD_WARNING; + } + rfg = bgp_rfapi_cfg_match_byname(bgp, arg_vrf, RFAPI_GROUP_CFG_VRF); + /* arg checks */ + if (!rfg) { + vty_out(vty, "VRF \"%s\" appears not to be configured.\n", + arg_vrf); + return CMD_WARNING; + } + rc = parse_deleter_args(vty, bgp, arg_prefix, NULL, NULL, NULL, NULL, + arg_rd, rfg, &cda); + if (rc != CMD_SUCCESS) /* parse error */ + return rc; + + start_count = rfapi_cfg_group_it_count(rfg); + clear_vnc_prefix(&cda); + vty_out(vty, "Cleared %u out of %d prefixes.\n", cda.pfx_count, + start_count); + print_cleared_stats(&cda); /* frees lists in cda */ + return CMD_SUCCESS; +} + +DEFUN (clear_vrf_prefix_rd, + clear_vrf_prefix_rd_cmd, + "clear vrf NAME [prefix ] [rd ASN:NN_OR_IP-ADDRESS]", + "Clear stored data\n" + "From a VRF\n" + "VRF name\n" + "Prefix related information\n" + "IPv4 prefix\n" + "IPv6 prefix\n" + "Specific VRF Route Distinguisher\n" + ": or :\n") +{ + char *arg_vrf = argv[2]->arg; + char *arg_prefix = NULL; /* optional */ + char *arg_rd = NULL; /* optional */ + int pargc = 3; + argc--; /* don't check parameter */ + while (pargc < argc) { + switch (argv[pargc++]->arg[0]) { + case 'r': + arg_rd = argv[pargc]->arg; + break; + case 'p': + arg_prefix = argv[pargc]->arg; + break; + default: + break; + } + pargc++; + } + return vnc_clear_vrf(vty, NULL, arg_vrf, arg_prefix, arg_rd); +} + +DEFUN (clear_vrf_all, + clear_vrf_all_cmd, + "clear vrf NAME all", + "Clear stored data\n" + "From a VRF\n" + "VRF name\n" + "All prefixes\n") +{ + char *arg_vrf = argv[2]->arg; + return vnc_clear_vrf(vty, NULL, arg_vrf, NULL, NULL); +} + +void rfapi_vty_init(void) +{ + install_element(ENABLE_NODE, &add_vnc_prefix_cost_life_lnh_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_life_cost_lnh_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_cost_lnh_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_life_lnh_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_lnh_cmd); + + install_element(ENABLE_NODE, &add_vnc_prefix_cost_life_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_life_cost_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_cost_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_life_cmd); + install_element(ENABLE_NODE, &add_vnc_prefix_cmd); + + install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cost_life_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_life_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cost_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_prefix_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_cost_life_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_cost_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_life_cmd); + install_element(ENABLE_NODE, &add_vnc_mac_vni_cmd); + + install_element(ENABLE_NODE, &add_vrf_prefix_rd_label_pref_cmd); + + install_element(ENABLE_NODE, &clear_vnc_nve_all_cmd); + install_element(ENABLE_NODE, &clear_vnc_nve_vn_un_cmd); + install_element(ENABLE_NODE, &clear_vnc_nve_un_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_nve_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_nve_un_cmd); + + install_element(ENABLE_NODE, &clear_vnc_prefix_vn_un_cmd); + install_element(ENABLE_NODE, &clear_vnc_prefix_un_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_prefix_un_cmd); + install_element(ENABLE_NODE, &clear_vnc_prefix_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_prefix_all_cmd); + + install_element(ENABLE_NODE, &clear_vnc_mac_vn_un_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_un_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_un_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_vn_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_all_cmd); + + install_element(ENABLE_NODE, &clear_vnc_mac_vn_un_prefix_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_un_vn_prefix_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_un_prefix_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_vn_prefix_cmd); + install_element(ENABLE_NODE, &clear_vnc_mac_all_prefix_cmd); + + install_element(ENABLE_NODE, &clear_vrf_prefix_rd_cmd); + install_element(ENABLE_NODE, &clear_vrf_all_cmd); + + install_element(ENABLE_NODE, &vnc_clear_counters_cmd); + + install_element(VIEW_NODE, &vnc_show_summary_cmd); + install_element(VIEW_NODE, &vnc_show_nves_cmd); + install_element(VIEW_NODE, &vnc_show_nves_ptct_cmd); + + install_element(VIEW_NODE, &vnc_show_registrations_pfx_cmd); + install_element(VIEW_NODE, &vnc_show_registrations_some_pfx_cmd); + install_element(VIEW_NODE, &vnc_show_responses_pfx_cmd); + install_element(VIEW_NODE, &vnc_show_responses_some_pfx_cmd); + install_element(VIEW_NODE, &show_vnc_queries_pfx_cmd); +} diff --git a/bgpd/rfapi/rfapi_vty.h b/bgpd/rfapi/rfapi_vty.h new file mode 100644 index 0000000..a563701 --- /dev/null +++ b/bgpd/rfapi/rfapi_vty.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef RFAPI_VTY_H +#define RFAPI_VTY_H + +#include "lib/vty.h" + +typedef enum { + SHOW_NVE_SUMMARY_ACTIVE_NVES, + SHOW_NVE_SUMMARY_UNKNOWN_NVES, /* legacy */ + SHOW_NVE_SUMMARY_REGISTERED, + SHOW_NVE_SUMMARY_QUERIES, + SHOW_NVE_SUMMARY_RESPONSES, + SHOW_NVE_SUMMARY_MAX +} show_nve_summary_t; + +#define VNC_SHOW_STR "VNC information\n" + +extern char *rfapiFormatSeconds(uint32_t seconds, char *buf, size_t len); + +extern char *rfapiFormatAge(time_t age, char *buf, size_t len); + +extern void rfapiRprefixApplyMask(struct rfapi_ip_prefix *rprefix); + +extern int rfapiQprefix2Raddr(struct prefix *qprefix, + struct rfapi_ip_addr *raddr); + +extern void rfapiQprefix2Rprefix(const struct prefix *qprefix, + struct rfapi_ip_prefix *rprefix); + +extern int rfapiRprefix2Qprefix(struct rfapi_ip_prefix *rprefix, + struct prefix *qprefix); + +extern int rfapiRaddr2Qprefix(struct rfapi_ip_addr *hia, struct prefix *pfx); + +extern int rfapiRprefixSame(struct rfapi_ip_prefix *hp1, + struct rfapi_ip_prefix *hp2); + +extern void rfapiL2o2Qprefix(struct rfapi_l2address_option *l2o, + struct prefix *pfx); + +extern int rfapiStr2EthAddr(const char *str, struct ethaddr *ea); + +extern const char *rfapi_ntop(int af, const void *src, char *buf, + socklen_t size); + +extern int rfapiDebugPrintf(void *dummy, const char *format, ...) + PRINTFRR(2, 3); + +extern int rfapiStream2Vty(void *stream, /* input */ + int (**fp)(void *, const char *, ...), /* output */ + struct vty **vty, /* output */ + void **outstream, /* output */ + const char **vty_newline); /* output */ + +/*------------------------------------------ + * rfapiRfapiIpAddr2Str + * + * UI helper: generate string from rfapi_ip_addr + * + * input: + * a IP v4/v6 address + * + * output + * buf put string here + * bufsize max space to write + * + * return value: + * NULL conversion failed + * non-NULL pointer to buf + --------------------------------------------*/ +extern const char *rfapiRfapiIpAddr2Str(struct rfapi_ip_addr *a, char *buf, + int bufsize); + +extern void rfapiPrintRfapiIpAddr(void *stream, struct rfapi_ip_addr *a); + +extern void rfapiPrintRfapiIpPrefix(void *stream, struct rfapi_ip_prefix *p); + +extern void rfapiPrintAdvertisedInfo(struct vty *vty, + struct rfapi_descriptor *rfd, safi_t safi, + struct prefix *p); + +extern void rfapiPrintDescriptor(struct vty *vty, struct rfapi_descriptor *rfd); + +extern void rfapiPrintMatchingDescriptors(struct vty *vty, + struct prefix *vn_prefix, + struct prefix *un_prefix); + +extern void rfapiPrintAttrPtrs(void *stream, struct attr *attr); + +/* + * Parse an address and put into a struct prefix + */ +extern int rfapiCliGetPrefixAddr(struct vty *vty, const char *str, + struct prefix *p); + +extern int rfapiCliGetRfapiIpAddr(struct vty *vty, const char *str, + struct rfapi_ip_addr *hai); + +extern void rfapiPrintNhl(void *stream, struct rfapi_next_hop_entry *next_hops); + +extern char *rfapiMonitorVpn2Str(struct rfapi_monitor_vpn *m, char *buf, + int size); + +extern const char *rfapiRfapiIpPrefix2Str(struct rfapi_ip_prefix *p, char *buf, + int bufsize); + +extern void rfapiShowItNode(void *stream, struct agg_node *rn); + +extern char *rfapiEthAddr2Str(const struct ethaddr *ea, char *buf, int bufsize); + +/* install vty commands */ +extern void rfapi_vty_init(void); + +/*------------------------------------------ + * rfapiShowRemoteRegistrations + * + * UI helper: produces the "remote" portion of the output + * of "show vnc registrations". + * + * input: + * stream pointer to output stream + * prefix_only pointer to prefix. If non-NULL, print only registrations + * matching the specified prefix + * show_expiring if non-zero, show expiring registrations + * show_local if non-zero, show local registrations + * show_imported if non-zero, show imported registrations + * + * return value: + * 0 nothing printed + * >0 something printed + --------------------------------------------*/ +extern int rfapiShowRemoteRegistrations(void *stream, + struct prefix *prefix_only, + int show_expiring, int show_local, + int show_remote, int show_imported); + +/*------------------------------------------ + * rfapi_monitor_count + * + * UI helper: count number of active monitors + * + * input: + * handle rfapi handle (NULL to count across + * all open handles) + * + * output + * + * return value: + * count of monitors + --------------------------------------------*/ +extern uint32_t rfapi_monitor_count(rfapi_handle); + +extern int rfapiShowVncQueries(void *stream, struct prefix *pfx_match); + + +#endif diff --git a/bgpd/rfapi/vnc_debug.c b/bgpd/rfapi/vnc_debug.c new file mode 100644 index 0000000..eb0d861 --- /dev/null +++ b/bgpd/rfapi/vnc_debug.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2016, LabN Consulting, L.L.C. + */ + +#include "lib/zebra.h" + +#include "lib/prefix.h" +#include "lib/linklist.h" +#include "lib/stream.h" +#include "lib/command.h" +#include "lib/log.h" +#include "bgpd/rfapi/vnc_debug.h" + +/* + * debug state storage + */ +unsigned long conf_vnc_debug; +unsigned long term_vnc_debug; + +struct vnc_debug { + unsigned long bit; + const char *name; +}; + +static const struct vnc_debug vncdebug[] = { + {VNC_DEBUG_RFAPI_QUERY, "rfapi-query"}, + {VNC_DEBUG_IMPORT_BI_ATTACH, "import-bi-attach"}, + {VNC_DEBUG_IMPORT_DEL_REMOTE, "import-del-remote"}, + {VNC_DEBUG_EXPORT_BGP_GETCE, "export-bgp-getce"}, + {VNC_DEBUG_EXPORT_BGP_DIRECT_ADD, "export-bgp-direct-add"}, + {VNC_DEBUG_IMPORT_BGP_ADD_ROUTE, "import-bgp-add-route"}, + {VNC_DEBUG_VERBOSE, "verbose"}, +}; + +#define VNC_STR "VNC information\n" + +/*********************************************************************** + * debug bgp vnc + ***********************************************************************/ +DEFUN (debug_bgp_vnc, + debug_bgp_vnc_cmd, + "debug bgp vnc ", + DEBUG_STR + BGP_STR + VNC_STR + "rfapi query handling\n" + "import BI atachment\n" + "import delete remote routes\n" + "verbose logging\n") +{ + size_t i; + + for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { + if (strmatch(argv[3]->text, vncdebug[i].name)) { + if (vty->node == CONFIG_NODE) { + conf_vnc_debug |= vncdebug[i].bit; + term_vnc_debug |= vncdebug[i].bit; + } else { + term_vnc_debug |= vncdebug[i].bit; + vty_out(vty, "BGP vnc %s debugging is on\n", + vncdebug[i].name); + } + return CMD_SUCCESS; + } + } + vty_out(vty, "Unknown debug flag: %s\n", argv[3]->arg); + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (no_debug_bgp_vnc, + no_debug_bgp_vnc_cmd, + "no debug bgp vnc ", + NO_STR + DEBUG_STR + BGP_STR + VNC_STR + "rfapi query handling\n" + "import BI atachment\n" + "import delete remote routes\n" + "verbose logging\n") +{ + size_t i; + + for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { + if (strmatch(argv[argc - 1]->text, vncdebug[i].name)) { + if (vty->node == CONFIG_NODE) { + conf_vnc_debug &= ~vncdebug[i].bit; + term_vnc_debug &= ~vncdebug[i].bit; + } else { + term_vnc_debug &= ~vncdebug[i].bit; + vty_out(vty, "BGP vnc %s debugging is off\n", + vncdebug[i].name); + } + return CMD_SUCCESS; + } + } + vty_out(vty, "Unknown debug flag: %s\n", argv[3]->arg); + return CMD_WARNING_CONFIG_FAILED; +} + +/*********************************************************************** + * no debug bgp vnc all + ***********************************************************************/ + +DEFUN (no_debug_bgp_vnc_all, + no_debug_bgp_vnc_all_cmd, + "no debug all bgp vnc", + NO_STR + DEBUG_STR + "Disable all VNC debugging\n" + BGP_STR + VNC_STR) +{ + term_vnc_debug = 0; + vty_out(vty, "All possible VNC debugging has been turned off\n"); + + return CMD_SUCCESS; +} + +/*********************************************************************** + * show/save + ***********************************************************************/ + +DEFUN_NOSH (show_debugging_bgp_vnc, + show_debugging_bgp_vnc_cmd, + "show debugging bgp vnc", + SHOW_STR + DEBUG_STR + BGP_STR + VNC_STR) +{ + size_t i; + + vty_out(vty, "BGP VNC debugging status:\n"); + + for (i = 0; i < (sizeof(vncdebug) / sizeof(struct vnc_debug)); ++i) { + if (term_vnc_debug & vncdebug[i].bit) { + vty_out(vty, " BGP VNC %s debugging is on\n", + vncdebug[i].name); + } + } + vty_out(vty, "\n"); + return CMD_SUCCESS; +} + +static int bgp_vnc_config_write_debug(struct vty *vty) +{ + int write = 0; + size_t i; + + for (i = 0; i < array_size(vncdebug); ++i) { + if (conf_vnc_debug & vncdebug[i].bit) { + vty_out(vty, "debug bgp vnc %s\n", vncdebug[i].name); + write++; + } + } + return write; +} + +static int bgp_vnc_config_write_debug(struct vty *vty); +static struct cmd_node debug_node = { + .name = "vnc debug", + .node = DEBUG_VNC_NODE, + .prompt = "", + .config_write = bgp_vnc_config_write_debug, +}; + +void vnc_debug_init(void) +{ + install_node(&debug_node); + install_element(ENABLE_NODE, &show_debugging_bgp_vnc_cmd); + + install_element(ENABLE_NODE, &debug_bgp_vnc_cmd); + install_element(CONFIG_NODE, &debug_bgp_vnc_cmd); + install_element(ENABLE_NODE, &no_debug_bgp_vnc_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_vnc_cmd); + + install_element(ENABLE_NODE, &no_debug_bgp_vnc_all_cmd); + install_element(CONFIG_NODE, &no_debug_bgp_vnc_all_cmd); +} diff --git a/bgpd/rfapi/vnc_debug.h b/bgpd/rfapi/vnc_debug.h new file mode 100644 index 0000000..c775127 --- /dev/null +++ b/bgpd/rfapi/vnc_debug.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2016, LabN Consulting, L.L.C. + */ + +#ifndef _QUAGGA_BGP_VNC_DEBUG_H +#define _QUAGGA_BGP_VNC_DEBUG_H + +#ifdef ENABLE_BGP_VNC + +/* + * debug state storage + */ +extern unsigned long conf_vnc_debug; +extern unsigned long term_vnc_debug; + +/* + * debug flag bits + */ +#define VNC_DEBUG_RFAPI_QUERY 0x00000001 +#define VNC_DEBUG_IMPORT_BI_ATTACH 0x00000002 +#define VNC_DEBUG_IMPORT_DEL_REMOTE 0x00000004 +#define VNC_DEBUG_EXPORT_BGP_GETCE 0x00000008 +#define VNC_DEBUG_EXPORT_BGP_DIRECT_ADD 0x00000010 +#define VNC_DEBUG_IMPORT_BGP_ADD_ROUTE 0x00000020 +#define VNC_DEBUG_VERBOSE 0x00000040 +#define VNC_DEBUG_ANY 0xFFFFFFFF + +#define VNC_DEBUG(bit) (term_vnc_debug & (VNC_DEBUG_ ## bit)) +#define vnc_zlog_debug_verbose if (VNC_DEBUG(VERBOSE)) zlog_debug +#define vnc_zlog_debug_any if (VNC_DEBUG(ANY)) zlog_debug + +extern void vnc_debug_init(void); + +#endif /* ENABLE_BGP_VNC */ + +#endif /* _QUAGGA_BGP_VNC_DEBUG_H */ diff --git a/bgpd/rfapi/vnc_export_bgp.c b/bgpd/rfapi/vnc_export_bgp.c new file mode 100644 index 0000000..7decb75 --- /dev/null +++ b/bgpd/rfapi/vnc_export_bgp.c @@ -0,0 +1,2091 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: vnc_export_bgp.c + * Purpose: Export routes to BGP directly (not via zebra) + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/log.h" +#include "lib/stream.h" +#include "lib/memory.h" +#include "lib/linklist.h" +#include "lib/plist.h" +#include "lib/routemap.h" +#include "lib/lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_aspath.h" + +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/vnc_export_bgp_p.h" +#include "bgpd/rfapi/vnc_export_table.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_debug.h" + + +static void vnc_direct_add_rn_group_rd(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + struct agg_node *rn, struct attr *attr, + afi_t afi, + struct rfapi_descriptor *irfd); + +/*********************************************************************** + * Export methods that set nexthop to CE (from 5226 roo EC) BEGIN + ***********************************************************************/ + +/* + * Memory allocation approach: make a ghost attr that + * has non-interned parts for the modifications. ghost attr + * memory is allocated by caller. + * + * - extract ce (=5226) EC and use as new nexthop + * - strip Tunnel Encap attr + * - copy all ECs + */ +static void encap_attr_export_ce(struct attr *new, struct attr *orig, + struct prefix *use_nexthop) +{ + /* + * Make "new" a ghost attr copy of "orig" + */ + memset(new, 0, sizeof(struct attr)); + *new = *orig; + + /* + * Set nexthop + */ + switch (use_nexthop->family) { + case AF_INET: + new->nexthop = use_nexthop->u.prefix4; + new->mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; /* bytes */ + new->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + break; + + case AF_INET6: + new->mp_nexthop_global = use_nexthop->u.prefix6; + new->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; /* bytes */ + break; + + default: + assert(0); + break; + } + + /* + * Set MED + * + * Note that it will be deleted when BGP sends to any eBGP + * peer unless PEER_FLAG_MED_UNCHANGED is set: + * + * neighbor NEIGHBOR attribute-unchanged med + */ + if (!CHECK_FLAG(new->flag, BGP_ATTR_MULTI_EXIT_DISC)) { + if (CHECK_FLAG(new->flag, BGP_ATTR_LOCAL_PREF)) { + if (new->local_pref > 255) + new->med = 0; + else + new->med = 255 - new->local_pref; + } else { + new->med = 255; /* shouldn't happen */ + } + new->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + } + + /* + * "new" is now a ghost attr: + * - it owns an "extra" struct + * - it owns any non-interned parts + * - any references to interned parts are not counted + * + * Caller should, after using the attr, call: + * - bgp_attr_flush() to free non-interned parts + */ +} + +static int getce(struct bgp *bgp, struct attr *attr, struct prefix *pfx_ce) +{ + uint8_t *ecp; + uint32_t i; + uint16_t localadmin = bgp->rfapi_cfg->resolve_nve_roo_local_admin; + struct ecommunity *ecomm = bgp_attr_get_ecommunity(attr); + + for (ecp = ecomm->val, i = 0; i < ecomm->size; + ++i, ecp += ECOMMUNITY_SIZE) { + + if (VNC_DEBUG(EXPORT_BGP_GETCE)) { + vnc_zlog_debug_any( + "%s: %02x %02x %02x %02x %02x %02x %02x %02x", + __func__, ecp[0], ecp[1], ecp[2], ecp[3], + ecp[4], ecp[5], ecp[6], ecp[7]); + } + + /* + * is it ROO? + */ + if (ecp[0] != 1 || ecp[1] != 3) { + continue; + } + + /* + * Match local admin value? + */ + if (ecp[6] != ((localadmin & 0xff00) >> 8) + || ecp[7] != (localadmin & 0xff)) + continue; + + memset((uint8_t *)pfx_ce, 0, sizeof(*pfx_ce)); + memcpy(&pfx_ce->u.prefix4, ecp + 2, 4); + pfx_ce->family = AF_INET; + pfx_ce->prefixlen = IPV4_MAX_BITLEN; + + return 0; + } + return -1; +} + + +void vnc_direct_bgp_add_route_ce(struct bgp *bgp, struct agg_node *rn, + struct bgp_path_info *bpi) +{ + struct attr *attr = bpi->attr; + struct peer *peer = bpi->peer; + const struct prefix *prefix = agg_node_get_prefix(rn); + afi_t afi = family2afi(prefix->family); + struct bgp_dest *udest; + struct bgp_path_info *ubpi; + struct attr hattr; + struct attr *iattr; + struct prefix ce_nexthop; + struct prefix post_routemap_nexthop; + + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", + __func__); + return; + } + + if ((bpi->type != ZEBRA_ROUTE_BGP) + || (bpi->sub_type != BGP_ROUTE_NORMAL + && bpi->sub_type != BGP_ROUTE_RFP + && bpi->sub_type != BGP_ROUTE_STATIC)) { + + vnc_zlog_debug_verbose( + "%s: wrong route type/sub_type for export, skipping", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp ce mode not enabled, skipping", + __func__); + return; + } + + /* + * prefix list check + */ + if (bgp->rfapi_cfg->plist_export_bgp[afi]) { + if (prefix_list_apply(bgp->rfapi_cfg->plist_export_bgp[afi], + prefix) + == PREFIX_DENY) { + vnc_zlog_debug_verbose( + "%s: prefix list denied, skipping", __func__); + return; + } + } + + + /* + * Extract CE + * This works only for IPv4 because IPv6 addresses are too big + * to fit in an extended community + */ + if (getce(bgp, attr, &ce_nexthop)) { + vnc_zlog_debug_verbose("%s: EC has no encoded CE, skipping", + __func__); + return; + } + + /* + * Is this route already represented in the unicast RIB? + * (look up prefix; compare route type, sub_type, peer, nexthop) + */ + udest = bgp_afi_node_get(bgp->rib[afi][SAFI_UNICAST], afi, SAFI_UNICAST, + prefix, NULL); + for (ubpi = bgp_dest_get_bgp_path_info(udest); ubpi; + ubpi = ubpi->next) { + struct prefix unicast_nexthop; + + if (CHECK_FLAG(ubpi->flags, BGP_PATH_REMOVED)) + continue; + + rfapiUnicastNexthop2Prefix(afi, ubpi->attr, &unicast_nexthop); + + if (ubpi->type == ZEBRA_ROUTE_VNC_DIRECT + && ubpi->sub_type == BGP_ROUTE_REDISTRIBUTE + && ubpi->peer == peer + && prefix_same(&unicast_nexthop, &ce_nexthop)) { + + vnc_zlog_debug_verbose( + "%s: already have matching exported unicast route, skipping", + __func__); + return; + } + } + + /* + * Construct new attribute set with CE addr as + * nexthop and without Tunnel Encap attr + */ + encap_attr_export_ce(&hattr, attr, &ce_nexthop); + if (bgp->rfapi_cfg->routemap_export_bgp) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = peer; + info.attr = &hattr; + ret = route_map_apply(bgp->rfapi_cfg->routemap_export_bgp, + prefix, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + return; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + /* + * Rule: disallow route-map alteration of next-hop, because it + * would make it too difficult to keep track of the correspondence + * between VPN routes and unicast routes. + */ + rfapiUnicastNexthop2Prefix(afi, iattr, &post_routemap_nexthop); + + if (!prefix_same(&ce_nexthop, &post_routemap_nexthop)) { + vnc_zlog_debug_verbose( + "%s: route-map modification of nexthop not allowed, skipping", + __func__); + bgp_attr_unintern(&iattr); + return; + } + + bgp_update(peer, prefix, 0, /* addpath_id */ + iattr, /* bgp_update copies this attr */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ + NULL, 0, /* tag not used for unicast */ + 0, NULL); /* EVPN not used */ + bgp_attr_unintern(&iattr); +} + + +/* + * "Withdrawing a Route" export process + */ +void vnc_direct_bgp_del_route_ce(struct bgp *bgp, struct agg_node *rn, + struct bgp_path_info *bpi) +{ + const struct prefix *p = agg_node_get_prefix(rn); + afi_t afi = family2afi(p->family); + struct bgp_path_info *vbpi; + struct prefix ce_nexthop; + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi", __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp ce mode not enabled, skipping", + __func__); + return; + } + + /* + * Extract CE + * This works only for IPv4 because IPv6 addresses are too big + * to fit in an extended community + */ + if (getce(bgp, bpi->attr, &ce_nexthop)) { + vnc_zlog_debug_verbose("%s: EC has no encoded CE, skipping", + __func__); + return; + } + + /* + * Look for other VPN routes with same prefix, same 5226 CE, + * same peer. If at least one is present, don't remove the + * route from the unicast RIB + */ + + for (vbpi = rn->info; vbpi; vbpi = vbpi->next) { + struct prefix ce; + if (bpi == vbpi) + continue; + if (bpi->peer != vbpi->peer) + continue; + if (getce(bgp, vbpi->attr, &ce)) + continue; + if (prefix_same(&ce, &ce_nexthop)) { + vnc_zlog_debug_verbose( + "%s: still have a route via CE, not deleting unicast", + __func__); + return; + } + } + + /* + * withdraw the route + */ + bgp_withdraw(bpi->peer, p, 0, /* addpath_id */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ + NULL, 0, NULL); /* tag not used for unicast */ +} + +static void vnc_direct_bgp_vpn_enable_ce(struct bgp *bgp, afi_t afi) +{ + struct agg_node *rn; + struct bgp_path_info *ri; + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (!bgp) + return; + + if (!(bgp->rfapi_cfg)) + return; + + if (!VNC_EXPORT_BGP_CE_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export of CE routes not enabled, skipping", + __func__); + return; + } + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + /* + * Go through entire ce import table and export to BGP unicast. + */ + for (rn = agg_route_top(bgp->rfapi->it_ce->imported_vpn[afi]); rn; + rn = agg_route_next(rn)) { + if (!rn->info) + continue; + + vnc_zlog_debug_verbose("%s: checking prefix %pRN", __func__, + rn); + + for (ri = rn->info; ri; ri = ri->next) { + + vnc_zlog_debug_verbose("%s: ri->sub_type: %d", __func__, + ri->sub_type); + + if (ri->sub_type == BGP_ROUTE_NORMAL + || ri->sub_type == BGP_ROUTE_RFP + || ri->sub_type == BGP_ROUTE_STATIC) { + + vnc_direct_bgp_add_route_ce(bgp, rn, ri); + } + } + } +} + +static void vnc_direct_bgp_vpn_disable_ce(struct bgp *bgp, afi_t afi) +{ + struct bgp_dest *dest; + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (!bgp) + return; + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + /* + * Go through the entire BGP unicast table and remove routes that + * originated from us + */ + for (dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + + struct bgp_path_info *ri; + struct bgp_path_info *next; + + for (ri = bgp_dest_get_bgp_path_info(dest), next = NULL; ri; + ri = next) { + + next = ri->next; + + if (ri->type == ZEBRA_ROUTE_VNC_DIRECT + && ri->sub_type == BGP_ROUTE_REDISTRIBUTE) { + + bgp_withdraw( + ri->peer, bgp_dest_get_prefix(dest), + 0, /* addpath_id */ + AFI_IP, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for unicast */ + NULL, 0, + NULL); /* tag not used for unicast */ + } + } + } +} + +/*********************************************************************** + * Export methods that set nexthop to CE (from 5226 roo EC) END + ***********************************************************************/ + +/*********************************************************************** + * Export methods that proxy nexthop BEGIN + ***********************************************************************/ + +static struct ecommunity *vnc_route_origin_ecom(struct agg_node *rn) +{ + struct ecommunity *new; + struct bgp_path_info *bpi; + + if (!rn->info) + return NULL; + + new = ecommunity_new(); + + for (bpi = rn->info; bpi; bpi = bpi->next) { + + struct ecommunity_val roec; + + switch (BGP_MP_NEXTHOP_FAMILY(bpi->attr->mp_nexthop_len)) { + case AF_INET: + memset(&roec, 0, sizeof(roec)); + roec.val[0] = 0x01; + roec.val[1] = 0x03; + memcpy(roec.val + 2, + &bpi->attr->mp_nexthop_global_in.s_addr, 4); + roec.val[6] = 0; + roec.val[7] = 0; + ecommunity_add_val(new, &roec, false, false); + break; + case AF_INET6: + /* No support for IPv6 addresses in extended communities + */ + break; + } + } + + if (!new->size) { + ecommunity_free(&new); + new = NULL; + } + + return new; +} + +static struct ecommunity *vnc_route_origin_ecom_single(struct in_addr *origin) +{ + struct ecommunity *new; + struct ecommunity_val roec; + + memset(&roec, 0, sizeof(roec)); + roec.val[0] = 0x01; + roec.val[1] = 0x03; + memcpy(roec.val + 2, &origin->s_addr, 4); + roec.val[6] = 0; + roec.val[7] = 0; + + new = ecommunity_new(); + ecommunity_add_val(new, &roec, false, false); + + if (!new->size) { + ecommunity_free(&new); + new = NULL; + } + + return new; +} + + +/* + * New memory allocation approach: make a ghost attr that + * has non-interned parts for the modifications. ghost attr + * memory is allocated by caller. + */ +static int +encap_attr_export(struct attr *new, struct attr *orig, + struct prefix *new_nexthop, + struct agg_node *rn) /* for VN addrs for ecom list */ + /* if rn is 0, use route's nexthop */ +{ + struct prefix orig_nexthop; + struct prefix *use_nexthop; + static struct ecommunity *ecom_ro; + + if (new_nexthop) { + use_nexthop = new_nexthop; + } else { + use_nexthop = &orig_nexthop; + orig_nexthop.family = + BGP_MP_NEXTHOP_FAMILY(orig->mp_nexthop_len); + if (orig_nexthop.family == AF_INET) { + orig_nexthop.prefixlen = IPV4_MAX_BITLEN; + orig_nexthop.u.prefix4 = orig->mp_nexthop_global_in; + } else if (orig_nexthop.family == AF_INET6) { + orig_nexthop.prefixlen = IPV6_MAX_BITLEN; + orig_nexthop.u.prefix6 = orig->mp_nexthop_global; + } else { + return -1; /* FAIL - can't compute nexthop */ + } + } + + + /* + * Make "new" a ghost attr copy of "orig" + */ + memset(new, 0, sizeof(struct attr)); + *new = *orig; + + /* + * Set nexthop + */ + switch (use_nexthop->family) { + case AF_INET: + new->nexthop = use_nexthop->u.prefix4; + new->mp_nexthop_len = BGP_ATTR_NHLEN_IPV4; /* bytes */ + new->flag |= ATTR_FLAG_BIT(BGP_ATTR_NEXT_HOP); + break; + + case AF_INET6: + new->mp_nexthop_global = use_nexthop->u.prefix6; + new->mp_nexthop_len = BGP_ATTR_NHLEN_IPV6_GLOBAL; /* bytes */ + break; + + default: + assert(0); + break; + } + + if (rn) { + ecom_ro = vnc_route_origin_ecom(rn); + } else { + /* TBD use lcom for IPv6 */ + ecom_ro = vnc_route_origin_ecom_single(&use_nexthop->u.prefix4); + } + if (bgp_attr_get_ecommunity(new)) { + if (ecom_ro) + bgp_attr_set_ecommunity( + new, + ecommunity_merge(ecom_ro, + bgp_attr_get_ecommunity(new))); + } else { + bgp_attr_set_ecommunity(new, ecom_ro); + } + + /* + * Set MED + * + * Note that it will be deleted when BGP sends to any eBGP + * peer unless PEER_FLAG_MED_UNCHANGED is set: + * + * neighbor NEIGHBOR attribute-unchanged med + */ + if (!CHECK_FLAG(new->flag, BGP_ATTR_MULTI_EXIT_DISC)) { + if (CHECK_FLAG(new->flag, BGP_ATTR_LOCAL_PREF)) { + if (new->local_pref > 255) + new->med = 0; + else + new->med = 255 - new->local_pref; + } else { + new->med = 255; /* shouldn't happen */ + } + new->flag |= ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC); + } + + /* + * "new" is now a ghost attr: + * - it owns an "extra" struct + * - it owns any non-interned parts + * - any references to interned parts are not counted + * + * Caller should, after using the attr, call: + * - bgp_attr_flush() to free non-interned parts + */ + + return 0; +} + +/* + * "Adding a Route" export process + */ +void vnc_direct_bgp_add_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn) +{ + struct attr attr = {0}; + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + const struct prefix *p = agg_node_get_prefix(rn); + afi_t afi = family2afi(p->family); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + if (!listcount(bgp->rfapi_cfg->rfg_export_direct_bgp_l)) { + vnc_zlog_debug_verbose( + "%s: no bgp-direct export nve group, skipping", + __func__); + return; + } + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + /* TBD set some configured med, see add_vnc_route() */ + + vnc_zlog_debug_verbose( + "%s: looping over nve-groups in direct-bgp export list", + __func__); + + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + nnode, rfgn)) { + + struct listnode *ln; + + /* + * If nve group is not defined yet, skip it + */ + if (!rfgn->rfg) + continue; + + /* + * If the nve group uses a different import table, skip it + */ + if (import_table != rfgn->rfg->rfapi_import_table) + continue; + + /* + * if no NVEs currently associated with this group, skip it + */ + if (rfgn->rfg->type != RFAPI_GROUP_CFG_VRF && !rfgn->rfg->nves) + continue; + + /* + * per-nve-group prefix list check + */ + if (rfgn->rfg->plist_export_bgp[afi]) { + if (prefix_list_apply(rfgn->rfg->plist_export_bgp[afi], + p) + == PREFIX_DENY) + + continue; + } + + if (rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { + vnc_direct_add_rn_group_rd(bgp, rfgn->rfg, rn, &attr, + afi, rfgn->rfg->rfd); + /* + * yuck! + * - but consistent with rest of function + */ + continue; + } + /* + * For each NVE that is assigned to the export nve group, + * generate + * a route with that NVE as its next hop + */ + for (ln = listhead(rfgn->rfg->nves); ln; + ln = listnextnode(ln)) { + vnc_direct_add_rn_group_rd(bgp, rfgn->rfg, rn, &attr, + afi, listgetdata(ln)); + } + } + + aspath_unintern(&attr.aspath); +} + +/* + * "Withdrawing a Route" export process + */ +void vnc_direct_bgp_del_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn) +{ + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + const struct prefix *p = agg_node_get_prefix(rn); + afi_t afi = family2afi(p->family); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi route node", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + if (!listcount(bgp->rfapi_cfg->rfg_export_direct_bgp_l)) { + vnc_zlog_debug_verbose( + "%s: no bgp-direct export nve group, skipping", + __func__); + return; + } + + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + nnode, rfgn)) { + + struct listnode *ln; + + /* + * If nve group is not defined yet, skip it + */ + if (!rfgn->rfg) + continue; + + /* + * if no NVEs currently associated with this group, skip it + */ + if (rfgn->rfg->type != RFAPI_GROUP_CFG_VRF && !rfgn->rfg->nves) + continue; + + /* + * If the nve group uses a different import table, + * skip it + */ + if (import_table != rfgn->rfg->rfapi_import_table) + continue; + + if (rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { + struct prefix nhp; + struct rfapi_descriptor *irfd; + + irfd = rfgn->rfg->rfd; + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) + continue; + + bgp_withdraw(irfd->peer, p, /* prefix */ + 0, /* addpath_id */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for unicast */ + NULL, 0, + NULL); /* tag not used for unicast */ + /* + * yuck! + * - but consistent with rest of function + */ + continue; + } + /* + * For each NVE that is assigned to the export nve group, + * generate + * a route with that NVE as its next hop + */ + for (ln = listhead(rfgn->rfg->nves); ln; + ln = listnextnode(ln)) { + + struct prefix nhp; + struct rfapi_descriptor *irfd; + + irfd = listgetdata(ln); + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) + continue; + + bgp_withdraw(irfd->peer, p, /* prefix */ + 0, /* addpath_id */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for unicast */ + NULL, 0, + NULL); /* tag not used for unicast */ + } + } +} + +void vnc_direct_bgp_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + struct rfapi_nve_group_cfg *rfg = rfd->rfg; + afi_t afi = family2afi(rfd->vn_addr.addr_family); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of nve vn addr", + __func__); + return; + } + + if (!bgp) + return; + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + /* + * Loop over the list of NVE-Groups configured for + * exporting to direct-bgp and see if this new NVE's + * group is among them. + */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + nnode, rfgn)) { + + /* + * Yes, this NVE's group is configured for export to direct-bgp + */ + if (rfgn->rfg == rfg) { + + struct agg_table *rt = NULL; + struct agg_node *rn; + struct attr attr = {0}; + struct rfapi_import_table *import_table; + + + import_table = rfg->rfapi_import_table; + + if (afi == AFI_IP || afi == AFI_IP6) { + rt = import_table->imported_vpn[afi]; + } else { + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", + __func__, afi); + return; + } + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + /* TBD set some configured med, see add_vnc_route() */ + + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + + if (rn->info) { + + struct prefix nhp; + struct rfapi_descriptor *irfd = rfd; + struct attr hattr; + struct attr *iattr; + struct bgp_path_info info; + const struct prefix *p = + agg_node_get_prefix(rn); + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, + &nhp)) + continue; + + /* + * per-nve-group prefix list check + */ + if (rfgn->rfg->plist_export_bgp[afi]) { + if (prefix_list_apply( + rfgn->rfg->plist_export_bgp + [afi], + p) + == PREFIX_DENY) + + continue; + } + + + /* + * Construct new attribute set with + * NVE's VN addr as + * nexthop and without Tunnel Encap attr + */ + if (encap_attr_export(&hattr, &attr, + &nhp, rn)) + continue; + + if (rfgn->rfg->routemap_export_bgp) { + route_map_result_t ret; + info.peer = irfd->peer; + info.attr = &hattr; + ret = route_map_apply( + rfgn->rfg + ->routemap_export_bgp, + p, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + continue; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + bgp_update( + irfd->peer, p, /* prefix */ + 0, /* addpath_id */ + iattr, /* bgp_update copies + it */ + afi, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, NULL, + /* RD not used for unicast */ + NULL, + /* tag not used for unicast */ + 0, 0, NULL); /* EVPN not used */ + + bgp_attr_unintern(&iattr); + } + } + + aspath_unintern(&attr.aspath); + } + } +} + + +void vnc_direct_bgp_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + struct listnode *node, *nnode; + struct rfapi_rfg_name *rfgn; + struct rfapi_nve_group_cfg *rfg = rfd->rfg; + afi_t afi = family2afi(rfd->vn_addr.addr_family); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of nve vn addr", + __func__); + return; + } + + if (!bgp) + return; + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + /* + * Loop over the list of NVE-Groups configured for + * exporting to direct-bgp and see if this new NVE's + * group is among them. + */ + for (ALL_LIST_ELEMENTS(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + nnode, rfgn)) { + + /* + * Yes, this NVE's group is configured for export to direct-bgp + */ + if (rfg && rfgn->rfg == rfg) { + + struct agg_table *rt = NULL; + struct agg_node *rn; + struct rfapi_import_table *import_table; + + import_table = rfg->rfapi_import_table; + + if (afi == AFI_IP || afi == AFI_IP6) { + rt = import_table->imported_vpn[afi]; + } else { + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", + __func__, afi); + return; + } + + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + + if (rn->info) { + const struct prefix *p = + agg_node_get_prefix(rn); + struct prefix nhp; + struct rfapi_descriptor *irfd = rfd; + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, + &nhp)) + continue; + + bgp_withdraw(irfd->peer, p, /* prefix */ + 0, /* addpath_id */ + afi, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for + unicast */ + NULL, 0, NULL); /* tag not + used for + unicast */ + } + } + } + } +} + +static void vnc_direct_add_rn_group_rd(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + struct agg_node *rn, struct attr *attr, + afi_t afi, struct rfapi_descriptor *irfd) +{ + struct prefix nhp; + struct bgp_path_info info; + struct attr hattr; + struct attr *iattr; + const struct prefix *p = agg_node_get_prefix(rn); + + if (irfd == NULL && rfg->type != RFAPI_GROUP_CFG_VRF) { + /* need new rfapi_handle, for peer strcture + * -- based on vnc_add_vrf_prefi */ + assert(rfg->rfd == NULL); + + if (!rfg->rt_export_list || !rfg->rfapi_import_table) { + vnc_zlog_debug_verbose( + "%s: VRF \"%s\" is missing RT import/export configuration.", + __func__, rfg->name); + return; + } + if (!rfg->rd.prefixlen) { + vnc_zlog_debug_verbose( + "%s: VRF \"%s\" is missing RD configuration.", + __func__, rfg->name); + return; + } + if (rfg->label > MPLS_LABEL_MAX) { + vnc_zlog_debug_verbose( + "%s: VRF \"%s\" is missing default label configuration.", + __func__, rfg->name); + return; + } + + irfd = XCALLOC(MTYPE_RFAPI_DESC, + sizeof(struct rfapi_descriptor)); + irfd->bgp = bgp; + rfg->rfd = irfd; + /* + * leave most fields empty as will get from (dynamic) config + * when needed + */ + irfd->default_tunneltype_option.type = BGP_ENCAP_TYPE_MPLS; + irfd->cookie = rfg; + if (rfg->vn_prefix.family + && !CHECK_FLAG(rfg->flags, RFAPI_RFG_VPN_NH_SELF)) { + rfapiQprefix2Raddr(&rfg->vn_prefix, &irfd->vn_addr); + } else { + memset(&irfd->vn_addr, 0, sizeof(struct rfapi_ip_addr)); + irfd->vn_addr.addr_family = AF_INET; + irfd->vn_addr.addr.v4 = bgp->router_id; + } + irfd->un_addr = irfd->vn_addr; /* sigh, need something in UN for + lookups */ + vnc_zlog_debug_verbose("%s: Opening RFD for VRF %s", __func__, + rfg->name); + rfapi_init_and_open(bgp, irfd, rfg); + } + + if (irfd == NULL || rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) + return; + + /* + * Construct new attribute set with NVE's VN + * addr as + * nexthop and without Tunnel Encap attr + */ + if (encap_attr_export(&hattr, attr, &nhp, rn)) + return; + + if (VNC_DEBUG(EXPORT_BGP_DIRECT_ADD)) { + vnc_zlog_debug_any("%s: attr follows", __func__); + rfapiPrintAttrPtrs(NULL, attr); + vnc_zlog_debug_any("%s: hattr follows", __func__); + rfapiPrintAttrPtrs(NULL, &hattr); + } + + if (rfg->routemap_export_bgp) { + route_map_result_t ret; + + info.peer = irfd->peer; + info.attr = &hattr; + ret = route_map_apply(rfg->routemap_export_bgp, p, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + vnc_zlog_debug_verbose( + "%s: route map says DENY, so not calling bgp_update", + __func__); + return; + } + } + + if (VNC_DEBUG(EXPORT_BGP_DIRECT_ADD)) { + vnc_zlog_debug_any("%s: hattr after route_map_apply:", + __func__); + rfapiPrintAttrPtrs(NULL, &hattr); + } + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + bgp_update(irfd->peer, p, /* prefix */ + 0, /* addpath_id */ + iattr, /* bgp_update copies it */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ + NULL, /* tag not used for unicast */ + 0, 0, NULL); /* EVPN not used */ + + bgp_attr_unintern(&iattr); + + return; +} + +/* + * Caller is responsible for ensuring that the specified nve-group + * is actually part of the list of exported nve groups. + */ +static void vnc_direct_bgp_add_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi) +{ + struct agg_table *rt = NULL; + struct agg_node *rn; + struct attr attr = {0}; + struct rfapi_import_table *import_table; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + import_table = rfg->rfapi_import_table; + if (!import_table) { + vnc_zlog_debug_verbose( + "%s: import table not defined, returning", __func__); + return; + } + + if (afi == AFI_IP || afi == AFI_IP6) { + rt = import_table->imported_vpn[afi]; + } else { + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); + return; + } + + if (!rfg->nves && rfg->type != RFAPI_GROUP_CFG_VRF) { + vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); + return; + } + + bgp_attr_default_set(&attr, bgp, BGP_ORIGIN_INCOMPLETE); + /* TBD set some configured med, see add_vnc_route() */ + + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + + if (rn->info) { + const struct prefix *p = agg_node_get_prefix(rn); + struct listnode *ln; + + /* + * per-nve-group prefix list check + */ + if (rfg->plist_export_bgp[afi]) { + if (prefix_list_apply( + rfg->plist_export_bgp[afi], p) + == PREFIX_DENY) + + continue; + } + if (rfg->type == RFAPI_GROUP_CFG_VRF) { + vnc_direct_add_rn_group_rd(bgp, rfg, rn, &attr, + afi, rfg->rfd); + /* + * yuck! + * - but consistent with rest of function + */ + continue; + } + /* + * For each NVE that is assigned to the export nve + * group, generate + * a route with that NVE as its next hop + */ + for (ln = listhead(rfg->nves); ln; + ln = listnextnode(ln)) { + vnc_direct_add_rn_group_rd(bgp, rfg, rn, &attr, + afi, + listgetdata(ln)); + } + } + } + + aspath_unintern(&attr.aspath); +} + + +/* + * Caller is responsible for ensuring that the specified nve-group + * is actually part of the list of exported nve groups. + */ +void vnc_direct_bgp_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) +{ + vnc_direct_bgp_add_group_afi(bgp, rfg, AFI_IP); + vnc_direct_bgp_add_group_afi(bgp, rfg, AFI_IP6); +} + +static void vnc_direct_del_rn_group_rd(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + struct agg_node *rn, afi_t afi, + struct rfapi_descriptor *irfd) +{ + if (irfd == NULL) + return; + + bgp_withdraw(irfd->peer, agg_node_get_prefix(rn), /* prefix */ + 0, /* addpath_id */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ + NULL, 0, NULL); /* tag not used for unicast */ + return; +} + +/* + * Caller is responsible for ensuring that the specified nve-group + * was actually part of the list of exported nve groups. + */ +static void vnc_direct_bgp_del_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi) +{ + struct agg_table *rt = NULL; + struct agg_node *rn; + struct rfapi_import_table *import_table; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + import_table = rfg->rfapi_import_table; + if (!import_table) { + vnc_zlog_debug_verbose( + "%s: import table not defined, returning", __func__); + return; + } + + rt = import_table->imported_vpn[afi]; + + if (!rfg->nves && rfg->type != RFAPI_GROUP_CFG_VRF) { + vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); + return; + } + + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) + if (rn->info) { + if (rfg->type == RFAPI_GROUP_CFG_VRF) + vnc_direct_del_rn_group_rd(bgp, rfg, rn, afi, + rfg->rfd); + else { + struct listnode *ln; + + /* + * For each NVE that is assigned to the export + * nve + * group, generate + * a route with that NVE as its next hop + */ + for (ln = listhead(rfg->nves); ln; + ln = listnextnode(ln)) + vnc_direct_del_rn_group_rd( + bgp, rfg, rn, afi, + listgetdata(ln)); + } + } +} + +/* + * Caller is responsible for ensuring that the specified nve-group + * was actually part of the list of exported nve groups. + */ +void vnc_direct_bgp_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) +{ + vnc_direct_bgp_del_group_afi(bgp, rfg, AFI_IP); + vnc_direct_bgp_del_group_afi(bgp, rfg, AFI_IP6); +} + +void vnc_direct_bgp_reexport_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi) +{ + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + if (VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + /* + * look in the list of currently-exported groups + */ + for (ALL_LIST_ELEMENTS_RO( + bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + /* + * If it matches, reexport it + */ + vnc_direct_bgp_del_group_afi(bgp, rfg, afi); + vnc_direct_bgp_add_group_afi(bgp, rfg, afi); + break; + } + } + } +} + + +static void vnc_direct_bgp_unexport_table(afi_t afi, struct agg_table *rt, + struct list *nve_list) +{ + if (nve_list) { + + struct agg_node *rn; + + for (rn = agg_route_top(rt); rn; rn = agg_route_next(rn)) { + + if (rn->info) { + + struct listnode *hln; + struct rfapi_descriptor *irfd; + + for (ALL_LIST_ELEMENTS_RO(nve_list, hln, + irfd)) { + + bgp_withdraw(irfd->peer, + agg_node_get_prefix(rn), + 0, /* addpath_id */ + afi, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for + unicast */ + NULL, 0, NULL); /* tag not + used for + unicast, + EVPN + neither */ + } + } + } + } +} + +static void import_table_to_nve_list_direct_bgp(struct bgp *bgp, + struct rfapi_import_table *it, + struct list **nves, + uint8_t family) +{ + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* + * Loop over the list of NVE-Groups configured for + * exporting to direct-bgp. + * + * Build a list of NVEs that use this import table + */ + *nves = NULL; + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_direct_bgp_l, node, + rfgn)) { + + /* + * If this NVE-Group's import table matches the current one + */ + if (rfgn->rfg && rfgn->rfg->rfapi_import_table == it) { + if (rfgn->rfg->nves) + nve_group_to_nve_list(rfgn->rfg, nves, family); + else if (rfgn->rfg->rfd + && rfgn->rfg->type == RFAPI_GROUP_CFG_VRF) { + if (!*nves) + *nves = list_new(); + listnode_add(*nves, rfgn->rfg->rfd); + } + } + } +} + +void vnc_direct_bgp_vpn_enable(struct bgp *bgp, afi_t afi) +{ + struct listnode *rfgn; + struct rfapi_nve_group_cfg *rfg; + + if (!bgp) + return; + + if (!VNC_EXPORT_BGP_GRP_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + /* + * Policy is applied per-nve-group, so we need to iterate + * over the groups to add everything. + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->nve_groups_sequential, rfgn, + rfg)) { + + /* + * contains policy management + */ + vnc_direct_bgp_add_group_afi(bgp, rfg, afi); + } +} + + +void vnc_direct_bgp_vpn_disable(struct bgp *bgp, afi_t afi) +{ + struct rfapi_import_table *it; + uint8_t family = afi2family(afi); + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (!bgp) + return; + + if (!bgp->rfapi) { + vnc_zlog_debug_verbose("%s: rfapi not initialized", __func__); + return; + } + + if (!family || (afi != AFI_IP && afi != AFI_IP6)) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + for (it = bgp->rfapi->imports; it; it = it->next) { + + struct list *nve_list = NULL; + + import_table_to_nve_list_direct_bgp(bgp, it, &nve_list, family); + + if (nve_list) { + vnc_direct_bgp_unexport_table( + afi, it->imported_vpn[afi], nve_list); + list_delete(&nve_list); + } + } +} + + +/*********************************************************************** + * Export methods that proxy nexthop END + ***********************************************************************/ + + +/*********************************************************************** + * Export methods that preserve original nexthop BEGIN + * rh = "registering nve" + ***********************************************************************/ + + +/* + * "Adding a Route" export process + * TBD do we need to check bpi->type and bpi->sub_type here, or does + * caller do it? + */ +void vnc_direct_bgp_rh_add_route(struct bgp *bgp, afi_t afi, + const struct prefix *prefix, struct peer *peer, + struct attr *attr) +{ + struct vnc_export_info *eti; + struct attr hattr; + struct rfapi_cfg *hc; + struct attr *iattr; + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of route node", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!(hc = bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp RH mode not enabled, skipping", + __func__); + return; + } + + /* + * prefix list check + */ + if (hc->plist_export_bgp[afi]) { + if (prefix_list_apply(hc->plist_export_bgp[afi], prefix) + == PREFIX_DENY) + return; + } + + /* + * Construct new attribute set with NVE's VN addr as + * nexthop and without Tunnel Encap attr + */ + if (encap_attr_export(&hattr, attr, NULL, NULL)) + return; + if (hc->routemap_export_bgp) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = peer; + info.attr = &hattr; + ret = route_map_apply(hc->routemap_export_bgp, prefix, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + return; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + /* + * record route information that we will need to expire + * this route + */ + eti = vnc_eti_get(bgp, EXPORT_TYPE_BGP, prefix, peer, + ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); + rfapiGetVncLifetime(attr, &eti->lifetime); + eti->lifetime = rfapiGetHolddownFromLifetime(eti->lifetime); + + /* + * export expiration timer is already running on + * this route: cancel it + */ + EVENT_OFF(eti->timer); + + bgp_update(peer, prefix, /* prefix */ + 0, /* addpath_id */ + iattr, /* bgp_update copies this attr */ + afi, SAFI_UNICAST, ZEBRA_ROUTE_VNC_DIRECT_RH, + BGP_ROUTE_REDISTRIBUTE, NULL, /* RD not used for unicast */ + NULL, /* tag not used for unicast, EVPN neither */ + 0, 0, NULL); /* EVPN not used */ + bgp_attr_unintern(&iattr); +} + +static void vncExportWithdrawTimer(struct event *t) +{ + struct vnc_export_info *eti = EVENT_ARG(t); + const struct prefix *p = agg_node_get_prefix(eti->node); + + /* + * withdraw the route + */ + bgp_withdraw(eti->peer, p, 0, /* addpath_id */ + family2afi(p->family), SAFI_UNICAST, eti->type, + eti->subtype, NULL, /* RD not used for unicast */ + NULL, 0, + NULL); /* tag not used for unicast, EVPN neither */ + + /* + * Free the eti + */ + vnc_eti_delete(eti); +} + +/* + * "Withdrawing a Route" export process + * TBD do we need to check bpi->type and bpi->sub_type here, or does + * caller do it? + */ +void vnc_direct_bgp_rh_del_route(struct bgp *bgp, afi_t afi, + const struct prefix *prefix, struct peer *peer) +{ + struct vnc_export_info *eti; + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi route node", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->redist[afi][ZEBRA_ROUTE_VNC_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of VNC direct routes is off", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export-to-bgp group mode not enabled, skipping", + __func__); + return; + } + + eti = vnc_eti_get(bgp, EXPORT_TYPE_BGP, prefix, peer, + ZEBRA_ROUTE_VNC_DIRECT_RH, BGP_ROUTE_REDISTRIBUTE); + + if (!eti->timer && eti->lifetime <= INT32_MAX) { + eti->timer = NULL; + event_add_timer(bm->master, vncExportWithdrawTimer, eti, + eti->lifetime, &eti->timer); + vnc_zlog_debug_verbose( + "%s: set expiration timer for %u seconds", __func__, + eti->lifetime); + } +} + + +void vnc_direct_bgp_rh_vpn_enable(struct bgp *bgp, afi_t afi) +{ + struct prefix_rd prd; + struct bgp_dest *pdest; + struct rfapi_cfg *hc; + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (!bgp) + return; + + if (!(hc = bgp->rfapi_cfg)) + return; + + if (!VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose( + "%s: export of RH routes not enabled, skipping", + __func__); + return; + } + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + /* + * Go through the entire BGP VPN table and export to BGP unicast. + */ + + vnc_zlog_debug_verbose("%s: starting RD loop", __func__); + + /* Loop over all the RDs */ + for (pdest = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); pdest; + pdest = bgp_route_next(pdest)) { + + struct bgp_table *table; + struct bgp_dest *dest; + struct bgp_path_info *ri; + const struct prefix *pdest_p = bgp_dest_get_prefix(pdest); + + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pdest_p->u.val, 8); + + /* This is the per-RD table of prefixes */ + table = bgp_dest_get_bgp_table_info(pdest); + + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p; + + /* + * skip prefix list check if no routes here + */ + if (!bgp_dest_has_bgp_path_info_data(dest)) + continue; + + vnc_zlog_debug_verbose("%s: checking prefix %pBD", + __func__, dest); + + dest_p = bgp_dest_get_prefix(dest); + + /* + * prefix list check + */ + if (hc->plist_export_bgp[afi]) { + if (prefix_list_apply(hc->plist_export_bgp[afi], + dest_p) + == PREFIX_DENY) { + + vnc_zlog_debug_verbose( + "%s: prefix list says DENY", + __func__); + continue; + } + } + + for (ri = bgp_dest_get_bgp_path_info(dest); ri; + ri = ri->next) { + + vnc_zlog_debug_verbose("%s: ri->sub_type: %d", + __func__, ri->sub_type); + + if (ri->sub_type == BGP_ROUTE_NORMAL + || ri->sub_type == BGP_ROUTE_RFP) { + + struct vnc_export_info *eti; + struct attr hattr; + struct attr *iattr; + + /* + * Construct new attribute set with + * NVE's VN addr as + * nexthop and without Tunnel Encap attr + */ + if (encap_attr_export(&hattr, ri->attr, + NULL, NULL)) { + vnc_zlog_debug_verbose( + "%s: encap_attr_export failed", + __func__); + continue; + } + + if (hc->routemap_export_bgp) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = ri->peer; + info.attr = &hattr; + ret = route_map_apply( + hc->routemap_export_bgp, + dest_p, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + vnc_zlog_debug_verbose( + "%s: route map says DENY", + __func__); + continue; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + /* + * record route information that we will + * need to expire + * this route + */ + eti = vnc_eti_get( + bgp, EXPORT_TYPE_BGP, dest_p, + ri->peer, + ZEBRA_ROUTE_VNC_DIRECT_RH, + BGP_ROUTE_REDISTRIBUTE); + rfapiGetVncLifetime(ri->attr, + &eti->lifetime); + + /* + * export expiration timer is + * already running on + * this route: cancel it + */ + EVENT_OFF(eti->timer); + + vnc_zlog_debug_verbose( + "%s: calling bgp_update", + __func__); + + bgp_update( + ri->peer, dest_p, /* prefix */ + 0, /* addpath_id */ + iattr, /* bgp_update copies + it */ + AFI_IP, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT_RH, + BGP_ROUTE_REDISTRIBUTE, NULL, + /* RD not used for unicast */ + NULL, + /* tag not used for unicast, + or EVPN */ + 0, 0, NULL); /* EVPN not used */ + + bgp_attr_unintern(&iattr); + } + } + } + } +} + +void vnc_direct_bgp_rh_vpn_disable(struct bgp *bgp, afi_t afi) +{ + struct bgp_dest *dest; + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (!bgp) + return; + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi: %d", __func__, afi); + return; + } + + /* + * Go through the entire BGP unicast table and remove routes that + * originated from us + */ + for (dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + struct bgp_path_info *ri; + struct bgp_path_info *next; + + for (ri = bgp_dest_get_bgp_path_info(dest), next = NULL; ri; + ri = next) { + + next = ri->next; + + if (ri->type == ZEBRA_ROUTE_VNC_DIRECT_RH + && ri->sub_type == BGP_ROUTE_REDISTRIBUTE) { + + struct vnc_export_info *eti; + + /* + * Delete routes immediately (no timer) + */ + eti = vnc_eti_checktimer( + bgp, EXPORT_TYPE_BGP, dest_p, ri->peer, + ZEBRA_ROUTE_VNC_DIRECT_RH, + BGP_ROUTE_REDISTRIBUTE); + if (eti) { + EVENT_OFF(eti->timer); + vnc_eti_delete(eti); + } + + bgp_withdraw(ri->peer, dest_p, /* prefix */ + 0, /* addpath_id */ + AFI_IP, SAFI_UNICAST, + ZEBRA_ROUTE_VNC_DIRECT_RH, + BGP_ROUTE_REDISTRIBUTE, + NULL, /* RD not used for unicast */ + NULL, 0, NULL); /* tag not used for + unicast, EVPN + neither */ + } + } + } +} + +void vnc_direct_bgp_rh_reexport(struct bgp *bgp, afi_t afi) +{ + if (VNC_EXPORT_BGP_RH_ENABLED(bgp->rfapi_cfg)) { + vnc_direct_bgp_rh_vpn_disable(bgp, afi); + vnc_direct_bgp_rh_vpn_enable(bgp, afi); + } +} + +/*********************************************************************** + * Generic Export methods + ***********************************************************************/ + +/* + * Assumes the correct mode bits are already turned on. Thus it + * is OK to call this function from, e.g., bgp_redistribute_set() + * without caring if export is enabled or not + */ +void vnc_export_bgp_enable(struct bgp *bgp, afi_t afi) +{ + if (!bgp->rfapi_cfg) + return; + + switch (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) { + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE: + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP: + vnc_direct_bgp_vpn_enable(bgp, afi); + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH: + vnc_direct_bgp_rh_vpn_enable(bgp, afi); + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE: + vnc_direct_bgp_vpn_enable_ce(bgp, afi); + break; + } +} + +void vnc_export_bgp_disable(struct bgp *bgp, afi_t afi) +{ + if (!bgp->rfapi_cfg) + return; + + switch (bgp->rfapi_cfg->flags & BGP_VNC_CONFIG_EXPORT_BGP_MODE_BITS) { + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_NONE: + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_GRP: + vnc_direct_bgp_vpn_disable(bgp, afi); + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_RH: + vnc_direct_bgp_rh_vpn_disable(bgp, afi); + break; + + case BGP_VNC_CONFIG_EXPORT_BGP_MODE_CE: + vnc_direct_bgp_vpn_disable_ce(bgp, afi); + break; + } +} + +void vnc_export_bgp_prechange(struct bgp *bgp) +{ + vnc_export_bgp_disable(bgp, AFI_IP); + vnc_export_bgp_disable(bgp, AFI_IP6); +} + +void vnc_export_bgp_postchange(struct bgp *bgp) +{ + vnc_export_bgp_enable(bgp, AFI_IP); + vnc_export_bgp_enable(bgp, AFI_IP6); +} + +void vnc_direct_bgp_reexport(struct bgp *bgp, afi_t afi) +{ + vnc_export_bgp_disable(bgp, afi); + vnc_export_bgp_enable(bgp, afi); +} diff --git a/bgpd/rfapi/vnc_export_bgp.h b/bgpd/rfapi/vnc_export_bgp.h new file mode 100644 index 0000000..9ea20b1 --- /dev/null +++ b/bgpd/rfapi/vnc_export_bgp.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ +#define _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ + +#include "lib/zebra.h" +#include "lib/prefix.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" + + +extern void vnc_direct_bgp_rh_reexport(struct bgp *bgp, afi_t afi); + +extern void vnc_export_bgp_prechange(struct bgp *bgp); + +extern void vnc_export_bgp_postchange(struct bgp *bgp); + +extern void vnc_export_bgp_enable(struct bgp *bgp, afi_t afi); + +extern void vnc_export_bgp_disable(struct bgp *bgp, afi_t afi); + +#endif /* _QUAGGA_RFAPI_VNC_EXPORT_BGP_H_ */ diff --git a/bgpd/rfapi/vnc_export_bgp_p.h b/bgpd/rfapi/vnc_export_bgp_p.h new file mode 100644 index 0000000..8f5b613 --- /dev/null +++ b/bgpd/rfapi/vnc_export_bgp_p.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ +#define _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ + +#include "lib/zebra.h" +#include "lib/prefix.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" + +#include "rfapi_private.h" + +extern void vnc_direct_bgp_add_route_ce(struct bgp *bgp, struct agg_node *rn, + struct bgp_path_info *bpi); + +extern void vnc_direct_bgp_del_route_ce(struct bgp *bgp, struct agg_node *rn, + struct bgp_path_info *bpi); + +extern void vnc_direct_bgp_add_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn); + +extern void vnc_direct_bgp_del_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn); + +extern void vnc_direct_bgp_add_nve(struct bgp *bgp, + struct rfapi_descriptor *rfd); + +extern void vnc_direct_bgp_del_nve(struct bgp *bgp, + struct rfapi_descriptor *rfd); + +extern void vnc_direct_bgp_add_group(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg); + +extern void vnc_direct_bgp_del_group(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg); + +extern void vnc_direct_bgp_reexport_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi); + + +extern void vnc_direct_bgp_rh_add_route(struct bgp *bgp, afi_t afi, + const struct prefix *prefix, + struct peer *peer, struct attr *attr); + + +extern void vnc_direct_bgp_rh_del_route(struct bgp *bgp, afi_t afi, + const struct prefix *prefix, + struct peer *peer); + +extern void vnc_direct_bgp_reexport(struct bgp *bgp, afi_t afi); + +#endif /* _QUAGGA_RFAPI_VNC_EXPORT_BGP_P_H_ */ diff --git a/bgpd/rfapi/vnc_export_table.c b/bgpd/rfapi/vnc_export_table.c new file mode 100644 index 0000000..4b6baca --- /dev/null +++ b/bgpd/rfapi/vnc_export_table.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/memory.h" +#include "lib/vty.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" + +#include "bgpd/rfapi/vnc_export_table.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/vnc_debug.h" + +struct agg_node *vnc_etn_get(struct bgp *bgp, vnc_export_type_t type, + const struct prefix *p) +{ + struct agg_table *t = NULL; + struct agg_node *rn = NULL; + afi_t afi; + + if (!bgp || !bgp->rfapi) + return NULL; + + afi = family2afi(p->family); + assert(afi == AFI_IP || afi == AFI_IP6); + + switch (type) { + case EXPORT_TYPE_BGP: + if (!bgp->rfapi->rt_export_bgp[afi]) + bgp->rfapi->rt_export_bgp[afi] = agg_table_init(); + t = bgp->rfapi->rt_export_bgp[afi]; + break; + + case EXPORT_TYPE_ZEBRA: + if (!bgp->rfapi->rt_export_zebra[afi]) + bgp->rfapi->rt_export_zebra[afi] = agg_table_init(); + t = bgp->rfapi->rt_export_zebra[afi]; + break; + } + + if (t) + rn = agg_node_get(t, p); + return rn; +} + +struct agg_node *vnc_etn_lookup(struct bgp *bgp, vnc_export_type_t type, + const struct prefix *p) +{ + struct agg_table *t = NULL; + struct agg_node *rn = NULL; + afi_t afi; + + if (!bgp || !bgp->rfapi) + return NULL; + + afi = family2afi(p->family); + assert(afi == AFI_IP || afi == AFI_IP6); + + switch (type) { + case EXPORT_TYPE_BGP: + if (!bgp->rfapi->rt_export_bgp[afi]) + bgp->rfapi->rt_export_bgp[afi] = agg_table_init(); + t = bgp->rfapi->rt_export_bgp[afi]; + break; + + case EXPORT_TYPE_ZEBRA: + if (!bgp->rfapi->rt_export_zebra[afi]) + bgp->rfapi->rt_export_zebra[afi] = agg_table_init(); + t = bgp->rfapi->rt_export_zebra[afi]; + break; + } + + if (t) + rn = agg_node_lookup(t, p); + return rn; +} + +struct vnc_export_info *vnc_eti_get(struct bgp *bgp, vnc_export_type_t etype, + const struct prefix *p, struct peer *peer, + uint8_t type, uint8_t subtype) +{ + struct agg_node *etn; + struct vnc_export_info *eti; + + etn = vnc_etn_get(bgp, etype, p); + assert(etn); + + for (eti = etn->info; eti; eti = eti->next) { + if (peer == eti->peer && type == eti->type + && subtype == eti->subtype) { + + break; + } + } + + if (eti) { + agg_unlock_node(etn); + } else { + eti = XCALLOC(MTYPE_RFAPI_ETI, sizeof(struct vnc_export_info)); + eti->node = etn; + eti->peer = peer; + peer_lock(peer); + eti->type = type; + eti->subtype = subtype; + eti->next = etn->info; + etn->info = eti; + } + + return eti; +} + +void vnc_eti_delete(struct vnc_export_info *goner) +{ + struct agg_node *etn; + struct vnc_export_info *eti; + struct vnc_export_info *eti_prev = NULL; + + etn = goner->node; + + for (eti = etn->info; eti; eti_prev = eti, eti = eti->next) { + if (eti == goner) + break; + } + + if (!eti) { + vnc_zlog_debug_verbose("%s: COULDN'T FIND ETI", __func__); + return; + } + + if (eti_prev) { + eti_prev->next = goner->next; + } else { + etn->info = goner->next; + } + + peer_unlock(eti->peer); + goner->node = NULL; + XFREE(MTYPE_RFAPI_ETI, goner); + + agg_unlock_node(etn); +} + +struct vnc_export_info *vnc_eti_checktimer(struct bgp *bgp, + vnc_export_type_t etype, + const struct prefix *p, + struct peer *peer, uint8_t type, + uint8_t subtype) +{ + struct agg_node *etn; + struct vnc_export_info *eti; + + etn = vnc_etn_lookup(bgp, etype, p); + if (!etn) + return NULL; + + for (eti = etn->info; eti; eti = eti->next) { + if (peer == eti->peer && type == eti->type + && subtype == eti->subtype) { + + break; + } + } + + agg_unlock_node(etn); + + if (eti && eti->timer) + return eti; + + return NULL; +} diff --git a/bgpd/rfapi/vnc_export_table.h b/bgpd/rfapi/vnc_export_table.h new file mode 100644 index 0000000..f715de0 --- /dev/null +++ b/bgpd/rfapi/vnc_export_table.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ +#define _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ + +#include "lib/table.h" +#include "frrevent.h" +#include "lib/vty.h" + +#include "bgpd/bgpd.h" + +#define VNC_EXPORT_TYPE_BGP 1 +#define VNC_EXPORT_TYPE_ZEBRA 2 + +typedef enum vnc_export_type { + EXPORT_TYPE_BGP, + EXPORT_TYPE_ZEBRA +} vnc_export_type_t; + +struct vnc_export_info { + struct vnc_export_info *next; + struct agg_node *node; + struct peer *peer; + uint8_t type; + uint8_t subtype; + uint32_t lifetime; + struct event *timer; +}; + +extern struct agg_node *vnc_etn_get(struct bgp *bgp, vnc_export_type_t type, + const struct prefix *p); + +extern struct agg_node *vnc_etn_lookup(struct bgp *bgp, vnc_export_type_t type, + const struct prefix *p); + +extern struct vnc_export_info * +vnc_eti_get(struct bgp *bgp, vnc_export_type_t etype, const struct prefix *p, + struct peer *peer, uint8_t type, uint8_t subtype); + +extern void vnc_eti_delete(struct vnc_export_info *goner); + +extern struct vnc_export_info * +vnc_eti_checktimer(struct bgp *bgp, vnc_export_type_t etype, + const struct prefix *p, struct peer *peer, uint8_t type, + uint8_t subtype); + + +#endif /* _QUAGGA_VNC_VNC_EXPORT_TABLE_H_ */ diff --git a/bgpd/rfapi/vnc_import_bgp.c b/bgpd/rfapi/vnc_import_bgp.c new file mode 100644 index 0000000..e688638 --- /dev/null +++ b/bgpd/rfapi/vnc_import_bgp.c @@ -0,0 +1,2932 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: vnc_import_bgp.c + * Purpose: Import routes from BGP unicast directly (not via zebra) + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/vty.h" +#include "lib/log.h" +#include "lib/memory.h" +#include "lib/linklist.h" +#include "lib/plist.h" +#include "lib/routemap.h" +#include "lib/lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_mplsvpn.h" /* for RD_TYPE_IP */ + +#include "bgpd/rfapi/vnc_export_bgp.h" +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/rfapi_monitor.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/vnc_import_bgp.h" +#include "bgpd/rfapi/vnc_import_bgp_p.h" +#include "bgpd/rfapi/vnc_debug.h" + +#define ENABLE_VNC_RHNCK + +#define DEBUG_RHN_LIST 0 + +static struct rfapi_descriptor vncHDBgpDirect; /* dummy nve descriptor */ +static struct rfapi_descriptor vncHDResolveNve; /* dummy nve descriptor */ + +/* + * For routes from another AS: + * + * If MED is set, + * LOCAL_PREF = 255 - MIN(255, MED) + * else + * LOCAL_PREF = default_local_pref + * + * For routes from the same AS: + * + * LOCAL_PREF unchanged + */ +uint32_t calc_local_pref(struct attr *attr, struct peer *peer) +{ + uint32_t local_pref = 0; + + if (!attr) { + if (peer) { + return peer->bgp->default_local_pref; + } + return bgp_get_default()->default_local_pref; + } + + if (peer && (peer->as != peer->bgp->as)) { + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) { + if (attr->med > 255) { + local_pref = 0; + } else { + local_pref = 255 - attr->med; + } + } else { + local_pref = peer->bgp->default_local_pref; + } + } else { + if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + local_pref = attr->local_pref; + } else { + if (peer && peer->bgp) { + local_pref = peer->bgp->default_local_pref; + } + } + } + + return local_pref; +} + +static int is_host_prefix(const struct prefix *p) +{ + switch (p->family) { + case AF_INET: + return (p->prefixlen == IPV4_MAX_BITLEN); + case AF_INET6: + return (p->prefixlen == IPV6_MAX_BITLEN); + } + return 0; +} + +/*********************************************************************** + * RHN list + ***********************************************************************/ + +struct prefix_bag { + struct prefix hpfx; /* ce address = unicast nexthop */ + struct prefix upfx; /* unicast prefix */ + struct bgp_path_info *ubpi; /* unicast route */ +}; + +static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff}; + +int vnc_prefix_cmp(const void *pfx1, const void *pfx2) +{ + int offset; + int shift; + uint8_t mask; + + const struct prefix *p1 = pfx1; + const struct prefix *p2 = pfx2; + + if (p1->family < p2->family) + return -1; + if (p1->family > p2->family) + return 1; + + if (p1->prefixlen < p2->prefixlen) + return -1; + if (p1->prefixlen > p2->prefixlen) + return 1; + + offset = p1->prefixlen / 8; + shift = p1->prefixlen % 8; + if (shift == 0 && offset) { /* catch aligned case */ + offset--; + shift = 8; + } + + /* Set both prefix's head pointer. */ + const uint8_t *pp1 = (const uint8_t *)&p1->u.prefix; + const uint8_t *pp2 = (const uint8_t *)&p2->u.prefix; + + while (offset--) { + if (*pp1 < *pp2) + return -1; + if (*pp1 > *pp2) + return 1; + ++pp1; + ++pp2; + } + + mask = maskbit[shift]; + if ((*pp1 & mask) < (*pp2 & mask)) + return -1; + if ((*pp1 & mask) > (*pp2 & mask)) + return 1; + + return 0; +} + +static void prefix_bag_free(void *pb) +{ + XFREE(MTYPE_RFAPI_PREFIX_BAG, pb); +} + +#if DEBUG_RHN_LIST +static void print_rhn_list(const char *tag1, const char *tag2) +{ + struct bgp *bgp; + struct skiplist *sl; + struct skiplistnode *p; + struct prefix_bag *pb; + int count = 0; + + bgp = bgp_get_default(); + if (!bgp) + return; + + sl = bgp->frapi->resolve_nve_nexthop; + if (!sl) { + vnc_zlog_debug_verbose("%s: %s: RHN List is empty", + (tag1 ? tag1 : ""), (tag2 ? tag2 : "")); + return; + } + + vnc_zlog_debug_verbose("%s: %s: RHN list:", (tag1 ? tag1 : ""), + (tag2 ? tag2 : "")); + + /* XXX uses secret knowledge of skiplist structure */ + for (p = sl->header->forward[0]; p; p = p->forward[0]) { + pb = p->value; + + vnc_zlog_debug_verbose( + "RHN Entry %d (q=%p): kpfx=%pFX, upfx=%pFX, hpfx=%pFX, ubpi=%p", + ++count, p, p->key, &pb->upfx, &pb->hpfx, pb->ubpi); + } +} +#endif + +#ifdef ENABLE_VNC_RHNCK +static void vnc_rhnck(char *tag) +{ + struct bgp *bgp; + struct skiplist *sl; + struct skiplistnode *p; + + bgp = bgp_get_default(); + if (!bgp) + return; + sl = bgp->rfapi->resolve_nve_nexthop; + + if (!sl) + return; + + /* XXX uses secret knowledge of skiplist structure */ + for (p = sl->header->forward[0]; p; p = p->forward[0]) { + struct prefix_bag *pb; + struct prefix *pkey; + afi_t afi; + struct prefix pfx_orig_nexthop; + + memset(&pfx_orig_nexthop, 0, + sizeof(pfx_orig_nexthop)); /* keep valgrind happy */ + + pkey = p->key; + pb = p->value; + + afi = family2afi(pb->upfx.family); + + rfapiUnicastNexthop2Prefix(afi, pb->ubpi->attr, + &pfx_orig_nexthop); + + /* pb->hpfx, pb->ubpi nexthop, pkey should all reflect the same + * pfx */ + assert(!vnc_prefix_cmp(&pb->hpfx, pkey)); + if (vnc_prefix_cmp(&pb->hpfx, &pfx_orig_nexthop)) { + vnc_zlog_debug_verbose( + "%s: %s: FATAL: resolve_nve_nexthop list item bpi nexthop %pFX != nve pfx %pFX", + __func__, tag, &pfx_orig_nexthop, &pb->hpfx); + assert(0); + } + } + vnc_zlog_debug_verbose("%s: vnc_rhnck OK", tag); +} + +#define VNC_RHNCK(n) \ + do { \ + char buf[BUFSIZ]; \ + snprintf(buf, sizeof(buf), "%s: %s", __func__, #n); \ + vnc_rhnck(buf); \ + } while (0) + +#else + +#define VNC_RHNCK(n) + +#endif + +/*********************************************************************** + * Add/Delete Unicast Route + ***********************************************************************/ + +/* + * "Adding a Route" import process + */ + +/* + * extract and package information from the BGP unicast route. + * Return code 0 means OK, non-0 means drop. + * + * If return code is 0, caller MUST release ecom + */ +static int process_unicast_route(struct bgp *bgp, /* in */ + afi_t afi, /* in */ + const struct prefix *prefix, /* in */ + struct bgp_path_info *info, /* in */ + struct ecommunity **ecom, /* OUT */ + struct prefix *unicast_nexthop) /* OUT */ +{ + struct rfapi_cfg *hc = bgp->rfapi_cfg; + struct peer *peer = info->peer; + struct attr *attr = info->attr; + struct attr hattr; + struct route_map *rmap = NULL; + struct prefix pfx_orig_nexthop; + + memset(&pfx_orig_nexthop, 0, + sizeof(pfx_orig_nexthop)); /* keep valgrind happy */ + + /* + * prefix list check + */ + if (hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { + vnc_zlog_debug_verbose("%s: HC prefix list is set, checking", + __func__); + if (prefix_list_apply( + hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi], + prefix) + == PREFIX_DENY) { + vnc_zlog_debug_verbose( + "%s: prefix list returns DENY, blocking route", + __func__); + return -1; + } + vnc_zlog_debug_verbose( + "%s: prefix list returns PASS, allowing route", + __func__); + } + + /* apply routemap, if any, later */ + rmap = hc->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; + + /* + * Extract original nexthop, which we expect to be a NVE connected + * router + * Note that this is the nexthop before any possible application of + * policy + */ + /* + * Incoming prefix is unicast. If v6, it is in multiprotocol area, + * but if v4 it is in attr->nexthop + */ + rfapiUnicastNexthop2Prefix(afi, attr, &pfx_orig_nexthop); + + /* + * route map handling + * This code is here because it allocates an interned attr which + * must be freed before we return. It's easier to put it after + * all of the possible returns above. + */ + memset(&hattr, 0, sizeof(hattr)); + /* hattr becomes a ghost attr */ + hattr = *attr; + + if (rmap) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = peer; + info.attr = &hattr; + ret = route_map_apply(rmap, prefix, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + vnc_zlog_debug_verbose( + "%s: route map \"%s\" says DENY, returning", + __func__, rmap->name); + return -1; + } + } + + /* + * Get the (possibly altered by policy) unicast nexthop + * for later lookup in the Import Table by caller + */ + rfapiUnicastNexthop2Prefix(afi, &hattr, unicast_nexthop); + + if (bgp_attr_get_ecommunity(&hattr)) + *ecom = ecommunity_dup(bgp_attr_get_ecommunity(&hattr)); + else + *ecom = ecommunity_new(); + + /* + * Done with hattr, clean up + */ + bgp_attr_flush(&hattr); + + /* + * Add EC that carries original NH of iBGP route (2 bytes = magic + * value indicating it came from an VNC gateway; default 5226, but + * must be user configurable). Note that this is the nexthop before + * any application of policy. + */ + { + struct ecommunity_val vnc_gateway_magic; + uint16_t localadmin; + + /* Using route origin extended community type */ + memset(&vnc_gateway_magic, 0, sizeof(vnc_gateway_magic)); + vnc_gateway_magic.val[0] = 0x01; + vnc_gateway_magic.val[1] = 0x03; + + /* Only works for IPv4 nexthops */ + if (prefix->family == AF_INET) { + memcpy(vnc_gateway_magic.val + 2, + &unicast_nexthop->u.prefix4, 4); + } + localadmin = htons(hc->resolve_nve_roo_local_admin); + memcpy(vnc_gateway_magic.val + 6, (char *)&localadmin, 2); + + ecommunity_add_val(*ecom, &vnc_gateway_magic, false, false); + } + + return 0; +} + + +static void vnc_import_bgp_add_route_mode_resolve_nve_one_bi( + struct bgp *bgp, afi_t afi, struct bgp_path_info *bpi, /* VPN bpi */ + struct prefix_rd *prd, /* RD */ + const struct prefix *prefix, /* unicast route prefix */ + uint32_t *local_pref, /* NULL = no local_pref */ + uint32_t *med, /* NULL = no med */ + struct ecommunity *ecom) /* generated ecoms */ +{ + struct prefix un; + struct prefix nexthop; + struct rfapi_ip_addr nexthop_h; + uint32_t lifetime; + uint32_t *plifetime; + struct bgp_attr_encap_subtlv *encaptlvs; + uint32_t label; + + struct rfapi_un_option optary[3]; + struct rfapi_un_option *opt = NULL; + int cur_opt = 0; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (bpi->type != ZEBRA_ROUTE_BGP + && bpi->type != ZEBRA_ROUTE_BGP_DIRECT) { + + return; + } + if (bpi->sub_type != BGP_ROUTE_NORMAL + && bpi->sub_type != BGP_ROUTE_STATIC + && bpi->sub_type != BGP_ROUTE_RFP) { + + return; + } + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + return; + + vncHDResolveNve.peer = bpi->peer; + if (!rfapiGetVncTunnelUnAddr(bpi->attr, &un)) { + if (rfapiQprefix2Raddr(&un, &vncHDResolveNve.un_addr)) + return; + } else { + memset(&vncHDResolveNve.un_addr, 0, + sizeof(vncHDResolveNve.un_addr)); + } + + /* Use nexthop of VPN route as nexthop of constructed route */ + rfapiNexthop2Prefix(bpi->attr, &nexthop); + rfapiQprefix2Raddr(&nexthop, &nexthop_h); + + if (rfapiGetVncLifetime(bpi->attr, &lifetime)) { + plifetime = NULL; + } else { + plifetime = &lifetime; + } + + encaptlvs = bgp_attr_get_vnc_subtlvs(bpi->attr); + if (bpi->attr->encap_tunneltype != BGP_ENCAP_TYPE_RESERVED + && bpi->attr->encap_tunneltype != BGP_ENCAP_TYPE_MPLS) { + opt = &optary[cur_opt++]; + memset(opt, 0, sizeof(struct rfapi_un_option)); + opt->type = RFAPI_UN_OPTION_TYPE_TUNNELTYPE; + opt->v.tunnel.type = bpi->attr->encap_tunneltype; + /* TBD parse bpi->attr->extra->encap_subtlvs */ + } + + struct ecommunity *new_ecom = ecommunity_dup(ecom); + + if (bgp_attr_get_ecommunity(bpi->attr)) + ecommunity_merge(new_ecom, bgp_attr_get_ecommunity(bpi->attr)); + + if (BGP_PATH_INFO_NUM_LABELS(bpi)) + label = decode_label(&bpi->extra->labels->label[0]); + else + label = MPLS_INVALID_LABEL; + + add_vnc_route(&vncHDResolveNve, bgp, SAFI_MPLS_VPN, + prefix, /* unicast route prefix */ + prd, &nexthop_h, /* new nexthop */ + local_pref, plifetime, + (struct bgp_tea_options *)encaptlvs, /* RFP options */ + opt, NULL, new_ecom, med, /* NULL => don't set med */ + ((label != MPLS_INVALID_LABEL) ? &label + : NULL), /* NULL= default */ + ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, + RFAPI_AHR_RFPOPT_IS_VNCTLV); /* flags */ + + ecommunity_free(&new_ecom); +} + +static void vnc_import_bgp_add_route_mode_resolve_nve_one_rd( + struct prefix_rd *prd, /* RD */ + struct bgp_table *table_rd, /* per-rd VPN route table */ + afi_t afi, struct bgp *bgp, + const struct prefix *prefix, /* unicast prefix */ + struct ecommunity *ecom, /* generated ecoms */ + uint32_t *local_pref, /* NULL = no local_pref */ + uint32_t *med, /* NULL = no med */ + struct prefix *ubpi_nexthop) /* unicast nexthop */ +{ + struct bgp_dest *bd; + struct bgp_path_info *bpi; + + if (!table_rd) + return; + + vnc_zlog_debug_verbose("%s: ubpi_nexthop=%pFX", __func__, ubpi_nexthop); + + /* exact match */ + bd = bgp_node_lookup(table_rd, ubpi_nexthop); + if (!bd) { + vnc_zlog_debug_verbose( + "%s: no match in RD's table for ubpi_nexthop", + __func__); + return; + } + + /* Iterate over bgp_info items at this node */ + for (bpi = bgp_dest_get_bgp_path_info(bd); bpi; bpi = bpi->next) { + + vnc_import_bgp_add_route_mode_resolve_nve_one_bi( + bgp, afi, bpi, /* VPN bpi */ + prd, prefix, local_pref, med, ecom); + } + + bgp_dest_unlock_node(bd); +} + +static void vnc_import_bgp_add_route_mode_resolve_nve( + struct bgp *bgp, const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info) /* unicast info */ +{ + afi_t afi = family2afi(prefix->family); + + struct prefix pfx_unicast_nexthop = {0}; /* happy valgrind */ + + struct ecommunity *ecom = NULL; + uint32_t local_pref; + uint32_t *med = NULL; + + struct prefix_bag *pb; + struct bgp_dest *bdp; /* prd table node */ + + /*debugging */ + if (VNC_DEBUG(VERBOSE)) { + char str_nh[PREFIX_STRLEN]; + struct prefix nh; + + nh.prefixlen = 0; + rfapiUnicastNexthop2Prefix(afi, info->attr, &nh); + if (nh.prefixlen) { + prefix2str(&nh, str_nh, sizeof(str_nh)); + } else { + str_nh[0] = '?'; + str_nh[1] = 0; + } + + vnc_zlog_debug_verbose( + "%s(bgp=%p, unicast prefix=%pFX, unicast nh=%s)", + __func__, bgp, prefix, str_nh); + } + + if (info->type != ZEBRA_ROUTE_BGP) { + vnc_zlog_debug_verbose( + "%s: unicast type %d=\"%s\" is not %d=%s, skipping", + __func__, info->type, zebra_route_string(info->type), + ZEBRA_ROUTE_BGP, "ZEBRA_ROUTE_BGP"); + return; + } + + /* + * Preliminary checks + */ + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", + __func__); + return; + } + + if (!(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi); + return; + } + + + if (process_unicast_route(bgp, afi, prefix, info, &ecom, + &pfx_unicast_nexthop)) { + + vnc_zlog_debug_verbose( + "%s: process_unicast_route error, skipping", __func__); + return; + } + + local_pref = calc_local_pref(info->attr, info->peer); + if (info->attr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + med = &info->attr->med; + + /* + * At this point, we have allocated: + * + * ecom ecommunity ptr, union of unicast and ROO parts (no NVE part) + * + * And we have set: + * + * pfx_unicast_nexthop nexthop of uncast route + */ + + if (!bgp->rfapi->resolve_nve_nexthop) { + bgp->rfapi->resolve_nve_nexthop = + skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, + vnc_prefix_cmp, prefix_bag_free); + } + + pb = XCALLOC(MTYPE_RFAPI_PREFIX_BAG, sizeof(struct prefix_bag)); + pb->hpfx = pfx_unicast_nexthop; + pb->ubpi = info; + pb->upfx = *prefix; + + bgp_path_info_lock(info); /* skiplist refers to it */ + skiplist_insert(bgp->rfapi->resolve_nve_nexthop, &pb->hpfx, pb); + + /* + * Iterate over RDs in VPN RIB. For each RD, look up unicast nexthop + * (exact match, /32). If an exact match is found, call add_vnc_route. + */ + + for (bdp = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); bdp; + bdp = bgp_route_next(bdp)) { + + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(bdp); + + if (!table) + continue; + + vnc_import_bgp_add_route_mode_resolve_nve_one_rd( + (struct prefix_rd *)bgp_dest_get_prefix(bdp), table, + afi, bgp, prefix, ecom, &local_pref, med, + &pfx_unicast_nexthop); + } + + + if (ecom) + ecommunity_free(&ecom); + + vnc_zlog_debug_verbose("%s: done", __func__); +} + + +static void vnc_import_bgp_add_route_mode_plain(struct bgp *bgp, + const struct prefix *prefix, + struct bgp_path_info *info) +{ + afi_t afi = family2afi(prefix->family); + struct peer *peer = info->peer; + struct attr *attr = info->attr; + struct attr hattr; + struct rfapi_cfg *hc = bgp->rfapi_cfg; + struct attr *iattr = NULL; + + struct rfapi_ip_addr vnaddr; + struct prefix vn_pfx_space; + struct prefix *vn_pfx = NULL; + int ahr_flags = 0; + struct ecommunity *ecom = NULL; + struct prefix_rd prd; + struct route_map *rmap = NULL; + uint32_t local_pref; + uint32_t *med = NULL; + + vnc_zlog_debug_verbose("%s(prefix=%pFX) entry", __func__, prefix); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", + __func__); + return; + } + + if (!hc) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi); + return; + } + + /* + * mode "plain" specific code + */ + { + vnc_zlog_debug_verbose("%s: NOT using redist RFG", __func__); + + /* + * prefix list check + */ + if (hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { + vnc_zlog_debug_verbose( + "%s: HC prefix list is set, checking", + __func__); + if (prefix_list_apply( + hc->plist_redist[ZEBRA_ROUTE_BGP_DIRECT] + [afi], + prefix) + == PREFIX_DENY) { + vnc_zlog_debug_verbose( + "%s: prefix list returns DENY, blocking route", + __func__); + return; + } + vnc_zlog_debug_verbose( + "%s: prefix list returns PASS, allowing route", + __func__); + } + + /* apply routemap, if any, later */ + rmap = hc->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; + + /* + * Incoming prefix is unicast. If v6, it is in multiprotocol + * area, + * but if v4 it is in attr->nexthop + */ + rfapiUnicastNexthop2Prefix(afi, attr, &vn_pfx_space); + vn_pfx = &vn_pfx_space; + + /* UN address */ + ahr_flags |= RFAPI_AHR_NO_TUNNEL_SUBTLV; + } + + if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) + vnc_zlog_debug_any("%s vn_pfx=%pFX", __func__, vn_pfx); + + /* + * Compute VN address + */ + if (rfapiQprefix2Raddr(vn_pfx, &vnaddr)) { + vnc_zlog_debug_verbose("%s: redist VN invalid, skipping", + __func__); + return; + } + + /* + * route map handling + * This code is here because it allocates an interned attr which + * must be freed before we return. It's easier to put it after + * all of the possible returns above. + */ + memset(&hattr, 0, sizeof(hattr)); + /* hattr becomes a ghost attr */ + hattr = *attr; + + if (rmap) { + struct bgp_path_info info; + route_map_result_t ret; + + memset(&info, 0, sizeof(info)); + info.peer = peer; + info.attr = &hattr; + ret = route_map_apply(rmap, prefix, &info); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + vnc_zlog_debug_verbose( + "%s: route map \"%s\" says DENY, returning", + __func__, rmap->name); + return; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + /* Now iattr is an allocated interned attr */ + + /* + * Mode "plain" specific code + * + * Sets RD in dummy HD + * Allocates ecom + */ + { + if (vnaddr.addr_family != AF_INET) { + vnc_zlog_debug_verbose( + "%s: can't auto-assign RD, VN AF (%d) is not IPv4, skipping", + __func__, vnaddr.addr_family); + if (iattr) { + bgp_attr_unintern(&iattr); + } + return; + } + memset(&prd, 0, sizeof(prd)); + rfapi_set_autord_from_vn(&prd, &vnaddr); + + if (iattr && bgp_attr_get_ecommunity(iattr)) + ecom = ecommunity_dup(bgp_attr_get_ecommunity(iattr)); + } + + local_pref = calc_local_pref(iattr, peer); + + if (iattr && (iattr->flag & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC))) { + med = &iattr->med; + } + + if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { + char buf[PREFIX_STRLEN]; + + rfapiRfapiIpAddr2Str(&vnaddr, buf, sizeof(buf)); + vnc_zlog_debug_any("%s: setting vnaddr to %s", __func__, buf); + } + + vncHDBgpDirect.peer = peer; + add_vnc_route(&vncHDBgpDirect, bgp, SAFI_MPLS_VPN, prefix, &prd, + &vnaddr, &local_pref, &(bgp->rfapi_cfg->redist_lifetime), + NULL, /* RFP options */ + NULL, NULL, ecom, med, /* med */ + NULL, /* label: default */ + ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, + ahr_flags); + vncHDBgpDirect.peer = NULL; + + if (ecom) + ecommunity_free(&ecom); + if (iattr) + bgp_attr_unintern(&iattr); +} + +static void vnc_import_bgp_add_route_mode_nvegroup( + struct bgp *bgp, const struct prefix *prefix, + struct bgp_path_info *info, struct rfapi_nve_group_cfg *rfg) +{ + afi_t afi = family2afi(prefix->family); + struct peer *peer = info->peer; + struct attr *attr = info->attr; + struct attr hattr; + struct attr *iattr = NULL; + + struct rfapi_ip_addr vnaddr; + struct prefix *vn_pfx = NULL; + int ahr_flags = 0; + struct ecommunity *ecom = NULL; + struct prefix_rd prd; + struct route_map *rmap = NULL; + uint32_t local_pref; + + vnc_zlog_debug_verbose("%s(prefix=%pFX) entry", __func__, prefix); + + assert(rfg); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", + __func__); + return; + } + + if (!(bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi); + return; + } + + + /* + * RFG-specific code + */ + { + + struct rfapi_ip_prefix pfx_un; + + vnc_zlog_debug_verbose("%s: using redist RFG", __func__); + + /* + * RFG prefix list check + */ + if (rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT][afi]) { + vnc_zlog_debug_verbose( + "%s: RFG prefix list is set, checking", + __func__); + if (prefix_list_apply( + rfg->plist_redist[ZEBRA_ROUTE_BGP_DIRECT] + [afi], + prefix) + == PREFIX_DENY) { + vnc_zlog_debug_verbose( + "%s: prefix list returns DENY, blocking route", + __func__); + return; + } + vnc_zlog_debug_verbose( + "%s: prefix list returns PASS, allowing route", + __func__); + } + + /* apply routemap, if any, later */ + rmap = rfg->routemap_redist[ZEBRA_ROUTE_BGP_DIRECT]; + + /* + * export nve group's VN addr prefix must be a /32 which + * will yield the VN addr to use + */ + vn_pfx = &rfg->vn_prefix; + + /* + * UN Address + */ + if (!is_host_prefix(&rfg->un_prefix)) { + /* NB prefixlen==0 means it has not been configured */ + vnc_zlog_debug_verbose( + "%s: redist RFG UN pfx not host pfx (plen=%d), skipping", + __func__, rfg->un_prefix.prefixlen); + return; + } + + rfapiQprefix2Rprefix(&rfg->un_prefix, &pfx_un); + + vncHDBgpDirect.un_addr = pfx_un.prefix; + } + + if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) + vnc_zlog_debug_any("%s vn_pfx=%pFX", __func__, vn_pfx); + + /* + * Compute VN address + */ + if (rfapiQprefix2Raddr(vn_pfx, &vnaddr)) { + vnc_zlog_debug_verbose("%s: redist VN invalid, skipping", + __func__); + return; + } + + /* + * route map handling + * This code is here because it allocates an interned attr which + * must be freed before we return. It's easier to put it after + * all of the possible returns above. + */ + memset(&hattr, 0, sizeof(hattr)); + /* hattr becomes a ghost attr */ + hattr = *attr; + + if (rmap) { + struct bgp_path_info path; + route_map_result_t ret; + + memset(&path, 0, sizeof(path)); + path.peer = peer; + path.attr = &hattr; + ret = route_map_apply(rmap, prefix, &path); + if (ret == RMAP_DENYMATCH) { + bgp_attr_flush(&hattr); + vnc_zlog_debug_verbose( + "%s: route map \"%s\" says DENY, returning", + __func__, rmap->name); + return; + } + } + + iattr = bgp_attr_intern(&hattr); + bgp_attr_flush(&hattr); + + /* Now iattr is an allocated interned attr */ + + /* + * RFG-specific code + * + * Sets RD in dummy HD + * Allocates ecom + */ + { + + memset(&prd, 0, sizeof(prd)); + prd = rfg->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + if (rfg->rd.family == AF_UNIX) { + rfapi_set_autord_from_vn(&prd, &vnaddr); + } + + if (rfg->rt_export_list) + ecom = ecommunity_dup( + bgp->rfapi_cfg->rfg_redist->rt_export_list); + else + ecom = ecommunity_new(); + + if (iattr && bgp_attr_get_ecommunity(iattr)) + ecom = ecommunity_merge(ecom, + bgp_attr_get_ecommunity(iattr)); + } + + local_pref = calc_local_pref(iattr, peer); + + if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) { + char buf[BUFSIZ]; + + buf[0] = 0; + rfapiRfapiIpAddr2Str(&vnaddr, buf, BUFSIZ); + buf[BUFSIZ - 1] = 0; + vnc_zlog_debug_any("%s: setting vnaddr to %s", __func__, buf); + } + + vncHDBgpDirect.peer = peer; + add_vnc_route(&vncHDBgpDirect, bgp, SAFI_MPLS_VPN, prefix, &prd, + &vnaddr, &local_pref, &(bgp->rfapi_cfg->redist_lifetime), + NULL, /* RFP options */ + NULL, NULL, ecom, NULL, /* med */ + NULL, /* label: default */ + ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, + ahr_flags); + vncHDBgpDirect.peer = NULL; + + if (ecom) + ecommunity_free(&ecom); + if (iattr) + bgp_attr_unintern(&iattr); +} + +static void vnc_import_bgp_del_route_mode_plain(struct bgp *bgp, + const struct prefix *prefix, + struct bgp_path_info *info) +{ + struct prefix_rd prd; + afi_t afi = family2afi(prefix->family); + struct prefix *vn_pfx = NULL; + struct rfapi_ip_addr vnaddr; + struct prefix vn_pfx_space; + + + assert(afi); + + /* + * Compute VN address + */ + + if (info) { + rfapiUnicastNexthop2Prefix(afi, info->attr, &vn_pfx_space); + } else { + vnc_zlog_debug_verbose("%s: no attr, can't delete route", + __func__); + return; + } + vn_pfx = &vn_pfx_space; + + vnaddr.addr_family = vn_pfx->family; + switch (vn_pfx->family) { + case AF_INET: + if (vn_pfx->prefixlen != IPV4_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist VN plen (%d) != 32, skipping", + __func__, vn_pfx->prefixlen); + return; + } + vnaddr.addr.v4 = vn_pfx->u.prefix4; + break; + + case AF_INET6: + if (vn_pfx->prefixlen != IPV6_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist VN plen (%d) != 128, skipping", + __func__, vn_pfx->prefixlen); + return; + } + vnaddr.addr.v6 = vn_pfx->u.prefix6; + break; + + default: + vnc_zlog_debug_verbose( + "%s: no redist RFG VN host pfx configured, skipping", + __func__); + return; + } + + + memset(&prd, 0, sizeof(prd)); + if (rfapi_set_autord_from_vn(&prd, &vnaddr)) { + vnc_zlog_debug_verbose("%s: can't auto-assign RD, skipping", + __func__); + return; + } + + vncHDBgpDirect.peer = info->peer; + vnc_zlog_debug_verbose("%s: setting peer to %p", __func__, + vncHDBgpDirect.peer); + del_vnc_route(&vncHDBgpDirect, info->peer, bgp, SAFI_MPLS_VPN, prefix, + &prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, + NULL, 1); + + vncHDBgpDirect.peer = NULL; +} + +static void vnc_import_bgp_del_route_mode_nvegroup(struct bgp *bgp, + const struct prefix *prefix, + struct bgp_path_info *info) +{ + struct prefix_rd prd; + afi_t afi = family2afi(prefix->family); + struct rfapi_nve_group_cfg *rfg = NULL; + struct prefix *vn_pfx = NULL; + struct rfapi_ip_addr vnaddr; + + + assert(afi); + + rfg = bgp->rfapi_cfg->rfg_redist; + assert(rfg); + + /* + * Compute VN address + */ + + /* + * export nve group's VN addr prefix must be a /32 which + * will yield the VN addr to use + */ + vn_pfx = &rfg->vn_prefix; + + + vnaddr.addr_family = vn_pfx->family; + switch (vn_pfx->family) { + case AF_INET: + if (vn_pfx->prefixlen != IPV4_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist VN plen (%d) != 32, skipping", + __func__, vn_pfx->prefixlen); + return; + } + vnaddr.addr.v4 = vn_pfx->u.prefix4; + break; + + case AF_INET6: + if (vn_pfx->prefixlen != IPV6_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist VN plen (%d) != 128, skipping", + __func__, vn_pfx->prefixlen); + return; + } + vnaddr.addr.v6 = vn_pfx->u.prefix6; + break; + + default: + vnc_zlog_debug_verbose( + "%s: no redist RFG VN host pfx configured, skipping", + __func__); + return; + } + + memset(&prd, 0, sizeof(prd)); + prd = rfg->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + if (rfg->rd.family == AF_UNIX) { + /* means "auto" with VN addr */ + if (rfapi_set_autord_from_vn(&prd, &vnaddr)) { + vnc_zlog_debug_verbose( + "%s: can't auto-assign RD, skipping", __func__); + return; + } + } + + + vncHDBgpDirect.peer = info->peer; + vnc_zlog_debug_verbose("%s: setting peer to %p", __func__, + vncHDBgpDirect.peer); + del_vnc_route(&vncHDBgpDirect, info->peer, bgp, SAFI_MPLS_VPN, prefix, + &prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, + NULL, 1); + + vncHDBgpDirect.peer = NULL; +} + +static void vnc_import_bgp_del_route_mode_resolve_nve_one_bi( + struct bgp *bgp, afi_t afi, struct bgp_path_info *bpi, /* VPN bpi */ + struct prefix_rd *prd, /* RD */ + const struct prefix *prefix) /* unicast route prefix */ +{ + struct prefix un; + + if (bpi->type != ZEBRA_ROUTE_BGP + && bpi->type != ZEBRA_ROUTE_BGP_DIRECT) { + + return; + } + if (bpi->sub_type != BGP_ROUTE_NORMAL + && bpi->sub_type != BGP_ROUTE_STATIC + && bpi->sub_type != BGP_ROUTE_RFP) { + + return; + } + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + return; + + vncHDResolveNve.peer = bpi->peer; + if (!rfapiGetVncTunnelUnAddr(bpi->attr, &un)) { + if (rfapiQprefix2Raddr(&un, &vncHDResolveNve.un_addr)) + return; + } else { + memset(&vncHDResolveNve.un_addr, 0, + sizeof(vncHDResolveNve.un_addr)); + } + + del_vnc_route(&vncHDResolveNve, vncHDResolveNve.peer, bgp, + SAFI_MPLS_VPN, prefix, /* unicast route prefix */ + prd, ZEBRA_ROUTE_BGP_DIRECT, BGP_ROUTE_REDISTRIBUTE, NULL, + 0); /* flags */ +} + +static void vnc_import_bgp_del_route_mode_resolve_nve_one_rd( + struct prefix_rd *prd, + struct bgp_table *table_rd, /* per-rd VPN route table */ + afi_t afi, struct bgp *bgp, + const struct prefix *prefix, /* unicast prefix */ + const struct prefix *ubpi_nexthop) /* unicast bpi's nexthop */ +{ + struct bgp_dest *bd; + struct bgp_path_info *bpi; + + if (!table_rd) + return; + + vnc_zlog_debug_verbose("%s: ubpi_nexthop=%pFX", __func__, ubpi_nexthop); + + + /* exact match */ + bd = bgp_node_lookup(table_rd, ubpi_nexthop); + if (!bd) { + vnc_zlog_debug_verbose( + "%s: no match in RD's table for ubpi_nexthop", + __func__); + return; + } + + /* Iterate over bgp_info items at this node */ + for (bpi = bgp_dest_get_bgp_path_info(bd); bpi; bpi = bpi->next) { + + vnc_import_bgp_del_route_mode_resolve_nve_one_bi( + bgp, afi, bpi, /* VPN bpi */ + prd, /* VPN RD */ + prefix); /* unicast route prefix */ + } + + bgp_dest_unlock_node(bd); +} + +static void +vnc_import_bgp_del_route_mode_resolve_nve(struct bgp *bgp, afi_t afi, + const struct prefix *prefix, + struct bgp_path_info *info) +{ + struct ecommunity *ecom = NULL; + struct prefix pfx_unicast_nexthop = {0}; /* happy valgrind */ + + // struct listnode *hnode; + // struct rfapi_descriptor *rfd; + struct prefix_bag *pb; + void *cursor; + struct skiplist *sl = bgp->rfapi->resolve_nve_nexthop; + int rc; + struct bgp_dest *bdp; /* prd table node */ + + if (!sl) { + vnc_zlog_debug_verbose("%s: no RHN entries, skipping", + __func__); + return; + } + + if (info->type != ZEBRA_ROUTE_BGP) { + vnc_zlog_debug_verbose( + "%s: unicast type %d=\"%s\" is not %d=%s, skipping", + __func__, info->type, zebra_route_string(info->type), + ZEBRA_ROUTE_BGP, "ZEBRA_ROUTE_BGP"); + return; + } + + if (process_unicast_route(bgp, afi, prefix, info, &ecom, + &pfx_unicast_nexthop)) { + + vnc_zlog_debug_verbose( + "%s: process_unicast_route error, skipping", __func__); + return; + } + + rc = skiplist_first_value(sl, &pfx_unicast_nexthop, (void *)&pb, + &cursor); + while (!rc) { + if (pb->ubpi == info) { + skiplist_delete(sl, &pfx_unicast_nexthop, pb); + bgp_path_info_unlock(info); + break; + } + rc = skiplist_next_value(sl, &pfx_unicast_nexthop, (void *)&pb, + &cursor); + } + + /* + * Iterate over RDs in VPN RIB. For each RD, look up unicast nexthop + * (exact match, /32). If an exact match is found, call add_vnc_route. + */ + + for (bdp = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); bdp; + bdp = bgp_route_next(bdp)) { + + struct bgp_table *table; + + table = bgp_dest_get_bgp_table_info(bdp); + + if (!table) + continue; + + vnc_import_bgp_del_route_mode_resolve_nve_one_rd( + (struct prefix_rd *)bgp_dest_get_prefix(bdp), table, + afi, bgp, prefix, &pfx_unicast_nexthop); + } + + if (ecom) + ecommunity_free(&ecom); +} + + +/*********************************************************************** + * Add/Delete CE->NVE routes + ***********************************************************************/ + +/* + * Should be called whan a bpi is added to VPN RIB. This function + * will check if it is a host route and return immediately if not. + */ +void vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + struct bgp *bgp, struct prefix_rd *prd, /* RD */ + struct bgp_table *table_rd, /* per-rd VPN route table */ + const struct prefix *prefix, /* VPN prefix */ + struct bgp_path_info *bpi) /* new VPN host route */ +{ + afi_t afi = family2afi(prefix->family); + struct skiplist *sl = NULL; + int rc; + struct prefix_bag *pb; + void *cursor; + struct rfapi_cfg *hc = NULL; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (afi != AFI_IP && afi != AFI_IP6) { + vnc_zlog_debug_verbose("%s: bad afi %d, skipping", __func__, + afi); + return; + } + + if (!(hc = bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi); + return; + } + + if (hc->redist_mode != VNC_REDIST_MODE_RESOLVE_NVE) { + vnc_zlog_debug_verbose("%s: not in resolve-nve mode, skipping", + __func__); + return; + } + + if (bgp->rfapi) + sl = bgp->rfapi->resolve_nve_nexthop; + + if (!sl) { + vnc_zlog_debug_verbose( + "%s: no resolve_nve_nexthop skiplist, skipping", + __func__); + return; + } + + if (!is_host_prefix(prefix)) { + vnc_zlog_debug_verbose("%s: not host prefix, skipping", + __func__); + return; + } + + rc = skiplist_first_value(sl, prefix, (void *)&pb, &cursor); + while (!rc) { + struct ecommunity *ecom; + struct prefix pfx_unicast_nexthop; + uint32_t *med = NULL; + uint32_t local_pref; + + memset(&pfx_unicast_nexthop, 0, + sizeof(pfx_unicast_nexthop)); /* keep valgrind happy */ + + if (VNC_DEBUG(IMPORT_BGP_ADD_ROUTE)) + vnc_zlog_debug_any( + "%s: examining RHN Entry (q=%p): upfx=%pFX, hpfx=%pFX, ubpi=%p", + __func__, cursor, &pb->upfx, &pb->hpfx, + pb->ubpi); + + if (process_unicast_route(bgp, afi, &pb->upfx, pb->ubpi, &ecom, + &pfx_unicast_nexthop)) { + + vnc_zlog_debug_verbose( + "%s: process_unicast_route error, skipping", + __func__); + continue; + } + local_pref = calc_local_pref(pb->ubpi->attr, pb->ubpi->peer); + + if (pb->ubpi->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_MULTI_EXIT_DISC)) + med = &pb->ubpi->attr->med; + + /* + * Sanity check + */ + if (vnc_prefix_cmp(&pfx_unicast_nexthop, prefix)) { + vnc_zlog_debug_verbose( + "%s: FATAL: resolve_nve_nexthop list item bpi nexthop %pFX != nve pfx %pFX", + __func__, &pfx_unicast_nexthop, prefix); + assert(0); + } + + vnc_import_bgp_add_route_mode_resolve_nve_one_bi( + bgp, afi, bpi, /* VPN bpi */ + prd, &pb->upfx, /* unicast prefix */ + &local_pref, med, ecom); + + if (ecom) + ecommunity_free(&ecom); + +#if DEBUG_RHN_LIST + /* debug */ + { + vnc_zlog_debug_verbose( + "%s: advancing past RHN Entry (q=%p): with prefix %pFX", + __func__, cursor, prefix); + print_rhn_list(__func__, NULL); /* debug */ + } +#endif + rc = skiplist_next_value(sl, prefix, (void *)&pb, &cursor); + } + vnc_zlog_debug_verbose("%s: done", __func__); +} + + +void vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + struct bgp *bgp, struct prefix_rd *prd, /* RD */ + struct bgp_table *table_rd, /* per-rd VPN route table */ + const struct prefix *prefix, /* VPN prefix */ + struct bgp_path_info *bpi) /* old VPN host route */ +{ + afi_t afi = family2afi(prefix->family); + struct skiplist *sl = NULL; + struct prefix_bag *pb; + void *cursor; + struct rfapi_cfg *hc = NULL; + int rc; + + vnc_zlog_debug_verbose("%s(bgp=%p, nve prefix=%pFX)", __func__, bgp, + prefix); + + if (afi != AFI_IP && afi != AFI_IP6) + return; + + if (!(hc = bgp->rfapi_cfg)) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi); + return; + } + + if (hc->redist_mode != VNC_REDIST_MODE_RESOLVE_NVE) { + vnc_zlog_debug_verbose("%s: not in resolve-nve mode, skipping", + __func__); + return; + } + + if (bgp->rfapi) + sl = bgp->rfapi->resolve_nve_nexthop; + + if (!sl) { + vnc_zlog_debug_verbose("%s: no RHN entries, skipping", + __func__); + return; + } + + if (!is_host_prefix(prefix)) { + vnc_zlog_debug_verbose("%s: not host route, skip", __func__); + return; + } + + /* + * Find all entries with key == CE in the RHN list + */ + rc = skiplist_first_value(sl, prefix, (void *)&pb, &cursor); + while (!rc) { + + struct ecommunity *ecom; + struct prefix pfx_unicast_nexthop; + + memset(&pfx_unicast_nexthop, 0, + sizeof(pfx_unicast_nexthop)); /* keep valgrind happy */ + + if (process_unicast_route(bgp, afi, &pb->upfx, pb->ubpi, &ecom, + &pfx_unicast_nexthop)) { + + vnc_zlog_debug_verbose( + "%s: process_unicast_route error, skipping", + __func__); + continue; + } + + /* + * Sanity check + */ + if (vnc_prefix_cmp(&pfx_unicast_nexthop, prefix)) { + vnc_zlog_debug_verbose( + "%s: FATAL: resolve_nve_nexthop list item bpi nexthop %pFX != nve pfx %pFX", + __func__, &pfx_unicast_nexthop, prefix); + assert(0); + } + + vnc_import_bgp_del_route_mode_resolve_nve_one_bi( + bgp, afi, bpi, prd, &pb->upfx); + + if (ecom) + ecommunity_free(&ecom); + + rc = skiplist_next_value(sl, prefix, (void *)&pb, &cursor); + } +} + + +/*********************************************************************** + * Exterior Routes + ***********************************************************************/ + +#define DEBUG_IS_USABLE_INTERIOR 1 + +static int is_usable_interior_route(struct bgp_path_info *bpi_interior) +{ + if (!VALID_INTERIOR_TYPE(bpi_interior->type)) { +#if DEBUG_IS_USABLE_INTERIOR + vnc_zlog_debug_verbose( + "%s: NO: type %d is not valid interior type", __func__, + bpi_interior->type); +#endif + return 0; + } + if (!CHECK_FLAG(bpi_interior->flags, BGP_PATH_VALID)) { +#if DEBUG_IS_USABLE_INTERIOR + vnc_zlog_debug_verbose("%s: NO: BGP_PATH_VALID not set", + __func__); +#endif + return 0; + } + return 1; +} + +/* + * There should be only one of these per prefix at a time. + * This should be called as a result of selection operation + * + * NB should be called espacially for bgp instances that are named, + * because the exterior routes will always come from one of those. + * We filter here on the instance name to make sure we get only the + * right routes. + */ +static void vnc_import_bgp_exterior_add_route_it( + struct bgp *bgp, /* exterior instance, we hope */ + const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info, /* unicast info */ + struct rfapi_import_table *it_only) /* NULL, or limit to this IT */ +{ + struct rfapi *h; + struct rfapi_cfg *hc; + struct prefix pfx_orig_nexthop; + struct rfapi_import_table *it; + struct bgp *bgp_default = bgp_get_default(); + afi_t afi = family2afi(prefix->family); + + if (!bgp_default) + return; + + h = bgp_default->rfapi; + hc = bgp_default->rfapi_cfg; + + vnc_zlog_debug_verbose("%s: entry with it=%p", __func__, it_only); + + if (!h || !hc) { + vnc_zlog_debug_verbose( + "%s: rfapi or rfapi_cfg not instantiated, skipping", + __func__); + return; + } + if (!hc->redist_bgp_exterior_view) { + vnc_zlog_debug_verbose("%s: exterior view not set, skipping", + __func__); + return; + } + if (bgp != hc->redist_bgp_exterior_view) { + vnc_zlog_debug_verbose( + "%s: bgp %p != hc->redist_bgp_exterior_view %p, skipping", + __func__, bgp, hc->redist_bgp_exterior_view); + return; + } + + if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: redist of exterior routes not enabled, skipping", + __func__); + return; + } + + /* + * Extract nexthop from exterior route + * + * Incoming prefix is unicast. If v6, it is in multiprotocol area, + * but if v4 it is in attr->nexthop + */ + rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_orig_nexthop); + + for (it = h->imports; it; it = it->next) { + struct agg_table *table; + struct agg_node *rn; + struct agg_node *par; + struct bgp_path_info *bpi_interior; + int have_usable_route; + + vnc_zlog_debug_verbose("%s: doing it %p", __func__, it); + + if (it_only && (it_only != it)) { + vnc_zlog_debug_verbose("%s: doesn't match it_only %p", + __func__, it_only); + continue; + } + + table = it->imported_vpn[afi]; + + for (rn = agg_node_match(table, &pfx_orig_nexthop), + have_usable_route = 0; + (!have_usable_route) && rn;) { + + vnc_zlog_debug_verbose("%s: it %p trying rn %p", + __func__, it, rn); + + for (bpi_interior = rn->info; bpi_interior; + bpi_interior = bpi_interior->next) { + struct prefix_rd *prd; + struct attr new_attr; + uint32_t label; + + if (!is_usable_interior_route(bpi_interior)) + continue; + + vnc_zlog_debug_verbose( + "%s: usable: bpi_interior %p", __func__, + bpi_interior); + + /* + * have a legitimate route to exterior's nexthop + * via NVE. + * + * Import unicast route to the import table + */ + have_usable_route = 1; + + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc + .import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels + ->label[0]); + else + label = MPLS_INVALID_LABEL; + + /* use local_pref from unicast route */ + memset(&new_attr, 0, sizeof(new_attr)); + new_attr = *bpi_interior->attr; + if (info->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF)) { + new_attr.local_pref = + info->attr->local_pref; + new_attr.flag |= ATTR_FLAG_BIT( + BGP_ATTR_LOCAL_PREF); + } + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, + bpi_interior->peer, NULL, /* rfd */ + prefix, NULL, afi, prd, &new_attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + + if (have_usable_route) { + /* + * Make monitor + * + * TBD factor this out into its own function + */ + struct prefix *pfx_mon = prefix_new(); + if (!RFAPI_MONITOR_EXTERIOR(rn)->source) { + RFAPI_MONITOR_EXTERIOR(rn)->source = + skiplist_new( + 0, NULL, + prefix_free_lists); + agg_lock_node(rn); /* for skiplist */ + } + agg_lock_node(rn); /* for skiplist entry */ + prefix_copy(pfx_mon, prefix); + if (!skiplist_insert( + RFAPI_MONITOR_EXTERIOR(rn)->source, + info, pfx_mon)) { + + bgp_path_info_lock(info); + } + } + par = agg_node_parent(rn); + if (par) + agg_lock_node(par); + agg_unlock_node(rn); + rn = par; + } + if (rn) + agg_unlock_node(rn); + + if (!have_usable_route) { + struct prefix *pfx_mon = prefix_new(); + prefix_copy(pfx_mon, prefix); + if (!skiplist_insert(it->monitor_exterior_orphans, info, + pfx_mon)) { + + bgp_path_info_lock(info); + } + } + } +} + +void vnc_import_bgp_exterior_add_route( + struct bgp *bgp, /* exterior instance, we hope */ + const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info) /* unicast info */ +{ + vnc_import_bgp_exterior_add_route_it(bgp, prefix, info, NULL); +} + +/* + * There should be only one of these per prefix at a time. + * This should probably be called as a result of selection operation. + * + * NB should be called espacially for bgp instances that are named, + * because the exterior routes will always come from one of those. + * We filter here on the instance name to make sure we get only the + * right routes. + */ +void vnc_import_bgp_exterior_del_route( + struct bgp *bgp, const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info) /* unicast info */ +{ + struct rfapi *h; + struct rfapi_cfg *hc; + struct rfapi_import_table *it; + struct prefix pfx_orig_nexthop; + afi_t afi = family2afi(prefix->family); + struct bgp *bgp_default = bgp_get_default(); + + if (!bgp_default) + return; + + memset(&pfx_orig_nexthop, 0, + sizeof(pfx_orig_nexthop)); /* keep valgrind happy */ + + h = bgp_default->rfapi; + hc = bgp_default->rfapi_cfg; + + if (!h || !hc) { + vnc_zlog_debug_verbose( + "%s: rfapi or rfapi_cfg not instantiated, skipping", + __func__); + return; + } + if (!hc->redist_bgp_exterior_view) { + vnc_zlog_debug_verbose("%s: exterior view not set, skipping", + __func__); + return; + } + if (bgp != hc->redist_bgp_exterior_view) { + vnc_zlog_debug_verbose( + "%s: bgp %p != hc->redist_bgp_exterior_view %p, skipping", + __func__, bgp, hc->redist_bgp_exterior_view); + return; + } + if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: redist of exterior routes no enabled, skipping", + __func__); + return; + } + + /* + * Extract nexthop from exterior route + * + * Incoming prefix is unicast. If v6, it is in multiprotocol area, + * but if v4 it is in attr->nexthop + */ + rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_orig_nexthop); + + for (it = h->imports; it; it = it->next) { + struct agg_table *table; + struct agg_node *rn; + struct agg_node *par; + struct bgp_path_info *bpi_interior; + int have_usable_route; + + table = it->imported_vpn[afi]; + + for (rn = agg_node_match(table, &pfx_orig_nexthop), + have_usable_route = 0; + (!have_usable_route) && rn;) { + + for (bpi_interior = rn->info; bpi_interior; + bpi_interior = bpi_interior->next) { + struct prefix_rd *prd; + uint32_t label; + + if (!is_usable_interior_route(bpi_interior)) + continue; + + /* + * have a legitimate route to exterior's nexthop + * via NVE. + * + * Import unicast route to the import table + */ + have_usable_route = 1; + + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc + .import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels + ->label[0]); + else + label = MPLS_INVALID_LABEL; + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_KILL, bpi_interior->peer, + NULL, /* rfd */ + prefix, NULL, afi, prd, + bpi_interior->attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + + /* + * Delete monitor + * + * TBD factor this out into its own function + */ + { + if (RFAPI_MONITOR_EXTERIOR(rn) + ->source) { + if (!skiplist_delete( + RFAPI_MONITOR_EXTERIOR( + rn) + ->source, + info, NULL)) { + + bgp_path_info_unlock( + info); + agg_unlock_node( + rn); /* sl entry + */ + } + if (skiplist_empty( + RFAPI_MONITOR_EXTERIOR( + rn) + ->source)) { + skiplist_free( + RFAPI_MONITOR_EXTERIOR( + rn) + ->source); + RFAPI_MONITOR_EXTERIOR( + rn) + ->source = NULL; + agg_unlock_node( + rn); /* skiplist + itself + */ + } + } + } + } + par = agg_node_parent(rn); + if (par) + agg_lock_node(par); + agg_unlock_node(rn); + rn = par; + } + if (rn) + agg_unlock_node(rn); + + if (!have_usable_route) { + if (!skiplist_delete(it->monitor_exterior_orphans, info, + NULL)) { + + bgp_path_info_unlock(info); + } + } + } +} + +/* + * This function should be called after a new interior VPN route + * has been added to an import_table. + * + * NB should also be called whenever an existing vpn interior route + * becomes valid (e.g., valid_interior_count is inremented) + */ +void vnc_import_bgp_exterior_add_route_interior( + struct bgp *bgp, struct rfapi_import_table *it, + struct agg_node *rn_interior, /* VPN IT node */ + struct bgp_path_info *bpi_interior) /* VPN IT route */ +{ + const struct prefix *p = agg_node_get_prefix(rn_interior); + afi_t afi = family2afi(p->family); + struct agg_node *par; + struct bgp_path_info *bpi_exterior; + struct prefix *pfx_exterior; /* exterior pfx */ + void *cursor; + int rc; + struct list *list_adopted; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (!is_usable_interior_route(bpi_interior)) { + vnc_zlog_debug_verbose( + "%s: not usable interior route, skipping", __func__); + return; + } + + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: redist of exterior routes no enabled, skipping", + __func__); + return; + } + + if (it == bgp->rfapi->it_ce) { + vnc_zlog_debug_verbose("%s: import table is it_ce, skipping", + __func__); + return; + } + + /*debugging */ + vnc_zlog_debug_verbose("%s: interior prefix=%pRN, bpi type=%d", + __func__, rn_interior, bpi_interior->type); + + if (RFAPI_HAS_MONITOR_EXTERIOR(rn_interior)) { + + vnc_zlog_debug_verbose( + "%s: has exterior monitor; ext src: %p", __func__, + RFAPI_MONITOR_EXTERIOR(rn_interior)->source); + + /* + * There is a monitor here already. Therefore, we do not need + * to do any pulldown. Just construct exterior routes based + * on the new interior route. + */ + cursor = NULL; + for (rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, (void **)&pfx_exterior, + &cursor); + !rc; rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, + (void **)&pfx_exterior, &cursor)) { + + struct prefix_rd *prd; + struct attr new_attr; + uint32_t label; + + assert(bpi_exterior); + assert(pfx_exterior); + + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc.import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels->label[0]); + else + label = MPLS_INVALID_LABEL; + + /* use local_pref from unicast route */ + memset(&new_attr, 0, sizeof(struct attr)); + new_attr = *bpi_interior->attr; + if (bpi_exterior + && (bpi_exterior->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { + new_attr.local_pref = + bpi_exterior->attr->local_pref; + new_attr.flag |= + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + } + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, bpi_interior->peer, + NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, &new_attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + vnc_zlog_debug_verbose( + "%s: finished constructing exteriors based on existing monitors", + __func__); + return; + } + + vnc_zlog_debug_verbose("%s: no exterior monitor", __func__); + + /* + * No monitor at this node. Is this the first valid interior + * route at this node? + */ + if (RFAPI_MONITOR_EXTERIOR(rn_interior)->valid_interior_count > 1) { + vnc_zlog_debug_verbose( + "%s: new interior route not first valid one, skipping pulldown", + __func__); + return; + } + + /* + * Look up the tree for possible pulldown candidates. + * Find nearest parent with an exterior route monitor + */ + for (par = agg_node_parent(rn_interior); par; + par = agg_node_parent(par)) { + if (RFAPI_HAS_MONITOR_EXTERIOR(par)) + break; + } + + if (par) { + + vnc_zlog_debug_verbose( + "%s: checking parent %p for possible pulldowns", + __func__, par); + + /* check monitors at par for possible pulldown */ + cursor = NULL; + for (rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(par)->source, + (void **)&bpi_exterior, + (void **)&pfx_exterior, &cursor); + !rc; + rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(par)->source, + (void **)&bpi_exterior, + (void **)&pfx_exterior, &cursor)) { + + struct prefix pfx_nexthop; + + memset(&pfx_nexthop, 0, + sizeof(struct prefix)); /* keep valgrind happy */ + + /* check original nexthop for prefix match */ + rfapiUnicastNexthop2Prefix(afi, bpi_exterior->attr, + &pfx_nexthop); + + if (prefix_match(p, &pfx_nexthop)) { + + struct bgp_path_info *bpi; + struct prefix_rd *prd; + struct attr new_attr; + uint32_t label; + + /* do pull-down */ + + /* + * add monitor to longer prefix + */ + struct prefix *pfx_mon = prefix_new(); + prefix_copy(pfx_mon, pfx_exterior); + if (!RFAPI_MONITOR_EXTERIOR(rn_interior) + ->source) { + RFAPI_MONITOR_EXTERIOR(rn_interior) + ->source = skiplist_new( + 0, NULL, prefix_free_lists); + agg_lock_node(rn_interior); + } + skiplist_insert( + RFAPI_MONITOR_EXTERIOR(rn_interior) + ->source, + bpi_exterior, pfx_mon); + agg_lock_node(rn_interior); + + /* + * Delete constructed exterior routes based on + * parent routes. + */ + for (bpi = par->info; bpi; bpi = bpi->next) { + if (bpi->extra) + prd = &bpi->extra->vnc->vnc + .import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi)) + label = decode_label( + &bpi->extra->labels + ->label[0]); + else + label = MPLS_INVALID_LABEL; + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_KILL, bpi->peer, + NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, + bpi->attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + + + /* + * Add constructed exterior routes based on + * the new interior route at longer prefix. + */ + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc + .import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels + ->label[0]); + else + label = MPLS_INVALID_LABEL; + + /* use local_pref from unicast route */ + memset(&new_attr, 0, sizeof(struct attr)); + new_attr = *bpi_interior->attr; + if (bpi_exterior + && (bpi_exterior->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { + new_attr.local_pref = + bpi_exterior->attr->local_pref; + new_attr.flag |= ATTR_FLAG_BIT( + BGP_ATTR_LOCAL_PREF); + } + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, + bpi_interior->peer, NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, &new_attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + } + + /* + * The only monitors at rn_interior are the ones we added just + * above, so we can use the rn_interior list to identify which + * monitors to delete from the parent. + */ + cursor = NULL; + for (rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, NULL, &cursor); + !rc; rc = skiplist_next( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, NULL, &cursor)) { + + + skiplist_delete(RFAPI_MONITOR_EXTERIOR(par)->source, + bpi_exterior, NULL); + agg_unlock_node(par); /* sl entry */ + } + if (skiplist_empty(RFAPI_MONITOR_EXTERIOR(par)->source)) { + skiplist_free(RFAPI_MONITOR_EXTERIOR(par)->source); + RFAPI_MONITOR_EXTERIOR(par)->source = NULL; + agg_unlock_node(par); /* sl itself */ + } + } + + vnc_zlog_debug_verbose("%s: checking orphans", __func__); + + /* + * See if any orphans can be pulled down to the current node + */ + cursor = NULL; + list_adopted = NULL; + for (rc = skiplist_next(it->monitor_exterior_orphans, + (void **)&bpi_exterior, (void **)&pfx_exterior, + &cursor); + !rc; rc = skiplist_next(it->monitor_exterior_orphans, + (void **)&bpi_exterior, + (void **)&pfx_exterior, &cursor)) { + + struct prefix pfx_nexthop; + afi_t afi_exterior = family2afi(pfx_exterior->family); + + vnc_zlog_debug_verbose( + "%s: checking exterior orphan at prefix %pFX", __func__, + pfx_exterior); + + if (afi_exterior != afi) { + vnc_zlog_debug_verbose( + "%s: exterior orphan afi %d != interior afi %d, skip", + __func__, afi_exterior, afi); + continue; + } + + /* check original nexthop for prefix match */ + rfapiUnicastNexthop2Prefix(afi, bpi_exterior->attr, + &pfx_nexthop); + + if (prefix_match(p, &pfx_nexthop)) { + + struct prefix_rd *prd; + struct attr new_attr; + uint32_t label; + + /* do pull-down */ + + /* + * add monitor to longer prefix + */ + + struct prefix *pfx_mon = prefix_new(); + prefix_copy(pfx_mon, pfx_exterior); + if (!RFAPI_MONITOR_EXTERIOR(rn_interior)->source) { + RFAPI_MONITOR_EXTERIOR(rn_interior)->source = + skiplist_new( + 0, NULL, prefix_free_lists); + agg_lock_node(rn_interior); /* sl */ + } + skiplist_insert( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + bpi_exterior, pfx_mon); + agg_lock_node(rn_interior); /* sl entry */ + if (!list_adopted) { + list_adopted = list_new(); + } + listnode_add(list_adopted, bpi_exterior); + + /* + * Add constructed exterior routes based on the + * new interior route at the longer prefix. + */ + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc.import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels->label[0]); + else + label = MPLS_INVALID_LABEL; + + /* use local_pref from unicast route */ + memset(&new_attr, 0, sizeof(struct attr)); + new_attr = *bpi_interior->attr; + if (bpi_exterior + && (bpi_exterior->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { + new_attr.local_pref = + bpi_exterior->attr->local_pref; + new_attr.flag |= + ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF); + } + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, bpi_interior->peer, + NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, &new_attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + } + if (list_adopted) { + struct listnode *node; + struct agg_node *an_bpi_exterior; + + for (ALL_LIST_ELEMENTS_RO(list_adopted, node, + an_bpi_exterior)) { + skiplist_delete(it->monitor_exterior_orphans, + an_bpi_exterior, NULL); + } + list_delete(&list_adopted); + } +} + +/* + * This function should be called after an interior VPN route + * has been deleted from an import_table. + * bpi_interior must still be valid, but it must already be detached + * from its route node and the route node's valid_interior_count + * must already be decremented. + * + * NB should also be called whenever an existing vpn interior route + * becomes invalid (e.g., valid_interior_count is decremented) + */ +void vnc_import_bgp_exterior_del_route_interior( + struct bgp *bgp, struct rfapi_import_table *it, + struct agg_node *rn_interior, /* VPN IT node */ + struct bgp_path_info *bpi_interior) /* VPN IT route */ +{ + const struct prefix *p = agg_node_get_prefix(rn_interior); + afi_t afi = family2afi(p->family); + struct agg_node *par; + struct bgp_path_info *bpi_exterior; + struct prefix *pfx_exterior; /* exterior pfx */ + void *cursor; + int rc; + + if (!VALID_INTERIOR_TYPE(bpi_interior->type)) { + vnc_zlog_debug_verbose( + "%s: type %d not valid interior type, skipping", + __func__, bpi_interior->type); + return; + } + + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: redist of exterior routes no enabled, skipping", + __func__); + return; + } + + if (it == bgp->rfapi->it_ce) { + vnc_zlog_debug_verbose("%s: it is it_ce, skipping", __func__); + return; + } + + /* If no exterior routes depend on this prefix, nothing to do */ + if (!RFAPI_HAS_MONITOR_EXTERIOR(rn_interior)) { + vnc_zlog_debug_verbose("%s: no exterior monitor, skipping", + __func__); + return; + } + + /*debugging */ + vnc_zlog_debug_verbose("%s: interior prefix=%pRN, bpi type=%d", + __func__, rn_interior, bpi_interior->type); + + /* + * Remove constructed routes based on the deleted interior route + */ + cursor = NULL; + for (rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, (void **)&pfx_exterior, + &cursor); + !rc; + rc = skiplist_next(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, (void **)&pfx_exterior, + &cursor)) { + + struct prefix_rd *prd; + uint32_t label; + + if (bpi_interior->extra) + prd = &bpi_interior->extra->vnc->vnc.import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi_interior)) + label = decode_label( + &bpi_interior->extra->labels->label[0]); + else + label = MPLS_INVALID_LABEL; + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_KILL, bpi_interior->peer, NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, bpi_interior->attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, BGP_ROUTE_REDISTRIBUTE, + &label); + } + + /* + * If there are no remaining valid interior routes at this prefix, + * we need to look up the tree for a possible node to move monitors to + */ + if (RFAPI_MONITOR_EXTERIOR(rn_interior)->valid_interior_count) { + vnc_zlog_debug_verbose( + "%s: interior routes still present, skipping", + __func__); + return; + } + + /* + * Find nearest parent with at least one valid interior route + * If none is found, par will end up NULL, and we will move + * the monitors to the orphan list for this import table + */ + for (par = agg_node_parent(rn_interior); par; + par = agg_node_parent(par)) { + if (RFAPI_MONITOR_EXTERIOR(par)->valid_interior_count) + break; + } + + vnc_zlog_debug_verbose("%s: par=%p, ext src: %p", __func__, par, + RFAPI_MONITOR_EXTERIOR(rn_interior)->source); + + /* move all monitors */ + /* + * We will use and delete every element of the source skiplist + */ + while (!skiplist_first(RFAPI_MONITOR_EXTERIOR(rn_interior)->source, + (void **)&bpi_exterior, + (void **)&pfx_exterior)) { + + struct prefix *pfx_mon = prefix_new(); + + prefix_copy(pfx_mon, pfx_exterior); + + if (par) { + + struct bgp_path_info *bpi; + + /* + * Add monitor to parent node + */ + if (!RFAPI_MONITOR_EXTERIOR(par)->source) { + RFAPI_MONITOR_EXTERIOR(par)->source = + skiplist_new( + 0, NULL, prefix_free_lists); + agg_lock_node(par); /* sl */ + } + skiplist_insert(RFAPI_MONITOR_EXTERIOR(par)->source, + bpi_exterior, pfx_mon); + agg_lock_node(par); /* sl entry */ + + /* Add constructed exterior routes based on parent */ + for (bpi = par->info; bpi; bpi = bpi->next) { + + struct prefix_rd *prd; + struct attr new_attr; + uint32_t label; + + if (bpi->type == ZEBRA_ROUTE_BGP_DIRECT_EXT) + continue; + + if (bpi->extra) + prd = &bpi->extra->vnc->vnc.import.rd; + else + prd = NULL; + + if (BGP_PATH_INFO_NUM_LABELS(bpi)) + label = decode_label( + &bpi->extra->labels->label[0]); + else + label = MPLS_INVALID_LABEL; + + /* use local_pref from unicast route */ + memset(&new_attr, 0, sizeof(new_attr)); + new_attr = *bpi->attr; + if (bpi_exterior + && (bpi_exterior->attr->flag + & ATTR_FLAG_BIT(BGP_ATTR_LOCAL_PREF))) { + new_attr.local_pref = + bpi_exterior->attr->local_pref; + new_attr.flag |= ATTR_FLAG_BIT( + BGP_ATTR_LOCAL_PREF); + } + + rfapiBgpInfoFilteredImportVPN( + it, FIF_ACTION_UPDATE, bpi->peer, + NULL, /* rfd */ + pfx_exterior, NULL, afi, prd, &new_attr, + ZEBRA_ROUTE_BGP_DIRECT_EXT, + BGP_ROUTE_REDISTRIBUTE, &label); + } + + } else { + + /* + * No interior route for exterior's nexthop. Save + * monitor + * in orphan list to await future route. + */ + skiplist_insert(it->monitor_exterior_orphans, + bpi_exterior, pfx_mon); + } + + skiplist_delete_first( + RFAPI_MONITOR_EXTERIOR(rn_interior)->source); + agg_unlock_node(rn_interior); /* sl entry */ + } + if (skiplist_empty(RFAPI_MONITOR_EXTERIOR(rn_interior)->source)) { + skiplist_free(RFAPI_MONITOR_EXTERIOR(rn_interior)->source); + RFAPI_MONITOR_EXTERIOR(rn_interior)->source = NULL; + agg_unlock_node(rn_interior); /* sl itself */ + } +} + +/*********************************************************************** + * Generic add/delete unicast routes + ***********************************************************************/ + +void vnc_import_bgp_add_route(struct bgp *bgp, const struct prefix *prefix, + struct bgp_path_info *info) +{ + afi_t afi = family2afi(prefix->family); + + if (VNC_DEBUG(VERBOSE)) { + struct prefix pfx_nexthop; + + rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_nexthop); + vnc_zlog_debug_verbose("%s: pfx %pFX, nh %pFX", __func__, + prefix, &pfx_nexthop); + } +#if DEBUG_RHN_LIST + print_rhn_list(__func__, "ENTER "); +#endif + VNC_RHNCK(enter); + + if (!afi) { + flog_err(EC_LIB_DEVELOPMENT, "%s: can't get afi of prefix", + __func__); + return; + } + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check vnc redist flag for bgp direct routes */ + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] is 0, skipping", + __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); + return; + } + + switch (bgp->rfapi_cfg->redist_mode) { + case VNC_REDIST_MODE_PLAIN: + vnc_import_bgp_add_route_mode_plain(bgp, prefix, info); + break; + + case VNC_REDIST_MODE_RFG: + if (bgp->rfapi_cfg->rfg_redist) + vnc_import_bgp_add_route_mode_nvegroup( + bgp, prefix, info, bgp->rfapi_cfg->rfg_redist); + else + vnc_zlog_debug_verbose("%s: mode RFG but no redist RFG", + __func__); + break; + + case VNC_REDIST_MODE_RESOLVE_NVE: + vnc_import_bgp_add_route_mode_resolve_nve(bgp, prefix, info); + break; + } +#if DEBUG_RHN_LIST + print_rhn_list(__func__, "LEAVE "); +#endif + VNC_RHNCK(leave); +} + +/* + * "Withdrawing a Route" import process + */ +void vnc_import_bgp_del_route(struct bgp *bgp, const struct prefix *prefix, + struct bgp_path_info *info) /* unicast info */ +{ + afi_t afi = family2afi(prefix->family); + + assert(afi); + + { + struct prefix pfx_nexthop; + + rfapiUnicastNexthop2Prefix(afi, info->attr, &pfx_nexthop); + vnc_zlog_debug_verbose("%s: pfx %pFX, nh %pFX", __func__, + prefix, &pfx_nexthop); + } +#if DEBUG_RHN_LIST + print_rhn_list(__func__, "ENTER "); +#endif + VNC_RHNCK(enter); + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* check bgp redist flag for vnc direct ("vpn") routes */ + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: bgp redistribution of afi=%d VNC direct routes is off", + __func__, afi); + return; + } + + switch (bgp->rfapi_cfg->redist_mode) { + case VNC_REDIST_MODE_PLAIN: + vnc_import_bgp_del_route_mode_plain(bgp, prefix, info); + break; + + case VNC_REDIST_MODE_RFG: + if (bgp->rfapi_cfg->rfg_redist) + vnc_import_bgp_del_route_mode_nvegroup(bgp, prefix, + info); + else + vnc_zlog_debug_verbose("%s: mode RFG but no redist RFG", + __func__); + break; + + case VNC_REDIST_MODE_RESOLVE_NVE: + vnc_import_bgp_del_route_mode_resolve_nve(bgp, afi, prefix, + info); + break; + } +#if DEBUG_RHN_LIST + print_rhn_list(__func__, "LEAVE "); +#endif + VNC_RHNCK(leave); +} + + +/*********************************************************************** + * Enable/Disable + ***********************************************************************/ + +void vnc_import_bgp_redist_enable(struct bgp *bgp, afi_t afi) +{ + /* iterate over bgp unicast v4 and v6 routes, call + * vnc_import_bgp_add_route */ + + struct bgp_dest *dest; + + vnc_zlog_debug_verbose("%s: entry, afi=%d", __func__, afi); + + if (bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: already enabled for afi %d, skipping", __func__, + afi); + return; + } + bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT] = 1; + + for (dest = bgp_table_top(bgp->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; + bpi = bpi->next) { + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + vnc_import_bgp_add_route(bgp, bgp_dest_get_prefix(dest), + bpi); + } + } + vnc_zlog_debug_verbose( + "%s: set redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] return", + __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); +} + +void vnc_import_bgp_exterior_redist_enable(struct bgp *bgp, afi_t afi) +{ + struct bgp *bgp_exterior; + struct bgp_dest *dest; + + bgp_exterior = bgp->rfapi_cfg->redist_bgp_exterior_view; + + if (bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: already enabled for afi %d, skipping", __func__, + afi); + return; + } + bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT] = 1; + + if (!bgp_exterior) { + vnc_zlog_debug_verbose( + "%s: no exterior view set yet, no routes to import yet", + __func__); + return; + } + + for (dest = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; + bpi = bpi->next) { + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + vnc_import_bgp_exterior_add_route( + bgp_exterior, bgp_dest_get_prefix(dest), bpi); + } + } + vnc_zlog_debug_verbose( + "%s: set redist[afi=%d][type=%d=ZEBRA_ROUTE_BGP_DIRECT] return", + __func__, afi, ZEBRA_ROUTE_BGP_DIRECT); +} + +/* + * This function is for populating a newly-created Import Table + */ +void vnc_import_bgp_exterior_redist_enable_it( + struct bgp *bgp, afi_t afi, struct rfapi_import_table *it_only) +{ + struct bgp *bgp_exterior; + struct bgp_dest *dest; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + bgp_exterior = bgp->rfapi_cfg->redist_bgp_exterior_view; + + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose("%s: not enabled for afi %d, skipping", + __func__, afi); + return; + } + + if (!bgp_exterior) { + vnc_zlog_debug_verbose( + "%s: no exterior view set yet, no routes to import yet", + __func__); + return; + } + + for (dest = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); dest; + dest = bgp_route_next(dest)) { + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; + bpi = bpi->next) { + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + vnc_import_bgp_exterior_add_route_it( + bgp_exterior, bgp_dest_get_prefix(dest), bpi, + it_only); + } + } +} + + +void vnc_import_bgp_redist_disable(struct bgp *bgp, afi_t afi) +{ + /* + * iterate over vpn routes, find routes of type ZEBRA_ROUTE_BGP_DIRECT, + * delete (call timer expire immediately) + */ + struct bgp_dest *dest1; + struct bgp_dest *dest2; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (!bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT]) { + vnc_zlog_debug_verbose( + "%s: already disabled for afi %d, skipping", __func__, + afi); + return; + } + + /* + * Two-level table for SAFI_MPLS_VPN + * Be careful when changing the things we iterate over + */ + for (dest1 = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); dest1; + dest1 = bgp_route_next(dest1)) { + const struct prefix *dest1_p; + + if (!bgp_dest_has_bgp_path_info_data(dest1)) + continue; + + dest1_p = bgp_dest_get_prefix(dest1); + for (dest2 = bgp_table_top(bgp_dest_get_bgp_table_info(dest1)); + dest2; dest2 = bgp_route_next(dest2)) { + const struct prefix *dest2_p = + bgp_dest_get_prefix(dest2); + struct bgp_path_info *bpi; + struct bgp_path_info *nextbpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest2); bpi; + bpi = nextbpi) { + + nextbpi = bpi->next; + + if (bpi->type != ZEBRA_ROUTE_BGP_DIRECT) + continue; + + struct rfapi_descriptor *rfd; + vncHDBgpDirect.peer = bpi->peer; + + assert(bpi->extra); + + rfd = bpi->extra->vnc->vnc.export.rfapi_handle; + + vnc_zlog_debug_verbose( + "%s: deleting bpi=%p, bpi->peer=%p, bpi->type=%d, bpi->sub_type=%d, bpi->extra->vnc->vnc.export.rfapi_handle=%p [passing rfd=%p]", + __func__, bpi, bpi->peer, bpi->type, + bpi->sub_type, + (bpi->extra ? bpi->extra->vnc->vnc + .export.rfapi_handle + : NULL), + rfd); + + del_vnc_route(rfd, bpi->peer, bgp, + SAFI_MPLS_VPN, dest2_p, + (struct prefix_rd *)dest1_p, + bpi->type, bpi->sub_type, NULL, + 1); /* kill */ + + vncHDBgpDirect.peer = NULL; + } + } + } + /* Clear RHN list */ + if (bgp->rfapi->resolve_nve_nexthop) { + struct prefix_bag *pb; + struct bgp_path_info *info; + while (!skiplist_first(bgp->rfapi->resolve_nve_nexthop, NULL, + (void *)&pb)) { + info = pb->ubpi; + skiplist_delete_first(bgp->rfapi->resolve_nve_nexthop); + bgp_path_info_unlock(info); + } + } + + bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT] = 0; + vnc_zlog_debug_verbose("%s: return", __func__); +} + + +void vnc_import_bgp_exterior_redist_disable(struct bgp *bgp, afi_t afi) +{ + struct rfapi_cfg *hc = bgp->rfapi_cfg; + struct bgp *bgp_exterior = hc->redist_bgp_exterior_view; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (!hc->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT]) { + vnc_zlog_debug_verbose( + "%s: already disabled for afi %d, skipping", __func__, + afi); + return; + } + + if (!bgp_exterior) { + vnc_zlog_debug_verbose( + "%s: bgp exterior view not defined, skipping", + __func__); + return; + } + + + { + struct bgp_dest *dest; + for (dest = bgp_table_top(bgp_exterior->rib[afi][SAFI_UNICAST]); + dest; dest = bgp_route_next(dest)) { + + struct bgp_path_info *bpi; + + for (bpi = bgp_dest_get_bgp_path_info(dest); bpi; + bpi = bpi->next) { + + if (CHECK_FLAG(bpi->flags, BGP_PATH_REMOVED)) + continue; + + vnc_import_bgp_exterior_del_route( + bgp_exterior, bgp_dest_get_prefix(dest), + bpi); + } + } +#if DEBUG_RHN_LIST + print_rhn_list(__func__, NULL); +#endif + } + + bgp->rfapi_cfg->redist[afi][ZEBRA_ROUTE_BGP_DIRECT_EXT] = 0; + vnc_zlog_debug_verbose("%s: return", __func__); +} diff --git a/bgpd/rfapi/vnc_import_bgp.h b/bgpd/rfapi/vnc_import_bgp.h new file mode 100644 index 0000000..c8d4170 --- /dev/null +++ b/bgpd/rfapi/vnc_import_bgp.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ +#define _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ + +#include "lib/zebra.h" +#include "lib/prefix.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" + +#define VALID_INTERIOR_TYPE(type) \ + (((type) == ZEBRA_ROUTE_BGP) || ((type) == ZEBRA_ROUTE_BGP_DIRECT)) + +extern uint32_t calc_local_pref(struct attr *attr, struct peer *peer); + +extern int vnc_prefix_cmp(const void *pfx1, const void *pfx2); + +extern void vnc_import_bgp_add_route(struct bgp *bgp, + const struct prefix *prefix, + struct bgp_path_info *info); + +extern void vnc_import_bgp_del_route(struct bgp *bgp, + const struct prefix *prefix, + struct bgp_path_info *info); + +extern void vnc_import_bgp_redist_enable(struct bgp *bgp, afi_t afi); + +extern void vnc_import_bgp_redist_disable(struct bgp *bgp, afi_t afi); + +extern void vnc_import_bgp_exterior_redist_enable(struct bgp *bgp, afi_t afi); + +extern void vnc_import_bgp_exterior_redist_disable(struct bgp *bgp, afi_t afi); + + +extern void vnc_import_bgp_exterior_add_route( + struct bgp *bgp, /* exterior instance, we hope */ + const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info); /* unicast info */ + +extern void vnc_import_bgp_exterior_del_route( + struct bgp *bgp, const struct prefix *prefix, /* unicast prefix */ + struct bgp_path_info *info); /* unicast info */ + +extern void vnc_import_bgp_add_vnc_host_route_mode_resolve_nve( + struct bgp *bgp, struct prefix_rd *prd, /* RD */ + struct bgp_table *table_rd, /* per-rd VPN route table */ + const struct prefix *prefix, /* VPN prefix */ + struct bgp_path_info *bpi); /* new VPN host route */ + +extern void vnc_import_bgp_del_vnc_host_route_mode_resolve_nve( + struct bgp *bgp, struct prefix_rd *prd, /* RD */ + struct bgp_table *table_rd, /* per-rd VPN route table */ + const struct prefix *prefix, /* VPN prefix */ + struct bgp_path_info *bpi); /* old VPN host route */ + +#endif /* _QUAGGA_RFAPI_VNC_IMPORT_BGP_H_ */ diff --git a/bgpd/rfapi/vnc_import_bgp_p.h b/bgpd/rfapi/vnc_import_bgp_p.h new file mode 100644 index 0000000..7019796 --- /dev/null +++ b/bgpd/rfapi/vnc_import_bgp_p.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +#ifndef _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ +#define _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ + +#include "lib/zebra.h" +#include "lib/prefix.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" + +extern void vnc_import_bgp_exterior_add_route_interior( + struct bgp *bgp, struct rfapi_import_table *it, + struct agg_node *rn_interior, /* VPN IT node */ + struct bgp_path_info *bpi_interior); /* VPN IT route */ + +extern void vnc_import_bgp_exterior_del_route_interior( + struct bgp *bgp, struct rfapi_import_table *it, + struct agg_node *rn_interior, /* VPN IT node */ + struct bgp_path_info *bpi_interior); /* VPN IT route */ + +extern void +vnc_import_bgp_exterior_redist_enable_it(struct bgp *bgp, afi_t afi, + struct rfapi_import_table *it_only); + +#endif /* _QUAGGA_RFAPI_VNC_IMPORT_BGP_P_H_ */ diff --git a/bgpd/rfapi/vnc_zebra.c b/bgpd/rfapi/vnc_zebra.c new file mode 100644 index 0000000..82c08ca --- /dev/null +++ b/bgpd/rfapi/vnc_zebra.c @@ -0,0 +1,887 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: vnc_zebra.c + * Purpose: Handle exchange of routes between VNC and Zebra + */ + +#include "lib/zebra.h" +#include "lib/prefix.h" +#include "lib/agg_table.h" +#include "lib/log.h" +#include "lib/command.h" +#include "lib/zclient.h" +#include "lib/stream.h" +#include "lib/ringbuf.h" +#include "lib/memory.h" +#include "lib/lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_advertise.h" + +#include "bgpd/rfapi/bgp_rfapi_cfg.h" +#include "bgpd/rfapi/rfapi.h" +#include "bgpd/rfapi/rfapi_import.h" +#include "bgpd/rfapi/rfapi_private.h" +#include "bgpd/rfapi/vnc_zebra.h" +#include "bgpd/rfapi/rfapi_vty.h" +#include "bgpd/rfapi/rfapi_backend.h" +#include "bgpd/rfapi/vnc_debug.h" + +static struct rfapi_descriptor vncHD1VR; /* Single-VR export dummy nve descr */ +static struct zclient *zclient_vnc = NULL; + +/*********************************************************************** + * REDISTRIBUTE: Zebra sends updates/withdraws to BGPD + ***********************************************************************/ + +/* + * Routes coming from zebra get added to VNC here + */ +static void vnc_redistribute_add(struct prefix *p, uint32_t metric, + uint8_t type) +{ + struct bgp *bgp = bgp_get_default(); + struct prefix_rd prd; + struct rfapi_ip_addr vnaddr; + afi_t afi; + uint32_t local_pref = + rfp_cost_to_localpref(metric > 255 ? 255 : metric); + + if (!bgp) + return; + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + afi = family2afi(p->family); + if (!afi) { + vnc_zlog_debug_verbose("%s: unknown prefix address family %d", + __func__, p->family); + return; + } + + if (!bgp->rfapi_cfg->redist[afi][type]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d] is 0, skipping", + __func__, afi, type); + return; + } + if (!bgp->rfapi_cfg->rfg_redist) { + vnc_zlog_debug_verbose("%s: no redist nve group, skipping", + __func__); + return; + } + + /* + * Assume nve group's configured VN address prefix is a host + * route which also happens to give the NVE VN address to use + * for redistributing into VNC. + */ + vnaddr.addr_family = bgp->rfapi_cfg->rfg_redist->vn_prefix.family; + switch (bgp->rfapi_cfg->rfg_redist->vn_prefix.family) { + case AF_INET: + if (bgp->rfapi_cfg->rfg_redist->vn_prefix.prefixlen + != IPV4_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist nve group VN prefix len (%d) != 32, skipping", + __func__, + bgp->rfapi_cfg->rfg_redist->vn_prefix + .prefixlen); + return; + } + vnaddr.addr.v4 = + bgp->rfapi_cfg->rfg_redist->vn_prefix.u.prefix4; + break; + case AF_INET6: + if (bgp->rfapi_cfg->rfg_redist->vn_prefix.prefixlen + != IPV6_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist nve group VN prefix len (%d) != 128, skipping", + __func__, + bgp->rfapi_cfg->rfg_redist->vn_prefix + .prefixlen); + return; + } + vnaddr.addr.v6 = + bgp->rfapi_cfg->rfg_redist->vn_prefix.u.prefix6; + break; + default: + vnc_zlog_debug_verbose( + "%s: no redist nve group VN host prefix configured, skipping", + __func__); + return; + } + + /* + * Assume nve group's configured UN address prefix is a host + * route which also happens to give the NVE UN address to use + * for redistributing into VNC. + */ + + /* + * Set UN address in dummy nve descriptor so add_vnc_route + * can use it in VNC tunnel SubTLV + */ + { + struct rfapi_ip_prefix pfx_un; + + rfapiQprefix2Rprefix(&bgp->rfapi_cfg->rfg_redist->un_prefix, + &pfx_un); + + switch (pfx_un.prefix.addr_family) { + case AF_INET: + if (pfx_un.length != IPV4_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist nve group UN prefix len (%d) != 32, skipping", + __func__, pfx_un.length); + return; + } + break; + case AF_INET6: + if (pfx_un.length != IPV6_MAX_BITLEN) { + vnc_zlog_debug_verbose( + "%s: redist nve group UN prefix len (%d) != 128, skipping", + __func__, pfx_un.length); + return; + } + break; + default: + vnc_zlog_debug_verbose( + "%s: no redist nve group UN host prefix configured, skipping", + __func__); + return; + } + + vncHD1VR.un_addr = pfx_un.prefix; + + if (!vncHD1VR.peer) { + /* + * Same setup as in rfapi_open() + */ + vncHD1VR.peer = peer_new(bgp); + vncHD1VR.peer->connection->status = + Established; /* keep bgp core happy */ + + bgp_peer_connection_buffers_free( + vncHD1VR.peer->connection); + + /* base code assumes have valid host pointer */ + vncHD1VR.peer->host = + XSTRDUP(MTYPE_BGP_PEER_HOST, ".zebra."); + + /* Mark peer as belonging to HD */ + SET_FLAG(vncHD1VR.peer->flags, PEER_FLAG_IS_RFAPI_HD); + } + } + + memset(&prd, 0, sizeof(prd)); + prd = bgp->rfapi_cfg->rfg_redist->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + add_vnc_route(&vncHD1VR, /* cookie + UN addr */ + bgp, SAFI_MPLS_VPN, p, &prd, &vnaddr, &local_pref, + &(bgp->rfapi_cfg->redist_lifetime), + NULL, /* RFP options */ + NULL, /* struct rfapi_un_option */ + NULL, /* struct rfapi_vn_option */ + bgp->rfapi_cfg->rfg_redist->rt_export_list, NULL, + NULL, /* label: default */ + type, BGP_ROUTE_REDISTRIBUTE, 0); /* flags */ +} + +/* + * Route deletions from zebra propagate to VNC here + */ +static void vnc_redistribute_delete(struct prefix *p, uint8_t type) +{ + struct bgp *bgp = bgp_get_default(); + struct prefix_rd prd; + afi_t afi; + + if (!bgp) + return; + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + afi = family2afi(p->family); + if (!afi) { + vnc_zlog_debug_verbose("%s: unknown prefix address family %d", + __func__, p->family); + return; + } + if (!bgp->rfapi_cfg->redist[afi][type]) { + vnc_zlog_debug_verbose( + "%s: bgp->rfapi_cfg->redist[afi=%d][type=%d] is 0, skipping", + __func__, afi, type); + return; + } + if (!bgp->rfapi_cfg->rfg_redist) { + vnc_zlog_debug_verbose("%s: no redist nve group, skipping", + __func__); + return; + } + + memset(&prd, 0, sizeof(prd)); + prd = bgp->rfapi_cfg->rfg_redist->rd; + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + + del_vnc_route(&vncHD1VR, /* use dummy ptr as cookie */ + vncHD1VR.peer, bgp, SAFI_MPLS_VPN, p, &prd, type, + BGP_ROUTE_REDISTRIBUTE, NULL, 0); +} + +/* + * Flush all redistributed routes of type + */ +static void vnc_redistribute_withdraw(struct bgp *bgp, afi_t afi, uint8_t type) +{ + struct prefix_rd prd; + struct bgp_table *table; + struct bgp_dest *pdest; + struct bgp_dest *dest; + + vnc_zlog_debug_verbose("%s: entry", __func__); + + if (!bgp) + return; + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + /* + * Loop over all the RDs + */ + for (pdest = bgp_table_top(bgp->rib[afi][SAFI_MPLS_VPN]); pdest; + pdest = bgp_route_next(pdest)) { + const struct prefix *pdest_p = bgp_dest_get_prefix(pdest); + + memset(&prd, 0, sizeof(prd)); + prd.family = AF_UNSPEC; + prd.prefixlen = 64; + memcpy(prd.val, pdest_p->u.val, 8); + + /* This is the per-RD table of prefixes */ + table = bgp_dest_get_bgp_table_info(pdest); + if (!table) + continue; + + for (dest = bgp_table_top(table); dest; + dest = bgp_route_next(dest)) { + + struct bgp_path_info *ri; + + for (ri = bgp_dest_get_bgp_path_info(dest); ri; + ri = ri->next) { + if (ri->type + == type) { /* has matching redist type */ + break; + } + } + if (ri) { + del_vnc_route( + &vncHD1VR, /* use dummy ptr as cookie */ + vncHD1VR.peer, bgp, SAFI_MPLS_VPN, + bgp_dest_get_prefix(dest), &prd, type, + BGP_ROUTE_REDISTRIBUTE, NULL, 0); + } + } + } + vnc_zlog_debug_verbose("%s: return", __func__); +} + +/* + * Zebra route add and delete treatment. + * + * Assumes 1 nexthop + */ +static int vnc_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + int add; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + add = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); + if (add) + vnc_redistribute_add(&api.prefix, api.metric, api.type); + else + vnc_redistribute_delete(&api.prefix, api.type); + + if (BGP_DEBUG(zebra, ZEBRA)) + vnc_zlog_debug_verbose( + "%s: Zebra rcvd: route delete %s %pFX metric %u", + __func__, zebra_route_string(api.type), &api.prefix, + api.metric); + + return 0; +} + +/*********************************************************************** + * vnc_bgp_zebra_*: VNC sends updates/withdraws to Zebra + ***********************************************************************/ + +/* + * low-level message builder + */ +static void vnc_zebra_route_msg(const struct prefix *p, unsigned int nhp_count, + void *nhp_ary, int add) /* 1 = add, 0 = del */ +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + int i; + struct in_addr **nhp_ary4 = nhp_ary; + struct in6_addr **nhp_ary6 = nhp_ary; + + if (!nhp_count) { + vnc_zlog_debug_verbose("%s: empty nexthop list, skipping", + __func__); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_VNC; + api.safi = SAFI_UNICAST; + api.prefix = *p; + + /* Nexthops */ + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api.nexthop_num = MIN(nhp_count, multipath_num); + for (i = 0; i < api.nexthop_num; i++) { + + api_nh = &api.nexthops[i]; + api_nh->vrf_id = VRF_DEFAULT; + switch (p->family) { + case AF_INET: + memcpy(&api_nh->gate.ipv4, nhp_ary4[i], + sizeof(api_nh->gate.ipv4)); + api_nh->type = NEXTHOP_TYPE_IPV4; + break; + case AF_INET6: + memcpy(&api_nh->gate.ipv6, nhp_ary6[i], + sizeof(api_nh->gate.ipv6)); + api_nh->type = NEXTHOP_TYPE_IPV6; + break; + } + } + + if (BGP_DEBUG(zebra, ZEBRA)) + vnc_zlog_debug_verbose( + "%s: Zebra send: route %s %pFX, nhp_count=%d", __func__, + (add ? "add" : "del"), &api.prefix, nhp_count); + + zclient_route_send((add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE), + zclient_vnc, &api); +} + + +static void +nve_list_to_nh_array(uint8_t family, struct list *nve_list, + unsigned int *nh_count_ret, + void **nh_ary_ret, /* returned address array */ + void **nhp_ary_ret) /* returned pointer array */ +{ + int nve_count = listcount(nve_list); + + *nh_count_ret = 0; + *nh_ary_ret = NULL; + *nhp_ary_ret = NULL; + + if (!nve_count) { + vnc_zlog_debug_verbose("%s: empty nve_list, skipping", + __func__); + return; + } + + if (family == AF_INET) { + struct listnode *ln; + struct in_addr *iap; + struct in_addr **v; + + /* + * Array of nexthop addresses + */ + *nh_ary_ret = + XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in_addr)); + + /* + * Array of pointers to nexthop addresses + */ + *nhp_ary_ret = XCALLOC(MTYPE_TMP, + nve_count * sizeof(struct in_addr *)); + iap = *nh_ary_ret; + v = *nhp_ary_ret; + + for (ln = listhead(nve_list); ln; ln = listnextnode(ln)) { + + struct rfapi_descriptor *irfd; + struct prefix nhp; + + irfd = listgetdata(ln); + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) + continue; + + *iap = nhp.u.prefix4; + *v = iap; + vnc_zlog_debug_verbose( + "%s: ipadr: (%p)<-0x%x, ptr: (%p)<-%p", + __func__, iap, nhp.u.prefix4.s_addr, v, iap); + + ++iap; + ++v; + ++*nh_count_ret; + } + + } else if (family == AF_INET6) { + + struct listnode *ln; + + *nh_ary_ret = + XCALLOC(MTYPE_TMP, nve_count * sizeof(struct in6_addr)); + + *nhp_ary_ret = XCALLOC(MTYPE_TMP, + nve_count * sizeof(struct in6_addr *)); + + for (ln = listhead(nve_list); ln; ln = listnextnode(ln)) { + + struct rfapi_descriptor *irfd; + struct in6_addr *iap = *nh_ary_ret; + struct in6_addr **v = *nhp_ary_ret; + struct prefix nhp; + + irfd = listgetdata(ln); + + if (rfapiRaddr2Qprefix(&irfd->vn_addr, &nhp)) + continue; + + *iap = nhp.u.prefix6; + *v = iap; + + ++iap; + ++v; + ++*nh_count_ret; + } + } +} + +static void import_table_to_nve_list_zebra(struct bgp *bgp, + struct rfapi_import_table *it, + struct list **nves, uint8_t family) +{ + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + /* + * Loop over the list of NVE-Groups configured for + * exporting to direct-bgp. + * + * Build a list of NVEs that use this import table + */ + *nves = NULL; + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + /* + * If this NVE-Group's import table matches the current one + */ + if (rfgn->rfg && rfgn->rfg->nves + && rfgn->rfg->rfapi_import_table == it) { + + nve_group_to_nve_list(rfgn->rfg, nves, family); + } + } +} + +static void vnc_zebra_add_del_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn, + int add) /* !0 = add, 0 = del */ +{ + struct list *nves; + const struct prefix *p = agg_node_get_prefix(rn); + unsigned int nexthop_count = 0; + void *nh_ary = NULL; + void *nhp_ary = NULL; + + vnc_zlog_debug_verbose("%s: entry, add=%d", __func__, add); + + if (zclient_vnc->sock < 0) + return; + + if (p->family != AF_INET && p->family != AF_INET6) { + flog_err(EC_LIB_DEVELOPMENT, + "%s: invalid route node addr family", __func__); + return; + } + + if (!vrf_bitmap_check(&zclient_vnc->redist[family2afi(p->family)] + [ZEBRA_ROUTE_VNC], + VRF_DEFAULT)) + return; + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + if (!listcount(bgp->rfapi_cfg->rfg_export_zebra_l)) { + vnc_zlog_debug_verbose( + "%s: no zebra export nve group, skipping", __func__); + return; + } + + import_table_to_nve_list_zebra(bgp, import_table, &nves, p->family); + + if (nves) { + nve_list_to_nh_array(p->family, nves, &nexthop_count, &nh_ary, + &nhp_ary); + + list_delete(&nves); + + if (nexthop_count) + vnc_zebra_route_msg(p, nexthop_count, nhp_ary, add); + } + + XFREE(MTYPE_TMP, nhp_ary); + XFREE(MTYPE_TMP, nh_ary); +} + +void vnc_zebra_add_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn) +{ + vnc_zebra_add_del_prefix(bgp, import_table, rn, 1); +} + +void vnc_zebra_del_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn) +{ + vnc_zebra_add_del_prefix(bgp, import_table, rn, 0); +} + + +static void vnc_zebra_add_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd, + int add) /* 0 = del, !0 = add */ +{ + struct listnode *node; + struct rfapi_rfg_name *rfgn; + struct rfapi_nve_group_cfg *rfg = rfd->rfg; + afi_t afi = family2afi(rfd->vn_addr.addr_family); + struct prefix nhp; + void *pAddr; + + vnc_zlog_debug_verbose("%s: entry, add=%d", __func__, add); + + if (zclient_vnc->sock < 0) + return; + + if (!vrf_bitmap_check(&zclient_vnc->redist[afi][ZEBRA_ROUTE_VNC], + VRF_DEFAULT)) + return; + + if (afi != AFI_IP && afi != AFI_IP6) { + flog_err(EC_LIB_DEVELOPMENT, "%s: invalid vn addr family", + __func__); + return; + } + + if (!bgp) + return; + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: bgp->rfapi_cfg is NULL, skipping", + __func__); + return; + } + + if (rfapiRaddr2Qprefix(&rfd->vn_addr, &nhp)) { + vnc_zlog_debug_verbose("%s: can't convert vn address, skipping", + __func__); + return; + } + + pAddr = &nhp.u.val; + + /* + * Loop over the list of NVE-Groups configured for + * exporting to zebra and see if this new NVE's + * group is among them. + */ + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + /* + * Yes, this NVE's group is configured for export to zebra + */ + if (rfgn->rfg == rfg) { + + struct agg_table *rt = NULL; + struct agg_node *rn; + struct rfapi_import_table *import_table; + import_table = rfg->rfapi_import_table; + + vnc_zlog_debug_verbose( + "%s: this nve's group is in zebra export list", + __func__); + + rt = import_table->imported_vpn[afi]; + + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + if (!rn->info) + continue; + + vnc_zlog_debug_verbose("%s: sending %s", + __func__, + (add ? "add" : "del")); + vnc_zebra_route_msg(agg_node_get_prefix(rn), 1, + &pAddr, add); + } + } + } +} + +void vnc_zebra_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + vnc_zebra_add_del_nve(bgp, rfd, 1); +} + +void vnc_zebra_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd) +{ + vnc_zebra_add_del_nve(bgp, rfd, 0); +} + +static void vnc_zebra_add_del_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi, int add) +{ + struct agg_table *rt = NULL; + struct agg_node *rn; + struct rfapi_import_table *import_table; + uint8_t family = afi2family(afi); + + struct list *nves = NULL; + unsigned int nexthop_count = 0; + void *nh_ary = NULL; + void *nhp_ary = NULL; + + vnc_zlog_debug_verbose("%s: entry", __func__); + import_table = rfg->rfapi_import_table; + if (!import_table) { + vnc_zlog_debug_verbose( + "%s: import table not defined, returning", __func__); + return; + } + + if (afi == AFI_IP || afi == AFI_IP6) { + rt = import_table->imported_vpn[afi]; + } else { + flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); + return; + } + + if (!family) { + flog_err(EC_LIB_DEVELOPMENT, "%s: computed bad family: %d", + __func__, family); + return; + } + + if (!rfg->nves) { + /* avoid segfault below if list doesn't exist */ + vnc_zlog_debug_verbose("%s: no NVEs in this group", __func__); + return; + } + + nve_group_to_nve_list(rfg, &nves, family); + if (nves) { + vnc_zlog_debug_verbose("%s: have nves", __func__); + nve_list_to_nh_array(family, nves, &nexthop_count, &nh_ary, + &nhp_ary); + + vnc_zlog_debug_verbose("%s: family: %d, nve count: %d", + __func__, family, nexthop_count); + + list_delete(&nves); + + if (nexthop_count) { + /* + * Walk the NVE-Group's VNC Import table + */ + for (rn = agg_route_top(rt); rn; + rn = agg_route_next(rn)) { + if (rn->info) { + vnc_zebra_route_msg( + agg_node_get_prefix(rn), + nexthop_count, nhp_ary, add); + } + } + } + XFREE(MTYPE_TMP, nhp_ary); + XFREE(MTYPE_TMP, nh_ary); + } +} + +void vnc_zebra_add_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) +{ + vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP, 1); + vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP6, 1); +} + +void vnc_zebra_del_group(struct bgp *bgp, struct rfapi_nve_group_cfg *rfg) +{ + vnc_zlog_debug_verbose("%s: entry", __func__); + vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP, 0); + vnc_zebra_add_del_group_afi(bgp, rfg, AFI_IP6, 0); +} + +void vnc_zebra_reexport_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, afi_t afi) +{ + struct listnode *node; + struct rfapi_rfg_name *rfgn; + + for (ALL_LIST_ELEMENTS_RO(bgp->rfapi_cfg->rfg_export_zebra_l, node, + rfgn)) { + + if (rfgn->rfg == rfg) { + vnc_zebra_add_del_group_afi(bgp, rfg, afi, 0); + vnc_zebra_add_del_group_afi(bgp, rfg, afi, 1); + break; + } + } +} + + +/*********************************************************************** + * CONTROL INTERFACE + ***********************************************************************/ + + +/* Other routes redistribution into BGP. */ +int vnc_redistribute_set(struct bgp *bgp, afi_t afi, int type) +{ + if (!bgp->rfapi_cfg) { + return CMD_WARNING_CONFIG_FAILED; + } + + /* Set flag to BGP instance. */ + bgp->rfapi_cfg->redist[afi][type] = 1; + + // bgp->redist[afi][type] = 1; + + /* Return if already redistribute flag is set. */ + if (vrf_bitmap_check(&zclient_vnc->redist[afi][type], VRF_DEFAULT)) + return CMD_WARNING_CONFIG_FAILED; + + vrf_bitmap_set(&zclient_vnc->redist[afi][type], VRF_DEFAULT); + + // vrf_bitmap_set(&zclient_vnc->redist[afi][type], VRF_DEFAULT); + + /* Return if zebra connection is not established. */ + if (zclient_vnc->sock < 0) + return CMD_WARNING_CONFIG_FAILED; + + if (BGP_DEBUG(zebra, ZEBRA)) + vnc_zlog_debug_verbose("Zebra send: redistribute add %s", + zebra_route_string(type)); + + /* Send distribute add message to zebra. */ + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient_vnc, afi, type, + 0, VRF_DEFAULT); + + return CMD_SUCCESS; +} + +/* Unset redistribution. */ +int vnc_redistribute_unset(struct bgp *bgp, afi_t afi, int type) +{ + vnc_zlog_debug_verbose("%s: type=%d entry", __func__, type); + + if (!bgp->rfapi_cfg) { + vnc_zlog_debug_verbose("%s: return (no rfapi_cfg)", __func__); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Unset flag from BGP instance. */ + bgp->rfapi_cfg->redist[afi][type] = 0; + + /* Return if zebra connection is disabled. */ + if (!vrf_bitmap_check(&zclient_vnc->redist[afi][type], VRF_DEFAULT)) + return CMD_WARNING_CONFIG_FAILED; + vrf_bitmap_unset(&zclient_vnc->redist[afi][type], VRF_DEFAULT); + + if (bgp->rfapi_cfg->redist[AFI_IP][type] == 0 + && bgp->rfapi_cfg->redist[AFI_IP6][type] == 0 + && zclient_vnc->sock >= 0) { + /* Send distribute delete message to zebra. */ + if (BGP_DEBUG(zebra, ZEBRA)) + vnc_zlog_debug_verbose( + "Zebra send: redistribute delete %s", + zebra_route_string(type)); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient_vnc, + afi, type, 0, VRF_DEFAULT); + } + + /* Withdraw redistributed routes from current BGP's routing table. */ + vnc_redistribute_withdraw(bgp, afi, type); + + vnc_zlog_debug_verbose("%s: return", __func__); + + return CMD_SUCCESS; +} + +extern struct zebra_privs_t bgpd_privs; + +static zclient_handler *const vnc_handlers[] = { + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = vnc_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = vnc_zebra_read_route, +}; + +/* + * Modeled after bgp_zebra.c'bgp_zebra_init() + * Charriere asks, "Is it possible to carry two?" + */ +void vnc_zebra_init(struct event_loop *master) +{ + /* Set default values. */ + zclient_vnc = zclient_new(master, &zclient_options_auxiliary, + vnc_handlers, array_size(vnc_handlers)); + zclient_init(zclient_vnc, ZEBRA_ROUTE_VNC, 0, &bgpd_privs); +} + +void vnc_zebra_destroy(void) +{ + if (zclient_vnc == NULL) + return; + zclient_stop(zclient_vnc); + zclient_free(zclient_vnc); + zclient_vnc = NULL; +} diff --git a/bgpd/rfapi/vnc_zebra.h b/bgpd/rfapi/vnc_zebra.h new file mode 100644 index 0000000..6d7e689 --- /dev/null +++ b/bgpd/rfapi/vnc_zebra.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2009-2016, LabN Consulting, L.L.C. + * + */ + +/* + * File: vnc_zebra.h + */ + +#ifndef _QUAGGA_BGP_VNC_ZEBRA_H +#define _QUAGGA_BGP_VNC_ZEBRA_H + +#include "lib/zebra.h" + +extern void vnc_zebra_add_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn); + +extern void vnc_zebra_del_prefix(struct bgp *bgp, + struct rfapi_import_table *import_table, + struct agg_node *rn); + +extern void vnc_zebra_add_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); + +extern void vnc_zebra_del_nve(struct bgp *bgp, struct rfapi_descriptor *rfd); + +extern void vnc_zebra_add_group(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg); + +extern void vnc_zebra_del_group(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg); + +extern void vnc_zebra_reexport_group_afi(struct bgp *bgp, + struct rfapi_nve_group_cfg *rfg, + afi_t afi); + +extern int vnc_redistribute_set(struct bgp *bgp, afi_t afi, int type); + +extern int vnc_redistribute_unset(struct bgp *bgp, afi_t afi, int type); + +#endif /* _QUAGGA_BGP_VNC_ZEBRA_H */ diff --git a/bgpd/rfp-example/librfp/Makefile b/bgpd/rfp-example/librfp/Makefile new file mode 100644 index 0000000..8deb93d --- /dev/null +++ b/bgpd/rfp-example/librfp/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C ../../.. bgpd/rfp-example/librfp/librfp.a +%: ALWAYS + @$(MAKE) -s -C ../../.. bgpd/rfp-example/librfp/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/bgpd/rfp-example/librfp/rfp.h b/bgpd/rfp-example/librfp/rfp.h new file mode 100644 index 0000000..ae74fed --- /dev/null +++ b/bgpd/rfp-example/librfp/rfp.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2015-2016, LabN Consulting, L.L.C. + * + */ + +/* Sample header file */ +#ifndef _RFP_H +#define _RFP_H + +#include "bgpd/rfapi/rfapi.h" +extern int bgp_rfp_cfg_write(void *vty, void *bgp); +/* TO BE REMOVED */ +void rfp_clear_vnc_nve_all(void); + +#endif /* _RFP_H */ diff --git a/bgpd/rfp-example/librfp/rfp_example.c b/bgpd/rfp-example/librfp/rfp_example.c new file mode 100644 index 0000000..1ada36b --- /dev/null +++ b/bgpd/rfp-example/librfp/rfp_example.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2015-2016, LabN Consulting, L.L.C. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* stub rfp */ +#include "rfp_internal.h" +#include "bgpd/rfapi/rfapi.h" +#include "lib/command.h" + +struct rfp_instance_t { + struct rfapi_rfp_cfg rfapi_config; + struct rfapi_rfp_cb_methods rfapi_callbacks; + struct event_loop *master; + uint32_t config_var; +}; + +struct rfp_instance_t + global_rfi; /* dynamically allocate in full implementation */ + +/*********************************************************************** + * Sample VTY / internal function + **********************************************************************/ +#define RFP_SHOW_STR "RFP information\n" +DEFUN (rfp_example_config_value, + rfp_example_config_value_cmd, + "rfp example-config-value VALUE", + RFP_SHOW_STR + "Example value to be configured\n" + "Value to display\n") +{ + uint32_t value = 0; + struct rfp_instance_t *rfi = NULL; + rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ + assert(rfi != NULL); + + value = strtoul(argv[2]->arg, NULL, 10); + if (rfi) + rfi->config_var = value; + return CMD_SUCCESS; +} + +DEFUN (rfp_holddown_factor, + rfp_holddown_factor_cmd, + "rfp holddown-factor (0-4294967295)", + RFP_SHOW_STR + "Set Hold-Down Factor as a percentage of registration lifetime.\n" + "Percentage of registration lifetime\n") +{ + struct rfp_instance_t *rfi; + uint32_t value = 0; + + value = strtoul((argv[--argc]->arg), NULL, 10); + rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ + if (!rfi) { + vty_out(vty, "VNC not configured\n"); + return CMD_WARNING; + } + rfi->rfapi_config.holddown_factor = value; + rfapi_rfp_set_configuration(rfi, &rfi->rfapi_config); + return CMD_SUCCESS; +} + + +DEFUN (rfp_full_table_download, + rfp_full_table_download_cmd, + "rfp full-table-download ", + RFP_SHOW_STR + "RFP full table download support (default=on)\n" + "Enable RFP full table download\n" + "Disable RFP full table download\n") +{ + struct rfp_instance_t *rfi; + rfapi_rfp_download_type old; + + rfi = rfapi_get_rfp_start_val(VTY_GET_CONTEXT(bgp)); /* BGP_NODE */ + if (!rfi) { + vty_out(vty, "VNC not configured\n"); + return CMD_WARNING; + } + old = rfi->rfapi_config.download_type; + if (argv[--argc]->arg[1] == 'n' || argv[argc]->arg[1] == 'N') + rfi->rfapi_config.download_type = RFAPI_RFP_DOWNLOAD_FULL; + else + rfi->rfapi_config.download_type = RFAPI_RFP_DOWNLOAD_PARTIAL; + if (old != rfi->rfapi_config.download_type) + rfapi_rfp_set_configuration(rfi, &rfi->rfapi_config); + return CMD_SUCCESS; +} + +static void rfp_vty_install(void) +{ + static int installed = 0; + if (installed) /* do this only once */ + return; + installed = 1; + /* example of new cli command */ + install_element(BGP_NODE, &rfp_example_config_value_cmd); + install_element(BGP_NODE, &rfp_holddown_factor_cmd); + install_element(BGP_NODE, &rfp_full_table_download_cmd); +} + +/*********************************************************************** + * RFAPI Callbacks + **********************************************************************/ + +/*------------------------------------------ + * rfp_response_cb + * + * Callbacks of this type are used to provide asynchronous + * route updates from RFAPI to the RFP client. + * + * response_cb + * called to notify the rfp client that a next hop list + * that has previously been provided in response to an + * rfapi_query call has been updated. Deleted routes are indicated + * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. + * + * By default, the routes an NVE receives via this callback include + * its own routes (that it has registered). However, these may be + * filtered out if the global BGP_VNC_CONFIG_FILTER_SELF_FROM_RSP + * flag is set. + * + * input: + * next_hops a list of possible next hops. + * This is a linked list allocated within the + * rfapi. The response_cb callback function is responsible + * for freeing this memory via rfapi_free_next_hop_list() + * in order to avoid memory leaks. + * + * userdata value (cookie) originally specified in call to + * rfapi_open() + * + *------------------------------------------*/ +static void rfp_response_cb(struct rfapi_next_hop_entry *next_hops, + void *userdata) +{ + /* + * Identify NVE based on userdata, which is a value passed + * to RFAPI in the rfapi_open call + */ + + /* process list of next_hops */ + + /* free next hops */ + rfapi_free_next_hop_list(next_hops); + return; +} + +/*------------------------------------------ + * rfp_local_cb + * + * Callbacks of this type are used to provide asynchronous + * route updates from RFAPI to the RFP client. + * + * local_cb + * called to notify the rfp client that a local route + * has been added or deleted. Deleted routes are indicated + * with lifetime==RFAPI_REMOVE_RESPONSE_LIFETIME. + * + * input: + * next_hops a list of possible next hops. + * This is a linked list allocated within the + * rfapi. The local_cb callback function is responsible + * for freeing this memory via rfapi_free_next_hop_list() + * in order to avoid memory leaks. + * + * userdata value (cookie) originally specified in call to + * rfapi_open() + * + *------------------------------------------*/ +static void rfp_local_cb(struct rfapi_next_hop_entry *next_hops, void *userdata) +{ + /* + * Identify NVE based on userdata, which is a value passed + * to RFAPI in the rfapi_open call + */ + + /* process list of local next_hops */ + + /* free next hops */ + rfapi_free_next_hop_list(next_hops); + return; +} + +/*------------------------------------------ + * rfp_close_cb + * + * Callbacks used to provide asynchronous + * notification that an rfapi_handle was invalidated + * + * input: + * pHandle Firmerly valid rfapi_handle returned to + * client via rfapi_open(). + * + * reason EIDRM handle administratively closed (clear nve ...) + * ESTALE handle invalidated by configuration change + * + *------------------------------------------*/ +static void rfp_close_cb(rfapi_handle pHandle, int reason) +{ + /* close / invalidate NVE with the pHandle returned by the rfapi_open + * call */ + return; +} + +/*------------------------------------------ + * rfp_cfg_write_cb + * + * This callback is used to generate output for any config parameters + * that may supported by RFP via RFP defined vty commands at the bgp + * level. See loglevel as an example. + * + * input: + * vty -- quagga vty context + * rfp_start_val -- value returned by rfp_start + * + * output: + * to vty, rfp related configuration + * + * return value: + * lines written +--------------------------------------------*/ +static int rfp_cfg_write_cb(struct vty *vty, void *rfp_start_val) +{ + struct rfp_instance_t *rfi = rfp_start_val; + int write = 0; + assert(rfp_start_val != NULL); + if (rfi->config_var != 0) { + vty_out(vty, " rfp example-config-value %u", rfi->config_var); + vty_out(vty, "\n"); + write++; + } + if (rfi->rfapi_config.holddown_factor != 0) { + vty_out(vty, " rfp holddown-factor %u\n", + rfi->rfapi_config.holddown_factor); + write++; + } + if (rfi->rfapi_config.download_type == RFAPI_RFP_DOWNLOAD_FULL) { + vty_out(vty, " rfp full-table-download on\n"); + write++; + } + return write; +} + +/*********************************************************************** + * RFAPI required functions + **********************************************************************/ + +/*------------------------------------------ + * rfp_start + * + * This function will start the RFP code + * + * input: + * master quagga thread_master to tie into bgpd threads + * + * output: + * cfgp Pointer to rfapi_rfp_cfg (null = use defaults), + * copied by caller, updated via rfp_set_configuration + * cbmp Pointer to rfapi_rfp_cb_methods, may be null + * copied by caller, updated via rfapi_rfp_set_cb_methods + * + * return value: + * rfp_start_val rfp returned value passed on rfp_stop and rfp_cfg_write + * +--------------------------------------------*/ +void *rfp_start(struct event_loop *master, struct rfapi_rfp_cfg **cfgp, + struct rfapi_rfp_cb_methods **cbmp) +{ + memset(&global_rfi, 0, sizeof(global_rfi)); + global_rfi.master = master; /* for BGPD threads */ + + /* initilize struct rfapi_rfp_cfg, see rfapi.h */ + global_rfi.rfapi_config.download_type = + RFAPI_RFP_DOWNLOAD_PARTIAL; /* default=partial */ + global_rfi.rfapi_config.ftd_advertisement_interval = + RFAPI_RFP_CFG_DEFAULT_FTD_ADVERTISEMENT_INTERVAL; + global_rfi.rfapi_config.holddown_factor = + 0; /* default: RFAPI_RFP_CFG_DEFAULT_HOLDDOWN_FACTOR */ + global_rfi.rfapi_config.use_updated_response = 1; /* 0=no */ + global_rfi.rfapi_config.use_removes = 1; /* 0=no */ + + + /* initilize structrfapi_rfp_cb_methods , see rfapi.h */ + global_rfi.rfapi_callbacks.cfg_cb = rfp_cfg_write_cb; + /* no group config */ + global_rfi.rfapi_callbacks.response_cb = rfp_response_cb; + global_rfi.rfapi_callbacks.local_cb = rfp_local_cb; + global_rfi.rfapi_callbacks.close_cb = rfp_close_cb; + + if (cfgp != NULL) + *cfgp = &global_rfi.rfapi_config; + if (cbmp != NULL) + *cbmp = &global_rfi.rfapi_callbacks; + + rfp_vty_install(); + + return &global_rfi; +} + +/*------------------------------------------ + * rfp_stop + * + * This function is called on shutdown to trigger RFP cleanup + * + * input: + * none + * + * output: + * none + * + * return value: + * rfp_start_val +--------------------------------------------*/ +void rfp_stop(void *rfp_start_val) +{ + assert(rfp_start_val != NULL); +} + +/* TO BE REMOVED */ +void rfp_clear_vnc_nve_all(void) +{ + return; +} diff --git a/bgpd/rfp-example/librfp/rfp_internal.h b/bgpd/rfp-example/librfp/rfp_internal.h new file mode 100644 index 0000000..83b0b55 --- /dev/null +++ b/bgpd/rfp-example/librfp/rfp_internal.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2015-2016, LabN Consulting, L.L.C. + * + */ + +/* Sample header file */ +#ifndef _RFP_INTERNAL_H +#define _RFP_INTERNAL_H +#include "lib/zebra.h" +#include "rfp.h" +#include "bgpd/rfapi/rfapi.h" + +#endif /* _RFP_INTERNAL_H */ diff --git a/bgpd/rfp-example/librfp/subdir.am b/bgpd/rfp-example/librfp/subdir.am new file mode 100644 index 0000000..254ab71 --- /dev/null +++ b/bgpd/rfp-example/librfp/subdir.am @@ -0,0 +1,17 @@ +# +# librfp +# + +if ENABLE_BGP_VNC +noinst_LIBRARIES += bgpd/rfp-example/librfp/librfp.a +RFPLDADD = bgpd/rfp-example/librfp/librfp.a +endif + +bgpd_rfp_example_librfp_librfp_a_SOURCES = \ + bgpd/rfp-example/librfp/rfp_example.c \ + # end + +noinst_HEADERS += \ + bgpd/rfp-example/librfp/rfp.h \ + bgpd/rfp-example/librfp/rfp_internal.h \ + # end diff --git a/bgpd/rfp-example/rfptest/.gitignore b/bgpd/rfp-example/rfptest/.gitignore new file mode 100644 index 0000000..d3d7c0a --- /dev/null +++ b/bgpd/rfp-example/rfptest/.gitignore @@ -0,0 +1 @@ +/rfptest diff --git a/bgpd/rfp-example/rfptest/Makefile b/bgpd/rfp-example/rfptest/Makefile new file mode 100644 index 0000000..659a9ce --- /dev/null +++ b/bgpd/rfp-example/rfptest/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C ../../.. bgpd/rfp-example/rfptest/rfptest +%: ALWAYS + @$(MAKE) -s -C ../../.. bgpd/rfp-example/rfptest/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/bgpd/rfp-example/rfptest/rfptest.c b/bgpd/rfp-example/rfptest/rfptest.c new file mode 100644 index 0000000..e57f8b0 --- /dev/null +++ b/bgpd/rfp-example/rfptest/rfptest.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2015-2016, LabN Consulting, L.L.C. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* dummy test program */ +#include +#include +#include "rfptest.h" +int main(void) +{ + printf("Your test code goes here.\n"); + exit(1); +} diff --git a/bgpd/rfp-example/rfptest/rfptest.h b/bgpd/rfp-example/rfptest/rfptest.h new file mode 100644 index 0000000..66f70ae --- /dev/null +++ b/bgpd/rfp-example/rfptest/rfptest.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright 2015-2016, LabN Consulting, L.L.C. + * + */ + +/* Sample header file */ +#ifndef _RFPTEST_H +#define _RFPTEST_H + +#endif /* _RFPTEST_H */ diff --git a/bgpd/rfp-example/rfptest/subdir.am b/bgpd/rfp-example/rfptest/subdir.am new file mode 100644 index 0000000..1b5024a --- /dev/null +++ b/bgpd/rfp-example/rfptest/subdir.am @@ -0,0 +1,23 @@ +# +# libtest +# + +if ENABLE_BGP_VNC +noinst_PROGRAMS += bgpd/rfp-example/rfptest/rfptest +endif + +bgpd_rfp_example_rfptest_rfptest_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(top_srcdir)/bgpd/rfapi \ + # end +bgpd_rfp_example_rfptest_rfptest_SOURCES = \ + bgpd/rfp-example/rfptest/rfptest.c \ + # end +noinst_HEADERS += \ + bgpd/rfp-example/rfptest/rfptest.h \ + # end + +bgpd_rfp_example_rfptest_rfptest_LDADD = \ + lib/libfrr.la \ + $(RFPLDADD) \ + # end diff --git a/bgpd/subdir.am b/bgpd/subdir.am new file mode 100644 index 0000000..6d6fad0 --- /dev/null +++ b/bgpd/subdir.am @@ -0,0 +1,235 @@ +# +# bgpd +# + +if BGPD +noinst_LIBRARIES += bgpd/libbgp.a +sbin_PROGRAMS += bgpd/bgpd +noinst_PROGRAMS += bgpd/bgp_btoa + +vtysh_daemons += bgpd + +if SNMP +module_LTLIBRARIES += bgpd/bgpd_snmp.la +endif +if RPKI +module_LTLIBRARIES += bgpd/bgpd_rpki.la +endif +if BGP_BMP +module_LTLIBRARIES += bgpd/bgpd_bmp.la +endif +man8 += $(MANBUILD)/frr-bgpd.8 +endif + +bgpd_libbgp_a_SOURCES = \ + bgpd/bgp_addpath.c \ + bgpd/bgp_advertise.c \ + bgpd/bgp_aspath.c \ + bgpd/bgp_attr.c \ + bgpd/bgp_attr_evpn.c \ + bgpd/bgp_bfd.c \ + bgpd/bgp_clist.c \ + bgpd/bgp_community.c \ + bgpd/bgp_community_alias.c \ + bgpd/bgp_conditional_adv.c \ + bgpd/bgp_damp.c \ + bgpd/bgp_debug.c \ + bgpd/bgp_dump.c \ + bgpd/bgp_ecommunity.c \ + bgpd/bgp_encap_tlv.c \ + bgpd/bgp_errors.c \ + bgpd/bgp_evpn.c \ + bgpd/bgp_evpn_mh.c \ + bgpd/bgp_evpn_vty.c \ + bgpd/bgp_filter.c \ + bgpd/bgp_flowspec.c \ + bgpd/bgp_flowspec_util.c \ + bgpd/bgp_flowspec_vty.c \ + bgpd/bgp_fsm.c \ + bgpd/bgp_io.c \ + bgpd/bgp_keepalives.c \ + bgpd/bgp_label.c \ + bgpd/bgp_labelpool.c \ + bgpd/bgp_lcommunity.c \ + bgpd/bgp_mac.c \ + bgpd/bgp_memory.c \ + bgpd/bgp_mpath.c \ + bgpd/bgp_mplsvpn.c \ + bgpd/bgp_network.c \ + bgpd/bgp_nexthop.c \ + bgpd/bgp_nht.c \ + bgpd/bgp_open.c \ + bgpd/bgp_packet.c \ + bgpd/bgp_pbr.c \ + bgpd/bgp_rd.c \ + bgpd/bgp_regex.c \ + bgpd/bgp_route.c \ + bgpd/bgp_routemap.c \ + bgpd/bgp_routemap_nb.c \ + bgpd/bgp_routemap_nb_config.c \ + bgpd/bgp_script.c \ + bgpd/bgp_table.c \ + bgpd/bgp_updgrp.c \ + bgpd/bgp_updgrp_adv.c \ + bgpd/bgp_updgrp_packet.c \ + bgpd/bgp_vpn.c \ + bgpd/bgp_vty.c \ + bgpd/bgp_zebra.c \ + bgpd/bgpd.c \ + bgpd/bgp_trace.c \ + bgpd/bgp_nhg.c \ + # end + +if ENABLE_BGP_VNC +bgpd_libbgp_a_SOURCES += \ + bgpd/rfapi/bgp_rfapi_cfg.c \ + bgpd/rfapi/rfapi_import.c \ + bgpd/rfapi/rfapi.c \ + bgpd/rfapi/rfapi_ap.c \ + bgpd/rfapi/rfapi_descriptor_rfp_utils.c \ + bgpd/rfapi/rfapi_encap_tlv.c \ + bgpd/rfapi/rfapi_nve_addr.c \ + bgpd/rfapi/rfapi_monitor.c \ + bgpd/rfapi/rfapi_rib.c \ + bgpd/rfapi/rfapi_vty.c \ + bgpd/rfapi/vnc_debug.c \ + bgpd/rfapi/vnc_export_bgp.c \ + bgpd/rfapi/vnc_export_table.c \ + bgpd/rfapi/vnc_import_bgp.c \ + bgpd/rfapi/vnc_zebra.c \ + # end +endif + +noinst_HEADERS += \ + bgpd/bgp_addpath.h \ + bgpd/bgp_addpath_types.h \ + bgpd/bgp_advertise.h \ + bgpd/bgp_aspath.h \ + bgpd/bgp_attr.h \ + bgpd/bgp_attr_evpn.h \ + bgpd/bgp_bfd.h \ + bgpd/bgp_clist.h \ + bgpd/bgp_community.h \ + bgpd/bgp_community_alias.h \ + bgpd/bgp_conditional_adv.h \ + bgpd/bgp_damp.h \ + bgpd/bgp_debug.h \ + bgpd/bgp_dump.h \ + bgpd/bgp_bmp.h \ + bgpd/bgp_ecommunity.h \ + bgpd/bgp_encap_tlv.h \ + bgpd/bgp_encap_types.h \ + bgpd/bgp_errors.h \ + bgpd/bgp_evpn.h \ + bgpd/bgp_evpn_mh.h \ + bgpd/bgp_evpn_private.h \ + bgpd/bgp_evpn_vty.h \ + bgpd/bgp_filter.h \ + bgpd/bgp_flowspec.h \ + bgpd/bgp_flowspec_private.h \ + bgpd/bgp_flowspec_util.h \ + bgpd/bgp_fsm.h \ + bgpd/bgp_io.h \ + bgpd/bgp_keepalives.h \ + bgpd/bgp_label.h \ + bgpd/bgp_labelpool.h \ + bgpd/bgp_lcommunity.h \ + bgpd/bgp_mac.h \ + bgpd/bgp_memory.h \ + bgpd/bgp_mpath.h \ + bgpd/bgp_mplsvpn.h \ + bgpd/bgp_mplsvpn_snmp.h \ + bgpd/bgp_network.h \ + bgpd/bgp_nexthop.h \ + bgpd/bgp_nht.h \ + bgpd/bgp_open.h \ + bgpd/bgp_packet.h \ + bgpd/bgp_pbr.h \ + bgpd/bgp_rd.h \ + bgpd/bgp_regex.h \ + bgpd/bgp_rpki.h \ + bgpd/bgp_route.h \ + bgpd/bgp_routemap_nb.h \ + bgpd/bgp_script.h \ + bgpd/bgp_snmp.h \ + bgpd/bgp_snmp_bgp4.h \ + bgpd/bgp_snmp_bgp4v2.h \ + bgpd/bgp_table.h \ + bgpd/bgp_updgrp.h \ + bgpd/bgp_vpn.h \ + bgpd/bgp_vty.h \ + bgpd/bgp_zebra.h \ + bgpd/bgpd.h \ + bgpd/bgp_trace.h \ + bgpd/bgp_nhg.h \ + \ + bgpd/rfapi/bgp_rfapi_cfg.h \ + bgpd/rfapi/rfapi_import.h \ + bgpd/rfapi/rfapi.h \ + bgpd/rfapi/rfapi_ap.h \ + bgpd/rfapi/rfapi_backend.h \ + bgpd/rfapi/rfapi_descriptor_rfp_utils.h \ + bgpd/rfapi/rfapi_encap_tlv.h \ + bgpd/rfapi/rfapi_nve_addr.h \ + bgpd/rfapi/rfapi_monitor.h \ + bgpd/rfapi/rfapi_private.h \ + bgpd/rfapi/rfapi_rib.h \ + bgpd/rfapi/rfapi_vty.h \ + bgpd/rfapi/vnc_debug.h \ + bgpd/rfapi/vnc_export_bgp.h \ + bgpd/rfapi/vnc_export_table.h \ + bgpd/rfapi/vnc_import_bgp.h \ + bgpd/rfapi/vnc_zebra.h \ + bgpd/rfapi/vnc_export_bgp_p.h \ + bgpd/rfapi/vnc_import_bgp_p.h \ + bgpd/bgp_vnc_types.h \ + # end + +bgpd_bgpd_SOURCES = bgpd/bgp_main.c +bgpd_bgp_btoa_SOURCES = bgpd/bgp_btoa.c + +# RFPLDADD is set in bgpd/rfp-example/librfp/subdir.am +bgpd_bgpd_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBYANG_LIBS) $(LIBCAP) $(LIBM) $(UST_LIBS) +bgpd_bgp_btoa_LDADD = bgpd/libbgp.a $(RFPLDADD) lib/libfrr.la $(LIBYANG_LIBS) $(LIBCAP) $(LIBM) $(UST_LIBS) + +bgpd_bgpd_snmp_la_SOURCES = bgpd/bgp_snmp_bgp4.c bgpd/bgp_snmp_bgp4v2.c bgpd/bgp_snmp.c bgpd/bgp_mplsvpn_snmp.c +bgpd_bgpd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +bgpd_bgpd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +bgpd_bgpd_snmp_la_LIBADD = lib/libfrrsnmp.la + +bgpd_bgpd_rpki_la_SOURCES = bgpd/bgp_rpki.c +bgpd_bgpd_rpki_la_CFLAGS = $(AM_CFLAGS) $(RTRLIB_CFLAGS) +bgpd_bgpd_rpki_la_LDFLAGS = $(MODULE_LDFLAGS) +bgpd_bgpd_rpki_la_LIBADD = $(RTRLIB_LIBS) + +bgpd_bgpd_bmp_la_SOURCES = bgpd/bgp_bmp.c +bgpd_bgpd_bmp_la_LIBADD = lib/libfrrcares.la +bgpd_bgpd_bmp_la_LDFLAGS = $(MODULE_LDFLAGS) + +clippy_scan += \ + bgpd/bgp_bmp.c \ + bgpd/bgp_debug.c \ + bgpd/bgp_evpn_vty.c \ + bgpd/bgp_labelpool.c \ + bgpd/bgp_route.c \ + bgpd/bgp_routemap.c \ + bgpd/bgp_rpki.c \ + bgpd/bgp_vty.c \ + bgpd/bgp_nexthop.c \ + bgpd/bgp_snmp.c \ + # end + +nodist_bgpd_bgpd_SOURCES = \ + yang/frr-bgp-types.yang.c \ + yang/frr-bgp.yang.c \ + yang/frr-bgp-common-structure.yang.c \ + yang/frr-bgp-common.yang.c \ + yang/frr-bgp-common-multiprotocol.yang.c \ + yang/frr-bgp-neighbor.yang.c \ + yang/frr-bgp-peer-group.yang.c \ + yang/frr-bgp-bmp.yang.c \ + yang/frr-bgp-rpki.yang.c \ + yang/frr-deviations-bgp-datacenter.yang.c \ + yang/frr-bgp-filter.yang.c \ + yang/frr-bgp-route-map.yang.c \ + # end diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..212e7d1 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# This file exists to document the proper way to initialize autotools, +# and so that those used to the presence of bootstrap.sh or autogen.sh +# will have an eaiser time. + +exec autoreconf -is -Wall,no-override diff --git a/buildtest.sh b/buildtest.sh new file mode 100755 index 0000000..90ef60e --- /dev/null +++ b/buildtest.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# SPDX-License-Identifier: NONE +# written 2012-2013 by David Lamparter, placed in Public Domain. +# +# builds some git commit of FRR in some different configurations +# usage: buildtest.sh [commit [configurations...]] + +basecfg="--prefix=/usr --enable-user=frr --enable-group=frr --enable-vty-group=frr --enable-configfile-mask=0660 --enable-logfile-mask=0640 --enable-vtysh --sysconfdir=/etc --localstatedir=/var --libdir=/usr/lib64/frr --enable-rtadv --disable-static --enable-isisd --enable-multipath=0 --enable-pimd --enable-werror" + +configs_base="gcc|$basecfg" + +configs_ext="gcc|$basecfg --enable-opaque-lsa --enable-ospf-te --enable-ospfclient --enable-isis-topology" +configs_snmp="gcc|$basecfg --enable-opaque-lsa --enable-ospf-te --enable-ospfclient --enable-isis-topology --enable-snmp" +configs_clang="clang|$basecfg --enable-opaque-lsa --enable-ospf-te --enable-ospfclient --enable-isis-topology" +configs_icc="icc|$basecfg --enable-opaque-lsa --enable-ospf-te --enable-ospfclient --enable-isis-topology" + +defconfigs="base ext" +net-snmp-config --version &> /dev/null && defconfigs="$defconfigs snmp" +clang --version &> /dev/null && defconfigs="$defconfigs clang" +icc --version &> /dev/null && defconfigs="$defconfigs icc" + +echo "enabled configurations: $defconfigs" + +cc_gcc="CC=gcc; export CC" +cc_clang="CC=clang; export CC" +cc_icc="CC=icc; export CC" + +############################### + +errfunc() { + echo "something went wrong! check $TEMP" + exit 1 +} + +set -e +trap errfunc ERR + +COMMITREF="$1" +COMMITISH="`git rev-list --max-count=1 ${COMMITREF:-HEAD}`" +TEMP="`mktemp -d -t frrbuild.XXXXXX`" +BASE="`pwd`" +CONFIGS="$2" +MAKE="${MAKE:-make}" + +echo using temporary directory: $TEMP +echo git commit used: +git --no-pager log -n 1 --pretty=oneline "$COMMITISH" + +cd "$TEMP" +git clone "$BASE" "source" +cd "source" +git checkout -b build "$COMMITISH" +git clean -d -f -x +sh bootstrap.sh + +cd .. + +echo -e "\n\n\n\n\033[33;1mmaking dist tarball\033[m" + +mkdir build_dist +cd build_dist +../source/configure +${MAKE} distdir=sdist dist-gzip +cd .. +tar zxvf build_dist/sdist.tar.gz + +for cfg in ${CONFIGS:-$defconfigs}; do + echo -e "\n\n\n\n\033[33;1mbuilding configuration $cfg\033[m" + config="\${configs_$cfg}" + eval "config=$config" + + cc="${config%%|*}" + args="${config#*|}" + + ccset="\${cc_$cc}" + eval "ccset=$ccset" + eval "$ccset" + + bdir="build_$cfg" + mkdir "$bdir" + cd "$bdir" + ../sdist/configure $args + ${MAKE} -j5 + ${MAKE} check + ${MAKE} DESTDIR="$TEMP/inst_$cfg" install + cd .. +done + +echo -e "\n\n\n\neverything seems ok. you may now\n\trm -rf $TEMP" diff --git a/config.version.in b/config.version.in new file mode 100644 index 0000000..e2e739c --- /dev/null +++ b/config.version.in @@ -0,0 +1,4 @@ +# this file is used to carry --with-pkg-extra-version into tarballs +EXTRAVERSION="@EXTRAVERSION@" +# for easy access by scripts before ./configure is run +DIST_PACKAGE_VERSION="@PACKAGE_VERSION@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..1a5a260 --- /dev/null +++ b/configure.ac @@ -0,0 +1,2948 @@ +## +## Configure template file for FRRouting. +## autoconf will generate a configure script. +## +## Copyright (c) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro +## Portions Copyright (c) 2003 Paul Jakma +## +AC_PREREQ([2.69]) + +AC_INIT([frr], [10.1.1], [https://github.com/frrouting/frr/issues]) +PACKAGE_URL="https://frrouting.org/" +AC_SUBST([PACKAGE_URL]) +PACKAGE_FULLNAME="FRRouting" +AC_SUBST([PACKAGE_FULLNAME]) + +CONFIG_ARGS="`echo $ac_configure_args | sed -e \"s% '[[A-Z]]*FLAGS=[[^']]\+'%%g\"`" +AC_SUBST([CONFIG_ARGS]) + +AC_CONFIG_SRCDIR([lib/zebra.h]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_AUX_DIR([m4/ac]) + +dnl ------------------------------ +dnl system paths +dnl ------------------------------ +dnl Versions of FRR (or Quagga, or Zebra) before ca. 9.2 used sysconfdir and +dnl localstatedir as-is, without appending /frr. The /frr was expected to be +dnl given on ./configure invocations. +dnl +dnl This does not match standard behavior by other packages and makes FRR +dnl specific packaging changes necessary to add these options. localstatedir +dnl was also misused to include the /run part (it normally is only /var), +dnl leaving no path configuration option that references /var itself. This +dnl is because runstatedir did not exist in ancient autoconf. +dnl +dnl The path options have been changed to expect plain / system prefix +dnl directories. As a temporary workaround to not break packaging, eventual +dnl /frr suffixes are stripped and a warning is printed. + +path_warn_banner=false + +AC_MSG_CHECKING([whether --sysconfdir option is FRR-specific]) +case "$sysconfdir" in + */frr) + AC_MSG_RESULT([yes, ends in /frr - removing suffix]) + AC_MSG_WARN([Please remove /frr suffix from --sysconfdir="${sysconfdir}" (it should be /etc in 99% of cases)]) + sysconfdir="${sysconfdir%/frr}" + path_warn_banner=true + ;; + *) + AC_MSG_RESULT([no, as expected]) + ;; +esac + +frr_sysconfdir="\${sysconfdir}/frr" + +AC_MSG_CHECKING([whether --localstatedir option is FRR-specific]) +case "$localstatedir" in + */run/frr) + AC_MSG_RESULT([yes, ends in /run/frr - removing suffix]) + AC_MSG_WARN([Please remove /run/frr suffix from --localstatedir=${localstatedir} (it should be /var in 99% of cases)]) + localstatedir="${localstatedir%/run/frr}" + path_warn_banner=true + ;; + */frr) + AC_MSG_RESULT([yes, ends in /frr - removing suffix]) + AC_MSG_WARN([The --localstatedir=${localstatedir} option seems to include /frr but not /run, this is unexpected. Please check for consistency.)]) + localstatedir="${localstatedir%/frr}" + path_warn_banner=true + ;; + *) + AC_MSG_RESULT([no, as expected]) + ;; +esac + +dnl runstatedir is either ${localstatedir}/run or plain /run +dnl the change of localstatedir above may impact this +dnl +dnl note runstatedir was never used with /frr as the other two above, so does +dnl not need the same cleanup hack +: "${runstatedir:=\${localstatedir\}/run}" +frr_runstatedir="\${runstatedir}/frr" + +if $path_warn_banner; then + AC_MSG_WARN([^]) + AC_MSG_WARN([^]) + AC_MSG_WARN([^ warnings regarding system path configuration were printed above]) + AC_MSG_WARN([^ paths have been adjusted by temporary workarounds]) + AC_MSG_WARN([^ please fix your ./configure invocation (remove /frr) so it will work without the workarounds]) + AC_MSG_WARN([^]) + AC_MSG_WARN([^]) +fi + +frr_libstatedir="\${localstatedir}/lib/frr" + +dnl ----------------------------------- +dnl Get hostname and other information. +dnl ----------------------------------- +AC_CANONICAL_BUILD() +AC_CANONICAL_HOST() + +AC_ARG_VAR([AR],[archiver command]) +AC_ARG_VAR([LD],[linker command]) +AC_ARG_VAR([OBJCOPY],[objcopy command]) +AC_ARG_VAR([OBJDUMP],[objdump command]) +AC_ARG_VAR([RANLIB],[ranlib command]) +AC_ARG_VAR([STRIP],[strip command]) + +hosttools_clippy="false" +build_clippy="true" + +dnl case 1: external clippy +if test -n "$with_clippy" -a "$with_clippy" != "no" -a "$with_clippy" != "yes"; then + if test "$enable_clippy_only" = "yes"; then + AC_MSG_ERROR([--enable-clippy-only does not make sense with --with-clippy]) + fi + + CLIPPY="$with_clippy" + build_clippy="false" + if test ! -x "$with_clippy"; then + AC_MSG_ERROR([clippy tool ($with_clippy) is not executable]) + fi + +dnl case 2: cross-compiling internal clippy +elif test "$host" != "$build"; then + if test "$srcdir" = "."; then + AC_MSG_ERROR([cross-compilation is only possible with builddir separate from srcdir or by building clippy separately and using the --with-clippy option. create a separate directory and run as .../path-to-frr/configure.]) + fi + test -d hosttools || mkdir hosttools + abssrc="`cd \"${srcdir}\"; pwd`" + + AC_MSG_NOTICE([...]) + AC_MSG_NOTICE([... cross-compilation: creating hosttools directory and self-configuring for build platform tools]) + AC_MSG_NOTICE([... use HOST_CPPFLAGS / HOST_CFLAGS / HOST_LDFLAGS if necessary]) + AC_MSG_NOTICE([...]) + + ( + for var in $ac_precious_vars; do + dnl special cases + case "$var" in + YACC|YFLAGS) continue;; + PYTHON*) retain=true;; + *) retain=false; + esac + + eval "hostvar=\"\${HOST_$var}\"" + eval "targetvar=\"\${$var}\"" + if test -n "$hostvar"; then + eval "$var='$hostvar'" + _AS_ECHO_LOG([host $var='$hostvar']) + elif $retain; then + _AS_ECHO_LOG([host retain $var='$targetvar']) + else + eval "unset $var" + _AS_ECHO_LOG([host unset $var]) + fi + done + cd hosttools + "${abssrc}/configure" "--host=$build" "--build=$build" "--enable-clippy-only" "--disable-nhrpd" "--disable-vtysh" + ) || exit 1 + + AC_MSG_NOTICE([...]) + AC_MSG_NOTICE([... cross-compilation: finished self-configuring for build platform tools]) + AC_MSG_NOTICE([...]) + + build_clippy="false" + hosttools_clippy="true" + CLIPPY="hosttools/lib/clippy" + +dnl case 3: normal build internal clippy +else + CLIPPY="lib/clippy\$(EXEEXT)" +fi +AC_SUBST([CLIPPY]) +AM_CONDITIONAL([BUILD_CLIPPY], [$build_clippy]) +AM_CONDITIONAL([HOSTTOOLS_CLIPPY], [$hosttools_clippy]) +AM_CONDITIONAL([ONLY_CLIPPY], [test "$enable_clippy_only" = "yes"]) + +# Disable portability warnings -- our automake code (in particular +# common.am) uses some constructs specific to gmake. +AM_INIT_AUTOMAKE([1.12 -Wno-portability foreign]) +m4_ifndef([AM_SILENT_RULES], [m4_define([AM_SILENT_RULES],[])]) +AM_SILENT_RULES([yes]) +AC_CONFIG_HEADERS([config.h]) + +AC_PATH_PROG([PERL], [perl]) +PKG_PROG_PKG_CONFIG + +dnl default is to match previous behavior +pkgsrcrcdir="" +AC_ARG_ENABLE([pkgsrcrcdir], + AS_HELP_STRING([--enable-pkgsrcrcdir], + [specify directory for rc.d scripts]), + pkgsrcrcdir="$enableval",) +dnl XXX add --pkgsrcrcdir to autoconf standard directory list somehow +AC_SUBST([pkgsrcrcdir]) +AM_CONDITIONAL([PKGSRC], [test "$pkgsrcrcdir" != ""]) + +AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directory (${libdir}/frr/modules)])], [ + moduledir="$withval" +], [ + moduledir="\${libdir}/frr/modules" +]) +AC_SUBST([moduledir], [$moduledir]) + +AC_ARG_WITH([scriptdir], [AS_HELP_STRING([--with-scriptdir=DIR], [script directory (${sysconfdir}/frr/scripts)])], [ + scriptdir="$withval" +], [ + scriptdir="\${frr_sysconfdir}/scripts" +]) +AC_SUBST([scriptdir], [$scriptdir]) + +AC_ARG_WITH([yangmodelsdir], [AS_HELP_STRING([--with-yangmodelsdir=DIR], [yang models directory (${datarootdir}/yang)])], [ + yangmodelsdir="$withval" +], [ + yangmodelsdir="\${datarootdir}/yang" +]) +AC_SUBST([yangmodelsdir]) + +AC_ARG_WITH([vici-socket], [AS_HELP_STRING([--with-vici-socket=PATH], [vici-socket (/var/run/charon.vici)])], [ + vici_socket="$withval" +], [ + vici_socket="/var/run/charon.vici" +]) +AC_DEFINE_UNQUOTED([VICI_SOCKET], ["$vici_socket"], [StrongSWAN vici socket path]) + +AC_ARG_ENABLE(tcmalloc, + AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]), +[case "${enableval}" in + yes) tcmalloc_enabled=true +LIBS="$LIBS -ltcmalloc_minimal" + ;; + no) tcmalloc_enabled=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-tcmalloc]) ;; +esac],[tcmalloc_enabled=false]) + + +dnl Thanks autoconf, but we don't want a default -g -O2. We have our own +dnl flag determination logic. +CFLAGS="${CFLAGS:-}" + +dnl -------------------- +dnl Check CC and friends +dnl -------------------- +dnl note orig_cflags is also used further down +orig_cflags="$CFLAGS" +orig_cxxflags="$CXXFLAGS" +AC_LANG([C]) +AC_PROG_CC +AC_PROG_CPP +AC_PROG_CXX +AM_PROG_CC_C_O +dnl remove autoconf default "-g -O2" +CFLAGS="$orig_cflags" +CXXFLAGS="$orig_cxxflags" + +dnl Some special handling for ICC later on +if test "$CC" = "icc"; then + cc_is_icc="yes" +fi + +PKG_PROG_PKG_CONFIG + +dnl it's 2019, sed is sed. +SED=sed +AC_SUBST([SED]) + +dnl try and enable CFLAGS that are useful for FRR +dnl - specifically, options to control warnings + +AC_SUBST([AC_CFLAGS]) +AC_USE_SYSTEM_EXTENSIONS +AC_DEFUN([AC_C_FLAG], [{ + m4_pushdef([cachename],[m4_translit([frr_cv_$1],[ =-+/{}$],[________])]) + AC_CACHE_CHECK([[whether $CC supports $1]], cachename, [ + AC_LANG_PUSH([C]) + ac_c_flag_save="$CFLAGS" + dnl GCC ignores unknown -Wno-whatever flags, but errors on -Wwhatever + dnl except when it ignores them it prints: + dnl cc1: note: unrecognized command-line option ‘-Wno-whatever’ may have been intended to silence earlier diagnostics + dnl which is annoying as hell. So check for the positive flag instead. + flag_add="$1" + if test "$flag_add" != "${flag_add#-Wno-}"; then + CFLAGS="$CFLAGS -W${flag_add#-Wno-}" + else + CFLAGS="$CFLAGS $flag_add" + fi + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[$4]])], + [ + cachename=yes + ], [ + cachename=no + ]) + CFLAGS="$ac_c_flag_save" + AC_LANG_POP([C]) + ]) + if test "$cachename" = "yes"; then + m4_if([$3], [], [AC_CFLAGS="$AC_CFLAGS $1"], [$3]) + else + : + $2 + fi + m4_popdef([cachename]) +}]) + +AC_DEFUN([AC_LINK_IFELSE_FLAGS], [{ + AC_LANG_PUSH([C]) + ac_cflags_save="$CFLAGS" + ac_libs_save="$LIBS" + CFLAGS="$CFLAGS $1" + LIBS="$LIBS $2" + AC_LINK_IFELSE( + [$3], + [ + CFLAGS="$ac_cflags_save" + LIBS="$ac_libs_save" + m4_default([$5], [ + AC_MSG_RESULT([yes]) + ]) + ], [ + CFLAGS="$ac_cflags_save" + LIBS="$ac_libs_save" + m4_default([$4], [ + AC_MSG_RESULT([no]) + ]) + ]) + AC_LANG_POP([C]) + }]) + +dnl ICC won't bail on unknown options without -diag-error 10006 +dnl need to do this first so we get useful results for the other options +if test "$cc_is_icc" = "yes"; then + AC_C_FLAG([-diag-error 10006]) +fi + +dnl autoconf 2.69 AC_PROG_CC_C99 is "state of the art" +dnl autoconf 2.70 AC_PROG_CC_C99 is deprecated and AC_PROC_CC tries to do C11 +m4_if(m4_version_compare(m4_defn([AC_AUTOCONF_VERSION]), [2.70]), [-1], [dnl + dnl autoconf < 2.70 + AC_PROG_CC_C99 + + dnl AC_PROG_CC_C99 may change CC to include -std=gnu99 or something + ac_cc="$CC" + CC="${CC% -std=gnu99}" + CC="${CC% -std=c99}" + + AC_C_FLAG([-std=gnu11], [CC="$ac_cc"], [CC="$CC -std=gnu11"]) +], [ + dnl autoconf >= 2.70 + if test "$ac_cv_prog_cc_c11" = "no"; then + AC_MSG_ERROR([ISO C11 compiler support (with GNU extensions) is required.]) + fi +]) + +dnl if the user has specified any CFLAGS, override our settings +if test "$enable_gcov" = "yes"; then + if test "$orig_cflags" = ""; then + AC_C_FLAG([--coverage]) + AC_C_FLAG([-O0]) + fi + + AC_LDFLAGS="${AC_LDFLAGS} --coverage" +fi + +if test "$enable_clang_coverage" = "yes"; then + AC_C_FLAG([-fprofile-instr-generate], [ + AC_MSG_ERROR([$CC does not support -fprofile-instr-generate.]) + ]) + AC_C_FLAG([-fcoverage-mapping], [ + AC_MSG_ERROR([$CC does not support -fcoverage-mapping.]) + ]) +fi + +AM_CONDITIONAL([SCRIPTING], [test "$enable_scripting" = "yes"]) + +if test "$enable_scripting" = "yes"; then + AX_PROG_LUA([5.3], [5.4], [], [ + AC_MSG_ERROR([Lua 5.3 is required to build with Lua support. No other version is supported.]) + ]) + AX_LUA_HEADERS([], [ + AC_MSG_ERROR([Lua 5.3 headers are required to build with Lua support. No other version is supported.]) + ]) + PKG_CHECK_MODULES([LUA], [lua5.3], [ + AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting]) + LIBS="$LIBS $LUA_LIBS" + SCRIPTING=true + ], [ + AX_LUA_LIBS([ + AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting]) + LIBS="$LIBS $LUA_LIB" + SCRIPTING=true + ], [ + SCRIPTING=false + AC_MSG_ERROR([Lua 5.3 libraries are required to build with Lua support. No other version is supported.]) + ]) + ]) +fi + +dnl the following flags go in CFLAGS rather than AC_CFLAGS since they make +dnl sense to be overridden by the user +if test "$enable_dev_build" = "yes"; then + AC_DEFINE([DEV_BUILD], [1], [Build for development]) + if test "$orig_cflags" = ""; then + AC_C_FLAG([-O0],,[CFLAGS="$CFLAGS -O0"]) + AC_C_FLAG([-g3],,[CFLAGS="$CFLAGS -g3"]) + AC_C_FLAG([-ggdb3],,[CFLAGS="$CFLAGS -ggdb3"]) + fi +else + if test "$orig_cflags" = ""; then + AC_C_FLAG([-g],,[CFLAGS="$CFLAGS -g"]) + AC_C_FLAG([-O2],,[CFLAGS="$CFLAGS -O2"]) + fi +fi + +dnl just stick -g into LDFLAGS, if we don't have it in CFLAGS it won't do much +LDFLAGS="$LDFLAGS -g" + +AM_CONDITIONAL([DEV_BUILD], [test "$enable_dev_build" = "yes"]) + +dnl -fms-extensions causes clang to have a built-in __wchar_t on OpenBSD, +dnl which just straight up breaks compiling any code. +dnl (2022-04-04 / OpenBSD 7 / clang 11.1.0) +AH_VERBATIM([OpenBSD], [ +#ifdef __OpenBSD__ +#define __wchar_t __wchar_t_ignore +#include +#undef __wchar_t +#endif +]) + +dnl always want these CFLAGS +AC_C_FLAG([-fms-extensions], [ + AC_MSG_ERROR([$CC does not support unnamed struct fields (-fms-extensions)]) +]) +AC_C_FLAG([-fno-omit-frame-pointer]) +AC_C_FLAG([-funwind-tables]) +AC_C_FLAG([-Wall]) +AC_C_FLAG([-Wextra]) +AC_C_FLAG([-Wformat-nonliteral]) +AC_C_FLAG([-Wformat-security]) +AC_C_FLAG([-Wswitch-enum]) +AC_C_FLAG([-Wstrict-prototypes]) +AC_C_FLAG([-Wmissing-prototypes]) +AC_C_FLAG([-Wmissing-declarations]) +AC_C_FLAG([-Wpointer-arith]) +AC_C_FLAG([-Wbad-function-cast]) +AC_C_FLAG([-Wwrite-strings]) +AC_C_FLAG([-Wundef]) +AC_C_FLAG([-Wimplicit-fallthrough]) +if test "$enable_gcc_ultra_verbose" = "yes" ; then + AC_C_FLAG([-Wcast-qual]) + AC_C_FLAG([-Wmissing-noreturn]) + AC_C_FLAG([-Wmissing-format-attribute]) + AC_C_FLAG([-Wunreachable-code]) + AC_C_FLAG([-Wpacked]) + AC_C_FLAG([-Wpadded]) + AC_C_FLAG([-Wshadow]) +else + AC_C_FLAG([-Wno-unused-result]) +fi +AC_C_FLAG([-Wno-unused-parameter]) +AC_C_FLAG([-Wno-missing-field-initializers]) +AC_C_FLAG([-Wno-microsoft-anon-tag]) +AC_C_FLAG([-Wno-error=deprecated-declarations]) + +AC_C_FLAG([-Wc++-compat], [], [CXX_COMPAT_CFLAGS="-Wc++-compat"]) +AC_SUBST([CXX_COMPAT_CFLAGS]) + +dnl ICC emits a broken warning for const char *x = a ? "b" : "c"; +dnl for some reason the string consts get 'promoted' to char *, +dnl triggering a const to non-const conversion warning. +if test "$cc_is_icc" = "yes"; then + AC_C_FLAG([-diag-disable 3179]) +fi + +if test "$enable_werror" = "yes" ; then + WERROR="-Werror" +fi +AC_SUBST([WERROR]) + +SAN_FLAGS="" +if test "$enable_address_sanitizer" = "yes"; then + AC_C_FLAG([-fsanitize=address], [ + AC_MSG_ERROR([$CC does not support Address Sanitizer.]) + ], [ + SAN_FLAGS="$SAN_FLAGS -fsanitize=address" + ]) +fi +if test "$enable_thread_sanitizer" = "yes"; then + AC_C_FLAG([-fsanitize=thread], [ + AC_MSG_ERROR([$CC does not support Thread Sanitizer.]) + ], [ + SAN_FLAGS="$SAN_FLAGS -fsanitize=thread" + ]) +fi +if test "$enable_memory_sanitizer" = "yes"; then + AC_C_FLAG([-fsanitize=memory -fPIE -pie], [ + AC_MSG_ERROR([$CC does not support Memory Sanitizer.]) + ], [ + SAN_FLAGS="$SAN_FLAGS -fsanitize=memory -fPIE -pie" + ]) +fi +if test "$enable_undefined_sanitizer" = "yes"; then + AC_C_FLAG([-fsanitize=undefined], [ + AC_MSG_ERROR([$CC does not support UndefinedBehaviorSanitizer.]) + ], [ + SAN_FLAGS="$SAN_FLAGS -fsanitize=undefined" + ]) +fi +AC_SUBST([SAN_FLAGS]) + +dnl frr-format.so +if test "$with_frr_format" != "no" -a "$with_frr_format" != "yes" -a -n "$with_frr_format"; then + AC_C_FLAG([-fplugin=${with_frr_format}], [ + AC_MSG_ERROR([specified frr-format plugin ($with_frr_format) does not work]) + ],,[ +#ifndef _FRR_ATTRIBUTE_PRINTFRR +#error plugin not loaded +#endif +#if _FRR_ATTRIBUTE_PRINTFRR < 0x10000 +#error plugin too old +#endif + ]) +elif test "$with_frr_format" = "no"; then + : #nothing +else + AC_C_FLAG([-fplugin=tools/gcc-plugins/frr-format.so],[ + AC_C_FLAG([-fplugin=frr-format],[ + if test "$with_frr_format" = "yes"; then + AC_MSG_ERROR([frr-format plugin requested but not found]) + fi + ],,[ +#ifndef _FRR_ATTRIBUTE_PRINTFRR +#error plugin not loaded +#endif +#if _FRR_ATTRIBUTE_PRINTFRR < 0x10000 +#error plugin too old +#endif + ]) + ],,[ +#ifndef _FRR_ATTRIBUTE_PRINTFRR +#error plugin not loaded +#endif +#if _FRR_ATTRIBUTE_PRINTFRR < 0x10000 +#error plugin too old +#endif + ]) +fi + +AC_MSG_CHECKING([whether linker supports __start/stop_section symbols]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include +int __attribute__((section("secttest"))) var = 1; +extern int __start_secttest, __stop_secttest; +]], [[ + void *a = &var, *b = &__start_secttest, *c = &__stop_secttest; + printf("%p %p %p\n", a, b, c); +]])], [ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_SECTION_SYMS, 1, [have __start/stop_section symbols]) +], [ + AC_MSG_RESULT(no) +]) + +dnl ---------- +dnl Essentials +dnl ---------- + +AX_PTHREAD([ + CC="$PTHREAD_CC" + AC_CFLAGS="$AC_CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" +], [ + AC_MSG_FAILURE([This FRR version needs pthreads]) +]) + +orig_cflags="$CFLAGS" +CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + +AC_SEARCH_LIBS([pthread_condattr_setclock], [], + [frr_cv_pthread_condattr_setclock=yes], + [frr_cv_pthread_condattr_setclock=no]) +if test "$frr_cv_pthread_condattr_setclock" = "yes"; then + AC_DEFINE([HAVE_PTHREAD_CONDATTR_SETCLOCK], [1], [Have pthread.h pthread_condattr_setclock]) +fi + +AC_CHECK_HEADERS([pthread_np.h],,, [ +#include +]) +AC_CHECK_FUNCS([pthread_setname_np pthread_set_name_np pthread_getthreadid_np]) + +CFLAGS="$orig_cflags" + +dnl -------------- +dnl Check programs +dnl -------------- +AC_PROG_INSTALL +AC_PROG_LN_S +AC_CHECK_TOOL([AR], [ar]) + +dnl ------- +dnl libtool +dnl ------- +AC_ARG_ENABLE([static-bin], + AS_HELP_STRING([--enable-static-bin], [link binaries statically])) +LT_INIT +_LT_CONFIG_LIBTOOL([ + patch -N -i "${srcdir}/m4/libtool-whole-archive.patch" libtool >&AS_MESSAGE_LOG_FD || \ + AC_MSG_WARN([Could not patch libtool for static linking support. Loading modules into a statically linked daemon will fail.]) +dnl the -i option is not POSIX sed and the BSDs implement it differently +dnl cat'ing the output back instead of mv/cp keeps permissions on libtool intact + sed -e 's%func_warning "relinking%true #\0%' libtool > libtool.sed && cat libtool.sed > libtool + sed -e 's%func_warning "remember to run%true #\0%' libtool > libtool.sed && cat libtool.sed > libtool + sed -e 's%func_warning ".*has not been installed in%true #\0%' libtool > libtool.sed && cat libtool.sed > libtool + test -f libtool.sed && rm libtool.sed +]) +if test "$enable_static_bin" = "yes"; then + AC_LDFLAGS_EXEC="-static" + if test "$enable_static" != "yes"; then + AC_MSG_ERROR([The --enable-static-bin option must be combined with --enable-static.]) + fi +fi +if test "$enable_shared" != "yes"; then + AC_MSG_ERROR([FRR cannot be built with --disable-shared. If you want statically linked daemons, use --enable-shared --enable-static --enable-static-bin]) +fi +AC_SUBST([AC_LDFLAGS]) +AC_SUBST([AC_LDFLAGS_EXEC]) +AM_CONDITIONAL([STATIC_BIN], [test "$enable_static_bin" = "yes"]) + +dnl libtool, the repository of all knowledge related linkers, is too stupid to +dnl correctly tell the linker how to build modules. +if test -z "$module_cmds"; then + module_cmds="`echo \"$archive_cmds\" | sed -e 's%$wl-soname $wl$soname%%'`" +fi +if test -z "$module_expsym_cmds"; then + module_expsym_cmds="`echo \"$archive_expsym_cmds\" | sed -e 's%$wl-soname $wl$soname%%'`" +fi + +AC_ARG_ENABLE([rpath], + [AS_HELP_STRING([--enable-rpath], [set hardcoded rpaths in the executable @<:@default=yes@:>@])], + [], + [enable_rpath=yes]) + +dnl $AR and $RANLIB are set by LT_INIT above +AC_MSG_CHECKING([whether $AR supports D option]) +if $AR crD conftest.a >/dev/null 2>/dev/null; then + AC_MSG_RESULT([yes]) + dnl ARFLAGS is for automake, AR_FLAGS for libtool m-( + ARFLAGS="crD" + AR_FLAGS="crD" +else + AC_MSG_RESULT([no]) + ARFLAGS="cru" + AR_FLAGS="cru" +fi +AC_SUBST([ARFLAGS]) +AC_SUBST([AR_FLAGS]) + +AC_MSG_CHECKING([whether $RANLIB supports D option]) +if $RANLIB -D conftest.a >conftest.err 2>&1; then + if grep -q -- '-D' conftest.err; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([yes]) + RANLIB="$RANLIB -D" + fi +else + AC_MSG_RESULT([no]) +fi +AC_SUBST([RANLIB]) + +test -f conftest.err && rm conftest.err +test -f conftest.a && rm conftest.a + +dnl ---------------------- +dnl Packages configuration +dnl ---------------------- +if test -f config.version; then + . ./config.version +elif test -f "${srcdir}/config.version"; then + . "${srcdir}/config.version" +fi +AC_ARG_WITH([pkg-extra-version], + AS_HELP_STRING([--with-pkg-extra-version=VER], [add extra version field, for packagers/distributions]), [ + if test "$withval" = "no"; then + EXTRAVERSION= + else + EXTRAVERSION=$withval + fi +], []) +AC_ARG_WITH([pkg-git-version], + AS_HELP_STRING([--with-pkg-git-version], [add git information to MOTD and build version string]), + [ test "$withval" != "no" && with_pkg_git_version="yes" ]) +AC_ARG_WITH([clippy], + AS_HELP_STRING([--with-clippy=PATH], [use external clippy helper program])) +AC_ARG_WITH([vtysh_pager], + AS_HELP_STRING([--with-vtysh-pager=PAGER], [control what pager is compiled in as default]), + VTYSH_PAGER=$withval, VTYSH_PAGER="more") +AC_ARG_ENABLE([vtysh], + AS_HELP_STRING([--disable-vtysh], [do not build integrated vty shell for FRR])) +AC_ARG_ENABLE([doc], + AS_HELP_STRING([--disable-doc], [do not build docs])) +AC_ARG_ENABLE([doc-html], + AS_HELP_STRING([--enable-doc-html], [build HTML docs])) +AC_ARG_ENABLE([zebra], + AS_HELP_STRING([--disable-zebra], [do not build zebra daemon])) +AC_ARG_ENABLE([bgpd], + AS_HELP_STRING([--disable-bgpd], [do not build bgpd])) +AC_ARG_ENABLE([mgmtd], + AS_HELP_STRING([--disable-mgmtd], [do not build mgmtd])) +AC_ARG_ENABLE([mgmtd_local_validations], + AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation])) +AC_ARG_ENABLE([mgmtd_test_be_client], + AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend client])) +AC_ARG_ENABLE([fpm_listener], + AS_HELP_STRING([--enable-fpm-listener], [build fpm listener test program])) +AC_ARG_ENABLE([ripd], + AS_HELP_STRING([--disable-ripd], [do not build ripd])) +AC_ARG_ENABLE([ripngd], + AS_HELP_STRING([--disable-ripngd], [do not build ripngd])) +AC_ARG_ENABLE([ospfd], + AS_HELP_STRING([--disable-ospfd], [do not build ospfd])) +AC_ARG_ENABLE([ospf6d], + AS_HELP_STRING([--disable-ospf6d], [do not build ospf6d])) +AC_ARG_ENABLE([ldpd], + AS_HELP_STRING([--disable-ldpd], [do not build ldpd])) +AC_ARG_ENABLE([nhrpd], + AS_HELP_STRING([--disable-nhrpd], [do not build nhrpd])) +AC_ARG_ENABLE([eigrpd], + AS_HELP_STRING([--disable-eigrpd], [do not build eigrpd])) +AC_ARG_ENABLE([babeld], + AS_HELP_STRING([--disable-babeld], [do not build babeld])) +AC_ARG_ENABLE([watchfrr], + AS_HELP_STRING([--disable-watchfrr], [do not build watchfrr])) +AC_ARG_ENABLE([isisd], + AS_HELP_STRING([--disable-isisd], [do not build isisd])) +AC_ARG_ENABLE([pimd], + AS_HELP_STRING([--disable-pimd], [do not build pimd])) +AC_ARG_ENABLE([pim6d], + AS_HELP_STRING([--disable-pim6d], [do not build pim6d])) +AC_ARG_ENABLE([pbrd], + AS_HELP_STRING([--disable-pbrd], [do not build pbrd])) +AC_ARG_ENABLE([sharpd], + AS_HELP_STRING([--enable-sharpd], [build sharpd])) +AC_ARG_ENABLE([staticd], + AS_HELP_STRING([--disable-staticd], [do not build staticd])) +AC_ARG_ENABLE([fabricd], + AS_HELP_STRING([--disable-fabricd], [do not build fabricd])) +AC_ARG_ENABLE([vrrpd], + AS_HELP_STRING([--disable-vrrpd], [do not build vrrpd])) +AC_ARG_ENABLE([pathd], + AS_HELP_STRING([--disable-pathd], [do not build pathd])) +AC_ARG_ENABLE([bgp-announce], + AS_HELP_STRING([--disable-bgp-announce], [turn off BGP route announcement])) +AC_ARG_ENABLE([bgp-vnc], + AS_HELP_STRING([--disable-bgp-vnc],[turn off BGP VNC support])) +AC_ARG_ENABLE([bgp-bmp], + AS_HELP_STRING([--disable-bgp-bmp],[turn off BGP BMP support])) +AC_ARG_ENABLE([snmp], + AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx])) +AC_ARG_ENABLE([config_rollbacks], + AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)])) +AC_ARG_ENABLE([sysrepo], + AS_HELP_STRING([--enable-sysrepo], [enable sysrepo integration])) +AC_ARG_ENABLE([grpc], + AS_HELP_STRING([--enable-grpc], [enable the gRPC northbound plugin])) +AC_ARG_ENABLE([zeromq], + AS_HELP_STRING([--enable-zeromq], [enable ZeroMQ handler (libfrrzmq)])) +AC_ARG_ENABLE([lttng], + AS_HELP_STRING([--enable-lttng], [enable LTTng tracing])) +AC_ARG_ENABLE([usdt], + AS_HELP_STRING([--enable-usdt], [enable USDT probes])) +AC_ARG_WITH([libpam], + AS_HELP_STRING([--with-libpam], [use libpam for PAM support in vtysh])) +AC_ARG_ENABLE([ospfapi], + AS_HELP_STRING([--disable-ospfapi], [do not build OSPFAPI to access the OSPF LSA Database])) +AC_ARG_ENABLE([ospfclient], + AS_HELP_STRING([--disable-ospfclient], [do not build OSPFAPI client for OSPFAPI, + (this is the default if --disable-ospfapi is set)])) +AC_ARG_WITH([log_timestamp_precision], + AS_HELP_STRING([--with-log-timestamp-precision=ARG], [set startup log timestamp precision, ARG must be 0-12])) +AC_ARG_ENABLE([multipath], + AS_HELP_STRING([--enable-multipath=ARG], [enable multipath function, ARG must be digit])) +AC_ARG_WITH([service_timeout], + AS_HELP_STRING([--with-service-timeout=ARG], [set service timeout value (2 minutes by default), ARG must be digit])) +AC_ARG_ENABLE([user], + AS_HELP_STRING([--enable-user=USER], [user to run FRR suite as (default frr)])) +AC_ARG_ENABLE([group], + AS_HELP_STRING([--enable-group=GROUP], [group to run FRR suite as (default frr)])) +AC_ARG_ENABLE([vty_group], + AS_HELP_STRING([--enable-vty-group=ARG], [set vty sockets to have specified group as owner])) +AC_ARG_ENABLE([configfile_mask], + AS_HELP_STRING([--enable-configfile-mask=ARG], [set mask for config files])) +AC_ARG_ENABLE([logfile_mask], + AS_HELP_STRING([--enable-logfile-mask=ARG], [set mask for log files])) +AC_ARG_ENABLE([realms], + AS_HELP_STRING([--enable-realms], [enable REALMS support under Linux])) +AC_ARG_ENABLE([rtadv], + AS_HELP_STRING([--disable-rtadv], [disable IPV6 router advertisement feature])) +AC_ARG_ENABLE([irdp], + AS_HELP_STRING([--enable-irdp], [enable IRDP server support in zebra])) +AC_ARG_ENABLE([capabilities], + AS_HELP_STRING([--disable-capabilities], [disable using POSIX capabilities])) +AC_ARG_ENABLE([gcc_ultra_verbose], + AS_HELP_STRING([--enable-gcc-ultra-verbose], [enable ultra verbose GCC warnings])) +AC_ARG_ENABLE([backtrace], + AS_HELP_STRING([--disable-backtrace], [disable crash backtraces (default autodetect)])) +AC_ARG_ENABLE([pcreposix], + AS_HELP_STRING([--enable-pcreposix], [enable using PCRE Posix libs for regex functions])) +AC_ARG_ENABLE([pcre2posix], + AS_HELP_STRING([--enable-pcre2posix], [enable using PCRE2 Posix libs for regex functions])) +AC_ARG_ENABLE([fpm], + AS_HELP_STRING([--enable-fpm], [enable Forwarding Plane Manager support])) +AC_ARG_ENABLE([werror], + AS_HELP_STRING([--enable-werror], [enable -Werror (recommended for developers only)])) +AC_ARG_ENABLE([cumulus], + AS_HELP_STRING([--enable-cumulus], [enable Cumulus Switch Special Extensions])) +AC_ARG_ENABLE([datacenter], + AS_HELP_STRING([--enable-datacenter], [enable Compilation for Data Center Extensions])) +AC_ARG_ENABLE([protobuf], + AS_HELP_STRING([--enable-protobuf], [Enable experimental protobuf support])) +AC_ARG_ENABLE([oldvpn_commands], + AS_HELP_STRING([--enable-oldvpn-commands], [Keep old vpn commands])) +AC_ARG_ENABLE([rpki], + AS_HELP_STRING([--enable-rpki], [enable RPKI prefix validation support])) +AC_ARG_ENABLE([clippy-only], + AS_HELP_STRING([--enable-clippy-only], [Only build clippy])) +AC_ARG_ENABLE([numeric_version], + AS_HELP_STRING([--enable-numeric-version], [Only numeric digits allowed in version (for Alpine)])) +AC_ARG_ENABLE([gcov], + AS_HELP_STRING([--enable-gcov], [Collect coverage information with gcov])) +AC_ARG_ENABLE([clang_coverage], + AS_HELP_STRING([--enable-clang-coverage], [Collect coverage information with Clang Coverage])) +AC_ARG_ENABLE([bfdd], + AS_HELP_STRING([--disable-bfdd], [do not build bfdd])) +AC_ARG_ENABLE([address-sanitizer], + AS_HELP_STRING([--enable-address-sanitizer], [enable AddressSanitizer support for detecting a wide variety of memory allocation and deallocation errors])) +AC_ARG_ENABLE([thread-sanitizer], + AS_HELP_STRING([--enable-thread-sanitizer], [enable ThreadSanitizer support for detecting data races])) +AC_ARG_ENABLE([memory-sanitizer], + AS_HELP_STRING([--enable-memory-sanitizer], [enable MemorySanitizer support for detecting uninitialized memory reads])) +AC_ARG_ENABLE([undefined-sanitizer], + AS_HELP_STRING([--enable-undefined-sanitizer], [enable UndefinedBehaviorSanitizer support for detecting undefined behavior])) +AC_ARG_WITH([crypto], + AS_HELP_STRING([--with-crypto=], [choose between different implementations of cryptographic functions(default value is --with-crypto=internal)])) +AC_ARG_WITH([frr-format], + AS_HELP_STRING([--with-frr-format[=<.../frr-format.so>]], [use frr-format GCC plugin])) + +AC_ARG_ENABLE([version-build-config], + AS_HELP_STRING([--disable-version-build-config], [do not include build configs in show version command])) + +#if openssl, else use the internal +AS_IF([test "$with_crypto" = "openssl"], [ +AC_CHECK_LIB([crypto], [EVP_DigestInit], [LIBS="$LIBS -lcrypto"], [], []) +if test "$ac_cv_lib_crypto_EVP_DigestInit" = "no"; then + AC_MSG_ERROR([build with openssl has been specified but openssl library was not found on your system]) +else + AC_DEFINE([CRYPTO_OPENSSL], [1], [Compile with openssl support]) +fi +], [test "$with_crypto" = "internal" || test "$with_crypto" = "" ], [AC_DEFINE([CRYPTO_INTERNAL], [1], [Compile with internal cryptographic implementation]) +], [AC_MSG_ERROR([Unknown value for --with-crypto])] +) + +AS_IF([test "$enable_clippy_only" != "yes"], [ +AC_CHECK_HEADERS([json-c/json.h]) +AC_CHECK_LIB([json-c], [json_object_get], [LIBS="$LIBS -ljson-c"], [], [-lm]) +if test "$ac_cv_lib_json_c_json_object_get" = "no"; then + AC_CHECK_LIB([json], [json_object_get], [LIBS="$LIBS -ljson"]) + if test "$ac_cv_lib_json_json_object_get" = "no"; then + AC_MSG_ERROR([libjson is needed to compile]) + fi +fi +]) + +AC_ARG_ENABLE([ccls], +AS_HELP_STRING([--enable-ccls], [Write .ccls config for this build])) + +AC_ARG_ENABLE([dev_build], + AS_HELP_STRING([--enable-dev-build], [build for development])) + +AC_ARG_ENABLE([scripting], + AS_HELP_STRING([--enable-scripting], [Build with scripting support])) + +AC_ARG_ENABLE([netlink-debug], + AS_HELP_STRING([--disable-netlink-debug], [don't pretty print netlink debug messages])) + +if test "$enable_netlink_debug" != "no" ; then + AC_DEFINE([NETLINK_DEBUG], [1], [Netlink extra debugging code]) +fi + +AM_CONDITIONAL([NETLINK_DEBUG], [test "$enable_netlink_debug" != "no"]) + +if test "$enable_datacenter" = "yes" ; then + AC_DEFINE([HAVE_DATACENTER], [1], [Compile extensions for a DataCenter]) + DFLT_NAME="datacenter" +else + DFLT_NAME="traditional" +fi + +if test "$enable_cumulus" = "yes" ; then + AC_DEFINE([HAVE_CUMULUS], [1], [Compile Special Cumulus Code in]) +fi + +AC_SUBST([DFLT_NAME]) +AC_DEFINE_UNQUOTED([DFLT_NAME], ["$DFLT_NAME"], [Name of the configuration default set]) + +# +# Python for clippy +# + +AS_IF([test "$host" = "$build"], [ + AM_PATH_PYTHON([3]) + AC_CHECK_HEADER([gelf.h], [], [ + AC_MSG_ERROR([libelf headers are required for building clippy. (Host only when cross-compiling.)]) + ]) + + LIBS_save="$LIBS" + AC_CHECK_LIB([elf], [elf_memory], [], [ + AC_MSG_ERROR([libelf is required for building clippy. (Host only when cross-compiling.)]) + ]) + + AC_CHECK_LIB([elf], [elf_getdata_rawchunk], [ + AC_DEFINE([HAVE_ELF_GETDATA_RAWCHUNK], [1], [Have elf_getdata_rawchunk()]) + ]) + AC_CHECK_LIB([elf], [gelf_getnote], [ + AC_DEFINE([HAVE_GELF_GETNOTE], [1], [Have gelf_getnote()]) + ]) + LIBS="$LIBS_save" + unset LIBS_save + + FRR_PYTHON_DEV +], [ + FRR_PYTHON +]) + +FRR_PYTHON_MODULES([pytest]) + +if test "$enable_doc" != "no"; then + FRR_PYTHON_MODULES([sphinx], , [ + if test "$enable_doc" = "yes"; then + AC_MSG_ERROR([Documentation was explicitly requested with --enable-doc but sphinx is not available for $PYTHON. Please disable docs or install sphinx.]) + fi + ]) +fi +AM_CONDITIONAL([DOC], [test "$enable_doc" != "no" -a "$frr_py_mod_sphinx" != "false"]) +AM_CONDITIONAL([DOC_HTML], [test "$enable_doc_html" = "yes"]) + +FRR_PYTHON_MOD_EXEC([sphinx], [--version], [ + PYSPHINX="-m sphinx" +], [ + PYSPHINX="-c 'import sys; from sphinx import main; sys.exit(main(sys.argv))'" +]) +AC_SUBST([PYSPHINX]) + +# +# Logic for old vpn commands support. +# +if test "$enable_oldvpn_commands" = "yes"; then + AC_DEFINE([KEEP_OLD_VPN_COMMANDS], [1], [Define for compiling with old vpn commands]) +fi + +AC_MSG_CHECKING([if zebra should be configurable to send Route Advertisements]) +if test "$enable_rtadv" != "no"; then + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_RTADV], [1], [Enable IPv6 Routing Advertisement support]) +else + AC_MSG_RESULT([no]) +fi + +if test "$enable_user" = "no"; then + enable_user="" +else + if test "$enable_user" = "yes" || test "$enable_user" = ""; then + enable_user="frr" + fi + AC_DEFINE_UNQUOTED([FRR_USER], ["${enable_user}"], [frr User]) +fi + +if test "$enable_group" = "no"; then + enable_group="" +else + if test "$enable_group" = "yes" || test "$enable_group" = ""; then + enable_group="frr" + fi + AC_DEFINE_UNQUOTED([FRR_GROUP], ["${enable_group}"], [frr Group]) +fi + +if test "$enable_vty_group" = "yes" ; then + AC_MSG_ERROR([--enable-vty-group requires a group as argument, not yes]) +elif test "$enable_vty_group" != ""; then + if test "$enable_vty_group" != "no"; then + AC_DEFINE_UNQUOTED([VTY_GROUP], ["${enable_vty_group}"], [VTY Sockets Group]) + fi +fi +AC_SUBST([enable_user]) +AC_SUBST([enable_group]) +AC_SUBST([enable_vty_group]) + +enable_configfile_mask=${enable_configfile_mask:-0600} +AC_DEFINE_UNQUOTED([CONFIGFILE_MASK], [${enable_configfile_mask}], [Mask for config files]) +AC_SUBST([enable_configfile_mask]) + +enable_logfile_mask=${enable_logfile_mask:-0600} +AC_DEFINE_UNQUOTED([LOGFILE_MASK], [${enable_logfile_mask}], [Mask for log files]) + +MPATH_NUM=16 + +case "${enable_multipath}" in + 0) + MPATH_NUM=64 + ;; + [[1-9]|[1-9][0-9]|[1-9][0-9][0-9]]) + MPATH_NUM="${enable_multipath}" + ;; + "") + ;; + *) + AC_MSG_FAILURE([Please specify digit to enable multipath ARG]) + ;; +esac + +AC_DEFINE_UNQUOTED([MULTIPATH_NUM], [$MPATH_NUM], [Maximum number of paths for a route]) + +case "${with_log_timestamp_precision}" in +[[0-9]|1[012]]) +;; +"") +;; +*) +AC_MSG_FAILURE([Please specify a number from 0-12 for log precision ARG]) +;; +esac +with_log_timestamp_precision=${with_log_timestamp_precision:-0} +AC_DEFINE_UNQUOTED([LOG_TIMESTAMP_PRECISION], [${with_log_timestamp_precision}], [Startup zlog timestamp precision]) + +AC_DEFINE_UNQUOTED([VTYSH_PAGER], ["$VTYSH_PAGER"], [What pager to use]) + +TIMEOUT_MIN=2 +case "${with_service_timeout}" in + [[1-9]|[1-9][0-9]|[1-9][0-9][0-9]]) + TIMEOUT_MIN="${with_service_timeout}" + ;; + 0|"") + ;; + *) + AC_MSG_FAILURE([Please specify digit for timeout ARG]) + ;; +esac +AC_SUBST([TIMEOUT_MIN]) + +dnl -------------------- +dnl Enable code coverage +dnl -------------------- +AM_CONDITIONAL([HAVE_GCOV], [test "$enable_gcov" != "no"]) + +dnl ------------------------------------ +dnl Alpine only accepts numeric versions +dnl ------------------------------------ +if test "$enable_numeric_version" != "" ; then + VERSION="`echo ${VERSION} | tr -c -d '[[.0-9]]'`" + PACKAGE_VERSION="`echo ${PACKAGE_VERSION} | tr -c -d '[[.0-9]]'`" +fi + +dnl ----------------------------------- +dnl Add extra version string to package +dnl name, string and version fields. +dnl ----------------------------------- +if test "$EXTRAVERSION" != "" ; then + VERSION="${VERSION}${EXTRAVERSION}" + PACKAGE_VERSION="${PACKAGE_VERSION}${EXTRAVERSION}" + AC_SUBST(PACKAGE_EXTRAVERSION, ["${EXTRAVERSION}"]) + PACKAGE_STRING="${PACKAGE_STRING}${EXTRAVERSION}" +fi +AC_SUBST([EXTRAVERSION]) + +if test "$with_pkg_git_version" = "yes"; then + if test -e "${srcdir}/.git"; then + AC_DEFINE([GIT_VERSION], [1], [include git version info]) + else with_pkg_git_version="no" + AC_MSG_WARN([--with-pkg-git-version given, but this is not a git checkout]) + fi +fi +AM_CONDITIONAL([GIT_VERSION], [test "$with_pkg_git_version" = "yes"]) + +AC_CHECK_TOOL([OBJCOPY], [objcopy], [:]) +if test "$OBJCOPY" != ":"; then + AC_CACHE_CHECK([for .interp value to use], [frr_cv_interp], [ + frr_cv_interp="" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main() { return 0; }]])], [ + if $OBJCOPY -j.interp -Obinary conftest conftest.interp; then + frr_cv_interp="`xargs -0 echo < conftest.interp`" + fi + test -f conftest.interp && rm conftest.interp + ]) + ]) +fi +if test -n "$frr_cv_interp"; then + AC_DEFINE_UNQUOTED([INTERP], ["$frr_cv_interp"], [.interp value]) +fi + +dnl ------------------------- +dnl Check other header files. +dnl ------------------------- +AC_CHECK_HEADERS([stropts.h sys/ksym.h \ + linux/version.h asm/types.h endian.h sys/endian.h]) + +AC_CHECK_LIB([atomic], [main], [LIBS="$LIBS -latomic"], [], []) + +ac_stdatomic_ok=false +AC_DEFINE([FRR_AUTOCONF_ATOMIC], [1], [did autoconf checks for atomic funcs]) +AC_CHECK_HEADER([stdatomic.h],[ + + AC_MSG_CHECKING([whether _Atomic qualifier works]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +#include +int main(int argc, char **argv) { + _Atomic int i = 0; + return i; +} +]])], [ + AC_DEFINE([HAVE_STDATOMIC_H], [1], [found stdatomic.h]) + AC_MSG_RESULT([yes]) + ac_stdatomic_ok=true + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +AS_IF([$ac_stdatomic_ok], [true], [ + AC_MSG_CHECKING([for __atomic_* builtins]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +int main(int argc, char **argv) { + volatile int i = 1; + __atomic_store_n (&i, 0, __ATOMIC_RELEASE); + return __atomic_load_n (&i, __ATOMIC_ACQUIRE); +} +]])], [ + AC_DEFINE([HAVE___ATOMIC], [1], [found __atomic builtins]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + + dnl FreeBSD 9 has a broken stdatomic.h where _Atomic doesn't work + AC_MSG_CHECKING([for __sync_* builtins]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +int main(int argc, char **argv) { + volatile int i = 1; + __sync_fetch_and_sub (&i, 1); + return __sync_val_compare_and_swap (&i, 0, 1); +} +]])], [ + AC_DEFINE([HAVE___SYNC], [1], [found __sync builtins]) + AC_MSG_RESULT([yes]) + + AC_MSG_CHECKING([for __sync_swap builtin]) + AC_LINK_IFELSE([AC_LANG_SOURCE([[ +int main(int argc, char **argv) { + volatile int i = 1; + return __sync_swap (&i, 2); +} +]])], [ + AC_DEFINE([HAVE___SYNC_SWAP], 1, [found __sync_swap builtin]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + + ], [ + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([stdatomic.h unavailable and $CC has neither __atomic nor __sync builtins]) + ]) + ]) +]) + +needsync=true + +AS_IF([$needsync], [ + dnl Linux + AC_MSG_CHECKING([for Linux futex() support]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include + +int main(void); +], +[ +{ + return syscall(SYS_futex, NULL, FUTEX_WAIT, 0, NULL, NULL, 0); +} +])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_SYNC_LINUX_FUTEX,,Have Linux futex support) + needsync=false + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +AS_IF([$needsync], [ + dnl FreeBSD + AC_MSG_CHECKING([for FreeBSD _umtx_op() support]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([ +#include +#include +#include +#include +int main(void); +], +[ +{ + return _umtx_op(NULL, UMTX_OP_WAIT_UINT, 0, NULL, NULL); +} +])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_SYNC_UMTX_OP,,Have FreeBSD _umtx_op() support) + needsync=false + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +AS_IF([$needsync], [ + dnl OpenBSD patch (not upstream at the time of writing this) + dnl https://marc.info/?l=openbsd-tech&m=147299508409549&w=2 + AC_MSG_CHECKING([for OpenBSD futex() support]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([ +#include +int main(void); +], +[ +{ + return futex(NULL, FUTEX_WAIT, 0, NULL, NULL, 0); +} +])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_SYNC_OPENBSD_FUTEX,,Have OpenBSD futex support) + needsync=false + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +dnl Utility macro to avoid retyping includes all the time +m4_define([FRR_INCLUDES], +[#ifdef SUNOS_5 +#define _POSIX_C_SOURCE 200809L +#define __EXTENSIONS__ +#endif +#include +#include +#include +#include +/* sys/conf.h depends on param.h on FBSD at least */ +#include +/* Required for MAXSIG */ +#include +#include +#include +#include +#include +#include +])dnl + +dnl Same applies for HAVE_NET_IF_VAR_H, which HAVE_NETINET6_ND6_H and +dnl HAVE_NETINET_IN_VAR_H depend upon. But if_var.h depends on if.h, hence +dnl an additional round for it. + +AC_CHECK_HEADERS([net/if_var.h], [], [], [FRR_INCLUDES]) + +m4_define([FRR_INCLUDES], +FRR_INCLUDES +[#ifdef HAVE_NET_IF_VAR_H +# include +#endif +])dnl + +AC_CHECK_HEADERS([netinet/in_var.h \ + net/if_dl.h net/netopt.h \ + inet/nd.h netinet/ip_icmp.h \ + sys/sysctl.h sys/sockio.h sys/conf.h], + [], [], [FRR_INCLUDES]) + +AC_CHECK_HEADERS([ucontext.h], [], [], +[#ifndef __USE_GNU +#define __USE_GNU +#endif /* __USE_GNU */ +FRR_INCLUDES +]) + +m4_define([UCONTEXT_INCLUDES], +[#include ])dnl + +AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.uc_regs], + [], [], [UCONTEXT_INCLUDES]) +AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.regs], + [AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.regs.nip], + [], [], [UCONTEXT_INCLUDES])], + [], [UCONTEXT_INCLUDES]) +AC_CHECK_MEMBERS([ucontext_t.uc_mcontext.gregs], + [], [], [UCONTEXT_INCLUDES]) + +m4_define([FRR_INCLUDES], +FRR_INCLUDES +[ +#include +#include +#ifdef HAVE_NETINET_IN_VAR_H +# include +#endif +#ifdef HAVE_NET_IF_DL_H +# include +#endif +#ifdef HAVE_NET_NETOPT_H +# include +#endif +#include +#ifdef HAVE_INET_ND_H +# include +#endif +#include +/* Required for IDRP */ +#ifdef HAVE_NETINET_IP_ICMP_H +# include +#endif +])dnl + +dnl V6 headers are checked below, after we check for v6 + +is_linux=false + +AC_MSG_CHECKING([which operating system interface to use]) +case "$host_os" in + sunos* | solaris2*) + AC_MSG_FAILURE([Solaris support has been removed please see versions prior or equal to 7.5]) + ;; + linux*) + AC_MSG_RESULT([Linux]) + + AC_DEFINE([GNU_LINUX], [1], [GNU Linux]) + AC_DEFINE([HAVE_NETLINK], [1], [netlink]) + AC_DEFINE([LINUX_IPV6], [1], [Linux IPv6 stack]) + + dnl Linux has a compilation problem with mixing + dnl netinet/in.h and linux/in6.h they are not + dnl compatible. There has been discussion on + dnl how to fix it but no real progress on implementation + dnl when they fix it, remove this + AC_DEFINE([IPV6_MINHOPCOUNT], [73], [Linux ipv6 Min Hop Count]) + + is_linux=true + ;; + openbsd*) + AC_MSG_RESULT([OpenBSD]) + + AC_DEFINE([OPEN_BSD], [1], [OpenBSD]) + AC_DEFINE([KAME], [1], [KAME IPv6]) + AC_DEFINE([BSD_V6_SYSCTL], [1], [BSD v6 sysctl to turn on and off forwarding]) + ;; + *) + AC_MSG_RESULT([BSD]) + + AC_DEFINE([HAVE_NET_RT_IFLIST], [1], [NET_RT_IFLIST]) + AC_DEFINE([KAME], [1], [KAME IPv6]) + AC_DEFINE([BSD_V6_SYSCTL], [1], [BSD v6 sysctl to turn on and off forwarding]) + ;; +esac +AM_CONDITIONAL([LINUX], [${is_linux}]) + +AC_SYS_LARGEFILE + +dnl ------------------------ +dnl Integrated REALMS option +dnl ------------------------ +if test "$enable_realms" = "yes"; then + case "$host_os" in + linux*) + AC_DEFINE([SUPPORT_REALMS], [1], [Realms support]) + ;; + *) + echo "Sorry, only Linux has REALMS support" + exit 1 + ;; + esac +fi + +dnl --------------- +dnl other functions +dnl --------------- +AC_CHECK_FUNCS([ \ + strlcat strlcpy \ + getgrouplist \ + openat \ + unlinkat \ + posix_fallocate \ + sendmmsg \ + explicit_bzero \ + ]) + +AC_CHECK_MEMBERS([struct mmsghdr.msg_hdr], [], [], FRR_INCLUDES) + +dnl ########################################################################## +dnl LARGE if block spans a lot of "configure"! +if test "$enable_clippy_only" != "yes"; then +dnl ########################################################################## + +# +# Logic for protobuf support. +# +PROTO3=false +# Enable Protobuf by default at all times. +# Check for protoc & protoc-c +# protoc is not required, it's only for a "be nice" helper target +AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false]) + +AC_CHECK_PROGS([PROTOC_C], [protoc-c], [/bin/false]) +if test "$PROTOC_C" = "/bin/false"; then + AC_MSG_FAILURE([protobuf requested but protoc-c not found. Install protobuf-c.]) +fi + +PKG_CHECK_MODULES([PROTOBUF_C], [libprotobuf-c >= 1.1.0],, [ + AC_MSG_FAILURE([minimum version (1.1.0) of libprotobuf-c not found. Install minimum required version of protobuf-c.]) +]) + +if test "$enable_protobuf3" = "yes"; then + PROTO3=true + AC_CHECK_HEADER([google/protobuf-c/protobuf-c.h], + [AC_CHECK_DECLS(PROTOBUF_C_LABEL_NONE, + AC_DEFINE([HAVE_PROTOBUF_VERSION_3], + [1], [Have Protobuf version 3]), + [PROTO3=false], + [#include ])], + [PROTO3=false && AC_MSG_FAILURE([protobuf3 requested but protobuf-c.h not found. Install protobuf-c.])]) +fi + +if test "$enable_protobuf" != "no"; then + AC_DEFINE([HAVE_PROTOBUF], [1], [protobuf]) +fi +# +# End of logic for protobuf support. +# + + +dnl --------------------- +dnl Integrated VTY option +dnl --------------------- +case "${enable_vtysh}" in +"no") + VTYSH="";; +*) + VTYSH="vtysh"; + AC_DEFINE([VTYSH], [1], [VTY shell]) + + prev_libs="$LIBS" + AC_CHECK_LIB([readline], [readline], [ + LIBREADLINE="-lreadline" + ], [ + dnl readline failed - it might be incorrectly linked and missing its + dnl termcap/tinfo/curses dependency. see if we can fix that... + AC_SEARCH_LIBS([tputs], [termcap tinfo curses ncurses], [ + LIBREADLINE="$ac_cv_search_tputs" + ], [ + AC_MSG_ERROR([libreadline (needed for vtysh) not found and/or missing dependencies]) + ]) + + dnl re-try with the lib we found above + unset ac_cv_lib_readline_main + AC_CHECK_LIB([readline], [main], [ + LIBREADLINE="-lreadline $LIBREADLINE" + ], [ + AC_MSG_ERROR([libreadline (needed for vtysh) not found and/or missing dependencies]) + ], [$LIBREADLINE]) + ], []) + LIBS="$LIBS -lreadline" + AC_CHECK_FUNCS([rl_clear_visible_line]) + LIBS="$prev_libs" + + AC_CHECK_HEADER([readline/history.h]) + if test "$ac_cv_header_readline_history_h" = "no"; then + AC_MSG_ERROR([readline is too old to have readline/history.h, please update to the latest readline library.]) + fi + AC_CHECK_LIB([readline], [rl_completion_matches], [true], [], [$LIBREADLINE]) + if test "$ac_cv_lib_readline_rl_completion_matches" = "no"; then + AC_DEFINE([rl_completion_matches], [completion_matches], [Old readline]) + fi + AC_CHECK_LIB([readline], [append_history], [frr_cv_append_history=yes], [frr_cv_append_history=no], [$LIBREADLINE]) + if test "$frr_cv_append_history" = "yes"; then + AC_DEFINE([HAVE_APPEND_HISTORY], [1], [Have history.h append_history]) + fi + ;; +esac +AC_SUBST([LIBREADLINE]) + +dnl ---------- +dnl PAM module +dnl +dnl FRR detects the PAM library it is built against by checking for a +dnl functional pam_misc.h (Linux-PAM) or openpam.h (OpenPAM) header. pam_misc.h +dnl is known to #include pam_appl.h, the standard header of a PAM library, and +dnl openpam.h doesn't do that, although depends on the header too. Hence a +dnl little assistance to AC_CHECK_HEADER is necessary for the proper detection +dnl of OpenPAM. +dnl ---------- +if test "$with_libpam" = "yes"; then + AC_CHECK_HEADER([security/pam_misc.h], + [AC_DEFINE([HAVE_PAM_MISC_H], [1], [Have pam_misc.h]) + AC_DEFINE([PAM_CONV_FUNC], [misc_conv], [Have misc_conv]) + pam_conv_func="misc_conv" + ], + [], FRR_INCLUDES) + AC_CHECK_HEADER([security/openpam.h], + [AC_DEFINE([HAVE_OPENPAM_H], [1], [Have openpam.h]) + AC_DEFINE([PAM_CONV_FUNC], [openpam_ttyconv], [Have openpam_ttyconv]) + pam_conv_func="openpam_ttyconv" + ], + [], FRR_INCLUDES[#include ]) + if test -z "$ac_cv_header_security_pam_misc_h$ac_cv_header_security_openpam_h" ; then + AC_MSG_WARN([*** pam support will not be built ***]) + with_libpam="no" + fi +fi + +if test "$with_libpam" = "yes"; then +dnl took this test from proftpds configure.in and suited to our needs +dnl ------------------------------------------------------------------------- +dnl +dnl This next check looks funky due to a linker problem with some versions +dnl of the PAM library. Prior to 0.72 release, the Linux PAM shared library +dnl omitted requiring libdl linking information. PAM-0.72 or better ships +dnl with RedHat 6.2 and Debian 2.2 or better. +AC_CHECK_LIB([pam], [pam_start], + [AC_CHECK_LIB([pam], [$pam_conv_func], + [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) + LIBPAM="-lpam"], + [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) + LIBPAM="-lpam -lpam_misc"] + ) + ], + + [AC_CHECK_LIB([pam], [pam_end], + [AC_CHECK_LIB([pam], [$pam_conv_func], + [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) + LIBPAM="-lpam -ldl"], + [AC_DEFINE([USE_PAM], [1], [Use PAM for authentication]) + LIBPAM="-lpam -ldl -lpam_misc"] + ) + ],AC_MSG_WARN([*** pam support will not be built ***]), + [-ldl]) + ] +) +fi +AC_SUBST([LIBPAM]) + +dnl ------------------------------- +dnl bgpd needs pow() and hence libm +dnl ------------------------------- +TMPLIBS="$LIBS" +LIBS="" +AC_SEARCH_LIBS([pow], [m], [ + LIBM="$LIBS" +], [ + AC_MSG_WARN([Unable to find working pow function - bgpd may not link]) +]) +LIBS="$TMPLIBS" +AC_SUBST([LIBM]) + +AC_CHECK_FUNCS([ppoll], [ + AC_DEFINE([HAVE_PPOLL], [1], [have Linux/BSD ppoll()]) +]) +AC_CHECK_FUNCS([pollts], [ + AC_DEFINE([HAVE_POLLTS], [1], [have NetBSD pollts()]) +]) + +AC_CHECK_HEADER([asm-generic/unistd.h], + [AC_CHECK_DECL(__NR_setns, + AC_DEFINE([HAVE_NETNS], [1], [Have netns]),, + FRR_INCLUDES [#include + ]) + AC_CHECK_FUNCS([setns])] + ) + +dnl -------------------------- +dnl Determine IS-IS I/O method +dnl -------------------------- +AC_DEFINE([ISIS_METHOD_PFPACKET], [1], [constant value for isis method pfpacket]) +AC_DEFINE([ISIS_METHOD_DLPI], [2], [constant value for isis method dlpi]) +AC_DEFINE([ISIS_METHOD_BPF], [3], [constant value for isis method bpf]) +AC_CHECK_HEADER([net/bpf.h]) +AC_CHECK_HEADER([sys/dlpi.h]) +AC_MSG_CHECKING([zebra IS-IS I/O method]) + +case "$host_os" in + linux*) + AC_MSG_RESULT([pfpacket]) + ISIS_METHOD_MACRO="ISIS_METHOD_PFPACKET" + ;; + *) + if test "$ac_cv_header_net_bpf_h" = "no"; then + if test "$ac_cv_header_sys_dlpi_h" = "no"; then + AC_MSG_RESULT([none]) + if test "$enable_isisd" = "yes" -o "$enable_fabricd" = "yes"; then + AC_MSG_FAILURE([IS-IS support requested but no packet backend found]) + fi + AC_MSG_WARN([*** IS-IS support will not be built ***]) + enable_isisd="no" + enable_fabricd="no" + else + AC_MSG_RESULT([DLPI]) + fi + ISIS_METHOD_MACRO="ISIS_METHOD_DLPI" + else + AC_MSG_RESULT([BPF]) + ISIS_METHOD_MACRO="ISIS_METHOD_BPF" + fi + ;; +esac +AC_DEFINE_UNQUOTED([ISIS_METHOD], [$ISIS_METHOD_MACRO], [selected method for isis, == one of the constants]) + +dnl --------------------------------------------------------------- +dnl figure out how to specify an interface in multicast sockets API +dnl --------------------------------------------------------------- +AC_CHECK_MEMBERS([struct ip_mreqn.imr_ifindex], [], [], FRR_INCLUDES) + +AC_CHECK_HEADERS([linux/mroute.h], [], [],[ + #include + #include + #define _LINUX_IN_H /* For Linux <= 2.6.25 */ + #include +]) + +AC_CHECK_HEADERS([linux/mroute6.h], [], [],[ + #include + #include + #define _LINUX_IN_H /* For Linux <= 2.6.25 */ + #include +]) + +m4_define([FRR_INCLUDES], +FRR_INCLUDES +[#ifdef HAVE_LINUX_MROUTE_H +# include +#endif +])dnl + +AC_CHECK_HEADERS([netinet/ip_mroute.h], [], [],[ + #include + #include + #include + #include +]) + +m4_define([FRR_INCLUDES], +FRR_INCLUDES +[#ifdef HAVE_NETINET_IP_MROUTE_H +# include +#endif +])dnl + +AC_CHECK_HEADERS([netinet6/ip6_mroute.h], [], [],[ + #include + #include + #include + #include + #include +]) + +m4_define([FRR_INCLUDES], +FRR_INCLUDES +[#ifdef HAVE_NETINET_IP6_MROUTE_H +# include +#endif +])dnl + +AC_MSG_CHECKING([for RFC3678 protocol-independed API]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + #include + ]], [[ + struct group_req gr; + int sock; + setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, (void*)&gr, sizeof(gr)); + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_RFC3678], [1], [Have RFC3678 protocol-independed API]) +],[ + AC_MSG_RESULT(no) +]) + +dnl --------------------------------------------------------------- +dnl figure out how to check link-state +dnl --------------------------------------------------------------- +AC_CHECK_HEADER([net/if_media.h], + [m4_define([LINK_DETECT_INCLUDES], + FRR_INCLUDES + [#include + ]) + AC_CHECK_MEMBERS([struct ifmediareq.ifm_status], + AC_DEFINE([HAVE_BSD_LINK_DETECT], [1], [BSD link-detect]), + [], LINK_DETECT_INCLUDES)], + [], + FRR_INCLUDES) + +dnl --------------------------------------------------------------- +dnl Additional, newer way to check link-state using ifi_link_state. +dnl Not available in all BSD's when ifmediareq available +dnl --------------------------------------------------------------- +AC_CHECK_MEMBERS([struct if_data.ifi_link_state], + AC_DEFINE([HAVE_BSD_IFI_LINK_STATE], [1], [BSD ifi_link_state available]), + [], FRR_INCLUDES) + +dnl ------------------------ +dnl TCP_MD5SIG socket option +dnl ------------------------ + +AC_CHECK_HEADER([netinet/tcp.h], + [m4_define([MD5_INCLUDES], + FRR_INCLUDES + [#include + ]) + AC_CHECK_DECLS([TCP_MD5SIG], [], [], MD5_INCLUDES)], + [], + FRR_INCLUDES) +if test "$ac_cv_have_decl_TCP_MD5SIG" = "no"; then + AC_CHECK_HEADER([linux/tcp.h], + [m4_define([MD5_INCLUDES], + FRR_INCLUDES + [#include + ]) + AC_CHECK_DECLS([TCP_MD5SIG], [], [], MD5_INCLUDES)]) +fi + +LIBS_save="$LIBS" +AC_CHECK_LIB([crypt], [crypt], [], [ + AC_CHECK_LIB([crypto], [DES_crypt]) +]) +LIBCRYPT="$LIBS" +LIBCRYPT="${LIBCRYPT%$LIBS_save}" +LIBCRYPT="${LIBCRYPT#$LIBS_save}" +AC_SUBST([LIBCRYPT]) +LIBS="$LIBS_save" +unset LIBS_save + +AC_CHECK_LIB([resolv], [res_init]) + +dnl --------------------------- +dnl check system has PCRE regexp +dnl --------------------------- +if test "$enable_pcreposix" = "yes"; then + AC_CHECK_LIB([pcreposix], [regexec], [], [ + AC_MSG_ERROR([--enable-pcreposix given but unable to find libpcreposix]) + ]) +fi +AC_SUBST([HAVE_LIBPCREPOSIX]) + +dnl --------------------------- +dnl check system has PCRE2 regexp +dnl --------------------------- +if test "$enable_pcre2posix" = "yes"; then + AC_CHECK_LIB([pcre2-posix], [regexec], [], [ + AC_MSG_ERROR([--enable-pcre2posix given but unable to find libpcre2-posix]) + ]) +fi +AC_SUBST([HAVE_LIBPCRE2_POSIX]) + +dnl ########################################################################## +dnl test "$enable_clippy_only" != "yes" +fi +dnl END OF LARGE if block +dnl ########################################################################## + +dnl ------------------ +dnl check C-Ares library +dnl ------------------ +PKG_CHECK_MODULES([CARES], [libcares], [ + c_ares_found=true +],[ + c_ares_found=false +]) +AM_CONDITIONAL([CARES], [$c_ares_found]) + + +dnl ---------------------------------------------------------------------------- +dnl figure out if domainname is available in the utsname struct (GNU extension). +dnl ---------------------------------------------------------------------------- +AC_CHECK_MEMBERS([struct utsname.domainname], [], [], [#include ]) + +dnl ------------------ +dnl IPv6 header checks +dnl ------------------ +AC_CHECK_HEADERS([netinet6/in6.h netinet/in6_var.h \ + netinet6/in6_var.h netinet6/nd6.h], [], [], + FRR_INCLUDES) + +m4_define([FRR_INCLUDES],dnl +FRR_INCLUDES +[#ifdef HAVE_NETINET6_IN6_H +#include +#endif +#ifdef HAVE_NETINET_IN6_VAR_H +#include +#endif +#include +#ifdef HAVE_NETINET6_IN6_VAR_H +# include +#endif +#ifdef HAVE_NETINET6_ND6_H +# include +#endif +])dnl + +dnl -------------------- +dnl Daemon disable check +dnl -------------------- + +AS_IF([test "$enable_bgpd" != "no"], [ + AC_DEFINE([HAVE_BGPD], [1], [bgpd]) +]) + +AS_IF([test "$enable_mgmtd" != "no"], [ + + AC_DEFINE([HAVE_MGMTD], [1], [mgmtd]) + + # Enable MGMTD local validations + AS_IF([test "$enable_mgmtd_local_validations" == "yes"], [ + AC_DEFINE([MGMTD_LOCAL_VALIDATIONS_ENABLED], [1], [Enable mgmtd local validations.]) + ]) +]) + +AS_IF([test "$enable_mgmtd_test_be_client" = "yes"], [ + AC_DEFINE([HAVE_MGMTD_TESTC], [1], [mgmtd_testc]) +]) + +AS_IF([test "$enable_fpm_listener" = "yes"], [ + AC_DEFINE([HAVE_FPM_LISTENER], [1], [fpm_listener]) +]) + +AS_IF([test "$enable_ripd" != "no"], [ + AC_DEFINE([HAVE_RIPD], [1], [ripd]) +]) + +AS_IF([test "$enable_ripngd" != "no"], [ + AC_DEFINE([HAVE_RIPNGD], [1], [ripngd]) +]) + +AS_IF([test "$enable_ospfd" != "no"], [ + AC_DEFINE([HAVE_OSPFD], [1], [ospfd]) +]) + +AS_IF([test "$enable_ospf6d" != "no"], [ + AC_DEFINE([HAVE_OSPF6D], [1], [ospf6d]) +]) + +AS_IF([test "$enable_ldpd" != "no"], [ + AC_DEFINE([HAVE_LDPD], [1], [ldpd]) +]) + +AS_IF([test "$enable_nhrpd" != "no"], [ + AC_DEFINE([HAVE_NHRPD], [1], [nhrpd]) +]) + +AS_IF([test "$enable_eigrpd" != "no"], [ + AC_DEFINE([HAVE_EIGRPD], [1], [eigrpd]) +]) + +AS_IF([test "$enable_babeld" != "no"], [ + AC_DEFINE([HAVE_BABELD], [1], [babeld]) +]) + +AS_IF([test "$enable_isisd" != "no"], [ + AC_DEFINE([HAVE_ISISD], [1], [isisd]) +]) + +AS_IF([test "$enable_pimd" != "no"], [ + AC_DEFINE([HAVE_PIMD], [1], [pimd]) +]) + +AS_IF([test "$enable_pim6d" != "no"], [ + AC_DEFINE([HAVE_PIM6D], [1], [pim6d]) +]) + +AS_IF([test "$enable_pbrd" != "no"], [ + AC_DEFINE([HAVE_PBRD], [1], [pbrd]) +]) + +AS_IF([test "$enable_sharpd" = "yes"], [ + AC_DEFINE([HAVE_SHARPD], [1], [sharpd]) +]) + +AS_IF([test "$enable_staticd" != "no"], [ + AC_DEFINE([HAVE_STATICD], [1], [staticd]) +]) + +AS_IF([test "$enable_fabricd" != "no"], [ + AC_DEFINE([HAVE_FABRICD], [1], [fabricd]) +]) + +AS_IF([test "$enable_vrrpd" != "no"], [ + AC_DEFINE([HAVE_VRRPD], [1], [vrrpd]) +]) + +if test "$enable_bfdd" = "no"; then + AC_DEFINE([HAVE_BFDD], [0], [bfdd]) + BFDD="" +else + AC_DEFINE([HAVE_BFDD], [1], [bfdd]) + BFDD="bfdd" + + case $host_os in + linux*) + AC_DEFINE([BFD_LINUX], [1], [bfdd]) + ;; + + *) + AC_DEFINE([BFD_BSD], [1], [bfdd]) + ;; + esac +fi + +AS_IF([test "$enable_pathd" != "no"], [ + AC_DEFINE([HAVE_PATHD], [1], [pathd]) +]) + +if test "$ac_cv_lib_json_c_json_object_get" = "no" -a "$BFDD" = "bfdd"; then + AC_MSG_ERROR(["you must use json-c library to use bfdd"]) +fi + +NHRPD="" +case "$host_os" in + linux*) + case "${enable_nhrpd}" in + no) + ;; + yes) + if test "$enable_clippy_only" != "yes"; then + if test "$c_ares_found" != "true" ; then + AC_MSG_ERROR([nhrpd requires libcares. Please install c-ares and its -dev headers.]) + fi + fi + NHRPD="nhrpd" + ;; + *) + if test "$c_ares_found" = "true" ; then + NHRPD="nhrpd" + fi + ;; + esac + ;; + *) + if test "$enable_nhrpd" = "yes"; then + AC_MSG_ERROR([nhrpd requires kernel APIs that are only present on Linux.]) + fi + ;; +esac + +if test "$enable_watchfrr" = "no";then + WATCHFRR="" +else + WATCHFRR="watchfrr" +fi + +OSPFCLIENT="" +if test "$enable_ospfapi" != "no";then + AC_DEFINE([SUPPORT_OSPF_API], [1], [OSPFAPI]) + + if test "$enable_ospfclient" != "no";then + OSPFCLIENT="ospfclient" + fi +fi + +if test "$enable_bgp_announce" = "no";then + AC_DEFINE([DISABLE_BGP_ANNOUNCE], [1], [Disable BGP installation to zebra]) +else + AC_DEFINE([DISABLE_BGP_ANNOUNCE], [0], [Disable BGP installation to zebra]) +fi + +if test "$enable_bgp_vnc" != "no";then + AC_DEFINE([ENABLE_BGP_VNC], [1], [Enable BGP VNC support]) +fi + +bgpd_bmp=false +case "${enable_bgp_bmp}" in + no) + ;; + yes) + if test "$c_ares_found" != "true" ; then + AC_MSG_ERROR([BMP support requires libcares. Please install c-ares and its -dev headers.]) + fi + bgpd_bmp=true + ;; + *) + if test "$c_ares_found" = "true" ; then + bgpd_bmp=true + fi + ;; +esac + +if test "$enable_version_build_config" != "no";then + AC_DEFINE([ENABLE_VERSION_BUILD_CONFIG], [1], [Report build configs in show version]) +fi + +dnl ########################################################################## +dnl LARGE if block +if test "$enable_clippy_only" != "yes"; then +dnl ########################################################################## + +dnl ------------------ +dnl check Net-SNMP library +dnl ------------------ +if test "$enable_snmp" != "" -a "$enable_snmp" != "no"; then + AC_PATH_TOOL([NETSNMP_CONFIG], [net-snmp-config], [no]) + if test "$NETSNMP_CONFIG" = "no"; then + AC_MSG_ERROR([--enable-snmp given but unable to find net-snmp-config]) + fi + SNMP_LIBS="`${NETSNMP_CONFIG} --agent-libs`" + SNMP_CFLAGS="`${NETSNMP_CONFIG} --base-cflags`" + # net-snmp lists all of its own dependencies. we absolutely do not want that + # among other things we avoid a GPL vs. OpenSSL license conflict here + for removelib in crypto ssl sensors pci wrap; do + SNMP_LIBS="`echo $SNMP_LIBS | sed -e 's/\(^\|\s\)-l'$removelib'\b/ /g' -e 's/\(^\|\s\)\([^\s]*\/\)\?lib'$removelib'\.[^\s]\+\b/ /g'`" + done + AC_MSG_CHECKING([whether we can link to Net-SNMP]) + AC_LINK_IFELSE_FLAGS([$SNMP_CFLAGS], [$SNMP_LIBS], [AC_LANG_PROGRAM([ +int main(void); +], +[ +{ + return 0; +} +])], [ + AC_MSG_RESULT([no]) + AC_MSG_ERROR([--enable-snmp given but not usable])]) + case "${enable_snmp}" in + yes) + SNMP_METHOD=agentx + ;; + agentx) + SNMP_METHOD="${enable_snmp}" + ;; + *) + AC_MSG_ERROR([--enable-snmp given with an unknown method (${enable_snmp}). Use yes or agentx]) + ;; + esac + AH_TEMPLATE([SNMP_AGENTX], [Use SNMP AgentX to interface with snmpd]) + AC_DEFINE_UNQUOTED(AS_TR_CPP(SNMP_${SNMP_METHOD}),,[SNMP method to interface with snmpd]) +fi +AC_SUBST([SNMP_LIBS]) +AC_SUBST([SNMP_CFLAGS]) + +dnl --------------- +dnl libyang +dnl --------------- +PKG_CHECK_MODULES([LIBYANG], [libyang >= 2.1.128], , [ +AC_MSG_ERROR([m4_normalize([libyang >= 2.1.128 is required, and was not found on your system. +Please consult doc/developer/building-libyang.rst for instructions on installing or building libyang.])])]) +ac_cflags_save="$CFLAGS" +CFLAGS="$CFLAGS $LIBYANG_CFLAGS" +AC_CHECK_MEMBER([struct lyd_node.priv], [], [ + AC_MSG_ERROR([m4_normalize([ + libyang needs to be compiled with ENABLE_LYD_PRIV=ON. + Instructions for this are included in the build documentation for your platform at http://docs.frrouting.org/projects/dev-guide/en/latest/building.html]) + ]) +], [[#include ]]) + +AC_CHECK_LIB([yang],[lyd_find_xpath3],[],[AC_MSG_ERROR([m4_normalize([ +libyang missing lyd_find_xpath3])])]) +dnl -- don't add lyd_new_list3 to this list unless bug is fixed upstream +dnl -- https://github.com/CESNET/libyang/issues/2149 +AC_CHECK_FUNCS([ly_strerrcode ly_strvecode lyd_trim_xpath]) + +CFLAGS="$ac_cflags_save" + +dnl --------------- +dnl configuration rollbacks +dnl --------------- +SQLITE3=false +if test "$enable_config_rollbacks" = "yes"; then + PKG_CHECK_MODULES([SQLITE3], [sqlite3], [ + AC_DEFINE([HAVE_CONFIG_ROLLBACKS], [1], [Enable configuration rollbacks]) + AC_DEFINE([HAVE_SQLITE3], [1], [Enable sqlite3 database]) + SQLITE3=true + ], [ + AC_MSG_ERROR([--enable-config-rollbacks given but sqlite3 was not found on your system.]) + ]) +fi + +dnl --------------- +dnl sysrepo +dnl --------------- +if test "$enable_sysrepo" = "yes"; then + PKG_CHECK_MODULES([SYSREPO], [sysrepo >= 2.1.42], + [AC_DEFINE([HAVE_SYSREPO], [1], [Enable sysrepo integration]) + SYSREPO=true], + [SYSREPO=false + AC_MSG_ERROR([sysrepo was not found on your system.])] + ) +fi + +dnl --------------- +dnl gRPC +dnl --------------- +if test "$enable_grpc" = "yes"; then + AC_LANG_PUSH([C++]) + AX_CXX_COMPILE_STDCXX([11], [ext]) + PKG_CHECK_MODULES([GRPC], [grpc >= 6.0.0 grpc++ >= 1.16.1 protobuf >= 3.6.1 ], [ + AC_CHECK_PROGS([PROTOC], [protoc], [/bin/false]) + if test "$PROTOC" = "/bin/false"; then + AC_MSG_FAILURE([grpc requested but protoc not found.]) + fi + + AC_DEFINE([HAVE_GRPC], [1], [Enable the gRPC northbound plugin]) + GRPC=true + ], [ + GRPC=false + AC_MSG_ERROR([grpc/grpc++ were not found on your system.]) + ]) + AC_LANG_POP([C++]) +fi + +dnl --------- +dnl DPDK +dnl --------- +if test "$enable_dp_dpdk" = "yes"; then + PKG_CHECK_MODULES([DPDK], [libdpdk], [ + AC_DEFINE([HAVE_DPDK], [1], [Enable DPDK backend]) + DPDK=true + ], [ + AC_MSG_ERROR([configuration specifies --enable-dp-dpdk but DPDK libs were not found]) + ]) +fi + +dnl ----- +dnl LTTng +dnl ----- +if test "$enable_lttng" = "yes"; then + PKG_CHECK_MODULES([UST], [lttng-ust >= 2.9.0], [ + AC_DEFINE([HAVE_LTTNG], [1], [Enable LTTng support]) + LTTNG=true + ], [ + AC_MSG_ERROR([configuration specifies --enable-lttng but lttng-ust was not found]) + ]) +fi + +dnl ---- +dnl USDT +dnl ---- +if test "$enable_usdt" = "yes"; then + AC_CHECK_HEADERS([sys/sdt.h], [ + AC_DEFINE([HAVE_USDT], [1], [Enable USDT probes]) + USDT=true + ], [ + AC_MSG_ERROR([configuration specifies --enable-usdt but no USDT kernel headers (sys/sdt.h) found]) + ]) +fi + +dnl ------ +dnl ZeroMQ +dnl ------ +if test "$enable_zeromq" != "no"; then + PKG_CHECK_MODULES([ZEROMQ], [libzmq >= 4.0.0], [ + AC_DEFINE([HAVE_ZEROMQ], [1], [Enable ZeroMQ support]) + ZEROMQ=true + ], [ + if test "$enable_zeromq" = "yes"; then + AC_MSG_ERROR([configuration specifies --enable-zeromq but libzmq was not found]) + fi + ]) +fi + +dnl ------------------------------------ +dnl Enable RPKI and add librtr to libs +dnl ------------------------------------ +if test "$enable_rpki" = "yes"; then + PKG_CHECK_MODULES([RTRLIB], [rtrlib >= 0.8.0], + [RPKI=true], + [RPKI=false + AC_MSG_ERROR([rtrlib was not found on your system or is too old.])] + ) +fi + +dnl ------------------------------------ +dnl pimd and pim6d not supported on OpenBSD and MacOS +dnl ------------------------------------ +if test "$enable_pimd" != "no"; then +AC_MSG_CHECKING([for pimd OS support]) +case "$host_os" in + darwin*) + AC_MSG_RESULT([no]) + enable_pimd="no" + ;; + openbsd*) + AC_MSG_RESULT([no]) + enable_pimd="no" + ;; + *) + AC_MSG_RESULT([yes]) + ;; +esac +fi + +if test "$enable_pim6d" != "no"; then +AC_MSG_CHECKING([for pim6d OS support]) +case "$host_os" in + linux*) + AC_MSG_RESULT([yes]) + ;; + *) + AC_MSG_RESULT([no]) + enable_pim6d="no" + ;; +esac +fi + +dnl ------------------------------------- +dnl VRRP is only supported on linux +dnl ------------------------------------- +if test "$enable_vrrpd" != "no"; then +AC_MSG_CHECKING([for VRRP OS support]) +case "$host_os" in + linux*) + AC_MSG_RESULT([yes]) + ;; + *) + AC_MSG_RESULT([no]) + enable_vrrpd="no" + ;; +esac +fi + +dnl ------------------------------------------ +dnl Check whether rtrlib was build with ssh support +dnl ------------------------------------------ +AC_MSG_CHECKING([whether the RTR Library is compiled with SSH]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include "rtrlib/rtrlib.h"]], + [[struct tr_ssh_config config;]])], + [AC_MSG_RESULT([yes]) + AC_DEFINE([FOUND_SSH], [1], [found_ssh])], + AC_MSG_RESULT([no]) +) + +dnl --------------- +dnl dlopen & dlinfo +dnl --------------- +LIBS_save="$LIBS" +AC_SEARCH_LIBS([dlopen], [dl dld], [], [ + AC_MSG_ERROR([unable to find the dlopen()]) +]) + +AC_CHECK_HEADERS([link.h]) + +AC_CACHE_CHECK([for dlinfo(RTLD_DI_ORIGIN)], [frr_cv_rtld_di_origin], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include +#ifdef HAVE_LINK_H +#include +#endif +#include +]], [[ + char origin[1]; + dlinfo (NULL, RTLD_DI_ORIGIN, &origin); +]])], [ + frr_cv_rtld_di_origin=yes + ], [ + frr_cv_rtld_di_origin=no + ]) +]) +if test "$frr_cv_rtld_di_origin" = "yes"; then + AC_DEFINE([HAVE_DLINFO_ORIGIN], [1], [Have dlinfo RTLD_DI_ORIGIN]) +fi + +AC_CACHE_CHECK([for dlinfo(RTLD_DI_LINKMAP)], [frr_cv_rtld_di_linkmap], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include +#ifdef HAVE_LINK_H +#include +#endif +#include +]], [[ + struct link_map *lm = NULL; + dlinfo (NULL, RTLD_DI_LINKMAP, &lm); +]])], [ + frr_cv_rtld_di_linkmap=yes + ], [ + frr_cv_rtld_di_linkmap=no + ]) +]) +if test "$frr_cv_rtld_di_linkmap" = "yes"; then + AC_DEFINE([HAVE_DLINFO_LINKMAP], [1], [Have dlinfo RTLD_DI_LINKMAP]) +fi + +LIBDL="$LIBS" +LIBDL="${LIBDL%$LIBS_save}" +LIBDL="${LIBDL#$LIBS_save}" +AC_SUBST([LIBDL]) +LIBS="$LIBS_save" +unset LIBS_save + +dnl ########################################################################## +dnl test "$enable_clippy_only" != "yes" +fi +dnl END OF LARGE if block +dnl ########################################################################## + +dnl --------------------------- +dnl sockaddr and netinet checks +dnl --------------------------- +AC_CHECK_TYPES([ + struct sockaddr_dl, + struct vifctl, struct mfcctl, struct sioc_sg_req, + vifi_t, struct sioc_vif_req, struct igmpmsg, + struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq, + struct nd_opt_adv_interval, + struct nd_opt_homeagent_info, struct nd_opt_adv_interval, + struct nd_opt_rdnss, struct nd_opt_dnssl], + [], [], FRR_INCLUDES) + +AC_CHECK_MEMBERS([struct sockaddr.sa_len, + struct sockaddr_in.sin_len, struct sockaddr_un.sun_len, + struct sockaddr_dl.sdl_len, + struct if6_aliasreq.ifra_lifetime, + struct nd_opt_adv_interval.nd_opt_ai_type], + [], [], FRR_INCLUDES) + +dnl --------------------------- +dnl IRDP/pktinfo/icmphdr checks +dnl --------------------------- + +AC_CHECK_TYPES([struct in_pktinfo], [ + AC_CHECK_TYPES([struct icmphdr], [ + IRDP=true + ], [ + IRDP=false + ], [FRR_INCLUDES]) +], [ + IRDP=false +], [FRR_INCLUDES]) + +case "${enable_irdp}" in +yes) + $IRDP || AC_MSG_ERROR(['IRDP requires in_pktinfo at the moment!']) + ;; +no) + IRDP=false + ;; +*) + IRDP=false + ;; +esac + + +dnl ----------------------- +dnl checking for IP_PKTINFO +dnl ----------------------- +AC_MSG_CHECKING([for IP_PKTINFO]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + ]], [[ + int opt = IP_PKTINFO; + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_IP_PKTINFO], [1], [Have IP_PKTINFO]) +],[ + AC_MSG_RESULT([no]) +]) + +dnl --------------------------- +dnl checking for IP_RECVDSTADDR +dnl --------------------------- +AC_MSG_CHECKING([for IP_RECVDSTADDR]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + ]], [[ + int opt = IP_RECVDSTADDR; + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_IP_RECVDSTADDR], [1], [Have IP_RECVDSTADDR]) +],[ + AC_MSG_RESULT([no]) +]) + +dnl ---------------------- +dnl checking for IP_RECVIF +dnl ---------------------- +AC_MSG_CHECKING([for IP_RECVIF]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + ]], [[ + int opt = IP_RECVIF; + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_IP_RECVIF], [1], [Have IP_RECVIF]) +],[ + AC_MSG_RESULT([no]) +]) + +dnl ---------------------- +dnl checking for SO_BINDANY +dnl ---------------------- +AC_MSG_CHECKING([for SO_BINDANY]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + ]], [[ + int opt = SO_BINDANY; + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_SO_BINDANY], [1], [Have SO_BINDANY]) +],[ + AC_MSG_RESULT([no]) +]) + +dnl ---------------------- +dnl checking for IP_FREEBIND +dnl ---------------------- +AC_MSG_CHECKING([for IP_FREEBIND]) +AC_COMPILE_IFELSE( +[ AC_LANG_PROGRAM([[ + #include + ]], [[ + int opt = IP_FREEBIND; + ]]) +],[ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_IP_FREEBIND], [1], [Have IP_FREEBIND]) +],[ + AC_MSG_RESULT([no]) +]) + +dnl -------------------------------------- +dnl checking for be32dec existence or not +dnl -------------------------------------- +AC_CHECK_DECLS([be32enc, be32dec], [], [], [ + #ifdef HAVE_SYS_ENDIAN_H + #include + #endif + #ifdef HAVE_ENDIAN_H + #include + #endif +]) + +dnl -------------------------------------- +dnl checking for clock_time monotonic struct and call +dnl -------------------------------------- +AC_CHECK_DECL([CLOCK_MONOTONIC], + [AC_CHECK_LIB([rt], [clock_gettime], [LIBS="$LIBS -lrt"]) + AC_DEFINE([HAVE_CLOCK_MONOTONIC], [1], [Have monotonic clock]) +], [AC_MSG_RESULT([no])], [FRR_INCLUDES]) + +AC_CHECK_DECL([CLOCK_THREAD_CPUTIME_ID], [ + AC_DEFINE([HAVE_CLOCK_THREAD_CPUTIME_ID], [1], [Have cpu-time clock]) +], [AC_MSG_RESULT([no])], [FRR_INCLUDES]) + +AC_SEARCH_LIBS([clock_nanosleep], [rt], [ + AC_DEFINE([HAVE_CLOCK_NANOSLEEP], [1], [Have clock_nanosleep()]) +]) + +dnl -------------------------------------- +dnl checking for flex and bison +dnl -------------------------------------- + +dnl autoconf 2.69 AC_PROG_LEX has no parameters +dnl autoconf 2.70 AC_PROG_LEX prints a deprecation warning without params +m4_if(m4_version_compare(m4_defn([AC_AUTOCONF_VERSION]), [2.70]), [-1], [dnl + dnl autoconf < 2.70 + AC_PROG_LEX +], [ + dnl autoconf >= 2.70 + AC_PROG_LEX([noyywrap]) +]) + +AC_MSG_CHECKING([version of flex]) +frr_ac_flex_version="$(eval $LEX -V | grep flex | head -n 1)" +frr_ac_flex_version="${frr_ac_flex_version##* }" +AC_MSG_RESULT([$frr_ac_flex_version]) +AX_COMPARE_VERSION([$frr_ac_flex_version], [lt], [2.5.20], [ + LEX="$SHELL $missing_dir/missing flex" + if test -f "${srcdir}/lib/command_lex.c" -a -f "${srcdir}/lib/command_lex.h"; then + AC_MSG_WARN([using pregenerated flex output files]) + else + AC_MSG_ERROR([flex failure and pregenerated files not included (probably a git build)]) + fi + AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy]) + AC_SUBST([LEXLIB], ['']) +]) + +AC_PROG_YACC +dnl thanks GNU bison for this b*llshit... +AC_MSG_CHECKING([version of bison]) +frr_ac_bison_version="$(eval $YACC -V | grep bison | head -n 1)" +frr_ac_bison_version="${frr_ac_bison_version##* }" +frr_ac_bison_missing="false" +case "x${frr_ac_bison_version}x" in + x2.7*) + BISON_OPENBRACE='"' + BISON_CLOSEBRACE='"' + BISON_VERBOSE='' + AC_MSG_RESULT([$frr_ac_bison_version - 2.7 or older]) + ;; + x2.*|x1.*) + AC_MSG_RESULT([$frr_ac_bison_version]) + AC_MSG_WARN([installed bison is too old. Please install GNU bison 2.7.x or newer.]) + frr_ac_bison_missing="true" + ;; + x) + AC_MSG_RESULT([none]) + AC_MSG_WARN([could not determine bison version. Please install GNU bison 2.7.x or newer.]) + frr_ac_bison_missing="true" + ;; + x3.[012][^0-9]*) + BISON_OPENBRACE='{' + BISON_CLOSEBRACE='}' + BISON_VERBOSE='-Dparse.error=verbose' + AC_MSG_RESULT([$frr_ac_bison_version - 3.0 to 3.2]) + ;; + *) + BISON_OPENBRACE='{' + BISON_CLOSEBRACE='}' + BISON_VERBOSE='-Dparse.error=verbose -Wno-yacc' + AC_MSG_RESULT([$frr_ac_bison_version - 3.3 or newer]) + ;; +esac +AC_SUBST([BISON_OPENBRACE]) +AC_SUBST([BISON_CLOSEBRACE]) +AC_SUBST([BISON_VERBOSE]) + +if $frr_ac_bison_missing; then + YACC="$SHELL $missing_dir/missing bison -y" + if test -f "${srcdir}/lib/command_parse.c" -a -f "${srcdir}/lib/command_parse.h"; then + AC_MSG_WARN([using pregenerated bison output files]) + else + AC_MSG_ERROR([bison failure and pregenerated files not included (probably a git build)]) + fi +fi + +dnl ------------------- +dnl capabilities checks +dnl ------------------- +if test "$enable_capabilities" != "no"; then + AC_MSG_CHECKING([whether prctl PR_SET_KEEPCAPS is available]) + AC_COMPILE_IFELSE( + [ AC_LANG_PROGRAM([[ + #include + ]], [[ + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + ]]) + ],[AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_PR_SET_KEEPCAPS], [1], [prctl]) + frr_ac_keepcaps="yes" + ],[AC_MSG_RESULT(no) + ]) + if test "$frr_ac_keepcaps" = "yes"; then + AC_CHECK_HEADERS([sys/capability.h]) + fi + if test "$ac_cv_header_sys_capability_h" = "yes"; then + AC_CHECK_LIB([cap], [cap_init], + [AC_DEFINE([HAVE_LCAPS], [1], [Capabilities]) + LIBCAP="-lcap" + frr_ac_lcaps="yes"] + ) + fi + if test "$frr_ac_scaps" = "yes" \ + -o "$frr_ac_lcaps" = "yes"; then + AC_DEFINE([HAVE_CAPABILITIES], [1], [capabilities]) + fi + + case "$host_os" in + linux*) + if test "$enable_clippy_only" != "yes"; then + if test "$frr_ac_lcaps" != "yes"; then + AC_MSG_ERROR([libcap and/or its headers were not found. Running FRR without libcap support built in causes a huge performance penalty.]) + fi + fi + ;; + esac +else + case "$host_os" in + linux*) + AC_MSG_WARN([Running FRR without libcap support built in causes a huge performance penalty.]) + ;; + esac +fi +AC_SUBST([LIBCAP]) + +dnl --------------------------- +dnl check for glibc 'backtrace' +dnl --------------------------- +if test "$enable_backtrace" != "no" ; then + backtrace_ok=no + PKG_CHECK_MODULES([UNWIND], [libunwind], [ + AC_DEFINE([HAVE_LIBUNWIND], [1], [libunwind]) + backtrace_ok=yes + ], [ + true + ]) + + if test "$backtrace_ok" = "no"; then + AC_CHECK_HEADER([unwind.h], [ + AC_SEARCH_LIBS([unw_getcontext], [unwind], [ + AC_DEFINE([HAVE_LIBUNWIND], [1], [libunwind]) + backtrace_ok=yes + ]) + ]) + fi + + if test "$backtrace_ok" = "no"; then + AC_CHECK_HEADER([execinfo.h], [ + AC_SEARCH_LIBS([backtrace], [execinfo], [ + AC_DEFINE([HAVE_GLIBC_BACKTRACE], [1], [Glibc backtrace]) + backtrace_ok=yes + ],, [-lm]) + ]) + fi + + if test "$enable_backtrace" = "yes" -a "$backtrace_ok" = "no"; then + dnl user explicitly requested backtrace but we failed to find support + AC_MSG_FAILURE([failed to find backtrace or libunwind support]) + fi +fi + +dnl ----------------------------------------- +dnl check for malloc mallinfo struct and call +dnl this must try and link using LIBS, in +dnl order to check no alternative allocator +dnl has been specified, which might not provide +dnl mallinfo +dnl ----------------------------------------- +AC_CHECK_HEADERS([malloc.h malloc_np.h malloc/malloc.h],,, [FRR_INCLUDES]) + +AC_CACHE_CHECK([whether mallinfo is available], [frr_cv_mallinfo], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [ +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_NP_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +]], [[ +struct mallinfo ac_x; ac_x = mallinfo (); +]])], [ + frr_cv_mallinfo=yes + ], [ + frr_cv_mallinfo=no + ]) +]) +if test "$frr_cv_mallinfo" = "yes"; then + AC_DEFINE([HAVE_MALLINFO], [1], [mallinfo]) +fi + +AC_CACHE_CHECK([whether mallinfo2 is available], [frr_cv_mallinfo2], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [ +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_NP_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +]], [[ +struct mallinfo2 ac_x; ac_x = mallinfo2 (); +]])], [ + frr_cv_mallinfo2=yes + ], [ + frr_cv_mallinfo2=no + ]) +]) +if test "$frr_cv_mallinfo2" = "yes"; then + AC_DEFINE([HAVE_MALLINFO2], [1], [mallinfo2]) +fi + +AC_MSG_CHECKING([whether malloc_usable_size is available]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [ +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +]], [[ +size_t ac_x; ac_x = malloc_usable_size(NULL); +]])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_MALLOC_USABLE_SIZE], [1], [malloc_usable_size]) +], [ + AC_MSG_RESULT([no]) + + AC_MSG_CHECKING([whether malloc_size is available]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +]], [[ +size_t ac_x; ac_x = malloc_size(NULL); +]])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_MALLOC_SIZE], [1], [malloc_size]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +dnl ---------- +dnl configure date +dnl ---------- +dev_version=`echo $VERSION | grep dev` +#don't expire deprecated code in non 'dev' branch +if test "$dev_version" = ""; then + CONFDATE=0 +else + CONFDATE=`date '+%Y%m%d'` +fi +AC_SUBST([CONFDATE]) + +dnl get the full path, recursing through variables... +AC_DEFUN([AX_SUBST_EXPANDED], [ + AX_RECURSIVE_EVAL([[$]$1], [e_$1]) + AC_SUBST([e_$1]) +]) + +AX_SUBST_EXPANDED([bindir]) +AX_SUBST_EXPANDED([sbindir]) +AX_SUBST_EXPANDED([frr_sysconfdir]) +AX_SUBST_EXPANDED([frr_runstatedir]) +AX_SUBST_EXPANDED([frr_libstatedir]) +AX_SUBST_EXPANDED([moduledir]) +AX_SUBST_EXPANDED([yangmodelsdir]) +AX_SUBST_EXPANDED([scriptdir]) + +dnl strip duplicate trailing slashes if necessary +dnl note this uses e_bindir / e_sbindir created above +watchfrr_sh="\${e_sbindir%/}/watchfrr.sh" +AX_SUBST_EXPANDED([watchfrr_sh]) +vtysh_bin="\${e_bindir%/}/vtysh" +AX_SUBST_EXPANDED([vtysh_bin]) + +dnl various features +AM_CONDITIONAL([SUPPORT_REALMS], [test "$enable_realms" = "yes"]) +AM_CONDITIONAL([ENABLE_BGP_VNC], [test "$enable_bgp_vnc" != "no"]) +AM_CONDITIONAL([BGP_BMP], [$bgpd_bmp]) +dnl northbound +AM_CONDITIONAL([SQLITE3], [$SQLITE3]) +AM_CONDITIONAL([SYSREPO], [test "$enable_sysrepo" = "yes"]) +AM_CONDITIONAL([GRPC], [test "$enable_grpc" = "yes"]) +AM_CONDITIONAL([ZEROMQ], [test "$ZEROMQ" = "true"]) +dnl plugins +AM_CONDITIONAL([RPKI], [test "$RPKI" = "true"]) +AM_CONDITIONAL([SNMP], [test "$SNMP_METHOD" = "agentx"]) +AM_CONDITIONAL([IRDP], [$IRDP]) +AM_CONDITIONAL([FPM], [test "$enable_fpm" = "yes"]) +AM_CONDITIONAL([HAVE_PROTOBUF], [test "$enable_protobuf" != "no"]) +AM_CONDITIONAL([HAVE_PROTOBUF3], [$PROTO3]) + +dnl PCEP plugin +AS_IF([test "$enable_pathd" != "no"], [ + AC_SUBST([PATHD_PCEP_LIBS], ["pceplib/libpcep_pcc.la"]) + AC_SUBST([PATHD_PCEP_INCL], ["-I./pceplib "]) + ]) +AC_CHECK_LIB([cunit], [CU_initialize_registry], [pcep_cunit=yes],[pcep_cunit=no]) +AM_CONDITIONAL([PATHD_PCEP_TEST], [test "x${pcep_cunit}" = xyes]) +AC_CHECK_PROG(VALGRIND_CHECK, valgrind, yes) +AM_CONDITIONAL([HAVE_VALGRIND_PCEP], [test "$VALGRIND_CHECK" = "yes"]) + +dnl daemons +AM_CONDITIONAL([VTYSH], [test "$VTYSH" = "vtysh"]) +AM_CONDITIONAL([ZEBRA], [test "$enable_zebra" != "no"]) +AM_CONDITIONAL([BGPD], [test "$enable_bgpd" != "no"]) +AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"]) +AM_CONDITIONAL([MGMTD_TESTC], [test "$enable_mgmtd_test_be_client" = "yes"]) +AM_CONDITIONAL([FPM_LISTENER], [test "enable_fpm_listener" = "yes"]) +AM_CONDITIONAL([RIPD], [test "$enable_ripd" != "no"]) +AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"]) +AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"]) +AM_CONDITIONAL([BFDD], [test "$BFDD" = "bfdd"]) +AM_CONDITIONAL([NHRPD], [test "$NHRPD" = "nhrpd"]) +AM_CONDITIONAL([EIGRPD], [test "$enable_eigrpd" != "no"]) +AM_CONDITIONAL([WATCHFRR], [test "$WATCHFRR" = "watchfrr"]) +AM_CONDITIONAL([OSPFCLIENT], [test "$OSPFCLIENT" = "ospfclient"]) +AM_CONDITIONAL([RIPNGD], [test "$enable_ripngd" != "no"]) +AM_CONDITIONAL([BABELD], [test "$enable_babeld" != "no"]) +AM_CONDITIONAL([OSPF6D], [test "$enable_ospf6d" != "no"]) +AM_CONDITIONAL([ISISD], [test "$enable_isisd" != "no"]) +AM_CONDITIONAL([PIMD], [test "$enable_pimd" != "no"]) +AM_CONDITIONAL([PIM6D], [test "$enable_pim6d" != "no"]) +AM_CONDITIONAL([PBRD], [test "$enable_pbrd" != "no"]) +AM_CONDITIONAL([SHARPD], [test "$enable_sharpd" = "yes"]) +AM_CONDITIONAL([STATICD], [test "$enable_staticd" != "no"]) +AM_CONDITIONAL([FABRICD], [test "$enable_fabricd" != "no"]) +AM_CONDITIONAL([VRRPD], [test "$enable_vrrpd" != "no"]) +AM_CONDITIONAL([PATHD], [test "$enable_pathd" != "no"]) +AM_CONDITIONAL([PATHD_PCEP], [test "$enable_pathd" != "no"]) +AM_CONDITIONAL([DP_DPDK], [test "$enable_dp_dpdk" = "yes"]) + +AC_CONFIG_FILES([Makefile],[ + test "$enable_dev_build" = "yes" && makefile_devbuild="--dev-build" + ${PYTHON} "${ac_abs_top_srcdir}/python/makefile.py" ${makefile_devbuild} || exit 1 +], [ + PYTHON="$PYTHON" + enable_dev_build="$enable_dev_build" +]) + +AC_CONFIG_FILES([ + config.version + redhat/frr.spec + alpine/APKBUILD + snapcraft/snapcraft.yaml + lib/version.h + lib/config_paths.h + tests/lib/cli/test_cli.refout pkgsrc/mgmtd.sh + pkgsrc/bgpd.sh pkgsrc/ospf6d.sh pkgsrc/ospfd.sh + pkgsrc/ripd.sh pkgsrc/ripngd.sh pkgsrc/zebra.sh + pkgsrc/eigrpd.sh]) + +AC_CONFIG_FILES([tools/frr], [chmod +x tools/frr]) +AC_CONFIG_FILES([tools/watchfrr.sh], [chmod +x tools/watchfrr.sh]) +AC_CONFIG_FILES([tools/frrinit.sh], [chmod +x tools/frrinit.sh]) +AC_CONFIG_FILES([tools/frrcommon.sh]) +AC_CONFIG_FILES([tools/frr.service]) +AC_CONFIG_FILES([tools/frr@.service]) + +# dnl write out a ccls file with our compile configuration +# dnl have to add -Wno-unused-function otherwise foobar_cmd_magic causes +# dnl all DEFPY(), et al., macros to flag as errors. +AS_IF([test "$enable_ccls" = "yes"], [ + AC_CONFIG_COMMANDS([gen-dot-ccls], [ + cat > "${srcdir}/.ccls" <> "${srcdir}/.ccls" + fi + if test -n "$FRR_ALL_CCLS_FLAGS"; then + echo ${FRR_ALL_CCLS_FLAGS} | tr ' ' '\n' >> "${srcdir}/.ccls" + fi + if test -n "$FRR_ALL_CCLS_CFLAGS"; then + cat >> "${srcdir}/.ccls" < "${dst}.tmp" + test -f "$dst" \ + && diff "${dst}.tmp" "${dst}" >/dev/null 2>/dev/null \ + && rm "${dst}.tmp" \ + || mv "${dst}.tmp" "${dst}" + ], [ + PERL="$PERL" + ]) +]) + +## Hack, but working solution to avoid rebuilding of frr.info. +## It's already in CVS until texinfo 4.7 is more common. +AC_OUTPUT + +if test "$enable_rpath" = "yes" ; then + true +else + # See https://old-en.opensuse.org/openSUSE:Packaging_Guidelines#Removing_Rpath + sed -e 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool > libtool.sed && cat libtool.sed > libtool + sed -e 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool > libtool.sed && cat libtool.sed > libtool + test -f libtool.sed && rm libtool.sed +fi + +echo " +FRRouting configuration +------------------------------ +FRR version : ${PACKAGE_VERSION} +host operating system : ${host_os} +source code location : ${srcdir} +compiler : ${CC} +compiler flags : ${CFLAGS} ${WERROR} ${AC_CFLAGS} ${SAN_FLAGS} +make : ${MAKE-make} +linker flags : ${LDFLAGS} ${SAN_FLAGS} ${LIBS} ${LIBCAP} ${LIBREADLINE} ${LIBM} +state file directory : ${e_frr_runstatedir} +config file directory : ${e_frr_sysconfdir} +module directory : ${e_moduledir} +script directory : ${e_scriptdir} +user to run as : ${enable_user} +group to run as : ${enable_group} +group for vty sockets : ${enable_vty_group} +config file mask : ${enable_configfile_mask} +log file mask : ${enable_logfile_mask} +zebra protobuf enabled : ${enable_protobuf:-no} +vici socket path : ${vici_socket} + +The above user and group must have read/write access to the state file +directory and to the config files in the config file directory." + +if test -n "$enable_datacenter"; then + AC_MSG_WARN([The --enable-datacenter compile time option is deprecated. Please modify the init script to pass -F datacenter to the daemons instead.]) +fi + +if test "$enable_doc" != "no" -a "$frr_py_mod_sphinx" = "false"; then + AC_MSG_WARN([sphinx is missing but required to build documentation]) +fi +if test "$frr_py_mod_pytest" = "false"; then + AC_MSG_WARN([pytest is missing, unit tests cannot be performed]) +fi + +if $path_warn_banner; then + AC_MSG_WARN([^]) + AC_MSG_WARN([^]) + AC_MSG_WARN([^ warnings regarding system path configuration were printed at the very top of output]) + AC_MSG_WARN([^ paths have been adjusted by temporary workarounds]) + AC_MSG_WARN([^ please fix your ./configure invocation (remove /frr) so it will work without the workarounds]) +fi diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..fa2b508 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,21 @@ +mdate-sh +draft-zebra-00.txt +*.pdf +*.eps +frr.ps +frr.dvi +stamp-vti +*.aux +*.cp +*.cps +*.fn +*.fns +*.ky +*.kys +*.log +*.op +*.pg +*.toc +*.tp +*.vr +refix diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..3b4d3d2 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,18 @@ +all: ALWAYS + @$(MAKE) -s -C .. doc +%: ALWAYS + @$(MAKE) -s -C .. doc/$@ +html: + @$(MAKE) -s -C .. doc/user/_build/html/.buildinfo +info: + @$(MAKE) -s -C .. doc/user/_build/texinfo/frr.info +pdf: + @$(MAKE) -s -C .. doc/user/_build/latexpdf +frr.info: info +frr.pdf: pdf + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles html info frr.info pdf frr.pdf +.SUFFIXES: diff --git a/doc/accords/README.md b/doc/accords/README.md new file mode 100644 index 0000000..0461b04 --- /dev/null +++ b/doc/accords/README.md @@ -0,0 +1,32 @@ +FRR accords +=========== + + +This directory contains some text documents with "accords" agreed upon by the +FRR community. The idea here is that by passing text documents through the +PR/review process, consensus for work items, design decisions, etc. can be +established and documented. They can also be changed later by followup PRs if +consensus shifts. This is intended to reduce friction, and provide more +transparency for newcomers & less frequent contributors. + +Examples of things that could go here: + +- agreement on how to fix some deeper-seated existing shortcoming in FRR that + might take some time to fix, to get consensus before putting time into it. + +- larger design (especially system/package integration) decisions that are not + immediately tangible to the code. + +- scoping decisions, particularly negative (i.e. we decided at some point that + FRR is not the right place for something) - these are otherwise lost in some + ancient closed PR, and some new contributor might be unaware and waste time. + +Files in this directory are not formatted in any specific way and not rendered +into documentation. They're intended to be read with your code editor of +choice. + +To avoid misunderstandings, there is one "rule" about wording: the consensus +actual is worded with "will", "going to", "is" - this reflects the idea that +when the PR is merged, it *is* community consensus. Words like "should", +"would" or "might" should be limited to context and reference that is provided +as rationale for the consensus. diff --git a/doc/accords/cli-colors b/doc/accords/cli-colors new file mode 100644 index 0000000..04bdfc7 --- /dev/null +++ b/doc/accords/cli-colors @@ -0,0 +1,44 @@ +Adding colors to FRR CLI output +=============================== + + +There were multiple approaches/attempts to get colored output for the CLI into +FRR, most recently End of 2022 in PR #12497. After some discussion, some items +crystallized out: + +First, generally speaking, colors (or other rich output formatting) must be +used sparingly. In particular, e.g. "every IP address" is not something to +color. The output formatting needs to have an actual purpose to improve UX, +not turn it into a christmas tree. + +In the long run, the CLI will hopefully become a YANG frontend. In that case, +the CLI frontend component is a great place to apply all kinds of output/UI/UX +features. However, this is a long way off. + +That said, an implementation in the current vtysh+daemon ecosystem is not out +of the question, especially if the use of colors/formatting is limited to +important places (which is desirable anyway - see general statement above.) +We don't want to litter formatting all over every single vty_out call. + +A color option on a per-command/DEFUN level (i.e. the way `[json]` is done) was +rejected. The decision to color output must take information from vtysh's +environment into account, notably the TERM environment variable, the NO_COLOR +environment variable, and whether stdout is a terminal or not. An explicit +`--color` switch (or `terminal color` vtysh command, or other similar things) +is needed too. To be clear, the switch must not be on individual commands, it +needs to be on the vtysh session level. + +Lastly, the output pager needs to work with this. + + +Suggested implementation +------------------------ + +(not part of the consensus / accord, only to record discussion) + +As far as discussion went, the most promising approach to actually implement +this is to put some type of unconditional formatting tag into daemon's vty_out +calls. This would be some escape-like sequence - an actual ANSI color code +itself is not particularly readable or pretty, though that would work as well. +vtysh would then, while passing through the output from the daemons, replace or +remove these tags according to terminal/user settings. diff --git a/doc/accords/frr-service-is-watchfrr b/doc/accords/frr-service-is-watchfrr new file mode 100644 index 0000000..2301c83 --- /dev/null +++ b/doc/accords/frr-service-is-watchfrr @@ -0,0 +1,16 @@ +The "FRR" service unit is watchfrr +================================== + + +"FRR" on the distribution/OS level is one service (generally called "frr"). +Exposing individual daemons (zebra, staticd, bgpd, ...) as service units does +not match FRR's internal expectations. + +At some future point, watchfrr will add functionality to receive "router bgp", +"router ospf" etc. commands (or their YANG variants) and automatically start +the required daemons. In particular with multi-instance setups, this will +simplify config (no more mucking around /etc/frr/daemons - if watchfrr +understands which daemons are needed by a given config, the daemons file is +pointless.) + +This to some degree assumes an "integrated-config world". diff --git a/doc/accords/integrated-config-wins b/doc/accords/integrated-config-wins new file mode 100644 index 0000000..5a02b99 --- /dev/null +++ b/doc/accords/integrated-config-wins @@ -0,0 +1,10 @@ +Integrated config wins +====================== + + +The use of split-configuration setups (zebra.conf, staticd.conf, bgpd.conf, +etc.) in FRR is considered deprecated and will go away at some point. + +At this point there is no timeline yet on removing split-config support, and +this needs to go through an extensive deprecation period with increasingly +loud user warnings. diff --git a/doc/developer/.gitignore b/doc/developer/.gitignore new file mode 100644 index 0000000..81c60dc --- /dev/null +++ b/doc/developer/.gitignore @@ -0,0 +1,2 @@ +/_templates +/_build diff --git a/doc/developer/.readthedocs.yaml b/doc/developer/.readthedocs.yaml new file mode 100644 index 0000000..90ee5c7 --- /dev/null +++ b/doc/developer/.readthedocs.yaml @@ -0,0 +1,18 @@ +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + apt_packages: + - graphviz + +python: + install: + - requirements: doc/developer/requirements.txt + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/developer/conf.py diff --git a/doc/developer/MLD-and-PIMv6-Design.png b/doc/developer/MLD-and-PIMv6-Design.png new file mode 100644 index 0000000..b5066de Binary files /dev/null and b/doc/developer/MLD-and-PIMv6-Design.png differ diff --git a/doc/developer/Makefile b/doc/developer/Makefile new file mode 100644 index 0000000..38afb43 --- /dev/null +++ b/doc/developer/Makefile @@ -0,0 +1,16 @@ +all: ALWAYS + @$(MAKE) -s -C ../.. developer-html +help: ALWAYS + @$(MAKE) -s -C ../.. doc/help +pdf: ALWAYS + @$(MAKE) -s -C ../.. doc/developer/_build/latexpdf +info: ALWAYS + @$(MAKE) -s -C ../.. doc/developer/_build/texinfo/frr.info +%: ALWAYS + @$(MAKE) -s -C ../.. doc/developer/_build/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/doc/developer/PIMv6-Design.pptx b/doc/developer/PIMv6-Design.pptx new file mode 100644 index 0000000..fc17059 Binary files /dev/null and b/doc/developer/PIMv6-Design.pptx differ diff --git a/doc/developer/_static/overrides.css b/doc/developer/_static/overrides.css new file mode 100644 index 0000000..302b8d6 --- /dev/null +++ b/doc/developer/_static/overrides.css @@ -0,0 +1,255 @@ +/* remove max-width restriction */ +div.body { + max-width: none; +} + +/* Palette URL: http://paletton.com/#uid=70p0p0kt6uvcDRAlhBavokxLJ6w */ + +:root { +--primary-0: #F36F16; /* Main Primary color */ +--primary-1: #FFC39A; +--primary-2: #FF9A55; +--primary-3: #A34403; +--primary-4: #341500; +--primary-9: #FFF3EB; + +--secondary-1-0: #F39C16; /* Main Secondary color (1) */ +--secondary-1-1: #FFD79A; +--secondary-1-2: #FFBC55; +--secondary-1-3: #A36403; +--secondary-1-4: #341F00; +--secondary-1-9: #FFF7EB; + +--secondary-2-0: #1A599F; /* Main Secondary color (2) */ +--secondary-2-1: #92B9E5; +--secondary-2-2: #477CB8; +--secondary-2-3: #0A386B; +--secondary-2-4: #011122; +--secondary-2-9: #E3EBF4; + +--complement-0: #0E9A83; /* Main Complement color */ +--complement-1: #8AE4D4; +--complement-2: #3CB4A0; +--complement-3: #026857; +--complement-4: #00211B; +--complement-9: #E0F4F0; +} + +/* new */ + +body { + font-family: "Fira Sans", Helvetica, Arial, sans-serif; + font-weight:400; +} +h1, h2, h3, h4, h5, h6 { + font-family: "Fira Sans", Helvetica, Arial, sans-serif; + font-weight:500; +} +code, pre, tt { + font-family: "Fira Mono"; +} +h1 { + background-color:var(--secondary-1-1); + border-bottom:1px solid var(--secondary-1-0); + font-weight:300; +} +h2 { + margin-top:36pt; +} + +a, +a:hover, +a:visited, +.code-block-caption a.headerlink:hover, +.rst-content dl:not(.docutils) dt .headerlink { + color: var(--complement-0); +} +.code-block-caption a.headerlink { + visibility:hidden; +} + +/* admonitions */ + +.admonition.warning { + border:1px dashed var(--primary-2); +} +.admonition.warning .admonition-title { + color: var(--primary-3); + background-color: var(--primary-1); +} +.admonition.note, +.admonition.hint { + border:1px dashed var(--complement-2); +} +.admonition.note .admonition-title, +.admonition.hint .admonition-title { + color: var(--complement-3); + background-color: var(--complement-1); +} +.admonition.seealso, +div.seealso { + background-color:var(--complement-9); +} +.admonition.seealso .admonition-title { + color: var(--complement-3); + background-color:var(--complement-1); + border-bottom:1px solid var(--complement-2); +} +.admonition.admonition-todo .admonition-title { + background-image: repeating-linear-gradient( + 135deg, + #ffa, + #ffa 14.14213452px, + #bbb 14.14213452px, + #bbb 28.28427124px + ); + color:#000; +} +.admonition.admonition-todo { + background-image: repeating-linear-gradient( + 135deg, + #ffd, + #ffd 14.14213452px, + #eed 14.14213452px, + #eed 28.28427124px + ); +} + +.rst-content dl .admonition p.last { + margin-bottom:0 !important; +} + +/* file block */ + +.code-block-caption { +/* border-radius: 4px; */ + font-style:italic; + font-weight:300; + border-bottom: 1px solid var(--secondary-2-1); + background-color: var(--secondary-2-9); + padding:2px 8px; +} + +/* navbar */ + +.wy-nav-side { + background-color: var(--secondary-1-4); + border-right:2px solid var(--primary-3); +} +.wy-menu-vertical a, +.wy-menu-vertical a:visited, +.wy-menu-vertical a:hover, +.wy-side-nav-search>a, +.wy-side-nav-search .wy-dropdown>a { + color: var(--primary-0); +} + +nav div.wy-side-nav-search { + background-color: #eee; +} +nav div.wy-side-scroll { + background-color: var(--secondary-1-4); +} +nav .wy-menu-vertical a:hover { + background-color:var(--primary-0); + color:var(--primary-4); +} +nav .wy-menu-vertical li.current ul a:hover { + background-color:var(--secondary-1-2); + color:var(--primary-4); +} +nav .wy-menu-vertical li.current ul a { + background-color:var(--secondary-1-1); + color:var(--primary-3); +} +nav .wy-menu-vertical li.on a:hover, +nav .wy-menu-vertical li.current>a:hover { + background-color:#fcfcfc; +} +.wy-side-nav-search input[type=text] { + border-color:var(--primary-2); +} +.wy-menu-vertical li.toctree-l1.current>a { + border-top:1px solid var(--secondary-1-3); + border-bottom:1px solid var(--secondary-1-3); +} +.wy-menu-vertical li.toctree-l2.current>a { + background-color:var(--secondary-1-2); +} +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + background-color:var(--secondary-1-9); +} + +.wy-nav-content { + padding: 25pt 40pt; +} +div[role=navigation] > hr { + display:none; +} +div[role=navigation] { + margin-bottom:15pt; +} +h1 { + margin-left:-40pt; + margin-right:-40pt; + padding:5pt 40pt 5pt 40pt; +} + +.rst-content pre.literal-block, .rst-content div[class^='highlight'] { + border-color:var(--secondary-1-1); +} + +span.pre { + color: var(--complement-3); +} +pre { + background-color: var(--secondary-1-9); + border-color: var(--secondary-1-1); +} +.highlight .p { color: var(--secondary-2-3); } +.highlight .k { color: var(--secondary-2-0); } +.highlight .kt { color: var(--complement-0); } +.highlight .cm { color: var(--primary-3); } +.highlight .ow { color: var(--primary-3); } +.highlight .na { color: var(--primary-2); } +.highlight .nv { color: var(--complement-0); } + +.rst-content code.frrfmtout { + background-color: var(--secondary-1-9); + border-color: var(--secondary-1-1); + font-size:100%; +} +.rst-content code.frrfmtout::before { + content: "⇒ \""; +} +.rst-content code.frrfmtout::after { + content: "\""; +} +.rst-content code.frrfmtout span { + color: var(--secondary-1-4); + font-size:100%; +} + +strong { + font-weight:500; +} +.rst-content dl:not(.docutils) dt { + font-family:Fira Mono; + font-weight:600; + background-color:var(--secondary-2-9); + color:var(--secondary-2-3); + border-top:2px solid var(--secondary-2-2); +} +dt code.descname { + color: var(--secondary-2-4); +} + +@media (min-width: 1200px) { + .container { width: auto; } +} +@media (min-width: 992px) { + .container { width: auto; } +} +@media (min-width: 768px) { + .container { width: auto; } +} diff --git a/doc/developer/bgp-typecodes.rst b/doc/developer/bgp-typecodes.rst new file mode 100644 index 0000000..c7921a7 --- /dev/null +++ b/doc/developer/bgp-typecodes.rst @@ -0,0 +1,25 @@ +BGP-4[+] UPDATE Attribute Preprocessor Constants +================================================ + +This is a list of preprocessor constants that map to BGP attributes defined by +various BGP RFCs. In the code these are defined as BGP_ATTR_. + ++-------+------------------+------------------------------------------+ +| Value | Attribute | References | ++=======+==================+==========================================+ +| 1 | ORIGIN | [RFC 4271] | +| 2 | AS_PATH | [RFC 4271] | +| 3 | NEXT_HOP | [RFC 4271] | +| 4 | MULTI_EXIT_DISC | [RFC 4271] | +| 5 | LOCAL_PREF | [RFC 4271] | +| 6 | ATOMIC_AGGREGATE | [RFC 4271] | +| 7 | AGGREGATOR | [RFC 4271] | +| 8 | COMMUNITIES | [RFC 1997] | +| 9 | ORIGINATOR_ID | [RFC 4456] | +| 10 | CLUSTER_LIST | [RFC 4456] | +| 14 | MP_REACH_NLRI | [RFC 4760] | +| 15 | MP_UNREACH_NLRI | [RFC 4760] | +| 16 | EXT_COMMUNITIES | [RFC 4360] | +| 17 | AS4_PATH | [RFC 4893] | +| 18 | AS4_AGGREGATOR | [RFC 4893] | ++-------+------------------+------------------------------------------+ diff --git a/doc/developer/bgpd.rst b/doc/developer/bgpd.rst new file mode 100644 index 0000000..a35fa61 --- /dev/null +++ b/doc/developer/bgpd.rst @@ -0,0 +1,11 @@ +.. _bgpd: + +**** +BGPD +**** + +.. toctree:: + :maxdepth: 2 + + next-hop-tracking + bgp-typecodes diff --git a/doc/developer/bmp.rst b/doc/developer/bmp.rst new file mode 100644 index 0000000..1c0e4b0 --- /dev/null +++ b/doc/developer/bmp.rst @@ -0,0 +1,49 @@ +.. _bmp: + +*** +BMP +*** + +RFC 7854 +======== +Missing features (non exhaustive): + - Per-Peer Header + + - Peer Type Flag + - Peer Distingsher + + - Peer Up + + - Reason codes (according to TODO comments in code) + +Peer Type Flag and Peer Distinguisher can be implemented easily using RFC 9069's base code. + +RFC 9069 +======== +Everything that isn't listed here is implemented and should be working. +Missing features (should be exhaustive): + +- Per-Peer Header + + - Timestamp + + - set to 0 + - value is now saved `struct bgp_path_info -> locrib_uptime` + - needs testing + +- Peer Up/Down + + - VRF/Table Name TLV + + - code for TLV exists + - need better RFC understanding + +- Peer Down Only + + - Reason code (bc not supported in RFC 7854 either) + +- Statistics Report + + - Stat Type = 8: (64-bit Gauge) Number of routes in Loc-RIB. + - Stat Type = 10: Number of routes in per-AFI/SAFI Loc-RIB. The value is + structured as: 2-byte AFI, 1-byte SAFI, followed by a 64-bit Gauge. diff --git a/doc/developer/building-docker.rst b/doc/developer/building-docker.rst new file mode 100644 index 0000000..644e02b --- /dev/null +++ b/doc/developer/building-docker.rst @@ -0,0 +1,204 @@ +Docker +====== + +This page covers how to build FRR Docker images. + +Images +"""""" +FRR has Docker build infrastructure to produce Docker images containing +source-built FRR on the following base platforms: + +* Alpine +* Centos 7 +* Centos 8 + +The following platform images are used to support Travis CI and can also +be used to reproduce topotest failures when the docker host is Ubuntu +(tested on 20.04 and 22.04): + +* Ubuntu 20.04 +* Ubuntu 22.04 + +The following platform images may also be built, but these simply install a +binary package from an existing repository and do not perform source builds: + +* Debian 10 + +Some of these are available on `DockerHub +`_. + +There is no guarantee on what is and is not available from DockerHub at time of +writing. + +Scripts +""""""" + +Some platforms contain an included build script that may be run from the host. +This will set appropriate packaging environment variables and clean up +intermediate build images. + +These scripts serve another purpose. They allow building platform packages +without needing the platform. For example, the Centos 8 docker image can also +be leveraged to build Centos 8 RPMs that can then be used separately from +Docker. + +If you are only interested in the Docker images and don't want the cleanup +functionality of the scripts you can ignore them and perform a normal Docker +build. If you want to build multi-arch docker images this is required as the +scripts do not support using Buildkit for multi-arch builds. + +Building Alpine Image +--------------------- + +Script:: + + ./docker/alpine/build.sh + +No script:: + + docker build -f docker/alpine/Dockerfile . + +No script, multi-arch (ex. amd64, arm64, armv7):: + + docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -f docker/alpine/Dockerfile -t frr:latest . + + +Building Debian Image +--------------------- + +:: + + cd docker/debian + docker build . + +Multi-arch (ex. amd64, arm64, armv7):: + + cd docker/debian + docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t frr-debian:latest . + +Building Centos 7 Image +----------------------- + +Script:: + + ./docker/centos-7/build.sh + +No script:: + + docker build -f docker/centos-7/Dockerfile . + +No script, multi-arch (ex. amd64, arm64):: + + docker buildx build --platform linux/amd64,linux/arm64 -f docker/centos-7/Dockerfile -t frr-centos7:latest . + + +Building Centos 8 Image +----------------------- + +Script:: + + ./docker/centos-8/build.sh + +No script:: + + docker build -f docker/centos-8/Dockerfile . + +No script, multi-arch (ex. amd64, arm64):: + + docker buildx build --platform linux/amd64,linux/arm64 -f docker/centos-8/Dockerfile -t frr-centos8:latest . + + + +Building ubi 8 Image +----------------------- + +Script:: + + ./docker/ubi-8/build.sh + +Script with params, an example could be this (all that info will go to docker label) :: + + ./docker/ubi-8/build.sh frr:ubi-8-my-test "$(git rev-parse --short=10 HEAD)" my_release my_name my_vendor + +No script:: + + docker build -f docker/ubi-8/Dockerfile . + +No script, multi-arch (ex. amd64, arm64):: + + docker buildx build --platform linux/amd64,linux/arm64 -f docker/ubi-8/Dockerfile -t frr-ubi-8:latest . + + + +Building Ubuntu 20.04 Image +--------------------------- + +Build image (from project root directory):: + + docker build -t frr-ubuntu20:latest --build-arg=UBUNTU_VERSION=20.04 -f docker/ubuntu-ci/Dockerfile . + +Running Full Topotest:: + + docker run --init -it --privileged --name frr-ubuntu20 -v /lib/modules:/lib/modules \ + frr-ubuntu20:latest bash -c 'cd ~/frr/tests/topotests ; sudo pytest -nauto --dist=loadfile' + +Extract results from the above run into `run-results` dir and analyze:: + + tests/topotests/analyze.py -C frr-ubuntu20 -Ar run-results + +Start the container:: + + docker run -d --init --privileged --name frr-ubuntu20 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu20:latest + +Running a topotest (when the docker host is Ubuntu):: + + docker exec frr-ubuntu20 bash -c 'cd ~/frr/tests/topotests/ospf_topo1 ; sudo pytest test_ospf_topo1.py' + +Starting an interactive bash session:: + + docker exec -it frr-ubuntu20 bash + +Stopping an removing a container:: + + docker stop frr-ubuntu20 ; docker rm frr-ubuntu20 + +Removing the built image:: + + docker rmi frr-ubuntu20:latest + + +Building Ubuntu 22.04 Image +--------------------------- + +Build image (from project root directory):: + + docker build -t frr-ubuntu22:latest -f docker/ubuntu-ci/Dockerfile . + +Running Full Topotest:: + + docker run --init -it --privileged --name frr-ubuntu22 -v /lib/modules:/lib/modules \ + frr-ubuntu22:latest bash -c 'cd ~/frr/tests/topotests ; sudo pytest -nauto --dist=loadfile' + +Extract results from the above run into `run-results` dir and analyze:: + + tests/topotests/analyze.py -C frr-ubuntu22 -Ar run-results + +Start the container:: + + docker run -d --init --privileged --name frr-ubuntu22 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu22:latest + +Running a topotest (when the docker host is Ubuntu):: + + docker exec frr-ubuntu22 bash -c 'cd ~/frr/tests/topotests/ospf_topo1 ; sudo pytest test_ospf_topo1.py' + +Starting an interactive bash session:: + + docker exec -it frr-ubuntu22 bash + +Stopping an removing a container:: + + docker stop frr-ubuntu22 ; docker rm frr-ubuntu22 + +Removing the built image:: + + docker rmi frr-ubuntu22:latest diff --git a/doc/developer/building-frr-for-alpine.rst b/doc/developer/building-frr-for-alpine.rst new file mode 100644 index 0000000..68e58c9 --- /dev/null +++ b/doc/developer/building-frr-for-alpine.rst @@ -0,0 +1,109 @@ +Alpine Linux 3.7+ +========================================================= + +For building Alpine Linux dev packages, we use docker. + +Install docker 17.05 or later +----------------------------- + +Depending on your host, there are different ways of installing docker. Refer +to the documentation here for instructions on how to install a free version of +docker: https://www.docker.com/community-edition + +Pre-built packages and docker images +------------------------------------ + +The master branch of https://github.com/frrouting/frr.git has a +continuous delivery of docker images to docker hub at: +https://hub.docker.com/r/ajones17/frr/. These images have the frr packages +in /pkgs/apk and have the frr package pre-installed. To copy Alpine +packages out of these images: + +:: + + id=`docker create ajones17/frr:latest` + docker cp ${id}:/pkgs _some_directory_ + docker rm $id + +To run the frr daemons (see below for how to configure them): + +:: + + docker run -it --rm --name frr ajones17/frr:latest + docker exec -it frr /bin/sh + +Work with sources +----------------- + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + +Build apk packages +------------------ + +:: + + ./docker/alpine/build.sh + +This will put the apk packages in: + +:: + + ./docker/pkgs/apk/x86_64/ + +Usage +----- + +To create a base image with the frr packages installed: + +:: + + docker build --rm -f docker/alpine/Dockerfile -t frr:latest . + +Or, if you don't have a git checkout of the sources, you can build a base +image directly off the github account: + +:: + + docker build --rm -f docker/alpine/Dockerfile -t frr:latest \ + https://github.com/frrouting/frr.git + +And to run the image: + +:: + + docker run -it --rm --name frr frr:latest + +In the default configuration, none of the frr daemons will be running. +To configure the daemons, exec into the container and edit the configuration +files or mount a volume with configuration files into the container on +startup. To configure by hand: + +:: + + docker exec -it frr /bin/sh + vi /etc/frr/daemons + /etc/init.d/frr start + +Or, to configure the daemons using /etc/frr from a host volume, put the +config files in, say, ./docker/etc and bind mount that into the +container: + +:: + + docker run -it --rm -v `pwd`/docker/etc:/etc/frr frr:latest + +We can also build the base image directly from docker-compose, with a +docker-compose.yml file like this one: + +:: + + version: '2.2' + + services: + frr: + build: + context: https://github.com/frrouting/frr.git + dockerfile: docker/alpine/Dockerfile diff --git a/doc/developer/building-frr-for-archlinux.rst b/doc/developer/building-frr-for-archlinux.rst new file mode 100644 index 0000000..8b0df21 --- /dev/null +++ b/doc/developer/building-frr-for-archlinux.rst @@ -0,0 +1,125 @@ +Arch Linux +================ + +Installing Dependencies +----------------------- + +.. code-block:: console + + sudo pacman -Syu + sudo pacman -S \ + git autoconf automake libtool make cmake pcre readline texinfo \ + pkg-config pam json-c bison flex python-pytest \ + c-ares python python2-ipaddress python-sphinx \ + net-snmp perl libcap libelf libunwind protobuf-c + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + + +ZeroMQ +^^^^^^ + +.. code-block:: console + + sudo pacman -S zeromq + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd --system -g frr --home-dir /var/run/frr/ \ + -c "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` [*Create the file if it doesn't exist*] and +append the following values (ignore the other settings): + +:: + + # Enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Enable packet forwarding for IPv6 + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install service files +^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service + sudo systemctl enable frr + +Start FRR +^^^^^^^^^ + +.. code-block:: shell + + systemctl start frr diff --git a/doc/developer/building-frr-for-centos6.rst b/doc/developer/building-frr-for-centos6.rst new file mode 100644 index 0000000..3531162 --- /dev/null +++ b/doc/developer/building-frr-for-centos6.rst @@ -0,0 +1,274 @@ +.. _building-centos6: + +CentOS 6 +======================================== + +This document describes installation from source. If you want to build an RPM, +see :ref:`packaging-redhat`. + +Instructions are tested with ``CentOS 6.8`` on ``x86_64`` platform + +Warning: +-------- +``CentOS 6`` is very old and not fully supported by the FRR community +anymore. Building FRR takes multiple manual steps to update the build +system with newer packages than what's available from the archives. +However, the built packages can still be installed afterwards on +a standard ``CentOS 6`` without any special packages. + +Support for CentOS 6 is now on a best-effort base by the community. + +CentOS 6 restrictions: +---------------------- + +- PIMd is not supported on ``CentOS 6``. Upgrade to ``CentOS 7`` if + PIMd is needed +- MPLS is not supported on ``CentOS 6``. MPLS requires Linux Kernel 4.5 + or higher (LDP can be built, but may have limited use without MPLS) +- Zebra is unable to detect what bridge/vrf an interface is associated + with (IFLA\_INFO\_SLAVE\_KIND does not exist in the kernel headers, + you can use a newer kernel + headers to get this functionality) +- frr\_reload.py will not work, as this requires Python 2.7, and CentOS + 6 only has 2.6. You can install Python 2.7 via IUS, but it won't work + properly unless you compile and install the ipaddr package for it. +- Building the package requires Sphinx >= 1.1. Only a non-standard + package provides a newer sphinx and requires manual installation + (see below) + + +Install required packages +------------------------- + +Add packages: + +.. code-block:: shell + + sudo yum install git autoconf automake libtool make \ + readline-devel texinfo net-snmp-devel groff pkgconfig \ + json-c-devel pam-devel flex epel-release c-ares-devel libcap-devel \ + elfutils-libelf-devel protobuf-c-devel + +Install newer version of bison (CentOS 6 package source is too old) from CentOS +7: + +.. code-block:: shell + + sudo yum install rpm-build + curl -O http://vault.centos.org/7.0.1406/os/Source/SPackages/bison-2.7-4.el7.src.rpm + rpmbuild --rebuild ./bison-2.7-4.el7.src.rpm + sudo yum install ./rpmbuild/RPMS/x86_64/bison-2.7-4.el6.x86_64.rpm + rm -rf rpmbuild + +Install newer version of autoconf and automake (Package versions are too old): + +.. code-block:: shell + + curl -O http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz + tar xvf autoconf-2.69.tar.gz + cd autoconf-2.69 + ./configure --prefix=/usr + make + sudo make install + cd .. + + curl -O http://ftp.gnu.org/gnu/automake/automake-1.15.tar.gz + tar xvf automake-1.15.tar.gz + cd automake-1.15 + ./configure --prefix=/usr + make + sudo make install + cd .. + +Install ``Python 2.7`` in parallel to default 2.6. Make sure you've install +EPEL (``epel-release`` as above). Then install current ``python27``: +``python27-devel`` and ``pytest`` + +.. code-block:: shell + + sudo rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm + sudo rpm -ivh https://centos6.iuscommunity.org/ius-release.rpm + sudo yum install python27 python27-pip python27-devel + sudo pip2.7 install pytest + +Please note that ``CentOS 6`` needs to keep python pointing to version 2.6 for +``yum`` to keep working, so don't create a symlink for python2.7 to python. + +Install newer ``Sphinx-Build`` based on ``Python 2.7``. + +Create a new repo ``/etc/yum.repos.d/puias6.repo`` with the following contents: + +:: + + ### Name: RPM Repository for RHEL 6 - PUIAS (used for Sphinx-Build) + ### URL: http://springdale.math.ias.edu/data/puias/computational + [puias-computational] + name = RPM Repository for RHEL 6 - Sphinx-Build + baseurl = http://springdale.math.ias.edu/data/puias/computational/$releasever/$basearch + #mirrorlist = + enabled = 1 + protect = 0 + gpgkey = + gpgcheck = 0 + +Update rpm database & Install newer sphinx + +.. code-block:: shell + + sudo yum update + sudo yum install python27-sphinx + +Install libyang and its dependencies: + +.. code-block:: shell + + sudo yum install pcre-devel doxygen cmake + git clone https://github.com/CESNET/libyang.git + cd libyang + git checkout v2.1.128 + mkdir build ; cd build + cmake -DENABLE_LYD_PRIV=ON -DCMAKE_INSTALL_PREFIX:PATH=/usr -D CMAKE_BUILD_TYPE:String="Release" .. + make build-rpm + sudo yum install ./rpms/RPMS/x86_64/libyang-0.16.111-0.x86_64.rpm ./rpms/RPMS/x86_64/libyang-devel-0.16.111-0.x86_64.rpm + cd ../.. + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not using any +packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo groupadd -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ + -c "FRR FRRouting suite" -d /var/run/frr frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +.. code-block:: shell + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --bindir=/usr/bin \ + --sbindir=/usr/lib/frr \ + --libdir=/usr/lib/frr \ + --libexecdir=/usr/lib/frr \ + --with-moduledir=/usr/lib/frr/modules \ + --disable-pimd \ + --enable-snmp=agentx \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --disable-ldpd \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + make check + sudo make install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /var/log/frr + sudo mkdir /etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /etc/frr/babeld.conf + sudo touch /etc/frr/bfdd.conf + sudo touch /etc/frr/bgpd.conf + sudo touch /etc/frr/eigrpd.conf + sudo touch /etc/frr/isisd.conf + sudo touch /etc/frr/ldpd.conf + sudo touch /etc/frr/nhrpd.conf + sudo touch /etc/frr/ospf6d.conf + sudo touch /etc/frr/ospfd.conf + sudo touch /etc/frr/pbrd.conf + sudo touch /etc/frr/pimd.conf + sudo touch /etc/frr/ripd.conf + sudo touch /etc/frr/ripngd.conf + sudo touch /etc/frr/staticd.conf + sudo touch /etc/frr/zebra.conf + sudo chown -R frr:frr /etc/frr/ + sudo touch /etc/frr/vtysh.conf + sudo chown frr:frrvty /etc/frr/vtysh.conf + sudo chmod 640 /etc/frr/*.conf + +Install daemon config file +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo install -p -m 644 tools/etc/frr/daemons /etc/frr/ + sudo chown frr:frr /etc/frr/daemons + +Edit /etc/frr/daemons as needed to select the required daemons +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. +Enable the daemons as required by changing the value to ``yes`` + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Edit :file:`/etc/sysctl.conf` and set the following values (ignore the other +settings):: + + # Controls IP packet forwarding + net.ipv4.ip_forward = 1 + net.ipv6.conf.all.forwarding=1 + + # Controls source route verification + net.ipv4.conf.default.rp_filter = 0 + +Load the modified sysctl's on the system: + +.. code-block:: shell + + sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf + +Add init.d startup file +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo install -p -m 755 tools/frr /etc/init.d/frr + sudo chkconfig --add frr + +Enable FRR daemon at startup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo chkconfig frr on + +Start FRR manually (or reboot) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo /etc/init.d/frr start diff --git a/doc/developer/building-frr-for-centos7.rst b/doc/developer/building-frr-for-centos7.rst new file mode 100644 index 0000000..eabf515 --- /dev/null +++ b/doc/developer/building-frr-for-centos7.rst @@ -0,0 +1,161 @@ +CentOS 7 +======================================== + +This document describes installation from source. If you want to build an RPM, +see :ref:`packaging-redhat`. + +CentOS 7 restrictions: +---------------------- + +- MPLS is not supported on ``CentOS 7`` with default kernel. MPLS + requires Linux Kernel 4.5 or higher (LDP can be built, but may have + limited use without MPLS) + +Install required packages +------------------------- + +Add packages: + +:: + + sudo yum install git autoconf automake libtool make \ + readline-devel texinfo net-snmp-devel groff pkgconfig \ + json-c-devel pam-devel bison flex pytest c-ares-devel \ + python-devel python-sphinx libcap-devel \ + elfutils-libelf-devel libunwind-devel protobuf-c-devel + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo groupadd -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ + -c "FRR FRRouting suite" -d /var/run/frr frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --bindir=/usr/bin \ + --sbindir=/usr/lib/frr \ + --libdir=/usr/lib/frr \ + --libexecdir=/usr/lib/frr \ + --with-moduledir=/usr/lib/frr/modules \ + --enable-snmp=agentx \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --disable-ldpd \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion \ + SPHINXBUILD=/usr/bin/sphinx-build + make + make check + sudo make install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo mkdir /var/log/frr + sudo mkdir /etc/frr + sudo touch /etc/frr/zebra.conf + sudo touch /etc/frr/bgpd.conf + sudo touch /etc/frr/ospfd.conf + sudo touch /etc/frr/ospf6d.conf + sudo touch /etc/frr/isisd.conf + sudo touch /etc/frr/ripd.conf + sudo touch /etc/frr/ripngd.conf + sudo touch /etc/frr/pimd.conf + sudo touch /etc/frr/nhrpd.conf + sudo touch /etc/frr/eigrpd.conf + sudo touch /etc/frr/babeld.conf + sudo chown -R frr:frr /etc/frr/ + sudo touch /etc/frr/vtysh.conf + sudo chown frr:frrvty /etc/frr/vtysh.conf + sudo chmod 640 /etc/frr/*.conf + +Install daemon config file +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -p -m 644 tools/etc/frr/daemons /etc/frr/ + sudo chown frr:frr /etc/frr/daemons + +Edit /etc/frr/daemons as needed to select the required daemons +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. +Enable the daemons as required by changing the value to ``yes`` + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the +following content: + +:: + + # Sysctl for routing + # + # Routing: We need to forward packets + net.ipv4.conf.all.forwarding=1 + net.ipv6.conf.all.forwarding=1 + +Load the modified sysctl's on the system: + +:: + + sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf + +Install frr Service +^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -p -m 644 tools/frr.service /usr/lib/systemd/system/frr.service + +Register the systemd files +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl preset frr.service + +Enable required frr at startup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl enable frr + +Reboot or start FRR manually +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl start frr diff --git a/doc/developer/building-frr-for-centos8.rst b/doc/developer/building-frr-for-centos8.rst new file mode 100644 index 0000000..2d514ea --- /dev/null +++ b/doc/developer/building-frr-for-centos8.rst @@ -0,0 +1,155 @@ +CentOS 8 +======== + +This document describes installation from source. If you want to build an RPM, +see :ref:`packaging-redhat`. + +Install required packages +------------------------- + +Add packages: + +:: + + sudo dnf install --enablerepo=PowerTools git autoconf pcre-devel \ + automake libtool make readline-devel texinfo net-snmp-devel pkgconfig \ + groff pkgconfig json-c-devel pam-devel bison flex python2-pytest \ + c-ares-devel python2-devel libcap-devel \ + elfutils-libelf-devel libunwind-devel \ + protobuf-c-devel + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo groupadd -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ + -c "FRR FRRouting suite" -d /var/run/frr frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --bindir=/usr/bin \ + --sbindir=/usr/lib/frr \ + --libdir=/usr/lib/frr \ + --libexecdir=/usr/lib/frr \ + --with-moduledir=/usr/lib/frr/modules \ + --enable-snmp=agentx \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --disable-ldpd \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion \ + SPHINXBUILD=/usr/bin/sphinx-build + make + make check + sudo make install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo mkdir /var/log/frr + sudo mkdir /etc/frr + sudo touch /etc/frr/zebra.conf + sudo touch /etc/frr/bgpd.conf + sudo touch /etc/frr/ospfd.conf + sudo touch /etc/frr/ospf6d.conf + sudo touch /etc/frr/isisd.conf + sudo touch /etc/frr/ripd.conf + sudo touch /etc/frr/ripngd.conf + sudo touch /etc/frr/pimd.conf + sudo touch /etc/frr/nhrpd.conf + sudo touch /etc/frr/eigrpd.conf + sudo touch /etc/frr/babeld.conf + sudo chown -R frr:frr /etc/frr/ + sudo touch /etc/frr/vtysh.conf + sudo chown frr:frrvty /etc/frr/vtysh.conf + sudo chmod 640 /etc/frr/*.conf + +Install daemon config file +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -p -m 644 tools/etc/frr/daemons /etc/frr/ + sudo chown frr:frr /etc/frr/daemons + +Edit /etc/frr/daemons as needed to select the required daemons +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Look for the section with ``watchfrr_enable=...`` and ``zebra=...`` etc. +Enable the daemons as required by changing the value to ``yes`` + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the +following content: + +:: + + # Sysctl for routing + # + # Routing: We need to forward packets + net.ipv4.conf.all.forwarding=1 + net.ipv6.conf.all.forwarding=1 + +Load the modified sysctl's on the system: + +:: + + sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf + +Install frr Service +^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -p -m 644 tools/frr.service /usr/lib/systemd/system/frr.service + +Register the systemd files +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl preset frr.service + +Enable required frr at startup +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl enable frr + +Reboot or start FRR manually +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo systemctl start frr diff --git a/doc/developer/building-frr-for-debian12.rst b/doc/developer/building-frr-for-debian12.rst new file mode 100644 index 0000000..06bc18c --- /dev/null +++ b/doc/developer/building-frr-for-debian12.rst @@ -0,0 +1,119 @@ +Debian 12 +========= + +Install required packages +------------------------- + +Add packages: + +:: + + sudo apt-get install git autoconf automake libtool make \ + libprotobuf-c-dev protobuf-c-compiler build-essential \ + python3-dev python3-pytest python3-sphinx libjson-c-dev \ + libelf-dev libreadline-dev cmake libcap-dev bison flex \ + pkg-config texinfo gdb libgrpc-dev python3-grpc-tools + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo addgroup --system --gid 92 frr + sudo addgroup --system --gid 85 frrvty + sudo adduser --system --ingroup frr --home /var/opt/frr/ \ + --gecos "FRR suite" --shell /bin/false frr + sudo usermod -a -G frrvty frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + make check + sudo make install + +For more compile options, see ``./configure --help`` + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Edit ``/etc/frr/daemons`` and enable the FRR daemons for the protocols you need + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Edit ``/etc/sysctl.conf`` and uncomment the following values (ignore the +other settings) + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +**Reboot** or use ``sysctl -p`` to apply the same config to the running +system + +Troubleshooting +--------------- + +Shared library error +^^^^^^^^^^^^^^^^^^^^ + +If you try and start any of the frrouting daemons you may see the below +error due to the frrouting shared library directory not being found: + +:: + + ./zebra: error while loading shared libraries: libfrr.so.0: cannot open + shared object file: No such file or directory + +The fix is to add the following line to /etc/ld.so.conf which will +continue to reference the library directory after the system reboots. To +load the library directory path immediately run the ldconfig command +after adding the line to the file eg: + +:: + + echo include /usr/local/lib >> /etc/ld.so.conf + ldconfig diff --git a/doc/developer/building-frr-for-debian8.rst b/doc/developer/building-frr-for-debian8.rst new file mode 100644 index 0000000..fe4eeea --- /dev/null +++ b/doc/developer/building-frr-for-debian8.rst @@ -0,0 +1,150 @@ +Debian 8 +======================================== + +Debian 8 restrictions: +---------------------- + +- MPLS is not supported on ``Debian 8`` with default kernel. MPLS + requires Linux Kernel 4.5 or higher (LDP can be built, but may have + limited use without MPLS) + +Install required packages +------------------------- + +Add packages: + +:: + + sudo apt-get install git autoconf automake libtool make \ + libreadline-dev texinfo libjson-c-dev pkg-config bison flex python3-pip \ + libc-ares-dev python3-dev python3-sphinx build-essential \ + libsnmp-dev libcap-dev libelf-dev libprotobuf-c-dev protobuf-c-compiler + +Install newer pytest (>3.0) from pip + +:: + + sudo pip3 install pytest + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo addgroup --system --gid 92 frr + sudo addgroup --system --gid 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /bin/false frr + sudo usermod -a -G frrvty frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + make check + sudo make install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -m 755 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/zebra.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/bgpd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospfd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospf6d.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/isisd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripngd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/pimd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ldpd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/nhrpd.conf + sudo install -m 640 -o frr -g frrvty /dev/null /etc/frr/vtysh.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Edit ``/etc/sysctl.conf`` and uncomment the following values (ignore the +other settings) + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +**Reboot** or use ``sysctl -p`` to apply the same config to the running +system + +Troubleshooting +^^^^^^^^^^^^^^^ + +**Local state directory** + +The local state directory must exist and have the correct permissions +applied for the frrouting daemons to start. In the above ./configure +example the local state directory is set to ``/var`` such that ``/var/run/frr`` +is used. Debian considers ``/var/run/frr`` to be temporary and this is removed +after a reboot. + +When using a different local state directory you need to create the new +directory and change the ownership to the frr user, for example: + +:: + + mkdir /var/opt/frr + chown frr /var/opt/frr + +**Shared library error** + +If you try and start any of the frrouting daemons you may see the below +error due to the frrouting shared library directory not being found: + +:: + + ./zebra: error while loading shared libraries: libfrr.so.0: cannot open shared object file: No such file or directory + +The fix is to add the following line to /etc/ld.so.conf which will +continue to reference the library directory after the system reboots. To +load the library directory path immediately run the ldconfig command +after adding the line to the file eg: + +:: + + echo include /usr/local/lib >> /etc/ld.so.conf + ldconfig diff --git a/doc/developer/building-frr-for-debian9.rst b/doc/developer/building-frr-for-debian9.rst new file mode 100644 index 0000000..a590cf7 --- /dev/null +++ b/doc/developer/building-frr-for-debian9.rst @@ -0,0 +1,127 @@ +Debian 9 +======================================== + +Install required packages +------------------------- + +Add packages: + +:: + + sudo apt-get install git autoconf automake libtool make \ + libreadline-dev texinfo libjson-c-dev pkg-config bison flex \ + libc-ares-dev python3-dev python3-pytest python3-sphinx build-essential \ + libsnmp-dev libcap-dev libelf-dev libunwind-dev \ + libprotobuf-c-dev protobuf-c-compiler + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo addgroup --system --gid 92 frr + sudo addgroup --system --gid 85 frrvty + sudo adduser --system --ingroup frr --home /var/opt/frr/ \ + --gecos "FRR suite" --shell /bin/false frr + sudo usermod -a -G frrvty frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example.) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + make check + sudo make install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo install -m 755 -o frr -g frr -d /var/log/frr + sudo install -m 755 -o frr -g frr -d /var/opt/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/zebra.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/bgpd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospfd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ospf6d.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/isisd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ripngd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/pimd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/ldpd.conf + sudo install -m 640 -o frr -g frr /dev/null /etc/frr/nhrpd.conf + sudo install -m 640 -o frr -g frrvty /dev/null /etc/frr/vtysh.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Edit ``/etc/sysctl.conf`` and uncomment the following values (ignore the +other settings) + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +**Reboot** or use ``sysctl -p`` to apply the same config to the running +system + +Troubleshooting +--------------- + +Shared library error +^^^^^^^^^^^^^^^^^^^^ + +If you try and start any of the frrouting daemons you may see the below +error due to the frrouting shared library directory not being found: + +:: + + ./zebra: error while loading shared libraries: libfrr.so.0: cannot open + shared object file: No such file or directory + +The fix is to add the following line to /etc/ld.so.conf which will +continue to reference the library directory after the system reboots. To +load the library directory path immediately run the ldconfig command +after adding the line to the file eg: + +:: + + echo include /usr/local/lib >> /etc/ld.so.conf + ldconfig diff --git a/doc/developer/building-frr-for-fedora.rst b/doc/developer/building-frr-for-fedora.rst new file mode 100644 index 0000000..35a24b2 --- /dev/null +++ b/doc/developer/building-frr-for-fedora.rst @@ -0,0 +1,136 @@ +Fedora 24+ +========== + +This document describes installation from source. If you want to build an RPM, +see :ref:`packaging-redhat`. + +These instructions have been tested on Fedora 24+. + +Installing Dependencies +----------------------- + +.. code-block:: console + + sudo dnf install git autoconf automake libtool make \ + readline-devel texinfo net-snmp-devel groff pkgconfig json-c-devel \ + pam-devel python3-pytest bison flex c-ares-devel python3-devel \ + python3-sphinx perl-core patch libcap-devel \ + elfutils-libelf-devel libunwind-devel protobuf-c-devel + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ + -c "FRR FRRouting suite" -d /var/run/frr frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the following +content: + +:: + + # + # Enable packet forwarding + # + net.ipv4.conf.all.forwarding=1 + net.ipv6.conf.all.forwarding=1 + # + # Enable MPLS Label processing on all interfaces + # + #net.mpls.conf.eth0.input=1 + #net.mpls.conf.eth1.input=1 + #net.mpls.conf.eth2.input=1 + #net.mpls.platform_labels=100000 + +.. note:: + + MPLS must be invidividually enabled on each interface that requires it. See + the example in the config block above. + +Load the modified sysctls on the system: + +.. code-block:: console + + sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf + +Create a new file ``/etc/modules-load.d/mpls.conf`` with the following content: + +:: + + # Load MPLS Kernel Modules + mpls-router + mpls-iptunnel + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + + +.. note:: + Fedora ships with the ``firewalld`` service enabled. You may run into some + issues with the iptables rules it installs by default. If you wish to just + stop the service and clear `ALL` rules do these commands: + + .. code-block:: console + + sudo systemctl disable firewalld.service + sudo systemctl stop firewalld.service + sudo iptables -F + +Install frr Service +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -p -m 644 tools/frr.service /usr/lib/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: frr + + sudo systemctl start frr diff --git a/doc/developer/building-frr-for-freebsd10.rst b/doc/developer/building-frr-for-freebsd10.rst new file mode 100644 index 0000000..beefb59 --- /dev/null +++ b/doc/developer/building-frr-for-freebsd10.rst @@ -0,0 +1,130 @@ +FreeBSD 10 +========================================== + +FreeBSD 10 restrictions: +------------------------ + +- MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS + +Install required packages +------------------------- + +Add packages: (Allow the install of the package management tool if this +is first package install and asked) + +:: + + pkg install git autoconf automake libtool gmake json-c pkgconf \ + bison flex py36-pytest c-ares python3.6 py36-sphinx libunwind \ + protobuf-c + +.. include:: building-libunwind-note.rst + +Make sure there is no /usr/bin/flex preinstalled (and use the newly +installed in /usr/local/bin): (FreeBSD frequently provides a older flex +as part of the base OS which takes preference in path) + +.. include:: building-libyang.rst + +:: + + rm -f /usr/bin/flex + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +:: + + pw groupadd frr -g 101 + pw groupadd frrvty -g 102 + pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ + -d /usr/local/etc/frr -s /usr/sbin/nologin + +(You may prefer different options on configure statement. These are just +an example) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + export MAKE=gmake + export LDFLAGS="-L/usr/local/lib" + export CPPFLAGS="-I/usr/local/include" + ./configure \ + --sysconfdir=/usr/local/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --prefix=/usr/local \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /usr/local/etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/babeld.conf + sudo touch /usr/local/etc/frr/bfdd.conf + sudo touch /usr/local/etc/frr/bgpd.conf + sudo touch /usr/local/etc/frr/eigrpd.conf + sudo touch /usr/local/etc/frr/isisd.conf + sudo touch /usr/local/etc/frr/ldpd.conf + sudo touch /usr/local/etc/frr/nhrpd.conf + sudo touch /usr/local/etc/frr/ospf6d.conf + sudo touch /usr/local/etc/frr/ospfd.conf + sudo touch /usr/local/etc/frr/pbrd.conf + sudo touch /usr/local/etc/frr/pimd.conf + sudo touch /usr/local/etc/frr/ripd.conf + sudo touch /usr/local/etc/frr/ripngd.conf + sudo touch /usr/local/etc/frr/staticd.conf + sudo touch /usr/local/etc/frr/zebra.conf + sudo chown -R frr:frr /usr/local/etc/frr/ + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf + sudo chmod 640 /usr/local/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running system. diff --git a/doc/developer/building-frr-for-freebsd11.rst b/doc/developer/building-frr-for-freebsd11.rst new file mode 100644 index 0000000..7c8fb83 --- /dev/null +++ b/doc/developer/building-frr-for-freebsd11.rst @@ -0,0 +1,135 @@ +FreeBSD 11 +========== + +FreeBSD 11 restrictions: +------------------------ + +- MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS + +Install required packages +------------------------- + +Add packages: (Allow the install of the package management tool if this +is first package install and asked) + +.. code-block:: shell + + pkg install git autoconf automake libtool gmake json-c pkgconf \ + bison flex py36-pytest c-ares python3.6 py36-sphinx texinfo libunwind \ + protobuf-c + +.. include:: building-libunwind-note.rst + +Make sure there is no /usr/bin/flex preinstalled (and use the newly +installed in /usr/local/bin): (FreeBSD frequently provides a older flex +as part of the base OS which takes preference in path) + +.. include:: building-libyang.rst + +.. code-block:: shell + + rm -f /usr/bin/flex + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not using any +packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + pw groupadd frr -g 101 + pw groupadd frrvty -g 102 + pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ + -d /usr/local/etc/frr -s /usr/sbin/nologin + + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +.. code-block:: shell + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + setenv MAKE gmake + setenv LDFLAGS -L/usr/local/lib + setenv CPPFLAGS -I/usr/local/include + ln -s /usr/local/bin/sphinx-build-3.6 /usr/local/bin/sphinx-build + ./configure \ + --sysconfdir=/usr/local/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --prefix=/usr/local \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /usr/local/etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/babeld.conf + sudo touch /usr/local/etc/frr/bfdd.conf + sudo touch /usr/local/etc/frr/bgpd.conf + sudo touch /usr/local/etc/frr/eigrpd.conf + sudo touch /usr/local/etc/frr/isisd.conf + sudo touch /usr/local/etc/frr/ldpd.conf + sudo touch /usr/local/etc/frr/nhrpd.conf + sudo touch /usr/local/etc/frr/ospf6d.conf + sudo touch /usr/local/etc/frr/ospfd.conf + sudo touch /usr/local/etc/frr/pbrd.conf + sudo touch /usr/local/etc/frr/pimd.conf + sudo touch /usr/local/etc/frr/ripd.conf + sudo touch /usr/local/etc/frr/ripngd.conf + sudo touch /usr/local/etc/frr/staticd.conf + sudo touch /usr/local/etc/frr/zebra.conf + sudo chown -R frr:frr /usr/local/etc/frr/ + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf + sudo chmod 640 /usr/local/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running system. diff --git a/doc/developer/building-frr-for-freebsd13.rst b/doc/developer/building-frr-for-freebsd13.rst new file mode 100644 index 0000000..86506a9 --- /dev/null +++ b/doc/developer/building-frr-for-freebsd13.rst @@ -0,0 +1,122 @@ +FreeBSD 13 +========== + +FreeBSD 13 restrictions: +------------------------ + +- MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS +- PIM for IPv6 is not currently supported on ``FreeBSD``. + +Install required packages +------------------------- + +Add packages: (Allow the install of the package management tool if this +is first package install and asked) + +.. code-block:: shell + + pkg install git autoconf automake libtool gmake json-c pkgconf \ + bison py39-pytest c-ares py39-sphinx texinfo libunwind libyang2 + +.. include:: building-libunwind-note.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not using any +packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + pw groupadd frr -g 101 + pw groupadd frrvty -g 102 + pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ + -d /usr/local/etc/frr -s /usr/sbin/nologin + + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +.. code-block:: shell + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + export MAKE=gmake LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include + ./configure \ + --sysconfdir=/usr/local/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --prefix=/usr/local \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /usr/local/etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/babeld.conf + sudo touch /usr/local/etc/frr/bfdd.conf + sudo touch /usr/local/etc/frr/bgpd.conf + sudo touch /usr/local/etc/frr/eigrpd.conf + sudo touch /usr/local/etc/frr/isisd.conf + sudo touch /usr/local/etc/frr/ldpd.conf + sudo touch /usr/local/etc/frr/nhrpd.conf + sudo touch /usr/local/etc/frr/ospf6d.conf + sudo touch /usr/local/etc/frr/ospfd.conf + sudo touch /usr/local/etc/frr/pbrd.conf + sudo touch /usr/local/etc/frr/pimd.conf + sudo touch /usr/local/etc/frr/ripd.conf + sudo touch /usr/local/etc/frr/ripngd.conf + sudo touch /usr/local/etc/frr/staticd.conf + sudo touch /usr/local/etc/frr/zebra.conf + sudo chown -R frr:frr /usr/local/etc/frr/ + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf + sudo chmod 640 /usr/local/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running system. diff --git a/doc/developer/building-frr-for-freebsd14.rst b/doc/developer/building-frr-for-freebsd14.rst new file mode 100644 index 0000000..b3fd37a --- /dev/null +++ b/doc/developer/building-frr-for-freebsd14.rst @@ -0,0 +1,122 @@ +FreeBSD 14 +========== + +FreeBSD 14 restrictions: +------------------------ + +- MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS +- PIM for IPv6 is not currently supported on ``FreeBSD``. + +Install required packages +------------------------- + +Add packages: (Allow the install of the package management tool if this +is first package install and asked) + +.. code-block:: shell + + pkg install autoconf automake bison c-ares git gmake json-c libtool \ + libunwind libyang2 pkgconf protobuf-c py39-pytest py39-sphinx texinfo + +.. include:: building-libunwind-note.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not using any +packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + pw groupadd frr -g 101 + pw groupadd frrvty -g 102 + pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ + -d /usr/local/etc/frr -s /usr/sbin/nologin + + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +.. code-block:: shell + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + export MAKE=gmake LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include + ./configure \ + --sysconfdir=/usr/local/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --prefix=/usr/local \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /usr/local/etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/babeld.conf + sudo touch /usr/local/etc/frr/bfdd.conf + sudo touch /usr/local/etc/frr/bgpd.conf + sudo touch /usr/local/etc/frr/eigrpd.conf + sudo touch /usr/local/etc/frr/isisd.conf + sudo touch /usr/local/etc/frr/ldpd.conf + sudo touch /usr/local/etc/frr/nhrpd.conf + sudo touch /usr/local/etc/frr/ospf6d.conf + sudo touch /usr/local/etc/frr/ospfd.conf + sudo touch /usr/local/etc/frr/pbrd.conf + sudo touch /usr/local/etc/frr/pimd.conf + sudo touch /usr/local/etc/frr/ripd.conf + sudo touch /usr/local/etc/frr/ripngd.conf + sudo touch /usr/local/etc/frr/staticd.conf + sudo touch /usr/local/etc/frr/zebra.conf + sudo chown -R frr:frr /usr/local/etc/frr/ + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf + sudo chmod 640 /usr/local/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running system. diff --git a/doc/developer/building-frr-for-freebsd9.rst b/doc/developer/building-frr-for-freebsd9.rst new file mode 100644 index 0000000..9f9073d --- /dev/null +++ b/doc/developer/building-frr-for-freebsd9.rst @@ -0,0 +1,140 @@ +FreeBSD 9 +========================================= + +FreeBSD 9 restrictions: +----------------------- + +- MPLS is not supported on ``FreeBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS + +Install required packages +------------------------- + +Add packages: (Allow the install of the package management tool if this +is first package install and asked) + +:: + + pkg install -y git autoconf automake libtool gmake \ + pkgconf texinfo json-c bison flex py36-pytest c-ares \ + python3 py36-sphinx libexecinfo protobuf-c + +Make sure there is no /usr/bin/flex preinstalled (and use the newly +installed in /usr/local/bin): (FreeBSD frequently provides a older flex +as part of the base OS which takes preference in path) + +:: + + rm -f /usr/bin/flex + +For building with clang (instead of gcc), upgrade clang from 3.4 default +to 3.6 *This is needed to build FreeBSD packages as well - for packages +clang is default* (Clang 3.4 as shipped with FreeBSD 9 crashes during +compile) + +:: + + pkg install clang36 + pkg delete clang34 + mv /usr/bin/clang /usr/bin/clang34 + ln -s /usr/local/bin/clang36 /usr/bin/clang + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +:: + + pw groupadd frr -g 101 + pw groupadd frrvty -g 102 + pw adduser frr -g 101 -u 101 -G 102 -c "FRR suite" \ + -d /usr/local/etc/frr -s /usr/sbin/nologin + +(You may prefer different options on configure statement. These are just +an example) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + export MAKE=gmake + export LDFLAGS="-L/usr/local/lib" + export CPPFLAGS="-I/usr/local/include" + ./configure \ + --sysconfdir=/usr/local/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --prefix=/usr/local \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + sudo mkdir /usr/local/etc/frr + +For integrated config file: + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/frr.conf + +For individual config files: + +.. note:: Integrated config is preferred to individual config. + +.. code-block:: shell + + sudo touch /usr/local/etc/frr/babeld.conf + sudo touch /usr/local/etc/frr/bfdd.conf + sudo touch /usr/local/etc/frr/bgpd.conf + sudo touch /usr/local/etc/frr/eigrpd.conf + sudo touch /usr/local/etc/frr/isisd.conf + sudo touch /usr/local/etc/frr/ldpd.conf + sudo touch /usr/local/etc/frr/nhrpd.conf + sudo touch /usr/local/etc/frr/ospf6d.conf + sudo touch /usr/local/etc/frr/ospfd.conf + sudo touch /usr/local/etc/frr/pbrd.conf + sudo touch /usr/local/etc/frr/pimd.conf + sudo touch /usr/local/etc/frr/ripd.conf + sudo touch /usr/local/etc/frr/ripngd.conf + sudo touch /usr/local/etc/frr/staticd.conf + sudo touch /usr/local/etc/frr/zebra.conf + sudo chown -R frr:frr /usr/local/etc/frr/ + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/local/etc/frr/vtysh.conf + sudo chmod 640 /usr/local/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running system. diff --git a/doc/developer/building-frr-for-netbsd6.rst b/doc/developer/building-frr-for-netbsd6.rst new file mode 100644 index 0000000..77c0e00 --- /dev/null +++ b/doc/developer/building-frr-for-netbsd6.rst @@ -0,0 +1,139 @@ +NetBSD 6 +======================================== + +NetBSD 6 restrictions: +---------------------- + +- MPLS is not supported on ``NetBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS + +Install required packages +------------------------- + +Configure Package location: + +:: + + PKG_PATH="ftp://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/`uname -m`/`uname -r`/All" + export PKG_PATH + +Add packages: + +:: + + sudo pkg_add git autoconf automake libtool gmake openssl \ + pkg-config json-c py36-test python36 py36-sphinx \ + protobuf-c + +Install SSL Root Certificates (for git https access): + +:: + + sudo pkg_add mozilla-rootcerts + sudo touch /etc/openssl/openssl.cnf + sudo mozilla-rootcerts install + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo groupadd -g 92 frr + sudo groupadd -g 93 frrvty + sudo useradd -g 92 -u 92 -G frrvty -c "FRR suite" \ + -d /nonexistent -s /sbin/nologin frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + MAKE=gmake + export LDFLAGS="-L/usr/pkg/lib -R/usr/pkg/lib" + export CPPFLAGS="-I/usr/pkg/include" + ./configure \ + --sysconfdir=/usr/pkg/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo mkdir /var/log/frr + sudo mkdir /usr/pkg/etc/frr + sudo touch /usr/pkg/etc/frr/zebra.conf + sudo touch /usr/pkg/etc/frr/bgpd.conf + sudo touch /usr/pkg/etc/frr/ospfd.conf + sudo touch /usr/pkg/etc/frr/ospf6d.conf + sudo touch /usr/pkg/etc/frr/isisd.conf + sudo touch /usr/pkg/etc/frr/ripd.conf + sudo touch /usr/pkg/etc/frr/ripngd.conf + sudo touch /usr/pkg/etc/frr/pimd.conf + sudo chown -R frr:frr /usr/pkg/etc/frr + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/pkg/etc/frr/*.conf + sudo chmod 640 /usr/pkg/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running +system + +Install rc.d init files +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + cp pkgsrc/*.sh /etc/rc.d/ + chmod 555 /etc/rc.d/*.sh + +Enable FRR processes +^^^^^^^^^^^^^^^^^^^^ + +(Enable the required processes only) + +:: + + echo "zebra=YES" >> /etc/rc.conf + echo "bgpd=YES" >> /etc/rc.conf + echo "ospfd=YES" >> /etc/rc.conf + echo "ospf6d=YES" >> /etc/rc.conf + echo "isisd=YES" >> /etc/rc.conf + echo "ripngd=YES" >> /etc/rc.conf + echo "ripd=YES" >> /etc/rc.conf + echo "pimd=YES" >> /etc/rc.conf diff --git a/doc/developer/building-frr-for-netbsd7.rst b/doc/developer/building-frr-for-netbsd7.rst new file mode 100644 index 0000000..abb04a0 --- /dev/null +++ b/doc/developer/building-frr-for-netbsd7.rst @@ -0,0 +1,129 @@ +NetBSD 7 +======================================== + +NetBSD 7 restrictions: +---------------------- + +- MPLS is not supported on ``NetBSD``. MPLS requires a Linux Kernel + (4.5 or higher). LDP can be built, but may have limited use without + MPLS + +Install required packages +------------------------- + +:: + + sudo pkgin install git autoconf automake libtool gmake openssl \ + pkg-config json-c python36 py36-test py36-sphinx \ + protobuf-c + +Install SSL Root Certificates (for git https access): + +:: + + sudo pkgin install mozilla-rootcerts + sudo touch /etc/openssl/openssl.cnf + sudo mozilla-rootcerts install + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +Add frr groups and user +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo groupadd -g 92 frr + sudo groupadd -g 93 frrvty + sudo useradd -g 92 -u 92 -G frrvty -c "FRR suite" \ + -d /nonexistent -s /sbin/nologin frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + MAKE=gmake + export LDFLAGS="-L/usr/pkg/lib -R/usr/pkg/lib" + export CPPFLAGS="-I/usr/pkg/include" + ./configure \ + --sysconfdir=/usr/pkg/etc \ + --localstatedir=/var \ + --enable-pkgsrcrcdir=/usr/pkg/share/examples/rc.d \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + gmake + gmake check + sudo gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + sudo mkdir /usr/pkg/etc/frr + sudo touch /usr/pkg/etc/frr/zebra.conf + sudo touch /usr/pkg/etc/frr/bgpd.conf + sudo touch /usr/pkg/etc/frr/ospfd.conf + sudo touch /usr/pkg/etc/frr/ospf6d.conf + sudo touch /usr/pkg/etc/frr/isisd.conf + sudo touch /usr/pkg/etc/frr/ripd.conf + sudo touch /usr/pkg/etc/frr/ripngd.conf + sudo touch /usr/pkg/etc/frr/pimd.conf + sudo chown -R frr:frr /usr/pkg/etc/frr + sudo touch /usr/local/etc/frr/vtysh.conf + sudo chown frr:frrvty /usr/pkg/etc/frr/*.conf + sudo chmod 640 /usr/pkg/etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/sysctl.conf``: + +:: + + # Routing: We need to forward packets + net.inet.ip.forwarding=1 + net.inet6.ip6.forwarding=1 + +**Reboot** or use ``sysctl`` to apply the same config to the running +system + +Install rc.d init files +^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + cp pkgsrc/*.sh /etc/rc.d/ + chmod 555 /etc/rc.d/*.sh + +Enable FRR processes +^^^^^^^^^^^^^^^^^^^^ + +(Enable the required processes only) + +:: + + echo "zebra=YES" >> /etc/rc.conf + echo "bgpd=YES" >> /etc/rc.conf + echo "ospfd=YES" >> /etc/rc.conf + echo "ospf6d=YES" >> /etc/rc.conf + echo "isisd=YES" >> /etc/rc.conf + echo "ripngd=YES" >> /etc/rc.conf + echo "ripd=YES" >> /etc/rc.conf + echo "pimd=YES" >> /etc/rc.conf diff --git a/doc/developer/building-frr-for-openbsd6.rst b/doc/developer/building-frr-for-openbsd6.rst new file mode 100644 index 0000000..6d7f346 --- /dev/null +++ b/doc/developer/building-frr-for-openbsd6.rst @@ -0,0 +1,182 @@ +OpenBSD 6 +========================================= + +Install required packages +------------------------- + +Configure PKG\_PATH + +:: + + export PKG_PATH=http://ftp5.usa.openbsd.org/pub/OpenBSD/$(uname -r)/packages/$(machine -a)/ + +Add packages: + +:: + + pkg_add clang libcares python3 + pkg_add git autoconf-2.69p2 automake-1.15.1 libtool bison + pkg_add gmake json-c py-test py-sphinx libexecinfo protobuf-c + +Select Python2.7 as default (required for pytest) + +:: + + ln -s /usr/local/bin/python2.7 /usr/local/bin/python + +.. include:: building-libyang.rst + +Get FRR, compile it and install it (from Git) +--------------------------------------------- + +**This assumes you want to build and install FRR from source and not +using any packages** + +Add frr group and user +^^^^^^^^^^^^^^^^^^^^^^ + +:: + + groupadd -g 525 _frr + groupadd -g 526 _frrvty + useradd -g 525 -u 525 -c "FRR suite" -G _frrvty \ + -d /nonexistent -s /sbin/nologin _frr + +Download Source, configure and compile it +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(You may prefer different options on configure statement. These are just +an example) + +.. warning:: + + In openbsd the proper links for the libyang library may not have been created. + +:: + + ln -s /usr/lib/libyang.so.1.10.17 /usr/lib/libyang.so + +.. warning:: + + ``openbsd`` since version 6.2 has ``clang`` as the default compiler so to + build frr, clang must be used (the included gcc version is very old). + +:: + + git clone https://github.com/frrouting/frr.git frr + cd frr + export AUTOCONF_VERSION="2.69" + export AUTOMAKE_VERSION="1.15" + ./bootstrap.sh + export LDFLAGS="-L/usr/local/lib" + export CPPFLAGS="-I/usr/local/include" + ./configure \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --enable-multipath=64 \ + --enable-user=_frr \ + --enable-group=_frr \ + --enable-vty-group=_frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion \ + CC=clang + gmake + gmake check + doas gmake install + +Create empty FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + doas mkdir /var/frr + doas chown _frr:_frr /var/frr + doas chmod 755 /var/frr + doas mkdir /etc/frr + doas touch /etc/frr/zebra.conf + doas touch /etc/frr/bgpd.conf + doas touch /etc/frr/ospfd.conf + doas touch /etc/frr/ospf6d.conf + doas touch /etc/frr/isisd.conf + doas touch /etc/frr/ripd.conf + doas touch /etc/frr/ripngd.conf + doas touch /etc/frr/pimd.conf + doas touch /etc/frr/ldpd.conf + doas touch /etc/frr/nhrpd.conf + doas chown -R _frr:_frr /etc/frr + doas touch /etc/frr/vtysh.conf + doas chown -R _frr:_frrvty /etc/frr/vtysh.conf + doas chmod 750 /etc/frr + doas chmod 640 /etc/frr/*.conf + +Enable IP & IPv6 forwarding +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following lines to the end of ``/etc/rc.conf``: + +:: + + net.inet6.ip6.forwarding=1 # 1=Permit forwarding of IPv6 packets + net.inet6.ip6.mforwarding=1 # 1=Permit forwarding of IPv6 multicast packets + net.inet6.ip6.multipath=1 # 1=Enable IPv6 multipath routing + +**Reboot** to apply the config to the system + +Enable MPLS Forwarding +^^^^^^^^^^^^^^^^^^^^^^ + +To enable MPLS forwarding on a given interface, use the following +command: + +:: + + doas ifconfig em0 mpls + +Alternatively, to make MPLS forwarding persistent across reboots, add +the "mpls" keyword in the hostname.\* files of the desired interfaces. +Example: + +:: + + cat /etc/hostname.em0 + inet 10.0.1.1 255.255.255.0 mpls + +Install rc.d init files +^^^^^^^^^^^^^^^^^^^^^^^ + +(create them in /etc/rc.d - no example are included at this time with +FRR source) + +Example (for zebra - store as ``/etc/rc.d/frr_zebra.sh``) + +:: + + #!/bin/sh + # + # $OpenBSD: frr_zebra.rc,v 1.1 2013/04/18 20:29:08 sthen Exp $ + + daemon="/usr/local/sbin/zebra -d" + + . /etc/rc.d/rc.subr + + rc_cmd $1 + +Enable FRR processes +^^^^^^^^^^^^^^^^^^^^ + +(Enable the required processes only) + +:: + + echo "frr_zebra=YES" >> /etc/rc.conf + echo "frr_bgpd=YES" >> /etc/rc.conf + echo "frr_ospfd=YES" >> /etc/rc.conf + echo "frr_ospf6d=YES" >> /etc/rc.conf + echo "frr_isisd=YES" >> /etc/rc.conf + echo "frr_ripngd=YES" >> /etc/rc.conf + echo "frr_ripd=YES" >> /etc/rc.conf + echo "frr_pimd=YES" >> /etc/rc.conf + echo "frr_ldpd=YES" >> /etc/rc.conf diff --git a/doc/developer/building-frr-for-opensuse.rst b/doc/developer/building-frr-for-opensuse.rst new file mode 100644 index 0000000..6e9913d --- /dev/null +++ b/doc/developer/building-frr-for-opensuse.rst @@ -0,0 +1,148 @@ +openSUSE +======== + +This document describes installation from source. + +These instructions have been tested on openSUSE Tumbleweed in a Raspberry Pi 400. + +Installing Dependencies +----------------------- + +.. code-block:: console + + zypper in git autoconf automake libtool make \ + readline-devel texinfo net-snmp-devel groff pkgconfig libjson-c-devel\ + pam-devel python3-pytest bison flex c-ares-devel python3-devel\ + python3-Sphinx perl patch libcap-devel \ + libelf-devel libunwind-devel protobuf-c + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo useradd -u 92 -g 92 -M -r -G frrvty -s /sbin/nologin \ + -c "FRR FRRouting suite" -d /var/run/frr frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +.. note:: + + In some platforms like raspberry for performance reasons + some directories are in file systems (/var/run, ...) mounted with tempfs + so will disapear after every reboot. + In frr the /var/run/frr is used to store pid files for every daemon. + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Create a new file ``/etc/sysctl.d/90-routing-sysctl.conf`` with the following +content: + +:: + + # + # Enable packet forwarding + # + net.ipv4.conf.all.forwarding=1 + net.ipv6.conf.all.forwarding=1 + # + # Enable MPLS Label processing on all interfaces + # + #net.mpls.conf.eth0.input=1 + #net.mpls.conf.eth1.input=1 + #net.mpls.conf.eth2.input=1 + #net.mpls.platform_labels=100000 + +.. note:: + + MPLS must be invidividually enabled on each interface that requires it. See + the example in the config block above. + +Load the modified sysctls on the system: + +.. code-block:: console + + sudo sysctl -p /etc/sysctl.d/90-routing-sysctl.conf + +Create a new file ``/etc/modules-load.d/mpls.conf`` with the following content: + +:: + + # Load MPLS Kernel Modules + mpls-router + mpls-iptunnel + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + + +.. note:: + The ``firewalld`` service could be enabled. You may run into some + issues with the iptables rules it installs by default. If you wish to just + stop the service and clear `ALL` rules do these commands: + + .. code-block:: console + + sudo systemctl disable firewalld.service + sudo systemctl stop firewalld.service + sudo iptables -F + +Install frr Service +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -p -m 644 tools/frr.service /usr/lib/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``bgpd=no`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: console + + sudo systemctl start frr + +Check the starting messages of frr with + +.. code-block:: console + + journalctl -u frr --follow diff --git a/doc/developer/building-frr-for-openwrt.rst b/doc/developer/building-frr-for-openwrt.rst new file mode 100644 index 0000000..47cf2cb --- /dev/null +++ b/doc/developer/building-frr-for-openwrt.rst @@ -0,0 +1,79 @@ +OpenWrt +======= + +General info about OpenWrt buildsystem: `link `_. + +Prepare build environment +------------------------- + +For Debian based distributions, run: + +:: + + sudo apt-get install git build-essential libssl-dev libncurses5-dev \ + unzip zlib1g-dev subversion mercurial + +For other environments, instructions can be found in the +`official documentation +`_. + + +Get OpenWrt Sources (from Git) +------------------------------ + +.. note:: + The OpenWrt build will fail if you run it as root. So take care to run it as a nonprivileged user. + +Clone the OpenWrt sources and retrieve the package feeds + +:: + + git clone https://github.com/openwrt/openwrt.git + cd openwrt + ./scripts/feeds update -a + ./scripts/feeds install -a + +Configure OpenWrt for your target and select the needed FRR packages in Network -> Routing and Redirection -> frr, +exit and save + +:: + + make menuconfig + +Then, to compile either a complete OpenWrt image, or the FRR packages, run: + +:: + + make or make package/frr/compile + +It may be possible that on first build ``make package/frr/compile`` not +to work and it may be needed to run a ``make`` for the entire build +environment. Add ``V=s`` to get more debugging output. + +More information about OpenWrt buildsystem can be found `here +`__. + +Work with sources +----------------- + +To update to a newer version, or change other options, you need to edit the ``feeds/packages/frr/Makefile``. + +More information about working with patches in OpenWrt buildsystem can be found `here +`__. + +Usage +----- + +Edit ``/usr/sbin/frr.init`` and add/remove the daemons name in section +``DAEMONS=`` or don't install unneeded packages For example: zebra bgpd ldpd +isisd nhrpd ospfd ospf6d pimd ripd ripngd + +Enable the service +^^^^^^^^^^^^^^^^^^ + +- ``service frr enable`` + +Start the service +^^^^^^^^^^^^^^^^^ + +- ``service frr start`` diff --git a/doc/developer/building-frr-for-ubuntu1404.rst b/doc/developer/building-frr-for-ubuntu1404.rst new file mode 100644 index 0000000..dd3f98a --- /dev/null +++ b/doc/developer/building-frr-for-ubuntu1404.rst @@ -0,0 +1,137 @@ +Ubuntu 14.04 LTS +================ + +This document describes installation from source. If you want to build a +``deb``, see :ref:`packaging-debian`. + +Installing Dependencies +----------------------- + +.. code-block:: console + + apt-get update + apt-get install \ + git autoconf automake libtool make libreadline-dev texinfo \ + pkg-config libpam0g-dev libjson-c-dev bison flex python3-pytest \ + libc-ares-dev python3-dev python3-sphinx install-info build-essential \ + protobuf-c-compiler libprotobuf-c-dev \ + libsnmp-dev perl libcap-dev libelf-dev + +.. include:: building-libyang.rst + + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the +other settings): + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +.. warning:: + + MPLS is not supported on Ubuntu 14.04 with the default kernel. MPLS requires + kernel 4.5 or higher. LDPD can be built, but may have limited use without + MPLS. For an updated Ubuntu Kernel, see + http://kernel.ubuntu.com/~kernel-ppa/mainline/ + +Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install the init.d service +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 755 tools/frr /etc/init.d/frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start the init.d service +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + /etc/init.d/frr start + +Use ``/etc/init.d/frr status`` to check its status. diff --git a/doc/developer/building-frr-for-ubuntu1604.rst b/doc/developer/building-frr-for-ubuntu1604.rst new file mode 100644 index 0000000..f3b6aa0 --- /dev/null +++ b/doc/developer/building-frr-for-ubuntu1604.rst @@ -0,0 +1,136 @@ +Ubuntu 16.04 LTS +================ + +This document describes installation from source. If you want to build a +``deb``, see :ref:`packaging-debian`. + +Installing Dependencies +----------------------- + +.. code-block:: console + + apt-get update + apt-get install \ + git autoconf automake libtool make libreadline-dev texinfo \ + pkg-config libpam0g-dev libjson-c-dev bison flex python3-pytest \ + libc-ares-dev python3-dev python-ipaddress python3-sphinx \ + install-info build-essential libsnmp-dev perl libcap-dev \ + libelf-dev libprotobuf-c-dev protobuf-c-compiler + +.. include:: building-libyang.rst + + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the +other settings): + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +.. warning:: + + MPLS is not supported on Ubuntu 16.04 with the default kernel. MPLS requires + kernel 4.5 or higher. LDPD can be built, but may have limited use without + MPLS. For an updated Ubuntu Kernel, see + http://kernel.ubuntu.com/~kernel-ppa/mainline/ + +Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install service files +^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: console + + systemctl start frr diff --git a/doc/developer/building-frr-for-ubuntu1804.rst b/doc/developer/building-frr-for-ubuntu1804.rst new file mode 100644 index 0000000..b4880e2 --- /dev/null +++ b/doc/developer/building-frr-for-ubuntu1804.rst @@ -0,0 +1,143 @@ +Ubuntu 18.04 LTS +================ + +This document describes installation from source. If you want to build a +``deb``, see :ref:`packaging-debian`. + +Installing Dependencies +----------------------- + +.. code-block:: console + + sudo apt update + sudo apt-get install \ + git autoconf automake libtool make libreadline-dev texinfo \ + pkg-config libpam0g-dev libjson-c-dev bison flex \ + libc-ares-dev python3-dev python3-sphinx \ + install-info build-essential libsnmp-dev perl libcap-dev \ + protobuf-c-compiler libprotobuf-c-dev \ + libelf-dev libunwind-dev + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + + +ZeroMQ +^^^^^^ + +.. code-block:: console + + sudo apt-get install libzmq5 libzmq3-dev + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the +other settings): + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +Ubuntu 18.04 ships with kernel 4.15. MPLS modules are present by default. To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +If the above command returns an error, you may need to install the appropriate +or latest linux-modules-extra--generic package. For example +``apt-get install linux-modules-extra-`uname -r`-generic`` + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install service files +^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: shell + + systemctl start frr diff --git a/doc/developer/building-frr-for-ubuntu2004.rst b/doc/developer/building-frr-for-ubuntu2004.rst new file mode 100644 index 0000000..3db97c4 --- /dev/null +++ b/doc/developer/building-frr-for-ubuntu2004.rst @@ -0,0 +1,163 @@ +Ubuntu 20.04 LTS +================ + +This document describes installation from source. If you want to build a +``deb``, see :ref:`packaging-debian`. + +Installing Dependencies +----------------------- + +.. code-block:: console + + sudo apt update + sudo apt-get install \ + git autoconf automake libtool make libreadline-dev texinfo \ + pkg-config libpam0g-dev libjson-c-dev bison flex \ + libc-ares-dev python3-dev python3-sphinx \ + install-info build-essential libsnmp-dev perl \ + protobuf-c-compiler libprotobuf-c-dev \ + libcap-dev libelf-dev libunwind-dev + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +GRPC +^^^^ +If GRPC is enabled using ``--enable-grpc`` the following packages should be +installed. + +.. code-block:: console + + sudo apt-get install libgrpc++-dev protobuf-compiler-grpc + + +Config Rollbacks +^^^^^^^^^^^^^^^^ + +If config rollbacks are enabled using ``--enable-config-rollbacks`` +the sqlite3 developer package also should be installed. + +.. code-block:: console + + sudo apt install libsqlite3-dev + + +ZeroMQ +^^^^^^ + +.. code-block:: console + + sudo apt-get install libzmq5 libzmq3-dev + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the +other settings): + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +Ubuntu 20.04 ships with kernel 5.4; MPLS modules are present by default. To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +If the above command returns an error, you may need to install the appropriate +or latest linux-modules-extra--generic package. For example +``apt-get install linux-modules-extra-`uname -r`-generic`` + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install service files +^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: shell + + systemctl start frr diff --git a/doc/developer/building-frr-for-ubuntu2204.rst b/doc/developer/building-frr-for-ubuntu2204.rst new file mode 100644 index 0000000..c898c3c --- /dev/null +++ b/doc/developer/building-frr-for-ubuntu2204.rst @@ -0,0 +1,164 @@ +Ubuntu 22.04 LTS +================ + +This document describes installation from source. If you want to build a +``deb``, see :ref:`packaging-debian`. + +Installing Dependencies +----------------------- + +.. code-block:: console + + sudo apt update + sudo apt-get install \ + git autoconf automake libtool make libreadline-dev texinfo \ + pkg-config libpam0g-dev libjson-c-dev bison flex \ + libc-ares-dev python3-dev python3-sphinx \ + install-info build-essential libsnmp-dev perl \ + libcap-dev libelf-dev libunwind-dev \ + protobuf-c-compiler libprotobuf-c-dev + +.. include:: building-libunwind-note.rst + +.. include:: building-libyang.rst + +GRPC +^^^^ +If GRPC is enabled using ``--enable-grpc`` the following packages should be +installed. + +.. code-block:: console + + sudo apt-get install libgrpc++-dev protobuf-compiler-grpc + + +Config Rollbacks +^^^^^^^^^^^^^^^^ + +If config rollbacks are enabled using ``--enable-config-rollbacks`` +the sqlite3 developer package also should be installed. + +.. code-block:: console + + sudo apt install libsqlite3-dev + + +ZeroMQ +^^^^^^ +This is optional + +.. code-block:: console + + sudo apt-get install libzmq5 libzmq3-dev + +Building & Installing FRR +------------------------- + +Add FRR user and groups +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo groupadd -r -g 92 frr + sudo groupadd -r -g 85 frrvty + sudo adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRR suite" --shell /sbin/nologin frr + sudo usermod -a -G frrvty frr + +Compile +^^^^^^^ + +.. include:: include-compile.rst + +Install FRR configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 775 -o frr -g frr -d /var/log/frr + sudo install -m 775 -o frr -g frrvty -d /etc/frr + sudo install -m 640 -o frr -g frrvty tools/etc/frr/vtysh.conf /etc/frr/vtysh.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/frr.conf /etc/frr/frr.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons.conf /etc/frr/daemons.conf + sudo install -m 640 -o frr -g frr tools/etc/frr/daemons /etc/frr/daemons + +Tweak sysctls +^^^^^^^^^^^^^ + +Some sysctls need to be changed in order to enable IPv4/IPv6 forwarding and +MPLS (if supported by your platform). If your platform does not support MPLS, +skip the MPLS related configuration in this section. + +Edit :file:`/etc/sysctl.conf` and uncomment the following values (ignore the +other settings): + +:: + + # Uncomment the next line to enable packet forwarding for IPv4 + net.ipv4.ip_forward=1 + + # Uncomment the next line to enable packet forwarding for IPv6 + # Enabling this option disables Stateless Address Autoconfiguration + # based on Router Advertisements for this host + net.ipv6.conf.all.forwarding=1 + +Reboot or use ``sysctl -p`` to apply the same config to the running system. + +Add MPLS kernel modules +""""""""""""""""""""""" + +Ubuntu 20.04 ships with kernel 5.4; MPLS modules are present by default. To +enable, add the following lines to :file:`/etc/modules-load.d/modules.conf`: + +:: + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + +And load the kernel modules on the running system: + +.. code-block:: console + + sudo modprobe mpls-router mpls-iptunnel + +If the above command returns an error, you may need to install the appropriate +or latest linux-modules-extra--generic package. For example +``apt-get install linux-modules-extra-`uname -r`-generic`` + +Enable MPLS Forwarding +"""""""""""""""""""""" + +Edit :file:`/etc/sysctl.conf` and the following lines. Make sure to add a line +equal to :file:`net.mpls.conf.eth0.input` for each interface used with MPLS. + +:: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +Install service files +^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + sudo install -m 644 tools/frr.service /etc/systemd/system/frr.service + sudo systemctl enable frr + +Enable daemons +^^^^^^^^^^^^^^ + +Open :file:`/etc/frr/daemons` with your text editor of choice. Look for the +section with ``watchfrr_enable=...`` and ``zebra=...`` etc. Enable the daemons +as required by changing the value to ``yes``. + +Start FRR +^^^^^^^^^ + +.. code-block:: shell + + systemctl start frr diff --git a/doc/developer/building-libunwind-note.rst b/doc/developer/building-libunwind-note.rst new file mode 100644 index 0000000..0beb1f8 --- /dev/null +++ b/doc/developer/building-libunwind-note.rst @@ -0,0 +1,6 @@ +.. note:: + + The ``libunwind`` library is optional but highly recommended, as it improves + backtraces printed for crashes and debugging. However, if it is not + available for some reason, it can simply be left out without any loss of + functionality. diff --git a/doc/developer/building-libyang.rst b/doc/developer/building-libyang.rst new file mode 100644 index 0000000..8d9876c --- /dev/null +++ b/doc/developer/building-libyang.rst @@ -0,0 +1,47 @@ +FRR depends on the relatively new ``libyang`` library to provide YANG/NETCONF +support. Unfortunately, most distributions do not yet offer a ``libyang`` +package from their repositories. Therefore we offer two options to install this +library. + +**Option 1: Binary Install** + +The FRR project builds some binary ``libyang`` packages. + +RPM packages are at our `RPM repository `_. + +DEB packages are available as CI artifacts `here +`_. + +.. warning:: + + ``libyang`` version 2.1.128 or newer is required to build FRR. + +.. note:: + + The ``libyang`` development packages need to be installed in addition to the + libyang core package in order to build FRR successfully. Make sure to + download and install those from the link above alongside the binary + packages. + + Depending on your platform, you may also need to install the PCRE + development package. Typically this is ``libpcre2-dev`` or ``pcre2-devel``. + +**Option 2: Source Install** + +.. note:: + + Ensure that the `libyang build requirements + `_ + are met before continuing. Usually this entails installing ``cmake`` and + ``libpcre2-dev`` or ``pcre2-devel``. + +.. code-block:: console + + git clone https://github.com/CESNET/libyang.git + cd libyang + git checkout v2.1.128 + mkdir build; cd build + cmake --install-prefix /usr \ + -D CMAKE_BUILD_TYPE:String="Release" .. + make + sudo make install diff --git a/doc/developer/building.rst b/doc/developer/building.rst new file mode 100644 index 0000000..6762604 --- /dev/null +++ b/doc/developer/building.rst @@ -0,0 +1,36 @@ +.. _building: + +************ +Building FRR +************ + +.. toctree:: + :maxdepth: 2 + + static-linking + building-frr-for-alpine + building-frr-for-archlinux + building-frr-for-centos6 + building-frr-for-centos7 + building-frr-for-centos8 + building-frr-for-debian8 + building-frr-for-debian9 + building-frr-for-debian12 + building-frr-for-fedora + building-frr-for-freebsd9 + building-frr-for-freebsd10 + building-frr-for-freebsd11 + building-frr-for-freebsd13 + building-frr-for-freebsd14 + building-frr-for-netbsd6 + building-frr-for-netbsd7 + building-frr-for-openbsd6 + building-frr-for-opensuse + building-frr-for-openwrt + building-frr-for-ubuntu1404 + building-frr-for-ubuntu1604 + building-frr-for-ubuntu1804 + building-frr-for-ubuntu2004 + building-frr-for-ubuntu2204 + building-docker + cross-compiling diff --git a/doc/developer/checkpatch.rst b/doc/developer/checkpatch.rst new file mode 100644 index 0000000..4ef261b --- /dev/null +++ b/doc/developer/checkpatch.rst @@ -0,0 +1,1242 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +.. _checkpatch: + +========== +Checkpatch +========== + +Checkpatch (scripts/checkpatch.pl) is a perl script which checks for trivial +style violations in patches and optionally corrects them. Checkpatch can +also be run on file contexts and without the kernel tree. + +Checkpatch is not always right. Your judgement takes precedence over checkpatch +messages. If your code looks better with the violations, then its probably +best left alone. + + +Options +======= + +This section will describe the options checkpatch can be run with. + +Usage:: + + ./scripts/checkpatch.pl [OPTION]... [FILE]... + +Available options: + + - -q, --quiet + + Enable quiet mode. + + - -v, --verbose + Enable verbose mode. Additional verbose test descriptions are output + so as to provide information on why that particular message is shown. + + - --no-tree + + Run checkpatch without the kernel tree. + + - --no-signoff + + Disable the 'Signed-off-by' line check. The sign-off is a simple line at + the end of the explanation for the patch, which certifies that you wrote it + or otherwise have the right to pass it on as an open-source patch. + + Example:: + + Signed-off-by: Random J Developer + + Setting this flag effectively stops a message for a missing signed-off-by + line in a patch context. + + - --patch + + Treat FILE as a patch. This is the default option and need not be + explicitly specified. + + - --emacs + + Set output to emacs compile window format. This allows emacs users to jump + from the error in the compile window directly to the offending line in the + patch. + + - --terse + + Output only one line per report. + + - --showfile + + Show the diffed file position instead of the input file position. + + - -g, --git + + Treat FILE as a single commit or a git revision range. + + Single commit with: + + - + - ^ + - ~n + + Multiple commits with: + + - .. + - ... + - - + + - -f, --file + + Treat FILE as a regular source file. This option must be used when running + checkpatch on source files in the kernel. + + - --subjective, --strict + + Enable stricter tests in checkpatch. By default the tests emitted as CHECK + do not activate by default. Use this flag to activate the CHECK tests. + + - --list-types + + Every message emitted by checkpatch has an associated TYPE. Add this flag + to display all the types in checkpatch. + + Note that when this flag is active, checkpatch does not read the input FILE, + and no message is emitted. Only a list of types in checkpatch is output. + + - --types TYPE(,TYPE2...) + + Only display messages with the given types. + + Example:: + + ./scripts/checkpatch.pl mypatch.patch --types EMAIL_SUBJECT,BRACES + + - --ignore TYPE(,TYPE2...) + + Checkpatch will not emit messages for the specified types. + + Example:: + + ./scripts/checkpatch.pl mypatch.patch --ignore EMAIL_SUBJECT,BRACES + + - --show-types + + By default checkpatch doesn't display the type associated with the messages. + Set this flag to show the message type in the output. + + - --max-line-length=n + + Set the max line length (default 100). If a line exceeds the specified + length, a LONG_LINE message is emitted. + + + The message level is different for patch and file contexts. For patches, + a WARNING is emitted. While a milder CHECK is emitted for files. So for + file contexts, the --strict flag must also be enabled. + + - --min-conf-desc-length=n + + Set the Kconfig entry minimum description length, if shorter, warn. + + - --tab-size=n + + Set the number of spaces for tab (default 8). + + - --root=PATH + + PATH to the kernel tree root. + + This option must be specified when invoking checkpatch from outside + the kernel root. + + - --no-summary + + Suppress the per file summary. + + - --mailback + + Only produce a report in case of Warnings or Errors. Milder Checks are + excluded from this. + + - --summary-file + + Include the filename in summary. + + - --debug KEY=[0|1] + + Turn on/off debugging of KEY, where KEY is one of 'values', 'possible', + 'type', and 'attr' (default is all off). + + - --fix + + This is an EXPERIMENTAL feature. If correctable errors exists, a file + .EXPERIMENTAL-checkpatch-fixes is created which has the + automatically fixable errors corrected. + + - --fix-inplace + + EXPERIMENTAL - Similar to --fix but input file is overwritten with fixes. + + DO NOT USE this flag unless you are absolutely sure and you have a backup + in place. + + - --ignore-perl-version + + Override checking of perl version. Runtime errors maybe encountered after + enabling this flag if the perl version does not meet the minimum specified. + + - --codespell + + Use the codespell dictionary for checking spelling errors. + + - --codespellfile + + Use the specified codespell file. + Default is '/usr/share/codespell/dictionary.txt'. + + - --typedefsfile + + Read additional types from this file. + + - --color[=WHEN] + + Use colors 'always', 'never', or only when output is a terminal ('auto'). + Default is 'auto'. + + - --kconfig-prefix=WORD + + Use WORD as a prefix for Kconfig symbols (default is `CONFIG_`). + + - -h, --help, --version + + Display the help text. + +Message Levels +============== + +Messages in checkpatch are divided into three levels. The levels of messages +in checkpatch denote the severity of the error. They are: + + - ERROR + + This is the most strict level. Messages of type ERROR must be taken + seriously as they denote things that are very likely to be wrong. + + - WARNING + + This is the next stricter level. Messages of type WARNING requires a + more careful review. But it is milder than an ERROR. + + - CHECK + + This is the mildest level. These are things which may require some thought. + +Type Descriptions +================= + +This section contains a description of all the message types in checkpatch. + +.. Types in this section are also parsed by checkpatch. +.. The types are grouped into subsections based on use. + + +Allocation style +---------------- + + **ALLOC_ARRAY_ARGS** + The first argument for kcalloc or kmalloc_array should be the + number of elements. sizeof() as the first argument is generally + wrong. + + See: https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html + + **ALLOC_SIZEOF_STRUCT** + The allocation style is bad. In general for family of + allocation functions using sizeof() to get memory size, + constructs like:: + + p = alloc(sizeof(struct foo), ...) + + should be:: + + p = alloc(sizeof(*p), ...) + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#allocating-memory + + **ALLOC_WITH_MULTIPLY** + Prefer kmalloc_array/kcalloc over kmalloc/kzalloc with a + sizeof multiply. + + See: https://www.kernel.org/doc/html/latest/core-api/memory-allocation.html + + +API usage +--------- + + **ARCH_DEFINES** + Architecture specific defines should be avoided wherever + possible. + + **ARCH_INCLUDE_LINUX** + Whenever asm/file.h is included and linux/file.h exists, a + conversion can be made when linux/file.h includes asm/file.h. + However this is not always the case (See signal.h). + This message type is emitted only for includes from arch/. + + **AVOID_BUG** + BUG() or BUG_ON() should be avoided totally. + Use WARN() and WARN_ON() instead, and handle the "impossible" + error condition as gracefully as possible. + + See: https://www.kernel.org/doc/html/latest/process/deprecated.html#bug-and-bug-on + + **CONSIDER_KSTRTO** + The simple_strtol(), simple_strtoll(), simple_strtoul(), and + simple_strtoull() functions explicitly ignore overflows, which + may lead to unexpected results in callers. The respective kstrtol(), + kstrtoll(), kstrtoul(), and kstrtoull() functions tend to be the + correct replacements. + + See: https://www.kernel.org/doc/html/latest/process/deprecated.html#simple-strtol-simple-strtoll-simple-strtoul-simple-strtoull + + **CONSTANT_CONVERSION** + Use of __constant_ form is discouraged for the following functions:: + + __constant_cpu_to_be[x] + __constant_cpu_to_le[x] + __constant_be[x]_to_cpu + __constant_le[x]_to_cpu + __constant_htons + __constant_ntohs + + Using any of these outside of include/uapi/ is not preferred as using the + function without __constant_ is identical when the argument is a + constant. + + In big endian systems, the macros like __constant_cpu_to_be32(x) and + cpu_to_be32(x) expand to the same expression:: + + #define __constant_cpu_to_be32(x) ((__force __be32)(__u32)(x)) + #define __cpu_to_be32(x) ((__force __be32)(__u32)(x)) + + In little endian systems, the macros __constant_cpu_to_be32(x) and + cpu_to_be32(x) expand to __constant_swab32 and __swab32. __swab32 + has a __builtin_constant_p check:: + + #define __swab32(x) \ + (__builtin_constant_p((__u32)(x)) ? \ + ___constant_swab32(x) : \ + __fswab32(x)) + + So ultimately they have a special case for constants. + Similar is the case with all of the macros in the list. Thus + using the __constant_... forms are unnecessarily verbose and + not preferred outside of include/uapi. + + See: https://lore.kernel.org/lkml/1400106425.12666.6.camel@joe-AO725/ + + **DEPRECATED_API** + Usage of a deprecated RCU API is detected. It is recommended to replace + old flavourful RCU APIs by their new vanilla-RCU counterparts. + + The full list of available RCU APIs can be viewed from the kernel docs. + + See: https://www.kernel.org/doc/html/latest/RCU/whatisRCU.html#full-list-of-rcu-apis + + **DEPRECATED_VARIABLE** + EXTRA_{A,C,CPP,LD}FLAGS are deprecated and should be replaced by the new + flags added via commit f77bf01425b1 ("kbuild: introduce ccflags-y, + asflags-y and ldflags-y"). + + The following conversion scheme maybe used:: + + EXTRA_AFLAGS -> asflags-y + EXTRA_CFLAGS -> ccflags-y + EXTRA_CPPFLAGS -> cppflags-y + EXTRA_LDFLAGS -> ldflags-y + + See: + + 1. https://lore.kernel.org/lkml/20070930191054.GA15876@uranus.ravnborg.org/ + 2. https://lore.kernel.org/lkml/1313384834-24433-12-git-send-email-lacombar@gmail.com/ + 3. https://www.kernel.org/doc/html/latest/kbuild/makefiles.html#compilation-flags + + **DEVICE_ATTR_FUNCTIONS** + The function names used in DEVICE_ATTR is unusual. + Typically, the store and show functions are used with _store and + _show, where is a named attribute variable of the device. + + Consider the following examples:: + + static DEVICE_ATTR(type, 0444, type_show, NULL); + static DEVICE_ATTR(power, 0644, power_show, power_store); + + The function names should preferably follow the above pattern. + + See: https://www.kernel.org/doc/html/latest/driver-api/driver-model/device.html#attributes + + **DEVICE_ATTR_RO** + The DEVICE_ATTR_RO(name) helper macro can be used instead of + DEVICE_ATTR(name, 0444, name_show, NULL); + + Note that the macro automatically appends _show to the named + attribute variable of the device for the show method. + + See: https://www.kernel.org/doc/html/latest/driver-api/driver-model/device.html#attributes + + **DEVICE_ATTR_RW** + The DEVICE_ATTR_RW(name) helper macro can be used instead of + DEVICE_ATTR(name, 0644, name_show, name_store); + + Note that the macro automatically appends _show and _store to the + named attribute variable of the device for the show and store methods. + + See: https://www.kernel.org/doc/html/latest/driver-api/driver-model/device.html#attributes + + **DEVICE_ATTR_WO** + The DEVICE_AATR_WO(name) helper macro can be used instead of + DEVICE_ATTR(name, 0200, NULL, name_store); + + Note that the macro automatically appends _store to the + named attribute variable of the device for the store method. + + See: https://www.kernel.org/doc/html/latest/driver-api/driver-model/device.html#attributes + + **DUPLICATED_SYSCTL_CONST** + Commit d91bff3011cf ("proc/sysctl: add shared variables for range + check") added some shared const variables to be used instead of a local + copy in each source file. + + Consider replacing the sysctl range checking value with the shared + one in include/linux/sysctl.h. The following conversion scheme may + be used:: + + &zero -> SYSCTL_ZERO + &one -> SYSCTL_ONE + &int_max -> SYSCTL_INT_MAX + + See: + + 1. https://lore.kernel.org/lkml/20190430180111.10688-1-mcroce@redhat.com/ + 2. https://lore.kernel.org/lkml/20190531131422.14970-1-mcroce@redhat.com/ + + **ENOSYS** + ENOSYS means that a nonexistent system call was called. + Earlier, it was wrongly used for things like invalid operations on + otherwise valid syscalls. This should be avoided in new code. + + See: https://lore.kernel.org/lkml/5eb299021dec23c1a48fa7d9f2c8b794e967766d.1408730669.git.luto@amacapital.net/ + + **ENOTSUPP** + ENOTSUPP is not a standard error code and should be avoided in new patches. + EOPNOTSUPP should be used instead. + + See: https://lore.kernel.org/netdev/20200510182252.GA411829@lunn.ch/ + + **EXPORT_SYMBOL** + EXPORT_SYMBOL should immediately follow the symbol to be exported. + + **IN_ATOMIC** + in_atomic() is not for driver use so any such use is reported as an ERROR. + Also in_atomic() is often used to determine if sleeping is permitted, + but it is not reliable in this use model. Therefore its use is + strongly discouraged. + + However, in_atomic() is ok for core kernel use. + + See: https://lore.kernel.org/lkml/20080320201723.b87b3732.akpm@linux-foundation.org/ + + **LOCKDEP** + The lockdep_no_validate class was added as a temporary measure to + prevent warnings on conversion of device->sem to device->mutex. + It should not be used for any other purpose. + + See: https://lore.kernel.org/lkml/1268959062.9440.467.camel@laptop/ + + **MALFORMED_INCLUDE** + The #include statement has a malformed path. This has happened + because the author has included a double slash "//" in the pathname + accidentally. + + **USE_LOCKDEP** + lockdep_assert_held() annotations should be preferred over + assertions based on spin_is_locked() + + See: https://www.kernel.org/doc/html/latest/locking/lockdep-design.html#annotations + + **UAPI_INCLUDE** + No #include statements in include/uapi should use a uapi/ path. + + **USLEEP_RANGE** + usleep_range() should be preferred over udelay(). The proper way of + using usleep_range() is mentioned in the kernel docs. + + See: https://www.kernel.org/doc/html/latest/timers/timers-howto.html#delays-information-on-the-various-kernel-delay-sleep-mechanisms + + +Comments +-------- + + **BLOCK_COMMENT_STYLE** + The comment style is incorrect. The preferred style for multi- + line comments is:: + + /* + * This is the preferred style + * for multi line comments. + */ + + The networking comment style is a bit different, with the first line + not empty like the former:: + + /* This is the preferred comment style + * for files in net/ and drivers/net/ + */ + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting + + **C99_COMMENTS** + C99 style single line comments (//) should not be used. + Prefer the block comment style instead. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#commenting + + **DATA_RACE** + Applications of data_race() should have a comment so as to document the + reasoning behind why it was deemed safe. + + See: https://lore.kernel.org/lkml/20200401101714.44781-1-elver@google.com/ + + **FSF_MAILING_ADDRESS** + Kernel maintainers reject new instances of the GPL boilerplate paragraph + directing people to write to the FSF for a copy of the GPL, since the + FSF has moved in the past and may do so again. + So do not write paragraphs about writing to the Free Software Foundation's + mailing address. + + See: https://lore.kernel.org/lkml/20131006222342.GT19510@leaf/ + + +Commit message +-------------- + + **BAD_SIGN_OFF** + The signed-off-by line does not fall in line with the standards + specified by the community. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#developer-s-certificate-of-origin-1-1 + + **BAD_STABLE_ADDRESS_STYLE** + The email format for stable is incorrect. + Some valid options for stable address are:: + + 1. stable@vger.kernel.org + 2. stable@kernel.org + + For adding version info, the following comment style should be used:: + + stable@vger.kernel.org # version info + + **COMMIT_COMMENT_SYMBOL** + Commit log lines starting with a '#' are ignored by git as + comments. To solve this problem addition of a single space + infront of the log line is enough. + + **COMMIT_MESSAGE** + The patch is missing a commit description. A brief + description of the changes made by the patch should be added. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes + + **EMAIL_SUBJECT** + Naming the tool that found the issue is not very useful in the + subject line. A good subject line summarizes the change that + the patch brings. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes + + **FROM_SIGN_OFF_MISMATCH** + The author's email does not match with that in the Signed-off-by: + line(s). This can be sometimes caused due to an improperly configured + email client. + + This message is emitted due to any of the following reasons:: + + - The email names do not match. + - The email addresses do not match. + - The email subaddresses do not match. + - The email comments do not match. + + **MISSING_SIGN_OFF** + The patch is missing a Signed-off-by line. A signed-off-by + line should be added according to Developer's certificate of + Origin. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin + + **NO_AUTHOR_SIGN_OFF** + The author of the patch has not signed off the patch. It is + required that a simple sign off line should be present at the + end of explanation of the patch to denote that the author has + written it or otherwise has the rights to pass it on as an open + source patch. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin + + **DIFF_IN_COMMIT_MSG** + Avoid having diff content in commit message. + This causes problems when one tries to apply a file containing both + the changelog and the diff because patch(1) tries to apply the diff + which it found in the changelog. + + See: https://lore.kernel.org/lkml/20150611134006.9df79a893e3636019ad2759e@linux-foundation.org/ + + **GERRIT_CHANGE_ID** + To be picked up by gerrit, the footer of the commit message might + have a Change-Id like:: + + Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b + Signed-off-by: A. U. Thor + + The Change-Id line must be removed before submitting. + + **GIT_COMMIT_ID** + The proper way to reference a commit id is: + commit <12+ chars of sha1> ("") + + An example may be:: + + Commit e21d2170f36602ae2708 ("video: remove unnecessary + platform_set_drvdata()") removed the unnecessary + platform_set_drvdata(), but left the variable "dev" unused, + delete it. + + See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes + + +Comparison style +---------------- + + **ASSIGN_IN_IF** + Do not use assignments in if condition. + Example:: + + if ((foo = bar(...)) < BAZ) { + + should be written as:: + + foo = bar(...); + if (foo < BAZ) { + + **BOOL_COMPARISON** + Comparisons of A to true and false are better written + as A and !A. + + See: https://lore.kernel.org/lkml/1365563834.27174.12.camel@joe-AO722/ + + **COMPARISON_TO_NULL** + Comparisons to NULL in the form (foo == NULL) or (foo != NULL) + are better written as (!foo) and (foo). + + **CONSTANT_COMPARISON** + Comparisons with a constant or upper case identifier on the left + side of the test should be avoided. + + +Indentation and Line Breaks +--------------------------- + + **CODE_INDENT** + Code indent should use tabs instead of spaces. + Outside of comments, documentation and Kconfig, + spaces are never used for indentation. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#indentation + + **DEEP_INDENTATION** + Indentation with 6 or more tabs usually indicate overly indented + code. + + It is suggested to refactor excessive indentation of + if/else/for/do/while/switch statements. + + See: https://lore.kernel.org/lkml/1328311239.21255.24.camel@joe2Laptop/ + + **SWITCH_CASE_INDENT_LEVEL** + switch should be at the same indent as case. + Example:: + + switch (suffix) { + case 'G': + case 'g': + mem <<= 30; + break; + case 'M': + case 'm': + mem <<= 20; + break; + case 'K': + case 'k': + mem <<= 10; + fallthrough; + default: + break; + } + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#indentation + + **LONG_LINE** + The line has exceeded the specified maximum length. + To use a different maximum line length, the --max-line-length=n option + may be added while invoking checkpatch. + + Earlier, the default line length was 80 columns. Commit bdc48fa11e46 + ("checkpatch/coding-style: deprecate 80-column warning") increased the + limit to 100 columns. This is not a hard limit either and it's + preferable to stay within 80 columns whenever possible. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#breaking-long-lines-and-strings + + **LONG_LINE_STRING** + A string starts before but extends beyond the maximum line length. + To use a different maximum line length, the --max-line-length=n option + may be added while invoking checkpatch. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#breaking-long-lines-and-strings + + **LONG_LINE_COMMENT** + A comment starts before but extends beyond the maximum line length. + To use a different maximum line length, the --max-line-length=n option + may be added while invoking checkpatch. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#breaking-long-lines-and-strings + + **SPLIT_STRING** + Quoted strings that appear as messages in userspace and can be + grepped, should not be split across multiple lines. + + See: https://lore.kernel.org/lkml/20120203052727.GA15035@leaf/ + + **MULTILINE_DEREFERENCE** + A single dereferencing identifier spanned on multiple lines like:: + + struct_identifier->member[index]. + member = <foo>; + + is generally hard to follow. It can easily lead to typos and so makes + the code vulnerable to bugs. + + If fixing the multiple line dereferencing leads to an 80 column + violation, then either rewrite the code in a more simple way or if the + starting part of the dereferencing identifier is the same and used at + multiple places then store it in a temporary variable, and use that + temporary variable only at all the places. For example, if there are + two dereferencing identifiers:: + + member1->member2->member3.foo1; + member1->member2->member3.foo2; + + then store the member1->member2->member3 part in a temporary variable. + It not only helps to avoid the 80 column violation but also reduces + the program size by removing the unnecessary dereferences. + + But if none of the above methods work then ignore the 80 column + violation because it is much easier to read a dereferencing identifier + on a single line. + + **TRAILING_STATEMENTS** + Trailing statements (for example after any conditional) should be + on the next line. + Statements, such as:: + + if (x == y) break; + + should be:: + + if (x == y) + break; + + +Macros, Attributes and Symbols +------------------------------ + + **AVOID_EXTERNS** + Function prototypes don't need to be declared extern in .h + files. It's assumed by the compiler and is unnecessary. + + **AVOID_L_PREFIX** + Local symbol names that are prefixed with `.L` should be avoided, + as this has special meaning for the assembler; a symbol entry will + not be emitted into the symbol table. This can prevent `objtool` + from generating correct unwind info. + + Symbols with STB_LOCAL binding may still be used, and `.L` prefixed + local symbol names are still generally usable within a function, + but `.L` prefixed local symbol names should not be used to denote + the beginning or end of code regions via + `SYM_CODE_START_LOCAL`/`SYM_CODE_END` + + **BIT_MACRO** + Defines like: 1 << <digit> could be BIT(digit). + The BIT() macro is defined via include/linux/bits.h:: + + #define BIT(nr) (1UL << (nr)) + + **CONST_READ_MOSTLY** + When a variable is tagged with the __read_mostly annotation, it is a + signal to the compiler that accesses to the variable will be mostly + reads and rarely(but NOT never) a write. + + const __read_mostly does not make any sense as const data is already + read-only. The __read_mostly annotation thus should be removed. + + **DATE_TIME** + It is generally desirable that building the same source code with + the same set of tools is reproducible, i.e. the output is always + exactly the same. + + The kernel does *not* use the ``__DATE__`` and ``__TIME__`` macros, + and enables warnings if they are used as they can lead to + non-deterministic builds. + + See: https://www.kernel.org/doc/html/latest/kbuild/reproducible-builds.html#timestamps + + **DEFINE_ARCH_HAS** + The ARCH_HAS_xyz and ARCH_HAVE_xyz patterns are wrong. + + For big conceptual features use Kconfig symbols instead. And for + smaller things where we have compatibility fallback functions but + want architectures able to override them with optimized ones, we + should either use weak functions (appropriate for some cases), or + the symbol that protects them should be the same symbol we use. + + See: https://lore.kernel.org/lkml/CA+55aFycQ9XJvEOsiM3txHL5bjUc8CeKWJNR_H+MiicaddB42Q@mail.gmail.com/ + + **DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON** + do {} while(0) macros should not have a trailing semicolon. + + **INIT_ATTRIBUTE** + Const init definitions should use __initconst instead of + __initdata. + + Similarly init definitions without const require a separate + use of const. + + **INLINE_LOCATION** + The inline keyword should sit between storage class and type. + + For example, the following segment:: + + inline static int example_function(void) + { + ... + } + + should be:: + + static inline int example_function(void) + { + ... + } + + **MISPLACED_INIT** + It is possible to use section markers on variables in a way + which gcc doesn't understand (or at least not the way the + developer intended):: + + static struct __initdata samsung_pll_clock exynos4_plls[nr_plls] = { + + does not put exynos4_plls in the .initdata section. The __initdata + marker can be virtually anywhere on the line, except right after + "struct". The preferred location is before the "=" sign if there is + one, or before the trailing ";" otherwise. + + See: https://lore.kernel.org/lkml/1377655732.3619.19.camel@joe-AO722/ + + **MULTISTATEMENT_MACRO_USE_DO_WHILE** + Macros with multiple statements should be enclosed in a + do - while block. Same should also be the case for macros + starting with `if` to avoid logic defects:: + + #define macrofun(a, b, c) \ + do { \ + if (a == 5) \ + do_this(b, c); \ + } while (0) + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#macros-enums-and-rtl + + **PREFER_FALLTHROUGH** + Use the `fallthrough;` pseudo keyword instead of + `/* fallthrough */` like comments. + + **TRAILING_SEMICOLON** + Macro definition should not end with a semicolon. The macro + invocation style should be consistent with function calls. + This can prevent any unexpected code paths:: + + #define MAC do_something; + + If this macro is used within a if else statement, like:: + + if (some_condition) + MAC; + + else + do_something; + + Then there would be a compilation error, because when the macro is + expanded there are two trailing semicolons, so the else branch gets + orphaned. + + See: https://lore.kernel.org/lkml/1399671106.2912.21.camel@joe-AO725/ + + **SINGLE_STATEMENT_DO_WHILE_MACRO** + For the multi-statement macros, it is necessary to use the do-while + loop to avoid unpredictable code paths. The do-while loop helps to + group the multiple statements into a single one so that a + function-like macro can be used as a function only. + + But for the single statement macros, it is unnecessary to use the + do-while loop. Although the code is syntactically correct but using + the do-while loop is redundant. So remove the do-while loop for single + statement macros. + + **WEAK_DECLARATION** + Using weak declarations like __attribute__((weak)) or __weak + can have unintended link defects. Avoid using them. + + +Functions and Variables +----------------------- + + **CAMELCASE** + Avoid CamelCase Identifiers. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#naming + + **CONST_CONST** + Using `const <type> const *` is generally meant to be + written `const <type> * const`. + + **CONST_STRUCT** + Using const is generally a good idea. Checkpatch reads + a list of frequently used structs that are always or + almost always constant. + + The existing structs list can be viewed from + `scripts/const_structs.checkpatch`. + + See: https://lore.kernel.org/lkml/alpine.DEB.2.10.1608281509480.3321@hadrien/ + + **EMBEDDED_FUNCTION_NAME** + Embedded function names are less appropriate to use as + refactoring can cause function renaming. Prefer the use of + "%s", __func__ to embedded function names. + + Note that this does not work with -f (--file) checkpatch option + as it depends on patch context providing the function name. + + **FUNCTION_ARGUMENTS** + This warning is emitted due to any of the following reasons: + + 1. Arguments for the function declaration do not follow + the identifier name. Example:: + + void foo + (int bar, int baz) + + This should be corrected to:: + + void foo(int bar, int baz) + + 2. Some arguments for the function definition do not + have an identifier name. Example:: + + void foo(int) + + All arguments should have identifier names. + + **FUNCTION_WITHOUT_ARGS** + Function declarations without arguments like:: + + int foo() + + should be:: + + int foo(void) + + **GLOBAL_INITIALISERS** + Global variables should not be initialized explicitly to + 0 (or NULL, false, etc.). Your compiler (or rather your + loader, which is responsible for zeroing out the relevant + sections) automatically does it for you. + + **INITIALISED_STATIC** + Static variables should not be initialized explicitly to zero. + Your compiler (or rather your loader) automatically does + it for you. + + **MULTIPLE_ASSIGNMENTS** + Multiple assignments on a single line makes the code unnecessarily + complicated. So on a single line assign value to a single variable + only, this makes the code more readable and helps avoid typos. + + **RETURN_PARENTHESES** + return is not a function and as such doesn't need parentheses:: + + return (bar); + + can simply be:: + + return bar; + + +Permissions +----------- + + **DEVICE_ATTR_PERMS** + The permissions used in DEVICE_ATTR are unusual. + Typically only three permissions are used - 0644 (RW), 0444 (RO) + and 0200 (WO). + + See: https://www.kernel.org/doc/html/latest/filesystems/sysfs.html#attributes + + **EXECUTE_PERMISSIONS** + There is no reason for source files to be executable. The executable + bit can be removed safely. + + **EXPORTED_WORLD_WRITABLE** + Exporting world writable sysfs/debugfs files is usually a bad thing. + When done arbitrarily they can introduce serious security bugs. + In the past, some of the debugfs vulnerabilities would seemingly allow + any local user to write arbitrary values into device registers - a + situation from which little good can be expected to emerge. + + See: https://lore.kernel.org/linux-arm-kernel/cover.1296818921.git.segoon@openwall.com/ + + **NON_OCTAL_PERMISSIONS** + Permission bits should use 4 digit octal permissions (like 0700 or 0444). + Avoid using any other base like decimal. + + **SYMBOLIC_PERMS** + Permission bits in the octal form are more readable and easier to + understand than their symbolic counterparts because many command-line + tools use this notation. Experienced kernel developers have been using + these traditional Unix permission bits for decades and so they find it + easier to understand the octal notation than the symbolic macros. + For example, it is harder to read S_IWUSR|S_IRUGO than 0644, which + obscures the developer's intent rather than clarifying it. + + See: https://lore.kernel.org/lkml/CA+55aFw5v23T-zvDZp-MmD_EYxF8WbafwwB59934FV7g21uMGQ@mail.gmail.com/ + + +Spacing and Brackets +-------------------- + + **ASSIGNMENT_CONTINUATIONS** + Assignment operators should not be written at the start of a + line but should follow the operand at the previous line. + + **BRACES** + The placement of braces is stylistically incorrect. + The preferred way is to put the opening brace last on the line, + and put the closing brace first:: + + if (x is true) { + we do y + } + + This applies for all non-functional blocks. + However, there is one special case, namely functions: they have the + opening brace at the beginning of the next line, thus:: + + int function(int x) + { + body of function + } + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#placing-braces-and-spaces + + **BRACKET_SPACE** + Whitespace before opening bracket '[' is prohibited. + There are some exceptions: + + 1. With a type on the left:: + + int [] a; + + 2. At the beginning of a line for slice initialisers:: + + [0...10] = 5, + + 3. Inside a curly brace:: + + = { [0...10] = 5 } + + **CONCATENATED_STRING** + Concatenated elements should have a space in between. + Example:: + + printk(KERN_INFO"bar"); + + should be:: + + printk(KERN_INFO "bar"); + + **ELSE_AFTER_BRACE** + `else {` should follow the closing block `}` on the same line. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#placing-braces-and-spaces + + **LINE_SPACING** + Vertical space is wasted given the limited number of lines an + editor window can display when multiple blank lines are used. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#spaces + + **OPEN_BRACE** + The opening brace should be following the function definitions on the + next line. For any non-functional block it should be on the same line + as the last construct. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#placing-braces-and-spaces + + **POINTER_LOCATION** + When using pointer data or a function that returns a pointer type, + the preferred use of * is adjacent to the data name or function name + and not adjacent to the type name. + Examples:: + + char *linux_banner; + unsigned long long memparse(char *ptr, char **retptr); + char *match_strdup(substring_t *s); + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#spaces + + **SPACING** + Whitespace style used in the kernel sources is described in kernel docs. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#spaces + + **TRAILING_WHITESPACE** + Trailing whitespace should always be removed. + Some editors highlight the trailing whitespace and cause visual + distractions when editing files. + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#spaces + + **UNNECESSARY_PARENTHESES** + Parentheses are not required in the following cases: + + 1. Function pointer uses:: + + (foo->bar)(); + + could be:: + + foo->bar(); + + 2. Comparisons in if:: + + if ((foo->bar) && (foo->baz)) + if ((foo == bar)) + + could be:: + + if (foo->bar && foo->baz) + if (foo == bar) + + 3. addressof/dereference single Lvalues:: + + &(foo->bar) + *(foo->bar) + + could be:: + + &foo->bar + *foo->bar + + **WHILE_AFTER_BRACE** + while should follow the closing bracket on the same line:: + + do { + ... + } while(something); + + See: https://www.kernel.org/doc/html/latest/process/coding-style.html#placing-braces-and-spaces + + +Others +------ + + **CONFIG_DESCRIPTION** + Kconfig symbols should have a help text which fully describes + it. + + **CORRUPTED_PATCH** + The patch seems to be corrupted or lines are wrapped. + Please regenerate the patch file before sending it to the maintainer. + + **CVS_KEYWORD** + Since linux moved to git, the CVS markers are no longer used. + So, CVS style keywords ($Id$, $Revision$, $Log$) should not be + added. + + **DEFAULT_NO_BREAK** + switch default case is sometimes written as "default:;". This can + cause new cases added below default to be defective. + + A "break;" should be added after empty default statement to avoid + unwanted fallthrough. + + **DOS_LINE_ENDINGS** + For DOS-formatted patches, there are extra ^M symbols at the end of + the line. These should be removed. + + **DT_SCHEMA_BINDING_PATCH** + DT bindings moved to a json-schema based format instead of + freeform text. + + See: https://www.kernel.org/doc/html/latest/devicetree/bindings/writing-schema.html + + **DT_SPLIT_BINDING_PATCH** + Devicetree bindings should be their own patch. This is because + bindings are logically independent from a driver implementation, + they have a different maintainer (even though they often + are applied via the same tree), and it makes for a cleaner history in the + DT only tree created with git-filter-branch. + + See: https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters + + **EMBEDDED_FILENAME** + Embedding the complete filename path inside the file isn't particularly + useful as often the path is moved around and becomes incorrect. + + **FILE_PATH_CHANGES** + Whenever files are added, moved, or deleted, the MAINTAINERS file + patterns can be out of sync or outdated. + + So MAINTAINERS might need updating in these cases. + + **MEMSET** + The memset use appears to be incorrect. This may be caused due to + badly ordered parameters. Please recheck the usage. + + **NOT_UNIFIED_DIFF** + The patch file does not appear to be in unified-diff format. Please + regenerate the patch file before sending it to the maintainer. + + **PRINTF_0XDECIMAL** + Prefixing 0x with decimal output is defective and should be corrected. + + **SPDX_LICENSE_TAG** + The source file is missing or has an improper SPDX identifier tag. + The Linux kernel requires the precise SPDX identifier in all source files, + and it is thoroughly documented in the kernel docs. + + See: https://www.kernel.org/doc/html/latest/process/license-rules.html + + **TYPO_SPELLING** + Some words may have been misspelled. Consider reviewing them. diff --git a/doc/developer/cli.rst b/doc/developer/cli.rst new file mode 100644 index 0000000..59073b3 --- /dev/null +++ b/doc/developer/cli.rst @@ -0,0 +1,1007 @@ +.. _command-line-interface: + +Command Line Interface +====================== + +FRR features a flexible modal command line interface. Often when adding new +features or modifying existing code it is necessary to create or modify CLI +commands. FRR has a powerful internal CLI system that does most of the heavy +lifting for you. + +Modes +----- +FRR's CLI is organized by modes. Each mode is associated with some set of +functionality, e.g. EVPN, or some underlying object such as an interface. Each +mode contains a set of commands that control the associated functionality or +object. Users move between the modes by entering a command, which is usually +different for each source and destination mode. + +A summary of the modes is given in the following figure. + +.. graphviz:: ../figures/nodes.dot + +.. seealso:: :ref:`cli-data-structures` + +Walkup +^^^^^^ +FRR exhibits, for historical reasons, a peculiar behavior called 'walkup'. +Suppose a user is in ``OSPF_NODE``, which contains only OSPF-specific commands, +and enters the following command: :: + + ip route 192.168.100.0/24 10.0.2.2 + +This command is not defined in ``OSPF_NODE``, so the matcher will fail to match +the command in that node. The matcher will then check "parent" nodes of +``OSPF_NODE``. In this case the direct parent of ``OSPF_NODE`` is +``CONFIG_NODE``, so the current node switches to ``CONFIG_NODE`` and the command +is tried in that node. Since static route commands are defined in +``CONFIG_NODE`` the command succeeds. The procedure of attempting to execute +unmatched commands by sequentially "walking up" to parent nodes only happens in +children (direct and indirect) below ``CONFIG_NODE`` and stops at +``CONFIG_NODE``. + +Unfortunately, the internal representation of the various modes is not actually +a graph. Instead, there is an array. The parent-child relationships are not +explicitly defined in any datastructure but instead are hard-coded into the +specific commands that switch nodes. For walkup, there is a function that takes +a node and returns the parent of the node. This interface causes all manner of +insidious problems, even for experienced developers, and needs to be fixed at +some point in the future. + +Deprecation of old style of commands +------------------------------------ + +There are currently 2 styles of defining commands within a FRR source file. +``DEFUN`` and ``DEFPY``. ``DEFPY`` should be used for all new commands that +a developer is writing. This is because it allows for much better handling +of command line arguments as well as ensuring that input is correct. ``DEFUN`` +is listed here for historical reasons as well as for ensuring that existing +code can be understood by new developers. + +Defining Commands +----------------- +All definitions for the CLI system are exposed in ``lib/command.h``. In this +header there are a set of macros used to define commands. These macros are +collectively referred to as "DEFUNs", because of their syntax: + +:: + + DEFUN(command_name, + command_name_cmd, + "example command FOO...", + "Examples\n" + "CLI command\n" + "Argument\n") + { + // ...command handler... + } + +DEFUNs generally take four arguments which are expanded into the appropriate +constructs for hooking into the CLI. In order these are: + +- **Function name** - the name of the handler function for the command +- **Command name** - the identifier of the ``struct cmd_element`` for the + command. By convention this should be the function name with ``_cmd`` + appended. +- **Command definition** - an expression in FRR's CLI grammar that defines the + form of the command and its arguments, if any +- **Doc string** - a newline-delimited string that documents each element in + the command definition + +In the above example, ``command_name`` is the function name, +``command_name_cmd`` is the command name, ``"example..."`` is the definition and +the last argument is the doc string. The block following the macro is the body +of the handler function, details on which are presented later in this section. + +In order to make the command show up to the user it must be installed into the +CLI graph. To do this, call: + +``install_element(NODE, &command_name_cmd);`` + +This will install the command into the specified CLI node. Usually these calls +are grouped together in a CLI initialization function for a set of commands, and +the DEFUNs themselves are grouped into the same source file to avoid cluttering +the codebase. The names of these files follow the form ``*_vty.[ch]`` by +convention. Please do not scatter individual CLI commands in the middle of +source files; instead expose the necessary functions in a header and place the +command definition in a ``*_vty.[ch]`` file. + +.. note:: + + Please see :ref:`cli-workflow` for requirements when creating CLI commands + (e.g., JSON structure and formatting). + +Definition Grammar +^^^^^^^^^^^^^^^^^^ +FRR uses its own grammar for defining CLI commands. The grammar draws from +syntax commonly seen in \*nix manpages and should be fairly intuitive. The +parser is implemented in Bison and the lexer in Flex. These may be found in +``lib/command_parse.y`` and ``lib/command_lex.l``, respectively. + + **ProTip**: if you define a new command and find that the parser is + throwing syntax or other errors, the parser is the last place you want + to look. Bison is very stable and if it detects a syntax error, 99% of + the time it will be a syntax error in your definition. + +The formal grammar in BNF is given below. This is the grammar implemented in the +Bison parser. At runtime, the Bison parser reads all of the CLI strings and +builds a combined directed graph that is used to match and interpret user input. + +Human-friendly explanations of how to use this grammar are given a bit later in +this section alongside information on the :ref:`cli-data-structures` constructed +by the parser. + +.. productionlist:: + command: `cmd_token_seq` + : `cmd_token_seq` `placeholder_token` "..." + cmd_token_seq: *empty* + : `cmd_token_seq` `cmd_token` + cmd_token: `simple_token` + : `selector` + simple_token: `literal_token` + : `placeholder_token` + literal_token: WORD `varname_token` + varname_token: "$" WORD + placeholder_token: `placeholder_token_real` `varname_token` + placeholder_token_real: IPV4 + : IPV4_PREFIX + : IPV6 + : IPV6_PREFIX + : VARIABLE + : RANGE + : MAC + : MAC_PREFIX + : ASNUM + selector: "<" `selector_seq_seq` ">" `varname_token` + : "{" `selector_seq_seq` "}" `varname_token` + : "[" `selector_seq_seq` "]" `varname_token` + : "![" `selector_seq_seq` "]" `varname_token` + selector_seq_seq: `selector_seq_seq` "|" `selector_token_seq` + : `selector_token_seq` + selector_token_seq: `selector_token_seq` `selector_token` + : `selector_token` + selector_token: `selector` + : `simple_token` + +Tokens +^^^^^^ +The various capitalized tokens in the BNF above are in fact themselves +placeholders, but not defined as such in the formal grammar; the grammar +provides the structure, and the tokens are actually more like a type system for +the strings you write in your CLI definitions. A CLI definition string is broken +apart and each piece is assigned a type by the lexer based on a set of regular +expressions. The parser uses the type information to verify the string and +determine the structure of the CLI graph; additional metadata (such as the raw +text of each token) is encoded into the graph as it is constructed by the +parser, but this is merely a dumb copy job. + +Here is a brief summary of the various token types along with examples. + ++-----------------+--------------------------+-------------------------------------------------------+ +| Token type | Syntax | Description | ++=================+==========================+=======================================================+ +| ``WORD`` | ``show ip bgp`` | Matches itself. In the example every token is a WORD. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``IPV4`` | ``A.B.C.D`` | Matches an IPv4 address. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``IPV6`` | ``X:X::X:X`` | Matches an IPv6 address. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``IPV4_PREFIX`` | ``A.B.C.D/M`` | Matches an IPv4 prefix in CIDR notation. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``IPV6_PREFIX`` | ``X:X::X:X/M`` | Matches an IPv6 prefix in CIDR notation. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``MAC`` | ``X:X:X:X:X:X`` | Matches a 48-bit mac address. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``MAC_PREFIX`` | ``X:X:X:X:X:X/M`` | Matches a 48-bit mac address with a mask. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``VARIABLE`` | ``FOOBAR`` | Matches anything. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``RANGE`` | ``(X-Y)`` | Matches numbers in the range X..Y inclusive. | ++-----------------+--------------------------+-------------------------------------------------------+ +| ``ASNUM`` | ``<A.B|(1-4294967295)>`` | Matches an AS in plain or dot format. | ++-----------------+--------------------------+-------------------------------------------------------+ + +When presented with user input, the parser will search over all defined +commands in the current context to find a match. It is aware of the various +types of user input and has a ranking system to help disambiguate commands. For +instance, suppose the following commands are defined in the user's current +context: + +:: + + example command FOO + example command (22-49) + example command A.B.C.D/X + +The following table demonstrates the matcher's choice for a selection of +possible user input. + ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| Input | Matched command | Reason | ++=================================+===========================+==============================================================================================================+ +| ``example command eLi7eH4xx0r`` | example command FOO | ``eLi7eH4xx0r`` is not an integer or IPv4 prefix, | +| | | but FOO is a variable and matches all input. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| ``example command 42`` | example command (22-49) | ``42`` is not an IPv4 prefix. It does match both | +| | | ``(22-49)`` and ``FOO``, but RANGE tokens are more specific and have a higher priority than VARIABLE tokens. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ +| ``example command 10.3.3.0/24`` | example command A.B.C.D/X | The user entered an IPv4 prefix, which is best matched by the last command. | ++---------------------------------+---------------------------+--------------------------------------------------------------------------------------------------------------+ + +Rules +^^^^^ +There are also constructs which allow optional tokens, mutual exclusion, +one-or-more selection and repetition. + +- ``<angle|brackets>`` -- Contain sequences of tokens separated by pipes and + provide mutual exclusion. User input matches at most one option. +- ``[square brackets]`` -- Contains sequences of tokens that can be omitted. + ``[<a|b>]`` can be shortened to ``[a|b]``. +- ``![exclamation square brackets]`` -- same as ``[square brackets]``, but + only allow skipping the contents if the command input starts with ``no``. + (For cases where the positive command needs a parameter, but the parameter + is optional for the negative case.) +- ``{curly|braces}`` -- similar to angle brackets, but instead of mutual + exclusion, curly braces indicate that one or more of the pipe-separated + sequences may be provided in any order. +- ``VARIADICS...`` -- Any token which accepts input (anything except WORD) + which occurs as the last token of a line may be followed by an ellipsis, + which indicates that input matching the token may be repeated an unlimited + number of times. +- ``$name`` -- Specify a variable name for the preceding token. See + "Variable Names" below. + +Some general notes: + +- Options are allowed at the beginning of the command. The developer is + entreated to use these extremely sparingly. They are most useful for + implementing the 'no' form of configuration commands. Please think carefully + before using them for anything else. There is usually a better solution, even + if it is just separating out the command definition into separate ones. +- The developer should judiciously apply separation of concerns when defining + commands. CLI definitions for two unrelated or vaguely related commands or + configuration items should be defined in separate commands. Clarity is + preferred over LOC (within reason). +- The maximum number of space-separated tokens that can be entered is + presently limited to 256. Please keep this limit in mind when + implementing new CLI. + +Variable Names +^^^^^^^^^^^^^^ +The parser tries to fill the "varname" field on each token. This can happen +either manually or automatically. Manual specifications work by appending +``$name`` after the input specifier: + +:: + + foo bar$cmd WORD$name A.B.C.D$ip + +Note that you can also assign variable names to fixed input tokens, this can be +useful if multiple commands share code. You can also use "$name" after a +multiple-choice option: + +:: + + foo bar <A.B.C.D|X:X::X:X>$addr [optionA|optionB]$mode + +The variable name is in this case assigned to the last token in each of the +branches. + +Automatic assignment of variable names works by applying the following rules: + +- manual names always have priority +- a ``[no]`` at the beginning receives ``no`` as varname on the ``no`` token +- ``VARIABLE`` tokens whose text is not ``WORD`` or ``NAME`` receive a cleaned + lowercase version of the token text as varname, e.g. ``ROUTE-MAP`` becomes + ``route_map``. +- other variable tokens (i.e. everything except "fixed") receive the text of + the preceding fixed token as varname, if one can be found. E.g. + ``ip route A.B.C.D/M INTERFACE`` assigns "route" to the ``A.B.C.D/M`` token. + +These rules should make it possible to avoid manual varname assignment in 90% of +the cases. + +Doc Strings +^^^^^^^^^^^ +Each token in a command definition should be documented with a brief doc string +that informs a user of the meaning and/or purpose of the subsequent command +tree. These strings are provided as the last parameter to DEFUN macros, +concatenated together and separated by an escaped newline (``\n``). These are +best explained by example. + +:: + + DEFUN (config_terminal, + config_terminal_cmd, + "configure terminal", + "Configuration from vty interface\n" + "Configuration terminal\n") + +The last parameter is split into two lines for readability. Two newline +delimited doc strings are present, one for each token in the command. The second +string documents the functionality of the ``terminal`` command in the +``configure`` subtree. + +Note that the first string, for ``configure`` does not contain documentation for +'terminal'. This is because the CLI is best envisioned as a tree, with tokens +defining branches. An imaginary ``start`` token is the root of every command in +a CLI node. Each subsequent written token descends into a subtree, so the +documentation for that token ideally summarizes all the functionality contained +in the subtree. + +A consequence of this structure is that the developer must be careful to use the +same doc strings when defining multiple commands that are part of the same tree. +Commands which share prefixes must share the same doc strings for those +prefixes. On startup the parser will generate warnings if it notices +inconsistent doc strings. Behavior is undefined; the same token may show up +twice in completions, with different doc strings, or it may show up once with a +random doc string. Parser warnings should be heeded and fixed to avoid confusing +users. + +The number of doc strings provided must be equal to the amount of tokens present +in the command definition, read left to right, ignoring any special constructs. + +In the examples below, each arrowed token needs a doc string. + +:: + + "show ip bgp" + ^ ^ ^ + + "command <foo|bar> [example]" + ^ ^ ^ ^ + +DEFPY +^^^^^ +``DEFPY(...)`` is an enhanced version of ``DEFUN()`` which is preprocessed by +:file:`python/clidef.py`. The python script parses the command definition +string, extracts variable names and types, and generates a C wrapper function +that parses the variables and passes them on. This means that in the CLI +function body, you will receive additional parameters with appropriate types. + +This is best explained by an example. Invoking ``DEFPY`` like this: + +.. code-block:: c + + DEFPY(func, func_cmd, "[no] foo bar A.B.C.D (0-99)$num", "...help...") + +defines the handler function like this: + +.. code-block:: c + + func(self, vty, argc, argv, /* standard CLI arguments */ + const char *no, /* unparsed "no" */ + struct in_addr bar, /* parsed IP address */ + const char *bar_str, /* unparsed IP address */ + long num, /* parsed num */ + const char *num_str) /* unparsed num */ + +Note that as documented in the previous section, ``bar`` is automatically +applied as variable name for ``A.B.C.D``. The Python script then detects this as +an IP address argument and generates code to parse it into a ``struct in_addr``, +passing it in ``bar``. The raw value is passed in ``bar_str``. The range/number +argument works in the same way with the explicitly given variable name. + +Type rules +"""""""""" + ++----------------------------+--------------------------------+--------------------------+ +| Token(s) | Type | Value if omitted by user | ++============================+================================+==========================+ +| ``A.B.C.D`` | ``struct in_addr`` | ``0.0.0.0`` | ++----------------------------+--------------------------------+--------------------------+ +| ``X:X::X:X`` | ``struct in6_addr`` | ``::`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D + X:X::X:X`` | ``const union sockunion *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D/M`` | ``const struct prefix_ipv4 *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``X:X::X:X/M`` | ``const struct prefix_ipv6 *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``A.B.C.D/M + X:X::X:X/M`` | ``const struct prefix *`` | ``all-zeroes struct`` | ++----------------------------+--------------------------------+--------------------------+ +| ``(0-9)`` | ``long`` | ``0`` | ++----------------------------+--------------------------------+--------------------------+ +| ``VARIABLE`` | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| ``word`` | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ +| *all other* | ``const char *`` | ``NULL`` | ++----------------------------+--------------------------------+--------------------------+ + +Note the following details: + +- Not all parameters are pointers, some are passed as values. +- When the type is not ``const char *``, there will be an extra ``_str`` + argument with type ``const char *``. +- You can give a variable name not only to ``VARIABLE`` tokens but also to + ``word`` tokens (e.g. constant words). This is useful if some parts of a + command are optional. The type will be ``const char *``. +- ``[no]`` will be passed as ``const char *no``. +- Most pointers will be ``NULL`` when the argument is optional and the + user did not supply it. As noted in the table above, some prefix + struct type arguments are passed as pointers to all-zeroes structs, + not as ``NULL`` pointers. +- If a parameter is not a pointer, but is optional and the user didn't use it, + the default value will be passed. Check the ``_str`` argument if you need to + determine whether the parameter was omitted. +- If the definition contains multiple parameters with the same variable name, + they will be collapsed into a single function parameter. The python code will + detect if the types are compatible (i.e. IPv4 + IPv6 variants) and choose a + corresponding C type. +- The standard DEFUN parameters (``self, vty, argc, argv``) are still present + and can be used. A DEFUN can simply be **edited into a DEFPY without further + changes and it will still work**; this allows easy forward migration. +- A file may contain both ``DEFUN`` and ``DEFPY`` statements. + +Getting a parameter dump +"""""""""""""""""""""""" +The clidef.py script can be called to get a list of DEFUNs/DEFPYs with the +parameter name/type list: + +:: + + lib/clippy python/clidef.py --all-defun --show lib/plist.c > /dev/null + +The generated code is printed to stdout, the info dump to stderr. The +``--all-defun`` argument will make it process DEFUN blocks as well as DEFPYs, +which is useful prior to converting some DEFUNs. **The dump does not list the +``_str`` arguments** to keep the output shorter. + +Note that the ``clidef.py`` script cannot be run with python directly, it needs +to be run with *clippy* since the latter makes the CLI parser available. + +Include & Makefile requirements +""""""""""""""""""""""""""""""" +A source file that uses DEFPY needs to include the ``*_clippy.c`` file **before +all DEFPY statements**: + +.. code-block:: c + + /* GPL header */ + #include ... + ... + #include "daemon/filename_clippy.c" + + DEFPY(...) + DEFPY(...) + + install_element(...) + +This dependency needs to be marked in ``Makefile.am`` or ``subdir.am``: (there +is no ordering requirement) + +.. code-block:: make + + # ... + + # if linked into a LTLIBRARY (.la/.so): + filename.lo: filename_clippy.c + + # if linked into an executable or static library (.a): + filename.o: filename_clippy.c + +Handlers +^^^^^^^^ +The block that follows a CLI definition is executed when a user enters input +that matches the definition. Its function signature looks like this: + +.. code-block:: c + + int (*func) (const struct cmd_element *, struct vty *, int, struct cmd_token *[]); + +The first argument is the command definition struct. The last argument is an +ordered array of tokens that correspond to the path taken through the graph, and +the argument just prior to that is the length of the array. + +The arrangement of the token array has changed from Quagga's CLI implementation. +In the old system, missing arguments were padded with ``NULL`` so that the same +parts of a command would show up at the same indices regardless of what was +entered. The new system does not perform such padding and therefore it is +generally *incorrect* to assume consistent indices in this array. As a simple +example: + +Command definition: + +:: + + command [foo] <bar|baz> + +User enters: + +:: + + command foo bar + +Array: + +:: + + [0] -> command + [1] -> foo + [2] -> bar + +User enters: + +:: + + command baz + +Array: + +:: + + [0] -> command + [1] -> baz + + +.. _cli-data-structures: + +Data Structures +--------------- +On startup, the CLI parser sequentially parses each command string definition +and constructs a directed graph with each token forming a node. This graph is +the basis of the entire CLI system. It is used to match user input in order to +generate command completions and match commands to functions. + +There is one graph per CLI node (not the same as a graph node in the CLI graph). +The CLI node struct keeps a reference to its graph (see :file:`lib/command.h`). + +While most of the graph maintains the form of a tree, special constructs +outlined in the Rules section introduce some quirks. ``<>``, ``[]`` and ``{}`` +form self-contained 'subgraphs'. Each subgraph is a tree except that all of the +'leaves' actually share a child node. This helps with minimizing graph size and +debugging. + +As a working example, here is the graph of the following command: :: + + show [ip] bgp neighbors [<A.B.C.D|X:X::X:X|WORD>] [json] + +.. figure:: ../figures/cligraph.png + :align: center + + Graph of example CLI command + + +``FORK`` and ``JOIN`` nodes are plumbing nodes that don't correspond to user +input. They're necessary in order to deduplicate these constructs where +applicable. + +Options follow the same form, except that there is an edge from the ``FORK`` +node to the ``JOIN`` node. Since all of the subgraphs in the example command are +optional, all of them have this edge. + +Keywords follow the same form, except that there is an edge from ``JOIN`` to +``FORK``. Because of this the CLI graph cannot be called acyclic. There is +special logic in the input matching code that keeps a stack of paths already +taken through the node in order to disallow following the same path more than +once. + +Variadics are a bit special; they have an edge back to themselves, which allows +repeating the same input indefinitely. + +The leaves of the graph are nodes that have no out edges. These nodes are +special; their data section does not contain a token, as most nodes do, or +``NULL``, as in ``FORK``/``JOIN`` nodes, but instead has a pointer to a +``cmd_element``. All paths through the graph that terminate on a leaf are +guaranteed to be defined by that command. When a user enters a complete command, +the command matcher tokenizes the input and executes a DFS on the CLI graph. If +it is simultaneously able to exhaust all input (one input token per graph node), +and then find exactly one leaf connected to the last node it reaches, then the +input has matched the corresponding command and the command is executed. If it +finds more than one node, then the command is ambiguous (more on this in +deduplication). If it cannot exhaust all input, the command is unknown. If it +exhausts all input but does not find an edge node, the command is incomplete. + +The parser uses an incremental strategy to build the CLI graph for a node. Each +command is parsed into its own graph, and then this graph is merged into the +overall graph. During this merge step, the parser makes a best-effort attempt to +remove duplicate nodes. If it finds a node in the overall graph that is equal to +a node in the corresponding position in the command graph, it will intelligently +merge the properties from the node in the command graph into the +already-existing node. Subgraphs are also checked for isomorphism and merged +where possible. The definition of whether two nodes are 'equal' is based on the +equality of some set of token properties; read the parser source for the most +up-to-date definition of equality. + +When the parser is unable to deduplicate some complicated constructs, this can +result in two identical paths through separate parts of the graph. If this +occurs and the user enters input that matches these paths, they will receive an +'ambiguous command' error and will be unable to execute the command. Most of the +time the parser can detect and warn about duplicate commands, but it will not +always be able to do this. Hence care should be taken before defining a new +command to ensure it is not defined elsewhere. + +struct cmd\_token +^^^^^^^^^^^^^^^^^ + +.. code-block:: c + + /* Command token struct. */ + struct cmd_token + { + enum cmd_token_type type; // token type + uint8_t attr; // token attributes + bool allowrepeat; // matcher can match token repetitively? + + char *text; // token text + char *desc; // token description + long long min, max; // for ranges + char *arg; // user input that matches this token + char *varname; // variable name + }; + +This struct is used in the CLI graph to match input against. It is also used to +pass user input to command handler functions, as it is frequently useful for +handlers to have access to that information. When a command is matched, the +sequence of ``cmd_tokens`` that form the matching path are duplicated and placed +in order into ``*argv[]``. Before this happens the ``->arg`` field is set to +point at the snippet of user input that matched it. + +For most nontrivial commands the handler function will need to determine which +of the possible matching inputs was entered. Previously this was done by +looking at the first few characters of input. This is now considered an +anti-pattern and should be avoided. Instead, use the ``->type`` or ``->text`` +fields for this logic. The ``->type`` field can be used when the possible +inputs differ in type. When the possible types are the same, use the ``->text`` +field. This field has the full text of the corresponding token in the +definition string and using it makes for much more readable code. An example is +helpful. + +Command definition: + +:: + + command <(1-10)|foo|BAR> + +In this example, the user may enter any one of: + +* an integer between 1 and 10 +* "foo" +* anything at all + +If the user enters "command f", then: + +:: + + argv[1]->type == WORD_TKN + argv[1]->arg == "f" + argv[1]->text == "foo" + +Range tokens have some special treatment; a token with ``->type == RANGE_TKN`` +will have the ``->min`` and ``->max`` fields set to the bounding values of the +range. + +struct cmd\_element +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: c + + struct cmd_node { + /* Node index. */ + enum node_type node; + + /* Prompt character at vty interface. */ + const char *prompt; + + /* Is this node's configuration goes to vtysh ? */ + int vtysh; + + /* Node's configuration write function */ + int (*func)(struct vty *); + + /* Node's command graph */ + struct graph *cmdgraph; + + /* Vector of this node's command list. */ + vector cmd_vector; + + /* Hashed index of command node list, for de-dupping primarily */ + struct hash *cmd_hash; + }; + +This struct corresponds to a CLI mode. The last three fields are most relevant +here. + +cmdgraph + This is a pointer to the command graph that was described in the first part + of this section. It is the datastructure used for matching user input to + commands. + +cmd_vector + This is a list of all the ``struct cmd_element`` defined in the mode. + +cmd_hash + This is a hash table of all the ``struct cmd_element`` defined in the mode. + When ``install_element`` is called, it checks that the element it is given is + not already present in the hash table as a safeguard against duplicate calls + resulting in a command being defined twice, which renders the command + ambiguous. + +All ``struct cmd_node`` are themselves held in a static vector defined in +:file:`lib/command.c` that defines the global CLI space. + +Command Abbreviation & Matching Priority +---------------------------------------- +It is possible for users to elide parts of tokens when the CLI matcher does not +need them to make an unambiguous match. This is best explained by example. + +Command definitions: + +:: + + command dog cow + command dog crow + +User input: + +:: + + c d c -> ambiguous command + c d co -> match "command dog cow" + + +The parser will look ahead and attempt to disambiguate the input based on tokens +later on in the input string. + +Command definitions: + +:: + + show ip bgp A.B.C.D + show ipv6 bgp X:X::X:X + +User enters: + +:: + + s i b 4.3.2.1 -> match "show ip bgp A.B.C.D" + s i b ::e0 -> match "show ipv6 bgp X:X::X:X" + +Reading left to right, both of these commands would be ambiguous since 'i' does +not explicitly select either 'ip' or 'ipv6'. However, since the user later +provides a token that matches only one of the commands (an IPv4 or IPv6 address) +the parser is able to look ahead and select the appropriate command. This has +some implications for parsing the ``*argv[]`` that is passed to the command +handler. + +Now consider a command definition such as: + +:: + + command <foo|VAR> + +'foo' only matches the string 'foo', but 'VAR' matches any input, including +'foo'. Who wins? In situations like this the matcher will always choose the +'better' match, so 'foo' will win. + +Consider also: + +:: + + show <ip|ipv6> foo + +User input: + +:: + + show ip foo + +``ip`` partially matches ``ipv6`` but exactly matches ``ip``, so ``ip`` will +win. + +Adding a CLI Node +----------------- + +To add a new CLI node, you should: + +#. define a new numerical node constant +#. define a node structure in the relevant daemon +#. call ``install_node()`` in the relevant daemon +#. define and install the new node in vtysh +#. define corresponding node entry commands in daemon and vtysh +#. add a new entry to the ``ctx_keywords`` dictionary in ``tools/frr-reload.py`` + +Defining the numerical node constant +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Add your new node value to the enum before ``NODE_TYPE_MAX`` in +``lib/command.h``: + +.. code-block:: c + + enum node_type { + AUTH_NODE, // Authentication mode of vty interface. + VIEW_NODE, // View node. Default mode of vty interface. + [...] + MY_NEW_NODE, + NODE_TYPE_MAX, // maximum + }; + +Defining a node structure +^^^^^^^^^^^^^^^^^^^^^^^^^ +In your daemon-specific code where you define your new commands that +attach to the new node, add a node definition: + +.. code-block:: c + + static struct cmd_node my_new_node = { + .name = "my new node name", + .node = MY_NEW_NODE, // enum node_type lib/command.h + .parent_node = CONFIG_NODE, + .prompt = "%s(my-new-node-prompt)# ", + .config_write = my_new_node_config_write, + }; + +You will need to define ``my_new_node_config_write(struct vty \*vty)`` +(or omit this field if you have no relevant configuration to save). + +Calling ``install_node()`` +^^^^^^^^^^^^^^^^^^^^^^^^^^ +In the daemon's initialization function, before installing your new commands +with ``install_element()``, add a call ``install_node(&my_new_node)``. + +Defining and installing the new node in vtysh +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The build tools automatically collect command definitions for vtysh. +However, new nodes must be coded in vtysh specifically. + +In ``vtysh/vtysh.c``, define a stripped-down node structure and +call ``install_node()``: + +.. code-block:: c + + static struct cmd_node my_new_node = { + .name = "my new node name", + .node = MY_NEW_NODE, /* enum node_type lib/command.h */ + .parent_node = CONFIG_NODE, + .prompt = "%s(my-new-node-prompt)# ", + }; + [...] + void vtysh_init_vty(void) + { + [...] + install_node(&my_new_node) + [...] + } + +Defining corresponding node entry commands in daemon and vtysh +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The command that descends into the new node is typically programmed +with ``VTY_PUSH_CONTEXT`` or equivalent in the daemon's CLI handler function. +(If the CLI has been updated to use the new northbound architecture, +``VTY_PUSH_XPATH`` is used instead.) + +In vtysh, you must implement a corresponding node change so that vtysh +tracks the daemon's movement through the node tree. + +Although the build tools typically scan daemon code for CLI definitions +to replicate their parsing in vtysh, the node-descent function in the +daemon must be blocked from this replication so that a hand-coded +skeleton can be written in ``vtysh.c``. + +Accordingly, use one of the ``*_NOSH`` macros such as ``DEFUN_NOSH``, +``DEFPY_NOSH``, or ``DEFUN_YANG_NOSH`` for the daemon's node-descent +CLI definition, and use ``DEFUNSH`` in ``vtysh.c`` for the vtysh equivalent. + +.. seealso:: :ref:`vtysh-special-defuns` + +Examples: + +``zebra_whatever.c`` + +.. code-block:: c + + DEFPY_NOSH(my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + [...] + VTY_PUSH_CONTEXT(MY_NEW_NODE, bar); + [...] + } + + +``ripd_whatever.c`` + +.. code-block:: c + + DEFPY_YANG_NOSH(my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + [...] + VTY_PUSH_XPATH(MY_NEW_NODE, xbar); + [...] + } + + +``vtysh.c`` + +.. code-block:: c + + DEFUNSH(VTYSH_ZEBRA, my_new_node, + my_new_node_cmd, + "my-new-node foo", + "New Thing\n" + "A foo\n") + { + vty->node = MY_NEW_NODE; + return CMD_SUCCESS; + } + [...] + install_element(CONFIG_NODE, &my_new_node_cmd); + + +Adding a new entry to the ``ctx_keywords`` dictionary +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In file ``tools/frr-reload.py``, the ``ctx_keywords`` dictionary +describes the various node relationships. +Add a new node entry at the appropriate level in this dictionary. + +.. code-block:: python + + ctx_keywords = { + [...] + "key chain ": { + "key ": {} + }, + [...] + "my-new-node": {}, + [...] + } + + + +Inspection & Debugging +---------------------- + +Permutations +^^^^^^^^^^^^ +It is sometimes useful to check all the possible combinations of input that +would match an arbitrary definition string. There is a tool in +:file:`tools/permutations` that reads CLI definition strings on ``stdin`` and +prints out all matching input permutations. It also dumps a text representation +of the graph, which is more useful for debugging than anything else. It looks +like this: + +.. code-block:: shell + + $ ./permutations "show [ip] bgp [<view|vrf> WORD]" + + show ip bgp view WORD + show ip bgp vrf WORD + show ip bgp + show bgp view WORD + show bgp vrf WORD + show bgp + +This functionality is also built into VTY/VTYSH; :clicmd:`list permutations` +will list all possible matching input permutations in the current CLI node. + +Graph Inspection +^^^^^^^^^^^^^^^^ +When in the Telnet or VTYSH console, :clicmd:`show cli graph` will dump the +entire command space of the current mode in the DOT graph language. This can be +fed into one of the various GraphViz layout engines, such as ``dot``, +``neato``, etc. + +For example, to generate an image of the entire command space for the top-level +mode (``ENABLE_NODE``): + +.. code-block:: shell + + sudo vtysh -c 'show cli graph' | dot -Tjpg -Grankdir=LR > graph.jpg + +To do the same for the BGP mode: + +.. code-block:: shell + + sudo vtysh -c 'conf t' -c 'router bgp' -c 'show cli graph' | dot -Tjpg -Grankdir=LR > bgpgraph.jpg + +This information is very helpful when debugging command resolution, tracking +down duplicate / ambiguous commands, and debugging patches to the CLI graph +builder. diff --git a/doc/developer/conf.py b/doc/developer/conf.py new file mode 100644 index 0000000..495c604 --- /dev/null +++ b/doc/developer/conf.py @@ -0,0 +1,406 @@ +# -*- coding: utf-8 -*- +# +# FRR documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 31 16:00:52 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import re +import pygments +from sphinx.highlighting import lexers +from sphinx.util import logging +logger = logging.getLogger(__name__) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = "1.0" + +# prolog for various variable substitutions +rst_prolog = "" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.todo", "sphinx.ext.graphviz"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst'] +source_suffix = ".rst" + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = u"FRR" +copyright = u"2017, FRR" +author = u"FRR authors" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The short X.Y version. +version = u"?.?" +# The full version, including alpha/beta/rc tags. +release = u"?.?-?" + + +# ----------------------------------------------------------------------------- +# Extract values from codebase for substitution into docs. +# ----------------------------------------------------------------------------- + +# Various installation prefixes. Values are extracted from config.status. +# Reasonable defaults are set in case that file does not exist. +replace_vars = { + "AUTHORS": author, + "COPYRIGHT_YEAR": "1999-2005", + "COPYRIGHT_STR": "Copyright (c) 1999-2005", + "PACKAGE_NAME": project.lower(), + "PACKAGE_TARNAME": project.lower(), + "PACKAGE_STRING": project.lower() + " latest", + "PACKAGE_URL": "https://frrouting.org/", + "PACKAGE_VERSION": "latest", + "INSTALL_PREFIX_ETC": "/etc/frr", + "INSTALL_PREFIX_SBIN": "/usr/lib/frr", + "INSTALL_PREFIX_STATE": "/var/run/frr", + "INSTALL_PREFIX_MODULES": "/usr/lib/frr/modules", + "INSTALL_USER": "frr", + "INSTALL_GROUP": "frr", + "INSTALL_VTY_GROUP": "frrvty", + "GROUP": "frr", + "USER": "frr", +} + +# extract version information, installation location, other stuff we need to +# use when building final documents +val = re.compile('^S\["([^"]+)"\]="(.*)"$') +try: + with open("../../config.status", "r") as cfgstatus: + for ln in cfgstatus.readlines(): + m = val.match(ln) + if not m or m.group(1) not in replace_vars.keys(): + continue + replace_vars[m.group(1)] = m.group(2) +except IOError: + # if config.status doesn't exist, just ignore it + pass + +# manually fill out some of these we can't get from config.status +replace_vars["COPYRIGHT_STR"] = "Copyright (c)" +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["COPYRIGHT_YEAR"]) +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["AUTHORS"]) +release = replace_vars["PACKAGE_VERSION"] +version = release.split("-")[0] + +# add substitutions to prolog +for key, value in replace_vars.items(): + rst_prolog += ".. |{0}| replace:: {1}\n".format(key, value) + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [ + "_build", + "building-libunwind-note.rst", + "building-libyang.rst", + "topotests-snippets.rst", + "topotests-markers.rst", + "include-compile.rst", +] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "default" + +try: + import sphinx_rtd_theme + + html_theme = "sphinx_rtd_theme" +except ImportError: + pass + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = { +# 'sidebarbgcolor': '#374249' +# } + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = "../figures/frr-icon.svg" + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = "../figures/frr-logo-icon.png" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "FRRdoc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "FRR.tex", u"FRR Developer's Manual", u"FRR", "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = "../figures/frr-logo-medium.png" + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "frr", u"FRR Developer's Manual", [author], 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "frr", + u"FRR Developer's Manual", + author, + "FRR", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# contents of ../extra/frrlexer.py. +# This is read here to support VPATH build. Since this section is execfile()'d +# with the file location, we can safely use a relative path here to save the +# contents of the lexer file for later use even if our relative path changes +# due to VPATH. +with open("../extra/frrlexer.py", "rb") as lex: + frrlexerpy = lex.read() + +frrfmt_re = re.compile(r'^\s*%(?P<spec>[^\s]+)\s+\((?P<types>.*)\)\s*$') + +def parse_frrfmt(env, text, node): + from sphinx import addnodes + + m = frrfmt_re.match(text) + if not m: + logger.warning('could not parse frrfmt:: %r' % (text), location=node) + node += addnodes.desc_name(text, text) + return text + + spec, types = m.group('spec'), m.group('types') + + node += addnodes.desc_sig_operator('%', '%') + node += addnodes.desc_name(spec + ' ', spec + ' ') + plist = addnodes.desc_parameterlist() + for typ in types.split(','): + typ = typ.strip() + plist += addnodes.desc_parameter(typ, typ) + node += plist + return '%' + spec + +# custom extensions here +def setup(app): + # object type for FRR CLI commands, can be extended to document parent CLI + # node later on + app.add_object_type("clicmd", "clicmd") + + # printfrr extensions + app.add_object_type("frrfmt", "frrfmt", parse_node=parse_frrfmt) + + if "add_css_file" in dir(app): + app.add_css_file("overrides.css") + else: + app.add_stylesheet("overrides.css") + + # load Pygments lexer for FRR config syntax + # + # NB: in Pygments 2.2+ this can be done with `load_lexer_from_file`, but we + # do it manually since not all of our supported build platforms have 2.2 + # yet. + # + # frrlexer = pygments.lexers.load_lexer_from_file('../extra/frrlexer.py', lexername="FRRLexer") + custom_namespace = {} + exec(frrlexerpy, custom_namespace) + lexers["frr"] = custom_namespace["FRRLexer"]() diff --git a/doc/developer/cross-compiling.rst b/doc/developer/cross-compiling.rst new file mode 100644 index 0000000..c503487 --- /dev/null +++ b/doc/developer/cross-compiling.rst @@ -0,0 +1,326 @@ +Cross-Compiling +=============== + +FRR is capable of being cross-compiled to a number of different architectures. +With an adequate toolchain this process is fairly straightforward, though one +must exercise caution to validate this toolchain's correctness before attempting +to compile FRR or its dependencies; small oversights in the construction of the +build tools may lead to problems which quickly become difficult to diagnose. + +Toolchain Preliminary +--------------------- + +The first step to cross-compiling any program is to identify the system which +the program (FRR) will run on. From here on this will be called the "host" +machine, following autotools' convention, while the machine building FRR will be +called the "build" machine. The toolchain will of course be installed onto the +build machine and be leveraged to build FRR for the host machine to run. + +.. note:: + + The build machine used while writing this guide was ``x86_64-pc-linux-gnu`` + and the target machine was ``arm-linux-gnueabihf`` (a Raspberry Pi 3B+). + Replace this with your targeted tuple below if you plan on running the + commands from this guide: + + .. code-block:: shell + + export HOST_ARCH="arm-linux-gnueabihf" + + For your given target, the build system's OS may have some support for + building cross compilers natively, or may even offer binary toolchains built + upstream for the target architecture. Check your package manager or OS + documentation before committing to building a toolchain from scratch. + +This guide will not detail *how* to build a cross-compiling toolchain but +will instead assume one already exists and is installed on the build system. +The methods for building the toolchain itself may differ between operating +systems so consult the OS documentation for any particulars regarding +cross-compilers. The OSDev wiki has a `pleasant tutorial`_ on cross-compiling in +the context of operating system development which bootstraps from only the +native GCC and binutils on the build machine. This may be useful if the build +machine's OS does not offer existing tools to build a cross-compiler targeting +the host. + +.. _pleasant tutorial: https://wiki.osdev.org/GCC_Cross-Compiler + +This guide will also not demonstrate how to build all of FRR's dependencies for the +target architecture. Instead, general instructions for using a cross-compiling +toolchain to compile packages using CMake, Autotools, and Makefiles are +provided; these three cases apply to almost all FRR dependencies. + +.. _glibc mismatch: + +.. warning:: + + Ensure the versions and implementations of the C standard library (glibc or + what have you) match on the host and the build toolchain. ``ldd --version`` + will help you here. Upgrade one or the other if the they do not match. + +Testing the Toolchain +--------------------- + +Before any cross-compilation begins it would be prudent to test the new +toolchain by writing, compiling and linking a simple program. + +.. code-block:: shell + + # A small program + cat > nothing.c <<EOF + int main() { return 0; } + EOF + + # Build and link with the cross-compiler + ${HOST_ARCH}-gcc -o nothing nothing.c + + # Inspect the resulting binary, results may vary + file ./nothing + + # nothing: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), + # dynamically linked, interpreter /lib/ld-linux-armhf.so.3, + # for GNU/Linux 3.2.0, not stripped + +If this produced no errors then the installed toolchain is probably ready to +start compiling the build dependencies and eventually FRR itself. There still +may be lurking issues but fundamentally the toolchain can produce binaries and +that's good enough to start working with it. + +.. warning:: + + If any errors occurred during the previous functional test please look back + and address them before moving on; this indicates your cross-compiling + toolchain is *not* in a position to build FRR or its dependencies. Even if + everything was fine, keep in mind that many errors from here on *may still + be related* to your toolchain (e.g. libstdc++.so or other components) and this + small test is not a guarantee of complete toolchain coherence. + +Cross-compiling Dependencies +---------------------------- + +When compiling FRR it is necessary to compile some of its dependencies alongside +it on the build machine. This is so symbols from the shared libraries (which +will be loaded at run-time on the host machine) can be linked to the FRR +binaries at compile time; additionally, headers for these libraries are needed +during the compile stage for a successful build. + +Sysroot Overview +^^^^^^^^^^^^^^^^ + +All build dependencies should be installed into a "root" directory on the build +computer, hereafter called the "sysroot". This directory will be prefixed to +paths while searching for requisite libraries and headers during the build +process. Often this may be set via a ``--prefix`` flag when building the +dependent packages, meaning a ``make install`` will copy compiled libraries into +(e.g.) ``/usr/${HOST_ARCH}/usr``. + +If the toolchain was built on the build machine then there is likely already a +sysroot where those tools and standard libraries were installed; it may be +helpful to use that directory as the sysroot for this build as well. + +Basic Workflow +^^^^^^^^^^^^^^ + +Before compiling or building any dependencies, make note of which daemons are +being targeted and which libraries will be needed. Not all dependencies are +necessary if only building with a subset of the daemons. + +The following workflow will compile and install any libraries which can be built +with Autotools. The resultant library will be installed into the sysroot +``/usr/${HOST_ARCH}``. + +.. code-block:: shell + + ./configure \ + CC=${HOST_ARCH}-gcc \ + CXX=${HOST_ARCH}-g++ \ + --build=${HOST_ARCH} \ + --prefix=/usr/${HOST_ARCH} + make + make install + +Some libraries like ``json-c`` and ``libyang`` are packaged with CMake and can +be built and installed generally like: + +.. code-block:: shell + + mkdir build + cd build + CC=${HOST_ARCH}-gcc \ + CXX=${HOST_ARCH}-g++ \ + cmake \ + --install-prefix /usr/${HOST_ARCH} \ + .. + make + make install + +For programs with only a Makefile (e.g. ``libcap``) the process may look still a +little different: + +.. code-block:: shell + + CC=${HOST_ARCH}-gcc make + make install DESTDIR=/usr/${HOST_ARCH} + +These three workflows should handle the bulk of building and installing the +build-time dependencies for FRR. Verify that the installed files are being +placed correctly into the sysroot and were actually built using the +cross-compile toolchain, not by the native toolchain by accident. + +Dependency Notes +^^^^^^^^^^^^^^^^ + +There are a lot of things that can go wrong during a cross-compilation. Some of +the more common errors and a few special considerations are collected below for +reference. + +libyang +""""""" + +``-DENABLE_LYD_PRIV=ON`` should be provided during the CMake step. + +Ensure also that the version of ``libyang`` being installed corresponds to the +version required by the targeted FRR version. + +gRPC +"""" + +This piece is requisite only if the ``--enable-grpc`` flag will be passed +later on to FRR. One may get burned when compiling gRPC if the ``protoc`` +version on the build machine differs from the version of ``protoc`` being linked +to during a gRPC build. The error messages from this defect look like: + +.. code-block:: shell + + gens/src/proto/grpc/channelz/channelz.pb.h: In member function ‘void grpc::channelz::v1::ServerRef::set_name(const char*, size_t)’: + gens/src/proto/grpc/channelz/channelz.pb.h:9127:64: error: ‘EmptyDefault’ is not a member of ‘google::protobuf::internal::ArenaStringPtr’ + 9127 | name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, ::std::string( + +This happens because protocol buffer code generation uses ``protoc`` to create +classes with different getters and setters corresponding to the protobuf data +defined by the source tree's ``.proto`` files. Clearly the cross-compiled +``protoc`` cannot be used for this code generation because that binary is built +for a different CPU. + +The solution is to install matching versions of native and cross-compiled +protocol buffers; this way the native binary will generate code and the +cross-compiled library will be linked to by gRPC and these versions will not +disagree. + +---- + +The ``-latomic`` linker flag may also be necessary here if using ``libstdc++`` +since GCC's C++11 implementation makes library calls in certain cases for +``<atomic>`` so ``-latomic`` cannot be assumed. + +Cross-compiling FRR Itself +-------------------------- + +With all the necessary libraries cross-compiled and installed into the sysroot, +the last thing to actually build is FRR itself: + +.. code-block:: shell + + # Clone and bootstrap the build + git clone 'https://github.com/FRRouting/frr.git' + # (e.g.) git checkout stable/7.5 + ./bootstrap.sh + + # Build clippy using the native toolchain + mkdir build-clippy + cd build-clippy + ../configure --enable-clippy-only + make clippy-only + cd .. + + # Next, configure FRR and use the clippy we just built + ./configure \ + CC=${HOST_ARCH}-gcc \ + CXX=${HOST_ARCH}-g++ \ + --host=${HOST_ARCH} \ + --with-sysroot=/usr/${HOST_ARCH} \ + --with-clippy=./build-clippy/lib/clippy \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir="\${prefix}/lib/frr" \ + --prefix=/usr \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --disable-doc \ + --enable-grpc + + # Send it + make + +Installation to Host Machine +---------------------------- + +If no errors were observed during the previous steps it is safe to ``make +install`` FRR into its own directory. + +.. code-block:: shell + + # Install FRR its own "sysroot" + make install DESTDIR=/some/path/to/sysroot + +After running the above command, FRR binaries, modules and example configuration +files will be installed into some path on the build machine. The directory +will have folders like ``/usr`` and ``/etc``; this "root" should now be copied +to the host and installed on top of the root directory there. + +.. code-block:: shell + + # Tar this sysroot (preserving permissions) + tar -C /some/path/to/sysroot -cpvf frr-${HOST_ARCH}.tar . + + # Transfer tar file to host machine + scp frr-${HOST_ARCH}.tar me@host-machine: + + # Overlay the tarred sysroot on top of the host machine's root + ssh me@host-machine <<-EOF + # May need to elevate permissions here + tar -C / -xpvf frr-${HOST_ARCH}.tar.gz . + EOF + +Now FRR should be installed just as if ``make install`` had been run on the host +machine. Create configuration files and assign permissions as needed. Lastly, +ensure the correct users and groups exist for FRR on the host machine. + +Troubleshooting +--------------- + +Even when every precaution has been taken some things may still go wrong! This +section details some common runtime problems. + +Mismatched Libraries +^^^^^^^^^^^^^^^^^^^^ + +If you see something like this after installing on the host: + +.. code-block:: console + + /usr/lib/frr/zebra: error while loading shared libraries: libyang.so.1: cannot open shared object file: No such file or directory + +... at least one of FRR's dependencies which was linked to the binary earlier is +not available on the host OS. Even if it has been installed the host +repository's version may lag what is needed for more recent FRR builds (this is +likely to happen with YANG at the moment). + +If the matching library is not available from the host OS package manager it may +be possible to compile them using the same toolchain used to compile FRR. The +library may have already been built earlier when compiling FRR on the build +machine, in which case it may be as simple as following the same workflow laid +out during the `Installation to Host Machine`_. + +Mismatched Glibc Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The version and implementation of the C standard library must match on both the +host and build toolchain. The error corresponding to this misconfiguration will +look like: + +.. code-block:: console + + /usr/lib/frr/zebra: /lib/${HOST_ARCH}/libc.so.6: version `GLIBC_2.32' not found (required by /usr/lib/libfrr.so.0) + +See the earlier warning about preventing a `glibc mismatch`_. diff --git a/doc/developer/cspf.rst b/doc/developer/cspf.rst new file mode 100644 index 0000000..7a5a55e --- /dev/null +++ b/doc/developer/cspf.rst @@ -0,0 +1,197 @@ +Path Computation Algorithms +=========================== + +Introduction +------------ + +Both RSVP-TE and Segment Routing Flex Algo need to compute end to end path +with other constraints as the standard IGP metric. Based on Shortest Path First +(SPF) algorithms, a new class of Constrained SPF (CSPF) is provided by the FRR +library. + +Supported constraints are as follow: +- Standard IGP metric (here, CSPF provides the same result as a normal SPF) +- Traffic Engineering (TE) IGP metric +- Delay from the IGP Extended Metrics +- Bandwidth for a given Class of Service (CoS) for bandwidth reservation + +Algorithm +--------- + +The CSPF algorithm is based on a Priority Queue which store the on-going +possible path sorted by their respective weights. This weight corresponds +to the cost of the cuurent path from the source up to the current node. + +The algorithm is as followed: + +.. code-block:: c + + cost = MAX_COST; + Priority_Queue.empty(); + Visited_Node.empty(); + Processed_Path.empty(); + src = new_path(source_address); + src.cost = 0; + dst = new_destinatio(destination_address); + dst.cost = MAX_COST; + Processed_Path.add(src); + Processed_Path.add(dst); + while (Priority_Queue.count != 0) { + current_path = Priority_Queue.pop(); + current_node = next_path.destination; + Visited_Node.add(current_node); + for (current_node.edges: edge) { + if (prune_edge(current_path, edge) + continue; + if (relax(current_path) && cost > current_path.cost) { + optim_path = current_path; + cost = current_path.cost; + } + } + } + + prune_edge(path, edge) { + // check that path + edge meet constraints e.g. + if (current_path.cost + edge.cost > constrained_cost) + return false; + else + return true; + } + + relax_edge(current_path, edge) { + next_node = edge.destination; + if (Visited_Node.get(next_node)) + return false; + next_path = Processed_Path.get(edge.destination); + if (!next_path) { + next_path = new path(edge.destination); + Processed_Path.add(next_path); + } + total_cost = current_path.cost + edge.cost; + if (total_cost < next_path.cost) { + next_path = current_path; + next_path.add_edge(edge); + next_path.cost = total_cost; + Priority_Queue.add(next_path); + } + return (next_path.destination == destination); + } + + +Definition +---------- + +.. c:struct:: constraints + +This is the constraints structure that contains: + +- cost: the total cost that the path must respect +- ctype: type of constraints: + + - CSPF_METRIC for standard metric + - CSPF_TE_METRIC for TE metric + - CSPF_DELAY for delay metric + +- bw: bandwidth that the path must respect +- cos: Class of Service (COS) for the bandwidth +- family: AF_INET or AF_INET6 +- type: RSVP_TE, SR_TE or SRV6_TE + +.. c:struct:: c_path + +This is the Constraint Path structure that contains: + +- edges: List of Edges that compose the path +- status: FAILED, IN_PROGRESS, SUCCESS, NO_SOURCE, NO_DESTINATION, SAME_SRC_DST +- weight: the cost from source to the destination of the path +- dst: key of the destination vertex + +.. c:struct:: cspf + +This is the main structure for path computation. Even if it is public, you +don't need to set manually the internal field of the structure. Instead, use +the following functions: + +.. c:function:: struct cspf *cspf_new(void); + +Function to create an empty cspf for future call of path computation + +.. c:function:: struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src, const struct ls_vertex *dst, struct constraints *csts); + +This function initialize the cspf with source and destination vertex and +constraints and return pointer to the cspf structure. If input cspf structure +is NULL, a new cspf structure is allocated and initialize. + +.. c:function:: struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted, const struct in_addr src, const struct in_addr dst, struct constraints *csts); + +Same as cspf_init, but here, source and destination vertex are extract from +the TED data base based on respective IPv4 source and destination addresses. + +.. c:function:: struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted, const struct in6_addr src, const struct in6_addr dst, struct constraints *csts); + +Same as cspf_init_v4 but with IPv6 source and destination addresses. + +.. c:function:: void cspf_clean(struct cspf *algo); + +Clean internal structure of cspf in order to reuse it for another path +computation. + +.. c:function:: void cspf_del(struct cspf *algo); + +Delete cspf structure. A call to cspf_clean() function is perform prior to +free allocated memeory. + +.. c:function:: struct c_path *compute_p2p_path(struct ls_ted *ted, struct cspf *algo); + +Compute point to point path from the ted and cspf. +The function always return a constraints path. The status of the path gives +indication about the success or failure of the algorithm. If cspf structure has +not been initialize with a call to `cspf_init() or cspf_init_XX()`, the +algorithm returns a constraints path with status set to FAILED. +Note that a call to `cspf_clean()` is performed at the end of this function, +thus it is mandatory to initialize the cspf structure again prior to call again +the path computation algorithm. + + +Usage +----- + +Of course, CSPF algorithm needs a network topology that contains the +various metrics. Link State provides such Traffic Engineering Database. + +To perform a Path Computation with given constraints, proceed as follow: + +.. code-block:: c + + struct cspf *algo; + struct ls_ted *ted; + struct in_addr src; + struct in_addr dst; + struct constraints csts; + struct c_path *path; + + // Create a new CSPF structure + algo = cspf_new(); + + // Initialize constraints + csts.cost = 100; + csts.ctype = CSPF_TE_METRIC; + csts.family = AF_INET; + csts.type = SR_TE; + csts.bw = 1000000; + csts.cos = 3; + + // Then, initialise th CSPF with source, destination and constraints + cspf_init_v4(algo, ted, src, dst, &csts); + + // Finally, got the Computed Path; + path = compute_p2p_path(ted, algo); + + if (path.status == SUCCESS) + zlog_info("Got a valid constraints path"); + else + zlog_info("Unable to compute constraints path. Got %d status", path->status); + + +If you would compute another path, you must call `cspf_init()` prior to +`compute_p2p_path()` to change source, destination and/or constraints. diff --git a/doc/developer/draft-zebra-00.ms b/doc/developer/draft-zebra-00.ms new file mode 100644 index 0000000..b5d6924 --- /dev/null +++ b/doc/developer/draft-zebra-00.ms @@ -0,0 +1,209 @@ +.pl 10.0i +.po 0 +.ll 7.2i +.lt 7.2i +.nr LL 7.2i +.nr LT 7.2i +.ds LF Ishiguro +.ds RF FORMFEED[Page %] +.ds CF +.ds LH RFC DRAFT +.ds RH March 1998 +.ds CH +.hy 0 +.ad l +Network Working Group K. Ishiguro +Request for Comments: DRAFT Digital Magic Labs, Inc. + March 1998 +.sp 2 +.ce +Zebra Protocol Draft +.sp 2 +.fi +.ne 4 +Status of this Memo +.sp +.in 3 +This draft is very eary beta version. +.sp +.in 0 +.ne 4 +Introduction +.sp +.in 3 +The zebra protocol is a communication protocol between kernel +routing table manager and routing protocol daemon. It is built over +TCP/IP protocol suite. +.sp +.in 0 +.ne 4 +Request message formats +.sp +.in 3 +zebra is TCP-based protocol. +.sp +Below is request packet format. +.sp +.in 0 +.DS +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Length (2) | Command (1) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +.DE +.sp +.in 3 +Length is total packet length. +.sp +Here is summary of command list. +.sp +.in 0 +.DS +1 - ZEBRA_IPV4_ROUTE_ADD +2 - ZEBRA_IPV4_ROUTE_DELETE +3 - ZEBRA_IPV6_ROUTE_ADD +4 - ZEBRA_IPV6_ROUTE_DELETE +5 - ZEBRA_GET_ONE_INTERFACE +6 - ZEBRA_GET_ALL_INTERFACE +7 - ZEBRA_GET_HOSTINFO +.DE +.sp +.in 0 +.ne 4 +IPv4 reply message formats +.sp +.in 0 +.DS +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+ +| Type (1) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Gateway (4) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +.DE +.sp +.in 3 +Type field specify route's origin type. +.sp +.in 0 +.DS +1 - ZEBRA_ROUTE_RESERVE +2 - ZEBRA_ROUTE_CONNECT +3 - ZEBRA_ROUTE_STATIC +4 - ZEBRA_ROUTE_RIP +5 - ZEBRA_ROUTE_RIPNG +6 - ZEBRA_ROUTE_BGP +7 - ZEBRA_ROUTE_RADIX +.DE +.sp +.in 3 +After above message there can be variale length IPv4 prefix data. +Each IPv4 prefix is encoded as a two tuple of the form <masklength, +prefix> +.sp +.in 0 +.DS ++----------------------+ +|Subnet mask (1 octet) | ++----------------------+ +|IPv4 prefix (variable)| ++----------------------+ +.DE +.sp +.in 0 +.ne 4 +IPv6 reply message formats +.sp +.in 0 +.DS +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+ +| Type (1) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Gateway (16) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +.DE +.sp +.in 3 +Type field specify route's origin type. +.sp +.in 0 +.DS +1 - ZEBRA_ROUTE_RESERVE +2 - ZEBRA_ROUTE_CONNECT +3 - ZEBRA_ROUTE_STATIC +4 - ZEBRA_ROUTE_RIP +5 - ZEBRA_ROUTE_RIPNG +6 - ZEBRA_ROUTE_BGP +7 - ZEBRA_ROUTE_RADIX +.DE +.sp +.in 0 +.DS ++----------------------+ +| ifindex (4 octet) | ++----------------------+ +| prefixlen (1 octet)| ++----------------------+ +|IPv6 prefix (variable)| ++----------------------+ +.DE +.sp +.in 3 +I am not sure but it seems some operation systems IPv6 +implementation may need interface index when add and delete +linklocal routes. +.sp +I have added ifindex field to specify IPv6 routes interface +index. If this index is value zero, it will ignored. +.sp +.in 0 +.ne 4 +Interface information message format. +.sp +.in 0 +.DS +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Interface name (20) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Index (1) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Interface flag (4) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Interface metric (4) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Interface MTU (4) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Interface Address count (4) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +.DE +.sp +.in 3 +Address message format. +.sp +.in 0 +.ne 4 +Host inforamtion message format. +.sp +.in 0 +.DS +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|IPv4 forwarding|IPv6 forwarding| ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +.DE +.sp +.in 3 +Host information contain IPv4/IPv6 forwarding information. diff --git a/doc/developer/fpm.rst b/doc/developer/fpm.rst new file mode 100644 index 0000000..56d3367 --- /dev/null +++ b/doc/developer/fpm.rst @@ -0,0 +1,119 @@ +FPM +=== + +FPM stands for Forwarding Plane Manager and it's a module for use with Zebra. + +The encapsulation header for the messages exchanged with the FPM is +defined by the file :file:`fpm/fpm.h` in the frr tree. The routes +themselves are encoded in Netlink or protobuf format, with Netlink +being the default. + +Netlink is standard format for encoding messages to talk with kernel space +in Linux and it is also the name of the socket type used by it. +The FPM netlink usage differs from Linux's in: + +- Linux netlink sockets use datagrams in a multicast fashion, FPM uses + as a stream and it is unicast. +- FPM netlink messages might have more or less information than a normal + Linux netlink socket message (example: RTM_NEWROUTE might add an extra + route attribute to signalize VxLAN encapsulation). + +Protobuf is one of a number of new serialization formats wherein the +message schema is expressed in a purpose-built language. Code for +encoding/decoding to/from the wire format is generated from the +schema. Protobuf messages can be extended easily while maintaining +backward-compatibility with older code. Protobuf has the following +advantages over Netlink: + +- Code for serialization/deserialization is generated automatically. This + reduces the likelihood of bugs, allows third-party programs to be integrated + quickly, and makes it easy to add fields. +- The message format is not tied to an OS (Linux), and can be evolved + independently. + +.. note:: + + Currently there are two FPM modules in ``zebra``: + + * ``fpm`` + * ``dplane_fpm_nl`` + +fpm +^^^ + +The first FPM implementation that was built using hooks in ``zebra`` route +handling functions. It uses its own netlink/protobuf encoding functions to +translate ``zebra`` route data structures into formatted binary data. + + +dplane_fpm_nl +^^^^^^^^^^^^^ + +The newer FPM implementation that was built using ``zebra``'s data plane +framework as a plugin. It only supports netlink and it shares ``zebra``'s +netlink functions to translate route event snapshots into formatted binary +data. + + +Protocol Specification +---------------------- + +FPM (in any mode) uses a TCP connection to talk with external applications. +It operates as TCP client and uses the CLI configured address/port to connect +to the FPM server (defaults to port ``2620``). + +FPM frames all data with a header to help the external reader figure how +many bytes it has to read in order to read the full message (this helps +simulates datagrams like in the original netlink Linux kernel usage). + +Frame header: + +:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +---------------+---------------+-------------------------------+ + | Version | Message type | Message length | + +---------------+---------------+-------------------------------+ + | Data... | + +---------------------------------------------------------------+ + + +Version +^^^^^^^ + +Currently there is only one version, so it should be always ``1``. + + +Message Type +^^^^^^^^^^^^ + +Defines what underlining protocol we are using: netlink (``1``) or protobuf (``2``). + + +Message Length +^^^^^^^^^^^^^^ + +Amount of data in this frame in network byte order. + + +Data +^^^^ + +The netlink or protobuf message payload. + + +Route Status Notification from ASIC +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The dplane_fpm_nl has the ability to read route netlink messages +from the underlying fpm implementation that can tell zebra +whether or not the route has been Offloaded/Failed or Trapped. +The end developer must send the data up the same socket that has +been created to listen for FPM messages from Zebra. The data sent +must have a Frame Header with Version set to 1, Message Type set to 1 +and an appropriate message Length. The message data must contain +a RTM_NEWROUTE netlink message that sends the prefix and nexthops +associated with the route. Finally rtm_flags must contain +RTM_F_OFFLOAD, RTM_F_TRAP and or RTM_F_OFFLOAD_FAILED to signify +what has happened to the route in the ASIC. diff --git a/doc/developer/frr-release-procedure.rst b/doc/developer/frr-release-procedure.rst new file mode 100644 index 0000000..9dbc9b4 --- /dev/null +++ b/doc/developer/frr-release-procedure.rst @@ -0,0 +1,274 @@ +.. _frr-release-procedure: + +FRR Release Procedure +===================== + +``<version>`` - version to be released, e.g. 7.3 +``origin`` - FRR upstream repository + +Stage 1 - Preparation +--------------------- + +#. Prepare changelog for the new release + + Note: use ``tools/release_notes.py`` to help draft release notes changelog + + .. code-block:: console + + ./tools/release_notes.py -b dev/9.1 -t frr-9.0.1 + + dev/9.1 is the branch to be renamed to stable/9.1, and frr-9.0.1 in this + example is the latest tag from which to generate the logs. + +#. Checkout the existing ``dev/<version>`` branch. + + .. code-block:: console + + git checkout dev/<version> + +#. Create and push a new branch called ``stable/<version>`` based on the + ``dev/<version>`` branch. + + .. code-block:: console + + git checkout -b stable/<version> + +#. Remove the development branch called ``dev/<version>`` + + .. code-block:: console + + git push origin --delete dev/<version> + +#. Update Changelog for Red Hat Packages: + + Edit :file:`redhat/frr.spec.in` and look for the ``%changelog`` section: + + - Change last (top of list) entry from ``%{version}`` to the **last** + released version number. For example, if ``<version>`` is ``7.3`` and the + last public release was ``7.2``, you would use ``7.2``, changing the file + like so:: + + * Tue Nov 7 2017 Martin Winter <mwinter@opensourcerouting.org> - %{version} + + to:: + + * Tue Nov 7 2017 Martin Winter <mwinter@opensourcerouting.org> - 7.2 + + - Add new entry to the top of the list with ``%{version}`` tag. Make sure + to watch the format, i.e. the day is always 2 characters, with the 1st + character being a space if the day is one digit. + + - Add the changelog text below this entry. + +#. Update Changelog for Debian Packages: + + Update :file:`debian/changelog`: + + - Run following with **last** release version number and debian revision + (usually -1) as argument to ``dch --newversion VERSION``. For example, if + ``<version>`` is ``7.3`` then you will run ``dch --newversion 7.3-1``. + + - The ``dch`` will run an editor, and you should add the changelog text below + this entry, usually that would be: **New upstream version**. + + - Verify the changelog format using ``dpkg-parsechangelog``. In the + repository root: + + .. code-block:: console + + dpkg-parsechangelog + + You should see output like this:: + + vagrant@local ~/frr> dpkg-parsechangelog + Source: frr + Version: 7.3-dev-0 + Distribution: UNRELEASED + Urgency: medium + Maintainer: FRRouting-Dev <dev@lists.frrouting.org> + Timestamp: 1540478210 + Date: Thu, 25 Oct 2018 16:36:50 +0200 + Changes: + frr (7.3-dev-0) RELEASED; urgency=medium + . + * Your Changes Here + +#. Commit the changes, adding the changelog to the commit message. Follow all + existing commit guidelines. The commit message should be akin to:: + + debian, redhat: updating changelog for new release + +#. Change main version number: + + - Edit :file:`configure.ac` and change version in the ``AC_INIT`` command + to ``<version>`` + + Add and commit this change. This commit should be separate from the commit + containing the changelog. The commit message should be:: + + FRR Release <version> + + The version field should be complete; i.e. for ``8.0.0``, the version should + be ``8.0.0`` and not ``8.0`` or ``8``. + + +Stage 2 - Staging +----------------- + +#. Push the stable branch to a new remote branch prefixed with ``rc``:: + + git push origin stable/<version>:rc/version + + This will trigger the NetDEF CI, which serve as a sanity check on the + release branch. Verify that all tests pass and that all package builds are + successful. To do this, go to the NetDEF CI located here: + + https://ci1.netdef.org/browse/FRR-FRR + + In the top left, look for ``rc-<version>`` in the "Plan branch" dropdown. + Select this version. Note that it may take a few minutes for the CI to kick + in on this new branch and appear in the list. + +#. Push the stable branch: + + .. code-block:: console + + git push origin stable/<version>:refs/heads/stable/<version> + +#. Create and push a git tag for the version: + + .. code-block:: console + + git tag -a frr-<version> -m "FRRouting Release <version>" + git push origin frr-<version> + +#. Create a new branch based on ``master``, cherry-pick the commit made earlier + that added the changelogs, and use it to create a PR against ``master``. + This way ``master`` has the latest changelog for the next cycle. + +#. Kick off the "Release" build plan on the CI system for the correct release. + Contact Martin Winter for this step. Ensure all release packages build + successfully. + +#. Kick off the Snapcraft build plan for the release. + +#. Build Docker images + + 1. Log into the Netdef Docker build VM + 2. ``sudo -su builduser`` + 3. Suppose we are releasing 8.5.0, then ``X.Y.Z`` is ``8.5.0``. Run this: + + .. code-block:: console + + cd /home/builduser/frr + TAG=X.Y.Z + git fetch --all + git checkout frr-$TAG + docker buildx build --platform linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/arm/v7,linux/arm/v6 -f docker/alpine/Dockerfile -t quay.io/frrouting/frr:$TAG --push . + git tag docker/$TAG + git push origin docker/$TAG + + This will build a multi-arch image and upload it to Quay, as well as + create a git tag corresponding to the commit that the image was built + from and upload that to Github. It's important that the git tag point to + the exact codebase that was used to build the docker image, so if any + changes need to be made on top of the ``frr-$TAG`` release tag, make + sure these changes are committed and pointed at by the ``docker/X.Y.Z`` + tag. + + +Stage 3 - Publish +----------------- + +#. Upload both the Debian and RPM packages to their respective repositories. + +#. Coordinate with the maintainer of FRR's RPM repository to publish the RPM + packages on that repository. Update the repository webpage. Verify that the + instructions on the webpage work and that FRR is installable from the + repository on a Red Hat system. + + Current maintainer: *Martin Winter* + +#. Coordinate with the maintainer of FRR Debian package to publish the Debian + packages on that repository. Update the repository webpage. Verify that the + instructions on the webpage work and that FRR is installable from the + repository on a Debian system. + + Current maintainer: *Jafar Al-Gharaibeh* + +#. Log in to the Read The Docs instance. in the "FRRouting" project, navigate + to the "Overview" tab. Ensure there is a ``stable-<version>`` version listed + and that it is enabled. Go to "Admin" and then "Advanced Settings". Change + "Default version" to the new version. This ensures that the documentation + shown to visitors is that of the latest release by default. + + This step must be performed by someone with administrative access to the + Read the Docs instance. + +#. On GitHub, go to the <https://github.com/FRRouting/frr/releases>_ and click + "Draft a new release". Write a release announcement. The release + announcement should follow the template in + ``release-announcement-template.md``, located next to this document. Check + for spelling errors, and optionally (but preferably) have other maintainers + proofread the announcement text. + + Do not attach any packages or source tarballs to the GitHub release. + + Publish the release once it is reviewed. + +#. Deploy Snapcraft release. Remember that this will automatically upgrade Snap + users. + + Current maintainer: *Martin Winter* + +#. Build and publish the Docker containers. + + Current maintainer: *Quentin Young* + +#. Clone the ``frr-www`` repository: + + .. code-block:: console + + git clone https://github.com/FRRouting/frr-www.git + +#. Add a new release announcement, using a previous announcement as template: + + .. code-block:: console + + cp content/release/<old-version>.md content/release/<new-version>.md + + Paste the GitHub release announcement text into this document, and **remove + line breaks**. In other words, this:: + + This is one continuous + sentence that should be + rendered on one line + + Needs to be changed to this:: + + This is one continuous sentence that should be rendered on one line + + This is very important otherwise the announcement will be unreadable on the + website. + + To get the number of commiters and commits, here is a couple of handy commands: + + .. code-block:: console + + # The number of commits + % git log --oneline --no-merges base_8.2...base_8.1 | wc -l + + # The number of commiters + % git shortlog --summary --no-merges base_8.2...base_8.1 | wc -l + + Make sure to add a link to the GitHub releases page at the top. + +#. Deploy the updated ``frr-www`` on the frrouting.org web server and verify + that the announcement text is visible. + +#. Update readthedocs.org (Default Version) for https://docs.frrouting.org to + be the version of this latest release. + +#. Send an email to ``announce@lists.frrouting.org``. The text of this email + should include text as appropriate from the GitHub release and a link to the + GitHub release, Debian repository, and RPM repository. diff --git a/doc/developer/fuzzing.rst b/doc/developer/fuzzing.rst new file mode 100644 index 0000000..8a33187 --- /dev/null +++ b/doc/developer/fuzzing.rst @@ -0,0 +1,164 @@ +.. _fuzzing: + +Fuzzing +======= + +This page describes the fuzzing targets and supported fuzzers available in FRR +and how to use them. Familiarity with fuzzing techniques and tools is assumed. + +Overview +-------- + +It is well known that networked applications tend to be difficult to fuzz on +their network-facing attack surfaces. Approaches involving actual network +transmission tend to be slow and are subject to intermediate devices and +networking stacks which tend to drop fuzzed packets, especially if the fuzzing +surface covers IP itself. Some time was spent on fuzzing FRR this way with some +mediocre results but attention quickly turned towards skipping the actual +networking and instead adding fuzzing targets directly in the packet processing +code for use by more traditional in- and out-of-process fuzzers. Results from +this approach have been very fruitful. + +The patches to add fuzzing targets are kept in a separate git branch. Typically +it is better to keep them in the main branch so they are kept up to date and do +not need to be constantly synchronized with the main codebase. Unfortunately, +changes to FRR to support fuzzing necessarily extend far beyond the +entrypoints. Checksums must be disarmed, interactions with the kernel must be +skipped, sockets and files must be avoided, desired under/overflows must be +marked, etc. There are the usual ``LD_PRELOAD`` libraries to emulate these +things (preeny et al) but FRR is a very kernel-reliant program and these +libraries tend to create annoying problems when used with FRR for whatever +reason. Keeping this code in the main codebase is cluttering, difficult to work +with / around, and runs the risk of accidentally introducing bugs even if +``#ifdef``'d out. Consequently it's in a separate branch that is rebased on +``master`` from time to time. + + +Code +---- + +The git branch with fuzzing targets is located here: + +https://github.com/FRRouting/frr/tree/fuzz + +To build libFuzzer targets, pass ``--enable-libfuzzer`` to ``configure``. +To build AFL targets, compile with ``afl-clang`` as usual. + +Fuzzing with sanitizers is strongly recommended, especially ASAN, which you can +enable by passing ``--enable-address-sanitizer`` to ``configure``. + +Suggested UBSAN flags: ``-fsanitize-recover=unsigned-integer-overflow,implicit-conversion -fsanitize=unsigned-integer-overflow,implicit-conversion,nullability-arg,nullability-assign,nullability-return`` +Recommended cflags: ``-Wno-all -g3 -O3 -funroll-loops`` + +Design +------ + +All fuzzing targets have support for libFuzzer and AFL. This is done by writing +the target as a libFuzzer entrypoint (``LLVMFuzzerTestOneInput()``) and calling +it from the AFL entrypoint in ``main()``. New targets should use this rule. + +When adding AFL entrypoints, it's a good idea to use AFL persistent mode for +better performance. Grep ``bgpd/bgp_main.c`` for ``__AFL_INIT()`` for an +example of how to do this in FRR. Typically it involves moving all internal +daemon setup into a setup function. Then this setup function is called exactly +once for the lifetime of the process. In ``LLVMFuzzerTestOneInput()`` this +means you need to call it at the start of the function protected by a static +boolean that is set to true, since that function is your entrypoint. You also +need to call it prior to ``__AFL_INIT()`` in ``main()`` because ``main()`` is +your entrypoint in the AFL case. + +Adding support to daemons +^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section describes how to add entrypoints to daemons that do not have any +yet. + +Because libFuzzer has its own ``main()`` function, when adding fuzzing support +to a daemon that doesn't have any targets already, ``main()`` needs to be +``#ifdef``'d out like so: + +.. code:: c + + #ifndef FUZZING_LIBFUZZER + + int main(int argc, char **argv) + { + ... + } + + #endif /* FUZZING_LIBFUZZER */ + + +The ``FUZZING_LIBFUZZER`` macro is set by ``--enable-libfuzzer``. + +Because libFuzzer can only be linked into daemons that have +``LLVMFuzzerTestOneInput()`` implemented, we can't pass ``-fsanitize=fuzzer`` +to all daemons in ``AM_CFLAGS``. It needs to go into a variable specific to +each daemon. Since it can be thought of as a kind of sanitizer, for daemons +that have libFuzzer support there are now individual flags variables for those +daemons named ``DAEMON_SAN_FLAGS`` (e.g. ``BGPD_SAN_FLAGS``, +``ZEBRA_SAN_FLAGS``). This variable has the contents of the generic +``SAN_FLAGS`` plus any fuzzing-related flags. It is used in daemons' +``subdir.am`` in place of ``SAN_FLAGS``. Daemons that don't support libFuzzer +still use ``SAN_FLAGS``. If you want to add fuzzing support to a daemon you +need to do this flag variable conversion; look at ``configure.ac`` for +examples, it is fairly straightforward. Remember to update ``subdir.am`` to use +the new variable. + +Do note that when fuzzing is enabled, ``SAN_FLAGS`` gains +``-fsanitize=fuzzer-no-link``; the result is that all daemons are instrumented +for fuzzing but only the ones with ``LLVMFuzzerTestOneInput()`` actually get +linked with libFuzzer. + + +Targets +------- + +A given daemon can have lots of different paths that are interesting to fuzz. +There's not really a great way to handle this, most fuzzers assume the program +has one entrypoint. The approach taken in FRR for multiple entrypoints is to +control which path is taken within ``LLVMFuzzerTestOneInput()`` using +``#ifdef`` and passing whatever controlling macro definition you want. Take a +look at that function for the daemon you're interested in fuzzing, pick the +target, add ``#define MY_TARGET 1`` somewhere before the ``#ifdef`` switch, +recompile. + +.. list-table:: Fuzzing Targets + + * - Daemon + - Target + - Fuzzers + * - bgpd + - packet parser + - libfuzzer, afl + * - ospfd + - packet parser + - libfuzzer, afl + * - pimd + - packet parser + - libfuzzer, afl + * - vrrpd + - packet parser + - libfuzzer, afl + * - vrrpd + - zapi parser + - libfuzzer, afl + * - zebra + - netlink + - libfuzzer, afl + * - zebra + - zserv / zapi + - libfuzzer, afl + + +Fuzzer Notes +------------ + +Some interesting seed corpuses for various daemons are available `here +<https://github.com/qlyoung/frr-fuzz/tree/master/samples>`_. + +For libFuzzer, you need to pass ``-rss_limit_mb=0`` if you are fuzzing with +ASAN enabled, as you should. + +For AFL, afl++ is strongly recommended; afl proper isn't really maintained +anymore. diff --git a/doc/developer/grpc.rst b/doc/developer/grpc.rst new file mode 100644 index 0000000..4e81adf --- /dev/null +++ b/doc/developer/grpc.rst @@ -0,0 +1,524 @@ +.. _grpc-dev: + +*************** +Northbound gRPC +*************** + +To enable gRPC support one needs to add `--enable-grpc` when running +`configure`. Additionally, when launching each daemon one needs to request +the gRPC module be loaded and which port to bind to. This can be done by adding +`-M grpc:<port>` to the daemon's CLI arguments. + +Currently there is no gRPC "routing" so you will need to bind your gRPC +`channel` to the particular daemon's gRPC port to interact with that daemon's +gRPC northbound interface. + +The minimum version of gRPC known to work is 1.16.1. + +.. _grpc-languages-bindings: + +Programming Language Bindings +============================= + +The gRPC supported programming language bindings can be found here: +https://grpc.io/docs/languages/ + +After picking a programming language that supports gRPC bindings, the +next step is to generate the FRR northbound bindings. To generate the +northbound bindings you'll need the programming language binding +generator tools and those are language specific. + +C++ Example +----------- + +The next sections will use C++ as an example for accessing FRR +northbound through gRPC. + +.. _grpc-c++-generate: + +Generating C++ FRR Bindings +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Generating FRR northbound bindings for C++ example: + +:: + + # Install gRPC (e.g., on Ubuntu 20.04) + sudo apt-get install libgrpc++-dev libgrpc-dev + + mkdir /tmp/frr-cpp + cd grpc + + protoc --cpp_out=/tmp/frr-cpp \ + --grpc_out=/tmp/frr-cpp \ + -I $(pwd) \ + --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` \ + frr-northbound.proto + + +.. _grpc-c++-if-sample: + +Using C++ To Get Version and Interfaces State +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Below is a sample program to print all interfaces discovered. + +:: + + # test.cpp + #include <string> + #include <sstream> + #include <grpc/grpc.h> + #include <grpcpp/create_channel.h> + #include "frr-northbound.pb.h" + #include "frr-northbound.grpc.pb.h" + + int main() { + frr::GetRequest request; + frr::GetResponse reply; + grpc::ClientContext context; + grpc::Status status; + + auto channel = grpc::CreateChannel("localhost:50051", + grpc::InsecureChannelCredentials()); + auto stub = frr::Northbound::NewStub(channel); + + request.set_type(frr::GetRequest::ALL); + request.set_encoding(frr::JSON); + request.set_with_defaults(true); + request.add_path("/frr-interface:lib"); + auto stream = stub->Get(&context, request); + + std::ostringstream ss; + while (stream->Read(&reply)) + ss << reply.data().data() << std::endl; + + status = stream->Finish(); + assert(status.ok()); + std::cout << "Interface Info:\n" << ss.str() << std::endl; + } + +Below is how to compile and run the program, with the example output: + +:: + + $ g++ -o test test.cpp frr-northbound.grpc.pb.cc frr-northbound.pb.cc -lgrpc++ -lprotobuf + $ ./test + Interface Info: + { + "frr-interface:lib": { + "interface": [ + { + "name": "lo", + "vrf": "default", + "state": { + "if-index": 1, + "mtu": 0, + "mtu6": 65536, + "speed": 0, + "metric": 0, + "phy-address": "00:00:00:00:00:00" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0, + "ptm-status": "disabled" + } + } + }, + { + "name": "r1-eth0", + "vrf": "default", + "state": { + "if-index": 2, + "mtu": 1500, + "mtu6": 1500, + "speed": 10000, + "metric": 0, + "phy-address": "02:37:ac:63:59:b9" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0, + "ptm-status": "disabled" + } + } + } + ] + }, + "frr-zebra:zebra": { + "mcast-rpf-lookup": "mrib-then-urib", + "workqueue-hold-timer": 10, + "zapi-packets": 1000, + "import-kernel-table": { + "distance": 15 + }, + "dplane-queue-limit": 200 + } + } + + + +.. _grpc-python-example: + +Python Example +-------------- + +The next sections will use Python as an example for writing scripts to use +the northbound. + +.. _grpc-python-generate: + +Generating Python FRR Bindings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Generating FRR northbound bindings for Python example: + +:: + + # Install python3 virtual environment capability e.g., + sudo apt-get install python3-venv + + # Create a virtual environment for python grpc and activate + python3 -m venv venv-grpc + source venv-grpc/bin/activate + + # Install grpc requirements + pip install grpcio grpcio-tools + + mkdir /tmp/frr-python + cd grpc + + python3 -m grpc_tools.protoc \ + --python_out=/tmp/frr-python \ + --grpc_python_out=/tmp/frr-python \ + -I $(pwd) \ + frr-northbound.proto + +.. _grpc-python-if-sample: + +Using Python To Get Capabilities and Interfaces State +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Below is a sample script to print capabilities and all interfaces Python +discovered. This demostrates the 2 different RPC results one gets from gRPC, +Unary (`GetCapabilities`) and Streaming (`Get`) for the interface state. + +:: + + import grpc + import frr_northbound_pb2 + import frr_northbound_pb2_grpc + + channel = grpc.insecure_channel('localhost:50051') + stub = frr_northbound_pb2_grpc.NorthboundStub(channel) + + # Print Capabilities + request = frr_northbound_pb2.GetCapabilitiesRequest() + response = stub.GetCapabilities(request) + print(response) + + # Print Interface State and Config + request = frr_northbound_pb2.GetRequest() + request.path.append("/frr-interface:lib") + request.type=frr_northbound_pb2.GetRequest.ALL + request.encoding=frr_northbound_pb2.XML + + for r in stub.Get(request): + print(r.data.data) + +The previous script will output something like: + +:: + + frr_version: "7.7-dev-my-manual-build" + rollback_support: true + supported_modules { + name: "frr-filter" + organization: "FRRouting" + revision: "2019-07-04" + } + supported_modules { + name: "frr-interface" + organization: "FRRouting" + revision: "2020-02-05" + } + [...] + supported_encodings: JSON + supported_encodings: XML + + <lib xmlns="http://frrouting.org/yang/interface"> + <interface> + <name>lo</name> + <vrf>default</vrf> + <state> + <if-index>1</if-index> + <mtu>0</mtu> + <mtu6>65536</mtu6> + <speed>0</speed> + <metric>0</metric> + <phy-address>00:00:00:00:00:00</phy-address> + </state> + <zebra xmlns="http://frrouting.org/yang/zebra"> + <state> + <up-count>0</up-count> + <down-count>0</down-count> + </state> + </zebra> + </interface> + <interface> + <name>r1-eth0</name> + <vrf>default</vrf> + <state> + <if-index>2</if-index> + <mtu>1500</mtu> + <mtu6>1500</mtu6> + <speed>10000</speed> + <metric>0</metric> + <phy-address>f2:62:2e:f3:4c:e4</phy-address> + </state> + <zebra xmlns="http://frrouting.org/yang/zebra"> + <state> + <up-count>0</up-count> + <down-count>0</down-count> + </state> + </zebra> + </interface> + </lib> + +.. _grpc-ruby-example: + +Ruby Example +------------ + +Next sections will use Ruby as an example for writing scripts to use +the northbound. + +.. _grpc-ruby-generate: + +Generating Ruby FRR Bindings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Generating FRR northbound bindings for Ruby example: + +:: + + # Install the required gems: + # - grpc: the gem that will talk with FRR's gRPC plugin. + # - grpc-tools: the gem that provides the code generator. + gem install grpc + gem install grpc-tools + + # Create your project/scripts directory: + mkdir /tmp/frr-ruby + + # Go to FRR's grpc directory: + cd grpc + + # Generate the ruby bindings: + grpc_tools_ruby_protoc \ + --ruby_out=/tmp/frr-ruby \ + --grpc_out=/tmp/frr-ruby \ + frr-northbound.proto + + +.. _grpc-ruby-if-sample: + +Using Ruby To Get Interfaces State +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Here is a sample script to print all interfaces FRR discovered: + +:: + + require 'frr-northbound_services_pb' + + # Create the connection with FRR's gRPC: + stub = Frr::Northbound::Stub.new('localhost:50051', :this_channel_is_insecure) + + # Create a new state request to get interface state: + request = Frr::GetRequest.new + request.type = :STATE + request.path.push('/frr-interface:lib') + + # Ask FRR. + response = stub.get(request) + + # Print the response. + response.each do |result| + result.data.data.each_line do |line| + puts line + end + end + + +.. note:: + + The generated files will assume that they are in the search path (e.g. + inside gem) so you'll need to either edit it to use ``require_relative`` or + tell Ruby where to look for them. For simplicity we'll use ``-I .`` to tell + it is in the current directory. + + +The previous script will output something like this: + +:: + + $ cd /tmp/frr-ruby + # Add `-I.` so ruby finds the FRR generated file locally. + $ ruby -I. interface.rb + { + "frr-interface:lib": { + "interface": [ + { + "name": "eth0", + "vrf": "default", + "state": { + "if-index": 2, + "mtu": 1500, + "mtu6": 1500, + "speed": 1000, + "metric": 0, + "phy-address": "11:22:33:44:55:66" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0 + } + } + }, + { + "name": "lo", + "vrf": "default", + "state": { + "if-index": 1, + "mtu": 0, + "mtu6": 65536, + "speed": 0, + "metric": 0, + "phy-address": "00:00:00:00:00:00" + }, + "frr-zebra:zebra": { + "state": { + "up-count": 0, + "down-count": 0 + } + } + } + ] + } + } + + +.. _grpc-ruby-bfd-profile-sample: + +Using Ruby To Create BFD Profiles +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In this example you'll learn how to edit configuration using JSON +and programmatic (XPath) format. + +:: + + require 'frr-northbound_services_pb' + + # Create the connection with FRR's gRPC: + stub = Frr::Northbound::Stub.new('localhost:50051', :this_channel_is_insecure) + + # Create a new candidate configuration change. + new_candidate = stub.create_candidate(Frr::CreateCandidateRequest.new) + + # Use JSON to configure. + request = Frr::LoadToCandidateRequest.new + request.candidate_id = new_candidate.candidate_id + request.type = :MERGE + request.config = Frr::DataTree.new + request.config.encoding = :JSON + request.config.data = <<-EOJ + { + "frr-bfdd:bfdd": { + "bfd": { + "profile": [ + { + "name": "test-prof", + "detection-multiplier": 4, + "required-receive-interval": 800000 + } + ] + } + } + } + EOJ + + # Load configuration to candidate. + stub.load_to_candidate(request) + + # Commit candidate. + stub.commit( + Frr::CommitRequest.new( + candidate_id: new_candidate.candidate_id, + phase: :ALL, + comment: 'create test-prof' + ) + ) + + # + # Now lets delete the previous profile and create a new one. + # + + # Create a new candidate configuration change. + new_candidate = stub.create_candidate(Frr::CreateCandidateRequest.new) + + # Edit the configuration candidate. + request = Frr::EditCandidateRequest.new + request.candidate_id = new_candidate.candidate_id + + # Delete previously created profile. + request.delete.push( + Frr::PathValue.new( + path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof']", + ) + ) + + # Add new profile with two configurations. + request.update.push( + Frr::PathValue.new( + path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof-2']/detection-multiplier", + value: 5.to_s + ) + ) + request.update.push( + Frr::PathValue.new( + path: "/frr-bfdd:bfdd/bfd/profile[name='test-prof-2']/desired-transmission-interval", + value: 900_000.to_s + ) + ) + + # Modify the candidate. + stub.edit_candidate(request) + + # Commit the candidate configuration. + stub.commit( + Frr::CommitRequest.new( + candidate_id: new_candidate.candidate_id, + phase: :ALL, + comment: 'replace test-prof with test-prof-2' + ) + ) + + +And here is the new FRR configuration: + +:: + + $ sudo vtysh -c 'show running-config' + ... + bfd + profile test-prof-2 + detect-multiplier 5 + transmit-interval 900 + ! + ! diff --git a/doc/developer/hooks.rst b/doc/developer/hooks.rst new file mode 100644 index 0000000..b37a4ae --- /dev/null +++ b/doc/developer/hooks.rst @@ -0,0 +1,171 @@ +.. highlight:: c + +Hooks +===== + +Libfrr provides type-safe subscribable hook points where other pieces of +code can add one or more callback functions. "type-safe" in this case +applies to the function pointers used for subscriptions. The +implementations checks (at compile-time) whether a callback to be added has +the appropriate function signature (parameters) for the hook. + +Example: + +.. code-block:: c + :caption: mydaemon.h + + #include "hook.h" + DECLARE_HOOK(some_update_event, (struct eventinfo *info), (info)); + +.. code-block:: c + :caption: mydaemon.c + + #include "mydaemon.h" + DEFINE_HOOK(some_update_event, (struct eventinfo *info), (info)); + ... + hook_call(some_update_event, info); + +.. code-block:: c + :caption: mymodule.c + + #include "mydaemon.h" + static int event_handler(struct eventinfo *info); + ... + hook_register(some_update_event, event_handler); + +Do not use parameter names starting with "hook", these can collide with +names used by the hook code itself. + + +Return values +------------- + +Callbacks to be placed on hooks always return "int" for now; hook_call will +sum up the return values from each called function. (The default is 0 if no +callbacks are registered.) + +There are no pre-defined semantics for the value, in most cases it is +ignored. For success/failure indication, 0 should be success, and +handlers should make sure to only return 0 or 1 (not -1 or other values). + +There is no built-in way to abort executing a chain after a failure of one +of the callbacks. If this is needed, the hook can use an extra +``bool *aborted`` argument. + + +Priorities +---------- + +Hooks support a "priority" value for ordering registered calls +relative to each other. The priority is a signed integer where lower +values are called earlier. There are also "Koohs", which is hooks with +reverse priority ordering (for cleanup/deinit hooks, so you can use the +same priority value). + +Recommended priority value ranges are: + +======================== =================================================== +Range Usage +------------------------ --------------------------------------------------- + -999 ... 0 ... 999 main executable / daemon, or library + +-1999 ... -1000 modules registering calls that should run before + the daemon's bits + +1000 ... 1999 modules' calls that should run after daemon's + (includes default value: 1000) +======================== =================================================== + +Note: the default value is 1000, based on the following 2 expectations: + +- most hook_register() usage will be in loadable modules +- usage of hook_register() in the daemon itself may need relative ordering + to itself, making an explicit value the expected case + +The priority value is passed as extra argument on hook_register_prio() / +hook_register_arg_prio(). Whether a hook runs in reverse is determined +solely by the code defining / calling the hook. (DECLARE_KOOH is actually +the same thing as DECLARE_HOOK, it's just there to make it obvious.) + + +Definition +---------- + +.. c:macro:: DECLARE_HOOK(name, arglist, passlist) +.. c:macro:: DECLARE_KOOH(name, arglist, passlist) + + :param name: Name of the hook to be defined + :param arglist: Function definition style parameter list in braces. + :param passlist: List of the same parameters without their types. + + Note: the second and third macro args must be the hook function's + parameter list, with the same names for each parameter. The second + macro arg is with types (used for defining things), the third arg is + just the names (used for passing along parameters). + + This macro must be placed in a header file; this header file must be + included to register a callback on the hook. + + Examples: + + .. code-block:: c + + DECLARE_HOOK(foo, (), ()); + DECLARE_HOOK(bar, (int arg), (arg)); + DECLARE_HOOK(baz, (const void *x, in_addr_t y), (x, y)); + +.. c:macro:: DEFINE_HOOK(name, arglist, passlist) + + Implements an hook. Each ``DECLARE_HOOK`` must have be accompanied by + exactly one ``DEFINE_HOOK``, which needs to be placed in a source file. + **The hook can only be called from this source file.** This is intentional + to avoid overloading and/or misusing hooks for distinct purposes. + + The compiled source file will include a global symbol with the name of the + hook prefixed by `_hook_`. Trying to register a callback for a hook that + doesn't exist will therefore result in a linker error, or a module + load-time error for dynamic modules. + +.. c:macro:: DEFINE_KOOH(name, arglist, passlist) + + Same as ``DEFINE_HOOK``, but the sense of priorities / order of callbacks + is reversed. This should be used for cleanup hooks. + +.. c:function:: int hook_call(name, ...) + + Calls the specified named hook. Parameters to the hook are passed right + after the hook name, e.g.: + + .. code-block:: c + + hook_call(foo); + hook_call(bar, 0); + hook_call(baz, NULL, INADDR_ANY); + + Returns the sum of return values from all callbacks. The ``DEFINE_HOOK`` + statement for the hook must be placed in the file before any ``hook_call`` + use of the hook. + + +Callback registration +--------------------- + +.. c:function:: void hook_register(name, int (*callback)(...)) +.. c:function:: void hook_register_prio(name, int priority, int (*callback)(...)) +.. c:function:: void hook_register_arg(name, int (*callback)(void *arg, ...), void *arg) +.. c:function:: void hook_register_arg_prio(name, int priority, int (*callback)(void *arg, ...), void *arg) + + Register a callback with an hook. If the caller needs to pass an extra + argument to the callback, the _arg variant can be used and the extra + parameter will be passed as first argument to the callback. There is no + typechecking for this argument. + + The priority value is used as described above. The variants without a + priority parameter use 1000 as priority value. + +.. c:function:: void hook_unregister(name, int (*callback)(...)) +.. c:function:: void hook_unregister_arg(name, int (*callback)(void *arg, ...), void *arg) + + Removes a previously registered callback from a hook. Note that there + is no _prio variant of these calls. The priority value is only used during + registration. diff --git a/doc/developer/images/PCEPlib_design.jpg b/doc/developer/images/PCEPlib_design.jpg new file mode 100644 index 0000000..41aada3 Binary files /dev/null and b/doc/developer/images/PCEPlib_design.jpg differ diff --git a/doc/developer/images/PCEPlib_internal_deps.jpg b/doc/developer/images/PCEPlib_internal_deps.jpg new file mode 100644 index 0000000..8380021 Binary files /dev/null and b/doc/developer/images/PCEPlib_internal_deps.jpg differ diff --git a/doc/developer/images/PCEPlib_socket_comm.jpg b/doc/developer/images/PCEPlib_socket_comm.jpg new file mode 100644 index 0000000..3d62a46 Binary files /dev/null and b/doc/developer/images/PCEPlib_socket_comm.jpg differ diff --git a/doc/developer/images/PCEPlib_threading_model.jpg b/doc/developer/images/PCEPlib_threading_model.jpg new file mode 100644 index 0000000..afe91c2 Binary files /dev/null and b/doc/developer/images/PCEPlib_threading_model.jpg differ diff --git a/doc/developer/images/PCEPlib_threading_model_frr_infra.jpg b/doc/developer/images/PCEPlib_threading_model_frr_infra.jpg new file mode 100644 index 0000000..5648a9d Binary files /dev/null and b/doc/developer/images/PCEPlib_threading_model_frr_infra.jpg differ diff --git a/doc/developer/images/PCEPlib_timers.jpg b/doc/developer/images/PCEPlib_timers.jpg new file mode 100644 index 0000000..a178ee9 Binary files /dev/null and b/doc/developer/images/PCEPlib_timers.jpg differ diff --git a/doc/developer/include-compile.rst b/doc/developer/include-compile.rst new file mode 100644 index 0000000..49fd6c8 --- /dev/null +++ b/doc/developer/include-compile.rst @@ -0,0 +1,30 @@ +Clone the FRR git repo and use the included ``configure`` script to configure +FRR's build time options to your liking. The full option listing can be +obtained by running ``./configure -h``. The options shown below are examples. + +.. code-block:: console + + git clone https://github.com/frrouting/frr.git frr + cd frr + ./bootstrap.sh + ./configure \ + --prefix=/usr \ + --includedir=\${prefix}/include \ + --bindir=\${prefix}/bin \ + --sbindir=\${prefix}/lib/frr \ + --libdir=\${prefix}/lib/frr \ + --libexecdir=\${prefix}/lib/frr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --with-moduledir=\${prefix}/lib/frr/modules \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-snmp=agentx \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + sudo make install diff --git a/doc/developer/index.rst b/doc/developer/index.rst new file mode 100644 index 0000000..bd794b1 --- /dev/null +++ b/doc/developer/index.rst @@ -0,0 +1,26 @@ +FRRouting Developer's Guide +=========================== + +.. toctree:: + :maxdepth: 2 + + workflow + checkpatch + building + packaging + process-architecture + library + fuzzing + tracing + testing + mgmtd-dev + bgpd + fpm + grpc + ospf + zebra + vtysh + path + pceplib + link-state + northbound/northbound diff --git a/doc/developer/ldpd-basic-test-setup.md b/doc/developer/ldpd-basic-test-setup.md new file mode 100644 index 0000000..b25a2b6 --- /dev/null +++ b/doc/developer/ldpd-basic-test-setup.md @@ -0,0 +1,681 @@ +## Topology + +The goal of this test is to verify that the all the basic functionality +of ldpd is working as expected, be it running on Linux or OpenBSD. In +addition to that, more advanced features are also tested, like LDP +sessions over IPv6, MD5 authentication and pseudowire signaling. + +In the topology below there are 3 PE routers, 3 CE routers and one P +router (not attached to any consumer site). + +All routers have IPv4 addresses and OSPF is used as the IGP. The +three routers from the bottom of the picture, P, PE2 and PE3, are also +configured for IPv6 (dual-stack) and static IPv6 routes are used to +provide connectivity among them. + +The three CEs share the same VPLS membership. LDP is used to set up the +LSPs among the PEs and to signal the pseudowires. MD5 authentication is +used to protect all LDP sessions. + +``` + CE1 172.16.1.1/24 + + + | + +---+---+ + | PE1 | + | IOS XE| + | | + +---+---+ + | + | 10.0.1.0/24 + | + +---+---+ + | P | + +------+ IOS XR+------+ + | | | | + | +-------+ | + 10.0.2.0/24 | | 10.0.3.0/24 +2001:db8:2::/64 | | 2001:db8:3::/64 + | | + +---+---+ +---+---+ + | PE2 | | PE3 | + |OpenBSD+-------------+ Linux | + | | | | + +---+---+ 10.0.4.0/24 +---+---+ + | 2001:db8:4::/64 | + + + + 172.16.1.2/24 CE2 CE3 172.16.1.3/24 +``` + +## Configuration + +#### Linux +1 - Enable IPv4/v6 forwarding: +``` +# sysctl -w net.ipv4.ip_forward=1 +# sysctl -w net.ipv6.conf.all.forwarding=1 +``` + +2 - Enable MPLS forwarding: +``` +# modprobe mpls-router +# modprobe mpls-iptunnel +# echo 100000 > /proc/sys/net/mpls/platform_labels +# echo 1 > /proc/sys/net/mpls/conf/eth1/input +# echo 1 > /proc/sys/net/mpls/conf/eth2/input +``` + +3 - Set up the interfaces: +``` +# ip link add name lo1 type dummy +# ip link set dev lo1 up +# ip addr add 4.4.4.4/32 dev lo1 +# ip -6 addr add 4:4:4::4/128 dev lo1 +# ip link set dev eth1 up +# ip addr add 10.0.4.4/24 dev eth1 +# ip -6 addr add 2001:db8:4::4/64 dev eth1 +# ip link set dev eth2 up +# ip addr add 10.0.3.4/24 dev eth2 +# ip -6 addr add 2001:db8:3::4/64 dev eth2 +``` + +4 - Set up the bridge and pseudowire interfaces: +``` +# ip link add type bridge +# ip link set dev bridge0 up +# ip link set dev eth0 up +# ip link set dev eth0 master bridge0 +# ip link add name mpw0 type dummy +# ip link set dev mpw0 up +# ip link set dev mpw0 master bridge0 +# ip link add name mpw1 type dummy +# ip link set dev mpw1 up +# ip link set dev mpw1 master bridge0 +``` + +> NOTE: MPLS support in the Linux kernel is very recent and it still +doesn't support pseudowire interfaces. We are using here dummy interfaces +just to show how the VPLS configuration should look like in the future. + +5 - Add static IPv6 routes for the remote loopbacks: +``` +# ip -6 route add 2:2:2::2/128 via 2001:db8:3::2 +# ip -6 route add 3:3:3::3/128 via 2001:db8:4::3 +``` + +6 - Edit /etc/frr/ospfd.conf: +``` +router ospf + network 4.4.4.4/32 area 0.0.0.0 + network 10.0.3.4/24 area 0.0.0.0 + network 10.0.4.4/24 area 0.0.0.0 +! +``` + +7 - Edit /etc/frr/ldpd.conf: +``` +debug mpls ldp messages recv +debug mpls ldp messages sent +debug mpls ldp zebra +! +mpls ldp + router-id 4.4.4.4 + dual-stack cisco-interop + neighbor 1.1.1.1 password opensourcerouting + neighbor 2.2.2.2 password opensourcerouting + neighbor 3.3.3.3 password opensourcerouting + ! + address-family ipv4 + discovery transport-address 4.4.4.4 + label local advertise explicit-null + ! + interface eth2 + ! + interface eth1 + ! + ! + address-family ipv6 + discovery transport-address 4:4:4::4 + ttl-security disable + ! + interface eth2 + ! + interface eth1 + ! + ! +! +l2vpn ENG type vpls + bridge br0 + member interface eth0 + ! + member pseudowire mpw0 + neighbor lsr-id 1.1.1.1 + pw-id 100 + ! + member pseudowire mpw1 + neighbor lsr-id 3.3.3.3 + neighbor address 3:3:3::3 + pw-id 100 + ! +! +``` + +> NOTE: We have to disable ttl-security under the ipv6 address-family +in order to interoperate with the IOS-XR router. GTSM is mandatory for +LDPv6 but the IOS-XR implementation is not RFC compliant in this regard. + +8 - Run zebra, ospfd and ldpd. + +#### OpenBSD +1 - Enable IPv4/v6 forwarding: +``` +# sysctl net.inet.ip.forwarding=1 +# sysctl net.inet6.ip6.forwarding=1 +``` + +2 - Enable MPLS forwarding: +``` +# ifconfig em2 10.0.2.3/24 mpls +# ifconfig em3 10.0.4.3/24 mpls +``` + +3 - Set up the interfaces: +``` +# ifconfig lo1 alias 3.3.3.3 netmask 255.255.255.255 +# ifconfig lo1 inet6 3:3:3::3/128 +# ifconfig em2 inet6 2001:db8:2::3/64 +# ifconfig em3 inet6 2001:db8:4::3/64 +``` + +4 - Set up the bridge and pseudowire interfaces: +``` +# ifconfig bridge0 create +# ifconfig bridge0 up +# ifconfig em1 up +# ifconfig bridge0 add em1 +# ifconfig mpw0 create +# ifconfig mpw0 up +# ifconfig bridge0 add mpw0 +# ifconfig mpw1 create +# ifconfig mpw1 up +# ifconfig bridge0 add mpw1 +``` + +5 - Add static IPv6 routes for the remote loopbacks: +``` +# route -n add 4:4:4::4/128 2001:db8:4::4 +# route -n add 2:2:2::2/128 2001:db8:2::2 +``` + +6 - Edit /etc/frr/ospfd.conf: +``` +router ospf + network 10.0.2.3/24 area 0 + network 10.0.4.3/24 area 0 + network 3.3.3.3/32 area 0 +! +``` + +7 - Edit /etc/frr/ldpd.conf: +``` +debug mpls ldp messages recv +debug mpls ldp messages sent +debug mpls ldp zebra +! +mpls ldp + router-id 3.3.3.3 + dual-stack cisco-interop + neighbor 1.1.1.1 password opensourcerouting + neighbor 2.2.2.2 password opensourcerouting + neighbor 4.4.4.4 password opensourcerouting + ! + address-family ipv4 + discovery transport-address 3.3.3.3 + label local advertise explicit-null + ! + interface em3 + ! + interface em2 + ! + ! + address-family ipv6 + discovery transport-address 3:3:3::3 + ttl-security disable + ! + interface em3 + ! + interface em2 + ! + ! +! +l2vpn ENG type vpls + bridge br0 + member interface em1 + ! + member pseudowire mpw0 + neighbor lsr-id 1.1.1.1 + pw-id 100 + ! + member pseudowire mpw1 + neighbor lsr-id 4.4.4.4 + neighbor address 4:4:4::4 + pw-id 100 + ! +! +``` + +8 - Run zebra, ospfd and ldpd. + +#### Cisco routers +CE1 (IOS): +``` +interface FastEthernet0/0 + ip address 172.16.1.1 255.255.255.0 + ! +! +``` + +CE2 (IOS): +``` +interface FastEthernet0/0 + ip address 172.16.1.2 255.255.255.0 + ! +! +``` + +CE3 (IOS): +``` +interface FastEthernet0/0 + ip address 172.16.1.3 255.255.255.0 + ! +! +``` + +PE1 - IOS-XE (1): +``` +mpls ldp neighbor 2.2.2.2 password opensourcerouting +mpls ldp neighbor 3.3.3.3 password opensourcerouting +mpls ldp neighbor 4.4.4.4 password opensourcerouting +! +l2vpn vfi context VFI + vpn id 1 + member pseudowire2 + member pseudowire1 +! +bridge-domain 1 + member GigabitEthernet1 service-instance 1 + member vfi VFI +! +interface Loopback1 + ip address 1.1.1.1 255.255.255.255 +! +interface pseudowire1 + encapsulation mpls + neighbor 3.3.3.3 100 +! +interface pseudowire2 + encapsulation mpls + neighbor 4.4.4.4 100 +! +interface GigabitEthernet3 + ip address 10.0.1.1 255.255.255.0 + mpls ip +! +router ospf 1 + network 0.0.0.0 255.255.255.255 area 0 +! +``` + +P - IOS-XR (2): +``` +interface Loopback1 + ipv4 address 2.2.2.2 255.255.255.255 + ipv6 address 2:2:2::2/128 +! +interface GigabitEthernet0/0/0/0 + ipv4 address 10.0.1.2 255.255.255.0 +! +interface GigabitEthernet0/0/0/1 + ipv4 address 10.0.2.2 255.255.255.0 + ipv6 address 2001:db8:2::2/64 + ipv6 enable +! +interface GigabitEthernet0/0/0/2 + ipv4 address 10.0.3.2 255.255.255.0 + ipv6 address 2001:db8:3::2/64 + ipv6 enable +! +router static + address-family ipv6 unicast + 3:3:3::3/128 2001:db8:2::3 + 4:4:4::4/128 2001:db8:3::4 + ! +! +router ospf 1 + router-id 2.2.2.2 + address-family ipv4 unicast + area 0 + interface Loopback1 + ! + interface GigabitEthernet0/0/0/0 + ! + interface GigabitEthernet0/0/0/1 + ! + interface GigabitEthernet0/0/0/2 + ! + ! +! +mpls ldp + router-id 2.2.2.2 + neighbor + 1.1.1.1:0 password clear opensourcerouting + 3.3.3.3:0 password clear opensourcerouting + 4.4.4.4:0 password clear opensourcerouting + ! + address-family ipv4 + ! + address-family ipv6 + discovery transport-address 2:2:2::2 + ! + interface GigabitEthernet0/0/0/0 + address-family ipv4 + ! + ! + interface GigabitEthernet0/0/0/1 + address-family ipv4 + ! + address-family ipv6 + ! + ! + interface GigabitEthernet0/0/0/2 + address-family ipv4 + ! + address-family ipv6 + ! + ! +! +``` + +## Verification - Control Plane + +Using the CLI on the Linux box, the goal is to ensure that everything +is working as expected. + +First, verify that all the required adjacencies and neighborships sessions +were established: + +``` +linux# show mpls ldp discovery +Local LDP Identifier: 4.4.4.4:0 +Discovery Sources: + Interfaces: + eth1: xmit/recv + LDP Id: 3.3.3.3:0, Transport address: 3.3.3.3 + Hold time: 15 sec + LDP Id: 3.3.3.3:0, Transport address: 3:3:3::3 + Hold time: 15 sec + eth2: xmit/recv + LDP Id: 2.2.2.2:0, Transport address: 2.2.2.2 + Hold time: 15 sec + LDP Id: 2.2.2.2:0, Transport address: 2:2:2::2 + Hold time: 15 sec + Targeted Hellos: + 4.4.4.4 -> 1.1.1.1: xmit/recv + LDP Id: 1.1.1.1:0, Transport address: 1.1.1.1 + Hold time: 45 sec + 4:4:4::4 -> 3:3:3::3: xmit/recv + LDP Id: 3.3.3.3:0, Transport address: 3:3:3::3 + Hold time: 45 sec + +linux# show mpls ldp neighbor +Peer LDP Identifier: 1.1.1.1:0 + TCP connection: 4.4.4.4:40921 - 1.1.1.1:646 + Session Holdtime: 180 sec + State: OPERATIONAL; Downstream-Unsolicited + Up time: 00:06:02 + LDP Discovery Sources: + IPv4: + Targeted Hello: 1.1.1.1 + +Peer LDP Identifier: 2.2.2.2:0 + TCP connection: 4:4:4::4:52286 - 2:2:2::2:646 + Session Holdtime: 180 sec + State: OPERATIONAL; Downstream-Unsolicited + Up time: 00:06:02 + LDP Discovery Sources: + IPv4: + Interface: eth2 + IPv6: + Interface: eth2 + +Peer LDP Identifier: 3.3.3.3:0 + TCP connection: 4:4:4::4:60575 - 3:3:3::3:646 + Session Holdtime: 180 sec + State: OPERATIONAL; Downstream-Unsolicited + Up time: 00:05:57 + LDP Discovery Sources: + IPv4: + Interface: eth1 + IPv6: + Targeted Hello: 3:3:3::3 + Interface: eth1 +``` + +Note that the neighborships with the P and PE2 routers were established +over IPv6, since this is the default behavior for dual-stack LSRs, as +specified in RFC 7552. If desired, the **dual-stack transport-connection +prefer ipv4** command can be used to establish these sessions over IPv4 +(the command should be applied an all routers). + +Now, verify that there's a remote label for each PE address: +``` +linux# show mpls ldp binding +1.1.1.1/32 + Local binding: label: 20 + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 imp-null + 2.2.2.2 24000 + 3.3.3.3 20 +2.2.2.2/32 + Local binding: label: 21 + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 18 + 2.2.2.2 imp-null + 3.3.3.3 21 +3.3.3.3/32 + Local binding: label: 22 + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 21 + 2.2.2.2 24003 + 3.3.3.3 imp-null +4.4.4.4/32 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 22 + 2.2.2.2 24001 + 3.3.3.3 22 +10.0.1.0/24 + Local binding: label: 23 + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 imp-null + 2.2.2.2 imp-null + 3.3.3.3 23 +10.0.2.0/24 + Local binding: label: 24 + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 20 + 2.2.2.2 imp-null + 3.3.3.3 imp-null +10.0.3.0/24 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 19 + 2.2.2.2 imp-null + 3.3.3.3 24 +10.0.4.0/24 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 1.1.1.1 23 + 2.2.2.2 24002 + 3.3.3.3 imp-null +2:2:2::2/128 + Local binding: label: 18 + Remote bindings: + Peer Label + ----------------- --------- + 2.2.2.2 imp-null + 3.3.3.3 18 +3:3:3::3/128 + Local binding: label: 19 + Remote bindings: + Peer Label + ----------------- --------- + 2.2.2.2 24007 +4:4:4::4/128 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 2.2.2.2 24006 + 3.3.3.3 19 +2001:db8:2::/64 + Local binding: label: - + Remote bindings: + Peer Label + ----------------- --------- + 2.2.2.2 imp-null + 3.3.3.3 imp-null +2001:db8:3::/64 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 2.2.2.2 imp-null +2001:db8:4::/64 + Local binding: label: imp-null + Remote bindings: + Peer Label + ----------------- --------- + 3.3.3.3 imp-null +``` + +Check if the pseudowires are up: +``` +linux# show l2vpn atom vc +Interface Peer ID VC ID Name Status +--------- --------------- ---------- ---------------- ---------- +mpw1 3.3.3.3 100 ENG UP +mpw0 1.1.1.1 100 ENG UP +``` + +Check the label bindings of the pseudowires: +``` +linux# show l2vpn atom binding + Destination Address: 1.1.1.1, VC ID: 100 + Local Label: 25 + Cbit: 1, VC Type: Ethernet, GroupID: 0 + MTU: 1500 + Remote Label: 16 + Cbit: 1, VC Type: Ethernet, GroupID: 0 + MTU: 1500 + Destination Address: 3.3.3.3, VC ID: 100 + Local Label: 26 + Cbit: 1, VC Type: Ethernet, GroupID: 0 + MTU: 1500 + Remote Label: 26 + Cbit: 1, VC Type: Ethernet, GroupID: 0 + MTU: 1500 +``` + +## Verification - Data Plane + +Verify that all the exchanged label mappings were installed in zebra: +``` +linux# show mpls table + Inbound Outbound + Label Type Nexthop Label +-------- ------- --------------- -------- + 17 LDP 2001:db8:3::2 3 + 19 LDP 2001:db8:3::2 24005 + 20 LDP 10.0.3.2 24000 + 21 LDP 10.0.3.2 3 + 22 LDP 10.0.3.2 24001 + 23 LDP 10.0.3.2 3 + 24 LDP 10.0.3.2 3 + 25 LDP 10.0.3.2 3 + +linux# show ip route ldp +Codes: K - kernel route, C - connected, S - static, R - RIP, + O - OSPF, I - IS-IS, B - BGP, P - PIM, A - Babel, L - LDP, + > - selected route, * - FIB route + +L>* 1.1.1.1/32 [0/0] via 10.0.3.2, eth2 label 24000 +L>* 3.3.3.3/32 [0/0] via 10.0.3.2, eth2 label 24001 +``` + +Verify that all the exchanged label mappings were installed in the kernel: +``` +$ ip -M ro +17 via inet6 2001:db8:3::2 dev eth2 proto zebra +19 as to 24005 via inet6 2001:db8:3::2 dev eth2 proto zebra +20 as to 24000 via inet 10.0.3.2 dev eth2 proto zebra +21 via inet 10.0.3.2 dev eth2 proto zebra +22 as to 24001 via inet 10.0.3.2 dev eth2 proto zebra +23 via inet 10.0.3.2 dev eth2 proto zebra +24 via inet 10.0.3.2 dev eth2 proto zebra +25 via inet 10.0.3.2 dev eth2 proto zebra +$ +$ ip route | grep mpls +1.1.1.1 encap mpls 24000 via 10.0.3.2 dev eth2 proto zebra metric 20 +3.3.3.3 encap mpls 24001 via 10.0.3.2 dev eth2 proto zebra metric 20 +``` + +Now ping PE1's loopback using lo1's address as a source address: +``` +$ ping -c 5 -I 4.4.4.4 1.1.1.1 +PING 1.1.1.1 (1.1.1.1) from 4.4.4.4 : 56(84) bytes of data. +64 bytes from 1.1.1.1: icmp_seq=1 ttl=253 time=3.02 ms +64 bytes from 1.1.1.1: icmp_seq=2 ttl=253 time=3.13 ms +64 bytes from 1.1.1.1: icmp_seq=3 ttl=253 time=3.19 ms +64 bytes from 1.1.1.1: icmp_seq=4 ttl=253 time=3.07 ms +64 bytes from 1.1.1.1: icmp_seq=5 ttl=253 time=3.27 ms + +--- 1.1.1.1 ping statistics --- +5 packets transmitted, 5 received, 0% packet loss, time 4005ms +rtt min/avg/max/mdev = 3.022/3.140/3.278/0.096 ms +``` + +Verify that the ICMP echo request packets are leaving with the MPLS +label advertised by the P router. Also, verify that the ICMP echo reply +packets are arriving with an explicit-null MPLS label: +``` +# tcpdump -n -i eth2 mpls and icmp +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on eth2, link-type EN10MB (Ethernet), capture size 262144 bytes +10:01:40.758771 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 1, length 64 +10:01:40.761777 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 1, length 64 +10:01:41.760343 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 2, length 64 +10:01:41.763448 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 2, length 64 +10:01:42.761758 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 3, length 64 +10:01:42.764924 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 3, length 64 +10:01:43.763193 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 4, length 64 +10:01:43.766237 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 4, length 64 +10:01:44.764552 MPLS (label 24000, exp 0, [S], ttl 64) IP 4.4.4.4 > 1.1.1.1: ICMP echo request, id 13370, seq 5, length 64 +10:01:44.767803 MPLS (label 0, exp 0, [S], ttl 254) IP 1.1.1.1 > 4.4.4.4: ICMP echo reply, id 13370, seq 5, length 64 +``` diff --git a/doc/developer/library.rst b/doc/developer/library.rst new file mode 100644 index 0000000..2e36c25 --- /dev/null +++ b/doc/developer/library.rst @@ -0,0 +1,21 @@ +.. _libfrr: + +*************************** +Library Facilities (libfrr) +*************************** + +.. toctree:: + :maxdepth: 2 + + memtypes + rcu + lists + logging + xrefs + locking + hooks + cli + modules + scripting + + diff --git a/doc/developer/link-state.rst b/doc/developer/link-state.rst new file mode 100644 index 0000000..aaa253d --- /dev/null +++ b/doc/developer/link-state.rst @@ -0,0 +1,499 @@ +Link State API Documentation +============================ + +Introduction +------------ + +The Link State (LS) API aims to provide a set of structures and functions to +build and manage a Traffic Engineering Database for the various FRR daemons. +This API has been designed for several use cases: + +- BGP Link State (BGP-LS): where BGP protocol need to collect the link state + information from the routing daemons (IS-IS and/or OSPF) to implement RFC7752 +- Path Computation Element (PCE): where path computation algorithms are based + on Traffic Engineering Database +- ReSerVation Protocol (RSVP): where signaling need to know the Traffic + Engineering topology of the network in order to determine the path of + RSVP tunnels + +Architecture +------------ + +The main requirements from the various uses cases are as follow: + +- Provides a set of data model and function to ease Link State information + manipulation (storage, serialize, parse ...) +- Ease and normalize Link State information exchange between FRR daemons +- Provides database structure for Traffic Engineering Database (TED) + +To ease Link State understanding, FRR daemons have been classified into two +categories: + +- **Consumer**: Daemons that consume Link State information e.g. BGPd +- **Producer**: Daemons that are able to collect Link State information and + send them to consumer daemons e.g. OSPFd IS-ISd + +Zebra daemon, and more precisely, the ZAPI message is used to convey the Link +State information between *producer* and *consumer*, but, Zebra acts as a +simple pass through and does not store any Link State information. A new ZAPI +**Opaque** message has been design for that purpose. + +Each consumer and producer daemons are free to store or not Link State data and +organise the information following the Traffic Engineering Database model +provided by the API or any other data structure e.g. Hash, RB-tree ... + +Link State API +-------------- + +This is the low level API that allows any daemons manipulate the Link State +elements that are stored in the Link State Database. + +Data structures +^^^^^^^^^^^^^^^ + +3 types of Link State structure have been defined: + +.. c:struct:: ls_node + + that groups all information related to a node + +.. c:struct:: ls_attributes + + that groups all information related to a link + +.. c:struct:: ls_prefix + + that groups all information related to a prefix + +These 3 types of structures are those handled by BGP-LS (see RFC7752) and +suitable to describe a Traffic Engineering topology. + +Each structure, in addition to the specific parameters, embed the node +identifier which advertises the Link State and a bit mask as flags to +indicates which parameters are valid i.e. for which the value is valid and +corresponds to a Link State information conveyed by the routing protocol. + +.. c:struct:: ls_node_id + + defines the Node identifier as router ID IPv4 address plus the area ID for + OSPF or the ISO System ID plus the IS-IS level for IS-IS. + +Functions +^^^^^^^^^ + +A set of functions is provided to create, delete and compare Link State +Node, Atribute and Prefix: + +.. c:function:: struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr router_id, struct in6_addr router6_id) +.. c:function:: struct ls_attributes *ls_attributes_new(struct ls_node_id adv, struct in_addr local, struct in6_addr local6, uint32_t local_id) +.. c:function:: struct ls_prefix *ls_prefix_new(struct ls_node_id adv, struct prefix p) + + Create respectively a new Link State Node, Attribute or Prefix. + Structure is dynamically allocated. Link State Node ID (adv) is mandatory + and: + + - at least one of IPv4 or IPv6 must be provided for the router ID + (router_id or router6_id) for Node + - at least one of local, local6 or local_id must be provided for Attribute + - prefix is mandatory for Link State Prefix. + +.. c:function:: void ls_node_del(struct ls_node *node) +.. c:function:: void ls_attributes_del(struct ls_attributes *attr) +.. c:function:: void ls_prefix_del(struct ls_prefix *pref) + + Remove, respectively Link State Node, Attributes or Prefix. + Data structure is freed. + +.. c:function:: void ls_attributes_srlg_del(struct ls_attributes *attr) + + Remove SRLGs attribute if defined. Data structure is freed. + +.. c:function:: int ls_node_same(struct ls_node *n1, struct ls_node *n2) +.. c:function:: int ls_attributes_same(struct ls_attributes *a1, struct ls_attributes *a2) +.. c:function:: int ls_prefix_same(struct ls_prefix *p1, struct ls_prefix*p2) + + Check, respectively if two Link State Nodes, Attributes or Prefix are equal. + Note that these routines have the same return value sense as '==' (which is + different from a comparison). + + +Link State TED +-------------- + +This is the high level API that provides functions to create, update, delete a +Link State Database to build a Traffic Engineering Database (TED). + +Data Structures +^^^^^^^^^^^^^^^ + +The Traffic Engineering is modeled as a Graph in order to ease Path Computation +algorithm implementation. Denoted **G(V, E)**, a graph is composed by a list of +**Vertices (V)** which represents the network Node and a list of **Edges (E)** +which represents Link. An additional list of **prefixes (P)** is also added and +also attached to the *Vertex (V)* which advertise it. + +*Vertex (V)* contains the list of outgoing *Edges (E)* that connect this Vertex +with its direct neighbors and the list of incoming *Edges (E)* that connect +the direct neighbors to this Vertex. Indeed, the *Edge (E)* is unidirectional, +thus, it is necessary to add 2 Edges to model a bidirectional relation between +2 Vertices. Finally, the *Vertex (V)* contains a pointer to the corresponding +Link State Node. + +*Edge (E)* contains the source and destination Vertex that this Edge +is connecting and a pointer to the corresponding Link State Attributes. + +A unique Key is used to identify both Vertices and Edges within the Graph. + + +:: + + -------------- --------------------------- -------------- + | Connected |---->| Connected Edge Va to Vb |--->| Connected | + --->| Vertex | --------------------------- | Vertex |----> + | | | | + | - Key (Va) | | - Key (Vb) | + <---| - Vertex | --------------------------- | - Vertex |<---- + | |<----| Connected Edge Vb to Va |<---| | + -------------- --------------------------- -------------- + + +4 data structures have been defined to implement the Graph model: + +.. c:struct:: ls_vertex +.. c:struct:: ls_edge +.. c:struct:: ls_ted + + - :c:struct:`ls_prefix` + +TED stores Vertex, Edge and Subnet elements with a RB Tree structure. +The Vertex key corresponds to the Router ID for OSPF and ISO System ID for +IS-IS. The Edge key corresponds to the IPv4 address, the lowest 64 bits of +the IPv6 address or the combination of the local & remote ID of the interface. +The Subnet key corresponds to the Prefix address (v4 or v6). + +An additional status for Vertex, Edge and Subnet allows to determine the state +of the element in the TED: UNSET, NEW, UPDATE, DELETE, SYNC, ORPHAN. Normal +state is SYNC. NEW, UPDATE and DELETE are temporary state when element is +processed. UNSET is normally never used and ORPHAN serves to identify elements +that must be remove when TED is cleaning. + +Vertex, Edges and Subnets management functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. c:function:: struct ls_vertex *ls_vertex_add(struct ls_ted *ted, struct ls_node *node) +.. c:function:: struct ls_edge *ls_edge_add(struct ls_ted *ted, struct ls_attributes *attributes) +.. c:function:: struct ls_subnet *ls_subnet_add(struct ls_ted *ted, struct ls_prefix *pref) + + Add, respectively new Vertex, Edge or Subnet to the Link State Datebase. + Vertex, Edge or Subnet are created from, respectively the Link State Node, + Attribute or Prefix structure. Data structure are dynamically allocated. + +.. c:function:: struct ls_vertex *ls_vertex_update(struct ls_ted *ted, struct ls_node *node) +.. c:function:: struct ls_edge *ls_edge_update(struct ls_ted *ted, struct ls_attributes *attributes) +.. c:function:: struct ls_subnet *ls_subnet_update(struct ls_ted *ted, struct ls_prefix *pref) + + Update, respectively Vertex, Edge or Subnet with, respectively the Link + State Node, Attribute or Prefix. A new data structure is created if no one + corresponds to the Link State Node, Attribute or Prefix. If element already + exists in the TED, its associated Link State information is replaced by the + new one if there are different and the old associated Link State information + is deleted and memory freed. + +.. c:function:: void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex) +.. c:function:: void ls_vertex_del_all(struct ls_ted *ted, struct ls_vertex *vertex) +.. c:function:: void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge) +.. c:function:: void ls_edge_del_all(struct ls_ted *ted, struct ls_edge *edge) +.. c:function:: void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet) +.. c:function:: void ls_subnet_del_all(struct ls_ted *ted, struct ls_subnet *subnet) + + Delete, respectively Link State Vertex, Edge or Subnet. Data structure are + freed but not the associated Link State information with the simple `_del()` + form of the function while the `_del_all()` version freed also associated + Link State information. TED is not modified if Vertex, Edge or Subnet is + NULL or not found in the Data Base. Note that references between Vertices, + Edges and Subnets are removed first. + +.. c:function:: struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, const uint64_t key) +.. c:function:: struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, struct ls_node_id id) + + Find Vertex in the TED by its unique key or its Link State Node ID. + Return Vertex if found, NULL otherwise. + +.. c:function:: struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, const uint64_t key) +.. c:function:: struct ls_edge *ls_find_edge_by_source(struct ls_ted *ted, struct ls_attributes *attributes); +.. c:function:: struct ls_edge *ls_find_edge_by_destination(struct ls_ted *ted, struct ls_attributes *attributes); + + Find Edge in the Link State Data Base by its key, source or distination + (local IPv4 or IPv6 address or local ID) informations of the Link State + Attributes. Return Edge if found, NULL otherwise. + +.. c:function:: struct ls_subnet *ls_find_subnet(struct ls_ted *ted, const struct prefix prefix) + + Find Subnet in the Link State Data Base by its key, i.e. the associated + prefix. Return Subnet if found, NULL otherwise. + +.. c:function:: int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2) +.. c:function:: int ls_edge_same(struct ls_edge *e1, struct ls_edge *e2) +.. c:function:: int ls_subnet_same(struct ls_subnet *s1, struct ls_subnet *s2) + + Check, respectively if two Vertices, Edges or Subnets are equal. + Note that these routines has the same return value sense as '==' + (which is different from a comparison). + + +TED management functions +^^^^^^^^^^^^^^^^^^^^^^^^ + +Some helpers functions have been also provided to ease TED management: + +.. c:function:: struct ls_ted *ls_ted_new(const uint32_t key, char *name, uint32_t asn) + + Create a new Link State Data Base. Key must be different from 0. + Name could be NULL and AS number equal to 0 if unknown. + +.. c:function:: void ls_ted_del(struct ls_ted *ted) +.. c:function:: void ls_ted_del_all(struct ls_ted *ted) + + Delete existing Link State Data Base. Vertices, Edges, and Subnets are not + removed with ls_ted_del() function while they are with ls_ted_del_all(). + +.. c:function:: void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, struct ls_edge *edge) + + Connect Source and Destination Vertices by given Edge. Only non NULL source + and destination vertices are connected. + +.. c:function:: void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +.. c:function:: void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) + + Connect / Disconnect Link State Edge to the Link State Vertex which could be + a Source (source = true) or a Destination (source = false) Vertex. + +.. c:function:: void ls_disconnect_edge(struct ls_edge *edge) + + Disconnect Link State Edge from both Source and Destination Vertex. + Note that Edge is not removed but its status is marked as ORPHAN. + +.. c:function:: void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex, struct zclient *zclient) + + Clean Vertex structure by removing all Edges and Subnets marked as ORPHAN + from this vertex. Corresponding Link State Update message is sent if zclient + parameter is not NULL. Note that associated Link State Attribute and Prefix + are also removed and memory freed. + +.. c:function:: void ls_ted_clean(struct ls_ted *ted) + + Clean Link State Data Base by removing all Vertices, Edges and SubNets + marked as ORPHAN. Note that associated Link State Node, Attributes and + Prefix are removed too. + +.. c:function:: void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_edge(struct ls_edeg *edge, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_subnet(struct ls_subnet *subnet, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_vertices(struct ls_ted *ted, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_edges(struct ls_ted *ted, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_subnets(struct ls_ted *ted, struct vty *vty, struct json_object *json, bool verbose) +.. c:function:: void ls_show_ted(struct ls_ted *ted, struct vty *vty, struct json_object *json, bool verbose) + + Respectively, show Vertex, Edge, Subnet provided as parameter, all Vertices, + all Edges, all Subnets and the whole TED if not specified. Output could be + more detailed with verbose parameter for VTY output. If both JSON and VTY + output are specified, JSON takes precedence over VTY. + +.. c:function:: void ls_dump_ted(struct ls_ted *ted) + + Dump TED information to the current logging output. + +Link State Messages +------------------- + +This part of the API provides functions and data structure to ease the +communication between the *Producer* and *Consumer* daemons. + +Communications principles +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Recent ZAPI Opaque Message is used to exchange Link State data between daemons. +For that purpose, Link State API provides new functions to serialize and parse +Link State information through the ZAPI Opaque message. A dedicated flag, +named ZAPI_OPAQUE_FLAG_UNICAST, allows daemons to send a unicast or a multicast +Opaque message and is used as follow for the Link State exchange: + +- Multicast: To send data update to all daemons that have subscribed to the + Link State Update message +- Unicast: To send initial Link State information from a particular daemon. All + data are send only to the daemon that request Link State Synchronisatio + +Figure 1 below, illustrates the ZAPI Opaque message exchange between a +*Producer* (an IGP like OSPF or IS-IS) and a *Consumer* (e.g. BGP). The +message sequences are as follows: + +- First, both *Producer* and *Consumer* must register to their respective ZAPI + Opaque Message: **Link State Sync** for the *Producer* in order to receive + Database synchronisation request from a *Consumer*, **Link State Update** for + the *Consumer* in order to received any Link State update from a *Producer*. + These register messages are stored by Zebra to determine to which daemon it + should redistribute the ZAPI messages it receives. +- Then, the *Consumer* sends a **Link State Synchronistation** request with the + Multicast method in order to receive the complete Link State Database from a + *Producer*. ZEBRA daemon forwards this message to any *Producer* daemons that + previously registered to this message. If no *Producer* has yet registered, + the request is lost. Thus, if the *Consumer* receives no response whithin a + given timer, it means that no *Producer* are available right now. So, the + *Consumer* must send the same request until it receives a Link State Database + Synchronistation message. This behaviour is necessary as we can't control in + which order daemons are started. It is up to the *Consumer* daemon to fix the + timeout and the number of retry. +- When a *Producer* receives a **Link State Synchronisation** request, it + starts sending all elements of its own Link State Database through the + **Link State Database Synchronisation** message. These messages are send with + the Unicast method to avoid flooding other daemons with these elements. ZEBRA + layer ensures to forward the message to the right daemon. +- When a *Producer* update its Link State Database, it automatically sends a + **Link State Update** message with the Multicast method. In turn, ZEBRA + daemon forwards the message to all *Consumer* daemons that previously + registered to this message. if no daemon is registered, the message is lost. +- A daemon could unregister from the ZAPI Opaque message registry at any time. + In this case, the ZEBRA daemon stops to forward any messages it receives to + this daemon, even if it was previously converns. + +:: + + IGP ZEBRA Consumer + (OSPF/IS-IS) (ZAPI Opaque Thread) (e.g. BGP) + | | | \ + | | Register LS Update | | + | |<----------------------------| Register Phase + | | | | + | | Request LS Sync | | + | |<----------------------------| | + : : : A | + | Register LS Sync | | | | + |----------------------------->| | | / + : : : |TimeOut + : : : | + | | | | + | | Request LS Sync | v \ + | Request LS Sync |<----------------------------| | + |<-----------------------------| | Synchronistation + | LS DB Update | | Phase + |----------------------------->| LS DB Update | | + | |---------------------------->| | + | LS DB Update (cont'd) | | | + |----------------------------->| LS DB Update (cont'd) | | + | . |---------------------------->| | + | . | . | | + | . | . | | + | LS DB Update (end) | . | | + |----------------------------->| LS DB Update (end) | | + | |---------------------------->| | + | | | / + : : : + : : : + | LS DB Update | | \ + |----------------------------->| LS DB Update | | + | |---------------------------->| Update Phase + | | | | + : : : / + : : : + | | | \ + | | Unregister LS Update | | + | |<----------------------------| Deregister Phase + | | | | + | LS DB Update | | | + |----------------------------->| | | + | | | / + | | | + + Figure 1: Link State messages exchange + + +Data Structures +^^^^^^^^^^^^^^^ + +The Link State Message is defined to convey Link State parameters from +the routing protocol (OSPF or IS-IS) to other daemons e.g. BGP. + +.. c:struct:: ls_message + +The structure is composed of: + +- Event of the message: + + - Sync: Send the whole LS DB following a request + - Add: Send the a new Link State element + - Update: Send an update of an existing Link State element + - Delete: Indicate that the given Link State element is removed + +- Type of Link State element: Node, Attribute or Prefix +- Remote node id when known +- Data: Node, Attributes or Prefix + +A Link State Message can carry only one Link State Element (Node, Attributes +of Prefix) at once, and only one Link State Message is sent through ZAPI +Opaque Link State type at once. + +Functions +^^^^^^^^^ + +.. c:function:: int ls_register(struct zclient *zclient, bool server) +.. c:function:: int ls_unregister(struct zclient *zclient, bool server) + + Register / Unregister daemon to received ZAPI Link State Opaque messages. + Server must be set to true for *Producer* and to false for *Consumer*. + +.. c:function:: int ls_request_sync(struct zclient *zclient) + + Request initial Synchronisation to collect the whole Link State Database. + +.. c:function:: struct ls_message *ls_parse_msg(struct stream *s) + + Parse Link State Message from stream. Used this function once receiving a + new ZAPI Opaque message of type Link State. + +.. c:function:: void ls_delete_msg(struct ls_message *msg) + + Delete existing message. Data structure is freed. + +.. c:function:: int ls_send_msg(struct zclient *zclient, struct ls_message *msg, struct zapi_opaque_reg_info *dst) + + Send Link State Message as new ZAPI Opaque message of type Link State. + If destination is not NULL, message is sent as Unicast otherwise it is + broadcast to all registered daemon. + +.. c:function:: struct ls_message *ls_vertex2msg(struct ls_message *msg, struct ls_vertex *vertex) +.. c:function:: struct ls_message *ls_edge2msg(struct ls_message *msg, struct ls_edge *edge) +.. c:function:: struct ls_message *ls_subnet2msg(struct ls_message *msg, struct ls_subnet *subnet) + + Create respectively a new Link State Message from a Link State Vertex, Edge + or Subnet. If Link State Message is NULL, a new data structure is + dynamically allocated. Note that the Vertex, Edge and Subnet status is used + to determine the corresponding Link State Message event: ADD, UPDATE, + DELETE, SYNC. + +.. c:function:: int ls_msg2vertex(struct ls_ted *ted, struct ls_message *msg) +.. c:function:: int ls_msg2edge(struct ls_ted *ted, struct ls_message *msg) +.. c:function:: int ls_msg2subnet(struct ls_ted *ted, struct ls_message *msg) + + Convert Link State Message respectively in Vertex, Edge or Subnet and + update the Link State Database accordingly to the message event: SYNC, ADD, + UPDATE or DELETE. + +.. c:function:: struct ls_element *ls_msg2ted(struct ls_ted *ted, struct ls_message *msg, bool delete) +.. c:function:: struct ls_element *ls_stream2ted(struct ls_ted *ted, struct ls_message *msg, bool delete) + + Convert Link State Message or Stream Buffer in a Link State element (Vertex, + Edge or Subnet) and update the Link State Database accordingly to the + message event: SYNC, ADD, UPDATE or DELETE. The function return the generic + structure ls_element that point to the Vertex, Edge or Subnet which has been + added, updated or synchronous in the database. Note that the delete boolean + parameter governs the action for the DELETE action: true, Link State Element + is removed from the database and NULL is return. If set to false, database + is not updated and the function sets the Link State Element status to + Delete and return the element for futur deletion by the calling function. + +.. c:function:: int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, struct zapi_opaque_reg_info *dst) + + Send all the content of the Link State Data Base to the given destination. + Link State content is sent is this order: Vertices, Edges then Subnet. + This function must be used when a daemon request a Link State Data Base + Synchronization. diff --git a/doc/developer/lists.rst b/doc/developer/lists.rst new file mode 100644 index 0000000..ccac10a --- /dev/null +++ b/doc/developer/lists.rst @@ -0,0 +1,777 @@ +.. _lists: + +Type-safe containers +==================== + +.. note:: + + This section previously used the term *list*; it was changed to *container* + to be more clear. + +Common container interface +-------------------------- + +FRR includes a set of container implementations with abstracted +common APIs. The purpose of this is easily allow swapping out one +data structure for another while also making the code easier to read and write. +There is one API for unsorted containers and a similar but not identical API +for sorted containers - and heaps use a middle ground of both. + +For unsorted containers, the following implementations exist: + +- single-linked list with tail pointer (e.g. STAILQ in BSD) + +- double-linked list + +- atomic single-linked list with tail pointer + + +Being partially sorted, the oddball structure: + +- an 8-ary heap + + +For sorted containers, these data structures are implemented: + +- single-linked list + +- atomic single-linked list + +- skiplist + +- red-black tree (based on OpenBSD RB_TREE) + +- hash table (note below) + +Except for hash tables, each of the sorted data structures has a variant with +unique and non-unique items. Hash tables always require unique items +and mostly follow the "sorted" API but use the hash value as sorting +key. Also, iterating while modifying does not work with hash tables. +Conversely, the heap always has non-unique items, but iterating while modifying +doesn't work either. + + +The following sorted structures are likely to be implemented at some point +in the future: + +- atomic skiplist + +- atomic hash table (note below) + + +The APIs are all designed to be as type-safe as possible. This means that +there will be a compiler warning when an item doesn't match the container, or +the return value has a different type, or other similar situations. **You +should never use casts with these APIs.** If a cast is necessary in relation +to these APIs, there is probably something wrong with the overall design. + +Only the following pieces use dynamically allocated memory: + +- the hash table itself is dynamically grown and shrunk + +- skiplists store up to 4 next pointers inline but will dynamically allocate + memory to hold an item's 5th up to 16th next pointer (if they exist) + +- the heap uses a dynamically grown and shrunk array of items + +Cheat sheet +----------- + +Available types: + +:: + + DECLARE_LIST + DECLARE_ATOMLIST + DECLARE_DLIST + + DECLARE_HEAP + + DECLARE_SORTLIST_UNIQ + DECLARE_SORTLIST_NONUNIQ + DECLARE_ATOMLIST_UNIQ + DECLARE_ATOMLIST_NONUNIQ + DECLARE_SKIPLIST_UNIQ + DECLARE_SKIPLIST_NONUNIQ + DECLARE_RBTREE_UNIQ + DECLARE_RBTREE_NONUNIQ + + DECLARE_HASH + +Functions provided: + ++------------------------------------+-------+------+------+---------+------------+ +| Function | LIST | HEAP | HASH | \*_UNIQ | \*_NONUNIQ | ++====================================+=======+======+======+=========+============+ +| _init, _fini | yes | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ +| _first, _next, _next_safe, | yes | yes | yes | yes | yes | +| | | | | | | +| _const_first, _const_next | | | | | | ++------------------------------------+-------+------+------+---------+------------+ +| _last, _prev, _prev_safe, | DLIST | -- | -- | RB only | RB only | +| | only | | | | | +| _const_last, _const_prev | | | | | | ++------------------------------------+-------+------+------+---------+------------+ +| _swap_all | yes | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ +| _anywhere | yes | -- | -- | -- | -- | ++------------------------------------+-------+------+------+---------+------------+ +| _add_head, _add_tail, _add_after | yes | -- | -- | -- | -- | ++------------------------------------+-------+------+------+---------+------------+ +| _add | -- | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ +| _member | yes | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ +| _del, _pop | yes | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ +| _find, _const_find | -- | -- | yes | yes | -- | ++------------------------------------+-------+------+------+---------+------------+ +| _find_lt, _find_gteq, | -- | -- | -- | yes | yes | +| | | | | | | +| _const_find_lt, _const_find_gteq | | | | | | ++------------------------------------+-------+------+------+---------+------------+ +| use with frr_each() macros | yes | yes | yes | yes | yes | ++------------------------------------+-------+------+------+---------+------------+ + + + +Datastructure type setup +------------------------ + +Each of the data structures has a ``PREDECL_*`` and a ``DECLARE_*`` macro to +set up an "instantiation" of the container. This works somewhat similar to C++ +templating, though much simpler. + +**In all following text, the Z prefix is replaced with a name chosen +for the instance of the datastructure.** + +The common setup pattern will look like this: + +.. code-block:: c + + #include <typesafe.h> + + PREDECL_XXX(Z); + struct item { + int otherdata; + struct Z_item mylistitem; + } + + struct Z_head mylisthead; + + /* unsorted: */ + DECLARE_XXX(Z, struct item, mylistitem); + + /* sorted, items that compare as equal cannot be added to list */ + int compare_func(const struct item *a, const struct item *b); + DECLARE_XXX_UNIQ(Z, struct item, mylistitem, compare_func); + + /* sorted, items that compare as equal can be added to list */ + int compare_func(const struct item *a, const struct item *b); + DECLARE_XXX_NONUNIQ(Z, struct item, mylistitem, compare_func); + + /* hash tables: */ + int compare_func(const struct item *a, const struct item *b); + uint32_t hash_func(const struct item *a); + DECLARE_XXX(Z, struct item, mylistitem, compare_func, hash_func); + +``XXX`` is replaced with the name of the data structure, e.g. ``SKIPLIST`` +or ``ATOMLIST``. The ``DECLARE_XXX`` invocation can either occur in a `.h` +file (if the container needs to be accessed from several C files) or it can be +placed in a `.c` file (if the container is only accessed from that file.) The +``PREDECL_XXX`` invocation defines the ``struct Z_item`` and ``struct +Z_head`` types and must therefore occur before these are used. + +To switch between compatible data structures, only these two lines need to be +changes. To switch to a data structure with a different API, some source +changes are necessary. + +Common iteration macros +----------------------- + +The following iteration macros work across all data structures: + +.. c:macro:: frr_each(Z, head, item) + + Equivalent to: + + .. code-block:: c + + for (item = Z_first(&head); item; item = Z_next(&head, item)) + + Note that this will fail if the container is modified while being iterated + over. + +.. c:macro:: frr_each_safe(Z, head, item) + + Same as the previous, but the next element is pre-loaded into a "hidden" + variable (named ``Z_safe``.) Equivalent to: + + .. code-block:: c + + for (item = Z_first(&head); item; item = next) { + next = Z_next_safe(&head, item); + ... + } + + .. warning:: + + Iterating over hash tables while adding or removing items is not + possible. The iteration position will be corrupted when the hash + tables is resized while iterating. This will cause items to be + skipped or iterated over twice. + +.. c:macro:: frr_each_from(Z, head, item, from) + + Iterates over the container, starting at item ``from``. This variant is + "safe" as in the previous macro. Equivalent to: + + .. code-block:: c + + for (item = from; item; item = from) { + from = Z_next_safe(&head, item); + ... + } + + .. note:: + + The ``from`` variable is written to. This is intentional - you can + resume iteration after breaking out of the loop by keeping the ``from`` + value persistent and reusing it for the next loop. + +.. c:macro:: frr_rev_each(Z, head, item) +.. c:macro:: frr_rev_each_safe(Z, head, item) +.. c:macro:: frr_rev_each_from(Z, head, item, from) + + Reverse direction variants of the above. Only supported on containers that + implement ``_last`` and ``_prev`` (i.e. ``RBTREE`` and ``DLIST``). + +To iterate over ``const`` pointers, add ``_const`` to the name of the +datastructure (``Z`` above), e.g. ``frr_each (mylist, head, item)`` becomes +``frr_each (mylist_const, head, item)``. + +Common API +---------- + +The following documentation assumes that a container has been defined using +``Z`` as the name, and ``itemtype`` being the type of the items (e.g. +``struct item``.) + +.. c:function:: void Z_init(struct Z_head *) + + Initializes the container for use. For most implementations, this just sets + some values. Hash tables are the only implementation that allocates + memory in this call. + +.. c:function:: void Z_fini(struct Z_head *) + + Reverse the effects of :c:func:`Z_init()`. The container must be empty + when this function is called. + + .. warning:: + + This function may ``assert()`` if the container is not empty. + +.. c:function:: size_t Z_count(const struct Z_head *) + + Returns the number of items in a structure. All structures store a + counter in their `Z_head` so that calling this function completes + in O(1). + + .. note:: + + For atomic containers with concurrent access, the value will already be + outdated by the time this function returns and can therefore only be + used as an estimate. + +.. c:function:: bool Z_member(const struct Z_head *, const itemtype *) + + Determines whether some item is a member of the given container. The + item must either be valid on some container, or set to all zeroes. + + On some containers, if no faster way to determine membership is possible, + this is simply ``item == Z_find(head, item)``. + + Not currently available for atomic containers. + +.. c:function:: const itemtype *Z_const_first(const struct Z_head *) +.. c:function:: itemtype *Z_first(struct Z_head *) + + Returns the first item in the structure, or ``NULL`` if the structure is + empty. This is O(1) for all data structures except red-black trees + where it is O(log n). + +.. c:function:: const itemtype *Z_const_last(const struct Z_head *) +.. c:function:: itemtype *Z_last(struct Z_head *) + + Last item in the structure, or ``NULL``. Only available on containers + that support reverse iteration (i.e. ``RBTREE`` and ``DLIST``). + +.. c:function:: itemtype *Z_pop(struct Z_head *) + + Remove and return the first item in the structure, or ``NULL`` if the + structure is empty. Like :c:func:`Z_first`, this is O(1) for all + data structures except red-black trees where it is O(log n) again. + + This function can be used to build queues (with unsorted structures) or + priority queues (with sorted structures.) + + Another common pattern is deleting all container items: + + .. code-block:: c + + while ((item = Z_pop(head))) + item_free(item); + + .. note:: + + This function can - and should - be used with hash tables. It is not + affected by the "modification while iterating" problem. To remove + all items from a hash table, use the loop demonstrated above. + +.. c:function:: const itemtype *Z_const_next(const struct Z_head *, const itemtype *prev) +.. c:function:: itemtype *Z_next(struct Z_head *, itemtype *prev) + + Return the item that follows after ``prev``, or ``NULL`` if ``prev`` is + the last item. + + .. warning:: + + ``prev`` must not be ``NULL``! Use :c:func:`Z_next_safe()` if + ``prev`` might be ``NULL``. + +.. c:function:: itemtype *Z_next_safe(struct Z_head *, itemtype *prev) + + Same as :c:func:`Z_next()`, except that ``NULL`` is returned if + ``prev`` is ``NULL``. + +.. c:function:: const itemtype *Z_const_prev(const struct Z_head *, const itemtype *next) +.. c:function:: itemtype *Z_prev(struct Z_head *, itemtype *next) +.. c:function:: itemtype *Z_prev_safe(struct Z_head *, itemtype *next) + + As above, but preceding item. Only available on structures that support + reverse iteration (i.e. ``RBTREE`` and ``DLIST``). + +.. c:function:: itemtype *Z_del(struct Z_head *, itemtype *item) + + Remove ``item`` from the container and return it. + + .. note:: + + This function's behaviour is undefined if ``item`` is not actually + on the container. Some structures return ``NULL`` in this case while + others return ``item``. The function may also call ``assert()`` (but + most don't.) + +.. c:function:: itemtype *Z_swap_all(struct Z_head *, struct Z_head *) + + Swap the contents of 2 containers (of identical type). This exchanges the + contents of the two head structures and updates pointers if necessary for + the particular data structure. Fast for all structures. + + (Not currently available on atomic containers.) + +.. todo:: + + ``Z_del_after()`` / ``Z_del_hint()``? + +API for unsorted structures +--------------------------- + +Since the insertion position is not pre-defined for unsorted data, there +are several functions exposed to insert data: + +.. note:: + + ``item`` must not be ``NULL`` for any of the following functions. + +.. c:macro:: DECLARE_XXX(Z, type, field) + + :param listtype XXX: ``LIST``, ``DLIST`` or ``ATOMLIST`` to select a data + structure implementation. + :param token Z: Gives the name prefix that is used for the functions + created for this instantiation. ``DECLARE_XXX(foo, ...)`` + gives ``struct foo_item``, ``foo_add_head()``, ``foo_count()``, etc. Note + that this must match the value given in ``PREDECL_XXX(foo)``. + :param typename type: Specifies the data type of the list items, e.g. + ``struct item``. Note that ``struct`` must be added here, it is not + automatically added. + :param token field: References a struct member of ``type`` that must be + typed as ``struct foo_item``. This struct member is used to + store "next" pointers or other data structure specific data. + +.. c:function:: void Z_add_head(struct Z_head *, itemtype *item) + + Insert an item at the beginning of the structure, before the first item. + This is an O(1) operation for non-atomic lists. + +.. c:function:: void Z_add_tail(struct Z_head *, itemtype *item) + + Insert an item at the end of the structure, after the last item. + This is also an O(1) operation for non-atomic lists. + +.. c:function:: void Z_add_after(struct Z_head *, itemtype *after, itemtype *item) + + Insert ``item`` behind ``after``. If ``after`` is ``NULL``, the item is + inserted at the beginning of the list as with :c:func:`Z_add_head`. + This is also an O(1) operation for non-atomic lists. + + A common pattern is to keep a "previous" pointer around while iterating: + + .. code-block:: c + + itemtype *prev = NULL, *item; + + frr_each_safe(Z, head, item) { + if (something) { + Z_add_after(head, prev, item); + break; + } + prev = item; + } + + .. todo:: + + maybe flip the order of ``item`` & ``after``? + ``Z_add_after(head, item, after)`` + +.. c:function:: bool Z_anywhere(const itemtype *) + + Returns whether an item is a member of *any* container of this type. + The item must either be valid on some container, or set to all zeroes. + + Guaranteed to be fast (pointer compare or similar.) + + Not currently available for sorted and atomic containers. Might be added + for sorted containers at some point (when needed.) + + +API for sorted structures +------------------------- + +Sorted data structures do not need to have an insertion position specified, +therefore the insertion calls are different from unsorted containers. Also, +sorted containers can be searched for a value. + +.. c:macro:: DECLARE_XXX_UNIQ(Z, type, field, compare_func) + + :param listtype XXX: One of the following: + ``SORTLIST`` (single-linked sorted list), ``SKIPLIST`` (skiplist), + ``RBTREE`` (RB-tree) or ``ATOMSORT`` (atomic single-linked list). + :param token Z: Gives the name prefix that is used for the functions + created for this instantiation. ``DECLARE_XXX(foo, ...)`` + gives ``struct foo_item``, ``foo_add()``, ``foo_count()``, etc. Note + that this must match the value given in ``PREDECL_XXX(foo)``. + :param typename type: Specifies the data type of the items, e.g. + ``struct item``. Note that ``struct`` must be added here, it is not + automatically added. + :param token field: References a struct member of ``type`` that must be + typed as ``struct foo_item``. This struct member is used to + store "next" pointers or other data structure specific data. + :param funcptr compare_func: Item comparison function, must have the + following function signature: + ``int function(const itemtype *, const itemtype*)``. This function + may be static if the container is only used in one file. + +.. c:macro:: DECLARE_XXX_NONUNIQ(Z, type, field, compare_func) + + Same as above, but allow adding multiple items to the container that compare + as equal in ``compare_func``. Ordering between these items is undefined + and depends on the container implementation. + +.. c:function:: itemtype *Z_add(struct Z_head *, itemtype *item) + + Insert an item at the appropriate sorted position. If another item exists + in the container that compares as equal (``compare_func()`` == 0), ``item`` + is not inserted and the already-existing item in the container is + returned. Otherwise, on successful insertion, ``NULL`` is returned. + + For ``_NONUNIQ`` containers, this function always returns NULL since + ``item`` can always be successfully added to the container. + +.. c:function:: const itemtype *Z_const_find(const struct Z_head *, const itemtype *ref) +.. c:function:: itemtype *Z_find(struct Z_head *, const itemtype *ref) + + Search the container for an item that compares equal to ``ref``. If no + equal item is found, return ``NULL``. + + This function is likely used with a temporary stack-allocated value for + ``ref`` like so: + + .. code-block:: c + + itemtype searchfor = { .foo = 123 }; + + itemtype *item = Z_find(head, &searchfor); + + .. note:: + + The ``Z_find()`` function is only available for containers that contain + unique items (i.e. ``DECLARE_XXX_UNIQ``.) This is because on a container + with non-unique items, more than one item may compare as equal to + the item that is searched for. + +.. c:function:: const itemtype *Z_const_find_gteq(const struct Z_head *, const itemtype *ref) +.. c:function:: itemtype *Z_find_gteq(struct Z_head *, const itemtype *ref) + + Search the container for an item that compares greater or equal to + ``ref``. See :c:func:`Z_find()` above. + +.. c:function:: const itemtype *Z_const_find_lt(const struct Z_head *, const itemtype *ref) +.. c:function:: itemtype *Z_find_lt(struct Z_head *, const itemtype *ref) + + Search the container for an item that compares less than + ``ref``. See :c:func:`Z_find()` above. + + +API for hash tables +------------------- + +.. c:macro:: DECLARE_HASH(Z, type, field, compare_func, hash_func) + + :param listtype HASH: Only ``HASH`` is currently available. + :param token Z: Gives the name prefix that is used for the functions + created for this instantiation. ``DECLARE_XXX(foo, ...)`` + gives ``struct foo_item``, ``foo_add()``, ``foo_count()``, etc. Note + that this must match the value given in ``PREDECL_XXX(foo)``. + :param typename type: Specifies the data type of the items, e.g. + ``struct item``. Note that ``struct`` must be added here, it is not + automatically added. + :param token field: References a struct member of ``type`` that must be + typed as ``struct foo_item``. This struct member is used to + store "next" pointers or other data structure specific data. + :param funcptr compare_func: Item comparison function, must have the + following function signature: + ``int function(const itemtype *, const itemtype*)``. This function + may be static if the container is only used in one file. For hash tables, + this function is only used to check for equality, the ordering is + ignored. + :param funcptr hash_func: Hash calculation function, must have the + following function signature: + ``uint32_t function(const itemtype *)``. The hash value for items + stored in a hash table is cached in each item, so this value need not + be cached by the user code. + + .. warning:: + + Items that compare as equal cannot be inserted. Refer to the notes + about sorted structures in the previous section. + + +.. c:function:: void Z_init_size(struct Z_head *, size_t size) + + Same as :c:func:`Z_init()` but preset the minimum hash table to + ``size``. + +Hash tables also support :c:func:`Z_add()` and :c:func:`Z_find()` with +the same semantics as noted above. :c:func:`Z_find_gteq()` and +:c:func:`Z_find_lt()` are **not** provided for hash tables. + +Hash table invariants +^^^^^^^^^^^^^^^^^^^^^ + +There are several ways to injure yourself using the hash table API. + +First, note that there are two functions related to computing uniqueness of +objects inserted into the hash table. There is a hash function and a comparison +function. The hash function computes the hash of the object. Our hash table +implementation uses `chaining +<https://en.wikipedia.org/wiki/Hash_table#Separate_chaining_with_linked_lists>`_. +This means that your hash function does not have to be perfect; multiple +objects having the same computed hash will be placed into a linked list +corresponding to that key. The closer to perfect the hash function, the better +performance, as items will be more evenly distributed and the chain length will +not be long on any given lookup, minimizing the number of list operations +required to find the correct item. However, the comparison function *must* be +perfect, in the sense that any two unique items inserted into the hash table +must compare not equal. At insertion time, if you try to insert an item that +compares equal to an existing item the insertion will not happen and +``hash_get()`` will return the existing item. However, this invariant *must* be +maintained while the object is in the hash table. Suppose you insert items +``A`` and ``B`` into the hash table which both hash to the same value ``1234`` +but do not compare equal. They will be placed in a chain like so:: + + 1234 : A -> B + +Now suppose you do something like this elsewhere in the code:: + + *A = *B + +I.e. you copy all fields of ``B`` into ``A``, such that the comparison function +now says that they are equal based on their contents. At this point when you +look up ``B`` in the hash table, ``hash_get()`` will search the chain for the +first item that compares equal to ``B``, which will be ``A``. This leads to +insidious bugs. + +.. warning:: + + Never modify the values looked at by the comparison or hash functions after + inserting an item into a hash table. + +A similar situation can occur with the hash allocation function. ``hash_get()`` +accepts a function pointer that it will call to get the item that should be +inserted into the list if the provided item is not already present. There is a +builtin function, ``hash_alloc_intern``, that will simply return the item you +provided; if you always want to store the value you pass to ``hash_get`` you +should use this one. If you choose to provide a different one, that function +*must* return a new item that hashes and compares equal to the one you provided +to ``hash_get()``. If it does not the behavior of the hash table is undefined. + +.. warning:: + + Always make sure your hash allocation function returns a value that hashes + and compares equal to the item you provided to ``hash_get()``. + +Finally, if you maintain pointers to items you have inserted into a hash table, +then before deallocating them you must release them from the hash table. This +is basic memory management but worth repeating as bugs have arisen from failure +to do this. + + +API for heaps +------------- + +Heaps provide the same API as the sorted data structures, except: + +* none of the find functions (:c:func:`Z_find()`, :c:func:`Z_find_gteq()` + or :c:func:`Z_find_lt()`) are available. +* iterating over the heap yields the items in semi-random order, only the + first item is guaranteed to be in order and actually the "lowest" item + on the heap. Being a heap, only the rebalancing performed on removing the + first item (either through :c:func:`Z_pop()` or :c:func:`Z_del()`) causes + the new lowest item to bubble up to the front. +* all heap modifications are O(log n). However, cacheline efficiency and + latency is likely quite a bit better than with other data structures. + +Atomic lists +------------ + +`atomlist.h` provides an unsorted and a sorted atomic single-linked list. +Since atomic memory accesses can be considerably slower than plain memory +accessses (depending on the CPU type), these lists should only be used where +necessary. + +The following guarantees are provided regarding concurrent access: + +- the operations are lock-free but not wait-free. + + Lock-free means that it is impossible for all threads to be blocked. Some + thread will always make progress, regardless of what other threads do. (This + even includes a random thread being stopped by a debugger in a random + location.) + + Wait-free implies that the time any single thread might spend in one of the + calls is bounded. This is not provided here since it is not normally + relevant to practical operations. What this means is that if some thread is + hammering a particular list with requests, it is possible that another + thread is blocked for an extended time. The lock-free guarantee still + applies since the hammering thread is making progress. + +- without a RCU mechanism in place, the point of contention for atomic lists + is memory deallocation. As it is, **a rwlock is required for correct + operation**. The *read* lock must be held for all accesses, including + reading the list, adding items to the list, and removing items from the + list. The *write* lock must be acquired and released before deallocating + any list element. If this is not followed, an use-after-free can occur + as a MT race condition when an element gets deallocated while another + thread is accessing the list. + + .. note:: + + The *write* lock does not need to be held for deleting items from the + list, and there should not be any instructions between the + ``pthread_rwlock_wrlock`` and ``pthread_rwlock_unlock``. The write lock + is used as a sequence point, not as an exclusion mechanism. + +- insertion operations are always safe to do with the read lock held. + Added items are immediately visible after the insertion call returns and + should not be touched anymore. + +- when removing a *particular* (pre-determined) item, the caller must ensure + that no other thread is attempting to remove that same item. If this cannot + be guaranteed by architecture, a separate lock might need to be added. + +- concurrent `pop` calls are always safe to do with only the read lock held. + This does not fall under the previous rule since the `pop` call will select + the next item if the first is already being removed by another thread. + + **Deallocation locking still applies.** Assume another thread starts + reading the list, but gets task-switched by the kernel while reading the + first item. `pop` will happily remove and return that item. If it is + deallocated without acquiring and releasing the write lock, the other thread + will later resume execution and try to access the now-deleted element. + +- the list count should be considered an estimate. Since there might be + concurrent insertions or removals in progress, it might already be outdated + by the time the call returns. No attempt is made to have it be correct even + for a nanosecond. + +Overall, atomic lists are well-suited for MT queues; concurrent insertion, +iteration and removal operations will work with the read lock held. + +Code snippets +^^^^^^^^^^^^^ + +Iteration: + +.. code-block:: c + + struct item *i; + + pthread_rwlock_rdlock(&itemhead_rwlock); + frr_each(itemlist, &itemhead, i) { + /* lock must remain held while iterating */ + ... + } + pthread_rwlock_unlock(&itemhead_rwlock); + +Head removal (pop) and deallocation: + +.. code-block:: c + + struct item *i; + + pthread_rwlock_rdlock(&itemhead_rwlock); + i = itemlist_pop(&itemhead); + pthread_rwlock_unlock(&itemhead_rwlock); + + /* i might still be visible for another thread doing an + * frr_each() (but won't be returned by another pop()) */ + ... + + pthread_rwlock_wrlock(&itemhead_rwlock); + pthread_rwlock_unlock(&itemhead_rwlock); + /* i now guaranteed to be gone from the list. + * note nothing between wrlock() and unlock() */ + XFREE(MTYPE_ITEM, i); + +FAQ +--- + +What are the semantics of ``const`` in the container APIs? + ``const`` pointers to list heads and/or items are interpreted to mean that + both the container itself as well as the data items are read-only. + +Why is it ``PREDECL`` + ``DECLARE`` instead of ``DECLARE`` + ``DEFINE``? + The rule is that a ``DEFINE`` must be in a ``.c`` file, and linked exactly + once because it defines some kind of global symbol. This is not the case + for the data structure macros; they only define ``static`` symbols and it + is perfectly fine to include both ``PREDECL`` and ``DECLARE`` in a header + file. It is also perfectly fine to have the same ``DECLARE`` statement in + 2 ``.c`` files, but only **if the macro arguments are identical.** Maybe + don't do that unless you really need it. + +FRR lists +--------- + +.. TODO:: + + document + +BSD lists +--------- + +.. TODO:: + + refer to external docs diff --git a/doc/developer/locking.rst b/doc/developer/locking.rst new file mode 100644 index 0000000..bce1311 --- /dev/null +++ b/doc/developer/locking.rst @@ -0,0 +1,79 @@ +.. _locking: + +Locking +======= + +FRR ships two small wrappers around ``pthread_mutex_lock()`` / +``pthread_mutex_unlock``. Use ``#include "frr_pthread.h"`` to get these +macros. + +.. c:macro:: frr_with_mutex (mutex) + + (With ``pthread_mutex_t *mutex``.) + + Begin a C statement block that is executed with the mutex locked. Any + exit from the block (``break``, ``return``, ``goto``, end of block) will + cause the mutex to be unlocked:: + + int somefunction(int option) + { + frr_with_mutex (&my_mutex) { + /* mutex will be locked */ + + if (!option) + /* mutex will be unlocked before return */ + return -1; + + if (something(option)) + /* mutex will be unlocked before goto */ + goto out_err; + + somethingelse(); + + /* mutex will be unlocked at end of block */ + } + + return 0; + + out_err: + somecleanup(); + return -1; + } + + This is a macro that internally uses a ``for`` loop. It is explicitly + acceptable to use ``break`` to get out of the block. Even though a single + statement works correctly, FRR coding style requires that this macro always + be used with a ``{ ... }`` block. + +.. c:macro:: frr_mutex_lock_autounlock(mutex) + + (With ``pthread_mutex_t *mutex``.) + + Lock mutex and unlock at the end of the current C statement block:: + + int somefunction(int option) + { + frr_mutex_lock_autounlock(&my_mutex); + /* mutex will be locked */ + + ... + if (error) + /* mutex will be unlocked before return */ + return -1; + ... + + /* mutex will be unlocked before return */ + return 0; + } + + This is a macro that internally creates a variable with a destructor. + When the variable goes out of scope (i.e. the block ends), the mutex is + released. + + .. warning:: + + This macro should only used when :c:func:`frr_with_mutex` would + result in excessively/weirdly nested code. This generally is an + indicator that the code might be trying to do too many things with + the lock held. Try any possible venues to reduce the amount of + code covered by the lock and move to :c:func:`frr_with_mutex`. diff --git a/doc/developer/logging.rst b/doc/developer/logging.rst new file mode 100644 index 0000000..82cc8b2 --- /dev/null +++ b/doc/developer/logging.rst @@ -0,0 +1,882 @@ +.. _logging: + +.. highlight:: c + +Logging +======= + +One of the most frequent decisions to make while writing code for FRR is what +to log, what level to log it at, and when to log it. Here is a list of +recommendations for these decisions. + + +printfrr() +---------- + +``printfrr()`` is FRR's modified version of ``printf()``, designed to make +life easier when printing nontrivial datastructures. The following variants +are available: + +.. c:function:: ssize_t snprintfrr(char *buf, size_t len, const char *fmt, ...) +.. c:function:: ssize_t vsnprintfrr(char *buf, size_t len, const char *fmt, va_list) + + These correspond to ``snprintf``/``vsnprintf``. If you pass NULL for buf + or 0 for len, no output is written but the return value is still calculated. + + The return value is always the full length of the output, unconstrained by + `len`. It does **not** include the terminating ``\0`` character. A + malformed format string can result in a ``-1`` return value. + +.. c:function:: ssize_t csnprintfrr(char *buf, size_t len, const char *fmt, ...) +.. c:function:: ssize_t vcsnprintfrr(char *buf, size_t len, const char *fmt, va_list) + + Same as above, but the ``c`` stands for "continue" or "concatenate". The + output is appended to the string instead of overwriting it. + +.. c:function:: char *asprintfrr(struct memtype *mt, const char *fmt, ...) +.. c:function:: char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) + + These functions allocate a dynamic buffer (using MTYPE `mt`) and print to + that. If the format string is malformed, they return a copy of the format + string, so the return value is always non-NULL and always dynamically + allocated with `mt`. + +.. c:function:: char *asnprintfrr(struct memtype *mt, char *buf, size_t len, const char *fmt, ...) +.. c:function:: char *vasnprintfrr(struct memtype *mt, char *buf, size_t len, const char *fmt, va_list) + + This variant tries to use the static buffer provided, but falls back to + dynamic allocation if it is insufficient. + + The return value can be either `buf` or a newly allocated string using + `mt`. You MUST free it like this:: + + char *ret = asnprintfrr(MTYPE_FOO, buf, sizeof(buf), ...); + if (ret != buf) + XFREE(MTYPE_FOO, ret); + +.. c:function:: ssize_t bprintfrr(struct fbuf *fb, const char *fmt, ...) +.. c:function:: ssize_t vbprintfrr(struct fbuf *fb, const char *fmt, va_list) + + These are the "lowest level" functions, which the other variants listed + above use to implement their functionality on top. Mainly useful for + implementing printfrr extensions since those get a ``struct fbuf *`` to + write their output to. + +.. c:macro:: FMT_NSTD(expr) + + This macro turns off/on format warnings as needed when non-ISO-C + compatible printfrr extensions are used (e.g. ``%.*p`` or ``%Ld``.):: + + vty_out(vty, "standard compatible %pI4\n", &addr); + FMT_NSTD(vty_out(vty, "non-standard %-47.*pHX\n", (int)len, buf)); + + When the frr-format plugin is in use, this macro is a no-op since the + frr-format plugin supports all printfrr extensions. Since the FRR CI + includes a system with the plugin enabled, this means format errors will + not slip by undetected even with FMT_NSTD. + +.. note:: + + ``printfrr()`` does not support the ``%n`` format. It does support ISO C23 + ``%b``, ``%w99d`` and ``%wf99d`` additions, but the latter two are not + supported by the ``frr-format`` plugin yet, and all 3 aren't supported by + the older compilers still in use on some supported platforms. + + ``%b`` can be used with ``FMT_NSTD``, but ``%w99d`` and ``%wf99d`` require + work in the ``frr-format`` plugin before they are really usable. + + +AS-Safety +^^^^^^^^^ + +``printfrr()`` are AS-Safe under the following conditions: + +* the ``[v]as[n]printfrr`` variants are not AS-Safe (allocating memory) +* floating point specifiers are not AS-Safe (system printf is used for these) +* the positional ``%1$d`` syntax should not be used (8 arguments are supported + while AS-Safe) +* extensions are only AS-Safe if their printer is AS-Safe + +printfrr Extensions +------------------- + +``printfrr()`` format strings can be extended with suffixes after `%p` or `%d`. +Printf features like field lengths can be used normally with these extensions, +e.g. ``%-15pI4`` works correctly, **except if the extension consumes the +width or precision**. Extensions that do so are listed below as ``%*pXX`` +rather than ``%pXX``. + +The extension specifier after ``%p`` or ``%d`` is always an uppercase letter; +by means of established pattern uppercase letters and numbers form the type +identifier which may be followed by lowercase flags. + +You can grep the FRR source for ``printfrr_ext_autoreg`` to see all extended +printers and what exactly they do. More printers are likely to be added as +needed/useful, so the list here may be outdated. + +.. note:: + + The ``zlog_*``/``flog_*`` and ``vty_out`` functions all use printfrr + internally, so these extensions are available there. However, they are + **not** available when calling ``snprintf`` directly. You need to call + ``snprintfrr`` instead. + +Networking data types +^^^^^^^^^^^^^^^^^^^^^ + +.. role:: frrfmtout(code) + +.. frrfmt:: %pI4 (struct in_addr *, in_addr_t *) + + :frrfmtout:`1.2.3.4` + + ``%pI4s``: :frrfmtout:`*` — print star instead of ``0.0.0.0`` (for multicast) + +.. frrfmt:: %pI6 (struct in6_addr *) + + :frrfmtout:`fe80::1234` + + ``%pI6s``: :frrfmtout:`*` — print star instead of ``::`` (for multicast) + +.. frrfmt:: %pEA (struct ethaddr *) + + :frrfmtout:`01:23:45:67:89:ab` + +.. frrfmt:: %pIA (struct ipaddr *) + + :frrfmtout:`1.2.3.4` / :frrfmtout:`fe80::1234` + + ``%pIAs``: — print star instead of zero address (for multicast) + +.. frrfmt:: %pFX (struct prefix *) + + :frrfmtout:`1.2.3.0/24` / :frrfmtout:`fe80::1234/64` + + This accepts the following types: + + - :c:struct:`prefix` + - :c:struct:`prefix_ipv4` + - :c:struct:`prefix_ipv6` + - :c:struct:`prefix_eth` + - :c:struct:`prefix_evpn` + - :c:struct:`prefix_fs` + + It does **not** accept the following types: + + - :c:struct:`prefix_ls` + - :c:struct:`prefix_rd` + - :c:struct:`prefix_sg` (use :frrfmt:`%pPSG4`) + - :c:union:`prefixptr` (dereference to get :c:struct:`prefix`) + - :c:union:`prefixconstptr` (dereference to get :c:struct:`prefix`) + + Options: + + ``%pFXh``: (address only) :frrfmtout:`1.2.3.0` / :frrfmtout:`fe80::1234` + +.. frrfmt:: %pPSG4 (struct prefix_sg *) + + :frrfmtout:`(*,1.2.3.4)` + + This is *(S,G)* output for use in zebra. (Note prefix_sg is not a prefix + "subclass" like the other prefix_* structs.) + +.. frrfmt:: %pSU (union sockunion *) + + ``%pSU``: :frrfmtout:`1.2.3.4` / :frrfmtout:`fe80::1234` + + ``%pSUs``: :frrfmtout:`1.2.3.4` / :frrfmtout:`fe80::1234%89` + (adds IPv6 scope ID as integer) + + ``%pSUp``: :frrfmtout:`1.2.3.4:567` / :frrfmtout:`[fe80::1234]:567` + (adds port) + + ``%pSUps``: :frrfmtout:`1.2.3.4:567` / :frrfmtout:`[fe80::1234%89]:567` + (adds port and scope ID) + +.. frrfmt:: %pRN (struct route_node *, struct bgp_node *, struct agg_node *) + + :frrfmtout:`192.168.1.0/24` (dst-only node) + + :frrfmtout:`2001:db8::/32 from fe80::/64` (SADR node) + +.. frrfmt:: %pNH (struct nexthop *) + + ``%pNHvv``: :frrfmtout:`via 1.2.3.4, eth0` — verbose zebra format + + ``%pNHv``: :frrfmtout:`1.2.3.4, via eth0` — slightly less verbose zebra format + + ``%pNHs``: :frrfmtout:`1.2.3.4 if 15` — same as :c:func:`nexthop2str()` + + ``%pNHcg``: :frrfmtout:`1.2.3.4` — compact gateway only + + ``%pNHci``: :frrfmtout:`eth0` — compact interface only + +.. frrfmt:: %dPF (int) + + :frrfmtout:`AF_INET` + + Prints an `AF_*` / `PF_*` constant. ``PF`` is used here to avoid confusion + with `AFI` constants, even though the FRR codebase prefers `AF_INET` over + `PF_INET` & co. + +.. frrfmt:: %dSO (int) + + :frrfmtout:`SOCK_STREAM` + +Time/interval formats +^^^^^^^^^^^^^^^^^^^^^ + +.. frrfmt:: %pTS (struct timespec *) + +.. frrfmt:: %pTV (struct timeval *) + +.. frrfmt:: %pTT (time_t *) + + Above 3 options internally result in the same code being called, support + the same flags and produce equal output with one exception: ``%pTT`` + has no sub-second precision and the formatter will never print a + (nonsensical) ``.000``. + + Exactly one of ``I``, ``M`` or ``R`` must immediately follow after + ``TS``/``TV``/``TT`` to specify whether the input is an interval, monotonic + timestamp or realtime timestamp: + + ``%pTVI``: input is an interval, not a timestamp. Print interval. + + ``%pTVIs``: input is an interval, convert to wallclock by subtracting it + from current time (i.e. interval has passed **s**\ ince.) + + ``%pTVIu``: input is an interval, convert to wallclock by adding it to + current time (i.e. **u**\ ntil interval has passed.) + + ``%pTVM`` - input is a timestamp on CLOCK_MONOTONIC, convert to wallclock + time (by grabbing current CLOCK_MONOTONIC and CLOCK_REALTIME and doing the + math) and print calendaric date. + + ``%pTVMs`` - input is a timestamp on CLOCK_MONOTONIC, print interval + **s**\ ince that timestamp (elapsed.) + + ``%pTVMu`` - input is a timestamp on CLOCK_MONOTONIC, print interval + **u**\ ntil that timestamp (deadline.) + + ``%pTVR`` - input is a timestamp on CLOCK_REALTIME, print calendaric date. + + ``%pTVRs`` - input is a timestamp on CLOCK_REALTIME, print interval + **s**\ ince that timestamp. + + ``%pTVRu`` - input is a timestamp on CLOCK_REALTIME, print interval + **u**\ ntil that timestamp. + + ``%pTVA`` - reserved for CLOCK_TAI in case a PTP implementation is + interfaced to FRR. Not currently implemented. + + .. note:: + + If ``%pTVRs`` or ``%pTVRu`` are used, this is generally an indication + that a CLOCK_MONOTONIC timestamp should be used instead (or added in + parallel.) CLOCK_REALTIME might be adjusted by NTP, PTP or similar + procedures, causing bogus intervals to be printed. + + ``%pTVM`` on first look might be assumed to have the same problem, but + on closer thought the assumption is always that current system time is + correct. And since a CLOCK_MONOTONIC interval is also quite safe to + assume to be correct, the (past) absolute timestamp to be printed from + this can likely be correct even if it doesn't match what CLOCK_REALTIME + would have indicated at that point in the past. This logic does, + however, not quite work for *future* times. + + Generally speaking, almost all use cases in FRR should (and do) use + CLOCK_MONOTONIC (through :c:func:`monotime()`.) + + Flags common to printing calendar times and intervals: + + ``p``: include spaces in appropriate places (depends on selected format.) + + ``%p.3TV...``: specify sub-second resolution (use with ``FMT_NSTD`` to + suppress gcc warning.) As noted above, ``%pTT`` will never print sub-second + digits since there are none. Only some formats support printing sub-second + digits and the default may vary. + + The following flags are available for printing calendar times/dates: + + (no flag): :frrfmtout:`Sat Jan 1 00:00:00 2022` - print output from + ``ctime()``, in local time zone. Since FRR does not currently use/enable + locale support, this is always the C locale. (Locale support getting added + is unlikely for the time being and would likely break other things worse + than this.) + + ``i``: :frrfmtout:`2022-01-01T00:00:00.123` - ISO8601 timestamp in local + time zone (note there is no ``Z`` or ``+00:00`` suffix.) Defaults to + millisecond precision. + + ``ip``: :frrfmtout:`2022-01-01 00:00:00.123` - use readable form of ISO8601 + with space instead of ``T`` separator. + + The following flags are available for printing intervals: + + (no flag): :frrfmtout:`9w9d09:09:09.123` - does not match any + preexisting format; added because it does not lose precision (like ``t``) + for longer intervals without printing huge numbers (like ``h``/``m``). + Defaults to millisecond precision. The week/day fields are left off if + they're zero, ``p`` adds a space after the respective letter. + + ``t``: :frrfmtout:`9w9d09h`, :frrfmtout:`9d09h09m`, :frrfmtout:`09:09:09` - + this replaces :c:func:`frrtime_to_interval()`. ``p`` adds spaces after + week/day/hour letters. + + ``d``: print decimal number of seconds. Defaults to millisecond precision. + + ``x`` / ``tx`` / ``dx``: Like no flag / ``t`` / ``d``, but print + :frrfmtout:`-` for zero or negative intervals (for use with unset timers.) + + ``h``: :frrfmtout:`09:09:09` + + ``hx``: :frrfmtout:`09:09:09`, :frrfmtout:`--:--:--` - this replaces + :c:func:`pim_time_timer_to_hhmmss()`. + + ``m``: :frrfmtout:`09:09` + + ``mx``: :frrfmtout:`09:09`, :frrfmtout:`--:--` - this replaces + :c:func:`pim_time_timer_to_mmss()`. + +FRR library helper formats +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. frrfmt:: %pTH (struct event *) + + Print remaining time on timer event. Interval-printing flag characters + listed above for ``%pTV`` can be added, e.g. ``%pTHtx``. + + ``NULL`` pointers are printed as ``-``. + +.. frrfmt:: %pTHD (struct event *) + + Print debugging information for given event. Sample output: + + .. code-block:: none + + {(thread *)NULL} + {(thread *)0x55a3b5818910 arg=0x55a3b5827c50 timer r=7.824 mld_t_query() &mld_ifp->t_query from pimd/pim6_mld.c:1369} + {(thread *)0x55a3b5827230 arg=0x55a3b5827c50 read fd=16 mld_t_recv() &mld_ifp->t_recv from pimd/pim6_mld.c:1186} + + (The output is aligned to some degree.) + +FRR daemon specific formats +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following formats are only available in specific daemons, as the code +implementing them is part of the daemon, not the library. + +zebra +""""" + +.. frrfmt:: %pZN (struct route_node *) + + Print information for a RIB node, including zebra-specific data. + + :frrfmtout:`::/0 src fe80::/64 (MRIB)` (``%pZN``) + + :frrfmtout:`1234` (``%pZNt`` - table number) + +bgpd +"""" + +.. frrfmt:: %pBD (struct bgp_dest *) + + Print prefix for a BGP destination. When using ``--enable-dev-build`` include + the pointer value for the bgp_dest. + + :frrfmtout:`fe80::1234/64` + +.. frrfmt:: %pBP (struct peer *) + + :frrfmtout:`192.168.1.1(leaf1.frrouting.org)` + + Print BGP peer's IP and hostname together. + +pimd/pim6d +"""""""""" + +.. frrfmt:: %pPA (pim_addr *) + + Format IP address according to IP version (pimd vs. pim6d) being compiled. + + :frrfmtout:`fe80::1234` / :frrfmtout:`10.0.0.1` + + :frrfmtout:`*` (``%pPAs`` - replace 0.0.0.0/:: with star) + +.. frrfmt:: %pSG (pim_sgaddr *) + + Format S,G pair according to IP version (pimd vs. pim6d) being compiled. + Braces are included. + + :frrfmtout:`(*,224.0.0.0)` + + +General utility formats +^^^^^^^^^^^^^^^^^^^^^^^ + +.. frrfmt:: %m (no argument) + + :frrfmtout:`Permission denied` + + Prints ``strerror(errno)``. Does **not** consume any input argument, don't + pass ``errno``! + + (This is a GNU extension not specific to FRR. FRR guarantees it is + available on all systems in printfrr, though BSDs support it in printf too.) + +.. frrfmt:: %pSQ (char *) + + ([S]tring [Q]uote.) Like ``%s``, but produce a quoted string. Options: + + ``n`` - treat ``NULL`` as empty string instead. + + ``q`` - include ``""`` quotation marks. Note: ``NULL`` is printed as + ``(null)``, not ``"(null)"`` unless ``n`` is used too. This is + intentional. + + ``s`` - use escaping suitable for RFC5424 syslog. This means ``]`` is + escaped too. + + If a length is specified (``%*pSQ`` or ``%.*pSQ``), null bytes in the input + string do not end the string and are just printed as ``\x00``. + +.. frrfmt:: %pSE (char *) + + ([S]tring [E]scape.) Like ``%s``, but escape special characters. + Options: + + ``n`` - treat ``NULL`` as empty string instead. + + Unlike :frrfmt:`%pSQ`, this escapes many more characters that are fine for + a quoted string but not on their own. + + If a length is specified (``%*pSE`` or ``%.*pSE``), null bytes in the input + string do not end the string and are just printed as ``\x00``. + +.. frrfmt:: %pVA (struct va_format *) + + Recursively invoke printfrr, with arguments passed in through: + + .. c:struct:: va_format + + .. c:member:: const char *fmt + + Format string to use for the recursive printfrr call. + + .. c:member:: va_list *va + + Formatting arguments. Note this is passed as a pointer, not - as in + most other places - a direct struct reference. Internally uses + ``va_copy()`` so repeated calls can be made (e.g. for determining + output length.) + +.. frrfmt:: %pFB (struct fbuf *) + + Insert text from a ``struct fbuf *``, i.e. the output of a call to + :c:func:`bprintfrr()`. + +.. frrfmt:: %*pHX (void *, char *, unsigned char *) + + ``%pHX``: :frrfmtout:`12 34 56 78` + + ``%pHXc``: :frrfmtout:`12:34:56:78` (separate with [c]olon) + + ``%pHXn``: :frrfmtout:`12345678` (separate with [n]othing) + + Insert hexdump. This specifier requires a precision or width to be + specified. A precision (``%.*pHX``) takes precedence, but generates a + compiler warning since precisions are undefined for ``%p`` in ISO C. If + no precision is given, the width is used instead (and normal handling of + the width is suppressed). + + Note that width and precision are ``int`` arguments, not ``size_t``. Use + like:: + + char *buf; + size_t len; + + snprintfrr(out, sizeof(out), "... %*pHX ...", (int)len, buf); + + /* with padding to width - would generate a warning due to %.*p */ + FMT_NSTD(snprintfrr(out, sizeof(out), "... %-47.*pHX ...", (int)len, buf)); + +.. frrfmt:: %*pHS (void *, char *, unsigned char *) + + ``%pHS``: :frrfmtout:`hex.dump` + + This is a complementary format for :frrfmt:`%*pHX` to print the text + representation for a hexdump. Non-printable characters are replaced with + a dot. + +.. frrfmt:: %pIS (struct iso_address *) + + ([IS]o Network address) - Format ISO Network Address + + ``%pIS``: :frrfmtout:`01.0203.04O5` + ISO Network address is printed as separated byte. The number of byte of the + address is embeded in the `iso_net` structure. + + ``%pISl``: :frrfmtout:`01.0203.04O5.0607.0809.1011.1213.14` - long format to + print the long version of the ISO Network address which include the System + ID and the PSEUDO-ID of the IS-IS system + + Note that the `ISO_ADDR_STRLEN` define gives the total size of the string + that could be used in conjunction to snprintfrr. Use like:: + + char buf[ISO_ADDR_STRLEN]; + struct iso_address addr = {.addr_len = 4, .area_addr = {1, 2, 3, 4}}; + snprintfrr(buf, ISO_ADDR_STRLEN, "%pIS", &addr); + +.. frrfmt:: %pSY (uint8_t *) + + (IS-IS [SY]stem ID) - Format IS-IS System ID + + ``%pSY``: :frrfmtout:`0102.0304.0506` + +.. frrfmt:: %pPN (uint8_t *) + + (IS-IS [P]seudo [N]ode System ID) - Format IS-IS Pseudo Node System ID + + ``%pPN``: :frrfmtout:`0102.0304.0506.07` + +.. frrfmt:: %pLS (uint8_t *) + + (IS-IS [L]sp fragment [S]ystem ID) - Format IS-IS Pseudo System ID + + ``%pLS``: :frrfmtout:`0102.0304.0506.07-08` + + Note that the `ISO_SYSID_STRLEN` define gives the total size of the string + that could be used in conjunction to snprintfrr. Use like:: + + char buf[ISO_SYSID_STRLEN]; + uint8_t id[8] = {1, 2, 3, 4 , 5 , 6 , 7, 8}; + snprintfrr(buf, SYS_ID_SIZE, "%pSY", id); + + +Integer formats +^^^^^^^^^^^^^^^ + +.. note:: + + These formats currently only exist for advanced type checking with the + ``frr-format`` GCC plugin. They should not be used directly since they will + cause compiler warnings when used without the plugin. Use with + :c:macro:`FMT_NSTD` if necessary. + + As anticipated, ISO C23 has introduced new modifiers for this, specifically + ``%w64d`` (= ``%Ld``) and ``%w64u`` (= ``%Lu``). Unfortunately, these new + modifiers are not supported by ``frr-format`` yet. + +.. frrfmt:: %Lu (uint64_t) + + :frrfmtout:`12345` + +.. frrfmt:: %Ld (int64_t) + + :frrfmtout:`-12345` + +Log levels +---------- + +Errors and warnings +^^^^^^^^^^^^^^^^^^^ + +If it is something that the user will want to look at and maybe do +something, it is either an **error** or a **warning**. + +We're expecting that warnings and errors are in some way visible to the +user (in the worst case by looking at the log after the network broke, but +maybe by a syslog collector from all routers.) Therefore, anything that +needs to get the user in the loop—and only these things—are warnings or +errors. + +Note that this doesn't necessarily mean the user needs to fix something in +the FRR instance. It also includes when we detect something else needs +fixing, for example another router, the system we're running on, or the +configuration. The common point is that the user should probably do +*something*. + +Deciding between a warning and an error is slightly less obvious; the rule +of thumb here is that an error will cause considerable fallout beyond its +direct effect. Closing a BGP session due to a malformed update is an error +since all routes from the peer are dropped; discarding one route because +its attributes don't make sense is a warning. + +This also loosely corresponds to the kind of reaction we're expecting from +the user. An error is likely to need immediate response while a warning +might be snoozed for a bit and addressed as part of general maintenance. +If a problem will self-repair (e.g. by retransmits), it should be a +warning—unless the impact until that self-repair is very harsh. + +Examples for warnings: + +* a BGP update, LSA or LSP could not be processed, but operation is + proceeding and the broken pieces are likely to self-fix later +* some kind of controller cannot be reached, but we can work without it +* another router is using some unknown or unsupported capability + +Examples for errors: + +* dropping a BGP session due to malformed data +* a socket for routing protocol operation cannot be opened +* desynchronization from network state because something went wrong +* *everything that we as developers would really like to be notified about, + i.e. some assumption in the code isn't holding up* + + +Informational messages +^^^^^^^^^^^^^^^^^^^^^^ + +Anything that provides introspection to the user during normal operation +is an **info** message. + +This includes all kinds of operational state transitions and events, +especially if they might be interesting to the user during the course of +figuring out a warning or an error. + +By itself, these messages should mostly be statements of fact. They might +indicate the order and relationship in which things happened. Also covered +are conditions that might be "operational issues" like a link failure due +to an unplugged cable. If it's pretty much the point of running a routing +daemon for, it's not a warning or an error, just business as usual. + +The user should be able to see the state of these bits from operational +state output, i.e. `show interface` or `show foobar neighbors`. The log +message indicating the change may have been printed weeks ago, but the +state can always be viewed. (If some state change has an info message but +no "show" command, maybe that command needs to be added.) + +Examples: + +* all kinds of up/down state changes + + * interface coming up or going down + * addresses being added or deleted + * peers and neighbors coming up or going down + +* rejection of some routes due to user-configured route maps +* backwards compatibility handling because another system on the network + has a different or smaller feature set + +.. note:: + The previously used **notify** priority is replaced with *info* in all + cases. We don't currently have a well-defined use case for it. + + +Debug messages and asserts +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Everything that is only interesting on-demand, or only while developing, +is a **debug** message. It might be interesting to the user for a +particularly evasive issue, but in general these are details that an +average user might not even be able to make sense of. + +Most (or all?) debug messages should be behind a `debug foobar` category +switch that controls which subset of these messages is currently +interesting and thus printed. If a debug message doesn't have such a +guard, there should be a good explanation as to why. + +Conversely, debug messages are the only thing that should be guarded by +these switches. Neither info nor warning or error messages should be +hidden in this way. + +**Asserts** should only be used as pretty crashes. We are expecting that +asserts remain enabled in production builds, but please try to not use +asserts in a way that would cause a security problem if the assert wasn't +there (i.e. don't use them for length checks.) + +The purpose of asserts is mainly to help development and bug hunting. If +the daemon crashes, then having some more information is nice, and the +assert can provide crucial hints that cut down on the time needed to track +an issue. That said, if the issue can be reasonably handled and/or isn't +going to crash the daemon, it shouldn't be an assert. + +For anything else where internal constraints are violated but we're not +breaking due to it, it's an error instead (not a debug.) These require +"user action" of notifying the developers. + +Examples: + +* mismatched :code:`prev`/:code:`next` pointers in lists +* some field that is absolutely needed is :code:`NULL` +* any other kind of data structure corruption that will cause the daemon + to crash sooner or later, one way or another + +Thread-local buffering +---------------------- + +The core logging code in :file:`lib/zlog.c` allows setting up per-thread log +message buffers in order to improve logging performance. The following rules +apply for this buffering: + +* Only messages of priority *DEBUG* or *INFO* are buffered. +* Any higher-priority message causes the thread's entire buffer to be flushed, + thus message ordering is preserved on a per-thread level. +* There is no guarantee on ordering between different threads; in most cases + this is arbitrary to begin with since the threads essentially race each + other in printing log messages. If an order is established with some + synchronization primitive, add calls to :c:func:`zlog_tls_buffer_flush()`. +* The buffers are only ever accessed by the thread they are created by. This + means no locking is necessary. + +Both the main/default thread and additional threads created by +:c:func:`frr_pthread_new()` with the default :c:func:`frr_run()` handler will +initialize thread-local buffering and call :c:func:`zlog_tls_buffer_flush()` +when idle. + +If some piece of code runs for an extended period, it may be useful to insert +calls to :c:func:`zlog_tls_buffer_flush()` in appropriate places: + +.. c:function:: void zlog_tls_buffer_flush(void) + + Write out any pending log messages that the calling thread may have in its + buffer. This function is safe to call regardless of the per-thread log + buffer being set up / in use or not. + +When working with threads that do not use the :c:struct:`thread_master` +event loop, per-thread buffers can be managed with: + +.. c:function:: void zlog_tls_buffer_init(void) + + Set up thread-local buffering for log messages. This function may be + called repeatedly without adverse effects, but remember to call + :c:func:`zlog_tls_buffer_fini()` at thread exit. + + .. warning:: + + If this function is called, but :c:func:`zlog_tls_buffer_flush()` is + not used, log message output will lag behind since messages will only be + written out when the buffer is full. + + Exiting the thread without calling :c:func:`zlog_tls_buffer_fini()` + will cause buffered log messages to be lost. + +.. c:function:: void zlog_tls_buffer_fini(void) + + Flush pending messages and tear down thread-local log message buffering. + This function may be called repeatedly regardless of whether + :c:func:`zlog_tls_buffer_init()` was ever called. + +Log targets +----------- + +The actual logging subsystem (in :file:`lib/zlog.c`) is heavily separated +from the actual log writers. It uses an atomic linked-list (`zlog_targets`) +with RCU to maintain the log targets to be called. This list is intended to +function as "backend" only, it **is not used for configuration**. + +Logging targets provide their configuration layer on top of this and maintain +their own capability to enumerate and store their configuration. Some targets +(e.g. syslog) are inherently single instance and just stuff their config in +global variables. Others (e.g. file/fd output) are multi-instance capable. +There is another layer boundary here between these and the VTY configuration +that they use. + +Basic internals +^^^^^^^^^^^^^^^ + +.. c:struct:: zlog_target + + This struct needs to be filled in by any log target and then passed to + :c:func:`zlog_target_replace()`. After it has been registered, + **RCU semantics apply**. Most changes to associated data should make a + copy, change that, and then replace the entire struct. + + Additional per-target data should be "appended" by embedding this struct + into a larger one, for use with `containerof()`, and + :c:func:`zlog_target_clone()` and :c:func:`zlog_target_free()` should be + used to allocate/free the entire container struct. + + Do not use this structure to maintain configuration. It should only + contain (a copy of) the data needed to perform the actual logging. For + example, the syslog target uses this: + + .. code-block:: c + + struct zlt_syslog { + struct zlog_target zt; + int syslog_facility; + }; + + static void zlog_syslog(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsgs) + { + struct zlt_syslog *zte = container_of(zt, struct zlt_syslog, zt); + size_t i; + + for (i = 0; i < nmsgs; i++) + if (zlog_msg_prio(msgs[i]) <= zt->prio_min) + syslog(zlog_msg_prio(msgs[i]) | zte->syslog_facility, "%s", + zlog_msg_text(msgs[i], NULL)); + } + + +.. c:function:: struct zlog_target *zlog_target_clone(struct memtype *mt, struct zlog_target *oldzt, size_t size) + + Allocates a logging target struct. Note that the ``oldzt`` argument may be + ``NULL`` to allocate a "from scratch". If ``oldzt`` is not ``NULL``, the + generic bits in :c:struct:`zlog_target` are copied. **Target specific + bits are not copied.** + +.. c:function:: struct zlog_target *zlog_target_replace(struct zlog_target *oldzt, struct zlog_target *newzt) + + Adds, replaces or deletes a logging target (either ``oldzt`` or ``newzt`` may be ``NULL``.) + + Returns ``oldzt`` for freeing. The target remains possibly in use by + other threads until the RCU cycle ends. This implies you cannot release + resources (e.g. memory, file descriptors) immediately. + + The replace operation is not atomic; for a brief period it is possible that + messages are delivered on both ``oldzt`` and ``newzt``. + + .. warning:: + + ``oldzt`` must remain **functional** until the RCU cycle ends. + +.. c:function:: void zlog_target_free(struct memtype *mt, struct zlog_target *zt) + + Counterpart to :c:func:`zlog_target_clone()`, frees a target (using RCU.) + +.. c:member:: void (*zlog_target.logfn)(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsg) + + Called on a target to deliver "normal" logging messages. ``msgs`` is an + array of opaque structs containing the actual message. Use ``zlog_msg_*`` + functions to access message data (this is done to allow some optimizations, + e.g. lazy formatting the message text and timestamp as needed.) + + .. note:: + + ``logfn()`` must check each individual message's priority value against + the configured ``prio_min``. While the ``prio_min`` field is common to + all targets and used by the core logging code to early-drop unneeded log + messages, the array is **not** filtered for each ``logfn()`` call. + +.. c:member:: void (*zlog_target.logfn_sigsafe)(struct zlog_target *zt, const char *text, size_t len) + + Called to deliver "exception" logging messages (i.e. SEGV messages.) + Must be Async-Signal-Safe (may not allocate memory or call "complicated" + libc functions.) May be ``NULL`` if the log target cannot handle this. + +Standard targets +^^^^^^^^^^^^^^^^ + +:file:`lib/zlog_targets.c` provides the standard file / fd / syslog targets. +The syslog target is single-instance while file / fd targets can be +instantiated as needed. There are 3 built-in targets that are fully +autonomous without any config: + +- startup logging to `stderr`, until either :c:func:`zlog_startup_end()` or + :c:func:`zlog_aux_init()` is called. +- stdout logging for non-daemon programs using :c:func:`zlog_aux_init()` +- crashlogs written to :file:`/var/tmp/frr.daemon.crashlog` + +The regular CLI/command-line logging setup is handled by :file:`lib/log_vty.c` +which makes the appropriate instantiations of syslog / file / fd targets. + +.. todo:: + + :c:func:`zlog_startup_end()` should do an explicit switchover from + startup stderr logging to configured logging. Currently, configured logging + starts in parallel as soon as the respective setup is executed. This results + in some duplicate logging. diff --git a/doc/developer/memtypes.rst b/doc/developer/memtypes.rst new file mode 100644 index 0000000..2e181c4 --- /dev/null +++ b/doc/developer/memtypes.rst @@ -0,0 +1,140 @@ +.. highlight:: c + +Memtypes +======== + +FRR includes wrappers around ``malloc()`` and ``free()`` that count the number +of objects currently allocated, for each of a defined ``MTYPE``. + +To this extent, there are *memory groups* and *memory types*. Each memory +type must belong to a memory group, this is used just to provide some basic +structure. + +Example: + +.. code-block:: c + :caption: mydaemon.h + + DECLARE_MGROUP(MYDAEMON); + DECLARE_MTYPE(MYNEIGHBOR); + +.. code-block:: c + :caption: mydaemon.c + + DEFINE_MGROUP( MYDAEMON, "My daemon's memory"); + DEFINE_MTYPE( MYDAEMON, MYNEIGHBOR, "Neighbor entry"); + DEFINE_MTYPE_STATIC(MYDAEMON, MYNEIGHBORNAME, "Neighbor name"); + + struct neigh *neighbor_new(const char *name) + { + struct neigh *n = XMALLOC(MYNEIGHBOR, sizeof(*n)); + n->name = XSTRDUP(MYNEIGHBORNAME, name); + return n; + } + + void neighbor_free(struct neigh *n) + { + XFREE(MYNEIGHBORNAME, n->name); + XFREE(MYNEIGHBOR, n); + } + + +Definition +---------- + +.. c:struct:: memtype + + This is the (internal) type used for MTYPE definitions. The macros below + should be used to create these, but in some cases it is useful to pass a + ``struct memtype *`` pointer to some helper function. + + The ``MTYPE_name`` created by the macros is declared as a pointer, i.e. + a function taking a ``struct memtype *`` argument can be called with an + ``MTYPE_name`` argument (as opposed to ``&MTYPE_name``.) + + .. note:: + + As ``MTYPE_name`` is a variable assigned from ``&_mt_name`` and not a + constant expression, it cannot be used as initializer for static + variables. In the case please fall back to ``&_mt_name``. + +.. c:macro:: DECLARE_MGROUP(name) + + This macro forward-declares a memory group and should be placed in a + ``.h`` file. It expands to an ``extern struct memgroup`` statement. + +.. c:macro:: DEFINE_MGROUP(mname, description) + + Defines/implements a memory group. Must be placed into exactly one ``.c`` + file (multiple inclusion will result in a link-time symbol conflict). + + Contains additional logic (constructor and destructor) to register the + memory group in a global list. + +.. c:macro:: DECLARE_MTYPE(name) + + Forward-declares a memory type and makes ``MTYPE_name`` available for use. + Note that the ``MTYPE_`` prefix must not be included in the name, it is + automatically prefixed. + + ``MTYPE_name`` is created as a `static const` symbol, i.e. a compile-time + constant. It refers to an ``extern struct memtype _mt_name``, where `name` + is replaced with the actual name. + +.. c:macro:: DEFINE_MTYPE(group, name, description) + + Define/implement a memory type, must be placed into exactly one ``.c`` + file (multiple inclusion will result in a link-time symbol conflict). + + Like ``DEFINE_MGROUP``, this contains actual code to register the MTYPE + under its group. + +.. c:macro:: DEFINE_MTYPE_STATIC(group, name, description) + + Same as ``DEFINE_MTYPE``, but the ``DEFINE_MTYPE_STATIC`` variant places + the C ``static`` keyword on the definition, restricting the MTYPE's + availability to the current source file. This should be appropriate in + >80% of cases. + + .. todo:: + + Daemons currently have ``daemon_memory.[ch]`` files listing all of + their MTYPEs. This is not how it should be, most of these types + should be moved into the appropriate files where they are used. + Only a few MTYPEs should remain non-static after that. + + +Usage +----- + +.. c:function:: void *XMALLOC(struct memtype *mtype, size_t size) + +.. c:function:: void *XCALLOC(struct memtype *mtype, size_t size) + +.. c:function:: void *XSTRDUP(struct memtype *mtype, const char *name) + + Allocation wrappers for malloc/calloc/realloc/strdup, taking an extra + mtype parameter. + +.. c:function:: void *XREALLOC(struct memtype *mtype, void *ptr, size_t size) + + Wrapper around realloc() with MTYPE tracking. Note that ``ptr`` may + be NULL, in which case the function does the same as XMALLOC (regardless + of whether the system realloc() supports this.) + +.. c:function:: void XFREE(struct memtype *mtype, void *ptr) + + Wrapper around free(), again taking an extra mtype parameter. This is + actually a macro, with the following additional properties: + + - the macro contains ``ptr = NULL`` + - if ptr is NULL, no operation is performed (as is guaranteed by system + implementations.) Do not surround XFREE with ``if (ptr != NULL)`` + checks. + +.. c:function:: void XCOUNTFREE(struct memtype *mtype, void *ptr) + + This macro is used to count the ``ptr`` as freed without actually freeing + it. This may be needed in some very specific cases, for example, when the + ``ptr`` was allocated using any of the above wrappers and will be freed + by some external library using simple ``free()``. diff --git a/doc/developer/mgmtd-dev.rst b/doc/developer/mgmtd-dev.rst new file mode 100644 index 0000000..b979af0 --- /dev/null +++ b/doc/developer/mgmtd-dev.rst @@ -0,0 +1,426 @@ +.. +.. SPDX-License-Identifier: GPL-2.0-or-later +.. +.. June 19 2023, Christian Hopps <chopps@labn.net> +.. +.. Copyright (c) 2023, LabN Consulting, L.L.C. +.. + +.. _mgmtd_dev: + +MGMTD Development +================= + +Overview +-------- + +``mgmtd`` (Management Daemon) is a new centralized management daemon for FRR. + +Previously, ``vtysh`` was the only centralized management service provided. +Internally ``vtysh`` connects to each daemon and sends CLI commands (both +configuration and operational state queries) over a socket connection. This +service only supports CLI which is no longer sufficient. + +An important next step was made with the addition of YANG support. A YANG +infrastructure was added through a new development called *northbound*. This +*northbound* interface added the capability of daemons to be configured and +queried using YANG models. However, this interface was per daemon and not +centralized, which is not sufficient. + +``mgmtd`` harnesses this new *northbound* interface to provide a centralized +interface for all daemons. It utilizes the daemons YANG models to interact with +each daemon. ``mgmtd`` currently provides the CLI interface for each daemon that +has been converted to it, but in the future RESTCONF and NETCONF servers can +easily be added as *front-ends* to mgmtd to support those protocols as well. + +Conversion Status +^^^^^^^^^^^^^^^^^ + +Fully Converted To MGMTD +"""""""""""""""""""""""" + +- lib/distribute +- lib/filter +- lib/if_rmap +- lib/routemap +- lib/affinitymap +- lib/if +- lib/vrf +- ripd +- ripngd +- staticd +- zebra (* - partial) + +Converted To Northbound +""""""""""""""""""""""" +- bfdd +- pathd +- pbrd +- pimd + +Converted To Northbound With Issues +""""""""""""""""""""""""""""""""""" +- eigrp +- isisd + +Unconverted +""""""""""" +- babel +- bgpd +- ldpd +- lib/event +- lib/keychain +- lib/log_vty +- lib/nexthop_group +- lib/zlog_5424_cli +- nhrpd +- ospfd +- ospf6d +- pceplib +- qdb +- sharpd +- vrrpd + +Converting A Daemon to MGMTD +---------------------------- + +A daemon must first be transitioned to the new :ref:`northbound` interface if that +has not already been done (see :ref:`nb-retrofit` for how to do this). Once this +is done a few simple steps are all that is required move the daemon over to +``mgmtd`` control. + +Overview of Changes +^^^^^^^^^^^^^^^^^^^ + +Adding support for a *northbound* converted daemon involves very little work. It +requires enabling *frontend* (CLI and YANG) and *backend* (YANG) support. +``mgmtd`` was designed to keep this as simple as possible. + +Front-End Interface: + +#. Add YANG module file to ``mgmtd/subdir.am`` (e.g., ``yang/frr-staticd.yang.c``). + +#. Add CLI handler file[s] to ``mgmtd/subdir.am``. The `subdir.am` variable to + use is indicated in the next 2 steps. + + #. [if needed] Exclude (:code:`#ifndef`) non-configuration CLI handlers from + CLI source file (e.g., inside :file:`staticd/static_vty.c`) and add the + file to :code:`nodist_mgmtd_libmgmt_be_nb_la_SOURCES` in + :file:`mgmtd/subdir.am`. + + #. [otherwise] Remove CLI handler file from _SOURCES variable in the daemon + :file:`subdir.am` file (e.g in :file:`staticd/subdir.am`) and add to + :code:`mgmtd_libmgmtd_a_SOURCES` in :file:`mgmtd/subdir.am`. + +#. In order to have mgmtd try and load existing per-daemon config files, add + the daemon to the :code:`mgmt_daemons` array in :file:`lib/vty.c`. With the + official release of the mgmtd code FRR is no longer supporting per daemon log + files but it will take a while before all of the topotest is converted. + +#. In the daemon's :code:`struct frr_daemon_info` (i.e., inside it's + :code:`FRR_DAEMON_INFO()`) set the `.flags` bit `FRR_NO_SPLIT_CONFIG`. This + will keep the daemon from trying to read it's per-daemon config file as mgmtd + will now be doing this. + +#. Add the daemon's YANG module description[s] into the array + :code:`mgmt_yang_modules` defined in :file:`mgmtd/mgmt_main.c` (see + :ref:`mgmtd-config-write`). Make sure that all YANG modules that the daemon + uses are present in the mgmtd list. To find this list look in the daemon's + equivalent yang module array variable. + +#. Initialize the CLI handlers inside :code:`mgmt_vty_init` in :file:`mgmtd/mgmt_vty.c`. + +#. Direct ``vtysh`` to send CLI commands to ``mgmtd`` by modifying + ``vtysh/vtysh.h``. At the top of this file each daemon has a bit + ``#define``'d (e.g., ``#define VTYSH_STATICD 0x08000``) below this there are + groupings, replace all the uses of the daemons bit with ``VTYSH_MGMTD`` + instead so that the CLI commands get properly routed to ``mgmtd`` rather than + the daemon now. + + #. Remove initialization (and installation) of library CLI routines. These will + correspond with the VTYSH removals from the last step i.e.,: + + - change access_list_init() to access_list_init_new(false) and remove from + VTYSH_ACL_CONFIG (leave in VTYSH_ACL_SHOW). + - remove if_cmd_init_default() => remove from VTYSH_INTERFACE_SUBSET + - remove if_cmd_init() => remove from VTYSH_INTERFACE_SUBSET + - change route_map_init() to route_map_init_new(false) and remove from + VTYSH_ROUTE_MAP_CONFIG (leave in VTYSH_ROUTE_MAP_SHOW). + - remove vrf_cmd_init(NULL) => remove from VTYSH_INTERFACE_SUBSET + ... + +Back-End Interface: + +#. In the daemon's main file initialize the BE client library. You add a global + `struct mgmt_be_client *mgmt_be_client` near the daemons `event_loop *master` + variable. Then where the daemon used to initialize it's CLI/VTY code replace + that with the client initialization by calling `mgmt_be_client_create`. + Likewise in the daemon's sigint cleanup code, operational walks should be + canceled with a call to `nb_oper_cancel_all_walks`, and then the BE client + should be destroyed with a call to `mgmt_be_client_destroy` and to be safe + NULL out the global `mgmt_be_client` variable. + +#. In ``mgmtd/mgmt_be_adapter.c`` add xpath prefix mappings to a one or both + mapping arrays (``be_client_config_xpaths`` and ``be_client_oper_xpaths``) to + direct ``mgmtd`` to send config and oper-state requests to your daemon. NOTE: + make sure to include library supported xpaths prefixes as well (e.g., + "/frr-interface:lib"). A good way to figure these paths out are to look in + each of the YANG modules that the daemon uses and include each of their paths + in the array. + +Add YANG and CLI into MGMTD +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As an example here is the addition made to ``mgmtd/subdir.am`` for adding +``staticd`` support. + +.. code-block:: make + + if STATICD + nodist_mgmtd_mgmtd_SOURCES += \ + yang/frr-staticd.yang.c \ + yang/frr-bfdd.yang.c \ + # end + nodist_mgmtd_libmgmt_be_nb_la_SOURCES += staticd/static_vty.c + endif + +An here is the addition to the modules array in ``mgmtd/mgmt_main.c``: + +.. code-block:: c + + #ifdef HAVE_STATICD + extern const struct frr_yang_module_info frr_staticd_info; + #endif + + static const struct frr_yang_module_info *const mgmt_yang_modules[] = { + &frr_filter_info, + ... + #ifdef HAVE_STATICD + &frr_staticd_info, + #endif + } + + +CLI Config and Show Handlers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The daemon's CLI handlers for configuration (which having been converted to the +:ref:`northbound` now simply generate YANG changes) will be linked directly into +``mgmtd``. + +If the operational and debug CLI commands are kept in files separate from the +daemon's configuration CLI commands then no extra work is required. Otherwise some +CPP #ifndef's will be required. + +``mgmtd`` supports both config and operational state. However, many +daemons have not had their operational state CLI commands converted over to the +new YANG based methods. If that is the case and if both types of CLI handlers +are present in a single file (e.g. a ``xxx_vty.c`` or ``xxx_cli.c`` file) then +:code:`#ifndef` will need to be used to exclude the non-config CLI handlers from +``mgmtd``. The same goes for unconverted *debug* CLI handlers. For example: + +.. code-block:: c + + DEFPY(daemon_one_config, daemon_one_config_cmd, + "daemon one [optional-arg]" + ... + { + ... + } + + #ifndef INCLUDE_MGMTD_CMDDEFS_ONLY + DEFPY(daemon_show_oper, daemon_show_oper_cmd, + "show daemon oper [all]" + ... + { + ... + } + #endif /* ifndef INCLUDE_MGMTD_CMDDEFS_ONLY */ + + void daemon_vty_init(void) + { + install_element(CONFIG_NODE, &daemon_one_config_cmd); + ... + + #ifndef INCLUDE_MGMTD_CMDDEFS_ONLY + install_element(ENABLE_NODE, &daemon_show_oper_cmd); + #endif /* ifndef INCLUDE_MGMTD_CMDDEFS_ONLY */ + + } + +.. _mgmtd-config-write: + +CLI Config Write Handlers (:code:`cli_show`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To support writing out the CLI configuration file the northbound API defines a +2 callbacks (:code:`cli_show` and :code:`cli_show_end`). Pointers to these +callbacks used to live side-by-side in a daemons :code:`struct frr_yang_module_info`, +with the daemons back-end configuration and operational state callbacks +(normally in a file named `<daemon>_nb.c`). + +However, these 2 functionalities need to be split up now. The *frontend* config +writing callbacks (:code:`cli_show`) should now be linked into ``mgmtd`` while +the *backend* config and oper-state callbacks (e.g., :code:`create`, +:code:`modify`, etc) should continue to be linked into the daemon. + +So you will need to define 2 :code:`struct frr_yang_module_info` arrays. + +#. The existing array remains in the same place in the daemon, but with all the + :code:`cli_show` handlers removed. + +#. The removed :code:`cli_show` handlers should be added to a new + :code:`struct frr_yang_module_info` array. This second array should be + included in the same file that includes that actual function pointed to by + the the :code:`cli_show` callbacks (i.e., the file is compiled into + ``mgmtd``). + + This new :code:`struct frr_yang_module_info` array is the one to be included + in mgmtd in `mgmt_yang_modules` inside ``mgmtd/mgmt_main.c``. + +Back-End Client Connection +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order for your daemon to communicate with mgmtd you need to initialize the +backend client library. You normally do this where you used to initialize your +CLI/VTY code. + +.. code-block:: c + + ... + struct event_loop *master; + + static struct mgmt_be_client *mgmt_be_client; + ... + + int main(int argc, char **argv) + { + ... + rip_init(); + rip_if_init(); + mgmt_be_client = mgmt_be_client_create("ripd", NULL, 0, master); + +Likewise the client should be cleaned up in the daemon cleanup routine. + +.. code-block:: c + + /* SIGINT handler. */ + static void sigint(void) + { + zlog_notice("Terminating on signal"); + ... + nb_oper_cancel_all_walks(); + mgmt_be_client_destroy(mgmt_be_client); + mgmt_be_client = NULL; + + +Back-End XPATH mappings +^^^^^^^^^^^^^^^^^^^^^^^ + +In order for ``mgmtd`` to direct YANG modeled data to your daemon you should add +some XPATH mappings to ``mgmtd/mgmt_be_adapter.c``. These XPATHs determine which +YANG modeled data (e.g., config changes) get sent over the *back-end* interface +to your daemon. There are 4 arrays to possibly update: configuration, +operational, notification, and RPC. You only need to add entries to the array +that you require mapping for. + +Additionally the back-end client can specify these XPATH mappings when it +first connects to mgmtd using it's initial ``SUBSCRIBE`` message. + +NOTE: the notif array (``be_client_notif_xpaths``), is a slightly different from +the other 3 types (config, oper and rpc) in that it maps xpaths the back-end +client wishes to *receive* notifications for, not the ones it may generate. +Normally a back-end client is generating notifications; however, mgmtd supports +back-end clients also "subscribing" to receive these notifications as well from +other back-end clients through notif_xpath maps. + +Config Map Example +"""""""""""""""""" +Below are the strings added for staticd config support: + +.. code-block:: c + + #if HAVE_STATICD + static const char *const staticd_xpaths[] = { + "/frr-vrf:lib", + "/frr-interface:lib", + "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd", + NULL, + }; + #endif + + static const char *const *be_client_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + #ifdef HAVE_STATICD + [MGMTD_BE_CLIENT_ID_STATICD] = staticd_xpaths, + #endif + }; + + +Operational Map Example +""""""""""""""""""""""" +Below are the strings added for zebra operational state support (note zebra is +not conditionalized b/c it should always be present): + +.. code-block:: c + + static const char *const zebra_oper_xpaths[] = { + "/frr-interface:lib/interface", + "/frr-vrf:lib/vrf/frr-zebra:zebra", + "/frr-zebra:zebra", + NULL, + }; + + static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths, + }; + + +RPC Map Example +""""""""""""""" +Below is the string added for ripd RPC support: + +.. code-block:: c + + static const char *const ripd_rpc_xpaths[] = { + "/frr-ripd", + NULL, + }; + + static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + #ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = ripd_rpc_xpaths, + #endif + }; + + +Notification Map Example +"""""""""""""""""""""""" +There are no current back-end daemons that wish to receive other back-end +notifications so the array is empty. This may change in the future, and of +course any back-end daemon can utilize the connect (``BeSubscribeReq``) messages +as well. + + +MGMTD Internals +--------------- + +This section will describe the internal functioning of ``mgmtd``, for now a +couple diagrams are included to aide in source code perusal. + + +The client side of a CLI configuration change + +.. figure:: ../figures/cli-change-client.svg + :align: center + + +The server (mgmtd) side of a CLI configuration change + +.. figure:: ../figures/cli-change-mgmtd.svg + :align: center + + +The client and server sides of oper-state query + +.. figure:: ../figures/cli-oper-state.svg + :align: center diff --git a/doc/developer/modules.rst b/doc/developer/modules.rst new file mode 100644 index 0000000..0feac8e --- /dev/null +++ b/doc/developer/modules.rst @@ -0,0 +1,142 @@ +.. _modules: + +Modules +======= + +FRR has facilities to load DSOs at startup via ``dlopen()``. These are used to +implement modules, such as SNMP and FPM. + +Limitations +----------- + +- can't load, unload, or reload during runtime. This just needs some + work and can probably be done in the future. +- doesn't fix any of the "things need to be changed in the code in the + library" issues. Most prominently, you can't add a CLI node because + CLI nodes are listed in the library... +- if your module crashes, the daemon crashes. Should be obvious. +- **does not provide a stable API or ABI**. Your module must match a + version of FRR and you may have to update it frequently to match + changes. +- **does not create a license boundary**. Your module will need to link + libzebra and include header files from the daemons, meaning it will + be GPL-encumbered. + +Installation +------------ + +Look for ``moduledir`` in ``configure.ac``, default is normally +``/usr/lib64/frr/modules`` but depends on ``--libdir`` / ``--prefix``. + +The daemon's name is prepended when looking for a module, e.g. "snmp" +tries to find "zebra\_snmp" first when used in zebra. This is just to +make it nicer for the user, with the snmp module having the same name +everywhere. + +Modules can be packaged separately from FRR. The SNMP and FPM modules +are good candidates for this because they have dependencies (net-snmp / +protobuf) that are not FRR dependencies. However, any distro packages +should have an "exact-match" dependency onto the FRR package. Using a +module from a different FRR version will probably blow up nicely. + +For snapcraft (and during development), modules can be loaded with full +path (e.g. -M ``$SNAP/lib/frr/modules/zebra_snmp.so``). Note that +libtool puts output files in the .libs directory, so during development +you have to use ``./zebra -M .libs/zebra_snmp.so``. + +Creating a module +----------------- + +... best to look at the existing SNMP or FPM modules. + +Basic boilerplate: + +:: + + #include "hook.h" + #include "module.h" + #include "libfrr.h" + #include "frrevent.h" + + static int module_late_init(struct event_loop *master) + { + /* Do initialization stuff here */ + return 0; + } + + static int + module_init (void) + { + hook_register(frr_late_init, module_late_init); + return 0; + } + + FRR_MODULE_SETUP( + .name = "my module", + .version = "0.0", + .description = "my module", + .init = module_init, + ); + +The ``frr_late_init`` hook will be called after the daemon has finished +its other startup and is about to enter the main event loop; this is the +best place for most initialisation. + +Compiler & Linker magic +----------------------- + +There's a ``THIS_MODULE`` (like in the Linux kernel), which uses +``visibility`` attributes to restrict it to the current module. If you +get a linker error with ``_frrmod_this_module``, there is some linker +SNAFU. This shouldn't be possible, though one way to get it would be to +not include libzebra (which provides a fallback definition for the +symbol). + +libzebra and the daemons each have their own ``THIS_MODULE``, as do all +loadable modules. In any other libraries (e.g. ``libfrrsnmp``), +``THIS_MODULE`` will use the definition in libzebra; same applies if the +main executable doesn't use ``FRR_DAEMON_INFO`` (e.g. all testcases). + +The deciding factor here is "what dynamic linker unit are you using the +symbol from." If you're in a library function and want to know who +called you, you can't use ``THIS_MODULE`` (because that'll just tell you +you're in the library). Put a macro around your function that adds +``THIS_MODULE`` in the *caller's code calling your function*. + +The idea is to use this in the future for module unloading. Hooks +already remember which module they were installed by, as groundwork for +a function that removes all of a module's installed hooks. + +There's also the ``frr_module`` symbol in modules, pretty much a +standard entry point for loadable modules. + +Command line parameters +----------------------- + +Command line parameters can be passed directly to a module by appending a +colon to the module name when loading it, e.g. ``-M mymodule:myparameter``. +The text after the colon will be accessible in the module's code through +``THIS_MODULE->load_args``. For example, see how the format parameter is +configured in the ``zfpm_init()`` function inside ``zebra_fpm.c``. + +Hooks +----- + +Hooks are just points in the code where you can register your callback +to be called. The parameter list is specific to the hook point. Since +there is no stable API, the hook code has some extra type safety checks +making sure you get a compiler warning when the hook parameter list +doesn't match your callback. Don't ignore these warnings. + +Relation to MTYPE macros +------------------------ + +The MTYPE macros, while primarily designed to decouple MTYPEs from the +library and beautify the code, also work very nicely with loadable +modules -- both constructors and destructors are executed when +loading/unloading modules. + +This means there is absolutely no change required to MTYPEs, you can +just use them in a module and they will even clean up themselves when we +implement module unloading and an unload happens. In fact, it's +impossible to create a bug where unloading fails to de-register a MTYPE. diff --git a/doc/developer/next-hop-tracking.rst b/doc/developer/next-hop-tracking.rst new file mode 100644 index 0000000..99e1d65 --- /dev/null +++ b/doc/developer/next-hop-tracking.rst @@ -0,0 +1,350 @@ +Next Hop Tracking +================== + +Next hop tracking is an optimization feature that reduces the processing time +involved in the BGP bestpath algorithm by monitoring changes to the routing +table. + +Background +----------- + +Recursive routes are of the form: + +:: + + p/m --> n + [Ex: 1.1.0.0/16 --> 2.2.2.2] + +where 'n' itself is resolved through another route as follows: + +:: + + p2/m --> h, interface + [Ex: 2.2.2.0/24 --> 3.3.3.3, eth0] + +Usually, BGP routes are recursive in nature and BGP nexthops get resolved +through an IGP route. IGP usually adds its routes pointing to an interface +(these are called non-recursive routes). + +When BGP receives a recursive route from a peer, it needs to validate the +nexthop. The path is marked valid or invalid based on the reachability status +of the nexthop. Nexthop validation is also important for BGP decision process +as the metric to reach the nexthop is a parameter to best path selection +process. + +As it goes with routing, this is a dynamic process. Route to the nexthop can +change. The nexthop can become unreachable or reachable. In the current BGP +implementation, the nexthop validation is done periodically in the scanner run. +The default scanner run interval is one minute. Every minute, the scanner task +walks the entire BGP table. It checks the validity of each nexthop with Zebra +(the routing table manager) through a request and response message exchange +between BGP and Zebra process. BGP process is blocked for that duration. The +mechanism has two major drawbacks: + +- The scanner task runs to completion. That can potentially starve the other + tasks for long periods of time, based on the BGP table size and number of + nexthops. + +- Convergence around routing changes that affect the nexthops can be long + (around a minute with the default intervals). The interval can be shortened + to achieve faster reaction time, but it makes the first problem worse, with + the scanner task consuming most of the CPU resources. + +The next-hop tracking feature makes this process event-driven. It eliminates +periodic nexthop validation and introduces an asynchronous communication path +between BGP and Zebra for route change notifications that can then be acted +upon. + +Goal +---- + +Stating the obvious, the main goal is to remove the two limitations we +discussed in the previous section. The goals, in a constructive tone, +are the following: + +- **Fairness**: the scanner run should not consume an unjustly high amount of + CPU time. This should give an overall good performance and response time to + other events (route changes, session events, IO/user interface). + +- **Convergence**: BGP must react to nexthop changes instantly and provide + sub-second convergence. This may involve diverting the routes from one + nexthop to another. + +Overview of changes +------------------------ + +The changes are in both BGP and Zebra modules. The short summary is +the following: + +- Zebra implements a registration mechanism by which clients can + register for next hop notification. Consequently, it maintains a + separate table, per (VRF, AF) pair, of next hops and interested + client-list per next hop. + +- When the main routing table changes in Zebra, it evaluates the next + hop table: for each next hop, it checks if the route table + modifications have changed its state. If so, it notifies the + interested clients. + +- BGP is one such client. It registers the next hops corresponding to + all of its received routes/paths. It also threads the paths against + each nexthop structure. + +- When BGP receives a next hop notification from Zebra, it walks the + corresponding path list. It makes them valid or invalid depending + on the next hop notification. It then re-computes best path for the + corresponding destination. This may result in re-announcing those + destinations to peers. + +Design +------ + +Modules +^^^^^^^ + +The core design introduces an "nht" (next hop tracking) module in BGP +and "rnh" (recursive nexthop) module in Zebra. The "nht" module +provides the following APIs: + ++----------------------------+--------------------------------------------------+ +| Function | Action | ++============================+==================================================+ +| bgp_find_or_add_nexthop() | find or add a nexthop in BGP nexthop table | ++----------------------------+--------------------------------------------------+ +| bgp_parse_nexthop_update() | parse a nexthop update message coming from zebra | ++----------------------------+--------------------------------------------------+ + +The "rnh" module provides the following APIs: + ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| Function | Action | ++============================+==========================================================================================================+ +| zebra_add_rnh() | add a recursive nexthop | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_delete_rnh() | delete a recursive nexthop | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_lookup_rnh() | lookup a recursive nexthop | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_add_rnh_client() | register a client for nexthop notifications against a recursive nexthop | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_remove_rnh_client() | remove the client registration for a recursive nexthop | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_evaluate_rnh_table() | (re)evaluate the recursive nexthop table (most probably because the main routing table has changed). | ++----------------------------+----------------------------------------------------------------------------------------------------------+ +| zebra_cleanup_rnh_client() | Cleanup a client from the "rnh" module data structures (most probably because the client is going away). | ++----------------------------+----------------------------------------------------------------------------------------------------------+ + +4.2. Control flow + +The next hop registration control flow is the following: + +:: + + <==== BGP Process ====>|<==== Zebra Process ====> + | + receive module nht module | zserv module rnh module + ---------------------------------------------------------------------- + | | | + bgp_update_ | | | + main() | bgp_find_or_add_ | | + | nexthop() | | + | | | + | | zserv_nexthop_ | + | | register() | + | | | zebra_add_rnh() + | | | + + +The next hop notification control flow is the following: + +:: + + <==== Zebra Process ====>|<==== BGP Process ====> + | + rib module rnh module | zebra module nht module + ---------------------------------------------------------------------- + | | | + meta_queue_ | | | + process() | zebra_evaluate_ | | + | rnh_table() | | + | | | + | | bgp_read_nexthop_ | + | | update() | + | | | bgp_parse_ + | | | nexthop_update() + | | | + + +zclient message format +^^^^^^^^^^^^^^^^^^^^^^ + +ZEBRA_NEXTHOP_REGISTER and ZEBRA_NEXTHOP_UNREGISTER messages are +encoded in the following way: + +:: + + . 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AF | prefix len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . Nexthop prefix . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AF | prefix len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . Nexthop prefix . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +``ZEBRA_NEXTHOP_UPDATE`` message is encoded as follows: + +:: + + . 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | AF | prefix len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . Nexthop prefix getting resolved . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | metric | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | #nexthops | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | nexthop type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . resolving Nexthop details . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | nexthop type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . resolving Nexthop details . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +BGP data structure +^^^^^^^^^^^^^^^^^^ +Legend: + +:: + + /\ struct bgp_node: a BGP destination/route/prefix + \/ + + [ ] struct bgp_path_info: a BGP path (e.g. route received from a peer) + + _ + (_) struct bgp_nexthop_cache: a BGP nexthop + + /\ NULL + \/--+ ^ + | : + +--[ ]--[ ]--[ ]--> NULL + /\ : + \/--+ : + | : + +--[ ]--[ ]--> NULL + : + _ : + (_)........... + + +Zebra data structure +^^^^^^^^^^^^^^^^^^^^ + +RNH table:: + + . O + / \ + O O + / \ + O O + + struct rnh + { + uint8_t flags; + struct route_entry *state; + struct list *client_list; + struct route_node *node; + }; + +User interface changes +^^^^^^^^^^^^^^^^^^^^^^ + +:: + + frr# show ip nht + 3.3.3.3 + resolved via kernel + via 11.0.0.6, swp1 + Client list: bgp(fd 12) + 11.0.0.10 + resolved via connected + is directly connected, swp2 + Client list: bgp(fd 12) + 11.0.0.18 + resolved via connected + is directly connected, swp4 + Client list: bgp(fd 12) + 11.11.11.11 + resolved via kernel + via 10.0.1.2, eth0 + Client list: bgp(fd 12) + + frr# show ip bgp nexthop + Current BGP nexthop cache: + 3.3.3.3 valid [IGP metric 0], #paths 3 + Last update: Wed Oct 16 04:43:49 2013 + + 11.0.0.10 valid [IGP metric 1], #paths 1 + Last update: Wed Oct 16 04:43:51 2013 + + 11.0.0.18 valid [IGP metric 1], #paths 2 + Last update: Wed Oct 16 04:43:47 2013 + + 11.11.11.11 valid [IGP metric 0], #paths 1 + Last update: Wed Oct 16 04:43:47 2013 + + frr# show ipv6 nht + frr# show ip bgp nexthop detail + + frr# debug bgp nht + frr# debug zebra nht + + 6. Sample test cases + + r2----r3 + / \ / + r1----r4 + + - Verify that a change in IGP cost triggers NHT + + shutdown the r1-r4 and r2-r4 links + + no shut the r1-r4 and r2-r4 links and wait for OSPF to come back + up + + We should be back to the original nexthop via r4 now + - Verify that a NH becoming unreachable triggers NHT + + Shutdown all links to r4 + - Verify that a NH becoming reachable triggers NHT + + no shut all links to r4 + +Future work +^^^^^^^^^^^ + +- route-policy for next hop validation (e.g. ignore default route) +- damping for rapid next hop changes +- prioritized handling of nexthop changes ((un)reachability vs. metric + changes) +- handling recursion loop, e.g:: + + 11.11.11.11/32 -> 12.12.12.12 + 12.12.12.12/32 -> 11.11.11.11 + 11.0.0.0/8 -> <interface> +- better statistics diff --git a/doc/developer/northbound/advanced-topics.rst b/doc/developer/northbound/advanced-topics.rst new file mode 100644 index 0000000..eb75602 --- /dev/null +++ b/doc/developer/northbound/advanced-topics.rst @@ -0,0 +1,301 @@ +Advanced Topics +=============== + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 1 + +Auto-generated CLI commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to have less code to maintain, it should be possible to write a +tool that auto-generates CLI commands based on the FRR YANG models. As a +matter of fact, there are already a number of NETCONF-based CLIs that do +exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__). + +The problem however is that there isn’t an exact one-to-one mapping +between the existing CLI commands and the corresponding YANG nodes from +the native models. As an example, ripd’s +``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` command +changes three YANG leaves at the same time. In order to auto-generate +CLI commands and retain their original form, it’s necessary to add +annotations in the YANG modules to specify how the commands should look +like. Without YANG annotations, the CLI auto-generator will generate a +command for each YANG leaf, (leaf-)list and presence-container. The +ripd’s ``timers basic`` command, for instance, would become three +different commands, which would be undesirable. + +The good news is that *libyang* allows users to create plugins to +implement their own YANG extensions, which can be used to implement CLI +annotations. If done properly, a CLI generator can save FRR developers +from writing and maintaining hundreds if not thousands of DEFPYs! + +CLI on a separate program +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flexible design of the northbound architecture opens the door to +move the CLI to a separate program in the long-term future. Some +advantages of doing so would be: + +* Treat the CLI as just another northbound client, instead of having CLI + commands embedded in the binaries of all FRR daemons. + +* Improved robustness: bugs in CLI commands (e.g. null-pointer dereferences) or + in the CLI code itself wouldn’t affect the FRR daemons. + +* Foster innovation by allowing other CLI programs to be implemented, possibly + using higher level programming languages. + +The problem, however, is that the northbound retrofitting process will +convert only the CLI configuration commands and EXEC commands in a first +moment. Retrofitting the “show” commands is a completely different story +and shouldn’t happen anytime soon. This should hinder progress towards +moving the CLI to a separate program. + +Proposed feature: confirmed commits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Confirmed commits allow the user to request an automatic rollback to the +previous configuration if the commit operation is not confirmed within a +number of minutes. This is particularly useful when the user is +accessing the CLI through the network (e.g. using SSH) and any +configuration change might cause an unexpected loss of connectivity +between the user and the router (e.g. misconfiguration of a routing +protocol). By using a confirmed commit, the user can rest assured the +connectivity will be restored after the given timeout expires, avoiding +the need to access the router physically to fix the problem. + +Example of how this feature could be provided in the CLI: +``commit confirmed [minutes <1-60>]``. The ability to do confirmed +commits should also be exposed in the northbound API so that the +northbound plugins can also take advantage of it (in the case of the +Sysrepo plugin, confirmed commit is implemented externally in the +*netopeer2-server* daemon). + +Proposed feature: enable/disable configuration commands/sections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the ``lyd_node`` data structure from *libyang* can hold private +data, it should be possible to mark configuration commands or sections +as active or inactive. This would allow CLI users to leverage this +feature to disable parts of the running configuration without actually +removing the associated commands, and then re-enable the disabled +configuration commands or sections later when necessary. Example: + +:: + + ripd(config)# show configuration running + Configuration: + [snip] + ! + router rip + default-metric 2 + distance 80 + network eth0 + network eth1 + ! + end + ripd(config)# disable router rip + ripd(config)# commit + % Configuration committed successfully (Transaction ID #7). + + ripd(config)# show configuration running + Configuration: + [snip] + ! + !router rip + !default-metric 2 + !distance 80 + !network eth0 + !network eth1 + ! + end + ripd(config)# enable router rip + ripd(config)# commit + % Configuration committed successfully (Transaction ID #8). + + ripd(config)# show configuration running + [snip] + frr defaults traditional + ! + router rip + default-metric 2 + distance 80 + network eth0 + network eth1 + ! + end + +This capability could be useful in a number of occasions, like disabling +configuration commands that are no longer necessary (e.g. ACLs) but that +might be necessary at a later point in the future. Other example is +allowing users to disable a configuration section for testing purposes, +and then re-enable it easily without needing to copy and paste any +command. + +Configuration reloads +~~~~~~~~~~~~~~~~~~~~~ + +Given the limitations of the previous northbound architecture, the FRR +daemons didn’t have the ability to reload their configuration files by +themselves. The SIGHUP handler of most daemons would only re-read the +configuration file and merge it into the running configuration. In most +cases, however, what is desired is to replace the running configuration +by the updated configuration file. The *frr-reload.py* script was +written to work around this problem and it does it well to a certain +extent. The problem with the *frr-reload.py* script is that it’s full of +special cases here and there, which makes it fragile and unreliable. +Maintaining the script is also an additional burden for FRR developers, +few of whom are familiar with its code or know when it needs to be +updated to account for a new feature. + +In the new northbound architecture, reloading the configuration file can +be easily implemented using a configuration transaction. Once the FRR +northbound retrofitting process is complete, all daemons should have the +ability to reload their configuration files upon receiving the SIGHUP +signal, or when the ``configuration load [...] replace`` command is +used. Once that point is reached, the *frr-reload.py* script will no +longer be necessary and should be removed from the FRR repository. + +Configuration changes coming from the kernel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This +`post <http://discuss.tail-f.com/t/who-should-not-set-configuration-once-a-system-is-up-and-running/111>`__ +from the Tail-f’s® forum describes the problem of letting systems +configure themselves behind the users back. Here are some selected +snippets from it: > Traditionally, northbound interface users are the +ones in charge of providing configuration data for systems. > > In some +systems, we see a deviation from this traditional practice; allowing +systems to configure “themselves” behind the scenes (or behind the users +back). > > While there might be a business case for such a practice, +this kind of configuration remains “dangerous” from northbound users +perspective and makes systems hard to predict and even harder to debug. +(…) > > With the advent of transactional Network configuration, this +practice can not work anymore. The fact that systems are given the right +to change configuration is a key here in breaking transactional +configuration in a Network. + +FRR is immune to some of the problems described in the aforementioned +post. Management clients can configure interfaces that don’t yet exist, +and once an interface is deleted from the kernel, its configuration is +retained in FRR. + +There are however some cases where information learned from the kernel +(e.g. using netlink) can affect the running configuration of all FRR +daemons. Examples: interface rename events, VRF rename events, interface +being moved to a different VRF, etc. In these cases, since these events +can’t be ignored, the best we can do is to send YANG notifications to +the management clients to inform about the configuration changes. The +management clients should then be prepared to handle such notifications +and react accordingly. + +Interfaces and VRFs +~~~~~~~~~~~~~~~~~~~ + +As of now zebra doesn’t have the ability to create VRFs or virtual +interfaces in the kernel. The ``vrf`` and ``interface`` commands only +create pre-provisioned VRFs and interfaces that are only activated when +the corresponding information is learned from the kernel. When +configuring FRR using an external management client, like a NETCONF +client, it might be desirable to actually create functional VRFs and +virtual interfaces (e.g. VLAN subinterfaces, bridges, etc) that are +installed in the kernel using OS-specific APIs (e.g. netlink, routing +socket, etc). Work needs to be done in this area to make this possible. + +Shared configuration objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One of the existing problems in FRR is that it’s hard to ensure that all +daemons are in sync with respect to the shared configuration objects +(e.g. interfaces, VRFs, route-maps, ACLs, etc). When a route-map is +configured using *vtysh*, the same command is sent to all relevant +daemons (the daemons that implement route-maps), which ensures +synchronization among them. The problem is when a daemon starts after +the route-maps are created. In this case this daemon wouldn’t be aware +of the previously configured route-maps (unlike the other daemons), +which can lead to a lot of confusion and unexpected problems. + +With the new northbound architecture, configuration objects can be +manipulated using higher level abstractions, which opens more +possibilities to solve this decades-long problem. As an example, one +solution would be to make the FRR daemons fetch the shared configuration +objects from zebra using the ZAPI interface during initialization. The +shared configuration objects could be requested using a list of XPaths +expressions in the ``ZEBRA_HELLO`` message, which zebra would respond by +sending the shared configuration objects encoded in the JSON format. +This solution however doesn’t address the case where zebra starts or +restarts after the other FRR daemons. Other solution would be to store +the shared configuration objects in the northbound SQL database and make +all daemons fetch these objects from there. So far no work has been made +on this area as more investigation needs to be done. + +vtysh support +~~~~~~~~~~~~~ + +As explained in the [[Transactional CLI]] page, all commands introduced +by the transactional CLI are not yet available in *vtysh*. This needs to +be addressed in the short term future. Some challenges for doing that +work include: + +* How to display configurations (running, candidates and rollbacks) in a more + clever way? The implementation of the ``show running-config`` command in + *vtysh* is not something that should be followed as an example. A better idea + would be to fetch the desired configuration from all daemons (encoded in JSON + for example), merge them all into a single ``lyd_node`` variable and then + display the combined configurations from this variable (the configuration + merges would transparently take care of combining the shared configuration + objects). In order to be able to manipulate the JSON configurations, *vtysh* + will need to load the YANG modules from all daemons at startup (this might + have a minimal impact on startup time). The only issue with this approach is + that the ``cli_show()`` callbacks from all daemons are embedded in their + binaries and thus not accessible externally. It might be necessary to compile + these callbacks on a separate shared library so that they are accessible to + *vtysh* too. Other than that, displaying the combined configurations in the + JSON/XML formats should be straightforward. + +* With the current design, transaction IDs are per-daemon and not global across + all FRR daemons. This means that the same transaction ID can represent + different transactions on different daemons. Given this observation, how to + implement the ``rollback configuration`` command in *vtysh*? The easy solution + would be to add a ``daemon WORD`` argument to specify the context of the + rollback, but per-daemon rollbacks would certainly be confusing and convoluted + to end users. A better idea would be to attack the root of the problem: change + configuration transactions to be global instead of being per-daemon. This + involves a bigger change in the northbound architecture, and would have + implications on how transactions are stored in the SQL database + (daemon-specific and shared configuration objects would need to have their own + tables or columns). + +* Loading configuration files in the JSON or XML formats will be tricky, as + *vtysh* will need to know which sections of the configuration should be sent + to which daemons. *vtysh* will either need to fetch the YANG modules + implemented by all daemons at runtime or obtain this information at + compile-time somehow. + +Detecting type mismatches at compile-time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As described in the [[Retrofitting Configuration Commands]] page, the +northbound configuration callbacks detect type mismatches at runtime +when fetching data from the the ``dnode`` parameter (which represents +the configuration node being created, modified, deleted or moved). When +a type mismatch is detected, the program aborts and displays a backtrace +showing where the problem happened. It would be desirable to detect such +type mismatches at compile-time, the earlier the problems are detected +the sooner they are fixed. + +One possible solution to this problem would be to auto-generate C +structures from the YANG models and provide a function that converts a +libyang’s ``lyd_node`` variable to a C structure containing the same +information. The northbound callbacks could then fetch configuration +data from this C structure, which would naturally lead to type +mismatches being detected at compile time. One of the challenges of +doing this would be the handling of YANG lists and leaf-lists. It would +be necessary to use dynamic data structures like hashes or rb-trees to +hold all elements of the lists and leaf-lists, and the process of +converting a ``lyd_node`` to an auto-generated C-structure could be +expensive. At this point it’s unclear if it’s worth adding more +complexity in the northbound architecture to solve this specific +problem. diff --git a/doc/developer/northbound/architecture.rst b/doc/developer/northbound/architecture.rst new file mode 100644 index 0000000..4e84f1d --- /dev/null +++ b/doc/developer/northbound/architecture.rst @@ -0,0 +1,282 @@ +Architecture +============ + +Introduction +------------ + +The goal of the new northbound API is to provide a better interface to +configure and monitor FRR programatically. The current design based on +CLI commands is no longer adequate in a world where computer networks +are becoming increasingly bigger, more diverse and more complex. Network +scripting using *expect* and screen scraping techniques is too primitive +and unreliable to be used in large-scale networks. What is proposed is +to modernize FRR to turn it into an API-first routing stack, and +reposition the CLI on top of this API. The most important change, +however, is not the API that will be provided to external users. In +fact, multiple APIs will be supported and users will have the ability to +write custom management APIs if necessary. The biggest change is the +introduction of a model-driven management architecture based on the +`YANG <https://tools.ietf.org/html/rfc7950>`__ modeling language. +Instead of writing code tied to any particular user interface +(e.g. DEFUNs), YANG allows us to write API-agnostic code (in the form of +callbacks) that can be used by any management interface. As an example, +it shouldn’t matter if a set of configuration changes is coming from a +`NETCONF <https://tools.ietf.org/html/rfc6241>`__ session or from a CLI +terminal, the same callbacks should be called to process the +configuration changes regardless of where they came from. This +model-driven design ensures feature parity across all management +interfaces supported by FRR. + +Quoting :rfc:`7950`: + + YANG is a language originally designed to model data for the NETCONF + protocol. A YANG module defines hierarchies of data that can be used for + NETCONF-based operations, including configuration, state data, RPCs, and + notifications. This allows a complete description of all data sent between a + NETCONF client and server. Although out of scope for this specification, + YANG can also be used with protocols other than NETCONF. + +While the YANG and NETCONF specifications are tightly coupled with one +another, both are independent to a certain extent and are evolving +separately. Examples of other management protocols that use YANG include +`RESTCONF <https://tools.ietf.org/html/rfc8040>`__, +`gNMI <https://github.com/openconfig/reference/tree/master/rpc/gnmi>`__ +and +`CoAP <https://www.ietf.org/archive/id/draft-vanderstok-core-comi-11.txt>`__. + +In addition to being management-protocol independent, some other +advantages of using YANG in FRR are listed below: + +* Have a formal contract between FRR and application developers (management + clients). A management client that has access to the FRR YANG models knows + about all existing configuration options available for use. This information + can be used to auto-generate user-friendly interfaces like Web-UIs, custom + CLIs and even code bindings for several different programming languages. Using + `PyangBind <https://github.com/robshakir/pyangbind>`__, for example, it’s + possible to generate Python class hierarchies from YANG models and use these + classes to instantiate objects that mirror the structure of the YANG modules + and can be serialized/deserialized using different encoding formats. + +* Support different encoding formats for instance data. Currently only JSON and + XML are supported, but `GPB + <https://developers.google.com/protocol-buffers/>`__ and `CBOR + <http://cbor.io/>`__ are other viable options in the long term. Additional + encoding formats can be implemented in the *libyang* library for optimal + performance, or externally by translating data to/from one of the supported + formats (with a performance penalty). + +* Have a formal mechanism to introduce backward-incompatible changes based on + `semantic versioning <http://www.openconfig.net/docs/semver/>`__ (not part of + the YANG standard, which allows backward-compatible module updates only). + +* Provide seamless support to the industry-standard NETCONF/RESTCONF protocols + as alternative management APIs. If FRR configuration/state data is modeled + using YANG, supporting YANG-based protocols like NETCONF and RESTCONF is much + easier. + +As important as shifting to a model-driven management paradigm, the new +northbound architecture also introduces the concept of configuration +transactions. Configuration transactions allow management clients to +commit multiple configuration changes at the same time and rest assured +that either all changes will be applied or none will (all-or-nothing). +Configuration transactions are implemented as pseudo-atomic operations +and facilitate automation by removing the burden of error recovery from +the management side. Another property of configuration transactions is +that the configuration changes are always processed in a pre-defined +order to ensure consistency. Configuration transactions that encompass +multiple network devices are called network-wide transactions and are +also supported by the new northbound architecture. When FRR is built +using the ``--enable-config-rollbacks`` option, all committed +transactions are recorded in the FRR rollback log, which can reside +either in memory (volatile) or on persistent storage. + + Network-wide Transactions is the most important leap in network + management technology since SNMP. The error recovery and sequencing + tasks are removed from the manager side. This is usually more than + half the cost in a mature system; more than the entire cost of the + managed devices. + `[source] <https://www.nanog.org/sites/default/files/tuesday_tutorial_moberg_netconf_35.pdf>`__. + +Figures 1 and 2 below illustrate the old and new northbound architecture +of FRR, respectively. As it can be seen, in the old architecture the CLI +was the only interface used to configure and monitor FRR (the SNMP +plugin was’t taken into account given the small number of implemented +MIBs). This means that the only way to automate FRR was by writing +scripts that send CLI commands and parse the text output (which usually +doesn’t have any structure) using screen scraping and regular +expressions. + +.. figure:: images/arch-before.png + :alt: diagram of northbound architecture prior to nbapi conversion + + Old northbound architecture + +The new northbound architectures, on the other hand, features a +multitude of different management APIs, all of them connected to the +northbound layer of the FRR daemons. By default, only the CLI interface +is compiled built-in in the FRR daemons. The other management interfaces +are provided as optional plugins and need to be loaded during the daemon +initialization (e.g. *zebra -M grpc*). This design makes it possible to +integrate FRR with different NETCONF solutions without introducing +vendor lock-in. The [[Plugins - Writing Your Own]] page explains how to +write custom northbound plugins that can be tailored to all needs +(e.g. support custom transport protocols, different data encoding +formats, fine-grained access control, etc). + +.. figure:: images/arch-after.png + :alt: diagram of northbound architecture after nbapi conversion + + New northbound architecture + +Figure 3 shows the internal view of the FRR northbound architecture. In +this image we can see that northbound layer is an abstract entity +positioned between the northbound callbacks and the northbound clients. +The northbound layer is responsible to process the requests coming from +the northbound clients and call the appropriate callbacks to satisfy +these requests. The northbound plugins communicate with the northbound +layer through a public API, which allow users to write third-party +plugins that can be maintained separately. The northbound plugins, in +turn, have their own APIs to communicate with external management +clients. + +.. figure:: images/nb-layer.png + :alt: diagram of northbound architecture internals + + New northbound architecture - internal view + +Initially the CLI (and all of its commands) will be maintained inside +the FRR daemons. In the long term, however, the goal is to move the CLI +to a separate program just like any other management client. The +[[Advanced Topics]] page describes the motivations and challenges of +doing that. Last but not least, the *libyang* block inside the +northbound layer is the engine that makes everything possible. The +*libyang* library will be described in more detail in the following +sections. + +YANG models +----------- + +The main decision to be made when using YANG is which models to +implement. There’s a general consensus that using standard models is +preferable over using custom (native) models. The reasoning is that +applications based on standard models can be reused for all network +appliances that support those models, whereas the same doesn’t apply for +applications written based on custom models. + +That said, there are multiple standards bodies publishing YANG models +and unfortunately not all of them are converging (or at least not yet). +In the context of FRR, which is a routing stack, the two sets of YANG +models that would make sense to implement are the ones from IETF and +from the OpenConfig working group. The question that arises is: which +one of them should we commit to? Or should we try to support both +somehow, at the cost of extra development efforts? + +Another problem, from an implementation point of view, is that it’s +challenging to adapt the existing code base to match standard models. A +more reasonable solution, at least in a first moment, would be to use +YANG deviations and augmentations to do the opposite: adapt the standard +models to the existing code. In practice however this is not as simple +as it seems. There are cases where the differences are too substantial +to be worked around without restructuring the code by changing its data +structures and their relationships. As an example, the *ietf-rip* model +places per-interface RIP configuration parameters inside the +*control-plane-protocol* list (which is augmented by *ietf-rip*). This +means that it’s impossible to configure RIP interface parameters without +first configuring a RIP routing instance. The *ripd* daemon on the other +hand allows the operator to configure RIP interface parameters even if +``router rip`` is not configured. If we were to implement the *ietf-rip* +module natively, we’d need to change ripd’s CLI commands (and the +associated code) to reflect the new configuration hierarchy. + +Taking into account that FRR has a huge code base and that the +northbound retrofitting process per-se will cause a lot of impact, it +was decided to take a conservative approach and write custom YANG models +for FRR modeled after the existing CLI commands. Having YANG models that +closely mirror the CLI commands will allow the FRR developers to +retrofit the code base much more easily, without introducing +backward-incompatible changes in the CLI and reducing the likelihood of +introducing bugs. The [[Retrofitting Configuration Commands]] page +explains in detail how to convert configuration commands to the new +northbound model. + +Even though having native YANG models is not the ideal solution, it will +be already a big step forward for FRR to migrate to a model-driven +management architecture, with support for configuration transactions and +multiple management interfaces, including NETCONF and RESTCONF (through +the northbound plugins). + +The new northbound also features an experimental YANG module translator +that will allow users to translate to and from standard YANG models by +using translation tables. The [[YANG module translator]] page describes +this mechanism in more detail. At this point it’s unclear what can be +achieved through module translation and if that can be considered as a +definitive solution to support standard models or not. + +Northbound Architecture +----------------------- + +.. figure:: images/lys-node.png + :alt: diagram of libyanbg's lys_node data structure + + ``libyang's`` lys_node data structure + + +.. figure:: images/lyd-node.png + :alt: diagram of libyanbg's lyd_node data structure + + ``libyang's`` lyd_node data structure + + +.. figure:: images/ly-ctx.png + :alt: diagram of libyanbg's ly_ctx data structure + + ``libyang's`` ly_ctx data structure + + +.. figure:: images/transactions.png + :alt: diagram showing how configuration transactions work + + Configuration transactions + + +Testing +------- + +The new northbound adds the libyang library as a new mandatory +dependency for FRR. To obtain and install this library, follow the steps +below: + +.. code-block:: console + + git clone https://github.com/CESNET/libyang + cd libyang + git checkout devel + mkdir build ; cd build + cmake -DENABLE_LYD_PRIV=ON .. + make + sudo make install + + +.. note:: + + first make sure to install the libyang + `requirements <https://github.com/CESNET/libyang#build-requirements>`__. + + +FRR needs libyang from version 0.16.7 or newer, which is maintained in +the ``devel`` branch. libyang 0.15.x is maintained in the ``master`` +branch and doesn’t contain one small feature used by FRR (the +``LY_CTX_DISABLE_SEARCHDIR_CWD`` flag). FRR also makes use of the +libyang’s ``ENABLE_LYD_PRIV`` feature, which is disabled by default and +needs to be enabled at compile time. + +It’s advisable (but not required) to install sqlite3 and build FRR with +``--enable-config-rollbacks`` in order to have access to the +configuration rollback feature. + +To test the northbound, the suggested method is to use the +[[Transactional CLI]] with the *ripd* daemon and play with the new +commands. The ``debug northbound`` command can be used to see which +northbound callbacks are called in response to the ``commit`` command. +For reference, the [[Demos]] page shows a small demonstration of the +transactional CLI in action and what it’s capable of. diff --git a/doc/developer/northbound/demos.rst b/doc/developer/northbound/demos.rst new file mode 100644 index 0000000..7c5ae0c --- /dev/null +++ b/doc/developer/northbound/demos.rst @@ -0,0 +1,10 @@ +Demos +===== + +Transactional CLI +----------------- + +This short demo shows some of the capabilities of the new transactional +CLI: + +|asciicast1| diff --git a/doc/developer/northbound/images/arch-after.png b/doc/developer/northbound/images/arch-after.png new file mode 100644 index 0000000..01e6ae6 Binary files /dev/null and b/doc/developer/northbound/images/arch-after.png differ diff --git a/doc/developer/northbound/images/arch-before.png b/doc/developer/northbound/images/arch-before.png new file mode 100644 index 0000000..ab2bb0d Binary files /dev/null and b/doc/developer/northbound/images/arch-before.png differ diff --git a/doc/developer/northbound/images/ly-ctx.png b/doc/developer/northbound/images/ly-ctx.png new file mode 100644 index 0000000..4d4e138 Binary files /dev/null and b/doc/developer/northbound/images/ly-ctx.png differ diff --git a/doc/developer/northbound/images/lyd-node.png b/doc/developer/northbound/images/lyd-node.png new file mode 100644 index 0000000..4ba2b48 Binary files /dev/null and b/doc/developer/northbound/images/lyd-node.png differ diff --git a/doc/developer/northbound/images/lys-node.png b/doc/developer/northbound/images/lys-node.png new file mode 100644 index 0000000..e9e46e7 Binary files /dev/null and b/doc/developer/northbound/images/lys-node.png differ diff --git a/doc/developer/northbound/images/nb-layer.png b/doc/developer/northbound/images/nb-layer.png new file mode 100644 index 0000000..4aa1fd6 Binary files /dev/null and b/doc/developer/northbound/images/nb-layer.png differ diff --git a/doc/developer/northbound/images/transactions.png b/doc/developer/northbound/images/transactions.png new file mode 100644 index 0000000..d18faf4 Binary files /dev/null and b/doc/developer/northbound/images/transactions.png differ diff --git a/doc/developer/northbound/links.rst b/doc/developer/northbound/links.rst new file mode 100644 index 0000000..e8fb327 --- /dev/null +++ b/doc/developer/northbound/links.rst @@ -0,0 +1,228 @@ +Links +===== + +RFCs +~~~~ + +- `RFC 7950 - The YANG 1.1 Data Modeling + Language <https://tools.ietf.org/html/rfc7950>`__ +- `RFC 7951 - JSON Encoding of Data Modeled with + YANG <https://tools.ietf.org/html/rfc7951>`__ +- `RFC 8342 - Network Management Datastore Architecture + (NMDA) <https://tools.ietf.org/html/rfc8342>`__ +- `RFC 6087 - Guidelines for Authors and Reviewers of YANG Data Model + Documents <https://tools.ietf.org/html/rfc6087>`__ +- `RFC 8340 - YANG Tree + Diagrams <https://tools.ietf.org/html/rfc8340>`__ +- `RFC 6991 - Common YANG Data + Types <https://tools.ietf.org/html/rfc6991>`__ +- `RFC 6241 - Network Configuration Protocol + (NETCONF) <https://tools.ietf.org/html/rfc6241>`__ +- `RFC 8040 - RESTCONF + Protocol <https://tools.ietf.org/html/rfc8040>`__ + +YANG models +~~~~~~~~~~~ + +- Collection of several YANG models, including models from standards + organizations such as the IETF and vendor specific models: + https://github.com/YangModels/yang +- OpenConfig: https://github.com/openconfig/public + +Presentations +~~~~~~~~~~~~~ + +- FRR Advanced Northbound API (May 2018) + + - Slides: + https://www.dropbox.com/s/zhybthruwocbqaw/netdef-frr-northbound.pdf?dl=1 + +- Ok, We Got Data Models, Now What? + + - Video: https://www.youtube.com/watch?v=2oqkiZ83vAA + - Slides: + https://www.nanog.org/sites/default/files/20161017_Alvarez_Ok_We_Got_v1.pdf + +- Data Model-Driven Management: Latest Industry and Tool Developments + + - Video: https://www.youtube.com/watch?v=n_oKGJ_jgYQ + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG72/1559/20180219_Claise_Data_Modeling-Driven_Management__v1.pdf + +- Network Automation And Programmability: Reality Versus The Vendor + Hype When Considering Legacy And NFV Networks + + - Video: https://www.youtube.com/watch?v=N5wbYncUS9o + - Slides: + https://www.nanog.org/sites/default/files/1_Moore_Network_Automation_And_Programmability.pdf + +- Lightning Talk: The API is the new CLI? + + - Video: https://www.youtube.com/watch?v=ngi0erGNi58 + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG72/1638/20180221_Grundemann_Lightning_Talk_The_v1.pdf + +- Lightning Talk: OpenConfig - progress toward vendor-neutral network + management + + - Video: https://www.youtube.com/watch?v=10rSUbeMmT4 + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG71/1535/20171004_Shaikh_Lightning_Talk_Openconfig_v1.pdf + +- Getting started with OpenConfig + + - Video: https://www.youtube.com/watch?v=L7trUNK8NJI + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG71/1456/20171003_Alvarez_Getting_Started_With_v1.pdf + +- Why NETCONF and YANG + + - Video: https://www.youtube.com/watch?v=mp4h8aSTba8 + +- NETCONF and YANG Concepts + + - Video: https://www.youtube.com/watch?v=UwYYvT7DBvg + +- NETCONF Tutorial + + - Video: https://www.youtube.com/watch?v=N4vov1mI14U + +Whitepapers +~~~~~~~~~~~ + +- Automating Network and Service Configuration Using NETCONF and YANG: + http://www.tail-f.com/wordpress/wp-content/uploads/2013/02/Tail-f-Presentation-Netconf-Yang.pdf +- Creating the Programmable Network: The Business Case for NETCONF/YANG + in Network Devices: + http://www.tail-f.com/wordpress/wp-content/uploads/2013/10/HR-Tail-f-NETCONF-WP-10-08-13.pdf +- NETCONF/YANG: What’s Holding Back Adoption & How to Accelerate It: + https://www.oneaccess-net.com/images/public/wp_heavy_reading.pdf +- Achieving Automation with YANG Modeling Technologies: + https://www.cisco.com/c/dam/en/us/products/collateral/cloud-systems-management/network-services-orchestrator/idc-achieving-automation-wp.pdf + +Blog posts and podcasts +~~~~~~~~~~~~~~~~~~~~~~~ + +- OpenConfig and IETF YANG Models: Can they converge? - + http://rob.sh/post/215/ +- OpenConfig: Standardized Models For Networking - + https://packetpushers.net/openconfig-standardized-models-networking/ +- (Podcast) OpenConfig: From Basics to Implementations - + https://blog.ipspace.net/2017/02/openconfig-from-basics-to.html +- (Podcast) How Did NETCONF Start on Software Gone Wild - + https://blog.ipspace.net/2017/12/how-did-netconf-start-on-software-gone.html +- YANG Data Models in the Industry: Current State of Affairs (March + 2018) - + https://www.claise.be/2018/03/yang-data-models-in-the-industry-current-state-of-affairs-march-2018/ +- Why Data Model-driven Telemetry is the only useful Telemetry? - + https://www.claise.be/2018/02/why-data-model-driven-telemetry-is-the-only-useful-telemetry/ +- NETCONF versus RESTCONF: Capabilitity Comparisons for Data + Model-driven Management - + https://www.claise.be/2017/10/netconf-versus-restconf-capabilitity-comparisons-for-data-model-driven-management-2/ +- An Introduction to NETCONF/YANG - + https://www.fir3net.com/Networking/Protocols/an-introduction-to-netconf-yang.html +- Network Automation and the Rise of NETCONF - + https://medium.com/@k.okasha/network-automation-and-the-rise-of-netconf-e96cc33fe28 +- YANG and the Road to a Model Driven Network - + https://medium.com/@k.okasha/yang-and-road-to-a-model-driven-network-e9e52d47148d + +Software +~~~~~~~~ + +libyang +^^^^^^^ + + libyang is a YANG data modelling language parser and toolkit written + (and providing API) in C. + +- GitHub page: https://github.com/CESNET/libyang +- Documentaion: https://netopeer.liberouter.org/doc/libyang/master/ + +pyang +^^^^^ + + pyang is a YANG validator, transformator and code generator, written + in python. It can be used to validate YANG modules for correctness, + to transform YANG modules into other formats, and to generate code + from the modules. + +- GitHub page: https://github.com/mbj4668/pyang +- Documentaion: https://github.com/mbj4668/pyang/wiki/Documentation + +ncclient +^^^^^^^^ + + ncclient is a Python library that facilitates client-side scripting + and application development around the NETCONF protocol. + +- GitHub page: https://github.com/ncclient/ncclient +- Documentaion: https://ncclient.readthedocs.io/en/latest/ + +YDK +^^^ + + ydk-gen is a developer tool that can generate API’s that are modeled + in YANG. Currently, it generates language binding for Python, Go and + C++ with planned support for other language bindings in the future. + +- GitHub pages: + + - Generator: https://github.com/CiscoDevNet/ydk-gen + - Python: https://github.com/CiscoDevNet/ydk-py + + - Python samples: https://github.com/CiscoDevNet/ydk-py-samples + + - Go: https://github.com/CiscoDevNet/ydk-go + - C++: https://github.com/CiscoDevNet/ydk-cpp + +- Documentation: + + - Python: http://ydk.cisco.com/py/docs/ + - Go: http://ydk.cisco.com/go/docs/ + - C++: http://ydk.cisco.com/cpp/docs/ + +- (Blog post) Simplifying Network Programmability with Model-Driven + APIs: + https://blogs.cisco.com/sp/simplifying-network-programmability-with-model-driven-apis +- (Video introduction) Infrastructure as a Code Using YANG, OpenConfig + and YDK: https://www.youtube.com/watch?v=G1b6vJW1R5w + +pyangbind +^^^^^^^^^ + + A plugin for pyang that creates Python bindings for a YANG model. + +- GitHub page: https://github.com/robshakir/pyangbind +- Documentation: http://pynms.io/pyangbind/ + +Sysrepo +^^^^^^^ + + Sysrepo is an YANG-based configuration and operational state data + store for Unix/Linux applications. + +- GitHub page: https://github.com/sysrepo/sysrepo +- Official webpage: http://www.sysrepo.org/ +- Documentation: http://www.sysrepo.org/static/doc/html/ + +Netopeer2 +^^^^^^^^^ + + Netopeer2 is a set of tools implementing network configuration tools + based on the NETCONF Protocol. This is the second generation of the + toolset, originally available as the Netopeer project. Netopeer2 is + based on the new generation of the NETCONF and YANG libraries - + libyang and libnetconf2. The Netopeer server uses sysrepo as a + NETCONF datastore implementation. + +- GitHub page: https://github.com/CESNET/Netopeer2 + +Clixon +^^^^^^ + + Clixon is an automatic configuration manager where you generate + interactive CLI, NETCONF, RESTCONF and embedded databases with + transaction support from a YANG specification. + +- GitHub page: https://github.com/clicon/clixon +- Project page: http://www.clicon.org/ diff --git a/doc/developer/northbound/northbound.rst b/doc/developer/northbound/northbound.rst new file mode 100644 index 0000000..c5f4e2f --- /dev/null +++ b/doc/developer/northbound/northbound.rst @@ -0,0 +1,21 @@ +.. _northbound: + +************** +Northbound API +************** + +.. toctree:: + :maxdepth: 2 + + architecture + transactional-cli + retrofitting-configuration-commands + operational-data-rpcs-and-notifications + advanced-topics + yang-tools + yang-module-translator + demos + links + plugins-sysrepo + ppr-basic-test-topology + ppr-mpls-basic-test-topology diff --git a/doc/developer/northbound/operational-data-rpcs-and-notifications.rst b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst new file mode 100644 index 0000000..07f92c2 --- /dev/null +++ b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst @@ -0,0 +1,565 @@ +Operational Data, RPCs and Notifications +======================================== + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 1 + +Operational data +~~~~~~~~~~~~~~~~ + +Writing API-agnostic code for YANG-modeled operational data is +challenging. Sysrepo, for instance, has completely different API to +fetch operational data. So how can we write API-agnostic callbacks +that can be used by both the Sysrepo plugin, and any other northbound +client that might be written in the future? + +As an additional requirement, the callbacks must be designed in a way +that makes in-place XPath filtering possible. As an example, a +management client might want to retrieve only a subset of a large YANG +list (e.g. a BGP table), and for optimal performance it should be +possible to filter out the unwanted elements locally in the managed +devices instead of returning all elements and performing the filtering +on the management application. + +To meet all these requirements, the four callbacks below were introduced +in the northbound architecture: + +.. code:: c + + /* + * Operational data callback. + * + * The callback function should return the value of a specific leaf or + * inform if a typeless value (presence containers or leafs of type + * empty) exists or not. + * + * xpath + * YANG data path of the data we want to get + * + * list_entry + * pointer to list entry + * + * Returns: + * pointer to newly created yang_data structure, or NULL to indicate + * the absence of data + */ + struct yang_data *(*get_elem)(const char *xpath, void *list_entry); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return the next entry in the list. The + * 'list_entry' parameter will be NULL on the first invocation. + * + * list_entry + * pointer to a list entry + * + * Returns: + * pointer to the next entry in the list, or NULL to signal that the + * end of the list was reached + */ + void *(*get_next)(void *list_entry); + + /* + * Operational data callback for YANG lists. + * + * The callback function should fill the 'keys' parameter based on the + * given list_entry. + * + * list_entry + * pointer to a list entry + * + * keys + * structure to be filled based on the attributes of the provided + * list entry + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + int (*get_keys)(void *list_entry, struct yang_list_keys *keys); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return a list entry based on the list + * keys given as a parameter. + * + * keys + * structure containing the keys of the list entry + * + * Returns: + * a pointer to the list entry if found, or NULL if not found + */ + void *(*lookup_entry)(struct yang_list_keys *keys); + +These callbacks were designed to provide maximum flexibility. Each +callback does one and only one task, they are indivisible primitives +that can be combined in several different ways to iterate over operational +data. The extra flexibility certainly has a performance cost, but it’s the +price to pay if we want to expose FRR operational data using several +different management interfaces (e.g. Sysrepo+Netopeer2). In the +future it might be possible to introduce optional callbacks that do +things like returning multiple objects at once. They would provide +enhanced performance when iterating over large lists, but their use +would be limited by the northbound plugins that can be integrated with +them. + +The [[Plugins - Writing Your Own]] page explains how the northbound +plugins can fetch operational data using the aforementioned northbound +callbacks, and how in-place XPath filtering can be implemented. + +Example +^^^^^^^ + +Now let’s move to an example to show how these callbacks are implemented +in practice. The following YANG container is part of the *ietf-rip* +module and contains operational data about RIP neighbors: + +.. code:: yang + + container neighbors { + description + "Neighbor information."; + list neighbor { + key "address"; + description + "A RIP neighbor."; + leaf address { + type inet:ipv4-address; + description + "IP address that a RIP neighbor is using as its + source address."; + } + leaf last-update { + type yang:date-and-time; + description + "The time when the most recent RIP update was + received from this neighbor."; + } + leaf bad-packets-rcvd { + type yang:counter32; + description + "The number of RIP invalid packets received from + this neighbor which were subsequently discarded + for any reason (e.g. a version 0 packet, or an + unknown command type)."; + } + leaf bad-routes-rcvd { + type yang:counter32; + description + "The number of routes received from this neighbor, + in valid RIP packets, which were ignored for any + reason (e.g. unknown address family, or invalid + metric)."; + } + } + } + +We know that this is operational data because the ``neighbors`` +container is within the ``state`` container, which has the +``config false;`` property (which is applied recursively). + +As expected, the ``gen_northbound_callbacks`` tool also generates +skeleton callbacks for nodes that represent operational data: + +.. code:: c + + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor", + .cbs.get_next = ripd_state_neighbors_neighbor_get_next, + .cbs.get_keys = ripd_state_neighbors_neighbor_get_keys, + .cbs.lookup_entry = ripd_state_neighbors_neighbor_lookup_entry, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/address", + .cbs.get_elem = ripd_state_neighbors_neighbor_address_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/last-update", + .cbs.get_elem = ripd_state_neighbors_neighbor_last_update_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd", + .cbs.get_elem = ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd", + .cbs.get_elem = ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem, + }, + +The ``/frr-ripd:ripd/state/neighbors/neighbor`` list within the +``neighbors`` container has three different callbacks that need to be +implemented. Let’s start with the first one, the ``get_next`` callback: + +.. code:: c + + static void *ripd_state_neighbors_neighbor_get_next(void *list_entry) + { + struct listnode *node; + + if (list_entry == NULL) + node = listhead(peer_list); + else + node = listnextnode((struct listnode *)list_entry); + + return node; + } + +Given a list entry, the job of this callback is to find the next element +from the list. When the ``list_entry`` parameter is NULL, then the first +element of the list should be returned. + +*ripd* uses the ``rip_peer`` structure to represent RIP neighbors, and +the ``peer_list`` global variable (linked list) is used to store all RIP +neighbors. + +In order to be able to iterate over the list of RIP neighbors, the +callback returns a ``listnode`` variable instead of a ``rip_peer`` +variable. The ``listnextnode`` macro can then be used to find the next +element from the linked list. + +Now the second callback, ``get_keys``: + +.. code:: c + + static int ripd_state_neighbors_neighbor_get_keys(void *list_entry, + struct yang_list_keys *keys) + { + struct listnode *node = list_entry; + struct rip_peer *peer = listgetdata(node); + + keys->num = 1; + (void)inet_ntop(AF_INET, &peer->addr, keys->key[0].value, + sizeof(keys->key[0].value)); + + return NB_OK; + } + +This one is easy. First, we obtain the RIP neighbor from the +``listnode`` structure. Then, we fill the ``keys`` parameter according +to the attributes of the RIP neighbor. In this case, the ``neighbor`` +YANG list has only one key: the neighbor IP address. We then use the +``inet_ntop()`` function to transform this binary IP address into a +string (the lingua franca of the FRR northbound). + +The last callback for the ``neighbor`` YANG list is the ``lookup_entry`` +callback: + +.. code:: c + + static void * + ripd_state_neighbors_neighbor_lookup_entry(struct yang_list_keys *keys) + { + struct in_addr address; + + yang_str2ipv4(keys->key[0].value, &address); + + return rip_peer_lookup(&address); + } + +This callback is the counterpart of the ``get_keys`` callback: given an +array of list keys, the associated list entry should be returned. The +``yang_str2ipv4()`` function is used to convert the list key (an IP +address) from a string to an ``in_addr`` structure. Then the +``rip_peer_lookup()`` function is used to find the list entry. + +Finally, each YANG leaf inside the ``neighbor`` list has its associated +``get_elem`` callback: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/address + */ + static struct yang_data * + ripd_state_neighbors_neighbor_address_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_ipv4(xpath, &peer->addr); + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/last-update + */ + static struct yang_data * + ripd_state_neighbors_neighbor_last_update_get_elem(const char *xpath, + void *list_entry) + { + /* TODO: yang:date-and-time is tricky */ + return NULL; + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd + */ + static struct yang_data * + ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_uint32(xpath, peer->recv_badpackets); + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd + */ + static struct yang_data * + ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_uint32(xpath, peer->recv_badroutes); + } + +These callbacks receive the list entry as parameter and return the +corresponding data using the ``yang_data_new_*()`` wrapper functions. +Not much to explain here. + +Iterating over operational data without blocking the main pthread +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One of the problems we have in FRR is that some “show” commands in the +CLI can take too long, potentially long enough to the point of +triggering some protocol timeouts and bringing sessions down. + +To avoid this kind of problem, northbound clients are encouraged to do +one of the following: + +* Create a separate pthread for handling requests to fetch operational data. + +* Iterate over YANG lists and leaf-lists asynchronously, returning a maximum + number of elements per time instead of returning all elements in one shot. + +In order to handle both cases correctly, the ``get_next`` callbacks need +to use locks to prevent the YANG lists from being modified while they +are being iterated over. If that is not done, the list entry returned by +this callback can become a dangling pointer when used in another +callback. + +Currently the Sysrepo plugin runs only in the main pthread. The plan in the +short-term is to introduce a separate pthread only for handling operational +data, and use the main pthread only for handling configuration changes, +RPCs and notifications. + +RPCs and Actions +~~~~~~~~~~~~~~~~ + +The FRR northbound supports YANG RPCs and Actions through the ``rpc()`` +callback, which is documented as follows in the *lib/northbound.h* file: + +.. code:: c + + /* + * RPC and action callback. + * + * Both 'input' and 'output' are lists of 'yang_data' structures. The + * callback should fetch all the input parameters from the 'input' list, + * and add output parameters to the 'output' list if necessary. + * + * xpath + * xpath of the YANG RPC or action + * + * input + * read-only list of input parameters + * + * output + * list of output parameters to be populated by the callback + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + int (*rpc)(const char *xpath, const struct list *input, + struct list *output); + +Note that the same callback is used for both RPCs and actions, which are +essentially the same thing. In the case of YANG actions, the ``xpath`` +parameter can be consulted to find the data node associated to the +operation. + +As part of the northbound retrofitting process, it’s suggested to model +some EXEC-level commands using YANG so that their functionality is +exposed to other management interfaces other than the CLI. As an +example, if the ``clear bgp`` command is modeled using a YANG RPC, and a +corresponding ``rpc`` callback is written, then it should be possible to +clear BGP neighbors using NETCONF and RESTCONF with that RPC (the Sysrepo +plugin has full support for YANG RPCs and actions). + +Here’s an example of a very simple RPC modeled using YANG: + +.. code:: yang + + rpc clear-rip-route { + description + "Clears RIP routes from the IP routing table and routes + redistributed into the RIP protocol."; + } + +This RPC doesn’t have any input or output parameters. Below we can see +the implementation of the corresponding ``rpc`` callback, whose skeleton +was automatically generated by the ``gen_northbound_callbacks`` tool: + +.. code:: c + + /* + * XPath: /frr-ripd:clear-rip-route + */ + static int clear_rip_route_rpc(const char *xpath, const struct list *input, + struct list *output) + { + struct route_node *rp; + struct rip_info *rinfo; + struct list *list; + struct listnode *listnode; + + /* Clear received RIP routes */ + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + if (list == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (!rip_route_rte(rinfo)) + continue; + + if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + rip_zebra_ipv4_delete(rp); + break; + } + + if (rinfo) { + RIP_TIMER_OFF(rinfo->t_timeout); + RIP_TIMER_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + rip_info_free(rinfo); + } + + if (list_isempty(list)) { + list_delete_and_null(&list); + rp->info = NULL; + route_unlock_node(rp); + } + } + + return NB_OK; + } + +If the ``clear-rip-route`` RPC had any input parameters, they would be +available in the ``input`` list given as a parameter to the callback. +Similarly, the ``output`` list can be used to append output parameters +generated by the RPC, if any are defined in the YANG model. + +The northbound clients (CLI and northbound plugins) have the +responsibility to create and delete the ``input`` and ``output`` lists. +However, in the cases where the RPC or action doesn’t have any input or +output parameters, the northbound client can pass NULL pointers to the +``rpc`` callback to avoid creating linked lists unnecessarily. We can +see this happening in the example below: + +.. code:: c + + /* + * XPath: /frr-ripd:clear-rip-route + */ + DEFPY (clear_ip_rip, + clear_ip_rip_cmd, + "clear ip rip", + CLEAR_STR + IP_STR + "Clear IP RIP database\n") + { + return nb_cli_rpc("/frr-ripd:clear-rip-route", NULL, NULL); + } + +``nb_cli_rpc()`` is a helper function that merely finds the appropriate +``rpc`` callback based on the XPath provided in the first argument, and +map the northbound error code from the ``rpc`` callback to a vty error +code (e.g. ``CMD_SUCCESS``, ``CMD_WARNING``). The second and third +arguments provided to the function refer to the ``input`` and ``output`` +lists. In this case, both arguments are set to NULL since the YANG RPC +in question doesn’t have any input/output parameters. + +Notifications +~~~~~~~~~~~~~ + +YANG notifations are sent using the ``nb_notification_send()`` function, +documented in the *lib/northbound.h* file as follows: + +.. code:: c + + /* + * Send a YANG notification. This is a no-op unless the 'nb_notification_send' + * hook was registered by a northbound plugin. + * + * xpath + * xpath of the YANG notification + * + * arguments + * linked list containing the arguments that should be sent. This list is + * deleted after being used. + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + extern int nb_notification_send(const char *xpath, struct list *arguments); + +The northbound doesn’t use callbacks for notifications because +notifications are generated locally and sent to the northbound clients. +This way, whenever a notification needs to be sent, it’s possible to +call the appropriate function directly instead of finding a callback +based on the XPath of the YANG notification. + +As an example, the *ietf-rip* module contains the following +notification: + +.. code:: yang + + notification authentication-failure { + description + "This notification is sent when the system + receives a PDU with the wrong authentication + information."; + leaf interface-name { + type string; + description + "Describes the name of the RIP interface."; + } + } + +The following convenience function was implemented in *ripd* to send +*authentication-failure* YANG notifications: + +.. code:: c + + /* + * XPath: /frr-ripd:authentication-failure + */ + void ripd_notif_send_auth_failure(const char *ifname) + { + const char *xpath = "/frr-ripd:authentication-failure"; + struct list *arguments; + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + arguments = yang_data_list_new(); + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); + data = yang_data_new_string(xpath_arg, ifname); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); + } + +Now sending the *authentication-failure* YANG notification should be as +simple as calling the above function and provide the appropriate +interface name. The notification will be processed by all northbound +plugins that subscribed a callback to the ``nb_notification_send`` hook. +The Sysrepo plugin, for instance, uses this hook to relay the notifications +to the *sysrepod* daemon, which can generate NETCONF notifications to subscribed +clients. When no northbound plugin is loaded, ``nb_notification_send()`` doesn’t +do anything and the notifications are ignored. diff --git a/doc/developer/northbound/plugins-sysrepo.rst b/doc/developer/northbound/plugins-sysrepo.rst new file mode 100644 index 0000000..f4df68c --- /dev/null +++ b/doc/developer/northbound/plugins-sysrepo.rst @@ -0,0 +1,193 @@ +Plugins Sysrepo +=============== + +Installation +------------ + +Required dependencies +^^^^^^^^^^^^^^^^^^^^^ +Install FRR build required dependencies, check `Building FRR +<https://docs.frrouting.org/projects/dev-guide/en/latest/building.html>`_ document for specific platform required packages. +Below are debian systems required packages: + +.. code-block:: console + + sudo apt-get install git autoconf automake libtool make \ + libprotobuf-c-dev protobuf-c-compiler build-essential \ + python3-dev python3-pytest python3-sphinx libjson-c-dev \ + libelf-dev libreadline-dev cmake libcap-dev bison flex \ + pkg-config texinfo gdb libgrpc-dev python3-grpc-tools libpcre2-dev + +libyang +^^^^^^^ + +.. note:: + + FRR requires version 2.1.128 or newer, in this document we will + be compiling and installing libyang version 2.1.148. + +.. code-block:: console + + git clone https://github.com/CESNET/libyang.git + cd libyang + git checkout v2.1.148 + mkdir build; cd build + cmake --install-prefix /usr \ + -DCMAKE_BUILD_TYPE:String="Release" .. + make + sudo make install + +Sysrepo +^^^^^^^ + +.. note:: + + The following code block assumes you have installed libyang v2.1.148, if you have + libyang v2.1.128 change sysrepo version to 2.2.105. + +.. code-block:: console + + git clone https://github.com/sysrepo/sysrepo.git + cd sysrepo/ + git checkout v2.2.150 + mkdir build; cd build + cmake --install-prefix /usr \ + -DCMAKE_BUILD_TYPE:String="Release" .. + make + sudo make install + +Verify that sysrepo is installed correctly: + +.. code-block:: console + + sudo sysrepoctl -l + +FRR +^^^ + +Follow the steps of `Building FRR +<https://docs.frrouting.org/projects/dev-guide/en/latest/building.html>`_ + + +Make sure to use ``--enable-sysrepo`` configure-time option while building FRR. + +Below is an example of frr configure-time options, your options +might vary, however in order to allow sysrepo plugin you have +to keep ``--enable-sysrepo`` option: + +.. code-block:: console + + ./bootstrap.sh + ./configure \ + --localstatedir=/var/opt/frr \ + --sbindir=/usr/lib/frr \ + --sysconfdir=/etc/frr \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-configfile-mask=0640 \ + --enable-logfile-mask=0640 \ + --enable-fpm \ + --enable-sysrepo \ + --with-pkg-git-version \ + --with-pkg-extra-version=-MyOwnFRRVersion + make + make check + sudo make install + + +Initialization +-------------- + +Install FRR YANG modules in Sysrepo datastore: + +.. code-block:: console + + cd frr/yang/ + sudo sysrepoctl -i ./ietf/ietf-interfaces.yang -o frr -g frr + sudo sysrepoctl -i frr-vrf.yang -o frr -g frr + sudo sysrepoctl -i frr-interface.yang -o frr -g frr + sudo sysrepoctl -i frr-route-types.yang -o frr -g frr + sudo sysrepoctl -i frr-filter.yang -o frr -g frr + sudo sysrepoctl -i frr-route-map.yang -o frr -g frr + sudo sysrepoctl -i frr-isisd.yang -o frr -g frr + sudo sysrepoctl -i frr-bfdd.yang -o frr -g frr + sudo sysrepoctl -i ./ietf/ietf-routing-types.yang -o frr -g frr + sudo sysrepoctl -i frr-nexthop.yang -o frr -g frr + sudo sysrepoctl -i frr-if-rmap.yang -o frr -g frr + sudo sysrepoctl -i frr-ripd.yang -o frr -g frr + sudo sysrepoctl -i frr-ripngd.yang -o frr -g frr + sudo sysrepoctl -i frr-affinity-map.yang -o frr -g frr + sudo sysrepoctl -i ./ietf/frr-deviations-ietf-interfaces.yang -o frr -g frr + + +Start FRR daemons with sysrepo plugin: + +.. code-block:: console + + sudo /usr/lib/frr/isisd -M sysrepo --log stdout + +Any daemon running with ``-M sysrepo`` will subscribe to its frr yang moduels +on sysrepo and you be able to configure it by editing module configuration on sysrepo. + +Managing the configuration +-------------------------- + +Testing +^^^^^^^ + +To test FRR intergartion with sysrepo, ``sysrepocfg`` tool can be used +to edit frr configuration on sysrepo + +Example: + +Edit sysrepo running datastore configuration for the desiged frr module: + +.. code-block:: console + + sudo sysrepocfg -E nano -d running -m frr-isisd -f json + +Paste the following json configuration: + +.. code-block:: console + + { + "frr-isisd:isis": { + "instance": [ + { + "area-tag": "testnet", + "vrf": "default", + "is-type": "level-1" + } + ] + } + } + +Exit and save config to the same file. + +After that, this configuration should get reflected to vtysh: + +.. code-block:: console + + show run + Building configuration... + + Current configuration: + ! + frr version 9.2-dev-MyOwnFRRVersion + frr defaults traditional + hostname bullseye + ! + router isis testnet + is-type level-1 + exit + ! + end + +NETCONF +^^^^^^^ + +To manage sysrepo configuration through netconf +you can use `netopeer2 <https://github.com/CESNET/netopeer2>`_ as a netfconf server that can +be easily integrated with sysrepo. diff --git a/doc/developer/northbound/ppr-basic-test-topology.rst b/doc/developer/northbound/ppr-basic-test-topology.rst new file mode 100644 index 0000000..4929c9b --- /dev/null +++ b/doc/developer/northbound/ppr-basic-test-topology.rst @@ -0,0 +1,1627 @@ +IS-IS PPR Basic +=============== + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 2 + +Software +~~~~~~~~ + +The FRR PPR implementation for IS-IS is available here: +https://github.com/opensourcerouting/frr/tree/isisd-ppr + +Topology +~~~~~~~~ + +In this topology we have an IS-IS network consisting of 12 routers. CE1 +and CE2 are the consumer edges, connected to R11 and R14, respectively. +Three hosts are connected to the CEs using only static routes. + +Router R11 advertises 6 PPR TLVs, which corresponds to three +bi-directional GRE tunnels: \* **6000:1::1 <-> 6000:2::1:** {R11 - R21 - +R22 - R23 - R14} (IPv6 Node Addresses only) \* **6000:1::2 <-> +6000:2::2:** {R11 - R21 - R32 - R41 - R33 - R23 - R14} (IPv6 Node and +Interface Addresses) \* **6000:1::3 <-> 6000:2::3:** {R11 - R21 - R99 - +R23 - R14} (misconfigured path) + +PBR rules are configured on R11 and R14 to route the traffic between +Host 1 and Host 3 using the first PPR tunnel. Traffic between Host 2 and +Host 3 uses the regular IS-IS shortest path. + +Additional information: \* Addresses in the 4000::/16 range refer to +interface addresses, where the last hextet corresponds to the node ID. +\* Addresses in the 5000::/16 range refer to loopback addresses, where +the last hextet corresponds to the node ID. \* Addresses in the +6000::/16 range refer to PPR-ID addresses. + +:: + + +-------+ +-------+ +-------+ + | | | | | | + | HOST1 | | HOST2 | | HOST3 | + | | | | | | + +---+---+ +---+---+ +---+---+ + | | | + |fd00:10:1::/64 | | + +-----+ +------+ fd00:20:1::/64| + | |fd00:10:2::/64 | + | | | + +-+--+--+ +---+---+ + | | | | + | CE1 | | CE2 | + | | | | + +---+---+ +---+---+ + | | + | | + |fd00:10:0::/64 fd00:20:0::/64| + | | + | | + +---+---+ +-------+ +-------+ +---+---+ + | |4000:101::/64| |4000:102::/64| |4000:103::/64| | + | R11 +-------------+ R12 +-------------+ R13 +-------------+ R14 | + | | | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | | | | | | + |4000:104::/64 | |4000:106::/64 | |4000:108::/64 | + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + | |4000:105::/64 | |4000:107::/64 | |4000:109::/64 + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | |4000:110::/64| |4000:111::/64| | + | R21 +-------------+ R22 +-------------+ R23 | + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | | | | | | + | |4000:113::/64 | |4000:115::/64 | |4000:117::/64 + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + |4000:112::/64 | |4000:114::/64 | |4000:116::/64 | + | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | |4000:118::/64| |4000:119::/64| |4000:120::/64| | + | R31 +-------------+ R32 +-------------+ R33 +-------------+ R34 | + | | | | | | | | + +-------+ +---+---+ +---+---+ +-------+ + | | + |4000:121::/64 | + +----------+----------+ + | + | + +---+---+ + | | + | R41 | + | | + +-------+ + +Configuration +~~~~~~~~~~~~~ + +PPR TLV processing needs to be enabled on all IS-IS routers using the +``ppr on`` command. The advertisements of all PPR TLVs is done by router +R11. + +CLI configuration +^^^^^^^^^^^^^^^^^ + +.. code:: yaml + + --- + + routers: + + host1: + links: + eth-ce1: + peer: [ce1, eth-host1] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:1::1/64 + ! + ipv6 route ::/0 fd00:10:1::100 + + host2: + links: + eth-ce1: + peer: [ce1, eth-host2] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:2::1/64 + ! + ipv6 route ::/0 fd00:10:2::100 + + host3: + links: + eth-ce2: + peer: [ce2, eth-host3] + frr: + zebra: + staticd: + config: | + interface eth-ce2 + ipv6 address fd00:20:1::1/64 + ! + ipv6 route ::/0 fd00:20:1::100 + + ce1: + links: + eth-host1: + peer: [host1, eth-ce1] + eth-host2: + peer: [host2, eth-ce1] + eth-rt11: + peer: [rt11, eth-ce1] + frr: + zebra: + staticd: + config: | + interface eth-host1 + ipv6 address fd00:10:1::100/64 + ! + interface eth-host2 + ipv6 address fd00:10:2::100/64 + ! + interface eth-rt11 + ipv6 address fd00:10:0::100/64 + ! + ipv6 route ::/0 fd00:10:0::11 + + ce2: + links: + eth-host3: + peer: [host3, eth-ce2] + eth-rt14: + peer: [rt14, eth-ce2] + frr: + zebra: + staticd: + config: | + interface eth-host3 + ipv6 address fd00:20:1::100/64 + ! + interface eth-rt14 + ipv6 address fd00:20:0::100/64 + ! + ipv6 route ::/0 fd00:20:0::14 + + rt11: + links: + lo-ppr: + eth-ce1: + peer: [ce1, eth-rt11] + eth-rt12: + peer: [rt12, eth-rt11] + eth-rt21: + peer: [rt21, eth-rt11] + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:1::1/128 + ipv6 address 6000:1::2/128 + ipv6 address 6000:1::3/128 + ! + interface lo + ipv6 address 5000::11/128 + ipv6 router isis 1 + ! + interface eth-ce1 + ipv6 address fd00:10:0::11/64 + ! + interface eth-rt12 + ipv6 address 4000:101::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:104::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:10::/32 fd00:10:0::100 + ! + ppr group VOIP + ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + ppr group INTERFACE_PDES + ppr ipv6 6000:1::2/128 prefix 5000::11/128 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::33/128 + pde ipv6-interface 4000:121::41/64 + pde ipv6-node 5000::32/128 + pde ipv6-interface 4000:113::21/64 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::2/128 prefix 5000::14/128 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::32/128 + pde ipv6-interface 4000:121::41/64 + pde ipv6-node 5000::33/128 + pde ipv6-interface 4000:116::23/64 + pde ipv6-node 5000::14/128 + ! + ! + ppr group BROKEN + ppr ipv6 6000:1::3/128 prefix 5000::11/128 metric 1500 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + ! non-existing node!!! + pde ipv6-node 5000::99/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::3/128 prefix 5000::14/128 metric 1500 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + ! non-existing node!!! + pde ipv6-node 5000::99/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + router isis 1 + net 49.0000.0000.0000.0011.00 + is-type level-1 + topology ipv6-unicast + ppr on + ppr advertise VOIP + ppr advertise INTERFACE_PDES + ppr advertise BROKEN + ! + + rt12: + links: + eth-rt11: + peer: [rt11, eth-rt12] + eth-rt13: + peer: [rt13, eth-rt12] + eth-rt21: + peer: [rt21, eth-rt12] + eth-rt22: + peer: [rt22, eth-rt12] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::12/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:101::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:102::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:105::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:106::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0012.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt13: + links: + eth-rt12: + peer: [rt12, eth-rt13] + eth-rt14: + peer: [rt14, eth-rt13] + eth-rt22: + peer: [rt22, eth-rt13] + eth-rt23: + peer: [rt23, eth-rt13] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::13/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:102::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:103::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:107::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:108::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0013.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt14: + links: + lo-ppr: + eth-ce2: + peer: [ce2, eth-rt14] + eth-rt13: + peer: [rt13, eth-rt14] + eth-rt23: + peer: [rt23, eth-rt14] + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:2::1/128 + ipv6 address 6000:2::2/128 + ipv6 address 6000:2::3/128 + ! + interface lo + ipv6 address 5000::14/128 + ipv6 router isis 1 + ! + interface eth-ce2 + ipv6 address fd00:20:0::14/64 + ! + interface eth-rt13 + ipv6 address 4000:103::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:109::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:20::/32 fd00:20:0::100 + ! + router isis 1 + net 49.0000.0000.0000.0014.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt21: + links: + eth-rt11: + peer: [rt11, eth-rt21] + eth-rt12: + peer: [rt12, eth-rt21] + eth-rt22: + peer: [rt22, eth-rt21] + eth-rt31: + peer: [rt31, eth-rt21] + eth-rt32: + peer: [rt32, eth-rt21] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::21/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:104::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt12 + ipv6 address 4000:105::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:110::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:112::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:113::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0021.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt22: + links: + eth-rt12: + peer: [rt12, eth-rt22] + eth-rt13: + peer: [rt13, eth-rt22] + eth-rt21: + peer: [rt21, eth-rt22] + eth-rt23: + peer: [rt23, eth-rt22] + eth-rt32: + peer: [rt32, eth-rt22] + eth-rt33: + peer: [rt33, eth-rt22] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::22/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:106::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:107::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:110::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:111::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:114::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:115::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0022.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt23: + links: + eth-rt13: + peer: [rt13, eth-rt23] + eth-rt14: + peer: [rt14, eth-rt23] + eth-rt22: + peer: [rt22, eth-rt23] + eth-rt33: + peer: [rt33, eth-rt23] + eth-rt34: + peer: [rt34, eth-rt23] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::23/128 + ipv6 router isis 1 + ! + interface eth-rt13 + ipv6 address 4000:108::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:109::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:111::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:116::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:117::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0023.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt31: + links: + eth-rt21: + peer: [rt21, eth-rt31] + eth-rt32: + peer: [rt32, eth-rt31] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::31/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:112::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:118::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0031.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt32: + links: + eth-rt21: + peer: [rt21, eth-rt32] + eth-rt22: + peer: [rt22, eth-rt32] + eth-rt31: + peer: [rt31, eth-rt32] + eth-rt33: + peer: [rt33, eth-rt32] + eth-sw1: + peer: [sw1, eth-rt32] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::32/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:113::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:114::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:118::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:119::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::32/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0032.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt33: + links: + eth-rt22: + peer: [rt22, eth-rt33] + eth-rt23: + peer: [rt23, eth-rt33] + eth-rt32: + peer: [rt32, eth-rt33] + eth-rt34: + peer: [rt34, eth-rt33] + eth-sw1: + peer: [sw1, eth-rt33] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::33/128 + ipv6 router isis 1 + ! + interface eth-rt22 + ipv6 address 4000:115::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:116::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:119::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:120::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::33/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0033.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt34: + links: + eth-rt23: + peer: [rt23, eth-rt34] + eth-rt33: + peer: [rt33, eth-rt34] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::34/128 + ipv6 router isis 1 + ! + interface eth-rt23 + ipv6 address 4000:117::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:120::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0034.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt41: + links: + eth-sw1: + peer: [sw1, eth-rt41] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::41/128 + ipv6 router isis 1 + ! + interface eth-sw1 + ipv6 address 4000:121::41/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0041.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + switches: + sw1: + links: + eth-rt32: + peer: [rt32, eth-sw1] + eth-rt33: + peer: [rt33, eth-sw1] + eth-rt41: + peer: [rt41, eth-sw1] + + frr: + base-config: | + hostname %(node) + password 1 + log file %(logdir)/%(node).log + log commands + ! + debug zebra rib + debug isis ppr + debug isis events + debug isis route-events + debug isis spf-events + debug isis lsp-gen + ! + +YANG +^^^^ + +PPR can also be configured using NETCONF, RESTCONF and gRPC based on the +following YANG models: \* +`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__ +\* +`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__ + +As an example, here’s R11 configuration in the XML format: + +.. code:: xml + + <lib xmlns="http://frrouting.org/yang/interface"> + <interface> + <name>lo-ppr</name> + <vrf>default</vrf> + </interface> + <interface> + <name>lo</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + </isis> + </interface> + <interface> + <name>eth-ce1</name> + <vrf>default</vrf> + </interface> + <interface> + <name>eth-rt12</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + <interface> + <name>eth-rt21</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + </lib> + <ppr xmlns="http://frrouting.org/yang/ppr"> + <group> + <name>VOIP</name> + <ipv6> + <ppr-id>6000:1::1/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::1/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + </group> + <group> + <name>INTERFACE_PDES</name> + <ipv6> + <ppr-id>6000:1::2/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::33/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:121::41/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::32/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:113::21/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </ipv6> + <ipv6> + <ppr-id>6000:2::2/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::32/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:121::41/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::33/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:116::23/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </ipv6> + </group> + <group> + <name>BROKEN</name> + <ipv6> + <ppr-id>6000:1::3/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::99/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>1500</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::3/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::99/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>1500</ppr-metric> + </attributes> + </ipv6> + </group> + </ppr> + <isis xmlns="http://frrouting.org/yang/isisd"> + <instance> + <area-tag>1</area-tag> + <area-address>49.0000.0000.0000.0011.00</area-address> + <multi-topology> + <ipv6-unicast> + </ipv6-unicast> + </multi-topology> + <ppr> + <enable>true</enable> + <ppr-advertise> + <name>VOIP</name> + </ppr-advertise> + <ppr-advertise> + <name>INTERFACE_PDES</name> + </ppr-advertise> + <ppr-advertise> + <name>BROKEN</name> + </ppr-advertise> + </ppr> + </instance> + </isis> + +Verification - Control Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers: + +:: + + # show isis database detail 0000.0000.0011 + Area 1: + IS-IS Level-1 link-state database: + LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL + debian.00-00 1233 0x00000009 0x7bd4 683 0/0/0 + Protocols Supported: IPv4, IPv6 + Area Address: 49.0000 + MT Router Info: ipv4-unicast + MT Router Info: ipv6-unicast + Hostname: debian + MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast + MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::3/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 1500 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::3/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 1500 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::2/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:113::21 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::2/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:116::23 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::1/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::1/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + +The PPR TLVs can also be seen using a modified version of Wireshark as +seen below: + +.. figure:: https://user-images.githubusercontent.com/931662/61582441-9551e500-ab01-11e9-8f6f-400ee3fba927.png + :alt: s2 + + s2 + +Using the ``show isis ppr`` command, verify that all routers installed +the PPR-IDs for the paths they are part of. Example: + +Router RT11 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Tail-End - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Tail-End - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Tail-End - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Head-End Up 00:45:41 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Head-End Up 00:45:41 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Head-End Up 00:45:41 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:2::1/128 [115/50] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + I>* 6000:2::2/128 [115/0] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + I>* 6000:2::3/128 [115/1500] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + +Router RT12 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT13 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT14 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Head-End Up 00:45:45 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Head-End Up 00:45:45 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Head-End Up 00:45:45 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Tail-End - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Tail-End - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Tail-End - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + I>* 6000:1::2/128 [115/0] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + I>* 6000:1::3/128 [115/1500] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + +Router RT21 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:46 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:46 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Mid-Point Up 00:45:46 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:46 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:46 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Mid-Point Down - + + # show isis ppr id ipv6 6000:2::3/128 detail + Area 1: + PPR-ID: 6000:2::3/128 (Native IPv6) + PPR-Prefix: 5000::14/128 + PDEs: + 5000::11/128 (IPv6 Node Address) + 5000::21/128 (IPv6 Node Address) [LOCAL] + 5000::99/128 (IPv6 Node Address) [NEXT] + 5000::23/128 (IPv6 Node Address) + 5000::14/128 (IPv6 Node Address) + Attributes: + Metric: 1500 + Position: Mid-Point + Originator: 0000.0000.0011 + Level: L1 + Algorithm: 1 + MT-ID: ipv4-unicast + Status: Down: PDE is unreachable + Last change: 00:00:37 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:1::2/128 [115/0] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:1::3/128 [115/1500] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:2::1/128 [115/50] via fe80::c88e:7fff:fe5f:a08d, eth-rt22, 00:01:38 + I>* 6000:2::2/128 [115/0] via fe80::8b2:9eff:fe98:f66a, eth-rt32, 00:01:38 + +Router RT22 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:47 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:47 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::2cb5:edff:fe60:29b1, eth-rt21, 00:01:38 + I>* 6000:2::1/128 [115/50] via fe80::e8d9:63ff:fea3:177b, eth-rt23, 00:01:38 + +Router RT23 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:49 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:49 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Mid-Point Down - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:49 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:49 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Mid-Point Up 00:45:49 + + # show isis ppr id ipv6 6000:1::3/128 detail + Area 1: + PPR-ID: 6000:1::3/128 (Native IPv6) + PPR-Prefix: 5000::11/128 + PDEs: + 5000::14/128 (IPv6 Node Address) + 5000::23/128 (IPv6 Node Address) [LOCAL] + 5000::99/128 (IPv6 Node Address) [NEXT] + 5000::21/128 (IPv6 Node Address) + 5000::11/128 (IPv6 Node Address) + Attributes: + Metric: 1500 + Position: Mid-Point + Originator: 0000.0000.0011 + Level: L1 + Algorithm: 1 + MT-ID: ipv4-unicast + Status: Down: PDE is unreachable + Last change: 00:02:50 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::d09f:1bff:fe31:e9c9, eth-rt22, 00:01:40 + I>* 6000:1::2/128 [115/0] via fe80::c0c3:b3ff:fe9f:b5d3, eth-rt33, 00:01:40 + I>* 6000:2::1/128 [115/50] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + I>* 6000:2::2/128 [115/0] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + I>* 6000:2::3/128 [115/1500] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + +Router RT31 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT32 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:51 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:51 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via 4000:113::21, eth-rt21, 00:01:42 + I>* 6000:2::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:42 + +Router RT33 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:52 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:52 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:43 + I>* 6000:2::2/128 [115/0] via 4000:116::23, eth-rt23, 00:01:43 + +Router RT34 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT41 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:55 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:55 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via fe80::b4b9:60ff:feee:3c73, eth-sw1, 00:01:46 + I>* 6000:2::2/128 [115/0] via fe80::bc2a:d9ff:fe65:97f2, eth-sw1, 00:01:46 + +As it can be seen by the output of ``show isis ppr id ipv6 ... detail``, +routers R21 and R23 couldn’t install the third PPR path because of an +unreachable PDE (configuration error). + +Verification - Forwarding Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On Router R11, use the ``traceroute`` tool to ensure that the PPR paths +were installed correctly in the network: + +:: + + root@rt11:~# traceroute 6000:2::1 + traceroute to 6000:2::1 (6000:2::1), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 0.612 ms 0.221 ms 0.241 ms + 2 4000:110::22 (4000:110::22) 0.257 ms 0.113 ms 0.105 ms + 3 4000:111::23 (4000:111::23) 0.257 ms 0.151 ms 0.098 ms + 4 6000:2::1 (6000:2::1) 0.346 ms 0.139 ms 0.100 ms + root@rt11:~# + root@rt11:~# traceroute 6000:2::2 + traceroute to 6000:2::2 (6000:2::2), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 4.383 ms 4.148 ms 0.044 ms + 2 4000:113::32 (4000:113::32) 0.272 ms 0.065 ms 0.064 ms + 3 4000:121::41 (4000:121::41) 0.263 ms 0.101 ms 0.086 ms + 4 4000:115::33 (4000:115::33) 0.351 ms 4000:119::33 (4000:119::33) 0.249 ms 4000:115::33 (4000:115::33) 0.153 ms + 5 4000:111::23 (4000:111::23) 0.232 ms 0.293 ms 0.131 ms + 6 6000:2::2 (6000:2::2) 0.184 ms 0.212 ms 0.140 ms + root@rt11:~# + root@rt11:~# traceroute 6000:2::3 + traceroute to 6000:2::3 (6000:2::3), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 1.537 ms !N 1.347 ms !N 1.075 ms !N + +The failure on the third traceroute is expected since the 6000:2::3 +PPR-ID is misconfigured. + +Now ping Host 3 from Host 1 and use tcpdump or wireshark to verify that +the ICMP packets are being tunneled using GRE and following the {R11 - +R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and +R21: + +.. figure:: https://user-images.githubusercontent.com/931662/61582398-d4cc0180-ab00-11e9-83a8-d219f98010b9.png + :alt: s1 + + s1 + +Using ``traceroute`` it’s also possible to see that the ICMP packets are +being tunneled through the IS-IS network: + +:: + + root@host1:~# traceroute fd00:20:1::1 -s fd00:10:1::1 + traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets + 1 fd00:10:1::100 (fd00:10:1::100) 0.354 ms 0.092 ms 0.031 ms + 2 fd00:10::11 (fd00:10::11) 0.125 ms 0.022 ms 0.026 ms + 3 * * * + 4 * * * + 5 fd00:20:1::1 (fd00:20:1::1) 0.235 ms 0.106 ms 0.091 ms diff --git a/doc/developer/northbound/ppr-mpls-basic-test-topology.rst b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst new file mode 100644 index 0000000..aceec5f --- /dev/null +++ b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst @@ -0,0 +1,1986 @@ +IS-IS PPR Basic MPLS +==================== + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 2 + +Software +~~~~~~~~ + +The FRR PPR implementation for IS-IS is available here: +https://github.com/opensourcerouting/frr/tree/isisd-ppr-sr + +Topology +~~~~~~~~ + +In this topology we have an IS-IS network consisting of 12 routers. CE1 +and CE2 are the consumer edges, connected to R11 and R14, respectively. +Three hosts are connected to the CEs using only static routes. + +Router R11 advertises 6 PPR TLVs: \* **IPv6 prefixes 6000:1::1/128 and +6000:2::1/128:** {R11 - R21 - R22 - R23 - R14} (IPv6 Node Addresses). \* +**MPLS SR Prefix-SIDs 500 and 501:** {R11 - R21 - R22 - R23 - R14} (SR +Prefix-SIDs). \* **MPLS SR Prefix-SIDs 502 and 503:** {R11 - R21 - R31 - +R32 - R41 - R33 - R34 - R23 - R14} (SR Prefix-SIDs) + +PBR rules are configured on R11 and R14 to route the traffic between +Host 1 and Host 3 using the first PPR tunnel, whereas all other traffic +between CE1 and CE2 uses the second PPR tunnel. + +Additional information: \* Addresses in the 4000::/16 range refer to +interface addresses, where the last hextet corresponds to the node ID. +\* Addresses in the 5000::/16 range refer to loopback addresses, where +the last hextet corresponds to the node ID. \* Addresses in the +6000::/16 range refer to PPR-ID addresses. + +:: + + +-------+ +-------+ +-------+ + | | | | | | + | HOST1 | | HOST2 | | HOST3 | + | | | | | | + +---+---+ +---+---+ +---+---+ + | | | + |fd00:10:1::/64 | | + +-----+ +------+ fd00:20:1::/64| + | |fd00:10:2::/64 | + | | | + +-+--+--+ +---+---+ + | | | | + | CE1 | | CE2 | + | | | | + +---+---+ +---+---+ + | | + | | + |fd00:10:0::/64 fd00:20:0::/64| + | | + | | + +---+---+ +-------+ +-------+ +---+---+ + | |4000:101::/64| |4000:102::/64| |4000:103::/64| | + | R11 +-------------+ R12 +-------------+ R13 +-------------+ R14 | + | | | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | | | | | | + |4000:104::/64 | |4000:106::/64 | |4000:108::/64 | + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + | |4000:105::/64 | |4000:107::/64 | |4000:109::/64 + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | |4000:110::/64| |4000:111::/64| | + | R21 +-------------+ R22 +-------------+ R23 | + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | | | | | | + | |4000:113::/64 | |4000:115::/64 | |4000:117::/64 + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + |4000:112::/64 | |4000:114::/64 | |4000:116::/64 | + | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | |4000:118::/64| |4000:119::/64| |4000:120::/64| | + | R31 +-------------+ R32 +-------------+ R33 +-------------+ R34 | + | | | | | | | | + +-------+ +---+---+ +---+---+ +-------+ + | | + |4000:121::/64 | + +----------+----------+ + | + | + +---+---+ + | | + | R41 | + | | + +-------+ + +Configuration +~~~~~~~~~~~~~ + +PPR TLV processing needs to be enabled on all IS-IS routers using the +``ppr on`` command. The advertisements of all PPR TLVs is done by router +R11. + +CLI configuration +^^^^^^^^^^^^^^^^^ + +.. code:: yaml + + --- + + routers: + + host1: + links: + eth-ce1: + peer: [ce1, eth-host1] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:1::1/64 + ! + ipv6 route ::/0 fd00:10:1::100 + + host2: + links: + eth-ce1: + peer: [ce1, eth-host2] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:2::1/64 + ! + ipv6 route ::/0 fd00:10:2::100 + + host3: + links: + eth-ce2: + peer: [ce2, eth-host3] + frr: + zebra: + staticd: + config: | + interface eth-ce2 + ipv6 address fd00:20:1::1/64 + ! + ipv6 route ::/0 fd00:20:1::100 + + ce1: + links: + eth-host1: + peer: [host1, eth-ce1] + eth-host2: + peer: [host2, eth-ce1] + eth-rt11: + peer: [rt11, eth-ce1] + frr: + zebra: + staticd: + config: | + interface eth-host1 + ipv6 address fd00:10:1::100/64 + ! + interface eth-host2 + ipv6 address fd00:10:2::100/64 + ! + interface eth-rt11 + ipv6 address fd00:10:0::100/64 + ! + ipv6 route ::/0 fd00:10:0::11 label 16501 + + ce2: + links: + eth-host3: + peer: [host3, eth-ce2] + eth-rt14: + peer: [rt14, eth-ce2] + frr: + zebra: + staticd: + config: | + interface eth-host3 + ipv6 address fd00:20:1::100/64 + ! + interface eth-rt14 + ipv6 address fd00:20:0::100/64 + ! + ipv6 route ::/0 fd00:20:0::14 label 16500 + + rt11: + links: + lo: + mpls: yes + lo-ppr: + eth-ce1: + peer: [ce1, eth-rt11] + mpls: yes + eth-rt12: + peer: [rt12, eth-rt11] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt11] + mpls: yes + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:1::1/128 + ! + interface lo + ip address 10.0.0.11/32 + ipv6 address 5000::11/128 + ipv6 router isis 1 + ! + interface eth-ce1 + ipv6 address fd00:10:0::11/64 + ! + interface eth-rt12 + ipv6 address 4000:101::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:104::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:10::/32 fd00:10:0::100 + ! + ppr group PPR_IPV6 + ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + ppr group PPR_MPLS_1 + ppr mpls 500 prefix 5000::11/128 + pde prefix-sid 14 + pde prefix-sid 23 + pde prefix-sid 22 + pde prefix-sid 21 + pde prefix-sid 11 + ! + ppr mpls 501 prefix 5000::14/128 + pde prefix-sid 11 + pde prefix-sid 21 + pde prefix-sid 22 + pde prefix-sid 23 + pde prefix-sid 14 + ! + ! + ppr group PPR_MPLS_2 + ppr mpls 502 prefix 5000::11/128 + pde prefix-sid 14 + pde prefix-sid 23 + pde prefix-sid 34 + pde prefix-sid 33 + pde prefix-sid 41 + pde prefix-sid 32 + pde prefix-sid 31 + pde prefix-sid 21 + pde prefix-sid 11 + ! + ppr mpls 503 prefix 5000::14/128 + pde prefix-sid 11 + pde prefix-sid 21 + pde prefix-sid 31 + pde prefix-sid 32 + pde prefix-sid 41 + pde prefix-sid 33 + pde prefix-sid 34 + pde prefix-sid 23 + pde prefix-sid 14 + ! + ! + router isis 1 + net 49.0000.0000.0000.0011.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::11/128 index 11 no-php-flag + ppr on + ppr advertise PPR_IPV6 + ppr advertise PPR_MPLS_1 + ppr advertise PPR_MPLS_2 + ! + + rt12: + links: + lo: + mpls: yes + eth-rt11: + peer: [rt11, eth-rt12] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt12] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt12] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt12] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.12/32 + ipv6 address 5000::12/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:101::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:102::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:105::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:106::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0012.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::12/128 index 12 no-php-flag + ppr on + ! + + rt13: + links: + lo: + mpls: yes + eth-rt12: + peer: [rt12, eth-rt13] + mpls: yes + eth-rt14: + peer: [rt14, eth-rt13] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt13] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt13] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.13/32 + ipv6 address 5000::13/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:102::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:103::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:107::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:108::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0013.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::13/128 index 13 no-php-flag + ppr on + ! + + rt14: + links: + lo: + mpls: yes + lo-ppr: + eth-ce2: + peer: [ce2, eth-rt14] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt14] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt14] + mpls: yes + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:2::1/128 + ! + interface lo + ip address 10.0.0.14/32 + ipv6 address 5000::14/128 + ipv6 router isis 1 + ! + interface eth-ce2 + ipv6 address fd00:20:0::14/64 + ! + interface eth-rt13 + ipv6 address 4000:103::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:109::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:20::/32 fd00:20:0::100 + ! + router isis 1 + net 49.0000.0000.0000.0014.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::14/128 index 14 no-php-flag + ppr on + ! + + rt21: + links: + lo: + mpls: yes + eth-rt11: + peer: [rt11, eth-rt21] + mpls: yes + eth-rt12: + peer: [rt12, eth-rt21] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt21] + mpls: yes + eth-rt31: + peer: [rt31, eth-rt21] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt21] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.21/32 + ipv6 address 5000::21/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:104::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt12 + ipv6 address 4000:105::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:110::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:112::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:113::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0021.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::21/128 index 21 no-php-flag + ppr on + ! + + rt22: + links: + lo: + mpls: yes + eth-rt12: + peer: [rt12, eth-rt22] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt22] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt22] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt22] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt22] + mpls: yes + eth-rt33: + mpls: yes + peer: [rt33, eth-rt22] + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.22/32 + ipv6 address 5000::22/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:106::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:107::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:110::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:111::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:114::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:115::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0022.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::22/128 index 22 no-php-flag + ppr on + ! + + rt23: + links: + lo: + mpls: yes + eth-rt13: + peer: [rt13, eth-rt23] + mpls: yes + eth-rt14: + peer: [rt14, eth-rt23] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt23] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt23] + mpls: yes + eth-rt34: + peer: [rt34, eth-rt23] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.23/32 + ipv6 address 5000::23/128 + ipv6 router isis 1 + ! + interface eth-rt13 + ipv6 address 4000:108::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:109::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:111::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:116::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:117::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0023.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 20000 27999 + segment-routing prefix 5000::23/128 index 23 no-php-flag + ppr on + ! + + rt31: + links: + lo: + mpls: yes + eth-rt21: + peer: [rt21, eth-rt31] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt31] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.31/32 + ipv6 address 5000::31/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:112::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:118::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0031.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::31/128 index 31 no-php-flag + ppr on + ! + + rt32: + links: + lo: + mpls: yes + eth-rt21: + peer: [rt21, eth-rt32] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt32] + mpls: yes + eth-rt31: + peer: [rt31, eth-rt32] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt32] + mpls: yes + eth-sw1: + peer: [sw1, eth-rt32] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.32/32 + ipv6 address 5000::32/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:113::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:114::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:118::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:119::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::32/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0032.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::32/128 index 32 no-php-flag + ppr on + ! + + rt33: + links: + lo: + mpls: yes + eth-rt22: + peer: [rt22, eth-rt33] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt33] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt33] + mpls: yes + eth-rt34: + peer: [rt34, eth-rt33] + mpls: yes + eth-sw1: + peer: [sw1, eth-rt33] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.33/32 + ipv6 address 5000::33/128 + ipv6 router isis 1 + ! + interface eth-rt22 + ipv6 address 4000:115::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:116::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:119::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:120::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::33/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0033.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::33/128 index 33 no-php-flag + ppr on + ! + + rt34: + links: + lo: + mpls: yes + eth-rt23: + peer: [rt23, eth-rt34] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt34] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.34/32 + ipv6 address 5000::34/128 + ipv6 router isis 1 + ! + interface eth-rt23 + ipv6 address 4000:117::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:120::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0034.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::34/128 index 34 no-php-flag + ppr on + ! + + rt41: + links: + lo: + mpls: yes + eth-sw1: + peer: [sw1, eth-rt41] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.41/32 + ipv6 address 5000::41/128 + ipv6 router isis 1 + ! + interface eth-sw1 + ipv6 address 4000:121::41/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0041.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::41/128 index 41 no-php-flag + ppr on + ! + + switches: + sw1: + links: + eth-rt32: + peer: [rt32, eth-sw1] + eth-rt33: + peer: [rt33, eth-sw1] + eth-rt41: + peer: [rt41, eth-sw1] + + frr: + #valgrind: yes + base-config: | + hostname %(node) + password 1 + log file %(logdir)/%(node).log + log commands + ! + debug zebra rib + debug isis sr-events + debug isis ppr + debug isis events + debug isis route-events + debug isis spf-events + debug isis lsp-gen + ! + +.. + + NOTE: it’s of fundamental importance to enable MPLS processing on the + loopback interfaces, otherwise the tail-end routers of the PPR-MPLS + tunnels will drop the labeled packets they receive. + +YANG +^^^^ + +PPR can also be configured using NETCONF, RESTCONF and gRPC based on the +following YANG models: \* +`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__ +\* +`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__ + +As an example, here’s R11 configuration in the XML format: + +.. code:: xml + + <lib xmlns="http://frrouting.org/yang/interface"> + <interface> + <name>lo-ppr</name> + <vrf>default</vrf> + </interface> + <interface> + <name>lo</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + </isis> + </interface> + <interface> + <name>eth-ce1</name> + <vrf>default</vrf> + </interface> + <interface> + <name>eth-rt12</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + <interface> + <name>eth-rt21</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + </lib> + <ppr xmlns="http://frrouting.org/yang/ppr"> + <group> + <name>PPR_IPV6</name> + <ipv6> + <ppr-id>6000:1::1/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::1/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + </group> + <group> + <name>PPR_MPLS_1</name> + <mpls> + <ppr-id>500</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>22</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + <mpls> + <ppr-id>501</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>22</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + </group> + <group> + <name>PPR_MPLS_2</name> + <mpls> + <ppr-id>502</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>34</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>33</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>41</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>32</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>31</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + <mpls> + <ppr-id>503</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>31</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>32</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>41</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>33</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>34</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + </group> + </ppr> + <isis xmlns="http://frrouting.org/yang/isisd"> + <instance> + <area-tag>1</area-tag> + <area-address>49.0000.0000.0000.0011.00</area-address> + <multi-topology> + <ipv6-unicast> + </ipv6-unicast> + </multi-topology> + <segment-routing> + <enabled>true</enabled> + <prefix-sid-map> + <prefix-sid> + <prefix>5000::11/128</prefix> + <sid-value>11</sid-value> + <last-hop-behavior>no-php</last-hop-behavior> + </prefix-sid> + </prefix-sid-map> + </segment-routing> + <ppr> + <enable>true</enable> + <ppr-advertise> + <name>PPR_IPV6</name> + </ppr-advertise> + <ppr-advertise> + <name>PPR_MPLS_1</name> + </ppr-advertise> + <ppr-advertise> + <name>PPR_MPLS_2</name> + </ppr-advertise> + </ppr> + </instance> + </isis> + +Verification - Control Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers: + +:: + + # show isis database detail 0000.0000.0011 + Area 1: + IS-IS Level-1 link-state database: + LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL + debian.00-00 * 980 0x00000003 0x3b69 894 0/0/0 + Protocols Supported: IPv4, IPv6 + Area Address: 49.0000 + MT Router Info: ipv4-unicast + MT Router Info: ipv6-unicast + Hostname: debian + TE Router ID: 10.0.0.11 + Router Capability: 10.0.0.11 , D:0, S:0 + Segment Routing: I:1 V:1, SRGB Base: 16000 Range: 8000 + Algorithm: 0: SPF 0: Strict SPF + MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast + Adjacency-SID: 16, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0 + MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast + Adjacency-SID: 17, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0 + IPv4 Interface Address: 10.0.0.11 + Extended IP Reachability: 10.0.0.11/32 (Metric: 10) + MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast + Subtlvs: + SR Prefix-SID Index: 11, Algorithm: 0, Flags: NO-PHP + MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::1/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::1/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 500 (MPLS) + PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 501 (MPLS) + PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 502 (MPLS) + PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 503 (MPLS) + PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0 + +Using the ``show isis ppr`` command, verify that all routers installed +the PPR-IDs for the paths they are part of. Example: + +Router RT11 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Tail-End Up 00:00:42 + 1 L1 501 (MPLS) 5000::14/128 0 Head-End Up 00:00:41 + 1 L1 502 (MPLS) 5000::11/128 0 Tail-End Up 00:00:42 + 1 L1 503 (MPLS) 5000::14/128 0 Head-End Up 00:00:41 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Tail-End - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Head-End Up 00:00:41 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 implicit-null + 17 SR (IS-IS) fe80::345f:dfff:fea4:913d implicit-null + 16011 SR (IS-IS) lo - + 16012 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16012 + 16013 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16013 + 16014 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16014 + 16021 SR (IS-IS) fe80::345f:dfff:fea4:913d 16021 + 16022 SR (IS-IS) fe80::345f:dfff:fea4:913d 16022 + 16022 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16022 + 16023 SR (IS-IS) fe80::345f:dfff:fea4:913d 16023 + 16023 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16023 + 16031 SR (IS-IS) fe80::345f:dfff:fea4:913d 16031 + 16032 SR (IS-IS) fe80::345f:dfff:fea4:913d 16032 + 16033 SR (IS-IS) fe80::345f:dfff:fea4:913d 16033 + 16033 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16033 + 16034 SR (IS-IS) fe80::345f:dfff:fea4:913d 16034 + 16034 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16034 + 16041 SR (IS-IS) fe80::345f:dfff:fea4:913d 16041 + 16500 PPR (IS-IS) lo - + 16501 PPR (IS-IS) fe80::345f:dfff:fea4:913d 16501 + 16502 PPR (IS-IS) lo - + 16503 PPR (IS-IS) fe80::345f:dfff:fea4:913d 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:2::1/128 [115/50] via fe80::345f:dfff:fea4:913d, eth-rt21, 00:00:41 + +Router RT12 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ---------------------------------------------------------------------- + 16 SR (IS-IS) fe80::60ad:96ff:fe3f:9989 implicit-null + 17 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 implicit-null + 18 SR (IS-IS) fe80::941c:12ff:fe55:8a12 implicit-null + 19 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 implicit-null + 16011 SR (IS-IS) fe80::60ad:96ff:fe3f:9989 16011 + 16012 SR (IS-IS) lo - + 16013 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16013 + 16014 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16014 + 16021 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16021 + 16022 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16022 + 16023 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16023 + 16023 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16023 + 16031 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16031 + 16032 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16032 + 16032 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16032 + 16033 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16033 + 16034 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16034 + 16034 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16034 + 16041 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16041 + 16041 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16041 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT13 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ---------------------------------------------------------------------- + 16 SR (IS-IS) fe80::1c70:63ff:fe40:3a35 implicit-null + 17 SR (IS-IS) fe80::20:56ff:feff:b218 implicit-null + 18 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a implicit-null + 19 SR (IS-IS) fe80::387d:34ff:fe02:87c3 implicit-null + 16011 SR (IS-IS) fe80::20:56ff:feff:b218 16011 + 16012 SR (IS-IS) fe80::20:56ff:feff:b218 16012 + 16013 SR (IS-IS) lo - + 16014 SR (IS-IS) fe80::1c70:63ff:fe40:3a35 16014 + 16021 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16021 + 16021 SR (IS-IS) fe80::20:56ff:feff:b218 16021 + 16022 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16022 + 16023 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20023 + 16031 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16031 + 16031 SR (IS-IS) fe80::20:56ff:feff:b218 16031 + 16032 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16032 + 16033 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20033 + 16033 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16033 + 16034 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20034 + 16041 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20041 + 16041 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16041 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT14 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Head-End Up 00:00:46 + 1 L1 501 (MPLS) 5000::14/128 0 Tail-End Up 00:00:47 + 1 L1 502 (MPLS) 5000::11/128 0 Head-End Up 00:00:46 + 1 L1 503 (MPLS) 5000::14/128 0 Tail-End Up 00:00:47 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Head-End Up 00:00:46 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Tail-End - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad implicit-null + 17 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 implicit-null + 16011 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16011 + 16012 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16012 + 16013 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16013 + 16014 SR (IS-IS) lo - + 16021 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20021 + 16021 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16021 + 16022 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20022 + 16022 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16022 + 16023 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20023 + 16031 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20031 + 16031 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16031 + 16032 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20032 + 16032 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16032 + 16033 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20033 + 16034 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20034 + 16041 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20041 + 16500 PPR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20500 + 16501 PPR (IS-IS) lo - + 16502 PPR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20502 + 16503 PPR (IS-IS) lo - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::4c7b:a1ff:fe66:6ca7, eth-rt23, 00:00:02 + +Router RT21 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:49 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:48 + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:49 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:48 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:49 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:48 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::b886:2cff:fe84:a76f implicit-null + 17 SR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 implicit-null + 18 SR (IS-IS) fe80::e877:a2ff:feb7:4438 implicit-null + 19 SR (IS-IS) fe80::a0c2:82ff:fe39:204c implicit-null + 20 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 implicit-null + 16011 SR (IS-IS) fe80::e877:a2ff:feb7:4438 16011 + 16012 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16012 + 16013 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16013 + 16013 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16013 + 16014 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16014 + 16014 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16014 + 16021 SR (IS-IS) lo - + 16022 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16022 + 16023 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16023 + 16031 SR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 16031 + 16032 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16032 + 16033 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16033 + 16033 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16033 + 16034 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16034 + 16034 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16034 + 16041 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16041 + 16500 PPR (IS-IS) fe80::e877:a2ff:feb7:4438 16500 + 16501 PPR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16501 + 16502 PPR (IS-IS) fe80::e877:a2ff:feb7:4438 16502 + 16503 PPR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::e877:a2ff:feb7:4438, eth-rt11, 00:00:04 + I>* 6000:2::1/128 [115/50] via fe80::ac6a:8aff:fe14:4f36, eth-rt22, 00:00:04 + +Router RT22 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:50 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:50 + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:50 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:50 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 implicit-null + 17 SR (IS-IS) fe80::c436:63ff:feb3:4f5d implicit-null + 18 SR (IS-IS) fe80::56:41ff:fe53:a6b2 implicit-null + 19 SR (IS-IS) fe80::b423:eaff:fea1:8247 implicit-null + 20 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 implicit-null + 21 SR (IS-IS) fe80::7402:b8ff:fee9:682e implicit-null + 16011 SR (IS-IS) fe80::b423:eaff:fea1:8247 16011 + 16011 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 16011 + 16012 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 16012 + 16013 SR (IS-IS) fe80::c436:63ff:feb3:4f5d 16013 + 16014 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20014 + 16014 SR (IS-IS) fe80::c436:63ff:feb3:4f5d 16014 + 16021 SR (IS-IS) fe80::b423:eaff:fea1:8247 16021 + 16022 SR (IS-IS) lo - + 16023 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20023 + 16031 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16031 + 16031 SR (IS-IS) fe80::b423:eaff:fea1:8247 16031 + 16032 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16032 + 16033 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16033 + 16034 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16034 + 16034 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20034 + 16041 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16041 + 16041 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16041 + 16500 PPR (IS-IS) fe80::b423:eaff:fea1:8247 16500 + 16501 PPR (IS-IS) fe80::56:41ff:fe53:a6b2 20501 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::b423:eaff:fea1:8247, eth-rt21, 00:00:06 + I>* 6000:2::1/128 [115/50] via fe80::56:41ff:fe53:a6b2, eth-rt23, 00:00:06 + +Router RT23 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:52 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:52 + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:52 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:52 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:52 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:52 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::c4ca:41ff:fe2d:de8c implicit-null + 17 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 implicit-null + 18 SR (IS-IS) fe80::5c15:8aff:feea:1d07 implicit-null + 19 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f implicit-null + 20 SR (IS-IS) fe80::d0dc:6eff:fe71:9f19 implicit-null + 20011 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16011 + 20011 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16011 + 20012 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16012 + 20012 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16012 + 20013 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16013 + 20014 SR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16014 + 20021 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16021 + 20022 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16022 + 20023 SR (IS-IS) lo - + 20031 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16031 + 20031 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16031 + 20032 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16032 + 20032 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16032 + 20033 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16033 + 20034 SR (IS-IS) fe80::d0dc:6eff:fe71:9f19 16034 + 20041 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16041 + 20500 PPR (IS-IS) fe80::5c15:8aff:feea:1d07 16500 + 20501 PPR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16501 + 20502 PPR (IS-IS) fe80::d0dc:6eff:fe71:9f19 16502 + 20503 PPR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::5c15:8aff:feea:1d07, eth-rt22, 00:00:07 + I>* 6000:2::1/128 [115/50] via fe80::c4ca:41ff:fe2d:de8c, eth-rt14, 00:00:07 + +Router RT31 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:54 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:54 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 implicit-null + 17 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 implicit-null + 16011 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16011 + 16012 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16012 + 16013 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16013 + 16013 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16013 + 16014 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16014 + 16014 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16014 + 16021 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16021 + 16022 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16022 + 16022 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16022 + 16023 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16023 + 16023 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16023 + 16031 SR (IS-IS) lo - + 16032 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16032 + 16033 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16033 + 16034 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16034 + 16041 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16041 + 16502 PPR (IS-IS) fe80::a067:c6ff:fe2c:3385 16502 + 16503 PPR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT32 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:55 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:55 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::881f:d3ff:febd:9e8c implicit-null + 17 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 implicit-null + 18 SR (IS-IS) fe80::9863:abff:fed0:d7e implicit-null + 19 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 implicit-null + 20 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 implicit-null + 21 SR (IS-IS) fe80::40c4:e6ff:fe26:767f implicit-null + 16011 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16011 + 16012 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16012 + 16012 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16012 + 16013 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16013 + 16014 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16014 + 16014 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16014 + 16014 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16014 + 16021 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16021 + 16022 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16022 + 16023 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16023 + 16023 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16023 + 16023 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16023 + 16031 SR (IS-IS) fe80::9863:abff:fed0:d7e 16031 + 16032 SR (IS-IS) lo - + 16033 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16033 + 16033 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16033 + 16034 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16034 + 16034 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16034 + 16041 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 16041 + 16502 PPR (IS-IS) fe80::9863:abff:fed0:d7e 16502 + 16503 PPR (IS-IS) fe80::a4e9:77ff:feaa:f690 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT33 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:57 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:57 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::2832:a9ff:fec3:7078 implicit-null + 17 SR (IS-IS) fe80::7806:e1ff:fe72:9b1f implicit-null + 18 SR (IS-IS) fe80::5476:31ff:fe94:c39 implicit-null + 19 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 implicit-null + 20 SR (IS-IS) fe80::68c9:2ff:fe04:5eba implicit-null + 21 SR (IS-IS) fe80::d053:97ff:fee2:1711 implicit-null + 16011 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16011 + 16011 SR (IS-IS) fe80::5476:31ff:fe94:c39 16011 + 16011 SR (IS-IS) fe80::d053:97ff:fee2:1711 16011 + 16012 SR (IS-IS) fe80::d053:97ff:fee2:1711 16012 + 16013 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20013 + 16013 SR (IS-IS) fe80::d053:97ff:fee2:1711 16013 + 16014 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20014 + 16021 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16021 + 16021 SR (IS-IS) fe80::5476:31ff:fe94:c39 16021 + 16021 SR (IS-IS) fe80::d053:97ff:fee2:1711 16021 + 16022 SR (IS-IS) fe80::d053:97ff:fee2:1711 16022 + 16023 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20023 + 16031 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16031 + 16031 SR (IS-IS) fe80::5476:31ff:fe94:c39 16031 + 16032 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16032 + 16032 SR (IS-IS) fe80::5476:31ff:fe94:c39 16032 + 16033 SR (IS-IS) lo - + 16034 SR (IS-IS) fe80::7806:e1ff:fe72:9b1f 16034 + 16041 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 16041 + 16502 PPR (IS-IS) fe80::a4e9:77ff:feaa:f690 16502 + 16503 PPR (IS-IS) fe80::7806:e1ff:fe72:9b1f 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT34 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:59 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:59 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::ac33:5dff:fe99:81ec implicit-null + 17 SR (IS-IS) fe80::f009:b9ff:fe05:e540 implicit-null + 16011 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16011 + 16011 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20011 + 16012 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16012 + 16012 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20012 + 16013 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20013 + 16014 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20014 + 16021 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16021 + 16021 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20021 + 16022 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16022 + 16022 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20022 + 16023 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20023 + 16031 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16031 + 16032 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16032 + 16033 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16033 + 16034 SR (IS-IS) lo - + 16041 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16041 + 16502 PPR (IS-IS) fe80::ac33:5dff:fe99:81ec 16502 + 16503 PPR (IS-IS) fe80::f009:b9ff:fe05:e540 20503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT41 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:01:01 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:01:01 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 implicit-null + 17 SR (IS-IS) fe80::2832:a9ff:fec3:7078 implicit-null + 16011 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16011 + 16012 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16012 + 16012 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16012 + 16013 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16013 + 16013 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16013 + 16014 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16014 + 16021 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16021 + 16022 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16022 + 16022 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16022 + 16023 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16023 + 16031 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16031 + 16032 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16032 + 16033 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16033 + 16034 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16034 + 16041 SR (IS-IS) lo - + 16502 PPR (IS-IS) fe80::2832:a9ff:fec3:7078 16502 + 16503 PPR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Notice how R23 uses a different SRGB compared to the other routers in +the network. As such, this router install different labels for PPR-IDs +500 and 501 (e.g. 20500 instead of 16500 using the default SRGB). + +Verification - Forwarding Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Ping Host 3 from Host2 and use tcpdump or wireshark to verify that the +ICMP packets are being tunneled using MPLS LSPs and following the {R11 - +R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and +R21: + +.. figure:: https://user-images.githubusercontent.com/931662/64057179-2e980080-cb70-11e9-89c3-ff43e6d66cae.png + :alt: wireshark + + wireshark + +Using ``traceroute`` it’s also possible to see that the ICMP packets are +being tunneled through the IS-IS network: + +:: + + root@host2:~# traceroute -n fd00:20:1::1 -s fd00:10:2::1 + traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets + 1 fd00:10:2::100 1.996 ms 1.832 ms 1.725 ms + 2 * * * + 3 * * * + 4 * * * + 5 * * * + 6 * * * + 7 * * * + 8 fd00:20::100 0.154 ms 0.191 ms 0.116 ms + 9 fd00:20:1::1 0.125 ms 0.105 ms 0.104 ms diff --git a/doc/developer/northbound/retrofitting-configuration-commands.rst b/doc/developer/northbound/retrofitting-configuration-commands.rst new file mode 100644 index 0000000..d328be9 --- /dev/null +++ b/doc/developer/northbound/retrofitting-configuration-commands.rst @@ -0,0 +1,1928 @@ + +.. _nb-retrofit: + +Retrofitting Configuration Commands +=================================== + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 2 + +Retrofitting process +-------------------- + +This page explains how to convert existing CLI configuration commands to +the new northbound model. This documentation is meant to be the primary +reference for developers working on the northbound retrofitting process. +We’ll show several examples taken from the ripd northbound conversion to +illustrate some concepts described herein. + +Step 1: writing a YANG module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first step is to write a YANG module that models faithfully the +commands that are going to be converted. As explained in the +[[Architecture]] page, the goal is to introduce the new YANG-based +Northbound API without introducing backward incompatible changes in the +CLI. The northbound retrofitting process should be completely +transparent to FRR users. + +The developer is free to choose whether to write a full YANG module or a +partial YANG module and increment it gradually. For developers who lack +experience with YANG it’s probably a better idea to model one command at +time. + +It’s recommended to reuse definitions from standard YANG models whenever +possible to facilitate the process of writing module translators using +the [[YANG module translator]]. As an example, the frr-ripd YANG module +incorporated several parts of the IETF RIP YANG module. The repositories +below contain big collections of YANG models that might be used as a +reference: + +* https://github.com/YangModels/yang + +* https://github.com/openconfig/public + +When writing a YANG module, it’s highly recommended to follow the +guidelines from `RFC 6087 <https://tools.ietf.org/html/rfc6087>`__. In +general, most commands should be modeled fairly easy. Here are a few +guidelines specific to authors of FRR YANG models: + +* Use presence-containers or lists to model commands that change the CLI node + (e.g. ``router rip``, ``interface eth0``). This way, if the presence-container + or list entry is removed, all configuration options below them are removed + automatically (exactly like the CLI behaves when a configuration object is + removed using a *no* command). This recommendation is orthogonal to the `YANG + authoring guidelines for OpenConfig models + <https://github.com/openconfig/public/blob/master/doc/openconfig_style_guide.md>`__ + where the use of presence containers is discouraged. OpenConfig YANG models + however were not designed to replicate the behavior of legacy CLI commands. + +* When using YANG lists, be careful to identify what should be the key leaves. + In the ``offset-list WORD <in|out> (0-16) IFNAME`` command, for example, both + the direction (``<in|out>``) and the interface name should be the keys of the + list. This can be only known by analyzing the data structures used to store + the commands. + +* For clarity, use non-presence containers to group leaves that are associated + to the same configuration command (as we’ll see later, this also facilitate + the process of writing ``cli_show`` callbacks). + +* YANG leaves of type *enumeration* should define explicitly the value of each + *enum* option based on the value used in the FRR source code. + +* Default values should be taken from the source code whenever they exist. + +Some commands are more difficult to model and demand the use of more +advanced YANG constructs like *choice*, *when* and *must* statements. +**One key requirement is that it should be impossible to load an invalid +JSON/XML configuration to FRR**. The YANG modules should model exactly +what the CLI accepts in the form of commands, and all restrictions +imposed by the CLI should be defined in the YANG models whenever +possible. As we’ll see later, not all constraints can be expressed using +the YANG language and sometimes we’ll need to resort to code-level +validation in the northbound callbacks. + + Tip: the :doc:`yang-tools` page details several tools and commands that + might be useful when writing a YANG module, like validating YANG + files, indenting YANG files, validating instance data, etc. + +In the example YANG snippet below, we can see the use of the *must* +statement that prevents ripd from redistributing RIP routes into itself. +Although ripd CLI doesn’t allow the operator to enter *redistribute rip* +under *router rip*, we don’t have the same protection when configuring +ripd using other northbound interfaces (e.g. NETCONF). So without this +constraint it would be possible to feed an invalid configuration to ripd +(i.e. a bug). + +.. code:: yang + + list redistribute { + key "protocol"; + description + "Redistributes routes learned from other routing protocols."; + leaf protocol { + type frr-route-types:frr-route-types-v4; + description + "Routing protocol."; + must '. != "rip"'; + } + [snip] + } + +In the example below, we use the YANG *choice* statement to ensure that +either the ``password`` leaf or the ``key-chain`` leaf is configured, +but not both. This is in accordance to the sanity checks performed by +the *ip rip authentication* commands. + +.. code:: yang + + choice authentication-data { + description + "Choose whether to use a simple password or a key-chain."; + leaf authentication-password { + type string { + length "1..16"; + } + description + "Authentication string."; + } + leaf authentication-key-chain { + type string; + description + "Key-chain name."; + } + } + +Once finished, the new YANG model should be put into the FRR *yang/* top +level directory. This will ensure it will be installed automatically by +``make install``. It’s also encouraged (but not required) to put sample +configurations under *yang/examples/* using either JSON or XML files. + +Step 2: generate skeleton northbound callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the *gen_northbound_callbacks* tool to generate skeleton callbacks +for the YANG module. Example: + +.. code:: sh + + $ tools/gen_northbound_callbacks frr-ripd > ripd/rip_northbound.c + +The tool will look for the given module in the ``YANG_MODELS_PATH`` +directory defined during the installation. For each schema node of the +YANG module, the tool will generate skeleton callbacks based on the +properties of the node. Example: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance + */ + static int ripd_instance_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + /* TODO: implement me. */ + return NB_OK; + } + + static int ripd_instance_delete(enum nb_event event, + const struct lyd_node *dnode) + { + /* TODO: implement me. */ + return NB_OK; + } + + /* + * XPath: /frr-ripd:ripd/instance/allow-ecmp + */ + static int ripd_instance_allow_ecmp_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + /* TODO: implement me. */ + return NB_OK; + } + + [snip] + + const struct frr_yang_module_info frr_ripd_info = { + .name = "frr-ripd", + .nodes = { + { + .xpath = "/frr-ripd:ripd/instance", + .cbs.create = ripd_instance_create, + .cbs.delete = ripd_instance_delete, + }, + { + .xpath = "/frr-ripd:ripd/instance/allow-ecmp", + .cbs.modify = ripd_instance_allow_ecmp_modify, + }, + [snip] + { + .xpath = "/frr-ripd:ripd/state/routes/route", + .cbs.get_next = ripd_state_routes_route_get_next, + .cbs.get_keys = ripd_state_routes_route_get_keys, + .cbs.lookup_entry = ripd_state_routes_route_lookup_entry, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/prefix", + .cbs.get_elem = ripd_state_routes_route_prefix_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/next-hop", + .cbs.get_elem = ripd_state_routes_route_next_hop_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/interface", + .cbs.get_elem = ripd_state_routes_route_interface_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/metric", + .cbs.get_elem = ripd_state_routes_route_metric_get_elem, + }, + { + .xpath = "/frr-ripd:clear-rip-route", + .cbs.rpc = clear_rip_route_rpc, + }, + [snip] + +After the C source file is generated, it’s necessary to add a copyright +header on it and indent the code using ``clang-format``. + +Step 3: update the *frr_yang_module_info* array of all relevant daemons +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We must inform the northbound about which daemons will implement the new +YANG module. This is done by updating the ``frr_daemon_info`` structure +of these daemons, with help of the ``FRR_DAEMON_INFO`` macro. + +When a YANG module is specific to a single daemon, like the frr-ripd +module, then only the corresponding daemon should be updated. When the +YANG module is related to a subset of libfrr (e.g. route-maps), then all +FRR daemons that make use of that subset must be updated. + +Example: + +.. code:: c + + static const struct frr_yang_module_info *ripd_yang_modules[] = { + &frr_interface_info, + &frr_ripd_info, + }; + + FRR_DAEMON_INFO(ripd, RIP, .vty_port = RIP_VTY_PORT, + [snip] + .yang_modules = ripd_yang_modules, + .n_yang_modules = array_size(ripd_yang_modules), ) + +Step 4: implement the northbound configuration callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Implementing the northbound configuration callbacks consists mostly of +copying code from the corresponding CLI commands and make the required +adaptations. + +It’s recommended to convert one command or a small group of related +commands per commit. Small commits are preferred to facilitate the +review process. Both “old” and “new” command can coexist without +problems, so the retrofitting process can happen gradually over time. + +The configuration callbacks +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These are the four main northbound configuration callbacks, as defined +in the ``lib/northbound.h`` file: + +.. code:: c + + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or leaf of type + * empty has been created. + * + * For presence-containers and list entries, the callback is supposed to + * initialize the default values of its children (if any) from the YANG + * models. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being created. + * + * resource + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*create)(enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource); + + /* + * Configuration callback. + * + * The value of a leaf has been modified. + * + * List keys don't need to implement this callback. When a list key is + * modified, the northbound treats this as if the list was deleted and a + * new one created with the updated key value. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being modified + * + * resource + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*modify)(enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource); + + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or optional leaf + * has been deleted. + * + * The callback is supposed to delete the entire configuration object, + * including its children when they exist. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being deleted. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*delete)(enum nb_event event, const struct lyd_node *dnode); + + /* + * Configuration callback. + * + * A list entry or leaf-list entry has been moved. Only applicable when + * the "ordered-by user" statement is present. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being moved. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*move)(enum nb_event event, const struct lyd_node *dnode); + +Since skeleton northbound callbacks are generated automatically by the +*gen_northbound_callbacks* tool, the developer doesn’t need to worry +about which callbacks need to be implemented. + + NOTE: once a daemon starts, it reads its YANG modules and validates + that all required northbound callbacks were implemented. If any + northbound callback is missing, an error is logged and the program + exists. + +Transaction phases +^^^^^^^^^^^^^^^^^^ + +Configuration transactions and their phases were described in detail in +the [[Architecture]] page. Here’s the definition of the ``nb_event`` +enumeration as defined in the *lib/northbound.h* file: + +.. code:: c + + /* Northbound events. */ + enum nb_event { + /* + * The configuration callback is supposed to verify that the changes are + * valid and can be applied. + */ + NB_EV_VALIDATE, + + /* + * The configuration callback is supposed to prepare all resources + * required to apply the changes. + */ + NB_EV_PREPARE, + + /* + * Transaction has failed, the configuration callback needs to release + * all resources previously allocated. + */ + NB_EV_ABORT, + + /* + * The configuration changes need to be applied. The changes can't be + * rejected at this point (errors are logged and ignored). + */ + NB_EV_APPLY, + }; + +When converting a CLI command, we must identify all error-prone +operations and perform them in the ``NB_EV_PREPARE`` phase of the +northbound callbacks. When the operation in question involves the +allocation of a specific resource (e.g. file descriptors), we can store +the allocated resource in the ``resource`` variable given to the +callback. This way the allocated resource can be obtained in the other +phases of the transaction using the same parameter. + +Here’s the ``create`` northbound callback associated to the +``router rip`` command: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance + */ + static int ripd_instance_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + int socket; + + switch (event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + socket = rip_create_socket(); + if (socket < 0) + return NB_ERR_RESOURCE; + resource->fd = socket; + break; + case NB_EV_ABORT: + socket = resource->fd; + close(socket); + break; + case NB_EV_APPLY: + socket = resource->fd; + rip_create(socket); + break; + } + + return NB_OK; + } + +Note that the socket creation is an error-prone operation since it +depends on the underlying operating system, so the socket must be +created during the ``NB_EV_PREPARE`` phase and stored in +``resource->fd``. This socket is then either closed or used depending on +the outcome of the preparation phase of the whole transaction. + +During the ``NB_EV_VALIDATE`` phase, the northbound callbacks must +validate if the intended changes are valid. As an example, FRR doesn’t +allow the operator to deconfigure active interfaces: + +.. code:: c + + static int lib_interface_delete(enum nb_event event, + const struct lyd_node *dnode) + { + struct interface *ifp; + + ifp = yang_dnode_get_entry(dnode); + + switch (event) { + case NB_EV_VALIDATE: + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { + zlog_warn("%s: only inactive interfaces can be deleted", + __func__); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if_delete(ifp); + break; + } + + return NB_OK; + } + +Note however that it’s preferred to use YANG to model the validation +constraints whenever possible. Code-level validations should be used +only to validate constraints that can’t be modeled using the YANG +language. + +Most callbacks don’t need to perform any validations nor perform any +error-prone operations, so in these cases we can use the following +pattern to return early if ``event`` is different than ``NB_EV_APPLY``: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/distance/default + */ + static int ripd_instance_distance_default_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + if (event != NB_EV_APPLY) + return NB_OK; + + rip->distance = yang_dnode_get_uint8(dnode, NULL); + + return NB_OK; + } + +During development it’s recommend to use the *debug northbound* command +to debug configuration transactions and see what callbacks are being +called. Example: + +:: + + ripd# conf t + ripd(config)# debug northbound + ripd(config)# router rip + ripd(config-router)# allow-ecmp + ripd(config-router)# network eth0 + ripd(config-router)# redistribute ospf metric 2 + ripd(config-router)# commit + % Configuration committed successfully. + + ripd(config-router)# + +Now the ripd log: + +:: + + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [apply_finish] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(null)] + +Getting the data +^^^^^^^^^^^^^^^^ + +One parameter that is common to all northbound configuration callbacks +is the ``dnode`` parameter. This is a libyang data node structure that +contains information relative to the configuration change that is being +performed. For ``create`` callbacks, it contains the configuration node +that is being added. For ``delete`` callbacks, it contains the +configuration node that is being deleted. For ``modify`` callbacks, it +contains the configuration node that is being modified. + +In order to get the actual data value out of the ``dnode`` variable, we +need to use the ``yang_dnode_get_*()`` wrappers documented in +*lib/yang_wrappers.h*. + +The advantage of passing a ``dnode`` structure to the northbound +callbacks is that the whole candidate being committed is made available, +so the callbacks can obtain values from other portions of the +configuration if necessary. This can be done by providing an xpath +expression to the second parameter of the ``yang_dnode_get_*()`` +wrappers to specify the element we want to get. The example below shows +a callback that gets the values of two leaves that are part of the same +list entry: + +.. code:: c + + static int + ripd_instance_redistribute_metric_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + int type; + uint8_t metric; + + if (event != NB_EV_APPLY) + return NB_OK; + + type = yang_dnode_get_enum(dnode, "../protocol"); + metric = yang_dnode_get_uint8(dnode, NULL); + + rip->route_map[type].metric_config = true; + rip->route_map[type].metric = metric; + rip_redistribute_conf_update(type); + + return NB_OK; + } + +.. + + NOTE: if the wrong ``yang_dnode_get_*()`` wrapper is used, the code + will log an error and abort. An example would be using + ``yang_dnode_get_enum()`` to get the value of a boolean data node. + +No need to check if the configuration value has changed +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A common pattern in CLI commands is this: + +.. code:: c + + DEFUN (...) + { + [snip] + if (new_value == old_value) + return CMD_SUCCESS; + [snip] + } + +Several commands need to check if the new value entered by the user is +the same as the one currently configured. Then, if yes, ignore the +command since nothing was changed. + +The northbound callbacks on the other hand don’t need to perform this +check since they act on effective configuration changes. Using the CLI +as an example, if the operator enters the same command multiple times, +the northbound layer will detect that nothing has changed in the +configuration and will avoid calling the northbound callbacks +unnecessarily. + +In some cases, however, it might be desirable to check for +inconsistencies and notify the northbound when that happens: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/interface + */ + static int ripd_instance_interface_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + const char *ifname; + + if (event != NB_EV_APPLY) + return NB_OK; + + ifname = yang_dnode_get_string(dnode, NULL); + + return rip_enable_if_add(ifname); + } + +.. code:: c + + /* Add interface to rip_enable_if. */ + int rip_enable_if_add(const char *ifname) + { + int ret; + + ret = rip_enable_if_lookup(ifname); + if (ret >= 0) + return NB_ERR_INCONSISTENCY; + + vector_set(rip_enable_interface, + XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); + + rip_enable_apply_all(); /* TODOVJ */ + + return NB_OK; + } + +In the example above, the ``rip_enable_if_add()`` function should never +return ``NB_ERR_INCONSISTENCY`` in normal conditions. This is because +the northbound layer guarantees that the same interface will never be +added more than once (except when it’s removed and re-added again). But +to be on the safe side it’s probably wise to check for internal +inconsistencies to ensure everything is working as expected. + +Default values +^^^^^^^^^^^^^^ + +Whenever creating a new presence-container or list entry, it’s usually +necessary to initialize certain variables to their default values. FRR +most of the time uses special constants for that purpose +(e.g. ``RIP_DEFAULT_METRIC_DEFAULT``, ``DFLT_BGP_HOLDTIME``, etc). Now +that we have YANG models, we want to fetch the default values from these +models instead. This will allow us to changes default values smoothly +without needing to touch the code. Better yet, it will allow users to +create YANG deviations to define custom default values easily. + +To fetch default values from the loaded YANG models, use the +``yang_get_default_*()`` wrapper functions +(e.g. ``yang_get_default_bool()``) documented in *lib/yang_wrappers.h*. + +Example: + +.. code:: c + + int rip_create(int socket) + { + rip = XCALLOC(MTYPE_RIP, sizeof(struct rip)); + + /* Set initial values. */ + rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE); + rip->default_metric = + yang_get_default_uint8("%s/default-metric", RIP_INSTANCE); + [snip] + } + +Configuration options are edited individually +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Several CLI commands edit multiple configuration options at the same +time. Some examples taken from ripd: + +* ``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` + * */frr-ripd:ripd/instance/timers/flush-interval* + * */frr-ripd:ripd/instance/timers/holddown-interval* + * */frr-ripd:ripd/instance/timers/update-interval* + +* ``distance (1-255) A.B.C.D/M [WORD]`` + * */frr-ripd:ripd/instance/distance/source/prefix* + * */frr-ripd:ripd/instance/distance/source/distance* + * */frr-ripd:ripd/instance/distance/source/access-list* + +In the new northbound model, there’s one or more separate callbacks for +each configuration option. This usually has implications when converting +code from CLI commands to the northbound commands. An example of this is +the following commit from ripd: +`7cf2f2eaf <https://github.com/opensourcerouting/frr/commit/7cf2f2eaf43ef5df294625d1ab4c708db8293510>`__. +The ``rip_distance_set()`` and ``rip_distance_unset()`` functions were +torn apart and their code split into a few different callbacks. + +For lists and presence-containers, it’s possible to use the +``yang_dnode_set_entry()`` function to attach user data to a libyang +data node, and then retrieve this value in the other callbacks (for the +same node or any of its children) using the ``yang_dnode_get_entry()`` +function. Example: + +.. code:: c + + static int ripd_instance_distance_source_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + struct prefix_ipv4 prefix; + struct route_node *rn; + + if (event != NB_EV_APPLY) + return NB_OK; + + yang_dnode_get_ipv4p(&prefix, dnode, "./prefix"); + + /* Get RIP distance node. */ + rn = route_node_get(rip_distance_table, (struct prefix *)&prefix); + rn->info = rip_distance_new(); + yang_dnode_set_entry(dnode, rn); + + return NB_OK; + } + +.. code:: c + + static int + ripd_instance_distance_source_distance_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + struct route_node *rn; + uint8_t distance; + struct rip_distance *rdistance; + + if (event != NB_EV_APPLY) + return NB_OK; + + /* Set distance value. */ + rn = yang_dnode_get_entry(dnode); + distance = yang_dnode_get_uint8(dnode, NULL); + rdistance = rn->info; + rdistance->distance = distance; + + return NB_OK; + } + +Commands that edit multiple configuration options at the same time can +also use the ``apply_finish`` optional callback, documented as follows +in the *lib/northbound.h* file: + +.. code:: c + + /* + * Optional configuration callback for YANG lists and containers. + * + * The 'apply_finish' callbacks are called after all other callbacks + * during the apply phase (NB_EV_APPLY). These callbacks are called only + * under one of the following two cases: + * * The container or a list entry has been created; + * * Any change is made within the descendants of the list entry or + * container (e.g. a child leaf was modified, created or deleted). + * + * This callback is useful in the cases where a single event should be + * triggered regardless if the container or list entry was changed once + * or multiple times. + * + * dnode + * libyang data node from the YANG list or container. + */ + void (*apply_finish)(const struct lyd_node *dnode); + +Here’s an example of how this callback can be used: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/timers/ + */ + static void ripd_instance_timers_apply_finish(const struct lyd_node *dnode) + { + /* Reset update timer thread. */ + rip_event(RIP_UPDATE_EVENT, 0); + } + +.. code:: c + + { + .xpath = "/frr-ripd:ripd/instance/timers", + .cbs.apply_finish = ripd_instance_timers_apply_finish, + .cbs.cli_show = cli_show_rip_timers, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", + .cbs.modify = ripd_instance_timers_flush_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", + .cbs.modify = ripd_instance_timers_holddown_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/update-interval", + .cbs.modify = ripd_instance_timers_update_interval_modify, + }, + +In this example, we want to call the ``rip_event()`` function only once +regardless if all RIP timers were modified or only one of them. Without +the ``apply_finish`` callback we’d need to call ``rip_event()`` in the +``modify`` callback of each timer (a YANG leaf), resulting in redundant +call to the ``rip_event()`` function if multiple timers are changed at +once. + +Bonus: libyang user types +^^^^^^^^^^^^^^^^^^^^^^^^^ + +When writing YANG modules, it’s advisable to create derived types for +data types that are used on multiple places (e.g. MAC addresses, IS-IS +networks, etc). Here’s how `RFC +7950 <https://tools.ietf.org/html/rfc7950#page-25>`__ defines derived +types: > YANG can define derived types from base types using the +“typedef” > statement. A base type can be either a built-in type or a +derived > type, allowing a hierarchy of derived types. > > A derived +type can be used as the argument for the “type” statement. > > YANG +Example: > > typedef percent { > type uint8 { > range “0 .. 100”; > } > +} > > leaf completed { > type percent; > } + +Derived types are essentially built-in types with imposed restrictions. +As an example, the ``ipv4-address`` derived type from IETF is defined +using the ``string`` built-in type with a ``pattern`` constraint (a +regular expression): + +:: + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + +Sometimes, however, it’s desirable to have a binary representation of +the derived type that is different from the associated built-in type. +Taking the ``ipv4-address`` example above, it would be more convenient +to manipulate this YANG type using ``in_addr`` structures instead of +strings. libyang allow us to do that using the user types plugin: +https://netopeer.liberouter.org/doc/libyang/master/howtoschemaplugins.html#usertypes + +Here’s how the the ``ipv4-address`` derived type is implemented in FRR +(*yang/libyang_plugins/frr_user_types.c*): + +.. code:: c + + static int ipv4_address_store_clb(const char *type_name, const char *value_str, + lyd_val *value, char **err_msg) + { + value->ptr = malloc(sizeof(struct in_addr)); + if (!value->ptr) + return 1; + + if (inet_pton(AF_INET, value_str, value->ptr) != 1) { + free(value->ptr); + return 1; + } + + return 0; + } + +.. code:: c + + struct lytype_plugin_list frr_user_types[] = { + {"ietf-inet-types", "2013-07-15", "ipv4-address", + ipv4_address_store_clb, free}, + {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone", + ipv4_address_store_clb, free}, + [snip] + {NULL, NULL, NULL, NULL, NULL} /* terminating item */ + }; + +Now, in addition to the string representation of the data value, libyang +will also store the data in the binary format we specified (an +``in_addr`` structure). + +Whenever a new derived type is implemented in FRR, it’s also recommended +to write new wrappers in the *lib/yang_wrappers.c* file +(e.g. ``yang_dnode_get_ipv4()``, ``yang_get_default_ipv4()``, etc). + +Step 5: rewrite the CLI commands as dumb wrappers around the northbound callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once the northbound callbacks are implemented, we need to rewrite the +associated CLI commands on top of the northbound layer. This is the +easiest part of the retrofitting process. + +For protocol daemons, it’s recommended to put all CLI commands on a +separate C file (e.g. *ripd/rip_cli.c*). This helps to keep the code +more clean by separating the main protocol code from the user interface. +It should also help when moving the CLI to a separate program in the +future. + +For libfrr commands, it’s not possible to centralize all commands in a +single file because the *extract.pl* script from *vtysh* treats commands +differently depending on the file in which they are defined (e.g. DEFUNs +from *lib/routemap.c* are installed using the ``VTYSH_RMAP_SHOW`` constant, +which identifies the daemons that support route-maps). In this case, the +CLI commands should be rewritten but maintained in the same file. + +Since all CLI configuration commands from FRR will need to be rewritten, +this is an excellent opportunity to rework this part of the code to make +the commands easier to maintain and extend. These are the three main +recommendations: + +#. Always use DEFPY instead of DEFUN to improve code readability +#. Always try to join multiple DEFUNs into a single DEFPY whenever possible. As + an example, there’s no need to have both ``distance (1-255) A.B.C.D/M`` and + ``distance (1-255) A.B.C.D/M WORD`` when a single ``distance (1-255) + A.B.C.D/M [WORD]`` would suffice. +#. When making a negative form of a command, put ``[no]`` in the positive form + and use ``![...]`` to mark portions of the command that should be optional + only in the ``no`` version. + +To rewrite a CLI command as a dumb wrapper around the northbound +callbacks, use the ``nb_cli_cfg_change()`` function. This function +accepts as a parameter an array of ``cli_config_change`` structures that +specify the changes that need to performed on the candidate +configuration. Here’s the declaration of this structure (taken from the +``lib/northbound_cli.h`` file): + +.. code:: c + + struct cli_config_change { + /* + * XPath (absolute or relative) of the configuration option being + * edited. + */ + char xpath[XPATH_MAXLEN]; + + /* + * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or + * NB_OP_DESTROY). + */ + enum nb_operation operation; + + /* + * New value of the configuration option. Should be NULL for typeless + * YANG data (e.g. presence-containers). For convenience, NULL can also + * be used to restore a leaf to its default value. + */ + const char *value; + }; + +The ``nb_cli_cfg_change()`` function positions the CLI command on top on +top of the northbound layer. Instead of changing the running +configuration directly, this function changes the candidate +configuration instead, as described in the [[Transactional CLI]] page. +When the transactional CLI is not in use (i.e. the default mode), then +``nb_cli_cfg_change()`` performs an implicit ``commit`` operation after +changing the candidate configuration. + + NOTE: the ``nb_cli_cfg_change()`` function clones the candidate + configuration before actually editing it. This way, if any error + happens during the editing, the original candidate is restored to + avoid inconsistencies. Either all changes from the configuration + command are performed successfully or none are. It’s like a + mini-transaction but happening on the candidate configuration (thus + the northbound callbacks are not involved). + +Other important details to keep in mind while rewriting the CLI +commands: + +* ``nb_cli_cfg_change()`` returns CLI errors codes (e.g. ``CMD_SUCCESS``, + ``CMD_WARNING``), so the return value of this function can be used as the + return value of CLI commands. + +* Calls to ``VTY_PUSH_CONTEXT`` and ``VTY_PUSH_CONTEXT_SUB`` should be converted + to calls to ``VTY_PUSH_XPATH``. Similarly, the following macros aren’t + necessary anymore and can be removed: + + * ``VTY_DECLVAR_CONTEXT`` + * ``VTY_DECLVAR_CONTEXT_SUB`` + * ``VTY_GET_CONTEXT`` + * ``VTY_CHECK_CONTEXT``. + + The ``nb_cli_cfg_change()`` functions uses the ``VTY_CHECK_XPATH`` macro to + check if the data node being edited still exists before doing anything else. + +The examples below provide additional details about how the conversion +should be done. + +Example 1 +^^^^^^^^^ + +In this first example, the *router rip* command becomes a dumb wrapper +around the ``ripd_instance_create()`` callback. Note that we don’t need +to check if the ``/frr-ripd:ripd/instance`` data path already exists +before trying to create it. The northbound will detect when this +presence-container already exists and do nothing. The +``VTY_PUSH_XPATH()`` macro is used to change the vty node and set the +context for other commands under *router rip*. + +.. code:: c + + DEFPY_NOSH (router_rip, + router_rip_cmd, + "router rip", + "Enable a routing process\n" + "Routing Information Protocol (RIP)\n") + { + int ret; + + struct cli_config_change changes[] = { + { + .xpath = "/frr-ripd:ripd/instance", + .operation = NB_OP_CREATE, + .value = NULL, + }, + }; + + ret = nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(RIP_NODE, changes[0].xpath); + + return ret; + } + +Example 2 +^^^^^^^^^ + +Here we can see the use of relative xpaths (starting with ``./``), which +are more convenient that absolute xpaths (which would be +``/frr-ripd:ripd/instance/default-metric`` in this example). This is +possible because the use of ``VTY_PUSH_XPATH()`` in the *router rip* +command set the vty base xpath to ``/frr-ripd:ripd/instance``. + +.. code:: c + + DEFPY (rip_default_metric, + rip_default_metric_cmd, + "default-metric (1-16)", + "Set a metric of redistribute routes\n" + "Default metric\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./default-metric", + .operation = NB_OP_MODIFY, + .value = default_metric_str, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +In the command below we the ``value`` to NULL to indicate that we want +to set this leaf to its default value. This is better than hardcoding +the default value because the default might change in the future. Also, +users might define custom defaults by using YANG deviations, so it’s +better to write code that works correctly regardless of the default +values defined in the YANG models. + +.. code:: c + + DEFPY (no_rip_default_metric, + no_rip_default_metric_cmd, + "no default-metric [(1-16)]", + NO_STR + "Set a metric of redistribute routes\n" + "Default metric\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./default-metric", + .operation = NB_OP_MODIFY, + .value = NULL, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +Example 3 +^^^^^^^^^ + +This example shows how one command can change multiple leaves at the +same time. + +.. code:: c + + DEFPY (rip_timers, + rip_timers_cmd, + "timers basic (5-2147483647)$update (5-2147483647)$timeout (5-2147483647)$garbage", + "Adjust routing timers\n" + "Basic routing protocol update timers\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./timers/update-interval", + .operation = NB_OP_MODIFY, + .value = update_str, + }, + { + .xpath = "./timers/holddown-interval", + .operation = NB_OP_MODIFY, + .value = timeout_str, + }, + { + .xpath = "./timers/flush-interval", + .operation = NB_OP_MODIFY, + .value = garbage_str, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +Example 4 +^^^^^^^^^ + +This example shows how to create a list entry: + +.. code:: c + + DEFPY (rip_distance_source, + rip_distance_source_cmd, + "distance (1-255) A.B.C.D/M$prefix [WORD$acl]", + "Administrative distance\n" + "Distance value\n" + "IP source prefix\n" + "Access list name\n") + { + char xpath_list[XPATH_MAXLEN]; + struct cli_config_change changes[] = { + { + .xpath = ".", + .operation = NB_OP_CREATE, + }, + { + .xpath = "./distance", + .operation = NB_OP_MODIFY, + .value = distance_str, + }, + { + .xpath = "./access-list", + .operation = acl ? NB_OP_MODIFY : NB_OP_DESTROY, + .value = acl, + }, + }; + + snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']", + prefix_str); + + return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes)); + } + +The ``xpath_list`` variable is used to hold the xpath that identifies +the list entry. The keys of the list entry should be embedded in this +xpath and don’t need to be part of the array of configuration changes. +All entries from the ``changes`` array use relative xpaths which are +based on the xpath of the list entry. + +The ``access-list`` optional leaf can be either modified or deleted +depending whether the optional *WORD* parameter is present or not. + +When deleting a list entry, all non-key leaves can be ignored: + +.. code:: c + + DEFPY (no_rip_distance_source, + no_rip_distance_source_cmd, + "no distance (1-255) A.B.C.D/M$prefix [WORD$acl]", + NO_STR + "Administrative distance\n" + "Distance value\n" + "IP source prefix\n" + "Access list name\n") + { + char xpath_list[XPATH_MAXLEN]; + struct cli_config_change changes[] = { + { + .xpath = ".", + .operation = NB_OP_DESTROY, + }, + }; + + snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']", + prefix_str); + + return nb_cli_cfg_change(vty, xpath_list, changes, 1); + } + +Example 5 +^^^^^^^^^ + +This example shows a DEFPY statement that performs two validations +before calling ``nb_cli_cfg_change()``: + +.. code:: c + + DEFPY (ip_rip_authentication_string, + ip_rip_authentication_string_cmd, + "ip rip authentication string LINE$password", + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication string\n" + "Authentication string\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./frr-ripd:rip/authentication/password", + .operation = NB_OP_MODIFY, + .value = password, + }, + }; + + if (strlen(password) > 16) { + vty_out(vty, + "%% RIPv2 authentication string must be shorter than 16\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s", + VTY_GET_XPATH, + "/frr-ripd:rip/authentication/key-chain")) { + vty_out(vty, "%% key-chain configuration exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +These two validations are not strictly necessary since the configuration +change is validated using libyang afterwards. The issue with the libyang +validation is that the error messages from libyang are too verbose: + +:: + + ripd# conf t + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + % Failed to edit candidate configuration. + + Value "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" does not satisfy the constraint "1..16" (range, length, or pattern). + Failed to create node "authentication-password" as a child of "rip". + YANG path: /frr-interface:lib/interface[name='eth0'][vrf='Default-IP-Routing-Table']/frr-ripd:rip/authentication-password + +On the other hand, the original error message from ripd is much cleaner: + +:: + + ripd# conf t + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + % RIPv2 authentication string must be shorter than 16 + +The second validation is a bit more complex. If we try to create the +``authentication/password`` leaf when the ``authentication/key-chain`` +leaf already exists (both are under a YANG *choice* statement), libyang +will automatically delete the ``authentication/key-chain`` and create +``authentication/password`` on its place. This is different from the +original ripd behavior where the *ip rip authentication key-chain* +command must be removed before configuring the *ip rip authentication +string* command. + +In the spirit of not introducing any backward-incompatible changes in +the CLI, converted commands should retain some of their validation +checks to preserve their original behavior. + +Step 6: implement the ``cli_show`` callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The traditional method used by FRR to display the running configuration +consists of looping through all CLI nodes all call their ``func`` +callbacks one by one, which in turn read the configuration from internal +variables and dump them to the terminal in the form of CLI commands. + +The problem with this approach is twofold. First, since the callbacks +read the configuration from internal variables, they can’t display +anything other than the running configuration. Second, they don’t have +the ability to display default values when requested by the user +(e.g. *show configuration candidate with-defaults*). + +The new northbound architecture solves these problems by introducing a +new callback: ``cli_show``. Here’s the signature of this function (taken +from the *lib/northbound.h* file): + +.. code:: c + + /* + * Optional callback to show the CLI command associated to the given + * YANG data node. + * + * vty + * the vty terminal to dump the configuration to + * + * dnode + * libyang data node that should be shown in the form of a CLI + * command + * + * show_defaults + * specify whether to display default configuration values or not. + * This parameter can be ignored most of the time since the + * northbound doesn't call this callback for default leaves or + * non-presence containers that contain only default child nodes. + * The exception are commands associated to multiple configuration + * options, in which case it might be desirable to hide one or more + * parts of the command when this parameter is set to false. + */ + void (*cli_show)(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); + +One of the main differences to the old CLI ``func`` callbacks is that +the ``cli_show`` callbacks are associated to YANG data paths and not to +CLI nodes. This means we can define one separate callback for each CLI +command, making the code more modular and easier to maintain (among +other advantages that will be more clear later). For enhanced code +readability, it’s recommended to position the ``cli_show`` callbacks +immediately after their associated command definitions (DEFPYs). + +The ``cli_show`` callbacks are used by the ``nb_cli_show_config_cmds()`` +function to display configurations stored inside ``nb_config`` +structures. The configuration being displayed can be anything from the +running configuration (*show configuration running*), a candidate +configuration (*show configuration candidate*) or a rollback +configuration (*show configuration transaction (1-4294967296)*). The +``nb_cli_show_config_cmds()`` function works by iterating over all data +nodes from the given configuration and calling the ``cli_show`` callback +for the nodes where it’s defined. If a list has dozens of entries, the +``cli_show`` callback associated to this list will be called multiple +times with the ``dnode`` parameter pointing to different list entries on +each iteration. + +For backward compatibility with the *show running-config* command, we +can’t get rid of the CLI ``func`` callbacks at this point in time. +However, we can make the CLI ``func`` callbacks call the corresponding +``cli_show`` callbacks to avoid code duplication. The +``nb_cli_show_dnode_cmds()`` function can be used for that purpose. Once +the CLI retrofitting process finishes for all FRR daemons, we can remove +the legacy CLI ``func`` callbacks and turn *show running-config* into a +shorthand for *show configuration running*. + +Regarding displaying configuration with default values, this is +something that is taken care of by the ``nb_cli_show_config_cmds()`` +function itself. When the *show configuration* command is used without +the *with-defaults* option, ``nb_cli_show_config_cmds()`` will skip +calling ``cli_show`` callbacks for data nodes that contain only default +values (e.g. default leaves or non-presence containers that contain only +default child nodes). There are however some exceptional cases where the +implementer of the ``cli_show`` callback should take into consideration +if default values should be displayed or not. This and other concepts +will be explained in more detail in the examples below. + +.. _example-1-1: + +Example 1 +^^^^^^^^^ + +Command: ``default-metric (1-16)`` + +YANG representation: + +.. code:: yang + + leaf default-metric { + type uint8 { + range "1..16"; + } + default "1"; + description + "Default metric of redistributed routes."; + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance/default-metric", + .cbs.modify = ripd_instance_default_metric_modify, + + .cbs.cli_show = cli_show_rip_default_metric, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " default-metric %s\n", + yang_dnode_get_string(dnode, NULL)); + } + +In this first example, the *default-metric* command was modeled using a +YANG leaf, and we added a new ``cli_show`` callback attached to the YANG +path of this leaf. + +The callback makes use of the ``yang_dnode_get_string()`` function to +obtain the string value of the configuration option. The following would +also be possible: + +.. code:: c + + vty_out(vty, " default-metric %u\n", + yang_dnode_get_uint8(dnode, NULL)); + +Both options are possible because libyang stores both a binary +representation and a textual representation of all values stored in a +data node (``lyd_node``). For simplicity, it’s recommended to always use +``yang_dnode_get_string()`` in the ``cli_show`` callbacks. + +.. _example-2-1: + +Example 2 +^^^^^^^^^ + +Command: ``router rip`` + +YANG representation: + +.. code:: yang + + container instance { + presence "Present if the RIP protocol is enabled."; + description + "RIP routing instance."; + [snip] + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance", + .cbs.create = ripd_instance_create, + .cbs.delete = ripd_instance_delete, + + .cbs.cli_show = cli_show_router_rip, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, "!\n"); + vty_out(vty, "router rip\n"); + } + +In this example, the ``cli_show`` callback doesn’t need to obtain any +value from the ``dnode`` parameter since presence-containers don’t hold +any data (apart from their child nodes, but they have their own +``cli_show`` callbacks). + +.. _example-3-1: + +Example 3 +^^^^^^^^^ + +Command: ``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` + +YANG representation: + +.. code:: yang + + container timers { + description + "Settings of basic timers"; + leaf flush-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "120"; + description + "Interval before a route is flushed from the routing + table."; + } + leaf holddown-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "180"; + description + "Interval before better routes are released."; + } + leaf update-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "30"; + description + "Interval at which RIP updates are sent."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + + .xpath = "/frr-ripd:ripd/instance/timers", + + .cbs.cli_show = cli_show_rip_timers, + + }, + + { + .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", + .cbs.modify = ripd_instance_timers_flush_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", + .cbs.modify = ripd_instance_timers_holddown_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/update-interval", + .cbs.modify = ripd_instance_timers_update_interval_modify, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " timers basic %s %s %s\n", + yang_dnode_get_string(dnode, "./update-interval"), + yang_dnode_get_string(dnode, "./holddown-interval"), + yang_dnode_get_string(dnode, "./flush-interval")); + } + +This command is a bit different since it changes three leaves at the +same time. This means we need to have a single ``cli_show`` callback in +order to display the three leaves together in the same line. + +The new ``cli_show_rip_timers()`` callback was added attached to the +*timers* non-presence container that groups the three leaves. Without +the *timers* non-presence container we’d need to display the *timers +basic* command inside the ``cli_show_router_rip()`` callback, which +would break our requirement of having a separate ``cli_show`` callback +for each configuration command. + +.. _example-4-1: + +Example 4 +^^^^^^^^^ + +Command: +``redistribute <kernel|connected|static|ospf|isis|bgp|eigrp|nhrp|table|vnc|babel|sharp> [{metric (0-16)|route-map WORD}]`` + +YANG representation: + +.. code:: yang + + list redistribute { + key "protocol"; + description + "Redistributes routes learned from other routing protocols."; + leaf protocol { + type frr-route-types:frr-route-types-v4; + description + "Routing protocol."; + must '. != "rip"'; + } + leaf route-map { + type string { + length "1..max"; + } + description + "Applies the conditions of the specified route-map to + routes that are redistributed into the RIP routing + instance."; + } + leaf metric { + type uint8 { + range "0..16"; + } + description + "Metric used for the redistributed route. If a metric is + not specified, the metric configured with the + default-metric attribute in RIP router configuration is + used. If the default-metric attribute has not been + configured, the default metric for redistributed routes + is 0."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance/redistribute", + .cbs.create = ripd_instance_redistribute_create, + .cbs.delete = ripd_instance_redistribute_delete, + + .cbs.cli_show = cli_show_rip_redistribute, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/route-map", + .cbs.modify = ripd_instance_redistribute_route_map_modify, + .cbs.delete = ripd_instance_redistribute_route_map_delete, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/metric", + .cbs.modify = ripd_instance_redistribute_metric_modify, + .cbs.delete = ripd_instance_redistribute_metric_delete, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " redistribute %s", + yang_dnode_get_string(dnode, "./protocol")); + if (yang_dnode_exists(dnode, "./metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "./metric")); + if (yang_dnode_exists(dnode, "./route-map")) + vty_out(vty, " route-map %s", + yang_dnode_get_string(dnode, "./route-map")); + vty_out(vty, "\n"); + } + +Similar to the previous example, the *redistribute* command changes +several leaves at the same time, and we need a single callback to +display all leaves in a single line in accordance to the CLI command. In +this case, the leaves are already grouped by a YANG list so there’s no +need to add a non-presence container. The new ``cli_show`` callback was +attached to the YANG path of the list. + +It’s also worth noting the use of the ``yang_dnode_exists()`` function +to check if optional leaves exist in the configuration before displaying +them. + +.. _example-5-1: + +Example 5 +^^^^^^^^^ + +Command: +``ip rip authentication mode <md5 [auth-length <rfc|old-ripd>]|text>`` + +YANG representation: + +.. code:: yang + + container authentication-scheme { + description + "Specify the authentication scheme for the RIP interface"; + leaf mode { + type enumeration { + [snip] + } + default "none"; + description + "Specify the authentication mode."; + } + leaf md5-auth-length { + when "../mode = 'md5'"; + type enumeration { + [snip] + } + default "20"; + description + "MD5 authentication data length."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + + { + + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme", + + .cbs.cli_show = cli_show_ip_rip_authentication_scheme, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode", + .cbs.modify = lib_interface_rip_authentication_scheme_mode_modify, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length", + .cbs.modify = lib_interface_rip_authentication_scheme_md5_auth_length_modify, + .cbs.delete = lib_interface_rip_authentication_scheme_md5_auth_length_delete, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_ip_rip_authentication_scheme(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) + { + switch (yang_dnode_get_enum(dnode, "./mode")) { + case RIP_NO_AUTH: + vty_out(vty, " no ip rip authentication mode\n"); + break; + case RIP_AUTH_SIMPLE_PASSWORD: + vty_out(vty, " ip rip authentication mode text\n"); + break; + case RIP_AUTH_MD5: + vty_out(vty, " ip rip authentication mode md5"); + if (show_defaults + || !yang_dnode_is_default(dnode, "./md5-auth-length")) { + if (yang_dnode_get_enum(dnode, "./md5-auth-length") + == RIP_AUTH_MD5_SIZE) + vty_out(vty, " auth-length rfc"); + else + vty_out(vty, " auth-length old-ripd"); + } + vty_out(vty, "\n"); + break; + } + } + +This is the most complex ``cli_show`` callback we have in ripd. Its +complexity comes from the following: + +* The ``ip rip authentication mode ...`` command changes two YANG leaves at the + same time. + +* Part of the command should be hidden when the ``show_defaults`` parameter is + set to false. + +This is the behavior we want to implement: + +:: + + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication mode md5 + ripd(config-if)# + ripd(config-if)# show configuration candidate + Configuration: + ! + [snip] + ! + interface eth0 + ip rip authentication mode md5 + ! + end + ripd(config-if)# + ripd(config-if)# show configuration candidate with-defaults + Configuration: + ! + [snip] + ! + interface eth0 + [snip] + ip rip authentication mode md5 auth-length old-ripd + ! + end + +Note that ``auth-length old-ripd`` should be hidden unless the +configuration is shown using the *with-defaults* option. This is why the +``cli_show_ip_rip_authentication_scheme()`` callback needs to consult +the value of the *show_defaults* parameter. It’s expected that only a +very small minority of all ``cli_show`` callbacks will need to consult +the *show_defaults* parameter (there’s a chance this might be the only +case!) + +In the case of the *timers basic* command seen before, we need to +display the value of all leaves even if only one of them has a value +different from the default. Hence the ``cli_show_rip_timers()`` callback +was able to completely ignore the *show_defaults* parameter. + +Step 7: consolidation +~~~~~~~~~~~~~~~~~~~~~ + +As mentioned in the fourth step, the northbound retrofitting process can +happen gradually over time, since both “old” and “new” commands can +coexist without problems. Once all commands from a given daemon were +converted, we can proceed to the consolidation step, which consists of +the following: + +* Remove the vty configuration lock, which is enabled by default in all daemons. + Now multiple users should be able to edit the configuration concurrently, + using either shared or private candidate configurations. + +* Reference commit: `57dccdb1 + <https://github.com/opensourcerouting/frr/commit/57dccdb18b799556214dcfb8943e248c0bf1f6a6>`__. + +* Stop using the qobj infrastructure to keep track of configuration objects. + This is not necessary anymore, the northbound uses a similar mechanism to keep + track of YANG data nodes in the candidate configuration. + +* Reference commit: `4e6d63ce + <https://github.com/opensourcerouting/frr/commit/4e6d63cebd988af650c1c29d0f2e5a251c8d2e7a>`__. + +* Make the daemon SIGHUP handler re-read the configuration file (and ensure it’s + not doing anything other than that). + +* Reference commit: `5e57edb4 + <https://github.com/opensourcerouting/frr/commit/5e57edb4b71ff03f9a22d9ec1412c3c5167f90cf>`__. + +Final Considerations +-------------------- + +Testing +~~~~~~~ + +Converting CLI commands to the new northbound model can be a complicated +task for beginners, but the more commands one converts, the easier it +gets. It’s highly recommended to perform as much testing as possible on +the converted commands to reduce the likelihood of introducing +regressions. Tools like topotests, ANVL and the `CLI +fuzzer <https://github.com/rwestphal/frr-cli-fuzzer>`__ can be used to +catch hidden bugs that might be present. As usual, it’s also recommended +to use valgrind and static code analyzers to catch other types of +problems like memory leaks. + +Amount of work +~~~~~~~~~~~~~~ + +The output below gives a rough estimate of the total number of +configuration commands that need to be converted per daemon: + +.. code:: sh + + $ for dir in lib zebra bgpd ospfd ospf6d isisd ripd ripngd eigrpd pimd pbrd ldpd nhrpd babeld ; do echo -n "$dir: " && cd $dir && grep -ERn "DEFUN|DEFPY" * | grep -Ev "clippy|show|clear" | wc -l && cd ..; done + lib: 302 + zebra: 181 + bgpd: 569 + ospfd: 198 + ospf6d: 99 + isisd: 126 + ripd: 64 + ripngd: 44 + eigrpd: 58 + pimd: 113 + pbrd: 9 + ldpd: 46 + nhrpd: 24 + babeld: 28 + +As it can be seen, the northbound retrofitting process will demand a lot +of work from FRR developers and should take months to complete. Everyone +is welcome to collaborate! diff --git a/doc/developer/northbound/transactional-cli.rst b/doc/developer/northbound/transactional-cli.rst new file mode 100644 index 0000000..5c495d3 --- /dev/null +++ b/doc/developer/northbound/transactional-cli.rst @@ -0,0 +1,244 @@ +Transactional CLI +================= + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 1 + +Introduction +~~~~~~~~~~~~ + +All FRR daemons have built-in support for the CLI, which can be accessed +either through local telnet or via the vty socket (e.g. by using +*vtysh*). This will not change with the introduction of the Northbound +API. However, a new command-line option will be available for all FRR +daemons: ``--tcli``. When given, this option makes the daemon start with +a transactional CLI and configuration commands behave a bit different. +Instead of editing the running configuration, they will edit the +candidate configuration. In other words, the configuration commands +won’t be applied immediately, that has to be done on a separate step +using the new ``commit`` command. + +The transactional CLI simply leverages the new capabilities provided by +the Northbound API and exposes the concept of candidate configurations +to CLI users too. When the transactional mode is not used, the +configuration commands also edit the candidate configuration, but +there’s an implicit ``commit`` after each command. + +In order for the transactional CLI to work, all configuration commands +need to be converted to the new northbound model. Commands not converted +to the new northbound model will change the running configuration +directly since they bypass the FRR northbound layer. For this reason, +starting a daemon with the transactional CLI is not advisable unless all +of its commands have already been converted. When that’s not the case, +we can run into a situation like this: + +:: + + ospfd(config)# router ospf + ospfd(config-router)# ospf router-id 1.1.1.1 + [segfault in ospfd] + +The segfault above can happen if ``router ospf`` edits the candidate +configuration but ``ospf router-id 1.1.1.1`` edits the running +configuration. The second command tries to set +``ospf->router_id_static`` but, since the previous ``router ospf`` +command hasn’t been commited yet, the ``ospf`` global variable is set to +NULL, which leads to the crash. Besides this problem, having a set of +commands that edit the candidate configuration and others that edit the +running configuration is confusing at best. The ``--tcli`` option should +be used only by developers until the northbound retrofitting process is +complete. + +Configuration modes +~~~~~~~~~~~~~~~~~~~ + +When using the transactional CLI (``--tcli``), FRR supports three +different forms of the ``configure`` command: + +* ``configure terminal``: in this mode, a single candidate configuration is + shared by all users. This means that one user might delete a configuration + object that’s being edited by another user, in which case the CLI will detect + and report the problem. If one user issues the ``commit`` command, all changes + done by all users are committed. + +* ``configure private``: users have a private candidate configuration that is + edited separately from the other users. The ``commit`` command commits only + the changes done by the user. + +* ``configure exclusive``: similar to ``configure private``, but also locks the + running configuration to prevent other users from changing it. The + configuration lock is released when the user exits the configuration mode. + +When using ``configure terminal`` or ``configure private``, the +candidate configuration being edited might become outdated if another +user commits a different candidate configuration on another session. +TODO: show image to illustrate the problem. + +New commands +~~~~~~~~~~~~ + +The list below contains the new CLI commands introduced by Northbound +API. The commands are available when a daemon is started using the +transactional CLI (``--tcli``). Currently ``vtysh`` doesn’t support any +of these new commands. + +Please refer to the [[Demos]] page to see a demo of the transactional +CLI in action. + +-------------- + +``commit check`` +'''''''''''''''' + +Check if the candidate configuration is valid or not. + +``commit [force] [comment LINE...]`` +'''''''''''''''''''''''''''''''''''' + +Commit the changes done in the candidate configuration into the running +configuration. + +Options: + +* ``force``: commit even if the candidate configuration is outdated. It’s + usually a better option to use the ``update`` command instead. + +* ``comment LINE...``: assign a comment to the configuration transaction. This + comment is displayed when viewing the recorded transactions in the output of + the ``show configuration transaction`` command. + +``discard`` +''''''''''' + +Discard the changes done in the candidate configuration. + +``configuration database max-transactions (1-100)`` +''''''''''''''''''''''''''''''''''''''''''''''''''' + +Set the maximum number of transactions to store in the rollback log. + +``configuration load <file [<json|xml> [translate WORD]] FILENAME|transaction (1-4294967296)> [replace]`` +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Load a new configuration into the candidate configuration. When loading +the configuration from a file, it’s assumed that the configuration will +be in the form of CLI commands by default. The ``json`` and ``xml`` +options can be used to load configurations in the JSON and XML formats, +respectively. It’s also possible to load a configuration from a previous +transaction by specifying the desired transaction ID +(``(1-4294967296)``). + +Options: + +* ``translate WORD``: translate the JSON/XML configuration file using the YANG + module translator. + +* ``replace``: replace the candidate by the loaded configuration. The default is + to merge the loaded configuration into the candidate configuration. + +``rollback configuration (1-4294967296)`` +''''''''''''''''''''''''''''''''''''''''' + +Roll back the running configuration to a previous configuration +identified by its transaction ID (``(1-4294967296)``). + +``show configuration candidate [<json|xml> [translate WORD]] [<with-defaults|changes>]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the candidate configuration. + +Options: + +* ``json``: show the configuration in the JSON format. +* ``xml``: show the configuration in the XML format. +* ``translate WORD``: translate the JSON/XML output using the YANG module translator. +* ``with-defaults``: show default values that are hidden by default. +* ``changes``: show only the changes done in the candidate configuration. + +``show configuration compare <candidate|running|transaction (1-4294967296)> <candidate|running|transaction (1-4294967296)> [<json|xml> [translate WORD]]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the difference between two different configurations. + +Options: + +* ``json``: show the configuration differences in the JSON format. +* ``xml``: show the configuration differences in the XML format. +* ``translate WORD``: translate the JSON/XML output using the YANG module translator. + +``show configuration running [<json|xml> [translate WORD]] [with-defaults]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the running configuration. + +Options: + +* ``json``: show the configuration in the JSON format. +* ``xml``: show the configuration in the XML format. +* ``translate WORD``: translate the JSON/XML output using the YANG module translator. +* ``with-defaults``: show default values that are hidden by default. + +NOTE: ``show configuration running`` shows only the running +configuration as known by the northbound layer. Configuration +commands not converted to the new northbound model will not be +displayed. To show the full running configuration, the legacy +``show running-config`` command must be used. + +``show configuration transaction [(1-4294967296) [<json|xml> [translate WORD]] [changes]]`` +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +When a transaction ID (``(1-4294967296)``) is given, show the +configuration associated to the previously committed transaction. + +When a transaction ID is not given, show all recorded transactions in +the rollback log. + +Options: + +* ``json``: show the configuration in the JSON format. +* ``xml``: show the configuration in the XML format. +* ``translate WORD``: translate the JSON/XML output using the YANG module translator. +* ``with-defaults``: show default values that are hidden by default. +* ``changes``: show changes compared to the previous transaction. + +``show yang module [module-translator WORD] [WORD <summary|tree|yang|yin>]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +When a YANG module is not given, show all loaded YANG modules. +Otherwise, show detailed information about the given module. + +Options: + +* ``module-translator WORD``: change the context to modules loaded by the + specified YANG module translator. +* ``summary``: display summary information about the module. +* ``tree``: display module in the tree (RFC 8340) format. +* ``yang``: display module in the YANG format. +* ``yin``: display module in the YIN format. + +``show yang module-translator`` +''''''''''''''''''''''''''''''' + +Show all loaded YANG module translators. + +``update`` +'''''''''' + +Rebase the candidate configuration on top of the latest running +configuration. Conflicts are resolved automatically by giving preference +to the changes done in the candidate configuration. + +The candidate configuration might be outdated if the running +configuration was updated after the candidate was created. + +``yang module-translator load FILENAME`` +'''''''''''''''''''''''''''''''''''''''' + +Load a YANG module translator from the filesystem. + +``yang module-translator unload WORD`` +'''''''''''''''''''''''''''''''''''''' + +Unload a YANG module translator identified by its name. diff --git a/doc/developer/northbound/yang-module-translator.rst b/doc/developer/northbound/yang-module-translator.rst new file mode 100644 index 0000000..17ae160 --- /dev/null +++ b/doc/developer/northbound/yang-module-translator.rst @@ -0,0 +1,633 @@ +YANG Module Translation +======================= + +.. contents:: Table of contents + :local: + :backlinks: entry + :depth: 1 + +Introduction +------------ + +One key requirement for the FRR northbound architecture is that it +should be possible to configure/monitor FRR using different sets of YANG +models. This is especially important considering that the industry +hasn’t reached a consensus to provide a single source of standard models +for network management. At this moment both the IETF and OpenConfig +models are widely implemented and are unlikely to converge, at least not +in the short term. In the ideal scenario, management applications should +be able to use either IETF or OpenConfig models to configure and monitor +FRR programatically (or even both at the same time!). + +But how can FRR support multiple sets of YANG models at the same time? +There must be only a single source of truth that models the existing +implementation accurately (the native models). Writing different code +paths or callbacks for different models would be inviable, it would lead +to a lot of duplicated code and extra maintenance overhead. + +In order to support different sets of YANG modules without introducing +the overhead of writing additional code, the solution is to create a +mechanism that dynamically translates YANG instance data between +non-native models to native models and vice-versa. Based on this idea, +an experimental YANG module translator was implemented within the FRR +northbound layer. The translator works by translating XPaths at runtime +using translation tables provided by the user. The translator itself is +modeled using YANG and users can create translators using simple JSON +files. + +A YANG module translator consists of two components: deviation modules +and translation tables. + +Deviation Modules +----------------- + +The first step when writing a YANG module translator is to create a +`deviations <https://tools.ietf.org/html/rfc7950#page-131>`__ module for +each module that is going be translated. This is necessary because in +most cases it won’t be possible to create a perfect translator that +covers the non-native models on their entirety. Some non-native modules +might contain nodes that can’t be mapped to a corresponding node in the +FRR native models. This is either because the corresponding +functionality is not implemented in FRR or because it’s modeled in a +different way that is incompatible. + +An an example, *ripd* doesn’t have BFD support yet, so we need to create +a YANG deviation to modify the *ietf-rip* module and remove the ``bfd`` +container from it: + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:bfd" { + deviate not-supported; + } + +In the example below, while both the *frr-ripd* and *ietf-rip* modules +support RIP authentication, they model the authentication data in +different ways, making translation not possible given the constraints of +the current module translator. A new deviation is necessary to remove +the ``authentication`` container from the *ietf-rip* module: + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:authentication" { + deviate not-supported; + } + +.. + + NOTE: it should be possible to translate the + ``ietf-rip:authentication`` container if the *frr-ripd* module is + modified to model the corresponding data in a compatible way. Another + option is to improve the module translator to make more complex + translations possible, instead of requiring one-to-one XPath + mappings. + +Sometimes creating a mapping between nodes from the native and +non-native models is possible, but the nodes have different properties +that need to be normalized to allow the translation. In the example +below, a YANG deviation is used to change the type and the default value +from a node from the ``ietf-rip`` module. + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:timers/ietf-rip:flush-interval" { + deviate replace { + default "120"; + } + deviate replace { + type uint32; + } + } + +The deviation modules allow the management applications to know which +parts of the custom modules (e.g. IETF/OC) can be used to configure and +monitor FRR. + +In order to facilitate the process of creating YANG deviation modules, +the *gen_yang_deviations* tool was created to automate part of the +process. This tool creates a “not-supported” deviation for all nodes +from the given non-native module. Example: + +:: + + $ tools/gen_yang_deviations ietf-rip > yang/ietf/frr-deviations-ietf-rip.yang + $ head -n 40 yang/ietf/frr-deviations-ietf-rip.yang + deviation "/ietf-rip:clear-rip-route" { + deviate not-supported; + } + + deviation "/ietf-rip:clear-rip-route/ietf-rip:input" { + deviate not-supported; + } + + deviation "/ietf-rip:clear-rip-route/ietf-rip:input/ietf-rip:rip-instance" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:enabled" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:route-policy" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:default-metric" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:distance" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:triggered-update-threshold" { + deviate not-supported; + } + +Once all existing nodes are listed in the deviation module, it’s easy to +check the deviations that need to be removed or modified. This is more +convenient than starting with a blank deviations module and listing +manually all nodes that need to be deviated. + +After removing and/or modifying the auto-generated deviations, the next +step is to write the module XPath translation table as we’ll see in the +next section. Before that, it’s possible to use the *yanglint* tool to +check how the non-native module looks like after applying the +deviations. Example: + +:: + + $ yanglint -f tree yang/ietf/ietf-rip@2018-02-03.yang yang/ietf/frr-deviations-ietf-rip.yang + module: ietf-rip + + augment /ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol: + +--rw rip + +--rw originate-default-route + | +--rw enabled? boolean <false> + +--rw default-metric? uint8 <1> + +--rw distance? uint8 <0> + +--rw timers + | +--rw update-interval? uint32 <30> + | +--rw holddown-interval? uint32 <180> + | +--rw flush-interval? uint32 <120> + +--rw interfaces + | +--rw interface* [interface] + | +--rw interface ietf-interfaces:interface-ref + | +--rw split-horizon? enumeration <simple> + +--ro ipv4 + +--ro neighbors + | +--ro neighbor* [ipv4-address] + | +--ro ipv4-address ietf-inet-types:ipv4-address + | +--ro last-update? ietf-yang-types:date-and-time + | +--ro bad-packets-rcvd? ietf-yang-types:counter32 + | +--ro bad-routes-rcvd? ietf-yang-types:counter32 + +--ro routes + +--ro route* [ipv4-prefix] + +--ro ipv4-prefix ietf-inet-types:ipv4-prefix + +--ro next-hop? ietf-inet-types:ipv4-address + +--ro interface? ietf-interfaces:interface-ref + +--ro metric? uint8 + + rpcs: + +---x clear-rip-route + +.. + + NOTE: the same output can be obtained using the + ``show yang module module-translator ietf ietf-rip tree`` command in + FRR once the *ietf* module translator is loaded. + +In the example above, it can be seen that the vast majority of the +*ietf-rip* nodes were removed because of the “not-supported” deviations. +When a module translator is loaded, FRR calculates the coverage of the +translator by dividing the number of YANG nodes before applying the +deviations by the number of YANG nodes after applying the deviations. +The calculated coverage is displayed in the output of the +``show yang module-translator`` command: + +:: + + ripd# show yang module-translator + Family Module Deviations Coverage (%) + ----------------------------------------------------------------------- + ietf ietf-interfaces frr-deviations-ietf-interfaces 3.92 + ietf ietf-routing frr-deviations-ietf-routing 1.56 + ietf ietf-rip frr-deviations-ietf-rip 13.60 + +As it can be seen in the output above, the *ietf* module translator +covers only ~13% of the original *ietf-rip* module. This is in part +because the *ietf-rip* module models both RIPv2 and RIPng. Also, +*ietf-rip.yang* contains several knobs that aren’t implemented in *ripd* +yet (e.g. BFD support, per-interface timers, statistics, etc). Work can +be done over time to increase the coverage to a more reasonable number. + +Translation Tables +------------------ + +Below is an example of a translator for the IETF family of models: + +.. code:: json + + { + "frr-module-translator:frr-module-translator": { + "family": "ietf", + "module": [ + { + "name": "ietf-interfaces@2018-01-09", + "deviations": "frr-deviations-ietf-interfaces", + "mappings": [ + { + "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']" + }, + { + "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']/description", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/description" + } + ] + }, + { + "name": "ietf-routing@2018-01-25", + "deviations": "frr-deviations-ietf-routing", + "mappings": [ + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']", + "native": "/frr-ripd:ripd/instance" + } + ] + }, + { + "name": "ietf-rip@2018-02-03", + "deviations": "frr-deviations-ietf-rip", + "mappings": [ + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric", + "native": "/frr-ripd:ripd/instance/default-metric" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/distance", + "native": "/frr-ripd:ripd/instance/distance/default" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/originate-default-route/enabled", + "native": "/frr-ripd:ripd/instance/default-information-originate" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/update-interval", + "native": "/frr-ripd:ripd/instance/timers/update-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/holddown-interval", + "native": "/frr-ripd:ripd/instance/timers/holddown-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/flush-interval", + "native": "/frr-ripd:ripd/instance/timers/flush-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']", + "native": "/frr-ripd:ripd/instance/interface[.='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']/split-horizon", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/frr-ripd:rip/split-horizon" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/last-update", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/last-update" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-packets-rcvd", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-packets-rcvd" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-routes-rcvd", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-routes-rcvd" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/next-hop", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/next-hop" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/interface", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/interface" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/metric", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/metric" + }, + { + "custom": "/ietf-rip:clear-rip-route", + "native": "/frr-ripd:clear-rip-route" + } + ] + } + ] + } + } + +The main motivation to use YANG itself to model YANG module translators +was a practical one: leverage *libyang* to validate the structure of the +user input (JSON files) instead of doing that manually in the +*lib/yang_translator.c* file (tedious and error-prone work). + +Module translators can be loaded using the following CLI command: + +:: + + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Module translator "ietf" loaded successfully. + +Module translators can also be loaded/unloaded programatically using the +``yang_translator_load()/yang_translator_unload()`` functions within the +northbound plugins. These functions are documented in the +*lib/yang_translator.h* file. + +Each module translator must be assigned a “family” identifier +(e.g. IETF, OpenConfig), and can contain mappings for multiple +interrelated YANG modules. The mappings consist of pairs of +custom/native XPath expressions that should be equivalent, despite +belonging to different YANG modules. + +Example: + +.. code:: json + + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric", + "native": "/frr-ripd:ripd/instance/default-metric" + }, + +The nodes pointed by the custom and native XPaths must have compatible +types. In the case of the example above, both nodes point to a YANG leaf +of type ``uint8``, so the mapping is valid. + +In the example below, the “custom” XPath points to a YANG list +(typeless), and the “native” XPath points to a YANG leaf-list of +strings. In this exceptional case, the types are also considered to be +compatible. + +.. code:: json + + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']", + "native": "/frr-ripd:ripd/instance/interface[.='KEY1']" + }, + +The ``KEY1..KEY4`` values have a special meaning and are used to +preserve the list keys while performing the XPath translation. + +Once a YANG module translator is loaded and validated at a syntactic +level using *libyang*, further validations are performed to check for +missing mappings (after loading the deviation modules) and incompatible +YANG types. Example: + +:: + + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Failed to load "/usr/local/share/yang/ietf/frr-ietf-translator.json" + + Please check the logs for more details. + +:: + + 2018/09/03 15:18:45 RIP: yang_translator_validate_cb: YANG types are incompatible (xpath: "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/default-metric") + 2018/09/03 15:18:45 RIP: yang_translator_validate_cb: missing mapping for "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/distance" + 2018/09/03 15:18:45 RIP: yang_translator_validate: failed to validate "ietf" module translator: 2 error(s) + +Overall, this translation mechanism based on XPath mappings is simple +and functional, but only to a certain extent. The native models need to +be reasonably similar to the models that are going be translated, +otherwise the translation is compromised and a good coverage can’t be +achieved. Other translation techniques must be investigated to address +this shortcoming and make it possible to create more powerful YANG +module translators. + +YANG module translators can be evaluated based on the following metrics: + +* Translation potential: is it possible to make complex translations, taking + several variables into account? + +* Complexity: measure of how easy or hard it is to write a module translator. + +* Speed: measure of how fast the translation can be achieved. Translation speed + is of fundamental importance, especially for operational data. + +* Robustness: can the translator be checked for inconsistencies at load time? A + module translator based on scripts wouldn’t fare well on this metric. + +* Round-trip conversions: can the translated data be translated back to the + original format without information loss? + +CLI Demonstration +----------------- + +As of now the only northbound client that supports the YANG module +translator is the FRR embedded CLI. The confd and sysrepo plugins need +to be extended to support the module translator, which might be used not +only for configuration data, but also for operational data, RPCs and +notifications. + +In this demonstration, we’ll use the CLI ``configuration load`` command +to load the following JSON configuration file specified using the IETF +data hierarchy: + +.. code:: json + + { + "ietf-interfaces:interfaces": { + "interface": [ + { + "description": "Engineering", + "name": "eth0" + } + ] + }, + "ietf-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "name": "main", + "type": "ietf-rip:ripv2", + "ietf-rip:rip": { + "default-metric": "2", + "distance": "80", + "interfaces": { + "interface": [ + { + "interface": "eth0", + "split-horizon": "poison-reverse" + } + ] + }, + "originate-default-route": { + "enabled": "true" + }, + "timers": { + "flush-interval": "241", + "holddown-interval": "181", + "update-interval": "31" + } + } + } + ] + } + } + } + +In order to load this configuration file, it’s necessary to load the +IETF module translator first. Then, when entering the +``configuration load`` command, the ``translate ietf`` parameters must +be given to specify that the input needs to be translated using the +previously loaded ``ietf`` module translator. Example: + +:: + + ripd(config)# configuration load file json /mnt/renato/git/frr/yang/example/ietf-rip.json + % Failed to load configuration: + + Unknown element "interfaces". + ripd(config)# + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Module translator "ietf" loaded successfully. + + ripd(config)# + ripd(config)# configuration load file json translate ietf /mnt/renato/git/frr/yang/example/ietf-rip.json + +Now let’s check the candidate configuration to see if the configuration +file was loaded successfully: + +:: + + ripd(config)# show configuration candidate + Configuration: + ! + frr version 5.1-dev + frr defaults traditional + ! + interface eth0 + description Engineering + ip rip split-horizon poisoned-reverse + ! + router rip + default-metric 2 + distance 80 + network eth0 + default-information originate + timers basic 31 181 241 + ! + end + ripd(config)# show configuration candidate json + { + "frr-interface:lib": { + "interface": [ + { + "name": "eth0", + "vrf": "default", + "description": "Engineering", + "frr-ripd:rip": { + "split-horizon": "poison-reverse" + } + } + ] + }, + "frr-ripd:ripd": { + "instance": { + "default-metric": 2, + "distance": { + "default": 80 + }, + "interface": [ + "eth0" + ], + "default-information-originate": true, + "timers": { + "flush-interval": 241, + "holddown-interval": 181, + "update-interval": 31 + } + } + } + } + +As it can be seen, the candidate configuration is identical to the one +defined in the *ietf-rip.json* file, only the structure is different. +This means that the *ietf-rip.json* file was translated successfully. + +The ``ietf`` module translator can also be used to do the translation in +other direction: transform data from the native format to the IETF +format. This is shown below by altering the output of the +``show configuration candidate json`` command using the +``translate ietf`` parameter: + +:: + + ripd(config)# show configuration candidate json translate ietf + { + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "description": "Engineering" + } + ] + }, + "ietf-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "ietf-rip:ripv2", + "name": "main", + "ietf-rip:rip": { + "interfaces": { + "interface": [ + { + "interface": "eth0", + "split-horizon": "poison-reverse" + } + ] + }, + "default-metric": 2, + "distance": 80, + "originate-default-route": { + "enabled": true + }, + "timers": { + "flush-interval": 241, + "holddown-interval": 181, + "update-interval": 31 + } + } + } + ] + } + } + } + +As expected, this output is exactly identical to the configuration +defined in the *ietf-rip.json* file. The module translator was able to +do a round-trip conversion without information loss. + +Implementation Details +---------------------- + +A different libyang context is allocated for each YANG module +translator. This is important to avoid collisions and ensure that +non-native data can’t be instantiated in the running and candidate +configurations. diff --git a/doc/developer/northbound/yang-tools.rst b/doc/developer/northbound/yang-tools.rst new file mode 100644 index 0000000..fb5a287 --- /dev/null +++ b/doc/developer/northbound/yang-tools.rst @@ -0,0 +1,114 @@ +Yang Tools +========== + +Here's some information about various tools for working with yang +models. + +yanglint cheat sheet +~~~~~~~~~~~~~~~~~~~~ + + libyang project includes a feature-rich tool called yanglint(1) for + validation and conversion of the schemas and YANG modeled data. The + source codes are located at /tools/lint and can be used to explore + how an application is supposed to use the libyang library. + yanglint(1) binary as well as its man page are installed together + with the library itself. + +Validate a YANG module: + +.. code:: sh + + $ yanglint -p <yang-search-path> module.yang + +Generate tree representation of a YANG module: + +.. code:: sh + + $ yanglint -p <yang-search-path> -f tree module.yang + +Validate JSON/XML instance data: + +.. code:: sh + + $ yanglint -p <yang-search-path> module.yang data.{json,xml} + +Convert JSON/XML instance data to another format: + +.. code:: sh + + $ yanglint -p <yang-search-path> -f xml module.yang data.json + $ yanglint -p <yang-search-path> -f json module.yang data.xml + +*yanglint* also features an interactive mode which is very useful when +needing to validate data from multiple modules at the same time. The +*yanglint* README provides several examples: +https://github.com/CESNET/libyang/blob/master/tools/lint/examples/README.md + +Man page (groff): +https://github.com/CESNET/libyang/blob/master/tools/lint/yanglint.1 + +pyang cheat sheet +~~~~~~~~~~~~~~~~~ + + pyang is a YANG validator, transformator and code generator, written + in python. It can be used to validate YANG modules for correctness, + to transform YANG modules into other formats, and to generate code + from the modules. + +Obtaining and installing pyang: + +.. code:: sh + + $ git clone https://github.com/mbj4668/pyang.git + $ cd pyang/ + $ sudo python setup.py install + +Validate a YANG module: + +.. code:: sh + + $ pyang --ietf -p <yang-search-path> module.yang + +Generate tree representation of a YANG module: + +.. code:: sh + + $ pyang -f tree -p <yang-search-path> module.yang + +Indent a YANG file: + +.. code:: sh + + $ pyang -p <yang-search-path> \ + --keep-comments -f yang --yang-canonical \ + module.yang -o module.yang + +Generate skeleton instance data: + +* XML: + + .. code:: sh + + $ pyang -p <yang-search-path> \ + -f sample-xml-skeleton --sample-xml-skeleton-defaults \ + module.yang [augmented-module1.yang ...] -o module.xml + +* JSON: + + .. code:: sh + + $ pyang -p <yang-search-path> \ + -f jsonxsl module.yang -o module.xsl + $ xsltproc -o module.json module.xsl module.xml + +Validate XML instance data (works only with YANG 1.0): + +.. code:: sh + + $ yang2dsdl -v module.xml module.yang + +vim +~~~ + +YANG syntax highlighting for vim: +https://github.com/nathanalderson/yang.vim diff --git a/doc/developer/ospf-api.rst b/doc/developer/ospf-api.rst new file mode 100644 index 0000000..41c31b2 --- /dev/null +++ b/doc/developer/ospf-api.rst @@ -0,0 +1,383 @@ +OSPF API Documentation +====================== + +Disclaimer +---------- + +The OSPF daemon contains an API for application access to the LSA database. +This API and documentation was created by Ralph Keller, originally as patch for +Zebra. Unfortunately, the page containing documentation for the API is no +longer online. This page is an attempt to recreate documentation for the API +(with lots of help from the WayBackMachine). + +Ralph has kindly licensed this documentation under GPLv2+. Please preserve the +acknowledgements at the bottom of this document. + +Introduction +------------ + +This page describes an API that allows external applications to access the +link-state database (LSDB) of the OSPF daemon. The implementation is based on +the OSPF code from FRRouting (forked from Quagga and formerly Zebra) routing +protocol suite and is subject to the GNU General Public License. The OSPF API +provides you with the following functionality: + +- Retrieval of the full or partial link-state database of the OSPF daemon. + This allows applications to obtain an exact copy of the LSDB including router + LSAs, network LSAs and so on. Whenever a new LSA arrives at the OSPF daemon, + the API module immediately informs the application by sending a message. This + way, the application is always synchronized with the LSDB of the OSPF daemon. +- Origination of own opaque LSAs (of type 9, 10, or 11) which are then + distributed transparently to other routers within the flooding scope and + received by other applications through the OSPF API. + +Opaque LSAs, which are described in :rfc:`2370`, allow you to distribute +application-specific information within a network using the OSPF protocol. The +information contained in opaque LSAs is transparent for the routing process but +it can be processed by other modules such as traffic engineering (e.g., +MPLS-TE). + +Architecture +------------ + +The following picture depicts the architecture of the Quagga/Zebra protocol +suite. The OSPF daemon is extended with opaque LSA capabilities and an API for +external applications. The OSPF core module executes the OSPF protocol by +discovering neighbors and exchanging neighbor state. The opaque module, +implemented by Masahiko Endo, provides functions to exchange opaque LSAs +between routers. Opaque LSAs can be generated by several modules such as the +MPLS-TE module or the API server module. These modules then invoke the opaque +module to flood their data to neighbors within the flooding scope. + +The client, which is an application potentially running on a different node +than the OSPF daemon, links against the OSPF API client library. This client +library establishes a socket connection with the API server module of the OSPF +daemon and uses this connection to retrieve LSAs and originate opaque LSAs. + +.. figure:: ../figures/ospf_api_architecture.png + :alt: image + + image + +The OSPF API server module works like any other internal opaque module (such as +the MPLS-TE module), but listens to connections from external applications that +want to communicate with the OSPF daemon. The API server module can handle +multiple clients concurrently. + +One of the main objectives of the implementation is to make as little changes +to the existing Zebra code as possible. + +Installation & Configuration +---------------------------- + +Download FRRouting and unpack it. + +Configure and build FRR (note that ``--enable-opaque-lsa`` also enables the +ospfapi server and ospfclient). + +:: + + % sh ./configure --enable-opaque-lsa + % make + +This should also compile the client library and sample application in +ospfclient. + +Make sure that you have enabled opaque LSAs in your configuration. Add the +``ospf opaque-lsa`` statement to your :file:`ospfd.conf`: + +:: + + ! -*- ospf -*- + ! + ! OSPFd sample configuration file + ! + ! + hostname xxxxx + password xxxxx + + router ospf + router-id 10.0.0.1 + network 10.0.0.1/24 area 1 + neighbor 10.0.0.2 + network 10.0.1.2/24 area 1 + neighbor 10.0.1.1 + ospf opaque-lsa <============ add this statement! + +Usage +----- + +In the following we describe how you can use the sample application to +originate opaque LSAs. The sample application first registers with the OSPF +daemon the opaque type it wants to inject and then waits until the OSPF daemon +is ready to accept opaque LSAs of that type. Then the client application +originates an opaque LSA, waits 10 seconds and then updates the opaque LSA with +new opaque data. After another 20 seconds, the client application deletes the +opaque LSA from the LSDB. If the clients terminates unexpectedly, the OSPF API +module will remove all the opaque LSAs that the application registered. Since +the opaque LSAs are flooded to other routers, we will see the opaque LSAs in +all routers according to the flooding scope of the opaque LSA. + +We have a very simple demo setup, just two routers connected with an ATM +point-to-point link. Start the modified OSPF daemons on two adjacent routers. +First run on msr2: + +.. code-block:: console + + # ./ospfd --apiserver -f /usr/local/etc/ospfd.conf + +And on the neighboring router msr3: + +.. code-block:: console + + # ./ospfd --apiserver -f /usr/local/etc/ospfd.conf + +Now the two routers form adjacency and start exchanging their databases. +Looking at the OSPF daemon of msr2 (or msr3), you see this: + +.. code-block:: console + + ospfd> show ip ospf database + + OSPF Router with ID (10.0.0.1) + + Router Link States (Area 0.0.0.1) + + Link ID ADV Router Age Seq# CkSum Link count + 10.0.0.1 10.0.0.1 55 0x80000003 0xc62f 2 + 10.0.0.2 10.0.0.2 55 0x80000003 0xe3e4 3 + + Net Link States (Area 0.0.0.1) + + Link ID ADV Router Age Seq# CkSum + 10.0.0.2 10.0.0.2 60 0x80000001 0x5fcb + +Now we start the sample main application that originates an opaque LSA. + +.. code-block:: console + + # cd ospfapi/apiclient + # ./main msr2 10 250 20 0.0.0.0 0.0.0.1 + +This originates an opaque LSA of type 10 (area local), with opaque type 250 +(experimental), opaque id of 20 (chosen arbitrarily), interface address 0.0.0.0 +(which is used only for opaque LSAs type 9), and area 0.0.0.1 + +Again looking at the OSPF database you see: + +.. code-block:: console + + ospfd> show ip ospf database + + OSPF Router with ID (10.0.0.1) + + Router Link States (Area 0.0.0.1) + + Link ID ADV Router Age Seq# CkSum Link count + 10.0.0.1 10.0.0.1 437 0x80000003 0xc62f 2 + 10.0.0.2 10.0.0.2 437 0x80000003 0xe3e4 3 + + Net Link States (Area 0.0.0.1) + + Link ID ADV Router Age Seq# CkSum + 10.0.0.2 10.0.0.2 442 0x80000001 0x5fcb + + Area-Local Opaque-LSA (Area 0.0.0.1) + + Opaque-Type/Id ADV Router Age Seq# CkSum + 250.0.0.20 10.0.0.1 0 0x80000001 0x58a6 <=== opaque LSA + +You can take a closer look at this opaque LSA: + +.. code-block:: console + + ospfd> show ip ospf database opaque-area + + OSPF Router with ID (10.0.0.1) + + + Area-Local Opaque-LSA (Area 0.0.0.1) + + LS age: 4 + Options: 66 + LS Type: Area-Local Opaque-LSA + Link State ID: 250.0.0.20 (Area-Local Opaque-Type/ID) + Advertising Router: 10.0.0.1 + LS Seq Number: 80000001 + Checksum: 0x58a6 + Length: 24 + Opaque-Type 250 (Private/Experimental) + Opaque-ID 0x14 + Opaque-Info: 4 octets of data + Added using OSPF API: 4 octets of opaque data + Opaque data: 1 0 0 0 <==== counter is 1 + +Note that the main application updates the opaque LSA after 10 seconds, then it +looks as follows: + +.. code-block:: console + + ospfd> show ip ospf database opaque-area + + OSPF Router with ID (10.0.0.1) + + + Area-Local Opaque-LSA (Area 0.0.0.1) + + LS age: 1 + Options: 66 + LS Type: Area-Local Opaque-LSA + Link State ID: 250.0.0.20 (Area-Local Opaque-Type/ID) + Advertising Router: 10.0.0.1 + LS Seq Number: 80000002 + Checksum: 0x59a3 + Length: 24 + Opaque-Type 250 (Private/Experimental) + Opaque-ID 0x14 + Opaque-Info: 4 octets of data + Added using OSPF API: 4 octets of opaque data + Opaque data: 2 0 0 0 <==== counter is now 2 + +Note that the payload of the opaque LSA has changed as you can see above. + +Then, again after another 20 seconds, the opaque LSA is flushed from the LSDB. + +Important note: +^^^^^^^^^^^^^^^ + +In order to originate an opaque LSA, there must be at least one active +opaque-capable neighbor. Thus, you cannot originate opaque LSAs if no neighbors +are present. If you try to originate when no neighbors are ready, you will +receive a not ready error message. The reason for this restriction is that it +might be possible that some routers have an identical opaque LSA from a +previous origination in their LSDB that unfortunately could not be flushed due +to a crash, and now if the router comes up again and starts originating a new +opaque LSA, the new opaque LSA is considered older since it has a lower +sequence number and is ignored by other routers (that consider the stalled +opaque LSA as more recent). However, if the originating router first +synchronizes the database before originating opaque LSAs, it will detect the +older opaque LSA and can flush it first. + +Protocol and Message Formats +---------------------------- + +If you are developing your own client application and you don't want to make +use of the client library (due to the GNU license restriction or whatever +reason), you can implement your own client-side message handling. The OSPF API +uses two connections between the client and the OSPF API server: One connection +is used for a synchronous request /reply protocol and another connection is +used for asynchronous notifications (e.g., LSA update, neighbor status change). + +Each message begins with the following header: + +.. figure:: ../figures/ospf_api_msghdr.png + :alt: image + + image + +The message type field can take one of the following values: + ++-------------------------------+---------+ +| Messages to OSPF daemon | Value | ++===============================+=========+ +| MSG\_REGISTER\_OPAQUETYPE | 1 | ++-------------------------------+---------+ +| MSG\_UNREGISTER\_OPAQUETYPE | 2 | ++-------------------------------+---------+ +| MSG\_REGISTER\_EVENT | 3 | ++-------------------------------+---------+ +| MSG\_SYNC\_LSDB | 4 | ++-------------------------------+---------+ +| MSG\_ORIGINATE\_REQUEST | 5 | ++-------------------------------+---------+ +| MSG\_DELETE\_REQUEST | 6 | ++-------------------------------+---------+ + ++-----------------------------+---------+ +| Messages from OSPF daemon | Value | ++=============================+=========+ +| MSG\_REPLY | 10 | ++-----------------------------+---------+ +| MSG\_READY\_NOTIFY | 11 | ++-----------------------------+---------+ +| MSG\_LSA\_UPDATE\_NOTIFY | 12 | ++-----------------------------+---------+ +| MSG\_LSA\_DELETE\_NOTIFY | 13 | ++-----------------------------+---------+ +| MSG\_NEW\_IF | 14 | ++-----------------------------+---------+ +| MSG\_DEL\_IF | 15 | ++-----------------------------+---------+ +| MSG\_ISM\_CHANGE | 16 | ++-----------------------------+---------+ +| MSG\_NSM\_CHANGE | 17 | ++-----------------------------+---------+ + +The synchronous requests and replies have the following message formats: + +.. figure:: ../figures/ospf_api_msgs1.png + :alt: image + + image + +The origin field allows origin-based filtering using the following origin +types: + ++-------------------------+---------+ +| Origin | Value | ++=========================+=========+ +| NON\_SELF\_ORIGINATED | 0 | ++-------------------------+---------+ +| SELF\_ORIGINATED | 1 | ++-------------------------+---------+ +| ANY\_ORIGIN | 2 | ++-------------------------+---------+ + +The reply message has one of the following error codes: + ++--------------------------+---------+ +| Error code | Value | ++==========================+=========+ +| API\_OK | 0 | ++--------------------------+---------+ +| API\_NOSUCHINTERFACE | -1 | ++--------------------------+---------+ +| API\_NOSUCHAREA | -2 | ++--------------------------+---------+ +| API\_NOSUCHLSA | -3 | ++--------------------------+---------+ +| API\_ILLEGALSATYPE | -4 | ++--------------------------+---------+ +| API\_ILLEGALOPAQUETYPE | -5 | ++--------------------------+---------+ +| API\_OPAQUETYPEINUSE | -6 | ++--------------------------+---------+ +| API\_NOMEMORY | -7 | ++--------------------------+---------+ +| API\_ERROR | -99 | ++--------------------------+---------+ +| API\_UNDEF | -100 | ++--------------------------+---------+ + +The asynchronous notifications have the following message formats: + +.. figure:: ../figures/ospf_api_msgs2.png + :alt: image + + image + + +.. Do not delete these acknowledgements! + +Original Acknowledgments from Ralph Keller +------------------------------------------ + +I would like to thank Masahiko Endo, the author of the opaque LSA extension +module, for his great support. His wonderful ASCII graphs explaining the +internal workings of this code, and his invaluable input proved to be crucial +in designing a useful API for accessing the link state database of the OSPF +daemon. Once, he even decided to take the plane from Tokyo to Zurich so that we +could actually meet and have face-to-face discussions, which was a lot of fun. +Clearly, without Masahiko no API would ever be completed. I also would like to +thank Daniel Bauer who wrote an opaque LSA implementation too and was willing +to test the OSPF API code in one of his projects. diff --git a/doc/developer/ospf-sr.rst b/doc/developer/ospf-sr.rst new file mode 100644 index 0000000..1c16443 --- /dev/null +++ b/doc/developer/ospf-sr.rst @@ -0,0 +1,347 @@ +OSPF Segment Routing +==================== + +This is an EXPERIMENTAL support of `RFC 8665`. +DON'T use it for production network. + +Supported Features +------------------ + +* Automatic computation of Primary and Backup Adjacency SID with + Cisco experimental remote IP address +* SRGB & SRLB configuration +* Prefix configuration for Node SID with optional NO-PHP flag (Linux + kernel support both mode) +* Node MSD configuration (with Linux Kernel >= 4.10 a maximum of 32 labels + could be stack) +* Automatic provisioning of MPLS table +* Equal Cost Multi-Path (ECMP) +* Static route configuration with label stack up to 32 labels +* TI-LFA (for P2P interfaces only) + +Interoperability +---------------- + +* Tested on various topology including point-to-point and LAN interfaces + in a mix of FRRouting instance and Cisco IOS-XR 6.0.x +* Check OSPF LSA conformity with latest wireshark release 2.5.0-rc + +Implementation details +---------------------- + +Concepts +^^^^^^^^ + +Segment Routing used 3 different OPAQUE LSA in OSPF to carry the various +information: + +* **Router Information:** flood the Segment Routing capabilities of the node. + This include the supported algorithms, the Segment Routing Global Block + (SRGB) and the Maximum Stack Depth (MSD). +* **Extended Link:** flood the Adjaceny and Lan Adjacency Segment Identifier +* **Extended Prefix:** flood the Prefix Segment Identifier + +The implementation follows previous TE and Router Information codes. It used the +OPAQUE LSA functions defined in ospf_opaque.[c,h] as well as the OSPF API. This +latter is mandatory for the implementation as it provides the Callback to +Segment Routing functions (see below) when an Extended Link / Prefix or Router +Information LSA s are received. + +Overview +^^^^^^^^ + +Following files where modified or added: + +* ospd_ri.[c,h] have been modified to add the new TLVs for Segment Routing. +* ospf_ext.[c,h] implement RFC7684 as base support of Extended Link and Prefix + Opaque LSA. +* ospf_sr.[c,h] implement the earth of Segment Routing. It adds a new Segment + Routing database to manage Segment Identifiers per Link and Prefix and + Segment Routing enable node, Callback functions to process incoming LSA and + install MPLS FIB entry through Zebra. + +The figure below shows the relation between the various files: + +* ospf_sr.c centralized all the Segment Routing processing. It receives Opaque + LSA Router Information (4.0.0.0) from ospf_ri.c and Extended Prefix + (7.0.0.X) Link (8.0.0.X) from ospf_ext.c. Once received, it parse TLVs and + SubTLVs and store information in SRDB (which is defined in ospf_sr.h). For + each received LSA, NHLFE is computed and send to Zebra to add/remove new + MPLS labels entries and FEC. New CLI configurations are also centralized in + ospf_sr.c. This CLI will trigger the flooding of new LSA Router Information + (4.0.0.0), Extended Prefix (7.0.0.X) and Link (8.0.0.X) by ospf_ri.c, + respectively ospf_ext.c. +* ospf_ri.c send back to ospf_sr.c received Router Information LSA and update + Self Router Information LSA with parameters provided by ospf_sr.c i.e. SRGB + and MSD. It use ospf_opaque.c functions to send/received these Opaque LSAs. +* ospf_ext.c send back to ospf_sr.c received Extended Prefix and Link Opaque + LSA and send self Extended Prefix and Link Opaque LSA through ospf_opaque.c + functions. + +:: + + +-----------+ +-------+ + | | | | + | ospf_sr.c +-----+ SRDB | + +-----------+ +--+ | | + | +-^-------^-+ | +-------+ + | | | | | + | | | | | + | | | | +--------+ + | | | | | + +---v----------+ | | | +-----v-------+ + | | | | | | | + | ospf_ri.c +--+ | +-------+ ospf_ext.c | + | LSA 4.0.0.0 | | | LSA 7.0.0.X | + | | | | LSA 8.0.0.X | + +---^----------+ | | | + | | +-----^-------+ + | | | + | | | + | +--------v------------+ | + | | | | + | | ZEBRA: Labels + FEC | | + | | | | + | +---------------------+ | + | | + | | + | +---------------+ | + | | | | + +---------> ospf_opaque.c <---------+ + | | + +---------------+ + + Figure 1: Overview of Segment Routing interaction + +Module interactions +^^^^^^^^^^^^^^^^^^^ + +To process incoming LSA, the code is based on the capability to call `hook()` +functions when LSA are inserted or delete to / from the LSDB and the +possibility to register particular treatment for Opaque LSA. The first point +is provided by the OSPF API feature and the second by the Opaque implementation +itself. Indeed, it is possible to register callback function for a given Opaque +LSA ID (see `ospf_register_opaque_functab()` function defined in +`ospf_opaque.c`). Each time a new LSA is added to the LSDB, the +`new_lsa_hook()` function previously register for this LSA type is called. For +Opaque LSA it is the `ospf_opaque_lsa_install_hook()`. For deletion, it is +`ospf_opaque_lsa_delete_hook()`. + +Note that incoming LSA which is already present in the LSDB will be inserted +after the old instance of this LSA remove from the LSDB. Thus, after the first +time, each incoming LSA will trigger a `delete` following by an `install`. This +is not very helpful to handle real LSA deletion. In fact, LSA deletion is done +by Flushing LSA i.e. flood LSA after setting its age to MAX_AGE. Then, a garbage +function has the role to remove all LSA with `age == MAX_AGE` in the LSDB. So, +to handle LSA Flush, the best is to look to the LSA age to determine if it is +an installation or a future deletion i.e. the flushed LSA is first store in the +LSDB with MAX_AGE waiting for the garbage collector function. + +Router Information LSAs +^^^^^^^^^^^^^^^^^^^^^^^ + +To activate Segment Routing, new CLI command `segment-routing on` has been +introduced. When this command is activated, function +`ospf_router_info_update_sr()` is called to indicate to Router Information +process that Segment Routing TLVs must be flood. Same function is called to +modify the Segment Routing Global Block (SRGB) and Maximum Stack Depth (MSD) +TLV. Only Shortest Path First (SPF) Algorithm is supported, so no possibility +to modify this TLV is offer by the code. + +When Opaque LSA Type 4 i.e. Router Information are stored in LSDB, function +`ospf_opaque_lsa_install_hook()` will call the previously registered function +`ospf_router_info_lsa_update()`. In turn, the function will simply trigger +`ospf_sr_ri_lsa_update()` or `ospf_sr_ri_lsa_delete` in function of the LSA +age. Before, it verifies that the LSA Opaque Type is 4 (Router Information). +Self Opaque LSA are not send back to the Segment Routing functions as +information are already stored. + +Extended Link Prefix LSAs +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Like for Router Information, Segment Routing is activate at the Extended +Link/Prefix level with new `segment-routing on` command. This triggers +automatically the flooding of Extended Link LSA for all ospf interfaces where +adjacency is full. For Extended Prefix LSA, the new CLI command +`segment-routing prefix ...` will trigger the flooding of Prefix SID +TLV/SubTLVs. + +When Opaque LSA Type 7 i.e. Extended Prefix and Type 8 i.e. Extended Link are +store in the LSDB, `ospf_ext_pref_update_lsa()` respectively +`ospf_ext_link_update_lsa()` are called like for Router Information LSA. In +turn, they respectively trigger `ospf_sr_ext_prefix_lsa_update()` / +`ospf_sr_ext_link_lsa_update()` or `ospf_sr_ext_prefix_lsa_delete()` / +`ospf_sr_ext_link_lsa_delete()` if the LSA age is equal to MAX_AGE. + +Zebra +^^^^^ + +When a new MPLS entry or new Forwarding Equivalent Class (FEC) must be added or +deleted in the data plane, `add_sid_nhlfe()` respectively `del_sid_nhlfe()` are +called. Once check the validity of labels, they are send to ZEBRA layer through +`ZEBRA_MPLS_LABELS_ADD` command, respectively `ZEBRA_MPLS_LABELS_DELETE` +command for deletion. This is completed by a new labelled route through +`ZEBRA_ROUTE_ADD` command, respectively `ZEBRA_ROUTE_DELETE` command. + +TI-LFA +^^^^^^ + +Experimental support for Topology Independent LFA (Loop-Free Alternate), see +for example 'draft-bashandy-rtgwg-segment-routing-ti-lfa-05'. The related +files are `ospf_ti_lfa.c/h`. + +The current implementation is rather naive and does not support the advanced +optimizations suggested in e.g. RFC7490 or RFC8102. It focuses on providing +the essential infrastructure which can also later be used to enhance the +algorithmic aspects. + +Supported features: + +* Link and node protection +* Intra-area support +* Proper use of Prefix- and Adjacency-SIDs in label stacks +* Asymmetric weights (using reverse SPF) +* Non-adjacent P/Q spaces +* Protection of Prefix-SIDs + +If configured for every SPF run the routing table is enriched with additional +backup paths for every prefix. The corresponding Prefix-SIDs are updated with +backup paths too within the OSPF SR update task. + +Informal High-Level Algorithm Description: + +:: + + p_spaces = empty_list() + + for every protected_resource (link or node): + p_space = generate_p_space(protected_resource) + p_space.q_spaces = empty_list() + + for every destination that is affected by the protected_resource: + q_space = generate_q_space(destination) + + # The label stack is stored in q_space + generate_label_stack(p_space, q_space) + + # The p_space collects all its q_spaces + p_spaces.q_spaces.add(q_space) + + p_spaces.add(p_space) + + adjust_routing_table(p_spaces) + +Possible Performance Improvements: + +* Improve overall datastructures, get away from linked lists for vertices +* Don't calculate a Q space for every destination, but for a minimum set of + backup paths that cover all destinations in the post-convergence SPF. The + thinking here is that once a backup path is known that it is also a backup + path for all nodes on the path themselves. This can be done by using the + leafs of a trimmed minimum spanning tree generated out of the post- + convergence SPF tree for that particular P space. +* For an alternative (maybe better) optimization look at + https://tools.ietf.org/html/rfc7490#section-5.2.1.3 which describes using + the Q space of the node which is affected by e.g. a link failure. Note that + this optimization is topology dependent. + +It is highly recommended to read e.g. `Segment Routing I/II` by Filsfils to +understand the basics of Ti-LFA. + +Configuration +------------- + +Linux Kernel +^^^^^^^^^^^^ + +In order to use OSPF Segment Routing, you must setup MPLS data plane. Up to +know, only Linux Kernel version >= 4.5 is supported. + +First, the MPLS modules aren't loaded by default, so you'll need to load them +yourself: + +:: + + modprobe mpls_router + modprobe mpls_gso + modprobe mpls_iptunnel + +Then, you must activate MPLS on the interface you would used: + +:: + + sysctl -w net.mpls.conf.enp0s9.input=1 + sysctl -w net.mpls.conf.lo.input=1 + sysctl -w net.mpls.platform_labels=1048575 + +The last line fix the maximum MPLS label value. + +Once OSPFd start with Segment Routing, you could check that MPLS routes are +enable with: + +:: + + ip -M route + ip route + +The first command show the MPLS LFIB table while the second show the FIB +table which contains route with MPLS label encapsulation. + +If you disable Penultimate Hop Popping with the `no-php-flag` (see below), you +MUST check that RP filter is not enable for the interface you intend to use, +especially the `lo` one. For that purpose, disable RP filtering with: + +:: + + systcl -w net.ipv4.conf.all.rp_filter=0 + sysctl -w net.ipv4.conf.lo.rp_filter=0 + +OSPFd +^^^^^ + +Here it is a simple example of configuration to enable Segment Routing. Note +that `opaque capability` and `router information` must be set to activate +Opaque LSA prior to Segment +Routing. + +:: + + router ospf + ospf router-id 192.168.1.11 + capability opaque + segment-routing on + segment-routing global-block 10000 19999 local-block 5000 5999 + segment-routing node-msd 8 + segment-routing prefix 192.168.1.11/32 index 1100 + +The first segment-routing statement enables it. The second and third one set +the SRGB and SRLB respectively, fourth line the MSD and finally, set the +Prefix SID index for a given prefix. + +Note that only prefix of Loopback interface could be configured with a Prefix +SID. It is possible to add `no-php-flag` at the end of the prefix command to +disable Penultimate Hop Popping. This advertises to peers that they MUST NOT pop +the MPLS label prior to sending the packet. + +Known limitations +----------------- + +* Runs only within default VRF +* Only single Area is supported. ABR is not yet supported +* Only SPF algorithm is supported +* Extended Prefix Range is not supported +* With NO Penultimate Hop Popping, it is not possible to express a Segment + Path with an Adjacency SID due to the impossibility for the Linux Kernel to + perform double POP instruction. + +Credits +------- + +* Author: Anselme Sawadogo <anselmesawadogo@gmail.com> +* Author: Olivier Dugeon <olivier.dugeon@orange.com> +* Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + +This work has been performed in the framework of the H2020-ICT-2014 +project 5GEx (Grant Agreement no. 671636), which is partially funded +by the European Commission. + diff --git a/doc/developer/ospf.rst b/doc/developer/ospf.rst new file mode 100644 index 0000000..837a0bd --- /dev/null +++ b/doc/developer/ospf.rst @@ -0,0 +1,13 @@ +.. _ospfd: + +***** +OSPFD +***** + +.. toctree:: + :maxdepth: 2 + + ospf-api + ospf-sr + cspf + diff --git a/doc/developer/packaging-debian.rst b/doc/developer/packaging-debian.rst new file mode 100644 index 0000000..c2c3b7e --- /dev/null +++ b/doc/developer/packaging-debian.rst @@ -0,0 +1,167 @@ +.. _packaging-debian: + +Packaging Debian +================ + +(Tested on Ubuntu 14.04, 16.04, 17.10, 18.04, Debian jessie, stretch and +buster.) + +1. Install the Debian packaging tools: + + .. code-block:: shell + + sudo apt install fakeroot debhelper devscripts + +2. Checkout FRR under an **unprivileged** user account: + + .. code-block:: shell + + git clone https://github.com/frrouting/frr.git frr + cd frr + + If you wish to build a package for a branch other than master: + + .. code-block:: shell + + git checkout <branch> + +3. Install build dependencies using the `mk-build-deps` tool from the + `devscripts` package: + + .. code-block:: shell + + sudo mk-build-deps --install --remove debian/control + + Alternatively, you can manually install build dependencies for your + platform as outlined in :ref:`building`. + +4. Install `git-buildpackage` package: + + .. code-block:: shell + + sudo apt-get install git-buildpackage + +5. (optional) Append a distribution identifier if needed (see below under + :ref:`multi-dist`.) + +6. Build Debian Binary and/or Source Packages: + + .. code-block:: shell + + gbp buildpackage --git-builder=dpkg-buildpackage --git-debian-branch="$(git rev-parse --abbrev-ref HEAD)" $options + + Where `$options` may contain any or all of the following items: + + * build profiles specified with ``-P``, e.g. + ``-Ppkg.frr.nortrlib,pkg.frr.rtrlib``. + Multiple values are separated by commas and there must not be a space + after the ``-P``. + + The following build profiles are currently available: + + +----------------+-------------------+-----------------------------------------+ + | Profile | Negation | Effect | + +================+===================+=========================================+ + | pkg.frr.rtrlib | pkg.frr.nortrlib | builds frr-rpki-rtrlib package (or not) | + +----------------+-------------------+-----------------------------------------+ + | pkg.frr.lua | pkg.frr.nolua | builds lua scripting extension | + +----------------+-------------------+-----------------------------------------+ + | pkg.frr.pim6d | pkg.frr.nopim6d | builds pim6d (default enabled) | + +----------------+-------------------+-----------------------------------------+ + + * the ``-uc -us`` options to disable signing the packages with your GPG key + + (git builds of the `master` or `stable/X.X` branches won't be signed by + default since their target release is set to ``UNRELEASED``.) + + * the ``--build=type`` accepts following options (see ``dpkg-buildpackage`` manual page): + + * ``source`` builds the source package + * ``any`` builds the architecture specific binary packages + * ``all`` build the architecture independent binary packages + * ``binary`` build the architecture specific and independent binary packages (alias for ``any,all``) + * ``full`` builds everything (alias for ``source,any,all``) + + Alternatively, you might want to replace ``dpkg-buildpackage`` with + ``debuild`` wrapper that also runs ``lintian`` and ``debsign`` on the final + packages. + +7. Done! + + If all worked correctly, then you should end up with the Debian packages in + the parent directory of where `debuild` ran. If distributed, please make sure + you distribute it together with the sources (``frr_*.orig.tar.xz``, + ``frr_*.debian.tar.xz`` and ``frr_*.dsc``) + +.. note:: + + A package created from `master` or `stable/X.X` is slightly different from + a package created from the `debian` branch. The changelog for the former + is autogenerated and sets the Debian revision to ``-0``, which causes an + intentional lintian warning. The `debian` branch on the other hand has + a manually maintained changelog that contains proper Debian release + versioning. + + +.. _multi-dist: + +Multi-Distribution builds +========================= + +You can optionally append a distribution identifier in case you want to +make multiple versions of the package available in the same repository. + +.. code-block:: shell + + dch -l '~deb8u' 'build for Debian 8 (jessie)' + dch -l '~deb9u' 'build for Debian 9 (stretch)' + dch -l '~ubuntu14.04.' 'build for Ubuntu 14.04 (trusty)' + dch -l '~ubuntu16.04.' 'build for Ubuntu 16.04 (xenial)' + dch -l '~ubuntu18.04.' 'build for Ubuntu 18.04 (bionic)' + +Between building packages for specific distributions, the only difference +in the package itself lies in the automatically generated shared library +dependencies, e.g. libjson-c2 or libjson-c3. This means that the +architecture independent packages should **not** have a suffix appended. +Also, the current Debian testing/unstable releases should not have any suffix +appended. + +For example, at the end of 2018 (i.e. ``buster``/Debian 10 is the current +"testing" release), the following is a complete list of `.deb` files for +Debian 8, 9 and 10 packages for FRR 6.0.1-1 with RPKI support:: + + frr_6.0.1-1_amd64.deb + frr_6.0.1-1~deb8u1_amd64.deb + frr_6.0.1-1~deb9u1_amd64.deb + frr-dbg_6.0.1-1_amd64.deb + frr-dbg_6.0.1-1~deb8u1_amd64.deb + frr-dbg_6.0.1-1~deb9u1_amd64.deb + frr-rpki-rtrlib_6.0.1-1_amd64.deb + frr-rpki-rtrlib_6.0.1-1~deb8u1_amd64.deb + frr-rpki-rtrlib_6.0.1-1~deb9u1_amd64.deb + frr-doc_6.0.1-1_all.deb + frr-pythontools_6.0.1-1_all.deb + +Note that there are no extra versions of the `frr-doc` and `frr-pythontools` +packages (because they are for architecture ``all``, not ``amd64``), and the +version for Debian 10 does **not** have a ``~deb10u1`` suffix. + +.. warning:: + + Do not use the ``-`` character in the version suffix. The last ``-`` in + the version number is the separator between upstream version and Debian + version. ``6.0.1-1~foobar-2`` means upstream version ``6.0.1-1~foobar``, + Debian version ``2``. This is not what you want. + + The only allowed characters in the Debian version are ``0-9 A-Z a-z + . ~`` + +.. note:: + + The separating character for the suffix **must** be the tilde (``~``) + because the tilde is ordered in version-comparison before the empty + string. That means the order of the above packages is the following: + + ``6.0.1-1`` newer than ``6.0.1-1~deb9u1`` newer than ``6.0.1-1~deb8u1`` + + If you use another character (e.g. ``+``), the untagged version will be + regarded as the "oldest"! diff --git a/doc/developer/packaging-redhat.rst b/doc/developer/packaging-redhat.rst new file mode 100644 index 0000000..d88f449 --- /dev/null +++ b/doc/developer/packaging-redhat.rst @@ -0,0 +1,98 @@ +.. _packaging-redhat: + +Packaging Red Hat +================= + +Tested on CentOS 6, CentOS 7, CentOS 8 and Fedora 24. + +1. On CentOS 6, refer to :ref:`building-centos6` for details on installing + sufficiently up-to-date package versions to enable building FRR. + + Newer automake/autoconf/bison is only needed to build the RPM and is **not** + needed to install the binary RPM package. + +2. Install the build dependencies for your platform. Refer to the + platform-specific build documentation on how to do this. + +3. Install the following additional packages:: + + yum install rpm-build net-snmp-devel pam-devel libcap-devel + + For CentOS 7 and CentOS 8, the package will be built using python3 + and requires additional python3 packages:: + + yum install python3-devel python3-sphinx + + .. note:: + + For CentOS 8 you need to install ``platform-python-devel`` package + to provide ``/usr/bin/pathfix.py``:: + + yum install platform-python-devel + + + If ``yum`` is not present on your system, use ``dnf`` instead. + + You should enable ``PowerTools`` repo if using CentOS 8 which + is disabled by default. + +4. Checkout FRR:: + + git clone https://github.com/frrouting/frr.git frr + +5. Run Bootstrap and make distribution tar.gz:: + + cd frr + ./bootstrap.sh + ./configure --with-pkg-extra-version=-MyRPMVersion + make dist + + .. note:: + + The only ``configure`` option respected when building RPMs is + ``--with-pkg-extra-version``. + +6. Create RPM directory structure and populate with sources:: + + mkdir rpmbuild + mkdir rpmbuild/SOURCES + mkdir rpmbuild/SPECS + cp redhat/*.spec rpmbuild/SPECS/ + cp frr*.tar.gz rpmbuild/SOURCES/ + +7. Edit :file:`rpm/SPECS/frr.spec` with configuration as needed. + + Look at the beginning of the file and adjust the following parameters to + enable or disable features as required:: + + ############### FRRouting (FRR) configure options ################# + # with-feature options + %{!?with_pam: %global with_pam 0 } + %{!?with_ospfclient: %global with_ospfclient 1 } + %{!?with_ospfapi: %global with_ospfapi 1 } + %{!?with_irdp: %global with_irdp 1 } + %{!?with_rtadv: %global with_rtadv 1 } + %{!?with_ldpd: %global with_ldpd 1 } + %{!?with_nhrpd: %global with_nhrpd 1 } + %{!?with_eigrp: %global with_eigrpd 1 } + %{!?with_shared: %global with_shared 1 } + %{!?with_multipath: %global with_multipath 256 } + %{!?frr_user: %global frr_user frr } + %{!?vty_group: %global vty_group frrvty } + %{!?with_fpm: %global with_fpm 0 } + %{!?with_watchfrr: %global with_watchfrr 1 } + %{!?with_bgp_vnc: %global with_bgp_vnc 0 } + %{!?with_pimd: %global with_pimd 1 } + %{!?with_pim6d: %global with_pim6d 1 } + %{!?with_rpki: %global with_rpki 0 } + +8. Build the RPM:: + + rpmbuild --define "_topdir `pwd`/rpmbuild" -ba rpmbuild/SPECS/frr.spec + + If building with RPKI, then download and install the additional RPKI + packages from + https://ci1.netdef.org/browse/RPKI-RTRLIB/latestSuccessful/artifact + +If all works correctly, then you should end up with the RPMs under +:file:`rpmbuild/RPMS` and the source RPM under :file:`rpmbuild/SRPMS`. diff --git a/doc/developer/packaging.rst b/doc/developer/packaging.rst new file mode 100644 index 0000000..0c072e4 --- /dev/null +++ b/doc/developer/packaging.rst @@ -0,0 +1,10 @@ +******************** +Releases & Packaging +******************** + +.. toctree:: + :maxdepth: 2 + + frr-release-procedure + packaging-debian + packaging-redhat diff --git a/doc/developer/path-internals-daemon.rst b/doc/developer/path-internals-daemon.rst new file mode 100644 index 0000000..29f0172 --- /dev/null +++ b/doc/developer/path-internals-daemon.rst @@ -0,0 +1,115 @@ +PATHD Internals +=============== + +Architecture +------------ + +Overview +........ + +The pathd deamon manages the segment routing policies, it owns the data +structures representing them and can load modules that manipulate them like the +PCEP module. Its responsibility is to select a candidate path for each +configured policy and to install it into Zebra. + +Zebra +..... + +Zebra manages policies that are active or pending to be activated due to the +next hop not being available yet. In zebra, policy data structures and APIs are +defined in `zebra_srte.[hc]`. + +The responsibilities of Zebra are: + + - Store the policies' segment list. + - Install the policies when their next-hop is available. + - Notify other daemons of the status of the policies. + +Adding and removing policies is done using the commands `ZEBRA_SR_POLICY_SET` +and `ZEBRA_SR_POLICY_DELETE` as parameter of the function `zebra_send_sr_policy` +all defined in `zclient.[hc]`. + +If the first segment of the policy is an unknown label, it is kept until +notified by the mpls hooks `zebra_mpls_label_created`, and then it is installed. + +To get notified when a policy status changes, a client can implement the +`sr_policy_notify_status` callback defined in `zclient.[hc]`. + +For encoding/decoding the various data structures used to comunicate with zebra, +the following functions are available from `zclient.[hc]`: +`zapi_sr_policy_encode`, `zapi_sr_policy_decode` and +`zapi_sr_policy_notify_status_decode`. + + +Pathd +..... + + +The pathd daemon manages all the possible candidate paths for the segment +routing policies and selects the best one following the +`segment routing policy draft <https://tools.ietf.org/html/draft-ietf-spring-segment-routing-policy-06#section-2.9>`_. +It also supports loadable modules for handling dynamic candidate paths and the +creation of new policies and candidate paths at runtime. + +The responsibilities of the pathd base daemon, not including any optional +modules, are: + + - Store the policies and all the possible candidate paths for them. + - Select the best candidate path for each policy and send it to Zebra. + - Provide VTYSH configuration to set up policies and candidate paths. + - Provide a Northbound API to manipulate **configured** policies and candidate paths. + - Handle loadable modules for extending the functionality. + - Provide an API to the loadable module to manipulate policies and candidate paths. + + +Threading Model +--------------- + +The daemon runs completely inside the main thread using FRR event model, there +is no threading involved. + + +Source Code +----------- + +Internal Data Structures +........................ + +The main data structures for policies and candidate paths are defined in +`pathd.h` and implemented in `pathd.c`. + +When modifying these structures, either directly or through the functions +exported by `pathd.h`, nothing should be deleted/freed right away. The deletion +or modification flags must be set and when all the changes are done, the +function `srte_apply_changes` must be called. When called, a new candidate path +may be elected and sent to Zebra, and all the structures flagged as deleted +will be freed. In addition, a hook will be called so dynamic modules can perform +any required action when the elected candidate path changes. + + +Northbound API +.............. + +The northbound API is defined in `path_nb.[ch]` and implemented in +`path_nb_config.c` for configuration data and `path_nb_state.c` for operational +data. + + +Command Line Client +................... + +The command-line client (VTYSH) is implemented in `path_cli.c`. + + +Interface with Zebra +.................... + +All the functions interfacing with Zebra are defined and implemented in +`path_zebra.[hc]`. + + +Loadable Module API +................... + +For the time being, the API the loadable module uses is defined by `pathd.h`, +but in the future, it should be moved to a dedicated include file. diff --git a/doc/developer/path-internals-pcep.rst b/doc/developer/path-internals-pcep.rst new file mode 100644 index 0000000..a6b2220 --- /dev/null +++ b/doc/developer/path-internals-pcep.rst @@ -0,0 +1,193 @@ +PCEP Module Internals +===================== + +Introduction +------------ + +The PCEP module for the pathd daemon implements the PCEP protocol described in +:rfc:`5440` to update the policies and candidate paths. + +The protocol encoding/decoding and the basic session management is handled by +the `pceplib external library 1.2 <https://github.com/volta-networks/pceplib/tree/devel-1.2>`_. + +Together with pceplib, this module supports at least partially: + + - :rfc:`5440` + + Most of the protocol defined in the RFC is implemented. + All the messages can be parsed, but this was only tested in the context + of segment routing. Only a very small subset of metric types can be + configured, and there is a known issue with some Cisco routers not + following the IANA numbers for metrics. + + - :rfc:`8231` + + Support delegation of candidate path after performing the initial + computation request. If the PCE does not respond or cannot compute + a path, an empty candidate path is delegated to the PCE. + Only tested in the context of segment routing. + + - :rfc:`8408` + + Only used to comunicate the support for segment routing to the PCE. + + - :rfc:`8664` + + All the NAI types are implemented, but only the MPLS NAI are supported. + If the PCE provide segments that are not MPLS labels, the PCC will + return an error. + +Note that pceplib supports more RFCs and drafts, see pceplib +`README <https://github.com/volta-networks/pceplib/blob/master/README.md>`_ +for more details. + + +Architecture +------------ + +Overview +........ + +The module is separated into multiple layers: + + - pathd interface + - command-line console + - controller + - PCC + - pceplib interface + +The pathd interface handles all the interactions with the daemon API. + +The command-line console handles all the VTYSH configuration commands. + +The controller manages the multiple PCC connections and the interaction between +them and the daemon interface. + +The PCC handles a single connection to a PCE through a pceplib session. + +The pceplib interface abstracts the API of the pceplib. + +.. figure:: ../figures/pcep_module_threading_overview.svg + + +Threading Model +--------------- + +The module requires multiple threads to cooperate: + + - The main thread used by the pathd daemon. + - The controller pthread used to isolate the PCC from the main thread. + - The possible threads started in the pceplib library. + +To ensure thread safety, all the controller and PCC state data structures can +only be read and modified in the controller thread, and all the global data +structures can only be read and modified in the main thread. Most of the +interactions between these threads are done through FRR timers and events. + +The controller is the bridge between the two threads, all the functions that +**MUST** be called from the main thread start with the prefix `pcep_ctrl_` and +all the functions that **MUST** be called from the controller thread start +with the prefix `pcep_thread_`. When an asynchronous action must be taken in +a different thread, an FRR event is sent to the thread. If some synchronous +operation is needed, the calling thread will block and run a callback in the +other thread, there the result is **COPIED** and returned to the calling thread. + +No function other than the controller functions defined for it should be called +from the main thread. The only exception being some utility functions from +`path_pcep_lib.[hc]`. + +All the calls to pathd API functions **MUST** be performed in the main thread, +for that, the controller sends FRR events handled in function +`path_pcep.c:pcep_main_event_handler`. + +For the same reason, the console client only runs in the main thread. It can +freely use the global variable, but **MUST** use controller's `pcep_ctrl_` +functions to interact with the PCCs. + + +Source Code +----------- + +Generic Data Structures +....................... + +The data structures are defined in multiple places, and where they are defined +dictates where they can be used. + +The data structures defined in `path_pcep.h` can be used anywhere in the module. + +Internally, throughout the module, the `struct path` data structure is used +to describe PCEP messages. It is a simplified flattened structure that can +represent multiple complex PCEP message types. The conversion from this +structure to the PCEP data structures used by pceplib is done in the pceplib +interface layer. + +The data structures defined in `path_pcep_controller.h` should only be used +in `path_pcep_controller.c`. Even if a structure pointer is passed as a parameter +to functions defined in `path_pcep_pcc.h`, these should consider it as an opaque +data structure only used to call back controller functions. + +The same applies to the structures defined in `path_pcep_pcc.h`, even if the +controller owns a reference to this data structure, it should never read or +modify it directly, it should be considered an opaque structure. + +The global data structure can be accessed from the pathd interface layer +`path_pcep.c` and the command line client code `path_pcep_cli.c`. + + +Interface With Pathd +.................... + +All the functions calling or called by the pathd daemon are implemented in +`path_pcep.c`. These functions **MUST** run in the main FRR thread, and +all the interactions with the controller and the PCCs **MUST** pass through +the controller's `pcep_ctrl_` prefixed functions. + +To handle asynchronous events from the PCCs, a callback is passed to +`pcep_ctrl_initialize` that is called in the FRR main thread context. + + +Command Line Client +................... + +All the command line configuration commands (VTYSH) are implemented in +`path_pcep_cli.c`. All the functions there run in the main FRR thread and +can freely access the global variables. All the interaction with the +controller's and the PCCs **MUST** pass through the controller `pcep_ctrl_` +prefixed functions. + + +Debugging Helpers +................. + +All the functions formating data structures for debugging and logging purposes +are implemented in `path_pcep_debug.[hc]`. + + +Interface with pceplib +...................... + +All the functions calling the pceplib external library are defined in +`path_pcep_lib.[hc]`. Some functions are called from the main FRR thread, like +`pcep_lib_initialize`, `pcep_lib_finalize`; some can be called from either +thread, like `pcep_lib_free_counters`; some function must be called from the +controller thread, like `pcep_lib_connect`. This will probably be formalized +later on with function prefix like done in the controller. + + +Controller +.......... + +The controller is defined and implemented in `path_pcep_controller.[hc]`. +Part of the controller code runs in FRR main thread and part runs in its own +FRR pthread started to isolate the main thread from the PCCs' event loop. +To communicate between the threads it uses FRR events, timers and +`event_execute` calls. + + +PCC +... + +Each PCC instance owns its state and runs in the controller thread. They are +defined and implemented in `path_pcep_pcc.[hc]`. All the interactions with +the daemon must pass through some controller's `pcep_thread_` prefixed function. diff --git a/doc/developer/path-internals.rst b/doc/developer/path-internals.rst new file mode 100644 index 0000000..2c2df0f --- /dev/null +++ b/doc/developer/path-internals.rst @@ -0,0 +1,11 @@ +.. _path_internals: + +********* +Internals +********* + +.. toctree:: + :maxdepth: 2 + + path-internals-daemon + path-internals-pcep diff --git a/doc/developer/path.rst b/doc/developer/path.rst new file mode 100644 index 0000000..b6d2438 --- /dev/null +++ b/doc/developer/path.rst @@ -0,0 +1,11 @@ +.. _path: + +***** +PATHD +***** + +.. toctree:: + :maxdepth: 2 + + path-internals + diff --git a/doc/developer/pceplib.rst b/doc/developer/pceplib.rst new file mode 100644 index 0000000..774617d --- /dev/null +++ b/doc/developer/pceplib.rst @@ -0,0 +1,781 @@ +.. _pceplib: + +******* +PCEPlib +******* + +Overview +======== + +The PCEPlib is a PCEP implementation library that can be used by either a PCE +or PCC. + +Currently, only the FRR pathd has been implemented as a PCC with the PCEPlib. +The PCEPlib is able to simultaneously connect to multiple PCEP peers and can +maintain persistent PCEP connections. + + +PCEPlib compliance +================== + +The PCEPlib implements version 1 of the PCEP protocol, according to `RFC 5440 <https://tools.ietf.org/html/rfc5440>`_. + +Additionally, the PCEPlib implements the following PCEP extensions: + +- `RFC 8281 <https://tools.ietf.org/html/rfc8281>`_ PCE initiated for PCE-Initiated LSP Setup +- `RFC 8231 <https://tools.ietf.org/html/rfc8231>`_ Extensions for Stateful PCE +- `RFC 8232 <https://tools.ietf.org/html/rfc8232>`_ Optimizations of Label Switched Path State Synchronization Procedures for a Stateful PCE +- `RFC 8282 <https://tools.ietf.org/html/rfc8282>`_ Extensions to PCEP for Inter-Layer MPLS and GMPLS Traffic Engineering +- `RFC 8408 <https://tools.ietf.org/html/rfc8408>`_ Conveying Path Setup Type in PCE Communication Protocol (PCEP) Messages +- `draft-ietf-pce-segment-routing-07 <https://tools.ietf.org/html/draft-ietf-pce-segment-routing-07>`_, + `draft-ietf-pce-segment-routing-16 <https://tools.ietf.org/html/draft-ietf-pce-segment-routing-16>`_, + `RFC 8664 <https://tools.ietf.org/html/rfc8664>`_ Segment routing protocol extensions +- `RFC 7470 <https://tools.ietf.org/html/rfc7470>`_ Conveying Vendor-Specific Constraints +- `Draft-ietf-pce-association-group-10 <https://tools.ietf.org/html/draft-ietf-pce-association-group-10>`_ + Establishing Relationships Between Sets of Label Switched Paths +- `Draft-barth-pce-segment-routing-policy-cp-04 <https://tools.ietf.org/html/draft-barth-pce-segment-routing-policy-cp-04>`_ + Segment Routing Policy Candidate Paths + + +PCEPlib Architecture +==================== + +The PCEPlib is comprised of the following modules, each of which will be +detailed in the following sections. + +- **pcep_messages** + - PCEP messages, objects, and TLVs implementations + +- **pcep_pcc** + - PCEPlib public PCC API with a sample PCC binary + +- **pcep_session_logic** + - PCEP Session handling + +- **pcep_socket_comm** + - Socket communications + +- **pcep_timers** + - PCEP timers + +- **pcep_utils** + - Internal utilities used by the PCEPlib modules. + +The interaction of these modules can be seen in the following diagram. + +PCEPlib Architecture: + +.. image:: images/PCEPlib_design.jpg + + +PCEP Session Logic library +-------------------------- + +The PCEP Session Logic library orchestrates calls to the rest of the PCC libraries. + +PCEP Session Logic library responsibilities: + +- Handle messages received from "PCEP Socket Comm" +- Create and manage "PCEP Session" objects +- Set timers and react to timer expirations +- Manage counters + +The PCEP Session Logic library will have 2 main triggers controlled by a +pthread condition variable: + +- Timer expirations - ``on_timer_expire()`` callback +- Messages received from PCEP SocketComm - ``message_received()`` callback + +The counters are created and managed using the ``pcep_utils/pcep_utils_counters.h`` +counters library. The following are the different counter groups managed: + +- **COUNTER_SUBGROUP_ID_RX_MSG** +- **COUNTER_SUBGROUP_ID_TX_MSG** +- **COUNTER_SUBGROUP_ID_RX_OBJ** +- **COUNTER_SUBGROUP_ID_TX_OBJ** +- **COUNTER_SUBGROUP_ID_RX_SUBOBJ** +- **COUNTER_SUBGROUP_ID_TX_SUBOBJ** +- **COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ** +- **COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ** +- **COUNTER_SUBGROUP_ID_RX_TLV** +- **COUNTER_SUBGROUP_ID_TX_TLV** +- **COUNTER_SUBGROUP_ID_EVENT** + +The counters can be obtained and reset as explained later in the PCEPlib PCC API. + +PCEP Socket Comm library +------------------------ + +PCEP communication can be configured to be handled internally in this simple +library. When this library is instantiated by the PCEP Session Logic, callbacks +are provided to handle received messages and error conditions. + +The following diagram illustrates how the library works. + +PCEPlib Socket Comm: + +.. image:: images/PCEPlib_socket_comm.jpg + + +PCEP Timers library +------------------- + +Timers can be configured to be handled internally by this library. When this +library is instantiated by the PCEP Session Logic, callbacks are provided to +ha:0 +ndle timer expirations. The following timers are implemented and handled, +according to `RFC 5440 <https://tools.ietf.org/html/rfc5440>`_. + +- Open KeepWait (fixed at 60 seconds) + - Set once the PCC sends an Open, and if it expires before receiving a KeepAlive or PCErr, then the PCC should send a PCErr and close the TCP connection + +- Keepalive timer + - How often the PCC should send Keepalive messages to the PCE (and vice-versa) + - The timer will be reset after any message is sent: any message serves as a Keepalive + +- DeadTimer + - If no messages are received before expiration, the session is declared as down + - Reset everytime any message is received + +- PCReq request timer + - How long the PCC waits for the PCE to reply to PCReq messages. + +PCEPlib Timers: + +.. image:: images/PCEPlib_timers.jpg + + +PCEP Messages library +--------------------- + +The PCEP Messages library has all of the implemented PCEP messages, objects, +TLVs, and related functionality. + +The following header files can be used for creating and handling received PCEP +entities. + +- pcep-messages.h +- pcep-objects.h +- pcep-tlvs.h + + +PCEP Messages ++++++++++++++ + +The following PCEP messages can be created and received: + +- ``struct pcep_message* pcep_msg_create_open(...);`` +- ``struct pcep_message* pcep_msg_create_open_with_tlvs(...);`` +- ``struct pcep_message* pcep_msg_create_request(...);`` +- ``struct pcep_message* pcep_msg_create_request_ipv6(...);`` +- ``struct pcep_message* pcep_msg_create_reply(...);`` +- ``struct pcep_message* pcep_msg_create_close(...);`` +- ``struct pcep_message* pcep_msg_create_error(...);`` +- ``struct pcep_message* pcep_msg_create_error_with_objects(...);`` +- ``struct pcep_message* pcep_msg_create_keepalive(...);`` +- ``struct pcep_message* pcep_msg_create_report(...);`` +- ``struct pcep_message* pcep_msg_create_update(...);`` +- ``struct pcep_message* pcep_msg_create_initiate(...);`` + +Refer to ``pcep_messages/include/pcep-messages.h`` and the API section +below for more details. + + +PCEP Objects +++++++++++++ + +The following PCEP objects can be created and received: + +- ``struct pcep_object_open* pcep_obj_create_open(...);`` +- ``struct pcep_object_rp* pcep_obj_create_rp(...);`` +- ``struct pcep_object_notify* pcep_obj_create_notify(...);`` +- ``struct pcep_object_nopath* pcep_obj_create_nopath(...);`` +- ``struct pcep_object_association_ipv4* pcep_obj_create_association_ipv4(...);`` +- ``struct pcep_object_association_ipv6* pcep_obj_create_association_ipv6(...);`` +- ``struct pcep_object_endpoints_ipv4* pcep_obj_create_endpoint_ipv4(...);`` +- ``struct pcep_object_endpoints_ipv6* pcep_obj_create_endpoint_ipv6(...);`` +- ``struct pcep_object_bandwidth* pcep_obj_create_bandwidth(...);`` +- ``struct pcep_object_metric* pcep_obj_create_metric(...);`` +- ``struct pcep_object_lspa* pcep_obj_create_lspa(...);`` +- ``struct pcep_object_svec* pcep_obj_create_svec(...);`` +- ``struct pcep_object_error* pcep_obj_create_error(...);`` +- ``struct pcep_object_close* pcep_obj_create_close(...);`` +- ``struct pcep_object_srp* pcep_obj_create_srp(...);`` +- ``struct pcep_object_lsp* pcep_obj_create_lsp(...);`` +- ``struct pcep_object_vendor_info* pcep_obj_create_vendor_info(...);`` +- ``struct pcep_object_ro* pcep_obj_create_ero(...);`` +- ``struct pcep_object_ro* pcep_obj_create_rro(...);`` +- ``struct pcep_object_ro* pcep_obj_create_iro(...);`` +- ``struct pcep_ro_subobj_ipv4* pcep_obj_create_ro_subobj_ipv4(...);`` +- ``struct pcep_ro_subobj_ipv6* pcep_obj_create_ro_subobj_ipv6(...);`` +- ``struct pcep_ro_subobj_unnum* pcep_obj_create_ro_subobj_unnum(...);`` +- ``struct pcep_ro_subobj_32label* pcep_obj_create_ro_subobj_32label(...);`` +- ``struct pcep_ro_subobj_asn* pcep_obj_create_ro_subobj_asn(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_nonai(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv4_node(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv6_node(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv4_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_ipv6_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj(...);`` +- ``struct pcep_ro_subobj_sr* pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj(...);`` + +Refer to ``pcep_messages/include/pcep-objects.h`` and the API section +below for more details. + + +PCEP TLVs ++++++++++ + +The following PCEP TLVs (Tag, Length, Value) can be created and received: + +- Open Object TLVs + - ``struct pcep_object_tlv_stateful_pce_capability* pcep_tlv_create_stateful_pce_capability(...);`` + - ``struct pcep_object_tlv_lsp_db_version* pcep_tlv_create_lsp_db_version(...);`` + - ``struct pcep_object_tlv_speaker_entity_identifier* pcep_tlv_create_speaker_entity_id(...);`` + - ``struct pcep_object_tlv_path_setup_type* pcep_tlv_create_path_setup_type(...);`` + - ``struct pcep_object_tlv_path_setup_type_capability* pcep_tlv_create_path_setup_type_capability(...);`` + - ``struct pcep_object_tlv_sr_pce_capability* pcep_tlv_create_sr_pce_capability(...);`` + +- LSP Object TLVs + - ``struct pcep_object_tlv_ipv4_lsp_identifier* pcep_tlv_create_ipv4_lsp_identifiers(...);`` + - ``struct pcep_object_tlv_ipv6_lsp_identifier* pcep_tlv_create_ipv6_lsp_identifiers(...);`` + - ``struct pcep_object_tlv_symbolic_path_name* pcep_tlv_create_symbolic_path_name(...);`` + - ``struct pcep_object_tlv_lsp_error_code* pcep_tlv_create_lsp_error_code(...);`` + - ``struct pcep_object_tlv_rsvp_error_spec* pcep_tlv_create_rsvp_ipv4_error_spec(...);`` + - ``struct pcep_object_tlv_rsvp_error_spec* pcep_tlv_create_rsvp_ipv6_error_spec(...);`` + - ``struct pcep_object_tlv_nopath_vector* pcep_tlv_create_nopath_vector(...);`` + - ``struct pcep_object_tlv_vendor_info* pcep_tlv_create_vendor_info(...);`` + - ``struct pcep_object_tlv_arbitrary* pcep_tlv_create_tlv_arbitrary(...);`` + +- SRPAG (SR Association Group) TLVs + - ``struct pcep_object_tlv_srpag_pol_id *pcep_tlv_create_srpag_pol_id_ipv4(...);`` + - ``struct pcep_object_tlv_srpag_pol_id *pcep_tlv_create_srpag_pol_id_ipv6(...);`` + - ``struct pcep_object_tlv_srpag_pol_name *pcep_tlv_create_srpag_pol_name(...);`` + - ``struct pcep_object_tlv_srpag_cp_id *pcep_tlv_create_srpag_cp_id(...);`` + - ``struct pcep_object_tlv_srpag_cp_pref *pcep_tlv_create_srpag_cp_pref(...);`` + +Refer to ``pcep_messages/include/pcep-tlvs.h`` and the API section +below for more details. + + +PCEP PCC +-------- + +This module has a Public PCC API library (explained in detail later) and a +sample PCC binary. The APIs in this library encapsulate other PCEPlib libraries +for simplicity. With this API, the PCEPlib PCC can be started and stopped, and +the PCEPlib event queue can be accessed. The PCEP Messages library is not +encapsulated, and should be used directly. + + +Internal Dependencies +--------------------- + +The following diagram illustrates the internal PCEPlib library dependencies. + +PCEPlib internal dependencies: + +.. image:: images/PCEPlib_internal_deps.jpg + + +External Dependencies +--------------------- + +Originally the PCEPlib was based on the open source `libpcep project <https://www.acreo.se/open-software-libpcep>`_, +but that dependency has been reduced to just one source file (pcep-tools.[ch]). + + +PCEPlib Threading model +----------------------- + +The PCEPlib can be run in stand-alone mode whereby a thread is launched for +timers and socket comm, as is illustrated in the following diagram. + +PCEPlib Threading model: + +.. image:: images/PCEPlib_threading_model.jpg + +The PCEPlib can also be configured to use an external timers and socket +infrastructure like the FRR threads and tasks. In this case, no internal +threads are launched for timers and socket comm, as is illustrated in the +following diagram. + +PCEPlib Threading model with external infra: + +.. image:: images/PCEPlib_threading_model_frr_infra.jpg + + +Building +-------- + +The autotools build system is used and integrated with the frr build system. + +Testing +------- + +The Unit Tests for an individual library are executed with the ``make check`` +command. The Unit Test binary will be written to the project ``build`` directory. +All Unit Tests are executed with Valgrind, and any memory issues reported by +Valgrind will cause the Unit Test to fail. + + +PCEPlib PCC API +=============== + +The following sections describe the PCEPlib PCC API. + + +PCEPlib PCC Initialization and Destruction +------------------------------------------ + +The PCEPlib can be initialized to handle memory, timers, and socket comm +internally in what is called stand-alone mode, or with an external +infrastructure, like FRR. + +PCEPlib PCC Initialization and Destruction in stand-alone mode +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +PCEPlib PCC initialization and destruction functions: + +- ``bool initialize_pcc();`` +- ``bool initialize_pcc_wait_for_completion();`` +- ``bool destroy_pcc();`` + +The PCC can be initialized with either ``initialize_pcc()`` or +``initialize_pcc_wait_for_completion()``. + +- ``initialize_pcc_wait_for_completion()`` blocks until ``destroy_pcc()`` + is called from a separate pthread. +- ``initialize_pcc()`` is non-blocking and will be stopped when + ``destroy_pcc()`` is called. + +Both initialize functions will launch 3 pthreads: + +- 1 Timer pthread +- 1 SocketComm pthread +- 1 SessionLogic pthread + +When ``destroy_pcc()`` is called, all pthreads will be stopped and all +resources will be released. + +All 3 functions return true upon success, and false otherwise. + +PCEPlib PCC Initialization and Destruction with FRR infrastructure +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +PCEPlib PCC initialization and destruction functions: + +- ``bool initialize_pcc_infra(struct pceplib_infra_config *infra_config);`` +- ``bool destroy_pcc();`` + +The ``pceplib_infra_config`` struct has the following fields: + +- **void *pceplib_infra_mt** + - FRR Memory type pointer for infra related memory management + +- **void *pceplib_messages_mt** + - FRR Memory type pointer for PCEP messages related memory management + +- **pceplib_malloc_func mfunc** + - FRR malloc function pointer + +- **pceplib_calloc_func cfunc** + - FRR calloc function pointer + +- **pceplib_realloc_func rfunc** + - FRR realloc function pointer + +- **pceplib_strdup_func sfunc** + - FRR strdup function pointer + +- **pceplib_free_func ffunc** + - FRR free function pointer + +- **void *external_infra_data** + - FRR data used by FRR timers and sockets infrastructure + +- **ext_timer_create timer_create_func** + - FRR timer create function pointer + +- **ext_timer_cancel timer_cancel_func** + - FRR timer cancel function pointer + +- **ext_socket_write socket_write_func** + - FRR socket write function pointer, indicating fd is ready to be written to + +- **ext_socket_read socket_read_func** + - FRR socket write function pointer, indicating fd is ready to be read from + + +PCEPlib PCC configuration +------------------------- + +PCEPlib PCC configuratoin functions: + +- ``pcep_configuration *create_default_pcep_configuration();`` +- ``void destroy_pcep_configuration(pcep_configuration *config);`` + +A ``pcep_configuration`` object with default values is created with +``create_default_pcep_configuration()``. These values can be tailored to +specific use cases. + +Created ``pcep_configuration`` objects are destroyed with +``destroy_pcep_configuration()``. + + +PCEPlib PCC configuration paramaters +++++++++++++++++++++++++++++++++++++ + +The ``pcep_configuration`` object is defined in ``pcep_session_logic/include/pcep_session_logic.h`` +The attributes in the ``pcep_configuration`` object are detailed as follows. + +PCEP Connection parameters: + +- **dst_pcep_port** + - Defaults to 0, in which case the default PCEP TCP destination port + 4189 will be used. + - Set to use a specific PCEP TCP destination port. + +- **src_pcep_port** + - Defaults to 0, in which case the default PCEP TCP source port + 4189 will be used. + - Set to use a specific PCEP TCP source port. + +- **Source IP** + - Defaults to IPv4 INADDR_ANY + - Set **src_ip.src_ipv4** and **is_src_ipv6=false** to set the source IPv4. + - Set **src_ip.src_ipv6** and **is_src_ipv6=true** to set the source IPv6. + +- **socket_connect_timeout_millis** + - Maximum amount of time to wait to connect to the PCE TCP socket + before failing, in milliseconds. + +PCEP Versioning: + +- **pcep_msg_versioning->draft_ietf_pce_segment_routing_07** + - Defaults to false, in which case draft 16 versioning will be used. + - Set to true to use draft 07 versioning. + +PCEP Open Message Parameters: + +- **keep_alive_seconds** + - Sent to PCE in PCEP Open Msg + - Recommended value = 30, Minimum value = 1 + - Disabled by setting value = 0 + +- **dead_timer_seconds** + - Sent to PCE in PCEP Open Msg + - Recommended value = 4 * keepalive timer value + +- Supported value ranges for PCEP Open Message received from the PCE + - **min_keep_alive_seconds**, **max_keep_alive_seconds** + - **min_dead_timer_seconds**, **max_dead_timer_seconds** + +- **request_time_seconds** + - When a PCC sends a PcReq to a PCE, the amount of time a PCC will + wait for a PcRep reply from the PCE. + +- **max_unknown_requests** + - If a PCC/PCE receives PCRep/PCReq messages with unknown requests + at a rate equal or greater than MAX-UNKNOWN-REQUESTS per minute, + the PCC/PCE MUST send a PCEP CLOSE message. + - Recommended value = 5 + +- **max_unknown_messages** + - If a PCC/PCE receives unrecognized messages at a rate equal or + greater than MAX-UNKNOWN-MESSAGES per minute, the PCC/PCE MUST + send a PCEP CLOSE message + - Recommended value = 5 + +Stateful PCE Capability TLV configuration parameters (RFC 8231, 8232, 8281, and +draft-ietf-pce-segment-routing-16): + +- **support_stateful_pce_lsp_update** + - If this flag is true, then a Stateful PCE Capability TLV will + be added to the PCEP Open object, with the LSP Update Capability + U-flag set true. + - The rest of these parameters are used to configure the Stateful + PCE Capability TLV + +- **support_pce_lsp_instantiation** + - Sets the I-flag true, indicating the PCC allows instantiation + of an LSP by a PCE. + +- **support_include_db_version** + - Sets the S-bit true, indicating the PCC will include the + LSP-DB-VERSION TLV in each LSP object. See lsp_db_version below. + +- **support_lsp_triggered_resync** + - Sets the T-bit true, indicating the PCE can trigger resynchronization + of LSPs at any point in the life of the session. + +- **support_lsp_delta_sync** + - Sets the D-bit true, indicating the PCEP speaker allows incremental + (delta) State Synchronization. + +- **support_pce_triggered_initial_sync** + - Sets the F-bit true, indicating the PCE SHOULD trigger initial (first) + State Synchronization + +LSP DB Version TLV configuration parameters: + +- **lsp_db_version** + - If this parameter has a value other than 0, and the above + support_include_db_version flag is true, then an LSP DB + Version TLV will be added to the PCEP Open object. + - This parameter should only be set if LSP-DB survived a restart + and is available. + - This value will be copied over to the pcep_session upon initialization. + +SR PCE Capability sub-TLV configuration parameters (draft-ietf-pce-segment-routing-16): + +- **support_sr_te_pst** + - If this flag is true, then an SR PCE Capability sub-TLV will be + added to a Path Setup type Capability TLV, which will be added + to the PCEP Open object. + - The PST used in the Path Setup type Capability will be 1, + indicating the Path is setup using Segment Routing Traffic Engineering. + +Only set the following fields if the **support_sr_te_pst** flag is true. + +- **pcc_can_resolve_nai_to_sid** + - Sets the N-flag true, indicating that the PCC is capable of resolving + a Node or Adjacency Identifier to a SID + +- **max_sid_depth** + - If set other than 0, then the PCC imposes a limit on the Maximum + SID depth. + - If this parameter is other than 0, then the X bit will be true, + and the parameter value will be set in the MSD field. + + +PCEPlib PCC connections +----------------------- + +PCEPlib PCC connect and disconnect functions: + +- ``pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip);`` +- ``pcep_session *connect_pce_ipv6(pcep_configuration *config, struct in6_addr *pce_ip);`` +- ``void disconnect_pce(pcep_session *session);`` + +When connecting to a PCE, a ``pcep_session`` will be returned on success, NULL +otherwise. + +Refer to the above PCC configuration parameters section for setting the source +and destination PCEP TCP ports, and the source IP address and version. + + +PCEP Messages, Objects, and TLVs +-------------------------------- + +The PCEP messages, objects, and TLVs created in the PCEPlib are high-level API +structures, meaning they need to be encoded before being sent on-the-wire, and +the raw data received needs to be decoded into these structures. This makes +using these objects much easier for the library consumer, since they do not +need to know the detailed raw format of the PCEP entities. + + +PCEP Messages ++++++++++++++ + +Received messages (in the ``pcep_event`` explained below) are of type +``pcep_message``, which have the following fields: + +- ``struct pcep_message_header *msg_header;`` + - Defines the PCEP version and message type + +- ``double_linked_list *obj_list;`` + - A double linked list of the message objects + - Each entry is a pointer to a ``struct pcep_object_header``, and + using the ``object_class`` and ``object_type`` fields, the pointer + can be cast to the appropriate object structure to access the + rest of the object fields + +- ``uint8_t *encoded_message;`` + - This field is only populated for received messages or once the + ``pcep_encode_message()`` function has been called on the message. + - This field is a pointer to the raw PCEP data for the entire + message, including all objects and TLVs. + +- ``uint16_t encoded_message_length;`` + - This field is only populated for received messages or once the + ``pcep_encode_message()`` function has been called on the message. + - This field is the length of the entire raw message, including + all objects and TLVs. + - This field is in host byte order. + + +PCEP Objects +++++++++++++ + +A PCEP message has a double linked list of pointers to ``struct pcep_object_header`` +structures, which have the following fields: + +- ``enum pcep_object_classes object_class;`` +- ``enum pcep_object_types object_type;`` +- ``bool flag_p;`` + - PCC Processing rule bit: When set, the object MUST be taken into + account, when cleared the object is optional + +- ``bool flag_i;`` + - PCE Ignore bit: indicates to a PCC whether or not an optional + object was processed + +- ``double_linked_list *tlv_list;`` + - A double linked list of the object TLVs + - Each entry is a pointer to a ``struct pcep_object_tlv_header``, and + using the TLV type field, the pointer can be cast to the + appropriate TLV structure to access the rest of the TLV fields + +- ``uint8_t *encoded_object;`` + - This field is only populated for received objects or once the + ``pcep_encode_object()`` (called by ``pcep_encode_message()``) + function has been called on the object. + - Pointer into the encoded_message field (from the pcep_message) + where the raw object PCEP data starts. + +- ``uint16_t encoded_object_length;`` + - This field is only populated for received objects or once the + ``pcep_encode_object()`` (called by ``pcep_encode_message()``) + function has been called on the object. + - This field is the length of the entire raw TLV + - This field is in host byte order. + +The object class and type can be used to cast the ``struct pcep_object_header`` +pointer to the appropriate object structure so the specific object fields can +be accessed. + + +PCEP TLVs ++++++++++ + +A PCEP object has a double linked list of pointers to ``struct pcep_object_tlv_header`` +structures, which have the following fields: + +- ``enum pcep_object_tlv_types type;`` +- ``uint8_t *encoded_tlv;`` + - This field is only populated for received TLVs or once the + ``pcep_encode_tlv()`` (called by ``pcep_encode_message()``) + function has been called on the TLV. + - Pointer into the encoded_message field (from the pcep_message) + where the raw TLV PCEP data starts. + +- ``uint16_t encoded_tlv_length;`` + - This field is only populated for received TLVs or once the + ``pcep_encode_tlv()`` (called by ``pcep_encode_message()``) + function has been called on the TLV. + - This field is the length of the entire raw TLV + - This field is in host byte order. + + +Memory management ++++++++++++++++++ + +Any of the PCEPlib Message Library functions that receive a pointer to a +``double_linked_list``, ``pcep_object_header``, or ``pcep_object_tlv_header``, +transfer the ownership of the entity to the PCEPlib. The memory will be freed +internally when the encapsulating structure is freed. If the memory for any of +these is freed by the caller, then there will be a double memory free error +when the memory is freed internally in the PCEPlib. + +Any of the PCEPlib Message Library functions that receive either a pointer to a +``struct in_addr`` or ``struct in6_addr`` will allocate memory for the IP +address internally and copy the IP address. It is the responsibility of the +caller to manage the memory for the IP address passed into the PCEPlib Message +Library functions. + +For messages received via the event queue (explained below), the message will +be freed when the event is freed by calling ``destroy_pcep_event()``. + +When sending messages, the message will be freed internally in the PCEPlib when +the ``send_message()`` ``pcep_pcc`` API function when the ``free_after_send`` flag +is set true. + +To manually delete a message, call the ``pcep_msg_free_message()`` function. +Internally, this will call ``pcep_obj_free_object()`` and ``pcep_obj_free_tlv()`` +appropriately. + + +Sending a PCEP Report message +----------------------------- + +This section shows how to send a PCEP Report messages from the PCC to the PCE, +and serves as an example of how to send other messages. Refer to the sample +PCC binary located in ``pcep_pcc/src/pcep_pcc.c`` for code examples os sending +a PCEP Report message. + +The Report message must have at least an SRP, LSP, and ERO object. + +The PCEP Report message objects are created with the following APIs: + +- ``struct pcep_object_srp *pcep_obj_create_srp(...);`` +- ``struct pcep_object_lsp *pcep_obj_create_lsp(...);`` +- ``struct pcep_object_ro *pcep_obj_create_ero(...);`` + - Create ero subobjects with the ``pcep_obj_create_ro_subobj_*(...);`` functions + +PCEP Report message is created with the following API: + +- ``struct pcep_header *pcep_msg_create_report(double_linked_list *report_object_list);`` + +A PCEP report messages is sent with the following API: + +- ``void send_message(pcep_session *session, pcep_message *message, bool free_after_send);`` + + +PCEPlib Received event queue +---------------------------- + +PCEP events and messages of interest to the PCEPlib consumer will be stored +internally in a message queue for retrieval. + +The following are the event types: + +- **MESSAGE_RECEIVED** +- **PCE_CLOSED_SOCKET** +- **PCE_SENT_PCEP_CLOSE** +- **PCE_DEAD_TIMER_EXPIRED** +- **PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED** +- **PCC_CONNECTED_TO_PCE** +- **PCC_CONNECTION_FAILURE** +- **PCC_PCEP_SESSION_CLOSED** +- **PCC_RCVD_INVALID_OPEN** +- **PCC_SENT_INVALID_OPEN** +- **PCC_RCVD_MAX_INVALID_MSGS** +- **PCC_RCVD_MAX_UNKOWN_MSGS** + +The following PCEP messages will not be posted on the message queue, as they +are handled internally in the library: + +- **Open** +- **Keep Alive** +- **Close** + +Received event queue API: + +- ``bool event_queue_is_empty();`` + - Returns true if the queue is empty, false otherwise + +- ``uint32_t event_queue_num_events_available();`` + - Return the number of events on the queue, 0 if empty + +- ``struct pcep_event *event_queue_get_event();`` + - Return the next event on the queue, NULL if empty + - The ``message`` pointer will only be non-NULL if ``event_type`` + is ``MESSAGE_RECEIVED`` + +- ``void destroy_pcep_event(struct pcep_event *event);`` + - Free the PCEP Event resources, including the PCEP message if present + + +PCEPlib Counters +---------------- + +The PCEPlib counters are managed in the ``pcep_session_logic`` library, and can +be accessed in the ``pcep_session_counters`` field of the ``pcep_session`` structure. +There are 2 API functions to manage the counters: + +- ``void dump_pcep_session_counters(pcep_session *session);`` + - Dump all of the counters to the logs + +- ``void reset_pcep_session_counters(pcep_session *session);`` + diff --git a/doc/developer/process-architecture.rst b/doc/developer/process-architecture.rst new file mode 100644 index 0000000..85126ca --- /dev/null +++ b/doc/developer/process-architecture.rst @@ -0,0 +1,328 @@ +.. _process-architecture: + +Process Architecture +==================== + +FRR is a suite of daemons that serve different functions. This document +describes internal architecture of daemons, focusing their general design +patterns, and especially how threads are used in the daemons that use them. + +Overview +-------- +The fundamental pattern used in FRR daemons is an `event loop +<https://en.wikipedia.org/wiki/Event_loop>`_. Some daemons use `kernel threads +<https://en.wikipedia.org/wiki/Thread_(computing)#Kernel_threads>`_. In these +daemons, each kernel thread runs its own event loop. The event loop +implementation is constructed to be thread safe and to allow threads other than +its owning thread to schedule events on it. The rest of this document describes +these two designs in detail. + +Terminology +----------- +Because this document describes the architecture for kernel threads as well as +the event system, a digression on terminology is in order here. + +Historically Quagga's loop system was viewed as an implementation of userspace +threading. Because of this design choice, the names for various datastructures +within the event system are variations on the term "thread". The primary +datastructure that holds the state of an event loop in this system is called a +"threadmaster". Events scheduled on the event loop - what would today be called +an 'event' or 'task' in systems such as libevent - are called "threads" and the +datastructure for them is ``struct event``. To add to the confusion, these +"threads" have various types, one of which is "event". To hopefully avoid some +of this confusion, this document refers to these "threads" as a 'task' except +where the datastructures are explicitly named. When they are explicitly named, +they will be formatted ``like this`` to differentiate from the conceptual +names. When speaking of kernel threads, the term used will be "pthread" since +FRR's kernel threading implementation uses the POSIX threads API. + +.. This should be broken into its document under :ref:`libfrr` +.. _event-architecture: + +Event Architecture +------------------ +This section presents a brief overview of the event model as currently +implemented in FRR. This doc should be expanded and broken off into its own +section. For now it provides basic information necessary to understand the +interplay between the event system and kernel threads. + +The core event system is implemented in :file:`lib/event.c` and +:file:`lib/frrevent.h`. The primary +structure is ``struct event_loop``, hereafter referred to as a +``threadmaster``. A ``threadmaster`` is a global state object, or context, that +holds all the tasks currently pending execution as well as statistics on tasks +that have already executed. The event system is driven by adding tasks to this +data structure and then calling a function to retrieve the next task to +execute. At initialization, a daemon will typically create one +``threadmaster``, add a small set of initial tasks, and then run a loop to +fetch each task and execute it. + +These tasks have various types corresponding to their general action. The types +are given by integer macros in :file:`frrevent.h` and are: + +``EVENT_READ`` + Task which waits for a file descriptor to become ready for reading and then + executes. + +``EVENT_WRITE`` + Task which waits for a file descriptor to become ready for writing and then + executes. + +``EVENT_TIMER`` + Task which executes after a certain amount of time has passed since it was + scheduled. + +``EVENT_EVENT`` + Generic task that executes with high priority and carries an arbitrary + integer indicating the event type to its handler. These are commonly used to + implement the finite state machines typically found in routing protocols. + +``EVENT_READY`` + Type used internally for tasks on the ready queue. + +``EVENT_UNUSED`` + Type used internally for ``struct event`` objects that aren't being used. + The event system pools ``struct event`` to avoid heap allocations; this is + the type they have when they're in the pool. + +``EVENT_EXECUTE`` + Just before a task is run its type is changed to this. This is used to show + ``X`` as the type in the output of :clicmd:`show event cpu`. + +The programmer never has to work with these types explicitly. Each type of task +is created and queued via special-purpose functions (actually macros, but +irrelevant for the time being) for the specific type. For example, to add a +``EVENT_READ`` task, you would call + +:: + + event_add_read(struct event_loop *master, int (*handler)(struct event *), void *arg, int fd, struct event **ref); + +The ``struct event`` is then created and added to the appropriate internal +datastructure within the ``threadmaster``. Note that the ``READ`` and +``WRITE`` tasks are independent - a ``READ`` task only tests for +readability, for example. + +The Event Loop +^^^^^^^^^^^^^^ +To use the event system, after creating a ``threadmaster`` the program adds an +initial set of tasks. As these tasks execute, they add more tasks that execute +at some point in the future. This sequence of tasks drives the lifecycle of the +program. When no more tasks are available, the program dies. Typically at +startup the first task added is an I/O task for VTYSH as well as any network +sockets needed for peerings or IPC. + +To retrieve the next task to run the program calls ``event_fetch()``. +``event_fetch()`` internally computes which task to execute next based on +rudimentary priority logic. Events (type ``EVENT_EVENT``) execute with the +highest priority, followed by expired timers and finally I/O tasks (type +``EVENT_READ`` and ``EVENT_WRITE``). When scheduling a task a function and an +arbitrary argument are provided. The task returned from ``event_fetch()`` is +then executed with ``event_call()``. + +The following diagram illustrates a simplified version of this infrastructure. + +.. todo: replace these with SVG +.. figure:: ../figures/threadmaster-single.png + :align: center + + Lifecycle of a program using a single threadmaster. + +The series of "task" boxes represents the current ready task queue. The various +other queues for other types are not shown. The fetch-execute loop is +illustrated at the bottom. + +Mapping the general names used in the figure to specific FRR functions: + +- ``task`` is ``struct event *`` +- ``fetch`` is ``event_fetch()`` +- ``exec()`` is ``event_call()`` +- ``cancel()`` is ``event_cancel()`` +- ``schedule()`` is any of the various task-specific ``event_add_*`` functions + +Adding tasks is done with various task-specific function-like macros. These +macros wrap underlying functions in :file:`event.c` to provide additional +information added at compile time, such as the line number the task was +scheduled from, that can be accessed at runtime for debugging, logging and +informational purposes. Each task type has its own specific scheduling function +that follow the naming convention ``event_add_<type>``; see :file:`frrevent.h` +for details. + +There are some gotchas to keep in mind: + +- I/O tasks are keyed off the file descriptor associated with the I/O + operation. This means that for any given file descriptor, only one of each + type of I/O task (``EVENT_READ`` and ``EVENT_WRITE``) can be scheduled. For + example, scheduling two write tasks one after the other will overwrite the + first task with the second, resulting in total loss of the first task and + difficult bugs. + +- Timer tasks are only as accurate as the monotonic clock provided by the + underlying operating system. + +- Memory management of the arbitrary handler argument passed in the schedule + call is the responsibility of the caller. + + +Kernel Thread Architecture +-------------------------- +Efforts have begun to introduce kernel threads into FRR to improve performance +and stability. Naturally a kernel thread architecture has long been seen as +orthogonal to an event-driven architecture, and the two do have significant +overlap in terms of design choices. Since the event model is tightly integrated +into FRR, careful thought has been put into how pthreads are introduced, what +role they fill, and how they will interoperate with the event model. + +Design Overview +^^^^^^^^^^^^^^^ +Each kernel thread behaves as a lightweight process within FRR, sharing the +same process memory space. On the other hand, the event system is designed to +run in a single process and drive serial execution of a set of tasks. With this +consideration, a natural choice is to implement the event system within each +kernel thread. This allows us to leverage the event-driven execution model with +the currently existing task and context primitives. In this way the familiar +execution model of FRR gains the ability to execute tasks simultaneously while +preserving the existing model for concurrency. + +The following figure illustrates the architecture with multiple pthreads, each +running their own ``threadmaster``-based event loop. + +.. todo: replace these with SVG +.. figure:: ../figures/threadmaster-multiple.png + :align: center + + Lifecycle of a program using multiple pthreads, each running their own + ``threadmaster`` + +Each roundrect represents a single pthread running the same event loop +described under :ref:`event-architecture`. Note the arrow from the ``exec()`` +box on the right to the ``schedule()`` box in the middle pthread. This +illustrates code running in one pthread scheduling a task onto another +pthread's threadmaster. A global lock for each ``threadmaster`` is used to +synchronize these operations. The pthread names are examples. + + +.. This should be broken into its document under :ref:`libfrr` +.. _kernel-thread-wrapper: + +Kernel Thread Wrapper +^^^^^^^^^^^^^^^^^^^^^ +The basis for the integration of pthreads and the event system is a lightweight +wrapper for both systems implemented in :file:`lib/frr_pthread.[ch]`. The +header provides a core datastructure, ``struct frr_pthread``, that encapsulates +structures from both POSIX threads and :file:`event.c`, :file:`frrevent.h`. +In particular, this +datastructure has a pointer to a ``threadmaster`` that runs within the pthread. +It also has fields for a name as well as start and stop functions that have +signatures similar to the POSIX arguments for ``pthread_create()``. + +Calling ``frr_pthread_new()`` creates and registers a new ``frr_pthread``. The +returned structure has a pre-initialized ``threadmaster``, and its ``start`` +and ``stop`` functions are initialized to defaults that will run a basic event +loop with the given threadmaster. Calling ``frr_pthread_run()`` starts the thread +with the ``start`` function. From there, the model is the same as the regular +event model. To schedule tasks on a particular pthread, simply use the regular +:file:`event.c` functions as usual and provide the ``threadmaster`` pointed to +from the ``frr_pthread``. As part of implementing the wrapper, the +:file:`event.c` functions were made thread-safe. Consequently, it is safe to +schedule events on a ``threadmaster`` belonging both to the calling thread as +well as *any other pthread*. This serves as the basis for inter-thread +communication and boils down to a slightly more complicated method of message +passing, where the messages are the regular task events as used in the +event-driven model. The only difference is thread cancellation, which requires +calling ``event_cancel_async()`` instead of ``event_cancel()`` to cancel a task +currently scheduled on a ``threadmaster`` belonging to a different pthread. +This is necessary to avoid race conditions in the specific case where one +pthread wants to guarantee that a task on another pthread is cancelled before +proceeding. + +In addition, the existing commands to show statistics and other information for +tasks within the event driven model have been expanded to handle multiple +pthreads; running :clicmd:`show event cpu` will display the usual event +breakdown, but it will do so for each pthread running in the program. For +example, :ref:`bgpd` runs a dedicated I/O pthread and shows the following +output for :clicmd:`show event cpu`: + +:: + + frr# show event cpu + + Event statistics for bgpd: + + Showing statistics for pthread main + ------------------------------------ + CPU (user+system): Real (wall-clock): + Active Runtime(ms) Invoked Avg uSec Max uSecs Avg uSec Max uSecs Type Thread + 0 1389.000 10 138900 248000 135549 255349 T subgroup_coalesce_timer + 0 0.000 1 0 0 18 18 T bgp_startup_timer_expire + 0 850.000 18 47222 222000 47795 233814 T work_queue_run + 0 0.000 10 0 0 6 14 T update_subgroup_merge_check_thread_cb + 0 0.000 8 0 0 117 160 W zclient_flush_data + 2 2.000 1 2000 2000 831 831 R bgp_accept + 0 1.000 1 1000 1000 2832 2832 E zclient_connect + 1 42082.000 240574 174 37000 178 72810 R vtysh_read + 1 152.000 1885 80 2000 96 6292 R zclient_read + 0 549346.000 2997298 183 7000 153 20242 E bgp_event + 0 2120.000 300 7066 14000 6813 22046 T (bgp_holdtime_timer) + 0 0.000 2 0 0 57 59 T update_group_refresh_default_originate_route_map + 0 90.000 1 90000 90000 73729 73729 T bgp_route_map_update_timer + 0 1417.000 9147 154 48000 132 61998 T bgp_process_packet + 300 71807.000 2995200 23 3000 24 11066 T (bgp_connect_timer) + 0 1894.000 12713 148 45000 112 33606 T (bgp_generate_updgrp_packets) + 0 0.000 1 0 0 105 105 W vtysh_write + 0 52.000 599 86 2000 138 6992 T (bgp_start_timer) + 1 1.000 8 125 1000 164 593 R vtysh_accept + 0 15.000 600 25 2000 15 153 T (bgp_routeadv_timer) + 0 11.000 299 36 3000 53 3128 RW bgp_connect_check + + + Showing statistics for pthread BGP I/O thread + ---------------------------------------------- + CPU (user+system): Real (wall-clock): + Active Runtime(ms) Invoked Avg uSec Max uSecs Avg uSec Max uSecs Type Thread + 0 1611.000 9296 173 13000 188 13685 R bgp_process_reads + 0 2995.000 11753 254 26000 182 29355 W bgp_process_writes + + + Showing statistics for pthread BGP Keepalives thread + ----------------------------------------------------- + CPU (user+system): Real (wall-clock): + Active Runtime(ms) Invoked Avg uSec Max uSecs Avg uSec Max uSecs Type Thread + No data to display yet. + +Attentive readers will notice that there is a third thread, the Keepalives +thread. This thread is responsible for -- surprise -- generating keepalives for +peers. However, there are no statistics showing for that thread. Although the +pthread uses the ``frr_pthread`` wrapper, it opts not to use the embedded +``threadmaster`` facilities. Instead it replaces the ``start`` and ``stop`` +functions with custom functions. This was done because the ``threadmaster`` +facilities introduce a small but significant amount of overhead relative to the +pthread's task. In this case since the pthread does not need the event-driven +model and does not need to receive tasks from other pthreads, it is simpler and +more efficient to implement it outside of the provided event facilities. The +point to take away from this example is that while the facilities to make using +pthreads within FRR easy are already implemented, the wrapper is flexible and +allows usage of other models while still integrating with the rest of the FRR +core infrastructure. Starting and stopping this pthread works the same as it +does for any other ``frr_pthread``; the only difference is that event +statistics are not collected for it, because there are no events. + +Notes on Design and Documentation +--------------------------------- +Because of the choice to embed the existing event system into each pthread +within FRR, at this time there is not integrated support for other models of +pthread use such as divide and conquer. Similarly, there is no explicit support +for thread pooling or similar higher level constructs. The currently existing +infrastructure is designed around the concept of long-running worker threads +responsible for specific jobs within each daemon. This is not to say that +divide and conquer, thread pooling, etc. could not be implemented in the +future. However, designs in this direction must be very careful to take into +account the existing codebase. Introducing kernel threads into programs that +have been written under the assumption of a single thread of execution must be +done very carefully to avoid insidious errors and to ensure the program remains +understandable and maintainable. + +In keeping with these goals, future work on kernel threading should be +extensively documented here and FRR developers should be very careful with +their design choices, as poor choices tightly integrated can prove to be +catastrophic for development efforts in the future. diff --git a/doc/developer/rcu.rst b/doc/developer/rcu.rst new file mode 100644 index 0000000..2335e8f --- /dev/null +++ b/doc/developer/rcu.rst @@ -0,0 +1,278 @@ +.. highlight:: c + +RCU +=== + +Introduction +------------ + +RCU (Read-Copy-Update) is, fundamentally, a paradigm of multithreaded +operation (and not a set of APIs.) The core ideas are: + +* longer, complicated updates to structures are made only on private, + "invisible" copies. Other threads, when they access the structure, see an + older (but consistent) copy. + +* once done, the updated copy is swapped in a single operation so that + other threads see either the old or the new data but no inconsistent state + between. + +* the old instance is only released after making sure that it is impossible + any other thread might still be reading it. + +For more information, please search for general or Linux kernel RCU +documentation; there is no way this doc can be comprehensive in explaining the +interactions: + +* https://en.wikipedia.org/wiki/Read-copy-update +* https://www.kernel.org/doc/html/latest/kernel-hacking/locking.html#avoiding-locks-read-copy-update +* https://lwn.net/Articles/262464/ +* http://www.rdrop.com/users/paulmck/RCU/rclock_OLS.2001.05.01c.pdf +* http://lse.sourceforge.net/locking/rcupdate.html + +RCU, the TL;DR +^^^^^^^^^^^^^^ + +#. data structures are always consistent for reading. That's the "R" part. +#. reading never blocks / takes a lock. +#. rcu_read_lock is not a lock in the traditional sense. Think of it as a + "reservation"; it notes what the *oldest* possible thing the thread might + be seeing is, and which thus can't be deleted yet. +#. you create some object, finish it up, and then publish it. +#. publishing is an ``atomic_*`` call with ``memory_order_release``, which + tells the compiler to make sure prior memory writes have completed before + doing the atomic op. +#. ``ATOMLIST_*`` ``add`` operations do the ``memory_order_release`` for you. +#. you can't touch the object after it is published, except with atomic ops. +#. because you can't touch it, if you want to change it you make a new copy, + work on that, and then publish the new copy. That's the "CU" part. +#. deleting the object is also an atomic op. +#. other threads that started working before you published / deleted an object + might not see the new object / still see the deleted object. +#. because other threads may still see deleted objects, the ``free()`` needs + to be delayed. That's what :c:func:`rcu_free()` is for. + + +When (not) to use RCU +^^^^^^^^^^^^^^^^^^^^^ + +RCU is designed for read-heavy workloads where objects are updated relatively +rarely, but frequently accessed. Do *not* indiscriminately replace locking by +RCU patterns. + +The "copy" part of RCU implies that, while updating, several copies of a given +object exist in parallel. Even after the updated copy is swapped in, the old +object remains queued for freeing until all other threads are guaranteed to +not be accessing it anymore, due to passing a sequence point. In addition to +the increased memory usage, there may be some bursted (due to batching) malloc +contention when the RCU cleanup thread does its thing and frees memory. + +Other useful patterns +^^^^^^^^^^^^^^^^^^^^^ + +In addition to the full "copy object, apply changes, atomically update" +approach, there are 2 "reduced" usage cases that can be done: + +* atomically updating single pieces of a particular object, e.g. some flags + or configuration piece + +* straight up read-only / immutable objects + +Both of these cases can be considered RCU "subsets". For example, when +maintaining an atomic list of items, but these items only have a single +integer value that needs to be updated, that value can be atomically updated +without copying the entire object. However, the object still needs to be +free'd through :c:func:`rcu_free()` since reading/updating and deleting might +be happening concurrently. The same applies for immutable objects; deletion +might still race with reading so they need to be free'd through RCU. + +FRR API +------- + +Before diving into detail on the provided functions, it is important to note +that the FRR RCU API covers the **cleanup part of RCU, not the read-copy-update +paradigm itself**. These parts are handled by standard C11 atomic operations, +and by extension through the atomic data structures (ATOMLIST, ATOMSORT & co.) + +The ``rcu_*`` functions only make sense in conjunction with these RCU access +patterns. If you're calling the RCU API but not using these, something is +wrong. The other way around is not necessarily true; it is possible to use +atomic ops & datastructures with other types of locking, e.g. rwlocks. + +.. c:function:: void rcu_read_lock() +.. c:function:: void rcu_read_unlock() + + These functions acquire / release the RCU read-side lock. All access to + RCU-guarded data must be inside a block guarded by these. Any number of + threads may hold the RCU read-side lock at a given point in time, including + both no threads at all and all threads. + + The functions implement a depth counter, i.e. can be nested. The nested + calls are cheap, since they only increment/decrement the counter. + Therefore, any place that uses RCU data and doesn't have a guarantee that + the caller holds RCU (e.g. ``lib/`` code) should just have its own + rcu_read_lock/rcu_read_unlock pair. + + At the "root" level (e.g. un-nested), these calls can incur the cost of one + syscall (to ``futex()``). That puts them on about the same cost as a + mutex lock/unlock. + + The ``thread_master`` code currently always holds RCU everywhere, except + while doing the actual ``poll()`` syscall. This is both an optimization as + well as an "easement" into getting RCU going. The current implementation + contract is that any ``struct event *`` callback is called with a RCU + holding depth of 1, and that this is owned by the thread so it may (should) + drop and reacquire it when doing some longer-running work. + + .. warning:: + + The RCU read-side lock must be held **continuously** for the entire time + any piece of RCU data is used. This includes any access to RCU data + after the initial ``atomic_load``. If the RCU read-side lock is + released, any RCU-protected pointers as well as the data they refer to + become invalid, as another thread may have called :c:func:`rcu_free` on + them. + +.. c:struct:: rcu_head +.. c:struct:: rcu_head_close +.. c:struct:: rcu_action + + The ``rcu_head`` structures are small (16-byte) bits that contain the + queueing machinery for the RCU sweeper/cleanup mechanisms. + + Any piece of data that is cleaned up by RCU needs to have a matching + ``rcu_head`` embedded in it. If there is more than one cleanup operation + to be done (e.g. closing a file descriptor), more than one ``rcu_head`` may + be embedded. + + .. warning:: + + It is not possible to reuse a ``rcu_head``. It is owned by the RCU code + as soon as ``rcu_*`` is called on it. + + The ``_close`` variant carries an extra ``int fd`` field to store the fd to + be closed. + + To minimize the amount of memory used for ``rcu_head``, details about the + RCU operation to be performed are moved into the ``rcu_action`` structure. + It contains e.g. the MTYPE for :c:func:`rcu_free` calls. The pointer to be + freed is stored as an offset relative to the ``rcu_head``, which means it + must be embedded as a struct field so the offset is constant. + + The ``rcu_action`` structure is an implementation detail. Using + ``rcu_free`` or ``rcu_close`` will set it up correctly without further + code needed. + + The ``rcu_head`` may be put in an union with other data if the other data + is only used during "life" of the data, since the ``rcu_head`` is used only + for the "death" of data. But note that other threads may still be reading + a piece of data while a thread is working to free it. + +.. c:function:: void rcu_free(struct memtype *mtype, struct X *ptr, field) + + Free a block of memory after RCU has ensured no other thread can be + accessing it anymore. The pointer remains valid for any other thread that + has called :c:func:`rcu_read_lock` before the ``rcu_free`` call. + + .. warning:: + + In some other RCU implementations, the pointer remains valid to the + *calling* thread if it is holding the RCU read-side lock. This is not + the case in FRR, particularly when running single-threaded. Enforcing + this rule also allows static analysis to find use-after-free issues. + + ``mtype`` is the libfrr ``MTYPE_FOO`` allocation type to pass to + :c:func:`XFREE`. + + ``field`` must be the name of a ``struct rcu_head`` member field in ``ptr``. + The offset of this field (which must be constant) is used to reduce the + memory size of ``struct rcu_head``. + + .. note:: + + ``rcu_free`` (and ``rcu_close``) calls are more efficient if they are + put close to each other. When freeing several RCU'd resources, try to + move the calls next to each other (even if the data structures do not + directly point to each other.) + + Having the calls bundled reduces the cost of adding the ``rcu_head`` to + the RCU queue; the RCU queue is an atomic data structure whose usage + will require the CPU to acquire an exclusive hold on relevant cache + lines. + +.. c:function:: void rcu_close(struct rcu_head_close *head, int fd) + + Close a file descriptor after ensuring no other thread might be using it + anymore. Same as :c:func:`rcu_free`, except it calls ``close`` instead of + ``free``. + +Internals +^^^^^^^^^ + +.. c:struct:: rcu_thread + + Per-thread state maintained by the RCU code, set up by the following + functions. A pointer to a thread's own ``rcu_thread`` is saved in + thread-local storage. + +.. c:function:: struct rcu_thread *rcu_thread_prepare(void) +.. c:function:: void rcu_thread_unprepare(struct rcu_thread *rcu_thread) +.. c:function:: void rcu_thread_start(struct rcu_thread *rcu_thread) + + Since the RCU code needs to have a list of all active threads, these + functions are used by the ``frr_pthread`` code to set up threads. Teardown + is automatic. It should not be necessary to call these functions. + + Any thread that accesses RCU-protected data needs to be registered with + these functions. Threads that do not access RCU-protected data may call + these functions but do not need to. + + Note that passing a pointer to RCU-protected data to some library which + accesses that pointer makes the library "access RCU-protected data". In + that case, either all of the library's threads must be registered for RCU, + or the code must instead pass a (non-RCU) copy of the data to the library. + +.. c:function:: int frr_pthread_non_controlled_startup(pthread_t thread, const char *name, const char *os_name) + + If a pthread is started outside the control of normal pthreads in frr + then frr_pthread_non_controlled_startup should be called. This will + properly setup both the pthread with rcu usage as well as some data + structures pertaining to the name of the pthread. This is especially + important if the pthread created ends up calling back into FRR and + one of the various zlog_XXX functions is called. + +.. c:function:: void rcu_shutdown(void) + + Stop the RCU sweeper thread and make sure all cleanup has finished. + + This function is called on daemon exit by the libfrr code to ensure pending + RCU operations are completed. This is mostly to get a clean exit without + memory leaks from queued RCU operations. It should not be necessary to + call this function as libfrr handles this. + +FRR specifics and implementation details +---------------------------------------- + +The FRR RCU infrastructure has the following characteristics: + +* it is Epoch-based with a 32-bit wrapping counter. (This is somewhat + different from other Epoch-based approaches which may be designed to only + use 3 counter values, but works out to a simple implementation.) + +* instead of tracking CPUs as the Linux kernel does, threads are tracked. This + has exactly zero semantic impact, RCU just cares about "threads of + execution", which the kernel can optimize to CPUs but we can't. But it + really boils down to the same thing. + +* there are no ``rcu_dereference`` and ``rcu_assign_pointer`` - use + ``atomic_load`` and ``atomic_store`` instead. (These didn't exist when the + Linux RCU code was created.) + +* there is no ``synchronize_rcu``; this is a design choice but may be revisited + at a later point. ``synchronize_rcu`` blocks a thread until it is guaranteed + that no other threads might still be accessing data structures that they may + have access to at the beginning of the function call. This is a blocking + design and probably not appropriate for FRR. Instead, ``rcu_call`` can be + used to have the RCU sweeper thread make a callback after the same constraint + is fulfilled in an asynchronous way. Most needs should be covered by + ``rcu_free`` and ``rcu_close``. diff --git a/doc/developer/release-announcement-template.md b/doc/developer/release-announcement-template.md new file mode 100644 index 0000000..658b87e --- /dev/null +++ b/doc/developer/release-announcement-template.md @@ -0,0 +1,40 @@ +<!--- +name: release-announcement-template +about: Template to use when drafing a new release announcement. DELETE THIS + BLOCK BEFORE PUBLISHING. +---> + +We are pleased to announce FRR <version>. + +<!-- Add a brief summary of major changes here --> + +Thank you to all contributors! + +Changelog +--------- + +<!-- List **only** user-visible changes in this section. When listing changes to individual daemons, alphabetize the list by daemon name. --> + +**All daemons:** +- <!-- List changes to all daemons --> + +<!-- If a new daemon was added, list it at the top here --> +**New daemon: <new>** +- Adds support for <protocol/feature> + +**daemon 1** +- <!-- List changes --> + +**daemon 2** +- <!-- List changes --> + +**daemon N** +- <!-- List changes --> + +### Internal improvements + +- <!-- List **only** user-invisible changes here --> + +### Packaging changes + +- <!-- List any new or removed packages here --> diff --git a/doc/developer/requirements.txt b/doc/developer/requirements.txt new file mode 100644 index 0000000..483a4e9 --- /dev/null +++ b/doc/developer/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme diff --git a/doc/developer/scripting.rst b/doc/developer/scripting.rst new file mode 100644 index 0000000..7a43314 --- /dev/null +++ b/doc/developer/scripting.rst @@ -0,0 +1,622 @@ +.. _scripting: + +Scripting +========= + +.. seealso:: User docs for scripting + +Overview +-------- + +FRR has the ability to call Lua scripts to perform calculations, make +decisions, or otherwise extend builtin behavior with arbitrary user code. This +is implemented using the standard Lua C bindings. The supported version of Lua +is 5.3. + +C objects may be passed into Lua and Lua objects may be retrieved by C code via +a encoding/decoding system. In this way, arbitrary data from FRR may be passed to +scripts. + +The Lua environment is isolated from the C environment; user scripts cannot +access FRR's address space unless explicitly allowed by FRR. + +For general information on how Lua is used to extend C, refer to Part IV of +"Programming in Lua". + +https://www.lua.org/pil/contents.html#24 + + +Design +------ + +Why Lua +^^^^^^^ + +Lua is designed to be embedded in C applications. It is very small; the +standard library is 220K. It is relatively fast. It has a simple, minimal +syntax that is relatively easy to learn and can be understood by someone with +little to no programming experience. Moreover it is widely used to add +scripting capabilities to applications. In short it is designed for this task. + +Reasons against supporting multiple scripting languages: + +- Each language would require different FFI methods, and specifically + different object encoders; a lot of code +- Languages have different capabilities that would have to be brought to + parity with each other; a lot of work +- Languages have vastly different performance characteristics; this would + create alot of basically unfixable issues, and result in a single de facto + standard scripting language (the fastest) +- Each language would need a dedicated maintainer for the above reasons; + this is pragmatically difficult +- Supporting multiple languages fractures the community and limits the audience + with which a given script can be shared + +General +------- + +FRR's scripting functionality is provided in the form of Lua functions in Lua +scripts (``.lua`` files). One Lua script may contain many Lua functions. These +are respectively encapsulated in the following structures: + +.. code-block:: c + + struct frrscript { + /* Lua file name */ + char *name; + + /* hash of lua_function_states */ + struct hash *lua_function_hash; + }; + + struct lua_function_state { + /* Lua function name */ + char *name; + + lua_State *L; + }; + + +`struct frrscript`: Since all Lua functions are contained within scripts, the +following APIs manipulates this structure. ``name`` contains the +Lua script name and a hash of Lua functions to their function names. + +`struct lua_function_state` is an internal structure, but it essentially contains +the name of the Lua function and its state (a stack), which is run using Lua +library functions. + +In general, to run a Lua function, these steps must take place: + +- Initialization +- Load +- Call +- Delete + +Initialization +^^^^^^^^^^^^^^ + +The ``frrscript`` object encapsulates the Lua function state(s) from +one Lua script file. To create, use ``frrscript_new()`` which takes the +name of the Lua script. +The string ".lua" is appended to the script name, and the resultant filename +will be used to look for the script when we want to load a Lua function from it. + +For example, to create ``frrscript`` for ``/etc/frr/scripts/bingus.lua``: + +.. code-block:: c + + struct frrscript *fs = frrscript_new("bingus"); + + +The script is *not* read at this stage. +This function cannot be used to test for a script's presence. + +Load +^^^^ + +The function to be called must first be loaded. Use ``frrscript_load()`` +which takes a ``frrscript`` object, the name of the Lua function +and a callback function. +The script file will be read to load and compile the function. + +For example, to load the Lua function ``on_foo`` +in ``/etc/frr/scripts/bingus.lua``: + +.. code-block:: c + + int ret = frrscript_load(fs, "on_foo", NULL); + + +This function returns 0 if and only if the Lua function was successfully loaded. +A non-zero return could indicate either a missing Lua script, a missing +Lua function, or an error when loading the function. + +During loading the script is validated for syntax and its environment +is set up. By default this does not include the Lua standard library; there are +security issues to consider, though for practical purposes untrusted users +should not be able to write the scripts directory anyway. + +Call +^^^^ + +After loading, a Lua function can be called any number of times. + +Input +""""" + +Inputs to the Lua script should be given by providing a list of parenthesized +pairs, +where the first and second field identify the name of the variable and the +value it is bound to, respectively. +The types of the values must have registered encoders (more below); the compiler +will warn you otherwise. + +These variables are first encoded in-order, then provided as arguments +to the Lua function. In the example, note that ``c`` is passed in as a value +while ``a`` and ``b`` are passed in as pointers. + +.. code-block:: c + + int a = 100, b = 200, c = 300; + frrscript_call(fs, "on_foo", ("a", &a), ("b", &b), ("c", c)); + + +.. code-block:: lua + + function on_foo(a, b, c) + -- a is 100, b is 200, c is 300 + ... + + +Output +"""""" + +.. code-block:: c + + int a = 100, b = 200, c = 300; + frrscript_call(fs, "on_foo", ("a", &a), ("b", &b), ("c", c)); + // a is 500, b is 200, c is 300 + + int* d = frrscript_get_result(fs, "on_foo", "d", lua_tointegerp); + // d is 800 + + +.. code-block:: lua + + function on_foo(a, b, c) + b = 600 + return { ["a"] = 500, ["c"] = 700, ["d"] = 800 } + end + + +**Lua functions being called must return a single table of string names to +values.** +(Lua functions should return an empty table if there is no output.) +The keys of the table are mapped back to names of variables in C. Note that +the values in the table can also be tables. Since tables are Lua's primary +data structure, this design lets us return any Lua value. + +After the Lua function returns, the names of variables to ``frrscript_call()`` +are matched against keys of the returned table, and then decoded. The types +being decoded must have registered decoders (more below); the compiler will +warn you otherwise. + +In the example, since ``a`` was in the returned table and ``b`` was not, +``a`` was decoded and its value modified, while ``b`` was not decoded. +``c`` was decoded as well, but its decoder is a noop. +What modifications happen given a variable depends whether its name was +in the returned table and the decoder's implementation. + +.. warning:: + Always keep in mind that non const-qualified pointers in + ``frrscript_call()`` may be modified - this may be a source of bugs. + On the other hand, const-qualified pointers and other values cannot + be modified. + + +.. tip:: + You can make a copy of a data structure and pass that in instead, + so that modifications only happen to that copy. + +``frrscript_call()`` returns 0 if and only if the Lua function was successfully +called. A non-zero return could indicate either a missing Lua script, a missing +Lua function, or an error from the Lua interpreter. + +In the above example, ``d`` was not an input to ``frrscript_call()``, so its +value must be explicitly retrieved with ``frrscript_get_result``. + +``frrscript_get_result()`` takes a +decoder and string name which is used as a key to search the returned table. +Returns the pointer to the decoded value, or NULL if it was not found. +In the example, ``d`` is a "new" value in C space, +so memory allocation might take place. Hence the caller is +responsible for memory deallocation. + +``frrscript_call()`` may be called multiple times without re-loading with +``frrscript_load()``. Results are not preserved between consecutive calls. + +.. code-block:: c + + frrscript_load(fs, "on_foo"); + + frrscript_call(fs, "on_foo"); + frrscript_get_result(fs, "on_foo", ...); + frrscript_call(fs, "on_foo"); + frrscript_get_result(fs, "on_foo", ...); + + +Delete +^^^^^^ + +To delete a script and the all Lua states associated with it: + +.. code-block:: c + + frrscript_delete(fs); + + +A complete example +"""""""""""""""""" + +So, a typical execution call, with error checking, looks something like this: + +.. code-block:: c + + struct frrscript *fs = frrscript_new("my_script"); // name *without* .lua + + int ret = frrscript_load(fs, "on_foo", NULL); + if (ret != 0) + goto DONE; // Lua script or function might have not been found + + int a = 100, b = 200, c = 300; + ret = frrscript_call(fs, "on_foo", ("a", &a), ("b", &b), ("c", c)); + if (ret != 0) + goto DONE; // Lua function might have not successfully run + + // a and b might be modified + assert(a == 500); + assert(b == 200); + + // c could not have been modified + assert(c == 300); + + // d is new + int* d = frrscript_get_result(fs, "on_foo", "d", lua_tointegerp); + + if (!d) + goto DONE; // "d" might not have been in returned table + + assert(*d == 800); + XFREE(MTYPE_SCRIPT_RES, d); // caller responsible for free + + DONE: + frrscript_delete(fs); + + +.. code-block:: lua + + function on_foo(a, b, c) + b = 600 + return { a = 500, c = 700, d = 800 } + end + + +Note that ``{ a = ...`` is same as ``{ ["a"] = ...``; it is Lua shorthand to +use the variable name as the key in a table. + +Encoding and Decoding +^^^^^^^^^^^^^^^^^^^^^ + +Earlier sections glossed over the types of values that can be passed into +``frrscript_call()`` and how data is passed between C and Lua. Lua, as a +dynamically typed, garbage collected language, cannot directly use C values +without some kind of encoding / decoding system to +translate types between the two runtimes. + +Lua communicates with C code using a stack. C code wishing to provide data to +Lua scripts must provide a function that encodes the C data into a Lua +representation and pushes it on the stack. C code wishing to retrieve data from +Lua must provide a corresponding decoder function that retrieves a Lua +value from the stack and converts it to the corresponding C type. + +Encoders and decoders are provided for common data types. +Developers wishing to pass their own data structures between C and Lua need to +create encoders and decoders for that data type. + +We try to keep them named consistently. +There are three kinds of encoders and decoders: + +1. lua_push*: encodes a value onto the Lua stack. + Required for ``frrscript_call``. + +2. lua_decode*: decodes a value from the Lua stack. + Required for ``frrscript_call``. + Only non const-qualified pointers may be actually decoded (more below). + +3. lua_to*: allocates memory and decodes a value from the Lua stack. + Required for ``frrscript_get_result``. + +This design allows us to combine typesafe *modification* of C values as well as +*allocation* of new C values. + +In the following sections, we will use the encoders/decoders for ``struct prefix`` as an example. + +Encoding +"""""""" + +An encoder function takes a ``lua_State *``, a C type and pushes that value onto +the Lua state (a stack). +For C structs, the usual case, +this will typically be encoded to a Lua table, then pushed onto the Lua stack. + +Here is the encoder function for ``struct prefix``: + +.. code-block:: c + + void lua_pushprefix(lua_State *L, struct prefix *prefix) + { + char buffer[PREFIX_STRLEN]; + + lua_newtable(L); + lua_pushstring(L, prefix2str(prefix, buffer, PREFIX_STRLEN)); + lua_setfield(L, -2, "network"); + lua_pushinteger(L, prefix->prefixlen); + lua_setfield(L, -2, "length"); + lua_pushinteger(L, prefix->family); + lua_setfield(L, -2, "family"); + } + +This function pushes a single value, a table, onto the Lua stack, whose +equivalent in Lua is: + +.. code-block:: c + + { ["network"] = "1.2.3.4/24", ["prefixlen"] = 24, ["family"] = 2 } + + +Decoding +"""""""" + +Decoders are a bit more involved. They do the reverse; a decoder function takes +a ``lua_State *``, pops a value off the Lua stack and converts it back into its +C type. + +There are two: ``lua_decode*`` and ``lua_to*``. The former does no mememory +allocation and is needed for ``frrscript_call``. +The latter performs allocation and is optional. + +A ``lua_decode_*`` function takes a ``lua_State*``, an index, and a pointer +to a C data structure, and directly modifies the structure with values from the +Lua stack. Note that only non const-qualified pointers may be modified; +``lua_decode_*`` for other types will be noops. + +Again, for ``struct prefix *``: + +.. code-block:: c + + void lua_decode_prefix(lua_State *L, int idx, struct prefix *prefix) + { + lua_getfield(L, idx, "network"); + (void)str2prefix(lua_tostring(L, -1), prefix); + /* pop the network string */ + lua_pop(L, 1); + /* pop the prefix table */ + lua_pop(L, 1); + } + + +Note: + - Before ``lua_decode*`` is run, the "prefix" table is already on the top of + the stack. ``frrscript_call`` does this for us. + - However, at the end of ``lua_decode*``, the "prefix" table should be popped. + - The other two fields in the "network" table are disregarded, meaning that any + modification to them is discarded in C space. In this case, this is desired + behavior. + +.. warning:: + + ``lua_decode*`` functions should pop all values that ``lua_to*`` pushed onto + the Lua stack. + For encoders that pushed a table, its decoder should pop the table at the end. + The above is an example. + + + +``int`` is not a non const-qualified pointer, so for ``int``: + +.. code-block:: c + + void lua_decode_int_noop(lua_State *L, int idx, int i) + { //noop + } + + +A ``lua_to*`` function provides identical functionality except that it first +allocates memory for the new C type before decoding the value from the Lua stack, +then returns a pointer to the newly allocated C type. You only need to implement +this function to use with ``frrscript_get_result`` to retrieve a result of +this type. + +This function can and should be implemented using ``lua_decode_*``: + +.. code-block:: c + + void *lua_toprefix(lua_State *L, int idx) + { + struct prefix *p = XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct prefix)); + + lua_decode_prefix(L, idx, p); + return p; + } + + +The returned data must always be copied off the stack and the copy must be +allocated with ``MTYPE_SCRIPT_RES``. This way it is possible to unload the script +(destroy the state) without invalidating any references to values stored in it. +Note that it is the caller's responsibility to free the data. + + +Registering encoders and decoders for frrscript_call +"""""""""""""""""""""""""""""""""""""""""""""""""""" + +To register a new type with its ``lua_push*`` and ``lua_decode*`` functions, +add the mapping in the following macros in ``frrscript.h``: + +.. code-block:: diff + + #define ENCODE_ARGS_WITH_STATE(L, value) \ + _Generic((value), \ + ... + - struct peer * : lua_pushpeer \ + + struct peer * : lua_pushpeer, \ + + struct prefix * : lua_pushprefix \ + )((L), (value)) + + #define DECODE_ARGS_WITH_STATE(L, value) \ + _Generic((value), \ + ... + - struct peer * : lua_decode_peer \ + + struct peer * : lua_decode_peer, \ + + struct prefix * : lua_decode_prefix \ + )((L), -1, (value)) + + +At compile time, the compiler will search for encoders/decoders for the type of +each value passed in via ``frrscript_call``. If a encoder/decoder cannot be +found, it will appear as a compile warning. Note that the types must +match *exactly*. +In the above example, we defined encoders/decoders for a value of +``struct prefix *``, but not ``struct prefix`` or ``const struct prefix *``. + +.. code-block:: diff + + #define DECODE_ARGS_WITH_STATE(L, value) \ + _Generic((value), \ + ... + + const struct prefix * : lua_decode_noop \ + )(L, -1, value) + + +.. note:: + + Encodable/decodable types are not restricted to simple values like integers, + strings and tables. + It is possible to encode a type such that the resultant object in Lua + is an actual object-oriented object, complete with methods that call + back into defined C functions. See the Lua manual for how to do this; + for a code example, look at how zlog is exported into the script environment. + + +Script Environment +------------------ + +Logging +^^^^^^^ + +For convenience, script environments are populated by default with a ``log`` +object which contains methods corresponding to each of the ``zlog`` levels: + +.. code-block:: lua + + log.info("info") + log.warn("warn") + log.error("error") + log.notice("notice") + log.debug("debug") + +The log messages will show up in the daemon's log output. + + +Examples +-------- + +For a complete code example involving passing custom types, retrieving results, +and doing complex calculations in Lua, look at the implementation of the +``match script SCRIPT`` command for BGP routemaps. This example calls into a +script with a function named ``route_match``, +provides route prefix and attributes received from a peer and expects the +function to return a match / no match / match and update result. + +An example script to use with this follows. This function matches, does not match +or updates a route depending on how many BGP UPDATE messages the peer has +received when the script is called, simply as a demonstration of what can be +accomplished with scripting. + +.. code-block:: lua + + + -- Example route map matching + -- author: qlyoung + -- + -- The following variables are available in the global environment: + -- log + -- logging library, with the usual functions + -- + -- route_match arguments: + -- table prefix + -- the route under consideration + -- table attributes + -- the route's attributes + -- table peer + -- the peer which received this route + -- integer RM_FAILURE + -- status code in case of failure + -- integer RM_NOMATCH + -- status code for no match + -- integer RM_MATCH + -- status code for match + -- integer RM_MATCH_AND_CHANGE + -- status code for match-and-set + -- + -- route_match returns table with following keys: + -- integer action, required + -- resultant status code. Should be one of RM_* + -- table attributes, optional + -- updated route attributes + -- + + function route_match(prefix, attributes, peer, + RM_FAILURE, RM_NOMATCH, RM_MATCH, RM_MATCH_AND_CHANGE) + + log.info("Evaluating route " .. prefix.network .. " from peer " .. peer.remote_id.string) + + function on_match (prefix, attributes) + log.info("Match") + return { + attributes = RM_MATCH + } + end + + function on_nomatch (prefix, attributes) + log.info("No match") + return { + action = RM_NOMATCH + } + end + + function on_match_and_change (prefix, attributes) + log.info("Match and change") + attributes["metric"] = attributes["metric"] + 7 + return { + action = RM_MATCH_AND_CHANGE, + attributes = attributes + } + end + + special_routes = { + ["172.16.10.4/24"] = on_match, + ["172.16.13.1/8"] = on_nomatch, + ["192.168.0.24/8"] = on_match_and_change, + } + + + if special_routes[prefix.network] then + return special_routes[prefix.network](prefix, attributes) + elseif peer.stats.update_in % 3 == 0 then + return on_match(prefix, attributes) + elseif peer.stats.update_in % 2 == 0 then + return on_nomatch(prefix, attributes) + else + return on_match_and_change(prefix, attributes) + end + end diff --git a/doc/developer/static-linking.rst b/doc/developer/static-linking.rst new file mode 100644 index 0000000..e9bb928 --- /dev/null +++ b/doc/developer/static-linking.rst @@ -0,0 +1,98 @@ +.. _static-linking: + +Static Linking +============== + +This document describes how to build FRR without hard dependencies on shared +libraries. Note that it's not possible to build FRR *completely* statically. +This document just covers how to statically link the dependencies that aren't +likely to be present on a given platform - libfrr and libyang. The resultant +binaries should still be fairly portable. For example, here is the DSO +dependency list for `bgpd` after using these steps: + +.. code-block:: shell + + $ ldd bgpd + linux-vdso.so.1 (0x00007ffe3a989000) + libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f9dc10c0000) + libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f9dc0eba000) + libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9dc0b1c000) + libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f9dc0918000) + libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007f9dc06e0000) + libjson-c.so.3 => /lib/x86_64-linux-gnu/libjson-c.so.3 (0x00007f9dc04d5000) + librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f9dc02cd000) + libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9dc00ae000) + libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f9dbfe96000) + libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9dbfaa5000) + /lib64/ld-linux-x86-64.so.2 (0x00007f9dc1449000) + +Procedure +--------- +Note that these steps have only been tested with LLVM 9 / clang. + +Today, libfrr can already be statically linked by passing these configure +options:: + + --enable-static --enable-static-bin --enable-shared + +libyang is more complicated. You must build and install libyang as a static +library. To do this, follow the usual libyang build procedure as listed in the +FRR developer docs, but set the ``ENABLE_STATIC`` option in your cmake +invocation. You also need to build with PIC enabled, which today is disabled +when building libyang statically. + +The resultant cmake command is:: + + cmake -DENABLE_STATIC=ON -DENABLE_LYD_PRIV=ON \ + --install-prefix /usr \ + -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE \ + -DCMAKE_BUILD_TYPE:String="Release" .. + +This produces a bunch of ``.a`` static archives that need to ultimately be linked +into FRR. However, not only is it 6 archives rather than the usual ``libyang.so``, +you will now also need to link FRR with ``libpcre.a``. Ubuntu's ``libpcre3-dev`` +package provides this, but it hasn't been built with PIC enabled, so it's not +usable for our purposes. So download ``libpcre`` from +`SourceForge <https://sourceforge.net/projects/pcre/>`_, and build it +like this: + +.. code-block:: shell + + ./configure --with-pic + make + +Hopefully you get a nice, usable, PIC ``libpcre.a``. + +So now we have to link all these static libraries into FRR. Rather than modify +FRR to accommodate this, the best option is to create an archive with all of +libyang's dependencies. Then to avoid making any changes to FRR build foo, +rename this ``libyang.a`` and copy it over the usual static library location. +Ugly but it works. To do this, go into your libyang build directory, which +should have a bunch of ``.a`` files. Copy ``libpcre.a`` into this directory. +Write the following into a shell script and run it: + +.. code-block:: shell + + #!/bin/bash + ar -M <<EOM + CREATE libyang_fat.a + ADDLIB libyang.a + ADDLIB libyangdata.a + ADDLIB libmetadata.a + ADDLIB libnacm.a + ADDLIB libuser_inet_types.a + ADDLIB libuser_yang_types.a + ADDLIB libpcre.a + SAVE + END + EOM + ranlib libyang_fat.a + +``libyang_fat.a`` is your archive. Now copy this over your install +``libyang.a``, which on my machine is located at +``/usr/lib/x86_64-linux-gnu/libyang.a`` (try ``locate libyang.a`` if not). + +Now when you build FRR with the static options enabled as above, clang should +pick up the static libyang and link it, leaving you with FRR binaries that have +no hard DSO dependencies beyond common system libraries. To verify, run ``ldd`` +over the resultant binaries. diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am new file mode 100644 index 0000000..652ee4e --- /dev/null +++ b/doc/developer/subdir.am @@ -0,0 +1,117 @@ +# +# doc/developer +# + +dev_RSTFILES = \ + doc/developer/bgp-typecodes.rst \ + doc/developer/bgpd.rst \ + doc/developer/bmp.rst \ + doc/developer/building-frr-for-alpine.rst \ + doc/developer/building-frr-for-archlinux.rst \ + doc/developer/building-frr-for-centos6.rst \ + doc/developer/building-frr-for-centos7.rst \ + doc/developer/building-frr-for-debian8.rst \ + doc/developer/building-frr-for-debian9.rst \ + doc/developer/building-frr-for-debian12.rst \ + doc/developer/building-frr-for-fedora.rst \ + doc/developer/building-frr-for-freebsd10.rst \ + doc/developer/building-frr-for-freebsd11.rst \ + doc/developer/building-frr-for-freebsd13.rst \ + doc/developer/building-frr-for-freebsd9.rst \ + doc/developer/building-frr-for-netbsd6.rst \ + doc/developer/building-frr-for-netbsd7.rst \ + doc/developer/building-frr-for-openbsd6.rst \ + doc/developer/building-frr-for-opensuse.rst \ + doc/developer/building-frr-for-openwrt.rst \ + doc/developer/building-frr-for-ubuntu1404.rst \ + doc/developer/building-frr-for-ubuntu1604.rst \ + doc/developer/building-frr-for-ubuntu1804.rst \ + doc/developer/building-frr-for-ubuntu2004.rst \ + doc/developer/building-frr-for-ubuntu2204.rst \ + doc/developer/building-libunwind-note.rst \ + doc/developer/building-libyang.rst \ + doc/developer/building.rst \ + doc/developer/checkpatch.rst \ + doc/developer/cli.rst \ + doc/developer/conf.py \ + doc/developer/cross-compiling.rst \ + doc/developer/frr-release-procedure.rst \ + doc/developer/grpc.rst \ + doc/developer/hooks.rst \ + doc/developer/include-compile.rst \ + doc/developer/index.rst \ + doc/developer/library.rst \ + doc/developer/link-state.rst \ + doc/developer/lists.rst \ + doc/developer/locking.rst \ + doc/developer/logging.rst \ + doc/developer/memtypes.rst \ + doc/developer/modules.rst \ + doc/developer/next-hop-tracking.rst \ + doc/developer/ospf-api.rst \ + doc/developer/ospf-sr.rst \ + doc/developer/ospf.rst \ + doc/developer/packaging-debian.rst \ + doc/developer/packaging-redhat.rst \ + doc/developer/packaging.rst \ + doc/developer/path-internals-daemon.rst \ + doc/developer/path-internals-pcep.rst \ + doc/developer/path-internals.rst \ + doc/developer/path.rst \ + doc/developer/rcu.rst \ + doc/developer/scripting.rst \ + doc/developer/static-linking.rst \ + doc/developer/tracing.rst \ + doc/developer/testing.rst \ + doc/developer/topotests-snippets.rst \ + doc/developer/topotests-markers.rst \ + doc/developer/topotests.rst \ + doc/developer/workflow.rst \ + doc/developer/xrefs.rst \ + doc/developer/zebra.rst \ + doc/developer/northbound/advanced-topics.rst \ + doc/developer/northbound/architecture.rst \ + doc/developer/northbound/demos.rst \ + doc/developer/northbound/links.rst \ + doc/developer/northbound/northbound.rst \ + doc/developer/northbound/operational-data-rpcs-and-notifications.rst \ + doc/developer/northbound/plugins-sysrepo.rst \ + doc/developer/northbound/ppr-basic-test-topology.rst \ + doc/developer/northbound/ppr-mpls-basic-test-topology.rst \ + doc/developer/northbound/retrofitting-configuration-commands.rst \ + doc/developer/northbound/transactional-cli.rst \ + doc/developer/northbound/yang-module-translator.rst \ + doc/developer/northbound/yang-tools.rst \ + # end + +EXTRA_DIST += \ + $(dev_RSTFILES) \ + doc/developer/draft-zebra-00.ms \ + doc/developer/ldpd-basic-test-setup.md \ + doc/developer/release-announcement-template.md \ + doc/developer/_static/overrides.css \ + # end + +DEVBUILD = doc/developer/_build +$(DEVBUILD)/.doctrees/environment.pickle: $(dev_RSTFILES) + +# +# nothing built automatically for "all" target. +# + +# +# standard targets +# + +developer-info: $(DEVBUILD)/texinfo/frr.info +developer-html: $(DEVBUILD)/html/.buildinfo +developer-pdf: $(DEVBUILD)/latexpdf + +# +# hook-in for clean +# + +.PHONY: clean-devdocs +clean-local: clean-devdocs +clean-devdocs: + -rm -rf "$(DEVBUILD)" diff --git a/doc/developer/testing.rst b/doc/developer/testing.rst new file mode 100644 index 0000000..5865a6b --- /dev/null +++ b/doc/developer/testing.rst @@ -0,0 +1,11 @@ +.. _testing: + +******* +Testing +******* + +.. toctree:: + :maxdepth: 2 + + topotests + topotests-jsontopo diff --git a/doc/developer/topotests-jsontopo.rst b/doc/developer/topotests-jsontopo.rst new file mode 100644 index 0000000..e2cc72c --- /dev/null +++ b/doc/developer/topotests-jsontopo.rst @@ -0,0 +1,454 @@ +.. _topotests-json: + +Topotests with JSON +=================== + +Overview +-------- + +On top of current topotests framework following enhancements are done: + + +* Creating the topology and assigning IPs to router' interfaces dynamically. + It is achieved by using json file, in which user specify the number of + routers, links to each router, interfaces for the routers and protocol + configurations for all routers. + +* Creating the configurations dynamically. It is achieved by using + :file:`/usr/lib/frr/frr-reload.py` utility, which takes running configuration + and the newly created configuration for any particular router and creates a + delta file(diff file) and loads it to router. + + +Logging of test case executions +------------------------------- + +* The execution log for each test is saved in the test specific directory create + under `/tmp/topotests` (e.g., + `/tmp/topotests/<testdirname.testfilename>/exec.log`) + +* Additionally all test logs are captured in the `topotest.xml` results file. + This file will be saved in `/tmp/topotests/topotests.xml`. In order to extract + the logs for a particular test one can use the `analyze.py` utility found in + the topotests base directory. + +* Router's current configuration, as it is changed during the test, can be + displayed on console or sent to logs by adding ``show_router_config = True`` in + :file:`pytest.ini`. + +Note: directory "/tmp/topotests/" is created by topotests by default, making +use of same directory to save execution logs. + +Guidelines +---------- + +Writing New Tests +^^^^^^^^^^^^^^^^^ + +This section will guide you in all recommended steps to produce a standard +topology test. + +This is the recommended test writing routine: + +* Create a json file which will have routers and protocol configurations +* Write and debug the tests +* Format the new code using `black <https://github.com/psf/black>`_ +* Create a Pull Request + +.. Note:: + + BGP tests MUST use generous convergence timeouts - you must ensure that any + test involving BGP uses a convergence timeout that is proportional to the + configured BGP timers. If the timers are not reduced from their defaults this + means 130 seconds; however, it is highly recommended that timers be reduced + from the default values unless the test requires they not be. + +File Hierarchy +^^^^^^^^^^^^^^ + +Before starting to write any tests one must know the file hierarchy. The +repository hierarchy looks like this: + +.. code-block:: console + + $ cd frr/tests/topotests + $ find ./* + ... + ./example_test/ + ./example_test/test_template_json.json # input json file, having topology, interfaces, bgp and other configuration + ./example_test/test_template_json.py # test script to write and execute testcases + ... + ./lib # shared test/topology functions + ./lib/topojson.py # library to create topology and configurations dynamically from json file + ./lib/common_config.py # library to create protocol's common configurations ex- static_routes, prefix_lists, route_maps etc. + ./lib/bgp.py # library to create and test bgp configurations + +Defining the Topology and initial configuration in JSON file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The first step to write a new test is to define the topology and initial +configuration. User has to define topology and initial configuration in JSON +file. Here is an example of JSON file:: + + BGP neighborship with single phy-link, sample JSON file: + { + "ipv4base": "192.168.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static" + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + } + ... + + +BGP neighboship with loopback interface, sample JSON file:: + + { + "ipv4base": "192.168.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", + "add_static_route":"yes"}, + "r2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "lo": { + "source_link": "lo" + } + } + } + } + } + } + } + }, + "static_routes": [ + { + "network": "1.0.2.17/32", + "next_hop": "192.168.0.1 + } + ] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", + "add_static_route":"yes"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static" + } + ], + "neighbor": { + "r1": { + "dest_link": { + "lo": { + "source_link": "lo" + } + } + }, + "r3": { + "dest_link": { + "lo": { + "source_link": "lo" + } + } + } + } + } + } + } + }, + "static_routes": [ + { + "network": "192.0.20.1/32", + "no_of_ip": 9, + "admin_distance": 100, + "next_hop": "192.168.0.1", + "tag": 4001 + } + ], + } + ... + +BGP neighborship with Multiple phy-links, sample JSON file:: + + { + "ipv4base": "192.168.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "192.168.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "64512", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static" + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + } + } + ... + + +JSON File Explained +""""""""""""""""""" + +Mandatory keywords/options in JSON: + +* ``ipv4base`` : base ipv4 address to generate ips, ex - 192.168.0.0 +* ``ipv4mask`` : mask for ipv4 address, ex - 30 +* ``ipv6base`` : base ipv6 address to generate ips, ex - fd00: +* ``ipv6mask`` : mask for ipv6 address, ex - 64 +* ``link_ip_start`` : physical interface base ipv4 and ipv6 address +* ``lo_prefix`` : loopback interface base ipv4 and ipv6 address +* ``routers`` : user can add number of routers as per topology, router's name + can be any logical name, ex- r1 or a0. +* ``r1`` : name of the router +* ``lo`` : loopback interface dict, ipv4 and/or ipv6 addresses generated automatically +* ``type`` : type of interface, to identify loopback interface +* ``links`` : physical interfaces dict, ipv4 and/or ipv6 addresses generated + automatically +* ``r2-link1`` : it will be used when routers have multiple links. 'r2' is router + name, 'link' is any logical name, '1' is to identify link number, + router name and link must be seperated by hyphen (``-``), ex- a0-peer1 + +Optional keywords/options in JSON: + +* ``bgp`` : bgp configuration +* ``local_as`` : Local AS number +* ``unicast`` : All SAFI configuration +* ``neighbor``: All neighbor details +* ``dest_link`` : Destination link to which router will connect +* ``router_id`` : bgp router-id +* ``source_link`` : if user wants to establish bgp neighborship with loopback + interface, add ``source_link``: ``lo`` +* ``keepalivetimer`` : Keep alive timer for BGP neighbor +* ``holddowntimer`` : Hold down timer for BGP neighbor +* ``static_routes`` : create static routes for routers +* ``redistribute`` : redistribute static and/or connected routes +* ``prefix_lists`` : create Prefix-lists for routers + +Building topology and configurations +"""""""""""""""""""""""""""""""""""" + +Topology and initial configuration as well as teardown are invoked through the +use of a pytest fixture:: + + + from lib import fixtures + + tgen = pytest.fixture(fixtures.tgen_json, scope="module") + + + # tgen is defined above + # topo is a fixture defined in ../conftest.py and automatically available + def test_bgp_convergence(tgen, topo): + bgp_convergence = bgp.verify_bgp_convergence(tgen, topo) + assert bgp_convergence + +The `fixtures.topo_json` function calls `topojson.setup_module_from_json()` to +create and return a new `topogen.Topogen()` object using the JSON config file +with the same base filename as the test (i.e., `test_file.py` -> +`test_file.json`). Additionally, the fixture calls `tgen.stop_topology()` after +all the tests have run to cleanup. The function is only invoked once per +file/module (scope="module"), but the resulting object is passed to each +function that has `tgen` as an argument. + +For more info on the powerful pytest fixtures feature please see `FIXTURES`_. + +.. _FIXTURES: https://docs.pytest.org/en/6.2.x/fixture.html + +Creating configuration files +"""""""""""""""""""""""""""" + +Router's configuration would be saved in config file frr_json.conf. Common +configurations are like, static routes, prefixlists and route maps etc configs, +these configs can be used by any other protocols as it is. +BGP config will be specific to BGP protocol testing. + +* json file is passed to API Topogen() which saves the JSON object in + `self.json_topo` +* The Topogen object is then passed to API build_config_from_json(), which looks + for configuration tags in new JSON object. +* If tag is found in the JSON object, configuration is created as per input and + written to file frr_json.conf +* Once JSON parsing is over, frr_json.conf is loaded onto respective router. + Config loading is done using 'vtysh -f <file>'. Initial config at this point + is also saved frr_json_initial.conf. This file can be used to reset + configuration on router, during the course of execution. +* Reset of configuration is done using frr "reload.py" utility, which + calculates the difference between router's running config and user's config + and loads delta file to router. API used - reset_config_on_router() + +Writing Tests +""""""""""""" + +Test topologies should always be bootstrapped from the +`example_test/test_template_json.py` when possible in order to take advantage of +the most recent infrastructure support code. + +Example: + + +* Define a module scoped fixture to setup/teardown and supply the tests with the + `Topogen` object. + +.. code-block:: python + + import pytest + from lib import fixtures + + tgen = pytest.fixture(fixtures.tgen_json, scope="module") + + +* Define test functions using pytest fixtures + +.. code-block:: python + + from lib import bgp + + # tgen is defined above + # topo is a global available fixture defined in ../conftest.py + def test_bgp_convergence(tgen, topo): + "Test for BGP convergence." + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + bgp_convergence = bgp.verify_bgp_convergence(tgen, topo) + assert bgp_convergence diff --git a/doc/developer/topotests-markers.rst b/doc/developer/topotests-markers.rst new file mode 100644 index 0000000..670bf0d --- /dev/null +++ b/doc/developer/topotests-markers.rst @@ -0,0 +1,115 @@ +.. _topotests-markers: + +Markers +-------- + +To allow for automated selective testing on large scale continuous integration +systems, all tests must be marked with at least one of the following markers: + +* babeld +* bfdd +* bgpd +* eigrpd +* isisd +* ldpd +* mgmtd +* nhrpd +* ospf6d +* ospfd +* pathd +* pbrd +* pimd +* ripd +* ripngd +* sharpd +* staticd +* vrrpd + +The markers corespond to the daemon subdirectories in FRR's source code and have +to be added to tests on a module level depending on which daemons are used +during the test. + +The goal is to have continuous integration systems scan code submissions, detect +changes to files in a daemons subdirectory and select only tests using that +daemon to run to shorten developers waiting times for test results and save test +infrastructure resources. + +Newly written modules and code changes on tests, which do not contain any or +incorrect markers will be rejected by reviewers. + + +Registering markers +^^^^^^^^^^^^^^^^^^^ +The Registration of new markers takes place in the file +``tests/topotests/pytest.ini``: + +.. code:: python3 + + # tests/topotests/pytest.ini + [pytest] + ... + markers = + babeld: Tests that run against BABELD + bfdd: Tests that run against BFDD + ... + vrrpd: Tests that run against VRRPD + + +Adding markers to tests +^^^^^^^^^^^^^^^^^^^^^^^ +Markers are added to a test by placing a global variable in the test module. + +Adding a single marker: + +.. code:: python3 + + import pytest + ... + + # add after imports, before defining classes or functions: + pytestmark = pytest.mark.bfdd + + ... + + def test_using_bfdd(): + + +Adding multiple markers: + +.. code:: python3 + + import pytest + ... + + # add after imports, before defining classes or functions: + pytestmark = [ + pytest.mark.bgpd, + pytest.mark.ospfd, + pytest.mark.ospf6d + ] + + ... + + def test_using_bgpd_ospfd_ospf6d(): + + +Selecting marked modules for testing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Selecting by a single marker: + +.. code:: bash + + pytest -v -m isisd + +Selecting by multiple markers: + +.. code:: bash + + pytest -v -m "isisd or ldpd or nhrpd" + + +Further Information +^^^^^^^^^^^^^^^^^^^ +The `online pytest documentation <https://docs.pytest.org/en/stable/example/markers.html>`_ +provides further information and usage examples for pytest markers. + diff --git a/doc/developer/topotests-snippets.rst b/doc/developer/topotests-snippets.rst new file mode 100644 index 0000000..fb3c928 --- /dev/null +++ b/doc/developer/topotests-snippets.rst @@ -0,0 +1,272 @@ +.. _topotests-snippets: + +Snippets +-------- + +This document will describe common snippets of code that are frequently needed +to perform some test checks. + +Checking for router / test failures +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following check uses the topogen API to check for software failure (e.g. +zebra died) and/or for errors manually set by ``Topogen.set_error()``. + +.. code:: py + + # Get the topology reference + tgen = get_topogen() + + # Check for errors in the topology + if tgen.routers_have_failure(): + # Skip the test with the topology errors as reason + pytest.skip(tgen.errors) + +Checking FRR routers version +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This code snippet is usually run after the topology setup to make sure all +routers instantiated in the topology have the correct software version. + +.. code:: py + + # Get the topology reference + tgen = get_topogen() + + # Get the router list + router_list = tgen.routers() + + # Run the check for all routers + for router in router_list.values(): + if router.has_version('<', '3'): + # Set topology error, so the next tests are skipped + tgen.set_error('unsupported version') + +A sample of this snippet in a test can be found `here +<ldp-vpls-topo1/test_ldp_vpls_topo1.py>`__. + +Interacting with equipment +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You might want to interact with the topology equipment during the tests and +there are different ways to do so. + +Notes: + +1. When using the Topogen API, all the equipment code derives from ``Topogear`` + (`lib/topogen.py <lib/topogen.py>`__). If you feel brave you can look by + yourself how the abstractions that will be mentioned here work. + +2. When not using the ``Topogen`` API there is only one way to interact with + the equipment, which is by calling the ``mininet`` API functions directly + to spawn commands. + +Interacting with the Linux sandbox +"""""""""""""""""""""""""""""""""" + +Without ``Topogen``: + +.. code:: py + + global net + output = net['r1'].cmd('echo "foobar"') + print 'output is: {}'.format(output) + +With ``Topogen``: + +.. code:: py + + tgen = get_topogen() + output = tgen.gears['r1'].run('echo "foobar"') + print 'output is: {}'.format(output) + +Interacting with VTYSH +"""""""""""""""""""""" + +Without ``Topogen``: + +.. code:: py + + global net + output = net['r1'].cmd('vtysh "show ip route" 2>/dev/null') + print 'output is: {}'.format(output) + +With ``Topogen``: + +.. code:: py + + tgen = get_topogen() + output = tgen.gears['r1'].vtysh_cmd("show ip route") + print 'output is: {}'.format(output) + +``Topogen`` also supports sending multiple lines of command: + +.. code:: py + + tgen = get_topogen() + output = tgen.gears['r1'].vtysh_cmd(""" + configure terminal + router bgp 10 + bgp router-id 10.0.255.1 + neighbor 1.2.3.4 remote-as 10 + ! + router bgp 11 + bgp router-id 10.0.255.2 + ! + """) + print 'output is: {}'.format(output) + +You might also want to run multiple commands and get only the commands that +failed: + +.. code:: py + + tgen = get_topogen() + output = tgen.gears['r1'].vtysh_multicmd(""" + configure terminal + router bgp 10 + bgp router-id 10.0.255.1 + neighbor 1.2.3.4 remote-as 10 + ! + router bgp 11 + bgp router-id 10.0.255.2 + ! + """, pretty_output=false) + print 'output is: {}'.format(output) + +Translating vtysh JSON output into Python structures: + +.. code:: py + + tgen = get_topogen() + json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True) + output = json.dumps(json_output, indent=4) + print 'output is: {}'.format(output) + + # You can also access the data structure as normal. For example: + # protocol = json_output['1.1.1.1/32']['protocol'] + # assert protocol == "ospf", "wrong protocol" + +.. note:: + + ``vtysh_(multi)cmd`` is only available for router types of equipment. + +Invoking mininet CLI +^^^^^^^^^^^^^^^^^^^^ + +Without ``Topogen``: + +.. code:: py + + CLI(net) + +With ``Topogen``: + +.. code:: py + + tgen = get_topogen() + tgen.mininet_cli() + +Reading files +^^^^^^^^^^^^^ + +Loading a normal text file content in the current directory: + +.. code:: py + + # If you are using Topogen + # CURDIR = CWD + # + # Otherwise find the directory manually: + CURDIR = os.path.dirname(os.path.realpath(__file__)) + + file_name = '{}/r1/show_ip_route.txt'.format(CURDIR) + file_content = open(file_name).read() + +Loading JSON from a file: + +.. code:: py + + import json + + file_name = '{}/r1/show_ip_route.json'.format(CURDIR) + file_content = json.loads(open(file_name).read()) + +Comparing JSON output +^^^^^^^^^^^^^^^^^^^^^ + +After obtaining JSON output formatted with Python data structures, you may use +it to assert a minimalist schema: + +.. code:: py + + tgen = get_topogen() + json_output = tgen.gears['r1'].vtysh_cmd("show ip route json", isjson=True) + + expect = { + '1.1.1.1/32': { + 'protocol': 'ospf' + } + } + + assertmsg = "route 1.1.1.1/32 was not learned through OSPF" + assert json_cmp(json_output, expect) is None, assertmsg + +``json_cmp`` function description (it might be outdated, you can find the +latest description in the source code at +:file:`tests/topotests/lib/topotest.py` + +.. code:: text + + JSON compare function. Receives two parameters: + * `d1`: json value + * `d2`: json subset which we expect + + Returns `None` when all keys that `d1` has matches `d2`, + otherwise a string containing what failed. + + Note: key absence can be tested by adding a key with value `None`. + +Pausing execution +^^^^^^^^^^^^^^^^^ + +Preferably, choose the ``sleep`` function that ``topotest`` provides, as it +prints a notice during the test execution to help debug topology test execution +time. + +.. code:: py + + # Using the topotest sleep + from lib import topotest + + topotest.sleep(10, 'waiting 10 seconds for bla') + # or just tell it the time: + # topotest.sleep(10) + # It will print 'Sleeping for 10 seconds'. + + # Or you can also use the Python sleep, but it won't show anything + from time import sleep + sleep(5) + +iproute2 Linux commands as JSON +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``topotest`` has two helpers implemented that parses the output of ``ip route`` +commands to JSON. It might simplify your comparison needs by only needing to +provide a Python dictionary. + +.. code:: py + + from lib import topotest + + tgen = get_topogen() + routes = topotest.ip4_route(tgen.gears['r1']) + expected = { + '10.0.1.0/24': {}, + '10.0.2.0/24': { + 'dev': 'r1-eth0' + } + } + + assertmsg = "failed to find 10.0.1.0/24 and/or 10.0.2.0/24" + assert json_cmp(routes, expected) is None, assertmsg diff --git a/doc/developer/topotests.rst b/doc/developer/topotests.rst new file mode 100644 index 0000000..e1702c4 --- /dev/null +++ b/doc/developer/topotests.rst @@ -0,0 +1,1518 @@ +.. _topotests: + +Topotests +========= + +Topotests is a suite of topology tests for FRR built on top of micronet. + +Installation and Setup +---------------------- + +Topotests run under python3. + +Tested with Ubuntu 22.04,Ubuntu 20.04, and Debian 12. + +Python protobuf version < 4 is required b/c python protobuf >= 4 requires a +protoc >= 3.19, and older package versions are shipped by in the above distros. + +Instructions are the same for all setups. However, ExaBGP is only used for +BGP tests. + +Tshark is only required if you enable any packet captures on test runs. + +Valgrind is only required if you enable valgrind on test runs. + +Installing Topotest Requirements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: shell + + apt-get install \ + gdb \ + iproute2 \ + net-tools \ + python3-pip \ + iputils-ping \ + tshark \ + valgrind + python3 -m pip install wheel + python3 -m pip install 'pytest>=6.2.4' 'pytest-xdist>=2.3.0' + python3 -m pip install 'scapy>=2.4.5' + python3 -m pip install xmltodict + python3 -m pip install git+https://github.com/Exa-Networks/exabgp@0659057837cd6c6351579e9f0fa47e9fb7de7311 + useradd -d /var/run/exabgp/ -s /bin/false exabgp + +The version of protobuf package that is installed on your system will determine +which versions of the python protobuf packages you need to install. + +.. code:: shell + + # - Either - For protobuf version <= 3.12 + python3 -m pip install 'protobuf<4' + + # - OR- for protobuf version >= 3.21 + python3 -m pip install 'protobuf>=4' + + # To enable the gRPC topotest also install: + python3 -m pip install grpcio grpcio-tools + + +Enable Coredumps +"""""""""""""""" + +Optional, will give better output. + +.. code:: shell + + disable apport (which move core files) + +Set ``enabled=0`` in ``/etc/default/apport``. + +Next, update security limits by changing :file:`/etc/security/limits.conf` to:: + + #<domain> <type> <item> <value> + * soft core unlimited + root soft core unlimited + * hard core unlimited + root hard core unlimited + +Reboot for options to take effect. + +SNMP Utilities Installation +""""""""""""""""""""""""""" + +To run SNMP test you need to install SNMP utilities and MIBs. Unfortunately +there are some errors in the upstream MIBS which need to be patched up. The +following steps will get you there on Ubuntu 20.04. + +.. code:: shell + + apt install libsnmp-dev + apt install snmpd snmp + apt install snmp-mibs-downloader + download-mibs + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/iana/IANA-IPPM-METRICS-REGISTRY-MIB -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/SNMPv2-PDU -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/IPATM-IPMC-MIB -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB + edit /etc/snmp/snmp.conf to look like this + # As the snmp packages come without MIB files due to license reasons, loading + # of MIBs is disabled by default. If you added the MIBs you can reenable + # loading them by commenting out the following line. + mibs +ALL + + +FRR Installation +^^^^^^^^^^^^^^^^ + +FRR needs to be installed separately. It is assume to be configured like the +standard Ubuntu Packages: + +- Binaries in :file:`/usr/lib/frr` +- State Directory :file:`/var/run/frr` +- Running under user ``frr``, group ``frr`` +- vtygroup: ``frrvty`` +- config directory: :file:`/etc/frr` +- For FRR Packages, install the dbg package as well for coredump decoding + +No FRR config needs to be done and no FRR daemons should be run ahead of the +test. They are all started as part of the test. + +Manual FRR build +"""""""""""""""" + +If you prefer to manually build FRR, then use the following suggested config: + +.. code:: shell + + ./configure \ + --prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-vtysh \ + --enable-pimd \ + --enable-pim6d \ + --enable-sharpd \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-vty-group=frrvty \ + --enable-snmp=agentx \ + --with-pkg-extra-version=-my-manual-build + +And create ``frr`` user and ``frrvty`` group as follows: + +.. code:: shell + + addgroup --system --gid 92 frr + addgroup --system --gid 85 frrvty + adduser --system --ingroup frr --home /var/run/frr/ \ + --gecos "FRRouting suite" --shell /bin/false frr + usermod -G frrvty frr + +Executing Tests +--------------- + +Configure your sudo environment +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Topotests must be run as root. Normally this will be accomplished through the +use of the ``sudo`` command. In order for topotests to be able to open new +windows (either XTerm or byobu/screen/tmux windows) certain environment +variables must be passed through the sudo command. One way to do this is to +specify the ``-E`` flag to ``sudo``. This will carry over most if not all +your environment variables include ``PATH``. For example: + +.. code:: shell + + sudo -E python3 -m pytest -s -v + +If you do not wish to use ``-E`` (e.g., to avoid ``sudo`` inheriting +``PATH``) you can modify your `/etc/sudoers` config file to specifically pass +the environment variables required by topotests. Add the following commands to +your ``/etc/sudoers`` config file. + +.. code:: shell + + Defaults env_keep="TMUX" + Defaults env_keep+="TMUX_PANE" + Defaults env_keep+="STY" + Defaults env_keep+="DISPLAY" + +If there was already an ``env_keep`` configuration there be sure to use the +``+=`` rather than ``=`` on the first line above as well. + + +Execute all tests in distributed test mode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: shell + + sudo -E pytest -s -v -nauto --dist=loadfile + +The above command must be executed from inside the topotests directory. + +All test\_\* scripts in subdirectories are detected and executed (unless +disabled in ``pytest.ini`` file). Pytest will execute up to N tests in parallel +where N is based on the number of cores on the host. + +Analyze Test Results (``analyze.py``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default router and execution logs are saved in ``/tmp/topotests`` and an XML +results file is saved in ``/tmp/topotests/topotests.xml``. An analysis tool +``analyze.py`` is provided to archive and analyze these results after the run +completes. + +After the test run completes one should pick an archive directory to store the +results in and pass this value to ``analyze.py``. On first execution the results +are moved to that directory from ``/tmp/topotests``. Subsequent runs of +``analyze.py`` with the same args will use that directories contents for instead +of copying any new results from ``/tmp``. Below is an example of this which also +shows the default behavior which is to display all failed and errored tests in +the run. + +.. code:: shell + + ~/frr/tests/topotests# ./analyze.py -Ar run-save + bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge + ospf_basic_functionality/test_ospf_lan.py::test_ospf_lan_tc1_p0 + bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2.py::test_BGP_GR_10_p2 + bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_routingTable + +Here we see that 4 tests have failed. We can dig deeper by displaying the +captured logs and errors. First let's redisplay the results enumerated by adding +the ``-E`` flag + +.. code:: shell + + ~/frr/tests/topotests# ./analyze.py -Ar run-save -E + 0 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge + 1 ospf_basic_functionality/test_ospf_lan.py::test_ospf_lan_tc1_p0 + 2 bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2.py::test_BGP_GR_10_p2 + 3 bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_routingTable + +Now to look at the error message for a failed test we use ``-T N`` where N is +the number of the test we are interested in along with ``--errmsg`` option. + +.. code:: shell + + ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0 --errmsg + bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge: AssertionError: BGP did not converge: + + IPv4 Unicast Summary: + BGP router identifier 172.30.1.1, local AS number 100 VIEW 1 vrf-id -1 + BGP table version 1 + RIB entries 1, using 184 bytes of memory + Peers 3, using 2169 KiB of memory + + Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc + 172.16.1.1 4 65001 0 0 0 0 0 never Connect 0 N/A + 172.16.1.2 4 65002 0 0 0 0 0 never Connect 0 N/A + 172.16.1.5 4 65005 0 0 0 0 0 never Connect 0 N/A + + Total number of neighbors 3 + + assert False + +Now to look at the error text for a failed test we can use ``-T RANGES`` where +``RANGES`` can be a number (e.g., ``5``), a range (e.g., ``0-10``), or a comma +separated list numbers and ranges (e.g., ``5,10-20,30``) of the test cases we +are interested in along with ``--errtext`` option. In the example below we'll +select the first failed test case. + +.. code:: shell + + ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0 --errtext + bgp_multiview_topo1/test_bgp_multiview_topo1.py::test_bgp_converge: def test_bgp_converge(): + "Check for BGP converged on all peers and BGP views" + + global fatal_error + global net + [...] + else: + # Bail out with error if a router fails to converge + bgpStatus = net["r%s" % i].cmd('vtysh -c "show ip bgp view %s summary"' % view) + > assert False, "BGP did not converge:\n%s" % bgpStatus + E AssertionError: BGP did not converge: + E + E IPv4 Unicast Summary: + E BGP router identifier 172.30.1.1, local AS number 100 VIEW 1 vrf-id -1 + [...] + E Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc + E 172.16.1.1 4 65001 0 0 0 0 0 never Connect 0 N/A + E 172.16.1.2 4 65002 0 0 0 0 0 never Connect 0 N/A + [...] + +To look at the full capture for a test including the stdout and stderr which +includes full debug logs, use ``--full`` option, or specify a ``-T RANGES`` without +specifying ``--errmsg`` or ``--errtext``. + +.. code:: shell + + ~/frr/tests/topotests# ./analyze.py -Ar run-save -T0 + @classname: bgp_multiview_topo1.test_bgp_multiview_topo1 + @name: test_bgp_converge + @time: 141.401 + @message: AssertionError: BGP did not converge: + [...] + system-out: --------------------------------- Captured Log --------------------------------- + 2021-08-09 02:55:06,581 DEBUG: lib.micronet_compat.topo: Topo(unnamed): Creating + 2021-08-09 02:55:06,581 DEBUG: lib.micronet_compat.topo: Topo(unnamed): addHost r1 + [...] + 2021-08-09 02:57:16,932 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary" 2> /dev/null | grep ^[0-9] | grep -vP " 11\\s+(\\d+)"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False}) + 2021-08-09 02:57:22,290 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary" 2> /dev/null | grep ^[0-9] | grep -vP " 11\\s+(\\d+)"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False}) + 2021-08-09 02:57:27,636 DEBUG: topolog.r1: LinuxNamespace(r1): cmd_status("['/bin/bash', '-c', 'vtysh -c "show ip bgp view 1 summary"']", kwargs: {'encoding': 'utf-8', 'stdout': -1, 'stderr': -2, 'shell': False}) + --------------------------------- Captured Out --------------------------------- + system-err: --------------------------------- Captured Err --------------------------------- + +Filtered results +"""""""""""""""" + +There are 4 types of test results, [e]rrored, [f]ailed, [p]assed, and +[s]kipped. One can select the set of results to show with the ``-S`` or +``--select`` flags along with the letters for each type (i.e., ``-S efps`` +would select all results). By default ``analyze.py`` will use ``-S ef`` (i.e., +[e]rrors and [f]ailures) unless the ``--search`` filter is given in which case +the default is to search all results (i.e., ``-S efps``). + +One can find all results which contain a ``REGEXP``. To filter results using a +regular expression use the ``--search REGEXP`` option. In this case, by default, +all result types will be searched for a match against the given ``REGEXP``. If a +test result output contains a match it is selected into the set of results to show. + +An example of using ``--search`` would be to search all tests results for some +log message, perhaps a warning or error. + +Using XML Results File from CI +"""""""""""""""""""""""""""""" + +``analyze.py`` actually only needs the ``topotests.xml`` file to run. This is +very useful for analyzing a CI run failure where one only need download the +``topotests.xml`` artifact from the run and then pass that to ``analyze.py`` +with the ``-r`` or ``--results`` option. + +For local runs if you wish to simply copy the ``topotests.xml`` file (leaving +the log files where they are), you can pass the ``-a`` (or ``--save-xml``) +instead of the ``-A`` (or ``-save``) options. + +Analyze Results from a Container Run +"""""""""""""""""""""""""""""""""""" + +``analyze.py`` can also be used with ``docker`` or ``podman`` containers. +Everything works exactly as with a host run except that you specify the name of +the container, or the container-id, using the `-C` or ``--container`` option. +``analyze.py`` will then use the results inside that containers +``/tmp/topotests`` directory. It will extract and save those results when you +pass the ``-A`` or ``-a`` options just as withe host results. + + +Execute single test +^^^^^^^^^^^^^^^^^^^ + +.. code:: shell + + cd test_to_be_run + sudo -E pytest ./test_to_be_run.py + +For example, and assuming you are inside the frr directory: + +.. code:: shell + + cd tests/topotests/bgp_l3vpn_to_bgp_vrf + sudo -E pytest ./test_bgp_l3vpn_to_bgp_vrf.py + +For further options, refer to pytest documentation. + +Test will set exit code which can be used with ``git bisect``. + +For the simulated topology, see the description in the python file. + +Running Topotests with AddressSanitizer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Topotests can be run with AddressSanitizer. It requires GCC 4.8 or newer. +(Ubuntu 16.04 as suggested here is fine with GCC 5 as default). For more +information on AddressSanitizer, see +https://github.com/google/sanitizers/wiki/AddressSanitizer. + +The checks are done automatically in the library call of ``checkRouterRunning`` +(ie at beginning of tests when there is a check for all daemons running). No +changes or extra configuration for topotests is required beside compiling the +suite with AddressSanitizer enabled. + +If a daemon crashed, then the errorlog is checked for AddressSanitizer output. +If found, then this is added with context (calling test) to +:file:`/tmp/AddressSanitizer.txt` in Markdown compatible format. + +Compiling for GCC AddressSanitizer requires to use ``gcc`` as a linker as well +(instead of ``ld``). Here is a suggest way to compile frr with AddressSanitizer +for ``master`` branch: + +.. code:: shell + + git clone https://github.com/FRRouting/frr.git + cd frr + ./bootstrap.sh + ./configure \ + --enable-address-sanitizer \ + --prefix=/usr/lib/frr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr --bindir=/usr/lib/frr \ + --with-moduledir=/usr/lib/frr/modules \ + --enable-multipath=0 --enable-rtadv \ + --enable-tcp-zebra --enable-fpm --enable-pimd \ + --enable-sharpd + make + sudo make install + # Create symlink for vtysh, so topotest finds it in /usr/lib/frr + sudo ln -s /usr/lib/frr/vtysh /usr/bin/ + +and create ``frr`` user and ``frrvty`` group as shown above. + +Debugging Topotest Failures +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Install and run tests inside ``tmux`` or ``byobu`` for best results. + +``XTerm`` is also fully supported. GNU ``screen`` can be used in most +situations; however, it does not work as well with launching ``vtysh`` or shell +on error. + +For the below debugging options which launch programs or CLIs, topotest should +be run within ``tmux`` (or ``screen``)_, as ``gdb``, the shell or ``vtysh`` will +be launched using that windowing program, otherwise ``xterm`` will be attempted +to launch the given programs. + +NOTE: you must run the topotest (pytest) such that your DISPLAY, STY or TMUX +environment variables are carried over. You can do this by passing the +``-E`` flag to ``sudo`` or you can modify your ``/etc/sudoers`` config to +automatically pass that environment variable through to the ``sudo`` +environment. + +.. _screen: https://www.gnu.org/software/screen/ +.. _tmux: https://github.com/tmux/tmux/wiki + +Capturing Packets +""""""""""""""""" + +One can view and capture packets on any of the networks or interfaces defined by +the topotest by specifying the ``--pcap=NET|INTF|all[,NET|INTF,...]`` CLI option +as shown in the examples below. + +.. code:: shell + + # Capture on all networks in isis_topo1 test + sudo -E pytest isis_topo1 --pcap=all + + # Capture on `sw1` network + sudo -E pytest isis_topo1 --pcap=sw1 + + # Capture on `sw1` network and on interface `eth0` on router `r2` + sudo -E pytest isis_topo1 --pcap=sw1,r2:r2-eth0 + +For each capture a window is opened displaying a live summary of the captured +packets. Additionally, the entire packet stream is captured in a pcap file in +the tests log directory e.g.,: + +.. code:: console + + $ sudo -E pytest isis_topo1 --pcap=sw1,r2:r2-eth0 + ... + $ ls -l /tmp/topotests/isis_topo1.test_isis_topo1/ + -rw------- 1 root root 45172 Apr 19 05:30 capture-r2-r2-eth0.pcap + -rw------- 1 root root 48412 Apr 19 05:30 capture-sw1.pcap + ... + +Viewing Live Daemon Logs +"""""""""""""""""""""""" + +One can live view daemon or the frr logs in separate windows using the +``--logd`` CLI option as shown below. + +.. code:: shell + + # View `ripd` logs on all routers in test + sudo -E pytest rip_allow_ecmp --logd=ripd + + # View `ripd` logs on all routers and `mgmtd` log on `r1` + sudo -E pytest rip_allow_ecmp --logd=ripd --logd=mgmtd,r1 + +For each capture a window is opened displaying a live summary of the captured +packets. Additionally, the entire packet stream is captured in a pcap file in +the tests log directory e.g., + +When using a unified log file ``frr.log`` one substitutes ``frr`` for the +daemon name in the ``--logd`` CLI option, e.g., + +.. code:: shell + + # View `frr` log on all routers in test + sudo -E pytest some_test_suite --logd=frr + +Spawning Debugging CLI, ``vtysh`` or Shells on Routers on Test Failure +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +One can have a debugging CLI invoked on test failures by specifying the +``--cli-on-error`` CLI option as shown in the example below. + +.. code:: shell + + sudo -E pytest --cli-on-error all-protocol-startup + +The debugging CLI can run shell or vtysh commands on any combination of routers +It can also open shells or vtysh in their own windows for any combination of +routers. This is usually the most useful option when debugging failures. Here is +the help command from within a CLI launched on error: + +.. code:: shell + + test_bgp_multiview_topo1/test_bgp_routingTable> help + + Basic Commands: + cli :: open a secondary CLI window + help :: this help + hosts :: list hosts + quit :: quit the cli + + HOST can be a host or one of the following: + - '*' for all hosts + - '.' for the parent munet + - a regex specified between '/' (e.g., '/rtr.*/') + + New Window Commands: + logd HOST [HOST ...] DAEMON :: tail -f on the logfile of the given DAEMON for the given HOST[S] + pcap NETWORK :: capture packets from NETWORK into file capture-NETWORK.pcap the command is run within a new window which also shows packet summaries. NETWORK can also be an interface specified as HOST:INTF. To capture inside the host namespace. + stderr HOST [HOST ...] DAEMON :: tail -f on the stderr of the given DAEMON for the given HOST[S] + stdlog HOST [HOST ...] :: tail -f on the `frr.log` for the given HOST[S] + stdout HOST [HOST ...] DAEMON :: tail -f on the stdout of the given DAEMON for the given HOST[S] + term HOST [HOST ...] :: open terminal[s] (TMUX or XTerm) on HOST[S], * for all + vtysh ROUTER [ROUTER ...] :: + xterm HOST [HOST ...] :: open XTerm[s] on HOST[S], * for all + Inline Commands: + [ROUTER ...] COMMAND :: execute vtysh COMMAND on the router[s] + [HOST ...] sh <SHELL-COMMAND> :: execute <SHELL-COMMAND> on hosts + [HOST ...] shi <INTERACTIVE-COMMAND> :: execute <INTERACTIVE-COMMAND> on HOST[s] + + test_bgp_multiview_topo1/test_bgp_routingTable> r1 show int br + ------ Host: r1 ------ + Interface Status VRF Addresses + --------- ------ --- --------- + erspan0 down default + gre0 down default + gretap0 down default + lo up default + r1-eth0 up default 172.16.1.254/24 + r1-stub up default 172.20.0.1/28 + + ---------------------- + test_bgp_multiview_topo1/test_bgp_routingTable> + +Additionally, one can have ``vtysh`` or a shell launched on all routers when a +test fails. To launch the given process on each router after a test failure +specify one of ``--shell-on-error`` or ``--vtysh-on-error``. + +Spawning ``vtysh`` or Shells on Routers +""""""""""""""""""""""""""""""""""""""" + +Topotest can automatically launch a shell or ``vtysh`` for any or all routers in +a test. This is enabled by specifying 1 of 2 CLI arguments ``--shell`` or +``--vtysh``. Both of these options can be set to a single router value, multiple +comma-seperated values, or ``all``. + +When either of these options are specified topotest will pause after setup and +each test to allow for inspection of the router state. + +Here's an example of launching ``vtysh`` on routers ``rt1`` and ``rt2``. + +.. code:: shell + + sudo -E pytest --vtysh=rt1,rt2 all-protocol-startup + +.. _debug_with_gdb: + +Debugging with GDB +"""""""""""""""""" + +Topotest can automatically launch any daemon with ``gdb``, possibly setting +breakpoints for any test run. This is enabled by specifying 1 or 2 CLI arguments +``--gdb-routers`` and ``--gdb-daemons``. Additionally ``--gdb-breakpoints`` can +be used to automatically set breakpoints in the launched ``gdb`` processes. + +Each of these options can be set to a single value, multiple comma-seperated +values, or ``all``. If ``--gdb-routers`` is empty but ``--gdb_daemons`` is set +then the given daemons will be launched in ``gdb`` on all routers in the test. +Likewise if ``--gdb_routers`` is set, but ``--gdb_daemons`` is empty then all +daemons on the given routers will be launched in ``gdb``. + +Here's an example of launching ``zebra`` and ``bgpd`` inside ``gdb`` on router +``r1`` with a breakpoint set on ``nb_config_diff`` + +.. code:: shell + + sudo -E pytest --gdb-routers=r1 \ + --gdb-daemons=bgpd,zebra \ + --gdb-breakpoints=nb_config_diff \ + all-protocol-startup + +Finally, for Emacs users, you can specify ``--gdb-use-emacs``. When specified +the first router and daemon to be launched in gdb will be launched and run with +Emacs gdb functionality by using `emacsclient --eval` commands. This provides an +IDE debugging experience for Emacs users. This functionality works best when +using password-less sudo. + +Reporting Memleaks with FRR Memory Statistics +""""""""""""""""""""""""""""""""""""""""""""" + +FRR reports all allocated FRR memory objects on exit to standard error. +Topotest can be run to report such output as errors in order to check for +memleaks in FRR memory allocations. Specifying the CLI argument +``--memleaks`` will enable reporting FRR-based memory allocations at exit as errors. + +.. code:: shell + + sudo -E pytest --memleaks all-protocol-startup + + +StdErr log from daemos after exit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When running with ``--memleaks``, to enable the reporting of other, +non-memory related, messages seen on StdErr after the daemons exit, +the following env variable can be set:: + + export TOPOTESTS_CHECK_STDERR=Yes + +(The value doesn't matter at this time. The check is whether the env +variable exists or not.) There is no pass/fail on this reporting; the +Output will be reported to the console. + +Collect Memory Leak Information +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When running with ``--memleaks``, FRR processes report unfreed memory +allocations upon exit. To enable also reporting of memory leaks to a specific +location, define an environment variable ``TOPOTESTS_CHECK_MEMLEAK`` with the +file prefix, i.e.: + +:: + + export TOPOTESTS_CHECK_MEMLEAK="/home/mydir/memleak_" + +For tests that support the TOPOTESTS_CHECK_MEMLEAK environment variable, this +will enable output to the information to files with the given prefix (followed +by testname), e.g.,: +file:`/home/mydir/memcheck_test_bgp_multiview_topo1.txt` in case +of a memory leak. + +Detecting Memleaks with Valgrind +"""""""""""""""""""""""""""""""" + +Topotest can automatically launch all daemons with ``valgrind`` to check for +memleaks. This is enabled by specifying 1 to 3 CLI arguments. +``--valgrind-memleaks`` enables memleak detection. ``--valgrind-extra`` enables +extra functionality including generating a suppression file. The suppression +file ``tools/valgrind.supp`` is used when memleak detection is enabled. Finally, +``--valgrind-leak-kinds=KINDS`` can be used to modify what types of links are +reported. This corresponds to valgrind's ``--show-link-kinds`` arg. The value is +either ``all`` or a comma-separated list of types: +``definite,indirect,possible,reachable``. The default is ``definite,possible``. + +.. code:: shell + + sudo -E pytest --valgrind-memleaks all-protocol-startup + +.. note:: GDB can be used in conjection with valgrind. + + When you enable ``--valgrind-memleaks`` and you also launch various daemons + under GDB (debug_with_gdb_) topotest will connect the two utilities using + ``--vgdb-error=0`` and attaching to a ``vgdb`` process. This is very + useful for debugging bugs with use of uninitialized errors, et al. + +Collecting Performance Data using perf(1) +""""""""""""""""""""""""""""""""""""""""" + +Topotest can automatically launch any daemon under ``perf(1)`` to collect +performance data. The daemon is run in non-daemon mode with ``perf record -g``. +The ``perf.data`` file will be saved in the router specific directory under the +tests run directoy. + +Here's an example of collecting performance data from ``mgmtd`` on router ``r1`` +during the config_timing test. + +.. code:: console + + $ sudo -E pytest --perf=mgmtd,r1 config_timing + ... + $ find /tmp/topotests/ -name '*perf.data*' + /tmp/topotests/config_timing.test_config_timing/r1/perf.data + +To specify different arguments for ``perf record``, one can use the +``--perf-options`` this will replace the ``-g`` used by default. + +Running Daemons under RR Debug (``rr record``) +"""""""""""""""""""""""""""""""""""""""""""""" + +Topotest can automatically launch any daemon under ``rr(1)`` to collect +execution state. The daemon is run in the foreground with ``rr record``. + +The execution state will be saved in the router specific directory +(in a `rr` subdir that rr creates) under the test's run directoy. + +Here's an example of collecting ``rr`` execution state from ``mgmtd`` on router +``r1`` during the ``config_timing`` test. + +.. code:: console + + $ sudo -E pytest --rr-routers=r1 --rr-daemons=mgmtd config_timing + ... + $ find /tmp/topotests/ -name '*perf.data*' + /tmp/topotests/config_timing.test_config_timing/r1/perf.data + +To specify additional arguments for ``rr record``, one can use the +``--rr-options``. + +.. _code_coverage: + +Code coverage +""""""""""""" +Code coverage reporting requires installation of the ``gcov`` and ``lcov`` +packages. + +Code coverage can automatically be gathered for any topotest run. To support +this FRR must first be compiled with the ``--enable-gcov`` configure option. +This will cause *.gnco files to be created during the build. When topotests are +run the statistics are generated and stored in *.gcda files. Topotest +infrastructure will gather these files, capture the information into a +``coverage.info`` ``lcov`` file and also report the coverage summary. + +To enable code coverage support pass the ``--cov-topotest`` argument to pytest. +If you build your FRR in a directory outside of the FRR source directory you +will also need to pass the ``--cov-frr-build-dir`` argument specifying the build +directory location. + +During the topotest run the *.gcda files are generated into a ``gcda`` +sub-directory of the top-level run directory (i.e., normally +``/tmp/topotests/gcda``). These files will then be copied at the end of the +topotest run into the FRR build directory where the ``gcov`` and ``lcov`` +utilities expect to find them. This is done to deal with the various different +file ownership and permissions. + +At the end of the run ``lcov`` will be run to capture all of the coverage data +into a ``coverage.info`` file. This file will be located in the top-level run +directory (i.e., normally ``/tmp/topotests/coverage.info``). + +The ``coverage.info`` file can then be used to generate coverage reports or file +markup (e.g., using the ``genhtml`` utility) or enable markup within your +IDE/editor if supported (e.g., the emacs ``cov-mode`` package) + +NOTE: the *.gcda files in ``/tmp/topotests/gcda`` are cumulative so if you do +not remove them they will aggregate data across multiple topotest runs. + + +.. _topotests_docker: + +Running Tests with Docker +------------------------- + +There is a Docker image which allows to run topotests. + +Quickstart +^^^^^^^^^^ + +If you have Docker installed, you can run the topotests in Docker. The easiest +way to do this, is to use the make targets from this repository. + +Your current user needs to have access to the Docker daemon. Alternatively you +can run these commands as root. + +.. code:: console + + make topotests + +This command will pull the most recent topotests image from Dockerhub, compile +FRR inside of it, and run the topotests. + +Advanced Usage +^^^^^^^^^^^^^^ + +Internally, the topotests make target uses a shell script to pull the image and +spawn the Docker container. + +There are several environment variables which can be used to modify the +behavior of the script, these can be listed by calling it with ``-h``: + +.. code:: console + + ./tests/topotests/docker/frr-topotests.sh -h + +For example, a volume is used to cache build artifacts between multiple runs of +the image. If you need to force a complete recompile, you can set +``TOPOTEST_CLEAN``: + +.. code:: console + + TOPOTEST_CLEAN=1 ./tests/topotests/docker/frr-topotests.sh + +By default, ``frr-topotests.sh`` will build frr and run pytest. If you append +arguments and the first one starts with ``/`` or ``./``, they will replace the +call to pytest. If the appended arguments do not match this patttern, they will +be provided to pytest as arguments. So, to run a specific test with more +verbose logging: + +.. code:: console + + ./tests/topotests/docker/frr-topotests.sh -vv -s all-protocol-startup/test_all_protocol_startup.py + +And to compile FRR but drop into a shell instead of running pytest: + +.. code:: console + + ./tests/topotests/docker/frr-topotests.sh /bin/bash + +Development +^^^^^^^^^^^ + +The Docker image just includes all the components to run the topotests, but not +the topotests themselves. So if you just want to write tests and don't want to +make changes to the environment provided by the Docker image. You don't need to +build your own Docker image if you do not want to. + +When developing new tests, there is one caveat though: The startup script of +the container will run a ``git-clean`` on its copy of the FRR tree to avoid any +pollution of the container with build artefacts from the host. This will also +result in your newly written tests being unavailable in the container unless at +least added to the index with ``git-add``. + +If you do want to test changes to the Docker image, you can locally build the +image and run the tests without pulling from the registry using the following +commands: + +.. code:: console + + make topotests-build + TOPOTEST_PULL=0 make topotests + + +.. _topotests-guidelines: + +Guidelines +---------- + +Executing Tests +^^^^^^^^^^^^^^^ + +To run the whole suite of tests the following commands must be executed at the +top level directory of topotest: + +.. code:: shell + + $ # Change to the top level directory of topotests. + $ cd path/to/topotests + $ # Tests must be run as root, since micronet requires it. + $ sudo -E pytest + +In order to run a specific test, you can use the following command: + +.. code:: shell + + $ # running a specific topology + $ sudo -E pytest ospf-topo1/ + $ # or inside the test folder + $ cd ospf-topo1 + $ sudo -E pytest # to run all tests inside the directory + $ sudo -E pytest test_ospf_topo1.py # to run a specific test + $ # or outside the test folder + $ cd .. + $ sudo -E pytest ospf-topo1/test_ospf_topo1.py # to run a specific one + +The output of the tested daemons will be available at the temporary folder of +your machine: + +.. code:: shell + + $ ls /tmp/topotest/ospf-topo1.test_ospf-topo1/r1 + ... + zebra.err # zebra stderr output + zebra.log # zebra log file + zebra.out # zebra stdout output + ... + +You can also run memory leak tests to get reports: + +.. code:: shell + + $ # Set the environment variable to apply to a specific test... + $ sudo -E env TOPOTESTS_CHECK_MEMLEAK="/tmp/memleak_report_" pytest ospf-topo1/test_ospf_topo1.py + $ # ...or apply to all tests adding this line to the configuration file + $ echo 'memleak_path = /tmp/memleak_report_' >> pytest.ini + $ # You can also use your editor + $ $EDITOR pytest.ini + $ # After running tests you should see your files: + $ ls /tmp/memleak_report_* + memleak_report_test_ospf_topo1.txt + +Writing a New Test +^^^^^^^^^^^^^^^^^^ + +This section will guide you in all recommended steps to produce a standard +topology test. + +This is the recommended test writing routine: + +- Write a topology (Graphviz recommended) +- Obtain configuration files +- Write the test itself +- Format the new code using `black <https://github.com/psf/black>`_ +- Create a Pull Request + +Some things to keep in mind: + +- BGP tests MUST use generous convergence timeouts - you must ensure + that any test involving BGP uses a convergence timeout of at least + 130 seconds. +- Topotests are run on a range of Linux versions: if your test + requires some OS-specific capability (like mpls support, or vrf + support), there are test functions available in the libraries that + will help you determine whether your test should run or be skipped. +- Avoid including unstable data in your test: don't rely on link-local + addresses or ifindex values, for example, because these can change + from run to run. +- Using sleep is almost never appropriate. As an example: if the test resets the + peers in BGP, the test should look for the peers re-converging instead of just + sleeping an arbitrary amount of time and continuing on. See + ``verify_bgp_convergence`` as a good example of this. In particular look at + it's use of the ``@retry`` decorator. If you are having troubles figuring out + what to look for, please do not be afraid to ask. +- Don't duplicate effort. There exists many protocol utility functions that can + be found in their eponymous module under ``tests/topotests/lib/`` (e.g., + ``ospf.py``) + + + +Topotest File Hierarchy +""""""""""""""""""""""" + +Before starting to write any tests one must know the file hierarchy. The +repository hierarchy looks like this: + +.. code:: shell + + $ cd path/to/topotest + $ find ./* + ... + ./README.md # repository read me + ./GUIDELINES.md # this file + ./conftest.py # test hooks - pytest related functions + ./example-test # example test folder + ./example-test/__init__.py # python package marker - must always exist. + ./example-test/test_template.jpg # generated topology picture - see next section + ./example-test/test_template.dot # Graphviz dot file + ./example-test/test_template.py # the topology plus the test + ... + ./ospf-topo1 # the ospf topology test + ./ospf-topo1/r1 # router 1 configuration files + ./ospf-topo1/r1/zebra.conf # zebra configuration file + ./ospf-topo1/r1/ospfd.conf # ospf configuration file + ./ospf-topo1/r1/ospfroute.txt # 'show ip ospf' output reference file + # removed other for shortness sake + ... + ./lib # shared test/topology functions + ./lib/topogen.py # topogen implementation + ./lib/topotest.py # topotest implementation + +Guidelines for creating/editing topotest: + +- New topologies that don't fit the existing directories should create its own +- Always remember to add the ``__init__.py`` to new folders, this makes auto + complete engines and pylint happy +- Router (Quagga/FRR) specific code should go on topotest.py +- Generic/repeated router actions should have an abstraction in + topogen.TopoRouter. +- Generic/repeated non-router code should go to topotest.py +- pytest related code should go to conftest.py (e.g. specialized asserts) + +Defining the Topology +""""""""""""""""""""" + +The first step to write a new test is to define the topology. This step can be +done in many ways, but the recommended is to use Graphviz to generate a drawing +of the topology. It allows us to see the topology graphically and to see the +names of equipment, links and addresses. + +Here is an example of Graphviz dot file that generates the template topology +:file:`tests/topotests/example-test/test_template.dot` (the inlined code might +get outdated, please see the linked file):: + + graph template { + label="template"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon, + label="r2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + s1 [ + shape=oval, + label="s1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + s2 [ + shape=oval, + label="s2\n192.168.1.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- s1 [label="eth0\n.1"]; + + r1 -- s2 [label="eth1\n.100"]; + r2 -- s2 [label="eth0\n.1"]; + } + +Here is the produced graph: + +.. graphviz:: + + graph template { + label="template"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon, + label="r2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + s1 [ + shape=oval, + label="s1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + s2 [ + shape=oval, + label="s2\n192.168.1.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- s1 [label="eth0\n.1"]; + + r1 -- s2 [label="eth1\n.100"]; + r2 -- s2 [label="eth0\n.1"]; + } + +Generating / Obtaining Configuration Files +"""""""""""""""""""""""""""""""""""""""""" + +In order to get the configuration files or command output for each router, we +need to run the topology and execute commands in ``vtysh``. The quickest way to +achieve that is writing the topology building code and running the topology. + +To bootstrap your test topology, do the following steps: + +- Copy the template test + +.. code:: shell + + $ mkdir new-topo/ + $ touch new-topo/__init__.py + $ cp example-test/test_template.py new-topo/test_new_topo.py + +- Modify the template according to your dot file + +Here is the template topology described in the previous section in python code: + +.. code:: py + + topodef = { + "s1": "r1" + "s2": ("r1", "r2") + } + +If more specialized topology definitions, or router initialization arguments are +required a build function can be used instead of a dictionary: + +.. code:: py + + def build_topo(tgen): + "Build function" + + # Create 2 routers + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + # Create a switch with just one router connected to it to simulate a + # empty network. + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + # Create a connection between r1 and r2 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + +- Run the topology + +Topogen allows us to run the topology without running any tests, you can do +that using the following example commands: + +.. code:: shell + + $ # Running your bootstraped topology + $ sudo -E pytest -s --topology-only new-topo/test_new_topo.py + $ # Running the test_template.py topology + $ sudo -E pytest -s --topology-only example-test/test_template.py + $ # Running the ospf_topo1.py topology + $ sudo -E pytest -s --topology-only ospf-topo1/test_ospf_topo1.py + +Parameters explanation: + +.. program:: pytest + +.. option:: -s + + Actives input/output capture. If this is not specified a new window will be + opened for the interactive CLI, otherwise it will be activated inline. + +.. option:: --topology-only + + Don't run any tests, just build the topology. + +After executing the commands above, you should get the following terminal +output: + +.. code:: shell + + frr/tests/topotests# sudo -E pytest -s --topology-only ospf_topo1/test_ospf_topo1.py + ============================= test session starts ============================== + platform linux -- Python 3.9.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 + rootdir: /home/chopps/w/frr/tests/topotests, configfile: pytest.ini + plugins: forked-1.3.0, xdist-2.3.0 + collected 11 items + + [...] + unet> + +The last line shows us that we are now using the CLI (Command Line +Interface), from here you can call your router ``vtysh`` or even bash. + +Here's the help text: + +.. code:: shell + + unet> help + + Commands: + help :: this help + sh [hosts] <shell-command> :: execute <shell-command> on <host> + term [hosts] :: open shell terminals for hosts + vtysh [hosts] :: open vtysh terminals for hosts + [hosts] <vtysh-command> :: execute vtysh-command on hosts + +Here are some commands example: + +.. code:: shell + + unet> sh r1 ping 10.0.3.1 + PING 10.0.3.1 (10.0.3.1) 56(84) bytes of data. + 64 bytes from 10.0.3.1: icmp_seq=1 ttl=64 time=0.576 ms + 64 bytes from 10.0.3.1: icmp_seq=2 ttl=64 time=0.083 ms + 64 bytes from 10.0.3.1: icmp_seq=3 ttl=64 time=0.088 ms + ^C + --- 10.0.3.1 ping statistics --- + 3 packets transmitted, 3 received, 0% packet loss, time 1998ms + rtt min/avg/max/mdev = 0.083/0.249/0.576/0.231 ms + + unet> r1 show run + Building configuration... + + Current configuration: + ! + frr version 8.1-dev-my-manual-build + frr defaults traditional + hostname r1 + log file /tmp/topotests/ospf_topo1.test_ospf_topo1/r1/zebra.log + [...] + end + + unet> show daemons + ------ Host: r1 ------ + zebra ospfd ospf6d staticd + ------- End: r1 ------ + ------ Host: r2 ------ + zebra ospfd ospf6d staticd + ------- End: r2 ------ + ------ Host: r3 ------ + zebra ospfd ospf6d staticd + ------- End: r3 ------ + ------ Host: r4 ------ + zebra ospfd ospf6d staticd + ------- End: r4 ------ + +After you successfully configured your topology, you can obtain the +configuration files (per-daemon) using the following commands: + +.. code:: shell + + unet> sh r3 vtysh -d ospfd + + Hello, this is FRRouting (version 3.1-devrzalamena-build). + Copyright 1996-2005 Kunihiro Ishiguro, et al. + + r1# show running-config + Building configuration... + + Current configuration: + ! + frr version 3.1-devrzalamena-build + frr defaults traditional + no service integrated-vtysh-config + ! + log file ospfd.log + ! + router ospf + ospf router-id 10.0.255.3 + redistribute kernel + redistribute connected + redistribute static + network 10.0.3.0/24 area 0 + network 10.0.10.0/24 area 0 + network 172.16.0.0/24 area 1 + ! + line vty + ! + end + r1# + +You can also login to the node specified by nsenter using bash, etc. +A pid file for each node will be created in the relevant test dir. +You can run scripts inside the node, or use vtysh's <tab> or <?> feature. + +.. code:: shell + + [unet shell] + # cd tests/topotests/srv6_locator + # ./test_srv6_locator.py --topology-only + unet> r1 show segment-routing srv6 locator + Locator: + Name ID Prefix Status + -------------------- ------- ------------------------ ------- + loc1 1 2001:db8:1:1::/64 Up + loc2 2 2001:db8:2:2::/64 Up + + [Another shell] + # nsenter -a -t $(cat /tmp/topotests/srv6_locator.test_srv6_locator/r1.pid) bash --norc + # vtysh + r1# r1 show segment-routing srv6 locator + Locator: + Name ID Prefix Status + -------------------- ------- ------------------------ ------- + loc1 1 2001:db8:1:1::/64 Up + loc2 2 2001:db8:2:2::/64 Up + +Writing Tests +""""""""""""" + +Test topologies should always be bootstrapped from +:file:`tests/topotests/example_test/test_template.py` because it contains +important boilerplate code that can't be avoided, like: + +Example: + +.. code:: py + + # For all routers arrange for: + # - starting zebra using config file from <rtrname>/zebra.conf + # - starting ospfd using an empty config file. + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF) + + +- The topology definition or build function + +.. code:: py + + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3") + } + + def build_topo(tgen): + # topology build code + ... + +- pytest setup/teardown fixture to start the topology and supply ``tgen`` + argument to tests. + +.. code:: py + + + @pytest.fixture(scope="module") + def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + tgen = Topogen(topodef, module.__name__) + # or + tgen = Topogen(build_topo, module.__name__) + + ... + + # Start and configure the router daemons + tgen.start_router() + + # Provide tgen as argument to each test function + yield tgen + + # Teardown after last test runs + tgen.stop_topology() + + +Requirements: + +- Directory name for a new topotest must not contain hyphen (``-``) characters. + To separate words, use underscores (``_``). For example, ``tests/topotests/bgp_new_example``. +- Test code should always be declared inside functions that begin with the + ``test_`` prefix. Functions beginning with different prefixes will not be run + by pytest. +- Configuration files and long output commands should go into separated files + inside folders named after the equipment. +- Tests must be able to run without any interaction. To make sure your test + conforms with this, run it without the :option:`-s` parameter. +- Use `black <https://github.com/psf/black>`_ code formatter before creating + a pull request. This ensures we have a unified code style. +- Mark test modules with pytest markers depending on the daemons used during the + tests (see :ref:`topotests-markers`) +- Always use IPv4 :rfc:`5737` (``192.0.2.0/24``, ``198.51.100.0/24``, + ``203.0.113.0/24``) and IPv6 :rfc:`3849` (``2001:db8::/32``) ranges reserved + for documentation. + +Tips: + +- Keep results in stack variables, so people inspecting code with ``pdb`` can + easily print their values. + +Don't do this: + +.. code:: py + + assert foobar(router1, router2) + +Do this instead: + +.. code:: py + + result = foobar(router1, router2) + assert result + +- Use ``assert`` messages to indicate where the test failed. + +Example: + +.. code:: py + + for router in router_list: + # ... + assert condition, 'Router "{}" condition failed'.format(router.name) + +Debugging Execution +^^^^^^^^^^^^^^^^^^^ + +The most effective ways to inspect topology tests are: + +- Run pytest with ``--pdb`` option. This option will cause a pdb shell to + appear when an assertion fails + +Example: ``pytest -s --pdb ospf-topo1/test_ospf_topo1.py`` + +- Set a breakpoint in the test code with ``pdb`` + +Example: + +.. code:: py + + # Add the pdb import at the beginning of the file + import pdb + # ... + + # Add a breakpoint where you think the problem is + def test_bla(): + # ... + pdb.set_trace() + # ... + +The `Python Debugger <https://docs.python.org/2.7/library/pdb.html>`__ (pdb) +shell allows us to run many useful operations like: + +- Setting breaking point on file/function/conditions (e.g. ``break``, + ``condition``) +- Inspecting variables (e.g. ``p`` (print), ``pp`` (pretty print)) +- Running python code + +.. tip:: + + The TopoGear (equipment abstraction class) implements the ``__str__`` method + that allows the user to inspect equipment information. + +Example of pdb usage: + +.. code:: shell + + > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(121)test_ospf_convergence() + -> for rnum in range(1, 5): + (Pdb) help + Documented commands (type help <topic>): + ======================================== + EOF bt cont enable jump pp run unt + a c continue exit l q s until + alias cl d h list quit step up + args clear debug help n r tbreak w + b commands disable ignore next restart u whatis + break condition down j p return unalias where + + Miscellaneous help topics: + ========================== + exec pdb + + Undocumented commands: + ====================== + retval rv + + (Pdb) list + 116 title2="Expected output") + 117 + 118 def test_ospf_convergence(): + 119 "Test OSPF daemon convergence" + 120 pdb.set_trace() + 121 -> for rnum in range(1, 5): + 122 router = 'r{}'.format(rnum) + 123 + 124 # Load expected results from the command + 125 reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) + 126 expected = open(reffile).read() + (Pdb) step + > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(122)test_ospf_convergence() + -> router = 'r{}'.format(rnum) + (Pdb) step + > /media/sf_src/topotests/ospf-topo1/test_ospf_topo1.py(125)test_ospf_convergence() + -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) + (Pdb) print rnum + 1 + (Pdb) print router + r1 + (Pdb) tgen = get_topogen() + (Pdb) pp tgen.gears[router] + <lib.topogen.TopoRouter object at 0x7f74e06c9850> + (Pdb) pp str(tgen.gears[router]) + 'TopoGear<name="r1",links=["r1-eth0"<->"s1-eth0","r1-eth1"<->"s3-eth0"]> TopoRouter<>' + (Pdb) l 125 + 120 pdb.set_trace() + 121 for rnum in range(1, 5): + 122 router = 'r{}'.format(rnum) + 123 + 124 # Load expected results from the command + 125 -> reffile = os.path.join(CWD, '{}/ospfroute.txt'.format(router)) + 126 expected = open(reffile).read() + 127 + 128 # Run test function until we get an result. Wait at most 60 seconds. + 129 test_func = partial(compare_show_ip_ospf, router, expected) + 130 result, diff = topotest.run_and_expect(test_func, '', + (Pdb) router1 = tgen.gears[router] + (Pdb) router1.vtysh_cmd('show ip ospf route') + '============ OSPF network routing table ============\r\nN 10.0.1.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth0\r\nN 10.0.2.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.3, r1-eth1\r\nN 10.0.3.0/24 [10] area: 0.0.0.0\r\n directly attached to r1-eth1\r\nN 10.0.10.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.0.0/24 [20] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\nN IA 172.16.1.0/24 [30] area: 0.0.0.0\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF router routing table =============\r\nR 10.0.255.2 [10] area: 0.0.0.0, ASBR\r\n via 10.0.3.3, r1-eth1\r\nR 10.0.255.3 [10] area: 0.0.0.0, ABR, ASBR\r\n via 10.0.3.1, r1-eth1\r\nR 10.0.255.4 IA [20] area: 0.0.0.0, ASBR\r\n via 10.0.3.1, r1-eth1\r\n\r\n============ OSPF external routing table ===========\r\n\r\n\r\n' + (Pdb) tgen.cli() + unet> + +To enable more debug messages in other Topogen subsystems, more +logging messages can be displayed by modifying the test configuration file +``pytest.ini``: + +.. code:: ini + + [topogen] + # Change the default verbosity line from 'info'... + #verbosity = info + # ...to 'debug' + verbosity = debug + +Instructions for use, write or debug topologies can be found in :ref:`topotests-guidelines`. +To learn/remember common code snippets see :ref:`topotests-snippets`. + +Before creating a new topology, make sure that there isn't one already that +does what you need. If nothing is similar, then you may create a new topology, +preferably, using the newest template +(:file:`tests/topotests/example-test/test_template.py`). + +.. include:: topotests-markers.rst + +.. include:: topotests-snippets.rst + +License +------- + +All the configs and scripts are licensed under a ISC-style license. See Python +scripts for details. diff --git a/doc/developer/tracing.rst b/doc/developer/tracing.rst new file mode 100644 index 0000000..76f6004 --- /dev/null +++ b/doc/developer/tracing.rst @@ -0,0 +1,411 @@ +.. _tracing: + +Tracing +======= + +FRR has a small but growing number of static tracepoints available for use with +various tracing systems. These tracepoints can assist with debugging, +performance analysis and to help understand program flow. They can also be used +for monitoring. + +Developers are encouraged to write new static tracepoints where sensible. They +are not compiled in by default, and even when they are, they have no overhead +unless enabled by a tracer, so it is okay to be liberal with them. + + +Supported tracers +----------------- + +Presently two types of tracepoints are supported: + +- `LTTng tracepoints <https://lttng.org/>`_ +- `USDT probes <http://dtrace.org/guide/chp-usdt.html>`_ + +LTTng is a tracing framework for Linux only. It offers extremely low overhead +and very rich tracing capabilities. FRR supports LTTng-UST, which is the +userspace implementation. LTTng tracepoints are very rich in detail. No kernel +modules are needed. Besides only being available for Linux, the primary +downside of LTTng is the need to link to ``lttng-ust``. + +USDT probes originate from Solaris, where they were invented for use with +dtrace. They are a kernel feature. At least Linux and FreeBSD support them. No +library is needed; support is compiled in via a system header +(``<sys/sdt.h>``). USDT probes are much slower than LTTng tracepoints and offer +less flexibility in what information can be gleaned from them. + +LTTng is capable of tracing USDT probes but has limited support for them. +SystemTap and dtrace both work only with USDT probes. + + +Usage +----- + +To compile with tracepoints, use one of the following configure flags: + +.. program:: configure.ac + +.. option:: --enable-lttng=yes + + Generate LTTng tracepoints + +.. option:: --enable-usdt=yes + + Generate USDT probes + +To trace with LTTng, compile with either one (prefer :option:`--enable-lttng` +run the target in non-forking mode (no ``-d``) and use LTTng as usual (refer to +LTTng user manual). When using USDT probes with LTTng, follow the example in +`this article +<https://lttng.org/blog/2019/10/15/new-dynamic-user-space-tracing-in-lttng/>`_. +To trace with dtrace or SystemTap, compile with `--enable-usdt=yes` and +use your tracer as usual. + +To see available USDT probes:: + + readelf -n /usr/lib/frr/bgpd + +Example:: + + root@host ~> readelf -n /usr/lib/frr/bgpd + + Displaying notes found in: .note.ABI-tag + Owner Data size Description + GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) + OS: Linux, ABI: 3.2.0 + + Displaying notes found in: .note.gnu.build-id + Owner Data size Description + GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) + Build ID: 4f42933a69dcb42a519bc459b2105177c8adf55d + + Displaying notes found in: .note.stapsdt + Owner Data size Description + stapsdt 0x00000045 NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: packet_read + Location: 0x000000000045ee48, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-96(%rbp) 8@-104(%rbp) + stapsdt 0x00000047 NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: open_process + Location: 0x000000000047c43b, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-224(%rbp) 2@-226(%rbp) + stapsdt 0x00000049 NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: update_process + Location: 0x000000000047c4bf, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-208(%rbp) 2@-210(%rbp) + stapsdt 0x0000004f NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: notification_process + Location: 0x000000000047c557, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-192(%rbp) 2@-194(%rbp) + stapsdt 0x0000004c NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: keepalive_process + Location: 0x000000000047c5db, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-176(%rbp) 2@-178(%rbp) + stapsdt 0x0000004a NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: refresh_process + Location: 0x000000000047c673, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-160(%rbp) 2@-162(%rbp) + stapsdt 0x0000004d NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: capability_process + Location: 0x000000000047c6f7, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-144(%rbp) 2@-146(%rbp) + stapsdt 0x0000006f NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: output_filter + Location: 0x000000000048e33a, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-144(%rbp) 8@-152(%rbp) 4@-156(%rbp) 4@-160(%rbp) 8@-168(%rbp) + stapsdt 0x0000007d NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: process_update + Location: 0x0000000000491f10, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-800(%rbp) 8@-808(%rbp) 4@-812(%rbp) 4@-816(%rbp) 4@-820(%rbp) 8@-832(%rbp) + stapsdt 0x0000006e NT_STAPSDT (SystemTap probe descriptors) + Provider: frr_bgp + Name: input_filter + Location: 0x00000000004940ed, Base: 0x00000000005a09d2, Semaphore: 0x0000000000000000 + Arguments: 8@-144(%rbp) 8@-152(%rbp) 4@-156(%rbp) 4@-160(%rbp) 8@-168(%rbp) + + +To see available LTTng probes, run the target, create a session and then:: + + lttng list --userspace | grep frr + +Example:: + + root@host ~> lttng list --userspace | grep frr + PID: 11157 - Name: /usr/lib/frr/bgpd + frr_libfrr:route_node_get (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:list_sort (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:list_delete_node (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:list_remove (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:list_add (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:memfree (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:memalloc (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:frr_pthread_stop (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:frr_pthread_run (loglevel: TRACE_DEBUG_LINE (13)) (type: tracepoint) + frr_libfrr:thread_call (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:event_cancel_async (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:event_cancel (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:schedule_write (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:schedule_read (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:schedule_event (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:schedule_timer (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:hash_release (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:hash_insert (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_libfrr:hash_get (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:output_filter (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:input_filter (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:process_update (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:packet_read (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:refresh_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:capability_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:notification_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:update_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:keepalive_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + frr_bgp:open_process (loglevel: TRACE_INFO (6)) (type: tracepoint) + +When using LTTng, you can also get zlogs as trace events by enabling +the ``lttng_ust_tracelog:*`` event class. + +To see available SystemTap USDT probes, run:: + + stap -L 'process("/usr/lib/frr/bgpd").mark("*")' + +Example:: + + root@host ~> stap -L 'process("/usr/lib/frr/bgpd").mark("*")' + process("/usr/lib/frr/bgpd").mark("capability_process") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("input_filter") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long + process("/usr/lib/frr/bgpd").mark("keepalive_process") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("notification_process") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("open_process") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("output_filter") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long + process("/usr/lib/frr/bgpd").mark("packet_read") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("process_update") $arg1:long $arg2:long $arg3:long $arg4:long $arg5:long $arg6:long + process("/usr/lib/frr/bgpd").mark("refresh_process") $arg1:long $arg2:long + process("/usr/lib/frr/bgpd").mark("update_process") $arg1:long $arg2:long + +When using SystemTap, you can also easily attach to an existing function:: + + stap -L 'process("/usr/lib/frr/bgpd").function("bgp_update_receive")' + +Example:: + + root@host ~> stap -L 'process("/usr/lib/frr/bgpd").function("bgp_update_receive")' + process("/usr/lib/frr/bgpd").function("bgp_update_receive@bgpd/bgp_packet.c:1531") $peer:struct peer* $size:bgp_size_t $attr:struct attr $restart:_Bool $nlris:struct bgp_nlri[] $__func__:char const[] const + +Complete ``bgp.stp`` example using SystemTap to show BGP peer, prefix and aspath +using ``process_update`` USDT:: + + global pkt_size; + probe begin + { + ansi_clear_screen(); + println("Starting..."); + } + probe process("/usr/lib/frr/bgpd").function("bgp_update_receive") + { + pkt_size <<< $size; + } + probe process("/usr/lib/frr/bgpd").mark("process_update") + { + aspath = @cast($arg6, "attr")->aspath; + printf("> %s via %s (%s)\n", + user_string($arg2), + user_string(@cast($arg1, "peer")->host), + user_string(@cast(aspath, "aspath")->str)); + } + probe end + { + if (@count(pkt_size)) + print(@hist_linear(pkt_size, 0, 20, 2)); + } + +Output:: + + Starting... + > 192.168.0.0/24 via 192.168.0.1 (65534) + > 192.168.100.1/32 via 192.168.0.1 (65534) + > 172.16.16.1/32 via 192.168.0.1 (65534 65030) + ^Cvalue |-------------------------------------------------- count + 0 | 0 + 2 | 0 + 4 |@ 1 + 6 | 0 + 8 | 0 + ~ + 18 | 0 + 20 | 0 + >20 |@@@@@ 5 + + +Concepts +-------- + +Tracepoints are statically defined points in code where a developer has +determined that outside observers might gain something from knowing what is +going on at that point. It's like logging but with the ability to dump large +amounts of internal data with much higher performance. LTTng has a good summary +`here <https://lttng.org/docs/#doc-what-is-tracing>`_. + +Each tracepoint has a "provider" and name. The provider is basically a +namespace; for example, ``bgpd`` uses the provider name ``frr_bgp``. The name +is arbitrary, but because providers share a global namespace on the user's +system, all providers from FRR should be prefixed by ``frr_``. The tracepoint +name is just the name of the event. Events are globally named by their provider +and name. For example, the event when BGP reads a packet from a peer is +``frr_bgp:packet_read``. + +To do tracing, the tracing tool of choice is told which events to listen to. +For example, to listen to all events from FRR's BGP implementation, you would +enable the events ``frr_bgp:*``. In the same tracing session you could also +choose to record all memory allocations by enabling the ``malloc`` tracepoints +in ``libc`` as well as all kernel skb operations using the various in-kernel +tracepoints. This allows you to build as complete a view as desired of what the +system is doing during the tracing window (subject to what tracepoints are +available). + +Of particular use are the tracepoints for FRR's internal event scheduler; +tracing these allows you to see all events executed by all event loops for the +target(s) in question. Here's a couple events selected from a trace of BGP +during startup:: + + ... + + [18:41:35.750131763] (+0.000048901) host frr_libfrr:thread_call: { cpu_id = + 1 }, { threadmaster_name = "default", function_name = "zclient_connect", + scheduled_from = "lib/zclient.c", scheduled_on_line = 3877, thread_addr = + 0x0, file_descriptor = 0, event_value = 0, argument_ptr = 0xA37F70, timer = + 0 } + + [18:41:35.750175124] (+0.000020001) host frr_libfrr:thread_call: { cpu_id = + 1 }, { threadmaster_name = "default", function_name = "frr_config_read_in", + scheduled_from = "lib/libfrr.c", scheduled_on_line = 934, thread_addr = 0x0, + file_descriptor = 0, event_value = 0, argument_ptr = 0x0, timer = 0 } + + [18:41:35.753341264] (+0.000010532) host frr_libfrr:thread_call: { cpu_id = + 1 }, { threadmaster_name = "default", function_name = "bgp_event", + scheduled_from = "bgpd/bgpd.c", scheduled_on_line = 142, thread_addr = 0x0, + file_descriptor = 2, event_value = 2, argument_ptr = 0xE4D780, timer = 2 } + + [18:41:35.753404186] (+0.000004910) host frr_libfrr:thread_call: { cpu_id = + 1 }, { threadmaster_name = "default", function_name = "zclient_read", + scheduled_from = "lib/zclient.c", scheduled_on_line = 3891, thread_addr = + 0x0, file_descriptor = 40, event_value = 40, argument_ptr = 0xA37F70, timer + = 40 } + + ... + + +Very useful for getting a time-ordered look into what the process is doing. + + +Adding Tracepoints +------------------ + +Adding new tracepoints is a two step process: + +1. Define the tracepoint +2. Use the tracepoint + +Tracepoint definitions state the "provider" and name of the tracepoint, along +with any values it will produce, and how to format them. This is done with +macros provided by LTTng. USDT probes do not use definitions and are inserted +at the trace site with a single macro. However, to maintain support for both +platforms, you must define an LTTng tracepoint when adding a new one. +``frrtrace()`` will expand to the appropriate ``DTRACE_PROBEn`` macro when USDT +is in use. + +If you are adding new tracepoints to a daemon that has no tracepoints, that +daemon's ``subdir.am`` must be updated to conditionally link ``lttng-ust``. +Look at ``bgpd/subdir.am`` for an example of how to do this; grep for +``UST_LIBS``. Create new files named ``<daemon>_trace.[ch]``. Use +``bgpd/bgp_trace.[h]`` as boilerplate. If you are adding tracepoints to a +daemon that already has them, look for the ``<daemon>_trace.h`` file; +tracepoints are written here. + +Refer to the `LTTng developer docs +<https://lttng.org/docs/#doc-c-application>`_ for details on how to define +tracepoints. + +To use them, simply add a call to ``frrtrace()`` at the point you'd like the +event to be emitted, like so: + +.. code-block:: c + + ... + + switch (type) { + case BGP_MSG_OPEN: + frrtrace(2, frr_bgp, open_process, peer, size); /* tracepoint */ + atomic_fetch_add_explicit(&peer->open_in, 1, + memory_order_relaxed); + mprc = bgp_open_receive(peer, size); + + ... + +After recompiling this tracepoint will now be available, either as a USDT probe +or LTTng tracepoint, depending on your compilation choice. + + +trace.h +^^^^^^^ + +Because FRR supports multiple types of tracepoints, the code for creating them +abstracts away the underlying system being used. This abstraction code is in +``lib/trace.h``. There are 2 function-like macros that are used for working +with tracepoints. + +- ``frrtrace()`` defines tracepoints +- ``frrtrace_enabled()`` checks whether a tracepoint is enabled + +There is also ``frrtracelog()``, which is used in zlog core code to make zlog +messages available as trace events to LTTng. This should not be used elsewhere. + +There is additional documentation in the header. The key thing to note is that +you should never include ``trace.h`` in source where you plan to put +tracepoints; include the tracepoint definition header instead (e.g. +:file:`bgp_trace.h`). + + +Limitations +----------- + +Tracers do not like ``fork()`` or ``dlopen()``. LTTng has some workarounds for +this involving interceptor libraries using ``LD_PRELOAD``. + +If you're running FRR in a typical daemonizing way (``-d`` to the daemons) +you'll need to run the daemons like so: + +.. code-block:: shell + + LD_PRELOAD=liblttng-ust-fork.so <daemon> + + +If you're using systemd this you can accomplish this for all daemons by +modifying ``frr.service`` like so: + +.. code-block:: diff + + --- a/frr.service + +++ b/frr.service + @@ -7,6 +7,7 @@ Before=network.target + OnFailure=heartbeat-failed@%n + + [Service] + +Environment="LD_PRELOAD=liblttng-ust-fork.so" + Nice=-5 + Type=forking + NotifyAccess=all + + +USDT tracepoints are relatively high overhead and probably shouldn't be used +for "flight recorder" functionality, i.e. enabling and passively recording all +events for monitoring purposes. It's generally okay to use LTTng like this, +though. diff --git a/doc/developer/vtysh.rst b/doc/developer/vtysh.rst new file mode 100644 index 0000000..323ea57 --- /dev/null +++ b/doc/developer/vtysh.rst @@ -0,0 +1,212 @@ +.. _vtysh: + +***** +VTYSH +***** + +.. seealso:: :ref:`command-line-interface` + +.. _vtysh-architecture: + +Architecture +============ + +VTYSH is a shell for FRR daemons. It amalgamates all the CLI commands defined +in each of the daemons and presents them to the user in a single shell, which +saves the user from having to telnet to each of the daemons and use their +individual shells. The amalgamation is achieved by +:ref:`extracting <vtysh-command-extraction>` commands from daemons and +injecting them into VTYSH at build time. + +At runtime, VTYSH maintains an instance of a CLI mode tree just like each +daemon. However, the mode tree in VTYSH contains (almost) all commands from +every daemon in the same tree, whereas individual daemons have trees that only +contain commands relevant to themselves. VTYSH also uses the library CLI +facilities to maintain the user's current position in the tree (the current +node). Note that this position must be synchronized with all daemons; if a +daemon receives a command that causes it to change its current node, VTYSH must +also change its node. Since the extraction script does not understand the +handler code of commands, but only their definitions, this and other behaviors +must be manually programmed into VTYSH for every case where the internal state +of VTYSH must change in response to a command. Details on how this is done are +discussed in the :ref:`vtysh-special-defuns` section. + +VTYSH also handles writing and applying the integrated configuration file, +:file:`/etc/frr/frr.conf`. Since it has knowledge of the entire command space +of FRR, it can intelligently distribute configuration commands only to the +daemons that understand them. Similarly, when writing the configuration file it +takes care of combining multiple instances of configuration blocks and +simplifying the output. This is discussed in :ref:`vtysh-configuration`. + +.. _vtysh-command-extraction: + +Command Extraction +------------------ + +To build ``vtysh``, the :file:`python/xref2vtysh.py` script scans through the +:file:`frr.xref` file created earlier in the build process. This file contains +a list of all ``DEFUN`` and ``install_element`` sites in the code, generated +directly from the binaries (and therefore matching exactly what is really +available.) + +This list is collated and transformed into ``DEFSH`` (and ``install_element``) +statements, output to ``vtysh_cmd.c``. Each ``DEFSH`` +contains the name of the command plus ``_vtysh``, as well as a flag that +indicates which daemons the command was found in. When the command is executed +in VTYSH, this flag is inspected to determine which daemons to send the command +to. This way, commands are only sent to the daemons that know about them, +avoiding spurious errors from daemons that don't have the command defined. + +The extraction script contains lots of hardcoded knowledge about what sources +to look at and what flags to use for certain commands. + +.. note:: + + The ``vtysh_scan`` Makefile variable and ``#ifndef VTYSH_EXTRACT_PL`` + checks in source files are no longer used. Remove them when rebasing older + changes. + +.. _vtysh-special-defuns: + +Special DEFUNs +-------------- + +In addition to the vanilla ``DEFUN`` macro for defining CLI commands, there are +several VTYSH-specific ``DEFUN`` variants that each serve different purposes. + +``DEFSH`` + Used almost exclusively by generated VTYSH code. This macro defines a + ``cmd_element`` with no handler function; the command, when executed, is + simply forwarded to the daemons indicated in the daemon flag. + +``DEFUN_NOSH`` + Used by daemons. Has the same expansion as a ``DEFUN``, but ``xref2vtysh.py`` + will skip these definitions when extracting commands. This is typically used + when VTYSH must take some special action upon receiving the command, and the + programmer therefore needs to write VTYSH's copy of the command manually + instead of using the generated version. + +``DEFUNSH`` + The same as ``DEFUN``, but with an argument that allows specifying the + ``->daemon`` field of the generated ``cmd_element``. This is used by VTYSH + to determine which daemons to send the command to. + +``DEFUNSH_ATTR`` + A version of ``DEFUNSH`` that allows setting the ``->attr`` field of the + generated ``cmd_element``. Not used in practice. + +.. _vtysh-configuration: + +Configuration Management +------------------------ + +When integrated configuration is used, VTYSH manages writing, reading and +applying the FRR configuration file. VTYSH can be made to read and apply an +integrated configuration to all running daemons by launching it with ``-f +<file>``. It sends the appropriate configuration lines to the relevant daemons +in the same way that commands entered by the user on VTYSH's shell prompt are +processed. + +Configuration writing is more complicated. VTYSH makes a best-effort attempt to +combine and simplify the configuration as much as possible. A working example +is best to explain this behavior. + +Example +^^^^^^^ + +Suppose we have just *staticd* and *zebra* running on the system, and use VTYSH +to apply the following configuration snippet: + +.. code-block:: frr + + ! + vrf blue + ip protocol static route-map ExampleRoutemap + ip route 192.168.0.0/24 192.168.0.1 + exit-vrf + ! + +Note that *staticd* defines static route commands and *zebra* defines ``ip +protocol`` commands. Therefore if we ask only *zebra* for its configuration, we +get the following:: + + (config)# do sh running-config zebra + Building configuration... + + ... + ! + vrf blue + ip protocol static route-map ExampleRoutemap + exit-vrf + ! + ... + +Note that the static route doesn't show up there. Similarly, if we ask +*staticd* for its configuration, we get:: + + (config)# do sh running-config staticd + + ... + ! + vrf blue + ip route 192.168.0.0/24 192.168.0.1 + exit-vrf + ! + ... + +But when we display the configuration with VTYSH, we see:: + + ubuntu-bionic(config)# do sh running-config + + ... + ! + vrf blue + ip protocol static route-map ExampleRoutemap + ip route 192.168.0.0/24 192.168.0.1 + exit-vrf + ! + ... + +This is because VTYSH asks each daemon for its currently running configuration, +and combines equivalent blocks together. In the above example, it combined the +``vrf blue`` blocks from both *zebra* and *staticd* together into one. This is +done in :file:`vtysh_config.c`. + +Protocol +======== + +VTYSH communicates with FRR daemons by way of domain socket. Each daemon +creates its own socket, typically in :file:`/var/run/frr/<daemon>.vty`. The +protocol is very simple. In the VTYSH to daemon direction, messages are simply +NUL-terminated strings, whose content are CLI commands. Here is a typical +message from VTYSH to a daemon: + +:: + + Request + + 00000000: 646f 2077 7269 7465 2074 6572 6d69 6e61 do write termina + 00000010: 6c0a 00 l.. + + +The response format has some more data in it. First is a NUL-terminated string +containing the plaintext response, which is just the output of the command that +was sent in the request. This is displayed to the user. The plaintext response +is followed by 3 null marker bytes, followed by a 1-byte status code that +indicates whether the command was successful or not. + +:: + + Response + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Plaintext Response | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Marker (0x00) | Status Code | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +The first ``0x00`` byte in the marker also serves to terminate the plaintext +response. diff --git a/doc/developer/workflow.rst b/doc/developer/workflow.rst new file mode 100644 index 0000000..f720f62 --- /dev/null +++ b/doc/developer/workflow.rst @@ -0,0 +1,1740 @@ +.. _process-and-workflow: + +******************* +Process & Workflow +******************* + +.. highlight:: none + +FRR is a large project developed by many different groups. This section +documents standards for code style & quality, commit messages, pull requests +and best practices that all contributors are asked to follow. + +This chapter is "descriptive/post-factual" in that it documents pratices that +are in use; it is not "definitive/pre-factual" in prescribing practices. This +means that when a procedure changes, it is agreed upon, then put into practice, +and then documented here. If this document doesn't match reality, it's the +document that needs to be updated, not reality. + +Mailing Lists +============= + +The FRR development group maintains multiple mailing lists for use by the +community. Italicized lists are private. + ++----------------------------------+--------------------------------+ +| Topic | List | ++==================================+================================+ +| Development | dev@lists.frrouting.org | ++----------------------------------+--------------------------------+ +| Users & Operators | frog@lists.frrouting.org | ++----------------------------------+--------------------------------+ +| Announcements | announce@lists.frrouting.org | ++----------------------------------+--------------------------------+ +| *Security* | security@lists.frrouting.org | ++----------------------------------+--------------------------------+ +| *Technical Steering Committee* | tsc@lists.frrouting.org | ++----------------------------------+--------------------------------+ + +The Development list is used to discuss and document general issues related to +project development and governance. The public +`Slack instance <https://frrouting.slack.com>`_ and weekly technical meetings +provide a higher bandwidth channel for discussions. The results of such +discussions must be reflected in updates, as appropriate, to code (i.e., +merges), `GitHub issues`_, and for governance or process changes, updates to +the Development list and either this file or information posted at +https://frrouting.org/. + +Development & Release Cycle +=========================== + +Development +----------- + +.. figure:: ../figures/git_branches.png + :align: center + :scale: 55% + :alt: Merging Git branches into a central trunk + + Rough outline of FRR development workflow + +The master Git for FRR resides on `GitHub`_. + +There is one main branch for development, ``master``. For each major release +(2.0, 3.0 etc) a new release branch is created based on the master. Significant +bugfixes should be backported to upcoming and existing release branches no more +than 1 year old. As a general rule new features are not backported to release +branches. + +Subsequent point releases based on a major branch are handled with git tags. + +Releases +-------- +FRR employs a ``<MAJOR>.<MINOR>.<BUGFIX>`` versioning scheme. + +``MAJOR`` + Significant new features or multiple minor features. This should mostly + cover any kind of disruptive change that is visible or "risky" to operators. + New features or protocols do not necessarily trigger this. (This was changed + for FRR 7.x after feedback from users that the pace of major version number + increments was too high.) + +``MINOR`` + General incremental development releases, excluding "major" changes + mentioned above. Not necessarily fully backwards compatible, as smaller + (but still visible) changes or deprecated feature removals may still happen. + However, there shouldn't be any huge "surprises" between minor releases. + +``BUGFIX`` + Fixes for actual bugs and/or security issues. Fully compatible. + +Releases are scheduled in a 4-month cycle on the first Tuesday each +March/July/November. Walking backwards from this date: + + - 6 weeks earlier, ``master`` is frozen for new features, and feature PRs + are considered lowest priority (regardless of when they were opened.) + + - 4 weeks earlier, the stable branch separates from master (named + ``dev/MAJOR.MINOR`` at this point) and tagged as ``base_X.Y``. + Master is unfrozen and new features may again proceed. + + Part of unfreezing master is editing the ``AC_INIT`` statement in + :file:`configure.ac` to reflect the new development version that master + now refers to. This is accompanied by a ``frr-X.Y-dev`` tag on master, + which should always be on the first commit on master *after* the stable + branch was forked (even if that is not the edit to ``AC_INIT``; it's more + important to have it on the very first commit on master after the fork.) + + (The :file:`configure.ac` edit and tag push are considered git housekeeping + and are pushed directly to ``master``, not through a PR.) + + Below is the snippet of the commands to use in this step. + + .. code-block:: console + + % git remote --verbose + upstream git@github.com:frrouting/frr (fetch) + upstream git@github.com:frrouting/frr (push) + + % git checkout master + % git pull upstream master + % git checkout -b dev/8.2 + % git tag base_8.2 + % git push upstream base_8.2 + % git push upstream dev/8.2 + % git checkout master + % sed -i 's/8.2-dev/8.3-dev/' configure.ac + % git add configure.ac + % git commit -s -m "build: FRR 8.3 development version" + % git tag -a frr-8.3-dev -m "frr-8.3-dev" + % git push upstream master + % git push upstream frr-8.3-dev + + In this step, we also have to update package versions to reflect + the development version. Versions need to be updated using + a standard way of development (Pull Requests) based on master branch. + + Only change the version number with no other changes. This will produce + packages with the a version number that is higher than any previous + version. Once the release is done, whatever updates we make to changelog + files on the release branch need to be cherry-picked to the master branch. + + Update essential dates in advance for reference table (below) when + the next freeze, dev/X.Y, RC, and release phases are scheduled. This should + go in the ``master`` branch. + + - 2 weeks earlier, a ``frr-X.Y-rc`` release candidate is tagged. + + .. code-block:: console + + % git remote --verbose + upstream git@github.com:frrouting/frr (fetch) + upstream git@github.com:frrouting/frr (push) + + % git checkout dev/8.2 + % git tag frr-8.2-rc + % git push upstream frr-8.2-rc + + - on release date, the branch is renamed to ``stable/MAJOR.MINOR``. + +The 2 week window between each of these events should be used to run any and +all testing possible for the release in progress. However, the current +intention is to stick to the schedule even if known issues remain. This would +hopefully occur only after all avenues of fixing issues are exhausted, but to +achieve this, an as exhaustive as possible list of issues needs to be available +as early as possible, i.e. the first 2-week window. + +For reference, the expected release schedule according to the above is: + ++---------+------------+------------+------------+ +| Release | 2024-03-12 | 2024-07-02 | 2024-11-05 | ++---------+------------+------------+------------+ +| RC | 2024-02-27 | 2024-06-18 | 2024-10-22 | ++---------+------------+------------+------------+ +| dev/X.Y | 2024-02-13 | 2024-06-04 | 2024-10-08 | ++---------+------------+------------+------------+ +| freeze | 2024-01-30 | 2024-05-21 | 2024-09-24 | ++---------+------------+------------+------------+ + +Here is the hint on how to get the dates easily: + + .. code-block:: console + + ~$ # Release date is 2023-11-07 (First Tuesday each March/July/November) + ~$ date +%F --date='2023-11-07 -42 days' # Next freeze date + 2023-09-26 + ~$ date +%F --date='2023-11-07 -28 days' # Next dev/X.Y date + 2023-10-10 + ~$ date +%F --date='2023-11-07 -14 days' # Next RC date + 2023-10-24 + +Each release is managed by one or more volunteer release managers from the FRR +community. These release managers are expected to handle the branch for a period +of one year. To spread and distribute this workload, this should be rotated for +subsequent releases. The release managers are currently assumed/expected to +run a release management meeting during the weeks listed above. Barring other +constraints, this would be scheduled before the regular weekly FRR community +call such that important items can be carried over into that call. + +Bugfixes are applied to the two most recent releases. It is expected that +each bugfix backported should include some reasoning for its inclusion +as well as receiving approval by the release managers for that release before +accepted into the release branch. This does not necessarily preclude backporting of +bug fixes to older than the two most recent releases. + +Security fixes are backported to all releases less than or equal to at least one +year old. Security fixes may also be backported to older releases depending on +severity. + +For detailed instructions on how to produce an FRR release, refer to +:ref:`frr-release-procedure`. + + +Long term support branches ( LTS ) +----------------------------------------- + +This kind of branch is not yet officially supported, and need experimentation +before being effective. + +Previous definition of releases prevents long term support of previous releases. +For instance, bug and security fixes are not applied if the stable branch is too +old. + +Because the FRR users have a need to backport bug and security fixes after the +stable branch becomes too old, there is a need to provide support on a long term +basis on that stable branch. If that support is applied on that stable branch, +then that branch is a long term support branch. + +Having a LTS branch requires extra-work and requires one person to be in charge +of that maintenance branch for a certain amount of time. The amount of time will +be by default set to 4 months, and can be increased. 4 months stands for the time +between two releases, this time can be applied to the decision to continue with a +LTS release or not. In all cases, that time period will be well-defined and +published. Also, a self nomination from a person that proposes to handle the LTS +branch is required. The work can be shared by multiple people. In all cases, there +must be at least one person that is in charge of the maintenance branch. The person +on people responsible for a maintenance branch must be a FRR maintainer. Note that +they may choose to abandon support for the maintenance branch at any time. If +no one takes over the responsibility of the LTS branch, then the support will be +discontinued. + +The LTS branch duties are the following ones: + +- organise meetings on a (bi-)weekly or monthly basis, the handling of issues + and pull requested relative to that branch. When time permits, this may be done + during the regularly scheduled FRR meeting. + +- ensure the stability of the branch, by using and eventually adapting the + checking the CI tools of FRR ( indeed, maintaining may lead to create + maintenance branches for topotests or for CI). + +It will not be possible to backport feature requests to LTS branches. Actually, it +is a false good idea to use LTS for that need. Introducing feature requests may +break the paradigm where all more recent releases should also include the feature +request. This would require the LTS maintainer to ensure that all more recent +releases have support for this feature request. Moreover, introducing features +requests may result in breaking the stability of the branch. LTS branches are first +done to bring long term support for stability. + +Development Branches +-------------------- + +Occassionally the community will desire the ability to work together +on a feature that is considered useful to FRR. In this case the +parties may ask the Maintainers for the creation of a development +branch in the main FRR repository. Requirements for this to happen +are: + +- A one paragraph description of the feature being implemented to + allow for the facilitation of discussion about the feature. This + might include pointers to relevant RFC's or presentations that + explain what is planned. This is intended to set a somewhat + low bar for organization. +- A branch maintainer must be named. This person is responsible for + keeping the branch up to date, and general communication about the + project with the other FRR Maintainers. Additionally this person + must already be a FRR Maintainer. +- Commits to this branch must follow the normal PR and commit process + as outlined in other areas of this document. The goal of this is + to prevent the current state where large features are submitted + and are so large they are difficult to review. + +After a development branch has completed the work together, a final +review can be made and the branch merged into master. If a development +branch is becomes un-maintained or not being actively worked on after +three months then the Maintainers can decide to remove the branch. + +Debian Branches +--------------- + +The Debian project contains "official" packages for FRR. While FRR +Maintainers may participate in creating these, it is entirely the Debian +project's decision what to ship and how to work on this. + +As a courtesy and for FRR's benefit, this packaging work is currently visible +in git branches named ``debian/*`` on the main FRR git repository. These +branches are for the exclusive use by people involved in Debian packaging work +for FRR. Direct commit access may be handed out and FRR git rules (review, +testing, etc.) do not apply. Do not push to these branches without talking +to the people noted under ``Maintainer:`` and ``Uploaders:`` in +``debian/control`` on the target branch -- even if you are a FRR Maintainer. + +Changelog +--------- +The changelog will be the base for the release notes. A changelog entry for +your changes is usually not required and will be added based on your commit +messages by the maintainers. However, you are free to include an update to the +changelog with some better description. + +Accords: non-code community consensus +===================================== + +The FRR repository has a place for "accords" - these are items of +consideration for FRR that influence how we work as a community, but either +haven't resulted in code *yet*, or may *never* result in code being written. +They are placed in the ``doc/accords/`` directory. + +The general idea is to simply pass small blurbs of text through our normal PR +procedures, giving them the same visibility, comment and review mechanisms as +code PRs - and changing them later is another PR. Please refer to the README +file in ``doc/accords/`` for further details. The file names of items in that +directory are hopefully helpful in determining whether some of them might be +relevant to your work. + +Submitting Patches and Enhancements +=================================== + +FRR accepts patches using GitHub pull requests. + +The base branch for new contributions and non-critical bug fixes should be +``master``. Please ensure your pull request is based on this branch when you +submit it. + +Code submitted by pull request will be automatically tested by one or more CI +systems. Once the automated tests succeed, other developers will review your +code for quality and correctness. After any concerns are resolved, your code +will be merged into the branch it was submitted against. + +The title of the pull request should provide a high level technical +summary of the included patches. The description should provide +additional details that will help the reviewer to understand the context +of the included patches. + +Squash commits +-------------- + +Before merging make sure a PR has squashed the following kinds of commits: + +- Fixes/review feedback +- Typos +- Merges and rebases +- Work in progress + +This helps to automatically generate human-readable changelog messages. + +Commit Guidelines +----------------- + +There is a built-in commit linter. Basic rules: + +- Commit messages must be prefixed with the name of the changed subsystem, followed + by a colon and a space and start with an imperative verb. + + `Check <https://github.com/FRRouting/frr/tree/master/.github/commitlint.config.js>`_ all + the supported subsystems. + +- Commit messages must not end with a period ``.`` + +Why was my pull request closed? +------------------------------- + +Pull requests older than 180 days will be closed. Exceptions can be made for +pull requests that have active review comments, or that are awaiting other +dependent pull requests. Closed pull requests are easy to recreate, and little +work is lost by closing a pull request that subsequently needs to be reopened. + +We want to limit the total number of pull requests in flight to: + +- Maintain a clean project +- Remove old pull requests that would be difficult to rebase as the underlying code has changed over time +- Encourage code velocity + +.. _license-for-contributions: + +License for Contributions +------------------------- +FRR is under a “GPLv2 or later” license. Any code submitted must be released +under the same license (preferred) or any license which allows redistribution +under this GPLv2 license (eg MIT License). +It is forbidden to push any code that prevents from using GPLv3 license. This +becomes a community rule, as FRR produces binaries that links with Apache 2.0 +libraries. Apache 2.0 and GPLv2 license are incompatible, if put together. +Please see `<http://www.apache.org/licenses/GPL-compatibility.html>`_ for +more information. This rule guarantees the user to distribute FRR binary code +without any licensing issues. + +Pre-submission Checklist +------------------------ +- Format code (see `Code Formatting <#code-formatting>`__) +- Verify and acknowledge license (see :ref:`license-for-contributions`) +- Ensure you have properly signed off (see :ref:`signing-off`) +- Test building with various configurations: + + - ``buildtest.sh`` + +- Verify building source distribution: + + - ``make dist`` (and try rebuilding from the resulting tar file) + +- Run unit tests: + + - ``make test`` + +- In the case of a major new feature or other significant change, document + plans for continued maintenance of the feature. In addition it is a + requirement that automated testing must be written that exercises + the new feature within our existing CI infrastructure. Also the + addition of automated testing to cover any pull request is encouraged. + +- All new code must use the current latest version of acceptable code. + + - If a daemon is converted to YANG, then new code must use YANG. + - DEFPY's must be used for new cli + - Typesafe lists must be used + - printf formatting changes must be used + +.. _signing-off: + +Signing Off +----------- +Code submitted to FRR must be signed off. We have the same requirements for +using the signed-off-by process as the Linux kernel. In short, you must include +a ``Signed-off-by`` tag in every patch. + +An easy way to do this is to use ``git commit -s`` where ``-s`` will automatically +append a signed-off line to the end of your commit message. Also, if you commit +and forgot to add the line you can use ``git commit --amend -s`` to add the +signed-off line to the last commit. + +``Signed-off-by`` is a developer's certification that they have the right to +submit the patch for inclusion into the project. It is an agreement to the +:ref:`Developer's Certificate of Origin <developers-certificate-of-origin>`. +Code without a proper ``Signed-off-by`` line cannot and will not be merged. + +If you are unfamiliar with this process, you should read the +`official policy at kernel.org <https://www.kernel.org/doc/html/latest/process/submitting-patches.html>`_. +You might also find +`this article <http://www.linuxfoundation.org/content/how-participate-linux-community-0>`_ +about participating in the Linux community on the Linux Foundation website to +be a helpful resource. + +.. _developers-certificate-of-origin: + +In short, when you sign off on a commit, you assert your agreement to all of +the following:: + + Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part by + me, under the same open source license (unless I am permitted to + submit under a different license), as indicated in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +After Submitting Your Changes +----------------------------- + +- Watch for Continuous Integration (CI) test results + + - You should automatically receive an email with the test results + within less than 2 hrs of the submission. If you don’t get the + email, then check status on the GitHub pull request. + - Please notify the development mailing list if you think something + doesn't work. + +- If the tests failed: + + - In general, expect the community to ignore the submission until + the tests pass. + - It is up to you to fix and resubmit. + + - This includes fixing existing unit (“make test”) tests if your + changes broke or changed them. + - It also includes fixing distribution packages for the failing + platforms (ie if new libraries are required). + - Feel free to ask for help on the development list. + + - Go back to the submission process and repeat until the tests pass. + +- If the tests pass: + + - Wait for reviewers. Someone will review your code or be assigned + to review your code. + - Respond to any comments or concerns the reviewer has. Use e-mail or + add a comment via github to respond or to let the reviewer know how + their comment or concern is addressed. + - An author must never delete or manually dismiss someone else's comments + or review. (A review may be overridden by agreement in the weekly + technical meeting.) + - When you have addressed someone's review comments, please click the + "re-request review" button (in the top-right corner of the PR page, next + to the reviewer's name, an icon that looks like "reload") + - The responsibility for keeping a PR moving rests with the author at + least as long as there are either negative CI results or negative review + comments. If you forget to mark a review comment as addressed (by + clicking re-request review), the reviewer may very well not notice and + won't come back to your PR. + - Automatically generated comments, e.g., those generated by CI systems, + may be deleted by authors and others when such comments are not the most + recent results from that automated comment source. + - After all comments and concerns are addressed, expect your patch + to be merged. + +- Watch out for questions on the mailing list. At this time there will + be a manual code review and further (longer) tests by various + community members. +- Your submission is done once it is merged to the master branch. + +Programming Languages, Tools and Libraries +========================================== + +The core of FRR is written in C (gcc or clang supported) and makes +use of GNU compiler extensions. Additionally, the CLI generation +tool, `clippy`, requires Python. A few other non-essential scripts are +implemented in Perl and Python. FRR requires the following tools +to build distribution packages: automake, autoconf, texinfo, libtool and +gawk and various libraries (i.e. libpam and libjson-c). + +If your contribution requires a new library or other tool, then please +highlight this in your description of the change. Also make sure it’s +supported by all FRR platform OSes or provide a way to build +without the library (potentially without the new feature) on the other +platforms. + +Documentation should be written in reStructuredText. Sphinx extensions may be +utilized but pure ReST is preferred where possible. See +:ref:`documentation`. + +Use of C++ +---------- + +While C++ is not accepted for core components of FRR, extensions, modules or +other distinct components may want to use C++ and include FRR header files. +There is no requirement on contributors to work to retain C++ compatibility, +but fixes for C++ compatibility are welcome. + +This implies that the burden of work to keep C++ compatibility is placed with +the people who need it, and they may provide it at their leisure to the extent +it is useful to them. So, if only a subset of header files, or even parts of +a header file are made available to C++, this is perfectly fine. + +Code Reviews +============ + +Code quality is paramount for any large program. Consequently we require +reviews of all submitted patches by at least one person other than the +submitter before the patch is merged. + +Because of the nature of the software, FRR's maintainer list (i.e. those with +commit permissions) tends to contain employees / members of various +organizations. In order to prevent conflicts of interest, we use an honor +system in which submissions from an individual representing one company should +be merged by someone unaffiliated with that company. + +Guidelines for code review +-------------------------- + +- As a rule of thumb, the depth of the review should be proportional to the + scope and / or impact of the patch. + +- Anyone may review a patch. + +- When using GitHub reviews, marking "Approve" on a code review indicates + willingness to merge the PR. + +- For individuals with merge rights, marking "Changes requested" is equivalent + to a NAK. + +- For a PR you marked with "Changes requested", please respond to updates in a + timely manner to avoid impeding the flow of development. + +- Rejected or obsolete PRs are generally closed by the submitter based + on requests and/or agreement captured in a PR comment. The comment + may originate with a reviewer or document agreement reached on Slack, + the Development mailing list, or the weekly technical meeting. + +- Reviewers may ask for new automated testing if they feel that the + code change is large enough/significant enough to warrant such + a requirement. + +For project members with merge permissions, the following patterns have +emerged: + +- a PR with any reviews requesting changes may not be merged. + +- a PR with any negative CI result may not be merged. + +- an open "yellow" review mark ("review requested, but not done") should be + given some time (a few days up to weeks, depending on the size of the PR), + but is not a merge blocker. + +- a "textbubble" review mark ("review comments, but not positive/negative") + should be read through but is not a merge blocker. + +- non-trivial PRs are generally given some time (again depending on the size) + for people to mark an interest in reviewing. Trivial PRs may be merged + immediately when CI is green. + + +Coding Practices & Style +======================== + +Commit messages +--------------- + +Commit messages should be formatted in the same way as Linux kernel +commit messages. The format is roughly:: + + dir: short summary + + extended summary + +``dir`` should be the top level source directory under which the change was +made. For example, a change in :file:`bgpd/rfapi` would be formatted as:: + + bgpd: short summary + + ... + +The first line should be no longer than 50 characters. Subsequent lines should +be wrapped to 72 characters. + +The purpose of commit messages is to briefly summarize what the commit is +changing. Therefore, the extended summary portion should be in the form of an +English paragraph. Brief examples of program output are acceptable but if +present should be short (on the order of 10 lines) and clearly demonstrate what +has changed. The goal should be that someone with only passing familiarity with +the code in question can understand what is being changed. + +Commit messages consisting entirely of program output are *unacceptable*. These +do not describe the behavior changed. For example, putting VTYSH output or the +result of test runs as the sole content of commit messages is unacceptable. + +You must also sign off on your commit. + +.. seealso:: :ref:`signing-off` + + +Source File Header +------------------ + +New files must have a copyright header (see :ref:`license-for-contributions` +above) added to the file. The header should be: + +.. code-block:: c + + // SPDX-License-Identifier: GPL-2.0-or-later + /* + * Title/Function of file + * Copyright (C) YEAR Author’s Name + */ + + #include <zebra.h> + +A ``SPDX-License-Identifier`` header is required in all source files, i.e. +``.c``, ``.h``, ``.cpp`` and ``.py`` files. The license boilerplate should be +removed in these files. Some existing files are missing this header, this is +slowly being fixed. + +A ``SPDX-License-Identifier`` header *and* the full license boilerplate is +required in schema definition files, i.e. ``.yang`` and ``.proto``. The +rationale for this is that these files are likely to be individually copied to +places outside FRR, and having only the SPDX header would become a "dangling +pointer". + +.. warning:: + + **DO NOT REMOVE A "Copyright" LINE OR AUTHOR NAME, EVER.** + + **DO NOT APPLY AN SPDX HEADER WHEN THE LICENSE IS UNCLEAR, UNLESS YOU HAVE + CHECKED WITH *ALL* SIGNIFICANT AUTHORS.** + +Please to keep ``#include <zebra.h>``. The absolute first header included in +any C file **must** be either ``zebra.h`` or ``config.h`` (with HAVE_CONFIG_H +guard.) + + +Adding Copyright Claims to Existing Files +----------------------------------------- + +When adding copyright claims for modifications to an existing file, please +add a ``Portions:`` section as shown below. If this section already exists, add +your new claim at the end of the list. + +.. code-block:: c + + /* + * Title/Function of file + * Copyright (C) YEAR Author’s Name + * Portions: + * Copyright (C) 2010 Entity A .... + * Copyright (C) 2016 Your name [optional brief change description] + * ... + */ + +Defensive coding requirements +----------------------------- + +In general, code submitted into FRR will be rejected if it uses unsafe +programming practices. While there is no enforced overall ruleset, the +following requirements have achieved consensus: + +- ``strcpy``, ``strcat`` and ``sprintf`` are unacceptable without exception. + Use ``strlcpy``, ``strlcat`` and ``snprintf`` instead. (Rationale: even if + you know the operation cannot overflow the buffer, a future code change may + inadvertedly introduce an overflow.) + +- buffer size arguments, particularly to ``strlcpy`` and ``snprintf``, must + use ``sizeof()`` whereever possible. Particularly, do not use a size + constant in these cases. (Rationale: changing a buffer to another size + constant may leave the write operations on a now-incorrect size limit.) + +- For stack allocated structs and arrays that should be zero initialized, + prefer initializer expressions over ``memset()`` wherever possible. This + helps prevent ``memset()`` calls being missed in branches, and eliminates the + error class of an incorrect ``size`` argument to ``memset()``. + + For example, instead of: + + .. code-block:: c + + struct foo mystruct; + ... + memset(&mystruct, 0x00, sizeof(struct foo)); + + Prefer: + + .. code-block:: c + + struct foo mystruct = {}; + +- Do not zero initialize stack allocated values that must be initialized with a + nonzero value in order to be used. This way the compiler and memory checking + tools can catch uninitialized value use that would otherwise be suppressed by + the (incorrect) zero initialization. + +- Usage of ``system()`` or other c library routines that cause signals to + possibly be ignored are not allowed. This includes the ``fork()`` and + ``execXX`` call patterns, which is actually what system() does underneath + the covers. This pattern causes the system shutdown to never work properly + as the SIGINT sent is never received. It is better to just prohibit code + that does this instead of having to debug shutdown issues again. + +Other than these specific rules, coding practices from the Linux kernel as +well as CERT or MISRA C guidelines may provide useful input on safe C code. +However, these rules are not applied as-is; some of them expressly collide +with established practice. + + +Container implementations +^^^^^^^^^^^^^^^^^^^^^^^^^ + +In particular to gain defensive coding benefits from better compiler type +checks, there is a set of replacement container data structures to be found +in :file:`lib/typesafe.h`. They're documented under :ref:`lists`. + +Unfortunately, the FRR codebase is quite large, and migrating existing code to +use these new structures is a tedious and far-reaching process (even if it +can be automated with coccinelle, the patches would touch whole swaths of code +and create tons of merge conflicts for ongoing work.) Therefore, little +existing code has been migrated. + +However, both **new code and refactors of existing code should use the new +containers**. If there are any reasons this can't be done, please work to +remove these reasons (e.g. by adding necessary features to the new containers) +rather than falling back to the old code. + +In order of likelyhood of removal, these are the old containers: + +- :file:`nhrpd/list.*`, ``hlist_*`` ⇒ ``DECLARE_LIST`` +- :file:`nhrpd/list.*`, ``list_*`` ⇒ ``DECLARE_DLIST`` +- :file:`lib/skiplist.*`, ``skiplist_*`` ⇒ ``DECLARE_SKIPLIST`` +- :file:`lib/*_queue.h` (BSD), ``SLIST_*`` ⇒ ``DECLARE_LIST`` +- :file:`lib/*_queue.h` (BSD), ``LIST_*`` ⇒ ``DECLARE_DLIST`` +- :file:`lib/*_queue.h` (BSD), ``STAILQ_*`` ⇒ ``DECLARE_LIST`` +- :file:`lib/*_queue.h` (BSD), ``TAILQ_*`` ⇒ ``DECLARE_DLIST`` +- :file:`lib/hash.*`, ``hash_*`` ⇒ ``DECLARE_HASH`` +- :file:`lib/linklist.*`, ``list_*`` ⇒ ``DECLARE_DLIST`` +- open-coded linked lists ⇒ ``DECLARE_LIST``/``DECLARE_DLIST`` + + +Code Formatting +--------------- + +C Code +^^^^^^ + +For C code, FRR uses Linux kernel style except where noted below. Code which +does not comply with these style guidelines will not be accepted. + +The project provides multiple tools to allow you to correctly style your code +as painlessly as possible, primarily built around ``clang-format``. + +clang-format + + In the project root there is a :file:`.clang-format` configuration file + which can be used with the ``clang-format`` source formatter tool from the + LLVM project. Most of the time, this is the easiest and smartest tool to + use. It can be run in a variety of ways. If you point it at a C source file + or directory of source files, it will format all of them. In the LLVM source + tree there are scripts that allow you to integrate it with ``git``, ``vim`` + and ``emacs``, and there are third-party plugins for other editors. The + ``git`` integration is particularly useful; suppose you have some changes in + your git index. Then, with the integration installed, you can do the + following: + + :: + + git clang-format + + This will format *only* the changes present in your index. If you have just + made a few commits and would like to correctly style only the changes made + in those commits, you can use the following syntax: + + :: + + git clang-format HEAD~X + + Where X is one more than the number of commits back from the tip of your + branch you would like ``clang-format`` to look at (similar to specifying the + target for a rebase). + + The ``vim`` plugin is particularly useful. It allows you to select lines in + visual line mode and press a key binding to invoke ``clang-format`` on only + those lines. + + When using ``clang-format``, it is recommended to use the latest version. + Each consecutive version generally has better handling of various edge + cases. You may notice on occasion that two consecutive runs of + ``clang-format`` over the same code may result in changes being made on the + second run. This is an unfortunate artifact of the tool. Please check with + the kernel style guide if in doubt. + + One stylistic problem with the FRR codebase is the use of ``DEFUN`` macros + for defining CLI commands. ``clang-format`` will happily format these macro + invocations, but the result is often unsightly and difficult to read. + Consequently, FRR takes a more relaxed position with how these are + formatted. In general you should lean towards using the style exemplified in + the section on :ref:`command-line-interface`. Because ``clang-format`` + mangles this style, there is a Python script named ``tools/indent.py`` that + wraps ``clang-format`` and handles ``DEFUN`` macros as well as some other + edge cases specific to FRR. If you are submitting a new file, it is + recommended to run that script over the new file, preferably after ensuring + that the latest stable release of ``clang-format`` is in your ``PATH``. + + Documentation on ``clang-format`` and its various integrations is maintained + on the LLVM website. + + https://clang.llvm.org/docs/ClangFormat.html + +checkpatch.sh +checkpatch.pl + + .. seealso:: :ref:`checkpatch` + + In the Linux kernel source tree there is a Perl script used to check + incoming patches for style errors. FRR uses a shell script front end and an + adapted version of the perl script for the same purpose. These scripts can + be found at :file:`tools/checkpatch.sh` and :file:`tools/checkpatch.pl`. + This script takes a git-formatted diff or patch file, applies it to a clean + FRR tree, and inspects the result to catch potential style errors. Running + this script on your patches before submission is highly recommended. The CI + system runs this script as well and will comment on the PR with the results + if style errors are found. + + It is run like this:: + + ./checkpatch.sh <patch> <tree> + + Reports are generated on ``stderr`` and the exit code indicates whether + issues were found (2, 1) or not (0). + + Where ``<patch>`` is the path to the diff or patch file and ``<tree>`` is + the path to your FRR source tree. The tree should be on the branch that you + intend to submit the patch against. The script will make a best-effort + attempt to save the state of your working tree and index before applying the + patch, and to restore it when it is done, but it is still recommended that + you have a clean working tree as the script does perform a hard reset on + your tree during its run. + + The script reports two classes of issues, namely WARNINGs and ERRORs. Please + pay attention to both of them. The script will generally report WARNINGs + where it cannot be 100% sure that a particular issue is real. In most cases + WARNINGs indicate an issue that needs to be fixed. Sometimes the script will + report false positives; these will be handled in code review on a + case-by-case basis. Since the script only looks at changed lines, + occasionally changing one part of a line can cause the script to report a + style issue already present on that line that is unrelated to the change. + When convenient it is preferred that these be cleaned up inline, but this is + not required. + + In general, a developer should heed the information reported by checkpatch. + However, some flexibility is needed for cases where human judgement yields + better clarity than the script. Accordingly, it may be appropriate to + ignore some checkpatch.sh warnings per discussion among the submitter(s) + and reviewer(s) of a change. Misreporting of errors by the script is + possible. When this occurs, the exception should be handled either by + patching checkpatch to correct the false error report, or by documenting the + exception in this document under :ref:`style-exceptions`. If the incorrect + report is likely to appear again, a checkpatch update is preferred. + + If the script finds one or more WARNINGs it will exit with 1. If it finds + one or more ERRORs it will exit with 2. + + For convenience the Linux documentation for the :file:`tools/checkpatch.pl` + script has been included unmodified (i.e., it has not been updated to + reflect local changes) :doc:`here <checkpatch>` + + +Please remember that while FRR provides these tools for your convenience, +responsibility for properly formatting your code ultimately lies on the +shoulders of the submitter. As such, it is recommended to double-check the +results of these tools to avoid delays in merging your submission. + +In some cases, these tools modify or flag the format in ways that go beyond or +even conflict [#tool_style_conflicts]_ with the canonical documented Linux +kernel style. In these cases, the Linux kernel style takes priority; +non-canonical issues flagged by the tools are not compulsory but rather are +opportunities for discussion among the submitter(s) and reviewer(s) of a change. + +**Whitespace changes in untouched parts of the code are not acceptable +in patches that change actual code.** To change/fix formatting issues, +please create a separate patch that only does formatting changes and +nothing else. + +Kernel and BSD styles are documented externally: + +- https://www.kernel.org/doc/html/latest/process/coding-style.html +- http://man.openbsd.org/style + +For GNU coding style, use ``indent`` with the following invocation: + +:: + + indent -nut -nfc1 file_for_submission.c + + +Historically, FRR used fixed-width integral types that do not exist in any +standard but were defined by most platforms at some point. Officially these +types are not guaranteed to exist. Therefore, please use the fixed-width +integral types introduced in the C99 standard when contributing new code to +FRR. If you need to convert a large amount of code to use the correct types, +there is a shell script in :file:`tools/convert-fixedwidth.sh` that will do the +necessary replacements. + ++-----------+--------------------------+ +| Incorrect | Correct | ++===========+==========================+ +| u_int8_t | uint8_t | ++-----------+--------------------------+ +| u_int16_t | uint16_t | ++-----------+--------------------------+ +| u_int32_t | uint32_t | ++-----------+--------------------------+ +| u_int64_t | uint64_t | ++-----------+--------------------------+ +| u_char | uint8_t or unsigned char | ++-----------+--------------------------+ +| u_short | unsigned short | ++-----------+--------------------------+ +| u_int | unsigned int | ++-----------+--------------------------+ +| u_long | unsigned long | ++-----------+--------------------------+ + +FRR also uses unnamed struct fields, enabled with ``-fms-extensions`` (cf. +https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html). The following two +patterns can/should be used where contextually appropriate: + +.. code-block:: c + + struct outer { + struct inner; + }; + +.. code-block:: c + + struct outer { + union { + struct inner; + struct inner inner_name; + }; + }; + + +.. _style-exceptions: + +Exceptions +"""""""""" + +FRR project code comes from a variety of sources, so there are some +stylistic exceptions in place. They are organized here by branch. + +For ``master``: + +BSD coding style applies to: + +- ``ldpd/`` + +``babeld`` uses, approximately, the following style: + +- K&R style braces +- Indents are 4 spaces +- Function return types are on their own line + +For ``stable/3.0`` and ``stable/2.0``: + +GNU coding style apply to the following parts: + +- ``lib/`` +- ``zebra/`` +- ``bgpd/`` +- ``ospfd/`` +- ``ospf6d/`` +- ``isisd/`` +- ``ripd/`` +- ``ripngd/`` +- ``vtysh/`` + +BSD coding style applies to: + +- ``ldpd/`` + + +Python Code +^^^^^^^^^^^ + +Format all Python code with `black <https://github.com/psf/black>`_. + +In a line:: + + python3 -m black <file.py> + +Run this on any Python files you modify before committing. + +FRR's Python code has been formatted with black version 19.10b. + + +YANG +^^^^ + +FRR uses YANG to define data models for its northbound interface. YANG models +should follow conventions used by the IETF standard models. From a practical +standpoint, this corresponds to the output produced by the ``yanglint`` tool +included in the ``libyang`` project, which is used by FRR to parse and validate +YANG models. You should run the following command on all YANG documents you +write: + +.. code-block:: console + + yanglint -f yang <model> + +The output of this command should be identical to the input file. The sole +exception to this is comments. ``yanglint`` does not support comments and will +strip them from its output. You may include comments in your YANG documents, +but they should be indented appropriately (use spaces). Where possible, +comments should be eschewed in favor of a suitable ``description`` statement. + +In short, a diff between your input file and the output of ``yanglint`` should +either be empty or contain only comments. + +Specific Exceptions +^^^^^^^^^^^^^^^^^^^ + +Most of the time checkpatch errors should be corrected. Occasionally as a group +maintainers will decide to ignore certain stylistic issues. Usually this is +because correcting the issue is not possible without large unrelated code +changes. When an exception is made, if it is unlikely to show up again and +doesn't warrant an update to checkpatch, it is documented here. + ++------------------------------------------+---------------------------------------------------------------+ +| Issue | Ignore Reason | ++==========================================+===============================================================+ +| DEFPY_HIDDEN, DEFPY_ATTR: complex macros | DEF* macros cannot be wrapped in parentheses without updating | +| should be wrapped in parentheses | all usages of the macro, which would be highly disruptive. | ++------------------------------------------+---------------------------------------------------------------+ + +Types of configurables +---------------------- + +.. note:: + + This entire section essentially just argues to not make configuration + unnecessarily involved for the user. Rather than rules, this is more of + a list of conclusions intended to help make FRR usable for operators. + + +Almost every feature FRR has comes with its own set of switches and options. +There are several stages at which configuration can be applied. In order of +preference, these are: + +- at configuration/runtime, through YANG. + + This is the preferred way for all FRR knobs. Not all daemons and features + are fully YANGified yet, so in some cases new features cannot rely on a + YANG interface. If a daemon already implements a YANG interface (even + partial), new CLI options must be implemented through a YANG model. + + .. warning:: + + Unlike everything else in this section being guidelines with some slack, + implementing and using a YANG interface for new CLI options in (even + partially!) YANGified daemons is a hard requirement. + + +- at configuration/runtime, through the CLI. + + The "good old" way for all regular configuration. More involved for users + to automate *correctly* than YANG. + +- at startup, by loading additional modules. + + If a feature introduces a dependency on additional libraries (e.g. libsnmp, + rtrlib, etc.), this is the best way to encapsulate the dependency. Having + a separate module allows the distribution to create a separate package + with the extra dependency, so FRR can still be installed without pulling + everything in. + + A module may also be appropriate if a feature is large and reasonably well + isolated. Reducing the amount of running the code is a security benefit, + so even if there are no new external dependencies, modules can be useful. + + While modules cannot currently be loaded at runtime, this is a tradeoff + decision that was made to allow modules to change/extend code that is very + hard to (re)adjust at runtime. If there is a case for runtime (un)loading + of modules, this tradeoff can absolutely be reevaluated. + +- at startup, with command line options. + + This interface is only appropriate for options that have an effect very + early in FRR startup, i.e. before configuration is loaded. Anything that + affects configuration load itself should be here, as well as options + changing the environment FRR runs in. + + If a tunable can be changed at runtime, a command line option is only + acceptable if the configured value has an effect before configuration is + loaded (e.g. zebra reads routes from the kernel before loading config, so + the netlink buffer size is an appropriate command line option.) + +- at compile time, with ``./configure`` options. + + This is the absolute last preference for tunables, since the distribution + needs to make the decision for the user and/or the user needs to rebuild + FRR in order to change the option. + + "Good" configure options do one of three things: + + - set distribution-specific parameters, most prominently all the path + options. File system layout is a distribution/packaging choice, so the + user would hopefully never need to adjust these. + + - changing toolchain behavior, e.g. instrumentation, warnings, + optimizations and sanitizers. + + - enabling/disabling parts of the build, especially if they need + additional dependencies. Being able to build only parts of FRR, or + without some library, is useful. **The only effect these options should + have is adding or removing files from the build result.** If a knob + in this category causes the same binary to exist in different variants, + it is likely implemented incorrectly! + + .. note:: + + This last guideline is currently ignored by several configure options. + ``vtysh`` in general depends on the entire list of enabled daemons, + and options like ``--enable-bgp-vnc`` and ``--enable-ospfapi`` change + daemons internally. Consider this more of an "ideal" than a "rule". + + +Whenever adding new knobs, please try reasonably hard to go up as far as +possible on the above list. Especially ``./configure`` flags are often enough +the "easy way out" but should be avoided when at all possible. To a lesser +degree, the same applies to command line options. + + +Compile-time conditional code +----------------------------- + +Many users access FRR via binary packages from 3rd party sources; +compile-time code puts inclusion/exclusion in the hands of the package +maintainer. Please think very carefully before making code conditional +at compile time, as it increases regression testing, maintenance +burdens, and user confusion. In particular, please avoid gratuitous +``--enable-…`` switches to the configure script - in general, code +should be of high quality and in working condition, or it shouldn’t be +in FRR at all. + +When code must be compile-time conditional, try have the compiler make +it conditional rather than the C pre-processor so that it will still be +checked by the compiler, even if disabled. For example, + +:: + + if (SOME_SYMBOL) + frobnicate(); + +is preferred to + +:: + + #ifdef SOME_SYMBOL + frobnicate (); + #endif /* SOME_SYMBOL */ + +Note that the former approach requires ensuring that ``SOME_SYMBOL`` will be +defined (watch your ``AC_DEFINE``\ s). + +Debug-guards in code +-------------------- + +Debugging statements are an important methodology to allow developers to fix +issues found in the code after it has been released. The caveat here is that +the developer must remember that people will be using the code at scale and in +ways that can be unexpected for the original implementor. As such debugs +**MUST** be guarded in such a way that they can be turned off. FRR has the +ability to turn on/off debugs from the CLI and it is expected that the +developer will use this convention to allow control of their debugs. + +Custom syntax-like block macros +------------------------------- + +FRR uses some macros that behave like the ``for`` or ``if`` C keywords. These +macros follow these patterns: + +- loop-style macros are named ``frr_each_*`` (and ``frr_each``) +- single run macros are named ``frr_with_*`` +- to avoid confusion, ``frr_with_*`` macros must always use a ``{ ... }`` + block even if the block only contains one statement. The ``frr_each`` + constructs are assumed to be well-known enough to use normal ``for`` rules. +- ``break``, ``return`` and ``goto`` all work correctly. For loop-style + macros, ``continue`` works correctly too. + +Both the ``each`` and ``with`` keywords are inspired by other (more +higher-level) programming languages that provide these constructs. + +There are also some older iteration macros, e.g. ``ALL_LIST_ELEMENTS`` and +``FOREACH_AFI_SAFI``. These macros in some cases do **not** fulfill the above +pattern (e.g. ``break`` does not work in ``FOREACH_AFI_SAFI`` because it +expands to 2 nested loops.) + +Static Analysis and Sanitizers +------------------------------ +Clang/LLVM and GCC come with a variety of tools that can be used to help find +bugs in FRR. + +clang-analyze + This is a static analyzer that scans the source code looking for patterns + that are likely to be bugs. The tool is run automatically on pull requests + as part of CI and new static analysis warnings will be placed in the CI + results. FRR aims for absolutely zero static analysis errors. While the + project is not quite there, code that introduces new static analysis errors + is very unlikely to be merged. + +AddressSanitizer + This is an excellent tool that provides runtime instrumentation for + detecting memory errors. As part of CI FRR is built with this + instrumentation and run through a series of tests to look for any results. + Testing your own code with this tool before submission is encouraged. You + can enable it by passing:: + + --enable-address-sanitizer + + to ``configure``. + +ThreadSanitizer + Similar to AddressSanitizer, this tool provides runtime instrumentation for + detecting data races. If you are working on or around multithreaded code, + extensive testing with this instrumtation enabled is *highly* recommended. + You can enable it by passing:: + + --enable-thread-sanitizer + + to ``configure``. + +MemorySanitizer + Similar to AddressSanitizer, this tool provides runtime instrumentation for + detecting use of uninitialized heap memory. Testing your own code with this + tool before submission is encouraged. You can enable it by passing:: + + --enable-memory-sanitizer + + to ``configure``. + +All of the above tools are available in the Clang/LLVM toolchain since 3.4. +AddressSanitizer and ThreadSanitizer are available in recent versions of GCC, +but are no longer actively maintained. MemorySanitizer is not available in GCC. + +.. note:: + + The different Sanitizers are mostly incompatible with each other. Please + refer to GCC/LLVM documentation for details. + +frr-format plugin + This is a GCC plugin provided with FRR that does extended type checks for + ``%pFX``-style printfrr extensions. To use this plugin, + + 1. install GCC plugin development files, e.g.:: + + apt-get install gcc-10-plugin-dev + + 2. **before** running ``configure``, compile the plugin with:: + + make -C tools/gcc-plugins CXX=g++-10 + + (Edit the GCC version to what you're using, it should work for GCC 9 or + newer.) + + After this, the plugin should be automatically picked up by ``configure``. + The plugin does not change very frequently, so you can keep it around across + work on different FRR branches. After a ``git clean -x``, the ``make`` line + will need to be run again. You can also add ``--with-frr-format`` to the + ``configure`` line to make sure the plugin is used, otherwise if something + is not set up correctly it might be silently ignored. + + .. warning:: + + Do **not** enable this plugin for package/release builds. It is intended + for developer/debug builds only. Since it modifies the compiler, it may + cause silent corruption of the executable files. + + Using the plugin also changes the string for ``PRI[udx]64`` from the + system value to ``%L[udx]`` (normally ``%ll[udx]`` or ``%l[udx]``.) + +Additionally, the FRR codebase is regularly scanned for static analysis +errors with Coverity and pull request changes are scanned as part of the +Continuous Integration (CI) process. Developers can scan their commits for +Coverity static analysis errors prior to submission using the +``scan-build`` command. To use this command, the ``clang-tools`` package must +be installed. For example, this can be accomplished on Ubuntu with the +``sudo apt-get install clang-tools`` command. Then, touch the files you want scanned and +invoke the ``scan-build`` command. For example:: + + cd ~/GitHub/frr + touch ospfd/ospf_flood.c ospfd/ospf_vty.c ospfd/ospf_opaque.c + cd build + scan-build make -j32 + +The results of the scan including any static analysis errors will appear inline. +Additionally, there will a directory in the /tmp containing the Coverity +reports (e.g., scan-build-2023-06-09-120100-473730-1). + +Executing non-installed dynamic binaries +---------------------------------------- + +Since FRR uses the GNU autotools build system, it inherits its shortcomings. +To execute a binary directly from the build tree under a wrapper like +`valgrind`, `gdb` or `strace`, use:: + + ./libtool --mode=execute valgrind [--valgrind-opts] zebra/zebra [--zebra-opts] + +While replacing valgrind/zebra as needed. The `libtool` script is found in +the root of the build directory after `./configure` has completed. Its purpose +is to correctly set up `LD_LIBRARY_PATH` so that libraries from the build tree +are used. (On some systems, `libtool` is also available from PATH, but this is +not always the case.) + +.. _cli-workflow: + +CLI changes +----------- + +CLI's are a complicated ugly beast. Additions or changes to the CLI should use +a DEFPY to encapsulate one setting as much as is possible. Additionally as new +DEFPY's are added to the system, documentation should be provided for the new +commands. + +Backwards Compatibility +----------------------- + +As a general principle, changes to CLI and code in the lib/ directory should be +made in a backwards compatible fashion. This means that changes that are purely +stylistic in nature should be avoided, e.g., renaming an existing macro or +library function name without any functional change. When adding new parameters +to common functions, it is also good to consider if this too should be done in +a backward compatible fashion, e.g., by preserving the old form in addition to +adding the new form. + +This is not to say that minor or even major functional changes to CLI and +common code should be avoided, but rather that the benefit gained from a change +should be weighed against the added cost/complexity to existing code. Also, +that when making such changes, it is good to preserve compatibility when +possible to do so without introducing maintenance overhead/cost. It is also +important to keep in mind, existing code includes code that may reside in +private repositories (and is yet to be submitted) or code that has yet to be +migrated from Quagga to FRR. + +That said, compatibility measures can (and should) be removed when either: + +- they become a significant burden, e.g. when data structures change and the + compatibility measure would need a complex adaptation layer or becomes + flat-out impossible +- some measure of time (dependent on the specific case) has passed, so that + the compatibility grace period is considered expired. + +For CLI commands, the deprecation period is 1 year. + +In all cases, compatibility pieces should be marked with compiler/preprocessor +annotations to print warnings at compile time, pointing to the appropriate +update path. A ``-Werror`` build should fail if compatibility bits are used. To +avoid compilation issues in released code, such compiler/preprocessor +annotations must be ignored non-development branches. For example: + +.. code-block:: c + + #if CONFDATE > 20180403 + CPP_NOTICE("Use of <XYZ> is deprecated, please use <ABC>") + #endif + +Preferably, the shell script :file:`tools/fixup-deprecated.py` will be +updated along with making non-backwards compatible code changes, or an +alternate script should be introduced, to update the code to match the +change. When the script is updated, there is no need to preserve the +deprecated code. Note that this does not apply to user interface +changes, just internal code, macros and libraries. + +Miscellaneous +------------- + +When in doubt, follow the guidelines in the Linux kernel style guide, or ask on +the development mailing list / public Slack instance. + +JSON Output +^^^^^^^^^^^ + +New JSON output in FRR needs to be backed by schema, in particular a YANG model. +When adding new JSON, first search for an existing YANG model, either in FRR or +a standard model (e.g., IETF) and use that model as the basis for any JSON +structure and *especially* for key names and canonical values formats. + +If no YANG model exists to support the JSON then an FRR YANG model needs to be +added to or created to support the JSON format. + +* All JSON keys are to be ``camelCased``, with no spaces. YANG modules almost + always use ``kebab-case`` (i.e., all lower case with hyphens to separate + words), so these identifiers need to be mapped to ``camelCase`` by removing + the hyphen (or symbol) and capitalizing the following letter, for + example "router-id" becomes "routerId" +* Commands which output JSON should produce ``{}`` if they have nothing to + display +* In general JSON commands include a ``json`` keyword typically at the end of + the CLI command (e.g., ``show ip ospf json``) + +Use of const +^^^^^^^^^^^^ + +Please consider using ``const`` when possible: it's a useful hint to +callers about the limits to side-effects from your apis, and it makes +it possible to use your apis in paths that involve ``const`` +objects. If you encounter existing apis that *could* be ``const``, +consider including changes in your own pull-request. + +Help with specific warnings +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +FRR's configure script enables a whole batch of extra warnings, some of which +may not be obvious in how to fix. Here are some notes on specific warnings: + +* ``-Wstrict-prototypes``: you probably just forgot the ``void`` in a function + declaration with no parameters, i.e. ``static void foo() {...}`` rather than + ``static void foo(void) {...}``. + + Without the ``void``, in C, it's a function with *unspecified* parameters + (and varargs calling convention.) This is a notable difference to C++, where + the ``void`` is optional and an empty parameter list means no parameters. + +* ``"strict match required"`` from the frr-format plugin: check if you are + using a cast in a printf parameter list. The frr-format plugin cannot + access correct full type information for casts like + ``printfrr(..., (uint64_t)something, ...)`` and will print incorrect + warnings particularly if ``uint64_t``, ``size_t`` or ``ptrdiff_t`` are + involved. The problem is *not* triggered with a variable or function return + value of the exact same type (without a cast). + + Since these cases are very rare, community consensus is to just work around + the warning even though the code might be correct. If you are running into + this, your options are: + + 1. try to avoid the cast altogether, maybe using a different printf format + specifier (e.g. ``%lu`` instead of ``%zu`` or ``PRIu64``). + 2. fix the type(s) of the function/variable/struct member being printed + 3. create a temporary variable with the value and print that without a cast + (this is the last resort and was not necessary anywhere so far.) + + +.. _documentation: + +Documentation +============= + +FRR uses Sphinx+RST as its documentation system. The document you are currently +reading was generated by Sphinx from RST source in +:file:`doc/developer/workflow.rst`. The documentation is structured as follows: + ++-----------------------+-------------------------------------------+ +| Directory | Contents | ++=======================+===========================================+ +| :file:`doc/user` | User documentation; configuration guides; | +| | protocol overviews | ++-----------------------+-------------------------------------------+ +| :file:`doc/developer` | Developer's documentation; API specs; | +| | datastructures; architecture overviews; | +| | project management procedure | ++-----------------------+-------------------------------------------+ +| :file:`doc/manpages` | Source for manpages | ++-----------------------+-------------------------------------------+ +| :file:`doc/figures` | Images and diagrams | ++-----------------------+-------------------------------------------+ +| :file:`doc/extra` | Miscellaneous Sphinx extensions, scripts, | +| | customizations, etc. | ++-----------------------+-------------------------------------------+ + +Each of these directories, with the exception of :file:`doc/figures` and +:file:`doc/extra`, contains a Sphinx-generated Makefile and configuration +script :file:`conf.py` used to set various document parameters. The makefile +can be used for a variety of targets; invoke `make help` in any of these +directories for a listing of available output formats. For convenience, there +is a top-level :file:`Makefile.am` that has targets for PDF and HTML +documentation for both developer and user documentation, respectively. That +makefile is also responsible for building manual pages packed with distribution +builds. + +Indent and styling should follow existing conventions: + +- 3 spaces for indents under directives +- Cross references may contain only lowercase alphanumeric characters and + hyphens ('-') +- Lines wrapped to 80 characters where possible + +Characters for header levels should follow Python documentation guide: + +- ``#`` with overline, for parts +- ``*`` with overline, for chapters +- ``=``, for sections +- ``-``, for subsections +- ``^``, for subsubsections +- ``"``, for paragraphs + +After you have made your changes, please make sure that you can invoke +``make latexpdf`` and ``make html`` with no warnings. + +The documentation is currently incomplete and needs love. If you find a broken +cross-reference, figure, dead hyperlink, style issue or any other nastiness we +gladly accept documentation patches. + +To build the docs, please ensure you have installed a recent version of +`Sphinx <http://www.sphinx-doc.org/en/stable/install.html>`_. If you want to +build LaTeX or PDF docs, you will also need a full LaTeX distribution +installed. + +Code +---- + +FRR is a large and complex software project developed by many different people +over a long period of time. Without adequate documentation, it can be +exceedingly difficult to understand code segments, APIs and other interfaces. +In the interest of keeping the project healthy and maintainable, you should +make every effort to document your code so that other people can understand +what it does without needing to closely read the code itself. + +Some specific guidelines that contributors should follow are: + +- Functions exposed in header files should have descriptive comments above + their signatures in the header file. At a minimum, a function comment should + contain information about the return value, parameters, and a general summary + of the function's purpose. Documentation on parameter values can be omitted + if it is (very) obvious what they are used for. + + Function comments must follow the style for multiline comments laid out in + the kernel style guide. + + Example: + + .. code-block:: c + + /* + * Determines whether or not a string is cool. + * + * text + * the string to check for coolness + * + * is_clccfc + * whether capslock is cruise control for cool + * + * Returns: + * 7 if the text is cool, 0 otherwise + */ + int check_coolness(const char *text, bool is_clccfc); + + Function comments should make it clear what parameters and return values are + used for. + +- Static functions should have descriptive comments in the same form as above + if what they do is not immediately obvious. Use good engineering judgement + when deciding whether a comment is necessary. If you are unsure, document + your code. +- Global variables, static or not, should have a comment describing their use. +- **For new code in lib/, these guidelines are hard requirements.** + +If you make significant changes to portions of the codebase covered in the +Developer's Manual, add a major subsystem or feature, or gain arcane mastery of +some undocumented or poorly documented part of the codebase, please document +your work so others can benefit. If you add a major feature or introduce a new +API, please document the architecture and API to the best of your abilities in +the Developer's Manual, using good judgement when choosing where to place it. + +Finally, if you come across some code that is undocumented and feel like +going above and beyond, document it! We absolutely appreciate and accept +patches that document previously undocumented code. + +User +---- + +If you are contributing code that adds significant user-visible functionality +please document how to use it in :file:`doc/user`. Use good judgement when +choosing where to place documentation. For example, instructions on how to use +your implementation of a new BGP draft should go in the BGP chapter instead of +being its own chapter. If you are adding a new protocol daemon, please create a +new chapter. + +FRR Specific Markup +------------------- + +FRR has some customizations applied to the Sphinx markup that go a long way +towards making documentation easier to use, write and maintain. + +CLI Commands +^^^^^^^^^^^^ + +When documenting CLI please use the ``.. clicmd::`` directive. This directive +will format the command and generate index entries automatically. For example, +the command :clicmd:`show pony` would be documented as follows: + +.. code-block:: rest + + .. clicmd:: show pony + + Prints an ASCII pony. Example output::: + + >>\. + /_ )`. + / _)`^)`. _.---. _ + (_,' \ `^-)"" `.\ + | | \ + \ / | + / \ /.___.'\ (\ (_ + < ,"|| \ |`. \`-' + \\ () )| )/ + hjw |_>|> /_] // + /_] /_] + + +When documented this way, CLI commands can be cross referenced with the +``:clicmd:`` inline markup like so: + +.. code-block:: rest + + :clicmd:`show pony` + +This is very helpful for users who want to quickly remind themselves what a +particular command does. + +When documenting a cli that has a ``no`` form, please do not include the ``no`` +form. I.e. ``no show pony`` would not be documented anywhere. Since most +commands have ``no`` forms, users should be able to infer these or get help +from vtysh's completions. + +When documenting commands that have lots of possible variants, just document +the single command in summary rather than enumerating each possible variant. +E.g. for ``show pony [foo|bar]``, do not: + +.. code-block:: rest + + .. clicmd:: show pony + .. clicmd:: show pony foo + .. clicmd:: show pony bar + +Do: + +.. code-block:: rest + + .. clicmd:: show pony [foo|bar] + + +Configuration Snippets +^^^^^^^^^^^^^^^^^^^^^^ + +When putting blocks of example configuration please use the +``.. code-block::`` directive and specify ``frr`` as the highlighting language, +as in the following example. This will tell Sphinx to use a custom Pygments +lexer to highlight FRR configuration syntax. + +.. code-block:: rest + + .. code-block:: frr + + ! + ! Example configuration file. + ! + log file /tmp/log.log + service integrated-vtysh-config + ! + ip route 1.2.3.0/24 reject + ipv6 route de:ea:db:ee:ff::/64 reject + ! + + +.. _GitHub: https://github.com/frrouting/frr +.. _GitHub issues: https://github.com/frrouting/frr/issues + +.. rubric:: Footnotes + +.. [#tool_style_conflicts] For example, lines over 80 characters are allowed + for text strings to make it possible to search the code for them: please + see `Linux kernel style (breaking long lines and strings) <https://www.kernel.org/doc/html/v4.10/process/coding-style.html#breaking-long-lines-and-strings>`_ + and `Issue #1794 <https://github.com/FRRouting/frr/issues/1794>`_. diff --git a/doc/developer/xrefs.rst b/doc/developer/xrefs.rst new file mode 100644 index 0000000..e8e07df --- /dev/null +++ b/doc/developer/xrefs.rst @@ -0,0 +1,215 @@ +.. _xrefs: + +Introspection (xrefs) +===================== + +The FRR library provides an introspection facility called "xrefs." The intent +is to provide structured access to annotated entities in the compiled binary, +such as log messages and thread scheduling calls. + +Enabling and use +---------------- + +Support for emitting an xref is included in the macros for the specific +entities, e.g. :c:func:`zlog_info` contains the relevant statements. The only +requirement for the system to work is a GNU compatible linker that supports +section start/end symbols. (The only known linker on any system FRR supports +that does not do this is the Solaris linker.) + +To verify xrefs have been included in a binary or dynamic library, run +``readelf -n binary``. For individual object files, it's +``readelf -S object.o | grep xref_array`` instead. + +Structure and contents +---------------------- + +As a slight improvement to security and fault detection, xrefs are divided into +a ``const struct xref *`` and an optional ``struct xrefdata *``. The required +const part contains: + +.. c:member:: enum xref_type xref.type + + Identifies what kind of object the xref points to. + +.. c:member:: int line +.. c:member:: const char *xref.file +.. c:member:: const char *xref.func + + Source code location of the xref. ``func`` will be ``<global>`` for + xrefs outside of a function. + +.. c:member:: struct xrefdata *xref.xrefdata + + The optional writable part of the xref. NULL if no non-const part exists. + +The optional non-const part has: + +.. c:member:: const struct xref *xrefdata.xref + + Pointer back to the constant part. Since circular pointers are close to + impossible to emit from inside a function body's static variables, this + is initialized at startup. + +.. c:member:: char xrefdata.uid[16] + + Unique identifier, see below. + +.. c:member:: const char *xrefdata.hashstr +.. c:member:: uint32_t xrefdata.hashu32[2] + + Input to unique identifier calculation. These should encompass all + details needed to make an xref unique. If more than one string should + be considered, use string concatenation for the initializer. + +Both structures can be extended by embedding them in a larger type-specific +struct, e.g. ``struct xref_logmsg *``. + +Unique identifiers +------------------ + +All xrefs that have a writable ``struct xrefdata *`` part are assigned an +unique identifier, which is formed as base32 (crockford) SHA256 on: + +- the source filename +- the ``hashstr`` field +- the ``hashu32`` fields + +.. note:: + + Function names and line numbers are intentionally not included to allow + moving items within a file without affecting the identifier. + +For running executables, this hash is calculated once at startup. When +directly reading from an ELF file with external tooling, the value must be +calculated when necessary. + +The identifiers have the form ``AXXXX-XXXXX`` where ``X`` is +``0-9, A-Z except I,L,O,U`` and ``A`` is ``G-Z except I,L,O,U`` (i.e. the +identifiers always start with a letter.) When reading identifiers from user +input, ``I`` and ``L`` should be replaced with ``1`` and ``O`` should be +replaced with ``0``. There are 49 bits of entropy in this identifier. + +Underlying machinery +-------------------- + +Xrefs are nothing other than global variables with some extra glue to make +them possible to find from the outside by looking at the binary. The first +non-obvious part is that they can occur inside of functions, since they're +defined as ``static``. They don't have a visible name -- they don't need one. + +To make finding these variables possible, another global variable, a pointer +to the first one, is created in the same way. However, it is put in a special +ELF section through ``__attribute__((section("xref_array")))``. This is the +section you can see with readelf. + +Finally, on the level of a whole executable or library, the linker will stuff +the individual pointers consecutive to each other since they're in the same +section — hence the array. Start and end of this array is given by the +linker-autogenerated ``__start_xref_array`` and ``__stop_xref_array`` symbols. +Using these, both a constructor to run at startup as well as an ELF note are +created. + +The ELF note is the entrypoint for externally retrieving xrefs from a binary +without having to run it. It can be found by walking through the ELF data +structures even if the binary has been fully stripped of debug and section +information. SystemTap's SDT probes & LTTng's trace points work in the same +way (though they emit 1 note for each probe, while xrefs only emit one note +in total which refers to the array.) Using xrefs does not impact SystemTap +or LTTng, the notes have identifiers they can be distinguished by. + +The ELF structure of a linked binary (library or executable) will look like +this:: + + $ readelf --wide -l -n lib/.libs/libfrr.so + + Elf file type is DYN (Shared object file) + Entry point 0x67d21 + There are 12 program headers, starting at offset 64 + + Program Headers: + Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align + PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002a0 0x0002a0 R 0x8 + INTERP 0x125560 0x0000000000125560 0x0000000000125560 0x00001c 0x00001c R 0x10 + [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] + LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x02aff0 0x02aff0 R 0x1000 + LOAD 0x02b000 0x000000000002b000 0x000000000002b000 0x0b2889 0x0b2889 R E 0x1000 + LOAD 0x0de000 0x00000000000de000 0x00000000000de000 0x070048 0x070048 R 0x1000 + LOAD 0x14e428 0x000000000014f428 0x000000000014f428 0x00fb70 0x01a2b8 RW 0x1000 + DYNAMIC 0x157a40 0x0000000000158a40 0x0000000000158a40 0x000270 0x000270 RW 0x8 + NOTE 0x0002e0 0x00000000000002e0 0x00000000000002e0 0x00004c 0x00004c R 0x4 + TLS 0x14e428 0x000000000014f428 0x000000000014f428 0x000000 0x000008 R 0x8 + GNU_EH_FRAME 0x12557c 0x000000000012557c 0x000000000012557c 0x00819c 0x00819c R 0x4 + GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 + GNU_RELRO 0x14e428 0x000000000014f428 0x000000000014f428 0x009bd8 0x009bd8 R 0x1 + + (...) + + Displaying notes found in: .note.gnu.build-id + Owner Data size Description + GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: 6a1f66be38b523095ebd6ec13cc15820cede903d + + Displaying notes found in: .note.FRR + Owner Data size Description + FRRouting 0x00000010 Unknown note type: (0x46455258) description data: 6c eb 15 00 00 00 00 00 74 ec 15 00 00 00 00 00 + +Where 0x15eb6c…0x15ec74 are the offsets (relative to the note itself) where +the xref array is in the file. Also note the owner is clearly marked as +"FRRouting" and the type is "XREF" in hex. + +For SystemTap's use of ELF notes, refer to +https://libstapsdt.readthedocs.io/en/latest/how-it-works/internals.html as an +entry point. + +.. note:: + + Due to GCC bug 41091, the "xref_array" section is not correctly generated + for C++ code when compiled by GCC. A workaround is present for runtime + functionality, but to extract the xrefs from a C++ source file, it needs + to be built with clang (or a future fixed version of GCC) instead. + +Extraction tool +--------------- + +The FRR source contains a matching tool to extract xref data from compiled ELF +binaries in ``python/xrelfo.py``. This tool uses CPython extensions +implemented in ``clippy`` and must therefore be executed with that. + +``xrelfo.py`` processes input from one or more ELF file (.o, .so, executable), +libtool object (.lo, .la, executable wrapper script) or JSON (output from +``xrelfo.py``) and generates an output JSON file. During standard FRR build, +it is invoked on all binaries and libraries and the result is combined into +``frr.json``. + +ELF files from any operating system, CPU architecture and endianness can be +processed on any host. Any issues with this are bugs in ``xrelfo.py`` +(or clippy's ELF code.) + +``xrelfo.py`` also performs some sanity checking, particularly on log +messages. The following options are available: + +.. option:: -o OUTPUT + + Filename to write JSON output to. As a convention, a ``.xref`` filename + extension is used. + +.. option:: -Wlog-format + + Performs extra checks on log message format strings, particularly checks + for ``\t`` and ``\n`` characters (which should not be used in log messages). + +.. option:: -Wlog-args + + Generates cleanup hints for format string arguments where + :c:func:`printfrr()` extensions could be used, e.g. replacing ``inet_ntoa`` + with ``%pI4``. + +.. option:: --profile + + Runs the Python profiler to identify hotspots in the ``xrelfo.py`` code. + +``xrelfo.py`` uses information about C structure definitions saved in +``python/xrefstructs.json``. This file is included with the FRR sources and +only needs to be regenerated when some of the ``struct xref_*`` definitions +are changed (which should be almost never). The file is written by +``python/tiabwarfo.py``, which uses ``pahole`` to extract the necessary data +from DWARF information. diff --git a/doc/developer/zebra.rst b/doc/developer/zebra.rst new file mode 100644 index 0000000..482df96 --- /dev/null +++ b/doc/developer/zebra.rst @@ -0,0 +1,247 @@ +.. _zebra: + +***** +Zebra +***** + +.. _zebra-protocol: + +Overview of the Zebra Protocol +============================== + +The Zebra protocol (or ``ZAPI``) is used by protocol daemons to +communicate with the **zebra** daemon. + +Each protocol daemon may request and send information to and from the +**zebra** daemon such as interface states, routing state, +nexthop-validation, and so on. Protocol daemons may also install +routes with **zebra**. The **zebra** daemon manages which routes are +installed into the forwarding table with the kernel. Some daemons use +more than one ZAPI connection. This is supported: each ZAPI session is +identified by a tuple of: ``{protocol, instance, session_id}``. LDPD +is an example: it uses a second, synchronous ZAPI session to manage +label blocks. The default value for ``session_id`` is zero; daemons +who use multiple ZAPI sessions must assign unique values to the +sessions' ids. + +The Zebra protocol is a streaming protocol, with a common header. Version 0 +lacks a version field and is implicitly versioned. Version 1 and all subsequent +versions have a version field. Version 0 can be distinguished from all other +versions by examining the 3rd byte of the header, which contains a marker value +of 255 (in Quagga) or 254 (in FRR) for all versions except version 0. The +marker byte corresponds to the command field in version 0, and the marker value +is a reserved command in version 0. + +Version History +--------------- + +- Version 0 + + Used by all versions of GNU Zebra and all version of Quagga up to and + including Quagga 0.98. This version has no ``version`` field, and so is + implicitly versioned as version 0. + +- Version 1 + + Added ``marker`` and ``version`` fields, increased ``command`` field to 16 + bits. Used by Quagga versions 0.99.3 through 0.99.20. + +- Version 2 + + Used by Quagga versions 0.99.21 through 0.99.23. + +- Version 3 + + Added ``vrf_id`` field. Used by Quagga versions 0.99.23 until FRR fork. + +- Version 4 + + Change marker value to 254 to prevent people mixing and matching Quagga and + FRR daemon binaries. Used by FRR versions 2.0 through 3.0.3. + +- Version 5 + + Increased VRF identifier field from 16 to 32 bits. Used by FRR versions 4.0 + through 5.0.1. + +- Version 6 + + Removed the following commands: + + * ZEBRA_IPV4_ROUTE_ADD + * ZEBRA_IPV4_ROUTE_DELETE + * ZEBRA_IPV6_ROUTE_ADD + * ZEBRA_IPV6_ROUTE_DELETE + + Used since FRR version 6.0. + + +Zebra Protocol Definition +========================= + +Zebra Protocol Header Field Definitions +--------------------------------------- + +Length + Total packet length including this header. + +Marker + Static marker. The marker value, when it exists, is 255 in all versions of + Quagga. It is 254 in all versions of FRR. This is to allow version 0 headers + (which do not include version explicitly) to be distinguished from versioned + headers. + +Version + Zebra protocol version number. Clients should not continue processing + messages past the version field for versions they do not recognise. + +Command + The Zebra protocol command. + + +Current Version +^^^^^^^^^^^^^^^ + +:: + + Version 5, 6 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Marker | Version | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | VRF ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Command | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Past Versions +^^^^^^^^^^^^^ + +:: + + Version 0 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Command | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +:: + + Version 1, 2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Marker | Version | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Command | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +:: + + Version 3, 4 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | Marker | Version | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | VRF ID | Command | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Zebra Protocol Commands +----------------------- + +The definitions of zebra protocol commands can be found at ``lib/zclient.h``. + + +Zebra Dataplane +=============== + +The zebra dataplane subsystem provides a framework for FIB +programming. Zebra uses the dataplane to program the local kernel as +it makes changes to objects such as IP routes, MPLS LSPs, and +interface IP addresses. The dataplane runs in its own pthread, in +order to off-load work from the main zebra pthread. + +The zebra dataplane API is versioned; the version number must be +updated along with API changes. Plugins can test the current version +number and confirm that they are compatible with the current version. + + +Dataplane batching +================== + +Dataplane batching is an optimization feature that reduces the processing +time involved in the user space to kernel space transition for every message we +want to send. + +Design +----------- + +With our dataplane abstraction, we create a queue of dataplane context objects +for the messages we want to send to the kernel. In a separate pthread, we +loop over this queue and send the context objects to the appropriate +dataplane. A batching enhancement tightly integrates with the dataplane +context objects so they are able to be batch sent to dataplanes that support +it. + +There is one main change in the dataplane code. It does not call +kernel-dependent functions one-by-one, but instead it hands a list of work down +to the kernel level for processing. + +Netlink +^^^^^^^ + +At the moment, this is the only dataplane that allows for batch sending +messages to it. + +When messages must be sent to the kernel, they are consecutively added +to the batch represented by the `struct nl_batch`. Context objects are firstly +encoded to their binary representation. All the encoding functions use the same +interface: take a context object, a buffer and a size of the buffer as an +argument. It is important that they should handle a situation in which a message +wouldn't fit in the buffer and return a proper error. To achieve a zero-copy +(in the user space only) messages are encoded to the same buffer which will +be passed to the kernel. Hence, we can theoretically hit the boundary of the +buffer. + +Messages stored in the batch are sent if one of the conditions occurs: + +- When an encoding function returns the buffer overflow error. The context + object that caused this error is re-added to the new, empty batch. + +- When the size of the batch hits certain limit. + +- When the namespace of a currently being processed context object is + different from all the previous ones. They have to be sent through + distinct sockets, so the messages cannot share the same buffer. + +- After the last message from the list is processed. + +As mentioned earlier, there is a special threshold which is smaller than +the size of the underlying buffer. It prevents the overflow error and thus +eliminates the case, in which a message is encoded twice. + +The buffer used in the batching is global, since allocating that big amount of +memory every time wouldn't be most effective. However, its size can be changed +dynamically, using hidden vtysh command: +``zebra kernel netlink batch-tx-buf (1-1048576) (1-1048576)``. This feature is +only used in tests and shouldn't be utilized in any other place. + +For every failed message in the batch, the kernel responds with an error +message. Error messages are kept in the same order as they were sent, so parsing the +response is straightforward. We use the two pointer technique to match +requests with responses and then set appropriate status of dataplane context +objects. There is also a global receive buffer and it is assumed that whatever +the kernel sends it will fit in this buffer. The payload of netlink error messages +consists of a error code and the original netlink message of the request, so +the batch response won't be bigger than the batch request increased by +some space for the headers. diff --git a/doc/extra/frrlexer.py b/doc/extra/frrlexer.py new file mode 100644 index 0000000..39ce5e6 --- /dev/null +++ b/doc/extra/frrlexer.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: ISC +# Copyright (c) 2017 Vincent Bernat <bernat@luffy.cx> + +from pygments.lexer import RegexLexer, bygroups +from pygments.token import Text, Comment, Keyword +from pygments.token import String, Number, Name + + +class FRRLexer(RegexLexer): + name = "frr" + aliases = ["frr"] + tokens = { + "root": [ + (r"^[ \t]*!.*?\n", Comment.Singleline), + (r'"(\\\\|\\"|[^"])*"', String.Double), + ( + r"[a-f0-9]*:[a-f0-9]*:[a-f0-9:]*(:\d+\.\d+\.\d+\.\d+)?(/\d+)?", + Number, + ), # IPv6 + (r"\d+\.\d+\.\d+\.\d+(/\d+)?", Number), # IPv4 + (r"^([ \t]*)(no[ \t]+)?([-\w]+)", bygroups(Text, Keyword, Name.Function)), + (r"[ \t]+", Text), + (r"\n", Text), + (r"\d+", Number), + (r"\S+", Text), + ], + } diff --git a/doc/extra/spelling_wordlist.txt b/doc/extra/spelling_wordlist.txt new file mode 100644 index 0000000..271f5e4 --- /dev/null +++ b/doc/extra/spelling_wordlist.txt @@ -0,0 +1,243 @@ +abr +Admin +AFI +agentx +AgentX +APK +apps +ARP +ASes +ASv +autoconfiguration +Autoconfiguration +autoconfigured +autodetected +babeld +backtrace +backtraces +behaviour +bgp +bgpd +BGPD +blackhole +charon +cisco +Cisco +cli +clicmd +conf +config +Config +cr +crashlog +dataplane +de +deconfigured +deserialization +dev +distincts +dstmask +eBGP +eigrp +eigrpd +EOR +eth +ethernet +ethernets +extcommunity +failover +fallback +favour +filesystem +Flavel +frontend +frr +FRR +frrvty +gcc +ge +glibc +gre +hardcoded +holddown +Holddown +holdtime +honour +honoured +hostname +hsls +iBGP +ibm +ietf +igmp +inet +init +insta +interdomain +internet +ip +IP +iptables +ipv +IPv +IPvX +IPv4 +IPv6 +isis +isisd +lan +ldpd +le +libc +libcap +libexecinfo +Loc +localhost +logfile +loopback +lsa +LSA +lsas +LSAs +Masaki +Mbit +Mbits +macvlan +macvlans +mib +motd +mpls +mrib +mroute +mroutes +mrouting +mtu +multicast +Multicast +multicasting +multihop +multipath +Multipath +Multipoint +multiprotocol +Multiprotocol +namespace +nd +neighbour +neighbouring +neighbours +Netlink +netmask +netmasks +nexthop +nexthops +nflog +nhrp +NHRP +nhrpd +noauth +noninterfering +Noninterfering +nopassword +nve +NVE +opennhrp +optimisation +optimisations +ospf +OSPF +ospfd +pathing +pce +peerings +performant +php +pid +pim +pimd +ppp +pre +prepend +Prepend +prepended +proc +protobuf +Protobuf +PtP +ra +readonly +rebalance +reconverge +reestablish +reestimated +requestlist +reservable +Reservable +rfc +RFP +RIBs +ripd +Ripd +ripng +ripngd +Roughan +rp +rpki +rtt +RTT +Rxmt +µs +safi +setgid +sm +smux +smuxpeer +snmp +SNMP +snmpd +snmptrap +snmptrapd +Solaris +src +stateful +stdout +strftime +strongSwan +subagents +subsubsections +summarisation +summarise +summarising +supercedes +synchronisation +sysctl +syslogd +tc +te +Timestamp +tmp +trapsink +txt +unicast +Unicast +unix +urib +useable +username +usr +vertices +vici +vn +VN +VNC +vrf +vrfs +vrrp +vty +Vty +vtysh +wildcard +wildcarded +Wilfong +xDSL +xFF diff --git a/doc/figures/cli-change-client.drawio b/doc/figures/cli-change-client.drawio new file mode 100644 index 0000000..c7a68d4 --- /dev/null +++ b/doc/figures/cli-change-client.drawio @@ -0,0 +1,522 @@ +<mxfile host="Electron" modified="2023-06-19T07:55:43.434Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.2.8 Chrome/112.0.5615.165 Electron/24.2.0 Safari/537.36" etag="hHcr6k13KyEFOw_PaIFY" version="21.2.8" type="device"> + <diagram name="Page-1" id="58cdce13-f638-feb5-8d6f-7d28b1aa9fa0"> + <mxGraphModel dx="2074" dy="1264" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="1"> + <root> + <mxCell id="0" /> + <mxCell id="1" parent="0" /> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-239" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;jumpStyle=gap;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-28" target="nUYlmBzm2YxJIW5L2hvB-238" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="265.02000000000004" y="307.47999999999996" /> + <mxPoint x="265.02000000000004" y="307.47999999999996" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-240" value="copy of vty-&gt;cfg_changes<br>to protobuf msg" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-239" vertex="1" connectable="0"> + <mxGeometry x="-0.1005" relative="1" as="geometry"> + <mxPoint x="56" y="-15" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-80" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;jumpStyle=gap;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-77" target="nUYlmBzm2YxJIW5L2hvB-78" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="120.01999999999998" y="672.48" /> + <mxPoint x="120.01999999999998" y="672.48" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-11" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;strokeColor=#ff0000;labelBackgroundColor=none;endArrow=open;fontFamily=Verdana;align=left;entryX=0;entryY=0.5;entryDx=0;entryDy=0;jumpStyle=gap;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-1" target="nUYlmBzm2YxJIW5L2hvB-7" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="260" y="505" /> + <mxPoint x="260" y="505" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-34" value="N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-11" vertex="1" connectable="0"> + <mxGeometry x="-0.3317" y="1" relative="1" as="geometry"> + <mxPoint x="60" y="-14" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-15" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-1" target="nUYlmBzm2YxJIW5L2hvB-13" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="170.0200000000002" y="492.47999999999996" as="sourcePoint" /> + <mxPoint x="200.0200000000002" y="567.48" as="targetPoint" /> + <Array as="points"> + <mxPoint x="190.01999999999998" y="522.48" /> + <mxPoint x="190.01999999999998" y="567.48" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-35" value="N+1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-15" vertex="1" connectable="0"> + <mxGeometry x="-0.5391" relative="1" as="geometry"> + <mxPoint x="20" y="2" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-1" value="user cmd:<br>&nbsp;"ip route 10.0.0.0/24 null0"<br>-------------------------------<br><br>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="467.47999999999996" width="120" height="75" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-3" style="edgeStyle=orthogonalEdgeStyle;html=1;labelBackgroundColor=none;endArrow=open;endSize=8;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;align=left;entryX=0;entryY=0.5;entryDx=0;entryDy=0;jumpStyle=gap;exitX=1;exitY=0.25;exitDx=0;exitDy=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-1" target="nUYlmBzm2YxJIW5L2hvB-5" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="260.02000000000004" y="476.97999999999996" as="sourcePoint" /> + <mxPoint x="300.02000000000004" y="367.47999999999996" as="targetPoint" /> + <Array as="points"> + <mxPoint x="280.02000000000004" y="486.47999999999996" /> + <mxPoint x="280.02000000000004" y="397.47999999999996" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-33" value="1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-3" vertex="1" connectable="0"> + <mxGeometry x="-0.3723" y="-1" relative="1" as="geometry"> + <mxPoint x="36" y="-76" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-5" value="nb_cli_enqueue_change" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=10;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="345.02000000000004" y="377.47999999999996" width="130" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-7" value="nb_cli_enqueue_change" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=10;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="345.02000000000004" y="484.97999999999996" width="130" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-29" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;jumpStyle=gap;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-13" target="nUYlmBzm2YxJIW5L2hvB-28" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="210" y="345" /> + <mxPoint x="210" y="345" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-31" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-13" target="nUYlmBzm2YxJIW5L2hvB-27" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-32" value="<font style="font-size: 7px;">file or !mgmtd</font>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=7;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-31" vertex="1" connectable="0"> + <mxGeometry x="-0.3307" y="1" relative="1" as="geometry"> + <mxPoint x="11" y="-9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-13" value="nb_cli_apply_changes" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="210.01999999999998" y="547.48" width="100" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-14" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=4;rounded=1;labelBackgroundColor=none;strokeColor=#000000;fontFamily=Verdana;fontSize=12;fontColor=default;startSize=8;endSize=8;shape=connector;" parent="1" edge="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="409.62000000000006" y="467.47999999999996" as="sourcePoint" /> + <mxPoint x="409.62000000000006" y="427.47999999999996" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-17" value="" style="triangle;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;rotation=-90;" parent="1" vertex="1"> + <mxGeometry x="575" y="355" width="20" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-18" value="" style="triangle;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;rotation=-90;" parent="1" vertex="1"> + <mxGeometry x="575" y="385" width="20" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-19" value="" style="triangle;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;rotation=-90;" parent="1" vertex="1"> + <mxGeometry x="575" y="475" width="20" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-20" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=4;rounded=1;labelBackgroundColor=none;strokeColor=#000000;fontFamily=Verdana;fontSize=12;fontColor=default;startSize=8;endSize=8;shape=connector;" parent="1" edge="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="584.63" y="465" as="sourcePoint" /> + <mxPoint x="584.63" y="425" as="targetPoint" /> + <Array as="points"> + <mxPoint x="584.63" y="445" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-21" value="<font style="font-size: 10px;">candidate<br>ds</font>" style="shape=datastore;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="530" y="577.48" width="60" height="60" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-23" value="<font style="font-size: 10px;">candidate<br>ds</font>" style="shape=datastore;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="644.98" y="577.48" width="60" height="60" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-24" value="" style="shape=singleArrow;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="600" y="592.48" width="29.98" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-61" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-27" target="nUYlmBzm2YxJIW5L2hvB-59" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-27" value="nb_cli_apply_changes_internal" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="380.02000000000004" y="547.48" width="130" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-38" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-238" target="nUYlmBzm2YxJIW5L2hvB-37" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="825.02" y="272.47999999999996" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-39" value="<font style="font-size: 10px;"><i>socket connection</i><br>FE client -&gt; adapter SETCFG_REQ<br><br></font>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-38" vertex="1" connectable="0"> + <mxGeometry x="-0.0889" y="2" relative="1" as="geometry"> + <mxPoint x="-27" y="22" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-255" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-28" target="nUYlmBzm2YxJIW5L2hvB-246" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="230" y="200" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-256" value="implicit_commit<br style="font-size: 10px;">(legacy CLI)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-255" vertex="1" connectable="0"> + <mxGeometry x="-0.5348" y="-1" relative="1" as="geometry"> + <mxPoint x="-11" y="9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-28" value="vty_mgmt_send_config_data" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="200.01999999999998" y="327.47999999999996" width="130" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-238" value="mgmt_fe_send_setcfg_req" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="420.02000000000004" y="252.48000000000002" width="130" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-30" value="<font style="font-size: 7px;">mgmtd</font>" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=16;fontFamily=Verdana;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="235.01999999999998" y="517.48" width="50" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-36" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=4;rounded=1;labelBackgroundColor=none;strokeColor=#000000;fontFamily=Verdana;fontSize=12;fontColor=default;startSize=8;endSize=8;shape=connector;" parent="1" edge="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="294.62" y="472.43" as="sourcePoint" /> + <mxPoint x="294.62" y="432.43" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-41" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=doubleBlock;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-37" target="nUYlmBzm2YxJIW5L2hvB-40" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-42" value="<font style="font-size: 10px;">validates input and creates TXN (CONFIG)<br><i>can happen multiple times</i><br></font>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-41" vertex="1" connectable="0"> + <mxGeometry x="0.197" y="1" relative="1" as="geometry"> + <mxPoint x="114" y="-4" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-37" value="mgmt_fe_session_handle_setcfg_req_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="730.02" y="297.47999999999996" width="190" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-55" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-40" target="nUYlmBzm2YxJIW5L2hvB-44" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-56" value="<font style="font-size: 10px;">copy protobuf -&gt; txn_req.set_cfg.cfg_changes<br style="border-color: var(--border-color); font-size: 10px;"></font><span style="font-size: 10px;"><font style="font-size: 10px;">TIMER: MGMTD_TXN_PROC_SETCFG</font><br style="font-size: 10px;"></span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-55" vertex="1" connectable="0"> + <mxGeometry x="0.2852" y="-1" relative="1" as="geometry"> + <mxPoint x="126" y="-31" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-40" value="mgmt_txn_send_set_config_req" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="752.52" y="377.47999999999996" width="145" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-60" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-44" target="nUYlmBzm2YxJIW5L2hvB-59" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="675.02" y="547.48" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-128" value="1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-60" vertex="1" connectable="0"> + <mxGeometry x="-0.3733" y="3" relative="1" as="geometry"> + <mxPoint x="21" y="-13" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-69" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.436;entryY=0.026;entryDx=0;entryDy=0;entryPerimeter=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-44" target="nUYlmBzm2YxJIW5L2hvB-68" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-70" value="implicit_commit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-69" vertex="1" connectable="0"> + <mxGeometry x="-0.1764" y="-3" relative="1" as="geometry"> + <mxPoint x="48" y="-3" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-129" value="2" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-69" vertex="1" connectable="0"> + <mxGeometry x="-0.2682" y="-1" relative="1" as="geometry"> + <mxPoint x="-4" y="-11" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-72" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=baseDash;startSize=8;endSize=8;endFill=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-44" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="980.02" y="517.48" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-130" value="2" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-72" vertex="1" connectable="0"> + <mxGeometry x="-0.1117" y="-3" relative="1" as="geometry"> + <mxPoint x="-29" y="-13" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-44" value="<div>mgmt_txn_process_set_cfg</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="752.52" y="497.47999999999996" width="145" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-49" value="" style="shape=singleArrow;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="480.02" y="375" width="70" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-50" value="" style="shape=singleArrow;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;rotation=-180;" parent="1" vertex="1"> + <mxGeometry x="615" y="375" width="70" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-59" value="<div>nb_candidate_edit</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="542.5" y="524.98" width="105" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-64" value="struct<br>nb_cfg_change" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;fontSize=10;fontFamily=Verdana;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="535" y="315" width="100" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-68" value="<div>mgmt_txn_send_commit_config_req</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffe6cc;strokeColor=#d79b00;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;" parent="1" vertex="1"> + <mxGeometry x="752.52" y="592.48" width="167.5" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-74" value="user cmd:<br>&nbsp;"ip route 10.0.1.0/24 null0"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="550" width="100" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-75" value="user cmd:<br>&nbsp;"ip route 10.0.2.0/24 null0"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="70.01999999999998" y="560" width="100" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-76" value="user cmd:<br>&nbsp;"ip route 10.0.3.0/24 null0"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="80.01999999999998" y="570" width="100" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-77" value="user cmd:<br>"XFRR_end_configuration"<br>&nbsp;config or EOF" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;dashed=1;dashPattern=1 4;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="626.98" width="120" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-90" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-78" target="nUYlmBzm2YxJIW5L2hvB-84" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-78" value="vty_mgmt_send_commit_config" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="215.01999999999998" y="631.48" width="140" height="31" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-88" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-84" target="nUYlmBzm2YxJIW5L2hvB-87" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="540" y="715" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-89" value="<i style="font-size: 10px;">socket connection<br style="font-size: 10px;"></i>FE client -&gt; adapter COMMCFG_REQ" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-88" vertex="1" connectable="0"> + <mxGeometry x="-0.0463" y="1" relative="1" as="geometry"> + <mxPoint x="-34" y="30" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-84" value="mgmt_fe_send_commitcfg_req" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="215.01999999999998" y="730" width="140" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-93" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;entryX=0.166;entryY=0.994;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-87" target="nUYlmBzm2YxJIW5L2hvB-68" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-87" value="mgmt_fe_session_handle_commit_config_req_msg<br>create txn if none yet<br>if running DS not locked, lock" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="490.00000000000006" y="700" width="220" height="90" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-95" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-68" target="nUYlmBzm2YxJIW5L2hvB-159" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="835.02" y="632.48" as="sourcePoint" /> + <mxPoint x="883.7977777777774" y="718.48" as="targetPoint" /> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-96" value="<span style="font-size: 10px;">curr_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG<br style="font-size: 10px;"></span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-95" vertex="1" connectable="0"> + <mxGeometry x="0.2852" y="-1" relative="1" as="geometry"> + <mxPoint x="91" y="-21" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-131" value="<span style="font-size: 10px;">next_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG<br style="font-size: 10px;"></span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="989.9977193457571" y="669.9979556509891" as="geometry"> + <mxPoint x="-46" y="1" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-159" value="TIMER:<br style="font-size: 7px;">MGMTD_TXN_PROC_COMCFG" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=7;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1"> + <mxGeometry x="800.02" y="717.26" width="120" height="80" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-241" value="<i style="border-color: var(--border-color);">does nothing more</i>:<span style="font-size: 9px;"><br>when</span><b style="font-size: 9px;"> not implicit_commit:</b><br style="font-size: 9px;">&nbsp;<font face="Courier New"><b>mgmt (set|delete)-config</b></font> CLI<br style="font-size: 9px;">(no_implicit_commit == true)<br style="font-size: 9px;">inside <font face="Courier New"><b>XFRR_{start,end}_config</b></font><br style="font-size: 9px;">(pending_allowed == true)" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=9;fillColor=#eeeeee;strokeColor=#36393d;" parent="1" vertex="1"> + <mxGeometry x="940.02" y="472.42999999999995" width="140" height="100.05" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-243" value="user cmd:<br>"XFRR_start_configuration"<br>&nbsp;config file read indicator" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;dashed=1;dashPattern=1 4;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="417.47999999999996" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-257" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-245" target="nUYlmBzm2YxJIW5L2hvB-246" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="150" y="243" /> + <mxPoint x="200" y="243" /> + <mxPoint x="200" y="180" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-258" value="NO implicit commit<br style="font-size: 10px;">(vtysh -f file)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;horizontal=0;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-257" vertex="1" connectable="0"> + <mxGeometry x="-0.8771" y="-1" relative="1" as="geometry"> + <mxPoint x="9" y="-41" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-245" value="user cmd:<br>"configure terminal"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=default;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;strokeWidth=1;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="367.47999999999996" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-248" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;strokeWidth=2;fillColor=#fa6800;startArrow=open;startFill=0;shadow=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-246" target="nUYlmBzm2YxJIW5L2hvB-247" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="625" y="190" as="targetPoint" /> + <Array as="points"> + <mxPoint x="585" y="193" /> + <mxPoint x="585" y="193" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-250" value="<i>socket connection<br style="font-size: 9px;"></i>FE client -&gt; adapter LOCKDS_REQ" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=9;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-248" vertex="1" connectable="0"> + <mxGeometry x="-0.0567" y="1" relative="1" as="geometry"> + <mxPoint x="5" y="-16" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-246" value="vty_mgmt_lock_cand_inline" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="380.02" y="172.48" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-247" value="LOCK CANDIDATE" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffe6cc;strokeColor=#d79b00;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;" parent="1" vertex="1"> + <mxGeometry x="680" y="175.00000000000003" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-252" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-251" target="nUYlmBzm2YxJIW5L2hvB-245" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-253" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;exitX=0.248;exitY=0.923;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-251" target="nUYlmBzm2YxJIW5L2hvB-243" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="90.01999999999998" y="322.47999999999996" /> + <mxPoint x="50.019999999999996" y="322.47999999999996" /> + <mxPoint x="50.019999999999996" y="443.47999999999996" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-254" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;exitX=0.088;exitY=0.793;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-251" target="nUYlmBzm2YxJIW5L2hvB-1" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="60" y="510" as="targetPoint" /> + <Array as="points"> + <mxPoint x="71" y="253" /> + <mxPoint x="40" y="253" /> + <mxPoint x="40" y="513" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-251" value="EVENT: VTYSH_READ" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=7;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999999" y="142.48000000000002" width="120" height="80" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-260" value="UNLOCK CANDIDATE" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffe6cc;strokeColor=#d79b00;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;" parent="1" vertex="1"> + <mxGeometry x="680" y="120.00000000000001" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-265" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-261" target="nUYlmBzm2YxJIW5L2hvB-262" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="10" y="707" /> + <mxPoint x="10" y="130" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-266" value="NO implicit commit" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;horizontal=0;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-265" vertex="1" connectable="0"> + <mxGeometry x="-0.781" y="-1" relative="1" as="geometry"> + <mxPoint x="9" y="-12" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-261" value="user cmd:<br>"end/exit"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=default;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;strokeWidth=1;" parent="1" vertex="1"> + <mxGeometry x="60.01999999999998" y="690" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-262" value="vty_mgmt_lock_cand_inline" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="380.02" y="120" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-275" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-270" target="nUYlmBzm2YxJIW5L2hvB-268" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="300" y="35" /> + <mxPoint x="300" y="35" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-270" value="vty_mgmt_set_config_result_notified" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="360" y="11.25" width="180" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-263" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;strokeWidth=2;fillColor=#fa6800;startArrow=open;startFill=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-262" target="nUYlmBzm2YxJIW5L2hvB-260" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="500.02" y="140" as="sourcePoint" /> + <mxPoint x="680.02" y="140" as="targetPoint" /> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-264" value="<i>socket connection<br style="font-size: 9px;"></i>FE client -&gt; adapter LOCKDS_REQ" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=9;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-263" vertex="1" connectable="0"> + <mxGeometry x="-0.0567" y="1" relative="1" as="geometry"> + <mxPoint x="5" y="-16" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-272" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-267" target="nUYlmBzm2YxJIW5L2hvB-271" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="630" y="76" /> + <mxPoint x="630" y="76" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-273" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-267" target="nUYlmBzm2YxJIW5L2hvB-270" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="630" y="40" /> + <mxPoint x="630" y="40" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-267" value="EVENT: REPLY NOTIFICATIONS" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=7;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1"> + <mxGeometry x="660" y="5" width="120" height="80" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-269" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;jumpStyle=gap;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-268" target="nUYlmBzm2YxJIW5L2hvB-251" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="80" y="80" /> + <mxPoint x="80" y="80" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-268" value="<div>VTYSH</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#cdeb8b;strokeColor=#36393d;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;" parent="1" vertex="1"> + <mxGeometry x="30" y="15" width="77.48" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-274" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-271" target="nUYlmBzm2YxJIW5L2hvB-268" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="330" y="76" /> + <mxPoint x="330" y="50" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-271" value="vty_mgmt_commit_config_result_notified" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="360" y="58.75" width="180" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-292" value="" style="group" parent="1" vertex="1" connectable="0"> + <mxGeometry x="950" y="710" width="140" height="130" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-284" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="nUYlmBzm2YxJIW5L2hvB-292" vertex="1"> + <mxGeometry width="140" height="130" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-278" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;" parent="nUYlmBzm2YxJIW5L2hvB-292" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="20" y="60" as="sourcePoint" /> + <mxPoint x="110.01999999999998" y="60" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-279" value="<i style="font-size: 10px;">socket&nbsp;</i>async" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-278" vertex="1" connectable="0"> + <mxGeometry x="-0.0463" y="1" relative="1" as="geometry"> + <mxPoint x="-8" y="-9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-282" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#0050ef;" parent="nUYlmBzm2YxJIW5L2hvB-292" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="20" y="90" as="sourcePoint" /> + <mxPoint x="110" y="90" as="targetPoint" /> + <Array as="points"> + <mxPoint x="50" y="89.77000000000001" /> + <mxPoint x="50" y="89.77000000000001" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-283" value="<span style="font-size: 10px;"><font style="font-size: 10px;">timer/event&nbsp;</font>async<br style="font-size: 10px;"></span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-282" vertex="1" connectable="0"> + <mxGeometry x="0.2852" y="-1" relative="1" as="geometry"> + <mxPoint x="-28" y="-11" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-285" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;strokeWidth=2;fillColor=#fa6800;startArrow=open;startFill=0;" parent="nUYlmBzm2YxJIW5L2hvB-292" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="20" y="35" as="sourcePoint" /> + <mxPoint x="110" y="35" as="targetPoint" /> + <Array as="points"> + <mxPoint x="20" y="35" /> + <mxPoint x="20" y="35" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-286" value="<i>socket&nbsp; sync (short-circuit)<br></i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=9;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-285" vertex="1" connectable="0"> + <mxGeometry x="-0.0567" y="1" relative="1" as="geometry"> + <mxPoint x="5" y="-16" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-287" value="" style="endArrow=open;html=1;rounded=1;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=12;fontColor=default;startSize=8;endSize=8;shape=connector;" parent="nUYlmBzm2YxJIW5L2hvB-292" edge="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="20" y="120" as="sourcePoint" /> + <mxPoint x="105" y="119.19999999999999" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-288" value="function sync" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=9;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-287" vertex="1" connectable="0"> + <mxGeometry x="-0.26" y="2" relative="1" as="geometry"> + <mxPoint x="6" y="-8" as="offset" /> + </mxGeometry> + </mxCell> + </root> + </mxGraphModel> + </diagram> +</mxfile> diff --git a/doc/figures/cli-change-client.svg b/doc/figures/cli-change-client.svg new file mode 100644 index 0000000..9194f24 --- /dev/null +++ b/doc/figures/cli-change-client.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1099" height="842" viewBox="-0.5 -0.5 1099 842" style="background-color: rgb(255, 255, 255);"><defs><filter id="dropShadow"><feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"/><feOffset in="blur" dx="3" dy="3" result="offsetBlur"/><feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"/><feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"/><feBlend in="SourceGraphic" in2="offsetBlur"/></filter></defs><g filter="url(#dropShadow)"><path d="M 264 322.48 L 264 277.5 Q 264 267.5 274 267.5 L 416.78 267.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 408.9 272 L 417.9 267.5 L 408.9 263" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 253px; margin-left: 360px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">copy of vty->cfg_changes<br />to protobuf msg</div></div></div></foreignObject><text x="360" y="256" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">copy of vty->cfg_changes...</text></switch></g><path d="M 119 661.98 L 119 664.74 Q 119 667.5 119 657.5 L 119 649.75 Q 119 642 129 642 L 211.78 642" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 203.9 646.5 L 212.9 642 L 203.9 637.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 179.02 499.98 L 341.78 499.98" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 333.9 504.48 L 342.9 499.98 L 333.9 495.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 485px; margin-left: 295px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">N</div></div></div></foreignObject><text x="295" y="489" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">N</text></switch></g><path d="M 179.02 517.5 L 184.01 517.5 Q 189 517.5 189 527.5 L 189 552.5 Q 189 562.5 197.89 562.49 L 206.78 562.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 198.91 566.99 L 207.9 562.48 L 198.9 557.99" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 527px; margin-left: 210px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">N+1</div></div></div></foreignObject><text x="210" y="531" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">N+1</text></switch></g><rect x="59.02" y="462.48" width="120" height="75" rx="18" ry="18" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 500px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br /> "ip route 10.0.0.0/24 null0"<br />-------------------------------<br /><br /></div></div></div></foreignObject><text x="119" y="502" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><path d="M 179.02 481.23 L 269 481.2 Q 279 481.2 279 471.2 L 279 402.5 Q 279 392.5 289 392.5 L 341.78 392.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 333.9 396.98 L 342.9 392.48 L 333.9 387.98" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 407px; margin-left: 296px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">1</div></div></div></foreignObject><text x="296" y="410" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">1</text></switch></g><rect x="344.02" y="372.48" width="130" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 392px; margin-left: 345px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">nb_cli_enqueue_change</div></div></div></foreignObject><text x="409" y="395" fill="#000000" font-family="Verdana" font-size="10px" text-anchor="middle">nb_cli_enqueue_change</text></switch></g><rect x="344.02" y="479.98" width="130" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 500px; margin-left: 345px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">nb_cli_enqueue_change</div></div></div></foreignObject><text x="409" y="503" fill="#000000" font-family="Verdana" font-size="10px" text-anchor="middle">nb_cli_enqueue_change</text></switch></g><path d="M 259.02 542.48 L 259.02 502.98 M 259.02 496.98 M 259.02 496.98 L 259.02 484.21 M 259.02 478.21 M 259.02 478.21 L 259.02 364.72" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 263.52 372.6 L 259.02 363.6 L 254.52 372.6" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 309.02 562.48 L 376.78 562.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 368.9 566.98 L 377.9 562.48 L 368.9 557.98" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 553px; margin-left: 344px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><font style="font-size: 7px;">file or !mgmtd</font></div></div></div></foreignObject><text x="344" y="555" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">file or !mgmtd</text></switch></g><rect x="209.02" y="542.48" width="100" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 562px; margin-left: 210px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">nb_cli_apply_changes</div></div></div></foreignObject><text x="259" y="565" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">nb_cli_apply_changes</text></switch></g><path d="M 408.62 462.48 L 408.62 422.48" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" stroke-dasharray="4 12" pointer-events="stroke"/><path d="M 574 350 L 594 365 L 574 380 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="rotate(-90,584,365)" pointer-events="all"/><path d="M 574 380 L 594 395 L 574 410 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="rotate(-90,584,395)" pointer-events="all"/><path d="M 574 470 L 594 485 L 574 500 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="rotate(-90,584,485)" pointer-events="all"/><path d="M 583.63 460 L 583.63 450 Q 583.63 440 583.63 430 L 583.63 420" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" stroke-dasharray="4 12" pointer-events="stroke"/><path d="M 529 580.48 C 529 569.81 589 569.81 589 580.48 L 589 624.48 C 589 635.15 529 635.15 529 624.48 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 529 580.48 C 529 588.48 589 588.48 589 580.48 M 529 584.48 C 529 592.48 589 592.48 589 584.48 M 529 588.48 C 529 596.48 589 596.48 589 588.48" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 612px; margin-left: 530px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 10px;">candidate<br />ds</font></div></div></div></foreignObject><text x="559" y="616" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">candidate...</text></switch></g><path d="M 643.98 580.48 C 643.98 569.81 703.98 569.81 703.98 580.48 L 703.98 624.48 C 703.98 635.15 643.98 635.15 643.98 624.48 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 643.98 580.48 C 643.98 588.48 703.98 588.48 703.98 580.48 M 643.98 584.48 C 643.98 592.48 703.98 592.48 703.98 584.48 M 643.98 588.48 C 643.98 596.48 703.98 596.48 703.98 588.48" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 612px; margin-left: 645px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><font style="font-size: 10px;">candidate<br />ds</font></div></div></div></foreignObject><text x="674" y="616" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">candidate...</text></switch></g><path d="M 599 597.98 L 622.98 597.98 L 622.98 587.48 L 628.98 602.48 L 622.98 617.48 L 622.98 606.98 L 599 606.98 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 509.02 562.5 L 519.01 562.5 Q 529 562.5 529 552.5 L 529 546.25 Q 529 540 534.13 539.99 L 539.26 539.98" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 531.39 544.5 L 540.38 539.98 L 531.37 535.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="379.02" y="542.48" width="130" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 562px; margin-left: 380px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">nb_cli_apply_changes_internal</div></div></div></foreignObject><text x="444" y="565" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">nb_cli_apply_changes_internal</text></switch></g><path d="M 549.02 267.5 L 814 267.5 Q 824 267.5 824.01 277.5 L 824.02 288.01" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 819.01 280.25 L 824.02 290.24 L 829.01 280.24" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 288px; margin-left: 660px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><font style="font-size: 10px;"><i>socket connection</i><br />FE client -> adapter SETCFG_REQ<br /><br /></font></div></div></div></foreignObject><text x="660" y="291" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket connection...</text></switch></g><path d="M 229 322.48 L 229 205 Q 229 195 239 195 L 376.78 195" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 368.9 199.5 L 377.9 195 L 368.9 190.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)rotate(-90 219.5 265.98)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 266px; margin-left: 220px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">implicit_commit<br style="font-size: 10px;" />(legacy CLI)</div></div></div></foreignObject><text x="220" y="269" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">implicit_commit...</text></switch></g><rect x="199.02" y="322.48" width="130" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 342px; margin-left: 200px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_send_config_data</div></div></div></foreignObject><text x="264" y="345" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_send_config_data</text></switch></g><rect x="419.02" y="247.48" width="130" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 267px; margin-left: 420px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_fe_send_setcfg_req</div></div></div></foreignObject><text x="484" y="270" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_fe_send_setcfg_req</text></switch></g><rect x="234.02" y="512.48" width="50" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 527px; margin-left: 259px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 16px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><font style="font-size: 7px;">mgmtd</font></div></div></div></foreignObject><text x="259" y="532" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="16px" text-anchor="middle">mgmtd</text></switch></g><path d="M 293.62 467.43 L 293.62 427.43" fill="none" stroke="#000000" stroke-width="4" stroke-miterlimit="10" stroke-dasharray="4 12" pointer-events="stroke"/><path d="M 824.02 332.48 L 824.02 357.36" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 824.02 371.36 L 820.52 364.36 L 827.52 364.36 Z M 824.02 364.36 L 820.52 357.36 L 827.52 357.36 Z" fill="#ff0000" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 353px; margin-left: 940px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><font style="font-size: 10px;">validates input and creates TXN (CONFIG)<br /><i>can happen multiple times</i><br /></font></div></div></div></foreignObject><text x="940" y="356" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">validates input and creates TXN (CONFIG)...</text></switch></g><rect x="729.02" y="292.48" width="190" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 188px; height: 1px; padding-top: 312px; margin-left: 730px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_fe_session_handle_setcfg_req_msg</div></div></div></foreignObject><text x="824" y="315" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_fe_session_handle_setcfg_req_msg</text></switch></g><path d="M 824.02 412.48 L 824.02 488.01" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 819.02 480.24 L 824.02 490.24 L 829.02 480.24" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 433px; margin-left: 950px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><font style="font-size: 10px;">copy protobuf -> txn_req.set_cfg.cfg_changes<br style="border-color: var(--border-color); font-size: 10px;" /></font><span style="font-size: 10px;"><font style="font-size: 10px;">TIMER: MGMTD_TXN_PROC_SETCFG</font><br style="font-size: 10px;" /></span></div></div></div></foreignObject><text x="950" y="436" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">copy protobuf -> txn_req.set_cfg.cfg_changes...</text></switch></g><rect x="751.52" y="372.48" width="145" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 392px; margin-left: 753px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_set_config_req</div></div></div></foreignObject><text x="824" y="395" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_set_config_req</text></switch></g><path d="M 751.52 512.5 L 709 512.5 Q 699 512.5 699 522.5 L 699 531.25 Q 699 540 689 540 L 648.74 540" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 656.62 535.5 L 647.62 540 L 656.62 544.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 503px; margin-left: 731px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">1</div></div></div></foreignObject><text x="731" y="507" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">1</text></switch></g><path d="M 824 532.48 L 824 550 Q 824 560 824.19 570 L 824.51 586.28" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 819.86 578.49 L 824.53 587.4 L 828.85 578.32" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 553px; margin-left: 870px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">implicit_commit</div></div></div></foreignObject><text x="870" y="556" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">implicit_commit</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 543px; margin-left: 820px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">2</div></div></div></foreignObject><text x="820" y="547" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">2</text></switch></g><path d="M 896.52 512.5 L 927.8 512.5 Q 937.8 512.5 947.8 512.5 L 979.02 512.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 979.02 517.48 L 979.02 507.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 503px; margin-left: 905px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">2</div></div></div></foreignObject><text x="905" y="507" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle">2</text></switch></g><rect x="751.52" y="492.48" width="145" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 512px; margin-left: 753px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>mgmt_txn_process_set_cfg</div></div></div></div></foreignObject><text x="824" y="515" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_process_set_cfg</text></switch></g><path d="M 479.02 384 L 535.02 384 L 535.02 370 L 549.02 390 L 535.02 410 L 535.02 396 L 479.02 396 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 614 384 L 670 384 L 670 370 L 684 390 L 670 410 L 670 396 L 614 396 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="rotate(-180,649,390)" pointer-events="all"/><rect x="541.5" y="519.98" width="105" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 103px; height: 1px; padding-top: 540px; margin-left: 543px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>nb_candidate_edit</div></div></div></div></foreignObject><text x="594" y="542" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">nb_candidate_edit</text></switch></g><rect x="534" y="310" width="100" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 330px; margin-left: 584px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">struct<br />nb_cfg_change</div></div></div></foreignObject><text x="584" y="333" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">struct...</text></switch></g><rect x="751.52" y="587.48" width="167.5" height="40" rx="9.6" ry="9.6" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 607px; margin-left: 753px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>mgmt_txn_send_commit_config_req</div></div></div></div></foreignObject><text x="835" y="610" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_commit_config_req</text></switch></g><rect x="59.02" y="545" width="100" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 565px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br /> "ip route 10.0.1.0/24 null0"</div></div></div></foreignObject><text x="109" y="567" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><rect x="69.02" y="555" width="100" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 575px; margin-left: 70px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br /> "ip route 10.0.2.0/24 null0"</div></div></div></foreignObject><text x="119" y="577" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><rect x="79.02" y="565" width="100" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 585px; margin-left: 80px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br /> "ip route 10.0.3.0/24 null0"</div></div></div></foreignObject><text x="129" y="587" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><rect x="59.02" y="621.98" width="120" height="40" rx="9.6" ry="9.6" fill="#ffffc0" stroke="#ff0000" stroke-dasharray="1 4" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 642px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br />"XFRR_end_configuration"<br /> config or EOF</div></div></div></foreignObject><text x="119" y="644" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><path d="M 284.02 657.48 L 284.02 722.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 279.52 714.88 L 284.02 723.88 L 288.52 714.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="214.02" y="626.48" width="140" height="31" rx="7.44" ry="7.44" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 642px; margin-left: 215px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_send_commit_config</div></div></div></foreignObject><text x="284" y="644" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_send_commit_config</text></switch></g><path d="M 354.02 740 L 484.53 740" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 476.76 745 L 486.76 740 L 476.76 735" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 770px; margin-left: 385px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="font-size: 10px;">socket connection<br style="font-size: 10px;" /></i>FE client -> adapter COMMCFG_REQ</div></div></div></foreignObject><text x="385" y="773" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket connection...</text></switch></g><rect x="214.02" y="725" width="140" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 740px; margin-left: 215px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_fe_send_commitcfg_req</div></div></div></foreignObject><text x="284" y="742" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_fe_send_commitcfg_req</text></switch></g><path d="M 709 740 L 769.3 740 Q 779.3 740 779.3 730 L 779.32 629.48" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 783.82 637.36 L 779.32 628.36 L 774.82 637.36" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="489" y="695" width="220" height="90" rx="21.6" ry="21.6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 218px; height: 1px; padding-top: 740px; margin-left: 490px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_fe_session_handle_commit_config_req_msg<br />create txn if none yet<br />if running DS not locked, lock</div></div></div></foreignObject><text x="599" y="742" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_fe_session_handle_commit_config_req_msg...</text></switch></g><path d="M 835.3 627.48 L 835.3 659.9 Q 835.3 669.9 845.3 669.9 L 852.15 669.9 Q 859 669.9 859 679.9 L 859.02 710.02" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 854.52 702.14 L 859.02 711.14 L 863.52 702.14" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 653px; margin-left: 950px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><span style="font-size: 10px;">curr_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG<br style="font-size: 10px;" /></span></div></div></div></foreignObject><text x="950" y="656" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">curr_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG </text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 666px; margin-left: 943px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><span style="font-size: 10px;">next_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG<br style="font-size: 10px;" /></span></div></div></div></foreignObject><text x="943" y="669" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">next_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG </text></switch></g><ellipse cx="859.02" cy="752.26" rx="60" ry="40" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 752px; margin-left: 800px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">TIMER:<br style="font-size: 7px;" />MGMTD_TXN_PROC_COMCFG</div></div></div></foreignObject><text x="859" y="754" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">TIMER:...</text></switch></g><rect x="939.02" y="467.43" width="140" height="100.05" fill="#eeeeee" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 517px; margin-left: 940px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><i style="border-color: var(--border-color);">does nothing more</i>:<span style="font-size: 9px;"><br />when</span><b style="font-size: 9px;"> not implicit_commit:</b><br style="font-size: 9px;" /> <font face="Courier New"><b>mgmt (set|delete)-config</b></font> CLI<br style="font-size: 9px;" />(no_implicit_commit == true)<br style="font-size: 9px;" />inside <font face="Courier New"><b>XFRR_{start,end}_config</b></font><br style="font-size: 9px;" />(pending_allowed == true)</div></div></div></foreignObject><text x="1009" y="520" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">does nothing more:...</text></switch></g><rect x="59.02" y="412.48" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="#ff0000" stroke-dasharray="1 4" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 430px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br />"XFRR_start_configuration"<br /> config file read indicator</div></div></div></foreignObject><text x="119" y="432" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><path d="M 149 362.48 L 149 248 Q 149 238 159 238 L 189 238 Q 199 238 199 228 L 199 185 Q 199 175 209 175 L 376.78 175" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 368.9 179.5 L 377.9 175 L 368.9 170.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)rotate(-90 159.5 294.98)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 295px; margin-left: 160px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">NO implicit commit<br style="font-size: 10px;" />(vtysh -f file)</div></div></div></foreignObject><text x="160" y="298" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">NO implicit commit...</text></switch></g><rect x="59.02" y="362.48" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 380px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br />"configure terminal"</div></div></div></foreignObject><text x="119" y="382" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><path d="M 503.49 188 L 574 188 Q 584 188 594 188 L 674.53 188" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 511.26 183 L 501.26 188 L 511.26 193" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 666.76 193 L 676.76 188 L 666.76 183" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 172px; margin-left: 590px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i>socket connection<br style="font-size: 9px;" /></i>FE client -> adapter LOCKDS_REQ</div></div></div></foreignObject><text x="590" y="174" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">socket connection...</text></switch></g><rect x="379.02" y="167.48" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 185px; margin-left: 380px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_lock_cand_inline</div></div></div></foreignObject><text x="439" y="187" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_lock_cand_inline</text></switch></g><rect x="679" y="170" width="120" height="35" rx="8.4" ry="8.4" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 188px; margin-left: 680px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">LOCK CANDIDATE</div></div></div></foreignObject><text x="739" y="190" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">LOCK CANDIDATE</text></switch></g><path d="M 119.02 217.48 L 119.02 360.24" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 114.52 352.36 L 119.02 361.36 L 123.52 352.36" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 88.78 211.32 L 88.8 307.5 Q 88.8 317.5 78.8 317.5 L 59 317.5 Q 49 317.5 49 327.5 L 49 428.7 Q 49 438.7 52.89 438.71 L 56.78 438.72" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 48.89 443.2 L 57.9 438.73 L 48.92 434.2" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 69.58 200.92 L 69.6 238 Q 69.6 248 59.6 248 L 49 248 Q 39 248 39 258 L 39 498 Q 39 508 47.89 508 L 56.78 508" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 48.9 512.5 L 57.9 508 L 48.9 503.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="119.02" cy="177.48" rx="60" ry="40" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 177px; margin-left: 60px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">EVENT: VTYSH_READ</div></div></div></foreignObject><text x="119" y="180" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">EVENT: VTYSH_READ</text></switch></g><rect x="679" y="115" width="120" height="35" rx="8.4" ry="8.4" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 133px; margin-left: 680px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">UNLOCK CANDIDATE</div></div></div></foreignObject><text x="739" y="135" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">UNLOCK CANDIDATE</text></switch></g><path d="M 59.02 702.5 L 19 702.5 Q 9 702.5 9 692.5 L 9 135 Q 9 125 19 125 L 376.78 125" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 368.9 129.5 L 377.9 125 L 368.9 120.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)rotate(-90 19.5 631.02)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 631px; margin-left: 20px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">NO implicit commit</div></div></div></foreignObject><text x="20" y="634" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">NO implicit commit</text></switch></g><rect x="59.02" y="685" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 703px; margin-left: 60px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">user cmd:<br />"end/exit"</div></div></div></foreignObject><text x="119" y="705" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">user cmd:...</text></switch></g><rect x="379.02" y="115" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 133px; margin-left: 380px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_lock_cand_inline</div></div></div></foreignObject><text x="439" y="135" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_lock_cand_inline</text></switch></g><path d="M 359 30 L 309 30 Q 299 30 289 30 L 31.24 30" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 39.12 25.5 L 30.12 30 L 39.12 34.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="359" y="6.25" width="180" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 24px; margin-left: 360px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_set_config_result_notified</div></div></div></foreignObject><text x="449" y="26" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_set_config_result_notified</text></switch></g><path d="M 503.49 132.5 L 674.53 132.5" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><path d="M 511.26 127.5 L 501.26 132.5 L 511.26 137.5" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 666.76 137.5 L 676.76 132.5 L 666.76 127.5" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 116px; margin-left: 590px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i>socket connection<br style="font-size: 9px;" /></i>FE client -> adapter LOCKDS_REQ</div></div></div></foreignObject><text x="590" y="119" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">socket connection...</text></switch></g><path d="M 681.08 71 L 639 71 Q 629 71 619 71 L 541.24 71" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 549.12 66.5 L 540.12 71 L 549.12 75.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 659.47 35 L 639 35 Q 629 35 619 35 L 541.24 35" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 549.12 30.5 L 540.12 35 L 549.12 39.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="719" cy="40" rx="60" ry="40" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 40px; margin-left: 660px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">EVENT: REPLY NOTIFICATIONS</div></div></div></foreignObject><text x="719" y="42" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">EVENT: REPLY NOTIFICATIONS</text></switch></g><path d="M 79 50 L 79 122 M 79 128 M 79 128 L 79 145.44" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 74.5 137.56 L 79 146.56 L 83.5 137.56" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="29" y="10" width="77.48" height="40" rx="9.6" ry="9.6" fill="#cdeb8b" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 75px; height: 1px; padding-top: 30px; margin-left: 30px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>VTYSH</div></div></div></div></foreignObject><text x="68" y="32" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">VTYSH</text></switch></g><path d="M 359 71.3 L 339 71.3 Q 329 71.3 329 61.3 L 329 53.15 Q 329 45 319 45 L 108.72 45" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 116.6 40.5 L 107.6 45 L 116.6 49.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="359" y="53.75" width="180" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 71px; margin-left: 360px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_commit_config_result_notified</div></div></div></foreignObject><text x="449" y="74" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_commit_config_result_notified</text></switch></g><rect x="949" y="705" width="140" height="130" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="none"/><path d="M 969 765 L 1054.55 765" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="none"/><path d="M 1046.78 770 L 1056.78 765 L 1046.78 760" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 756px; margin-left: 1005px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: nowrap;"><i style="font-size: 10px;">socket </i>async</div></div></div></foreignObject><text x="1005" y="759" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket async</text></switch></g><path d="M 969 795 L 989 795 Q 999 795 1009 795 L 1054.53 795" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="none"/><path d="M 1046.76 800 L 1056.76 795 L 1046.76 790" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 786px; margin-left: 1000px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: nowrap;"><span style="font-size: 10px;"><font style="font-size: 10px;">timer/event </font>async<br style="font-size: 10px;" /></span></div></div></div></foreignObject><text x="1000" y="789" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">timer/event async </text></switch></g><path d="M 973.47 740 L 1054.53 740" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="none"/><path d="M 981.24 735 L 971.24 740 L 981.24 745" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><path d="M 1046.76 745 L 1056.76 740 L 1046.76 735" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 724px; margin-left: 1017px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: nowrap;"><i>socket  sync (short-circuit)<br /></i></div></div></div></foreignObject><text x="1017" y="726" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">socket  sync (short-circuit) </text></switch></g><path d="M 969 825 L 1051.76 824.22" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/><path d="M 1043.92 828.8 L 1052.88 824.21 L 1043.84 819.8" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="none"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 815px; margin-left: 1006px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: nowrap;">function sync</div></div></div></foreignObject><text x="1006" y="818" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">function sync</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file diff --git a/doc/figures/cli-change-mgmtd.drawio b/doc/figures/cli-change-mgmtd.drawio new file mode 100644 index 0000000..e8beade --- /dev/null +++ b/doc/figures/cli-change-mgmtd.drawio @@ -0,0 +1,421 @@ +<mxfile host="Electron" modified="2023-06-19T08:43:10.542Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.4.0 Chrome/112.0.5615.204 Electron/24.5.1 Safari/537.36" etag="nT5OZWDjYXR5quOjpvZj" version="21.4.0" type="device"> + <diagram name="Page-1" id="58cdce13-f638-feb5-8d6f-7d28b1aa9fa0"> + <mxGraphModel dx="974" dy="1264" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="1"> + <root> + <mxCell id="0" /> + <mxCell id="1" parent="0" /> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-158" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;jumpStyle=gap;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-65" target="nUYlmBzm2YxJIW5L2hvB-157" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-150" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;startArrow=none;startFill=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-65" target="nUYlmBzm2YxJIW5L2hvB-148" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1350" y="320" /> + <mxPoint x="1350" y="320" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-65" value="mgmt_txn_prepare_cfg" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1280" y="279.78000000000003" width="145" height="20.44" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-160" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-217" target="nUYlmBzm2YxJIW5L2hvB-161" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1920" y="380" as="targetPoint" /> + <Array as="points"> + <mxPoint x="1607" y="590" /> + <mxPoint x="1840" y="590" /> + <mxPoint x="1840" y="660" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-174" value="MESSAGE_TXN_REQ create" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-160" vertex="1" connectable="0"> + <mxGeometry x="-0.5683" relative="1" as="geometry"> + <mxPoint x="-17" y="-10" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-218" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-66" target="nUYlmBzm2YxJIW5L2hvB-217" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1490" y="590" /> + <mxPoint x="1490" y="590" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-66" value="mgmt_txn_send_be_txn_create" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1280" y="580" width="145" height="20" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-217" value="mgmt_be_send_txn_req" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1505" y="581" width="145" height="20" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-203" value="does nothing cfg_data replys will cause next transition " style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#eeeeee;strokeColor=#36393d;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1240" y="640" width="180" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-214" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-67" target="nUYlmBzm2YxJIW5L2hvB-213" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-216" value="next_phase =  PHASE_TXN_DELETE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-214" vertex="1" connectable="0"> + <mxGeometry x="-0.2492" y="-1" relative="1" as="geometry"> + <mxPoint x="10" y="19" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-67" value="mgmt_txn_send_be_cfg_apply" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1200" y="709.9999999999999" width="145" height="20" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-213" value="mgmt_be_send_cfgapply_req" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1440" y="709.9999999999999" width="145" height="20" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-111" value="mgmt_txn_send_commit_cfg_reply" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="1161.25" y="769.9999999999999" width="145" height="20" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-140" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-94" target="nUYlmBzm2YxJIW5L2hvB-66" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1270" y="590" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-145" value="PHASE_TXN_CREATE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-140" vertex="1" connectable="0"> + <mxGeometry x="0.2148" y="-2" relative="1" as="geometry"> + <mxPoint x="49" y="112" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-141" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-94" target="nUYlmBzm2YxJIW5L2hvB-65" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1300" y="260" /> + <mxPoint x="1353" y="260" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-144" value="PHASE_PREPARE_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-141" vertex="1" connectable="0"> + <mxGeometry x="-0.1955" y="3" relative="1" as="geometry"> + <mxPoint x="13" y="-7" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-142" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-94" target="nUYlmBzm2YxJIW5L2hvB-67" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1210" y="340" /> + <mxPoint x="1210" y="340" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-146" value="PHASE_CFG_APPLY" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-142" vertex="1" connectable="0"> + <mxGeometry x="0.6696" y="2" relative="1" as="geometry"> + <mxPoint x="48" y="68" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-143" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-94" target="nUYlmBzm2YxJIW5L2hvB-111" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1180" y="380" /> + <mxPoint x="1180" y="380" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-147" value="PHASE_TXN_DELETE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-143" vertex="1" connectable="0"> + <mxGeometry x="0.7799" y="3" relative="1" as="geometry"> + <mxPoint x="51" y="48" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-204" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-94" target="nUYlmBzm2YxJIW5L2hvB-203" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1250" y="340" /> + <mxPoint x="1250" y="340" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-205" value="PHASE_SEND_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-204" vertex="1" connectable="0"> + <mxGeometry x="0.857" y="3" relative="1" as="geometry"> + <mxPoint x="37" y="18" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-94" value="mgmt_txn_process_commit_cfg" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1150" y="170" width="167.5" height="70" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-97" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-65" target="nUYlmBzm2YxJIW5L2hvB-138" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1360" y="281" as="sourcePoint" /> + <mxPoint x="1318" y="225" as="targetPoint" /> + <Array as="points"> + <mxPoint x="1580" y="290" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-113" value="curr_phase = PHASE_TXN_CREATE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-97" vertex="1" connectable="0"> + <mxGeometry x="0.2534" y="-1" relative="1" as="geometry"> + <mxPoint x="-101" y="35" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-119" value="mgmt_txn_send_commit_config_req" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffe6cc;strokeColor=#d79b00;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1150" y="50" width="167.5" height="40" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-122" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-119" target="nUYlmBzm2YxJIW5L2hvB-138" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="826" y="435" as="sourcePoint" /> + <mxPoint x="824" y="521" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-123" value="curr_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG " style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-122" vertex="1" connectable="0"> + <mxGeometry x="0.2852" y="-1" relative="1" as="geometry"> + <mxPoint x="-23" y="-21" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-132" value="next_phase == MGMTD_COMMIT_PHASE_PREPARE_CFG " style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="1425" y="89.99851851851847" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-139" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-138" target="nUYlmBzm2YxJIW5L2hvB-94" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1540" y="190" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-138" value="TIMER: MGMTD_TXN_PROC_COMCFG" style="ellipse;whiteSpace=wrap;fontFamily=Verdana;fontSize=8;fillColor=#b1ddf0;strokeColor=#10739e;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1520" y="30" width="120" height="80" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-156" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;startArrow=none;startFill=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-65" target="nUYlmBzm2YxJIW5L2hvB-154" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1353" y="310" /> + <mxPoint x="1513" y="310" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-148" value="GET nb_config_change's nb_config_diff(cand, run) or  txn->commit_cfg_req->req.commit_cfg.cfg_chgs " style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#e1d5e7;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;strokeColor=#9673a6;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1280" y="330" width="145" height="50" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-154" value="mgmt_txn_create_config_batches" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#e1d5e7;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;strokeColor=#9673a6;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1440" y="330" width="145" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-157" value="TIMER: MGMTD_TXN_ COMMITCFG_TIMEOUT" style="ellipse;whiteSpace=wrap;fontFamily=Verdana;fontSize=8;fillColor=#b1ddf0;strokeColor=#10739e;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1590" y="190" width="90" height="60" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-176" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=doubleBlock;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-161" target="nUYlmBzm2YxJIW5L2hvB-175" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1970" y="90" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-177" value="MESSAGE_CFG_DATA_REPLY" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-176" vertex="1" connectable="0"> + <mxGeometry x="0.177" relative="1" as="geometry"> + <mxPoint x="-10" y="255" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-224" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;endFill=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-161" target="nUYlmBzm2YxJIW5L2hvB-223" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1890" y="460" as="targetPoint" /> + <Array as="points"> + <mxPoint x="1940" y="305" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-225" value="MESSAGE_CFG_APPLY_REPLY;" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-224" vertex="1" connectable="0"> + <mxGeometry x="-0.0859" y="3" relative="1" as="geometry"> + <mxPoint x="-7" y="64" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-161" value="Backend Client" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#cdeb8b;strokeColor=#36393d;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1935" y="610" width="205" height="120" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-169" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-163" target="nUYlmBzm2YxJIW5L2hvB-168" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-163" value="mgmt_txn_notify_be_txn_reply" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="2020" y="465" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-192" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=doubleBlock;startSize=8;endSize=8;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-168" target="nUYlmBzm2YxJIW5L2hvB-171" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-168" value="mgmt_txn_send_be_cfg_data" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="2020" y="415" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-188" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-171" target="nUYlmBzm2YxJIW5L2hvB-187" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-189" value="batch: PHASE_TXN_REQ -> PHASE_SEND_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-188" vertex="1" connectable="0"> + <mxGeometry x="-0.176" relative="1" as="geometry"> + <mxPoint x="-9" y="-55" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-171" value="mgmt_be_send_cfgdata_req" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="2025" y="310" width="157.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-200" value="txn: PHASE_TXN_REQ -> PHASE_SEND_CFG" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-187" target="nUYlmBzm2YxJIW5L2hvB-199" edge="1"> + <mxGeometry x="1" y="80" relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="2095" y="240" /> + <mxPoint x="2095" y="240" /> + </Array> + <mxPoint x="75" y="-80" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-187" value="mgmt_move_txn_cfg_batch_to_next" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="2025" y="255" width="157.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-201" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-199" target="nUYlmBzm2YxJIW5L2hvB-138" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="2095" y="30" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-202" value="curr_phase = PHASE_SEND_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-201" vertex="1" connectable="0"> + <mxGeometry x="0.4129" y="-3" relative="1" as="geometry"> + <mxPoint x="121" y="-7" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-199" value="mgmt_try_move_commit_to_next_phase if all backend clients have all been sent their batches move to next phase and post EVENT/TIMER" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="2002.5" y="160" width="185" height="60" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-172" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=doubleBlock;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-171" target="nUYlmBzm2YxJIW5L2hvB-161" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1957.5" y="175" as="sourcePoint" /> + <Array as="points"> + <mxPoint x="2000" y="325" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-173" value="MESSAGE_CFG_DATA_REQ" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-172" vertex="1" connectable="0"> + <mxGeometry x="-0.1783" y="-1" relative="1" as="geometry"> + <mxPoint x="-9" y="-30" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-195" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-175" target="nUYlmBzm2YxJIW5L2hvB-179" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-197" value="batch: PHASE_SEND_CFG -> PHASE_APPLY_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-195" vertex="1" connectable="0"> + <mxGeometry x="0.463" y="-1" relative="1" as="geometry"> + <mxPoint x="1" y="-9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-175" value="mgmt_txn_notify_be_cfgdata_reply" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1770" y="75" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-210" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-179" target="nUYlmBzm2YxJIW5L2hvB-180" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-179" value="mgmt_move_txn_cfg_batch_to_next" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1770" y="160" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-181" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-180" target="nUYlmBzm2YxJIW5L2hvB-138" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1770" y="30" as="targetPoint" /> + <Array as="points"> + <mxPoint x="1720" y="235" /> + <mxPoint x="1720" y="50" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-182" value="curr_phase = PHASE_APPLY_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-181" vertex="1" connectable="0"> + <mxGeometry x="-0.4275" relative="1" as="geometry"> + <mxPoint x="50" y="72" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-180" value="mgmt_try_move_commit_to_next_phase" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1763.75" y="220" width="180" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-164" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-161" target="nUYlmBzm2YxJIW5L2hvB-163" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="2120" y="510" as="targetPoint" /> + <mxPoint x="2104" y="610" as="sourcePoint" /> + <Array as="points"> + <mxPoint x="2104" y="580" /> + <mxPoint x="2104" y="580" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-167" value="MESSAGE_TXN_REPLY" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-164" vertex="1" connectable="0"> + <mxGeometry x="-0.5021" y="1" relative="1" as="geometry"> + <mxPoint x="-13" y="-25" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-183" value="curr_phase = PHASE_TXN_CREATE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="1470" y="569.9974074074072" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-184" value="next_phase = PHASE_SEND_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="1459.9985714285715" y="609.998574414664" as="geometry"> + <mxPoint x="5" y="-3" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-186" value="For each of the batches of cfgdata send msg and move" style="verticalLabelPosition=middle;verticalAlign=middle;strokeWidth=2;shape=mxgraph.lean_mapping.physical_pull;pointerEvents=1;fontFamily=Verdana;fontSize=8;fontColor=default;labelPosition=right;align=left;horizontal=1;" parent="1" vertex="1"> + <mxGeometry x="2025" y="370" width="30" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-191" value="For each of the  batches of cfgdata" style="verticalLabelPosition=middle;verticalAlign=middle;strokeWidth=2;shape=mxgraph.lean_mapping.physical_pull;pointerEvents=1;fontFamily=Verdana;fontSize=8;fontColor=default;labelPosition=right;align=left;horizontal=1;" parent="1" vertex="1"> + <mxGeometry x="1935" y="55" width="30" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-211" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-213" target="nUYlmBzm2YxJIW5L2hvB-161" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="1945" y="670" as="targetPoint" /> + <mxPoint x="1435" y="600" as="sourcePoint" /> + <Array as="points"> + <mxPoint x="1850" y="720" /> + <mxPoint x="1850" y="670" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-212" value="MESSAGE_CFG_APPLY" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-211" vertex="1" connectable="0"> + <mxGeometry x="-0.5683" relative="1" as="geometry"> + <mxPoint x="-31" y="-10" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-219" value="batch: comm_phase = PHASE_TXN_CREATE" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="1470" y="539.9974074074072" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-220" value="batch: comm_phase = PHASE_APPLY_CFG" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="1390" y="699.9974074074072" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-228" value="batch: PHASE_APPLY_CFG -> PHASE_TXN_DELETE" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-223" target="nUYlmBzm2YxJIW5L2hvB-227" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-223" value="mgmt_txn_notify_be_cfg_apply_reply for each batch id in reply" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1750" y="290" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-226" value="mgmt_txn_send_be_txn_delete" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1750" y="420" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-229" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-227" target="nUYlmBzm2YxJIW5L2hvB-226" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-233" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-227" target="nUYlmBzm2YxJIW5L2hvB-232" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1730" y="375" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-227" value="mgmt_move_txn_cfg_batch_to_next" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1750" y="360" width="167.5" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-230" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#ff0000;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;entryX=-0.012;entryY=0.122;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-226" target="nUYlmBzm2YxJIW5L2hvB-161" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="2055" y="699.96" as="targetPoint" /> + <mxPoint x="1700" y="470" as="sourcePoint" /> + <Array as="points"> + <mxPoint x="1870" y="625" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-231" value="MESSAGE_TXN_REQ delete" style="edgeLabel;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;horizontal=0;" parent="nUYlmBzm2YxJIW5L2hvB-230" vertex="1" connectable="0"> + <mxGeometry x="-0.5683" relative="1" as="geometry"> + <mxPoint x="-10" y="9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-234" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=8;fontColor=default;endArrow=open;startSize=8;endSize=8;strokeWidth=1;fillColor=#0050ef;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-232" target="nUYlmBzm2YxJIW5L2hvB-138" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="1690" y="70" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-232" value="mgmt_try_move_commit_to_next_phase" style="rounded=1;whiteSpace=wrap;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="1560" y="420" width="180" height="30" as="geometry" /> + </mxCell> + </root> + </mxGraphModel> + </diagram> +</mxfile> diff --git a/doc/figures/cli-change-mgmtd.svg b/doc/figures/cli-change-mgmtd.svg new file mode 100644 index 0000000..279f74a --- /dev/null +++ b/doc/figures/cli-change-mgmtd.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1048" height="781" viewBox="-0.5 -0.5 1048 781" style="background-color: rgb(255, 255, 255);"><defs><filter id="dropShadow"><feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"/><feOffset in="blur" dx="3" dy="3" result="offsetBlur"/><feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"/><feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"/><feBlend in="SourceGraphic" in2="offsetBlur"/></filter></defs><g filter="url(#dropShadow)"><path d="M 275 274 L 475 274 Q 485 274 485 264 L 485 236.24" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 489.5 244.12 L 485 235.12 L 480.5 244.12" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><path d="M 200 284.22 L 200 294.11 Q 200 304 200 307.88 L 200 311.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 195.5 303.88 L 200 312.88 L 204.5 303.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="130" y="263.78" width="145" height="20.44" rx="4.91" ry="4.91" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 274px; margin-left: 131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_prepare_cfg</div></div></div></foreignObject><text x="203" y="276" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_prepare_cfg</text></switch></g><path d="M 500 574 L 680 574 Q 690 574 690 584 L 690 634 Q 690 644 700 644 L 780.53 644" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 772.76 649 L 782.76 644 L 772.76 639" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="560" y="562">MESSAGE_TXN_REQ</text><text x="560" y="572">create</text></g><path d="M 275 574 L 330 574 Q 340 574 346.38 574 L 352.76 574" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 344.88 578.5 L 353.88 574 L 344.88 569.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="130" y="564" width="145" height="20" rx="4.8" ry="4.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 574px; margin-left: 131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_be_txn_create</div></div></div></foreignObject><text x="203" y="576" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_be_txn_create</text></switch></g><rect x="355" y="565" width="145" height="20" rx="4.8" ry="4.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 575px; margin-left: 356px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_be_send_txn_req</div></div></div></foreignObject><text x="428" y="577" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_be_send_txn_req</text></switch></g><rect x="90" y="624" width="180" height="30" rx="7.2" ry="7.2" fill="#eeeeee" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 639px; margin-left: 91px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">does nothing<br />cfg_data replys will cause next transition<div><br /></div></div></div></div></foreignObject><text x="180" y="641" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">does nothing...</text></switch></g><path d="M 195 704 L 287.76 704" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 279.88 708.5 L 288.88 704 L 279.88 699.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="241" y="722">next_phase =</text><text x="241" y="732"> PHASE_TXN_DELETE</text></g><rect x="50" y="694" width="145" height="20" rx="4.8" ry="4.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 704px; margin-left: 51px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_be_cfg_apply</div></div></div></foreignObject><text x="123" y="706" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_be_cfg_apply</text></switch></g><rect x="290" y="694" width="145" height="20" rx="4.8" ry="4.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 704px; margin-left: 291px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_be_send_cfgapply_req</div></div></div></foreignObject><text x="363" y="706" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_be_send_cfgapply_req</text></switch></g><rect x="11.25" y="754" width="145" height="20" rx="4.8" ry="4.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 764px; margin-left: 12px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_commit_cfg_reply</div></div></div></foreignObject><text x="84" y="766" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_commit_cfg_reply</text></switch></g><path d="M 120 224 L 120 564 Q 120 574 123.88 574 L 127.76 574" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 119.88 578.5 L 128.88 574 L 119.88 569.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="167" y="558">PHASE_TXN_CREATE</text></g><path d="M 150 224 L 150 234 Q 150 244 160 244 L 193 244 Q 203 244 203 252.77 L 203 261.54" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 198.5 253.66 L 203 262.66 L 207.5 253.66" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="180" y="237">PHASE_PREPARE_CFG</text></g><path d="M 60 224 L 60 314 Q 60 324 60 334 L 60 691.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 55.5 683.88 L 60 692.88 L 64.5 683.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="110" y="687">PHASE_CFG_APPLY</text></g><path d="M 30 224 L 30 354 Q 30 364 30 374 L 30 751.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 25.5 743.88 L 30 752.88 L 34.5 743.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="84" y="747">PHASE_TXN_DELETE</text></g><path d="M 100 224 L 100 314 Q 100 324 100 334 L 100 621.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 95.5 613.88 L 100 622.88 L 104.5 613.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="140" y="616">PHASE_SEND_CFG</text></g><rect x="0" y="154" width="167.5" height="70" rx="16.8" ry="16.8" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 189px; margin-left: 1px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_process_commit_cfg</div></div></div></foreignObject><text x="84" y="191" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_process_commit_cfg</text></switch></g><path d="M 275 274 L 420 274 Q 430 274 430 264 L 430 96.24" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 434.5 104.12 L 430 95.12 L 425.5 104.12" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="330" y="252">curr_phase =</text><text x="330" y="262">PHASE_TXN_CREATE</text></g><rect x="0" y="34" width="167.5" height="40" rx="9.6" ry="9.6" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 54px; margin-left: 1px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_commit_config_req</div></div></div></foreignObject><text x="84" y="56" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_commit_config_req</text></switch></g><path d="M 167.5 54 L 367.76 54" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 359.88 58.5 L 368.88 54 L 359.88 49.5" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="274.5" y="27">curr_phase ==</text><text x="274.5" y="37">MGMTD_COMMIT_PHASE_PREPARE_CFG</text></g><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="275" y="67">next_phase ==</text><text x="275" y="77">MGMTD_COMMIT_PHASE_PREPARE_CFG</text></g><path d="M 390 83.81 L 390 164 Q 390 174 380 174 L 169.74 174" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 177.62 169.5 L 168.62 174 L 177.62 178.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="430" cy="54" rx="60" ry="40" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 54px; margin-left: 371px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">TIMER:<br />MGMTD_TXN_PROC_COMCFG</div></div></div></foreignObject><text x="430" y="56" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">TIMER:...</text></switch></g><path d="M 202.5 284.22 L 202.5 289.11 Q 202.5 294 212.5 294 L 353 294 Q 363 294 363 302.88 L 363 311.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 358.5 303.88 L 363 312.88 L 367.5 303.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="130" y="314" width="145" height="50" rx="12" ry="12" fill="#e1d5e7" stroke="#9673a6" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 339px; margin-left: 131px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">GET nb_config_change's<br />nb_config_diff(cand, run)<br />or <br />txn->commit_cfg_req->req.commit_cfg.cfg_chgs<div><br /></div></div></div></div></foreignObject><text x="203" y="341" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">GET nb_config_change's...</text></switch></g><rect x="290" y="314" width="145" height="30" rx="7.2" ry="7.2" fill="#e1d5e7" stroke="#9673a6" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 143px; height: 1px; padding-top: 329px; margin-left: 291px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_create_config_batches</div></div></div></foreignObject><text x="363" y="331" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_create_config_batches</text></switch></g><ellipse cx="485" cy="204" rx="45" ry="30" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 204px; margin-left: 441px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">TIMER:<br />MGMTD_TXN_<br />COMMITCFG_TIMEOUT</div></div></div></foreignObject><text x="485" y="206" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">TIMER:...</text></switch></g><path d="M 820 594 L 820 84 Q 820 74 814.87 74 L 809.74 74" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 789.74 74 L 799.74 69 L 799.74 79 Z M 799.74 74 L 809.74 69 L 809.74 79 Z" fill="#ff0000" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px" transform="rotate(-90,810.5,523.5)"><text x="810" y="526">MESSAGE_CFG_DATA_REPLY</text></g><path d="M 790 594 L 790 299 Q 790 289 780.99 289 L 771.97 289" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 779.74 284 L 769.74 289 L 779.74 294" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px" transform="rotate(-90,780.5,507.5)"><text x="780" y="510">MESSAGE_CFG_APPLY_REPLY;</text></g><rect x="785" y="594" width="205" height="120" rx="28.8" ry="28.8" fill="#cdeb8b" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 203px; height: 1px; padding-top: 654px; margin-left: 786px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">Backend Client</div></div></div></foreignObject><text x="888" y="656" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">Backend Client</text></switch></g><path d="M 953.8 449 L 953.8 439 Q 953.8 429 953.8 439 L 953.8 444 Q 953.8 449 953.8 440.12 L 953.8 431.24" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 958.3 439.12 L 953.8 430.12 L 949.3 439.12" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="870" y="449" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 464px; margin-left: 871px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_notify_be_txn_reply</div></div></div></foreignObject><text x="954" y="466" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_notify_be_txn_reply</text></switch></g><path d="M 953.75 399 L 953.75 343.12" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 953.75 325.12 L 958.25 334.12 L 949.25 334.12 Z M 953.75 334.12 L 958.25 343.12 L 949.25 343.12 Z" fill="#ff0000" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="870" y="399" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 414px; margin-left: 871px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_be_cfg_data</div></div></div></foreignObject><text x="954" y="416" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_be_cfg_data</text></switch></g><path d="M 953.8 294 L 953.8 284 Q 953.8 274 953.8 281.5 L 953.8 285.25 Q 953.8 289 953.8 280.12 L 953.8 271.24" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 958.3 279.12 L 953.8 270.12 L 949.3 279.12" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="944.8" y="225">batch: PHASE_TXN_REQ -> PHASE_SEND_CFG</text></g><rect x="875" y="294" width="157.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 156px; height: 1px; padding-top: 309px; margin-left: 876px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_be_send_cfgdata_req</div></div></div></foreignObject><text x="954" y="311" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_be_send_cfgdata_req</text></switch></g><path d="M 945 239 L 945 231.5 Q 945 224 945 215.12 L 945 206.24" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 949.5 214.12 L 945 205.12 L 940.5 214.12" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="939.5" y="126.5">txn: PHASE_TXN_REQ -> PHASE_SEND_CFG</text></g><rect x="875" y="239" width="157.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 156px; height: 1px; padding-top: 254px; margin-left: 876px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_move_txn_cfg_batch_to_next</div></div></div></foreignObject><text x="954" y="256" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_move_txn_cfg_batch_to_next</text></switch></g><path d="M 945 144 L 945 24 Q 945 14 935 14 L 432.24 14" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 440.12 9.5 L 431.12 14 L 440.12 18.5" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="740" y="7">curr_phase = PHASE_SEND_CFG</text></g><rect x="852.5" y="144" width="185" height="60" rx="14.4" ry="14.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 183px; height: 1px; padding-top: 174px; margin-left: 854px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_try_move_commit_to_next_phase<br />if all backend clients<br />have all been sent their batches<br />move to next phase and post EVENT/TIMER</div></div></div></foreignObject><text x="945" y="176" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_try_move_commit_to_next_phase...</text></switch></g><path d="M 875 309 L 860 309 Q 850 309 850 319 L 850 571.76" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 850 591.76 L 845 581.76 L 855 581.76 Z M 850 581.76 L 845 571.76 L 855 571.76 Z" fill="#ff0000" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px" transform="rotate(-90,840.5,380.5)"><text x="840" y="383">MESSAGE_CFG_DATA_REQ</text></g><path d="M 703.75 89 L 703.75 141.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 699.25 133.88 L 703.75 142.88 L 708.25 133.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="703.75" y="123">batch: PHASE_SEND_CFG -> PHASE_APPLY_CFG</text></g><rect x="620" y="59" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 74px; margin-left: 621px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_notify_be_cfgdata_reply</div></div></div></foreignObject><text x="704" y="76" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_notify_be_cfgdata_reply</text></switch></g><path d="M 703.8 174 L 703.8 184 Q 703.8 194 703.8 197.88 L 703.8 201.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 699.3 193.88 L 703.8 202.88 L 708.3 193.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="620" y="144" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 159px; margin-left: 621px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_move_txn_cfg_batch_to_next</div></div></div></foreignObject><text x="704" y="161" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_move_txn_cfg_batch_to_next</text></switch></g><path d="M 613.75 219 L 580 219 Q 570 219 570 209 L 570 44 Q 570 34 560 34 L 484.2 34" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 492.08 29.5 L 483.08 34 L 492.08 38.5" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="620" y="246.75">curr_phase = PHASE_APPLY_CFG</text></g><rect x="613.75" y="204" width="180" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 219px; margin-left: 615px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_try_move_commit_to_next_phase</div></div></div></foreignObject><text x="704" y="221" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_try_move_commit_to_next_phase</text></switch></g><path d="M 954 594 L 954 574 Q 954 564 954 554 L 954 483.47" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 959 491.24 L 954 481.24 L 949 491.24" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px" transform="rotate(-90,940.5,539.5)"><text x="940" y="542">MESSAGE_TXN_REPLY</text></g><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="320" y="552">curr_phase =</text><text x="320" y="562">PHASE_TXN_CREATE</text></g><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="315" y="589">next_phase =</text><text x="315" y="599">PHASE_SEND_CFG</text></g><path d="M 896.96 356.21 C 891.58 352.95 884.78 353.59 880.07 357.81 C 875.37 362.03 873.83 368.87 876.25 374.79 C 878.67 380.7 884.51 384.35 890.73 383.85 C 896.96 383.35 902.17 378.8 903.66 372.57" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 902.21 372.57 L 904.38 368.85 L 905 373.31 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px"><text x="906.5" y="361.5">For each of the</text><text x="906.5" y="371.5">batches of cfgdata</text><text x="906.5" y="381.5">send msg and move</text></g><path d="M 806.96 41.21 C 801.58 37.95 794.78 38.59 790.07 42.81 C 785.37 47.03 783.83 53.87 786.25 59.79 C 788.67 65.7 794.51 69.35 800.73 68.85 C 806.96 68.35 812.17 63.8 813.66 57.57" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 812.21 57.57 L 814.38 53.85 L 815 58.31 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px"><text x="816.5" y="51.5">For each of the</text><text x="816.5" y="61.5"> batches of cfgdata</text></g><path d="M 435 704 L 690 704 Q 700 704 700 694 L 700 664 Q 700 654 710 654 L 780.53 654" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 772.76 659 L 782.76 654 L 772.76 649" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="490" y="697">MESSAGE_CFG_APPLY</text></g><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="320" y="522">batch: comm_phase =</text><text x="320" y="532">PHASE_TXN_CREATE</text></g><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="240" y="682">batch: comm_phase =</text><text x="240" y="692">PHASE_APPLY_CFG</text></g><path d="M 683.75 304 L 683.75 341.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 679.25 333.88 L 683.75 342.88 L 688.25 333.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px"><text x="683.25" y="326.5">batch: PHASE_APPLY_CFG -> PHASE_TXN_DELETE</text></g><rect x="600" y="274" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 289px; margin-left: 601px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_notify_be_cfg_apply_reply<br />for each batch id in reply</div></div></div></foreignObject><text x="684" y="291" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_notify_be_cfg_apply_reply...</text></switch></g><rect x="600" y="404" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 419px; margin-left: 601px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_be_txn_delete</div></div></div></foreignObject><text x="684" y="421" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_be_txn_delete</text></switch></g><path d="M 683.8 374 L 683.8 384 Q 683.8 394 683.8 389 L 683.8 386.5 Q 683.8 384 683.8 392.88 L 683.8 401.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 679.3 393.88 L 683.8 402.88 L 688.3 393.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><path d="M 600 359 L 590 359 Q 580 359 580 369 L 580 401.76" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 575.5 393.88 L 580 402.88 L 584.5 393.88" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="600" y="344" width="167.5" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 166px; height: 1px; padding-top: 359px; margin-left: 601px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_move_txn_cfg_batch_to_next</div></div></div></foreignObject><text x="684" y="361" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_move_txn_cfg_batch_to_next</text></switch></g><path d="M 720 434 L 720 598.6 Q 720 608.6 730 608.61 L 778.07 608.64" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 770.3 613.63 L 780.3 608.64 L 770.31 603.63" fill="none" stroke="#ff0000" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g fill="rgb(0, 0, 0)" font-family="Verdana" text-anchor="middle" font-size="8px" transform="rotate(-90,710.5,493.5)"><text x="710" y="491">MESSAGE_TXN_REQ</text><text x="710" y="501">delete</text></g><path d="M 540 404 L 540 64 Q 540 54 530 54 L 492.24 54" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 500.12 49.5 L 491.12 54 L 500.12 58.5" fill="none" stroke="#001dbc" stroke-miterlimit="10" pointer-events="all"/><rect x="410" y="404" width="180" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 419px; margin-left: 411px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_try_move_commit_to_next_phase</div></div></div></foreignObject><text x="500" y="421" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_try_move_commit_to_next_phase</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file diff --git a/doc/figures/cli-oper-state.drawio b/doc/figures/cli-oper-state.drawio new file mode 100644 index 0000000..4b86b58 --- /dev/null +++ b/doc/figures/cli-oper-state.drawio @@ -0,0 +1,377 @@ +<mxfile host="Electron" modified="2024-01-04T05:29:53.817Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.1.16 Chrome/120.0.6099.109 Electron/28.1.0 Safari/537.36" etag="qF0825mlH7rzndmYEIdj" version="22.1.16" type="device"> + <diagram name="Page-1" id="58cdce13-f638-feb5-8d6f-7d28b1aa9fa0"> + <mxGraphModel dx="1398" dy="842" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="1"> + <root> + <mxCell id="0" /> + <mxCell id="1" parent="0" /> + <mxCell id="kVfNefTpehhSeJQHV--9-92" value="<div style="font-size: 12px;">Frontend CLI (mgmtd)</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#dae8fc;strokeColor=#6c8ebf;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=12;align=center;verticalAlign=top;fontStyle=1" parent="1" vertex="1"> + <mxGeometry x="10" y="61.42000000000001" width="425" height="312.17" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-30" value="<div style="font-size: 12px;">MGMTD</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#dae8fc;strokeColor=#6c8ebf;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=12;align=center;verticalAlign=top;fontStyle=1" parent="1" vertex="1"> + <mxGeometry x="50" y="400" width="730" height="410" as="geometry" /> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="lldLKuc6OoWEgdetZcLS-3" target="lldLKuc6OoWEgdetZcLS-4" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="115" y="645" /> + <mxPoint x="115" y="645" /> + </Array> + <mxPoint x="385.0031707317075" y="605" as="sourcePoint" /> + <mxPoint x="385.93" y="695" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-10" value="xpath" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;fontSize=8;" parent="lldLKuc6OoWEgdetZcLS-6" vertex="1" connectable="0"> + <mxGeometry x="0.062" y="2" relative="1" as="geometry"> + <mxPoint x="-22" y="4" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;startArrow=classic;startFill=1;" parent="1" source="lldLKuc6OoWEgdetZcLS-3" target="kVfNefTpehhSeJQHV--9-3" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="215" y="695" /> + <mxPoint x="215" y="695" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-6" value="txn" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;labelBackgroundColor=none;" parent="kVfNefTpehhSeJQHV--9-4" vertex="1" connectable="0"> + <mxGeometry x="-0.1676" y="-2" relative="1" as="geometry"> + <mxPoint x="12" y="-26" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="lldLKuc6OoWEgdetZcLS-3" target="kVfNefTpehhSeJQHV--9-7" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="255" y="595" /> + <mxPoint x="255" y="742" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-10" value="clients (bitmask)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Helvetica;fontColor=default;labelBackgroundColor=none;" parent="kVfNefTpehhSeJQHV--9-8" vertex="1" connectable="0"> + <mxGeometry x="-0.1299" y="1" relative="1" as="geometry"> + <mxPoint x="29" y="58" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-3" value="fe_adapter_handle_get_tree" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="75" y="575" width="160" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-73" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;endArrow=doubleBlock;endFill=1;" parent="1" source="kVfNefTpehhSeJQHV--9-46" target="kVfNefTpehhSeJQHV--9-72" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="530.037037037037" y="571" as="sourcePoint" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-77" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-72" target="kVfNefTpehhSeJQHV--9-76" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-72" value="be_adapter_handle_get_tree<br>mgmt_txn_notify_tree_data_reply<br>------------------------------------<br>merge tree data<br>when all clients respond or timeout" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="550" y="530" width="160" height="60" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;endArrow=doubleBlock;endFill=1;" parent="1" source="kVfNefTpehhSeJQHV--9-7" target="kVfNefTpehhSeJQHV--9-11" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-7" value="mgmt_txn_send_get_tree_oper" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="320" y="725" width="160" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-11" value="mgmt_be_send_native" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="565" y="725" width="130" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-83" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;labelBackgroundColor=none;endArrow=open;strokeColor=#C73500;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="kVfNefTpehhSeJQHV--9-76" target="kVfNefTpehhSeJQHV--9-82" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="320" y="543.75" as="sourcePoint" /> + <mxPoint x="300.03703703703695" y="326.25" as="targetPoint" /> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-84" value="<i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">socket connection<br style="border-color: var(--border-color);"></i><font style="font-size: 10px;">FE adapter -&gt; FE client<br style="border-color: var(--border-color); font-family: Verdana;"></font><span style="font-family: Verdana; font-size: 10px;">MGMT_MSG_CODE_TREE_DATA</span><br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;"><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">struct mgmt_msg_tree_data</i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Helvetica;fontColor=default;labelBackgroundColor=none;" parent="kVfNefTpehhSeJQHV--9-83" vertex="1" connectable="0"> + <mxGeometry x="0.5" relative="1" as="geometry"> + <mxPoint x="80" y="84" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-76" value="txn_get_tree_data_done<br>fe_adapter_send_tree_data" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="320" y="545" width="160" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-88" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-85" target="kVfNefTpehhSeJQHV--9-86" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-85" value="session-&gt;get_tree_notify" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="235" y="256.79999999999995" width="160" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-90" value="vty_mgmt_resume_response" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="235" y="118.92000000000002" width="160" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-91" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-86" target="kVfNefTpehhSeJQHV--9-90" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-86" value="<b>vty_mgmt_get_tree_result_notified<br></b>displays result<br>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="225" y="174.03" width="180" height="64.93" as="geometry" /> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="lldLKuc6OoWEgdetZcLS-4" target="lldLKuc6OoWEgdetZcLS-3" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="445" y="392.21000000000004" as="sourcePoint" /> + <mxPoint x="445" y="485" as="targetPoint" /> + <Array as="points"> + <mxPoint x="145" y="645" /> + <mxPoint x="145" y="645" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-11" value="clients (bitmask)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;fontFamily=Helvetica;fontColor=default;labelBackgroundColor=none;" parent="lldLKuc6OoWEgdetZcLS-9" vertex="1" connectable="0"> + <mxGeometry x="-0.1435" y="-1" relative="1" as="geometry"> + <mxPoint x="29" y="-9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-4" value="mgmt_be_interested_clients" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="75" y="665" width="120" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-90" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;strokeColor=default;fontFamily=Helvetica;fontSize=11;fontColor=default;endArrow=classic;startSize=8;endSize=8;entryX=0.5;entryY=0;entryDx=0;entryDy=0;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-78" target="nUYlmBzm2YxJIW5L2hvB-84" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-78" value="vty_mgmt_send_get_tree_req" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="45.01999999999998" y="255.79999999999998" width="140" height="31" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-88" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-84" target="nUYlmBzm2YxJIW5L2hvB-87" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="540" y="715" as="targetPoint" /> + <Array as="points"> + <mxPoint x="115" y="470" /> + <mxPoint x="115" y="470" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-89" value="<i style="font-size: 10px;">socket connection<br style="font-size: 10px;"></i>FE client -&gt; FE adapter<br>MGMT_MSG_CODE_GET_TREE<br><i>struct mgmt_msg_get_tree</i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-88" vertex="1" connectable="0"> + <mxGeometry x="-0.0463" y="1" relative="1" as="geometry"> + <mxPoint x="89" y="34" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-84" value="mgmt_fe_send_get_tree_req" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;" parent="1" vertex="1"> + <mxGeometry x="45.01999999999998" y="320.79999999999995" width="140" height="30" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-93" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;strokeColor=default;fontFamily=Helvetica;fontSize=11;fontColor=default;endArrow=classic;startSize=8;endSize=8;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-87" target="lldLKuc6OoWEgdetZcLS-3" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="532.8049999999998" y="542.2400000000002" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-5" value="xpath" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-93" vertex="1" connectable="0"> + <mxGeometry x="-0.2901" y="1" relative="1" as="geometry"> + <mxPoint x="-21" y="15" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-87" value="fe_adapter_handle_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#b1ddf0;strokeColor=#10739e;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=7;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="75" y="487.5" width="160" height="35" as="geometry" /> + </mxCell> + <mxCell id="lldLKuc6OoWEgdetZcLS-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;endArrow=classic;fontSize=11;fontFamily=Helvetica;strokeColor=default;startSize=8;endSize=8;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-245" target="nUYlmBzm2YxJIW5L2hvB-78" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-245" value=""show mgmt get-data-tree WORD$path [json|xml]"" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=default;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;fontColor=#000000;align=center;strokeWidth=1;" parent="1" vertex="1"> + <mxGeometry x="55.01999999999999" y="198.27999999999997" width="120" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-252" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;strokeColor=default;fontFamily=Helvetica;fontSize=11;fontColor=default;endArrow=classic;startSize=8;endSize=8;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-251" target="nUYlmBzm2YxJIW5L2hvB-245" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-251" value="EVENT: VTYSH_READ" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=7;fillColor=#b1ddf0;strokeColor=#10739e;" parent="1" vertex="1"> + <mxGeometry x="55.01999999999999" y="93.92000000000004" width="120" height="80" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-275" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;strokeColor=default;fontFamily=Helvetica;fontSize=11;fontColor=default;endArrow=classic;startSize=8;endSize=8;endFill=1;" parent="1" source="kVfNefTpehhSeJQHV--9-90" target="nUYlmBzm2YxJIW5L2hvB-268" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="320" y="30" /> + </Array> + <mxPoint x="320.03703703703695" y="134.71000000000004" as="sourcePoint" /> + <mxPoint x="158.76" y="20" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-269" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=default;strokeColor=default;fontFamily=Helvetica;fontSize=11;fontColor=default;endArrow=classic;startSize=8;endSize=8;jumpStyle=gap;endFill=1;" parent="1" source="nUYlmBzm2YxJIW5L2hvB-268" target="nUYlmBzm2YxJIW5L2hvB-251" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="120" y="95" /> + <mxPoint x="120" y="95" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-268" value="<div>VTYSH</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#cdeb8b;strokeColor=#36393d;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;" parent="1" vertex="1"> + <mxGeometry x="81.28" y="10" width="77.48" height="40" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-3" value="mgmt_create_txn" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="115" y="725" width="120" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-12" value="mgmt_txn_req_alloc" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="340" y="647.83" width="120" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;startArrow=classic;startFill=1;" parent="1" source="kVfNefTpehhSeJQHV--9-12" target="kVfNefTpehhSeJQHV--9-7" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="374.78" y="645" as="sourcePoint" /> + <mxPoint x="374.78" y="755" as="targetPoint" /> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-14" value="txn_req<br>MGMTD_TXN_PROC_GETTREE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=8;labelBackgroundColor=none;" parent="kVfNefTpehhSeJQHV--9-13" vertex="1" connectable="0"> + <mxGeometry x="-0.1676" y="-2" relative="1" as="geometry"> + <mxPoint x="37" y="-2" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-18" value="<div style="text-align: center;"><span style="background-color: initial;">for each of the clients</span></div><div style="text-align: center;"><span style="background-color: initial;">in bitmask</span></div>" style="verticalLabelPosition=middle;html=1;verticalAlign=middle;strokeWidth=2;shape=mxgraph.lean_mapping.physical_pull;pointerEvents=1;fontFamily=Verdana;fontSize=10;fontColor=default;labelPosition=right;align=left;horizontal=1;" parent="1" vertex="1"> + <mxGeometry x="525" y="687.83" width="30" height="30" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-41" value="be_client_send_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="660" y="186.42000000000002" width="130" height="25" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-46" value="be_adapter_handle_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#b1ddf0;strokeColor=#10739e;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=7;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="550" y="437.83" width="160" height="35" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-50" value="<i style="font-size: 10px;">socket connection<br style="font-size: 10px;"></i>BE client -&gt; BE adapter<br>MGMT_MSG_CODE_TREE_DATA<br><i>struct mgmt_msg_tree_data</i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="1" vertex="1" connectable="0"> + <mxGeometry x="529.997037037037" y="360.00370370370393" as="geometry"> + <mxPoint x="21" y="1" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-59" value="be_client_send_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="670" y="196.42000000000002" width="130" height="25" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-60" value="<div style="font-size: 12px;">Backend Client (ospfd, staticd, ...)</div>" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#E6FFCC;strokeColor=#36393d;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=12;align=center;verticalAlign=top;fontStyle=1" parent="1" vertex="1"> + <mxGeometry x="480" y="102.02000000000001" width="380" height="207.57" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-61" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;startArrow=classic;startFill=1;" parent="1" source="kVfNefTpehhSeJQHV--9-64" target="kVfNefTpehhSeJQHV--9-65" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="720" y="186.2" /> + <mxPoint x="720" y="186.2" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-62" value="<span style="font-style: normal;">(1) build oper state tree<br></span>struct mgmt_msg_tree_data" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Helvetica;fontColor=default;labelBackgroundColor=none;fontStyle=2" parent="kVfNefTpehhSeJQHV--9-61" vertex="1" connectable="0"> + <mxGeometry x="0.038" y="1" relative="1" as="geometry"> + <mxPoint x="71" y="-1" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-63" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-64" target="kVfNefTpehhSeJQHV--9-68" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="670" y="213.63" /> + <mxPoint x="670" y="213.63" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-11" value="(2)" style="edgeLabel;html=1;align=center;verticalAlign=bottom;resizable=0;points=[];labelBackgroundColor=none;" vertex="1" connectable="0" parent="kVfNefTpehhSeJQHV--9-63"> + <mxGeometry x="0.1063" y="-1" relative="1" as="geometry"> + <mxPoint x="3" y="1" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-64" value="be_client_handle_get_tree" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="690" y="201.2" width="140" height="25" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-65" value="nb_oper_data_iterate" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="690" y="131.13" width="110" height="25" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-66" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-67" target="kVfNefTpehhSeJQHV--9-64" edge="1"> + <mxGeometry relative="1" as="geometry"> + <Array as="points"> + <mxPoint x="760" y="238.63" /> + <mxPoint x="760" y="238.63" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-67" value="be_client_handle_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#b1ddf0;strokeColor=#10739e;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=7;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="670" y="251.79999999999998" width="160" height="35" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-70" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;labelBackgroundColor=none;endArrow=open;strokeColor=#C73500;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;" parent="1" source="kVfNefTpehhSeJQHV--9-68" edge="1" target="kVfNefTpehhSeJQHV--9-46"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="624.257037037037" y="257.56999999999994" as="sourcePoint" /> + <mxPoint x="624.257037037037" y="482.1700000000001" as="targetPoint" /> + <Array as="points" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-68" value="be_client_send_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#ffffc0;strokeColor=#ff0000;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=8;align=center;fontColor=#000000;" parent="1" vertex="1"> + <mxGeometry x="500" y="201.2" width="130" height="25" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-74" value="<div style="text-align: center;"><span style="background-color: initial;">for each of the</span></div>queried&nbsp;<span style="background-color: initial; text-align: center;">BE clients</span>" style="verticalLabelPosition=middle;html=1;verticalAlign=middle;strokeWidth=2;shape=mxgraph.lean_mapping.physical_pull;pointerEvents=1;fontFamily=Verdana;fontSize=10;fontColor=default;labelPosition=right;align=left;horizontal=1;" parent="1" vertex="1"> + <mxGeometry x="635" y="487.8299999999999" width="30" height="32.83" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-69" style="edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;labelBackgroundColor=none;endArrow=none;strokeColor=#C73500;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;endFill=0;startArrow=open;startFill=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="kVfNefTpehhSeJQHV--9-67" target="kVfNefTpehhSeJQHV--9-11" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="740.037037037037" y="312.21000000000004" as="sourcePoint" /> + <mxPoint x="570" y="775.037037037037" as="targetPoint" /> + <Array as="points"> + <mxPoint x="750" y="740" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-81" value="<i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">socket connection<br style="border-color: var(--border-color);"></i>BE adapter -&gt; BE client<br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;"><span style="font-family: Verdana; font-size: 10px;">MGMT_MSG_CODE_GET_TREE</span><br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;"><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">struct mgmt_msg_get_tree</i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="kVfNefTpehhSeJQHV--9-69" vertex="1" connectable="0"> + <mxGeometry x="-0.7023" y="-3" relative="1" as="geometry"> + <mxPoint x="93" y="-4" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-87" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontFamily=Helvetica;fontSize=8;fontColor=default;" parent="1" source="kVfNefTpehhSeJQHV--9-82" target="kVfNefTpehhSeJQHV--9-85" edge="1"> + <mxGeometry relative="1" as="geometry" /> + </mxCell> + <mxCell id="kVfNefTpehhSeJQHV--9-82" value="fe_client_handle_native_msg" style="rounded=1;whiteSpace=wrap;html=1;arcSize=24;fillColor=#b1ddf0;strokeColor=#10739e;shadow=0;comic=0;labelBackgroundColor=none;fontFamily=Verdana;fontSize=7;fontColor=default;align=center;" parent="1" vertex="1"> + <mxGeometry x="235" y="315.8" width="160" height="35" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-284" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Verdana;fontSize=12;fontColor=default;" parent="1" vertex="1"> + <mxGeometry x="930" y="20" width="130" height="100" as="geometry" /> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-278" value="" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#C73500;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#fa6800;" parent="1" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="950" y="50" as="sourcePoint" /> + <mxPoint x="1040.02" y="50" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-279" value="<i style="font-size: 10px;">socket&nbsp;</i>async" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-278" vertex="1" connectable="0"> + <mxGeometry x="-0.0463" y="1" relative="1" as="geometry"> + <mxPoint x="-8" y="-9" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-282" style="edgeStyle=orthogonalEdgeStyle;shape=connector;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;labelBackgroundColor=none;strokeColor=#001DBC;fontFamily=Verdana;fontSize=12;fontColor=default;endArrow=open;startSize=8;endSize=8;dashed=1;dashPattern=1 4;strokeWidth=2;fillColor=#0050ef;" parent="1" edge="1"> + <mxGeometry relative="1" as="geometry"> + <mxPoint x="950" y="80" as="sourcePoint" /> + <mxPoint x="1040" y="80" as="targetPoint" /> + <Array as="points"> + <mxPoint x="980" y="79.76999999999998" /> + <mxPoint x="980" y="79.76999999999998" /> + </Array> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-283" value="<span style="font-size: 10px;"><font style="font-size: 10px;">timer/event&nbsp;</font>async<br style="font-size: 10px;"></span>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-282" vertex="1" connectable="0"> + <mxGeometry x="0.2852" y="-1" relative="1" as="geometry"> + <mxPoint x="-28" y="-11" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-287" value="" style="endArrow=classic;html=1;rounded=1;labelBackgroundColor=none;strokeColor=#330000;fontFamily=Verdana;fontSize=12;fontColor=default;startSize=8;endSize=8;shape=connector;endFill=1;" parent="1" edge="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="950" y="110" as="sourcePoint" /> + <mxPoint x="1035" y="109.20000000000005" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="nUYlmBzm2YxJIW5L2hvB-288" value="function sync" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=9;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" parent="nUYlmBzm2YxJIW5L2hvB-287" vertex="1" connectable="0"> + <mxGeometry x="-0.26" y="2" relative="1" as="geometry"> + <mxPoint x="6" y="-8" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-6" value="" style="endArrow=none;dashed=1;html=1;strokeWidth=2;rounded=0;entryX=0.323;entryY=0.002;entryDx=0;entryDy=0;entryPerimeter=0;fillColor=#f5f5f5;strokeColor=#B3B3B3;" edge="1" parent="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="290" y="800" as="sourcePoint" /> + <mxPoint x="290" y="410" as="targetPoint" /> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-7" value="<i><font color="#999999">mgmt_fe_adapter.c</font></i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" vertex="1" connectable="0" parent="1"> + <mxGeometry x="169.99999999999977" y="570" as="geometry"> + <mxPoint x="29" y="212" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-8" value="<i><font color="#999999">mgmt_txn.c</font></i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" vertex="1" connectable="0" parent="1"> + <mxGeometry x="369.9999999999998" y="570" as="geometry"> + <mxPoint x="29" y="212" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-9" value="<i><font color="#999999">mgmt_be_adapter.c</font></i>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=10;fontFamily=Verdana;fontColor=default;labelBackgroundColor=none;" vertex="1" connectable="0" parent="1"> + <mxGeometry x="604.9999999999998" y="570" as="geometry"> + <mxPoint x="29" y="212" as="offset" /> + </mxGeometry> + </mxCell> + <mxCell id="eMqbX30VPKpUhST_t5Pw-10" value="z" style="endArrow=none;dashed=1;html=1;strokeWidth=2;rounded=0;entryX=0.323;entryY=0.002;entryDx=0;entryDy=0;entryPerimeter=0;fillColor=#f5f5f5;strokeColor=#B3B3B3;" edge="1" parent="1"> + <mxGeometry width="50" height="50" relative="1" as="geometry"> + <mxPoint x="510" y="800" as="sourcePoint" /> + <mxPoint x="510" y="410" as="targetPoint" /> + </mxGeometry> + </mxCell> + </root> + </mxGraphModel> + </diagram> +</mxfile> diff --git a/doc/figures/cli-oper-state.svg b/doc/figures/cli-oper-state.svg new file mode 100644 index 0000000..bda7d7b --- /dev/null +++ b/doc/figures/cli-oper-state.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1057" height="807" viewBox="-0.5 -0.5 1057 807" style="background-color: rgb(255, 255, 255);"><defs><filter id="dropShadow"><feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"/><feOffset in="blur" dx="3" dy="3" result="offsetBlur"/><feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"/><feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"/><feBlend in="SourceGraphic" in2="offsetBlur"/></filter></defs><g filter="url(#dropShadow)"><rect x="0" y="51.42" width="425" height="312.17" rx="74.92" ry="74.92" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 423px; height: 1px; padding-top: 58px; margin-left: 1px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"><div style="font-size: 12px;">Frontend CLI (mgmtd)</div></div></div></div></foreignObject><text x="213" y="70" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle" font-weight="bold">Frontend CLI (mgmtd)</text></switch></g><rect x="40" y="390" width="730" height="410" rx="98.4" ry="98.4" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 728px; height: 1px; padding-top: 397px; margin-left: 41px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"><div style="font-size: 12px;">MGMTD</div></div></div></div></foreignObject><text x="405" y="409" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle" font-weight="bold">MGMTD</text></switch></g><path d="M 105 595 L 105 635 L 105 648.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 105 653.88 L 101.5 646.88 L 105 648.63 L 108.5 646.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 631px; margin-left: 85px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">xpath</div></div></div></foreignObject><text x="85" y="633" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">xpath</text></switch></g><path d="M 205 601.37 L 205 685 L 205 708.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 205 596.12 L 208.5 603.12 L 205 601.37 L 201.5 603.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 205 713.88 L 201.5 706.88 L 205 708.63 L 208.5 706.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 619px; margin-left: 215px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">txn</div></div></div></foreignObject><text x="215" y="622" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">txn</text></switch></g><path d="M 225 585 L 245 585 L 245 732 L 303.63 732" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 308.88 732 L 301.88 735.5 L 303.63 732 L 301.88 728.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 724px; margin-left: 275px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">clients (bitmask)</div></div></div></foreignObject><text x="275" y="727" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">clients (bitmask)</text></switch></g><rect x="65" y="565" width="160" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 580px; margin-left: 66px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">fe_adapter_handle_get_tree</div></div></div></foreignObject><text x="145" y="582" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">fe_adapter_handle_get_tree</text></switch></g><path d="M 620 462.83 L 620 504.88" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 620 518.88 L 616.5 511.88 L 623.5 511.88 Z M 620 511.88 L 616.5 504.88 L 623.5 504.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 540 550 L 476.37 550" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 471.12 550 L 478.12 546.5 L 476.37 550 L 478.12 553.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="540" y="520" width="160" height="60" rx="14.4" ry="14.4" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 550px; margin-left: 541px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_adapter_handle_get_tree<br />mgmt_txn_notify_tree_data_reply<br />------------------------------------<br />merge tree data<br />when all clients respond or timeout</div></div></div></foreignObject><text x="620" y="552" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">be_adapter_handle_get_tree...</text></switch></g><path d="M 470 730 L 539.88 730" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 553.88 730 L 546.88 733.5 L 546.88 726.5 Z M 546.88 730 L 539.88 733.5 L 539.88 726.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="310" y="715" width="160" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 730px; margin-left: 311px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_send_get_tree_oper</div></div></div></foreignObject><text x="390" y="732" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_send_get_tree_oper</text></switch></g><rect x="555" y="715" width="130" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 730px; margin-left: 556px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_be_send_native</div></div></div></foreignObject><text x="620" y="732" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_be_send_native</text></switch></g><path d="M 390 535 L 390 447.92 Q 390 437.92 380 437.92 L 315 437.92 Q 305 437.92 305 427.92 L 305 345.27" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 310 353.04 L 305 343.04 L 300 353.04" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 495px; margin-left: 385px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">socket connection<br style="border-color: var(--border-color);" /></i><font style="font-size: 10px;">FE adapter -> FE client<br style="border-color: var(--border-color); font-family: Verdana;" /></font><span style="font-family: Verdana; font-size: 10px;">MGMT_MSG_CODE_TREE_DATA</span><br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;" /><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">struct mgmt_msg_tree_data</i></div></div></div></foreignObject><text x="385" y="498" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">socket connection...</text></switch></g><rect x="310" y="535" width="160" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 550px; margin-left: 311px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">txn_get_tree_data_done<br />fe_adapter_send_tree_data</div></div></div></foreignObject><text x="390" y="552" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">txn_get_tree_data_done...</text></switch></g><path d="M 305 246.8 L 305 226.77 L 305 248.92 L 305 235.33" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 305 230.08 L 308.5 237.08 L 305 235.33 L 301.5 237.08 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="225" y="246.8" width="160" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 262px; margin-left: 226px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">session->get_tree_notify</div></div></div></foreignObject><text x="305" y="264" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">session->get_tree_notify</text></switch></g><rect x="225" y="108.92" width="160" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 124px; margin-left: 226px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_resume_response</div></div></div></foreignObject><text x="305" y="126" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_resume_response</text></switch></g><path d="M 305 164.03 L 305 144 L 305 158.92 L 305 145.29" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 305 140.04 L 308.5 147.04 L 305 145.29 L 301.5 147.04 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="215" y="164.03" width="180" height="64.93" rx="15.58" ry="15.58" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 178px; height: 1px; padding-top: 196px; margin-left: 216px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><b>vty_mgmt_get_tree_result_notified<br /></b>displays result<br /></div></div></div></foreignObject><text x="305" y="199" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_get_tree_result_notified...</text></switch></g><path d="M 135 655 L 135 635 L 135 601.37" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 135 596.12 L 138.5 603.12 L 135 601.37 L 131.5 603.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 621px; margin-left: 165px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">clients (bitmask)</div></div></div></foreignObject><text x="165" y="623" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">clients (bitmask)</text></switch></g><rect x="65" y="655" width="120" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 670px; margin-left: 66px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_be_interested_clients</div></div></div></foreignObject><text x="125" y="672" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_be_interested_clients</text></switch></g><path d="M 105 276.8 L 105 296.77 L 105 290.77 L 105.01 302.93" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 105.02 309.68 L 100.51 300.69 L 105.01 302.93 L 109.51 300.68 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="35.02" y="245.8" width="140" height="31" rx="7.44" ry="7.44" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 261px; margin-left: 36px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">vty_mgmt_send_get_tree_req</div></div></div></foreignObject><text x="105" y="264" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">vty_mgmt_send_get_tree_req</text></switch></g><path d="M 105 340.8 L 105 450 Q 105 460 105 466.51 L 105 473.03" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 100 465.26 L 105 475.26 L 110 465.26" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 441px; margin-left: 195px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="font-size: 10px;">socket connection<br style="font-size: 10px;" /></i>FE client -> FE adapter<br />MGMT_MSG_CODE_GET_TREE<br /><i>struct mgmt_msg_get_tree</i></div></div></div></foreignObject><text x="195" y="444" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket connection...</text></switch></g><rect x="35.02" y="310.8" width="140" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 326px; margin-left: 36px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_fe_send_get_tree_req</div></div></div></foreignObject><text x="105" y="328" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_fe_send_get_tree_req</text></switch></g><path d="M 145 512.5 L 145 557.13" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 145 563.88 L 140.5 554.88 L 145 557.13 L 149.5 554.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 546px; margin-left: 125px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">xpath</div></div></div></foreignObject><text x="125" y="549" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">xpath</text></switch></g><rect x="65" y="477.5" width="160" height="35" rx="8.4" ry="8.4" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 495px; margin-left: 66px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">fe_adapter_handle_native_msg</div></div></div></foreignObject><text x="145" y="497" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">fe_adapter_handle_native_msg</text></switch></g><path d="M 105 223.28 L 105 243.31 L 105 225.77 L 105 237.93" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 105 244.68 L 100.5 235.68 L 105 237.93 L 109.5 235.68 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="45.02" y="188.28" width="120" height="35" rx="8.4" ry="8.4" fill="#ffffc0" stroke="rgb(0, 0, 0)" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 206px; margin-left: 46px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">"show mgmt get-data-tree WORD$path [json|xml]"</div></div></div></foreignObject><text x="105" y="208" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">"show mgmt get-data-tree WORD...</text></switch></g><path d="M 105.02 163.92 L 105 183.92 L 105 168.31 L 105 180.41" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 105 187.16 L 100.5 178.16 L 105 180.41 L 109.5 178.16 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="105.02" cy="123.92" rx="60" ry="40" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 124px; margin-left: 46px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">EVENT: VTYSH_READ</div></div></div></foreignObject><text x="105" y="126" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">EVENT: VTYSH_READ</text></switch></g><path d="M 310 108.92 L 310 20 L 156.63 20" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 149.88 20 L 158.88 15.5 L 156.63 20 L 158.88 24.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 110.02 40 L 110.02 76.19" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 110.02 82.94 L 105.52 73.94 L 110.02 76.19 L 114.52 73.94 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="71.28" y="0" width="77.48" height="40" rx="9.6" ry="9.6" fill="#cdeb8b" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 75px; height: 1px; padding-top: 20px; margin-left: 72px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"><div>VTYSH</div></div></div></div></foreignObject><text x="110" y="22" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="8px" text-anchor="middle">VTYSH</text></switch></g><rect x="105" y="715" width="120" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 730px; margin-left: 106px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_create_txn</div></div></div></foreignObject><text x="165" y="732" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_create_txn</text></switch></g><rect x="330" y="637.83" width="120" height="30" rx="7.2" ry="7.2" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 653px; margin-left: 331px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">mgmt_txn_req_alloc</div></div></div></foreignObject><text x="390" y="655" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">mgmt_txn_req_alloc</text></switch></g><path d="M 390 674.2 L 390 708.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 390 668.95 L 393.5 675.95 L 390 674.2 L 386.5 675.95 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 390 713.88 L 386.5 706.88 L 390 708.63 L 393.5 706.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 686px; margin-left: 425px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">txn_req<br />MGMTD_TXN_PROC_GETTREE</div></div></div></foreignObject><text x="425" y="689" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">txn_req...</text></switch></g><path d="M 536.96 680.04 C 531.58 676.78 524.78 677.42 520.07 681.64 C 515.37 685.86 513.83 692.7 516.25 698.62 C 518.67 704.53 524.51 708.18 530.73 707.68 C 536.96 707.18 542.17 702.63 543.66 696.4" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 542.21 696.4 L 544.38 692.68 L 545 697.14 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 693px; margin-left: 547px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><div style="text-align: center;"><span style="background-color: initial;">for each of the clients</span></div><div style="text-align: center;"><span style="background-color: initial;">in bitmask</span></div></div></div></div></foreignObject><text x="547" y="696" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px">for ea...</text></switch></g><rect x="650" y="176.42" width="130" height="25" rx="6" ry="6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 189px; margin-left: 651px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_client_send_native_msg</div></div></div></foreignObject><text x="715" y="191" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">be_client_send_native_msg</text></switch></g><rect x="540" y="427.83" width="160" height="35" rx="8.4" ry="8.4" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 445px; margin-left: 541px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_adapter_handle_native_msg</div></div></div></foreignObject><text x="620" y="447" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">be_adapter_handle_native_msg</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 351px; margin-left: 541px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="font-size: 10px;">socket connection<br style="font-size: 10px;" /></i>BE client -> BE adapter<br />MGMT_MSG_CODE_TREE_DATA<br /><i>struct mgmt_msg_tree_data</i></div></div></div></foreignObject><text x="541" y="354" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket connection...</text></switch></g><rect x="660" y="186.42" width="130" height="25" rx="6" ry="6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 199px; margin-left: 661px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_client_send_native_msg</div></div></div></foreignObject><text x="725" y="201" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">be_client_send_native_msg</text></switch></g><rect x="470" y="92.02" width="380" height="207.57" rx="49.82" ry="49.82" fill="#e6ffcc" stroke="#36393d" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 378px; height: 1px; padding-top: 99px; margin-left: 471px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 12px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;"><div style="font-size: 12px;">Backend Client (ospfd, staticd, ...)</div></div></div></div></foreignObject><text x="660" y="111" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="12px" text-anchor="middle" font-weight="bold">Backend Client (ospfd, staticd, ...)</text></switch></g><path d="M 710 184.83 L 710 176.23 L 710 152.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 710 190.08 L 706.5 183.08 L 710 184.83 L 713.5 183.08 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><path d="M 710 147.25 L 713.5 154.25 L 710 152.5 L 706.5 154.25 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 168px; margin-left: 780px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-style: italic; white-space: nowrap;"><span style="font-style: normal;">(1) build oper state tree<br /></span>struct mgmt_msg_tree_data</div></div></div></foreignObject><text x="780" y="171" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle" font-style="italic">(1) build oper state tree...</text></switch></g><path d="M 680 203.69 L 660 203.69 L 626.37 203.69" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 621.12 203.69 L 628.12 200.19 L 626.37 203.69 L 628.12 207.19 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 201px; margin-left: 650px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">(2)</div></div></div></foreignObject><text x="650" y="201" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">(2)</text></switch></g><rect x="680" y="191.2" width="140" height="25" rx="6" ry="6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 138px; height: 1px; padding-top: 204px; margin-left: 681px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_client_handle_get_tree</div></div></div></foreignObject><text x="750" y="206" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">be_client_handle_get_tree</text></switch></g><rect x="680" y="121.13" width="110" height="25" rx="6" ry="6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 108px; height: 1px; padding-top: 134px; margin-left: 681px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">nb_oper_data_iterate</div></div></div></foreignObject><text x="735" y="136" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">nb_oper_data_iterate</text></switch></g><path d="M 750 241.8 L 750 228.62 L 750 222.57" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 750 217.32 L 753.5 224.32 L 750 222.57 L 746.5 224.32 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="660" y="241.8" width="160" height="35" rx="8.4" ry="8.4" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 259px; margin-left: 661px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_client_handle_native_msg</div></div></div></foreignObject><text x="740" y="261" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">be_client_handle_native_msg</text></switch></g><path d="M 555 216.2 L 555 312 Q 555 322 565 322 L 610 322 Q 620 322 620 332 L 620 423.36" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 615 415.59 L 620 425.59 L 625 415.59" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><rect x="490" y="191.2" width="130" height="25" rx="6" ry="6" fill="#ffffc0" stroke="#ff0000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 204px; margin-left: 491px;"><div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 8px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">be_client_send_native_msg</div></div></div></foreignObject><text x="555" y="206" fill="#000000" font-family="Verdana" font-size="8px" text-anchor="middle">be_client_send_native_msg</text></switch></g><path d="M 646.96 480.25 C 641.58 476.68 634.78 477.38 630.07 482 C 625.37 486.62 623.83 494.11 626.25 500.58 C 628.67 507.05 634.51 511.05 640.73 510.5 C 646.96 509.95 652.17 504.97 653.66 498.16" fill="none" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><path d="M 652.21 498.16 L 654.38 494.08 L 655 498.97 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 494px; margin-left: 657px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><div style="text-align: center;"><span style="background-color: initial;">for each of the</span></div>queried <span style="background-color: initial; text-align: center;">BE clients</span></div></div></div></foreignObject><text x="657" y="497" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px">for ea...</text></switch></g><path d="M 740 281.27 L 740 720 Q 740 730 730 730 L 685 730" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 745 289.04 L 740 279.04 L 735 289.04" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 349px; margin-left: 830px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">socket connection<br style="border-color: var(--border-color);" /></i>BE adapter -> BE client<br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;" /><span style="font-family: Verdana; font-size: 10px;">MGMT_MSG_CODE_GET_TREE</span><br style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;" /><i style="border-color: var(--border-color); font-family: Verdana; font-size: 10px;">struct mgmt_msg_get_tree</i></div></div></div></foreignObject><text x="830" y="352" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket connection...</text></switch></g><path d="M 305 305.8 L 305 285.77 L 305 296.77 L 305 283.17" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 305 277.92 L 308.5 284.92 L 305 283.17 L 301.5 284.92 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/><rect x="225" y="305.8" width="160" height="35" rx="8.4" ry="8.4" fill="#b1ddf0" stroke="#10739e" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 323px; margin-left: 226px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 7px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">fe_client_handle_native_msg</div></div></div></foreignObject><text x="305" y="325" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="7px" text-anchor="middle">fe_client_handle_native_msg</text></switch></g><rect x="920" y="10" width="130" height="100" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/><path d="M 940 40 L 1025.55 40" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 1017.78 45 L 1027.78 40 L 1017.78 35" fill="none" stroke="#c73500" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 30px; margin-left: 975px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i style="font-size: 10px;">socket </i>async</div></div></div></foreignObject><text x="975" y="33" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">socket async</text></switch></g><path d="M 940 70 L 960 70 Q 970 70 980 70 L 1025.53 70" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="2 8" pointer-events="stroke"/><path d="M 1017.76 75 L 1027.76 70 L 1017.76 65" fill="none" stroke="#001dbc" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 60px; margin-left: 970px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><span style="font-size: 10px;"><font style="font-size: 10px;">timer/event </font>async<br style="font-size: 10px;" /></span></div></div></div></foreignObject><text x="970" y="63" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">timer/event async </text></switch></g><path d="M 940 100 L 1017.13 99.27" fill="none" stroke="#330000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1023.88 99.21 L 1014.92 103.8 L 1017.13 99.27 L 1014.84 94.8 Z" fill="#330000" stroke="#330000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 90px; margin-left: 978px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 9px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;">function sync</div></div></div></foreignObject><text x="978" y="93" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="9px" text-anchor="middle">function sync</text></switch></g><path d="M 280 790 L 280 400" fill="none" stroke="#b3b3b3" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 772px; margin-left: 189px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i><font color="#999999">mgmt_fe_adapter.c</font></i></div></div></div></foreignObject><text x="189" y="775" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">mgmt_fe_adapter.c</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 772px; margin-left: 389px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i><font color="#999999">mgmt_txn.c</font></i></div></div></div></foreignObject><text x="389" y="775" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">mgmt_txn.c</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 772px; margin-left: 624px;"><div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 10px; font-family: Verdana; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"><i><font color="#999999">mgmt_be_adapter.c</font></i></div></div></div></foreignObject><text x="624" y="775" fill="rgb(0, 0, 0)" font-family="Verdana" font-size="10px" text-anchor="middle">mgmt_be_adapter.c</text></switch></g><path d="M 500 790 L 500 400" fill="none" stroke="#b3b3b3" stroke-width="2" stroke-miterlimit="10" stroke-dasharray="6 6" pointer-events="stroke"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 595px; margin-left: 500px;"><div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;"><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">z</div></div></div></foreignObject><text x="500" y="599" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">z</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Text is not SVG - cannot display</text></a></switch></svg> \ No newline at end of file diff --git a/doc/figures/cligraph.png b/doc/figures/cligraph.png new file mode 100644 index 0000000..96b84e8 Binary files /dev/null and b/doc/figures/cligraph.png differ diff --git a/doc/figures/cligraph.svg b/doc/figures/cligraph.svg new file mode 100644 index 0000000..a1dd017 --- /dev/null +++ b/doc/figures/cligraph.svg @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" + "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Generated by graphviz version 2.38.0 (20140413.2041) + --> +<!-- Title: %3 Pages: 1 --> +<svg width="300pt" height="980pt" + viewBox="0.00 0.00 299.50 980.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 976)"> +<title>%3 + + +n0xd46960 + +START_TKN + + +n0xd46be0 + +WORD_TKN +" +show +" + + +n0xd46960->n0xd46be0 + + + + +n0xd47f80 + +FORK_TKN + + +n0xd46be0->n0xd47f80 + + + + +n0xd47c70 + +WORD_TKN +" +ip +" + + +n0xd47f80->n0xd47c70 + + + + +n0xd484c0 + +JOIN_TKN + + +n0xd47f80->n0xd484c0 + + + + +n0xd47c70->n0xd484c0 + + + + +n0xd47ca0 + +WORD_TKN +" +bgp +" + + +n0xd484c0->n0xd47ca0 + + + + +n0xd48540 + +WORD_TKN +" +neighbors +" + + +n0xd47ca0->n0xd48540 + + + + +n0xd490c0 + +FORK_TKN + + +n0xd48540->n0xd490c0 + + + + +n0xd48fc0 + +IPV4_TKN +A.B.C.D + + +n0xd490c0->n0xd48fc0 + + + + +n0xd491e0 + +JOIN_TKN + + +n0xd490c0->n0xd491e0 + + + + +n0xd49340 + +IPV6_TKN +X:X::X:X + + +n0xd490c0->n0xd49340 + + + + +n0xd49480 + +VARIABLE_TKN +WORD + + +n0xd490c0->n0xd49480 + + + + +n0xd48fc0->n0xd491e0 + + + + +n0xd496e0 + +FORK_TKN + + +n0xd491e0->n0xd496e0 + + + + +n0xd495e0 + +WORD_TKN +" +json +" + + +n0xd496e0->n0xd495e0 + + + + +n0xd497c0 + +JOIN_TKN + + +n0xd496e0->n0xd497c0 + + + + +n0xd495e0->n0xd497c0 + + + + +end0xd49900 + +end + + +n0xd497c0->end0xd49900 + + + + +n0xd49340->n0xd491e0 + + + + +n0xd49480->n0xd491e0 + + + + + diff --git a/doc/figures/fig-normal-processing.dia b/doc/figures/fig-normal-processing.dia new file mode 100644 index 0000000..c9e8e68 --- /dev/null +++ b/doc/figures/fig-normal-processing.dia @@ -0,0 +1,1738 @@ + + + + + + + + + + + + + #A4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Best +Path +Selection# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Local +RIB# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From Peer A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From Peer B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From Peer C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From Peer D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To Peer A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To Peer B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To Peer C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To Peer D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #“Out” Filter +for Peer X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #“In” Filter +for Peer X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-normal-processing.png b/doc/figures/fig-normal-processing.png new file mode 100644 index 0000000..e4b8fdc Binary files /dev/null and b/doc/figures/fig-normal-processing.png differ diff --git a/doc/figures/fig-normal-processing.txt b/doc/figures/fig-normal-processing.txt new file mode 100644 index 0000000..01f0e17 --- /dev/null +++ b/doc/figures/fig-normal-processing.txt @@ -0,0 +1,11 @@ + + _______________________________ + / _________ _________ \ +From Peer A --->|(A)-|Best | | |-[A]|--->To Peer A +From Peer B --->|(B)-|Path |-->|Local-RIB|-[B]|--->To Peer B +From Peer C --->|(C)-|Selection| | |-[C]|--->To Peer C +From Peer D --->|(D)-|_________| |_________|-[D]|--->To Peer D + \_______________________________/ + +Key: (X) - 'In' Filter applied to Peer X's announcements + [X] - 'Out' Filter applied to announcements to Peer X diff --git a/doc/figures/fig-rs-processing.dia b/doc/figures/fig-rs-processing.dia new file mode 100644 index 0000000..b2bf213 --- /dev/null +++ b/doc/figures/fig-rs-processing.dia @@ -0,0 +1,4239 @@ + + + + + + + + + + + + + #A4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Best +Path +Selection# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Main +Loc-RIB# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From Peer A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From RS-Client B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #“Out” Filter +for Peer X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #“In” Filter +for Peer X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From RS-Client D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #From RS-Client C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Best +Path +Selection# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Loc-RIB +For C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Best +Path +Selection# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Loc-RIB +For D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Best +Path +Selection# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Loc-RIB +For B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Export Policy +of RS-Client X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Import Policy +of RS-Client X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #X# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To RS-Client B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #B# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To RS-Client C# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To Peer A# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #D# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #To RS-Client D# + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-rs-processing.png b/doc/figures/fig-rs-processing.png new file mode 100644 index 0000000..1f77263 Binary files /dev/null and b/doc/figures/fig-rs-processing.png differ diff --git a/doc/figures/fig-rs-processing.txt b/doc/figures/fig-rs-processing.txt new file mode 100644 index 0000000..eafe146 --- /dev/null +++ b/doc/figures/fig-rs-processing.txt @@ -0,0 +1,47 @@ +From Peer A + | From RS-Client B + | | From RS-Client C + | | | From RS-Client D + | | | | + | | | | Main / Normal RIB + | | | | ________________________________ + | | | | / _________ _________ \ + | | | +--->|(D)-|Best | | Main | | + | | +--|--->|(C)-|Path |-->|Local-RIB|->[A]|--->To Peer A + | +--|--|--->|(B)-|Selection| | | | + +--|--|--|--->|(A)-|_________| |_________| | + | | | | \________________________________/ + | | | | + | | | | ________________________________ + | | | | / _________ _________ \ + | | | +--->*D*->|{B}-|Best | |RS-Client| | + | | +--|--->*C*->|{B}-|Path |-->|Local-RIB|->[B]|--->To RS-Client B + | | | | | |Selection| | for B | | + +--|--|--|-------->|{B}-|_________| |_________| | + | | | | \________________________________/ + | | | | + | | | | ________________________________ + | | | | / _________ _________ \ + | | | +--->*D*->|{C}-|Best | |RS-Client| | + | | | | | |Path |-->|Local-RIB|->[C]|--->To RS-Client C + | +--|--|--->*B*->|{C}-|Selection| | for C | | + +--|--|--|-------->|{C}-|_________| |_________| | + | | | \________________________________/ + | | | + | | | ________________________________ + | | | / _________ _________ \ + | | | | |Best | |RS-Client| | + | | +------>*C*->|{D}-|Path |-->|Local-RIB|->[D]|--->To RS-Client D + | +--------->*B*->|{D}-|Selection| | for D | | + +----------------->|{D}-|_________| |_________| | + \________________________________/ + + +Key: (X) - 'In' Filter applied to Peer X's announcements before + considering announcement for the normal main Local-RIB + [X] - 'Out' Filter applied to announcements to Peer X + *X* - 'Export' Filter of RS-Client X, to apply X's policies + before its routes may be considered for other RS-Clients + RIBs. + {X} - 'Import' Filter of RS-Client X, to apply X's policies + on routes before allowing them into X's RIB. diff --git a/doc/figures/fig-vnc-commercial-route-reflector.dia b/doc/figures/fig-vnc-commercial-route-reflector.dia new file mode 100644 index 0000000..0da5bd1 --- /dev/null +++ b/doc/figures/fig-vnc-commercial-route-reflector.dia @@ -0,0 +1,794 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 5 +VN 172.16.130.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 6 +VN 172.16.132.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 7 +VN 172.16.6.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 8 +VN 172.16.8.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 9 +VN 172.16.134.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 3 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Commercial Router +Route Reflector +192.168.1.104# + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-commercial-route-reflector.png b/doc/figures/fig-vnc-commercial-route-reflector.png new file mode 100644 index 0000000..ca8a248 Binary files /dev/null and b/doc/figures/fig-vnc-commercial-route-reflector.png differ diff --git a/doc/figures/fig-vnc-commercial-route-reflector.txt b/doc/figures/fig-vnc-commercial-route-reflector.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig-vnc-frr-route-reflector.dia b/doc/figures/fig-vnc-frr-route-reflector.dia new file mode 100644 index 0000000..634f0b1 --- /dev/null +++ b/doc/figures/fig-vnc-frr-route-reflector.dia @@ -0,0 +1,763 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 5 +VN 172.16.130.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 6 +VN 172.16.132.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 7 +VN 172.16.6.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 8 +VN 172.16.8.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 9 +VN 172.16.134.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #BGP Route Reflector 1 +192.168.1.100# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 3 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-frr-route-reflector.png b/doc/figures/fig-vnc-frr-route-reflector.png new file mode 100644 index 0000000..4770521 Binary files /dev/null and b/doc/figures/fig-vnc-frr-route-reflector.png differ diff --git a/doc/figures/fig-vnc-frr-route-reflector.txt b/doc/figures/fig-vnc-frr-route-reflector.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig-vnc-gw-rr.dia b/doc/figures/fig-vnc-gw-rr.dia new file mode 100644 index 0000000..dab27f7 --- /dev/null +++ b/doc/figures/fig-vnc-gw-rr.dia @@ -0,0 +1,1155 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 1 +VN 172.16.1.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 2 +VN 172.16.2.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 3 +VN 172.16.3.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 1 +172.16.1.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 2 +172.16.2.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 3 +172.16.3.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 4 +172.16.4.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #VNC Gateway 1 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 1 (NVA) +192.168.1.103# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 (NVA) +192.168.1.104# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #VNC Gateway 2 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RR +192.168.1.105# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-gw-rr.png b/doc/figures/fig-vnc-gw-rr.png new file mode 100644 index 0000000..7ae0630 Binary files /dev/null and b/doc/figures/fig-vnc-gw-rr.png differ diff --git a/doc/figures/fig-vnc-gw-rr.txt b/doc/figures/fig-vnc-gw-rr.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig-vnc-gw.dia b/doc/figures/fig-vnc-gw.dia new file mode 100644 index 0000000..8270e20 --- /dev/null +++ b/doc/figures/fig-vnc-gw.dia @@ -0,0 +1,1058 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 1 +VN 172.16.1.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 2 +VN 172.16.2.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 3 +VN 172.16.3.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 1 +172.16.1.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 2 +172.16.2.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 3 +172.16.3.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #CE 4 +172.16.4.2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #VNC Gateway 1 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 1 (NVA) +192.168.1.103# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 (NVA) +192.168.1.104# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #VNC Gateway 2 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-gw.png b/doc/figures/fig-vnc-gw.png new file mode 100644 index 0000000..df8f23f Binary files /dev/null and b/doc/figures/fig-vnc-gw.png differ diff --git a/doc/figures/fig-vnc-gw.txt b/doc/figures/fig-vnc-gw.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig-vnc-mesh.dia b/doc/figures/fig-vnc-mesh.dia new file mode 100644 index 0000000..a8f702f --- /dev/null +++ b/doc/figures/fig-vnc-mesh.dia @@ -0,0 +1,1071 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 1 +192.168.1.100# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 3 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 7 +VN 172.16.6.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 8 +VN 172.16.8.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 9 +VN 172.16.134.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 5 +VN 172.16.130.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 6 +VN 172.16.132.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 1 +VN 172.16.0.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 2 +VN 172.16.2.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 3 +VN 172.16.128.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-mesh.png b/doc/figures/fig-vnc-mesh.png new file mode 100644 index 0000000..fa0762d Binary files /dev/null and b/doc/figures/fig-vnc-mesh.png differ diff --git a/doc/figures/fig-vnc-mesh.txt b/doc/figures/fig-vnc-mesh.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig-vnc-redundant-route-reflectors.dia b/doc/figures/fig-vnc-redundant-route-reflectors.dia new file mode 100644 index 0000000..4065b8b --- /dev/null +++ b/doc/figures/fig-vnc-redundant-route-reflectors.dia @@ -0,0 +1,871 @@ + + + + + + + + + + + + + #Letter# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 4 +VN 172.16.4.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 5 +VN 172.16.130.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 6 +VN 172.16.132.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 7 +VN 172.16.6.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 8 +VN 172.16.8.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVE 9 +VN 172.16.134.1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #BGP Route Reflector 1 +192.168.1.100# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #Commercial Router +Route Reflector +192.168.1.104# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 2 +192.168.1.101# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #NVA 3 +192.168.1.102# + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig-vnc-redundant-route-reflectors.png b/doc/figures/fig-vnc-redundant-route-reflectors.png new file mode 100644 index 0000000..06a27b6 Binary files /dev/null and b/doc/figures/fig-vnc-redundant-route-reflectors.png differ diff --git a/doc/figures/fig-vnc-redundant-route-reflectors.txt b/doc/figures/fig-vnc-redundant-route-reflectors.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc/figures/fig_dmvpn_topologies.png b/doc/figures/fig_dmvpn_topologies.png new file mode 100644 index 0000000..a0dcc3e Binary files /dev/null and b/doc/figures/fig_dmvpn_topologies.png differ diff --git a/doc/figures/fig_topologies_full.dia b/doc/figures/fig_topologies_full.dia new file mode 100644 index 0000000..7ec3398 --- /dev/null +++ b/doc/figures/fig_topologies_full.dia @@ -0,0 +1,533 @@ + + + + + + + + + + + + + #A4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF3# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF1# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig_topologies_full.png b/doc/figures/fig_topologies_full.png new file mode 100644 index 0000000..d39e5e2 Binary files /dev/null and b/doc/figures/fig_topologies_full.png differ diff --git a/doc/figures/fig_topologies_full.txt b/doc/figures/fig_topologies_full.txt new file mode 100644 index 0000000..cc8025a --- /dev/null +++ b/doc/figures/fig_topologies_full.txt @@ -0,0 +1,6 @@ +(RF1)--(RF2) + | \ / | + | \/ | + | /\ | + | / \ | +(RF3)--(RF4) diff --git a/doc/figures/fig_topologies_rs.dia b/doc/figures/fig_topologies_rs.dia new file mode 100644 index 0000000..f8aa18d --- /dev/null +++ b/doc/figures/fig_topologies_rs.dia @@ -0,0 +1,499 @@ + + + + + + + + + + + + + #A4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RS# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF2# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF4# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF3# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #RF1# + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/fig_topologies_rs.png b/doc/figures/fig_topologies_rs.png new file mode 100644 index 0000000..014225c Binary files /dev/null and b/doc/figures/fig_topologies_rs.png differ diff --git a/doc/figures/fig_topologies_rs.txt b/doc/figures/fig_topologies_rs.txt new file mode 100644 index 0000000..0bd1730 --- /dev/null +++ b/doc/figures/fig_topologies_rs.txt @@ -0,0 +1,5 @@ +(RF1) (RF2) + \ / + [RS] + / \ +(RF3) (RF4) diff --git a/doc/figures/frr-icon.svg b/doc/figures/frr-icon.svg new file mode 100644 index 0000000..f7bdebc --- /dev/null +++ b/doc/figures/frr-icon.svg @@ -0,0 +1,39 @@ + +image/svg+xml \ No newline at end of file diff --git a/doc/figures/frr-logo-icon.png b/doc/figures/frr-logo-icon.png new file mode 100644 index 0000000..37973fc Binary files /dev/null and b/doc/figures/frr-logo-icon.png differ diff --git a/doc/figures/frr-logo-medium.png b/doc/figures/frr-logo-medium.png new file mode 100644 index 0000000..04e18eb Binary files /dev/null and b/doc/figures/frr-logo-medium.png differ diff --git a/doc/figures/frr-logo-small.png b/doc/figures/frr-logo-small.png new file mode 100644 index 0000000..2a9a2c7 Binary files /dev/null and b/doc/figures/frr-logo-small.png differ diff --git a/doc/figures/frr-logo.png b/doc/figures/frr-logo.png new file mode 100644 index 0000000..6e66414 Binary files /dev/null and b/doc/figures/frr-logo.png differ diff --git a/doc/figures/git_branches.png b/doc/figures/git_branches.png new file mode 100644 index 0000000..21001c3 Binary files /dev/null and b/doc/figures/git_branches.png differ diff --git a/doc/figures/git_branches.svg b/doc/figures/git_branches.svg new file mode 100644 index 0000000..0c2c96e --- /dev/null +++ b/doc/figures/git_branches.svg @@ -0,0 +1,720 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + 1.0ReleaseBranch + + + + + + + + + + Master(Stable) + + + + + + + + + 1.1ReleaseBranch + Version 1.0.a1 + Version 1.1.a1 + Version 1.1.a2 + + + + + Version 1.1.b1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Patch Email (Patchwork) + Github Pull Request + Github Pull Request + Patch Email (Patchwork) + Patch Email (Patchwork) + Github Pull Request + Github Pull Request + Github Pull Request + Patch Email (Patchwork) + Github Pull Request + Github Pull Request + Github Pull Request + Github Pull Request + Github Pull Request + Github Pull Request + + + + + + + + + Version 1.0.a2 + Version 1.0.b1 + Version 1.0.0 + Version 1.1.0 + Version 1.1.1 + Version 1.1.2 + + + + + + + + + + + + + + + + + + diff --git a/doc/figures/nodes.dot b/doc/figures/nodes.dot new file mode 100644 index 0000000..4ce147b --- /dev/null +++ b/doc/figures/nodes.dot @@ -0,0 +1,63 @@ +/* + * FRR CLI modes and their relationships. + * + * Each edge is labeled with the command that causes a transition along that + * edge. Exit commands and their back edges are implicit. + */ +digraph climodes { + ratio = "auto" + mincross = 2.0 + graph [fontsize = 9] + rankdir = LR + + AUTH_NODE -> VIEW_NODE -> ENABLE_NODE; + VIEW_NODE -> AUTH_ENABLE_NODE -> ENABLE_NODE; + ENABLE_NODE -> CONFIG_NODE [ label="configure terminal" ]; + CONFIG_NODE -> RIP_NODE [ label="router rip" ]; + CONFIG_NODE -> RIPNG_NODE [ label="router ripng" ]; + CONFIG_NODE -> BABEL_NODE [ label="router babel" ]; + CONFIG_NODE -> EIGRP_NODE [ label="router eigrp (1-65535)" ]; + CONFIG_NODE -> BGP_NODE [ label="router bgp ASN" ]; + subgraph cluster0 { + BGP_NODE -> BGP_VPNV4_NODE [ label="address-family vpnv4 [unicast]" ]; + BGP_NODE -> BGP_VPNV6_NODE [ label="address-family vpnv6 [unicast]" ]; + BGP_NODE -> BGP_IPV4_NODE [ label="address-family ipv4 [unicast]" ]; + BGP_NODE -> BGP_IPV4L_NODE [ label="address-family ipv4 labeled-unicast" ]; + BGP_NODE -> BGP_IPV4M_NODE [ label="address-family ipv4 multicast" ]; + BGP_NODE -> BGP_IPV6_NODE [ label="address-family ipv6 [unicast]" ]; + BGP_NODE -> BGP_IPV6L_NODE [ label="address-family ipv6 labeled-unicast" ]; + BGP_NODE -> BGP_IPV6M_NODE [ label="address-family ipv6 multicast" ]; + BGP_NODE -> BGP_FLOWSPECV4_NODE [ label="address-family ipv4 flowspec" ]; + BGP_NODE -> BGP_FLOWSPECV6_NODE [ label="address-family ipv6 flowspec" ]; + BGP_NODE -> BGP_EVPN_NODE [ label="address-family l2vpn evpn" ]; + BGP_EVPN_NODE -> BGP_EVPN_VNI_NODE [ label="vni (1-16777215)" ]; + BGP_NODE -> BGP_VRF_POLICY_NODE [ label="vrf-policy NAME" ]; + BGP_NODE -> BGP_VNC_DEFAULTS_NODE [ label="vnc defaults" ]; + BGP_NODE -> BGP_VNC_NVE_GROUP_NODE [ label="vnc nve-group NAME" ]; + BGP_NODE -> BGP_VNC_L2_GROUP_NODE [ label="vnc l2-group NAME" ]; + } + subgraph cluster1 { + LDP_NODE -> LDP_IPV4_NODE [ label="address-family ipv4" ]; + LDP_NODE -> LDP_IPV6_NODE [ label="address-family ipv6" ]; + LDP_IPV4_NODE -> LDP_IPV4_IFACE_NODE [ label="interface IFNAME" ]; + LDP_IPV6_NODE -> LDP_IPV6_IFACE_NODE [ label="interface IFNAME" ]; + LDP_NODE -> LDP_L2VPN_NODE [ label="address-family l2vpn WORD type vpls" ]; + LDP_NODE -> LDP_PSEUDOWIRE_NODE [ label="member pseudowire IFNAME" ]; + } + CONFIG_NODE -> OSPF_NODE [ label="router ospf [(1-65535)] [vrf NAME]" ]; + CONFIG_NODE -> OSPF6_NODE [ label="router ospf6" ]; + CONFIG_NODE -> LDP_NODE [ label="mpls ldp" ]; + CONFIG_NODE -> ISIS_NODE [ label="router isis WORD [vrf NAME]" ]; + CONFIG_NODE -> RMAP_NODE [ label="route-map WORD (1-65535)" ]; + CONFIG_NODE -> PW_NODE [ label="pseudowire IFNAME" ]; + CONFIG_NODE -> VTY_NODE [ label="line vty" ]; + CONFIG_NODE -> KEYCHAIN_NODE [ label="key chain WORD" ]; + CONFIG_NODE -> KEYCHAIN_KEY_NODE [ label="key (0-2147483647)" ]; + KEYCHAIN_NODE -> KEYCHAIN_KEY_NODE [ label="key (0-2147483647)" ]; + KEYCHAIN_KEY_NODE -> KEYCHAIN_NODE [ label="no key (0-2147483647)" ]; + CONFIG_NODE -> VRF_NODE [ label="vrf NAME" ]; + CONFIG_NODE -> INTERFACE_NODE [ label="interface IFNAME vrf NAME" ]; + INTERFACE_NODE -> LINK_PARAMS_NODE [ label="link-params" ]; + CONFIG_NODE -> NH_GROUP_NODE [ label="nexthop-group NAME" ]; + CONFIG_NODE -> RPKI_NODE [ label="rpki" ]; +} diff --git a/doc/figures/ospf_api_architecture.png b/doc/figures/ospf_api_architecture.png new file mode 100644 index 0000000..bd10a38 Binary files /dev/null and b/doc/figures/ospf_api_architecture.png differ diff --git a/doc/figures/ospf_api_msghdr.png b/doc/figures/ospf_api_msghdr.png new file mode 100644 index 0000000..5bc840f Binary files /dev/null and b/doc/figures/ospf_api_msghdr.png differ diff --git a/doc/figures/ospf_api_msgs1.png b/doc/figures/ospf_api_msgs1.png new file mode 100644 index 0000000..a23cc61 Binary files /dev/null and b/doc/figures/ospf_api_msgs1.png differ diff --git a/doc/figures/ospf_api_msgs2.png b/doc/figures/ospf_api_msgs2.png new file mode 100644 index 0000000..8997ac4 Binary files /dev/null and b/doc/figures/ospf_api_msgs2.png differ diff --git a/doc/figures/pcep_module_threading_overview.svg b/doc/figures/pcep_module_threading_overview.svg new file mode 100644 index 0000000..4d2d2a2 --- /dev/null +++ b/doc/figures/pcep_module_threading_overview.svg @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MAIN PTHREAD + + + + + + CONTROLLER PTHREAD + + + + + + + + CLI + + + + + + + + PATHDINTERFACE + + + + + + + + PCC + + + + + + + + + + + + + + VTYSH + + + + + + Northbound + + + + + + pathd API + + + + + + + + + + + + + + PCEPLIBINTERFACE + + + + + + + + + + + + + + + + + + + + + + + + CONTROLLER + + + + + + pcep_ctrl_XXX + + + + + + pcep_thread_XXX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pceplib API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/figures/threadmaster-multiple.png b/doc/figures/threadmaster-multiple.png new file mode 100644 index 0000000..2ded50c Binary files /dev/null and b/doc/figures/threadmaster-multiple.png differ diff --git a/doc/figures/threadmaster-single.png b/doc/figures/threadmaster-single.png new file mode 100644 index 0000000..a068389 Binary files /dev/null and b/doc/figures/threadmaster-single.png differ diff --git a/doc/figures/threadmaster.svg b/doc/figures/threadmaster.svg new file mode 100644 index 0000000..a8d2c6a --- /dev/null +++ b/doc/figures/threadmaster.svg @@ -0,0 +1,42 @@ + + + + Layer 1 + + + + I/O + task3 + task3 + + ... + + + + Timer + Event + Ready + + + + + + thread_fetch() + thread_call() + task1 + task2 + task9 + task4 + task7 + ... + ... + ... + ... + ... + ... + ... + + + + + diff --git a/doc/licenses/BSD-2-Clause b/doc/licenses/BSD-2-Clause new file mode 100644 index 0000000..d11142d --- /dev/null +++ b/doc/licenses/BSD-2-Clause @@ -0,0 +1,19 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/licenses/BSD-3-Clause b/doc/licenses/BSD-3-Clause new file mode 100644 index 0000000..46b46ec --- /dev/null +++ b/doc/licenses/BSD-3-Clause @@ -0,0 +1,23 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/doc/licenses/GPL-2.0 b/doc/licenses/GPL-2.0 new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/doc/licenses/GPL-2.0 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/doc/licenses/GPL-3.0 b/doc/licenses/GPL-3.0 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/doc/licenses/GPL-3.0 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/doc/licenses/ISC b/doc/licenses/ISC new file mode 100644 index 0000000..02add5e --- /dev/null +++ b/doc/licenses/ISC @@ -0,0 +1,11 @@ +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/doc/licenses/LGPL-2.1 b/doc/licenses/LGPL-2.1 new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/doc/licenses/LGPL-2.1 @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/doc/licenses/LicenseRef-Skiplist-BSD-0-Clause b/doc/licenses/LicenseRef-Skiplist-BSD-0-Clause new file mode 100644 index 0000000..764f7cc --- /dev/null +++ b/doc/licenses/LicenseRef-Skiplist-BSD-0-Clause @@ -0,0 +1,15 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/doc/licenses/MIT b/doc/licenses/MIT new file mode 100644 index 0000000..73e41b1 --- /dev/null +++ b/doc/licenses/MIT @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/doc/licenses/Unlicense b/doc/licenses/Unlicense new file mode 100644 index 0000000..efb9808 --- /dev/null +++ b/doc/licenses/Unlicense @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/doc/manpages/.gitignore b/doc/manpages/.gitignore new file mode 100644 index 0000000..81c60dc --- /dev/null +++ b/doc/manpages/.gitignore @@ -0,0 +1,2 @@ +/_templates +/_build diff --git a/doc/manpages/Makefile b/doc/manpages/Makefile new file mode 100644 index 0000000..7cccfa2 --- /dev/null +++ b/doc/manpages/Makefile @@ -0,0 +1,12 @@ +all: ALWAYS + @$(MAKE) -s -C ../.. doc/manpages/man.stamp +help: ALWAYS + @$(MAKE) -s -C ../.. doc/help +%: ALWAYS + @$(MAKE) -s -C ../.. doc/manpages/_build/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/doc/manpages/bfd-options.rst b/doc/manpages/bfd-options.rst new file mode 100644 index 0000000..e335ed1 --- /dev/null +++ b/doc/manpages/bfd-options.rst @@ -0,0 +1,10 @@ +BFD SOCKET +---------- + +The following option controls the BFD daemon control socket location. + +.. option:: --bfdctl bfd-control-socket + + Opens the BFD daemon control socket located at the pointed location. + + (default: |INSTALL_PREFIX_STATE|/bfdd.sock) diff --git a/doc/manpages/common-options.rst b/doc/manpages/common-options.rst new file mode 100644 index 0000000..a47a233 --- /dev/null +++ b/doc/manpages/common-options.rst @@ -0,0 +1,163 @@ +HELP AND VERSION +---------------- + +.. option:: -h, --help + + Print a short description of the daemon's command line options. + +.. option:: -v, --version + + Print version and build information for the daemon. + +Both of these options inhibit normal operation and will immediately exit. + +PROCESS CONTROL +--------------- +These options control background operation: + +.. option:: -d, --daemon + + Launches the process in background/daemon mode, forking and detaching from the terminal. + + The parent process will delay its exit until the daemon/child has finished its initialization and has entered its main loop. This is important for zebra startup because the other daemons will attempt to connect to zebra. A return from zebra -d guarantees its readiness to accept these connections. + +.. option:: -t, --terminal + + Opens an interactive VTY session on the terminal, allowing for both state and configuration operations. Note that the terminal starts operating after startup has completed and the configuration file has been loaded. + + The process will exit when end of file is detected on the terminal. It is possible to daemonize a process started with -t (but without -d) by sending SIGQUIT to the process (normally mapped to a ^\ keypress.) + + +The combination of :option:`--daemon` and :option:`--terminal` will delay the daemon from going into background until the terminal session ends (by end of file.) + +If the process receives SIGINT (e.g. a ^C keypress) in this mode, it will exit instead of daemonizing. + +It is safe to suspend (SIGTSTP / ^Z) the terminal session opened by the previous two options; this will only stop the terminal but not the protocol daemon itself (which runs in a separate second process.) + +CONFIGURATION AND PATHS +----------------------- +The following options control configuration and file system locations for frr processes: + +.. option:: -f, --config_file config-file + + Specify a configuration file to be used instead of the default /etc/frr/.conf file. + + Note that the daemon will attempt to write to this file if the write file command is issued on its VTY interface or through vtysh. + +.. option:: -C, --dryrun + + Load the configuration file and check its validity, then exit. + +.. option:: -i, --pid_file pid-file + + Output a pid file to a location other than the default /var/run/frr/.pid. + +.. option:: -z, --socket zclient-path + + Override the path of the ZAPI socket used to communicate between zebra and the various protocol daemons. The default is /var/run/frr/zserv.api. The value of this option must be the same across all daemons. + +.. option:: -N, --pathspace pathspace + + Insert pathspace into all default paths, changing the defaults to: + + /etc/frr/pathspace/.conf + /var/run/frr/pathspace/.pid + /var/run/frr/pathspace/.vty + /var/run/frr/pathspace/zserv.api + + ´.´ and ´/´ characters will not be accepted in pathspace, but the empty string will be accepted. + + Note that this only changes the respective defaults, it has no effect on the respective path if the -f, -i, -z or --vty_socket options are used. + + The purpose of this option is to easily group all file system related bits together for running multiple fully-separate "logical routers" on a system, particularly with Linux network namespaces. Groups of daemons running with distinct pathspace values will be completely unaware of each other and not interact in any way. + + This option does not do any system setup (like network namespaces.) This must be done by the user, for example by running: + + ip netns exec namespace -N namespace + + +PROCESS CREDENTIALS +------------------- +.. option:: -u, --user user + + (default: frr) + +.. option:: -g, --group group + + (default: frr) + + Change the user/group which the daemon will switch to. + +.. option:: -S, --skip_runas + + Skip setting the process effective user and group. + + +Note that there is an additional group, frrvty, which controls group ownership of the VTY sockets. The name of this group cannot currently be changed, and user must be a member of this group. + + +VTY SETUP +--------- +These following options control the daemon's VTY (interactive command line) interface. The interface is available over TCP, using the telnet protocol, as well as through the vtysh frontend. + +.. option:: -A, --vty_addr vty-addr + + Specify an IP/IPv6 address to bind the TCP VTY interface to. It is generally recommended to specify ::1 or 127.0.0.1. For reasons of backwards compatibility, the default is to listen on all interfaces. + +.. option:: -P, --vty_port vty-port + + Override the daemon's default TCP VTY port (each daemon has a different default value upwards of 2600, listed below.) Specifying 0 disables the TCP VTY interface. + + Default ports are::: + + zebra 2601 + ripd 2602 + ripngd 2603 + ospfd 2604 + bgpd 2605 + ospf6d 2606 + isisd 2608 + babeld 2609 + nhrpd 2610 + pimd 2611 + ldpd 2612 + eigrpd 2613 + pbrd 2615 + staticd 2616 + bfdd 2617 + fabricd 2618 + vrrpd 2619 + + Port 2607 is used for ospfd's Opaque LSA API. + +.. option:: --vty_socket vty-path + + Overrides the directory used for the .vty sockets. vtysh connects to these sockets in order to access each daemon's VTY. + Default: /var/run/frr[/] + + NB: Unlike the other options, this option specifies a directory, not a full path. + + This option is primarily used by the SNAP packaging system, its semantics may change. It should not be necessary in most other scenarios. + +MODULE LOADING +-------------- +frr supports optional dynamically loadable modules, although these can only be loaded at startup. The set of available modules may vary across distributions and packages, and modules may be available for installation as separate packages. + +.. option:: -M, --module module[:options] + + Load a module named module, optionally passing options to it. + + If there is a ´/´ character in module, the value is assumed to be a pathname to a module. + + If there is no ´/´ character, the module directory (see next option) is searched first for a module named "_.so", then for ".so". This allows for a module to exist in variations appropriate for particular daemons, e.g. zebra_snmp and bgp_snmp, with the correct one selected by -M snmp. + + The meaning of options is specific to the module being loaded. Most modules currently ignore it. + + Modules are loaded in the order as listed on the command line. This is not generally relevant. + +.. option:: --moduledir module-path + + Look for modules in the module-path directory instead of the default /usr/lib/frr/modules. (This path is not affected by the -N option.) + +The list of loaded modules can be inspected at runtime with the show modules VTY command. + diff --git a/doc/manpages/conf.py b/doc/manpages/conf.py new file mode 100644 index 0000000..73dea09 --- /dev/null +++ b/doc/manpages/conf.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# +# FRR documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 31 16:00:52 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import re + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = "1.0" + +# prolog for various variable substitutions +rst_prolog = "" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.todo"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst'] +source_suffix = ".rst" + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = u"FRR" +copyright = u"2017, FRR" +author = u"FRR authors" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The short X.Y version. +version = u"?.?" +# The full version, including alpha/beta/rc tags. +release = u"?.?-?" + + +# ----------------------------------------------------------------------------- +# Extract values from codebase for substitution into docs. +# ----------------------------------------------------------------------------- + +# Various installation prefixes. Values are extracted from config.status. +# Reasonable defaults are set in case that file does not exist. +replace_vars = { + "AUTHORS": author, + "COPYRIGHT_YEAR": "1999-2005", + "COPYRIGHT_STR": "Copyright (c) 1999-2005", + "PACKAGE_NAME": project.lower(), + "PACKAGE_TARNAME": project.lower(), + "PACKAGE_STRING": project.lower() + " latest", + "PACKAGE_URL": "https://frrouting.org/", + "PACKAGE_VERSION": "latest", + "INSTALL_PREFIX_ETC": "/etc/frr", + "INSTALL_PREFIX_SBIN": "/usr/lib/frr", + "INSTALL_PREFIX_STATE": "/var/run/frr", + "INSTALL_PREFIX_MODULES": "/usr/lib/frr/modules", + "INSTALL_USER": "frr", + "INSTALL_GROUP": "frr", + "INSTALL_VTY_GROUP": "frrvty", + "GROUP": "frr", + "USER": "frr", +} + +# extract version information, installation location, other stuff we need to +# use when building final documents +val = re.compile('^S\["([^"]+)"\]="(.*)"$') +try: + with open("../../config.status", "r") as cfgstatus: + for ln in cfgstatus.readlines(): + m = val.match(ln) + if not m or m.group(1) not in replace_vars.keys(): + continue + replace_vars[m.group(1)] = m.group(2) +except IOError: + # if config.status doesn't exist, just ignore it + pass + +# manually fill out some of these we can't get from config.status +replace_vars["COPYRIGHT_STR"] = "Copyright (c)" +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["COPYRIGHT_YEAR"]) +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["AUTHORS"]) +release = replace_vars["PACKAGE_VERSION"] +version = release.split("-")[0] + +# add substitutions to prolog +for key, value in replace_vars.items(): + rst_prolog += ".. |{0}| replace:: {1}\n".format(key, value) + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [ + "_build", + "common-options.rst", + "epilogue.rst", + "defines.rst", + "bfd-options.rst", +] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "default" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "FRRdoc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "FRR.tex", u"FRR User Manual", u"FRR", "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). + +# If true, show URL addresses after external links. +# man_show_urls = False + +fwfrr = "{0} routing engine for use with FRRouting." + +man_pages = [ + ("frr-bfdd", "frr-bfdd", fwfrr.format("a bfd"), [], 8), + ("frr-bgpd", "frr-bgpd", fwfrr.format("a BGPv4, BGPv4+, BGPv4-"), [], 8), + ("frr-eigrpd", "frr-eigrpd", fwfrr.format("an EIGRP"), [], 8), + ("frr-fabricd", "frr-fabricd", fwfrr.format("an OpenFabric"), [], 8), + ("frr-isisd", "frr-isisd", fwfrr.format("an IS-IS"), [], 8), + ("frr-ldpd", "frr-ldpd", fwfrr.format("an LDP"), [], 8), + ("frr-nhrpd", "frr-nhrpd", fwfrr.format("a Next Hop Routing Protocol"), [], 8), + ("frr-ospf6d", "frr-ospf6d", fwfrr.format("an OSPFv3"), [], 8), + ("frr-ospfclient", "frr-ospfclient", "an example ospf-api client", [], 8), + ("frr-ospfd", "frr-ospfd", fwfrr.format("an OSPFv2"), [], 8), + ("frr-pbrd", "frr-pbrd", fwfrr.format("a PBR"), [], 8), + ("frr-pimd", "frr-pimd", fwfrr.format("a PIM"), [], 8), + ("frr-ripd", "frr-ripd", fwfrr.format("a RIP"), [], 8), + ("frr-ripngd", "frr-ripngd", fwfrr.format("a RIPNG"), [], 8), + ("frr-sharpd", "frr-sharpd", fwfrr.format("a SHARP"), [], 8), + ("frr-staticd", "frr-staticd", fwfrr.format("a static route manager"), [], 8), + ("frr-vrrpd", "frr-vrrpd", fwfrr.format("a VRRP"), [], 8), + ( + "frr-watchfrr", + "frr-watchfrr", + "a program to monitor the status of FRRouting daemons", + [], + 8, + ), + ( + "frr-zebra", + "frr-zebra", + "a routing manager for use with associated FRRouting components.", + [], + 8, + ), + ("frr", "frr", "a systemd interaction script", [], 1), + ("mtracebis", "mtracebis", "a multicast trace client", [], 8), + ("vtysh", "vtysh", "an integrated shell for FRRouting.", [], 1), +] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# custom extensions here diff --git a/doc/manpages/defines.rst b/doc/manpages/defines.rst new file mode 100644 index 0000000..ac24cfa --- /dev/null +++ b/doc/manpages/defines.rst @@ -0,0 +1,3 @@ +.. |synopsis-options| replace:: [-d|-t|-dt] [-C] [-f config-file] [-i pid-file] [-z zclient-path] [-u user] [-g group] [-A vty-addr] [-P vty-port] [-M module[:options]] [-N pathspace] [--vty_socket vty-path] [--moduledir module-path] +.. |synopsis-options-hv| replace:: [-h] [-v] +.. |seealso-programs| replace:: frr-zebra(8), vtysh(1), frr-ripd(8), frr-ripngd(8), frr-ospfd(8), frr-ospf6d(8), frr-bgpd(8), frr-isisd(8), frr-babeld(8), frr-nhrpd(8), frr-pimd(8), frr-pbrd(8), frr-ldpd(8), frr-eigrpd(8), frr-staticd(8), frr-fabricd(8), frr-vrrpd(8), mtracebis(8) diff --git a/doc/manpages/epilogue.rst b/doc/manpages/epilogue.rst new file mode 100644 index 0000000..77fed81 --- /dev/null +++ b/doc/manpages/epilogue.rst @@ -0,0 +1,16 @@ +WARNING +======= +This man page is intended to be a quick reference for command line options. The definitive document is the info file |PACKAGE_STRING| or the documentation available on the project website at |PACKAGE_URL|. + +DIAGNOSTICS +=========== +The daemon may log to standard output, to a VTY, to a log file, or through syslog to the system logs. FRR supports many debugging options, see the Info file, web docs or source for details. + +SEE ALSO +======== +|seealso-programs| +|PACKAGE_URL| + +BUGS +==== +FRR eats bugs for breakfast. If you have food for the maintainers, please email . diff --git a/doc/manpages/frr-bfdd.rst b/doc/manpages/frr-bfdd.rst new file mode 100644 index 0000000..1f8b147 --- /dev/null +++ b/doc/manpages/frr-bfdd.rst @@ -0,0 +1,40 @@ +**** +BFDD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: bfdd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a communication failure detection component that works with +the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst +.. include:: bfd-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-bgpd.rst b/doc/manpages/frr-bgpd.rst new file mode 100644 index 0000000..f7e2026 --- /dev/null +++ b/doc/manpages/frr-bgpd.rst @@ -0,0 +1,82 @@ +**** +BGPD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: bgpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +.. option:: -p, --bgp_port + + Set the bgp protocol's port number. When port number is 0, that means do not + listen bgp port. + +.. option:: -l, --listenon + + Specify a specific IP address for bgpd to listen on, rather than its default + of ``0.0.0.0`` / ``::``. This can be useful to constrain bgpd to an internal + address, or to run multiple bgpd processes on one host. + +.. option:: -n, --no_kernel + + Do not install learned routes into the linux kernel. This option is useful + for a route-reflector environment or if you are running multiple bgp + processes in the same namespace. This option is different than the --no_zebra + option in that a ZAPI connection is made. + +.. option:: -e, --ecmp + + Run BGP with a limited ecmp capability, that is different than what BGP + was compiled with. The value specified must be greater than 0 and less + than or equal to the MULTIPATH_NUM specified on compilation. + +.. option:: -Z, --no_zebra + + Do not communicate with zebra at all. This is different than the --no_kernel + option in that we do not even open a ZAPI connection to the zebra process. + +.. option:: -s, --socket_size + + When opening tcp connections to our peers, set the socket send buffer + size that the kernel will use for the peers socket. This option + is only really useful at a very large scale. Experimentation should + be done to see if this is helping or not at the scale you are running + at. + +LABEL MANAGER +------------- + +.. option:: -I, --int_num + + Set zclient id. This is required when using Zebra label manager in proxy mode. + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-eigrpd.rst b/doc/manpages/frr-eigrpd.rst new file mode 100644 index 0000000..bc82446 --- /dev/null +++ b/doc/manpages/frr-eigrpd.rst @@ -0,0 +1,38 @@ +****** +EIGRPD +****** + +.. include:: defines.rst +.. |DAEMON| replace:: eigrpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-fabricd.rst b/doc/manpages/frr-fabricd.rst new file mode 100644 index 0000000..c14c076 --- /dev/null +++ b/doc/manpages/frr-fabricd.rst @@ -0,0 +1,38 @@ +******* +FABRICD +******* + +.. include:: defines.rst +.. |DAEMON| replace:: fabricd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-isisd.rst b/doc/manpages/frr-isisd.rst new file mode 100644 index 0000000..68761f6 --- /dev/null +++ b/doc/manpages/frr-isisd.rst @@ -0,0 +1,38 @@ +***** +ISISD +***** + +.. include:: defines.rst +.. |DAEMON| replace:: isisd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-ldpd.rst b/doc/manpages/frr-ldpd.rst new file mode 100644 index 0000000..113f066 --- /dev/null +++ b/doc/manpages/frr-ldpd.rst @@ -0,0 +1,38 @@ +**** +LDPD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: ldpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-nhrpd.rst b/doc/manpages/frr-nhrpd.rst new file mode 100644 index 0000000..cae01c6 --- /dev/null +++ b/doc/manpages/frr-nhrpd.rst @@ -0,0 +1,38 @@ +***** +NHRPD +***** + +.. include:: defines.rst +.. |DAEMON| replace:: nhrpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-ospf6d.rst b/doc/manpages/frr-ospf6d.rst new file mode 100644 index 0000000..cfc6860 --- /dev/null +++ b/doc/manpages/frr-ospf6d.rst @@ -0,0 +1,39 @@ +****** +OSPF6D +****** + +.. include:: defines.rst +.. |DAEMON| replace:: ospf6d + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-ospfclient.rst b/doc/manpages/frr-ospfclient.rst new file mode 100644 index 0000000..c52b108 --- /dev/null +++ b/doc/manpages/frr-ospfclient.rst @@ -0,0 +1,43 @@ +********** +OSPFCLIENT +********** + +.. include:: defines.rst + +SYNOPSIS +======== +ospfclient + +DESCRIPTION +=========== +ospfclient is an example ospf-api client to test the ospfd daemon. + +OPTIONS +======= + +.. option:: ospfd + + A router where the API-enabled OSPF daemon is running. + +.. option:: lsatype + + The value has to be either "9", "10", or "11", depending on the flooding scope. + +.. option:: opaquetype + + The value has to be in the range of 0-255 (for example, experimental applications might use opaquetype larger than 128). + +.. option:: opaqueid + + Arbitrary application instance (24 bits). + +.. option:: ifaddr + + Interface IP address for type 9, otherwise it will be ignored. + +.. option:: areaid + + Area in the IP address format for type 10, otherwise it will be ignored. + + +.. include:: epilogue.rst diff --git a/doc/manpages/frr-ospfd.rst b/doc/manpages/frr-ospfd.rst new file mode 100644 index 0000000..951a022 --- /dev/null +++ b/doc/manpages/frr-ospfd.rst @@ -0,0 +1,43 @@ +***** +OSPFD +***** + +.. include:: defines.rst +.. |DAEMON| replace:: ospfd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +.. option:: -a, --apiserver + + Enable the OSPF API server. + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-pbrd.rst b/doc/manpages/frr-pbrd.rst new file mode 100644 index 0000000..d9a80b1 --- /dev/null +++ b/doc/manpages/frr-pbrd.rst @@ -0,0 +1,38 @@ +**** +PBRD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: pbrd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + + diff --git a/doc/manpages/frr-pimd.rst b/doc/manpages/frr-pimd.rst new file mode 100644 index 0000000..d758266 --- /dev/null +++ b/doc/manpages/frr-pimd.rst @@ -0,0 +1,38 @@ +**** +PIMD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: pimd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-ripd.rst b/doc/manpages/frr-ripd.rst new file mode 100644 index 0000000..af4590c --- /dev/null +++ b/doc/manpages/frr-ripd.rst @@ -0,0 +1,38 @@ +**** +RIPD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: ripd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-ripngd.rst b/doc/manpages/frr-ripngd.rst new file mode 100644 index 0000000..aedd689 --- /dev/null +++ b/doc/manpages/frr-ripngd.rst @@ -0,0 +1,38 @@ +****** +RIPNGD +****** + +.. include:: defines.rst +.. |DAEMON| replace:: ripngd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-sharpd.rst b/doc/manpages/frr-sharpd.rst new file mode 100644 index 0000000..016f3f9 --- /dev/null +++ b/doc/manpages/frr-sharpd.rst @@ -0,0 +1,38 @@ +****** +SHARPD +****** + +.. include:: defines.rst +.. |DAEMON| replace:: sharpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + + diff --git a/doc/manpages/frr-staticd.rst b/doc/manpages/frr-staticd.rst new file mode 100644 index 0000000..ccbcf32 --- /dev/null +++ b/doc/manpages/frr-staticd.rst @@ -0,0 +1,38 @@ +******* +STATICD +******* + +.. include:: defines.rst +.. |DAEMON| replace:: staticd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + + diff --git a/doc/manpages/frr-vrrpd.rst b/doc/manpages/frr-vrrpd.rst new file mode 100644 index 0000000..0e73b07 --- /dev/null +++ b/doc/manpages/frr-vrrpd.rst @@ -0,0 +1,40 @@ +***** +VRRPD +***** + +.. include:: defines.rst +.. |DAEMON| replace:: vrrpd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing component that works with the FRRouting routing engine. +It implements the Virtual Router Redundancy Protocol. Support for both VRRPv2 +and VRRPv3 is present. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-watchfrr.rst b/doc/manpages/frr-watchfrr.rst new file mode 100644 index 0000000..d8c82ea --- /dev/null +++ b/doc/manpages/frr-watchfrr.rst @@ -0,0 +1,134 @@ +******** +WATCHFRR +******** + +.. include:: defines.rst +.. |DAEMON| replace:: watchfrr + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| [option...] ... + + +DESCRIPTION +=========== +|DAEMON| is a watchdog program that monitors the status of supplied frr daemons and tries to restart them in case they become unresponsive or shut down. + +To determine whether a daemon is running, it tries to connect to the daemon's VTY UNIX stream socket, and send echo commands to ensure the daemon responds. When the daemon crashes, EOF is received from the socket, so that |DAEMON| can react immediately. + +In order to avoid restarting the daemons in quick succession, you can supply the -m and -M options to set the minimum and maximum delay between the restart commands. The minimum restart delay is recalculated each time a restart is attempted. If the time since the last restart attempt exceeds twice the value of -M, the restart delay is set to the value of -m, otherwise the interval is doubled (but capped at the value of -M). + +OPTIONS +======= + +.. option:: --dry + + Run |DAEMON| in "dry-run" mode, only monitoring the specified daemons but not performing any start/stop/restart actions. + +.. option:: -d, --daemon + + Run in daemon mode. When supplied, error messages are sent to Syslog instead of standard output (stdout). + +.. option:: -S , --statedir + + Set the VTY socket directory (the default value is "/var/run/frr"). + +.. option:: -N , --pathspace + + Insert the given name into paths used by the FRR daemons. This is appended + to the VTY socket directory and passed to the daemons which also add it to + their paths in /etc. + +.. option:: --netns[=] + + (Linux only.) Switch network namespaces when starting watchfrr. The name + defaults to the value passed with -N (which it should be used in conjunction + with.) If the name is not specified, the option has no effect. + + If the network namespace does not exist, it is created in a manner + compatible with iproute2. Network namespaces are not removed by FRR, this + must be done with "ip netns delete". + +.. option:: -l , --loglevel + + Set the logging level (the default value is "6"). The value should range from 0 (LOG_EMERG) to 7 (LOG_DEBUG), but higher number can be supplied if extra debugging messages are required. + +.. option:: --min-restart-interval + + Set the minimum number of seconds to wait between invocations of the daemon restart commands (the default value is "60"). + +.. option:: --max-restart-interval + + Set the maximum number of seconds to wait between invocations of the daemon restart commands (the default value is "600"). + +.. option:: -i , --interval + + Set the status polling interval in seconds (the default value is "5"). + +.. option:: -t , --timeout + + Set the unresponsiveness timeout in seconds (the default value is "10"). + +.. option:: -T , --restart-timeout + + Set the restart (kill) timeout in seconds (the default value is "20"). If any background jobs are still running after this period has elapsed, they will be killed. + +.. option:: -p , --pid-file + + Set the process identifier filename (the default value is "/var/run/frr/|DAEMON|.pid"). + +.. option:: -b , --blank-string + + When the supplied string is found in any of the command line option arguments (i.e., -r, -s, or -k), replace it with a space. + + This is an ugly hack to circumvent problems with passing the command line arguments containing embedded spaces. + +.. option:: -v, --version + + Display the version information and exit. + +.. option:: -h, --help + + Display the usage information and exit. + +The following 3 options specify scripts that |DAEMON| uses to perform start/stop/restart actions. Reasonable default values are built into watchfrr, so the use of these options should no longer be necessary: + +.. option:: -s command, --start-command command + + Supply a Bourne shell command to start a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. + +.. option:: -k command, --kill-command command + + Supply a Bourne shell command to stop a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. + +.. option:: -r command, --restart command + + Supply a Bourne shell command to restart a single daemon. The command string should contain the '%s' placeholder to be substituted with the daemon name. + +PREVIOUS OPTIONS +================ +Prior versions of |DAEMON| supported some additional options that no longer exist::: + + -a, -A, -e, -R, -z + +The ``-a``, ``-A`` and ``-R`` options were used to select alternate monitoring modes that offered different patterns of restarting daemons. The "correct" mode (phased restart) is now the default. The -e and -z options used to disable some monitoring aspects, |DAEMON| now always has all monitoring features enabled. + +Removing these options should result in correct operation, if it does not please file a bug report. + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr-zebra.rst b/doc/manpages/frr-zebra.rst new file mode 100644 index 0000000..6cc46b8 --- /dev/null +++ b/doc/manpages/frr-zebra.rst @@ -0,0 +1,68 @@ +***** +ZEBRA +***** + +.. include:: defines.rst +.. |DAEMON| replace:: zebra + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a routing manager that implements the zebra route engine. zebra supports all protocol daemons in the FRRouting suite. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst + +.. option:: -b, --batch + + Runs in batch mode, zebra parses its config and exits. + +.. option:: -s, --nl-bufsize + + Set netlink receive buffer size. There are cases where zebra daemon can't handle flood of netlink messages from kernel. If you ever see "recvmsg overrun" messages in zebra log, you are in trouble. + + Solution is to increase receive buffer of netlink socket. Note that kernel < 2.6.14 doesn't allow increasing it over maximum value defined in /proc/sys/net/core/rmem_max. If you want to do it, you have to increase maximum before starting zebra. + + Note that this affects Linux only. + + +.. option:: -n, --vrfwnetns + + Enable namespace VRF backend. By default, the VRF backend relies on VRF-lite support from the Linux kernel. This option permits discovering Linux named network namespaces and mapping it to FRR VRF contexts. + +ROUTES +------ + +.. option:: -r, --retain + + When the program terminates, do not flush routes installed by zebra from the kernel. + +.. option:: -R, --routing-table + + Specify which kernel routing table *Zebra* should communicate with. + If this option is not specified the default table (RT_TABLE_MAIN) is used. + + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/frr.rst b/doc/manpages/frr.rst new file mode 100644 index 0000000..3d3479a --- /dev/null +++ b/doc/manpages/frr.rst @@ -0,0 +1,44 @@ +*** +FRR +*** + +.. include:: defines.rst + +SYNOPSIS +======== +frr [ start ] + +frr [ stop ] + +frr [ reload ] + +frr [ restart ] + +frr [ status ] + + +DESCRIPTION +=========== +frr is a systemd interaction script for the FRRouting routing engine. + +OPTIONS +======= +Options available for the frr command: + +start + Start enabled FRR daemons + +stop + Stop enabled FRR daemons + +reload + Reload modified configuration files + +restart + Stop all running daemons and then restart them + +status + Status of all the daemon + +.. include:: epilogue.rst + diff --git a/doc/manpages/index.rst b/doc/manpages/index.rst new file mode 100644 index 0000000..58a1d9e --- /dev/null +++ b/doc/manpages/index.rst @@ -0,0 +1,30 @@ +.. FRR documentation master file, created by + sphinx-quickstart on Wed Jan 31 12:00:55 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. toctree:: + :maxdepth: 2 + + frr + frr-bfdd + frr-bgpd + frr-eigrpd + frr-isisd + frr-fabricd + frr-ldpd + frr-nhrpd + frr-ospf6d + frr-ospfclient + frr-ospfd + frr-pimd + frr-pbrd + frr-ripd + frr-ripngd + frr-sharpd + frr-staticd + frr-watchfrr + frr-zebra + frr-vrrpd + mtracebis + vtysh diff --git a/doc/manpages/mtracebis.rst b/doc/manpages/mtracebis.rst new file mode 100644 index 0000000..d3ba803 --- /dev/null +++ b/doc/manpages/mtracebis.rst @@ -0,0 +1,44 @@ +********* +MTRACEBIS +********* + +.. include:: defines.rst +.. |PROGRAM| replace:: mtracebis + +SYNOPSIS +======== +|PROGRAM| |synopsis-options-hv| + +|PROGRAM| [] + +DESCRIPTION +=========== +|PROGRAM| is a program for initiating multicast traceroute, or "mtrace", queries. + +It can initiate two types of mtrace queries: weak and group. + +Weak tests whether the interfaces towards the source are multicast enabled and is +initiated by supplying only the multicast source address. + +Group tests whether there is multicast routing protocol state for particular +multicast group and is initiated by supplying mutlicast source and group. + +The first query sent is a full query, capable of crossing the network all the way +to the source. If this fails, hop-by-hop queries are initiated. + +Hop-by-hop queries start by requesting only a response from the nearest router. +Following that, next query is extended to the next two routers, and so on... +until a set of routers is tested for connectivity. + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|PROGRAM| + The default location of the |PROGRAM| binary. + +.. include:: epilogue.rst + +AUTHORS +======= + +Mladen Sablic diff --git a/doc/manpages/subdir.am b/doc/manpages/subdir.am new file mode 100644 index 0000000..9284212 --- /dev/null +++ b/doc/manpages/subdir.am @@ -0,0 +1,75 @@ +# +# doc/manpages +# + +man_RSTFILES = \ + doc/manpages/bfd-options.rst \ + doc/manpages/common-options.rst \ + doc/manpages/conf.py \ + doc/manpages/defines.rst \ + doc/manpages/epilogue.rst \ + doc/manpages/frr-bfdd.rst \ + doc/manpages/frr-bgpd.rst \ + doc/manpages/frr-eigrpd.rst \ + doc/manpages/frr-fabricd.rst \ + doc/manpages/frr-isisd.rst \ + doc/manpages/frr-ldpd.rst \ + doc/manpages/frr-nhrpd.rst \ + doc/manpages/frr-ospf6d.rst \ + doc/manpages/frr-ospfclient.rst \ + doc/manpages/frr-ospfd.rst \ + doc/manpages/frr-pbrd.rst \ + doc/manpages/frr-pimd.rst \ + doc/manpages/frr-ripd.rst \ + doc/manpages/frr-ripngd.rst \ + doc/manpages/frr-sharpd.rst \ + doc/manpages/frr-staticd.rst \ + doc/manpages/frr-vrrpd.rst \ + doc/manpages/frr-watchfrr.rst \ + doc/manpages/frr-zebra.rst \ + doc/manpages/frr.rst \ + doc/manpages/index.rst \ + doc/manpages/mtracebis.rst \ + doc/manpages/vtysh.rst \ + # end + +EXTRA_DIST += $(man_RSTFILES) + +MANPARENT = doc/manpages/_build +MANBUILD = $(MANPARENT)/man +doc/manpages/_build/.doctrees/environment.pickle: $(man_RSTFILES) + +# +# automake integration +# + +rstman1dir = $(mandir)/man1 +rstman8dir = $(mandir)/man8 + +rstman1_DATA = +rstman8_DATA = + +if DOC +rstman1_DATA += $(man1) +rstman8_DATA += $(man8) +endif # DOC + +man1 = $(MANBUILD)/frr.1 +man8 = + +# dependency +$(man8) $(man1): $(MANBUILD)/man.stamp + +# +# hook-ins for clean / doc +# (install is handled by automake _DATA) +# + +clean-local: clean-manpages +.PHONY: clean-manpages +clean-manpages: + -rm -rf $(MANPARENT) + +doc: doc-man +.PHONY: doc-man +doc-man: $(rstman8_DATA) $(rstman1_DATA) diff --git a/doc/manpages/vtysh.rst b/doc/manpages/vtysh.rst new file mode 100644 index 0000000..396fcfc --- /dev/null +++ b/doc/manpages/vtysh.rst @@ -0,0 +1,107 @@ +***** +VTYSH +***** + +.. include:: defines.rst +.. |DAEMON| replace:: vtysh + +SYNOPSIS +======== +vtysh [ -b ] + +vtysh [ -E ] [ -d *daemon* ] [ -c *command* ] + +DESCRIPTION +=========== +vtysh is an integrated shell for the FRRouting suite of protocol daemons. + +OPTIONS +======= +OPTIONS available for the vtysh command: + +.. option:: -b, --boot + + Execute boot startup configuration. It makes sense only if integrated config file is in use (not default in FRRouting). See Info file frr for more info. + +.. option:: -c, --command command + + Specify command to be executed under batch mode. It behaves like -c option in any other shell - command is executed and vtysh exits. + + It's useful for gathering info from FRRouting daemons or reconfiguring daemons from inside shell scripts, etc. Note that multiple commands may be executed by using more than one -c option and/or embedding linefeed characters inside the command string. + +.. option:: -d, --daemon daemon_name + + Specify which daemon to connect to. By default, vtysh attempts to connect to all FRRouting daemons running on the system. With this flag, one can specify a single daemon to connect to instead. For example, specifying '-d ospfd' will connect only to ospfd. This can be particularly useful inside scripts with -c where the command is targeted for a single daemon. + +.. option:: -e, --execute command + + Alias for -c. It's here only for compatibility with Zebra routing software and older FRR versions. This will be removed in future. + +.. option:: -E, --echo + + When the -c option is being used, this flag will cause the standard vtysh prompt and command to be echoed prior to displaying the results. This is particularly useful to separate the results when executing multiple commands. + +.. option:: -C, --dryrun + + When the -C option is being used, this flag will check the config for syntatic validity. + +.. option:: -m, --markfile + + Mark the input file with context ends, useful for cleanup of a config file that has a lot of extraneous space and end markers + +.. option:: -n, --noerror + + When executing cli that does not invoke a vtysh shell, if an error ocurrs ignore it for purposes of return codes from vtysh. + +.. option:: -H, --histfile + + Override the history file for vtysh commands. You can set ``vtysh -H /dev/null`` to turn logging of at all. + +.. option:: -u, --user + + Restrict access to configuration commands by preventing use of the "enable" command. This option provides the same limited "security" as password-protected telnet access. *This security should not be relied on in production environments.* + + Caveat emptor: VTYSH was never designed to be a privilege broker and is not built using secure coding practices. No guarantees of security are provided for this option and under no circumstances should this option be used to provide any semblance of secure read-only access to FRR. + +.. option:: -h, --help + + Display a usage message on standard output and exit. + +.. option:: -t, --timestamp + + Print a timestamp before going to shell or reading the configuration file. + +.. option:: --no-fork + + When used in conjunction with ``-b``, prevents vtysh from forking children to handle configuring each target daemon. + + +ENVIRONMENT VARIABLES +===================== +VTYSH_PAGER + This should be the name of the pager to use. Default is more. + +VTYSH_HISTFILE + Override the history file for vtysh commands. Logging can be turned off using ``VTYSH_HISTFILE=/dev/null vtysh``. + Environment is preferred way to override the history file path over command line argument (-H/--histfile). + +FILES +===== +|INSTALL_PREFIX_SBIN|/vtysh + The default location of the vtysh binary. + +|INSTALL_PREFIX_ETC|/vtysh.conf + The default location of the vtysh config file. + +|INSTALL_PREFIX_ETC|/frr.conf + The default location of the integrated FRRouting routing engine config file if integrated config file is in use. + +${HOME}/.history_frr + Location of history of commands entered via cli + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/mpls/ChangeLog.opaque.txt b/doc/mpls/ChangeLog.opaque.txt new file mode 100644 index 0000000..afcfaa3 --- /dev/null +++ b/doc/mpls/ChangeLog.opaque.txt @@ -0,0 +1,192 @@ +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2013.07.01 + +1. Feature enhancements + + 1.1 Update ospf_te.[c,h] in conformance to RFC3630 and clean the code. + Add new directive to enable MPLS-TE per interface instead of globally + + 1.2 Add support for RFC4970 "Router Information" and RFC5088 "PCE + Capabilities announcement". + + 1.3 Incorporate the mpls documentation into the main stream doc. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.12.03 + +1. Bug fixes + + 1.1 Though a new member "oi" has added to "struct ospf_lsa" to control + flooding scope of type-9 Opaque-LSAs, the value was always NULL + because no one set it. + + 1.2 In the function "show_ip_ospf_database_summary()" and "show_lsa_ + detail_adv_router()", VTY output for type-11 Opaque-LSAs did not + work properly. + + 1.3 URL for the opaque-type assignment reference has changed. + + 1.4 In the file "ospf_mpls_te.c", printf formats have changed to + avoid compiler warning messages; "%lu" -> "%u", "%lx" -> "%x". + Note that this hack depends on OS, compiler and their versions. + + 1.5 One of attached documentation "opaque_lsa.txt" has changed to + reflect the latest coding. + +2. Feature enhancements + + 2.1 Knowing that it is an ugly hack, an "officially unallocated" + opaque-type value 0 has newly introduced as a "wildcard", + which matches to all opaque-type. + This value must not be flooded to the network, of course. + + 2.2 The Opaque-core module makes use of newly introduced hooks to + dispatch every LSDB change (LSA installation and deletion) to + preregistered opaque users. + Therefore, by providing appropriate callback functions as new + parameters of "ospf_register_opaque_functab()", an opaque user + can refer to every LSA instance to be installed into, or to be + deleted from, the LSDB. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.10.31 + +1. Bug fixes + + 1.1 Since each LSA has their own lifetime, they will remain in a + routing domain (being stored in LSDB of each router), until their + age naturally reach to MaxAge or explicitly being flushed by the + originated router. Therefore, if a router restarted with a short + downtime, it is possible that previously flooded self-originated + LSAs might received if the NSM status is not less than Exchange. + + There were some problems in the way of handling self-originated + Opaque-LSAs if they are contained in a received LSUpd message, + but not installed to the local LSDB yet. + Regardless of some conditions to start originating Opaque-LSAs + (there should be at least one opaque-capable full-state neighbor), + the function "ospf_flood()" will be called to flood and install + this brand-new looking LSA. + As the result, when the NSM of an opaque-capable neighbor gets + full, internal state inconsistency happens; a user of Opaque-LSA + such as MPLS-TE can refer to self-originated LSAs in the local + LSDB, but cannot modify their contents... + + Above problems have fixed with a policy "flush it from the whole + routing domain and keep silent until the flushing completed". + By using this sweeping technique, we can be free from confusion + caused by self-originated LSAs received via network. + + 1.2 The function "ospf_opaque_type_name()" contained massive ifdefs + corresponding to each "opaque-type". + These unnecessary ifdefs are removed completely. + + 1.3 In the function "ospf_delete_opaque_functab()", there was an + improper loop control that causes illegal memory access. + Original coding was "next = nextnode (node)". + + 1.4 The function "ospf_mpls_te_ism_change()" could not handle the + case when the ISM changes from Waiting to DR/BDR/Other. + So, there was a case that even if one of an ISM become + operational and MPLS-TE module has started, the corresponding + Opaque-LSA cannot be originated. + + 1.5 The function "ospf_opaque_lsa_reoriginate_schedule()" did not + allow to be called multiple times, simply because handling + module for the given "lsa-type & opaque-type" already exists. + But this assumption seems to be wrong. + Change the policy to allow this function to be called multiple + times and let the caller to decide what should do when the + corresponding callback function "(* functab->lsa_originator)()" + is called. + +2. Feature enhancements + + 2.1 The global bitmap "opaque" has introduced instead of former flag + "OpaqueCapable", to store complex conditions to handle Opaque-LSAs. + + 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic + -06.txt", no significant changes with 05 version, though. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.08.03 + +1. Bug fixes + + 1.1 Even if the ospfd started with opaque capability enabled, when + the ospfd receives an unknown opaque-type (unregistered by the + function "ospf_register_opaque_functab()" beforehand), the LSA + was discarded. As the result, only the opaque-LSAs that have + commonly registered by opaque-capable ospf routers can be + flooded in a routing domain. + + This behavior has fixed so that arbitrary opaque-type LSAs can + be flooded among opaque-capable ospf routers. + If the ospfd has opaque-LSA capability but disabled at runtime, + received opaque-LSAs can be accepted and registered to LSDB as + is, but not be flooded to the network; those opaque LSAs will + remain in LSDB until explicitly flushed by incoming LSUpd + messages with MaxAge, or their age naturally reaches to MaxAge. + + 1.2 The function "ospf_register_opaque_functab()" did not check + if the entry corresponding to the given "lsa-type, opaque-type" + combination already exists or not. + This problem has fixed not to allow multiple registration. + + 1.3 Since type-11 (AS external) LSAs will be flooded beyond areas, + there is little relationship between "struct lsa" and "struct + area". More specifically, the pointer address "lsa->area" can + be NULL if the lsa-type is 11, thus an illegal memory access + will happen. This problem has fixed. + + 1.4 When self-originated opaque-LSAs are received via network and + if the corresponding opaque-type functions are not available + (they have already deleted) at that time, those LSAs were + dropped due to "unknown opaque-type" error. + After the problem 1.1 has fixed, those "self-originated" LSAs + were registered to LSDB and then flooded to the network, even + if the processing functions did not exist... + + After all, this problem has fixed so that those LSAs should + explicitly be flushed from the routing domain immediately, if + the processing functions cannot find at that time. + + 1.5 Some typo have fixed. + + --- EXAMPLE --- + static int + opaque_lsa_originate_callback (list funclist, void *lsa_type_dependent) + ^^^^^ + --- EXAMPLE --- + +2. Feature enhancements + + 2.1 According to the description of rfc2328 in section 10.8, any + change in the router's optional capabilities should trigger + the option re-negotiation procedures with neighbors. + + --- EXCERPT --- + If for some reason the router's optional + capabilities change, the Database Exchange procedure should be + restarted by reverting to neighbor state ExStart. + --- EXCERPT --- + + For the opaque-capability changes, this feature has implemented. + More specifically, if "ospf opaque-lsa" or "no ospf opaque-lsa" + VTY command is given at runtime, all self-originated LSAs will + be flushed immediately and then all neighbor status will be + forced to ExStart by generating SeqNumberMismatch events. + + 2.1 When we change opaque-capability dynamically (ON -> OFF -> ON), + there was no trigger at "OFF->ON" timing to reactivate opaque + LSA handling modules (such as MPLS-TE) that have once forcibly + stopped at "ON->OFF" timing. + Now this dynamic reactivation feature has added. + + 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic + -05.txt", no significant changes with 04 version, though. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.03.28 + + Initial release of Opaque-LSA/MPLS-TE extensions for the zebra/ospfd. diff --git a/doc/mpls/cli_summary.txt b/doc/mpls/cli_summary.txt new file mode 100644 index 0000000..c60d0ae --- /dev/null +++ b/doc/mpls/cli_summary.txt @@ -0,0 +1,90 @@ +Summary of CLI commands, expanded for Opaque-LSA/MPLS-TE. +--------------------------------------------------------- + +router> + + show ip ospf database (asbr-summary|external|max-age|network|router|self-originate|summary|opaque-link|opaque-area|opaque-external) + + show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) (self-originate|) + + show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D + + show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D (self-originate|) + + show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) A.B.C.D adv-router A.B.C.D + + show ip ospf database (asbr-summary|external|network|router|summary|opaque-link|opaque-area|opaque-external) adv-router A.B.C.D + + --> Add database items: opaque-link, opaque-area, opaque-external + + show mpls-te interface [INTERFACE] + + --> Show current MPLS-TE link-TLV parameters. + If [INTERFACE] is omitted, all interfaces will be displayed. + + show mpls-te router + + --> Show current MPLS-TE Router-TLV parameters. + +router> enable +router# +router# configure terminal +router(config)# interface [INTERFACE] +router(config-if)# + + mpls-te link max-bw BANDWIDTH + + --> Set MPLS-TE link-TLV parameter: Maximum Bandwidth (Bytes/sec). + In integer or floating point format (1000, or 1.0e3) + + mpls-te link max-rsv-bw BANDWIDTH + + --> Set MPLS-TE link-TLV parameter: Maximum Reservable Bandwidth (Bytes/sec). + In integer or floating point format (1000, or 1.0e3) + + mpls-te link metric <0-4294967295> + + --> Set MPLS-TE link-TLV parameter: MPLS-TE metric. + + mpls-te link rsc-clsclr BITPATTERN + + --> Set MPLS-TE link-TLV parameter: Resource Class/Color. + In 32-bit hexadecimal format, with leading "0x" (0x0 - 0xffffffff) + + mpls-te link unrsv-bw <0-7> BANDWIDTH + + --> Set MPLS-TE link-TLV parameter: Unreserved Bandwidth (Bytes/sec). + In integer or floating point format (1000, or 1.0e3) + +router(config-if)# exit +router(config)# router ospf +router(config-router)# + + mpls-te + + --> Enable MPLS-TE functionality. + Note that master-switch "ospf opaque-lsa" must also be specified. + + mpls-te on + + --> Alias of "mpls-te" command. + + mpls-te router-address A.B.C.D + + --> Set MPLS-TE Router-TLV parameter: Router Address. + + no mpls-te + + --> Disable MPLS-TE functionality. + + no ospf opaque-lsa + + --> Disable Opaque-LSAs capability. + This node behaves Opaque-incapable node. + + ospf opaque-lsa + + --> Enable Opaque-LSAs capability. + This is the master-switch to make this node Opaque-capable. + +router# exit diff --git a/doc/mpls/opaque_lsa.txt b/doc/mpls/opaque_lsa.txt new file mode 100644 index 0000000..ae89ee3 --- /dev/null +++ b/doc/mpls/opaque_lsa.txt @@ -0,0 +1,365 @@ +1. List of "opaque-type dependent" callback functions per LSA-type. + + (N = 9,10,11) + | + | struct + | list struct struct + +-> +-------+ listnode listnode + | head |-----> +------+ +------ + | tail | | next |--------------------> | next + | count | /--| prev |<---------------------| prev + +-------+ | data |----+ | + |///////| +------+ | + +-------+ | + | + struct | + ospf_opaque_tabent | + +----------------------+ <--+ + | opaque_type | + +----------------------+ + | (Callback functions) | + +----------------------+ + + +2. Self-originated Opaque-LSAs per LSA-type. + +2.1 Type-11 (AS-external) Opaque-LSAs + + struct + ospf + +---> +-------------------+ + | |///////////////////| + | +-------------------+ + | | opaque | + | +-------------------+ + | |///////////////////| + | +-------------------+ + | | opaque_lsa_self |---+ + | +-------------------+ | + | |///////////////////| | + | +-------------------+ | + | | + ......|.............................|....................................... + : | | Almost common for type-9,10,11 LSA : + : | +-----------------------+ : + : | | : + : | | struct : + : | | list struct struct : + : | +-> +-------+ listnode listnode : + : | | head |-----> +------+ +------ : + : | | tail | | next |--------------------> | next : + : | | count | /--| prev |<---------------------| prev : + : | +-------+ | data |---+ | : + : | |///////| +------+ | : + : | +-------+ | : + : | | : + : | struct | : + : | opaque_info_per_type | : + : | +-------------------+ <--------+ : + : | | opaque_type | <------------+ : + : | +-------------------+ | : + : | | status | | : + : | +-------------------+ | : + : | | t_opaque_lsa_self | | : + : | +-------------------+ | : + : +-----| owner | | struct : + : +-------------------+ | ospf_opaque_tabent : + : | functab |-------------------> +---------------- : + : +-------------------+ | | opaque_type : + : | id_list |---+ | |(Callback Funcs) : + : +-------------------+ | | | : + : | | : + : +-----------------------+ | : + : | | : + : | struct | : + : | list struct | struct : + : +-> +-------+ listnode | listnode : + : | head |-----> +------+ | +------ : + : | tail | | next |--------------------> | next : + : | count | /--| prev |<---------------------| prev : + : +-------+ | data |---+ | | : + : |///////| +------+ | | : + : +-------+ | | : + : | | : + : struct | | : + : opaque_info_per_id | | : + : +-------------------+ <--------+ | : + : | opaque_id | | : + : +-------------------+ | : + : | t_opaque_lsa_self | | : + : +-------------------+ | : + : | opqctl_type |--------------+ : + : +-------------------+ : + : | lsa |---+ : + : +-------------------+ | : + : | : + : struct | : + : ospf_lsa | : + : +-------------+ <-------+ : + : |/////////////| struct : + : +-------------+ lsa_header : + : | data |--------------> +-------- : + : +-------------+ | : + : |/////////////| : + : +-------------+ : + : +--------| area | : + : | +-------------+ : + : --- |/////////////| : + : +-------------+ : + : +-----| oi | : + : | +-------------+ : + : --- : + :..........................................................................: + +2.2 Type-10 (area-local) Opaque-LSAs + + struct + ospf + +---------+ <-----------+ + |/////////| | + +---------+ | + | + struct | + ospf_area | + +--+---> +-----------------+ | + | | | top |-----+ + | | +-----------------+ + | | |/////////////////| struct + | | +-----------------+ ospf_lsa + | | | router_lsa_self |-----------> +--------- + | | +-----------------+ | + | | | opaque_lsa_self |-----+ | + | | +-----------------+ | + | | |/////////////////| | + | | +-----------------+ | + | | | + ...|..|.............................|....................................... + : | | | Almost common for type-9,10,11 LSA : + : | | +-----------------------+ : + : | | | : + : | | | struct : + : | | | list struct struct : + : | | +-> +-------+ listnode listnode : + : | | | head |-----> +------+ +------ : + : | | | tail | | next |--------------------> | next : + : | | | count | /--| prev |<---------------------| prev : + : | | +-------+ | data |---+ | : + : | | |///////| +------+ | : + : | | +-------+ | : + : | | | : + : | | struct | : + : | | opaque_info_per_type | : + : | | +-------------------+ <--------+ : + : | | | opaque_type | <------------+ : + : | | +-------------------+ | : + : | | | status | | : + : | | +-------------------+ | : + : | | | t_opaque_lsa_self | | : + : | | +-------------------+ | : + : | +-----| owner | | struct : + : | +-------------------+ | ospf_opaque_tabent : + : | | functab |-------------------> +---------------- : + : | +-------------------+ | | opaque_type : + : | | id_list |---+ | |(Callback Funcs) : + : | +-------------------+ | | | : + : | | | : + : | +-----------------------+ | : + : | | | : + : | | struct | : + : | | list struct | struct : + : | +-> +-------+ listnode | listnode : + : | | head |-----> +------+ | +------ : + : | | tail | | next |--------------------> | next : + : | | count | /--| prev |<---------------------| prev : + : | +-------+ | data |---+ | | : + : | |///////| +------+ | | : + : | +-------+ | | : + : | | | : + : | struct | | : + : | opaque_info_per_id | | : + : | +-------------------+ <--------+ | : + : | | opaque_id | | : + : | +-------------------+ | : + : | | t_opaque_lsa_self | | : + : | +-------------------+ | : + : | | opqctl_type |--------------+ : + : | +-------------------+ : + : | | lsa |---+ : + : | +-------------------+ | : + : | | : + : | struct | : + : | ospf_lsa | : + : | +-------------+ <-------+ : + : | |/////////////| struct : + : | +-------------+ lsa_header : + : | | data |--------------> +-------- : + : | +-------------+ | : + : | |/////////////| : + : | +-------------+ : + : +--------| area | : + : +-------------+ : + : |/////////////| : + : +-------------+ : + : +-----| oi | : + : | +-------------+ : + : --- : + :..........................................................................: + +2.3 Type-9 (link-local) Opaque-LSAs + + struct + ospf_area + +------> +---------+ <---------+ + | |/////////| | + | +---------+ | + | | + | struct | + | ospf_interface | + | +-+-> +-----------------+ | + | | | |/////////////////| | + | | | +-----------------+ | + | | | | area |---+ + | | | +-----------------+ + | | | |/////////////////| struct + | | | +-----------------+ ospf_lsa + | | | |network_lsa_self |-----------> +--------- + | | | +-----------------+ | + | | | | opaque_lsa_self |-----+ | + | | | +-----------------+ | + | | | |/////////////////| | + | | | +-----------------+ | + | | | | + ...|..|.|...........................|....................................... + : | | | | Almost common for type-9,10,11 LSA : + : | | | +-----------------------+ : + : | | | | : + : | | | | struct : + : | | | | list struct struct : + : | | | +-> +-------+ listnode listnode : + : | | | | head |-----> +------+ +------ : + : | | | | tail | | next |--------------------> | next : + : | | | | count | /--| prev |<---------------------| prev : + : | | | +-------+ | data |---+ | : + : | | | |///////| +------+ | : + : | | | +-------+ | : + : | | | | : + : | | | struct | : + : | | | opaque_info_per_type | : + : | | | +-------------------+ <--------+ : + : | | | | opaque_type | <------------+ : + : | | | +-------------------+ | : + : | | | | status | | : + : | | | +-------------------+ | : + : | | | | t_opaque_lsa_self | | : + : | | | +-------------------+ | : + : | | +---| owner | | struct : + : | | +-------------------+ | ospf_opaque_tabent : + : | | | functab |-------------------> +---------------- : + : | | +-------------------+ | | opaque_type : + : | | | id_list |---+ | |(Callback Funcs) : + : | | +-------------------+ | | | : + : | | | | : + : | | +-----------------------+ | : + : | | | | : + : | | | struct | : + : | | | list struct | struct : + : | | +-> +-------+ listnode | listnode : + : | | | head |-----> +------+ | +------ : + : | | | tail | | next |--------------------> | next : + : | | | count | /--| prev |<---------------------| prev : + : | | +-------+ | data |---+ | | : + : | | |///////| +------+ | | : + : | | +-------+ | | : + : | | | | : + : | | struct | | : + : | | opaque_info_per_id | | : + : | | +-------------------+ <--------+ | : + : | | | opaque_id | | : + : | | +-------------------+ | : + : | | | t_opaque_lsa_self | | : + : | | +-------------------+ | : + : | | | opqctl_type |--------------+ : + : | | +-------------------+ : + : | | | lsa |---+ : + : | | +-------------------+ | : + : | | | : + : | | struct | : + : | | ospf_lsa | : + : | | +-------------+ <-------+ : + : | | |/////////////| struct : + : | | +-------------+ lsa_header : + : | | | data |--------------> +-------- : + : | | +-------------+ | : + : | | |/////////////| : + : | | +-------------+ : + : +--|-----| area | : + : | +-------------+ : + : | |/////////////| : + : | +-------------+ : + : +-----| oi | : + : +-------------+ : + :..........................................................................: + + +3. Internal structures for MPLS-TE parameter management. + + struct + ospf_mpls_te + +-------------+ + | status | + +-------------+ + | iflist |---+ + +-------------+ | + |(Router-TLV) | | + +-------------+ | + | + +---------------------+ + | + | struct + | list struct struct + +---> +-------+ listnode listnode + | head |-----> +------+ +------ + | tail | | next |--------------------> | next + | count | /--| prev |<---------------------| prev + +-------+ | data |---+ | + |///////| +------+ | + +-------+ | + | + +--------------------------------+ + | + | struct + | ospf_mpls_te_linkparms + +-> +----------------+ + | instance | struct + +----------------+ interface + | ifp |--------------------> +----------+ + +----------------+ +----> |//////////| + | area |----+ | +----------+ + +----------------+ | | | info |-----+ + | flags | | | +----------+ | + +----------------+ | | |//////////| | + | (Link-TLV) | | | +----------+ | + +----------------+ | | | + | (Link-SubTLVs) | | | struct | + +----------------+ | | ospf_if_info | + | | +----------+ <---+ + | | |//////////| + struct | | +----------+ + ospf_area | | | oifs |-----+ + +-> +--------------+ <----+ | +----------+ | + | |//////////////| | | + | +--------------+ | struct | + | | route_table | + | struct | +-----------+ <--+ + | ospf_interface | | route_top | - - - - -. + | +--------------+ <----+ | +-----------+ . + | |//////////////| | | . + | +--------------+ | | struct . + | | ifp |------|----------+ route_node . + | +--------------+ | +-----------+ < - - - - + | |//////////////| | |///////////| + | +--------------+ | +-----------+ + +---| area | +-----------------| info | + +--------------+ +-----------+ + |//////////////| |///////////| + +--------------+ +-----------+ diff --git a/doc/mpls/ospfd.conf b/doc/mpls/ospfd.conf new file mode 100644 index 0000000..2b15fa4 --- /dev/null +++ b/doc/mpls/ospfd.conf @@ -0,0 +1,76 @@ +! +! Zebra configuration saved from vty +! 2001/03/16 22:07:53 +! +hostname HOSTNAME +password PASSWORD +log file /var/log/ospfd.log +! +debug ospf ism +debug ospf nsm +debug ospf lsa +debug ospf zebra +debug ospf event +debug ospf packet all detail +! +! +interface fxp0 + ip ospf hello-interval 60 + ip ospf dead-interval 240 + mpls-te on + mpls-te link metric 999 + mpls-te link max-bw 1.25e+06 + mpls-te link max-rsv-bw 1.25e+06 + mpls-te link unrsv-bw 0 1.25e+06 + mpls-te link unrsv-bw 1 1.25e+06 + mpls-te link unrsv-bw 2 1.25e+06 + mpls-te link unrsv-bw 3 1.25e+06 + mpls-te link unrsv-bw 4 1.25e+06 + mpls-te link unrsv-bw 5 1.25e+06 + mpls-te link unrsv-bw 6 1.25e+06 + mpls-te link unrsv-bw 7 1.25e+06 + mpls-te link rsc-clsclr 0xab +! +interface de1 + ip ospf hello-interval 60 + ip ospf dead-interval 240 + mpls-te link metric 111 + mpls-te link max-bw 1.25e+06 + mpls-te link max-rsv-bw 1.25e+06 + mpls-te link unrsv-bw 0 1.25e+06 + mpls-te link unrsv-bw 1 1.25e+06 + mpls-te link unrsv-bw 2 1.25e+06 + mpls-te link unrsv-bw 3 1.25e+06 + mpls-te link unrsv-bw 4 1.25e+06 + mpls-te link unrsv-bw 5 1.25e+06 + mpls-te link unrsv-bw 6 1.25e+06 + mpls-te link unrsv-bw 7 1.25e+06 + mpls-te link rsc-clsclr 0xcd +! +interface de0 + mpls-te link metric 0 + mpls-te link rsc-clsclr 0x0 +! +interface lp0 + ip ospf network point-to-point +! +interface tun0 + ip ospf network point-to-point +! +interface sl0 + ip ospf network point-to-point +! +interface ppp0 + ip ospf network point-to-point +! +interface lo0 +! +router ospf + compatible rfc1583 + network 192.168.0.0/16 area 1 + ospf opaque-lsa + mpls-te + mpls-te router-address 1.2.3.4 +! +line vty +! diff --git a/doc/subdir.am b/doc/subdir.am new file mode 100644 index 0000000..2795326 --- /dev/null +++ b/doc/subdir.am @@ -0,0 +1,179 @@ +# +# doc +# + +# You can set these variables from the command line. +SPHINXOPTS ?= +PAPER ?= + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) + +### + +AM_V_SPHINX = $(am__v_SPHINX_$(V)) +am__v_SPHINX_ = $(am__v_SPHINX_$(AM_DEFAULT_VERBOSITY)) +am__v_SPHINX_0 = @echo " SPHINX " $@; +am__v_SPHINX_1 = +AM_V_MAKEINFO = $(am__v_MAKEINFO_$(V)) +am__v_MAKEINFO_ = $(am__v_MAKEINFO_$(AM_DEFAULT_VERBOSITY)) +am__v_MAKEINFO_0 = @echo " MAKEINFO" $@; +am__v_MAKEINFO_1 = + +# +# real-file sphinx targets that work for dependencies +# + +doc/%/_build/.doctrees/environment.pickle: + $(AM_V_SPHINX) ( \ + subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ + $(PYTHON) $(PYSPHINX) -a -q -b text -d "$${subdoc}/_build/.doctrees" \ + $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/text" \ + ) +doc/%/_build/html/.buildinfo: doc/%/_build/.doctrees/environment.pickle + $(AM_V_SPHINX) ( \ + subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ + $(PYTHON) $(PYSPHINX) -q -b html -d "$${subdoc}/_build/.doctrees" \ + $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/html" \ + ) +.PRECIOUS: doc/%/_build/texinfo/frr.texi +doc/%/_build/texinfo/frr.texi: doc/%/_build/.doctrees/environment.pickle + $(AM_V_SPHINX) ( \ + subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ + $(PYTHON) $(PYSPHINX) -q -b texinfo -d "$${subdoc}/_build/.doctrees" \ + $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/texinfo" \ + ) +doc/%/_build/texinfo/frr.info: doc/%/_build/texinfo/frr.texi + $(AM_V_MAKEINFO)$(MAKEINFO) --no-split -o '$@' '$<' +doc/%/_build/man/man.stamp: doc/%/_build/.doctrees/environment.pickle + $(AM_V_SPHINX) ( \ + subdoc="$@"; subdoc="$${subdoc#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ + $(MKDIR_P) "$${subdoc}/_build/man"; touch $@.tmp; \ + $(PYTHON) $(PYSPHINX) -a -q -b man -d "$${subdoc}/_build/.doctrees" \ + $(ALLSPHINXOPTS) "$(top_srcdir)/$${subdoc}" "$${subdoc}/_build/man" && \ + mv -f $@.tmp $@ \ + ) + +# +# auxiliary sphinx targets (output name = directory, +# deps will not work very well) +# + +SPHINXTARGETS = \ + html dirhtml singlehtml pickle json \ + htmlhelp qthelp applehelp devhelp \ + epub latex text man texinfo gettext \ + changes linkcheck doctest coverage \ + xml pseudoxml \ + # end + +M_SPHINXTARGETS = $(addprefix doc/%/_build/,$(SPHINXTARGETS)) +.PRECIOUS: $(M_SPHINXTARGETS) +$(M_SPHINXTARGETS): doc/%/_build/.doctrees/environment.pickle + $(AM_V_SPHINX) ( \ + target="$@"; \ + builder="$${target##*/}"; \ + subdoc="$${target#doc/}"; subdoc="doc/$${subdoc%%/*}"; \ + rm -rf "$@"; \ + $(PYTHON) $(PYSPHINX) -q -b $${builder} -d $${subdoc}/_build/.doctrees \ + $(ALLSPHINXOPTS) $(top_srcdir)/$${subdoc} $@ \ + ) + +.PHONY: doc/%/_build/latexpdf +doc/%/_build/latexpdf: doc/%/_build/latex + @make -C $< all-pdf + +# If you want to build the developer's docs in other formats, try the +# following: +# +# $ cd developer +# $ make help + +# dist tarballs want doc sources +EXTRA_DIST += \ + doc/mpls/ChangeLog.opaque.txt \ + doc/mpls/ospfd.conf \ + doc/mpls/cli_summary.txt \ + doc/mpls/opaque_lsa.txt \ + doc/figures/cli-change-client.drawio \ + doc/figures/cli-change-client.svg \ + doc/figures/cli-change-mgmtd.drawio \ + doc/figures/cli-change-mgmtd.svg \ + doc/figures/cligraph.png \ + doc/figures/cligraph.svg \ + doc/figures/fig-normal-processing.dia \ + doc/figures/fig-normal-processing.png \ + doc/figures/fig-normal-processing.txt \ + doc/figures/fig-rs-processing.dia \ + doc/figures/fig-rs-processing.png \ + doc/figures/fig-rs-processing.txt \ + doc/figures/fig_topologies_full.dia \ + doc/figures/fig_topologies_full.png \ + doc/figures/fig_topologies_full.txt \ + doc/figures/fig_topologies_rs.dia \ + doc/figures/fig_topologies_rs.png \ + doc/figures/fig_topologies_rs.txt \ + doc/figures/fig-vnc-commercial-route-reflector.dia \ + doc/figures/fig-vnc-commercial-route-reflector.png \ + doc/figures/fig-vnc-commercial-route-reflector.txt \ + doc/figures/fig-vnc-frr-route-reflector.dia \ + doc/figures/fig-vnc-frr-route-reflector.png \ + doc/figures/fig-vnc-frr-route-reflector.txt \ + doc/figures/fig-vnc-gw.dia \ + doc/figures/fig-vnc-gw.png \ + doc/figures/fig-vnc-gw-rr.dia \ + doc/figures/fig-vnc-gw-rr.png \ + doc/figures/fig-vnc-gw-rr.txt \ + doc/figures/fig-vnc-gw.txt \ + doc/figures/fig-vnc-mesh.dia \ + doc/figures/fig-vnc-mesh.png \ + doc/figures/fig-vnc-mesh.txt \ + doc/figures/fig-vnc-redundant-route-reflectors.dia \ + doc/figures/fig-vnc-redundant-route-reflectors.png \ + doc/figures/fig-vnc-redundant-route-reflectors.txt \ + doc/figures/frr-icon.svg \ + doc/figures/frr-logo-icon.png \ + doc/figures/frr-logo-medium.png \ + doc/figures/frr-logo.png \ + doc/figures/frr-logo-small.png \ + doc/figures/git_branches.png \ + doc/figures/git_branches.svg \ + doc/figures/ospf_api_architecture.png \ + doc/figures/ospf_api_msghdr.png \ + doc/figures/ospf_api_msgs1.png \ + doc/figures/ospf_api_msgs2.png \ + doc/extra/frrlexer.py \ + # end + + +.PHONY: doc/help +doc/help: + @echo "Please use \`make doc/{user,manpages,developer}/' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" diff --git a/doc/user/.gitignore b/doc/user/.gitignore new file mode 100644 index 0000000..81c60dc --- /dev/null +++ b/doc/user/.gitignore @@ -0,0 +1,2 @@ +/_templates +/_build diff --git a/doc/user/.readthedocs.yaml b/doc/user/.readthedocs.yaml new file mode 100644 index 0000000..ba5698c --- /dev/null +++ b/doc/user/.readthedocs.yaml @@ -0,0 +1,15 @@ +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +python: + install: + - requirements: doc/user/requirements.txt +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: doc/user/conf.py diff --git a/doc/user/Makefile b/doc/user/Makefile new file mode 100644 index 0000000..840ee5b --- /dev/null +++ b/doc/user/Makefile @@ -0,0 +1,16 @@ +all: ALWAYS + @$(MAKE) -s -C ../.. doc-user +help: ALWAYS + @$(MAKE) -s -C ../.. doc/help +pdf: ALWAYS + @$(MAKE) -s -C ../.. doc/user/_build/latexpdf +info: ALWAYS + @$(MAKE) -s -C ../.. doc/user/_build/texinfo/frr.info +%: ALWAYS + @$(MAKE) -s -C ../.. doc/user/_build/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/doc/user/Useful_Sysctl_Settings.md b/doc/user/Useful_Sysctl_Settings.md new file mode 100644 index 0000000..0ebf911 --- /dev/null +++ b/doc/user/Useful_Sysctl_Settings.md @@ -0,0 +1,59 @@ +# Useful Sysctl Settings +Sysctl on Linux systems can tweak many useful behaviors. When it comes to a routing protocol suite like FRRouting there are numerous values depending on your use case that make sense to optimize. + +The below sysctl values provide a logical set of defaults which can be further optimized. + + +``` +# /etc/sysctl.d/99frr_defaults.conf +# Place this file at the location above and reload the device. +# or run the sysctl -p /etc/sysctl.d/99frr_defaults.conf + +# Enables IPv4/IPv6 Routing +net.ipv4.ip_forward = 1 +net.ipv6.conf.all.forwarding=1 + +# Routing +net.ipv6.route.max_size=131072 +net.ipv4.conf.all.ignore_routes_with_linkdown=1 +net.ipv6.conf.all.ignore_routes_with_linkdown=1 + +# Best Settings for Peering w/ BGP Unnumbered +# and OSPF Neighbors +net.ipv4.conf.all.rp_filter = 0 +net.ipv4.conf.default.rp_filter = 0 +net.ipv4.conf.lo.rp_filter = 0 +net.ipv4.conf.all.forwarding = 1 +net.ipv4.conf.default.forwarding = 1 +net.ipv4.conf.default.arp_announce = 2 +net.ipv4.conf.default.arp_notify = 1 +net.ipv4.conf.default.arp_ignore=1 +net.ipv4.conf.all.arp_announce = 2 +net.ipv4.conf.all.arp_notify = 1 +net.ipv4.conf.all.arp_ignore=1 +net.ipv4.icmp_errors_use_inbound_ifaddr=1 + +# Miscellaneous Settings + +# Keep ipv6 permanent addresses on an admin down +net.ipv6.conf.all.keep_addr_on_down=1 +net.ipv6.route.skip_notify_on_dev_down=1 + +# igmp +net.ipv4.igmp_max_memberships=1000 +net.ipv4.neigh.default.mcast_solicit = 10 + +# MLD +net.ipv6.mld_max_msf=512 + +# Garbage Collection Settings for ARP and Neighbors +net.ipv4.neigh.default.gc_thresh2=7168 +net.ipv4.neigh.default.gc_thresh3=8192 +net.ipv4.neigh.default.base_reachable_time_ms=14400000 +net.ipv6.neigh.default.gc_thresh2=3584 +net.ipv6.neigh.default.gc_thresh3=4096 +net.ipv6.neigh.default.base_reachable_time_ms=14400000 + +# Use neigh information on selection of nexthop for multipath hops +net.ipv4.fib_multipath_use_neigh=1 +``` diff --git a/doc/user/_static/overrides.css b/doc/user/_static/overrides.css new file mode 100644 index 0000000..638f619 --- /dev/null +++ b/doc/user/_static/overrides.css @@ -0,0 +1,295 @@ +/* remove max-width restriction */ +div.body { + max-width: none; +} + +/* styling for the protocols vs. OS table in overview.rst */ +/* first, general bits */ +div.body td.mark { + text-align: center; + border-left: 1px solid #ccc; +} +table.mark th { + text-align: center; +} +table.mark td { + vertical-align: middle; +} +table.mark cite { + font-weight: bold; +} + +/* individual Y/N/... cells */ +td.mark { + width: 4.5em; +} +table.mark strong { + display:block; + text-align: center; + margin:auto; + padding-top: 8pt; + padding-bottom: 2pt; +} +td.mark span { + display: block; + padding: 3px 1px; + border: 1px dotted #666; + width: 36pt; + margin:auto; + text-align:center; +} +table.mark tr td:first-child { + padding-left:1.5em; +} +table.mark tr td:first-child cite { + margin-left:-1.5em; +} +span.mark-y { background-color: #77ffaa; } +span.mark-geq { background-color: #aaff77; } +span.mark-cp { background-color: #ffbb55; } +span.mark-n { background-color: #ff8877; } +span.mark-dag { background-color: #ffee99; font-size: 8pt; padding:0px 1px; border-top:0px; } + +/* for the legend below */ +li span.mark { + display: inline-block; + padding: 3px 1px; + border: 1px dotted #666; + width: 36pt; + text-align: center; +} + +/* Palette URL: http://paletton.com/#uid=70p0p0kt6uvcDRAlhBavokxLJ6w */ + +:root { +--primary-0: #F36F16; /* Main Primary color */ +--primary-1: #FFC39A; +--primary-2: #FF9A55; +--primary-3: #A34403; +--primary-4: #341500; +--primary-9: #FFF3EB; + +--secondary-1-0: #F39C16; /* Main Secondary color (1) */ +--secondary-1-1: #FFD79A; +--secondary-1-2: #FFBC55; +--secondary-1-3: #A36403; +--secondary-1-4: #341F00; +--secondary-1-9: #FFF7EB; + +--secondary-2-0: #1A599F; /* Main Secondary color (2) */ +--secondary-2-1: #92B9E5; +--secondary-2-2: #477CB8; +--secondary-2-3: #0A386B; +--secondary-2-4: #011122; +--secondary-2-9: #E3EBF4; + +--complement-0: #0E9A83; /* Main Complement color */ +--complement-1: #8AE4D4; +--complement-2: #3CB4A0; +--complement-3: #026857; +--complement-4: #00211B; +--complement-9: #E0F4F0; +} + +/* new */ + +body { + font-family: "Fira Sans", Helvetica, Arial, sans-serif; + font-weight:400; +} +h1, h2, h3, h4, h5, h6 { + font-family: "Fira Sans", Helvetica, Arial, sans-serif; + font-weight:500; +} +code, pre, tt { + font-family: "Fira Mono"; +} +h1 { + background-color:var(--secondary-1-1); + border-bottom:1px solid var(--secondary-1-0); + font-weight:300; +} +h2 { + margin-top:36pt; +} + +a, +a:hover, +a:visited, +.code-block-caption a.headerlink:hover, +.rst-content dl:not(.docutils) dt .headerlink { + color: var(--complement-0); +} +.code-block-caption a.headerlink { + visibility:hidden; +} + +/* admonitions */ + +.admonition.warning { + border:1px dashed var(--primary-2); +} +.admonition.warning .admonition-title { + color: var(--primary-3); + background-color: var(--primary-1); +} +.admonition.note, +.admonition.hint { + border:1px dashed var(--complement-2); +} +.admonition.note .admonition-title, +.admonition.hint .admonition-title { + color: var(--complement-3); + background-color: var(--complement-1); +} +.admonition.seealso, +div.seealso { + background-color:var(--complement-9); +} +.admonition.seealso .admonition-title { + color: var(--complement-3); + background-color:var(--complement-1); + border-bottom:1px solid var(--complement-2); +} +.admonition.admonition-todo .admonition-title { + background-image: repeating-linear-gradient( + 135deg, + #ffa, + #ffa 14.14213452px, + #bbb 14.14213452px, + #bbb 28.28427124px + ); + color:#000; +} +.admonition.admonition-todo { + background-image: repeating-linear-gradient( + 135deg, + #ffd, + #ffd 14.14213452px, + #eed 14.14213452px, + #eed 28.28427124px + ); +} + +.rst-content dl .admonition p.last { + margin-bottom:0 !important; +} + +/* file block */ + +.code-block-caption { +/* border-radius: 4px; */ + font-style:italic; + font-weight:300; + border-bottom: 1px solid var(--secondary-2-1); + background-color: var(--secondary-2-9); + padding:2px 8px; +} + +/* navbar */ + +.wy-nav-side { + background-color: var(--secondary-1-4); + border-right:2px solid var(--primary-3); +} +.wy-menu-vertical a, +.wy-menu-vertical a:visited, +.wy-menu-vertical a:hover, +.wy-side-nav-search>a, +.wy-side-nav-search .wy-dropdown>a { + color: var(--primary-0); +} + +nav div.wy-side-nav-search { + background-color: #eee; +} +nav div.wy-side-scroll { + background-color: var(--secondary-1-4); +} +nav .wy-menu-vertical a:hover { + background-color:var(--primary-0); + color:var(--primary-4); +} +nav .wy-menu-vertical li.current ul a:hover { + background-color:var(--secondary-1-2); + color:var(--primary-4); +} +nav .wy-menu-vertical li.current ul a { + background-color:var(--secondary-1-1); + color:var(--primary-3); +} +nav .wy-menu-vertical li.on a:hover, +nav .wy-menu-vertical li.current>a:hover { + background-color:#fcfcfc; +} +.wy-side-nav-search input[type=text] { + border-color:var(--primary-2); +} +.wy-menu-vertical li.toctree-l1.current>a { + border-top:1px solid var(--secondary-1-3); + border-bottom:1px solid var(--secondary-1-3); +} +.wy-menu-vertical li.toctree-l2.current>a { + background-color:var(--secondary-1-2); +} +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a { + background-color:var(--secondary-1-9); +} + +.wy-nav-content { + padding: 25pt 40pt; +} +div[role=navigation] > hr { + display:none; +} +div[role=navigation] { + margin-bottom:15pt; +} +h1 { + margin-left:-40pt; + margin-right:-40pt; + padding:5pt 40pt 5pt 40pt; +} + +.rst-content pre.literal-block, .rst-content div[class^='highlight'] { + border-color:var(--secondary-1-1); +} + +span.pre { + color: var(--complement-3); +} +pre { + background-color: var(--secondary-1-9); + border-color: var(--secondary-1-1); +} +.highlight .p { color: var(--secondary-2-3); } +.highlight .k { color: var(--secondary-2-0); } +.highlight .kt { color: var(--complement-0); } +.highlight .cm { color: var(--primary-3); } +.highlight .ow { color: var(--primary-3); } +.highlight .na { color: var(--primary-2); } +.highlight .nv { color: var(--complement-0); } + +strong { + font-weight:500; +} +.rst-content dl:not(.docutils) dt { + font-family:Fira Mono; + font-weight:600; + background-color:var(--secondary-2-9); + color:var(--secondary-2-3); + border-top:2px solid var(--secondary-2-2); +} +dt code.descname { + color: var(--secondary-2-4); +} + +@media (min-width: 1200px) { + .container { width: auto; } +} +@media (min-width: 992px) { + .container { width: auto; } +} +@media (min-width: 768px) { + .container { width: auto; } +} diff --git a/doc/user/_static/overrides.js b/doc/user/_static/overrides.js new file mode 100644 index 0000000..73bf612 --- /dev/null +++ b/doc/user/_static/overrides.js @@ -0,0 +1,13 @@ +/* special styling for the protocols vs. OS table in overview.rst + * + * unfortunately this can't be done in straight CSS because we're changing + * the styling on the parent. + */ +$(document).ready(function() { + $("span.mark:contains('Y')" ).addClass("mark-y" ).parent("td").addClass("mark"); + $("span.mark:contains('≥')" ).addClass("mark-geq").parent("td").addClass("mark"); + $("span.mark:contains('N')" ).addClass("mark-n" ).parent("td").addClass("mark"); + $("span.mark:contains('CP')").addClass("mark-cp" ).parent("td").addClass("mark"); + $("span.mark:contains('†')" ).addClass("mark-dag").parent("td").addClass("mark"); + $('td.mark').parents('table').addClass("mark").children('colgroup').remove(); +}); diff --git a/doc/user/affinitymap.rst b/doc/user/affinitymap.rst new file mode 100644 index 0000000..b7dcb14 --- /dev/null +++ b/doc/user/affinitymap.rst @@ -0,0 +1,32 @@ +.. _affinity-map: + +************* +Affinity Maps +************* + +Affinity maps provide a means of configuring Standard Admininistrative-Group +(RFC3630, RFC5305 and RFC5329) and Extended Admininistrative-Group (RFC7308). +An affinity-map maps a specific bit position to a human readable-name. + +An affinity refers to a color or a ressource class in the Traffic Engineering +terminology. The bit position means the position of the bit set starting from +the least significant bit. For example, if the affinity 'blue' has bit position +0 the extended Admin-Group value will be 0x01. If the affinity 'red' bit +position 2 was added to a link in combination with the 'blue' affinity, the +Admin-Group value would be 0x05. + +Command +------- + +.. clicmd:: affinity-map NAME bit-position (0-1023) + + Map the affinity name NAME to the bit-position. The bit-position is the key + so that only one name can be mapped to particular bit-position. + +.. clicmd:: no affinity-map NAME + + Remove the affinity-map mapping. + +Affinity-maps with a bit-position value higher than 31 are not compatible with +Standard Admininistrative-Group. The CLI disallow the usage of such +affinity-maps when Standard Admininistrative-Groups are required. \ No newline at end of file diff --git a/doc/user/babeld.rst b/doc/user/babeld.rst new file mode 100644 index 0000000..b7b7c1f --- /dev/null +++ b/doc/user/babeld.rst @@ -0,0 +1,272 @@ +.. _babel: + +***** +Babel +***** + +Babel is an interior gateway protocol that is suitable both for wired networks +and for wireless mesh networks. Babel has been described as 'RIP on speed' -- +it is based on the same principles as RIP, but includes a number of refinements +that make it react much faster to topology changes without ever counting to +infinity, and allow it to perform reliable link quality estimation on wireless +links. Babel is a double-stack routing protocol, meaning that a single Babel +instance is able to perform routing for both IPv4 and IPv6. + +FRR implements Babel as described in :rfc:`6126`. + +.. _configuring-babeld: + +Configuring babeld +================== + +The *babeld* daemon can be invoked with any of the common +options (:ref:`common-invocation-options`). + +The *zebra* daemon must be running before *babeld* is +invoked. Also, if *zebra* is restarted then *babeld* +must be too. + +.. include:: config-include.rst + +.. _babel-configuration: + +Babel configuration +=================== + +.. clicmd:: router babel + + Enable or disable Babel routing. + +.. clicmd:: babel diversity + + Enable or disable routing using radio frequency diversity. This is + highly recommended in networks with many wireless nodes. + If you enable this, you will probably want to set `babel + diversity-factor` and `babel channel` below. + + +.. clicmd:: babel diversity-factor (1-256) + + Sets the multiplicative factor used for diversity routing, in units of + 1/256; lower values cause diversity to play a more important role in + route selection. The default it 256, which means that diversity plays + no role in route selection; you will probably want to set that to 128 + or less on nodes with multiple independent radios. + +.. clicmd:: network IFNAME + + Enable or disable Babel on the given interface. + + +.. clicmd:: babel + + Specifies whether this interface is wireless, which disables a number + of optimisations that are only correct on wired interfaces. + Specifying `wireless` (the default) is always correct, but may + cause slower convergence and extra routing traffic. + +.. clicmd:: babel split-horizon + + Specifies whether to perform split-horizon on the interface. Specifying + ``no babel split-horizon`` is always correct, while ``babel + split-horizon`` is an optimisation that should only be used on symmetric + and transitive (wired) networks. The default is ``babel split-horizon`` + on wired interfaces, and ``no babel split-horizon`` on wireless + interfaces. This flag is reset when the wired/wireless status of an + interface is changed. + + +.. clicmd:: babel hello-interval (20-655340) + + Specifies the time in milliseconds between two scheduled hellos. On + wired links, Babel notices a link failure within two hello intervals; + on wireless links, the link quality value is reestimated at every + hello interval. The default is 4000 ms. + + +.. clicmd:: babel update-interval (20-655340) + + Specifies the time in milliseconds between two scheduled updates. Since + Babel makes extensive use of triggered updates, this can be set to fairly + high values on links with little packet loss. The default is 20000 ms. + + +.. clicmd:: babel channel (1-254) +.. clicmd:: babel channel interfering +.. clicmd:: babel channel noninterfering + + Set the channel number that diversity routing uses for this interface (see + `babel diversity` above). Noninterfering interfaces are assumed to only + interfere with themselves, interfering interfaces are assumed to interfere + with all other channels except noninterfering channels, and interfaces with + a channel number interfere with interfering interfaces and interfaces with + the same channel number. The default is ``babel channel interfering`` for + wireless interfaces, and ``babel channel noninterfering`` for wired + interfaces. This is reset when the wired/wireless status of an interface is + changed. + + +.. clicmd:: babel rxcost (1-65534) + + Specifies the base receive cost for this interface. For wireless + interfaces, it specifies the multiplier used for computing the ETX + reception cost (default 256); for wired interfaces, it specifies the + cost that will be advertised to neighbours. This value is reset when + the wired/wireless attribute of the interface is changed. + +.. note:: + Do not use this command unless you know what you are doing; in most + networks, acting directly on the cost using route maps is a better + technique. + + +.. clicmd:: babel rtt-decay (1-256) + + This specifies the decay factor for the exponential moving average of + RTT samples, in units of 1/256. Higher values discard old samples + faster. The default is 42. + + +.. clicmd:: babel rtt-min (1-65535) + + This specifies the minimum RTT, in milliseconds, starting from which we + increase the cost to a neighbour. The additional cost is linear in + (rtt - rtt-min). The default is 10 ms. + + +.. clicmd:: babel rtt-max (1-65535) + + This specifies the maximum RTT, in milliseconds, above which we don't + increase the cost to a neighbour. The default is 120 ms. + + +.. clicmd:: babel max-rtt-penalty (0-65535) + + This specifies the maximum cost added to a neighbour because of RTT, i.e. + when the RTT is higher or equal than rtt-max. The default is 150. Setting it + to 0 effectively disables the use of a RTT-based cost. + + +.. clicmd:: babel enable-timestamps + + Enable or disable sending timestamps with each Hello and IHU message in + order to compute RTT values. The default is `no babel enable-timestamps`. + + +.. clicmd:: babel resend-delay (20-655340) + + Specifies the time in milliseconds after which an 'important' request or + update will be resent. The default is 2000 ms. You probably don't want to + tweak this value. + + +.. clicmd:: babel smoothing-half-life (0-65534) + + Specifies the time constant, in seconds, of the smoothing algorithm used for + implementing hysteresis. Larger values reduce route oscillation at the cost + of very slightly increasing convergence time. The value 0 disables + hysteresis, and is suitable for wired networks. The default is 4 s. + +.. _babel-redistribution: + +Babel redistribution +==================== + + +.. clicmd:: redistribute KIND + + Specify which kind of routes should be redistributed into Babel. + +.. _show-babel-information: + +Show Babel information +====================== + +These commands dump various parts of *babeld*'s internal state. + + +.. clicmd:: show babel route + + +.. clicmd:: show babel route A.B.C.D + + +.. clicmd:: show babel route X:X::X:X + + +.. clicmd:: show babel route A.B.C.D/M + + +.. clicmd:: show babel route X:X::X:X/M + + +.. clicmd:: show babel interface + + +.. clicmd:: show babel interface IFNAME + + +.. clicmd:: show babel neighbor + + +.. clicmd:: show babel parameters + +Babel debugging commands +======================== + + simple: debug babel KIND + simple: no debug babel KIND + +.. clicmd:: debug babel KIND + + Enable or disable debugging messages of a given kind. ``KIND`` can + be one of: + + - ``common`` + - ``filter`` + - ``timeout`` + - ``interface`` + - ``route`` + - ``all`` + +.. note:: + If you have compiled with the ``NO_DEBUG`` flag, then these commands aren't + available. + + +Babel sample configuration file +=============================== + +.. code-block:: frr + + debug babel common + !debug babel kernel + !debug babel filter + !debug babel timeout + !debug babel interface + !debug babel route + !debug babel all + + router babel + ! network wlan0 + ! network eth0 + ! redistribute ipv4 kernel + ! no redistribute ipv6 static + + ! The defaults are fine for a wireless interface + + !interface wlan0 + + ! A few optimisation tweaks are optional but recommended on a wired interface + ! Disable link quality estimation, enable split horizon processing, and + ! increase the hello and update intervals. + + !interface eth0 + ! babel wired + ! babel split-horizon + ! babel hello-interval 12000 + ! babel update-interval 36000 + + ! log file /var/log/frr/babeld.log + log stdout + diff --git a/doc/user/basic.rst b/doc/user/basic.rst new file mode 100644 index 0000000..5fdd188 --- /dev/null +++ b/doc/user/basic.rst @@ -0,0 +1,1061 @@ +.. _basic-commands: + +************** +Basic Commands +************** + +The following sections discuss commands common to all the routing daemons. + +.. _config-commands: + +Config Commands +=============== + +In the config file, you can write the debugging options, a vty's password, +routing daemon configurations, a log file name, and so forth. This information +forms the initial command set for a routing beast as it is starting. + +.. _config-file: + +Integrated Config File +---------------------- + +FRR uses a single configuration file located in |INSTALL_PREFIX_ETC|/frr.conf. +When FRR is started using an init script or ``systemd``, ``vtysh`` is invoked to +read the config file and send the appropriate portions to only the daemons +interested in them. Running configuration updates are persisted back to this +single file using ``vtysh`` as well. + +.. include:: prior-config-files.rst + +.. _basic-config-commands: + +Basic Config Commands +--------------------- + +.. clicmd:: hostname HOSTNAME + + Set hostname of the router. It is only for current ``vtysh``, it will not be + saved to any configuration file even with ``write file``. + +.. clicmd:: domainname DOMAINNAME + + Set domainname of the router. It is only for current ``vtysh``, it will not + be saved to any configuration file even with ``write file``. + +.. clicmd:: password PASSWORD + + Set password for vty interface. The ``no`` form of the command deletes the + password. If there is no password, a vty won't accept connections. + +.. clicmd:: enable password PASSWORD + + Set enable password. The ``no`` form of the command deletes the enable + password. + +.. clicmd:: service cputime-stats + + Collect CPU usage statistics for individual FRR event handlers and CLI + commands. This is enabled by default and can be disabled if the extra + overhead causes a noticeable slowdown on your system. + + Disabling these statistics will also make the + :clicmd:`service cputime-warning (1-4294967295)` limit non-functional. + +.. clicmd:: service cputime-warning (1-4294967295) + + Warn if the CPU usage of an event handler or CLI command exceeds the + specified limit (in milliseconds.) Such warnings are generally indicative + of some routine in FRR mistakenly blocking/hogging the processing loop and + should be reported as a FRR bug. + + This command has no effect if :clicmd:`service cputime-stats` is disabled. + +.. clicmd:: service walltime-warning (1-4294967295) + + Warn if the total wallclock time spent handling an event or executing a CLI + command exceeds the specified limit (in milliseconds.) This includes time + spent waiting for I/O or other tasks executing and may produce excessive + warnings if the system is overloaded. (This may still be useful to + provide an immediate sign that FRR is not operating correctly due to + externally caused starvation.) + +.. clicmd:: log trap LEVEL + + These commands are deprecated and are present only for historical + compatibility. The log trap command sets the current logging level for all + enabled logging destinations, and it sets the default for all future logging + commands that do not specify a level. The normal default logging level is + debugging. The ``no`` form of the command resets the default level for + future logging commands to debugging, but it does not change the logging + level of existing logging destinations. + + +.. clicmd:: log stdout LEVEL + + Enable logging output to stdout. If the optional second argument specifying + the logging level is not present, the default logging level (typically + debugging) will be used. The ``no`` form of the command disables logging to + stdout. The ``LEVEL`` argument must have one of these values: emergencies, + alerts, critical, errors, warnings, notifications, informational, or + debugging. Note that the existing code logs its most important messages with + severity ``errors``. + + .. note:: + + If ``systemd`` is in use and stdout is connected to systemd, FRR will + automatically switch to ``journald`` extended logging for this target. + + .. warning:: + + FRRouting uses the ``writev()`` system call to write log messages. This + call is supposed to be atomic, but in reality this does not hold for + pipes or terminals, only regular files. This means that in rare cases, + concurrent log messages from distinct threads may get jumbled in + terminal output. Use a log file and ``tail -f`` if this rare chance is + inacceptable to your setup. + +.. clicmd:: log file [FILENAME [LEVEL]] + + If you want to log into a file, please specify ``filename`` as + in this example: + + :: + + log file /var/log/frr/bgpd.log informational + + If the optional second argument specifying the logging level is not present, + the default logging level (typically debugging, but can be changed using the + deprecated ``log trap`` command) will be used. The ``no`` form of the command + disables logging to a file. + +.. clicmd:: log daemon DAEMON file [FILENAME [LEVEL]] + + Configure file logging for a single FRR daemon. If you want to log + into a file, please specify ``filename`` as in this example: + + :: + + log daemon bgpd file /var/log/frr/bgpd.log informational + + If the optional second argument specifying the logging level is not present, + the default logging level (typically debugging, but can be changed using the + deprecated ``log trap`` command) will be used. The ``no`` form of the command + disables logging to a file for a single FRR daemon. + +.. clicmd:: log syslog [LEVEL] + + Enable logging output to syslog. If the optional second argument specifying + the logging level is not present, the default logging level (typically + debugging, but can be changed using the deprecated ``log trap`` command) will + be used. The ``no`` form of the command disables logging to syslog. + + .. note:: + + This uses the system's ``syslog()`` API, which does not support message + batching or structured key/value data pairs. If possible, use + :clicmd:`log extended EXTLOGNAME` with + :clicmd:`destination syslog [supports-rfc5424]` instead of this. + +.. clicmd:: log extended EXTLOGNAME + + Create an extended logging target with the specified name. The name has + no further meaning and is only used to identify the target. Multiple + targets can be created and deleted with the ``no`` form. + + Refer to :ref:`ext-log-target` for further details and suboptions. + +.. clicmd:: log monitor [LEVEL] + + This command is deprecated and does nothing. + +.. clicmd:: log facility [FACILITY] + + This command changes the facility used in syslog messages. The default + facility is ``daemon``. The ``no`` form of the command resets the facility + to the default ``daemon`` facility. + +.. clicmd:: log record-priority + + To include the severity in all messages logged to a file, to stdout, or to + a terminal monitor (i.e. anything except syslog), + use the ``log record-priority`` global configuration command. + To disable this option, use the ``no`` form of the command. By default, + the severity level is not included in logged messages. Note: some + versions of syslogd can be configured to include the facility and + level in the messages emitted. + +.. clicmd:: log timestamp precision [(0-6)] + + This command sets the precision of log message timestamps to the given + number of digits after the decimal point. Currently, the value must be in + the range 0 to 6 (i.e. the maximum precision is microseconds). To restore + the default behavior (1-second accuracy), use the ``no`` form of the + command, or set the precision explicitly to 0. + + :: + + log timestamp precision 3 + + In this example, the precision is set to provide timestamps with + millisecond accuracy. + +.. clicmd:: log commands + + This command enables the logging of all commands typed by a user to all + enabled log destinations. The note that logging includes full command lines, + including passwords. If the daemon startup option `--command-log-always` + is used to start the daemon then this command is turned on by default + and cannot be turned off and the [no] form of the command is disallowed. + +.. clicmd:: log filtered-file [FILENAME [LEVEL]] + + Configure a destination file for filtered logs with the + :clicmd:`log filter-text WORD` command. + +.. clicmd:: log filter-text WORD + + This command forces logs to be filtered on a specific string. A log message + will only be printed if it matches on one of the filters in the log-filter + table. The filter only applies to file logging targets configured with + :clicmd:`log filtered-file [FILENAME [LEVEL]]`. + + .. note:: + + Log filters help when you need to turn on debugs that cause significant + load on the system (enabling certain debugs can bring FRR to a halt). + Log filters prevent this but you should still expect a small performance + hit due to filtering each of all those logs. + + .. note:: + + This setting is not saved to ``frr.conf`` and not shown in + :clicmd:`show running-config`. It is intended for ephemeral debugging + purposes only. + +.. clicmd:: clear log filter-text + + This command clears all current filters in the log-filter table. + + +.. clicmd:: log immediate-mode + + Use unbuffered output for log and debug messages; normally there is + some internal buffering. + +.. clicmd:: log unique-id + + Include ``[XXXXX-XXXXX]`` log message unique identifier in the textual part + of log messages. This is enabled by default, but can be disabled with + ``no log unique-id``. Please make sure the IDs are enabled when including + logs for FRR bug reports. + + The unique identifiers are automatically generated based on source code + file name, format string (before filling out) and severity. They do not + change "randomly", but some cleanup work may cause large chunks of ID + changes between releases. The IDs always start with a letter, consist of + letters and numbers (and a dash for readability), are case insensitive, and + ``I``, ``L``, ``O`` & ``U`` are excluded. + + This option will not affect future logging targets which allow putting the + unique identifier in auxiliary metadata outside the log message text + content. (No such logging target exists currently, but RFC5424 syslog and + systemd's journald both support it.) + +.. clicmd:: debug unique-id XXXXX-XXXXX backtrace + + Print backtraces (call stack) for specific log messages, identified by + their unique ID (see above.) Includes source code location and current + event handler being executed. On some systems you may need to install a + `debug symbols` package to get proper function names rather than raw code + pointers. + + This command can be issued inside and outside configuration mode, and is + saved to configuration only if it was given in configuration mode. + + .. warning:: + + Printing backtraces can significantly slow down logging calls and cause + log files to quickly balloon in size. Remember to disable backtraces + when they're no longer needed. + +.. clicmd:: debug routemap [detail] + + This command turns on debugging of routemaps. When detail is specified + more data is provided to the operator about the reasoning about what + is going on in the routemap code. + +.. clicmd:: service password-encryption + + Encrypt password. + +.. clicmd:: service advanced-vty + + Enable advanced mode VTY. + +.. clicmd:: service terminal-length (0-512) + + Set system wide line configuration. This configuration command applies to + all VTY interfaces. + +.. clicmd:: line vty + + Enter vty configuration mode. + +.. clicmd:: banner motd default + + Set default motd string. + +.. clicmd:: banner motd file FILE + + Set motd string from file. The file must be in directory specified + under ``--sysconfdir``. + +.. clicmd:: banner motd line LINE + + Set motd string from an input. + +.. clicmd:: exec-timeout MINUTE [SECOND] + + Set VTY connection timeout value. When only one argument is specified + it is used for timeout value in minutes. Optional second argument is + used for timeout value in seconds. Default timeout value is 10 minutes. + When timeout value is zero, it means no timeout. + + Not setting this, or setting the values to 0 0, means a timeout will not be + enabled. + +.. clicmd:: access-class ACCESS-LIST + + Restrict vty connections with an access list. + +.. clicmd:: allow-reserved-ranges + + Allow using IPv4 reserved (Class E) IP ranges for daemons. E.g.: setting + IPv4 addresses for interfaces or allowing reserved ranges in BGP next-hops. + + If you need multiple FRR instances (or FRR + any other daemon) running in a + single router and peering via 127.0.0.0/8, it's also possible to use this + knob if turned on. + + Default: off. + +.. _sample-config-file: + +Sample Config File +------------------ + +Below is a sample configuration file for the zebra daemon. + +.. code-block:: frr + + ! + ! Zebra configuration file + ! + frr version 6.0 + frr defaults traditional + ! + hostname Router + password zebra + enable password zebra + ! + log stdout + ! + ! + + +``!`` and ``#`` are comment characters. If the first character of the word is +one of the comment characters then from the rest of the line forward will be +ignored as a comment. + +.. code-block:: frr + + password zebra!password + +If a comment character is not the first character of the word, it's a normal +character. So in the above example ``!`` will not be regarded as a comment and +the password is set to ``zebra!password``. + + +Configuration versioning, profiles and upgrade behavior +------------------------------------------------------- + +All |PACKAGE_NAME| daemons share a mechanism to specify a configuration profile +and version for loading and saving configuration. Specific configuration +settings take different default values depending on the selected profile and +version. + +While the profile can be selected by user configuration and will remain over +upgrades, |PACKAGE_NAME| will always write configurations using its current +version. This means that, after upgrading, a ``write file`` may write out a +slightly different configuration than what was read in. + +Since the previous configuration is loaded with its version's defaults, but +the new configuration is written with the new defaults, any default that +changed between versions will result in an appropriate configuration entry +being written out. **FRRouting configuration is sticky, staying consistent +over upgrades.** Changed defaults will only affect new configuration. + +Note that the loaded version persists into interactive configuration +sessions. Commands executed in an interactive configuration session are +no different from configuration loaded at startup. This means that when, +say, you configure a new BGP peer, the defaults used for configuration +are the ones selected by the last ``frr version`` command. + +.. warning:: + + Saving the configuration does not bump the daemons forward to use the new + version for their defaults, but restarting them will, since they will then + apply the new ``frr version`` command that was written out. Manually + execute the ``frr version`` command in ``show running-config`` to avoid + this intermediate state. + +This is visible in ``show running-config``: + +.. code-block:: frr + + Current configuration: + ! + ! loaded from 6.0 + frr version 6.1-dev + frr defaults traditional + ! + +If you save and then restart with this configuration, the old defaults will +no longer apply. Similarly, you could execute ``frr version 6.1-dev``, causing +the new defaults to apply and the ``loaded from 6.0`` comment to disappear. + + +Profiles +^^^^^^^^ + +|PACKAGE_NAME| provides configuration profiles to adapt its default settings +to various usage scenarios. Currently, the following profiles are +implemented: + +* ``traditional`` - reflects defaults adhering mostly to IETF standards or + common practices in wide-area internet routing. +* ``datacenter`` - reflects a single administrative domain with intradomain + links using aggressive timers. + +Your distribution/installation may pre-set a profile through the ``-F`` command +line option on all daemons. All daemons must be configured for the same +profile. The value specified on the command line is only a pre-set and any +``frr defaults`` statement in the configuration will take precedence. + +.. note:: + + The profile must be the same across all daemons. Mismatches may result + in undefined behavior. + +You can freely switch between profiles without causing any interruption or +configuration changes. All settings remain at their previous values, and +``show running-configuration`` output will have new output listing the previous +default values as explicit configuration. New configuration, e.g. adding a +BGP peer, will use the new defaults. To apply the new defaults for existing +configuration, the previously-invisible old defaults that are now shown must +be removed from the configuration. + + +Upgrade practices for interactive configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you configure |PACKAGE_NAME| interactively and use the configuration +writing functionality to make changes persistent, the following +recommendations apply in regards to upgrades: + +1. Skipping major versions should generally work but is still inadvisable. + To avoid unneeded issue, upgrade one major version at a time and write + out the configuration after each update. + +2. After installing a new |PACKAGE_NAME| version, check the configuration + for differences against your old configuration. If any defaults changed + that affect your setup, lines may appear or disappear. If a new line + appears, it was previously the default (or not supported) and is now + necessary to retain previous behavior. If a line disappears, it + previously wasn't the default, but now is, so it is no longer necessary. + +3. Check the log files for deprecation warnings by using ``grep -i deprecat``. + +4. After completing each upgrade, save the configuration and either restart + |PACKAGE_NAME| or execute ``frr version `` to ensure defaults of + the new version are fully applied. + + +Upgrade practices for autogenerated configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using |PACKAGE_NAME| with generated configurations (e.g. Ansible, +Puppet, etc.), upgrade considerations differ somewhat: + +1. Always write out a ``frr version`` statement in the configurations you + generate. This ensures that defaults are applied consistently. + +2. Try to not run more distinct versions of |PACKAGE_NAME| than necessary. + Each version may need to be checked individually. If running a mix of + older and newer installations, use the oldest version for the + ``frr version`` statement. + +3. When rolling out upgrades, generate a configuration as usual with the old + version identifier and load it. Check for any differences or deprecation + warnings. If there are differences in the configuration, propagate these + back to the configuration generator to minimize relying on actual default + values. + +4. After the last installation of an old version is removed, change the + configuration generation to a newer ``frr version`` as appropriate. Perform + the same checks as when rolling out upgrades. + + +.. _terminal-mode-commands: + +Terminal Mode Commands +====================== + +.. clicmd:: write terminal + + Displays the current configuration to the vty interface. + +.. clicmd:: write file + + Write current configuration to configuration file. + +.. clicmd:: configure [terminal] + + Change to configuration mode. This command is the first step to + configuration. + +.. clicmd:: terminal length (0-512) + + Set terminal display length to ``(0-512)``. If length is 0, no display + control is performed. + +.. clicmd:: who + + Show a list of currently connected vty sessions. + +.. clicmd:: list + + List all available commands. + +.. clicmd:: show version + + Show the current version of |PACKAGE_NAME| and its build host information. + +.. clicmd:: show logging + + Shows the current configuration of the logging system. This includes the + status of all logging destinations. + +.. clicmd:: show log-filter + + Shows the current log filters applied to each daemon. + +.. clicmd:: show memory [DAEMON] + + Show information on how much memory is used for which specific things in + |PACKAGE_NAME|. Output may vary depending on system capabilities but will + generally look something like this: + + :: + + frr# show memory + System allocator statistics: + Total heap allocated: 1584 KiB + Holding block headers: 0 bytes + Used small blocks: 0 bytes + Used ordinary blocks: 1484 KiB + Free small blocks: 2096 bytes + Free ordinary blocks: 100 KiB + Ordinary blocks: 2 + Small blocks: 60 + Holding blocks: 0 + (see system documentation for 'mallinfo' for meaning) + --- qmem libfrr --- + Buffer : 3 24 72 + Buffer data : 1 4120 4120 + Host config : 3 (variably sized) 72 + Command Tokens : 3427 72 247160 + Command Token Text : 2555 (variably sized) 83720 + Command Token Help : 2555 (variably sized) 61720 + Command Argument : 2 (variably sized) 48 + Command Argument Name : 641 (variably sized) 15672 + [...] + --- qmem Label Manager --- + --- qmem zebra --- + ZEBRA VRF : 1 912 920 + Route Entry : 11 80 968 + Static route : 1 192 200 + RIB destination : 8 48 448 + RIB table info : 4 16 96 + Nexthop tracking object : 1 200 200 + Zebra Name Space : 1 312 312 + --- qmem Table Manager --- + + To understand system allocator statistics, refer to your system's + :manpage:`mallinfo(3)` man page. + + Below these statistics, statistics on individual memory allocation types + in |PACKAGE_NAME| (so-called `MTYPEs`) is printed: + + * the first column of numbers is the current count of allocations made for + the type (the number decreases when items are freed.) + * the second column is the size of each item. This is only available if + allocations on a type are always made with the same size. + * the third column is the total amount of memory allocated for the + particular type, including padding applied by malloc. This means that + the number may be larger than the first column multiplied by the second. + Overhead incurred by malloc's bookkeeping is not included in this, and + the column may be missing if system support is not available. + + When executing this command from ``vtysh``, each of the daemons' memory + usage is printed sequentially. You can specify the daemon's name to print + only its memory usage. + +.. clicmd:: show motd + + Show current motd banner. + +.. clicmd:: show history + + Dump the vtysh cli history. + +.. clicmd:: logmsg LEVEL MESSAGE + + Send a message to all logging destinations that are enabled for messages of + the given severity. + +.. clicmd:: find REGEX... + + This command performs a regex search across all defined commands in all + modes. As an example, suppose you're in enable mode and can't remember where + the command to turn OSPF segment routing on is: + + :: + + frr# find segment-routing on + (ospf) segment-routing on + (isis) segment-routing on + + + The CLI mode is displayed next to each command. In this example, + :clicmd:`segment-routing on` is under the `router ospf` mode. + + Similarly, suppose you want a listing of all commands that contain "l2vpn" + and "neighbor": + + :: + + frr# find l2vpn.*neighbor + (view) show [ip] bgp l2vpn evpn neighbors advertised-routes [json] + (view) show [ip] bgp l2vpn evpn neighbors routes [json] + (view) show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN neighbors advertised-routes [json] + (view) show [ip] bgp l2vpn evpn rd ASN:NN_OR_IP-ADDRESS:NN neighbors routes [json] + ... + + + Note that when entering spaces as part of a regex specification, repeated + spaces will be compressed into a single space for matching purposes. This is + a consequence of spaces being used to delimit CLI tokens. If you need to + match more than one space, use the ``\s`` escape. + + POSIX Extended Regular Expressions are supported. + + +.. _common-show-commands: + +.. clicmd:: show event cpu [r|w|t|e|x] + + This command displays system run statistics for all the different event + types. If no options is specified all different run types are displayed + together. Additionally you can ask to look at (r)ead, (w)rite, (t)imer, + (e)vent and e(x)ecute thread event types. + +.. clicmd:: show event poll + + This command displays FRR's poll data. It allows a glimpse into how + we are setting each individual fd for the poll command at that point + in time. + +.. clicmd:: show event timers + + This command displays FRR's timer data for timers that will pop in + the future. + +.. clicmd:: show configuration running [ [translate WORD]] [with-defaults] DAEMON + + This command displays the northbound/YANG configuration data for a + daemon in text/vty, json, or xml format. + +.. clicmd:: show yang operational-data XPATH [{format |translate TRANSLATOR|with-config}] DAEMON + + Display the YANG operational data starting from XPATH. The default + format is JSON, but can be displayed in XML as well. + + Normally YANG operational data are located inside containers marked + as `read-only`. + + Optionally it is also possible to display configuration leaves in + addition to operational data with the option `with-config`. This + option enables the display of configuration leaves with their + currently configured value (if the leaf is optional it will only show + if it was created or has a default value). + +.. _common-invocation-options: + +Common Invocation Options +========================= + +These options apply to all |PACKAGE_NAME| daemons. + + +.. option:: -d, --daemon + + Run in daemon mode. + +.. option:: -f, --config_file + + Set configuration file name. + +.. option:: -h, --help + + Display this help and exit. + +.. option:: -i, --pid_file + + Upon startup the process identifier of the daemon is written to a file, + typically in :file:`/var/run`. This file can be used by the init system + to implement commands such as ``.../init.d/zebra status``, + ``.../init.d/zebra restart`` or ``.../init.d/zebra stop``. + + The file name is an run-time option rather than a configure-time option so + that multiple routing daemons can be run simultaneously. This is useful when + using |PACKAGE_NAME| to implement a routing looking glass. One machine can + be used to collect differing routing views from differing points in the + network. + +.. option:: -A, --vty_addr

+ + Set the VTY local address to bind to. If set, the VTY socket will only be + bound to this address. + +.. option:: -P, --vty_port + + Set the VTY TCP port number. If set to 0 then the TCP VTY sockets will not + be opened. + +.. option:: -u + + Set the user and group to run as. + +.. option:: -N + + Set the namespace that the daemon will run in. A "/" will + be added to all files that use the statedir. If you have "/var/run/frr" + as the default statedir then it will become "/var/run/frr/". + +.. option:: -o, --vrfdefaultname + + Set the name used for the *Default VRF* in CLI commands and YANG models. + This option must be the same for all running daemons. By default, the name + is "default". + + .. seealso:: :ref:`zebra-vrf` + +.. option:: -v, --version + + Print program version. + +.. option:: --command-log-always + + Cause the daemon to always log commands entered to the specified log file. + This also makes the `no log commands` command disallowed. Enabling this + is suggested if you have need to track what the operator is doing on + this router. + +.. option:: --log + + When initializing the daemon, setup the log to go to either stdout, + syslog or to a file. These values will be displayed as part of + a show run. Additionally they can be overridden at runtime if + desired via the normal log commands. + +.. option:: --log-level + + When initializing the daemon, allow the specification of a default + log level at startup from one of the specified levels. + +.. option:: --tcli + + Enable the transactional CLI mode. + +.. option:: --limit-fds + + Limit the number of file descriptors that will be used internally + by the FRR daemons. By default, the daemons use the system ulimit + value. + +.. _loadable-module-support: + +Loadable Module Support +======================= + +FRR supports loading extension modules at startup. Loading, reloading or +unloading modules at runtime is not supported (yet). To load a module, use +the following command line option at daemon startup: + + +.. option:: -M, --module + + Load the specified module, optionally passing options to it. If the module + name contains a slash (/), it is assumed to be a full pathname to a file to + be loaded. If it does not contain a slash, the |INSTALL_PREFIX_MODULES| + directory is searched for a module of the given name; first with the daemon + name prepended (e.g. ``zebra_mod`` for ``mod``), then without the daemon + name prepended. + + This option is available on all daemons, though some daemons may not have + any modules available to be loaded. + + +The SNMP Module +--------------- + +If SNMP is enabled during compile-time and installed as part of the package, +the ``snmp`` module can be loaded for the *Zebra*, *bgpd*, *ospfd*, *ospf6d* +and *ripd* daemons. + +The module ignores any options passed to it. Refer to :ref:`snmp-support` for +information on its usage. + + +The FPM Module +-------------- + +If FPM is enabled during compile-time and installed as part of the package, the +``fpm`` module can be loaded for the *zebra* daemon. This provides the +Forwarding Plane Manager ("FPM") API. + +The module expects its argument to be either ``Netlink`` or ``protobuf``, +specifying the encapsulation to use. ``Netlink`` is the default, and +``protobuf`` may not be available if the module was built without protobuf +support. Refer to :ref:`zebra-fib-push-interface` for more information. + + +.. _virtual-terminal-interfaces: + +Virtual Terminal Interfaces +=========================== + +VTY -- Virtual Terminal [aka TeletYpe] Interface is a command line +interface (CLI) for user interaction with the routing daemon. + + +.. _vty-overview: + +VTY Overview +------------ + +VTY stands for Virtual TeletYpe interface. It means you can connect to +the daemon via the telnet protocol. + +To enable a VTY interface, you have to setup a VTY password. If there +is no VTY password, one cannot connect to the VTY interface at all. + +:: + + % telnet localhost 2601 + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + + Hello, this is |PACKAGE_NAME| (version |PACKAGE_VERSION|) + |COPYRIGHT_STR| + + User Access Verification + + Password: XXXXX + Router> ? + enable . . . Turn on privileged commands + exit . . . Exit current mode and down to previous mode + help . . . Description of the interactive help system + list . . . Print command list + show . . . Show system inform + + wh. . . Display who is on a vty + Router> enable + Password: XXXXX + Router# configure terminal + Router(config)# interface eth0 + Router(config-if)# ip address 10.0.0.1/8 + Router(config-if)# ^Z + Router# + + +.. _vty-modes: + +VTY Modes +--------- + +There are three basic VTY modes: + +There are commands that may be restricted to specific VTY modes. + +.. _vty-view-mode: + +VTY View Mode +^^^^^^^^^^^^^ + +This mode is for read-only access to the CLI. One may exit the mode by +leaving the system, or by entering `enable` mode. + +.. _vty-enable-mode: + +VTY Enable Mode +^^^^^^^^^^^^^^^ + +This mode is for read-write access to the CLI. One may exit the mode by +leaving the system, or by escaping to view mode. + +.. _vty-other-modes: + +VTY Other Modes +^^^^^^^^^^^^^^^ + +This page is for describing other modes. + +.. _vty-cli-commands: + +VTY CLI Commands +---------------- + +Commands that you may use at the command-line are described in the following +three subsubsections. + +.. _cli-movement-commands: + +CLI Movement Commands +^^^^^^^^^^^^^^^^^^^^^ + +These commands are used for moving the CLI cursor. The :kbd:`C` character +means press the Control Key. + +:kbd:`C-f` / :kbd:`LEFT` + Move forward one character. + +:kbd:`C-b` / :kbd:`RIGHT` + Move backward one character. + +:kbd:`M-f` + Move forward one word. + +:kbd:`M-b` + Move backward one word. + +:kbd:`C-a` + Move to the beginning of the line. + +:kbd:`C-e` + Move to the end of the line. + + +.. _cli-editing-commands: + +CLI Editing Commands +^^^^^^^^^^^^^^^^^^^^ + +These commands are used for editing text on a line. The :kbd:`C` +character means press the Control Key. + + +:kbd:`C-h` / :kbd:`DEL` + Delete the character before point. + + +:kbd:`C-d` + Delete the character after point. + + +:kbd:`M-d` + Forward kill word. + + +:kbd:`C-w` + Backward kill word. + + +:kbd:`C-k` + Kill to the end of the line. + + +:kbd:`C-u` + Kill line from the beginning, erasing input. + + +:kbd:`C-t` + Transpose character. + + +CLI Advanced Commands +^^^^^^^^^^^^^^^^^^^^^ + +There are several additional CLI commands for command line completions, +insta-help, and VTY session management. + + +:kbd:`C-c` + Interrupt current input and moves to the next line. + + +:kbd:`C-z` + End current configuration session and move to top node. + + +:kbd:`C-n` / :kbd:`DOWN` + Move down to next line in the history buffer. + + +:kbd:`C-p` / :kbd:`UP` + Move up to previous line in the history buffer. + + +:kbd:`TAB` + Use command line completion by typing :kbd:`TAB`. + + +:kbd:`?` + You can use command line help by typing ``help`` at the beginning of the + line. Typing :kbd:`?` at any point in the line will show possible + completions. + +Pipe Actions +^^^^^^^^^^^^ + +VTY supports optional modifiers at the end of commands that perform +postprocessing on command output or modify the action of commands. These do not +show up in the :kbd:`?` or :kbd:`TAB` suggestion lists. + +``... | include REGEX`` + Filters the output of the preceding command, including only lines which + match the POSIX Extended Regular Expression ``REGEX``. Do not put the regex + in quotes. + + Examples: + + :: + + frr# show ip bgp sum json | include remoteAs + "remoteAs":0, + "remoteAs":455, + "remoteAs":99, + + :: + + frr# show run | include neigh.*[0-9]{2}\.0\.[2-4]\.[0-9]* + neighbor 10.0.2.106 remote-as 99 + neighbor 10.0.2.107 remote-as 99 + neighbor 10.0.2.108 remote-as 99 + neighbor 10.0.2.109 remote-as 99 + neighbor 10.0.2.110 remote-as 99 + neighbor 10.0.3.111 remote-as 111 + diff --git a/doc/user/bfd.rst b/doc/user/bfd.rst new file mode 100644 index 0000000..3ca104a --- /dev/null +++ b/doc/user/bfd.rst @@ -0,0 +1,766 @@ +.. _bfd: + +********************************** +Bidirectional Forwarding Detection +********************************** + +:abbr:`BFD (Bidirectional Forwarding Detection)` stands for +Bidirectional Forwarding Detection and it is described and extended by +the following RFCs: + +* :rfc:`5880` +* :rfc:`5881` +* :rfc:`5882` +* :rfc:`5883` + +Currently, there are two implementations of the BFD commands in FRR: + +* :abbr:`PTM (Prescriptive Topology Manager)`: an external daemon which + implements BFD; +* ``bfdd``: a BFD implementation that is able to talk with remote peers; + +This document will focus on the later implementation: *bfdd*. + + +.. _bfd-starting: + +Starting BFD +============ + +.. include:: config-include.rst + +*bfdd* default configuration file is :file:`bfdd.conf`. *bfdd* searches +the current directory first then |INSTALL_PREFIX_ETC|/bfdd.conf. All of +*bfdd*'s command must be configured in :file:`bfdd.conf`. + +*bfdd* specific invocation options are described below. Common options +may also be specified (:ref:`common-invocation-options`). + +.. program:: bfdd + +.. option:: --bfdctl + + Set the BFD daemon control socket location. If using a non-default + socket location:: + + /usr/lib/frr/bfdd --bfdctl /tmp/bfdd.sock + + + The default UNIX socket location is |INSTALL_PREFIX_STATE|/bfdd.sock + + This option overrides the location addition that the -N option provides + to the bfdd.sock + +.. option:: --dplaneaddr :
[<:port>] + + Configure the distributed BFD data plane listening socket bind address. + + One would expect the data plane to run in the same machine as FRR, so + the suggested configuration would be: + + --dplaneaddr unix:/var/run/frr/bfdd_dplane.sock + + Or using IPv4: + + --dplaneaddr ipv4:127.0.0.1 + + Or using IPv6: + + --dplaneaddr ipv6:[::1] + + It is also possible to specify a port (for IPv4/IPv6 only): + + --dplaneaddr ipv6:[::1]:50701 + + (if ommited the default port is ``50700``). + + It is also possible to operate in client mode (instead of listening for + connections). To connect to a data plane server append the letter 'c' to + the protocol, example: + + --dplaneaddr ipv4c:127.0.0.1 + +.. note:: + + When using UNIX sockets don't forget to check the file permissions + before attempting to use it. + + +.. _bfd-commands: + +BFDd Commands +============= + +.. clicmd:: bfd + + Opens the BFD daemon configuration node. + +.. clicmd:: peer [{multihop|local-address |interface IFNAME|vrf NAME}] + + Creates and configures a new BFD peer to listen and talk to. + + `multihop` tells the BFD daemon that we should expect packets with + TTL less than 254 (because it will take more than one hop) and to + listen on the multihop port (4784). When using multi-hop mode + `echo-mode` will not work (see :rfc:`5883` section 3). + + `local-address` provides a local address that we should bind our + peer listener to and the address we should use to send the packets. + This option is mandatory for IPv6. + + `interface` selects which interface we should use. + + `vrf` selects which domain we want to use. + + +.. clicmd:: profile WORD + + Creates a peer profile that can be configured in multiple peers. + + Deleting the profile will cause all peers using it to reset to the default + values. + + +.. clicmd:: show bfd [vrf NAME] peers [json] + + Show all configured BFD peers information and current status. + +.. clicmd:: show bfd [vrf NAME] peer [{multihop|local-address |interface IFNAME}]> [json] + + Show status for a specific BFD peer. + +.. clicmd:: show bfd [vrf NAME] peers brief [json] + + Show all configured BFD peers information and current status in brief. + +.. clicmd:: show bfd distributed + + Show the BFD data plane (distributed BFD) statistics. + + +.. _bfd-peer-config: + +Peer / Profile Configuration +---------------------------- + +BFD peers and profiles share the same BFD session configuration commands. + +.. clicmd:: detect-multiplier (2-255) + + Configures the detection multiplier to determine packet loss. The + remote transmission interval will be multiplied by this value to + determine the connection loss detection timer. The default value is + 3. + + Example: when the local system has `detect-multiplier 3` and the + remote system has `transmission interval 300`, the local system will + detect failures only after 900 milliseconds without receiving + packets. + +.. clicmd:: receive-interval (10-60000) + + Configures the minimum interval that this system is capable of + receiving control packets. The default value is 300 milliseconds. + +.. clicmd:: transmit-interval (10-60000) + + The minimum transmission interval (less jitter) that this system + wants to use to send BFD control packets. Defaults to 300ms. + +.. clicmd:: echo receive-interval + + Configures the minimum interval that this system is capable of + receiving echo packets. Disabled means that this system doesn't want + to receive echo packets. The default value is 50 milliseconds. + +.. clicmd:: echo transmit-interval (10-60000) + + The minimum transmission interval (less jitter) that this system + wants to use to send BFD echo packets. Defaults to 50ms. + +.. clicmd:: echo-mode + + Enables or disables the echo transmission mode. This mode is disabled + by default. If you are not using distributed BFD then echo mode works + only when the peer is also FRR. + + It is recommended that the transmission interval of control packets + to be increased after enabling echo-mode to reduce bandwidth usage. + For example: `transmit-interval 2000`. + + Echo mode is not supported on multi-hop setups (see :rfc:`5883` + section 3). + +.. clicmd:: shutdown + + Enables or disables the peer. When the peer is disabled an + 'administrative down' message is sent to the remote peer. + + +.. clicmd:: passive-mode + + Mark session as passive: a passive session will not attempt to start + the connection and will wait for control packets from peer before it + begins replying. + + This feature is useful when you have a router that acts as the + central node of a star network and you want to avoid sending BFD + control packets you don't need to. + + The default is active-mode (or ``no passive-mode``). + +.. clicmd:: minimum-ttl (1-254) + + For multi hop sessions only: configure the minimum expected TTL for + an incoming BFD control packet. + + This feature serves the purpose of thightening the packet validation + requirements to avoid receiving BFD control packets from other + sessions. + + The default value is 254 (which means we only expect one hop between + this system and the peer). + + +BFD Peer Specific Commands +-------------------------- + +.. clicmd:: profile BFDPROF + + Configure peer to use the profile configurations. + + Notes: + + - Profile configurations can be overridden on a peer basis by specifying + non-default parameters in peer configuration node. + - Non existing profiles can be configured and they will only be applied + once they start to exist. + - If the profile gets updated the new configuration will be applied to all + peers with the profile without interruptions. + + +.. _bfd-bgp-peer-config: + +BGP BFD Configuration +--------------------- + +The following commands are available inside the BGP configuration node. + +.. clicmd:: neighbor bfd + + Listen for BFD events registered on the same target as this BGP + neighbor. When BFD peer goes down it immediately asks BGP to shutdown + the connection with its neighbor and, when it goes back up, notify + BGP to try to connect to it. + + +.. clicmd:: neighbor bfd check-control-plane-failure + + Allow to write CBIT independence in BFD outgoing packets. Also allow to + read both C-BIT value of BFD and lookup BGP peer status. This command is + useful when a BFD down event is caught, while the BGP peer requested that + local BGP keeps the remote BGP entries as staled if such issue is detected. + This is the case when graceful restart is enabled, and it is wished to + ignore the BD event while waiting for the remote router to restart. + + Disabling this disables presence of CBIT independence in BFD outgoing + packets and pays attention to BFD down notifications. This is the default. + + +.. clicmd:: neighbor bfd profile BFDPROF + + Same as command ``neighbor bfd``, but applies the + BFD profile to the sessions it creates or that already exist. + + +.. _bfd-isis-peer-config: + +IS-IS BFD Configuration +----------------------- + +The following commands are available inside the interface configuration node. + +.. clicmd:: isis bfd + + Listen for BFD events on peers created on the interface. Every time + a new neighbor is found a BFD peer is created to monitor the link + status for fast convergence. + + Note that there will be just one BFD session per interface. In case both + IPv4 and IPv6 support are configured then just a IPv6 based session is + created. + +.. clicmd:: isis bfd profile BFDPROF + + Use a BFD profile BFDPROF as provided in the BFD configuration. + + +.. _bfd-ospf-peer-config: + +OSPF BFD Configuration +---------------------- + +The following commands are available inside the interface configuration node. + +.. clicmd:: ip ospf bfd + + Listen for BFD events on peers created on the interface. Every time + a new neighbor is found a BFD peer is created to monitor the link + status for fast convergence. + +.. clicmd:: ip ospf bfd profile BFDPROF + + Same as command ``ip ospf bfd``, but applies the BFD profile to the sessions + it creates or that already exist. + + +.. _bfd-ospf6-peer-config: + +OSPF6 BFD Configuration +----------------------- + +The following commands are available inside the interface configuration node. + +.. clicmd:: ipv6 ospf6 bfd [profile BFDPROF] + + Listen for BFD events on peers created on the interface. Every time + a new neighbor is found a BFD peer is created to monitor the link + status for fast convergence. + + Optionally uses the BFD profile ``BFDPROF`` in the created sessions under + that interface. + + +.. _bfd-pim-peer-config: + +PIM BFD Configuration +--------------------- + +The following commands are available inside the interface configuration node. + +.. clicmd:: ip pim bfd [profile BFDPROF] + + Listen for BFD events on peers created on the interface. Every time + a new neighbor is found a BFD peer is created to monitor the link + status for fast convergence. + + Optionally uses the BFD profile ``BFDPROF`` in the created sessions under + that interface. + + +.. _bfd-rip-peer-config: + +RIP BFD configuration +--------------------- + +The following commands are available inside the interface configuration node: + +.. clicmd:: ip rip bfd + + Automatically create BFD session for each RIP peer discovered in this + interface. When the BFD session monitor signalize that the link is down + the RIP peer is removed and all the learned routes associated with that + peer are removed. + + +.. clicmd:: ip rip bfd profile BFD_PROFILE_NAME + + Selects a BFD profile for the BFD sessions created in this interface. + + +The following command is available in the RIP router configuration node: + +.. clicmd:: bfd default-profile BFD_PROFILE_NAME + + Selects a default BFD profile for all sessions without a profile specified. + + +.. _bfd-static-peer-config: + +BFD Static Route Monitoring Configuration +----------------------------------------- + +A monitored static route conditions the installation to the RIB on the +BFD session running state: when BFD session is up the route is installed +to RIB, but when the BFD session is down it is removed from the RIB. + +The following commands are available inside the configuration node: + +.. clicmd:: ip route A.B.C.D/M A.B.C.D bfd [{multi-hop|source A.B.C.D|profile BFDPROF}] + + Configure a static route for ``A.B.C.D/M`` using gateway ``A.B.C.D`` and use + the gateway address as BFD peer destination address. + +.. clicmd:: ipv6 route X:X::X:X/M [from X:X::X:X/M] X:X::X:X bfd [{multi-hop|source X:X::X:X|profile BFDPROF}] + + Configure a static route for ``X:X::X:X/M`` using gateway + ``X:X::X:X`` and use the gateway address as BFD peer destination + address. + +The static routes when uninstalled will no longer show up in the output of +the command ``show ip route`` or ``show ipv6 route``, instead we must use the +BFD static route show command to see these monitored route status. + +.. clicmd:: show bfd static route [json] + + Show all monitored static routes and their status. + + Example output: + + :: + + Showing BFD monitored static routes: + + Route groups: + rtg1 peer 172.16.0.1 (status: uninstalled): + 2001:db8::100/128 + + Next hops: + VRF default IPv4 Unicast: + 192.168.100.0/24 peer 172.16.0.1 (status: uninstalled) + + VRF default IPv4 Multicast: + + VRF default IPv6 Unicast: + +.. _bfd-configuration: + +Configuration +============= + +Before applying ``bfdd`` rules to integrated daemons (like BGPd), we must +create the corresponding peers inside the ``bfd`` configuration node. + +Here is an example of BFD configuration: + +:: + + bfd + peer 192.168.0.1 + no shutdown + ! + ! + router bgp 65530 + neighbor 192.168.0.1 remote-as 65531 + neighbor 192.168.0.1 bfd + neighbor 192.168.0.2 remote-as 65530 + neighbor 192.168.0.2 bfd + neighbor 192.168.0.3 remote-as 65532 + neighbor 192.168.0.3 bfd + ! + +Peers can be identified by its address (use ``multihop`` when you need +to specify a multi hop peer). + +Here are the available peer configurations: + +:: + + bfd + ! Configure a fast profile + profile fast + receive-interval 150 + transmit-interval 150 + ! + + ! Configure peer with fast profile + peer 192.168.0.6 + profile fast + no shutdown + ! + + ! Configure peer with fast profile and override receive speed. + peer 192.168.0.7 + profile fast + receive-interval 500 + no shutdown + ! + + ! configure a peer on an specific interface + peer 192.168.0.1 interface eth0 + no shutdown + ! + + ! configure a multihop peer + peer 192.168.0.2 multihop local-address 192.168.0.3 + shutdown + ! + + ! configure a peer in a different vrf + peer 192.168.0.3 vrf foo + shutdown + ! + + ! configure a peer with every option possible + peer 192.168.0.4 + detect-multiplier 50 + receive-interval 60000 + transmit-interval 3000 + shutdown + ! + + ! configure a peer on an interface from a separate vrf + peer 192.168.0.5 interface eth1 vrf vrf2 + no shutdown + ! + + ! remove a peer + no peer 192.168.0.3 vrf foo + + +.. _bfd-status: + +Status +====== + +You can inspect the current BFD peer status with the following commands: + +:: + + frr# show bfd peers + BFD Peers: + peer 192.168.0.1 + ID: 1 + Remote ID: 1 + Status: up + Uptime: 1 minute(s), 51 second(s) + Diagnostics: ok + Remote diagnostics: ok + Peer Type: dynamic + Local timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + Echo transmission interval: disabled + Remote timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + + peer 192.168.1.1 + ID: 2 + Remote ID: 2 + Status: up + Uptime: 1 minute(s), 53 second(s) + Diagnostics: ok + Remote diagnostics: ok + Peer Type: configured + Local timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + Echo transmission interval: disabled + Remote timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + + frr# show bfd peer 192.168.1.1 + BFD Peer: + peer 192.168.1.1 + ID: 2 + Remote ID: 2 + Status: up + Uptime: 3 minute(s), 4 second(s) + Diagnostics: ok + Remote diagnostics: ok + Peer Type: dynamic + Local timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + Echo transmission interval: disabled + Remote timers: + Detect-multiplier: 3 + Receive interval: 300ms + Transmission interval: 300ms + Echo receive interval: 50ms + + frr# show bfd peer 192.168.0.1 json + {"multihop":false,"peer":"192.168.0.1","id":1,"remote-id":1,"status":"up","uptime":161,"diagnostic":"ok","remote-diagnostic":"ok","receive-interval":300,"transmit-interval":300,"echo-receive-interval":50,"echo-transmit-interval":0,"detect-multiplier":3,"remote-receive-interval":300,"remote-transmit-interval":300,"remote-echo-receive-interval":50,"remote-detect-multiplier":3,"peer-type":"dynamic"} + +If you are running IPV4 BFD Echo, on a Linux platform, we also +calculate round trip time for the packets. We display minimum, +average and maximum time it took to receive the looped Echo packets +in the RTT fields. + +You can inspect the current BFD peer status in brief with the following commands: + +:: + + frr# show bfd peers brief + Session count: 1 + SessionId LocalAddress PeerAddress Status + ========= ============ =========== ====== + 1 192.168.0.1 192.168.0.2 up + + +You can also inspect peer session counters with the following commands: + +:: + + frr# show bfd peers counters + BFD Peers: + peer 192.168.2.1 interface r2-eth2 + Control packet input: 28 packets + Control packet output: 28 packets + Echo packet input: 0 packets + Echo packet output: 0 packets + Session up events: 1 + Session down events: 0 + Zebra notifications: 2 + + peer 192.168.0.1 + Control packet input: 54 packets + Control packet output: 103 packets + Echo packet input: 965 packets + Echo packet output: 966 packets + Session up events: 1 + Session down events: 0 + Zebra notifications: 4 + + frr# show bfd peer 192.168.0.1 counters + peer 192.168.0.1 + Control packet input: 126 packets + Control packet output: 247 packets + Echo packet input: 2409 packets + Echo packet output: 2410 packets + Session up events: 1 + Session down events: 0 + Zebra notifications: 4 + + frr# show bfd peer 192.168.0.1 counters json + {"multihop":false,"peer":"192.168.0.1","control-packet-input":348,"control-packet-output":685,"echo-packet-input":6815,"echo-packet-output":6816,"session-up":1,"session-down":0,"zebra-notifications":4} + +You can also clear packet counters per session with the following commands, only the packet counters will be reset: + +:: + + frr# clear bfd peers counters + + frr# show bfd peers counters + BFD Peers: + peer 192.168.2.1 interface r2-eth2 + Control packet input: 0 packets + Control packet output: 0 packets + Echo packet input: 0 packets + Echo packet output: 0 packets + Session up events: 1 + Session down events: 0 + Zebra notifications: 2 + + peer 192.168.0.1 + Control packet input: 0 packets + Control packet output: 0 packets + Echo packet input: 0 packets + Echo packet output: 0 packets + Session up events: 1 + Session down events: 0 + Zebra notifications: 4 + + +.. _bfd-distributed: + +Distributed BFD +=============== + +The distributed BFD is the separation of the BFD protocol control plane from +the data plane. FRR implements its own BFD data plane protocol so vendors can +study and include it in their own software/hardware without having to modify +the FRR source code. The protocol definitions can be found at +``bfdd/bfddp_packet.h`` header (or the installed +``/usr/include/frr/bfdd/bfddp_packet.h``). + +To use this feature the BFD daemon needs to be started using the command line +option :option:`--dplaneaddr`. When operating using this option the BFD daemon +will not attempt to establish BFD sessions, but it will offload all its work to +the data plane that is (or will be) connected. Data plane reconnection is also +supported. + +The BFD data plane will be responsible for: + +* Sending/receiving the BFD protocol control/echo packets + +* Notifying BFD sessions state changes + +* Keeping the number of packets/bytes received/transmitted per session + + +The FRR BFD daemon will be responsible for: + +* Adding/updating BFD session settings + +* Asking for BFD session counters + +* Redistributing the state changes to the integrated protocols (``bgpd``, + ``ospfd`` etc...) + + +BFD daemon will also keep record of data plane communication statistics with +the command :clicmd:`show bfd distributed`. + +Sample output: + +:: + + frr# show bfd distributed + Data plane + ========== + File descriptor: 16 + Input bytes: 1296 + Input bytes peak: 72 + Input messages: 42 + Input current usage: 0 + Output bytes: 568 + Output bytes peak: 136 + Output messages: 19 + Output full events: 0 + Output current usage: 0 + + +.. _bfd-debugging: + +Debugging +========= + +By default only informational, warning and errors messages are going to be +displayed. If you want to get debug messages and other diagnostics then make +sure you have `debugging` level enabled: + +:: + + config + log file /var/log/frr/frr.log debugging + log syslog debugging + +You may also fine tune the debug messages by selecting one or more of the +debug levels: + +.. clicmd:: debug bfd distributed + + Toggle BFD data plane (distributed BFD) debugging. + + Activates the following debug messages: + + * Data plane received / send messages + * Connection events + +.. clicmd:: debug bfd network + + Toggle network events: show messages about socket failures and unexpected + BFD messages that may not belong to registered peers. + +.. clicmd:: debug bfd peer + + Toggle peer event log messages: show messages about peer creation/removal + and state changes. + +.. clicmd:: debug bfd zebra + + Toggle zebra message events: show messages about interfaces, local + addresses, VRF and daemon peer registrations. diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst new file mode 100644 index 0000000..150a915 --- /dev/null +++ b/doc/user/bgp.rst @@ -0,0 +1,5511 @@ +.. _bgp: + +*** +BGP +*** + +:abbr:`BGP` stands for Border Gateway Protocol. The latest BGP version is 4. +BGP-4 is one of the Exterior Gateway Protocols and the de facto standard +interdomain routing protocol. BGP-4 is described in :rfc:`1771` and updated by +:rfc:`4271`. :rfc:`2858` adds multiprotocol support to BGP-4. + +.. _starting-bgp: + +Starting BGP +============ + +.. include:: config-include.rst + +*bgpd* specific invocation options are described below. Common options may also +be specified (:ref:`common-invocation-options`). + +.. program:: bgpd + +.. option:: -p, --bgp_port + + Set the bgp protocol's port number. When port number is 0, that means do not + listen bgp port. + +.. option:: -l, --listenon + + Specify specific IP addresses for bgpd to listen on, rather than its default + of ``0.0.0.0`` / ``::``. This can be useful to constrain bgpd to an internal + address, or to run multiple bgpd processes on one host. Multiple addresses + can be specified. + + In the following example, bgpd is started listening for connections on the + addresses 100.0.1.2 and fd00::2:2. The options -d (runs in daemon mode) and + -f (uses specific configuration file) are also used in this example as we + are likely to run multiple bgpd instances, each one with different + configurations, when using -l option. + + Note that this option implies the --no_kernel option, and no learned routes will be installed into the linux kernel. + +.. code-block:: shell + + # /usr/lib/frr/bgpd -d -f /some-folder/bgpd.conf -l 100.0.1.2 -l fd00::2:2 + +.. option:: -n, --no_kernel + + Do not install learned routes into the linux kernel. This option is useful + for a route-reflector environment or if you are running multiple bgp + processes in the same namespace. This option is different than the --no_zebra + option in that a ZAPI connection is made. + + This option can also be toggled during runtime by using the + ``[no] bgp no-rib`` commands in VTY shell. + + Note that this option will persist after saving the configuration during + runtime, unless unset by the ``no bgp no-rib`` command in VTY shell prior to + a configuration write operation. + +.. option:: -S, --skip_runas + + Skip the normal process of checking capabilities and changing user and group + information. + +.. option:: -e, --ecmp + + Run BGP with a limited ecmp capability, that is different than what BGP + was compiled with. The value specified must be greater than 0 and less + than or equal to the MULTIPATH_NUM specified on compilation. + +.. option:: -Z, --no_zebra + + Do not communicate with zebra at all. This is different than the --no_kernel + option in that we do not even open a ZAPI connection to the zebra process. + +.. option:: -s, --socket_size + + When opening tcp connections to our peers, set the socket send buffer + size that the kernel will use for the peers socket. This option + is only really useful at a very large scale. Experimentation should + be done to see if this is helping or not at the scale you are running + at. + +.. option:: --v6-with-v4-nexthops + + Allow BGP to peer in the V6 afi, when the interface only has v4 addresses. + This allows bgp to install the v6 routes with a v6 nexthop that has the + v4 address encoded in the nexthop. Zebra's equivalent option currently + overrides the bgp setting. This setting is only really usable when + the operator has turned off communication to zebra and is running bgpd + as a complete standalone process. + +LABEL MANAGER +------------- + +.. option:: -I, --int_num + + Set zclient id. This is required when using Zebra label manager in proxy mode. + +.. _bgp-basic-concepts: + +Basic Concepts +============== + +.. _bgp-autonomous-systems: + +Autonomous Systems +------------------ + +From :rfc:`1930`: + + An AS is a connected group of one or more IP prefixes run by one or more + network operators which has a SINGLE and CLEARLY DEFINED routing policy. + +Each AS has an identifying number associated with it called an :abbr:`ASN +(Autonomous System Number)`. This is a two octet value ranging in value from 1 +to 65535. The AS numbers 64512 through 65535 are defined as private AS numbers. +Private AS numbers must not be advertised on the global Internet. + +The :abbr:`ASN (Autonomous System Number)` is one of the essential elements of +BGP. BGP is a distance vector routing protocol, and the AS-Path framework +provides distance vector metric and loop detection to BGP. + +.. seealso:: :rfc:`1930` + +.. _bgp-address-families: + +Address Families +---------------- + +Multiprotocol extensions enable BGP to carry routing information for multiple +network layer protocols. BGP supports an Address Family Identifier (AFI) for +IPv4 and IPv6. Support is also provided for multiple sets of per-AFI +information via the BGP Subsequent Address Family Identifier (SAFI). FRR +supports SAFIs for unicast information, labeled information (:rfc:`3107` and +:rfc:`8277`), and Layer 3 VPN information (:rfc:`4364` and :rfc:`4659`). + +.. _bgp-route-selection: + +Route Selection +--------------- + +The route selection process used by FRR's BGP implementation uses the following +decision criterion, starting at the top of the list and going towards the +bottom until one of the factors can be used. + +1. **Weight check** + + Prefer higher local weight routes to lower routes. + +2. **Local preference check** + + Prefer higher local preference routes to lower. + + If ``bgp bestpath aigp`` is enabled, and both paths that are compared have + AIGP attribute, BGP uses AIGP tie-breaking unless both of the paths have the + AIGP metric attribute. This means that the AIGP attribute is not evaluated + during the best path selection process between two paths when one path does + not have the AIGP attribute. + +3. **Local route check** + + Prefer local routes (statics, aggregates, redistributed) to received routes. + +4. **AS path length check** + + Prefer shortest hop-count AS_PATHs. + +5. **Origin check** + + Prefer the lowest origin type route. That is, prefer IGP origin routes to + EGP, to Incomplete routes. + +6. **MED check** + + Where routes with a MED were received from the same AS, prefer the route + with the lowest MED. :ref:`bgp-med`. + +7. **External check** + + Prefer the route received from an external, eBGP peer over routes received + from other types of peers. + +8. **IGP cost check** + + Prefer the route with the lower IGP cost. + +9. **Multi-path check** + + If multi-pathing is enabled, then check whether the routes not yet + distinguished in preference may be considered equal. If + :clicmd:`bgp bestpath as-path multipath-relax` is set, all such routes are + considered equal, otherwise routes received via iBGP with identical AS_PATHs + or routes received from eBGP neighbours in the same AS are considered equal. + +10. **Already-selected external check** + + Where both routes were received from eBGP peers, then prefer the route + which is already selected. Note that this check is not applied if + :clicmd:`bgp bestpath compare-routerid` is configured. This check can + prevent some cases of oscillation. + +11. **Router-ID check** + + Prefer the route with the lowest `router-ID`. If the route has an + `ORIGINATOR_ID` attribute, through iBGP reflection, then that router ID is + used, otherwise the `router-ID` of the peer the route was received from is + used. + +12. **Cluster-List length check** + + The route with the shortest cluster-list length is used. The cluster-list + reflects the iBGP reflection path the route has taken. + +13. **Peer address** + + Prefer the route received from the peer with the higher transport layer + address, as a last-resort tie-breaker. + +.. _bgp-capability-negotiation: + +Capability Negotiation +---------------------- + +When adding IPv6 routing information exchange feature to BGP. There were some +proposals. :abbr:`IETF (Internet Engineering Task Force)` +:abbr:`IDR (Inter Domain Routing)` adopted a proposal called Multiprotocol +Extension for BGP. The specification is described in :rfc:`2283`. The protocol +does not define new protocols. It defines new attributes to existing BGP. When +it is used exchanging IPv6 routing information it is called BGP-4+. When it is +used for exchanging multicast routing information it is called MBGP. + +*bgpd* supports Multiprotocol Extension for BGP. So if a remote peer supports +the protocol, *bgpd* can exchange IPv6 and/or multicast routing information. + +Traditional BGP did not have the feature to detect a remote peer's +capabilities, e.g. whether it can handle prefix types other than IPv4 unicast +routes. This was a big problem using Multiprotocol Extension for BGP in an +operational network. :rfc:`2842` adopted a feature called Capability +Negotiation. *bgpd* use this Capability Negotiation to detect the remote peer's +capabilities. If a peer is only configured as an IPv4 unicast neighbor, *bgpd* +does not send these Capability Negotiation packets (at least not unless other +optional BGP features require capability negotiation). + +By default, FRR will bring up peering with minimal common capability for the +both sides. For example, if the local router has unicast and multicast +capabilities and the remote router only has unicast capability the local router +will establish the connection with unicast only capability. When there are no +common capabilities, FRR sends Unsupported Capability error and then resets the +connection. + +.. _bgp-router-configuration: + +BGP Router Configuration +======================== + +ASN and Router ID +----------------- + +First of all you must configure BGP router with the :clicmd:`router bgp ASN` +command. The AS number is an identifier for the autonomous system. The AS +identifier can either be a number or two numbers separated by a period. The +BGP protocol uses the AS identifier for detecting whether the BGP connection is +internal or external. + +.. clicmd:: router bgp ASN + + Enable a BGP protocol process with the specified ASN. After + this statement you can input any `BGP Commands`. + +.. clicmd:: bgp router-id A.B.C.D + + This command specifies the router-ID. If *bgpd* connects to *zebra* it gets + interface and address information. In that case default router ID value is + selected as the largest IP Address of the interfaces. When `router zebra` is + not enabled *bgpd* can't get interface information so `router-id` is set to + 0.0.0.0. So please set router-id by hand. + + +.. _bgp-multiple-autonomous-systems: + +Multiple Autonomous Systems +--------------------------- + +FRR's BGP implementation is capable of running multiple autonomous systems at +once. Each configured AS corresponds to a :ref:`zebra-vrf`. In the past, to get +the same functionality the network administrator had to run a new *bgpd* +process; using VRFs allows multiple autonomous systems to be handled in a +single process. + +When using multiple autonomous systems, all router config blocks after the +first one must specify a VRF to be the target of BGP's route selection. This +VRF must be unique within respect to all other VRFs being used for the same +purpose, i.e. two different autonomous systems cannot use the same VRF. +However, the same AS can be used with different VRFs. + +.. note:: + + The separated nature of VRFs makes it possible to peer a single *bgpd* + process to itself, on one machine. Note that this can be done fully within + BGP without a corresponding VRF in the kernel or Zebra, which enables some + practical use cases such as :ref:`route reflectors ` + and route servers. + +Configuration of additional autonomous systems, or of a router that targets a +specific VRF, is accomplished with the following command: + +.. clicmd:: router bgp ASN vrf VRFNAME + + ``VRFNAME`` is matched against VRFs configured in the kernel. When ``vrf + VRFNAME`` is not specified, the BGP protocol process belongs to the default + VRF. + +An example configuration with multiple autonomous systems might look like this: + +.. code-block:: frr + + router bgp 1 + neighbor 10.0.0.1 remote-as 20 + neighbor 10.0.0.2 remote-as 30 + ! + router bgp 2 vrf blue + neighbor 10.0.0.3 remote-as 40 + neighbor 10.0.0.4 remote-as 50 + ! + router bgp 3 vrf red + neighbor 10.0.0.5 remote-as 60 + neighbor 10.0.0.6 remote-as 70 + ... + +.. seealso:: :ref:`bgp-vrf-route-leaking` +.. seealso:: :ref:`zebra-vrf` + + +.. _bgp-views: + +Views +----- + +In addition to supporting multiple autonomous systems, FRR's BGP implementation +also supports *views*. + +BGP views are almost the same as normal BGP processes, except that routes +selected by BGP are not installed into the kernel routing table. Each BGP view +provides an independent set of routing information which is only distributed +via BGP. Multiple views can be supported, and BGP view information is always +independent from other routing protocols and Zebra/kernel routes. BGP views use +the core instance (i.e., default VRF) for communication with peers. + +.. clicmd:: router bgp AS-NUMBER view NAME + + Make a new BGP view. You can use an arbitrary word for the ``NAME``. Routes + selected by the view are not installed into the kernel routing table. + + With this command, you can setup Route Server like below. + + .. code-block:: frr + + ! + router bgp 1 view 1 + neighbor 10.0.0.1 remote-as 2 + neighbor 10.0.0.2 remote-as 3 + ! + router bgp 2 view 2 + neighbor 10.0.0.3 remote-as 4 + neighbor 10.0.0.4 remote-as 5 + +.. clicmd:: show [ip] bgp view NAME + + Display the routing table of BGP view ``NAME``. + + +Route Selection +--------------- + +.. clicmd:: bgp bestpath as-path confed + + This command specifies that the length of confederation path sets and + sequences should should be taken into account during the BGP best path + decision process. + +.. clicmd:: bgp bestpath as-path multipath-relax + + This command specifies that BGP decision process should consider paths + of equal AS_PATH length candidates for multipath computation. Without + the knob, the entire AS_PATH must match for multipath computation. + +.. clicmd:: bgp bestpath compare-routerid + + Ensure that when comparing routes where both are equal on most metrics, + including local-pref, AS_PATH length, IGP cost, MED, that the tie is broken + based on router-ID. + + If this option is enabled, then the already-selected check, where + already selected eBGP routes are preferred, is skipped. + + If a route has an `ORIGINATOR_ID` attribute because it has been reflected, + that `ORIGINATOR_ID` will be used. Otherwise, the router-ID of the peer the + route was received from will be used. + + The advantage of this is that the route-selection (at this point) will be + more deterministic. The disadvantage is that a few or even one lowest-ID + router may attract all traffic to otherwise-equal paths because of this + check. It may increase the possibility of MED or IGP oscillation, unless + other measures were taken to avoid these. The exact behaviour will be + sensitive to the iBGP and reflection topology. + +.. clicmd:: bgp bestpath peer-type multipath-relax + + This command specifies that BGP decision process should consider paths + from all peers for multipath computation. If this option is enabled, + paths learned from any of eBGP, iBGP, or confederation neighbors will + be multipath if they are otherwise considered equal cost. + +.. clicmd:: bgp bestpath aigp + + Use the bgp bestpath aigp command to evaluate the AIGP attribute during + the best path selection process between two paths that have the AIGP + attribute. + + When bgp bestpath aigp is disabled, BGP does not use AIGP tie-breaking + rules unless paths have the AIGP attribute. + + Disabled by default. + +.. clicmd:: bgp bestpath med missing-as-worst + + If the paths MED value is missing and this command is configured + then treat it as the worse possible value that it can be. + +.. clicmd:: maximum-paths (1-128) + + Sets the maximum-paths value used for ecmp calculations for this + bgp instance in EBGP. The maximum value listed, 128, can be limited by + the ecmp cli for bgp or if the daemon was compiled with a lower + ecmp value. This value can also be set in ipv4/ipv6 unicast/labeled + unicast to only affect those particular afi/safi's. + +.. clicmd:: maximum-paths ibgp (1-128) [equal-cluster-length] + + Sets the maximum-paths value used for ecmp calculations for this + bgp instance in IBGP. The maximum value listed, 128, can be limited by + the ecmp cli for bgp or if the daemon was compiled with a lower + ecmp value. This value can also be set in ipv4/ipv6 unicast/labeled + unicast to only affect those particular afi/safi's. + +.. _bgp-distance: + +Administrative Distance Metrics +------------------------------- + +.. clicmd:: distance bgp (1-255) (1-255) (1-255) + + This command changes distance value of BGP. The arguments are the distance + values for external routes, internal routes and local routes + respectively. + +.. clicmd:: distance (1-255) A.B.C.D/M + +.. clicmd:: distance (1-255) A.B.C.D/M WORD + + Sets the administrative distance for a particular route. + + If the system has a static route configured from the kernel, it has a + distance of 0. In some cases, it might be useful to override the route + from the FRR. E.g.: Kernel has a statically configured default route, + and you received another default route from the BGP and want to install + it to be preferred over the static route. In such a case, you MUST set + a higher distance from the kernel. + + .. seealso:: :ref:`administrative-distance` + +.. _bgp-requires-policy: + +Require policy on EBGP +---------------------- + +.. clicmd:: bgp ebgp-requires-policy + + This command requires incoming and outgoing filters to be applied + for eBGP sessions as part of RFC-8212 compliance. Without the incoming + filter, no routes will be accepted. Without the outgoing filter, no + routes will be announced. + + This is enabled by default for the traditional configuration and + turned off by default for datacenter configuration. + + When you enable/disable this option you MUST clear the session. + + When the incoming or outgoing filter is missing you will see + "(Policy)" sign under ``show bgp summary``: + + .. code-block:: frr + + exit1# show bgp summary + + IPv4 Unicast Summary: + BGP router identifier 10.10.10.1, local AS number 65001 VRF default vrf-id 0 + BGP table version 4 + RIB entries 7, using 1344 bytes of memory + Peers 2, using 43 KiB of memory + + Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc + 192.168.0.2 4 65002 8 10 0 0 0 00:03:09 5 (Policy) N/A + fe80:1::2222 4 65002 9 11 0 0 0 00:03:09 (Policy) (Policy) N/A + + Additionally a `show bgp neighbor` command would indicate in the `For address family:` + block that: + + .. code-block:: frr + + exit1# show bgp neighbor + ... + For address family: IPv4 Unicast + Update group 1, subgroup 1 + Packet Queue length 0 + Inbound soft reconfiguration allowed + Community attribute sent to this neighbor(all) + Inbound updates discarded due to missing policy + Outbound updates discarded due to missing policy + 0 accepted prefixes + +Reject routes with AS_SET or AS_CONFED_SET types +------------------------------------------------ + +.. clicmd:: bgp reject-as-sets + + This command enables rejection of incoming and outgoing routes having AS_SET or AS_CONFED_SET type. + +Enforce first AS +---------------- + +.. clicmd:: bgp enforce-first-as + + To configure a router to deny an update received from an external BGP (eBGP) + peer that does not list its autonomous system number at the beginning of + the `AS_PATH` in the incoming update, use the ``bgp enforce-first-as`` command + in router configuration mode. + + In order to exclude an arbitrary neighbor from this enforcement, use the + command ``no neighbor NAME enforce-first-as``. And vice-versa if a global + enforcement is disabled, you can override this behavior per neighbor too. + + Default: enabled. + +.. note:: + + If you have a peering to RS (Route-Server), most likely you MUST disable the + first AS enforcement. + +Suppress duplicate updates +-------------------------- + +.. clicmd:: bgp suppress-duplicates + + For example, BGP routers can generate multiple identical announcements with + empty community attributes if stripped at egress. This is an undesired behavior. + Suppress duplicate updates if the route actually not changed. + Default: enabled. + +Send Hard Reset CEASE Notification for Administrative Reset +----------------------------------------------------------- + +.. clicmd:: bgp hard-administrative-reset + + Send Hard Reset CEASE Notification for 'Administrative Reset' events. + + When disabled, and Graceful Restart Notification capability is exchanged + between the peers, Graceful Restart procedures apply, and routes will be + retained. + + Enabled by default. + +Disable checking if nexthop is connected on EBGP sessions +--------------------------------------------------------- + +.. clicmd:: bgp disable-ebgp-connected-route-check + + This command is used to disable the connection verification process for EBGP peering sessions + that are reachable by a single hop but are configured on a loopback interface or otherwise + configured with a non-directly connected IP address. + +.. _bgp-route-flap-dampening: + +Route Flap Dampening +-------------------- + +.. clicmd:: bgp dampening (1-45) (1-20000) (1-50000) (1-255) + + This command enables (with optionally specified dampening parameters) or + disables route-flap dampening for all routes of a BGP instance. + +.. clicmd:: neighbor PEER dampening [(1-45) [(1-20000) (1-20000) (1-255)]] + + This command enables (with optionally specified dampening parameters) or + disables route-flap dampening for all routes learned from a BGP peer. + +.. clicmd:: neighbor GROUP dampening [(1-45) [(1-20000) (1-20000) (1-255)]] + + This command enables (with optionally specified dampening parameters) or + disables route-flap dampening for all routes learned from peers of a peer + group. + + half-life + Half-life time for the penalty in minutes (default value: 15). + + reuse-threshold + Value to start reusing a route (default value: 750). + + suppress-threshold + Value to start suppressing a route (default value: 2000). + + max-suppress + Maximum duration to suppress a stable route in minutes (default value: + 60). + + The route-flap damping algorithm is compatible with :rfc:`2439`. The use of + these commands is not recommended nowadays. + + At the moment, route-flap dampening is not working per VRF and is working only + for IPv4 unicast and multicast. + + With different parameter sets configurable for BGP instances, peer groups and + peers, the active dampening profile for a route is chosen on the fly, + allowing for various changes in configuration (i.e. peer group memberships) + during runtime. The parameter sets are taking precedence in the following + order: + + 1. Peer + 2. Peer group + 3. BGP instance + + The negating commands do not allow to exclude a peer/peer group from a peer + group/BGP instances configuration. + +.. seealso:: + https://www.ripe.net/publications/docs/ripe-378 + +.. _bgp-med: + +Multi-Exit Discriminator +------------------------ + +The BGP :abbr:`MED (Multi-Exit Discriminator)` attribute has properties which +can cause subtle convergence problems in BGP. These properties and problems +have proven to be hard to understand, at least historically, and may still not +be widely understood. The following attempts to collect together and present +what is known about MED, to help operators and FRR users in designing and +configuring their networks. + +The BGP :abbr:`MED` attribute is intended to allow one AS to indicate its +preferences for its ingress points to another AS. The MED attribute will not be +propagated on to another AS by the receiving AS - it is 'non-transitive' in the +BGP sense. + +E.g., if AS X and AS Y have 2 different BGP peering points, then AS X might set +a MED of 100 on routes advertised at one and a MED of 200 at the other. When AS +Y selects between otherwise equal routes to or via AS X, AS Y should prefer to +take the path via the lower MED peering of 100 with AS X. Setting the MED +allows an AS to influence the routing taken to it within another, neighbouring +AS. + +In this use of MED it is not really meaningful to compare the MED value on +routes where the next AS on the paths differs. E.g., if AS Y also had a route +for some destination via AS Z in addition to the routes from AS X, and AS Z had +also set a MED, it wouldn't make sense for AS Y to compare AS Z's MED values to +those of AS X. The MED values have been set by different administrators, with +different frames of reference. + +The default behaviour of BGP therefore is to not compare MED values across +routes received from different neighbouring ASes. In FRR this is done by +comparing the neighbouring, left-most AS in the received AS_PATHs of the routes +and only comparing MED if those are the same. + +Unfortunately, this behaviour of MED, of sometimes being compared across routes +and sometimes not, depending on the properties of those other routes, means MED +can cause the order of preference over all the routes to be undefined. That is, +given routes A, B, and C, if A is preferred to B, and B is preferred to C, then +a well-defined order should mean the preference is transitive (in the sense of +orders [#med-transitivity-rant]_) and that A would be preferred to C. + +However, when MED is involved this need not be the case. With MED it is +possible that C is actually preferred over A. So A is preferred to B, B is +preferred to C, but C is preferred to A. This can be true even where BGP +defines a deterministic 'most preferred' route out of the full set of A,B,C. +With MED, for any given set of routes there may be a deterministically +preferred route, but there need not be any way to arrange them into any order +of preference. With unmodified MED, the order of preference of routes literally +becomes undefined. + +That MED can induce non-transitive preferences over routes can cause issues. +Firstly, it may be perceived to cause routing table churn locally at speakers; +secondly, and more seriously, it may cause routing instability in iBGP +topologies, where sets of speakers continually oscillate between different +paths. + +The first issue arises from how speakers often implement routing decisions. +Though BGP defines a selection process that will deterministically select the +same route as best at any given speaker, even with MED, that process requires +evaluating all routes together. For performance and ease of implementation +reasons, many implementations evaluate route preferences in a pair-wise fashion +instead. Given there is no well-defined order when MED is involved, the best +route that will be chosen becomes subject to implementation details, such as +the order the routes are stored in. That may be (locally) non-deterministic, +e.g.: it may be the order the routes were received in. + +This indeterminism may be considered undesirable, though it need not cause +problems. It may mean additional routing churn is perceived, as sometimes more +updates may be produced than at other times in reaction to some event . + +This first issue can be fixed with a more deterministic route selection that +ensures routes are ordered by the neighbouring AS during selection. +:clicmd:`bgp deterministic-med`. This may reduce the number of updates as routes +are received, and may in some cases reduce routing churn. Though, it could +equally deterministically produce the largest possible set of updates in +response to the most common sequence of received updates. + +A deterministic order of evaluation tends to imply an additional overhead of +sorting over any set of n routes to a destination. The implementation of +deterministic MED in FRR scales significantly worse than most sorting +algorithms at present, with the number of paths to a given destination. That +number is often low enough to not cause any issues, but where there are many +paths, the deterministic comparison may quickly become increasingly expensive +in terms of CPU. + +Deterministic local evaluation can *not* fix the second, more major, issue of +MED however. Which is that the non-transitive preference of routes MED can +cause may lead to routing instability or oscillation across multiple speakers +in iBGP topologies. This can occur with full-mesh iBGP, but is particularly +problematic in non-full-mesh iBGP topologies that further reduce the routing +information known to each speaker. This has primarily been documented with iBGP +:ref:`route-reflection ` topologies. However, any +route-hiding technologies potentially could also exacerbate oscillation with MED. + +This second issue occurs where speakers each have only a subset of routes, and +there are cycles in the preferences between different combinations of routes - +as the undefined order of preference of MED allows - and the routes are +distributed in a way that causes the BGP speakers to 'chase' those cycles. This +can occur even if all speakers use a deterministic order of evaluation in route +selection. + +E.g., speaker 4 in AS A might receive a route from speaker 2 in AS X, and from +speaker 3 in AS Y; while speaker 5 in AS A might receive that route from +speaker 1 in AS Y. AS Y might set a MED of 200 at speaker 1, and 100 at speaker +3. I.e, using ASN:ID:MED to label the speakers: + +:: + + . + /---------------\\ + X:2------|--A:4-------A:5--|-Y:1:200 + Y:3:100--|-/ | + \\---------------/ + + + +Assuming all other metrics are equal (AS_PATH, ORIGIN, 0 IGP costs), then based +on the RFC4271 decision process speaker 4 will choose X:2 over Y:3:100, based +on the lower ID of 2. Speaker 4 advertises X:2 to speaker 5. Speaker 5 will +continue to prefer Y:1:200 based on the ID, and advertise this to speaker 4. +Speaker 4 will now have the full set of routes, and the Y:1:200 it receives +from 5 will beat X:2, but when speaker 4 compares Y:1:200 to Y:3:100 the MED +check now becomes active as the ASes match, and now Y:3:100 is preferred. +Speaker 4 therefore now advertises Y:3:100 to 5, which will also agrees that +Y:3:100 is preferred to Y:1:200, and so withdraws the latter route from 4. +Speaker 4 now has only X:2 and Y:3:100, and X:2 beats Y:3:100, and so speaker 4 +implicitly updates its route to speaker 5 to X:2. Speaker 5 sees that Y:1:200 +beats X:2 based on the ID, and advertises Y:1:200 to speaker 4, and the cycle +continues. + +The root cause is the lack of a clear order of preference caused by how MED +sometimes is and sometimes is not compared, leading to this cycle in the +preferences between the routes: + +:: + + . + /---> X:2 ---beats---> Y:3:100 --\\ + | | + | | + \\---beats--- Y:1:200 <---beats---/ + + + +This particular type of oscillation in full-mesh iBGP topologies can be +avoided by speakers preferring already selected, external routes rather than +choosing to update to new a route based on a post-MED metric (e.g. router-ID), +at the cost of a non-deterministic selection process. FRR implements this, as +do many other implementations, so long as it is not overridden by setting +:clicmd:`bgp bestpath compare-routerid`, and see also +:ref:`bgp-route-selection`. + +However, more complex and insidious cycles of oscillation are possible with +iBGP route-reflection, which are not so easily avoided. These have been +documented in various places. See, e.g.: + +- [bgp-route-osci-cond]_ +- [stable-flexible-ibgp]_ +- [ibgp-correctness]_ + +for concrete examples and further references. + +There is as of this writing *no* known way to use MED for its original purpose; +*and* reduce routing information in iBGP topologies; *and* be sure to avoid the +instability problems of MED due the non-transitive routing preferences it can +induce; in general on arbitrary networks. + +There may be iBGP topology specific ways to reduce the instability risks, even +while using MED, e.g.: by constraining the reflection topology and by tuning +IGP costs between route-reflector clusters, see :rfc:`3345` for details. In the +near future, the Add-Path extension to BGP may also solve MED oscillation while +still allowing MED to be used as intended, by distributing "best-paths per +neighbour AS". This would be at the cost of distributing at least as many +routes to all speakers as a full-mesh iBGP would, if not more, while also +imposing similar CPU overheads as the "Deterministic MED" feature at each +Add-Path reflector. + +More generally, the instability problems that MED can introduce on more +complex, non-full-mesh, iBGP topologies may be avoided either by: + +- Setting :clicmd:`bgp always-compare-med`, however this allows MED to be compared + across values set by different neighbour ASes, which may not produce + coherent desirable results, of itself. +- Effectively ignoring MED by setting MED to the same value (e.g.: 0) using + :clicmd:`set metric METRIC` on all received routes, in combination with + setting :clicmd:`bgp always-compare-med` on all speakers. This is the simplest + and most performant way to avoid MED oscillation issues, where an AS is happy + not to allow neighbours to inject this problematic metric. + +As MED is evaluated after the AS_PATH length check, another possible use for +MED is for intra-AS steering of routes with equal AS_PATH length, as an +extension of the last case above. As MED is evaluated before IGP metric, this +can allow cold-potato routing to be implemented to send traffic to preferred +hand-offs with neighbours, rather than the closest hand-off according to the +IGP metric. + +Note that even if action is taken to address the MED non-transitivity issues, +other oscillations may still be possible. E.g., on IGP cost if iBGP and IGP +topologies are at cross-purposes with each other - see the Flavel and Roughan +paper above for an example. Hence the guideline that the iBGP topology should +follow the IGP topology. + +.. clicmd:: bgp deterministic-med + + Carry out route-selection in way that produces deterministic answers + locally, even in the face of MED and the lack of a well-defined order of + preference it can induce on routes. Without this option the preferred route + with MED may be determined largely by the order that routes were received + in. + + Setting this option will have a performance cost that may be noticeable when + there are many routes for each destination. Currently in FRR it is + implemented in a way that scales poorly as the number of routes per + destination increases. + + The default is that this option is not set. + +Note that there are other sources of indeterminism in the route selection +process, specifically, the preference for older and already selected routes +from eBGP peers, :ref:`bgp-route-selection`. + +.. clicmd:: bgp always-compare-med + + Always compare the MED on routes, even when they were received from + different neighbouring ASes. Setting this option makes the order of + preference of routes more defined, and should eliminate MED induced + oscillations. + + If using this option, it may also be desirable to use + :clicmd:`set metric METRIC` to set MED to 0 on routes received from external + neighbours. + + This option can be used, together with :clicmd:`set metric METRIC` to use + MED as an intra-AS metric to steer equal-length AS_PATH routes to, e.g., + desired exit points. + + +.. _bgp-graceful-restart: + +Graceful Restart +---------------- + +BGP graceful restart functionality as defined in +`RFC-4724 `_ defines the mechanisms that +allows BGP speaker to continue to forward data packets along known routes +while the routing protocol information is being restored. + + +Usually, when BGP on a router restarts, all the BGP peers detect that the +session went down and then came up. This "down/up" transition results in a +"routing flap" and causes BGP route re-computation, generation of BGP routing +updates, and unnecessary churn to the forwarding tables. + +The following functionality is provided by graceful restart: + +1. The feature allows the restarting router to indicate to the helping peer the + routes it can preserve in its forwarding plane during control plane restart + by sending graceful restart capability in the OPEN message sent during + session establishment. Graceful restart notification flag and/or restart + time can also be changed during the dynamic BGP capabilities. If using + dynamic capabilities, no session reset is required, thus it's very useful + to increase restart time before doing a software upgrade or so. +2. The feature allows helping router to advertise to all other peers the routes + received from the restarting router which are preserved in the forwarding + plane of the restarting router during control plane restart. + + +:: + + + + (R1)-----------------------------------------------------------------(R2) + + 1. BGP Graceful Restart Capability exchanged between R1 & R2. + + <---------------------------------------------------------------------> + + 2. Kill BGP Process at R1. + + ----------------------------------------------------------------------> + + 3. R2 Detects the above BGP Restart & verifies BGP Restarting + Capability of R1. + + 4. Start BGP Process at R1. + + 5. Re-establish the BGP session between R1 & R2. + + <---------------------------------------------------------------------> + + 6. R2 Send initial route updates, followed by End-Of-Rib. + + <---------------------------------------------------------------------- + + 7. R1 was waiting for End-Of-Rib from R2 & which has been received + now. + + 8. R1 now runs BGP Best-Path algorithm. Send Initial BGP Update, + followed by End-Of Rib + + <---------------------------------------------------------------------> + + +.. _bgp-GR-preserve-forwarding-state: + +BGP-GR Preserve-Forwarding State +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +BGP OPEN message carrying optional capabilities for Graceful Restart has +8 bit “Flags for Address Family” for given AFI and SAFI. This field contains +bit flags relating to routes that were advertised with the given AFI and SAFI. + +.. code-block:: frr + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |F| Reserved | + +-+-+-+-+-+-+-+-+ + +The most significant bit is defined as the Forwarding State (F) bit, which +can be used to indicate whether the forwarding state for routes that were +advertised with the given AFI and SAFI has indeed been preserved during the +previous BGP restart. When set (value 1), the bit indicates that the +forwarding state has been preserved. +The remaining bits are reserved and MUST be set to zero by the sender and +ignored by the receiver. + +.. clicmd:: bgp graceful-restart preserve-fw-state + +FRR gives us the option to enable/disable the "F" flag using this specific +vty command. However, it doesn't have the option to enable/disable +this flag only for specific AFI/SAFI i.e. when this command is used, it +applied to all the supported AFI/SAFI combinations for this peer. + +.. _bgp-end-of-rib-message: + +End-of-RIB (EOR) message +^^^^^^^^^^^^^^^^^^^^^^^^ + +An UPDATE message with no reachable Network Layer Reachability Information +(NLRI) and empty withdrawn NLRI is specified as the End-of-RIB marker that can +be used by a BGP speaker to indicate to its peer the completion of the initial +routing update after the session is established. + +For the IPv4 unicast address family, the End-of-RIB marker is an UPDATE message +with the minimum length. For any other address family, it is an UPDATE message +that contains only the MP_UNREACH_NLRI attribute with no withdrawn routes for +that . + +Although the End-of-RIB marker is specified for the purpose of BGP graceful +restart, it is noted that the generation of such a marker upon completion of +the initial update would be useful for routing convergence in general, and thus +the practice is recommended. + +.. _bgp-route-selection-deferral-timer: + +Route Selection Deferral Timer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Specifies the time the restarting router defers the route selection process +after restart. + +Restarting Router : The usage of route election deferral timer is specified +in https://tools.ietf.org/html/rfc4724#section-4.1 + +Once the session between the Restarting Speaker and the Receiving Speaker is +re-established, the Restarting Speaker will receive and process BGP messages +from its peers. + +However, it MUST defer route selection for an address family until it either. + +1. Receives the End-of-RIB marker from all its peers (excluding the ones with + the "Restart State" bit set in the received capability and excluding the ones + that do not advertise the graceful restart capability). +2. The Selection_Deferral_Timer timeout. + +.. clicmd:: bgp graceful-restart select-defer-time (0-3600) + + This is command, will set deferral time to value specified. + + +.. clicmd:: bgp graceful-restart rib-stale-time (1-3600) + + This is command, will set the time for which stale routes are kept in RIB. + +.. clicmd:: bgp graceful-restart restart-time (0-4095) + + Set the time to wait to delete stale routes before a BGP open message + is received. + + Using with Long-lived Graceful Restart capability, this is recommended + setting this timer to 0 and control stale routes with + ``bgp long-lived-graceful-restart stale-time``. + + Default value is 120. + +.. clicmd:: bgp graceful-restart stalepath-time (1-4095) + + This is command, will set the max time (in seconds) to hold onto + restarting peer's stale paths. + + It also controls Enhanced Route-Refresh timer. + + If this command is configured and the router does not receive a Route-Refresh EoRR + message, the router removes the stale routes from the BGP table after the timer + expires. The stale path timer is started when the router receives a Route-Refresh + BoRR message. + +.. clicmd:: bgp graceful-restart notification + + Indicate Graceful Restart support for BGP NOTIFICATION messages. + + After changing this parameter, you have to reset the peers in order to advertise + N-bit in Graceful Restart capability. + + Without Graceful-Restart Notification capability (N-bit not set), GR is not + activated when receiving CEASE/HOLDTIME expire notifications. + + When sending ``CEASE/Administrative Reset`` (``clear bgp``), the session is closed + and routes are not retained. When N-bit is set and ``bgp hard-administrative-reset`` + is turned off Graceful-Restart is activated and routes are retained. + + Enabled by default. + +.. _bgp-per-peer-graceful-restart: + +BGP Per Peer Graceful Restart +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ability to enable and disable graceful restart, helper and no GR at all mode +functionality at peer level. + +So bgp graceful restart can be enabled at modes global BGP level or at per +peer level. There are two FSM, one for BGP GR global mode and other for peer +per GR. + +Default global mode is helper and default peer per mode is inherit from global. +If per peer mode is configured, the GR mode of this particular peer will +override the global mode. + +.. _bgp-GR-global-mode-cmd: + +BGP GR Global Mode Commands +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: bgp graceful-restart + + This command will enable BGP graceful restart functionality at the global + level. + +.. clicmd:: bgp graceful-restart-disable + + This command will disable both the functionality graceful restart and helper + mode. + + +.. _bgp-GR-peer-mode-cmd: + +BGP GR Peer Mode Commands +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: neighbor A.B.C.D graceful-restart + + This command will enable BGP graceful restart functionality at the peer + level. + +.. clicmd:: neighbor A.B.C.D graceful-restart-helper + + This command will enable BGP graceful restart helper only functionality + at the peer level. + +.. clicmd:: neighbor A.B.C.D graceful-restart-disable + + This command will disable the entire BGP graceful restart functionality + at the peer level. + + +Long-lived Graceful Restart +--------------------------- + +Currently, only restarter mode is supported. This capability is advertised only +if graceful restart capability is negotiated. + +.. clicmd:: bgp long-lived-graceful-restart stale-time (1-16777215) + + Specifies the maximum time to wait before purging long-lived stale routes for + helper routers. + + Default is 0, which means the feature is off by default. Only graceful + restart takes into account. + +.. _bgp-shutdown: + +Administrative Shutdown +----------------------- + +.. clicmd:: bgp shutdown [message MSG...] + + Administrative shutdown of all peers of a bgp instance. Drop all BGP peers, + but preserve their configurations. The peers are notified in accordance with + `RFC 8203 `_ by sending a + ``NOTIFICATION`` message with error code ``Cease`` and subcode + ``Administrative Shutdown`` prior to terminating connections. This global + shutdown is independent of the neighbor shutdown, meaning that individually + shut down peers will not be affected by lifting it. + + An optional shutdown message `MSG` can be specified. + + +.. _bgp-network: + +Networks +-------- + +.. clicmd:: network A.B.C.D/M + + This command adds the announcement network. + + .. code-block:: frr + + router bgp 1 + address-family ipv4 unicast + network 10.0.0.0/8 + exit-address-family + + This configuration example says that network 10.0.0.0/8 will be + announced to all neighbors. Some vendors' routers don't advertise + routes if they aren't present in their IGP routing tables; `bgpd` + doesn't care about IGP routes when announcing its routes. + + +.. clicmd:: bgp network import-check + + This configuration modifies the behavior of the network statement. + If you have this configured the underlying network must exist in + the rib. If you have the [no] form configured then BGP will not + check for the networks existence in the rib. For versions 7.3 and + before frr defaults for datacenter were the network must exist, + traditional did not check for existence. For versions 7.4 and beyond + both traditional and datacenter the network must exist. + +.. _bgp-ipv6-support: + +IPv6 Support +------------ + +.. clicmd:: neighbor A.B.C.D activate + + This configuration modifies whether to enable an address family for a + specific neighbor. By default only the IPv4 unicast address family is + enabled. + + .. code-block:: frr + + router bgp 1 + address-family ipv6 unicast + neighbor 2001:0DB8::1 activate + network 2001:0DB8:5009::/64 + exit-address-family + + This configuration example says that network 2001:0DB8:5009::/64 will be + announced and enables the neighbor 2001:0DB8::1 to receive this announcement. + + By default, only the IPv4 unicast address family is announced to all + neighbors. Using the 'no bgp default ipv4-unicast' configuration overrides + this default so that all address families need to be enabled explicitly. + + .. code-block:: frr + + router bgp 1 + no bgp default ipv4-unicast + neighbor 10.10.10.1 remote-as 2 + neighbor 2001:0DB8::1 remote-as 3 + address-family ipv4 unicast + neighbor 10.10.10.1 activate + network 192.168.1.0/24 + exit-address-family + address-family ipv6 unicast + neighbor 2001:0DB8::1 activate + network 2001:0DB8:5009::/64 + exit-address-family + + This configuration demonstrates how the 'no bgp default ipv4-unicast' might + be used in a setup with two upstreams where each of the upstreams should only + receive either IPv4 or IPv6 announcements. + + Using the ``bgp default ipv6-unicast`` configuration, IPv6 unicast + address family is enabled by default for all new neighbors. + + +.. _bgp-route-aggregation: + +Route Aggregation +----------------- + +.. _bgp-route-aggregation-ipv4: + +Route Aggregation-IPv4 Address Family +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: aggregate-address A.B.C.D/M + + This command specifies an aggregate address. + + In order to advertise an aggregated prefix, a more specific (longer) prefix + MUST exist in the BGP table. For example, if you want to create an + ``aggregate-address 10.0.0.0/24``, you should make sure you have something + like ``10.0.0.5/32`` or ``10.0.0.0/26``, or any other smaller prefix in the + BGP table. The routing information table (RIB) is not enough, you have to + redistribute them into the BGP table. + +.. clicmd:: aggregate-address A.B.C.D/M route-map NAME + + Apply a route-map for an aggregated prefix. + +.. clicmd:: aggregate-address A.B.C.D/M origin + + Override ORIGIN for an aggregated prefix. + +.. clicmd:: aggregate-address A.B.C.D/M as-set + + This command specifies an aggregate address. Resulting routes include + AS set. + +.. clicmd:: aggregate-address A.B.C.D/M summary-only + + This command specifies an aggregate address. + + Longer prefixes advertisements of more specific routes to all neighbors are suppressed. + +.. clicmd:: aggregate-address A.B.C.D/M matching-MED-only + + Configure the aggregated address to only be created when the routes MED + match, otherwise no aggregated route will be created. + +.. clicmd:: aggregate-address A.B.C.D/M suppress-map NAME + + Similar to `summary-only`, but will only suppress more specific routes that + are matched by the selected route-map. + + + This configuration example sets up an ``aggregate-address`` under the ipv4 + address-family. + + .. code-block:: frr + + router bgp 1 + address-family ipv4 unicast + aggregate-address 10.0.0.0/8 + aggregate-address 20.0.0.0/8 as-set + aggregate-address 40.0.0.0/8 summary-only + aggregate-address 50.0.0.0/8 route-map aggr-rmap + exit-address-family + + +.. _bgp-route-aggregation-ipv6: + +Route Aggregation-IPv6 Address Family +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: aggregate-address X:X::X:X/M + + This command specifies an aggregate address. + +.. clicmd:: aggregate-address X:X::X:X/M route-map NAME + + Apply a route-map for an aggregated prefix. + +.. clicmd:: aggregate-address X:X::X:X/M origin + + Override ORIGIN for an aggregated prefix. + +.. clicmd:: aggregate-address X:X::X:X/M as-set + + This command specifies an aggregate address. Resulting routes include + AS set. + +.. clicmd:: aggregate-address X:X::X:X/M summary-only + + This command specifies an aggregate address. + + Longer prefixes advertisements of more specific routes to all neighbors are suppressed + +.. clicmd:: aggregate-address X:X::X:X/M matching-MED-only + + Configure the aggregated address to only be created when the routes MED + match, otherwise no aggregated route will be created. + +.. clicmd:: aggregate-address X:X::X:X/M suppress-map NAME + + Similar to `summary-only`, but will only suppress more specific routes that + are matched by the selected route-map. + + + This configuration example sets up an ``aggregate-address`` under the ipv6 + address-family. + + .. code-block:: frr + + router bgp 1 + address-family ipv6 unicast + aggregate-address 10::0/64 + aggregate-address 20::0/64 as-set + aggregate-address 40::0/64 summary-only + aggregate-address 50::0/64 route-map aggr-rmap + exit-address-family + + +.. _bgp-redistribute-to-bgp: + +Redistribution +-------------- + +Redistribution configuration should be placed under the ``address-family`` +section for the specific AF to redistribute into. Protocol availability for +redistribution is determined by BGP AF; for example, you cannot redistribute +OSPFv3 into ``address-family ipv4 unicast`` as OSPFv3 supports IPv6. + +.. clicmd:: redistribute [metric (0-4294967295)] [route-map WORD] + + Redistribute routes from other protocols into BGP. + + Note - When redistributing a static route, or any better Admin Distance route, + into BGP for which the same path is learned dynamically from another BGP + speaker, if the redistribute path is more preferred from a BGP Best Path + standpoint than the dynamically learned path, then BGP will not export + the best path to Zebra(RIB) for installation into the routing table, + unless BGP receives the path before the static route is created. + +.. clicmd:: redistribute (1-65535)] [metric (0-4294967295)] [route-map WORD] + + Redistribute routes from a routing table ID into BGP. There are two + techniques for redistribution: + + - Standard Table Redistribution ``table (1-65535)``: + - Routes from the specified routing table ID are imported into the + default routing table using the ``ip import-table ID`` command. + - These routes are identified by the protocol type "T[ID]" when + displayed with ``show (ip|ipv6) route``. + - The ``redistribute table ID`` command then integrates these routes + into BGP. + + - Direct Table Redistribution ``table-direct (1-65535)``: + - This method directly imports routes from the designated routing table + ID into BGP, omitting the step of adding to the default routing table. + - This method is especially relevant when the specified table ID is + checked against routing by appending the appropriate `ip rules`. + +Redistribute routes from a routing table number into BGP. + +.. clicmd:: redistribute vnc-direct + + Redistribute VNC direct (not via zebra) routes to BGP process. + +.. clicmd:: bgp update-delay MAX-DELAY + +.. clicmd:: bgp update-delay MAX-DELAY ESTABLISH-WAIT + + This feature is used to enable read-only mode on BGP process restart or when + a BGP process is cleared using 'clear ip bgp \*'. Note that this command is + configured at the global level and applies to all bgp instances/vrfs. It + cannot be used at the same time as the "update-delay" command described below, + which is entered in each bgp instance/vrf desired to delay update installation + and advertisements. The global and per-vrf approaches to defining update-delay + are mutually exclusive. + + When applicable, read-only mode would begin as soon as the first peer reaches + Established status and a timer for max-delay seconds is started. During this + mode BGP doesn't run any best-path or generate any updates to its peers. This + mode continues until: + + 1. All the configured peers, except the shutdown peers, have sent explicit EOR + (End-Of-RIB) or an implicit-EOR. The first keep-alive after BGP has reached + Established is considered an implicit-EOR. + If the establish-wait optional value is given, then BGP will wait for + peers to reach established from the beginning of the update-delay till the + establish-wait period is over, i.e. the minimum set of established peers for + which EOR is expected would be peers established during the establish-wait + window, not necessarily all the configured neighbors. + 2. max-delay period is over. + + On hitting any of the above two conditions, BGP resumes the decision process + and generates updates to its peers. + + Default max-delay is 0, i.e. the feature is off by default. + + +.. clicmd:: update-delay MAX-DELAY + +.. clicmd:: update-delay MAX-DELAY ESTABLISH-WAIT + + This feature is used to enable read-only mode on BGP process restart or when + a BGP process is cleared using 'clear ip bgp \*'. Note that this command is + configured under the specific bgp instance/vrf that the feature is enabled for. + It cannot be used at the same time as the global "bgp update-delay" described + above, which is entered at the global level and applies to all bgp instances. + The global and per-vrf approaches to defining update-delay are mutually + exclusive. + + When applicable, read-only mode would begin as soon as the first peer reaches + Established status and a timer for max-delay seconds is started. During this + mode BGP doesn't run any best-path or generate any updates to its peers. This + mode continues until: + + 1. All the configured peers, except the shutdown peers, have sent explicit EOR + (End-Of-RIB) or an implicit-EOR. The first keep-alive after BGP has reached + Established is considered an implicit-EOR. + If the establish-wait optional value is given, then BGP will wait for + peers to reach established from the beginning of the update-delay till the + establish-wait period is over, i.e. the minimum set of established peers for + which EOR is expected would be peers established during the establish-wait + window, not necessarily all the configured neighbors. + 2. max-delay period is over. + + On hitting any of the above two conditions, BGP resumes the decision process + and generates updates to its peers. + + Default max-delay is 0, i.e. the feature is off by default. + +.. clicmd:: table-map ROUTE-MAP-NAME + + This feature is used to apply a route-map on route updates from BGP to + Zebra. All the applicable match operations are allowed, such as match on + prefix, next-hop, communities, etc. Set operations for this attach-point are + limited to metric and next-hop only. Any operation of this feature does not + affect BGPs internal RIB. + + Supported for ipv4 and ipv6 address families. It works on multi-paths as + well, however, metric setting is based on the best-path only. + +.. _bgp-peers: + +Peers +----- + +.. _bgp-defining-peers: + +Defining Peers +^^^^^^^^^^^^^^ + +.. clicmd:: neighbor PEER remote-as ASN + + Creates a new neighbor whose remote-as is ASN. PEER can be an IPv4 address + or an IPv6 address or an interface to use for the connection. + + .. code-block:: frr + + router bgp 1 + neighbor 10.0.0.1 remote-as 2 + + In this case my router, in AS-1, is trying to peer with AS-2 at 10.0.0.1. + + This command must be the first command used when configuring a neighbor. If + the remote-as is not specified, *bgpd* will complain like this: :: + + can't find neighbor 10.0.0.1 + +.. clicmd:: neighbor PEER remote-as internal + + Create a peer as you would when you specify an ASN, except that if the + peers ASN is different than mine as specified under the :clicmd:`router bgp ASN` + command the connection will be denied. + +.. clicmd:: neighbor PEER remote-as external + + Create a peer as you would when you specify an ASN, except that if the + peers ASN is the same as mine as specified under the :clicmd:`router bgp ASN` + command the connection will be denied. + +.. clicmd:: neighbor PEER oad + + Mark a peer belonging to the One Administrative Domain. + + Some networks span more than one autonomous system and require more + flexibility in the propagation of path attributes.It is worth noting that + these multi-AS networks have a common or single administrative entity. + These networks are said to belong to One Administrative Domain (OAD). + It is desirable to carry IBGP-only attributes across EBGP peerings when + the peers belong to an OAD. + + Enabling this peering sub-type will allow the propagation of non-transitive + attributes across EBGP peerings (e.g. local-preference). Make sure to + turn this peering type on for all peers in the OAD. + + Disabled by default. + +.. clicmd:: bgp listen range peer-group PGNAME + + Accept connections from any peers in the specified prefix. Configuration + from the specified peer-group is used to configure these peers. + +.. note:: + + When using BGP listen ranges, if the associated peer group has TCP MD5 + authentication configured, your kernel must support this on prefixes. On + Linux, this support was added in kernel version 4.14. If your kernel does + not support this feature you will get a warning in the log file, and the + listen range will only accept connections from peers without MD5 configured. + + Additionally, we have observed that when using this option at scale (several + hundred peers) the kernel may hit its option memory limit. In this situation + you will see error messages like: + + ``bgpd: sockopt_tcp_signature: setsockopt(23): Cannot allocate memory`` + + In this case you need to increase the value of the sysctl + ``net.core.optmem_max`` to allow the kernel to allocate the necessary option + memory. + +.. clicmd:: bgp listen limit <1-65535> + + Define the maximum number of peers accepted for one BGP instance. This + limit is set to 100 by default. Increasing this value will really be + possible if more file descriptors are available in the BGP process. This + value is defined by the underlying system (ulimit value), and can be + overridden by `--limit-fds`. More information is available in chapter + (:ref:`common-invocation-options`). + +.. clicmd:: coalesce-time (0-4294967295) + + The time in milliseconds that BGP will delay before deciding what peers + can be put into an update-group together in order to generate a single + update for them. The default time is 1000. + +.. _bgp-configuring-peers: + +Configuring Peers +^^^^^^^^^^^^^^^^^ + +.. clicmd:: neighbor PEER shutdown [message MSG...] [rtt (1-65535) [count (1-255)]] + + Shutdown the peer. We can delete the neighbor's configuration by + ``no neighbor PEER remote-as ASN`` but all configuration of the neighbor + will be deleted. When you want to preserve the configuration, but want to + drop the BGP peer, use this syntax. + + Optionally you can specify a shutdown message `MSG`. + + Also, you can specify optionally ``rtt`` in milliseconds to automatically + shutdown the peer if round-trip-time becomes higher than defined. + + Additional ``count`` parameter is the number of keepalive messages to count + before shutdown the peer if round-trip-time becomes higher than defined. + +.. clicmd:: neighbor PEER disable-connected-check + + Allow peerings between directly connected eBGP peers using loopback + addresses. + +.. clicmd:: neighbor PEER disable-link-bw-encoding-ieee + + By default bandwidth in extended communities is carried encoded as IEEE + floating-point format, which is according to the draft. + + Older versions have the implementation where extended community bandwidth + value is carried encoded as uint32. To enable backward compatibility we + need to disable IEEE floating-point encoding option per-peer. + +.. clicmd:: neighbor PEER extended-link-bandwidth + + By default bandwidth in extended communities is carried encoded as IEEE + floating-point format, and is limited to maximum of 25 Gbps. + + Enabling this parameter, you can use the bandwidth of to 4294967295 Mbps. + + This is disabled by default. + +.. clicmd:: neighbor PEER enforce-first-as + + Discard updates received from the specified (eBGP) peer if the AS_PATH + attribute does not contain the PEER's ASN as the first AS_PATH segment. + + You can enable or disable this enforcement globally too using + ``bgp enforce-first-as`` command. + + Default: enabled. + +.. clicmd:: neighbor PEER extended-optional-parameters + + Force Extended Optional Parameters Length format to be used for OPEN messages. + + By default, it's disabled. If the standard optional parameters length is + higher than one-octet (255), then extended format is enabled automatically. + + For testing purposes, extended format can be enabled with this command. + +.. clicmd:: neighbor PEER ebgp-multihop + + Specifying ``ebgp-multihop`` allows sessions with eBGP neighbors to + establish when they are multiple hops away. When the neighbor is not + directly connected and this knob is not enabled, the session will not + establish. + + If the peer's IP address is not in the RIB and is reachable via the + default route, then you have to enable ``ip nht resolve-via-default``. + +.. clicmd:: neighbor PEER description ... + + Set description of the peer. + +.. clicmd:: neighbor PEER interface IFNAME + + When you connect to a BGP peer over an IPv6 link-local address, you have to + specify the IFNAME of the interface used for the connection. To specify + IPv4 session addresses, see the ``neighbor PEER update-source`` command + below. + +.. clicmd:: neighbor PEER interface remote-as + + Configure an unnumbered BGP peer. ``PEER`` should be an interface name. The + session will be established via IPv6 link locals. Use ``internal`` for iBGP + and ``external`` for eBGP sessions, or specify an ASN if you wish. Finally + this connection type is meant for point to point connections. If you are + on an ethernet segment and attempt to use this with more than one bgp + neighbor, only one neighbor will come up, due to how this feature works. + +.. clicmd:: neighbor PEER next-hop-self [force] + + This command specifies an announced route's nexthop as being equivalent to + the address of the bgp router if it is learned via eBGP. This will also + bypass third-party next-hops in favor of the local bgp address. If the + optional keyword ``force`` is specified the modification is done also for + routes learned via iBGP. + +.. clicmd:: neighbor PEER attribute-unchanged [{as-path|next-hop|med}] + + This command specifies attributes to be left unchanged for advertisements + sent to a peer. Use this to leave the next-hop unchanged in ipv6 + configurations, as the route-map directive to leave the next-hop unchanged + is only available for ipv4. + +.. clicmd:: neighbor PEER update-source + + Specify the IPv4 or IPv6 source address to use for the :abbr:`BGP` session to this + neighbour, may be specified as either an IP address directly or as an + interface name (in which case the *zebra* daemon MUST be running in order + for *bgpd* to be able to retrieve interface state). When there are multiple + addresses on the choosen IFNAME then BGP will use the address that matches + the most number of bits in comparison to the destination peer address. + + .. code-block:: frr + + router bgp 64555 + neighbor foo update-source 192.168.0.1 + neighbor bar update-source lo0 + + +.. clicmd:: neighbor PEER default-originate [route-map WORD] + + *bgpd*'s default is to not announce the default route (0.0.0.0/0) even if it + is in routing table. When you want to announce default routes to the peer, + use this command. + + If ``route-map`` keyword is specified, then the default route will be + originated only if route-map conditions are met. For example, announce + the default route only if ``10.10.10.10/32`` route exists and set an + arbitrary community for a default route. + + .. code-block:: frr + + router bgp 64555 + address-family ipv4 unicast + neighbor 192.168.255.1 default-originate route-map default + ! + ip prefix-list p1 seq 5 permit 10.10.10.10/32 + ! + route-map default permit 10 + match ip address prefix-list p1 + set community 123:123 + ! + +.. clicmd:: neighbor PEER port PORT + +.. clicmd:: neighbor PEER password PASSWORD + + Set a MD5 password to be used with the tcp socket that is being used + to connect to the remote peer. Please note if you are using this + command with a large number of peers on linux you should consider + modifying the `net.core.optmem_max` sysctl to a larger value to + avoid out of memory errors from the linux kernel. + +.. clicmd:: neighbor PEER send-community + + Send the communities to the peer. + + Default: enabled. + +.. clicmd:: neighbor PEER send-community extended rpki + + Send the extended RPKI communities to the peer. RPKI extended community + can be send only to iBGP and eBGP-OAD peers. + + Default: enabled. + +.. clicmd:: neighbor PEER weight WEIGHT + + This command specifies a default `weight` value for the neighbor's routes. + +.. clicmd:: neighbor PEER maximum-prefix NUMBER [force] + + Sets a maximum number of prefixes we can receive from a given peer. If this + number is exceeded, the BGP session will be destroyed. + + In practice, it is generally preferable to use a prefix-list to limit what + prefixes are received from the peer instead of using this knob. Tearing down + the BGP session when a limit is exceeded is far more destructive than merely + rejecting undesired prefixes. The prefix-list method is also much more + granular and offers much smarter matching criterion than number of received + prefixes, making it more suited to implementing policy. + + If ``force`` is set, then ALL prefixes are counted for maximum instead of + accepted only. This is useful for cases where an inbound filter is applied, + but you want maximum-prefix to act on ALL (including filtered) prefixes. This + option requires `soft-reconfiguration inbound` to be enabled for the peer. + +.. clicmd:: neighbor PEER maximum-prefix-out NUMBER + + Sets a maximum number of prefixes we can send to a given peer. + + Since sent prefix count is managed by update-groups, this option + creates a separate update-group for outgoing updates. + +.. clicmd:: neighbor PEER local-as AS-NUMBER [no-prepend] [replace-as] + + Specify an alternate AS for this BGP process when interacting with the + specified peer. With no modifiers, the specified local-as is prepended to + the received AS_PATH when receiving routing updates from the peer, and + prepended to the outgoing AS_PATH (after the process local AS) when + transmitting local routes to the peer. + + If the no-prepend attribute is specified, then the supplied local-as is not + prepended to the received AS_PATH. + + If the replace-as attribute is specified, then only the supplied local-as is + prepended to the AS_PATH when transmitting local-route updates to this peer. + + Note that replace-as can only be specified if no-prepend is. + + This command is only allowed for eBGP peers. + +.. clicmd:: neighbor as-override + + Override AS number of the originating router with the local AS number. + + Usually this configuration is used in PEs (Provider Edge) to replace + the incoming customer AS number so the connected CE (Customer Edge) + can use the same AS number as the other customer sites. This allows + customers of the provider network to use the same AS number across + their sites. + + This command is only allowed for eBGP peers. + +.. clicmd:: neighbor allowas-in [<(1-10)|origin>] + + Accept incoming routes with AS path containing AS number with the same value + as the current system AS. + + This is used when you want to use the same AS number in your sites, but you + can't connect them directly. This is an alternative to + `neighbor WORD as-override`. + + The parameter `(1-10)` configures the amount of accepted occurrences of the + system AS number in AS path. + + The parameter `origin` configures BGP to only accept routes originated with + the same AS number as the system. + + This command is only allowed for eBGP peers. + +.. clicmd:: neighbor addpath-tx-all-paths + + Configure BGP to send all known paths to neighbor in order to preserve multi + path capabilities inside a network. + +.. clicmd:: neighbor addpath-tx-bestpath-per-AS + + Configure BGP to send best known paths to neighbor in order to preserve multi + path capabilities inside a network. + +.. clicmd:: neighbor addpath-tx-best-selected (1-6) + + Configure BGP to calculate and send N best known paths to the neighbor. + +.. clicmd:: neighbor disable-addpath-rx + + Do not accept additional paths from this neighbor. + +.. clicmd:: neighbor addpath-rx-paths-limit (1-65535) + + Limit the maximum number of paths a BGP speaker can receive from a peer, optimizing + the transmission of BGP routes by selectively relaying pertinent routes instead of + the entire set. + + If this command is configured, the sender will only send the number of paths specified + in PATHS-LIMIT capability. + + To exchange this limit, both peers must support the PATHS-LIMIT capability. + +.. clicmd:: neighbor PEER ttl-security hops NUMBER + + This command enforces Generalized TTL Security Mechanism (GTSM), as + specified in RFC 5082. With this command, only neighbors that are the + specified number of hops away will be allowed to become neighbors. This + command is mutually exclusive with *ebgp-multihop*. + +.. clicmd:: neighbor PEER capability extended-nexthop + + Allow bgp to negotiate the extended-nexthop capability with it's peer. + If you are peering over a v6 LL address then this capability is turned + on automatically. If you are peering over a v6 Global Address then + turning on this command will allow BGP to install v4 routes with + v6 nexthops if you do not have v4 configured on interfaces. + +.. clicmd:: neighbor PEER capability dynamic + + Allow BGP to negotiate the Dynamic Capability with its peers. + + Dynamic Capability defines a new BGP message (CAPABILITY) that can be used + to set/unset BGP capabilities without bringing down a BGP session. + + This includes changing graceful-restart (LLGR also) timers, + enabling/disabling add-path, and other supported capabilities. + +.. clicmd:: neighbor PEER capability fqdn + + Allow BGP to negotiate the FQDN Capability with its peers. + + FQDN Capability defines a new BGP message (CAPABILITY) allowing the + use of peer's name and domain name. + + This capability is activated by default. The ``no neighbor PEER capability + fqdn`` avoid negotiation of that capability. This is useful for peers who + are not supporting this capability or supporting BGP Capabilities + Negotiation RFC 2842. + +.. clicmd:: neighbor accept-own + + Enable handling of self-originated VPN routes containing ``accept-own`` community. + + This feature allows you to handle self-originated VPN routes, which a BGP speaker + receives from a route-reflector. A 'self-originated' route is one that was + originally advertised by the speaker itself. As per :rfc:`4271`, a BGP speaker rejects + advertisements that originated the speaker itself. However, the BGP ACCEPT_OWN + mechanism enables a router to accept the prefixes it has advertised, when reflected + from a route-reflector that modifies certain attributes of the prefix. + + A special community called ``accept-own`` is attached to the prefix by the + route-reflector, which is a signal to the receiving router to bypass the ORIGINATOR_ID + and NEXTHOP/MP_REACH_NLRI check. + + Default: disabled. + +.. clicmd:: neighbor path-attribute discard (1-255)... + + Drops specified path attributes from BGP UPDATE messages from the specified neighbor. + + If you do not want specific attributes, you can drop them using this command, and + let the BGP proceed by ignoring those attributes. + +.. clicmd:: neighbor path-attribute treat-as-withdraw (1-255)... + + Received BGP UPDATES that contain specified path attributes are treat-as-withdraw. If + there is an existing prefix in the BGP routing table, it will be removed. + +.. clicmd:: neighbor graceful-shutdown + + Mark all routes from this neighbor as less preferred by setting ``graceful-shutdown`` + community, and local-preference to 0. + +.. clicmd:: bgp fast-external-failover + + This command causes bgp to take down ebgp peers immediately + when a link flaps. `bgp fast-external-failover` is the default + and will not be displayed as part of a `show run`. The no form + of the command turns off this ability. + +.. clicmd:: bgp default-originate timer (0-3600) + + Set the period to rerun the default-originate route-map scanner process. The + default is 5 seconds. With a full routing table, it might be useful to increase + this setting to avoid scanning the whole BGP table aggressively. + +.. clicmd:: bgp default ipv4-unicast + + This command allows the user to specify that the IPv4 Unicast address + family is turned on by default or not. This command defaults to on + and is not displayed. + The `no bgp default ipv4-unicast` form of the command is displayed. + +.. clicmd:: bgp default ipv4-multicast + + This command allows the user to specify that the IPv4 Multicast address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv4-multicast` form of the command is displayed. + +.. clicmd:: bgp default ipv4-vpn + + This command allows the user to specify that the IPv4 MPLS VPN address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv4-vpn` form of the command is displayed. + +.. clicmd:: bgp default ipv4-flowspec + + This command allows the user to specify that the IPv4 Flowspec address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv4-flowspec` form of the command is displayed. + +.. clicmd:: bgp default ipv6-unicast + + This command allows the user to specify that the IPv6 Unicast address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv6-unicast` form of the command is displayed. + +.. clicmd:: bgp default ipv6-multicast + + This command allows the user to specify that the IPv6 Multicast address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv6-multicast` form of the command is displayed. + +.. clicmd:: bgp default ipv6-vpn + + This command allows the user to specify that the IPv6 MPLS VPN address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv6-vpn` form of the command is displayed. + +.. clicmd:: bgp default ipv6-flowspec + + This command allows the user to specify that the IPv6 Flowspec address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default ipv6-flowspec` form of the command is displayed. + +.. clicmd:: bgp default l2vpn-evpn + + This command allows the user to specify that the L2VPN EVPN address + family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default l2vpn-evpn` form of the command is displayed. + +.. clicmd:: bgp default show-hostname + + This command shows the hostname of the peer in certain BGP commands + outputs. It's easier to troubleshoot if you have a number of BGP peers. + +.. clicmd:: bgp default show-nexthop-hostname + + This command shows the hostname of the next-hop in certain BGP commands + outputs. It's easier to troubleshoot if you have a number of BGP peers + and a number of routes to check. + +.. clicmd:: bgp default dynamic-capability + + This command enables dynamic capability advertisement by default + for all the neighbors. + + For ``datacenter`` profile, this is enabled by default. + +.. clicmd:: bgp default software-version-capability + + This command enables software version capability advertisement by default + for all the neighbors. + + For ``datacenter`` profile, this is enabled by default. + + .. code-block:: frr + + IPv4 Unicast Summary: + BGP router identifier 10.0.0.6, local AS number 65001 VRF default vrf-id 0 + BGP table version 12 + RIB entries 23, using 4600 bytes of memory + Peers 3, using 2174 KiB of memory + + Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc + 10.0.0.4 4 65001 20 22 12 0 0 00:00:11 5 12 FRRouting/8.5.1 + 10.0.0.5 4 65001 21 22 12 0 0 00:00:11 5 12 FRRouting/9.0 + 192.168.67.7 4 65001 27 31 12 0 0 00:00:23 2 10 FRRouting/9.1-dev-MyOwnFRRVersion-g3c8c08dcd9 + + Total number of neighbors 3 + +.. clicmd:: neighbor PEER advertisement-interval (0-600) + + Setup the minimum route advertisement interval(mrai) for the + peer in question. This number is between 0 and 600 seconds, + with the default advertisement interval being 0. + +.. clicmd:: neighbor PEER timers (0-65535) (0-65535) + + Set keepalive and hold timers for a neighbor. The first value is keepalive + and the second is hold time. + +.. clicmd:: neighbor PEER timers connect (1-65535) + + Set connect timer for a neighbor. The connect timer controls how long BGP + waits between connection attempts to a neighbor. + +.. clicmd:: neighbor PEER timers delayopen (1-240) + + This command allows the user enable the + `RFC 4271 ` DelayOpenTimer with the + specified interval or disable it with the negating command for the peer. By + default, the DelayOpenTimer is disabled. The timer interval may be set to a + duration of 1 to 240 seconds. + +.. clicmd:: bgp minimum-holdtime (1-65535) + + This command allows user to prevent session establishment with BGP peers + with lower holdtime less than configured minimum holdtime. + When this command is not set, minimum holdtime does not work. + +.. clicmd:: bgp tcp-keepalive (1-65535) (1-65535) (1-30) + + This command allows user to configure TCP keepalive with new BGP peers. + Each parameter respectively stands for TCP keepalive idle timer (seconds), + interval (seconds), and maximum probes. By default, TCP keepalive is + disabled. + +Displaying Information about Peers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: show bgp neighbors WORD bestpath-routes [detail] [json] [wide] + + For the given neighbor, WORD, that is specified list the routes selected + by BGP as having the best path. + + If ``detail`` option is specified, the detailed version of all routes + will be displayed. The same format as ``show [ip] bgp [afi] [safi] PREFIX`` + will be used, but for the whole table of received, advertised or filtered + prefixes. + + If ``json`` option is specified, output is displayed in JSON format. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + +.. _bgp-peer-filtering: + +Peer Filtering +^^^^^^^^^^^^^^ + +.. clicmd:: neighbor PEER distribute-list NAME [in|out] + + This command specifies a distribute-list for the peer. `direct` is + ``in`` or ``out``. + +.. clicmd:: neighbor PEER prefix-list NAME [in|out] + +.. clicmd:: neighbor PEER filter-list NAME [in|out] + +.. clicmd:: neighbor PEER route-map NAME [in|out] + + Apply a route-map on the neighbor. `direct` must be `in` or `out`. + +.. clicmd:: bgp route-reflector allow-outbound-policy + + By default, attribute modification via route-map policy out is not reflected + on reflected routes. This option allows the modifications to be reflected as + well. Once enabled, it affects all reflected routes. + +.. clicmd:: neighbor PEER sender-as-path-loop-detection + + Enable the detection of sender side AS path loops and filter the + bad routes before they are sent. + + This setting is disabled by default. + +.. _bgp-peer-group: + +Peer Groups +^^^^^^^^^^^ + +Peer groups are used to help improve scaling by generating the same +update information to all members of a peer group. Note that this means +that the routes generated by a member of a peer group will be sent back +to that originating peer with the originator identifier attribute set to +indicated the originating peer. All peers not associated with a +specific peer group are treated as belonging to a default peer group, +and will share updates. + +.. clicmd:: neighbor WORD peer-group + + This command defines a new peer group. + +.. clicmd:: neighbor PEER peer-group PGNAME + + This command bind specific peer to peer group WORD. + +.. clicmd:: neighbor PEER solo + + This command is used to indicate that routes advertised by the peer + should not be reflected back to the peer. This command only is only + meaningful when there is a single peer defined in the peer-group. + +.. clicmd:: show [ip] bgp peer-group [json] + + This command displays configured BGP peer-groups. + + .. code-block:: frr + + exit1-debian-9# show bgp peer-group + + BGP peer-group test1, remote AS 65001 + Peer-group type is external + Configured address-families: IPv4 Unicast; IPv6 Unicast; + 1 IPv4 listen range(s) + 192.168.100.0/24 + 2 IPv6 listen range(s) + 2001:db8:1::/64 + 2001:db8:2::/64 + Peer-group members: + 192.168.200.1 Active + 2001:db8::1 Active + + BGP peer-group test2 + Peer-group type is external + Configured address-families: IPv4 Unicast; + + Optional ``json`` parameter is used to display JSON output. + + .. code-block:: frr + + { + "test1":{ + "remoteAs":65001, + "type":"external", + "addressFamiliesConfigured":[ + "IPv4 Unicast", + "IPv6 Unicast" + ], + "dynamicRanges":{ + "IPv4":{ + "count":1, + "ranges":[ + "192.168.100.0\/24" + ] + }, + "IPv6":{ + "count":2, + "ranges":[ + "2001:db8:1::\/64", + "2001:db8:2::\/64" + ] + } + }, + "members":{ + "192.168.200.1":{ + "status":"Active" + }, + "2001:db8::1":{ + "status":"Active" + } + } + }, + "test2":{ + "type":"external", + "addressFamiliesConfigured":[ + "IPv4 Unicast" + ] + } + } + +Capability Negotiation +^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: neighbor PEER strict-capability-match + + Strictly compares remote capabilities and local capabilities. If + capabilities are different, send Unsupported Capability error then reset + connection. + + You may want to disable sending Capability Negotiation OPEN message optional + parameter to the peer when remote peer does not implement Capability + Negotiation. Please use *dont-capability-negotiate* command to disable the + feature. + +.. clicmd:: neighbor PEER dont-capability-negotiate + + Suppress sending Capability Negotiation as OPEN message optional parameter + to the peer. This command only affects the peer is configured other than + IPv4 unicast configuration. + + When remote peer does not have capability negotiation feature, remote peer + will not send any capabilities at all. In that case, bgp configures the peer + with configured capabilities. + + You may prefer locally configured capabilities more than the negotiated + capabilities even though remote peer sends capabilities. If the peer is + configured by *override-capability*, *bgpd* ignores received capabilities + then override negotiated capabilities with configured values. + + Additionally the operator should be reminded that this feature fundamentally + disables the ability to use widely deployed BGP features. BGP unnumbered, + hostname support, AS4, Addpath, Route Refresh, ORF, Dynamic Capabilities, + and graceful restart. + +.. clicmd:: neighbor PEER override-capability + + Override the result of Capability Negotiation with local configuration. + Ignore remote peer's capability value. + +.. clicmd:: neighbor PEER capability software-version + + Send the software version in the BGP OPEN message to the neighbor. This is + very useful in environments with a large amount of peers with different + versions of FRR or any other vendor. + + Disabled by default. + +.. clicmd:: neighbor PEER aigp + + Send and receive AIGP attribute for this neighbor. This is valid only for + eBGP neighbors. + + Disabled by default. iBGP neighbors have this option enabled implicitly. + +.. _bgp-as-path-access-lists: + +AS Path Access Lists +-------------------- + +AS path access list is user defined AS path. + +.. clicmd:: bgp as-path access-list WORD [seq (0-4294967295)] permit|deny LINE + + This command defines a new AS path access list. + +.. clicmd:: show bgp as-path-access-list [json] + + Display all BGP AS Path access lists. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show bgp as-path-access-list WORD [json] + + Display the specified BGP AS Path access list. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. _bgp-bogon-filter-example: + +Bogon ASN filter policy configuration example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: frr + + bgp as-path access-list 99 permit _0_ + bgp as-path access-list 99 permit _23456_ + bgp as-path access-list 99 permit _1310[0-6][0-9]_|_13107[0-1]_ + bgp as-path access-list 99 seq 20 permit ^65 + +.. _bgp-using-as-path-in-route-map: + +Using AS Path in Route Map +-------------------------- + +.. clicmd:: match as-path WORD + + For a given as-path, WORD, match it on the BGP as-path given for the prefix + and if it matches do normal route-map actions. The no form of the command + removes this match from the route-map. + +.. clicmd:: set as-path prepend AS-PATH + + Prepend the given string of AS numbers to the AS_PATH of the BGP path's NLRI. + The no form of this command removes this set operation from the route-map. + +.. clicmd:: set as-path prepend last-as NUM + + Prepend the existing last AS number (the leftmost ASN) to the AS_PATH. + The no form of this command removes this set operation from the route-map. + +.. clicmd:: set as-path replace [] + + Replace a specific AS number to local AS number or a configured AS number. + ``any`` replaces each AS number in the AS-PATH with either the local AS + number or the configured AS number. + +.. clicmd:: set as-path replace as-path-access-list WORD [] + + Replace some AS numbers from the AS_PATH of the BGP path's NLRI. Substituted + AS numbers are conformant with the regex defined in as-path access-list + WORD. Changed AS numbers are replaced either by the local AS number or the + configured AS number. + The no form of this command removes this set operation from the route-map. + +.. clicmd:: set as-path exclude all + + Remove all AS numbers from the AS_PATH of the BGP path's NLRI. The no form of + this command removes this set operation from the route-map. + +.. clicmd:: set as-path exclude as-path-access-list WORD + + Remove some AS numbers from the AS_PATH of the BGP path's NLRI. Removed AS + numbers are conformant with the regex defined in as-path access-list WORD. + The no form of this command removes this set operation from the route-map. + + +.. _bgp-communities-attribute: + +Communities Attribute +--------------------- + +The BGP communities attribute is widely used for implementing policy routing. +Network operators can manipulate BGP communities attribute based on their +network policy. BGP communities attribute is defined in :rfc:`1997` and +:rfc:`1998`. It is an optional transitive attribute, therefore local policy can +travel through different autonomous system. + +The communities attribute is a set of communities values. Each community value +is 4 octet long. The following format is used to define the community value. + +``AS:VAL`` + This format represents 4 octet communities value. ``AS`` is high order 2 + octet in digit format. ``VAL`` is low order 2 octet in digit format. This + format is useful to define AS oriented policy value. For example, + ``7675:80`` can be used when AS 7675 wants to pass local policy value 80 to + neighboring peer. + +``graceful-shutdown`` + ``graceful-shutdown`` represents well-known communities value + ``GRACEFUL_SHUTDOWN`` ``0xFFFF0000`` ``65535:0``. :rfc:`8326` implements + the purpose Graceful BGP Session Shutdown to reduce the amount of + lost traffic when taking BGP sessions down for maintenance. The use + of the community needs to be supported from your peers side to + actually have any effect. + +``accept-own`` + ``accept-own`` represents well-known communities value ``ACCEPT_OWN`` + ``0xFFFF0001`` ``65535:1``. :rfc:`7611` implements a way to signal + to a router to accept routes with a local nexthop address. This + can be the case when doing policing and having traffic having a + nexthop located in another VRF but still local interface to the + router. It is recommended to read the RFC for full details. + +``route-filter-translated-v4`` + ``route-filter-translated-v4`` represents well-known communities value + ``ROUTE_FILTER_TRANSLATED_v4`` ``0xFFFF0002`` ``65535:2``. + +``route-filter-v4`` + ``route-filter-v4`` represents well-known communities value + ``ROUTE_FILTER_v4`` ``0xFFFF0003`` ``65535:3``. + +``route-filter-translated-v6`` + ``route-filter-translated-v6`` represents well-known communities value + ``ROUTE_FILTER_TRANSLATED_v6`` ``0xFFFF0004`` ``65535:4``. + +``route-filter-v6`` + ``route-filter-v6`` represents well-known communities value + ``ROUTE_FILTER_v6`` ``0xFFFF0005`` ``65535:5``. + +``llgr-stale`` + ``llgr-stale`` represents well-known communities value ``LLGR_STALE`` + ``0xFFFF0006`` ``65535:6``. + Assigned and intended only for use with routers supporting the + Long-lived Graceful Restart Capability as described in + [Draft-IETF-uttaro-idr-bgp-persistence]_. + Routers receiving routes with this community may (depending on + implementation) choose allow to reject or modify routes on the + presence or absence of this community. + +``no-llgr`` + ``no-llgr`` represents well-known communities value ``NO_LLGR`` + ``0xFFFF0007`` ``65535:7``. + Assigned and intended only for use with routers supporting the + Long-lived Graceful Restart Capability as described in + [Draft-IETF-uttaro-idr-bgp-persistence]_. + Routers receiving routes with this community may (depending on + implementation) choose allow to reject or modify routes on the + presence or absence of this community. + +``accept-own-nexthop`` + ``accept-own-nexthop`` represents well-known communities value + ``accept-own-nexthop`` ``0xFFFF0008`` ``65535:8``. + [Draft-IETF-agrewal-idr-accept-own-nexthop]_ describes + how to tag and label VPN routes to be able to send traffic between VRFs + via an internal layer 2 domain on the same PE device. Refer to + [Draft-IETF-agrewal-idr-accept-own-nexthop]_ for full details. + +``blackhole`` + ``blackhole`` represents well-known communities value ``BLACKHOLE`` + ``0xFFFF029A`` ``65535:666``. :rfc:`7999` documents sending prefixes to + EBGP peers and upstream for the purpose of blackholing traffic. + Prefixes tagged with the this community should normally not be + re-advertised from neighbors of the originating network. Upon receiving + ``BLACKHOLE`` community from a BGP speaker, ``NO_ADVERTISE`` community + is added automatically. + +``no-export`` + ``no-export`` represents well-known communities value ``NO_EXPORT`` + ``0xFFFFFF01``. All routes carry this value must not be advertised to + outside a BGP confederation boundary. If neighboring BGP peer is part of BGP + confederation, the peer is considered as inside a BGP confederation + boundary, so the route will be announced to the peer. + +``no-advertise`` + ``no-advertise`` represents well-known communities value ``NO_ADVERTISE`` + ``0xFFFFFF02``. All routes carry this value must not be advertise to other + BGP peers. + +``local-AS`` + ``local-AS`` represents well-known communities value ``NO_EXPORT_SUBCONFED`` + ``0xFFFFFF03``. All routes carry this value must not be advertised to + external BGP peers. Even if the neighboring router is part of confederation, + it is considered as external BGP peer, so the route will not be announced to + the peer. + +``no-peer`` + ``no-peer`` represents well-known communities value ``NOPEER`` + ``0xFFFFFF04`` ``65535:65284``. :rfc:`3765` is used to communicate to + another network how the originating network want the prefix propagated. + +When the communities attribute is received duplicate community values in the +attribute are ignored and value is sorted in numerical order. + +.. [Draft-IETF-uttaro-idr-bgp-persistence] +.. [Draft-IETF-agrewal-idr-accept-own-nexthop] + +.. _bgp-community-lists: + +Community Lists +^^^^^^^^^^^^^^^ +Community lists are user defined lists of community attribute values. These +lists can be used for matching or manipulating the communities attribute in +UPDATE messages. + +There are two types of community list: + +standard + This type accepts an explicit value for the attribute. + +expanded + This type accepts a regular expression. Because the regex must be + interpreted on each use expanded community lists are slower than standard + lists. + +.. clicmd:: bgp community-list standard NAME permit|deny COMMUNITY + + This command defines a new standard community list. ``COMMUNITY`` is + communities value. The ``COMMUNITY`` is compiled into community structure. + We can define multiple community list under same name. In that case match + will happen user defined order. Once the community list matches to + communities attribute in BGP updates it return permit or deny by the + community list definition. When there is no matched entry, deny will be + returned. When ``COMMUNITY`` is empty it matches to any routes. + +.. clicmd:: bgp community-list expanded NAME permit|deny COMMUNITY + + This command defines a new expanded community list. ``COMMUNITY`` is a + string expression of communities attribute. ``COMMUNITY`` can be a regular + expression (:ref:`bgp-regular-expressions`) to match the communities + attribute in BGP updates. The expanded community is only used to filter, + not `set` actions. + +.. deprecated:: 5.0 + It is recommended to use the more explicit versions of this command. + +.. clicmd:: bgp community-list NAME permit|deny COMMUNITY + + When the community list type is not specified, the community list type is + automatically detected. If ``COMMUNITY`` can be compiled into communities + attribute, the community list is defined as a standard community list. + Otherwise it is defined as an expanded community list. This feature is left + for backward compatibility. Use of this feature is not recommended. + + Note that all community lists share the same namespace, so it's not + necessary to specify ``standard`` or ``expanded``; these modifiers are + purely aesthetic. + +.. clicmd:: show bgp community-list [NAME detail] + + Displays community list information. When ``NAME`` is specified the + specified community list's information is shown. + + :: + + # show bgp community-list + Named Community standard list CLIST + permit 7675:80 7675:100 no-export + deny internet + Named Community expanded list EXPAND + permit : + + # show bgp community-list CLIST detail + Named Community standard list CLIST + permit 7675:80 7675:100 no-export + deny internet + + +.. _bgp-numbered-community-lists: + +Numbered Community Lists +^^^^^^^^^^^^^^^^^^^^^^^^ + +When number is used for BGP community list name, the number has +special meanings. Community list number in the range from 1 to 99 is +standard community list. Community list number in the range from 100 +to 500 is expanded community list. These community lists are called +as numbered community lists. On the other hand normal community lists +is called as named community lists. + +.. clicmd:: bgp community-list (1-99) permit|deny COMMUNITY + + This command defines a new community list. The argument to (1-99) defines + the list identifier. + +.. clicmd:: bgp community-list (100-500) permit|deny COMMUNITY + + This command defines a new expanded community list. The argument to + (100-500) defines the list identifier. + +.. _bgp-community-alias: + +Community alias +^^^^^^^^^^^^^^^ + +BGP community aliases are useful to quickly identify what communities are set +for a specific prefix in a human-readable format. Especially handy for a huge +amount of communities. Accurately defined aliases can help you faster spot +things on the wire. + +.. clicmd:: bgp community alias NAME ALIAS + + This command creates an alias name for a community that will be used + later in various CLI outputs in a human-readable format. + + .. code-block:: frr + + ~# vtysh -c 'show run' | grep 'bgp community alias' + bgp community alias 65001:14 community-1 + bgp community alias 65001:123:1 lcommunity-1 + + ~# vtysh -c 'show ip bgp 172.16.16.1/32' + BGP routing table entry for 172.16.16.1/32, version 21 + Paths: (2 available, best #2, table default) + Advertised to non peer-group peers: + 65030 + 192.168.0.2 from 192.168.0.2 (172.16.16.1) + Origin incomplete, metric 0, valid, external, best (Neighbor IP) + Community: 65001:12 65001:13 community-1 65001:65534 + Large Community: lcommunity-1 65001:123:2 + Last update: Fri Apr 16 12:51:27 2021 + +.. clicmd:: show bgp [afi] [safi] [all] alias WORD [wide|json] + + Display prefixes with matching BGP community alias. + +.. _bgp-using-communities-in-route-map: + +Using Communities in Route Maps +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In :ref:`route-map` we can match on or set the BGP communities attribute. Using +this feature network operator can implement their network policy based on BGP +communities attribute. + +The following commands can be used in route maps: + +.. clicmd:: match alias WORD + + This command performs match to BGP updates using community alias WORD. When + the one of BGP communities value match to the one of community alias value in + community alias, it is match. + +.. clicmd:: match community WORD exact-match [exact-match] + + This command perform match to BGP updates using community list WORD. When + the one of BGP communities value match to the one of communities value in + community list, it is match. When `exact-match` keyword is specified, match + happen only when BGP updates have completely same communities value + specified in the community list. + +.. clicmd:: set community additive + + This command sets the community value in BGP updates. If the attribute is + already configured, the newly provided value replaces the old one unless the + ``additive`` keyword is specified, in which case the new value is appended + to the existing value. + + If ``none`` is specified as the community value, the communities attribute + is not sent. + + It is not possible to set an expanded community list. + +.. clicmd:: set comm-list WORD delete + + This command remove communities value from BGP communities attribute. The + ``word`` is community list name. When BGP route's communities value matches + to the community list ``word``, the communities value is removed. When all + of communities value is removed eventually, the BGP update's communities + attribute is completely removed. + +.. _bgp-communities-example: + +Example Configuration +^^^^^^^^^^^^^^^^^^^^^ + +The following configuration is exemplary of the most typical usage of BGP +communities attribute. In the example, AS 7675 provides an upstream Internet +connection to AS 100. When the following configuration exists in AS 7675, the +network operator of AS 100 can set local preference in AS 7675 network by +setting BGP communities attribute to the updates. + +.. code-block:: frr + + router bgp 7675 + neighbor 192.168.0.1 remote-as 100 + address-family ipv4 unicast + neighbor 192.168.0.1 route-map RMAP in + exit-address-family + ! + bgp community-list 70 permit 7675:70 + bgp community-list 80 permit 7675:80 + bgp community-list 90 permit 7675:90 + ! + route-map RMAP permit 10 + match community 70 + set local-preference 70 + ! + route-map RMAP permit 20 + match community 80 + set local-preference 80 + ! + route-map RMAP permit 30 + match community 90 + set local-preference 90 + + +The following configuration announces ``10.0.0.0/8`` from AS 100 to AS 7675. +The route has communities value ``7675:80`` so when above configuration exists +in AS 7675, the announced routes' local preference value will be set to 80. + +.. code-block:: frr + + router bgp 100 + network 10.0.0.0/8 + neighbor 192.168.0.2 remote-as 7675 + address-family ipv4 unicast + neighbor 192.168.0.2 route-map RMAP out + exit-address-family + ! + ip prefix-list PLIST permit 10.0.0.0/8 + ! + route-map RMAP permit 10 + match ip address prefix-list PLIST + set community 7675:80 + + +The following configuration is an example of BGP route filtering using +communities attribute. This configuration only permit BGP routes which has BGP +communities value (``0:80`` and ``0:90``) or ``0:100``. The network operator can +set special internal communities value at BGP border router, then limit the +BGP route announcements into the internal network. + +.. code-block:: frr + + router bgp 7675 + neighbor 192.168.0.1 remote-as 100 + address-family ipv4 unicast + neighbor 192.168.0.1 route-map RMAP in + exit-address-family + ! + bgp community-list 1 permit 0:80 0:90 + bgp community-list 1 permit 0:100 + ! + route-map RMAP permit in + match community 1 + + +The following example filters BGP routes which have a community value of +``1:1``. When there is no match community-list returns ``deny``. To avoid +filtering all routes, a ``permit`` line is set at the end of the +community-list. + +.. code-block:: frr + + router bgp 7675 + neighbor 192.168.0.1 remote-as 100 + address-family ipv4 unicast + neighbor 192.168.0.1 route-map RMAP in + exit-address-family + ! + bgp community-list standard FILTER deny 1:1 + bgp community-list standard FILTER permit + ! + route-map RMAP permit 10 + match community FILTER + + +The following configuration is an example of communities value deletion. With +this configuration the community values ``100:1`` and ``100:2`` are removed +from BGP updates. For communities value deletion, only ``permit`` +community-list is used. ``deny`` community-list is ignored. + +.. code-block:: frr + + router bgp 7675 + neighbor 192.168.0.1 remote-as 100 + address-family ipv4 unicast + neighbor 192.168.0.1 route-map RMAP in + exit-address-family + ! + bgp community-list standard DEL permit 100:1 100:2 + ! + route-map RMAP permit 10 + set comm-list DEL delete + + +.. _bgp-extended-communities-attribute: + +Extended Communities Attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +BGP extended communities attribute is introduced with MPLS VPN/BGP technology. +MPLS VPN/BGP expands capability of network infrastructure to provide VPN +functionality. At the same time it requires a new framework for policy routing. +With BGP Extended Communities Attribute we can use Route Target or Site of +Origin for implementing network policy for MPLS VPN/BGP. + +BGP Extended Communities Attribute is similar to BGP Communities Attribute. It +is an optional transitive attribute. BGP Extended Communities Attribute can +carry multiple Extended Community value. Each Extended Community value is +eight octet length. + +BGP Extended Communities Attribute provides an extended range compared with BGP +Communities Attribute. Adding to that there is a type field in each value to +provides community space structure. + +There are two format to define Extended Community value. One is AS based format +the other is IP address based format. + +``AS:VAL`` + This is a format to define AS based Extended Community value. ``AS`` part + is 2 octets Global Administrator subfield in Extended Community value. + ``VAL`` part is 4 octets Local Administrator subfield. ``7675:100`` + represents AS 7675 policy value 100. + +``IP-Address:VAL`` + This is a format to define IP address based Extended Community value. + ``IP-Address`` part is 4 octets Global Administrator subfield. ``VAL`` part + is 2 octets Local Administrator subfield. + +.. _bgp-extended-community-lists: + +Extended Community Lists +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: bgp extcommunity-list standard NAME permit|deny EXTCOMMUNITY + + This command defines a new standard extcommunity-list. `extcommunity` is + extended communities value. The `extcommunity` is compiled into extended + community structure. We can define multiple extcommunity-list under same + name. In that case match will happen user defined order. Once the + extcommunity-list matches to extended communities attribute in BGP updates + it return permit or deny based upon the extcommunity-list definition. When + there is no matched entry, deny will be returned. When `extcommunity` is + empty it matches to any routes. + +.. clicmd:: bgp extcommunity-list expanded NAME permit|deny LINE + + This command defines a new expanded extcommunity-list. `line` is a string + expression of extended communities attribute. `line` can be a regular + expression (:ref:`bgp-regular-expressions`) to match an extended communities + attribute in BGP updates. + + Note that all extended community lists shares a single name space, so it's + not necessary to specify their type when creating or destroying them. + +.. clicmd:: show bgp extcommunity-list [NAME detail] + + This command displays current extcommunity-list information. When `name` is + specified the community list's information is shown. + + +.. _bgp-extended-communities-in-route-map: + +BGP Extended Communities in Route Map +""""""""""""""""""""""""""""""""""""" + +.. clicmd:: match extcommunity WORD + +.. clicmd:: set extcommunity none + + This command resets the extended community value in BGP updates. If the attribute is + already configured or received from the peer, the attribute is discarded and set to + none. This is useful if you need to strip incoming extended communities. + +.. clicmd:: set extcommunity rt EXTCOMMUNITY + + This command sets Route Target value. + +.. clicmd:: set extcommunity nt EXTCOMMUNITY + + This command sets Node Target value. + + If the receiving BGP router supports Node Target Extended Communities, + it will install the route with the community that contains it's own + local BGP Identifier. Otherwise, it's not installed. + +.. clicmd:: set extcommunity soo EXTCOMMUNITY + + This command sets Site of Origin value. + +.. clicmd:: set extcomumnity color EXTCOMMUNITY + + This command sets colors values. + +.. clicmd:: set extcommunity bandwidth <(1-25600) | cumulative | num-multipaths> [non-transitive] + + This command sets the BGP link-bandwidth extended community for the prefix + (best path) for which it is applied. The link-bandwidth can be specified as + an ``explicit value`` (specified in Mbps), or the router can be told to use + the ``cumulative bandwidth`` of all multipaths for the prefix or to compute + it based on the ``number of multipaths``. The link bandwidth extended + community is encoded as ``transitive`` unless the set command explicitly + configures it as ``non-transitive``. + +.. seealso:: :ref:`wecmp_linkbw` + +Note that the extended expanded community is only used for `match` rule, not for +`set` actions. + +.. _bgp-large-communities-attribute: + +Large Communities Attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The BGP Large Communities attribute was introduced in Feb 2017 with +:rfc:`8092`. + +The BGP Large Communities Attribute is similar to the BGP Communities Attribute +except that it has 3 components instead of two and each of which are 4 octets +in length. Large Communities bring additional functionality and convenience +over traditional communities, specifically the fact that the ``GLOBAL`` part +below is now 4 octets wide allowing seamless use in networks using 4-byte ASNs. + +``GLOBAL:LOCAL1:LOCAL2`` + This is the format to define Large Community values. Referencing :rfc:`8195` + the values are commonly referred to as follows: + + - The ``GLOBAL`` part is a 4 octet Global Administrator field, commonly used + as the operators AS number. + - The ``LOCAL1`` part is a 4 octet Local Data Part 1 subfield referred to as + a function. + - The ``LOCAL2`` part is a 4 octet Local Data Part 2 field and referred to + as the parameter subfield. + + As an example, ``65551:1:10`` represents AS 65551 function 1 and parameter + 10. The referenced RFC above gives some guidelines on recommended usage. + +.. _bgp-large-community-lists: + +Large Community Lists +""""""""""""""""""""" + +Two types of large community lists are supported, namely `standard` and +`expanded`. + +.. clicmd:: bgp large-community-list standard NAME permit|deny LARGE-COMMUNITY + + This command defines a new standard large-community-list. `large-community` + is the Large Community value. We can add multiple large communities under + same name. In that case the match will happen in the user defined order. + Once the large-community-list matches the Large Communities attribute in BGP + updates it will return permit or deny based upon the large-community-list + definition. When there is no matched entry, a deny will be returned. When + `large-community` is empty it matches any routes. + +.. clicmd:: bgp large-community-list expanded NAME permit|deny LINE + + This command defines a new expanded large-community-list. Where `line` is a + string matching expression, it will be compared to the entire Large + Communities attribute as a string, with each large-community in order from + lowest to highest. `line` can also be a regular expression which matches + this Large Community attribute. + + Note that all community lists share the same namespace, so it's not + necessary to specify ``standard`` or ``expanded``; these modifiers are + purely aesthetic. + +.. clicmd:: show bgp large-community-list + +.. clicmd:: show bgp large-community-list NAME detail + + This command display current large-community-list information. When + `name` is specified the community list information is shown. + +.. clicmd:: show ip bgp large-community-info + + This command displays the current large communities in use. + +.. _bgp-large-communities-in-route-map: + +Large Communities in Route Map +"""""""""""""""""""""""""""""" + +.. clicmd:: match large-community LINE [exact-match] + + Where `line` can be a simple string to match, or a regular expression. It + is very important to note that this match occurs on the entire + large-community string as a whole, where each large-community is ordered + from lowest to highest. When `exact-match` keyword is specified, match + happen only when BGP updates have completely same large communities value + specified in the large community list. + +.. clicmd:: set large-community LARGE-COMMUNITY + +.. clicmd:: set large-community LARGE-COMMUNITY LARGE-COMMUNITY + +.. clicmd:: set large-community LARGE-COMMUNITY additive + + These commands are used for setting large-community values. The first + command will overwrite any large-communities currently present. + The second specifies two large-communities, which overwrites the current + large-community list. The third will add a large-community value without + overwriting other values. Multiple large-community values can be specified. + +Note that the large expanded community is only used for `match` rule, not for +`set` actions. + +.. _bgp-roles-and-only-to-customers: + +BGP Roles and Only to Customers +------------------------------- + +BGP roles are defined in :rfc:`9234` and provide an easy way to route leaks +prevention, detection and mitigation. + +To enable its mechanics, you must set your local role to reflect your type of +peering relationship with your neighbor. Possible values of ``LOCAL-ROLE`` are: + +- provider +- rs-server +- rs-client +- customer +- peer + +The local Role value is negotiated with the new BGP Role capability with a +built-in check of the corresponding value. In case of mismatch the new OPEN +Roles Mismatch Notification <2, 11> would be sent. + +The correct Role pairs are: + +* Provider - Customer +* Peer - Peer +* RS-Server - RS-Client + +.. code-block:: shell + + ~# vtysh -c 'show bgp neighbor' | grep 'Role' + Local Role: customer + Neighbor Role: provider + Role: advertised and received + +If strict-mode is set BGP session won't become established until BGP neighbor +set local Role on its side. This configuration parameter is defined in +:rfc:`9234` and used to enforce corresponding configuration at your +counter-part side. Default value - disabled. + +Routes that sent from provider, rs-server, or peer local-role (or if received +by customer, rs-clinet, or peer local-role) will be marked with a new +Only to Customer (OTC) attribute. + +Routes with this attribute can only be sent to your neighbor if your +local-role is provider or rs-server. Routes with this attribute can be +received only if your local-role is customer or rs-client. + +In case of peer-peer relationship routes can be received only if +OTC value is equal to your neighbor AS number. + +All these rules with OTC help to detect and mitigate route leaks and +happened automatically if local-role is set. + +.. clicmd:: neighbor PEER local-role LOCAL-ROLE [strict-mode] + + This command set your local-role to ``LOCAL-ROLE``: + . + + This role helps to detect and prevent route leaks. + + If ``strict-mode`` is set, your neighbor must send you Capability with the + value of his role (by setting local-role on his side). Otherwise, a Role + Mismatch Notification will be sent. + +Labeled unicast +--------------- + +*bgpd* supports labeled information, as per :rfc:`3107`. + +.. clicmd:: bgp labeled-unicast + +By default, locally advertised prefixes use the `implicit-null` label to +encode in the outgoing NLRI. The following command uses the `explicit-null` +label value for all the BGP instances. + +.. _bgp-l3vpn-vrfs: + +L3VPN VRFs +---------- + +*bgpd* supports :abbr:`L3VPN (Layer 3 Virtual Private Networks)` :abbr:`VRFs +(Virtual Routing and Forwarding)` for IPv4 :rfc:`4364` and IPv6 :rfc:`4659`. +L3VPN routes, and their associated VRF MPLS labels, can be distributed to VPN +SAFI neighbors in the *default*, i.e., non VRF, BGP instance. VRF MPLS labels +are reached using *core* MPLS labels which are distributed using LDP or BGP +labeled unicast. *bgpd* also supports inter-VRF route leaking. + + +L3VPN over GRE interfaces +^^^^^^^^^^^^^^^^^^^^^^^^^ + +In MPLS-VPN or SRv6-VPN, an L3VPN next-hop entry requires that the path +chosen respectively contains a labelled path or a valid SID IPv6 address. +Otherwise the L3VPN entry will not be installed. It is possible to ignore +that check when the path chosen by the next-hop uses a GRE interface, and +there is a route-map configured at inbound side of ipv4-vpn or ipv6-vpn +address family with following syntax: + +.. clicmd:: set l3vpn next-hop encapsulation gre + +The incoming BGP L3VPN entry is accepted, provided that the next hop of the +L3VPN entry uses a path that takes the GRE tunnel as outgoing interface. The +remote endpoint should be configured just behind the GRE tunnel; remote +device configuration may vary depending whether it acts at edge endpoint or +not: in any case, the expectation is that incoming MPLS traffic received at +this endpoint should be considered as a valid path for L3VPN. + +.. _bgp-vrf-route-leaking: + +VRF Route Leaking +----------------- + +BGP routes may be leaked (i.e. copied) between a unicast VRF RIB and the VPN +SAFI RIB of the default VRF for use in MPLS-based L3VPNs. Unicast routes may +also be leaked between any VRFs (including the unicast RIB of the default BGP +instanced). A shortcut syntax is also available for specifying leaking from one +VRF to another VRF using the default instance's VPN RIB as the intermediary. A +common application of the VRF-VRF feature is to connect a customer's private +routing domain to a provider's VPN service. Leaking is configured from the +point of view of an individual VRF: ``import`` refers to routes leaked from VPN +to a unicast VRF, whereas ``export`` refers to routes leaked from a unicast VRF +to VPN. + +Required parameters +^^^^^^^^^^^^^^^^^^^ + +Routes exported from a unicast VRF to the VPN RIB must be augmented by two +parameters: + +- an :abbr:`RD (Route Distinguisher)` +- an :abbr:`RTLIST (Route-target List)` + +Configuration for these exported routes must, at a minimum, specify these two +parameters. + +Routes imported from the VPN RIB to a unicast VRF are selected according to +their RTLISTs. Routes whose RTLIST contains at least one route-target in +common with the configured import RTLIST are leaked. Configuration for these +imported routes must specify an RTLIST to be matched. + +The RD, which carries no semantic value, is intended to make the route unique +in the VPN RIB among all routes of its prefix that originate from all the +customers and sites that are attached to the provider's VPN service. +Accordingly, each site of each customer is typically assigned an RD that is +unique across the entire provider network. + +The RTLIST is a set of route-target extended community values whose purpose is +to specify route-leaking policy. Typically, a customer is assigned a single +route-target value for import and export to be used at all customer sites. This +configuration specifies a simple topology wherein a customer has a single +routing domain which is shared across all its sites. More complex routing +topologies are possible through use of additional route-targets to augment the +leaking of sets of routes in various ways. + +When using the shortcut syntax for vrf-to-vrf leaking, the RD and RT are +auto-derived. + +General configuration +^^^^^^^^^^^^^^^^^^^^^ + +Configuration of route leaking between a unicast VRF RIB and the VPN SAFI RIB +of the default VRF is accomplished via commands in the context of a VRF +address-family: + +.. clicmd:: rd vpn export AS:NN|IP:nn + + Specifies the route distinguisher to be added to a route exported from the + current unicast VRF to VPN. + +.. clicmd:: rt vpn import|export|both RTLIST... + + Specifies the route-target list to be attached to a route (export) or the + route-target list to match against (import) when exporting/importing between + the current unicast VRF and VPN. The `rt vpn export RTLIST` command is not + mandatory and can be replaced or completed by the `set extcommunity rt` + command in the route-map attached with the `route-map vpn export`. The below + configuration illustrates how the route target is selected based on the + prefixes, and not solely on vrf criterium: + + .. code-block:: frr + + access-list acl1 permit 192.0.2.0/24 + access-list acl2 permit 192.0.3.0/24 + route-map rmap permit 10 + match address acl1 + set extcommunity rt 65001:10 + ! + route-map rmap permit 20 + match address acl1 + set extcommunity rt 65001:20 + ! + router bgp 65001 vrf vrf1 + ! + address-family ipv4 unicast + rd vpn export 65001:1 + import vpn + export vpn + rt vpn import 65001:1 + route-map vpn export rmap + + + The RTLIST is a space-separated list of route-targets, which are BGP + extended community values as described in + :ref:`bgp-extended-communities-attribute`. + +.. clicmd:: label vpn export allocation-mode per-vrf|per-nexthop + + Select how labels are allocated in the given VRF. By default, the `per-vrf` + mode is selected, and one label is used for all prefixes from the VRF. The + `per-nexthop` will use a unique label for all prefixes that are reachable + via the same nexthop. + +.. clicmd:: label vpn export (0..1048575)|auto + + Enables an MPLS label to be attached to a route exported from the current + unicast VRF to VPN. If the value specified is ``auto``, the label value is + automatically assigned from a pool maintained by the Zebra daemon. If Zebra + is not running, or if this command is not configured, automatic label + assignment will not complete, which will block corresponding route export. + +.. clicmd:: nexthop vpn export A.B.C.D|X:X::X:X + + Specifies an optional nexthop value to be assigned to a route exported from + the current unicast VRF to VPN. If left unspecified, the nexthop will be set + to 0.0.0.0 or 0:0::0:0 (self). + +.. clicmd:: route-map vpn import|export MAP + + Specifies an optional route-map to be applied to routes imported or exported + between the current unicast VRF and VPN. + +.. clicmd:: import|export vpn + + Enables import or export of routes between the current unicast VRF and VPN. + +.. clicmd:: import vrf VRFNAME + + Shortcut syntax for specifying automatic leaking from vrf VRFNAME to + the current VRF using the VPN RIB as intermediary. The RD and RT + are auto derived and should not be specified explicitly for either the + source or destination VRF's. + + This shortcut syntax mode is not compatible with the explicit + `import vpn` and `export vpn` statements for the two VRF's involved. + The CLI will disallow attempts to configure incompatible leaking + modes. + +.. clicmd:: bgp retain route-target all + +It is possible to retain or not VPN prefixes that are not imported by local +VRF configuration. This can be done via the following command in the context +of the global VPNv4/VPNv6 family. This command defaults to on and is not +displayed. +The `no bgp retain route-target all` form of the command is displayed. + +.. clicmd:: neighbor soo EXTCOMMUNITY + +Without this command, SoO extended community attribute is configured using +an inbound route map that sets the SoO value during the update process. +With the introduction of the new BGP per-neighbor Site-of-Origin (SoO) feature, +two new commands configured in sub-modes under router configuration mode +simplify the SoO value configuration. + +If we configure SoO per neighbor at PEs, the SoO community is automatically +added for all routes from the CPEs. Routes are validated and prevented from +being sent back to the same CPE (e.g.: multi-site). This is especially needed +when using ``as-override`` or ``allowas-in`` to prevent routing loops. + +.. clicmd:: mpls bgp forwarding + +It is possible to permit BGP install VPN prefixes without transport labels, +by issuing the following command under the interface configuration context. +This configuration will install VPN prefixes originated from an e-bgp session, +and with the next-hop directly connected. + +.. clicmd:: mpls bgp l3vpn-multi-domain-switching + +Redistribute labeled L3VPN routes from AS to neighboring AS (RFC-4364 option +B, or within the same AS when the iBGP peer uses ``next-hop-self`` to rewrite +the next-hop attribute). The labeled L3VPN routes received on this interface are +re-advertised with local labels and an MPLS table swap entry is set to bind +the local label to the received label. + +.. _bgp-l3vpn-srv6: + +L3VPN SRv6 +---------- + +.. clicmd:: segment-routing srv6 + + Use SRv6 backend with BGP L3VPN, and go to its configuration node. + +.. clicmd:: locator NAME + + Specify the SRv6 locator to be used for SRv6 L3VPN. The Locator name must + be set in zebra, but user can set it in any order. + +L3VPN SRv6 SID reachability +--------------------------- + +In the context of IPv4 L3VPN over SRv6 specific usecase, 2001:db8:12::2 +is the peer IPv6 address of r2, and 2001:db8:2:2:: is the SRv6 SID +advertised by router r2 for prefix P. On r1, the SID reachability is +checked in order to install the prefix P. The below output indicates +that the 2001:db8:2:2:: prefix is valid. + + +.. code-block:: frr + + r1# show bgp nexthop detail + Current BGP nexthop cache: + 2001:db8:2:2:: valid [IGP metric 0], #paths 4 + gate 2001:db8:12::2, if eth0 + Last update: Tue Nov 14 10:36:28 2023 + Paths: + 1/1 192.168.2.0/24 VRF vrf10 flags 0x4018 + 1/3 192.168.2.0/24 RD 65002:10 VRF default flags 0x418 + 2001:db8:12::2 valid [IGP metric 0], #paths 0, peer 2001:db8:12::2 + if eth0 + Last update: Tue Nov 14 10:36:26 2023 + Paths: + +General configuration +^^^^^^^^^^^^^^^^^^^^^ + +Configuration of the SRv6 SID used to advertise a L3VPN for both IPv4 and IPv6 +is accomplished via the following command in the context of a VRF: + +.. clicmd:: sid vpn per-vrf export (1..1048575)|auto + + Enables a SRv6 SID to be attached to a route exported from the current + unicast VRF to VPN. A single SID is used for both IPv4 and IPv6 address + families. If you want to set a SID for only IPv4 address family or IPv6 + address family, you need to use the command ``sid vpn export (1..1048575)|auto`` + in the context of an address-family. If the value specified is ``auto``, + the SID value is automatically assigned from a pool maintained by the Zebra + daemon. If Zebra is not running, or if this command is not configured, automatic + SID assignment will not complete, which will block corresponding route export. + +.. _bgp-evpn: + +Ethernet Virtual Network - EVPN +------------------------------- + +Note: When using EVPN features and if you have a large number of hosts, make +sure to adjust the size of the arp neighbor cache to avoid neighbor table +overflow and/or excessive garbage collection. On Linux, the size of the table +and garbage collection frequency can be controlled via the following +sysctl configurations: + +.. code-block:: shell + + net.ipv4.neigh.default.gc_thresh1 + net.ipv4.neigh.default.gc_thresh2 + net.ipv4.neigh.default.gc_thresh3 + + net.ipv6.neigh.default.gc_thresh1 + net.ipv6.neigh.default.gc_thresh2 + net.ipv6.neigh.default.gc_thresh3 + +For more information, see ``man 7 arp``. + +.. _bgp-enabling-evpn: + +Enabling EVPN +^^^^^^^^^^^^^ + +EVPN should be enabled on the BGP instance corresponding to the VRF acting as +the underlay for the VXLAN tunneling. In most circumstances this will be the +default VRF. The command to enable EVPN for a BGP instance is +``advertise-all-vni`` which lives under ``address-family l2vpn evpn``: + +.. code-block:: frr + + router bgp 65001 + ! + address-family l2vpn evpn + advertise-all-vni + +A more comprehensive configuration example can be found in the :ref:`evpn` page. + +.. _bgp-evpn-l3-route-targets: + +EVPN L3 Route-Targets +^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: route-target + +Modify the route-target set for EVPN advertised type-2/type-5 routes. +RTLIST is a list of any of matching +``(A.B.C.D:MN|EF:OPQR|GHJK:MN|*:OPQR|*:MN)`` where ``*`` indicates wildcard +matching for the AS number. It will be set to match any AS number. This is +useful in datacenter deployments with Downstream VNI. ``auto`` is used to +retain the autoconfigure that is default behavior for L3 RTs. + +.. _bgp-evpn-advertise-pip: + +EVPN advertise-PIP +^^^^^^^^^^^^^^^^^^ + +In a EVPN symmetric routing MLAG deployment, all EVPN routes advertised +with anycast-IP as next-hop IP and anycast MAC as the Router MAC (RMAC - in +BGP EVPN Extended-Community). +EVPN picks up the next-hop IP from the VxLAN interface's local tunnel IP and +the RMAC is obtained from the MAC of the L3VNI's SVI interface. +Note: Next-hop IP is used for EVPN routes whether symmetric routing is +deployed or not but the RMAC is only relevant for symmetric routing scenario. + +Current behavior is not ideal for Prefix (type-5) and self (type-2) +routes. This is because the traffic from remote VTEPs routed sub optimally +if they land on the system where the route does not belong. + +The advertise-pip feature advertises Prefix (type-5) and self (type-2) +routes with system's individual (primary) IP as the next-hop and individual +(system) MAC as Router-MAC (RMAC), while leaving the behavior unchanged for +other EVPN routes. + +To support this feature there needs to have ability to co-exist a +(system-MAC, system-IP) pair with a (anycast-MAC, anycast-IP) pair with the +ability to terminate VxLAN-encapsulated packets received for either pair on +the same L3VNI (i.e associated VLAN). This capability is needed per tenant +VRF instance. + +To derive the system-MAC and the anycast MAC, there must be a +separate/additional MAC-VLAN interface corresponding to L3VNI’s SVI. +The SVI interface’s MAC address can be interpreted as system-MAC +and MAC-VLAN interface's MAC as anycast MAC. + +To derive system-IP and anycast-IP, the default BGP instance's router-id is used +as system-IP and the VxLAN interface’s local tunnel IP as the anycast-IP. + +User has an option to configure the system-IP and/or system-MAC value if the +auto derived value is not preferred. + +Note: By default, advertise-pip feature is enabled and user has an option to +disable the feature via configuration CLI. Once the feature is disabled under +bgp vrf instance or MAC-VLAN interface is not configured, all the routes follow +the same behavior of using same next-hop and RMAC values. + +.. clicmd:: advertise-pip [ip [mac ]] + +Enables or disables advertise-pip feature, specify system-IP and/or system-MAC +parameters. + +EVPN advertise-svi-ip +^^^^^^^^^^^^^^^^^^^^^ +Typically, the SVI IP address is reused on VTEPs across multiple racks. However, +if you have unique SVI IP addresses that you want to be reachable you can use the +advertise-svi-ip option. This option advertises the SVI IP/MAC address as a type-2 +route and eliminates the need for any flooding over VXLAN to reach the IP from a +remote VTEP. + +.. clicmd:: advertise-svi-ip + +Note that you should not enable both the advertise-svi-ip and the advertise-default-gw +at the same time. + +.. _bgp-evpn-overlay-index-gateway-ip: + +EVPN Overlay Index Gateway IP +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +RFC https://datatracker.ietf.org/doc/html/rfc9136 explains the use of overlay +indexes for recursive route resolution for EVPN type-5 route. + +We support gateway IP overlay index. +A gateway IP, advertised with EVPN prefix route, is used to find an EVPN MAC/IP +route with its IP field same as the gateway IP. This MAC/IP entry provides the +nexthop VTEP and the tunnel information required for the VxLAN encapsulation. + +Functionality: + +:: + + . +--------+ BGP +--------+ BGP +--------+ +--------+ + SN1 | | IPv4 | | EVPN | | | | + ======+ Host1 +------+ PE1 +------+ PE2 +------+ Host2 + + | | | | | | | | + +--------+ +--------+ +--------+ +--------+ + +Consider above topology where prefix SN1 is connected behind host1. Host1 +advertises SN1 to PE1 over BGP IPv4 session. PE1 advertises SN1 to PE2 using +EVPN type-5 route with host1 IP as the gateway IP. PE1 also advertises +Host1 MAC/IP as type-2 route which is used to resolve host1 gateway IP. + +PE2 receives this type-5 route and imports it into the vrf based on route +targets. BGP prefix imported into the vrf uses gateway IP as its BGP nexthop. +This route is installed into zebra if following conditions are satisfied: + +1. Gateway IP nexthop is L3 reachable. +2. PE2 has received EVPN type-2 route with IP field set to gateway IP. + +Topology requirements: + +1. This feature is supported for asymmetric routing model only. While + sending packets to SN1, ingress PE (PE2) performs routing and + egress PE (PE1) performs only bridging. +2. This feature supports only traditional(non vlan-aware) bridge model. Bridge + interface associated with L2VNI is an L3 interface. i.e., this interface is + configured with an address in the L2VNI subnet. Note that the gateway IP + should also have an address in the same subnet. +3. As this feature works in asymmetric routing model, all L2VNIs and corresponding + VxLAN and bridge interfaces should be present at all the PEs. +4. L3VNI configuration is required to generate and import EVPN type-5 routes. + L3VNI VxLAN and bridge interfaces also should be present. + +A PE can use one of the following two mechanisms to advertise an EVPN type-5 +route with gateway IP. + +1. CLI to add gateway IP while generating EVPN type-5 route from a BGP IPv4/IPv6 +prefix: + +.. clicmd:: advertise unicast [gateway-ip] + +When this CLI is configured for a BGP vrf under L2VPN EVPN address family, EVPN +type-5 routes are generated for BGP prefixes in the vrf. Nexthop of the BGP +prefix becomes the gateway IP of the corresponding type-5 route. + +If the above command is configured without the "gateway-ip" keyword, type-5 +routes are generated without overlay index. + +2. Add gateway IP to EVPN type-5 route using a route-map: + +.. clicmd:: set evpn gateway-ip + +When route-map with above set clause is applied as outbound policy in BGP, it +will set the gateway-ip in EVPN type-5 NLRI. + +Example configuration: + +.. code-block:: frr + + router bgp 100 + neighbor 192.168.0.1 remote-as 101 + ! + address-family ipv4 l2vpn evpn + neighbor 192.168.0.1 route-map RMAP out + exit-address-family + ! + route-map RMAP permit 10 + set evpn gateway-ip 10.0.0.1 + set evpn gateway-ip 10::1 + +A PE that receives a type-5 route with gateway IP overlay index should have +"enable-resolve-overlay-index" configuration enabled to recursively resolve the +overlay index nexthop and install the prefix into zebra. + +.. clicmd:: enable-resolve-overlay-index + +Example configuration: + +.. code-block:: frr + + router bgp 65001 + bgp router-id 192.168.100.1 + no bgp ebgp-requires-policy + neighbor 10.0.1.2 remote-as 65002 + ! + address-family l2vpn evpn + neighbor 10.0.1.2 activate + advertise-all-vni + enable-resolve-overlay-index + exit-address-family + ! + +.. _bgp-evpn-mac-vrf-site-of-origin: + +EVPN MAC-VRF Site-of-Origin +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In some EVPN deployments it is useful to associate a logical VTEP's Layer 2 +domain (MAC-VRF) with a Site-of-Origin "site" identifier. This provides a +BGP topology-independent means of marking and import-filtering EVPN routes +originated from a particular L2 domain. One situation where this is valuable +is when deploying EVPN using anycast VTEPs, i.e. Active/Active MLAG, as it +can be used to avoid ownership conflicts between the two control planes +(EVPN vs MLAG). + +Example Use Case (MLAG Anycast VTEPs): + +During normal operation, an MLAG VTEP will advertise EVPN routes for attached +hosts using a shared anycast IP as the BGP next-hop. It is expected for its +MLAG peer to drop routes originated by the MLAG Peer since they have a Martian +(self) next-hop. However, prior to the anycast IP being assigned to the local +system, the anycast BGP next-hop will not be considered a Martian (self) IP. +This results in a timing window where hosts that are locally attached to the +MLAG pair's L2 domain can be learned both as "local" (via MLAG) or "remote" +(via an EVPN route with a non-local next-hop). This can trigger erroneous MAC +Mobility events, as the host "moves" between one MLAG Peer's Unique VTEP-IP +and the shared anycast VTEP-IP, which causes unnecessary control plane and +data plane events to propagate throughout the EVPN domain. +By associating the MAC-VRF of both MLAG VTEPs with the same site identifier, +EVPN routes originated by one MLAG VTEP will ignored by its MLAG peer, ensuring +that only the MLAG control plane attempts to take ownership of local hosts. + +The EVPN MAC-VRF Site-of-Origin feature works by influencing two behaviors: + +1. All EVPN routes originating from the local MAC-VRF will have a + Site-of-Origin extended community added to the route, matching the + configured value. +2. EVPN routes will be subjected to a "self SoO" check during MAC-VRF + or IP-VRF import processing. If the EVPN route is found to carry a + Site-of-Origin extended community whose value matches the locally + configured MAC-VRF Site-of-Origin, the route will be maintained in + the global EVPN RIB ("show bgp l2vpn evpn route") but will not be + imported into the corresponding MAC-VRF ("show bgp vni") or IP-VRF + ("show bgp [vrf ] [ipv4 | ipv6 [unicast]]"). + +The import filtering described in item (2) is constrained just to Type-2 +(MAC-IP) and Type-3 (IMET) EVPN routes. + +The EVPN MAC-VRF Site-of-Origin can be configured using a single CLI command +under ``address-family l2vpn evpn`` of the EVPN underlay BGP instance. + +.. clicmd:: mac-vrf soo + +Example configuration: + +.. code-block:: frr + + router bgp 100 + neighbor 192.168.0.1 remote-as 101 + ! + address-family ipv4 l2vpn evpn + neighbor 192.168.0.1 activate + advertise-all-vni + mac-vrf soo 100.64.0.0:777 + exit-address-family + +This configuration ensures: + +1. EVPN routes originated from a local L2VNI will have a Site-of-Origin + extended community with the value ``100.64.0.0:777`` +2. Received EVPN routes carrying a Site-of-Origin extended community with the + value ``100.64.0.0:777`` will not be imported into a local MAC-VRF (L2VNI) + or IP-VRF (L3VNI). + +.. _bgp-evpn-mh: + +EVPN Multihoming +^^^^^^^^^^^^^^^^ + +All-Active Multihoming is used for redundancy and load sharing. Servers +are attached to two or more PEs and the links are bonded (link-aggregation). +This group of server links is referred to as an Ethernet Segment. + +Ethernet Segments +""""""""""""""""" +An Ethernet Segment can be configured by specifying a system-MAC and a +local discriminator or a complete ESINAME against the bond interface on the +PE (via zebra) - + +.. clicmd:: evpn mh es-id <(1-16777215)|ESINAME> + +.. clicmd:: evpn mh es-sys-mac X:X:X:X:X:X + +The sys-mac and local discriminator are used for generating a 10-byte, +Type-3 Ethernet Segment ID. ESINAME is a 10-byte, Type-0 Ethernet Segment ID - +"00:AA:BB:CC:DD:EE:FF:GG:HH:II". + +Type-1 (EAD-per-ES and EAD-per-EVI) routes are used to advertise the locally +attached ESs and to learn off remote ESs in the network. Local Type-2/MAC-IP +routes are also advertised with a destination ESI allowing for MAC-IP syncing +between Ethernet Segment peers. +Reference: RFC 7432, RFC 8365 + +EVPN-MH is intended as a replacement for MLAG or Anycast VTEPs. In +multihoming each PE has an unique VTEP address which requires the introduction +of a new dataplane construct, MAC-ECMP. Here a MAC/FDB entry can point to a +list of remote PEs/VTEPs. + +BUM handling +"""""""""""" +Type-4 (ESR) routes are used for Designated Forwarder (DF) election. DFs +forward BUM traffic received via the overlay network. This implementation +uses a preference based DF election specified by draft-ietf-bess-evpn-pref-df. +The DF preference is configurable per-ES (via zebra) - + +.. clicmd:: evpn mh es-df-pref (1-16777215) + +BUM traffic is rxed via the overlay by all PEs attached to a server but +only the DF can forward the de-capsulated traffic to the access port. To +accommodate that non-DF filters are installed in the dataplane to drop +the traffic. + +Similarly traffic received from ES peers via the overlay cannot be forwarded +to the server. This is split-horizon-filtering with local bias. + +Knobs for interop +""""""""""""""""" +Some vendors do not send EAD-per-EVI routes. To interop with them we +need to relax the dependency on EAD-per-EVI routes and activate a remote +ES-PE based on just the EAD-per-ES route. + +Note that by default we advertise and expect EAD-per-EVI routes. + +.. clicmd:: disable-ead-evi-rx + +.. clicmd:: disable-ead-evi-tx + +Fast failover +""""""""""""" +As the primary purpose of EVPN-MH is redundancy keeping the failover efficient +is a recurring theme in the implementation. Following sub-features have +been introduced for the express purpose of efficient ES failovers. + +- Layer-2 Nexthop Groups and MAC-ECMP via L2NHG. + +- Host routes (for symmetric IRB) via L3NHG. + On dataplanes that support layer3 nexthop groups the feature can be turned + on via the following BGP config - + +.. clicmd:: use-es-l3nhg + +- Local ES (MAC/Neigh) failover via ES-redirect. + On dataplanes that do not have support for ES-redirect the feature can be + turned off via the following zebra config - + +.. clicmd:: evpn mh redirect-off + +Uplink/Core tracking +"""""""""""""""""""" +When all the underlay links go down the PE no longer has access to the VxLAN ++overlay. To prevent blackholing of traffic the server/ES links are +protodowned on the PE. A link can be setup for uplink tracking via the +following zebra configuration - + +.. clicmd:: evpn mh uplink + +Proxy advertisements +"""""""""""""""""""" +To handle hitless upgrades support for proxy advertisement has been added +as specified by draft-rbickhart-evpn-ip-mac-proxy-adv. This allows a PE +(say PE1) to proxy advertise a MAC-IP rxed from an ES peer (say PE2). When +the ES peer (PE2) goes down PE1 continues to advertise hosts learnt from PE2 +for a holdtime during which it attempts to establish local reachability of +the host. This holdtime is configurable via the following zebra commands - + +.. clicmd:: evpn mh neigh-holdtime (0-86400) + +.. clicmd:: evpn mh mac-holdtime (0-86400) + +Startup delay +""""""""""""" +When a switch is rebooted we wait for a brief period to allow the underlay +and EVPN network to converge before enabling the ESs. For this duration the +ES bonds are held protodown. The startup delay is configurable via the +following zebra command - + +.. clicmd:: evpn mh startup-delay (0-3600) + +EAD-per-ES fragmentation +"""""""""""""""""""""""" +The EAD-per-ES route carries the EVI route targets for all the broadcast +domains associated with the ES. Depending on the EVI scale the EAD-per-ES +route maybe fragmented. + +The number of EVIs per-EAD route can be configured via the following +BGP command - + +.. clicmd:: ead-es-frag evi-limit (1-1000) + +Sample Configuration +^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: frr + + ! + router bgp 5556 + ! + address-family l2vpn evpn + ead-es-frag evi-limit 200 + exit-address-family + ! + ! + +EAD-per-ES route-target +""""""""""""""""""""""" +The EAD-per-ES route by default carries all the EVI route targets. Depending +on EVI scale that can result in route fragmentation. In some cases it maybe +necessary to avoid this fragmentation and that can be done via the following +workaround - +1. Configure a single supplementary BD per-tenant VRF. This SBD needs to +be provisioned on all EVPN PEs associated with the tenant-VRF. +2. Config the SBD's RT as the EAD-per-ES route's export RT. + +Sample Configuration +^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: frr + + ! + router bgp 5556 + ! + address-family l2vpn evpn + ead-es-route-target export 5556:1001 + ead-es-route-target export 5556:1004 + ead-es-route-target export 5556:1008 + exit-address-family + ! + +Support with VRF network namespace backend +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +It is possible to separate overlay networks contained in VXLAN interfaces from +underlay networks by using VRFs. VRF-lite and VRF-netns backends can be used for +that. In the latter case, it is necessary to set both bridge and vxlan interface +in the same network namespace, as below example illustrates: + +.. code-block:: shell + + # linux shell + ip netns add vrf1 + ip link add name vxlan101 type vxlan id 101 dstport 4789 dev eth0 local 10.1.1.1 + ip link set dev vxlan101 netns vrf1 + ip netns exec vrf1 ip link set dev lo up + ip netns exec vrf1 brctl addbr bridge101 + ip netns exec vrf1 brctl addif bridge101 vxlan101 + +This makes it possible to separate not only layer 3 networks like VRF-lite networks. +Also, VRF netns based make possible to separate layer 2 networks on separate VRF +instances. + +.. _bgp-conditional-advertisement: + +BGP Conditional Advertisement +----------------------------- +The BGP conditional advertisement feature uses the ``non-exist-map`` or the +``exist-map`` and the ``advertise-map`` keywords of the neighbor advertise-map +command in order to track routes by the route prefix. + +``non-exist-map`` + 1. If a route prefix is not present in the output of non-exist-map command, + then advertise the route specified by the advertise-map command. + + 2. If a route prefix is present in the output of non-exist-map command, + then do not advertise the route specified by the addvertise-map command. + +``exist-map`` + 1. If a route prefix is present in the output of exist-map command, + then advertise the route specified by the advertise-map command. + + 2. If a route prefix is not present in the output of exist-map command, + then do not advertise the route specified by the advertise-map command. + +This feature is useful when some prefixes are advertised to one of its peers +only if the information from the other peer is not present (due to failure in +peering session or partial reachability etc). + +The conditional BGP announcements are sent in addition to the normal +announcements that a BGP router sends to its peer. + +The conditional advertisement process is triggered by the BGP scanner process, +which runs every 60 by default. This means that the maximum time for the +conditional advertisement to take effect is the value of the process timer. + +As an optimization, while the process always runs on each timer expiry, it +determines whether or not the conditional advertisement policy or the routing +table has changed; if neither have changed, no processing is necessary and the +scanner exits early. + +.. clicmd:: neighbor A.B.C.D advertise-map NAME [exist-map|non-exist-map] NAME + + This command enables BGP scanner process to monitor routes specified by + exist-map or non-exist-map command in BGP table and conditionally advertises + the routes specified by advertise-map command. + +.. clicmd:: bgp conditional-advertisement timer (5-240) + + Set the period to rerun the conditional advertisement scanner process. The + default is 60 seconds. + +Sample Configuration +^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: frr + + interface enp0s9 + ip address 10.10.10.2/24 + ! + interface enp0s10 + ip address 10.10.20.2/24 + ! + interface lo + ip address 203.0.113.1/32 + ! + router bgp 2 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 10.10.10.1 remote-as 1 + neighbor 10.10.20.3 remote-as 3 + ! + address-family ipv4 unicast + neighbor 10.10.10.1 soft-reconfiguration inbound + neighbor 10.10.20.3 soft-reconfiguration inbound + neighbor 10.10.20.3 advertise-map ADV-MAP non-exist-map EXIST-MAP + exit-address-family + ! + ip prefix-list DEFAULT seq 5 permit 192.0.2.5/32 + ip prefix-list DEFAULT seq 10 permit 192.0.2.1/32 + ip prefix-list EXIST seq 5 permit 10.10.10.10/32 + ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0 + ip prefix-list IP1 seq 5 permit 10.139.224.0/20 + ! + bgp community-list standard DC-ROUTES seq 5 permit 64952:3008 + bgp community-list standard DC-ROUTES seq 10 permit 64671:501 + bgp community-list standard DC-ROUTES seq 15 permit 64950:3009 + bgp community-list standard DEFAULT-ROUTE seq 5 permit 65013:200 + ! + route-map ADV-MAP permit 10 + match ip address prefix-list IP1 + ! + route-map ADV-MAP permit 20 + match community DC-ROUTES + ! + route-map EXIST-MAP permit 10 + match community DEFAULT-ROUTE + match ip address prefix-list DEFAULT-ROUTE + ! + +Sample Output +^^^^^^^^^^^^^ + +When default route is present in R2'2 BGP table, 10.139.224.0/20 and 192.0.2.1/32 are not advertised to R3. + +.. code-block:: frr + + Router2# show ip bgp + BGP table version is 20, local router ID is 203.0.113.1, vrf id 0 + Default local pref 100, local AS 2 + Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed + Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self + Origin codes: i - IGP, e - EGP, ? - incomplete + RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 0.0.0.0/0 10.10.10.1 0 0 1 i + *> 10.139.224.0/20 10.10.10.1 0 0 1 ? + *> 192.0.2.1/32 10.10.10.1 0 0 1 i + *> 192.0.2.5/32 10.10.10.1 0 0 1 i + + Displayed 4 routes and 4 total paths + Router2# show ip bgp neighbors 10.10.20.3 + + !--- Output suppressed. + + For address family: IPv4 Unicast + Update group 7, subgroup 7 + Packet Queue length 0 + Inbound soft reconfiguration allowed + Community attribute sent to this neighbor(all) + Condition NON_EXIST, Condition-map *EXIST-MAP, Advertise-map *ADV-MAP, status: Withdraw + 0 accepted prefixes + + !--- Output suppressed. + + Router2# show ip bgp neighbors 10.10.20.3 advertised-routes + BGP table version is 20, local router ID is 203.0.113.1, vrf id 0 + Default local pref 100, local AS 2 + Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed + Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self + Origin codes: i - IGP, e - EGP, ? - incomplete + RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 0.0.0.0/0 0.0.0.0 0 1 i + *> 192.0.2.5/32 0.0.0.0 0 1 i + + Total number of prefixes 2 + +When default route is not present in R2'2 BGP table, 10.139.224.0/20 and 192.0.2.1/32 are advertised to R3. + +.. code-block:: frr + + Router2# show ip bgp + BGP table version is 21, local router ID is 203.0.113.1, vrf id 0 + Default local pref 100, local AS 2 + Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed + Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self + Origin codes: i - IGP, e - EGP, ? - incomplete + RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 10.139.224.0/20 10.10.10.1 0 0 1 ? + *> 192.0.2.1/32 10.10.10.1 0 0 1 i + *> 192.0.2.5/32 10.10.10.1 0 0 1 i + + Displayed 3 routes and 3 total paths + + Router2# show ip bgp neighbors 10.10.20.3 + + !--- Output suppressed. + + For address family: IPv4 Unicast + Update group 7, subgroup 7 + Packet Queue length 0 + Inbound soft reconfiguration allowed + Community attribute sent to this neighbor(all) + Condition NON_EXIST, Condition-map *EXIST-MAP, Advertise-map *ADV-MAP, status: Advertise + 0 accepted prefixes + + !--- Output suppressed. + + Router2# show ip bgp neighbors 10.10.20.3 advertised-routes + BGP table version is 21, local router ID is 203.0.113.1, vrf id 0 + Default local pref 100, local AS 2 + Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed + Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self + Origin codes: i - IGP, e - EGP, ? - incomplete + RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 10.139.224.0/20 0.0.0.0 0 1 ? + *> 192.0.2.1/32 0.0.0.0 0 1 i + *> 192.0.2.5/32 0.0.0.0 0 1 i + + Total number of prefixes 3 + Router2# + +.. _bgp-debugging: + +Debugging +--------- + +.. clicmd:: show debug + + Show all enabled debugs. + +.. clicmd:: show bgp listeners + + Display Listen sockets and the vrf that created them. Useful for debugging of when + listen is not working and this is considered a developer debug statement. + +.. clicmd:: debug bgp allow-martian + + Enable or disable BGP accepting martian nexthops from a peer. Please note + this is not an actual debug command and this command is also being deprecated + and will be removed soon. The new command is :clicmd:`bgp allow-martian-nexthop` + +.. clicmd:: debug bgp bfd + + Enable or disable debugging for BFD events. This will show BFD integration + library messages and BGP BFD integration messages that are mostly state + transitions and validation problems. + +.. clicmd:: debug bgp conditional-advertisement + + Enable or disable debugging of BGP conditional advertisement. + +.. clicmd:: debug bgp neighbor-events + + Enable or disable debugging for neighbor events. This provides general + information on BGP events such as peer connection / disconnection, session + establishment / teardown, and capability negotiation. + +.. clicmd:: debug bgp updates [detail] + + Enable or disable debugging for BGP updates. This provides information on + BGP UPDATE messages transmitted and received between local and remote + instances. + + If ``detail`` is specified, the output will include the full BGP UPDATE with + detailed information such as attribute length, withdraw length, and more. + +.. clicmd:: debug bgp updates [ [prefix-list WORD]] + + Enable or disable debugging for BGP updates. Optionally, you can specify + a prefix-list to filter the updates for an arbitrary neighbor. + +.. clicmd:: debug bgp keepalives + + Enable or disable debugging for BGP keepalives. This provides information on + BGP KEEPALIVE messages transmitted and received between local and remote + instances. + +.. clicmd:: debug bgp bestpath + + Enable or disable debugging for bestpath selection on the specified prefix. + +.. clicmd:: debug bgp nht + + Enable or disable debugging of BGP nexthop tracking. + +.. clicmd:: debug bgp update-groups + + Enable or disable debugging of dynamic update groups. This provides general + information on group creation, deletion, join and prune events. + +.. clicmd:: debug bgp zebra + + Enable or disable debugging of communications between *bgpd* and *zebra*. + +Dumping Messages and Routing Tables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: dump bgp all PATH [INTERVAL] + +.. clicmd:: dump bgp all-et PATH [INTERVAL] + + + Dump all BGP packet and events to `path` file. + If `interval` is set, a new file will be created for echo `interval` of + seconds. The path `path` can be set with date and time formatting + (strftime). The type ‘all-et’ enables support for Extended Timestamp Header + (:ref:`packet-binary-dump-format`). + +.. clicmd:: dump bgp updates PATH [INTERVAL] + +.. clicmd:: dump bgp updates-et PATH [INTERVAL] + + + Dump only BGP updates messages to `path` file. + If `interval` is set, a new file will be created for echo `interval` of + seconds. The path `path` can be set with date and time formatting + (strftime). The type ‘updates-et’ enables support for Extended Timestamp + Header (:ref:`packet-binary-dump-format`). + +.. clicmd:: dump bgp routes-mrt PATH + +.. clicmd:: dump bgp routes-mrt PATH INTERVAL + + + Dump whole BGP routing table to `path`. This is heavy process. The path + `path` can be set with date and time formatting (strftime). If `interval` is + set, a new file will be created for echo `interval` of seconds. + + Note: the interval variable can also be set using hours and minutes: 04h20m00. + + +.. _bgp-other-commands: + +Other BGP Commands +------------------ + +The following are available in the top level *enable* mode: + +.. clicmd:: clear bgp \* + + Clear all peers. + +.. clicmd:: clear bgp ipv4|ipv6 ASNUM + + Clear peers with the AS number in plain or dotted format. + +.. clicmd:: clear bgp ipv4|ipv6 \* + + Clear all peers with this address-family activated. + +.. clicmd:: clear bgp ipv4|ipv6 unicast \* + + Clear all peers with this address-family and sub-address-family activated. + +.. clicmd:: clear bgp ipv4|ipv6 PEER + + Clear peers with address of X.X.X.X and this address-family activated. + +.. clicmd:: clear bgp ipv4|ipv6 unicast PEER + + Clear peer with address of X.X.X.X and this address-family and sub-address-family activated. + +.. clicmd:: clear bgp ipv4|ipv6 PEER soft|in|out + + Clear peer using soft reconfiguration in this address-family. + +.. clicmd:: clear bgp ipv4|ipv6 unicast PEER soft|in|out + + Clear peer using soft reconfiguration in this address-family and sub-address-family. + +.. clicmd:: clear bgp [ipv4|ipv6] [unicast] PEER|\* message-stats + + Clear BGP message statistics for a specified peer or for all peers, + optionally filtered by activated address-family and sub-address-family. + +.. clicmd:: clear bgp [ipv4|ipv6] [unicast] PEER|\* capabilities + + Clear specific BGP capabilities for a specified peer or for all peers. This + includes such capabilities like FQDN capability, that can't be controlled by + any other configuration knob. + + For example, if you want to change the FQDN, you MUST reset the BGP session + in order to send a new FQDN capability to the peer. This command allows you + to resend FQDN capability without resetting the session. + + .. code-block:: frr + + hostname bgp-new.example.com + clear bgp 10.10.10.1 capabilities + +.. note:: + + Changing the hostname is possible only when connected to the specific daemon. + If you change the hostname via ``vtysh``, it won't be changed. + +The following are available in the ``router bgp`` mode: + +.. clicmd:: write-quanta (1-64) + + BGP message Tx I/O is vectored. This means that multiple packets are written + to the peer socket at the same time each I/O cycle, in order to minimize + system call overhead. This value controls how many are written at a time. + Under certain load conditions, reducing this value could make peer traffic + less 'bursty'. In practice, leave this settings on the default (64) unless + you truly know what you are doing. + +.. clicmd:: read-quanta (1-10) + + Unlike Tx, BGP Rx traffic is not vectored. Packets are read off the wire one + at a time in a loop. This setting controls how many iterations the loop runs + for. As with write-quanta, it is best to leave this setting on the default. + +The following command is available in ``config`` mode as well as in the +``router bgp`` mode: + +.. clicmd:: bgp graceful-shutdown + + The purpose of this command is to initiate BGP Graceful Shutdown which + is described in :rfc:`8326`. The use case for this is to minimize or + eliminate the amount of traffic loss in a network when a planned + maintenance activity such as software upgrade or hardware replacement + is to be performed on a router. The feature works by re-announcing + routes to eBGP peers with the GRACEFUL_SHUTDOWN community included. + Peers are then expected to treat such paths with the lowest preference. + This happens automatically on a receiver running FRR; with other + routing protocol stacks, an inbound policy may have to be configured. + In FRR, triggering graceful shutdown also results in announcing a + LOCAL_PREF of 0 to iBGP peers. + + Graceful shutdown can be configured per BGP instance or globally for + all of BGP. These two options are mutually exclusive. The no form of + the command causes graceful shutdown to be stopped, and routes will + be re-announced without the GRACEFUL_SHUTDOWN community and/or with + the usual LOCAL_PREF value. Note that if this option is saved to + the startup configuration, graceful shutdown will remain in effect + across restarts of *bgpd* and will need to be explicitly disabled. + +.. clicmd:: bgp input-queue-limit (1-4294967295) + + Set the BGP Input Queue limit for all peers when messaging parsing. Increase + this only if you have the memory to handle large queues of messages at once. + +.. clicmd:: bgp output-queue-limit (1-4294967295) + + Set the BGP Output Queue limit for all peers when messaging parsing. Increase + this only if you have the memory to handle large queues of messages at once. + +.. _bgp-displaying-bgp-information: + +Displaying BGP Information +========================== + +The following four commands display the IPv6 and IPv4 routing tables, depending +on whether or not the ``ip`` keyword is used. +Actually, :clicmd:`show ip bgp` command was used on older `Quagga` routing +daemon project, while :clicmd:`show bgp` command is the new format. The choice +has been done to keep old format with IPv4 routing table, while new format +displays IPv6 routing table. + +.. clicmd:: show ip bgp [all] [wide|json [detail]] + +.. clicmd:: show ip bgp A.B.C.D [json] + +.. clicmd:: show bgp [all] [wide|json [detail]] + +.. clicmd:: show bgp X:X::X:X [json] + + These commands display BGP routes. When no route is specified, the default + is to display all BGP routes. + + :: + + BGP table version is 0, local router ID is 10.1.1.1 + Status codes: s suppressed, d damped, h history, * valid, > best, i - internal + Origin codes: i - IGP, e - EGP, ? - incomplete + + Network Next Hop Metric LocPrf Weight Path + \*> 1.1.1.1/32 0.0.0.0 0 32768 i + + Total number of prefixes 1 + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + This is especially handy dealing with IPv6 prefixes and + if :clicmd:`[no] bgp default show-nexthop-hostname` is enabled. + + If ``all`` option is specified, ``ip`` keyword is ignored, show bgp all and + show ip bgp all commands display routes for all AFIs and SAFIs. + + If ``json`` option is specified, output is displayed in JSON format. + + If ``detail`` option is specified after ``json``, more verbose JSON output + will be displayed. + +Some other commands provide additional options for filtering the output. + +.. clicmd:: show [ip] bgp regexp LINE + + This command displays BGP routes using AS path regular expression + (:ref:`bgp-regular-expressions`). + +.. clicmd:: show [ip] bgp [all] summary [wide] [json] + + Show a bgp peer summary for the specified address family. + +The old command structure :clicmd:`show ip bgp` may be removed in the future +and should no longer be used. In order to reach the other BGP routing tables +other than the IPv6 routing table given by :clicmd:`show bgp`, the new command +structure is extended with :clicmd:`show bgp [afi] [safi]`. + +``wide`` option gives more output like ``LocalAS`` and extended ``Desc`` to +64 characters. + + .. code-block:: frr + + exit1# show ip bgp summary wide + + IPv4 Unicast Summary: + BGP router identifier 192.168.100.1, local AS number 65534 VRF default vrf-id 0 + BGP table version 3 + RIB entries 5, using 920 bytes of memory + Peers 1, using 27 KiB of memory + + Neighbor V AS LocalAS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc + 192.168.0.2 4 65030 123 15 22 0 0 0 00:07:00 0 1 us-east1-rs1.frrouting.org + + Total number of neighbors 1 + exit1# + +If PfxRcd and/or PfxSnt is shown as ``(Policy)``, that means that the EBGP +default policy is turned on, but you don't have any filters applied for +incoming/outgoing directions. + +.. seealso:: :ref:`bgp-requires-policy` + +.. clicmd:: show bgp [afi] [safi] [all] [wide|json] + +.. clicmd:: show bgp vrfs [] [json] + + The command displays all bgp vrf instances basic info like router-id, + configured and established neighbors, + evpn related basic info like l3vni, router-mac, vxlan-interface. + User can get that information as JSON format when ``json`` keyword + at the end of cli is presented. + + .. code-block:: frr + + torc-11# show bgp vrfs + Type Id routerId #PeersCfg #PeersEstb Name + L3-VNI RouterMAC Interface + DFLT 0 17.0.0.6 3 3 default + 0 00:00:00:00:00:00 unknown + VRF 21 17.0.0.6 0 0 sym_1 + 8888 34:11:12:22:22:01 vlan4034_l3 + VRF 32 17.0.0.6 0 0 sym_2 + 8889 34:11:12:22:22:01 vlan4035_l3 + + Total number of VRFs (including default): 3 + +.. clicmd:: show bgp [ | l2vpn evpn] + + These commands display BGP routes for the specific routing table indicated by + the selected afi and the selected safi. If no afi and no safi value is given, + the command falls back to the default IPv6 routing table. + +.. clicmd:: show bgp l2vpn evpn route [type ] + + EVPN prefixes can also be filtered by EVPN route type. + +.. clicmd:: show bgp l2vpn evpn route [detail] [type ] self-originate [json] + + Display self-originated EVPN prefixes which can also be filtered by EVPN route type. + +.. clicmd:: show bgp vni [vtep VTEP] [type ] [] + + Display per-VNI EVPN routing table in bgp. Filter route-type, vtep, or VNI. + +.. clicmd:: show bgp [afi] [safi] [all] summary [json] + + Show a bgp peer summary for the specified address family, and subsequent + address-family. + +.. clicmd:: show bgp [afi] [safi] [all] summary failed [json] + + Show a bgp peer summary for peers that are not successfully exchanging routes + for the specified address family, and subsequent address-family. + +.. clicmd:: show bgp [afi] [safi] [all] summary established [json] + + Show a bgp peer summary for peers that are successfully exchanging routes + for the specified address family, and subsequent address-family. + +.. clicmd:: show bgp [afi] [safi] [all] summary neighbor [PEER] [json] + + Show a bgp summary for the specified peer, address family, and + subsequent address-family. The neighbor filter can be used in combination + with the failed, established filters. + +.. clicmd:: show bgp [afi] [safi] [all] summary remote-as [json] + + Show a bgp peer summary for the specified remote-as ASN or type (``internal`` + for iBGP and ``external`` for eBGP sessions), address family, and subsequent + address-family. The remote-as filter can be used in combination with the + failed, established filters. + +.. clicmd:: show bgp [afi] [safi] [all] summary terse [json] + + Shorten the output. Do not show the following information about the BGP + instances: the number of RIB entries, the table version and the used memory. + The ``terse`` option can be used in combination with the remote-as, neighbor, + failed and established filters, and with the ``wide`` option as well. + +.. clicmd:: show bgp [afi] [safi] [neighbor [PEER] [routes|advertised-routes|received-routes] [ | detail] [json] + + This command shows information on a specific BGP peer of the relevant + afi and safi selected. + + The ``routes`` keyword displays only routes in this address-family's BGP + table that were received by this peer and accepted by inbound policy. + + The ``advertised-routes`` keyword displays only the routes in this + address-family's BGP table that were permitted by outbound policy and + advertised to to this peer. + + The ``received-routes`` keyword displays all routes belonging to this + address-family (prior to inbound policy) that were received by this peer. + + If a specific prefix is specified, the detailed version of that prefix will + be displayed. + + If ``detail`` option is specified, the detailed version of all routes + will be displayed. The same format as ``show [ip] bgp [afi] [safi] PREFIX`` + will be used, but for the whole table of received, advertised or filtered + prefixes. + + If ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show bgp [ VIEWVRFNAME] [afi] [safi] neighbors PEER received prefix-filter [json] + + Display Address Prefix ORFs received from this peer. + +.. clicmd:: show bgp [afi] [safi] [all] dampening dampened-paths [wide|json] + + Display paths suppressed due to dampening of the selected afi and safi + selected. + +.. clicmd:: show bgp [afi] [safi] [all] dampening flap-statistics [wide|json] + + Display flap statistics of routes of the selected afi and safi selected. + +.. clicmd:: show bgp [afi] [safi] [all] dampening parameters [json] + + Display details of configured dampening parameters of the selected afi and + safi. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show bgp [afi] [safi] [all] version (1-4294967295) [wide|json] + + Display prefixes with matching version numbers. The version number and + above having prefixes will be listed here. + + It helps to identify which prefixes were installed at some point. + + Here is an example of how to check what prefixes were installed starting + with an arbitrary version: + +.. code-block:: shell + + # vtysh -c 'show bgp ipv4 unicast json' | jq '.tableVersion' + 9 + # vtysh -c 'show ip bgp version 9 json' | jq -r '.routes | keys[]' + 192.168.3.0/24 + # vtysh -c 'show ip bgp version 8 json' | jq -r '.routes | keys[]' + 192.168.2.0/24 + 192.168.3.0/24 + +.. clicmd:: show bgp [afi] [safi] statistics + + Display statistics of routes of the selected afi and safi. + +.. clicmd:: show bgp statistics-all + + Display statistics of routes of all the afi and safi. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] cidr-only [wide|json] + + Display routes with non-natural netmasks. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] prefix-list WORD [wide|json] + + Display routes that match the specified prefix-list. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] access-list WORD [wide|json] + + Display routes that match the specified access-list. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] filter-list WORD [wide|json] + + Display routes that match the specified AS-Path filter-list. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] route-map WORD [wide|json] + + Display routes that match the specified route-map. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] longer-prefixes [wide|json] + + Displays the specified route and all more specific routes. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] self-originate [wide|json] + + Display self-originated routes. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] neighbors A.B.C.D [advertised-routes|received-routes|filtered-routes] [ | detail] [json|wide] + + Display the routes advertised to a BGP neighbor or received routes + from neighbor or filtered routes received from neighbor based on the + option specified. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + This is especially handy dealing with IPv6 prefixes and + if :clicmd:`[no] bgp default show-nexthop-hostname` is enabled. + + If ``all`` option is specified, ``ip`` keyword is ignored and, + routes displayed for all AFIs and SAFIs. + if afi is specified, with ``all`` option, routes will be displayed for + each SAFI in the selcted AFI + + If a specific prefix is specified, the detailed version of that prefix will + be displayed. + + If ``detail`` option is specified, the detailed version of all routes + will be displayed. The same format as ``show [ip] bgp [afi] [safi] PREFIX`` + will be used, but for the whole table of received, advertised or filtered + prefixes. + + If ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show [ip] bgp [afi] [safi] [all] detail-routes + + Display the detailed version of all routes. The same format as using + ``show [ip] bgp [afi] [safi] PREFIX``, but for the whole BGP table. + + If ``all`` option is specified, ``ip`` keyword is ignored and, + routes displayed for all AFIs and SAFIs. + + If ``afi`` is specified, with ``all`` option, routes will be displayed for + each SAFI in the selected AFI. + +.. clicmd:: show [ip] bgp [ VIEWVRFNAME] [afi] [safi] detail [json] + + Display the detailed version of all routes from the specified bgp vrf table + for a given afi + safi. + + If no vrf is specified, then it is assumed as a default vrf and routes + are displayed from default vrf table. + + If ``all`` option is specified as vrf name, then all bgp vrf tables routes + from a given afi+safi are displayed in the detailed output of routes. + + If ``json`` option is specified, detailed output is displayed in JSON format. + + Following are sample output for few examples of how to use this command. + +.. code-block:: frr + + torm-23# sh bgp ipv4 unicast detail (OR) sh bgp vrf default ipv4 unicast detail + + !--- Output suppressed. + + BGP routing table entry for 172.16.16.1/32 + Paths: (1 available, best #1, table default) + Not advertised to any peer + Local, (Received from a RR-client) + 172.16.16.1 (metric 20) from torm-22(172.16.16.1) (192.168.0.10) + Origin IGP, metric 0, localpref 100, valid, internal + Last update: Fri May 8 12:54:05 2023 + BGP routing table entry for 172.16.16.2/32 + Paths: (1 available, best #1, table default) + Not advertised to any peer + Local + 0.0.0.0 from 0.0.0.0 (172.16.16.2) + Origin incomplete, metric 0, weight 32768, valid, sourced, bestpath-from-AS Local, best (First path received) + Last update: Wed May 8 12:54:41 2023 + + Displayed 2 routes and 2 total paths + +.. code-block:: frr + + torm-23# sh bgp vrf all detail + + Instance default: + + !--- Output suppressed. + + BGP routing table entry for 172.16.16.1/32 + Paths: (1 available, best #1, table default) + Not advertised to any peer + Local, (Received from a RR-client) + 172.16.16.1 (metric 20) from torm-22(172.16.16.1) (192.168.0.10) + Origin IGP, metric 0, localpref 100, valid, internal + Last update: Fri May 8 12:44:05 2023 + BGP routing table entry for 172.16.16.2/32 + Paths: (1 available, best #1, table default) + Not advertised to any peer + Local + 0.0.0.0 from 0.0.0.0 (172.16.16.2) + Origin incomplete, metric 0, weight 32768, valid, sourced, bestpath-from-AS Local, best (First path received) + Last update: Wed May 8 12:45:01 2023 + + Displayed 2 routes and 2 total paths + + Instance vrf3: + + !--- Output suppressed. + + BGP routing table entry for 192.168.0.2/32 + Paths: (1 available, best #1, vrf vrf3) + Not advertised to any peer + Imported from 172.16.16.1:12:[2]:[0]:[48]:[00:02:00:00:00:58]:[32]:[192.168.0.2], VNI 1008/4003 + Local + 172.16.16.1 from torm-22(172.16.16.1) (172.16.16.1) announce-nh-self + Origin IGP, localpref 100, valid, internal, bestpath-from-AS Local, best (First path received) + Extended Community: RT:65000:1008 ET:8 Rmac:00:02:00:00:00:58 + Last update: Fri May 8 02:41:55 2023 + BGP routing table entry for 192.168.1.2/32 + Paths: (1 available, best #1, vrf vrf3) + Not advertised to any peer + Imported from 172.16.16.1:13:[2]:[0]:[48]:[00:02:00:00:00:58]:[32]:[192.168.1.2], VNI 1009/4003 + Local + 172.16.16.1 from torm-22(172.16.16.1) (172.16.16.1) announce-nh-self + Origin IGP, localpref 100, valid, internal, bestpath-from-AS Local, best (First path received) + Extended Community: RT:65000:1009 ET:8 Rmac:00:02:00:00:00:58 + Last update: Fri May 8 02:41:55 2023 + + Displayed 2 routes and 2 total paths + + +.. code-block:: frr + + torm-23# sh bgp vrf vrf3 ipv4 unicast detail + + !--- Output suppressed. + + BGP routing table entry for 192.168.0.2/32 + Paths: (1 available, best #1, vrf vrf3) + Not advertised to any peer + Imported from 172.16.16.1:12:[2]:[0]:[48]:[00:02:00:00:00:58]:[32]:[192.168.0.2], VNI 1008/4003 + Local + 172.16.16.1 from torm-22(172.16.16.1) (172.16.16.1) announce-nh-self + Origin IGP, localpref 100, valid, internal, bestpath-from-AS Local, best (First path received) + Extended Community: RT:65000:1008 ET:8 Rmac:00:02:00:00:00:58 + Last update: Fri May 8 02:23:35 2023 + BGP routing table entry for 192.168.1.2/32 + Paths: (1 available, best #1, vrf vrf3) + Not advertised to any peer + Imported from 172.16.16.1:13:[2]:[0]:[48]:[00:02:00:00:00:58]:[32]:[192.168.1.2], VNI 1009/4003 + Local + 172.16.16.1 from torm-22(172.16.16.1) (172.16.16.1) announce-nh-self + Origin IGP, localpref 100, valid, internal, bestpath-from-AS Local, best (First path received) + Extended Community: RT:65000:1009 ET:8 Rmac:00:02:00:00:00:58 + Last update: Fri May 8 02:23:55 2023 + + Displayed 2 routes and 2 total paths + +.. _bgp-display-routes-by-community: + +Displaying Routes by Community Attribute +---------------------------------------- + +The following commands allow displaying routes based on their community +attribute. + +.. clicmd:: show [ip] bgp [all] community [wide|json] + +.. clicmd:: show [ip] bgp [all] community COMMUNITY [wide|json] + +.. clicmd:: show [ip] bgp [all] community COMMUNITY exact-match [wide|json] + + These commands display BGP routes which have the community attribute. + attribute. When ``COMMUNITY`` is specified, BGP routes that match that + community are displayed. When `exact-match` is specified, it display only + routes that have an exact match. + +.. clicmd:: show [ip] bgp community-list WORD [json] + +.. clicmd:: show [ip] bgp community-list WORD exact-match [json] + + These commands display BGP routes for the address family specified that + match the specified community list. When `exact-match` is specified, it + displays only routes that have an exact match. + + If ``wide`` option is specified, then the prefix table's width is increased + to fully display the prefix and the nexthop. + + This is especially handy dealing with IPv6 prefixes and + if :clicmd:`[no] bgp default show-nexthop-hostname` is enabled. + + If ``all`` option is specified, ``ip`` keyword is ignored and, + routes displayed for all AFIs and SAFIs. + if afi is specified, with ``all`` option, routes will be displayed for + each SAFI in the selcted AFI + + If ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show bgp labelpool [json] + + These commands display information about the BGP labelpool used for + the association of MPLS labels with routes for L3VPN and Labeled Unicast + + If ``chunks`` option is specified, output shows the current list of label + chunks granted to BGP by Zebra, indicating the start and end label in + each chunk + + If ``inuse`` option is specified, output shows the current inuse list of + label to prefix mappings + + If ``ledger`` option is specified, output shows ledger list of all + label requests made per prefix + + If ``requests`` option is specified, output shows current list of label + requests which have not yet been fulfilled by the labelpool + + If ``summary`` option is specified, output is a summary of the counts for + the chunks, inuse, ledger and requests list along with the count of + outstanding chunk requests to Zebra and the number of zebra reconnects + that have happened + + If ``json`` option is specified, output is displayed in JSON format. + +.. _bgp-display-routes-by-lcommunity: + +Displaying Routes by Large Community Attribute +---------------------------------------------- + +The following commands allow displaying routes based on their +large community attribute. + +.. clicmd:: show [ip] bgp large-community + +.. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY + +.. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY exact-match + +.. clicmd:: show [ip] bgp large-community LARGE-COMMUNITY json + + These commands display BGP routes which have the large community attribute. + attribute. When ``LARGE-COMMUNITY`` is specified, BGP routes that match that + large community are displayed. When `exact-match` is specified, it display + only routes that have an exact match. When `json` is specified, it display + routes in json format. + +.. clicmd:: show [ip] bgp large-community-list WORD + +.. clicmd:: show [ip] bgp large-community-list WORD exact-match + +.. clicmd:: show [ip] bgp large-community-list WORD json + + These commands display BGP routes for the address family specified that + match the specified large community list. When `exact-match` is specified, + it displays only routes that have an exact match. When `json` is specified, + it display routes in json format. + +.. _bgp-display-routes-by-as-path: + + +Displaying Routes by AS Path +---------------------------- + +.. clicmd:: show bgp ipv4|ipv6 regexp LINE + + This commands displays BGP routes that matches a regular + expression `line` (:ref:`bgp-regular-expressions`). + +.. clicmd:: show [ip] bgp ipv4 vpn + +.. clicmd:: show [ip] bgp ipv6 vpn + + Print active IPV4 or IPV6 routes advertised via the VPN SAFI. + +.. clicmd:: show bgp ipv4 vpn summary + +.. clicmd:: show bgp ipv6 vpn summary + + Print a summary of neighbor connections for the specified AFI/SAFI combination. + +Displaying Routes by Route Distinguisher +---------------------------------------- + +.. clicmd:: show bgp [ vpn | l2vpn evpn [route]] rd + + For L3VPN and EVPN address-families, routes can be displayed on a per-RD + (Route Distinguisher) basis or for all RD's. + +.. clicmd:: show bgp l2vpn evpn rd [overlay | tags] + + Use the ``overlay`` or ``tags`` keywords to display the overlay/tag + information about the EVPN prefixes in the selected Route Distinguisher. + +.. clicmd:: show bgp l2vpn evpn route rd mac [ip ] [json] + + For EVPN Type 2 (macip) routes, a MAC address (and optionally an IP address) + can be supplied to the command to only display matching prefixes in the + specified RD. + +Displaying Update Group Information +----------------------------------- + +.. clicmd:: show bgp update-groups [advertise-queue|advertised-routes|packet-queue] + + Display Information about each individual update-group being used. + If SUBGROUP-ID is specified only display about that particular group. If + advertise-queue is specified the list of routes that need to be sent + to the peers in the update-group is displayed, advertised-routes means + the list of routes we have sent to the peers in the update-group and + packet-queue specifies the list of packets in the queue to be sent. + +.. clicmd:: show bgp update-groups statistics + + Display Information about update-group events in FRR. + +Displaying Nexthop Information +------------------------------ +.. clicmd:: show [ip] bgp [ VIEWVRFNAME] nexthop ipv4 [A.B.C.D] [detail] [json] + +.. clicmd:: show [ip] bgp [ VIEWVRFNAME] nexthop ipv6 [X:X::X:X] [detail] [json] + +.. clicmd:: show [ip] bgp [ VIEWVRFNAME] nexthop [] [detail] [json] + +.. clicmd:: show [ip] bgp all nexthop [json] + + Display information about nexthops to bgp neighbors. If a certain nexthop is + specified, also provides information about paths associated with the nexthop. + With detail option provides information about gates of each nexthop. + +.. clicmd:: show [ip] bgp [ VIEWVRFNAME] import-check-table [detail] [json] + + Display information about nexthops from table that is used to check network's + existence in the rib for network statements. + +Segment-Routing IPv6 +-------------------- + +.. clicmd:: show bgp segment-routing srv6 + + This command displays information about SRv6 L3VPN in bgpd. Specifically, + what kind of Locator is being used, and its Locator chunk information. + And the SID of the SRv6 Function that is actually managed on bgpd. + In the following example, bgpd is using a Locator named loc1, and two SRv6 + Functions are managed to perform VPNv6 VRF redirect for vrf10 and vrf20. + +:: + + router# show bgp segment-routing srv6 + locator_name: loc1 + locator_chunks: + - 2001:db8:1:1::/64 + functions: + - sid: 2001:db8:1:1::100 + locator: loc1 + - sid: 2001:db8:1:1::200 + locator: loc1 + bgps: + - name: default + vpn_policy[AFI_IP].tovpn_sid: none + vpn_policy[AFI_IP6].tovpn_sid: none + - name: vrf10 + vpn_policy[AFI_IP].tovpn_sid: none + vpn_policy[AFI_IP6].tovpn_sid: 2001:db8:1:1::100 + - name: vrf20 + vpn_policy[AFI_IP].tovpn_sid: none + vpn_policy[AFI_IP6].tovpn_sid: 2001:db8:1:1::200 + +AS-notation support +------------------- + +By default, the ASN value output follows how the BGP ASN instance is +expressed in the configuration. Three as-notation outputs are available: + +- plain output: both AS4B and AS2B use a single number. + ` router bgp 65536`. + +- dot output: AS4B values are using two numbers separated by a period. + `router bgp 1.1` means that the AS number is 65536. + +- dot+ output: AS2B and AS4B values are using two numbers separated by a + period. `router bgp 0.5` means that the AS number is 5. + +The below option permits forcing the as-notation output: + +.. clicmd:: router bgp ASN as-notation dot|dot+|plain + + The chosen as-notation format will override the BGP ASN output. + +.. _bgp-route-reflector: + +Route Reflector +=============== + +BGP routers connected inside the same AS through BGP belong to an internal +BGP session, or IBGP. In order to prevent routing table loops, IBGP does not +advertise IBGP-learned routes to other routers in the same session. As such, +IBGP requires a full mesh of all peers. For large networks, this quickly becomes +unscalable. Introducing route reflectors removes the need for the full-mesh. + +When route reflectors are configured, these will reflect the routes announced +by the peers configured as clients. A route reflector client is configured +with: + +.. clicmd:: neighbor PEER route-reflector-client + + +To avoid single points of failure, multiple route reflectors can be configured. + +A cluster is a collection of route reflectors and their clients, and is used +by route reflectors to avoid looping. + +.. clicmd:: bgp cluster-id A.B.C.D + +.. clicmd:: bgp no-rib + +To set and unset the BGP daemon ``-n`` / ``--no_kernel`` options during runtime +to disable BGP route installation to the RIB (Zebra), the ``[no] bgp no-rib`` +commands can be used; + +Please note that setting the option during runtime will withdraw all routes in +the daemons RIB from Zebra and unsetting it will announce all routes in the +daemons RIB to Zebra. If the option is passed as a command line argument when +starting the daemon and the configuration gets saved, the option will persist +unless removed from the configuration with the negating command prior to the +configuration write operation. At this point in time non SAFI_UNICAST BGP +data is not properly withdrawn from zebra when this command is issued. + +.. clicmd:: bgp allow-martian-nexthop + +When a peer receives a martian nexthop as part of the NLRI for a route +permit the nexthop to be used as such, instead of rejecting and resetting +the connection. + +.. clicmd:: bgp send-extra-data zebra + +This command turns on the ability of BGP to send extra data to zebra. Currently, +it's the AS-Path, communities, and the path selection reason. The default +behavior in BGP is not to send this data. If the routes were sent to zebra and +the option is changed, bgpd doesn't reinstall the routes to comply with the new +setting. + +.. clicmd:: bgp session-dscp (0-63) + +This command allows the BGP daemon to control, at a global level, the DSCP value +used in outgoing packets for each BGP connection. + +.. _bgp-suppress-fib: + +Suppressing routes not installed in FIB +======================================= + +The FRR implementation of BGP advertises prefixes learnt from a peer to other +peers even if the routes do not get installed in the FIB. There can be +scenarios where the hardware tables in some of the routers (along the path from +the source to destination) is full which will result in all routes not getting +installed in the FIB. If these routes are advertised to the downstream routers +then traffic will start flowing and will be dropped at the intermediate router. + +The solution is to provide a configurable option to check for the FIB install +status of the prefixes and advertise to peers if the prefixes are successfully +installed in the FIB. The advertisement of the prefixes are suppressed if it is +not installed in FIB. + +The following conditions apply will apply when checking for route installation +status in FIB: + +1. The advertisement or suppression of routes based on FIB install status + applies only for newly learnt routes from peer (routes which are not in + BGP local RIB). +2. If the route received from peer already exists in BGP local RIB and route + attributes have changed (best path changed), the old path is deleted and + new path is installed in FIB. The FIB install status will not have any + effect. Therefore only when the route is received first time the checks + apply. +3. The feature will not apply for routes learnt through other means like + redistribution to bgp from other protocols. This is applicable only to + peer learnt routes. +4. If a route is installed in FIB and then gets deleted from the dataplane, + then routes will not be withdrawn from peers. This will be considered as + dataplane issue. +5. The feature will slightly increase the time required to advertise the routes + to peers since the route install status needs to be received from the FIB +6. If routes are received by the peer before the configuration is applied, then + the bgp sessions need to be reset for the configuration to take effect. +7. If the route which is already installed in dataplane is removed for some + reason, sending withdraw message to peers is not currently supported. + +.. clicmd:: bgp suppress-fib-pending + + This command is applicable at the global level and at an individual + bgp level. If applied at the global level all bgp instances will + wait for fib installation before announcing routes and there is no + way to turn it off for a particular bgp vrf. + +.. _routing-policy: + +Routing Policy +============== + +You can set different routing policy for a peer. For example, you can set +different filter for a peer. + +.. code-block:: frr + + ! + router bgp 1 view 1 + neighbor 10.0.0.1 remote-as 2 + address-family ipv4 unicast + neighbor 10.0.0.1 distribute-list 1 in + exit-address-family + ! + router bgp 1 view 2 + neighbor 10.0.0.1 remote-as 2 + address-family ipv4 unicast + neighbor 10.0.0.1 distribute-list 2 in + exit-address-family + +This means BGP update from a peer 10.0.0.1 goes to both BGP view 1 and view 2. +When the update is inserted into view 1, distribute-list 1 is applied. On the +other hand, when the update is inserted into view 2, distribute-list 2 is +applied. + + +.. _bgp-regular-expressions: + +BGP Regular Expressions +======================= + +BGP regular expressions are based on :t:`POSIX 1003.2` regular expressions. The +following description is just a quick subset of the POSIX regular expressions. + + +.\* + Matches any single character. + +\* + Matches 0 or more occurrences of pattern. + +\+ + Matches 1 or more occurrences of pattern. + +? + Match 0 or 1 occurrences of pattern. + +^ + Matches the beginning of the line. + +$ + Matches the end of the line. + +_ + The ``_`` character has special meanings in BGP regular expressions. It + matches to space and comma , and AS set delimiter ``{`` and ``}`` and AS + confederation delimiter ``(`` and ``)``. And it also matches to the + beginning of the line and the end of the line. So ``_`` can be used for AS + value boundaries match. This character technically evaluates to + ``(^|[,{}()]|$)``. + + +.. _bgp-configuration-examples: + +Miscellaneous Configuration Examples +==================================== + +Example of a session to an upstream, advertising only one prefix to it. + +.. code-block:: frr + + router bgp 64512 + bgp router-id 10.236.87.1 + neighbor upstream peer-group + neighbor upstream remote-as 64515 + neighbor upstream capability dynamic + neighbor 10.1.1.1 peer-group upstream + neighbor 10.1.1.1 description ACME ISP + + address-family ipv4 unicast + network 10.236.87.0/24 + neighbor upstream prefix-list pl-allowed-adv out + exit-address-family + ! + ip prefix-list pl-allowed-adv seq 5 permit 82.195.133.0/25 + ip prefix-list pl-allowed-adv seq 10 deny any + +A more complex example including upstream, peer and customer sessions +advertising global prefixes and NO_EXPORT prefixes and providing actions for +customer routes based on community values. Extensive use is made of route-maps +and the 'call' feature to support selective advertising of prefixes. This +example is intended as guidance only, it has NOT been tested and almost +certainly contains silly mistakes, if not serious flaws. + +.. code-block:: frr + + router bgp 64512 + bgp router-id 10.236.87.1 + neighbor upstream capability dynamic + neighbor cust capability dynamic + neighbor peer capability dynamic + neighbor 10.1.1.1 remote-as 64515 + neighbor 10.1.1.1 peer-group upstream + neighbor 10.2.1.1 remote-as 64516 + neighbor 10.2.1.1 peer-group upstream + neighbor 10.3.1.1 remote-as 64517 + neighbor 10.3.1.1 peer-group cust-default + neighbor 10.3.1.1 description customer1 + neighbor 10.4.1.1 remote-as 64518 + neighbor 10.4.1.1 peer-group cust + neighbor 10.4.1.1 description customer2 + neighbor 10.5.1.1 remote-as 64519 + neighbor 10.5.1.1 peer-group peer + neighbor 10.5.1.1 description peer AS 1 + neighbor 10.6.1.1 remote-as 64520 + neighbor 10.6.1.1 peer-group peer + neighbor 10.6.1.1 description peer AS 2 + + address-family ipv4 unicast + network 10.123.456.0/24 + network 10.123.456.128/25 route-map rm-no-export + neighbor upstream route-map rm-upstream-out out + neighbor cust route-map rm-cust-in in + neighbor cust route-map rm-cust-out out + neighbor cust send-community both + neighbor peer route-map rm-peer-in in + neighbor peer route-map rm-peer-out out + neighbor peer send-community both + neighbor 10.3.1.1 prefix-list pl-cust1-network in + neighbor 10.4.1.1 prefix-list pl-cust2-network in + neighbor 10.5.1.1 prefix-list pl-peer1-network in + neighbor 10.6.1.1 prefix-list pl-peer2-network in + exit-address-family + ! + ip prefix-list pl-default permit 0.0.0.0/0 + ! + ip prefix-list pl-upstream-peers permit 10.1.1.1/32 + ip prefix-list pl-upstream-peers permit 10.2.1.1/32 + ! + ip prefix-list pl-cust1-network permit 10.3.1.0/24 + ip prefix-list pl-cust1-network permit 10.3.2.0/24 + ! + ip prefix-list pl-cust2-network permit 10.4.1.0/24 + ! + ip prefix-list pl-peer1-network permit 10.5.1.0/24 + ip prefix-list pl-peer1-network permit 10.5.2.0/24 + ip prefix-list pl-peer1-network permit 192.168.0.0/24 + ! + ip prefix-list pl-peer2-network permit 10.6.1.0/24 + ip prefix-list pl-peer2-network permit 10.6.2.0/24 + ip prefix-list pl-peer2-network permit 192.168.1.0/24 + ip prefix-list pl-peer2-network permit 192.168.2.0/24 + ip prefix-list pl-peer2-network permit 172.16.1/24 + ! + bgp as-path access-list seq 5 asp-own-as permit ^$ + bgp as-path access-list seq 10 asp-own-as permit _64512_ + ! + ! ################################################################# + ! Match communities we provide actions for, on routes receives from + ! customers. Communities values of :X, with X, have actions: + ! + ! 100 - blackhole the prefix + ! 200 - set no_export + ! 300 - advertise only to other customers + ! 400 - advertise only to upstreams + ! 500 - set no_export when advertising to upstreams + ! 2X00 - set local_preference to X00 + ! + ! blackhole the prefix of the route + bgp community-list standard cm-blackhole permit 64512:100 + ! + ! set no-export community before advertising + bgp community-list standard cm-set-no-export permit 64512:200 + ! + ! advertise only to other customers + bgp community-list standard cm-cust-only permit 64512:300 + ! + ! advertise only to upstreams + bgp community-list standard cm-upstream-only permit 64512:400 + ! + ! advertise to upstreams with no-export + bgp community-list standard cm-upstream-noexport permit 64512:500 + ! + ! set local-pref to least significant 3 digits of the community + bgp community-list standard cm-prefmod-100 permit 64512:2100 + bgp community-list standard cm-prefmod-200 permit 64512:2200 + bgp community-list standard cm-prefmod-300 permit 64512:2300 + bgp community-list standard cm-prefmod-400 permit 64512:2400 + bgp community-list expanded cme-prefmod-range permit 64512:2... + ! + ! Informational communities + ! + ! 3000 - learned from upstream + ! 3100 - learned from customer + ! 3200 - learned from peer + ! + bgp community-list standard cm-learnt-upstream permit 64512:3000 + bgp community-list standard cm-learnt-cust permit 64512:3100 + bgp community-list standard cm-learnt-peer permit 64512:3200 + ! + ! ################################################################### + ! Utility route-maps + ! + ! These utility route-maps generally should not used to permit/deny + ! routes, i.e. they do not have meaning as filters, and hence probably + ! should be used with 'on-match next'. These all finish with an empty + ! permit entry so as not interfere with processing in the caller. + ! + route-map rm-no-export permit 10 + set community additive no-export + route-map rm-no-export permit 20 + ! + route-map rm-blackhole permit 10 + description blackhole, up-pref and ensure it cannot escape this AS + set ip next-hop 127.0.0.1 + set local-preference 10 + set community additive no-export + route-map rm-blackhole permit 20 + ! + ! Set local-pref as requested + route-map rm-prefmod permit 10 + match community cm-prefmod-100 + set local-preference 100 + route-map rm-prefmod permit 20 + match community cm-prefmod-200 + set local-preference 200 + route-map rm-prefmod permit 30 + match community cm-prefmod-300 + set local-preference 300 + route-map rm-prefmod permit 40 + match community cm-prefmod-400 + set local-preference 400 + route-map rm-prefmod permit 50 + ! + ! Community actions to take on receipt of route. + route-map rm-community-in permit 10 + description check for blackholing, no point continuing if it matches. + match community cm-blackhole + call rm-blackhole + route-map rm-community-in permit 20 + match community cm-set-no-export + call rm-no-export + on-match next + route-map rm-community-in permit 30 + match community cme-prefmod-range + call rm-prefmod + route-map rm-community-in permit 40 + ! + ! ##################################################################### + ! Community actions to take when advertising a route. + ! These are filtering route-maps, + ! + ! Deny customer routes to upstream with cust-only set. + route-map rm-community-filt-to-upstream deny 10 + match community cm-learnt-cust + match community cm-cust-only + route-map rm-community-filt-to-upstream permit 20 + ! + ! Deny customer routes to other customers with upstream-only set. + route-map rm-community-filt-to-cust deny 10 + match community cm-learnt-cust + match community cm-upstream-only + route-map rm-community-filt-to-cust permit 20 + ! + ! ################################################################### + ! The top-level route-maps applied to sessions. Further entries could + ! be added obviously.. + ! + ! Customers + route-map rm-cust-in permit 10 + call rm-community-in + on-match next + route-map rm-cust-in permit 20 + set community additive 64512:3100 + route-map rm-cust-in permit 30 + ! + route-map rm-cust-out permit 10 + call rm-community-filt-to-cust + on-match next + route-map rm-cust-out permit 20 + ! + ! Upstream transit ASes + route-map rm-upstream-out permit 10 + description filter customer prefixes which are marked cust-only + call rm-community-filt-to-upstream + on-match next + route-map rm-upstream-out permit 20 + description only customer routes are provided to upstreams/peers + match community cm-learnt-cust + ! + ! Peer ASes + ! outbound policy is same as for upstream + route-map rm-peer-out permit 10 + call rm-upstream-out + ! + route-map rm-peer-in permit 10 + set community additive 64512:3200 + + +Example of how to set up a 6-Bone connection. + +.. code-block:: frr + + ! bgpd configuration + ! ================== + ! + ! MP-BGP configuration + ! + router bgp 7675 + bgp router-id 10.0.0.1 + neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 remote-as `as-number` + ! + address-family ipv6 + network 3ffe:506::/32 + neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 activate + neighbor 3ffe:1cfa:0:2:2a0:c9ff:fe9e:f56 route-map set-nexthop out + neighbor 3ffe:1cfa:0:2:2c0:4fff:fe68:a231 remote-as `as-number` + neighbor 3ffe:1cfa:0:2:2c0:4fff:fe68:a231 route-map set-nexthop out + exit-address-family + ! + ipv6 access-list all permit any + ! + ! Set output nexthop address. + ! + route-map set-nexthop permit 10 + match ipv6 address all + set ipv6 nexthop global 3ffe:1cfa:0:2:2c0:4fff:fe68:a225 + set ipv6 nexthop local fe80::2c0:4fff:fe68:a225 + ! + log file bgpd.log + ! + +.. _bgp-tcp-mss: + +BGP tcp-mss support +=================== +TCP provides a mechanism for the user to specify the max segment size. +setsockopt API is used to set the max segment size for TCP session. We +can configure this as part of BGP neighbor configuration. + +This document explains how to avoid ICMP vulnerability issues by limiting +TCP max segment size when you are using MTU discovery. Using MTU discovery +on TCP paths is one method of avoiding BGP packet fragmentation. + +TCP negotiates a maximum segment size (MSS) value during session connection +establishment between two peers. The MSS value negotiated is primarily based +on the maximum transmission unit (MTU) of the interfaces to which the +communicating peers are directly connected. However, due to variations in +link MTU on the path taken by the TCP packets, some packets in the network +that are well within the MSS value might be fragmented when the packet size +exceeds the link's MTU. + +This feature is supported with TCP over IPv4 and TCP over IPv6. + +CLI Configuration: +------------------ +Below configuration can be done in router bgp mode and allows the user to +configure the tcp-mss value per neighbor. The configuration gets applied +only after hard reset is performed on that neighbor. If we configure tcp-mss +on both the neighbors then both neighbors need to be reset. + +The configuration takes effect based on below rules, so there is a configured +tcp-mss and a synced tcp-mss value per TCP session. + +By default if the configuration is not done then the TCP max segment size is +set to the Maximum Transmission unit (MTU) – (IP/IP6 header size + TCP header +size + ethernet header). For IPv4 its MTU – (20 bytes IP header + 20 bytes TCP +header + 12 bytes ethernet header) and for IPv6 its MTU – (40 bytes IPv6 header ++ 20 bytes TCP header + 12 bytes ethernet header). + +If the config is done then it reduces 12-14 bytes for the ether header and +uses it after synchronizing in TCP handshake. + +.. clicmd:: neighbor tcp-mss (1-65535) + +When tcp-mss is configured kernel reduces 12-14 bytes for ethernet header. +E.g. if tcp-mss is configured as 150 the synced value will be 138. + +Note: configured and synced value is different since TCP module will reduce +12 bytes for ethernet header. + +Running config: +--------------- + +.. code-block:: frr + + frr# show running-config + Building configuration... + + Current configuration: + ! + router bgp 100 + bgp router-id 192.0.2.1 + neighbor 198.51.100.2 remote-as 100 + neighbor 198.51.100.2 tcp-mss 150 => new entry + neighbor 2001:DB8::2 remote-as 100 + neighbor 2001:DB8::2 tcp-mss 400 => new entry + +Show command: +------------- + +.. code-block:: frr + + frr# show bgp neighbors 198.51.100.2 + BGP neighbor is 198.51.100.2, remote AS 100, local AS 100, internal link + Hostname: frr + BGP version 4, remote router ID 192.0.2.2, local router ID 192.0.2.1 + BGP state = Established, up for 02:15:28 + Last read 00:00:28, Last write 00:00:28 + Hold time is 180, keepalive interval is 60 seconds + Configured tcp-mss is 150, synced tcp-mss is 138 => new display + +.. code-block:: frr + + frr# show bgp neighbors 2001:DB8::2 + BGP neighbor is 2001:DB8::2, remote AS 100, local AS 100, internal link + Hostname: frr + BGP version 4, remote router ID 192.0.2.2, local router ID 192.0.2.1 + BGP state = Established, up for 02:16:34 + Last read 00:00:34, Last write 00:00:34 + Hold time is 180, keepalive interval is 60 seconds + Configured tcp-mss is 400, synced tcp-mss is 388 => new display + +Show command json output: +------------------------- + +.. code-block:: frr + + frr# show bgp neighbors 2001:DB8::2 json + { + "2001:DB8::2":{ + "remoteAs":100, + "localAs":100, + "nbrInternalLink":true, + "hostname":"frr", + "bgpVersion":4, + "remoteRouterId":"192.0.2.2", + "localRouterId":"192.0.2.1", + "bgpState":"Established", + "bgpTimerUpMsec":8349000, + "bgpTimerUpString":"02:19:09", + "bgpTimerUpEstablishedEpoch":1613054251, + "bgpTimerLastRead":9000, + "bgpTimerLastWrite":9000, + "bgpInUpdateElapsedTimeMsecs":8347000, + "bgpTimerHoldTimeMsecs":180000, + "bgpTimerKeepAliveIntervalMsecs":60000, + "bgpTcpMssConfigured":400, => new entry + "bgpTcpMssSynced":388, => new entry + +.. code-block:: frr + + frr# show bgp neighbors 198.51.100.2 json + { + "198.51.100.2":{ + "remoteAs":100, + "localAs":100, + "nbrInternalLink":true, + "hostname":"frr", + "bgpVersion":4, + "remoteRouterId":"192.0.2.2", + "localRouterId":"192.0.2.1", + "bgpState":"Established", + "bgpTimerUpMsec":8370000, + "bgpTimerUpString":"02:19:30", + "bgpTimerUpEstablishedEpoch":1613054251, + "bgpTimerLastRead":30000, + "bgpTimerLastWrite":30000, + "bgpInUpdateElapsedTimeMsecs":8368000, + "bgpTimerHoldTimeMsecs":180000, + "bgpTimerKeepAliveIntervalMsecs":60000, + "bgpTcpMssConfigured":150, => new entry + "bgpTcpMssSynced":138, => new entry + +.. include:: routeserver.rst + +.. include:: rpki.rst + +.. include:: wecmp_linkbw.rst + +.. include:: flowspec.rst + +.. [#med-transitivity-rant] For some set of objects to have an order, there *must* be some binary ordering relation that is defined for *every* combination of those objects, and that relation *must* be transitive. I.e.:, if the relation operator is <, and if a < b and b < c then that relation must carry over and it *must* be that a < c for the objects to have an order. The ordering relation may allow for equality, i.e. a < b and b < a may both be true and imply that a and b are equal in the order and not distinguished by it, in which case the set has a partial order. Otherwise, if there is an order, all the objects have a distinct place in the order and the set has a total order) +.. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 +.. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 +.. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 + +.. _bgp-fast-convergence: + +BGP fast-convergence support +============================ +Whenever BGP peer address becomes unreachable we must bring down the BGP +session immediately. Currently only single-hop EBGP sessions are brought +down immediately.IBGP and multi-hop EBGP sessions wait for hold-timer +expiry to bring down the sessions. + +This new configuration option helps user to teardown BGP sessions immediately +whenever peer becomes unreachable. + +.. clicmd:: bgp fast-convergence + +This configuration is available at the bgp level. When enabled, configuration +is applied to all the neighbors configured in that bgp instance. + +.. code-block:: frr + + router bgp 64496 + neighbor 10.0.0.2 remote-as 64496 + neighbor fd00::2 remote-as 64496 + bgp fast-convergence + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + neighbor fd00::2 activate + exit-address-family diff --git a/doc/user/bmp.rst b/doc/user/bmp.rst new file mode 100644 index 0000000..14d0849 --- /dev/null +++ b/doc/user/bmp.rst @@ -0,0 +1,173 @@ +.. _bmp: + +*** +BMP +*** + +:abbr:`BMP` (BGP Monitoring Protocol, :rfc:`7854`) is used to send monitoring +data from BGP routers to network management entities. + +Implementation characteristics +============================== + +The `BMP` implementation in FRR has the following properties: + +- only the :rfc:`7854` features are currently implemented. This means protocol + version 3 without any extensions. It is not possible to use an older draft + protocol version of BMP. + +- the following statistics codes are implemented: + + - 0: count of prefixes rejected + - 2: count of duplicate prefix withdrawals + - 3: count of **prefixes** with loop in cluster id + - 4: count of **prefixes** with loop in AS-path + - 5: count of **prefixes** with loop in originator + - 7: count of **routes** in adj-rib-in + - 8: count of **routes** in Loc-RIB + - 11: count of updates subjected to :rfc:`7607` "treat as withdrawal" + handling due to errors + - 65531: *experimental* count of prefixes rejected due to invalid next-hop + + Note that stat items 3, 4 and 5 are specified to count updates, but FRR + implements them as prefix-based counters. + +- **route mirroring** is fully implemented, however BGP OPEN messages are not + currently included in route mirroring messages. Their contents can be + extracted from the "peer up" notification for sessions that established + successfully. OPEN messages for failed sessions cannot currently be + mirrored. + +- **route monitoring** is available for IPv4 and IPv6 AFIs, unicast, multicast, + EVPN and VPN SAFIs. Other SAFIs (VPN, Labeled-Unicast, Flowspec, etc.) are not + currently supported. + +- monitoring peers that have BGP **add-path** enabled on the session will + result in somewhat unpredictable behaviour. Currently, the outcome is: + + - route mirroring functions as intended, messages are copied verbatim + - the add-path ID is never included in route monitoring messages + - if multiple paths were received from a peer, an unpredictable path is + picked and sent on the BMP session. The selection will differ for + pre-policy and post-policy monitoring sessions. + - as long as any path is present, something will be advertised on BMP + sessions. Only after the last path is gone a withdrawal will be sent on + BMP sessions. + - updates to additional paths will trigger BMP route monitoring messages. + There is no guarantee on consistency regarding which path is sent in these + messages. + +- monitoring peers with :rfc:`5549` extended next-hops has not been tested. + +Starting BMP +============ + +BMP is implemented as a loadable module. This means that to use BMP, ``bgpd`` +must be started with the ``-M bmp`` option. It is not possible to enable BMP +if ``bgpd`` was started without this option. + +Configuring BMP +=============== + +All of FRR's BMP configuration options are located inside the +:clicmd:`router bgp ASN` block. Configure BGP first before proceeding to BMP +setup. + +There is one option that applies to the BGP instance as a whole: + +.. clicmd:: bmp mirror buffer-limit(0-4294967294) + + This sets the maximum amount of memory used for buffering BGP messages + (updates, keepalives, ...) for sending in BMP Route Mirroring. + + The buffer is for the entire BGP instance; if multiple BMP targets are + configured they reference the same buffer and do not consume additional + memory. Queue overhead is included in accounting this memory, so the + actual space available for BGP messages is slightly less than the value + configured here. + + If the buffer fills up, the oldest messages are removed from the buffer and + any BMP sessions where the now-removed messages were still pending have + their **entire** queue flushed and a "Mirroring Messages Lost" BMP message + is sent. + + BMP Route Monitoring is not affected by this option. + +All other configuration is managed per targets: + +.. clicmd:: bmp targets NAME + + Create/delete a targets group. As implied by the plural name, targets may + cover multiple outbound active BMP sessions as well as inbound passive + listeners. + + If BMP sessions have the same configuration, putting them in the same + ``bmp targets`` will reduce overhead. + +BMP session configuration +------------------------- + +Inside a ``bmp targets`` block, the following commands control session +establishment: + + +.. clicmd:: bmp connect HOSTNAME port (1-65535) {min-retry MSEC|max-retry MSEC} [source-interface WORD] + + Add/remove an active outbound BMP session. HOSTNAME is resolved via DNS, + if multiple addresses are returned they are tried in nondeterministic + order. Only one connection will be established even if multiple addresses + are returned. ``min-retry`` and ``max-retry`` specify (in milliseconds) + bounds for exponential backoff. ``source-interface`` is the local interface on + which the connection has to bind. + +.. warning:: + + ``ip access-list`` and ``ipv6 access-list`` are checked for outbound + connections resulting from ``bmp connect`` statements. + +.. clicmd:: bmp listener port (1-65535) + + Accept incoming BMP sessions on the specified address and port. You can + use ``0.0.0.0`` and ``::`` to listen on all IPv4/IPv6 addresses. + +.. clicmd:: ip access-list NAME +.. clicmd:: ipv6 access-list NAME + + Restrict BMP sessions to the addresses allowed by the respective access + lists. The access lists are checked for both passive and active BMP + sessions. Changes do not affect currently established sessions. + +BMP data feed configuration +--------------------------- + +The following commands configure what BMP messages are sent on sessions +associated with a particular ``bmp targets``: + +.. clicmd:: bmp stats [interval (100-86400000)] + + Send BMP Statistics (counter) messages at the specified interval (in + milliseconds.) + +.. clicmd:: bmp stats send-experimental + + Send BMP Statistics (counter) messages whose code is defined as + experimental (in the [65531-65534] range). + +.. clicmd:: bmp monitor AFI SAFI + + Perform Route Monitoring for the specified AFI and SAFI. Only IPv4 and + IPv6 are currently valid for AFI. SAFI valid values are currently + unicast, multicast, evpn and vpn. + Other AFI/SAFI combinations may be added in the future. + + All BGP neighbors are included in Route Monitoring. Options to select + a subset of BGP sessions may be added in the future. + +.. clicmd:: bmp mirror + + Perform Route Mirroring for all BGP neighbors. Since this provides a + direct feed of BGP messages, there are no AFI/SAFI options to be + configured. + + All BGP neighbors are included in Route Mirroring. Options to select + a subset of BGP sessions may be added in the future. diff --git a/doc/user/bugs.rst b/doc/user/bugs.rst new file mode 100644 index 0000000..2af9e31 --- /dev/null +++ b/doc/user/bugs.rst @@ -0,0 +1,70 @@ +.. index:: + pair: bug reports; contact + +.. _bug-reports: + +************** +Reporting Bugs +************** + +This file describes the procedure for reporting FRRouting bugs. You are asked +to follow this format when submitting bug reports. + +Bugs submitted with woefully incomplete information will receive little +attention and are likely to be closed. If you hit a suspected bug in an older +version, you may be asked to test with a later version in your environment. + +Often you may be asked for additional information to help solve the bug. Bugs +may be closed after 30 days of non-response to requests to reconfirm or supply +additional information. + +Please report bugs on the project GitHub issue tracker at +https://github.com/frrouting/frr/issues + +Report Format & Requested Information +===================================== + +When reporting a bug, please provide the following information. + +#. Your FRR version if it is a release build, or the commit hash if you built + from source. + +#. If you compiled from source, please provide your ``./configure`` line, + including all option flags. + +#. A full list of the FRR daemons you run. + +#. Your platform name and version, e.g. ``Ubuntu 18.04``. + +#. Problem description. + + - Provide as much information as possible. + - Copy and paste relevant commands and their output to describe your network + setup. + - Topology diagrams are helpful when reporting bugs involving more than one + box. + - Platform routing tables and interface configurations are useful if you are + reporting a routing issue. + + *Please be sure to review the provided information and censor any sensitive + material.* + +#. All FRR configuration files you use. Again, please be sure to censor any + sensitive information. For sensitive v4 / v6 addresses, we ask that you + censor the inner octets; e.g., ``192.XXX.XXX.32/24``. + +#. If you are reporting a crash and have a core file, please supply a stack + trace using GDB: + + :: + + $ gdb exec_file core_file + (gdb) bt . + +#. Run all FRR daemons with full debugging on and send *only* the portion of + logs which are relevant to your problem. + +#. Patches, workarounds, and fixes are always welcome. + +.. seealso:: :ref:`basic-config-commands` + diff --git a/doc/user/conf.py b/doc/user/conf.py new file mode 100644 index 0000000..728f9c9 --- /dev/null +++ b/doc/user/conf.py @@ -0,0 +1,402 @@ +# -*- coding: utf-8 -*- +# +# FRR documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 31 16:00:52 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import re +import pygments +import sphinx +from sphinx.highlighting import lexers + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = "1.0" + +# prolog for various variable substitutions +rst_prolog = "" + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ["sphinx.ext.todo"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst'] +source_suffix = ".rst" + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = u"FRR" +copyright = u"2017, FRR" +author = u"FRR authors" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The short X.Y version. +version = u"?.?" +# The full version, including alpha/beta/rc tags. +release = u"?.?-?" + + +# ----------------------------------------------------------------------------- +# Extract values from codebase for substitution into docs. +# ----------------------------------------------------------------------------- + +# Various installation prefixes. Values are extracted from config.status. +# Reasonable defaults are set in case that file does not exist. +replace_vars = { + "AUTHORS": author, + "COPYRIGHT_YEAR": "1999-2005", + "COPYRIGHT_STR": "Copyright (c) 1999-2005", + "PACKAGE_NAME": project.lower(), + "PACKAGE_TARNAME": project.lower(), + "PACKAGE_STRING": project.lower() + " latest", + "PACKAGE_URL": "https://frrouting.org/", + "PACKAGE_VERSION": "latest", + "INSTALL_PREFIX_ETC": "/etc/frr", + "INSTALL_PREFIX_SBIN": "/usr/lib/frr", + "INSTALL_PREFIX_STATE": "/var/run/frr", + "INSTALL_PREFIX_MODULES": "/usr/lib/frr/modules", + "INSTALL_USER": "frr", + "INSTALL_GROUP": "frr", + "INSTALL_VTY_GROUP": "frrvty", + "GROUP": "frr", + "USER": "frr", +} + +# extract version information, installation location, other stuff we need to +# use when building final documents +val = re.compile('^S\["([^"]+)"\]="(.*)"$') +try: + with open("../../config.status", "r") as cfgstatus: + for ln in cfgstatus.readlines(): + m = val.match(ln) + if not m or m.group(1) not in replace_vars.keys(): + continue + replace_vars[m.group(1)] = m.group(2) +except IOError: + # if config.status doesn't exist, just ignore it + pass + +# manually fill out some of these we can't get from config.status +replace_vars["COPYRIGHT_STR"] = "Copyright (c)" +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["COPYRIGHT_YEAR"]) +replace_vars["COPYRIGHT_STR"] += " {0}".format(replace_vars["AUTHORS"]) +release = replace_vars["PACKAGE_VERSION"] +version = release.split("-")[0] + +# add substitutions to prolog +for key, value in replace_vars.items(): + rst_prolog += ".. |{0}| replace:: {1}\n".format(key, value) + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [ + "_build", + "rpki.rst", + "routeserver.rst", + "ospf_fundamentals.rst", + "flowspec.rst", + "snmptrap.rst", + "wecmp_linkbw.rst", +] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "default" + +try: + import sphinx_rtd_theme + + html_theme = "sphinx_rtd_theme" +except ImportError: + pass + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = { +# 'sidebarbgcolor': '#374249' +# } + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = "../figures/frr-icon.svg" + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = "../figures/frr-logo-icon.png" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = "FRRdoc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "FRR.tex", u"FRR User Manual", u"FRR", "manual"), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = "../figures/frr-logo-medium.png" + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "frr", u"FRR User Manual", [author], 1)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "frr", + u"FRR User Manual", + author, + "FRR", + "One line description of project.", + "Miscellaneous", + ), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False + +# contents of ../extra/frrlexer.py. +# This is read here to support VPATH build. Since this section is execfile()'d +# with the file location, we can safely use a relative path here to save the +# contents of the lexer file for later use even if our relative path changes +# due to VPATH. +with open("../extra/frrlexer.py", "rb") as lex: + frrlexerpy = lex.read() + +# Parse version string into int array +def vparse(s): + a = [] + + for c in s: + if c != ".": + a.append(int(c)) + + while len(a) < 3: + a.append(0) + + return a[:3] + + +# custom extensions here +def setup(app): + # object type for FRR CLI commands, can be extended to document parent CLI + # node later on + app.add_object_type("clicmd", "clicmd", indextemplate="pair: %s; configuration command") + + # I dont care how stupid this is + if "add_js_file" in dir(app): + app.add_js_file("overrides.js") + else: + app.add_javascript("overrides.js") + + if "add_css_file" in dir(app): + app.add_css_file("overrides.css") + else: + app.add_stylesheet("overrides.css") + + + # load Pygments lexer for FRR config syntax + # + # NB: in Pygments 2.2+ this can be done with `load_lexer_from_file`, but we + # do it manually since not all of our supported build platforms have 2.2 + # yet. + # + # frrlexer = pygments.lexers.load_lexer_from_file('../extra/frrlexer.py', lexername="FRRLexer") + custom_namespace = {} + exec(frrlexerpy, custom_namespace) + lexers["frr"] = custom_namespace["FRRLexer"]() diff --git a/doc/user/config-include.rst b/doc/user/config-include.rst new file mode 100644 index 0000000..3a34151 --- /dev/null +++ b/doc/user/config-include.rst @@ -0,0 +1,12 @@ +.. +.. January 12 2024, Christian Hopps +.. +.. Copyright (c) 2024, LabN Consulting, L.L.C. +.. +.. + +Configuration for the daemon should be saved in the FRR integrated configuration +file located in |INSTALL_PREFIX_ETC|/frr.conf, see :ref:`config-file` for more +information on system configuration. + +.. include:: prior-config-files.rst diff --git a/doc/user/eigrpd.rst b/doc/user/eigrpd.rst new file mode 100644 index 0000000..58a2957 --- /dev/null +++ b/doc/user/eigrpd.rst @@ -0,0 +1,196 @@ +.. _eigrp: + +***** +EIGRP +***** + +.. glossary:: + + DUAL + The *Diffusing Update ALgorithm*, a :term:`Bellman-Ford` based routing + algorithm used by EIGRP. + +EIGRP -- Routing Information Protocol is widely deployed interior gateway +routing protocol. EIGRP was developed in the 1990's. EIGRP is a +:term:`distance-vector` protocol and is based on the :term:`DUAL` algorithms. +As a distance-vector protocol, the EIGRP router send updates to its +neighbors as networks change, thus allowing the convergence to a +known topology. + +*eigrpd* supports EIGRP as described in RFC7868 + +.. _starting-and-stopping-eigrpd: + +Starting and Stopping eigrpd +============================ + +.. include:: config-include.rst + +If starting daemons by hand then please note, the EIGRP protocol requires +interface information maintained by *zebra* daemon. So running *zebra* is +mandatory to run *eigrpd*. Thus minimum sequence for running EIGRP is: + +:: + + # zebra -d + # eigrpd -d + +Please note that *zebra* must be invoked before *eigrpd*. + +To stop *eigrpd*, please use:: + + kill `cat /var/run/frr/eigrpd.pid` + +Certain signals have special meanings to *eigrpd*. + ++------------------+-----------------------------------------------------------+ +| Signal | Meaning | ++==================+===========================================================+ +| SIGHUP & SIGUSR1 | Rotate the log file | ++------------------+-----------------------------------------------------------+ +| SIGINT & SIGTERM | Sweep all installed EIGRP routes and gracefully terminate | ++------------------+-----------------------------------------------------------+ + + +*eigrpd* invocation options. Common options that can be specified +(:ref:`common-invocation-options`). + +.. program:: eigrpd + +.. _eigrp-configuration: + +EIGRP Configuration +=================== + +.. clicmd:: router eigrp (1-65535) [vrf NAME] + + The `router eigrp` command is necessary to enable EIGRP. To disable EIGRP, + use the `no router eigrp (1-65535)` command. EIGRP must be enabled before + carrying out any of the EIGRP commands. Specify vrf NAME if you want + eigrp to work within the specified vrf. + +.. clicmd:: network NETWORK + + Set the EIGRP enable interface by `network`. The interfaces which + have addresses matching with `network` are enabled. + + This group of commands either enables or disables EIGRP interfaces between + certain numbers of a specified network address. For example, if the + network for 10.0.0.0/24 is EIGRP enabled, this would result in all the + addresses from 10.0.0.0 to 10.0.0.255 being enabled for EIGRP. The `no + network` command will disable EIGRP for the specified network. + + Below is very simple EIGRP configuration. Interface `eth0` and + interface which address match to `10.0.0.0/8` are EIGRP enabled. + + .. code-block:: frr + + ! + router eigrp 1 + network 10.0.0.0/8 + ! + + +.. clicmd:: passive-interface (IFNAME|default) + + + This command sets the specified interface to passive mode. On passive mode + interface, all receiving packets are ignored and eigrpd does not send either + multicast or unicast EIGRP packets except to EIGRP neighbors specified with + `neighbor` command. The interface may be specified as `default` to make + eigrpd default to passive on all interfaces. + + The default is to be passive on all interfaces. + +.. _how-to-announce-eigrp-route: + +How to Announce EIGRP route +=========================== + +Redistribute routes into EIGRP: + +.. clicmd:: redistribute [metric (1-4294967295) (0-4294967295) (0-255) (1-255) (1-65535)] + + The ``redistribute`` family of commands imports routing information from + other sources into EIGRP's tables. Redistribution may be disabled with the + ``no`` form of the commands. + + Note that connected routes on interfaces EIGRP is enabled on are announced + by default. + + Optionally, various EIGRP metrics may be specified. These metrics will be + applied to the imported routes. + + +.. _show-eigrp-information: + +Show EIGRP Information +====================== + +.. clicmd:: show ip eigrp [vrf NAME] topology + + Display current EIGRP status. + + :: + + eigrpd> **show ip eigrp topology** + # show ip eigrp topo + + EIGRP Topology Table for AS(4)/ID(0.0.0.0) + + Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply + r - reply Status, s - sia Status + + P 10.0.2.0/24, 1 successors, FD is 256256, serno: 0 + via Connected, enp0s3 + +.. clicmd:: show ip eigrp [vrf NAME] interface + + Display the list of interfaces associated with a particular eigrp + instance. + +.. clicmd:: show ip eigrp [vrf NAME] neighbor + + Display the list of neighbors that have been established within + a particular eigrp instance. + +EIGRP Debug Commands +==================== + +Debug for EIGRP protocol. + +.. clicmd:: debug eigrp packets + + Debug eigrp packets + + ``debug eigrp`` will show EIGRP packets that are sent and received. + +.. clicmd:: debug eigrp transmit + + Debug eigrp transmit events + + ``debug eigrp transmit`` will display detailed information about the EIGRP + transmit events. + +.. clicmd:: show debugging eigrp + + Display *eigrpd*'s debugging option. + + ``show debugging eigrp`` will show all information currently set for eigrpd + debug. + + +Sample configuration +==================== + +.. code-block:: frr + + hostname eigrpd + password zebra + enable password please-set-at-here + ! + router eigrp 4453 + network 192.168.1.0/24 + ! + log stdout + diff --git a/doc/user/evpn.rst b/doc/user/evpn.rst new file mode 100644 index 0000000..7c4d9fe --- /dev/null +++ b/doc/user/evpn.rst @@ -0,0 +1,530 @@ +.. _evpn: + +**** +EVPN +**** + +:abbr:`EVPN` stands for Ethernet Virtual Private Network. This is an extension +of BGP that enables the signaling of bridged (L2) and routed (L3) VPNs over a +common network. EVPN is described in :rfc:`7432` and is updated by several +additional RFCs and IETF drafts including :rfc:`9135` (Integrated Routing +and Bridging in Ethernet VPN), :rfc:`9136` (IP Prefix Advertisement in Ethernet +VPN), :rfc:`8584` (Framework for Ethernet VPN Designated Forwarder Election +Extensibility), and :rfc:`8365` (A Network Virtualization Overlay Solution Using +Ethernet VPN). FRR supports All-Active Layer-2 Multihoming for devices (MHD) via +LACP Ethernet Segments as well as both Symmetric and Asymmetric IRB. +FRR implements MAC-VRFs using a "VLAN-Based Service Interface" (:rfc:`7432`) +and performs processing of Symmetric IRB routes following the +"Interface-less IP-VRF-to-IP-VRF Model" (:rfc:`9136`). + +.. _evpn-concepts: + +EVPN Concepts +============= +BGP-EVPN is the control plane for the transport of Ethernet frames, regardless +of whether those frames are bridged or routed. In the case of a VLAN-Based +Service Interface with VXLAN encap, a single VNI is used to represent an EVPN +Instance (EVI) and will have its own Route Distinguisher and set of +Import/Export Route-Targets. + +A VNI is considered to be either Layer-2 (tied to a MAC-VRF) or Layer-3 +(tied to an IP-VRF), which indicates what kind of information is represented by +the VRF. An IP-VRF represents a routing table (operating in much the same way as +a VRF traditionally operates in L3VPN), while a MAC-VRF represents a bridging +table i.e. MAC (fdb) and ARP/NDP entries. + +A MAC-VRF can be thought of as a VLAN with or without an SVI associated with it. +An SVI is a Layer-3 interface bound to a bridging domain. In Linux an SVI can +either be a traditional bridge or a VLAN subinterface of a VLAN-aware bridge. +If there is an SVI for the VLAN, ARP/NDP entries can be bound to the MACs within +the broadcast domain. Without an SVI, the VLAN operates in traditional L2 +fashion and MACs are the only type of host addresses known within the VLAN. + +In the same way that there can be a many-to-one relationship of SVIs to a VRF, +there can also be a many-to-one relationship of MAC-VRFs (L2VNIs) to an IP-VRF +(L3VNI). In FRR the L3VNI association for an L2VNI is determined by the +presence of an SVI for the VLAN and the VRF membership of the SVI. +If an L2VNI does not have an SVI or its SVI is not enslaved to a VRF, the L2VNI +will be associated with the "default" VRF. If an L2VNI has an SVI whose master +device is a VRF, then that L2VNI will be associated with its master VRF. + +.. _evpn-frr-configuration: + +FRR Configuration +================= +FRR learns about the system's Linux network interface configuration from the +kernel via Netlink, however it does not manage network interfaces directly. +The following sections will include examples of Linux interface configurations +that are compatible with FRR's EVPN implementation. While there are multiple +interface managers that can setup a proper kernel config (e.g. ifupdown2), +these examples will use iproute2 to add/configure the interfaces. + +All of the examples will follow the same basic setup but use different, yet +compatible, interface configurations. + +In this example we will setup the following: + +* An IP-VRF named vrf1, associated with L3VNI 100 +* An IP-VRF named vrf2, associated with L3VNI 200 +* An IP-VRF named vrf3, with no L3VNI associations +* A MAC-VRF using VLAN 10, associated with L2VNI 110 and IP-VRF vrf1 +* A MAC-VRF using VLAN 20, associated with L2VNI 220 and IP-VRF vrf2 +* A MAC-VRF using VLAN 30, associated with L2VNI 330 and IP-VRF vrf3 +* A MAC-VRF using VLAN 40, associated with L2VNI 440 and IP-VRF default +* A MAC-VRF using VLAN 50, associated with L2VNI 550 and operating L2-Only + +.. _evpn-sample-configuration: + +Sample Configuration +-------------------- +This is a sample FRR configuration that implements the above EVPN environment. +The first snippet will be the config in its entiretly, then each config element +will be explained individually later in the document. + +The following snippet will result in a functional EVPN control plane if the +corresponding Linux interface configuration is correct, compatible, and active: + +.. code-block:: frr + + vrf vrf1 + vni 100 + exit-vrf + ! + vrf vrf2 + vni 200 + exit-vrf + ! + router bgp 4200000000 + neighbor 192.168.122.12 remote-as internal + ! + address-family ipv4 unicast + network 100.64.0.1/32 + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.122.12 activate + advertise-all-vni + advertise-svi-ip + exit-address-family + exit + ! + router bgp 4200000000 vrf vrf1 + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family + exit + ! + router bgp 4200000000 vrf vrf2 + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family + exit + +A VRF will get its L3VNI association as a result of the ``vni`` command under +the ``vrf`` stanza. Until this L3VNI association is made, zebra will discover +the VNI from netlink but will consider it to be an L2VNI. The current L2 vs L3 +context of a VNI can be seen in the output of ``show evpn vni``. + +In this configuration we are telling zebra to consider VXLAN-ID 100 to be the +L3VNI for vrf1 and VXLAN-ID 200 to be the L3VNI for vrf2. + +.. code-block:: frr + + vrf vrf1 + vni 100 + exit-vrf + ! + vrf vrf2 + vni 200 + exit-vrf + +The VTEP-IP (100.64.0.1) needs to be reachable by other VTEPs in the EVPN +environment in order for VXLAN decapsulation to function. In this example we +will advertise our local VTEP-IP using BGP (via the ``network`` statement), but +static routes or other routing protocols like IS-IS or OSPF can also be used. + +In order to enable EVPN for a BGP instance, we must use the command +``advertise-all-vni``. In this example we will be using the default VRF to +carry the l2vpn evpn address-family, so we will enable EVPN for the default VRF. + +In this example, we plan to exchange EVPN routes with 192.168.122.12, so we +will activate the l2vpn evpn address-family for this peer in order to allow +EVPN NLRI to be advertised and received. + +The ``advertise-svi-ip`` command also belongs in the BGP instance where EVPN is +enabled. This command tells FRR to originate "self" Type-2 routes for all the +MAC/IP pairs associated with the local SVI interfaces. + +.. code-block:: frr + + router bgp 4200000000 + neighbor 192.168.122.12 remote-as internal + ! + address-family ipv4 unicast + network 100.64.0.1/32 + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.122.12 activate + advertise-all-vni + advertise-svi-ip + exit-address-family + exit + +IPv4 and IPv6 BGP Prefixes from an IP-VRF are not exported to EVPN as Type-5 +routes until the respective ``advertise unicast`` command has been +configured in the BGP instance of the VRF in question. All routes in the BGP +RIB (locally originated, learned from a peer, or leaked from another VRF) will +be eligible to be exported to EVPN so long as they are valid and selected in +the VRF's unicast table. + +In this example, the BGP instances for vrf1 and vrf2 will have their static +routes redistributed into the BGP loc-rib for the ipv4 unicast and ipv6 unicast +address-families via the ``redistribute static`` statements. These unicast +prefixes will then be exported into EVPN as Type-5 routes as a result of the +``advertise ipv4 unicast`` and ``advertise ipv6 unicast`` commands. + +.. code-block:: frr + + router bgp 4200000000 vrf vrf1 + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family + exit + ! + router bgp 4200000000 vrf vrf2 + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + advertise ipv6 unicast + exit-address-family + exit + +.. _evpn-linux-interface-configuration: + +Linux Interface Configuration +============================= +The Linux kernel offers several options for configuring netdevices for an +EVPN-VXLAN environment. The following section will include samples of a few +netdev configurations that are compatible with FRR which implement the +environment described above. + +Some high-level config considerations: + +* The local VTEP-IP should always be set to a reachable IP on the lo device. +* An L3VNI should always have an SVI (aka the L3-SVI). +* An L3-SVI should not be assigned an IP address, link-local or otherwise. + + * IPv6 address autoconfiguration can be disabled via ``addrgenmode none``. + +* An SVI for an L2VNI is only needed for routing (IRB) or ARP/ND suppression. + + * ARP/ND suppression is a kernel function, it is not managed by FRR. + * ARP/ND suppression is enabled per bridge_slave via ``neigh_suppress``. + * ARP/ND suppression should only be enabled on vxlan interfaces. + * IPv4/IPv6 forwarding should be disabled on SVIs not used for routing (IRB). + +* Dynamic MAC/VTEP learning should be disabled on VXLAN interfaces used in EVPN. + + * Dynamic MAC learning is a function of the kernel bridge driver, not FRR. + * Dynamic MAC learning is toggled per bridge_slave via ``learning {on|off}``. + * Dynamic VTEP learning is a function of the kernel vxlan driver, not FRR. + * Dynamic VTEP learning is toggled per vxlan interface via ``[no]learning``. + +* The VXLAN interfaces should not have a ``remote`` VTEP defined. + + * Remote VTEPs are learned via EVPN, so static VTEPs are unnecessary. + +.. _evpn-traditional-bridge-traditional-vxlan-devices: + +Traditional Bridges and Traditional VXLAN Devices +------------------------------------------------- +In the traditional bridge model, we use a separate ``bridge`` interface per +MAC-VRF which acts as the SVI for that broadcast domain. A bridge is considered +"traditional" if ``vlan_filtering`` is set to ``0`` (disabled) which indicates +the bridge only has one broadcast domain which does not consider VLAN tags. +Similarly, only one VNI is carried by each "traditional" ``vxlan`` interface. +So in this deployment model, each VXLAN-enabled broadcast domain will have one +traditional vxlan interface enslaved to one traditional bridge. + +Bridges created for an L3VNI broadcast domain should only have one member: the +L3VNI vxlan device. Bridges created for an L2VNI broadcast domain generally +have multiple members: the L2VNI vxlan device, plus any host/network ports +where the L2 domain will be carried. + +To carry the broadcast domains of multiple traditional bridges over the same +host/network port, a tagged ``vlan`` sub-interface of the port must be created +per broadcast domain. The vlan sub-interfaces would then be enslaved to the +traditional bridge, ensuring that only packets tagged with the expected VID are +associated with the expected broadcast domain. + +.. code-block:: shell + + ################### + ## vxlan vtep-ip ## + ################### + ip addr add 100.64.0.1/32 dev lo + + ############################# + ## ip-vrf vrf1 / l3vni 100 ## + ############################# + ip link add vrf1 type vrf table 1100 + ip link set vrf1 up + ip link add br100 type bridge + ip link set br100 master vrf1 addrgenmode none + ip link set br100 addr aa:bb:cc:00:00:64 + ip link add vni100 type vxlan local 100.64.0.1 dstport 4789 id 100 nolearning + ip link set vni100 master br100 addrgenmode none + ip link set vni100 type bridge_slave neigh_suppress on learning off + ip link set vni100 up + ip link set br100 up + + ############################# + ## ip-vrf vrf2 / l3vni 200 ## + ############################# + ip link add vrf2 type vrf table 1200 + ip link set vrf2 up + ip link add br200 type bridge + ip link set br200 master vrf2 addrgenmode none + ip link set br200 addr aa:bb:cc:00:00:c8 + ip link add vni200 type vxlan local 100.64.0.1 dstport 4789 id 200 nolearning + ip link set vni200 master br200 addrgenmode none + ip link set vni200 type bridge_slave neigh_suppress on learning off + ip link set vni200 up + ip link set br200 up + + ################# + ## ip-vrf vrf3 ## + ################# + ip link add vrf3 type vrf table 1300 + ip link set vrf3 up + + ############### + ## l2vni 110 ## + ############### + ip link add br10 type bridge + ip link set br10 master vrf1 + ip link set br10 addr aa:bb:cc:00:00:6e + ip addr add 10.0.10.1/24 dev br10 + ip addr add 2001:db8:0:10::1/64 dev br10 + ip link add vni110 type vxlan local 100.64.0.1 dstport 4789 id 110 nolearning + ip link set vni110 master br10 addrgenmode none + ip link set vni110 type bridge_slave neigh_suppress on learning off + ip link set vni110 up + ip link set br10 up + + ############### + ## l2vni 220 ## + ############### + ip link add br20 type bridge + ip link set br20 master vrf2 + ip link set br20 addr aa:bb:cc:00:00:dc + ip addr add 10.0.20.1/24 dev br20 + ip addr add 2001:db8:0:20::1/64 dev br20 + ip link add vni220 type vxlan local 100.64.0.1 dstport 4789 id 220 nolearning + ip link set vni220 master br20 addrgenmode none + ip link set vni220 type bridge_slave neigh_suppress on learning off + ip link set vni220 up + ip link set br20 up + + ############### + ## l2vni 330 ## + ############### + ip link add br30 type bridge + ip link set br30 master vrf3 + ip link set br30 addr aa:bb:cc:00:01:4a + ip addr add 10.0.30.1/24 dev br30 + ip addr add 2001:db8:0:30::1/64 dev br30 + ip link add vni330 type vxlan local 100.64.0.1 dstport 4789 id 330 nolearning + ip link set vni330 master br30 addrgenmode none + ip link set vni330 type bridge_slave neigh_suppress on learning off + ip link set vni330 up + ip link set br30 up + + ############### + ## l2vni 440 ## + ############### + ip link add br40 type bridge + ip link set br40 addr aa:bb:cc:00:01:b8 + ip addr add 10.0.40.1/24 dev br40 + ip addr add 2001:db8:0:40::1/64 dev br40 + ip link add vni440 type vxlan local 100.64.0.1 dstport 4789 id 440 nolearning + ip link set vni440 master br40 addrgenmode none + ip link set vni440 type bridge_slave neigh_suppress on learning off + ip link set vni440 up + ip link set br40 up + + ############### + ## l2vni 550 ## + ############### + ip link add br50 type bridge + ip link set br50 addrgenmode none + ip link set br50 addr aa:bb:cc:00:02:26 + ip link add vni550 type vxlan local 100.64.0.1 dstport 4789 id 550 nolearning + ip link set vni550 master br50 addrgenmode none + ip link set vni550 type bridge_slave neigh_suppress on learning off + sysctl -w net.ipv4.conf.br50.forwarding=0 + sysctl -w net.ipv6.conf.br50.forwarding=0 + ip link set vni550 up + ip link set br50 up + + ################## + ## create vlan subinterface of eth0 for each l2vni vlan and enslave each + ## subinterface to the corresponding bridge + ################## + ip link set eth0 up + for i in 10 20 30 40 50; do + ip link add link eth0 name eth0.$i type vlan id $i; + ip link set eth0.$i master br$i; + ip link set eth0.$i up; + done + + +To begin with, it creates a ``vrf`` interface named "vrf1" that is bound to the +kernel routing table with ID 1100. This will represent the IP-VRF "vrf1" which +we will later allocate an L3VNI for. + +.. code-block:: shell + + ip link add vrf1 type vrf table 1100 + +This block creates a traditional ``bridge`` interface named "br100", binds it to +the VRF named "vrf1", disables IPv6 address autoconfiguration, and statically +defines the MAC address of "br100". This traditional bridge is used for the +L3VNI broadcast domain mapping to VRF "vrf1", i.e. "br100" is vrf1's L3-SVI. + +.. code-block:: shell + + ip link add br100 type bridge + ip link set br100 master vrf1 addrgenmode none + ip link set br100 addr aa:bb:cc:00:00:64 + +Here a traditional ``vxlan`` interface is created with the name "vni100" which +uses a VTEP-IP of 100.64.0.1, carries VNI 100, and has Dynamic VTEP learning +disabled. IPv6 address autoconfiguration is disabled for "vni100", then the +interface is enslaved to "br100", ARP/ND suppression is enabled, and Dynamic +MAC Learning is disabled. + +.. code-block:: shell + + ip link add vni100 type vxlan local 100.64.0.1 dstport 4789 id 100 nolearning + ip link set vni100 master br100 addrgenmode none + ip link set vni100 type bridge_slave neigh_suppress on learning off + +This completes the necessary configuration for a VRF and L3VNI. + +Here a traditional bridge named "br10" is created. We add "br10" to "vrf1" by +setting "vrf1" as the ``master`` of "br10". It is not necessary to set the SVI +MAC statically, but it is done here for consistency's sake. Since "br10" will +be used for routing, IPv4 and IPv6 addresses are also added to the SVI. + +.. code-block:: shell + + ip link add br10 type bridge + ip link set br10 master vrf1 + ip link set br10 addr aa:bb:cc:00:00:6e + ip addr add 10.0.10.1/24 dev br10 + ip addr add 2001:db8:0:10::1/64 dev br10 + +If the SVI will not be used for routing, IP addresses should not be assigned to +the SVI interface and IPv4/IPv6 "forwarding" should be disabled for the SVI via +the appropriate sysctl nodes. + +.. code-block:: shell + + sysctl -w net.ipv4.conf..forwarding=0 + sysctl -w net.ipv6.conf..forwarding=0 + +The following commands create a ``vxlan`` interface for VNI 100. Other than the +VNI, The interface settings are the same for an L2VNI as they are for an L3VNI. + +.. code-block:: shell + + ip link add vni110 type vxlan local 100.64.0.1 dstport 4789 id 110 nolearning + ip link set vni110 master br10 addrgenmode none + ip link set vni110 type bridge_slave neigh_suppress on learning off + +Finally, to limit a traditional bridge's broadcast domain to traffic matching +specific VLAN-IDs, ``vlan`` subinterfaces of a host/network port need to be +setup. This example shows the creation of a VLAN subinterface of "eth0" +matching VID 10 with the name "eth0.10". By enslaving "eth0.10" to "br10" +(instead of "eth0") we ensure that only Ethernet frames ingressing "eth0" +tagged with VID 10 will be associated with the "br10" broadcast domain. + +.. code-block:: shell + + ip link add link eth0 name eth0.10 type vlan id 10 + ip link set eth0.10 master br10 + +If you do not want to restrict the broadcast domain by VLAN-ID, you can skip +the creation of the VLAN subinterfaces and directly enslave "eth0" to "br10". + +.. code-block:: shell + + ip link set eth0 master br10 + +This completes the necessary configuration for an L2VNI. + +Displaying EVPN information +--------------------------- + +.. clicmd:: show evpn mac vni (1-16777215) detail [json] + + Display detailed information about MAC addresses for + a specified VNI. + +.. clicmd:: show vrf [] vni [json] + + Displays VRF to L3VNI mapping. It also displays L3VNI associated + router-mac, svi interface and vxlan interface. + User can get that information as JSON format when ``json`` keyword + at the end of cli is presented. + + .. code-block:: frr + + tor2# show vrf vni + VRF VNI VxLAN IF L3-SVI State Rmac + sym_1 9288 vxlan21 vlan210_l3 Up 21:31:36:ff:ff:20 + sym_2 9289 vxlan21 vlan210_l3 Up 21:31:36:ff:ff:20 + sym_3 9290 vxlan21 vlan210_l3 Up 21:31:36:ff:ff:20 + tor2# show vrf sym_1 vni + VRF VNI VxLAN IF L3-SVI State Rmac + sym_1 9288 vxlan21 vlan210_l3 Up 44:38:36:ff:ff:20 diff --git a/doc/user/extlog.rst b/doc/user/extlog.rst new file mode 100644 index 0000000..bb872c6 --- /dev/null +++ b/doc/user/extlog.rst @@ -0,0 +1,189 @@ +.. _ext-log-target: + +*********************** +Extended Logging Target +*********************** + +After creating one or more extended logging targets with the +:clicmd:`log extended EXTLOGNAME` command, the target(s) must be configured +for the desired logging output. + +Each extended log target supports emitting log messages in one of the following +formats: + +- ``rfc5424`` - :rfc:`5424` - modern syslog with ISO 8601 timestamps, time zone and + structured data (key/value pairs) support +- ``rfc3164`` - :rfc:`3164` - legacy BSD syslog, timestamps with 1 second granularity +- ``local-syslog`` - same as :rfc:`3164`, but without the hostname field +- ``journald`` - systemd's `native journald protocol `_. + This protocol also supports structured data (key/value pairs). + +Destinations +------------ + +The output location is configured with the following subcommands: + +.. clicmd:: destination none + + Disable the target while retaining its remaining configuration. + +.. clicmd:: destination syslog [supports-rfc5424] + + Send log messages to the system's standard log destination + (``/dev/log``). This does not use the C library's ``syslog()`` function, + instead writing directly to ``/dev/log``. + + On NetBSD and FreeBSD, the RFC5424 format is automatically used when + the OS version is recent enough (5.0 for NetBSD, 12.0 for FreeBSD). + Unfortunately, support for this format cannot be autodetected otherwise, + and particularly on Linux systems must be enabled manually. + +.. clicmd:: destination journald + + Send log messages to systemd's journald. + +.. clicmd:: destination > \ + [format FORMAT] + + Send log messages to one of the daemon's file descriptors. The + ``fd (0-63)`` and ``fd envvar WORD`` variants are intended to work with + the shell's ``command 3>something`` and bash's + ``command {ENVVAR}>something`` I/O redirection specifiers. + + Only file descriptors open at a daemon's startup time can be used for + this; accidental misuse of a file descriptor that has been opened by + FRR itself is prevented. + + Using FIFOs with this option will work but is unsupported and can cause + daemons to hang or crash depending on reader behavior. + + Format defaults to RFC5424 if none is specified. + + .. note:: + + When starting FRR daemons from custom shell scripts, make sure not + to leak / leave extraneous file descriptors open. FRR daemons do not + close these. + +.. clicmd:: destination file PATH \ + [create [{user WORD|group WORD|mode PERMS}]|no-create] \ + [format FORMAT] + + Log to a regular file. File permissions can be specified when FRR creates + the file itself. + + Format defaults to RFC5424 if none is specified. + + .. note:: + + FRR will never change permissions or ownership on an existing log file. + In many cases, FRR will also not have permissions to set user and group + arbitrarily. + +.. clicmd:: destination unix PATH [format FORMAT] + + Connect to a UNIX domain socket and send log messages there. This will + autodetect ``SOCK_STREAM``, ``SOCK_SEQPACKET`` and ``SOCK_DGRAM`` and + adjust behavior appropriately. + +Options +------- + +.. clicmd:: priority PRIORITY + + Select minimum priority of messages to send to this target. Defaults to + `debugging`. + +.. clicmd:: facility FACILITY + + Select syslog facility for messages on this target. Defaults to `daemon`. + The :clicmd:`log facility [FACILITY]` command does not affect extended + targets. + +.. clicmd:: timestamp precision (0-9) + + Set desired number of sub-second timestamp digits. This only has an effect + for RFC5424 and journald format targets; the RFC3164 and local-syslogd + formats do not support any sub-second digits. + +.. clicmd:: timestamp local-time + + Use the local system timezone for timestamps rather than UTC (the default.) + + RFC5424 and journald formats include zone information (``Z`` or ``+-NN:NN`` + suffix in ISO8601). RFC3164 and local-syslogd offer no way of identifying + the time zone used, care must be taken that this option and the receiver + are configured identically, or the timestamp is replaced at the receiver. + + .. note:: + + FRR includes a timestamp in journald messages, but journald always + provides its own timestamp. + +.. clicmd:: structured-data + + Select additional key/value data to be included for the RFC5424 and journald + formats. Refer to the next section for details. + + ``unique-id`` and ``error-category`` are enabled by default. + + .. warning:: + + Log messages can grow in size significantly when enabling additional + data. + + +Structured data +--------------- + +When using the RFC5424 or journald formats, FRR can provide additional metadata +for log messages as key/value pairs. The following information can be added +in this way: + ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| Switch | 5424 group | 5424 item(s) | journald field | Contents | ++====================+====================+==============+==================+=============================================+ +| always active | ``location@50145`` | ``tid`` | ``TID`` | Thread ID | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| always active | ``location@50145`` | ``instance`` | ``FRR_INSTANCE`` | Multi-instance number | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``unique-id`` | ``location@50145`` | ``id`` | ``FRR_ID`` | ``XXXXX-XXXXX`` unique message identifier | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``error-category`` | ``location@50145`` | ``ec`` | ``FRR_EC`` | Integer error category number | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``code-location`` | ``location@50145`` | ``file`` | ``CODE_FILE`` | Source code file name | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``code-location`` | ``location@50145`` | ``line`` | ``CODE_LINE`` | Source code line number | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``code-location`` | ``location@50145`` | ``func`` | ``CODE_FUNC`` | Source code function name | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``format-args`` | ``args@50145`` | ``argN`` | ``FRR_ARGn`` | Message printf format arguments (n = 1..16) | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ +| ``version`` | ``origin`` | multiple | n/a | FRR version information (IETF format) | ++--------------------+--------------------+--------------+------------------+---------------------------------------------+ + +The information added by ``version`` is +``[origin enterpriseId="50145" software="FRRouting" swVersion="..."]`` +and is the same for all log messages. (Hence makes little sense to include in +most scenarios.) 50145 is the FRRouting IANA Enterprise Number. + +Crashlogs / backtraces do not include any additional information since it +cannot safely be retrieved from a crash handler. However, all of the above +destinations will deliver crashlogs. + + +Restart and Reconfiguration caveats +----------------------------------- + +FRR uses "add-delete" semantics when reconfiguring log targets of any type +(including both extended targets mentioned here as well as the global +:clicmd:`log stdout LEVEL` and :clicmd:`log syslog [LEVEL]` variants.) This +means that when changing logging configuration, log messages from threads +executing in parallel may be duplicated for a brief window of time. + +For the ``unix``, ``syslog`` and ``journald`` extended destinations, messages +can be lost when the receiver is restarted without the use of socket +activation (i.e. keeping the receiver socket open.) FRR does not buffer +log messages for later delivery, meaning anything logged while the receiver +is unavailable is lost. Since systemd provides socket activation for +journald, no messages will be lost on the ``journald`` target. diff --git a/doc/user/fabricd.rst b/doc/user/fabricd.rst new file mode 100644 index 0000000..48d264f --- /dev/null +++ b/doc/user/fabricd.rst @@ -0,0 +1,304 @@ +.. _fabricd: + +********** +OpenFabric +********** + +OpenFabric, specified in :t:`draft-white-openfabric-06.txt`, is a routing +protocol derived from IS-IS, providing link-state routing with efficient +flooding for topologies like spine-leaf networks. + +FRR implements OpenFabric in a daemon called *fabricd* + +.. _configuring-fabricd: + +Configuring fabricd +=================== + +There are no *fabricd* specific options. Common options can be specified +(:ref:`common-invocation-options`) to *fabricd*. *fabricd* needs to acquire +interface information from *zebra* in order to function. Therefore *zebra* must +be running before invoking *fabricd*. Also, if *zebra* is restarted then *fabricd* +must be too. + +Like other daemons, *fabricd* configuration is done in an OpenFabric specific +configuration file :file:`fabricd.conf`. + +.. _openfabric-router: + +OpenFabric router +================= + +To enable the OpenFabric routing protocol, an OpenFabric router needs to be created +in the configuration: + +.. clicmd:: router openfabric WORD + + Enable or disable the OpenFabric process by specifying the OpenFabric domain with + 'WORD'. + +.. clicmd:: net XX.XXXX. ... .XXX.XX + + Set/Unset network entity title (NET) provided in ISO format. + +.. clicmd:: domain-password [clear | md5] + + Configure the authentication password for a domain, as clear text or md5 one. + +.. clicmd:: attached-bit [receive ignore | send] + + Set attached bit for inter-area traffic: + + - receive + If LSP received with attached bit set, create default route to neighbor + - send + If L1|L2 router, set attached bit in LSP sent to L1 router + +.. clicmd:: log-adjacency-changes + + Log changes in adjacency state. + +.. clicmd:: set-overload-bit + + Set overload bit to avoid any transit traffic. + +.. clicmd:: purge-originator + + + Enable or disable :rfc:`6232` purge originator identification. + +.. clicmd:: fabric-tier (0-14) + + + Configure a static tier number to advertise as location in the fabric + +.. _openfabric-timer: + +OpenFabric Timer +================ + +.. clicmd:: lsp-gen-interval (1-120) + + + Set minimum interval in seconds between regenerating same LSP. + +.. clicmd:: lsp-refresh-interval (1-65235) + + + Set LSP refresh interval in seconds. + +.. clicmd:: max-lsp-lifetime (360-65535) + + + Set LSP maximum LSP lifetime in seconds. + +.. clicmd:: spf-interval (1-120) + + + Set minimum interval between consecutive SPF calculations in seconds. + +.. _openfabric-interface: + +OpenFabric interface +==================== + +.. clicmd:: ip router openfabric WORD + + +.. _ip-router-openfabric-word: + + Activate OpenFabric on this interface. Note that the name + of OpenFabric instance must be the same as the one used to configure the + routing process (see command :clicmd:`router openfabric WORD`). + +.. clicmd:: openfabric csnp-interval (1-600) + + + Set CSNP interval in seconds. + +.. clicmd:: openfabric hello-interval (1-600) + + + Set Hello interval in seconds. + +.. clicmd:: openfabric hello-multiplier (2-100) + + + Set multiplier for Hello holding time. + +.. clicmd:: openfabric metric (0-16777215) + + + Set interface metric value. + +.. clicmd:: openfabric passive + + + Configure the passive mode for this interface. + +.. clicmd:: openfabric password [clear | md5] + + + Configure the authentication password (clear or encoded text) for the + interface. + +.. clicmd:: openfabric psnp-interval (1-120) + + + Set PSNP interval in seconds. + +.. _showing-openfabric-information: + +Showing OpenFabric information +============================== + +.. clicmd:: show openfabric summary + + Show summary information about OpenFabric. + +.. clicmd:: show openfabric hostname + + Show which hostnames are associated with which OpenFabric system ids. + +.. clicmd:: show openfabric interface + +.. clicmd:: show openfabric interface detail + +.. clicmd:: show openfabric interface + + Show state and configuration of specified OpenFabric interface, or all interfaces + if no interface is given with or without details. + +.. clicmd:: show openfabric neighbor + +.. clicmd:: show openfabric neighbor + +.. clicmd:: show openfabric neighbor detail + + Show state and information of specified OpenFabric neighbor, or all neighbors if + no system id is given with or without details. + +.. clicmd:: show openfabric database + +.. clicmd:: show openfabric database [detail] + +.. clicmd:: show openfabric database [detail] + +.. clicmd:: show openfabric database detail + + Show the OpenFabric database globally, for a specific LSP id without or with + details. + +.. clicmd:: show openfabric topology + + Show calculated OpenFabric paths and associated topology information. + +.. _debugging-openfabric: + +Debugging OpenFabric +==================== + +.. clicmd:: debug openfabric adj-packets + + OpenFabric Adjacency related packets. + +.. clicmd:: debug openfabric checksum-errors + + OpenFabric LSP checksum errors. + +.. clicmd:: debug openfabric events + + OpenFabric Events. + +.. clicmd:: debug openfabric local-updates + + OpenFabric local update packets. + +.. clicmd:: debug openfabric lsp-gen + + Generation of own LSPs. + +.. clicmd:: debug openfabric lsp-sched + + Debug scheduling of generation of own LSPs. + +.. clicmd:: debug openfabric packet-dump + + OpenFabric packet dump. + +.. clicmd:: debug openfabric protocol-errors + + OpenFabric LSP protocol errors. + +.. clicmd:: debug openfabric route-events + + OpenFabric Route related events. + +.. clicmd:: debug openfabric snp-packets + + OpenFabric CSNP/PSNP packets. + +.. clicmd:: debug openfabric spf-events + +.. clicmd:: debug openfabric spf-statistics + +.. clicmd:: debug openfabric spf-triggers + + OpenFabric Shortest Path First Events, Timing and Statistic Data and + triggering events. + +.. clicmd:: debug openfabric update-packets + + Update-related packets. + +.. clicmd:: show debugging openfabric + + Print which OpenFabric debug levels are active. + +Sample configuration +==================== + +A simple example: + +.. code-block:: frr + + ! + interface lo + ip address 192.0.2.1/32 + ip router openfabric 1 + ipv6 address 2001:db8::1/128 + ipv6 router openfabric 1 + ! + interface eth0 + ip router openfabric 1 + ipv6 router openfabric 1 + ! + interface eth1 + ip router openfabric 1 + ipv6 router openfabric 1 + ! + router openfabric 1 + net 49.0000.0000.0001.00 + + +Alternative example: + +.. code-block:: frr + + hostname fabricd + + router openfabric DEAD + net 47.0023.0000.0003.0300.0100.0102.0304.0506.00 + lsp-lifetime 65535 + + hostname isisd-router + domain-password foobar + + interface eth0 + ip router openfabric DEAD + openfabric hello-interval 5 + openfabric lsp-interval 1000 + + ! -- optional + openfabric retransmit-interval 10 + openfabric retransmit-throttle-interval diff --git a/doc/user/filter.rst b/doc/user/filter.rst new file mode 100644 index 0000000..c1146e5 --- /dev/null +++ b/doc/user/filter.rst @@ -0,0 +1,173 @@ +********* +Filtering +********* + +FRR provides many very flexible filtering features. Filtering is used +for both input and output of the routing information. Once filtering is +defined, it can be applied in any direction. + +IP Access List +============== + +.. clicmd:: access-list NAME [seq (1-4294967295)] permit IPV4-NETWORK + +.. clicmd:: access-list NAME [seq (1-4294967295)] deny IPV4-NETWORK + + seq + seq `number` can be set either automatically or manually. In the + case that sequential numbers are set manually, the user may pick any + number less than 4294967295. In the case that sequential number are set + automatically, the sequential number will increase by a unit of five (5) + per list. If a list with no specified sequential number is created + after a list with a specified sequential number, the list will + automatically pick the next multiple of five (5) as the list number. + For example, if a list with number 2 already exists and a new list with + no specified number is created, the next list will be numbered 5. If + lists 2 and 7 already exist and a new list with no specified number is + created, the new list will be numbered 10. + + Basic filtering is done by `access-list` as shown in the + following example. + + .. code-block:: frr + + access-list filter deny 10.0.0.0/9 + access-list filter permit 10.0.0.0/8 + access-list filter seq 13 permit 10.0.0.0/7 + +.. clicmd:: show access-list [json] + + Display all IPv4 or IPv6 access lists. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show access-list WORD [json] + + Display the specified IPv4 or IPv6 access list. + + If the ``json`` option is specified, output is displayed in JSON format. + + +IP Prefix List +============== + +*ip prefix-list* provides the most powerful prefix based +filtering mechanism. In addition to *access-list* functionality, +*ip prefix-list* has prefix length range specification and +sequential number specification. You can add or delete prefix based +filters to arbitrary points of prefix-list using sequential number specification. + +If no ip prefix-list is specified, it acts as permit. If *ip prefix-list* +is defined, and no match is found, default deny is applied. + +.. clicmd:: ip prefix-list NAME (permit|deny) PREFIX [le LEN] [ge LEN] + +.. clicmd:: ip prefix-list NAME seq NUMBER (permit|deny) PREFIX [le LEN] [ge LEN] + + You can create *ip prefix-list* using above commands. + + seq + seq `number` can be set either automatically or manually. In the + case that sequential numbers are set manually, the user may pick any + number less than 4294967295. In the case that sequential number are set + automatically, the sequential number will increase by a unit of five (5) + per list. If a list with no specified sequential number is created + after a list with a specified sequential number, the list will + automatically pick the next multiple of five (5) as the list number. + For example, if a list with number 2 already exists and a new list with + no specified number is created, the next list will be numbered 5. If + lists 2 and 7 already exist and a new list with no specified number is + created, the new list will be numbered 10. + + le + Specifies prefix length. The prefix list will be applied if the prefix + length is less than or equal to the le prefix length. + + ge + Specifies prefix length. The prefix list will be applied if the prefix + length is greater than or equal to the ge prefix length. + + + Less than or equal to prefix numbers and greater than or equal to + prefix numbers can be used together. The order of the le and ge + commands does not matter. + + If a prefix list with a different sequential number but with the exact + same rules as a previous list is created, an error will result. + However, in the case that the sequential number and the rules are + exactly similar, no error will result. + + If a list with the same sequential number as a previous list is created, + the new list will overwrite the old list. + + Matching of IP Prefix is performed from the smaller sequential number to the + larger. The matching will stop once any rule has been applied. + + In the case of no le or ge command, the prefix length must match exactly the + length specified in the prefix list. + + +.. _ip-prefix-list-description: + +ip prefix-list description +-------------------------- + +.. clicmd:: ip prefix-list NAME description DESC + + Descriptions may be added to prefix lists. This command adds a + description to the prefix list. + + +.. _showing-ip-prefix-list: + +Showing ip prefix-list +---------------------- + +.. clicmd:: show ip prefix-list [json] + + Display all IP prefix lists. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show ip prefix-list NAME [json] + + Show IP prefix list can be used with a prefix list name. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show ip prefix-list NAME seq NUM [json] + + Show IP prefix list can be used with a prefix list name and sequential + number. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show ip prefix-list NAME A.B.C.D/M + + If the command longer is used, all prefix lists with prefix lengths equal to + or longer than the specified length will be displayed. If the command first + match is used, the first prefix length match will be displayed. + +.. clicmd:: show ip prefix-list NAME A.B.C.D/M longer +.. clicmd:: show ip prefix-list NAME A.B.C.D/M first-match +.. clicmd:: show ip prefix-list summary [json] +.. clicmd:: show ip prefix-list summary NAME [json] +.. clicmd:: show ip prefix-list detail [json] +.. clicmd:: show ip prefix-list detail NAME [json] + +.. clicmd:: debug prefix-list NAME match [address-mode] + + Execute the prefix list matching code for the specified list and prefix. + Shows which entry matched, if any. (``address-mode`` is used for + PIM RP lookups and skips prefix length checks.) + + The return value from this command is success only if the prefix-list + result is to permit the prefix, so the command can be used in scripting. + +Clear counter of ip prefix-list +------------------------------- + +.. clicmd:: clear ip prefix-list [NAME [A.B.C.D/M]] + + Clears the counters of all IP prefix lists. Clear IP Prefix List can be used + with a specified NAME or NAME and prefix. diff --git a/doc/user/flowspec.rst b/doc/user/flowspec.rst new file mode 100644 index 0000000..faf5973 --- /dev/null +++ b/doc/user/flowspec.rst @@ -0,0 +1,376 @@ +.. _flowspec: + +Flowspec +======== + +.. _features-of-the-current-implementation-flowspec: + +Overview +--------- + +Flowspec introduces a new :abbr:`NLRI (Network Layer Reachability Information)` +encoding format that is used to distribute traffic rule flow specifications. +Basically, instead of simply relying on destination IP address for IP prefixes, +the IP prefix is replaced by a n-tuple consisting of a rule. That rule can be a +more or less complex combination of the following: + + +- Network source/destination (can be one or the other, or both). +- Layer 4 information for UDP/TCP: source port, destination port, or any port. +- Layer 4 information for ICMP type and ICMP code. +- Layer 4 information for TCP Flags. +- Layer 3 information: DSCP value, Protocol type, packet length, fragmentation. +- Misc layer 4 TCP flags. + +Note that if originally Flowspec defined IPv4 rules, this is also possible to use +IPv6 address-family. The same set of combinations as defined for IPv4 can be used. + +A combination of the above rules is applied for traffic filtering. This is +encoded as part of specific BGP extended communities and the action can range +from the obvious rerouting (to nexthop or to separate VRF) to shaping, or +discard. + +The following IETF drafts and RFCs have been used to implement FRR Flowspec: + +- :rfc:`5575` +- [Draft-IETF-IDR-Flowspec-redirect-IP]_ +- [Draft-IETF-IDR-Flow-Spec-V6]_ + +.. _design-principles-flowspec: + +Design Principles +----------------- + +FRR implements the Flowspec client side, that is to say that BGP is able to +receive Flowspec entries, but is not able to act as manager and send Flowspec +entries. + +Linux provides the following mechanisms to implement policy based routing: + +- Filtering the traffic with ``Netfilter``. + ``Netfilter`` provides a set of tools like ``ipset`` and ``iptables`` that are + powerful enough to be able to filter such Flowspec filter rule. + +- using non standard routing tables via ``iproute2`` (via the ``ip rule`` + command provided by ``iproute2``). + ``iproute2`` is already used by FRR's :ref:`pbr` daemon which provides basic + policy based routing based on IP source and destination criterion. + +Below example is an illustration of what Flowspec will inject in the underlying +system: + +.. code-block:: shell + + # linux shell + ipset create match0x102 hash:net,net counters + ipset add match0x102 32.0.0.0/16,40.0.0.0/16 + iptables -N match0x102 -t mangle + iptables -A match0x102 -t mangle -j MARK --set-mark 102 + iptables -A match0x102 -t mangle -j ACCEPT + iptables -i ntfp3 -t mangle -I PREROUTING -m set --match-set match0x102 + src,dst -g match0x102 + ip rule add fwmark 102 lookup 102 + ip route add 40.0.0.0/16 via 44.0.0.2 table 102 + +For handling an incoming Flowspec entry, the following workflow is applied: + +- Incoming Flowspec entries are handled by *bgpd*, stored in the BGP RIB. +- Flowspec entry is installed according to its complexity. + +It will be installed if one of the following filtering action is seen on the +BGP extended community: either redirect IP, or redirect VRF, in conjunction +with rate option, for redirecting traffic. Or rate option set to 0, for +discarding traffic. + +According to the degree of complexity of the Flowspec entry, it will be +installed in *zebra* RIB. For more information about what is supported in the +FRR implementation as rule, see :ref:`flowspec-known-issues` chapter. Flowspec +entry is split in several parts before being sent to *zebra*. + +- *zebra* daemon receives the policy routing configuration + +Policy Based Routing entities necessary to policy route the traffic in the +underlying system, are received by *zebra*. Two filtering contexts will be +created or appended in ``Netfilter``: ``ipset`` and ``iptable`` context. The +former is used to define an IP filter based on multiple criterium. For +instance, an ipset ``net:net`` is based on two ip addresses, while +``net,port,net`` is based on two ip addresses and one port (for ICMP, UDP, or +TCP). The way the filtering is used (for example, is src port or dst port +used?) is defined by the latter filtering context. ``iptable`` command will +reference the ``ipset`` context and will tell how to filter and what to do. In +our case, a marker will be set to indicate ``iproute2`` where to forward the +traffic to. Sometimes, for dropping action, there is no need to add a marker; +the ``iptable`` will tell to drop all packets matching the ``ipset`` entry. + +Configuration Guide +------------------- + +In order to configure an IPv4 Flowspec engine, use the following configuration. +As of today, it is only possible to configure Flowspec on the default VRF. + +.. code-block:: frr + + router bgp + neighbor remote-as + neighbor remote-as + address-family ipv4 flowspec + neighbor activate + exit + address-family ipv6 flowspec + neighbor activate + exit + exit + +You can see Flowspec entries, by using one of the following show commands: + +.. clicmd:: show bgp ipv4 flowspec [detail | A.B.C.D] + +.. clicmd:: show bgp ipv6 flowspec [detail | A:B::C:D] + +Per-interface configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One nice feature to use is the ability to apply Flowspec to a specific +interface, instead of applying it to the whole machine. Despite the following +IETF draft [Draft-IETF-IDR-Flowspec-Interface-Set]_ is not implemented, it is +possible to manually limit Flowspec application to some incoming interfaces. +Actually, not using it can result to some unexpected behaviour like accounting +twice the traffic, or slow down the traffic (filtering costs). To limit +Flowspec to one specific interface, use the following command, under +`flowspec address-family` node. + +.. clicmd:: local-install + +By default, Flowspec is activated on all interfaces. Installing it to a named +interface will result in allowing only this interface. Conversely, enabling any +interface will flush all previously configured interfaces. + +VRF redirection +^^^^^^^^^^^^^^^ + +Another nice feature to configure is the ability to redirect traffic to a +separate VRF. This feature does not go against the ability to configure +Flowspec only on default VRF. Actually, when you receive incoming BGP flowspec +entries on that default VRF, you can redirect traffic to an other VRF. + +As a reminder, BGP flowspec entries have a BGP extended community that contains +a Route Target. Finding out a local VRF based on Route Target consists in the +following: + +- A configuration of each VRF must be done, with its Route Target set + Each VRF is being configured within a BGP VRF instance with its own Route + Target list. Route Target accepted format matches the following: + ``A.B.C.D:U16``, or ``U16:U32``, ``U32:U16``. + +- The first VRF with the matching Route Target will be selected to route traffic + to. Use the following command under ipv4 unicast address-family node + +.. clicmd:: rt redirect import RTLIST... + +In order to illustrate, if the Route Target configured in the Flowspec entry is +``E.F.G.H:II``, then a BGP VRF instance with the same Route Target will be set +set. That VRF will then be selected. The below full configuration example +depicts how Route Targets are configured and how VRFs and cross VRF +configuration is done. Note that the VRF are mapped on Linux Network +Namespaces. For data traffic to cross VRF boundaries, virtual ethernet +interfaces are created with private IP addressing scheme. + +.. code-block:: frr + + router bgp + neighbor remote-as + address-family ipv4 flowspec + neighbor A.B.C.D activate + exit + exit + router bgp vrf vrf2 + address-family ipv4 unicast + rt redirect import + exit + exit + +Similarly, it is possible to do the same for IPv6 flowspec rules, by using +an IPv6 extended community. The format is defined on :rfc:`5701`, and that +community contains an IPv6 address encoded in the attribute, and matches the +locally configured imported route target IPv6 defined under the appropriate +BGP VRF instance. Below example defines an IPv6 extended community containing +`E:F::G:H` address followed by 2 bytes chosen by admin ( here `JJ`). + +.. code-block:: frr + + router bgp + neighbor remote-as + address-family ipv6 flowspec + neighbor A:B::C:D activate + exit + exit + router bgp vrf vrf2 + address-family ipv6 unicast + rt6 redirect import + exit + exit + + +Flowspec monitoring & troubleshooting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can monitor policy-routing objects by using one of the following commands. +Those command rely on the filtering contexts configured from BGP, and get the +statistics information retrieved from the underlying system. In other words, +those statistics are retrieved from ``Netfilter``. + +.. clicmd:: show pbr ipset IPSETNAME | iptable + +``IPSETNAME`` is the policy routing object name created by ``ipset``. About +rule contexts, it is possible to know which rule has been configured to +policy-route some specific traffic. The :clicmd:`show pbr iptable` command +displays for forwarded traffic, which table is used. Then it is easy to use +that table identifier to dump the routing table that the forwarded traffic will +match. + +.. code-block:: frr + +.. clicmd:: show ip route table TABLEID + + ``TABLEID`` is the table number identifier referencing the non standard + routing table used in this example. + +.. clicmd:: debug bgp flowspec + + You can troubleshoot Flowspec, or BGP policy based routing. For instance, if + you encounter some issues when decoding a Flowspec entry, you should enable + :clicmd:`debug bgp flowspec`. + +.. clicmd:: debug bgp pbr [error] + + If you fail to apply the flowspec entry into *zebra*, there should be some + relationship with policy routing mechanism. Here, + :clicmd:`debug bgp pbr error` could help. + + To get information about policy routing contexts created/removed, only use + :clicmd:`debug bgp pbr` command. + +Ensuring that a Flowspec entry has been correctly installed and that incoming +traffic is policy-routed correctly can be checked as demonstrated below. First +of all, you must check whether the Flowspec entry has been installed or not. + +.. code-block:: frr + + CLI# show bgp ipv4 flowspec 5.5.5.2/32 + BGP flowspec entry: (flags 0x418) + Destination Address 5.5.5.2/32 + IP Protocol = 17 + Destination Port >= 50 , <= 90 + FS:redirect VRF RT:255.255.255.255:255 + received for 18:41:37 + installed in PBR (match0x271ce00) + +This means that the Flowspec entry has been installed in an ``iptable`` named +``match0x271ce00``. Once you have confirmation it is installed, you can check +whether you find the associate entry by executing following command. You can +also check whether incoming traffic has been matched by looking at counter +line. + +.. code-block:: frr + + CLI# show pbr ipset match0x271ce00 + IPset match0x271ce00 type net,port + to 5.5.5.0/24:proto 6:80-120 (8) + pkts 1000, bytes 1000000 + to 5.5.5.2:proto 17:50-90 (5) + pkts 1692918, bytes 157441374 + +As you can see, the entry is present. note that an ``iptable`` entry can be +used to host several Flowspec entries. In order to know where the matching +traffic is redirected to, you have to look at the policy routing rules. The +policy-routing is done by forwarding traffic to a routing table number. That +routing table number is reached by using a ``iptable``. The relationship +between the routing table number and the incoming traffic is a ``MARKER`` that +is set by the IPtable referencing the IPSet. In Flowspec case, ``iptable`` +referencing the ``ipset`` context have the same name. So it is easy to know +which routing table is used by issuing following command: + +.. code-block:: frr + + CLI# show pbr iptable + IPtable match0x271ce00 action redirect (5) + pkts 1700000, bytes 158000000 + table 257, fwmark 257 + ... + +As you can see, by using following Linux commands, the MARKER ``0x101`` is +present in both ``iptable`` and ``ip rule`` contexts. + +.. code-block:: shell + + # iptables -t mangle --list match0x271ce00 -v + Chain match0x271ce00 (1 references) + pkts bytes target prot opt in out source destination + 1700K 158M MARK all -- any any anywhere anywhere + MARK set 0x101 + 1700K 158M ACCEPT all -- any any anywhere anywhere + + # ip rule list + 0:from all lookup local + 0:from all fwmark 0x101 lookup 257 + 32766:from all lookup main + 32767:from all lookup default + +This allows us to see where the traffic is forwarded to. + +.. _flowspec-known-issues: + +Limitations / Known Issues +-------------------------- + +As you can see, Flowspec is rich and can be very complex. As of today, not all +Flowspec rules will be able to be converted into Policy Based Routing actions. + +- The ``Netfilter`` driver is not integrated into FRR yet. Not having this + piece of code prevents from injecting flowspec entries into the underlying + system. + +- There are some limitations around filtering contexts + + If I take example of UDP ports, or TCP ports in Flowspec, the information + can be a range of ports, or a unique value. This case is handled. + However, complexity can be increased, if the flow is a combination of a list + of range of ports and an enumerate of unique values. Here this case is not + handled. Similarly, it is not possible to create a filter for both src port + and dst port. For instance, filter on src port from [1-1000] and dst port = + 80. The same kind of complexity is not possible for packet length, ICMP type, + ICMP code. + +There are some other known issues: + +- The validation procedure depicted in :rfc:`5575` is not available. + + This validation procedure has not been implemented, as this feature was not + used in the existing setups you shared with us. + +- The filtering action shaper value, if positive, is not used to apply shaping. + + If value is positive, the traffic is redirected to the wished destination, + without any other action configured by Flowspec. + It is recommended to configure Quality of Service if needed, more globally on + a per interface basis. + +- Upon an unexpected crash or other event, *zebra* may not have time to flush + PBR contexts. + + That is to say ``ipset``, ``iptable`` and ``ip rule`` contexts. This is also a + consequence due to the fact that ip rule / ipset / iptables are not discovered + at startup (not able to read appropriate contexts coming from Flowspec). + +Appendix +-------- + +More information with a public presentation that explains the design of Flowspec +inside FRRouting. + +[Presentation]_ + +.. [Draft-IETF-IDR-Flowspec-redirect-IP] +.. [Draft-IETF-IDR-Flowspec-Interface-Set] +.. [Draft-IETF-IDR-Flow-Spec-V6] +.. [Presentation] diff --git a/doc/user/frr-reload.rst b/doc/user/frr-reload.rst new file mode 100644 index 0000000..bd295db --- /dev/null +++ b/doc/user/frr-reload.rst @@ -0,0 +1,41 @@ +.. _frr-reload: + + +The frr-reload.py script +======================== + +The ``frr-reload.py`` script attempts to update the configuration of running +daemons. It takes as argument the path of the configuration file that we want +to apply. The script will attempt to retrieve the running configuration from +daemons, calculate the delta between that config and the intended one, and +execute the required sequence of vtysh commands to enforce the changes. + +Options +------- + +There are several options that control the behavior of ``frr-reload``: + +* ``--input INPUT``: uses the specified input file as the running configuration + instead of retrieving it from a ``show running-config`` in vtysh +* ``--reload``: applies the configuration delta to the daemons. Either this or + ``--test`` MUST be specified. +* ``--test``: only outputs the configuration delta, without enforcing it. + Either this or ``--reload`` MUST be specified. +* ``--debug``: enable debug messages +* ``--stdout``: print output to stdout +* ``--bindir BINDIR``: path to the vtysh executable +* ``--confdir CONFDIR``: path to the existing daemon config files +* ``--rundir RUNDIR``: path to a folder to be used to write the temporary files + needed by the script to do its job. The script should have write access to it +* ``--daemon DAEMON``: by default ``frr-reload.py`` assumes that we are using + integrated config and attempting to update the configuration for all daemons. + If this is not the case, e.g. each daemon has its individual config file, + then the delta can only be computed on a per-daemon basis. This option allows + the user to specify the daemon for which the config is intended. DAEMON + should be one of the keywords allowed in vtysh as an option for ``show + running-config``. +* ``--vty_socket VTY_SOCKET``: the socket to be used by vtysh to connect to the + running daemons. +* ``--overwrite``: overwrite the existing daemon config file with the new + config after the delta has been applied. The file name will be ``frr.conf`` + for integrate config, or ``DAEMON.conf`` when using per-daemon config files. diff --git a/doc/user/glossary.rst b/doc/user/glossary.rst new file mode 100644 index 0000000..6b49336 --- /dev/null +++ b/doc/user/glossary.rst @@ -0,0 +1,41 @@ +******** +Glossary +******** + +.. glossary:: + + distance-vector + A distance-vector routing protocol in data networks determines the best + route for data packets based on distance. Distance-vector routing + protocols measure the distance by the number of routers a packet has to + pass. Some distance-vector protocols also take into account network + latency and other factors that influence traffic on a given route. To + determine the best route across a network, routers on which a + distance-vector protocol is implemented exchange information with one + another, usually routing tables plus hop counts for destination networks + and possibly other traffic information. Distance-vector routing protocols + also require that a router informs its neighbours of network topology + changes periodically. [distance-vector-rp]_ + + link-state + Link-state algorithms (also known as shortest path first algorithms) + flood routing information to all nodes in the internetwork. Each router, + however, sends only the portion of the routing table that describes the + state of its own links. In link-state algorithms, each router builds a + picture of the entire network in its routing tables. Distance vector + algorithms (also known as Bellman-Ford algorithms) call for each router + to send all or some portion of its routing table, but only to its + neighbors. In essence, link-state algorithms send small updates + everywhere, while distance vector algorithms send larger updates only to + neighboring routers. Distance vector algorithms know only about their + neighbors. [link-state-rp]_ + + Bellman-Ford + The Bellman–Ford algorithm is an algorithm that computes shortest paths + from a single source vertex to all of the other vertices in a weighted + digraph. [bellman-ford]_ + + +.. [distance-vector-rp] https://en.wikipedia.org/wiki/Distance-vector_routing_protocol +.. [link-state-rp] https://en.wikipedia.org/wiki/Link-state_routing_protocol +.. [bellman-ford] https://en.wikipedia.org/wiki/Bellman-Ford_algorithm diff --git a/doc/user/grpc.rst b/doc/user/grpc.rst new file mode 100644 index 0000000..d6767fc --- /dev/null +++ b/doc/user/grpc.rst @@ -0,0 +1,66 @@ +.. _grpc: + +*************** +Northbound gRPC +*************** + +.. program:: configure + +*gRPC* provides a combined front end to all FRR daemons using the YANG +northbound. It is currently disabled by default due its experimental +stage, but it can be enabled with :option:`--enable-grpc` option in the +configure script. + + +.. _grpc-features: + +Northbound gRPC Features +======================== + +* Get/set configuration using JSON/XML/XPath encondings. +* Execute YANG RPC calls. +* Lock/unlock configuration. +* Create/edit/load/update/commit candidate configuration. +* List/get transactions. + + +.. note:: + + There is currently no support for YANG notifications. + + +.. note:: + + You can find more information on how to code programs to interact + with FRR by reading the gRPC Programming Language Bindings section + in the `developer's documentation + `_. + + +.. _grpc-config: + +Daemon gRPC Configuration +========================= + +The *gRPC* module accepts the following run time option: + +- ``port``: the port to listen to (defaults to ``50051``). + + +.. note:: + + At the moment only localhost connections with no SSL/TLS are + supported. + + +To configure FRR daemons to listen to gRPC you need to append the +following parameter to the daemon's command line: ``-M grpc`` +(optionally ``-M grpc:PORT`` to specify listening port). + +To do that in production you need to edit the ``/etc/frr/daemons`` file +so the daemons get started with the command line argument. Example: + +:: + + # other daemons... + bfdd_options=" --daemon -A 127.0.0.1 -M grpc" diff --git a/doc/user/images/pathd_config.png b/doc/user/images/pathd_config.png new file mode 100644 index 0000000..b7be1a6 Binary files /dev/null and b/doc/user/images/pathd_config.png differ diff --git a/doc/user/images/pathd_general.png b/doc/user/images/pathd_general.png new file mode 100644 index 0000000..1da6d46 Binary files /dev/null and b/doc/user/images/pathd_general.png differ diff --git a/doc/user/images/pathd_initiated_multi.png b/doc/user/images/pathd_initiated_multi.png new file mode 100644 index 0000000..5c818e6 Binary files /dev/null and b/doc/user/images/pathd_initiated_multi.png differ diff --git a/doc/user/index.rst b/doc/user/index.rst new file mode 100644 index 0000000..4789677 --- /dev/null +++ b/doc/user/index.rst @@ -0,0 +1,103 @@ +FRRouting User Guide +==================== + +############ +Introduction +############ + +.. _introduction: +.. toctree:: + :maxdepth: 2 + + overview + installation + setup + +###### +Basics +###### + +.. _basics: +.. toctree:: + :maxdepth: 2 + + basic + extlog + vtysh + grpc + filter + routemap + affinitymap + ipv6 + kernel + snmp + scripting + nexthop_groups +.. modules + +######### +Protocols +######### + +.. _protocols: +.. toctree:: + :maxdepth: 2 + + zebra + bfd + bgp + babeld + fabricd + ldpd + eigrpd + evpn + isisd + nhrpd + ospfd + ospf6d + pathd + pim + pimv6 + pbr + ripd + ripngd + sharp + static + vnc + vrrp + bmp + watchfrr + mgmtd + +######## +Appendix +######## + +.. _appendix: +.. toctree:: + :maxdepth: 2 + + bugs + packet-dumps + glossary + frr-reload + +################ +Copyright notice +################ + +Copyright (c) 1996-2018 Kunihiro Ishiguro, et al. + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that this permission notice may be stated in a translation +approved by Kunihiro Ishiguro. diff --git a/doc/user/installation.rst b/doc/user/installation.rst new file mode 100644 index 0000000..e49f104 --- /dev/null +++ b/doc/user/installation.rst @@ -0,0 +1,600 @@ +.. index:: + single: How to install FRR + single: Installing FRR + single: Building FRR + +.. _installation: + +Installation +============ + +This section covers the basics of building, installing and setting up FRR. + + +From Packages +------------- + +The project publishes packages for Red Hat, Centos, Debian and Ubuntu on the +`GitHub releases `_. page. External +contributors offer packages for many other platforms including \*BSD, Alpine, +Gentoo, Docker, and others. There is currently no documentation on how to use +those but we hope to add it soon. + +From Snapcraft +-------------- + +In addition to traditional packages the project also builds and publishes +universal Snap images, available at https://snapcraft.io/frr. + +From Source +----------- + +Building FRR from source is the best way to ensure you have the latest features +and bug fixes. Details for each supported platform, including dependency +package listings, permissions, and other gotchas, are in the `developer's +documentation +`_. This +section provides a brief overview on the process. + + +Getting the Source +^^^^^^^^^^^^^^^^^^ + +FRR's source is available on the project +`GitHub page `_. + +.. code-block:: shell + + git clone https://github.com/FRRouting/frr.git + +When building from Git there are several branches to choose from. The +``master`` branch is the primary development branch. It should be considered +unstable. Each release has its own branch named ``stable/X.X``, where ``X.X`` +is the release version. + +In addition, release tarballs are published on the GitHub releases page +`here `_. + + +.. index:: + single: Configuration options + single: Options for configuring + single: Build options + single: Distribution configuration + single: Options to `./configure` + +.. _build-configuration: + +Build Configuration +^^^^^^^^^^^^^^^^^^^ + +FRR has an excellent configure script which automatically detects most host +configurations. There are several additional configure options to customize the +build to include or exclude specific features and dependencies. + +First, update the build system. Change into your FRR source directory and issue: + +.. code-block:: shell + + ./bootstrap.sh + +This will install any missing build scripts and update the Autotools +configuration. Once this is done you can move on to choosing your configuration +options from the list below. + +.. _frr-configuration: + +.. program:: configure + +.. option:: --enable-tcmalloc + + Enable the alternate malloc library. In some cases this is faster and more efficient, + in some cases it is not. + +.. option:: --disable-doc + + Do not build any documentation, including this one. + +.. option:: --enable-doc-html + + From the documentation build html docs as well in addition to the normal output. + +.. option:: --disable-zebra + + Do not build zebra daemon. This generally only be useful in a scenario where + you are building bgp as a standalone server. + +.. option:: --disable-ripd + + Do not build ripd. + +.. option:: --disable-ripngd + + Do not build ripngd. + +.. option:: --disable-ospfd + + Do not build ospfd. + +.. option:: --disable-ospf6d + + Do not build ospf6d. + +.. option:: --disable-bgpd + + Do not build bgpd. + +.. option:: --disable-ldpd + + Do not build ldpd. + +.. option:: --disable-nhrpd + + Do not build nhrpd. + +.. option:: --disable-eigrpd + + Do not build eigrpd. + +.. option:: --disable-babeld + + Do not build babeld. + +.. option:: --disable-watchfrr + + Do not build watchfrr. Watchfrr is used to integrate daemons into startup/shutdown + software available on your machine. This is needed for systemd integration, if you + disable watchfrr you cannot have any systemd integration. + +.. option:: --enable-werror + + Build with all warnings converted to errors as a compile option. This + is recommended for developers only. + +.. option:: --disable-pimd + + Turn off building of pimd. On some BSD platforms pimd will not build properly due + to lack of kernel support. + +.. option:: --disable-vrrpd + + Turn off building of vrrpd. Linux is required for vrrpd support; + other platforms are not supported. + +.. option:: --disable-pbrd + + Turn off building of pbrd. This daemon currently requires linux in order to function + properly. + +.. option:: --enable-sharpd + + Turn on building of sharpd. This daemon facilitates testing of FRR and can also + be used as a quick and easy route generator. + +.. option:: --disable-staticd + + Do not build staticd. This daemon is necessary if you want static routes. + +.. option:: --disable-bfdd + + Do not build bfdd. + +.. option:: --disable-bgp-announce + + Make *bgpd* which does not make bgp announcements at all. This + feature is good for using *bgpd* as a BGP announcement listener. + +.. option:: --disable-bgp-vnc + + Turn off bgpd's ability to use VNC. + +.. option:: --disable-bgp-bmp + + Turn off BGP BMP support + +.. option:: --enable-datacenter + + This option is deprecated as it is superseded by the `-F` (profile) command + line option which allows adjusting the setting at startup rather than + compile time. + + Enable system defaults to work as if in a Data Center. See defaults.h + for what is changed by this configure option. + +.. option:: --enable-snmp + + Enable SNMP support. By default, SNMP support is disabled. + +.. option:: --disable-ospfapi + + Disable support for OSPF-API, an API to interface directly with ospfd. + OSPF-API is enabled if --enable-opaque-lsa is set. + +.. option:: --disable-ospfclient + + Disable installation of the python ospfclient and building of the example + OSPF-API client. + +.. option:: --disable-isisd + + Do not build isisd. + +.. option:: --disable-fabricd + + Do not build fabricd. + +.. option:: --enable-isis-topology + + Enable IS-IS topology generator. + +.. option:: --enable-realms + + Enable the support of Linux Realms. Convert tag values from 1-255 into a + realm value when inserting into the Linux kernel. Then routing policy can be + assigned to the realm. See the tc man page. This option is currently not + compatible with the usage of nexthop groups in the linux kernel itself. + +.. option:: --enable-irdp + + Enable IRDP server support. This is deprecated. + +.. option:: --disable-rtadv + + Disable support IPV6 router advertisement in zebra. + +.. option:: --enable-gcc-rdynamic + + Pass the ``-rdynamic`` option to the linker driver. This is in most cases + necessary for getting usable backtraces. This option defaults to on if the + compiler is detected as gcc, but giving an explicit enable/disable is + suggested. + +.. option:: --disable-backtrace + + Controls backtrace support for the crash handlers. This is autodetected by + default. Using the switch will enforce the requested behaviour, failing with + an error if support is requested but not available. On BSD systems, this + needs libexecinfo, while on glibc support for this is part of libc itself. + +.. option:: --enable-dev-build + + Turn on some options for compiling FRR within a development environment in + mind. Specifically turn on -g3 -O0 for compiling options and add inclusion + of grammar sandbox. + +.. option:: --disable-snmp + + Build without SNMP support. + +.. option:: --disable-vtysh + + Build without VTYSH. + +.. option:: --enable-fpm + + Build with FPM module support. + +.. option:: --enable-fpm-listener + + Build a small fpm listener for testing. + +.. option:: --with-service-timeout=X + + Set timeout value for FRR service. The time of restarting or reloading FRR + service should not exceed this value. This number can be from 0-999. + Additionally if this parameter is not passed or setting X = 0, FRR will take + default value: 2 minutes. + +.. option:: --enable-numeric-version + + Alpine Linux does not allow non-numeric characters in the version string. + With this option, we provide a way to strip out these characters for APK dev + package builds. + +.. option:: --disable-version-build-config + + Remove the "configuerd with" field that has all of the build configuration + arguments when reporting the version string in `show version` command. + +.. option:: --with-pkg-extra-version=VER + + Add extra version field, for packagers/distributions + +.. option:: --with-pkg-git-version + + Add git information to MOTD and build version string + +.. option:: --enable-multipath=X + + Compile FRR with up to X way ECMP supported. This number can be from 0-999. + For backwards compatibility with older configure options when setting X = 0, + we will build FRR with 64 way ECMP. This is needed because there are + hardcoded arrays that FRR builds towards, so we need to know how big to + make these arrays at build time. Additionally if this parameter is + not passed in FRR will default to 16 ECMP. + +.. option:: --enable-gcov + + Code coverage reports from gcov require adjustments to the C and LD flags. + With this option, gcov instrumentation is added to the build and coverage + reports are created during execution. The check-coverage make target is + also created to ease report uploading to codecov.io. The upload requires + the COMMIT (git hash) and TOKEN (codecov upload token) environment variables + be set. + +.. option:: --enable-config-rollbacks + + Build with configuration rollback support. Requires SQLite3. + +.. option:: --enable-sysrepo + + Build the Sysrepo northbound plugin. + +.. option:: --enable-grpc + + Enable the gRPC northbound plugin. + +.. option:: --enable-zeromq + + Enable the ZeroMQ handler. + +.. option:: --with-libpam + + Use libpam for PAM support in vtysh. + +.. option:: --enable-pcreposix + + Turn on the usage of PCRE Posix libs for regex functionality. + +.. option:: --enable-pcre2posix + + Turn on the usage of PCRE2 Posix libs for regex functionality. + + PCRE2 versions <= 10.31 work a bit differently. We suggest using at least + >= 10.36. + +.. option:: --enable-rpath + + Set hardcoded rpaths in the executable [default=yes]. + +.. option:: --enable-scripting + + Enable Lua scripting [default=no]. + +You may specify any combination of the above options to the configure +script. By default, the executables are placed in :file:`/usr/local/sbin` +and the configuration files in :file:`/usr/local/etc`. The :file:`/usr/local/` +installation prefix and other directories may be changed using the following +options to the configuration script. + +.. option:: --enable-ccls + + Enable the creation of a :file:`.ccls` file in the top level source + directory. + + Some development environments (e.g., LSP server within emacs, et al.) can + utilize :clicmd:`ccls` to provide highly sophisticated IDE features (e.g., + semantically accurate jump-to definition/reference, and even code + refactoring). The `--enable-ccls` causes :file:`configure` to generate a + configuration for the :clicmd:`ccls` command, based on the configured + FRR build environment. + +.. option:: --prefix + + Install architecture-independent files in `prefix` [/usr/local]. + +.. option:: --sysconfdir + + Look for configuration files in `dir`/frr [`prefix`/etc]. Note that sample + configuration files will be installed here. Should be ``/etc`` unless + your platform splits package configuration locations. + +.. option:: --localstatedir + + Configure base directory for local state. Indirectly controls + ``--runstatedir``. Should be ``/var`` in most cases. + +.. option:: --runstatedir + + Configure FRR to use `dir`/frr for local state files, such as pid files and + unix sockets. Should be ``/var/run`` (default through ``--localstatedir``) + or ``/run`` in most cases. + +.. option:: --with-scriptdir + + Look for Lua scripts in ``dir`` [``prefix``/etc/frr/scripts]. + +.. option:: --with-yangmodelsdir + + Look for YANG modules in `dir` [`prefix`/share/yang]. Note that the FRR + YANG modules will be installed here. + +.. option:: --with-vici-socket + + Set StrongSWAN vici interface socket path [/var/run/charon.vici]. + +.. note:: + + The former ``--enable-systemd`` option does not exist anymore. Support for + systemd is now always available through built-in functions, without + depending on libsystemd. + +Python dependency, documentation and tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +FRR uses Python for these components: + +* configuration reloading (see :ref:`FRR-RELOAD ` for details), +* documentation, +* unit tests. + +Additionally, FRR ships Python extensions written in C which are used during +its build process. + +To this extent, FRR needs the following: + +* an installation of CPython, preferably version 3.2 or newer (2.7 works but + is end of life and will stop working at some point.) +* development files (mostly headers) for that version of CPython +* an installation of `sphinx` for that version of CPython, to build the + documentation +* an installation of `pytest` for that version of CPython, to run the unit + tests + +The `sphinx` and `pytest` dependencies can be avoided by not building +documentation / not running ``make check``, but the CPython dependency is a +hard dependency of the FRR build process (for the `clippy` tool.) + +.. index:: + single: FRR Least-Privileges + single: FRR Privileges + +.. _least-privilege-support: + +Least-Privilege Support +""""""""""""""""""""""" + +Additionally, you may configure zebra to drop its elevated privileges +shortly after startup and switch to another user. The configure script will +automatically try to configure this support. There are three configure +options to control the behaviour of FRR daemons. + +.. option:: --enable-user + + Switch to user `user shortly after startup, and run as user `user` in normal + operation. + +.. option:: --enable-group + + Switch real and effective group to `group` shortly after startup. + +.. option:: --enable-vty-group + + Create Unix Vty sockets (for use with vtysh) with group ownership set to + `group`. This allows one to create a separate group which is restricted to + accessing only the vty sockets, hence allowing one to delegate this group to + individual users, or to run vtysh setgid to this group. + +The default user and group which will be configured is 'frr' if no user or +group is specified. Note that this user or group requires write access to the +local state directory (see :option:`--localstatedir`) and requires at least +read access, and write access if you wish to allow daemons to write out their +configuration, to the configuration directory (see :option:`--sysconfdir`). + +On systems which have the 'libcap' capabilities manipulation library (currently +only Linux), FRR will retain only minimal capabilities required and will only +raise these capabilities for brief periods. On systems without libcap, FRR will +run as the user specified and only raise its UID to 0 for brief periods. + + +.. index:: + pair: building; Linux + pair: configuration; Linux + +Linux Notes +""""""""""" + +There are several options available only to GNU/Linux systems. If you use +GNU/Linux, make sure that the current kernel configuration is what you want. +FRR will run with any kernel configuration but some recommendations do exist. + +:makevar:`CONFIG_NETLINK` + Kernel/User Netlink socket. This enables an advanced interface between + the Linux kernel and *zebra* (:ref:`kernel-interface`). + +:makevar:`CONFIG_RTNETLINK` + This makes it possible to receive Netlink routing messages. If you specify + this option, *zebra* can detect routing information updates directly from + the kernel (:ref:`kernel-interface`). + +:makevar:`CONFIG_IP_MULTICAST` + This option enables IP multicast and should be specified when you use *ripd* + (:ref:`rip`) or *ospfd* (:ref:`ospfv2`) because these protocols use + multicast. + +Linux sysctl settings and kernel modules +```````````````````````````````````````` + +There are several kernel parameters that impact overall operation of FRR when +using Linux as a router. Generally these parameters should be set in a +sysctl related configuration file, e.g., :file:`/etc/sysctl.conf` on +Ubuntu based systems and a new file +:file:`/etc/sysctl.d/90-routing-sysctl.conf` on Centos based systems. +Additional kernel modules are also needed to support MPLS forwarding. + +:makevar:`IPv4 and IPv6 forwarding` + The following are set to enable IP forwarding in the kernel: + + .. code-block:: shell + + net.ipv4.conf.all.forwarding=1 + net.ipv6.conf.all.forwarding=1 + +:makevar:`MPLS forwarding` + Basic MPLS support was introduced in the kernel in version 4.1 and + additional capability was introduced in 4.3 and 4.5. + For some general information on Linux MPLS support, see + https://www.netdevconf.org/1.1/proceedings/slides/prabhu-mpls-tutorial.pdf. + The following modules should be loaded to support MPLS forwarding, + and are generally added to a configuration file such as + :file:`/etc/modules-load.d/modules.conf`: + + .. code-block:: shell + + # Load MPLS Kernel Modules + mpls_router + mpls_iptunnel + + The following is an example to enable MPLS forwarding in the + kernel, typically by editing :file:`/etc/sysctl.conf`: + + .. code-block:: shell + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + + Make sure to add a line equal to :file:`net.mpls.conf..input` for + each interface *''* used with MPLS and to set labels to an + appropriate value. + +:makevar:`VRF forwarding` + General information on Linux VRF support can be found in + https://www.kernel.org/doc/Documentation/networking/vrf.txt. + + Kernel support for VRFs was introduced in 4.3, but there are known issues + in versions up to 4.15 (for IPv4) and 5.0 (for IPv6). The FRR CI system + doesn't perform VRF tests on older kernel versions, and VRFs may not work + on them. If you experience issues with VRF support, you should upgrade your + kernel version. + + .. seealso:: :ref:`zebra-vrf` + +Building +^^^^^^^^ + +Once you have chosen your configure options, run the configure script and pass +the options you chose: + +.. code-block:: shell + + ./configure \ + --prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-pimd \ + --enable-watchfrr \ + ... + +After configuring the software, you are ready to build and install it in your +system. + +.. code-block:: shell + + make && sudo make install + +If everything finishes successfully, FRR should be installed. You should now +skip to the section on :ref:`basic-setup`. diff --git a/doc/user/ipv6.rst b/doc/user/ipv6.rst new file mode 100644 index 0000000..4f01061 --- /dev/null +++ b/doc/user/ipv6.rst @@ -0,0 +1,223 @@ +.. _ipv6-support: + +************ +IPv6 Support +************ + +FRR fully supports IPv6 routing. As described so far, FRR supports RIPng, +OSPFv3, and BGP-4+. You can give IPv6 addresses to an interface and configure +static IPv6 routing information. FRR IPv6 also provides automatic address +configuration via a feature called ``address auto configuration``. To do it, +the router must send router advertisement messages to the all nodes that exist +on the network. + +Previous versions of FRR could be built without IPv6 support. This is +no longer possible. + +Router Advertisement +==================== + +.. clicmd:: show ipv6 nd ra-interfaces [vrf ] + + Show configured route advertisement interfaces. VRF subcommand only + applicable for netns-based vrfs. + +.. clicmd:: ipv6 nd suppress-ra + + Don't send router advertisement messages. The ``no`` form of this command + enables sending RA messages. + +.. clicmd:: ipv6 nd prefix ipv6prefix [valid-lifetime] [preferred-lifetime] [off-link] [no-autoconfig] [router-address] + + Configuring the IPv6 prefix to include in router advertisements. Several prefix + specific optional parameters and flags may follow: + + - ``valid-lifetime``: the length of time in seconds during what the prefix is + valid for the purpose of on-link determination. Value ``infinite`` represents + infinity (i.e. a value of all one bits (``0xffffffff``)). + Range: ``(0-4294967295)`` Default: ``2592000`` + + - ``preferred-lifetime``: the length of time in seconds during what addresses + generated from the prefix remain preferred. Value ``infinite`` represents + infinity. + Range: ``(0-4294967295)`` Default: ``604800`` + + - ``off-link``: indicates that advertisement makes no statement about on-link or + off-link properties of the prefix. + Default: not set, i.e. this prefix can be used for on-link determination. + + - ``no-autoconfig``: indicates to hosts on the local link that the specified prefix + cannot be used for IPv6 autoconfiguration. + + Default: not set, i.e. prefix can be used for autoconfiguration. + + - ``router-address``: indicates to hosts on the local link that the specified + prefix contains a complete IP address by setting R flag. + + Default: not set, i.e. hosts do not assume a complete IP address is placed. + +.. clicmd:: ipv6 nd ra-interval [(1-1800)] + + The maximum time allowed between sending unsolicited multicast router + advertisements from the interface, in seconds. + Default: ``600`` + +.. clicmd:: ipv6 nd ra-interval [msec (70-1800000)] + + The maximum time allowed between sending unsolicited multicast router + advertisements from the interface, in milliseconds. + Default: ``600000`` + +.. clicmd:: ipv6 nd ra-fast-retrans + + RFC4861 states that consecutive RA packets should be sent no more + frequently than three seconds apart. FRR by default allows faster + transmissions of RA packets in order to speed convergence and + neighbor establishment, particularly for unnumbered peering. By + turning off ipv6 nd ra-fast-retrans, the implementation is + compliant with the RFC at the cost of slower convergence + and neighbor establishment. + Default: enabled + +.. clicmd:: ipv6 nd ra-retrans-interval [(0-4294967295)] + + The value to be placed in the retrans timer field of router advertisements + sent from the interface, in msec. Indicates the interval between router + advertisement retransmissions. Setting the value to zero indicates that + the value is unspecified by this router. Must be between zero or 4294967295 + msec. + Default: ``0`` + +.. clicmd:: ipv6 nd ra-hop-limit [(0-255)] + + The value to be placed in the hop count field of router advertisements sent + from the interface, in hops. Indicates the maximum diameter of the network. + Setting the value to zero indicates that the value is unspecified by this + router. Must be between zero or 255 hops. + Default: ``64`` + +.. clicmd:: ipv6 nd ra-lifetime [(0-9000)] + + The value to be placed in the Router Lifetime field of router advertisements + sent from the interface, in seconds. Indicates the usefulness of the router + as a default router on this interface. Setting the value to zero indicates + that the router should not be considered a default router on this interface. + Must be either zero or between value specified with ``ipv6 nd ra-interval`` + (or default) and 9000 seconds. + Default: ``1800`` + +.. clicmd:: ipv6 nd reachable-time [(1-3600000)] + + The value to be placed in the Reachable Time field in the Router + Advertisement messages sent by the router, in milliseconds. The configured + time enables the router to detect unavailable neighbors. The value zero + means unspecified (by this router). + Default: ``0`` + +.. clicmd:: ipv6 nd managed-config-flag + + Set/unset flag in IPv6 router advertisements which indicates to hosts that + they should use managed (stateful) protocol for addresses autoconfiguration + in addition to any addresses autoconfigured using stateless address + autoconfiguration. + Default: not set + +.. clicmd:: ipv6 nd other-config-flag + + Set/unset flag in IPv6 router advertisements which indicates to hosts that + they should use administered (stateful) protocol to obtain autoconfiguration + information other than addresses. + Default: not set + +.. clicmd:: ipv6 nd home-agent-config-flag + + Set/unset flag in IPv6 router advertisements which indicates to hosts that + the router acts as a Home Agent and includes a Home Agent Option. + Default: not set + + +.. clicmd:: ipv6 nd home-agent-preference [(0-65535)] + + The value to be placed in Home Agent Option, when Home Agent config flag is + set, which indicates to hosts Home Agent preference. The default value of 0 + stands for the lowest preference possible. + Default: ``0`` + +.. clicmd:: ipv6 nd home-agent-lifetime [(0-65520)] + + The value to be placed in Home Agent Option, when Home Agent config flag is set, + which indicates to hosts Home Agent Lifetime. The default value of 0 means to + place the current Router Lifetime value. + + Default: ``0`` + +.. clicmd:: ipv6 nd adv-interval-option + + Include an Advertisement Interval option which indicates to hosts the maximum time, + in milliseconds, between successive unsolicited Router Advertisements. + Default: not set + +.. clicmd:: ipv6 nd router-preference [(high|medium|low)] + + Set default router preference in IPv6 router advertisements per RFC4191. + Default: medium + +.. clicmd:: ipv6 nd mtu [(1-65535)] + + Include an MTU (type 5) option in each RA packet to assist the attached + hosts in proper interface configuration. The announced value is not verified + to be consistent with router interface MTU. + + Default: don't advertise any MTU option. + +.. clicmd:: ipv6 nd rdnss ipv6address [lifetime] + + Recursive DNS server address to advertise using the RDNSS (type 25) option + described in RFC8106. Can be specified more than once to advertise multiple + addresses. Note that hosts may choose to limit the number of RDNSS addresses + to track. + + Optional parameter: + + - ``lifetime``: the maximum time in seconds over which the specified address + may be used for domain name resolution. Value ``infinite`` represents + infinity (i.e. a value of all one bits (``0xffffffff``)). A value of 0 + indicates that the address must no longer be used. + Range: ``(0-4294967295)`` Default: ``3 * ra-interval`` + + Default: do not emit RDNSS option + +.. clicmd:: ipv6 nd dnssl domain-name-suffix [lifetime] + + Advertise DNS search list using the DNSSL (type 31) option described in + RFC8106. Specify more than once to advertise multiple domain name suffixes. + Host implementations may limit the number of honored search list entries. + + Optional parameter: + + - ``lifetime``: the maximum time in seconds over which the specified domain + suffix may be used in the course of name resolution. Value ``infinite`` + represents infinity (i.e. a value of all one bits (``0xffffffff``)). A + value of 0 indicates that the name suffix must no longer be used. + Range: ``(0-4294967295)`` Default: ``3 * ra-interval`` + + Default: do not emit DNSSL option + +Router Advertisement Configuration Example +========================================== +A small example: + +.. code-block:: frr + + interface eth0 + no ipv6 nd suppress-ra + ipv6 nd prefix 2001:0DB8:5009::/64 + + +.. seealso:: + + - :rfc:`2462` (IPv6 Stateless Address Autoconfiguration) + - :rfc:`4861` (Neighbor Discovery for IP Version 6 (IPv6)) + - :rfc:`6275` (Mobility Support in IPv6) + - :rfc:`4191` (Default Router Preferences and More-Specific Routes) + - :rfc:`8106` (IPv6 Router Advertisement Options for DNS Configuration) diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst new file mode 100644 index 0000000..7412611 --- /dev/null +++ b/doc/user/isisd.rst @@ -0,0 +1,846 @@ +.. _isis: + +**** +ISIS +**** + +:abbr:`ISIS (Intermediate System to Intermediate System)` is a routing protocol +which is described in :t:`ISO10589`, :rfc:`1195`, :rfc:`5308`. ISIS is an +:abbr:`IGP (Interior Gateway Protocol)`. Compared with :abbr:`RIP`, +:abbr:`ISIS` can provide scalable network support and faster convergence times +like :abbr:`OSPF`. ISIS is widely used in large networks such as :abbr:`ISP +(Internet Service Provider)` and carrier backbone networks. + +.. _configuring-isisd: + +Configuring isisd +================= + +There are no *isisd* specific options. Common options can be specified +(:ref:`common-invocation-options`) to *isisd*. *isisd* needs to acquire +interface information from *zebra* in order to function. Therefore *zebra* must +be running before invoking *isisd*. Also, if *zebra* is restarted then *isisd* +must be too. + +.. include:: config-include.rst + +.. _isis-router: + +ISIS router +=========== + +To start the ISIS process you have to specify the ISIS router. As of this +writing, *isisd* does not support multiple ISIS processes. + +.. clicmd:: router isis WORD [vrf NAME] + + Enable or disable the ISIS process by specifying the ISIS domain with + 'WORD'. *isisd* does not yet support multiple ISIS processes but you must + specify the name of ISIS process. The ISIS process name 'WORD' is then used + for interface (see command :clicmd:`ip router isis WORD`). + +.. clicmd:: net XX.XXXX. ... .XXX.XX + + Set/Unset network entity title (NET) provided in ISO format. + +.. clicmd:: hostname dynamic + + Enable support for dynamic hostname. + +.. clicmd:: area-password [clear | md5] + +.. clicmd:: domain-password [clear | md5] + + Configure the authentication password for an area, respectively a domain, as + clear text or md5 one. + +.. clicmd:: attached-bit [receive ignore | send] + + Set attached bit for inter-area traffic: + + - receive + If LSP received with attached bit set, create default route to neighbor + - send + If L1|L2 router, set attached bit in LSP sent to L1 router + +.. clicmd:: log-adjacency-changes + + Log changes in adjacency state. + +.. clicmd:: log-pdu-drops + + Log any dropped PDUs. + +.. clicmd:: metric-style [narrow | transition | wide] + + Set old-style (ISO 10589) or new-style packet formats: + + - narrow + Use old style of TLVs with narrow metric + - transition + Send and accept both styles of TLVs during transition + - wide + Use new style of TLVs to carry wider metric. FRR uses this as a default value + +.. clicmd:: advertise-high-metrics + + Advertise high metric value on all interfaces to gracefully shift traffic off the router. Reference: :rfc:`3277` + + For narrow metrics, the high metric value is 63; for wide metrics, 16777215; for transition metrics, 62. + +.. clicmd:: set-overload-bit + + Set overload bit to avoid any transit traffic. + +.. clicmd:: set-overload-bit on-startup (0-86400) + + Set overload bit on startup for the specified duration, in seconds. Reference: :rfc:`3277` + +.. clicmd:: purge-originator + + Enable or disable :rfc:`6232` purge originator identification. + +.. clicmd:: lsp-mtu (128-4352) + + Configure the maximum size of generated LSPs, in bytes. + +.. clicmd:: advertise-passive-only + + Advertise prefixes of passive interfaces only. + +.. _isis-timer: + +ISIS Timer +========== + +.. clicmd:: lsp-gen-interval [level-1 | level-2] (1-120) + + Set minimum interval in seconds between regenerating same LSP, + globally, for an area (level-1) or a domain (level-2). + +.. clicmd:: lsp-refresh-interval [level-1 | level-2] (1-65235) + + Set LSP refresh interval in seconds, globally, for an area (level-1) or a + domain (level-2). + +.. clicmd:: max-lsp-lifetime [level-1 | level-2] (350-65535) + + Set LSP maximum LSP lifetime in seconds, globally, for an area (level-1) or + a domain (level-2). + +.. clicmd:: spf-interval [level-1 | level-2] (1-120) + + Set minimum interval between consecutive SPF calculations in seconds. + +.. _isis-fast-reroute: + +ISIS Fast-Reroute +================= + +Unless stated otherwise, commands in this section apply to all LFA +flavors (local LFA, Remote LFA and TI-LFA). + +.. clicmd:: spf prefix-priority [critical | high | medium] WORD + + Assign a priority to the prefixes that match the specified access-list. + + By default loopback prefixes have medium priority and non-loopback prefixes + have low priority. + +.. clicmd:: fast-reroute priority-limit [critical | high | medium] [level-1 | level-2] + + Limit LFA backup computation up to the specified prefix priority. + +.. clicmd:: fast-reroute lfa tiebreaker [downstream | lowest-backup-metric | node-protecting] index (1-255) [level-1 | level-2] + + Configure a tie-breaker for multiple local LFA backups. Lower indexes are + processed first. + +.. clicmd:: fast-reroute load-sharing disable [level-1 | level-2] + + Disable load sharing across multiple LFA backups. + +.. clicmd:: fast-reroute remote-lfa prefix-list [WORD] [level-1 | level-2] + + Configure a prefix-list to select eligible PQ nodes for remote LFA + backups (valid for all protected interfaces). + +.. clicmd:: redistribute table (1-65535) [metric (0-16777215)|route-map WORD] + + Redistribute routes from a given routing table into the given ISIS + level database. + +.. _isis-region: + +ISIS region +=========== + +.. clicmd:: is-type [level-1 | level-1-2 | level-2-only] + + Define the ISIS router behavior: + + - level-1 + Act as a station router only + - level-1-2 + Act as both a station router and an area router + - level-2-only + Act as an area router only + +.. _isis-interface: + +ISIS interface +============== + +.. _ip-router-isis-word: + +.. clicmd:: router isis WORD + + Activate ISIS adjacency on this interface. Note that the name of ISIS + instance must be the same as the one used to configure the ISIS process (see + command :clicmd:`router isis WORD`). To enable IPv4, issue ``ip router isis + WORD``; to enable IPv6, issue ``ipv6 router isis WORD``. + +.. clicmd:: isis circuit-type [level-1 | level-1-2 | level-2] + + Configure circuit type for interface: + + - level-1 + Level-1 only adjacencies are formed + - level-1-2 + Level-1-2 adjacencies are formed + - level-2-only + Level-2 only adjacencies are formed + +.. clicmd:: isis csnp-interval (1-600) [level-1 | level-2] + + Set CSNP interval in seconds globally, for an area (level-1) or a domain + (level-2). + +.. clicmd:: isis hello padding + + Add padding to IS-IS hello packets. + +.. clicmd:: isis hello padding during-adjacency-formation + + Add padding to IS-IS hello packets during adjacency formation only. + +.. clicmd:: isis hello-interval [level-1 | level-2] (1-600) + + Set Hello interval in seconds globally, for an area (level-1) or a domain + (level-2). + +.. clicmd:: isis hello-multiplier [level-1 | level-2] (2-100) + + Set multiplier for Hello holding time globally, for an area (level-1) or a + domain (level-2). + +.. clicmd:: isis metric [level-1 | level-2] [(0-255) | (0-16777215)] + + Set default metric value globally, for an area (level-1) or a domain + (level-2). Max value depend if metric support narrow or wide value (see + command :clicmd:`metric-style [narrow | transition | wide]`). + +.. clicmd:: isis network point-to-point + + Set network type to 'Point-to-Point' (broadcast by default). + +.. clicmd:: isis passive + + Configure the passive mode for this interface. + +.. clicmd:: isis password [clear | md5] + + Configure the authentication password (clear or encoded text) for the + interface. + +.. clicmd:: isis priority (0-127) [level-1 | level-2] + + Set priority for Designated Router election, globally, for the area + (level-1) or the domain (level-2). + +.. clicmd:: isis psnp-interval (1-120) [level-1 | level-2] + + Set PSNP interval in seconds globally, for an area (level-1) or a domain + (level-2). + +.. clicmd:: isis three-way-handshake + + Enable or disable :rfc:`5303` Three-Way Handshake for P2P adjacencies. + Three-Way Handshake is enabled by default. + +.. clicmd:: isis fast-reroute lfa [level-1 | level-2] + + Enable per-prefix local LFA fast reroute link protection. + +.. clicmd:: isis fast-reroute lfa [level-1 | level-2] exclude interface IFNAME + + Exclude an interface from the local LFA backup nexthop computation. + +.. clicmd:: isis fast-reroute remote-lfa tunnel mpls-ldp [level-1 | level-2] + + Enable per-prefix Remote LFA fast reroute link protection. Note that other + routers in the network need to be configured to accept LDP targeted hello + messages in order for RLFA to work. + +.. clicmd:: isis fast-reroute remote-lfa maximum-metric (1-16777215) [level-1 | level-2] + + Limit Remote LFA PQ node selection within the specified metric. + +.. clicmd:: isis fast-reroute ti-lfa [level-1|level-2] [node-protection [link-fallback]] + + Enable per-prefix TI-LFA fast reroute link or node protection. + When node protection is used, option link-fallback enables the computation and use of + link-protecting LFAs for destinations unprotected by node protection. + +.. _showing-isis-information: + +Showing ISIS information +======================== + +.. clicmd:: show isis [vrf ] summary [json] + + Show summary information about ISIS. + +.. clicmd:: show isis [vrf ] hostname + + Show information about ISIS node. + +.. clicmd:: show isis [vrf ] interface [detail] [IFNAME] [json] + + Show state and configuration of ISIS specified interface, or all interfaces + if no interface is given with or without details. + +.. clicmd:: show isis [vrf ] neighbor [detail] [SYSTEMID] [json] + + Show state and information of ISIS specified neighbor, or all neighbors if + no system id is given with or without details. + +.. clicmd:: show isis [vrf ] database [detail] [LSPID] [json] + + Show the ISIS database globally, for a specific LSP id without or with + details. + +.. clicmd:: show isis [vrf ] topology [level-1|level-2] [algorithm [(128-255)]] + + Show topology IS-IS paths to Intermediate Systems, globally, in area + (level-1) or domain (level-2). + +.. clicmd:: show isis [vrf ] route [level-1|level-2] [prefix-sid|backup] [algorithm [(128-255)]] + + Show the ISIS routing table, as determined by the most recent SPF + calculation. + +.. clicmd:: show isis [vrf ] fast-reroute summary [level-1|level-2] + + Show information about the number of prefixes having LFA protection, + and network-wide LFA coverage. + + +.. _isis-traffic-engineering: + +Traffic Engineering +=================== + +.. note:: + + IS-IS-TE supports RFC 5305 (base TE), RFC 6119 (IPv6) and RFC 7810 / 8570 + (Extended Metric) with or without Multi-Topology. All Traffic Engineering + information are stored in a database formally named TED. However, best + acccuracy is provided without Multi-Topology due to inconsistency of Traffic + Engineering Advertisement of 3rd party commercial routers when MT is enabled. + At this time, FRR offers partial support for some of the routing protocol + extensions that can be used with MPLS-TE. FRR does not currently support a + complete RSVP-TE solution. + +.. clicmd:: mpls-te on + + Enable Traffic Engineering LSP flooding. + +.. clicmd:: mpls-te router-address + + Configure stable IP address for MPLS-TE. + +.. clicmd:: mpls-te router-address ipv6 + + Configure stable IPv6 address for MPLS-TE. + +.. clicmd:: mpls-te export + + Export Traffic Engineering DataBase to other daemons through the ZAPI + Opaque Link State messages. + +.. clicmd:: show isis mpls-te interface + +.. clicmd:: show isis mpls-te interface INTERFACE + + Show MPLS Traffic Engineering parameters for all or specified interface. + +.. clicmd:: show isis mpls-te router + + Show Traffic Engineering router parameters. + +.. clicmd:: show isis [vrf ] mpls-te database [detail|json] + +.. clicmd:: show isis [vrf ] mpls-te database vertex [WORD] [detail|json] + +.. clicmd:: show isis [vrf ] mpls-te database edge [A.B.C.D|X:X::X:X] [detail|json] + +.. clicmd:: show isis [vrf ] mpls-te database subnet [A.B.C.D/M|X:X::X:X/M] [detail|json] + + Show Traffic Engineering Database + +.. seealso:: + + :ref:`ospf-traffic-engineering` + +.. _isis-segment-routing: + +Segment Routing +=============== + +This is an EXPERIMENTAL support of Segment Routing as per RFC8667 +for MPLS dataplane. It supports IPv4, IPv6 and ECMP and has been +tested against Cisco & Juniper routers. + +Known limitations: + - No support for level redistribution (L1 to L2 or L2 to L1) + - No support for binding SID + - No support for SRMS + - No support for SRLB + - Only one SRGB and default SPF Algorithm is supported + +.. clicmd:: segment-routing on + + Enable Segment Routing. + +.. clicmd:: segment-routing global-block (16-1048575) (16-1048575) [local-block (16-1048575) (16-1048575)] + + Set the Segment Routing Global Block i.e. the label range used by MPLS + to store label in the MPLS FIB for Prefix SID. Note that the block size + may not exceed 65535. Optionally sets also the Segment Routing Local Block. + The negative command always unsets both. + +.. clicmd:: segment-routing node-msd (1-16) + + Set the Maximum Stack Depth supported by the router. The value depend of the + MPLS dataplane. E.g. for Linux kernel, since version 4.13 the maximum value + is 32. + +.. clicmd:: segment-routing prefix [algorithm (128-255)] `_ on an MPLS Segment-Routing +dataplane. The compatibility has been tested against Cisco. + +IS-IS uses by default the `Shortest-Path-First` algorithm that basically +calculates paths based on the shortest total metric to the destinations. +Flex-Algo allows new algorithms to run in parallel to compute paths in different +manners, based on metrics (IGP metric or a new type of metrics such as Traffic +Engineering (TE) metric and minimum delay...) and constraints. New metric types +are not yet implemented but constraints are already operational. Constraints can +restrict paths to links with specific affinities or avoid links with specific +affinities. Combinations of these are also possible. + +The administrator can configure up to 128 Flex-Algos in an IS-IS area. +To do so, it defines a set of Flex-Algo Definitions (FAD) which +have the following characteristics: + +- a numeric identifier (ID) between 128 and 255 inclusive + +- a set of constraints (basically, include or exclude a certain given set of + links, designated by a admin-group) + +- the calculation type (only the `Shortest-Path-First` is currently supported) + +- the metric type (only the IGP inherited metric type is currently supported) + +- some additional flags (not supported for the moment). + +A subset of routers advertises the Flex-Algo Definitions (FAD) to the other +routers within an area. In order to use a common set of FADs, each router runs a +FAD election process for each locally configured algorithm, using the following +rules: + +- If a locally configured FAD is not advertised to the area, the router does not + participate in the particular flex algorithm. + +- If a given flex algorithm is running, the participation in this particular + flex algorithm stops when its advertisements are over. + +- A router includes its own FAD in the election process if and only if it is + advertised to the other routers. + +- If only one router advertises the FAD, the FAD is elected. + +- If several FADs are advertised with different priorities, the one with the + highest priority value is selected. + +- If there are multiple advertisements of the FAD with the same highest + priority, the FAD of the router with the highest IS-IS system-ID is + selected. + +Routers only use the specifications of the elected FAD regardless of the locally +configured definitions. If a router does not support one of the FAD +characteristics, it stops participating in the Flex-Algo. + +For each running Flex-Algo, the Segment-Routing SIDs must be +configured with values unique to the algorithm. It allows routers to identify +which flex algorithm they must use for a given packet. + +The following commands configure Flex-Algo at the 'router isis' configuration +level. Segment-Routing prefixes must be configured for the Flex-Algo. + +.. clicmd:: flex-algo (128-255) + + Add a Flex-Algo Definition (FAD) and enter the FAD configuration + level. The algorithm ID value is in the range of 128 to 255 inclusive. + +.. clicmd:: affinity-map NAME bit-position (0-255) + + Add the specified 'affinity-map'. Affinity-map definitions are used in + FADs and in interfaces admin-group definition. + + Affinity-maps format in advertisement TLVs use the extended admin-group + format defined in the RFC7308 section 2.2. The extended admin-group uses a + 256 bits field. If an affinity-map is set, the bit at the extended + admin-group 'bit-position' is set 1, else it is set to 0. + +The following commands configure Flex-Algo at the 'router isis' and +'flex-algo (128-255)' configuration level. + +.. clicmd:: advertise-definition + + Advertise the current FAD to other IS-IS routers by using specific IS-IS + TLVs. By default, the definition is is not shared with other routers. + +   A router can advertise a FAD without participating in the Flex-Algo. + +.. clicmd:: priority (0-255) + + Set the specified 'priority' in the current FAD advertisements . + +.. clicmd:: metric-type [igp|te|delay] + + Set the 'metric-type' for the current FAD. 'igp' is + the default value and refers to the classic 'Shortest-Path-First' algorithm. + If the 'te' or the 'delay' metric is selected, the value is advertised but + the flex algorithm is disabled locally because these types are not currently + supported. + +.. clicmd:: no metric-type + + Reset the 'metric-type' to the default 'igp' metric. + +.. clicmd:: affinity exclude-any NAME + + Add the specified affinity to the list of exclude-any affinities. The + Flex-Algo will compute paths that exclude the segments with any of + the specified affinities. + +.. clicmd:: no affinity exclude-any NAME + + Remove the specified affinity to the list of exclude-any affinities. + +.. clicmd:: affinity include-all NAME + + Add the specified affinity to the list of include-all affinities. The + Flex-Algo will compute paths that include the segments with all + the specified affinities. + +.. clicmd:: no affinity include-all NAME + + Remove the specified affinity to the list of include-all affinities. + +.. clicmd:: affinity include-any NAME + + Add the specified affinity to the list of include-any affinities. The + Flex-Algo will compute paths that include the segments with any of + the specified affinities. + +.. clicmd:: no affinity include-any NAME + + Remove the specified affinity to the list of include-any affinities. + +The following commands configure Flex-Algo at the 'interface' configuration +level. + +.. clicmd:: isis affinity flex-algo NAME + + Add the specified affinity to the interface. + +.. clicmd:: no isis affinity flex-algo NAME + + Remove the specified affinity from the interface. + +The following command show Flex-Algo information: + +.. clicmd:: show isis flex-algo [(128-255)] + + Show information about the elected FADs + +'show isis route', 'show isis topology' and 'show isis segment-routing node' +includes an 'algorithm (128-255)' optional argument. See +:ref:`showing-isis-information` and :ref:`isis-segment-routing`. + +.. _isis-srv6: + +Segment Routing over IPv6 (SRv6) +================================ + +This feature enables extensions in IS-IS to support Segment Routing over IPv6 +data plane (SRv6) as per RFC 9352. + +.. clicmd:: segment-routing srv6 + + Enable Segment Routing over IPv6 data plane (SRv6). + +.. clicmd:: locator NAME + + Specify the SRv6 locator to use for SRv6. The locator must be configured in + Zebra. Once the locator is configured, IS-IS automatically allocates prefix + SID and adjacency SIDs, creates local SID entries in the data plane, and + advertises them in the IGP domain. + +.. clicmd:: interface NAME + + Specify the dummy interface used to install SRv6 SIDs in the Linux data plane. + The interface must be created manually. By default, the interface is 'sr0'. + The interface can be created using the iproute2 utility: + + .. code-block:: bash + + ip link add sr0 type dummy + ip link set sr0 up + +.. clicmd:: show isis segment-routing srv6 node + + Show detailed information about all learned SRv6 Nodes. + +Debugging ISIS +============== + +.. clicmd:: debug isis adj-packets + + IS-IS Adjacency related packets. + +.. clicmd:: debug isis events + + IS-IS Events. + +.. clicmd:: debug isis packet-dump + + IS-IS packet dump. + +.. clicmd:: debug isis route-events + + IS-IS Route related events. + +.. clicmd:: debug isis snp-packets + + IS-IS CSNP/PSNP packets. + +.. clicmd:: debug isis spf-events + + IS-IS Shortest Path First Events. + +.. clicmd:: debug isis update-packets + + + Update related packets. + +.. clicmd:: debug isis te-events + + IS-IS Traffic Engineering events + +.. clicmd:: debug isis sr-events + + + IS-IS Segment Routing events. + +.. clicmd:: debug isis lfa + + + IS-IS LFA events. + +.. clicmd:: show debugging isis + + Print which ISIS debug level is activate. + +.. _isis-config-examples: + +ISIS Configuration Examples +=========================== + +A simple example, with MD5 authentication enabled: + +.. code-block:: frr + + ! + interface eth0 + ip router isis FOO + isis network point-to-point + isis circuit-type level-2-only + ! + router isis FOO + net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 + metric-style wide + is-type level-2-only + + +A Traffic Engineering configuration, with Inter-ASv2 support. + +First, the :file:`zebra.conf` part: + +.. code-block:: frr + + hostname HOSTNAME + password PASSWORD + log file /var/log/zebra.log + ! + interface eth0 + ip address 10.2.2.2/24 + link-params + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + admin-grp 0xab + ! + interface eth1 + ip address 10.1.1.1/24 + link-params + enable + metric 100 + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + neighbor 10.1.1.2 as 65000 + + +Then the :file:`isisd.conf` itself: + +.. code-block:: frr + + hostname HOSTNAME + password PASSWORD + log file /var/log/isisd.log + ! + ! + interface eth0 + ip router isis FOO + ! + interface eth1 + ip router isis FOO + ! + ! + router isis FOO + isis net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 + mpls-te on + mpls-te router-address 10.1.1.1 + ! + line vty + +A Segment Routing configuration, with IPv4, IPv6, SRGB and MSD configuration. + +.. code-block:: frr + + hostname HOSTNAME + password PASSWORD + log file /var/log/isisd.log + ! + ! + interface eth0 + ip router isis SR + isis network point-to-point + ! + interface eth1 + ip router isis SR + ! + ! + router isis SR + net 49.0000.0000.0000.0001.00 + is-type level-1 + topology ipv6-unicast + lsp-gen-interval 2 + segment-routing on + segment-routing node-msd 8 + segment-routing prefix 10.1.1.1/32 index 100 explicit-null + segment-routing prefix 2001:db8:1000::1/128 index 101 explicit-null + ! + +An SRv6 configuration: + +.. code-block:: frr + + hostname HOSTNAME + password PASSWORD + log file /var/log/isisd.log + ! + ! + interface eth0 + ipv6 router isis FOO + ip router isis FOO + isis hello-interval 5 + ! + interface eth1 + ip router isis FOO + ! + ! + router isis FOO + net 49.0001.1111.1111.1111.00 + is-type level-2-only + metric-style wide + segment-routing srv6 + locator loc1 + ! + line vty + + +.. _isis-vrf-config-examples: + +ISIS Vrf Configuration Examples +=============================== + +A simple vrf example: + +.. code-block:: frr + + ! + interface eth0 vrf RED + ip router isis FOO + isis network point-to-point + isis circuit-type level-2-only + ! + router isis FOO vrf RED + net 47.0023.0000.0000.0000.0000.0000.0000.1900.0004.00 + metric-style wide + is-type level-2-only diff --git a/doc/user/kernel.rst b/doc/user/kernel.rst new file mode 100644 index 0000000..210ede7 --- /dev/null +++ b/doc/user/kernel.rst @@ -0,0 +1,36 @@ +.. _kernel-interface: + +**************** +Kernel Interface +**************** + +There are several different methods for reading kernel routing table +information, updating kernel routing tables, and for looking up interfaces. +FRR relies heavily on the Netlink (``man 7 netlink``) interface to +communicate with the Kernel. However, other interfaces are still used +in some parts of the code. + +- ioctl + This method is a very traditional way for reading or writing kernel + information. `ioctl` can be used for looking up interfaces and for + modifying interface addresses, flags, mtu settings and other types of + information. Also, `ioctl` can insert and delete kernel routing table + entries. It will soon be available on almost any platform which zebra + supports, but it is a little bit ugly thus far, so if a better method is + supported by the kernel, zebra will use that. + +- sysctl + This is a program that can lookup kernel information using MIB (Management + Information Base) syntax. Normally, it only provides a way of getting + information from the kernel. So one would usually want to change kernel + information using another method such as `ioctl`. + +- proc filesystem + This is a special filesystem mount that provides an easy way of getting + kernel information. + +- routing socket / Netlink + Netlink first appeard in Linux kernel 2.0. It makes asynchronous + communication between the kernel and FRR possible, similar to a routing + socket on BSD systems. Netlink communication is done by reading/writing + over Netlink socket. diff --git a/doc/user/ldpd.rst b/doc/user/ldpd.rst new file mode 100644 index 0000000..cbed734 --- /dev/null +++ b/doc/user/ldpd.rst @@ -0,0 +1,377 @@ +.. _ldp: + +*** +LDP +*** + +The *ldpd* daemon is a standardised protocol that permits exchanging MPLS label +information between MPLS devices. The LDP protocol creates peering between +devices, so as to exchange that label information. This information is stored in +MPLS table of *zebra*, and it injects that MPLS information in the underlying +system (Linux kernel or OpenBSD system for instance). +*ldpd* provides necessary options to create a Layer 2 VPN across MPLS network. +For instance, it is possible to interconnect several sites that share the same +broadcast domain. + +FRR implements LDP as described in :rfc:`5036`; other LDP standard are the +following ones: :rfc:`6720`, :rfc:`6667`, :rfc:`5919`, :rfc:`5561`, :rfc:`7552`, +:rfc:`4447`. +Because MPLS is already available, FRR also supports :rfc:`3031`. + +Running Ldpd +============ + +The *ldpd* daemon can be invoked with any of the common +options (:ref:`common-invocation-options`). + +.. option:: --ctl_socket + + This option allows you to override the path to the ldpd.sock file + used to control this daemon. If specified this option overrides + the -N option path addition. + +The *zebra* daemon must be running before *ldpd* is invoked. + +.. include:: config-include.rst + +.. _understanding-ldp: + +Understanding LDP principles +============================ + +Let's first introduce some definitions that permit understand better the LDP +protocol: + +- `LSR` : Labeled Switch Router. Networking devices handling labels used to + forward traffic between and through them. + +- `LER` : Labeled Edge Router. A Labeled edge router is located at the edge of + an MPLS network, generally between an IP network and an MPLS network. + + +``LDP`` aims at sharing label information across devices. It tries to establish +peering with remote LDP capable devices, first by discovering using UDP port 646 +, then by peering using TCP port 646. Once the TCP session is established, the +label information is shared, through label advertisements. + +There are different methods to send label advertisement modes. The +implementation actually supports the following : Liberal Label Retention + +Downstream Unsolicited + Independent Control. +The other advertising modes are depicted below, and compared with the current +implementation. + +- Liberal label retention versus conservative mode + In liberal mode, every label sent by every LSR is stored in the MPLS table. + In conservative mode, only the label that was sent by the best next hop + (determined by the IGP metric) for that particular FEC is stored in the MPLS + table. + +- Independent LSP Control versus ordered LSP Control + MPLS has two ways of binding labels to FEC’s; either through ordered LSP + control, or independent LSP control. + Ordered LSP control only binds a label to a FEC if it is the egress LSR, or + the router received a label binding for a FEC from the next hop router. In + this mode, an MPLS router will create a label binding for each FEC and + distribute it to its neighbors so long as he has a entry in the RIB for the + destination. + In the other mode, label bindings are made without any dependencies on another + router advertising a label for a particular FEC. Each router makes it own + independent decision to create a label for each FEC. + By default IOS uses Independent LSP Control, while Juniper implements the + Ordered Control. Both modes are interoperable, the difference is that Ordered + Control prevent blackholing during the LDP convergence process, at cost of + slowing down the convergence itself + +- unsolicited downstream versus downstream on demand + Downstream on demand label distribution is where an LSR must explicitly + request that a label be sent from its downstream router for a particular FEC. + Unsolicited label distribution is where a label is sent from the downstream + router without the original router requesting it. + +.. _configuring-ldpd: + +.. _ldp-configuration: + +LDP Configuration +=================== + +.. clicmd:: mpls ldp + + Enable or disable LDP daemon + +.. clicmd:: router-id A.B.C.D + + The following command located under MPLS router node configures the MPLS + router-id of the local device. + +.. clicmd:: ordered-control + + Configure LDP Ordered Label Distribution Control. + +.. clicmd:: address-family [ipv4 | ipv6] + + Configure LDP for IPv4 or IPv6 address-family. Located under MPLS route node, + this subnode permits configuring the LDP neighbors. + +.. clicmd:: interface IFACE + + Located under MPLS address-family node, use this command to enable or disable + LDP discovery per interface. IFACE stands for the interface name where LDP is + enabled. By default it is disabled. Once this command executed, the + address-family interface node is configured. + +.. clicmd:: discovery transport-address A.B.C.D | A:B::C:D + + Located under mpls address-family interface node, use this command to set + the IPv4 or IPv6 transport-address used by the LDP protocol to talk on this + interface. + +.. clicmd:: ttl-security disable + + Located under the LDP address-family node, use this command to disable the + GTSM procedures described in RFC 6720 (for the IPv4 address-family) and + RFC 7552 (for the IPv6 address-family). + + Since GTSM is mandatory for LDPv6, the only effect of disabling GTSM for the + IPv6 address-family is that *ldpd* will not discard packets with a hop limit + below 255. This may be necessary to interoperate with older implementations. + Outgoing packets will still be sent using a hop limit of 255 for maximum + compatibility. + + If GTSM is enabled, multi-hop neighbors should have either GTSM disabled + individually or configured with an appropriate ttl-security hops distance. + +.. clicmd:: neighbor A.B.C.D password PASSWORD + + The following command located under MPLS router node configures the router + of a LDP device. This device, if found, will have to comply with the + configured password. PASSWORD is a clear text password wit its digest sent + through the network. + +.. clicmd:: neighbor A.B.C.D holdtime HOLDTIME + + The following command located under MPLS router node configures the holdtime + value in seconds of the LDP neighbor ID. Configuring it triggers a keepalive + mechanism. That value can be configured between 15 and 65535 seconds. After + this time of non response, the LDP established session will be considered as + set to down. By default, no holdtime is configured for the LDP devices. + +.. clicmd:: neighbor A.B.C.D ttl-security disable + + Located under the MPLS LDP node, use this command to override the global + configuration and enable/disable GTSM for the specified neighbor. + +.. clicmd:: neighbor A.B.C.D ttl-security hops (1-254) + + Located under the MPLS LDP node, use this command to set the maximum number + of hops the specified neighbor may be away. When GTSM is enabled for this + neighbor, incoming packets are required to have a TTL/hop limit of 256 + minus this value, ensuring they have not passed through more than the + expected number of hops. The default value is 1. + +.. clicmd:: discovery hello holdtime HOLDTIME + +.. clicmd:: discovery hello interval INTERVAL + + INTERVAL value ranges from 1 to 65535 seconds. Default value is 5 seconds. + This is the value between each hello timer message sent. + HOLDTIME value ranges from 1 to 65535 seconds. Default value is 15 seconds. + That value is added as a TLV in the LDP messages. + +.. clicmd:: dual-stack transport-connection prefer ipv4 + + When *ldpd* is configured for dual-stack operation, the transport connection + preference is IPv6 by default (as specified by :rfc:`7552`). On such + circumstances, *ldpd* will refuse to establish TCP connections over IPv4. + You can use above command to change the transport connection preference to + IPv4. In this case, it will be possible to distribute label mappings for + IPv6 FECs over TCPv4 connections. + +.. _show-ldp-information: + +Show LDP Information +==================== + +These commands dump various parts of *ldpd*. + +.. clicmd:: show mpls ldp neighbor [A.B.C.D] + + This command dumps the various neighbors discovered. Below example shows that + local machine has an operation neighbor with ID set to 1.1.1.1. + + :: + + west-vm# show mpls ldp neighbor + AF ID State Remote Address Uptime + ipv4 1.1.1.1 OPERATIONAL 1.1.1.1 00:01:37 + west-vm# + +.. clicmd:: show mpls ldp neighbor [A.B.C.D] capabilities + +.. clicmd:: show mpls ldp neighbor [A.B.C.D] detail + + Above commands dump other neighbor information. + +.. clicmd:: show mpls ldp discovery [detail] + +.. clicmd:: show mpls ldp ipv4 discovery [detail] + +.. clicmd:: show mpls ldp ipv6 discovery [detail] + + Above commands dump discovery information. + +.. clicmd:: show mpls ldp ipv4 interface + +.. clicmd:: show mpls ldp ipv6 interface + + Above command dumps the IPv4 or IPv6 interface per where LDP is enabled. + Below output illustrates what is dumped for IPv4. + + :: + + west-vm# show mpls ldp ipv4 interface + AF Interface State Uptime Hello Timers ac + ipv4 eth1 ACTIVE 00:08:35 5/15 0 + ipv4 eth3 ACTIVE 00:08:35 5/15 1 + + +.. clicmd:: show mpls ldp ipv4|ipv6 binding + + Above command dumps the binding obtained through MPLS exchanges with LDP. + + :: + + west-vm# show mpls ldp ipv4 binding + AF Destination Nexthop Local Label Remote Label In Use + ipv4 1.1.1.1/32 1.1.1.1 16 imp-null yes + ipv4 2.2.2.2/32 1.1.1.1 imp-null 16 no + ipv4 10.0.2.0/24 1.1.1.1 imp-null imp-null no + ipv4 10.115.0.0/24 1.1.1.1 imp-null 17 no + ipv4 10.135.0.0/24 1.1.1.1 imp-null imp-null no + ipv4 10.200.0.0/24 1.1.1.1 17 imp-null yes + west-vm# + + +LDP debugging commands +======================== + + +.. clicmd:: debug mpls ldp KIND + + Enable or disable debugging messages of a given kind. ``KIND`` can + be one of: + + - ``discovery`` + - ``errors`` + - ``event`` + - ``labels`` + - ``messages`` + - ``zebra`` + + +Sample configuration +==================== + +Below configuration gives a typical MPLS configuration of a device located in a +MPLS backbone. LDP is enabled on two interfaces and will attempt to peer with +two neighbors with router-id set to either 1.1.1.1 or 3.3.3.3. + +.. code-block:: frr + + mpls ldp + router-id 2.2.2.2 + neighbor 1.1.1.1 password test + neighbor 3.3.3.3 password test + ! + address-family ipv4 + discovery transport-address 2.2.2.2 + ! + interface eth1 + ! + interface eth3 + ! + exit-address-family + ! + + +Deploying LDP across a backbone generally is done in a full mesh configuration +topology. LDP is typically deployed with an IGP like OSPF, that helps discover +the remote IPs. Below example is an OSPF configuration extract that goes with +LDP configuration + +.. code-block:: frr + + router ospf + ospf router-id 2.2.2.2 + network 0.0.0.0/0 area 0 + ! + + +Below output shows the routing entry on the LER side. The OSPF routing entry +(10.200.0.0) is associated with Label entry (17), and shows that MPLS push action +that traffic to that destination will be applied. + +:: + + north-vm# show ip route + Codes: K - kernel route, C - connected, S - static, R - RIP, + O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, + T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, + F - PBR, + > - selected route, * - FIB route + + O>* 1.1.1.1/32 [110/120] via 10.115.0.1, eth2, label 16, 00:00:15 + O>* 2.2.2.2/32 [110/20] via 10.115.0.1, eth2, label implicit-null, 00:00:15 + O 3.3.3.3/32 [110/10] via 0.0.0.0, loopback1 onlink, 00:01:19 + C>* 3.3.3.3/32 is directly connected, loopback1, 00:01:29 + O>* 10.0.2.0/24 [110/11] via 10.115.0.1, eth2, label implicit-null, 00:00:15 + O 10.100.0.0/24 [110/10] is directly connected, eth1, 00:00:32 + C>* 10.100.0.0/24 is directly connected, eth1, 00:00:32 + O 10.115.0.0/24 [110/10] is directly connected, eth2, 00:00:25 + C>* 10.115.0.0/24 is directly connected, eth2, 00:00:32 + O>* 10.135.0.0/24 [110/110] via 10.115.0.1, eth2, label implicit-null, 00:00:15 + O>* 10.200.0.0/24 [110/210] via 10.115.0.1, eth2, label 17, 00:00:15 + north-vm# + + +Additional example demonstrating use of some miscellaneous config options: + +.. code-block:: frr + + interface eth0 + ! + interface eth1 + ! + interface lo + ! + mpls ldp + dual-stack cisco-interop + neighbor 10.0.1.5 password opensourcerouting + neighbor 172.16.0.1 password opensourcerouting + ! + address-family ipv4 + discovery transport-address 10.0.1.1 + label local advertise explicit-null + ! + interface eth0 + ! + interface eth1 + ! + ! + address-family ipv6 + discovery transport-address 2001:db8::1 + ! + interface eth1 + ! + ! + ! + l2vpn ENG type vpls + bridge br0 + member interface eth2 + ! + member pseudowire mpw0 + neighbor lsr-id 1.1.1.1 + pw-id 100 + ! + ! + diff --git a/doc/user/mgmtd.rst b/doc/user/mgmtd.rst new file mode 100644 index 0000000..8b197bb --- /dev/null +++ b/doc/user/mgmtd.rst @@ -0,0 +1,434 @@ +.. _mgmtd: + +************************* +MGMTd (Management Daemon) +************************* + +The FRR Management Daemon (from now on referred to as MGMTd) is a new +centralized entity representing the FRR Management Plane which can take +management requests from any kind of UI/Frontend entity (e.g. CLI, Netconf, +Restconf, Grpc etc.) over a new unified and common Frontend interface and +can help maintain configurational data or retrieve operational data from +any number of FRR managed entities/components that have been integrated +with the new FRR Centralised Management Framework. + +For organizing the management data to be owned by the FRR Management plane, +management data is stored in YANG in compliance with a pre-defined set +of YANG based schema. Data shall also be stored/retrieved in YANG format only. + +The MGMTd also acts as a separate computational entity for offloading much +of the management related computational overload involved in maintaining of +management data and processing of management requests, from individual +component daemons (which can otherwise be a signficant burden on the individual +components, affecting performance of its other functionalities). + +Lastly, the MGMTd works in-tandem with one (or more) MGMT Frontend +Clients and a bunch of MGMT Backend Clients to realize the entirety +of the FRR Management plane. Some of the advanatages of this new framework +are: + + 1. Consolidation and management of all Management data by a single entity. + 2. Better control over configuration validation, commit and rollback. + 3. Faster collection of configuration data (without needing to involve + individual component daemons). + 4. Offload computational burden of YANG data parsing and validations + of new configuration data being provisoned away from individual + component daemons + 5. Improve performance of individual component daemons while loading + huge configuration or retrieving huge operational dataset. + +The new FRR Management Daemon consists of the following sub-components: + - MGMT Frontend Interface + - MGMT Backend Interface + - MGMT Transaction Engine + +.. _mgmt_fe: + +MGMT Frontend Interface +======================= + +The MGMT Frontend Interface is a bunch of message-based APIs that lets +any UI/Frontend client to interact with the MGMT daemon to requests a +set of management operations on a specific datastore/database. +Following is a list of databases/datastores supported by the MGMT +Frontend Interface and MGMTd: + + - Candidate Database: + + - Consists of configuration data items only. + - Data can be edited anytime using SET_CONFIG API. + - Data can be retrieved anytime using GET_CONFIG/GET_DATA API. + + - Running Database: + + - Consists of configuration data items only. + - Data cannot be edited using SET_CONFIG API. + - Data can only be modified using COMMIT_CONFIG API after which un-committed + data from Candidate database will be first validated and applied to + individualBackend component(s). Only on successful validation and apply on + all individual components will the new data be copied over to the Running + database. + - Data can be retrieved anytime using GET_CONFIG/GET_DATA API. + + - Operational Database: + + - Consists of non-configurational data items. + - Data is not stored on MGMT daemon. Rather it will be need to be fetched + in real-time from the corresponding Backend component (if present). + - Data can be retrieved anytime using GET_DATA API. + +Frontend Clients connected to MGMTd via Frontend Interface can themselves have +multiple connections from one (or more) of its own remote clients. The MGMT +Frontend Interface supports reresenting each of the remote clients for a given +Frontend client(e.g. Netconf clients on a single Netconf server) as individual +Frontend Client Sessions. So a single connection from a single Frontend Client +can create more than one Frontend Client sessions. + +Following are some of the management operations supported: + - INIT_SESSION/CLOSE_SESSION: Create/Destroy a session. Rest of all the + operations are supported only in the context of a specific session. + - LOCK_DB/UNLOCK_DB: Lock/Unlock Management datastores/databases. + - GET_CONFIG/GET_DATA: Retrieve configurational/operational data from a + specific datastore/database. + - SET_CONFIG/DELETE_CONFIG: Add/Modify/Delete specific data in a specific + datastore/database. + - COMMIT_CONFIG: Validate and/or apply the uncommited set of configurations + from one configuration database to another. + - Currently committing configurations from Candidate to Running database + is only allowed, and not vice versa. + +Front-End Native Protobuf API +""""""""""""""""""""""""""""" +The exact set of message-based APIs are represented as Google Protobuf +messages and can be found in the following file distributed with FRR codebase. + +.. code-block:: frr + + lib/mgmt.proto + +Front-End Native (non-protobuf) API +""""""""""""""""""""""""""""""""""" +Additionally there exists a "native" API that does not utilize ``protobuf``s +this native API and the front-end messages and structures it supports are +documented in the header file ``lib/mgmt_msg_native.h``. + +Connecting to MGMTd +""""""""""""""""""" +The MGMT daemon implements a MGMT Frontend Server that opens a UNIX +socket-based IPC channel on the following path to listen for incoming +connections from all possible Frontend clients: + +.. code-block:: frr + + /var/run/frr/mgmtd_fe.sock + +Each connection received from a Frontend client is managed and tracked +as a MGMT Frontend adapter by the MGMT Frontend Adapter sub-component +implemented by MGMTd. + +To facilitate faster development/integration of Frontend clients with +MGMT Frontend Interface, a C-based library has been developed. The API +specification of this library can be found at: + +.. code-block:: frr + + lib/mgmt_fe_client.h + +Following is a list of protobuf message types supported on the MGMT Frontend +Interface: + + - SESSION_REQ + - SESSION_REPLY + - LOCK_DB_REQ + - LOCK_DB_REPLY + - UNLOCK_DB_REQ + - UNLOCK_DB_REPLY + - GET_CONFIG_REQ + - GET_CONFIG_REPLY + - SET_CONFIG_REQ + - SET_CONFIG_REPLY + - COMMIT_CONFIG_REQ + - COMMIT_CONFIG_REPLY + - GET_DATA_REQ + - GET_DATA_REPLY + +Following is a list of native messages types supported by the MGMTd Front-End +API: + + - ERROR (receive) - received in response to any sent native message. + - TREE_DATA (receive) - returned data from a datastore + - GET_DATA (send) - get a tree of data + - NOTIFY (receive) - a notification received from mgmtd + - EDIT (send) - edit configuration datastore + - EDIT_REPLY (receive) - reply for an edit operation + - RPC (send) - sending (invoking) an RPC. + - RPC_REPLY (receive) - reply from invoking an RPC + - NOTIFY_SELECT (send) - specify the sub-set of notifications the front-end + wishes to receive, rather than the default of receiving all. + + +Please refer to the MGMT Frontend Client Developers Reference and Guide +(coming soon) for more details. + +MGMTD Backend Interface +======================= +The MGMT Backend Interface is a bunch of message-based APIs that can be +used by individual component daemons like BGPd, Staticd, Zebra to connect +with MGMTd and utilize the new FRR Management Framework to let any Frontend +clients to retrieve any operational data or manipulate any configuration data +owned by the individual daemon component. + +Like the MGMT Frontend Interface, the MGMT Backend Interface is is also +comprised of the following: + + - MGMT Backend Server (running on MGMT daemon) + - MGMT Backend Adapter (running on MGMT daemon) + - MGMT Backend client (running on Backend component daemons) + +The MGMT Backend Client and MGMT Backend Adapter sub-component communicates +using a specific set of message-based APIs. + +The exact set of message-based APIs are represented as Google Protobuf +messages and can be found in the following file distributed with FRR codebase. + +.. code-block:: frr + + lib/mgmt.proto + +The MGMT daemon implements a MGMT Backend Server that opens a UNIX +socket-based IPC channel on the following path to listen for incoming +connections from all possible Backend clients: + +.. code-block:: frr + + /var/run/frr/mgmtd_be.sock + +Each connection received from a Backend client is managed and tracked +as a MGMT Backend adapter by the MGMT Backend Adapter sub-component +implemented by MGMTd. + +To facilitate faster development/integration of Backend clients with +MGMTd, a C-based library has been developed. The API specification +of this library can be found at: + +.. code-block:: frr + + lib/mgmt_be_client.h + +Following is a list of message types supported on the MGMT Backend Interface: + + - SUBSCRIBE_REQ + - SUBSCRIBE_REPLY + - TXN_REQ + - TXN_REPLY + - CREATE_CFGDATA_REQ + - CREATE_CFGDATA_ERROR + - VALIDATE_CFGDATA_REQ + - VALIDATE_CFGDATA_REPLY + - APPLY_CFGDATA_REQ + - APPLY_CFGDATA_REPLY + - GET_OPERDATA_REQ + - GET_OPERDATA_REPLY + +Please refer to the MGMT Backend Client Developers Reference and Guide +(coming soon) for more details. + +MGMTD Transaction Engine +======================== + +The MGMT Transaction sub-component is the main brain of the MGMT daemon that +takes management requests from one (or more) Frontend Client translates +them into transactions and drives them to completion in co-oridination with +one (or more) Backend client daemons involved in the request. + +A transaction can be seen as a set of management procedures executed over +the Backend Interface with one (or more) individual Backend component +daemons, as a result of some management request initiated from a specific +Frontend client session. These group of operations on the Backend Interface +with one (or more) individual components involved should be executed without +taking any further management requests from other Frontend client sessions. +To maintain this kind of atomic behavior a lock needs to be acquired +(sometimes implicitly if not explicitly) by the corresponding Frontend client +session, on the various datastores/databases involved in the management request +being executed. The same datastores/databases need to be unlocked when all +the procedures have been executed and the transaction is being closed. + +Following are some of the transaction types supported by MGMT: + + - Configuration Transactions + + - Used to execute management operations like SET_CONFIG and COMMIT_CONFIG + that involve writing/over-writing the contents of Candidate and Running + databases. + - One (and only) can be created and be in-progress at any given time. + - Once initiated by a specific Frontend Client session and is still + in-progress, all subsequent SET_CONFIG and COMMIT_CONFIG operations + from other Frontend Client sessions will be rejected and responded + with failure. + - Requires acquiring write-lock on Candidate (and later Running) databases. + + - Show Transactions + + - Used to execute management operations like GET_CONFIG and GET_DATA + that involve only reading the contents of Candidate and Running + databases (and sometimes real-time retrieval of operational data + from individual component daemons). + - Multiple instance of this transaction type can be created and be + in-progress at any given time. + - However, when a configuration transaction is currently in-progress + show transaction can be initiated by any Frontend Client session. + - Requires acquiring read-lock on Candidate and/or Running databases. + - NOTE: Currently GET_DATA on Operational database is NOT supported. To + be added in a future time soon. + +MGMTD Configuration Rollback and Commit History +=============================================== + +The MGMT daemon maintains upto 10 last configuration commit buffers +and can rollback the contents of the Running Database to any of the +commit-ids maintained in the commit buffers. + +Once the number of commit buffers exceeds 10, the oldest commit +buffer is deleted to make space for the latest commit. Also on +rollback to a specific commit-id, buffer of all the later commits +are deleted from commit record. + +Configuration rollback is only allowed via VTYSH shell as of today +and is not possible through the MGMT Frontend interface. + +MGMT Configuration commands +=========================== + +.. clicmd:: mgmt set-config XPATH VALUE + + This command uses a SET_CONFIG request over the MGMT Frontend Interface + for the specified xpath with specific value. This command is used for + testing purpose only. But can be used to set configuration data from CLI + using SET_CONFIG operations. + +.. clicmd:: mgmt delete-config XPATH + + This command uses a SET_CONFIG request (with delete option) over the + MGMT Frontend Interface o delete the YANG data node at the given + xpath unless it is a key-leaf node(in which case it is not deleted). + +.. clicmd:: mgmt load-config FILE + + This command loads configuration in JSON format from the filepath specified, + and merges or replaces the Candidate DB as per the option specified. + +.. clicmd:: mgmt save-config FILE + + This command dumps the DB specified in the db-name into the file in JSON + format. This command in not supported for the Operational DB. + +.. clicmd:: mgmt commit abort + + This command will abort any configuration present on the Candidate but not + been applied to the Running DB. + +.. clicmd:: mgmt commit apply + + This command commits any uncommited changes in the Candidate DB to the + Running DB. + +.. clicmd:: mgmt commit check + + This command validates the configuration but does not apply them to the + Running DB. + +.. clicmd:: mgmt rollback commit-id WORD + + This command rolls back the Running Database contents to the state + corresponding to the commit-id specified. + +.. clicmd:: mgmt rollback last WORD + + This command rolls back the last specified number of recent commits. + + +MGMT Show commands +================== + +.. clicmd:: show mgmt backend-adapter all + + This command shows the backend adapter information and the clients/daemons + connected to the adapters. + +.. clicmd:: show mgmt backend-yang-xpath-registry + + This command shows which Backend adapters are registered for which YANG + data subtree(s). + +.. clicmd:: show mgmt frontend-adapter all [detail] + + This command shows the frontend adapter information and the clients + connected to the adapters. + +.. clicmd:: show mgmt transaction all + + Shows the list of transaction and bunch of information about the transaction. + +.. clicmd:: show mgmt get-config [candidate|running] XPATH + + This command uses the GET_CONFIG operation over the MGMT Frontend interface and + returns the xpaths and values of the nodes of the subtree pointed by the . + +.. clicmd:: show mgmt get-data [candidate|operation|running] XPATH + + This command uses the GET_DATA operation over the MGMT Frontend interface and + returns the xpaths and values of the nodes of the subtree pointed by the . + Currenlty supported values for 'candidate' and 'running' only + ('operational' shall be supported in future soon). + +.. clicmd:: show mgmt datastore-contents [candidate|operation|running] [xpath WORD] [file WORD] json|xml + + This command dumps the subtree pointed by the xpath in JSON or XML format. If filepath is + not present then the tree will be printed on the shell. + +.. clicmd:: show mgmt commit-history + + This command dumps details of upto last 10 commits handled by MGMTd. + + +MGMT Daemon debug commands +========================== + +The following debug commands enable debugging within the management daemon: + +.. clicmd:: debug mgmt backend + + Enable[/Disable] debugging messages related to backend operations within the + management daemon. + +.. clicmd:: debug mgmt datastore + + Enable[/Disable] debugging messages related to YANG datastore operations + within the management daemon. + +.. clicmd:: debug mgmt frontend + + Enable[/Disable] debugging messages related to frontend operations within the + management daemon. + +.. clicmd:: debug mgmt transaction + + Enable[/Disable] debugging messages related to transactions within the + management daemon. + + +MGMT Client debug commands +========================== + +The following debug commands enable debugging within the management front and +backend clients: + +.. clicmd:: debug mgmt client backend + + Enable[/Disable] debugging messages related to backend operations inside the + backend mgmtd clients. + +.. clicmd:: debug mgmt client frontend + + Enable[/Disable] debugging messages related to frontend operations inside the + frontend mgmtd clients. diff --git a/doc/user/nexthop_groups.rst b/doc/user/nexthop_groups.rst new file mode 100644 index 0000000..45f64ee --- /dev/null +++ b/doc/user/nexthop_groups.rst @@ -0,0 +1,29 @@ +.. _nexthop-groups: + +Nexthop Groups +============== + +Nexthop groups are a way to encapsulate ECMP information together. It's a +listing of ECMP nexthops used to forward packets. + +.. clicmd:: nexthop-group NAME + + Create a nexthop-group with an associated NAME. This will put you into a + sub-mode where you can specify individual nexthops. To exit this mode type + exit or end as per normal conventions for leaving a sub-mode. + +.. clicmd:: nexthop [A.B.C.D|X:X::X:XX] [interface [onlink]] [nexthop-vrf NAME] [label LABELS] + + Create a v4 or v6 nexthop. All normal rules for creating nexthops that you + are used to are allowed here. The syntax was intentionally kept the same as + creating nexthops as you would for static routes. + +.. clicmd:: resilient buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295) + + Create a resilient Nexthop Group with the specified number of buckets, and + associated timers. Instead of using the normal kernel hashing methodology + this specifies that X buckets will be created for the nexthop group and + when a nexthop is lost the buckets forwarding that particular nexthop + will be automatically re-assigned. This cli command must be the first + command entered currently. Additionally this command only works with linux 5.19 + kernels or newer. diff --git a/doc/user/nhrpd.rst b/doc/user/nhrpd.rst new file mode 100644 index 0000000..54527a0 --- /dev/null +++ b/doc/user/nhrpd.rst @@ -0,0 +1,447 @@ +.. _nhrp: + +**** +NHRP +**** + +*nhrpd* is an implementation of the :abbr:`NHRP (Next Hop Routing Protocol)`. +NHRP is described in :rfc:`2332`. + +NHRP is used to improve the efficiency of routing computer network traffic over +:abbr:`NBMA (Non-Broadcast, Multiple Access)` networks. NHRP provides an +ARP-like solution that allows a system to dynamically learn the NBMA address of +the other systems that are part of that network, allowing these systems to +directly communicate without requiring traffic to use an intermediate hop. + +NHRP is a client-server protocol. The server side is called the :abbr:`NHS +(Next Hop Server)` or the hub, while a client is referred to as the :abbr:`NHC +(Next Hop Client)` or the spoke. When a node is configured as an NHC, it +registers its address with the NHS which keeps track of all registered spokes. +An NHC client can then query the addresses of other clients from NHS allowing +all spokes to communicate directly with each other. + +Cisco Dynamic Multipoint VPN (DMVPN) is based on NHRP, and |PACKAGE_NAME| nhrpd +implements this scenario. + +.. _routing-design: + +Routing Design +============== + +nhrpd never handles routing of prefixes itself. You need to run some +real routing protocol (e.g. BGP) to advertise routes over the tunnels. +What nhrpd does it establishes 'shortcut routes' that optimizes the +routing protocol to avoid going through extra nodes in NBMA GRE mesh. + +nhrpd does route NHRP domain addresses individually using per-host prefixes. +This is similar to Cisco FlexVPN; but in contrast to opennhrp which uses +a generic subnet route. + +To create NBMA GRE tunnel you might use the following (Linux terminal +commands): + +.. code-block:: console + + ip tunnel add gre1 mode gre key 42 ttl 64 + ip addr add 10.255.255.2/32 dev gre1 + ip link set gre1 up + + +Note that the IP-address is assigned as host prefix to gre1. nhrpd will +automatically create additional host routes pointing to gre1 when +a connection with these hosts is established. + +The gre1 subnet prefix should be announced by routing protocol from the +hub nodes (e.g. BGP 'network' announce). This allows the routing protocol +to decide which is the closest hub and determine the relay hub on prefix +basis when direct tunnel is not established. + +nhrpd will redistribute directly connected neighbors to zebra. Within +hub nodes, these routes should be internally redistributed using some +routing protocol (e.g. iBGP) to allow hubs to be able to relay all traffic. + +This can be achieved in hubs with the following bgp configuration (network +command defines the GRE subnet): + +.. code-block:: frr + + router bgp 65555 + address-family ipv4 unicast + network 172.16.0.0/16 + redistribute nhrp + exit-address-family + + +.. _configuring-nhrp: + +Configuring NHRP +================ + +.. clicmd:: ip nhrp holdtime (1-65000) + + Holdtime is the number of seconds that have to pass before stopping to + advertise an NHRP NBMA address as valid. It also controls how often NHRP + registration requests are sent. By default registrations are sent every one + third of the holdtime. + +.. clicmd:: ip nhrp map A.B.C.D|X:X::X:X A.B.C.D|local + + Map an IP address of a station to the station's NBMA address. + +.. clicmd:: ip nhrp network-id (1-4294967295) + + Enable NHRP on this interface and set the interface's network ID. The + network ID is used to allow creating multiple nhrp domains on a router when + multiple interfaces are configured on the router. Interfaces configured + with the same ID are part of the same logical NBMA network. The ID is a + local only parameter and is not sent to other NHRP nodes and so IDs on + different nodes do not need to match. When NHRP packets are received on an + interface they are assigned to the local NHRP domain for that interface. + +.. clicmd:: ip nhrp nhs A.B.C.D nbma A.B.C.D|FQDN + + Configure the Next Hop Server address and its NBMA address. + +.. clicmd:: ip nhrp nhs dynamic nbma A.B.C.D + + Configure the Next Hop Server to have a dynamic address and set its NBMA + address. + +.. clicmd:: ip nhrp registration no-unique + + Allow the client to not set the unique flag in the NHRP packets. This is + useful when a station has a dynamic IP address that could change over time. + +.. clicmd:: ip nhrp shortcut + + Enable shortcut (spoke-to-spoke) tunnels to allow NHC to talk to each others + directly after establishing a connection without going through the hub. + +.. clicmd:: ip nhrp mtu + + Configure NHRP advertised MTU. + + +.. _hub-functionality: + +Hub Functionality +================= + +In addition to routing nhrp redistributed host prefixes, the hub nodes +are also responsible to send NHRP Traffic Indication messages that +trigger creation of the shortcut tunnels. + +nhrpd sends Traffic Indication messages based on network traffic captured +using NFLOG. Typically you want to send Traffic Indications for network +traffic that is routed from gre1 back to gre1 in rate limited manner. +This can be achieved with the following iptables rule. + +.. code-block:: shell + + iptables -A FORWARD -i gre1 -o gre1 \\ + -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \\ + --hashlimit-mode srcip,dstip --hashlimit-srcmask 24 --hashlimit-dstmask 24 \\ + --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128 + + +You can fine tune the src/dstmask according to the prefix lengths you announce +internal, add additional IP range matches, or rate limitation if needed. +However, the above should be good in most cases. + +This kernel NFLOG target's nflog-group is configured in global nhrp config +with: + +.. clicmd:: nhrp nflog-group (1-65535) + +To start sending these traffic notices out from hubs, use the nhrp +per-interface directive: + +.. clicmd:: ip nhrp redirect + +This enable redirect replies on the NHS similar to ICMP redirects except this +is managed by the nhrp protocol. This setting allows spokes to communicate with +each others directly. + +.. _integration-with-ike: + +Integration with IKE +==================== + +nhrpd needs tight integration with IKE daemon for various reasons. +Currently only strongSwan is supported as IKE daemon. + +nhrpd connects to strongSwan using VICI protocol based on UNIX socket which +can be configured using the command below (default to /var/run/charon.vici). + +strongSwan currently needs few patches applied. Please check out the +original patches at: +https://git-old.alpinelinux.org/user/tteras/strongswan/ + +Actively maintained patches are also available at: +https://gitlab.alpinelinux.org/alpine/aports/-/tree/master/main/strongswan + +.. _multicast-functionality: + +Multicast Functionality +======================= + +nhrpd can be configured to forward multicast packets, allowing routing +protocols that use multicast (such as OSPF) to be supported in the DMVPN +network. + +This support requires an iptables NFLOG rule to allow nhrpd to intercept +multicast packets. A second iptables rule is also usually used to drop the +original multicast packet. + + .. code-block:: shell + + iptables -A OUTPUT -d 224.0.0.0/24 -o gre1 -j NFLOG --nflog-group 2 + iptables -A OUTPUT -d 224.0.0.0/24 -o gre1 -j DROP + +.. clicmd:: nhrp multicast-nflog-group (1-65535) + + Sets the nflog group that nhrpd will listen on for multicast packets. This + value must match the nflog-group value set in the iptables rule. + +.. clicmd:: ip nhrp map multicast A.B.C.D|X:X::X:X A.B.C.D|dynamic + + Sends multicast packets to the specified NBMA address. If dynamic is + specified then destination NBMA address (or addresses) are learnt + dynamically. + +.. _nhrp-events: + +NHRP Events +=========== + +.. clicmd:: nhrp event socket SOCKET + + Configure the Unix path for the event socket. + +.. _show-nhrp: + +Show NHRP +========== + +.. clicmd:: show [ip|ipv6] nhrp cache [json] + + Dump the cache entries. + +.. clicmd:: show [ip|ipv6] nhrp opennhrp [json] + + Dump the cache entries with opennhrp format. + +.. clicmd:: show [ip|ipv6] nhrp nhs [json] + + Dump the hub context. + +.. clicmd:: show dmvpn [json] + + Dump the security contexts. + +Configuration Example +===================== + +.. figure:: ../figures/fig_dmvpn_topologies.png + :alt: image + + image + +IPSec configurration example +---------------------------- + +This changes required on all nodes as HUB and Spokes. + +ipsec.conf file + +.. code-block:: shell + + config setup + conn dmvpn + authby=secret + auto=add + keyexchange=ikev2 + ike=aes256-aes256-sha256-modp2048 + esp=aes256-aes256-sha256-modp2048 + dpdaction=clear + dpddelay=300s + left=%any + leftid=%any + right=%any + rightid=%any + leftprotoport=gre + rightprotoport=gre + type=transport + keyingtries=%forever + +ipsec.secrets file + +.. code-block:: shell + + %any : PSK "some_s3cret!" + + +HUB configuration example +------------------------- + +Creating gre interface + +.. code-block:: console + + ip tunnel add gre1 mode gre key 42 ttl 64 + ip addr add 10.0.0.254/32 dev gre1 + ip link set gre1 up + +Adding iptables rules to provide possibility shortcut tunnels and connect spokes directly + +.. code-block:: shell + + iptables -A FORWARD -i gre1 -o gre1 \\ + -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \\ + --hashlimit-mode srcip,dstip --hashlimit-srcmask 24 --hashlimit-dstmask 24 \\ + --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128 + +FRR config on HUB + +.. code-block:: frr + + nhrp nflog-group 1 + ! + interface gre1 + description DMVPN Tunnel Interface + ip address 10.0.0.254/32 + ip nhrp network-id 1 + ip nhrp redirect + ip nhrp registration no-unique + ip nhrp shortcut + tunnel protection vici profile dmvpn + tunnel source eth0 + ! + router bgp 65000 + bgp router-id 10.0.0.254 + no bgp ebgp-requires-policy + neighbor SPOKES peer-group + neighbor SPOKES disable-connected-check + neighbor 10.0.0.1 remote-as 65001 + neighbor 10.0.0.1 peer-group SPOKES + neighbor 10.0.0.2 remote-as 65002 + neighbor 10.0.0.2 peer-group SPOKES + neighbor 10.0.0.3 remote-as 65003 + neighbor 10.0.0.3 peer-group SPOKES + ! + address-family ipv4 unicast + network 172.16.0.0/24 + redistribute nhrp + exit-address-family + +Spoke1 configuration +-------------------- + +Creating gre interface + +.. code-block:: console + + ip tunnel add gre1 mode gre key 42 ttl 64 + ip addr add 10.0.0.1/32 dev gre1 + ip link set gre1 up + + +FRR config on Spoke1 + +.. code-block:: frr + + interface gre1 + description DMVPN Tunnel Interface + ip address 10.0.0.1/32 + ip nhrp network-id 1 + ip nhrp nhs dynamic nbma 198.51.100.1 + ip nhrp redirect + ip nhrp registration no-unique + ip nhrp shortcut + no link-detect + tunnel protection vici profile dmvpn + tunnel source eth0 + ! + router bgp 65001 + no bgp ebgp-requires-policy + neighbor 10.0.0.254 remote-as 65000 + neighbor 10.0.0.254 disable-connected-check + ! + address-family ipv4 unicast + network 172.16.1.0/24 + exit-address-family + + +Spoke2 configuration +-------------------- + +Creating gre interface + +.. code-block:: console + + ip tunnel add gre1 mode gre key 42 ttl 64 + ip addr add 10.0.0.1/32 dev gre1 + ip link set gre1 up + +FRR config on Spoke2 + +.. code-block:: frr + + interface gre1 + description DMVPN Tunnel Interface + ip address 10.0.0.2/32 + ip nhrp network-id 1 + ip nhrp nhs dynamic nbma 198.51.100.1 + ip nhrp redirect + ip nhrp registration no-unique + ip nhrp shortcut + no link-detect + tunnel protection vici profile dmvpn + tunnel source eth0 + ! + router bgp 65002 + no bgp ebgp-requires-policy + neighbor 10.0.0.254 remote-as 65000 + neighbor 10.0.0.254 disable-connected-check + ! + address-family ipv4 unicast + network 172.16.2.0/24 + exit-address-family + + +Spoke3 configuration +-------------------- + +Creating gre interface + +.. code-block:: console + + ip tunnel add gre1 mode gre key 42 ttl 64 + ip addr add 10.0.0.3/32 dev gre1 + ip link set gre1 up + +FRR config on Spoke3 + +.. code-block:: frr + + interface gre1 + description DMVPN Tunnel Interface + ip address 10.0.0.3/32 + ip nhrp network-id 1 + ip nhrp nhs dynamic nbma 198.51.100.1 + ip nhrp redirect + ip nhrp registration no-unique + ip nhrp shortcut + no link-detect + tunnel protection vici profile dmvpn + tunnel source eth0 + ! + router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.0.254 remote-as 65000 + neighbor 10.0.0.254 disable-connected-check + ! + address-family ipv4 unicast + network 172.16.3.0/24 + exit-address-family + diff --git a/doc/user/ospf6d.rst b/doc/user/ospf6d.rst new file mode 100644 index 0000000..ea41ba5 --- /dev/null +++ b/doc/user/ospf6d.rst @@ -0,0 +1,1009 @@ +.. _ospfv3: + +****** +OSPFv3 +****** + +*ospf6d* is a daemon support OSPF version 3 for IPv6 network. OSPF for IPv6 is +described in :rfc:`2740`. + +.. _ospf6-router: + +Configuring OSPF6 +***************** + +.. include:: config-include.rst + +Configuration Commands +====================== + +.. clicmd:: router ospf6 [vrf NAME] + +.. clicmd:: ospf6 router-id A.B.C.D + + Set router's Router-ID. + +.. clicmd:: timers throttle spf (0-600000) (0-600000) (0-600000) + + This command sets the initial `delay`, the `initial-holdtime` + and the `maximum-holdtime` between when SPF is calculated and the + event which triggered the calculation. The times are specified in + milliseconds and must be in the range of 0 to 600000 milliseconds. + + The `delay` specifies the minimum amount of time to delay SPF + calculation (hence it affects how long SPF calculation is delayed after + an event which occurs outside of the holdtime of any previous SPF + calculation, and also serves as a minimum holdtime). + + Consecutive SPF calculations will always be separated by at least + 'hold-time' milliseconds. The hold-time is adaptive and initially is + set to the `initial-holdtime` configured with the above command. + Events which occur within the holdtime of the previous SPF calculation + will cause the holdtime to be increased by `initial-holdtime`, bounded + by the `maximum-holdtime` configured with this command. If the adaptive + hold-time elapses without any SPF-triggering event occurring then + the current holdtime is reset to the `initial-holdtime`. + + .. code-block:: frr + + router ospf6 + timers throttle spf 200 400 10000 + + + In this example, the `delay` is set to 200ms, the initial holdtime is set + to 400ms and the `maximum holdtime` to 10s. Hence there will always be at + least 200ms between an event which requires SPF calculation and the actual + SPF calculation. Further consecutive SPF calculations will always be + separated by between 400ms to 10s, the hold-time increasing by 400ms each + time an SPF-triggering event occurs within the hold-time of the previous + SPF calculation. + +.. clicmd:: auto-cost reference-bandwidth COST + + + This sets the reference bandwidth for cost calculations, where this + bandwidth is considered equivalent to an OSPF cost of 1, specified in + Mbits/s. The default is 100Mbit/s (i.e. a link of bandwidth 100Mbit/s + or higher will have a cost of 1. Cost of lower bandwidth links will be + scaled with reference to this cost). + + This configuration setting MUST be consistent across all routers + within the OSPF domain. + +.. clicmd:: maximum-paths (1-64) + + Use this command to control the maximum number of parallel routes that + OSPFv3 can support. The default is 64. + +.. clicmd:: write-multiplier (1-100) + + Use this command to tune the amount of work done in the packet read and + write threads before relinquishing control. The parameter is the number + of packets to process before returning. The default value of this parameter + is 20. + +.. clicmd:: clear ipv6 ospf6 process [vrf NAME] + + This command clears up the database and routing tables and resets the + neighborship by restarting the interface state machine. This will be + helpful when there is a change in router-id and if user wants the router-id + change to take effect, user can use this cli instead of restarting the + ospf6d daemon. + +.. clicmd:: clear ipv6 ospf6 [vrf NAME] interface [IFNAME] + + This command restarts the interface state machine for all interfaces in the + VRF or only for the specific interface if ``IFNAME`` is specified. + +ASBR Summarisation Support in OSPFv3 +==================================== + + External routes in OSPFv3 are carried by type 5/7 LSA (external LSAs). + External LSAs are generated by ASBR (Autonomous System Boundary Router). + Large topology database requires a large amount of router memory, which + slows down all processes, including SPF calculations. + It is necessary to reduce the size of the OSPFv3 topology database, + especially in a large network. Summarising routes keeps the routing + tables smaller and easier to troubleshoot. + + External route summarization must be configured on ASBR. + Stub area do not allow ASBR because they don’t allow type 5 LSAs. + + An ASBR will inject a summary route into the OSPFv3 domain. + + Summary route will only be advertised if you have at least one subnet + that falls within the summary range. + + Users will be allowed an option in the CLI to not advertise range of + ipv6 prefixes as well. + + The configuration of ASBR Summarisation is supported using the CLI command + +.. clicmd:: summary-address X:X::X:X/M [tag (1-4294967295)] [{metric (0-16777215) | metric-type (1-2)}] + + This command will advertise a single External LSA on behalf of all the + prefixes falling under this range configured by the CLI. + The user is allowed to configure tag, metric and metric-type as well. + By default, tag is not configured, default metric as 20 and metric-type + as type-2 gets advertised. + A summary route is created when one or more specific routes are learned and + removed when no more specific route exist. + The summary route is also installed in the local system with Null0 as + next-hop to avoid leaking traffic. + +.. clicmd:: no summary-address X:X::X:X/M [tag (1-4294967295)] [{metric (0-16777215) | metric-type (1-2)}] + + This command can be used to remove the summarisation configuration. + This will flush the single External LSA if it was originated and advertise + the External LSAs for all the existing individual prefixes. + +.. clicmd:: summary-address X:X::X:X/M no-advertise + + This command can be used when user do not want to advertise a certain + range of prefixes using the no-advertise option. + This command when configured will flush all the existing external LSAs + falling under this range. + +.. clicmd:: no summary-address X:X::X:X/M no-advertise + + This command can be used to remove the previous configuration. + When configured, tt will resume originating external LSAs for all the prefixes + falling under the configured range. + +.. clicmd:: aggregation timer (5-1800) + + The summarisation command takes effect after the aggregation timer expires. + By default the value of this timer is 5 seconds. User can modify the time + after which the external LSAs should get originated using this command. + +.. clicmd:: no aggregation timer (5-1800) + + This command removes the timer configuration. It reverts back to default + 5 second timer. + +.. clicmd:: show ipv6 ospf6 summary-address [detail] [json] + + This command can be used to see all the summary-address related information. + When detail option is used, it shows all the prefixes falling under each + summary-configuration apart from other information. + +.. _ospf6-area: + +OSPF6 area +========== + +.. clicmd:: area A.B.C.D range X:X::X:X/M [] + +.. clicmd:: area (0-4294967295) range X:X::X:X/M [] + + Summarize a group of internal subnets into a single Inter-Area-Prefix LSA. + This command can only be used at the area boundary (ABR router). + + By default, the metric of the summary route is calculated as the highest + metric among the summarized routes. The `cost` option, however, can be used + to set an explicit metric. + + The `not-advertise` option, when present, prevents the summary route from + being advertised, effectively filtering the summarized routes. + +.. clicmd:: area A.B.C.D nssa [no-summary] [default-information-originate [metric-type (1-2)] [metric (0-16777214)]] + +.. clicmd:: area (0-4294967295) nssa [no-summary] [default-information-originate [metric-type (1-2)] [metric (0-16777214)]] + + Configure the area to be a NSSA (Not-So-Stubby Area). + + The following functionalities are implemented as per RFC 3101: + + 1. Advertising Type-7 LSA into NSSA area when external route is + redistributed into OSPFv3. + 2. Processing Type-7 LSA received from neighbor and installing route in the + route table. + 3. Support for NSSA ABR functionality which is generating Type-5 LSA when + backbone area is configured. Currently translation of Type-7 LSA to + Type-5 LSA is enabled by default. + 4. Support for NSSA Translator functionality when there are multiple NSSA + ABR in an area. + + An NSSA ABR can be configured with the `no-summary` option to prevent the + advertisement of summaries into the area. In that case, a single Type-3 LSA + containing a default route is originated into the NSSA. + + NSSA ABRs and ASBRs can be configured with `default-information-originate` + option to originate a Type-7 default route into the NSSA area. In the case + of NSSA ASBRs, the origination of the default route is conditioned to the + existence of a default route in the RIB that wasn't learned via the OSPF + protocol. + +.. clicmd:: area A.B.C.D nssa range X:X::X:X/M [] + +.. clicmd:: area (0-4294967295) nssa range X:X::X:X/M [] + + Summarize a group of external subnets into a single Type-7 LSA, which is + then translated to a Type-5 LSA and avertised to the backbone. + This command can only be used at the area boundary (NSSA ABR router). + + By default, the metric of the summary route is calculated as the highest + metric among the summarized routes. The `cost` option, however, can be used + to set an explicit metric. + + The `not-advertise` option, when present, prevents the summary route from + being advertised, effectively filtering the summarized routes. + +.. clicmd:: area A.B.C.D export-list NAME + +.. clicmd:: area (0-4294967295) export-list NAME + + Filter Type-3 summary-LSAs announced to other areas originated from intra- + area paths from specified area. + + .. code-block:: frr + + router ospf6 + area 0.0.0.10 export-list foo + ! + ipv6 access-list foo permit 2001:db8:1000::/64 + ipv6 access-list foo deny any + + With example above any intra-area paths from area 0.0.0.10 and from range + 2001:db8::/32 (for example 2001:db8:1::/64 and 2001:db8:2::/64) are announced + into other areas as Type-3 summary-LSA's, but any others (for example + 2001:200::/48) aren't. + + This command is only relevant if the router is an ABR for the specified + area. + +.. clicmd:: area A.B.C.D import-list NAME + +.. clicmd:: area (0-4294967295) import-list NAME + + Same as export-list, but it applies to paths announced into specified area + as Type-3 summary-LSAs. + +.. clicmd:: area A.B.C.D filter-list prefix NAME in + +.. clicmd:: area A.B.C.D filter-list prefix NAME out + +.. clicmd:: area (0-4294967295) filter-list prefix NAME in + +.. clicmd:: area (0-4294967295) filter-list prefix NAME out + + Filtering Type-3 summary-LSAs to/from area using prefix lists. This command + makes sense in ABR only. + +.. _ospf6-interface: + +OSPF6 interface +=============== + +.. clicmd:: ipv6 ospf6 area + + Enable OSPFv3 on the interface and add it to the specified area. + +.. clicmd:: ipv6 ospf6 cost COST + + Sets interface's output cost. Default value depends on the interface + bandwidth and on the auto-cost reference bandwidth. + +.. clicmd:: ipv6 ospf6 hello-interval HELLOINTERVAL + + Sets interface's Hello Interval. Default 10 + +.. clicmd:: ipv6 ospf6 dead-interval DEADINTERVAL + + Sets interface's Router Dead Interval. Default value is 40. + +.. clicmd:: ipv6 ospf6 graceful-restart hello-delay HELLODELAYINTERVAL + + Set the length of time during which Grace-LSAs are sent at 1-second intervals + while coming back up after an unplanned outage. During this time, no hello + packets are sent. + + A higher hello delay will increase the chance that all neighbors are notified + about the ongoing graceful restart before receiving a hello packet (which is + crucial for the graceful restart to succeed). The hello delay shouldn't be set + too high, however, otherwise the adjacencies might time out. As a best practice, + it's recommended to set the hello delay and hello interval with the same values. + The default value is 10 seconds. + +.. clicmd:: ipv6 ospf6 retransmit-interval RETRANSMITINTERVAL + + Sets interface's Rxmt Interval. Default value is 5. + +.. clicmd:: ipv6 ospf6 priority PRIORITY + + Sets interface's Router Priority. Default value is 1. + +.. clicmd:: ipv6 ospf6 transmit-delay TRANSMITDELAY + + Sets interface's Inf-Trans-Delay. Default value is 1. + +.. clicmd:: ipv6 ospf6 network (broadcast|point-to-point|point-to-multipoint) + + Set explicitly network type for specified interface. + + The only functional difference between ``point-to-point`` (PtP) and + ``point-to-multipoint`` (PtMP) mode is the packet addressing for database + flooding and updates. PtP will use multicast packets while PtMP will + unicast them. Apart from this, + :clicmd:`ipv6 ospf6 p2p-p2mp connected-prefixes ` has a + different default for PtP and PtMP. There are no other differences, in + particular FRR does not impose a limit of one neighbor in PtP mode. + + FRR does not support NBMA mode for IPv6 and likely never will, as NBMA is + considered deprecated for IPv6. Refer to `this IETF OSPF working group + discussion + `_ + for context. + +OSPF6 point-to-point and point-to-multipoint operation +====================================================== + +OSPFv3, by default, operates in broadcast mode where it elects a DR and BDR +for each network segment. This can be changed to point-to-point (PtP) / +point-to-multipoint (PtMP) mode by configuration. The actual physical +interface characteristics do not matter for this setting, all interfaces can +be configured for all modes. However, routers must be configured for the same +mode to form adjacencies. + +The main advantages of PtP/PtMP mode are: + +- no DR/BDR election +- adjacencies can be suppressed in a pairwise manner for any two routers, e.g. + to represent the underlying topology if it isn't a true full mesh +- distinct costs can be set for each pair of routers and direction + +The main downside is less efficient flooding on networks with a large number +of OSPFv3 routers. + +.. warning:: + + All options in this section should be considered "advanced" configuration + options. Inconsistent or nonsensical combinations can easily result in a + non-functional setup. + +.. clicmd:: ipv6 ospf6 p2p-p2mp disable-multicast-hello + + Disables sending normal multicast hellos when in PtP/PtMP mode. Some + vendors do this automatically for PtMP mode while others have a separate + ``no-broadcast`` option matching this. + + If this setting is used, you must issue + :clicmd:`ipv6 ospf6 neighbor X:X::X:X poll-interval (1-65535)` for each + neighbor to send unicast hello packets. + +.. clicmd:: ipv6 ospf6 p2p-p2mp config-neighbors-only + + Only form adjacencies with neighbors that are explicitly configured with + the :clicmd:`ipv6 ospf6 neighbor X:X::X:X` command. Hellos from other + routers are ignored. + + .. warning:: + + This setting is not intended to provide any security benefit. Do not + run OSPFv3 over untrusted links without additional security measures + (e.g. IPsec.) + +.. clicmd:: ipv6 ospf6 p2p-p2mp connected-prefixes + + For global/ULA prefixes configured on this interfaces, do (not) advertise + the full prefix to the area. Regardless of this setting, the router's own + address, as a /128 host route with the "LA" (Local Address) bit set, will + always be advertised. + + The default is to include connected prefixes for PtP mode and exclude them + for PtMP mode. Since these prefixes will cover other router's addresses, + these addresses can become unreachable if the link is partitioned if the + other router does not advertise the address as a /128. However, conversely, + if all routers have this flag set, the overall prefix will not be advertised + anywhere. End hosts on this link will therefore be unreachable (and + blackholing best-practices for non-existing prefixes apply.) It may be + preferable to have only one router announce the connected prefix. + + The Link LSA (which is not propagated into the area) always includes all + prefixes on the interface. This setting only affects the Router LSA that + is visible to all routers in the area. + + .. note:: + + Before interacting with this setting, consider either not configuring + any global/ULA IPv6 address on the interface, or directly configuring a + /128 if needed. OSPFv3 relies exclusively on link-local addresses to do + its signaling and there is absolutely no reason to configure global/ULA + addresses as far as OSPFv3 is concerned. + +.. clicmd:: ipv6 ospf6 neighbor X:X::X:X + + Explicitly configure a neighbor by its link-local address on this interface. + This statement has no effect other than allowing an adjacency when + :clicmd:`ipv6 ospf6 p2p-p2mp config-neighbors-only` is set. This command + does **not** cause unicast hellos to be sent. + + Only link-local addresses can be used to establish explicit neighbors. + When using this command, you should probably assign static IPv6 link-local + addresses to all routers on this link. It would technically be possible to + use the neighbor's Router ID (IPv4 address) here to ease working with + changing link-local addresses but this is not planned as a feature at the + time of writing. Global/ULA IPv6 addresses cannot be supported here due to + the way OSPFv3 works. + +.. clicmd:: ipv6 ospf6 neighbor X:X::X:X poll-interval (1-65535) + + Send unicast hellos to this neighbor at the specified interval (in seconds.) + The interval is only used while there is no adjacency with this neighbor. + As soon as an adjacency is formed, the interface's + :clicmd:`ipv6 ospf6 hello-interval HELLOINTERVAL` value is used. + (``hello-interval`` must be the same on all routers on this link.) + + :rfc:`2328` recommends a "much larger" value than ``hello-interval`` for + this setting, but this is a legacy of ATM and X.25 networks and nowadays you + should probably just use the same value as for ``hello-interval``. + +.. clicmd:: ipv6 ospf6 neighbor X:X::X:X cost (1-65535) + + Use a distinct cost for paths traversing this neighbor. The default is + to use the interface's cost value (which may be automatically calculated + based on link bandwidth.) Note that costs are directional in OSPF and the + reverse direction must be set on the other router. + + +OSPF6 route-map +=============== + +Usage of *ospfd6*'s route-map support. + +.. clicmd:: set metric [+|-](0-4294967295) + + Set a metric for matched route when sending announcement. Use plus (+) sign + to add a metric value to an existing metric. Use minus (-) sign to + substract a metric value from an existing metric. + +.. _redistribute-routes-to-ospf6: + +Redistribute routes to OSPF6 +============================ + +.. clicmd:: redistribute [metric-type (1-2)] [metric (0-16777214)] [route-map WORD] + + Redistribute routes of the specified protocol or kind into OSPFv3, with the + metric type and metric set if specified, filtering the routes using the + given route-map if specified. + +.. clicmd:: default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map WORD}] + + The command injects default route in the connected areas. The always + argument injects the default route regardless of it being present in the + router. Metric values and route-map can also be specified optionally. + +Graceful Restart +================ + +.. clicmd:: graceful-restart [grace-period (1-1800)] + + + Configure Graceful Restart (RFC 5187) restarting support. + When enabled, the default grace period is 120 seconds. + + To perform a graceful shutdown, the "graceful-restart prepare ipv6 ospf" + EXEC-level command needs to be issued before restarting the ospf6d daemon. + + When Graceful Restart is enabled and the ospf6d daemon crashes or is killed + abruptely (e.g. SIGKILL), it will attempt an unplanned Graceful Restart once + it restarts. + +.. clicmd:: graceful-restart helper enable [A.B.C.D] + + + Configure Graceful Restart (RFC 5187) helper support. + By default, helper support is disabled for all neighbors. + This config enables/disables helper support on this router + for all neighbors. + To enable/disable helper support for a specific + neighbor, the router-id (A.B.C.D) has to be specified. + +.. clicmd:: graceful-restart helper strict-lsa-checking + + + If 'strict-lsa-checking' is configured then the helper will + abort the Graceful Restart when a LSA change occurs which + affects the restarting router. + By default 'strict-lsa-checking' is enabled" + +.. clicmd:: graceful-restart helper supported-grace-time (10-1800) + + + Supports as HELPER for configured grace period. + +.. clicmd:: graceful-restart helper planned-only + + + It helps to support as HELPER only for planned + restarts. By default, it supports both planned and + unplanned outages. + +.. clicmd:: graceful-restart prepare ipv6 ospf + + + Initiate a graceful restart for all OSPFv3 instances configured with the + "graceful-restart" command. The ospf6d daemon should be restarted during + the instance-specific grace period, otherwise the graceful restart will fail. + + This is an EXEC-level command. + + +.. _Authentication-trailer: + +Authentication trailer support: +=============================== +IPv4 version of OSPF supports authentication as part of the base RFC. +When IPv6 version of OSPF was developed there was IPSec support for IPv6, +Hence OSPFv3(IPv6 version of OSPF) suggest to use IPSec as authentication +and encryption mechanism. IPSec supports authentication using AH header and +Encryption using ESP. + +There are few disadvantages of using IPSec with OSPFv3. + 1. If encryption is enabled for OSPFv3 packets, then its not + possible to give priority to control packets. + 2. IPSec has platform dependency and may not be supported + in all platforms. + 3. It is performance intensive. + 4. Its difficult to configure. + + +Some advantages of OSPFv3 authentication trailer feature. + 1. It provides replay protection via sequence number. + 2. It provides IPv6 source address protection. + 3. No platform dependency. + 4. Easy to implement and maintain. + + +This feature is support for ``RFC7166``. + +FRR supports MD5 and SHA256 internally and relays on openssl for other hash +algorithms. If user wants to use only MD5 and SHA256, no special action is +required. If user wants complete support of authentication trailer with all +hash algorithms follow below steps. + + +Installing Dependencies: +------------------------ + +.. code-block:: console + + sudo apt update + sudo apt-get install openssl + + +Compile: +-------- +Follow normal compilation as mentioned in the build page. If you want to +use all the hash algorithms then follow the steps mentioned in note before +compiling. + + +.. note:: + + If your platform supports ``openssl``, please make sure to add + ``--with-crypto=openssl`` to your configure options. + Default value is ``--with-crypto=internal`` + + +CLI Configuration: +------------------ +There are two ways in which authentication trailer can be configured for +OSPFv3. These commands are mutually exclusive, only one can be configured +at any time. + + 1. Using manual key configuration. + 2. Using keychain. + + +List of hash algorithms supported: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Without openssl: +++++++++++++++++ + ``MD5`` + ``HMAC-SHA-256`` + + +With openssl: ++++++++++++++ + ``MD5`` + ``HMAC-SHA-1`` + ``HMAC-SHA-256`` + ``HMAC-SHA-384`` + ``HMAC-SHA-512`` + + +Example configuration of manual key: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Without openssl: +++++++++++++++++ + +.. clicmd:: ipv6 ospf6 authentication key-id (1-65535) hash-algo key WORD + +With openssl: ++++++++++++++ + +.. clicmd:: ipv6 ospf6 authentication key-id (1-65535) hash-algo key WORD + + +Example configuration of keychain: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: ipv6 ospf6 authentication keychain KEYCHAIN_NAME + + +Running configuration: +---------------------- + +Manual key: +^^^^^^^^^^^ + +.. code-block:: frr + + frr# show running-config + Building configuration... + + Current configuration: + ! + interface ens192 + ipv6 address 2001:DB8::2/64 + ipv6 ospf6 authentication key-id 10 hash-algo hmac-sha-256 key abhinay + +Keychain: +^^^^^^^^^ + +.. code-block:: frr + + frr# show running-config + Building configuration... + + Current configuration: + ! + interface ens192 + ipv6 address 2001:DB8::2/64 + ipv6 ospf6 authentication keychain abhinay + + +Example keychain config: +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: frr + + frr#show running-config + Building configuration... + + Current configuration: + ! + key chain abcd + key 100 + key-string password + cryptographic-algorithm sha1 + exit + key 200 + key-string password + cryptographic-algorithm sha256 + exit + ! + key chain pqr + key 300 + key-string password + cryptographic-algorithm sha384 + exit + key 400 + key-string password + cryptographic-algorithm sha384 + exit + ! + +Show commands: +-------------- +There is an interface show command that displays if authentication trailer +is enabled or not. json output is also supported. + +There is support for drop counters, which will help in debugging the feature. + +.. code-block:: frr + + frr# show ipv6 ospf6 interface ens192 + ens192 is up, type BROADCAST + Interface ID: 5 + Number of I/F scoped LSAs is 2 + 0 Pending LSAs for LSUpdate in Time 00:00:00 [thread off] + 0 Pending LSAs for LSAck in Time 00:00:00 [thread off] + Authentication trailer is enabled with manual key ==> new info added + Packet drop Tx 0, Packet drop Rx 0 + + +OSPFv3 supports options in hello and database description packets hence +the presence of authentication trailer needs to be stored in OSPFv3 +neighbor info. Since RFC specifies that we need to handled sequence number +for every ospf6 packet type, sequence number recvd in authentication header +from the neighbor is stored in neighbor to validate the packet. +json output is also supported. + +.. code-block:: frr + + frr# show ipv6 ospf6 neighbor 2.2.2.2 detail + Neighbor 2.2.2.2%ens192 + Area 1 via interface ens192 (ifindex 3) + 0 Pending LSAs for LSUpdate in Time 00:00:00 [thread off] + 0 Pending LSAs for LSAck in Time 00:00:00 [thread off] + Authentication header present ==> new info added + hello DBDesc LSReq LSUpd LSAck + Higher sequence no 0x0 0x0 0x0 0x0 0x0 + Lower sequence no 0x242E 0x1DC4 0x1DC3 0x23CC 0x1DDA + +Sent packet sequence number is maintained per ospf6 router for every packet +that is sent out of router, so sequence number is maintained per ospf6 process. + +.. code-block:: frr + + frr# show ipv6 ospf6 + OSPFv3 Routing Process (0) with Router-ID 2.2.2.2 + Number of areas in this router is 1 + Authentication Sequence number info + Higher sequence no 3, Lower sequence no 1656 + +Debug command: +-------------- +Below command can be used to enable ospfv3 authentication trailer +specific logs if you have to debug the feature. + +.. clicmd:: debug ospf6 authentication [] + +Feature supports authentication trailer tx/rx drop counters for debugging, +which can be used to see if packets are getting dropped due to error in +processing authentication trailer information in OSPFv3 packet. +json output is also supported. + +.. code-block:: frr + + frr# show ipv6 ospf6 interface ens192 + ens192 is up, type BROADCAST + Interface ID: 5 + Number of I/F scoped LSAs is 2 + 0 Pending LSAs for LSUpdate in Time 00:00:00 [thread off] + 0 Pending LSAs for LSAck in Time 00:00:00 [thread off] + Authentication trailer is enabled with manual key + Packet drop Tx 0, Packet drop Rx 0 ==> new counters + +Clear command: +-------------- +Below command can be used to clear the tx/rx drop counters in interface. +Below command can be used to clear all ospfv3 interface or specific +interface by specifying the interface name. + +.. clicmd:: clear ipv6 ospf6 auth-counters interface [IFNAME] + + + +.. _showing-ospf6-information: + +Showing OSPF6 information +========================= + +.. clicmd:: show ipv6 ospf6 [vrf ] [json] + + Show information on a variety of general OSPFv3 and area state and + configuration information. JSON output can be obtained by appending 'json' + to the end of command. + +.. clicmd:: show ipv6 ospf6 [vrf ] database [] [json] + + This command shows LSAs present in the LSDB. There are three view options. + These options helps in viewing all the parameters of the LSAs. JSON output + can be obtained by appending 'json' to the end of command. JSON option is + not applicable with 'dump' option. + +.. clicmd:: show ipv6 ospf6 [vrf ] database [json] + + These options filters out the LSA based on its type. The three views options + works here as well. JSON output can be obtained by appending 'json' to the + end of command. + +.. clicmd:: show ipv6 ospf6 [vrf ] database adv-router A.B.C.D linkstate-id A.B.C.D [json] + + The LSAs additinally can also be filtered with the linkstate-id and + advertising-router fields. We can use the LSA type filter and views with + this command as well and visa-versa. JSON output can be obtained by + appending 'json' to the end of command. + +.. clicmd:: show ipv6 ospf6 [vrf ] database self-originated [json] + + This command is used to filter the LSAs which are originated by the present + router. All the other filters are applicable here as well. + +.. clicmd:: show ipv6 ospf6 [vrf ] interface [json] + + To see OSPF interface configuration like costs. JSON output can be + obtained by appending "json" in the end. + +.. clicmd:: show ipv6 ospf6 [vrf ] neighbor [json] + + Shows state and chosen (Backup) DR of neighbor. JSON output can be + obtained by appending 'json' at the end. + +.. clicmd:: show ipv6 ospf6 [vrf ] interface traffic [json] + + Shows counts of different packets that have been received and transmitted + by the interfaces. JSON output can be obtained by appending "json" at the + end. + +.. clicmd:: show ipv6 route ospf6 + + This command shows internal routing table. + +.. clicmd:: show ipv6 ospf6 zebra [json] + + Shows state about what is being redistributed between zebra and OSPF6. + JSON output can be obtained by appending "json" at the end. + +.. clicmd:: show ipv6 ospf6 [vrf ] redistribute [json] + + Shows the routes which are redistributed by the router. JSON output can + be obtained by appending 'json' at the end. + +.. clicmd:: show ipv6 ospf6 [vrf ] route [] [json] + + This command displays the ospfv3 routing table as determined by the most + recent SPF calculations. Options are provided to view the different types + of routes. Other than the standard view there are two other options, detail + and summary. JSON output can be obtained by appending 'json' to the end of + command. + +.. clicmd:: show ipv6 ospf6 [vrf ] route X:X::X:X/M match [detail] [json] + + The additional match option will match the given address to the destination + of the routes, and return the result accordingly. + +.. clicmd:: show ipv6 ospf6 [vrf ] interface [IFNAME] prefix [detail| []] [json] + + This command shows the prefixes present in the interface routing table. + Interface name can also be given. JSON output can be obtained by appending + 'json' to the end of command. + +.. clicmd:: show ipv6 ospf6 [vrf ] spf tree [json] + + This commands shows the spf tree from the recent spf calculation with the + calling router as the root. If json is appended in the end, we can get the + tree in JSON format. Each area that the router belongs to has it's own + JSON object, with each router having "cost", "isLeafNode" and "children" as + arguments. + +.. clicmd:: show ipv6 ospf6 graceful-restart helper [detail] [json] + + This command shows the graceful-restart helper details including helper + configuration parameters. + +.. _ospf6-debugging: + +OSPFv3 Debugging +================ + +The following debug commands are supported: + +.. clicmd:: debug ospf6 abr + + Toggle OSPFv3 ABR debugging messages. + +.. clicmd:: debug ospf6 asbr + + Toggle OSPFv3 ASBR debugging messages. + +.. clicmd:: debug ospf6 border-routers {router-id [A.B.C.D] | area-id [A.B.C.D]} + + Toggle OSPFv3 border router debugging messages. This can be specified for a + router with specific Router-ID/Area-ID. + +.. clicmd:: debug ospf6 flooding + + Toggle OSPFv3 flooding debugging messages. + +.. clicmd:: debug ospf6 interface + + Toggle OSPFv3 interface related debugging messages. + +.. clicmd:: debug ospf6 lsa + + Toggle OSPFv3 Link State Advertisements debugging messages. + +.. clicmd:: debug ospf6 lsa aggregation + + Toggle OSPFv3 Link State Advertisements summarization debugging messages. + +.. clicmd:: debug ospf6 message + + Toggle OSPFv3 message exchange debugging messages. + +.. clicmd:: debug ospf6 neighbor + + Toggle OSPFv3 neighbor interaction debugging messages. + +.. clicmd:: debug ospf6 nssa + + Toggle OSPFv3 Not So Stubby Area (NSSA) debugging messages. + +.. clicmd:: debug ospf6 route + + Toggle OSPFv3 routes debugging messages. + +.. clicmd:: debug ospf6 spf + + Toggle OSPFv3 Shortest Path calculation debugging messages. + +.. clicmd:: debug ospf6 zebra + + Toggle OSPFv3 zebra interaction debugging messages. + +.. clicmd:: debug ospf6 graceful-restart + + Toggle OSPFv3 graceful-restart helper debugging messages. + +Sample configuration +==================== + +Example of ospf6d configured on one interface and area: + +.. code-block:: frr + + interface eth0 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 instance-id 0 + ! + router ospf6 + ospf6 router-id 212.17.55.53 + area 0.0.0.0 range 2001:770:105:2::/64 + ! + + +Larger example with policy and various options set: + + +.. code-block:: frr + + debug ospf6 neighbor state + ! + interface fxp0 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 cost 1 + ipv6 ospf6 hello-interval 10 + ipv6 ospf6 dead-interval 40 + ipv6 ospf6 retransmit-interval 5 + ipv6 ospf6 priority 0 + ipv6 ospf6 transmit-delay 1 + ipv6 ospf6 instance-id 0 + ! + interface lo0 + ipv6 ospf6 cost 1 + ipv6 ospf6 hello-interval 10 + ipv6 ospf6 dead-interval 40 + ipv6 ospf6 retransmit-interval 5 + ipv6 ospf6 priority 1 + ipv6 ospf6 transmit-delay 1 + ipv6 ospf6 instance-id 0 + ! + router ospf6 + router-id 255.1.1.1 + redistribute static route-map static-ospf6 + ! + access-list access4 permit 127.0.0.1/32 + ! + ipv6 access-list access6 permit 3ffe:501::/32 + ipv6 access-list access6 permit 2001:200::/48 + ipv6 access-list access6 permit ::1/128 + ! + ipv6 prefix-list test-prefix seq 1000 deny any + ! + route-map static-ospf6 permit 10 + match ipv6 address prefix-list test-prefix + set metric-type type-2 + set metric 2000 + ! + line vty + access-class access4 + ipv6 access-class access6 + exec-timeout 0 0 + ! diff --git a/doc/user/ospf_fundamentals.rst b/doc/user/ospf_fundamentals.rst new file mode 100644 index 0000000..3032d27 --- /dev/null +++ b/doc/user/ospf_fundamentals.rst @@ -0,0 +1,558 @@ +.. _ospf-fundamentals: + +OSPF Fundamentals +================= + +.. index:: + pair: Link-state routing protocol; OSPF + pair: Distance-vector routing protocol; OSPF + + +:abbr:`OSPF` is, mostly, a link-state routing protocol. In contrast to +:term:`distance-vector` protocols, such as :abbr:`RIP` or :abbr:`BGP`, where +routers describe available `paths` (i.e. routes) to each other, in +:term:`link-state` protocols routers instead describe the state of their links +to their immediate neighboring routers. + +.. index:: + single: Link State Announcement + single: Link State Advertisement + single: LSA flooding + single: Link State Database + + +Each router describes their link-state information in a message known as an +:abbr:`LSA (Link State Advertisement)`, which is then propagated through to all +other routers in a link-state routing domain, by a process called `flooding`. +Each router thus builds up an :abbr:`LSDB (Link State Database)` of all the +link-state messages. From this collection of LSAs in the LSDB, each router can +then calculate the shortest path to any other router, based on some common +metric, by using an algorithm such as +`Edsger Dijkstra's `_ +:abbr:`SPF (Shortest Path First)` algorithm. + +.. index:: + pair: Link-state routing protocol; advantages + +By describing connectivity of a network in this way, in terms of +routers and links rather than in terms of the paths through a network, +a link-state protocol can use less bandwidth and converge more quickly +than other protocols. A link-state protocol need distribute only one +link-state message throughout the link-state domain when a link on any +single given router changes state, in order for all routers to +reconverge on the best paths through the network. In contrast, distance +vector protocols can require a progression of different path update +messages from a series of different routers in order to converge. + +.. index:: + pair: Link-state routing protocol; disadvantages + +The disadvantage to a link-state protocol is that the process of +computing the best paths can be relatively intensive when compared to +distance-vector protocols, in which near to no computation need be done +other than (potentially) select between multiple routes. This overhead +is mostly negligible for modern embedded CPUs, even for networks with +thousands of nodes. The primary scaling overhead lies more in coping +with the ever greater frequency of LSA updates as the size of a +link-state area increases, in managing the :abbr:`LSDB` and required +flooding. + +This section aims to give a distilled, but accurate, description of the +more important workings of :abbr:`OSPF` which an administrator may need +to know to be able best configure and trouble-shoot :abbr:`OSPF`. + +OSPF Mechanisms +--------------- + +:abbr:`OSPF` defines a range of mechanisms, concerned with detecting, +describing and propagating state through a network. These mechanisms +will nearly all be covered in greater detail further on. They may be +broadly classed as: + + +.. index:: + pair: Hello protocol; OSPF + +The Hello Protocol +^^^^^^^^^^^^^^^^^^ + +The OSPF Hello protocol allows OSPF to quickly detect changes in two-way +reachability between routers on a link. OSPF can additionally avail of other +sources of reachability information, such as link-state information provided by +hardware, or through dedicated reachability protocols such as +:abbr:`BFD (Bidirectional Forwarding Detection)`. + +OSPF also uses the Hello protocol to propagate certain state between routers +sharing a link, for example: + +- Hello protocol configured state, such as the dead-interval. +- Router priority, for DR/BDR election. +- DR/BDR election results. +- Any optional capabilities supported by each router. + +The Hello protocol is comparatively trivial and will not be explored in more +detail. + + +.. index:: + pair: LSA; OSPF + +.. _ospf-lsas: + +LSAs +^^^^ + +At the heart of :abbr:`OSPF` are :abbr:`LSA (Link State Advertisement)` +messages. Despite the name, some :abbr:`LSA` s do not, strictly speaking, +describe link-state information. Common :abbr:`LSA` s describe information +such as: + +- Routers, in terms of their links. +- Networks, in terms of attached routers. +- Routes, external to a link-state domain: + + External Routes + Routes entirely external to :abbr:`OSPF`. Routers originating such + routes are known as :abbr:`ASBR (Autonomous-System Border Router)` + routers. + + Summary Routes + Routes which summarise routing information relating to OSPF areas + external to the OSPF link-state area at hand, originated by + :abbr:`ABR (Area Boundary Router)` routers. + +.. _ospf-lsa-flooding: + +LSA Flooding +"""""""""""" + +OSPF defines several related mechanisms, used to manage synchronisation of +:abbr:`LSDB` s between neighbors as neighbors form adjacencies and the +propagation, or `flooding` of new or updated :abbr:`LSA` s. + + +.. index:: + pair: Area; OSPF + +.. _ospf-areas: + +Areas +^^^^^ + +OSPF provides for the protocol to be broken up into multiple smaller and +independent link-state areas. Each area must be connected to a common backbone +area by an :abbr:`ABR (Area Boundary Router)`. These :abbr:`ABR` routers are +responsible for summarising the link-state routing information of an area into +`Summary LSAs`, possibly in a condensed (i.e. aggregated) form, and then +originating these summaries into all other areas the :abbr:`ABR` is connected +to. + +Note that only summaries and external routes are passed between areas. As +these describe *paths*, rather than any router link-states, routing between +areas hence is by :term:`distance-vector`, **not** link-state. + +OSPF LSAs +--------- + +The core objects in OSPF are :abbr:`LSA` s. Everything else in OSPF revolves +around detecting what to describe in LSAs, when to update them, how to flood +them throughout a network and how to calculate routes from them. + +There are a variety of different :abbr:`LSA` s, for purposes such as describing +actual link-state information, describing paths (i.e. routes), describing +bandwidth usage of links for :abbr:`TE (Traffic Engineering)` purposes, and +even arbitrary data by way of *Opaque* :abbr:`LSA` s. + +LSA Header +^^^^^^^^^^ + +All LSAs share a common header with the following information: + +- Type + + Different types of :abbr:`LSA` s describe different things in + :abbr:`OSPF`. Types include: + + - Router LSA + - Network LSA + - Network Summary LSA + - Router Summary LSA + - AS-External LSA + + The specifics of the different types of LSA are examined below. + +- Advertising Router + + The Router ID of the router originating the LSA. + +.. seealso:: + + :clicmd:`ospf router-id A.B.C.D`. + +- LSA ID + + The ID of the LSA, which is typically derived in some way from the + information the LSA describes, e.g. a Router LSA uses the Router ID as + the LSA ID, a Network LSA will have the IP address of the :abbr:`DR` + as its LSA ID. + + The combination of the Type, ID and Advertising Router ID must uniquely + identify the :abbr:`LSA`. There can however be multiple instances of + an LSA with the same Type, LSA ID and Advertising Router ID, see + :ref:`sequence number `. + +- Age + + A number to allow stale :abbr:`LSA` s to, eventually, be purged by routers + from their :abbr:`LSDB` s. + + The value nominally is one of seconds. An age of 3600, i.e. 1 hour, is + called the `MaxAge`. MaxAge LSAs are ignored in routing + calculations. LSAs must be periodically refreshed by their Advertising + Router before reaching MaxAge if they are to remain valid. + + Routers may deliberately flood LSAs with the age artificially set to + 3600 to indicate an LSA is no longer valid. This is called + `flushing` of an LSA. + + It is not abnormal to see stale LSAs in the LSDB, this can occur where + a router has shutdown without flushing its LSA(s), e.g. where it has + become disconnected from the network. Such LSAs do little harm. + +.. _ospf-lsa-sequence-number: + +- Sequence Number + + A number used to distinguish newer instances of an LSA from older instances. + +Link-State LSAs +^^^^^^^^^^^^^^^ + +Of all the various kinds of :abbr:`LSA` s, just two types comprise the +actual link-state part of :abbr:`OSPF`, Router :abbr:`LSA` s and +Network :abbr:`LSA` s. These LSA types are absolutely core to the +protocol. + +Instances of these LSAs are specific to the link-state area in which +they are originated. Routes calculated from these two LSA types are +called `intra-area routes`. + +- Router LSA + + Each OSPF Router must originate a router :abbr:`LSA` to describe + itself. In it, the router lists each of its :abbr:`OSPF` enabled + interfaces, for the given link-state area, in terms of: + + Cost + The output cost of that interface, scaled inversely to some commonly known + reference value, :clicmd:`auto-cost reference-bandwidth (1-4294967)`. + + Link Type + Transit Network + + A link to a multi-access network, on which the router has at least one + Full adjacency with another router. + + :abbr:`PtP (Point-to-Point)` + A link to a single remote router, with a Full adjacency. No + :abbr:`DR (Designated Router)` is elected on such links; no network + LSA is originated for such a link. + + Stub + A link with no adjacent neighbors, or a host route. + + - Link ID and Data + + These values depend on the Link Type: + + +----------------+-----------------------------------+------------------------------------------+ + | Link Type | Link ID | Link Data | + +================+===================================+==========================================+ + | Transit | Link IP address of the :abbr:`DR` | Interface IP address | + +----------------+-----------------------------------+------------------------------------------+ + | Point-to-Point | Router ID of the remote router | Local interface IP address, or the | + | | | :abbr:`ifindex (MIB-II interface index)` | + | | | for unnumbered links | + +----------------+-----------------------------------+------------------------------------------+ + | Stub | IP address | Subnet Mask | + +----------------+-----------------------------------+------------------------------------------+ + + Links on a router may be listed multiple times in the Router LSA, e.g. a + :abbr:`PtP` interface on which OSPF is enabled must *always* be described + by a Stub link in the Router :abbr:`LSA`, in addition to being listed as + PtP link in the Router :abbr:`LSA` if the adjacency with the remote router + is Full. + + Stub links may also be used as a way to describe links on which OSPF is + *not* spoken, known as `passive interfaces`, see + :clicmd:`ip ospf passive [A.B.C.D]`. + +- Network LSA + + On multi-access links (e.g. ethernets, certain kinds of ATM and X.25 + configurations), routers elect a :abbr:`DR`. The :abbr:`DR` is + responsible for originating a Network :abbr:`LSA`, which helps reduce + the information needed to describe multi-access networks with multiple + routers attached. The :abbr:`DR` also acts as a hub for the flooding of + :abbr:`LSA` s on that link, thus reducing flooding overheads. + + The contents of the Network LSA describes the: + + - Subnet Mask + + As the :abbr:`LSA` ID of a Network LSA must be the IP address of the + :abbr:`DR`, the Subnet Mask together with the :abbr:`LSA` ID gives + you the network address. + + - Attached Routers + + Each router fully-adjacent with the :abbr:`DR` is listed in the LSA, + by their Router-ID. This allows the corresponding Router :abbr:`LSA` s to be + easily retrieved from the :abbr:`LSDB`. + +Summary of Link State LSAs: + ++-------------+----------------------------+--------------------------------------------+ +| LSA Type | LSA ID | LSA Data Describes | ++=============+============================+============================================+ +| Router LSA | Router ID | The :abbr:`OSPF` enabled links of the | +| | | router, within a specific link-state area. | ++-------------+----------------------------+--------------------------------------------+ +| Network LSA | The IP address of the | The subnet mask of the network and the | +| | :abbr:`DR` for the network | Router IDs of all routers on the network | ++-------------+----------------------------+--------------------------------------------+ + +With an LSDB composed of just these two types of :abbr:`LSA`, it is +possible to construct a directed graph of the connectivity between all +routers and networks in a given OSPF link-state area. So, not +surprisingly, when OSPF routers build updated routing tables, the first +stage of :abbr:`SPF` calculation concerns itself only with these two +LSA types. + +.. _ospf-link-state-lsa-examples: + +Link-State LSA Examples +^^^^^^^^^^^^^^^^^^^^^^^ + +The example below shows two :abbr:`LSA` s, both originated by the same router +(Router ID 192.168.0.49) and with the same :abbr:`LSA` ID (192.168.0.49), but +of different LSA types. + +The first LSA being the router LSA describing 192.168.0.49's links: 2 links +to multi-access networks with fully-adjacent neighbors (i.e. Transit +links) and 1 being a Stub link (no adjacent neighbors). + +The second LSA being a Network LSA, for which 192.168.0.49 is the +:abbr:`DR`, listing the Router IDs of 4 routers on that network which +are fully adjacent with 192.168.0.49. + +:: + + # show ip ospf database router 192.168.0.49 + + OSPF Router with ID (192.168.0.53) + + Router Link States (Area 0.0.0.0) + + LS age: 38 + Options: 0x2 : *|-|-|-|-|-|E|* + LS Flags: 0x6 + Flags: 0x2 : ASBR + LS Type: router-LSA + Link State ID: 192.168.0.49 + Advertising Router: 192.168.0.49 + LS Seq Number: 80000f90 + Checksum: 0x518b + Length: 60 + Number of Links: 3 + + Link connected to: a Transit Network + (Link ID) Designated Router address: 192.168.1.3 + (Link Data) Router Interface address: 192.168.1.3 + Number of TOS metrics: 0 + TOS 0 Metric: 10 + + Link connected to: a Transit Network + (Link ID) Designated Router address: 192.168.0.49 + (Link Data) Router Interface address: 192.168.0.49 + Number of TOS metrics: 0 + TOS 0 Metric: 10 + + Link connected to: Stub Network + (Link ID) Net: 192.168.3.190 + (Link Data) Network Mask: 255.255.255.255 + Number of TOS metrics: 0 + TOS 0 Metric: 39063 + # show ip ospf database network 192.168.0.49 + + OSPF Router with ID (192.168.0.53) + + Net Link States (Area 0.0.0.0) + + LS age: 285 + Options: 0x2 : *|-|-|-|-|-|E|* + LS Flags: 0x6 + LS Type: network-LSA + Link State ID: 192.168.0.49 (address of Designated Router) + Advertising Router: 192.168.0.49 + LS Seq Number: 80000074 + Checksum: 0x0103 + Length: 40 + Network Mask: /29 + Attached Router: 192.168.0.49 + Attached Router: 192.168.0.52 + Attached Router: 192.168.0.53 + Attached Router: 192.168.0.54 + + +Note that from one LSA, you can find the other. E.g. Given the +Network-LSA you have a list of Router IDs on that network, from which +you can then look up, in the local :abbr:`LSDB`, the matching Router +LSA. From that Router-LSA you may (potentially) find links to other +Transit networks and Routers IDs which can be used to lookup the +corresponding Router or Network LSA. And in that fashion, one can find +all the Routers and Networks reachable from that starting :abbr:`LSA`. + +Given the Router LSA instead, you have the IP address of the +:abbr:`DR` of any attached transit links. Network LSAs will have that IP +as their LSA ID, so you can then look up that Network LSA and from that +find all the attached routers on that link, leading potentially to more +links and Network and Router LSAs, etc. etc. + +From just the above two :abbr:`LSA` s, one can already see the +following partial topology: + +:: + + ------------------------ Network: ...... + | Designated Router IP: 192.168.1.3 + | + IP: 192.168.1.3 + (transit link) + (cost: 10) + Router ID: 192.168.0.49(stub)---------- IP: 192.168.3.190/32 + (cost: 10) (cost: 39063) + (transit link) + IP: 192.168.0.49 + | + | + ------------------------------ Network: 192.168.0.48/29 + | | | Designated Router IP: 192.168.0.49 + | | | + | | Router ID: 192.168.0.54 + | | + | Router ID: 192.168.0.53 + | + Router ID: 192.168.0.52 + + +Note the Router IDs, though they look like IP addresses and often are +IP addresses, are not strictly speaking IP addresses, nor need they be +reachable addresses (though, OSPF will calculate routes to Router IDs). + +External LSAs +^^^^^^^^^^^^^ + +External, or "Type 5", :abbr:`LSA` s describe routing information which is +entirely external to :abbr:`OSPF`, and is "injected" into +:abbr:`OSPF`. Such routing information may have come from another +routing protocol, such as RIP or BGP, they may represent static routes +or they may represent a default route. + +An :abbr:`OSPF` router which originates External :abbr:`LSA` s is known as an +:abbr:`ASBR (AS Boundary Router)`. Unlike the link-state :abbr:`LSA` s, and +most other :abbr:`LSA` s, which are flooded only within the area in +which they originate, External :abbr:`LSA` s are flooded through-out +the :abbr:`OSPF` network to all areas capable of carrying External +:abbr:`LSA` s (:ref:`ospf-areas`). + +Routes internal to OSPF (intra-area or inter-area) are always preferred +over external routes. + +The External :abbr:`LSA` describes the following: + +IP Network number + The IP Network number of the route is described by the :abbr:`LSA` ID field. + +IP Network Mask + The body of the External LSA describes the IP Network Mask of the route. + This, together with the :abbr:`LSA` ID, describes the prefix of the IP route + concerned. + +Metric + The cost of the External Route. This cost may be an OSPF cost (also known as + a "Type 1" metric), i.e. equivalent to the normal OSPF costs, or an + externally derived cost ("Type 2" metric) which is not comparable to OSPF + costs and always considered larger than any OSPF cost. Where there are both + Type 1 and 2 External routes for a route, the Type 1 is always preferred. + +Forwarding Address + The address of the router to forward packets to for the route. This may be, + and usually is, left as 0 to specify that the ASBR originating the External + :abbr:`LSA` should be used. There must be an internal OSPF route to the + forwarding address, for the forwarding address to be usable. + +Tag + An arbitrary 4-bytes of data, not interpreted by OSPF, which may carry + whatever information about the route which OSPF speakers desire. + +AS External LSA Example +^^^^^^^^^^^^^^^^^^^^^^^ + +To illustrate, below is an example of an External :abbr:`LSA` in the +:abbr:`LSDB` of an OSPF router. It describes a route to the IP prefix of +192.168.165.0/24, originated by the ASBR with Router-ID 192.168.0.49. The +metric of 20 is external to OSPF. The forwarding address is 0, so the route +should forward to the originating ASBR if selected. + +:: + + # show ip ospf database external 192.168.165.0 + LS age: 995 + Options: 0x2 : *|-|-|-|-|-|E|* + LS Flags: 0x9 + LS Type: AS-external-LSA + Link State ID: 192.168.165.0 (External Network Number) + Advertising Router: 192.168.0.49 + LS Seq Number: 800001d8 + Checksum: 0xea27 + Length: 36 + Network Mask: /24 + Metric Type: 2 (Larger than any link state path) + TOS: 0 + Metric: 20 + Forward Address: 0.0.0.0 + External Route Tag: 0 + + +We can add this to our partial topology from above, which now looks +like::: + + --------------------- Network: ...... + | Designated Router IP: 192.168.1.3 + | + IP: 192.168.1.3 /---- External route: 192.168.165.0/24 + (transit link) / Cost: 20 (External metric) + (cost: 10) / + Router ID: 192.168.0.49(stub)---------- IP: 192.168.3.190/32 + (cost: 10) (cost: 39063) + (transit link) + IP: 192.168.0.49 + | + | + ------------------------------ Network: 192.168.0.48/29 + | | | Designated Router IP: 192.168.0.49 + | | | + | | Router ID: 192.168.0.54 + | | + | Router ID: 192.168.0.53 + | + Router ID: 192.168.0.52 + + +Summary LSAs +^^^^^^^^^^^^ + +Summary LSAs are created by :abbr:`ABR` s to summarise the destinations +available within one area to other areas. These LSAs may describe IP networks, +potentially in aggregated form, or :abbr:`ASBR` routers. diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst new file mode 100644 index 0000000..70c15e7 --- /dev/null +++ b/doc/user/ospfd.rst @@ -0,0 +1,1485 @@ +.. _ospfv2: + +****** +OSPFv2 +****** + +:abbr:`OSPF (Open Shortest Path First)` version 2 is a routing protocol which +is described in :rfc:`2328`. OSPF is an :abbr:`IGP (Interior Gateway +Protocol)`. Compared with :abbr:`RIP`, :abbr:`OSPF` can provide scalable +network support and faster convergence times. OSPF is widely used in large +networks such as :abbr:`ISP (Internet Service Provider)` backbone and +enterprise networks. + +.. include:: ospf_fundamentals.rst + +.. _configuring-ospfd: + +Configuring OSPF +================ + +*ospfd* accepts all :ref:`common-invocation-options`. + +.. option:: -n, --instance + + Specify the instance number for this invocation of *ospfd*. + +.. option:: -a, --apiserver + + Enable the OSPF API server. This is required to use ``ospfclient``. + +.. option:: -l, --apiserver_addr
+ + Specify the local IPv4 address to which to bind the OSPF API server socket. + If unspecified, connections are accepted to any address. Specification of + 127.0.0.1 can be used to limit socket access to local applications. + +*ospfd* must acquire interface information from *zebra* in order to function. +Therefore *zebra* must be running before invoking *ospfd*. Also, if *zebra* is +restarted then *ospfd* must be too. + +.. include:: config-include.rst + +.. _ospf-multi-instance: + +Multi-instance Support +---------------------- + +OSPF supports multiple instances. Each instance is identified by a positive +nonzero integer that must be provided when adding configuration items specific +to that instance. Enabling instances is done with :file:`/etc/frr/daemons` in +the following manner: + +:: + + ... + ospfd=yes + ospfd_instances=1,5,6 + ... + +The ``ospfd_instances`` variable controls which instances are started and what +their IDs are. In this example, after starting FRR you should see the following +processes: + +.. code-block:: shell + + # ps -ef | grep "ospfd" + frr 11816 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 1 + frr 11822 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 2 + frr 11828 1 0 17:30 ? 00:00:00 /usr/lib/frr/ospfd --daemon -A 127.0.0.1 -n 3 + + +The instance number should be specified in the config when addressing a particular instance: + +.. code-block:: frr + + router ospf 5 + ospf router-id 1.2.3.4 + area 0.0.0.0 authentication message-digest + ... + +.. _ospf-router: + +Routers +------- + +To start OSPF process you have to specify the OSPF router. + +.. clicmd:: router ospf [{(1-65535)|vrf NAME}] + + + Enable or disable the OSPF process. + + Multiple instances don't support `vrf NAME`. + +.. clicmd:: ospf router-id A.B.C.D + + + This sets the router-ID of the OSPF process. The router-ID may be an IP + address of the router, but need not be - it can be any arbitrary 32bit + number. However it MUST be unique within the entire OSPF domain to the OSPF + speaker - bad things will happen if multiple OSPF speakers are configured + with the same router-ID! If one is not specified then *ospfd* will obtain a + router-ID automatically from *zebra*. + +.. clicmd:: ospf abr-type TYPE + + + `type` can be cisco|ibm|shortcut|standard. The "Cisco" and "IBM" types + are equivalent. + + The OSPF standard for ABR behaviour does not allow an ABR to consider + routes through non-backbone areas when its links to the backbone are + down, even when there are other ABRs in attached non-backbone areas + which still can reach the backbone - this restriction exists primarily + to ensure routing-loops are avoided. + + With the "Cisco" or "IBM" ABR type, the default in this release of FRR, this + restriction is lifted, allowing an ABR to consider summaries learned from + other ABRs through non-backbone areas, and hence route via non-backbone + areas as a last resort when, and only when, backbone links are down. + + Note that areas with fully-adjacent virtual-links are considered to be + "transit capable" and can always be used to route backbone traffic, and + hence are unaffected by this setting (:clicmd:`area A.B.C.D virtual-link A.B.C.D`). + + More information regarding the behaviour controlled by this command can + be found in :rfc:`3509`, and :t:`draft-ietf-ospf-shortcut-abr-02.txt`. + + Quote: "Though the definition of the :abbr:`ABR (Area Border Router)` + in the OSPF specification does not require a router with multiple + attached areas to have a backbone connection, it is actually + necessary to provide successful routing to the inter-area and + external destinations. If this requirement is not met, all traffic + destined for the areas not connected to such an ABR or out of the + OSPF domain, is dropped. This document describes alternative ABR + behaviors implemented in Cisco and IBM routers." + +.. clicmd:: ospf rfc1583compatibility + + + :rfc:`2328`, the successor to :rfc:`1583`, suggests according + to section G.2 (changes) in section 16.4 a change to the path + preference algorithm that prevents possible routing loops that were + possible in the old version of OSPFv2. More specifically it demands + that inter-area paths and intra-area backbone path are now of equal preference + but still both preferred to external paths. + + This command should NOT be set normally. + +.. clicmd:: log-adjacency-changes [detail] + + + Configures ospfd to log changes in adjacency. With the optional + detail argument, all changes in adjacency status are shown. Without detail, + only changes to full or regressions are shown. + +.. clicmd:: passive-interface default + + Make all interfaces that belong to this router passive by default. For the + description of passive interface look at :clicmd:`ip ospf passive [A.B.C.D]`. + Per-interface configuration takes precedence over the default value. + +.. clicmd:: timers throttle spf (0-600000) (0-600000) (0-600000) + + This command sets the initial `delay`, the `initial-holdtime` + and the `maximum-holdtime` between when SPF is calculated and the + event which triggered the calculation. The times are specified in + milliseconds and must be in the range of 0 to 600000 milliseconds. + + The `delay` specifies the minimum amount of time to delay SPF + calculation (hence it affects how long SPF calculation is delayed after + an event which occurs outside of the holdtime of any previous SPF + calculation, and also serves as a minimum holdtime). + + Consecutive SPF calculations will always be separated by at least + 'hold-time' milliseconds. The hold-time is adaptive and initially is + set to the `initial-holdtime` configured with the above command. + Events which occur within the holdtime of the previous SPF calculation + will cause the holdtime to be increased by `initial-holdtime`, bounded + by the `maximum-holdtime` configured with this command. If the adaptive + hold-time elapses without any SPF-triggering event occurring then + the current holdtime is reset to the `initial-holdtime`. The current + holdtime can be viewed with :clicmd:`show ip ospf`, where it is expressed as + a multiplier of the `initial-holdtime`. + + .. code-block:: frr + + router ospf + timers throttle spf 200 400 10000 + + + In this example, the `delay` is set to 200ms, the initial holdtime is set to + 400ms and the `maximum holdtime` to 10s. Hence there will always be at least + 200ms between an event which requires SPF calculation and the actual SPF + calculation. Further consecutive SPF calculations will always be separated + by between 400ms to 10s, the hold-time increasing by 400ms each time an + SPF-triggering event occurs within the hold-time of the previous SPF + calculation. + + This command supersedes the *timers spf* command in previous FRR + releases. + +.. clicmd:: max-metric router-lsa [on-startup (5-86400)|on-shutdown (5-100)] + +.. clicmd:: max-metric router-lsa administrative + + + This enables :rfc:`3137` support, where the OSPF process describes its + transit links in its router-LSA as having infinite distance so that other + routers will avoid calculating transit paths through the router while still + being able to reach networks through the router. + + This support may be enabled administratively (and indefinitely) or + conditionally. Conditional enabling of max-metric router-lsas can be for a + period of seconds after startup and/or for a period of seconds prior to + shutdown. + + Enabling this for a period after startup allows OSPF to converge fully first + without affecting any existing routes used by other routers, while still + allowing any connected stub links and/or redistributed routes to be + reachable. Enabling this for a period of time in advance of shutdown allows + the router to gracefully excuse itself from the OSPF domain. + + Enabling this feature administratively allows for administrative + intervention for whatever reason, for an indefinite period of time. Note + that if the configuration is written to file, this administrative form of + the stub-router command will also be written to file. If *ospfd* is + restarted later, the command will then take effect until manually + deconfigured. + + Configured state of this feature as well as current status, such as the + number of second remaining till on-startup or on-shutdown ends, can be + viewed with the :clicmd:`show ip ospf` command. + +.. clicmd:: auto-cost reference-bandwidth (1-4294967) + + + This sets the reference + bandwidth for cost calculations, where this bandwidth is considered + equivalent to an OSPF cost of 1, specified in Mbits/s. The default is + 100Mbit/s (i.e. a link of bandwidth 100Mbit/s or higher will have a + cost of 1. Cost of lower bandwidth links will be scaled with reference + to this cost). + + This configuration setting MUST be consistent across all routers within the + OSPF domain. + +.. clicmd:: neighbor A.B.C.D [poll-interval (1-65535)] [priority (0-255)] + + + Configures OSPF neighbors for non-broadcast multi-access (NBMA) networks + and point-to-multipoint non-broadcast networks. The `poll-interval` + specifies the rate for sending hello packets to neighbors that are not + active. When the configured neighbor is discovered, hello packets will be + sent at the rate of the hello-interval. The default `poll-interval` is 60 + seconds. The `priority` is used to for the Designated Router (DR) election + on non-broadcast multi-access networks. + +.. clicmd:: network A.B.C.D/M area A.B.C.D + +.. clicmd:: network A.B.C.D/M area (0-4294967295) + + + + This command specifies the OSPF enabled interface(s). If the interface has + an address from range 192.168.1.0/24 then the command below enables ospf + on this interface so router can provide network information to the other + ospf routers via this interface. + + .. code-block:: frr + + router ospf + network 192.168.1.0/24 area 0.0.0.0 + + Prefix length in interface must be equal or bigger (i.e. smaller network) than + prefix length in network statement. For example statement above doesn't enable + ospf on interface with address 192.168.1.1/23, but it does on interface with + address 192.168.1.129/25. + + Note that the behavior when there is a peer address + defined on an interface changed after release 0.99.7. + Currently, if a peer prefix has been configured, + then we test whether the prefix in the network command contains + the destination prefix. Otherwise, we test whether the network command prefix + contains the local address prefix of the interface. + + It is also possible to enable OSPF on a per interface/subnet basis + using the interface command (:clicmd:`ip ospf area AREA [ADDR]`). + However, mixing both network commands (:clicmd:`network`) and interface + commands (:clicmd:`ip ospf`) on the same router is not supported. + +.. clicmd:: proactive-arp + + + This command enables or disables sending ARP requests to update neighbor + table entries. It speeds up convergence for /32 networks on a P2P + connection. + + This feature is enabled by default. + +.. clicmd:: clear ip ospf [(1-65535)] process + + This command can be used to clear the ospf process data structures. This + will clear the ospf neighborship as well and it will get re-established. + This will clear the LSDB too. This will be helpful when there is a change + in router-id and if user wants the router-id change to take effect, user can + use this cli instead of restarting the ospfd daemon. + +.. clicmd:: clear ip ospf [(1-65535)] neighbor + + This command can be used to clear the ospf neighbor data structures. This + will clear the ospf neighborship and it will get re-established. This + command can be used when the neighbor state get stuck at some state and + this can be used to recover it from that state. + +.. clicmd:: maximum-paths (1-64) + + Use this command to control the maximum number of equal cost paths to reach + a specific destination. The upper limit may differ if you change the value + of MULTIPATH_NUM during compilation. The default is MULTIPATH_NUM (64). + +.. clicmd:: write-multiplier (1-100) + + Use this command to tune the amount of work done in the packet read and + write threads before relinquishing control. The parameter is the number + of packets to process before returning. The defult value of this parameter + is 20. + +.. clicmd:: socket buffer (1-4000000000) + + This command controls the ospf instance's socket buffer sizes. The + 'no' form resets one or both values to the default. + +.. clicmd:: no socket-per-interface + + Ordinarily, ospfd uses a socket per interface for sending + packets. This command disables those per-interface sockets, and + causes ospfd to use a single socket per ospf instance for sending + and receiving packets. + +.. _ospf-area: + +Areas +----- + +.. clicmd:: area A.B.C.D range A.B.C.D/M [advertise [cost (0-16777215)]] + +.. clicmd:: area (0-4294967295) range A.B.C.D/M [advertise [cost (0-16777215)]] + + + + Summarize intra area paths from specified area into one Type-3 summary-LSA + announced to other areas. This command can be used only in ABR and ONLY + router-LSAs (Type-1) and network-LSAs (Type-2) (i.e. LSAs with scope area) can + be summarized. Type-5 AS-external-LSAs can't be summarized - their scope is AS. + + .. code-block:: frr + + router ospf + network 192.168.1.0/24 area 0.0.0.0 + network 10.0.0.0/8 area 0.0.0.10 + area 0.0.0.10 range 10.0.0.0/8 + + + With configuration above one Type-3 Summary-LSA with routing info 10.0.0.0/8 is + announced into backbone area if area 0.0.0.10 contains at least one intra-area + network (i.e. described with router or network LSA) from this range. + +.. clicmd:: area A.B.C.D range A.B.C.D/M not-advertise + +.. clicmd:: area (0-4294967295) range A.B.C.D/M not-advertise + + + Instead of summarizing intra area paths filter them - i.e. intra area paths from this + range are not advertised into other areas. + This command makes sense in ABR only. + +.. clicmd:: area A.B.C.D range A.B.C.D/M {substitute A.B.C.D/M|cost (0-16777215)} + +.. clicmd:: area (0-4294967295) range A.B.C.D/M {substitute A.B.C.D/M|cost (0-16777215)} + + + Substitute summarized prefix with another prefix. + + .. code-block:: frr + + router ospf + network 192.168.1.0/24 area 0.0.0.0 + network 10.0.0.0/8 area 0.0.0.10 + area 0.0.0.10 range 10.0.0.0/8 substitute 11.0.0.0/8 + + + One Type-3 summary-LSA with routing info 11.0.0.0/8 is announced into backbone area if + area 0.0.0.10 contains at least one intra-area network (i.e. described with router-LSA or + network-LSA) from range 10.0.0.0/8. + + By default, the metric of the summary route is calculated as the highest + metric among the summarized routes. The `cost` option, however, can be used + to set an explicit metric. + + This command makes sense in ABR only. + +.. clicmd:: area A.B.C.D virtual-link A.B.C.D + +.. clicmd:: area (0-4294967295) virtual-link A.B.C.D + + + +.. clicmd:: area A.B.C.D shortcut + +.. clicmd:: area (0-4294967295) shortcut + + + + Configure the area as Shortcut capable. See :rfc:`3509`. This requires + that the 'abr-type' be set to 'shortcut'. + +.. clicmd:: area A.B.C.D stub + +.. clicmd:: area (0-4294967295) stub + + + + Configure the area to be a stub area. That is, an area where no router + originates routes external to OSPF and hence an area where all external + routes are via the ABR(s). Hence, ABRs for such an area do not need + to pass AS-External LSAs (type-5s) or ASBR-Summary LSAs (type-4) into the + area. They need only pass Network-Summary (type-3) LSAs into such an area, + along with a default-route summary. + +.. clicmd:: area A.B.C.D stub no-summary + +.. clicmd:: area (0-4294967295) stub no-summary + + + + Prevents an *ospfd* ABR from injecting inter-area + summaries into the specified stub area. + +.. clicmd:: area A.B.C.D nssa + +.. clicmd:: area (0-4294967295) nssa + + Configure the area to be a NSSA (Not-So-Stubby Area). This is an area that + allows OSPF to import external routes into a stub area via a new LSA type + (type 7). An NSSA autonomous system boundary router (ASBR) will generate this + type of LSA. The area border router (ABR) translates the LSA type 7 into LSA + type 5, which is propagated into the OSPF domain. NSSA areas are defined in + RFC 3101. + +.. clicmd:: area A.B.C.D nssa suppress-fa + +.. clicmd:: area (0-4294967295) nssa suppress-fa + + Configure the router to set the forwarding address to 0.0.0.0 in all LSA type 5 + translated from LSA type 7. The router needs to be elected the translator of the + area for this command to take effect. This feature causes routers that are + configured not to advertise forwarding addresses into the backbone to direct + forwarded traffic to the NSSA ABR translator. + +.. clicmd:: area A.B.C.D nssa default-information-originate [metric-type (1-2)] [metric (0-16777214)] + +.. clicmd:: area (0-4294967295) nssa default-information-originate [metric-type (1-2)] [metric (0-16777214)] + + NSSA ABRs and ASBRs can be configured with the `default-information-originate` + option to originate a Type-7 default route into the NSSA area. In the case + of NSSA ASBRs, the origination of the default route is conditioned to the + existence of a default route in the RIB that wasn't learned via the OSPF + protocol. + +.. clicmd:: area A.B.C.D nssa range A.B.C.D/M [] + +.. clicmd:: area (0-4294967295) nssa range A.B.C.D/M [] + + Summarize a group of external subnets into a single Type-7 LSA, which is + then translated to a Type-5 LSA and avertised to the backbone. + This command can only be used at the area boundary (NSSA ABR router). + + By default, the metric of the summary route is calculated as the highest + metric among the summarized routes. The `cost` option, however, can be used + to set an explicit metric. + + The `not-advertise` option, when present, prevents the summary route from + being advertised, effectively filtering the summarized routes. + +.. clicmd:: area A.B.C.D default-cost (0-16777215) + + + Set the cost of default-summary LSAs announced to stubby areas. + +.. clicmd:: area A.B.C.D export-list NAME + +.. clicmd:: area (0-4294967295) export-list NAME + + + + Filter Type-3 summary-LSAs announced to other areas originated from intra- + area paths from specified area. + + .. code-block:: frr + + router ospf + network 192.168.1.0/24 area 0.0.0.0 + network 10.0.0.0/8 area 0.0.0.10 + area 0.0.0.10 export-list foo + ! + access-list foo permit 10.10.0.0/16 + access-list foo deny any + + With example above any intra-area paths from area 0.0.0.10 and from range + 10.10.0.0/16 (for example 10.10.1.0/24 and 10.10.2.128/30) are announced into + other areas as Type-3 summary-LSA's, but any others (for example 10.11.0.0/16 + or 10.128.30.16/30) aren't. + + This command is only relevant if the router is an ABR for the specified + area. + +.. clicmd:: area A.B.C.D import-list NAME + +.. clicmd:: area (0-4294967295) import-list NAME + + + + Same as export-list, but it applies to paths announced into specified area + as Type-3 summary-LSAs. + +.. clicmd:: area A.B.C.D filter-list prefix NAME in + +.. clicmd:: area A.B.C.D filter-list prefix NAME out + +.. clicmd:: area (0-4294967295) filter-list prefix NAME in + +.. clicmd:: area (0-4294967295) filter-list prefix NAME out + + + + + + Filtering Type-3 summary-LSAs to/from area using prefix lists. This command + makes sense in ABR only. + +.. clicmd:: area A.B.C.D authentication + +.. clicmd:: area (0-4294967295) authentication + + + + Specify that simple password authentication should be used for the given + area. + +.. clicmd:: area A.B.C.D authentication message-digest + +.. clicmd:: area (0-4294967295) authentication message-digest + + Specify that OSPF packets must be authenticated with MD5 HMACs within the + given area. Keying material must also be configured on a per-interface basis + (:clicmd:`ip ospf message-digest-key`). + + MD5 authentication may also be configured on a per-interface basis + (:clicmd:`ip ospf authentication message-digest`). Such per-interface + settings will override any per-area authentication setting. + +.. _ospf-interface: + +Interfaces +---------- + +.. clicmd:: ip ospf area AREA [ADDR] + + + Enable OSPF on the interface, optionally restricted to just the IP address + given by `ADDR`, putting it in the `AREA` area. If you have a lot of + interfaces, and/or a lot of subnets, then enabling OSPF via this command + instead of (:clicmd:`network A.B.C.D/M area A.B.C.D`) may result in a + slight performance improvement. + + Notice that, mixing both network commands (:clicmd:`network`) and interface + commands (:clicmd:`ip ospf`) on the same router is not supported. + If (:clicmd:`ip ospf`) is present, (:clicmd:`network`) commands will fail. + +.. clicmd:: ip ospf authentication-key AUTH_KEY + + + Set OSPF authentication key to a simple password. After setting `AUTH_KEY`, + all OSPF packets are authenticated. `AUTH_KEY` has length up to 8 chars. + + Simple text password authentication is insecure and deprecated in favour of + MD5 HMAC authentication. + +.. clicmd:: ip ospf authentication message-digest + + Specify that MD5 HMAC authentication must be used on this interface. MD5 + keying material must also be configured. Overrides any authentication + enabled on a per-area basis + (:clicmd:`area A.B.C.D authentication message-digest`) + + Note that OSPF MD5 authentication requires that time never go backwards + (correct time is NOT important, only that it never goes backwards), even + across resets, if ospfd is to be able to promptly reestablish adjacencies + with its neighbors after restarts/reboots. The host should have system time + be set at boot from an external or non-volatile source (e.g. battery backed + clock, NTP, etc.) or else the system clock should be periodically saved to + non-volatile storage and restored at boot if MD5 authentication is to be + expected to work reliably. + +.. clicmd:: ip ospf message-digest-key KEYID md5 KEY + + + Set OSPF authentication key to a cryptographic password. The cryptographic + algorithm is MD5. + + KEYID identifies secret key used to create the message digest. This ID is + part of the protocol and must be consistent across routers on a link. + + KEY is the actual message digest key, of up to 16 chars (larger strings will + be truncated), and is associated with the given KEYID. + +.. clicmd:: ip ospf authentication key-chain KEYCHAIN + + Specify that HMAC cryptographic authentication must be used on this interface + using a key chain. Overrides any authentication enabled on a per-area basis + (:clicmd:`area A.B.C.D authentication message-digest`). + + ``KEYCHAIN``: Specifies the name of the key chain that contains the authentication + key(s) and cryptographic algorithms to be used for OSPF authentication. The key chain + is a logical container that holds one or more authentication keys, + allowing for key rotation and management. + + Note that OSPF HMAC cryptographic authentication requires that time never go backwards + (correct time is NOT important, only that it never goes backwards), even + across resets, if ospfd is to be able to promptly reestablish adjacencies + with its neighbors after restarts/reboots. The host should have system time + be set at boot from an external or non-volatile source (e.g. battery backed + clock, NTP, etc.) or else the system clock should be periodically saved to + non-volatile storage and restored at boot if HMAC cryptographic authentication is to be + expected to work reliably. + + Example: + + .. code:: sh + + r1(config)#key chain temp + r1(config-keychain)#key 13 + r1(config-keychain-key)#key-string ospf + r1(config-keychain-key)#cryptographic-algorithm hmac-sha-256 + r1(config)#int eth0 + r1(config-if)#ip ospf authentication key-chain temp + r1(config-if)#ip ospf area 0 + +.. clicmd:: ip ospf cost (1-65535) + + + Set link cost for the specified interface. The cost value is set to + router-LSA's metric field and used for SPF calculation. + +.. clicmd:: ip ospf dead-interval (1-65535) + +.. clicmd:: ip ospf dead-interval minimal hello-multiplier (2-20) + + + Set number of seconds for RouterDeadInterval timer value used for Wait Timer + and Inactivity Timer. This value must be the same for all routers attached + to a common network. The default value is 40 seconds. + + If 'minimal' is specified instead, then the dead-interval is set to 1 second + and one must specify a hello-multiplier. The hello-multiplier specifies how + many Hellos to send per second, from 2 (every 500ms) to 20 (every 50ms). + Thus one can have 1s convergence time for OSPF. If this form is specified, + then the hello-interval advertised in Hello packets is set to 0 and the + hello-interval on received Hello packets is not checked, thus the + hello-multiplier need NOT be the same across multiple routers on a common + link. + +.. clicmd:: ip ospf hello-interval (1-65535) + + + Set number of seconds for HelloInterval timer value. Setting this value, + Hello packet will be sent every timer value seconds on the specified interface. + This value must be the same for all routers attached to a common network. + The default value is 10 seconds. + + This command has no effect if + :clicmd:`ip ospf dead-interval minimal hello-multiplier (2-20)` is also + specified for the interface. + +.. clicmd:: ip ospf graceful-restart hello-delay (1-1800) + + Set the length of time during which Grace-LSAs are sent at 1-second intervals + while coming back up after an unplanned outage. During this time, no hello + packets are sent. + + A higher hello delay will increase the chance that all neighbors are notified + about the ongoing graceful restart before receiving a hello packet (which is + crucial for the graceful restart to succeed). The hello delay shouldn't be set + too high, however, otherwise the adjacencies might time out. As a best practice, + it's recommended to set the hello delay and hello interval with the same values. + The default value is 10 seconds. + +.. clicmd:: ip ospf network (broadcast|non-broadcast|point-to-multipoint [delay-reflood|non-broadcast]|point-to-point [dmvpn]) + + When configuring a point-to-point network on an interface and the interface + has a /32 address associated with then OSPF will treat the interface + as being `unnumbered`. If you are doing this you *must* set the + net.ipv4.conf..rp_filter value to 0. In order for + the ospf multicast packets to be delivered by the kernel. + + When used in a DMVPN network at a spoke, this OSPF will be configured in + point-to-point, but the HUB will be a point-to-multipoint. To make this + topology work, specify the optional 'dmvpn' parameter at the spoke. + + When the network is configured as point-to-multipoint and `non-broadcast` + is specified, the network doesn't support broadcast or multicast delivery + and neighbors cannot be discovered from OSPF hello received from the + OSPFAllRouters (224.0.0.5). Rather, they must be explicitly configured + using the :clicmd:`neighbor A.B.C.D` configuration command as they are + on non-broadcast networks. + + When the network is configured as point-to-multipoint and `delay-reflood` + is specified, LSAs received on the interface from neighbors on the + interface will not be flooded back out on the interface immediately. + Rather, they will be added to the neighbor's link state retransmission + list and only sent to the neighbor if the neighbor doesn't acknowledge + the LSA prior to the link state retransmission timer expiring. + + Set explicitly network type for specified interface. + +.. clicmd:: ip ospf priority (0-255) + + + Set RouterPriority integer value. The router with the highest priority will + be more eligible to become Designated Router. Setting the value to 0, makes + the router ineligible to become Designated Router. The default value is 1. + +.. clicmd:: ip ospf retransmit-interval (1-65535) + + + Set number of seconds for RxmtInterval timer value. This value is used when + retransmitting Database Description and Link State Request packets. The + default value is 5 seconds. + +.. clicmd:: ip ospf transmit-delay (1-65535) [A.B.C.D] + + + Set number of seconds for InfTransDelay value. LSAs' age should be + incremented by this value when transmitting. The default value is 1 second. + +.. clicmd:: ip ospf passive [A.B.C.D] + + Do not speak OSPF on the interface, but do advertise the interface as a stub + link in the router-:abbr:`LSA (Link State Advertisement)` for this router. + This allows one to advertise addresses on such connected interfaces without + having to originate AS-External/Type-5 LSAs (which have global flooding + scope) - as would occur if connected addresses were redistributed into + OSPF (:ref:`redistribute-routes-to-ospf`). This is the only way to + advertise non-OSPF links into stub areas. + +.. clicmd:: ip ospf prefix-suppression [A.B.C.D] + + Configure OSPF to not advertise the IPv4 prefix associated with the + OSPF interface. The associated IPv4 prefix will be omitted from an OSPF + router-LSA or advertised with a host mask in an OSPF network-LSA as + specified in RFC 6860, "Hiding Transit-Only Networks in OSPF". If an + optional IPv4 address is specified, the prefix suppression will apply + to the OSPF interface associated with the specified interface address. + +.. clicmd:: ip ospf neighbor-filter NAME [A.B.C.D] + + Configure an IP prefix-list to use to filter packets received from + OSPF neighbors on the OSPF interface. The prefix-list should include rules + to permit or deny OSPF neighbors by IP source address. This is useful for + multi-access interfaces where adjacencies with only a subset of the + reachable neighbors are desired. Applications include testing partially + meshed topologies, OSPF Denial of Sevice (DoS) mitigation, and avoidance + of adjacencies with OSPF neighbors not meeting traffic engineering criteria. + + Example: + +.. code-block:: frr + + ! + ! Prefix-list to block neighbor with source address 10.1.0.2 + ! + ip prefix-list nbr-filter seq 10 deny 10.1.0.2/32 + ip prefix-list nbr-filter seq 200 permit any + ! + ! Configure the neighbor filter prefix-list on interface eth0 + ! + interface eth0 + ip ospf neighbor-filter nbr-filter + ! + +.. clicmd:: ip ospf area (A.B.C.D|(0-4294967295)) + + + Enable ospf on an interface and set associated area. + +OSPF route-map +============== + +Usage of *ospfd*'s route-map support. + +.. clicmd:: set metric [+|-](0-4294967295) + + Set a metric for matched route when sending announcement. Use plus (+) sign + to add a metric value to an existing metric. Use minus (-) sign to + substract a metric value from an existing metric. + +.. _redistribute-routes-to-ospf: + +Redistribution +-------------- + +.. _ospf-redistribute: + +.. clicmd:: redistribute [metric-type (1-2)] [metric (0-16777214)] [route-map WORD] + + Redistribute routes of the specified protocol or kind into OSPF, with the + metric type and metric set if specified, filtering the routes using the + given route-map if specified. Redistributed routes may also be filtered + with distribute-lists, see + :ref:`ospf distribute-list configuration `. + + Redistributed routes are distributed as into OSPF as Type-5 External LSAs + into links to areas that accept external routes, Type-7 External LSAs for + NSSA areas and are not redistributed at all into Stub areas, where external + routes are not permitted. + + Note that for connected routes, one may instead use the + :clicmd:`ip ospf passive [A.B.C.D]` configuration. + +.. clicmd:: default-information originate + +.. clicmd:: default-information originate metric (0-16777214) + +.. clicmd:: default-information originate metric (0-16777214) metric-type (1|2) + +.. clicmd:: default-information originate metric (0-16777214) metric-type (1|2) route-map WORD + +.. clicmd:: default-information originate always + +.. clicmd:: default-information originate always metric (0-16777214) + +.. clicmd:: default-information originate always metric (0-16777214) metric-type (1|2) + +.. clicmd:: default-information originate always metric (0-16777214) metric-type (1|2) route-map WORD + + + Originate an AS-External (type-5) LSA describing a default route into all + external-routing capable areas, of the specified metric and metric type. If + the 'always' keyword is given then the default is always advertised, even + when there is no default present in the routing table. + +.. _ospf-distribute-list: + +.. clicmd:: distribute-list NAME out + + Apply the access-list filter, NAME, to redistributed routes of the given + type before allowing the routes to be redistributed into OSPF + (:ref:`ospf redistribution `). + +.. clicmd:: default-metric (0-16777214) + + +.. clicmd:: distance (1-255) + + +.. clicmd:: distance ospf (intra-area|inter-area|external) (1-255) + + + +Graceful Restart +================ + +.. clicmd:: graceful-restart [grace-period (1-1800)] + + + Configure Graceful Restart (RFC 3623) restarting support. + When enabled, the default grace period is 120 seconds. + + To perform a graceful shutdown, the "graceful-restart prepare ip ospf" + EXEC-level command needs to be issued before restarting the ospfd daemon. + + When Graceful Restart is enabled and the ospfd daemon crashes or is killed + abruptely (e.g. SIGKILL), it will attempt an unplanned Graceful Restart once + it restarts. + +.. clicmd:: graceful-restart helper enable [A.B.C.D] + + + Configure Graceful Restart (RFC 3623) helper support. + By default, helper support is disabled for all neighbors. + This config enables/disables helper support on this router + for all neighbors. + To enable/disable helper support for a specific + neighbor, the router-id (A.B.C.D) has to be specified. + +.. clicmd:: graceful-restart helper strict-lsa-checking + + + If 'strict-lsa-checking' is configured then the helper will + abort the Graceful Restart when a LSA change occurs which + affects the restarting router. + By default 'strict-lsa-checking' is enabled" + +.. clicmd:: graceful-restart helper supported-grace-time (10-1800) + + + Supports as HELPER for configured grace period. + +.. clicmd:: graceful-restart helper planned-only + + + It helps to support as HELPER only for planned + restarts. By default, it supports both planned and + unplanned outages. + + +.. clicmd:: graceful-restart prepare ip ospf + + + Initiate a graceful restart for all OSPF instances configured with the + "graceful-restart" command. The ospfd daemon should be restarted during + the instance-specific grace period, otherwise the graceful restart will fail. + + This is an EXEC-level command. + + +.. _showing-ospf-information: + +Showing Information +=================== + +.. _show-ip-ospf: + +.. clicmd:: show ip ospf [vrf ] [json] + + Show information on a variety of general OSPF and area state and + configuration information. + +.. clicmd:: show ip ospf interface [INTERFACE] [json] + + Show state and configuration of OSPF the specified interface, or all + interfaces if no interface is given. + +.. clicmd:: show ip ospf neighbor [json] + +.. clicmd:: show ip ospf [vrf ] neighbor INTERFACE [json] + +.. clicmd:: show ip ospf neighbor detail [json] + +.. clicmd:: show ip ospf [vrf ] neighbor A.B.C.D [detail] [json] + +.. clicmd:: show ip ospf [vrf ] neighbor INTERFACE detail [json] + + Display lsa information of LSDB. + Json o/p of this command covers base route information + i.e all LSAs except opaque lsa info. + +.. clicmd:: show ip ospf [vrf ] database [self-originate] [json] + + Show the OSPF database summary. + +.. clicmd:: show ip ospf [vrf ] database max-age [json] + + Show all MaxAge LSAs present in the OSPF link-state database. + +.. clicmd:: show ip ospf [vrf ] database detail [LINK-STATE-ID] [adv-router A.B.C.D] [json] + +.. clicmd:: show ip ospf [vrf ] database detail [LINK-STATE-ID] [self-originate] [json] + +.. clicmd:: show ip ospf [vrf ] database (asbr-summary|external|network|router|summary|nssa-external|opaque-link|opaque-area|opaque-as) [LINK-STATE-ID] [adv-router A.B.C.D] [json] + +.. clicmd:: show ip ospf [vrf ] database (asbr-summary|external|network|router|summary|nssa-external|opaque-link|opaque-area|opaque-as) [LINK-STATE-ID] [self-originate] [json] + + Show detailed information about the OSPF link-state database. + +.. clicmd:: show ip ospf route [detail] [json] + + Show the OSPF routing table, as determined by the most recent SPF + calculation. When detail option is used, it shows more information + to the CLI like advertising router ID for each route, etc. + +.. clicmd:: show ip ospf [vrf ] border-routers [json] + + Show the list of ABR and ASBR border routers summary learnt via + OSPFv2 Type-3 (Summary LSA) and Type-4 (Summary ASBR LSA). + User can get that information as JSON format when ``json`` keyword + at the end of cli is presented. + +.. clicmd:: show ip ospf [{(1-65535)|vrf }] graceful-restart helper [detail] [json] + + Displays the Graceful Restart Helper details including helper + config changes. + +.. _opaque-lsa: + +Opaque LSA +========== + +.. clicmd:: ospf opaque-lsa + +.. clicmd:: capability opaque + + + + *ospfd* supports Opaque LSA (:rfc:`5250`) as partial support for + MPLS Traffic Engineering LSAs. The opaque-lsa capability must be + enabled in the configuration. An alternate command could be + "mpls-te on" (:ref:`ospf-traffic-engineering`). Note that FRR + offers only partial support for some of the routing protocol + extensions that are used with MPLS-TE; it does not support a + complete RSVP-TE solution. + +.. clicmd:: ip ospf capability opaque [A.B.C.D] + + Enable or disable OSPF LSA database exchange and flooding on an interface. + The default is that opaque capability is enabled as long as the opaque + capability is enabled with the :clicmd:`capability opaque` command at the + OSPF instance level (using the command above). Note that disabling opaque + LSA support on an interface will impact the applications using opaque LSAs + if the opaque LSAs are not received on other flooding paths by all the + OSPF routers using those applications. For example, OSPF Graceful Restart + uses opaque-link LSAs and disabling support on an interface will disable + graceful restart signaling on that interface. + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID adv-router ADV-ROUTER + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) adv-router ADV-ROUTER + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) LINK-STATE-ID self-originate + +.. clicmd:: show ip ospf [vrf ] database (opaque-link|opaque-area|opaque-external) self-originate + + Show Opaque LSA from the database. + +.. clicmd:: show ip ospf (1-65535) reachable-routers + +.. clicmd:: show ip ospf [vrf ] reachable-routers + + Show routing table of reachable routers. + +.. _ospf-traffic-engineering: + +Traffic Engineering +=================== + +.. note:: + + At this time, FRR offers partial support for some of the routing + protocol extensions that can be used with MPLS-TE. FRR does not + support a complete RSVP-TE solution currently. + +.. clicmd:: mpls-te on + + + Enable Traffic Engineering LSA flooding. + +.. clicmd:: mpls-te router-address + + Configure stable IP address for MPLS-TE. This IP address is then advertise + in Opaque LSA Type-10 TLV=1 (TE) option 1 (Router-Address). + +.. clicmd:: mpls-te inter-as area |as + + + Enable :rfc:`5392` support - Inter-AS TE v2 - to flood Traffic Engineering + parameters of Inter-AS link. 2 modes are supported: AREA and AS; LSA are + flood in AREA with Opaque Type-10, respectively in AS with Opaque + Type-11. In all case, Opaque-LSA TLV=6. + +.. clicmd:: mpls-te export + + Export Traffic Engineering Data Base to other daemons through the ZAPI + Opaque Link State messages. + +.. clicmd:: show ip ospf mpls-te interface + +.. clicmd:: show ip ospf mpls-te interface INTERFACE + + Show MPLS Traffic Engineering parameters for all or specified interface. + +.. clicmd:: show ip ospf mpls-te router + + Show Traffic Engineering router parameters. + +.. clicmd:: show ip ospf mpls-te database [verbose|json] + +.. clicmd:: show ip ospf mpls-te database vertex [self-originate|adv-router ADV-ROUTER] [verbose|json] + +.. clicmd:: show ip ospf mpls-te database edge [A.B.C.D] [verbose|json] + +.. clicmd:: show ip ospf mpls-te database subnet [A.B.C.D/M] [verbose|json] + + Show Traffic Engineering Database + +.. _router-information: + +Router Information +================== + +.. clicmd:: router-info [as | area] + + + Enable Router Information (:rfc:`4970`) LSA advertisement with AS scope + (default) or Area scope flooding when area is specified. Old syntax + `router-info area ` is always supported but mark as deprecated + as the area ID is no more necessary. Indeed, router information support + multi-area and detect automatically the areas. + +.. clicmd:: pce address + + +.. clicmd:: pce domain as (0-65535) + + +.. clicmd:: pce neighbor as (0-65535) + + +.. clicmd:: pce flag BITPATTERN + + +.. clicmd:: pce scope BITPATTERN + + + The commands are conform to :rfc:`5088` and allow OSPF router announce Path + Computation Element (PCE) capabilities through the Router Information (RI) + LSA. Router Information must be enable prior to this. The command set/unset + respectively the PCE IP address, Autonomous System (AS) numbers of + controlled domains, neighbor ASs, flag and scope. For flag and scope, please + refer to :rfc`5088` for the BITPATTERN recognition. Multiple 'pce neighbor' + command could be specified in order to specify all PCE neighbors. + +.. clicmd:: show ip ospf router-info + + Show Router Capabilities flag. + +.. clicmd:: show ip ospf router-info pce + + Show Router Capabilities PCE parameters. + +Segment Routing +=============== + +This is an EXPERIMENTAL support of Segment Routing as per `RFC 8665` for MPLS +dataplane. + +.. clicmd:: segment-routing on + + Enable Segment Routing. Even if this also activate routing information + support, it is preferable to also activate routing information, and set + accordingly the Area or AS flooding. + +.. clicmd:: segment-routing global-block (16-1048575) (16-1048575) [local-block (16-1048575) (16-1048575)] + + Set the Segment Routing Global Block i.e. the label range used by MPLS to + store label in the MPLS FIB for Prefix SID. Optionally also set the Local + Block, i.e. the label range used for Adjacency SID. The negative version + of the command always unsets both ranges. + +.. clicmd:: segment-routing node-msd (1-16) + + Fix the Maximum Stack Depth supported by the router. The value depend of the + MPLS dataplane. E.g. for Linux kernel, since version 4.13 it is 32. + +.. clicmd:: segment-routing prefix A.B.C.D/M [index (0-65535)|no-php-flag|explicit-null] + + prefix with /32 corresponding to a loopback interface are currently + supported. The 'no-php-flag' means NO Penultimate Hop Popping that allows SR + node to request to its neighbor to not pop the label. The 'explicit-null' means that + neighbor nodes must swap the incoming label by the MPLS Explicit Null label + before delivering the packet. + +.. clicmd:: show ip ospf database segment-routing [json] + + Show Segment Routing Data Base, all SR nodes, specific advertised router or + self router. Optional JSON output can be obtained by appending 'json' to the + end of the command. + +External Route Summarisation +============================ +This feature summarises originated external LSAs(Type-5 and Type-7). +Summary Route will be originated on-behalf of all matched external LSAs. + +.. clicmd:: summary-address A.B.C.D/M [tag (1-4294967295)] + + This command enable/disables summarisation for the configured address + range. Tag is the optional parameter. If tag configured Summary route + will be originated with the configured tag. + +.. clicmd:: summary-address A.B.C.D/M no-advertise + + This command to ensure not advertise the summary lsa for the matched + external LSAs. + +.. clicmd:: aggregation timer (5-1800) + + Configure aggregation delay timer interval. Summarisation starts only after + this delay timer expiry. By default, delay interval is 5 seconds. + + + The no form of the command resets the aggregation delay interval to default + value. + +.. clicmd:: show ip ospf [vrf ] summary-address [detail] [json] + + Show configuration for display all configured summary routes with + matching external LSA information. + +TI-LFA +====== + +Experimental support for Topology Independent LFA (Loop-Free Alternate), see +for example 'draft-bashandy-rtgwg-segment-routing-ti-lfa-05'. Note that +TI-LFA requires a proper Segment Routing configuration. + +.. clicmd:: fast-reroute ti-lfa [node-protection] + + Configured on the router level. Activates TI-LFA for all interfaces. + + Note that so far only P2P interfaces are supported. + +.. _debugging-ospf: + +Debugging OSPF +============== + +.. clicmd:: debug ospf [(1-65535)] bfd + + Enable or disable debugging for BFD events. This will show BFD integration + library messages and OSPF BFD integration messages that are mostly state + transitions and validation problems. + +.. clicmd:: debug ospf [(1-65535)] client-api + + Show debug information for the OSPF opaque data client API. + +.. clicmd:: debug ospf [(1-65535)] default-information + + Show debug information of default information + +.. clicmd:: debug ospf [(1-65535)] packet (hello|dd|ls-request|ls-update|ls-ack|all) (send|recv) [detail] + + + Dump Packet for debugging + +.. clicmd:: debug ospf [(1-65535)] ism [status|events|timers] + + + + Show debug information of Interface State Machine + +.. clicmd:: debug ospf [(1-65535)] nsm [status|events|timers] + + + + Show debug information of Network State Machine + +.. clicmd:: debug ospf [(1-65535)] event + + + Show debug information of OSPF event + +.. clicmd:: debug ospf [(1-65535)] nssa + + + Show debug information about Not So Stub Area + +.. clicmd:: debug ospf [(1-65535)] ldp-sync + + Show debug information about LDP-Sync + +.. clicmd:: debug ospf [(1-65535)] lsa [aggregate|flooding|generate|install|refresh] + + + + Show debug detail of Link State messages + +.. clicmd:: debug ospf [(1-65535)] sr + + Show debug information about Segment Routing + +.. clicmd:: debug ospf [(1-65535)] te + + + Show debug information about Traffic Engineering LSA + +.. clicmd:: debug ospf [(1-65535)] ti-lfa + + Show debug information about SR TI-LFA + +.. clicmd:: debug ospf [(1-65535)] zebra [interface|redistribute] + + + + Show debug information of ZEBRA API + +.. clicmd:: debug ospf [(1-65535)] graceful-restart + + + Enable/disable debug information for OSPF Graceful Restart Helper + +.. clicmd:: show debugging ospf + + + +Sample Configuration +==================== + +A simple example, with MD5 authentication enabled: + +.. code-block:: frr + + ! + interface bge0 + ip ospf authentication message-digest + ip ospf message-digest-key 1 md5 ABCDEFGHIJK + ! + router ospf + network 192.168.0.0/16 area 0.0.0.1 + area 0.0.0.1 authentication message-digest + + +An :abbr:`ABR` router, with MD5 authentication and performing summarisation +of networks between the areas: + +.. code-block:: frr + + ! + password ABCDEF + log file /var/log/frr/ospfd.log + service advanced-vty + ! + interface eth0 + ip ospf authentication message-digest + ip ospf message-digest-key 1 md5 ABCDEFGHIJK + ! + interface ppp0 + ip ospf passive + ! + interface br0 + ip ospf authentication message-digest + ip ospf message-digest-key 2 md5 XYZ12345 + ! + router ospf + ospf router-id 192.168.0.1 + redistribute connected + network 192.168.0.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + network 192.168.1.0/24 area 0.0.0.1 + area 0.0.0.0 authentication message-digest + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 192.168.0.0/24 + area 0.0.0.1 authentication message-digest + area 0.0.0.1 range 10.2.0.0/16 + ! + + +A Traffic Engineering configuration, with Inter-ASv2 support. + +First, the :file:`zebra.conf` part: + +.. code-block:: frr + + interface eth0 + ip address 198.168.1.1/24 + link-params + enable + admin-grp 0xa1 + metric 100 + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + ! + interface eth1 + ip address 192.168.2.1/24 + link-params + enable + metric 10 + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + neighbor 192.168.2.2 as 65000 + hostname HOSTNAME + password PASSWORD + log file /var/log/zebra.log + ! + interface eth0 + ip address 198.168.1.1/24 + link-params + enable + admin-grp 0xa1 + metric 100 + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + ! + interface eth1 + ip address 192.168.2.1/24 + link-params + enable + metric 10 + max-bw 1.25e+07 + max-rsv-bw 1.25e+06 + unrsv-bw 0 1.25e+06 + unrsv-bw 1 1.25e+06 + unrsv-bw 2 1.25e+06 + unrsv-bw 3 1.25e+06 + unrsv-bw 4 1.25e+06 + unrsv-bw 5 1.25e+06 + unrsv-bw 6 1.25e+06 + unrsv-bw 7 1.25e+06 + neighbor 192.168.2.2 as 65000 + +Then the :file:`ospfd.conf` itself: + +.. code-block:: frr + + hostname HOSTNAME + password PASSWORD + log file /var/log/ospfd.log + ! + ! + interface eth0 + ip ospf hello-interval 60 + ip ospf dead-interval 240 + ! + interface eth1 + ip ospf hello-interval 60 + ip ospf dead-interval 240 + ! + ! + router ospf + ospf router-id 192.168.1.1 + network 192.168.0.0/16 area 1 + ospf opaque-lsa + mpls-te + mpls-te router-address 192.168.1.1 + mpls-te inter-as area 1 + ! + line vty + +A router information example with PCE advertisement: + +.. code-block:: frr + + ! + router ospf + ospf router-id 192.168.1.1 + network 192.168.0.0/16 area 1 + capability opaque + mpls-te + mpls-te router-address 192.168.1.1 + router-info area 0.0.0.1 + pce address 192.168.1.1 + pce flag 0x80 + pce domain as 65400 + pce neighbor as 65500 + pce neighbor as 65200 + pce scope 0x80 + ! diff --git a/doc/user/overview.rst b/doc/user/overview.rst new file mode 100644 index 0000000..2ef88ac --- /dev/null +++ b/doc/user/overview.rst @@ -0,0 +1,574 @@ +.. _overview: + +******** +Overview +******** + +`FRR`_ is a fully featured, high performance, free software IP routing suite. + +FRR implements all standard routing protocols such as BGP, RIP, OSPF, IS-IS and +more (see :ref:`feature-matrix`), as well as many of their extensions. + +FRR is a high performance suite written primarily in C. It can easily handle +full Internet routing tables and is suitable for use on hardware ranging from +cheap SBCs to commercial grade routers. It is actively used in production by +hundreds of companies, universities, research labs and governments. + +FRR is distributed under GPLv2, with development modeled after the Linux +kernel. Anyone may contribute features, bug fixes, tools, documentation +updates, or anything else. + +FRR is a fork of `Quagga `_. + +.. _how-to-get-frr: + +How to get FRR +============== + +The official FRR website is located at |PACKAGE_URL| and contains further +information, as well as links to additional resources. + +Several distributions provide packages for FRR. Check your distribution's +repositories to find out if a suitable version is available. + +Up-to-date Debian & Redhat packages are available at https://deb.frrouting.org/ +& https://rpm.frrouting.org/ respectively. + +For instructions on installing from source, refer to the +`developer documentation `_. + + +.. _about-frr: + +About FRR +========= + +FRR provides IP routing services. Its role in a networking stack is to exchange +routing information with other routers, make routing and policy decisions, and +inform other layers of these decisions. In the most common scenario, FRR +installs routing decisions into the OS kernel, allowing the kernel networking +stack to make the corresponding forwarding decisions. + +In addition to dynamic routing FRR supports the full range of L3 configuration, +including static routes, addresses, router advertisements etc. It has some +light L2 functionality as well, but this is mostly left to the platform. This +makes it suitable for deployments ranging from small home networks with static +routes to Internet exchanges running full Internet tables. + +FRR runs on all modern \*NIX operating systems, including Linux and the BSDs. +Feature support varies by platform; see the :ref:`feature-matrix`. + +System Requirements +------------------- + +System resources needed by FRR are highly dependent on workload. Routing +software performance is particularly susceptible to external factors such as: + +* Kernel networking stack +* Physical NIC +* Peer behavior +* Routing information scale + +Because of these factors - especially the last one - it's difficult to lay out +resource requirements. + +To put this in perspective, FRR can be run on very low resource systems such as +SBCs, provided it is not stressed too much. If you want to set up 4 Raspberry +Pis to play with BGP or OSPF, it should work fine. If you ask a FRR to process +a complete internet routing table on a Raspberry Pi, you will be disappointed. +However, given enough resources, FRR ought to be capable of acting as a core IX +router. Such a use case requires at least 4gb of memory and a recent quad-core +server processor at a minimum. + +If you are new to networking, an important thing to remember is that FRR is +control plane software. It does not itself forward packets - it exchanges +information with peers about how to forward packets. Forwarding plane +performance largely depends on choice of NIC / ASIC. + + +System Architecture +------------------- + +.. index:: + pair: architecture; FRR + +Traditional routing software is made as a one process program which provides +all of the routing protocol functionalities. FRR takes a different approach. +FRR is a suite of daemons that work together to build the routing table. Each +major protocol is implemented in its own daemon, and these daemons talk to a +middleman daemon (*zebra*), which is responsible for coordinating routing +decisions and talking to the dataplane. + +This architecture allows for high resiliency, since an error, crash or exploit +in one protocol daemon will generally not affect the others. It is also +flexible and extensible since the modularity makes it easy to implement new +protocols and tie them into the suite. Additionally, each daemon implements a +plugin system allowing new functionality to be loaded at runtime. + +An illustration of the large scale architecture is given below. + +:: + + +----+ +----+ +-----+ +----+ +----+ +----+ +-----+ + |bgpd| |ripd| |ospfd| |ldpd| |pbrd| |pimd| |.....| + +----+ +----+ +-----+ +----+ +----+ +----+ +-----+ + | | | | | | | + +----v-------v--------v-------v-------v-------v--------v + | | + | Zebra | + | | + +------------------------------------------------------+ + | | | + | | | + +------v------+ +---------v--------+ +------v------+ + | | | | | | + | *NIX Kernel | | Remote dataplane | | ........... | + | | | | | | + +-------------+ +------------------+ +-------------+ + + +All of the FRR daemons can be managed through a single integrated user +interface shell called *vtysh*. *vtysh* connects to each daemon through a UNIX +domain socket and then works as a proxy for user input. In addition to a +unified frontend, *vtysh* also provides the ability to configure all the +daemons using a single configuration file through the integrated configuration +mode. This avoids the overhead of maintaining a separate configuration file for +each daemon. + +FRR is currently implementing a new internal configuration system based on YANG +data models. When this work is completed, FRR will be a fully programmable +routing stack. + + +.. index:: + pair: platforms; FRR + pair: operating systems; FRR + +.. _supported-platforms: + +Supported Platforms +------------------- + + +Currently FRR supports GNU/Linux and BSD. Porting FRR to other platforms is not +too difficult as platform dependent code should be mostly limited to the +*Zebra* daemon. Protocol daemons are largely platform independent. Please let +us know if you can get FRR to run on a platform which is not listed below: + +- GNU/Linux +- FreeBSD +- NetBSD +- OpenBSD + +Versions of these platforms that are older than around 2 years from the point +of their original release (in case of GNU/Linux, this is since the kernel's +release on https://kernel.org/) may need some work. Similarly, the following +platforms may work with some effort: + +- MacOS + +Recent versions of the following compilers are well tested: + +- GNU's GCC +- LLVM's Clang +- Intel's ICC + +.. _unsupported-platforms: + +Unsupported Platforms +--------------------- + +In General if the platform you are attempting to use is not listed above then +FRR does not support being run on that platform. The only caveat here is that +version 7.5 and before Solaris was supported in a limited fashion. + +.. _feature-matrix: + +Feature Matrix +^^^^^^^^^^^^^^ + +The following table lists all protocols cross-referenced to all operating +systems that have at least CI build tests. Note that for features, only +features with system dependencies are included here; if you don't see the +feature you're interested in, it should be supported on your platform. + +.. role:: mark + +.. comment - the :mark:`X` pieces mesh with a little bit of JavaScript and + CSS in _static/overrides.{js,css} respectively. The JS code looks at the + presence of the 'Y' 'N' '≥' '†' or 'CP' strings. This seemed to be the + best / least intrusive way of getting a nice table in HTML. The table + will look somewhat shoddy on other sphinx targets like PDF or info (but + should still be readable.) + ++-----------------------------------+----------------+--------------+------------+------------+ +| Daemon / Feature | Linux | OpenBSD | FreeBSD | NetBSD | ++===================================+================+==============+============+============+ +| **FRR Core** | | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| `zebra` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| VRF | :mark:`≥4.8` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| MPLS | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `pbrd` (Policy Routing) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| **WAN / Carrier protocols** | | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| `bgpd` (BGP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| VRF / L3VPN | :mark:`≥4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` | +| | :mark:`†4.3` | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| EVPN | :mark:`≥4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` | +| | :mark:`†4.9` | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| VNC (Virtual Network Control) | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | ++-----------------------------------+----------------+--------------+------------+------------+ +| Flowspec | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `ldpd` (LDP) | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| VPWS / PW | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| VPLS | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `nhrpd` (NHRP) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| **Link-State Routing** | | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| `ospfd` (OSPFv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| Segment Routing | :mark:`≥4.12` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `ospf6d` (OSPFv3) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `isisd` (IS-IS) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| **Distance-Vector Routing** | | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| `ripd` (RIPv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `ripngd` (RIPng) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `babeld` (BABEL) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `eigrpd` (EIGRP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| **Multicast Routing** | | | | | ++-----------------------------------+----------------+--------------+------------+------------+ +| `pimd` (PIM) | :mark:`≥4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| SSM (Source Specific) | :mark:`Y` | :mark:`N` | :mark:`Y` | :mark:`Y` | ++-----------------------------------+----------------+--------------+------------+------------+ +| ASM (Any Source) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| EVPN BUM Forwarding | :mark:`≥5.0` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ +| `vrrpd` (VRRP) | :mark:`≥5.1` | :mark:`N` | :mark:`N` | :mark:`N` | ++-----------------------------------+----------------+--------------+------------+------------+ + +The indicators have the following semantics: + +* :mark:`Y` - daemon/feature fully functional +* :mark:`≥X.X` - fully functional with kernel version X.X or newer +* :mark:`†X.X` - restricted functionality or impaired performance with kernel version X.X or newer +* :mark:`CP` - control plane only (i.e. BGP route server / route reflector) +* :mark:`N` - daemon/feature not supported by operating system + + +Known Kernel Issues +------------------- + +- Linux < 4.11 + + v6 Route Replacement - Linux kernels before 4.11 can cause issues with v6 + route deletion when you have ECMP routes installed into the kernel. This + especially becomes apparent if the route is being transformed from one ECMP + path to another. + + +.. index:: + pair: rfcs; FRR + +.. _supported-rfcs: + +Supported RFCs +-------------- + +FRR implements the following RFCs: + +.. note:: This list is incomplete. + +BGP +---- + +- :rfc:`1771` + :t:`A Border Gateway Protocol 4 (BGP-4). Y. Rekhter & T. Li. March 1995.` +- :rfc:`1965` + :t:`Autonomous System Confederations for BGP. P. Traina. June 1996.` +- :rfc:`1997` + :t:`BGP Communities Attribute. R. Chandra, P. Traina & T. Li. August 1996.` +- :rfc:`1998` + :t:`An Application of the BGP Community Attribute in Multi-home Routing. E. Chen, T. Bates. August 1996.` +- :rfc:`2385` + :t:`Protection of BGP Sessions via the TCP MD5 Signature Option. A. Heffernan. August 1998.` +- :rfc:`2439` + :t:`BGP Route Flap Damping. C. Villamizar, R. Chandra, R. Govindan. November 1998.` +- :rfc:`2545` + :t:`Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing. P. Marques, F. Dupont. March 1999.` +- :rfc:`2796` + :t:`BGP Route Reflection An alternative to full mesh IBGP. T. Bates & R. Chandrasekeran. June 1996.` +- :rfc:`2842` + :t:`Capabilities Advertisement with BGP-4. R. Chandra, J. Scudder. May 2000.` +- :rfc:`2858` + :t:`Multiprotocol Extensions for BGP-4. T. Bates, Y. Rekhter, R. Chandra, D. Katz. June 2000.` +- :rfc:`2918` + :t:`Route Refresh Capability for BGP-4. E. Chen, September 2000.` +- :rfc:`3107` + :t:`Carrying Label Information in BGP-4. Y. Rekhter & E. Rosen. May 2001.` +- :rfc:`3765` + :t:`NOPEER Community for Border Gateway Protocol (BGP) Route Scope Control. G.Huston. April 2001.` +- :rfc:`4271` + :t:`A Border Gateway Protocol 4 (BGP-4). Updates RFC1771. Y. Rekhter, T. Li & S. Hares. January 2006.` +- :rfc:`4360` + :t:`BGP Extended Communities Attribute. S. Sangli, D. Tappan, Y. Rekhter. February 2006.` +- :rfc:`4364` + :t:`BGP/MPLS IP Virtual Private Networks (VPNs). Y. Rekhter. February 2006.` +- :rfc:`4456` + :t:`BGP Route Reflection An alternative to full mesh IBGP. T. Bates, E. Chen, R. Chandra. April 2006.` +- :rfc:`4486` + :t:`Subcodes for BGP Cease Notification Message. E. Chen, V. Gillet. April 2006.` +- :rfc:`4659` + :t:`BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN. J. De Clercq, D. Ooms, M. Carugi, F. Le Faucheur. September 2006.` +- :rfc:`4724` + :t:`Graceful Restart Mechanism for BGP. S. Sangli, E. Chen, R. Fernando, J. Scudder, Y. Rekhter. January 2007.` +- :rfc:`4760` + :t:`Multiprotocol Extensions for BGP-4. T. Bates, R. Chandra, D. Katz, Y. Rekhter. January 2007.` +- :rfc:`4893` + :t:`BGP Support for Four-octet AS Number Space. Q. Vohra, E. Chen May 2007.` +- :rfc:`5004` + :t:`Avoid BGP Best Path Transitions from One External to Another. E. Chen & S. Sangli. September 2007 (Partial support).` +- :rfc:`5065` + :t:`Autonomous System Confederations for BGP. P. Traina, D. McPherson, J. Scudder. August 2007.` +- :rfc:`5082` + :t:`The Generalized TTL Security Mechanism (GTSM). V. Gill, J. Heasley, D. Meyer, P. Savola, C. Pingnataro. October 2007.` +- :rfc:`5291` + :t:`Outbound Route Filtering Capability. E. Chen, Y. Rekhter. August 2008.` +- :rfc:`5292` + :t:`Address-Prefix-Based Outbound Route Filter for BGP-4. E. Chen, S. Sangli. August 2008.` +- :rfc:`5396` + :t:`Textual Representation of Autonomous System (AS) Numbers. G. Michaelson, G. Huston. December 2008.` +- :rfc:`5492` + :t:`Capabilities Advertisement with BGP-4. J. Scudder, R. Chandra. February 2009.` +- :rfc:`5575` + :t:`Dissemination of Flow Specification Rules. P. Marques, N. Sheth, R. Raszuk, B. Greene, J. Mauch, D. McPherson. August 2009.` +- :rfc:`5668` + :t:`4-Octet AS Specific BGP Extended Community. Y. Rekhter, S. Sangli, D. Tappan October 2009.` +- :rfc:`6286` + :t:`Autonomous-System-Wide Unique BGP Identifier for BGP-4. E. Chen, J. Yuan. June 2011.` +- :rfc:`6472` + :t:`Recommendation for Not Using AS_SET and AS_CONFED_SET in BGP. W. Kumari, K. Sriram. December 2011.` +- :rfc:`6608` + :t:`Subcodes for BGP Finite State Machine Error. J. Dong, M. Chen, Huawei Technologies, A. Suryanarayana, Cisco Systems. May 2012.` +- :rfc:`6810` + :t:`The Resource Public Key Infrastructure (RPKI) to Router Protocol. R. Bush, R. Austein. January 2013.` +- :rfc:`6811` + :t:`BGP Prefix Origin Validation. P. Mohapatra, J. Scudder, D. Ward, R. Bush, R. Austein. January 2013.` +- :rfc:`6938` + :t:`Deprecation of BGP Path Attributes: DPA, ADVERTISER, and RCID_PATH / CLUSTER_ID. J. Scudder. May 2013.` +- :rfc:`6996` + :t:`Autonomous System (AS) Reservation for Private Use. J. Mitchell. July 2013.` +- :rfc:`7196` + :t:`Making Route Flap Damping Usable. C. Pelsser, R. Bush, K. Patel, P. Mohapatra, O. Maennel. May 2014.` +- :rfc:`7300` + :t:`Reservation of Last Autonomous System (AS) Numbers. J. Haas, J. Mitchell. July 2014.` +- :rfc:`7313` + :t:`Enhanced Route Refresh Capability for BGP-4. K. Patel, E. Chen, B. Venkatachalapathy. July 2014.` +- :rfc:`7606` + :t:`Revised Error Handling for BGP UPDATE Messages. E. Chen, J. Scudder, P. Mohapatra, K. Patel. August 2015.` +- :rfc:`7607` + :t:`Codification of AS 0 Processing. W. Kumari, R. Bush, H. Schiller, K. Patel. August 2015.` +- :rfc:`7611` + :t:`BGP ACCEPT_OWN Community Attribute. J. Uttaro, P. Mohapatra, D. Smith, R. Raszuk, J. Scudder. August 2015.` +- :rfc:`7911` + :t:`Advertisement of Multiple Paths in BGP. D. Walton, A. Retana, E. Chen, J. Scudder. July 2016.` +- :rfc:`7947` + :t:`Internet Exchange BGP Route Server. E. Jasinska, N. Hilliard, R. Raszuk, N. Bakker. September 2016.` +- :rfc:`7999` + :t:`BLACKHOLE Community. T. King, C. Dietzel, J. Snijders, G. Doering, G. Hankins. October 2016.` +- :rfc:`8050` + :t:`Multi-Threaded Routing Toolkit (MRT) Routing Information Export Format with BGP Additional Path Extensions. C. Petrie, T. King. May 2017.` +- :rfc:`8092` + :t:`BGP Large Communities Attribute. J. Heitz, Ed., J. Snijders, Ed, K. Patel, I. Bagdonas, N. Hilliard. February 2017.` +- :rfc:`8093` + :t:`Deprecation of BGP Path Attribute Values 30, 31, 129, 241, 242, and 243. J. Snijders. February 2017.` +- :rfc:`8097` + :t:`BGP Prefix Origin Validation State Extended Community. P. Mohapatra, K. Patel, J. Scudder, D. Ward, R. Bush. March 2017.` +- :rfc:`8195` + :t:`Use of BGP Large Communities. J. Snijders, J. Heasley, M. Schmidt. June 2017.` +- :rfc:`8203` + :t:`BGP Administrative Shutdown Communication. J. Snijders, J. Heitz, J. Scudder. July 2017.` +- :rfc:`8212` + :t:`Default External BGP (EBGP) Route Propagation Behavior without Policies. J. Mauch, J. Snijders, G. Hankins. July 2017.` +- :rfc:`8277` + :t:`Using BGP to Bind MPLS Labels to Address Prefixes. E. Rosen. October 2017.` +- :rfc:`8538` + :t:`Notification Message Support for BGP Graceful Restart. K. Patel, R. Fernando, J. Scudder, J. Haas. March 2019.` +- :rfc:`8654` + :t:`Extended Message Support for BGP. R. Bush, K. Patel, D. Ward. October 2019.` +- :rfc:`9003` + :t:`Extended BGP Administrative Shutdown Communication. J. Snijders, J. Heitz, J. Scudder, A. Azimov. January 2021.` +- :rfc:`9012` + :t:`The BGP Tunnel Encapsulation Attribute. K. Patel, G. Van de Velde, S. Sangli, J. Scudder. April 2021.` +- :rfc:`9072` + :t:`Extended Optional Parameters Length for BGP OPEN Message. E. Chen, J. Scudder. July 2021.` +- :rfc:`9234` + :t:`Route Leak Prevention and Detection Using Roles in UPDATE and OPEN Messages. A. Azimov, E. Bogomazov, R. Bush, K. Patel, K. Sriram. May 2022.` +- :rfc:`9384` + :t:`A BGP Cease NOTIFICATION Subcode for Bidirectional Forwarding Detection (BFD). J. Haas. March 2023.` +- :rfc:`9494` + :t:`Long-Lived Graceful Restart for BGP. J. Uttaro, E. Chen, B. Decraene, J. Scudder. November 2023.` + +OSPF +---- + +- :rfc:`2328` + :t:`OSPF Version 2. J. Moy. April 1998.` +- :rfc:`2370` + :t:`The OSPF Opaque LSA Option R. Coltun. July 1998.` +- :rfc:`3101` + :t:`The OSPF Not-So-Stubby Area (NSSA) Option P. Murphy. January 2003.` +- :rfc:`2740` + :t:`OSPF for IPv6. R. Coltun, D. Ferguson, J. Moy. December 1999.` +- :rfc:`3137` + :t:`OSPF Stub Router Advertisement, A. Retana, L. Nguyen, R. White, A. Zinin, D. McPherson. June 2001` + +ISIS +---- + +RIP +---- + +- :rfc:`1058` + :t:`Routing Information Protocol. C.L. Hedrick. Jun-01-1988.` +- :rfc:`2082` + :t:`RIP-2 MD5 Authentication. F. Baker, R. Atkinson. January 1997.` +- :rfc:`2453` + :t:`RIP Version 2. G. Malkin. November 1998.` +- :rfc:`2080` + :t:`RIPng for IPv6. G. Malkin, R. Minnear. January 1997.` + +PIM +---- + +BFD +---- +- :rfc:`5880` + :t:`Bidirectional Forwarding Detection (BFD), D. Katz, D. Ward. June 2010` +- :rfc:`5881` + :t:`Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop), D. Katz, D. Ward. June 2010` +- :rfc:`5882` + :t:`Generic Application of Bidirectional Forwarding Detection (BFD), D. Katz, D. Ward. June 2010` +- :rfc:`5883` + :t:`Bidirectional Forwarding Detection (BFD) for Multihop Paths, D. Katz, D. Ward. June 2010` + +MPLS +---- + +- :rfc:`2858` + :t:`Multiprotocol Extensions for BGP-4. T. Bates, Y. Rekhter, R. Chandra, D. Katz. June 2000.` +- :rfc:`4364` + :t:`BGP/MPLS IP Virtual Private Networks (VPNs). Y. Rekhter. Feb 2006.` +- :rfc:`4447` + :t:`Pseudowire Setup and Maintenance Using the Label Distribution Protocol (LDP), L. Martini, E. Rosen, N. El-Aawar, T. Smith, and G. Heron. April 2006.` +- :rfc:`4659` + :t:`BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN. J. De Clercq, D. Ooms, M. Carugi, F. Le Faucheur. September 2006` +- :rfc:`4762` + :t:`Virtual Private LAN Service (VPLS) Using Label Distribution Protocol (LDP) Signaling, M. Lasserre and V. Kompella. January 2007.` +- :rfc:`5036` + :t:`LDP Specification, L. Andersson, I. Minei, and B. Thomas. October 2007.` +- :rfc:`5561` + :t:`LDP Capabilities, B. Thomas, K. Raza, S. Aggarwal, R. Aggarwal, and JL. Le Roux. July 2009.` +- :rfc:`5918` + :t:`Label Distribution Protocol (LDP) 'Typed Wildcard' Forward Equivalence Class (FEC), R. Asati, I. Minei, and B. Thomas. August 2010.` +- :rfc:`5919` + :t:`Signaling LDP Label Advertisement Completion, R. Asati, P. Mohapatra, E. Chen, and B. Thomas. August 2010.` +- :rfc:`6667` + :t:`LDP 'Typed Wildcard' Forwarding Equivalence Class (FEC) for PWid and Generalized PWid FEC Elements, K. Raza, S. Boutros, and C. Pignataro. July 2012.` +- :rfc:`6720` + :t:`The Generalized TTL Security Mechanism (GTSM) for the Label Distribution Protocol (LDP), C. Pignataro and R. Asati. August 2012.` +- :rfc:`7552` + :t:`Updates to LDP for IPv6, R. Asati, C. Pignataro, K. Raza, V. Manral, and R. Papneja. June 2015.` + +VRRP +---- + +- :rfc:`3768` + :t:`Virtual Router Redundancy Protocol (VRRP). R. Hinden. April 2004.` +- :rfc:`5798` + :t:`Virtual Router Redundancy Protocol (VRRP) Version 3 for IPv4 and IPv6. S. Nadas. June 2000.` + +SNMP +---- + +**When SNMP support is enabled, the following RFCs are also supported:** + +- :rfc:`1227` + :t:`SNMP MUX protocol and MIB. M.T. Rose. May-01-1991.` +- :rfc:`1657` + :t:`Definitions of Managed Objects for the Fourth Version of the Border + Gateway Protocol (BGP-4) using SMIv2. S. Willis, J. Burruss, J. Chu, Editor. + July 1994.` +- :rfc:`1724` + :t:`RIP Version 2 MIB Extension. G. Malkin & F. Baker. November 1994.` +- :rfc:`1850` + :t:`OSPF Version 2 Management Information Base. F. Baker, R. Coltun. + November 1995.` +- :rfc:`2741` + :t:`Agent Extensibility (AgentX) Protocol. M. Daniele, B. Wijnen. January 2000.` + + +.. index:: + pair: mailing lists; contact + +.. _mailing-lists: + +Mailing Lists +============= + +Italicized lists are private. + ++--------------------------------+------------------------------+ +| Topic | List | ++================================+==============================+ +| Development | dev@lists.frrouting.org | ++--------------------------------+------------------------------+ +| Users & Operators | frog@lists.frrouting.org | ++--------------------------------+------------------------------+ +| Announcements | announce@lists.frrouting.org | ++--------------------------------+------------------------------+ +| *Security* | security@lists.frrouting.org | ++--------------------------------+------------------------------+ +| *Technical Steering Committee* | tsc@lists.frrouting.org | ++--------------------------------+------------------------------+ + +The Development list is used to discuss and document general issues related to +project development and governance. The public `Slack`_ instance and weekly +technical meetings provide a higher bandwidth channel for discussions. The +results of such discussions are reflected in updates, as appropriate, to code +(i.e., merges), `GitHub issues`_ tracked issues, and for governance or process +changes, updates to the Development list and either this file or information +posted at `FRR`_. + + +Bug Reports +=========== + +For information on reporting bugs, please see :ref:`bug-reports`. + +.. _frr: https://frrouting.org +.. _github: https://github.com/frrouting/frr/ +.. _github issues: https://github.com/frrouting/frr/issues +.. _slack: https://frrouting.org/community diff --git a/doc/user/packet-dumps.rst b/doc/user/packet-dumps.rst new file mode 100644 index 0000000..6120c3b --- /dev/null +++ b/doc/user/packet-dumps.rst @@ -0,0 +1,221 @@ +.. _packet-binary-dump-format: + +Packet Binary Dump Format +========================= + +FRR can dump routing protocol packets into a file with a binary format. + +It seems to be better that we share the MRT's header format for +backward compatibility with MRT's dump logs. We should also define the +binary format excluding the header, because we must support both IP +v4 and v6 addresses as socket addresses and / or routing entries. + +In the last meeting, we discussed to have a version field in the +header. But Masaki told us that we can define new 'type' value rather +than having a 'version' field, and it seems to be better because we +don't need to change header format. + +Here is the common header format. This is same as that of MRT.:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Subtype | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +If 'type' is PROTOCOL_BGP4MP_ET, the common header format will +contain an additional microsecond field (RFC6396 2011).:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Subtype | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Microsecond | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_STATE_CHANGE, and +Address Family == IP (version 4):: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source AS number | Destination AS number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | Address Family | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Old State | New State | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Where State is the value defined in RFC1771. + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_STATE_CHANGE, +and Address Family == IP version 6:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source AS number | Destination AS number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | Address Family | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Old State | New State | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_MESSAGE, +and Address Family == IP (version 4):: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source AS number | Destination AS number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | Address Family | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BGP Message Packet | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Where BGP Message Packet is the whole contents of the +BGP4 message including header portion. + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_MESSAGE, +and Address Family == IP version 6:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source AS number | Destination AS number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | Address Family | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Destination IP address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BGP Message Packet | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_ENTRY, +and Address Family == IP (version 4):: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | View # | Status | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time Last Change | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address Family | SAFI | Next-Hop-Len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Hop Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prefix Length | Address Prefix [variable] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Attribute Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BGP Attribute [variable length] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +If 'type' is PROTOCOL_BGP4MP, 'subtype' is BGP4MP_ENTRY, +and Address Family == IP version 6:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | View # | Status | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Time Last Change | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address Family | SAFI | Next-Hop-Len | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Hop Address | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Hop Address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Hop Address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next Hop Address (Cont'd) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prefix Length | Address Prefix [variable] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Address Prefix (cont'd) [variable] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Attribute Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BGP Attribute [variable length] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +BGP4 Attribute must not contain MP_UNREACH_NLRI. If BGP Attribute has +MP_REACH_NLRI field, it must has zero length NLRI, e.g., MP_REACH_NLRI has only +Address Family, SAFI and next-hop values. + +If 'type' is PROTOCOL_BGP4MP and 'subtype' is BGP4MP_SNAPSHOT:: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | View # | File Name [variable] | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The file specified in "File Name" contains all routing entries, +which are in the format of ``subtype == BGP4MP_ENTRY``. + +:: + + Constants: + + /\* type value \*/ + #define MSG_PROTOCOL_BGP4MP 16 + #define MSG_PROTOCOL_BGP4MP_ET 17 + /\* subtype value \*/ + #define BGP4MP_STATE_CHANGE 0 + #define BGP4MP_MESSAGE 1 + #define BGP4MP_ENTRY 2 + #define BGP4MP_SNAPSHOT 3 diff --git a/doc/user/pathd.rst b/doc/user/pathd.rst new file mode 100644 index 0000000..2519ac4 --- /dev/null +++ b/doc/user/pathd.rst @@ -0,0 +1,625 @@ +.. _path: + +**** +PATH +**** + +:abbr:`PATH` is a daemon that handles the installation and deletion +of Segment Routing (SR) Policies. +Based on MPLS (This means that your OS of choice must support MPLS), +SR add a stack of MPLS labels to ingress packets so these +packets are egress through the desired path. + +.. image:: images/pathd_general.png + +The SR policies and Segment Lists can be configured either locally by means +of vtysh or centralized based on a SDN controller (ODL, Cisco, ...) +communicating using the PCEP protocol (:rfc:`5440`). + + +.. _starting-path: + +Configuration +============= + +Explicit Segment Lists +---------------------- + +This is the simplest way of configuration, no remote PCE is necessary. +In order to create a config that match the graphics used in this documentation, +we will create a segment list (SL) called SL1 with an element for each hop and +that element will be assigned a MPLS label. +Then the SL1 will be used in the policy ``example1``, please note also the +preference as in the case of multiple segment list it will be used with the +criteria of bigger number more preference. +Let see now the final configuration that match the graphics shown above. + + +.. code-block:: frr + + segment-routing + traffic-eng + segment-list SL1 + index 10 mpls label 16001 + index 20 mpls label 16002 + ! + policy color 1 endpoint 192.0.2.4 + name example1 + binding-sid 1111 + candidate-path preference 100 name CP1 explicit segment-list SL1 + + +Explicit Segment Lists and Traffic Engineering Database (TED) +------------------------------------------------------------- + +Sometimes is difficult to know the values of MPLS labels +(adjacency changes,...). +Based on the support of IS-IS or OSPF we can activate TED support what will +allow pathd to resolve MPLS based in different types of segments +(:rfc: `draft-ietf-spring-segment-routing-policy-07`). The supported types are +Type C (prefix and local interface), Type E (prefix and algorithm), +Type F (a pair of IP's). +So the configuration would change to this + +.. code-block:: frr + + segment-routing + traffic-eng + mpls-te on + mpls-te import ospfv2 + segment-list SL1 + index 10 nai prefix 10.1.2.1/32 iface 1 + index 20 nai adjacency 10.1.20.1 10.1.20.2 + ! + policy color 1 endpoint 192.0.2.4 + name example1 + binding-sid 1111 + candidate-path preference 100 name CP1 explicit segment-list SL1 + + +In this case no MPLS are provided but the pathd TED support will resolve the +configuration provided to corresponding MPLS labels. + +.. note:: + Please note the ``mpls-te`` configuration added that activate the TED + support and points to ``ospfv2`` so + the ospfv2 (:ref:`ospf-traffic-engineering`) daemon must be also + running and configure to export TED information. + +.. note:: + It would be the same for isis (:ref:`isis-traffic-engineering`) but in the + moment of writting it's not fully tested. + +Dynamic Segment Lists +--------------------- + +One of the useful options to configure is the creation of policies with +the dynamic option. In this case based on a given endpoint the SL will be +,first calculated, and then sended by means of PCEP protocol by the configured +PCE. + +.. code-block:: frr + + traffic-eng + ! + pcep + ! + pce PCE1 + address ip 192.0.2.10 + ! + pcc + peer PCE1 precedence 10 + ! + policy color 1 endpoint 192.0.2.4 + name example + binding-sid 1111 + candidate-path preference 100 name CP2 dynamic + +.. note:: + Please note the configuration for the remote pce which allows pathd to + connect to the given PCE and act as a PCC (PCEP Client) + +.. note:: + If the TED support feature is active, the data obtained from PCE will + be validated, so in a SL from PCEP/PCE the IP and MPLS will be checked + against local TED obtained and built from the igp configured in that + case. + +.. image:: images/pathd_config.png + +Pce Initiated +------------- + +We can step forward in the use of our controller not only by asking to +calculate paths to an endpoint but also to create the whole policies in the +controller and obtain those by means of the PCEP protocol. + + +.. code-block:: frr + + traffic-eng + ! + pcep + ! + pce PCE1 + address ip 192.0.2.10 + pce-initiated + ! + pce PCE2 + address ip 192.0.2.9 + pce-initiated + ! + pcc + peer PCE1 precedence 10 + peer PCE2 precedence 20 + ! + +.. note:: + Now there is no locally created policies in the config as they will + be obtain from the configured pce. + Please check command :clicmd:`show sr-te policy` in ``vtysh`` to see + the obtained policies. + +.. note:: + Another interesting command is :clicmd:`show mpls table` + to check the installed mpls configuration based in those obtained + policies. + +.. note:: + SR Policies could be a mix of local, remote obtained from PCE and + delegated to a PCE (but while testing Pce Initiated with Cisco PCE, + happens that controller sends PCE initiated delete commands to delete + the locally created configuration related to that PCE). + + +.. image:: images/pathd_initiated_multi.png + +Starting +======== + +Default configuration file for *pathd* is :file:`pathd.conf`. The typical +location of :file:`pathd.conf` is |INSTALL_PREFIX_ETC|/pathd.conf. + +If the user is using integrated config, then :file:`pathd.conf` need not be +present and the :file:`frr.conf` is read instead. + +.. program:: pathd + +:abbr:`PATH` supports all the common FRR daemon start options which are +documented elsewhere. + +PCEP Support +============ + +A pceplib is included in the frr source tree and build by default. + + +To start pathd with pcep support the extra parameter `-M pathd_pcep` should be +passed to the pathd daemon. + +An example of command line with pcep module could be this + +.. code-block:: frr + + pathd -u root -g root -f pathd.conf -z /tmp/zebra-demo1.sock --vty_socket=/var/run/demo1.vty -i /tmp/pathd-demo1.pid -M frr/modules/pathd_pcep.so --log file:/tmp/kk.txt + +Pathd Configuration +=================== + +Example: + +.. code-block:: frr + + debug pathd pcep basic + segment-routing + traffic-eng + mpls-te on + mpls-te import ospfv2 + segment-list SL1 + index 10 mpls label 16010 + index 20 mpls label 16030 + ! + segment-list SL2 + index 10 nai prefix 10.1.2.1/32 iface 1 + index 20 nai adjacency 10.1.20.1 10.1.20.2 + index 30 nai prefix 10.10.10.5/32 algorithm 0 + index 40 mpls label 18001 + ! + policy color 1 endpoint 192.0.2.1 + name default + binding-sid 4000 + candidate-path preference 100 name CP1 explicit segment-list SL1 + candidate-path preference 200 name CP2 dynamic + affinity include-any 0x000000FF + bandwidth 100000 + metric bound msd 16 required + metric te 10 + objective-function mcp required + ! + pcep + pce-config GROUP1 + source-address 192.0.2.1 + tcp-md5-auth secret + timer keep-alive 30 + ! + pce PCE1 + config GROUP1 + address ip 192.0.2.10 + ! + pce PCE2 + config GROUP1 + address ip 192.0.2.9 + ! + pcc + peer PCE1 precedence 10 + peer PCE2 precedence 20 + ! + ! + ! + ! + + +.. _path-commands: + +Configuration Commands +---------------------- + +.. clicmd:: segment-routing + + Configure segment routing. + +.. clicmd:: traffic-eng + + Configure segment routing traffic engineering. + +.. clicmd:: mpls-te + + Activate/Deactivate use of internal Traffic Engineering Database + +.. clicmd:: mpls-te import + + Load data from the selected igp + +.. clicmd:: segment-list NAME + + Delete or start a segment list definition. + +.. clicmd:: index INDEX mpls label LABEL +.. clicmd:: index INDEX nai adjacency A.B.C.D A.B.C.D +.. clicmd:: index INDEX nai prefix A.B.C.D/M algorithm <0|1> +.. clicmd:: index INDEX nai prefix A.B.C.D/M iface (0-65535) + + Delete or specify a segment in a segment list definition. + + +.. clicmd:: policy color COLOR endpoint ENDPOINT + + Delete or start a policy definition. + + +.. clicmd:: name NAME + + Specify the policy name. + + +.. clicmd:: binding-sid LABEL + + Specify the policy SID. + + +.. clicmd:: candidate-path preference PREFERENCE name NAME explicit segment-list SEGMENT-LIST-NAME + + Delete or define an explicit candidate path. + + +.. clicmd:: candidate-path preference PREFERENCE name NAME dynamic + + Delete or start a dynamic candidate path definition. + + +.. clicmd:: affinity BITPATTERN + + Delete or specify an affinity constraint for a dynamic candidate path. + + +.. clicmd:: bandwidth BANDWIDTH [required] + + Delete or specify a bandwidth constraint for a dynamic candidate path. + + +.. clicmd:: metric [bound] METRIC VALUE [required] [computed] + + Delete or specify a metric constraint for a dynamic candidate path. + + The possible metrics are: + - igp: IGP metric + - te: TE metric + - hc: Hop Counts + - abc: Aggregate bandwidth consumption + - mll: Load of the most loaded link + - igp: Cumulative IGP cost + - cte: Cumulative TE cost + - igp: P2MP IGP metric + - pte: P2MP TE metric + - phc: P2MP hop count metric + - msd: Segment-ID (SID) Depth + - pd: Path Delay metric + - pdv: Path Delay Variation metric + - pl: Path Loss metric + - ppd: P2MP Path Delay metric + - pdv: P2MP Path Delay variation metric + - ppl: P2MP Path Loss metric + - nap: Number of adaptations on a path + - nlp: Number of layers on a path + - dc: Domain Count metric + - bnc: Border Node Count metric + + +.. clicmd:: objective-function OBJFUN1 [required] + + Delete or specify a PCEP objective function constraint for a dynamic + candidate path. + + The possible functions are: + - mcp: Minimum Cost Path [RFC5541] + - mlp: Minimum Load Path [RFC5541] + - mbp: Maximum residual Bandwidth Path [RFC5541] + - mbc: Minimize aggregate Bandwidth Consumption [RFC5541] + - mll: Minimize the Load of the most loaded Link [RFC5541] + - mcc: Minimize the Cumulative Cost of a set of paths [RFC5541] + - spt: Shortest Path Tree [RFC8306] + - mct: Minimum Cost Tree [RFC8306] + - mplp: Minimum Packet Loss Path [RFC8233] + - mup: Maximum Under-Utilized Path [RFC8233] + - mrup: Maximum Reserved Under-Utilized Path [RFC8233] + - mtd: Minimize the number of Transit Domains [RFC8685] + - mbn: Minimize the number of Border Nodes [RFC8685] + - mctd: Minimize the number of Common Transit Domains [RFC8685] + - msl: Minimize the number of Shared Links [RFC8800] + - mss: Minimize the number of Shared SRLGs [RFC8800] + - msn: Minimize the number of Shared Nodes [RFC8800] + + +.. clicmd:: debug pathd pcep [basic|path|message|pceplib] + + Enable or disable debugging for the pcep module: + + - basic: Enable basic PCEP logging + - path: Log the path structures + - message: Log the PCEP messages + - pceplib: Enable pceplib logging + + +.. clicmd:: pcep + + Configure PCEP support. + + +.. clicmd:: pce-config NAME + + Define a shared PCE configuration that can be used in multiple PCE + declarations. + + +.. clicmd:: pce NAME + + Define or delete a PCE definition. + + +.. clicmd:: config WORD + + Select a shared configuration. If not defined, the default + configuration will be used. + + +.. clicmd:: address [port (1024-65535)] + + Define the address and port of the PCE. + + If not specified, the port is the standard PCEP port 4189. + + This should be specified in the PCC peer definition. + + +.. clicmd:: source-address [ip A.B.C.D | ipv6 X:X::X:X] [port PORT] + + Define the address and/or port of the PCC as seen by the PCE. + This can be used in a configuration group or a PCC peer declaration. + + If not specified, the source address will be the router identifier selected + by zebra, and the port will be the standard PCEP port 4189. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. clicmd:: tcp-md5-auth WORD + + Enable TCP MD5 security with the given secret. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. clicmd:: sr-draft07 + + Specify if a PCE only support segment routing draft 7, this flag will limit + the PCC behavior to this draft. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. clicmd:: pce-initiated + + Specify if PCE-initiated LSP should be allowed for this PCE. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. clicmd:: timer [keep-alive (1-63)] [min-peer-keep-alive (1-255)] [max-peer-keep-alive (1-255)] [dead-timer (4-255)] [min-peer-dead-timer (4-255)] [max-peer-dead-timer (4-255)] [pcep-request (1-120)] [session-timeout-interval (1-120)] [delegation-timeout (1-60)] + + Specify the PCEP timers. + + This can be specified in either the PCC peer definition or in a + configuration group. + + +.. clicmd:: pcc + + Disable or start the definition of a PCC. + + +.. clicmd:: msd (1-32) + + Specify the maximum SID depth in a PCC definition. + +.. clicmd:: no msd [(1-32)] + + Default the maximum SID depth to 4. + +.. clicmd:: peer WORD [precedence (1-255)] + + Specify a peer and its precedence in a PCC definition. + +Debugging +--------- + +.. clicmd:: debug pathd policy + + Enable or disable Pathd policy information. + +Introspection Commands +---------------------- + +.. clicmd:: show sr-te policy [detail] + + Display the segment routing policies. + +.. code-block:: frr + + router# show sr-te policy + + Endpoint Color Name BSID Status + ------------------------------------------ + 192.0.2.1 1 default 4000 Active + + +.. code-block:: frr + + router# show sr-te policy detail + + Endpoint: 192.0.2.1 Color: 1 Name: LOW_DELAY BSID: 4000 Status: Active + Preference: 100 Name: cand1 Type: explicit Segment-List: sl1 Protocol-Origin: Local + * Preference: 200 Name: cand1 Type: dynamic Segment-List: 32453452 Protocol-Origin: PCEP + +The asterisk (*) marks the best, e.g. active, candidate path. Note that for segment-lists which are +retrieved via PCEP a random number based name is generated. + + +.. clicmd:: show sr-te pcep counters + + Display the counters from pceplib. + + +.. clicmd:: show sr-te pcep pce-config [NAME] + + Display a shared configuration. if no name is specified, the default + configuration will be displayed. + + +.. clicmd:: show sr-te pcep pcc + + Display PCC information. + + +.. clicmd:: show sr-te pcep session [NAME] [json] + + Display the information of a PCEP session, if not name is specified all the + sessions will be displayed. + + +Utility Commands +---------------- + +.. clicmd:: clear sr-te pcep session [NAME] + + Reset the pcep session by disconnecting from the PCE and performing the + normal reconnection process. No configuration is changed. + + +Usage with BGP route-maps +========================= + +It is possible to steer traffic 'into' a segment routing policy for routes +learned through BGP using route-maps: + +.. code-block:: frr + + route-map SET_SR_POLICY permit 10 + set sr-te color 1 + ! + router bgp 1 + bgp router-id 192.0.2.2 + neighbor 192.0.2.1 remote-as 1 + neighbor 192.0.2.1 update-source lo + ! + address-family ipv4 unicast + neighbor 192.0.2.1 next-hop-self + neighbor 192.0.2.1 route-map SET_SR_POLICY in + redistribute static + exit-address-family + ! + ! + +In this case, the SR Policy with color `1` and endpoint `192.0.2.1` is selected. + + +Sample configuration +==================== + +.. code-block:: frr + + ! Default pathd configuration sample + ! + password frr + log stdout + + segment-routing + traffic-eng + segment-list test1 + index 10 mpls label 123 + index 20 mpls label 456 + ! + segment-list test2 + index 10 mpls label 321 + index 20 mpls label 654 + ! + policy color 1 endpoint 192.0.2.1 + name one + binding-sid 100 + candidate-path preference 100 name test1 explicit segment-list test1 + candidate-path preference 200 name test2 explicit segment-list test2 + ! + policy color 2 endpoint 192.0.2.2 + name two + binding-sid 101 + candidate-path preference 100 name def explicit segment-list test2 + candidate-path preference 200 name dyn dynamic + bandwidth 12345 + metric bound abc 16 required + metric te 10 + ! + ! + pcep + pcc-peer PCE1 + address ip 127.0.0.1 + sr-draft07 + ! + pcc + peer PCE1 + ! + ! + ! + diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst new file mode 100644 index 0000000..6ea153c --- /dev/null +++ b/doc/user/pbr.rst @@ -0,0 +1,422 @@ +.. _pbr: + +*** +PBR +*** + +:abbr:`PBR` is Policy Based Routing, which means forwarding based on +packet fields other than solely the destination IP address. +This implementation currently works only on Linux. Note that some +functionality (VLAN matching, packet mangling) is not supported by +the default Linux kernel dataplane provider. + +.. _starting-pbr: + +Starting PBR +============ + +.. include:: config-include.rst + +.. program:: pbrd + +:abbr:`PBR` supports all the common FRR daemon start options, which are +documented elsewhere. + +.. _nexthop-groups: + +PBR Nexthop Groups +================== + +A nexthop group is a list of ECMP nexthops used to forward packets +when a pbr-map is matched. +For details on specifying a nexthop group in the CLI, see +the nexthop-groups section. + +Showing Nexthop Group Information +--------------------------------- + +.. clicmd:: show pbr nexthop-groups [NAME] [json] + + Display information on a PBR nexthop-group. If ``NAME`` is omitted, all + nexthop groups are shown. Setting ``json`` will provide the same + information in an array of objects that adhere to the schema below: + + +-----------+----------------------------+---------+ + | Key | Description | Type | + +===========+============================+=========+ + | id | Unique ID | Integer | + +-----------+----------------------------+---------+ + | name | Name of this group | String | + +-----------+----------------------------+---------+ + | valid | Is this group well-formed? | Boolean | + +-----------+----------------------------+---------+ + | installed | ... and is it installed? | Boolean | + +-----------+----------------------------+---------+ + | nexthops | Nexthops within this group | Array | + +-----------+----------------------------+---------+ + + Each element within ``nexthops`` describes a single target within this + group, and its structure is described by the JSON below: + + +---------+------------------------------+---------+ + | Key | Description | Type | + +=========+==============================+=========+ + | nexthop | Name of this nexthop | String | + +---------+------------------------------+---------+ + | valid | Is this nexthop well-formed? | Boolean | + +---------+------------------------------+---------+ + +.. _pbr-maps: + +PBR Maps +======== + +PBR maps are a way to specify a set of rules that are applied to +packets received on individual interfaces. +If a received packet matches a rule, the rule's nexthop-group or +nexthop is used to forward it; any other actions +specified in the rule are also applied to the packet. + +.. clicmd:: pbr-map NAME seq (1-700) + + Create a pbr-map rule with map NAME and specified sequence number. + This command puts the CLI into a new submode for pbr-map rule specification. + To exit this submode, type ``exit`` or ``end``. + +.. clicmd:: match src-ip PREFIX + + Match the packet's source IP address. + + This command accepts both v4 and v6 prefixes. + +.. clicmd:: match dst-ip PREFIX + + Match the packet's destination IP address. + + This command accepts both v4 and v6 prefixes. + +.. clicmd:: match src-port (1-65535) + + Match the packet's UDP or TCP source port. + +.. clicmd:: match dst-port (1-65535) + + Match the packet's UDP or TCP destination port. + +.. clicmd:: match ip-protocol PROTOCOL + + Match the packet's IP protocol. + + Protocol names are queried from the protocols database (``/etc/protocols``; + see ``man 5 protocols`` and ``man 3 getprotobyname``). + +.. clicmd:: match mark (1-4294967295) + + Match the packet's meta-information mark. + The mark value is attached to the packet by the kernel/dataplane and + is platform-specific. + Currently, this field is supported only on linux and corresponds to + the underlying `ip rule .... fwmark XXXX` command. + +.. clicmd:: match dscp (DSCP|0-63) + + Match the packet's IP differentiated services code point (DSCP). + The specified DSCP may also be a standard name for a + differentiated service code point such as ``cs0`` or ``af11``. + + You may only specify one dscp per route map rule; to match on multiple + dscp values you will need to create several rules, one for each value. + +.. clicmd:: match ecn (0-3) + + Match the packet's IP explicit congestion notification (ECN) field. + +.. clicmd:: match pcp (0-7) + + Match the packet's 802.1Q Priority Code Point. + Zero is the default (nominally, "best effort"). + The Linux kernel dataplane provider does not currently support + matching PCPs, + so this field will be ignored unless other dataplane providers are used. + +.. clicmd:: match vlan (1-4094) + + Match the packet's VLAN (802.1Q) identifier. + Note that VLAN IDs 0 and 4095 are reserved. + The Linux kernel dataplane provider does not currently support + VLAN-matching facilities, + so this field will be ignored unless other dataplane providers are used. + +.. clicmd:: match vlan (tagged|untagged|untagged-or-zero) + + Match packets according to whether or not they have a VLAN tag. + Use `untagged-or-zero` to also match packets with either no VLAN tag + or with the reserved VLAN ID of 0 (indicating an untagged frame that + includes other 802.1Q fields). + The Linux kernel dataplane provider does not currently support + VLAN-matching facilities, + so this field will be ignored unless other dataplane providers are used. + +.. clicmd:: set nexthop-group NAME + + Action: + forward the packet using nexthop-group NAME. + +.. clicmd:: set nexthop [A.B.C.D|X:X::X:XX|blackhole] [interface] [nexthop-vrf NAME] + + Action: + forward the packet using the specified single nexthop. + If `blackhole`, packets will be sent to a blackhole route and dropped. + +.. clicmd:: set vrf unchanged|NAME + + Action: + If set to ``unchanged``, the rule will use the vrf table the interface + is in as its lookup. + If set to NAME, the rule will use that vrf table as its lookup. + + Not supported with NETNS VRF backend. + +.. clicmd:: set queue-id (1-65535) + + Action: + set the egress port queue identifier. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set pcp (0-7) + + Action: + set the 802.1Q priority code point (PCP). + A PCP of zero is the default (nominally, "best effort"). + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set vlan (1-4094) + + Action: + set the VLAN tag. Identifiers 0 and 4095 are reserved. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: strip vlan + + Action: + strip inner vlan tags. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + It is invalid to specify both a `strip` and `set vlan` action. + +.. clicmd:: set src-ip [A.B.C.D/M|X:X::X:X/M] + + Action: + Set the source IP address of matched packets, possibly using a mask `M`. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set dst-ip [A.B.C.D/M|X:X::X:X/M] + + Action: + set the destination IP address of matched packets, possibly using a mask + `M`. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set src-port (1-65535) + + Action: + set the source port of matched packets. Note that this action only makes + sense with layer 4 protocols that use ports, such as TCP, UDP, and SCTP. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set dst-port (1-65535) + + Action: + set the destination port of matched packets. Note that this action only + makes sense with layer 4 protocols that use ports, such as TCP, UDP, and + SCTP. + The Linux Kernel dataplane provider does not currently support + packet mangling, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set dscp DSCP + + Action: + set the differentiated services code point (DSCP) of matched packets. + The Linux Kernel dataplane provider does not currently support + this action, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: set ecn (0-3) + + Action: + set the explicit congestion notification (ECN) of matched packets. + The Linux Kernel dataplane provider does not currently support + this action, + so this field will be ignored unless another dataplane provider is used. + +.. clicmd:: show pbr map [NAME] [detail] [json] + + Display pbr maps either all or by ``NAME``. If ``detail`` is set, it will + give information about each rule's unique internal ID and some extra + debugging information about install state for the nexthop/nexthop group. + Setting ``json`` will provide the same information in an array of objects + that adher to the schema below: + + +----------+--------------------------------+---------+ + | Key | Description | Type | + +==========+================================+=========+ + | name | Map name | String | + +----------+--------------------------------+---------+ + | valid | Is the map well-formed? | Boolean | + +----------+--------------------------------+---------+ + | policies | Rules to match packets against | Array | + +----------+--------------------------------+---------+ + + Each element of the ``policies`` array is composed of a set of objects + representing the policies associated with this map. Each policy is + described below (not all fields are required): + + +-----------------+-------------------------------------------+---------+ + | Key | Description | Type | + +=================+===========================================+=========+ + | id | Unique ID | Integer | + +-----------------+-------------------------------------------+---------+ + | sequenceNumber | Order of this policy within the map | Integer | + +-----------------+-------------------------------------------+---------+ + | ruleNumber | Rule number to install into | Integer | + +-----------------+-------------------------------------------+---------+ + | vrfUnchanged | Use interface's VRF | Boolean | + +-----------------+-------------------------------------------+---------+ + | installed | Is this policy installed? | Boolean | + +-----------------+-------------------------------------------+---------+ + | installedReason | Why (or why not?) | String | + +-----------------+-------------------------------------------+---------+ + | matchSrc | Match packets with this source address | String | + +-----------------+-------------------------------------------+---------+ + | matchDst | ... or with this destination address | String | + +-----------------+-------------------------------------------+---------+ + | matchMark | ... or with this marker | Integer | + +-----------------+-------------------------------------------+---------+ + | vrfName | Associated VRF (if relevant) | String | + +-----------------+-------------------------------------------+---------+ + | nexthopGroup | This policy's nexthop group (if relevant) | Object | + +-----------------+-------------------------------------------+---------+ + + Finally, the ``nexthopGroup`` object above contains information FRR + knows about the configured nexthop for this policy: + + +---------------------+--------------------------------------+---------+ + | Key | Description | Type | + +=====================+======================================+=========+ + | tableId | Nexthop table ID | Integer | + +---------------------+--------------------------------------+---------+ + | name | Name of the nexthop group | String | + +---------------------+--------------------------------------+---------+ + | installed | Is this nexthop group installed? | Boolean | + +---------------------+--------------------------------------+---------+ + | installedInternally | Does FRR think NHG is installed? | Integer | + +---------------------+--------------------------------------+---------+ + + +.. index:: + pair: policy; PBR + +.. _pbr-policy: + +PBR Policy +========== + +After you have specified a PBR map, in order for it to be enabled, it must +be applied to an interface. This policy application to an interface +causes the policy to be installed into the kernel. + +.. clicmd:: pbr-policy NAME + + This command is available under interface sub-mode. + It enables the PBR map NAME on the interface. + +.. note:: + This command will not dynamically create PBR maps on sub-interfaces + (i.e. vlans), even if one is on the master. + Each sub-interface must have the PBR map enabled explicitly. + +.. clicmd:: show pbr interface [NAME] [json] + + Enumerates all interfaces which ``pbrd`` is keeping track of. Passing + ``json`` will return an array of interfaces; each returned interface will + adhere to the JSON schema below: + + +--------+----------------------------+---------+ + | Key | Description | Type | + +========+============================+=========+ + | name | Interface name | String | + +--------+----------------------------+---------+ + | index | Device Index | Integer | + +--------+----------------------------+---------+ + | policy | PBR map for this interface | String | + +--------+----------------------------+---------+ + | valid | Is the map well-formed? | Boolean | + +--------+----------------------------+---------+ + +.. clicmd:: pbr table range (10000-4294966272) (10000-4294966272) + + Set or unset the range used to assign numeric table IDs to new + nexthop-group tables. Existing tables will not be modified to fit in this + range, so this range should be configured before adding nexthop groups. + + .. seealso:: :ref:`pbr-details` + + +.. _pbr-debugs: + +PBR Debugs +=========== + +.. clicmd:: debug pbr events|map|nht|zebra + + Debug pbr in pbrd daemon. You must specify what types of debugs to turn on. + +.. _pbr-details: + +PBR Details +=========== + +Internally, a PBR map is translated into two separate constructs in the +Linux kernel. + + +The PBR map creates an `ip rule ...` that is inserted into the Linux +kernel that points to a table to use for forwarding once the rule matches. + + +The creation of a nexthop or nexthop-group is translated to a +table with a default route having the specified nexthop(s). + + +Sample configuration +==================== + +.. code-block:: frr + + nexthop-group TEST + nexthop 4.5.6.7 + nexthop 5.6.7.8 + ! + pbr-map BLUE seq 100 + match dst-ip 9.9.9.0/24 + match src-ip 10.10.10.0/24 + set nexthop-group TEST + ! + int swp1 + pbr-policy BLUE + + diff --git a/doc/user/pim.rst b/doc/user/pim.rst new file mode 100644 index 0000000..b19bb9d --- /dev/null +++ b/doc/user/pim.rst @@ -0,0 +1,753 @@ +.. _pim: + +*** +PIM +*** + +PIM -- Protocol Independent Multicast + +*pimd* supports pim-sm as well as igmp v2 and v3. pim is +vrf aware and can work within the context of vrf's in order to +do S,G mrouting. Additionally PIM can be used in the EVPN underlay +network for optimizing forwarding of overlay BUM traffic. + +.. note:: + + On Linux for PIM-SM operation you *must* have kernel version 4.19 or greater. + To use PIM for EVPN BUM forwarding, kernels 5.0 or greater are required. + OpenBSD has no multicast support and FreeBSD, and NetBSD only + have support for SSM. + +.. _starting-and-stopping-pimd: + +Starting and Stopping pimd +========================== + +.. include:: config-include.rst + +If starting daemons by hand then please note, *pimd* requires zebra for proper +operation. Additionally *pimd* depends on routing properly setup and working in +the network that it is working on. + +:: + + # zebra -d + # pimd -d + + +Please note that *zebra* must be invoked before *pimd*. + +To stop *pimd* please use:: + + kill `cat /var/run/frr/pimd.pid` + +Certain signals have special meanings to *pimd*. + ++---------+---------------------------------------------------------------------+ +| Signal | Meaning | ++=========+=====================================================================+ +| SIGUSR1 | Rotate the *pimd* logfile | ++---------+---------------------------------------------------------------------+ +| SIGINT | *pimd* sweeps all installed PIM mroutes then terminates gracefully. | +| SIGTERM | | ++---------+---------------------------------------------------------------------+ + +*pimd* invocation options. Common options that can be specified +(:ref:`common-invocation-options`). + +.. clicmd:: ip pim rp A.B.C.D A.B.C.D/M + + In order to use pim, it is necessary to configure a RP for join messages to + be sent to. Currently the only methodology to do this is via static rp + commands. All routers in the pim network must agree on these values. The + first ip address is the RP's address and the second value is the matching + prefix of group ranges covered. This command is vrf aware, to configure for + a vrf, enter the vrf submode. + +.. clicmd:: ip pim rp keep-alive-timer (1-65535) + + Modify the time out value for a S,G flow from 1-65535 seconds at RP. + The normal keepalive period for the KAT(S,G) defaults to 210 seconds. + However, at the RP, the keepalive period must be at least the + Register_Suppression_Time, or the RP may time out the (S,G) state + before the next Null-Register arrives. Thus, the KAT(S,G) is set to + max(Keepalive_Period, RP_Keepalive_Period) when a Register-Stop is sent. + If choosing a value below 31 seconds be aware that some hardware platforms + cannot see data flowing in better than 30 second chunks. This command is + vrf aware, to configure for a vrf, enter the vrf submode. + +.. clicmd:: ip pim register-accept-list PLIST + + When pim receives a register packet the source of the packet will be compared + to the prefix-list specified, PLIST, and if a permit is received normal + processing continues. If a deny is returned for the source address of the + register packet a register stop message is sent to the source. + +.. clicmd:: ip pim spt-switchover infinity-and-beyond [prefix-list PLIST] + + On the last hop router if it is desired to not switch over to the SPT tree + configure this command. Optional parameter prefix-list can be use to control + which groups to switch or not switch. If a group is PERMIT as per the + PLIST, then the SPT switchover does not happen for it and if it is DENY, + then the SPT switchover happens. + This command is vrf aware, to configure for a vrf, + enter the vrf submode. + +.. clicmd:: ip pim ecmp + + If pim has the a choice of ECMP nexthops for a particular RPF, pim will + cause S,G flows to be spread out amongst the nexthops. If this command is + not specified then the first nexthop found will be used. This command is vrf + aware, to configure for a vrf, enter the vrf submode. + +.. clicmd:: ip pim ecmp rebalance + + If pim is using ECMP and an interface goes down, cause pim to rebalance all + S,G flows across the remaining nexthops. If this command is not configured + pim only modifies those S,G flows that were using the interface that went + down. This command is vrf aware, to configure for a vrf, enter the vrf + submode. + +.. clicmd:: ip pim join-prune-interval (1-65535) + + Modify the join/prune interval that pim uses to the new value. Time is + specified in seconds. This command is vrf aware, to configure for a vrf, + enter the vrf submode. The default time is 60 seconds. If you enter + a value smaller than 60 seconds be aware that this can and will affect + convergence at scale. + +.. clicmd:: ip pim keep-alive-timer (1-65535) + + Modify the time out value for a S,G flow from 1-65535 seconds. If choosing + a value below 31 seconds be aware that some hardware platforms cannot see data + flowing in better than 30 second chunks. This command is vrf aware, to + configure for a vrf, enter the vrf submode. + +.. clicmd:: ip pim packets (1-255) + + When processing packets from a neighbor process the number of packets + incoming at one time before moving on to the next task. The default value is + 3 packets. This command is only useful at scale when you can possibly have + a large number of pim control packets flowing. This command is vrf aware, to + configure for a vrf, enter the vrf submode. + +.. clicmd:: ip pim register-suppress-time (1-65535) + + Modify the time that pim will register suppress a FHR will send register + notifications to the kernel. This command is vrf aware, to configure for a + vrf, enter the vrf submode. + +.. clicmd:: ip pim send-v6-secondary + + When sending pim hello packets tell pim to send any v6 secondary addresses + on the interface. This information is used to allow pim to use v6 nexthops + in it's decision for RPF lookup. This command is vrf aware, to configure for + a vrf, enter the vrf submode. + +.. clicmd:: ip pim ssm prefix-list WORD + + Specify a range of group addresses via a prefix-list that forces pim to + never do SM over. This command is vrf aware, to configure for a vrf, enter + the vrf submode. + +.. clicmd:: ip multicast rpf-lookup-mode WORD + + Modify how PIM does RPF lookups in the zebra routing table. You can use + these choices: + + longer-prefix + Lookup the RPF in both tables using the longer prefix as a match + + lower-distance + Lookup the RPF in both tables using the lower distance as a match + + mrib-only + Lookup in the Multicast RIB only + + mrib-then-urib + Lookup in the Multicast RIB then the Unicast Rib, returning first found. + This is the default value for lookup if this command is not entered + + urib-only + Lookup in the Unicast Rib only. + +.. clicmd:: ip igmp generate-query-once [version (2-3)] + + Generate IGMP query (v2/v3) on user requirement. This will not depend on + the existing IGMP general query timer.If no version is provided in the cli, + the default will be the igmp version enabled on that interface. + +.. clicmd:: ip igmp watermark-warn (1-65535) + + Configure watermark warning generation for an igmp group limit. Generates + warning once the configured group limit is reached while adding new groups. + 'no' form of the command disables the warning generation. This command is + vrf aware. To configure per vrf, enter vrf submode. + +.. _pim-interface-configuration: + +PIM Interface Configuration +=========================== + +PIM interface commands allow you to configure an interface as either a Receiver +or a interface that you would like to form pim neighbors on. If the interface +is in a vrf, enter the interface command with the vrf keyword at the end. + +.. clicmd:: ip pim active-active + + Turn on pim active-active configuration for a Vxlan interface. This + command will not do anything if you do not have the underlying ability + of a mlag implementation. + +.. clicmd:: ip pim bsm + + Tell pim that we would like to use this interface to process bootstrap + messages. This is enabled by default. 'no' form of this command is used to + restrict bsm messages on this interface. + +.. clicmd:: ip pim unicast-bsm + + Tell pim that we would like to allow interface to process unicast bootstrap + messages. This is enabled by default. 'no' form of this command is used to + restrict processing of unicast bsm messages on this interface. + +.. clicmd:: ip pim drpriority (0-4294967295) + + Set the DR Priority for the interface. This command is useful to allow the + user to influence what node becomes the DR for a lan segment. + +.. clicmd:: ip pim hello (1-65535) (1-65535) + + Set the pim hello and hold interval for a interface. + +.. clicmd:: ip pim + + Tell pim that we would like to use this interface to form pim neighbors + over. Please note that this command does not enable the reception of IGMP + reports on the interface. Refer to the next `ip igmp` command for IGMP + management. + +.. clicmd:: ip pim use-source A.B.C.D + + If you have multiple addresses configured on a particular interface + and would like pim to use a specific source address associated with + that interface. + +.. clicmd:: ip pim passive + + Disable sending and receiving pim control packets on the interface. + +.. clicmd:: ip igmp + + Tell pim to receive IGMP reports and Query on this interface. The default + version is v3. This command is useful on a LHR. + +.. clicmd:: ip igmp join A.B.C.D [A.B.C.D] + + Join multicast group or source-group on an interface. + +.. clicmd:: ip igmp query-interval (1-65535) + + Set the IGMP query interval that PIM will use. + +.. clicmd:: ip igmp query-max-response-time (1-65535) + + Set the IGMP query response timeout value. If an report is not returned in + the specified time we will assume the S,G or \*,G has timed out. + +.. clicmd:: ip igmp version (2-3) + + Set the IGMP version used on this interface. The default value is 3. + +.. clicmd:: ip multicast boundary oil WORD + + Set a pim multicast boundary, based upon the WORD prefix-list. If a pim join + or IGMP report is received on this interface and the Group is denied by the + prefix-list, PIM will ignore the join or report. + +.. clicmd:: ip igmp last-member-query-count (1-255) + + Set the IGMP last member query count. The default value is 2. 'no' form of + this command is used to to configure back to the default value. + +.. clicmd:: ip igmp last-member-query-interval (1-65535) + + Set the IGMP last member query interval in deciseconds. The default value is + 10 deciseconds. 'no' form of this command is used to to configure back to the + default value. + +.. clicmd:: ip mroute INTERFACE A.B.C.D [A.B.C.D] + + Set a static multicast route for a traffic coming on the current interface to + be forwarded on the given interface if the traffic matches the group address + and optionally the source address. + + +.. seealso:: + + :ref:`bfd-pim-peer-config` + + +.. _pim-multicast-rib: + +PIM Multicast RIB +================= + +In order to influence Multicast RPF lookup, it is possible to insert +into zebra routes for the Multicast RIB. These routes are only +used for RPF lookup and will not be used by zebra for insertion +into the kernel *or* for normal rib processing. As such it is +possible to create weird states with these commands. Use with +caution. Most of the time this will not be necessary. + +.. clicmd:: ip mroute A.B.C.D/M A.B.C.D (1-255) + + Insert into the Multicast Rib Route A.B.C.D/M with specified nexthop. The + distance can be specified as well if desired. + +.. clicmd:: ip mroute A.B.C.D/M INTERFACE (1-255) + + Insert into the Multicast Rib Route A.B.C.D/M using the specified INTERFACE. + The distance can be specified as well if desired. + +.. _msdp-configuration: + +Multicast Source Discovery Protocol (MSDP) Configuration +======================================================== + +MSDP can be setup in different ways: + +* MSDP meshed-group: where all peers are connected with each other creating + a fully meshed network. SAs (source active) messages are not forwarded in + this mode because the origin is able to send SAs to all members. + + This setup is commonly used with anycast. + +* MSDP peering: when there is one or more peers that are not fully meshed. SAs + may be forwarded depending on the result of filtering and RPF checks. + + This setup is commonly consistent with BGP peerings (for RPF checks). + +* MSDP default peer: there is only one peer and all SAs will be forwarded + there. + +.. note:: + + MSDP default peer and SA filtering is not implemented. + + +Commands available for MSDP: + +.. clicmd:: ip msdp timers (1-65535) (1-65535) [(1-65535)] + + Configure global MSDP timers. + + First value is the keep-alive interval. This configures the interval in + seconds between keep-alive messages. The default value is 60 seconds. It + should be less than the remote hold time. + + Second value is the hold-time. This configures the interval in seconds before + closing a non responding connection. The default value is 75. This value + should be greater than the remote keep alive time. + + Third value is the connection retry interval and it is optional. This + configures the interval between connection attempts. The default value + is 30 seconds. + +.. clicmd:: ip msdp mesh-group WORD member A.B.C.D + + Create or update a mesh group to include the specified MSDP peer. + +.. clicmd:: ip msdp mesh-group WORD source A.B.C.D + + Create or update a mesh group to set the source address used to connect to + peers. + +.. clicmd:: ip msdp peer A.B.C.D source A.B.C.D + + Create a regular MSDP session with peer using the specified source address. + + +.. _show-pim-information: + +Show PIM Information +==================== + +All PIM show commands are vrf aware and typically allow you to insert a +specified vrf command if information is desired about a specific vrf. If no +vrf is specified then the default vrf is assumed. Finally the special keyword +'all' allows you to look at all vrfs for the command. Naming a vrf 'all' will +cause great confusion. + +.. clicmd:: show ip igmp interface + + Display IGMP interface information. + +.. clicmd:: show ip igmp [vrf NAME] join [json] + + Display IGMP static join information for a specific vrf. + +.. index:: show ip igmp [vrf NAME$vrf_name] groups [INTERFACE$ifname [GROUP$grp_str]] [detail] [json$json] +.. clicmd:: show ip igmp [vrf NAME$vrf_name] groups [INTERFACE$ifname [GROUP$grp_str]] [detail] [json$json] + + Display IGMP static join information for all the vrfs present. + +.. index:: show ip igmp vrf all groups [GROUP$grp_str] [detail$detail] [json$json] +.. clicmd:: show ip igmp vrf all groups [GROUP$grp_str] [detail$detail] [json$json] + + Display IGMP groups information. + +.. clicmd:: show ip igmp groups retransmissions + + Display IGMP group retransmission information. + +.. clicmd:: show ip igmp [vrf NAME] sources [json] + + Display IGMP sources information. + +.. clicmd:: show ip igmp sources retransmissions + + Display IGMP source retransmission information. + +.. clicmd:: show ip igmp statistics + + Display IGMP statistics information. + +.. clicmd:: show ip multicast + + Display various information about the interfaces used in this pim instance. + +.. clicmd:: show ip mroute [vrf NAME] [A.B.C.D [A.B.C.D]] [fill] [json] + + Display information about installed into the kernel S,G mroutes. If + one address is specified we assume it is the Group we are interested + in displaying data on. If the second address is specified then it is + Source Group. The keyword `fill` says to fill in all assumed data + for test/data gathering purposes. + +.. clicmd:: show ip mroute [vrf NAME] count [json] + + Display information about installed into the kernel S,G mroutes and in + addition display data about packet flow for the mroutes for a specific + vrf. + +.. clicmd:: show ip mroute vrf all count [json] + + Display information about installed into the kernel S,G mroutes and in + addition display data about packet flow for the mroutes for all vrfs. + +.. clicmd:: show ip mroute [vrf NAME] summary [json] + + Display total number of S,G mroutes and number of S,G mroutes installed + into the kernel for a specific vrf. + +.. clicmd:: show ip mroute vrf all summary [json] + + Display total number of S,G mroutes and number of S,G mroutes + installed into the kernel for all vrfs. + +.. clicmd:: show ip msdp mesh-group + + Display the configured mesh-groups, the local address associated with each + mesh-group, the peer members included in each mesh-group, and their status. + +.. clicmd:: show ip msdp peer + + Display information about the MSDP peers. That includes the peer address, + the local address used to establish the connection to the peer, the + connection status, and the number of active sources. + +.. clicmd:: show ip pim assert + + Display information about asserts in the PIM system for S,G mroutes. + This command does not show S,G Channel states that in a NOINFO state. + +.. clicmd:: show ip pim assert-internal + + Display internal assert state for S,G mroutes + +.. clicmd:: show ip pim assert-metric + + Display metric information about assert state for S,G mroutes + +.. clicmd:: show ip pim assert-winner-metric + + Display winner metric for assert state for S,G mroutes + +.. clicmd:: show ip pim group-type + + Display SSM group ranges. + +.. clicmd:: show ip pim interface + + Display information about interfaces PIM is using. + +.. clicmd:: show ip pim mlag [vrf NAME|all] interface [detail|WORD] [json] + + Display mlag interface information. + +.. clicmd:: show ip pim join + + Display information about PIM joins received. If one address is specified + then we assume it is the Group we are interested in displaying data on. + If the second address is specified then it is Source Group. + +.. clicmd:: show ip pim local-membership + + Display information about PIM interface local-membership. + +.. clicmd:: show ip pim mlag summary [json] + + Display mlag information state that PIM is keeping track of. + +.. clicmd:: show ip pim neighbor + + Display information about PIM neighbors. + +.. clicmd:: show ip pim nexthop + + Display information about pim nexthops that are being used. + +.. clicmd:: show ip pim nexthop-lookup + + Display information about a S,G pair and how the RPF would be chosen. This + is especially useful if there are ECMP's available from the RPF lookup. + +.. clicmd:: show ip pim [vrf NAME] rp-info [A.B.C.D/M] [json] + + Display information about RP's that are configured on this router. + + You can filter the output by specifying an arbitrary group range. + + .. code-block:: frr + + # show ip pim rp-info + RP address group/prefix-list OIF I am RP Source Group-Type + 192.168.10.123 225.0.0.0/24 eth2 yes Static ASM + 192.168.10.123 239.0.0.0/8 eth2 yes Static ASM + 192.168.10.123 239.4.0.0/24 eth2 yes Static SSM + + # show ip pim rp-info 239.4.0.0/25 + RP address group/prefix-list OIF I am RP Source Group-Type + 192.168.10.123 239.0.0.0/8 eth2 yes Static ASM + 192.168.10.123 239.4.0.0/24 eth2 yes Static SSM + +.. clicmd:: show ip pim rpf + + Display information about currently being used S,G's and their RPF lookup + information. Additionally display some statistics about what has been + happening on the router. + +.. clicmd:: show ip pim secondary + + Display information about an interface and all the secondary addresses + associated with it. + +.. clicmd:: show ip pim state + + Display information about known S,G's and incoming interface as well as the + OIL and how they were chosen. + +.. clicmd:: show ip pim [vrf NAME] upstream [A.B.C.D [A.B.C.D]] [json] + + Display upstream information about a S,G mroute. Allow the user to + specify sub Source and Groups that we are only interested in. + +.. clicmd:: show ip pim upstream-join-desired + + Display upstream information for S,G's and if we desire to + join the multicast tree + +.. clicmd:: show ip pim upstream-rpf + + Display upstream information for S,G's and the RPF data associated with them. + +.. clicmd:: show ip pim [vrf NAME] mlag upstream [A.B.C.D [A.B.C.D]] [json] + + Display upstream entries that are synced across MLAG switches. + Allow the user to specify sub Source and Groups address filters. + +.. clicmd:: show ip pim mlag summary + + Display PIM MLAG (multi-chassis link aggregation) session status and + control message statistics. + +.. clicmd:: show ip pim bsr + + Display current bsr, its uptime and last received bsm age. + +.. clicmd:: show ip pim bsrp-info + + Display group-to-rp mappings received from E-BSR. + +.. clicmd:: show ip pim bsm-database + + Display all fragments of stored bootstrap message in user readable format. + +.. clicmd:: mtrace A.B.C.D [A.B.C.D] + + Display multicast traceroute towards source, optionally for particular group. + +.. clicmd:: show ip multicast count [vrf NAME] [json] + + Display multicast data packets count per interface for a vrf. + +.. clicmd:: show ip multicast count vrf all [json] + + Display multicast data packets count per interface for all vrf. + + +.. seealso:: + + :ref:`multicast-rib-commands` + + +PIM Debug Commands +================== + +The debugging subsystem for PIM behaves in accordance with how FRR handles +debugging. You can specify debugging at the enable CLI mode as well as the +configure CLI mode. If you specify debug commands in the configuration cli +mode, the debug commands can be persistent across restarts of the FRR pimd if +the config was written out. + +.. clicmd:: debug igmp + + This turns on debugging for IGMP protocol activity. + +.. clicmd:: debug mtrace + + This turns on debugging for mtrace protocol activity. + +.. clicmd:: debug mroute + + This turns on debugging for PIM interaction with kernel MFC cache. + +.. clicmd:: debug pim events + + This turns on debugging for PIM system events. Especially timers. + +.. clicmd:: debug pim nht + + This turns on debugging for PIM nexthop tracking. It will display + information about RPF lookups and information about when a nexthop changes. + +.. clicmd:: debug pim nht detail + + This turns on debugging for PIM nexthop in detail. This is not enabled + by default. + +.. clicmd:: debug pim packet-dump + + This turns on an extraordinary amount of data. Each pim packet sent and + received is dumped for debugging purposes. This should be considered a + developer only command. + +.. clicmd:: debug pim packets + + This turns on information about packet generation for sending and about + packet handling from a received packet. + +.. clicmd:: debug pim trace + + This traces pim code and how it is running. + +.. clicmd:: debug pim bsm + + This turns on debugging for BSR message processing. + +.. clicmd:: debug pim zebra + + This gathers data about events from zebra that come up through the ZAPI. + +PIM Clear Commands +================== +Clear commands reset various variables. + +.. clicmd:: clear ip interfaces + + Reset interfaces. + +.. clicmd:: clear ip igmp interfaces + + Reset IGMP interfaces. + +.. clicmd:: clear ip mroute + + Reset multicast routes. + +.. clicmd:: clear ip mroute [vrf NAME] count + + When this command is issued, reset the counts of data shown for + packet count, byte count and wrong interface to 0 and start count + up from this spot. + +.. clicmd:: clear ip pim interfaces + + Reset PIM interfaces. + +.. clicmd:: clear ip pim oil + + Rescan PIM OIL (output interface list). + +.. clicmd:: clear ip pim [vrf NAME] bsr-data + + This command will clear the BSM scope data struct. This command also + removes the next hop tracking for the bsr and resets the upstreams + for the dynamically learnt RPs. + +PIM EVPN configuration +====================== +To use PIM in the underlay for overlay BUM forwarding associate a multicast +group with the L2 VNI. The actual configuration is based on your distribution. +Here is an ifupdown2 example:: + + auto vx-10100 + iface vx-10100 + vxlan-id 10100 + bridge-access 100 + vxlan-local-tunnelip 27.0.0.11 + vxlan-mcastgrp 239.1.1.100 + +.. note:: + + PIM will see the ``vxlan-mcastgrp`` configuration and auto configure state + to properly forward BUM traffic. + +PIM also needs to be configured in the underlay to allow the BUM MDT to be +setup. This is existing PIM configuration: + +- Enable pim on the underlay L3 interface via the "ip pim" command. +- Configure RPs for the BUM multicast group range. +- Ensure the PIM is enabled on the lo of the VTEPs and the RP. + + +Sample configuration +==================== + +.. code-block:: frr + + debug igmp + debug pim + debug pim zebra + + ! You may want to enable ssmpingd for troubleshooting + ! See http://www.venaas.no/multicast/ssmping/ + ! + ip ssmpingd 1.1.1.1 + ip ssmpingd 2.2.2.2 + + ! HINTS: + ! - Enable "ip pim ssm" on the interface directly attached to the + ! multicast source host (if this is the first-hop router) + ! - Enable "ip pim ssm" on pim-routers-facing interfaces + ! - Enable "ip igmp" on IGMPv3-hosts-facing interfaces + ! - In order to inject IGMPv3 local membership information in the + ! PIM protocol state, enable both "ip pim ssm" and "ip igmp" on + ! the same interface; otherwise PIM won't advertise + ! IGMPv3-learned membership to other PIM routers + + interface eth0 + ip pim ssm + ip igmp + diff --git a/doc/user/pimv6.rst b/doc/user/pimv6.rst new file mode 100644 index 0000000..b8567e4 --- /dev/null +++ b/doc/user/pimv6.rst @@ -0,0 +1,501 @@ +.. _pimv6: + +***** +PIMv6 +***** + +PIMv6 -- Protocol Independent Multicast for IPv6 + +*pim6d* supports pim-sm as well as MLD v1 and v2. PIMv6 is +vrf aware and can work within the context of vrf's in order to +do S,G mrouting. + +.. _starting-and-stopping-pim6d: + +Starting and Stopping pim6d +=========================== + +.. include:: config-include.rst + +If starting daemons by hand then please note, *pim6d* requires zebra for proper +operation. Additionally *pim6d* depends on routing properly setup and working in +the network that it is working on. + +:: + + # zebra -d + # pim6d -d + + +Please note that *zebra* must be invoked before *pim6d*. + +To stop *pim6d* please use:: + + kill `cat /var/run/frr/pim6d.pid` + +Certain signals have special meanings to *pim6d*. + ++---------+---------------------------------------------------------------------+ +| Signal | Meaning | ++=========+=====================================================================+ +| SIGUSR1 | Rotate the *pim6d* logfile | ++---------+---------------------------------------------------------------------+ +| SIGINT | *pim6d* sweeps all installed PIM mroutes then terminates gracefully.| +| SIGTERM | | ++---------+---------------------------------------------------------------------+ + +*pim6d* invocation options. Common options that can be specified +(:ref:`common-invocation-options`). + +.. clicmd:: ipv6 pim rp X:X::X:X Y:Y::Y:Y/M + + In order to use pimv6, it is necessary to configure a RP for join messages to + be sent to. Currently the only methodology to do this is via static rp + commands. All routers in the pimv6 network must agree on these values. The + first ipv6 address is the RP's address and the second value is the matching + prefix of group ranges covered. This command is vrf aware, to configure for + a vrf, enter the vrf submode. + +.. clicmd:: ipv6 pim rp X:X::X:X prefix-list WORD + + This CLI helps in configuring RP address for a range of groups specified + by the prefix-list. + +.. clicmd:: ipv6 pim rp keep-alive-timer (1-65535) + + Modify the time out value for a S,G flow from 1-65535 seconds at RP. + The normal keepalive period for the KAT(S,G) defaults to 210 seconds. + However, at the RP, the keepalive period must be at least the + Register_Suppression_Time, or the RP may time out the (S,G) state + before the next Null-Register arrives. Thus, the KAT(S,G) is set to + max(Keepalive_Period, RP_Keepalive_Period) when a Register-Stop is sent. + If choosing a value below 31 seconds be aware that some hardware platforms + cannot see data flowing in better than 30 second chunks. This command is + vrf aware, to configure for a vrf, enter the vrf submode. + +.. clicmd:: ipv6 pim spt-switchover infinity-and-beyond [prefix-list PLIST] + + On the last hop router if it is desired to not switch over to the SPT tree + configure this command. Optional parameter prefix-list can be use to control + which groups to switch or not switch. If a group is PERMIT as per the + PLIST, then the SPT switchover does not happen for it and if it is DENY, + then the SPT switchover happens. + This command is vrf aware, to configure for a vrf, + enter the vrf submode. + +.. clicmd:: ipv6 pim join-prune-interval (1-65535) + + Modify the join/prune interval that pim uses to the new value. Time is + specified in seconds. This command is vrf aware, to configure for a vrf, + enter the vrf submode. The default time is 60 seconds. If you enter + a value smaller than 60 seconds be aware that this can and will affect + convergence at scale. + +.. clicmd:: ipv6 pim keep-alive-timer (1-65535) + + Modify the time out value for a S,G flow from 1-65535 seconds. If choosing + a value below 31 seconds be aware that some hardware platforms cannot see data + flowing in better than 30 second chunks. This command is vrf aware, to + configure for a vrf, enter the vrf submode. + +.. clicmd:: ipv6 pim packets (1-255) + + When processing packets from a neighbor process the number of packets + incoming at one time before moving on to the next task. The default value is + 3 packets. This command is only useful at scale when you can possibly have + a large number of pim control packets flowing. This command is vrf aware, to + configure for a vrf, enter the vrf submode. + +.. clicmd:: ipv6 pim register-suppress-time (1-65535) + + Modify the time that pim will register suppress a FHR will send register + notifications to the kernel. This command is vrf aware, to configure for a + vrf, enter the vrf submode. + +.. clicmd:: ipv6 ssmpingd [X:X::X:X] + + Enable ipv6 ssmpingd configuration. A network level management tool + to check whether one can receive multicast packets via SSM from host. + The host target given to ssmping must run the ssmpingd daemon which listens + for IPv4 and IPv6 unicast requests. When it receives one, it responds to a + well known SSM multicast group which ssmping just have joined. + +.. _pimv6-interface-configuration: + +PIMv6 Interface Configuration +============================= + +PIMv6 interface commands allow you to configure an interface as either a Receiver +or a interface that you would like to form pimv6 neighbors on. If the interface +is in a vrf, enter the interface command with the vrf keyword at the end. + +.. clicmd:: ipv6 pim active-active + + Turn on pim active-active configuration for a Vxlan interface. This + command will not do anything if you do not have the underlying ability + of a mlag implementation. + +.. clicmd:: ipv6 pim drpriority (0-4294967295) + + Set the DR Priority for the interface. This command is useful to allow the + user to influence what node becomes the DR for a lan segment. + +.. clicmd:: ipv6 pim hello (1-65535) (1-65535) + + Set the pim hello and hold interval for a interface. + +.. clicmd:: ipv6 pim + + Tell pim that we would like to use this interface to form pim neighbors + over. Please note that this command does not enable the reception of MLD + reports on the interface. Refer to the next ``ipv6 mld`` command for MLD + management. + +.. clicmd:: ipv6 pim use-source X:X::X:X + + If you have multiple addresses configured on a particular interface + and would like pim to use a specific source address associated with + that interface. + +.. clicmd:: ipv6 pim passive + + Disable sending and receiving pim control packets on the interface. + +.. clicmd:: ipv6 pim bsm + + Tell pim that we would like to use this interface to process bootstrap + messages. This is enabled by default. 'no' form of this command is used to + restrict bsm messages on this interface. + +.. clicmd:: ipv6 pim unicast-bsm + + Tell pim that we would like to allow interface to process unicast bootstrap + messages. This is enabled by default. 'no' form of this command is used to + restrict processing of unicast bsm messages on this interface. + +.. clicmd:: ipv6 mld + + Tell pim to receive MLD reports and Query on this interface. The default + version is v2. This command is useful on a LHR. + +.. clicmd:: ipv6 mld join X:X::X:X [Y:Y::Y:Y] + + Join multicast group or source-group on an interface. + +.. clicmd:: ipv6 mld query-interval (1-65535) + + Set the MLD query interval that PIM will use. + +.. clicmd:: ipv6 mld query-max-response-time (1-65535) + + Set the MLD query response timeout value. If an report is not returned in + the specified time we will assume the S,G or \*,G has timed out. + +.. clicmd:: ipv6 mld version (1-2) + + Set the MLD version used on this interface. The default value is 2. + +.. clicmd:: ipv6 multicast boundary oil WORD + + Set a PIMv6 multicast boundary, based upon the WORD prefix-list. If a PIMv6 + join or MLD report is received on this interface and the Group is denied by + the prefix-list, PIMv6 will ignore the join or report. + +.. clicmd:: ipv6 mld last-member-query-count (1-255) + + Set the MLD last member query count. The default value is 2. 'no' form of + this command is used to configure back to the default value. + +.. clicmd:: ipv6 mld last-member-query-interval (1-65535) + + Set the MLD last member query interval in deciseconds. The default value is + 10 deciseconds. 'no' form of this command is used to to configure back to the + default value. + +.. clicmd:: ipv6 mroute INTERFACE X:X::X:X [Y:Y::Y:Y] + + Set a static multicast route for a traffic coming on the current interface to + be forwarded on the given interface if the traffic matches the group address + and optionally the source address. + +.. _show-pimv6-information: + +Show PIMv6 Information +====================== + +All PIMv6 show commands are vrf aware and typically allow you to insert a +specified vrf command if information is desired about a specific vrf. If no +vrf is specified then the default vrf is assumed. Finally the special keyword +'all' allows you to look at all vrfs for the command. Naming a vrf 'all' will +cause great confusion. + +PIM protocol state +------------------ + +.. clicmd:: show ipv6 pim [vrf NAME] group-type [json] + + Display SSM group ranges. + +.. clicmd:: show ipv6 pim interface + + Display information about interfaces PIM is using. + +.. clicmd:: show ipv6 pim [vrf NAME] join [X:X::X:X [X:X::X:X]] [json] +.. clicmd:: show ipv6 pim vrf all join [json] + + Display information about PIM joins received. If one address is specified + then we assume it is the Group we are interested in displaying data on. + If the second address is specified then it is Source Group. + +.. clicmd:: show ipv6 pim [vrf NAME] local-membership [json] + + Display information about PIM interface local-membership. + +.. clicmd:: show ipv6 pim [vrf NAME] neighbor [detail|WORD] [json] +.. clicmd:: show ipv6 pim vrf all neighbor [detail|WORD] [json] + + Display information about PIM neighbors. + +.. clicmd:: show ipv6 pim [vrf NAME] nexthop + + Display information about pim nexthops that are being used. + +.. clicmd:: show ipv6 pim [vrf NAME] nexthop-lookup X:X::X:X X:X::X:X + + Display information about a S,G pair and how the RPF would be chosen. This + is especially useful if there are ECMP's available from the RPF lookup. + +.. clicmd:: show ipv6 pim [vrf NAME] rp-info [json] +.. clicmd:: show ipv6 pim vrf all rp-info [json] + + Display information about RP's that are configured on this router. + +.. clicmd:: show ipv6 pim [vrf NAME] rpf [json] +.. clicmd:: show ipv6 pim vrf all rpf [json] + + Display information about currently being used S,G's and their RPF lookup + information. Additionally display some statistics about what has been + happening on the router. + +.. clicmd:: show ipv6 pim [vrf NAME] secondary + + Display information about an interface and all the secondary addresses + associated with it. + +.. clicmd:: show ipv6 pim [vrf NAME] state [X:X::X:X [X:X::X:X]] [json] +.. clicmd:: show ipv6 pim vrf all state [X:X::X:X [X:X::X:X]] [json] + + Display information about known S,G's and incoming interface as well as the + OIL and how they were chosen. + +.. clicmd:: show ipv6 pim [vrf NAME] upstream [X:X::X:X [Y:Y::Y:Y]] [json] +.. clicmd:: show ipv6 pim vrf all upstream [json] + + Display upstream information about a S,G mroute. Allow the user to + specify sub Source and Groups that we are interested in. + +.. clicmd:: show ipv6 pim [vrf NAME] upstream-join-desired [json] + + Display upstream information for S,G's and if we desire to + join the multicast tree + +.. clicmd:: show ipv6 pim [vrf NAME] upstream-rpf [json] + + Display upstream information for S,G's and the RPF data associated with them. + +.. clicmd:: show ipv6 pim [vrf NAME] interface traffic [WORD] [json] + + Display information about the number of PIM protocol packets sent/received + on an interface. + +MLD state +--------- + +.. clicmd:: show ipv6 mld [vrf NAME] interface [IFNAME] [detail|json] + + Display per-interface MLD state, elected querier and related timers. Use + the ``detail`` or ``json`` options for further information (the JSON output + always contains all details.) + +.. clicmd:: show ipv6 mld [vrf NAME] statistics [interface IFNAME] [json] + + Display packet and error counters for MLD interfaces. All counters are + packet counters (not bytes) and wrap at 64 bit. In some rare cases, + malformed received MLD reports may be partially processed and counted on + multiple counters. + +.. clicmd:: show ipv6 mld [vrf NAME] joins [{interface IFNAME|groups X:X::X:X/M|sources X:X::X:X/M|detail}] [json] + + Display joined groups tracked by MLD. ``interface``, ``groups`` and + ``sources`` options may be used to limit output to a subset (note ``sources`` + refers to the multicast traffic sender, not the host that joined to receive + the traffic.) + + The ``detail`` option also reports which hosts have joined (subscribed) to + particular ``S,G``. This information is only available for MLDv2 hosts with + a MLDv2 querier. MLDv1 joins are recorded as "untracked" and shown in the + ``NonTrkSeen`` output column. + +.. clicmd:: show ipv6 mld [vrf NAME] groups [json] + + Display MLD group information. + +General multicast routing state +------------------------------- + +.. clicmd:: show ipv6 multicast + + Display various information about the interfaces used in this pim instance. + +.. clicmd:: show ipv6 multicast count [vrf NAME] [json] + + Display multicast data packets count per interface for a vrf. + +.. clicmd:: show ipv6 multicast count vrf all [json] + + Display multicast data packets count per interface for all vrf. + +.. clicmd:: show ipv6 mroute [vrf NAME] [X:X::X:X [X:X::X:X]] [fill] [json] + + Display information about installed into the kernel S,G mroutes. If + one address is specified we assume it is the Group we are interested + in displaying data on. If the second address is specified then it is + Source Group. The keyword ``fill`` says to fill in all assumed data + for test/data gathering purposes. + +.. clicmd:: show ipv6 mroute [vrf NAME] count [json] + + Display information about installed into the kernel S,G mroutes and in + addition display data about packet flow for the mroutes for a specific + vrf. + +.. clicmd:: show ipv6 mroute vrf all count [json] + + Display information about installed into the kernel S,G mroutes and in + addition display data about packet flow for the mroutes for all vrfs. + +.. clicmd:: show ipv6 mroute [vrf NAME] summary [json] + + Display total number of S,G mroutes and number of S,G mroutes installed + into the kernel for a specific vrf. + +.. clicmd:: show ipv6 mroute vrf all summary [json] + + Display total number of S,G mroutes and number of S,G mroutes + installed into the kernel for all vrfs. + +.. clicmd:: show ipv6 pim bsr + + Display current bsr, its uptime and last received bsm age. + +.. clicmd:: show ipv6 pim bsrp-info + + Display group-to-rp mappings received from E-BSR. + +.. clicmd:: show ipv6 pim bsm-database + + Display all fragments of stored bootstrap message in user readable format. + +PIMv6 Clear Commands +==================== + +Clear commands reset various variables. + +.. clicmd:: clear ipv6 mroute + + Reset multicast routes. + +.. clicmd:: clear ipv6 mroute [vrf NAME] count + + When this command is issued, reset the counts of data shown for + packet count, byte count and wrong interface to 0 and start count + up from this spot. + +.. clicmd:: clear ipv6 pim interfaces + + Reset PIMv6 interfaces. + +.. clicmd:: clear ipv6 pim [vrf NAME] interface traffic + + When this command is issued, resets the information about the + number of PIM protocol packets sent/received on an interface. + +.. clicmd:: clear ipv6 pim oil + + Rescan PIMv6 OIL (output interface list). + +.. clicmd:: clear ipv6 pim [vrf NAME] bsr-data + + This command will clear the BSM scope data struct. This command also + removes the next hop tracking for the bsr and resets the upstreams + for the dynamically learnt RPs. + +PIMv6 Debug Commands +==================== + +The debugging subsystem for PIMv6 behaves in accordance with how FRR handles +debugging. You can specify debugging at the enable CLI mode as well as the +configure CLI mode. If you specify debug commands in the configuration cli +mode, the debug commands can be persistent across restarts of the FRR pim6d if +the config was written out. + +.. clicmd:: debug mld + + This turns on debugging for MLD protocol activity. + +.. clicmd:: debug pimv6 events + + This turns on debugging for PIMv6 system events. Especially timers. + +.. clicmd:: debug pimv6 nht + + This turns on debugging for PIMv6 nexthop tracking. It will display + information about RPF lookups and information about when a nexthop changes. + +.. clicmd:: debug pimv6 nht detail + + This turns on debugging for PIMv6 nexthop in detail. This is not enabled + by default. + +.. clicmd:: debug pimv6 packet-dump + + This turns on an extraordinary amount of data. Each pim packet sent and + received is dumped for debugging purposes. This should be considered a + developer only command. + +.. clicmd:: debug pimv6 packets + + This turns on information about packet generation for sending and about + packet handling from a received packet. + +.. clicmd:: debug pimv6 trace + + This traces pim code and how it is running. + +.. clicmd:: debug pimv6 zebra + + This gathers data about events from zebra that come up through the ZAPI. + +.. clicmd:: debug mroute6 + + This turns on debugging for PIMv6 interaction with kernel MFC cache. + +.. clicmd:: debug mroute6 detail + + This turns on detailed debugging for PIMv6 interaction with kernel MFC cache. + +.. clicmd:: debug mld events + + This turns on debugging for MLD system events. + +.. clicmd:: debug mld packets + + This turns on information about MLD protocol packets handling. + +.. clicmd:: debug mld trace [detail] + + This traces mld code and how it is running. + +.. clicmd:: debug pimv6 bsm + + This turns on debugging for BSR message processing. diff --git a/doc/user/prior-config-files.rst b/doc/user/prior-config-files.rst new file mode 100644 index 0000000..a01b688 --- /dev/null +++ b/doc/user/prior-config-files.rst @@ -0,0 +1,23 @@ +.. +.. January 12 2024, Christian Hopps +.. +.. Copyright (c) 2024, LabN Consulting, L.L.C. +.. +.. + +Prior versions of FRR supported reading and writing per-daemon config files; +however, with the introduction of the centralized management daemon ``mgmtd`` +this could no longer be supported. + +In order to allow for an orderly transition from per-daemon config files to the +integrated config file, FRR daemons will continue to try and **read** their +specific per-daemon configuration file as before. Additionally the config can +still be loaded directly using the ``-f`` or ``--config-file`` CLI options; +however, these files will **not** be updated when the configuration is written +(e.g., with the ``write mem`` command). + +.. warning:: + + Per-daemon files will **no longer** be updated when the user issues a ``write + memory`` command. Therefore these per-daemon config files should only be used + as a mechanism for transitioning to the integrated config, and then removed. diff --git a/doc/user/requirements.txt b/doc/user/requirements.txt new file mode 100644 index 0000000..483a4e9 --- /dev/null +++ b/doc/user/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme diff --git a/doc/user/ripd.rst b/doc/user/ripd.rst new file mode 100644 index 0000000..ea13dc9 --- /dev/null +++ b/doc/user/ripd.rst @@ -0,0 +1,560 @@ +.. _rip: + +*** +RIP +*** + +RIP -- Routing Information Protocol is widely deployed interior gateway +protocol. RIP was developed in the 1970s at Xerox Labs as part of the +XNS routing protocol. RIP is a :term:`distance-vector` protocol and is +based on the :term:`Bellman-Ford` algorithms. As a distance-vector +protocol, RIP router send updates to its neighbors periodically, thus +allowing the convergence to a known topology. In each update, the +distance to any given network will be broadcast to its neighboring +router. + +*ripd* supports RIP version 2 as described in RFC2453 and RIP +version 1 as described in RFC1058. + +.. _starting-and-stopping-ripd: + +Starting and Stopping ripd +========================== + +.. include:: config-include.rst + +RIP uses UDP port 520 to send and receive RIP packets. So the user must have +the capability to bind the port, generally this means that the user must have +superuser privileges. + +If starting daemons by hand then please note, RIP protocol requires interface +information maintained by *zebra* daemon. So running *zebra* is mandatory to run +*ripd*. Thus minimum sequence for running RIP is like below: + +:: + + # zebra -d + # ripd -d + + +Please note that *zebra* must be invoked before *ripd*. + +To stop *ripd*. Please use:: + + kill `cat /var/run/frr/ripd.pid` + +Certain signals have special meanings to *ripd*. + + +-------------+------------------------------------------------------+ + | Signal | Action | + +=============+======================================================+ + | ``SIGHUP`` | Reload configuration file :file:`ripd.conf`. | + | | All configurations are reset. All routes learned | + | | so far are cleared and removed from routing table. | + +-------------+------------------------------------------------------+ + | ``SIGUSR1`` | Rotate the *ripd* logfile. | + +-------------+------------------------------------------------------+ + | ``SIGINT`` | | + | ``SIGTERM`` | Sweep all installed routes and gracefully terminate. | + +-------------+------------------------------------------------------+ + +*ripd* invocation options. Common options that can be specified +(:ref:`common-invocation-options`). + + +.. _rip-netmask: + +RIP netmask +----------- + +The netmask features of *ripd* support both version 1 and version 2 of RIP. +Version 1 of RIP originally contained no netmask information. In RIP version 1, +network classes were originally used to determine the size of the netmask. +Class A networks use 8 bits of mask, Class B networks use 16 bits of masks, +while Class C networks use 24 bits of mask. Today, the most widely used method +of a network mask is assigned to the packet on the basis of the interface that +received the packet. Version 2 of RIP supports a variable length subnet mask +(VLSM). By extending the subnet mask, the mask can be divided and reused. Each +subnet can be used for different purposes such as large to middle size LANs and +WAN links. FRR *ripd* does not support the non-sequential netmasks that are +included in RIP Version 2. + +In a case of similar information with the same prefix and metric, the old +information will be suppressed. Ripd does not currently support equal cost +multipath routing. + +.. _rip-configuration: + +RIP Configuration +================= + +.. clicmd:: router rip [vrf NAME] + + The `router rip` command is necessary to enable RIP. To disable RIP, use the + `no router rip` command. RIP must be enabled before carrying out any of the + RIP commands. + +.. clicmd:: network NETWORK + + + Set the RIP enable interface by NETWORK. The interfaces which have addresses + matching with NETWORK are enabled. + + This group of commands either enables or disables RIP interfaces between + certain numbers of a specified network address. For example, if the network + for 10.0.0.0/24 is RIP enabled, this would result in all the addresses from + 10.0.0.0 to 10.0.0.255 being enabled for RIP. The `no network` command will + disable RIP for the specified network. + +.. clicmd:: network IFNAME + + + Set a RIP enabled interface by IFNAME. Both the sending and + receiving of RIP packets will be enabled on the port specified in the + `network ifname` command. The `no network ifname` command will disable + RIP on the specified interface. + +.. clicmd:: neighbor A.B.C.D + + + Specify a RIP neighbor to send updates to. This is required when a neighbor + is connected via a network that does not support multicast, or when it is + desired to statically define a neighbor. RIP updates will be sent via unicast + to each neighbour. Neighbour updates are in addition to any multicast updates + sent when an interface is not in passive mode (see the `passive-interface` + command). RIP will continue to process updates received from both the + neighbor and any received via multicast. The `no neighbor a.b.c.d` command + will disable the RIP neighbor. + + Below is very simple RIP configuration. Interface `eth0` and interface which + address match to `10.0.0.0/8` are RIP enabled. + + .. code-block:: frr + + ! + router rip + network 10.0.0.0/8 + network eth0 + ! + + +.. clicmd:: passive-interface (IFNAME|default) + + + This command sets the specified interface to passive mode. On passive mode + interface, all receiving packets are processed as normal and ripd does not + send either multicast or unicast RIP packets except to RIP neighbors + specified with `neighbor` command. The interface may be specified as + `default` to make ripd default to passive on all interfaces. + + The default is to be passive on all interfaces. + +.. clicmd:: ip split-horizon [poisoned-reverse] + + + Control split-horizon on the interface. Default is `ip split-horizon`. If + you don't perform split-horizon on the interface, please specify `no ip + split-horizon`. + + If `poisoned-reverse` is also set, the router sends the poisoned routes + with highest metric back to the sending router. + +.. clicmd:: allow-ecmp [1-MULTIPATH_NUM] + + Control how many ECMP paths RIP can inject for the same prefix. If specified + without a number, a maximum is taken (compiled with ``--enable-multipath``). + +.. _rip-version-control: + +RIP Version Control +=================== + +RIP can be configured to send either Version 1 or Version 2 packets. The +default is to send RIPv2 while accepting both RIPv1 and RIPv2 (and replying +with packets of the appropriate version for REQUESTS / triggered updates). The +version to receive and send can be specified globally, and further overridden on +a per-interface basis if needs be for send and receive separately (see below). + +It is important to note that RIPv1 cannot be authenticated. Further, if RIPv1 +is enabled then RIP will reply to REQUEST packets, sending the state of its RIP +routing table to any remote routers that ask on demand. For a more detailed +discussion on the security implications of RIPv1 see :ref:`rip-authentication`. + +.. clicmd:: version VERSION + + Set RIP version to accept for reads and send. VERSION can be either + ``1`` or ``2``. + + Disabling RIPv1 by specifying version 2 is STRONGLY encouraged, + :ref:`rip-authentication`. This may become the default in a future release. + + Default: Send Version 2, and accept either version. + +.. clicmd:: ip rip send version VERSION + + VERSION can be ``1``, ``2``, or ``1 2``. + + This interface command overrides the global rip version setting, and selects + which version of RIP to send packets with, for this interface specifically. + Choice of RIP Version 1, RIP Version 2, or both versions. In the latter + case, where ``1 2`` is specified, packets will be both broadcast and + multicast. + + Default: Send packets according to the global version (version 2) + +.. clicmd:: ip rip receive version VERSION + + VERSION can be ``1``, ``2``, or ``1 2``. + + This interface command overrides the global rip version setting, and selects + which versions of RIP packets will be accepted on this interface. Choice of + RIP Version 1, RIP Version 2, or both. + + Default: Accept packets according to the global setting (both 1 and 2). + + +.. _how-to-announce-rip-route: + +How to Announce RIP route +========================= + +.. clicmd:: redistribute [metric (0-16)] [route-map WORD] + + Redistribute routes from other sources into RIP. + +If you want to specify RIP only static routes: + +.. clicmd:: default-information originate + +.. clicmd:: route A.B.C.D/M + + + This command is specific to FRR. The `route` command makes a static route + only inside RIP. This command should be used only by advanced users who are + particularly knowledgeable about the RIP protocol. In most cases, we + recommend creating a static route in FRR and redistributing it in RIP using + `redistribute static`. + +.. _filtering-rip-routes: + +Filtering RIP Routes +==================== + +RIP routes can be filtered by a distribute-list. + +.. clicmd:: distribute-list [prefix] LIST IFNAME + + You can apply access lists to the interface with a `distribute-list` command. + If prefix is specified LIST is a prefix-list. If prefix is not specified + then LIST is the access list name. `in` specifies packets being received, + and `out` specifies outgoing packets. Finally if an interface is specified + it will be applied against a specific interface. + + The `distribute-list` command can be used to filter the RIP path. + `distribute-list` can apply access-lists to a chosen interface. First, one + should specify the access-list. Next, the name of the access-list is used in + the distribute-list command. For example, in the following configuration + ``eth0`` will permit only the paths that match the route 10.0.0.0/8 + + .. code-block:: frr + + ! + router rip + distribute-list private in eth0 + ! + access-list private permit 10 10.0.0.0/8 + access-list private deny any + ! + + + `distribute-list` can be applied to both incoming and outgoing data. + +.. _rip-metric-manipulation: + +RIP Metric Manipulation +======================= + +RIP metric is a value for distance for the network. Usually +*ripd* increment the metric when the network information is +received. Redistributed routes' metric is set to 1. + +.. clicmd:: default-metric (1-16) + + + This command modifies the default metric value for redistributed routes. + The default value is 1. This command does not affect connected route even if + it is redistributed by *redistribute connected*. To modify connected route's + metric value, please use ``redistribute connected metric`` or *route-map*. + *offset-list* also affects connected routes. + +.. clicmd:: offset-list ACCESS-LIST (in|out) + +.. clicmd:: offset-list ACCESS-LIST (in|out) IFNAME + + +.. _rip-distance: + +RIP distance +============ + +Distance value is used in zebra daemon. Default RIP distance is 120. + +.. clicmd:: distance (1-255) + + + Set default RIP distance to specified value. + +.. clicmd:: distance (1-255) A.B.C.D/M + + + Set default RIP distance to specified value when the route's source IP + address matches the specified prefix. + +.. clicmd:: distance (1-255) A.B.C.D/M ACCESS-LIST + + + Set default RIP distance to specified value when the route's source IP + address matches the specified prefix and the specified access-list. + +.. _rip-route-map: + +RIP route-map +============= + +Usage of *ripd*'s route-map support. + +Optional argument route-map MAP_NAME can be added to each `redistribute` +statement. + +.. code-block:: frr + + redistribute static [route-map MAP_NAME] + redistribute connected [route-map MAP_NAME] + ..... + + +Cisco applies route-map _before_ routes will exported to rip route table. In +current FRR's test implementation, *ripd* applies route-map after routes are +listed in the route table and before routes will be announced to an interface +(something like output filter). I think it is not so clear, but it is draft and +it may be changed at future. + +Route-map statement (:ref:`route-map`) is needed to use route-map +functionality. + +.. clicmd:: match interface WORD + + This command match to incoming interface. Notation of this match is + different from Cisco. Cisco uses a list of interfaces - NAME1 NAME2 ... + NAMEN. Ripd allows only one name (maybe will change in the future). Next - + Cisco means interface which includes next-hop of routes (it is somewhat + similar to "ip next-hop" statement). Ripd means interface where this route + will be sent. This difference is because "next-hop" of same routes which + sends to different interfaces must be different. Maybe it'd be better to + made new matches - say "match interface-out NAME" or something like that. + +.. clicmd:: match ip address WORD + +.. clicmd:: match ip address prefix-list WORD + + Match if route destination is permitted by access-list. + +.. clicmd:: match ip next-hop WORD + +.. clicmd:: match ip next-hop prefix-list WORD + + Match if route next-hop (meaning next-hop listed in the rip route-table as + displayed by "show ip rip") is permitted by access-list. + +.. clicmd:: match metric (0-4294967295) + + This command match to the metric value of RIP updates. For other protocol + compatibility metric range is shown as (0-4294967295). But for RIP protocol + only the value range (0-16) make sense. + +.. clicmd:: set ip next-hop A.B.C.D + + This command set next hop value in RIPv2 protocol. This command does not + affect RIPv1 because there is no next hop field in the packet. + +.. clicmd:: set metric (0-4294967295) + + Set a metric for matched route when sending announcement. The metric value + range is very large for compatibility with other protocols. For RIP, valid + metric values are from 1 to 16. + +.. _rip-authentication: + +RIP Authentication +================== + +RIPv2 allows packets to be authenticated via either an insecure plain +text password, included with the packet, or via a more secure MD5 based +:abbr:`HMAC (keyed-Hashing for Message AuthentiCation)`, +RIPv1 can not be authenticated at all, thus when authentication is +configured `ripd` will discard routing updates received via RIPv1 +packets. + +However, unless RIPv1 reception is disabled entirely, +:ref:`rip-version-control`, RIPv1 REQUEST packets which are received, +which query the router for routing information, will still be honoured +by `ripd`, and `ripd` WILL reply to such packets. This allows +`ripd` to honour such REQUESTs (which sometimes is used by old +equipment and very simple devices to bootstrap their default route), +while still providing security for route updates which are received. + +In short: Enabling authentication prevents routes being updated by +unauthenticated remote routers, but still can allow routes (I.e. the +entire RIP routing table) to be queried remotely, potentially by anyone +on the internet, via RIPv1. + +To prevent such unauthenticated querying of routes disable RIPv1, +:ref:`rip-version-control`. + +.. clicmd:: ip rip authentication mode md5 + + + Set the interface with RIPv2 MD5 authentication. + +.. clicmd:: ip rip authentication mode text + + + Set the interface with RIPv2 simple password authentication. + +.. clicmd:: ip rip authentication string STRING + + + RIP version 2 has simple text authentication. This command sets + authentication string. The string must be shorter than 16 characters. + +.. clicmd:: ip rip authentication key-chain KEY-CHAIN + + + Specify Keyed MD5 chain. + + .. code-block:: frr + + ! + key chain test + key 1 + key-string test + ! + interface eth1 + ip rip authentication mode md5 + ip rip authentication key-chain test + ! + + +.. _rip-timers: + +RIP Timers +========== + +.. clicmd:: timers basic UPDATE TIMEOUT GARBAGE + + + RIP protocol has several timers. User can configure those timers' values + by `timers basic` command. + + The default settings for the timers are as follows: + + - The update timer is 30 seconds. Every update timer seconds, the RIP + process is awakened to send an unsolicited Response message containing + the complete routing table to all neighboring RIP routers. + - The timeout timer is 180 seconds. Upon expiration of the timeout, the + route is no longer valid; however, it is retained in the routing table + for a short time so that neighbors can be notified that the route has + been dropped. + - The garbage collect timer is 120 seconds. Upon expiration of the + garbage-collection timer, the route is finally removed from the routing + table. + + The ``timers basic`` command allows the the default values of the timers + listed above to be changed. + + +.. _show-rip-information: + +Show RIP Information +==================== + +To display RIP routes. + +.. clicmd:: show ip rip [vrf NAME] + + Show RIP routes. + +The command displays all RIP routes. For routes that are received +through RIP, this command will display the time the packet was sent and +the tag information. This command will also display this information +for routes redistributed into RIP. + +.. clicmd:: show ip rip [vrf NAME] status + + The command displays current RIP status. It includes RIP timer, + filtering, version, RIP enabled interface and RIP peer information. + +:: + + ripd> **show ip rip status** + Routing Protocol is "rip" + Sending updates every 30 seconds with +/-50%, next due in 35 seconds + Timeout after 180 seconds, garbage collect after 120 seconds + Outgoing update filter list for all interface is not set + Incoming update filter list for all interface is not set + Default redistribution metric is 1 + Redistributing: kernel connected + Default version control: send version 2, receive version 2 + Interface Send Recv + Routing for Networks: + eth0 + eth1 + 1.1.1.1 + 203.181.89.241 + Routing Information Sources: + Gateway BadPackets BadRoutes Distance Last Update + + +RIP Debug Commands +================== + +Debug for RIP protocol. + +.. clicmd:: debug rip events + + Shows RIP events. Sending and receiving packets, timers, and changes in + interfaces are events shown with *ripd*. + +.. clicmd:: debug rip packet + + Shows display detailed information about the RIP packets. The origin and + port number of the packet as well as a packet dump is shown. + +.. clicmd:: debug rip zebra + + This command will show the communication between *ripd* and *zebra*. The + main information will include addition and deletion of paths to the kernel + and the sending and receiving of interface information. + +.. clicmd:: show debugging rip + + Shows all information currently set for ripd debug. + + +Sample configuration +==================== + +.. code-block:: frr + + + debug rip events + debug rip packet + + router rip + network 11.0.0.0/8 + network eth0 + route 10.0.0.0/8 + distribute-list private-only in eth0 + + access-list private-only permit 10.0.0.0/8 + access-list private-only deny any diff --git a/doc/user/ripngd.rst b/doc/user/ripngd.rst new file mode 100644 index 0000000..f55ee39 --- /dev/null +++ b/doc/user/ripngd.rst @@ -0,0 +1,158 @@ +.. _ripng: + +***** +RIPng +***** + +*ripngd* supports the RIPng protocol as described in :rfc:`2080`. It's an IPv6 +reincarnation of the RIP protocol. + +.. _invoking-ripngd: + +Invoking ripngd +=============== + +.. include:: config-include.rst + +There are no `ripngd` specific invocation options. Common options can be +specified (:ref:`common-invocation-options`). + +.. _ripngd-configuration: + +ripngd Configuration +==================== + +Currently ripngd supports the following commands: + +.. clicmd:: router ripng [vrf NAME] + + Enable RIPng. + +.. clicmd:: network NETWORK + + Set RIPng enabled interface by NETWORK. + +.. clicmd:: network IFNAME + + Set RIPng enabled interface by IFNAME. + +.. clicmd:: route NETWORK + + Set RIPng static routing announcement of NETWORK. + +.. clicmd:: allow-ecmp [1-MULTIPATH_NUM] + + Control how many ECMP paths RIPng can inject for the same prefix. If specified + without a number, a maximum is taken (compiled with ``--enable-multipath``). + +.. _ripngd-terminal-mode-commands: + +ripngd Terminal Mode Commands +============================= + +.. clicmd:: show ipv6 ripng [vrf NAME] status + +.. clicmd:: show debugging ripng + +.. clicmd:: debug ripng events + +.. clicmd:: debug ripng packet + +.. clicmd:: debug ripng zebra + + +ripngd Filtering Commands +========================= + +RIPng routes can be filtered by a distribute-list. + +.. clicmd:: distribute-list [prefix] LIST IFNAME + + You can apply access lists to the interface with a `distribute-list` command. + If prefix is specified LIST is a prefix-list. If prefix is not specified + then LIST is the access list name. `in` specifies packets being received, + and `out` specifies outgoing packets. Finally if an interface is specified + it will be applied against a specific interface. + + The ``distribute-list`` command can be used to filter the RIPNG path. + ``distribute-list`` can apply access-lists to a chosen interface. First, one + should specify the access-list. Next, the name of the access-list is used in + the distribute-list command. For example, in the following configuration + ``eth0`` will permit only the paths that match the route 10.0.0.0/8 + + .. code-block:: frr + + ! + router ripng + distribute-list private in eth0 + ! + access-list private permit 10 10.0.0.0/8 + access-list private deny any + ! + + + `distribute-list` can be applied to both incoming and outgoing data. + + +.. _ripng-route-map: + +RIPng route-map +=============== + +Usage of *ripngd*'s route-map support. + +Route-map statement (:ref:`route-map`) is needed to use route-map +functionality. + +.. clicmd:: match interface WORD + + This command match to incoming interface. Notation of this match is + different from Cisco. Cisco uses a list of interfaces - NAME1 NAME2 ... + NAMEN. Ripngd allows only one name (maybe will change in the future). Next - + Cisco means interface which includes next-hop of routes (it is somewhat + similar to "ipv6 next-hop" statement). Ripngd means interface where this route + will be sent. This difference is because "next-hop" of same routes which + sends to different interfaces must be different. + +.. clicmd:: match ipv6 address WORD + +.. clicmd:: match ipv6 address prefix-list WORD + + Match if route destination is permitted by access-list/prefix-list. + +.. clicmd:: match metric (0-4294967295) + + This command match to the metric value of RIPng updates. For other protocol + compatibility metric range is shown as (0-4294967295). But for RIPng protocol + only the value range (0-16) make sense. + +.. clicmd:: set ipv6 next-hop local IPV6_ADDRESS + + Set the link-local IPv6 nexthop address. + +.. clicmd:: set metric (1-16) + + Set a metric for matched route when sending announcement. The metric value + range is very large for compatibility with other protocols. For RIPng, valid + metric values are from 1 to 16. + +.. clicmd:: set tag + + Set a tag on the matched route. + + +Sample configuration +==================== + +.. code-block:: frr + + debug ripng events + debug ripng packet + + router ripng + network sit1 + route 3ffe:506::0/32 + distribute-list local-only out sit1 + + ipv6 access-list local-only permit 3ffe:506::0/32 + ipv6 access-list local-only deny any diff --git a/doc/user/routemap.rst b/doc/user/routemap.rst new file mode 100644 index 0000000..1d2f4e3 --- /dev/null +++ b/doc/user/routemap.rst @@ -0,0 +1,428 @@ +.. _route-map: + +********** +Route Maps +********** + +Route maps provide a means to both filter and/or apply actions to route, hence +allowing policy to be applied to routes. + +For a route reflector to apply a ``route-map`` to reflected routes, be sure to +include ``bgp route-reflector allow-outbound-policy`` in ``router bgp`` mode. + +Route maps are an ordered list of route map entries. Each entry may specify up +to four distinct sets of clauses: + +.. glossary:: + + Matching Conditions + A route-map entry may, optionally, specify one or more conditions which + must be matched if the entry is to be considered further, as governed by + the Match Policy. If a route-map entry does not explicitly specify any + matching conditions, then it always matches. + + Set Actions + A route-map entry may, optionally, specify one or more Set Actions to set + or modify attributes of the route. + + Matching Policy + This specifies the policy implied if the :term:`Matching Conditions` are + met or not met, and which actions of the route-map are to be taken, if + any. The two possibilities are: + + - :dfn:`permit`: If the entry matches, then carry out the + :term:`Set Actions`. Then finish processing the route-map, permitting + the route, unless an :term:`Exit Policy` action indicates otherwise. + + - :dfn:`deny`: If the entry matches, then finish processing the route-map and + deny the route (return `deny`). + + The `Matching Policy` is specified as part of the command which defines + the ordered entry in the route-map. See below. + + Call Action + Call to another route-map, after any :term:`Set Actions` have been + carried out. If the route-map called returns `deny` then processing of + the route-map finishes and the route is denied, regardless of the + :term:`Matching Policy` or the :term:`Exit Policy`. If the called + route-map returns `permit`, then :term:`Matching Policy` and :term:`Exit + Policy` govern further behaviour, as normal. + + Exit Policy + An entry may, optionally, specify an alternative :dfn:`Exit Policy` to + take if the entry matched, rather than the normal policy of exiting the + route-map and permitting the route. The two possibilities are: + + - :dfn:`next`: Continue on with processing of the route-map entries. + + - :dfn:`goto N`: Jump ahead to the first route-map entry whose order in + the route-map is >= N. Jumping to a previous entry is not permitted. + +The default action of a route-map, if no entries match, is to deny. I.e. a +route-map essentially has as its last entry an empty *deny* entry, which +matches all routes. To change this behaviour, one must specify an empty +*permit* entry as the last entry in the route-map. + +To summarise the above: + ++--------+--------+----------+ +| | Match | No Match | ++========+========+==========+ +| Permit | action | cont | ++--------+--------+----------+ +| Deny | deny | cont | ++--------+--------+----------+ + +action + - Apply *set* statements + - If *call* is present, call given route-map. If that returns a ``deny``, + finish processing and return ``deny``. + - If *Exit Policy* is *next*, goto next route-map entry + - If *Exit Policy* is *goto*, goto first entry whose order in the + list is >= the given order. + - Finish processing the route-map and permit the route. + +deny + The route is denied by the route-map (return ``deny``). + +cont + goto next route-map entry + +.. _route-map-show-command: + +.. clicmd:: show route-map [WORD] [json] + + Display data about each daemons knowledge of individual route-maps. + If WORD is supplied narrow choice to that particular route-map. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. _route-map-clear-counter-command: + +.. clicmd:: clear route-map counter [WORD] + + Clear counters that are being stored about the route-map utilization + so that subsuquent show commands will indicate since the last clear. + If WORD is specified clear just that particular route-map's counters. + +.. _route-map-command: + +Route Map Command +================= + +.. clicmd:: route-map ROUTE-MAP-NAME (permit|deny) ORDER + + Configure the `order`'th entry in `route-map-name` with ``Match Policy`` of + either *permit* or *deny*. + +.. _route-map-match-command: + +Route Map Match Command +======================= + +.. clicmd:: match ip address ACCESS_LIST + + Matches the specified `access_list` + +.. clicmd:: match ip address prefix-list PREFIX_LIST + + Matches the specified `PREFIX_LIST` + +.. clicmd:: match ip address prefix-len 0-32 + + Matches the specified `prefix-len`. This is a Zebra specific command. + +.. clicmd:: match ipv6 address ACCESS_LIST + + Matches the specified `access_list` + +.. clicmd:: match ipv6 address prefix-list PREFIX_LIST + + Matches the specified `PREFIX_LIST` + +.. clicmd:: match ipv6 address prefix-len 0-128 + + Matches the specified `prefix-len`. This is a Zebra specific command. + +.. clicmd:: match ip next-hop ACCESS_LIST + + Match the next-hop according to the given access-list. + +.. clicmd:: match ip next-hop address IPV4_ADDR + + This is a BGP specific match command. Matches the specified `ipv4_addr`. + +.. clicmd:: match ip next-hop prefix-list PREFIX_LIST + + Match the next-hop according to the given prefix-list. + +.. clicmd:: match ipv6 next-hop ACCESS_LIST + + Match the next-hop according to the given access-list. + +.. clicmd:: match ipv6 next-hop address IPV6_ADDR + + This is a BGP specific match command. Matches the specified `ipv6_addr`. + +.. clicmd:: match ipv6 next-hop prefix-list PREFIX_LIST + + Match the next-hop according to the given prefix-list. + +.. clicmd:: match as-path AS_PATH + + Matches the specified `as_path`. + +.. clicmd:: match metric METRIC + + Matches the specified `metric`. + +.. clicmd:: match tag + + Matches the specified tag (or untagged) value associated with the route. + +.. clicmd:: match local-preference METRIC + + Matches the specified `local-preference`. + +.. clicmd:: match community COMMUNITY_LIST [] + + Matches the specified `community_list`. ``exact-match`` specifies to + do the exact matching of the communities, while ``any`` - can match any + community specified in COMMUNITY_LIST. + +.. clicmd:: match peer IPV4_ADDR + + This is a BGP specific match command. Matches the peer ip address + if the neighbor was specified in this manner. + +.. clicmd:: match peer IPV6_ADDR + + This is a BGP specific match command. Matches the peer ipv6 + address if the neighbor was specified in this manner. + +.. clicmd:: match peer INTERFACE_NAME + + This is a BGP specific match command. Matches the peer + interface name specified if the neighbor was specified + in this manner. + +.. clicmd:: match peer PEER_GROUP_NAME + + This is a BGP specific match command. Matches the peer + group name specified for the peer in question. + +.. clicmd:: match source-protocol PROTOCOL_NAME + + This is a ZEBRA and BGP specific match command. Matches the + originating protocol specified. + +.. clicmd:: match source-instance NUMBER + + This is a ZEBRA specific match command. The number is a range from (0-255). + Matches the originating protocols instance specified. + +.. clicmd:: match evpn route-type ROUTE_TYPE_NAME + + This is a BGP EVPN specific match command. It matches to EVPN route-type + from type-1 (EAD route-type) to type-5 (Prefix route-type). + User can provide in an integral form (1-5) or string form of route-type + (i.e ead, macip, multicast, es, prefix). + +.. clicmd:: match evpn vni NUMBER + + This is a BGP EVPN specific match command which matches to EVPN VNI id. + The number is a range from (1-6777215). + +.. _route-map-set-command: + +Route Map Set Command +===================== + +.. program:: configure + +.. clicmd:: set tag + + Set a tag on the matched route. + + Additionally if you have compiled with the :option:`--enable-realms` + configure option. Tag values from (1-255) are sent to the Linux kernel as a + realm value. Then route policy can be applied. See the tc man page. As + a note realms cannot currently be used with the installation of nexthops + as nexthop groups in the linux kernel. + +.. clicmd:: set ip next-hop IPV4_ADDRESS + + Set the BGP nexthop address to the specified IPV4_ADDRESS. For both + incoming and outgoing route-maps. + +.. clicmd:: set ip next-hop peer-address + + Set the BGP nexthop address to the address of the peer. For an incoming + route-map this means the ip address of our peer is used. For an outgoing + route-map this means the ip address of our self is used to establish the + peering with our neighbor. + +.. clicmd:: set ip next-hop unchanged + + Set the route-map as unchanged. Pass the route-map through without + changing it's value. + +.. clicmd:: set ipv6 next-hop peer-address + + Set the BGP nexthop address to the address of the peer. For an incoming + route-map this means the ipv6 address of our peer is used. For an outgoing + route-map this means the ip address of our self is used to establish the + peering with our neighbor. + +.. clicmd:: set ipv6 next-hop prefer-global + + For Incoming and Import Route-maps if we receive a v6 global and v6 LL + address for the route, then prefer to use the global address as the nexthop. + +.. clicmd:: set ipv6 next-hop global IPV6_ADDRESS + + Set the next-hop to the specified IPV6_ADDRESS for both incoming and + outgoing route-maps. + +.. clicmd:: set local-preference LOCAL_PREF + + Set the BGP local preference to `local_pref`. + +.. clicmd:: set local-preference +LOCAL_PREF + + Add the BGP local preference to an existing `local_pref`. + +.. clicmd:: set local-preference -LOCAL_PREF + + Subtract the BGP local preference from an existing `local_pref`. + +.. clicmd:: set distance (1-255) + + Set the Administrative distance to use for the route. + This is only locally significant and will not be dispersed to peers. + +.. clicmd:: set weight WEIGHT + + Set the route's weight. + +.. clicmd:: set metric <[+|-](1-4294967295)|rtt|+rtt|-rtt> + + Set the route metric. When used with BGP, set the BGP attribute MED to a + specific value. Use `+`/`-` to add or subtract the specified value to/from + the existing/MED. Use `rtt` to set the MED to the round trip time or + `+rtt`/`-rtt` to add/subtract the round trip time to/from the MED. + +.. clicmd:: set min-metric <(0-4294967295)> + + Set the minimum meric for the route. + +.. clicmd:: set max-metric <(0-4294967295)> + + Set the maximum meric for the route. + +.. clicmd:: set aigp-metric + + Set the BGP attribute AIGP to a specific value. If ``igp-metric`` is specified, + then the value is taken from the IGP protocol, otherwise an arbitrary value. + +.. clicmd:: set as-path prepend AS_PATH + + Set the BGP AS path to prepend. + +.. clicmd:: set as-path exclude AS-NUMBER... + + Drop AS-NUMBER from the BGP AS path. + +.. clicmd:: set community COMMUNITY + + Set the BGP community attribute. + +.. clicmd:: set extended-comm-list delete + + Set BGP extended community list for deletion. + +.. clicmd:: set ipv6 next-hop local IPV6_ADDRESS + + Set the BGP-4+ link local IPv6 nexthop address. + +.. clicmd:: set origin ORIGIN + + Set BGP route origin. + +.. clicmd:: set table (1-4294967295) + + Set the BGP table to a given table identifier + +.. clicmd:: set sr-te color (1-4294967295) + + Set the color of a SR-TE Policy to be applied to a learned route. The SR-TE + Policy is uniquely determined by the color and the BGP nexthop. + +.. clicmd:: set l3vpn next-hop encapsulation gre + + Accept L3VPN traffic over GRE encapsulation. + +.. _route-map-call-command: + +Route Map Call Command +====================== + +.. clicmd:: call NAME + + Call route-map `name`. If it returns deny, deny the route and + finish processing the route-map. + + +.. _route-map-exit-action-command: + +Route Map Exit Action Command +============================= + +.. clicmd:: on-match next + + Proceed on to the next entry in the route-map. + +.. clicmd:: continue (1-65535) + + Proceed to the specified sequence in the route-map. + +.. clicmd:: on-match goto N + + Proceed processing the route-map at the first entry whose order is >= N + + +.. _route-map-optimization-command: + +Route Map Optimization Command +============================== + +.. clicmd:: route-map ROUTE-MAP-NAME optimization + + Enable route-map processing optimization for `route-map-name`. + The optimization is enabled by default. + Instead of sequentially passing through all the route-map indexes + until a match is found, the search for the best-match index will be + based on a look-up in a prefix-tree. A per-route-map prefix-tree + will be constructed for this purpose. The prefix-tree will compose + of all the prefixes in all the prefix-lists that are included in the + match rule of all the sequences of a route-map. + + +Route Map Examples +================== + +A simple example of a route-map: + +.. code-block:: frr + + route-map test permit 10 + match ip address 10 + set local-preference 200 + + +This means that if a route matches ip access-list number 10 it's +local-preference value is set to 200. + +See :ref:`bgp-configuration-examples` for examples of more sophisticated +usage of route-maps, including of the ``call`` action. + diff --git a/doc/user/routeserver.rst b/doc/user/routeserver.rst new file mode 100644 index 0000000..969cd17 --- /dev/null +++ b/doc/user/routeserver.rst @@ -0,0 +1,542 @@ +.. _configuring-frr-as-a-route-server: + +Configuring FRR as a Route Server +================================= + +The purpose of a Route Server is to centralize the peerings between BGP +speakers. For example if we have an exchange point scenario with four BGP +speakers, each of which maintaining a BGP peering with the other three +(:ref:`fig-topologies-full`), we can convert it into a centralized scenario where +each of the four establishes a single BGP peering against the Route Server +(:ref:`fig-topologies-rs`). + +We will first describe briefly the Route Server model implemented by FRR. +We will explain the commands that have been added for configuring that +model. And finally we will show a full example of FRR configured as Route +Server. + +.. _description-of-the-route-server-model: + +Description of the Route Server model +------------------------------------- + +First we are going to describe the normal processing that BGP announcements +suffer inside a standard BGP speaker, as shown in :ref:`fig-normal-processing`, +it consists of three steps: + +- When an announcement is received from some peer, the `In` filters configured + for that peer are applied to the announcement. These filters can reject the + announcement, accept it unmodified, or accept it with some of its attributes + modified. + +- The announcements that pass the `In` filters go into the Best Path Selection + process, where they are compared to other announcements referred to the same + destination that have been received from different peers (in case such other + announcements exist). For each different destination, the announcement which + is selected as the best is inserted into the BGP speaker's Loc-RIB. + +- The routes which are inserted in the Loc-RIB are considered for announcement + to all the peers (except the one from which the route came). This is done by + passing the routes in the Loc-RIB through the `Out` filters corresponding to + each peer. These filters can reject the route, accept it unmodified, or + accept it with some of its attributes modified. Those routes which are + accepted by the `Out` filters of a peer are announced to that peer. + +.. _fig-normal-processing: + +.. figure:: ../figures/fig-normal-processing.png + :alt: Normal announcement processing + :align: center + + Announcement processing inside a 'normal' BGP speaker + +.. _fig-topologies-full: + +.. figure:: ../figures/fig_topologies_full.png + :alt: Full Mesh BGP Topology + :align: center + + Full Mesh + +.. _fig-topologies-rs: + +.. figure:: ../figures/fig_topologies_rs.png + :alt: Route Server BGP Topology + :align: center + + Route server and clients + +Of course we want that the routing tables obtained in each of the routers are +the same when using the route server than when not. But as a consequence of +having a single BGP peering (against the route server), the BGP speakers can no +longer distinguish from/to which peer each announce comes/goes. + +.. _filter-delegation: + +This means that the routers connected to the route server are not able to apply +by themselves the same input/output filters as in the full mesh scenario, so +they have to delegate those functions to the route server. + +Even more, the 'best path' selection must be also performed inside the route +server on behalf of its clients. The reason is that if, after applying the +filters of the announcer and the (potential) receiver, the route server decides +to send to some client two or more different announcements referred to the same +destination, the client will only retain the last one, considering it as an +implicit withdrawal of the previous announcements for the same destination. +This is the expected behavior of a BGP speaker as defined in :rfc:`1771`, +and even though there are some proposals of mechanisms that permit multiple +paths for the same destination to be sent through a single BGP peering, none +are currently supported by most existing BGP implementations. + +As a consequence a route server must maintain additional information and +perform additional tasks for a RS-client that those necessary for common BGP +peerings. Essentially a route server must: + +.. _route-server-tasks: + +- Maintain a separated Routing Information Base (Loc-RIB) + for each peer configured as RS-client, containing the routes + selected as a result of the 'Best Path Selection' process + that is performed on behalf of that RS-client. + +- Whenever it receives an announcement from a RS-client, + it must consider it for the Loc-RIBs of the other RS-clients. + + - This means that for each of them the route server must pass the + announcement through the appropriate `Out` filter of the + announcer. + + - Then through the appropriate `In` filter of the potential receiver. + + - Only if the announcement is accepted by both filters it will be passed + to the 'Best Path Selection' process. + + - Finally, it might go into the Loc-RIB of the receiver. + +When we talk about the 'appropriate' filter, both the announcer and the +receiver of the route must be taken into account. Suppose that the route server +receives an announcement from client A, and the route server is considering it +for the Loc-RIB of client B. The filters that should be applied are the same +that would be used in the full mesh scenario, i.e., first the `Out` filter of +router A for announcements going to router B, and then the `In` filter of +router B for announcements coming from router A. + +We call 'Export Policy' of a RS-client to the set of `Out` filters that the +client would use if there was no route server. The same applies for the 'Import +Policy' of a RS-client and the set of `In` filters of the client if there was +no route server. + +It is also common to demand from a route server that it does not modify some +BGP attributes (next-hop, as-path and MED) that are usually modified by +standard BGP speakers before announcing a route. + +The announcement processing model implemented by FRR is shown in +:ref:`fig-rs-processing`. The figure shows a mixture of RS-clients (B, C and D) +with normal BGP peers (A). There are some details that worth additional +comments: + +- Announcements coming from a normal BGP peer are also considered for the + Loc-RIBs of all the RS-clients. But logically they do not pass through any + export policy. + +- Those peers that are configured as RS-clients do not receive any announce + from the `Main` Loc-RIB. + +- Apart from import and export policies, `In` and `Out` filters can also be set + for RS-clients. `In` filters might be useful when the route server has also + normal BGP peers. On the other hand, `Out` filters for RS-clients are + probably unnecessary, but we decided not to remove them as they do not hurt + anybody (they can always be left empty). + +.. _fig-rs-processing: +.. figure:: ../figures/fig-rs-processing.png + :align: center + :alt: Route Server Processing Model + + Announcement processing model implemented by the Route Server + +.. _commands-for-configuring-a-route-server: + +Commands for configuring a Route Server +--------------------------------------- + +Now we will describe the commands that have been added to frr +in order to support the route server features. + +.. clicmd:: neighbor PEER-GROUP route-server-client + +.. clicmd:: neighbor A.B.C.D route-server-client + +.. clicmd:: neighbor X:X::X:X route-server-client + + This command configures the peer given by `peer`, `A.B.C.D` or `X:X::X:X` as + an RS-client. + + Actually this command is not new, it already existed in standard FRR. It + enables the transparent mode for the specified peer. This means that some + BGP attributes (as-path, next-hop and MED) of the routes announced to that + peer are not modified. + + With the route server patch, this command, apart from setting the + transparent mode, creates a new Loc-RIB dedicated to the specified peer + (those named `Loc-RIB for X` in :ref:`fig-rs-processing`.). Starting from + that moment, every announcement received by the route server will be also + considered for the new Loc-RIB. + +.. clicmd:: neigbor A.B.C.D|X.X::X.X|peer-group route-map WORD in|out + + This set of commands can be used to specify the route-map that represents + the Import or Export policy of a peer which is configured as a RS-client + (with the previous command). + +.. clicmd:: match peer A.B.C.D|X:X::X:X + + This is a new *match* statement for use in route-maps, enabling them to + describe import/export policies. As we said before, an import/export policy + represents a set of input/output filters of the RS-client. This statement + makes possible that a single route-map represents the full set of filters + that a BGP speaker would use for its different peers in a non-RS scenario. + + The *match peer* statement has different semantics whether it is used inside + an import or an export route-map. In the first case the statement matches if + the address of the peer who sends the announce is the same that the address + specified by {A.B.C.D|X:X::X:X}. For export route-maps it matches when + {A.B.C.D|X:X::X:X} is the address of the RS-Client into whose Loc-RIB the + announce is going to be inserted (how the same export policy is applied + before different Loc-RIBs is shown in :ref:`fig-rs-processing`.). + +.. clicmd:: call WORD + + This command (also used inside a route-map) jumps into a different + route-map, whose name is specified by `WORD`. When the called + route-map finishes, depending on its result the original route-map + continues or not. Apart from being useful for making import/export + route-maps easier to write, this command can also be used inside + any normal (in or out) route-map. + +.. _example-of-route-server-configuration: + +Example of Route Server Configuration +------------------------------------- + +Finally we are going to show how to configure a FRR daemon to act as a +Route Server. For this purpose we are going to present a scenario without +route server, and then we will show how to use the configurations of the BGP +routers to generate the configuration of the route server. + +All the configuration files shown in this section have been taken +from scenarios which were tested using the VNUML tool +`http://www.dit.upm.es/vnuml,VNUML `_. + +.. _configuration-of-the-bgp-routers-without-route-server: + +Configuration of the BGP routers without Route Server +----------------------------------------------------- + +We will suppose that our initial scenario is an exchange point with three +BGP capable routers, named RA, RB and RC. Each of the BGP speakers generates +some routes (with the `network` command), and establishes BGP peerings +against the other two routers. These peerings have In and Out route-maps +configured, named like 'PEER-X-IN' or 'PEER-X-OUT'. For example the +configuration file for router RA could be the following: + +.. code-block:: frr + + #Configuration for router 'RA' + ! + hostname RA + password **** + ! + router bgp 65001 + no bgp default ipv4-unicast + neighbor 2001:0DB8::B remote-as 65002 + neighbor 2001:0DB8::C remote-as 65003 + ! + address-family ipv6 + network 2001:0DB8:AAAA:1::/64 + network 2001:0DB8:AAAA:2::/64 + network 2001:0DB8:0000:1::/64 + network 2001:0DB8:0000:2::/64 + neighbor 2001:0DB8::B activate + neighbor 2001:0DB8::B soft-reconfiguration inbound + neighbor 2001:0DB8::B route-map PEER-B-IN in + neighbor 2001:0DB8::B route-map PEER-B-OUT out + neighbor 2001:0DB8::C activate + neighbor 2001:0DB8::C soft-reconfiguration inbound + neighbor 2001:0DB8::C route-map PEER-C-IN in + neighbor 2001:0DB8::C route-map PEER-C-OUT out + exit-address-family + ! + ipv6 prefix-list COMMON-PREFIXES seq 5 permit 2001:0DB8:0000::/48 ge 64 le 64 + ipv6 prefix-list COMMON-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-A-PREFIXES seq 5 permit 2001:0DB8:AAAA::/48 ge 64 le 64 + ipv6 prefix-list PEER-A-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-B-PREFIXES seq 5 permit 2001:0DB8:BBBB::/48 ge 64 le 64 + ipv6 prefix-list PEER-B-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-C-PREFIXES seq 5 permit 2001:0DB8:CCCC::/48 ge 64 le 64 + ipv6 prefix-list PEER-C-PREFIXES seq 10 deny any + ! + route-map PEER-B-IN permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set metric 100 + route-map PEER-B-IN permit 20 + match ipv6 address prefix-list PEER-B-PREFIXES + set community 65001:11111 + ! + route-map PEER-C-IN permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set metric 200 + route-map PEER-C-IN permit 20 + match ipv6 address prefix-list PEER-C-PREFIXES + set community 65001:22222 + ! + route-map PEER-B-OUT permit 10 + match ipv6 address prefix-list PEER-A-PREFIXES + ! + route-map PEER-C-OUT permit 10 + match ipv6 address prefix-list PEER-A-PREFIXES + ! + line vty + ! + + +.. _configuration-of-the-bgp-routers-with-route-server: + +Configuration of the BGP routers with Route Server +-------------------------------------------------- + +To convert the initial scenario into one with route server, first we must +modify the configuration of routers RA, RB and RC. Now they must not peer +between them, but only with the route server. For example, RA's +configuration would turn into: + +.. code-block:: frr + + # Configuration for router 'RA' + ! + hostname RA + password **** + ! + router bgp 65001 + no bgp default ipv4-unicast + neighbor 2001:0DB8::FFFF remote-as 65000 + ! + address-family ipv6 + network 2001:0DB8:AAAA:1::/64 + network 2001:0DB8:AAAA:2::/64 + network 2001:0DB8:0000:1::/64 + network 2001:0DB8:0000:2::/64 + + neighbor 2001:0DB8::FFFF activate + neighbor 2001:0DB8::FFFF soft-reconfiguration inbound + exit-address-family + ! + line vty + ! + + +Which is logically much simpler than its initial configuration, as it now +maintains only one BGP peering and all the filters (route-maps) have +disappeared. + +.. _configuration-of-the-route-server-itself: + +Configuration of the Route Server itself +---------------------------------------- + +As we said when we described the functions of a route server +(:ref:`description-of-the-route-server-model`), it is in charge of all the +route filtering. To achieve that, the In and Out filters from the RA, RB and RC +configurations must be converted into Import and Export policies in the route +server. + +This is a fragment of the route server configuration (we only show +the policies for client RA): + +.. code-block:: frr + + # Configuration for Route Server ('RS') + ! + hostname RS + password ix + ! + router bgp 65000 view RS + no bgp default ipv4-unicast + neighbor 2001:0DB8::A remote-as 65001 + neighbor 2001:0DB8::B remote-as 65002 + neighbor 2001:0DB8::C remote-as 65003 + ! + address-family ipv6 + neighbor 2001:0DB8::A activate + neighbor 2001:0DB8::A route-server-client + neighbor 2001:0DB8::A route-map RSCLIENT-A-IMPORT in + neighbor 2001:0DB8::A route-map RSCLIENT-A-EXPORT out + neighbor 2001:0DB8::A soft-reconfiguration inbound + + neighbor 2001:0DB8::B activate + neighbor 2001:0DB8::B route-server-client + neighbor 2001:0DB8::B route-map RSCLIENT-B-IMPORT in + neighbor 2001:0DB8::B route-map RSCLIENT-B-EXPORT out + neighbor 2001:0DB8::B soft-reconfiguration inbound + + neighbor 2001:0DB8::C activate + neighbor 2001:0DB8::C route-server-client + neighbor 2001:0DB8::C route-map RSCLIENT-C-IMPORT in + neighbor 2001:0DB8::C route-map RSCLIENT-C-EXPORT out + neighbor 2001:0DB8::C soft-reconfiguration inbound + exit-address-family + ! + ipv6 prefix-list COMMON-PREFIXES seq 5 permit 2001:0DB8:0000::/48 ge 64 le 64 + ipv6 prefix-list COMMON-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-A-PREFIXES seq 5 permit 2001:0DB8:AAAA::/48 ge 64 le 64 + ipv6 prefix-list PEER-A-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-B-PREFIXES seq 5 permit 2001:0DB8:BBBB::/48 ge 64 le 64 + ipv6 prefix-list PEER-B-PREFIXES seq 10 deny any + ! + ipv6 prefix-list PEER-C-PREFIXES seq 5 permit 2001:0DB8:CCCC::/48 ge 64 le 64 + ipv6 prefix-list PEER-C-PREFIXES seq 10 deny any + ! + route-map RSCLIENT-A-IMPORT permit 10 + match peer 2001:0DB8::B + call A-IMPORT-FROM-B + route-map RSCLIENT-A-IMPORT permit 20 + match peer 2001:0DB8::C + call A-IMPORT-FROM-C + ! + route-map A-IMPORT-FROM-B permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set metric 100 + route-map A-IMPORT-FROM-B permit 20 + match ipv6 address prefix-list PEER-B-PREFIXES + set community 65001:11111 + ! + route-map A-IMPORT-FROM-C permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set metric 200 + route-map A-IMPORT-FROM-C permit 20 + match ipv6 address prefix-list PEER-C-PREFIXES + set community 65001:22222 + ! + route-map RSCLIENT-A-EXPORT permit 10 + match peer 2001:0DB8::B + match ipv6 address prefix-list PEER-A-PREFIXES + route-map RSCLIENT-A-EXPORT permit 20 + match peer 2001:0DB8::C + match ipv6 address prefix-list PEER-A-PREFIXES + ! + ... + ... + ... + + +If you compare the initial configuration of RA with the route server +configuration above, you can see how easy it is to generate the Import and +Export policies for RA from the In and Out route-maps of RA's original +configuration. + +When there was no route server, RA maintained two peerings, one with RB and +another with RC. Each of this peerings had an In route-map configured. To +build the Import route-map for client RA in the route server, simply add +route-map entries following this scheme: + +:: + + route-map permit 10 + match peer + call + route-map permit 20 + match peer + call + + +This is exactly the process that has been followed to generate the route-map +RSCLIENT-A-IMPORT. The route-maps that are called inside it (A-IMPORT-FROM-B +and A-IMPORT-FROM-C) are exactly the same than the In route-maps from the +original configuration of RA (PEER-B-IN and PEER-C-IN), only the name is +different. + +The same could have been done to create the Export policy for RA (route-map +RSCLIENT-A-EXPORT), but in this case the original Out route-maps where so +simple that we decided not to use the `call WORD` commands, and we +integrated all in a single route-map (RSCLIENT-A-EXPORT). + +The Import and Export policies for RB and RC are not shown, but +the process would be identical. + +Further considerations about Import and Export route-maps +--------------------------------------------------------- + +The current version of the route server patch only allows to specify a +route-map for import and export policies, while in a standard BGP speaker +apart from route-maps there are other tools for performing input and output +filtering (access-lists, community-lists, ...). But this does not represent +any limitation, as all kinds of filters can be included in import/export +route-maps. For example suppose that in the non-route-server scenario peer +RA had the following filters configured for input from peer B: + +.. code-block:: frr + + neighbor 2001:0DB8::B prefix-list LIST-1 in + neighbor 2001:0DB8::B filter-list LIST-2 in + neighbor 2001:0DB8::B route-map PEER-B-IN in + ... + ... + route-map PEER-B-IN permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set local-preference 100 + route-map PEER-B-IN permit 20 + match ipv6 address prefix-list PEER-B-PREFIXES + set community 65001:11111 + + +It is possible to write a single route-map which is equivalent to the three +filters (the community-list, the prefix-list and the route-map). That route-map +can then be used inside the Import policy in the route server. Lets see how to +do it: + +.. code-block:: frr + + neighbor 2001:0DB8::A route-map RSCLIENT-A-IMPORT in + ... + ! + ... + route-map RSCLIENT-A-IMPORT permit 10 + match peer 2001:0DB8::B + call A-IMPORT-FROM-B + ... + ... + ! + route-map A-IMPORT-FROM-B permit 1 + match ipv6 address prefix-list LIST-1 + match as-path LIST-2 + on-match goto 10 + route-map A-IMPORT-FROM-B deny 2 + route-map A-IMPORT-FROM-B permit 10 + match ipv6 address prefix-list COMMON-PREFIXES + set local-preference 100 + route-map A-IMPORT-FROM-B permit 20 + match ipv6 address prefix-list PEER-B-PREFIXES + set community 65001:11111 + ! + ... + ... + + +The route-map A-IMPORT-FROM-B is equivalent to the three filters (LIST-1, +LIST-2 and PEER-B-IN). The first entry of route-map A-IMPORT-FROM-B (sequence +number 1) matches if and only if both the prefix-list LIST-1 and the +filter-list LIST-2 match. If that happens, due to the 'on-match goto 10' +statement the next route-map entry to be processed will be number 10, and as of +that point route-map A-IMPORT-FROM-B is identical to PEER-B-IN. If the first +entry does not match, `on-match goto 10`' will be ignored and the next +processed entry will be number 2, which will deny the route. + +Thus, the result is the same that with the three original filters, i.e., if +either LIST-1 or LIST-2 rejects the route, it does not reach the route-map +PEER-B-IN. In case both LIST-1 and LIST-2 accept the route, it passes to +PEER-B-IN, which can reject, accept or modify the route. diff --git a/doc/user/rpki.rst b/doc/user/rpki.rst new file mode 100644 index 0000000..98f9b10 --- /dev/null +++ b/doc/user/rpki.rst @@ -0,0 +1,322 @@ +.. _prefix-origin-validation-using-rpki: + +Prefix Origin Validation Using RPKI +=================================== + +Prefix Origin Validation allows BGP routers to verify if the origin AS of an IP +prefix is legitimate to announce this IP prefix. The required attestation +objects are stored in the Resource Public Key Infrastructure (:abbr:`RPKI`). +However, RPKI-enabled routers do not store cryptographic data itself but only +validation information. The validation of the cryptographic data (so called +Route Origin Authorization, or short :abbr:`ROA`, objects) will be performed by +trusted cache servers. The RPKI/RTR protocol defines a standard mechanism to +maintain the exchange of the prefix/origin AS mapping between the cache server +and routers. In combination with a BGP Prefix Origin Validation scheme a +router is able to verify received BGP updates without suffering from +cryptographic complexity. + +The RPKI/RTR protocol is defined in :rfc:`6810` and the validation scheme in +:rfc:`6811`. The current version of Prefix Origin Validation in FRR implements +both RFCs. + +For a more detailed but still easy-to-read background, we suggest: + +- [Securing-BGP]_ +- [Resource-Certification]_ + +.. _features-of-the-current-implementation: + +Features of the Current Implementation +-------------------------------------- + +In a nutshell, the current implementation provides the following features + +- The BGP router can connect to one or more RPKI cache servers to receive + validated prefix to origin AS mappings. Advanced failover can be implemented + by server sockets with different preference values. +- If no connection to an RPKI cache server can be established after a + pre-defined timeout, the router will process routes without prefix origin + validation. It still will try to establish a connection to an RPKI cache + server in the background. +- By default, enabling RPKI does not change best path selection. In particular, + invalid prefixes will still be considered during best path selection. + However, the router can be configured to ignore all invalid prefixes. +- Route maps can be configured to match a specific RPKI validation state. This + allows the creation of local policies, which handle BGP routes based on the + outcome of the Prefix Origin Validation. +- Updates from the RPKI cache servers are directly applied and path selection + is updated accordingly. (Soft reconfiguration **must** be enabled for this + to work). + + +.. _enabling-rpki: + +Enabling RPKI +------------- + +You must install ``frr-rpki-rtrlib`` additional package for RPKI support, +otherwise ``bgpd`` daemon won't startup. + +.. clicmd:: rpki + + This command enables the RPKI configuration mode. Most commands that start + with *rpki* can only be used in this mode. + + This command is available either in *configure node* for default *vrf* or + in *vrf node* for specific *vrf*. When it is used in a telnet session, + leaving of this mode cause rpki to be initialized. + + Executing this command alone does not activate prefix validation. You need + to configure at least one reachable cache server. See section + :ref:`configuring-rpki-rtr-cache-servers` for configuring a cache server. + +Remember to add ``-M rpki`` to the variable ``bgpd_options`` in +:file:`/etc/frr/daemons` , like so:: + + bgpd_options=" -A 127.0.0.1 -M rpki" + +instead of the default setting:: + + bgpd_options=" -A 127.0.0.1" + +Otherwise you will encounter an error when trying to enter RPKI +configuration mode due to the ``rpki`` module not being loaded when the BGP +daemon is initialized. + +Examples of the error:: + + router(config)# debug rpki + % [BGP] Unknown command: debug rpki + + router(config)# rpki + % [BGP] Unknown command: rpki + + router(config-vrf)# rpki + % [BGP] Unknown command: rpki + +Note that the RPKI commands will be available in vtysh when running +``find rpki`` regardless of whether the module is loaded. + +.. _configuring-rpki-rtr-cache-servers: + +Configuring RPKI/RTR Cache Servers +---------------------------------- + +RPKI/RTR can be configured independently, either in configure node, or in *vrf* +sub context. If configured in configure node, the core *bgp* instance of default +*vrf* is impacted by the configuration. + +Each RPKI/RTR context is mapped to a *vrf* and can be made up of a specific list +of cache-servers, and specific settings. + +The following commands are available for independent of a specific cache server. + +.. clicmd:: rpki polling_period (1-3600) + + Set the number of seconds the router waits until the router asks the cache + again for updated data. + + The default value is 300 seconds. + +.. clicmd:: rpki expire_interval (600-172800) + + Set the number of seconds the router waits until the router expires the cache. + + The default value is 7200 seconds. + +.. clicmd:: rpki retry_interval (1-7200) + + Set the number of seconds the router waits until retrying to connect to the + cache server. + + The default value is 600 seconds. + +.. clicmd:: rpki cache tcp HOST PORT [source A.B.C.D] preference (1-255) + + Add a TCP cache server to the socket. + +.. clicmd:: rpki cache ssh HOST PORT SSH_USERNAME SSH_PRIVKEY_PATH [KNOWN_HOSTS_PATH] [source A.B.C.D] preference (1-255) + + Add a SSH cache server to the socket. + + SSH_USERNAME + SSH username to establish an SSH connection to the cache server. + + SSH_PRIVKEY_PATH + Local path that includes the private key file of the router. + + KNOWN_HOSTS_PATH + Local path that includes the known hosts file. The default value depends + on the configuration of the operating system environment, usually + :file:`~/.ssh/known_hosts`. + + source A.B.C.D + Source address of the RPKI connection to access cache server. + +.. _validating-bgp-updates: + +Validating BGP Updates +---------------------- + +.. clicmd:: match rpki notfound|invalid|valid + + + Create a clause for a route map to match prefixes with the specified RPKI + state. + + In the following example, the router prefers valid routes over invalid + prefixes because invalid routes have a lower local preference. + + .. code-block:: frr + + ! Allow for invalid routes in route selection process + route bgp 65001 + ! + ! Set local preference of invalid prefixes to 10 + route-map rpki permit 10 + match rpki invalid + set local-preference 10 + ! + ! Set local preference of valid prefixes to 500 + route-map rpki permit 500 + match rpki valid + set local-preference 500 + +.. clicmd:: match rpki-extcommunity notfound|invalid|valid + + Create a clause for a route map to match prefixes with the specified RPKI + state, that is derived from the Origin Validation State extended community + attribute (OVS). OVS extended community is non-transitive and is exchanged + only between iBGP peers. + +.. _debugging: + +Debugging +--------- + +.. clicmd:: debug rpki + + + Enable or disable debugging output for RPKI. + +.. _displaying-rpki: + +Displaying RPKI +--------------- + +.. clicmd:: show rpki configuration [vrf NAME] [json] + + Display RPKI configuration state including timers values. + +.. clicmd:: show rpki prefix [ASN] [vrf NAME] [json] + + Display validated prefixes received from the cache servers filtered + by the specified prefix. The AS number space has been increased + to allow the choice of using AS 0 because RFC-7607 specifically + calls out the usage of 0 in a special case. + +.. clicmd:: show rpki as-number ASN [vrf NAME] [json] + + Display validated prefixes received from the cache servers filtered + by ASN. The usage of AS 0 is allowed because RFC-76067 specifically + calls out the usage of 0 in a special case. + +.. clicmd:: show rpki prefix-table [vrf NAME] [json] + + Display all validated prefix to origin AS mappings/records which have been + received from the cache servers and stored in the router. Based on this data, + the router validates BGP Updates. + +.. clicmd:: show rpki cache-server [vrf NAME] [json] + + Display all configured cache servers, whether active or not. + +.. clicmd:: show rpki cache-connection [vrf NAME] [json] + + Display all cache connections, and show which is connected or not. + +.. clicmd:: show bgp [vrf NAME] [afi] [safi] rpki + + Display for the specified prefix or address the bgp paths that match the given rpki state. + +.. clicmd:: show bgp [vrf NAME] [afi] [safi] rpki + + Display all prefixes that match the given rpki state. + +RPKI Configuration Example +-------------------------- + +.. code-block:: frr + + hostname bgpd1 + password zebra + ! log stdout + debug bgp updates + debug bgp keepalives + debug rpki + ! + vrf VRF1 + rpki + rpki polling_period 1000 + rpki timeout 10 + ! SSH Example: + rpki cache ssh example.com 22 rtr-ssh ./ssh_key/id_rsa preference 1 + ! TCP Example: + rpki cache tcp rpki-validator.realmv6.org 8282 preference 2 + exit + ! + exit-vrf + ! + rpki + rpki polling_period 1000 + rpki timeout 10 + ! SSH Example: + rpki cache ssh example.com source 198.51.100.223 22 rtr-ssh ./ssh_key/id_rsa preference 1 + ! TCP Example: + rpki cache tcp rpki-validator.realmv6.org 8282 preference 2 + exit + ! + router bgp 65001 + bgp router-id 198.51.100.223 + neighbor 203.0.113.1 remote-as 65002 + neighbor 203.0.113.1 update-source 198.51.100.223 + address-family ipv4 + network 192.0.2.0/24 + neighbor 203.0.113.1 route-map rpki in + exit-address-family + ! + address-family ipv6 + neighbor 203.0.113.1 activate + neighbor 203.0.113.1 route-map rpki in + exit-address-family + ! + router bgp 65001 vrf VRF1 + bgp router-id 198.51.100.223 + neighbor 203.0.113.1 remote-as 65002 + address-family ipv4 + network 192.0.2.0/24 + neighbor 203.0.113.1 route-map rpki in + exit-address-family + ! + address-family ipv6 + neighbor 203.0.113.1 activate + neighbor 203.0.113.1 route-map rpki in + exit-address-family + ! + route-map rpki permit 10 + match rpki invalid + set local-preference 10 + ! + route-map rpki permit 20 + match rpki notfound + set local-preference 20 + ! + route-map rpki permit 30 + match rpki valid + set local-preference 30 + ! + route-map rpki permit 40 + ! + +.. [Securing-BGP] Geoff Huston, Randy Bush: Securing BGP, In: The Internet Protocol Journal, Volume 14, No. 2, 2011. +.. [Resource-Certification] Geoff Huston: Resource Certification, In: The Internet Protocol Journal, Volume 12, No.1, 2009. diff --git a/doc/user/scripting.rst b/doc/user/scripting.rst new file mode 100644 index 0000000..42855de --- /dev/null +++ b/doc/user/scripting.rst @@ -0,0 +1,85 @@ +.. _scripting-user: + +********* +Scripting +********* + +The behavior of FRR may be extended or customized using its built-in scripting +capabilities. The scripting language is Lua 5.3. This guide assumes Lua +knowledge. For more information on Lua, consult the Lua 5.3 reference manual, or +*Programming in Lua* (note that the free version covers only Lua 5.0). + +https://www.lua.org/manual/5.3/ + +http://www.lua.org/pil/contents.html + +Scripting +========= + +.. seealso:: Developer docs for scripting + +How to use +---------- + +1. Identify the Lua function name. See :ref:`lua-hook-calls`. + +2. Write the Lua script + +3. Configure FRR to use the Lua script + +In order to use scripting, FRR must be built with ``--enable-scripting``. + +.. note:: + + Scripts are typically loaded just-in-time. This means you can change the + contents of a script that is in use without restarting FRR. Not all + scripting locations may behave this way; refer to the documentation for the + particular location. + + +Example: on_rib_process_dplane_results +-------------------------------------- + +This example shows how to write a Lua script that logs changes when a route is +added. + +First, identify the Lua hook call to attach a Lua function to: this will be the +name of the Lua function. In this case, since the hook call is +`on_rib_process_dplane_results`: + +.. code-block:: lua + + function on_rib_process_dplane_results(ctx) + log.info(ctx.rinfo.zd_dest.network) + return {} + + +The documentation for :ref:`on-rib-process-dplane-results` tells us its +arguments. Here, the destination prefix for a route is being logged out. + +Scripts live in :file:`/etc/frr/scripts/` by default. This is configurable at +compile time via ``--with-scriptdir``. It may be overridden at runtime with the +``--scriptdir`` daemon option. + +The documentation for :ref:`on-rib-process-dplane-results` indicates that the +``script`` command should be used to set the script. Assuming that the above +function was created in :file:`/etc/frr/scripts/my_dplane_script.lua`, the +following vtysh command sets the script for the hook call: + +.. code-block:: console + + script on_rib_process_dplane_results my_dplane_script + + +After the script is set, when the hook call is hit, FRR will look for a +*on_rib_process_dplane_results* function in +:file:`/etc/frr/scripts/my_dplane_script.lua` and run it with the ``ctx`` object +as its argument. + + +.. _lua-hook-calls: + +Available Lua hook calls +======================== + +:ref:`on-rib-process-dplane-results` diff --git a/doc/user/setup.rst b/doc/user/setup.rst new file mode 100644 index 0000000..372494f --- /dev/null +++ b/doc/user/setup.rst @@ -0,0 +1,325 @@ +.. _basic-setup: + +Basic Setup +============ + +After installing FRR, some basic configuration must be completed before it is +ready to use. + +Crash logs +---------- + +If any daemon should crash for some reason (segmentation fault, assertion +failure, etc.), it will attempt to write a backtrace to a file located in +:file:`/var/tmp/frr/[-]./crashlog`. This feature is +not affected by any configuration options. + +The crashlog file's directory also contains files corresponding to per-thread +message buffers in files named +:file:`/var/tmp/frr/[-]./logbuf.`. In case of a +crash, these may contain unwritten buffered log messages. To show the contents +of these buffers, pipe their contents through ``tr '\0' '\n'``. A blank line +marks the end of valid unwritten data (it will generally be followed by +garbled, older log messages since the buffer is not cleared.) + +.. _daemons-configuration-file: + +Daemons Configuration File +-------------------------- +After a fresh install, starting FRR will do nothing. This is because daemons +must be explicitly enabled by editing a file in your configuration directory. +This file is usually located at :file:`/etc/frr/daemons` and determines which +daemons are activated when issuing a service start / stop command via init or +systemd. The file initially looks like this: + +:: + + zebra=no + bgpd=no + ospfd=no + ospf6d=no + ripd=no + ripngd=no + isisd=no + pimd=no + ldpd=no + nhrpd=no + eigrpd=no + babeld=no + sharpd=no + staticd=no + pbrd=no + bfdd=no + fabricd=no + + # + # If this option is set the /etc/init.d/frr script automatically loads + # the config via "vtysh -b" when the servers are started. + # Check /etc/pam.d/frr if you intend to use "vtysh"! + # + vtysh_enable=yes + zebra_options=" -s 90000000 --daemon -A 127.0.0.1" + bgpd_options=" --daemon -A 127.0.0.1" + ospfd_options=" --daemon -A 127.0.0.1" + ospf6d_options=" --daemon -A ::1" + ripd_options=" --daemon -A 127.0.0.1" + ripngd_options=" --daemon -A ::1" + isisd_options=" --daemon -A 127.0.0.1" + pimd_options=" --daemon -A 127.0.0.1" + ldpd_options=" --daemon -A 127.0.0.1" + nhrpd_options=" --daemon -A 127.0.0.1" + eigrpd_options=" --daemon -A 127.0.0.1" + babeld_options=" --daemon -A 127.0.0.1" + sharpd_options=" --daemon -A 127.0.0.1" + staticd_options=" --daemon -A 127.0.0.1" + pbrd_options=" --daemon -A 127.0.0.1" + bfdd_options=" --daemon -A 127.0.0.1" + fabricd_options=" --daemon -A 127.0.0.1" + + #MAX_FDS=1024 + # The list of daemons to watch is automatically generated by the init script. + #watchfrr_options="" + + # for debugging purposes, you can specify a "wrap" command to start instead + # of starting the daemon directly, e.g. to use valgrind on ospfd: + # ospfd_wrap="/usr/bin/valgrind" + # or you can use "all_wrap" for all daemons, e.g. to use perf record: + # all_wrap="/usr/bin/perf record --call-graph -" + # the normal daemon command is added to this at the end. + +Breaking this file down: + +:: + + bgpd=yes + +To enable a particular daemon, simply change the corresponding 'no' to 'yes'. +Subsequent service restarts should start the daemon. + +:: + + vtysh_enable=yes + +As the comment says, this causes :ref:`VTYSH ` to apply +configuration when starting the daemons. This is useful for a variety of +reasons touched on in the VTYSH documentation and should generally be enabled. + +:: + + MAX_FDS=1024 + +This allows the operator to control the number of open file descriptors +each daemon is allowed to start with. The current assumed value on +most operating systems is 1024. If the operator plans to run bgp with +several thousands of peers then this is where we would modify FRR to +allow this to happen. + +:: + + FRR_NO_ROOT="yes" + +This option allows you to run FRR as a non-root user. Use this option +only when you know what you are doing since most of the daemons +in FRR will not be able to run under a regular user. This option +is useful for example when you run FRR in a container with a designated +user instead of root. + +:: + + zebra_options=" -s 90000000 --daemon -A 127.0.0.1" + bgpd_options=" --daemon -A 127.0.0.1" + ... + +The next set of lines controls what options are passed to daemons when started +from the service script. Usually daemons will have ``--daemon`` and ``-A +
`` specified in order to daemonize and listen for VTY commands on a +particular address. + +The remaining file content regarding `watchfrr_options` and `*_wrap` settings +should not normally be needed; refer to the comments in case they are. + +Services +-------- +FRR daemons have their own terminal interface or VTY. After installation, it's +a good idea to setup each daemon's port number to connect to them. To do this +add the following entries to :file:`/etc/services`. + +:: + + zebrasrv 2600/tcp # zebra service + zebra 2601/tcp # zebra vty + ripd 2602/tcp # RIPd vty + ripngd 2603/tcp # RIPngd vty + ospfd 2604/tcp # OSPFd vty + bgpd 2605/tcp # BGPd vty + ospf6d 2606/tcp # OSPF6d vty + ospfapi 2607/tcp # ospfapi + isisd 2608/tcp # ISISd vty + babeld 2609/tcp # BABELd vty + nhrpd 2610/tcp # nhrpd vty + pimd 2611/tcp # PIMd vty + ldpd 2612/tcp # LDPd vty + eigprd 2613/tcp # EIGRPd vty + bfdd 2617/tcp # bfdd vty + fabricd 2618/tcp # fabricd vty + vrrpd 2619/tcp # vrrpd vty + + +If you use a FreeBSD newer than 2.2.8, the above entries are already added to +:file:`/etc/services` so there is no need to add it. If you specify a port +number when starting the daemon, these entries may not be needed. + +You may need to make changes to the config files in |INSTALL_PREFIX_ETC|. + +Systemd +------- +Although not installed when installing from source, FRR provides a service file +for use with ``systemd``. It is located in :file:`tools/frr.service` in the Git +repository. If ``systemctl status frr.service`` indicates that the FRR service +is not found, copy the service file from the Git repository into your preferred +location. A good place is usually ``/etc/systemd/system/``. + +After issuing a ``systemctl daemon-reload``, you should be able to start the +FRR service via ``systemctl start frr``. If this fails, or no daemons are +started. check the ``journalctl`` logs for an indication of what went wrong. + +Operations +---------- + +This section covers a few common operational tasks and how to perform them. + +Interactive Shell +^^^^^^^^^^^^^^^^^ +FRR offers an IOS-like interactive shell called ``vtysh`` where a user can run +individual configuration or show commands. To get into this shell, issue the +``vtysh`` command from either a privilege user (root, or with sudo) or a user +account that is part of the ``frrvty`` group. +e.g. + +.. code-block:: console + + root@ub18:~# vtysh + + Hello, this is FRRouting (version 8.1-dev). + Copyright 1996-2005 Kunihiro Ishiguro, et al. + + ub18# + +.. note:: + The default install location for vtysh is /usr/bin/vtysh + + +Restarting +^^^^^^^^^^ + +Restarting kills all running FRR daemons and starts them again. Any unsaved +configuration will be lost. + +.. code-block:: console + + service frr restart + +.. note:: + + Alternatively, you can invoke the init script directly:: + + /etc/init.d/frr restart + + Or, if using systemd:: + + systemctl restart frr + +Reloading +^^^^^^^^^ + +Reloading applies the differential between on-disk configuration and the +current effective configuration of running FRR processes. This includes +starting daemons that were previously stopped and any changes made to +individual or unified daemon configuration files. + +.. code-block:: console + + service frr reload + +.. note:: + + Alternatively, you can invoke the init script directly:: + + /etc/init.d/frr reload + + Or, if using systemd:: + + systemctl reload frr + +See :ref:`FRR-RELOAD ` for more about the `frr-reload.py` script. + + +Starting a new daemon +^^^^^^^^^^^^^^^^^^^^^ + +Suppose *bgpd* and *zebra* are running, and you wish to start *pimd*. In +``/etc/frr/daemons`` make the following change: + +.. code-block:: diff + + - pimd=no + + pimd=yes + +Then perform a reload. + +Currently there is no way to stop or restart an individual daemon. This is +because FRR's monitoring program cannot currently distinguish between a crashed +/ killed daemon versus one that has been intentionally stopped or restarted. +The closest that can be achieved is to remove all configuration for the daemon, +and set its line in ``/etc/frr/daemons`` to ``=no``. Once this is done, the +daemon will be stopped the next time FRR is restarted. + + +Network Namespaces +^^^^^^^^^^^^^^^^^^ + +It is possible to run FRR in different network namespaces so it can be +further compartmentalized (e.g. confining to a smaller subset network). +The network namespace configuration can be used in the default FRR +configuration pathspace or it can be used in a different pathspace +(`-N/--pathspace`). + +To use FRR network namespace in the default pathspace you should add +or uncomment the ``watchfrr_options`` line in ``/etc/frr/daemons``: + +.. code-block:: diff + + - #watchfrr_options="--netns" + + watchfrr_options="--netns=" + +If you want to use a different pathspace with the network namespace +(the recommended way) you should add/uncomment the ``watchfrr_options`` +line in ``/etc/frr//daemons``: + +.. code-block:: diff + + - #watchfrr_options="--netns" + + #watchfrr_options="--netns=" + + + + # `--netns` argument is optional and if not provided it will + + # default to the pathspace name. + + watchfrr_options="--netns" + +To start FRR in the new pathspace+network namespace the initialization script +should be called with an extra parameter: + + +.. code:: + + /etc/init.d/frr start + + +.. note:: + + Some Linux distributions might not use the default init script + shipped with FRR, in that case you might want to try running the + bundled script in ``/usr/lib/frr/frrinit.sh``. + + On systemd you might create different units or parameterize the + existing one. See the man page: + https://www.freedesktop.org/software/systemd/man/systemd.unit.html diff --git a/doc/user/sharp.rst b/doc/user/sharp.rst new file mode 100644 index 0000000..2be38a3 --- /dev/null +++ b/doc/user/sharp.rst @@ -0,0 +1,306 @@ +.. _sharp: + +***** +SHARP +***** + +:abbr:`SHARP (Super Happy Advanced Routing Process)` is a daemon that provides +miscellaneous functionality used for testing FRR and creating proof-of-concept +labs. + +.. _starting-sharp: + +Starting SHARP +============== + +.. include:: config-include.rst + +.. program:: sharpd + +:abbr:`SHARP` supports all the common FRR daemon start options which are +documented elsewhere. + +.. _using-sharp: + +Using SHARP +=========== + +All sharp commands are under the enable node and preceded by the ``sharp`` +keyword. At present, no sharp commands will be preserved in the config. + +.. clicmd:: sharp install routes A.B.C.D |nexthop-group NAME> (1-1000000) [instance (0-255)] [repeat (2-1000)] [opaque WORD] + + Install up to 1,000,000 (one million) /32 routes starting at ``A.B.C.D`` + with specified nexthop ``E.F.G.H`` or ``X:X::X:X``. The nexthop is + a ``NEXTHOP_TYPE_IPV4`` or ``NEXTHOP_TYPE_IPV6`` and must be reachable + to be installed into the kernel. Alternatively a nexthop-group NAME + can be specified and used as the nexthops. The routes are installed into + zebra as ``ZEBRA_ROUTE_SHARP`` and can be used as part of a normal route + redistribution. Route installation time is noted in the debug + log. When zebra successfully installs a route into the kernel and SHARP + receives success notifications for all routes this is logged as well. + Instance (0-255) if specified causes the routes to be installed in a different + instance. If repeat is used then we will install/uninstall the routes the + number of times specified. If the keyword opaque is specified then the + next word is sent down to zebra as part of the route installation. + +.. clicmd:: sharp remove routes A.B.C.D (1-1000000) + + Remove up to 1,000,000 (one million) /32 routes starting at ``A.B.C.D``. The + routes are removed from zebra. Route deletion start is noted in the debug + log and when all routes have been successfully deleted the debug log will be + updated with this information as well. + +.. clicmd:: sharp data route + + Allow end user doing route install and deletion to get timing information + from the vty or vtysh instead of having to read the log file. This command + is informational only and you should look at sharp_vty.c for explanation + of the output as that it may change. + +.. clicmd:: sharp label vrf NAME label (0-1000000) + + Install a label into the kernel that causes the specified vrf NAME table to + be used for pop and forward operations when the specified label is seen. + +.. clicmd:: sharp watch [vrf VRF_NAME] neighbor + + Instruct zebra to notify sharpd about neighbor events in the specified vrf. + If no vrf is specified then assume default. + +.. clicmd:: sharp watch |import [connected] + + Instruct zebra to monitor and notify sharp when the specified nexthop is + changed. The notification from zebra is written into the debug log. + The nexthop or import choice chooses the type of nexthop we are asking + zebra to watch for us. This choice affects zebra's decision on what + matches. Connected tells zebra whether or not that we want the route + matched against to be a static or connected route for the nexthop keyword, + for the import keyword connected means exact match. The no form of + the command obviously turns this watching off. + +.. clicmd:: sharp data nexthop + + Allow end user to dump associated data with the nexthop tracking that + may have been turned on. + +.. clicmd:: sharp watch [vrf NAME] redistribute ROUTETYPE + + Allow end user to monitor redistributed routes of ROUTETYPE + origin. + +.. clicmd:: sharp lsp [update] (0-100000) nexthop-group NAME [prefix A.B.C.D/M TYPE [instance (0-255)]] + + Install an LSP using the specified in-label, with nexthops as + listed in nexthop-group ``NAME``. If ``update`` is included, the + update path is used. The LSP is installed as type ZEBRA_LSP_SHARP. + If ``prefix`` is specified, an existing route with type ``TYPE`` + (and optional ``instance`` id) will be updated to use the LSP. + +.. clicmd:: sharp remove lsp (0-100000) nexthop-group NAME [prefix A.B.C.D/M TYPE [instance (0-255)]] + + Remove a SHARPD LSP that uses the specified in-label, where the + nexthops are specified in nexthop-group ``NAME``. If ``prefix`` is + specified, remove label bindings from the route of type ``TYPE`` + also. + +.. clicmd:: sharp send opaque type (1-255) (1-1000) + + Send opaque ZAPI messages with subtype ``type``. Sharpd will send + a stream of messages if the count is greater than one. + +.. clicmd:: sharp send opaque unicast type (1-255) PROTOCOL [{instance (0-1000) | session (1-1000)}] (1-1000) + + Send unicast opaque ZAPI messages with subtype ``type``. The + protocol, instance, and session_id identify a single target zapi + client. Sharpd will send a stream of messages if the count is + greater than one. + +.. clicmd:: sharp send opaque PROTOCOL [{instance (0-1000) | session (1-1000)}] type (1-1000) + + Send opaque ZAPI registration and unregistration messages for a + single subtype. The messages must specify a protocol daemon by + name, and can include optional zapi ``instance`` and ``session`` + values. + +.. clicmd:: sharp create session (1-1024) + + Create an additional zapi client session for testing, using the + specified session id. + +.. clicmd:: sharp remove session (1-1024) + + Remove a test zapi client session that was created with the + specified session id. + +.. clicmd:: sharp neigh discover [vrf NAME] IFNAME + + Send an ARP/NDP request to trigger the addition of a neighbor in the ARP + table. + +.. clicmd:: sharp import-te + + Import Traffic Engineering Database produced by OSPF or IS-IS. + +.. clicmd:: show sharp ted [verbose|json] + +.. clicmd:: show sharp ted [] [verbose|json] + + Show imported Traffic Engineering Data Base + +.. clicmd:: show sharp cspf source destination (0-16777215) [rsv-bw (0-7) BANDWIDTH] + + Show the result of a call to the Constraint Shortest Path First (CSPF) + algorithm that allows to compute a path between a source and a + destination under various constraints. Standard Metric, TE Metric, Delay + and Bandwidth are supported constraints. Prior to use this function, it is + necessary to import a Traffic Engineering Database with `sharp import-te` + command (see above). + +.. clicmd:: sharp install seg6-routes [vrf NAME] nexthop-seg6 X:X::X:X encap X:X::X:X (1-1000000) + + This command installs a route for SRv6 Transit behavior (on Linux it is + known as seg6 route). The count, destination, vrf, etc. have the same + meaning as in the ``sharp install routes`` command. With this command, + sharpd will request zebra to configure seg6 route via ZEBRA_ROUTE_ADD + ZAPI. As in the following example. + +:: + + router# sharp install seg6-routes 1::A nexthop-seg6 2001::2 encap A:: 1 + router# sharp install seg6-routes 1::B nexthop-seg6 2001::2 encap B:: 1 + + router# show ipv6 route + D>* 1::A/128 [150/0] via 2001::2, dum0, seg6 a::, weight 1, 00:00:01 + D>* 1::B/128 [150/0] via 2001::2, dum0, seg6 b::, weight 1, 00:00:01 + + bash# ip -6 route list + 1::A encap seg6 mode encap segs 1 [ a:: ] via 2001::2 dev dum0 proto 194 metric 20 pref medium + 1::B encap seg6 mode encap segs 1 [ b:: ] via 2001::2 dev dum0 proto 194 metric 20 pref medium + +.. clicmd:: sharp install seg6local-routes [vrf NAME] X:X::X:X nexthop-seg6local NAME ACTION ARGS.. (1-1000000) + + This command installs a route for SRv6 Endpoint behavior (on Linux it is + known as seg6local route). The count, destination, vrf, etc. have the same + meaning as in the ``sharp install routes`` command. With this command, + sharpd will request zebra to configure seg6local route via ZEBRA_ROUTE_ADD + ZAPI. As in the following example. + + There are many End Functions defined in SRv6, which have been standardized + in RFC 8986. The current implementation supports End, End.X, End.T, End.DX4, + End.DT6 and End.DT46, which can be configured as follows. + +:: + + router# sharp install seg6local-routes 1::1 nexthop-seg6local dum0 End 1 + router# sharp install seg6local-routes 1::2 nexthop-seg6local dum0 End_X 2001::1 1 + router# sharp install seg6local-routes 1::3 nexthop-seg6local dum0 End_T 10 1 + router# sharp install seg6local-routes 1::4 nexthop-seg6local dum0 End_DX4 10.0.0.1 1 + router# sharp install seg6local-routes 1::5 nexthop-seg6local dum0 End_DT6 10 1 + router# sharp install seg6local-routes 1::6 nexthop-seg6local dum0 End_DT46 10 1 + + router# show ipv6 route + D>* 1::1/128 [150/0] is directly connected, dum0, seg6local End USP, weight 1, 00:00:05 + D>* 1::2/128 [150/0] is directly connected, dum0, seg6local End.X nh6 2001::1, weight 1, 00:00:05 + D>* 1::3/128 [150/0] is directly connected, dum0, seg6local End.T table 10, weight 1, 00:00:05 + D>* 1::4/128 [150/0] is directly connected, dum0, seg6local End.DX4 nh4 10.0.0.1, weight 1, 00:00:05 + D>* 1::5/128 [150/0] is directly connected, dum0, seg6local End.DT6 table 10, weight 1, 00:00:05 + D>* 1::6/128 [150/0] is directly connected, dum0, seg6local End.DT46 table 10, weight 1, 00:00:05 + + bash# ip -6 route + 1::1 encap seg6local action End dev dum0 proto 194 metric 20 pref medium + 1::2 encap seg6local action End.X nh6 2001::1 dev dum0 proto 194 metric 20 pref medium + 1::3 encap seg6local action End.T table 10 dev dum0 proto 194 metric 20 pref medium + 1::4 encap seg6local action End.DX4 nh4 10.0.0.1 dev dum0 proto 194 metric 20 pref medium + 1::5 encap seg6local action End.DT6 table 10 dev dum0 proto 194 metric 20 pref medium + 1::6 encap seg6local action End.DT46 table 10 dev dum0 proto 194 metric 20 pref medium + +.. clicmd:: show sharp segment-routing srv6 + + This command shows us what SRv6 locator chunk, sharp is holding as zclient. + An SRv6 locator is defined for each SRv6 router, and a single locator may + be shared by multiple protocols. + + In the FRRouting implementation, the Locator chunk get request is executed + by a routing protocol daemon such as sharpd or bgpd, And then Zebra + allocates a Locator Chunk, which is a subset of the Locator Prefix, and + notifies the requesting protocol daemon of this information. + + This command example shows how the locator chunk of sharpd itself is + allocated. + +:: + + router# show segment-routing srv6 locator + Locator: + Name ID 2 2001:db8:2:2::/64 Up + + router# show sharp segment-routing srv6 + Locator loc1 has 1 prefix chunks + 2001:db8:1:1::/64 + +.. clicmd:: sharp srv6-manager get-locator-chunk + + This command requests the SRv6 locator to allocate a locator chunk via ZAPI. + This chunk can be owned by the protocol daemon, and the chunk obtained by + sharpd will not be used by the SRv6 mechanism of another routing protocol. + + Since this request is made asynchronously, it can be issued before the SRv6 + locator is configured on the zebra side, and as soon as it is ready on the + zebra side, sharpd can check the allocated locator chunk via zapi. + +:: + + router# show segment-routing srv6 locator loc1 detail + Name: loc1 + Prefix: 2001:db8:1:1::/64 + Chunks: + - prefix: 2001:db8:1:1::/64, owner: system + + router# show sharp segment-routing srv6 + (nothing) + + router# sharp srv6-manager get-locator-chunk loc1 + + router# show segment-routing srv6 locator loc1 detail + Name: loc1 + Prefix: 2001:db8:1:1::/64 + Chunks: + - prefix: 2001:db8:1:1::/64, owner: sharp + + router# show sharp segment-routing srv6 + Locator loc1 has 1 prefix chunks + 2001:db8:1:1::/64 + +.. clicmd:: sharp srv6-manager release-locator-chunk + + This command releases a locator chunk that has already been allocated by + ZAPI. The freed chunk will have its owner returned to the system and will + be available to another protocol daemon. + +:: + + router# show segment-routing srv6 locator loc1 detail + Name: loc1 + Prefix: 2001:db8:1:1::/64 + Chunks: + - prefix: 2001:db8:1:1::/64, owner: sharp + + router# show sharp segment-routing srv6 + Locator loc1 has 1 prefix chunks + 2001:db8:1:1::/64 + + router# sharp srv6-manager release-locator-chunk loc1 + + router# show segment-routing srv6 locator loc1 detail + Name: loc1 + Prefix: 2001:db8:1:1::/64 + Chunks: + - prefix: 2001:db8:1:1::/64, owner: system + + router# show sharp segment-routing srv6 + (nothing) + +.. clicmd:: sharp interface IFNAME protodown + + Set an interface protodown. diff --git a/doc/user/snmp.rst b/doc/user/snmp.rst new file mode 100644 index 0000000..3c2d11a --- /dev/null +++ b/doc/user/snmp.rst @@ -0,0 +1,264 @@ +.. _snmp-support: + +************ +SNMP Support +************ + +:abbr:`SNMP (Simple Network Managing Protocol)` is a widely implemented feature +for collecting network information from router and/or host. FRR itself does +not support SNMP agent (server daemon) functionality but is able to connect to +a SNMP agent using the the AgentX protocol (:rfc:`2741`) and make the +routing protocol MIBs available through it. + +Note that SNMP Support needs to be enabled at compile-time and loaded as module +on daemon startup. Refer to :ref:`loadable-module-support` on the latter. If +you do not start the daemons with snmp module support snmp will not work +properly. + +.. _getting-and-installing-an-snmp-agent: + +Getting and installing an SNMP agent +==================================== + +The supported SNMP agent is AgentX. We recommend to use +the latest version of `net-snmp` which was formerly known as `ucd-snmp`. It is +free and open software and available at `http://www.net-snmp.org/ `_ +and as binary package for most Linux distributions. + +.. _net-smtp-configuration: + +NET-SNMP configuration +====================== + +Routers with a heavy amount of routes (e.g. BGP full table) might experience +problems with a hanging vtysh from time to time, 100% CPU on the snmpd or +even crashes of the frr daemon(s) due to stalls within AgentX. Once snmp +agents connects they start receiving a heavy amount of SNMP data (all the +routes) which cannot be handled quick enough. It's recommended (by several +vendors as well) to exclude these OID's unless you really need them, which +can be achieved by amending the default view from SNMP + +:file:`/etc/snmp/snmpd.conf`: + +:: + + # This is the default view + view all included .1 80 + # Remove ipRouteTable from view + view all excluded .1.3.6.1.2.1.4.21 + # Remove ipNetToMediaTable from view + view all excluded .1.3.6.1.2.1.4.22 + # Remove ipNetToPhysicalPhysAddress from view + view all excluded .1.3.6.1.2.1.4.35 + # Remove ipCidrRouteTable from view + view all excluded .1.3.6.1.2.1.4.24 + # Optionally protect SNMP private/secret values + view all excluded .1.3.6.1.6.3.15 + view all excluded .1.3.6.1.6.3.16 + view all excluded .1.3.6.1.6.3.18 + # Optionally allow SNMP public info (sysName, location, etc) + view system included .iso.org.dod.internet.mgmt.mib-2.system + + +.. _agentx-configuration: + +AgentX configuration +==================== + +.. program:: configure + +To enable AgentX protocol support, FRR must have been build with the +:option:`--enable-snmp` or `--enable-snmp=agentx` option. Both the +master SNMP agent (snmpd) and each of the FRR daemons must be configured. In +:file:`/etc/snmp/snmpd.conf`, the ``master agentx`` directive should be added. +In each of the FRR daemons, ``agentx`` command will enable AgentX support. + +:file:`/etc/snmp/zebra.conf`: + +:: + + # + # example access restrictions setup + # + com2sec readonly default public + group MyROGroup v1 readonly + view all included .1 80 + access MyROGroup "" any noauth exact all none none + # + # enable master agent for AgentX subagents + # + master agentx + +:file:`/etc/frr/ospfd.conf:` + + .. code-block:: frr + + ! ... the rest of ospfd.conf has been omitted for clarity ... + ! + agentx + ! + + +Upon successful connection, you should get something like this in the log of +each FRR daemons: + +:: + + 2012/05/25 11:39:08 ZEBRA: snmp[info]: NET-SNMP version 5.4.3 AgentX subagent connected + + +Then, you can use the following command to check everything works as expected: + +:: + + # snmpwalk -c public -v1 localhost .1.3.6.1.2.1.14.1.1 + OSPF-MIB::ospfRouterId.0 = IpAddress: 192.168.42.109 + [...] + +An example below is how to query SNMP for BGP: + + .. code-block:: shell + + $ # BGP4-MIB (https://www.circitor.fr/Mibs/Mib/B/BGP4-MIB.mib) + $ snmpwalk -c public -v2c -On -Ln localhost .1.3.6.1.2.1.15 + + $ # BGP4V2-MIB (http://www.circitor.fr/Mibs/Mib/B/BGP4V2-MIB.mib) + $ # Information about the peers (bgp4V2PeerTable): + $ snmpwalk -c public -v2c -On -Ln localhost .1.3.6.1.3.5.1.1.2 + ... + .1.3.6.1.3.5.1.1.2.1.1.1.1.192.168.10.124 = Gauge32: 0 + .1.3.6.1.3.5.1.1.2.1.1.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Gauge32: 0 + .1.3.6.1.3.5.1.1.2.1.2.1.1.192.168.10.124 = INTEGER: 1 + .1.3.6.1.3.5.1.1.2.1.2.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = INTEGER: 2 + .1.3.6.1.3.5.1.1.2.1.3.1.1.192.168.10.124 = Hex-STRING: C0 A8 0A 11 + .1.3.6.1.3.5.1.1.2.1.3.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Hex-STRING: 2A 02 47 80 0A BC 00 00 00 00 00 00 00 00 00 01 + .1.3.6.1.3.5.1.1.2.1.4.1.1.192.168.10.124 = INTEGER: 1 + .1.3.6.1.3.5.1.1.2.1.4.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = INTEGER: 2 + .1.3.6.1.3.5.1.1.2.1.5.1.1.192.168.10.124 = Hex-STRING: C0 A8 0A 7C + .1.3.6.1.3.5.1.1.2.1.5.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Hex-STRING: 2A 02 47 80 0A BC 00 00 00 00 00 00 00 00 00 02 + .1.3.6.1.3.5.1.1.2.1.6.1.1.192.168.10.124 = Gauge32: 179 + .1.3.6.1.3.5.1.1.2.1.6.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Gauge32: 179 + .1.3.6.1.3.5.1.1.2.1.7.1.1.192.168.10.124 = Gauge32: 65002 + .1.3.6.1.3.5.1.1.2.1.7.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Gauge32: 65002 + .1.3.6.1.3.5.1.1.2.1.8.1.1.192.168.10.124 = Hex-STRING: C0 A8 0A 11 + .1.3.6.1.3.5.1.1.2.1.8.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Hex-STRING: C0 A8 0A 11 + .1.3.6.1.3.5.1.1.2.1.9.1.1.192.168.10.124 = Gauge32: 41894 + .1.3.6.1.3.5.1.1.2.1.9.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Gauge32: 39960 + .1.3.6.1.3.5.1.1.2.1.10.1.1.192.168.10.124 = Gauge32: 65001 + .1.3.6.1.3.5.1.1.2.1.10.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Gauge32: 65001 + .1.3.6.1.3.5.1.1.2.1.11.1.1.192.168.10.124 = Hex-STRING: C8 C8 C8 CA + .1.3.6.1.3.5.1.1.2.1.11.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = Hex-STRING: C8 C8 C8 CA + .1.3.6.1.3.5.1.1.2.1.12.1.1.192.168.10.124 = INTEGER: 2 + .1.3.6.1.3.5.1.1.2.1.12.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = INTEGER: 2 + .1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.10.124 = INTEGER: 6 + .1.3.6.1.3.5.1.1.2.1.13.1.2.42.2.71.128.10.188.0.0.0.0.0.0.0.0.0.2 = INTEGER: 6 + + $ # Information about the BGP table (bgp4V2NlriTable): + $ snmpwalk -c public -v2c -On -Ln localhost .1.3.6.1.3.5.1.1.9 + ... + .1.3.6.1.3.5.1.1.9.1.1.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.1.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.1.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.1.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.2.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.2.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.2.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 2 + .1.3.6.1.3.5.1.1.9.1.2.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 2 + .1.3.6.1.3.5.1.1.9.1.3.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.3.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.3.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.3.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.4.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.4.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.4.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 2 + .1.3.6.1.3.5.1.1.9.1.4.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 2 + .1.3.6.1.3.5.1.1.9.1.5.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Hex-STRING: 0A 00 00 00 + .1.3.6.1.3.5.1.1.9.1.5.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Hex-STRING: 0A 00 00 02 + .1.3.6.1.3.5.1.1.9.1.5.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 20 01 0D B8 00 00 00 00 00 00 00 00 00 00 00 01 + .1.3.6.1.3.5.1.1.9.1.5.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 20 01 0D B8 00 01 00 00 00 00 00 00 00 00 00 00 + .1.3.6.1.3.5.1.1.9.1.6.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 31 + .1.3.6.1.3.5.1.1.9.1.6.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 32 + .1.3.6.1.3.5.1.1.9.1.6.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 128 + .1.3.6.1.3.5.1.1.9.1.6.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 56 + .1.3.6.1.3.5.1.1.9.1.7.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.7.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.7.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.7.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.8.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.8.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.8.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.8.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 3 + .1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 3 + .1.3.6.1.3.5.1.1.9.1.10.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.10.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.10.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 4 + .1.3.6.1.3.5.1.1.9.1.10.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 4 + .1.3.6.1.3.5.1.1.9.1.11.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Hex-STRING: C0 A8 0C 01 + .1.3.6.1.3.5.1.1.9.1.11.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Hex-STRING: C0 A8 0C 01 + .1.3.6.1.3.5.1.1.9.1.11.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: FE 80 00 00 00 00 00 00 30 39 84 FF FE 9A 24 2B + .1.3.6.1.3.5.1.1.9.1.11.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: FE 80 00 00 00 00 00 00 30 39 84 FF FE 9A 24 2B + .1.3.6.1.3.5.1.1.9.1.14.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 0 + .1.3.6.1.3.5.1.1.9.1.14.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 0 + .1.3.6.1.3.5.1.1.9.1.14.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + .1.3.6.1.3.5.1.1.9.1.14.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + .1.3.6.1.3.5.1.1.9.1.15.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.15.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.15.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.15.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + .1.3.6.1.3.5.1.1.9.1.16.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.16.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.16.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + .1.3.6.1.3.5.1.1.9.1.16.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 1 + 1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 1 + 1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 2 + 1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 1 + 1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 2 + 1.3.6.1.3.5.1.1.9.1.18.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.18.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.18.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.18.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.19.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.19.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.19.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.19.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = INTEGER: 0 + 1.3.6.1.3.5.1.1.9.1.20.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 0 + 1.3.6.1.3.5.1.1.9.1.20.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 0 + 1.3.6.1.3.5.1.1.9.1.20.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + 1.3.6.1.3.5.1.1.9.1.20.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 0 + 1.3.6.1.3.5.1.1.9.1.21.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Hex-STRING: 00 00 00 00 + 1.3.6.1.3.5.1.1.9.1.21.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Hex-STRING: 00 00 00 00 + 1.3.6.1.3.5.1.1.9.1.21.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 00 00 00 00 + 1.3.6.1.3.5.1.1.9.1.21.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 00 00 00 00 + 1.3.6.1.3.5.1.1.9.1.22.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Gauge32: 1 + 1.3.6.1.3.5.1.1.9.1.22.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Gauge32: 1 + .1.3.6.1.3.5.1.1.9.1.22.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 1 + .1.3.6.1.3.5.1.1.9.1.22.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Gauge32: 1 + .1.3.6.1.3.5.1.1.9.1.24.1.1.1.1.10.0.0.0.31.1.192.168.12.1.1 = Hex-STRING: 02 01 FD E9 + .1.3.6.1.3.5.1.1.9.1.24.1.1.1.1.10.0.0.2.32.1.192.168.12.1.1 = Hex-STRING: 02 01 FD E9 + .1.3.6.1.3.5.1.1.9.1.24.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 02 01 FD E9 + .1.3.6.1.3.5.1.1.9.1.24.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1.1 = Hex-STRING: 02 01 FD E9 + + +The AgentX protocol can be transported over a Unix socket or using TCP or UDP. +It usually defaults to a Unix socket and depends on how NetSNMP was built. If +need to configure FRR to use another transport, you can configure it through +:file:`/etc/snmp/frr.conf`: + +:: + + [snmpd] + # Use a remote master agent + agentXSocket tcp:192.168.15.12:705 + + +Here is the syntax for using AgentX: + +.. clicmd:: agentx + + Once enabled, it can't be unconfigured. Only removing from the daemons file + the keyword ``agentx`` takes an effect. + +.. include:: snmptrap.rst diff --git a/doc/user/snmptrap.rst b/doc/user/snmptrap.rst new file mode 100644 index 0000000..df534e2 --- /dev/null +++ b/doc/user/snmptrap.rst @@ -0,0 +1,217 @@ +Handling SNMP Traps +=================== + +To handle snmp traps make sure your snmp setup of frr works correctly as +described in the frr documentation in :ref:`snmp-support`. + +BGP handles both :rfc:`4273` and [Draft-IETF-idr-bgp4-mibv2-11]_ MIBs. +The BGP4 MIBs will send traps on peer up/down events. These should be +visible in your snmp logs with a message similar to: + +:: + + snmpd[13733]: Got trap from peer on fd 14 + +To react on these traps they should be handled by a trapsink. Configure your +trapsink by adding the following lines to :file:`/etc/snmpd/snmpd.conf`: + +:: + + # send traps to the snmptrapd on localhost + trapsink localhost + + +This will send all traps to an snmptrapd running on localhost. You can of +course also use a dedicated management station to catch traps. Configure the +snmptrapd daemon by adding the following line to +:file:`/etc/snmpd/snmptrapd.conf`: + +:: + + traphandle .1.3.6.1.4.1.3317.1.2.2 /etc/snmp/snmptrap_handle.sh + + +This will use the bash script :file:`/etc/snmp/snmptrap_handle.sh` to handle +the BGP4 traps. To add traps for other protocol daemons, lookup their +appropriate OID from their mib. (For additional information about which traps +are supported by your mib, lookup the mib on +`http://www.oidview.com/mibs/detail.html `_). + +Make sure *snmptrapd* is started. + +The snmptrap_handle.sh script I personally use for handling BGP4 traps is +below. You can of course do all sorts of things when handling traps, like sound +a siren, have your display flash, etc., be creative ;). + +.. code-block:: shell + + #!/bin/bash + + # routers name + ROUTER=`hostname -s` + + #email address use to sent out notification + EMAILADDR="john@doe.com" + #email address used (allongside above) where warnings should be sent + EMAILADDR_WARN="sms-john@doe.com" + + # type of notification + TYPE="Notice" + + # local snmp community for getting AS belonging to peer + COMMUNITY="" + + # if a peer address is in $WARN_PEERS a warning should be sent + WARN_PEERS="192.0.2.1" + + # get stdin + INPUT=`cat -` + + # get some vars from stdin + uptime=`echo $INPUT | cut -d' ' -f5` + peer=`echo $INPUT | cut -d' ' -f8 | sed -e 's/SNMPv2-SMI::mib-2.15.3.1.14.//g'` + peerstate=`echo $INPUT | cut -d' ' -f13` + errorcode=`echo $INPUT | cut -d' ' -f9 | sed -e 's/\\"//g'` + suberrorcode=`echo $INPUT | cut -d' ' -f10 | sed -e 's/\\"//g'` + remoteas=`snmpget -v2c -c $COMMUNITY localhost SNMPv2-SMI::mib-2.15.3.1.9.$peer | cut -d' ' -f4` + + WHOISINFO=`whois -h whois.ripe.net " -r AS$remoteas" | egrep '(as-name|descr)'` + asname=`echo "$WHOISINFO" | grep "^as-name:" | sed -e 's/^as-name://g' -e 's/ //g' -e 's/^ //g' | uniq` + asdescr=`echo "$WHOISINFO" | grep "^descr:" | sed -e 's/^descr://g' -e 's/ //g' -e 's/^ //g' | uniq` + + # if peer address is in $WARN_PEER, the email should also + # be sent to $EMAILADDR_WARN + for ip in $WARN_PEERS; do + if [ "x$ip" == "x$peer" ]; then + EMAILADDR="$EMAILADDR,$EMAILADDR_WARN" + TYPE="WARNING" + break + fi + done + + # convert peer state + case "$peerstate" in + 1) peerstate="Idle" ;; + 2) peerstate="Connect" ;; + 3) peerstate="Active" ;; + 4) peerstate="Opensent" ;; + 5) peerstate="Openconfirm" ;; + 6) peerstate="Established" ;; + *) peerstate="Unknown" ;; + esac + + # get textual messages for errors + case "$errorcode" in + 00) + error="No error" + suberror="" + ;; + 01) + error="Message Header Error" + case "$suberrorcode" in + 01) suberror="Connection Not Synchronized" ;; + 02) suberror="Bad Message Length" ;; + 03) suberror="Bad Message Type" ;; + *) suberror="Unknown" ;; + esac + ;; + 02) + error="OPEN Message Error" + case "$suberrorcode" in + 01) suberror="Unsupported Version Number" ;; + 02) suberror="Bad Peer AS" ;; + 03) suberror="Bad BGP Identifier" ;; + 04) suberror="Unsupported Optional Parameter" ;; + 05) suberror="Authentication Failure" ;; + 06) suberror="Unacceptable Hold Time" ;; + *) suberror="Unknown" ;; + esac + ;; + 03) + error="UPDATE Message Error" + case "$suberrorcode" in + 01) suberror="Malformed Attribute List" ;; + 02) suberror="Unrecognized Well-known Attribute" ;; + 03) suberror="Missing Well-known Attribute" ;; + 04) suberror="Attribute Flags Error" ;; + 05) suberror="Attribute Length Error" ;; + 06) suberror="Invalid ORIGIN Attribute" ;; + 07) suberror="AS Routing Loop" ;; + 08) suberror="Invalid NEXT_HOP Attribute" ;; + 09) suberror="Optional Attribute Error" ;; + 10) suberror="Invalid Network Field" ;; + 11) suberror="Malformed AS_PATH" ;; + *) suberror="Unknown" ;; + esac + ;; + 04) + error="Hold Timer Expired" + suberror="" + ;; + 05) + error="Finite State Machine Error" + suberror="" + ;; + 06) + error="Cease" + case "$suberrorcode" in + 01) suberror="Maximum Number of Prefixes Reached" ;; + 02) suberror="Administrative Shutdown" ;; + 03) suberror="Peer De-configured" ;; + 04) suberror="Administrative Reset" ;; + 05) suberror="Connection Rejected" ;; + 06) suberror="Other Configuration Change" ;; + 07) suberror="Connection Collision Resolution" ;; + 08) suberror="Out of Resources" ;; + 09) suberror="MAX" ;; + *) suberror="Unknown" ;; + esac + ;; + *) + error="Unknown" + suberror="" + ;; + esac + + # create textual message from errorcodes + if [ "x$suberror" == "x" ]; then + NOTIFY="$errorcode ($error)" + else + NOTIFY="$errorcode/$suberrorcode ($error/$suberror)" + fi + + # form a decent subject + SUBJECT="$TYPE: $ROUTER [bgp] $peer is $peerstate: $NOTIFY" + # create the email body + MAIL=`cat << EOF + BGP notification on router $ROUTER. + + Peer: $peer + AS: $remoteas + New state: $peerstate + Notification: $NOTIFY + + Info: + $asname + $asdescr + + Snmpd uptime: $uptime + EOF` + + # mail the notification + echo "$MAIL" | mail -s "$SUBJECT" $EMAILADDR + +.. _traps-mib-selection: + +Traps Mib Selection in BGP +-------------------------- + +Both :rfc:`4273` and [Draft-IETF-idr-bgp4-mibv2-11]_ MIBs define traps for +dealing with up/down events and state transition. The user has the +possibility to select the MIB he wants to receive traps from: + +.. clicmd:: bgp snmp traps + +By default, only rfc4273 traps are enabled and sent. + +.. [Draft-IETF-idr-bgp4-mibv2-11] diff --git a/doc/user/static.rst b/doc/user/static.rst new file mode 100644 index 0000000..922c71a --- /dev/null +++ b/doc/user/static.rst @@ -0,0 +1,178 @@ +.. _static: + +****** +STATIC +****** + +:abbr:`STATIC` is a daemon that handles the installation and deletion +of static routes. + +.. _starting-static: + +Starting STATIC +=============== + +.. program:: staticd + +:abbr:`STATIC` supports all the common FRR daemon start options which are +documented elsewhere. + +.. include:: config-include.rst + +.. _static-route-commands: + +Static Route Commands +===================== + +Static routing is a very fundamental feature of routing technology. It defines +a static prefix and gateway, with several possible forms. + +.. clicmd:: ip route NETWORK GATEWAY [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ip route NETWORK IFNAME [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ip route NETWORK GATEWAY IFNAME [DISTANCE] [onlink] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ip route NETWORK (Null0|blackhole|reject) [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ipv6 route NETWORK [from SRCPREFIX] GATEWAY [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ipv6 route NETWORK [from SRCPREFIX] IFNAME [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ipv6 route NETWORK [from SRCPREFIX] GATEWAY IFNAME [DISTANCE] [onlink] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + +.. clicmd:: ipv6 route NETWORK [from SRCPREFIX] (Null0|blackhole|reject) [DISTANCE] [table TABLENO] [nexthop-vrf VRFNAME] [vrf VRFNAME] + + NETWORK is destination prefix with a valid v4 or v6 network based upon + initial form of the command. + + GATEWAY is the IP address to use as next-hop for the prefix. Currently, it must match + the v4 or v6 route type specified at the start of the command. + + IFNAME is the name of the interface to use as next-hop. If only IFNAME is specified + (without GATEWAY), a connected route will be created. + + When both IFNAME and GATEWAY are specified together, it binds the route to the specified + interface. In this case, it is also possible to specify ``onlink`` to force the kernel + to consider the next-hop as "on link" on the given interface. + + Alternatively, the gateway can be specified as ``Null0`` or ``blackhole`` to create a blackhole + route that drops all traffic. It can also be specified as ``reject`` to create an unreachable + route that rejects traffic with ICMP "Destination Unreachable" messages. + + TABLENO is an optional parameter for namespaces that allows you to create the + route in a specified table associated with the vrf namespace. ``table`` will + be rejected if you are not using namespace based vrfs. + + ``vrf`` VRFNAME allows you to create the route in a specified vrf. + + ``nexthop-vrf`` VRFNAME allows you to create a leaked route with a nexthop in the + specified VRFNAME. ``nexthop-vrf`` cannot be currently used with namespace based vrfs. + + The IPv6 variant allows the installation of a static source-specific route + with the SRCPREFIX sub command. These routes are currently supported + on Linux operating systems only, and perform AND matching on packet's + destination and source addresses in the kernel's forwarding path. Note + that destination longest-prefix match is "more important" than source + LPM, e.g. ``2001:db8:1::/64 from 2001:db8::/48`` will win over + ``2001:db8::/48 from 2001:db8:1::/64`` if both match. + +.. _multiple-route-command: + +Multiple nexthop static route +============================= + +To create multiple nexthops to the same NETWORK (also known as a multipath route), just reenter the same +network statement with different nexthop information. + +.. code-block:: frr + + ip route 10.0.0.1/32 10.0.0.2 + ip route 10.0.0.1/32 10.0.0.3 + ip route 10.0.0.1/32 eth0 + + +If there is no route to 10.0.0.2 and 10.0.0.3, and interface eth0 +is reachable, then the last route is installed into the kernel. + +If zebra has been compiled with multipath support, and both 10.0.0.2 and +10.0.0.3 are reachable, zebra will install a multipath route via both +nexthops, if the platform supports this. + +:: + + router> show ip route + S> 10.0.0.1/32 [1/0] via 10.0.0.2 inactive + via 10.0.0.3 inactive + * is directly connected, eth0 + + +.. code-block:: frr + + ip route 10.0.0.0/8 10.0.0.2 + ip route 10.0.0.0/8 10.0.0.3 + ip route 10.0.0.0/8 null0 255 + + +This will install a multipath route via the specified next-hops if they are +reachable, as well as a high-distance blackhole route, which can be useful to +prevent traffic destined for a prefix to match less-specific routes (e.g. +default) should the specified gateways not be reachable. E.g.: + +:: + + router> show ip route 10.0.0.0/8 + Routing entry for 10.0.0.0/8 + Known via "static", distance 1, metric 0 + 10.0.0.2 inactive + 10.0.0.3 inactive + + Routing entry for 10.0.0.0/8 + Known via "static", distance 255, metric 0 + directly connected, Null0 + +Also, if the user wants to configure a static route for a specific VRF, then +a specific VRF configuration mode is available. After entering into that mode +with :clicmd:`vrf VRF` the user can enter the same route command as before, +but this time, the route command will apply to the VRF. + +.. code-block:: frr + + # case with VRF + configure + vrf r1-cust1 + ip route 10.0.0.0/24 10.0.0.2 + exit-vrf + + +SR-TE Route Commands +==================== + +It is possible to specify a route using a SR-TE policy configured in Zebra. + +e.g. to use the SR-TE policy with endpoint 6.6.6.6 and color 123 to reach the +network 9.9.9.9/24: + +.. code-block:: frr + + ip route 9.9.9.9/24 6.6.6.6 color 123 + +SRv6 Route Commands +==================== + +It is possible to specify a static route for ipv6 prefixes using an SRv6 +`segments` instruction. The `/` separator can be used to specify +multiple segments instructions. + +.. code-block:: frr + + ipv6 route X:X::X:X segments U:U::U:U/Y:Y::Y:Y/Z:Z::Z:Z + + +:: + + router(config)# ipv6 route 2005::1/64 ens3 segments 2001:db8:aaaa::7/2002::4/2002::3/2002::2 + + router# show ipv6 route + [..] + S>* 2005::/64 [1/0] is directly connected, ens3, seg6 2001:db8:aaaa::7,2002::4,2002::3,2002::2, weight 1, 00:00:06 diff --git a/doc/user/subdir.am b/doc/user/subdir.am new file mode 100644 index 0000000..4879f7f --- /dev/null +++ b/doc/user/subdir.am @@ -0,0 +1,133 @@ +# +# doc/user +# + +user_RSTFILES = \ + doc/user/affinitymap.rst \ + doc/user/babeld.rst \ + doc/user/ldpd.rst \ + doc/user/basic.rst \ + doc/user/bgp.rst \ + doc/user/bmp.rst \ + doc/user/bugs.rst \ + doc/user/conf.py \ + doc/user/eigrpd.rst \ + doc/user/evpn.rst \ + doc/user/extlog.rst \ + doc/user/fabricd.rst \ + doc/user/filter.rst \ + doc/user/frr-reload.rst \ + doc/user/glossary.rst \ + doc/user/grpc.rst \ + doc/user/index.rst \ + doc/user/installation.rst \ + doc/user/ipv6.rst \ + doc/user/isisd.rst \ + doc/user/kernel.rst \ + doc/user/nexthop_groups.rst \ + doc/user/nhrpd.rst \ + doc/user/ospf6d.rst \ + doc/user/ospfd.rst \ + doc/user/ospf_fundamentals.rst \ + doc/user/overview.rst \ + doc/user/packet-dumps.rst \ + doc/user/pathd.rst \ + doc/user/pim.rst \ + doc/user/pimv6.rst \ + doc/user/ripd.rst \ + doc/user/pbr.rst \ + doc/user/ripngd.rst \ + doc/user/routemap.rst \ + doc/user/routeserver.rst \ + doc/user/rpki.rst \ + doc/user/scripting.rst \ + doc/user/setup.rst \ + doc/user/sharp.rst \ + doc/user/snmp.rst \ + doc/user/snmptrap.rst \ + doc/user/static.rst \ + doc/user/vnc.rst \ + doc/user/vrrp.rst \ + doc/user/vtysh.rst \ + doc/user/zebra.rst \ + doc/user/bfd.rst \ + doc/user/flowspec.rst \ + doc/user/watchfrr.rst \ + doc/user/wecmp_linkbw.rst \ + doc/user/mgmtd.rst \ + # end + +EXTRA_DIST += \ + $(user_RSTFILES) \ + doc/user/Useful_Sysctl_Settings.md \ + doc/user/_static/overrides.css \ + doc/user/_static/overrides.js \ + # end + +USERBUILD = doc/user/_build +$(USERBUILD)/.doctrees/environment.pickle: $(user_RSTFILES) + +# +# automake integration (things that should be built in "all") +# + +if DOC +nodist_noinst_DATA += $(USERBUILD)/texinfo/frr.info +endif +if DOC_HTML +nodist_noinst_DATA += $(USERBUILD)/html/.buildinfo +endif + +# +# standard targets +# + +.PHONY: info html pdf +info: $(USERBUILD)/texinfo/frr.info +html: $(USERBUILD)/html/.buildinfo +pdf: $(USERBUILD)/latexpdf + +# +# hook-ins for clean / install / doc +# + +.PHONY: clean-userdocs +clean-local: clean-userdocs +clean-userdocs: + -rm -rf "$(USERBUILD)" + +# INSTALL_INFO=install-info +.PHONY: install-info uninstall-info install-html uninstall-html + +install-info: $(USERBUILD)/texinfo/frr.info + $(MKDIR_P) "$(DESTDIR)$(infodir)" + $(INSTALL_DATA) "$<" "$(DESTDIR)$(infodir)" + [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --info-dir="$(DESTDIR)$(infodir)" "$<" || true +uninstall-info: $(USERBUILD)/texinfo/frr.info + -rm -f "$(DESTDIR)$(infodir)/$<" + [ -z "${DESTDIR}" ] && $(INSTALL_INFO) --delete --info-dir="$(DESTDIR)$(infodir)" "$<" || true + +install-html: $(USERBUILD)/html/.buildinfo + $(MKDIR_P) "$(DESTDIR)$(htmldir)" + cp -r "$(USERBUILD)/html" "$(DESTDIR)$(htmldir)" +uninstall-html: + -rm -rf "$(DESTDIR)$(htmldir)/html" + +.PHONY: install-data-local uninstall-local +if DOC +DOC_INFO=info +TARGET_INSTALL_INFO=install-info +TARGET_UNINSTALL_INFO=uninstall-info +endif +if DOC_HTML +DOC_HTML=html +TARGET_INSTALL_HTML=install-html +TARGET_UNINSTALL_HTML=uninstall-html +endif + +# leave the comments in, this was causing weird reordering issues in automake +install-data-local: $(TARGET_INSTALL_INFO) $(TARGET_INSTALL_HTML) +# +uninstall-local: $(TARGET_UNINSTALL_INFO) $(TARGET_UNINSTALL_HTML) +# +doc: $(DOC_INFO) $(DOC_HTML) diff --git a/doc/user/vnc.rst b/doc/user/vnc.rst new file mode 100644 index 0000000..4ff27c6 --- /dev/null +++ b/doc/user/vnc.rst @@ -0,0 +1,1393 @@ +.. _vnc-and-vnc-gw: + +************** +VNC and VNC-GW +************** + +This chapter describes how to use :abbr:`VNC (Virtual Network Control)` +services, including :abbr:`NVA (Network Virtualization Authority)` and +:abbr:`VNC-GW (VNC Gateway)` functions. Background information on NVAs, +:abbr:`NVE (Network Virtualization Edge)` s, :abbr:`UN (Underlay Network)` s, +and :abbr:`VN (Virtual Network)` is available from the +`IETF `_. :abbr:`VNC-GW (VNC Gateway)` s +support the import/export of routing information between VNC and :abbr:`CE +(customer edge)` routers operating within a VN. Both IP/Layer 3 (L3) VNs, and +IP with Ethernet/Layer 2 (L2) VNs are supported. + +BGP, with IP VPNs and Tunnel Encapsulation, is used to distribute VN +information between NVAs. BGP based IP VPN support is defined in :rfc:`4364`, +and :rfc:`4659`. Encapsulation information is provided via the Tunnel +Encapsulation Attribute, :rfc:`5512`. + +The protocol that is used to communicate routing and Ethernet / Layer 2 (L2) +forwarding information between NVAs and NVEs is referred to as the Remote +Forwarder Protocol (RFP). `OpenFlow` is an example RFP. Specific RFP +implementations may choose to implement either a `hard-state` or `soft-state` +prefix and address registration model. To support a `soft-state` refresh model, +a `lifetime` in seconds is associated with all registrations and responses. + +The chapter also provides sample configurations for basic example scenarios. + +.. _configuring-vnc: + +Configuring VNC +=============== + +Virtual Network Control (:abbr:`VNC`) service configuration commands appear in +the `router bgp` section of the BGPD configuration file +(:ref:`bgp-configuration-examples`). The commands are broken down into the +following areas: + +- :dfn:`General VNC` configuration applies to general VNC operation and is + primarily used to control the method used to advertise tunnel information. + +- :dfn:`Remote Forwarder Protocol (RFP)` configuration relates to the protocol + used between NVAs and NVEs. + +- :dfn:`VNC Defaults` provides default parameters for registered NVEs. + +- :dfn:`VNC NVE Group` provides for configuration of a specific set of + registered NVEs and overrides default parameters. + +- :dfn:`Redistribution` and :dfn:`Export` control VNC-GW operation, i.e., the + import/export of routing information between VNC and customer edge routers + (:abbr:`CE` s) operating within a VN. + + +.. _general-vnc-configuration: + +General VNC Configuration +------------------------- + +.. _rfp-related-configuration: + +RFP Related Configuration +------------------------- + +The protocol that is used to communicate routing and Ethernet / L2 forwarding +information between NVAs and NVEs is referred to as the Remote Forwarder +Protocol (RFP). Currently, only a simple example RFP is included in FRR. +Developers may use this example as a starting point to integrate FRR with an +RFP of their choosing, e.g., `OpenFlow`. The example code includes the +following sample configuration: + +.. clicmd:: rfp example-config-value VALUE + +This is a simple example configuration parameter included as part of the RFP +example code. VALUE must be in the range of 0 to 4294967295. + +.. _vnc-defaults-configuration: + +VNC Defaults Configuration +-------------------------- + +The VNC Defaults section allows the user to specify default values for +configuration parameters for all registered NVEs. +Default values are overridden by :ref:`vnc-nve-group-configuration`. + +.. clicmd:: vnc defaults + +Enter VNC configuration mode for specifying VNC default behaviors. Use +`exit-vnc` to leave VNC configuration mode. `vnc defaults` is optional. + +.. code-block:: frr + + vnc defaults + ... various VNC defaults + exit-vnc + + +These are the statements that can appear between ``vnc defaults`` and +``exit-vnc``. Documentation for these statements is given in +:ref:`vnc-nve-group-configuration`. + +- :clicmd:`rt import RT-LIST` +- :clicmd:`rt export RT-LIST` +- :clicmd:`rt both RT-LIST` +- :clicmd:`rd ROUTE-DISTINGUISHER` +- :clicmd:`l2rd NVE-ID-VALUE` +- :clicmd:`response-lifetime LIFETIME|infinite` +- :clicmd:`export bgp|zebra route-map MAP-NAME` +- :clicmd:`export bgp|zebra no route-map` + +.. clicmd:: exit-vnc + + Exit VNC configuration mode. + +.. _vnc-nve-group-configuration: + +VNC NVE Group Configuration +--------------------------- + +A NVE Group corresponds to a specific set of NVEs. A Client NVE is +assigned to an NVE Group based on whether there is a match for either +its virtual or underlay network address against the VN and/or UN address +prefixes specified in the NVE Group definition. When an NVE Group +definition specifies both VN and UN address prefixes, then an NVE must +match both prefixes in order to be assigned to the NVE Group. In the +event that multiple NVE Groups match based on VN and/or UN addresses, +the NVE is assigned to the first NVE Group listed in the configuration. +If an NVE is not assigned to an NVE Group, its messages will be ignored. + +Configuration values specified for an NVE group apply to all +member NVEs and override configuration values specified in the VNC +Defaults section. + +**At least one `nve-group` is mandatory for useful VNC operation.** + +.. clicmd:: vnc nve-group NAME + + Enter VNC configuration mode for defining the NVE group `name`. + Use `exit` or `exit-vnc` to exit group configuration mode. + + .. code-block:: frr + + vnc nve-group group1 + ... configuration commands + exit-vnc + + +The following statements are valid in an NVE group definition: + +.. clicmd:: l2rd NVE-ID-VALUE + + Set the value used to distinguish NVEs connected to the same physical + Ethernet segment (i.e., at the same location) [#]_. + + The nve-id subfield may be specified as either a literal value in the range + 1-255, or it may be specified as `auto:vn`, which means to use the + least-significant octet of the originating NVE's VN address. + +.. clicmd:: prefix vn|un A.B.C.D/M|X:X::X:X/M + + Specify the matching prefix for this NVE group by either virtual-network + address (`vn`) or underlay-network address (`un`). Either or both + virtual-network and underlay-network prefixes may be specified. Subsequent + virtual-network or underlay-network values within a `vnc nve-group` + `exit-vnc` block override their respective previous values. + + These prefixes are used only for determining assignments of NVEs to NVE + Groups. + +.. clicmd:: rd ROUTE-DISTINGUISHER + + Specify the route distinguisher for routes advertised via BGP + VPNs. The route distinguisher must be in one of these forms: + + - ``IPv4-address:two-byte-integer`` + - ``four-byte-autonomous-system-number:two-byte-integer`` + - ``two-byte-autonomous-system-number:four-byte-integer`` + - ``auto:vn:two-byte-integer`` + + Routes originated by NVEs in the NVE group will use the group's specified + `route-distinguisher` when they are advertised via BGP. If the `auto` form + is specified, it means that a matching NVE has its RD set to + ``rd_type=IP=1:IPv4-address=VN-address:two-byte-integer``, for IPv4 VN + addresses and + ``rd_type=IP=1:IPv4-address=Last-four-bytes-of-VN-address:two-byte-integer``, + for IPv6 VN addresses. + + If the NVE group definition does not specify a `route-distinguisher`, then + the default `route-distinguisher` is used. If neither a group nor a default + `route-distinguisher` is configured, then the advertised RD is set to + ``two-byte-autonomous-system-number=0:four-byte-integer=0``. + +.. clicmd:: response-lifetime LIFETIME|infinite + + Specify the response lifetime, in seconds, to be included in RFP response + messages sent to NVEs. If the value 'infinite' is given, an infinite + lifetime will be used. + + Note that this parameter is not the same as the lifetime supplied by NVEs in + RFP registration messages. This parameter does not affect the lifetime value + attached to routes sent by this server via BGP. + + If the NVE group definition does not specify a `response-lifetime`, the + default `response-lifetime` will be used. If neither a group nor a default + `response-lifetime` is configured, the value 3600 will be used. The maximum + response lifetime is 2147483647. + +.. clicmd:: rt export RT-LIST + +.. clicmd:: rt import RT-LIST + +.. clicmd:: rt both RT-LIST + + Specify route target import and export lists. `rt-list` is a + space-separated list of route targets, each element of which is + in one of the following forms: + + - ``IPv4-address:two-byte-integer`` + - ``four-byte-autonomous-system-number:two-byte-integer`` + - ``two-byte-autonomous-system-number:four-byte-integer`` + + The first form, `rt export`, specifies an `export rt-list`. The `export + rt-list` will be attached to routes originated by NVEs in the NVE group + when they are advertised via BGP. If the NVE group definition does not + specify an `export rt-list`, then the default `export rt-list` is used. + If neither a group nor a default `export rt-list` is configured, then no + RT list will be sent; in turn, these routes will probably not be + processed by receiving NVAs. + + The second form, `rt import` specifies an `import rt-list`, which is a + filter for incoming routes. In order to be made available to NVEs in the + group, incoming BGP VPN routes must have RT lists that have at least one + route target in common with the group's `import rt-list`. + + If the NVE group definition does not specify an import filter, then the + default `import rt-list` is used. If neither a group nor a default + `import rt-list` is configured, there can be no RT intersections when + receiving BGP routes and therefore no incoming BGP routes will be + processed for the group. + + The third, `rt both`, is a shorthand way of specifying both lists + simultaneously, and is equivalent to `rt export `rt-list`` followed by + `rt import `rt-list``. + +.. clicmd:: export bgp|zebra route-map MAP-NAME + + Specify that the named route-map should be applied to routes being exported + to bgp or zebra. This parameter is used in conjunction with + :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item + is optional. + +.. clicmd:: export bgp|zebra no route-map + + Specify that no route-map should be applied to routes being exported to bgp + or zebra. This parameter is used in conjunction with + :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item + is optional. + +.. clicmd:: export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME + + Specify that the named prefix-list filter should be applied to routes being + exported to bgp or zebra. Prefix-lists for ipv4 and ipv6 are independent of + each other. This parameter is used in conjunction with + :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item + is optional. + +.. clicmd:: export bgp|zebra no ipv4|ipv6 prefix-list + + Specify that no prefix-list filter should be applied to routes being + exported to bgp or zebra. This parameter is used in conjunction with + :ref:`configuring-export-of-routes-to-other-routing-protocols`. This item + is optional. + +.. _vnc-l2-group-configuration: + +VNC L2 Group Configuration +-------------------------- + +The route targets advertised with prefixes and addresses registered by an NVE +are determined based on the NVE's associated VNC NVE Group Configuration, +:ref:`vnc-nve-group-configuration`. Layer 2 (L2) Groups are used to override +the route targets for an NVE's Ethernet registrations based on the Logical +Network Identifier and label value. A Logical Network Identifier is used to +uniquely identify a logical Ethernet segment and is conceptually similar to the +Ethernet Segment Identifier defined in :rfc:`7432`. Both the Logical Network +Identifier and Label are passed to VNC via RFP prefix and address registration. + +Note that a corresponding NVE group configuration must be present, and that +other NVE associated configuration information, notably RD, is not impacted by +L2 Group Configuration. + +.. clicmd:: vnc l2-group NAME + + Enter VNC configuration mode for defining the L2 group `name`. + Use `exit` or `exit-vnc` to exit group configuration mode. + + .. code-block:: frr + + vnc l2-group group1 + ... configuration commands + exit-vnc + + + + Delete the L2 group named `name`. + +The following statements are valid in a L2 group definition: + +.. clicmd:: logical-network-id VALUE + + Define the Logical Network Identifier with a value in the range of + 0-4294967295 that identifies the logical Ethernet segment. + +.. clicmd:: labels LABEL-LIST + + + Add or remove labels associated with the group. `label-list` is a + space separated list of label values in the range of 0-1048575. + +.. clicmd:: rt import RT-TARGET + +.. clicmd:: rt export RT-TARGET + +.. clicmd:: rt both RT-TARGET + + Specify the route target import and export value associated with the group. + A complete definition of these parameters is given above, + :ref:`vnc-nve-group-configuration`. + +.. _configuring-redistribution-of-routes-from-other-routing-protocols: + +Configuring Redistribution of Routes from Other Routing Protocols +----------------------------------------------------------------- + +Routes from other protocols (including BGP) can be provided to VNC (both for +RFP and for redistribution via BGP) from three sources: the zebra kernel +routing process; directly from the main (default) unicast BGP RIB; or directly +from a designated BGP unicast exterior routing RIB instance. + +The protocol named in the `vnc redistribute` command indicates the route +source: `bgp-direct` routes come directly from the main (default) unicast BGP +RIB and are available for RFP and are redistributed via BGP; +`bgp-direct-to-nve-groups` routes come directly from a designated BGP unicast +routing RIB and are made available only to RFP; and routes from other protocols +come from the zebra kernel routing process. +Note that the zebra process does not need to be active if +only `bgp-direct` or `bgp-direct-to-nve-groups` routes are used. + +zebra routes +^^^^^^^^^^^^ + +Routes originating from protocols other than BGP must be obtained +via the zebra routing process. +Redistribution of these routes into VNC does not support policy mechanisms +such as prefix-lists or route-maps. + +bgp-direct routes +^^^^^^^^^^^^^^^^^ + +`bgp-direct` redistribution supports policy via +prefix lists and route-maps. This policy is applied to incoming +original unicast routes before the redistribution translations +(described below) are performed. + +Redistribution of `bgp-direct` routes is performed in one of three +possible modes: `plain`, `nve-group`, or `resolve-nve`. +The default mode is `plain`. +These modes indicate the kind of translations applied to routes before +they are added to the VNC RIB. + +In `plain` mode, the route's next hop is unchanged and the RD is set +based on the next hop. +For `bgp-direct` redistribution, the following translations are performed: + +- The VN address is set to the original unicast route's next hop address. +- The UN address is NOT set. (VN->UN mapping will occur via + ENCAP route or attribute, based on `vnc advertise-un-method` + setting, generated by the RFP registration of the actual NVE) +- The RD is set to as if auto:vn:0 were specified (i.e., + `rd_type=IP=1`:`IPv4-address=VN-address`:`two-byte-integer=0`) +- The RT list is included in the extended community list copied from the + original unicast route (i.e., it must be set in the original unicast route). + +In `nve-group` mode, routes are registered with VNC as if they came from an NVE +in the nve-group designated in the `vnc redistribute nve-group` command. The +following translations are performed: + +- The next hop/VN address is set to the VN prefix configured for the + redistribute nve-group. +- The UN address is set to the UN prefix configured for the redistribute + nve-group. +- The RD is set to the RD configured for the redistribute nve-group. +- The RT list is set to the RT list configured for the redistribute nve-group. + If `bgp-direct` routes are being redistributed, any extended communities + present in the original unicast route will also be included. + +In `resolve-nve` mode, the next hop of the original BGP route is typically the +address of an NVE connected router (CE) connected by one or more NVEs. +Each of the connected NVEs will register, via RFP, a VNC host route to the CE. +This mode may be though of as a mechanism to proxy RFP registrations of BGP +unicast routes on behalf of registering NVEs. + +Multiple copies of the BGP route, one per matching NVE host route, will be +added to VNC. In other words, for a given BGP unicast route, each instance of +a RFP-registered host route to the unicast route's next hop will result in an +instance of an imported VNC route. Each such imported VNC route will have a +prefix equal to the original BGP unicast route's prefix, and a next hop equal +to the next hop of the matching RFP-registered host route. If there is no +RFP-registered host route to the next hop of the BGP unicast route, no +corresponding VNC route will be imported. + +The following translations are applied: + +- The Next Hop is set to the next hop of the NVE route (i.e., the + VN address of the NVE). + +- The extended community list in the new route is set to the + union of: + +- Any extended communities in the original BGP route + + - Any extended communities in the NVE route + - An added route-origin extended community with the next hop of the + original BGP route + is added to the new route. + The value of the local administrator field defaults 5226 but may + be configured by the user via the `roo-ec-local-admin` parameter. + +- The Tunnel Encapsulation attribute is set to the value of the Tunnel + Encapsulation attribute of the NVE route, if any. + + +bgp-direct-to-nve-groups routes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Unicast routes from the main or a designated instance of BGP may be +redistributed to VNC as bgp-direct-to-nve-groups routes. These routes are NOT +announced via BGP, but they are made available for local RFP lookup in response +to queries from NVEs. + +A non-main/default BGP instance is configured using the +`router bgp AS view NAME` command as described elsewhere in this document. + +In order for a route in the unicast BGP RIB to be made available to a querying +NVE, there must already be, available to that NVE, an (interior) VNC route +matching the next hop address of the unicast route. When the unicast route is +provided to the NVE, its next hop is replaced by the next hop of the +corresponding NVE. If there are multiple longest-prefix-match VNC routes, the +unicast route will be replicated for each. + +There is currently no policy (prefix-list or route-map) support for +`bgp-direct-to-nve-groups` routes. + +Redistribution Command Syntax +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. clicmd:: vnc redistribute ipv4|ipv6 bgp|bgp-direct|ipv6 bgp-direct-to-nve-groups|connected|kernel|ospf|rip|static + +.. clicmd:: vnc redistribute ipv4|ipv6 bgp-direct-to-nve-groups view VIEWNAME + + + Import (or do not import) prefixes from another routing protocols. Specify + both the address family to import (`ipv4` or `ipv6`) and the protocol + (`bgp`, `bgp-direct`, `bgp-direct-to-nve-groups`, `connected`, `kernel`, + `ospf`, `rip`, or `static`). Repeat this statement as needed for each + combination of address family and routing protocol. Prefixes from protocol + `bgp-direct` are imported from unicast BGP in the same bgpd process. + Prefixes from all other protocols (including `bgp`) are imported via the + `zebra` kernel routing process. + +.. clicmd:: vnc redistribute mode plain|nve-group|resolve-nve + + Redistribute routes from other protocols into VNC using the specified mode. + Not all combinations of modes and protocols are supported. + +.. clicmd:: vnc redistribute nve-group GROUP-NAME + + + When using `nve-group` mode, assign (or do not assign) the NVE group + `group-name` to routes redistributed from another routing protocol. + `group-name` must be configured using `vnc nve-group`. + + The VN and UN prefixes of the nve-group must both be configured, and each + prefix must be specified as a full-length (/32 for IPv4, /128 for IPv6) + prefix. + +.. clicmd:: vnc redistribute lifetime LIFETIME|infinite + + Assign a registration lifetime, either `lifetime` seconds or `infinite`, to + prefixes redistributed from other routing protocols as if they had been + received via RFP registration messages from an NVE. `lifetime` can be any + integer between 1 and 4294967295, inclusive. + +.. clicmd:: vnc redistribute resolve-nve roo-ec-local-admin 0-65536 + + Assign a value to the local-administrator subfield used in the + Route Origin extended community that is assigned to routes exported + under the `resolve-nve` mode. The default value is `5226`. + +The following four `prefix-list` and `route-map` commands may be specified +in the context of an nve-group or not. If they are specified in the context +of an nve-group, they apply only if the redistribution mode is `nve-group`, +and then only for routes being redistributed from `bgp-direct`. If they are +specified outside the context of an nve-group, then they apply only for +redistribution modes `plain` and `resolve-nve`, and then only for routes +being redistributed from `bgp-direct`. + +.. clicmd:: vnc redistribute bgp-direct (ipv4|ipv6) prefix-list LIST-NAME + + When redistributing `bgp-direct` routes, + specifies that the named prefix-list should be applied. + +.. clicmd:: vnc redistribute bgp-direct no (ipv4|ipv6) prefix-list + + When redistributing `bgp-direct` routes, + specifies that no prefix-list should be applied. + +.. clicmd:: vnc redistribute bgp-direct route-map MAP-NAME + + When redistributing `bgp-direct` routes, + specifies that the named route-map should be applied. + +.. clicmd:: vnc redistribute bgp-direct no route-map + + When redistributing `bgp-direct` routes, + specifies that no route-map should be applied. + +.. _configuring-export-of-routes-to-other-routing-protocols: + +Configuring Export of Routes to Other Routing Protocols +------------------------------------------------------- + +Routes from VNC (both for RFP and for redistribution via BGP) can be provided +to other protocols, either via zebra or directly to BGP. + +It is important to note that when exporting routes to other protocols, the +downstream protocol must also be configured to import the routes. For example, +when VNC routes are exported to unicast BGP, the BGP configuration must include +a corresponding `redistribute vnc-direct` statement. + +.. clicmd:: export bgp|zebra mode none|group-nve|registering-nve|ce + + Specify how routes should be exported to bgp or zebra. If the mode is + `none`, routes are not exported. If the mode is `group-nve`, routes are + exported according to nve-group or vrf-policy group configuration + (:ref:`vnc-nve-group-configuration`): if a group is configured to allow + export, then each prefix visible to the group is exported with next hops set + to the currently-registered NVEs. If the mode is `registering-nve`, then all + VNC routes are exported with their original next hops. If the mode is `ce`, + only VNC routes that have an NVE connected CE Router encoded in a Route + Origin Extended Community are exported. This extended community must have an + administrative value that matches the configured `roo-ec-local-admin` value. + The next hop of the exported route is set to the encoded NVE connected CE + Router. + + The default for both bgp and zebra is mode `none`. + +.. clicmd:: vnc export bgp|zebra group-nve group GROUP-NAME + +.. clicmd:: vnc export bgp|zebra group-nve no group GROUP-NAME + + When export mode is `group-nve`, export (or do not export) prefixes from the + specified nve-group or vrf-policy group to unicast BGP or to zebra. Repeat + this statement as needed for each nve-group to be exported. Each VNC prefix + that is exported will result in N exported routes to the prefix, each with a + next hop corresponding to one of the N NVEs currently associated with the + nve-group. + +Some commands have a special meaning under certain export modes. + +:clicmd:`export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME` + When export mode is `ce` or `registering-nve`, + specifies that the named prefix-list should be applied to routes + being exported to bgp or zebra. + Prefix-lists for ipv4 and ipv6 are independent of each other. + +:clicmd:`export bgp|zebra no ipv4|ipv6 prefix-list` + When export mode is `ce` or `registering-nve`, + specifies that no prefix-list should be applied to routes + being exported to bgp or zebra. + +:clicmd:`export bgp|zebra route-map MAP-NAME` + When export mode is `ce` or `registering-nve`, specifies that the named + route-map should be applied to routes being exported to bgp or zebra. + +:clicmd:`export bgp|zebra no route-map` + When export mode is `ce` or `registering-nve`, specifies that no route-map + should be applied to routes being exported to bgp or zebra. + + When the export mode is `group-nve`, policy for exported routes is specified + per-NVE-group or vrf-policy group inside a `nve-group` `RFG-NAME` block via + the following commands(:ref:`vnc-nve-group-configuration`): + +:clicmd:`export bgp|zebra route-map MAP-NAME` + This command is valid inside a `nve-group` `RFG-NAME` block. It specifies + that the named route-map should be applied to routes being exported to bgp + or zebra. + +:clicmd:`export bgp|zebra no route-map` + This command is valid inside a `nve-group` `RFG-NAME` block. It specifies + that no route-map should be applied to routes being exported to bgp or + zebra. + +:clicmd:`export bgp|zebra ipv4|ipv6 prefix-list LIST-NAME` + This command is valid inside a `nve-group` `RFG-NAME` block. It specifies + that the named prefix-list filter should be applied to routes being exported + to bgp or zebra. Prefix-lists for ipv4 and ipv6 are independent of each + other. + +:clicmd:`export bgp|zebra no ipv4|ipv6 prefix-list` + This command is valid inside a `nve-group` `RFG-NAME` block. It specifies + that no prefix-list filter should be applied to routes being exported to + bgp or zebra. + +.. _manual-address-control: + +Manual Address Control +====================== + +The commands in this section can be used to augment normal dynamic VNC. The +`add vnc` commands can be used to manually add IP prefix or Ethernet MAC +address forwarding information. The `clear vnc` commands can be used to remove +manually and dynamically added information. + +.. clicmd:: add vnc prefix (A.B.C.D/M|X:X::X:X/M) vn (A.B.C.D|X:X::X:X) un (A.B.C.D|X:X::X:X) [cost (0-255)] [lifetime (infinite|(1-4294967295))] [local-next-hop (A.B.C.D|X:X::X:X) [local-cost (0-255)]] + + Register an IP prefix on behalf of the NVE identified by the VN and UN + addresses. The `cost` parameter provides the administrative preference of + the forwarding information for remote advertisement. If omitted, it defaults + to 255 (lowest preference). The `lifetime` parameter identifies the period, + in seconds, that the information remains valid. If omitted, it defaults to + `infinite`. The optional `local-next-hop` parameter is used to configure a + nexthop to be used by an NVE to reach the prefix via a locally connected CE + router. This information remains local to the NVA, i.e., not passed to other + NVAs, and is only passed to registered NVEs. When specified, it is also + possible to provide a `local-cost` parameter to provide a forwarding + preference. If omitted, it defaults to 255 (lowest preference). + +.. clicmd:: add vnc mac xx:xx:xx:xx:xx:xx virtual-network-identifier (1-4294967295) vn (A.B.C.D|X:X::X:X) un (A.B.C.D|X:X::X:X) [prefix (A.B.C.D/M|X:X::X:X/M)] [cost (0-255)] [lifetime (infinite|(1-4294967295))] + + Register a MAC address for a logical Ethernet (L2VPN) on behalf of the NVE + identified by the VN and UN addresses. The optional `prefix` parameter is to + support enable IP address mediation for the given prefix. The `cost` + parameter provides the administrative preference of the forwarding + information. If omitted, it defaults to 255. The `lifetime` parameter + identifies the period, in seconds, that the information remains valid. If + omitted, it defaults to `infinite`. + +.. clicmd:: clear vnc prefix (\*|A.B.C.D/M|X:X::X:X/M) (\*|[(vn|un) (A.B.C.D|X:X::X:X|\*) [(un|vn) (A.B.C.D|X:X::X:X|\*)] [mac xx:xx:xx:xx:xx:xx] [local-next-hop (A.B.C.D|X:X::X:X)]) + + Delete the information identified by prefix, VN address, and UN address. + Any or all of these parameters may be wildcarded to (potentially) match more + than one registration. The optional `mac` parameter specifies a layer-2 MAC + address that must match the registration(s) to be deleted. The optional + `local-next-hop` parameter is used to delete specific local nexthop + information. + +.. clicmd:: clear vnc mac (\*|xx:xx:xx:xx:xx:xx) virtual-network-identifier (\*|(1-4294967295)) (\*|[(vn|un) (A.B.C.D|X:X::X:X|\*) [(un|vn) (A.B.C.D|X:X::X:X|\*)] [prefix (\*|A.B.C.D/M|X:X::X:X/M)]) + + Delete mac forwarding information. Any or all of these parameters may be + wildcarded to (potentially) match more than one registration. The default + value for the `prefix` parameter is the wildcard value `*`. + +.. clicmd:: clear vnc nve (\*|((vn|un) (A.B.C.D|X:X::X:X) [(un|vn) (A.B.C.D|X:X::X:X)])) + + Delete prefixes associated with the NVE specified by the given VN and UN + addresses. It is permissible to specify only one of VN or UN, in which case + any matching registration will be deleted. It is also permissible to specify + `*` in lieu of any VN or UN address, in which case all registrations will + match. + +.. _other-vnc-related-commands: + +Other VNC-Related Commands +========================== + +Note: VNC-Related configuration can be obtained via the `show +running-configuration` command when in `enable` mode. + +The following commands are used to clear and display Virtual Network Control +related information: + +.. clicmd:: clear vnc counters + + Reset the counter values stored by the NVA. Counter + values can be seen using the `show vnc` commands listed above. This + command is only available in `enable` mode. + +.. clicmd:: show vnc summary + + Print counter values and other general information + about the NVA. Counter values can be reset + using the `clear vnc counters` command listed below. + +.. clicmd:: show vnc nves + +.. clicmd:: show vnc nves vn|un ADDRESS + + Display the NVA's current clients. Specifying `address` limits the output to + the NVEs whose addresses match `address`. The time since the NVA last + communicated with the NVE, per-NVE summary counters and each NVE's addresses + will be displayed. + +.. clicmd:: show vnc queries + +.. clicmd:: show vnc queries PREFIX + + Display active Query information. Queries remain valid for the default + Response Lifetime (:ref:`vnc-defaults-configuration`) or NVE-group Response + Lifetime (:ref:`vnc-nve-group-configuration`). Specifying `prefix` limits + the output to Query Targets that fall within `prefix`. + + Query information is provided for each querying NVE, and includes the Query + Target and the time remaining before the information is removed. + +.. clicmd:: show vnc registrations [all|local|remote|holddown|imported] + +.. clicmd:: show vnc registrations [all|local|remote|holddown|imported] PREFIX + + Display local, remote, holddown, and/or imported registration information. + Local registrations are routes received via RFP, which are present in the + NVA Registrations Cache. Remote registrations are routes received via BGP + (VPN SAFIs), which are present in the NVE-group import tables. Holddown + registrations are local and remote routes that have been withdrawn but whose + holddown timeouts have not yet elapsed. Imported information represents + routes that are imported into NVA and are made available to querying NVEs. + Depending on configuration, imported routes may also be advertised via BGP. + Specifying `prefix` limits the output to the registered prefixes that fall + within `prefix`. + + Registration information includes the registered prefix, the registering NVE + addresses, the registered administrative cost, the registration lifetime and + the time since the information was registered or, in the case of Holddown + registrations, the amount of time remaining before the information is + removed. + +.. clicmd:: show vnc responses [active|removed] + +.. clicmd:: show vnc responses [active|removed] PREFIX + + Display all, active and/or removed response information which are + present in the NVA Responses Cache. Responses remain valid for the + default Response Lifetime (:ref:`vnc-defaults-configuration`) or + NVE-group Response Lifetime (:ref:`vnc-nve-group-configuration`.) + When Removal Responses are enabled (:ref:`general-vnc-configuration`), + such responses are listed for the Response Lifetime. Specifying + `prefix` limits the output to the addresses that fall within + `prefix`. + + Response information is provided for each querying NVE, and includes + the response prefix, the prefix-associated registering NVE addresses, + the administrative cost, the provided response lifetime and the time + remaining before the information is to be removed or will become inactive. + +.. clicmd:: show memory vnc + + Print the number of memory items allocated by the NVA. + +.. _example-vnc-and-vnc-gw-configurations: + +Example VNC and VNC-GW Configurations +===================================== + +.. _vnc-mesh-nva-config: + +Mesh NVA Configuration +---------------------- + +This example includes three NVAs, nine NVEs, and two NVE groups. Note that +while not shown, a single physical device may support multiple logical NVEs. +:ref:`vnc-fig-vnc-mesh` shows ``code NVA-1`` (192.168.1.100), ``NVA 2`` +(192.168.1.101), and ``NVA 3`` (192.168.1.102), which are connected in a full +mesh. Each is a member of the autonomous system 64512. Each NVA provides VNC +services to three NVE clients in the 172.16.0.0/16 virtual-network address +range. The 172.16.0.0/16 address range is partitioned into two NVE groups, +``group1`` (172.16.0.0/17) and ``group2`` (172.16.128.0/17). + +Each NVE belongs to either NVE group ``group1`` or NVE group ``group2``. The +NVEs ``NVE 1``, ``NVE 2``, ``NVE 4``, ``NVE 7``, and ``NVE 8`` are members of +the NVE group ``group1``. The NVEs ``NVE 3``, ``NVE 5``, ``NVE 6``, and ``NVE +9`` are members of the NVE group ``group2``. + +Each NVA advertises NVE underlay-network IP addresses using the +Tunnel Encapsulation Attribute. + +.. _vnc-fig-vnc-mesh: + +.. figure:: ../figures/fig-vnc-mesh.png + :align: center + :alt: Three-way Mesh + + A three-way full mesh with three NVEs per NVA. + +:file:`bgpd.conf` for ``NVA 1`` (192.168.1.100): + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.100 + + neighbor 192.168.1.101 remote-as 64512 + neighbor 192.168.1.102 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.101 activate + neighbor 192.168.1.102 activate + exit-address-family + + vnc defaults + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + + vnc nve-group group1 + prefix vn 172.16.0.0/17 + rt both 1000:1 + exit-vnc + + vnc nve-group group2 + prefix vn 172.16.128.0/17 + rt both 1000:2 + exit-vnc + + exit + +:file:`bgpd.conf` for ``NVA 2`` (192.168.1.101): + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.101 + + neighbor 192.168.1.100 remote-as 64512 + neighbor 192.168.1.102 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + neighbor 192.168.1.102 activate + exit-address-family + + vnc nve-group group1 + prefix vn 172.16.0.0/17 + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + exit + +:file:`bgpd.conf` for ``NVA 3`` (192.168.1.102): + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.102 + + neighbor 192.168.1.101 remote-as 64512 + neighbor 192.168.1.102 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + neighbor 192.168.1.101 activate + exit-address-family + + vnc defaults + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + + vnc nve-group group1 + prefix vn 172.16.128.0/17 + exit-vnc + exit + + +Mesh NVA and VNC-GW Configuration +--------------------------------- + +This example includes two NVAs, each with two associated NVEs, and two VNC-GWs, +each supporting two CE routers physically attached to the four NVEs. Note that +this example is showing a more complex configuration where VNC-GW is separated +from normal NVA functions; it is equally possible to simplify the configuration +and combine NVA and VNC-GW functions in a single FRR instance. + +.. _vnc-fig-vnc-gw: +.. figure:: ../figures/fig-vnc-gw.png + :align: center + :alt: FRR VNC Gateway + + Meshed NVEs and VNC-GWs + +As shown in :ref:`vnc-fig-vnc-gw`, NVAs and VNC-GWs are connected in a full iBGP +mesh. The VNC-GWs each have two CEs configured as route-reflector clients. +Each client provides BGP updates with unicast routes that the VNC-GW reflects +to the other client. The VNC-GW also imports these unicast routes into VPN +routes to be shared with the other VNC-GW and the two NVAs. This route +importation is controlled with the ``vnc redistribute`` statements shown in the +configuration. Similarly, registrations sent by NVEs via RFP to the NVAs are +exported by the VNC-GWs to the route-reflector clients as unicast routes. RFP +registrations exported this way have a next-hop address of the CE behind the +connected (registering) NVE. Exporting VNC routes as IPv4 unicast is enabled +with the ``vnc export`` command below. + +The configuration for ``VNC-GW 1`` is shown below. + +.. code-block:: frr + + router bgp 64512 + bgp router-id 192.168.1.101 + bgp cluster-id 1.2.3.4 + neighbor 192.168.1.102 remote-as 64512 + neighbor 192.168.1.103 remote-as 64512 + neighbor 192.168.1.104 remote-as 64512 + neighbor 172.16.1.2 remote-as 64512 + neighbor 172.16.2.2 remote-as 64512 + ! + address-family ipv4 unicast + redistribute vnc-direct + no neighbor 192.168.1.102 activate + no neighbor 192.168.1.103 activate + no neighbor 192.168.1.104 activate + neighbor 172.16.1.2 route-reflector-client + neighbor 172.16.2.2 route-reflector-client + exit-address-family + ! + address-family ipv4 vpn + neighbor 192.168.1.102 activate + neighbor 192.168.1.103 activate + neighbor 192.168.1.104 activate + exit-address-family + vnc export bgp mode ce + vnc redistribute mode resolve-nve + vnc redistribute ipv4 bgp-direct + exit + +Note that in the VNC-GW configuration, the neighboring VNC-GW and NVAs each +have a statement disabling the IPv4 unicast address family. IPv4 unicast is on +by default and this prevents the other VNC-GW and NVAs from learning unicast +routes advertised by the route-reflector clients. + +Configuration for ``NVA 2``: + +.. code-block:: frr + + router bgp 64512 + bgp router-id 192.168.1.104 + neighbor 192.168.1.101 remote-as 64512 + neighbor 192.168.1.102 remote-as 64512 + neighbor 192.168.1.103 remote-as 64512 + ! + address-family ipv4 unicast + no neighbor 192.168.1.101 activate + no neighbor 192.168.1.102 activate + no neighbor 192.168.1.103 activate + exit-address-family + ! + address-family ipv4 vpn + neighbor 192.168.1.101 activate + neighbor 192.168.1.102 activate + neighbor 192.168.1.103 activate + exit-address-family + ! + vnc defaults + response-lifetime 3600 + exit-vnc + vnc nve-group nve1 + prefix vn 172.16.1.1/32 + response-lifetime 3600 + rt both 1000:1 1000:2 + exit-vnc + vnc nve-group nve2 + prefix vn 172.16.2.1/32 + response-lifetime 3600 + rt both 1000:1 1000:2 + exit-vnc + exit + +.. TBD make this its own example: +.. +.. @float Figure,fig:fig-vnc-gw-rr +.. @center @image{fig-vnc-gw-rr,400pt,,FRR VNC Gateway with RR} +.. @end float +.. An NVA can also import unicast routes from BGP without advertising the +.. imported routes as VPN routes. Such imported routes, while not +.. distributed to other NVAs or VNC-GWs, are are available to NVEs via +.. RFP query messages sent to the NVA. @ref{fig:fig-vnc-gw-rr} +.. shows an example topology where unicast routes are imported into NVAs +.. from a Route Reflector. (@pxref{Route Reflector} for route reflector +.. configuration details.) The following three lines can be added to the +.. ``NVA 1`` and ``NVA 2`` configurations to import routes into VNC +.. for local VNC use: +.. +.. @verbatim +.. neighbor 192.168.1.105 remote-as 64512 +.. vnc redistribute mode plain +.. vnc redistribute ipv4 bgp-direct-to-nve-groups +.. @end verbatim + +.. _vnc-with-frr-route-reflector-config: + +VNC with FRR Route Reflector Configuration +------------------------------------------ + +A route reflector eliminates the need for a fully meshed NVA network by acting +as the hub between NVAs. :ref:`vnc-fig-vnc-frr-route-reflector` shows BGP +route reflector ``BGP Route Reflector 1`` (192.168.1.100) as a route reflector +for NVAs ``NVA 2``(192.168.1.101) and ``NVA 3`` (192.168.1.102). + +.. _vnc-fig-vnc-frr-route-reflector: +.. figure:: ../figures/fig-vnc-frr-route-reflector.png + :align: center + :alt: FRR Route Reflector + + Two NVAs and a BGP Route Reflector + +``NVA 2`` and ``NVA 3`` advertise NVE underlay-network IP addresses using the +Tunnel Encapsulation Attribute. ``BGP Route Reflector 1`` ``reflects'' +advertisements from ``NVA 2`` to ``NVA 3`` and vice versa. + +As in the example of :ref:`vnc-mesh-nva-config`, there are two NVE groups. The +172.16.0.0/16 address range is partitioned into two NVE groups, ``group1`` +(172.16.0.0/17) and ``group2`` (172.16.128.0/17). The NVE ``NVE 4``, ``NVE +7``, and ``NVE 8`` are members of the NVE group ``group1``. The NVEs ``NVE +5``, ``NVE 6``, and ``NVE 9`` are members of the NVE group ``group2``. + +:file:`bgpd.conf` for ``BGP Route Reflector 1`` on 192.168.1.100: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.100 + + neighbor 192.168.1.101 remote-as 64512 + neighbor 192.168.1.101 port 7179 + neighbor 192.168.1.101 description iBGP-client-192-168-1-101 + + neighbor 192.168.1.102 remote-as 64512 + neighbor 192.168.1.102 port 7179 + neighbor 192.168.1.102 description iBGP-client-192-168-1-102 + + address-family ipv4 unicast + neighbor 192.168.1.101 route-reflector-client + neighbor 192.168.1.102 route-reflector-client + exit-address-family + + address-family ipv4 vpn + neighbor 192.168.1.101 activate + neighbor 192.168.1.102 activate + + neighbor 192.168.1.101 route-reflector-client + neighbor 192.168.1.102 route-reflector-client + exit-address-family + + exit + +:file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.101 + + neighbor 192.168.1.100 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + exit-address-family + + vnc nve-group group1 + prefix vn 172.16.0.0/17 + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + exit + +:file:`bgpd.conf` for ``NVA 2`` on 192.168.1.102: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.102 + + neighbor 192.168.1.100 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + exit-address-family + + vnc defaults + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + + vnc nve-group group1 + prefix vn 172.16.128.0/17 + exit-vnc + exit + +While not shown, an NVA can also be configured as a route reflector. + +.. _vnc-with-commercial-route-reflector-config: + +VNC with Commercial Route Reflector Configuration +------------------------------------------------- + +This example is identical to :ref:`vnc-with-frr-route-reflector-config` +with the exception that the route reflector is a commercial router. Only the +VNC-relevant configuration is provided. + +.. figure:: ../figures/fig-vnc-commercial-route-reflector.png + :align: center + :alt: Commercial Route Reflector + + Two NVAs with a commercial route reflector + +:file:`bgpd.conf` for BGP route reflector ``Commercial Router`` on 192.168.1.104::: + + version 8.5R1.13; + routing-options { + rib inet.0 { + static { + route 172.16.0.0/16 next-hop 192.168.1.104; + } + } + autonomous-system 64512; + resolution { + rib inet.3 { + resolution-ribs inet.0; + } + rib bgp.l3vpn.0 { + resolution-ribs inet.0; + } + } + } + protocols { + bgp { + advertise-inactive; + family inet { + labeled-unicast; + } + group 1 { + type internal; + advertise-inactive; + advertise-peer-as; + import h; + family inet { + unicast; + } + family inet-vpn { + unicast; + } + cluster 192.168.1.104; + neighbor 192.168.1.101; + neighbor 192.168.1.102; + } + } + } + policy-options { + policy-statement h { + from protocol bgp; + then { + as-path-prepend 64512; + accept; + } + } + } + +:file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.101 + + neighbor 192.168.1.100 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + exit-address-family + + vnc nve-group group1 + prefix vn 172.16.0.0/17 + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + exit + +:file:`bgpd.conf` for ``NVA 3`` on 192.168.1.102: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.102 + + neighbor 192.168.1.100 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + exit-address-family + + vnc defaults + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + + vnc nve-group group1 + prefix vn 172.16.128.0/17 + exit-vnc + exit + +VNC with Redundant Route Reflectors Configuration +------------------------------------------------- + +This example combines the previous two +(:ref:`vnc-with-frr-route-reflector-config` and +:ref:`vnc-with-commercial-route-reflector-config`) into a redundant route +reflector configuration. BGP route reflectors ``BGP Route Reflector 1`` and +``Commercial Router`` are the route reflectors for NVAs ``NVA 2`` and ``NVA +3``. The two NVAs have connections to both route reflectors. + +.. figure:: ../figures/fig-vnc-redundant-route-reflectors.png + :align: center + :alt: Redundant Route Reflectors + + FRR-based NVA with redundant route reflectors + +:file:`bgpd.conf` for ``BPGD Route Reflector 1`` on 192.168.1.100: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.100 + bgp cluster-id 192.168.1.100 + + neighbor 192.168.1.104 remote-as 64512 + + neighbor 192.168.1.101 remote-as 64512 + neighbor 192.168.1.101 description iBGP-client-192-168-1-101 + neighbor 192.168.1.101 route-reflector-client + + neighbor 192.168.1.102 remote-as 64512 + neighbor 192.168.1.102 description iBGP-client-192-168-1-102 + neighbor 192.168.1.102 route-reflector-client + + address-family ipv4 vpn + neighbor 192.168.1.101 activate + neighbor 192.168.1.102 activate + neighbor 192.168.1.104 activate + + neighbor 192.168.1.101 route-reflector-client + neighbor 192.168.1.102 route-reflector-client + exit-address-family + exit + +:file:`bgpd.conf` for ``NVA 2`` on 192.168.1.101: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.101 + + neighbor 192.168.1.100 remote-as 64512 + neighbor 192.168.1.104 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + neighbor 192.168.1.104 activate + exit-address-family + + vnc nve-group group1 + prefix vn 172.16.0.0/17 + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + exit + +:file:`bgpd.conf` for ``NVA 3`` on 192.168.1.102: + +.. code-block:: frr + + router bgp 64512 + + bgp router-id 192.168.1.102 + + neighbor 192.168.1.100 remote-as 64512 + neighbor 192.168.1.104 remote-as 64512 + + address-family ipv4 vpn + neighbor 192.168.1.100 activate + neighbor 192.168.1.104 activate + exit-address-family + + vnc defaults + rd 64512:1 + response-lifetime 200 + rt both 1000:1 1000:2 + exit-vnc + + vnc nve-group group1 + prefix vn 172.16.128.0/17 + exit-vnc + exit + +:file:`bgpd.conf` for the Commercial Router route reflector on 192.168.1.104::: + + routing-options { + rib inet.0 { + static { + route 172.16.0.0/16 next-hop 192.168.1.104; + } + } + autonomous-system 64512; + resolution { + rib inet.3 { + resolution-ribs inet.0; + } + rib bgp.l3vpn.0 { + resolution-ribs inet.0; + } + } + } + protocols { + bgp { + advertise-inactive; + family inet { + labeled-unicast; + } + group 1 { + type internal; + advertise-inactive; + advertise-peer-as; + import h; + family inet { + unicast; + } + family inet-vpn { + unicast; + } + cluster 192.168.1.104; + neighbor 192.168.1.101; + neighbor 192.168.1.102; + } + + group 2 { + type internal; + advertise-inactive; + advertise-peer-as; + import h; + family inet { + unicast; + } + family inet-vpn { + unicast; + } + neighbor 192.168.1.100; + } + + } + } + policy-options { + policy-statement h { + from protocol bgp; + then { + as-path-prepend 64512; + accept; + } + } + } + +.. [#] The nve-id is carried in the route distinguisher. It is the second octet + of the eight-octet route distinguisher generated for Ethernet / L2 + advertisements. The first octet is a constant 0xFF, and the third + through eighth octets are set to the L2 + ethernet address being advertised. + diff --git a/doc/user/vrrp.rst b/doc/user/vrrp.rst new file mode 100644 index 0000000..d99fc23 --- /dev/null +++ b/doc/user/vrrp.rst @@ -0,0 +1,569 @@ +.. _vrrp: + +**** +VRRP +**** + +:abbr:`VRRP` stands for Virtual Router Redundancy Protocol. This protocol is +used to allow multiple backup routers on the same segment to take over +operation of each others' IP addresses if the primary router fails. This is +typically used to provide fault-tolerant gateways to hosts on the segment. + +FRR implements VRRPv2 (:rfc:`3768`) and VRRPv3 (:rfc:`5798`). For VRRPv2, no +authentication methods are supported; these are deprecated in the VRRPv2 +specification as they do not provide any additional security over the base +protocol. + +.. note:: + + - VRRP is supported on Linux 5.1+ + - VRRP does not implement Accept_Mode + +.. _vrrp-starting: + +Starting VRRP +============= + +.. include:: config-include.rst + +.. program:: vrrpd + +:abbr:`VRRP` supports all the common FRR daemon start options which are +documented elsewhere. + +.. _vrrp-protocol-overview: + +Protocol Overview +================= + +From :rfc:`5798`: + + VRRP specifies an election protocol that dynamically assigns responsibility + for a virtual router to one of the VRRP routers on a LAN. The VRRP router + controlling the IPv4 or IPv6 address(es) associated with a virtual router is + called the Master, and it forwards packets sent to these IPv4 or IPv6 + addresses. VRRP Master routers are configured with virtual IPv4 or IPv6 + addresses, and VRRP Backup routers infer the address family of the virtual + addresses being carried based on the transport protocol. Within a VRRP + router, the virtual routers in each of the IPv4 and IPv6 address families + are a domain unto themselves and do not overlap. The election process + provides dynamic failover in the forwarding responsibility should the Master + become unavailable. For IPv4, the advantage gained from using VRRP is a + higher-availability default path without requiring configuration of dynamic + routing or router discovery protocols on every end-host. For IPv6, the + advantage gained from using VRRP for IPv6 is a quicker switchover to Backup + routers than can be obtained with standard IPv6 Neighbor Discovery + mechanisms. + +VRRP accomplishes these goals primarily by using a virtual MAC address shared +between the physical routers participating in a VRRP virtual router. This +reduces churn in the neighbor tables of hosts and downstream switches and makes +router failover theoretically transparent to these devices. + +FRR implements the election protocol and handles changing the operating system +interface configuration in response to protocol state changes. + +As a consequence of the shared virtual MAC requirement, VRRP is currently +supported only on Linux, as Linux is the only operating system that provides +the necessary features in its network stack to make implementing this protocol +feasible. + +When a VRRP router is acting as the Master router, FRR allows the interface(s) +with the backed-up IP addresses to remain up and functional. When the router +transitions to Backup state, these interfaces are set into ``protodown`` mode. +This is an interface mode that is functionally equivalent to ``NO-CARRIER``. +Physical drivers typically use this state indication to drop traffic on an +interface. In the case of VRRP, the interfaces in question are macvlan devices, +which are virtual interfaces. Since the IP addresses managed by VRRP are on +these interfaces, this has the same effect as removing these addresses from the +interface, but is implemented as a state flag. + +.. _vrrp-configuration: + +Configuring VRRP +================ + +VRRP is configured on a per-interface basis, with some global defaults +accessible outside the interface context. + +.. _vrrp-system-configuration: + +System Configuration +-------------------- + +FRR's VRRP implementation uses Linux macvlan devices to to implement the shared +virtual MAC feature of the protocol. Currently, it does not create those system +interfaces - they must be configured outside of FRR before VRRP can be enabled +on them. + +Each interface on which VRRP will be enabled must have at least one macvlan +device configured with the virtual MAC and placed in the proper operation mode. +The addresses backed up by VRRP are assigned to these interfaces. + +Suppose you have an interface ``eth0`` with the following configuration: + +.. code-block:: console + + $ ip addr show eth0 + 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether 02:17:45:00:aa:aa brd ff:ff:ff:ff:ff:ff + inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0 + valid_lft 72532sec preferred_lft 72532sec + inet6 fe80::17:45ff:fe00:aaaa/64 scope link + valid_lft forever preferred_lft forever + +Suppose that the IPv4 and IPv6 addresses you want to back up are ``10.0.2.16`` +and ``2001:db8::370:7334``, and that they will be managed by the virtual router +with id ``5``. A macvlan device with the appropriate MAC address must be created +before VRRP can begin to operate. + +If you are using ``ifupdown2``, the configuration is as follows: + +.. code-block:: console + + iface eth0 + ... + vrrp 5 10.0.2.16/24 2001:0db8::0370:7334/64 + +Applying this configuration with ``ifreload -a`` will create the appropriate +macvlan device. If you are using ``iproute2``, the equivalent configuration is: + +.. code-block:: console + + ip link add vrrp4-2-1 link eth0 addrgenmode random type macvlan mode bridge + ip link set dev vrrp4-2-1 address 00:00:5e:00:01:05 + ip addr add 10.0.2.16/24 dev vrrp4-2-1 + ip link set dev vrrp4-2-1 up + + ip link add vrrp6-2-1 link eth0 addrgenmode random type macvlan mode bridge + ip link set dev vrrp6-2-1 address 00:00:5e:00:02:05 + ip addr add 2001:db8::370:7334/64 dev vrrp6-2-1 + ip link set dev vrrp6-2-1 up + +In either case, the created interfaces will look like this: + +.. code-block:: console + + $ ip addr show vrrp4-2-1 + 5: vrrp4-2-1@eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 + link/ether 00:00:5e:00:01:05 brd ff:ff:ff:ff:ff:ff + inet 10.0.2.16/24 scope global vrrp4-2-1 + valid_lft forever preferred_lft forever + inet6 fe80::dc56:d11a:e69d:ea72/64 scope link stable-privacy + valid_lft forever preferred_lft forever + + $ ip addr show vrrp6-2-1 + 8: vrrp6-2-1@eth0: mtu 1500 qdisc noqueue state UP group default qlen 1000 + link/ether 00:00:5e:00:02:05 brd ff:ff:ff:ff:ff:ff + inet6 2001:db8::370:7334/64 scope global + valid_lft forever preferred_lft forever + inet6 fe80::f8b7:c9dd:a1e8:9844/64 scope link stable-privacy + valid_lft forever preferred_lft forever + +Using ``vrrp4-2-1`` as an example, a few things to note about this interface: + +- It is slaved to ``eth0``; any packets transmitted on this interface will + egress via ``eth0`` +- Its MAC address is set to the VRRP IPv4 virtual MAC specified by the RFC for + :abbr:`VRID (Virtual Router ID)` ``5`` +- The :abbr:`VIP (Virtual IP)` address ``10.0.2.16`` must not be present on + the parent interface ``eth0``. +- The link local address on the interface is not derived from the interface + MAC + +First to note is that packets transmitted on this interface will egress via +``eth0``, but with their Ethernet source MAC set to the VRRP virtual MAC. This +is how FRR's VRRP implementation accomplishes the virtual MAC requirement on +real hardware. + +Ingress traffic is a more complicated matter. Macvlan devices have multiple +operating modes that change how ingress traffic is handled. Of relevance to +FRR's implementation are the ``bridge`` and ``private`` modes. In ``private`` +mode, any ingress traffic on ``eth0`` (in our example) with a source MAC +address equal to the MAC address on any of ``eth0``'s macvlan devices will be +placed *only* on that macvlan device. This curious behavior is undesirable, +since FRR's implementation of VRRP needs to be able to receive advertisements +from neighbors while in Backup mode - i.e., while its macvlan devices are in +``protodown on``. If the macvlan devices are instead set to ``bridge`` mode, +all ingress traffic shows up on all interfaces - including ``eth0`` - +regardless of source MAC or any other factor. Consequently, macvlans used by +FRR for VRRP must be set to ``bridge`` mode or the protocol will not function +correctly. + +As for the MAC address assigned to this interface, the last byte of the address +holds the :abbr:`VRID (Virtual Router Identifier)`, in this case ``0x05``. The +second to last byte is ``0x01``, as specified by the RFC for IPv4 operation. +The IPv6 MAC address is be identical except that the second to last byte is +defined to be ``0x02``. Two things to note from this arrangement: + +1. There can only be up to 255 unique Virtual Routers on an interface (only 1 + byte is available for the VRID) +2. IPv4 and IPv6 addresses must be assigned to different macvlan devices, + because they have different MAC addresses + +Finally, take note of the generated IPv6 link local address on the interface. +For interfaces on which VRRP will operate in IPv6 mode, this link local +*cannot* be derived using the usual EUI-64 method. This is because VRRP +advertisements are sent from the link local address of this interface, and VRRP +uses the source address of received advertisements as part of its election +algorithm. If the IPv6 link local of a router is equivalent to the IPv6 link +local in a received advertisement, this can cause both routers to assume the +Master role (very bad). ``ifupdown`` knows to set the ``addrgenmode`` of the +interface properly, but when using ``iproute2`` to create the macvlan devices, +you must be careful to manually specify ``addrgenmode random``. + +A brief note on the Backup state +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It is worth noting here that an alternate choice for the implementation of the +Backup state, such as removing all the IP addresses assigned to the macvlan +device or deleting their local routes instead of setting the device into +``protodown on``, would allow the protocol to function regardless of whether +the macvlan device(s) are set to ``private`` or ``bridge`` mode. Indeed, the +strange behavior of the kernel macvlan driver in ``private`` mode, whereby it +performs what may be thought of as a sort of interface-level layer 2 "NAT" +based on source MAC, can be traced back to a patch clearly designed to +accommodate a VRRP implementation from a different vendor. However, the +``protodown`` based implementation allows for a configuration model in which +FRR does not dynamically manage the addresses assigned on a system, but instead +just manages interface state. Such a scenario was in mind when this protocol +implementation was initially built, which is why the other choices are not +currently present. Since support for placing macvlan devices into ``protodown`` +was not added to Linux until version 5.1, this also explains the relatively +restrictive kernel versioning requirement. + +In the future other methods of implementing Backup state may be added along +with a configuration knob to choose between them. + +.. _vrrp-interface-configuration: + +Interface Configuration +----------------------- + +Continuing with the example from the previous section, we assume the macvlan +interfaces have been properly configured with the proper MAC addresses and the +IPvX addresses assigned. + +In FRR, a possible VRRPv3 configuration for this interface is: + +.. code-block:: frr + + interface eth0 + vrrp 5 version 3 + vrrp 5 priority 200 + vrrp 5 advertisement-interval 1500 + vrrp 5 ip 10.0.2.16 + vrrp 5 ipv6 2001:0db8::0370:7334 + +VRRP will activate as soon as the first IPvX address configuration line is +encountered. If you do not want this behavior, use the :clicmd:`vrrp (1-255) +shutdown` command, and apply the ``no`` form when you are ready to activate +VRRP. + +At this point executing ``show vrrp`` will display the following: + +.. code-block:: console + + ubuntu-bionic# show vrrp + + Virtual Router ID 5 + Protocol Version 3 + Autoconfigured Yes + Shutdown No + Interface eth0 + VRRP interface (v4) vrrp4-2-5 + VRRP interface (v6) vrrp6-2-5 + Primary IP (v4) 10.0.2.15 + Primary IP (v6) fe80::9b91:7155:bf6a:d386 + Virtual MAC (v4) 00:00:5e:00:01:05 + Virtual MAC (v6) 00:00:5e:00:02:05 + Status (v4) Master + Status (v6) Master + Priority 200 + Effective Priority (v4) 200 + Effective Priority (v6) 200 + Preempt Mode Yes + Accept Mode Yes + Advertisement Interval 1500 ms + Master Advertisement Interval (v4) 1000 ms + Master Advertisement Interval (v6) 1000 ms + Advertisements Tx (v4) 14 + Advertisements Tx (v6) 14 + Advertisements Rx (v4) 0 + Advertisements Rx (v6) 0 + Gratuitous ARP Tx (v4) 1 + Neigh. Adverts Tx (v6) 1 + State transitions (v4) 2 + State transitions (v6) 2 + Skew Time (v4) 210 ms + Skew Time (v6) 210 ms + Master Down Interval (v4) 3210 ms + Master Down Interval (v6) 3210 ms + IPv4 Addresses 1 + .................................. 10.0.2.16 + IPv6 Addresses 1 + .................................. 2001:db8::370:7334 + +At this point, VRRP has sent gratuitous ARP requests for the IPv4 address, +Unsolicited Neighbor Advertisements for the IPv6 address, and has asked Zebra +to send Router Advertisements on its behalf. It is also transmitting VRRPv3 +advertisements on the macvlan interfaces. + +The Primary IP fields are of some interest, as the behavior may be +counterintuitive. These fields show the source address used for VRRP +advertisements. Although VRRPv3 advertisements are always transmitted on the +macvlan interfaces, in the IPv4 case the source address is set to the primary +IPv4 address on the base interface, ``eth0`` in this case. This is a protocol +requirement, and IPv4 VRRP will not function unless the base interface has an +IPv4 address assigned. In the IPv6 case the link local of the macvlan interface +is used. + +If any misconfiguration errors are detected, VRRP for the misconfigured address +family will not come up and the configuration issue will be logged to FRR's +configured logging destination. + +Per the RFC, IPv4 and IPv6 virtual routers are independent of each other. For +instance, it is possible for the IPv4 router to be in Backup state while the +IPv6 router is in Master state; or for either to be completely inoperative +while the other is operative, etc. Instances sharing the same base interface +and VRID are shown together in the show output for conceptual convenience. + +To complete your VRRP deployment, configure other routers on the segment with +the exact same system and FRR configuration as shown above. Provided each +router receives the others' VRRP advertisements, the Master election protocol +will run, one Master will be elected, and the other routers will place their +macvlan interfaces into ``protodown on`` until Master fails or priority values +are changed to favor another router. + +Switching the protocol version to VRRPv2 is accomplished simply by changing +``version 3`` to ``version 2`` in the VRID configuration line. Note that VRRPv2 +does not support IPv6, so any IPv6 configuration will be rejected by FRR when +using VRRPv2. + +.. note:: + + All VRRP routers initially start in Backup state, and wait for the + calculated Master Down Interval to pass before they assume Master status. + This prevents downstream neighbor table churn if another router is already + Master with higher priority, meaning this box will ultimately assume Backup + status once the first advertisement is received. However, if the calculated + Master Down Interval is high and this router is configured such that it will + ultimately assume Master status, then it will take a while for this to + happen. This is a known issue. + + +All interface configuration commands are documented below. + +.. clicmd:: vrrp (1-255) [version (2-3)] + + Create a VRRP router with the specified VRID on the interface. Optionally + specify the protocol version. If the protocol version is not specified, the + default is VRRPv3. + +.. clicmd:: vrrp (1-255) advertisement-interval (10-40950) + + Set the advertisement interval. This is the interval at which VRRP + advertisements will be sent. Values are given in milliseconds, but must be + multiples of 10, as VRRP itself uses centiseconds. + +.. clicmd:: vrrp (1-255) ip A.B.C.D + + Add an IPv4 address to the router. This address must already be configured + on the appropriate macvlan device. Adding an IP address to the router will + implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to + override this behavior. + +.. clicmd:: vrrp (1-255) ipv6 X:X::X:X + + Add an IPv6 address to the router. This address must already be configured + on the appropriate macvlan device. Adding an IP address to the router will + implicitly activate the router; see :clicmd:`[no] vrrp (1-255) shutdown` to + override this behavior. + + This command will fail if the protocol version is set to VRRPv2, as VRRPv2 + does not support IPv6. + +.. clicmd:: vrrp (1-255) preempt + + Toggle preempt mode. When enabled, preemption allows Backup routers with + higher priority to take over Master status from the existing Master. Enabled + by default. + +.. clicmd:: vrrp (1-255) checksum-with-ipv4-pseudoheader + + Specify whether VRRPv3 checksum should involve IPv4 pseudoheader. This + command should not affect VRRPv2 and IPv6. Enabled by default. + +.. clicmd:: vrrp (1-255) priority (1-254) + + Set the router priority. The router with the highest priority is elected as + the Master. If all routers in the VRRP virtual router are configured with + the same priority, the router with the highest primary IP address is elected + as the Master. Priority value 255 is reserved for the acting Master router. + +.. clicmd:: vrrp (1-255) shutdown + + Place the router into administrative shutdown. VRRP will not activate for + this router until this command is removed with the ``no`` form. + +.. _vrrp-global-configuration: + +Global Configuration +-------------------- + +Show commands, global defaults and debugging configuration commands. + +.. clicmd:: show vrrp [interface INTERFACE] [(1-255)] [json] + + Shows VRRP status for some or all configured VRRP routers. Specifying an + interface will only show routers configured on that interface. Specifying a + VRID will only show routers with that VRID. Specifying ``json`` will dump + each router state in a JSON array. + +.. clicmd:: debug vrrp [{protocol|autoconfigure|packets|sockets|ndisc|arp|zebra}] + + Toggle debugging logs for VRRP components. + If no component is specified, debugging for all components are turned on/off. + + protocol + Logs state changes, election protocol decisions, and interface status + changes. + + autoconfigure + Logs actions taken by the autoconfiguration procedures. See + :ref:`vrrp-autoconfiguration`. + + packets + Logs details of ingress and egress packets. Includes packet decodes and + hex dumps. + + sockets + Logs details of socket configuration and initialization. + + ndisc + Logs actions taken by the Neighbor Discovery component of VRRP. + + arp + Logs actions taken by the ARP component of VRRP. + + zebra + Logs communications with Zebra. + +.. clicmd:: vrrp default + + Configure defaults for new VRRP routers. These values will not affect + already configured VRRP routers, but will be applied to newly configured + ones. + +.. _vrrp-autoconfiguration: + +Autoconfiguration +----------------- + +In light of the complicated configuration required on the base system before +VRRP can be enabled, FRR has the ability to automatically configure VRRP +sessions by inspecting the interfaces present on the system. Since it is quite +unlikely that macvlan devices with VRRP virtual MACs will exist on systems not +using VRRP, this can be a convenient shortcut to automatically generate FRR +configuration. + +After configuring the interfaces as described in +:ref:`vrrp-system-configuration`, and configuring any defaults you may want, +execute the following command: + +.. clicmd:: vrrp autoconfigure [version (2-3)] + + Generates VRRP configuration based on the interface configuration on the + base system. If the protocol version is not specified, the default is VRRPv3. + Any existing interfaces that are configured properly for VRRP - + i.e. have the correct MAC address, link local address (when required), IPv4 + and IPv6 addresses - are used to create a VRRP router on their parent + interfaces, with VRRP IPvX addresses taken from the addresses assigned to + the macvlan devices. The generated configuration appears in the output of + ``show run``, which can then be modified as needed and written to the config + file. The ``version`` parameter controls the protocol version; if using + VRRPv2, keep in mind that IPv6 is not supported and will not be configured. + +The following configuration is then generated for you: + +.. code-block:: frr + + interface eth0 + vrrp 5 + vrrp 5 ip 10.0.2.16 + vrrp 5 ipv6 2001:db8::370:7334 + + +VRRP is automatically activated. Global defaults, if set, are applied. + +You can then edit this configuration with **vtysh** as needed, and commit it by +writing to the configuration file. + + +Troubleshooting +--------------- + +My virtual routers are not seeing each others' advertisements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Check: + +- Is your kernel at least 5.1? +- Did you set the macvlan devices to ``bridge`` mode? +- If using IPv4 virtual addresses, does the parent of the macvlan devices have + an IPv4 address? +- If using IPv6 virtual addresses, is ``addrgenmode`` correctly set to + ``random`` and not the default ``eui64``? +- Is a firewall (``iptables``) or policy (``ip rule``) dropping multicast + traffic? +- Do you have unusual ``sysctls`` enabled that could affect the operation of + multicast traffic? +- Are you running in ESXi? See below. + + +My master router is not forwarding traffic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There's several possible causes here. If you're sure your configuration is +otherwise correct, the following sysctl likely needs to be turned on: + +.. code-block:: console + + sysctl -w net.ipv4.conf.eth0.ignore_routes_with_linkdown=1 + +Without this setting, it's possible to create topologies in which virtual +routers holding mastership status will not forward traffic. + +Issue reference: https://github.com/FRRouting/frr/issues/7391 + + +My router is running in ESXi and VRRP isn't working +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, ESXi traffic security settings don't allow traffic to egress a VNIC +that does not have the MAC address assigned to the VNIC. This breaks VRRP, +since virtual MACs are the basis of the protocol. + +On ESXi before 6.7, you need to enable Promiscuous Mode in the ESXi settings. +This is a significant security issue in some deployments so make sure you +understand what you're doing. On 6.7 and later, you can use the MAC Learning +feature instead, explained `here +`_. + +Issue reference: https://github.com/FRRouting/frr/issues/5386 + + +My router cannot interoperate with branded routers / L3 switches +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +FRR includes a pseudoheader when calculating VRRPv3 checksums by default, +regardless of whether it's IPv4 or IPv6. + +Some vendors have different interpretations of `VRRPv3 RFC 5798 #5.2.8 +`_. In such cases, +their checksums are calculated with a pseudoheader only when it comes to IPv6. + +You need to disable ``checksum-with-ipv4-pseudoheader`` so that FRR computes and +accepts such checksums. + +Issue reference: https://github.com/FRRouting/frr/issues/9951 diff --git a/doc/user/vtysh.rst b/doc/user/vtysh.rst new file mode 100644 index 0000000..9722231 --- /dev/null +++ b/doc/user/vtysh.rst @@ -0,0 +1,226 @@ +.. _vty-shell: + +********* +VTY shell +********* + +.. program:: configure + +*vtysh* provides a combined frontend to all FRR daemons in a single combined +session. It is enabled by default at build time, but can be disabled through +the :option:`--disable-vtysh` option to the configure script. + +*vtysh* has a configuration file, :file:`vtysh.conf`. The location of that +file cannot be changed from |INSTALL_PREFIX_ETC| since it contains options +controlling authentication behavior. This file will also not be written by +configuration-save commands, it is intended to be updated manually by an +administrator with an external editor. + +.. warning:: + + This also means the ``hostname``, ``domainname``, and ``banner motd`` commands + (which do have effect for vtysh) need to be manually updated + in :file:`vtysh.conf`. + + +.. clicmd:: copy FILENAME running-config + + Process and load a configuration file manually; each line in the + file is read and processed as if it were being typed (or piped) to + vtysh. + + +Live logs +========= + +.. clicmd:: terminal monitor [DAEMON] + + Receive and display log messages. + + It is not currently possible to change the minimum message priority (fixed + to debug) or output formatting. These will likely be made configurable in + the future. + + Log messages are received asynchronously and may be printed both during + command execution as well as while on the prompt. They are printed to + stderr, unlike regular CLI output which is printed to stdout. The intent is + that stdin/stdout might be driven by some script while log messages are + visible on stderr. If stdout and stderr are the same file, the prompt and + pending input will be cleared and reprinted appropriately. + + .. note:: + + If ``vtysh`` cannot keep up, some log messages may be lost. The daemons + do **not** wait for, get blocked by, or buffer messages for ``vtysh``. + + +Pager usage +=========== + +*vtysh* can call an external paging program (e.g. *more* or *less*) to +paginate long output from commands. This feature used to be enabled by +default but is now controlled by the ``VTYSH_PAGER`` environment variable +and the :clicmd:`terminal paginate` command: + +.. envvar:: VTYSH_PAGER + + If set, the ``VTYSH_PAGER`` environment variable causes *vtysh* to pipe + output from commands through the given command. Note that this happens + regardless of the length of the output. As such, standard pager behavior + (particularly waiting at the end of output) tends to be annoying to the + user. Using ``less -EFX`` is recommended for a better user experience. + + If this environment variable is unset, *vtysh* defaults to not using any + pager. + + This variable should be set by the user according to their preferences, + in their :file:`~/.profile` file. + +.. clicmd:: terminal paginate + + Enables/disables vtysh output pagination. This command is intended to + be placed in :file:`vtysh.conf` to set a system-wide default. If this + is enabled but ``VTYSH_PAGER`` is not set, the system default pager + (likely ``more`` or ``/usr/bin/pager``) will be used. + + +Permissions and setup requirements +================================== + +*vtysh* connects to running daemons through Unix sockets located in +|INSTALL_PREFIX_STATE|. Running vtysh thus requires access to that directory, +plus membership in the |INSTALL_VTY_GROUP| group (which is the group that the +daemons will change ownership of their sockets to). + +To restrict access to FRR configuration, make sure no unauthorized users are +members of the |INSTALL_VTY_GROUP| group. + +.. warning:: + + VTYSH implements a CLI option ``-u, --user`` that disallows entering the + characters "en" on the command line, which ideally restricts access to + configuration commands. However, VTYSH was never designed to be a privilege + broker and is not built using secure coding practices. No guarantees of + security are provided for this option and under no circumstances should this + option be used to provide any semblance of security or read-only access to + FRR. + +PAM support (experimental) +-------------------------- + +vtysh has working (but rather useless) PAM support. It will perform an +"authenticate" PAM call using |PACKAGE_NAME| as service name. No other +(accounting, session, password change) calls will be performed by vtysh. + +Users using vtysh still need to have appropriate access to the daemons' VTY +sockets, usually by being member of the |INSTALL_VTY_GROUP| group. If they +have this membership, PAM support is useless since they can connect to daemons +and issue commands using some other tool. Alternatively, the *vtysh* binary +could be made SGID (set group ID) to the |INSTALL_VTY_GROUP| group. + +.. warning:: + + No security guarantees are made for this configuration. + + +.. clicmd:: username USERNAME nopassword + + If PAM support is enabled at build-time, this command allows disabling the + use of PAM on a per-user basis. If vtysh finds that an user is trying to + use vtysh and a "nopassword" entry is found, no calls to PAM will be made + at all. + + +.. _integrated-configuration-file: + +Integrated configuration file +============================= + +FRR uses a single configuration file, :file:`frr.conf`, for all daemons. This +replaces the individual files like :file:`zebra.conf` or :file:`bgpd.conf` used +in previous versions of the software. + +:file:`frr.conf` is located in |INSTALL_PREFIX_ETC|. All daemons check for the +existence of this file at startup, and if it exists will not load their +individual configuration files. Instead, ``vtysh -b`` must be invoked to +process :file:`frr.conf` and apply its settings to the individual daemons. + +.. warning:: + + *vtysh -b* must also be executed after restarting any daemon. + + +Configuration saving, file ownership and permissions +---------------------------------------------------- + +The :file:`frr.conf` file is not written by any of the daemons; instead *vtysh* +contains the necessary logic to collect configuration from all of the daemons, +combine it and write it out. + +.. warning:: + + Daemons must be running for *vtysh* to be able to collect their + configuration. Any configuration from non-running daemons is permanently + lost after doing a configuration save. + +Since the *vtysh* command may be running as ordinary user on the system, +configuration writes will be tried through *watchfrr*, using the ``write +integrated`` command internally. Since *watchfrr* is running as superuser, +*vtysh* is able to ensure correct ownership and permissions on +:file:`frr.conf`. + +If *watchfrr* is not running or the configuration write fails, *vtysh* will +attempt to directly write to the file. This is likely to fail if running as +unprivileged user; alternatively it may leave the file with incorrect owner or +permissions. + +Writing the configuration can be triggered directly by invoking *vtysh -w*. +This may be useful for scripting. Note this command should be run as either the +superuser or the FRR user. + +We recommend you do not mix the use of the two types of files. + +.. clicmd:: service integrated-vtysh-config + + + Control whether integrated :file:`frr.conf` file is written when + 'write file' is issued. + + These commands need to be placed in :file:`vtysh.conf` to have any effect. + Note that since :file:`vtysh.conf` is not written by FRR itself, they + therefore need to be manually placed in that file. + + This command has 3 states: + + + service integrated-vtysh-config + *vtysh* will always write :file:`frr.conf`. + + + no service integrated-vtysh-config + *vtysh* will never write :file:`frr.conf`; instead it will ask + daemons to write their individual configuration files. + + Neither option present (default) + *vtysh* will check whether :file:`frr.conf` exists. If it does, + configuration writes will update that file. Otherwise, writes are performed + through the individual daemons. + + This command is primarily intended for packaging/distribution purposes, to + preset one of the two operating modes and ensure consistent operation across + installations. + +.. clicmd:: write integrated + + Unconditionally (regardless of ``service integrated-vtysh-config`` setting) + write out integrated :file:`frr.conf` file through *watchfrr*. If *watchfrr* + is not running, this command is unavailable. + +.. warning:: + + Configuration changes made while some daemon is not running will be + invisible to that daemon. The daemon will start up with its saved + configuration (either in its individual configuration file, or in + :file:`frr.conf`). This is particularly troublesome for route-maps and + prefix lists, which would otherwise be synchronized between daemons. + diff --git a/doc/user/watchfrr.rst b/doc/user/watchfrr.rst new file mode 100644 index 0000000..3a19e3c --- /dev/null +++ b/doc/user/watchfrr.rst @@ -0,0 +1,28 @@ +.. _watchfrr: + +******** +WATCHFRR +******** + +:abbr:`WATCHFRR` is a daemon that handles failed daemon processes and +intelligently restarts them as needed. + +Starting WATCHFRR +================= + +WATCHFRR is started as per normal systemd startup and typically does not +require end users management. + +WATCHFRR commands +================= + +.. clicmd:: show watchfrr + + Give status information about the state of the different daemons being + watched by WATCHFRR + +.. clicmd:: watchfrr ignore DAEMON + + Tell WATCHFRR to ignore a particular DAEMON if it goes unresponsive. + This is particularly useful when you are a developer and need to debug + a working system, without watchfrr pulling the rug out from under you. diff --git a/doc/user/wecmp_linkbw.rst b/doc/user/wecmp_linkbw.rst new file mode 100644 index 0000000..4df6559 --- /dev/null +++ b/doc/user/wecmp_linkbw.rst @@ -0,0 +1,297 @@ +.. _wecmp_linkbw: + +Weighted ECMP using BGP link bandwidth +====================================== + +.. _features-of-wecmp-linkbw: + +Overview +-------- + +In normal equal cost multipath (ECMP), the route to a destination has +multiple next hops and traffic is expected to be equally distributed +across these next hops. In practice, flow-based hashing is used so that +all traffic associated with a particular flow uses the same next hop, +and by extension, the same path across the network. + +Weighted ECMP using BGP link bandwidth introduces support for network-wide +unequal cost multipathing (UCMP) to an IP destination. The unequal cost +load balancing is implemented by the forwarding plane based on the weights +associated with the next hops of the IP prefix. These weights are computed +based on the bandwidths of the corresponding multipaths which are encoded +in the ``BGP link bandwidth extended community`` as specified in +[Draft-IETF-idr-link-bandwidth]_. Exchange of an appropriate BGP link +bandwidth value for a prefix across the network results in network-wide +unequal cost multipathing. + +One of the primary use cases of this capability is in the data center when +a service (represented by its anycast IP) has an unequal set of resources +across the regions (e.g., PODs) of the data center and the network itself +provides the load balancing function instead of an external load balancer. +Refer to [Draft-IETF-mohanty-bess-ebgp-dmz]_ and :rfc:`7938` for details +on this use case. This use case is applicable in a pure L3 network as +well as in a EVPN network. + +The traditional use case for BGP link bandwidth to load balance traffic +to the exit routers in the AS based on the bandwidth of their external +eBGP peering links is also supported. + + +Design Principles +----------------- + +Next hop weight computation and usage +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As described, in UCMP, there is a weight associated with each next hop of an +IP prefix, and traffic is expected to be distributed across the next hops in +proportion to their weight. The weight of a next hop is a simple factoring +of the bandwidth of the corresponding path against the total bandwidth of +all multipaths, mapped to the range 1 to 100. What happens if not all the +paths in the multipath set have link bandwidth associated with them? In such +a case, in adherence to [Draft-IETF-idr-link-bandwidth]_, the behavior +reverts to standard ECMP among all the multipaths, with the link bandwidth +being effectively ignored. + +Note that there is no change to either the BGP best path selection algorithm +or to the multipath computation algorithm; the mapping of link bandwidth to +weight happens at the time of installation of the route in the RIB. + +If data forwarding is implemented by means of the Linux kernel, the next hop’s +weight is used in the hash calculation. The kernel uses the Hash threshold +algorithm and use of the next hop weight is built into it; next hops need +not be expanded to achieve UCMP. UCMP for IPv4 is available in older Linux +kernels too, while UCMP for IPv6 is available from the 4.16 kernel onwards. + +If data forwarding is realized in hardware, common implementations expand +the next hops (i.e., they are repeated) in the ECMP container in proportion +to their weight. For example, if the weights associated with 3 next hops for +a particular route are 50, 25 and 25 and the ECMP container has a size of 16 +next hops, the first next hop will be repeated 8 times and the other 2 next +hops repeated 4 times each. Other implementations are also possible. + +Unequal cost multipath across a network +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For the use cases listed above, it is not sufficient to support UCMP on just +one router (e.g., egress router), or individually, on multiple routers; UCMP +must be deployed across the entire network. This is achieved by employing the +BGP link-bandwidth extended community. + +At the router which originates the BGP link bandwidth, there has to be user +configuration to trigger it, which is described below. Receiving routers +would use the received link bandwidth from their downstream routers to +determine the next hop weight as described in the earlier section. Further, +if the received link bandwidth is a transitive attribute, it would be +propagated to eBGP peers, with the additional change that if the next hop +is set to oneself, the cumulative link bandwidth of all downstream paths +is propagated to other routers. In this manner, the entire network will +know how to distribute traffic to an anycast service across the network. + +The BGP link-bandwidth extended community is encoded in bytes-per-second. +In the use case where UCMP must be based on the number of paths, a reference +bandwidth of 1 Mbps is used. So, for example, if there are 4 equal cost paths +to an anycast IP, the encoded bandwidth in the extended community will be +500,000. The actual value itself doesn’t matter as long as all routers +originating the link-bandwidth are doing it in the same way. + + +Configuration Guide +------------------- + +The configuration for weighted ECMP using BGP link bandwidth requires +one essential step - using a route-map to inject the link bandwidth +extended community. An additional option is provided to control the +processing of received link bandwidth. + +Injecting link bandwidth into the network +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +At the "entry point" router that is injecting the prefix to which weighted +load balancing must be performed, a route-map must be configured to +attach the link bandwidth extended community. + +For the use case of providing weighted load balancing for an anycast service, +this configuration will typically need to be applied at the TOR or Leaf +router that is connected to servers which provide the anycast service and +the bandwidth would be based on the number of multipaths for the destination. + +For the use case of load balancing to the exit router, the exit router should +be configured with the route map specifying the a bandwidth value that +corresponds to the bandwidth of the link connecting to its eBGP peer in the +adjoining AS. In addition, the link bandwidth extended community must be +explicitly configured to be non-transitive. + +The complete syntax of the route-map set command can be found at +:ref:`bgp-extended-communities-in-route-map` + +This route-map is supported only at two attachment points: +(a) the outbound route-map attached to a peer or peer-group, per address-family +(b) the EVPN advertise route-map used to inject IPv4 or IPv6 unicast routes +into EVPN as type-5 routes. + +Since the link bandwidth origination is done by using a route-map, it can +be constrained to certain prefixes (e.g., only for anycast services) or it +can be generated for all prefixes. Further, when the route-map is used in +the neighbor context, the link bandwidth usage can be constrained to certain +peers only. + +A sample configuration is shown below and illustrates link bandwidth +advertisement towards the "SPINE" peer-group for anycast IPs in the +range 192.168.x.x + +.. code-block:: frr + + ip prefix-list anycast_ip seq 10 permit 192.168.0.0/16 le 32 + route-map anycast_ip permit 10 + match ip address prefix-list anycast_ip + set extcommunity bandwidth num-multipaths + route-map anycast_ip permit 20 + ! + router bgp 65001 + neighbor SPINE peer-group + neighbor SPINE remote-as external + neighbor 172.16.35.1 peer-group SPINE + neighbor 172.16.36.1 peer-group SPINE + ! + address-family ipv4 unicast + network 110.0.0.1/32 + network 192.168.44.1/32 + neighbor SPINE route-map anycast_ip out + exit-address-family + ! + + +Controlling link bandwidth processing on the receiver +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +There is no configuration necessary to process received link bandwidth and +translate it into the weight associated with the corresponding next hop; +that happens by default. If some of the multipaths do not have the link +bandwidth extended community, the default behavior is to revert to normal +ECMP as recommended in [Draft-IETF-idr-link-bandwidth]_. + +The operator can change these behaviors with the following configuration: + +.. clicmd:: bgp bestpath bandwidth + +The different options imply behavior as follows: + +- ignore: Ignore link bandwidth completely for route installation + (i.e., do regular ECMP, not weighted) +- skip-missing: Skip paths without link bandwidth and do UCMP among + the others (if at least some paths have link-bandwidth) +- default-weight-for-missing: Assign a low default weight (value 1) + to paths not having link bandwidth + +This configuration is per BGP instance similar to other BGP route-selection +controls; it operates on both IPv4-unicast and IPv6-unicast routes in that +instance. In an EVPN network, this configuration (if required) should be +implemented in the tenant VRF and is again applicable for IPv4-unicast and +IPv6-unicast, including the ones sourced from EVPN type-5 routes. + +A sample snippet of FRR configuration on a receiver to skip paths without +link bandwidth and do weighted ECMP among the other paths (if some of them +have link bandwidth) is as shown below. + +.. code-block:: frr + + router bgp 65021 + bgp bestpath as-path multipath-relax + bgp bestpath bandwidth skip-missing + neighbor LEAF peer-group + neighbor LEAF remote-as external + neighbor 172.16.35.2 peer-group LEAF + neighbor 172.16.36.2 peer-group LEAF + ! + address-family ipv4 unicast + network 130.0.0.1/32 + exit-address-family + ! + + +Stopping the propagation of the link bandwidth outside a domain +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The link bandwidth extended community will get automatically propagated +with the prefix to EBGP peers, if it is encoded as a transitive attribute +by the originator. If this propagation has to be stopped outside of a +particular domain (e.g., stopped from being propagated to routers outside +of the data center core network), the mechanism available is to disable +the advertisement of all BGP extended communities on the specific peering/s. +In other words, the propagation cannot be blocked just for the link bandwidth +extended community. The configuration to disable all extended communities +can be applied to a peer or peer-group (per address-family). + +Of course, the other common way to stop the propagation of the link bandwidth +outside the domain is to block the prefixes themselves from being advertised +and possibly, announce only an aggregate route. This would be quite common +in a EVPN network. + +BGP link bandwidth and UCMP monitoring & troubleshooting +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Existing operational commands to display the BGP routing table for a specific +prefix will show the link bandwidth extended community also, if present. + +An example of an IPv4-unicast route received with the link bandwidth +attribute from two peers is shown below: + +.. code-block:: frr + + CLI# show bgp ipv4 unicast 192.168.10.1/32 + BGP routing table entry for 192.168.10.1/32 + Paths: (2 available, best #2, table default) + Advertised to non peer-group peers: + l1(swp1) l2(swp2) l3(swp3) l4(swp4) + 65002 + fe80::202:ff:fe00:1b from l2(swp2) (110.0.0.2) + (fe80::202:ff:fe00:1b) (used) + Origin IGP, metric 0, valid, external, multipath, bestpath-from-AS 65002 + Extended Community: LB:65002:125000000 (1000.000 Mbps) + Last update: Thu Feb 20 18:34:16 2020 + + 65001 + fe80::202:ff:fe00:15 from l1(swp1) (110.0.0.1) + (fe80::202:ff:fe00:15) (used) + Origin IGP, metric 0, valid, external, multipath, bestpath-from-AS 65001, best (Older Path) + Extended Community: LB:65001:62500000 (500.000 Mbps) + Last update: Thu Feb 20 18:22:34 2020 + +The weights associated with the next hops of a route can be seen by querying +the RIB for a specific route. + +For example, the next hop weights corresponding to the link bandwidths in the +above example is illustrated below: + +.. code-block:: frr + + spine1# show ip route 192.168.10.1/32 + Routing entry for 192.168.10.1/32 + Known via "bgp", distance 20, metric 0, best + Last update 00:00:32 ago + * fe80::202:ff:fe00:1b, via swp2, weight 66 + * fe80::202:ff:fe00:15, via swp1, weight 33 + +For troubleshooting, existing debug logs ``debug bgp updates``, +``debug bgp bestpath ``, ``debug bgp zebra`` and +``debug zebra kernel`` can be used. + +A debug log snippet when ``debug bgp zebra`` is enabled and a route is +installed by BGP in the RIB with next hop weights is shown below: + +.. code-block:: frr + + 2020-02-29T06:26:19.927754+00:00 leaf1 bgpd[5459]: bgp_zebra_announce: p=192.168.150.1/32, bgp_is_valid_label: 0 + 2020-02-29T06:26:19.928096+00:00 leaf1 bgpd[5459]: Tx route add VRF 33 192.168.150.1/32 metric 0 tag 0 count 2 + 2020-02-29T06:26:19.928289+00:00 leaf1 bgpd[5459]: nhop [1]: 110.0.0.6 if 35 VRF 33 wt 50 RMAC 0a:11:2f:7d:35:20 + 2020-02-29T06:26:19.928479+00:00 leaf1 bgpd[5459]: nhop [2]: 110.0.0.5 if 35 VRF 33 wt 50 RMAC 32:1e:32:a3:6c:bf + 2020-02-29T06:26:19.928668+00:00 leaf1 bgpd[5459]: bgp_zebra_announce: 192.168.150.1/32: announcing to zebra (recursion NOT set) + + +References +---------- + +.. [Draft-IETF-idr-link-bandwidth] +.. [Draft-IETF-mohanty-bess-ebgp-dmz] + diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst new file mode 100644 index 0000000..37644dc --- /dev/null +++ b/doc/user/zebra.rst @@ -0,0 +1,1964 @@ +.. _zebra: + +***** +Zebra +***** + +*zebra* is an IP routing manager. It provides kernel routing +table updates, interface lookups, and redistribution of routes between +different routing protocols. + +.. _invoking-zebra: + +Invoking zebra +============== + +Besides the common invocation options (:ref:`common-invocation-options`), the +*zebra* specific invocation options are listed below. + +.. program:: zebra + +.. option:: -b, --batch + + Runs in batch mode. *zebra* parses configuration file and terminates + immediately. + +.. option:: -K TIME, --graceful_restart TIME + + If this option is specified, the graceful restart time is TIME seconds. + Zebra, when started, will read in routes. Those routes that Zebra + identifies that it was the originator of will be swept in TIME seconds. + If no time is specified then we will sweep those routes immediately. + Under the \*BSD's, there is no way to properly store the originating + route and the route types in this case will show up as a static route + with an admin distance of 255. + +.. option:: -r, --retain + + When program terminates, do not flush routes installed by *zebra* from the + kernel. + +.. option:: -e X, --ecmp X + + Run zebra with a limited ecmp ability compared to what it is compiled to. + If you are running zebra on hardware limited functionality you can + force zebra to limit the maximum ecmp allowed to X. This number + is bounded by what you compiled FRR with as the maximum number. + +.. option:: -n, --vrfwnetns + + When *Zebra* starts with this option, the VRF backend is based on Linux + network namespaces. That implies that all network namespaces discovered by + ZEBRA will create an associated VRF. The other daemons will operate on the VRF + VRF defined by *Zebra*, as usual. If this option is specified when running + *Zebra*, one must also specify the same option for *mgmtd*. + + .. seealso:: :ref:`zebra-vrf` + +.. option:: -z , --socket + + If this option is supplied on the cli, the path to the zebra + control socket(zapi), is used. This option overrides a -N + option if handed to it on the cli. + +.. option:: --v6-rr-semantics + + The linux kernel is receiving the ability to use the same route + replacement semantics for v6 that v4 uses. If you are using a + kernel that supports this functionality then run *Zebra* with this + option and we will use Route Replace Semantics instead of delete + than add. + +.. option:: --routing-table + + Specify which kernel routing table *Zebra* should communicate with. + If this option is not specified the default table (RT_TABLE_MAIN) is + used. + +.. option:: --asic-offload=[notify_on_offload|notify_on_ack] + + The linux kernel has the ability to use asic-offload ( see switchdev + development ). When the operator knows that FRR will be working in + this way, allow them to specify this with FRR. At this point this + code only supports asynchronous notification of the offload state. + In other words the initial ACK received for linux kernel installation + does not give zebra any data about what the state of the offload + is. This option takes the optional parameters notify_on_offload + or notify_on_ack. This signals to zebra to notify upper level + protocols about route installation/update on ack received from + the linux kernel or from offload notification. + + +.. option:: -s , --nl-bufsize + + Allow zebra to modify the default receive buffer size to SIZE + in bytes. Under \*BSD only the -s option is available. + +.. option:: --v6-with-v4-nexthops + + Signal to zebra that v6 routes with v4 nexthops are accepted + by the underlying dataplane. This will be communicated to + the upper level daemons that can install v6 routes with v4 + nexthops. + +.. _interface-commands: + +Configuration Addresses behaviour +================================= + +At startup, *Zebra* will first discover the underlying networking objects +from the operating system. This includes interfaces, addresses of +interfaces, static routes, etc. Then, it will read the configuration +file, including its own interface addresses, static routes, etc. All this +information comprises the operational context from *Zebra*. But +configuration context from *Zebra* will remain the same as the one from +:file:`zebra.conf` config file. As an example, executing the following +:clicmd:`show running-config` will reflect what was in :file:`zebra.conf`. +In a similar way, networking objects that are configured outside of the +*Zebra* like *iproute2* will not impact the configuration context from +*Zebra*. This behaviour permits you to continue saving your own config +file, and decide what is really to be pushed on the config file, and what +is dependent on the underlying system. +Note that inversely, from *Zebra*, you will not be able to delete networking +objects that were previously configured outside of *Zebra*. + + +Interface Commands +================== + +.. _standard-commands: + +Standard Commands +----------------- + + +.. clicmd:: interface IFNAME + + +.. clicmd:: interface IFNAME vrf VRF + + +.. clicmd:: shutdown + + + Up or down the current interface. + + +.. clicmd:: ip address ADDRESS/PREFIX + +.. clicmd:: ipv6 address ADDRESS/PREFIX + + + + Set the IPv4 or IPv6 address/prefix for the interface. + + +.. clicmd:: ip address LOCAL-ADDR peer PEER-ADDR/PREFIX + + + Configure an IPv4 Point-to-Point address on the interface. (The concept of + PtP addressing does not exist for IPv6.) + + ``local-addr`` has no subnet mask since the local side in PtP addressing is + always a single (/32) address. ``peer-addr/prefix`` can be an arbitrary subnet + behind the other end of the link (or even on the link in Point-to-Multipoint + setups), though generally /32s are used. + + +.. clicmd:: description DESCRIPTION ... + + Set description for the interface. + + +.. clicmd:: mpls + + Choose mpls kernel processing value on the interface, for linux. Interfaces + configured with mpls will not automatically turn on if mpls kernel modules do not + happen to be loaded. This command will fail on 3.X linux kernels and does not + work on non-linux systems at all. 'enable' and 'disable' will respectively turn + on and off mpls on the given interface. + +.. clicmd:: multicast + + + Enable or disable multicast flag for the interface. + + +.. clicmd:: bandwidth (1-1000000) + + Set bandwidth value of the interface in Megabits/sec. This is for + calculating OSPF cost. This command does not affect the actual device + configuration. + + +.. clicmd:: link-detect + + + Enable or disable link-detect on platforms which support this. Currently only + Linux, and only where network interface drivers support reporting + link-state via the ``IFF_RUNNING`` flag. + + In FRR, link-detect is on by default. + +.. _link-parameters-commands: + +Link Parameters Commands +------------------------ + +.. note:: + + At this time, FRR offers partial support for some of the routing + protocol extensions that can be used with MPLS-TE. FRR does not + support a complete RSVP-TE solution currently. + +.. clicmd:: link-params + + Enter into the link parameters sub node. This command activates the link + parameters and allows to configure routing information that could be used + as part of Traffic Engineering on this interface. MPLS-TE must be enabled at + the OSPF (:ref:`ospf-traffic-engineering`) or ISIS + (:ref:`isis-traffic-engineering`) router level in complement to this. To + disable link parameters, use the ``no`` version of this command. + +Under link parameter statement, the following commands set the different TE values: + +.. clicmd:: metric (0-4294967295) + +.. clicmd:: max-bw BANDWIDTH + +.. clicmd:: max-rsv-bw BANDWIDTH + +.. clicmd:: unrsv-bw (0-7) BANDWIDTH + + These commands specifies the Traffic Engineering parameters of the interface + in conformity to RFC3630 (OSPF) or RFC5305 (ISIS). There are respectively + the TE Metric (different from the OSPF or ISIS metric), Maximum Bandwidth + (interface speed by default), Maximum Reservable Bandwidth, Unreserved + Bandwidth for each 0-7 priority and Admin Group (ISIS) or Resource + Class/Color (OSPF). + + Note that BANDWIDTH is specified in IEEE floating point format and express + in Bytes/second. + +.. clicmd:: admin-grp 0x(0-FFFFFFFF) + + This commands configures the Traffic Engineering Admin-Group of the interface + as specified in RFC3630 (OSPF) or RFC5305 (ISIS). Admin-group is also known + as Resource Class/Color in the OSPF protocol. + +.. clicmd:: affinity AFFINITY-MAP-NAME + + This commands configures the Traffic Engineering Admin-Group of the + interface using the affinity-map definitions (:ref:`affinity-map`). + Multiple AFFINITY-MAP-NAME can be specified at the same time. Affinity-map + names are added or removed if ``no`` is present. It means that specifying one + value does not override the full list. + + ``admin-grp`` and ``affinity`` commands provide two ways of setting + admin-groups. They cannot be both set on the same interface. + +.. clicmd:: affinity-mode [extended|standard|both] + + This commands configures which admin-group format is set by the affinity + command. ``extended`` Admin-Group is the default and uses the RFC7308 format. + ``standard`` mode uses the standard admin-group format that is defined by + RFC3630, RFC5305 and RFC5329. When the ``standard`` mode is set, + affinity-maps with bit-positions higher than 31 cannot be applied to the + interface. The ``both`` mode allows setting standard and extended admin-group + on the link at the same time. In this case, the bit-positions 0 to 31 are + the same on standard and extended admin-groups. + + Note that extended admin-groups are only supported by IS-IS for the moment. + +.. clicmd:: delay (0-16777215) [min (0-16777215) | max (0-16777215)] + +.. clicmd:: delay-variation (0-16777215) + +.. clicmd:: packet-loss PERCENTAGE + +.. clicmd:: res-bw BANDWIDTH + +.. clicmd:: ava-bw BANDWIDTH + +.. clicmd:: use-bw BANDWIDTH + + These command specifies additional Traffic Engineering parameters of the + interface in conformity to draft-ietf-ospf-te-metrics-extension-05.txt and + draft-ietf-isis-te-metrics-extension-03.txt. There are respectively the + delay, jitter, loss, available bandwidth, reservable bandwidth and utilized + bandwidth. + + Note that BANDWIDTH is specified in IEEE floating point format and express + in Bytes/second. Delays and delay variation are express in micro-second + (µs). Loss is specified in PERCENTAGE ranging from 0 to 50.331642% by step + of 0.000003. + +.. clicmd:: neighbor as (0-65535) + + Specifies the remote ASBR IP address and Autonomous System (AS) number + for InterASv2 link in OSPF (RFC5392). Note that this option is not yet + supported for ISIS (RFC5316). + +Global Commands +------------------------ + +.. clicmd:: zebra protodown reason-bit (0-31) + + This command is only supported for linux and a kernel > 5.1. + Change reason-bit frr uses for setting protodown. We default to 7, but + if another userspace app ever conflicts with this, you can change it here. + The descriptor for this bit should exist in :file:`/etc/iproute2/protodown_reasons.d/` + to display with :clicmd:`ip -d link show`. + +Nexthop Tracking +================ + +Nexthop tracking doesn't resolve nexthops via the default route by default. +Allowing this might be useful when e.g. you want to allow BGP to peer across +the default route. + +.. clicmd:: zebra nexthop-group keep (1-3600) + + Set the time that zebra will keep a created and installed nexthop group + before removing it from the system if the nexthop group is no longer + being used. The default time is 180 seconds. + +.. clicmd:: ip nht resolve-via-default + + Allow IPv4 nexthop tracking to resolve via the default route. This parameter + is configured per-VRF, so the command is also available in the VRF subnode. + + This is enabled by default for a traditional profile. + +.. clicmd:: ipv6 nht resolve-via-default + + Allow IPv6 nexthop tracking to resolve via the default route. This parameter + is configured per-VRF, so the command is also available in the VRF subnode. + + This is enabled by default for a traditional profile. + +.. clicmd:: show ip nht [vrf NAME] [A.B.C.D|X:X::X:X] [mrib] [json] + + Show nexthop tracking status for address resolution. If vrf is not specified + then display the default vrf. If ``all`` is specified show all vrf address + resolution output. If an ipv4 or ipv6 address is not specified then display + all addresses tracked, else display the requested address. The mrib keyword + indicates that the operator wants to see the multicast rib address resolution + table. An alternative form of the command is ``show ip import-check`` and this + form of the command is deprecated at this point in time. + User can get that information as JSON string when ``json`` key word + at the end of cli is presented. + +.. clicmd:: show ip nht route-map [vrf ] [json] + + This command displays route-map attach point to nexthop tracking and + displays list of protocol with its applied route-map. + When zebra considers sending NHT resoultion, the nofification only + sent to appropriate client protocol only after applying route-map filter. + User can get that information as JSON format when ``json`` keyword + at the end of cli is presented. + +PBR dataplane programming +========================= + +Some dataplanes require the PBR nexthop to be resolved into a SMAC, DMAC and +outgoing interface + +.. clicmd:: pbr nexthop-resolve + + Resolve PBR nexthop via ip neigh tracking + +.. _administrative-distance: + +Administrative Distance +======================= + +Administrative distance allows FRR to make decisions about what routes +should be installed in the rib based upon the originating protocol. +The lowest Admin Distance is the route selected. This is purely a +subjective decision about ordering and care has been taken to choose +the same distances that other routing suites have chosen. + ++------------+-----------+ +| Protocol | Distance | ++------------+-----------+ +| System | 0 | ++------------+-----------+ +| Kernel | 0 | ++------------+-----------+ +| Connect | 0 | ++------------+-----------+ +| Static | 1 | ++------------+-----------+ +| NHRP | 10 | ++------------+-----------+ +| EBGP | 20 | ++------------+-----------+ +| EIGRP | 90 | ++------------+-----------+ +| BABEL | 100 | ++------------+-----------+ +| OSPF | 110 | ++------------+-----------+ +| ISIS | 115 | ++------------+-----------+ +| OPENFABRIC | 115 | ++------------+-----------+ +| RIP | 120 | ++------------+-----------+ +| Table | 150 | ++------------+-----------+ +| SHARP | 150 | ++------------+-----------+ +| IBGP | 200 | ++------------+-----------+ +| PBR | 200 | ++------------+-----------+ + +An admin distance of 255 indicates to Zebra that the route should not be +installed into the Data Plane. Additionally routes with an admin distance +of 255 will not be redistributed. + +Zebra does treat Kernel routes as special case for the purposes of Admin +Distance. Upon learning about a route that is not originated by FRR +we read the metric value as a uint32_t. The top byte of the value +is interpreted as the Administrative Distance and the low three bytes +are read in as the metric. This special case is to facilitate VRF +default routes. + +.. code-block:: shell + + $ # Set administrative distance to 255 for Zebra + $ ip route add 192.0.2.0/24 metric $(( 2**32 - 2**24 )) dev lo + $ vtysh -c 'show ip route 192.0.2.0/24 json' | jq '."192.0.2.0/24"[] | (.distance, .metric)' + 255 + 0 + $ # Set administrative distance to 192 for Zebra + $ ip route add 192.0.2.0/24 metric $(( 2**31 + 2**30 )) dev lo + $ vtysh -c 'show ip route 192.0.2.0/24 json' | jq '."192.0.2.0/24"[] | (.distance, .metric)' + 192 + 0 + $ # Set administrative distance to 128, and metric 100 for Zebra + $ ip route add 192.0.2.0/24 metric $(( 2**31 + 100 )) dev lo + $ vtysh -c 'show ip route 192.0.2.0/24 json' | jq '."192.0.2.0/24"[] | (.distance, .metric)' + 128 + 100 + +Route Replace Semantics +======================= + +When using the Linux Kernel as a forwarding plane, routes are installed +with a metric of 20 to the kernel. Please note that the kernel's metric +value bears no resemblence to FRR's RIB metric or admin distance. It +merely is a way for the Linux Kernel to decide which route to use if it +has multiple routes for the same prefix from multiple sources. An example +here would be if someone else was running another routing suite besides +FRR at the same time, the kernel must choose what route to use to forward +on. FRR choose the value of 20 because of two reasons. FRR wanted a +value small enough to be chosen but large enough that the operator could +allow route prioritization by the kernel when multiple routing suites are +being run and FRR wanted to take advantage of Route Replace semantics that +the linux kernel offers. In order for Route Replacement semantics to +work FRR must use the same metric when issuing the replace command. +Currently FRR only supports Route Replace semantics using the Linux +Kernel. + +.. _zebra-vrf: + +Virtual Routing and Forwarding +============================== + +FRR supports :abbr:`VRF (Virtual Routing and Forwarding)`. VRF is a way to +separate networking contexts on the same machine. Those networking contexts are +associated with separate interfaces, thus making it possible to associate one +interface with a specific VRF. + +VRF can be used, for example, when instantiating per enterprise networking +services, without having to instantiate the physical host machine or the +routing management daemons for each enterprise. As a result, interfaces are +separate for each set of VRF, and routing daemons can have their own context +for each VRF. + +This conceptual view introduces the *Default VRF* case. If the user does not +configure any specific VRF, then by default, FRR uses the *Default VRF*. The +name "default" is used to refer to this VRF in various CLI commands and YANG +models. It is possible to change that name by passing the ``-o`` option to all +daemons, for example, one can use ``-o vrf0`` to change the name to "vrf0". +The easiest way to pass the same option to all daemons is to use the +``frr_global_options`` variable in the +:ref:`Daemons Configuration File `. + +Configuring VRF networking contexts can be done in various ways on FRR. The VRF +interfaces can be configured by entering in interface configuration mode +:clicmd:`interface IFNAME vrf VRF`. + +A VRF backend mode is chosen when running *Zebra*. + +If no option is chosen, then the *Linux VRF* implementation as references in +https://www.kernel.org/doc/Documentation/networking/vrf.txt will be mapped over +the *Zebra* VRF. The routing table associated to that VRF is a Linux table +identifier located in the same *Linux network namespace* where *Zebra* started. +Please note when using the *Linux VRF* routing table it is expected that a +default Kernel route will be installed that has a metric as outlined in the +www.kernel.org doc above. The Linux Kernel does table lookup via a combination +of rule application of the rule table and then route lookup of the specified +table. If no route match is found then the next applicable rule is applied +to find the next route table to use to look for a route match. As such if +your VRF table does not have a default blackhole route with a high metric +VRF route lookup will leave the table specified by the VRF, which is undesirable. + +If the :option:`-n` option is chosen, then the *Linux network namespace* will +be mapped over the *Zebra* VRF. That implies that *Zebra* is able to configure +several *Linux network namespaces*. The routing table associated to that VRF +is the whole routing tables located in that namespace. For instance, this mode +matches OpenStack Network Namespaces. It matches also OpenFastPath. The default +behavior remains Linux VRF which is supported by the Linux kernel community, +see https://www.kernel.org/doc/Documentation/networking/vrf.txt. + +Because of that difference, there are some subtle differences when running some +commands in relationship to VRF. Here is an extract of some of those commands: + +.. clicmd:: vrf VRF + + This command is available on configuration mode. By default, above command + permits accessing the VRF configuration mode. This mode is available for + both VRFs. It is to be noted that *Zebra* does not create Linux VRF. + The network administrator can however decide to provision this command in + configuration file to provide more clarity about the intended configuration. + +.. clicmd:: netns NAMESPACE + + This command is based on VRF configuration mode. This command is available + when *Zebra* is run in :option:`-n` mode. This command reflects which *Linux + network namespace* is to be mapped with *Zebra* VRF. It is to be noted that + *Zebra* creates and detects added/suppressed VRFs from the Linux environment + (in fact, those managed with iproute2). The network administrator can however + decide to provision this command in configuration file to provide more clarity + about the intended configuration. + +.. clicmd:: show ip route vrf VRF + + The show command permits dumping the routing table associated to the VRF. If + *Zebra* is launched with default settings, this will be the ``TABLENO`` of + the VRF configured on the kernel, thanks to information provided in + https://www.kernel.org/doc/Documentation/networking/vrf.txt. If *Zebra* is + launched with :option:`-n` option, this will be the default routing table of + the *Linux network namespace* ``VRF``. + +.. clicmd:: show ip route vrf VRF table TABLENO + + The show command is only available with :option:`-n` option. This command + will dump the routing table ``TABLENO`` of the *Linux network namespace* + ``VRF``. + +.. clicmd:: show ip route vrf VRF tables + + This command will dump the routing tables within the vrf scope. If ``vrf all`` + is executed, all routing tables will be dumped. + +.. clicmd:: show route summary [vrf VRF] [table TABLENO] [prefix] + + This command will dump a summary output of the specified VRF and TABLENO + combination. If neither VRF or TABLENO is specified FRR defaults to + the default vrf and default table. If prefix is specified dump the + number of prefix routes. + +.. _zebra-table-allocation: + +Table Allocation +================ + +Some services like BGP flowspec allocate routing tables to perform policy +routing based on netfilter criteria and IP rules. In order to avoid +conflicts between VRF allocated routing tables and those services, Zebra +proposes to define a chunk of routing tables to use by other services. + +Allocation configuration can be done like below, with the range of the +chunk of routing tables to be used by the given service. + +.. clicmd:: ip table range + +.. _zebra-ecmp: + +ECMP +==== + +FRR supports ECMP as part of normal operations and is generally compiled +with a limit of 64 way ECMP. This of course can be modified via configure +options on compilation if the end operator desires to do so. Individual +protocols each have their own way of dictating ECMP policy and their +respective documentation should be read. + +ECMP can be inspected in zebra by doing a ``show ip route X`` command. + +.. code-block:: shell + + eva# show ip route 4.4.4.4/32 + Codes: K - kernel route, C - connected, S - static, R - RIP, + O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, + T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, + F - PBR, f - OpenFabric, + > - selected route, * - FIB route, q - queued, r - rejected, b - backup + t - trapped, o - offload failure + + D>* 4.4.4.4/32 [150/0] via 192.168.161.1, enp39s0, weight 1, 00:00:02 + * via 192.168.161.2, enp39s0, weight 1, 00:00:02 + * via 192.168.161.3, enp39s0, weight 1, 00:00:02 + * via 192.168.161.4, enp39s0, weight 1, 00:00:02 + * via 192.168.161.5, enp39s0, weight 1, 00:00:02 + * via 192.168.161.6, enp39s0, weight 1, 00:00:02 + * via 192.168.161.7, enp39s0, weight 1, 00:00:02 + * via 192.168.161.8, enp39s0, weight 1, 00:00:02 + * via 192.168.161.9, enp39s0, weight 1, 00:00:02 + * via 192.168.161.10, enp39s0, weight 1, 00:00:02 + * via 192.168.161.11, enp39s0, weight 1, 00:00:02 + * via 192.168.161.12, enp39s0, weight 1, 00:00:02 + * via 192.168.161.13, enp39s0, weight 1, 00:00:02 + * via 192.168.161.14, enp39s0, weight 1, 00:00:02 + * via 192.168.161.15, enp39s0, weight 1, 00:00:02 + * via 192.168.161.16, enp39s0, weight 1, 00:00:02 + +In this example we have 16 way ecmp for the 4.4.4.4/32 route. The ``*`` character +tells us that the route is installed in the Data Plane, or FIB. + +If you are using the Linux kernel as a Data Plane, this can be inspected +via a ``ip route show X`` command: + +.. code-block:: shell + + sharpd@eva ~/f/doc(ecmp_doc_change)> ip route show 4.4.4.4/32 + 4.4.4.4 nhid 185483868 proto sharp metric 20 + nexthop via 192.168.161.1 dev enp39s0 weight 1 + nexthop via 192.168.161.10 dev enp39s0 weight 1 + nexthop via 192.168.161.11 dev enp39s0 weight 1 + nexthop via 192.168.161.12 dev enp39s0 weight 1 + nexthop via 192.168.161.13 dev enp39s0 weight 1 + nexthop via 192.168.161.14 dev enp39s0 weight 1 + nexthop via 192.168.161.15 dev enp39s0 weight 1 + nexthop via 192.168.161.16 dev enp39s0 weight 1 + nexthop via 192.168.161.2 dev enp39s0 weight 1 + nexthop via 192.168.161.3 dev enp39s0 weight 1 + nexthop via 192.168.161.4 dev enp39s0 weight 1 + nexthop via 192.168.161.5 dev enp39s0 weight 1 + nexthop via 192.168.161.6 dev enp39s0 weight 1 + nexthop via 192.168.161.7 dev enp39s0 weight 1 + nexthop via 192.168.161.8 dev enp39s0 weight 1 + nexthop via 192.168.161.9 dev enp39s0 weight 1 + +Once installed into the FIB, FRR currently has little control over what +nexthops are chosen to forward packets on. Currently the Linux kernel +has a ``fib_multipath_hash_policy`` sysctl which dictates how the hashing +algorithm is used to forward packets. + +.. _zebra-svd: + +Single Vxlan Device Support +=========================== + +FRR supports configuring VLAN-to-VNI mappings for EVPN-VXLAN, +when working with the Linux kernel. In this new way, the mapping of a VLAN +to a VNI is configured against a container VXLAN interface which is referred +to as a ‘Single VXLAN device (SVD)’. Multiple VLAN to VNI mappings can be +configured against the same SVD. This allows for a significant scaling of +the number of VNIs since a separate VXLAN interface is no longer required +for each VNI. Sample configuration of SVD with VLAN to VNI mappings is shown +below. + +If you are using the Linux kernel as a Data Plane, this can be configured +via `ip link`, `bridge link` and `bridge vlan` commands: + +.. code-block:: shell + + # linux shell + ip link add dev bridge type bridge + ip link set dev bridge type bridge vlan_filtering 1 + ip link add dev vxlan0 type vxlan external + ip link set dev vxlan0 master bridge + bridge link set dev vxlan0 vlan_tunnel on + bridge vlan add dev vxlan0 vid 100 + bridge vlan add dev vxlan0 vid 100 tunnel_info id 100 + bridge vlan tunnelshow + port vlan ids tunnel id + bridge None + vxlan0 100 100 + +.. clicmd:: show evpn access-vlan [IFNAME VLAN-ID | detail] [json] + + Show information for EVPN Access VLANs. + + :: + + VLAN SVI L2-VNI VXLAN-IF # Members + bridge.20 vlan20 20 vxlan0 0 + bridge.10 vlan10 0 vxlan0 0 + +.. _zebra-mpls: + +MPLS Commands +============= + +You can configure static mpls entries in zebra. Basically, handling MPLS +consists of popping, swapping or pushing labels to IP packets. + +MPLS Acronyms +------------- + +:abbr:`LSR (Labeled Switch Router)` + Networking devices handling labels used to forward traffic between and through + them. + +:abbr:`LER (Labeled Edge Router)` + A Labeled edge router is located at the edge of an MPLS network, generally + between an IP network and an MPLS network. + +MPLS Push Action +---------------- + +The push action is generally used for LER devices, which want to encapsulate +all traffic for a wished destination into an MPLS label. This action is stored +in routing entry, and can be configured like a route: + +.. clicmd:: ip route NETWORK MASK GATEWAY|INTERFACE label LABEL + + NETWORK and MASK stand for the IP prefix entry to be added as static + route entry. + GATEWAY is the gateway IP address to reach, in order to reach the prefix. + INTERFACE is the interface behind which the prefix is located. + LABEL is the MPLS label to use to reach the prefix abovementioned. + + You can check that the static entry is stored in the zebra RIB database, by + looking at the presence of the entry. + + :: + + zebra(configure)# ip route 1.1.1.1/32 10.0.1.1 label 777 + zebra# show ip route + Codes: K - kernel route, C - connected, S - static, R - RIP, + O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, + T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, + F - PBR, + > - selected route, * - FIB route + + S>* 1.1.1.1/32 [1/0] via 10.0.1.1, r2-eth0, label 777, 00:39:42 + +MPLS Swap and Pop Action +------------------------ + +The swap action is generally used for LSR devices, which swap a packet with a +label, with an other label. The Pop action is used on LER devices, at the +termination of the MPLS traffic; this is used to remove MPLS header. + +.. clicmd:: mpls lsp INCOMING_LABEL GATEWAY OUTGOING_LABEL|explicit-null|implicit-null + + INCOMING_LABEL and OUTGOING_LABEL are MPLS labels with values ranging from 16 + to 1048575. + GATEWAY is the gateway IP address where to send MPLS packet. + The outgoing label can either be a value or have an explicit-null label header. This + specific header can be read by IP devices. The incoming label can also be removed; in + that case the implicit-null keyword is used, and the outgoing packet emitted is an IP + packet without MPLS header. + +You can check that the MPLS actions are stored in the zebra MPLS table, by looking at the +presence of the entry. + +.. clicmd:: show mpls table + +:: + + zebra(configure)# mpls lsp 18 10.125.0.2 implicit-null + zebra(configure)# mpls lsp 19 10.125.0.2 20 + zebra(configure)# mpls lsp 21 10.125.0.2 explicit-null + zebra# show mpls table + Inbound Outbound + Label Type Nexthop Label + -------- ------- --------------- -------- + 18 Static 10.125.0.2 implicit-null + 19 Static 10.125.0.2 20 + 21 Static 10.125.0.2 IPv4 Explicit Null + + +MPLS label chunks +----------------- + +MPLS label chunks are handled in the zebra label manager service, +which ensures a same label value or label chunk can not be used by +multiple CP routing daemons at the same time. + +Label requests originate from CP routing daemons, and are resolved +over the default MPLS range (16-1048575). There are two kind of +requests: +- Static label requests request an exact label value or range. For +instance, segment routing label blocks requests originating from +IS-IS are part of it. +- Dynamic label requests only need a range of label values. The +'bgp l3vpn export auto' command uses such requests. + +Allocated label chunks table can be dumped using the command + +.. clicmd:: show debugging label-table [json] + +:: + + zebra# show debugging label-table + Proto ospf: [300/350] + Proto srte: [500/500] + Proto isis: [1200/1300] + Proto ospf: [20000/21000] + Proto isis: [22000/23000] + +.. clicmd:: mpls label dynamic-block (16-1048575) (16-1048575) + + Define a range of labels where dynamic label requests will + allocate label chunks from. This command guarantees that + static label values outside that range will not conflict + with the dynamic label requests. When the dynamic-block + range is configured, static label requests that match that + range are not accepted. + +.. _zebra-srv6: + +Segment-Routing IPv6 +==================== + +Segment-Routing is source routing paradigm that allows +network operator to encode network intent into the packets. +SRv6 is an implementation of Segment-Routing +with application of IPv6 and segment-routing-header. + +All routing daemon can use the Segment-Routing base +framework implemented on zebra to use SRv6 routing mechanism. +In that case, user must configure initial srv6 setting on +FRR's cli or frr.conf or zebra.conf. This section shows how +to configure SRv6 on FRR. Of course SRv6 can be used as standalone, +and this section also helps that case. + +.. clicmd:: show segment-routing srv6 manager [json] + + This command dumps the SRv6 information configured on zebra, including + the encapsulation parameters (e.g., the IPv6 source address used for + the encapsulated packets). + + Example:: + + router# sh segment-routing srv6 manager + Parameters: + Encapsulation: + Source Address: + Configured: fc00:0:1::1 + + + To get the same information in json format, you can use the ``json`` keyword:: + + rose-srv6# sh segment-routing srv6 manager json + { + "parameters":{ + "encapsulation":{ + "sourceAddress":{ + "configured":"fc00:0:1::1" + } + } + } + } + + +.. clicmd:: show segment-routing srv6 locator [json] + + This command dump SRv6-locator configured on zebra. SRv6-locator is used + to route to the node before performing the SRv6-function. and that works as + aggregation of SRv6-function's IDs. Following console log shows two + SRv6-locators loc1 and loc2. All locators are identified by unique IPv6 + prefix. User can get that information as JSON string when ``json`` key word + at the end of cli is presented. + +:: + + router# sh segment-routing srv6 locator + Locator: + Name ID Prefix Status + -------------------- ------- ------------------------ ------- + loc1 1 2001:db8:1:1::/64 Up + loc2 2 2001:db8:2:2::/64 Up + +.. clicmd:: show segment-routing srv6 locator NAME detail [json] + + As shown in the example, by specifying the name of the locator, you + can see the detailed information for each locator. Locator can be + represented by a single IPv6 prefix, but SRv6 is designed to share this + Locator among multiple Routing Protocols. For this purpose, zebra divides + the IPv6 prefix block that makes the Locator unique into multiple chunks, + and manages the ownership of each chunk. + + For example, loc1 has system as its owner. For example, loc1 is owned by + system, which means that it is not yet proprietary to any routing protocol. + For example, loc2 has sharp as its owner. This means that the shaprd for + function development holds the owner of the chunk of this locator, and no + other routing protocol will use this area. + +:: + + router# show segment-routing srv6 locator loc1 detail + Name: loc1 + Prefix: 2001:db8:1:1::/64 + Chunks: + - prefix: 2001:db8:1:1::/64, owner: system + + router# show segment-routing srv6 locator loc2 detail + Name: loc2 + Prefix: 2001:db8:2:2::/64 + Chunks: + - prefix: 2001:db8:2:2::/64, owner: sharp + +.. clicmd:: segment-routing + + Move from configure mode to segment-routing node. + +.. clicmd:: srv6 + + Move from segment-routing node to srv6 node. + +.. clicmd:: locators + + Move from srv6 node to locator node. In this locator node, user can + configure detailed settings such as the actual srv6 locator. + +.. clicmd:: locator NAME + + Create a new locator. If the name of an existing locator is specified, + move to specified locator's configuration node to change the settings it. + +.. clicmd:: prefix X:X::X:X/M [func-bits (0-64)] [block-len 40] [node-len 24] + + Set the ipv6 prefix block of the locator. SRv6 locator is defined by + RFC8986. The actual routing protocol specifies the locator and allocates a + SID to be used by each routing protocol. This SID is included in the locator + as an IPv6 prefix. + + Following example console log shows the typical configuration of SRv6 + data-plane. After a new SRv6 locator, named loc1, is created, loc1's prefix + is configured as ``2001:db8:1:1::/64``. If user or some routing daemon + allocates new SID on this locator, new SID will allocated in range of this + prefix. For example, if some routing daemon creates new SID on locator + (``2001:db8:1:1::/64``), Then new SID will be ``2001:db8:1:1:7::/80``, + ``2001:db8:1:1:8::/80``, and so on. Each locator has default SID that is + SRv6 local function "End". Usually default SID is allocated as + ``PREFIX:1::``. (``PREFIX`` is locator's prefix) For example, if user + configure the locator's prefix as ``2001:db8:1:1::/64``, then default SID + will be ``2001:db8:1:1:1::``) + + This command takes three optional parameters: ``func-bits``, ``block-len`` + and ``node-len``. These parameters allow users to set the format for the SIDs + allocated from the SRv6 Locator. SID Format is defined in RFC 8986. + + According to RFC 8986, an SRv6 SID consists of BLOCK:NODE:FUNCTION:ARGUMENT, + where BLOCK is the SRv6 SID block (i.e., the IPv6 prefix allocated for SRv6 + SIDs by the operator), NODE is the identifier of the parent node instantiating + the SID, FUNCTION identifies the local behavior associated to the SID and + ARGUMENT encodes additional information used to process the behavior. + BLOCK and NODE make up the SRv6 Locator. + + The function bits range is 16bits by default. If operator want to change + function bits range, they can configure with ``func-bits`` + option. + + The ``block-len`` and ``node-len`` parameters allow the user to configure the + length of the SRv6 SID block and SRv6 SID node, respectively. Both the lengths + are expressed in bits. + + ``block-len``, ``node-len`` and ``func-bits`` may be any value as long as + ``block-len+node-len = locator-len`` and ``block-len+node-len+func-bits <= 128``. + + When both ``block-len`` and ``node-len`` are omitted, the following default + values are used: ``block-len = 24``, ``node-len = prefix-len-24``. + + If only one parameter is omitted, the other parameter is derived from the first. + +:: + + router# configure terminal + router(config)# segment-routinig + router(config-sr)# srv6 + router(config-srv6)# locators + router(config-srv6-locs)# locator loc1 + router(config-srv6-loc)# prefix 2001:db8:1:1::/64 + + router(config-srv6-loc)# show run + ... + segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 + ! + ... + +.. clicmd:: behavior usid + + Specify the SRv6 locator as a Micro-segment (uSID) locator. When a locator is + specified as a uSID locator, all the SRv6 SIDs allocated from the locator by the routing + protocols are bound to the SRv6 uSID behaviors. For example, if you configure BGP to use + a locator specified as a uSID locator, BGP instantiates and advertises SRv6 uSID behaviors + (e.g., ``uDT4`` / ``uDT6`` / ``uDT46``) instead of classic SRv6 behaviors + (e.g., ``End.DT4`` / ``End.DT6`` / ``End.DT46``). + +:: + + router# configure terminal + router(config)# segment-routinig + router(config-sr)# srv6 + router(config-srv6)# locators + router(config-srv6-locators)# locator loc1 + router(config-srv6-locator)# prefix fc00:0:1::/48 block-len 32 node-len 16 func-bits 16 + router(config-srv6-locator)# behavior usid + + router(config-srv6-locator)# show run + ... + segment-routing + srv6 + locators + locator loc1 + prefix fc00:0:1::/48 + behavior usid + ! + ... + +.. clicmd:: encapsulation + + Configure parameters for SRv6 encapsulation. + +.. clicmd:: source-address X:X::X:X + + Configure the source address of the outer encapsulating IPv6 header. + +.. _multicast-rib-commands: + +Multicast RIB Commands +====================== + +The Multicast RIB provides a separate table of unicast destinations which +is used for Multicast Reverse Path Forwarding decisions. It is used with +a multicast source's IP address, hence contains not multicast group +addresses but unicast addresses. + +This table is fully separate from the default unicast table. However, +RPF lookup can include the unicast table. + +WARNING: RPF lookup results are non-responsive in this version of FRR, +i.e. multicast routing does not actively react to changes in underlying +unicast topology! + +.. clicmd:: ip multicast rpf-lookup-mode MODE + + + MODE sets the method used to perform RPF lookups. Supported modes: + + urib-only + Performs the lookup on the Unicast RIB. The Multicast RIB is never used. + + mrib-only + Performs the lookup on the Multicast RIB. The Unicast RIB is never used. + + mrib-then-urib + Tries to perform the lookup on the Multicast RIB. If any route is found, + that route is used. Otherwise, the Unicast RIB is tried. + + lower-distance + Performs a lookup on the Multicast RIB and Unicast RIB each. The result + with the lower administrative distance is used; if they're equal, the + Multicast RIB takes precedence. + + longer-prefix + Performs a lookup on the Multicast RIB and Unicast RIB each. The result + with the longer prefix length is used; if they're equal, the + Multicast RIB takes precedence. + + The ``mrib-then-urib`` setting is the default behavior if nothing is + configured. If this is the desired behavior, it should be explicitly + configured to make the configuration immune against possible changes in + what the default behavior is. + +.. warning:: + + Unreachable routes do not receive special treatment and do not cause + fallback to a second lookup. + +.. clicmd:: show [ip|ipv6] rpf ADDR + + Performs a Multicast RPF lookup, as configured with ``ip multicast + rpf-lookup-mode MODE``. ADDR specifies the multicast source address to look + up. + + :: + + > show ip rpf 192.0.2.1 + Routing entry for 192.0.2.0/24 using Unicast RIB + Known via "kernel", distance 0, metric 0, best + * 198.51.100.1, via eth0 + + + Indicates that a multicast source lookup for 192.0.2.1 would use an + Unicast RIB entry for 192.0.2.0/24 with a gateway of 198.51.100.1. + +.. clicmd:: show [ip|ipv6] rpf + + Prints the entire Multicast RIB. Note that this is independent of the + configured RPF lookup mode, the Multicast RIB may be printed yet not + used at all. + +.. clicmd:: ip mroute PREFIX NEXTHOP [DISTANCE] + + + Adds a static route entry to the Multicast RIB. This performs exactly as the + ``ip route`` command, except that it inserts the route in the Multicast RIB + instead of the Unicast RIB. + +.. _zebra-route-filtering: + +zebra Route Filtering +===================== + +Zebra supports :dfn:`prefix-list` s and :ref:`route-map` s to match routes +received from other FRR components. The permit/deny facilities provided by +these commands can be used to filter which routes zebra will install in the +kernel. + +.. clicmd:: ip protocol PROTOCOL route-map ROUTEMAP + + Apply a route-map filter to routes for the specified protocol. PROTOCOL can + be: + + - any, + - babel, + - bgp, + - connected, + - eigrp, + - isis, + - kernel, + - nhrp, + - openfabric, + - ospf, + - ospf6, + - rip, + - sharp, + - static, + - ripng, + - table, + - vnc. + + If you choose any as the option that will cause all protocols that are sending + routes to zebra. You can specify a :dfn:`ip protocol PROTOCOL route-map ROUTEMAP` + on a per vrf basis, by entering this command under vrf mode for the vrf you + want to apply the route-map against. + +.. clicmd:: set src ADDRESS + + Within a route-map, set the preferred source address for matching routes + when installing in the kernel. + + +The following creates a prefix-list that matches all addresses, a route-map +that sets the preferred source address, and applies the route-map to all +*rip* routes. + +.. code-block:: frr + + ip prefix-list ANY permit 0.0.0.0/0 le 32 + route-map RM1 permit 10 + match ip address prefix-list ANY + set src 10.0.0.1 + + ip protocol rip route-map RM1 + +IPv6 example for OSPFv3. + +.. code-block:: frr + + ipv6 prefix-list ANY seq 10 permit any + route-map RM6 permit 10 + match ipv6 address prefix-list ANY + set src 2001:db8:425:1000::3 + + ipv6 protocol ospf6 route-map RM6 + + +.. note:: + + For both IPv4 and IPv6, the IP address has to exist on some interface when + the route is getting installed into the system. Otherwise, kernel rejects + the route. To solve the problem of disappearing IPv6 addresses when the + interface goes down, use ``net.ipv6.conf.all.keep_addr_on_down`` + :ref:`sysctl option `. + +.. clicmd:: zebra route-map delay-timer (0-600) + + Set the delay before any route-maps are processed in zebra. The + default time for this is 5 seconds. + +.. _zebra-fib-push-interface: + +zebra FIB push interface +======================== + +Zebra supports a 'FIB push' interface that allows an external +component to learn the forwarding information computed by the FRR +routing suite. This is a loadable module that needs to be enabled +at startup as described in :ref:`loadable-module-support`. + +In FRR, the Routing Information Base (RIB) resides inside +zebra. Routing protocols communicate their best routes to zebra, and +zebra computes the best route across protocols for each prefix. This +latter information makes up the Forwarding Information Base +(FIB). Zebra feeds the FIB to the kernel, which allows the IP stack in +the kernel to forward packets according to the routes computed by +FRR. The kernel FIB is updated in an OS-specific way. For example, +the ``Netlink`` interface is used on Linux, and route sockets are +used on FreeBSD. + +The FIB push interface aims to provide a cross-platform mechanism to +support scenarios where the router has a forwarding path that is +distinct from the kernel, commonly a hardware-based fast path. In +these cases, the FIB needs to be maintained reliably in the fast path +as well. We refer to the component that programs the forwarding plane +(directly or indirectly) as the Forwarding Plane Manager or FPM. + +.. program:: configure + +The relevant zebra code kicks in when zebra is configured with the +:option:`--enable-fpm` flag and started with the module (``-M fpm`` +or ``-M dplane_fpm_nl``). + +.. note:: + + The ``fpm`` implementation attempts to connect to ``127.0.0.1`` port ``2620`` + by default without configurations. The ``dplane_fpm_nl`` only attempts to + connect to a server if configured. + +Zebra periodically attempts to connect to the well-known FPM port (``2620``). +Once the connection is up, zebra starts sending messages containing routes +over the socket to the FPM. Zebra sends a complete copy of the forwarding +table to the FPM, including routes that it may have picked up from the kernel. +The existing interaction of zebra with the kernel remains unchanged -- that +is, the kernel continues to receive FIB updates as before. + +The default FPM message format is netlink, however it can be controlled +with the module load-time option. The modules accept the following options: + +- ``fpm``: ``netlink`` and ``protobuf``. +- ``dplane_fpm_nl``: none, it only implements netlink. + +The zebra FPM interface uses replace semantics. That is, if a 'route +add' message for a prefix is followed by another 'route add' message, +the information in the second message is complete by itself, and +replaces the information sent in the first message. + +If the connection to the FPM goes down for some reason, zebra sends +the FPM a complete copy of the forwarding table(s) when it reconnects. + +For more details on the implementation, please read the developer's manual FPM +section. + +FPM Commands +============ + +``fpm`` implementation +---------------------- + +.. clicmd:: fpm connection ip A.B.C.D port (1-65535) + + Configure ``zebra`` to connect to a different FPM server than the default of + ``127.0.0.1:2620`` + +.. clicmd:: show zebra fpm stats + + Shows the FPM statistics. + + Sample output: + + :: + + Counter Total Last 10 secs + + connect_calls 3 2 + connect_no_sock 0 0 + read_cb_calls 2 2 + write_cb_calls 2 0 + write_calls 1 0 + partial_writes 0 0 + max_writes_hit 0 0 + t_write_yields 0 0 + nop_deletes_skipped 6 0 + route_adds 5 0 + route_dels 0 0 + updates_triggered 11 0 + redundant_triggers 0 0 + dests_del_after_update 0 0 + t_conn_down_starts 0 0 + t_conn_down_dests_processed 0 0 + t_conn_down_yields 0 0 + t_conn_down_finishes 0 0 + t_conn_up_starts 1 0 + t_conn_up_dests_processed 11 0 + t_conn_up_yields 0 0 + t_conn_up_aborts 0 0 + t_conn_up_finishes 1 0 + + +.. clicmd:: clear zebra fpm stats + + Reset statistics related to the zebra code that interacts with the + optional Forwarding Plane Manager (FPM) component. + + +``dplane_fpm_nl`` implementation +-------------------------------- + +.. clicmd:: fpm address [port (1-65535)] + + Configures the FPM server address. Once configured ``zebra`` will attempt + to connect to it immediately. + + The ``no`` form disables FPM entirely. ``zebra`` will close any current + connections and will not attempt to connect to it anymore. + +.. clicmd:: fpm use-next-hop-groups + + Use the new netlink messages ``RTM_NEWNEXTHOP`` / ``RTM_DELNEXTHOP`` to + group repeated route next hop information. + + The ``no`` form uses the old known FPM behavior of including next hop + information in the route (e.g. ``RTM_NEWROUTE``) messages. + +.. clicmd:: fpm use-route-replace + + Use the netlink ``NLM_F_REPLACE`` flag for updating routes instead of + two different messages to update a route + (``RTM_DELROUTE`` + ``RTM_NEWROUTE``). + +.. clicmd:: show fpm counters [json] + + Show the FPM statistics (plain text or JSON formatted). + + Sample output: + + :: + + FPM counters + ============ + Input bytes: 0 + Output bytes: 308 + Output buffer current size: 0 + Output buffer peak size: 308 + Connection closes: 0 + Connection errors: 0 + Data plane items processed: 0 + Data plane items enqueued: 0 + Data plane items queue peak: 0 + Buffer full hits: 0 + User FPM configurations: 1 + User FPM disable requests: 0 + +.. clicmd:: show fpm status [json] + + Show the FPM status. + +.. clicmd:: clear fpm counters + + Reset statistics related to the zebra code that interacts with the + optional Forwarding Plane Manager (FPM) component. + + +.. _zebra-dplane: + +Dataplane Commands +================== + +The zebra dataplane subsystem provides a framework for FIB +programming. Zebra uses the dataplane to program the local kernel as +it makes changes to objects such as IP routes, MPLS LSPs, and +interface IP addresses. The dataplane runs in its own pthread, in +order to off-load work from the main zebra pthread. + + +.. clicmd:: show zebra dplane [detailed] + + Display statistics about the updates and events passing through the + dataplane subsystem. + + +.. clicmd:: show zebra dplane providers + + Display information about the running dataplane plugins that are + providing updates to a FIB. By default, the local kernel plugin is + present. + + +.. clicmd:: zebra dplane limit [NUMBER] + + Configure the limit on the number of pending updates that are + waiting to be processed by the dataplane pthread. + + +DPDK dataplane +============== + +The zebra DPDK subsystem programs the dataplane via rte_XXX APIs. +This module needs be compiled in via "--enable-dp-dpdk=yes" +and enabled at start up time via the zebra daemon option "-M dplane_dpdk". + +To program the PBR rules as rte_flows you additionally need to configure +"pbr nexthop-resolve". This is used to expland the PBR actions into the +{SMAC, DMAC, outgoing port} needed by rte_flow. + + +.. clicmd:: show dplane dpdk port [detail] + + Displays the mapping table between zebra interfaces and DPDK port-ids. + Sample output: + + :: + Port Device IfName IfIndex sw,domain,port + + 0 0000:03:00.0 p0 4 0000:03:00.0,0,65535 + 1 0000:03:00.0 pf0hpf 6 0000:03:00.0,0,4095 + 2 0000:03:00.0 pf0vf0 15 0000:03:00.0,0,4096 + 3 0000:03:00.0 pf0vf1 16 0000:03:00.0,0,4097 + 4 0000:03:00.1 p1 5 0000:03:00.1,1,65535 + 5 0000:03:00.1 pf1hpf 7 0000:03:00.1,1,20479 + +.. clicmd:: show dplane dpdk pbr flows + Displays the DPDK stats per-PBR entry. + Sample output: + + :: + Rules if pf0vf0 + Seq 1 pri 300 + SRC Match 77.0.0.8/32 + DST Match 88.0.0.8/32 + Tableid: 10000 + Action: nh: 45.0.0.250 intf: p0 + Action: mac: 00:00:5e:00:01:fa + DPDK flow: installed 0x40 + DPDK flow stats: packets 13 bytes 1586 + +.. clicmd:: show dplane dpdk counters + Displays the ZAPI message handler counters + + Sample output: + + :: + Ignored updates: 0 + PBR rule adds: 1 + PBR rule dels: 0 + + +zebra Terminal Mode Commands +============================ + +.. clicmd:: show [ip|ipv6] route + + Display current routes which zebra holds in its database. + +:: + + Router# show ip route + Codes: K - kernel route, C - connected, L - local, S - static, + R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP, + T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP, + F - PBR, f - OpenFabric, t - Table-Direct, + > - selected route, * - FIB route, q - queued, r - rejected, b - backup + t - trapped, o - offload failure + + K>* 0.0.0.0/0 [0/100] via 192.168.119.1, enp13s0, 00:30:22 + S> 4.5.6.7/32 [1/0] via 192.168.119.1 (recursive), weight 1, 00:30:22 + * via 192.168.119.1, enp13s0, weight 1, 00:30:22 + K>* 169.254.0.0/16 [0/1000] is directly connected, virbr2 linkdown, 00:30:22 + L>* 192.168.119.205/32 is directly connected, enp13s0, 00:30:22 + + +.. clicmd:: show [ip|ipv6] route [PREFIX] [nexthop-group] + + Display detailed information about a route. If [nexthop-group] is + included, it will display the nexthop group ID the route is using as well. + +.. clicmd:: show [ip|ipv6] route summary + + Display summary information about routes received from each protocol. + This command displays the entries received from each route and as such + this total can be more than the actual number of FIB routes. Finally + due to the way that linux supports local and connected routes the FIB + total may not be exactly what is shown in the equivalent `ip route show` + command to see the state of the linux kernel. + +.. clicmd:: show interface [NAME] [{vrf VRF|brief}] [json] + +.. clicmd:: show interface [NAME] [{vrf all|brief}] [json] + +.. clicmd:: show interface [NAME] [{vrf VRF|brief}] [nexthop-group] + +.. clicmd:: show interface [NAME] [{vrf all|brief}] [nexthop-group] + + Display interface information. If no extra information is added, it will + dump information on all interfaces. If [NAME] is specified, it will display + detailed information about that single interface. If [nexthop-group] is + specified, it will display nexthop groups pointing out that interface. + + If the ``json`` option is specified, output is displayed in JSON format. + +.. clicmd:: show ip prefix-list [NAME] + +.. clicmd:: show ip protocol + +.. clicmd:: show ip forward + + Display whether the host's IP forwarding function is enabled or not. + Almost any UNIX kernel can be configured with IP forwarding disabled. + If so, the box can't work as a router. + +.. clicmd:: show ipv6 forward + + Display whether the host's IP v6 forwarding is enabled or not. + +.. clicmd:: show ip neigh + + Display the ip neighbor table + +.. clicmd:: show pbr rule + + Display the pbr rule table with resolved nexthops + +.. clicmd:: show zebra + + Display various statistics related to the installation and deletion + of routes, neighbor updates, and LSP's into the kernel. In addition + show various zebra state that is useful when debugging an operator's + setup. + +.. clicmd:: show zebra client [summary] + + Display statistics about clients that are connected to zebra. This is + useful for debugging and seeing how much data is being passed between + zebra and it's clients. If the summary form of the command is chosen + a table is displayed with shortened information. + +.. clicmd:: show zebra router table summary + + Display summarized data about tables created, their afi/safi/tableid + and how many routes each table contains. Please note this is the + total number of route nodes in the table. Which will be higher than + the actual number of routes that are held. + +.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json] + + Display nexthop groups created by zebra. The [vrf NAME] option + is only meaningful if you have started zebra with the --vrfwnetns + option as that nexthop groups are per namespace in linux. + If you specify singleton you would like to see the singleton + nexthop groups that do have an afi. [type] allows you to filter those + only coming from a specific NHG type (protocol). + +.. clicmd:: show zebra route dump [ VRFNAME] + + It dumps all the routes from RIB with detailed information including + internal flags, status etc. This is defined as a hidden command. + + +Router-id +========= + +Many routing protocols require a router-id to be configured. To have a +consistent router-id across all daemons, the following commands are available +to configure and display the router-id: + +.. clicmd:: [ip] router-id A.B.C.D + + Allow entering of the router-id. This command also works under the + vrf subnode, to allow router-id's per vrf. + +.. clicmd:: [ip] router-id A.B.C.D vrf NAME + + Configure the router-id of this router from the configure NODE. + A show run of this command will display the router-id command + under the vrf sub node. This command is deprecated and will + be removed at some point in time in the future. + +.. clicmd:: show [ip] router-id [vrf NAME] + + Display the user configured router-id. + +For protocols requiring an IPv6 router-id, the following commands are available: + +.. clicmd:: ipv6 router-id X:X::X:X + + Configure the IPv6 router-id of this router. Like its IPv4 counterpart, + this command works under the vrf subnode, to allow router-id's per vrf. + +.. clicmd:: show ipv6 router-id [vrf NAME] + + Display the user configured IPv6 router-id. + +.. _zebra-sysctl: + +sysctl settings +=============== + +The linux kernel has a variety of sysctl's that affect it's operation as a router. This +section is meant to act as a starting point for those sysctl's that must be used in +order to provide FRR with smooth operation as a router. This section is not meant +as the full documentation for sysctl's. The operator must use the sysctl documentation +with the linux kernel for that. The following link has helpful references to many relevant +sysctl values: https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt + +Expected sysctl settings +------------------------ + +.. option:: net.ipv4.ip_forward = 1 + + This global option allows the linux kernel to forward (route) ipv4 packets incoming from one + interface to an outgoing interface. If this is set to 0, the system will not route transit + ipv4 packets, i.e. packets that are not sent to/from a process running on the local system. + +.. option:: net.ipv4.conf.{all,default,}.forwarding = 1 + + The linux kernel can selectively enable forwarding (routing) of ipv4 packets on a per + interface basis. The forwarding check in the kernel dataplane occurs against the ingress + Layer 3 interface, i.e. if the ingress L3 interface has forwarding set to 0, packets will not + be routed. + +.. option:: net.ipv6.conf.{all,default,}.forwarding = 1 + + This per interface option allows the linux kernel to forward (route) transit ipv6 packets + i.e. incoming from one Layer 3 interface to an outgoing Layer 3 interface. + The forwarding check in the kernel dataplane occurs against the ingress Layer 3 interface, + i.e. if the ingress L3 interface has forwarding set to 0, packets will not be routed. + +.. option:: net.ipv6.conf.all.keep_addr_on_down = 1 + + When an interface is taken down, do not remove the v6 addresses associated with the interface. + This option is recommended because this is the default behavior for v4 as well. + +.. option:: net.ipv6.route.skip_notify_on_dev_down = 1 + + When an interface is taken down, the linux kernel will not notify, via netlink, about routes + that used that interface being removed from the FIB. This option is recommended because this + is the default behavior for v4 as well. + +Optional sysctl settings +------------------------ + +.. option:: net.ipv4.conf.{all,default,}.bc_forwarding = 0 + + This per interface option allows the linux kernel to optionally allow Directed Broadcast + (i.e. Routed Broadcast or Subnet Broadcast) packets to be routed onto the connected network + segment where the subnet exists. + If the local router receives a routed packet destined for a broadcast address of a connected + subnet, setting bc_forwarding to 1 on the interface with the target subnet assigned to it will + allow non locally-generated packets to be routed via the broadcast route. + If bc_forwarding is set to 0, routed packets destined for a broadcast route will be dropped. + e.g. + Host1 (SIP:192.0.2.10, DIP:10.0.0.255) -> (eth0:192.0.2.1/24) Router1 (eth1:10.0.0.1/24) -> BC + If net.ipv4.conf.{all,default,}.bc_forwarding=1, then Router1 will forward each + packet destined to 10.0.0.255 onto the eth1 interface with a broadcast DMAC (ff:ff:ff:ff:ff:ff). + +.. option:: net.ipv4.conf.{all,default,}.arp_accept = 1 + + This per interface option allows the linux kernel to optionally skip the creation of ARP + entries upon the receipt of a Gratuitous ARP (GARP) frame carrying an IP that is not already + present in the ARP cache. Setting arp_accept to 0 on an interface will ensure NEW ARP entries + are not created due to the arrival of a GARP frame. + Note: This does not impact how the kernel reacts to GARP frames that carry a "known" IP + (that is already in the ARP cache) -- an existing ARP entry will always be updated + when a GARP for that IP is received. + +.. option:: net.ipv4.conf.{all,default,}.arp_ignore = 0 + + This per interface option allows the linux kernel to control what conditions must be met in + order for an ARP reply to be sent in response to an ARP request targeting a local IP address. + When arp_ignore is set to 0, the kernel will send ARP replies in response to any ARP Request + with a Target-IP matching a local address. + When arp_ignore is set to 1, the kernel will send ARP replies if the Target-IP in the ARP + Request matches an IP address on the interface the Request arrived at. + When arp_ignore is set to 2, the kernel will send ARP replies only if the Target-IP matches an + IP address on the interface where the Request arrived AND the Sender-IP falls within the subnet + assigned to the local IP/interface. + +.. option:: net.ipv4.conf.{all,default,}.arp_notify = 1 + + This per interface option allows the linux kernel to decide whether to send a Gratuitious ARP + (GARP) frame when the Layer 3 interface comes UP. + When arp_notify is set to 0, no GARP is sent. + When arp_notify is set to 1, a GARP is sent when the interface comes UP. + +.. option:: net.ipv6.conf.{all,default,}.ndisc_notify = 1 + + This per interface option allows the linux kernel to decide whether to send an Unsolicited + Neighbor Advertisement (U-NA) frame when the Layer 3 interface comes UP. + When ndisc_notify is set to 0, no U-NA is sent. + When ndisc_notify is set to 1, a U-NA is sent when the interface comes UP. + +Useful sysctl settings +---------------------- + +.. option:: net.ipv6.conf.all.use_oif_addrs_only = 1 + + When enabled, the candidate source addresses for destinations routed via this interface are + restricted to the set of addresses configured on this interface (RFC 6724 section 4). If + an operator has hundreds of IP addresses per interface this solves the latency problem. + +Debugging +========= + +.. clicmd:: debug zebra mpls [detailed] + + MPLS-related events and information. + +.. clicmd:: debug zebra events + + Zebra events + +.. clicmd:: debug zebra nht [detailed] + + Nexthop-tracking / reachability information + +.. clicmd:: debug zebra vxlan + + VxLAN (EVPN) events + +.. clicmd:: debug zebra pseudowires + + Pseudowire events. + +.. clicmd:: debug zebra packet [] [detail] + + ZAPI message and packet details + +.. clicmd:: debug zebra kernel + + Kernel / OS events. + +.. clicmd:: debug zebra kernel msgdump [] + + Raw OS (netlink) message details. + +.. clicmd:: debug zebra rib [detailed] + + RIB events. + +.. clicmd:: debug zebra fpm + + FPM (forwarding-plane manager) events. + +.. clicmd:: debug zebra dplane [detailed] + + Dataplane / FIB events. + +.. clicmd:: debug zebra pbr + + PBR (policy-based routing) events. + +.. clicmd:: debug zebra mlag + + MLAG events. + +.. clicmd:: debug zebra evpn mh + + EVPN multi-hop events. + +.. clicmd:: debug zebra nexthop [detail] + + Nexthop and nexthop-group events. + +Scripting +========= + +.. clicmd:: zebra on-rib-process script SCRIPT + + Set a Lua script for :ref:`on-rib-process-dplane-results` hook call. + SCRIPT is the basename of the script, without ``.lua``. + +Data structures +--------------- + +.. _const-struct-zebra-dplane-ctx: + +const struct zebra_dplane_ctx +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + * integer zd_op + * integer zd_status + * integer zd_provider + * integer zd_vrf_id + * integer zd_table_id + * integer zd_ifname + * integer zd_ifindex + * table rinfo (if zd_op is DPLANE_OP_ROUTE*, DPLANE_NH_*) + + * prefix zd_dest + * prefix zd_src + * integer zd_afi + * integer zd_safi + * integer zd_type + * integer zd_old_type + * integer zd_tag + * integer zd_old_tag + * integer zd_metric + * integer zd_old_metric + * integer zd_instance + * integer zd_old_instance + * integer zd_distance + * integer zd_old_distance + * integer zd_mtu + * integer zd_nexthop_mtu + * table nhe + + * integer id + * integer old_id + * integer afi + * integer vrf_id + * integer type + * nexthop_group ng + * nh_grp + * integer nh_grp_count + + * integer zd_nhg_id + * nexthop_group zd_ng + * nexthop_group backup_ng + * nexthop_group zd_old_ng + * nexthop_group old_backup_ng + + * integer label (if zd_op is DPLANE_OP_LSP_*) + * table pw (if zd_op is DPLANE_OP_PW_*) + + * integer type + * integer af + * integer status + * integer flags + * integer local_label + * integer remote_label + + * table macinfo (if zd_op is DPLANE_OP_MAC_*) + + * integer vid + * integer br_ifindex + * ethaddr mac + * integer vtep_ip + * integer is_sticky + * integer nhg_id + * integer update_flags + + * table rule (if zd_op is DPLANE_OP_RULE_*) + + * integer sock + * integer unique + * integer seq + * string ifname + * integer priority + * integer old_priority + * integer table + * integer old_table + * integer filter_bm + * integer old_filter_bm + * integer fwmark + * integer old_fwmark + * integer dsfield + * integer old_dsfield + * integer ip_proto + * integer old_ip_proto + * prefix src_ip + * prefix old_src_ip + * prefix dst_ip + * prefix old_dst_ip + + * table iptable (if zd_op is DPLANE_OP_IPTABLE_*) + + * integer sock + * integer vrf_id + * integer unique + * integer type + * integer filter_bm + * integer fwmark + * integer action + * integer pkt_len_min + * integer pkt_len_max + * integer tcp_flags + * integer dscp_value + * integer fragment + * integer protocol + * integer nb_interface + * integer flow_label + * integer family + * string ipset_name + + * table ipset (if zd_op is DPLANE_OP_IPSET_*) + * integer sock + * integer vrf_id + * integer unique + * integer type + * integer family + * string ipset_name + + * table neigh (if zd_op is DPLANE_OP_NEIGH_*) + + * ipaddr ip_addr + * table link + + * ethaddr mac + * ipaddr ip_addr + + * integer flags + * integer state + * integer update_flags + + * table br_port (if zd_op is DPLANE_OP_BR_PORT_UPDATE) + + * integer sph_filter_cnt + * integer flags + * integer backup_nhg_id + + * table neightable (if zd_op is DPLANE_OP_NEIGH_TABLE_UPDATE) + + * integer family + * integer app_probes + * integer ucast_probes + * integer mcast_probes + + * table gre (if zd_op is DPLANE_OP_GRE_SET)** + + * integer link_ifindex + * integer mtu + + +.. _const-struct-nh-grp: + +const struct nh_grp +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: console + + * integer id + * integer weight + + +.. _zebra-hook-calls: + +Zebra Hook calls +---------------- + +.. _on-rib-process-dplane-results: + +on_rib_process_dplane_results +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Called when RIB processes dataplane events. +Set script location with the ``zebra on-rib-process script SCRIPT`` command. + +**Arguments** + +* :ref:`const struct zebra_dplane_ctx` ctx + + +.. code-block:: lua + + function on_rib_process_dplane_results(ctx) + log.info(ctx.rinfo.zd_dest.network) + return {} diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000..6f91eb5 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1 @@ +pkgs/ diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile new file mode 100644 index 0000000..1cff06f --- /dev/null +++ b/docker/alpine/Dockerfile @@ -0,0 +1,81 @@ +# syntax=docker/dockerfile:1 + +# Create a basic stage set up to build APKs +FROM alpine:3.19 as alpine-builder +RUN apk add \ + --update-cache \ + abuild \ + alpine-conf \ + alpine-sdk \ + && setup-apkcache /var/cache/apk \ + && mkdir -p /pkgs/apk \ + && echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +RUN adduser -D -G abuild builder && su builder -c 'abuild-keygen -a -n' + +# This stage builds an APK for libyang +FROM alpine-builder as alpine-apk-builder-libyang +RUN mkdir -p /src/libyang +COPY docker/alpine/libyang/APKBUILD /src/libyang +RUN chown -R builder /pkgs /src +USER builder +RUN cd /src/libyang \ + && abuild checksum \ + && git init \ + && abuild -r -P /pkgs/apk + +# This stage builds a dist tarball from the source +FROM alpine:3.19 as source-builder +RUN mkdir -p /src/alpine /pkgs/apk +COPY alpine/APKBUILD.in /src/alpine +COPY --from=alpine-apk-builder-libyang /pkgs/apk/src /pkgs/apk +RUN cd /pkgs/apk && apk add --allow-untrusted */*.apk +RUN source /src/alpine/APKBUILD.in \ + && apk add \ + --no-cache \ + --update-cache \ + $makedepends +COPY . /src +ARG PKGVER +RUN cd /src \ + && ./bootstrap.sh \ + && ./configure \ + --enable-numeric-version \ + --with-pkg-extra-version="_git$PKGVER" \ + && make dist + +# This stage builds an APK from the dist tarball +FROM alpine-builder as alpine-apk-builder +COPY --from=source-builder /src/frr-*.tar.gz /src/alpine/* /dist/ +COPY --from=alpine-apk-builder-libyang /pkgs/apk/src /pkgs/apk +RUN cd /pkgs/apk && apk add --allow-untrusted */*.apk +RUN find /pkgs/apk -type f -name APKINDEX.tar.gz -delete +RUN chown -R builder /dist /pkgs +USER builder +RUN cd /dist \ + && abuild checksum \ + && git init \ + && abuild -r -P /pkgs/apk + +# This stage installs frr from the apk +FROM alpine:3.19 +RUN mkdir -p /pkgs/apk +COPY --from=alpine-apk-builder /pkgs/apk/ /pkgs/apk/ +RUN apk add \ + --no-cache \ + --update-cache \ + tini \ + && apk add \ + --no-cache \ + --allow-untrusted /pkgs/apk/*/*.apk \ + && rm -rf /pkgs + +# Own the config / PID files +RUN mkdir -p /var/run/frr +RUN chown -R frr:frr /etc/frr /var/run/frr + +# Simple init manager for reaping processes and forwarding signals +ENTRYPOINT ["/sbin/tini", "--"] + +# Default CMD starts watchfrr +COPY docker/alpine/docker-start /usr/lib/frr/docker-start +CMD ["/usr/lib/frr/docker-start"] diff --git a/docker/alpine/Dockerfile-coverage b/docker/alpine/Dockerfile-coverage new file mode 100644 index 0000000..5fdb117 --- /dev/null +++ b/docker/alpine/Dockerfile-coverage @@ -0,0 +1,12 @@ +FROM alpine:3.7 +ARG commit +ARG token +ENV COMMIT=${commit} +ENV TOKEN=${token} +ADD . /src +RUN cd /src && \ + source alpine/APKBUILD.in && \ + apk add --no-cache alpine-sdk $makedepends $checkdepends && \ + ./bootstrap.sh && \ + ./configure --enable-gcov +ENTRYPOINT [ "/bin/sh", "-c", "cd /src && make && make -j 1 check-coverage" ] diff --git a/docker/alpine/build.sh b/docker/alpine/build.sh new file mode 100755 index 0000000..80ee81c --- /dev/null +++ b/docker/alpine/build.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -e +set -x + +## +# Package version needs to be decimal +## + +## +# Set GITREV=0 or similar in ENV if you want the tag to just be updated to -0 +# everytime for automation usage/scripts/etc locally. +# +# Ex) GITREV=0 ./build.sh +## + +GITREV="${GITREV:=$(git rev-parse --short=10 HEAD)}" +PKGVER="$(printf '%u\n' 0x$GITREV)" + +docker build \ + --pull \ + --file=docker/alpine/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:alpine-builder-$GITREV" \ + --target=alpine-builder \ + . + +# Keep .apk files for debugging purposes, docker image as well. +docker build \ + --pull \ + --file=docker/alpine/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:alpine-apk-builder-$GITREV" \ + --target=alpine-apk-builder \ + . + +CONTAINER_ID="$(docker create "frr:alpine-apk-builder-$GITREV")" +docker cp "${CONTAINER_ID}:/pkgs/" docker/alpine +docker rm "${CONTAINER_ID}" + +docker build \ + --file=docker/alpine/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:alpine-$GITREV" \ + . + +docker rmi "frr:alpine-builder-$GITREV" +docker rmi "frr:alpine-apk-builder-$GITREV" diff --git a/docker/alpine/docker-start b/docker/alpine/docker-start new file mode 100755 index 0000000..995cea4 --- /dev/null +++ b/docker/alpine/docker-start @@ -0,0 +1,18 @@ +#!/bin/bash + +if [ -r "/lib/lsb/init-functions" ]; then + . /lib/lsb/init-functions +else + log_success_msg() { + echo "$@" + } + log_warning_msg() { + echo "$@" >&2 + } + log_failure_msg() { + echo "$@" >&2 + } +fi + +source /usr/lib/frr/frrcommon.sh +/usr/lib/frr/watchfrr $(daemon_list) diff --git a/docker/alpine/libyang/10-remove-non-standard-headers.patch b/docker/alpine/libyang/10-remove-non-standard-headers.patch new file mode 100644 index 0000000..18812b5 --- /dev/null +++ b/docker/alpine/libyang/10-remove-non-standard-headers.patch @@ -0,0 +1,298 @@ +From 8f4907590afbe3eafabcf5b461c0ae51b65c3a37 Mon Sep 17 00:00:00 2001 +From: Michal Vasko +Date: Thu, 10 Jun 2021 15:07:02 +0200 +Subject: [PATCH] libyang BUGFIX do not include non-standard headers + +Fixes #1614 +--- + src/context.c | 1 - + src/diff.c | 1 - + src/log.c | 1 - + src/out.c | 1 - + src/plugins_types.c | 1 - + src/plugins_types/bits.c | 1 - + src/plugins_types/date_and_time.c | 1 - + src/plugins_types/identityref.c | 1 - + src/plugins_types/integer.c | 1 - + src/plugins_types/ipv4_address.c | 1 - + src/plugins_types/ipv4_address_no_zone.c | 1 - + src/plugins_types/ipv4_prefix.c | 1 - + src/plugins_types/ipv6_address.c | 1 - + src/plugins_types/ipv6_address_no_zone.c | 1 - + src/plugins_types/ipv6_prefix.c | 1 - + src/plugins_types/union.c | 1 - + src/schema_compile_node.c | 1 - + src/tree_data_helpers.c | 1 - + src/tree_schema.c | 1 - + src/validation.c | 1 - + src/xpath.c | 1 - + tools/re/main.c | 1 - + 22 files changed, 22 deletions(-) + +diff --git a/src/context.c b/src/context.c +index eb671255..ac62cac5 100644 +--- a/src/context.c ++++ b/src/context.c +@@ -17,7 +17,6 @@ + #define _XOPEN_SOURCE 1 + #define _XOPEN_SOURCE_EXTENDED 1 + #endif +-#include + + #include "context.h" + +diff --git a/src/diff.c b/src/diff.c +index b40dd73a..4971c6fe 100644 +--- a/src/diff.c ++++ b/src/diff.c +@@ -12,7 +12,6 @@ + * https://opensource.org/licenses/BSD-3-Clause + */ + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "diff.h" + +diff --git a/src/log.c b/src/log.c +index 97c7b283..9cd5fd0d 100644 +--- a/src/log.c ++++ b/src/log.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "log.h" + +diff --git a/src/out.c b/src/out.c +index 37beb696..898d663a 100644 +--- a/src/out.c ++++ b/src/out.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "out.h" + #include "out_internal.h" +diff --git a/src/plugins_types.c b/src/plugins_types.c +index 26bac210..a2cf0f38 100644 +--- a/src/plugins_types.c ++++ b/src/plugins_types.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/bits.c b/src/plugins_types/bits.c +index 9d086ffb..ef87691b 100644 +--- a/src/plugins_types/bits.c ++++ b/src/plugins_types/bits.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/date_and_time.c b/src/plugins_types/date_and_time.c +index 0d52dbb1..a23caaa9 100644 +--- a/src/plugins_types/date_and_time.c ++++ b/src/plugins_types/date_and_time.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c +index 90546d69..91ddbde2 100644 +--- a/src/plugins_types/identityref.c ++++ b/src/plugins_types/identityref.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/integer.c b/src/plugins_types/integer.c +index 44e87f99..bf2b7812 100644 +--- a/src/plugins_types/integer.c ++++ b/src/plugins_types/integer.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv4_address.c b/src/plugins_types/ipv4_address.c +index a95752ea..a7369d6b 100644 +--- a/src/plugins_types/ipv4_address.c ++++ b/src/plugins_types/ipv4_address.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv4_address_no_zone.c b/src/plugins_types/ipv4_address_no_zone.c +index a17a7efe..1fb34b06 100644 +--- a/src/plugins_types/ipv4_address_no_zone.c ++++ b/src/plugins_types/ipv4_address_no_zone.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv4_prefix.c b/src/plugins_types/ipv4_prefix.c +index 3108b2c5..6fb93390 100644 +--- a/src/plugins_types/ipv4_prefix.c ++++ b/src/plugins_types/ipv4_prefix.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv6_address.c b/src/plugins_types/ipv6_address.c +index c0d20fa4..d09425b3 100644 +--- a/src/plugins_types/ipv6_address.c ++++ b/src/plugins_types/ipv6_address.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv6_address_no_zone.c b/src/plugins_types/ipv6_address_no_zone.c +index c612b663..06bd1891 100644 +--- a/src/plugins_types/ipv6_address_no_zone.c ++++ b/src/plugins_types/ipv6_address_no_zone.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/ipv6_prefix.c b/src/plugins_types/ipv6_prefix.c +index b3ad34b6..91431fef 100644 +--- a/src/plugins_types/ipv6_prefix.c ++++ b/src/plugins_types/ipv6_prefix.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/plugins_types/union.c b/src/plugins_types/union.c +index a8ec43b3..89e81c7a 100644 +--- a/src/plugins_types/union.c ++++ b/src/plugins_types/union.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* strdup */ +-#include + + #include "plugins_types.h" + +diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c +index 424b7f8f..273023de 100644 +--- a/src/schema_compile_node.c ++++ b/src/schema_compile_node.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "schema_compile_node.h" + +diff --git a/src/tree_data_helpers.c b/src/tree_data_helpers.c +index 488efbbb..2d9ba624 100644 +--- a/src/tree_data_helpers.c ++++ b/src/tree_data_helpers.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include + #include +diff --git a/src/tree_schema.c b/src/tree_schema.c +index 93f29796..4a57cc47 100644 +--- a/src/tree_schema.c ++++ b/src/tree_schema.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "tree_schema.h" + +diff --git a/src/validation.c b/src/validation.c +index b9eda810..e2062256 100644 +--- a/src/validation.c ++++ b/src/validation.c +@@ -12,7 +12,6 @@ + * https://opensource.org/licenses/BSD-3-Clause + */ + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "validation.h" + +diff --git a/src/xpath.c b/src/xpath.c +index b68a76b8..ea1cdfc9 100644 +--- a/src/xpath.c ++++ b/src/xpath.c +@@ -12,7 +12,6 @@ + * https://opensource.org/licenses/BSD-3-Clause + */ + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include "xpath.h" + +diff --git a/tools/re/main.c b/tools/re/main.c +index b512ad80..4d8aa99c 100644 +--- a/tools/re/main.c ++++ b/tools/re/main.c +@@ -13,7 +13,6 @@ + */ + + #define _GNU_SOURCE /* asprintf, strdup */ +-#include + + #include + #include +-- +2.31.1 + diff --git a/docker/alpine/libyang/11-utest-dont-parse-dlerror.patch b/docker/alpine/libyang/11-utest-dont-parse-dlerror.patch new file mode 100644 index 0000000..054862f --- /dev/null +++ b/docker/alpine/libyang/11-utest-dont-parse-dlerror.patch @@ -0,0 +1,40 @@ +From 2054431ea3024b177083f09c66c1bb4c3d08b048 Mon Sep 17 00:00:00 2001 +From: Wesley Coakley +Date: Wed, 16 Jun 2021 00:30:50 -0400 +Subject: [PATCH] don't attempt to parse dlerror() in utests + +--- + tests/utests/basic/test_plugins.c | 17 ----------------- + 1 file changed, 17 deletions(-) + +diff --git a/tests/utests/basic/test_plugins.c b/tests/utests/basic/test_plugins.c +index fd9e6130..662fd9b4 100644 +--- a/tests/utests/basic/test_plugins.c ++++ b/tests/utests/basic/test_plugins.c +@@ -36,23 +36,6 @@ static void + test_add_invalid(void **state) + { + assert_int_equal(LY_ESYS, lyplg_add(TESTS_BIN "/plugins/plugin_does_not_exist" LYPLG_SUFFIX)); +- +-#ifdef __APPLE__ +- CHECK_LOG("Loading \""TESTS_BIN "/plugins/plugin_does_not_exist" LYPLG_SUFFIX "\" as a plugin failed " +- "(dlopen("TESTS_BIN "/plugins/plugin_does_not_exist" LYPLG_SUFFIX ", 2): image not found).", NULL); +-#else +- CHECK_LOG("Loading \""TESTS_BIN "/plugins/plugin_does_not_exist" LYPLG_SUFFIX "\" as a plugin failed " +- "("TESTS_BIN "/plugins/plugin_does_not_exist" LYPLG_SUFFIX ": cannot open shared object file: " +- "No such file or directory).", NULL); +-#endif +- +- assert_int_equal(LY_EINVAL, lyplg_add(TESTS_BIN "/plugins/plugin_invalid" LYPLG_SUFFIX)); +-#ifndef __APPLE__ +- /* OS X prints address of the symbol being searched and cmocka doesn't support wildcards in string checking assert */ +- CHECK_LOG("Processing user type plugin \""TESTS_BIN "/plugins/plugin_invalid"LYPLG_SUFFIX "\" failed, " +- "missing type plugins information ("TESTS_BIN "/plugins/plugin_invalid"LYPLG_SUFFIX ": " +- "undefined symbol: plugins_types__).", NULL); +-#endif + } + + static void +-- +2.31.1 + diff --git a/docker/alpine/libyang/APKBUILD b/docker/alpine/libyang/APKBUILD new file mode 100755 index 0000000..6973fd6 --- /dev/null +++ b/docker/alpine/libyang/APKBUILD @@ -0,0 +1,40 @@ +# Contributor: Sören Tempel +# Maintainer: Christian Franke +pkgname=libyang +pkgver=2.1.128 +pkgrel=0 +pkgdesc="YANG data modelling language parser and toolkit" +url="https://github.com/CESNET/libyang" +arch="all" +license="BSD-3-Clause-Clear" +makedepends="bison cmake cmocka-dev flex pcre2-dev" +checkdepends="expect grep shunit2" +subpackages="$pkgname-dev $pkgname-doc" +source="$pkgname-$pkgver.tar.gz::https://github.com/CESNET/libyang/archive/v$pkgver.tar.gz" + +# secfixes: +# 1.0.215-r1: +# - CVE-2021-28902 +# - CVE-2021-28903 +# - CVE-2021-28904 +# - CVE-2021-28905 +# - CVE-2021-28906 + +build() { + if [ "$CBUILD" != "$CHOST" ]; then + CMAKE_CROSSOPTS="-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_HOST_SYSTEM_NAME=Linux" + fi + cmake -B build \ + -DCMAKE_BUILD_TYPE=None \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DBUILD_SHARED_LIBS=True \ + -DCMAKE_C_FLAGS="$CFLAGS" \ + -DENABLE_BUILD_TESTS=ON \ + "$CMAKE_CROSSOPTS" + make -C build +} + +package() { + make -C build DESTDIR="$pkgdir" install +} diff --git a/docker/centos-7/Dockerfile b/docker/centos-7/Dockerfile new file mode 100644 index 0000000..8739cee --- /dev/null +++ b/docker/centos-7/Dockerfile @@ -0,0 +1,58 @@ +# This stage builds an rpm from the source +FROM centos:centos7 as centos-7-builder +RUN yum install -y epel-release +RUN yum install -y rpm-build autoconf automake libtool make \ + readline-devel texinfo net-snmp-devel groff pkgconfig \ + json-c-devel pam-devel bison flex pytest c-ares-devel \ + python3-devel python3-sphinx libcap-devel systemd-devel \ + protobuf-c-devel \ + https://ci1.netdef.org/artifact/LIBYANG-LIBYANGV2/shared/build-2/CentOS-7-x86_64-Packages/libyang2-2.0.0.10.g2eb910e4-1.el7.x86_64.rpm \ + https://ci1.netdef.org/artifact/LIBYANG-LIBYANGV2/shared/build-2/CentOS-7-x86_64-Packages/libyang2-devel-2.0.0.10.g2eb910e4-1.el7.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-0.8.0-1.el7.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-devel-0.8.0-1.el7.x86_64.rpm + +COPY . /src +ARG PKGVER + +RUN echo '%_smp_mflags %( echo "-j$(/usr/bin/getconf _NPROCESSORS_ONLN)"; )' >> /root/.rpmmacros \ + && cd /src \ + && ./bootstrap.sh \ + && ./configure \ + --enable-rpki \ + --enable-numeric-version \ + --with-pkg-extra-version="_git$PKGVER" \ + && make dist \ + && cd / \ + && mkdir -p /rpmbuild/{SOURCES,SPECS} \ + && cp /src/frr*.tar.gz /rpmbuild/SOURCES \ + && cp /src/redhat/frr.spec /rpmbuild/SPECS \ + && rpmbuild \ + --define "_topdir /rpmbuild" \ + -ba /rpmbuild/SPECS/frr.spec + +# This stage installs frr from the rpm +FROM centos:centos7 +RUN mkdir -p /pkgs/rpm \ + && yum install -y https://ci1.netdef.org/artifact/LIBYANG-LIBYANGV2/shared/build-2/CentOS-7-x86_64-Packages/libyang2-2.0.0.10.g2eb910e4-1.el7.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-0.8.0-1.el7.x86_64.rpm + +COPY --from=centos-7-builder /rpmbuild/RPMS/ /pkgs/rpm/ + +RUN yum install -y /pkgs/rpm/*/*.rpm \ + && rm -rf /pkgs + +# Own the config / PID files +RUN mkdir -p /var/run/frr +RUN chown -R frr:frr /etc/frr /var/run/frr + +# Add tini because no CentOS7 package +ENV TINI_VERSION v0.19.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /sbin/tini +RUN chmod +x /sbin/tini + +# Simple init manager for reaping processes and forwarding signals +ENTRYPOINT ["/sbin/tini", "--"] + +# Default CMD starts watchfrr +COPY docker/centos-7/docker-start /usr/lib/frr/docker-start +CMD ["/usr/lib/frr/docker-start"] diff --git a/docker/centos-7/build.sh b/docker/centos-7/build.sh new file mode 100755 index 0000000..b3022d7 --- /dev/null +++ b/docker/centos-7/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +## +# Package version needs to be decimal +## +GITREV="$(git rev-parse --short=10 HEAD)" +PKGVER="$(printf '%u\n' 0x$GITREV)" + +mkdir -p docker/centos-7/pkgs +docker build \ + --file=docker/centos-7/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:centos-7-builder-$GITREV" \ + --target=centos-7-builder \ + . + +# Copy RPM package from container to host +CONTAINER_ID="$(docker create "frr:centos-7-builder-$GITREV")" +docker cp "${CONTAINER_ID}:/rpmbuild/RPMS/x86_64/" docker/centos-7/pkgs +docker rm "${CONTAINER_ID}" + +docker build \ + --cache-from="frr:centos-7-builder-$GITREV" \ + --file=docker/centos-7/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:centos-7-$GITREV" \ + . + +docker rmi "frr:centos-7-builder-$GITREV" diff --git a/docker/centos-7/docker-start b/docker/centos-7/docker-start new file mode 100755 index 0000000..d954142 --- /dev/null +++ b/docker/centos-7/docker-start @@ -0,0 +1,4 @@ +#!/bin/bash + +source /usr/lib/frr/frrcommon.sh +/usr/lib/frr/watchfrr $(daemon_list) diff --git a/docker/centos-8/Dockerfile b/docker/centos-8/Dockerfile new file mode 100644 index 0000000..baed1fe --- /dev/null +++ b/docker/centos-8/Dockerfile @@ -0,0 +1,67 @@ +# This stage builds an rpm from the source +FROM centos:centos8 as centos-8-builder + +RUN sed -i -e "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-* +RUN sed -i -e "s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g" /etc/yum.repos.d/CentOS-* + +RUN dnf install --enablerepo=powertools -y rpm-build git autoconf pcre-devel \ + systemd-devel \ + automake libtool make readline-devel texinfo net-snmp-devel pkgconfig \ + groff pkgconfig json-c-devel pam-devel bison flex python3-pytest \ + c-ares-devel python3-devel python3-sphinx libcap-devel platform-python-devel \ + protobuf-c-devel \ + https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-00181/RedHat-8-x86_64-Packages/libyang-2.1.80-1.el8.x86_64.rpm \ + https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-00181/RedHat-8-x86_64-Packages/libyang-devel-2.1.80-1.el8.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-0.8.0-1.el7.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-devel-0.8.0-1.el7.x86_64.rpm + +COPY . /src + +ARG PKGVER + +RUN echo '%_smp_mflags %( echo "-j$(/usr/bin/getconf _NPROCESSORS_ONLN)"; )' >> /root/.rpmmacros \ + && cd /src \ + && ./bootstrap.sh \ + && ./configure \ + --enable-rpki \ + --enable-numeric-version \ + --with-pkg-extra-version="_git$PKGVER" \ + && make dist \ + && cd / \ + && mkdir -p /rpmbuild/{SOURCES,SPECS} \ + && cp /src/frr*.tar.gz /rpmbuild/SOURCES \ + && cp /src/redhat/frr.spec /rpmbuild/SPECS \ + && rpmbuild \ + --define "_topdir /rpmbuild" \ + -ba /rpmbuild/SPECS/frr.spec + +# This stage installs frr from the rpm +FROM centos:centos8 + +RUN sed -i -e "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-* \ + && sed -i -e "s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g" /etc/yum.repos.d/CentOS-* + +RUN mkdir -p /pkgs/rpm \ + && yum install -y https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-00181/RedHat-8-x86_64-Packages/libyang-2.1.80-1.el8.x86_64.rpm \ + https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-00146/CentOS-7-x86_64-Packages/librtr-0.8.0-1.el7.x86_64.rpm + +COPY --from=centos-8-builder /rpmbuild/RPMS/ /pkgs/rpm/ + +RUN yum install -y /pkgs/rpm/*/*.rpm \ + && rm -rf /pkgs + +# Own the config / PID files +RUN mkdir -p /var/run/frr +RUN chown -R frr:frr /etc/frr /var/run/frr + +# Add tini because no CentOS8 package +ENV TINI_VERSION v0.19.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /sbin/tini +RUN chmod +x /sbin/tini + +# Simple init manager for reaping processes and forwarding signals +ENTRYPOINT ["/sbin/tini", "--"] + +# Default CMD starts watchfrr +COPY docker/centos-8/docker-start /usr/lib/frr/docker-start +CMD ["/usr/lib/frr/docker-start"] diff --git a/docker/centos-8/build.sh b/docker/centos-8/build.sh new file mode 100755 index 0000000..4a99184 --- /dev/null +++ b/docker/centos-8/build.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +set -e + +## +# Package version needs to be decimal +## +GITREV="$(git rev-parse --short=10 HEAD)" +PKGVER="$(printf '%u\n' 0x$GITREV)" + +mkdir -p docker/centos-8/pkgs +docker build \ + --file=docker/centos-8/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:centos-8-builder-$GITREV" \ + --target=centos-8-builder \ + . + +# Copy RPM package from container to host +CONTAINER_ID="$(docker create "frr:centos-8-builder-$GITREV")" +docker cp "${CONTAINER_ID}:/rpmbuild/RPMS/x86_64/" docker/centos-8/pkgs +docker rm "${CONTAINER_ID}" + +docker build \ + --cache-from="frr:centos-8-builder-$GITREV" \ + --file=docker/centos-8/Dockerfile \ + --build-arg="PKGVER=$PKGVER" \ + --tag="frr:centos-8-$GITREV" \ + . + +docker rmi "frr:centos-8-builder-$GITREV" diff --git a/docker/centos-8/docker-start b/docker/centos-8/docker-start new file mode 100755 index 0000000..d954142 --- /dev/null +++ b/docker/centos-8/docker-start @@ -0,0 +1,4 @@ +#!/bin/bash + +source /usr/lib/frr/frrcommon.sh +/usr/lib/frr/watchfrr $(daemon_list) diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile new file mode 100644 index 0000000..b317b05 --- /dev/null +++ b/docker/debian/Dockerfile @@ -0,0 +1,28 @@ +FROM debian:buster +MAINTAINER Rob Gil (rob@rem5.com) + +ENV DEBIAN_FRONTEND noninteractive +ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn + +RUN apt-get update && \ + apt-get install -y libpcre3-dev apt-transport-https ca-certificates curl wget logrotate \ + libc-ares2 libjson-c3 vim procps libreadline7 gnupg2 lsb-release apt-utils \ + libprotobuf-c-dev protobuf-c-compiler tini && rm -rf /var/lib/apt/lists/* + +RUN curl -s https://deb.frrouting.org/frr/keys.asc | apt-key add - +RUN echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) frr-stable | tee -a /etc/apt/sources.list.d/frr.list + +RUN apt-get update && \ + apt-get install -y frr frr-pythontools && \ + rm -rf /var/lib/apt/lists/* + +# Own the config / PID files +RUN mkdir -p /var/run/frr +RUN chown -R frr:frr /etc/frr /var/run/frr + +# Simple init manager for reaping processes and forwarding signals +ENTRYPOINT ["/usr/bin/tini", "--"] + +# Default CMD starts watchfrr +COPY --chmod=0755 docker-start /usr/lib/frr/docker-start +CMD ["/usr/lib/frr/docker-start"] diff --git a/docker/debian/README.md b/docker/debian/README.md new file mode 100644 index 0000000..3c1209b --- /dev/null +++ b/docker/debian/README.md @@ -0,0 +1,20 @@ +# Debian 10 Docker + +This is a binary docker container build of Debian 10 (buster) with FRR. + +# Build + +``` +docker build -t frr-debian:latest . +``` + +# Running + +``` +docker run -itd --privileged --name frr frr-debian:latest +``` + +vtysh +``` +docker exec -it frr vtysh +``` diff --git a/docker/debian/docker-start b/docker/debian/docker-start new file mode 100755 index 0000000..d954142 --- /dev/null +++ b/docker/debian/docker-start @@ -0,0 +1,4 @@ +#!/bin/bash + +source /usr/lib/frr/frrcommon.sh +/usr/lib/frr/watchfrr $(daemon_list) diff --git a/docker/ubi8-minimal/Dockerfile b/docker/ubi8-minimal/Dockerfile new file mode 100644 index 0000000..14a5cdd --- /dev/null +++ b/docker/ubi8-minimal/Dockerfile @@ -0,0 +1,136 @@ +# This stage builds an rpm from the source +ARG UBI8_MINIMAL_VERSION +FROM registry.access.redhat.com/ubi8/ubi-minimal:${UBI8_MINIMAL_VERSION} as ubi8-minimal-builder + +RUN rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8 + +ADD docker/ubi8-minimal/almalinux.repo /etc/yum.repos.d/almalinux.repo + +# ubi8-minimal comes with broken tzdata package installed, so we need to remove them +# and later reinstall it again: https://bugzilla.redhat.com/show_bug.cgi?id=1668185 +RUN rpm --quiet -e --nodeps tzdata >/dev/null 2>&1 + +# Keep the list of dependencies alphabetically sorted for easier maintenance +RUN microdnf --disableplugin=subscription-manager --setopt=install_weak_deps=0 install \ + autoconf \ + automake \ + bison \ + c-ares-devel \ + flex \ + git \ + groff \ + json-c-devel \ + libcap-devel \ + libssh-devel \ + libtool \ + make \ + net-snmp-devel \ + openssl \ + pam-devel \ + pcre-devel \ + pkgconfig \ + platform-python-devel \ + protobuf-c-devel \ + python3-devel \ + python3-pytest \ + python3-sphinx \ + readline-devel \ + rpm-build \ + systemd-devel \ + texinfo \ + tzdata \ + && microdnf --disableplugin=subscription-manager clean all + +RUN curl -sSL -o /tmp/libyang2.rpm https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-181/RedHat-8-x86_64-Packages/libyang-2.1.80-1.el8.x86_64.rpm \ + && rpm -i /tmp/libyang2.rpm \ + && rm -f /tmp/libyang2.rpm + +RUN curl -sSL -o /tmp/libyang2-devel.rpm https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-181/RedHat-8-x86_64-Packages/libyang-devel-2.1.80-1.el8.x86_64.rpm \ + && rpm -i /tmp/libyang2-devel.rpm \ + && rm -f /tmp/libyang2-devel.rpm + +RUN curl -sSL -o /tmp/librtr.rpm https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-149/RedHat-8-x86_64-Packages/librtr-0.8.0-1.el8.x86_64.rpm \ + && rpm -i /tmp/librtr.rpm \ + && rm -f /tmp/librtr.rpm + +RUN curl -sSL -o /tmp/librtr-devel.rpm https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-149/RedHat-8-x86_64-Packages/librtr-devel-0.8.0-1.el8.x86_64.rpm \ + && rpm -i /tmp/librtr-devel.rpm \ + && rm -f /tmp/librtr-devel.rpm + +COPY . /src + +ARG PKGVER + +RUN echo '%_smp_mflags %( echo "-j$(/usr/bin/getconf _NPROCESSORS_ONLN)"; )' >> /root/.rpmmacros \ + && cd /src \ + && ./bootstrap.sh \ + && ./configure \ + --enable-rpki \ + --enable-snmp=agentx \ + --enable-numeric-version \ + --with-pkg-extra-version="_git$PKGVER" \ + && make dist \ + && cd / \ + && mkdir -p /rpmbuild/{SOURCES,SPECS} \ + && cp /src/frr*.tar.gz /rpmbuild/SOURCES \ + && cp /src/redhat/frr.spec /rpmbuild/SPECS \ + && rpmbuild \ + --define "_topdir /rpmbuild" \ + -ba /rpmbuild/SPECS/frr.spec + +# This stage installs frr from the rpm +FROM registry.access.redhat.com/ubi8/ubi-minimal:${UBI8_MINIMAL_VERSION} +ARG FRR_IMAGE_TAG +ARG FRR_RELEASE +ARG FRR_NAME +ARG FRR_VENDOR +LABEL name=$FRR_NAME \ + vendor=$FRR_VENDOR \ + version=$FRR_IMAGE_TAG \ + release=$FRR_RELEASE + +ADD docker/ubi8-minimal/almalinux.repo /etc/yum.repos.d/almalinux.repo + +RUN rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-8 + +# Keep the list of dependencies alphabetically sorted for easier maintenance +RUN microdnf --disableplugin=subscription-manager --setopt=install_weak_deps=0 install \ + c-ares \ + initscripts \ + net-snmp-agent-libs \ + net-snmp-libs \ + openssl \ + protobuf-c \ + python3 \ + shadow-utils \ + systemd \ + && microdnf --disableplugin=subscription-manager clean all + +RUN curl -sSL -o /tmp/libyang2.rpm https://ci1.netdef.org/artifact/LIBYANG-LIBYANG2/shared/build-181/RedHat-8-x86_64-Packages/libyang-2.1.80-1.el8.x86_64.rpm \ + && rpm -i /tmp/libyang2.rpm \ + && rm -f /tmp/libyang2.rpm + +RUN curl -sSL -o /tmp/librtr.rpm https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-149/RedHat-8-x86_64-Packages/librtr-0.8.0-1.el8.x86_64.rpm \ + && rpm -i /tmp/librtr.rpm \ + && rm -f /tmp/librtr.rpm + +COPY --from=ubi8-minimal-builder /rpmbuild/RPMS/ /pkgs/rpm/ + +# Install packages and create FRR files and folders. Be sure to own the config / PID files +RUN rpm -i /pkgs/rpm/x86_64/*.rpm \ + && rm -rf /pkgs \ + && rm -rf /mnt/rootfs/var/cache/* /mnt/rootfs/var/log/dnf* /mnt/rootfs/var/log/yum.* \ + && mkdir -p /var/run/frr \ + && chown -R frr:frr /etc/frr /var/run/frr + +# There is no package for tini, add it manually +ENV TINI_VERSION v0.19.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /sbin/tini +RUN chmod +x /sbin/tini + +# Simple init manager for reaping processes and forwarding signals +ENTRYPOINT ["/sbin/tini", "--"] + +# Default CMD starts watchfrr +COPY docker/ubi8-minimal/docker-start /usr/lib/frr/docker-start +CMD ["/usr/lib/frr/docker-start"] diff --git a/docker/ubi8-minimal/almalinux.repo b/docker/ubi8-minimal/almalinux.repo new file mode 100644 index 0000000..9b9877b --- /dev/null +++ b/docker/ubi8-minimal/almalinux.repo @@ -0,0 +1,23 @@ +[AlmaLinux - baseos] +name=AlmaLinux $releasever - BaseOS +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos +# baseurl=https://repo.almalinux.org/almalinux/$releasever/BaseOS/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 + +[AlmaLinux - appstream] +name=AlmaLinux $releasever - AppStream +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/appstream +# baseurl=https://repo.almalinux.org/almalinux/$releasever/AppStream/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 + +[AlmaLinux - powertools] +name=AlmaLinux $releasever - PowerTools +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/powertools +# baseurl=https://repo.almalinux.org/almalinux/$releasever/PowerTools/$basearch/os/ +enabled=1 +gpgcheck=1 +countme=1 diff --git a/docker/ubi8-minimal/build.sh b/docker/ubi8-minimal/build.sh new file mode 100755 index 0000000..2aa45c9 --- /dev/null +++ b/docker/ubi8-minimal/build.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +set -e + +## +# Package version needs to be decimal +## +DISTRO=ubi8-minimal + +UBI8_MINIMAL_VERSION=$1 +if [ -z "$UBI8_MINIMAL_VERSION" ]; then + UBI8_MINIMAL_VERSION="latest" +fi + +GITREV="$2" +if [ -z "$GITREV" ];then + GITREV="$(git rev-parse --short=10 HEAD)" +fi + +FRR_IMAGE_TAG="$3" +if [ -z $FRR_IMAGE_TAG ];then + FRR_IMAGE_TAG="frr:ubi8-minimal-$GITREV" +fi +PKGVER="$(printf '%u\n' 0x$GITREV)" + +FRR_RELEASE="$4" +if [ -z $FRR_RELEASE ];then + FRR_RELEASE=$(git describe --tags --abbrev=0) +fi + +FRR_NAME=$5 +if [ -z $FRR_NAME ];then + FRR_NAME=frr +fi + +FRR_VENDOR=$6 +if [ -z $FRR_VENDOR ];then + FRR_VENDOR=frr +fi + +DOCKERFILE_PATH="$(dirname $(realpath $0))/Dockerfile" + +docker build \ + --cache-from="frr:$DISTRO-builder-$GITREV" \ + --file="$DOCKERFILE_PATH" \ + --build-arg="UBI8_MINIMAL_VERSION=$UBI8_MINIMAL_VERSION" \ + --build-arg="PKGVER=$PKGVER" \ + --build-arg="FRR_IMAGE_TAG=$FRR_IMAGE_TAG" \ + --build-arg="FRR_RELEASE=$FRR_RELEASE" \ + --build-arg="FRR_NAME=$FRR_NAME" \ + --build-arg="FRR_VENDOR=$FRR_VENDOR" \ + --tag="$FRR_IMAGE_TAG" \ + . + diff --git a/docker/ubi8-minimal/docker-start b/docker/ubi8-minimal/docker-start new file mode 100755 index 0000000..d954142 --- /dev/null +++ b/docker/ubi8-minimal/docker-start @@ -0,0 +1,4 @@ +#!/bin/bash + +source /usr/lib/frr/frrcommon.sh +/usr/lib/frr/watchfrr $(daemon_list) diff --git a/docker/ubuntu-ci/Dockerfile b/docker/ubuntu-ci/Dockerfile new file mode 100644 index 0000000..5c4649d --- /dev/null +++ b/docker/ubuntu-ci/Dockerfile @@ -0,0 +1,134 @@ +ARG UBUNTU_VERSION=22.04 +FROM ubuntu:$UBUNTU_VERSION + +ARG DEBIAN_FRONTEND=noninteractive +ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn + +# Update and install build requirements. +RUN apt update && apt upgrade -y && \ + # Basic build requirements from documentation + apt-get install -y \ + autoconf \ + automake \ + bison \ + build-essential \ + flex \ + git \ + install-info \ + libc-ares-dev \ + libcap-dev \ + libelf-dev \ + libjson-c-dev \ + libpam0g-dev \ + libreadline-dev \ + libsnmp-dev \ + libsqlite3-dev \ + lsb-release \ + libtool \ + lcov \ + make \ + perl \ + pkg-config \ + python3-dev \ + python3-sphinx \ + screen \ + texinfo \ + tmux \ + && \ + # Protobuf build requirements + apt-get install -y \ + libprotobuf-c-dev \ + protobuf-c-compiler \ + && \ + # Libyang2 extra build requirements + apt-get install -y \ + cmake \ + libpcre2-dev \ + && \ + # GRPC extra build requirements + apt-get install -y \ + libgrpc-dev \ + libgrpc++-dev \ + protobuf-compiler-grpc \ + && \ + # Runtime/triage/testing requirements + apt-get install -y \ + curl \ + gdb \ + kmod \ + iproute2 \ + iputils-ping \ + liblua5.3-dev \ + libssl-dev \ + lua5.3 \ + net-tools \ + python3 \ + python3-pip \ + snmp \ + snmp-mibs-downloader \ + snmpd \ + sudo \ + time \ + tshark \ + valgrind \ + yodl \ + && \ + download-mibs && \ + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/iana/IANA-IPPM-METRICS-REGISTRY-MIB -O /usr/share/snmp/mibs/iana/IANA-IPPM-METRICS-REGISTRY-MIB && \ + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/SNMPv2-PDU -O /usr/share/snmp/mibs/ietf/SNMPv2-PDU && \ + wget https://raw.githubusercontent.com/FRRouting/frr-mibs/main/ietf/IPATM-IPMC-MIB -O /usr/share/snmp/mibs/ietf/IPATM-IPMC-MIB && \ + python3 -m pip install wheel && \ + python3 -m pip install 'protobuf<4' grpcio grpcio-tools && \ + python3 -m pip install 'pytest>=6.2.4' 'pytest-xdist>=2.3.0' && \ + python3 -m pip install 'scapy>=2.4.5' && \ + python3 -m pip install xmltodict && \ + python3 -m pip install git+https://github.com/Exa-Networks/exabgp@0659057837cd6c6351579e9f0fa47e9fb7de7311 + +RUN groupadd -r -g 92 frr && \ + groupadd -r -g 85 frrvty && \ + adduser --system --ingroup frr --home /home/frr \ + --gecos "FRR suite" --shell /bin/bash frr && \ + usermod -a -G frrvty frr && \ + useradd -d /var/run/exabgp/ -s /bin/false exabgp && \ + echo 'frr ALL = NOPASSWD: ALL' | tee /etc/sudoers.d/frr && \ + mkdir -p /home/frr && chown frr.frr /home/frr + +# Install FRR built packages +RUN mkdir -p /etc/apt/keyrings && \ + curl -s -o /etc/apt/keyrings/frrouting.gpg https://deb.frrouting.org/frr/keys.gpg && \ + echo deb '[signed-by=/etc/apt/keyrings/frrouting.gpg]' https://deb.frrouting.org/frr \ + $(lsb_release -s -c) "frr-stable" > /etc/apt/sources.list.d/frr.list && \ + apt-get update && apt-get install -y librtr-dev libyang2-dev libyang2-tools + +USER frr:frr + +COPY --chown=frr:frr . /home/frr/frr/ + +RUN cd ~/frr && \ + ./bootstrap.sh && \ + ./configure \ + --prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-gcov \ + --enable-dev-build \ + --enable-mgmtd-test-be-client \ + --enable-rpki \ + --enable-sharpd \ + --enable-multipath=64 \ + --enable-user=frr \ + --enable-group=frr \ + --enable-config-rollbacks \ + --enable-grpc \ + --enable-vty-group=frrvty \ + --enable-snmp=agentx \ + --enable-scripting \ + --with-pkg-extra-version=-my-manual-build && \ + make -j $(nproc) && \ + sudo make install + +RUN cd ~/frr && make check || true + +COPY docker/ubuntu-ci/docker-start /usr/sbin/docker-start +CMD ["/usr/sbin/docker-start"] diff --git a/docker/ubuntu-ci/docker-start b/docker/ubuntu-ci/docker-start new file mode 100755 index 0000000..c383ea8 --- /dev/null +++ b/docker/ubuntu-ci/docker-start @@ -0,0 +1,5 @@ +#!/bin/bash +if [ $(uname -a | grep -ci Ubuntu) -ge 1 ]; then + sudo modprobe mpls-router mpls-iptunnel vrf +fi +while true ; do sleep 365d ; done diff --git a/docker/ubuntu20-ci/README.md b/docker/ubuntu20-ci/README.md new file mode 100644 index 0000000..6c0ff44 --- /dev/null +++ b/docker/ubuntu20-ci/README.md @@ -0,0 +1,57 @@ +# Ubuntu 20.04 + +This builds an ubuntu 20.04 container for dev / test + +# Build + +``` +docker build -t frr-ubuntu20:latest --build-arg=UBUNTU_VERSION=20.04 -f docker/ubuntu-ci/Dockerfile . +``` + +# Running Full Topotest + +``` +docker run --init -it --privileged --name frr-ubuntu20 -v /lib/modules:/lib/modules frr-ubuntu20:latest bash -c 'cd ~/frr/tests/topotests ; sudo pytest -nauto --dist=loadfile' +``` + +# Extract results from the above run into `run-results` dir and analyze + +``` +tests/topotests/analyze.py -C frr-ubuntu20 -Ar run-results +``` + +# Running + +``` +docker run -d --init --privileged --name frr-ubuntu20 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu20:latest +``` + +# make check + +``` +docker exec frr-ubuntu20 bash -c 'cd ~/frr ; make check' +``` + +# interactive bash + +``` +docker exec -it frr-ubuntu20 bash +``` + +# topotest -- when Host O/S is Ubuntu only + +``` +docker exec frr-ubuntu20 bash -c 'cd ~/frr/tests/topotests/ospf_topo1 ; sudo pytest test_ospf_topo1.py' +``` + +# stop & remove container + +``` +docker stop frr-ubuntu20 ; docker rm frr-ubuntu20 +``` + +# remove image + +``` +docker rmi frr-ubuntu20:latest +``` diff --git a/docker/ubuntu22-ci/README.md b/docker/ubuntu22-ci/README.md new file mode 100644 index 0000000..617192e --- /dev/null +++ b/docker/ubuntu22-ci/README.md @@ -0,0 +1,66 @@ +# Ubuntu 22.04 + +This builds an ubuntu 22.04 container for dev / test + +# Build + +``` +docker build -t frr-ubuntu22:latest -f docker/ubuntu-ci/Dockerfile . +``` + +# Run + +``` +docker run -d --init --privileged --name frr-ubuntu22 --mount type=bind,source=/lib/modules,target=/lib/modules frr-ubuntu22:latest +``` + +# Running full topotest (container stops at end) + +``` +docker run --init -it --privileged --name frr-ubuntu22 \ + -v /lib/modules:/lib/modules frr-ubuntu22:latest \ + bash -c 'cd /home/frr/frr/tests/topotests; sudo pytest -nauto --dist=loadfile' +``` + +# Extract results from the above run into `run-results` dir and analyze + +``` +tests/topotests/analyze.py -C frr-ubuntu22 -Ar run-results +``` + +# Extract coverage from a stopped container into host FRR source tree + +``` +docker export frr-ubuntu22 | tar --strip=3 --wildcards -vx '*.gc??' +lcov -b $(pwd) --capture --directory . --output-file=coverage.info +``` + +# make check + +``` +docker exec frr-ubuntu22 bash -c 'cd ~/frr ; make check' +``` + +# interactive bash + +``` +docker exec -it frr-ubuntu22 bash +``` + +# Run a specific topotest + +``` +docker exec frr-ubuntu22 bash -c 'cd ~/frr/tests/topotests ; sudo pytest ospf_topo1/test_ospf_topo1.py' +``` + +# stop & remove container + +``` +docker stop frr-ubuntu22 ; docker rm frr-ubuntu22 +``` + +# remove image + +``` +docker rmi frr-ubuntu22:latest +``` diff --git a/eigrpd/.gitignore b/eigrpd/.gitignore new file mode 100644 index 0000000..0303c6f --- /dev/null +++ b/eigrpd/.gitignore @@ -0,0 +1,2 @@ +eigrpd +eigrpd.conf diff --git a/eigrpd/Makefile b/eigrpd/Makefile new file mode 100644 index 0000000..b6d6076 --- /dev/null +++ b/eigrpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. eigrpd/eigrpd +%: ALWAYS + @$(MAKE) -s -C .. eigrpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/eigrpd/eigrp_cli.c b/eigrpd/eigrp_cli.c new file mode 100644 index 0000000..eaede73 --- /dev/null +++ b/eigrpd/eigrp_cli.c @@ -0,0 +1,1021 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP daemon CLI implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound_cli.h" + +#include "eigrp_structs.h" +#include "eigrpd.h" +#include "eigrp_zebra.h" +#include "eigrp_cli.h" + +#include "eigrpd/eigrp_cli_clippy.c" + +/* + * XPath: /frr-eigrpd:eigrpd/instance + */ +DEFPY_YANG_NOSH( + router_eigrp, + router_eigrp_cmd, + "router eigrp (1-65535)$as [vrf NAME]", + ROUTER_STR + EIGRP_STR + AS_STR + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + int rv; + + snprintf(xpath, sizeof(xpath), + "/frr-eigrpd:eigrpd/instance[asn='%s'][vrf='%s']", + as_str, vrf ? vrf : VRF_DEFAULT_NAME); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + rv = nb_cli_apply_changes(vty, NULL); + if (rv == CMD_SUCCESS) + VTY_PUSH_XPATH(EIGRP_NODE, xpath); + + return rv; +} + +DEFPY_YANG( + no_router_eigrp, + no_router_eigrp_cmd, + "no router eigrp (1-65535)$as [vrf NAME]", + NO_STR + ROUTER_STR + EIGRP_STR + AS_STR + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-eigrpd:eigrpd/instance[asn='%s'][vrf='%s']", + as_str, vrf ? vrf : VRF_DEFAULT_NAME); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes_clear_pending(vty, NULL); +} + +void eigrp_cli_show_header(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *asn = yang_dnode_get_string(dnode, "asn"); + const char *vrf = yang_dnode_get_string(dnode, "vrf"); + + vty_out(vty, "router eigrp %s", asn); + if (strcmp(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", vrf); + vty_out(vty, "\n"); +} + +void eigrp_cli_show_end_header(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/router-id + */ +DEFPY_YANG( + eigrp_router_id, + eigrp_router_id_cmd, + "eigrp router-id A.B.C.D$addr", + EIGRP_STR + "Router ID for this EIGRP process\n" + "EIGRP Router-ID in IP address format\n") +{ + nb_cli_enqueue_change(vty, "./router-id", NB_OP_MODIFY, addr_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_router_id, + no_eigrp_router_id_cmd, + "no eigrp router-id [A.B.C.D]", + NO_STR + EIGRP_STR + "Router ID for this EIGRP process\n" + "EIGRP Router-ID in IP address format\n") +{ + nb_cli_enqueue_change(vty, "./router-id", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_router_id(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *router_id = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " eigrp router-id %s\n", router_id); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/passive-interface + */ +DEFPY_YANG( + eigrp_passive_interface, + eigrp_passive_interface_cmd, + "[no] passive-interface IFNAME", + NO_STR + "Suppress routing updates on an interface\n" + "Interface to suppress on\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./passive-interface[.='%s']", ifname); + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *ifname = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " passive-interface %s\n", ifname); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/active-time + */ +DEFPY_YANG( + eigrp_timers_active, + eigrp_timers_active_cmd, + "timers active-time <(1-65535)$timer|disabled$disabled>", + "Adjust routing timers\n" + "Time limit for active state\n" + "Active state time limit in seconds\n" + "Disable time limit for active state\n") +{ + if (disabled) + nb_cli_enqueue_change(vty, "./active-time", NB_OP_MODIFY, "0"); + else + nb_cli_enqueue_change(vty, "./active-time", + NB_OP_MODIFY, timer_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_timers_active, + no_eigrp_timers_active_cmd, + "no timers active-time [<(1-65535)|disabled>]", + NO_STR + "Adjust routing timers\n" + "Time limit for active state\n" + "Active state time limit in seconds\n" + "Disable time limit for active state\n") +{ + nb_cli_enqueue_change(vty, "./active-time", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_active_time(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *timer = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " timers active-time %s\n", timer); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/variance + */ +DEFPY_YANG( + eigrp_variance, + eigrp_variance_cmd, + "variance (1-128)$variance", + "Control load balancing variance\n" + "Metric variance multiplier\n") +{ + nb_cli_enqueue_change(vty, "./variance", NB_OP_MODIFY, variance_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_variance, + no_eigrp_variance_cmd, + "no variance [(1-128)]", + NO_STR + "Control load balancing variance\n" + "Metric variance multiplier\n") +{ + nb_cli_enqueue_change(vty, "./variance", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_variance(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *variance = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " variance %s\n", variance); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/maximum-paths + */ +DEFPY_YANG( + eigrp_maximum_paths, + eigrp_maximum_paths_cmd, + "maximum-paths (1-32)$maximum_paths", + "Forward packets over multiple paths\n" + "Number of paths\n") +{ + nb_cli_enqueue_change(vty, "./maximum-paths", NB_OP_MODIFY, + maximum_paths_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_maximum_paths, + no_eigrp_maximum_paths_cmd, + "no maximum-paths [(1-32)]", + NO_STR + "Forward packets over multiple paths\n" + "Number of paths\n") +{ + nb_cli_enqueue_change(vty, "./maximum-paths", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_maximum_paths(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *maximum_paths = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " maximum-paths %s\n", maximum_paths); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K1 + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K2 + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K3 + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K4 + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K5 + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K6 + */ +DEFPY_YANG( + eigrp_metric_weights, + eigrp_metric_weights_cmd, + "metric weights (0-255)$k1 (0-255)$k2 (0-255)$k3 (0-255)$k4 (0-255)$k5 [(0-255)$k6]", + "Modify metrics and parameters for advertisement\n" + "Modify metric coefficients\n" + "K1\n" + "K2\n" + "K3\n" + "K4\n" + "K5\n" + "K6\n") +{ + nb_cli_enqueue_change(vty, "./metric-weights/K1", NB_OP_MODIFY, k1_str); + nb_cli_enqueue_change(vty, "./metric-weights/K2", NB_OP_MODIFY, k2_str); + nb_cli_enqueue_change(vty, "./metric-weights/K3", NB_OP_MODIFY, k3_str); + nb_cli_enqueue_change(vty, "./metric-weights/K4", NB_OP_MODIFY, k4_str); + nb_cli_enqueue_change(vty, "./metric-weights/K5", NB_OP_MODIFY, k5_str); + if (k6) + nb_cli_enqueue_change(vty, "./metric-weights/K6", + NB_OP_MODIFY, k6_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_metric_weights, + no_eigrp_metric_weights_cmd, + "no metric weights [(0-255) (0-255) (0-255) (0-255) (0-255) (0-255)]", + NO_STR + "Modify metrics and parameters for advertisement\n" + "Modify metric coefficients\n" + "K1\n" + "K2\n" + "K3\n" + "K4\n" + "K5\n" + "K6\n") +{ + nb_cli_enqueue_change(vty, "./metric-weights/K1", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./metric-weights/K2", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./metric-weights/K3", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./metric-weights/K4", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./metric-weights/K5", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./metric-weights/K6", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_metrics(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *k1, *k2, *k3, *k4, *k5, *k6; + + k1 = yang_dnode_exists(dnode, "K1") ? + yang_dnode_get_string(dnode, "K1") : "0"; + k2 = yang_dnode_exists(dnode, "K2") ? + yang_dnode_get_string(dnode, "K2") : "0"; + k3 = yang_dnode_exists(dnode, "K3") ? + yang_dnode_get_string(dnode, "K3") : "0"; + k4 = yang_dnode_exists(dnode, "K4") ? + yang_dnode_get_string(dnode, "K4") : "0"; + k5 = yang_dnode_exists(dnode, "K5") ? + yang_dnode_get_string(dnode, "K5") : "0"; + k6 = yang_dnode_exists(dnode, "K6") ? + yang_dnode_get_string(dnode, "K6") : "0"; + + vty_out(vty, " metric weights %s %s %s %s %s", + k1, k2, k3, k4, k5); + if (k6) + vty_out(vty, " %s", k6); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/network + */ +DEFPY_YANG( + eigrp_network, + eigrp_network_cmd, + "[no] network A.B.C.D/M$prefix", + NO_STR + "Enable routing on an IP network\n" + "EIGRP network prefix\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./network[.='%s']", prefix_str); + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_network(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *prefix = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " network %s\n", prefix); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/neighbor + */ +DEFPY_YANG( + eigrp_neighbor, + eigrp_neighbor_cmd, + "[no] neighbor A.B.C.D$addr", + NO_STR + "Specify a neighbor router\n" + "Neighbor address\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./neighbor[.='%s']", addr_str); + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_neighbor(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *prefix = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " neighbor %s\n", prefix); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/distribute-list + */ +DEFPY_YANG (eigrp_distribute_list, + eigrp_distribute_list_cmd, + "distribute-list ACCESSLIST4_NAME$name $dir [WORD$ifname]", + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (eigrp_distribute_list_prefix, + eigrp_distribute_list_prefix_cmd, + "distribute-list prefix PREFIXLIST4_NAME$name $dir [WORD$ifname]", + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (eigrp_no_distribute_list, + eigrp_no_distribute_list_cmd, + "no distribute-list [ACCESSLIST4_NAME$name] $dir [WORD$ifname]", + NO_STR + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (eigrp_no_distribute_list_prefix, + eigrp_no_distribute_list_prefix_cmd, + "no distribute-list prefix [PREFIXLIST4_NAME$name] $dir [WORD$ifname]", + NO_STR + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/route-map + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/delay + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/load + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu + */ +DEFPY_YANG( + eigrp_redistribute_source_metric, + eigrp_redistribute_source_metric_cmd, + "[no] redistribute " FRR_REDIST_STR_EIGRPD + "$proto [metric (1-4294967295)$bw (0-4294967295)$delay (0-255)$rlbt (1-255)$load (1-65535)$mtu]", + NO_STR + REDIST_STR + FRR_REDIST_HELP_STR_EIGRPD + "Metric for redistributed routes\n" + "Bandwidth metric in Kbits per second\n" + "EIGRP delay metric, in 10 microsecond units\n" + "EIGRP reliability metric where 255 is 100% reliable2 ?\n" + "EIGRP Effective bandwidth metric (Loading) where 255 is 100% loaded\n" + "EIGRP MTU of the path\n") +{ + char xpath[XPATH_MAXLEN], xpath_metric[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./redistribute[protocol='%s']", proto); + + if (no) { + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); + } + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + if (bw == 0 || delay == 0 || rlbt == 0 || load == 0 || mtu == 0) + return nb_cli_apply_changes(vty, NULL); + + snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/bandwidth", + xpath); + nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, bw_str); + snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/delay", xpath); + nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, delay_str); + snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/reliability", + xpath); + nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, rlbt_str); + snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/load", xpath); + nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, load_str); + snprintf(xpath_metric, sizeof(xpath_metric), "%s/metrics/mtu", xpath); + nb_cli_enqueue_change(vty, xpath_metric, NB_OP_MODIFY, mtu_str); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *proto = yang_dnode_get_string(dnode, "protocol"); + const char *bw, *delay, *load, *mtu, *rlbt; + + bw = yang_dnode_exists(dnode, "metrics/bandwidth") ? + yang_dnode_get_string(dnode, "metrics/bandwidth") : NULL; + delay = yang_dnode_exists(dnode, "metrics/delay") ? + yang_dnode_get_string(dnode, "metrics/delay") : NULL; + rlbt = yang_dnode_exists(dnode, "metrics/reliability") ? + yang_dnode_get_string(dnode, "metrics/reliability") : NULL; + load = yang_dnode_exists(dnode, "metrics/load") ? + yang_dnode_get_string(dnode, "metrics/load") : NULL; + mtu = yang_dnode_exists(dnode, "metrics/mtu") ? + yang_dnode_get_string(dnode, "metrics/mtu") : NULL; + + vty_out(vty, " redistribute %s", proto); + if (bw || rlbt || delay || load || mtu) + vty_out(vty, " metric %s %s %s %s %s", bw, delay, rlbt, load, + mtu); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/delay + */ +DEFPY_YANG( + eigrp_if_delay, + eigrp_if_delay_cmd, + "delay (1-16777215)$delay", + "Specify interface throughput delay\n" + "Throughput delay (tens of microseconds)\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/delay", + NB_OP_MODIFY, delay_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_if_delay, + no_eigrp_if_delay_cmd, + "no delay [(1-16777215)]", + NO_STR + "Specify interface throughput delay\n" + "Throughput delay (tens of microseconds)\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/delay", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_delay(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *delay = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " delay %s\n", delay); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth + */ +DEFPY_YANG( + eigrp_if_bandwidth, + eigrp_if_bandwidth_cmd, + "eigrp bandwidth (1-10000000)$bw", + EIGRP_STR + "Set bandwidth informational parameter\n" + "Bandwidth in kilobits\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/bandwidth", + NB_OP_MODIFY, bw_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_if_bandwidth, + no_eigrp_if_bandwidth_cmd, + "no eigrp bandwidth [(1-10000000)]", + NO_STR + EIGRP_STR + "Set bandwidth informational parameter\n" + "Bandwidth in kilobits\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/bandwidth", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_bandwidth(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *bandwidth = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " eigrp bandwidth %s\n", bandwidth); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval + */ +DEFPY_YANG( + eigrp_if_ip_hellointerval, + eigrp_if_ip_hellointerval_cmd, + "ip hello-interval eigrp (1-65535)$hello", + "Interface Internet Protocol config commands\n" + "Configures EIGRP hello interval\n" + EIGRP_STR + "Seconds between hello transmissions\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hello-interval", + NB_OP_MODIFY, hello_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_if_ip_hellointerval, + no_eigrp_if_ip_hellointerval_cmd, + "no ip hello-interval eigrp [(1-65535)]", + NO_STR + "Interface Internet Protocol config commands\n" + "Configures EIGRP hello interval\n" + EIGRP_STR + "Seconds between hello transmissions\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hello-interval", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + + +void eigrp_cli_show_hello_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *hello = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " ip hello-interval eigrp %s\n", hello); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time + */ +DEFPY_YANG( + eigrp_if_ip_holdinterval, + eigrp_if_ip_holdinterval_cmd, + "ip hold-time eigrp (1-65535)$hold", + "Interface Internet Protocol config commands\n" + "Configures EIGRP IPv4 hold time\n" + EIGRP_STR + "Seconds before neighbor is considered down\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hold-time", + NB_OP_MODIFY, hold_str); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_if_ip_holdinterval, + no_eigrp_if_ip_holdinterval_cmd, + "no ip hold-time eigrp [(1-65535)]", + NO_STR + "Interface Internet Protocol config commands\n" + "Configures EIGRP IPv4 hold time\n" + EIGRP_STR + "Seconds before neighbor is considered down\n") +{ + nb_cli_enqueue_change(vty, "./frr-eigrpd:eigrp/hold-time", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_hold_time(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *holdtime = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " ip hold-time eigrp %s\n", holdtime); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon + */ +/* NOT implemented. */ + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses + */ +DEFPY_YANG( + eigrp_ip_summary_address, + eigrp_ip_summary_address_cmd, + "ip summary-address eigrp (1-65535)$as A.B.C.D/M$prefix", + "Interface Internet Protocol config commands\n" + "Perform address summarization\n" + EIGRP_STR + AS_STR + "Summary /, e.g. 192.168.0.0/16\n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_auth, sizeof(xpath_auth), + "%s/summarize-addresses[.='%s']", xpath, prefix_str); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_ip_summary_address, + no_eigrp_ip_summary_address_cmd, + "no ip summary-address eigrp (1-65535)$as A.B.C.D/M$prefix", + NO_STR + "Interface Internet Protocol config commands\n" + "Perform address summarization\n" + EIGRP_STR + AS_STR + "Summary /, e.g. 192.168.0.0/16\n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_auth, sizeof(xpath_auth), + "%s/summarize-addresses[.='%s']", xpath, prefix_str); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_summarize_address(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const struct lyd_node *instance = + yang_dnode_get_parent(dnode, "instance"); + uint16_t asn = yang_dnode_get_uint16(instance, "asn"); + const char *summarize_address = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " ip summary-address eigrp %d %s\n", asn, + summarize_address); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication + */ +DEFPY_YANG( + eigrp_authentication_mode, + eigrp_authentication_mode_cmd, + "ip authentication mode eigrp (1-65535)$as $crypt", + "Interface Internet Protocol config commands\n" + "Authentication subcommands\n" + "Mode\n" + EIGRP_STR + AS_STR + "Keyed message digest\n" + "HMAC SHA256 algorithm \n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_auth, sizeof(xpath_auth), "%s/authentication", xpath); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, crypt); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_authentication_mode, + no_eigrp_authentication_mode_cmd, + "no ip authentication mode eigrp (1-65535)$as []", + NO_STR + "Interface Internet Protocol config commands\n" + "Authentication subcommands\n" + "Mode\n" + EIGRP_STR + AS_STR + "Keyed message digest\n" + "HMAC SHA256 algorithm \n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_auth, sizeof(xpath_auth), "%s/authentication", xpath); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, "none"); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_authentication(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const struct lyd_node *instance = + yang_dnode_get_parent(dnode, "instance"); + uint16_t asn = yang_dnode_get_uint16(instance, "asn"); + const char *crypt = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " ip authentication mode eigrp %d %s\n", asn, crypt); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain + */ +DEFPY_YANG( + eigrp_authentication_keychain, + eigrp_authentication_keychain_cmd, + "ip authentication key-chain eigrp (1-65535)$as WORD$name", + "Interface Internet Protocol config commands\n" + "Authentication subcommands\n" + "Key-chain\n" + EIGRP_STR + AS_STR + "Name of key-chain\n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_auth, sizeof(xpath_auth), "%s/keychain", xpath); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_eigrp_authentication_keychain, + no_eigrp_authentication_keychain_cmd, + "no ip authentication key-chain eigrp (1-65535)$as [WORD]", + NO_STR + "Interface Internet Protocol config commands\n" + "Authentication subcommands\n" + "Key-chain\n" + EIGRP_STR + AS_STR + "Name of key-chain\n") +{ + char xpath[XPATH_MAXLEN], xpath_auth[XPATH_MAXLEN + 64]; + + snprintf(xpath, sizeof(xpath), "./frr-eigrpd:eigrp/instance[asn='%s']", + as_str); + snprintf(xpath_auth, sizeof(xpath_auth), "%s/keychain", xpath); + nb_cli_enqueue_change(vty, xpath_auth, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void eigrp_cli_show_keychain(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const struct lyd_node *instance = + yang_dnode_get_parent(dnode, "instance"); + uint16_t asn = yang_dnode_get_uint16(instance, "asn"); + const char *keychain = yang_dnode_get_string(dnode, NULL); + + vty_out(vty, " ip authentication key-chain eigrp %d %s\n", asn, + keychain); +} + + +/* + * CLI installation procedures. + */ +static int eigrp_config_write(struct vty *vty); +static struct cmd_node eigrp_node = { + .name = "eigrp", + .node = EIGRP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = eigrp_config_write, +}; + +static int eigrp_config_write(struct vty *vty) +{ + const struct lyd_node *dnode; + int written = 0; + + dnode = yang_dnode_get(running_config->dnode, "/frr-eigrpd:eigrpd"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + + return written; +} + +void +eigrp_cli_init(void) +{ + install_element(CONFIG_NODE, &router_eigrp_cmd); + install_element(CONFIG_NODE, &no_router_eigrp_cmd); + + install_node(&eigrp_node); + install_default(EIGRP_NODE); + + install_element(EIGRP_NODE, &eigrp_router_id_cmd); + install_element(EIGRP_NODE, &no_eigrp_router_id_cmd); + install_element(EIGRP_NODE, &eigrp_passive_interface_cmd); + install_element(EIGRP_NODE, &eigrp_timers_active_cmd); + install_element(EIGRP_NODE, &no_eigrp_timers_active_cmd); + install_element(EIGRP_NODE, &eigrp_variance_cmd); + install_element(EIGRP_NODE, &no_eigrp_variance_cmd); + install_element(EIGRP_NODE, &eigrp_maximum_paths_cmd); + install_element(EIGRP_NODE, &no_eigrp_maximum_paths_cmd); + install_element(EIGRP_NODE, &eigrp_metric_weights_cmd); + install_element(EIGRP_NODE, &no_eigrp_metric_weights_cmd); + install_element(EIGRP_NODE, &eigrp_network_cmd); + install_element(EIGRP_NODE, &eigrp_neighbor_cmd); + install_element(EIGRP_NODE, &eigrp_distribute_list_cmd); + install_element(EIGRP_NODE, &eigrp_distribute_list_prefix_cmd); + install_element(EIGRP_NODE, &eigrp_no_distribute_list_cmd); + install_element(EIGRP_NODE, &eigrp_no_distribute_list_prefix_cmd); + install_element(EIGRP_NODE, &eigrp_redistribute_source_metric_cmd); + + vrf_cmd_init(NULL); + + if_cmd_init_default(); + + install_element(INTERFACE_NODE, &eigrp_if_delay_cmd); + install_element(INTERFACE_NODE, &no_eigrp_if_delay_cmd); + install_element(INTERFACE_NODE, &eigrp_if_bandwidth_cmd); + install_element(INTERFACE_NODE, &no_eigrp_if_bandwidth_cmd); + install_element(INTERFACE_NODE, &eigrp_if_ip_hellointerval_cmd); + install_element(INTERFACE_NODE, &no_eigrp_if_ip_hellointerval_cmd); + install_element(INTERFACE_NODE, &eigrp_if_ip_holdinterval_cmd); + install_element(INTERFACE_NODE, &no_eigrp_if_ip_holdinterval_cmd); + install_element(INTERFACE_NODE, &eigrp_ip_summary_address_cmd); + install_element(INTERFACE_NODE, &no_eigrp_ip_summary_address_cmd); + install_element(INTERFACE_NODE, &eigrp_authentication_mode_cmd); + install_element(INTERFACE_NODE, &no_eigrp_authentication_mode_cmd); + install_element(INTERFACE_NODE, &eigrp_authentication_keychain_cmd); + install_element(INTERFACE_NODE, &no_eigrp_authentication_keychain_cmd); +} diff --git a/eigrpd/eigrp_cli.h b/eigrpd/eigrp_cli.h new file mode 100644 index 0000000..ed7b274 --- /dev/null +++ b/eigrpd/eigrp_cli.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP CLI Functions. + * Copyright (C) 2019 + * Authors: + * Donnie Savage + */ + +#ifndef _EIGRP_CLI_H_ +#define _EIGRP_CLI_H_ + +/*Prototypes*/ +extern void eigrp_cli_show_header(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_end_header(struct vty *vty, + const struct lyd_node *dnode); +extern void eigrp_cli_show_router_id(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_active_time(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_variance(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_maximum_paths(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_metrics(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_network(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_neighbor(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_redistribute(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_delay(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_bandwidth(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_hello_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_hold_time(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_summarize_address(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_authentication(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_show_keychain(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void eigrp_cli_init(void); + +#endif /*EIGRP_CLI_H_ */ diff --git a/eigrpd/eigrp_const.h b/eigrpd/eigrp_const.h new file mode 100644 index 0000000..05fbae3 --- /dev/null +++ b/eigrpd/eigrp_const.h @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Definition of Constants. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_CONST_H_ +#define _ZEBRA_EIGRP_CONST_H_ + +#define EIGRP_NEIGHBOR_DOWN 0 +#define EIGRP_NEIGHBOR_PENDING 1 +#define EIGRP_NEIGHBOR_UP 2 +#define EIGRP_NEIGHBOR_STATE_MAX 3 + +/*Packet requiring ack will be retransmitted again after this time*/ +#define EIGRP_PACKET_RETRANS_TIME 2 /* in seconds */ +#define EIGRP_PACKET_RETRANS_MAX 16 /* number of retrans attempts */ +#define PLAINTEXT_LENGTH 81 + +/*Metric variance multiplier*/ +#define EIGRP_VARIANCE_DEFAULT 1 +#define EIGRP_MAX_PATHS_DEFAULT 4 + +/* Return values of functions involved in packet verification */ +#define MSG_OK 0 +#define MSG_NG 1 + +#define EIGRP_HEADER_VERSION 2 + +/* Default protocol, port number. */ +#ifndef IPPROTO_EIGRPIGP +#define IPPROTO_EIGRPIGP 88 +#endif /* IPPROTO_EIGRPIGP */ + +#define EIGRP_AUTH_MD5_TLV_SIZE 40 +#define EIGRP_AUTH_SHA256_TLV_SIZE 56 + +/*Cisco routers use only first 44 bytes of basic hello for their MD5 + * calculations*/ +#define EIGRP_MD5_BASIC_COMPUTE 44 +#define EIGRP_MD5_UPDATE_INIT_COMPUTE 40 + +#define EIGRP_AUTH_BASIC_HELLO_FLAG 0x01 +#define EIGRP_AUTH_TID_HELLO_FLAG 0x02 +#define EIGRP_AUTH_UPDATE_INIT_FLAG 0x04 +#define EIGRP_AUTH_UPDATE_FLAG 0x08 +#define EIGRP_AUTH_EXTRA_SALT_FLAG 0x10 + +#define EIGRP_NEXT_SEQUENCE_TLV_SIZE 8 + +/* IP TTL for EIGRP protocol. */ +#define EIGRP_IP_TTL 1 + +/* Default configuration file name for eigrp. */ +#define EIGRP_DEFAULT_CONFIG "eigrpd.conf" + +#define EIGRP_HELLO_INTERVAL_DEFAULT 5 +#define EIGRP_HOLD_INTERVAL_DEFAULT 15 +#define EIGRP_BANDWIDTH_DEFAULT 100000 +#define EIGRP_DELAY_DEFAULT 10 +#define EIGRP_RELIABILITY_DEFAULT 255 +#define EIGRP_LOAD_DEFAULT 1 + +#define EIGRP_MULTICAST_ADDRESS 0xe000000A /*224.0.0.10*/ + +#define EIGRP_MAX_METRIC 0xffffffffU /*4294967295*/ +enum metric_change { METRIC_DECREASE, METRIC_SAME, METRIC_INCREASE }; + +#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX +#define DEFAULT_ROUTE_TYPE(T) ((T) == DEFAULT_ROUTE) + +#define INTERFACE_DOWN_BY_ZEBRA 1 +#define INTERFACE_DOWN_BY_VTY 2 +#define INTERFACE_DOWN_BY_FINAL 3 + +#define EIGRP_HELLO_NORMAL 0x00 +#define EIGRP_HELLO_GRACEFUL_SHUTDOWN 0x01 +#define EIGRP_HELLO_ADD_SEQUENCE 0x02 +#define EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR 0x04 + +/* EIGRP Network Type. */ +#define EIGRP_IFTYPE_NONE 0 +#define EIGRP_IFTYPE_POINTOPOINT 1 +#define EIGRP_IFTYPE_BROADCAST 2 +#define EIGRP_IFTYPE_LOOPBACK 3 +#define EIGRP_IFTYPE_MAX 4 + +#define EIGRP_IF_ACTIVE 0 +#define EIGRP_IF_PASSIVE 1 + +/* EIGRP TT destination type */ +#define EIGRP_TOPOLOGY_TYPE_CONNECTED 0 // Connected network +#define EIGRP_TOPOLOGY_TYPE_REMOTE 1 // Remote internal network +#define EIGRP_TOPOLOGY_TYPE_REMOTE_EXTERNAL 2 // Remote external network + +/*EIGRP TT entry flags*/ +#define EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG (1 << 0) +#define EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG (1 << 1) +#define EIGRP_ROUTE_DESCRIPTOR_INTABLE_FLAG (1 << 2) +#define EIGRP_ROUTE_DESCRIPTOR_EXTERNAL_FLAG (1 << 3) + +/*EIGRP FSM state count, event count*/ +#define EIGRP_FSM_STATE_MAX 5 +#define EIGRP_FSM_EVENT_MAX 16 + +/*EEGRP FSM states*/ +enum eigrp_fsm_states { + EIGRP_FSM_STATE_PASSIVE, + EIGRP_FSM_STATE_ACTIVE_0, + EIGRP_FSM_STATE_ACTIVE_1, + EIGRP_FSM_STATE_ACTIVE_2, + EIGRP_FSM_STATE_ACTIVE_3, +}; + +/*EIGRP FSM events return values*/ +#define EIGRP_FSM_NEED_UPDATE 1 +#define EIGRP_FSM_NEED_QUERY 2 + +/*EIGRP FSM events*/ +enum eigrp_fsm_events { + /* + * Input event other than query from succ, + * FC is not satisfied + */ + EIGRP_FSM_EVENT_NQ_FCN, + + /* last reply, FD is reset */ + EIGRP_FSM_EVENT_LR, + + /* Query from succ, FC not satisfied */ + EIGRP_FSM_EVENT_Q_FCN, + + /* last reply, FC satisifed with current value of FDij */ + EIGRP_FSM_EVENT_LR_FCS, + + /* distance increase while in a active state */ + EIGRP_FSM_EVENT_DINC, + + /* Query from succ while in active state */ + EIGRP_FSM_EVENT_QACT, + + /* last reply, FC not satisfied */ + EIGRP_FSM_EVENT_LR_FCN, + + /* + * state not changed + * usually by receiving not last reply + */ + EIGRP_FSM_KEEP_STATE, +}; + +/** + * External routes originate from some other protocol - these are them + */ +#define NULL_PROTID 0 /*!< unknown protocol */ +#define IGRP_PROTID 1 /*!< IGRP.. whos your daddy! */ +#define EIGRP_PROTID 2 /*!< EIGRP - Just flat out the best */ +#define STATIC_PROTID 3 /*!< Staticly configured source */ +#define RIP_PROTID 4 /*!< Routing Information Protocol */ +#define HELLO_PROTID 5 /*!< Hello? RFC-891 you there? */ +#define OSPF_PROTID 6 /*!< OSPF - Open Shortest Path First */ +#define ISIS_PROTID 7 /*!< Intermediate System To Intermediate System */ +#define EGP_PROTID 8 /*!< Exterior Gateway Protocol */ +#define BGP_PROTID 9 /*!< Border Gateway Protocol */ +#define IDRP_PROTID 10 /*!< InterDomain Routing Protocol */ +#define CONN_PROTID 11 /*!< Connected source */ + +/* + * metric k-value defaults + */ +#define EIGRP_K1_DEFAULT 1 //!< unweighed inverse bandwidth +#define EIGRP_K2_DEFAULT 0 //!< no loading term +#define EIGRP_K3_DEFAULT 1 //!< unweighted delay +#define EIGRP_K4_DEFAULT 0 //!< no reliability term +#define EIGRP_K5_DEFAULT 0 //!< no reliability term +#define EIGRP_K6_DEFAULT 0 //!< do not add in extended metrics + +/* + * EIGRP Fixed header + */ +#define EIGRP_HEADER_LEN 20U +#define EIGRP_PACKET_MAX_LEN 65535U /* includes IP Header size. */ + +#define EIGRP_TLV_HDR_LENGTH 4 + +/** + * EIGRP Packet Opcodes + */ +#define EIGRP_OPC_UPDATE 1 /*!< packet containing routing information */ +#define EIGRP_OPC_REQUEST 2 /*!< sent to request one or more routes */ +#define EIGRP_OPC_QUERY 3 /*!< sent when a routing is in active start */ +#define EIGRP_OPC_REPLY 4 /*!< sent in response to a query */ +#define EIGRP_OPC_HELLO 5 /*!< sent to maintain a peering session */ +#define EIGRP_OPC_IPXSAP 6 /*!< IPX SAP information */ +#define EIGRP_OPC_PROBE 7 /*!< for test purposes */ +#define EIGRP_OPC_ACK 8 /*!< acknowledge */ +#define EIGRP_OPC_SIAQUERY 10 /*!< QUERY - with relaxed restrictions */ +#define EIGRP_OPC_SIAREPLY 11 /*!< REPLY - may contain old routing information */ + +/** + * EIGRP TLV Range definitions + * PDM TLV Range + * General 0x0000 + * IPv4 0x0100 ** TLVs for one and all + * ATALK 0x0200 ** legacy + * IPX 0x0300 ** discontinued + * IPv6 0x0400 ** legacy + * Multiprotocol 0x0600 ** wide metrics + * MultiTopology 0x00f0 ** deprecated + */ +#define EIGRP_TLV_RANGEMASK 0xfff0 /*!< should be 0xff00 - opps */ +#define EIGRP_TLV_GENERAL 0x0000 + +/** + * 1.2 TLV Definitions ** legacy + * These are considered legacyu and are only used for backward compability with + * older Cisco Routers. They should not be your first choice for packet codings + */ +#define EIGRP_TLV_IPv4 0x0100 /*!< Classic IPv4 TLV encoding */ +#define EIGRP_TLV_ATALK 0x0200 /*!< Classic Appletalk TLV encoding*/ +#define EIGRP_TLV_IPX 0x0300 /*!< Classic IPX TLV encoding */ +#define EIGRP_TLV_IPv6 0x0400 /*!< Classic IPv6 TLV encoding */ + +/** + * 2.0 Multi-Protocol TLV Definitions + * These are the current packet formats and should be used for packets + */ +#define EIGRP_TLV_MP 0x0600 /*!< Non-PDM specific encoding */ + +/** + * TLV type definitions. Generic (protocol-independent) TLV types are + * defined here. Protocol-specific ones are defined elsewhere. + */ +#define EIGRP_TLV_PARAMETER (EIGRP_TLV_GENERAL | 0x0001) /*!< eigrp parameters */ +#define EIGRP_TLV_PARAMETER_LEN (12U) +#define EIGRP_TLV_AUTH (EIGRP_TLV_GENERAL | 0x0002) /*!< authentication */ +#define EIGRP_TLV_SEQ (EIGRP_TLV_GENERAL | 0x0003) /*!< sequenced packet */ +#define EIGRP_TLV_SEQ_BASE_LEN (5U) +#define EIGRP_TLV_SW_VERSION (EIGRP_TLV_GENERAL | 0x0004) /*!< software version */ +#define EIGRP_TLV_SW_VERSION_LEN (8U) +#define EIGRP_TLV_NEXT_MCAST_SEQ (EIGRP_TLV_GENERAL | 0x0005) /*!< sequence number */ +#define EIGRP_TLV_PEER_TERMINATION (EIGRP_TLV_GENERAL | 0x0007) /*!< peer termination */ +#define EIGRP_TLV_PEER_TERMINATION_LEN (9U) +#define EIGRP_TLV_PEER_TIDLIST (EIGRP_TLV_GENERAL | 0x0008) /*!< peer sub-topology list */ + +/* Older cisco routers send TIDLIST value wrong, adding for backwards + * compatabily */ +#define EIGRP_TLV_PEER_MTRLIST (EIGRP_TLV_GENERAL | 0x00f5) + +/** + * Route Based TLVs + */ +#define EIGRP_TLV_REQUEST 0x0001 +#define EIGRP_TLV_INTERNAL 0x0002 +#define EIGRP_TLV_EXTERNAL 0x0003 +#define EIGRP_TLV_COMMUNITY 0x0004 +#define EIGRP_TLV_TYPEMASK 0x000f + +#define EIGRP_TLV_IPv4_REQ (EIGRP_TLV_IPv4 | EIGRP_TLV_REQUEST) +#define EIGRP_TLV_IPv4_INT (EIGRP_TLV_IPv4 | EIGRP_TLV_INTERNAL) +#define EIGRP_TLV_IPv4_EXT (EIGRP_TLV_IPv4 | EIGRP_TLV_EXTERNAL) +#define EIGRP_TLV_IPv4_COM (EIGRP_TLV_IPv4 | EIGRP_TLV_COMMUNITY) + +#define EIGRP_TLV_IPV4_SIZE_GRT_24_BIT 0x001D +#define EIGRP_TLV_IPV4_SIZE_GRT_16_BIT 0x001C +#define EIGRP_TLV_IPV4_SIZE_GRT_8_BIT 0x001B +#define EIGRP_TLV_IPV4_SIZE_GRT_0_BIT 0x001A +#define EIGRP_TLV_MAX_IPV4_BYTE EIGRP_TLV_IPV4_SIZE_GRT_24_BIT + +/* max number of TLV IPv4 prefixes in packet */ +#define EIGRP_TLV_MAX_IPv4 25 + +/** + * + * extdata flag field definitions + */ +#define EIGRP_OPAQUE_EXT 0x01 /*!< Route is external */ +#define EIGRP_OPAQUE_CD 0x02 /*!< Candidate default route */ + +/** + * Address-Family types are taken from: + * http://www.iana.org/assignments/address-family-numbers + * to provide a standards based exchange of AFI information between + * EIGRP routers. + */ +#define EIGRP_AF_IPv4 1 /*!< IPv4 (IP version 4) */ +#define EIGRP_AF_IPv6 2 /*!< IPv6 (IP version 6) */ +#define EIGRP_AF_IPX 11 /*!< IPX */ +#define EIGRP_AF_ATALK 12 /*!< Appletalk */ +#define EIGRP_SF_COMMON 16384 /*!< Cisco Service Family */ +#define EIGRP_SF_IPv4 16385 /*!< Cisco IPv4 Service Family */ +#define EIGRP_SF_IPv6 16386 /*!< Cisco IPv6 Service Family */ + +/** + * Authentication types supported by EIGRP + */ +#define EIGRP_AUTH_TYPE_NONE 0 +#define EIGRP_AUTH_TYPE_TEXT 1 +#define EIGRP_AUTH_TYPE_MD5 2 +#define EIGRP_AUTH_TYPE_MD5_LEN 16 +#define EIGRP_AUTH_TYPE_SHA256 3 +#define EIGRP_AUTH_TYPE_SHA256_LEN 32 + +/** + * opaque flag field definitions + */ +#define EIGRP_OPAQUE_SRCWD 0x01 /*!< Route Source Withdraw */ +#define EIGRP_OPAQUE_ACTIVE 0x04 /*!< Route is currently in active state */ +#define EIGRP_OPAQUE_REPL 0x08 /*!< Route is replicated from different tableid */ + +/** + * pak flag bit field definitions - 0 (none)-7 source priority + */ +#define EIGRP_PRIV_DEFAULT 0x00 /* 0 (none)-7 source priority */ +#define EIGRP_PRIV_LOW 0x01 +#define EIGRP_PRIV_MEDIUM 0x04 +#define EIGRP_PRIV_HIGH 0x07 + +/* + * Init bit definition. First unicast transmitted Update has this + * bit set in the flags field of the fixed header. It tells the neighbor + * to down-load his topology table. + */ +#define EIGRP_INIT_FLAG 0x01 + +/* + * CR bit (Conditionally Received) definition in flags field on header. Any + * packets with the CR-bit set can be accepted by an EIGRP speaker if and + * only if a previous Hello was received with the SEQUENCE_TYPE TLV present. + * + * This allows multicasts to be transmitted in order and reliably at the + * same time as unicasts are transmitted. + */ +#define EIGRP_CR_FLAG 0x02 + +/* + * RS bit. The Restart flag is set in the hello and the init + * update packets during the nsf signaling period. A nsf-aware + * router looks at the RS flag to detect if a peer is restarting + * and maintain the adjacency. A restarting router looks at + * this flag to determine if the peer is helping out with the restart. + */ +#define EIGRP_RS_FLAG 0x04 + +/* + * EOT bit. The End-of-Table flag marks the end of the start-up updates + * sent to a new peer. A nsf restarting router looks at this flag to + * determine if it has finished receiving the start-up updates from all + * peers. A nsf-aware router waits for this flag before cleaning up + * the stale routes from the restarting peer. + */ +#define EIGRP_EOT_FLAG 0x08 + +/** + * EIGRP Virtual Router ID + * + * Define values to deal with EIGRP virtual router ids. Virtual + * router IDs are stored in the upper short of the EIGRP fixed packet + * header. The lower short of the packet header continues to be used + * as asystem number. + * + * Virtual Router IDs are PDM-independent. All PDMs will use + * VRID_BASE to indicate the 'base' or 'legacy' EIGRP instance. + * All PDMs need to initialize their vrid to VRID_BASE for compatibility + * with legacy routers. + * Once IPv6 supports 'MTR Multicast', it will use the same VRID as + * IPv4. No current plans to support VRIDs on IPX. :) + * Initial usage of VRID is to signal usage of Multicast topology for + * MTR. + * + * VRID_MCAST is a well known constant, other VRIDs will be determined + * programmatic... + * + * With the addition of SAF the VRID space has been divided into two + * segments 0x0000-0x7fff is for EIGRP and vNets, 0x8000-0xffff is + * for saf and its associated vNets. + */ +#define EIGRP_VRID_MASK 0x8001 +#define EIGRP_VRID_AF_BASE 0x0000 +#define EIGRP_VRID_MCAST_BASE 0x0001 +#define EIGRP_VRID_SF_BASE 0x8000 + +/* Extended Attributes for a destination */ +#define EIGRP_ATTR_HDRLEN (2) +#define EIGRP_ATTR_MAXDATA (512) + +#define EIGRP_ATTR_NOOP 0 /*!< No-Op used as offset padding */ +#define EIGRP_ATTR_SCALED 1 /*!< Scaled metric values */ +#define EIGRP_ATTR_TAG 2 /*!< Tag assigned by Admin for dest */ +#define EIGRP_ATTR_COMM 3 /*!< Community attribute for dest */ +#define EIGRP_ATTR_JITTER 4 /*!< Variation in path delay */ +#define EIGRP_ATTR_QENERGY 5 /*!< Non-Active energy usage along path */ +#define EIGRP_ATTR_ENERGY 6 /*!< Active energy usage along path */ + +/* + * Begin EIGRP-BGP interoperability communities + */ +#define EIGRP_EXTCOMM_SOO_ASFMT 0x0003 /* Site-of-Origin, BGP AS format */ +#define EIGRP_EXTCOMM_SOO_ADRFMT 0x0103 /* Site-of-Origin, BGP/EIGRP addr format */ + +/* + * EIGRP Specific communities + */ +#define EIGRP_EXTCOMM_EIGRP 0x8800 /* EIGRP route information appended*/ +#define EIGRP_EXTCOMM_DAD 0x8801 /* EIGRP AS + Delay */ +#define EIGRP_EXTCOMM_VRHB 0x8802 /* EIGRP Vector: Reliability + Hop + BW */ +#define EIGRP_EXTCOMM_SRLM 0x8803 /* EIGRP System: Reserve +Load + MTU */ +#define EIGRP_EXTCOMM_SAR 0x8804 /* EIGRP System: Remote AS + Remote ID */ +#define EIGRP_EXTCOMM_RPM 0x8805 /* EIGRP Remote: Protocol + Metric */ +#define EIGRP_EXTCOMM_VRR 0x8806 /* EIGRP Vecmet: Rsvd + (internal) Routerid */ + +/* + * EIGRP Filter constants + */ +#define EIGRP_FILTER_IN 0 +#define EIGRP_FILTER_OUT 1 +#define EIGRP_FILTER_MAX 2 + +/* + * EIGRP Filter constants + */ +#define EIGRP_HSROLE_DEFAULT EIGRP_HSROLE_SPOKE +#define EIGRP_HSROLE_HUB 0x01 +#define EIGRP_HSROLE_SPOKE 0x02 + +#endif /* _ZEBRA_EIGRP_CONST_H_ */ diff --git a/eigrpd/eigrp_dump.c b/eigrpd/eigrp_dump.c new file mode 100644 index 0000000..2ebabd0 --- /dev/null +++ b/eigrpd/eigrp_dump.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Dump Functions and Debugging. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "linklist.h" +#include "frrevent.h" +#include "prefix.h" +#include "command.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "table.h" +#include "keychain.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_topology.h" + +/* Enable debug option variables -- valid only session. */ +unsigned long term_debug_eigrp = 0; +unsigned long term_debug_eigrp_nei = 0; +unsigned long term_debug_eigrp_packet[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +unsigned long term_debug_eigrp_zebra = 6; +unsigned long term_debug_eigrp_transmit = 0; + +/* Configuration debug option variables. */ +unsigned long conf_debug_eigrp = 0; +unsigned long conf_debug_eigrp_nei = 0; +unsigned long conf_debug_eigrp_packet[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +unsigned long conf_debug_eigrp_zebra = 0; +unsigned long conf_debug_eigrp_transmit = 0; + + +static int config_write_debug(struct vty *vty) +{ + int write = 0; + int i; + + const char *type_str[] = {"update", "request", "query", "reply", + "hello", "", "probe", "ack", + "", "SIA query", "SIA reply", "stub", + "all"}; + const char *detail_str[] = { + "", " send", " recv", "", + " detail", " send detail", " recv detail", " detail"}; + + + /* debug eigrp event. */ + + /* debug eigrp packet */ + for (i = 0; i < 10; i++) { + if (conf_debug_eigrp_packet[i] == 0 + && term_debug_eigrp_packet[i] == 0) + continue; + + vty_out(vty, "debug eigrp packet %s%s\n", type_str[i], + detail_str[conf_debug_eigrp_packet[i]]); + write = 1; + } + + return write; +} + +static int eigrp_neighbor_packet_queue_sum(struct eigrp_interface *ei) +{ + struct eigrp_neighbor *nbr; + struct listnode *node, *nnode; + int sum; + sum = 0; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { + sum += nbr->retrans_queue->count; + } + + return sum; +} + +/* + * Expects header to be in host order + */ +void eigrp_ip_header_dump(struct ip *iph) +{ + /* IP Header dump. */ + zlog_debug("ip_v %u", iph->ip_v); + zlog_debug("ip_hl %u", iph->ip_hl); + zlog_debug("ip_tos %u", iph->ip_tos); + zlog_debug("ip_len %u", iph->ip_len); + zlog_debug("ip_id %u", (uint32_t)iph->ip_id); + zlog_debug("ip_off %u", (uint32_t)iph->ip_off); + zlog_debug("ip_ttl %u", iph->ip_ttl); + zlog_debug("ip_p %u", iph->ip_p); + zlog_debug("ip_sum 0x%x", (uint32_t)iph->ip_sum); + zlog_debug("ip_src %pI4", &iph->ip_src); + zlog_debug("ip_dst %pI4", &iph->ip_dst); +} + +/* + * Expects header to be in host order + */ +void eigrp_header_dump(struct eigrp_header *eigrph) +{ + /* EIGRP Header dump. */ + zlog_debug("eigrp_version %u", eigrph->version); + zlog_debug("eigrp_opcode %u", eigrph->opcode); + zlog_debug("eigrp_checksum 0x%x", ntohs(eigrph->checksum)); + zlog_debug("eigrp_flags 0x%x", ntohl(eigrph->flags)); + zlog_debug("eigrp_sequence %u", ntohl(eigrph->sequence)); + zlog_debug("eigrp_ack %u", ntohl(eigrph->ack)); + zlog_debug("eigrp_vrid %u", ntohs(eigrph->vrid)); + zlog_debug("eigrp_AS %u", ntohs(eigrph->ASNumber)); +} + +const char *eigrp_if_name_string(struct eigrp_interface *ei) +{ + if (!ei) + return "inactive"; + + return ei->ifp->name; +} + +void show_ip_eigrp_interface_header(struct vty *vty, struct eigrp *eigrp) +{ + + vty_out(vty, + "\nEIGRP interfaces for AS(%d)\n\n%-16s %-10s %-10s %-6s %-12s %-7s %-14s %-12s %-8s %-8s %-8s\n %-44s %-12s %-7s %-14s %-12s %-8s\n", + eigrp->AS, "Interface", "Bandwidth", "Delay", "Peers", + "Xmit Queue", "Mean", "Pacing Time", "Multicast", "Pending", + "Hello", "Holdtime", "", "Un/Reliable", "SRTT", "Un/Reliable", + "Flow Timer", "Routes"); +} + +void show_ip_eigrp_interface_sub(struct vty *vty, struct eigrp *eigrp, + struct eigrp_interface *ei) +{ + vty_out(vty, "%-16s ", IF_NAME(ei)); + vty_out(vty, "%-11u", ei->params.bandwidth); + vty_out(vty, "%-11u", ei->params.delay); + vty_out(vty, "%-7u", ei->nbrs->count); + vty_out(vty, "%u %c %-10u", 0, '/', + eigrp_neighbor_packet_queue_sum(ei)); + vty_out(vty, "%-7u %-14u %-12u %-8u", 0, 0, 0, 0); + vty_out(vty, "%-8u %-8u \n", ei->params.v_hello, ei->params.v_wait); +} + +void show_ip_eigrp_interface_detail(struct vty *vty, struct eigrp *eigrp, + struct eigrp_interface *ei) +{ + vty_out(vty, "%-2s %s %d %-3s \n", "", "Hello interval is ", 0, " sec"); + vty_out(vty, "%-2s %s %s \n", "", "Next xmit serial", ""); + vty_out(vty, "%-2s %s %d %s %d %s %d %s %d \n", "", + "Un/reliable mcasts: ", 0, "/", 0, "Un/reliable ucasts: ", 0, + "/", 0); + vty_out(vty, "%-2s %s %d %s %d %s %d \n", "", "Mcast exceptions: ", 0, + " CR packets: ", 0, " ACKs suppressed: ", 0); + vty_out(vty, "%-2s %s %d %s %d \n", "", "Retransmissions sent: ", 0, + "Out-of-sequence rcvd: ", 0); + vty_out(vty, "%-2s %s %s %s \n", "", "Authentication mode is ", "not", + "set"); + vty_out(vty, "%-2s %s \n", "", "Use multicast"); +} + +void show_ip_eigrp_neighbor_header(struct vty *vty, struct eigrp *eigrp) +{ + vty_out(vty, + "\nEIGRP neighbors for AS(%d)\n\n%-3s %-17s %-20s %-6s %-8s %-6s %-5s %-5s %-5s\n %-41s %-6s %-8s %-6s %-4s %-6s %-5s \n", + eigrp->AS, "H", "Address", "Interface", "Hold", "Uptime", + "SRTT", "RTO", "Q", "Seq", "", "(sec)", "", "(ms)", "", "Cnt", + "Num"); +} + +void show_ip_eigrp_neighbor_sub(struct vty *vty, struct eigrp_neighbor *nbr, + int detail) +{ + + vty_out(vty, "%-3u %-17pI4 %-21s", 0, &nbr->src, IF_NAME(nbr->ei)); + if (nbr->t_holddown) + vty_out(vty, "%-7lu", + event_timer_remain_second(nbr->t_holddown)); + else + vty_out(vty, "- "); + vty_out(vty, "%-8u %-6u %-5u", 0, 0, EIGRP_PACKET_RETRANS_TIME); + vty_out(vty, "%-7lu", nbr->retrans_queue->count); + vty_out(vty, "%u\n", nbr->recv_sequence_number); + + + if (detail) { + vty_out(vty, " Version %u.%u/%u.%u", nbr->os_rel_major, + nbr->os_rel_minor, nbr->tlv_rel_major, + nbr->tlv_rel_minor); + vty_out(vty, ", Retrans: %lu, Retries: %lu", + nbr->retrans_queue->count, 0UL); + vty_out(vty, ", %s\n", eigrp_nbr_state_str(nbr)); + } +} + +/* + * Print standard header for show EIGRP topology output + */ +void show_ip_eigrp_topology_header(struct vty *vty, struct eigrp *eigrp) +{ + vty_out(vty, "\nEIGRP Topology Table for AS(%d)/ID(%pI4)\n\n", + eigrp->AS, &eigrp->router_id); + vty_out(vty, + "Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply\n r - reply Status, s - sia Status\n\n"); +} + +void show_ip_eigrp_prefix_descriptor(struct vty *vty, + struct eigrp_prefix_descriptor *tn) +{ + struct list *successors = eigrp_topology_get_successor(tn); + + vty_out(vty, "%-3c", (tn->state > 0) ? 'A' : 'P'); + + vty_out(vty, "%pFX, ", tn->destination); + vty_out(vty, "%u successors, ", (successors) ? successors->count : 0); + vty_out(vty, "FD is %u, serno: %" PRIu64 " \n", tn->fdistance, + tn->serno); + + if (successors) + list_delete(&successors); +} + +void show_ip_eigrp_route_descriptor(struct vty *vty, struct eigrp *eigrp, + struct eigrp_route_descriptor *te, + bool *first) +{ + if (te->reported_distance == EIGRP_MAX_METRIC) + return; + + if (*first) { + show_ip_eigrp_prefix_descriptor(vty, te->prefix); + *first = false; + } + + if (te->adv_router == eigrp->neighbor_self) + vty_out(vty, "%-7s%s, %s\n", " ", "via Connected", + IF_NAME(te->ei)); + else { + vty_out(vty, "%-7s%s%pI4 (%u/%u), %s\n", " ", "via ", + &te->adv_router->src, te->distance, + te->reported_distance, IF_NAME(te->ei)); + } +} + + +DEFUN_NOSH (show_debugging_eigrp, + show_debugging_eigrp_cmd, + "show debugging [eigrp]", + SHOW_STR + DEBUG_STR + EIGRP_STR) +{ + int i; + + vty_out(vty, "EIGRP debugging status:\n"); + + /* Show debug status for events. */ + if (IS_DEBUG_EIGRP(event, EVENT)) + vty_out(vty, " EIGRP event debugging is on\n"); + + /* Show debug status for EIGRP Packets. */ + for (i = 0; i < 11; i++) { + if (i == 8) + continue; + + if (IS_DEBUG_EIGRP_PACKET(i, SEND) + && IS_DEBUG_EIGRP_PACKET(i, RECV)) { + vty_out(vty, " EIGRP packet %s%s debugging is on\n", + lookup_msg(eigrp_packet_type_str, i + 1, NULL), + IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) + ? " detail" + : ""); + } else { + if (IS_DEBUG_EIGRP_PACKET(i, SEND)) + vty_out(vty, + " EIGRP packet %s send%s debugging is on\n", + lookup_msg(eigrp_packet_type_str, i + 1, + NULL), + IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) + ? " detail" + : ""); + if (IS_DEBUG_EIGRP_PACKET(i, RECV)) + vty_out(vty, + " EIGRP packet %s receive%s debugging is on\n", + lookup_msg(eigrp_packet_type_str, i + 1, + NULL), + IS_DEBUG_EIGRP_PACKET(i, PACKET_DETAIL) + ? " detail" + : ""); + } + } + + cmd_show_lib_debugs(vty); + return CMD_SUCCESS; +} + + +/* + [no] debug eigrp packet (hello|dd|ls-request|ls-update|ls-ack|all) + [send|recv [detail]] +*/ + +DEFUN (debug_eigrp_transmit, + debug_eigrp_transmit_cmd, + "debug eigrp transmit [detail]", + DEBUG_STR + EIGRP_STR + "EIGRP transmission events\n" + "packet sent\n" + "packet received\n" + "all packets\n" + "Detailed Information\n") +{ + int flag = 0; + int idx = 2; + + /* send or recv. */ + if (argv_find(argv, argc, "send", &idx)) + flag = EIGRP_DEBUG_SEND; + else if (argv_find(argv, argc, "recv", &idx)) + flag = EIGRP_DEBUG_RECV; + else if (argv_find(argv, argc, "all", &idx)) + flag = EIGRP_DEBUG_SEND_RECV; + + /* detail option */ + if (argv_find(argv, argc, "detail", &idx)) + flag = EIGRP_DEBUG_PACKET_DETAIL; + + if (vty->node == CONFIG_NODE) + DEBUG_TRANSMIT_ON(0, flag); + else + TERM_DEBUG_TRANSMIT_ON(0, flag); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_eigrp_transmit, + no_debug_eigrp_transmit_cmd, + "no debug eigrp transmit [detail]", + NO_STR + UNDEBUG_STR + EIGRP_STR + "EIGRP transmission events\n" + "packet sent\n" + "packet received\n" + "all packets\n" + "Detailed Information\n") +{ + int flag = 0; + int idx = 3; + + /* send or recv. */ + if (argv_find(argv, argc, "send", &idx)) + flag = EIGRP_DEBUG_SEND; + else if (argv_find(argv, argc, "recv", &idx)) + flag = EIGRP_DEBUG_RECV; + else if (argv_find(argv, argc, "all", &idx)) + flag = EIGRP_DEBUG_SEND_RECV; + + /* detail option */ + if (argv_find(argv, argc, "detail", &idx)) + flag = EIGRP_DEBUG_PACKET_DETAIL; + + if (vty->node == CONFIG_NODE) + DEBUG_TRANSMIT_OFF(0, flag); + else + TERM_DEBUG_TRANSMIT_OFF(0, flag); + + return CMD_SUCCESS; +} + +DEFUN (debug_eigrp_packets, + debug_eigrp_packets_all_cmd, + "debug eigrp packets [send|receive] [detail]", + DEBUG_STR + EIGRP_STR + "EIGRP packets\n" + "EIGRP SIA-Query packets\n" + "EIGRP SIA-Reply packets\n" + "EIGRP ack packets\n" + "EIGRP hello packets\n" + "EIGRP probe packets\n" + "EIGRP query packets\n" + "EIGRP reply packets\n" + "EIGRP request packets\n" + "EIGRP retransmissions\n" + "EIGRP stub packets\n" + "Display all EIGRP packets except Hellos\n" + "EIGRP update packets\n" + "Display all EIGRP packets\n" + "Send Packets\n" + "Receive Packets\n" + "Detail Information\n") +{ + int type = 0; + int flag = 0; + int i; + int idx = 0; + + /* Check packet type. */ + if (argv_find(argv, argc, "hello", &idx)) + type = EIGRP_DEBUG_HELLO; + if (argv_find(argv, argc, "update", &idx)) + type = EIGRP_DEBUG_UPDATE; + if (argv_find(argv, argc, "query", &idx)) + type = EIGRP_DEBUG_QUERY; + if (argv_find(argv, argc, "ack", &idx)) + type = EIGRP_DEBUG_ACK; + if (argv_find(argv, argc, "probe", &idx)) + type = EIGRP_DEBUG_PROBE; + if (argv_find(argv, argc, "stub", &idx)) + type = EIGRP_DEBUG_STUB; + if (argv_find(argv, argc, "reply", &idx)) + type = EIGRP_DEBUG_REPLY; + if (argv_find(argv, argc, "request", &idx)) + type = EIGRP_DEBUG_REQUEST; + if (argv_find(argv, argc, "siaquery", &idx)) + type = EIGRP_DEBUG_SIAQUERY; + if (argv_find(argv, argc, "siareply", &idx)) + type = EIGRP_DEBUG_SIAREPLY; + if (argv_find(argv, argc, "all", &idx)) + type = EIGRP_DEBUG_PACKETS_ALL; + + + /* All packet types, both send and recv. */ + flag = EIGRP_DEBUG_SEND_RECV; + + /* send or recv. */ + if (argv_find(argv, argc, "s", &idx)) + flag = EIGRP_DEBUG_SEND; + else if (argv_find(argv, argc, "r", &idx)) + flag = EIGRP_DEBUG_RECV; + + /* detail. */ + if (argv_find(argv, argc, "detail", &idx)) + flag |= EIGRP_DEBUG_PACKET_DETAIL; + + for (i = 0; i < 11; i++) + if (type & (0x01 << i)) { + if (vty->node == CONFIG_NODE) + DEBUG_PACKET_ON(i, flag); + else + TERM_DEBUG_PACKET_ON(i, flag); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_eigrp_packets, + no_debug_eigrp_packets_all_cmd, + "no debug eigrp packets [send|receive] [detail]", + NO_STR + UNDEBUG_STR + EIGRP_STR + "EIGRP packets\n" + "EIGRP SIA-Query packets\n" + "EIGRP SIA-Reply packets\n" + "EIGRP ack packets\n" + "EIGRP hello packets\n" + "EIGRP probe packets\n" + "EIGRP query packets\n" + "EIGRP reply packets\n" + "EIGRP request packets\n" + "EIGRP retransmissions\n" + "EIGRP stub packets\n" + "Display all EIGRP packets except Hellos\n" + "EIGRP update packets\n" + "Display all EIGRP packets\n" + "Send Packets\n" + "Receive Packets\n" + "Detailed Information\n") +{ + int type = 0; + int flag = 0; + int i; + int idx = 0; + + /* Check packet type. */ + if (argv_find(argv, argc, "hello", &idx)) + type = EIGRP_DEBUG_HELLO; + if (argv_find(argv, argc, "update", &idx)) + type = EIGRP_DEBUG_UPDATE; + if (argv_find(argv, argc, "query", &idx)) + type = EIGRP_DEBUG_QUERY; + if (argv_find(argv, argc, "ack", &idx)) + type = EIGRP_DEBUG_ACK; + if (argv_find(argv, argc, "probe", &idx)) + type = EIGRP_DEBUG_PROBE; + if (argv_find(argv, argc, "stub", &idx)) + type = EIGRP_DEBUG_STUB; + if (argv_find(argv, argc, "reply", &idx)) + type = EIGRP_DEBUG_REPLY; + if (argv_find(argv, argc, "request", &idx)) + type = EIGRP_DEBUG_REQUEST; + if (argv_find(argv, argc, "siaquery", &idx)) + type = EIGRP_DEBUG_SIAQUERY; + if (argv_find(argv, argc, "siareply", &idx)) + type = EIGRP_DEBUG_SIAREPLY; + + /* Default, both send and recv. */ + flag = EIGRP_DEBUG_SEND_RECV; + + /* send or recv. */ + if (argv_find(argv, argc, "send", &idx)) + flag = EIGRP_DEBUG_SEND; + else if (argv_find(argv, argc, "reply", &idx)) + flag = EIGRP_DEBUG_RECV; + + /* detail. */ + if (argv_find(argv, argc, "detail", &idx)) + flag |= EIGRP_DEBUG_PACKET_DETAIL; + + for (i = 0; i < 11; i++) + if (type & (0x01 << i)) { + if (vty->node == CONFIG_NODE) + DEBUG_PACKET_OFF(i, flag); + else + TERM_DEBUG_PACKET_OFF(i, flag); + } + + return CMD_SUCCESS; +} + +/* Debug node. */ +static int config_write_debug(struct vty *vty); +static struct cmd_node eigrp_debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_debug, +}; + +/* Initialize debug commands. */ +void eigrp_debug_init(void) +{ + install_node(&eigrp_debug_node); + + install_element(ENABLE_NODE, &show_debugging_eigrp_cmd); + install_element(ENABLE_NODE, &debug_eigrp_packets_all_cmd); + install_element(ENABLE_NODE, &no_debug_eigrp_packets_all_cmd); + install_element(ENABLE_NODE, &debug_eigrp_transmit_cmd); + install_element(ENABLE_NODE, &no_debug_eigrp_transmit_cmd); + + install_element(CONFIG_NODE, &show_debugging_eigrp_cmd); + install_element(CONFIG_NODE, &debug_eigrp_packets_all_cmd); + install_element(CONFIG_NODE, &no_debug_eigrp_packets_all_cmd); + install_element(CONFIG_NODE, &debug_eigrp_transmit_cmd); + install_element(CONFIG_NODE, &no_debug_eigrp_transmit_cmd); +} diff --git a/eigrpd/eigrp_dump.h b/eigrpd/eigrp_dump.h new file mode 100644 index 0000000..7a952f0 --- /dev/null +++ b/eigrpd/eigrp_dump.h @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Dump Functions and Debbuging. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRPD_DUMP_H_ +#define _ZEBRA_EIGRPD_DUMP_H_ + +#define EIGRP_TIME_DUMP_SIZE 16 + +/* general debug flags */ +extern unsigned long term_debug_eigrp; +#define EIGRP_DEBUG_EVENT 0x01 +#define EIGRP_DEBUG_DETAIL 0x02 +#define EIGRP_DEBUG_TIMERS 0x04 + +/* neighbor debug flags */ +extern unsigned long term_debug_eigrp_nei; +#define EIGRP_DEBUG_NEI 0x01 + +/* packet debug flags */ +extern unsigned long term_debug_eigrp_packet[]; +#define EIGRP_DEBUG_UPDATE 0x01 +#define EIGRP_DEBUG_REQUEST 0x02 +#define EIGRP_DEBUG_QUERY 0x04 +#define EIGRP_DEBUG_REPLY 0x08 +#define EIGRP_DEBUG_HELLO 0x10 +#define EIGRP_DEBUG_PROBE 0x40 +#define EIGRP_DEBUG_ACK 0x80 +#define EIGRP_DEBUG_SIAQUERY 0x200 +#define EIGRP_DEBUG_SIAREPLY 0x400 +#define EIGRP_DEBUG_STUB 0x800 +#define EIGRP_DEBUG_PACKETS_ALL 0xfff + +extern unsigned long term_debug_eigrp_transmit; +#define EIGRP_DEBUG_SEND 0x01 +#define EIGRP_DEBUG_RECV 0x02 +#define EIGRP_DEBUG_SEND_RECV 0x03 +#define EIGRP_DEBUG_PACKET_DETAIL 0x04 + +/* zebra debug flags */ +extern unsigned long term_debug_eigrp_zebra; +#define EIGRP_DEBUG_ZEBRA_INTERFACE 0x01 +#define EIGRP_DEBUG_ZEBRA_REDISTRIBUTE 0x02 +#define EIGRP_DEBUG_ZEBRA 0x03 + +/* Macro for setting debug option. */ +#define CONF_DEBUG_NEI_ON(a, b) conf_debug_eigrp_nei[a] |= (b) +#define CONF_DEBUG_NEI_OFF(a, b) conf_debug_eigrp_nei[a] &= ~(b) +#define TERM_DEBUG_NEI_ON(a, b) term_debug_eigrp_nei[a] |= (b) +#define TERM_DEBUG_NEI_OFF(a, b) term_debug_eigrp_nei[a] &= ~(b) +#define DEBUG_NEI_ON(a, b) \ + do { \ + CONF_DEBUG_NEI_ON(a, b); \ + TERM_DEBUG_NEI_ON(a, b); \ + } while (0) +#define DEBUG_NEI_OFF(a, b) \ + do { \ + CONF_DEBUG_NEI_OFF(a, b); \ + TERM_DEBUG_NEI_OFF(a, b); \ + } while (0) + +#define CONF_DEBUG_PACKET_ON(a, b) conf_debug_eigrp_packet[a] |= (b) +#define CONF_DEBUG_PACKET_OFF(a, b) conf_debug_eigrp_packet[a] &= ~(b) +#define TERM_DEBUG_PACKET_ON(a, b) term_debug_eigrp_packet[a] |= (b) +#define TERM_DEBUG_PACKET_OFF(a, b) term_debug_eigrp_packet[a] &= ~(b) +#define DEBUG_PACKET_ON(a, b) \ + do { \ + CONF_DEBUG_PACKET_ON(a, b); \ + TERM_DEBUG_PACKET_ON(a, b); \ + } while (0) +#define DEBUG_PACKET_OFF(a, b) \ + do { \ + CONF_DEBUG_PACKET_OFF(a, b); \ + TERM_DEBUG_PACKET_OFF(a, b); \ + } while (0) + +#define CONF_DEBUG_TRANSMIT_ON(a, b) conf_debug_eigrp_transmit |= (b) +#define CONF_DEBUG_TRANSMIT_OFF(a, b) conf_debug_eigrp_transmit &= ~(b) +#define TERM_DEBUG_TRANSMIT_ON(a, b) term_debug_eigrp_transmit |= (b) +#define TERM_DEBUG_TRANSMIT_OFF(a, b) term_debug_eigrp_transmit &= ~(b) +#define DEBUG_TRANSMIT_ON(a, b) \ + do { \ + CONF_DEBUG_TRANSMIT_ON(a, b); \ + TERM_DEBUG_TRANSMIT_ON(a, b); \ + } while (0) +#define DEBUG_TRANSMIT_OFF(a, b) \ + do { \ + CONF_DEBUG_TRANSMIT_OFF(a, b); \ + TERM_DEBUG_TRANSMIT_OFF(a, b); \ + } while (0) + +#define CONF_DEBUG_ON(a, b) conf_debug_eigrp_ ## a |= (EIGRP_DEBUG_ ## b) +#define CONF_DEBUG_OFF(a, b) conf_debug_eigrp_ ## a &= ~(EIGRP_DEBUG_ ## b) +#define TERM_DEBUG_ON(a, b) term_debug_eigrp_ ## a |= (EIGRP_DEBUG_ ## b) +#define TERM_DEBUG_OFF(a, b) term_debug_eigrp_ ## a &= ~(EIGRP_DEBUG_ ## b) +#define DEBUG_ON(a, b) \ + do { \ + CONF_DEBUG_ON(a, b); \ + TERM_DEBUG_ON(a, b); \ + } while (0) +#define DEBUG_OFF(a, b) \ + do { \ + CONF_DEBUG_OFF(a, b); \ + TERM_DEBUG_OFF(a, b); \ + } while (0) + +/* Macro for checking debug option. */ +#define IS_DEBUG_EIGRP_PACKET(a, b) \ + (term_debug_eigrp_packet[a] & EIGRP_DEBUG_##b) +#define IS_DEBUG_EIGRP_TRANSMIT(a, b) \ + (term_debug_eigrp_transmit & EIGRP_DEBUG_##b) +#define IS_DEBUG_EIGRP_NEI(a, b) (term_debug_eigrp_nei & EIGRP_DEBUG_##b) +#define IS_DEBUG_EIGRP(a, b) (term_debug_eigrp & EIGRP_DEBUG_##b) +#define IS_DEBUG_EIGRP_EVENT IS_DEBUG_EIGRP(event, EVENT) + +/* Prototypes. */ +extern const char *eigrp_if_name_string(struct eigrp_interface *); + +extern void eigrp_ip_header_dump(struct ip *); +extern void eigrp_header_dump(struct eigrp_header *); + +extern void show_ip_eigrp_interface_header(struct vty *, struct eigrp *); +extern void show_ip_eigrp_neighbor_header(struct vty *, struct eigrp *); +extern void show_ip_eigrp_topology_header(struct vty *, struct eigrp *); +extern void show_ip_eigrp_interface_detail(struct vty *, struct eigrp *, + struct eigrp_interface *); +extern void show_ip_eigrp_interface_sub(struct vty *, struct eigrp *, + struct eigrp_interface *); +extern void show_ip_eigrp_neighbor_sub(struct vty *, struct eigrp_neighbor *, + int); +extern void show_ip_eigrp_prefix_descriptor(struct vty *vty, + struct eigrp_prefix_descriptor *tn); +extern void show_ip_eigrp_route_descriptor(struct vty *vty, struct eigrp *eigrp, + struct eigrp_route_descriptor *ne, + bool *first); + +extern void eigrp_debug_init(void); + +#endif /* _ZEBRA_EIGRPD_DUMP_H_ */ diff --git a/eigrpd/eigrp_errors.c b/eigrpd/eigrp_errors.c new file mode 100644 index 0000000..9991fd5 --- /dev/null +++ b/eigrpd/eigrp_errors.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "eigrp_errors.h" + +/* clang-format off */ +static struct log_ref ferr_eigrp_err[] = { + { + .code = EC_EIGRP_PACKET, + .title = "EIGRP Packet Error", + .description = "EIGRP has a packet that does not correctly decode or encode", + .suggestion = "Gather log files from both sides of the neighbor relationship and open an issue" + }, + { + .code = EC_EIGRP_CONFIG, + .title = "EIGRP Configuration Error", + .description = "EIGRP has detected a configuration error", + .suggestion = "Correct the configuration issue, if it still persists open an Issue" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void eigrp_error_init(void) +{ + log_ref_add(ferr_eigrp_err); +} diff --git a/eigrpd/eigrp_errors.h b/eigrpd/eigrp_errors.h new file mode 100644 index 0000000..885702b --- /dev/null +++ b/eigrpd/eigrp_errors.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __EIGRP_ERRORS_H__ +#define __EIGRP_ERRORS_H__ + +#include "lib/ferr.h" + +enum eigrp_log_refs { + EC_EIGRP_PACKET = EIGRP_FERR_START, + EC_EIGRP_CONFIG, +}; + +extern void eigrp_error_init(void); + +#endif diff --git a/eigrpd/eigrp_filter.c b/eigrpd/eigrp_filter.c new file mode 100644 index 0000000..eceef6b --- /dev/null +++ b/eigrpd/eigrp_filter.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Filter Functions. + * Copyright (C) 2013-2015 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + * + */ + +#include + +#include "if.h" +#include "command.h" +#include "prefix.h" +#include "table.h" +#include "frrevent.h" +#include "memory.h" +#include "log.h" +#include "stream.h" +#include "filter.h" +#include "sockunion.h" +#include "sockopt.h" +#include "routemap.h" +#include "if_rmap.h" +#include "plist.h" +#include "distribute.h" +#include "md5.h" +#include "keychain.h" +#include "privs.h" +#include "vrf.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_const.h" +#include "eigrpd/eigrp_filter.h" +#include "eigrpd/eigrp_packet.h" + +/* + * Distribute-list update functions. + */ +void eigrp_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist) +{ + struct eigrp *e = eigrp_lookup(ctx->vrf->vrf_id); + struct interface *ifp; + struct eigrp_interface *ei = NULL; + struct access_list *alist; + struct prefix_list *plist; + // struct route_map *routemap; + + /* if no interface address is present, set list to eigrp process struct + */ + + /* Check if distribute-list was set for process or interface */ + if (!dist->ifname) { + /* access list IN for whole process */ + if (dist->list[DISTRIBUTE_V4_IN]) { + alist = access_list_lookup( + AFI_IP, dist->list[DISTRIBUTE_V4_IN]); + if (alist) + e->list[EIGRP_FILTER_IN] = alist; + else + e->list[EIGRP_FILTER_IN] = NULL; + } else { + e->list[EIGRP_FILTER_IN] = NULL; + } + + /* access list OUT for whole process */ + if (dist->list[DISTRIBUTE_V4_OUT]) { + alist = access_list_lookup( + AFI_IP, dist->list[DISTRIBUTE_V4_OUT]); + if (alist) + e->list[EIGRP_FILTER_OUT] = alist; + else + e->list[EIGRP_FILTER_OUT] = NULL; + } else { + e->list[EIGRP_FILTER_OUT] = NULL; + } + + /* PREFIX_LIST IN for process */ + if (dist->prefix[DISTRIBUTE_V4_IN]) { + plist = prefix_list_lookup( + AFI_IP, dist->prefix[DISTRIBUTE_V4_IN]); + if (plist) { + e->prefix[EIGRP_FILTER_IN] = plist; + } else + e->prefix[EIGRP_FILTER_IN] = NULL; + } else + e->prefix[EIGRP_FILTER_IN] = NULL; + + /* PREFIX_LIST OUT for process */ + if (dist->prefix[DISTRIBUTE_V4_OUT]) { + plist = prefix_list_lookup( + AFI_IP, dist->prefix[DISTRIBUTE_V4_OUT]); + if (plist) { + e->prefix[EIGRP_FILTER_OUT] = plist; + + } else + e->prefix[EIGRP_FILTER_OUT] = NULL; + } else + e->prefix[EIGRP_FILTER_OUT] = NULL; + + // TODO: check Graceful restart after 10sec + + /* cancel GR scheduled */ + event_cancel(&(e->t_distribute)); + + /* schedule Graceful restart for whole process in 10sec */ + event_add_timer(master, eigrp_distribute_timer_process, e, (10), + &e->t_distribute); + + return; + } + + ifp = if_lookup_by_name(dist->ifname, e->vrf_id); + if (ifp == NULL) + return; + + /*struct eigrp_if_info * info = ifp->info; + ei = info->eigrp_interface;*/ + struct listnode *node, *nnode; + struct eigrp_interface *ei2; + /* Find proper interface */ + for (ALL_LIST_ELEMENTS(e->eiflist, node, nnode, ei2)) { + if (strcmp(ei2->ifp->name, ifp->name) == 0) { + ei = ei2; + break; + } + } + assert(ei != NULL); + + /* Access-list for interface in */ + if (dist->list[DISTRIBUTE_V4_IN]) { + alist = access_list_lookup(AFI_IP, + dist->list[DISTRIBUTE_V4_IN]); + if (alist) { + ei->list[EIGRP_FILTER_IN] = alist; + } else + ei->list[EIGRP_FILTER_IN] = NULL; + } else { + ei->list[EIGRP_FILTER_IN] = NULL; + } + + /* Access-list for interface in */ + if (dist->list[DISTRIBUTE_V4_OUT]) { + alist = access_list_lookup(AFI_IP, + dist->list[DISTRIBUTE_V4_OUT]); + if (alist) + ei->list[EIGRP_FILTER_OUT] = alist; + else + ei->list[EIGRP_FILTER_OUT] = NULL; + + } else + ei->list[EIGRP_FILTER_OUT] = NULL; + + /* Prefix-list for interface in */ + if (dist->prefix[DISTRIBUTE_V4_IN]) { + plist = prefix_list_lookup(AFI_IP, + dist->prefix[DISTRIBUTE_V4_IN]); + if (plist) + ei->prefix[EIGRP_FILTER_IN] = plist; + else + ei->prefix[EIGRP_FILTER_IN] = NULL; + } else + ei->prefix[EIGRP_FILTER_IN] = NULL; + + /* Prefix-list for interface out */ + if (dist->prefix[DISTRIBUTE_V4_OUT]) { + plist = prefix_list_lookup(AFI_IP, + dist->prefix[DISTRIBUTE_V4_OUT]); + if (plist) + ei->prefix[EIGRP_FILTER_OUT] = plist; + else + ei->prefix[EIGRP_FILTER_OUT] = NULL; + } else + ei->prefix[EIGRP_FILTER_OUT] = NULL; + + // TODO: check Graceful restart after 10sec + + /* Cancel GR scheduled */ + event_cancel(&(ei->t_distribute)); + /* schedule Graceful restart for interface in 10sec */ + event_add_timer(master, eigrp_distribute_timer_interface, ei, 10, + &ei->t_distribute); +} + +/* + * Function called by prefix-list and access-list update + */ +void eigrp_distribute_update_interface(struct interface *ifp) +{ + struct distribute *dist; + struct eigrp *eigrp; + + eigrp = eigrp_lookup(ifp->vrf->vrf_id); + if (!eigrp) + return; + dist = distribute_lookup(eigrp->distribute_ctx, ifp->name); + if (dist) + eigrp_distribute_update(eigrp->distribute_ctx, + dist); +} + +/* Update all interface's distribute list. + * Function used in hook for prefix-list + */ +void eigrp_distribute_update_all(struct prefix_list *notused) +{ + struct vrf *vrf; + struct interface *ifp; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) + eigrp_distribute_update_interface(ifp); + } +} + +/* + * Function used in hook for acces-list + */ +void eigrp_distribute_update_all_wrapper(struct access_list *notused) +{ + eigrp_distribute_update_all(NULL); +} + +/* + * @fn eigrp_distribute_timer_process + * + * @param[in] thread current execution thread timer is associated with + * + * @return void + * + * @par + * Called when 10sec waiting time expire and + * executes Graceful restart for whole process + */ +void eigrp_distribute_timer_process(struct event *thread) +{ + struct eigrp *eigrp; + + eigrp = EVENT_ARG(thread); + + /* execute GR for whole process */ + eigrp_update_send_process_GR(eigrp, EIGRP_GR_FILTER, NULL); +} + +/* + * @fn eigrp_distribute_timer_interface + * + * @param[in] thread current execution thread timer is associated with + * + * @return void + * + * @par + * Called when 10sec waiting time expire and + * executes Graceful restart for interface + */ +void eigrp_distribute_timer_interface(struct event *thread) +{ + struct eigrp_interface *ei; + + ei = EVENT_ARG(thread); + ei->t_distribute = NULL; + + /* execute GR for interface */ + eigrp_update_send_interface_GR(ei, EIGRP_GR_FILTER, NULL); +} diff --git a/eigrpd/eigrp_filter.h b/eigrpd/eigrp_filter.h new file mode 100644 index 0000000..dc8af8f --- /dev/null +++ b/eigrpd/eigrp_filter.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Filter Functions. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + * + */ + +#ifndef EIGRPD_EIGRP_FILTER_H_ +#define EIGRPD_EIGRP_FILTER_H_ + +extern void eigrp_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist); +extern void eigrp_distribute_update_interface(struct interface *ifp); +extern void eigrp_distribute_update_all(struct prefix_list *plist); +extern void eigrp_distribute_update_all_wrapper(struct access_list *alist); +extern void eigrp_distribute_timer_process(struct event *thread); +extern void eigrp_distribute_timer_interface(struct event *thread); + +#endif /* EIGRPD_EIGRP_FILTER_H_ */ diff --git a/eigrpd/eigrp_fsm.c b/eigrpd/eigrp_fsm.c new file mode 100644 index 0000000..6d8061e --- /dev/null +++ b/eigrpd/eigrp_fsm.c @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRPd Finite State Machine (DUAL). + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * + * This file contains functions for executing logic of finite state machine + * + * +------------ + + * | (7) | + * | v + * +=====================================+ + * | | + * | Passive | + * | | + * +=====================================+ + * ^ | ^ ^ ^ | + * (3)| | (1)| | (1)| | + * | (0)| | (3)| | (2)| + * | | | | | +---------------+ + * | | | | | \ + * +--------+ | | | +-----------------+ \ + * / / / | \ \ + * / / / +----+ \ \ + * | | | | | | + * | v | | | v + * +===========+ (6) +===========+ +===========+ (6) +===========+ + * | |------->| | (5) | |-------->| | + * | | (4) | |------>| | (4) | | + * | ACTIVE 0 |<-------| ACTIVE 1 | | ACTIVE 2 |<--------| ACTIVE 3 + * | + * +--| | +--| | +--| | +--| | + * | +===========+ | +===========+ | +===========+ | + * +===========+ + * | ^ |(5) | ^ | ^ ^ | ^ + * | | +---------|------|------------|----+ | | | + * +-------+ +------+ +---------+ +---------+ + * (7) (7) (7) (7) + * + * 0- input event other than query from successor, FC not satisfied + * 1- last reply, FD is reset + * 2- query from successor, FC not satisfied + * 3- last reply, FC satisfied with current value of FDij + * 4- distance increase while in active state + * 5- query from successor while in active state + * 6- last reply, FC not satisfied with current value of FDij + * 7- state not changed, usually by receiving not last reply + */ + +#include + +#include "frrevent.h" +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "log.h" +#include "linklist.h" +#include "vty.h" + +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_metric.h" + +/* + * Prototypes + */ +int eigrp_fsm_event_keep_state(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_nq_fcn(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_q_fcn(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_lr(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_dinc(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_lr_fcs(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_lr_fcn(struct eigrp_fsm_action_message *); +int eigrp_fsm_event_qact(struct eigrp_fsm_action_message *); + +//--------------------------------------------------------------------- + +/* + * NSM - field of fields of struct containing one function each. + * Which function is used depends on actual state of FSM and occurred + * event(arrow in diagram). Usage: + * NSM[actual/starting state][occurred event].func + * Functions are should be executed within separate thread. + */ +const struct { + int (*func)(struct eigrp_fsm_action_message *); +} NSM[EIGRP_FSM_STATE_MAX][EIGRP_FSM_EVENT_MAX] = { + { + // PASSIVE STATE + {eigrp_fsm_event_nq_fcn}, /* Event 0 */ + {eigrp_fsm_event_keep_state}, /* Event 1 */ + {eigrp_fsm_event_q_fcn}, /* Event 2 */ + {eigrp_fsm_event_keep_state}, /* Event 3 */ + {eigrp_fsm_event_keep_state}, /* Event 4 */ + {eigrp_fsm_event_keep_state}, /* Event 5 */ + {eigrp_fsm_event_keep_state}, /* Event 6 */ + {eigrp_fsm_event_keep_state}, /* Event 7 */ + }, + { + // Active 0 state + {eigrp_fsm_event_keep_state}, /* Event 0 */ + {eigrp_fsm_event_keep_state}, /* Event 1 */ + {eigrp_fsm_event_keep_state}, /* Event 2 */ + {eigrp_fsm_event_lr_fcs}, /* Event 3 */ + {eigrp_fsm_event_keep_state}, /* Event 4 */ + {eigrp_fsm_event_qact}, /* Event 5 */ + {eigrp_fsm_event_lr_fcn}, /* Event 6 */ + {eigrp_fsm_event_keep_state}, /* Event 7 */ + }, + { + // Active 1 state + {eigrp_fsm_event_keep_state}, /* Event 0 */ + {eigrp_fsm_event_lr}, /* Event 1 */ + {eigrp_fsm_event_keep_state}, /* Event 2 */ + {eigrp_fsm_event_keep_state}, /* Event 3 */ + {eigrp_fsm_event_dinc}, /* Event 4 */ + {eigrp_fsm_event_qact}, /* Event 5 */ + {eigrp_fsm_event_keep_state}, /* Event 6 */ + {eigrp_fsm_event_keep_state}, /* Event 7 */ + }, + { + // Active 2 state + {eigrp_fsm_event_keep_state}, /* Event 0 */ + {eigrp_fsm_event_keep_state}, /* Event 1 */ + {eigrp_fsm_event_keep_state}, /* Event 2 */ + {eigrp_fsm_event_lr_fcs}, /* Event 3 */ + {eigrp_fsm_event_keep_state}, /* Event 4 */ + {eigrp_fsm_event_keep_state}, /* Event 5 */ + {eigrp_fsm_event_lr_fcn}, /* Event 6 */ + {eigrp_fsm_event_keep_state}, /* Event 7 */ + }, + { + // Active 3 state + {eigrp_fsm_event_keep_state}, /* Event 0 */ + {eigrp_fsm_event_lr}, /* Event 1 */ + {eigrp_fsm_event_keep_state}, /* Event 2 */ + {eigrp_fsm_event_keep_state}, /* Event 3 */ + {eigrp_fsm_event_dinc}, /* Event 4 */ + {eigrp_fsm_event_keep_state}, /* Event 5 */ + {eigrp_fsm_event_keep_state}, /* Event 6 */ + {eigrp_fsm_event_keep_state}, /* Event 7 */ + }, +}; + +static const char *packet_type2str(uint8_t packet_type) +{ + if (packet_type == EIGRP_OPC_UPDATE) + return "Update"; + if (packet_type == EIGRP_OPC_REQUEST) + return "Request"; + if (packet_type == EIGRP_OPC_QUERY) + return "Query"; + if (packet_type == EIGRP_OPC_REPLY) + return "Reply"; + if (packet_type == EIGRP_OPC_HELLO) + return "Hello"; + if (packet_type == EIGRP_OPC_IPXSAP) + return "IPXSAP"; + if (packet_type == EIGRP_OPC_ACK) + return "Ack"; + if (packet_type == EIGRP_OPC_SIAQUERY) + return "SIA Query"; + if (packet_type == EIGRP_OPC_SIAREPLY) + return "SIA Reply"; + + return "Unknown"; +} + +static const char *prefix_state2str(enum eigrp_fsm_states state) +{ + switch (state) { + case EIGRP_FSM_STATE_PASSIVE: + return "Passive"; + case EIGRP_FSM_STATE_ACTIVE_0: + return "Active oij0"; + case EIGRP_FSM_STATE_ACTIVE_1: + return "Active oij1"; + case EIGRP_FSM_STATE_ACTIVE_2: + return "Active oij2"; + case EIGRP_FSM_STATE_ACTIVE_3: + return "Active oij3"; + } + + return "Unknown"; +} + +static const char *fsm_state2str(enum eigrp_fsm_events event) +{ + switch (event) { + case EIGRP_FSM_KEEP_STATE: + return "Keep State Event"; + case EIGRP_FSM_EVENT_NQ_FCN: + return "Non Query Event Feasability not satisfied"; + case EIGRP_FSM_EVENT_LR: + return "Last Reply Event"; + case EIGRP_FSM_EVENT_Q_FCN: + return "Query Event Feasability not satisfied"; + case EIGRP_FSM_EVENT_LR_FCS: + return "Last Reply Event Feasability satisfied"; + case EIGRP_FSM_EVENT_DINC: + return "Distance Increase Event"; + case EIGRP_FSM_EVENT_QACT: + return "Query from Successor while in active state"; + case EIGRP_FSM_EVENT_LR_FCN: + return "Last Reply Event, Feasibility not satisfied"; + } + + return "Unknown"; +} + +static const char *change2str(enum metric_change change) +{ + switch (change) { + case METRIC_DECREASE: + return "Decrease"; + case METRIC_SAME: + return "Same"; + case METRIC_INCREASE: + return "Increase"; + } + + return "Unknown"; +} +/* + * Main function in which are make decisions which event occurred. + * msg - argument of type struct eigrp_fsm_action_message contain + * details about what happen + * + * Return number of occurred event (arrow in diagram). + * + */ +static enum eigrp_fsm_events +eigrp_get_fsm_event(struct eigrp_fsm_action_message *msg) +{ + // Loading base information from message + // struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *entry = msg->entry; + uint8_t actual_state = prefix->state; + enum metric_change change; + + if (entry == NULL) { + entry = eigrp_route_descriptor_new(); + entry->adv_router = msg->adv_router; + entry->ei = msg->adv_router->ei; + entry->prefix = prefix; + msg->entry = entry; + } + + /* + * Calculate resultant metrics and insert to correct position + * in entries list + */ + change = eigrp_topology_update_distance(msg); + + /* Store for display later */ + msg->change = change; + + switch (actual_state) { + case EIGRP_FSM_STATE_PASSIVE: { + struct eigrp_route_descriptor *head = + listnode_head(prefix->entries); + + if (head->reported_distance < prefix->fdistance) { + return EIGRP_FSM_KEEP_STATE; + } + /* + * if best entry doesn't satisfy feasibility condition it means + * move to active state + * dependently if it was query from successor + */ + if (msg->packet_type == EIGRP_OPC_QUERY) { + return EIGRP_FSM_EVENT_Q_FCN; + } else { + return EIGRP_FSM_EVENT_NQ_FCN; + } + + break; + } + case EIGRP_FSM_STATE_ACTIVE_0: { + if (msg->packet_type == EIGRP_OPC_REPLY) { + struct eigrp_route_descriptor *head = + listnode_head(prefix->entries); + + listnode_delete(prefix->rij, entry->adv_router); + if (prefix->rij->count) + return EIGRP_FSM_KEEP_STATE; + + zlog_info("All reply received"); + if (head->reported_distance < prefix->fdistance) { + return EIGRP_FSM_EVENT_LR_FCS; + } + + return EIGRP_FSM_EVENT_LR_FCN; + } else if (msg->packet_type == EIGRP_OPC_QUERY + && (entry->flags + & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_QACT; + } + + return EIGRP_FSM_KEEP_STATE; + + break; + } + case EIGRP_FSM_STATE_ACTIVE_1: { + if (msg->packet_type == EIGRP_OPC_QUERY + && (entry->flags & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_QACT; + } else if (msg->packet_type == EIGRP_OPC_REPLY) { + listnode_delete(prefix->rij, entry->adv_router); + + if (change == METRIC_INCREASE + && (entry->flags + & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_DINC; + } else if (prefix->rij->count) { + return EIGRP_FSM_KEEP_STATE; + } else { + zlog_info("All reply received"); + return EIGRP_FSM_EVENT_LR; + } + } else if (msg->packet_type == EIGRP_OPC_UPDATE + && change == METRIC_INCREASE + && (entry->flags + & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_DINC; + } + return EIGRP_FSM_KEEP_STATE; + + break; + } + case EIGRP_FSM_STATE_ACTIVE_2: { + if (msg->packet_type == EIGRP_OPC_REPLY) { + struct eigrp_route_descriptor *head = + listnode_head(prefix->entries); + + listnode_delete(prefix->rij, entry->adv_router); + if (prefix->rij->count) { + return EIGRP_FSM_KEEP_STATE; + } else { + zlog_info("All reply received"); + if (head->reported_distance + < prefix->fdistance) { + return EIGRP_FSM_EVENT_LR_FCS; + } + + return EIGRP_FSM_EVENT_LR_FCN; + } + } + return EIGRP_FSM_KEEP_STATE; + + break; + } + case EIGRP_FSM_STATE_ACTIVE_3: { + if (msg->packet_type == EIGRP_OPC_REPLY) { + listnode_delete(prefix->rij, entry->adv_router); + + if (change == METRIC_INCREASE + && (entry->flags + & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_DINC; + } else if (prefix->rij->count) { + return EIGRP_FSM_KEEP_STATE; + } else { + zlog_info("All reply received"); + return EIGRP_FSM_EVENT_LR; + } + } else if (msg->packet_type == EIGRP_OPC_UPDATE + && change == METRIC_INCREASE + && (entry->flags + & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG)) { + return EIGRP_FSM_EVENT_DINC; + } + return EIGRP_FSM_KEEP_STATE; + + break; + } + } + + return EIGRP_FSM_KEEP_STATE; +} + +/* + * Function made to execute in separate thread. + * Load argument from thread and execute proper NSM function + */ +int eigrp_fsm_event(struct eigrp_fsm_action_message *msg) +{ + enum eigrp_fsm_events event = eigrp_get_fsm_event(msg); + + zlog_info( + "EIGRP AS: %d State: %s Event: %s Network: %pI4 Packet Type: %s Reply RIJ Count: %d change: %s", + msg->eigrp->AS, prefix_state2str(msg->prefix->state), + fsm_state2str(event), &msg->prefix->destination->u.prefix4, + packet_type2str(msg->packet_type), msg->prefix->rij->count, + change2str(msg->change)); + (*(NSM[msg->prefix->state][event].func))(msg); + + return 1; +} + +/* + * Function of event 0. + * + */ +int eigrp_fsm_event_nq_fcn(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct list *successors = eigrp_topology_get_successor(prefix); + struct eigrp_route_descriptor *ne; + + assert(successors); // If this is NULL we have shit the bed, fun huh? + + ne = listnode_head(successors); + prefix->state = EIGRP_FSM_STATE_ACTIVE_1; + prefix->rdistance = prefix->distance = prefix->fdistance = ne->distance; + prefix->reported_metric = ne->total_metric; + + if (eigrp_nbr_count_get(eigrp)) { + prefix->req_action |= EIGRP_FSM_NEED_QUERY; + listnode_add(eigrp->topology_changes_internalIPV4, prefix); + } else { + eigrp_fsm_event_lr(msg); // in the case that there are no more + // neighbors left + } + + list_delete(&successors); + + return 1; +} + +int eigrp_fsm_event_q_fcn(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct list *successors = eigrp_topology_get_successor(prefix); + struct eigrp_route_descriptor *ne; + + assert(successors); // If this is NULL somebody poked us in the eye. + + ne = listnode_head(successors); + prefix->state = EIGRP_FSM_STATE_ACTIVE_3; + prefix->rdistance = prefix->distance = prefix->fdistance = ne->distance; + prefix->reported_metric = ne->total_metric; + if (eigrp_nbr_count_get(eigrp)) { + prefix->req_action |= EIGRP_FSM_NEED_QUERY; + listnode_add(eigrp->topology_changes_internalIPV4, prefix); + } else { + eigrp_fsm_event_lr(msg); // in the case that there are no more + // neighbors left + } + + list_delete(&successors); + + return 1; +} + +int eigrp_fsm_event_keep_state(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *ne = listnode_head(prefix->entries); + + if (prefix->state == EIGRP_FSM_STATE_PASSIVE) { + if (!eigrp_metrics_is_same(prefix->reported_metric, + ne->total_metric)) { + prefix->rdistance = prefix->fdistance = + prefix->distance = ne->distance; + prefix->reported_metric = ne->total_metric; + if (msg->packet_type == EIGRP_OPC_QUERY) + eigrp_send_reply(msg->adv_router, prefix); + prefix->req_action |= EIGRP_FSM_NEED_UPDATE; + listnode_add(eigrp->topology_changes_internalIPV4, + prefix); + } + eigrp_topology_update_node_flags(eigrp, prefix); + eigrp_update_routing_table(eigrp, prefix); + } + + if (msg->packet_type == EIGRP_OPC_QUERY) + eigrp_send_reply(msg->adv_router, prefix); + + return 1; +} + +int eigrp_fsm_event_lr(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *ne = listnode_head(prefix->entries); + + prefix->fdistance = prefix->distance = prefix->rdistance = ne->distance; + prefix->reported_metric = ne->total_metric; + + if (prefix->state == EIGRP_FSM_STATE_ACTIVE_3) { + struct list *successors = eigrp_topology_get_successor(prefix); + + assert(successors); // It's like Napolean and Waterloo + + ne = listnode_head(successors); + eigrp_send_reply(ne->adv_router, prefix); + list_delete(&successors); + } + + prefix->state = EIGRP_FSM_STATE_PASSIVE; + prefix->req_action |= EIGRP_FSM_NEED_UPDATE; + listnode_add(eigrp->topology_changes_internalIPV4, prefix); + eigrp_topology_update_node_flags(eigrp, prefix); + eigrp_update_routing_table(eigrp, prefix); + eigrp_update_topology_table_prefix(eigrp, eigrp->topology_table, + prefix); + + return 1; +} + +int eigrp_fsm_event_dinc(struct eigrp_fsm_action_message *msg) +{ + struct list *successors = eigrp_topology_get_successor(msg->prefix); + struct eigrp_route_descriptor *ne; + + assert(successors); // Trump and his big hands + + ne = listnode_head(successors); + msg->prefix->state = msg->prefix->state == EIGRP_FSM_STATE_ACTIVE_1 + ? EIGRP_FSM_STATE_ACTIVE_0 + : EIGRP_FSM_STATE_ACTIVE_2; + msg->prefix->distance = ne->distance; + if (!msg->prefix->rij->count) + (*(NSM[msg->prefix->state][eigrp_get_fsm_event(msg)].func))( + msg); + + + list_delete(&successors); + return 1; +} + +int eigrp_fsm_event_lr_fcs(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *ne = listnode_head(prefix->entries); + + prefix->state = EIGRP_FSM_STATE_PASSIVE; + prefix->distance = prefix->rdistance = ne->distance; + prefix->reported_metric = ne->total_metric; + prefix->fdistance = prefix->fdistance > prefix->distance + ? prefix->distance + : prefix->fdistance; + if (prefix->state == EIGRP_FSM_STATE_ACTIVE_2) { + struct list *successors = eigrp_topology_get_successor(prefix); + + assert(successors); // Having a spoon and all you need is a + // knife + ne = listnode_head(successors); + eigrp_send_reply(ne->adv_router, prefix); + + list_delete(&successors); + } + prefix->req_action |= EIGRP_FSM_NEED_UPDATE; + listnode_add(eigrp->topology_changes_internalIPV4, prefix); + eigrp_topology_update_node_flags(eigrp, prefix); + eigrp_update_routing_table(eigrp, prefix); + eigrp_update_topology_table_prefix(eigrp, eigrp->topology_table, + prefix); + + return 1; +} + +int eigrp_fsm_event_lr_fcn(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *best_successor; + struct list *successors = eigrp_topology_get_successor(prefix); + + assert(successors); // Routing without a stack + + prefix->state = prefix->state == EIGRP_FSM_STATE_ACTIVE_0 + ? EIGRP_FSM_STATE_ACTIVE_1 + : EIGRP_FSM_STATE_ACTIVE_3; + + best_successor = listnode_head(successors); + prefix->rdistance = prefix->distance = best_successor->distance; + prefix->reported_metric = best_successor->total_metric; + + if (eigrp_nbr_count_get(eigrp)) { + prefix->req_action |= EIGRP_FSM_NEED_QUERY; + listnode_add(eigrp->topology_changes_internalIPV4, prefix); + } else { + eigrp_fsm_event_lr(msg); // in the case that there are no more + // neighbors left + } + + list_delete(&successors); + + return 1; +} + +int eigrp_fsm_event_qact(struct eigrp_fsm_action_message *msg) +{ + struct list *successors = eigrp_topology_get_successor(msg->prefix); + struct eigrp_route_descriptor *ne; + + assert(successors); // Cats and no Dogs + + ne = listnode_head(successors); + msg->prefix->state = EIGRP_FSM_STATE_ACTIVE_2; + msg->prefix->distance = ne->distance; + + list_delete(&successors); + return 1; +} diff --git a/eigrpd/eigrp_fsm.h b/eigrpd/eigrp_fsm.h new file mode 100644 index 0000000..e9c5223 --- /dev/null +++ b/eigrpd/eigrp_fsm.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Finite State Machine (DUAL). + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRP_FSM_H +#define _ZEBRA_EIGRP_FSM_H + +extern int eigrp_fsm_event(struct eigrp_fsm_action_message *msg); + + +#endif /* _ZEBRA_EIGRP_DUAL_H */ diff --git a/eigrpd/eigrp_hello.c b/eigrpd/eigrp_hello.c new file mode 100644 index 0000000..ee0e245 --- /dev/null +++ b/eigrpd/eigrp_hello.c @@ -0,0 +1,783 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Sending and Receiving EIGRP Hello Packets. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "vty.h" +#include "md5.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_errors.h" + +/* Packet Type String. */ +static const struct message eigrp_general_tlv_type_str[] = { + {EIGRP_TLV_PARAMETER, "PARAMETER"}, + {EIGRP_TLV_AUTH, "AUTH"}, + {EIGRP_TLV_SEQ, "SEQ"}, + {EIGRP_TLV_SW_VERSION, "SW_VERSION"}, + {EIGRP_TLV_NEXT_MCAST_SEQ, "NEXT_MCAST_SEQ"}, + {EIGRP_TLV_PEER_TERMINATION, "PEER_TERMINATION"}, + {EIGRP_TLV_PEER_MTRLIST, "PEER_MTRLIST"}, + {EIGRP_TLV_PEER_TIDLIST, "PEER_TIDLIST"}, + {0}}; + + +/* + * @fn eigrp_hello_timer + * + * @param[in] thread current execution thread timer is associated with + * + * @return void + * + * @par + * Called once per "hello" time interval, default 5 seconds + * Sends hello packet via multicast for all interfaces eigrp + * is configured for + */ +void eigrp_hello_timer(struct event *thread) +{ + struct eigrp_interface *ei; + + ei = EVENT_ARG(thread); + + if (IS_DEBUG_EIGRP(0, TIMERS)) + zlog_debug("Start Hello Timer (%s) Expire [%u]", IF_NAME(ei), + ei->params.v_hello); + + /* Sending hello packet. */ + eigrp_hello_send(ei, EIGRP_HELLO_NORMAL, NULL); + + /* Hello timer set. */ + event_add_timer(master, eigrp_hello_timer, ei, ei->params.v_hello, + &ei->t_hello); +} + +/** + * @fn eigrp_hello_parameter_decode + * + * @param[in] nbr neighbor the ACK should be sent to + * @param[in] param pointer packet TLV is stored to + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Encode Parameter TLV, used to convey metric weights and the hold time. + * + * @usage + * Note the addition of K6 for the new extended metrics, and does not apply to + * older TLV packet formats. + */ +static struct eigrp_neighbor * +eigrp_hello_parameter_decode(struct eigrp_neighbor *nbr, + struct eigrp_tlv_hdr_type *tlv) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct TLV_Parameter_Type *param = (struct TLV_Parameter_Type *)tlv; + + /* First validate TLV length */ + if (tlv->length < sizeof(struct TLV_Parameter_Type)) + return NULL; + + /* copy over the values passed in by the neighbor */ + nbr->K1 = param->K1; + nbr->K2 = param->K2; + nbr->K3 = param->K3; + nbr->K4 = param->K4; + nbr->K5 = param->K5; + nbr->K6 = param->K6; + nbr->v_holddown = ntohs(param->hold_time); + + /* + * Check K1-K5 have the correct values to be able to become neighbors + * K6 does not have to match + */ + if ((eigrp->k_values[0] == nbr->K1) && (eigrp->k_values[1] == nbr->K2) + && (eigrp->k_values[2] == nbr->K3) + && (eigrp->k_values[3] == nbr->K4) + && (eigrp->k_values[4] == nbr->K5)) { + + if (eigrp_nbr_state_get(nbr) == EIGRP_NEIGHBOR_DOWN) { + zlog_info( + "Neighbor %pI4 (%s) is pending: new adjacency", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + + /* Expedited hello sent */ + eigrp_hello_send(nbr->ei, EIGRP_HELLO_NORMAL, NULL); + + // if(ntohl(nbr->ei->address->u.prefix4.s_addr) > + // ntohl(nbr->src.s_addr)) + eigrp_update_send_init(nbr); + + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_PENDING); + } + } else { + if (eigrp_nbr_state_get(nbr) != EIGRP_NEIGHBOR_DOWN) { + if ((param->K1 & param->K2 & param->K3 & param->K4 + & param->K5) + == 255) { + zlog_info( + "Neighbor %pI4 (%s) is down: Interface PEER-TERMINATION received", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + eigrp_nbr_delete(nbr); + return NULL; + } else { + zlog_info( + "Neighbor %pI4 (%s) going down: Kvalue mismatch", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); + } + } + } + + return nbr; +} + +static uint8_t +eigrp_hello_authentication_decode(struct stream *s, + struct eigrp_tlv_hdr_type *tlv_header, + struct eigrp_neighbor *nbr) +{ + struct TLV_MD5_Authentication_Type *md5; + + md5 = (struct TLV_MD5_Authentication_Type *)tlv_header; + + if (md5->auth_type == EIGRP_AUTH_TYPE_MD5) { + /* Validate tlv length */ + if (md5->length < sizeof(struct TLV_MD5_Authentication_Type)) + return 0; + + return eigrp_check_md5_digest(s, md5, nbr, + EIGRP_AUTH_BASIC_HELLO_FLAG); + } else if (md5->auth_type == EIGRP_AUTH_TYPE_SHA256) { + /* Validate tlv length */ + if (md5->length < sizeof(struct TLV_SHA256_Authentication_Type)) + return 0; + + return eigrp_check_sha256_digest( + s, (struct TLV_SHA256_Authentication_Type *)tlv_header, + nbr, EIGRP_AUTH_BASIC_HELLO_FLAG); + } + + return 0; +} + +/** + * @fn eigrp_sw_version_decode + * + * @param[in] nbr neighbor the ACK shoudl be sent to + * @param[in] param pointer to TLV software version information + * + * @return void + * + * @par + * Read the software version in the specified location. + * This consists of two bytes of OS version, and two bytes of EIGRP + * revision number. + */ +static void eigrp_sw_version_decode(struct eigrp_neighbor *nbr, + struct eigrp_tlv_hdr_type *tlv) +{ + struct TLV_Software_Type *version = (struct TLV_Software_Type *)tlv; + + /* Validate TLV length */ + if (tlv->length < sizeof(struct TLV_Software_Type)) + return; + + nbr->os_rel_major = version->vender_major; + nbr->os_rel_minor = version->vender_minor; + nbr->tlv_rel_major = version->eigrp_major; + nbr->tlv_rel_minor = version->eigrp_minor; + return; +} + +/** + * @fn eigrp_peer_termination_decode + * + * @param[in] nbr neighbor the ACK shoudl be sent to + * @param[in] tlv pointer to TLV software version information + * + * @return void + * + * @par + * Read the address in the TLV and match to out address. If + * a match is found, move the sending neighbor to the down state. If + * out address is not in the TLV, then ignore the peer termination + */ +static void eigrp_peer_termination_decode(struct eigrp_neighbor *nbr, + struct eigrp_tlv_hdr_type *tlv) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + struct TLV_Peer_Termination_type *param = + (struct TLV_Peer_Termination_type *)tlv; + + /* Validate TLV length */ + if (tlv->length < sizeof(struct TLV_Peer_Termination_type)) + return; + + uint32_t my_ip = nbr->ei->address.u.prefix4.s_addr; + uint32_t received_ip = param->neighbor_ip; + + if (my_ip == received_ip) { + zlog_info( + "Neighbor %pI4 (%s) is down: Peer Termination received", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + /* set neighbor to DOWN */ + nbr->state = EIGRP_NEIGHBOR_DOWN; + /* delete neighbor */ + eigrp_nbr_delete(nbr); + } +} + +/** + * @fn eigrp_peer_termination_encode + * + * @param[in,out] s packet stream TLV is stored to + * @param[in] nbr_addr pointer to neighbor address for Peer + * Termination TLV + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Function used to encode Peer Termination TLV to Hello packet. + */ +static uint16_t eigrp_peer_termination_encode(struct stream *s, + struct in_addr *nbr_addr) +{ + uint16_t length = EIGRP_TLV_PEER_TERMINATION_LEN; + + /* fill in type and length */ + stream_putw(s, EIGRP_TLV_PEER_TERMINATION); + stream_putw(s, length); + + /* fill in unknown field 0x04 */ + stream_putc(s, 0x04); + + /* finally neighbor IP address */ + stream_put_ipv4(s, nbr_addr->s_addr); + + return (length); +} + +/* + * @fn eigrp_hello_receive + * + * @param[in] eigrp eigrp routing process + * @param[in] iph pointer to ip header + * @param[in] eigrph pointer to eigrp header + * @param[in] s input ip stream + * @param[in] ei eigrp interface packet arrived on + * @param[in] size size of eigrp packet + * + * @return void + * + * @par + * This is the main worker function for processing hello packets. It + * will validate the peer associated with the src ip address of the ip + * header, and then decode each of the general TLVs which the packet + * may contain. + * + * @usage + * Not all TLVs are current decoder. This is a work in progress.. + */ +void eigrp_hello_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_tlv_hdr_type *tlv_header; + struct eigrp_neighbor *nbr; + uint16_t type; + uint16_t length; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + if (IS_DEBUG_EIGRP_PACKET(eigrph->opcode - 1, RECV)) + zlog_debug("Processing Hello size[%u] int(%s) nbr(%pI4)", size, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id), + &nbr->src); + + size -= EIGRP_HEADER_LEN; + if (size < 0) + return; + + tlv_header = (struct eigrp_tlv_hdr_type *)eigrph->tlv; + + do { + type = ntohs(tlv_header->type); + length = ntohs(tlv_header->length); + + /* Validate length against packet size */ + if (length > size) + return; + + if ((length > 0) && (length <= size)) { + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug( + " General TLV(%s)", + lookup_msg(eigrp_general_tlv_type_str, + type, NULL)); + + // determine what General TLV is being processed + switch (type) { + case EIGRP_TLV_PARAMETER: + nbr = eigrp_hello_parameter_decode(nbr, + tlv_header); + if (!nbr) + return; + break; + case EIGRP_TLV_AUTH: { + if (eigrp_hello_authentication_decode( + s, tlv_header, nbr) + == 0) + return; + else + break; + break; + } + case EIGRP_TLV_SEQ: + break; + case EIGRP_TLV_SW_VERSION: + eigrp_sw_version_decode(nbr, tlv_header); + break; + case EIGRP_TLV_NEXT_MCAST_SEQ: + break; + case EIGRP_TLV_PEER_TERMINATION: + eigrp_peer_termination_decode(nbr, tlv_header); + return; + break; + case EIGRP_TLV_PEER_MTRLIST: + case EIGRP_TLV_PEER_TIDLIST: + break; + default: + break; + } + } + + tlv_header = (struct eigrp_tlv_hdr_type *)(((char *)tlv_header) + + length); + size -= length; + + } while (size > 0); + + + /*If received packet is hello with Parameter TLV*/ + if (ntohl(eigrph->ack) == 0) { + /* increment statistics. */ + ei->hello_in++; + if (nbr) + eigrp_nbr_state_update(nbr); + } + + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug("Hello Packet received from %pI4", &nbr->src); +} + +uint32_t FRR_MAJOR; +uint32_t FRR_MINOR; + +void eigrp_sw_version_initialize(void) +{ + char ver_string[] = VERSION; + char *dash = strstr(ver_string, "-"); + int ret; + + if (dash) + dash[0] = '\0'; + + ret = sscanf(ver_string, "%" SCNu32 ".%" SCNu32, &FRR_MAJOR, + &FRR_MINOR); + if (ret != 2) + flog_err(EC_EIGRP_PACKET, + "Did not Properly parse %s, please fix VERSION string", + VERSION); +} + +/** + * @fn eigrp_sw_version_encode + * + * @param[in,out] s packet stream TLV is stored to + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Store the software version in the specified location. + * This consists of two bytes of OS version, and two bytes of EIGRP + * revision number. + */ +static uint16_t eigrp_sw_version_encode(struct stream *s) +{ + uint16_t length = EIGRP_TLV_SW_VERSION_LEN; + + // setup the tlv fields + stream_putw(s, EIGRP_TLV_SW_VERSION); + stream_putw(s, length); + + stream_putc(s, FRR_MAJOR); //!< major os version + stream_putc(s, FRR_MINOR); //!< minor os version + + /* and the core eigrp version */ + stream_putc(s, EIGRP_MAJOR_VERSION); + stream_putc(s, EIGRP_MINOR_VERSION); + + return (length); +} + +/** + * @fn eigrp_tidlist_encode + * + * @param[in,out] s packet stream TLV is stored to + * + * @return void + * + * @par + * If doing mutli-topology, then store the supported TID list. + * This is currently a place holder function + */ +static uint16_t eigrp_tidlist_encode(struct stream *s) +{ + // uint16_t length = EIGRP_TLV_SW_VERSION_LEN; + return 0; +} + +/** + * @fn eigrp_sequence_encode + * + * @param[in,out] s packet stream TLV is stored to + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Part of conditional receive process + * + */ +static uint16_t eigrp_sequence_encode(struct eigrp *eigrp, struct stream *s) +{ + uint16_t length = EIGRP_TLV_SEQ_BASE_LEN; + struct eigrp_interface *ei; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + size_t backup_end, size_end; + int found; + + // add in the parameters TLV + backup_end = stream_get_endp(s); + stream_putw(s, EIGRP_TLV_SEQ); + size_end = s->endp; + stream_putw(s, 0x0000); + stream_putc(s, IPV4_MAX_BYTELEN); + + found = 0; + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (nbr->multicast_queue->count > 0) { + length += (uint16_t)stream_put_ipv4( + s, nbr->src.s_addr); + found = 1; + } + } + } + + if (found == 0) { + stream_set_endp(s, backup_end); + return 0; + } + + backup_end = stream_get_endp(s); + stream_set_endp(s, size_end); + stream_putw(s, length); + stream_set_endp(s, backup_end); + + return length; +} + +/** + * @fn eigrp_sequence_encode + * + * @param[in,out] s packet stream TLV is stored to + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Part of conditional receive process + * + */ +static uint16_t eigrp_next_sequence_encode(struct eigrp *eigrp, + struct stream *s) +{ + uint16_t length = EIGRP_NEXT_SEQUENCE_TLV_SIZE; + + // add in the parameters TLV + stream_putw(s, EIGRP_TLV_NEXT_MCAST_SEQ); + stream_putw(s, EIGRP_NEXT_SEQUENCE_TLV_SIZE); + stream_putl(s, eigrp->sequence_number + 1); + + return length; +} + +/** + * @fn eigrp_hello_parameter_encode + * + * @param[in] ei pointer to interface hello packet came in on + * @param[in,out] s packet stream TLV is stored to + * + * @return uint16_t number of bytes added to packet stream + * + * @par + * Encode Parameter TLV, used to convey metric weights and the hold time. + * + * @usage + * Note the addition of K6 for the new extended metrics, and does not apply to + * older TLV packet formats. + */ +static uint16_t eigrp_hello_parameter_encode(struct eigrp_interface *ei, + struct stream *s, uint8_t flags) +{ + // add in the parameters TLV + stream_putw(s, EIGRP_TLV_PARAMETER); + stream_putw(s, EIGRP_TLV_PARAMETER_LEN); + + // if graceful shutdown is needed to be announced, send all 255 in K + // values + if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) { + stream_putc(s, 0xff); /* K1 */ + stream_putc(s, 0xff); /* K2 */ + stream_putc(s, 0xff); /* K3 */ + stream_putc(s, 0xff); /* K4 */ + stream_putc(s, 0xff); /* K5 */ + stream_putc(s, 0xff); /* K6 */ + } else // set k values + { + stream_putc(s, ei->eigrp->k_values[0]); /* K1 */ + stream_putc(s, ei->eigrp->k_values[1]); /* K2 */ + stream_putc(s, ei->eigrp->k_values[2]); /* K3 */ + stream_putc(s, ei->eigrp->k_values[3]); /* K4 */ + stream_putc(s, ei->eigrp->k_values[4]); /* K5 */ + stream_putc(s, ei->eigrp->k_values[5]); /* K6 */ + } + + // and set hold time value.. + stream_putw(s, ei->params.v_wait); + + return EIGRP_TLV_PARAMETER_LEN; +} + +/** + * @fn eigrp_hello_encode + * + * @param[in] ei pointer to interface hello packet came in on + * @param[in] s packet stream TLV is stored to + * @param[in] ack if non-zero, neigbors sequence packet to ack + * @param[in] flags type of hello packet + * @param[in] nbr_addr pointer to neighbor address for Peer + * Termination TLV + * + * @return eigrp_packet pointer initialize hello packet + * + * @par + * Allocate an EIGRP hello packet, and add in the the approperate TLVs + * + */ +static struct eigrp_packet *eigrp_hello_encode(struct eigrp_interface *ei, + in_addr_t addr, uint32_t ack, + uint8_t flags, + struct in_addr *nbr_addr) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + + // allocate a new packet to be sent + ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), NULL); + + if (ep) { + // encode common header feilds + eigrp_packet_header_init(EIGRP_OPC_HELLO, ei->eigrp, ep->s, 0, + 0, ack); + + // encode Authentication TLV + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); + } else if ((ei->params.auth_type == EIGRP_AUTH_TYPE_SHA256) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_SHA256_to_stream(ep->s, ei); + } + + /* encode appropriate parameters to Hello packet */ + if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) + length += eigrp_hello_parameter_encode( + ei, ep->s, EIGRP_HELLO_GRACEFUL_SHUTDOWN); + else + length += eigrp_hello_parameter_encode( + ei, ep->s, EIGRP_HELLO_NORMAL); + + // figure out the version of code we're running + length += eigrp_sw_version_encode(ep->s); + + if (flags & EIGRP_HELLO_ADD_SEQUENCE) { + length += eigrp_sequence_encode(ei->eigrp, ep->s); + length += eigrp_next_sequence_encode(ei->eigrp, ep->s); + } + + // add in the TID list if doing multi-topology + length += eigrp_tidlist_encode(ep->s); + + /* encode Peer Termination TLV if needed */ + if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR) + length += + eigrp_peer_termination_encode(ep->s, nbr_addr); + + // Set packet length + ep->length = length; + + // set soruce address for the hello packet + ep->dst.s_addr = addr; + + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(ei, ep->s, + EIGRP_AUTH_BASIC_HELLO_FLAG); + } else if ((ei->params.auth_type == EIGRP_AUTH_TYPE_SHA256) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_sha256_digest(ei, ep->s, + EIGRP_AUTH_BASIC_HELLO_FLAG); + } + + // EIGRP Checksum + eigrp_packet_checksum(ei, ep->s, length); + } + + return (ep); +} + +/** + * @fn eigrp_hello_send + * + * @param[in] nbr neighbor the ACK should be sent to + * + * @return void + * + * @par + * Send (unicast) a hello packet with the destination address + * associated with the neighbor. The eigrp header ACK feild will be + * updated to the neighbor's sequence number to acknolodge any + * outstanding packets + */ +void eigrp_hello_send_ack(struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *ep; + + /* if packet succesfully created, add it to the interface queue */ + ep = eigrp_hello_encode(nbr->ei, nbr->src.s_addr, + nbr->recv_sequence_number, EIGRP_HELLO_NORMAL, + NULL); + + if (ep) { + if (IS_DEBUG_EIGRP_PACKET(0, SEND)) + zlog_debug("Queueing [Hello] Ack Seq [%u] nbr [%pI4]", + nbr->recv_sequence_number, &nbr->src); + + /* Add packet to the top of the interface output queue*/ + eigrp_fifo_push(nbr->ei->obuf, ep); + + /* Hook thread to write packet. */ + if (nbr->ei->on_write_q == 0) { + listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); + nbr->ei->on_write_q = 1; + } + event_add_write(master, eigrp_write, nbr->ei->eigrp, + nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); + } +} + +/** + * @fn eigrp_hello_send + * + * @param[in] ei pointer to interface hello should be sent + * @param[in] flags type of hello packet + * @param[in] nbr_addr pointer to neighbor address for Peer + * Termination TLV + * + * @return void + * + * @par + * Build and enqueue a generic (multicast) periodic hello packet for + * sending. If no packets are currently queues, the packet will be + * sent immadiatly + */ +void eigrp_hello_send(struct eigrp_interface *ei, uint8_t flags, + struct in_addr *nbr_addr) +{ + struct eigrp_packet *ep = NULL; + + if (IS_DEBUG_EIGRP_PACKET(0, SEND)) + zlog_debug("Queueing [Hello] Interface(%s)", IF_NAME(ei)); + + /* if packet was succesfully created, then add it to the interface queue + */ + ep = eigrp_hello_encode(ei, htonl(EIGRP_MULTICAST_ADDRESS), 0, flags, + nbr_addr); + + if (ep) { + // Add packet to the top of the interface output queue + eigrp_fifo_push(ei->obuf, ep); + + /* Hook thread to write packet. */ + if (ei->on_write_q == 0) { + listnode_add(ei->eigrp->oi_write_q, ei); + ei->on_write_q = 1; + } + + if (ei->eigrp->t_write == NULL) { + if (flags & EIGRP_HELLO_GRACEFUL_SHUTDOWN) { + event_execute(master, eigrp_write, ei->eigrp, + ei->eigrp->fd, NULL); + } else { + event_add_write(master, eigrp_write, ei->eigrp, + ei->eigrp->fd, + &ei->eigrp->t_write); + } + } + } +} diff --git a/eigrpd/eigrp_interface.c b/eigrpd/eigrp_interface.c new file mode 100644 index 0000000..fb8f47e --- /dev/null +++ b/eigrpd/eigrp_interface.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Interface Functions. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "network.h" +#include "command.h" +#include "stream.h" +#include "log.h" +#include "keychain.h" +#include "vrf.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_metric.h" + +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_IF, "EIGRP interface"); + +struct eigrp_interface *eigrp_if_new(struct eigrp *eigrp, struct interface *ifp, + struct prefix *p) +{ + struct eigrp_interface *ei = ifp->info; + int i; + + if (ei) + return ei; + + ei = XCALLOC(MTYPE_EIGRP_IF, sizeof(struct eigrp_interface)); + + /* Set zebra interface pointer. */ + ei->ifp = ifp; + prefix_copy(&ei->address, p); + + ifp->info = ei; + listnode_add(eigrp->eiflist, ei); + + ei->type = EIGRP_IFTYPE_BROADCAST; + + /* Initialize neighbor list. */ + ei->nbrs = list_new(); + + ei->crypt_seqnum = frr_sequence32_next(); + + /* Initialize lists */ + for (i = 0; i < EIGRP_FILTER_MAX; i++) { + ei->list[i] = NULL; + ei->prefix[i] = NULL; + ei->routemap[i] = NULL; + } + + ei->eigrp = eigrp; + + ei->params.v_hello = EIGRP_HELLO_INTERVAL_DEFAULT; + ei->params.v_wait = EIGRP_HOLD_INTERVAL_DEFAULT; + ei->params.bandwidth = EIGRP_BANDWIDTH_DEFAULT; + ei->params.delay = EIGRP_DELAY_DEFAULT; + ei->params.reliability = EIGRP_RELIABILITY_DEFAULT; + ei->params.load = EIGRP_LOAD_DEFAULT; + ei->params.auth_type = EIGRP_AUTH_TYPE_NONE; + ei->params.auth_keychain = NULL; + + ei->curr_bandwidth = ifp->bandwidth; + ei->curr_mtu = ifp->mtu; + + return ei; +} + +int eigrp_if_delete_hook(struct interface *ifp) +{ + struct eigrp_interface *ei = ifp->info; + struct eigrp *eigrp; + + if (!ei) + return 0; + + list_delete(&ei->nbrs); + + eigrp = ei->eigrp; + listnode_delete(eigrp->eiflist, ei); + + eigrp_fifo_free(ei->obuf); + + XFREE(MTYPE_EIGRP_IF, ifp->info); + + return 0; +} + +static int eigrp_ifp_create(struct interface *ifp) +{ + struct eigrp_interface *ei = ifp->info; + + if (!ei) + return 0; + + ei->params.type = eigrp_default_iftype(ifp); + + eigrp_if_update(ifp); + + return 0; +} + +static int eigrp_ifp_up(struct interface *ifp) +{ + struct eigrp_interface *ei = ifp->info; + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: Interface[%s] state change to up.", + ifp->name); + + if (!ei) + return 0; + + if (ei->curr_bandwidth != ifp->bandwidth) { + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: Interface[%s] bandwidth change %d -> %d.", + ifp->name, ei->curr_bandwidth, + ifp->bandwidth); + + ei->curr_bandwidth = ifp->bandwidth; + // eigrp_if_recalculate_output_cost (ifp); + } + + if (ei->curr_mtu != ifp->mtu) { + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: Interface[%s] MTU change %u -> %u.", + ifp->name, ei->curr_mtu, ifp->mtu); + + ei->curr_mtu = ifp->mtu; + /* Must reset the interface (simulate down/up) when MTU + * changes. */ + eigrp_if_reset(ifp); + return 0; + } + + eigrp_if_up(ifp->info); + + return 0; +} + +static int eigrp_ifp_down(struct interface *ifp) +{ + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: Interface[%s] state change to down.", + ifp->name); + + if (ifp->info) + eigrp_if_down(ifp->info); + + return 0; +} + +static int eigrp_ifp_destroy(struct interface *ifp) +{ + if (if_is_up(ifp)) + zlog_warn("Zebra: got delete of %s, but interface is still up", + ifp->name); + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: interface delete %s index %d flags %llx metric %d mtu %d", + ifp->name, ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + if (ifp->info) + eigrp_if_free(ifp->info, INTERFACE_DOWN_BY_ZEBRA); + + return 0; +} + +struct list *eigrp_iflist; + +void eigrp_if_init(void) +{ + hook_register_prio(if_real, 0, eigrp_ifp_create); + hook_register_prio(if_up, 0, eigrp_ifp_up); + hook_register_prio(if_down, 0, eigrp_ifp_down); + hook_register_prio(if_unreal, 0, eigrp_ifp_destroy); + /* Initialize Zebra interface data structure. */ + // hook_register_prio(if_add, 0, eigrp_if_new); + hook_register_prio(if_del, 0, eigrp_if_delete_hook); +} + + +void eigrp_del_if_params(struct eigrp_if_params *eip) +{ + if (eip->auth_keychain) + free(eip->auth_keychain); +} + +/* + * Set the network byte order of the 3 bytes we send + * of the mtu of the link. + */ +static void eigrp_mtu_convert(struct eigrp_metrics *metric, uint32_t host_mtu) +{ + uint32_t network_mtu = htonl(host_mtu); + uint8_t *nm = (uint8_t *)&network_mtu; + + metric->mtu[0] = nm[1]; + metric->mtu[1] = nm[2]; + metric->mtu[2] = nm[3]; +} + +int eigrp_if_up(struct eigrp_interface *ei) +{ + struct eigrp_prefix_descriptor *pe; + struct eigrp_route_descriptor *ne; + struct eigrp_metrics metric; + struct eigrp_interface *ei2; + struct listnode *node, *nnode; + struct eigrp *eigrp; + + if (ei == NULL) + return 0; + + eigrp = ei->eigrp; + eigrp_adjust_sndbuflen(eigrp, ei->ifp->mtu); + + eigrp_if_stream_set(ei); + + /* Set multicast memberships appropriately for new state. */ + eigrp_if_set_multicast(ei); + + event_add_event(master, eigrp_hello_timer, ei, (1), &ei->t_hello); + + /*Prepare metrics*/ + metric.bandwidth = eigrp_bandwidth_to_scaled(ei->params.bandwidth); + metric.delay = eigrp_delay_to_scaled(ei->params.delay); + metric.load = ei->params.load; + metric.reliability = ei->params.reliability; + eigrp_mtu_convert(&metric, ei->ifp->mtu); + metric.hop_count = 0; + metric.flags = 0; + metric.tag = 0; + + /*Add connected entry to topology table*/ + + ne = eigrp_route_descriptor_new(); + ne->ei = ei; + ne->reported_metric = metric; + ne->total_metric = metric; + ne->distance = eigrp_calculate_metrics(eigrp, metric); + ne->reported_distance = 0; + ne->adv_router = eigrp->neighbor_self; + ne->flags = EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG; + + struct prefix dest_addr; + + dest_addr = ei->address; + apply_mask(&dest_addr); + pe = eigrp_topology_table_lookup_ipv4(eigrp->topology_table, + &dest_addr); + + if (pe == NULL) { + pe = eigrp_prefix_descriptor_new(); + pe->serno = eigrp->serno; + pe->destination = (struct prefix *)prefix_ipv4_new(); + prefix_copy(pe->destination, &dest_addr); + pe->af = AF_INET; + pe->nt = EIGRP_TOPOLOGY_TYPE_CONNECTED; + + ne->prefix = pe; + pe->reported_metric = metric; + pe->state = EIGRP_FSM_STATE_PASSIVE; + pe->fdistance = eigrp_calculate_metrics(eigrp, metric); + pe->req_action |= EIGRP_FSM_NEED_UPDATE; + eigrp_prefix_descriptor_add(eigrp->topology_table, pe); + listnode_add(eigrp->topology_changes_internalIPV4, pe); + + eigrp_route_descriptor_add(eigrp, pe, ne); + + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei2)) { + eigrp_update_send(ei2); + } + + pe->req_action &= ~EIGRP_FSM_NEED_UPDATE; + listnode_delete(eigrp->topology_changes_internalIPV4, pe); + } else { + struct eigrp_fsm_action_message msg; + + ne->prefix = pe; + eigrp_route_descriptor_add(eigrp, pe, ne); + + msg.packet_type = EIGRP_OPC_UPDATE; + msg.eigrp = eigrp; + msg.data_type = EIGRP_CONNECTED; + msg.adv_router = NULL; + msg.entry = ne; + msg.prefix = pe; + + eigrp_fsm_event(&msg); + } + + return 1; +} + +int eigrp_if_down(struct eigrp_interface *ei) +{ + struct listnode *node, *nnode; + struct eigrp_neighbor *nbr; + + if (ei == NULL) + return 0; + + /* Shutdown packet reception and sending */ + EVENT_OFF(ei->t_hello); + + eigrp_if_stream_unset(ei); + + /*Set infinite metrics to routes learned by this interface and start + * query process*/ + for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { + eigrp_nbr_delete(nbr); + } + + return 1; +} + +void eigrp_if_stream_set(struct eigrp_interface *ei) +{ + /* set output fifo queue. */ + if (ei->obuf == NULL) + ei->obuf = eigrp_fifo_new(); +} + +void eigrp_if_stream_unset(struct eigrp_interface *ei) +{ + struct eigrp *eigrp = ei->eigrp; + + if (ei->on_write_q) { + listnode_delete(eigrp->oi_write_q, ei); + if (list_isempty(eigrp->oi_write_q)) + event_cancel(&(eigrp->t_write)); + ei->on_write_q = 0; + } +} + +bool eigrp_if_is_passive(struct eigrp_interface *ei) +{ + if (ei->params.passive_interface == EIGRP_IF_ACTIVE) + return false; + + if (ei->eigrp->passive_interface_default == EIGRP_IF_ACTIVE) + return false; + + return true; +} + +void eigrp_if_set_multicast(struct eigrp_interface *ei) +{ + if (!eigrp_if_is_passive(ei)) { + /* The interface should belong to the EIGRP-all-routers group. + */ + if (!ei->member_allrouters + && (eigrp_if_add_allspfrouters(ei->eigrp, &ei->address, + ei->ifp->ifindex) + >= 0)) + /* Set the flag only if the system call to join + * succeeded. */ + ei->member_allrouters = true; + } else { + /* The interface should NOT belong to the EIGRP-all-routers + * group. */ + if (ei->member_allrouters) { + /* Only actually drop if this is the last reference */ + eigrp_if_drop_allspfrouters(ei->eigrp, &ei->address, + ei->ifp->ifindex); + /* Unset the flag regardless of whether the system call + to leave + the group succeeded, since it's much safer to assume + that + we are not a member. */ + ei->member_allrouters = false; + } + } +} + +uint8_t eigrp_default_iftype(struct interface *ifp) +{ + if (if_is_pointopoint(ifp)) + return EIGRP_IFTYPE_POINTOPOINT; + else if (if_is_loopback(ifp)) + return EIGRP_IFTYPE_LOOPBACK; + else + return EIGRP_IFTYPE_BROADCAST; +} + +void eigrp_if_free(struct eigrp_interface *ei, int source) +{ + struct prefix dest_addr; + struct eigrp_prefix_descriptor *pe; + struct eigrp *eigrp = ei->eigrp; + + if (source == INTERFACE_DOWN_BY_VTY) { + event_cancel(&ei->t_hello); + eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); + } + + dest_addr = ei->address; + apply_mask(&dest_addr); + pe = eigrp_topology_table_lookup_ipv4(eigrp->topology_table, + &dest_addr); + if (pe) + eigrp_prefix_descriptor_delete(eigrp, eigrp->topology_table, + pe); + + eigrp_if_down(ei); + + listnode_delete(ei->eigrp->eiflist, ei); +} + +/* Simulate down/up on the interface. This is needed, for example, when + the MTU changes. */ +void eigrp_if_reset(struct interface *ifp) +{ + struct eigrp_interface *ei = ifp->info; + + if (!ei) + return; + + eigrp_if_down(ei); + eigrp_if_up(ei); +} + +struct eigrp_interface *eigrp_if_lookup_by_local_addr(struct eigrp *eigrp, + struct interface *ifp, + struct in_addr address) +{ + struct listnode *node; + struct eigrp_interface *ei; + + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + if (ifp && ei->ifp != ifp) + continue; + + if (IPV4_ADDR_SAME(&address, &ei->address.u.prefix4)) + return ei; + } + + return NULL; +} + +/** + * @fn eigrp_if_lookup_by_name + * + * @param[in] eigrp EIGRP process + * @param[in] if_name Name of the interface + * + * @return struct eigrp_interface * + * + * @par + * Function is used for lookup interface by name. + */ +struct eigrp_interface *eigrp_if_lookup_by_name(struct eigrp *eigrp, + const char *if_name) +{ + struct eigrp_interface *ei; + struct listnode *node; + + /* iterate over all eigrp interfaces */ + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + /* compare int name with eigrp interface's name */ + if (strcmp(ei->ifp->name, if_name) == 0) { + return ei; + } + } + + return NULL; +} diff --git a/eigrpd/eigrp_interface.h b/eigrpd/eigrp_interface.h new file mode 100644 index 0000000..76f9c1b --- /dev/null +++ b/eigrpd/eigrp_interface.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Interface Functions. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_INTERFACE_H_ +#define _ZEBRA_EIGRP_INTERFACE_H_ + +/*Prototypes*/ +extern void eigrp_if_init(void); +extern int eigrp_if_new_hook(struct interface *); +extern int eigrp_if_delete_hook(struct interface *); + +extern bool eigrp_if_is_passive(struct eigrp_interface *ei); +extern void eigrp_del_if_params(struct eigrp_if_params *); +extern struct eigrp_interface *eigrp_if_new(struct eigrp *, struct interface *, + struct prefix *); +extern int eigrp_if_up(struct eigrp_interface *); +extern void eigrp_if_stream_set(struct eigrp_interface *); +extern void eigrp_if_set_multicast(struct eigrp_interface *); +extern uint8_t eigrp_default_iftype(struct interface *); +extern void eigrp_if_free(struct eigrp_interface *, int); +extern int eigrp_if_down(struct eigrp_interface *); +extern void eigrp_if_stream_unset(struct eigrp_interface *); + +extern struct eigrp_interface *eigrp_if_lookup_by_local_addr(struct eigrp *, + struct interface *, + struct in_addr); +extern struct eigrp_interface *eigrp_if_lookup_by_name(struct eigrp *, + const char *); + +/* Simulate down/up on the interface. */ +extern void eigrp_if_reset(struct interface *); + +#endif /* ZEBRA_EIGRP_INTERFACE_H_ */ diff --git a/eigrpd/eigrp_macros.h b/eigrpd/eigrp_macros.h new file mode 100644 index 0000000..75f1d7e --- /dev/null +++ b/eigrpd/eigrp_macros.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Macros Definition. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRP_MACROS_H_ +#define _ZEBRA_EIGRP_MACROS_H_ + +//-------------------------------------------------------------------------- + +#define EIGRP_IF_STRING_MAXLEN 40 +#define IF_NAME(I) eigrp_if_name_string ((I)) + +//-------------------------------------------------------------------------- + +#define EIGRP_PACKET_MTU(mtu) ((mtu) - (sizeof(struct ip))) + +/* Topology Macros */ + + +/* FSM macros*/ +#define EIGRP_FSM_EVENT_SCHEDULE(I, E) \ + event_add_event(master, eigrp_fsm_event, (I), (E)) + +#endif /* _ZEBRA_EIGRP_MACROS_H_ */ diff --git a/eigrpd/eigrp_main.c b/eigrpd/eigrp_main.c new file mode 100644 index 0000000..319ac92 --- /dev/null +++ b/eigrpd/eigrp_main.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Main Routine. + * Copyright (C) 2013-2015 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "keychain.h" +#include "distribute.h" +#include "libfrr.h" +#include "routemap.h" +#include "libagentx.h" +//#include "if_rmap.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_snmp.h" +#include "eigrpd/eigrp_filter.h" +#include "eigrpd/eigrp_errors.h" +#include "eigrpd/eigrp_vrf.h" +#include "eigrpd/eigrp_cli.h" +#include "eigrpd/eigrp_yang.h" +//#include "eigrpd/eigrp_routemap.h" + +/* eigprd privileges */ +zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, +}; + +struct zebra_privs_t eigrpd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +/* EIGRPd options. */ +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct event_loop *master; + +/* Forward declaration of daemon info structure. */ +static struct frr_daemon_info eigrpd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, eigrpd_di.config_file, config_default); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + keychain_terminate(); + + eigrp_terminate(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t eigrp_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const eigrpd_yang_modules[] = { + &frr_eigrpd_info, + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &ietf_key_chain_info, + &ietf_key_chain_deviation_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(eigrpd, EIGRP, + .vty_port = EIGRP_VTY_PORT, + .proghelp = "Implementation of the EIGRP routing protocol.", + + .signals = eigrp_signals, + .n_signals = array_size(eigrp_signals), + + .privs = &eigrpd_privs, + + .yang_modules = eigrpd_yang_modules, + .n_yang_modules = array_size(eigrpd_yang_modules), +); +/* clang-format on */ + +/* EIGRPd main routine. */ +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&eigrpd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + eigrp_sw_version_initialize(); + + /* EIGRP master init. */ + eigrp_master_init(); + + eigrp_om->master = frr_init(); + master = eigrp_om->master; + + libagentx_init(); + eigrp_error_init(); + eigrp_vrf_init(); + vrf_init(NULL, NULL, NULL, NULL); + + /*EIGRPd init*/ + eigrp_if_init(); + eigrp_zebra_init(); + eigrp_debug_init(); + + /* Get configuration file. */ + /* EIGRP VTY inits */ + eigrp_vty_init(); + keychain_init(); + eigrp_vty_show_init(); + eigrp_cli_init(); + +#ifdef HAVE_SNMP + eigrp_snmp_init(); +#endif /* HAVE_SNMP */ + + /* Access list install. */ + access_list_init(); + access_list_add_hook(eigrp_distribute_update_all_wrapper); + access_list_delete_hook(eigrp_distribute_update_all_wrapper); + + /* Prefix list initialize.*/ + prefix_list_init(); + prefix_list_add_hook(eigrp_distribute_update_all); + prefix_list_delete_hook(eigrp_distribute_update_all); + + /* + * XXX: This is just to get the CLI installed to suppress VTYSH errors. + * Routemaps in EIGRP are not yet functional. + */ + route_map_init(); + /*eigrp_route_map_init(); + route_map_add_hook (eigrp_rmap_update); + route_map_delete_hook (eigrp_rmap_update);*/ + /*if_rmap_init (EIGRP_NODE); */ + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/eigrpd/eigrp_metric.c b/eigrpd/eigrp_metric.c new file mode 100644 index 0000000..1662f48 --- /dev/null +++ b/eigrpd/eigrp_metric.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Metric Math Functions. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + */ + +#include + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_metric.h" + +eigrp_scaled_t eigrp_bandwidth_to_scaled(eigrp_bandwidth_t bandwidth) +{ + eigrp_bandwidth_t scaled = EIGRP_BANDWIDTH_MAX; + + if (bandwidth != EIGRP_BANDWIDTH_MAX) { + scaled = (EIGRP_CLASSIC_SCALER * EIGRP_BANDWIDTH_SCALER); + scaled = scaled / bandwidth; + + scaled = scaled ? scaled : EIGRP_BANDWIDTH_MIN; + } + + scaled = (scaled < EIGRP_METRIC_MAX) ? scaled : EIGRP_METRIC_MAX; + return (eigrp_scaled_t)scaled; +} + +eigrp_bandwidth_t eigrp_scaled_to_bandwidth(eigrp_scaled_t scaled) +{ + eigrp_bandwidth_t bandwidth = EIGRP_BANDWIDTH_MAX; + + if (scaled != EIGRP_CLASSIC_MAX) { + bandwidth = (EIGRP_CLASSIC_SCALER * EIGRP_BANDWIDTH_SCALER); + bandwidth = scaled * bandwidth; + bandwidth = (bandwidth < EIGRP_METRIC_MAX) + ? bandwidth + : EIGRP_BANDWIDTH_MAX; + } + + return bandwidth; +} + +eigrp_scaled_t eigrp_delay_to_scaled(eigrp_delay_t delay) +{ + delay = delay ? delay : EIGRP_DELAY_MIN; + return delay * EIGRP_CLASSIC_SCALER; +} + +eigrp_delay_t eigrp_scaled_to_delay(eigrp_scaled_t scaled) +{ + scaled = scaled / EIGRP_CLASSIC_SCALER; + scaled = scaled ? scaled : EIGRP_DELAY_MIN; + + return scaled; +} + +eigrp_metric_t eigrp_calculate_metrics(struct eigrp *eigrp, + struct eigrp_metrics metric) +{ + eigrp_metric_t composite = 0; + + if (metric.delay == EIGRP_MAX_METRIC) + return EIGRP_METRIC_MAX; + + /* + * EIGRP Composite = + * {K1*BW+[(K2*BW)/(256-load)]+(K3*delay)}*{K5/(reliability+K4)} + */ + + if (eigrp->k_values[0]) + composite += ((eigrp_metric_t)eigrp->k_values[0] * + (eigrp_metric_t)metric.bandwidth); + if (eigrp->k_values[1]) + composite += (((eigrp_metric_t)eigrp->k_values[1] * + (eigrp_metric_t)metric.bandwidth) / + (256 - metric.load)); + if (eigrp->k_values[2]) + composite += ((eigrp_metric_t)eigrp->k_values[2] * + (eigrp_metric_t)metric.delay); + if (eigrp->k_values[3] && !eigrp->k_values[4]) + composite *= (eigrp_metric_t)eigrp->k_values[3]; + if (!eigrp->k_values[3] && eigrp->k_values[4]) + composite *= ((eigrp_metric_t)eigrp->k_values[4] / + (eigrp_metric_t)metric.reliability); + if (eigrp->k_values[3] && eigrp->k_values[4]) + composite *= (((eigrp_metric_t)eigrp->k_values[4] / + (eigrp_metric_t)metric.reliability) + + (eigrp_metric_t)eigrp->k_values[3]); + + composite = + (composite <= EIGRP_METRIC_MAX) ? composite : EIGRP_METRIC_MAX; + + return composite; +} + +eigrp_metric_t +eigrp_calculate_total_metrics(struct eigrp *eigrp, + struct eigrp_route_descriptor *entry) +{ + struct eigrp_interface *ei = entry->ei; + eigrp_delay_t temp_delay; + eigrp_bandwidth_t bw; + + entry->total_metric = entry->reported_metric; + temp_delay = entry->total_metric.delay + + eigrp_delay_to_scaled(ei->params.delay); + + entry->total_metric.delay = temp_delay > EIGRP_METRIC_MAX_CLASSIC + ? EIGRP_METRIC_MAX_CLASSIC + : temp_delay; + + bw = eigrp_bandwidth_to_scaled(ei->params.bandwidth); + entry->total_metric.bandwidth = entry->total_metric.bandwidth > bw + ? bw + : entry->total_metric.bandwidth; + + return eigrp_calculate_metrics(eigrp, entry->total_metric); +} + +bool eigrp_metrics_is_same(struct eigrp_metrics metric1, + struct eigrp_metrics metric2) +{ + if ((metric1.bandwidth == metric2.bandwidth) + && (metric1.delay == metric2.delay) + && (metric1.hop_count == metric2.hop_count) + && (metric1.load == metric2.load) + && (metric1.reliability == metric2.reliability) + && (metric1.mtu[0] == metric2.mtu[0]) + && (metric1.mtu[1] == metric2.mtu[1]) + && (metric1.mtu[2] == metric2.mtu[2])) { + return true; + } + + return false; /* if different */ +} diff --git a/eigrpd/eigrp_metric.h b/eigrpd/eigrp_metric.h new file mode 100644 index 0000000..d4a8d09 --- /dev/null +++ b/eigrpd/eigrp_metric.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Metric Math Functions. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + */ + +#ifndef _ZEBRA_EIGRP_METRIC_H_ +#define _ZEBRA_EIGRP_METRIC_H_ + +/* Constants */ +#define EIGRP_BANDWIDTH_MIN 0x1ull /* 1 */ +#define EIGRP_BANDWIDTH_SCALER 10000000ull /* Inversion value */ +#define EIGRP_BANDWIDTH_MAX 0xffffffffffffffffull /* 1.84467441x10^19 */ + +#define EIGRP_DELAY_MIN 0x1ull /* 1 */ +#define EIGRP_DELAY_PICO 1000000ull +#define EIGRP_DELAY_MAX 0xffffffffffffffffull /* 1.84467441x10^19 */ + +#define EIGRP_MAX_LOAD 256 +#define EIGRP_MAX_HOPS 100 + +#define EIGRP_INACCESSIBLE 0xFFFFFFFFFFFFFFFFull + +#define EIGRP_METRIC_MAX 0xffffffffffffffffull /* 1.84467441x10^19 */ +#define EIGRP_METRIC_MAX_CLASSIC 0xffffffff +#define EIGRP_METRIC_SCALER 65536 /* CLASSIC to WIDE conversion */ + +#define EIGRP_CLASSIC_MAX 0xffffffff /* 4294967295 */ +#define EIGRP_CLASSIC_SCALER 256 /* IGRP to EIGRP conversion */ + + +/* Prototypes */ +extern eigrp_scaled_t eigrp_bandwidth_to_scaled(eigrp_bandwidth_t bw); +extern eigrp_bandwidth_t eigrp_scaled_to_bandwidth(eigrp_scaled_t scale); +extern eigrp_scaled_t eigrp_delay_to_scaled(eigrp_delay_t delay); +extern eigrp_delay_t eigrp_scaled_to_delay(eigrp_scaled_t scale); + +extern eigrp_metric_t eigrp_calculate_metrics(struct eigrp *eigrp, + struct eigrp_metrics metric); +extern eigrp_metric_t +eigrp_calculate_total_metrics(struct eigrp *eigrp, + struct eigrp_route_descriptor *rd); +extern bool eigrp_metrics_is_same(struct eigrp_metrics m1, + struct eigrp_metrics m2); + +#endif /* _ZEBRA_EIGRP_METRIC_H_ */ diff --git a/eigrpd/eigrp_neighbor.c b/eigrpd/eigrp_neighbor.c new file mode 100644 index 0000000..25209c3 --- /dev/null +++ b/eigrpd/eigrp_neighbor.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Neighbor Handling. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "linklist.h" +#include "prefix.h" +#include "memory.h" +#include "command.h" +#include "frrevent.h" +#include "stream.h" +#include "table.h" +#include "log.h" +#include "keychain.h" +#include "vty.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_errors.h" + +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_NEIGHBOR, "EIGRP neighbor"); + +struct eigrp_neighbor *eigrp_nbr_new(struct eigrp_interface *ei) +{ + struct eigrp_neighbor *nbr; + + /* Allcate new neighbor. */ + nbr = XCALLOC(MTYPE_EIGRP_NEIGHBOR, sizeof(struct eigrp_neighbor)); + + /* Relate neighbor to the interface. */ + nbr->ei = ei; + + /* Set default values. */ + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); + + return nbr; +} + +/** + *@fn void dissect_eigrp_sw_version (tvbuff_t *tvb, proto_tree *tree, + * proto_item *ti) + * + * @par + * Create a new neighbor structure and initalize it. + */ +static struct eigrp_neighbor *eigrp_nbr_add(struct eigrp_interface *ei, + struct eigrp_header *eigrph, + struct ip *iph) +{ + struct eigrp_neighbor *nbr; + + nbr = eigrp_nbr_new(ei); + nbr->src = iph->ip_src; + + return nbr; +} + +struct eigrp_neighbor *eigrp_nbr_get(struct eigrp_interface *ei, + struct eigrp_header *eigrph, + struct ip *iph) +{ + struct eigrp_neighbor *nbr; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { + if (iph->ip_src.s_addr == nbr->src.s_addr) { + return nbr; + } + } + + nbr = eigrp_nbr_add(ei, eigrph, iph); + listnode_add(ei->nbrs, nbr); + + return nbr; +} + +/** + * @fn eigrp_nbr_lookup_by_addr + * + * @param[in] ei EIGRP interface + * @param[in] nbr_addr Address of neighbor + * + * @return void + * + * @par + * Function is used for neighbor lookup by address + * in specified interface. + */ +struct eigrp_neighbor *eigrp_nbr_lookup_by_addr(struct eigrp_interface *ei, + struct in_addr *addr) +{ + struct eigrp_neighbor *nbr; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { + if (addr->s_addr == nbr->src.s_addr) { + return nbr; + } + } + + return NULL; +} + +/** + * @fn eigrp_nbr_lookup_by_addr_process + * + * @param[in] eigrp EIGRP process + * @param[in] nbr_addr Address of neighbor + * + * @return void + * + * @par + * Function is used for neighbor lookup by address + * in whole EIGRP process. + */ +struct eigrp_neighbor *eigrp_nbr_lookup_by_addr_process(struct eigrp *eigrp, + struct in_addr nbr_addr) +{ + struct eigrp_interface *ei; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + + /* iterate over all eigrp interfaces */ + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + /* iterate over all neighbors on eigrp interface */ + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + /* compare if neighbor address is same as arg address */ + if (nbr->src.s_addr == nbr_addr.s_addr) { + return nbr; + } + } + } + + return NULL; +} + + +/* Delete specified EIGRP neighbor from interface. */ +void eigrp_nbr_delete(struct eigrp_neighbor *nbr) +{ + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); + if (nbr->ei) + eigrp_topology_neighbor_down(nbr->ei->eigrp, nbr); + + /* Cancel all events. */ /* Thread lookup cost would be negligible. */ + event_cancel_event(master, nbr); + eigrp_fifo_free(nbr->multicast_queue); + eigrp_fifo_free(nbr->retrans_queue); + EVENT_OFF(nbr->t_holddown); + + if (nbr->ei) + listnode_delete(nbr->ei->nbrs, nbr); + XFREE(MTYPE_EIGRP_NEIGHBOR, nbr); +} + +void holddown_timer_expired(struct event *thread) +{ + struct eigrp_neighbor *nbr = EVENT_ARG(thread); + struct eigrp *eigrp = nbr->ei->eigrp; + + zlog_info("Neighbor %pI4 (%s) is down: holding time expired", &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + nbr->state = EIGRP_NEIGHBOR_DOWN; + eigrp_nbr_delete(nbr); +} + +uint8_t eigrp_nbr_state_get(struct eigrp_neighbor *nbr) +{ + return (nbr->state); +} + +void eigrp_nbr_state_set(struct eigrp_neighbor *nbr, uint8_t state) +{ + nbr->state = state; + + if (eigrp_nbr_state_get(nbr) == EIGRP_NEIGHBOR_DOWN) { + // reset all the seq/ack counters + nbr->recv_sequence_number = 0; + nbr->init_sequence_number = 0; + nbr->retrans_counter = 0; + + // Kvalues + nbr->K1 = EIGRP_K1_DEFAULT; + nbr->K2 = EIGRP_K2_DEFAULT; + nbr->K3 = EIGRP_K3_DEFAULT; + nbr->K4 = EIGRP_K4_DEFAULT; + nbr->K5 = EIGRP_K5_DEFAULT; + nbr->K6 = EIGRP_K6_DEFAULT; + + // hold time.. + nbr->v_holddown = EIGRP_HOLD_INTERVAL_DEFAULT; + EVENT_OFF(nbr->t_holddown); + + /* out with the old */ + if (nbr->multicast_queue) + eigrp_fifo_free(nbr->multicast_queue); + if (nbr->retrans_queue) + eigrp_fifo_free(nbr->retrans_queue); + + /* in with the new */ + nbr->retrans_queue = eigrp_fifo_new(); + nbr->multicast_queue = eigrp_fifo_new(); + + nbr->crypt_seqnum = 0; + } +} + +const char *eigrp_nbr_state_str(struct eigrp_neighbor *nbr) +{ + const char *state; + switch (nbr->state) { + case EIGRP_NEIGHBOR_DOWN: + state = "Down"; + break; + case EIGRP_NEIGHBOR_PENDING: + state = "Waiting for Init"; + break; + case EIGRP_NEIGHBOR_UP: + state = "Up"; + break; + default: + state = "Unknown"; + break; + } + + return (state); +} + +void eigrp_nbr_state_update(struct eigrp_neighbor *nbr) +{ + switch (nbr->state) { + case EIGRP_NEIGHBOR_DOWN: { + /*Start Hold Down Timer for neighbor*/ + // EVENT_OFF(nbr->t_holddown); + // EVENT_TIMER_ON(master, nbr->t_holddown, + // holddown_timer_expired, + // nbr, nbr->v_holddown); + break; + } + case EIGRP_NEIGHBOR_PENDING: { + /*Reset Hold Down Timer for neighbor*/ + EVENT_OFF(nbr->t_holddown); + event_add_timer(master, holddown_timer_expired, nbr, + nbr->v_holddown, &nbr->t_holddown); + break; + } + case EIGRP_NEIGHBOR_UP: { + /*Reset Hold Down Timer for neighbor*/ + EVENT_OFF(nbr->t_holddown); + event_add_timer(master, holddown_timer_expired, nbr, + nbr->v_holddown, &nbr->t_holddown); + break; + } + } +} + +int eigrp_nbr_count_get(struct eigrp *eigrp) +{ + struct eigrp_interface *iface; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + uint32_t counter; + + counter = 0; + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { + for (ALL_LIST_ELEMENTS(iface->nbrs, node2, nnode2, nbr)) { + if (nbr->state == EIGRP_NEIGHBOR_UP) { + counter++; + } + } + } + return counter; +} + +/** + * @fn eigrp_nbr_hard_restart + * + * @param[in] nbr Neighbor who would receive hard restart + * @param[in] vty Virtual terminal for log output + * @return void + * + * @par + * Function used for executing hard restart for neighbor: + * Send Hello packet with Peer Termination TLV with + * neighbor's address, set it's state to DOWN and delete the neighbor + */ +void eigrp_nbr_hard_restart(struct eigrp_neighbor *nbr, struct vty *vty) +{ + struct eigrp *eigrp = nbr->ei->eigrp; + + zlog_debug("Neighbor %pI4 (%s) is down: manually cleared", &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + if (vty != NULL) { + vty_time_print(vty, 0); + vty_out(vty, "Neighbor %pI4 (%s) is down: manually cleared\n", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + } + + /* send Hello with Peer Termination TLV */ + eigrp_hello_send(nbr->ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR, + &(nbr->src)); + /* set neighbor to DOWN */ + nbr->state = EIGRP_NEIGHBOR_DOWN; + /* delete neighbor */ + eigrp_nbr_delete(nbr); +} + +int eigrp_nbr_split_horizon_check(struct eigrp_route_descriptor *ne, + struct eigrp_interface *ei) +{ + if (ne->distance == EIGRP_MAX_METRIC) + return 0; + + return (ne->ei == ei); +} diff --git a/eigrpd/eigrp_neighbor.h b/eigrpd/eigrp_neighbor.h new file mode 100644 index 0000000..2ccc89c --- /dev/null +++ b/eigrpd/eigrp_neighbor.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Neighbor Handling. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_NEIGHBOR_H +#define _ZEBRA_EIGRP_NEIGHBOR_H + +/* Prototypes */ +extern struct eigrp_neighbor *eigrp_nbr_get(struct eigrp_interface *ei, + struct eigrp_header *, + struct ip *addr); +extern struct eigrp_neighbor *eigrp_nbr_new(struct eigrp_interface *ei); +extern void eigrp_nbr_delete(struct eigrp_neighbor *neigh); + +extern void holddown_timer_expired(struct event *thread); + +extern int eigrp_neighborship_check(struct eigrp_neighbor *neigh, + struct TLV_Parameter_Type *tlv); +extern void eigrp_nbr_state_update(struct eigrp_neighbor *neigh); +extern void eigrp_nbr_state_set(struct eigrp_neighbor *neigh, uint8_t state); +extern uint8_t eigrp_nbr_state_get(struct eigrp_neighbor *neigh); +extern int eigrp_nbr_count_get(struct eigrp *eigrp); +extern const char *eigrp_nbr_state_str(struct eigrp_neighbor *neigh); +extern struct eigrp_neighbor * +eigrp_nbr_lookup_by_addr(struct eigrp_interface *ei, struct in_addr *addr); +extern struct eigrp_neighbor * +eigrp_nbr_lookup_by_addr_process(struct eigrp *eigrp, struct in_addr addr); +extern void eigrp_nbr_hard_restart(struct eigrp_neighbor *nbr, struct vty *vty); + +extern int eigrp_nbr_split_horizon_check(struct eigrp_route_descriptor *ne, + struct eigrp_interface *ei); +#endif /* _ZEBRA_EIGRP_NEIGHBOR_H */ diff --git a/eigrpd/eigrp_network.c b/eigrpd/eigrp_network.c new file mode 100644 index 0000000..5ca5a18 --- /dev/null +++ b/eigrpd/eigrp_network.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Network Related Functions. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "sockunion.h" +#include "log.h" +#include "sockopt.h" +#include "privs.h" +#include "table.h" +#include "vty.h" +#include "lib_errors.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" + +static int eigrp_network_match_iface(const struct prefix *connected_prefix, + const struct prefix *prefix); +static void eigrp_network_run_interface(struct eigrp *, struct prefix *, + struct interface *); + +int eigrp_sock_init(struct vrf *vrf) +{ + int eigrp_sock = -1; + int ret; +#ifdef IP_HDRINCL + int hincl = 1; +#endif + + if (!vrf) + return eigrp_sock; + + frr_with_privs(&eigrpd_privs) { + eigrp_sock = vrf_socket( + AF_INET, SOCK_RAW, IPPROTO_EIGRPIGP, vrf->vrf_id, + vrf->vrf_id != VRF_DEFAULT ? vrf->name : NULL); + if (eigrp_sock < 0) { + zlog_err("%s: socket: %s", + __func__, safe_strerror(errno)); + exit(1); + } + +#ifdef IP_HDRINCL + /* we will include IP header with packet */ + ret = setsockopt(eigrp_sock, IPPROTO_IP, IP_HDRINCL, &hincl, + sizeof(hincl)); + if (ret < 0) { + zlog_warn("Can't set IP_HDRINCL option for fd %d: %s", + eigrp_sock, safe_strerror(errno)); + } +#elif defined(IPTOS_PREC_INTERNETCONTROL) +#warning "IP_HDRINCL not available on this system" +#warning "using IPTOS_PREC_INTERNETCONTROL" + ret = setsockopt_ipv4_tos(eigrp_sock, + IPTOS_PREC_INTERNETCONTROL); + if (ret < 0) { + zlog_warn("can't set sockopt IP_TOS %d to socket %d: %s", + tos, eigrp_sock, safe_strerror(errno)); + close(eigrp_sock); /* Prevent sd leak. */ + return ret; + } +#else /* !IPTOS_PREC_INTERNETCONTROL */ +#warning "IP_HDRINCL not available, nor is IPTOS_PREC_INTERNETCONTROL" + zlog_warn("IP_HDRINCL option not available"); +#endif /* IP_HDRINCL */ + + ret = setsockopt_ifindex(AF_INET, eigrp_sock, 1); + if (ret < 0) + zlog_warn("Can't set pktinfo option for fd %d", + eigrp_sock); + } + + return eigrp_sock; +} + +void eigrp_adjust_sndbuflen(struct eigrp *eigrp, unsigned int buflen) +{ + int newbuflen; + /* Check if any work has to be done at all. */ + if (eigrp->maxsndbuflen >= buflen) + return; + + /* Now we try to set SO_SNDBUF to what our caller has requested + * (the MTU of a newly added interface). However, if the OS has + * truncated the actual buffer size to somewhat less size, try + * to detect it and update our records appropriately. The OS + * may allocate more buffer space, than requested, this isn't + * a error. + */ + setsockopt_so_sendbuf(eigrp->fd, buflen); + newbuflen = getsockopt_so_sendbuf(eigrp->fd); + if (newbuflen < 0 || newbuflen < (int)buflen) + zlog_warn("%s: tried to set SO_SNDBUF to %u, but got %d", + __func__, buflen, newbuflen); + if (newbuflen >= 0) + eigrp->maxsndbuflen = (unsigned int)newbuflen; + else + zlog_warn("%s: failed to get SO_SNDBUF", __func__); +} + +int eigrp_if_ipmulticast(struct eigrp *top, struct prefix *p, + unsigned int ifindex) +{ + uint8_t val; + int ret, len; + + val = 0; + len = sizeof(val); + + /* Prevent receiving self-origined multicast packets. */ + ret = setsockopt(top->fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&val, + len); + if (ret < 0) + zlog_warn( + "can't setsockopt IP_MULTICAST_LOOP (0) for fd %d: %s", + top->fd, safe_strerror(errno)); + + /* Explicitly set multicast ttl to 1 -- endo. */ + val = 1; + ret = setsockopt(top->fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&val, + len); + if (ret < 0) + zlog_warn("can't setsockopt IP_MULTICAST_TTL (1) for fd %d: %s", + top->fd, safe_strerror(errno)); + + ret = setsockopt_ipv4_multicast_if(top->fd, p->u.prefix4, ifindex); + if (ret < 0) + zlog_warn( + "can't setsockopt IP_MULTICAST_IF (fd %d, addr %pI4, ifindex %u): %s", + top->fd, &p->u.prefix4, ifindex, safe_strerror(errno)); + + return ret; +} + +/* Join to the EIGRP multicast group. */ +int eigrp_if_add_allspfrouters(struct eigrp *top, struct prefix *p, + unsigned int ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast( + top->fd, IP_ADD_MEMBERSHIP, p->u.prefix4, + htonl(EIGRP_MULTICAST_ADDRESS), ifindex); + if (ret < 0) + zlog_warn( + "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllSPFRouters): %s; perhaps a kernel limit on # of multicast group memberships has been exceeded?", + top->fd, &p->u.prefix4, ifindex, safe_strerror(errno)); + else + zlog_debug("interface %pI4 [%u] join EIGRP Multicast group.", + &p->u.prefix4, ifindex); + + return ret; +} + +int eigrp_if_drop_allspfrouters(struct eigrp *top, struct prefix *p, + unsigned int ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast( + top->fd, IP_DROP_MEMBERSHIP, p->u.prefix4, + htonl(EIGRP_MULTICAST_ADDRESS), ifindex); + if (ret < 0) + zlog_warn( + "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllSPFRouters): %s", + top->fd, &p->u.prefix4, ifindex, safe_strerror(errno)); + else + zlog_debug("interface %pI4 [%u] leave EIGRP Multicast group.", + &p->u.prefix4, ifindex); + + return ret; +} + +int eigrp_network_set(struct eigrp *eigrp, struct prefix *p) +{ + struct vrf *vrf = vrf_lookup_by_id(eigrp->vrf_id); + struct route_node *rn; + struct interface *ifp; + + rn = route_node_get(eigrp->networks, p); + if (rn->info) { + /* There is already same network statement. */ + route_unlock_node(rn); + return 0; + } + + struct prefix *pref = prefix_new(); + prefix_copy(pref, p); + rn->info = (void *)pref; + + /* Schedule Router ID Update. */ + if (eigrp->router_id.s_addr == INADDR_ANY) + eigrp_router_id_update(eigrp); + /* Run network config now. */ + /* Get target interface. */ + FOR_ALL_INTERFACES (vrf, ifp) { + zlog_debug("Setting up %s", ifp->name); + eigrp_network_run_interface(eigrp, p, ifp); + } + return 1; +} + +/* Check whether interface matches given network + * returns: 1, true. 0, false + */ +static int eigrp_network_match_iface(const struct prefix *co_prefix, + const struct prefix *net) +{ + /* new approach: more elegant and conceptually clean */ + return prefix_match_network_statement(net, co_prefix); +} + +static void eigrp_network_run_interface(struct eigrp *eigrp, struct prefix *p, + struct interface *ifp) +{ + struct eigrp_interface *ei; + struct connected *co; + + /* if interface prefix is match specified prefix, + then create socket and join multicast group. */ + frr_each (if_connected, ifp->connected, co) { + if (CHECK_FLAG(co->flags, ZEBRA_IFA_SECONDARY)) + continue; + + if (p->family == co->address->family && !ifp->info + && eigrp_network_match_iface(co->address, p)) { + + ei = eigrp_if_new(eigrp, ifp, co->address); + + /* Relate eigrp interface to eigrp instance. */ + ei->eigrp = eigrp; + + /* if router_id is not configured, dont bring up + * interfaces. + * eigrp_router_id_update() will call eigrp_if_update + * whenever r-id is configured instead. + */ + if (if_is_operative(ifp)) + eigrp_if_up(ei); + } + } +} + +void eigrp_if_update(struct interface *ifp) +{ + struct listnode *node, *nnode; + struct route_node *rn; + struct eigrp *eigrp; + + /* + * In the event there are multiple eigrp autonymnous systems running, + * we need to check eac one and add the interface as approperate + */ + for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) { + if (ifp->vrf->vrf_id != eigrp->vrf_id) + continue; + + /* EIGRP must be on and Router-ID must be configured. */ + if (eigrp->router_id.s_addr == INADDR_ANY) + continue; + + /* Run each network for this interface. */ + for (rn = route_top(eigrp->networks); rn; rn = route_next(rn)) + if (rn->info != NULL) { + eigrp_network_run_interface(eigrp, &rn->p, ifp); + } + } +} + +int eigrp_network_unset(struct eigrp *eigrp, struct prefix *p) +{ + struct route_node *rn; + struct listnode *node, *nnode; + struct eigrp_interface *ei; + struct prefix *pref; + + rn = route_node_lookup(eigrp->networks, p); + if (rn == NULL) + return 0; + + pref = rn->info; + route_unlock_node(rn); + + if (!IPV4_ADDR_SAME(&pref->u.prefix4, &p->u.prefix4)) + return 0; + + prefix_ipv4_free((struct prefix_ipv4 **)&rn->info); + route_unlock_node(rn); /* initial reference */ + + /* Find interfaces that not configured already. */ + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { + bool found = false; + + for (rn = route_top(eigrp->networks); rn; rn = route_next(rn)) { + if (rn->info == NULL) + continue; + + if (eigrp_network_match_iface(&ei->address, &rn->p)) { + found = true; + route_unlock_node(rn); + break; + } + } + + if (!found) { + eigrp_if_free(ei, INTERFACE_DOWN_BY_VTY); + } + } + + return 1; +} + +void eigrp_external_routes_refresh(struct eigrp *eigrp, int type) +{ +} diff --git a/eigrpd/eigrp_network.h b/eigrpd/eigrp_network.h new file mode 100644 index 0000000..ac5c47f --- /dev/null +++ b/eigrpd/eigrp_network.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Network Related Functions. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRP_NETWORK_H +#define _ZEBRA_EIGRP_NETWORK_H + +/* Prototypes */ + +extern int eigrp_sock_init(struct vrf *vrf); +extern int eigrp_if_ipmulticast(struct eigrp *, struct prefix *, unsigned int); +extern int eigrp_network_set(struct eigrp *eigrp, struct prefix *p); +extern int eigrp_network_unset(struct eigrp *eigrp, struct prefix *p); + +extern void eigrp_hello_timer(struct event *thread); +extern void eigrp_if_update(struct interface *); +extern int eigrp_if_add_allspfrouters(struct eigrp *, struct prefix *, + unsigned int); +extern int eigrp_if_drop_allspfrouters(struct eigrp *top, struct prefix *p, + unsigned int ifindex); +extern void eigrp_adjust_sndbuflen(struct eigrp *, unsigned int); + +extern void eigrp_external_routes_refresh(struct eigrp *, int); + +#endif /* EIGRP_NETWORK_H_ */ diff --git a/eigrpd/eigrp_northbound.c b/eigrpd/eigrp_northbound.c new file mode 100644 index 0000000..4aeb635 --- /dev/null +++ b/eigrpd/eigrp_northbound.c @@ -0,0 +1,1579 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP daemon northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/keychain.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/table.h" +#include "lib/vrf.h" +#include "lib/zclient.h" +#include "lib/distribute.h" + +#include "eigrp_structs.h" +#include "eigrpd.h" +#include "eigrp_interface.h" +#include "eigrp_network.h" +#include "eigrp_zebra.h" +#include "eigrp_cli.h" + +/* Helper functions. */ +static void redistribute_get_metrics(const struct lyd_node *dnode, + struct eigrp_metrics *em) +{ + memset(em, 0, sizeof(*em)); + + if (yang_dnode_exists(dnode, "bandwidth")) + em->bandwidth = yang_dnode_get_uint32(dnode, "bandwidth"); + if (yang_dnode_exists(dnode, "delay")) + em->delay = yang_dnode_get_uint32(dnode, "delay"); +#if 0 /* TODO: How does MTU work? */ + if (yang_dnode_exists(dnode, "mtu")) + em->mtu[0] = yang_dnode_get_uint32(dnode, "mtu"); +#endif + if (yang_dnode_exists(dnode, "load")) + em->load = yang_dnode_get_uint32(dnode, "load"); + if (yang_dnode_exists(dnode, "reliability")) + em->reliability = yang_dnode_get_uint32(dnode, "reliability"); +} + +static struct eigrp_interface *eigrp_interface_lookup(const struct eigrp *eigrp, + const char *ifname) +{ + struct eigrp_interface *eif; + struct listnode *ln; + + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, ln, eif)) { + if (strcmp(ifname, eif->ifp->name)) + continue; + + return eif; + } + + return NULL; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance + */ +static int eigrpd_instance_create(struct nb_cb_create_args *args) +{ + struct eigrp *eigrp; + const char *vrf; + struct vrf *pVrf; + vrf_id_t vrfid; + + switch (args->event) { + case NB_EV_VALIDATE: + /* NOTHING */ + break; + case NB_EV_PREPARE: + vrf = yang_dnode_get_string(args->dnode, "vrf"); + + pVrf = vrf_lookup_by_name(vrf); + if (pVrf) + vrfid = pVrf->vrf_id; + else + vrfid = VRF_DEFAULT; + + eigrp = eigrp_get(yang_dnode_get_uint16(args->dnode, "asn"), + vrfid); + args->resource->ptr = eigrp; + break; + case NB_EV_ABORT: + eigrp_finish_final(args->resource->ptr); + break; + case NB_EV_APPLY: + nb_running_set_entry(args->dnode, args->resource->ptr); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_unset_entry(args->dnode); + eigrp_finish_final(eigrp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/router-id + */ +static int eigrpd_instance_router_id_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv4(&eigrp->router_id_static, args->dnode, + NULL); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_router_id_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->router_id_static.s_addr = INADDR_ANY; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/passive-interface + */ +static int +eigrpd_instance_passive_interface_create(struct nb_cb_create_args *args) +{ + struct eigrp_interface *eif; + struct eigrp *eigrp; + const char *ifname; + + switch (args->event) { + case NB_EV_VALIDATE: + eigrp = nb_running_get_entry(args->dnode, NULL, false); + if (eigrp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + ifname = yang_dnode_get_string(args->dnode, NULL); + eif = eigrp_interface_lookup(eigrp, ifname); + if (eif == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + eif = eigrp_interface_lookup(eigrp, ifname); + if (eif == NULL) + return NB_ERR_INCONSISTENCY; + + eif->params.passive_interface = EIGRP_IF_PASSIVE; + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_passive_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp_interface *eif; + struct eigrp *eigrp; + const char *ifname; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + eif = eigrp_interface_lookup(eigrp, ifname); + if (eif == NULL) + break; + + eif->params.passive_interface = EIGRP_IF_ACTIVE; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/active-time + */ +static int eigrpd_instance_active_time_modify(struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "active time not implemented yet"); + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/variance + */ +static int eigrpd_instance_variance_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->variance = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_variance_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->variance = EIGRP_VARIANCE_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/maximum-paths + */ +static int eigrpd_instance_maximum_paths_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->max_paths = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_maximum_paths_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->max_paths = EIGRP_MAX_PATHS_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K1 + */ +static int +eigrpd_instance_metric_weights_K1_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[0] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K1_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[0] = EIGRP_K1_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K2 + */ +static int +eigrpd_instance_metric_weights_K2_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[1] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K2_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[1] = EIGRP_K2_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K3 + */ +static int +eigrpd_instance_metric_weights_K3_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[2] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K3_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[2] = EIGRP_K3_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K4 + */ +static int +eigrpd_instance_metric_weights_K4_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[3] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K4_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[3] = EIGRP_K4_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K5 + */ +static int +eigrpd_instance_metric_weights_K5_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[4] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K5_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[4] = EIGRP_K5_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/metric-weights/K6 + */ +static int +eigrpd_instance_metric_weights_K6_modify(struct nb_cb_modify_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[5] = yang_dnode_get_uint8(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_metric_weights_K6_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp->k_values[5] = EIGRP_K6_DEFAULT; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/network + */ +static int eigrpd_instance_network_create(struct nb_cb_create_args *args) +{ + struct route_node *rnode; + struct prefix prefix; + struct eigrp *eigrp; + int exists; + + yang_dnode_get_ipv4p(&prefix, args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + eigrp = nb_running_get_entry(args->dnode, NULL, false); + /* If entry doesn't exist it means the list is empty. */ + if (eigrp == NULL) + break; + + rnode = route_node_get(eigrp->networks, &prefix); + exists = (rnode->info != NULL); + route_unlock_node(rnode); + if (exists) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + if (eigrp_network_set(eigrp, &prefix) == 0) + return NB_ERR_INCONSISTENCY; + break; + } + + return NB_OK; +} + +static int eigrpd_instance_network_destroy(struct nb_cb_destroy_args *args) +{ + struct route_node *rnode; + struct prefix prefix; + struct eigrp *eigrp; + int exists = 0; + + yang_dnode_get_ipv4p(&prefix, args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + eigrp = nb_running_get_entry(args->dnode, NULL, false); + /* If entry doesn't exist it means the list is empty. */ + if (eigrp == NULL) + break; + + rnode = route_node_get(eigrp->networks, &prefix); + exists = (rnode->info != NULL); + route_unlock_node(rnode); + if (exists == 0) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + eigrp_network_unset(eigrp, &prefix); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/neighbor + */ +static int eigrpd_instance_neighbor_create(struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "neighbor Command is not implemented yet"); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_neighbor_destroy(struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "no neighbor Command is not implemented yet"); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/distribute-list + */ +static int eigrpd_instance_distribute_list_create(struct nb_cb_create_args *args) +{ + struct eigrp *eigrp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + eigrp = nb_running_get_entry(args->dnode, NULL, true); + group_distribute_list_create_helper(args, eigrp->distribute_ctx); + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute + */ +static int eigrpd_instance_redistribute_create(struct nb_cb_create_args *args) +{ + struct eigrp_metrics metrics; + const char *vrfname; + struct eigrp *eigrp; + uint32_t proto; + vrf_id_t vrfid; + struct vrf *pVrf; + + switch (args->event) { + case NB_EV_VALIDATE: + proto = yang_dnode_get_enum(args->dnode, "protocol"); + vrfname = yang_dnode_get_string(args->dnode, "../vrf"); + + pVrf = vrf_lookup_by_name(vrfname); + if (pVrf) + vrfid = pVrf->vrf_id; + else + vrfid = VRF_DEFAULT; + + if (vrf_bitmap_check(&zclient->redist[AFI_IP][proto], vrfid)) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_enum(args->dnode, "protocol"); + redistribute_get_metrics(args->dnode, &metrics); + eigrp_redistribute_set(eigrp, proto, metrics); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_redistribute_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp *eigrp; + uint32_t proto; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_enum(args->dnode, "protocol"); + eigrp_redistribute_unset(eigrp, proto); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/route-map + */ +static int +eigrpd_instance_redistribute_route_map_modify(struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf( + args->errmsg, args->errmsg_len, + "'redistribute X route-map FOO' command not implemented yet"); + break; + } + + return NB_OK; +} + +static int +eigrpd_instance_redistribute_route_map_destroy(struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf( + args->errmsg, args->errmsg_len, + "'no redistribute X route-map FOO' command not implemented yet"); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth + */ +static int eigrpd_instance_redistribute_metrics_bandwidth_modify( + struct nb_cb_modify_args *args) +{ + struct eigrp_metrics metrics; + struct eigrp *eigrp; + uint32_t proto; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_enum(args->dnode, "../../protocol"); + redistribute_get_metrics(args->dnode, &metrics); + eigrp_redistribute_set(eigrp, proto, metrics); + break; + } + + return NB_OK; +} + +static int eigrpd_instance_redistribute_metrics_bandwidth_destroy( + struct nb_cb_destroy_args *args) +{ + struct eigrp_metrics metrics; + struct eigrp *eigrp; + uint32_t proto; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eigrp = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_enum(args->dnode, "../../protocol"); + redistribute_get_metrics(args->dnode, &metrics); + eigrp_redistribute_set(eigrp, proto, metrics); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/delay + */ +static int eigrpd_instance_redistribute_metrics_delay_modify( + struct nb_cb_modify_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_modify(args); +} + +static int eigrpd_instance_redistribute_metrics_delay_destroy( + struct nb_cb_destroy_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_destroy(args); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability + */ +static int eigrpd_instance_redistribute_metrics_reliability_modify( + struct nb_cb_modify_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_modify(args); +} + +static int eigrpd_instance_redistribute_metrics_reliability_destroy( + struct nb_cb_destroy_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_destroy(args); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/load + */ +static int +eigrpd_instance_redistribute_metrics_load_modify(struct nb_cb_modify_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_modify(args); +} + +static int eigrpd_instance_redistribute_metrics_load_destroy( + struct nb_cb_destroy_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_destroy(args); +} + +/* + * XPath: /frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu + */ +static int +eigrpd_instance_redistribute_metrics_mtu_modify(struct nb_cb_modify_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_modify(args); +} + +static int eigrpd_instance_redistribute_metrics_mtu_destroy( + struct nb_cb_destroy_args *args) +{ + return eigrpd_instance_redistribute_metrics_bandwidth_destroy(args); +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/delay + */ +static int lib_interface_eigrp_delay_modify(struct nb_cb_modify_args *args) +{ + struct eigrp_interface *ei; + struct interface *ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + + ei->params.delay = yang_dnode_get_uint32(args->dnode, NULL); + eigrp_if_reset(ifp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth + */ +static int lib_interface_eigrp_bandwidth_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct eigrp_interface *ei; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + + ei->params.bandwidth = yang_dnode_get_uint32(args->dnode, NULL); + eigrp_if_reset(ifp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval + */ +static int +lib_interface_eigrp_hello_interval_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct eigrp_interface *ei; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + + ei->params.v_hello = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time + */ +static int lib_interface_eigrp_hold_time_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct eigrp_interface *ei; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + ei = ifp->info; + if (ei == NULL) + return NB_ERR_INCONSISTENCY; + + ei->params.v_wait = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon + */ +static int +lib_interface_eigrp_split_horizon_modify(struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "split-horizon command not implemented yet"); + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance + */ +static int lib_interface_eigrp_instance_create(struct nb_cb_create_args *args) +{ + struct eigrp_interface *eif; + struct interface *ifp; + struct eigrp *eigrp; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp == NULL) { + /* + * XXX: we can't verify if the interface exists + * and is active until EIGRP is up. + */ + break; + } + + eigrp = eigrp_get(yang_dnode_get_uint16(args->dnode, "asn"), + ifp->vrf->vrf_id); + eif = eigrp_interface_lookup(eigrp, ifp->name); + if (eif == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + eigrp = eigrp_get(yang_dnode_get_uint16(args->dnode, "asn"), + ifp->vrf->vrf_id); + eif = eigrp_interface_lookup(eigrp, ifp->name); + if (eif == NULL) + return NB_ERR_INCONSISTENCY; + + nb_running_set_entry(args->dnode, eif); + break; + } + + return NB_OK; +} + +static int lib_interface_eigrp_instance_destroy(struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + nb_running_unset_entry(args->dnode); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses + */ +static int lib_interface_eigrp_instance_summarize_addresses_create( + struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "summary command not implemented yet"); + break; + } + + return NB_OK; +} + +static int lib_interface_eigrp_instance_summarize_addresses_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + /* TODO: Not implemented. */ + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + snprintf(args->errmsg, args->errmsg_len, + "no summary command not implemented yet"); + /* NOTHING */ + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication + */ +static int lib_interface_eigrp_instance_authentication_modify( + struct nb_cb_modify_args *args) +{ + struct eigrp_interface *eif; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eif = nb_running_get_entry(args->dnode, NULL, true); + eif->params.auth_type = yang_dnode_get_enum(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain + */ +static int +lib_interface_eigrp_instance_keychain_modify(struct nb_cb_modify_args *args) +{ + struct eigrp_interface *eif; + struct keychain *keychain; + + switch (args->event) { + case NB_EV_VALIDATE: + keychain = keychain_lookup( + yang_dnode_get_string(args->dnode, NULL)); + if (keychain == NULL) + return NB_ERR_INCONSISTENCY; + break; + case NB_EV_PREPARE: + args->resource->ptr = + strdup(yang_dnode_get_string(args->dnode, NULL)); + if (args->resource->ptr == NULL) + return NB_ERR_RESOURCE; + break; + case NB_EV_ABORT: + free(args->resource->ptr); + args->resource->ptr = NULL; + break; + case NB_EV_APPLY: + eif = nb_running_get_entry(args->dnode, NULL, true); + if (eif->params.auth_keychain) + free(eif->params.auth_keychain); + + eif->params.auth_keychain = args->resource->ptr; + break; + } + + return NB_OK; +} + +static int +lib_interface_eigrp_instance_keychain_destroy(struct nb_cb_destroy_args *args) +{ + struct eigrp_interface *eif; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + eif = nb_running_get_entry(args->dnode, NULL, true); + if (eif->params.auth_keychain) + free(eif->params.auth_keychain); + + eif->params.auth_keychain = NULL; + break; + } + + return NB_OK; +} + +/* clang-format off */ +const struct frr_yang_module_info frr_eigrpd_info = { + .name = "frr-eigrpd", + .nodes = { + { + .xpath = "/frr-eigrpd:eigrpd/instance", + .cbs = { + .create = eigrpd_instance_create, + .destroy = eigrpd_instance_destroy, + .cli_show = eigrp_cli_show_header, + .cli_show_end = eigrp_cli_show_end_header, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/router-id", + .cbs = { + .modify = eigrpd_instance_router_id_modify, + .destroy = eigrpd_instance_router_id_destroy, + .cli_show = eigrp_cli_show_router_id, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/passive-interface", + .cbs = { + .create = eigrpd_instance_passive_interface_create, + .destroy = eigrpd_instance_passive_interface_destroy, + .cli_show = eigrp_cli_show_passive_interface, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/active-time", + .cbs = { + .modify = eigrpd_instance_active_time_modify, + .cli_show = eigrp_cli_show_active_time, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/variance", + .cbs = { + .modify = eigrpd_instance_variance_modify, + .destroy = eigrpd_instance_variance_destroy, + .cli_show = eigrp_cli_show_variance, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/maximum-paths", + .cbs = { + .modify = eigrpd_instance_maximum_paths_modify, + .destroy = eigrpd_instance_maximum_paths_destroy, + .cli_show = eigrp_cli_show_maximum_paths, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights", + .cbs = { + .cli_show = eigrp_cli_show_metrics, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K1", + .cbs = { + .modify = eigrpd_instance_metric_weights_K1_modify, + .destroy = eigrpd_instance_metric_weights_K1_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K2", + .cbs = { + .modify = eigrpd_instance_metric_weights_K2_modify, + .destroy = eigrpd_instance_metric_weights_K2_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K3", + .cbs = { + .modify = eigrpd_instance_metric_weights_K3_modify, + .destroy = eigrpd_instance_metric_weights_K3_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K4", + .cbs = { + .modify = eigrpd_instance_metric_weights_K4_modify, + .destroy = eigrpd_instance_metric_weights_K4_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K5", + .cbs = { + .modify = eigrpd_instance_metric_weights_K5_modify, + .destroy = eigrpd_instance_metric_weights_K5_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/metric-weights/K6", + .cbs = { + .modify = eigrpd_instance_metric_weights_K6_modify, + .destroy = eigrpd_instance_metric_weights_K6_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/network", + .cbs = { + .create = eigrpd_instance_network_create, + .destroy = eigrpd_instance_network_destroy, + .cli_show = eigrp_cli_show_network, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/neighbor", + .cbs = { + .create = eigrpd_instance_neighbor_create, + .destroy = eigrpd_instance_neighbor_destroy, + .cli_show = eigrp_cli_show_neighbor, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/distribute-list", + .cbs = { + .create = eigrpd_instance_distribute_list_create, + .destroy = group_distribute_list_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/distribute-list/in/access-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + .cli_show = group_distribute_list_ipv4_cli_show, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/distribute-list/out/access-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + .cli_show = group_distribute_list_ipv4_cli_show, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/distribute-list/in/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + .cli_show = group_distribute_list_ipv4_cli_show, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/distribute-list/out/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + .cli_show = group_distribute_list_ipv4_cli_show, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute", + .cbs = { + .create = eigrpd_instance_redistribute_create, + .destroy = eigrpd_instance_redistribute_destroy, + .cli_show = eigrp_cli_show_redistribute, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/route-map", + .cbs = { + .modify = eigrpd_instance_redistribute_route_map_modify, + .destroy = eigrpd_instance_redistribute_route_map_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/bandwidth", + .cbs = { + .modify = eigrpd_instance_redistribute_metrics_bandwidth_modify, + .destroy = eigrpd_instance_redistribute_metrics_bandwidth_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/delay", + .cbs = { + .modify = eigrpd_instance_redistribute_metrics_delay_modify, + .destroy = eigrpd_instance_redistribute_metrics_delay_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/reliability", + .cbs = { + .modify = eigrpd_instance_redistribute_metrics_reliability_modify, + .destroy = eigrpd_instance_redistribute_metrics_reliability_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/load", + .cbs = { + .modify = eigrpd_instance_redistribute_metrics_load_modify, + .destroy = eigrpd_instance_redistribute_metrics_load_destroy, + } + }, + { + .xpath = "/frr-eigrpd:eigrpd/instance/redistribute/metrics/mtu", + .cbs = { + .modify = eigrpd_instance_redistribute_metrics_mtu_modify, + .destroy = eigrpd_instance_redistribute_metrics_mtu_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/delay", + .cbs = { + .modify = lib_interface_eigrp_delay_modify, + .cli_show = eigrp_cli_show_delay, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/bandwidth", + .cbs = { + .modify = lib_interface_eigrp_bandwidth_modify, + .cli_show = eigrp_cli_show_bandwidth, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/hello-interval", + .cbs = { + .modify = lib_interface_eigrp_hello_interval_modify, + .cli_show = eigrp_cli_show_hello_interval, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/hold-time", + .cbs = { + .modify = lib_interface_eigrp_hold_time_modify, + .cli_show = eigrp_cli_show_hold_time, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/split-horizon", + .cbs = { + .modify = lib_interface_eigrp_split_horizon_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance", + .cbs = { + .create = lib_interface_eigrp_instance_create, + .destroy = lib_interface_eigrp_instance_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/summarize-addresses", + .cbs = { + .create = lib_interface_eigrp_instance_summarize_addresses_create, + .destroy = lib_interface_eigrp_instance_summarize_addresses_destroy, + .cli_show = eigrp_cli_show_summarize_address, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/authentication", + .cbs = { + .modify = lib_interface_eigrp_instance_authentication_modify, + .cli_show = eigrp_cli_show_authentication, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-eigrpd:eigrp/instance/keychain", + .cbs = { + .modify = lib_interface_eigrp_instance_keychain_modify, + .destroy = lib_interface_eigrp_instance_keychain_destroy, + .cli_show = eigrp_cli_show_keychain, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/eigrpd/eigrp_packet.c b/eigrpd/eigrp_packet.c new file mode 100644 index 0000000..963d229 --- /dev/null +++ b/eigrpd/eigrp_packet.c @@ -0,0 +1,1333 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP General Sending and Receiving of EIGRP Packets. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "vty.h" +#include "keychain.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "sha256.h" +#include "lib_errors.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_errors.h" + +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_FIFO, "EIGRP FIFO"); +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_PACKET, "EIGRP Packet"); +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_IPV4_INT_TLV, "EIGRP IPv4 TLV"); +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_SEQ_TLV, "EIGRP SEQ TLV"); +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_AUTH_TLV, "EIGRP AUTH TLV"); +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_AUTH_SHA256_TLV, "EIGRP SHA TLV"); + +/* Packet Type String. */ +const struct message eigrp_packet_type_str[] = { + {EIGRP_OPC_UPDATE, "Update"}, + {EIGRP_OPC_REQUEST, "Request"}, + {EIGRP_OPC_QUERY, "Query"}, + {EIGRP_OPC_REPLY, "Reply"}, + {EIGRP_OPC_HELLO, "Hello"}, + {EIGRP_OPC_IPXSAP, "IPX-SAP"}, + {EIGRP_OPC_PROBE, "Probe"}, + {EIGRP_OPC_ACK, "Ack"}, + {EIGRP_OPC_SIAQUERY, "SIAQuery"}, + {EIGRP_OPC_SIAREPLY, "SIAReply"}, + {0}}; + + +static unsigned char zeropad[16] = {0}; + +/* Forward function reference*/ +static struct stream *eigrp_recv_packet(struct eigrp *eigrp, int fd, + struct interface **ifp, + struct stream *s); +static int eigrp_verify_header(struct stream *s, struct eigrp_interface *ei, + struct ip *addr, struct eigrp_header *header); +static int eigrp_check_network_mask(struct eigrp_interface *ei, + struct in_addr mask); + +static int eigrp_retrans_count_exceeded(struct eigrp_packet *ep, + struct eigrp_neighbor *nbr) +{ + return 1; +} + +int eigrp_make_md5_digest(struct eigrp_interface *ei, struct stream *s, + uint8_t flags) +{ + struct key *key = NULL; + struct keychain *keychain; + + unsigned char digest[EIGRP_AUTH_TYPE_MD5_LEN]; + MD5_CTX ctx; + uint8_t *ibuf; + size_t backup_get, backup_end; + struct TLV_MD5_Authentication_Type *auth_TLV; + + ibuf = s->data; + backup_end = s->endp; + backup_get = s->getp; + + auth_TLV = eigrp_authTLV_MD5_new(); + + stream_set_getp(s, EIGRP_HEADER_LEN); + stream_get(auth_TLV, s, EIGRP_AUTH_MD5_TLV_SIZE); + stream_set_getp(s, backup_get); + + keychain = keychain_lookup(ei->params.auth_keychain); + if (keychain) + key = key_lookup_for_send(keychain); + else { + eigrp_authTLV_MD5_free(auth_TLV); + return EIGRP_AUTH_TYPE_NONE; + } + + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + + /* Generate a digest. Each situation needs different handling */ + if (flags & EIGRP_AUTH_BASIC_HELLO_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); + MD5Update(&ctx, key->string, strlen(key->string)); + if (strlen(key->string) < 16) + MD5Update(&ctx, zeropad, 16 - strlen(key->string)); + } else if (flags & EIGRP_AUTH_UPDATE_INIT_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_UPDATE_INIT_COMPUTE); + } else if (flags & EIGRP_AUTH_UPDATE_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); + MD5Update(&ctx, key->string, strlen(key->string)); + if (strlen(key->string) < 16) + MD5Update(&ctx, zeropad, 16 - strlen(key->string)); + if (backup_end > (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)) { + MD5Update(&ctx, + ibuf + (EIGRP_HEADER_LEN + + EIGRP_AUTH_MD5_TLV_SIZE), + backup_end - 20 + - (EIGRP_HEADER_LEN + + EIGRP_AUTH_MD5_TLV_SIZE)); + } + } + + MD5Final(digest, &ctx); + + /* Append md5 digest to the end of the stream. */ + memcpy(auth_TLV->digest, digest, EIGRP_AUTH_TYPE_MD5_LEN); + + stream_set_endp(s, EIGRP_HEADER_LEN); + stream_put(s, auth_TLV, EIGRP_AUTH_MD5_TLV_SIZE); + stream_set_endp(s, backup_end); + + eigrp_authTLV_MD5_free(auth_TLV); + return EIGRP_AUTH_TYPE_MD5_LEN; +} + +int eigrp_check_md5_digest(struct stream *s, + struct TLV_MD5_Authentication_Type *authTLV, + struct eigrp_neighbor *nbr, uint8_t flags) +{ + MD5_CTX ctx; + unsigned char digest[EIGRP_AUTH_TYPE_MD5_LEN]; + unsigned char orig[EIGRP_AUTH_TYPE_MD5_LEN]; + struct key *key = NULL; + struct keychain *keychain; + uint8_t *ibuf; + size_t backup_end; + struct TLV_MD5_Authentication_Type *auth_TLV; + struct eigrp_header *eigrph; + + if (ntohl(nbr->crypt_seqnum) > ntohl(authTLV->key_sequence)) { + zlog_warn( + "interface %s: eigrp_check_md5 bad sequence %d (expect %d)", + IF_NAME(nbr->ei), ntohl(authTLV->key_sequence), + ntohl(nbr->crypt_seqnum)); + return 0; + } + + eigrph = (struct eigrp_header *)s->data; + eigrph->checksum = 0; + + auth_TLV = (struct TLV_MD5_Authentication_Type *)(s->data + + EIGRP_HEADER_LEN); + memcpy(orig, auth_TLV->digest, EIGRP_AUTH_TYPE_MD5_LEN); + memset(digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); + memset(auth_TLV->digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); + + ibuf = s->data; + backup_end = s->endp; + + keychain = keychain_lookup(nbr->ei->params.auth_keychain); + if (keychain) + key = key_lookup_for_send(keychain); + + if (!key) { + zlog_warn( + "Interface %s: Expected key value not found in config", + nbr->ei->ifp->name); + return 0; + } + + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + + /* Generate a digest. Each situation needs different handling */ + if (flags & EIGRP_AUTH_BASIC_HELLO_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); + MD5Update(&ctx, key->string, strlen(key->string)); + if (strlen(key->string) < 16) + MD5Update(&ctx, zeropad, 16 - strlen(key->string)); + } else if (flags & EIGRP_AUTH_UPDATE_INIT_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_UPDATE_INIT_COMPUTE); + } else if (flags & EIGRP_AUTH_UPDATE_FLAG) { + MD5Update(&ctx, ibuf, EIGRP_MD5_BASIC_COMPUTE); + MD5Update(&ctx, key->string, strlen(key->string)); + if (strlen(key->string) < 16) + MD5Update(&ctx, zeropad, 16 - strlen(key->string)); + if (backup_end > (EIGRP_HEADER_LEN + EIGRP_AUTH_MD5_TLV_SIZE)) { + MD5Update(&ctx, + ibuf + (EIGRP_HEADER_LEN + + EIGRP_AUTH_MD5_TLV_SIZE), + backup_end - 20 + - (EIGRP_HEADER_LEN + + EIGRP_AUTH_MD5_TLV_SIZE)); + } + } + + MD5Final(digest, &ctx); + + /* compare the two */ + if (memcmp(orig, digest, EIGRP_AUTH_TYPE_MD5_LEN) != 0) { + zlog_warn("interface %s: eigrp_check_md5 checksum mismatch", + IF_NAME(nbr->ei)); + return 0; + } + + /* save neighbor's crypt_seqnum */ + nbr->crypt_seqnum = authTLV->key_sequence; + + return 1; +} + +int eigrp_make_sha256_digest(struct eigrp_interface *ei, struct stream *s, + uint8_t flags) +{ + struct key *key = NULL; + struct keychain *keychain; + char source_ip[PREFIX_STRLEN]; + + unsigned char digest[EIGRP_AUTH_TYPE_SHA256_LEN]; + unsigned char buffer[1 + PLAINTEXT_LENGTH + 45 + 1] = {0}; + + HMAC_SHA256_CTX ctx; + void *ibuf; + size_t backup_get, backup_end; + struct TLV_SHA256_Authentication_Type *auth_TLV; + + ibuf = s->data; + backup_end = s->endp; + backup_get = s->getp; + + auth_TLV = eigrp_authTLV_SHA256_new(); + + stream_set_getp(s, EIGRP_HEADER_LEN); + stream_get(auth_TLV, s, EIGRP_AUTH_SHA256_TLV_SIZE); + stream_set_getp(s, backup_get); + + keychain = keychain_lookup(ei->params.auth_keychain); + if (keychain) + key = key_lookup_for_send(keychain); + + if (!key) { + zlog_warn( + "Interface %s: Expected key value not found in config", + ei->ifp->name); + eigrp_authTLV_SHA256_free(auth_TLV); + return 0; + } + + inet_ntop(AF_INET, &ei->address.u.prefix4, source_ip, PREFIX_STRLEN); + + memset(&ctx, 0, sizeof(ctx)); + buffer[0] = '\n'; + memcpy(buffer + 1, key, strlen(key->string)); + memcpy(buffer + 1 + strlen(key->string), source_ip, strlen(source_ip)); + HMAC__SHA256_Init(&ctx, buffer, + 1 + strlen(key->string) + strlen(source_ip)); + HMAC__SHA256_Update(&ctx, ibuf, strlen(ibuf)); + HMAC__SHA256_Final(digest, &ctx); + + + /* Put hmac-sha256 digest to it's place */ + memcpy(auth_TLV->digest, digest, EIGRP_AUTH_TYPE_SHA256_LEN); + + stream_set_endp(s, EIGRP_HEADER_LEN); + stream_put(s, auth_TLV, EIGRP_AUTH_SHA256_TLV_SIZE); + stream_set_endp(s, backup_end); + + eigrp_authTLV_SHA256_free(auth_TLV); + + return EIGRP_AUTH_TYPE_SHA256_LEN; +} + +int eigrp_check_sha256_digest(struct stream *s, + struct TLV_SHA256_Authentication_Type *authTLV, + struct eigrp_neighbor *nbr, uint8_t flags) +{ + return 1; +} + +void eigrp_write(struct event *thread) +{ + struct eigrp *eigrp = EVENT_ARG(thread); + struct eigrp_header *eigrph; + struct eigrp_interface *ei; + struct eigrp_packet *ep; + struct sockaddr_in sa_dst; + struct ip iph; + struct msghdr msg; + struct iovec iov[2]; + uint32_t seqno, ack; + + int ret; + int flags = 0; + struct listnode *node; +#ifdef WANT_EIGRP_WRITE_FRAGMENT + static uint16_t ipid = 0; +#endif /* WANT_EIGRP_WRITE_FRAGMENT */ +#define EIGRP_WRITE_IPHL_SHIFT 2 + + node = listhead(eigrp->oi_write_q); + assert(node); + ei = listgetdata(node); + assert(ei); + +#ifdef WANT_EIGRP_WRITE_FRAGMENT + /* seed ipid static with low order bits of time */ + if (ipid == 0) + ipid = (time(NULL) & 0xffff); +#endif /* WANT_EIGRP_WRITE_FRAGMENT */ + + /* Get one packet from queue. */ + ep = eigrp_fifo_next(ei->obuf); + if (!ep) { + flog_err(EC_LIB_DEVELOPMENT, + "%s: Interface %s no packet on queue?", __func__, + ei->ifp->name); + goto out; + } + if (ep->length < EIGRP_HEADER_LEN) { + flog_err(EC_EIGRP_PACKET, "%s: Packet just has a header?", + __func__); + eigrp_header_dump((struct eigrp_header *)ep->s->data); + eigrp_packet_delete(ei); + goto out; + } + + if (ep->dst.s_addr == htonl(EIGRP_MULTICAST_ADDRESS)) + eigrp_if_ipmulticast(eigrp, &ei->address, ei->ifp->ifindex); + + memset(&iph, 0, sizeof(iph)); + memset(&sa_dst, 0, sizeof(sa_dst)); + + /* + * We build and schedule packets to go out + * in the future. In the mean time we may + * process some update packets from the + * neighbor, thus making it necessary + * to update the ack we are using for + * this outgoing packet. + */ + eigrph = (struct eigrp_header *)STREAM_DATA(ep->s); + seqno = ntohl(eigrph->sequence); + ack = ntohl(eigrph->ack); + if (ep->nbr && (ack != ep->nbr->recv_sequence_number)) { + eigrph->ack = htonl(ep->nbr->recv_sequence_number); + ack = ep->nbr->recv_sequence_number; + eigrph->checksum = 0; + eigrp_packet_checksum(ei, ep->s, ep->length); + } + + sa_dst.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_dst.sin_len = sizeof(sa_dst); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + sa_dst.sin_addr = ep->dst; + sa_dst.sin_port = htons(0); + + /* Set DONTROUTE flag if dst is unicast. */ + if (!IN_MULTICAST(htonl(ep->dst.s_addr))) + flags = MSG_DONTROUTE; + + iph.ip_hl = sizeof(struct ip) >> EIGRP_WRITE_IPHL_SHIFT; + /* it'd be very strange for header to not be 4byte-word aligned but.. */ + if (sizeof(struct ip) + > (unsigned int)(iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT)) + iph.ip_hl++; /* we presume sizeof struct ip cant overflow + ip_hl.. */ + + iph.ip_v = IPVERSION; + iph.ip_tos = IPTOS_PREC_INTERNETCONTROL; + iph.ip_len = (iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT) + ep->length; + +#if defined(__DragonFly__) + /* + * DragonFly's raw socket expects ip_len/ip_off in network byte order. + */ + iph.ip_len = htons(iph.ip_len); +#endif + + iph.ip_off = 0; + iph.ip_ttl = EIGRP_IP_TTL; + iph.ip_p = IPPROTO_EIGRPIGP; + iph.ip_sum = 0; + iph.ip_src.s_addr = ei->address.u.prefix4.s_addr; + iph.ip_dst.s_addr = ep->dst.s_addr; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (caddr_t)&sa_dst; + msg.msg_namelen = sizeof(sa_dst); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + iov[0].iov_base = (char *)&iph; + iov[0].iov_len = iph.ip_hl << EIGRP_WRITE_IPHL_SHIFT; + iov[1].iov_base = stream_pnt(ep->s); + iov[1].iov_len = ep->length; + + /* send final fragment (could be first) */ + sockopt_iphdrincl_swab_htosys(&iph); + ret = sendmsg(eigrp->fd, &msg, flags); + sockopt_iphdrincl_swab_systoh(&iph); + + if (IS_DEBUG_EIGRP_TRANSMIT(0, SEND)) { + eigrph = (struct eigrp_header *)STREAM_DATA(ep->s); + zlog_debug( + "Sending [%s][%d/%d] to [%pI4] via [%s] ret [%d].", + lookup_msg(eigrp_packet_type_str, eigrph->opcode, NULL), + seqno, ack, &ep->dst, IF_NAME(ei), ret); + } + + if (ret < 0) + zlog_warn( + "*** sendmsg in eigrp_write failed to %pI4, id %d, off %d, len %d, interface %s, mtu %u: %s", + &iph.ip_dst, iph.ip_id, iph.ip_off, iph.ip_len, + ei->ifp->name, ei->ifp->mtu, safe_strerror(errno)); + + /* Now delete packet from queue. */ + eigrp_packet_delete(ei); + +out: + if (eigrp_fifo_next(ei->obuf) == NULL) { + ei->on_write_q = 0; + list_delete_node(eigrp->oi_write_q, node); + } + + /* If packets still remain in queue, call write thread. */ + if (!list_isempty(eigrp->oi_write_q)) { + event_add_write(master, eigrp_write, eigrp, eigrp->fd, + &eigrp->t_write); + } +} + +/* Starting point of packet process function. */ +void eigrp_read(struct event *thread) +{ + int ret; + struct stream *ibuf; + struct eigrp *eigrp; + struct eigrp_interface *ei; + struct ip *iph; + struct eigrp_header *eigrph; + struct interface *ifp; + struct eigrp_neighbor *nbr; + struct in_addr srcaddr; + uint16_t opcode = 0; + uint16_t length = 0; + + /* first of all get interface pointer. */ + eigrp = EVENT_ARG(thread); + + /* prepare for next packet. */ + event_add_read(master, eigrp_read, eigrp, eigrp->fd, &eigrp->t_read); + + stream_reset(eigrp->ibuf); + if (!(ibuf = eigrp_recv_packet(eigrp, eigrp->fd, &ifp, eigrp->ibuf))) { + /* This raw packet is known to be at least as big as its IP + * header. */ + return; + } + + /* Note that there should not be alignment problems with this assignment + because this is at the beginning of the stream data buffer. */ + iph = (struct ip *)STREAM_DATA(ibuf); + + // Substract IPv4 header size from EIGRP Packet itself + if (iph->ip_v == 4) + length = (iph->ip_len) - 20U; + + srcaddr = iph->ip_src; + + /* IP Header dump. */ + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV) + && IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) + eigrp_ip_header_dump(iph); + + /* Note that sockopt_iphdrincl_swab_systoh was called in + * eigrp_recv_packet. */ + if (ifp == NULL) { + struct connected *c; + /* Handle cases where the platform does not support retrieving + the ifindex, + and also platforms (such as Solaris 8) that claim to support + ifindex + retrieval but do not. */ + c = if_lookup_address((void *)&srcaddr, AF_INET, + eigrp->vrf_id); + + if (c == NULL) + return; + + ifp = c->ifp; + } + + /* associate packet with eigrp interface */ + ei = ifp->info; + + /* eigrp_verify_header() relies on a valid "ei" and thus can be called + only + after the checks below are passed. These checks in turn access the + fields of unverified "eigrph" structure for their own purposes and + must remain very accurate in doing this. + */ + if (!ei) + return; + + /* Self-originated packet should be discarded silently. */ + if (eigrp_if_lookup_by_local_addr(eigrp, NULL, iph->ip_src) + || (IPV4_ADDR_SAME(&srcaddr, &ei->address.u.prefix4))) { + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_debug( + "eigrp_read[%pI4]: Dropping self-originated packet", + &srcaddr); + return; + } + + /* Advance from IP header to EIGRP header (iph->ip_hl has been verified + by eigrp_recv_packet() to be correct). */ + + stream_forward_getp(ibuf, (iph->ip_hl * 4)); + eigrph = (struct eigrp_header *)stream_pnt(ibuf); + + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV) + && IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) + eigrp_header_dump(eigrph); + + if (ntohs(eigrph->ASNumber) != eigrp->AS) { + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_debug( + "ignoring packet from router %u sent to %pI4, wrong AS Number received: %u", + ntohs(eigrph->vrid), &iph->ip_dst, + ntohs(eigrph->ASNumber)); + return; + } + + /* If incoming interface is passive one, ignore it. */ + if (eigrp_if_is_passive(ei)) { + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_debug( + "ignoring packet from router %u sent to %pI4, received on a passive interface, %pI4", + ntohs(eigrph->vrid), &iph->ip_dst, + &ei->address.u.prefix4); + + if (iph->ip_dst.s_addr == htonl(EIGRP_MULTICAST_ADDRESS)) { + eigrp_if_set_multicast(ei); + } + return; + } + + /* else it must be a local eigrp interface, check it was received on + * correct link + */ + else if (ei->ifp != ifp) { + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_warn( + "Packet from [%pI4] received on wrong link %s", + &iph->ip_src, ifp->name); + return; + } + + /* Verify more EIGRP header fields. */ + ret = eigrp_verify_header(ibuf, ei, iph, eigrph); + if (ret < 0) { + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_debug( + "eigrp_read[%pI4]: Header check failed, dropping.", + &iph->ip_src); + return; + } + + /* calcualte the eigrp packet length, and move the pounter to the + start of the eigrp TLVs */ + opcode = eigrph->opcode; + + if (IS_DEBUG_EIGRP_TRANSMIT(0, RECV)) + zlog_debug( + "Received [%s][%d/%d] length [%u] via [%s] src [%pI4] dst [%pI4]", + lookup_msg(eigrp_packet_type_str, opcode, NULL), + ntohl(eigrph->sequence), ntohl(eigrph->ack), length, + IF_NAME(ei), &iph->ip_src, &iph->ip_dst); + + /* Read rest of the packet and call each sort of packet routine. */ + stream_forward_getp(ibuf, EIGRP_HEADER_LEN); + + /* New testing block of code for handling Acks */ + if (ntohl(eigrph->ack) != 0) { + struct eigrp_packet *ep = NULL; + + nbr = eigrp_nbr_get(ei, eigrph, iph); + + // neighbor must be valid, eigrp_nbr_get creates if none existed + assert(nbr); + + ep = eigrp_fifo_next(nbr->retrans_queue); + if ((ep) && (ntohl(eigrph->ack) == ep->sequence_number)) { + ep = eigrp_fifo_pop(nbr->retrans_queue); + eigrp_packet_free(ep); + + if ((nbr->state == EIGRP_NEIGHBOR_PENDING) + && (ntohl(eigrph->ack) + == nbr->init_sequence_number)) { + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_UP); + zlog_info( + "Neighbor(%pI4) adjacency became full", + &nbr->src); + nbr->init_sequence_number = 0; + nbr->recv_sequence_number = + ntohl(eigrph->sequence); + eigrp_update_send_EOT(nbr); + } else + eigrp_send_packet_reliably(nbr); + } + ep = eigrp_fifo_next(nbr->multicast_queue); + if (ep) { + if (ntohl(eigrph->ack) == ep->sequence_number) { + ep = eigrp_fifo_pop(nbr->multicast_queue); + eigrp_packet_free(ep); + if (nbr->multicast_queue->count > 0) { + eigrp_send_packet_reliably(nbr); + } + } + } + } + + + switch (opcode) { + case EIGRP_OPC_HELLO: + eigrp_hello_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + case EIGRP_OPC_PROBE: + // eigrp_probe_receive(eigrp, iph, eigrph, ibuf, ei, + // length); + break; + case EIGRP_OPC_QUERY: + eigrp_query_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + case EIGRP_OPC_REPLY: + eigrp_reply_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + case EIGRP_OPC_REQUEST: + // eigrp_request_receive(eigrp, iph, eigrph, ibuf, ei, + // length); + break; + case EIGRP_OPC_SIAQUERY: + eigrp_query_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + case EIGRP_OPC_SIAREPLY: + eigrp_reply_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + case EIGRP_OPC_UPDATE: + eigrp_update_receive(eigrp, iph, eigrph, ibuf, ei, length); + break; + default: + zlog_warn( + "interface %s: EIGRP packet header type %d unsupported", + IF_NAME(ei), opcode); + break; + } +} + +static struct stream *eigrp_recv_packet(struct eigrp *eigrp, + int fd, struct interface **ifp, + struct stream *ibuf) +{ + int ret; + struct ip *iph; + uint16_t ip_len; + unsigned int ifindex = 0; + struct iovec iov; + /* Header and data both require alignment. */ + char buff[CMSG_SPACE(SOPT_SIZE_CMSG_IFINDEX_IPV4())]; + struct msghdr msgh; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = (caddr_t)buff; + msgh.msg_controllen = sizeof(buff); + + ret = stream_recvmsg(ibuf, fd, &msgh, 0, (EIGRP_PACKET_MAX_LEN + 1)); + if (ret < 0) { + zlog_warn("stream_recvmsg failed: %s", safe_strerror(errno)); + return NULL; + } + if ((unsigned int)ret < sizeof(*iph)) /* ret must be > 0 now */ + { + zlog_warn( + "%s: discarding runt packet of length %d (ip header size is %u)", + __func__, ret, (unsigned int)sizeof(*iph)); + return NULL; + } + + /* Note that there should not be alignment problems with this assignment + because this is at the beginning of the stream data buffer. */ + iph = (struct ip *)STREAM_DATA(ibuf); + sockopt_iphdrincl_swab_systoh(iph); + + ip_len = iph->ip_len; + +#if defined(__FreeBSD__) && (__FreeBSD_version < 1000000) + /* + * Kernel network code touches incoming IP header parameters, + * before protocol specific processing. + * + * 1) Convert byteorder to host representation. + * --> ip_len, ip_id, ip_off + * + * 2) Adjust ip_len to strip IP header size! + * --> If user process receives entire IP packet via RAW + * socket, it must consider adding IP header size to + * the "ip_len" field of "ip" structure. + * + * For more details, see . + */ + ip_len = ip_len + (iph->ip_hl << 2); +#endif + +#if defined(__DragonFly__) + /* + * in DragonFly's raw socket, ip_len/ip_off are read + * in network byte order. + * As OpenBSD < 200311 adjust ip_len to strip IP header size! + */ + ip_len = ntohs(iph->ip_len) + (iph->ip_hl << 2); +#endif + + ifindex = getsockopt_ifindex(AF_INET, &msgh); + + *ifp = if_lookup_by_index(ifindex, eigrp->vrf_id); + + if (ret != ip_len) { + zlog_warn( + "%s read length mismatch: ip_len is %d, but recvmsg returned %d", + __func__, ip_len, ret); + return NULL; + } + + return ibuf; +} + +struct eigrp_fifo *eigrp_fifo_new(void) +{ + struct eigrp_fifo *new; + + new = XCALLOC(MTYPE_EIGRP_FIFO, sizeof(struct eigrp_fifo)); + return new; +} + +/* Free eigrp packet fifo. */ +void eigrp_fifo_free(struct eigrp_fifo *fifo) +{ + struct eigrp_packet *ep; + struct eigrp_packet *next; + + for (ep = fifo->head; ep; ep = next) { + next = ep->next; + eigrp_packet_free(ep); + } + fifo->head = fifo->tail = NULL; + fifo->count = 0; + + XFREE(MTYPE_EIGRP_FIFO, fifo); +} + +/* Free eigrp fifo entries without destroying fifo itself*/ +void eigrp_fifo_reset(struct eigrp_fifo *fifo) +{ + struct eigrp_packet *ep; + struct eigrp_packet *next; + + for (ep = fifo->head; ep; ep = next) { + next = ep->next; + eigrp_packet_free(ep); + } + fifo->head = fifo->tail = NULL; + fifo->count = 0; +} + +struct eigrp_packet *eigrp_packet_new(size_t size, struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *new; + + new = XCALLOC(MTYPE_EIGRP_PACKET, sizeof(struct eigrp_packet)); + new->s = stream_new(size); + new->retrans_counter = 0; + new->nbr = nbr; + + return new; +} + +void eigrp_send_packet_reliably(struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *ep; + + ep = eigrp_fifo_next(nbr->retrans_queue); + + if (ep) { + struct eigrp_packet *duplicate; + duplicate = eigrp_packet_duplicate(ep, nbr); + /* Add packet to the top of the interface output queue*/ + eigrp_fifo_push(nbr->ei->obuf, duplicate); + + /*Start retransmission timer*/ + event_add_timer(master, eigrp_unack_packet_retrans, nbr, + EIGRP_PACKET_RETRANS_TIME, + &ep->t_retrans_timer); + + /*Increment sequence number counter*/ + nbr->ei->eigrp->sequence_number++; + + /* Hook thread to write packet. */ + if (nbr->ei->on_write_q == 0) { + listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); + nbr->ei->on_write_q = 1; + } + event_add_write(master, eigrp_write, nbr->ei->eigrp, + nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); + } +} + +/* Calculate EIGRP checksum */ +void eigrp_packet_checksum(struct eigrp_interface *ei, struct stream *s, + uint16_t length) +{ + struct eigrp_header *eigrph; + + eigrph = (struct eigrp_header *)STREAM_DATA(s); + + /* Calculate checksum. */ + eigrph->checksum = in_cksum(eigrph, length); +} + +/* Make EIGRP header. */ +void eigrp_packet_header_init(int type, struct eigrp *eigrp, struct stream *s, + uint32_t flags, uint32_t sequence, uint32_t ack) +{ + struct eigrp_header *eigrph; + + stream_reset(s); + eigrph = (struct eigrp_header *)STREAM_DATA(s); + + eigrph->version = (uint8_t)EIGRP_HEADER_VERSION; + eigrph->opcode = (uint8_t)type; + eigrph->checksum = 0; + + eigrph->vrid = htons(eigrp->vrid); + eigrph->ASNumber = htons(eigrp->AS); + eigrph->ack = htonl(ack); + eigrph->sequence = htonl(sequence); + // if(flags == EIGRP_INIT_FLAG) + // eigrph->sequence = htonl(3); + eigrph->flags = htonl(flags); + + if (IS_DEBUG_EIGRP_TRANSMIT(0, PACKET_DETAIL)) + zlog_debug("Packet Header Init Seq [%u] Ack [%u]", + htonl(eigrph->sequence), htonl(eigrph->ack)); + + stream_forward_endp(s, EIGRP_HEADER_LEN); +} + +/* Add new packet to head of fifo. */ +void eigrp_fifo_push(struct eigrp_fifo *fifo, struct eigrp_packet *ep) +{ + ep->next = fifo->head; + ep->previous = NULL; + + if (fifo->tail == NULL) + fifo->tail = ep; + + if (fifo->count != 0) + fifo->head->previous = ep; + + fifo->head = ep; + + fifo->count++; +} + +/* Return last fifo entry. */ +struct eigrp_packet *eigrp_fifo_next(struct eigrp_fifo *fifo) +{ + return fifo->tail; +} + +void eigrp_packet_delete(struct eigrp_interface *ei) +{ + struct eigrp_packet *ep; + + ep = eigrp_fifo_pop(ei->obuf); + + if (ep) + eigrp_packet_free(ep); +} + +void eigrp_packet_free(struct eigrp_packet *ep) +{ + if (ep->s) + stream_free(ep->s); + + EVENT_OFF(ep->t_retrans_timer); + + XFREE(MTYPE_EIGRP_PACKET, ep); +} + +/* EIGRP Header verification. */ +static int eigrp_verify_header(struct stream *ibuf, struct eigrp_interface *ei, + struct ip *iph, struct eigrp_header *eigrph) +{ + /* Check network mask, Silently discarded. */ + if (!eigrp_check_network_mask(ei, iph->ip_src)) { + zlog_warn( + "interface %s: eigrp_read network address is not same [%pI4]", + IF_NAME(ei), &iph->ip_src); + return -1; + } + // + // /* Check authentication. The function handles logging actions, where + // required. */ + // if (! eigrp_check_auth(ei, eigrph)) + // return -1; + + return 0; +} + +/* Unbound socket will accept any Raw IP packets if proto is matched. + To prevent it, compare src IP address and i/f address with masking + i/f network mask. */ +static int eigrp_check_network_mask(struct eigrp_interface *ei, + struct in_addr ip_src) +{ + struct in_addr mask, me, him; + + if (ei->type == EIGRP_IFTYPE_POINTOPOINT) + return 1; + + masklen2ip(ei->address.prefixlen, &mask); + + me.s_addr = ei->address.u.prefix4.s_addr & mask.s_addr; + him.s_addr = ip_src.s_addr & mask.s_addr; + + if (IPV4_ADDR_SAME(&me, &him)) + return 1; + + return 0; +} + +void eigrp_unack_packet_retrans(struct event *thread) +{ + struct eigrp_neighbor *nbr; + nbr = (struct eigrp_neighbor *)EVENT_ARG(thread); + + struct eigrp_packet *ep; + ep = eigrp_fifo_next(nbr->retrans_queue); + + if (ep) { + struct eigrp_packet *duplicate; + duplicate = eigrp_packet_duplicate(ep, nbr); + + /* Add packet to the top of the interface output queue*/ + eigrp_fifo_push(nbr->ei->obuf, duplicate); + + ep->retrans_counter++; + if (ep->retrans_counter == EIGRP_PACKET_RETRANS_MAX) { + eigrp_retrans_count_exceeded(ep, nbr); + return; + } + + /*Start retransmission timer*/ + event_add_timer(master, eigrp_unack_packet_retrans, nbr, + EIGRP_PACKET_RETRANS_TIME, + &ep->t_retrans_timer); + + /* Hook thread to write packet. */ + if (nbr->ei->on_write_q == 0) { + listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); + nbr->ei->on_write_q = 1; + } + event_add_write(master, eigrp_write, nbr->ei->eigrp, + nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); + } +} + +void eigrp_unack_multicast_packet_retrans(struct event *thread) +{ + struct eigrp_neighbor *nbr; + nbr = (struct eigrp_neighbor *)EVENT_ARG(thread); + + struct eigrp_packet *ep; + ep = eigrp_fifo_next(nbr->multicast_queue); + + if (ep) { + struct eigrp_packet *duplicate; + duplicate = eigrp_packet_duplicate(ep, nbr); + /* Add packet to the top of the interface output queue*/ + eigrp_fifo_push(nbr->ei->obuf, duplicate); + + ep->retrans_counter++; + if (ep->retrans_counter == EIGRP_PACKET_RETRANS_MAX) { + eigrp_retrans_count_exceeded(ep, nbr); + return; + } + + /*Start retransmission timer*/ + event_add_timer(master, eigrp_unack_multicast_packet_retrans, + nbr, EIGRP_PACKET_RETRANS_TIME, + &ep->t_retrans_timer); + + /* Hook thread to write packet. */ + if (nbr->ei->on_write_q == 0) { + listnode_add(nbr->ei->eigrp->oi_write_q, nbr->ei); + nbr->ei->on_write_q = 1; + } + event_add_write(master, eigrp_write, nbr->ei->eigrp, + nbr->ei->eigrp->fd, &nbr->ei->eigrp->t_write); + } +} + +/* Get packet from tail of fifo. */ +struct eigrp_packet *eigrp_fifo_pop(struct eigrp_fifo *fifo) +{ + struct eigrp_packet *ep = NULL; + + ep = fifo->tail; + + if (ep) { + fifo->tail = ep->previous; + + if (fifo->tail == NULL) + fifo->head = NULL; + else + fifo->tail->next = NULL; + + fifo->count--; + } + + return ep; +} + +struct eigrp_packet *eigrp_packet_duplicate(struct eigrp_packet *old, + struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *new; + + new = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); + new->length = old->length; + new->retrans_counter = old->retrans_counter; + new->dst = old->dst; + new->sequence_number = old->sequence_number; + stream_copy(new->s, old->s); + + return new; +} + +static struct TLV_IPv4_Internal_type *eigrp_IPv4_InternalTLV_new(void) +{ + struct TLV_IPv4_Internal_type *new; + + new = XCALLOC(MTYPE_EIGRP_IPV4_INT_TLV, + sizeof(struct TLV_IPv4_Internal_type)); + + return new; +} + +struct TLV_IPv4_Internal_type *eigrp_read_ipv4_tlv(struct stream *s) +{ + struct TLV_IPv4_Internal_type *tlv; + uint32_t destination_tmp; + + tlv = eigrp_IPv4_InternalTLV_new(); + + tlv->type = stream_getw(s); + tlv->length = stream_getw(s); + tlv->forward.s_addr = stream_getl(s); + tlv->metric.delay = stream_getl(s); + tlv->metric.bandwidth = stream_getl(s); + tlv->metric.mtu[0] = stream_getc(s); + tlv->metric.mtu[1] = stream_getc(s); + tlv->metric.mtu[2] = stream_getc(s); + tlv->metric.hop_count = stream_getc(s); + tlv->metric.reliability = stream_getc(s); + tlv->metric.load = stream_getc(s); + tlv->metric.tag = stream_getc(s); + tlv->metric.flags = stream_getc(s); + + tlv->prefix_length = stream_getc(s); + + destination_tmp = stream_getc(s) << 24; + if (tlv->prefix_length > 8) + destination_tmp |= stream_getc(s) << 16; + if (tlv->prefix_length > 16) + destination_tmp |= stream_getc(s) << 8; + if (tlv->prefix_length > 24) + destination_tmp |= stream_getc(s); + + tlv->destination.s_addr = htonl(destination_tmp); + + return tlv; +} + +uint16_t eigrp_add_internalTLV_to_stream(struct stream *s, + struct eigrp_prefix_descriptor *pe) +{ + uint16_t length; + + stream_putw(s, EIGRP_TLV_IPv4_INT); + switch (pe->destination->prefixlen) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + length = EIGRP_TLV_IPV4_SIZE_GRT_0_BIT; + stream_putw(s, length); + break; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + length = EIGRP_TLV_IPV4_SIZE_GRT_8_BIT; + stream_putw(s, length); + break; + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + length = EIGRP_TLV_IPV4_SIZE_GRT_16_BIT; + stream_putw(s, length); + break; + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + case 32: + length = EIGRP_TLV_IPV4_SIZE_GRT_24_BIT; + stream_putw(s, length); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: Unexpected prefix length: %d", + __func__, pe->destination->prefixlen); + return 0; + } + stream_putl(s, 0x00000000); + + /*Metric*/ + stream_putl(s, pe->reported_metric.delay); + stream_putl(s, pe->reported_metric.bandwidth); + stream_putc(s, pe->reported_metric.mtu[2]); + stream_putc(s, pe->reported_metric.mtu[1]); + stream_putc(s, pe->reported_metric.mtu[0]); + stream_putc(s, pe->reported_metric.hop_count); + stream_putc(s, pe->reported_metric.reliability); + stream_putc(s, pe->reported_metric.load); + stream_putc(s, pe->reported_metric.tag); + stream_putc(s, pe->reported_metric.flags); + + stream_putc(s, pe->destination->prefixlen); + + stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 24) & 0xFF); + if (pe->destination->prefixlen > 8) + stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 16) & 0xFF); + if (pe->destination->prefixlen > 16) + stream_putc(s, (ntohl(pe->destination->u.prefix4.s_addr) >> 8) & 0xFF); + if (pe->destination->prefixlen > 24) + stream_putc(s, ntohl(pe->destination->u.prefix4.s_addr) & 0xFF); + + return length; +} + +uint16_t eigrp_add_authTLV_MD5_to_stream(struct stream *s, + struct eigrp_interface *ei) +{ + struct key *key; + struct keychain *keychain; + struct TLV_MD5_Authentication_Type *authTLV; + + authTLV = eigrp_authTLV_MD5_new(); + + authTLV->type = htons(EIGRP_TLV_AUTH); + authTLV->length = htons(EIGRP_AUTH_MD5_TLV_SIZE); + authTLV->auth_type = htons(EIGRP_AUTH_TYPE_MD5); + authTLV->auth_length = htons(EIGRP_AUTH_TYPE_MD5_LEN); + authTLV->key_sequence = 0; + memset(authTLV->Nullpad, 0, sizeof(authTLV->Nullpad)); + + keychain = keychain_lookup(ei->params.auth_keychain); + if (keychain) + key = key_lookup_for_send(keychain); + else { + free(ei->params.auth_keychain); + ei->params.auth_keychain = NULL; + eigrp_authTLV_MD5_free(authTLV); + return 0; + } + + if (key) { + authTLV->key_id = htonl(key->index); + memset(authTLV->digest, 0, EIGRP_AUTH_TYPE_MD5_LEN); + stream_put(s, authTLV, + sizeof(struct TLV_MD5_Authentication_Type)); + eigrp_authTLV_MD5_free(authTLV); + return EIGRP_AUTH_MD5_TLV_SIZE; + } + + eigrp_authTLV_MD5_free(authTLV); + + return 0; +} + +uint16_t eigrp_add_authTLV_SHA256_to_stream(struct stream *s, + struct eigrp_interface *ei) +{ + struct key *key; + struct keychain *keychain; + struct TLV_SHA256_Authentication_Type *authTLV; + + authTLV = eigrp_authTLV_SHA256_new(); + + authTLV->type = htons(EIGRP_TLV_AUTH); + authTLV->length = htons(EIGRP_AUTH_SHA256_TLV_SIZE); + authTLV->auth_type = htons(EIGRP_AUTH_TYPE_SHA256); + authTLV->auth_length = htons(EIGRP_AUTH_TYPE_SHA256_LEN); + authTLV->key_sequence = 0; + memset(authTLV->Nullpad, 0, sizeof(authTLV->Nullpad)); + + keychain = keychain_lookup(ei->params.auth_keychain); + if (keychain) + key = key_lookup_for_send(keychain); + else { + free(ei->params.auth_keychain); + ei->params.auth_keychain = NULL; + eigrp_authTLV_SHA256_free(authTLV); + return 0; + } + + if (key) { + authTLV->key_id = 0; + memset(authTLV->digest, 0, EIGRP_AUTH_TYPE_SHA256_LEN); + stream_put(s, authTLV, + sizeof(struct TLV_SHA256_Authentication_Type)); + eigrp_authTLV_SHA256_free(authTLV); + return EIGRP_AUTH_SHA256_TLV_SIZE; + } + + eigrp_authTLV_SHA256_free(authTLV); + + return 0; +} + +struct TLV_MD5_Authentication_Type *eigrp_authTLV_MD5_new(void) +{ + struct TLV_MD5_Authentication_Type *new; + + new = XCALLOC(MTYPE_EIGRP_AUTH_TLV, + sizeof(struct TLV_MD5_Authentication_Type)); + + return new; +} + +void eigrp_authTLV_MD5_free(struct TLV_MD5_Authentication_Type *authTLV) +{ + XFREE(MTYPE_EIGRP_AUTH_TLV, authTLV); +} + +struct TLV_SHA256_Authentication_Type *eigrp_authTLV_SHA256_new(void) +{ + struct TLV_SHA256_Authentication_Type *new; + + new = XCALLOC(MTYPE_EIGRP_AUTH_SHA256_TLV, + sizeof(struct TLV_SHA256_Authentication_Type)); + + return new; +} + +void eigrp_authTLV_SHA256_free(struct TLV_SHA256_Authentication_Type *authTLV) +{ + XFREE(MTYPE_EIGRP_AUTH_SHA256_TLV, authTLV); +} + +void eigrp_IPv4_InternalTLV_free( + struct TLV_IPv4_Internal_type *IPv4_InternalTLV) +{ + XFREE(MTYPE_EIGRP_IPV4_INT_TLV, IPv4_InternalTLV); +} + +struct TLV_Sequence_Type *eigrp_SequenceTLV_new(void) +{ + struct TLV_Sequence_Type *new; + + new = XCALLOC(MTYPE_EIGRP_SEQ_TLV, sizeof(struct TLV_Sequence_Type)); + + return new; +} diff --git a/eigrpd/eigrp_packet.h b/eigrpd/eigrp_packet.h new file mode 100644 index 0000000..6a0360e --- /dev/null +++ b/eigrpd/eigrp_packet.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP General Sending and Receiving of EIGRP Packets. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_PACKET_H +#define _ZEBRA_EIGRP_PACKET_H + +/*Prototypes*/ +extern void eigrp_read(struct event *thread); +extern void eigrp_write(struct event *thread); + +extern struct eigrp_packet *eigrp_packet_new(size_t size, + struct eigrp_neighbor *nbr); +extern struct eigrp_packet *eigrp_packet_duplicate(struct eigrp_packet *old, + struct eigrp_neighbor *nbr); +extern void eigrp_packet_free(struct eigrp_packet *ep); +extern void eigrp_packet_delete(struct eigrp_interface *ei); +extern void eigrp_packet_header_init(int type, struct eigrp *eigrp, + struct stream *s, uint32_t flags, + uint32_t sequence, uint32_t ack); +extern void eigrp_packet_checksum(struct eigrp_interface *ei, struct stream *s, + uint16_t length); + +extern struct eigrp_fifo *eigrp_fifo_new(void); +extern struct eigrp_packet *eigrp_fifo_next(struct eigrp_fifo *fifo); +extern struct eigrp_packet *eigrp_fifo_pop(struct eigrp_fifo *fifo); +extern void eigrp_fifo_push(struct eigrp_fifo *fifo, struct eigrp_packet *ep); +extern void eigrp_fifo_free(struct eigrp_fifo *fifo); +extern void eigrp_fifo_reset(struct eigrp_fifo *fifo); + +extern void eigrp_send_packet_reliably(struct eigrp_neighbor *nbr); + +extern struct TLV_IPv4_Internal_type *eigrp_read_ipv4_tlv(struct stream *s); +extern uint16_t +eigrp_add_internalTLV_to_stream(struct stream *s, + struct eigrp_prefix_descriptor *pe); +extern uint16_t eigrp_add_authTLV_MD5_to_stream(struct stream *s, + struct eigrp_interface *ei); +extern uint16_t eigrp_add_authTLV_SHA256_to_stream(struct stream *s, + struct eigrp_interface *ei); + +extern void eigrp_unack_packet_retrans(struct event *thread); +extern void eigrp_unack_multicast_packet_retrans(struct event *thread); + +/* + * untill there is reason to have their own header, these externs are found in + * eigrp_hello.c + */ +extern void eigrp_sw_version_initialize(void); +extern void eigrp_hello_send(struct eigrp_interface *ei, uint8_t flags, + struct in_addr *nbr_addr); +extern void eigrp_hello_send_ack(struct eigrp_neighbor *nbr); +extern void eigrp_hello_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size); +extern void eigrp_hello_timer(struct event *thread); + +/* + * These externs are found in eigrp_update.c + */ +extern bool eigrp_update_prefix_apply(struct eigrp *eigrp, + struct eigrp_interface *ei, int in, + struct prefix *prefix); +extern void eigrp_update_send(struct eigrp_interface *ei); +extern void eigrp_update_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size); +extern void eigrp_update_send_all(struct eigrp *eigrp, + struct eigrp_interface *exception); +extern void eigrp_update_send_init(struct eigrp_neighbor *nbr); +extern void eigrp_update_send_EOT(struct eigrp_neighbor *nbr); +extern void eigrp_update_send_GR_thread(struct event *thread); +extern void eigrp_update_send_GR(struct eigrp_neighbor *nbr, + enum GR_type gr_type, struct vty *vty); +extern void eigrp_update_send_interface_GR(struct eigrp_interface *ei, + enum GR_type gr_type, + struct vty *vty); +extern void eigrp_update_send_process_GR(struct eigrp *eigrp, + enum GR_type gr_type, struct vty *vty); + +/* + * These externs are found in eigrp_query.c + */ + +extern void eigrp_send_query(struct eigrp_interface *ei); +extern void eigrp_query_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size); +extern uint32_t eigrp_query_send_all(struct eigrp *eigrp); + +/* + * These externs are found in eigrp_reply.c + */ +extern void eigrp_send_reply(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_reply_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size); + +/* + * These externs are found in eigrp_siaquery.c + */ +extern void eigrp_send_siaquery(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_siaquery_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, + struct stream *s, struct eigrp_interface *ei, + int size); + +/* + * These externs are found in eigrp_siareply.c + */ +extern void eigrp_send_siareply(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_siareply_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, + struct stream *s, struct eigrp_interface *ei, + int size); + +extern struct TLV_MD5_Authentication_Type *eigrp_authTLV_MD5_new(void); +extern void eigrp_authTLV_MD5_free(struct TLV_MD5_Authentication_Type *authTLV); +extern struct TLV_SHA256_Authentication_Type *eigrp_authTLV_SHA256_new(void); +extern void +eigrp_authTLV_SHA256_free(struct TLV_SHA256_Authentication_Type *authTLV); + +extern int eigrp_make_md5_digest(struct eigrp_interface *ei, struct stream *s, + uint8_t flags); +extern int eigrp_check_md5_digest(struct stream *s, + struct TLV_MD5_Authentication_Type *authTLV, + struct eigrp_neighbor *nbr, uint8_t flags); +extern int eigrp_make_sha256_digest(struct eigrp_interface *ei, + struct stream *s, uint8_t flags); +extern int +eigrp_check_sha256_digest(struct stream *s, + struct TLV_SHA256_Authentication_Type *authTLV, + struct eigrp_neighbor *nbr, uint8_t flags); + + +extern void +eigrp_IPv4_InternalTLV_free(struct TLV_IPv4_Internal_type *IPv4_InternalTLV); + +extern struct TLV_Sequence_Type *eigrp_SequenceTLV_new(void); + +extern const struct message eigrp_packet_type_str[]; +extern const size_t eigrp_packet_type_str_max; + +#endif /* _ZEBRA_EIGRP_PACKET_H */ diff --git a/eigrpd/eigrp_query.c b/eigrpd/eigrp_query.c new file mode 100644 index 0000000..0e206cd --- /dev/null +++ b/eigrpd/eigrp_query.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Sending and Receiving EIGRP Query Packets. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "vty.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" + +uint32_t eigrp_query_send_all(struct eigrp *eigrp) +{ + struct eigrp_interface *iface; + struct listnode *node, *node2, *nnode2; + struct eigrp_prefix_descriptor *pe; + uint32_t counter; + + if (eigrp == NULL) { + zlog_debug("EIGRP Routing Process not enabled"); + return 0; + } + + counter = 0; + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { + eigrp_send_query(iface); + counter++; + } + + for (ALL_LIST_ELEMENTS(eigrp->topology_changes_internalIPV4, node2, + nnode2, pe)) { + if (pe->req_action & EIGRP_FSM_NEED_QUERY) { + pe->req_action &= ~EIGRP_FSM_NEED_QUERY; + listnode_delete(eigrp->topology_changes_internalIPV4, + pe); + } + } + + return counter; +} + +/*EIGRP QUERY read function*/ +void eigrp_query_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_neighbor *nbr; + struct TLV_IPv4_Internal_type *tlv; + struct prefix dest_addr; + + uint16_t type; + uint16_t length; + + /* increment statistics. */ + ei->query_in++; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + nbr->recv_sequence_number = ntohl(eigrph->sequence); + + while (s->endp > s->getp) { + type = stream_getw(s); + switch (type) { + case EIGRP_TLV_IPv4_INT: + stream_set_getp(s, s->getp - sizeof(uint16_t)); + + tlv = eigrp_read_ipv4_tlv(s); + + dest_addr.family = AF_INET; + dest_addr.u.prefix4 = tlv->destination; + dest_addr.prefixlen = tlv->prefix_length; + struct eigrp_prefix_descriptor *dest = + eigrp_topology_table_lookup_ipv4( + eigrp->topology_table, &dest_addr); + + /* If the destination exists (it should, but one never + * know)*/ + if (dest != NULL) { + struct eigrp_fsm_action_message msg; + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup( + dest->entries, nbr); + msg.packet_type = EIGRP_OPC_QUERY; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.metrics = tlv->metric; + msg.entry = entry; + msg.prefix = dest; + eigrp_fsm_event(&msg); + } + eigrp_IPv4_InternalTLV_free(tlv); + break; + + case EIGRP_TLV_IPv4_EXT: + /* DVS: processing of external routes needs packet and fsm work. + * for now, lets just not creash the box + */ + default: + length = stream_getw(s); + // -2 for type, -2 for len + for (length -= 4; length; length--) { + (void)stream_getc(s); + } + } + } + eigrp_hello_send_ack(nbr); + eigrp_query_send_all(eigrp); + eigrp_update_send_all(eigrp, nbr->ei); +} + +void eigrp_send_query(struct eigrp_interface *ei) +{ + struct eigrp_packet *ep = NULL; + uint16_t length = EIGRP_HEADER_LEN; + struct listnode *node, *nnode, *node2, *nnode2; + struct eigrp_neighbor *nbr; + struct eigrp_prefix_descriptor *pe; + bool has_tlv = false; + bool new_packet = true; + uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); + + for (ALL_LIST_ELEMENTS(ei->eigrp->topology_changes_internalIPV4, node, + nnode, pe)) { + if (!(pe->req_action & EIGRP_FSM_NEED_QUERY)) + continue; + + if (new_packet) { + ep = eigrp_packet_new(eigrp_mtu, NULL); + + /* Prepare EIGRP INIT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_QUERY, ei->eigrp, + ep->s, 0, + ei->eigrp->sequence_number, 0); + + // encode Authentication TLV, if needed + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, + ei); + } + new_packet = false; + } + + length += eigrp_add_internalTLV_to_stream(ep->s, pe); + has_tlv = true; + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (nbr->state == EIGRP_NEIGHBOR_UP) + listnode_add(pe->rij, nbr); + } + + if (length + EIGRP_TLV_MAX_IPV4_BYTE > eigrp_mtu) { + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && ei->params.auth_keychain != NULL) { + eigrp_make_md5_digest(ei, ep->s, + EIGRP_AUTH_UPDATE_FLAG); + } + + eigrp_packet_checksum(ei, ep->s, length); + ep->length = length; + + ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); + + ep->sequence_number = ei->eigrp->sequence_number; + ei->eigrp->sequence_number++; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + struct eigrp_packet *dup; + + if (nbr->state != EIGRP_NEIGHBOR_UP) + continue; + + dup = eigrp_packet_duplicate(ep, nbr); + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, dup); + + if (nbr->retrans_queue->count == 1) + eigrp_send_packet_reliably(nbr); + } + + has_tlv = false; + length = 0; + eigrp_packet_free(ep); + ep = NULL; + new_packet = true; + } + } + + if (!has_tlv) + return; + + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && ei->params.auth_keychain != NULL) + eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + + + /* EIGRP Checksum */ + eigrp_packet_checksum(ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); + + /*This ack number we await from neighbor*/ + ep->sequence_number = ei->eigrp->sequence_number; + ei->eigrp->sequence_number++; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + struct eigrp_packet *dup; + + if (nbr->state != EIGRP_NEIGHBOR_UP) + continue; + + dup = eigrp_packet_duplicate(ep, nbr); + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, dup); + + if (nbr->retrans_queue->count == 1) + eigrp_send_packet_reliably(nbr); + } + + eigrp_packet_free(ep); +} diff --git a/eigrpd/eigrp_reply.c b/eigrpd/eigrp_reply.c new file mode 100644 index 0000000..aae89e8 --- /dev/null +++ b/eigrpd/eigrp_reply.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Eigrp Sending and Receiving EIGRP Reply Packets. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "vty.h" +#include "keychain.h" +#include "plist.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_errors.h" + +void eigrp_send_reply(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + struct eigrp_interface *ei = nbr->ei; + struct eigrp *eigrp = ei->eigrp; + struct eigrp_prefix_descriptor *pe2; + + // TODO: Work in progress + /* Filtering */ + /* get list from eigrp process */ + pe2 = XCALLOC(MTYPE_EIGRP_PREFIX_DESCRIPTOR, + sizeof(struct eigrp_prefix_descriptor)); + memcpy(pe2, pe, sizeof(struct eigrp_prefix_descriptor)); + + if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, + pe2->destination)) { + zlog_info("REPLY SEND: Setting Metric to max"); + pe2->reported_metric.delay = EIGRP_MAX_METRIC; + } + + /* + * End of filtering + */ + + ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), nbr); + + /* Prepare EIGRP INIT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_REPLY, eigrp, ep->s, 0, + eigrp->sequence_number, 0); + + // encode Authentication TLV, if needed + if (ei->params.auth_type == EIGRP_AUTH_TYPE_MD5 + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); + } + + + length += eigrp_add_internalTLV_to_stream(ep->s, pe2); + + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + ep->sequence_number = eigrp->sequence_number; + + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } + + XFREE(MTYPE_EIGRP_PREFIX_DESCRIPTOR, pe2); +} + +/*EIGRP REPLY read function*/ +void eigrp_reply_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_neighbor *nbr; + struct TLV_IPv4_Internal_type *tlv; + + uint16_t type; + + /* increment statistics. */ + ei->reply_in++; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + nbr->recv_sequence_number = ntohl(eigrph->sequence); + + while (s->endp > s->getp) { + type = stream_getw(s); + + if (type != EIGRP_TLV_IPv4_INT) + continue; + + struct prefix dest_addr; + + stream_set_getp(s, s->getp - sizeof(uint16_t)); + + tlv = eigrp_read_ipv4_tlv(s); + + dest_addr.family = AF_INET; + dest_addr.u.prefix4 = tlv->destination; + dest_addr.prefixlen = tlv->prefix_length; + struct eigrp_prefix_descriptor *dest = + eigrp_topology_table_lookup_ipv4(eigrp->topology_table, + &dest_addr); + /* + * Destination must exists + */ + if (!dest) { + flog_err( + EC_EIGRP_PACKET, + "%s: Received prefix %pFX which we do not know about", + __func__, &dest_addr); + eigrp_IPv4_InternalTLV_free(tlv); + continue; + } + + struct eigrp_fsm_action_message msg; + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup(dest->entries, nbr); + + if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_IN, + &dest_addr)) { + tlv->metric.delay = EIGRP_MAX_METRIC; + } + /* + * End of filtering + */ + + msg.packet_type = EIGRP_OPC_REPLY; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.metrics = tlv->metric; + msg.entry = entry; + msg.prefix = dest; + eigrp_fsm_event(&msg); + + eigrp_IPv4_InternalTLV_free(tlv); + } + eigrp_hello_send_ack(nbr); +} diff --git a/eigrpd/eigrp_routemap.c b/eigrpd/eigrp_routemap.c new file mode 100644 index 0000000..420cb6c --- /dev/null +++ b/eigrpd/eigrp_routemap.c @@ -0,0 +1,1164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Filter Functions. + * Copyright (C) 2013-2015 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + * + * Note: This file contains skeleton for all possible matches and sets, + * but they are hidden in comment block and not properly implemented. + * At this time, the only function we consider useful for our use + * in distribute command in EIGRP is matching destination IP (with both + * access and prefix list). + * + */ + +#include + +#include "memory.h" +#include "prefix.h" +#include "if_rmap.h" +#include "routemap.h" +#include "command.h" +#include "filter.h" +#include "log.h" +#include "sockunion.h" /* for inet_aton () */ +#include "plist.h" + +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrp_const.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_routemap.h" + +void eigrp_if_rmap_update(struct if_rmap *if_rmap) +{ + struct interface *ifp; + struct eigrp_interface *ei, *ei2; + struct listnode *node, *nnode; + struct route_map *rmap; + struct eigrp *e; + + ifp = if_lookup_by_name(if_rmap->ifname); + if (ifp == NULL) + return; + + ei = NULL; + e = eigrp_lookup(); + for (ALL_LIST_ELEMENTS(e->eiflist, node, nnode, ei2)) { + if (strcmp(ei2->ifp->name, ifp->name) == 0) { + ei = ei2; + break; + } + } + + if (if_rmap->routemap[IF_RMAP_IN]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_IN]); + if (rmap) + ei->routemap[IF_RMAP_IN] = rmap; + else + ei->routemap[IF_RMAP_IN] = NULL; + } else + ei->routemap[EIGRP_FILTER_IN] = NULL; + + if (if_rmap->routemap[IF_RMAP_OUT]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_OUT]); + if (rmap) + ei->routemap[IF_RMAP_OUT] = rmap; + else + ei->routemap[IF_RMAP_OUT] = NULL; + } else + ei->routemap[EIGRP_FILTER_OUT] = NULL; +} + +void eigrp_if_rmap_update_interface(struct interface *ifp) +{ + struct if_rmap *if_rmap; + + if_rmap = if_rmap_lookup(ifp->name); + if (if_rmap) + eigrp_if_rmap_update(if_rmap); +} + +void eigrp_routemap_update_redistribute(void) +{ + int i; + struct eigrp *e; + + e = eigrp_lookup(); + + if (e) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (e->route_map[i].name) + e->route_map[i].map = route_map_lookup_by_name( + e->route_map[i].name); + } + } +} + +/* ARGSUSED */ +void eigrp_rmap_update(const char *notused) +{ + struct interface *ifp; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(iflist, node, nnode, ifp)) + eigrp_if_rmap_update_interface(ifp); + + eigrp_routemap_update_redistribute(); +} + +/* Add eigrp route map rule. */ +static int eigrp_route_match_add(struct vty *vty, struct route_map_index *index, + const char *command, const char *arg) +{ + enum rmap_compile_rets ret; + + ret = route_map_add_match(index, command, arg, type); + switch (ret) { + case RMAP_RULE_MISSING: + vty_out(vty, "%% Can't find rule.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + vty_out(vty, "%% Argument is malformed.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + /* + * Intentionally not handling these cases + */ + break; + } + + return CMD_SUCCESS; +} + +/* Delete rip route map rule. */ +static int eigrp_route_match_delete(struct vty *vty, + struct route_map_index *index, + const char *command, const char *arg) +{ + enum rmap_compile_rets ret; + + ret = route_map_delete_match(index, command, arg, type); + switch (ret) { + case RMAP_RULE_MISSING: + vty_out(vty, "%% Can't find rule.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + vty_out(vty, "%% Argument is malformed.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + /* + * These cases intentionally ignored + */ + break; + } + + return CMD_SUCCESS; +} + +/* Add eigrp route map rule. */ +static int eigrp_route_set_add(struct vty *vty, struct route_map_index *index, + const char *command, const char *arg) +{ + enum rmap_compile_rets ret; + + ret = route_map_add_set(index, command, arg); + switch (ret) { + case RMAP_RULE_MISSING: + vty_out(vty, "%% Can't find rule.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + /* + * rip, ripng and other protocols share the set metric command + * but only values from 0 to 16 are valid for rip and ripng + * if metric is out of range for rip and ripng, it is + * not for other protocols. Do not return an error + */ + if (strcmp(command, "metric")) { + vty_out(vty, "%% Argument is malformed.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + break; + case RMAP_COMPILE_SUCCESS: + /* + * These cases intentionally left blank here + */ + break; + } + + return CMD_SUCCESS; +} + +/* Delete eigrp route map rule. */ +static int eigrp_route_set_delete(struct vty *vty, + struct route_map_index *index, + const char *command, const char *arg) +{ + enum rmap_compile_rets ret; + + ret = route_map_delete_set(index, command, arg); + switch (ret) { + case RMAP_RULE_MISSING: + vty_out(vty, "%% Can't find rule.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + vty_out(vty, "%% Argument is malformed.\n"); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + /* + * These cases intentionally not handled + */ + break; + } + + return CMD_SUCCESS; +} + +/* Hook function for updating route_map assignment. */ +/* ARGSUSED */ +void eigrp_route_map_update(const char *notused) +{ + int i; + struct eigrp *e; + e = eigrp_lookup(); + + if (e) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (e->route_map[i].name) + e->route_map[i].map = route_map_lookup_by_name( + e->route_map[i].name); + } + } +} + + +/* `match metric METRIC' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_metric(void *rule, struct prefix *prefix, route_map_object_t type, + void *object) +{ + // uint32_t *metric; + // uint32_t check; + // struct rip_info *rinfo; + // struct eigrp_route_descriptor *te; + // struct eigrp_prefix_descriptor *pe; + // struct listnode *node, *node2, *nnode, *nnode2; + // struct eigrp *e; + // + // e = eigrp_lookup(); + // + // if (type == RMAP_EIGRP) + // { + // metric = rule; + // rinfo = object; + // + // /* If external metric is available, the route-map should + // work on this one (for redistribute purpose) */ + // /*check = (rinfo->external_metric) ? rinfo->external_metric : + // rinfo->metric;*/ + // + // if (check == *metric) + // return RMAP_MATCH; + // else + // return RMAP_NOMATCH; + // } + return RMAP_NOMATCH; +} + +/* Route map `match metric' match statement. `arg' is METRIC value */ +static void *route_match_metric_compile(const char *arg) +{ + // uint32_t *metric; + // + // metric = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + // *metric = atoi (arg); + // + // if(*metric > 0) + // return metric; + // + // XFREE (MTYPE_ROUTE_MAP_COMPILED, metric); + return NULL; +} + +/* Free route map's compiled `match metric' value. */ +static void route_match_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_metric_cmd = { + "metric", + route_match_metric, + route_match_metric_compile, + route_match_metric_free +}; + +/* `match interface IFNAME' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_interface(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // struct rip_info *rinfo; + // struct interface *ifp; + // char *ifname; + // + // if (type == RMAP_EIGRP) + // { + // ifname = rule; + // ifp = if_lookup_by_name(ifname); + // + // if (!ifp) + // return RMAP_NOMATCH; + // + // rinfo = object; + // + // /*if (rinfo->ifindex_out == ifp->ifindex || rinfo->ifindex == + // ifp->ifindex) + // return RMAP_MATCH; + // else + // return RMAP_NOMATCH;*/ + // } + return RMAP_NOMATCH; +} + +/* Route map `match interface' match statement. `arg' is IFNAME value */ +/* XXX I don`t know if I need to check does interface exist? */ +static void *route_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `match interface' value. */ +static void route_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for interface matching. */ +static const struct route_map_rule_cmd route_match_interface_cmd = { + "interface", + route_match_interface, + route_match_interface_compile, + route_match_interface_free +}; + +/* `match ip next-hop IP_ACCESS_LIST' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_ip_next_hop(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // struct access_list *alist; + // struct rip_info *rinfo; + // struct prefix_ipv4 p; + // + // if (type == RMAP_EIGRP) + // { + // rinfo = object; + // p.family = AF_INET; + // /*p.prefix = (rinfo->nexthop.s_addr) ? rinfo->nexthop : + // rinfo->from;*/ + // p.prefixlen = IPV4_MAX_BITLEN; + // + // alist = access_list_lookup (AFI_IP, (char *) rule); + // if (alist == NULL) + // return RMAP_NOMATCH; + // + // return (access_list_apply (alist, &p) == FILTER_DENY ? + // RMAP_NOMATCH : RMAP_MATCH); + // } + return RMAP_NOMATCH; +} + +/* Route map `ip next-hop' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_next_hop_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `. */ +static void route_match_ip_next_hop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip next-hop matching. */ +static const struct route_map_rule_cmd route_match_ip_next_hop_cmd = { + "ip next-hop", + route_match_ip_next_hop, + route_match_ip_next_hop_compile, + route_match_ip_next_hop_free +}; + +/* `match ip next-hop prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_prefix_list(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // struct prefix_list *plist; + // struct rip_info *rinfo; + // struct prefix_ipv4 p; + // + // if (type == RMAP_EIGRP) + // { + // rinfo = object; + // p.family = AF_INET; + // /*p.prefix = (rinfo->nexthop.s_addr) ? rinfo->nexthop : + // rinfo->from;*/ + // p.prefixlen = IPV4_MAX_BITLEN; + // + // plist = prefix_list_lookup (AFI_IP, (char *) rule); + // if (plist == NULL) + // return RMAP_NOMATCH; + // + // return (prefix_list_apply (plist, &p) == PREFIX_DENY ? + // RMAP_NOMATCH : RMAP_MATCH); + // } + return RMAP_NOMATCH; +} + +static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_prefix_list_cmd = { + "ip next-hop prefix-list", + route_match_ip_next_hop_prefix_list, + route_match_ip_next_hop_prefix_list_compile, + route_match_ip_next_hop_prefix_list_free +}; + +/* `match ip address IP_ACCESS_LIST' */ + +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_ip_address(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + struct access_list *alist; + + if (type == RMAP_EIGRP) { + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) + return RMAP_NOMATCH; + + return (access_list_apply(alist, prefix) == FILTER_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +/* Route map `ip address' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_ip_address_cmd = { + "ip address", + route_match_ip_address, + route_match_ip_address_compile, + route_match_ip_address_free +}; + +/* `match ip address prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_address_prefix_list(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + struct prefix_list *plist; + + if (type == RMAP_EIGRP) { + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY + ? RMAP_NOMATCH + : RMAP_MATCH); + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_address_prefix_list_cmd = { + "ip address prefix-list", + route_match_ip_address_prefix_list, + route_match_ip_address_prefix_list_compile, + route_match_ip_address_prefix_list_free +}; + +/* `match tag TAG' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_tag(void *rule, struct prefix *prefix, route_map_object_t type, + void *object) +{ + // unsigned short *tag; + // struct rip_info *rinfo; + // + // if (type == RMAP_EIGRP) + // { + // tag = rule; + // rinfo = object; + // + // /* The information stored by rinfo is host ordered. */ + // /*if (rinfo->tag == *tag) + // return RMAP_MATCH; + // else + // return RMAP_NOMATCH;*/ + // } + return RMAP_NOMATCH; +} + +/* Route map `match tag' match statement. `arg' is TAG value */ +static void *route_match_tag_compile(const char *arg) +{ + // unsigned short *tag; + // + // tag = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof(unsigned short)); + // *tag = atoi (arg); + // + // return tag; +} + +/* Free route map's compiled `match tag' value. */ +static void route_match_tag_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for tag matching. */ +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_match_tag_compile, + route_match_tag_free +}; + +/* Set metric to attribute. */ +static enum route_map_cmd_result_t +route_set_metric(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // if (type == RMAP_RIP) + // { + // struct rip_metric_modifier *mod; + // struct rip_info *rinfo; + // + // mod = rule; + // rinfo = object; + // + // /*if (mod->type == metric_increment) + // rinfo->metric_out += mod->metric; + // else if (mod->type == metric_decrement) + // rinfo->metric_out -= mod->metric; + // else if (mod->type == metric_absolute) + // rinfo->metric_out = mod->metric; + // + // if ((signed int)rinfo->metric_out < 1) + // rinfo->metric_out = 1; + // if (rinfo->metric_out > RIP_METRIC_INFINITY) + // rinfo->metric_out = RIP_METRIC_INFINITY;*/ + // + // rinfo->metric_set = 1; + // } + return RMAP_OKAY; +} + +/* set metric compilation. */ +static void *route_set_metric_compile(const char *arg) +{ + // int len; + // const char *pnt; + // int type; + // long metric; + // char *endptr = NULL; + // struct rip_metric_modifier *mod; + // + // len = strlen (arg); + // pnt = arg; + // + // if (len == 0) + // return NULL; + // + // /* Examine first character. */ + // if (arg[0] == '+') + // { + // //type = metric_increment; + // pnt++; + // } + // else if (arg[0] == '-') + // { + // //type = metric_decrement; + // pnt++; + // } + // /*else + // type = metric_absolute;*/ + // + // /* Check beginning with digit string. */ + // if (*pnt < '0' || *pnt > '9') + // return NULL; + // + // /* Convert string to integer. */ + // metric = strtol (pnt, &endptr, 10); + // + // if (metric == LONG_MAX || *endptr != '\0') + // return NULL; + // /*if (metric < 0 || metric > RIP_METRIC_INFINITY) + // return NULL;*/ + // + // mod = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, + // sizeof(struct rip_metric_modifier)); + // mod->type = type; + // mod->metric = metric; + + // return mod; +} + +/* Free route map's compiled `set metric' value. */ +static void route_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_set_metric_compile, + route_set_metric_free, +}; + +/* `set ip next-hop IP_ADDRESS' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ip_nexthop(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // struct in_addr *address; + // struct rip_info *rinfo; + // + // if(type == RMAP_RIP) + // { + // /* Fetch routemap's rule information. */ + // address = rule; + // rinfo = object; + // + // /* Set next hop value. */ + // rinfo->nexthop_out = *address; + // } + + return RMAP_OKAY; +} + +/* Route map `ip nexthop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ip_nexthop_compile(const char *arg) +{ + // int ret; + // struct in_addr *address; + // + // address = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof(struct + // in_addr)); + // + // ret = inet_aton (arg, address); + // + // if (ret == 0) + // { + // XFREE (MTYPE_ROUTE_MAP_COMPILED, address); + // return NULL; + // } + // + // return address; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_ip_nexthop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd route_set_ip_nexthop_cmd = { + "ip next-hop", + route_set_ip_nexthop, + route_set_ip_nexthop_compile, + route_set_ip_nexthop_free +}; + +/* `set tag TAG' */ + +/* Set tag to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_tag(void *rule, struct prefix *prefix, + route_map_object_t type, void *object) +{ + // unsigned short *tag; + // struct rip_info *rinfo; + // + // if(type == RMAP_RIP) + // { + // /* Fetch routemap's rule information. */ + // tag = rule; + // rinfo = object; + // + // /* Set next hop value. */ + // rinfo->tag_out = *tag; + // } + + return RMAP_OKAY; +} + +/* Route map `tag' compile function. Given string is converted + to unsigned short. */ +static void *route_set_tag_compile(const char *arg) +{ + // unsigned short *tag; + // + // tag = XMALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof(unsigned short)); + // *tag = atoi (arg); + // + // return tag; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_tag_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for tag set. */ +static const struct route_map_rule_cmd route_set_tag_cmd = { + "tag", + route_set_tag, + route_set_tag_compile, + route_set_tag_free +}; + +#define MATCH_STR "Match values from routing table\n" +#define SET_STR "Set values in destination routing protocol\n" + +DEFUN (match_metric, + match_metric_cmd, + "match metric (0-4294967295)", + MATCH_STR + "Match metric of route\n" + "Metric value\n") +{ + return eigrp_route_match_add(vty, vty->index, "metric", argv[0]); +} + +DEFUN (no_match_metric, + no_match_metric_cmd, + "no match metric", + NO_STR + MATCH_STR + "Match metric of route\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, "metric", + NULL); + + return eigrp_route_match_delete(vty, vty->index, "metric", argv[0]); +} + +ALIAS(no_match_metric, no_match_metric_val_cmd, + "no match metric (0-4294967295)", NO_STR MATCH_STR + "Match metric of route\n" + "Metric value\n") + +DEFUN (match_interface, + match_interface_cmd, + "match interface WORD", + MATCH_STR + "Match first hop interface of route\n" + "Interface name\n") +{ + return eigrp_route_match_add(vty, vty->index, "interface", argv[0]); +} + +DEFUN (no_match_interface, + no_match_interface_cmd, + "no match interface", + NO_STR + MATCH_STR + "Match first hop interface of route\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, "interface", + NULL); + + return eigrp_route_match_delete(vty, vty->index, "interface", argv[0]); +} + +ALIAS(no_match_interface, no_match_interface_val_cmd, "no match interface WORD", + NO_STR MATCH_STR + "Match first hop interface of route\n" + "Interface name\n") + +DEFUN (match_ip_next_hop, + match_ip_next_hop_cmd, + "match ip next-hop ACCESSLIST4_NAME", + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "IP Access-list name\n") +{ + return eigrp_route_match_add(vty, vty->index, "ip next-hop", argv[0]); +} + +DEFUN (no_match_ip_next_hop, + no_match_ip_next_hop_cmd, + "no match ip next-hop", + NO_STR + MATCH_STR + IP_STR + "Match next-hop address of route\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, "ip next-hop", + NULL); + + return eigrp_route_match_delete(vty, vty->index, "ip next-hop", + argv[0]); +} + +ALIAS(no_match_ip_next_hop, no_match_ip_next_hop_val_cmd, + "no match ip next-hop ACCESSLIST4_NAME", NO_STR MATCH_STR IP_STR + "Match next-hop address of route\n" + "IP Access-list name\n") + +DEFUN (match_ip_next_hop_prefix_list, + match_ip_next_hop_prefix_list_cmd, + "match ip next-hop prefix-list PREFIXLIST_NAME", + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + return eigrp_route_match_add(vty, vty->index, "ip next-hop prefix-list", + argv[0]); +} + +DEFUN (no_match_ip_next_hop_prefix_list, + no_match_ip_next_hop_prefix_list_cmd, + "no match ip next-hop prefix-list", + NO_STR + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "Match entries of prefix-lists\n") +{ + if (argc == 0) + return eigrp_route_match_delete( + vty, vty->index, "ip next-hop prefix-list", NULL); + + return eigrp_route_match_delete(vty, vty->index, + "ip next-hop prefix-list", argv[0]); +} + +ALIAS(no_match_ip_next_hop_prefix_list, + no_match_ip_next_hop_prefix_list_val_cmd, + "no match ip next-hop prefix-list PREFIXLIST_NAME", NO_STR MATCH_STR IP_STR + "Match next-hop address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") + +DEFUN (match_ip_address, + match_ip_address_cmd, + "match ip address ACCESSLIST4_NAME", + MATCH_STR + IP_STR + "Match address of route\n" + "IP Access-list name\n") +{ + return eigrp_route_match_add(vty, vty->index, "ip address", argv[0]); +} + +DEFUN (no_match_ip_address, + no_match_ip_address_cmd, + "no match ip address", + NO_STR + MATCH_STR + IP_STR + "Match address of route\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, "ip address", + NULL); + + return eigrp_route_match_delete(vty, vty->index, "ip address", argv[0]); +} + +ALIAS(no_match_ip_address, no_match_ip_address_val_cmd, + "no match ip address ACCESSLIST4_NAME", NO_STR MATCH_STR IP_STR + "Match address of route\n" + "IP Access-list name\n") + +DEFUN (match_ip_address_prefix_list, + match_ip_address_prefix_list_cmd, + "match ip address prefix-list PREFIXLIST_NAME", + MATCH_STR + IP_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + return eigrp_route_match_add(vty, vty->index, "ip address prefix-list", + argv[0]); +} + +DEFUN (no_match_ip_address_prefix_list, + no_match_ip_address_prefix_list_cmd, + "no match ip address prefix-list", + NO_STR + MATCH_STR + IP_STR + "Match address of route\n" + "Match entries of prefix-lists\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, + "ip address prefix-list", NULL); + + return eigrp_route_match_delete(vty, vty->index, + "ip address prefix-list", argv[0]); +} + +ALIAS(no_match_ip_address_prefix_list, no_match_ip_address_prefix_list_val_cmd, + "no match ip address prefix-list PREFIXLIST_NAME", NO_STR MATCH_STR IP_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") + +DEFUN (match_tag, + match_tag_cmd, + "match tag (0-65535)", + MATCH_STR + "Match tag of route\n" + "Metric value\n") +{ + return eigrp_route_match_add(vty, vty->index, "tag", argv[0]); +} + +DEFUN (no_match_tag, + no_match_tag_cmd, + "no match tag", + NO_STR + MATCH_STR + "Match tag of route\n") +{ + if (argc == 0) + return eigrp_route_match_delete(vty, vty->index, "tag", NULL); + + return eigrp_route_match_delete(vty, vty->index, "tag", argv[0]); +} + +ALIAS(no_match_tag, no_match_tag_val_cmd, "no match tag (0-65535)", + NO_STR MATCH_STR + "Match tag of route\n" + "Metric value\n") + +/* set functions */ + +DEFUN (set_metric, + set_metric_cmd, + "set metric (0-4294967295)", + SET_STR + "Metric value for destination routing protocol\n" + "Metric value\n") +{ + return eigrp_route_set_add(vty, vty->index, "metric", argv[0]); +} + +ALIAS(set_metric, set_metric_addsub_cmd, "set metric <+/-metric>", SET_STR + "Metric value for destination routing protocol\n" + "Add or subtract metric\n") + +DEFUN (no_set_metric, + no_set_metric_cmd, + "no set metric", + NO_STR + SET_STR + "Metric value for destination routing protocol\n") +{ + if (argc == 0) + return eigrp_route_set_delete(vty, vty->index, "metric", NULL); + + return eigrp_route_set_delete(vty, vty->index, "metric", argv[0]); +} + +ALIAS(no_set_metric, no_set_metric_val_cmd, + "no set metric ((0-4294967295)|<+/-metric>)", NO_STR SET_STR + "Metric value for destination routing protocol\n" + "Metric value\n" + "Add or subtract metric\n") + +DEFUN (set_ip_nexthop, + set_ip_nexthop_cmd, + "set ip next-hop A.B.C.D", + SET_STR + IP_STR + "Next hop address\n" + "IP address of next hop\n") +{ + union sockunion su; + int ret; + + ret = str2sockunion(argv[0], &su); + if (ret < 0) { + vty_out(vty, "%% Malformed next-hop address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return eigrp_route_set_add(vty, vty->index, "ip next-hop", argv[0]); +} + +DEFUN (no_set_ip_nexthop, + no_set_ip_nexthop_cmd, + "no set ip next-hop", + NO_STR + SET_STR + IP_STR + "Next hop address\n") +{ + if (argc == 0) + return eigrp_route_set_delete(vty, vty->index, "ip next-hop", + NULL); + + return eigrp_route_set_delete(vty, vty->index, "ip next-hop", argv[0]); +} + +ALIAS(no_set_ip_nexthop, no_set_ip_nexthop_val_cmd, + "no set ip next-hop A.B.C.D", NO_STR SET_STR IP_STR + "Next hop address\n" + "IP address of next hop\n") + +DEFUN (set_tag, + set_tag_cmd, + "set tag (0-65535)", + SET_STR + "Tag value for routing protocol\n" + "Tag value\n") +{ + return eigrp_route_set_add(vty, vty->index, "tag", argv[0]); +} + +DEFUN (no_set_tag, + no_set_tag_cmd, + "no set tag", + NO_STR + SET_STR + "Tag value for routing protocol\n") +{ + if (argc == 0) + return eigrp_route_set_delete(vty, vty->index, "tag", NULL); + + return eigrp_route_set_delete(vty, vty->index, "tag", argv[0]); +} + +ALIAS(no_set_tag, no_set_tag_val_cmd, "no set tag (0-65535)", NO_STR SET_STR + "Tag value for routing protocol\n" + "Tag value\n") + +/* Route-map init */ +void eigrp_route_map_init(void) +{ + route_map_init(); + route_map_init_vty(); + route_map_add_hook(eigrp_route_map_update); + route_map_delete_hook(eigrp_route_map_update); + + /*route_map_install_match (&route_match_metric_cmd); + route_map_install_match (&route_match_interface_cmd);*/ + /*route_map_install_match (&route_match_ip_next_hop_cmd); + route_map_install_match (&route_match_ip_next_hop_prefix_list_cmd); + route_map_install_match (&route_match_ip_address_cmd); + route_map_install_match (&route_match_ip_address_prefix_list_cmd);*/ + /*route_map_install_match (&route_match_tag_cmd);*/ + + /*route_map_install_set (&route_set_metric_cmd); + route_map_install_set (&route_set_ip_nexthop_cmd); + route_map_install_set (&route_set_tag_cmd);*/ + + /*install_element (RMAP_NODE, &route_match_metric_cmd); + install_element (RMAP_NODE, &no_match_metric_cmd); + install_element (RMAP_NODE, &no_match_metric_val_cmd); + install_element (RMAP_NODE, &route_match_interface_cmd); + install_element (RMAP_NODE, &no_match_interface_cmd); + install_element (RMAP_NODE, &no_match_interface_val_cmd); + install_element (RMAP_NODE, &route_match_ip_next_hop_cmd); + install_element (RMAP_NODE, &no_match_ip_next_hop_cmd); + install_element (RMAP_NODE, &no_match_ip_next_hop_val_cmd); + install_element (RMAP_NODE, &route_match_ip_next_hop_prefix_list_cmd); + install_element (RMAP_NODE, &no_match_ip_next_hop_prefix_list_cmd); + install_element (RMAP_NODE, + &no_match_ip_next_hop_prefix_list_val_cmd);*/ + /*install_element (RMAP_NODE, &route_match_ip_address_cmd); + install_element (RMAP_NODE, &no_match_ip_address_cmd); + install_element (RMAP_NODE, &no_match_ip_address_val_cmd); + install_element (RMAP_NODE, &route_match_ip_address_prefix_list_cmd); + install_element (RMAP_NODE, &no_match_ip_address_prefix_list_cmd); + install_element (RMAP_NODE, + &no_match_ip_address_prefix_list_val_cmd);*/ + /*install_element (RMAP_NODE, &route_match_tag_cmd); + install_element (RMAP_NODE, &no_match_tag_cmd); + install_element (RMAP_NODE, &no_match_tag_val_cmd);*/ + + /*install_element (RMAP_NODE, &set_metric_cmd); + install_element (RMAP_NODE, &set_metric_addsub_cmd); + install_element (RMAP_NODE, &no_set_metric_cmd); + install_element (RMAP_NODE, &no_set_metric_val_cmd); + install_element (RMAP_NODE, &set_ip_nexthop_cmd); + install_element (RMAP_NODE, &no_set_ip_nexthop_cmd); + install_element (RMAP_NODE, &no_set_ip_nexthop_val_cmd); + install_element (RMAP_NODE, &set_tag_cmd); + install_element (RMAP_NODE, &no_set_tag_cmd); + install_element (RMAP_NODE, &no_set_tag_val_cmd);*/ +} diff --git a/eigrpd/eigrp_routemap.h b/eigrpd/eigrp_routemap.h new file mode 100644 index 0000000..c797d10 --- /dev/null +++ b/eigrpd/eigrp_routemap.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * eigrp_routemap.h + * + * Created on: Nov 19, 2015 + * Author: root + */ + +#ifndef EIGRPD_EIGRP_ROUTEMAP_H_ +#define EIGRPD_EIGRP_ROUTEMAP_H_ + +#include "if_rmap.h" + +extern bool eigrp_routemap_prefix_apply(struct eigrp *eigrp, + struct eigrp_interface *ei, int in, + struct prefix *prefix); +extern void eigrp_route_map_update(const char *); +extern void eigrp_route_map_init(); +extern void eigrp_if_rmap_update(struct if_rmap *); +extern void eigrp_if_rmap_update_interface(struct interface *); +extern void eigrp_routemap_update_redistribute(void); +extern void eigrp_rmap_update(const char *); + +#endif /* EIGRPD_EIGRP_ROUTEMAP_H_ */ diff --git a/eigrpd/eigrp_siaquery.c b/eigrpd/eigrp_siaquery.c new file mode 100644 index 0000000..71486a1 --- /dev/null +++ b/eigrpd/eigrp_siaquery.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Sending and Receiving EIGRP SIA-Query Packets. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "vty.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" + +/*EIGRP SIA-QUERY read function*/ +void eigrp_siaquery_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_neighbor *nbr; + struct TLV_IPv4_Internal_type *tlv; + + uint16_t type; + + /* increment statistics. */ + ei->siaQuery_in++; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + nbr->recv_sequence_number = ntohl(eigrph->sequence); + + while (s->endp > s->getp) { + type = stream_getw(s); + if (type == EIGRP_TLV_IPv4_INT) { + struct prefix dest_addr; + + stream_set_getp(s, s->getp - sizeof(uint16_t)); + + tlv = eigrp_read_ipv4_tlv(s); + + dest_addr.family = AFI_IP; + dest_addr.u.prefix4 = tlv->destination; + dest_addr.prefixlen = tlv->prefix_length; + struct eigrp_prefix_descriptor *dest = + eigrp_topology_table_lookup_ipv4( + eigrp->topology_table, &dest_addr); + + /* If the destination exists (it should, but one never + * know)*/ + if (dest != NULL) { + struct eigrp_fsm_action_message msg; + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup( + dest->entries, nbr); + msg.packet_type = EIGRP_OPC_SIAQUERY; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.metrics = tlv->metric; + msg.entry = entry; + msg.prefix = dest; + eigrp_fsm_event(&msg); + } + eigrp_IPv4_InternalTLV_free(tlv); + } + } + eigrp_hello_send_ack(nbr); +} + +void eigrp_send_siaquery(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + + ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); + + /* Prepare EIGRP INIT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_SIAQUERY, nbr->ei->eigrp, ep->s, 0, + nbr->ei->eigrp->sequence_number, 0); + + // encode Authentication TLV, if needed + if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (nbr->ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); + } + + length += eigrp_add_internalTLV_to_stream(ep->s, pe); + + if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (nbr->ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(nbr->ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + ep->sequence_number = nbr->ei->eigrp->sequence_number; + + if (nbr->state == EIGRP_NEIGHBOR_UP) { + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } + } else + eigrp_packet_free(ep); +} diff --git a/eigrpd/eigrp_siareply.c b/eigrpd/eigrp_siareply.c new file mode 100644 index 0000000..6c8c1ef --- /dev/null +++ b/eigrpd/eigrp_siareply.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Sending and Receiving EIGRP SIA-Reply Packets. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "vty.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" + +/*EIGRP SIA-REPLY read function*/ +void eigrp_siareply_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_neighbor *nbr; + struct TLV_IPv4_Internal_type *tlv; + + uint16_t type; + + /* increment statistics. */ + ei->siaReply_in++; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + nbr->recv_sequence_number = ntohl(eigrph->sequence); + + while (s->endp > s->getp) { + type = stream_getw(s); + if (type == EIGRP_TLV_IPv4_INT) { + struct prefix dest_addr; + + stream_set_getp(s, s->getp - sizeof(uint16_t)); + + tlv = eigrp_read_ipv4_tlv(s); + + dest_addr.family = AFI_IP; + dest_addr.u.prefix4 = tlv->destination; + dest_addr.prefixlen = tlv->prefix_length; + struct eigrp_prefix_descriptor *dest = + eigrp_topology_table_lookup_ipv4( + eigrp->topology_table, &dest_addr); + + /* If the destination exists (it should, but one never + * know)*/ + if (dest != NULL) { + struct eigrp_fsm_action_message msg; + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup( + dest->entries, nbr); + msg.packet_type = EIGRP_OPC_SIAQUERY; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.metrics = tlv->metric; + msg.entry = entry; + msg.prefix = dest; + eigrp_fsm_event(&msg); + } + eigrp_IPv4_InternalTLV_free(tlv); + } + } + eigrp_hello_send_ack(nbr); +} + +void eigrp_send_siareply(struct eigrp_neighbor *nbr, + struct eigrp_prefix_descriptor *pe) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + + ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); + + /* Prepare EIGRP INIT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_SIAREPLY, nbr->ei->eigrp, ep->s, 0, + nbr->ei->eigrp->sequence_number, 0); + + // encode Authentication TLV, if needed + if (nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5 + && nbr->ei->params.auth_keychain != NULL) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); + } + + length += eigrp_add_internalTLV_to_stream(ep->s, pe); + + if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (nbr->ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(nbr->ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + ep->sequence_number = nbr->ei->eigrp->sequence_number; + + if (nbr->state == EIGRP_NEIGHBOR_UP) { + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } + } else + eigrp_packet_free(ep); +} diff --git a/eigrpd/eigrp_snmp.c b/eigrpd/eigrp_snmp.c new file mode 100644 index 0000000..492ef3e --- /dev/null +++ b/eigrpd/eigrp_snmp.c @@ -0,0 +1,1312 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP SNMP Support. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#ifdef HAVE_SNMP +#include +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "keychain.h" +#include "smux.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_snmp.h" + +struct list *eigrp_snmp_iflist; + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +/* EIGRP-MIB - 1.3.6.1.4.1.9.9.449.1*/ +#define EIGRPMIB 1,3,6,1,4,1,9,9,449,1 + +/* EIGRP-MIB instances. */ +oid eigrp_oid[] = {EIGRPMIB}; + +/* EIGRP VPN entry */ +#define EIGRPVPNID 1 +#define EIGRPVPNNAME 2 + +/* EIGRP Traffic statistics entry */ +#define EIGRPASNUMBER 1 +#define EIGRPNBRCOUNT 2 +#define EIGRPHELLOSSENT 3 +#define EIGRPHELLOSRCVD 4 +#define EIGRPUPDATESSENT 5 +#define EIGRPUPDATESRCVD 6 +#define EIGRPQUERIESSENT 7 +#define EIGRPQUERIESRCVD 8 +#define EIGRPREPLIESSENT 9 +#define EIGRPREPLIESRCVD 10 +#define EIGRPACKSSENT 11 +#define EIGRPACKSRCVD 12 +#define EIGRPINPUTQHIGHMARK 13 +#define EIGRPINPUTQDROPS 14 +#define EIGRPSIAQUERIESSENT 15 +#define EIGRPSIAQUERIESRCVD 16 +#define EIGRPASROUTERIDTYPE 17 +#define EIGRPASROUTERID 18 +#define EIGRPTOPOROUTES 19 +#define EIGRPHEADSERIAL 20 +#define EIGRPNEXTSERIAL 21 +#define EIGRPXMITPENDREPLIES 22 +#define EIGRPXMITDUMMIES 23 + +/* EIGRP topology entry */ +#define EIGRPDESTNETTYPE 1 +#define EIGRPDESTNET 2 +#define EIGRPDESTNETPREFIXLEN 4 +#define EIGRPACTIVE 5 +#define EIGRPSTUCKINACTIVE 6 +#define EIGRPDESTSUCCESSORS 7 +#define EIGRPFDISTANCE 8 +#define EIGRPROUTEORIGINTYPE 9 +#define EIGRPROUTEORIGINADDRTYPE 10 +#define EIGRPROUTEORIGINADDR 11 +#define EIGRPNEXTHOPADDRESSTYPE 12 +#define EIGRPNEXTHOPADDRESS 13 +#define EIGRPNEXTHOPINTERFACE 14 +#define EIGRPDISTANCE 15 +#define EIGRPREPORTDISTANCE 16 + +/* EIGRP peer entry */ +#define EIGRPHANDLE 1 +#define EIGRPPEERADDRTYPE 2 +#define EIGRPPEERADDR 3 +#define EIGRPPEERIFINDEX 4 +#define EIGRPHOLDTIME 5 +#define EIGRPUPTIME 6 +#define EIGRPSRTT 7 +#define EIGRPRTO 8 +#define EIGRPPKTSENQUEUED 9 +#define EIGRPLASTSEQ 10 +#define EIGRPVERSION 11 +#define EIGRPRETRANS 12 +#define EIGRPRETRIES 13 + +/* EIGRP interface entry */ +#define EIGRPPEERCOUNT 3 +#define EIGRPXMITRELIABLEQ 4 +#define EIGRPXMITUNRELIABLEQ 5 +#define EIGRPMEANSRTT 6 +#define EIGRPPACINGRELIABLE 7 +#define EIGRPPACINGUNRELIABLE 8 +#define EIGRPMFLOWTIMER 9 +#define EIGRPPENDINGROUTES 10 +#define EIGRPHELLOINTERVAL 11 +#define EIGRPXMITNEXTSERIAL 12 +#define EIGRPUMCASTS 13 +#define EIGRPRMCASTS 14 +#define EIGRPUUCASTS 15 +#define EIGRPRUCASTS 16 +#define EIGRPMCASTEXCEPTS 17 +#define EIGRPCRPKTS 18 +#define EIGRPACKSSUPPRESSED 19 +#define EIGRPRETRANSSENT 20 +#define EIGRPOOSRCVD 21 +#define EIGRPAUTHMODE 22 +#define EIGRPAUTHKEYCHAIN 23 + +/* SNMP value hack. */ +#define COUNTER ASN_COUNTER +#define INTEGER ASN_INTEGER +#define GAUGE ASN_GAUGE +#define TIMETICKS ASN_TIMETICKS +#define IPADDRESS ASN_IPADDRESS +#define STRING ASN_OCTET_STR +#define IPADDRESSPREFIXLEN ASN_INTEGER +#define IPADDRESSTYPE ASN_INTEGER +#define INTERFACEINDEXORZERO ASN_INTEGER +#define UINTEGER ASN_UNSIGNED + +/* Hook functions. */ +static uint8_t *eigrpVpnEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *eigrpTraffStatsEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *eigrpTopologyEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *eigrpPeerEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *eigrpInterfaceEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + + +struct variable eigrp_variables[] = { + /* EIGRP vpn variables */ + {EIGRPVPNID, INTEGER, NOACCESS, eigrpVpnEntry, 4, {1, 1, 1, 1}}, + {EIGRPVPNNAME, STRING, RONLY, eigrpVpnEntry, 4, {1, 1, 1, 2}}, + + /* EIGRP traffic stats variables */ + {EIGRPASNUMBER, + UINTEGER, + NOACCESS, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 1}}, + {EIGRPNBRCOUNT, UINTEGER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 2}}, + {EIGRPHELLOSSENT, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 3}}, + {EIGRPHELLOSRCVD, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 4}}, + {EIGRPUPDATESSENT, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 5}}, + {EIGRPUPDATESRCVD, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 6}}, + {EIGRPQUERIESSENT, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 7}}, + {EIGRPQUERIESRCVD, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 8}}, + {EIGRPREPLIESSENT, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 9}}, + {EIGRPREPLIESRCVD, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 10}}, + {EIGRPACKSSENT, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 11}}, + {EIGRPACKSRCVD, COUNTER, RONLY, eigrpTraffStatsEntry, 4, {2, 1, 1, 12}}, + {EIGRPINPUTQHIGHMARK, + INTEGER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 13}}, + {EIGRPINPUTQDROPS, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 14}}, + {EIGRPSIAQUERIESSENT, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 15}}, + {EIGRPSIAQUERIESRCVD, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 16}}, + {EIGRPASROUTERIDTYPE, + IPADDRESSTYPE, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 17}}, + {EIGRPASROUTERID, + IPADDRESS, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 18}}, + {EIGRPTOPOROUTES, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 19}}, + {EIGRPHEADSERIAL, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 20}}, + {EIGRPNEXTSERIAL, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 21}}, + {EIGRPXMITPENDREPLIES, + INTEGER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 22}}, + {EIGRPXMITDUMMIES, + COUNTER, + RONLY, + eigrpTraffStatsEntry, + 4, + {2, 1, 1, 23}}, + + /* EIGRP topology variables */ + {EIGRPDESTNETTYPE, + IPADDRESSTYPE, + NOACCESS, + eigrpTopologyEntry, + 4, + {3, 1, 1, 1}}, + {EIGRPDESTNET, + IPADDRESSPREFIXLEN, + NOACCESS, + eigrpTopologyEntry, + 4, + {3, 1, 1, 2}}, + {EIGRPDESTNETPREFIXLEN, + IPADDRESSTYPE, + NOACCESS, + eigrpTopologyEntry, + 4, + {3, 1, 1, 4}}, + {EIGRPACTIVE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 5}}, + {EIGRPSTUCKINACTIVE, + INTEGER, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 6}}, + {EIGRPDESTSUCCESSORS, + INTEGER, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 7}}, + {EIGRPFDISTANCE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 8}}, + {EIGRPROUTEORIGINTYPE, + STRING, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 9}}, + {EIGRPROUTEORIGINADDRTYPE, + IPADDRESSTYPE, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 10}}, + {EIGRPROUTEORIGINADDR, + IPADDRESS, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 11}}, + {EIGRPNEXTHOPADDRESSTYPE, + IPADDRESSTYPE, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 12}}, + {EIGRPNEXTHOPADDRESS, + IPADDRESS, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 13}}, + {EIGRPNEXTHOPINTERFACE, + STRING, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 14}}, + {EIGRPDISTANCE, INTEGER, RONLY, eigrpTopologyEntry, 4, {3, 1, 1, 15}}, + {EIGRPREPORTDISTANCE, + INTEGER, + RONLY, + eigrpTopologyEntry, + 4, + {3, 1, 1, 16}}, + + /* EIGRP peer variables */ + {EIGRPHANDLE, INTEGER, NOACCESS, eigrpPeerEntry, 4, {4, 1, 1, 1}}, + {EIGRPPEERADDRTYPE, + IPADDRESSTYPE, + RONLY, + eigrpPeerEntry, + 4, + {4, 1, 1, 2}}, + {EIGRPPEERADDR, IPADDRESS, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 3}}, + {EIGRPPEERIFINDEX, + INTERFACEINDEXORZERO, + RONLY, + eigrpPeerEntry, + 4, + {4, 1, 1, 4}}, + {EIGRPHOLDTIME, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 5}}, + {EIGRPUPTIME, STRING, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 6}}, + {EIGRPSRTT, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 7}}, + {EIGRPRTO, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 8}}, + {EIGRPPKTSENQUEUED, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 9}}, + {EIGRPLASTSEQ, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 10}}, + {EIGRPVERSION, STRING, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 11}}, + {EIGRPRETRANS, COUNTER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 12}}, + {EIGRPRETRIES, INTEGER, RONLY, eigrpPeerEntry, 4, {4, 1, 1, 13}}, + + /* EIGRP interface variables */ + {EIGRPPEERCOUNT, GAUGE, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 3}}, + {EIGRPXMITRELIABLEQ, + GAUGE, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 4}}, + {EIGRPXMITUNRELIABLEQ, + GAUGE, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 5}}, + {EIGRPMEANSRTT, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 6}}, + {EIGRPPACINGRELIABLE, + INTEGER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 7}}, + {EIGRPPACINGUNRELIABLE, + INTEGER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 8}}, + {EIGRPMFLOWTIMER, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 9}}, + {EIGRPPENDINGROUTES, + GAUGE, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 10}}, + {EIGRPHELLOINTERVAL, + INTEGER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 11}}, + {EIGRPXMITNEXTSERIAL, + COUNTER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 12}}, + {EIGRPUMCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 13}}, + {EIGRPRMCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 14}}, + {EIGRPUUCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 15}}, + {EIGRPRUCASTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 16}}, + {EIGRPMCASTEXCEPTS, + COUNTER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 17}}, + {EIGRPCRPKTS, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 18}}, + {EIGRPACKSSUPPRESSED, + COUNTER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 19}}, + {EIGRPRETRANSSENT, + COUNTER, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 20}}, + {EIGRPOOSRCVD, COUNTER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 21}}, + {EIGRPAUTHMODE, INTEGER, RONLY, eigrpInterfaceEntry, 4, {5, 1, 1, 22}}, + {EIGRPAUTHKEYCHAIN, + STRING, + RONLY, + eigrpInterfaceEntry, + 4, + {5, 1, 1, 23}}}; + +static struct eigrp_neighbor *eigrp_snmp_nbr_lookup(struct eigrp *eigrp, + struct in_addr *nbr_addr, + unsigned int *ifindex) +{ + struct listnode *node, *nnode, *node2, *nnode2; + struct eigrp_interface *ei; + struct eigrp_neighbor *nbr; + + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (IPV4_ADDR_SAME(&nbr->src, nbr_addr)) { + return nbr; + } + } + } + return NULL; +} + +static struct eigrp_neighbor * +eigrp_snmp_nbr_lookup_next(struct in_addr *nbr_addr, unsigned int *ifindex, + int first) +{ + struct listnode *node, *nnode, *node2, *nnode2; + struct eigrp_interface *ei; + struct eigrp_neighbor *nbr; + struct eigrp_neighbor *min = NULL; + struct eigrp *eigrp; + + eigrp = eigrp_lookup(); + + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (first) { + if (!min) + min = nbr; + else if (ntohl(nbr->src.s_addr) + < ntohl(min->src.s_addr)) + min = nbr; + } else if (ntohl(nbr->src.s_addr) + > ntohl(nbr_addr->s_addr)) { + if (!min) + min = nbr; + else if (ntohl(nbr->src.s_addr) + < ntohl(min->src.s_addr)) + min = nbr; + } + } + } + if (min) { + *nbr_addr = min->src; + *ifindex = 0; + return min; + } + return NULL; +} + +static struct eigrp_neighbor *eigrpNbrLookup(struct variable *v, oid *name, + size_t *length, + struct in_addr *nbr_addr, + unsigned int *ifindex, int exact) +{ + unsigned int len; + int first; + struct eigrp_neighbor *nbr; + struct eigrp *eigrp; + + eigrp = eigrp_lookup(); + + if (!eigrp) + return NULL; + + if (exact) { + if (*length != v->namelen + IN_ADDR_SIZE + 1) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, nbr_addr); + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + return eigrp_snmp_nbr_lookup(eigrp, nbr_addr, ifindex); + } else { + first = 0; + len = *length - v->namelen; + + if (len == 0) + first = 1; + + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(name + v->namelen, len, nbr_addr); + + len = *length - v->namelen - IN_ADDR_SIZE; + if (len >= 1) + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + nbr = eigrp_snmp_nbr_lookup_next(nbr_addr, ifindex, first); + + if (nbr) { + *length = v->namelen + IN_ADDR_SIZE + 1; + oid_copy_in_addr(name + v->namelen, nbr_addr); + name[v->namelen + IN_ADDR_SIZE] = *ifindex; + return nbr; + } + } + return NULL; +} + + +static uint8_t *eigrpVpnEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct eigrp *eigrp; + + eigrp = eigrp_lookup(); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case EIGRPVPNID: /* 1 */ + /* The unique VPN identifier */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPVPNNAME: /* 2 */ + /* The name given to the VPN */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +static uint32_t eigrp_neighbor_count(struct eigrp *eigrp) +{ + uint32_t count; + struct eigrp_interface *ei; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + + if (eigrp == NULL) { + return 0; + } + + count = 0; + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (nbr->state == EIGRP_NEIGHBOR_UP) + count++; + } + } + + return count; +} + + +static uint8_t *eigrpTraffStatsEntry(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct eigrp *eigrp; + struct eigrp_interface *ei; + struct listnode *node, *nnode; + int counter; + + eigrp = eigrp_lookup(); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case EIGRPASNUMBER: /* 1 */ + /* AS-number of this EIGRP instance. */ + if (eigrp) + return SNMP_INTEGER(eigrp->AS); + else + return SNMP_INTEGER(0); + case EIGRPNBRCOUNT: /* 2 */ + /* Neighbor count of this EIGRP instance */ + if (eigrp) + return SNMP_INTEGER(eigrp_neighbor_count(eigrp)); + else + return SNMP_INTEGER(0); + case EIGRPHELLOSSENT: /* 3 */ + /* Hello packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->hello_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPHELLOSRCVD: /* 4 */ + /* Hello packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->hello_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPUPDATESSENT: /* 5 */ + /* Update packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->update_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPUPDATESRCVD: /* 6 */ + /* Update packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->update_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPQUERIESSENT: /* 7 */ + /* Querry packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->query_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPQUERIESRCVD: /* 8 */ + /* Querry packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->query_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPREPLIESSENT: /* 9 */ + /* Reply packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->reply_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPREPLIESRCVD: /* 10 */ + /* Reply packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->reply_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPACKSSENT: /* 11 */ + /* Acknowledgement packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->ack_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPACKSRCVD: /* 12 */ + /* Acknowledgement packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->ack_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPINPUTQHIGHMARK: /* 13 */ + /* The highest number of EIGRP packets in the input queue */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPINPUTQDROPS: /* 14 */ + /* The number of EIGRP packets dropped from the input queue */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPSIAQUERIESSENT: /* 15 */ + /* SIA querry packets output count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->siaQuery_out; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPSIAQUERIESRCVD: /* 16 */ + /* SIA querry packets input count */ + if (eigrp) { + counter = 0; + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, + ei)) { + counter += ei->siaQuery_in; + } + return SNMP_INTEGER(counter); + } else + return SNMP_INTEGER(0); + case EIGRPASROUTERIDTYPE: /* 17 */ + /* Whether the router ID is set manually or automatically */ + if (eigrp) + if (eigrp->router_id_static != 0) + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(1); + else + return SNMP_INTEGER(0); + case EIGRPASROUTERID: /* 18 */ + /* Router ID for this EIGRP AS */ + if (eigrp) + if (eigrp->router_id_static != 0) + return SNMP_INTEGER(eigrp->router_id_static); + else + return SNMP_INTEGER(eigrp->router_id); + else + return SNMP_INTEGER(0); + case EIGRPTOPOROUTES: /* 19 */ + /* The total number of EIGRP derived routes currently existing + in the topology table for the AS */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPHEADSERIAL: /* 20 */ + /* The serial number of the first route in the internal + sequence for an AS*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPNEXTSERIAL: /* 21 */ + /* The serial number that would be assigned to the next new + or changed route in the topology table for the AS*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPXMITPENDREPLIES: /* 22 */ + /* Total number of outstanding replies expected to queries + that have been sent to peers in the current AS*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPXMITDUMMIES: /* 23 */ + /* Total number of currently existing dummies associated with + * the AS*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +static uint8_t *eigrpTopologyEntry(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct eigrp *eigrp; + + eigrp = eigrp_lookup(); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case EIGRPDESTNETTYPE: /* 1 */ + /* The format of the destination IP network number for a single + route in the topology table*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPDESTNET: /* 2 */ + /* The destination IP network number for a single route in the + * topology table*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPDESTNETPREFIXLEN: /* 4 */ + /* The prefix length associated with the destination IP network + address + for a single route in the topology table in the AS*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPACTIVE: /* 5 */ + /* A value of true(1) indicates the route to the destination + network has failed + A value of false(2) indicates the route is stable + (passive).*/ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPSTUCKINACTIVE: /* 6 */ + /* A value of true(1) indicates that that this route which is in + active state + has not received any replies to queries for alternate paths + */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPDESTSUCCESSORS: /* 7 */ + /* Next routing hop for a path to the destination IP network */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPFDISTANCE: /* 8 */ + /* Minimum distance from this router to the destination IP + * network */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPROUTEORIGINTYPE: /* 9 */ + /* Text string describing the internal origin of the EIGRP route + */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPROUTEORIGINADDRTYPE: /* 10 */ + /* The format of the IP address defined as the origin of this + topology route entry */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPROUTEORIGINADDR: /* 11 */ + /* If the origin of the topology route entry is external to this + router, + then this object is the IP address of the router from which + it originated */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPNEXTHOPADDRESSTYPE: /* 12 */ + /* The format of the next hop IP address */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPNEXTHOPADDRESS: /* 13 */ + /* Next hop IP address for the route */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPNEXTHOPINTERFACE: /* 14 */ + /* The interface through which the next hop IP address is + * reached */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPDISTANCE: /* 15 */ + /* The computed distance to the destination network entry from + * this router */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPREPORTDISTANCE: /* 16 */ + /* The computed distance to the destination network in the + topology entry + reported to this router by the originator of this route */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +static uint8_t *eigrpPeerEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct eigrp *eigrp; + struct eigrp_interface *ei; + struct eigrp_neighbor *nbr; + struct in_addr nbr_addr; + unsigned int ifindex; + + eigrp = eigrp_lookup(); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&nbr_addr, 0, sizeof(nbr_addr)); + ifindex = 0; + + nbr = eigrpNbrLookup(v, name, length, &nbr_addr, &ifindex, exact); + if (!nbr) + return NULL; + ei = nbr->ei; + if (!ei) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case EIGRPHANDLE: /* 1 */ + /* The unique internal identifier for the peer in the AS */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPEERADDRTYPE: /* 2 */ + /* The format of the remote source IP address used by the peer + */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPEERADDR: /* 3 */ + /* The source IP address used by the peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPEERIFINDEX: /* 4 */ + /* The ifIndex of the interface on this router */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPHOLDTIME: /* 5 */ + /* How much time must pass without receiving a hello packet from + this + EIGRP peer before this router declares the peer down */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPUPTIME: /* 6 */ + /* The elapsed time since the EIGRP adjacency was first + * established */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPSRTT: /* 7 */ + /* The computed smooth round trip time for packets to and from + * the peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRTO: /* 8 */ + /* The computed retransmission timeout for the peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPKTSENQUEUED: /* 9 */ + /* The number of any EIGRP packets currently enqueued */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPLASTSEQ: /* 10 */ + /* sequence number of the last EIGRP packet sent to this peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPVERSION: /* 11 */ + /* The EIGRP version information reported by the remote peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRETRANS: /* 12 */ + /* The cumulative number of retransmissions to this peer */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRETRIES: /* 13 */ + /* The number of times the current unacknowledged packet has + * been retried */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +static uint8_t *eigrpInterfaceEntry(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct eigrp *eigrp; + struct listnode *node, *nnode; + struct keychain *keychain; + struct list *keylist; + + eigrp = eigrp_lookup(); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case EIGRPPEERCOUNT: /* 3 */ + /* The number of EIGRP adjacencies currently formed with + peers reached through this interface */ + if (eigrp) { + return SNMP_INTEGER(eigrp_neighbor_count(eigrp)); + } else + return SNMP_INTEGER(0); + case EIGRPXMITRELIABLEQ: /* 4 */ + /* The number of EIGRP packets currently waiting in the reliable + transport transmission queue */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPXMITUNRELIABLEQ: /* 5 */ + /* The number of EIGRP packets currently waiting in the + unreliable + transport transmission queue */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPMEANSRTT: /* 6 */ + /* The average of all the computed smooth round trip time values + for a packet to and from all peers established on this + interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPACINGRELIABLE: /* 7 */ + /* The configured time interval between EIGRP packet + * transmissions */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPACINGUNRELIABLE: /* 8 */ + /* The configured time interval between EIGRP packet + transmissions + on the interface when the unreliable transport method is used + */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPMFLOWTIMER: /* 9 */ + /* The configured multicast flow control timer value */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPPENDINGROUTES: /* 10 */ + /* The number of queued EIGRP routing updates awaiting + * transmission */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPHELLOINTERVAL: /* 11 */ + /* The configured time interval between Hello packet + * transmissions */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPXMITNEXTSERIAL: /* 12 */ + /* The serial number of the next EIGRP packet that is to be + queued + for transmission */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPUMCASTS: /* 13 */ + /* The total number of unreliable EIGRP multicast packets sent + on this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRMCASTS: /* 14 */ + /* The total number of reliable EIGRP multicast packets sent + on this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPUUCASTS: /* 15 */ + /* The total number of unreliable EIGRP unicast packets sent + on this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRUCASTS: /* 16 */ + /* The total number of reliable EIGRP unicast packets sent + on this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPMCASTEXCEPTS: /* 17 */ + /* The total number of EIGRP multicast exception transmissions + */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPCRPKTS: /* 18 */ + /* The total number EIGRP Conditional-Receive packets sent on + * this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPACKSSUPPRESSED: /* 19 */ + /* The total number of individual EIGRP acknowledgement packets + that have been + suppressed and combined in an already enqueued outbound + reliable packet on this interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPRETRANSSENT: /* 20 */ + /* The total number EIGRP packet retransmissions sent on the + * interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPOOSRCVD: /* 21 */ + /* The total number of out-of-sequence EIGRP packets received */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPAUTHMODE: /* 22 */ + /* The EIGRP authentication mode of the interface */ + if (eigrp) { + return SNMP_INTEGER(1); + } else + return SNMP_INTEGER(0); + case EIGRPAUTHKEYCHAIN: /* 23 */ + /* The name of the authentication key-chain configured + on this interface. */ + keylist = keychain_list_get(); + for (ALL_LIST_ELEMENTS(keylist, node, nnode, keychain)) { + return (uint8_t *)keychain->name; + } + if (eigrp && keychain) { + *var_len = str_len(keychain->name); + return (uint8_t *)keychain->name; + } else + return (uint8_t *)"TEST"; + break; + default: + return NULL; + } + return NULL; +} + +/* Register EIGRP-MIB. */ +void eigrp_snmp_init() +{ + eigrp_snmp_iflist = list_new(); + smux_init(eigrp_om->master); + REGISTER_MIB("ciscoEigrpMIB", eigrp_variables, variable, eigrp_oid); +} +#endif diff --git a/eigrpd/eigrp_snmp.h b/eigrpd/eigrp_snmp.h new file mode 100644 index 0000000..3930b5c --- /dev/null +++ b/eigrpd/eigrp_snmp.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP SNMP Support. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + + +#ifndef _ZEBRA_EIGRP_SNMP_H +#define _ZEBRA_EIGRP_SNMP_H + +extern void eigrp_snmp_init(void); + + +#endif /* _ZEBRA_EIGRP_SNMP_H */ diff --git a/eigrpd/eigrp_structs.h b/eigrpd/eigrp_structs.h new file mode 100644 index 0000000..8735c48 --- /dev/null +++ b/eigrpd/eigrp_structs.h @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Definition of Data Structures. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_STRUCTS_H_ +#define _ZEBRA_EIGRP_STRUCTS_H_ + +#include "filter.h" + +#include "eigrpd/eigrp_const.h" +#include "eigrpd/eigrp_macros.h" + +struct eigrp_metrics { + uint32_t delay; + uint32_t bandwidth; + uint8_t mtu[3]; + uint8_t hop_count; + uint8_t reliability; + uint8_t load; + uint8_t tag; + uint8_t flags; +}; + +struct eigrp_extdata { + uint32_t orig; + uint32_t as; + uint32_t tag; + uint32_t metric; + uint16_t reserved; + uint8_t protocol; + uint8_t flags; +}; + +struct eigrp { + vrf_id_t vrf_id; + + uint16_t AS; /* Autonomous system number */ + uint16_t vrid; /* Virtual Router ID */ + uint8_t k_values[6]; /*Array for K values configuration*/ + uint8_t variance; /*Metric variance multiplier*/ + uint8_t max_paths; /*Maximum allowed paths for 1 prefix*/ + + /*Name of this EIGRP instance*/ + char *name; + + /* EIGRP Router ID. */ + struct in_addr router_id; /* Configured automatically. */ + struct in_addr router_id_static; /* Configured manually. */ + + struct list *eiflist; /* eigrp interfaces */ + uint8_t passive_interface_default; /* passive-interface default */ + + int fd; + unsigned int maxsndbuflen; + + uint32_t sequence_number; /*Global EIGRP sequence number*/ + + struct stream *ibuf; + struct list *oi_write_q; + + /*Threads*/ + struct event *t_write; + struct event *t_read; + struct event *t_distribute; /* timer for distribute list */ + + struct route_table *networks; /* EIGRP config networks. */ + + struct route_table *topology_table; + + uint64_t serno; /* Global serial number counter for topology entry + changes*/ + uint64_t serno_last_update; /* Highest serial number of information send + by last update*/ + struct list *topology_changes_internalIPV4; + struct list *topology_changes_externalIPV4; + + /*Neighbor self*/ + struct eigrp_neighbor *neighbor_self; + + /*Configured metric for redistributed routes*/ + struct eigrp_metrics dmetric[ZEBRA_ROUTE_MAX + 1]; + int redistribute; /* Num of redistributed protocols. */ + + /* Access-list. */ + struct access_list *list[EIGRP_FILTER_MAX]; + /* Prefix-list. */ + struct prefix_list *prefix[EIGRP_FILTER_MAX]; + /* Route-map. */ + struct route_map *routemap[EIGRP_FILTER_MAX]; + + /* For redistribute route map. */ + struct { + char *name; + struct route_map *map; + int metric_config; + uint32_t metric; + } route_map[ZEBRA_ROUTE_MAX]; + + /* distribute_ctx */ + struct distribute_ctx *distribute_ctx; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(eigrp); + +struct eigrp_if_params { + uint8_t passive_interface; + uint32_t v_hello; + uint16_t v_wait; + uint8_t type; /* type of interface */ + uint32_t bandwidth; + uint32_t delay; + uint8_t reliability; + uint8_t load; + + char *auth_keychain; /* Associated keychain with interface*/ + int auth_type; /* EIGRP authentication type */ +}; + +enum { MEMBER_ALLROUTERS = 0, + MEMBER_MAX, +}; + +/*EIGRP interface structure*/ +struct eigrp_interface { + struct eigrp_if_params params; + + /*multicast group refcnts */ + bool member_allrouters; + + /* This interface's parent eigrp instance. */ + struct eigrp *eigrp; + + /* Interface data from zebra. */ + struct interface *ifp; + + /* Packet send buffer. */ + struct eigrp_fifo *obuf; /* Output queue */ + + /* To which multicast groups do we currently belong? */ + + uint32_t curr_bandwidth; + uint32_t curr_mtu; + + uint8_t multicast_memberships; + + /* EIGRP Network Type. */ + uint8_t type; + + struct prefix address; /* Interface prefix */ + + /* Neighbor information. */ + struct list *nbrs; /* EIGRP Neighbor List */ + + /* Threads. */ + struct event *t_hello; /* timer */ + struct event *t_distribute; /* timer for distribute list */ + + int on_write_q; + + /* Statistics fields. */ + uint32_t hello_in; /* Hello message input count. */ + uint32_t update_in; /* Update message input count. */ + uint32_t query_in; /* Querry message input count. */ + uint32_t reply_in; /* Reply message input count. */ + uint32_t hello_out; /* Hello message output count. */ + uint32_t update_out; /* Update message output count. */ + uint32_t query_out; /* Query message output count. */ + uint32_t reply_out; /* Reply message output count. */ + uint32_t siaQuery_in; + uint32_t siaQuery_out; + uint32_t siaReply_in; + uint32_t siaReply_out; + uint32_t ack_out; + uint32_t ack_in; + + uint32_t crypt_seqnum; /* Cryptographic Sequence Number */ + + /* Access-list. */ + struct access_list *list[EIGRP_FILTER_MAX]; + /* Prefix-list. */ + struct prefix_list *prefix[EIGRP_FILTER_MAX]; + /* Route-map. */ + struct route_map *routemap[EIGRP_FILTER_MAX]; +}; + +/* Determines if it is first or last packet + * when packet consists of multiple packet + * chunks because of many route TLV + * (all won't fit into one packet) */ +enum Packet_part_type { + EIGRP_PACKET_PART_NA, + EIGRP_PACKET_PART_FIRST, + EIGRP_PACKET_PART_LAST +}; + +/* Neighbor Data Structure */ +struct eigrp_neighbor { + /* This neighbor's parent eigrp interface. */ + struct eigrp_interface *ei; + + /* EIGRP neighbor Information */ + uint8_t state; /* neigbor status. */ + + uint32_t recv_sequence_number; /* Last received sequence Number. */ + uint32_t init_sequence_number; + + /*If packet is unacknowledged, we try to send it again 16 times*/ + uint8_t retrans_counter; + + struct in_addr src; /* Neighbor Src address. */ + + uint8_t os_rel_major; // system version - just for show + uint8_t os_rel_minor; // system version - just for show + uint8_t tlv_rel_major; // eigrp version - tells us what TLV format to + // use + uint8_t tlv_rel_minor; // eigrp version - tells us what TLV format to + // use + + uint8_t K1; + uint8_t K2; + uint8_t K3; + uint8_t K4; + uint8_t K5; + uint8_t K6; + + /* Timer values. */ + uint16_t v_holddown; + + /* Threads. */ + struct event *t_holddown; + struct event *t_nbr_send_gr; /* thread for sending multiple GR packet + chunks */ + + struct eigrp_fifo *retrans_queue; + struct eigrp_fifo *multicast_queue; + + uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */ + + /* prefixes not received from neighbor during Graceful restart */ + struct list *nbr_gr_prefixes; + /* prefixes not yet send to neighbor during Graceful restart */ + struct list *nbr_gr_prefixes_send; + /* if packet is first or last during Graceful restart */ + enum Packet_part_type nbr_gr_packet_type; +}; + +//--------------------------------------------------------------------------------------------------------------------------------------------- + + +struct eigrp_packet { + struct eigrp_packet *next; + struct eigrp_packet *previous; + + /* Pointer to data stream. */ + struct stream *s; + + /* IP destination address. */ + struct in_addr dst; + + /*Packet retransmission thread*/ + struct event *t_retrans_timer; + + /*Packet retransmission counter*/ + uint8_t retrans_counter; + + uint32_t sequence_number; + + /* EIGRP packet length. */ + uint16_t length; + + struct eigrp_neighbor *nbr; +}; + +struct eigrp_fifo { + struct eigrp_packet *head; + struct eigrp_packet *tail; + + unsigned long count; +}; + +struct eigrp_header { + uint8_t version; + uint8_t opcode; + uint16_t checksum; + uint32_t flags; + uint32_t sequence; + uint32_t ack; + uint16_t vrid; + uint16_t ASNumber; + char *tlv[0]; + +} __attribute__((packed)); + + +/** + * Generic TLV type used for packet decoding. + * + * +-----+------------------+ + * | | | | + * | Type| Len | Vector | + * | | | | + * +-----+------------------+ + */ +struct eigrp_tlv_hdr_type { + uint16_t type; + uint16_t length; + uint8_t value[0]; +} __attribute__((packed)); + +struct TLV_Parameter_Type { + uint16_t type; + uint16_t length; + uint8_t K1; + uint8_t K2; + uint8_t K3; + uint8_t K4; + uint8_t K5; + uint8_t K6; + uint16_t hold_time; +} __attribute__((packed)); + +struct TLV_MD5_Authentication_Type { + uint16_t type; + uint16_t length; + uint16_t auth_type; + uint16_t auth_length; + uint32_t key_id; + uint32_t key_sequence; + uint8_t Nullpad[8]; + uint8_t digest[EIGRP_AUTH_TYPE_MD5_LEN]; + +} __attribute__((packed)); + +struct TLV_SHA256_Authentication_Type { + uint16_t type; + uint16_t length; + uint16_t auth_type; + uint16_t auth_length; + uint32_t key_id; + uint32_t key_sequence; + uint8_t Nullpad[8]; + uint8_t digest[EIGRP_AUTH_TYPE_SHA256_LEN]; + +} __attribute__((packed)); + +struct TLV_Sequence_Type { + uint16_t type; + uint16_t length; + uint8_t addr_length; + struct in_addr *addresses; +} __attribute__((packed)); + +struct TLV_Next_Multicast_Sequence { + uint16_t type; + uint16_t length; + uint32_t multicast_sequence; +} __attribute__((packed)); + +struct TLV_Software_Type { + uint16_t type; + uint16_t length; + uint8_t vender_major; + uint8_t vender_minor; + uint8_t eigrp_major; + uint8_t eigrp_minor; +} __attribute__((packed)); + +struct TLV_IPv4_Internal_type { + uint16_t type; + uint16_t length; + struct in_addr forward; + + /*Metrics*/ + struct eigrp_metrics metric; + + uint8_t prefix_length; + + struct in_addr destination; +} __attribute__((packed)); + +struct TLV_IPv4_External_type { + uint16_t type; + uint16_t length; + struct in_addr next_hop; + struct in_addr originating_router; + uint32_t originating_as; + uint32_t administrative_tag; + uint32_t external_metric; + uint16_t reserved; + uint8_t external_protocol; + uint8_t external_flags; + + /*Metrics*/ + struct eigrp_metrics metric; + + uint8_t prefix_length; + unsigned char destination_part[4]; + struct in_addr destination; +} __attribute__((packed)); + +/* EIGRP Peer Termination TLV - used for hard restart */ +struct TLV_Peer_Termination_type { + uint16_t type; + uint16_t length; + uint8_t unknown; + uint32_t neighbor_ip; +} __attribute__((packed)); + +/* Who executed Graceful restart */ +enum GR_type { EIGRP_GR_MANUAL, EIGRP_GR_FILTER }; + +//--------------------------------------------------------------------------------------------------------------------------------------------- + +/* EIGRP Topology table node structure */ +struct eigrp_prefix_descriptor { + struct list *entries, *rij; + uint32_t fdistance; // FD + uint32_t rdistance; // RD + uint32_t distance; // D + struct eigrp_metrics reported_metric; // RD for sending + + uint8_t nt; // network type + uint8_t state; // route fsm state + uint8_t af; // address family + uint8_t req_action; // required action + + struct prefix *destination; + + // If network type is REMOTE_EXTERNAL, pointer will have reference to + // its external TLV + struct TLV_IPv4_External_type *extTLV; + + uint64_t serno; /*Serial number for this entry. Increased with each + change of entry*/ +}; + +/* EIGRP Topology table record structure */ +struct eigrp_route_descriptor { + uint16_t type; + uint16_t afi; + + struct eigrp_prefix_descriptor *prefix; + struct eigrp_neighbor *adv_router; + struct in_addr nexthop; + + uint32_t reported_distance; // distance reported by neighbor + uint32_t distance; // sum of reported distance and link cost to + // advertised neighbor + + struct eigrp_metrics reported_metric; + struct eigrp_metrics total_metric; + + struct eigrp_metrics metric; + struct eigrp_extdata extdata; + + uint8_t flags; // used for marking successor and FS + + struct eigrp_interface *ei; // pointer for case of connected entry +}; + +//--------------------------------------------------------------------------------------------------------------------------------------------- +typedef enum { + EIGRP_CONNECTED, + EIGRP_INT, + EIGRP_EXT, +} msg_data_t; + +/* EIGRP Finite State Machine */ + +struct eigrp_fsm_action_message { + uint8_t packet_type; // UPDATE, QUERY, SIAQUERY, SIAREPLY + struct eigrp *eigrp; // which thread sent mesg + struct eigrp_neighbor *adv_router; // advertising neighbor + struct eigrp_route_descriptor *entry; + struct eigrp_prefix_descriptor *prefix; + msg_data_t data_type; // internal or external tlv type + struct eigrp_metrics metrics; + enum metric_change change; +}; + +#endif /* _ZEBRA_EIGRP_STRUCTURES_H_ */ diff --git a/eigrpd/eigrp_topology.c b/eigrpd/eigrp_topology.c new file mode 100644 index 0000000..f17be8f --- /dev/null +++ b/eigrpd/eigrp_topology.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Topology Table. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "log.h" +#include "linklist.h" +#include "vty.h" +#include "lib_errors.h" + +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_metric.h" + +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_ROUTE_DESCRIPTOR, "EIGRP Nexthop Entry"); +DEFINE_MTYPE(EIGRPD, EIGRP_PREFIX_DESCRIPTOR, "EIGRP Prefix"); + +static int eigrp_route_descriptor_cmp(struct eigrp_route_descriptor *rd1, + struct eigrp_route_descriptor *rd2); + +/* + * Returns linkedlist used as topology table + * cmp - assigned function for comparing topology nodes + * del - assigned function executed before deleting topology node by list + * function + */ +struct route_table *eigrp_topology_new(void) +{ + return route_table_init(); +} + +/* + * Returns new created toplogy node + * cmp - assigned function for comparing topology entry + */ +struct eigrp_prefix_descriptor *eigrp_prefix_descriptor_new(void) +{ + struct eigrp_prefix_descriptor *new; + new = XCALLOC(MTYPE_EIGRP_PREFIX_DESCRIPTOR, + sizeof(struct eigrp_prefix_descriptor)); + new->entries = list_new(); + new->rij = list_new(); + new->entries->cmp = (int (*)(void *, void *))eigrp_route_descriptor_cmp; + new->distance = new->fdistance = new->rdistance = EIGRP_MAX_METRIC; + new->destination = NULL; + + return new; +} + +/* + * Topology entry comparison + */ +static int eigrp_route_descriptor_cmp(struct eigrp_route_descriptor *entry1, + struct eigrp_route_descriptor *entry2) +{ + if (entry1->distance < entry2->distance) + return -1; + if (entry1->distance > entry2->distance) + return 1; + + return 0; +} + +/* + * Returns new topology entry + */ + +struct eigrp_route_descriptor *eigrp_route_descriptor_new(void) +{ + struct eigrp_route_descriptor *new; + + new = XCALLOC(MTYPE_EIGRP_ROUTE_DESCRIPTOR, + sizeof(struct eigrp_route_descriptor)); + new->reported_distance = EIGRP_MAX_METRIC; + new->distance = EIGRP_MAX_METRIC; + + return new; +} + +/* + * Freeing topology table list + */ +void eigrp_topology_free(struct eigrp *eigrp, struct route_table *table) +{ + eigrp_topology_delete_all(eigrp, table); + route_table_finish(table); +} + +/* + * Adding topology node to topology table + */ +void eigrp_prefix_descriptor_add(struct route_table *topology, + struct eigrp_prefix_descriptor *pe) +{ + struct route_node *rn; + + rn = route_node_get(topology, pe->destination); + if (rn->info) { + if (IS_DEBUG_EIGRP_EVENT) + zlog_debug( + "%s: %pFX Should we have found this entry in the topo table?", + __func__, pe->destination); + route_unlock_node(rn); + } + + rn->info = pe; +} + +/* + * Adding topology entry to topology node + */ +void eigrp_route_descriptor_add(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *node, + struct eigrp_route_descriptor *entry) +{ + struct list *l = list_new(); + + listnode_add(l, entry); + + if (listnode_lookup(node->entries, entry) == NULL) { + listnode_add_sort(node->entries, entry); + entry->prefix = node; + + eigrp_zebra_route_add(eigrp, node->destination, + l, node->fdistance); + } + + list_delete(&l); +} + +/* + * Deleting topology node from topology table + */ +void eigrp_prefix_descriptor_delete(struct eigrp *eigrp, + struct route_table *table, + struct eigrp_prefix_descriptor *pe) +{ + struct eigrp_route_descriptor *ne; + struct listnode *node, *nnode; + struct route_node *rn; + + if (!eigrp) + return; + + rn = route_node_lookup(table, pe->destination); + if (!rn) + return; + + /* + * Emergency removal of the node from this list. + * Whatever it is. + */ + listnode_delete(eigrp->topology_changes_internalIPV4, pe); + + for (ALL_LIST_ELEMENTS(pe->entries, node, nnode, ne)) + eigrp_route_descriptor_delete(eigrp, pe, ne); + list_delete(&pe->entries); + list_delete(&pe->rij); + eigrp_zebra_route_delete(eigrp, pe->destination); + prefix_free(&pe->destination); + + rn->info = NULL; + route_unlock_node(rn); // Lookup above + route_unlock_node(rn); // Initial creation + XFREE(MTYPE_EIGRP_PREFIX_DESCRIPTOR, pe); +} + +/* + * Deleting topology entry from topology node + */ +void eigrp_route_descriptor_delete(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *node, + struct eigrp_route_descriptor *entry) +{ + if (listnode_lookup(node->entries, entry) != NULL) { + listnode_delete(node->entries, entry); + eigrp_zebra_route_delete(eigrp, node->destination); + XFREE(MTYPE_EIGRP_ROUTE_DESCRIPTOR, entry); + } +} + +/* + * Deleting all nodes from topology table + */ +void eigrp_topology_delete_all(struct eigrp *eigrp, + struct route_table *topology) +{ + struct route_node *rn; + struct eigrp_prefix_descriptor *pe; + + for (rn = route_top(topology); rn; rn = route_next(rn)) { + pe = rn->info; + + if (!pe) + continue; + + eigrp_prefix_descriptor_delete(eigrp, topology, pe); + } +} + +struct eigrp_prefix_descriptor * +eigrp_topology_table_lookup_ipv4(struct route_table *table, + struct prefix *address) +{ + struct eigrp_prefix_descriptor *pe; + struct route_node *rn; + + rn = route_node_lookup(table, address); + if (!rn) + return NULL; + + pe = rn->info; + + route_unlock_node(rn); + + return pe; +} + +/* + * For a future optimization, put the successor list into it's + * own separate list from the full list? + * + * That way we can clean up all the list_new and list_delete's + * that we are doing. DBS + */ +struct list * +eigrp_topology_get_successor(struct eigrp_prefix_descriptor *table_node) +{ + struct list *successors = list_new(); + struct eigrp_route_descriptor *data; + struct listnode *node1, *node2; + + for (ALL_LIST_ELEMENTS(table_node->entries, node1, node2, data)) { + if (data->flags & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG) { + listnode_add(successors, data); + } + } + + /* + * If we have no successors return NULL + */ + if (!successors->count) { + list_delete(&successors); + successors = NULL; + } + + return successors; +} + +struct list * +eigrp_topology_get_successor_max(struct eigrp_prefix_descriptor *table_node, + unsigned int maxpaths) +{ + struct list *successors = eigrp_topology_get_successor(table_node); + + if (successors && successors->count > maxpaths) { + do { + struct listnode *node = listtail(successors); + + list_delete_node(successors, node); + + } while (successors->count > maxpaths); + } + + return successors; +} + +struct eigrp_route_descriptor * +eigrp_route_descriptor_lookup(struct list *entries, struct eigrp_neighbor *nbr) +{ + struct eigrp_route_descriptor *data; + struct listnode *node, *nnode; + for (ALL_LIST_ELEMENTS(entries, node, nnode, data)) { + if (data->adv_router == nbr) { + return data; + } + } + + return NULL; +} + +/* Lookup all prefixes from specified neighbor */ +struct list *eigrp_neighbor_prefixes_lookup(struct eigrp *eigrp, + struct eigrp_neighbor *nbr) +{ + struct listnode *node2, *node22; + struct eigrp_route_descriptor *entry; + struct eigrp_prefix_descriptor *pe; + struct route_node *rn; + + /* create new empty list for prefixes storage */ + struct list *prefixes = list_new(); + + /* iterate over all prefixes in topology table */ + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + pe = rn->info; + /* iterate over all neighbor entry in prefix */ + for (ALL_LIST_ELEMENTS(pe->entries, node2, node22, entry)) { + /* if entry is from specified neighbor, add to list */ + if (entry->adv_router == nbr) { + listnode_add(prefixes, pe); + } + } + } + + /* return list of prefixes from specified neighbor */ + return prefixes; +} + +enum metric_change +eigrp_topology_update_distance(struct eigrp_fsm_action_message *msg) +{ + struct eigrp *eigrp = msg->eigrp; + struct eigrp_prefix_descriptor *prefix = msg->prefix; + struct eigrp_route_descriptor *entry = msg->entry; + enum metric_change change = METRIC_SAME; + uint32_t new_reported_distance; + + assert(entry); + + switch (msg->data_type) { + case EIGRP_CONNECTED: + if (prefix->nt == EIGRP_TOPOLOGY_TYPE_CONNECTED) + return change; + + change = METRIC_DECREASE; + break; + case EIGRP_INT: + if (prefix->nt == EIGRP_TOPOLOGY_TYPE_CONNECTED) { + change = METRIC_INCREASE; + goto distance_done; + } + if (eigrp_metrics_is_same(msg->metrics, + entry->reported_metric)) { + return change; // No change + } + + new_reported_distance = + eigrp_calculate_metrics(eigrp, msg->metrics); + + if (entry->reported_distance < new_reported_distance) { + change = METRIC_INCREASE; + goto distance_done; + } else + change = METRIC_DECREASE; + + entry->reported_metric = msg->metrics; + entry->reported_distance = new_reported_distance; + eigrp_calculate_metrics(eigrp, msg->metrics); + entry->distance = eigrp_calculate_total_metrics(eigrp, entry); + break; + case EIGRP_EXT: + if (prefix->nt == EIGRP_TOPOLOGY_TYPE_REMOTE_EXTERNAL) { + if (eigrp_metrics_is_same(msg->metrics, + entry->reported_metric)) + return change; + } else { + change = METRIC_INCREASE; + goto distance_done; + } + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: Please implement handler", + __func__); + break; + } +distance_done: + /* + * Move to correct position in list according to new distance + */ + listnode_delete(prefix->entries, entry); + listnode_add_sort(prefix->entries, entry); + + return change; +} + +void eigrp_topology_update_all_node_flags(struct eigrp *eigrp) +{ + struct eigrp_prefix_descriptor *pe; + struct route_node *rn; + + if (!eigrp) + return; + + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + pe = rn->info; + + if (!pe) + continue; + + eigrp_topology_update_node_flags(eigrp, pe); + } +} + +void eigrp_topology_update_node_flags(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *dest) +{ + struct listnode *node; + struct eigrp_route_descriptor *entry; + + for (ALL_LIST_ELEMENTS_RO(dest->entries, node, entry)) { + if (entry->reported_distance < dest->fdistance) { + // is feasible successor, can be successor + if (((uint64_t)entry->distance + <= (uint64_t)dest->distance + * (uint64_t)eigrp->variance) + && entry->distance != EIGRP_MAX_METRIC) { + // is successor + entry->flags |= + EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG; + entry->flags &= + ~EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG; + } else { + // is feasible successor only + entry->flags |= + EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG; + entry->flags &= + ~EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG; + } + } else { + entry->flags &= ~EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG; + entry->flags &= ~EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG; + } + } +} + +void eigrp_update_routing_table(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *prefix) +{ + struct list *successors; + struct listnode *node; + struct eigrp_route_descriptor *entry; + + successors = eigrp_topology_get_successor_max(prefix, eigrp->max_paths); + + if (successors) { + eigrp_zebra_route_add(eigrp, prefix->destination, successors, + prefix->fdistance); + for (ALL_LIST_ELEMENTS_RO(successors, node, entry)) + entry->flags |= EIGRP_ROUTE_DESCRIPTOR_INTABLE_FLAG; + + list_delete(&successors); + } else { + eigrp_zebra_route_delete(eigrp, prefix->destination); + for (ALL_LIST_ELEMENTS_RO(prefix->entries, node, entry)) + entry->flags &= ~EIGRP_ROUTE_DESCRIPTOR_INTABLE_FLAG; + } +} + +void eigrp_topology_neighbor_down(struct eigrp *eigrp, + struct eigrp_neighbor *nbr) +{ + struct listnode *node2, *node22; + struct eigrp_prefix_descriptor *pe; + struct eigrp_route_descriptor *entry; + struct route_node *rn; + + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + pe = rn->info; + + if (!pe) + continue; + + for (ALL_LIST_ELEMENTS(pe->entries, node2, node22, entry)) { + struct eigrp_fsm_action_message msg; + + if (entry->adv_router != nbr) + continue; + + memset(&msg, 0, sizeof(msg)); + msg.metrics.delay = EIGRP_MAX_METRIC; + msg.packet_type = EIGRP_OPC_UPDATE; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.entry = entry; + msg.prefix = pe; + eigrp_fsm_event(&msg); + } + } + + eigrp_query_send_all(eigrp); + eigrp_update_send_all(eigrp, nbr->ei); +} + +void eigrp_update_topology_table_prefix(struct eigrp *eigrp, + struct route_table *table, + struct eigrp_prefix_descriptor *prefix) +{ + struct listnode *node1, *node2; + + struct eigrp_route_descriptor *entry; + for (ALL_LIST_ELEMENTS(prefix->entries, node1, node2, entry)) { + if (entry->distance == EIGRP_MAX_METRIC) { + eigrp_route_descriptor_delete(eigrp, prefix, entry); + } + } + if (prefix->distance == EIGRP_MAX_METRIC + && prefix->nt != EIGRP_TOPOLOGY_TYPE_CONNECTED) { + eigrp_prefix_descriptor_delete(eigrp, table, prefix); + } +} diff --git a/eigrpd/eigrp_topology.h b/eigrpd/eigrp_topology.h new file mode 100644 index 0000000..426c290 --- /dev/null +++ b/eigrpd/eigrp_topology.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Topology Table. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _ZEBRA_EIGRP_TOPOLOGY_H +#define _ZEBRA_EIGRP_TOPOLOGY_H + +#include "memory.h" + +DECLARE_MTYPE(EIGRP_PREFIX_DESCRIPTOR); + +/* EIGRP Topology table related functions. */ +extern struct route_table *eigrp_topology_new(void); +extern void eigrp_topology_init(struct route_table *table); +extern struct eigrp_prefix_descriptor *eigrp_prefix_descriptor_new(void); +extern struct eigrp_route_descriptor *eigrp_route_descriptor_new(void); +extern void eigrp_topology_free(struct eigrp *eigrp, struct route_table *table); +extern void eigrp_prefix_descriptor_add(struct route_table *table, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_route_descriptor_add(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *pe, + struct eigrp_route_descriptor *ne); +extern void eigrp_prefix_descriptor_delete(struct eigrp *eigrp, + struct route_table *table, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_route_descriptor_delete(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *pe, + struct eigrp_route_descriptor *ne); +extern void eigrp_topology_delete_all(struct eigrp *eigrp, + struct route_table *table); +extern struct eigrp_prefix_descriptor * +eigrp_topology_table_lookup_ipv4(struct route_table *table, struct prefix *p); +extern struct list * +eigrp_topology_get_successor(struct eigrp_prefix_descriptor *pe); +extern struct list * +eigrp_topology_get_successor_max(struct eigrp_prefix_descriptor *pe, + unsigned int maxpaths); +extern struct eigrp_route_descriptor * +eigrp_route_descriptor_lookup(struct list *entries, + struct eigrp_neighbor *neigh); +extern struct list *eigrp_neighbor_prefixes_lookup(struct eigrp *eigrp, + struct eigrp_neighbor *n); +extern void eigrp_topology_update_all_node_flags(struct eigrp *eigrp); +extern void +eigrp_topology_update_node_flags(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *pe); +extern enum metric_change +eigrp_topology_update_distance(struct eigrp_fsm_action_message *msg); +extern void eigrp_update_routing_table(struct eigrp *eigrp, + struct eigrp_prefix_descriptor *pe); +extern void eigrp_topology_neighbor_down(struct eigrp *eigrp, + struct eigrp_neighbor *neigh); +extern void +eigrp_update_topology_table_prefix(struct eigrp *eigrp, + struct route_table *table, + struct eigrp_prefix_descriptor *pe); + +#endif diff --git a/eigrpd/eigrp_types.h b/eigrpd/eigrp_types.h new file mode 100644 index 0000000..f3ff99d --- /dev/null +++ b/eigrpd/eigrp_types.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Definition of Data Types + * Copyright (C) 2018 + * Authors: + * Donnie Savage + */ + +#ifndef _ZEBRA_EIGRP_TYPES_H_ +#define _ZEBRA_EIGRP_TYPES_H_ + +typedef uint64_t eigrp_bandwidth_t; +typedef uint64_t eigrp_delay_t; +typedef uint64_t eigrp_metric_t; +typedef uint32_t eigrp_scaled_t; + +typedef uint32_t eigrp_system_metric_t; +typedef uint32_t eigrp_system_delay_t; +typedef uint32_t eigrp_system_bandwidth_t; + +#endif /* _ZEBRA_EIGRP_TYPES_H_ */ diff --git a/eigrpd/eigrp_update.c b/eigrpd/eigrp_update.c new file mode 100644 index 0000000..74f573d --- /dev/null +++ b/eigrpd/eigrp_update.c @@ -0,0 +1,1038 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Sending and Receiving EIGRP Update Packets. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#include "md5.h" +#include "vty.h" +#include "plist.h" +#include "plist_int.h" +#include "routemap.h" +#include "vty.h" + +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_macros.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_metric.h" + +bool eigrp_update_prefix_apply(struct eigrp *eigrp, struct eigrp_interface *ei, + int in, struct prefix *prefix) +{ + struct access_list *alist; + struct prefix_list *plist; + + alist = eigrp->list[in]; + if (alist && access_list_apply(alist, prefix) == FILTER_DENY) + return true; + + plist = eigrp->prefix[in]; + if (plist && prefix_list_apply(plist, prefix) == PREFIX_DENY) + return true; + + alist = ei->list[in]; + if (alist && access_list_apply(alist, prefix) == FILTER_DENY) + return true; + + plist = ei->prefix[in]; + if (plist && prefix_list_apply(plist, prefix) == PREFIX_DENY) + return true; + + return false; +} + +/** + * @fn remove_received_prefix_gr + * + * @param[in] nbr_prefixes List of neighbor prefixes + * @param[in] recv_prefix Prefix which needs to be removed from + * list + * + * @return void + * + * @par + * Function is used for removing received prefix + * from list of neighbor prefixes + */ +static void +remove_received_prefix_gr(struct list *nbr_prefixes, + struct eigrp_prefix_descriptor *recv_prefix) +{ + struct listnode *node1, *node11; + struct eigrp_prefix_descriptor *prefix = NULL; + + /* iterate over all prefixes in list */ + for (ALL_LIST_ELEMENTS(nbr_prefixes, node1, node11, prefix)) { + /* remove prefix from list if found */ + if (prefix == recv_prefix) { + listnode_delete(nbr_prefixes, prefix); + } + } +} + +/** + * @fn eigrp_update_receive_GR_ask + * + * @param[in] eigrp EIGRP process + * @param[in] nbr Neighbor update of who we + * received + * @param[in] nbr_prefixes Prefixes which weren't advertised + * + * @return void + * + * @par + * Function is used for notifying FSM about prefixes which + * weren't advertised by neighbor: + * We will send message to FSM with prefix delay set to infinity. + */ +static void eigrp_update_receive_GR_ask(struct eigrp *eigrp, + struct eigrp_neighbor *nbr, + struct list *nbr_prefixes) +{ + struct listnode *node1; + struct eigrp_prefix_descriptor *prefix; + struct eigrp_fsm_action_message fsm_msg; + + /* iterate over all prefixes which weren't advertised by neighbor */ + for (ALL_LIST_ELEMENTS_RO(nbr_prefixes, node1, prefix)) { + zlog_debug("GR receive: Neighbor not advertised %pFX", + prefix->destination); + + fsm_msg.metrics = prefix->reported_metric; + /* set delay to MAX */ + fsm_msg.metrics.delay = EIGRP_MAX_METRIC; + + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup(prefix->entries, nbr); + + fsm_msg.packet_type = EIGRP_OPC_UPDATE; + fsm_msg.eigrp = eigrp; + fsm_msg.data_type = EIGRP_INT; + fsm_msg.adv_router = nbr; + fsm_msg.entry = entry; + fsm_msg.prefix = prefix; + + /* send message to FSM */ + eigrp_fsm_event(&fsm_msg); + } +} + +/* + * EIGRP UPDATE read function + */ +void eigrp_update_receive(struct eigrp *eigrp, struct ip *iph, + struct eigrp_header *eigrph, struct stream *s, + struct eigrp_interface *ei, int size) +{ + struct eigrp_neighbor *nbr; + struct TLV_IPv4_Internal_type *tlv; + struct eigrp_prefix_descriptor *pe; + struct eigrp_route_descriptor *ne; + uint32_t flags; + uint16_t type; + uint16_t length; + uint8_t same; + struct prefix dest_addr; + uint8_t graceful_restart; + uint8_t graceful_restart_final; + struct list *nbr_prefixes = NULL; + + /* increment statistics. */ + ei->update_in++; + + /* get neighbor struct */ + nbr = eigrp_nbr_get(ei, eigrph, iph); + + /* neighbor must be valid, eigrp_nbr_get creates if none existed */ + assert(nbr); + + flags = ntohl(eigrph->flags); + + if (flags & EIGRP_CR_FLAG) { + return; + } + + same = 0; + graceful_restart = 0; + graceful_restart_final = 0; + if ((nbr->recv_sequence_number) == (ntohl(eigrph->sequence))) + same = 1; + + nbr->recv_sequence_number = ntohl(eigrph->sequence); + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug( + "Processing Update size[%u] int(%s) nbr(%pI4) seq [%u] flags [%0x]", + size, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id), + &nbr->src, nbr->recv_sequence_number, flags); + + + if ((flags == (EIGRP_INIT_FLAG + EIGRP_RS_FLAG + EIGRP_EOT_FLAG)) + && (!same)) { + /* Graceful restart Update received with all routes */ + + zlog_info("Neighbor %pI4 (%s) is resync: peer graceful-restart", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + + /* get all prefixes from neighbor from topology table */ + nbr_prefixes = eigrp_neighbor_prefixes_lookup(eigrp, nbr); + graceful_restart = 1; + graceful_restart_final = 1; + } else if ((flags == (EIGRP_INIT_FLAG + EIGRP_RS_FLAG)) && (!same)) { + /* Graceful restart Update received, routes also in next packet + */ + + zlog_info("Neighbor %pI4 (%s) is resync: peer graceful-restart", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id)); + + /* get all prefixes from neighbor from topology table */ + nbr_prefixes = eigrp_neighbor_prefixes_lookup(eigrp, nbr); + /* save prefixes to neighbor for later use */ + nbr->nbr_gr_prefixes = nbr_prefixes; + graceful_restart = 1; + graceful_restart_final = 0; + } else if ((flags == (EIGRP_EOT_FLAG)) && (!same)) { + /* If there was INIT+RS Update packet before, + * consider this as GR EOT */ + + if (nbr->nbr_gr_prefixes != NULL) { + /* this is final packet of GR */ + nbr_prefixes = nbr->nbr_gr_prefixes; + nbr->nbr_gr_prefixes = NULL; + + graceful_restart = 1; + graceful_restart_final = 1; + } + + } else if ((flags == (0)) && (!same)) { + /* If there was INIT+RS Update packet before, + * consider this as GR not final packet */ + + if (nbr->nbr_gr_prefixes != NULL) { + /* this is GR not final route packet */ + nbr_prefixes = nbr->nbr_gr_prefixes; + + graceful_restart = 1; + graceful_restart_final = 0; + } + + } else if ((flags & EIGRP_INIT_FLAG) + && (!same)) { /* When in pending state, send INIT update only + if it wasn't + already sent before (only if init_sequence + is 0) */ + if ((nbr->state == EIGRP_NEIGHBOR_PENDING) + && (nbr->init_sequence_number == 0)) + eigrp_update_send_init(nbr); + + if (nbr->state == EIGRP_NEIGHBOR_UP) { + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN); + eigrp_topology_neighbor_down(nbr->ei->eigrp, nbr); + nbr->recv_sequence_number = ntohl(eigrph->sequence); + zlog_info("Neighbor %pI4 (%s) is down: peer restarted", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_PENDING); + zlog_info( + "Neighbor %pI4 (%s) is pending: new adjacency", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + eigrp_update_send_init(nbr); + } + } + + /*If there is topology information*/ + while (s->endp > s->getp) { + type = stream_getw(s); + switch (type) { + case EIGRP_TLV_IPv4_INT: + stream_set_getp(s, s->getp - sizeof(uint16_t)); + + tlv = eigrp_read_ipv4_tlv(s); + + /*searching if destination exists */ + dest_addr.family = AF_INET; + dest_addr.u.prefix4 = tlv->destination; + dest_addr.prefixlen = tlv->prefix_length; + struct eigrp_prefix_descriptor *dest = + eigrp_topology_table_lookup_ipv4( + eigrp->topology_table, &dest_addr); + + /*if exists it comes to DUAL*/ + if (dest != NULL) { + /* remove received prefix from neighbor prefix + * list if in GR */ + if (graceful_restart) + remove_received_prefix_gr(nbr_prefixes, + dest); + + struct eigrp_fsm_action_message msg; + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup( + dest->entries, nbr); + + msg.packet_type = EIGRP_OPC_UPDATE; + msg.eigrp = eigrp; + msg.data_type = EIGRP_INT; + msg.adv_router = nbr; + msg.metrics = tlv->metric; + msg.entry = entry; + msg.prefix = dest; + eigrp_fsm_event(&msg); + } else { + /*Here comes topology information save*/ + pe = eigrp_prefix_descriptor_new(); + pe->serno = eigrp->serno; + pe->destination = + (struct prefix *)prefix_ipv4_new(); + prefix_copy(pe->destination, &dest_addr); + pe->af = AF_INET; + pe->state = EIGRP_FSM_STATE_PASSIVE; + pe->nt = EIGRP_TOPOLOGY_TYPE_REMOTE; + + ne = eigrp_route_descriptor_new(); + ne->ei = ei; + ne->adv_router = nbr; + ne->reported_metric = tlv->metric; + ne->reported_distance = eigrp_calculate_metrics( + eigrp, tlv->metric); + /* + * Filtering + */ + if (eigrp_update_prefix_apply(eigrp, ei, + EIGRP_FILTER_IN, + &dest_addr)) + ne->reported_metric.delay = + EIGRP_MAX_METRIC; + + ne->distance = eigrp_calculate_total_metrics( + eigrp, ne); + + pe->fdistance = pe->distance = pe->rdistance = + ne->distance; + ne->prefix = pe; + ne->flags = + EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG; + + eigrp_prefix_descriptor_add( + eigrp->topology_table, pe); + eigrp_route_descriptor_add(eigrp, pe, ne); + pe->distance = pe->fdistance = pe->rdistance = + ne->distance; + pe->reported_metric = ne->total_metric; + eigrp_topology_update_node_flags(eigrp, pe); + + pe->req_action |= EIGRP_FSM_NEED_UPDATE; + listnode_add( + eigrp->topology_changes_internalIPV4, + pe); + } + eigrp_IPv4_InternalTLV_free(tlv); + break; + + case EIGRP_TLV_IPv4_EXT: + /* DVS: processing of external routes needs packet and fsm work. + * for now, lets just not creash the box + */ + default: + length = stream_getw(s); + // -2 for type, -2 for len + for (length -= 4; length; length--) { + (void)stream_getc(s); + } + } + } + + /* ask about prefixes not present in GR update, + * if this is final GR packet */ + if (graceful_restart_final) { + eigrp_update_receive_GR_ask(eigrp, nbr, nbr_prefixes); + } + + /* + * We don't need to send separate Ack for INIT Update. INIT will be + * acked in EOT Update. + */ + if ((nbr->state == EIGRP_NEIGHBOR_UP) && !(flags == EIGRP_INIT_FLAG)) { + eigrp_hello_send_ack(nbr); + } + + eigrp_query_send_all(eigrp); + eigrp_update_send_all(eigrp, ei); + + if (nbr_prefixes) + list_delete(&nbr_prefixes); +} + +/*send EIGRP Update packet*/ +void eigrp_update_send_init(struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + + ep = eigrp_packet_new(EIGRP_PACKET_MTU(nbr->ei->ifp->mtu), nbr); + + /* Prepare EIGRP INIT UPDATE header */ + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug("Enqueuing Update Init Seq [%u] Ack [%u]", + nbr->ei->eigrp->sequence_number, + nbr->recv_sequence_number); + + eigrp_packet_header_init( + EIGRP_OPC_UPDATE, nbr->ei->eigrp, ep->s, EIGRP_INIT_FLAG, + nbr->ei->eigrp->sequence_number, nbr->recv_sequence_number); + + // encode Authentication TLV, if needed + if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (nbr->ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, nbr->ei); + eigrp_make_md5_digest(nbr->ei, ep->s, + EIGRP_AUTH_UPDATE_INIT_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(nbr->ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + nbr->init_sequence_number = nbr->ei->eigrp->sequence_number; + ep->sequence_number = nbr->ei->eigrp->sequence_number; + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug( + "Enqueuing Update Init Len [%u] Seq [%u] Dest [%pI4]", + ep->length, ep->sequence_number, &ep->dst); + + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } +} + +static void eigrp_update_place_on_nbr_queue(struct eigrp_neighbor *nbr, + struct eigrp_packet *ep, + uint32_t seq_no, int length) +{ + if ((nbr->ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (nbr->ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(nbr->ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(nbr->ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + ep->sequence_number = seq_no; + + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug( + "Enqueuing Update Init Len [%u] Seq [%u] Dest [%pI4]", + ep->length, ep->sequence_number, &ep->dst); + + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) + eigrp_send_packet_reliably(nbr); +} + +static void eigrp_update_send_to_all_nbrs(struct eigrp_interface *ei, + struct eigrp_packet *ep) +{ + struct listnode *node, *nnode; + struct eigrp_neighbor *nbr; + bool packet_sent = false; + + for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) { + struct eigrp_packet *ep_dup; + + if (nbr->state != EIGRP_NEIGHBOR_UP) + continue; + + if (packet_sent) + ep_dup = eigrp_packet_duplicate(ep, NULL); + else + ep_dup = ep; + + ep_dup->nbr = nbr; + packet_sent = true; + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep_dup); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } + } + + if (!packet_sent) + eigrp_packet_free(ep); +} + +void eigrp_update_send_EOT(struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + struct eigrp_route_descriptor *te; + struct eigrp_prefix_descriptor *pe; + struct listnode *node2, *nnode2; + struct eigrp_interface *ei = nbr->ei; + struct eigrp *eigrp = ei->eigrp; + struct prefix *dest_addr; + uint32_t seq_no = eigrp->sequence_number; + uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); + struct route_node *rn; + + ep = eigrp_packet_new(eigrp_mtu, nbr); + + /* Prepare EIGRP EOT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, EIGRP_EOT_FLAG, + seq_no, nbr->recv_sequence_number); + + // encode Authentication TLV, if needed + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); + } + + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + pe = rn->info; + for (ALL_LIST_ELEMENTS(pe->entries, node2, nnode2, te)) { + if (eigrp_nbr_split_horizon_check(te, ei)) + continue; + + if ((length + EIGRP_TLV_MAX_IPV4_BYTE) > eigrp_mtu) { + eigrp_update_place_on_nbr_queue(nbr, ep, seq_no, + length); + seq_no++; + + length = EIGRP_HEADER_LEN; + ep = eigrp_packet_new(eigrp_mtu, nbr); + eigrp_packet_header_init( + EIGRP_OPC_UPDATE, nbr->ei->eigrp, ep->s, + EIGRP_EOT_FLAG, seq_no, + nbr->recv_sequence_number); + + if ((ei->params.auth_type + == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += + eigrp_add_authTLV_MD5_to_stream( + ep->s, ei); + } + } + /* Get destination address from prefix */ + dest_addr = pe->destination; + + /* Check if any list fits */ + if (eigrp_update_prefix_apply( + eigrp, ei, EIGRP_FILTER_OUT, dest_addr)) + continue; + else { + length += eigrp_add_internalTLV_to_stream(ep->s, + pe); + } + } + } + + eigrp_update_place_on_nbr_queue(nbr, ep, seq_no, length); + eigrp->sequence_number = seq_no++; +} + +void eigrp_update_send(struct eigrp_interface *ei) +{ + struct eigrp_packet *ep; + struct listnode *node, *nnode; + struct eigrp_prefix_descriptor *pe; + uint8_t has_tlv; + struct eigrp *eigrp = ei->eigrp; + struct prefix *dest_addr; + uint32_t seq_no = eigrp->sequence_number; + uint16_t eigrp_mtu = EIGRP_PACKET_MTU(ei->ifp->mtu); + + if (ei->nbrs->count == 0) + return; + + uint16_t length = EIGRP_HEADER_LEN; + + ep = eigrp_packet_new(eigrp_mtu, NULL); + + /* Prepare EIGRP INIT UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, 0, seq_no, 0); + + // encode Authentication TLV, if needed + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); + } + + has_tlv = 0; + for (ALL_LIST_ELEMENTS(ei->eigrp->topology_changes_internalIPV4, node, + nnode, pe)) { + struct eigrp_route_descriptor *ne; + + if (!(pe->req_action & EIGRP_FSM_NEED_UPDATE)) + continue; + + ne = listnode_head(pe->entries); + if (eigrp_nbr_split_horizon_check(ne, ei)) + continue; + + if ((length + EIGRP_TLV_MAX_IPV4_BYTE) > eigrp_mtu) { + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(ei, ep->s, + EIGRP_AUTH_UPDATE_FLAG); + } + + eigrp_packet_checksum(ei, ep->s, length); + ep->length = length; + + ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); + + ep->sequence_number = seq_no; + seq_no++; + eigrp_update_send_to_all_nbrs(ei, ep); + + length = EIGRP_HEADER_LEN; + ep = eigrp_packet_new(eigrp_mtu, NULL); + eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, + 0, seq_no, 0); + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, + ei); + } + has_tlv = 0; + } + /* Get destination address from prefix */ + dest_addr = pe->destination; + + if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, + dest_addr)) { + // pe->reported_metric.delay = EIGRP_MAX_METRIC; + continue; + } else { + length += eigrp_add_internalTLV_to_stream(ep->s, pe); + has_tlv = 1; + } + } + + if (!has_tlv) { + eigrp_packet_free(ep); + return; + } + + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(ei, ep->s, length); + ep->length = length; + + ep->dst.s_addr = htonl(EIGRP_MULTICAST_ADDRESS); + + /*This ack number we await from neighbor*/ + ep->sequence_number = eigrp->sequence_number; + + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug("Enqueuing Update length[%u] Seq [%u]", length, + ep->sequence_number); + + eigrp_update_send_to_all_nbrs(ei, ep); + ei->eigrp->sequence_number = seq_no++; +} + +void eigrp_update_send_all(struct eigrp *eigrp, + struct eigrp_interface *exception) +{ + struct eigrp_interface *iface; + struct listnode *node, *node2, *nnode2; + struct eigrp_prefix_descriptor *pe; + + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) { + if (iface != exception) { + eigrp_update_send(iface); + } + } + + for (ALL_LIST_ELEMENTS(eigrp->topology_changes_internalIPV4, node2, + nnode2, pe)) { + if (pe->req_action & EIGRP_FSM_NEED_UPDATE) { + pe->req_action &= ~EIGRP_FSM_NEED_UPDATE; + listnode_delete(eigrp->topology_changes_internalIPV4, + pe); + } + } +} + +/** + * @fn eigrp_update_send_GR_part + * + * @param[in] nbr contains neighbor who would receive + * Graceful + * restart + * + * @return void + * + * @par + * Function used for sending Graceful restart Update packet + * and if there are multiple chunks, send only one of them. + * It is called from thread. Do not call it directly. + * + * Uses nbr_gr_packet_type from neighbor. + */ +static void eigrp_update_send_GR_part(struct eigrp_neighbor *nbr) +{ + struct eigrp_packet *ep; + uint16_t length = EIGRP_HEADER_LEN; + struct eigrp_prefix_descriptor *pe; + struct prefix *dest_addr; + struct eigrp_interface *ei = nbr->ei; + struct eigrp *eigrp = ei->eigrp; + struct list *prefixes; + uint32_t flags; + unsigned int send_prefixes; + struct route_node *rn; + + /* get prefixes to send to neighbor */ + prefixes = nbr->nbr_gr_prefixes_send; + + send_prefixes = 0; + + /* if there already were last packet chunk, we won't continue */ + if (nbr->nbr_gr_packet_type == EIGRP_PACKET_PART_LAST) + return; + + /* if this is first packet chunk, we need to decide, + * if there will be one or more chunks */ + if (nbr->nbr_gr_packet_type == EIGRP_PACKET_PART_FIRST) { + if (prefixes->count <= EIGRP_TLV_MAX_IPv4) { + /* there will be only one chunk */ + flags = EIGRP_INIT_FLAG + EIGRP_RS_FLAG + + EIGRP_EOT_FLAG; + nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_LAST; + } else { + /* there will be more chunks */ + flags = EIGRP_INIT_FLAG + EIGRP_RS_FLAG; + nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_NA; + } + } else { + /* this is not first chunk, and we need to decide, + * if there will be more chunks */ + if (prefixes->count <= EIGRP_TLV_MAX_IPv4) { + /* this is last chunk */ + flags = EIGRP_EOT_FLAG; + nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_LAST; + } else { + /* there will be more chunks */ + flags = 0; + nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_NA; + } + } + + ep = eigrp_packet_new(EIGRP_PACKET_MTU(ei->ifp->mtu), nbr); + + /* Prepare EIGRP Graceful restart UPDATE header */ + eigrp_packet_header_init(EIGRP_OPC_UPDATE, eigrp, ep->s, flags, + eigrp->sequence_number, + nbr->recv_sequence_number); + + // encode Authentication TLV, if needed + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + length += eigrp_add_authTLV_MD5_to_stream(ep->s, ei); + } + + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + pe = rn->info; + /* + * Filtering + */ + dest_addr = pe->destination; + + if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_OUT, + dest_addr)) { + /* do not send filtered route */ + zlog_info("Filtered prefix %pI4 won't be sent out.", + &dest_addr->u.prefix4); + } else { + /* sending route which wasn't filtered */ + length += eigrp_add_internalTLV_to_stream(ep->s, pe); + send_prefixes++; + } + + /* + * This makes no sense, Filter out then filter in??? + * Look into this more - DBS + */ + if (eigrp_update_prefix_apply(eigrp, ei, EIGRP_FILTER_IN, + dest_addr)) { + /* do not send filtered route */ + zlog_info("Filtered prefix %pI4 will be removed.", + &dest_addr->u.prefix4); + + /* prepare message for FSM */ + struct eigrp_fsm_action_message fsm_msg; + + struct eigrp_route_descriptor *entry = + eigrp_route_descriptor_lookup(pe->entries, nbr); + + fsm_msg.packet_type = EIGRP_OPC_UPDATE; + fsm_msg.eigrp = eigrp; + fsm_msg.data_type = EIGRP_INT; + fsm_msg.adv_router = nbr; + fsm_msg.metrics = pe->reported_metric; + /* Set delay to MAX */ + fsm_msg.metrics.delay = EIGRP_MAX_METRIC; + fsm_msg.entry = entry; + fsm_msg.prefix = pe; + + /* send message to FSM */ + eigrp_fsm_event(&fsm_msg); + } + + /* delete processed prefix from list */ + listnode_delete(prefixes, pe); + + /* if there are enough prefixes, send packet */ + if (send_prefixes >= EIGRP_TLV_MAX_IPv4) + break; + } + + /* compute Auth digest */ + if ((ei->params.auth_type == EIGRP_AUTH_TYPE_MD5) + && (ei->params.auth_keychain != NULL)) { + eigrp_make_md5_digest(ei, ep->s, EIGRP_AUTH_UPDATE_FLAG); + } + + /* EIGRP Checksum */ + eigrp_packet_checksum(ei, ep->s, length); + + ep->length = length; + ep->dst.s_addr = nbr->src.s_addr; + + /*This ack number we await from neighbor*/ + ep->sequence_number = eigrp->sequence_number; + + if (IS_DEBUG_EIGRP_PACKET(0, RECV)) + zlog_debug( + "Enqueuing Update Init Len [%u] Seq [%u] Dest [%pI4]", + ep->length, ep->sequence_number, &ep->dst); + + /*Put packet to retransmission queue*/ + eigrp_fifo_push(nbr->retrans_queue, ep); + + if (nbr->retrans_queue->count == 1) { + eigrp_send_packet_reliably(nbr); + } +} + +/** + * @fn eigrp_update_send_GR_thread + * + * @param[in] thread contains neighbor who would receive + * Graceful restart + * + * @return int always 0 + * + * @par + * Function used for sending Graceful restart Update packet + * in thread, it is prepared for multiple chunks of packet. + * + * Uses nbr_gr_packet_type and t_nbr_send_gr from neighbor. + */ +void eigrp_update_send_GR_thread(struct event *thread) +{ + struct eigrp_neighbor *nbr; + + /* get argument from thread */ + nbr = EVENT_ARG(thread); + /* remove this thread pointer */ + + /* if there is packet waiting in queue, + * schedule this thread again with small delay */ + if (nbr->retrans_queue->count > 0) { + event_add_timer_msec(master, eigrp_update_send_GR_thread, nbr, + 10, &nbr->t_nbr_send_gr); + return; + } + + /* send GR EIGRP packet chunk */ + eigrp_update_send_GR_part(nbr); + + /* if it wasn't last chunk, schedule this thread again */ + if (nbr->nbr_gr_packet_type != EIGRP_PACKET_PART_LAST) { + event_execute(master, eigrp_update_send_GR_thread, nbr, 0, NULL); + } +} + +/** + * @fn eigrp_update_send_GR + * + * @param[in] nbr Neighbor who would receive + * Graceful + * restart + * @param[in] gr_type Who executed Graceful restart + * @param[in] vty Virtual terminal for log output + * + * @return void + * + * @par + * Function used for sending Graceful restart Update packet: + * Creates Update packet with INIT, RS, EOT flags and include + * all route except those filtered + */ +void eigrp_update_send_GR(struct eigrp_neighbor *nbr, enum GR_type gr_type, + struct vty *vty) +{ + struct eigrp_prefix_descriptor *pe2; + struct list *prefixes; + struct route_node *rn; + struct eigrp_interface *ei = nbr->ei; + struct eigrp *eigrp = ei->eigrp; + + if (gr_type == EIGRP_GR_FILTER) { + /* function was called after applying filtration */ + zlog_info( + "Neighbor %pI4 (%s) is resync: route configuration changed", + &nbr->src, + ifindex2ifname(ei->ifp->ifindex, eigrp->vrf_id)); + } else if (gr_type == EIGRP_GR_MANUAL) { + /* Graceful restart was called manually */ + zlog_info("Neighbor %pI4 (%s) is resync: manually cleared", + &nbr->src, + ifindex2ifname(ei->ifp->ifindex, eigrp->vrf_id)); + + if (vty != NULL) { + vty_time_print(vty, 0); + vty_out(vty, + "Neighbor %pI4 (%s) is resync: manually cleared\n", + &nbr->src, + ifindex2ifname(ei->ifp->ifindex, + eigrp->vrf_id)); + } + } + + prefixes = list_new(); + /* add all prefixes from topology table to list */ + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + pe2 = rn->info; + listnode_add(prefixes, pe2); + } + + /* save prefixes to neighbor */ + nbr->nbr_gr_prefixes_send = prefixes; + /* indicate, that this is first GR Update packet chunk */ + nbr->nbr_gr_packet_type = EIGRP_PACKET_PART_FIRST; + /* execute packet sending in thread */ + event_execute(master, eigrp_update_send_GR_thread, nbr, 0, NULL); +} + +/** + * @fn eigrp_update_send_interface_GR + * + * @param[in] ei Interface to neighbors of which + * the + * GR + * is sent + * @param[in] gr_type Who executed Graceful restart + * @param[in] vty Virtual terminal for log output + * + * @return void + * + * @par + * Function used for sending Graceful restart Update packet + * to all neighbors on specified interface. + */ +void eigrp_update_send_interface_GR(struct eigrp_interface *ei, + enum GR_type gr_type, struct vty *vty) +{ + struct listnode *node; + struct eigrp_neighbor *nbr; + + /* iterate over all neighbors on eigrp interface */ + for (ALL_LIST_ELEMENTS_RO(ei->nbrs, node, nbr)) { + /* send GR to neighbor */ + eigrp_update_send_GR(nbr, gr_type, vty); + } +} + +/** + * @fn eigrp_update_send_process_GR + * + * @param[in] eigrp EIGRP process + * @param[in] gr_type Who executed Graceful restart + * @param[in] vty Virtual terminal for log output + * + * @return void + * + * @par + * Function used for sending Graceful restart Update packet + * to all neighbors in eigrp process. + */ +void eigrp_update_send_process_GR(struct eigrp *eigrp, enum GR_type gr_type, + struct vty *vty) +{ + struct listnode *node; + struct eigrp_interface *ei; + + /* iterate over all eigrp interfaces */ + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + /* send GR to all neighbors on interface */ + eigrp_update_send_interface_GR(ei, gr_type, vty); + } +} diff --git a/eigrpd/eigrp_vrf.c b/eigrpd/eigrp_vrf.c new file mode 100644 index 0000000..645233e --- /dev/null +++ b/eigrpd/eigrp_vrf.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * eigrp - vrf code + * Copyright (C) 2019 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "vrf.h" + +#include "eigrpd/eigrp_vrf.h" + +static int eigrp_vrf_new(struct vrf *vrf) +{ + return 0; +} + +static int eigrp_vrf_enable(struct vrf *vrf) +{ + return 0; +} + +static int eigrp_vrf_disable(struct vrf *vrf) +{ + return 0; +} + +static int eigrp_vrf_delete(struct vrf *vrf) +{ + return 0; +} + +void eigrp_vrf_init(void) +{ + vrf_init(eigrp_vrf_new, eigrp_vrf_enable, eigrp_vrf_disable, + eigrp_vrf_delete); +} diff --git a/eigrpd/eigrp_vrf.h b/eigrpd/eigrp_vrf.h new file mode 100644 index 0000000..146d864 --- /dev/null +++ b/eigrpd/eigrp_vrf.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * eigrp - vrf code + * Copyright (C) 2019 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __EIGRP_VRF_H__ + +extern void eigrp_vrf_init(void); +#endif diff --git a/eigrpd/eigrp_vty.c b/eigrpd/eigrp_vty.c new file mode 100644 index 0000000..88510e7 --- /dev/null +++ b/eigrpd/eigrp_vty.c @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP VTY Interface. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#include + +#include "memory.h" +#include "frrevent.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "plist.h" +#include "log.h" +#include "zclient.h" +#include "keychain.h" +#include "linklist.h" +#include "distribute.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_const.h" + +#include "eigrpd/eigrp_vty_clippy.c" + +static void eigrp_vty_display_prefix_entry(struct vty *vty, struct eigrp *eigrp, + struct eigrp_prefix_descriptor *pe, + bool all) +{ + bool first = true; + struct eigrp_route_descriptor *te; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(pe->entries, node, te)) { + if (all + || (((te->flags & EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG) + == EIGRP_ROUTE_DESCRIPTOR_SUCCESSOR_FLAG) + || ((te->flags & EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG) + == EIGRP_ROUTE_DESCRIPTOR_FSUCCESSOR_FLAG))) { + show_ip_eigrp_route_descriptor(vty, eigrp, te, &first); + first = false; + } + } +} + +static struct eigrp *eigrp_vty_get_eigrp(struct vty *vty, const char *vrf_name) +{ + struct vrf *vrf; + + if (vrf_name) + vrf = vrf_lookup_by_name(vrf_name); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) { + vty_out(vty, "VRF %s specified does not exist", + vrf_name ? vrf_name : VRF_DEFAULT_NAME); + return NULL; + } + + return eigrp_lookup(vrf->vrf_id); +} + +static void eigrp_topology_helper(struct vty *vty, struct eigrp *eigrp, + const char *all) +{ + struct eigrp_prefix_descriptor *tn; + struct route_node *rn; + + show_ip_eigrp_topology_header(vty, eigrp); + + for (rn = route_top(eigrp->topology_table); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + tn = rn->info; + eigrp_vty_display_prefix_entry(vty, eigrp, tn, + all ? true : false); + } +} + +DEFPY (show_ip_eigrp_topology_all, + show_ip_eigrp_topology_all_cmd, + "show ip eigrp [vrf NAME] topology [all-links$all]", + SHOW_STR + IP_STR + "IP-EIGRP show commands\n" + VRF_CMD_HELP_STR + "IP-EIGRP topology\n" + "Show all links in topology table\n") +{ + struct eigrp *eigrp; + + if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) { + struct vrf *v; + + RB_FOREACH (v, vrf_name_head, &vrfs_by_name) { + eigrp = eigrp_lookup(v->vrf_id); + if (!eigrp) + continue; + + vty_out(vty, "VRF %s:\n", v->name); + + eigrp_topology_helper(vty, eigrp, all); + } + } else { + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + eigrp_topology_helper(vty, eigrp, all); + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_eigrp_topology, + show_ip_eigrp_topology_cmd, + "show ip eigrp [vrf NAME] topology ", + SHOW_STR + IP_STR + "IP-EIGRP show commands\n" + VRF_CMD_HELP_STR + "IP-EIGRP topology\n" + "For a specific address\n" + "For a specific prefix\n") +{ + struct eigrp *eigrp; + struct eigrp_prefix_descriptor *tn; + struct route_node *rn; + struct prefix cmp; + + if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) { + vty_out(vty, "Specifying vrf `all` for a particular address/prefix makes no sense\n"); + return CMD_SUCCESS; + } + + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + show_ip_eigrp_topology_header(vty, eigrp); + + if (address_str) + prefix_str = address_str; + + if (str2prefix(prefix_str, &cmp) < 0) { + vty_out(vty, "%% Malformed address\n"); + return CMD_WARNING; + } + + rn = route_node_match(eigrp->topology_table, &cmp); + if (!rn) { + vty_out(vty, "%% Network not in table\n"); + return CMD_WARNING; + } + + if (!rn->info) { + vty_out(vty, "%% Network not in table\n"); + route_unlock_node(rn); + return CMD_WARNING; + } + + tn = rn->info; + eigrp_vty_display_prefix_entry(vty, eigrp, tn, argc == 5); + + route_unlock_node(rn); + return CMD_SUCCESS; +} + +static void eigrp_interface_helper(struct vty *vty, struct eigrp *eigrp, + const char *ifname, const char *detail) +{ + struct eigrp_interface *ei; + struct listnode *node; + + if (!ifname) + show_ip_eigrp_interface_header(vty, eigrp); + + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + if (!ifname || strcmp(ei->ifp->name, ifname) == 0) { + show_ip_eigrp_interface_sub(vty, eigrp, ei); + if (detail) + show_ip_eigrp_interface_detail(vty, eigrp, ei); + } + } +} + +DEFPY (show_ip_eigrp_interfaces, + show_ip_eigrp_interfaces_cmd, + "show ip eigrp [vrf NAME] interfaces [IFNAME] [detail]$detail", + SHOW_STR + IP_STR + "IP-EIGRP show commands\n" + VRF_CMD_HELP_STR + "IP-EIGRP interfaces\n" + "Interface name to look at\n" + "Detailed information\n") +{ + struct eigrp *eigrp; + + if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) { + struct vrf *v; + + RB_FOREACH (v, vrf_name_head, &vrfs_by_name) { + eigrp = eigrp_lookup(v->vrf_id); + if (!eigrp) + continue; + + vty_out(vty, "VRF %s:\n", v->name); + + eigrp_interface_helper(vty, eigrp, ifname, detail); + } + } else { + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, "EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + eigrp_interface_helper(vty, eigrp, ifname, detail); + } + + + return CMD_SUCCESS; +} + +static void eigrp_neighbors_helper(struct vty *vty, struct eigrp *eigrp, + const char *ifname, const char *detail) +{ + struct eigrp_interface *ei; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + + show_ip_eigrp_neighbor_header(vty, eigrp); + + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + if (!ifname || strcmp(ei->ifp->name, ifname) == 0) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (detail || (nbr->state == EIGRP_NEIGHBOR_UP)) + show_ip_eigrp_neighbor_sub(vty, nbr, + !!detail); + } + } + } +} + +DEFPY (show_ip_eigrp_neighbors, + show_ip_eigrp_neighbors_cmd, + "show ip eigrp [vrf NAME] neighbors [IFNAME] [detail]$detail", + SHOW_STR + IP_STR + "IP-EIGRP show commands\n" + VRF_CMD_HELP_STR + "IP-EIGRP neighbors\n" + "Interface to show on\n" + "Detailed Information\n") +{ + struct eigrp *eigrp; + + if (vrf && strncmp(vrf, "all", sizeof("all")) == 0) { + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + eigrp = eigrp_lookup(vrf->vrf_id); + if (!eigrp) + continue; + + vty_out(vty, "VRF %s:\n", vrf->name); + + eigrp_neighbors_helper(vty, eigrp, ifname, detail); + } + } else { + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + eigrp_neighbors_helper(vty, eigrp, ifname, detail); + } + + return CMD_SUCCESS; +} + +/* + * Execute hard restart for all neighbors + */ +DEFPY (clear_ip_eigrp_neighbors, + clear_ip_eigrp_neighbors_cmd, + "clear ip eigrp [vrf NAME] neighbors", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n") +{ + struct eigrp *eigrp; + struct eigrp_interface *ei; + struct listnode *node, *node2, *nnode2; + struct eigrp_neighbor *nbr; + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* iterate over all eigrp interfaces */ + for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) { + /* send Goodbye Hello */ + eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); + + /* iterate over all neighbors on eigrp interface */ + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (nbr->state != EIGRP_NEIGHBOR_DOWN) { + zlog_debug( + "Neighbor %pI4 (%s) is down: manually cleared", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + vty_time_print(vty, 0); + vty_out(vty, + "Neighbor %pI4 (%s) is down: manually cleared\n", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + + /* set neighbor to DOWN */ + nbr->state = EIGRP_NEIGHBOR_DOWN; + /* delete neighbor */ + eigrp_nbr_delete(nbr); + } + } + } + + return CMD_SUCCESS; +} + +/* + * Execute hard restart for all neighbors on interface + */ +DEFPY (clear_ip_eigrp_neighbors_int, + clear_ip_eigrp_neighbors_int_cmd, + "clear ip eigrp [vrf NAME] neighbors IFNAME", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n" + "Interface's name\n") +{ + struct eigrp *eigrp; + struct eigrp_interface *ei; + struct listnode *node2, *nnode2; + struct eigrp_neighbor *nbr; + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* lookup interface by specified name */ + ei = eigrp_if_lookup_by_name(eigrp, ifname); + if (ei == NULL) { + vty_out(vty, " Interface (%s) doesn't exist\n", ifname); + return CMD_WARNING; + } + + /* send Goodbye Hello */ + eigrp_hello_send(ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN, NULL); + + /* iterate over all neighbors on eigrp interface */ + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) { + if (nbr->state != EIGRP_NEIGHBOR_DOWN) { + zlog_debug( + "Neighbor %pI4 (%s) is down: manually cleared", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + vty_time_print(vty, 0); + vty_out(vty, + "Neighbor %pI4 (%s) is down: manually cleared\n", + &nbr->src, + ifindex2ifname(nbr->ei->ifp->ifindex, + eigrp->vrf_id)); + + /* set neighbor to DOWN */ + nbr->state = EIGRP_NEIGHBOR_DOWN; + /* delete neighbor */ + eigrp_nbr_delete(nbr); + } + } + + return CMD_SUCCESS; +} + +/* + * Execute hard restart for neighbor specified by IP + */ +DEFPY (clear_ip_eigrp_neighbors_IP, + clear_ip_eigrp_neighbors_IP_cmd, + "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n" + "IP-EIGRP neighbor address\n") +{ + struct eigrp *eigrp; + struct eigrp_neighbor *nbr; + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* lookup neighbor in whole process */ + nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr); + + /* if neighbor doesn't exists, notify user and exit */ + if (nbr == NULL) { + vty_out(vty, "Neighbor with entered address doesn't exists.\n"); + return CMD_WARNING; + } + + /* execute hard reset on neighbor */ + eigrp_nbr_hard_restart(nbr, vty); + + return CMD_SUCCESS; +} + +/* + * Execute graceful restart for all neighbors + */ +DEFPY (clear_ip_eigrp_neighbors_soft, + clear_ip_eigrp_neighbors_soft_cmd, + "clear ip eigrp [vrf NAME] neighbors soft", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n" + "Resync with peers without adjacency reset\n") +{ + struct eigrp *eigrp; + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* execute graceful restart on all neighbors */ + eigrp_update_send_process_GR(eigrp, EIGRP_GR_MANUAL, vty); + + return CMD_SUCCESS; +} + +/* + * Execute graceful restart for all neighbors on interface + */ +DEFPY (clear_ip_eigrp_neighbors_int_soft, + clear_ip_eigrp_neighbors_int_soft_cmd, + "clear ip eigrp [vrf NAME] neighbors IFNAME soft", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n" + "Interface's name\n" + "Resync with peer without adjacency reset\n") +{ + struct eigrp *eigrp; + struct eigrp_interface *ei; + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* lookup interface by specified name */ + ei = eigrp_if_lookup_by_name(eigrp, ifname); + if (ei == NULL) { + vty_out(vty, " Interface (%s) doesn't exist\n", argv[4]->arg); + return CMD_WARNING; + } + + /* execute graceful restart for all neighbors on interface */ + eigrp_update_send_interface_GR(ei, EIGRP_GR_MANUAL, vty); + return CMD_SUCCESS; +} + +/* + * Execute graceful restart for neighbor specified by IP + */ +DEFPY (clear_ip_eigrp_neighbors_IP_soft, + clear_ip_eigrp_neighbors_IP_soft_cmd, + "clear ip eigrp [vrf NAME] neighbors A.B.C.D$nbr_addr soft", + CLEAR_STR + IP_STR + "Clear IP-EIGRP\n" + VRF_CMD_HELP_STR + "Clear IP-EIGRP neighbors\n" + "IP-EIGRP neighbor address\n" + "Resync with peer without adjacency reset\n") +{ + struct eigrp *eigrp; + struct eigrp_neighbor *nbr; + + + /* Check if eigrp process is enabled */ + eigrp = eigrp_vty_get_eigrp(vty, vrf); + if (eigrp == NULL) { + vty_out(vty, " EIGRP Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + /* lookup neighbor in whole process */ + nbr = eigrp_nbr_lookup_by_addr_process(eigrp, nbr_addr); + + /* if neighbor doesn't exists, notify user and exit */ + if (nbr == NULL) { + vty_out(vty, "Neighbor with entered address doesn't exists.\n"); + return CMD_WARNING; + } + + /* execute graceful restart on neighbor */ + eigrp_update_send_GR(nbr, EIGRP_GR_MANUAL, vty); + + return CMD_SUCCESS; +} + +void eigrp_vty_show_init(void) +{ + install_element(VIEW_NODE, &show_ip_eigrp_interfaces_cmd); + + install_element(VIEW_NODE, &show_ip_eigrp_neighbors_cmd); + + install_element(VIEW_NODE, &show_ip_eigrp_topology_cmd); + install_element(VIEW_NODE, &show_ip_eigrp_topology_all_cmd); +} + +/* Install EIGRP related vty commands. */ +void eigrp_vty_init(void) +{ + /* commands for manual hard restart */ + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_cmd); + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_cmd); + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_cmd); + /* commands for manual graceful restart */ + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_soft_cmd); + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_int_soft_cmd); + install_element(ENABLE_NODE, &clear_ip_eigrp_neighbors_IP_soft_cmd); +} diff --git a/eigrpd/eigrp_vty.h b/eigrpd/eigrp_vty.h new file mode 100644 index 0000000..fa07a78 --- /dev/null +++ b/eigrpd/eigrp_vty.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP VTY Interface. + * Copyright (C) 2013-2016 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + * Frantisek Gazo + * Tomas Hvorkovy + * Martin Kontsek + * Lukas Koribsky + */ + +#ifndef _QUAGGA_EIGRP_VTY_H +#define _QUAGGA_EIGRP_VTY_H + +/* Prototypes. */ +extern void eigrp_vty_init(void); +extern void eigrp_vty_show_init(void); + +#endif /* _Quagga_EIGRP_VTY_H_ */ diff --git a/eigrpd/eigrp_yang.h b/eigrpd/eigrp_yang.h new file mode 100644 index 0000000..b59f5f9 --- /dev/null +++ b/eigrpd/eigrp_yang.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP YANG Functions. + * Copyright (C) 2019 + * Authors: + * Donnie Savage + */ + +#ifndef _EIGRP_YANG_H_ +#define _EIGRP_YANG_H_ + +/*Prototypes*/ + +/* eigrp_northbound.c */ +extern const struct frr_yang_module_info frr_eigrpd_info; + +#endif /*EIGRP_YANG_H_ */ diff --git a/eigrpd/eigrp_zebra.c b/eigrpd/eigrp_zebra.c new file mode 100644 index 0000000..a0eff68 --- /dev/null +++ b/eigrpd/eigrp_zebra.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for EIGRP. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "routemap.h" +#include "table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "filter.h" +#include "plist.h" +#include "log.h" +#include "nexthop.h" + +#include "eigrpd/eigrp_types.h" +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_dump.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_fsm.h" +#include "eigrpd/eigrp_metric.h" + +static int eigrp_interface_address_add(ZAPI_CALLBACK_ARGS); +static int eigrp_interface_address_delete(ZAPI_CALLBACK_ARGS); + +static int eigrp_zebra_read_route(ZAPI_CALLBACK_ARGS); + +/* Zebra structure to hold current status. */ +struct zclient *zclient = NULL; + +/* For registering threads. */ +extern struct event_loop *master; +struct in_addr router_id_zebra; + +/* Router-id update message from zebra. */ +static int eigrp_router_id_update_zebra(ZAPI_CALLBACK_ARGS) +{ + struct eigrp *eigrp; + struct prefix router_id; + zebra_router_id_update_read(zclient->ibuf, &router_id); + + router_id_zebra = router_id.u.prefix4; + + eigrp = eigrp_lookup(vrf_id); + + if (eigrp != NULL) + eigrp_router_id_update(eigrp); + + return 0; +} + +static int eigrp_zebra_route_notify_owner(ZAPI_CALLBACK_ARGS) +{ + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table, ¬e, NULL, + NULL)) + return -1; + + return 0; +} + +static void eigrp_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); +} + +static zclient_handler *const eigrp_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = eigrp_router_id_update_zebra, + [ZEBRA_INTERFACE_ADDRESS_ADD] = eigrp_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = eigrp_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = eigrp_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = eigrp_zebra_read_route, + [ZEBRA_ROUTE_NOTIFY_OWNER] = eigrp_zebra_route_notify_owner, +}; + +void eigrp_zebra_init(void) +{ + zclient = zclient_new(master, &zclient_options_default, eigrp_handlers, + array_size(eigrp_handlers)); + + zclient_init(zclient, ZEBRA_ROUTE_EIGRP, 0, &eigrpd_privs); + zclient->zebra_connected = eigrp_zebra_connected; +} + + +/* Zebra route add and delete treatment. */ +static int eigrp_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct eigrp *eigrp; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + if (IPV4_NET127(ntohl(api.prefix.u.prefix4.s_addr))) + return 0; + + eigrp = eigrp_lookup(vrf_id); + if (eigrp == NULL) + return 0; + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { + + } else /* if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) */ + { + } + + return 0; +} + +static int eigrp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: interface %s address add %pFX", c->ifp->name, + c->address); + + eigrp_if_update(c->ifp); + + return 0; +} + +static int eigrp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct interface *ifp; + struct eigrp_interface *ei; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: interface %s address delete %pFX", + c->ifp->name, c->address); + + ifp = c->ifp; + ei = ifp->info; + if (!ei) + return 0; + + /* Call interface hook functions to clean up */ + if (prefix_cmp(&ei->address, c->address) == 0) + eigrp_if_free(ei, INTERFACE_DOWN_BY_ZEBRA); + + connected_free(&c); + + return 0; +} + +void eigrp_zebra_route_add(struct eigrp *eigrp, struct prefix *p, + struct list *successors, uint32_t distance) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct eigrp_route_descriptor *te; + struct listnode *node; + int count = 0; + + if (!zclient->redist[AFI_IP][ZEBRA_ROUTE_EIGRP]) + return; + + memset(&api, 0, sizeof(api)); + api.vrf_id = eigrp->vrf_id; + api.type = ZEBRA_ROUTE_EIGRP; + api.safi = SAFI_UNICAST; + api.metric = distance; + memcpy(&api.prefix, p, sizeof(*p)); + + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + + /* Nexthop, ifindex, distance and metric information. */ + for (ALL_LIST_ELEMENTS_RO(successors, node, te)) { + if (count >= MULTIPATH_NUM) + break; + api_nh = &api.nexthops[count]; + api_nh->vrf_id = eigrp->vrf_id; + if (te->adv_router->src.s_addr) { + api_nh->gate.ipv4 = te->adv_router->src; + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + } else + api_nh->type = NEXTHOP_TYPE_IFINDEX; + api_nh->ifindex = te->ei->ifp->ifindex; + + count++; + } + api.nexthop_num = count; + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) { + zlog_debug("Zebra: Route add %pFX", p); + } + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); +} + +void eigrp_zebra_route_delete(struct eigrp *eigrp, struct prefix *p) +{ + struct zapi_route api; + + if (!zclient->redist[AFI_IP][ZEBRA_ROUTE_EIGRP]) + return; + + memset(&api, 0, sizeof(api)); + api.vrf_id = eigrp->vrf_id; + api.type = ZEBRA_ROUTE_EIGRP; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, p, sizeof(*p)); + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + + if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Zebra: Route del %pFX", p); + + return; +} + +static int eigrp_is_type_redistributed(int type, vrf_id_t vrf_id) +{ + return ((DEFAULT_ROUTE_TYPE(type)) + ? vrf_bitmap_check( + &zclient->default_information[AFI_IP], vrf_id) + : vrf_bitmap_check(&zclient->redist[AFI_IP][type], + vrf_id)); +} + +int eigrp_redistribute_set(struct eigrp *eigrp, int type, + struct eigrp_metrics metric) +{ + + if (eigrp_is_type_redistributed(type, eigrp->vrf_id)) { + if (eigrp_metrics_is_same(metric, eigrp->dmetric[type])) { + eigrp->dmetric[type] = metric; + } + + eigrp_external_routes_refresh(eigrp, type); + + // if (IS_DEBUG_EIGRP(zebra, ZEBRA_REDISTRIBUTE)) + // zlog_debug ("Redistribute[%s]: Refresh Type[%d], + // Metric[%d]", + // eigrp_redist_string(type), + // metric_type (eigrp, type), metric_value + // (eigrp, type)); + return CMD_SUCCESS; + } + + eigrp->dmetric[type] = metric; + + zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, type, 0, + eigrp->vrf_id); + + ++eigrp->redistribute; + + return CMD_SUCCESS; +} + +int eigrp_redistribute_unset(struct eigrp *eigrp, int type) +{ + + if (eigrp_is_type_redistributed(type, eigrp->vrf_id)) { + memset(&eigrp->dmetric[type], 0, sizeof(struct eigrp_metrics)); + zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, + type, 0, eigrp->vrf_id); + --eigrp->redistribute; + } + + return CMD_SUCCESS; +} diff --git a/eigrpd/eigrp_zebra.h b/eigrpd/eigrp_zebra.h new file mode 100644 index 0000000..927d562 --- /dev/null +++ b/eigrpd/eigrp_zebra.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for EIGRP. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRP_ZEBRA_H_ +#define _ZEBRA_EIGRP_ZEBRA_H_ + +#include "vty.h" +#include "vrf.h" + +extern void eigrp_zebra_init(void); + +extern void eigrp_zebra_route_add(struct eigrp *eigrp, struct prefix *p, + struct list *successors, uint32_t distance); +extern void eigrp_zebra_route_delete(struct eigrp *eigrp, struct prefix *); +extern int eigrp_redistribute_set(struct eigrp *, int, struct eigrp_metrics); +extern int eigrp_redistribute_unset(struct eigrp *, int); + +#endif /* _ZEBRA_EIGRP_ZEBRA_H_ */ diff --git a/eigrpd/eigrpd.c b/eigrpd/eigrpd.c new file mode 100644 index 0000000..c7dd96b --- /dev/null +++ b/eigrpd/eigrpd.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP Daemon Program. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "if.h" +#include "memory.h" +#include "stream.h" +#include "log.h" +#include "sockunion.h" /* for inet_aton () */ +#include "zclient.h" +#include "plist.h" +#include "sockopt.h" +#include "keychain.h" +#include "libfrr.h" +#include "lib_errors.h" +#include "distribute.h" + +#include "eigrpd/eigrp_structs.h" +#include "eigrpd/eigrpd.h" +#include "eigrpd/eigrp_interface.h" +#include "eigrpd/eigrp_zebra.h" +#include "eigrpd/eigrp_vty.h" +#include "eigrpd/eigrp_neighbor.h" +#include "eigrpd/eigrp_packet.h" +#include "eigrpd/eigrp_network.h" +#include "eigrpd/eigrp_topology.h" +#include "eigrpd/eigrp_filter.h" + +DEFINE_MGROUP(EIGRPD, "eigrpd"); + +DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_TOP, "EIGRP structure"); + +DEFINE_QOBJ_TYPE(eigrp); + +static struct eigrp_master eigrp_master; + +struct eigrp_master *eigrp_om; + +extern struct zclient *zclient; +extern struct in_addr router_id_zebra; + + +/* + * void eigrp_router_id_update(struct eigrp *eigrp) + * + * Description: + * update routerid associated with this instance of EIGRP. + * If the id changes, then call if_update for each interface + * to resync the topology database with all neighbors + * + * Select the router ID based on these priorities: + * 1. Statically assigned router ID is always the first choice. + * 2. If there is no statically assigned router ID, then try to stick + * with the most recent value, since changing router ID's is very + * disruptive. + * 3. Last choice: just go with whatever the zebra daemon recommends. + * + * Note: + * router id for EIGRP is really just a 32 bit number. Cisco historically + * displays it in dotted decimal notation, and will pickup an IP address + * from an interface so it can be 'auto-configed" to a uniqe value + * + * This does not work for IPv6, and to make the code simpler, its + * stored and processed internerall as a 32bit number + */ +void eigrp_router_id_update(struct eigrp *eigrp) +{ + struct vrf *vrf = vrf_lookup_by_id(eigrp->vrf_id); + struct interface *ifp; + struct in_addr router_id, router_id_old; + + router_id_old = eigrp->router_id; + + if (eigrp->router_id_static.s_addr != INADDR_ANY) + router_id = eigrp->router_id_static; + + else if (eigrp->router_id.s_addr != INADDR_ANY) + router_id = eigrp->router_id; + + else + router_id = router_id_zebra; + + eigrp->router_id = router_id; + if (router_id_old.s_addr != router_id.s_addr) { + /* update eigrp_interface's */ + FOR_ALL_INTERFACES (vrf, ifp) + eigrp_if_update(ifp); + } +} + +void eigrp_master_init(void) +{ + struct timeval tv; + + memset(&eigrp_master, 0, sizeof(eigrp_master)); + + eigrp_om = &eigrp_master; + eigrp_om->eigrp = list_new(); + + monotime(&tv); + eigrp_om->start_time = tv.tv_sec; +} + +/* Allocate new eigrp structure. */ +static struct eigrp *eigrp_new(uint16_t as, vrf_id_t vrf_id) +{ + struct eigrp *eigrp = XCALLOC(MTYPE_EIGRP_TOP, sizeof(struct eigrp)); + + /* init information relevant to peers */ + eigrp->vrf_id = vrf_id; + eigrp->vrid = 0; + eigrp->AS = as; + eigrp->router_id.s_addr = INADDR_ANY; + eigrp->router_id_static.s_addr = INADDR_ANY; + eigrp->sequence_number = 1; + + /*Configure default K Values for EIGRP Process*/ + eigrp->k_values[0] = EIGRP_K1_DEFAULT; + eigrp->k_values[1] = EIGRP_K2_DEFAULT; + eigrp->k_values[2] = EIGRP_K3_DEFAULT; + eigrp->k_values[3] = EIGRP_K4_DEFAULT; + eigrp->k_values[4] = EIGRP_K5_DEFAULT; + eigrp->k_values[5] = EIGRP_K6_DEFAULT; + + /* init internal data structures */ + eigrp->eiflist = list_new(); + eigrp->passive_interface_default = EIGRP_IF_ACTIVE; + eigrp->networks = eigrp_topology_new(); + + eigrp->fd = eigrp_sock_init(vrf_lookup_by_id(vrf_id)); + + if (eigrp->fd < 0) { + flog_err_sys( + EC_LIB_SOCKET, + "%s: fatal error: eigrp_sock_init was unable to open a socket", + __func__); + exit(1); + } + + eigrp->maxsndbuflen = getsockopt_so_sendbuf(eigrp->fd); + + eigrp->ibuf = stream_new(EIGRP_PACKET_MAX_LEN + 1); + + event_add_read(master, eigrp_read, eigrp, eigrp->fd, &eigrp->t_read); + eigrp->oi_write_q = list_new(); + + eigrp->topology_table = route_table_init(); + + eigrp->neighbor_self = eigrp_nbr_new(NULL); + eigrp->neighbor_self->src.s_addr = INADDR_ANY; + + eigrp->variance = EIGRP_VARIANCE_DEFAULT; + eigrp->max_paths = EIGRP_MAX_PATHS_DEFAULT; + + eigrp->serno = 0; + eigrp->serno_last_update = 0; + eigrp->topology_changes_externalIPV4 = list_new(); + eigrp->topology_changes_internalIPV4 = list_new(); + + eigrp->list[EIGRP_FILTER_IN] = NULL; + eigrp->list[EIGRP_FILTER_OUT] = NULL; + + eigrp->prefix[EIGRP_FILTER_IN] = NULL; + eigrp->prefix[EIGRP_FILTER_OUT] = NULL; + + eigrp->routemap[EIGRP_FILTER_IN] = NULL; + eigrp->routemap[EIGRP_FILTER_OUT] = NULL; + + /* Distribute list install. */ + eigrp->distribute_ctx = + distribute_list_ctx_create(vrf_lookup_by_id(eigrp->vrf_id)); + distribute_list_add_hook(eigrp->distribute_ctx, + eigrp_distribute_update); + distribute_list_delete_hook(eigrp->distribute_ctx, + eigrp_distribute_update); + + /* + eigrp->if_rmap_ctx = if_rmap_ctx_create(eigrp->vrf_id); + if_rmap_hook_add (eigrp_if_rmap_update); + if_rmap_hook_delete (eigrp_if_rmap_update); + */ + QOBJ_REG(eigrp, eigrp); + return eigrp; +} + +struct eigrp *eigrp_get(uint16_t as, vrf_id_t vrf_id) +{ + struct eigrp *eigrp; + + eigrp = eigrp_lookup(vrf_id); + if (eigrp == NULL) { + eigrp = eigrp_new(as, vrf_id); + listnode_add(eigrp_om->eigrp, eigrp); + } + + return eigrp; +} + +/* Shut down the entire process */ +void eigrp_terminate(void) +{ + struct eigrp *eigrp; + struct listnode *node, *nnode; + + /* shutdown already in progress */ + if (CHECK_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN)) + return; + + SET_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN); + + for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) + eigrp_finish(eigrp); + + frr_fini(); +} + +void eigrp_finish(struct eigrp *eigrp) +{ + eigrp_finish_final(eigrp); + + /* eigrp being shut-down? If so, was this the last eigrp instance? */ + if (CHECK_FLAG(eigrp_om->options, EIGRP_MASTER_SHUTDOWN) + && (listcount(eigrp_om->eigrp) == 0)) { + if (zclient) { + zclient_stop(zclient); + zclient_free(zclient); + } + exit(0); + } + + return; +} + +/* Final cleanup of eigrp instance */ +void eigrp_finish_final(struct eigrp *eigrp) +{ + struct eigrp_interface *ei; + struct eigrp_neighbor *nbr; + struct listnode *node, *nnode, *node2, *nnode2; + + for (ALL_LIST_ELEMENTS(eigrp->eiflist, node, nnode, ei)) { + for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) + eigrp_nbr_delete(nbr); + eigrp_if_free(ei, INTERFACE_DOWN_BY_FINAL); + } + + EVENT_OFF(eigrp->t_write); + EVENT_OFF(eigrp->t_read); + close(eigrp->fd); + + list_delete(&eigrp->eiflist); + list_delete(&eigrp->oi_write_q); + + eigrp_topology_free(eigrp, eigrp->topology_table); + + eigrp_nbr_delete(eigrp->neighbor_self); + + list_delete(&eigrp->topology_changes_externalIPV4); + list_delete(&eigrp->topology_changes_internalIPV4); + + listnode_delete(eigrp_om->eigrp, eigrp); + + stream_free(eigrp->ibuf); + distribute_list_delete(&eigrp->distribute_ctx); + XFREE(MTYPE_EIGRP_TOP, eigrp); +} + +/*Look for existing eigrp process*/ +struct eigrp *eigrp_lookup(vrf_id_t vrf_id) +{ + struct eigrp *eigrp; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(eigrp_om->eigrp, node, nnode, eigrp)) + if (eigrp->vrf_id == vrf_id) + return eigrp; + + return NULL; +} diff --git a/eigrpd/eigrpd.h b/eigrpd/eigrpd.h new file mode 100644 index 0000000..a6a4e39 --- /dev/null +++ b/eigrpd/eigrpd.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * EIGRP main header. + * Copyright (C) 2013-2014 + * Authors: + * Donnie Savage + * Jan Janovic + * Matej Perina + * Peter Orsag + * Peter Paluch + */ + +#ifndef _ZEBRA_EIGRPD_H +#define _ZEBRA_EIGRPD_H + +#include + +#include "filter.h" +#include "log.h" +#include "memory.h" + +DECLARE_MGROUP(EIGRPD); + +/* Set EIGRP version is "classic" - wide metrics comes next */ +#define EIGRP_MAJOR_VERSION 1 +#define EIGRP_MINOR_VERSION 2 + +#define EIGRP_TLV_32B_VERSION 1 /* Original 32bit scaled metrics */ +#define EIGRP_TLV_64B_VERSION 2 /* Current 64bit 'wide' metrics */ +#define EIGRP_TLV_MTR_VERSION 3 /* MTR TLVs with 32bit metric *Not Supported */ +#define EIGRP_TLV_SAF_VERSION 4 /* SAF TLVs with 64bit metric *Not Supported */ + +struct eigrp_master { + /* EIGRP instance. */ + struct list *eigrp; + + /* EIGRP thread master. */ + struct event_loop *master; + + /* Zebra interface list. */ + struct list *iflist; + + /* EIGRP start time. */ + time_t start_time; + + /* Various EIGRP global configuration. */ + uint8_t options; + +#define EIGRP_MASTER_SHUTDOWN (1 << 0) /* deferred-shutdown */ +}; + +/* Extern variables. */ +extern struct zclient *zclient; +extern struct event_loop *master; +extern struct eigrp_master *eigrp_om; +extern struct zebra_privs_t eigrpd_privs; + +/* Prototypes */ +extern void eigrp_master_init(void); +extern void eigrp_terminate(void); +extern void eigrp_finish_final(struct eigrp *eigrp); +extern void eigrp_finish(struct eigrp *eigrp); +extern struct eigrp *eigrp_get(uint16_t as, vrf_id_t vrf_id); +extern struct eigrp *eigrp_lookup(vrf_id_t vrf_id); +extern void eigrp_router_id_update(struct eigrp *eigrp); + +#endif /* _ZEBRA_EIGRPD_H */ diff --git a/eigrpd/subdir.am b/eigrpd/subdir.am new file mode 100644 index 0000000..e417132 --- /dev/null +++ b/eigrpd/subdir.am @@ -0,0 +1,75 @@ +# +# eigrpd +# + +if EIGRPD +sbin_PROGRAMS += eigrpd/eigrpd +vtysh_daemons += eigrpd +man8 += $(MANBUILD)/frr-eigrpd.8 +endif + +eigrpd_eigrpd_SOURCES = \ + eigrpd/eigrp_cli.c \ + eigrpd/eigrp_dump.c \ + eigrpd/eigrp_errors.c \ + eigrpd/eigrp_filter.c \ + eigrpd/eigrp_fsm.c \ + eigrpd/eigrp_hello.c \ + eigrpd/eigrp_interface.c \ + eigrpd/eigrp_main.c \ + eigrpd/eigrp_metric.c \ + eigrpd/eigrp_neighbor.c \ + eigrpd/eigrp_network.c \ + eigrpd/eigrp_northbound.c \ + eigrpd/eigrp_packet.c \ + eigrpd/eigrp_query.c \ + eigrpd/eigrp_reply.c \ + eigrpd/eigrp_siaquery.c \ + eigrpd/eigrp_siareply.c \ + eigrpd/eigrp_snmp.c \ + eigrpd/eigrp_topology.c \ + eigrpd/eigrp_update.c \ + eigrpd/eigrp_vrf.c \ + eigrpd/eigrp_vty.c \ + eigrpd/eigrp_zebra.c \ + eigrpd/eigrpd.c \ + # end + +eigrpdheaderdir = $(pkgincludedir)/eigrpd +eigrpdheader_HEADERS = \ + eigrpd/eigrp_dump.h \ + eigrpd/eigrp_topology.h \ + eigrpd/eigrpd.h \ + # end + +clippy_scan += \ + eigrpd/eigrp_cli.c \ + eigrpd/eigrp_vty.c \ + # end + +noinst_HEADERS += \ + eigrpd/eigrp_cli.h \ + eigrpd/eigrp_const.h \ + eigrpd/eigrp_errors.h \ + eigrpd/eigrp_filter.h \ + eigrpd/eigrp_fsm.h \ + eigrpd/eigrp_interface.h \ + eigrpd/eigrp_macros.h \ + eigrpd/eigrp_metric.h \ + eigrpd/eigrp_neighbor.h \ + eigrpd/eigrp_network.h \ + eigrpd/eigrp_packet.h \ + eigrpd/eigrp_snmp.h \ + eigrpd/eigrp_structs.h \ + eigrpd/eigrp_types.h \ + eigrpd/eigrp_vrf.h \ + eigrpd/eigrp_vty.h \ + eigrpd/eigrp_yang.h \ + eigrpd/eigrp_zebra.h \ + # end + +nodist_eigrpd_eigrpd_SOURCES = \ + yang/frr-eigrpd.yang.c \ + # end + +eigrpd_eigrpd_LDADD = lib/libfrr.la $(LIBCAP) diff --git a/fpm/Makefile b/fpm/Makefile new file mode 100644 index 0000000..1d280d1 --- /dev/null +++ b/fpm/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. fpm/libfrrfpm_pb.la +%: ALWAYS + @$(MAKE) -s -C .. fpm/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/fpm/fpm.h b/fpm/fpm.h new file mode 100644 index 0000000..70c0df5 --- /dev/null +++ b/fpm/fpm.h @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: ISC OR GPL-2.0-or-later +/* + * Public definitions pertaining to the Forwarding Plane Manager component. + * + * Permission is granted to use, copy, modify and/or distribute this + * software under either one of the licenses below. + * + * Note that if you use other files from the Quagga tree directly or + * indirectly, then the licenses in those files still apply. + * + * Please retain both licenses below when modifying this code in the + * Quagga tree. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + */ + +/* + * License Option 1: GPL + * License Option 2: ISC License + */ + +#ifndef _FPM_H +#define _FPM_H + +/* + * The Forwarding Plane Manager (FPM) is an optional component that + * may be used in scenarios where the router has a forwarding path + * that is distinct from the kernel, commonly a hardware-based fast + * path. It is responsible for programming forwarding information + * (such as routes and nexthops) in the fast path. + * + * In Quagga, the Routing Information Base is maintained in the + * 'zebra' infrastructure daemon. Routing protocols communicate their + * best routes to zebra, and zebra computes the best route across + * protocols for each prefix. This latter information comprises the + * bulk of the Forwarding Information Base. + * + * This header file defines a point-to-point interface using which + * zebra can update the FPM about changes in routes. The communication + * takes place over a stream socket. The FPM listens on a well-known + * TCP port, and zebra initiates the connection. + * + * All messages sent over the connection start with a short FPM + * header, fpm_msg_hdr_t. In the case of route add/delete messages, + * the header is followed by a netlink message. Zebra should send a + * complete copy of the forwarding table(s) to the FPM, including + * routes that it may have picked up from the kernel. + * + * The FPM interface uses replace semantics. That is, if a 'route add' + * message for a prefix is followed by another 'route add' message, the + * information in the second message is complete by itself, and replaces + * the information sent in the first message. + * + * If the connection to the FPM goes down for some reason, the client + * (zebra) should send the FPM a complete copy of the forwarding + * table(s) when it reconnects. + */ + +/* + * Local host as a default server for fpm connection + */ +#define FPM_DEFAULT_IP (htonl (INADDR_LOOPBACK)) + +/* + * Largest message that can be sent to or received from the FPM. + */ +#define FPM_MAX_MSG_LEN 4096 + +#ifdef __SUNPRO_C +#pragma pack(1) +#endif + +/* + * Header that precedes each fpm message to/from the FPM. + */ +typedef struct fpm_msg_hdr_t_ { + /* + * Protocol version. + */ + uint8_t version; + + /* + * Type of message, see below. + */ + uint8_t msg_type; + + /* + * Length of entire message, including the header, in network byte + * order. + */ + uint16_t msg_len; +} __attribute__((packed)) fpm_msg_hdr_t; + +#ifdef __SUNPRO_C +#pragma pack() +#endif + +/* + * The current version of the FPM protocol is 1. + */ +#define FPM_PROTO_VERSION 1 + +typedef enum fpm_msg_type_e_ { + FPM_MSG_TYPE_NONE = 0, + + /* + * Indicates that the payload is a completely formed netlink + * message. + * + * XXX Netlink cares about the alignment of messages. When any + * FPM_MSG_TYPE_NETLINK messages are sent over a channel, then all + * messages should be sized such that netlink alignment is + * maintained. + */ + FPM_MSG_TYPE_NETLINK = 1, + FPM_MSG_TYPE_PROTOBUF = 2, +} fpm_msg_type_e; + +/* + * The FPM message header is aligned to the same boundary as netlink + * messages (4). This means that a netlink message does not need + * padding when encapsulated in an FPM message. + */ +#define FPM_MSG_ALIGNTO 4 + +/* + * fpm_msg_align + * + * Round up the given length to the desired alignment. + * + * **NB**: Alignment is required only when netlink messages are used. + */ +static inline size_t fpm_msg_align(size_t len) +{ + return (len + FPM_MSG_ALIGNTO - 1) & ~(FPM_MSG_ALIGNTO - 1); +} + +/* + * The (rounded up) size of the FPM message header. This ensures that + * the message payload always starts at an aligned address. + */ +#define FPM_MSG_HDR_LEN (sizeof(fpm_msg_hdr_t)) + +#ifndef COMPILE_ASSERT +#define COMPILE_ASSERT(x) extern int __dummy[2 * !!(x) - 1] +#endif + +COMPILE_ASSERT(FPM_MSG_ALIGNTO == FPM_MSG_HDR_LEN); + +/* + * fpm_data_len_to_msg_len + * + * The length value that should be placed in the msg_len field of the + * header for a *payload* of size 'data_len'. + */ +static inline size_t fpm_data_len_to_msg_len(size_t data_len) +{ + return data_len + FPM_MSG_HDR_LEN; +} + +/* + * fpm_msg_data + * + * Pointer to the payload of the given fpm header. + */ +static inline void *fpm_msg_data(fpm_msg_hdr_t *hdr) +{ + return ((char *)hdr) + FPM_MSG_HDR_LEN; +} + +/* + * fpm_msg_len + */ +static inline size_t fpm_msg_len(const fpm_msg_hdr_t *hdr) +{ + return ntohs(hdr->msg_len); +} + +/* + * fpm_msg_data_len + */ +static inline size_t fpm_msg_data_len(const fpm_msg_hdr_t *hdr) +{ + return (fpm_msg_len(hdr) - FPM_MSG_HDR_LEN); +} + +/* + * fpm_msg_next + * + * Move to the next message in a buffer. + */ +static inline fpm_msg_hdr_t *fpm_msg_next(fpm_msg_hdr_t *hdr, size_t *len) +{ + size_t msg_len; + + msg_len = fpm_msg_len(hdr); + + if (len) { + if (*len < msg_len) { + assert(0); + return NULL; + } + *len -= msg_len; + } + + return (fpm_msg_hdr_t *)(((char *)hdr) + msg_len); +} + +/* + * fpm_msg_hdr_ok + * + * Returns true if a message header looks well-formed. + */ +static inline int fpm_msg_hdr_ok(const fpm_msg_hdr_t *hdr) +{ + size_t msg_len; + + if (hdr->msg_type == FPM_MSG_TYPE_NONE) + return 0; + + msg_len = fpm_msg_len(hdr); + + if (msg_len < FPM_MSG_HDR_LEN || msg_len > FPM_MAX_MSG_LEN) + return 0; + + /* + * Netlink messages must be aligned properly. + */ + if (hdr->msg_type == FPM_MSG_TYPE_NETLINK + && fpm_msg_align(msg_len) != msg_len) + return 0; + + return 1; +} + +/* + * fpm_msg_ok + * + * Returns true if a message looks well-formed. + * + * @param len The length in bytes from 'hdr' to the end of the buffer. + */ +static inline int fpm_msg_ok(const fpm_msg_hdr_t *hdr, size_t len) +{ + if (len < FPM_MSG_HDR_LEN) + return 0; + + if (!fpm_msg_hdr_ok(hdr)) + return 0; + + if (fpm_msg_len(hdr) > len) + return 0; + + return 1; +} + +// tcp maximum range +#define TCP_MAX_PORT 65535 + +// tcp minimum range +#define TCP_MIN_PORT 1 + +#endif /* _FPM_H */ diff --git a/fpm/fpm.proto b/fpm/fpm.proto new file mode 100644 index 0000000..beaa5d6 --- /dev/null +++ b/fpm/fpm.proto @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: ISC +// +// fpm.proto +// +// @copyright Copyright (C) 2016 Sproute Networks, Inc. +// +// @author Avneesh Sachdev +// +// Portions: +// Copyright (C) 2024 Carmine Scarpitta (for SRv6) +// +// Permission to use, copy, modify, and/or distribute this software +// for any purpose with or without fee is hereby granted, provided +// that the above copyright notice and this permission notice appear +// in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +// OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +syntax = "proto2"; + +// +// Protobuf definitions pertaining to the Forwarding Plane Manager component. +// + +package fpm; + +import "qpb/qpb.proto"; + +// +// A Nexthop for a route. It indicates how packets to a given prefix +// should be forwarded (for instance, send them out of a specified +// interface to a specified address). +// +message Nexthop { + optional qpb.IfIdentifier if_id = 2; + optional qpb.L3Address address = 3; +} + +message RouteKey { + optional qpb.L3Prefix prefix = 1; +} + +message DeleteRoute { + required uint32 vrf_id = 1; + required qpb.AddressFamily address_family = 2; + required qpb.SubAddressFamily sub_address_family = 3; + required RouteKey key = 4; +} + +enum RouteType { + UNKNOWN = 0; + NORMAL = 1; + UNREACHABLE = 2; + BLACKHOLE = 3; +} + +message AddRoute { + required uint32 vrf_id = 1; + required qpb.AddressFamily address_family = 2; + required qpb.SubAddressFamily sub_address_family = 3; + required RouteKey key = 4; + + optional RouteType route_type = 5; + + required qpb.Protocol protocol = 6; + + required int32 metric = 8; + + repeated Nexthop nexthops = 9; + + /* Source Address of outer encapsulating IPv6 header */ + optional qpb.Ipv6Address srv6_encap_source_address = 10; + /* SRv6 SID for VPN use cases */ + optional qpb.Ipv6Address srv6_vpn_sid = 11; +} + +/* SID Format - as per RFC 8986 section #3.1 */ +message SRv6SIDFormat +{ + /* Locator block length */ + required uint32 locator_block_length = 1; + /* Locator node length */ + required uint32 locator_node_length = 2; + /* Function length */ + required uint32 function_length = 3; + /* Argument length */ + required uint32 argument_length = 4; +} + +/* SRv6 Local SID */ +message SRv6LocalSID +{ + /* SRv6 SID value */ + required qpb.Ipv6Address sid = 1; + + /* SID Format - as per RFC 8986 section #3.1 */ + optional SRv6SIDFormat sid_format = 2; + + /* SRv6 Endpoint Behavior associated with the SID */ + oneof end_behavior + { + /* Endpoint */ + End end = 3; + /* Endpoint with L3 cross-connect */ + EndX end_x = 4; + /* Endpoint with specific IPv6 table lookup */ + EndT end_t = 5; + /* Endpoint with decapsulation and IPv6 cross-connect */ + EndDX6 end_dx6 = 7; + /* Endpoint with decapsulation and IPv4 cross-connect */ + EndDX4 end_dx4 = 8; + /* Endpoint with decapsulation and specific IPv6 table lookup */ + EndDT6 end_dt6 = 9; + /* Endpoint with decapsulation and specific IPv4 table lookup */ + EndDT4 end_dt4 = 10; + /* Endpoint with decapsulation and specific IP table lookup */ + EndDT46 end_dt46 = 11; + /* Endpoint behavior with NEXT-CSID, PSP and USD flavors */ + UN un = 12; + /* End.X behavior with NEXT-CSID, PSP and USD flavors */ + UA ua = 13; + /* End.DT6 behavior with NEXT-CSID flavor */ + UDT6 udt6 = 14; + /* End.DT4 behavior with NEXT-CSID flavor */ + UDT4 udt4 = 15; + /* End.DT46 behavior with NEXT-CSID flavor */ + UDT46 udt46 = 16; + } + + /* Endpoint */ + message End + { + } + + /* Endpoint with L3 cross-connect */ + message EndX + { + required Nexthop nexthop = 1; + } + + /* Endpoint with specific IPv6 table lookup */ + message EndT + { + required uint32 vrf_id = 1; + } + + /* Endpoint with decapsulation and IPv6 cross-connect */ + message EndDX6 + { + required Nexthop nexthop = 1; + } + + /* Endpoint with decapsulation and IPv4 cross-connect */ + message EndDX4 + { + required Nexthop nexthop = 1; + } + + /* Endpoint with decapsulation and specific IPv6 table lookup */ + message EndDT6 + { + required uint32 vrf_id = 1; + } + + /* Endpoint with decapsulation and specific IPv4 table lookup */ + message EndDT4 + { + required uint32 vrf_id = 1; + } + + /* Endpoint with decapsulation and specific IP table lookup */ + message EndDT46 + { + required uint32 vrf_id = 1; + } + + /* Endpoint behavior with NEXT-CSID, PSP and USD flavors */ + message UN + { + } + + /* End.X behavior with NEXT-CSID, PSP and USD flavors */ + message UA + { + required Nexthop nexthop = 1; + } + + /* End.DT6 behavior with NEXT-CSID flavor */ + message UDT6 + { + required uint32 vrf_id = 1; + } + + /* End.DT4 behavior with NEXT-CSID flavor */ + message UDT4 + { + required uint32 vrf_id = 1; + } + + /* End.DT46 behavior with NEXT-CSID flavor */ + message UDT46 + { + required uint32 vrf_id = 1; + } +} + +// +// Any message from the FPM. +// +message Message { + enum Type { + UNKNOWN_MSG = 0; + ADD_ROUTE = 1; + DELETE_ROUTE = 2; + /* Install an SRv6 Local SID */ + ADD_SRV6_LOCALSID = 3; + /* Remove an SRv6 Local SID */ + DELETE_SRV6_LOCALSID = 4; + }; + + optional Type type = 1; + + optional AddRoute add_route = 2; + optional DeleteRoute delete_route = 3; + + /* SRv6 Local SID */ + optional SRv6LocalSID srv6_localsid = 4; +} diff --git a/fpm/fpm_pb.c b/fpm/fpm_pb.c new file mode 100644 index 0000000..0e8f618 --- /dev/null +++ b/fpm/fpm_pb.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * fpm_pb.c + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +/* + * Main file for the fpm_pb library. + */ + +#include "config.h" +#include "xref.h" + +XREF_SETUP(); diff --git a/fpm/fpm_pb.h b/fpm/fpm_pb.h new file mode 100644 index 0000000..23d7e43 --- /dev/null +++ b/fpm/fpm_pb.h @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * fpm_pb.h + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + * + * Portions: + * Copyright (C) 2024 Carmine Scarpitta (for SRv6) + */ + +/* + * Public header file for fpm protobuf definitions. + */ + +#ifndef _FPM_PB_H +#define _FPM_PB_H + +#include "lib/route_types.h" +#include "lib/vrf.h" +#include "qpb/qpb.h" + +#include "fpm/fpm.pb-c.h" + +/* + * fpm__route_key__create + */ +#define fpm_route_key_create fpm__route_key__create +static inline Fpm__RouteKey *fpm__route_key__create(qpb_allocator_t *allocator, + struct prefix *prefix) +{ + Fpm__RouteKey *key; + + key = QPB_ALLOC(allocator, typeof(*key)); + if (!key) { + return NULL; + } + fpm__route_key__init(key); + + key->prefix = qpb__l3_prefix__create(allocator, prefix); + if (!key->prefix) { + return NULL; + } + + return key; +} + +/* + * fpm__nexthop__create + */ +#define fpm_nexthop_create fpm__nexthop__create +static inline Fpm__Nexthop * +fpm__nexthop__create(qpb_allocator_t *allocator, struct nexthop *nh) +{ + Fpm__Nexthop *nexthop; + uint8_t family; + + nexthop = QPB_ALLOC(allocator, typeof(*nexthop)); + if (!nexthop) + return NULL; + + fpm__nexthop__init(nexthop); + + if (nh->type == NEXTHOP_TYPE_IPV4 || + nh->type == NEXTHOP_TYPE_IPV4_IFINDEX) + family = AF_INET; + else if (nh->type == NEXTHOP_TYPE_IPV6 || + nh->type == NEXTHOP_TYPE_IPV6_IFINDEX) + family = AF_INET6; + else + return NULL; + + nexthop->if_id = qpb__if_identifier__create(allocator, nh->ifindex); + if (!nexthop->if_id) + return NULL; + + nexthop->address = qpb__l3_address__create(allocator, &nh->gate, family); + if (!nexthop->address) + return NULL; + + + return nexthop; +} + +/* + * fpm__nexthop__get + * + * Read out information from a protobuf nexthop structure. + */ +#define fpm_nexthop_get fpm__nexthop__get +static inline int fpm__nexthop__get(const Fpm__Nexthop *nh, + struct nexthop *nexthop) +{ + struct in_addr ipv4; + struct in6_addr ipv6; + uint32_t ifindex; + char *ifname; + + if (!nh) + return 0; + + if (!qpb_if_identifier_get(nh->if_id, &ifindex, &ifname)) + return 0; + + if (nh->address) { + if (nh->address->v4) { + memset(&ipv4, 0, sizeof(ipv4)); + if (!qpb__ipv4_address__get(nh->address->v4, &ipv4)) + return 0; + + nexthop->vrf_id = VRF_DEFAULT; + nexthop->type = NEXTHOP_TYPE_IPV4; + nexthop->gate.ipv4 = ipv4; + if (ifindex) { + nexthop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + nexthop->ifindex = ifindex; + } + return 1; + } + + if (nh->address->v6) { + memset(&ipv6, 0, sizeof(ipv6)); + if (!qpb__ipv6_address__get(nh->address->v6, &ipv6)) + return 0; + nexthop->vrf_id = VRF_DEFAULT; + nexthop->type = NEXTHOP_TYPE_IPV6; + nexthop->gate.ipv6 = ipv6; + if (ifindex) { + nexthop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + nexthop->ifindex = ifindex; + } + return 1; + } + } + + return 0; +} + +/* + * fpm__srv6_sid_format__create + */ +#define fpm_srv6_sid_format_create fpm__srv6_sid_format__create +static inline Fpm__SRv6SIDFormat * +fpm__srv6_sid_format__create(qpb_allocator_t *allocator, + uint8_t locator_block_length, + uint8_t locator_node_length, + uint8_t function_length, uint8_t argument_length) +{ + Fpm__SRv6SIDFormat *sid_format; + + sid_format = QPB_ALLOC(allocator, typeof(*sid_format)); + if (!sid_format) + return NULL; + fpm__srv6_sidformat__init(sid_format); + + sid_format->locator_block_length = locator_block_length; + sid_format->locator_node_length = locator_node_length; + sid_format->function_length = function_length; + sid_format->argument_length = argument_length; + + return sid_format; +} + +/* + * fpm__srv6_local_sid_end_behavior__create + */ +#define fpm_srv6_local_sid_end_behavior_create \ + fpm__srv6_local_sid_end_behavior__create +static inline Fpm__SRv6LocalSID__End * +fpm__srv6_local_sid_end_behavior__create(qpb_allocator_t *allocator) +{ + Fpm__SRv6LocalSID__End *end; + + end = QPB_ALLOC(allocator, typeof(*end)); + if (!end) + return NULL; + + fpm__srv6_local_sid__end__init(end); + + return end; +} + +/* + * fpm__srv6_local_sid_end_x_behavior__create + */ +#define fpm_srv6_local_sid_end_x_behavior_create \ + fpm__srv6_local_sid_end_x_behavior__create +static inline Fpm__SRv6LocalSID__EndX * +fpm__srv6_local_sid_end_x_behavior__create(qpb_allocator_t *allocator, + struct nexthop *nexthop) +{ + Fpm__SRv6LocalSID__EndX *end_x; + + end_x = QPB_ALLOC(allocator, typeof(*end_x)); + if (!end_x) + return NULL; + + fpm__srv6_local_sid__end_x__init(end_x); + + end_x->nexthop = fpm_nexthop_create(allocator, nexthop); + + return end_x; +} + +/* + * fpm__srv6_local_sid_end_t_behavior__create + */ +#define fpm_srv6_local_sid_end_t_behavior_create \ + fpm__srv6_local_sid_end_t_behavior__create +static inline Fpm__SRv6LocalSID__EndT * +fpm__srv6_local_sid_end_t_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__EndT *end_t; + + end_t = QPB_ALLOC(allocator, typeof(*end_t)); + if (!end_t) + return NULL; + + fpm__srv6_local_sid__end_t__init(end_t); + + end_t->vrf_id = vrf_id; + + return end_t; +} + +/* + * fpm__srv6_local_sid_end_dx6_behavior__create + */ +#define fpm_srv6_local_sid_end_dx6_behavior_create \ + fpm__srv6_local_sid_end_dx6_behavior__create +static inline Fpm__SRv6LocalSID__EndDX6 * +fpm__srv6_local_sid_end_dx6_behavior__create(qpb_allocator_t *allocator, + struct nexthop *nexthop) +{ + Fpm__SRv6LocalSID__EndDX6 *end_dx6; + + end_dx6 = QPB_ALLOC(allocator, typeof(*end_dx6)); + if (!end_dx6) + return NULL; + + fpm__srv6_local_sid__end_dx6__init(end_dx6); + + end_dx6->nexthop = fpm_nexthop_create(allocator, nexthop); + + return end_dx6; +} + +/* + * fpm__srv6_local_sid_end_dx4_behavior__create + */ +#define fpm_srv6_local_sid_end_dx4_behavior_create \ + fpm__srv6_local_sid_end_dx4_behavior__create +static inline Fpm__SRv6LocalSID__EndDX4 * +fpm__srv6_local_sid_end_dx4_behavior__create(qpb_allocator_t *allocator, + struct nexthop *nexthop) +{ + Fpm__SRv6LocalSID__EndDX4 *end_dx4; + + end_dx4 = QPB_ALLOC(allocator, typeof(*end_dx4)); + if (!end_dx4) + return NULL; + + fpm__srv6_local_sid__end_dx4__init(end_dx4); + + end_dx4->nexthop = fpm_nexthop_create(allocator, nexthop); + + return end_dx4; +} + +/* + * fpm__srv6_local_sid_end_dt6_behavior__create + */ +#define fpm_srv6_local_sid_end_dt6_behavior_create \ + fpm__srv6_local_sid_end_dt6_behavior__create +static inline Fpm__SRv6LocalSID__EndDT6 * +fpm__srv6_local_sid_end_dt6_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__EndDT6 *end_dt6; + + end_dt6 = QPB_ALLOC(allocator, typeof(*end_dt6)); + if (!end_dt6) + return NULL; + + fpm__srv6_local_sid__end_dt6__init(end_dt6); + + end_dt6->vrf_id = vrf_id; + + return end_dt6; +} + +/* + * fpm__srv6_local_sid_end_dt4_behavior__create + */ +#define fpm_srv6_local_sid_end_dt4_behavior_create \ + fpm__srv6_local_sid_end_dt4_behavior__create +static inline Fpm__SRv6LocalSID__EndDT4 * +fpm__srv6_local_sid_end_dt4_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__EndDT4 *end_dt4; + + end_dt4 = QPB_ALLOC(allocator, typeof(*end_dt4)); + if (!end_dt4) + return NULL; + + fpm__srv6_local_sid__end_dt4__init(end_dt4); + + end_dt4->vrf_id = vrf_id; + + return end_dt4; +} + +/* + * fpm__srv6_local_sid_end_dt46_behavior__create + */ +#define fpm_srv6_local_sid_end_dt46_behavior_create \ + fpm__srv6_local_sid_end_dt46_behavior__create +static inline Fpm__SRv6LocalSID__EndDT46 * +fpm__srv6_local_sid_end_dt46_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__EndDT46 *end_dt46; + + end_dt46 = QPB_ALLOC(allocator, typeof(*end_dt46)); + if (!end_dt46) + return NULL; + + fpm__srv6_local_sid__end_dt46__init(end_dt46); + + end_dt46->vrf_id = vrf_id; + + return end_dt46; +} + +/* + * fpm__srv6_local_sid_un_behavior__create + */ +#define fpm_srv6_local_sid_un_behavior_create \ + fpm__srv6_local_sid_un_behavior__create +static inline Fpm__SRv6LocalSID__UN * +fpm__srv6_local_sid_un_behavior__create(qpb_allocator_t *allocator) +{ + Fpm__SRv6LocalSID__UN *un; + + un = QPB_ALLOC(allocator, typeof(*un)); + if (!un) + return NULL; + + fpm__srv6_local_sid__un__init(un); + + return un; +} + +/* + * fpm__srv6_local_sid_ua_behavior__create + */ +#define fpm_srv6_local_sid_ua_behavior_create \ + fpm__srv6_local_sid_ua_behavior__create +static inline Fpm__SRv6LocalSID__UA * +fpm__srv6_local_sid_ua_behavior__create(qpb_allocator_t *allocator, + struct nexthop *nexthop) +{ + Fpm__SRv6LocalSID__UA *ua; + + ua = QPB_ALLOC(allocator, typeof(*ua)); + if (!ua) + return NULL; + + fpm__srv6_local_sid__ua__init(ua); + + ua->nexthop = fpm_nexthop_create(allocator, nexthop); + + return ua; +} + +/* + * fpm__srv6_local_sid_udt6_behavior__create + */ +#define fpm_srv6_local_sid_udt6_behavior_create \ + fpm__srv6_local_sid_udt6_behavior__create +static inline Fpm__SRv6LocalSID__UDT6 * +fpm__srv6_local_sid_udt6_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__UDT6 *udt6; + + udt6 = QPB_ALLOC(allocator, typeof(*udt6)); + if (!udt6) + return NULL; + + fpm__srv6_local_sid__udt6__init(udt6); + + udt6->vrf_id = vrf_id; + + return udt6; +} + +/* + * fpm__srv6_local_sid_udt4_behavior__create + */ +#define fpm_srv6_local_sid_udt4_behavior_create \ + fpm__srv6_local_sid_udt4_behavior__create +static inline Fpm__SRv6LocalSID__UDT4 * +fpm__srv6_local_sid_udt4_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__UDT4 *udt4; + + udt4 = QPB_ALLOC(allocator, typeof(*udt4)); + if (!udt4) + return NULL; + + fpm__srv6_local_sid__udt4__init(udt4); + + udt4->vrf_id = vrf_id; + + return udt4; +} + +/* + * fpm__srv6_local_sid_udt46_behavior__create + */ +#define fpm_srv6_local_sid_udt46_behavior_create \ + fpm__srv6_local_sid_udt46_behavior__create +static inline Fpm__SRv6LocalSID__UDT46 * +fpm__srv6_local_sid_udt46_behavior__create(qpb_allocator_t *allocator, + vrf_id_t vrf_id) +{ + Fpm__SRv6LocalSID__UDT46 *udt46; + + udt46 = QPB_ALLOC(allocator, typeof(*udt46)); + if (!udt46) + return NULL; + + fpm__srv6_local_sid__udt46__init(udt46); + + udt46->vrf_id = vrf_id; + + return udt46; +} + +#endif diff --git a/fpm/subdir.am b/fpm/subdir.am new file mode 100644 index 0000000..b598813 --- /dev/null +++ b/fpm/subdir.am @@ -0,0 +1,28 @@ +if FPM +if HAVE_PROTOBUF +lib_LTLIBRARIES += fpm/libfrrfpm_pb.la +endif +endif + +fpm_libfrrfpm_pb_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +fpm_libfrrfpm_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) +fpm_libfrrfpm_pb_la_SOURCES = \ + fpm/fpm.h \ + fpm/fpm_pb.h \ + fpm/fpm_pb.c \ + # end + +if FPM +if HAVE_PROTOBUF +nodist_fpm_libfrrfpm_pb_la_SOURCES = \ + fpm/fpm.pb-c.c \ + # end +endif +endif + +CLEANFILES += \ + fpm/fpm.pb-c.c \ + fpm/fpm.pb-c.h \ + # end + +EXTRA_DIST += fpm/fpm.proto diff --git a/gdb/lib.txt b/gdb/lib.txt new file mode 100644 index 0000000..435ec7e --- /dev/null +++ b/gdb/lib.txt @@ -0,0 +1,364 @@ +# GDB macros for use with Quagga. +# +# Macros in this file are not daemon specific. E.g., OS or FRR library +# APIs. +# +# The macro file can be loaded with 'source '. They can then be +# called by the user. Macros that explore more complicated structs generally +# take pointer arguments. +# +# E.g.: +# +# (gdb) source ~paul/code/frr/gdb/lib.txt +# (gdb) break bgp_packet.c:613 +# Breakpoint 3 at 0x7fa883033a32: file bgp_packet.c, line 613. +# (gdb) cont +# ... +# (gdb) cont +# Breakpoint 3, bgp_write_packet (peer=0x7fa885199080) at bgp_packet.c:614 +# 614 if (CHECK_FLAG (adv->path->peer->cap,PEER_CAP_RESTART_RCV) +# (gdb) dump_prefix4 &adv->rn->p +# IPv4:10.1.1.0/24 +# (gdb) dump_prefix &adv->rn->p +# IPv4:10.1.1.0/24 +# + + +define def_ntohs + set $data = (char *)$arg0 + set $i = 0 + + set $_ = $data[$i++] << 8 + set $_ += $data[$i++] +end +document def_ntohs +Read a 2-byte short at the given pointed to area as big-endian and +return it in $_ + +Argument: Pointer to a 2-byte, big-endian short word. +Returns: Integer value of that word in $_ +end + +define def_ntohl + set $data = (char *)$arg0 + set $i = 0 + + set $_ = $data[$i++] << 24 + set $_ += $data[$i++] << 16 + set $_ += $data[$i++] << 8 + set $_ += $data[$i++] +end +document def_ntohl +Read a 4-byte integer at the given pointed to area as big-endian and +return it in $_ + +Argument: Pointer to a big-endian 4-byte word. +Returns: Integer value of that word in $_ +end + +# NB: This is in more complicated iterative form, rather than more +# conventional and simpler recursive form, because GDB has a recursion limit +# on macro calls (I think). +define walk_route_table_next + # callee saves + set $_top = $top + set $_node = $node + set $_prevl = $prevl + + set $top = (struct route_node *)$arg0 + set $node = (struct route_node *)$arg1 + set $prevl = $node + + # first try left + #echo try left\n + set $node = $prevl->link[0] + + # otherwise try right + if ($node == 0) + #echo left null, try right\n + set $node = $prevl->link[1] + end + + # otherwise go up, till we find the first right that + # we havn't been to yet + if ($node == 0) + set $node = $prevl + while ($node != $top) + #echo right null, try up and right\n + + set $prevl = $node + set $parent = $node->parent + set $node = $parent->link[1] + + if ($node != 0 && $node != $prevl) + #echo found node \n + loop_break + end + + #echo go up\n + set $node = $parent + end + end + + #printf "next node: 0x%x\n", $node + + set $_ = $node + + set $top = $_top + set $node = $_node + set $prevl = $_prevl +end +document walk_route_table_next +Return the next node to visit in the given route_table (or subset of) and +the given current node. + +Arguments: +1st: (struct route_node *) to the top of the route_table to walk +2nd: (struct route_node *) to the current node + +Returns: The (struct route_node *) for the next to visit in $_ +end + +define walk_route_table + set $_visited = $visited + set $_node = $node + set $top = $_top + + set $node = (struct route_node *)$arg0 + set $top = (struct route_node *)$arg0 + set $visited = 0 + + while ($node != 0) + printf "Node: 0x%x", $node + + if ($node->info != 0) + printf "\tinfo: 0x%x", $node->info + set $visited = $visited + 1 + end + + printf "\n" + + walk_route_table_next $top $node + set $node = $_ + + # we've gotten back to the top, finish + if ($node == $top) + set $node = 0 + end + end + printf "Visited: %u\n", $visited + + set $top = $_top + set $visited = $_visited + set $node = $_node +end + +document walk_route_table +Walk through a routing table (or subset thereof) and dump all the non-null +(struct route_node *)->info pointers. + +Argument: A lib/hread.h::(struct route_node *) pointing to the route_node +under which all data should be dumped +end + +define dump_timeval + set $tv = (struct timeval *)$arg0 + set $day = 3600*24 + + if $tv->tv_sec > $day + printf "%d days, ", $tv->tv_sec / $day + end + if $tv->tv_sec > 3600 + printf "%dh", $tv->tv_sec / 3600 + end + if ($tv->tv_sec % 3600) > 60 + printf "%dm", ($tv->tv_sec % 3600) / 60 + end + printf "%d", $tv->tv_sec % 3600 % 60 + if $tv->tv_usec != 0 + printf ".%06d", $tv->tv_usec + end + printf "s" +end +document dump_timeval +Human readable dump of a (struct timeval *) argument +end + +define dump_s_addr + set $addr = (char *)$arg0 + + printf "%d.%d.%d.%d", $addr[0], $addr[1], $addr[2], $addr[3] +end + +define dump_s6_addr + set $a6 = (char *)$arg0 + set $field = 0 + + while ($field < 16) + set $i1 = $field++ + set $i2 = $field++ + + printf "%x%x", $a6[$i1], $a6[$i2] + + if ($field > 2 && ($field % 4 == 0)) + printf ":" + end + end +end +document dump_s6_addr +Interpret the memory starting at given address as an IPv6 s6_addr and +print in human readable form. +end + +define dump_prefix4 + set $p = (struct prefix *) $arg0 + echo IPv4: + dump_s_addr &($p->u.prefix4) + printf "/%d\n", $p->prefixlen +end +document dump_prefix4 +Textual dump of a (struct prefix4 *) argument. +end + +define dump_prefix6 + set $p = (struct prefix *) $arg0 + echo IPv6: + dump_s6_addr &($p->u.prefix6) + printf "/%d\n", $p->prefixlen +end +document dump_prefix6 +Textual dump of a (struct prefix6 *) argument. +end + +define dump_prefix + set $p = $arg0 + + if ($p->family == 2) + dump_prefix4 $p + end + if ($p->family == 10) + dump_prefix6 $p + end +end +document dump_prefix +Human readable dump of a (struct prefix *) argument. +end + +define rn_next_down + set $node = $arg0 + while ($node != 0) + print/x $node + if ($node->link[0] != 0) + set $node = $node->link[0] + else + set $node = $node->link[1] + end + end +end + +document rn_next_down +Walk left-down a given route table, dumping locations of route_nodes + +Argument: A single (struct route_node *). +end + +define rn_next_up + set $top = (struct route_node *)$arg0 + set $node = (struct route_node *)$arg1 + + while ($node != $top) + echo walk up\n + + set $prevl = $node + set $parent = $node->parent + set $node = $parent->link[1] + + if ($node != 0 && $node != $prevl) + echo found a node\n + loop_break + end + + echo going up\n + set $node = $parent + end + output/x $node + echo \n +end + +document rn_next_up +Walk up-and-right from the given route_node to the next valid route_node +which is not the given "top" route_node + +Arguments: +1st: A (struct route_node *) to the top of the route table. +2nd: The (struct route_node *) to walk up from +end + +define mq_walk + set $mg = (struct memgroup *)$arg0 + + while ($mg) + printf "showing active allocations in memory group %s\n", $mg->name + set $mt = (struct memtype *)$mg->types + while ($mt) + printf "memstats: %s:%zu\n", $mt->name, $mt->n_alloc + set $mt = $mt->next + end + set $mg = $mg->next + end +end + +document mq_walk +Walk the memory data structures to show what is holding memory. + +Arguments: +1st: A (struct memgroup *) where to start the walk. If you are not + sure where to start pass it mg_first, which is a global DS for + all memory allocated in FRR +end + +define __darr_meta + set $_ = ((struct darr_metadata *)$arg0) - 1 +end +document __darr_meta +Store a pointer to the struct darr_metadata in $_ for the given dynamic array. + +Argument: a pointer to a darr dynamic array. +Returns: pointer to the struct darr_metadata in $_. +end + +define darr_meta + __darr_meta $arg0 + p *$_ +end +document darr_meta +Print the struct darr_metadata for the given dynamic array. Store the value +in $_ as well. + +Argument: a pointer to a darr dynamic array. +Returns: pointer to the struct darr_metadata in $_. +end + +define darr_len + __darr_meta $arg0 + set $_ = $_->len + p $_ +end +document darr_len +Print the length of the given dynamic array, and store in $_. + +Argument: a pointer to a darr dynamic array. +Returns: length of the array. +end + +define darr_cap + __darr_meta $arg0 + set $_ = $_->cap + p $_ +end +document darr_len +Print the capacity of the given dynamic array, and store in $_. + +Argument: a pointer to a darr dynamic array. +Returns: capacity of the array. +end diff --git a/gdb/ospf.txt b/gdb/ospf.txt new file mode 100644 index 0000000..984104b --- /dev/null +++ b/gdb/ospf.txt @@ -0,0 +1,137 @@ +# GDB macros for use with Quagga. +# +# Macros in this file are specific to ospfd/. Definitions here depend on the +# lib.txt macros file, which must also be loaed. +# +# The macro file can be loaded with 'source '. They can then be +# called by the user. Macros that explore more complicated structs generally +# take pointer arguments. + +define dump_ospf_lsa_flags + set $flags = $arg0 + + printf "%u: ", $flags + + if $flags & 0x1 + echo Self, + end + if $flags & 0x2 + echo Self-checked, + end + if $flags & 0x4 + echo Recvd, + end + if $flags & 0x8 + echo Apprvd, + end + if $flags & 0x10 + echo Discard, + end + if $flags & 0x20 + echo Local-Xlt, + end + if $flags & 0x40 + echo Premature-Aged, + end + if $flags & 0x40 + echo In-Maxage, + end + echo \n +end + +define dump_ospf_lsa_data + set $lsad = (struct lsa_header *)$arg0 + + echo ID / AdvRtr: \t\t + dump_s_addr &$lsad->id.s_addr + echo \ : \ + dump_s_addr &$lsad->adv_router.s_addr + echo \n + + def_ntohs &$lsad->ls_age + printf "Type: %2u Age: %4u,", $lsad->type, $_ + + def_ntohs &$lsad->length + printf " length: %2u", $_ + + def_ntohl &$lsad->ls_seqnum + printf " Seqnum: 0x%08x", $_ + + def_ntohs &$lsad->checksum + printf " csum: 0x%04x\n", $_ + + # return the age + def_ntohs &$lsad->ls_age +end + +define dump_ospf_lsa + set $lsa = (struct ospf_lsa *)$arg0 + + #print/x *$lsa + + dump_ospf_lsa_data $lsa->data + + set $relage = $_ + (relative_time.tv_sec - $lsa->tv_recv.tv_sec) + printf "Relative age: %4u\n", $relage + + dump_ospf_lsa_flags $lsa->flags + + echo tv_recv: \ + dump_timeval &$lsa->tv_recv + echo \ tv_orig: \ + dump_timeval &$lsa->tv_orig + echo \n + + printf "lock %2u", $lsa->lock + printf " stat %2d", $lsa->stat + printf " rtx count: %u", $lsa->retransmit_counter + printf " rfsh list: %d", $lsa->refresh_list + printf "\n\n" +end + +define walk_ospf_lsdb + set $node = (struct route_node *)$arg0 + set $top = (struct route_node *)$arg0 + set $visited = 0 + + while ($node != 0) + set $prevl = $node + + if ($node->info != 0) + dump_ospf_lsa $node->info + set $visited = $visited + 1 + end + + walk_route_table_next $top $node + set $node = $_ + + # we've gotten back to the top, finish + if ($node == $top) + set $node = 0 + end + end + printf "Visited: %u\n", $visited +end + +document walk_ospf_lsdb +Walk through an OSPF LSDB (or subset thereof) and dump all the LSAs +contained there-in. + +Argument: A (struct route_node *) pointing to the top of the +LSDB route-table which should be dumped. +end + +define ospf_backbone_lsdb_top + set $type = $arg0 + + set $ospf = ospf_master->ospf->head->data + + output/x ((struct ospf *)$ospf)->backbone->lsdb->type[$type]->db->top + echo \n +end +document ospf_backbone_lsdb_top +Dump location of the LSDB in the backbone area for the given LSA type + +Argument: Integer LSA type +end + diff --git a/grpc/Makefile b/grpc/Makefile new file mode 100644 index 0000000..8748286 --- /dev/null +++ b/grpc/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. grpc/libfrrgrpc_pb.la +%: ALWAYS + @$(MAKE) -s -C .. grpc/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/grpc/frr-northbound.proto b/grpc/frr-northbound.proto new file mode 100644 index 0000000..b3b1dc4 --- /dev/null +++ b/grpc/frr-northbound.proto @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: BSD-2-Clause +// +// Copyright 2019 FRRouting +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS +// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +syntax = "proto3"; + +package frr; + +// Service specification for the FRR northbound interface. +service Northbound { + // Retrieve the capabilities supported by the target. + rpc GetCapabilities(GetCapabilitiesRequest) returns (GetCapabilitiesResponse) {} + + // Retrieve configuration data, state data or both from the target. + rpc Get(GetRequest) returns (stream GetResponse) {} + + // Create a new candidate configuration and return a reference to it. The + // created candidate is a copy of the running configuration. + rpc CreateCandidate(CreateCandidateRequest) returns (CreateCandidateResponse) {} + + // Delete a candidate configuration. + rpc DeleteCandidate(DeleteCandidateRequest) returns (DeleteCandidateResponse) {} + + // Update a candidate configuration by rebasing the changes on top of the + // latest running configuration. Resolve conflicts automatically by giving + // preference to the changes done in the candidate configuration. + rpc UpdateCandidate(UpdateCandidateRequest) returns (UpdateCandidateResponse) {} + + // Edit a candidate configuration. All changes are discarded if any error + // happens. + rpc EditCandidate(EditCandidateRequest) returns (EditCandidateResponse) {} + + // Load configuration data into a candidate configuration. Both merge and + // replace semantics are supported. + rpc LoadToCandidate(LoadToCandidateRequest) returns (LoadToCandidateResponse) {} + + // Create a new configuration transaction using a two-phase commit protocol. + rpc Commit(CommitRequest) returns (CommitResponse) {} + + // List the metadata of all configuration transactions recorded in the + // transactions database. + rpc ListTransactions(ListTransactionsRequest) returns (stream ListTransactionsResponse) {} + + // Fetch a configuration (identified by its transaction ID) from the + // transactions database. + rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {} + + // Lock the running configuration, preventing other users from changing it. + rpc LockConfig(LockConfigRequest) returns (LockConfigResponse) {} + + // Unlock the running configuration. + rpc UnlockConfig(UnlockConfigRequest) returns (UnlockConfigResponse) {} + + // Execute a YANG RPC. + rpc Execute(ExecuteRequest) returns (ExecuteResponse) {} +} + +// ----------------------- Parameters and return types ------------------------- + +// +// RPC: GetCapabilities() +// +message GetCapabilitiesRequest { + // Empty. +} + +message GetCapabilitiesResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + + // FRR version. + string frr_version = 1; + + // Indicates whether FRR was compiled with support for configuration + // rollbacks or not (--enable-config-rollbacks). + bool rollback_support = 2; + + // Supported schema modules. + repeated ModuleData supported_modules = 3; + + // Supported encodings. + repeated Encoding supported_encodings = 4; +} + +// +// RPC: Get() +// +message GetRequest { + // Type of elements within the data tree. + enum DataType { + // All data elements. + ALL = 0; + + // Config elements. + CONFIG = 1; + + // State elements. + STATE = 2; + } + + // The type of data being requested. + DataType type = 1; + + // Encoding to be used. + Encoding encoding = 2; + + // Include implicit default nodes. + bool with_defaults = 3; + + // Paths requested by the client. + repeated string path = 4; +} + +message GetResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::INVALID_ARGUMENT: Invalid YANG data path. + + // Timestamp in nanoseconds since Epoch. + int64 timestamp = 1; + + // The requested data. + DataTree data = 2; +} + +// +// RPC: CreateCandidate() +// +message CreateCandidateRequest { + // Empty. +} + +message CreateCandidateResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::RESOURCE_EXHAUSTED: can't create candidate + // configuration. + + // Handle to the new created candidate configuration. + uint32 candidate_id = 1; +} + +// +// RPC: DeleteCandidate() +// +message DeleteCandidateRequest { + // Candidate configuration to delete. + uint32 candidate_id = 1; +} + +message DeleteCandidateResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. +} + +// +// RPC: UpdateCandidate() +// +message UpdateCandidateRequest { + // Candidate configuration to update. + uint32 candidate_id = 1; +} + +message UpdateCandidateResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. +} + +// +// RPC: EditCandidate() +// +message EditCandidateRequest { + // Candidate configuration that is going to be edited. + uint32 candidate_id = 1; + + // Data elements to be created or updated. + repeated PathValue update = 2; + + // Paths to be deleted from the data tree. + repeated PathValue delete = 3; +} + +message EditCandidateResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::NOT_FOUND: Candidate wasn't found. + // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while editing the + // candidate configuration. +} + +// +// RPC: LoadToCandidate() +// +message LoadToCandidateRequest { + enum LoadType { + // Merge the data tree into the candidate configuration. + MERGE = 0; + + // Replace the candidate configuration by the provided data tree. + REPLACE = 1; + } + + // Candidate configuration that is going to be edited. + uint32 candidate_id = 1; + + // Load operation to apply. + LoadType type = 2; + + // Configuration data. + DataTree config = 3; +} + +message LoadToCandidateResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::INVALID_ARGUMENT: An error occurred while performing + // the load operation. +} + +// +// RPC: Commit() +// +message CommitRequest { + enum Phase { + // Validate if the configuration changes are valid (phase 0). + VALIDATE = 0; + + // Prepare resources to apply the configuration changes (phase 1). + PREPARE = 1; + + // Release previously allocated resources (phase 2). + ABORT = 2; + + // Apply the configuration changes (phase 2). + APPLY = 3; + + // All of the above (VALIDATE + PREPARE + ABORT/APPLY). + // + // This option can't be used to implement network-wide transactions, + // since they require the manager entity to take into account the results + // of the preparation phase of multiple managed devices. + ALL = 4; + } + + // Candidate configuration that is going to be committed. + uint32 candidate_id = 1; + + // Transaction phase. + Phase phase = 2; + + // Assign a comment to this commit. + string comment = 3; +} + +message CommitResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::FAILED_PRECONDITION: misuse of the two-phase commit + // protocol. + // - grpc::StatusCode::INVALID_ARGUMENT: Validation error. + // - grpc::StatusCode::RESOURCE_EXHAUSTED: Failure to allocate resource. + + // ID of the created configuration transaction (when the phase is APPLY + // or ALL). + uint32 transaction_id = 1; + + // Human-readable error or warning message(s). + string error_message = 2; +} + +// +// RPC: ListTransactions() +// +message ListTransactionsRequest { + // Empty. +} + +message ListTransactionsResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + + // Transaction ID. + uint32 id = 1; + + // Client that committed the transaction. + string client = 2; + + // Date and time the transaction was committed. + string date = 3; + + // Comment assigned to the transaction. + string comment = 4; +} + +// +// RPC: GetTransaction() +// +message GetTransactionRequest { + // Transaction to retrieve. + uint32 transaction_id = 1; + + // Encoding to be used. + Encoding encoding = 2; + + // Include implicit default nodes. + bool with_defaults = 3; +} + +message GetTransactionResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::NOT_FOUND: Transaction wasn't found in the transactions + // database. + + DataTree config = 1; +} + +// +// RPC: LockConfig() +// +message LockConfigRequest { + // Empty. +} + +message LockConfigResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration is + // locked already. +} + +// +// RPC: UnlockConfig() +// +message UnlockConfigRequest { + // Empty. +} + +message UnlockConfigResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::FAILED_PRECONDITION: Running configuration isn't + // locked. +} + +// +// RPC: Execute() +// +message ExecuteRequest { + // Path of the YANG RPC or YANG Action. + string path = 1; + + // Input parameters. + repeated PathValue input = 2; +} + +message ExecuteResponse { + // Return values: + // - grpc::StatusCode::OK: Success. + + // Output parameters. + repeated PathValue output = 1; +} + +// -------------------------------- Definitions -------------------------------- + +// YANG module. +message ModuleData { + // Name of the YANG module; + string name = 1; + + // Organization publishing the module. + string organization = 2; + + // Latest revision of the module; + string revision = 3; +} + +// Supported encodings for YANG instance data. +enum Encoding { + JSON = 0; + XML = 1; +} + +// Path-value pair representing a data element. +message PathValue { + // YANG data path. + string path = 1; + + // Data value. + string value = 2; +} + +// YANG instance data. +message DataTree { + Encoding encoding = 1; + string data = 2; +} diff --git a/grpc/frrgrpc_pb.c b/grpc/frrgrpc_pb.c new file mode 100644 index 0000000..938d777 --- /dev/null +++ b/grpc/frrgrpc_pb.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: ISC +/* + * libfrrgrpc_pb library stub source + */ + +#include "config.h" +#include "xref.h" + +XREF_SETUP(); diff --git a/grpc/subdir.am b/grpc/subdir.am new file mode 100644 index 0000000..a464edc --- /dev/null +++ b/grpc/subdir.am @@ -0,0 +1,45 @@ +if GRPC +lib_LTLIBRARIES += grpc/libfrrgrpc_pb.la +endif + +grpc_libfrrgrpc_pb_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +grpc_libfrrgrpc_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(GRPC_CXXFLAGS) + +if GRPC +nodist_grpc_libfrrgrpc_pb_la_SOURCES = \ + grpc/frr-northbound.pb.cc \ + grpc/frr-northbound.grpc.pb.cc \ + # end + +grpc_libfrrgrpc_pb_la_SOURCES = \ + grpc/frrgrpc_pb.c \ + # end +endif + +CLEANFILES += \ + grpc/frr-northbound.pb.cc \ + grpc/frr-northbound.pb.h \ + grpc/frr-northbound.grpc.pb.cc \ + grpc/frr-northbound.grpc.pb.h \ + # end + +EXTRA_DIST += grpc/frr-northbound.proto + +AM_V_PROTOC = $(am__v_PROTOC_$(V)) +am__v_PROTOC_ = $(am__v_PROTOC_$(AM_DEFAULT_VERBOSITY)) +am__v_PROTOC_0 = @echo " PROTOC " $@; +am__v_PROTOC_1 = + +SUFFIXES += .pb.h .pb.cc .grpc.pb.cc + +grpc/frr-northbound.grpc.pb.h: grpc/frr-northbound.grpc.pb.cc + @test -f $@ || rm -f $< || true + @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) $< +grpc/frr-northbound.pb.h: grpc/frr-northbound.pb.cc + @test -f $@ || rm -f $< || true + @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) $< + +.proto.pb.cc: + $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --cpp_out=$(top_builddir) $^ +.proto.grpc.pb.cc: + $(AM_V_PROTOC)$(PROTOC) -I$(top_srcdir) --grpc_out=$(top_builddir) --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` $^ diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h new file mode 100644 index 0000000..72393a8 --- /dev/null +++ b/include/linux/compiler_types.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_COMPILER_TYPES_H +#define __LINUX_COMPILER_TYPES_H + +#ifndef __ASSEMBLY__ + +#ifdef __CHECKER__ +# define __user __attribute__((noderef, address_space(1))) +# define __kernel __attribute__((address_space(0))) +# define __safe __attribute__((safe)) +# define __force __attribute__((force)) +# define __nocast __attribute__((nocast)) +# define __iomem __attribute__((noderef, address_space(2))) +# define __must_hold(x) __attribute__((context(x,1,1))) +# define __acquires(x) __attribute__((context(x,0,1))) +# define __releases(x) __attribute__((context(x,1,0))) +# define __acquire(x) __context__(x,1) +# define __release(x) __context__(x,-1) +# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0) +# define __percpu __attribute__((noderef, address_space(3))) +# define __rcu __attribute__((noderef, address_space(4))) +# define __private __attribute__((noderef)) +extern void __chk_user_ptr(const volatile void __user *); +extern void __chk_io_ptr(const volatile void __iomem *); +# define ACCESS_PRIVATE(p, member) (*((typeof((p)->member) __force *) &(p)->member)) +#else /* __CHECKER__ */ +# ifdef STRUCTLEAK_PLUGIN +# define __user __attribute__((user)) +# else +# define __user +# endif +# define __kernel +# define __safe +# define __force +# define __nocast +# define __iomem +# define __chk_user_ptr(x) (void)0 +# define __chk_io_ptr(x) (void)0 +# define __builtin_warning(x, y...) (1) +# define __must_hold(x) +# define __acquires(x) +# define __releases(x) +# define __acquire(x) (void)0 +# define __release(x) (void)0 +# define __cond_lock(x,c) (c) +# define __percpu +# define __rcu +# define __private +# define ACCESS_PRIVATE(p, member) ((p)->member) +#endif /* __CHECKER__ */ + +/* Indirect macros required for expanded argument pasting, eg. __LINE__. */ +#define ___PASTE(a,b) a##b +#define __PASTE(a,b) ___PASTE(a,b) + +#ifdef __KERNEL__ + +/* Attributes */ +#include + +/* Compiler specific macros. */ +#ifdef __clang__ +#include +#elif defined(__INTEL_COMPILER) +#include +#elif defined(__GNUC__) +/* The above compilers also define __GNUC__, so order is important here. */ +#include +#else +#error "Unknown compiler" +#endif + +/* + * Some architectures need to provide custom definitions of macros provided + * by linux/compiler-*.h, and can do so using asm/compiler.h. We include that + * conditionally rather than using an asm-generic wrapper in order to avoid + * build failures if any C compilation, which will include this file via an + * -include argument in c_flags, occurs prior to the asm-generic wrappers being + * generated. + */ +#ifdef CONFIG_HAVE_ARCH_COMPILER_H +#include +#endif + +struct ftrace_branch_data { + const char *func; + const char *file; + unsigned line; + union { + struct { + unsigned long correct; + unsigned long incorrect; + }; + struct { + unsigned long miss; + unsigned long hit; + }; + unsigned long miss_hit[2]; + }; +}; + +struct ftrace_likely_data { + struct ftrace_branch_data data; + unsigned long constant; +}; + +#ifdef CONFIG_ENABLE_MUST_CHECK +#define __must_check __attribute__((__warn_unused_result__)) +#else +#define __must_check +#endif + +#if defined(CC_USING_HOTPATCH) +#define notrace __attribute__((hotpatch(0, 0))) +#elif defined(CC_USING_PATCHABLE_FUNCTION_ENTRY) +#define notrace __attribute__((patchable_function_entry(0, 0))) +#else +#define notrace __attribute__((__no_instrument_function__)) +#endif + +/* + * it doesn't make sense on ARM (currently the only user of __naked) + * to trace naked functions because then mcount is called without + * stack and frame pointer being set up and there is no chance to + * restore the lr register to the value before mcount was called. + */ +#define __naked __attribute__((__naked__)) notrace + +#define __compiler_offsetof(a, b) __builtin_offsetof(a, b) + +/* + * Force always-inline if the user requests it so via the .config. + * Prefer gnu_inline, so that extern inline functions do not emit an + * externally visible function. This makes extern inline behave as per gnu89 + * semantics rather than c99. This prevents multiple symbol definition errors + * of extern inline functions at link time. + * A lot of inline functions can cause havoc with function tracing. + * Do not use __always_inline here, since currently it expands to inline again + * (which would break users of __always_inline). + */ +#if !defined(CONFIG_OPTIMIZE_INLINING) +#define inline inline __attribute__((__always_inline__)) __gnu_inline \ + __inline_maybe_unused notrace +#else +#define inline inline __gnu_inline \ + __inline_maybe_unused notrace +#endif + +/* + * gcc provides both __inline__ and __inline as alternate spellings of + * the inline keyword, though the latter is undocumented. New kernel + * code should only use the inline spelling, but some existing code + * uses __inline__. Since we #define inline above, to ensure + * __inline__ has the same semantics, we need this #define. + * + * However, the spelling __inline is strictly reserved for referring + * to the bare keyword. + */ +#define __inline__ inline + +/* + * GCC does not warn about unused static inline functions for -Wunused-function. + * Suppress the warning in clang as well by using __maybe_unused, but enable it + * for W=1 build. This will allow clang to find unused functions. Remove the + * __inline_maybe_unused entirely after fixing most of -Wunused-function warnings. + */ +#ifdef KBUILD_EXTRA_WARN1 +#define __inline_maybe_unused +#else +#define __inline_maybe_unused __maybe_unused +#endif + +/* + * Rather then using noinline to prevent stack consumption, use + * noinline_for_stack instead. For documentation reasons. + */ +#define noinline_for_stack noinline + +#endif /* __KERNEL__ */ + +#endif /* __ASSEMBLY__ */ + +/* + * The below symbols may be defined for one or more, but not ALL, of the above + * compilers. We don't consider that to be an error, so set them to nothing. + * For example, some of them are for compiler specific plugins. + */ +#ifndef __latent_entropy +# define __latent_entropy +#endif + +#ifndef __randomize_layout +# define __randomize_layout __designated_init +#endif + +#ifndef __no_randomize_layout +# define __no_randomize_layout +#endif + +#ifndef randomized_struct_fields_start +# define randomized_struct_fields_start +# define randomized_struct_fields_end +#endif + +#ifndef asm_volatile_goto +#define asm_volatile_goto(x...) asm goto(x) +#endif + +#ifdef CONFIG_CC_HAS_ASM_INLINE +#define asm_inline asm __inline +#else +#define asm_inline asm +#endif + +#ifndef __no_fgcse +# define __no_fgcse +#endif + +/* Are two types/vars the same type (ignoring qualifiers)? */ +#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) + +/* Is this type a native word size -- useful for atomic operations */ +#define __native_word(t) \ + (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || \ + sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long)) + +/* Helpers for emitting diagnostics in pragmas. */ +#ifndef __diag +#define __diag(string) +#endif + +#ifndef __diag_GCC +#define __diag_GCC(version, severity, string) +#endif + +#define __diag_push() __diag(push) +#define __diag_pop() __diag(pop) + +#define __diag_ignore(compiler, version, option, comment) \ + __diag_ ## compiler(version, ignore, option) +#define __diag_warn(compiler, version, option, comment) \ + __diag_ ## compiler(version, warn, option) +#define __diag_error(compiler, version, option, comment) \ + __diag_ ## compiler(version, error, option) + +#endif /* __LINUX_COMPILER_TYPES_H */ diff --git a/include/linux/fib_rules.h b/include/linux/fib_rules.h new file mode 100644 index 0000000..232df14 --- /dev/null +++ b/include/linux/fib_rules.h @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_FIB_RULES_H +#define __LINUX_FIB_RULES_H + +#include +#include + +/* rule is permanent, and cannot be deleted */ +#define FIB_RULE_PERMANENT 0x00000001 +#define FIB_RULE_INVERT 0x00000002 +#define FIB_RULE_UNRESOLVED 0x00000004 +#define FIB_RULE_IIF_DETACHED 0x00000008 +#define FIB_RULE_DEV_DETACHED FIB_RULE_IIF_DETACHED +#define FIB_RULE_OIF_DETACHED 0x00000010 + +/* try to find source address in routing lookups */ +#define FIB_RULE_FIND_SADDR 0x00010000 + +struct fib_rule_hdr { + __u8 family; + __u8 dst_len; + __u8 src_len; + __u8 tos; + + __u8 table; + __u8 res1; /* reserved */ + __u8 res2; /* reserved */ + __u8 action; + + __u32 flags; +}; + +struct fib_rule_uid_range { + __u32 start; + __u32 end; +}; + +struct fib_rule_port_range { + __u16 start; + __u16 end; +}; + +enum { + FRA_UNSPEC, + FRA_DST, /* destination address */ + FRA_SRC, /* source address */ + FRA_IIFNAME, /* interface name */ +#define FRA_IFNAME FRA_IIFNAME + FRA_GOTO, /* target to jump to (FR_ACT_GOTO) */ + FRA_UNUSED2, + FRA_PRIORITY, /* priority/preference */ + FRA_UNUSED3, + FRA_UNUSED4, + FRA_UNUSED5, + FRA_FWMARK, /* mark */ + FRA_FLOW, /* flow/class id */ + FRA_TUN_ID, + FRA_SUPPRESS_IFGROUP, + FRA_SUPPRESS_PREFIXLEN, + FRA_TABLE, /* Extended table id */ + FRA_FWMASK, /* mask for netfilter mark */ + FRA_OIFNAME, + FRA_PAD, + FRA_L3MDEV, /* iif or oif is l3mdev goto its table */ + FRA_UID_RANGE, /* UID range */ + FRA_PROTOCOL, /* Originator of the rule */ + FRA_IP_PROTO, /* ip proto */ + FRA_SPORT_RANGE, /* sport */ + FRA_DPORT_RANGE, /* dport */ + __FRA_MAX +}; + +#define FRA_MAX (__FRA_MAX - 1) + +enum { + FR_ACT_UNSPEC, + FR_ACT_TO_TBL, /* Pass to fixed table */ + FR_ACT_GOTO, /* Jump to another rule */ + FR_ACT_NOP, /* No operation */ + FR_ACT_RES3, + FR_ACT_RES4, + FR_ACT_BLACKHOLE, /* Drop without notification */ + FR_ACT_UNREACHABLE, /* Drop with ENETUNREACH */ + FR_ACT_PROHIBIT, /* Drop with EACCES */ + __FR_ACT_MAX, +}; + +#define FR_ACT_MAX (__FR_ACT_MAX - 1) + +#endif diff --git a/include/linux/if.h b/include/linux/if.h new file mode 100644 index 0000000..a8ccf6d --- /dev/null +++ b/include/linux/if.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Global definitions for the INET interface module. + * + * Version: @(#)if.h 1.0.2 04/18/93 + * + * Authors: Original taken from Berkeley UNIX 4.3, (c) UCB 1982-1988 + * Ross Biro + * Fred N. van Kempen, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _LINUX_IF_H +#define _LINUX_IF_H + +#include /* for compatibility with glibc */ +#include /* for "__kernel_caddr_t" et al */ +#include /* for "struct sockaddr" et al */ +#include /* for "__user" et al */ + +#ifndef __KERNEL__ +#include /* for struct sockaddr. */ +#endif + +#if __UAPI_DEF_IF_IFNAMSIZ +#define IFNAMSIZ 16 +#endif /* __UAPI_DEF_IF_IFNAMSIZ */ +#define IFALIASZ 256 +#define ALTIFNAMSIZ 128 +#include + +/* For glibc compatibility. An empty enum does not compile. */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || \ + __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 +/** + * enum net_device_flags - &struct net_device flags + * + * These are the &struct net_device flags, they can be set by drivers, the + * kernel and some can be triggered by userspace. Userspace can query and + * set these flags using userspace utilities but there is also a sysfs + * entry available for all dev flags which can be queried and set. These flags + * are shared for all types of net_devices. The sysfs entries are available + * via /sys/class/net//flags. Flags which can be toggled through sysfs + * are annotated below, note that only a few flags can be toggled and some + * other flags are always preserved from the original net_device flags + * even if you try to set them via sysfs. Flags which are always preserved + * are kept under the flag grouping @IFF_VOLATILE. Flags which are volatile + * are annotated below as such. + * + * You should have a pretty good reason to be extending these flags. + * + * @IFF_UP: interface is up. Can be toggled through sysfs. + * @IFF_BROADCAST: broadcast address valid. Volatile. + * @IFF_DEBUG: turn on debugging. Can be toggled through sysfs. + * @IFF_LOOPBACK: is a loopback net. Volatile. + * @IFF_POINTOPOINT: interface is has p-p link. Volatile. + * @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs. + * Volatile. + * @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile. + * @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile. + * @IFF_PROMISC: receive all packets. Can be toggled through sysfs. + * @IFF_ALLMULTI: receive all multicast packets. Can be toggled through + * sysfs. + * @IFF_MASTER: master of a load balancer. Volatile. + * @IFF_SLAVE: slave of a load balancer. Volatile. + * @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs. + * @IFF_PORTSEL: can set media type. Can be toggled through sysfs. + * @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs. + * @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled + * through sysfs. + * @IFF_LOWER_UP: driver signals L1 up. Volatile. + * @IFF_DORMANT: driver signals dormant. Volatile. + * @IFF_ECHO: echo sent packets. Volatile. + */ +enum net_device_flags { +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS + IFF_UP = 1<<0, /* sysfs */ + IFF_BROADCAST = 1<<1, /* volatile */ + IFF_DEBUG = 1<<2, /* sysfs */ + IFF_LOOPBACK = 1<<3, /* volatile */ + IFF_POINTOPOINT = 1<<4, /* volatile */ + IFF_NOTRAILERS = 1<<5, /* sysfs */ + IFF_RUNNING = 1<<6, /* volatile */ + IFF_NOARP = 1<<7, /* sysfs */ + IFF_PROMISC = 1<<8, /* sysfs */ + IFF_ALLMULTI = 1<<9, /* sysfs */ + IFF_MASTER = 1<<10, /* volatile */ + IFF_SLAVE = 1<<11, /* volatile */ + IFF_MULTICAST = 1<<12, /* sysfs */ + IFF_PORTSEL = 1<<13, /* sysfs */ + IFF_AUTOMEDIA = 1<<14, /* sysfs */ + IFF_DYNAMIC = 1<<15, /* sysfs */ +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO + IFF_LOWER_UP = 1<<16, /* volatile */ + IFF_DORMANT = 1<<17, /* volatile */ + IFF_ECHO = 1<<18, /* volatile */ +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ +}; +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS +#define IFF_UP IFF_UP +#define IFF_BROADCAST IFF_BROADCAST +#define IFF_DEBUG IFF_DEBUG +#define IFF_LOOPBACK IFF_LOOPBACK +#define IFF_POINTOPOINT IFF_POINTOPOINT +#define IFF_NOTRAILERS IFF_NOTRAILERS +#define IFF_RUNNING IFF_RUNNING +#define IFF_NOARP IFF_NOARP +#define IFF_PROMISC IFF_PROMISC +#define IFF_ALLMULTI IFF_ALLMULTI +#define IFF_MASTER IFF_MASTER +#define IFF_SLAVE IFF_SLAVE +#define IFF_MULTICAST IFF_MULTICAST +#define IFF_PORTSEL IFF_PORTSEL +#define IFF_AUTOMEDIA IFF_AUTOMEDIA +#define IFF_DYNAMIC IFF_DYNAMIC +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */ + +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define IFF_LOWER_UP IFF_LOWER_UP +#define IFF_DORMANT IFF_DORMANT +#define IFF_ECHO IFF_ECHO +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ + +#define IFF_VOLATILE (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\ + IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT) + +#define IF_GET_IFACE 0x0001 /* for querying only */ +#define IF_GET_PROTO 0x0002 + +/* For definitions see hdlc.h */ +#define IF_IFACE_V35 0x1000 /* V.35 serial interface */ +#define IF_IFACE_V24 0x1001 /* V.24 serial interface */ +#define IF_IFACE_X21 0x1002 /* X.21 serial interface */ +#define IF_IFACE_T1 0x1003 /* T1 telco serial interface */ +#define IF_IFACE_E1 0x1004 /* E1 telco serial interface */ +#define IF_IFACE_SYNC_SERIAL 0x1005 /* can't be set by software */ +#define IF_IFACE_X21D 0x1006 /* X.21 Dual Clocking (FarSite) */ + +/* For definitions see hdlc.h */ +#define IF_PROTO_HDLC 0x2000 /* raw HDLC protocol */ +#define IF_PROTO_PPP 0x2001 /* PPP protocol */ +#define IF_PROTO_CISCO 0x2002 /* Cisco HDLC protocol */ +#define IF_PROTO_FR 0x2003 /* Frame Relay protocol */ +#define IF_PROTO_FR_ADD_PVC 0x2004 /* Create FR PVC */ +#define IF_PROTO_FR_DEL_PVC 0x2005 /* Delete FR PVC */ +#define IF_PROTO_X25 0x2006 /* X.25 */ +#define IF_PROTO_HDLC_ETH 0x2007 /* raw HDLC, Ethernet emulation */ +#define IF_PROTO_FR_ADD_ETH_PVC 0x2008 /* Create FR Ethernet-bridged PVC */ +#define IF_PROTO_FR_DEL_ETH_PVC 0x2009 /* Delete FR Ethernet-bridged PVC */ +#define IF_PROTO_FR_PVC 0x200A /* for reading PVC status */ +#define IF_PROTO_FR_ETH_PVC 0x200B +#define IF_PROTO_RAW 0x200C /* RAW Socket */ + +/* RFC 2863 operational status */ +enum { + IF_OPER_UNKNOWN, + IF_OPER_NOTPRESENT, + IF_OPER_DOWN, + IF_OPER_LOWERLAYERDOWN, + IF_OPER_TESTING, + IF_OPER_DORMANT, + IF_OPER_UP, +}; + +/* link modes */ +enum { + IF_LINK_MODE_DEFAULT, + IF_LINK_MODE_DORMANT, /* limit upward transition to dormant */ +}; + +/* + * Device mapping structure. I'd just gone off and designed a + * beautiful scheme using only loadable modules with arguments + * for driver options and along come the PCMCIA people 8) + * + * Ah well. The get() side of this is good for WDSETUP, and it'll + * be handy for debugging things. The set side is fine for now and + * being very small might be worth keeping for clean configuration. + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFMAP +struct ifmap { + unsigned long mem_start; + unsigned long mem_end; + unsigned short base_addr; + unsigned char irq; + unsigned char dma; + unsigned char port; + /* 3 bytes spare */ +}; +#endif /* __UAPI_DEF_IF_IFMAP */ + +struct if_settings { + unsigned int type; /* Type of physical device or protocol */ + unsigned int size; /* Size of the data allocated by the caller */ + union { + /* {atm/eth/dsl}_settings anyone ? */ + raw_hdlc_proto __user *raw_hdlc; + cisco_proto __user *cisco; + fr_proto __user *fr; + fr_proto_pvc __user *fr_pvc; + fr_proto_pvc_info __user *fr_pvc_info; + + /* interface settings */ + sync_serial_settings __user *sync; + te1_settings __user *te1; + } ifs_ifsu; +}; + +/* + * Interface request structure used for socket + * ioctl's. All interface ioctl's must have parameter + * definitions which begin with ifr_name. The + * remainder may be interface specific. + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFREQ +struct ifreq { +#define IFHWADDRLEN 6 + union + { + char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + } ifr_ifrn; + + union { + struct sockaddr ifru_addr; + struct sockaddr ifru_dstaddr; + struct sockaddr ifru_broadaddr; + struct sockaddr ifru_netmask; + struct sockaddr ifru_hwaddr; + short ifru_flags; + int ifru_ivalue; + int ifru_mtu; + struct ifmap ifru_map; + char ifru_slave[IFNAMSIZ]; /* Just fits the size */ + char ifru_newname[IFNAMSIZ]; + void __user * ifru_data; + struct if_settings ifru_settings; + } ifr_ifru; +}; +#endif /* __UAPI_DEF_IF_IFREQ */ + +#define ifr_name ifr_ifrn.ifrn_name /* interface name */ +#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ +#define ifr_addr ifr_ifru.ifru_addr /* address */ +#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ +#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ +#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ +#define ifr_flags ifr_ifru.ifru_flags /* flags */ +#define ifr_metric ifr_ifru.ifru_ivalue /* metric */ +#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ +#define ifr_map ifr_ifru.ifru_map /* device map */ +#define ifr_slave ifr_ifru.ifru_slave /* slave device */ +#define ifr_data ifr_ifru.ifru_data /* for use by interface */ +#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */ +#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */ +#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */ +#define ifr_newname ifr_ifru.ifru_newname /* New name */ +#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/ + +/* + * Structure used in SIOCGIFCONF request. + * Used to retrieve interface configuration + * for machine (useful for programs which + * must know all networks accessible). + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFCONF +struct ifconf { + int ifc_len; /* size of buffer */ + union { + char __user *ifcu_buf; + struct ifreq __user *ifcu_req; + } ifc_ifcu; +}; +#endif /* __UAPI_DEF_IF_IFCONF */ + +#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */ +#define ifc_req ifc_ifcu.ifcu_req /* array of structures */ + +#endif /* _LINUX_IF_H */ diff --git a/include/linux/if_addr.h b/include/linux/if_addr.h new file mode 100644 index 0000000..dfcf3ce --- /dev/null +++ b/include/linux/if_addr.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_IF_ADDR_H +#define __LINUX_IF_ADDR_H + +#include +#include + +struct ifaddrmsg { + __u8 ifa_family; + __u8 ifa_prefixlen; /* The prefix length */ + __u8 ifa_flags; /* Flags */ + __u8 ifa_scope; /* Address scope */ + __u32 ifa_index; /* Link index */ +}; + +/* + * Important comment: + * IFA_ADDRESS is prefix address, rather than local interface address. + * It makes no difference for normally configured broadcast interfaces, + * but for point-to-point IFA_ADDRESS is DESTINATION address, + * local address is supplied in IFA_LOCAL attribute. + * + * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags. + * If present, the value from struct ifaddrmsg will be ignored. + */ +enum { + IFA_UNSPEC, + IFA_ADDRESS, + IFA_LOCAL, + IFA_LABEL, + IFA_BROADCAST, + IFA_ANYCAST, + IFA_CACHEINFO, + IFA_MULTICAST, + IFA_FLAGS, + IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ + IFA_TARGET_NETNSID, + __IFA_MAX, +}; + +#define IFA_MAX (__IFA_MAX - 1) + +/* ifa_flags */ +#define IFA_F_SECONDARY 0x01 +#define IFA_F_TEMPORARY IFA_F_SECONDARY + +#define IFA_F_NODAD 0x02 +#define IFA_F_OPTIMISTIC 0x04 +#define IFA_F_DADFAILED 0x08 +#define IFA_F_HOMEADDRESS 0x10 +#define IFA_F_DEPRECATED 0x20 +#define IFA_F_TENTATIVE 0x40 +#define IFA_F_PERMANENT 0x80 +#define IFA_F_MANAGETEMPADDR 0x100 +#define IFA_F_NOPREFIXROUTE 0x200 +#define IFA_F_MCAUTOJOIN 0x400 +#define IFA_F_STABLE_PRIVACY 0x800 + +struct ifa_cacheinfo { + __u32 ifa_prefered; + __u32 ifa_valid; + __u32 cstamp; /* created timestamp, hundredths of seconds */ + __u32 tstamp; /* updated timestamp, hundredths of seconds */ +}; + +/* backwards compatibility for userspace */ +#ifndef __KERNEL__ +#define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) +#endif + +#endif diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h new file mode 100644 index 0000000..02198fc --- /dev/null +++ b/include/linux/if_bridge.h @@ -0,0 +1,744 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Linux ethernet bridge + * + * Authors: + * Lennert Buytenhek + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _UAPI_LINUX_IF_BRIDGE_H +#define _UAPI_LINUX_IF_BRIDGE_H + +#include +#include +#include + +#define SYSFS_BRIDGE_ATTR "bridge" +#define SYSFS_BRIDGE_FDB "brforward" +#define SYSFS_BRIDGE_PORT_SUBDIR "brif" +#define SYSFS_BRIDGE_PORT_ATTR "brport" +#define SYSFS_BRIDGE_PORT_LINK "bridge" + +#define BRCTL_VERSION 1 + +#define BRCTL_GET_VERSION 0 +#define BRCTL_GET_BRIDGES 1 +#define BRCTL_ADD_BRIDGE 2 +#define BRCTL_DEL_BRIDGE 3 +#define BRCTL_ADD_IF 4 +#define BRCTL_DEL_IF 5 +#define BRCTL_GET_BRIDGE_INFO 6 +#define BRCTL_GET_PORT_LIST 7 +#define BRCTL_SET_BRIDGE_FORWARD_DELAY 8 +#define BRCTL_SET_BRIDGE_HELLO_TIME 9 +#define BRCTL_SET_BRIDGE_MAX_AGE 10 +#define BRCTL_SET_AGEING_TIME 11 +#define BRCTL_SET_GC_INTERVAL 12 +#define BRCTL_GET_PORT_INFO 13 +#define BRCTL_SET_BRIDGE_STP_STATE 14 +#define BRCTL_SET_BRIDGE_PRIORITY 15 +#define BRCTL_SET_PORT_PRIORITY 16 +#define BRCTL_SET_PATH_COST 17 +#define BRCTL_GET_FDB_ENTRIES 18 + +#define BR_STATE_DISABLED 0 +#define BR_STATE_LISTENING 1 +#define BR_STATE_LEARNING 2 +#define BR_STATE_FORWARDING 3 +#define BR_STATE_BLOCKING 4 + +struct __bridge_info { + __u64 designated_root; + __u64 bridge_id; + __u32 root_path_cost; + __u32 max_age; + __u32 hello_time; + __u32 forward_delay; + __u32 bridge_max_age; + __u32 bridge_hello_time; + __u32 bridge_forward_delay; + __u8 topology_change; + __u8 topology_change_detected; + __u8 root_port; + __u8 stp_enabled; + __u32 ageing_time; + __u32 gc_interval; + __u32 hello_timer_value; + __u32 tcn_timer_value; + __u32 topology_change_timer_value; + __u32 gc_timer_value; +}; + +struct __port_info { + __u64 designated_root; + __u64 designated_bridge; + __u16 port_id; + __u16 designated_port; + __u32 path_cost; + __u32 designated_cost; + __u8 state; + __u8 top_change_ack; + __u8 config_pending; + __u8 unused0; + __u32 message_age_timer_value; + __u32 forward_delay_timer_value; + __u32 hold_timer_value; +}; + +struct __fdb_entry { + __u8 mac_addr[ETH_ALEN]; + __u8 port_no; + __u8 is_local; + __u32 ageing_timer_value; + __u8 port_hi; + __u8 pad0; + __u16 vlan; +}; + +/* Bridge Flags */ +#define BRIDGE_FLAGS_MASTER 1 /* Bridge command to/from master */ +#define BRIDGE_FLAGS_SELF 2 /* Bridge command to/from lowerdev */ + +#define BRIDGE_MODE_VEB 0 /* Default loopback mode */ +#define BRIDGE_MODE_VEPA 1 /* 802.1Qbg defined VEPA mode */ +#define BRIDGE_MODE_UNDEF 0xFFFF /* mode undefined */ + +/* Bridge management nested attributes + * [IFLA_AF_SPEC] = { + * [IFLA_BRIDGE_FLAGS] + * [IFLA_BRIDGE_MODE] + * [IFLA_BRIDGE_VLAN_INFO] + * } + */ +enum { + IFLA_BRIDGE_FLAGS, + IFLA_BRIDGE_MODE, + IFLA_BRIDGE_VLAN_INFO, + IFLA_BRIDGE_VLAN_TUNNEL_INFO, + IFLA_BRIDGE_MRP, + IFLA_BRIDGE_CFM, + __IFLA_BRIDGE_MAX, +}; +#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) + +#define BRIDGE_VLAN_INFO_MASTER (1<<0) /* Operate on Bridge device as well */ +#define BRIDGE_VLAN_INFO_PVID (1<<1) /* VLAN is PVID, ingress untagged */ +#define BRIDGE_VLAN_INFO_UNTAGGED (1<<2) /* VLAN egresses untagged */ +#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */ +#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */ +#define BRIDGE_VLAN_INFO_BRENTRY (1<<5) /* Global bridge VLAN entry */ +#define BRIDGE_VLAN_INFO_ONLY_OPTS (1<<6) /* Skip create/delete/flags */ + +struct bridge_vlan_info { + __u16 flags; + __u16 vid; +}; + +enum { + IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC, + IFLA_BRIDGE_VLAN_TUNNEL_ID, + IFLA_BRIDGE_VLAN_TUNNEL_VID, + IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, + __IFLA_BRIDGE_VLAN_TUNNEL_MAX, +}; + +#define IFLA_BRIDGE_VLAN_TUNNEL_MAX (__IFLA_BRIDGE_VLAN_TUNNEL_MAX - 1) + +struct bridge_vlan_xstats { + __u64 rx_bytes; + __u64 rx_packets; + __u64 tx_bytes; + __u64 tx_packets; + __u16 vid; + __u16 flags; + __u32 pad2; +}; + +enum { + IFLA_BRIDGE_MRP_UNSPEC, + IFLA_BRIDGE_MRP_INSTANCE, + IFLA_BRIDGE_MRP_PORT_STATE, + IFLA_BRIDGE_MRP_PORT_ROLE, + IFLA_BRIDGE_MRP_RING_STATE, + IFLA_BRIDGE_MRP_RING_ROLE, + IFLA_BRIDGE_MRP_START_TEST, + IFLA_BRIDGE_MRP_INFO, + IFLA_BRIDGE_MRP_IN_ROLE, + IFLA_BRIDGE_MRP_IN_STATE, + IFLA_BRIDGE_MRP_START_IN_TEST, + __IFLA_BRIDGE_MRP_MAX, +}; + +#define IFLA_BRIDGE_MRP_MAX (__IFLA_BRIDGE_MRP_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_INSTANCE_UNSPEC, + IFLA_BRIDGE_MRP_INSTANCE_RING_ID, + IFLA_BRIDGE_MRP_INSTANCE_P_IFINDEX, + IFLA_BRIDGE_MRP_INSTANCE_S_IFINDEX, + IFLA_BRIDGE_MRP_INSTANCE_PRIO, + __IFLA_BRIDGE_MRP_INSTANCE_MAX, +}; + +#define IFLA_BRIDGE_MRP_INSTANCE_MAX (__IFLA_BRIDGE_MRP_INSTANCE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_PORT_STATE_UNSPEC, + IFLA_BRIDGE_MRP_PORT_STATE_STATE, + __IFLA_BRIDGE_MRP_PORT_STATE_MAX, +}; + +#define IFLA_BRIDGE_MRP_PORT_STATE_MAX (__IFLA_BRIDGE_MRP_PORT_STATE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_PORT_ROLE_UNSPEC, + IFLA_BRIDGE_MRP_PORT_ROLE_ROLE, + __IFLA_BRIDGE_MRP_PORT_ROLE_MAX, +}; + +#define IFLA_BRIDGE_MRP_PORT_ROLE_MAX (__IFLA_BRIDGE_MRP_PORT_ROLE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_RING_STATE_UNSPEC, + IFLA_BRIDGE_MRP_RING_STATE_RING_ID, + IFLA_BRIDGE_MRP_RING_STATE_STATE, + __IFLA_BRIDGE_MRP_RING_STATE_MAX, +}; + +#define IFLA_BRIDGE_MRP_RING_STATE_MAX (__IFLA_BRIDGE_MRP_RING_STATE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_RING_ROLE_UNSPEC, + IFLA_BRIDGE_MRP_RING_ROLE_RING_ID, + IFLA_BRIDGE_MRP_RING_ROLE_ROLE, + __IFLA_BRIDGE_MRP_RING_ROLE_MAX, +}; + +#define IFLA_BRIDGE_MRP_RING_ROLE_MAX (__IFLA_BRIDGE_MRP_RING_ROLE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_START_TEST_UNSPEC, + IFLA_BRIDGE_MRP_START_TEST_RING_ID, + IFLA_BRIDGE_MRP_START_TEST_INTERVAL, + IFLA_BRIDGE_MRP_START_TEST_MAX_MISS, + IFLA_BRIDGE_MRP_START_TEST_PERIOD, + IFLA_BRIDGE_MRP_START_TEST_MONITOR, + __IFLA_BRIDGE_MRP_START_TEST_MAX, +}; + +#define IFLA_BRIDGE_MRP_START_TEST_MAX (__IFLA_BRIDGE_MRP_START_TEST_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_INFO_UNSPEC, + IFLA_BRIDGE_MRP_INFO_RING_ID, + IFLA_BRIDGE_MRP_INFO_P_IFINDEX, + IFLA_BRIDGE_MRP_INFO_S_IFINDEX, + IFLA_BRIDGE_MRP_INFO_PRIO, + IFLA_BRIDGE_MRP_INFO_RING_STATE, + IFLA_BRIDGE_MRP_INFO_RING_ROLE, + IFLA_BRIDGE_MRP_INFO_TEST_INTERVAL, + IFLA_BRIDGE_MRP_INFO_TEST_MAX_MISS, + IFLA_BRIDGE_MRP_INFO_TEST_MONITOR, + IFLA_BRIDGE_MRP_INFO_I_IFINDEX, + IFLA_BRIDGE_MRP_INFO_IN_STATE, + IFLA_BRIDGE_MRP_INFO_IN_ROLE, + IFLA_BRIDGE_MRP_INFO_IN_TEST_INTERVAL, + IFLA_BRIDGE_MRP_INFO_IN_TEST_MAX_MISS, + __IFLA_BRIDGE_MRP_INFO_MAX, +}; + +#define IFLA_BRIDGE_MRP_INFO_MAX (__IFLA_BRIDGE_MRP_INFO_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_IN_STATE_UNSPEC, + IFLA_BRIDGE_MRP_IN_STATE_IN_ID, + IFLA_BRIDGE_MRP_IN_STATE_STATE, + __IFLA_BRIDGE_MRP_IN_STATE_MAX, +}; + +#define IFLA_BRIDGE_MRP_IN_STATE_MAX (__IFLA_BRIDGE_MRP_IN_STATE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_IN_ROLE_UNSPEC, + IFLA_BRIDGE_MRP_IN_ROLE_RING_ID, + IFLA_BRIDGE_MRP_IN_ROLE_IN_ID, + IFLA_BRIDGE_MRP_IN_ROLE_ROLE, + IFLA_BRIDGE_MRP_IN_ROLE_I_IFINDEX, + __IFLA_BRIDGE_MRP_IN_ROLE_MAX, +}; + +#define IFLA_BRIDGE_MRP_IN_ROLE_MAX (__IFLA_BRIDGE_MRP_IN_ROLE_MAX - 1) + +enum { + IFLA_BRIDGE_MRP_START_IN_TEST_UNSPEC, + IFLA_BRIDGE_MRP_START_IN_TEST_IN_ID, + IFLA_BRIDGE_MRP_START_IN_TEST_INTERVAL, + IFLA_BRIDGE_MRP_START_IN_TEST_MAX_MISS, + IFLA_BRIDGE_MRP_START_IN_TEST_PERIOD, + __IFLA_BRIDGE_MRP_START_IN_TEST_MAX, +}; + +#define IFLA_BRIDGE_MRP_START_IN_TEST_MAX (__IFLA_BRIDGE_MRP_START_IN_TEST_MAX - 1) + +struct br_mrp_instance { + __u32 ring_id; + __u32 p_ifindex; + __u32 s_ifindex; + __u16 prio; +}; + +struct br_mrp_ring_state { + __u32 ring_id; + __u32 ring_state; +}; + +struct br_mrp_ring_role { + __u32 ring_id; + __u32 ring_role; +}; + +struct br_mrp_start_test { + __u32 ring_id; + __u32 interval; + __u32 max_miss; + __u32 period; + __u32 monitor; +}; + +struct br_mrp_in_state { + __u32 in_state; + __u16 in_id; +}; + +struct br_mrp_in_role { + __u32 ring_id; + __u32 in_role; + __u32 i_ifindex; + __u16 in_id; +}; + +struct br_mrp_start_in_test { + __u32 interval; + __u32 max_miss; + __u32 period; + __u16 in_id; +}; + +enum { + IFLA_BRIDGE_CFM_UNSPEC, + IFLA_BRIDGE_CFM_MEP_CREATE, + IFLA_BRIDGE_CFM_MEP_DELETE, + IFLA_BRIDGE_CFM_MEP_CONFIG, + IFLA_BRIDGE_CFM_CC_CONFIG, + IFLA_BRIDGE_CFM_CC_PEER_MEP_ADD, + IFLA_BRIDGE_CFM_CC_PEER_MEP_REMOVE, + IFLA_BRIDGE_CFM_CC_RDI, + IFLA_BRIDGE_CFM_CC_CCM_TX, + IFLA_BRIDGE_CFM_MEP_CREATE_INFO, + IFLA_BRIDGE_CFM_MEP_CONFIG_INFO, + IFLA_BRIDGE_CFM_CC_CONFIG_INFO, + IFLA_BRIDGE_CFM_CC_RDI_INFO, + IFLA_BRIDGE_CFM_CC_CCM_TX_INFO, + IFLA_BRIDGE_CFM_CC_PEER_MEP_INFO, + IFLA_BRIDGE_CFM_MEP_STATUS_INFO, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_INFO, + __IFLA_BRIDGE_CFM_MAX, +}; + +#define IFLA_BRIDGE_CFM_MAX (__IFLA_BRIDGE_CFM_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_MEP_CREATE_UNSPEC, + IFLA_BRIDGE_CFM_MEP_CREATE_INSTANCE, + IFLA_BRIDGE_CFM_MEP_CREATE_DOMAIN, + IFLA_BRIDGE_CFM_MEP_CREATE_DIRECTION, + IFLA_BRIDGE_CFM_MEP_CREATE_IFINDEX, + __IFLA_BRIDGE_CFM_MEP_CREATE_MAX, +}; + +#define IFLA_BRIDGE_CFM_MEP_CREATE_MAX (__IFLA_BRIDGE_CFM_MEP_CREATE_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_MEP_DELETE_UNSPEC, + IFLA_BRIDGE_CFM_MEP_DELETE_INSTANCE, + __IFLA_BRIDGE_CFM_MEP_DELETE_MAX, +}; + +#define IFLA_BRIDGE_CFM_MEP_DELETE_MAX (__IFLA_BRIDGE_CFM_MEP_DELETE_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_MEP_CONFIG_UNSPEC, + IFLA_BRIDGE_CFM_MEP_CONFIG_INSTANCE, + IFLA_BRIDGE_CFM_MEP_CONFIG_UNICAST_MAC, + IFLA_BRIDGE_CFM_MEP_CONFIG_MDLEVEL, + IFLA_BRIDGE_CFM_MEP_CONFIG_MEPID, + __IFLA_BRIDGE_CFM_MEP_CONFIG_MAX, +}; + +#define IFLA_BRIDGE_CFM_MEP_CONFIG_MAX (__IFLA_BRIDGE_CFM_MEP_CONFIG_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_CC_CONFIG_UNSPEC, + IFLA_BRIDGE_CFM_CC_CONFIG_INSTANCE, + IFLA_BRIDGE_CFM_CC_CONFIG_ENABLE, + IFLA_BRIDGE_CFM_CC_CONFIG_EXP_INTERVAL, + IFLA_BRIDGE_CFM_CC_CONFIG_EXP_MAID, + __IFLA_BRIDGE_CFM_CC_CONFIG_MAX, +}; + +#define IFLA_BRIDGE_CFM_CC_CONFIG_MAX (__IFLA_BRIDGE_CFM_CC_CONFIG_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_CC_PEER_MEP_UNSPEC, + IFLA_BRIDGE_CFM_CC_PEER_MEP_INSTANCE, + IFLA_BRIDGE_CFM_CC_PEER_MEPID, + __IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX, +}; + +#define IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX (__IFLA_BRIDGE_CFM_CC_PEER_MEP_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_CC_RDI_UNSPEC, + IFLA_BRIDGE_CFM_CC_RDI_INSTANCE, + IFLA_BRIDGE_CFM_CC_RDI_RDI, + __IFLA_BRIDGE_CFM_CC_RDI_MAX, +}; + +#define IFLA_BRIDGE_CFM_CC_RDI_MAX (__IFLA_BRIDGE_CFM_CC_RDI_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_CC_CCM_TX_UNSPEC, + IFLA_BRIDGE_CFM_CC_CCM_TX_INSTANCE, + IFLA_BRIDGE_CFM_CC_CCM_TX_DMAC, + IFLA_BRIDGE_CFM_CC_CCM_TX_SEQ_NO_UPDATE, + IFLA_BRIDGE_CFM_CC_CCM_TX_PERIOD, + IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV, + IFLA_BRIDGE_CFM_CC_CCM_TX_IF_TLV_VALUE, + IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV, + IFLA_BRIDGE_CFM_CC_CCM_TX_PORT_TLV_VALUE, + __IFLA_BRIDGE_CFM_CC_CCM_TX_MAX, +}; + +#define IFLA_BRIDGE_CFM_CC_CCM_TX_MAX (__IFLA_BRIDGE_CFM_CC_CCM_TX_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_MEP_STATUS_UNSPEC, + IFLA_BRIDGE_CFM_MEP_STATUS_INSTANCE, + IFLA_BRIDGE_CFM_MEP_STATUS_OPCODE_UNEXP_SEEN, + IFLA_BRIDGE_CFM_MEP_STATUS_VERSION_UNEXP_SEEN, + IFLA_BRIDGE_CFM_MEP_STATUS_RX_LEVEL_LOW_SEEN, + __IFLA_BRIDGE_CFM_MEP_STATUS_MAX, +}; + +#define IFLA_BRIDGE_CFM_MEP_STATUS_MAX (__IFLA_BRIDGE_CFM_MEP_STATUS_MAX - 1) + +enum { + IFLA_BRIDGE_CFM_CC_PEER_STATUS_UNSPEC, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_INSTANCE, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_PEER_MEPID, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_CCM_DEFECT, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_RDI, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_PORT_TLV_VALUE, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_IF_TLV_VALUE, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEEN, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_TLV_SEEN, + IFLA_BRIDGE_CFM_CC_PEER_STATUS_SEQ_UNEXP_SEEN, + __IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX, +}; + +#define IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX (__IFLA_BRIDGE_CFM_CC_PEER_STATUS_MAX - 1) + +struct bridge_stp_xstats { + __u64 transition_blk; + __u64 transition_fwd; + __u64 rx_bpdu; + __u64 tx_bpdu; + __u64 rx_tcn; + __u64 tx_tcn; +}; + +#ifndef BRVLAN_RTA +#define BRVLAN_RTA(r) \ + ((struct rtattr *)(((char *)(r)) \ + + NLMSG_ALIGN(sizeof(struct br_vlan_msg)))) +#endif +/* Bridge vlan RTM header */ +struct br_vlan_msg { + __u8 family; + __u8 reserved1; + __u16 reserved2; + __u32 ifindex; +}; + +enum { + BRIDGE_VLANDB_DUMP_UNSPEC, + BRIDGE_VLANDB_DUMP_FLAGS, + __BRIDGE_VLANDB_DUMP_MAX, +}; +#define BRIDGE_VLANDB_DUMP_MAX (__BRIDGE_VLANDB_DUMP_MAX - 1) + +/* flags used in BRIDGE_VLANDB_DUMP_FLAGS attribute to affect dumps */ +#define BRIDGE_VLANDB_DUMPF_STATS (1 << 0) /* Include stats in the dump */ + +/* Bridge vlan RTM attributes + * [BRIDGE_VLANDB_ENTRY] = { + * [BRIDGE_VLANDB_ENTRY_INFO] + * ... + * } + */ +enum { + BRIDGE_VLANDB_UNSPEC, + BRIDGE_VLANDB_ENTRY, + __BRIDGE_VLANDB_MAX, +}; +#define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1) + +enum { + BRIDGE_VLANDB_ENTRY_UNSPEC, + BRIDGE_VLANDB_ENTRY_INFO, + BRIDGE_VLANDB_ENTRY_RANGE, + BRIDGE_VLANDB_ENTRY_STATE, + BRIDGE_VLANDB_ENTRY_TUNNEL_INFO, + BRIDGE_VLANDB_ENTRY_STATS, + __BRIDGE_VLANDB_ENTRY_MAX, +}; +#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1) + +/* [BRIDGE_VLANDB_ENTRY] = { + * [BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = { + * [BRIDGE_VLANDB_TINFO_ID] + * ... + * } + * } + */ +enum { + BRIDGE_VLANDB_TINFO_UNSPEC, + BRIDGE_VLANDB_TINFO_ID, + BRIDGE_VLANDB_TINFO_CMD, + __BRIDGE_VLANDB_TINFO_MAX, +}; +#define BRIDGE_VLANDB_TINFO_MAX (__BRIDGE_VLANDB_TINFO_MAX - 1) + +/* [BRIDGE_VLANDB_ENTRY] = { + * [BRIDGE_VLANDB_ENTRY_STATS] = { + * [BRIDGE_VLANDB_STATS_RX_BYTES] + * ... + * } + * ... + * } + */ +enum { + BRIDGE_VLANDB_STATS_UNSPEC, + BRIDGE_VLANDB_STATS_RX_BYTES, + BRIDGE_VLANDB_STATS_RX_PACKETS, + BRIDGE_VLANDB_STATS_TX_BYTES, + BRIDGE_VLANDB_STATS_TX_PACKETS, + BRIDGE_VLANDB_STATS_PAD, + __BRIDGE_VLANDB_STATS_MAX, +}; +#define BRIDGE_VLANDB_STATS_MAX (__BRIDGE_VLANDB_STATS_MAX - 1) + +/* Bridge multicast database attributes + * [MDBA_MDB] = { + * [MDBA_MDB_ENTRY] = { + * [MDBA_MDB_ENTRY_INFO] { + * struct br_mdb_entry + * [MDBA_MDB_EATTR attributes] + * } + * } + * } + * [MDBA_ROUTER] = { + * [MDBA_ROUTER_PORT] = { + * u32 ifindex + * [MDBA_ROUTER_PATTR attributes] + * } + * } + */ +enum { + MDBA_UNSPEC, + MDBA_MDB, + MDBA_ROUTER, + __MDBA_MAX, +}; +#define MDBA_MAX (__MDBA_MAX - 1) + +enum { + MDBA_MDB_UNSPEC, + MDBA_MDB_ENTRY, + __MDBA_MDB_MAX, +}; +#define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1) + +enum { + MDBA_MDB_ENTRY_UNSPEC, + MDBA_MDB_ENTRY_INFO, + __MDBA_MDB_ENTRY_MAX, +}; +#define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1) + +/* per mdb entry additional attributes */ +enum { + MDBA_MDB_EATTR_UNSPEC, + MDBA_MDB_EATTR_TIMER, + MDBA_MDB_EATTR_SRC_LIST, + MDBA_MDB_EATTR_GROUP_MODE, + MDBA_MDB_EATTR_SOURCE, + MDBA_MDB_EATTR_RTPROT, + __MDBA_MDB_EATTR_MAX +}; +#define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1) + +/* per mdb entry source */ +enum { + MDBA_MDB_SRCLIST_UNSPEC, + MDBA_MDB_SRCLIST_ENTRY, + __MDBA_MDB_SRCLIST_MAX +}; +#define MDBA_MDB_SRCLIST_MAX (__MDBA_MDB_SRCLIST_MAX - 1) + +/* per mdb entry per source attributes + * these are embedded in MDBA_MDB_SRCLIST_ENTRY + */ +enum { + MDBA_MDB_SRCATTR_UNSPEC, + MDBA_MDB_SRCATTR_ADDRESS, + MDBA_MDB_SRCATTR_TIMER, + __MDBA_MDB_SRCATTR_MAX +}; +#define MDBA_MDB_SRCATTR_MAX (__MDBA_MDB_SRCATTR_MAX - 1) + +/* multicast router types */ +enum { + MDB_RTR_TYPE_DISABLED, + MDB_RTR_TYPE_TEMP_QUERY, + MDB_RTR_TYPE_PERM, + MDB_RTR_TYPE_TEMP +}; + +enum { + MDBA_ROUTER_UNSPEC, + MDBA_ROUTER_PORT, + __MDBA_ROUTER_MAX, +}; +#define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1) + +/* router port attributes */ +enum { + MDBA_ROUTER_PATTR_UNSPEC, + MDBA_ROUTER_PATTR_TIMER, + MDBA_ROUTER_PATTR_TYPE, + __MDBA_ROUTER_PATTR_MAX +}; +#define MDBA_ROUTER_PATTR_MAX (__MDBA_ROUTER_PATTR_MAX - 1) + +struct br_port_msg { + __u8 family; + __u32 ifindex; +}; + +struct br_mdb_entry { + __u32 ifindex; +#define MDB_TEMPORARY 0 +#define MDB_PERMANENT 1 + __u8 state; +#define MDB_FLAGS_OFFLOAD (1 << 0) +#define MDB_FLAGS_FAST_LEAVE (1 << 1) +#define MDB_FLAGS_STAR_EXCL (1 << 2) +#define MDB_FLAGS_BLOCKED (1 << 3) + __u8 flags; + __u16 vid; + struct { + union { + __be32 ip4; + struct in6_addr ip6; + unsigned char mac_addr[ETH_ALEN]; + } u; + __be16 proto; + } addr; +}; + +enum { + MDBA_SET_ENTRY_UNSPEC, + MDBA_SET_ENTRY, + MDBA_SET_ENTRY_ATTRS, + __MDBA_SET_ENTRY_MAX, +}; +#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1) + +/* [MDBA_SET_ENTRY_ATTRS] = { + * [MDBE_ATTR_xxx] + * ... + * } + */ +enum { + MDBE_ATTR_UNSPEC, + MDBE_ATTR_SOURCE, + __MDBE_ATTR_MAX, +}; +#define MDBE_ATTR_MAX (__MDBE_ATTR_MAX - 1) + +/* Embedded inside LINK_XSTATS_TYPE_BRIDGE */ +enum { + BRIDGE_XSTATS_UNSPEC, + BRIDGE_XSTATS_VLAN, + BRIDGE_XSTATS_MCAST, + BRIDGE_XSTATS_PAD, + BRIDGE_XSTATS_STP, + __BRIDGE_XSTATS_MAX +}; +#define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) + +enum { + BR_MCAST_DIR_RX, + BR_MCAST_DIR_TX, + BR_MCAST_DIR_SIZE +}; + +/* IGMP/MLD statistics */ +struct br_mcast_stats { + __u64 igmp_v1queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_v2queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_v3queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_leaves[BR_MCAST_DIR_SIZE]; + __u64 igmp_v1reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_v2reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_v3reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_parse_errors; + + __u64 mld_v1queries[BR_MCAST_DIR_SIZE]; + __u64 mld_v2queries[BR_MCAST_DIR_SIZE]; + __u64 mld_leaves[BR_MCAST_DIR_SIZE]; + __u64 mld_v1reports[BR_MCAST_DIR_SIZE]; + __u64 mld_v2reports[BR_MCAST_DIR_SIZE]; + __u64 mld_parse_errors; + + __u64 mcast_bytes[BR_MCAST_DIR_SIZE]; + __u64 mcast_packets[BR_MCAST_DIR_SIZE]; +}; + +/* bridge boolean options + * BR_BOOLOPT_NO_LL_LEARN - disable learning from link-local packets + * + * IMPORTANT: if adding a new option do not forget to handle + * it in br_boolopt_toggle/get and bridge sysfs + */ +enum br_boolopt_id { + BR_BOOLOPT_NO_LL_LEARN, + BR_BOOLOPT_MAX +}; + +/* struct br_boolopt_multi - change multiple bridge boolean options + * + * @optval: new option values (bit per option) + * @optmask: options to change (bit per option) + */ +struct br_boolopt_multi { + __u32 optval; + __u32 optmask; +}; +#endif /* _UAPI_LINUX_IF_BRIDGE_H */ diff --git a/include/linux/if_link.h b/include/linux/if_link.h new file mode 100644 index 0000000..5389230 --- /dev/null +++ b/include/linux/if_link.h @@ -0,0 +1,1055 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_IF_LINK_H +#define _UAPI_LINUX_IF_LINK_H + +#include +#include + +/* This struct should be in sync with struct rtnl_link_stats64 */ +struct rtnl_link_stats { + __u32 rx_packets; /* total packets received */ + __u32 tx_packets; /* total packets transmitted */ + __u32 rx_bytes; /* total bytes received */ + __u32 tx_bytes; /* total bytes transmitted */ + __u32 rx_errors; /* bad packets received */ + __u32 tx_errors; /* packet transmit problems */ + __u32 rx_dropped; /* no space in linux buffers */ + __u32 tx_dropped; /* no space available in linux */ + __u32 multicast; /* multicast packets received */ + __u32 collisions; + + /* detailed rx_errors: */ + __u32 rx_length_errors; + __u32 rx_over_errors; /* receiver ring buff overflow */ + __u32 rx_crc_errors; /* recved pkt with crc error */ + __u32 rx_frame_errors; /* recv'd frame alignment error */ + __u32 rx_fifo_errors; /* recv'r fifo overrun */ + __u32 rx_missed_errors; /* receiver missed packet */ + + /* detailed tx_errors */ + __u32 tx_aborted_errors; + __u32 tx_carrier_errors; + __u32 tx_fifo_errors; + __u32 tx_heartbeat_errors; + __u32 tx_window_errors; + + /* for cslip etc */ + __u32 rx_compressed; + __u32 tx_compressed; + + __u32 rx_nohandler; /* dropped, no handler found */ +}; + +/* The main device statistics structure */ +struct rtnl_link_stats64 { + __u64 rx_packets; /* total packets received */ + __u64 tx_packets; /* total packets transmitted */ + __u64 rx_bytes; /* total bytes received */ + __u64 tx_bytes; /* total bytes transmitted */ + __u64 rx_errors; /* bad packets received */ + __u64 tx_errors; /* packet transmit problems */ + __u64 rx_dropped; /* no space in linux buffers */ + __u64 tx_dropped; /* no space available in linux */ + __u64 multicast; /* multicast packets received */ + __u64 collisions; + + /* detailed rx_errors: */ + __u64 rx_length_errors; + __u64 rx_over_errors; /* receiver ring buff overflow */ + __u64 rx_crc_errors; /* recved pkt with crc error */ + __u64 rx_frame_errors; /* recv'd frame alignment error */ + __u64 rx_fifo_errors; /* recv'r fifo overrun */ + __u64 rx_missed_errors; /* receiver missed packet */ + + /* detailed tx_errors */ + __u64 tx_aborted_errors; + __u64 tx_carrier_errors; + __u64 tx_fifo_errors; + __u64 tx_heartbeat_errors; + __u64 tx_window_errors; + + /* for cslip etc */ + __u64 rx_compressed; + __u64 tx_compressed; + + __u64 rx_nohandler; /* dropped, no handler found */ +}; + +/* The struct should be in sync with struct ifmap */ +struct rtnl_link_ifmap { + __u64 mem_start; + __u64 mem_end; + __u64 base_addr; + __u16 irq; + __u8 dma; + __u8 port; +}; + +/* + * IFLA_AF_SPEC + * Contains nested attributes for address family specific attributes. + * Each address family may create a attribute with the address family + * number as type and create its own attribute structure in it. + * + * Example: + * [IFLA_AF_SPEC] = { + * [AF_INET] = { + * [IFLA_INET_CONF] = ..., + * }, + * [AF_INET6] = { + * [IFLA_INET6_FLAGS] = ..., + * [IFLA_INET6_CONF] = ..., + * } + * } + */ + +enum { + IFLA_UNSPEC, + IFLA_ADDRESS, + IFLA_BROADCAST, + IFLA_IFNAME, + IFLA_MTU, + IFLA_LINK, + IFLA_QDISC, + IFLA_STATS, + IFLA_COST, +#define IFLA_COST IFLA_COST + IFLA_PRIORITY, +#define IFLA_PRIORITY IFLA_PRIORITY + IFLA_MASTER, +#define IFLA_MASTER IFLA_MASTER + IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ +#define IFLA_WIRELESS IFLA_WIRELESS + IFLA_PROTINFO, /* Protocol specific information for a link */ +#define IFLA_PROTINFO IFLA_PROTINFO + IFLA_TXQLEN, +#define IFLA_TXQLEN IFLA_TXQLEN + IFLA_MAP, +#define IFLA_MAP IFLA_MAP + IFLA_WEIGHT, +#define IFLA_WEIGHT IFLA_WEIGHT + IFLA_OPERSTATE, + IFLA_LINKMODE, + IFLA_LINKINFO, +#define IFLA_LINKINFO IFLA_LINKINFO + IFLA_NET_NS_PID, + IFLA_IFALIAS, + IFLA_NUM_VF, /* Number of VFs if device is SR-IOV PF */ + IFLA_VFINFO_LIST, + IFLA_STATS64, + IFLA_VF_PORTS, + IFLA_PORT_SELF, + IFLA_AF_SPEC, + IFLA_GROUP, /* Group the device belongs to */ + IFLA_NET_NS_FD, + IFLA_EXT_MASK, /* Extended info mask, VFs, etc */ + IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */ +#define IFLA_PROMISCUITY IFLA_PROMISCUITY + IFLA_NUM_TX_QUEUES, + IFLA_NUM_RX_QUEUES, + IFLA_CARRIER, + IFLA_PHYS_PORT_ID, + IFLA_CARRIER_CHANGES, + IFLA_PHYS_SWITCH_ID, + IFLA_LINK_NETNSID, + IFLA_PHYS_PORT_NAME, + IFLA_PROTO_DOWN, + IFLA_GSO_MAX_SEGS, + IFLA_GSO_MAX_SIZE, + IFLA_PAD, + IFLA_XDP, + IFLA_EVENT, + IFLA_NEW_NETNSID, + IFLA_IF_NETNSID, + IFLA_TARGET_NETNSID = IFLA_IF_NETNSID, /* new alias */ + IFLA_CARRIER_UP_COUNT, + IFLA_CARRIER_DOWN_COUNT, + IFLA_NEW_IFINDEX, + IFLA_MIN_MTU, + IFLA_MAX_MTU, + IFLA_PROP_LIST, + IFLA_ALT_IFNAME, /* Alternative ifname */ + IFLA_PERM_ADDRESS, + IFLA_PROTO_DOWN_REASON, + __IFLA_MAX +}; + + +#define IFLA_MAX (__IFLA_MAX - 1) + +enum { + IFLA_PROTO_DOWN_REASON_UNSPEC, + IFLA_PROTO_DOWN_REASON_MASK, /* u32, mask for reason bits */ + IFLA_PROTO_DOWN_REASON_VALUE, /* u32, reason bit value */ + + __IFLA_PROTO_DOWN_REASON_CNT, + IFLA_PROTO_DOWN_REASON_MAX = __IFLA_PROTO_DOWN_REASON_CNT - 1 +}; + +/* backwards compatibility for userspace */ +#ifndef __KERNEL__ +#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +#endif + +enum { + IFLA_INET_UNSPEC, + IFLA_INET_CONF, + __IFLA_INET_MAX, +}; + +#define IFLA_INET_MAX (__IFLA_INET_MAX - 1) + +/* ifi_flags. + + IFF_* flags. + + The only change is: + IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are + more not changeable by user. They describe link media + characteristics and set by device driver. + + Comments: + - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid + - If neither of these three flags are set; + the interface is NBMA. + + - IFF_MULTICAST does not mean anything special: + multicasts can be used on all not-NBMA links. + IFF_MULTICAST means that this media uses special encapsulation + for multicast frames. Apparently, all IFF_POINTOPOINT and + IFF_BROADCAST devices are able to use multicasts too. + */ + +/* IFLA_LINK. + For usual devices it is equal ifi_index. + If it is a "virtual interface" (f.e. tunnel), ifi_link + can point to real physical interface (f.e. for bandwidth calculations), + or maybe 0, what means, that real media is unknown (usual + for IPIP tunnels, when route to endpoint is allowed to change) + */ + +/* Subtype attributes for IFLA_PROTINFO */ +enum { + IFLA_INET6_UNSPEC, + IFLA_INET6_FLAGS, /* link flags */ + IFLA_INET6_CONF, /* sysctl parameters */ + IFLA_INET6_STATS, /* statistics */ + IFLA_INET6_MCAST, /* MC things. What of them? */ + IFLA_INET6_CACHEINFO, /* time values and max reasm size */ + IFLA_INET6_ICMP6STATS, /* statistics (icmpv6) */ + IFLA_INET6_TOKEN, /* device token */ + IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */ + __IFLA_INET6_MAX +}; + +#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) + +enum in6_addr_gen_mode { + IN6_ADDR_GEN_MODE_EUI64, + IN6_ADDR_GEN_MODE_NONE, + IN6_ADDR_GEN_MODE_STABLE_PRIVACY, + IN6_ADDR_GEN_MODE_RANDOM, +}; + +/* Bridge section */ + +enum { + IFLA_BR_UNSPEC, + IFLA_BR_FORWARD_DELAY, + IFLA_BR_HELLO_TIME, + IFLA_BR_MAX_AGE, + IFLA_BR_AGEING_TIME, + IFLA_BR_STP_STATE, + IFLA_BR_PRIORITY, + IFLA_BR_VLAN_FILTERING, + IFLA_BR_VLAN_PROTOCOL, + IFLA_BR_GROUP_FWD_MASK, + IFLA_BR_ROOT_ID, + IFLA_BR_BRIDGE_ID, + IFLA_BR_ROOT_PORT, + IFLA_BR_ROOT_PATH_COST, + IFLA_BR_TOPOLOGY_CHANGE, + IFLA_BR_TOPOLOGY_CHANGE_DETECTED, + IFLA_BR_HELLO_TIMER, + IFLA_BR_TCN_TIMER, + IFLA_BR_TOPOLOGY_CHANGE_TIMER, + IFLA_BR_GC_TIMER, + IFLA_BR_GROUP_ADDR, + IFLA_BR_FDB_FLUSH, + IFLA_BR_MCAST_ROUTER, + IFLA_BR_MCAST_SNOOPING, + IFLA_BR_MCAST_QUERY_USE_IFADDR, + IFLA_BR_MCAST_QUERIER, + IFLA_BR_MCAST_HASH_ELASTICITY, + IFLA_BR_MCAST_HASH_MAX, + IFLA_BR_MCAST_LAST_MEMBER_CNT, + IFLA_BR_MCAST_STARTUP_QUERY_CNT, + IFLA_BR_MCAST_LAST_MEMBER_INTVL, + IFLA_BR_MCAST_MEMBERSHIP_INTVL, + IFLA_BR_MCAST_QUERIER_INTVL, + IFLA_BR_MCAST_QUERY_INTVL, + IFLA_BR_MCAST_QUERY_RESPONSE_INTVL, + IFLA_BR_MCAST_STARTUP_QUERY_INTVL, + IFLA_BR_NF_CALL_IPTABLES, + IFLA_BR_NF_CALL_IP6TABLES, + IFLA_BR_NF_CALL_ARPTABLES, + IFLA_BR_VLAN_DEFAULT_PVID, + IFLA_BR_PAD, + IFLA_BR_VLAN_STATS_ENABLED, + IFLA_BR_MCAST_STATS_ENABLED, + IFLA_BR_MCAST_IGMP_VERSION, + IFLA_BR_MCAST_MLD_VERSION, + IFLA_BR_VLAN_STATS_PER_PORT, + __IFLA_BR_MAX, +}; + +#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) + +struct ifla_bridge_id { + __u8 prio[2]; + __u8 addr[6]; /* ETH_ALEN */ +}; + +enum { + BRIDGE_MODE_UNSPEC, + BRIDGE_MODE_HAIRPIN, +}; + +enum { + IFLA_BRPORT_UNSPEC, + IFLA_BRPORT_STATE, /* Spanning tree state */ + IFLA_BRPORT_PRIORITY, /* " priority */ + IFLA_BRPORT_COST, /* " cost */ + IFLA_BRPORT_MODE, /* mode (hairpin) */ + IFLA_BRPORT_GUARD, /* bpdu guard */ + IFLA_BRPORT_PROTECT, /* root port protection */ + IFLA_BRPORT_FAST_LEAVE, /* multicast fast leave */ + IFLA_BRPORT_LEARNING, /* mac learning */ + IFLA_BRPORT_UNICAST_FLOOD, /* flood unicast traffic */ + IFLA_BRPORT_PROXYARP, /* proxy ARP */ + IFLA_BRPORT_LEARNING_SYNC, /* mac learning sync from device */ + IFLA_BRPORT_PROXYARP_WIFI, /* proxy ARP for Wi-Fi */ + IFLA_BRPORT_ROOT_ID, /* designated root */ + IFLA_BRPORT_BRIDGE_ID, /* designated bridge */ + IFLA_BRPORT_DESIGNATED_PORT, + IFLA_BRPORT_DESIGNATED_COST, + IFLA_BRPORT_ID, + IFLA_BRPORT_NO, + IFLA_BRPORT_TOPOLOGY_CHANGE_ACK, + IFLA_BRPORT_CONFIG_PENDING, + IFLA_BRPORT_MESSAGE_AGE_TIMER, + IFLA_BRPORT_FORWARD_DELAY_TIMER, + IFLA_BRPORT_HOLD_TIMER, + IFLA_BRPORT_FLUSH, + IFLA_BRPORT_MULTICAST_ROUTER, + IFLA_BRPORT_PAD, + IFLA_BRPORT_MCAST_FLOOD, + IFLA_BRPORT_MCAST_TO_UCAST, + IFLA_BRPORT_VLAN_TUNNEL, + IFLA_BRPORT_BCAST_FLOOD, + IFLA_BRPORT_GROUP_FWD_MASK, + IFLA_BRPORT_NEIGH_SUPPRESS, + IFLA_BRPORT_ISOLATED, + IFLA_BRPORT_BACKUP_PORT, + IFLA_BRPORT_PEER_LINK = 60, /* MLAG peer link */ + IFLA_BRPORT_DUAL_LINK, /* MLAG Dual Connected link */ + IFLA_BRPORT_GROUP_FWD_MASKHI, + IFLA_BRPORT_DUAL_LINK_READY, + __IFLA_BRPORT_MAX +}; +#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) + +struct ifla_cacheinfo { + __u32 max_reasm_len; + __u32 tstamp; /* ipv6InterfaceTable updated timestamp */ + __u32 reachable_time; + __u32 retrans_time; +}; + +enum { + IFLA_INFO_UNSPEC, + IFLA_INFO_KIND, + IFLA_INFO_DATA, + IFLA_INFO_XSTATS, + IFLA_INFO_SLAVE_KIND, + IFLA_INFO_SLAVE_DATA, + __IFLA_INFO_MAX, +}; + +#define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) + +/* VLAN section */ + +enum { + IFLA_VLAN_UNSPEC, + IFLA_VLAN_ID, + IFLA_VLAN_FLAGS, + IFLA_VLAN_EGRESS_QOS, + IFLA_VLAN_INGRESS_QOS, + IFLA_VLAN_PROTOCOL, + __IFLA_VLAN_MAX, +}; + +#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) + +struct ifla_vlan_flags { + __u32 flags; + __u32 mask; +}; + +enum { + IFLA_VLAN_QOS_UNSPEC, + IFLA_VLAN_QOS_MAPPING, + __IFLA_VLAN_QOS_MAX +}; + +#define IFLA_VLAN_QOS_MAX (__IFLA_VLAN_QOS_MAX - 1) + +struct ifla_vlan_qos_mapping { + __u32 from; + __u32 to; +}; + +/* MACVLAN section */ +enum { + IFLA_MACVLAN_UNSPEC, + IFLA_MACVLAN_MODE, + IFLA_MACVLAN_FLAGS, + IFLA_MACVLAN_MACADDR_MODE, + IFLA_MACVLAN_MACADDR, + IFLA_MACVLAN_MACADDR_DATA, + IFLA_MACVLAN_MACADDR_COUNT, + __IFLA_MACVLAN_MAX, +}; + +#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) + +enum macvlan_mode { + MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */ + MACVLAN_MODE_VEPA = 2, /* talk to other ports through ext bridge */ + MACVLAN_MODE_BRIDGE = 4, /* talk to bridge ports directly */ + MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */ + MACVLAN_MODE_SOURCE = 16,/* use source MAC address list to assign */ +}; + +enum macvlan_macaddr_mode { + MACVLAN_MACADDR_ADD, + MACVLAN_MACADDR_DEL, + MACVLAN_MACADDR_FLUSH, + MACVLAN_MACADDR_SET, +}; + +#define MACVLAN_FLAG_NOPROMISC 1 + +/* VRF section */ +enum { + IFLA_VRF_UNSPEC, + IFLA_VRF_TABLE, + __IFLA_VRF_MAX +}; + +#define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1) + +enum { + IFLA_VRF_PORT_UNSPEC, + IFLA_VRF_PORT_TABLE, + __IFLA_VRF_PORT_MAX +}; + +#define IFLA_VRF_PORT_MAX (__IFLA_VRF_PORT_MAX - 1) + +/* MACSEC section */ +enum { + IFLA_MACSEC_UNSPEC, + IFLA_MACSEC_SCI, + IFLA_MACSEC_PORT, + IFLA_MACSEC_ICV_LEN, + IFLA_MACSEC_CIPHER_SUITE, + IFLA_MACSEC_WINDOW, + IFLA_MACSEC_ENCODING_SA, + IFLA_MACSEC_ENCRYPT, + IFLA_MACSEC_PROTECT, + IFLA_MACSEC_INC_SCI, + IFLA_MACSEC_ES, + IFLA_MACSEC_SCB, + IFLA_MACSEC_REPLAY_PROTECT, + IFLA_MACSEC_VALIDATION, + IFLA_MACSEC_PAD, + __IFLA_MACSEC_MAX, +}; + +#define IFLA_MACSEC_MAX (__IFLA_MACSEC_MAX - 1) + +/* XFRM section */ +enum { + IFLA_XFRM_UNSPEC, + IFLA_XFRM_LINK, + IFLA_XFRM_IF_ID, + __IFLA_XFRM_MAX +}; + +#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1) + +enum macsec_validation_type { + MACSEC_VALIDATE_DISABLED = 0, + MACSEC_VALIDATE_CHECK = 1, + MACSEC_VALIDATE_STRICT = 2, + __MACSEC_VALIDATE_END, + MACSEC_VALIDATE_MAX = __MACSEC_VALIDATE_END - 1, +}; + +/* IPVLAN section */ +enum { + IFLA_IPVLAN_UNSPEC, + IFLA_IPVLAN_MODE, + IFLA_IPVLAN_FLAGS, + __IFLA_IPVLAN_MAX +}; + +#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) + +enum ipvlan_mode { + IPVLAN_MODE_L2 = 0, + IPVLAN_MODE_L3, + IPVLAN_MODE_L3S, + IPVLAN_MODE_MAX +}; + +#define IPVLAN_F_PRIVATE 0x01 +#define IPVLAN_F_VEPA 0x02 + +/* Tunnel RTM header */ +struct tunnel_msg { + __u8 family; + __u8 reserved1; + __u16 reserved2; + __u32 ifindex; +}; + +enum { + VXLAN_VNIFILTER_ENTRY_UNSPEC, + VXLAN_VNIFILTER_ENTRY_START, + VXLAN_VNIFILTER_ENTRY_END, + VXLAN_VNIFILTER_ENTRY_GROUP, + VXLAN_VNIFILTER_ENTRY_GROUP6, + __VXLAN_VNIFILTER_ENTRY_MAX +}; +#define VXLAN_VNIFILTER_ENTRY_MAX (__VXLAN_VNIFILTER_ENTRY_MAX - 1) + +enum { + VXLAN_VNIFILTER_UNSPEC, + VXLAN_VNIFILTER_ENTRY, + __VXLAN_VNIFILTER_MAX +}; +#define VXLAN_VNIFILTER_MAX (__VXLAN_VNIFILTER_MAX - 1) + +/* VXLAN section */ +enum { + IFLA_VXLAN_UNSPEC, + IFLA_VXLAN_ID, + IFLA_VXLAN_GROUP, /* group or remote address */ + IFLA_VXLAN_LINK, + IFLA_VXLAN_LOCAL, + IFLA_VXLAN_TTL, + IFLA_VXLAN_TOS, + IFLA_VXLAN_LEARNING, + IFLA_VXLAN_AGEING, + IFLA_VXLAN_LIMIT, + IFLA_VXLAN_PORT_RANGE, /* source port */ + IFLA_VXLAN_PROXY, + IFLA_VXLAN_RSC, + IFLA_VXLAN_L2MISS, + IFLA_VXLAN_L3MISS, + IFLA_VXLAN_PORT, /* destination port */ + IFLA_VXLAN_GROUP6, + IFLA_VXLAN_LOCAL6, + IFLA_VXLAN_UDP_CSUM, + IFLA_VXLAN_UDP_ZERO_CSUM6_TX, + IFLA_VXLAN_UDP_ZERO_CSUM6_RX, + IFLA_VXLAN_REMCSUM_TX, + IFLA_VXLAN_REMCSUM_RX, + IFLA_VXLAN_GBP, + IFLA_VXLAN_REMCSUM_NOPARTIAL, + IFLA_VXLAN_COLLECT_METADATA, + IFLA_VXLAN_LABEL, + IFLA_VXLAN_GPE, + IFLA_VXLAN_TTL_INHERIT, + IFLA_VXLAN_DF, + IFLA_VXLAN_VNIFILTER, /* only applicable when COLLECT_METADATA mode is + on */ + __IFLA_VXLAN_MAX +}; +#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) + +struct ifla_vxlan_port_range { + __be16 low; + __be16 high; +}; + +/* GENEVE section */ +enum { + IFLA_GENEVE_UNSPEC, + IFLA_GENEVE_ID, + IFLA_GENEVE_REMOTE, + IFLA_GENEVE_TTL, + IFLA_GENEVE_TOS, + IFLA_GENEVE_PORT, /* destination port */ + IFLA_GENEVE_COLLECT_METADATA, + IFLA_GENEVE_REMOTE6, + IFLA_GENEVE_UDP_CSUM, + IFLA_GENEVE_UDP_ZERO_CSUM6_TX, + IFLA_GENEVE_UDP_ZERO_CSUM6_RX, + IFLA_GENEVE_LABEL, + __IFLA_GENEVE_MAX +}; +#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1) + +/* PPP section */ +enum { + IFLA_PPP_UNSPEC, + IFLA_PPP_DEV_FD, + __IFLA_PPP_MAX +}; +#define IFLA_PPP_MAX (__IFLA_PPP_MAX - 1) + +/* GTP section */ + +enum ifla_gtp_role { + GTP_ROLE_GGSN = 0, + GTP_ROLE_SGSN, +}; + +enum { + IFLA_GTP_UNSPEC, + IFLA_GTP_FD0, + IFLA_GTP_FD1, + IFLA_GTP_PDP_HASHSIZE, + IFLA_GTP_ROLE, + __IFLA_GTP_MAX, +}; +#define IFLA_GTP_MAX (__IFLA_GTP_MAX - 1) + +/* Bonding section */ + +enum { + IFLA_BOND_UNSPEC, + IFLA_BOND_MODE, + IFLA_BOND_ACTIVE_SLAVE, + IFLA_BOND_MIIMON, + IFLA_BOND_UPDELAY, + IFLA_BOND_DOWNDELAY, + IFLA_BOND_USE_CARRIER, + IFLA_BOND_ARP_INTERVAL, + IFLA_BOND_ARP_IP_TARGET, + IFLA_BOND_ARP_VALIDATE, + IFLA_BOND_ARP_ALL_TARGETS, + IFLA_BOND_PRIMARY, + IFLA_BOND_PRIMARY_RESELECT, + IFLA_BOND_FAIL_OVER_MAC, + IFLA_BOND_XMIT_HASH_POLICY, + IFLA_BOND_RESEND_IGMP, + IFLA_BOND_NUM_PEER_NOTIF, + IFLA_BOND_ALL_SLAVES_ACTIVE, + IFLA_BOND_MIN_LINKS, + IFLA_BOND_LP_INTERVAL, + IFLA_BOND_PACKETS_PER_SLAVE, + IFLA_BOND_AD_LACP_RATE, + IFLA_BOND_AD_SELECT, + IFLA_BOND_AD_INFO, + IFLA_BOND_AD_ACTOR_SYS_PRIO, + IFLA_BOND_AD_USER_PORT_KEY, + IFLA_BOND_AD_ACTOR_SYSTEM, + IFLA_BOND_TLB_DYNAMIC_LB, + IFLA_BOND_CL_START = 60, + IFLA_BOND_AD_LACP_BYPASS = IFLA_BOND_CL_START, + __IFLA_BOND_MAX, +}; + +#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) + +enum { + IFLA_BOND_AD_INFO_UNSPEC, + IFLA_BOND_AD_INFO_AGGREGATOR, + IFLA_BOND_AD_INFO_NUM_PORTS, + IFLA_BOND_AD_INFO_ACTOR_KEY, + IFLA_BOND_AD_INFO_PARTNER_KEY, + IFLA_BOND_AD_INFO_PARTNER_MAC, + __IFLA_BOND_AD_INFO_MAX, +}; + +#define IFLA_BOND_AD_INFO_MAX (__IFLA_BOND_AD_INFO_MAX - 1) + +enum { + IFLA_BOND_SLAVE_UNSPEC, + IFLA_BOND_SLAVE_STATE, + IFLA_BOND_SLAVE_MII_STATUS, + IFLA_BOND_SLAVE_LINK_FAILURE_COUNT, + IFLA_BOND_SLAVE_PERM_HWADDR, + IFLA_BOND_SLAVE_QUEUE_ID, + IFLA_BOND_SLAVE_AD_AGGREGATOR_ID, + IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE, + IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, + + IFLA_BOND_SLAVE_CL_START = 50, + IFLA_BOND_SLAVE_AD_RX_BYPASS = IFLA_BOND_SLAVE_CL_START, + __IFLA_BOND_SLAVE_MAX, +}; + +#define IFLA_BOND_SLAVE_MAX (__IFLA_BOND_SLAVE_MAX - 1) + +/* SR-IOV virtual function management section */ + +enum { + IFLA_VF_INFO_UNSPEC, + IFLA_VF_INFO, + __IFLA_VF_INFO_MAX, +}; + +#define IFLA_VF_INFO_MAX (__IFLA_VF_INFO_MAX - 1) + +enum { + IFLA_VF_UNSPEC, + IFLA_VF_MAC, /* Hardware queue specific attributes */ + IFLA_VF_VLAN, /* VLAN ID and QoS */ + IFLA_VF_TX_RATE, /* Max TX Bandwidth Allocation */ + IFLA_VF_SPOOFCHK, /* Spoof Checking on/off switch */ + IFLA_VF_LINK_STATE, /* link state enable/disable/auto switch */ + IFLA_VF_RATE, /* Min and Max TX Bandwidth Allocation */ + IFLA_VF_RSS_QUERY_EN, /* RSS Redirection Table and Hash Key query + * on/off switch + */ + IFLA_VF_STATS, /* network device statistics */ + IFLA_VF_TRUST, /* Trust VF */ + IFLA_VF_IB_NODE_GUID, /* VF Infiniband node GUID */ + IFLA_VF_IB_PORT_GUID, /* VF Infiniband port GUID */ + IFLA_VF_VLAN_LIST, /* nested list of vlans, option for QinQ */ + __IFLA_VF_MAX, +}; + +#define IFLA_VF_MAX (__IFLA_VF_MAX - 1) + +struct ifla_vf_mac { + __u32 vf; + __u8 mac[32]; /* MAX_ADDR_LEN */ +}; + +struct ifla_vf_vlan { + __u32 vf; + __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ + __u32 qos; +}; + +enum { + IFLA_VF_VLAN_INFO_UNSPEC, + IFLA_VF_VLAN_INFO, /* VLAN ID, QoS and VLAN protocol */ + __IFLA_VF_VLAN_INFO_MAX, +}; + +#define IFLA_VF_VLAN_INFO_MAX (__IFLA_VF_VLAN_INFO_MAX - 1) +#define MAX_VLAN_LIST_LEN 1 + +struct ifla_vf_vlan_info { + __u32 vf; + __u32 vlan; /* 0 - 4095, 0 disables VLAN filter */ + __u32 qos; + __be16 vlan_proto; /* VLAN protocol either 802.1Q or 802.1ad */ +}; + +struct ifla_vf_tx_rate { + __u32 vf; + __u32 rate; /* Max TX bandwidth in Mbps, 0 disables throttling */ +}; + +struct ifla_vf_rate { + __u32 vf; + __u32 min_tx_rate; /* Min Bandwidth in Mbps */ + __u32 max_tx_rate; /* Max Bandwidth in Mbps */ +}; + +struct ifla_vf_spoofchk { + __u32 vf; + __u32 setting; +}; + +struct ifla_vf_guid { + __u32 vf; + __u64 guid; +}; + +enum { + IFLA_VF_LINK_STATE_AUTO, /* link state of the uplink */ + IFLA_VF_LINK_STATE_ENABLE, /* link always up */ + IFLA_VF_LINK_STATE_DISABLE, /* link always down */ + __IFLA_VF_LINK_STATE_MAX, +}; + +struct ifla_vf_link_state { + __u32 vf; + __u32 link_state; +}; + +struct ifla_vf_rss_query_en { + __u32 vf; + __u32 setting; +}; + +enum { + IFLA_VF_STATS_RX_PACKETS, + IFLA_VF_STATS_TX_PACKETS, + IFLA_VF_STATS_RX_BYTES, + IFLA_VF_STATS_TX_BYTES, + IFLA_VF_STATS_BROADCAST, + IFLA_VF_STATS_MULTICAST, + IFLA_VF_STATS_PAD, + IFLA_VF_STATS_RX_DROPPED, + IFLA_VF_STATS_TX_DROPPED, + __IFLA_VF_STATS_MAX, +}; + +#define IFLA_VF_STATS_MAX (__IFLA_VF_STATS_MAX - 1) + +struct ifla_vf_trust { + __u32 vf; + __u32 setting; +}; + +/* VF ports management section + * + * Nested layout of set/get msg is: + * + * [IFLA_NUM_VF] + * [IFLA_VF_PORTS] + * [IFLA_VF_PORT] + * [IFLA_PORT_*], ... + * [IFLA_VF_PORT] + * [IFLA_PORT_*], ... + * ... + * [IFLA_PORT_SELF] + * [IFLA_PORT_*], ... + */ + +enum { + IFLA_VF_PORT_UNSPEC, + IFLA_VF_PORT, /* nest */ + __IFLA_VF_PORT_MAX, +}; + +#define IFLA_VF_PORT_MAX (__IFLA_VF_PORT_MAX - 1) + +enum { + IFLA_PORT_UNSPEC, + IFLA_PORT_VF, /* __u32 */ + IFLA_PORT_PROFILE, /* string */ + IFLA_PORT_VSI_TYPE, /* 802.1Qbg (pre-)standard VDP */ + IFLA_PORT_INSTANCE_UUID, /* binary UUID */ + IFLA_PORT_HOST_UUID, /* binary UUID */ + IFLA_PORT_REQUEST, /* __u8 */ + IFLA_PORT_RESPONSE, /* __u16, output only */ + __IFLA_PORT_MAX, +}; + +#define IFLA_PORT_MAX (__IFLA_PORT_MAX - 1) + +#define PORT_PROFILE_MAX 40 +#define PORT_UUID_MAX 16 +#define PORT_SELF_VF -1 + +enum { + PORT_REQUEST_PREASSOCIATE = 0, + PORT_REQUEST_PREASSOCIATE_RR, + PORT_REQUEST_ASSOCIATE, + PORT_REQUEST_DISASSOCIATE, +}; + +enum { + PORT_VDP_RESPONSE_SUCCESS = 0, + PORT_VDP_RESPONSE_INVALID_FORMAT, + PORT_VDP_RESPONSE_INSUFFICIENT_RESOURCES, + PORT_VDP_RESPONSE_UNUSED_VTID, + PORT_VDP_RESPONSE_VTID_VIOLATION, + PORT_VDP_RESPONSE_VTID_VERSION_VIOALTION, + PORT_VDP_RESPONSE_OUT_OF_SYNC, + /* 0x08-0xFF reserved for future VDP use */ + PORT_PROFILE_RESPONSE_SUCCESS = 0x100, + PORT_PROFILE_RESPONSE_INPROGRESS, + PORT_PROFILE_RESPONSE_INVALID, + PORT_PROFILE_RESPONSE_BADSTATE, + PORT_PROFILE_RESPONSE_INSUFFICIENT_RESOURCES, + PORT_PROFILE_RESPONSE_ERROR, +}; + +struct ifla_port_vsi { + __u8 vsi_mgr_id; + __u8 vsi_type_id[3]; + __u8 vsi_type_version; + __u8 pad[3]; +}; + + +/* IPoIB section */ + +enum { + IFLA_IPOIB_UNSPEC, + IFLA_IPOIB_PKEY, + IFLA_IPOIB_MODE, + IFLA_IPOIB_UMCAST, + __IFLA_IPOIB_MAX +}; + +enum { + IPOIB_MODE_DATAGRAM = 0, /* using unreliable datagram QPs */ + IPOIB_MODE_CONNECTED = 1, /* using connected QPs */ +}; + +#define IFLA_IPOIB_MAX (__IFLA_IPOIB_MAX - 1) + + +/* HSR section */ + +enum { + IFLA_HSR_UNSPEC, + IFLA_HSR_SLAVE1, + IFLA_HSR_SLAVE2, + IFLA_HSR_MULTICAST_SPEC, /* Last byte of supervision addr */ + IFLA_HSR_SUPERVISION_ADDR, /* Supervision frame multicast addr */ + IFLA_HSR_SEQ_NR, + IFLA_HSR_VERSION, /* HSR version */ + __IFLA_HSR_MAX, +}; + +#define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1) + +/* STATS section */ + +struct if_stats_msg { + __u8 family; + __u8 pad1; + __u16 pad2; + __u32 ifindex; + __u32 filter_mask; +}; + +/* A stats attribute can be netdev specific or a global stat. + * For netdev stats, lets use the prefix IFLA_STATS_LINK_* + */ +enum { + IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */ + IFLA_STATS_LINK_64, + IFLA_STATS_LINK_XSTATS, + IFLA_STATS_LINK_XSTATS_SLAVE, + IFLA_STATS_LINK_OFFLOAD_XSTATS, + IFLA_STATS_AF_SPEC, + __IFLA_STATS_MAX, +}; + +#define IFLA_STATS_MAX (__IFLA_STATS_MAX - 1) + +#define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1)) + +/* These are embedded into IFLA_STATS_LINK_XSTATS: + * [IFLA_STATS_LINK_XSTATS] + * -> [LINK_XSTATS_TYPE_xxx] + * -> [rtnl link type specific attributes] + */ +enum { + LINK_XSTATS_TYPE_UNSPEC, + LINK_XSTATS_TYPE_BRIDGE, + LINK_XSTATS_TYPE_BOND, + __LINK_XSTATS_TYPE_MAX +}; +#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1) + +/* These are stats embedded into IFLA_STATS_LINK_OFFLOAD_XSTATS */ +enum { + IFLA_OFFLOAD_XSTATS_UNSPEC, + IFLA_OFFLOAD_XSTATS_CPU_HIT, /* struct rtnl_link_stats64 */ + __IFLA_OFFLOAD_XSTATS_MAX +}; +#define IFLA_OFFLOAD_XSTATS_MAX (__IFLA_OFFLOAD_XSTATS_MAX - 1) + +/* XDP section */ + +#define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0) +#define XDP_FLAGS_SKB_MODE (1U << 1) +#define XDP_FLAGS_DRV_MODE (1U << 2) +#define XDP_FLAGS_HW_MODE (1U << 3) +#define XDP_FLAGS_MODES (XDP_FLAGS_SKB_MODE | \ + XDP_FLAGS_DRV_MODE | \ + XDP_FLAGS_HW_MODE) +#define XDP_FLAGS_MASK (XDP_FLAGS_UPDATE_IF_NOEXIST | \ + XDP_FLAGS_MODES) + +/* These are stored into IFLA_XDP_ATTACHED on dump. */ +enum { + XDP_ATTACHED_NONE = 0, + XDP_ATTACHED_DRV, + XDP_ATTACHED_SKB, + XDP_ATTACHED_HW, + XDP_ATTACHED_MULTI, +}; + +enum { + IFLA_XDP_UNSPEC, + IFLA_XDP_FD, + IFLA_XDP_ATTACHED, + IFLA_XDP_FLAGS, + IFLA_XDP_PROG_ID, + IFLA_XDP_DRV_PROG_ID, + IFLA_XDP_SKB_PROG_ID, + IFLA_XDP_HW_PROG_ID, + __IFLA_XDP_MAX, +}; + +#define IFLA_XDP_MAX (__IFLA_XDP_MAX - 1) + +enum { + IFLA_EVENT_NONE, + IFLA_EVENT_REBOOT, /* internal reset / reboot */ + IFLA_EVENT_FEATURES, /* change in offload features */ + IFLA_EVENT_BONDING_FAILOVER, /* change in active slave */ + IFLA_EVENT_NOTIFY_PEERS, /* re-sent grat. arp/ndisc */ + IFLA_EVENT_IGMP_RESEND, /* re-sent IGMP JOIN */ + IFLA_EVENT_BONDING_OPTIONS, /* change in bonding options */ +}; + +/* tun section */ + +enum { + IFLA_TUN_UNSPEC, + IFLA_TUN_OWNER, + IFLA_TUN_GROUP, + IFLA_TUN_TYPE, + IFLA_TUN_PI, + IFLA_TUN_VNET_HDR, + IFLA_TUN_PERSIST, + IFLA_TUN_MULTI_QUEUE, + IFLA_TUN_NUM_QUEUES, + IFLA_TUN_NUM_DISABLED_QUEUES, + __IFLA_TUN_MAX, +}; + +#define IFLA_TUN_MAX (__IFLA_TUN_MAX - 1) + +/* rmnet section */ + +#define RMNET_FLAGS_INGRESS_DEAGGREGATION (1U << 0) +#define RMNET_FLAGS_INGRESS_MAP_COMMANDS (1U << 1) +#define RMNET_FLAGS_INGRESS_MAP_CKSUMV4 (1U << 2) +#define RMNET_FLAGS_EGRESS_MAP_CKSUMV4 (1U << 3) + +enum { + IFLA_RMNET_UNSPEC, + IFLA_RMNET_MUX_ID, + IFLA_RMNET_FLAGS, + __IFLA_RMNET_MAX, +}; + +#define IFLA_RMNET_MAX (__IFLA_RMNET_MAX - 1) + +struct ifla_rmnet_flags { + __u32 flags; + __u32 mask; +}; + +#endif /* _UAPI_LINUX_IF_LINK_H */ diff --git a/include/linux/if_packet.h b/include/linux/if_packet.h new file mode 100644 index 0000000..057edb3 --- /dev/null +++ b/include/linux/if_packet.h @@ -0,0 +1,16 @@ +#ifndef __LINUX_IF_PACKET_H +#define __LINUX_IF_PACKET_H + +#include + +struct sockaddr_ll { + unsigned short sll_family; + __be16 sll_protocol; + int sll_ifindex; + unsigned short sll_hatype; + unsigned char sll_pkttype; + unsigned char sll_halen; + unsigned char sll_addr[8]; +}; + +#endif diff --git a/include/linux/if_tunnel.h b/include/linux/if_tunnel.h new file mode 100644 index 0000000..982a1b6 --- /dev/null +++ b/include/linux/if_tunnel.h @@ -0,0 +1,71 @@ +#ifndef _IF_TUNNEL_H_ +#define _IF_TUNNEL_H_ + +#include +#include +#include +#include +#include + + +#define SIOCGETTUNNEL (SIOCDEVPRIVATE + 0) +#define SIOCADDTUNNEL (SIOCDEVPRIVATE + 1) +#define SIOCDELTUNNEL (SIOCDEVPRIVATE + 2) +#define SIOCCHGTUNNEL (SIOCDEVPRIVATE + 3) +#define SIOCGETPRL (SIOCDEVPRIVATE + 4) +#define SIOCADDPRL (SIOCDEVPRIVATE + 5) +#define SIOCDELPRL (SIOCDEVPRIVATE + 6) +#define SIOCCHGPRL (SIOCDEVPRIVATE + 7) + +#define GRE_CSUM __cpu_to_be16(0x8000) +#define GRE_ROUTING __cpu_to_be16(0x4000) +#define GRE_KEY __cpu_to_be16(0x2000) +#define GRE_SEQ __cpu_to_be16(0x1000) +#define GRE_STRICT __cpu_to_be16(0x0800) +#define GRE_REC __cpu_to_be16(0x0700) +#define GRE_FLAGS __cpu_to_be16(0x00F8) +#define GRE_VERSION __cpu_to_be16(0x0007) + +struct ip_tunnel_parm { + char name[IFNAMSIZ]; + int link; + __be16 i_flags; + __be16 o_flags; + __be32 i_key; + __be32 o_key; + struct iphdr iph; +}; + +/* SIT-mode i_flags */ +#define SIT_ISATAP 0x0001 + +struct ip_tunnel_prl { + __be32 addr; + __u16 flags; + __u16 __reserved; + __u32 datalen; + __u32 __reserved2; + /* data follows */ +}; + +/* PRL flags */ +#define PRL_DEFAULT 0x0001 + +enum { + IFLA_GRE_UNSPEC, + IFLA_GRE_LINK, + IFLA_GRE_IFLAGS, + IFLA_GRE_OFLAGS, + IFLA_GRE_IKEY, + IFLA_GRE_OKEY, + IFLA_GRE_LOCAL, + IFLA_GRE_REMOTE, + IFLA_GRE_TTL, + IFLA_GRE_TOS, + IFLA_GRE_PMTUDISC, + __IFLA_GRE_MAX, +}; + +#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) + +#endif /* _IF_TUNNEL_H_ */ diff --git a/include/linux/libc-compat.h b/include/linux/libc-compat.h new file mode 100644 index 0000000..8254c93 --- /dev/null +++ b/include/linux/libc-compat.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Compatibility interface for userspace libc header coordination: + * + * Define compatibility macros that are used to control the inclusion or + * exclusion of UAPI structures and definitions in coordination with another + * userspace C library. + * + * This header is intended to solve the problem of UAPI definitions that + * conflict with userspace definitions. If a UAPI header has such conflicting + * definitions then the solution is as follows: + * + * * Synchronize the UAPI header and the libc headers so either one can be + * used and such that the ABI is preserved. If this is not possible then + * no simple compatibility interface exists (you need to write translating + * wrappers and rename things) and you can't use this interface. + * + * Then follow this process: + * + * (a) Include libc-compat.h in the UAPI header. + * e.g. #include + * This include must be as early as possible. + * + * (b) In libc-compat.h add enough code to detect that the comflicting + * userspace libc header has been included first. + * + * (c) If the userspace libc header has been included first define a set of + * guard macros of the form __UAPI_DEF_FOO and set their values to 1, else + * set their values to 0. + * + * (d) Back in the UAPI header with the conflicting definitions, guard the + * definitions with: + * #if __UAPI_DEF_FOO + * ... + * #endif + * + * This fixes the situation where the linux headers are included *after* the + * libc headers. To fix the problem with the inclusion in the other order the + * userspace libc headers must be fixed like this: + * + * * For all definitions that conflict with kernel definitions wrap those + * defines in the following: + * #if !__UAPI_DEF_FOO + * ... + * #endif + * + * This prevents the redefinition of a construct already defined by the kernel. + */ +#ifndef _UAPI_LIBC_COMPAT_H +#define _UAPI_LIBC_COMPAT_H + +/* We have included glibc headers... */ +#if defined(__GLIBC__) + +/* Coordinate with glibc net/if.h header. */ +#if defined(_NET_IF_H) && defined(__USE_MISC) + +/* GLIBC headers included first so don't define anything + * that would already be defined. */ + +#define __UAPI_DEF_IF_IFCONF 0 +#define __UAPI_DEF_IF_IFMAP 0 +#define __UAPI_DEF_IF_IFNAMSIZ 0 +#define __UAPI_DEF_IF_IFREQ 0 +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 0 +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ + +#else /* _NET_IF_H */ + +/* Linux headers included first, and we must define everything + * we need. The expectation is that glibc will check the + * __UAPI_DEF_* defines and adjust appropriately. */ + +#define __UAPI_DEF_IF_IFCONF 1 +#define __UAPI_DEF_IF_IFMAP 1 +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#define __UAPI_DEF_IF_IFREQ 1 +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1 +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 + +#endif /* _NET_IF_H */ + +/* Coordinate with glibc netinet/in.h header. */ +#if defined(_NETINET_IN_H) + +/* GLIBC headers included first so don't define anything + * that would already be defined. */ +#define __UAPI_DEF_IN_ADDR 0 +#define __UAPI_DEF_IN_IPPROTO 0 +#define __UAPI_DEF_IN_PKTINFO 0 +#define __UAPI_DEF_IP_MREQ 0 +#define __UAPI_DEF_SOCKADDR_IN 0 +#define __UAPI_DEF_IN_CLASS 0 + +#define __UAPI_DEF_IN6_ADDR 0 +/* The exception is the in6_addr macros which must be defined + * if the glibc code didn't define them. This guard matches + * the guard in glibc/inet/netinet/in.h which defines the + * additional in6_addr macros e.g. s6_addr16, and s6_addr32. */ +#if defined(__USE_MISC) || defined (__USE_GNU) +#define __UAPI_DEF_IN6_ADDR_ALT 0 +#else +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#endif +#define __UAPI_DEF_SOCKADDR_IN6 0 +#define __UAPI_DEF_IPV6_MREQ 0 +#define __UAPI_DEF_IPPROTO_V6 0 +#define __UAPI_DEF_IPV6_OPTIONS 0 +#define __UAPI_DEF_IN6_PKTINFO 0 +#define __UAPI_DEF_IP6_MTUINFO 0 + +#else + +/* Linux headers included first, and we must define everything + * we need. The expectation is that glibc will check the + * __UAPI_DEF_* defines and adjust appropriately. */ +#define __UAPI_DEF_IN_ADDR 1 +#define __UAPI_DEF_IN_IPPROTO 1 +#define __UAPI_DEF_IN_PKTINFO 1 +#define __UAPI_DEF_IP_MREQ 1 +#define __UAPI_DEF_SOCKADDR_IN 1 +#define __UAPI_DEF_IN_CLASS 1 + +#define __UAPI_DEF_IN6_ADDR 1 +/* We unconditionally define the in6_addr macros and glibc must + * coordinate. */ +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#define __UAPI_DEF_SOCKADDR_IN6 1 +#define __UAPI_DEF_IPV6_MREQ 1 +#define __UAPI_DEF_IPPROTO_V6 1 +#define __UAPI_DEF_IPV6_OPTIONS 1 +#define __UAPI_DEF_IN6_PKTINFO 1 +#define __UAPI_DEF_IP6_MTUINFO 1 + +#endif /* _NETINET_IN_H */ + +/* Coordinate with glibc netipx/ipx.h header. */ +#if defined(__NETIPX_IPX_H) + +#define __UAPI_DEF_SOCKADDR_IPX 0 +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 0 +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 0 +#define __UAPI_DEF_IPX_CONFIG_DATA 0 +#define __UAPI_DEF_IPX_ROUTE_DEF 0 + +#else /* defined(__NETIPX_IPX_H) */ + +#define __UAPI_DEF_SOCKADDR_IPX 1 +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 1 +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 1 +#define __UAPI_DEF_IPX_CONFIG_DATA 1 +#define __UAPI_DEF_IPX_ROUTE_DEF 1 + +#endif /* defined(__NETIPX_IPX_H) */ + +/* Definitions for xattr.h */ +#if defined(_SYS_XATTR_H) +#define __UAPI_DEF_XATTR 0 +#else +#define __UAPI_DEF_XATTR 1 +#endif + +/* If we did not see any headers from any supported C libraries, + * or we are being included in the kernel, then define everything + * that we need. Check for previous __UAPI_* definitions to give + * unsupported C libraries a way to opt out of any kernel definition. */ +#else /* !defined(__GLIBC__) */ + +/* Definitions for if.h */ +#ifndef __UAPI_DEF_IF_IFCONF +#define __UAPI_DEF_IF_IFCONF 1 +#endif +#ifndef __UAPI_DEF_IF_IFMAP +#define __UAPI_DEF_IF_IFMAP 1 +#endif +#ifndef __UAPI_DEF_IF_IFNAMSIZ +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#endif +#ifndef __UAPI_DEF_IF_IFREQ +#define __UAPI_DEF_IF_IFREQ 1 +#endif +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1 +#endif +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 +#endif + +/* Definitions for in.h */ +#ifndef __UAPI_DEF_IN_ADDR +#define __UAPI_DEF_IN_ADDR 1 +#endif +#ifndef __UAPI_DEF_IN_IPPROTO +#define __UAPI_DEF_IN_IPPROTO 1 +#endif +#ifndef __UAPI_DEF_IN_PKTINFO +#define __UAPI_DEF_IN_PKTINFO 1 +#endif +#ifndef __UAPI_DEF_IP_MREQ +#define __UAPI_DEF_IP_MREQ 1 +#endif +#ifndef __UAPI_DEF_SOCKADDR_IN +#define __UAPI_DEF_SOCKADDR_IN 1 +#endif +#ifndef __UAPI_DEF_IN_CLASS +#define __UAPI_DEF_IN_CLASS 1 +#endif + +/* Definitions for in6.h */ +#ifndef __UAPI_DEF_IN6_ADDR +#define __UAPI_DEF_IN6_ADDR 1 +#endif +#ifndef __UAPI_DEF_IN6_ADDR_ALT +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#endif +#ifndef __UAPI_DEF_SOCKADDR_IN6 +#define __UAPI_DEF_SOCKADDR_IN6 1 +#endif +#ifndef __UAPI_DEF_IPV6_MREQ +#define __UAPI_DEF_IPV6_MREQ 1 +#endif +#ifndef __UAPI_DEF_IPPROTO_V6 +#define __UAPI_DEF_IPPROTO_V6 1 +#endif +#ifndef __UAPI_DEF_IPV6_OPTIONS +#define __UAPI_DEF_IPV6_OPTIONS 1 +#endif +#ifndef __UAPI_DEF_IN6_PKTINFO +#define __UAPI_DEF_IN6_PKTINFO 1 +#endif +#ifndef __UAPI_DEF_IP6_MTUINFO +#define __UAPI_DEF_IP6_MTUINFO 1 +#endif + +/* Definitions for ipx.h */ +#ifndef __UAPI_DEF_SOCKADDR_IPX +#define __UAPI_DEF_SOCKADDR_IPX 1 +#endif +#ifndef __UAPI_DEF_IPX_ROUTE_DEFINITION +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 1 +#endif +#ifndef __UAPI_DEF_IPX_INTERFACE_DEFINITION +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 1 +#endif +#ifndef __UAPI_DEF_IPX_CONFIG_DATA +#define __UAPI_DEF_IPX_CONFIG_DATA 1 +#endif +#ifndef __UAPI_DEF_IPX_ROUTE_DEF +#define __UAPI_DEF_IPX_ROUTE_DEF 1 +#endif + +/* Definitions for xattr.h */ +#ifndef __UAPI_DEF_XATTR +#define __UAPI_DEF_XATTR 1 +#endif + +#endif /* __GLIBC__ */ + +#endif /* _UAPI_LIBC_COMPAT_H */ diff --git a/include/linux/lwtunnel.h b/include/linux/lwtunnel.h new file mode 100644 index 0000000..de696ca --- /dev/null +++ b/include/linux/lwtunnel.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LWTUNNEL_H_ +#define _UAPI_LWTUNNEL_H_ + +#include + +enum lwtunnel_encap_types { + LWTUNNEL_ENCAP_NONE, + LWTUNNEL_ENCAP_MPLS, + LWTUNNEL_ENCAP_IP, + LWTUNNEL_ENCAP_ILA, + LWTUNNEL_ENCAP_IP6, + LWTUNNEL_ENCAP_SEG6, + LWTUNNEL_ENCAP_BPF, + LWTUNNEL_ENCAP_SEG6_LOCAL, + __LWTUNNEL_ENCAP_MAX, +}; + +#define LWTUNNEL_ENCAP_MAX (__LWTUNNEL_ENCAP_MAX - 1) + +enum lwtunnel_ip_t { + LWTUNNEL_IP_UNSPEC, + LWTUNNEL_IP_ID, + LWTUNNEL_IP_DST, + LWTUNNEL_IP_SRC, + LWTUNNEL_IP_TTL, + LWTUNNEL_IP_TOS, + LWTUNNEL_IP_FLAGS, + LWTUNNEL_IP_PAD, + __LWTUNNEL_IP_MAX, +}; + +#define LWTUNNEL_IP_MAX (__LWTUNNEL_IP_MAX - 1) + +enum lwtunnel_ip6_t { + LWTUNNEL_IP6_UNSPEC, + LWTUNNEL_IP6_ID, + LWTUNNEL_IP6_DST, + LWTUNNEL_IP6_SRC, + LWTUNNEL_IP6_HOPLIMIT, + LWTUNNEL_IP6_TC, + LWTUNNEL_IP6_FLAGS, + LWTUNNEL_IP6_PAD, + __LWTUNNEL_IP6_MAX, +}; + +#define LWTUNNEL_IP6_MAX (__LWTUNNEL_IP6_MAX - 1) + +enum { + LWT_BPF_PROG_UNSPEC, + LWT_BPF_PROG_FD, + LWT_BPF_PROG_NAME, + __LWT_BPF_PROG_MAX, +}; + +#define LWT_BPF_PROG_MAX (__LWT_BPF_PROG_MAX - 1) + +enum { + LWT_BPF_UNSPEC, + LWT_BPF_IN, + LWT_BPF_OUT, + LWT_BPF_XMIT, + LWT_BPF_XMIT_HEADROOM, + __LWT_BPF_MAX, +}; + +#define LWT_BPF_MAX (__LWT_BPF_MAX - 1) + +#define LWT_BPF_MAX_HEADROOM 256 + +#endif /* _UAPI_LWTUNNEL_H_ */ diff --git a/include/linux/mpls_iptunnel.h b/include/linux/mpls_iptunnel.h new file mode 100644 index 0000000..521f2e6 --- /dev/null +++ b/include/linux/mpls_iptunnel.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * mpls tunnel api + * + * Authors: + * Roopa Prabhu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _UAPI_LINUX_MPLS_IPTUNNEL_H +#define _UAPI_LINUX_MPLS_IPTUNNEL_H + +/* MPLS tunnel attributes + * [RTA_ENCAP] = { + * [MPLS_IPTUNNEL_DST] + * [MPLS_IPTUNNEL_TTL] + * } + */ +enum { + MPLS_IPTUNNEL_UNSPEC, + MPLS_IPTUNNEL_DST, + MPLS_IPTUNNEL_TTL, + __MPLS_IPTUNNEL_MAX, +}; +#define MPLS_IPTUNNEL_MAX (__MPLS_IPTUNNEL_MAX - 1) + +#endif /* _UAPI_LINUX_MPLS_IPTUNNEL_H */ diff --git a/include/linux/neighbour.h b/include/linux/neighbour.h new file mode 100644 index 0000000..581af73 --- /dev/null +++ b/include/linux/neighbour.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NEIGHBOUR_H +#define __LINUX_NEIGHBOUR_H + +#include +#include + +struct ndmsg { + __u8 ndm_family; + __u8 ndm_pad1; + __u16 ndm_pad2; + __s32 ndm_ifindex; + __u16 ndm_state; + __u8 ndm_flags; + __u8 ndm_type; +}; + +enum { + NDA_UNSPEC, + NDA_DST, + NDA_LLADDR, + NDA_CACHEINFO, + NDA_PROBES, + NDA_VLAN, + NDA_PORT, + NDA_VNI, + NDA_IFINDEX, + NDA_MASTER, + NDA_LINK_NETNSID, + NDA_SRC_VNI, + NDA_PROTOCOL, /* Originator of entry */ + NDA_NH_ID, + NDA_FDB_EXT_ATTRS, + NDA_EXT_FLAGS, + __NDA_MAX +}; + +#define NDA_MAX (__NDA_MAX - 1) + +/* + * Neighbor Cache Entry Flags + */ + +#define NTF_USE 0x01 +#define NTF_SELF 0x02 +#define NTF_MASTER 0x04 +#define NTF_PROXY 0x08 /* == ATF_PUBL */ +#define NTF_EXT_LEARNED 0x10 +#define NTF_OFFLOADED 0x20 +#define NTF_STICKY 0x40 +#define NTF_ROUTER 0x80 + +/* Neighbor Cache Entry extended flags, part of NDA_EXT_FLAGS attribute */ +#define NTF_E_WEAK_OVERRIDE_STATE 0x01 +#define NTF_E_MH_PEER_SYNC 0x02 + +/* + * Neighbor Cache Entry States. + */ + +#define NUD_INCOMPLETE 0x01 +#define NUD_REACHABLE 0x02 +#define NUD_STALE 0x04 +#define NUD_DELAY 0x08 +#define NUD_PROBE 0x10 +#define NUD_FAILED 0x20 + +/* Dummy states */ +#define NUD_NOARP 0x40 +#define NUD_PERMANENT 0x80 +#define NUD_NONE 0x00 + +/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change + and make no address resolution or NUD. + NUD_PERMANENT also cannot be deleted by garbage collectors. + */ + +struct nda_cacheinfo { + __u32 ndm_confirmed; + __u32 ndm_used; + __u32 ndm_updated; + __u32 ndm_refcnt; +}; + +/***************************************************************** + * Neighbour tables specific messages. + * + * To retrieve the neighbour tables send RTM_GETNEIGHTBL with the + * NLM_F_DUMP flag set. Every neighbour table configuration is + * spread over multiple messages to avoid running into message + * size limits on systems with many interfaces. The first message + * in the sequence transports all not device specific data such as + * statistics, configuration, and the default parameter set. + * This message is followed by 0..n messages carrying device + * specific parameter sets. + * Although the ordering should be sufficient, NDTA_NAME can be + * used to identify sequences. The initial message can be identified + * by checking for NDTA_CONFIG. The device specific messages do + * not contain this TLV but have NDTPA_IFINDEX set to the + * corresponding interface index. + * + * To change neighbour table attributes, send RTM_SETNEIGHTBL + * with NDTA_NAME set. Changeable attribute include NDTA_THRESH[1-3], + * NDTA_GC_INTERVAL, and all TLVs in NDTA_PARMS unless marked + * otherwise. Device specific parameter sets can be changed by + * setting NDTPA_IFINDEX to the interface index of the corresponding + * device. + ****/ + +struct ndt_stats { + __u64 ndts_allocs; + __u64 ndts_destroys; + __u64 ndts_hash_grows; + __u64 ndts_res_failed; + __u64 ndts_lookups; + __u64 ndts_hits; + __u64 ndts_rcv_probes_mcast; + __u64 ndts_rcv_probes_ucast; + __u64 ndts_periodic_gc_runs; + __u64 ndts_forced_gc_runs; + __u64 ndts_table_fulls; +}; + +enum { + NDTPA_UNSPEC, + NDTPA_IFINDEX, /* u32, unchangeable */ + NDTPA_REFCNT, /* u32, read-only */ + NDTPA_REACHABLE_TIME, /* u64, read-only, msecs */ + NDTPA_BASE_REACHABLE_TIME, /* u64, msecs */ + NDTPA_RETRANS_TIME, /* u64, msecs */ + NDTPA_GC_STALETIME, /* u64, msecs */ + NDTPA_DELAY_PROBE_TIME, /* u64, msecs */ + NDTPA_QUEUE_LEN, /* u32 */ + NDTPA_APP_PROBES, /* u32 */ + NDTPA_UCAST_PROBES, /* u32 */ + NDTPA_MCAST_PROBES, /* u32 */ + NDTPA_ANYCAST_DELAY, /* u64, msecs */ + NDTPA_PROXY_DELAY, /* u64, msecs */ + NDTPA_PROXY_QLEN, /* u32 */ + NDTPA_LOCKTIME, /* u64, msecs */ + NDTPA_QUEUE_LENBYTES, /* u32 */ + NDTPA_MCAST_REPROBES, /* u32 */ + NDTPA_PAD, + __NDTPA_MAX +}; +#define NDTPA_MAX (__NDTPA_MAX - 1) + +struct ndtmsg { + __u8 ndtm_family; + __u8 ndtm_pad1; + __u16 ndtm_pad2; +}; + +struct ndt_config { + __u16 ndtc_key_len; + __u16 ndtc_entry_size; + __u32 ndtc_entries; + __u32 ndtc_last_flush; /* delta to now in msecs */ + __u32 ndtc_last_rand; /* delta to now in msecs */ + __u32 ndtc_hash_rnd; + __u32 ndtc_hash_mask; + __u32 ndtc_hash_chain_gc; + __u32 ndtc_proxy_qlen; +}; + +enum { + NDTA_UNSPEC, + NDTA_NAME, /* char *, unchangeable */ + NDTA_THRESH1, /* u32 */ + NDTA_THRESH2, /* u32 */ + NDTA_THRESH3, /* u32 */ + NDTA_CONFIG, /* struct ndt_config, read-only */ + NDTA_PARMS, /* nested TLV NDTPA_* */ + NDTA_STATS, /* struct ndt_stats, read-only */ + NDTA_GC_INTERVAL, /* u64, msecs */ + NDTA_PAD, + __NDTA_MAX +}; +#define NDTA_MAX (__NDTA_MAX - 1) + +/* FDB activity notification bits used in NFEA_ACTIVITY_NOTIFY: + * - FDB_NOTIFY_BIT - notify on activity/expire for any entry + * - FDB_NOTIFY_INACTIVE_BIT - mark as inactive to avoid multiple notifications + */ +enum { + FDB_NOTIFY_BIT = (1 << 0), + FDB_NOTIFY_INACTIVE_BIT = (1 << 1) +}; + +/* embedded into NDA_FDB_EXT_ATTRS: + * [NDA_FDB_EXT_ATTRS] = { + * [NFEA_ACTIVITY_NOTIFY] + * ... + * } + */ +enum { + NFEA_UNSPEC, + NFEA_ACTIVITY_NOTIFY, + NFEA_DONT_REFRESH, + __NFEA_MAX +}; +#define NFEA_MAX (__NFEA_MAX - 1) + +#endif diff --git a/include/linux/net_namespace.h b/include/linux/net_namespace.h new file mode 100644 index 0000000..0ed9dd6 --- /dev/null +++ b/include/linux/net_namespace.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Copyright (c) 2015 6WIND S.A. + * Author: Nicolas Dichtel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ +#ifndef _UAPI_LINUX_NET_NAMESPACE_H_ +#define _UAPI_LINUX_NET_NAMESPACE_H_ + +/* Attributes of RTM_NEWNSID/RTM_GETNSID messages */ +enum { + NETNSA_NONE, +#define NETNSA_NSID_NOT_ASSIGNED -1 + NETNSA_NSID, + NETNSA_PID, + NETNSA_FD, + NETNSA_TARGET_NSID, + __NETNSA_MAX, +}; + +#define NETNSA_MAX (__NETNSA_MAX - 1) + +#endif /* _UAPI_LINUX_NET_NAMESPACE_H_ */ diff --git a/include/linux/netconf.h b/include/linux/netconf.h new file mode 100644 index 0000000..fac4edd --- /dev/null +++ b/include/linux/netconf.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_NETCONF_H_ +#define _UAPI_LINUX_NETCONF_H_ + +#include +#include + +struct netconfmsg { + __u8 ncm_family; +}; + +enum { + NETCONFA_UNSPEC, + NETCONFA_IFINDEX, + NETCONFA_FORWARDING, + NETCONFA_RP_FILTER, + NETCONFA_MC_FORWARDING, + NETCONFA_PROXY_NEIGH, + NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN, + NETCONFA_INPUT, + NETCONFA_BC_FORWARDING, + __NETCONFA_MAX +}; +#define NETCONFA_MAX (__NETCONFA_MAX - 1) +#define NETCONFA_ALL -1 + +#define NETCONFA_IFINDEX_ALL -1 +#define NETCONFA_IFINDEX_DEFAULT -2 + +#endif /* _UAPI_LINUX_NETCONF_H_ */ diff --git a/include/linux/netlink.h b/include/linux/netlink.h new file mode 100644 index 0000000..750a36b --- /dev/null +++ b/include/linux/netlink.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI__LINUX_NETLINK_H +#define _UAPI__LINUX_NETLINK_H + +#include +#include /* for __kernel_sa_family_t */ +#include + +#define NETLINK_ROUTE 0 /* Routing/device hook */ +#define NETLINK_UNUSED 1 /* Unused number */ +#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ +#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ +#define NETLINK_SOCK_DIAG 4 /* socket monitoring */ +#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ +#define NETLINK_XFRM 6 /* ipsec */ +#define NETLINK_SELINUX 7 /* SELinux event notifications */ +#define NETLINK_ISCSI 8 /* Open-iSCSI */ +#define NETLINK_AUDIT 9 /* auditing */ +#define NETLINK_FIB_LOOKUP 10 +#define NETLINK_CONNECTOR 11 +#define NETLINK_NETFILTER 12 /* netfilter subsystem */ +#define NETLINK_IP6_FW 13 +#define NETLINK_DNRTMSG 14 /* DECnet routing messages */ +#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ +#define NETLINK_GENERIC 16 +/* leave room for NETLINK_DM (DM Events) */ +#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ +#define NETLINK_ECRYPTFS 19 +#define NETLINK_RDMA 20 +#define NETLINK_CRYPTO 21 /* Crypto layer */ +#define NETLINK_SMC 22 /* SMC monitoring */ + +#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG + +#define MAX_LINKS 32 + +struct sockaddr_nl { + __kernel_sa_family_t nl_family; /* AF_NETLINK */ + unsigned short nl_pad; /* zero */ + __u32 nl_pid; /* port ID */ + __u32 nl_groups; /* multicast groups mask */ +}; + +struct nlmsghdr { + __u32 nlmsg_len; /* Length of message including header */ + __u16 nlmsg_type; /* Message content */ + __u16 nlmsg_flags; /* Additional flags */ + __u32 nlmsg_seq; /* Sequence number */ + __u32 nlmsg_pid; /* Sending process port ID */ +}; + +/* Flags values */ + +#define NLM_F_REQUEST 0x01 /* It is request message. */ +#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */ +#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */ +#define NLM_F_ECHO 0x08 /* Echo this request */ +#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */ +#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */ + +/* Modifiers to GET request */ +#define NLM_F_ROOT 0x100 /* specify tree root */ +#define NLM_F_MATCH 0x200 /* return all matching */ +#define NLM_F_ATOMIC 0x400 /* atomic GET */ +#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) + +/* Modifiers to NEW request */ +#define NLM_F_REPLACE 0x100 /* Override existing */ +#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ +#define NLM_F_CREATE 0x400 /* Create, if it does not exist */ +#define NLM_F_APPEND 0x800 /* Add to end of list */ + +/* Modifiers to DELETE request */ +#define NLM_F_NONREC 0x100 /* Do not delete recursively */ + +/* Flags for ACK message */ +#define NLM_F_CAPPED 0x100 /* request was capped */ +#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ + +/* + 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL + 4.4BSD CHANGE NLM_F_REPLACE + + True CHANGE NLM_F_CREATE|NLM_F_REPLACE + Append NLM_F_CREATE + Check NLM_F_EXCL + */ + +#define NLMSG_ALIGNTO 4U +#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) +#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) +#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) +#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) +#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) +#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ + (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) +#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len <= (len)) +#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) + +#define NLMSG_NOOP 0x1 /* Nothing. */ +#define NLMSG_ERROR 0x2 /* Error */ +#define NLMSG_DONE 0x3 /* End of a dump */ +#define NLMSG_OVERRUN 0x4 /* Data lost */ + +#define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */ + +struct nlmsgerr { + int error; + struct nlmsghdr msg; + /* + * followed by the message contents unless NETLINK_CAP_ACK was set + * or the ACK indicates success (error == 0) + * message length is aligned with NLMSG_ALIGN() + */ + /* + * followed by TLVs defined in enum nlmsgerr_attrs + * if NETLINK_EXT_ACK was set + */ +}; + +/** + * enum nlmsgerr_attrs - nlmsgerr attributes + * @NLMSGERR_ATTR_UNUSED: unused + * @NLMSGERR_ATTR_MSG: error message string (string) + * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original + * message, counting from the beginning of the header (u32) + * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to + * be used - in the success case - to identify a created + * object or operation or similar (binary) + * @__NLMSGERR_ATTR_MAX: number of attributes + * @NLMSGERR_ATTR_MAX: highest attribute number + */ +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + NLMSGERR_ATTR_COOKIE, + + __NLMSGERR_ATTR_MAX, + NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 +}; + +#define NETLINK_ADD_MEMBERSHIP 1 +#define NETLINK_DROP_MEMBERSHIP 2 +#define NETLINK_PKTINFO 3 +#define NETLINK_BROADCAST_ERROR 4 +#define NETLINK_NO_ENOBUFS 5 +#ifndef __KERNEL__ +#define NETLINK_RX_RING 6 +#define NETLINK_TX_RING 7 +#endif +#define NETLINK_LISTEN_ALL_NSID 8 +#define NETLINK_LIST_MEMBERSHIPS 9 +#define NETLINK_CAP_ACK 10 +#define NETLINK_EXT_ACK 11 +#define NETLINK_GET_STRICT_CHK 12 + +struct nl_pktinfo { + __u32 group; +}; + +struct nl_mmap_req { + unsigned int nm_block_size; + unsigned int nm_block_nr; + unsigned int nm_frame_size; + unsigned int nm_frame_nr; +}; + +struct nl_mmap_hdr { + unsigned int nm_status; + unsigned int nm_len; + __u32 nm_group; + /* credentials */ + __u32 nm_pid; + __u32 nm_uid; + __u32 nm_gid; +}; + +#ifndef __KERNEL__ +enum nl_mmap_status { + NL_MMAP_STATUS_UNUSED, + NL_MMAP_STATUS_RESERVED, + NL_MMAP_STATUS_VALID, + NL_MMAP_STATUS_COPY, + NL_MMAP_STATUS_SKIP, +}; + +#define NL_MMAP_MSG_ALIGNMENT NLMSG_ALIGNTO +#define NL_MMAP_MSG_ALIGN(sz) __ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT) +#define NL_MMAP_HDRLEN NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr)) +#endif + +#define NET_MAJOR 36 /* Major 36 is reserved for networking */ + +enum { + NETLINK_UNCONNECTED = 0, + NETLINK_CONNECTED, +}; + +/* + * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * | Header | Pad | Payload | Pad | + * | (struct nlattr) | ing | | ing | + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * <-------------- nlattr->nla_len --------------> + */ + +struct nlattr { + __u16 nla_len; + __u16 nla_type; +}; + +/* + * nla_type (16 bits) + * +---+---+-------------------------------+ + * | N | O | Attribute Type | + * +---+---+-------------------------------+ + * N := Carries nested attributes + * O := Payload stored in network byte order + * + * Note: The N and O flag are mutually exclusive. + */ +#define NLA_F_NESTED (1 << 15) +#define NLA_F_NET_BYTEORDER (1 << 14) +#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER) + +#define NLA_ALIGNTO 4 +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) + +/* Generic 32 bitflags attribute content sent to the kernel. + * + * The value is a bitmap that defines the values being set + * The selector is a bitmask that defines which value is legit + * + * Examples: + * value = 0x0, and selector = 0x1 + * implies we are selecting bit 1 and we want to set its value to 0. + * + * value = 0x2, and selector = 0x2 + * implies we are selecting bit 2 and we want to set its value to 1. + * + */ +struct nla_bitfield32 { + __u32 value; + __u32 selector; +}; + +#endif /* _UAPI__LINUX_NETLINK_H */ diff --git a/include/linux/nexthop.h b/include/linux/nexthop.h new file mode 100644 index 0000000..d8ffa8c --- /dev/null +++ b/include/linux/nexthop.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_NEXTHOP_H +#define _UAPI_LINUX_NEXTHOP_H + +#include + +struct nhmsg { + unsigned char nh_family; + unsigned char nh_scope; /* return only */ + unsigned char nh_protocol; /* Routing protocol that installed nh */ + unsigned char resvd; + unsigned int nh_flags; /* RTNH_F flags */ +}; + +/* entry in a nexthop group */ +struct nexthop_grp { + __u32 id; /* nexthop id - must exist */ + __u8 weight; /* weight of this nexthop */ + __u8 resvd1; + __u16 resvd2; +}; + +enum { + NEXTHOP_GRP_TYPE_MPATH, /* hash-threshold nexthop group + * default type if not specified + */ + NEXTHOP_GRP_TYPE_RES, /* resilient nexthop group */ + __NEXTHOP_GRP_TYPE_MAX, +}; + +#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1) + +enum { + NHA_UNSPEC, + NHA_ID, /* u32; id for nexthop. id == 0 means auto-assign */ + + NHA_GROUP, /* array of nexthop_grp */ + NHA_GROUP_TYPE, /* u16 one of NEXTHOP_GRP_TYPE */ + /* if NHA_GROUP attribute is added, no other attributes can be set */ + + NHA_BLACKHOLE, /* flag; nexthop used to blackhole packets */ + /* if NHA_BLACKHOLE is added, OIF, GATEWAY, ENCAP can not be set */ + + NHA_OIF, /* u32; nexthop device */ + NHA_GATEWAY, /* be32 (IPv4) or in6_addr (IPv6) gw address */ + NHA_ENCAP_TYPE, /* u16; lwt encap type */ + NHA_ENCAP, /* lwt encap data */ + + /* NHA_OIF can be appended to dump request to return only + * nexthops using given device + */ + NHA_GROUPS, /* flag; only return nexthop groups in dump */ + NHA_MASTER, /* u32; only return nexthops with given master dev */ + + NHA_FDB, /* flag; nexthop belongs to a bridge fdb */ + /* if NHA_FDB is added, OIF, BLACKHOLE, ENCAP cannot be set */ + + /* nested; resilient nexthop group attributes */ + NHA_RES_GROUP, + /* nested; nexthop bucket attributes */ + NHA_RES_BUCKET, + + __NHA_MAX, +}; + +#define NHA_MAX (__NHA_MAX - 1) + +enum { + NHA_RES_GROUP_UNSPEC, + /* Pad attribute for 64-bit alignment. */ + NHA_RES_GROUP_PAD = NHA_RES_GROUP_UNSPEC, + + /* u16; number of nexthop buckets in a resilient nexthop group */ + NHA_RES_GROUP_BUCKETS, + /* clock_t as u32; nexthop bucket idle timer (per-group) */ + NHA_RES_GROUP_IDLE_TIMER, + /* clock_t as u32; nexthop unbalanced timer */ + NHA_RES_GROUP_UNBALANCED_TIMER, + /* clock_t as u64; nexthop unbalanced time */ + NHA_RES_GROUP_UNBALANCED_TIME, + + __NHA_RES_GROUP_MAX, +}; + +#define NHA_RES_GROUP_MAX (__NHA_RES_GROUP_MAX - 1) + +enum { + NHA_RES_BUCKET_UNSPEC, + /* Pad attribute for 64-bit alignment. */ + NHA_RES_BUCKET_PAD = NHA_RES_BUCKET_UNSPEC, + + /* u16; nexthop bucket index */ + NHA_RES_BUCKET_INDEX, + /* clock_t as u64; nexthop bucket idle time */ + NHA_RES_BUCKET_IDLE_TIME, + /* u32; nexthop id assigned to the nexthop bucket */ + NHA_RES_BUCKET_NH_ID, + + __NHA_RES_BUCKET_MAX, +}; + +#define NHA_RES_BUCKET_MAX (__NHA_RES_BUCKET_MAX - 1) + +#endif diff --git a/include/linux/pkt_cls.h b/include/linux/pkt_cls.h new file mode 100644 index 0000000..7ea59cf --- /dev/null +++ b/include/linux/pkt_cls.h @@ -0,0 +1,776 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_PKT_CLS_H +#define __LINUX_PKT_CLS_H + +#include +#include + +#define TC_COOKIE_MAX_SIZE 16 + +/* Action attributes */ +enum { + TCA_ACT_UNSPEC, + TCA_ACT_KIND, + TCA_ACT_OPTIONS, + TCA_ACT_INDEX, + TCA_ACT_STATS, + TCA_ACT_PAD, + TCA_ACT_COOKIE, + TCA_ACT_FLAGS, + TCA_ACT_HW_STATS, + TCA_ACT_USED_HW_STATS, + __TCA_ACT_MAX +}; + +#define TCA_ACT_FLAGS_NO_PERCPU_STATS 1 /* Don't use percpu allocator for + * actions stats. + */ + +/* tca HW stats type + * When user does not pass the attribute, he does not care. + * It is the same as if he would pass the attribute with + * all supported bits set. + * In case no bits are set, user is not interested in getting any HW statistics. + */ +#define TCA_ACT_HW_STATS_IMMEDIATE (1 << 0) /* Means that in dump, user + * gets the current HW stats + * state from the device + * queried at the dump time. + */ +#define TCA_ACT_HW_STATS_DELAYED (1 << 1) /* Means that in dump, user gets + * HW stats that might be out of date + * for some time, maybe couple of + * seconds. This is the case when + * driver polls stats updates + * periodically or when it gets async + * stats update from the device. + */ + +#define TCA_ACT_MAX __TCA_ACT_MAX +#define TCA_OLD_COMPAT (TCA_ACT_MAX+1) +#define TCA_ACT_MAX_PRIO 32 +#define TCA_ACT_BIND 1 +#define TCA_ACT_NOBIND 0 +#define TCA_ACT_UNBIND 1 +#define TCA_ACT_NOUNBIND 0 +#define TCA_ACT_REPLACE 1 +#define TCA_ACT_NOREPLACE 0 + +#define TC_ACT_UNSPEC (-1) +#define TC_ACT_OK 0 +#define TC_ACT_RECLASSIFY 1 +#define TC_ACT_SHOT 2 +#define TC_ACT_PIPE 3 +#define TC_ACT_STOLEN 4 +#define TC_ACT_QUEUED 5 +#define TC_ACT_REPEAT 6 +#define TC_ACT_REDIRECT 7 +#define TC_ACT_TRAP 8 /* For hw path, this means "trap to cpu" + * and don't further process the frame + * in hardware. For sw path, this is + * equivalent of TC_ACT_STOLEN - drop + * the skb and act like everything + * is alright. + */ +#define TC_ACT_VALUE_MAX TC_ACT_TRAP + +/* There is a special kind of actions called "extended actions", + * which need a value parameter. These have a local opcode located in + * the highest nibble, starting from 1. The rest of the bits + * are used to carry the value. These two parts together make + * a combined opcode. + */ +#define __TC_ACT_EXT_SHIFT 28 +#define __TC_ACT_EXT(local) ((local) << __TC_ACT_EXT_SHIFT) +#define TC_ACT_EXT_VAL_MASK ((1 << __TC_ACT_EXT_SHIFT) - 1) +#define TC_ACT_EXT_OPCODE(combined) ((combined) & (~TC_ACT_EXT_VAL_MASK)) +#define TC_ACT_EXT_CMP(combined, opcode) (TC_ACT_EXT_OPCODE(combined) == opcode) + +#define TC_ACT_JUMP __TC_ACT_EXT(1) +#define TC_ACT_GOTO_CHAIN __TC_ACT_EXT(2) +#define TC_ACT_EXT_OPCODE_MAX TC_ACT_GOTO_CHAIN + +/* These macros are put here for binary compatibility with userspace apps that + * make use of them. For kernel code and new userspace apps, use the TCA_ID_* + * versions. + */ +#define TCA_ACT_GACT 5 +#define TCA_ACT_IPT 6 +#define TCA_ACT_PEDIT 7 +#define TCA_ACT_MIRRED 8 +#define TCA_ACT_NAT 9 +#define TCA_ACT_XT 10 +#define TCA_ACT_SKBEDIT 11 +#define TCA_ACT_VLAN 12 +#define TCA_ACT_BPF 13 +#define TCA_ACT_CONNMARK 14 +#define TCA_ACT_SKBMOD 15 +#define TCA_ACT_CSUM 16 +#define TCA_ACT_TUNNEL_KEY 17 +#define TCA_ACT_SIMP 22 +#define TCA_ACT_IFE 25 +#define TCA_ACT_SAMPLE 26 + +/* Action type identifiers*/ +enum tca_id { + TCA_ID_UNSPEC = 0, + TCA_ID_POLICE = 1, + TCA_ID_GACT = TCA_ACT_GACT, + TCA_ID_IPT = TCA_ACT_IPT, + TCA_ID_PEDIT = TCA_ACT_PEDIT, + TCA_ID_MIRRED = TCA_ACT_MIRRED, + TCA_ID_NAT = TCA_ACT_NAT, + TCA_ID_XT = TCA_ACT_XT, + TCA_ID_SKBEDIT = TCA_ACT_SKBEDIT, + TCA_ID_VLAN = TCA_ACT_VLAN, + TCA_ID_BPF = TCA_ACT_BPF, + TCA_ID_CONNMARK = TCA_ACT_CONNMARK, + TCA_ID_SKBMOD = TCA_ACT_SKBMOD, + TCA_ID_CSUM = TCA_ACT_CSUM, + TCA_ID_TUNNEL_KEY = TCA_ACT_TUNNEL_KEY, + TCA_ID_SIMP = TCA_ACT_SIMP, + TCA_ID_IFE = TCA_ACT_IFE, + TCA_ID_SAMPLE = TCA_ACT_SAMPLE, + TCA_ID_CTINFO, + TCA_ID_MPLS, + TCA_ID_CT, + TCA_ID_GATE, + /* other actions go here */ + __TCA_ID_MAX = 255 +}; + +#define TCA_ID_MAX __TCA_ID_MAX + +struct tc_police { + __u32 index; + int action; +#define TC_POLICE_UNSPEC TC_ACT_UNSPEC +#define TC_POLICE_OK TC_ACT_OK +#define TC_POLICE_RECLASSIFY TC_ACT_RECLASSIFY +#define TC_POLICE_SHOT TC_ACT_SHOT +#define TC_POLICE_PIPE TC_ACT_PIPE + + __u32 limit; + __u32 burst; + __u32 mtu; + struct tc_ratespec rate; + struct tc_ratespec peakrate; + int refcnt; + int bindcnt; + __u32 capab; +}; + +struct tcf_t { + __u64 install; + __u64 lastuse; + __u64 expires; + __u64 firstuse; +}; + +struct tc_cnt { + int refcnt; + int bindcnt; +}; + +#define tc_gen \ + __u32 index; \ + __u32 capab; \ + int action; \ + int refcnt; \ + int bindcnt + +enum { + TCA_POLICE_UNSPEC, + TCA_POLICE_TBF, + TCA_POLICE_RATE, + TCA_POLICE_PEAKRATE, + TCA_POLICE_AVRATE, + TCA_POLICE_RESULT, + TCA_POLICE_TM, + TCA_POLICE_PAD, + TCA_POLICE_RATE64, + TCA_POLICE_PEAKRATE64, + __TCA_POLICE_MAX +#define TCA_POLICE_RESULT TCA_POLICE_RESULT +}; + +#define TCA_POLICE_MAX (__TCA_POLICE_MAX - 1) + +/* tca flags definitions */ +#define TCA_CLS_FLAGS_SKIP_HW (1 << 0) /* don't offload filter to HW */ +#define TCA_CLS_FLAGS_SKIP_SW (1 << 1) /* don't use filter in SW */ +#define TCA_CLS_FLAGS_IN_HW (1 << 2) /* filter is offloaded to HW */ +#define TCA_CLS_FLAGS_NOT_IN_HW (1 << 3) /* filter isn't offloaded to HW */ +#define TCA_CLS_FLAGS_VERBOSE (1 << 4) /* verbose logging */ + +/* U32 filters */ + +#define TC_U32_HTID(h) ((h)&0xFFF00000) +#define TC_U32_USERHTID(h) (TC_U32_HTID(h)>>20) +#define TC_U32_HASH(h) (((h)>>12)&0xFF) +#define TC_U32_NODE(h) ((h)&0xFFF) +#define TC_U32_KEY(h) ((h)&0xFFFFF) +#define TC_U32_UNSPEC 0 +#define TC_U32_ROOT (0xFFF00000) + +enum { + TCA_U32_UNSPEC, + TCA_U32_CLASSID, + TCA_U32_HASH, + TCA_U32_LINK, + TCA_U32_DIVISOR, + TCA_U32_SEL, + TCA_U32_POLICE, + TCA_U32_ACT, + TCA_U32_INDEV, + TCA_U32_PCNT, + TCA_U32_MARK, + TCA_U32_FLAGS, + TCA_U32_PAD, + __TCA_U32_MAX +}; + +#define TCA_U32_MAX (__TCA_U32_MAX - 1) + +struct tc_u32_key { + __be32 mask; + __be32 val; + int off; + int offmask; +}; + +struct tc_u32_sel { + unsigned char flags; + unsigned char offshift; + unsigned char nkeys; + + __be16 offmask; + __u16 off; + short offoff; + + short hoff; + __be32 hmask; + struct tc_u32_key keys[0]; +}; + +struct tc_u32_mark { + __u32 val; + __u32 mask; + __u32 success; +}; + +struct tc_u32_pcnt { + __u64 rcnt; + __u64 rhit; + __u64 kcnts[0]; +}; + +/* Flags */ + +#define TC_U32_TERMINAL 1 +#define TC_U32_OFFSET 2 +#define TC_U32_VAROFFSET 4 +#define TC_U32_EAT 8 + +#define TC_U32_MAXDEPTH 8 + + +/* RSVP filter */ + +enum { + TCA_RSVP_UNSPEC, + TCA_RSVP_CLASSID, + TCA_RSVP_DST, + TCA_RSVP_SRC, + TCA_RSVP_PINFO, + TCA_RSVP_POLICE, + TCA_RSVP_ACT, + __TCA_RSVP_MAX +}; + +#define TCA_RSVP_MAX (__TCA_RSVP_MAX - 1 ) + +struct tc_rsvp_gpi { + __u32 key; + __u32 mask; + int offset; +}; + +struct tc_rsvp_pinfo { + struct tc_rsvp_gpi dpi; + struct tc_rsvp_gpi spi; + __u8 protocol; + __u8 tunnelid; + __u8 tunnelhdr; + __u8 pad; +}; + +/* ROUTE filter */ + +enum { + TCA_ROUTE4_UNSPEC, + TCA_ROUTE4_CLASSID, + TCA_ROUTE4_TO, + TCA_ROUTE4_FROM, + TCA_ROUTE4_IIF, + TCA_ROUTE4_POLICE, + TCA_ROUTE4_ACT, + __TCA_ROUTE4_MAX +}; + +#define TCA_ROUTE4_MAX (__TCA_ROUTE4_MAX - 1) + + +/* FW filter */ + +enum { + TCA_FW_UNSPEC, + TCA_FW_CLASSID, + TCA_FW_POLICE, + TCA_FW_INDEV, + TCA_FW_ACT, /* used by CONFIG_NET_CLS_ACT */ + TCA_FW_MASK, + __TCA_FW_MAX +}; + +#define TCA_FW_MAX (__TCA_FW_MAX - 1) + +/* TC index filter */ + +enum { + TCA_TCINDEX_UNSPEC, + TCA_TCINDEX_HASH, + TCA_TCINDEX_MASK, + TCA_TCINDEX_SHIFT, + TCA_TCINDEX_FALL_THROUGH, + TCA_TCINDEX_CLASSID, + TCA_TCINDEX_POLICE, + TCA_TCINDEX_ACT, + __TCA_TCINDEX_MAX +}; + +#define TCA_TCINDEX_MAX (__TCA_TCINDEX_MAX - 1) + +/* Flow filter */ + +enum { + FLOW_KEY_SRC, + FLOW_KEY_DST, + FLOW_KEY_PROTO, + FLOW_KEY_PROTO_SRC, + FLOW_KEY_PROTO_DST, + FLOW_KEY_IIF, + FLOW_KEY_PRIORITY, + FLOW_KEY_MARK, + FLOW_KEY_NFCT, + FLOW_KEY_NFCT_SRC, + FLOW_KEY_NFCT_DST, + FLOW_KEY_NFCT_PROTO_SRC, + FLOW_KEY_NFCT_PROTO_DST, + FLOW_KEY_RTCLASSID, + FLOW_KEY_SKUID, + FLOW_KEY_SKGID, + FLOW_KEY_VLAN_TAG, + FLOW_KEY_RXHASH, + __FLOW_KEY_MAX, +}; + +#define FLOW_KEY_MAX (__FLOW_KEY_MAX - 1) + +enum { + FLOW_MODE_MAP, + FLOW_MODE_HASH, +}; + +enum { + TCA_FLOW_UNSPEC, + TCA_FLOW_KEYS, + TCA_FLOW_MODE, + TCA_FLOW_BASECLASS, + TCA_FLOW_RSHIFT, + TCA_FLOW_ADDEND, + TCA_FLOW_MASK, + TCA_FLOW_XOR, + TCA_FLOW_DIVISOR, + TCA_FLOW_ACT, + TCA_FLOW_POLICE, + TCA_FLOW_EMATCHES, + TCA_FLOW_PERTURB, + __TCA_FLOW_MAX +}; + +#define TCA_FLOW_MAX (__TCA_FLOW_MAX - 1) + +/* Basic filter */ + +struct tc_basic_pcnt { + __u64 rcnt; + __u64 rhit; +}; + +enum { + TCA_BASIC_UNSPEC, + TCA_BASIC_CLASSID, + TCA_BASIC_EMATCHES, + TCA_BASIC_ACT, + TCA_BASIC_POLICE, + TCA_BASIC_PCNT, + TCA_BASIC_PAD, + __TCA_BASIC_MAX +}; + +#define TCA_BASIC_MAX (__TCA_BASIC_MAX - 1) + + +/* Cgroup classifier */ + +enum { + TCA_CGROUP_UNSPEC, + TCA_CGROUP_ACT, + TCA_CGROUP_POLICE, + TCA_CGROUP_EMATCHES, + __TCA_CGROUP_MAX, +}; + +#define TCA_CGROUP_MAX (__TCA_CGROUP_MAX - 1) + +/* BPF classifier */ + +#define TCA_BPF_FLAG_ACT_DIRECT (1 << 0) + +enum { + TCA_BPF_UNSPEC, + TCA_BPF_ACT, + TCA_BPF_POLICE, + TCA_BPF_CLASSID, + TCA_BPF_OPS_LEN, + TCA_BPF_OPS, + TCA_BPF_FD, + TCA_BPF_NAME, + TCA_BPF_FLAGS, + TCA_BPF_FLAGS_GEN, + TCA_BPF_TAG, + TCA_BPF_ID, + __TCA_BPF_MAX, +}; + +#define TCA_BPF_MAX (__TCA_BPF_MAX - 1) + +/* Flower classifier */ + +enum { + TCA_FLOWER_UNSPEC, + TCA_FLOWER_CLASSID, + TCA_FLOWER_INDEV, + TCA_FLOWER_ACT, + TCA_FLOWER_KEY_ETH_DST, /* ETH_ALEN */ + TCA_FLOWER_KEY_ETH_DST_MASK, /* ETH_ALEN */ + TCA_FLOWER_KEY_ETH_SRC, /* ETH_ALEN */ + TCA_FLOWER_KEY_ETH_SRC_MASK, /* ETH_ALEN */ + TCA_FLOWER_KEY_ETH_TYPE, /* be16 */ + TCA_FLOWER_KEY_IP_PROTO, /* u8 */ + TCA_FLOWER_KEY_IPV4_SRC, /* be32 */ + TCA_FLOWER_KEY_IPV4_SRC_MASK, /* be32 */ + TCA_FLOWER_KEY_IPV4_DST, /* be32 */ + TCA_FLOWER_KEY_IPV4_DST_MASK, /* be32 */ + TCA_FLOWER_KEY_IPV6_SRC, /* struct in6_addr */ + TCA_FLOWER_KEY_IPV6_SRC_MASK, /* struct in6_addr */ + TCA_FLOWER_KEY_IPV6_DST, /* struct in6_addr */ + TCA_FLOWER_KEY_IPV6_DST_MASK, /* struct in6_addr */ + TCA_FLOWER_KEY_TCP_SRC, /* be16 */ + TCA_FLOWER_KEY_TCP_DST, /* be16 */ + TCA_FLOWER_KEY_UDP_SRC, /* be16 */ + TCA_FLOWER_KEY_UDP_DST, /* be16 */ + + TCA_FLOWER_FLAGS, + TCA_FLOWER_KEY_VLAN_ID, /* be16 */ + TCA_FLOWER_KEY_VLAN_PRIO, /* u8 */ + TCA_FLOWER_KEY_VLAN_ETH_TYPE, /* be16 */ + + TCA_FLOWER_KEY_ENC_KEY_ID, /* be32 */ + TCA_FLOWER_KEY_ENC_IPV4_SRC, /* be32 */ + TCA_FLOWER_KEY_ENC_IPV4_SRC_MASK,/* be32 */ + TCA_FLOWER_KEY_ENC_IPV4_DST, /* be32 */ + TCA_FLOWER_KEY_ENC_IPV4_DST_MASK,/* be32 */ + TCA_FLOWER_KEY_ENC_IPV6_SRC, /* struct in6_addr */ + TCA_FLOWER_KEY_ENC_IPV6_SRC_MASK,/* struct in6_addr */ + TCA_FLOWER_KEY_ENC_IPV6_DST, /* struct in6_addr */ + TCA_FLOWER_KEY_ENC_IPV6_DST_MASK,/* struct in6_addr */ + + TCA_FLOWER_KEY_TCP_SRC_MASK, /* be16 */ + TCA_FLOWER_KEY_TCP_DST_MASK, /* be16 */ + TCA_FLOWER_KEY_UDP_SRC_MASK, /* be16 */ + TCA_FLOWER_KEY_UDP_DST_MASK, /* be16 */ + TCA_FLOWER_KEY_SCTP_SRC_MASK, /* be16 */ + TCA_FLOWER_KEY_SCTP_DST_MASK, /* be16 */ + + TCA_FLOWER_KEY_SCTP_SRC, /* be16 */ + TCA_FLOWER_KEY_SCTP_DST, /* be16 */ + + TCA_FLOWER_KEY_ENC_UDP_SRC_PORT, /* be16 */ + TCA_FLOWER_KEY_ENC_UDP_SRC_PORT_MASK, /* be16 */ + TCA_FLOWER_KEY_ENC_UDP_DST_PORT, /* be16 */ + TCA_FLOWER_KEY_ENC_UDP_DST_PORT_MASK, /* be16 */ + + TCA_FLOWER_KEY_FLAGS, /* be32 */ + TCA_FLOWER_KEY_FLAGS_MASK, /* be32 */ + + TCA_FLOWER_KEY_ICMPV4_CODE, /* u8 */ + TCA_FLOWER_KEY_ICMPV4_CODE_MASK,/* u8 */ + TCA_FLOWER_KEY_ICMPV4_TYPE, /* u8 */ + TCA_FLOWER_KEY_ICMPV4_TYPE_MASK,/* u8 */ + TCA_FLOWER_KEY_ICMPV6_CODE, /* u8 */ + TCA_FLOWER_KEY_ICMPV6_CODE_MASK,/* u8 */ + TCA_FLOWER_KEY_ICMPV6_TYPE, /* u8 */ + TCA_FLOWER_KEY_ICMPV6_TYPE_MASK,/* u8 */ + + TCA_FLOWER_KEY_ARP_SIP, /* be32 */ + TCA_FLOWER_KEY_ARP_SIP_MASK, /* be32 */ + TCA_FLOWER_KEY_ARP_TIP, /* be32 */ + TCA_FLOWER_KEY_ARP_TIP_MASK, /* be32 */ + TCA_FLOWER_KEY_ARP_OP, /* u8 */ + TCA_FLOWER_KEY_ARP_OP_MASK, /* u8 */ + TCA_FLOWER_KEY_ARP_SHA, /* ETH_ALEN */ + TCA_FLOWER_KEY_ARP_SHA_MASK, /* ETH_ALEN */ + TCA_FLOWER_KEY_ARP_THA, /* ETH_ALEN */ + TCA_FLOWER_KEY_ARP_THA_MASK, /* ETH_ALEN */ + + TCA_FLOWER_KEY_MPLS_TTL, /* u8 - 8 bits */ + TCA_FLOWER_KEY_MPLS_BOS, /* u8 - 1 bit */ + TCA_FLOWER_KEY_MPLS_TC, /* u8 - 3 bits */ + TCA_FLOWER_KEY_MPLS_LABEL, /* be32 - 20 bits */ + + TCA_FLOWER_KEY_TCP_FLAGS, /* be16 */ + TCA_FLOWER_KEY_TCP_FLAGS_MASK, /* be16 */ + + TCA_FLOWER_KEY_IP_TOS, /* u8 */ + TCA_FLOWER_KEY_IP_TOS_MASK, /* u8 */ + TCA_FLOWER_KEY_IP_TTL, /* u8 */ + TCA_FLOWER_KEY_IP_TTL_MASK, /* u8 */ + + TCA_FLOWER_KEY_CVLAN_ID, /* be16 */ + TCA_FLOWER_KEY_CVLAN_PRIO, /* u8 */ + TCA_FLOWER_KEY_CVLAN_ETH_TYPE, /* be16 */ + + TCA_FLOWER_KEY_ENC_IP_TOS, /* u8 */ + TCA_FLOWER_KEY_ENC_IP_TOS_MASK, /* u8 */ + TCA_FLOWER_KEY_ENC_IP_TTL, /* u8 */ + TCA_FLOWER_KEY_ENC_IP_TTL_MASK, /* u8 */ + + TCA_FLOWER_KEY_ENC_OPTS, + TCA_FLOWER_KEY_ENC_OPTS_MASK, + + TCA_FLOWER_IN_HW_COUNT, + + TCA_FLOWER_KEY_PORT_SRC_MIN, /* be16 */ + TCA_FLOWER_KEY_PORT_SRC_MAX, /* be16 */ + TCA_FLOWER_KEY_PORT_DST_MIN, /* be16 */ + TCA_FLOWER_KEY_PORT_DST_MAX, /* be16 */ + + TCA_FLOWER_KEY_CT_STATE, /* u16 */ + TCA_FLOWER_KEY_CT_STATE_MASK, /* u16 */ + TCA_FLOWER_KEY_CT_ZONE, /* u16 */ + TCA_FLOWER_KEY_CT_ZONE_MASK, /* u16 */ + TCA_FLOWER_KEY_CT_MARK, /* u32 */ + TCA_FLOWER_KEY_CT_MARK_MASK, /* u32 */ + TCA_FLOWER_KEY_CT_LABELS, /* u128 */ + TCA_FLOWER_KEY_CT_LABELS_MASK, /* u128 */ + + TCA_FLOWER_KEY_MPLS_OPTS, + + TCA_FLOWER_KEY_HASH, /* u32 */ + TCA_FLOWER_KEY_HASH_MASK, /* u32 */ + + __TCA_FLOWER_MAX, +}; + +#define TCA_FLOWER_MAX (__TCA_FLOWER_MAX - 1) + +enum { + TCA_FLOWER_KEY_CT_FLAGS_NEW = 1 << 0, /* Beginning of a new connection. */ + TCA_FLOWER_KEY_CT_FLAGS_ESTABLISHED = 1 << 1, /* Part of an existing connection. */ + TCA_FLOWER_KEY_CT_FLAGS_RELATED = 1 << 2, /* Related to an established connection. */ + TCA_FLOWER_KEY_CT_FLAGS_TRACKED = 1 << 3, /* Conntrack has occurred. */ + TCA_FLOWER_KEY_CT_FLAGS_INVALID = 1 << 4, /* Conntrack is invalid. */ + TCA_FLOWER_KEY_CT_FLAGS_REPLY = 1 << 5, /* Packet is in the reply direction. */ + __TCA_FLOWER_KEY_CT_FLAGS_MAX, +}; + +enum { + TCA_FLOWER_KEY_ENC_OPTS_UNSPEC, + TCA_FLOWER_KEY_ENC_OPTS_GENEVE, /* Nested + * TCA_FLOWER_KEY_ENC_OPT_GENEVE_ + * attributes + */ + TCA_FLOWER_KEY_ENC_OPTS_VXLAN, /* Nested + * TCA_FLOWER_KEY_ENC_OPT_VXLAN_ + * attributes + */ + TCA_FLOWER_KEY_ENC_OPTS_ERSPAN, /* Nested + * TCA_FLOWER_KEY_ENC_OPT_ERSPAN_ + * attributes + */ + __TCA_FLOWER_KEY_ENC_OPTS_MAX, +}; + +#define TCA_FLOWER_KEY_ENC_OPTS_MAX (__TCA_FLOWER_KEY_ENC_OPTS_MAX - 1) + +enum { + TCA_FLOWER_KEY_ENC_OPT_GENEVE_UNSPEC, + TCA_FLOWER_KEY_ENC_OPT_GENEVE_CLASS, /* u16 */ + TCA_FLOWER_KEY_ENC_OPT_GENEVE_TYPE, /* u8 */ + TCA_FLOWER_KEY_ENC_OPT_GENEVE_DATA, /* 4 to 128 bytes */ + + __TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX, +}; + +#define TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX \ + (__TCA_FLOWER_KEY_ENC_OPT_GENEVE_MAX - 1) + +enum { + TCA_FLOWER_KEY_ENC_OPT_VXLAN_UNSPEC, + TCA_FLOWER_KEY_ENC_OPT_VXLAN_GBP, /* u32 */ + __TCA_FLOWER_KEY_ENC_OPT_VXLAN_MAX, +}; + +#define TCA_FLOWER_KEY_ENC_OPT_VXLAN_MAX \ + (__TCA_FLOWER_KEY_ENC_OPT_VXLAN_MAX - 1) + +enum { + TCA_FLOWER_KEY_ENC_OPT_ERSPAN_UNSPEC, + TCA_FLOWER_KEY_ENC_OPT_ERSPAN_VER, /* u8 */ + TCA_FLOWER_KEY_ENC_OPT_ERSPAN_INDEX, /* be32 */ + TCA_FLOWER_KEY_ENC_OPT_ERSPAN_DIR, /* u8 */ + TCA_FLOWER_KEY_ENC_OPT_ERSPAN_HWID, /* u8 */ + __TCA_FLOWER_KEY_ENC_OPT_ERSPAN_MAX, +}; + +#define TCA_FLOWER_KEY_ENC_OPT_ERSPAN_MAX \ + (__TCA_FLOWER_KEY_ENC_OPT_ERSPAN_MAX - 1) + +enum { + TCA_FLOWER_KEY_MPLS_OPTS_UNSPEC, + TCA_FLOWER_KEY_MPLS_OPTS_LSE, + __TCA_FLOWER_KEY_MPLS_OPTS_MAX, +}; + +#define TCA_FLOWER_KEY_MPLS_OPTS_MAX (__TCA_FLOWER_KEY_MPLS_OPTS_MAX - 1) + +enum { + TCA_FLOWER_KEY_MPLS_OPT_LSE_UNSPEC, + TCA_FLOWER_KEY_MPLS_OPT_LSE_DEPTH, + TCA_FLOWER_KEY_MPLS_OPT_LSE_TTL, + TCA_FLOWER_KEY_MPLS_OPT_LSE_BOS, + TCA_FLOWER_KEY_MPLS_OPT_LSE_TC, + TCA_FLOWER_KEY_MPLS_OPT_LSE_LABEL, + __TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX, +}; + +#define TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX \ + (__TCA_FLOWER_KEY_MPLS_OPT_LSE_MAX - 1) + +enum { + TCA_FLOWER_KEY_FLAGS_IS_FRAGMENT = (1 << 0), + TCA_FLOWER_KEY_FLAGS_FRAG_IS_FIRST = (1 << 1), +}; + +#define TCA_FLOWER_MASK_FLAGS_RANGE (1 << 0) /* Range-based match */ + +/* Match-all classifier */ + +struct tc_matchall_pcnt { + __u64 rhit; +}; + +enum { + TCA_MATCHALL_UNSPEC, + TCA_MATCHALL_CLASSID, + TCA_MATCHALL_ACT, + TCA_MATCHALL_FLAGS, + TCA_MATCHALL_PCNT, + TCA_MATCHALL_PAD, + __TCA_MATCHALL_MAX, +}; + +#define TCA_MATCHALL_MAX (__TCA_MATCHALL_MAX - 1) + +/* Extended Matches */ + +struct tcf_ematch_tree_hdr { + __u16 nmatches; + __u16 progid; +}; + +enum { + TCA_EMATCH_TREE_UNSPEC, + TCA_EMATCH_TREE_HDR, + TCA_EMATCH_TREE_LIST, + __TCA_EMATCH_TREE_MAX +}; +#define TCA_EMATCH_TREE_MAX (__TCA_EMATCH_TREE_MAX - 1) + +struct tcf_ematch_hdr { + __u16 matchid; + __u16 kind; + __u16 flags; + __u16 pad; /* currently unused */ +}; + +/* 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-----------------------+-+-+---+ + * | Unused |S|I| R | + * +-----------------------+-+-+---+ + * + * R(2) ::= relation to next ematch + * where: 0 0 END (last ematch) + * 0 1 AND + * 1 0 OR + * 1 1 Unused (invalid) + * I(1) ::= invert result + * S(1) ::= simple payload + */ +#define TCF_EM_REL_END 0 +#define TCF_EM_REL_AND (1<<0) +#define TCF_EM_REL_OR (1<<1) +#define TCF_EM_INVERT (1<<2) +#define TCF_EM_SIMPLE (1<<3) + +#define TCF_EM_REL_MASK 3 +#define TCF_EM_REL_VALID(v) (((v) & TCF_EM_REL_MASK) != TCF_EM_REL_MASK) + +enum { + TCF_LAYER_LINK, + TCF_LAYER_NETWORK, + TCF_LAYER_TRANSPORT, + __TCF_LAYER_MAX +}; +#define TCF_LAYER_MAX (__TCF_LAYER_MAX - 1) + +/* Ematch type assignments + * 1..32767 Reserved for ematches inside kernel tree + * 32768..65535 Free to use, not reliable + */ +#define TCF_EM_CONTAINER 0 +#define TCF_EM_CMP 1 +#define TCF_EM_NBYTE 2 +#define TCF_EM_U32 3 +#define TCF_EM_META 4 +#define TCF_EM_TEXT 5 +#define TCF_EM_VLAN 6 +#define TCF_EM_CANID 7 +#define TCF_EM_IPSET 8 +#define TCF_EM_IPT 9 +#define TCF_EM_MAX 9 + +enum { + TCF_EM_PROG_TC +}; + +enum { + TCF_EM_OPND_EQ, + TCF_EM_OPND_GT, + TCF_EM_OPND_LT +}; + +#endif diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h new file mode 100644 index 0000000..d03ed4d --- /dev/null +++ b/include/linux/rtnetlink.h @@ -0,0 +1,824 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI__LINUX_RTNETLINK_H +#define _UAPI__LINUX_RTNETLINK_H + +#include +#include +#include +#include +#include + +/* rtnetlink families. Values up to 127 are reserved for real address + * families, values above 128 may be used arbitrarily. + */ +#define RTNL_FAMILY_IPMR 128 +#define RTNL_FAMILY_IP6MR 129 +#define RTNL_FAMILY_MAX 129 + +/**** + * Routing/neighbour discovery messages. + ****/ + +/* Types of messages */ + +enum { + RTM_BASE = 16, +#define RTM_BASE RTM_BASE + + RTM_NEWLINK = 16, +#define RTM_NEWLINK RTM_NEWLINK + RTM_DELLINK, +#define RTM_DELLINK RTM_DELLINK + RTM_GETLINK, +#define RTM_GETLINK RTM_GETLINK + RTM_SETLINK, +#define RTM_SETLINK RTM_SETLINK + + RTM_NEWADDR = 20, +#define RTM_NEWADDR RTM_NEWADDR + RTM_DELADDR, +#define RTM_DELADDR RTM_DELADDR + RTM_GETADDR, +#define RTM_GETADDR RTM_GETADDR + + RTM_NEWROUTE = 24, +#define RTM_NEWROUTE RTM_NEWROUTE + RTM_DELROUTE, +#define RTM_DELROUTE RTM_DELROUTE + RTM_GETROUTE, +#define RTM_GETROUTE RTM_GETROUTE + + RTM_NEWNEIGH = 28, +#define RTM_NEWNEIGH RTM_NEWNEIGH + RTM_DELNEIGH, +#define RTM_DELNEIGH RTM_DELNEIGH + RTM_GETNEIGH, +#define RTM_GETNEIGH RTM_GETNEIGH + + RTM_NEWRULE = 32, +#define RTM_NEWRULE RTM_NEWRULE + RTM_DELRULE, +#define RTM_DELRULE RTM_DELRULE + RTM_GETRULE, +#define RTM_GETRULE RTM_GETRULE + + RTM_NEWQDISC = 36, +#define RTM_NEWQDISC RTM_NEWQDISC + RTM_DELQDISC, +#define RTM_DELQDISC RTM_DELQDISC + RTM_GETQDISC, +#define RTM_GETQDISC RTM_GETQDISC + + RTM_NEWTCLASS = 40, +#define RTM_NEWTCLASS RTM_NEWTCLASS + RTM_DELTCLASS, +#define RTM_DELTCLASS RTM_DELTCLASS + RTM_GETTCLASS, +#define RTM_GETTCLASS RTM_GETTCLASS + + RTM_NEWTFILTER = 44, +#define RTM_NEWTFILTER RTM_NEWTFILTER + RTM_DELTFILTER, +#define RTM_DELTFILTER RTM_DELTFILTER + RTM_GETTFILTER, +#define RTM_GETTFILTER RTM_GETTFILTER + + RTM_NEWACTION = 48, +#define RTM_NEWACTION RTM_NEWACTION + RTM_DELACTION, +#define RTM_DELACTION RTM_DELACTION + RTM_GETACTION, +#define RTM_GETACTION RTM_GETACTION + + RTM_NEWPREFIX = 52, +#define RTM_NEWPREFIX RTM_NEWPREFIX + + RTM_GETMULTICAST = 58, +#define RTM_GETMULTICAST RTM_GETMULTICAST + + RTM_GETANYCAST = 62, +#define RTM_GETANYCAST RTM_GETANYCAST + + RTM_NEWNEIGHTBL = 64, +#define RTM_NEWNEIGHTBL RTM_NEWNEIGHTBL + RTM_GETNEIGHTBL = 66, +#define RTM_GETNEIGHTBL RTM_GETNEIGHTBL + RTM_SETNEIGHTBL, +#define RTM_SETNEIGHTBL RTM_SETNEIGHTBL + + RTM_NEWNDUSEROPT = 68, +#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT + + RTM_NEWADDRLABEL = 72, +#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL + RTM_DELADDRLABEL, +#define RTM_DELADDRLABEL RTM_DELADDRLABEL + RTM_GETADDRLABEL, +#define RTM_GETADDRLABEL RTM_GETADDRLABEL + + RTM_GETDCB = 78, +#define RTM_GETDCB RTM_GETDCB + RTM_SETDCB, +#define RTM_SETDCB RTM_SETDCB + + RTM_NEWNETCONF = 80, +#define RTM_NEWNETCONF RTM_NEWNETCONF + RTM_DELNETCONF, +#define RTM_DELNETCONF RTM_DELNETCONF + RTM_GETNETCONF = 82, +#define RTM_GETNETCONF RTM_GETNETCONF + + RTM_NEWMDB = 84, +#define RTM_NEWMDB RTM_NEWMDB + RTM_DELMDB = 85, +#define RTM_DELMDB RTM_DELMDB + RTM_GETMDB = 86, +#define RTM_GETMDB RTM_GETMDB + + RTM_NEWNSID = 88, +#define RTM_NEWNSID RTM_NEWNSID + RTM_DELNSID = 89, +#define RTM_DELNSID RTM_DELNSID + RTM_GETNSID = 90, +#define RTM_GETNSID RTM_GETNSID + + RTM_NEWSTATS = 92, +#define RTM_NEWSTATS RTM_NEWSTATS + RTM_GETSTATS = 94, +#define RTM_GETSTATS RTM_GETSTATS + + RTM_NEWCACHEREPORT = 96, +#define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT + + RTM_NEWCHAIN = 100, +#define RTM_NEWCHAIN RTM_NEWCHAIN + RTM_DELCHAIN, +#define RTM_DELCHAIN RTM_DELCHAIN + RTM_GETCHAIN, +#define RTM_GETCHAIN RTM_GETCHAIN + + RTM_NEWNEXTHOP = 104, +#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP + RTM_DELNEXTHOP, +#define RTM_DELNEXTHOP RTM_DELNEXTHOP + RTM_GETNEXTHOP, +#define RTM_GETNEXTHOP RTM_GETNEXTHOP + + RTM_NEWLINKPROP = 108, +#define RTM_NEWLINKPROP RTM_NEWLINKPROP + RTM_DELLINKPROP, +#define RTM_DELLINKPROP RTM_DELLINKPROP + RTM_GETLINKPROP, +#define RTM_GETLINKPROP RTM_GETLINKPROP + + RTM_NEWVLAN = 112, +#define RTM_NEWNVLAN RTM_NEWVLAN + RTM_DELVLAN, +#define RTM_DELVLAN RTM_DELVLAN + RTM_GETVLAN, +#define RTM_GETVLAN RTM_GETVLAN + + RTM_NEWNEXTHOPBUCKET = 116, +#define RTM_NEWNEXTHOPBUCKET RTM_NEWNEXTHOPBUCKET + RTM_DELNEXTHOPBUCKET, +#define RTM_DELNEXTHOPBUCKET RTM_DELNEXTHOPBUCKET + RTM_GETNEXTHOPBUCKET, +#define RTM_GETNEXTHOPBUCKET RTM_GETNEXTHOPBUCKET + + RTM_NEWTUNNEL = 120, +#define RTM_NEWTUNNEL RTM_NEWTUNNEL + RTM_DELTUNNEL, +#define RTM_DELTUNNEL RTM_DELTUNNEL + RTM_GETTUNNEL, +#define RTM_GETTUNNEL RTM_GETTUNNEL + + __RTM_MAX, +#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) +}; + +#define RTM_NR_MSGTYPES (RTM_MAX + 1 - RTM_BASE) +#define RTM_NR_FAMILIES (RTM_NR_MSGTYPES >> 2) +#define RTM_FAM(cmd) (((cmd) - RTM_BASE) >> 2) + +/* + Generic structure for encapsulation of optional route information. + It is reminiscent of sockaddr, but with sa_family replaced + with attribute type. + */ + +struct rtattr { + unsigned short rta_len; + unsigned short rta_type; +}; + +/* Macros to handle rtattributes */ + +#define RTA_ALIGNTO 4U +#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) ) +#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \ + (rta)->rta_len >= sizeof(struct rtattr) && \ + (rta)->rta_len <= (len)) +#define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ + (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len))) +#define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len)) +#define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len)) +#define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0))) +#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0)) +#ifndef TUNNEL_RTA +#define TUNNEL_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + \ + NLMSG_ALIGN(sizeof(struct tunnel_msg)))) +#endif + + + + +/****************************************************************************** + * Definitions used in routing table administration. + ****/ + +struct rtmsg { + unsigned char rtm_family; + unsigned char rtm_dst_len; + unsigned char rtm_src_len; + unsigned char rtm_tos; + + unsigned char rtm_table; /* Routing table id */ + unsigned char rtm_protocol; /* Routing protocol; see below */ + unsigned char rtm_scope; /* See below */ + unsigned char rtm_type; /* See below */ + + unsigned rtm_flags; +}; + +/* rtm_type */ + +enum { + RTN_UNSPEC, + RTN_UNICAST, /* Gateway or direct route */ + RTN_LOCAL, /* Accept locally */ + RTN_BROADCAST, /* Accept locally as broadcast, + send as broadcast */ + RTN_ANYCAST, /* Accept locally as broadcast, + but send as unicast */ + RTN_MULTICAST, /* Multicast route */ + RTN_BLACKHOLE, /* Drop */ + RTN_UNREACHABLE, /* Destination is unreachable */ + RTN_PROHIBIT, /* Administratively prohibited */ + RTN_THROW, /* Not in this table */ + RTN_NAT, /* Translate this address */ + RTN_XRESOLVE, /* Use external resolver */ + __RTN_MAX +}; + +#define RTN_MAX (__RTN_MAX - 1) + + +/* rtm_protocol */ + +#define RTPROT_UNSPEC 0 +#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; + not used by current IPv4 */ +#define RTPROT_KERNEL 2 /* Route installed by kernel */ +#define RTPROT_BOOT 3 /* Route installed during boot */ +#define RTPROT_STATIC 4 /* Route installed by administrator */ + +/* Values of protocol >= RTPROT_STATIC are not interpreted by kernel; + they are just passed from user and back as is. + It will be used by hypothetical multiple routing daemons. + Note that protocol values should be standardized in order to + avoid conflicts. + */ + +#define RTPROT_GATED 8 /* Apparently, GateD */ +#define RTPROT_RA 9 /* RDISC/ND router advertisements */ +#define RTPROT_MRT 10 /* Merit MRT */ +#define RTPROT_ZEBRA 11 /* Zebra */ +#define RTPROT_BIRD 12 /* BIRD */ +#define RTPROT_DNROUTED 13 /* DECnet routing daemon */ +#define RTPROT_XORP 14 /* XORP */ +#define RTPROT_NTK 15 /* Netsukuku */ +#define RTPROT_DHCP 16 /* DHCP client */ +#define RTPROT_MROUTED 17 /* Multicast daemon */ +#define RTPROT_KEEPALIVED 18 /* Keepalived daemon */ +#define RTPROT_BABEL 42 /* Babel daemon */ +#define RTPROT_OPENR 99 /* Open Routing (Open/R) Routes */ +#define RTPROT_BGP 186 /* BGP Routes */ +#define RTPROT_ISIS 187 /* ISIS Routes */ +#define RTPROT_OSPF 188 /* OSPF Routes */ +#define RTPROT_RIP 189 /* RIP Routes */ +#define RTPROT_EIGRP 192 /* EIGRP Routes */ + +/* rtm_scope + + Really it is not scope, but sort of distance to the destination. + NOWHERE are reserved for not existing destinations, HOST is our + local addresses, LINK are destinations, located on directly attached + link and UNIVERSE is everywhere in the Universe. + + Intermediate values are also possible f.e. interior routes + could be assigned a value between UNIVERSE and LINK. +*/ + +enum rt_scope_t { + RT_SCOPE_UNIVERSE=0, +/* User defined values */ + RT_SCOPE_SITE=200, + RT_SCOPE_LINK=253, + RT_SCOPE_HOST=254, + RT_SCOPE_NOWHERE=255 +}; + +/* rtm_flags */ + +#define RTM_F_NOTIFY 0x100 /* Notify user of route change */ +#define RTM_F_CLONED 0x200 /* This route is cloned */ +#define RTM_F_EQUALIZE 0x400 /* Multipath equalizer: NI */ +#define RTM_F_PREFIX 0x800 /* Prefix addresses */ +#define RTM_F_LOOKUP_TABLE 0x1000 /* set rtm_table to FIB lookup result */ +#define RTM_F_FIB_MATCH 0x2000 /* return full fib lookup match */ +#define RTM_F_OFFLOAD 0x4000 /* route is offloaded */ +#define RTM_F_TRAP 0x8000 /* route is trapping packets */ +#define RTM_F_OFFLOAD_FAILED 0x20000000 /* route offload failed, this value + * is chosen to avoid conflicts with + * other flags defined in + * include/uapi/linux/ipv6_route.h + */ + +/* Reserved table identifiers */ + +enum rt_class_t { + RT_TABLE_UNSPEC=0, +/* User defined values */ + RT_TABLE_COMPAT=252, + RT_TABLE_DEFAULT=253, + RT_TABLE_MAIN=254, + RT_TABLE_LOCAL=255, + RT_TABLE_MAX=0xFFFFFFFF +}; + + +/* Routing message attributes */ + +enum rtattr_type_t { + RTA_UNSPEC, + RTA_DST, + RTA_SRC, + RTA_IIF, + RTA_OIF, + RTA_GATEWAY, + RTA_PRIORITY, + RTA_PREFSRC, + RTA_METRICS, + RTA_MULTIPATH, + RTA_PROTOINFO, /* no longer used */ + RTA_FLOW, + RTA_CACHEINFO, + RTA_SESSION, /* no longer used */ + RTA_MP_ALGO, /* no longer used */ + RTA_TABLE, + RTA_MARK, + RTA_MFC_STATS, + RTA_VIA, + RTA_NEWDST, + RTA_PREF, + RTA_ENCAP_TYPE, + RTA_ENCAP, + RTA_EXPIRES, + RTA_PAD, + RTA_UID, + RTA_TTL_PROPAGATE, + RTA_IP_PROTO, + RTA_SPORT, + RTA_DPORT, + RTA_NH_ID, + __RTA_MAX +}; + +#define RTA_MAX (__RTA_MAX - 1) + +#define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) +#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) + +/* RTM_MULTIPATH --- array of struct rtnexthop. + * + * "struct rtnexthop" describes all necessary nexthop information, + * i.e. parameters of path to a destination via this nexthop. + * + * At the moment it is impossible to set different prefsrc, mtu, window + * and rtt for different paths from multipath. + */ + +struct rtnexthop { + unsigned short rtnh_len; + unsigned char rtnh_flags; + unsigned char rtnh_hops; + int rtnh_ifindex; +}; + +/* rtnh_flags */ + +#define RTNH_F_DEAD 1 /* Nexthop is dead (used by multipath) */ +#define RTNH_F_PERVASIVE 2 /* Do recursive gateway lookup */ +#define RTNH_F_ONLINK 4 /* Gateway is forced on link */ +#define RTNH_F_OFFLOAD 8 /* Nexthop is offloaded */ +#define RTNH_F_LINKDOWN 16 /* carrier-down on nexthop */ +#define RTNH_F_UNRESOLVED 32 /* The entry is unresolved (ipmr) */ +#define RTNH_F_TRAP 64 /* Nexthop is trapping packets */ + +#define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | \ + RTNH_F_OFFLOAD | RTNH_F_TRAP) + +/* Macros to handle hexthops */ + +#define RTNH_ALIGNTO 4 +#define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) ) +#define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \ + ((int)(rtnh)->rtnh_len) <= (len)) +#define RTNH_NEXT(rtnh) ((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len))) +#define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len)) +#define RTNH_SPACE(len) RTNH_ALIGN(RTNH_LENGTH(len)) +#define RTNH_DATA(rtnh) ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0))) + +/* RTA_VIA */ +struct rtvia { + __kernel_sa_family_t rtvia_family; + __u8 rtvia_addr[0]; +}; + +/* RTM_CACHEINFO */ + +struct rta_cacheinfo { + __u32 rta_clntref; + __u32 rta_lastuse; + __s32 rta_expires; + __u32 rta_error; + __u32 rta_used; + +#define RTNETLINK_HAVE_PEERINFO 1 + __u32 rta_id; + __u32 rta_ts; + __u32 rta_tsage; +}; + +/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ + +enum { + RTAX_UNSPEC, +#define RTAX_UNSPEC RTAX_UNSPEC + RTAX_LOCK, +#define RTAX_LOCK RTAX_LOCK + RTAX_MTU, +#define RTAX_MTU RTAX_MTU + RTAX_WINDOW, +#define RTAX_WINDOW RTAX_WINDOW + RTAX_RTT, +#define RTAX_RTT RTAX_RTT + RTAX_RTTVAR, +#define RTAX_RTTVAR RTAX_RTTVAR + RTAX_SSTHRESH, +#define RTAX_SSTHRESH RTAX_SSTHRESH + RTAX_CWND, +#define RTAX_CWND RTAX_CWND + RTAX_ADVMSS, +#define RTAX_ADVMSS RTAX_ADVMSS + RTAX_REORDERING, +#define RTAX_REORDERING RTAX_REORDERING + RTAX_HOPLIMIT, +#define RTAX_HOPLIMIT RTAX_HOPLIMIT + RTAX_INITCWND, +#define RTAX_INITCWND RTAX_INITCWND + RTAX_FEATURES, +#define RTAX_FEATURES RTAX_FEATURES + RTAX_RTO_MIN, +#define RTAX_RTO_MIN RTAX_RTO_MIN + RTAX_INITRWND, +#define RTAX_INITRWND RTAX_INITRWND + RTAX_QUICKACK, +#define RTAX_QUICKACK RTAX_QUICKACK + RTAX_CC_ALGO, +#define RTAX_CC_ALGO RTAX_CC_ALGO + RTAX_FASTOPEN_NO_COOKIE, +#define RTAX_FASTOPEN_NO_COOKIE RTAX_FASTOPEN_NO_COOKIE + __RTAX_MAX +}; + +#define RTAX_MAX (__RTAX_MAX - 1) + +#define RTAX_FEATURE_ECN (1 << 0) +#define RTAX_FEATURE_SACK (1 << 1) +#define RTAX_FEATURE_TIMESTAMP (1 << 2) +#define RTAX_FEATURE_ALLFRAG (1 << 3) + +#define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \ + RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG) + +struct rta_session { + __u8 proto; + __u8 pad1; + __u16 pad2; + + union { + struct { + __u16 sport; + __u16 dport; + } ports; + + struct { + __u8 type; + __u8 code; + __u16 ident; + } icmpt; + + __u32 spi; + } u; +}; + +struct rta_mfc_stats { + __u64 mfcs_packets; + __u64 mfcs_bytes; + __u64 mfcs_wrong_if; +}; + +/**** + * General form of address family dependent message. + ****/ + +struct rtgenmsg { + unsigned char rtgen_family; +}; + +/***************************************************************** + * Link layer specific messages. + ****/ + +/* struct ifinfomsg + * passes link level specific information, not dependent + * on network protocol. + */ + +struct ifinfomsg { + unsigned char ifi_family; + unsigned char __ifi_pad; + unsigned short ifi_type; /* ARPHRD_* */ + int ifi_index; /* Link index */ + unsigned ifi_flags; /* IFF_* flags */ + unsigned ifi_change; /* IFF_* change mask */ +}; + +/******************************************************************** + * prefix information + ****/ + +struct prefixmsg { + unsigned char prefix_family; + unsigned char prefix_pad1; + unsigned short prefix_pad2; + int prefix_ifindex; + unsigned char prefix_type; + unsigned char prefix_len; + unsigned char prefix_flags; + unsigned char prefix_pad3; +}; + +enum +{ + PREFIX_UNSPEC, + PREFIX_ADDRESS, + PREFIX_CACHEINFO, + __PREFIX_MAX +}; + +#define PREFIX_MAX (__PREFIX_MAX - 1) + +struct prefix_cacheinfo { + __u32 preferred_time; + __u32 valid_time; +}; + + +/***************************************************************** + * Traffic control messages. + ****/ + +struct tcmsg { + unsigned char tcm_family; + unsigned char tcm__pad1; + unsigned short tcm__pad2; + int tcm_ifindex; + __u32 tcm_handle; + __u32 tcm_parent; +/* tcm_block_index is used instead of tcm_parent + * in case tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK + */ +#define tcm_block_index tcm_parent + __u32 tcm_info; +}; + +/* For manipulation of filters in shared block, tcm_ifindex is set to + * TCM_IFINDEX_MAGIC_BLOCK, and tcm_parent is aliased to tcm_block_index + * which is the block index. + */ +#define TCM_IFINDEX_MAGIC_BLOCK (0xFFFFFFFFU) + +enum { + TCA_UNSPEC, + TCA_KIND, + TCA_OPTIONS, + TCA_STATS, + TCA_XSTATS, + TCA_RATE, + TCA_FCNT, + TCA_STATS2, + TCA_STAB, + TCA_PAD, + TCA_DUMP_INVISIBLE, + TCA_CHAIN, + TCA_HW_OFFLOAD, + TCA_INGRESS_BLOCK, + TCA_EGRESS_BLOCK, + TCA_DUMP_FLAGS, + __TCA_MAX +}; + +#define TCA_MAX (__TCA_MAX - 1) + +#define TCA_DUMP_FLAGS_TERSE (1 << 0) /* Means that in dump user gets only basic + * data necessary to identify the objects + * (handle, cookie, etc.) and stats. + */ + +#define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) +#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) + +/******************************************************************** + * Neighbor Discovery userland options + ****/ + +struct nduseroptmsg { + unsigned char nduseropt_family; + unsigned char nduseropt_pad1; + unsigned short nduseropt_opts_len; /* Total length of options */ + int nduseropt_ifindex; + __u8 nduseropt_icmp_type; + __u8 nduseropt_icmp_code; + unsigned short nduseropt_pad2; + unsigned int nduseropt_pad3; + /* Followed by one or more ND options */ +}; + +enum { + NDUSEROPT_UNSPEC, + NDUSEROPT_SRCADDR, + __NDUSEROPT_MAX +}; + +#define NDUSEROPT_MAX (__NDUSEROPT_MAX - 1) + +#ifndef __KERNEL__ +/* RTnetlink multicast groups - backwards compatibility for userspace */ +#define RTMGRP_LINK 1 +#define RTMGRP_NOTIFY 2 +#define RTMGRP_NEIGH 4 +#define RTMGRP_TC 8 + +#define RTMGRP_IPV4_IFADDR 0x10 +#define RTMGRP_IPV4_MROUTE 0x20 +#define RTMGRP_IPV4_ROUTE 0x40 +#define RTMGRP_IPV4_RULE 0x80 + +#define RTMGRP_IPV6_IFADDR 0x100 +#define RTMGRP_IPV6_MROUTE 0x200 +#define RTMGRP_IPV6_ROUTE 0x400 +#define RTMGRP_IPV6_IFINFO 0x800 + +#define RTMGRP_DECnet_IFADDR 0x1000 +#define RTMGRP_DECnet_ROUTE 0x4000 + +#define RTMGRP_IPV6_PREFIX 0x20000 +#endif + +/* RTnetlink multicast groups */ +enum rtnetlink_groups { + RTNLGRP_NONE, +#define RTNLGRP_NONE RTNLGRP_NONE + RTNLGRP_LINK, +#define RTNLGRP_LINK RTNLGRP_LINK + RTNLGRP_NOTIFY, +#define RTNLGRP_NOTIFY RTNLGRP_NOTIFY + RTNLGRP_NEIGH, +#define RTNLGRP_NEIGH RTNLGRP_NEIGH + RTNLGRP_TC, +#define RTNLGRP_TC RTNLGRP_TC + RTNLGRP_IPV4_IFADDR, +#define RTNLGRP_IPV4_IFADDR RTNLGRP_IPV4_IFADDR + RTNLGRP_IPV4_MROUTE, +#define RTNLGRP_IPV4_MROUTE RTNLGRP_IPV4_MROUTE + RTNLGRP_IPV4_ROUTE, +#define RTNLGRP_IPV4_ROUTE RTNLGRP_IPV4_ROUTE + RTNLGRP_IPV4_RULE, +#define RTNLGRP_IPV4_RULE RTNLGRP_IPV4_RULE + RTNLGRP_IPV6_IFADDR, +#define RTNLGRP_IPV6_IFADDR RTNLGRP_IPV6_IFADDR + RTNLGRP_IPV6_MROUTE, +#define RTNLGRP_IPV6_MROUTE RTNLGRP_IPV6_MROUTE + RTNLGRP_IPV6_ROUTE, +#define RTNLGRP_IPV6_ROUTE RTNLGRP_IPV6_ROUTE + RTNLGRP_IPV6_IFINFO, +#define RTNLGRP_IPV6_IFINFO RTNLGRP_IPV6_IFINFO + RTNLGRP_DECnet_IFADDR, +#define RTNLGRP_DECnet_IFADDR RTNLGRP_DECnet_IFADDR + RTNLGRP_NOP2, + RTNLGRP_DECnet_ROUTE, +#define RTNLGRP_DECnet_ROUTE RTNLGRP_DECnet_ROUTE + RTNLGRP_DECnet_RULE, +#define RTNLGRP_DECnet_RULE RTNLGRP_DECnet_RULE + RTNLGRP_NOP4, + RTNLGRP_IPV6_PREFIX, +#define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX + RTNLGRP_IPV6_RULE, +#define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE + RTNLGRP_ND_USEROPT, +#define RTNLGRP_ND_USEROPT RTNLGRP_ND_USEROPT + RTNLGRP_PHONET_IFADDR, +#define RTNLGRP_PHONET_IFADDR RTNLGRP_PHONET_IFADDR + RTNLGRP_PHONET_ROUTE, +#define RTNLGRP_PHONET_ROUTE RTNLGRP_PHONET_ROUTE + RTNLGRP_DCB, +#define RTNLGRP_DCB RTNLGRP_DCB + RTNLGRP_IPV4_NETCONF, +#define RTNLGRP_IPV4_NETCONF RTNLGRP_IPV4_NETCONF + RTNLGRP_IPV6_NETCONF, +#define RTNLGRP_IPV6_NETCONF RTNLGRP_IPV6_NETCONF + RTNLGRP_MDB, +#define RTNLGRP_MDB RTNLGRP_MDB + RTNLGRP_MPLS_ROUTE, +#define RTNLGRP_MPLS_ROUTE RTNLGRP_MPLS_ROUTE + RTNLGRP_NSID, +#define RTNLGRP_NSID RTNLGRP_NSID + RTNLGRP_MPLS_NETCONF, +#define RTNLGRP_MPLS_NETCONF RTNLGRP_MPLS_NETCONF + RTNLGRP_IPV4_MROUTE_R, +#define RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV4_MROUTE_R + RTNLGRP_IPV6_MROUTE_R, +#define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R + RTNLGRP_NEXTHOP, +#define RTNLGRP_NEXTHOP RTNLGRP_NEXTHOP + RTNLGRP_BRVLAN, +#define RTNLGRP_BRVLAN RTNLGRP_BRVLAN + RTNLGRP_TUNNEL, +#define RTNLGRP_TUNNEL RTNLGRP_TUNNEL + __RTNLGRP_MAX +}; +#define RTNLGRP_MAX (__RTNLGRP_MAX - 1) + +/* TC action piece */ +struct tcamsg { + unsigned char tca_family; + unsigned char tca__pad1; + unsigned short tca__pad2; +}; + +enum { + TCA_ROOT_UNSPEC, + TCA_ROOT_TAB, +#define TCA_ACT_TAB TCA_ROOT_TAB +#define TCAA_MAX TCA_ROOT_TAB + TCA_ROOT_FLAGS, + TCA_ROOT_COUNT, + TCA_ROOT_TIME_DELTA, /* in msecs */ + __TCA_ROOT_MAX, +#define TCA_ROOT_MAX (__TCA_ROOT_MAX - 1) +}; + +#define TA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcamsg)))) +#define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg)) +/* tcamsg flags stored in attribute TCA_ROOT_FLAGS + * + * TCA_ACT_FLAG_LARGE_DUMP_ON user->kernel to request for larger than + * TCA_ACT_MAX_PRIO actions in a dump. All dump responses will contain the + * number of actions being dumped stored in for user app's consumption in + * TCA_ROOT_COUNT + * + * TCA_ACT_FLAG_TERSE_DUMP user->kernel to request terse (brief) dump that only + * includes essential action info (kind, index, etc.) + * + */ +#define TCA_FLAG_LARGE_DUMP_ON (1 << 0) +#define TCA_ACT_FLAG_LARGE_DUMP_ON TCA_FLAG_LARGE_DUMP_ON +#define TCA_ACT_FLAG_TERSE_DUMP (1 << 1) + +/* New extended info filters for IFLA_EXT_MASK */ +#define RTEXT_FILTER_VF (1 << 0) +#define RTEXT_FILTER_BRVLAN (1 << 1) +#define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2) +#define RTEXT_FILTER_SKIP_STATS (1 << 3) +#define RTEXT_FILTER_MRP (1 << 4) +#define RTEXT_FILTER_CFM_CONFIG (1 << 5) +#define RTEXT_FILTER_CFM_STATUS (1 << 6) + +/* End of information exported to user level */ + + + +#endif /* _UAPI__LINUX_RTNETLINK_H */ diff --git a/include/linux/seg6.h b/include/linux/seg6.h new file mode 100644 index 0000000..5b572ba --- /dev/null +++ b/include/linux/seg6.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note +/* + * SR-IPv6 implementation + * + * Author: + * David Lebrun + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_SEG6_H +#define _LINUX_SEG6_H + +#include +#include /* For struct in6_addr. */ + +/* + * SRH + */ +struct ipv6_sr_hdr { + __u8 nexthdr; + __u8 hdrlen; + __u8 type; + __u8 segments_left; + __u8 first_segment; /* Represents the last_entry field of SRH */ + __u8 flags; + __u16 tag; + + struct in6_addr segments[]; +}; + +#define SR6_FLAG1_PROTECTED (1 << 6) +#define SR6_FLAG1_OAM (1 << 5) +#define SR6_FLAG1_ALERT (1 << 4) +#define SR6_FLAG1_HMAC (1 << 3) + +#define SR6_TLV_INGRESS 1 +#define SR6_TLV_EGRESS 2 +#define SR6_TLV_OPAQUE 3 +#define SR6_TLV_PADDING 4 +#define SR6_TLV_HMAC 5 + +#define sr_has_hmac(srh) ((srh)->flags & SR6_FLAG1_HMAC) + +struct sr6_tlv { + __u8 type; + __u8 len; + __u8 data[0]; +}; + +#endif diff --git a/include/linux/seg6_genl.h b/include/linux/seg6_genl.h new file mode 100644 index 0000000..0c23052 --- /dev/null +++ b/include/linux/seg6_genl.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_SEG6_GENL_H +#define _LINUX_SEG6_GENL_H + +#define SEG6_GENL_NAME "SEG6" +#define SEG6_GENL_VERSION 0x1 + +enum { + SEG6_ATTR_UNSPEC, + SEG6_ATTR_DST, + SEG6_ATTR_DSTLEN, + SEG6_ATTR_HMACKEYID, + SEG6_ATTR_SECRET, + SEG6_ATTR_SECRETLEN, + SEG6_ATTR_ALGID, + SEG6_ATTR_HMACINFO, + __SEG6_ATTR_MAX, +}; + +#define SEG6_ATTR_MAX (__SEG6_ATTR_MAX - 1) + +enum { + SEG6_CMD_UNSPEC, + SEG6_CMD_SETHMAC, + SEG6_CMD_DUMPHMAC, + SEG6_CMD_SET_TUNSRC, + SEG6_CMD_GET_TUNSRC, + __SEG6_CMD_MAX, +}; + +#define SEG6_CMD_MAX (__SEG6_CMD_MAX - 1) + +#endif diff --git a/include/linux/seg6_hmac.h b/include/linux/seg6_hmac.h new file mode 100644 index 0000000..3fb3412 --- /dev/null +++ b/include/linux/seg6_hmac.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_SEG6_HMAC_H +#define _LINUX_SEG6_HMAC_H + +#include +#include + +#define SEG6_HMAC_SECRET_LEN 64 +#define SEG6_HMAC_FIELD_LEN 32 + +struct sr6_tlv_hmac { + struct sr6_tlv tlvhdr; + __u16 reserved; + __be32 hmackeyid; + __u8 hmac[SEG6_HMAC_FIELD_LEN]; +}; + +enum { + SEG6_HMAC_ALGO_SHA1 = 1, + SEG6_HMAC_ALGO_SHA256 = 2, +}; + +#endif diff --git a/include/linux/seg6_iptunnel.h b/include/linux/seg6_iptunnel.h new file mode 100644 index 0000000..3004e98 --- /dev/null +++ b/include/linux/seg6_iptunnel.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * SR-IPv6 implementation + * + * Author: + * David Lebrun + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_SEG6_IPTUNNEL_H +#define _LINUX_SEG6_IPTUNNEL_H + +#include /* For struct ipv6_sr_hdr. */ + +enum { + SEG6_IPTUNNEL_UNSPEC, + SEG6_IPTUNNEL_SRH, + __SEG6_IPTUNNEL_MAX, +}; +#define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1) + +struct seg6_iptunnel_encap { + int mode; + struct ipv6_sr_hdr srh[0]; +}; + +#define SEG6_IPTUN_ENCAP_SIZE(x) ((sizeof(*x)) + (((x)->srh->hdrlen + 1) << 3)) + +enum { + SEG6_IPTUN_MODE_INLINE, + SEG6_IPTUN_MODE_ENCAP, + SEG6_IPTUN_MODE_L2ENCAP, +}; + + +#endif diff --git a/include/linux/seg6_local.h b/include/linux/seg6_local.h new file mode 100644 index 0000000..401c8b0 --- /dev/null +++ b/include/linux/seg6_local.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SR-IPv6 implementation + * + * Author: + * David Lebrun + */ + +#ifndef _LINUX_SEG6_LOCAL_H +#define _LINUX_SEG6_LOCAL_H + +#include + +enum { + SEG6_LOCAL_UNSPEC, + SEG6_LOCAL_ACTION, + SEG6_LOCAL_SRH, + SEG6_LOCAL_TABLE, + SEG6_LOCAL_NH4, + SEG6_LOCAL_NH6, + SEG6_LOCAL_IIF, + SEG6_LOCAL_OIF, + SEG6_LOCAL_BPF, + SEG6_LOCAL_VRFTABLE, + SEG6_LOCAL_COUNTERS, + SEG6_LOCAL_FLAVORS, + __SEG6_LOCAL_MAX, +}; +#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1) + +enum { + SEG6_LOCAL_ACTION_UNSPEC = 0, + /* node segment */ + SEG6_LOCAL_ACTION_END = 1, + /* adjacency segment (IPv6 cross-connect) */ + SEG6_LOCAL_ACTION_END_X = 2, + /* lookup of next seg NH in table */ + SEG6_LOCAL_ACTION_END_T = 3, + /* decap and L2 cross-connect */ + SEG6_LOCAL_ACTION_END_DX2 = 4, + /* decap and IPv6 cross-connect */ + SEG6_LOCAL_ACTION_END_DX6 = 5, + /* decap and IPv4 cross-connect */ + SEG6_LOCAL_ACTION_END_DX4 = 6, + /* decap and lookup of DA in v6 table */ + SEG6_LOCAL_ACTION_END_DT6 = 7, + /* decap and lookup of DA in v4 table */ + SEG6_LOCAL_ACTION_END_DT4 = 8, + /* binding segment with insertion */ + SEG6_LOCAL_ACTION_END_B6 = 9, + /* binding segment with encapsulation */ + SEG6_LOCAL_ACTION_END_B6_ENCAP = 10, + /* binding segment with MPLS encap */ + SEG6_LOCAL_ACTION_END_BM = 11, + /* lookup last seg in table */ + SEG6_LOCAL_ACTION_END_S = 12, + /* forward to SR-unaware VNF with static proxy */ + SEG6_LOCAL_ACTION_END_AS = 13, + /* forward to SR-unaware VNF with masquerading */ + SEG6_LOCAL_ACTION_END_AM = 14, + /* custom BPF action */ + SEG6_LOCAL_ACTION_END_BPF = 15, + /* decap and lookup of DA in v4 or v6 table */ + SEG6_LOCAL_ACTION_END_DT46 = 16, + + __SEG6_LOCAL_ACTION_MAX, +}; + +#define SEG6_LOCAL_ACTION_MAX (__SEG6_LOCAL_ACTION_MAX - 1) + +enum { + SEG6_LOCAL_BPF_PROG_UNSPEC, + SEG6_LOCAL_BPF_PROG, + SEG6_LOCAL_BPF_PROG_NAME, + __SEG6_LOCAL_BPF_PROG_MAX, +}; + +#define SEG6_LOCAL_BPF_PROG_MAX (__SEG6_LOCAL_BPF_PROG_MAX - 1) + +/* SRv6 Behavior counters are encoded as netlink attributes guaranteeing the + * correct alignment. + * Each counter is identified by a different attribute type (i.e. + * SEG6_LOCAL_CNT_PACKETS). + * + * - SEG6_LOCAL_CNT_PACKETS: identifies a counter that counts the number of + * packets that have been CORRECTLY processed by an SRv6 Behavior instance + * (i.e., packets that generate errors or are dropped are NOT counted). + * + * - SEG6_LOCAL_CNT_BYTES: identifies a counter that counts the total amount + * of traffic in bytes of all packets that have been CORRECTLY processed by + * an SRv6 Behavior instance (i.e., packets that generate errors or are + * dropped are NOT counted). + * + * - SEG6_LOCAL_CNT_ERRORS: identifies a counter that counts the number of + * packets that have NOT been properly processed by an SRv6 Behavior instance + * (i.e., packets that generate errors or are dropped). + */ +enum { + SEG6_LOCAL_CNT_UNSPEC, + SEG6_LOCAL_CNT_PAD, /* pad for 64 bits values */ + SEG6_LOCAL_CNT_PACKETS, + SEG6_LOCAL_CNT_BYTES, + SEG6_LOCAL_CNT_ERRORS, + __SEG6_LOCAL_CNT_MAX, +}; + +#define SEG6_LOCAL_CNT_MAX (__SEG6_LOCAL_CNT_MAX - 1) + +/* SRv6 End* Flavor attributes */ +enum { + SEG6_LOCAL_FLV_UNSPEC, + SEG6_LOCAL_FLV_OPERATION, + SEG6_LOCAL_FLV_LCBLOCK_BITS, + SEG6_LOCAL_FLV_LCNODE_FN_BITS, + __SEG6_LOCAL_FLV_MAX, +}; + +#define SEG6_LOCAL_FLV_MAX (__SEG6_LOCAL_FLV_MAX - 1) + +/* Designed flavor operations for SRv6 End* Behavior */ +enum { + SEG6_LOCAL_FLV_OP_UNSPEC, + SEG6_LOCAL_FLV_OP_PSP, + SEG6_LOCAL_FLV_OP_USP, + SEG6_LOCAL_FLV_OP_USD, + SEG6_LOCAL_FLV_OP_NEXT_CSID, + __SEG6_LOCAL_FLV_OP_MAX +}; + +#define SEG6_LOCAL_FLV_OP_MAX (__SEG6_LOCAL_FLV_OP_MAX - 1) + +#endif diff --git a/include/linux/socket.h b/include/linux/socket.h new file mode 100644 index 0000000..8eb9602 --- /dev/null +++ b/include/linux/socket.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_SOCKET_H +#define _UAPI_LINUX_SOCKET_H + +/* + * Desired design of maximum size and alignment (see RFC2553) + */ +#define _K_SS_MAXSIZE 128 /* Implementation specific max size */ +#define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *)) + /* Implementation specific desired alignment */ + +typedef unsigned short __kernel_sa_family_t; + +struct __kernel_sockaddr_storage { + __kernel_sa_family_t ss_family; /* address family */ + /* Following field(s) are implementation specific */ + char __data[_K_SS_MAXSIZE - sizeof(unsigned short)]; + /* space to achieve desired size, */ + /* _SS_MAXSIZE value minus size of ss_family */ +} __attribute__ ((aligned(_K_SS_ALIGNSIZE))); /* force desired alignment */ + +#endif /* _UAPI_LINUX_SOCKET_H */ diff --git a/include/subdir.am b/include/subdir.am new file mode 100644 index 0000000..bcf3ef7 --- /dev/null +++ b/include/subdir.am @@ -0,0 +1,24 @@ +noinst_HEADERS += \ + include/linux/compiler_types.h \ + include/linux/libc-compat.h \ + include/linux/if.h \ + include/linux/if_addr.h \ + include/linux/if_bridge.h \ + include/linux/if_link.h \ + include/linux/lwtunnel.h \ + include/linux/mpls_iptunnel.h \ + include/linux/neighbour.h \ + include/linux/netconf.h \ + include/linux/netlink.h \ + include/linux/nexthop.h \ + include/linux/rtnetlink.h \ + include/linux/socket.h \ + include/linux/net_namespace.h \ + include/linux/fib_rules.h \ + include/linux/seg6.h \ + include/linux/seg6_genl.h \ + include/linux/seg6_hmac.h \ + include/linux/seg6_iptunnel.h \ + include/linux/seg6_local.h \ + include/linux/pkt_cls.h \ + # end diff --git a/isisd/.gitignore b/isisd/.gitignore new file mode 100644 index 0000000..b6184ca --- /dev/null +++ b/isisd/.gitignore @@ -0,0 +1,3 @@ +isisd +fabricd +isisd.conf diff --git a/isisd/AUTHORS b/isisd/AUTHORS new file mode 100644 index 0000000..80b3a28 --- /dev/null +++ b/isisd/AUTHORS @@ -0,0 +1,5 @@ +Sampo Saaristo +Ofer Wald +Hannes Gredler +Subbaiah Venkata +Olivier Dugeon diff --git a/isisd/Makefile b/isisd/Makefile new file mode 100644 index 0000000..db3b70e --- /dev/null +++ b/isisd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. isisd/isisd +%: ALWAYS + @$(MAKE) -s -C .. isisd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/isisd/README b/isisd/README new file mode 100644 index 0000000..4f13ff6 --- /dev/null +++ b/isisd/README @@ -0,0 +1,3 @@ +Constraints + + o Maximum number of interfaces 255 diff --git a/isisd/fabricd.c b/isisd/fabricd.c new file mode 100644 index 0000000..b229aa6 --- /dev/null +++ b/isisd/fabricd.c @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - OpenFabric extensions + * + * Copyright (C) 2018 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#include +#include "isisd/fabricd.h" +#include "isisd/isisd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_tx_queue.h" +#include "isisd/isis_csm.h" + +DEFINE_MTYPE_STATIC(ISISD, FABRICD_STATE, "ISIS OpenFabric"); +DEFINE_MTYPE_STATIC(ISISD, FABRICD_NEIGHBOR, "ISIS OpenFabric Neighbor Entry"); +DEFINE_MTYPE_STATIC(ISISD, FABRICD_FLOODING_INFO, "ISIS OpenFabric Flooding Log"); + +/* Tracks initial synchronization as per section 2.4 + * + * We declare the sync complete once we have seen at least one + * CSNP and there are no more LSPs with SSN or SRM set. + */ +enum fabricd_sync_state { + FABRICD_SYNC_PENDING, + FABRICD_SYNC_STARTED, + FABRICD_SYNC_COMPLETE +}; + +struct fabricd { + struct isis_area *area; + + enum fabricd_sync_state initial_sync_state; + time_t initial_sync_start; + struct isis_circuit *initial_sync_circuit; + struct event *initial_sync_timeout; + + struct isis_spftree *spftree; + struct skiplist *neighbors; + struct hash *neighbors_neighbors; + + uint8_t tier; + uint8_t tier_config; + uint8_t tier_pending; + struct event *tier_calculation_timer; + struct event *tier_set_timer; + + int csnp_delay; + bool always_send_csnp; +}; + +/* Code related to maintaining the neighbor lists */ + +struct neighbor_entry { + uint8_t id[ISIS_SYS_ID_LEN]; + struct isis_adjacency *adj; + bool present; +}; + +static struct neighbor_entry *neighbor_entry_new(const uint8_t *id, + struct isis_adjacency *adj) +{ + struct neighbor_entry *rv = XMALLOC(MTYPE_FABRICD_NEIGHBOR, + sizeof(*rv)); + + memcpy(rv->id, id, sizeof(rv->id)); + rv->adj = adj; + + return rv; +} + +static void neighbor_entry_del(struct neighbor_entry *neighbor) +{ + XFREE(MTYPE_FABRICD_NEIGHBOR, neighbor); +} + +static void neighbor_entry_del_void(void *arg) +{ + neighbor_entry_del((struct neighbor_entry *)arg); +} + +static void neighbor_lists_clear(struct fabricd *f) +{ + while (!skiplist_empty(f->neighbors)) + skiplist_delete_first(f->neighbors); + + hash_clean(f->neighbors_neighbors, neighbor_entry_del_void); +} + +static unsigned neighbor_entry_hash_key(const void *np) +{ + const struct neighbor_entry *n = np; + + return jhash(n->id, sizeof(n->id), 0x55aa5a5a); +} + +static bool neighbor_entry_hash_cmp(const void *a, const void *b) +{ + const struct neighbor_entry *na = a, *nb = b; + + return memcmp(na->id, nb->id, sizeof(na->id)) == 0; +} + +static int neighbor_entry_list_cmp(const void *a, const void *b) +{ + const struct neighbor_entry *na = a, *nb = b; + + return -memcmp(na->id, nb->id, sizeof(na->id)); +} + +static struct neighbor_entry *neighbor_entry_lookup_list(struct skiplist *list, + const uint8_t *id) +{ + struct neighbor_entry n = { {0} }; + + memcpy(n.id, id, sizeof(n.id)); + + struct neighbor_entry *rv; + + if (skiplist_search(list, &n, (void**)&rv)) + return NULL; + + if (!rv->present) + return NULL; + + return rv; +} + +static struct neighbor_entry *neighbor_entry_lookup_hash(struct hash *hash, + const uint8_t *id) +{ + struct neighbor_entry n = {{0}}; + + memcpy(n.id, id, sizeof(n.id)); + + struct neighbor_entry *rv = hash_lookup(hash, &n); + + if (!rv || !rv->present) + return NULL; + + return rv; +} + +static int fabricd_handle_adj_state_change(struct isis_adjacency *arg) +{ + struct fabricd *f = arg->circuit->area->fabricd; + + if (!f) + return 0; + + while (!skiplist_empty(f->neighbors)) + skiplist_delete_first(f->neighbors); + + struct listnode *node; + struct isis_circuit *circuit; + + for (ALL_LIST_ELEMENTS_RO(f->area->circuit_list, node, circuit)) { + if (circuit->state != C_STATE_UP) + continue; + + struct isis_adjacency *adj = circuit->u.p2p.neighbor; + + if (!adj || adj->adj_state != ISIS_ADJ_UP) + continue; + + struct neighbor_entry *n = neighbor_entry_new(adj->sysid, adj); + + skiplist_insert(f->neighbors, n, n); + } + + return 0; +} + +static void neighbors_neighbors_update(struct fabricd *f) +{ + hash_clean(f->neighbors_neighbors, neighbor_entry_del_void); + + struct listnode *node; + struct isis_vertex *v; + + for (ALL_QUEUE_ELEMENTS_RO(&f->spftree->paths, node, v)) { + if (v->d_N < 2 || !VTYPE_IS(v->type)) + continue; + + if (v->d_N > 2) + break; + + struct neighbor_entry *n = neighbor_entry_new(v->N.id, NULL); + struct neighbor_entry *inserted; + inserted = hash_get(f->neighbors_neighbors, n, + hash_alloc_intern); + assert(inserted == n); + } +} + +struct fabricd *fabricd_new(struct isis_area *area) +{ + struct fabricd *rv = XCALLOC(MTYPE_FABRICD_STATE, sizeof(*rv)); + + rv->area = area; + rv->initial_sync_state = FABRICD_SYNC_PENDING; + + rv->spftree = isis_spftree_new( + area, &area->lspdb[IS_LEVEL_2 - 1], area->isis->sysid, + ISIS_LEVEL2, SPFTREE_IPV4, SPF_TYPE_FORWARD, + F_SPFTREE_HOPCOUNT_METRIC, SR_ALGORITHM_SPF); + rv->neighbors = skiplist_new(0, neighbor_entry_list_cmp, + neighbor_entry_del_void); + rv->neighbors_neighbors = hash_create(neighbor_entry_hash_key, + neighbor_entry_hash_cmp, + "Fabricd Neighbors"); + + rv->tier = rv->tier_config = ISIS_TIER_UNDEFINED; + + rv->csnp_delay = FABRICD_DEFAULT_CSNP_DELAY; + return rv; +}; + +void fabricd_finish(struct fabricd *f) +{ + EVENT_OFF(f->initial_sync_timeout); + + EVENT_OFF(f->tier_calculation_timer); + + EVENT_OFF(f->tier_set_timer); + + isis_spftree_del(f->spftree); + neighbor_lists_clear(f); + skiplist_free(f->neighbors); + hash_free(f->neighbors_neighbors); +} + +static void fabricd_initial_sync_timeout(struct event *thread) +{ + struct fabricd *f = EVENT_ARG(thread); + + if (IS_DEBUG_ADJ_PACKETS) + zlog_debug( + "OpenFabric: Initial synchronization on %s timed out!", + f->initial_sync_circuit->interface->name); + f->initial_sync_state = FABRICD_SYNC_PENDING; + f->initial_sync_circuit = NULL; +} + +void fabricd_initial_sync_hello(struct isis_circuit *circuit) +{ + struct fabricd *f = circuit->area->fabricd; + + if (!f) + return; + + if (f->initial_sync_state > FABRICD_SYNC_PENDING) + return; + + f->initial_sync_state = FABRICD_SYNC_STARTED; + + long timeout = 2 * circuit->hello_interval[1] * circuit->hello_multiplier[1]; + + f->initial_sync_circuit = circuit; + if (f->initial_sync_timeout) + return; + + event_add_timer(master, fabricd_initial_sync_timeout, f, timeout, + &f->initial_sync_timeout); + f->initial_sync_start = monotime(NULL); + + if (IS_DEBUG_ADJ_PACKETS) + zlog_debug( + "OpenFabric: Started initial synchronization with %pSY on %s", + circuit->u.p2p.neighbor->sysid, + circuit->interface->name); +} + +bool fabricd_initial_sync_is_in_progress(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return false; + + if (f->initial_sync_state > FABRICD_SYNC_PENDING + && f->initial_sync_state < FABRICD_SYNC_COMPLETE) + return true; + + return false; +} + +bool fabricd_initial_sync_is_complete(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return false; + + return f->initial_sync_state == FABRICD_SYNC_COMPLETE; +} + +struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + if (!f) + return NULL; + + return f->initial_sync_circuit; +} + +void fabricd_initial_sync_finish(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return; + + if (monotime(NULL) - f->initial_sync_start < 5) + return; + + zlog_info("OpenFabric: Initial synchronization on %s complete.", + f->initial_sync_circuit->interface->name); + f->initial_sync_state = FABRICD_SYNC_COMPLETE; + f->initial_sync_circuit = NULL; + EVENT_OFF(f->initial_sync_timeout); +} + +static void fabricd_bump_tier_calculation_timer(struct fabricd *f); +static void fabricd_set_tier(struct fabricd *f, uint8_t tier); + +static uint8_t fabricd_calculate_fabric_tier(struct isis_area *area) +{ + struct isis_spftree *local_tree = fabricd_spftree(area); + struct listnode *node; + + struct isis_vertex *furthest_t0 = NULL, + *second_furthest_t0 = NULL; + + struct isis_vertex *v; + + for (ALL_QUEUE_ELEMENTS_RO(&local_tree->paths, node, v)) { + struct isis_lsp *lsp = lsp_for_vertex(local_tree, v); + + if (!lsp || !lsp->tlvs + || !lsp->tlvs->spine_leaf + || !lsp->tlvs->spine_leaf->has_tier + || lsp->tlvs->spine_leaf->tier != 0) + continue; + + second_furthest_t0 = furthest_t0; + furthest_t0 = v; + } + + if (!second_furthest_t0) { + zlog_info("OpenFabric: Could not find two T0 routers"); + return ISIS_TIER_UNDEFINED; + } + + zlog_info( + "OpenFabric: Found %pLS as furthest t0 from local system, dist == %u", + furthest_t0->N.id, furthest_t0->d_N); + + struct isis_spftree *remote_tree = + isis_run_hopcount_spf(area, furthest_t0->N.id, NULL); + + struct isis_vertex *furthest_from_remote = + isis_vertex_queue_last(&remote_tree->paths); + + if (!furthest_from_remote) { + zlog_info("OpenFabric: Found no furthest node in remote spf"); + isis_spftree_del(remote_tree); + return ISIS_TIER_UNDEFINED; + } else { + zlog_info( + "OpenFabric: Found %pLS as furthest from remote dist == %u", + furthest_from_remote->N.id, furthest_from_remote->d_N); + } + + int64_t tier = furthest_from_remote->d_N - furthest_t0->d_N; + isis_spftree_del(remote_tree); + + if (tier < 0 || tier >= ISIS_TIER_UNDEFINED) { + zlog_info("OpenFabric: Calculated tier %" PRId64 " seems implausible", + tier); + return ISIS_TIER_UNDEFINED; + } + + zlog_info("OpenFabric: Calculated %" PRId64 " as tier", tier); + return tier; +} + +static void fabricd_tier_set_timer(struct event *thread) +{ + struct fabricd *f = EVENT_ARG(thread); + + fabricd_set_tier(f, f->tier_pending); +} + +static void fabricd_tier_calculation_cb(struct event *thread) +{ + struct fabricd *f = EVENT_ARG(thread); + uint8_t tier = ISIS_TIER_UNDEFINED; + + tier = fabricd_calculate_fabric_tier(f->area); + if (tier == ISIS_TIER_UNDEFINED) + return; + + zlog_info("OpenFabric: Got tier %hhu from algorithm. Arming timer.", + tier); + f->tier_pending = tier; + event_add_timer(master, fabricd_tier_set_timer, f, + f->area->lsp_gen_interval[ISIS_LEVEL2 - 1], + &f->tier_set_timer); +} + +static void fabricd_bump_tier_calculation_timer(struct fabricd *f) +{ + /* Cancel timer if we already know our tier */ + if (f->tier != ISIS_TIER_UNDEFINED || f->tier_set_timer) { + EVENT_OFF(f->tier_calculation_timer); + return; + } + + /* If we need to calculate the tier, wait some + * time for the topology to settle before running + * the calculation */ + EVENT_OFF(f->tier_calculation_timer); + + event_add_timer(master, fabricd_tier_calculation_cb, f, + 2 * f->area->lsp_gen_interval[ISIS_LEVEL2 - 1], + &f->tier_calculation_timer); +} + +static void fabricd_set_tier(struct fabricd *f, uint8_t tier) +{ + if (f->tier == tier) + return; + + zlog_info("OpenFabric: Set own tier to %hhu", tier); + f->tier = tier; + + fabricd_bump_tier_calculation_timer(f); + lsp_regenerate_schedule(f->area, ISIS_LEVEL2, 0); +} + +void fabricd_run_spf(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return; + + isis_run_hopcount_spf(area, area->isis->sysid, f->spftree); + neighbors_neighbors_update(f); + fabricd_bump_tier_calculation_timer(f); +} + +struct isis_spftree *fabricd_spftree(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return NULL; + + return f->spftree; +} + +void fabricd_configure_tier(struct isis_area *area, uint8_t tier) +{ + struct fabricd *f = area->fabricd; + + if (!f || f->tier_config == tier) + return; + + f->tier_config = tier; + fabricd_set_tier(f, tier); +} + +uint8_t fabricd_tier(struct isis_area *area) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return ISIS_TIER_UNDEFINED; + + return f->tier; +} + +int fabricd_write_settings(struct isis_area *area, struct vty *vty) +{ + struct fabricd *f = area->fabricd; + int written = 0; + + if (!f) + return written; + + if (f->tier_config != ISIS_TIER_UNDEFINED) { + vty_out(vty, " fabric-tier %hhu\n", f->tier_config); + written++; + } + + if (f->csnp_delay != FABRICD_DEFAULT_CSNP_DELAY + || f->always_send_csnp) { + vty_out(vty, " triggered-csnp-delay %d%s\n", f->csnp_delay, + f->always_send_csnp ? " always" : ""); + } + + return written; +} + +static void move_to_queue(struct isis_lsp *lsp, struct neighbor_entry *n, + enum isis_tx_type type, struct isis_circuit *circuit) +{ + n->present = false; + + if (n->adj && n->adj->circuit == circuit) + return; + + if (IS_DEBUG_FLOODING) { + zlog_debug("OpenFabric: Adding %s to %s", + print_sys_hostname(n->id), + (type == TX_LSP_NORMAL) ? "RF" : "DNR"); + } + + if (n->adj) + isis_tx_queue_add(n->adj->circuit->tx_queue, lsp, type); + + uint8_t *neighbor_id = XMALLOC(MTYPE_FABRICD_FLOODING_INFO, + sizeof(n->id)); + + memcpy(neighbor_id, n->id, sizeof(n->id)); + listnode_add(lsp->flooding_neighbors[type], neighbor_id); +} + +static void mark_neighbor_as_present(struct hash_bucket *bucket, void *arg) +{ + struct neighbor_entry *n = bucket->data; + + n->present = true; +} + +static void handle_firsthops(struct hash_bucket *bucket, void *arg) +{ + struct isis_lsp *lsp = arg; + struct fabricd *f = lsp->area->fabricd; + struct isis_vertex *vertex = bucket->data; + + struct neighbor_entry *n; + + n = neighbor_entry_lookup_list(f->neighbors, vertex->N.id); + if (n) { + if (IS_DEBUG_FLOODING) { + zlog_debug("Removing %s from NL as its in the reverse path", + print_sys_hostname(n->id)); + } + n->present = false; + } + + n = neighbor_entry_lookup_hash(f->neighbors_neighbors, vertex->N.id); + if (n) { + if (IS_DEBUG_FLOODING) { + zlog_debug("Removing %s from NN as its in the reverse path", + print_sys_hostname(n->id)); + } + n->present = false; + } +} + +static struct isis_lsp *lsp_for_neighbor(struct fabricd *f, + struct neighbor_entry *n) +{ + uint8_t id[ISIS_SYS_ID_LEN + 1] = {0}; + + memcpy(id, n->id, sizeof(n->id)); + + struct isis_vertex vertex = {0}; + + isis_vertex_id_init(&vertex, id, VTYPE_NONPSEUDO_TE_IS); + + return lsp_for_vertex(f->spftree, &vertex); +} + +static void fabricd_free_lsp_flooding_info(void *val) +{ + XFREE(MTYPE_FABRICD_FLOODING_INFO, val); +} + +static void fabricd_lsp_reset_flooding_info(struct isis_lsp *lsp, + struct isis_circuit *circuit) +{ + lsp->flooding_time = time(NULL); + + XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface); + for (enum isis_tx_type type = TX_LSP_NORMAL; + type <= TX_LSP_CIRCUIT_SCOPED; type++) { + if (lsp->flooding_neighbors[type]) { + list_delete_all_node(lsp->flooding_neighbors[type]); + continue; + } + + lsp->flooding_neighbors[type] = list_new(); + lsp->flooding_neighbors[type]->del = + fabricd_free_lsp_flooding_info; + } + + if (circuit) { + lsp->flooding_interface = XSTRDUP(MTYPE_FABRICD_FLOODING_INFO, + circuit->interface->name); + } + + lsp->flooding_circuit_scoped = false; +} + +void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit) +{ + struct fabricd *f = lsp->area->fabricd; + assert(f); + + fabricd_lsp_reset_flooding_info(lsp, circuit); + + void *cursor = NULL; + struct neighbor_entry *n; + + /* Mark all elements in NL as present */ + while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) + n->present = true; + + /* Mark all elements in NN as present */ + hash_iterate(f->neighbors_neighbors, mark_neighbor_as_present, NULL); + + struct isis_vertex *originator = + isis_find_vertex(&f->spftree->paths, + lsp->hdr.lsp_id, + VTYPE_NONPSEUDO_TE_IS); + + /* Remove all IS from NL and NN in the shortest path + * to the IS that originated the LSP */ + if (originator) + hash_iterate(originator->firsthops, handle_firsthops, lsp); + + /* Iterate over all remaining IS in NL */ + cursor = NULL; + while (!skiplist_next(f->neighbors, NULL, (void **)&n, &cursor)) { + if (!n->present) + continue; + + struct isis_lsp *nlsp = lsp_for_neighbor(f, n); + if (!nlsp || !nlsp->tlvs) { + if (IS_DEBUG_FLOODING) { + zlog_debug("Moving %s to DNR as it has no LSP", + print_sys_hostname(n->id)); + } + + move_to_queue(lsp, n, TX_LSP_CIRCUIT_SCOPED, circuit); + continue; + } + + if (IS_DEBUG_FLOODING) { + zlog_debug("Considering %s from NL...", + print_sys_hostname(n->id)); + } + + /* For all neighbors of the NL IS check whether they are present + * in NN. If yes, remove from NN and set need_reflood. */ + bool need_reflood = false; + struct isis_extended_reach *er; + for (er = (struct isis_extended_reach *)nlsp->tlvs->extended_reach.head; + er; er = er->next) { + struct neighbor_entry *nn; + + nn = neighbor_entry_lookup_hash(f->neighbors_neighbors, + er->id); + + if (nn) { + if (IS_DEBUG_FLOODING) { + zlog_debug("Found neighbor %s in NN, removing it from NN and setting reflood.", + print_sys_hostname(nn->id)); + } + + nn->present = false; + need_reflood = true; + } + } + + move_to_queue(lsp, n, need_reflood ? + TX_LSP_NORMAL : TX_LSP_CIRCUIT_SCOPED, + circuit); + } + + if (IS_DEBUG_FLOODING) { + zlog_debug("OpenFabric: Flooding algorithm complete."); + } +} + +void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return; + + if (!circuit_scoped && !f->always_send_csnp) + return; + + struct listnode *node; + struct isis_circuit *circuit; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + if (!circuit->t_send_csnp[1]) + continue; + + EVENT_OFF(circuit->t_send_csnp[ISIS_LEVEL2 - 1]); + event_add_timer_msec(master, send_l2_csnp, circuit, + isis_jitter(f->csnp_delay, CSNP_JITTER), + &circuit->t_send_csnp[ISIS_LEVEL2 - 1]); + } +} + +struct list *fabricd_ip_addrs(struct isis_circuit *circuit) +{ + if (listcount(circuit->ip_addrs)) + return circuit->ip_addrs; + + if (!fabricd || !circuit->area || !circuit->area->circuit_list) + return NULL; + + struct listnode *node; + struct isis_circuit *c; + + for (ALL_LIST_ELEMENTS_RO(circuit->area->circuit_list, node, c)) { + if (c->circ_type != CIRCUIT_T_LOOPBACK) + continue; + + if (!listcount(c->ip_addrs)) + return NULL; + + return c->ip_addrs; + } + + return NULL; +} + +void fabricd_lsp_free(struct isis_lsp *lsp) +{ + XFREE(MTYPE_FABRICD_FLOODING_INFO, lsp->flooding_interface); + for (enum isis_tx_type type = TX_LSP_NORMAL; + type <= TX_LSP_CIRCUIT_SCOPED; type++) { + if (!lsp->flooding_neighbors[type]) + continue; + + list_delete(&lsp->flooding_neighbors[type]); + } +} + +void fabricd_update_lsp_no_flood(struct isis_lsp *lsp, + struct isis_circuit *circuit) +{ + if (!fabricd) + return; + + fabricd_lsp_reset_flooding_info(lsp, circuit); + lsp->flooding_circuit_scoped = true; +} + +void fabricd_configure_triggered_csnp(struct isis_area *area, int delay, + bool always_send_csnp) +{ + struct fabricd *f = area->fabricd; + + if (!f) + return; + + f->csnp_delay = delay; + f->always_send_csnp = always_send_csnp; +} + +void fabricd_init(void) +{ + hook_register(isis_adj_state_change_hook, + fabricd_handle_adj_state_change); +} diff --git a/isisd/fabricd.h b/isisd/fabricd.h new file mode 100644 index 0000000..c1df0ad --- /dev/null +++ b/isisd/fabricd.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - OpenFabric extensions + * + * Copyright (C) 2018 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#ifndef FABRICD_H +#define FABRICD_H + +#define FABRICD_DEFAULT_CSNP_DELAY 500 + +struct fabricd; + +struct isis_circuit; +struct isis_area; +struct isis_spftree; +struct isis_lsp; +struct vty; + +struct fabricd *fabricd_new(struct isis_area *area); +void fabricd_finish(struct fabricd *f); +void fabricd_initial_sync_hello(struct isis_circuit *circuit); +bool fabricd_initial_sync_is_complete(struct isis_area *area); +bool fabricd_initial_sync_is_in_progress(struct isis_area *area); +struct isis_circuit *fabricd_initial_sync_circuit(struct isis_area *area); +void fabricd_initial_sync_finish(struct isis_area *area); +void fabricd_run_spf(struct isis_area *area); +struct isis_spftree *fabricd_spftree(struct isis_area *area); +void fabricd_configure_tier(struct isis_area *area, uint8_t tier); +uint8_t fabricd_tier(struct isis_area *area); +int fabricd_write_settings(struct isis_area *area, struct vty *vty); +void fabricd_lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit); +void fabricd_trigger_csnp(struct isis_area *area, bool circuit_scoped); +struct list *fabricd_ip_addrs(struct isis_circuit *circuit); +void fabricd_lsp_free(struct isis_lsp *lsp); +void fabricd_update_lsp_no_flood(struct isis_lsp *lsp, + struct isis_circuit *circuit); +void fabricd_configure_triggered_csnp(struct isis_area *area, int delay, + bool always_send_csnp); +void fabricd_init(void); +void isis_vty_daemon_init(void); + +#endif diff --git a/isisd/isis_adjacency.c b/isisd/isis_adjacency.c new file mode 100644 index 0000000..c04f815 --- /dev/null +++ b/isisd/isis_adjacency.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_adjacency.c + * handling of IS-IS adjacencies + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "log.h" +#include "memory.h" +#include "hash.h" +#include "vty.h" +#include "linklist.h" +#include "frrevent.h" +#include "if.h" +#include "stream.h" +#include "bfd.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isisd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_dr.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_events.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_tlvs.h" +#include "isisd/fabricd.h" +#include "isisd/isis_nb.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_ADJACENCY, "ISIS adjacency"); +DEFINE_MTYPE(ISISD, ISIS_ADJACENCY_INFO, "ISIS adjacency info"); + +static struct isis_adjacency *adj_alloc(struct isis_circuit *circuit, + const uint8_t *id) +{ + struct isis_adjacency *adj; + + adj = XCALLOC(MTYPE_ISIS_ADJACENCY, sizeof(struct isis_adjacency)); + memcpy(adj->sysid, id, ISIS_SYS_ID_LEN); + + adj->snmp_idx = ++circuit->snmp_adj_idx_gen; + + if (circuit->snmp_adj_list == NULL) + circuit->snmp_adj_list = list_new(); + + adj->snmp_list_node = listnode_add(circuit->snmp_adj_list, adj); + + return adj; +} + +struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, + int level, struct isis_circuit *circuit) +{ + struct isis_adjacency *adj; + int i; + + adj = adj_alloc(circuit, id); /* P2P kludge */ + + if (snpa) { + memcpy(adj->snpa, snpa, ETH_ALEN); + } else { + memset(adj->snpa, ' ', ETH_ALEN); + } + + adj->circuit = circuit; + adj->level = level; + adj->flaps = 0; + adj->last_flap = time(NULL); + adj->threeway_state = ISIS_THREEWAY_DOWN; + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + listnode_add(circuit->u.bc.adjdb[level - 1], adj); + adj->dischanges[level - 1] = 0; + for (i = 0; i < DIS_RECORDS; + i++) /* clear N DIS state change records */ + { + adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = + ISIS_UNKNOWN_DIS; + adj->dis_record[(i * ISIS_LEVELS) + level - 1] + .last_dis_change = time(NULL); + } + } + adj->adj_sids = list_new(); + adj->srv6_endx_sids = list_new(); + listnode_add(circuit->area->adjacency_list, adj); + + return adj; +} + +struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, struct list *adjdb) +{ + struct isis_adjacency *adj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) + if (memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN) == 0) + return adj; + + return NULL; +} + +struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, + struct list *adjdb) +{ + struct listnode *node; + struct isis_adjacency *adj; + + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) + if (memcmp(adj->snpa, ssnpa, ETH_ALEN) == 0) + return adj; + + return NULL; +} + +struct isis_adjacency *isis_adj_find(const struct isis_area *area, int level, + const uint8_t *sysid) +{ + struct isis_adjacency *adj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) { + if (!(adj->level & level)) + continue; + + if (!memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) + return adj; + } + + return NULL; +} + +DEFINE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj)); + +void isis_delete_adj(void *arg) +{ + struct isis_adjacency *adj = arg; + + if (!adj) + return; + /* Remove self from snmp list without walking the list*/ + list_delete_node(adj->circuit->snmp_adj_list, adj->snmp_list_node); + + EVENT_OFF(adj->t_expire); + if (adj->adj_state != ISIS_ADJ_DOWN) + adj->adj_state = ISIS_ADJ_DOWN; + + hook_call(isis_adj_state_change_hook, adj); + + XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses); + XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses); + XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->ll_ipv6_addrs); + XFREE(MTYPE_ISIS_ADJACENCY_INFO, adj->global_ipv6_addrs); + adj_mt_finish(adj); + list_delete(&adj->adj_sids); + list_delete(&adj->srv6_endx_sids); + + listnode_delete(adj->circuit->area->adjacency_list, adj); + XFREE(MTYPE_ISIS_ADJACENCY, adj); + return; +} + +static const char *adj_state2string(int state) +{ + + switch (state) { + case ISIS_ADJ_INITIALIZING: + return "Initializing"; + case ISIS_ADJ_UP: + return "Up"; + case ISIS_ADJ_DOWN: + return "Down"; + default: + return "Unknown"; + } + + return NULL; /* not reached */ +} + +static const char *adj_level2string(int level) +{ + switch (level) { + case IS_LEVEL_1: + return "level-1"; + case IS_LEVEL_2: + return "level-2"; + case IS_LEVEL_1_AND_2: + return "level-1-2"; + default: + return "unknown"; + } + + return NULL; /* not reached */ +} + +static void isis_adj_route_switchover(struct isis_adjacency *adj) +{ + union g_addr ip = {}; + ifindex_t ifindex; + unsigned int i; + + if (!adj->circuit || !adj->circuit->interface) + return; + + ifindex = adj->circuit->interface->ifindex; + + for (i = 0; i < adj->ipv4_address_count; i++) { + ip.ipv4 = adj->ipv4_addresses[i]; + isis_circuit_switchover_routes(adj->circuit, AF_INET, &ip, + ifindex); + } + + for (i = 0; i < adj->ll_ipv6_count; i++) { + ip.ipv6 = adj->ll_ipv6_addrs[i]; + isis_circuit_switchover_routes(adj->circuit, AF_INET6, &ip, + ifindex); + } + + for (i = 0; i < adj->global_ipv6_count; i++) { + ip.ipv6 = adj->global_ipv6_addrs[i]; + isis_circuit_switchover_routes(adj->circuit, AF_INET6, &ip, + ifindex); + } +} + +void isis_adj_process_threeway(struct isis_adjacency **padj, + struct isis_threeway_adj *tw_adj, + enum isis_adj_usage adj_usage) +{ + enum isis_threeway_state next_tw_state = ISIS_THREEWAY_DOWN; + struct isis_adjacency *adj = *padj; + + if (tw_adj && !adj->circuit->disable_threeway_adj) { + if (tw_adj->state == ISIS_THREEWAY_DOWN) { + next_tw_state = ISIS_THREEWAY_INITIALIZING; + } else if (tw_adj->state == ISIS_THREEWAY_INITIALIZING) { + next_tw_state = ISIS_THREEWAY_UP; + } else if (tw_adj->state == ISIS_THREEWAY_UP) { + if (adj->threeway_state == ISIS_THREEWAY_DOWN) + next_tw_state = ISIS_THREEWAY_DOWN; + else + next_tw_state = ISIS_THREEWAY_UP; + } + } else { + next_tw_state = ISIS_THREEWAY_UP; + } + + if (next_tw_state != adj->threeway_state) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_info("ISIS-Adj (%s): Threeway state change %s to %s", + adj->circuit->area->area_tag, + isis_threeway_state_name(adj->threeway_state), + isis_threeway_state_name(next_tw_state)); + } + } + + if (next_tw_state != ISIS_THREEWAY_DOWN) + fabricd_initial_sync_hello(adj->circuit); + + if (next_tw_state == ISIS_THREEWAY_DOWN) { + isis_adj_state_change(padj, ISIS_ADJ_DOWN, "Neighbor restarted"); + return; + } + + if (next_tw_state == ISIS_THREEWAY_UP) { + if (adj->adj_state != ISIS_ADJ_UP) { + isis_adj_state_change(padj, ISIS_ADJ_UP, NULL); + adj->adj_usage = adj_usage; + } + } + + if (adj->threeway_state != next_tw_state) { + send_hello_sched(adj->circuit, 0, TRIGGERED_IIH_DELAY); + } + + adj->threeway_state = next_tw_state; +} +const char *isis_adj_name(const struct isis_adjacency *adj) +{ + static char buf[ISO_SYSID_STRLEN]; + + if (!adj) + return "NONE"; + + struct isis_dynhn *dyn; + + dyn = dynhn_find_by_id(adj->circuit->isis, adj->sysid); + if (dyn) + return dyn->hostname; + + snprintfrr(buf, sizeof(buf), "%pSY", adj->sysid); + return buf; +} +void isis_log_adj_change(struct isis_adjacency *adj, + enum isis_adj_state old_state, + enum isis_adj_state new_state, const char *reason) +{ + zlog_info( + "%%ADJCHANGE: Adjacency to %s (%s) for %s changed from %s to %s, %s", + isis_adj_name(adj), adj->circuit->interface->name, + adj_level2string(adj->level), adj_state2string(old_state), + adj_state2string(new_state), reason ? reason : "unspecified"); +} +void isis_adj_state_change(struct isis_adjacency **padj, + enum isis_adj_state new_state, const char *reason) +{ + struct isis_adjacency *adj = *padj; + enum isis_adj_state old_state = adj->adj_state; + struct isis_circuit *circuit = adj->circuit; + bool del = false; + + if (new_state == old_state) + return; + + if (old_state == ISIS_ADJ_UP && + !CHECK_FLAG(adj->circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z)) { + if (IS_DEBUG_EVENTS) + zlog_debug( + "ISIS-Adj (%s): Starting fast-reroute on state change %d->%d: %s", + circuit->area->area_tag, old_state, new_state, + reason ? reason : "unspecified"); + isis_adj_route_switchover(adj); + } + + adj->adj_state = new_state; + send_hello_sched(circuit, adj->level, TRIGGERED_IIH_DELAY); + + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Adjacency state change %d->%d: %s", + circuit->area->area_tag, old_state, new_state, + reason ? reason : "unspecified"); + } + + if (circuit->area->log_adj_changes) + isis_log_adj_change(adj, old_state, new_state, reason); + +#ifndef FABRICD + /* send northbound notification */ + isis_notif_adj_state_change(adj, new_state, reason); +#endif /* ifndef FABRICD */ + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { + if ((adj->level & level) == 0) + continue; + if (new_state == ISIS_ADJ_UP) { + circuit->adj_state_changes++; + circuit->upadjcount[level - 1]++; + /* update counter & timers for debugging + * purposes */ + adj->last_flap = time(NULL); + adj->flaps++; + } else if (old_state == ISIS_ADJ_UP) { + circuit->adj_state_changes++; + + circuit->upadjcount[level - 1]--; + if (circuit->upadjcount[level - 1] == 0) + isis_tx_queue_clean(circuit->tx_queue); + + if (new_state == ISIS_ADJ_DOWN) { + listnode_delete( + circuit->u.bc.adjdb[level - 1], + adj); + + del = true; + } + } + + if (circuit->u.bc.lan_neighs[level - 1]) { + list_delete_all_node( + circuit->u.bc.lan_neighs[level - 1]); + isis_adj_build_neigh_list( + circuit->u.bc.adjdb[level - 1], + circuit->u.bc.lan_neighs[level - 1]); + } + + /* On adjacency state change send new pseudo LSP if we + * are the DR */ + if (circuit->u.bc.is_dr[level - 1]) + lsp_regenerate_schedule_pseudo(circuit, level); + } + + } else if (circuit->circ_type == CIRCUIT_T_P2P) { + for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { + if ((adj->level & level) == 0) + continue; + if (new_state == ISIS_ADJ_UP) { + circuit->upadjcount[level - 1]++; + + /* update counter & timers for debugging + * purposes */ + adj->last_flap = time(NULL); + adj->flaps++; + + if (level == IS_LEVEL_1) { + event_add_timer( + master, send_l1_csnp, circuit, + 0, &circuit->t_send_csnp[0]); + } else { + event_add_timer( + master, send_l2_csnp, circuit, + 0, &circuit->t_send_csnp[1]); + } + } else if (old_state == ISIS_ADJ_UP) { + circuit->upadjcount[level - 1]--; + if (circuit->upadjcount[level - 1] == 0) + isis_tx_queue_clean(circuit->tx_queue); + + if (new_state == ISIS_ADJ_DOWN) { + if (adj->circuit->u.p2p.neighbor == adj) + adj->circuit->u.p2p.neighbor = + NULL; + + del = true; + } + } + } + } + + hook_call(isis_adj_state_change_hook, adj); + + if (del) { + isis_delete_adj(adj); + *padj = NULL; + } +} + + +void isis_adj_print(struct isis_adjacency *adj) +{ + struct isis_dynhn *dyn; + + if (!adj) + return; + dyn = dynhn_find_by_id(adj->circuit->isis, adj->sysid); + if (dyn) + zlog_debug("%s", dyn->hostname); + + zlog_debug("SystemId %20pSY SNPA %pSY, level %d; Holding Time %d", + adj->sysid, adj->snpa, adj->level, adj->hold_time); + if (adj->ipv4_address_count) { + zlog_debug("IPv4 Address(es):"); + for (unsigned int i = 0; i < adj->ipv4_address_count; i++) + zlog_debug("%pI4", &adj->ipv4_addresses[i]); + } + + if (adj->ll_ipv6_count) { + zlog_debug("IPv6 Address(es):"); + for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i], buf, + sizeof(buf)); + zlog_debug("%s", buf); + } + } + zlog_debug("Speaks: %s", nlpid2string(&adj->nlpids)); + + return; +} + +const char *isis_adj_yang_state(enum isis_adj_state state) +{ + switch (state) { + case ISIS_ADJ_DOWN: + return "down"; + case ISIS_ADJ_UP: + return "up"; + case ISIS_ADJ_INITIALIZING: + return "init"; + case ISIS_ADJ_UNKNOWN: + return "failed"; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +void isis_adj_expire(struct event *thread) +{ + struct isis_adjacency *adj; + + /* + * Get the adjacency + */ + adj = EVENT_ARG(thread); + assert(adj); + adj->t_expire = NULL; + + /* trigger the adj expire event */ + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "holding time expired"); +} + +/* + * show isis neighbor [detail] json + */ +void isis_adj_print_json(struct isis_adjacency *adj, struct json_object *json, + char detail) +{ + json_object *iface_json, *ipv4_addr_json, *ipv6_link_json, + *ipv6_non_link_json, *topo_json, *dis_flaps_json, + *area_addr_json, *adj_sid_json; + time_t now; + struct isis_dynhn *dyn; + int level; + char buf[256]; + + json_object_string_add(json, "adj", isis_adj_name(adj)); + + if (detail == ISIS_UI_LEVEL_BRIEF) { + if (adj->circuit) + json_object_string_add(json, "interface", + adj->circuit->interface->name); + else + json_object_string_add(json, "interface", + "NULL circuit!"); + json_object_int_add(json, "level", adj->level); + json_object_string_add(json, "state", + adj_state2string(adj->adj_state)); + now = time(NULL); + if (adj->last_upd) { + if (adj->last_upd + adj->hold_time < now) + json_object_string_add(json, "last-upd", + "expiring"); + else + json_object_string_add( + json, "expires-in", + time2string(adj->last_upd + + adj->hold_time - now)); + } + json_object_string_addf(json, "snpa", "%pSY", adj->snpa); + } + + if (detail == ISIS_UI_LEVEL_DETAIL) { + struct sr_adjacency *sra; + struct listnode *anode; + + level = adj->level; + iface_json = json_object_new_object(); + json_object_object_add(json, "interface", iface_json); + if (adj->circuit) + json_object_string_add(iface_json, "name", + adj->circuit->interface->name); + else + json_object_string_add(iface_json, "name", + "null-circuit"); + json_object_int_add(json, "level", adj->level); + json_object_string_add(iface_json, "state", + adj_state2string(adj->adj_state)); + now = time(NULL); + if (adj->last_upd) { + if (adj->last_upd + adj->hold_time < now) + json_object_string_add(iface_json, "last-upd", + "expiring"); + else + json_object_string_add( + json, "expires-in", + time2string(adj->last_upd + + adj->hold_time - now)); + } else + json_object_string_add(json, "expires-in", + time2string(adj->hold_time)); + json_object_int_add(iface_json, "adj-flaps", adj->flaps); + json_object_string_add(iface_json, "last-ago", + time2string(now - adj->last_flap)); + json_object_string_add(iface_json, "circuit-type", + circuit_t2string(adj->circuit_t)); + json_object_string_add(iface_json, "speaks", + nlpid2string(&adj->nlpids)); + if (adj->mt_count != 1 || + adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) { + topo_json = json_object_new_object(); + json_object_object_add(iface_json, "topologies", + topo_json); + for (unsigned int i = 0; i < adj->mt_count; i++) { + snprintfrr(buf, sizeof(buf), "topo-%d", i); + json_object_string_add( + topo_json, buf, + isis_mtid2str(adj->mt_set[i])); + } + } + json_object_string_addf(iface_json, "snpa", "%pSY", adj->snpa); + if (adj->circuit && + (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) { + dyn = dynhn_find_by_id(adj->circuit->isis, adj->lanid); + if (dyn) { + snprintfrr(buf, sizeof(buf), "%s-%02x", + dyn->hostname, + adj->lanid[ISIS_SYS_ID_LEN]); + json_object_string_add(iface_json, "lan-id", + buf); + } else { + json_object_string_addf(iface_json, "lan-id", + "%pSY", adj->lanid); + } + + json_object_int_add(iface_json, "lan-prio", + adj->prio[adj->level - 1]); + + dis_flaps_json = json_object_new_object(); + json_object_object_add(iface_json, "dis-flaps", + dis_flaps_json); + json_object_string_add( + dis_flaps_json, "dis-record", + isis_disflag2string( + adj->dis_record[ISIS_LEVELS + level - 1] + .dis)); + json_object_int_add(dis_flaps_json, "last", + adj->dischanges[level - 1]); + json_object_string_add( + dis_flaps_json, "ago", + time2string(now - (adj->dis_record[ISIS_LEVELS + + level - 1] + .last_dis_change))); + } + + if (adj->area_address_count) { + area_addr_json = json_object_new_object(); + json_object_object_add(iface_json, "area-address", + area_addr_json); + for (unsigned int i = 0; i < adj->area_address_count; + i++) { + json_object_string_addf( + area_addr_json, "isonet", "%pIS", + &adj->area_addresses[i]); + } + } + if (adj->ipv4_address_count) { + ipv4_addr_json = json_object_new_object(); + json_object_object_add(iface_json, "ipv4-address", + ipv4_addr_json); + for (unsigned int i = 0; i < adj->ipv4_address_count; + i++){ + inet_ntop(AF_INET, &adj->ipv4_addresses[i], buf, + sizeof(buf)); + json_object_string_add(ipv4_addr_json, "ipv4", buf); + } + } + if (adj->ll_ipv6_count) { + ipv6_link_json = json_object_new_object(); + json_object_object_add(iface_json, "ipv6-link-local", + ipv6_link_json); + for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i], buf, + sizeof(buf)); + json_object_string_add(ipv6_link_json, "ipv6", + buf); + } + } + if (adj->global_ipv6_count) { + ipv6_non_link_json = json_object_new_object(); + json_object_object_add(iface_json, "ipv6-global", + ipv6_non_link_json); + for (unsigned int i = 0; i < adj->global_ipv6_count; + i++) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &adj->global_ipv6_addrs[i], + buf, sizeof(buf)); + json_object_string_add(ipv6_non_link_json, + "ipv6", buf); + } + } + + adj_sid_json = json_object_new_object(); + json_object_object_add(iface_json, "adj-sid", adj_sid_json); + for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) { + const char *adj_type; + const char *backup; + uint32_t sid; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + adj_type = "LAN Adjacency-SID"; + sid = sra->u.ladj_sid->sid; + break; + case CIRCUIT_T_P2P: + adj_type = "Adjacency-SID"; + sid = sra->u.adj_sid->sid; + break; + default: + continue; + } + backup = (sra->type == ISIS_SR_ADJ_BACKUP) ? " (backup)" + : ""; + + json_object_string_add(adj_sid_json, "nexthop", + (sra->nexthop.family == AF_INET) + ? "IPv4" + : "IPv6"); + json_object_string_add(adj_sid_json, "adj-type", + adj_type); + json_object_string_add(adj_sid_json, "is-backup", + backup); + json_object_int_add(adj_sid_json, "sid", sid); + } + } + return; +} + +/* + * show isis neighbor [detail] + */ +void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, + char detail) +{ + time_t now; + struct isis_dynhn *dyn; + int level; + + vty_out(vty, " %-20s", isis_adj_name(adj)); + + if (detail == ISIS_UI_LEVEL_BRIEF) { + if (adj->circuit) + vty_out(vty, "%-12s", adj->circuit->interface->name); + else + vty_out(vty, "NULL circuit!"); + vty_out(vty, "%-3u", adj->level); /* level */ + vty_out(vty, "%-13s", adj_state2string(adj->adj_state)); + now = time(NULL); + if (adj->last_upd) { + if (adj->last_upd + adj->hold_time < now) + vty_out(vty, " Expiring "); + else + vty_out(vty, " %-9llu", + (unsigned long long)adj->last_upd + + adj->hold_time - now); + } else + vty_out(vty, " - "); + vty_out(vty, "%-10pSY", adj->snpa); + vty_out(vty, "\n"); + } + + if (detail == ISIS_UI_LEVEL_DETAIL) { + struct sr_adjacency *sra; + struct listnode *anode; + + level = adj->level; + vty_out(vty, "\n"); + if (adj->circuit) + vty_out(vty, " Interface: %s", + adj->circuit->interface->name); + else + vty_out(vty, " Interface: NULL circuit"); + vty_out(vty, ", Level: %u", adj->level); /* level */ + vty_out(vty, ", State: %s", adj_state2string(adj->adj_state)); + now = time(NULL); + if (adj->last_upd) { + if (adj->last_upd + adj->hold_time < now) + vty_out(vty, " Expiring"); + else + vty_out(vty, ", Expires in %s", + time2string(adj->last_upd + + adj->hold_time - now)); + } else + vty_out(vty, ", Expires in %s", + time2string(adj->hold_time)); + vty_out(vty, "\n"); + vty_out(vty, " Adjacency flaps: %u", adj->flaps); + vty_out(vty, ", Last: %s ago", + time2string(now - adj->last_flap)); + vty_out(vty, "\n"); + vty_out(vty, " Circuit type: %s", + circuit_t2string(adj->circuit_t)); + vty_out(vty, ", Speaks: %s", nlpid2string(&adj->nlpids)); + vty_out(vty, "\n"); + if (adj->mt_count != 1 + || adj->mt_set[0] != ISIS_MT_IPV4_UNICAST) { + vty_out(vty, " Topologies:\n"); + for (unsigned int i = 0; i < adj->mt_count; i++) + vty_out(vty, " %s\n", + isis_mtid2str(adj->mt_set[i])); + } + vty_out(vty, " SNPA: %pSY", adj->snpa); + if (adj->circuit + && (adj->circuit->circ_type == CIRCUIT_T_BROADCAST)) { + dyn = dynhn_find_by_id(adj->circuit->isis, adj->lanid); + if (dyn) + vty_out(vty, ", LAN id: %s.%02x", dyn->hostname, + adj->lanid[ISIS_SYS_ID_LEN]); + else + vty_out(vty, ", LAN id: %pPN", adj->lanid); + + vty_out(vty, "\n"); + vty_out(vty, " LAN Priority: %u", + adj->prio[adj->level - 1]); + + vty_out(vty, ", %s, DIS flaps: %u, Last: %s ago", + isis_disflag2string( + adj->dis_record[ISIS_LEVELS + level - 1] + .dis), + adj->dischanges[level - 1], + time2string(now - (adj->dis_record[ISIS_LEVELS + + level - 1] + .last_dis_change))); + } + vty_out(vty, "\n"); + + if (adj->area_address_count) { + vty_out(vty, " Area Address(es):\n"); + for (unsigned int i = 0; i < adj->area_address_count; + i++) { + vty_out(vty, " %pIS\n", + &adj->area_addresses[i]); + } + } + if (adj->ipv4_address_count) { + vty_out(vty, " IPv4 Address(es):\n"); + for (unsigned int i = 0; i < adj->ipv4_address_count; + i++) + vty_out(vty, " %pI4\n", + &adj->ipv4_addresses[i]); + } + if (adj->ll_ipv6_count) { + vty_out(vty, " IPv6 Address(es):\n"); + for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &adj->ll_ipv6_addrs[i], + buf, sizeof(buf)); + vty_out(vty, " %s\n", buf); + } + } + if (adj->global_ipv6_count) { + vty_out(vty, " Global IPv6 Address(es):\n"); + for (unsigned int i = 0; i < adj->global_ipv6_count; + i++) { + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &adj->global_ipv6_addrs[i], + buf, sizeof(buf)); + vty_out(vty, " %s\n", buf); + } + } + if (adj->circuit && adj->circuit->bfd_config.enabled) { + vty_out(vty, " BFD is %s%s\n", + adj->bfd_session ? "active, status " + : "configured", + !adj->bfd_session + ? "" + : bfd_get_status_str(bfd_sess_status( + adj->bfd_session))); + } + for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, anode, sra)) { + const char *adj_type; + const char *backup; + uint32_t sid; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + adj_type = "LAN Adjacency-SID"; + sid = sra->u.ladj_sid->sid; + break; + case CIRCUIT_T_P2P: + adj_type = "Adjacency-SID"; + sid = sra->u.adj_sid->sid; + break; + default: + continue; + } + backup = (sra->type == ISIS_SR_ADJ_BACKUP) ? " (backup)" + : ""; + + vty_out(vty, " %s %s%s: %u\n", + (sra->nexthop.family == AF_INET) ? "IPv4" + : "IPv6", + adj_type, backup, sid); + } + vty_out(vty, "\n"); + } + return; +} + +void isis_adj_build_neigh_list(struct list *adjdb, struct list *list) +{ + struct isis_adjacency *adj; + struct listnode *node; + + if (!list) { + zlog_warn("%s: NULL list", __func__); + return; + } + + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) { + if (!adj) { + zlog_warn("%s: NULL adj", __func__); + return; + } + + if ((adj->adj_state == ISIS_ADJ_UP + || adj->adj_state == ISIS_ADJ_INITIALIZING)) + listnode_add(list, adj->snpa); + } + return; +} + +void isis_adj_build_up_list(struct list *adjdb, struct list *list) +{ + struct isis_adjacency *adj; + struct listnode *node; + + if (adjdb == NULL) { + zlog_warn("%s: adjacency DB is empty", __func__); + return; + } + + if (!list) { + zlog_warn("%s: NULL list", __func__); + return; + } + + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) { + if (!adj) { + zlog_warn("%s: NULL adj", __func__); + return; + } + + if (adj->adj_state == ISIS_ADJ_UP) + listnode_add(list, adj); + } + + return; +} + +int isis_adj_usage2levels(enum isis_adj_usage usage) +{ + switch (usage) { + case ISIS_ADJ_LEVEL1: + return IS_LEVEL_1; + case ISIS_ADJ_LEVEL2: + return IS_LEVEL_2; + case ISIS_ADJ_LEVEL1AND2: + return IS_LEVEL_1 | IS_LEVEL_2; + case ISIS_ADJ_NONE: + return 0; + } + + assert(!"Reached end of function where we are not expecting to"); +} diff --git a/isisd/isis_adjacency.h b/isisd/isis_adjacency.h new file mode 100644 index 0000000..0ad36e4 --- /dev/null +++ b/isisd/isis_adjacency.h @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_adjacency.h + * IS-IS adjacency handling + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * + */ + +#ifndef _ZEBRA_ISIS_ADJACENCY_H +#define _ZEBRA_ISIS_ADJACENCY_H + +#include "isisd/isis_tlvs.h" + +DECLARE_MTYPE(ISIS_ADJACENCY_INFO); + +enum isis_adj_usage { + ISIS_ADJ_NONE, + ISIS_ADJ_LEVEL1, + ISIS_ADJ_LEVEL2, + ISIS_ADJ_LEVEL1AND2 +}; + +enum isis_system_type { + ISIS_SYSTYPE_UNKNOWN, + ISIS_SYSTYPE_ES, + ISIS_SYSTYPE_IS, + ISIS_SYSTYPE_L1_IS, + ISIS_SYSTYPE_L2_IS +}; + +enum isis_adj_state { + ISIS_ADJ_UNKNOWN, + ISIS_ADJ_INITIALIZING, + ISIS_ADJ_UP, + ISIS_ADJ_DOWN +}; + +/* + * we use the following codes to give an indication _why_ + * a specific adjacency is up or down + */ +enum isis_adj_updown_reason { + ISIS_ADJ_REASON_SEENSELF, + ISIS_ADJ_REASON_AREA_MISMATCH, + ISIS_ADJ_REASON_HOLDTIMER_EXPIRED, + ISIS_ADJ_REASON_AUTH_FAILED, + ISIS_ADJ_REASON_CHECKSUM_FAILED +}; + +#define DIS_RECORDS 8 /* keep the last 8 DIS state changes on record */ + +struct isis_dis_record { + int dis; /* is our neighbor the DIS ? */ + time_t last_dis_change; /* timestamp for last dis change */ +}; + +struct bfd_session; +struct isis_area; + +struct isis_adjacency { + uint8_t snpa[ETH_ALEN]; /* NeighbourSNPAAddress */ + uint8_t sysid[ISIS_SYS_ID_LEN]; /* neighbourSystemIdentifier */ + uint8_t lanid[ISIS_SYS_ID_LEN + 1]; /* LAN id on bcast circuits */ + int dischanges[ISIS_LEVELS]; /* how many DIS changes ? */ + /* an array of N levels for M records */ + struct isis_dis_record dis_record[DIS_RECORDS * ISIS_LEVELS]; + enum isis_adj_state adj_state; /* adjacencyState */ + enum isis_adj_usage adj_usage; /* adjacencyUsage */ + struct iso_address *area_addresses; /* areaAdressesOfNeighbour */ + unsigned int area_address_count; + struct nlpids nlpids; /* protocols spoken ... */ + struct in_addr *ipv4_addresses; + unsigned int ipv4_address_count; + struct in6_addr *ll_ipv6_addrs; /* Link local IPv6 neighbor address */ + unsigned int ll_ipv6_count; + struct in6_addr *global_ipv6_addrs; /* Global IPv6 neighbor address */ + unsigned int global_ipv6_count; + uint8_t prio[ISIS_LEVELS]; /* priorityOfNeighbour for DIS */ + int circuit_t; /* from hello PDU hdr */ + int level; /* level (1 or 2) */ + enum isis_system_type sys_type; /* neighbourSystemType */ + uint16_t hold_time; /* entryRemainingTime */ + time_t last_upd; + time_t last_flap; /* last time the adj flapped */ + enum isis_threeway_state threeway_state; + uint32_t ext_circuit_id; + int flaps; /* number of adjacency flaps */ + struct event *t_expire; /* expire after hold_time */ + struct isis_circuit *circuit; /* back pointer */ + uint16_t *mt_set; /* Topologies this adjacency is valid for */ + unsigned int mt_count; /* Number of entries in mt_set */ + struct bfd_session_params *bfd_session; + struct list *adj_sids; /* Segment Routing Adj-SIDs. */ + uint32_t snmp_idx; + struct listnode *snmp_list_node; + + struct list *srv6_endx_sids; /* SRv6 End.X SIDs. */ +}; + +struct isis_threeway_adj; + +struct isis_adjacency *isis_adj_lookup(const uint8_t *sysid, + struct list *adjdb); +struct isis_adjacency *isis_adj_lookup_snpa(const uint8_t *ssnpa, + struct list *adjdb); +struct isis_adjacency *isis_adj_find(const struct isis_area *area, int level, + const uint8_t *sysid); +struct isis_adjacency *isis_new_adj(const uint8_t *id, const uint8_t *snpa, + int level, struct isis_circuit *circuit); +void isis_delete_adj(void *adj); +void isis_adj_process_threeway(struct isis_adjacency **padj, + struct isis_threeway_adj *tw_adj, + enum isis_adj_usage adj_usage); +DECLARE_HOOK(isis_adj_state_change_hook, (struct isis_adjacency *adj), (adj)); +DECLARE_HOOK(isis_adj_ip_enabled_hook, + (struct isis_adjacency * adj, int family, bool global), + (adj, family, global)); +DECLARE_HOOK(isis_adj_ip_disabled_hook, + (struct isis_adjacency * adj, int family, bool global), + (adj, family, global)); +void isis_log_adj_change(struct isis_adjacency *adj, + enum isis_adj_state old_state, + enum isis_adj_state new_state, const char *reason); +void isis_adj_state_change(struct isis_adjacency **adj, + enum isis_adj_state state, const char *reason); +void isis_adj_print(struct isis_adjacency *adj); +const char *isis_adj_yang_state(enum isis_adj_state state); +void isis_adj_expire(struct event *thread); +void isis_adj_print_vty(struct isis_adjacency *adj, struct vty *vty, + char detail); +void isis_adj_print_json(struct isis_adjacency *adj, struct json_object *json, + char detail); +void isis_adj_build_neigh_list(struct list *adjdb, struct list *list); +void isis_adj_build_up_list(struct list *adjdb, struct list *list); +int isis_adj_usage2levels(enum isis_adj_usage usage); +void isis_bfd_startup_timer(struct event *thread); +const char *isis_adj_name(const struct isis_adjacency *adj); +#endif /* ISIS_ADJACENCY_H */ diff --git a/isisd/isis_affinitymap.c b/isisd/isis_affinitymap.c new file mode 100644 index 0000000..595091d --- /dev/null +++ b/isisd/isis_affinitymap.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* IS-IS affinity-map + * Copyright 2023 6WIND S.A. + */ + +#include +#include "lib/if.h" +#include "lib/vrf.h" +#include "isisd/isisd.h" +#include "isisd/isis_affinitymap.h" + +#ifndef FABRICD + +static void isis_affinity_map_update(const char *affmap_name, uint16_t old_pos, + uint16_t new_pos) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct listnode *area_node, *fa_node; + struct isis_area *area; + struct flex_algo *fa; + bool changed; + + if (!isis) + return; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, area_node, area)) { + changed = false; + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, fa_node, + fa)) { + if (admin_group_get(&fa->admin_group_exclude_any, + old_pos)) { + admin_group_unset(&fa->admin_group_exclude_any, + old_pos); + admin_group_set(&fa->admin_group_exclude_any, + new_pos); + changed = true; + } + if (admin_group_get(&fa->admin_group_include_any, + old_pos)) { + admin_group_unset(&fa->admin_group_include_any, + old_pos); + admin_group_set(&fa->admin_group_include_any, + new_pos); + changed = true; + } + if (admin_group_get(&fa->admin_group_include_all, + old_pos)) { + admin_group_unset(&fa->admin_group_include_all, + old_pos); + admin_group_set(&fa->admin_group_include_all, + new_pos); + changed = true; + } + } + if (changed) + lsp_regenerate_schedule(area, area->is_type, 0); + } +} + +void isis_affinity_map_init(void) +{ + affinity_map_init(); + + affinity_map_set_update_hook(isis_affinity_map_update); +} + +#endif /* ifndef FABRICD */ diff --git a/isisd/isis_affinitymap.h b/isisd/isis_affinitymap.h new file mode 100644 index 0000000..c432e99 --- /dev/null +++ b/isisd/isis_affinitymap.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* IS-IS affinity-map header + * Copyright 2023 6WIND S.A. + */ + +#ifndef __ISIS_AFFINITYMAP_H__ +#define __ISIS_AFFINITYMAP_H__ + +#include "lib/affinitymap.h" + +#ifndef FABRICD + +#ifdef __cplusplus +extern "C" { +#endif + +extern void isis_affinity_map_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef FABRICD */ + +#endif /* __ISIS_AFFINITYMAP_H__ */ diff --git a/isisd/isis_bfd.c b/isisd/isis_bfd.c new file mode 100644 index 0000000..5e24e35 --- /dev/null +++ b/isisd/isis_bfd.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - BFD support + * Copyright (C) 2018 Christian Franke + */ +#include + +#include "zclient.h" +#include "nexthop.h" +#include "bfd.h" +#include "lib_errors.h" + +#include "isisd/isis_bfd.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_common.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/fabricd.h" + +DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session"); + +static void adj_bfd_cb(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, void *arg) +{ + struct isis_adjacency *adj = arg; + + if (IS_DEBUG_BFD) + zlog_debug( + "ISIS-BFD: BFD changed status for adjacency %s old %s new %s", + isis_adj_name(adj), + bfd_get_status_str(bss->previous_state), + bfd_get_status_str(bss->state)); + + if (bss->state == BFD_STATUS_DOWN + && bss->previous_state == BFD_STATUS_UP) { + adj->circuit->area->bfd_signalled_down = true; + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, + "bfd session went down"); + } +} + +static void bfd_handle_adj_down(struct isis_adjacency *adj) +{ + bfd_sess_free(&adj->bfd_session); +} + +static void bfd_handle_adj_up(struct isis_adjacency *adj) +{ + struct isis_circuit *circuit = adj->circuit; + int family; + union g_addr dst_ip; + union g_addr src_ip; + struct list *local_ips; + struct prefix *local_ip; + + if (!circuit->bfd_config.enabled) { + if (IS_DEBUG_BFD) + zlog_debug( + "ISIS-BFD: skipping BFD initialization on adjacency with %s because BFD is not enabled for the circuit", + isis_adj_name(adj)); + goto out; + } + + /* If IS-IS IPv6 is configured wait for IPv6 address to be programmed + * before starting up BFD + */ + if (circuit->ipv6_router + && (listcount(circuit->ipv6_link) == 0 + || adj->ll_ipv6_count == 0)) { + if (IS_DEBUG_BFD) + zlog_debug( + "ISIS-BFD: skipping BFD initialization on adjacency with %s because IPv6 is enabled but not ready", + isis_adj_name(adj)); + return; + } + + /* + * If IS-IS is enabled for both IPv4 and IPv6 on the circuit, prefer + * creating a BFD session over IPv6. + */ + if (circuit->ipv6_router && adj->ll_ipv6_count) { + family = AF_INET6; + dst_ip.ipv6 = adj->ll_ipv6_addrs[0]; + local_ips = circuit->ipv6_link; + if (list_isempty(local_ips)) { + if (IS_DEBUG_BFD) + zlog_debug( + "ISIS-BFD: skipping BFD initialization: IPv6 enabled and no local IPv6 addresses"); + goto out; + } + local_ip = listgetdata(listhead(local_ips)); + src_ip.ipv6 = local_ip->u.prefix6; + } else if (circuit->ip_router && adj->ipv4_address_count) { + family = AF_INET; + dst_ip.ipv4 = adj->ipv4_addresses[0]; + local_ips = fabricd_ip_addrs(adj->circuit); + if (!local_ips || list_isempty(local_ips)) { + if (IS_DEBUG_BFD) + zlog_debug( + "ISIS-BFD: skipping BFD initialization: IPv4 enabled and no local IPv4 addresses"); + goto out; + } + local_ip = listgetdata(listhead(local_ips)); + src_ip.ipv4 = local_ip->u.prefix4; + } else + goto out; + + if (adj->bfd_session == NULL) + adj->bfd_session = bfd_sess_new(adj_bfd_cb, adj); + + bfd_sess_set_timers(adj->bfd_session, BFD_DEF_DETECT_MULT, + BFD_DEF_MIN_RX, BFD_DEF_MIN_TX); + if (family == AF_INET) + bfd_sess_set_ipv4_addrs(adj->bfd_session, &src_ip.ipv4, + &dst_ip.ipv4); + else + bfd_sess_set_ipv6_addrs(adj->bfd_session, &src_ip.ipv6, + &dst_ip.ipv6); + bfd_sess_set_interface(adj->bfd_session, adj->circuit->interface->name); + bfd_sess_set_vrf(adj->bfd_session, + adj->circuit->interface->vrf->vrf_id); + bfd_sess_set_profile(adj->bfd_session, circuit->bfd_config.profile); + bfd_sess_install(adj->bfd_session); + return; +out: + bfd_handle_adj_down(adj); +} + +static int bfd_handle_adj_state_change(struct isis_adjacency *adj) +{ + if (adj->adj_state == ISIS_ADJ_UP) + bfd_handle_adj_up(adj); + else + bfd_handle_adj_down(adj); + return 0; +} + +static void bfd_adj_cmd(struct isis_adjacency *adj) +{ + if (adj->adj_state == ISIS_ADJ_UP && adj->circuit->bfd_config.enabled) + bfd_handle_adj_up(adj); + else + bfd_handle_adj_down(adj); +} + +void isis_bfd_circuit_cmd(struct isis_circuit *circuit) +{ + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + struct list *adjdb = circuit->u.bc.adjdb[level - 1]; + + struct listnode *node; + struct isis_adjacency *adj; + + if (!adjdb) + continue; + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) + bfd_adj_cmd(adj); + } + break; + case CIRCUIT_T_P2P: + if (circuit->u.p2p.neighbor) + bfd_adj_cmd(circuit->u.p2p.neighbor); + break; + default: + break; + } +} + +static int bfd_handle_adj_ip_enabled(struct isis_adjacency *adj, int family, + bool global) +{ + + if (family != AF_INET6 || global) + return 0; + + if (adj->bfd_session) + return 0; + + if (adj->adj_state != ISIS_ADJ_UP) + return 0; + + bfd_handle_adj_up(adj); + + return 0; +} + +static int bfd_handle_circuit_add_addr(struct isis_circuit *circuit) +{ + struct isis_adjacency *adj; + struct listnode *node; + + if (circuit->area == NULL) + return 0; + + for (ALL_LIST_ELEMENTS_RO(circuit->area->adjacency_list, node, adj)) { + if (adj->bfd_session) + continue; + + if (adj->adj_state != ISIS_ADJ_UP) + continue; + + bfd_handle_adj_up(adj); + } + + return 0; +} + +void isis_bfd_init(struct event_loop *tm) +{ + bfd_protocol_integration_init(zclient, tm); + + hook_register(isis_adj_state_change_hook, bfd_handle_adj_state_change); + hook_register(isis_adj_ip_enabled_hook, bfd_handle_adj_ip_enabled); + hook_register(isis_circuit_add_addr_hook, bfd_handle_circuit_add_addr); +} diff --git a/isisd/isis_bfd.h b/isisd/isis_bfd.h new file mode 100644 index 0000000..3cf0ed5 --- /dev/null +++ b/isisd/isis_bfd.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - BFD support + * Copyright (C) 2018 Christian Franke + */ +#ifndef ISIS_BFD_H +#define ISIS_BFD_H + +struct isis_circuit; +struct event_loop; + +void isis_bfd_circuit_cmd(struct isis_circuit *circuit); +void isis_bfd_init(struct event_loop *tm); + +#endif + diff --git a/isisd/isis_bpf.c b/isisd/isis_bpf.c new file mode 100644 index 0000000..47f51a7 --- /dev/null +++ b/isisd/isis_bpf.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_bpf.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include + +#if ISIS_METHOD == ISIS_METHOD_BPF +#include +#include +#include +#include +#include + +#include "log.h" +#include "network.h" +#include "stream.h" +#include "if.h" +#include "lib_errors.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_flags.h" +#include "isisd/isisd.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_network.h" +#include "isisd/isis_pdu.h" + +#include "privs.h" + +struct bpf_insn llcfilter[] = { + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, + ETHER_HDR_LEN), /* check first byte */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0, 5), + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 1), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ISO_SAP, 0, + 3), /* check second byte */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHER_HDR_LEN + 2), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x03, 0, 1), /* check third byte */ + BPF_STMT(BPF_RET + BPF_K, (unsigned int)-1), + BPF_STMT(BPF_RET + BPF_K, 0)}; +unsigned int readblen = 0; +uint8_t *readbuff = NULL; + +/* + * Table 9 - Architectural constants for use with ISO 8802 subnetworks + * ISO 10589 - 8.4.8 + */ + +static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; +static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; +static char sock_buff[16384]; + +static int open_bpf_dev(struct isis_circuit *circuit) +{ + int i = 0, fd; + char bpfdev[128]; + struct ifreq ifr; + unsigned int blen, immediate; +#ifdef BIOCSSEESENT + unsigned int seesent; +#endif + struct timeval timeout; + struct bpf_program bpf_prog; + + do { + (void)snprintf(bpfdev, sizeof(bpfdev), "/dev/bpf%d", i++); + fd = open(bpfdev, O_RDWR); + } while (fd < 0 && errno == EBUSY); + + if (fd < 0) { + zlog_warn("open_bpf_dev(): failed to create bpf socket: %s", + safe_strerror(errno)); + return ISIS_WARNING; + } + + zlog_debug("Opened BPF device %s", bpfdev); + + memcpy(ifr.ifr_name, circuit->interface->name, sizeof(ifr.ifr_name)); + if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) < 0) { + zlog_warn("open_bpf_dev(): failed to bind to interface: %s", + safe_strerror(errno)); + return ISIS_WARNING; + } + + if (ioctl(fd, BIOCGBLEN, (caddr_t)&blen) < 0) { + zlog_warn("failed to get BPF buffer len"); + blen = circuit->interface->mtu; + } + + readblen = blen; + + if (readbuff == NULL) + readbuff = malloc(blen); + + zlog_debug("BPF buffer len = %u", blen); + + /* BPF(4): reads return immediately upon packet reception. + * Otherwise, a read will block until either the kernel + * buffer becomes full or a timeout occurs. + */ + immediate = 1; + if (ioctl(fd, BIOCIMMEDIATE, (caddr_t)&immediate) < 0) { + zlog_warn("failed to set BPF dev to immediate mode"); + } + +#ifdef BIOCSSEESENT + /* + * We want to see only incoming packets + */ + seesent = 0; + if (ioctl(fd, BIOCSSEESENT, (caddr_t)&seesent) < 0) { + zlog_warn("failed to set BPF dev to incoming only mode"); + } +#endif + + /* + * ...but all of them + */ + if (ioctl(fd, BIOCPROMISC) < 0) { + zlog_warn("failed to set BPF dev to promiscuous mode"); + } + + /* + * If the buffer length is smaller than our mtu, lets try to increase it + */ + if (blen < circuit->interface->mtu) { + if (ioctl(fd, BIOCSBLEN, &circuit->interface->mtu) < 0) { + zlog_warn("failed to set BPF buffer len (%u to %u)", + blen, circuit->interface->mtu); + } + } + + /* + * Set a timeout parameter - hope this helps select() + */ + timeout.tv_sec = 600; + timeout.tv_usec = 0; + if (ioctl(fd, BIOCSRTIMEOUT, (caddr_t)&timeout) < 0) { + zlog_warn("failed to set BPF device timeout"); + } + + /* + * And set the filter + */ + memset(&bpf_prog, 0, sizeof(bpf_prog)); + bpf_prog.bf_len = 8; + bpf_prog.bf_insns = &(llcfilter[0]); + if (ioctl(fd, BIOCSETF, (caddr_t)&bpf_prog) < 0) { + zlog_warn("%s: failed to install filter: %s", __func__, + safe_strerror(errno)); + return ISIS_WARNING; + } + + assert(fd > 0); + + circuit->fd = fd; + + return ISIS_OK; +} + +/* + * Create the socket and set the tx/rx funcs + */ +int isis_sock_init(struct isis_circuit *circuit) +{ + int retval = ISIS_OK; + + frr_with_privs(&isisd_privs) { + + retval = open_bpf_dev(circuit); + + if (retval != ISIS_OK) { + zlog_warn("%s: could not initialize the socket", + __func__); + break; + } + + if (if_is_broadcast(circuit->interface)) { + circuit->tx = isis_send_pdu_bcast; + circuit->rx = isis_recv_pdu_bcast; + } else { + zlog_warn("%s: unknown circuit type", __func__); + retval = ISIS_WARNING; + break; + } + } + + return retval; +} + +int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) +{ + int bytesread = 0, bytestoread = 0, offset, one = 1; + uint8_t *buff_ptr; + struct bpf_hdr *bpf_hdr; + + assert(circuit->fd > 0); + + if (ioctl(circuit->fd, FIONREAD, (caddr_t)&bytestoread) < 0) { + zlog_warn("ioctl() FIONREAD failed: %s", safe_strerror(errno)); + } + + if (bytestoread) { + bytesread = read(circuit->fd, readbuff, readblen); + } + if (bytesread < 0) { + zlog_warn("%s: read() failed: %s", __func__, + safe_strerror(errno)); + return ISIS_WARNING; + } + + if (bytesread == 0) + return ISIS_WARNING; + + buff_ptr = readbuff; + while (buff_ptr < readbuff + bytesread) { + bpf_hdr = (struct bpf_hdr *) buff_ptr; + assert(bpf_hdr->bh_caplen == bpf_hdr->bh_datalen); + offset = bpf_hdr->bh_hdrlen + LLC_LEN + ETHER_HDR_LEN; + + /* then we lose the BPF, LLC and ethernet headers */ + stream_write(circuit->rcv_stream, buff_ptr + offset, + bpf_hdr->bh_caplen - LLC_LEN - ETHER_HDR_LEN); + stream_set_getp(circuit->rcv_stream, 0); + + memcpy(ssnpa, buff_ptr + bpf_hdr->bh_hdrlen + ETHER_ADDR_LEN, + ETHER_ADDR_LEN); + + isis_handle_pdu(circuit, ssnpa); + stream_reset(circuit->rcv_stream); + buff_ptr += BPF_WORDALIGN(bpf_hdr->bh_hdrlen + + bpf_hdr->bh_datalen); + } + + + if (ioctl(circuit->fd, BIOCFLUSH, &one) < 0) + zlog_warn("Flushing failed: %s", safe_strerror(errno)); + + return ISIS_OK; +} + +int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) +{ + struct ether_header *eth; + ssize_t written; + size_t buflen; + + buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN + ETHER_HDR_LEN; + if (buflen > sizeof(sock_buff)) { + zlog_warn( + "%s: sock_buff size %zu is less than output pdu size %zu on circuit %s", + __func__, sizeof(sock_buff), buflen, + circuit->interface->name); + return ISIS_WARNING; + } + + stream_set_getp(circuit->snd_stream, 0); + + /* + * First the eth header + */ + eth = (struct ether_header *)sock_buff; + if (level == 1) + memcpy(eth->ether_dhost, ALL_L1_ISS, ETH_ALEN); + else + memcpy(eth->ether_dhost, ALL_L2_ISS, ETH_ALEN); + memcpy(eth->ether_shost, circuit->u.bc.snpa, ETH_ALEN); + size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN; + eth->ether_type = htons(isis_ethertype(frame_size)); + + /* + * Then the LLC + */ + sock_buff[ETHER_HDR_LEN] = ISO_SAP; + sock_buff[ETHER_HDR_LEN + 1] = ISO_SAP; + sock_buff[ETHER_HDR_LEN + 2] = 0x03; + + /* then we copy the data */ + memcpy(sock_buff + (LLC_LEN + ETHER_HDR_LEN), circuit->snd_stream->data, + stream_get_endp(circuit->snd_stream)); + + /* now we can send this */ + written = write(circuit->fd, sock_buff, buflen); + if (written < 0) { + zlog_warn("IS-IS bpf: could not transmit packet on %s: %s", + circuit->interface->name, safe_strerror(errno)); + if (ERRNO_IO_RETRY(errno)) + return ISIS_WARNING; + return ISIS_ERROR; + } + + return ISIS_OK; +} + +#endif /* ISIS_METHOD == ISIS_METHOD_BPF */ diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c new file mode 100644 index 0000000..7819b20 --- /dev/null +++ b/isisd/isis_circuit.c @@ -0,0 +1,1684 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_circuit.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#include +#ifdef GNU_LINUX +#include +#else +#include +#endif + +#include "log.h" +#include "memory.h" +#include "vrf.h" +#include "if.h" +#include "linklist.h" +#include "command.h" +#include "frrevent.h" +#include "vty.h" +#include "hash.h" +#include "prefix.h" +#include "stream.h" +#include "qobj.h" +#include "lib/northbound_cli.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dr.h" +#include "isisd/isisd.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_events.h" +#include "isisd/isis_srv6.h" +#include "isisd/isis_te.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_errors.h" +#include "isisd/isis_tx_queue.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_ldp_sync.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_CIRCUIT, "ISIS circuit"); + +DEFINE_QOBJ_TYPE(isis_circuit); + +DEFINE_HOOK(isis_if_new_hook, (struct interface *ifp), (ifp)); + +/* + * Prototypes. + */ +int isis_if_new_hook(struct interface *); +int isis_if_delete_hook(struct interface *); + +DEFINE_HOOK(isis_circuit_new_hook, (struct isis_circuit *circuit), (circuit)); +DEFINE_HOOK(isis_circuit_del_hook, (struct isis_circuit *circuit), (circuit)); + +static void isis_circuit_enable(struct isis_circuit *circuit) +{ + struct isis_area *area = circuit->area; + struct interface *ifp = circuit->interface; + + if (!area) { + area = isis_area_lookup(circuit->tag, ifp->vrf->vrf_id); + if (area) + isis_area_add_circuit(area, circuit); + } + + if (if_is_operative(ifp)) + isis_csm_state_change(IF_UP_FROM_Z, circuit, ifp); +} + +static void isis_circuit_disable(struct isis_circuit *circuit) +{ + struct isis_area *area = circuit->area; + struct interface *ifp = circuit->interface; + + if (if_is_operative(ifp)) + isis_csm_state_change(IF_DOWN_FROM_Z, circuit, ifp); + + if (area) + isis_area_del_circuit(area, circuit); +} + +struct isis_circuit *isis_circuit_new(struct interface *ifp, const char *tag) +{ + struct isis_circuit *circuit; + int i; + + circuit = XCALLOC(MTYPE_ISIS_CIRCUIT, sizeof(struct isis_circuit)); + + circuit->tag = XSTRDUP(MTYPE_ISIS_CIRCUIT, tag); + + /* + * Default values + */ +#ifndef FABRICD + circuit->is_type_config = yang_get_default_enum( + "/frr-interface:lib/interface/frr-isisd:isis/circuit-type"); + circuit->flags = 0; + + circuit->pad_hellos = yang_get_default_enum( + "/frr-interface:lib/interface/frr-isisd:isis/hello/padding"); + circuit->hello_interval[0] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1"); + circuit->hello_interval[1] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2"); + circuit->hello_multiplier[0] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1"); + circuit->hello_multiplier[1] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2"); + circuit->csnp_interval[0] = yang_get_default_uint16( + "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1"); + circuit->csnp_interval[1] = yang_get_default_uint16( + "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2"); + circuit->psnp_interval[0] = yang_get_default_uint16( + "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1"); + circuit->psnp_interval[1] = yang_get_default_uint16( + "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2"); + circuit->priority[0] = yang_get_default_uint8( + "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1"); + circuit->priority[1] = yang_get_default_uint8( + "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2"); + circuit->metric[0] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1"); + circuit->metric[1] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2"); + circuit->te_metric[0] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1"); + circuit->te_metric[1] = yang_get_default_uint32( + "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2"); + + for (i = 0; i < 2; i++) { + circuit->level_arg[i].level = i + 1; + circuit->level_arg[i].circuit = circuit; + } +#else + circuit->is_type_config = IS_LEVEL_1_AND_2; + circuit->flags = 0; + circuit->pad_hellos = ISIS_HELLO_PADDING_ALWAYS; + for (i = 0; i < 2; i++) { + circuit->hello_interval[i] = DEFAULT_HELLO_INTERVAL; + circuit->hello_multiplier[i] = DEFAULT_HELLO_MULTIPLIER; + circuit->csnp_interval[i] = DEFAULT_CSNP_INTERVAL; + circuit->psnp_interval[i] = DEFAULT_PSNP_INTERVAL; + circuit->priority[i] = DEFAULT_PRIORITY; + circuit->metric[i] = DEFAULT_CIRCUIT_METRIC; + circuit->te_metric[i] = DEFAULT_CIRCUIT_METRIC; + circuit->level_arg[i].level = i + 1; + circuit->level_arg[i].circuit = circuit; + } +#endif /* ifndef FABRICD */ + + circuit->is_type = circuit->is_type_config; + + circuit_mt_init(circuit); + isis_lfa_excluded_ifaces_init(circuit, ISIS_LEVEL1); + isis_lfa_excluded_ifaces_init(circuit, ISIS_LEVEL2); + + circuit->ldp_sync_info = ldp_sync_info_create(); + circuit->ldp_sync_info->enabled = LDP_IGP_SYNC_ENABLED; + + QOBJ_REG(circuit, isis_circuit); + + isis_circuit_if_bind(circuit, ifp); + + circuit->ip_addrs = list_new(); + circuit->ipv6_link = list_new(); + circuit->ipv6_non_link = list_new(); + + if (ifp->ifindex != IFINDEX_INTERNAL) + isis_circuit_enable(circuit); + + return circuit; +} + +void isis_circuit_del(struct isis_circuit *circuit) +{ + if (!circuit) + return; + + if (circuit->interface->ifindex != IFINDEX_INTERNAL) + isis_circuit_disable(circuit); + + isis_circuit_if_unbind(circuit, circuit->interface); + + QOBJ_UNREG(circuit); + + ldp_sync_info_free(&circuit->ldp_sync_info); + + circuit_mt_finish(circuit); + isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL1); + isis_lfa_excluded_ifaces_clear(circuit, ISIS_LEVEL2); + + list_delete(&circuit->ip_addrs); + list_delete(&circuit->ipv6_link); + list_delete(&circuit->ipv6_non_link); + + if (circuit->ext) { + isis_del_ext_subtlvs(circuit->ext); + circuit->ext = NULL; + } + + XFREE(MTYPE_TMP, circuit->bfd_config.profile); + XFREE(MTYPE_ISIS_CIRCUIT, circuit->tag); + + /* and lastly the circuit itself */ + XFREE(MTYPE_ISIS_CIRCUIT, circuit); + + return; +} + +void isis_circuit_configure(struct isis_circuit *circuit, + struct isis_area *area) +{ + assert(area); + circuit->isis = area->isis; + circuit->area = area; + + /* + * Whenever the is-type of an area is changed, the is-type of each + * circuit + * in that area is updated to a non-empty subset of the area is-type. + * Inversely, when configuring a new circuit, this property should be + * ensured as well. + */ + if (area->is_type != IS_LEVEL_1_AND_2) + circuit->is_type = area->is_type; + + /* + * Add the circuit into area + */ + listnode_add(area->circuit_list, circuit); + + circuit->idx = flags_get_index(&area->flags); + + hook_call(isis_circuit_new_hook, circuit); + + return; +} + +void isis_circuit_deconfigure(struct isis_circuit *circuit, + struct isis_area *area) +{ + hook_call(isis_circuit_del_hook, circuit); + + /* Free the index of SRM and SSN flags */ + flags_free_index(&area->flags, circuit->idx); + circuit->idx = 0; + + /* Reset IS type to configured */ + circuit->is_type = circuit->is_type_config; + + /* Remove circuit from area */ + assert(circuit->area == area); + listnode_delete(area->circuit_list, circuit); + circuit->area = NULL; + circuit->isis = NULL; + + return; +} + +struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp) +{ + return (struct isis_circuit *)ifp->info; +} + +DEFINE_HOOK(isis_circuit_add_addr_hook, (struct isis_circuit *circuit), + (circuit)); + +void isis_circuit_add_addr(struct isis_circuit *circuit, + struct connected *connected) +{ + struct listnode *node; + struct prefix_ipv4 *ipv4; + struct prefix_ipv6 *ipv6; + + if (connected->address->family == AF_INET) { + uint32_t addr = connected->address->u.prefix4.s_addr; + addr = ntohl(addr); + if (IPV4_NET0(addr) || IPV4_NET127(addr) || IN_CLASSD(addr)) + return; + + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ipv4)) + if (prefix_same((struct prefix *)ipv4, + connected->address)) + return; + + ipv4 = prefix_ipv4_new(); + ipv4->prefixlen = connected->address->prefixlen; + ipv4->prefix = connected->address->u.prefix4; + listnode_add(circuit->ip_addrs, ipv4); + + /* Update Local IP address parameter if MPLS TE is enable */ + if (circuit->ext && circuit->area + && IS_MPLS_TE(circuit->area->mta)) { + circuit->ext->local_addr.s_addr = ipv4->prefix.s_addr; + SET_SUBTLV(circuit->ext, EXT_LOCAL_ADDR); + } + + if (circuit->area) + lsp_regenerate_schedule(circuit->area, circuit->is_type, + 0); + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_EVENTS) + zlog_debug("Added IP address %pFX to circuit %s", + connected->address, + circuit->interface->name); +#endif /* EXTREME_DEBUG */ + } + if (connected->address->family == AF_INET6) { + if (IN6_IS_ADDR_LOOPBACK(&connected->address->u.prefix6)) + return; + + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, ipv6)) + if (prefix_same((struct prefix *)ipv6, + connected->address)) + return; + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, ipv6)) + if (prefix_same((struct prefix *)ipv6, + connected->address)) + return; + + ipv6 = prefix_ipv6_new(); + ipv6->prefixlen = connected->address->prefixlen; + ipv6->prefix = connected->address->u.prefix6; + + if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix)) + listnode_add(circuit->ipv6_link, ipv6); + else { + listnode_add(circuit->ipv6_non_link, ipv6); + /* Update Local IPv6 address param. if MPLS TE is on */ + if (circuit->ext && circuit->area + && IS_MPLS_TE(circuit->area->mta)) { + IPV6_ADDR_COPY(&circuit->ext->local_addr6, + &ipv6->prefix); + SET_SUBTLV(circuit->ext, EXT_LOCAL_ADDR6); + } + } + if (circuit->area) + lsp_regenerate_schedule(circuit->area, circuit->is_type, + 0); + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_EVENTS) + zlog_debug("Added IPv6 address %pFX to circuit %s", + connected->address, + circuit->interface->name); +#endif /* EXTREME_DEBUG */ + } + + hook_call(isis_circuit_add_addr_hook, circuit); + + return; +} + +void isis_circuit_del_addr(struct isis_circuit *circuit, + struct connected *connected) +{ + struct prefix_ipv4 *ipv4, *ip = NULL; + struct listnode *node; + struct prefix_ipv6 *ipv6, *ip6 = NULL; + int found = 0; + + if (connected->address->family == AF_INET) { + ipv4 = prefix_ipv4_new(); + ipv4->prefixlen = connected->address->prefixlen; + ipv4->prefix = connected->address->u.prefix4; + + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, ip)) + if (prefix_same((struct prefix *)ip, + (struct prefix *)ipv4)) + break; + + if (ip) { + listnode_delete(circuit->ip_addrs, ip); + prefix_ipv4_free(&ip); + if (circuit->area) + lsp_regenerate_schedule(circuit->area, + circuit->is_type, 0); + } else { + zlog_warn( + "Nonexistent ip address %pFX removal attempt from circuit %s", + connected->address, circuit->interface->name); + zlog_warn("Current ip addresses on %s:", + circuit->interface->name); + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, + ip)) { + zlog_warn(" %pFX", ip); + } + zlog_warn("End of addresses"); + } + + prefix_ipv4_free(&ipv4); + } + if (connected->address->family == AF_INET6) { + ipv6 = prefix_ipv6_new(); + ipv6->prefixlen = connected->address->prefixlen; + ipv6->prefix = connected->address->u.prefix6; + + if (IN6_IS_ADDR_LINKLOCAL(&ipv6->prefix)) { + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, + ip6)) { + if (prefix_same((struct prefix *)ip6, + (struct prefix *)ipv6)) + break; + } + if (ip6) { + listnode_delete(circuit->ipv6_link, ip6); + prefix_ipv6_free(&ip6); + found = 1; + } + } else { + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, + ip6)) { + if (prefix_same((struct prefix *)ip6, + (struct prefix *)ipv6)) + break; + } + if (ip6) { + listnode_delete(circuit->ipv6_non_link, ip6); + prefix_ipv6_free(&ip6); + found = 1; + } + } + + if (!found) { + zlog_warn( + "Nonexistent ip address %pFX removal attempt from circuit %s", + connected->address, circuit->interface->name); + zlog_warn("Current ip addresses on %s:", + circuit->interface->name); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, + ip6)) + zlog_warn(" %pFX", (struct prefix *)ip6); + zlog_warn(" -----"); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, + ip6)) + zlog_warn(" %pFX", (struct prefix *)ip6); + zlog_warn("End of addresses"); + } else if (circuit->area) + lsp_regenerate_schedule(circuit->area, circuit->is_type, + 0); + + prefix_ipv6_free(&ipv6); + } + return; +} + +static uint8_t isis_circuit_id_gen(struct isis *isis, struct interface *ifp) +{ + /* Circuit ids MUST be unique for any broadcast circuits. Otherwise, + * Pseudo-Node LSPs cannot be generated correctly. + * + * Currently, allocate one circuit ID for any circuit, limiting the total + * numer of circuits IS-IS can run on to 255. + * + * We should revisit this when implementing 3-way adjacencies for p2p, since + * we then have extended interface IDs available. + */ + uint8_t id = ifp->ifindex; + unsigned int i; + + for (i = 0; i < 256; i++) { + if (id && !_ISIS_CHECK_FLAG(isis->circuit_ids_used, id)) + break; + id++; + } + + if (i == 256) { + zlog_warn("Could not allocate a circuit id for '%s'", + ifp->name); + return 0; + } + + _ISIS_SET_FLAG(isis->circuit_ids_used, id); + return id; +} + +void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp) +{ + struct connected *conn; + + if (if_is_broadcast(ifp)) { + if (fabricd || circuit->circ_type_config == CIRCUIT_T_P2P) + circuit->circ_type = CIRCUIT_T_P2P; + else + circuit->circ_type = CIRCUIT_T_BROADCAST; + } else if (if_is_pointopoint(ifp)) { + circuit->circ_type = CIRCUIT_T_P2P; + } else if (if_is_loopback(ifp)) { + circuit->circ_type = CIRCUIT_T_LOOPBACK; + circuit->is_passive = 1; + } else { + /* It's normal in case of loopback etc. */ + if (IS_DEBUG_EVENTS) + zlog_debug("%s: unsupported media", __func__); + circuit->circ_type = CIRCUIT_T_UNKNOWN; + } + + frr_each (if_connected, ifp->connected, conn) + isis_circuit_add_addr(circuit, conn); +} + +void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp) +{ + struct connected *conn; + + assert(circuit->interface == ifp); + + /* destroy addresses */ + frr_each_safe (if_connected, ifp->connected, conn) + isis_circuit_del_addr(circuit, conn); + + circuit->circ_type = CIRCUIT_T_UNKNOWN; +} + +void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp) +{ + assert(circuit != NULL); + assert(ifp != NULL); + if (circuit->interface) + assert(circuit->interface == ifp); + else + circuit->interface = ifp; + if (ifp->info) + assert(ifp->info == circuit); + else + ifp->info = circuit; +} + +void isis_circuit_if_unbind(struct isis_circuit *circuit, struct interface *ifp) +{ + assert(circuit != NULL); + assert(ifp != NULL); + assert(circuit->interface == ifp); + assert(ifp->info == circuit); + circuit->interface = NULL; + ifp->info = NULL; +} + +static void isis_circuit_update_all_srmflags(struct isis_circuit *circuit, + int is_set) +{ + struct isis_area *area; + struct isis_lsp *lsp; + int level; + + assert(circuit); + area = circuit->area; + assert(area); + for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(level & circuit->is_type)) + continue; + + if (!lspdb_count(&area->lspdb[level - 1])) + continue; + + frr_each (lspdb, &area->lspdb[level - 1], lsp) { + if (is_set) { + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + } else { + isis_tx_queue_del(circuit->tx_queue, lsp); + } + } + } +} + +size_t isis_circuit_pdu_size(struct isis_circuit *circuit) +{ + return ISO_MTU(circuit); +} + +static bool isis_circuit_lfa_enabled(struct isis_circuit *circuit, int level) +{ + return (circuit->lfa_protection[level - 1] || + circuit->rlfa_protection[level - 1] || + circuit->tilfa_protection[level - 1]); +} + +void isis_circuit_switchover_routes(struct isis_circuit *circuit, int family, + union g_addr *nexthop_ip, ifindex_t ifindex) +{ + char is_type; + + if (!circuit->area) + return; + + is_type = circuit->area->is_type; + if ((is_type == IS_LEVEL_1 || is_type == IS_LEVEL_1_AND_2) && + isis_circuit_lfa_enabled(circuit, IS_LEVEL_1)) + isis_area_switchover_routes(circuit->area, family, nexthop_ip, + ifindex, IS_LEVEL_1); + if ((is_type == IS_LEVEL_2 || is_type == IS_LEVEL_1_AND_2) && + isis_circuit_lfa_enabled(circuit, IS_LEVEL_2)) + isis_area_switchover_routes(circuit->area, family, nexthop_ip, + ifindex, IS_LEVEL_2); +} + +void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream) +{ + size_t stream_size = isis_circuit_pdu_size(circuit); + + if (!*stream) { + *stream = stream_new(stream_size); + } else { + if (STREAM_SIZE(*stream) != stream_size) + stream_resize_inplace(stream, stream_size); + stream_reset(*stream); + } +} + +void isis_circuit_prepare(struct isis_circuit *circuit) +{ +#if ISIS_METHOD != ISIS_METHOD_DLPI + event_add_read(master, isis_receive, circuit, circuit->fd, + &circuit->t_read); +#else + event_add_timer_msec(master, isis_receive, circuit, + listcount(circuit->area->circuit_list) * 100, + &circuit->t_read); +#endif +} + +int isis_circuit_up(struct isis_circuit *circuit) +{ + int retv; + + /* Set the flags for all the lsps of the circuit. */ + isis_circuit_update_all_srmflags(circuit, 1); + + if (circuit->state == C_STATE_UP) + return ISIS_OK; + + if (circuit->is_passive) { + circuit->last_uptime = time(NULL); + /* make sure the union fields are initialized, else we + * could end with garbage values from a previous circuit + * type, which would then cause a segfault when building + * LSPs or computing the SPF tree + */ + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + circuit->u.bc.adjdb[0] = list_new(); + circuit->u.bc.adjdb[1] = list_new(); + } else if (circuit->circ_type == CIRCUIT_T_P2P) { + circuit->u.p2p.neighbor = NULL; + } + return ISIS_OK; + } + + if (circuit->area->lsp_mtu > isis_circuit_pdu_size(circuit)) { + flog_err( + EC_ISIS_CONFIG, + "Interface MTU %zu on %s is too low to support area lsp mtu %u!", + isis_circuit_pdu_size(circuit), + circuit->interface->name, circuit->area->lsp_mtu); + + /* Allow ISIS to continue configuration. With this + * configuration failure ISIS will attempt to send lsp + * packets but will fail until the mtu is configured properly + */ + } + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + circuit->circuit_id = + isis_circuit_id_gen(circuit->isis, circuit->interface); + if (!circuit->circuit_id) { + flog_err( + EC_ISIS_CONFIG, + "There are already 255 broadcast circuits active!"); + return ISIS_ERROR; + } + + /* + * Get the Hardware Address + */ + if (circuit->interface->hw_addr_len != ETH_ALEN) { + zlog_warn("unsupported link layer"); + } else { + memcpy(circuit->u.bc.snpa, circuit->interface->hw_addr, + ETH_ALEN); + } +#ifdef EXTREME_DEGUG + if (IS_DEBUG_EVENTS) + zlog_debug("%s: if_id %d, isomtu %d snpa %pSY", + __func__, circuit->interface->ifindex, + ISO_MTU(circuit), circuit->u.bc.snpa); +#endif /* EXTREME_DEBUG */ + + circuit->u.bc.adjdb[0] = list_new(); + circuit->u.bc.adjdb[1] = list_new(); + + /* + * ISO 10589 - 8.4.1 Enabling of broadcast circuits + */ + + /* initilizing the hello sending threads + * for a broadcast IF + */ + + /* 8.4.1 a) commence sending of IIH PDUs */ + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(circuit->is_type & level)) + continue; + + send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY); + circuit->u.bc.lan_neighs[level - 1] = list_new(); + + event_add_timer(master, isis_run_dr, + &circuit->level_arg[level - 1], + 2 * circuit->hello_interval[level - 1], + &circuit->u.bc.t_run_dr[level - 1]); + } + + /* 8.4.1 b) FIXME: solicit ES - 8.4.6 */ + /* 8.4.1 c) FIXME: listen for ESH PDUs */ + } else if (circuit->circ_type == CIRCUIT_T_P2P) { + /* initializing the hello send threads + * for a ptp IF + */ + circuit->u.p2p.neighbor = NULL; + send_hello_sched(circuit, 0, TRIGGERED_IIH_DELAY); + } + + /* initializing PSNP timers */ + if (circuit->is_type & IS_LEVEL_1) + event_add_timer( + master, send_l1_psnp, circuit, + isis_jitter(circuit->psnp_interval[0], PSNP_JITTER), + &circuit->t_send_psnp[0]); + + if (circuit->is_type & IS_LEVEL_2) + event_add_timer( + master, send_l2_psnp, circuit, + isis_jitter(circuit->psnp_interval[1], PSNP_JITTER), + &circuit->t_send_psnp[1]); + + /* unified init for circuits; ignore warnings below this level */ + retv = isis_sock_init(circuit); + if (retv != ISIS_OK) { + isis_circuit_down(circuit); + return retv; + } + + /* initialize the circuit streams after opening connection */ + isis_circuit_stream(circuit, &circuit->rcv_stream); + isis_circuit_stream(circuit, &circuit->snd_stream); + + isis_circuit_prepare(circuit); + + circuit->tx_queue = isis_tx_queue_new(circuit, send_lsp); + + circuit->last_uptime = time(NULL); + + if (circuit->area->mta && circuit->area->mta->status) + isis_link_params_update(circuit, circuit->interface); + + isis_if_ldp_sync_enable(circuit); + +#ifndef FABRICD + /* send northbound notification */ + isis_notif_if_state_change(circuit, false); +#endif /* ifndef FABRICD */ + + return ISIS_OK; +} + +void isis_circuit_down(struct isis_circuit *circuit) +{ +#ifndef FABRICD + /* send northbound notification */ + isis_notif_if_state_change(circuit, true); +#endif /* ifndef FABRICD */ + + isis_if_ldp_sync_disable(circuit); + + /* log adjacency changes if configured to do so */ + if (circuit->area->log_adj_changes) { + struct isis_adjacency *adj = NULL; + if (circuit->circ_type == CIRCUIT_T_P2P) { + adj = circuit->u.p2p.neighbor; + if (adj) + isis_log_adj_change( + adj, adj->adj_state, ISIS_ADJ_DOWN, + "circuit is being brought down"); + } else if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + struct list *adj_list; + struct listnode *node; + if (circuit->u.bc.adjdb[0]) { + adj_list = list_new(); + isis_adj_build_up_list(circuit->u.bc.adjdb[0], + adj_list); + for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) + isis_log_adj_change( + adj, adj->adj_state, + ISIS_ADJ_DOWN, + "circuit is being brought down"); + list_delete(&adj_list); + } + if (circuit->u.bc.adjdb[1]) { + adj_list = list_new(); + isis_adj_build_up_list(circuit->u.bc.adjdb[1], + adj_list); + for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) + isis_log_adj_change( + adj, adj->adj_state, + ISIS_ADJ_DOWN, + "circuit is being brought down"); + list_delete(&adj_list); + } + } + } + + /* Clear the flags for all the lsps of the circuit. */ + isis_circuit_update_all_srmflags(circuit, 0); + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + /* destroy neighbour lists */ + if (circuit->u.bc.lan_neighs[0]) { + list_delete(&circuit->u.bc.lan_neighs[0]); + circuit->u.bc.lan_neighs[0] = NULL; + } + if (circuit->u.bc.lan_neighs[1]) { + list_delete(&circuit->u.bc.lan_neighs[1]); + circuit->u.bc.lan_neighs[1] = NULL; + } + /* destroy adjacency databases */ + if (circuit->u.bc.adjdb[0]) { + circuit->u.bc.adjdb[0]->del = isis_delete_adj; + list_delete(&circuit->u.bc.adjdb[0]); + circuit->u.bc.adjdb[0] = NULL; + } + if (circuit->u.bc.adjdb[1]) { + circuit->u.bc.adjdb[1]->del = isis_delete_adj; + list_delete(&circuit->u.bc.adjdb[1]); + circuit->u.bc.adjdb[1] = NULL; + } + if (circuit->u.bc.is_dr[0]) { + isis_dr_resign(circuit, 1); + circuit->u.bc.is_dr[0] = 0; + } + memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); + if (circuit->u.bc.is_dr[1]) { + isis_dr_resign(circuit, 2); + circuit->u.bc.is_dr[1] = 0; + } + memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); + memset(circuit->u.bc.snpa, 0, ETH_ALEN); + + EVENT_OFF(circuit->u.bc.t_send_lan_hello[0]); + EVENT_OFF(circuit->u.bc.t_send_lan_hello[1]); + EVENT_OFF(circuit->u.bc.t_run_dr[0]); + EVENT_OFF(circuit->u.bc.t_run_dr[1]); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[0]); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[1]); + circuit->lsp_regenerate_pending[0] = 0; + circuit->lsp_regenerate_pending[1] = 0; + + _ISIS_CLEAR_FLAG(circuit->isis->circuit_ids_used, + circuit->circuit_id); + circuit->circuit_id = 0; + } else if (circuit->circ_type == CIRCUIT_T_P2P) { + isis_delete_adj(circuit->u.p2p.neighbor); + circuit->u.p2p.neighbor = NULL; + EVENT_OFF(circuit->u.p2p.t_send_p2p_hello); + } + + /* + * All adjacencies have to be gone, delete snmp list + * and reset snmpd idx generator + */ + if (circuit->snmp_adj_list != NULL) + list_delete(&circuit->snmp_adj_list); + + circuit->snmp_adj_idx_gen = 0; + + /* Cancel all active threads */ + EVENT_OFF(circuit->t_send_csnp[0]); + EVENT_OFF(circuit->t_send_csnp[1]); + EVENT_OFF(circuit->t_send_psnp[0]); + EVENT_OFF(circuit->t_send_psnp[1]); + EVENT_OFF(circuit->t_read); + + if (circuit->tx_queue) { + isis_tx_queue_free(circuit->tx_queue); + circuit->tx_queue = NULL; + } + + /* send one gratuitous hello to spead up convergence */ + if (circuit->state == C_STATE_UP) { + if (circuit->is_type & IS_LEVEL_1) + send_hello(circuit, IS_LEVEL_1); + if (circuit->is_type & IS_LEVEL_2) + send_hello(circuit, IS_LEVEL_2); + } + + circuit->upadjcount[0] = 0; + circuit->upadjcount[1] = 0; + + /* close the socket */ + if (circuit->fd) { + close(circuit->fd); + circuit->fd = 0; + } + + if (circuit->rcv_stream != NULL) { + stream_free(circuit->rcv_stream); + circuit->rcv_stream = NULL; + } + + if (circuit->snd_stream != NULL) { + stream_free(circuit->snd_stream); + circuit->snd_stream = NULL; + } + + event_cancel_event(master, circuit); + + return; +} + +void circuit_update_nlpids(struct isis_circuit *circuit) +{ + circuit->nlpids.count = 0; + + if (circuit->ip_router) { + circuit->nlpids.nlpids[0] = NLPID_IP; + circuit->nlpids.count++; + } + if (circuit->ipv6_router) { + circuit->nlpids.nlpids[circuit->nlpids.count] = NLPID_IPV6; + circuit->nlpids.count++; + } + return; +} + +void isis_circuit_print_json(struct isis_circuit *circuit, + struct json_object *json, char detail) +{ + int level; + json_object *iface_json, *ipv4_addr_json, *ipv6_link_json, + *ipv6_non_link_json, *hold_json, *lan_prio_json, *levels_json, + *level_json; + char buf_prx[INET6_BUFSIZ]; + char buf[255]; + + snprintfrr(buf, sizeof(buf), "0x%x", circuit->circuit_id); + if (detail == ISIS_UI_LEVEL_BRIEF) { + iface_json = json_object_new_object(); + json_object_object_add(json, "interface", iface_json); + json_object_string_add(iface_json, "name", + circuit->interface->name); + json_object_string_add(iface_json, "circuit-id", buf); + json_object_string_add(iface_json, "state", + circuit_state2string(circuit->state)); + json_object_string_add(iface_json, "type", + circuit_type2string(circuit->circ_type)); + json_object_string_add(iface_json, "level", + circuit_t2string(circuit->is_type)); + } + + if (detail == ISIS_UI_LEVEL_DETAIL) { + struct listnode *node; + struct prefix *ip_addr; + + iface_json = json_object_new_object(); + json_object_object_add(json, "interface", iface_json); + json_object_string_add(iface_json, "name", + circuit->interface->name); + json_object_string_add(iface_json, "state", + circuit_state2string(circuit->state)); + if (circuit->is_passive) + json_object_string_add(iface_json, "is-passive", + "passive"); + else + json_object_string_add(iface_json, "is-passive", + "active"); + json_object_string_add(iface_json, "circuit-id", buf); + json_object_string_add(iface_json, "type", + circuit_type2string(circuit->circ_type)); + json_object_string_add(iface_json, "level", + circuit_t2string(circuit->is_type)); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) + json_object_string_addf(iface_json, "snpa", "%pSY", + circuit->u.bc.snpa); + + + levels_json = json_object_new_array(); + json_object_object_add(iface_json, "levels", levels_json); + for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((circuit->is_type & level) == 0) + continue; + level_json = json_object_new_object(); + json_object_string_add(level_json, "level", + circuit_t2string(level)); + if (circuit->area->newmetric) + json_object_int_add(level_json, "metric", + circuit->te_metric[0]); + else + json_object_int_add(level_json, "metric", + circuit->metric[0]); + if (!circuit->is_passive) { + json_object_int_add(level_json, + "active-neighbors", + circuit->upadjcount[0]); + json_object_int_add(level_json, + "hello-interval", + circuit->hello_interval[0]); + hold_json = json_object_new_object(); + json_object_object_add(level_json, "holddown", + hold_json); + json_object_int_add( + hold_json, "count", + circuit->hello_multiplier[0]); + json_object_string_add( + hold_json, "pad", + isis_hello_padding2string( + circuit->pad_hellos)); + json_object_int_add(level_json, "cnsp-interval", + circuit->csnp_interval[0]); + json_object_int_add(level_json, "psnp-interval", + circuit->psnp_interval[0]); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + lan_prio_json = + json_object_new_object(); + json_object_object_add(level_json, + "lan", + lan_prio_json); + json_object_int_add( + lan_prio_json, "priority", + circuit->priority[0]); + json_object_string_add( + lan_prio_json, "is-dis", + (circuit->u.bc.is_dr[0] + ? "yes" + : "no")); + } + } + json_object_array_add(levels_json, level_json); + } + + if (listcount(circuit->ip_addrs) > 0) { + ipv4_addr_json = json_object_new_object(); + json_object_object_add(iface_json, "ip-prefix", + ipv4_addr_json); + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, + ip_addr)) { + snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX", + ip_addr); + json_object_string_add(ipv4_addr_json, "ip", + buf_prx); + } + } + if (listcount(circuit->ipv6_link) > 0) { + ipv6_link_json = json_object_new_object(); + json_object_object_add(iface_json, "ipv6-link-locals", + ipv6_link_json); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, + ip_addr)) { + snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX", + ip_addr); + json_object_string_add(ipv6_link_json, "ipv6", + buf_prx); + } + } + if (listcount(circuit->ipv6_non_link) > 0) { + ipv6_non_link_json = json_object_new_object(); + json_object_object_add(iface_json, "ipv6-prefixes", + ipv6_non_link_json); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, + ip_addr)) { + snprintfrr(buf_prx, INET6_BUFSIZ, "%pFX", + ip_addr); + json_object_string_add(ipv6_non_link_json, + "ipv6", buf_prx); + } + } + } + return; +} + +void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty, + char detail) +{ + if (detail == ISIS_UI_LEVEL_BRIEF) { + vty_out(vty, " %-12s", circuit->interface->name); + vty_out(vty, "0x%-7x", circuit->circuit_id); + vty_out(vty, "%-9s", circuit_state2string(circuit->state)); + vty_out(vty, "%-9s", circuit_type2string(circuit->circ_type)); + vty_out(vty, "%-9s", circuit_t2string(circuit->is_type)); + vty_out(vty, "\n"); + } + + if (detail == ISIS_UI_LEVEL_DETAIL) { + struct listnode *node; + struct prefix *ip_addr; + + vty_out(vty, " Interface: %s", circuit->interface->name); + vty_out(vty, ", State: %s", + circuit_state2string(circuit->state)); + if (circuit->is_passive) + vty_out(vty, ", Passive"); + else + vty_out(vty, ", Active"); + vty_out(vty, ", Circuit Id: 0x%x", circuit->circuit_id); + vty_out(vty, "\n"); + vty_out(vty, " Type: %s", + circuit_type2string(circuit->circ_type)); + vty_out(vty, ", Level: %s", circuit_t2string(circuit->is_type)); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) + vty_out(vty, ", SNPA: %-10pSY", circuit->u.bc.snpa); + vty_out(vty, "\n"); + if (circuit->is_type & IS_LEVEL_1) { + vty_out(vty, " Level-1 Information:\n"); + if (circuit->area->newmetric) + vty_out(vty, " Metric: %d", + circuit->te_metric[0]); + else + vty_out(vty, " Metric: %d", + circuit->metric[0]); + if (!circuit->is_passive) { + vty_out(vty, ", Active neighbors: %u\n", + circuit->upadjcount[0]); + vty_out(vty, + " Hello interval: %u, Holddown count: %u, Padding: %s\n", + circuit->hello_interval[0], + circuit->hello_multiplier[0], + isis_hello_padding2string( + circuit->pad_hellos)); + vty_out(vty, + " CNSP interval: %u, PSNP interval: %u\n", + circuit->csnp_interval[0], + circuit->psnp_interval[0]); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) + vty_out(vty, + " LAN Priority: %u, %s\n", + circuit->priority[0], + (circuit->u.bc.is_dr[0] + ? "is DIS" + : "is not DIS")); + } else { + vty_out(vty, "\n"); + } + } + if (circuit->is_type & IS_LEVEL_2) { + vty_out(vty, " Level-2 Information:\n"); + if (circuit->area->newmetric) + vty_out(vty, " Metric: %d", + circuit->te_metric[1]); + else + vty_out(vty, " Metric: %d", + circuit->metric[1]); + if (!circuit->is_passive) { + vty_out(vty, ", Active neighbors: %u\n", + circuit->upadjcount[1]); + vty_out(vty, + " Hello interval: %u, Holddown count: %u, Padding: %s\n", + circuit->hello_interval[1], + circuit->hello_multiplier[1], + isis_hello_padding2string( + circuit->pad_hellos)); + vty_out(vty, + " CNSP interval: %u, PSNP interval: %u\n", + circuit->csnp_interval[1], + circuit->psnp_interval[1]); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) + vty_out(vty, + " LAN Priority: %u, %s\n", + circuit->priority[1], + (circuit->u.bc.is_dr[1] + ? "is DIS" + : "is not DIS")); + } else { + vty_out(vty, "\n"); + } + } + if (listcount(circuit->ip_addrs) > 0) { + vty_out(vty, " IP Prefix(es):\n"); + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, node, + ip_addr)) + vty_out(vty, " %pFX\n", ip_addr); + } + if (listcount(circuit->ipv6_link) > 0) { + vty_out(vty, " IPv6 Link-Locals:\n"); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_link, node, + ip_addr)) + vty_out(vty, " %pFX\n", ip_addr); + } + if (listcount(circuit->ipv6_non_link) > 0) { + vty_out(vty, " IPv6 Prefixes:\n"); + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, node, + ip_addr)) + vty_out(vty, " %pFX\n", ip_addr); + } + + vty_out(vty, "\n"); + } + return; +} + +#ifdef FABRICD +DEFINE_HOOK(isis_circuit_config_write, + (struct isis_circuit *circuit, struct vty *vty), + (circuit, vty)); + +static int isis_interface_config_write(struct vty *vty) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + int write = 0; + struct interface *ifp; + struct isis_circuit *circuit; + int i; + + FOR_ALL_INTERFACES (vrf, ifp) { + /* IF name */ + if_vty_config_start(vty, ifp); + write++; + /* IF desc */ + if (ifp->desc) { + vty_out(vty, " description %s\n", ifp->desc); + write++; + } + /* ISIS Circuit */ + do { + circuit = circuit_scan_by_ifp(ifp); + if (circuit == NULL) + break; + if (circuit->ip_router) { + vty_out(vty, " ip router " PROTO_NAME " %s\n", + circuit->tag); + write++; + } + if (circuit->is_passive) { + vty_out(vty, " " PROTO_NAME " passive\n"); + write++; + } + if (circuit->circ_type_config == CIRCUIT_T_P2P) { + vty_out(vty, " " PROTO_NAME " network point-to-point\n"); + write++; + } + if (circuit->ipv6_router) { + vty_out(vty, " ipv6 router " PROTO_NAME " %s\n", + circuit->tag); + write++; + } + + /* ISIS - circuit type */ + if (!fabricd) { + if (circuit->is_type == IS_LEVEL_1) { + vty_out(vty, " " PROTO_NAME " circuit-type level-1\n"); + write++; + } else { + if (circuit->is_type == IS_LEVEL_2) { + vty_out(vty, + " " PROTO_NAME " circuit-type level-2-only\n"); + write++; + } + } + } + + /* ISIS - CSNP interval */ + if (circuit->csnp_interval[0] + == circuit->csnp_interval[1]) { + if (circuit->csnp_interval[0] + != DEFAULT_CSNP_INTERVAL) { + vty_out(vty, " " PROTO_NAME " csnp-interval %d\n", + circuit->csnp_interval[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->csnp_interval[i] + != DEFAULT_CSNP_INTERVAL) { + vty_out(vty, + " " PROTO_NAME " csnp-interval %d level-%d\n", + circuit->csnp_interval + [i], + i + 1); + write++; + } + } + } + + /* ISIS - PSNP interval */ + if (circuit->psnp_interval[0] + == circuit->psnp_interval[1]) { + if (circuit->psnp_interval[0] + != DEFAULT_PSNP_INTERVAL) { + vty_out(vty, " " PROTO_NAME " psnp-interval %d\n", + circuit->psnp_interval[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->psnp_interval[i] + != DEFAULT_PSNP_INTERVAL) { + vty_out(vty, + " " PROTO_NAME " psnp-interval %d level-%d\n", + circuit->psnp_interval + [i], + i + 1); + write++; + } + } + } + + /* ISIS - Hello padding - Defaults to always so only + * display if not always */ + switch (circuit->pad_hellos) { + case ISIS_HELLO_PADDING_DISABLED: + vty_out(vty, " no " PROTO_NAME " hello padding\n"); + write++; + break; + case ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION: + vty_out(vty, PROTO_NAME + " hello padding during-adjacency-formation\n"); + write++; + break; + case ISIS_HELLO_PADDING_ALWAYS: + break; + } + + if (circuit->disable_threeway_adj) { + vty_out(vty, " no isis three-way-handshake\n"); + write++; + } + + /* ISIS - Hello interval */ + if (circuit->hello_interval[0] + == circuit->hello_interval[1]) { + if (circuit->hello_interval[0] + != DEFAULT_HELLO_INTERVAL) { + vty_out(vty, + " " PROTO_NAME " hello-interval %d\n", + circuit->hello_interval[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->hello_interval[i] + != DEFAULT_HELLO_INTERVAL) { + vty_out(vty, + " " PROTO_NAME " hello-interval %d level-%d\n", + circuit->hello_interval + [i], + i + 1); + write++; + } + } + } + + /* ISIS - Hello Multiplier */ + if (circuit->hello_multiplier[0] + == circuit->hello_multiplier[1]) { + if (circuit->hello_multiplier[0] + != DEFAULT_HELLO_MULTIPLIER) { + vty_out(vty, + " " PROTO_NAME " hello-multiplier %d\n", + circuit->hello_multiplier[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->hello_multiplier[i] + != DEFAULT_HELLO_MULTIPLIER) { + vty_out(vty, + " " PROTO_NAME " hello-multiplier %d level-%d\n", + circuit->hello_multiplier + [i], + i + 1); + write++; + } + } + } + + /* ISIS - Priority */ + if (circuit->priority[0] == circuit->priority[1]) { + if (circuit->priority[0] != DEFAULT_PRIORITY) { + vty_out(vty, " " PROTO_NAME " priority %d\n", + circuit->priority[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->priority[i] + != DEFAULT_PRIORITY) { + vty_out(vty, + " " PROTO_NAME " priority %d level-%d\n", + circuit->priority[i], + i + 1); + write++; + } + } + } + + /* ISIS - Metric */ + if (circuit->te_metric[0] == circuit->te_metric[1]) { + if (circuit->te_metric[0] + != DEFAULT_CIRCUIT_METRIC) { + vty_out(vty, " " PROTO_NAME " metric %d\n", + circuit->te_metric[0]); + write++; + } + } else { + for (i = 0; i < 2; i++) { + if (circuit->te_metric[i] + != DEFAULT_CIRCUIT_METRIC) { + vty_out(vty, + " " PROTO_NAME " metric %d level-%d\n", + circuit->te_metric[i], + i + 1); + write++; + } + } + } + if (circuit->passwd.type == ISIS_PASSWD_TYPE_HMAC_MD5) { + vty_out(vty, " " PROTO_NAME " password md5 %s\n", + circuit->passwd.passwd); + write++; + } else if (circuit->passwd.type + == ISIS_PASSWD_TYPE_CLEARTXT) { + vty_out(vty, " " PROTO_NAME " password clear %s\n", + circuit->passwd.passwd); + write++; + } + if (circuit->bfd_config.enabled) { + vty_out(vty, " " PROTO_NAME " bfd\n"); + write++; + } + write += hook_call(isis_circuit_config_write, + circuit, vty); + } while (0); + if_vty_config_end(vty); + } + + return write; +} +#endif /* ifdef FABRICD */ + +void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router, + bool ipv6_router) +{ + struct isis_area *area = circuit->area; + int old_ipr = circuit->ip_router; + int old_ipv6r = circuit->ipv6_router; + + /* is there something to do? */ + if (old_ipr == ip_router && old_ipv6r == ipv6_router) + return; + + circuit->ip_router = ip_router; + circuit->ipv6_router = ipv6_router; + circuit_update_nlpids(circuit); + + if (area) { + area->ip_circuits += ip_router - old_ipr; + area->ipv6_circuits += ipv6_router - old_ipv6r; + + if (ip_router || ipv6_router) + lsp_regenerate_schedule(area, circuit->is_type, 0); + } +} + +ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive) +{ + if (circuit->is_passive == passive) + return ferr_ok(); + + if (if_is_loopback(circuit->interface) && !passive) + return ferr_cfg_invalid("loopback is always passive"); + + if (circuit->state != C_STATE_UP) { + circuit->is_passive = passive; + } else { + struct isis_area *area = circuit->area; + isis_csm_state_change(ISIS_DISABLE, circuit, area); + circuit->is_passive = passive; + isis_csm_state_change(ISIS_ENABLE, circuit, area); + } + + return ferr_ok(); +} + +ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level, + int metric) +{ + assert(level == IS_LEVEL_1 || level == IS_LEVEL_2); + if (metric > MAX_WIDE_LINK_METRIC) + return ferr_cfg_invalid("metric %d too large for wide metric", + metric); + if (circuit->area && circuit->area->oldmetric + && metric > MAX_NARROW_LINK_METRIC) + return ferr_cfg_invalid("metric %d too large for narrow metric", + metric); + + /* Don't modify metric if advertise high metrics is configured */ + if (circuit->area && circuit->area->advertise_high_metrics) + return ferr_ok(); + + /* inform ldp-sync of metric change + * if ldp-sync is running need to save metric + * and restore new values after ldp-sync completion. + */ + if (isis_ldp_sync_if_metric_config(circuit, level, metric)) { + circuit->te_metric[level - 1] = metric; + circuit->metric[level - 1] = metric; + if (circuit->area) + lsp_regenerate_schedule(circuit->area, level, 0); + } + return ferr_ok(); +} + +ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit) +{ + memset(&circuit->passwd, 0, sizeof(circuit->passwd)); + return ferr_ok(); +} + +ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit, + uint8_t passwd_type, const char *passwd) +{ + int len; + + if (!passwd) + return ferr_code_bug("no circuit password given"); + + len = strlen(passwd); + if (len > 254) + return ferr_code_bug( + "circuit password too long (max 254 chars)"); + + circuit->passwd.len = len; + strlcpy((char *)circuit->passwd.passwd, passwd, + sizeof(circuit->passwd.passwd)); + circuit->passwd.type = passwd_type; + return ferr_ok(); +} + +ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit, + const char *passwd) +{ + return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_CLEARTXT, + passwd); +} + +ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit, + const char *passwd) +{ + return isis_circuit_passwd_set(circuit, ISIS_PASSWD_TYPE_HMAC_MD5, + passwd); +} + +void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type) +{ + if (circuit->circ_type == circ_type) + return; + + if (circuit->state != C_STATE_UP) { + circuit->circ_type = circ_type; + circuit->circ_type_config = circ_type; + } else { + struct isis_area *area = circuit->area; + + isis_csm_state_change(ISIS_DISABLE, circuit, area); + circuit->circ_type = circ_type; + circuit->circ_type_config = circ_type; + isis_csm_state_change(ISIS_ENABLE, circuit, area); + } +} + +int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid, + bool enabled) +{ + struct isis_circuit_mt_setting *setting; + + setting = circuit_get_mt_setting(circuit, mtid); + if (setting->enabled != enabled) { + setting->enabled = enabled; + if (circuit->area) + lsp_regenerate_schedule(circuit->area, + IS_LEVEL_1 | IS_LEVEL_2, 0); + } + + return CMD_SUCCESS; +} + +int isis_if_new_hook(struct interface *ifp) +{ + return 0; +} + +int isis_if_delete_hook(struct interface *ifp) +{ + if (ifp->info) + isis_circuit_del(ifp->info); + + return 0; +} + +static int isis_ifp_create(struct interface *ifp) +{ + struct isis_circuit *circuit = ifp->info; + + if (circuit) + isis_circuit_enable(circuit); + + hook_call(isis_if_new_hook, ifp); + + return 0; +} + +static int isis_ifp_up(struct interface *ifp) +{ + struct isis_circuit *circuit = ifp->info; + + if (circuit) { + UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z); + isis_csm_state_change(IF_UP_FROM_Z, circuit, ifp); + } + + /* Notify SRv6 that the interface went up */ + isis_srv6_ifp_up_notify(ifp); + + return 0; +} + +static int isis_ifp_down(struct interface *ifp) +{ + afi_t afi; + struct isis_circuit *circuit = ifp->info; + + if (circuit && + !CHECK_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z)) { + SET_FLAG(circuit->flags, ISIS_CIRCUIT_IF_DOWN_FROM_Z); + for (afi = AFI_IP; afi <= AFI_IP6; afi++) + isis_circuit_switchover_routes( + circuit, afi == AFI_IP ? AF_INET : AF_INET6, + NULL, ifp->ifindex); + isis_csm_state_change(IF_DOWN_FROM_Z, circuit, ifp); + + SET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF); + } + + return 0; +} + +static int isis_ifp_destroy(struct interface *ifp) +{ + struct isis_circuit *circuit = ifp->info; + + if (circuit) + isis_circuit_disable(circuit); + + return 0; +} + +void isis_circuit_init(void) +{ + /* Initialize Zebra interface data structure */ + hook_register_prio(if_add, 0, isis_if_new_hook); + hook_register_prio(if_del, 0, isis_if_delete_hook); + + /* Install interface node */ +#ifdef FABRICD + if_cmd_init(isis_interface_config_write); +#else + if_cmd_init_default(); +#endif + hook_register_prio(if_real, 0, isis_ifp_create); + hook_register_prio(if_up, 0, isis_ifp_up); + hook_register_prio(if_down, 0, isis_ifp_down); + hook_register_prio(if_unreal, 0, isis_ifp_destroy); +} diff --git a/isisd/isis_circuit.h b/isisd/isis_circuit.h new file mode 100644 index 0000000..7acde41 --- /dev/null +++ b/isisd/isis_circuit.h @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_circuit.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef ISIS_CIRCUIT_H +#define ISIS_CIRCUIT_H + +#include "vty.h" +#include "if.h" +#include "qobj.h" +#include "prefix.h" +#include "ferr.h" +#include "nexthop.h" + +#include "isis_constants.h" +#include "isis_common.h" +#include "isis_csm.h" + +DECLARE_HOOK(isis_if_new_hook, (struct interface *ifp), (ifp)); + +struct isis_lsp; + +struct password { + struct password *next; + int len; + uint8_t *pass; +}; + +struct metric { + uint8_t metric_default; + uint8_t metric_error; + uint8_t metric_expense; + uint8_t metric_delay; +}; + +struct isis_bcast_info { + uint8_t snpa[ETH_ALEN]; /* SNPA of this circuit */ + char run_dr_elect[ISIS_LEVELS]; /* Should we run dr election ? */ + struct event *t_run_dr[ISIS_LEVELS]; /* DR election thread */ + struct event *t_send_lan_hello[ISIS_LEVELS]; /* send LAN IIHs in this + thread */ + struct list *adjdb[ISIS_LEVELS]; /* adjacency dbs */ + struct list *lan_neighs[ISIS_LEVELS]; /* list of lx neigh snpa */ + char is_dr[ISIS_LEVELS]; /* Are we level x DR ? */ + uint8_t l1_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-1 DR */ + uint8_t l2_desig_is[ISIS_SYS_ID_LEN + 1]; /* level-2 DR */ + struct event *t_refresh_pseudo_lsp[ISIS_LEVELS]; /* refresh pseudo-node + LSPs */ +}; + +struct isis_p2p_info { + struct isis_adjacency *neighbor; + struct event *t_send_p2p_hello; /* send P2P IIHs in this thread */ +}; + +struct isis_circuit_arg { + int level; + struct isis_circuit *circuit; +}; + +/* + * Hello padding types + */ +enum isis_hello_padding { + ISIS_HELLO_PADDING_ALWAYS, + ISIS_HELLO_PADDING_DISABLED, + ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION +}; + +struct isis_circuit { + enum isis_circuit_state state; + uint8_t circuit_id; /* l1/l2 bcast CircuitID */ + time_t last_uptime; + struct isis *isis; + struct isis_area *area; /* back pointer to the area */ + struct interface *interface; /* interface info from z */ + int fd; /* IS-IS l1/2 socket */ + int sap_length; /* SAP length for DLPI */ + struct nlpids nlpids; + /* + * Threads + */ + struct event *t_read; + struct event *t_send_csnp[ISIS_LEVELS]; + struct event *t_send_psnp[ISIS_LEVELS]; + struct isis_tx_queue *tx_queue; + struct isis_circuit_arg + level_arg[ISIS_LEVELS]; /* used as argument for threads */ + + /* there is no real point in two streams, just for programming kicker */ + int (*rx)(struct isis_circuit *circuit, uint8_t *ssnpa); + struct stream *rcv_stream; /* Stream for receiving */ + int (*tx)(struct isis_circuit *circuit, int level); + struct stream *snd_stream; /* Stream for sending */ + int idx; /* idx in S[RM|SN] flags */ +#define CIRCUIT_T_UNKNOWN 0 +#define CIRCUIT_T_BROADCAST 1 +#define CIRCUIT_T_P2P 2 +#define CIRCUIT_T_LOOPBACK 3 + int circ_type; /* type of the physical interface */ + int circ_type_config; /* config type of the physical interface */ + union { + struct isis_bcast_info bc; + struct isis_p2p_info p2p; + } u; + uint8_t priority[ISIS_LEVELS]; /* l1/2 IS configured priority */ + enum isis_hello_padding pad_hellos; /* type of Hello PDUs padding */ + char ext_domain; /* externalDomain (boolean) */ + int lsp_regenerate_pending[ISIS_LEVELS]; + uint64_t lsp_error_counter; + + /* + * Configurables + */ + char *tag; /* area tag */ + struct isis_passwd passwd; /* Circuit rx/tx password */ + int is_type_config; /* configured circuit is type */ + int is_type; /* circuit is type == level of circuit + * differentiated from circuit type (media) */ + uint32_t hello_interval[ISIS_LEVELS]; /* hello-interval in seconds */ + uint16_t hello_multiplier[ISIS_LEVELS]; /* hello-multiplier */ + uint16_t csnp_interval[ISIS_LEVELS]; /* csnp-interval in seconds */ + uint16_t psnp_interval[ISIS_LEVELS]; /* psnp-interval in seconds */ + uint8_t metric[ISIS_LEVELS]; + uint32_t te_metric[ISIS_LEVELS]; + struct isis_ext_subtlvs *ext; /* Extended parameters (TE + Adj SID */ + int ip_router; /* Route IP ? */ + int is_passive; /* Is Passive ? */ + struct list *mt_settings; /* IS-IS MT Settings */ + struct list *ip_addrs; /* our IP addresses */ + int ipv6_router; /* Route IPv6 ? */ + struct list *ipv6_link; /* our link local IPv6 addresses */ + struct list *ipv6_non_link; /* our non-link local IPv6 addresses */ + uint16_t upadjcount[ISIS_LEVELS]; +#define ISIS_CIRCUIT_FLAPPED_AFTER_SPF 0x01 +#define ISIS_CIRCUIT_IF_DOWN_FROM_Z 0x02 + uint8_t flags; + bool disable_threeway_adj; + struct { + bool enabled; + char *profile; + } bfd_config; + struct ldp_sync_info *ldp_sync_info; + bool lfa_protection[ISIS_LEVELS]; + bool rlfa_protection[ISIS_LEVELS]; + uint32_t rlfa_max_metric[ISIS_LEVELS]; + struct hash *lfa_excluded_ifaces[ISIS_LEVELS]; + bool tilfa_protection[ISIS_LEVELS]; + bool tilfa_node_protection[ISIS_LEVELS]; + bool tilfa_link_fallback[ISIS_LEVELS]; + /* + * Counters as in 10589--11.2.5.9 + */ + uint32_t adj_state_changes; /* changesInAdjacencyState */ + uint32_t init_failures; /* intialisationFailures */ + uint32_t ctrl_pdus_rxed; /* controlPDUsReceived */ + uint32_t ctrl_pdus_txed; /* controlPDUsSent */ + uint32_t desig_changes[ISIS_LEVELS]; /* lanLxDesignatedIntermediateSystemChanges + */ + uint32_t rej_adjacencies; /* rejectedAdjacencies */ + /* + * Counters as in ietf-isis@2019-09-09.yang + */ + uint32_t id_len_mismatches; /* id-len-mismatch */ + uint32_t max_area_addr_mismatches; /* max-area-addresses-mismatch */ + uint32_t auth_type_failures; /*authentication-type-fails */ + uint32_t auth_failures; /* authentication-fails */ + + uint32_t snmp_id; /* Circuit id in snmp */ + + uint32_t snmp_adj_idx_gen; /* Create unique id for adjacency on creation + */ + struct list *snmp_adj_list; /* List in id order */ + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(isis_circuit); + +void isis_circuit_init(void); +struct isis_circuit *isis_circuit_new(struct interface *ifp, const char *tag); +void isis_circuit_del(struct isis_circuit *circuit); +struct isis_circuit *circuit_scan_by_ifp(struct interface *ifp); +void isis_circuit_configure(struct isis_circuit *circuit, + struct isis_area *area); +void isis_circuit_deconfigure(struct isis_circuit *circuit, + struct isis_area *area); +void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp); +void isis_circuit_if_del(struct isis_circuit *circuit, struct interface *ifp); +void isis_circuit_if_bind(struct isis_circuit *circuit, struct interface *ifp); +void isis_circuit_if_unbind(struct isis_circuit *circuit, + struct interface *ifp); +void isis_circuit_add_addr(struct isis_circuit *circuit, + struct connected *conn); +void isis_circuit_del_addr(struct isis_circuit *circuit, + struct connected *conn); +void isis_circuit_prepare(struct isis_circuit *circuit); +int isis_circuit_up(struct isis_circuit *circuit); +void isis_circuit_down(struct isis_circuit *); +void circuit_update_nlpids(struct isis_circuit *circuit); +void isis_circuit_print_vty(struct isis_circuit *circuit, struct vty *vty, + char detail); +void isis_circuit_print_json(struct isis_circuit *circuit, + struct json_object *json, char detail); +size_t isis_circuit_pdu_size(struct isis_circuit *circuit); +void isis_circuit_switchover_routes(struct isis_circuit *circuit, int family, + union g_addr *nexthop_ip, + ifindex_t ifindex); +void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream); + +void isis_circuit_af_set(struct isis_circuit *circuit, bool ip_router, + bool ipv6_router); +ferr_r isis_circuit_passive_set(struct isis_circuit *circuit, bool passive); +void isis_circuit_is_type_set(struct isis_circuit *circuit, int is_type); +void isis_circuit_circ_type_set(struct isis_circuit *circuit, int circ_type); + +ferr_r isis_circuit_metric_set(struct isis_circuit *circuit, int level, + int metric); + +ferr_r isis_circuit_passwd_unset(struct isis_circuit *circuit); +ferr_r isis_circuit_passwd_set(struct isis_circuit *circuit, + uint8_t passwd_type, const char *passwd); +ferr_r isis_circuit_passwd_cleartext_set(struct isis_circuit *circuit, + const char *passwd); +ferr_r isis_circuit_passwd_hmac_md5_set(struct isis_circuit *circuit, + const char *passwd); + +int isis_circuit_mt_enabled_set(struct isis_circuit *circuit, uint16_t mtid, + bool enabled); + +#ifdef FABRICD +DECLARE_HOOK(isis_circuit_config_write, + (struct isis_circuit *circuit, struct vty *vty), + (circuit, vty)); +#endif + +DECLARE_HOOK(isis_circuit_add_addr_hook, (struct isis_circuit *circuit), + (circuit)); + +DECLARE_HOOK(isis_circuit_new_hook, (struct isis_circuit *circuit), (circuit)); +DECLARE_HOOK(isis_circuit_del_hook, (struct isis_circuit *circuit), (circuit)); + +#endif /* _ZEBRA_ISIS_CIRCUIT_H */ diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c new file mode 100644 index 0000000..e6cc794 --- /dev/null +++ b/isisd/isis_cli.c @@ -0,0 +1,4126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "northbound_cli.h" +#include "libfrr.h" +#include "yang.h" +#include "lib/linklist.h" +#include "isisd/isisd.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_flex_algo.h" + +#include "isisd/isis_cli_clippy.c" + +#ifndef FABRICD + +/* + * XPath: /frr-isisd:isis/instance + */ +DEFPY_YANG_NOSH(router_isis, router_isis_cmd, + "router isis WORD$tag [vrf NAME$vrf_name]", + ROUTER_STR + "ISO IS-IS\n" + "ISO Routing area tag\n" VRF_CMD_HELP_STR) +{ + int ret; + char base_xpath[XPATH_MAXLEN]; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + snprintf(base_xpath, XPATH_MAXLEN, + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name); + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, "%s", base_xpath); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(ISIS_NODE, base_xpath); + + return ret; +} + +DEFPY_YANG(no_router_isis, no_router_isis_cmd, + "no router isis WORD$tag [vrf NAME$vrf_name]", + NO_STR ROUTER_STR + "ISO IS-IS\n" + "ISO Routing area tag\n" VRF_CMD_HELP_STR) +{ + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + if (!yang_dnode_existsf( + vty->candidate_config->dnode, + "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name)) { + vty_out(vty, "ISIS area %s not found.\n", tag); + return CMD_ERR_NOTHING_TODO; + } + + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes_clear_pending( + vty, "/frr-isisd:isis/instance[area-tag='%s'][vrf='%s']", tag, + vrf_name); +} + +void cli_show_router_isis(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *vrf = NULL; + + vrf = yang_dnode_get_string(dnode, "vrf"); + + vty_out(vty, "!\n"); + vty_out(vty, "router isis %s", + yang_dnode_get_string(dnode, "area-tag")); + if (!strmatch(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", yang_dnode_get_string(dnode, "vrf")); + vty_out(vty, "\n"); +} + +void cli_show_router_isis_end(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/ + * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing + * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing + * XPath: /frr-isisd:isis/instance + */ +DEFPY_YANG(ip_router_isis, ip_router_isis_cmd, + "ip router isis WORD$tag", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, + tag); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv4-routing", + NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_HIDDEN(ip_router_isis, ip_router_isis_vrf_cmd, + "ip router isis WORD$tag vrf NAME$vrf_name", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n" VRF_CMD_HELP_STR) + +DEFPY_YANG(ip6_router_isis, ip6_router_isis_cmd, + "ipv6 router isis WORD$tag", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/area-tag", NB_OP_MODIFY, + tag); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/ipv6-routing", + NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_HIDDEN(ip6_router_isis, ip6_router_isis_vrf_cmd, + "ipv6 router isis WORD$tag vrf NAME$vrf_name", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n" VRF_CMD_HELP_STR) + +DEFPY_YANG(no_ip_router_isis, no_ip_router_isis_cmd, + "no $ip router isis [WORD]$tag", + NO_STR + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n") +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (!dnode) + return CMD_SUCCESS; + + /* + * If both ipv4 and ipv6 are off delete the interface isis container. + */ + if (strmatch(ip, "ipv6")) { + if (!yang_dnode_get_bool(dnode, "ipv4-routing")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis", + NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/ipv6-routing", + NB_OP_MODIFY, "false"); + } else { + if (!yang_dnode_get_bool(dnode, "ipv6-routing")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis", + NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/ipv4-routing", + NB_OP_MODIFY, "false"); + } + + return nb_cli_apply_changes(vty, NULL); +} + +ALIAS_HIDDEN(no_ip_router_isis, no_ip_router_isis_vrf_cmd, + "no $ip router isis WORD$tag vrf NAME$vrf_name", + NO_STR + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IP router interface commands\n" + "IS-IS routing protocol\n" + "Routing process tag\n" + VRF_CMD_HELP_STR) + +void cli_show_ip_isis_ipv4(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " ip router isis %s\n", + yang_dnode_get_string(dnode, "../area-tag")); +} + +void cli_show_ip_isis_ipv6(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " ipv6 router isis %s\n", + yang_dnode_get_string(dnode, "../area-tag")); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring + */ +DEFPY_YANG(isis_bfd, + isis_bfd_cmd, + "[no] isis bfd", + NO_STR PROTO_HELP + "Enable BFD support\n") +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (dnode == NULL) { + vty_out(vty, "ISIS is not enabled on this circuit\n"); + return CMD_SUCCESS; + } + + nb_cli_enqueue_change(vty, "./frr-isisd:isis/bfd-monitoring/enabled", + NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile + */ +DEFPY_YANG(isis_bfd_profile, + isis_bfd_profile_cmd, + "[no] isis bfd profile BFDPROF$profile", + NO_STR PROTO_HELP + "Enable BFD support\n" + "Use a pre-configured profile\n" + "Profile name\n") +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (dnode == NULL) { + vty_out(vty, "ISIS is not enabled on this circuit\n"); + return CMD_SUCCESS; + } + + if (no) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/bfd-monitoring/profile", + NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/bfd-monitoring/profile", + NB_OP_MODIFY, profile); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_bfd_monitoring(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, "enabled")) { + if (show_defaults) + vty_out(vty, " no isis bfd\n"); + } else { + vty_out(vty, " isis bfd\n"); + } + + if (yang_dnode_exists(dnode, "profile")) + vty_out(vty, " isis bfd profile %s\n", + yang_dnode_get_string(dnode, "profile")); +} + +/* + * XPath: /frr-isisd:isis/instance/area-address + */ +DEFPY_YANG(net, net_cmd, "[no] net WORD", + "Remove an existing Network Entity Title for this process\n" + "A Network Entity Title for this process (OSI only)\n" + "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, XPATH_MAXLEN, "./area-address[.='%s']", net); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_area_address(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " net %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/is-type + */ +DEFPY_YANG(is_type, is_type_cmd, "is-type $level", + "IS Level for this routing process (OSI only)\n" + "Act as a station router only\n" + "Act as both a station router and an area router\n" + "Act as an area router only\n") +{ + nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, + strmatch(level, "level-2-only") ? "level-2" + : level); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_is_type, no_is_type_cmd, + "no is-type []", + NO_STR + "IS Level for this routing process (OSI only)\n" + "Act as a station router only\n" + "Act as both a station router and an area router\n" + "Act as an area router only\n") +{ + nb_cli_enqueue_change(vty, "./is-type", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_is_type(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int is_type = yang_dnode_get_enum(dnode, NULL); + + switch (is_type) { + case IS_LEVEL_1: + vty_out(vty, " is-type level-1\n"); + break; + case IS_LEVEL_2: + vty_out(vty, " is-type level-2-only\n"); + break; + case IS_LEVEL_1_AND_2: + vty_out(vty, " is-type level-1-2\n"); + break; + } +} + +/* + * XPath: /frr-isisd:isis/instance/dynamic-hostname + */ +DEFPY_YANG(dynamic_hostname, dynamic_hostname_cmd, "[no] hostname dynamic", + NO_STR + "Dynamic hostname for IS-IS\n" + "Dynamic hostname\n") +{ + nb_cli_enqueue_change(vty, "./dynamic-hostname", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_dynamic_hostname(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " hostname dynamic\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/overload + */ +DEFPY_YANG(set_overload_bit, set_overload_bit_cmd, "[no] set-overload-bit", + "Reset overload bit to accept transit traffic\n" + "Set overload bit to avoid any transit traffic\n") +{ + nb_cli_enqueue_change(vty, "./overload/enabled", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " set-overload-bit\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/overload/on-startup + */ +DEFPY_YANG(set_overload_bit_on_startup, set_overload_bit_on_startup_cmd, + "set-overload-bit on-startup (0-86400)$val", + "Set overload bit to avoid any transit traffic\n" + "Set overload bit on startup\n" + "Set overload time in seconds\n") +{ + nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_MODIFY, + val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_overload_bit_on_startup, no_set_overload_bit_on_startup_cmd, + "no set-overload-bit on-startup [(0-86400)$val]", + NO_STR + "Reset overload bit to accept transit traffic\n" + "Set overload bit on startup\n" + "Set overload time in seconds\n") +{ + nb_cli_enqueue_change(vty, "./overload/on-startup", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_overload_on_startup(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " set-overload-bit on-startup %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/advertise-high-metrics + */ +DEFPY_YANG(advertise_high_metrics, advertise_high_metrics_cmd, + "[no] advertise-high-metrics", + NO_STR "Advertise high metric value on all interfaces\n") +{ + nb_cli_enqueue_change(vty, "./advertise-high-metrics", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_advertise_high_metrics(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " advertise-high-metrics\n"); + else if (show_defaults) + vty_out(vty, " no advertise-high-metrics\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/attach-send + */ +DEFPY_YANG(attached_bit_send, attached_bit_send_cmd, "[no] attached-bit send", + "Reset attached bit\n" + "Set attached bit for inter-area traffic\n" + "Set attached bit in LSP sent to L1 router\n") +{ + nb_cli_enqueue_change(vty, "./attach-send", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_attached_send(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " attached-bit send\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/attach-receive-ignore + */ +DEFPY_YANG( + attached_bit_receive_ignore, attached_bit_receive_ignore_cmd, + "[no] attached-bit receive ignore", + "Reset attached bit\n" + "Set attach bit for inter-area traffic\n" + "If LSP received with attached bit set, create default route to neighbor\n" + "Do not process attached bit\n") +{ + nb_cli_enqueue_change(vty, "./attach-receive-ignore", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_attached_receive(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " attached-bit receive ignore\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/metric-style + */ +DEFPY_YANG(metric_style, metric_style_cmd, + "metric-style $style", + "Use old-style (ISO 10589) or new-style packet formats\n" + "Use old style of TLVs with narrow metric\n" + "Send and accept both styles of TLVs during transition\n" + "Use new style of TLVs to carry wider metric\n") +{ + nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, style); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_metric_style, no_metric_style_cmd, + "no metric-style [narrow|transition|wide]", + NO_STR + "Use old-style (ISO 10589) or new-style packet formats\n" + "Use old style of TLVs with narrow metric\n" + "Send and accept both styles of TLVs during transition\n" + "Use new style of TLVs to carry wider metric\n") +{ + nb_cli_enqueue_change(vty, "./metric-style", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_metric_style(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int metric = yang_dnode_get_enum(dnode, NULL); + + switch (metric) { + case ISIS_NARROW_METRIC: + vty_out(vty, " metric-style narrow\n"); + break; + case ISIS_WIDE_METRIC: + vty_out(vty, " metric-style wide\n"); + break; + case ISIS_TRANSITION_METRIC: + vty_out(vty, " metric-style transition\n"); + break; + } +} + +/* + * XPath: /frr-isisd:isis/instance/area-password + */ +DEFPY_YANG(area_passwd, area_passwd_cmd, + "area-password $pwd_type WORD$pwd [authenticate snp $snp]", + "Configure the authentication password for an area\n" + "Clear-text authentication type\n" + "MD5 authentication type\n" + "Level-wide password\n" + "Authentication\n" + "SNP PDUs\n" + "Send but do not check PDUs on receiving\n" + "Send and check PDUs on receiving\n") +{ + nb_cli_enqueue_change(vty, "./area-password", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./area-password/password", NB_OP_MODIFY, + pwd); + nb_cli_enqueue_change(vty, "./area-password/password-type", + NB_OP_MODIFY, pwd_type); + nb_cli_enqueue_change(vty, "./area-password/authenticate-snp", + NB_OP_MODIFY, snp ? snp : "none"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_area_pwd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *snp; + + vty_out(vty, " area-password %s %s", + yang_dnode_get_string(dnode, "password-type"), + yang_dnode_get_string(dnode, "password")); + snp = yang_dnode_get_string(dnode, "authenticate-snp"); + if (!strmatch("none", snp)) + vty_out(vty, " authenticate snp %s", snp); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/domain-password + */ +DEFPY_YANG(domain_passwd, domain_passwd_cmd, + "domain-password $pwd_type WORD$pwd [authenticate snp $snp]", + "Set the authentication password for a routing domain\n" + "Clear-text authentication type\n" + "MD5 authentication type\n" + "Level-wide password\n" + "Authentication\n" + "SNP PDUs\n" + "Send but do not check PDUs on receiving\n" + "Send and check PDUs on receiving\n") +{ + nb_cli_enqueue_change(vty, "./domain-password", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./domain-password/password", NB_OP_MODIFY, + pwd); + nb_cli_enqueue_change(vty, "./domain-password/password-type", + NB_OP_MODIFY, pwd_type); + nb_cli_enqueue_change(vty, "./domain-password/authenticate-snp", + NB_OP_MODIFY, snp ? snp : "none"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_area_passwd, no_area_passwd_cmd, + "no $cmd", + NO_STR + "Configure the authentication password for an area\n" + "Set the authentication password for a routing domain\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./%s", cmd); +} + +void cli_show_isis_domain_pwd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *snp; + + vty_out(vty, " domain-password %s %s", + yang_dnode_get_string(dnode, "password-type"), + yang_dnode_get_string(dnode, "password")); + snp = yang_dnode_get_string(dnode, "authenticate-snp"); + if (!strmatch("none", snp)) + vty_out(vty, " authenticate snp %s", snp); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/generation-interval + * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/generation-interval + */ +DEFPY_YANG(lsp_gen_interval, lsp_gen_interval_cmd, + "lsp-gen-interval [level-1|level-2]$level (1-120)$val", + "Minimum interval between regenerating same LSP\n" + "Set interval for level 1 only\n" + "Set interval for level 2 only\n" + "Minimum interval in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./lsp/timers/level-1/generation-interval", + NB_OP_MODIFY, val_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./lsp/timers/level-2/generation-interval", + NB_OP_MODIFY, val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_lsp_gen_interval, no_lsp_gen_interval_cmd, + "no lsp-gen-interval [level-1|level-2]$level [(1-120)]", + NO_STR + "Minimum interval between regenerating same LSP\n" + "Set interval for level 1 only\n" + "Set interval for level 2 only\n" + "Minimum interval in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./lsp/timers/level-1/generation-interval", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./lsp/timers/level-2/generation-interval", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval + * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval + */ +DEFPY_YANG(lsp_refresh_interval, lsp_refresh_interval_cmd, + "lsp-refresh-interval [level-1|level-2]$level (1-65235)$val", + "LSP refresh interval\n" + "LSP refresh interval for Level 1 only\n" + "LSP refresh interval for Level 2 only\n" + "LSP refresh interval in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/refresh-interval", + NB_OP_MODIFY, val_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/refresh-interval", + NB_OP_MODIFY, val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_lsp_refresh_interval, no_lsp_refresh_interval_cmd, + "no lsp-refresh-interval [level-1|level-2]$level [(1-65235)]", + NO_STR + "LSP refresh interval\n" + "LSP refresh interval for Level 1 only\n" + "LSP refresh interval for Level 2 only\n" + "LSP refresh interval in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/refresh-interval", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/refresh-interval", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime + */ + +DEFPY_YANG(max_lsp_lifetime, max_lsp_lifetime_cmd, + "max-lsp-lifetime [level-1|level-2]$level (350-65535)$val", + "Maximum LSP lifetime\n" + "Maximum LSP lifetime for Level 1 only\n" + "Maximum LSP lifetime for Level 2 only\n" + "LSP lifetime in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/maximum-lifetime", + NB_OP_MODIFY, val_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/maximum-lifetime", + NB_OP_MODIFY, val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_max_lsp_lifetime, no_max_lsp_lifetime_cmd, + "no max-lsp-lifetime [level-1|level-2]$level [(350-65535)]", + NO_STR + "Maximum LSP lifetime\n" + "Maximum LSP lifetime for Level 1 only\n" + "Maximum LSP lifetime for Level 2 only\n" + "LSP lifetime in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/maximum-lifetime", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/maximum-lifetime", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +/* unified LSP timers command + * XPath: /frr-isisd:isis/instance/lsp/timers + */ + +DEFPY_YANG(lsp_timers, lsp_timers_cmd, + "lsp-timers [level-1|level-2]$level gen-interval (1-120)$gen refresh-interval (1-65235)$refresh max-lifetime (350-65535)$lifetime", + "LSP-related timers\n" + "LSP-related timers for Level 1 only\n" + "LSP-related timers for Level 2 only\n" + "Minimum interval between regenerating same LSP\n" + "Generation interval in seconds\n" + "LSP refresh interval\n" + "LSP refresh interval in seconds\n" + "Maximum LSP lifetime\n" + "Maximum LSP lifetime in seconds\n") +{ + if (!level || strmatch(level, "level-1")) { + nb_cli_enqueue_change( + vty, "./lsp/timers/level-1/generation-interval", + NB_OP_MODIFY, gen_str); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/refresh-interval", + NB_OP_MODIFY, refresh_str); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/maximum-lifetime", + NB_OP_MODIFY, lifetime_str); + } + if (!level || strmatch(level, "level-2")) { + nb_cli_enqueue_change( + vty, "./lsp/timers/level-2/generation-interval", + NB_OP_MODIFY, gen_str); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/refresh-interval", + NB_OP_MODIFY, refresh_str); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/maximum-lifetime", + NB_OP_MODIFY, lifetime_str); + } + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_lsp_timers, no_lsp_timers_cmd, + "no lsp-timers [level-1|level-2]$level [gen-interval (1-120) refresh-interval (1-65235) max-lifetime (350-65535)]", + NO_STR + "LSP-related timers\n" + "LSP-related timers for Level 1 only\n" + "LSP-related timers for Level 2 only\n" + "Minimum interval between regenerating same LSP\n" + "Generation interval in seconds\n" + "LSP refresh interval\n" + "LSP refresh interval in seconds\n" + "Maximum LSP lifetime\n" + "Maximum LSP lifetime in seconds\n") +{ + if (!level || strmatch(level, "level-1")) { + nb_cli_enqueue_change( + vty, "./lsp/timers/level-1/generation-interval", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/refresh-interval", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-1/maximum-lifetime", + NB_OP_MODIFY, NULL); + } + if (!level || strmatch(level, "level-2")) { + nb_cli_enqueue_change( + vty, "./lsp/timers/level-2/generation-interval", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/refresh-interval", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./lsp/timers/level-2/maximum-lifetime", + NB_OP_MODIFY, NULL); + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_lsp_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1_refresh = + yang_dnode_get_string(dnode, "level-1/refresh-interval"); + const char *l2_refresh = + yang_dnode_get_string(dnode, "level-2/refresh-interval"); + const char *l1_lifetime = + yang_dnode_get_string(dnode, "level-1/maximum-lifetime"); + const char *l2_lifetime = + yang_dnode_get_string(dnode, "level-2/maximum-lifetime"); + const char *l1_gen = + yang_dnode_get_string(dnode, "level-1/generation-interval"); + const char *l2_gen = + yang_dnode_get_string(dnode, "level-2/generation-interval"); + if (strmatch(l1_refresh, l2_refresh) + && strmatch(l1_lifetime, l2_lifetime) && strmatch(l1_gen, l2_gen)) + vty_out(vty, + " lsp-timers gen-interval %s refresh-interval %s max-lifetime %s\n", + l1_gen, l1_refresh, l1_lifetime); + else { + vty_out(vty, + " lsp-timers level-1 gen-interval %s refresh-interval %s max-lifetime %s\n", + l1_gen, l1_refresh, l1_lifetime); + vty_out(vty, + " lsp-timers level-2 gen-interval %s refresh-interval %s max-lifetime %s\n", + l2_gen, l2_refresh, l2_lifetime); + } +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/mtu + */ +DEFPY_YANG(area_lsp_mtu, area_lsp_mtu_cmd, "lsp-mtu (128-4352)$val", + "Configure the maximum size of generated LSPs\n" + "Maximum size of generated LSPs\n") +{ + nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_area_lsp_mtu, no_area_lsp_mtu_cmd, "no lsp-mtu [(128-4352)]", + NO_STR + "Configure the maximum size of generated LSPs\n" + "Maximum size of generated LSPs\n") +{ + nb_cli_enqueue_change(vty, "./lsp/mtu", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_lsp_mtu(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " lsp-mtu %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/advertise-passive-only + */ +DEFPY_YANG(advertise_passive_only, advertise_passive_only_cmd, + "[no] advertise-passive-only", + NO_STR "Advertise prefixes of passive interfaces only\n") +{ + nb_cli_enqueue_change(vty, "./advertise-passive-only", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_advertise_passive_only(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " advertise-passive-only\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/spf/minimum-interval + */ +DEFPY_YANG(spf_interval, spf_interval_cmd, + "spf-interval [level-1|level-2]$level (1-120)$val", + "Minimum interval between SPF calculations\n" + "Set interval for level 1 only\n" + "Set interval for level 2 only\n" + "Minimum interval between consecutive SPFs in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1", + NB_OP_MODIFY, val_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2", + NB_OP_MODIFY, val_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_spf_interval, no_spf_interval_cmd, + "no spf-interval [level-1|level-2]$level [(1-120)]", + NO_STR + "Minimum interval between SPF calculations\n" + "Set interval for level 1 only\n" + "Set interval for level 2 only\n" + "Minimum interval between consecutive SPFs in seconds\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./spf/minimum-interval/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_spf_min_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " spf-interval %s\n", l1); + else { + vty_out(vty, " spf-interval level-1 %s\n", l1); + vty_out(vty, " spf-interval level-2 %s\n", l2); + } +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay + */ +DEFPY_YANG(spf_delay_ietf, spf_delay_ietf_cmd, + "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)", + "IETF SPF delay algorithm\n" + "Delay used while in QUIET state\n" + "Delay used while in QUIET state in milliseconds\n" + "Delay used while in SHORT_WAIT state\n" + "Delay used while in SHORT_WAIT state in milliseconds\n" + "Delay used while in LONG_WAIT\n" + "Delay used while in LONG_WAIT state in milliseconds\n" + "Time with no received IGP events before considering IGP stable\n" + "Time with no received IGP events before considering IGP stable (in milliseconds)\n" + "Maximum duration needed to learn all the events related to a single failure\n" + "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") +{ + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_CREATE, + NULL); + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/init-delay", + NB_OP_MODIFY, init_delay_str); + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/short-delay", + NB_OP_MODIFY, short_delay_str); + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/long-delay", + NB_OP_MODIFY, long_delay_str); + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/hold-down", + NB_OP_MODIFY, holddown_str); + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay/time-to-learn", + NB_OP_MODIFY, time_to_learn_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_spf_delay_ietf, no_spf_delay_ietf_cmd, + "no spf-delay-ietf [init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)]", + NO_STR + "IETF SPF delay algorithm\n" + "Delay used while in QUIET state\n" + "Delay used while in QUIET state in milliseconds\n" + "Delay used while in SHORT_WAIT state\n" + "Delay used while in SHORT_WAIT state in milliseconds\n" + "Delay used while in LONG_WAIT\n" + "Delay used while in LONG_WAIT state in milliseconds\n" + "Time with no received IGP events before considering IGP stable\n" + "Time with no received IGP events before considering IGP stable (in milliseconds)\n" + "Maximum duration needed to learn all the events related to a single failure\n" + "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") +{ + nb_cli_enqueue_change(vty, "./spf/ietf-backoff-delay", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_spf_ietf_backoff(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, + " spf-delay-ietf init-delay %s short-delay %s long-delay %s holddown %s time-to-learn %s\n", + yang_dnode_get_string(dnode, "init-delay"), + yang_dnode_get_string(dnode, "short-delay"), + yang_dnode_get_string(dnode, "long-delay"), + yang_dnode_get_string(dnode, "hold-down"), + yang_dnode_get_string(dnode, "time-to-learn")); +} + +/* + * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name + */ +DEFPY_YANG(spf_prefix_priority, spf_prefix_priority_cmd, + "spf prefix-priority $priority ACCESSLIST_NAME$acl_name", + "SPF configuration\n" + "Configure a prefix priority list\n" + "Specify critical priority prefixes\n" + "Specify high priority prefixes\n" + "Specify medium priority prefixes\n" + "Access-list name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, XPATH_MAXLEN, + "./spf/prefix-priorities/%s/access-list-name", priority); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, acl_name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_spf_prefix_priority, no_spf_prefix_priority_cmd, + "no spf prefix-priority $priority [ACCESSLIST_NAME]", + NO_STR + "SPF configuration\n" + "Configure a prefix priority list\n" + "Specify critical priority prefixes\n" + "Specify high priority prefixes\n" + "Specify medium priority prefixes\n" + "Access-list name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, XPATH_MAXLEN, + "./spf/prefix-priorities/%s/access-list-name", priority); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_spf_prefix_priority(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " spf prefix-priority %s %s\n", + dnode->parent->schema->name, + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/purge-originator + */ +DEFPY_YANG(area_purge_originator, area_purge_originator_cmd, "[no] purge-originator", + NO_STR "Use the RFC 6232 purge-originator\n") +{ + nb_cli_enqueue_change(vty, "./purge-originator", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_purge_origin(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " purge-originator\n"); +} + + +/* + * XPath: /frr-isisd:isis/instance/admin-group-send-zero + */ +DEFPY_YANG(isis_admin_group_send_zero, isis_admin_group_send_zero_cmd, + "[no] admin-group-send-zero", + NO_STR + "Allow sending the default admin-group value of 0x00000000.\n") +{ + nb_cli_enqueue_change(vty, "./admin-group-send-zero", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_admin_group_send_zero(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " admin-group-send-zero\n"); +} + + +/* + * XPath: /frr-isisd:isis/instance/asla-legacy-flag + */ +DEFPY_HIDDEN(isis_asla_legacy_flag, isis_asla_legacy_flag_cmd, + "[no] asla-legacy-flag", + NO_STR "Set the legacy flag (aka. L-FLAG) in the ASLA Sub-TLV.\n") +{ + nb_cli_enqueue_change(vty, "./asla-legacy-flag", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_asla_legacy_flag(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " asla-legacy-flag\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te + */ +DEFPY_YANG(isis_mpls_te_on, isis_mpls_te_on_cmd, "mpls-te on", + MPLS_TE_STR "Enable the MPLS-TE functionality\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_mpls_te_on, no_isis_mpls_te_on_cmd, "no mpls-te [on]", + NO_STR + "Disable the MPLS-TE functionality\n" + "Disable the MPLS-TE functionality\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_te(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " mpls-te on\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/router-address + */ +DEFPY_YANG(isis_mpls_te_router_addr, isis_mpls_te_router_addr_cmd, + "mpls-te router-address A.B.C.D", + MPLS_TE_STR + "Stable IP address of the advertising router\n" + "MPLS-TE router address in IPv4 address format\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/router-address", + NB_OP_MODIFY, router_address_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_mpls_te_router_addr, no_isis_mpls_te_router_addr_cmd, + "no mpls-te router-address [A.B.C.D]", + NO_STR MPLS_TE_STR + "Delete IP address of the advertising router\n" + "MPLS-TE router address in IPv4 address format\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/router-address", + NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_te_router_addr(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " mpls-te router-address %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/router-address-v6 + */ +DEFPY_YANG(isis_mpls_te_router_addr_v6, isis_mpls_te_router_addr_v6_cmd, + "mpls-te router-address ipv6 X:X::X:X", + MPLS_TE_STR + "Stable IP address of the advertising router\n" + "IPv6 address\n" + "MPLS-TE router address in IPv6 address format\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/router-address-v6", NB_OP_MODIFY, + ipv6_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_mpls_te_router_addr_v6, no_isis_mpls_te_router_addr_v6_cmd, + "no mpls-te router-address ipv6 [X:X::X:X]", + NO_STR MPLS_TE_STR + "Delete IP address of the advertising router\n" + "IPv6 address\n" + "MPLS-TE router address in IPv6 address format\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/router-address-v6", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_te_router_addr_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " mpls-te router-address ipv6 %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG(isis_mpls_te_inter_as, isis_mpls_te_inter_as_cmd, + "[no] mpls-te inter-as [level-1|level-1-2|level-2-only]", + NO_STR MPLS_TE_STR + "Configure MPLS-TE Inter-AS support\n" + "AREA native mode self originate INTER-AS LSP with L1 only flooding scope\n" + "AREA native mode self originate INTER-AS LSP with L1 and L2 flooding scope\n" + "AS native mode self originate INTER-AS LSP with L2 only flooding scope\n") +{ + vty_out(vty, "MPLS-TE Inter-AS is not yet supported\n"); + return CMD_SUCCESS; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/export + */ +DEFPY_YANG(isis_mpls_te_export, isis_mpls_te_export_cmd, "mpls-te export", + MPLS_TE_STR "Enable export of MPLS-TE Link State information\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/export", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_mpls_te_export, no_isis_mpls_te_export_cmd, + "no mpls-te export", + NO_STR MPLS_TE_STR + "Disable export of MPLS-TE Link State information\n") +{ + nb_cli_enqueue_change(vty, "./mpls-te/export", NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_te_export(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " mpls-te export\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate + */ +DEFPY_YANG(isis_default_originate, isis_default_originate_cmd, + "[no] default-information originate $ip $level [always]$always [{metric (0-16777215)$metric|route-map RMAP_NAME$rmap}]", + NO_STR + "Control distribution of default information\n" + "Distribute a default route\n" + "Distribute default route for IPv4\n" + "Distribute default route for IPv6\n" + "Distribute default route into level-1\n" + "Distribute default route into level-2\n" + "Always advertise default route\n" + "Metric for default route\n" + "IS-IS default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + if (no) + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./always", NB_OP_MODIFY, + always ? "true" : "false"); + nb_cli_enqueue_change(vty, "./route-map", + rmap ? NB_OP_MODIFY : NB_OP_DESTROY, + rmap ? rmap : NULL); + nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, + metric_str ? metric_str : NULL); + if (strmatch(ip, "ipv6") && !always) { + vty_out(vty, + "Zebra doesn't implement default-originate for IPv6 yet\n"); + vty_out(vty, + "so use with care or use default-originate always.\n"); + } + } + + return nb_cli_apply_changes( + vty, "./default-information-originate/%s[level='%s']", ip, + level); +} + +static void vty_print_def_origin(struct vty *vty, const struct lyd_node *dnode, + const char *family, const char *level, + bool show_defaults) +{ + vty_out(vty, " default-information originate %s %s", family, level); + if (yang_dnode_get_bool(dnode, "always")) + vty_out(vty, " always"); + + if (yang_dnode_exists(dnode, "route-map")) + vty_out(vty, " route-map %s", + yang_dnode_get_string(dnode, "route-map")); + if (show_defaults || !yang_dnode_is_default(dnode, "metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "metric")); + + vty_out(vty, "\n"); +} + +void cli_show_isis_def_origin_ipv4(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *level = yang_dnode_get_string(dnode, "level"); + + vty_print_def_origin(vty, dnode, "ipv4", level, show_defaults); +} + +void cli_show_isis_def_origin_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *level = yang_dnode_get_string(dnode, "level"); + + vty_print_def_origin(vty, dnode, "ipv6", level, show_defaults); +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute + */ +DEFPY_YANG(isis_redistribute, isis_redistribute_cmd, + "[no] redistribute $level" + "[{metric (0-16777215)|route-map RMAP_NAME$route_map}]", + NO_STR REDIST_STR + "Redistribute IPv4 routes\n" + PROTO_IP_REDIST_HELP + "Redistribute IPv6 routes\n" + PROTO_IP6_REDIST_HELP + "Redistribute into level-1\n" + "Redistribute into level-2\n" + "Metric for redistributed routes\n" + "IS-IS default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + if (no) + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./route-map", + route_map ? NB_OP_MODIFY : NB_OP_DESTROY, + route_map ? route_map : NULL); + nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, + metric_str ? metric_str : NULL); + } + + return nb_cli_apply_changes( + vty, "./redistribute/%s[protocol='%s'][level='%s']", ip, proto, + level); +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/table + */ +DEFPY_YANG(isis_redistribute_table, isis_redistribute_table_cmd, + "[no] redistribute $ip table (1-65535)$table" + "$level [{metric (0-16777215)|route-map WORD}]", + NO_STR REDIST_STR "Redistribute IPv4 routes\n" + "Redistribute IPv6 routes\n" + "Non-main Kernel Routing Table\n" + "Table Id\n" + "Redistribute into level-1\n" + "Redistribute into level-2\n" + "Metric for redistributed routes\n" + "IS-IS default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + struct isis_redist_table_present_args rtda = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + int rv; + + rtda.rtda_table = table_str; + rtda.rtda_ip = ip; + rtda.rtda_level = level; + + if (no) { + if (!isis_redist_table_is_present(vty, &rtda)) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(xpath, sizeof(xpath), "./table[table='%s']", table_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + rv = nb_cli_apply_changes(vty, + "./redistribute/%s[protocol='table'][level='%s']", + ip, level); + if (rv == CMD_SUCCESS) { + if (isis_redist_table_get_first(vty, &rtda) > 0) + return CMD_SUCCESS; + nb_cli_enqueue_change(vty, "./table", NB_OP_DESTROY, + NULL); + nb_cli_apply_changes(vty, + "./redistribute/%s[protocol='table'][level='%s']", + ip, level); + } + return CMD_SUCCESS; + } + if (isis_redist_table_is_present(vty, &rtda)) + return CMD_SUCCESS; + + snprintf(xpath, sizeof(xpath), "./table[table='%s']", table_str); + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_entry, sizeof(xpath_entry), "%s/route-map", xpath); + nb_cli_enqueue_change(vty, xpath_entry, + route_map ? NB_OP_MODIFY : NB_OP_DESTROY, + route_map ? route_map : NULL); + snprintf(xpath_entry, sizeof(xpath_entry), "%s/metric", xpath); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_MODIFY, + metric_str ? metric_str : NULL); + return nb_cli_apply_changes(vty, + "./redistribute/%s[protocol='table'][level='%s']", + ip, level); +} + +static void vty_print_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults, const char *family, + bool table) +{ + const char *level; + const char *protocol = NULL; + const char *routemap = NULL; + uint16_t tableid; + + if (table) { + level = yang_dnode_get_string(dnode, "../level"); + tableid = yang_dnode_get_uint16(dnode, "table"); + vty_out(vty, " redistribute %s table %d ", family, tableid); + } else { + protocol = yang_dnode_get_string(dnode, "protocol"); + if (!table && strmatch(protocol, "table")) + return; + level = yang_dnode_get_string(dnode, "level"); + vty_out(vty, " redistribute %s %s ", family, protocol); + } + vty_out(vty, "%s", level); + if (show_defaults || !yang_dnode_is_default(dnode, "metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "%s", "metric")); + + if (yang_dnode_exists(dnode, "route-map")) + routemap = yang_dnode_get_string(dnode, "route-map"); + if (routemap) + vty_out(vty, " route-map %s", routemap); + vty_out(vty, "\n"); +} + +void cli_show_isis_redistribute_ipv4(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_print_redistribute(vty, dnode, show_defaults, "ipv4", false); +} + +void cli_show_isis_redistribute_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_print_redistribute(vty, dnode, show_defaults, "ipv6", false); +} + +void cli_show_isis_redistribute_ipv4_table(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_print_redistribute(vty, dnode, show_defaults, "ipv4", true); +} + +void cli_show_isis_redistribute_ipv6_table(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_print_redistribute(vty, dnode, show_defaults, "ipv6", true); +} + +int cli_cmp_isis_redistribute_table(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + uint16_t table1 = yang_dnode_get_uint16(dnode1, "table"); + uint16_t table2 = yang_dnode_get_uint16(dnode2, "table"); + + return table1 - table2; +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology + */ +DEFPY_YANG( + isis_topology, isis_topology_cmd, + "[no] topology $topology [overload]$overload", + NO_STR + "Configure IS-IS topologies\n" + "standard topology\n" + "IPv4 unicast topology\n" + "IPv4 management topology\n" + "IPv6 unicast topology\n" + "IPv4 multicast topology\n" + "IPv6 multicast topology\n" + "IPv6 management topology\n" + "IPv6 dst-src topology\n" + "Set overload bit for topology\n") +{ + char base_xpath[XPATH_MAXLEN]; + + /* Since standard is not configurable it is not present in the + * YANG model, so we need to validate it here + */ + if (strmatch(topology, "standard") || + strmatch(topology, "ipv4-unicast")) { + vty_out(vty, + "Cannot configure IPv4 unicast (Standard) topology\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (strmatch(topology, "ipv4-mgmt")) + snprintf(base_xpath, XPATH_MAXLEN, + "./multi-topology/ipv4-management"); + else if (strmatch(topology, "ipv6-mgmt")) + snprintf(base_xpath, XPATH_MAXLEN, + "./multi-topology/ipv6-management"); + else + snprintf(base_xpath, XPATH_MAXLEN, "./multi-topology/%s", + topology); + + if (no) + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + else { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./overload", NB_OP_MODIFY, + overload ? "true" : "false"); + } + + return nb_cli_apply_changes(vty, "%s", base_xpath); +} + +void cli_show_isis_mt_ipv4_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv4-multicast"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv4-mgmt"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +void cli_show_isis_mt_ipv6_unicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv6-unicast"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +void cli_show_isis_mt_ipv6_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv6-multicast"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv6-mgmt"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " topology ipv6-dstsrc"); + if (yang_dnode_get_bool(dnode, "overload")) + vty_out(vty, " overload"); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/enabled + */ +DEFPY_YANG (isis_sr_enable, + isis_sr_enable_cmd, + "segment-routing on", + SR_STR + "Enable Segment Routing\n") +{ + nb_cli_enqueue_change(vty, "./segment-routing/enabled", NB_OP_MODIFY, + "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_isis_sr_enable, + no_isis_sr_enable_cmd, + "no segment-routing [on]", + NO_STR + SR_STR + "Disable Segment Routing\n") +{ + nb_cli_enqueue_change(vty, "./segment-routing/enabled", NB_OP_MODIFY, + "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_sr_enabled(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " segment-routing on\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-block + */ + +DEFPY_YANG( + isis_sr_global_block_label_range, isis_sr_global_block_label_range_cmd, + "segment-routing global-block (16-1048575)$gb_lower_bound (16-1048575)$gb_upper_bound [local-block (16-1048575)$lb_lower_bound (16-1048575)$lb_upper_bound]", + SR_STR + "Segment Routing Global Block label range\n" + "The lower bound of the global block\n" + "The upper bound of the global block (block size may not exceed 65535)\n" + "Segment Routing Local Block label range\n" + "The lower bound of the local block\n" + "The upper bound of the local block (block size may not exceed 65535)\n") +{ + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srgb/lower-bound", + NB_OP_MODIFY, gb_lower_bound_str); + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srgb/upper-bound", + NB_OP_MODIFY, gb_upper_bound_str); + + nb_cli_enqueue_change( + vty, "./segment-routing/label-blocks/srlb/lower-bound", + NB_OP_MODIFY, lb_lower_bound ? lb_lower_bound_str : NULL); + nb_cli_enqueue_change( + vty, "./segment-routing/label-blocks/srlb/upper-bound", + NB_OP_MODIFY, lb_upper_bound ? lb_upper_bound_str : NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_sr_global_block_label_range, + no_isis_sr_global_block_label_range_cmd, + "no segment-routing global-block [(16-1048575) (16-1048575) local-block (16-1048575) (16-1048575)]", + NO_STR SR_STR + "Segment Routing Global Block label range\n" + "The lower bound of the global block\n" + "The upper bound of the global block (block size may not exceed 65535)\n" + "Segment Routing Local Block label range\n" + "The lower bound of the local block\n" + "The upper bound of the local block (block size may not exceed 65535)\n") +{ + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srgb/lower-bound", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srgb/upper-bound", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srlb/lower-bound", + NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, + "./segment-routing/label-blocks/srlb/upper-bound", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_label_blocks(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " segment-routing global-block %s %s", + yang_dnode_get_string(dnode, "srgb/lower-bound"), + yang_dnode_get_string(dnode, "srgb/upper-bound")); + if (!yang_dnode_is_default(dnode, "srlb/lower-bound") + || !yang_dnode_is_default(dnode, "srlb/upper-bound")) + vty_out(vty, " local-block %s %s", + yang_dnode_get_string(dnode, "srlb/lower-bound"), + yang_dnode_get_string(dnode, "srlb/upper-bound")); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/msd/node-msd + */ +DEFPY_YANG (isis_sr_node_msd, + isis_sr_node_msd_cmd, + "segment-routing node-msd (1-16)$msd", + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that can be stack (1-16)\n") +{ + nb_cli_enqueue_change(vty, "./segment-routing/msd/node-msd", + NB_OP_MODIFY, msd_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_isis_sr_node_msd, + no_isis_sr_node_msd_cmd, + "no segment-routing node-msd [(1-16)]", + NO_STR + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that can be stack (1-16)\n") +{ + nb_cli_enqueue_change(vty, "./segment-routing/msd/node-msd", + NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_node_msd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " segment-routing node-msd %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid + */ +DEFPY_YANG (isis_sr_prefix_sid, + isis_sr_prefix_sid_cmd, + "segment-routing prefix\ + $prefix\ + \ + [$lh_behavior] [n-flag-clear$n_flag_clear]", + SR_STR + "Prefix SID\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n" + "Specify the absolute value of Prefix Segment ID\n" + "The Prefix Segment ID value\n" + "Specify the index of Prefix Segment ID\n" + "The Prefix Segment ID index\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n" + "Not a node SID\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./sid-value-type", NB_OP_MODIFY, sid_type); + nb_cli_enqueue_change(vty, "./sid-value", NB_OP_MODIFY, sid_value_str); + if (lh_behavior) { + const char *value; + + if (strmatch(lh_behavior, "no-php-flag")) + value = "no-php"; + else if (strmatch(lh_behavior, "explicit-null")) + value = "explicit-null"; + else + value = "php"; + + nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY, + value); + } else + nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY, + NULL); + nb_cli_enqueue_change(vty, "./n-flag-clear", NB_OP_MODIFY, + n_flag_clear ? "true" : "false"); + + return nb_cli_apply_changes( + vty, "./segment-routing/prefix-sid-map/prefix-sid[prefix='%s']", + prefix_str); +} + +DEFPY_YANG (no_isis_sr_prefix_sid, + no_isis_sr_prefix_sid_cmd, + "no segment-routing prefix $prefix\ + [ []]\ + [n-flag-clear]", + NO_STR + SR_STR + "Prefix SID\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n" + "Specify the absolute value of Prefix Segment ID\n" + "The Prefix Segment ID value\n" + "Specify the index of Prefix Segment ID\n" + "The Prefix Segment ID index\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n" + "Not a node SID\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes( + vty, "./segment-routing/prefix-sid-map/prefix-sid[prefix='%s']", + prefix_str); +} + +void cli_show_isis_prefix_sid(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *prefix; + const char *lh_behavior; + const char *sid_value_type; + const char *sid_value; + bool n_flag_clear; + + prefix = yang_dnode_get_string(dnode, "prefix"); + lh_behavior = yang_dnode_get_string(dnode, "last-hop-behavior"); + sid_value_type = yang_dnode_get_string(dnode, "sid-value-type"); + sid_value = yang_dnode_get_string(dnode, "sid-value"); + n_flag_clear = yang_dnode_get_bool(dnode, "n-flag-clear"); + + vty_out(vty, " segment-routing prefix %s", prefix); + if (strmatch(sid_value_type, "absolute")) + vty_out(vty, " absolute"); + else + vty_out(vty, " index"); + vty_out(vty, " %s", sid_value); + if (strmatch(lh_behavior, "no-php")) + vty_out(vty, " no-php-flag"); + else if (strmatch(lh_behavior, "explicit-null")) + vty_out(vty, " explicit-null"); + if (n_flag_clear) + vty_out(vty, " n-flag-clear"); + vty_out(vty, "\n"); +} + +#ifndef FABRICD +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid + */ +DEFPY_YANG( + isis_sr_prefix_sid_algorithm, isis_sr_prefix_sid_algorithm_cmd, + "segment-routing prefix $prefix\ + algorithm (128-255)$algorithm\ + \ + [$lh_behavior] [n-flag-clear$n_flag_clear]", + SR_STR + "Prefix SID\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n" + "Algorithm Specific Prefix SID Configuration\n" + "Algorithm number\n" + "Specify the absolute value of Prefix Segment ID\n" + "The Prefix Segment ID value\n" + "Specify the index of Prefix Segment ID\n" + "The Prefix Segment ID index\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n" + "Not a node SID\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./sid-value-type", NB_OP_MODIFY, sid_type); + nb_cli_enqueue_change(vty, "./sid-value", NB_OP_MODIFY, sid_value_str); + if (lh_behavior) { + const char *value; + + if (strmatch(lh_behavior, "no-php-flag")) + value = "no-php"; + else if (strmatch(lh_behavior, "explicit-null")) + value = "explicit-null"; + else + value = "php"; + + nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY, + value); + } else + nb_cli_enqueue_change(vty, "./last-hop-behavior", NB_OP_MODIFY, + NULL); + nb_cli_enqueue_change(vty, "./n-flag-clear", NB_OP_MODIFY, + n_flag_clear ? "true" : "false"); + + return nb_cli_apply_changes( + vty, + "./segment-routing/algorithm-prefix-sids/algorithm-prefix-sid[prefix='%s'][algo='%s']", + prefix_str, algorithm_str); +} + +DEFPY_YANG( + no_isis_sr_prefix_algorithm_sid, no_isis_sr_prefix_sid_algorithm_cmd, + "no segment-routing prefix $prefix\ + algorithm (128-255)$algorithm\ + [ []]\ + [n-flag-clear]", + NO_STR SR_STR + "Prefix SID\n" + "IPv4 Prefix\n" + "IPv6 Prefix\n" + "Algorithm Specific Prefix SID Configuration\n" + "Algorithm number\n" + "Specify the absolute value of Prefix Segment ID\n" + "The Prefix Segment ID value\n" + "Specify the index of Prefix Segment ID\n" + "The Prefix Segment ID index\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n" + "Not a node SID\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes( + vty, + "./segment-routing/algorithm-prefix-sids/algorithm-prefix-sid[prefix='%s'][algo='%s']", + prefix_str, algorithm_str); + return CMD_SUCCESS; +} +#endif /* ifndef FABRICD */ + +void cli_show_isis_prefix_sid_algorithm(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *prefix; + const char *lh_behavior; + const char *sid_value_type; + const char *sid_value; + bool n_flag_clear; + uint32_t algorithm; + + prefix = yang_dnode_get_string(dnode, "prefix"); + sid_value_type = yang_dnode_get_string(dnode, "sid-value-type"); + sid_value = yang_dnode_get_string(dnode, "sid-value"); + algorithm = yang_dnode_get_uint32(dnode, "algo"); + lh_behavior = yang_dnode_get_string(dnode, "last-hop-behavior"); + n_flag_clear = yang_dnode_get_bool(dnode, "n-flag-clear"); + + vty_out(vty, " segment-routing prefix %s", prefix); + vty_out(vty, " algorithm %u", algorithm); + if (strmatch(sid_value_type, "absolute")) + vty_out(vty, " absolute"); + else + vty_out(vty, " index"); + vty_out(vty, " %s", sid_value); + + if (strmatch(lh_behavior, "no-php")) + vty_out(vty, " no-php-flag"); + else if (strmatch(lh_behavior, "explicit-null")) + vty_out(vty, " explicit-null"); + if (n_flag_clear) + vty_out(vty, " n-flag-clear"); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/locator + */ +DEFPY (isis_srv6_locator, + isis_srv6_locator_cmd, + "[no] locator NAME$loc_name", + NO_STR + "Specify SRv6 locator\n" + "Specify SRv6 locator\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./locator", NB_OP_DESTROY, loc_name); + else + nb_cli_enqueue_change(vty, "./locator", NB_OP_MODIFY, loc_name); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_srv6_locator(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " locator %s\n", yang_dnode_get_string(dnode, NULL)); +} + +void cli_show_isis_srv6_locator_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/enabled + */ +DEFPY_YANG_NOSH (isis_srv6_enable, + isis_srv6_enable_cmd, + "segment-routing srv6", + SR_STR + "Enable Segment Routing over IPv6 (SRv6)\n") +{ + int ret; + char xpath[XPATH_MAXLEN + 37]; + + snprintf(xpath, sizeof(xpath), "%s/segment-routing-srv6", + VTY_CURR_XPATH); + + nb_cli_enqueue_change(vty, "./segment-routing-srv6/enabled", + NB_OP_MODIFY, "true"); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(ISIS_SRV6_NODE, xpath); + + return ret; +} + +DEFPY_YANG (no_isis_srv6_enable, + no_isis_srv6_enable_cmd, + "no segment-routing srv6", + NO_STR + SR_STR + "Disable Segment Routing over IPv6 (SRv6)\n") +{ + nb_cli_enqueue_change(vty, "./segment-routing-srv6", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_srv6_enabled(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " segment-routing srv6\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd + */ +DEFPY_YANG_NOSH (isis_srv6_node_msd, + isis_srv6_node_msd_cmd, + "[no] node-msd", + NO_STR + "Segment Routing over IPv6 (SRv6) Maximum SRv6 SID Depths\n") +{ + int ret = CMD_SUCCESS; + char xpath[XPATH_MAXLEN + 37]; + + snprintf(xpath, sizeof(xpath), "%s/msd/node-msd", VTY_CURR_XPATH); + + if (no) { + nb_cli_enqueue_change(vty, "./msd/node_msd/max-segs-left", + NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./msd/node_msd/end-pop", + NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./msd/node_msd/h-encaps", + NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./msd/node_msd/end-d", + NB_OP_DESTROY, NULL); + ret = nb_cli_apply_changes(vty, NULL); + } else + VTY_PUSH_XPATH(ISIS_SRV6_NODE_MSD_NODE, xpath); + + return ret; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left + */ +DEFPY (isis_srv6_node_msd_max_segs_left, + isis_srv6_node_msd_max_segs_left_cmd, + "[no] max-segs-left (0-255)$max_segs_left", + NO_STR + "Specify Maximum Segments Left MSD\n" + "Specify Maximum Segments Left MSD\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./max-segs-left", NB_OP_DESTROY, + NULL); + else + nb_cli_enqueue_change(vty, "./max-segs-left", NB_OP_MODIFY, + max_segs_left_str); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop + */ +DEFPY (isis_srv6_node_msd_max_end_pop, + isis_srv6_node_msd_max_end_pop_cmd, + "[no] max-end-pop (0-255)$max_end_pop", + NO_STR + "Specify Maximum End Pop MSD\n" + "Specify Maximum End Pop MSD\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./max-end-pop", NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./max-end-pop", NB_OP_MODIFY, + max_end_pop_str); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps + */ +DEFPY (isis_srv6_node_msd_max_h_encaps, + isis_srv6_node_msd_max_h_encaps_cmd, + "[no] max-h-encaps (0-255)$max_h_encaps", + NO_STR + "Specify Maximum H.Encaps MSD\n" + "Specify Maximum H.Encaps MSD\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./max-h-encaps", NB_OP_DESTROY, + NULL); + else + nb_cli_enqueue_change(vty, "./max-h-encaps", NB_OP_MODIFY, + max_h_encaps_str); + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d + */ +DEFPY (isis_srv6_node_msd_max_end_d, + isis_srv6_node_msd_max_end_d_cmd, + "[no] max-end-d (0-255)$max_end_d", + NO_STR + "Specify Maximum End D MSD\n" + "Specify Maximum End D MSD\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./max-end-d", NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./max-end-d", NB_OP_MODIFY, + max_end_d_str); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_srv6_node_msd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " node-msd\n"); + if (yang_dnode_get_uint8(dnode, "max-segs-left") != + yang_get_default_uint8("%s/msd/node-msd/max-segs-left", ISIS_SRV6)) + vty_out(vty, " max-segs-left %u\n", + yang_dnode_get_uint8(dnode, "max-segs-left")); + if (yang_dnode_get_uint8(dnode, "max-end-pop") != + yang_get_default_uint8("%s/msd/node-msd/max-end-pop", ISIS_SRV6)) + vty_out(vty, " max-end-pop %u\n", + yang_dnode_get_uint8(dnode, "max-end-pop")); + if (yang_dnode_get_uint8(dnode, "max-h-encaps") != + yang_get_default_uint8("%s/msd/node-msd/max-h-encaps", ISIS_SRV6)) + vty_out(vty, " max-h-encaps %u\n", + yang_dnode_get_uint8(dnode, "max-h-encaps")); + if (yang_dnode_get_uint8(dnode, "max-end-d") != + yang_get_default_uint8("%s/msd/node-msd/max-end-d", ISIS_SRV6)) + vty_out(vty, " max-end-d %u\n", + yang_dnode_get_uint8(dnode, "max-end-d")); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/interface + */ +DEFPY (isis_srv6_interface, + isis_srv6_interface_cmd, + "[no] interface WORD$interface", + NO_STR + "Interface for Segment Routing over IPv6 (SRv6)\n" + "Interface for Segment Routing over IPv6 (SRv6)\n") +{ + if (no) { + nb_cli_enqueue_change(vty, "./interface", + NB_OP_MODIFY, NULL); + } else { + nb_cli_enqueue_change(vty, "./interface", + NB_OP_MODIFY, interface); + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_srv6_interface(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " interface %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/priority-limit + */ +DEFPY_YANG (isis_frr_lfa_priority_limit, + isis_frr_lfa_priority_limit_cmd, + "[no] fast-reroute priority-limit $priority [$level]", + NO_STR + "Configure Fast ReRoute\n" + "Limit backup computation up to the prefix priority\n" + "Compute for critical priority prefixes only\n" + "Compute for critical & high priority prefixes\n" + "Compute for critical, high & medium priority prefixes\n" + "Set priority-limit for level-1 only\n" + "Set priority-limit for level-2 only\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./fast-reroute/level-1/lfa/priority-limit", + NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change( + vty, + "./fast-reroute/level-1/lfa/priority-limit", + NB_OP_CREATE, priority); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./fast-reroute/level-2/lfa/priority-limit", + NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change( + vty, + "./fast-reroute/level-2/lfa/priority-limit", + NB_OP_CREATE, priority); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_frr_lfa_priority_limit(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " fast-reroute priority-limit %s %s\n", + yang_dnode_get_string(dnode, NULL), + dnode->parent->parent->schema->name); +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/tiebreaker + */ +DEFPY_YANG (isis_frr_lfa_tiebreaker, + isis_frr_lfa_tiebreaker_cmd, + "[no] fast-reroute lfa\ + tiebreaker $type\ + index (1-255)$index\ + [$level]", + NO_STR + "Configure Fast ReRoute\n" + "LFA configuration\n" + "Configure tiebreaker for multiple backups\n" + "Prefer backup path via downstream node\n" + "Prefer backup path with lowest total metric\n" + "Prefer node protecting backup path\n" + "Set preference order among tiebreakers\n" + "Index\n" + "Configure tiebreaker for level-1 only\n" + "Configure tiebreaker for level-2 only\n") +{ + char xpath[XPATH_MAXLEN]; + + if (!level || strmatch(level, "level-1")) { + if (no) { + snprintf( + xpath, XPATH_MAXLEN, + "./fast-reroute/level-1/lfa/tiebreaker[index='%s']", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } else { + snprintf( + xpath, XPATH_MAXLEN, + "./fast-reroute/level-1/lfa/tiebreaker[index='%s']/type", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, type); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + snprintf( + xpath, XPATH_MAXLEN, + "./fast-reroute/level-2/lfa/tiebreaker[index='%s']", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } else { + snprintf( + xpath, XPATH_MAXLEN, + "./fast-reroute/level-2/lfa/tiebreaker[index='%s']/type", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, type); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_frr_lfa_tiebreaker(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " fast-reroute lfa tiebreaker %s index %s %s\n", + yang_dnode_get_string(dnode, "type"), + yang_dnode_get_string(dnode, "index"), + dnode->parent->parent->schema->name); +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/lfa/load-sharing + */ +DEFPY_YANG (isis_frr_lfa_load_sharing, + isis_frr_lfa_load_sharing_cmd, + "[no] fast-reroute load-sharing disable [$level]", + NO_STR + "Configure Fast ReRoute\n" + "Load share prefixes across multiple backups\n" + "Disable load sharing\n" + "Disable load sharing for level-1 only\n" + "Disable load sharing for level-2 only\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, "./fast-reroute/level-1/lfa/load-sharing", + NB_OP_MODIFY, "true"); + } else { + nb_cli_enqueue_change( + vty, "./fast-reroute/level-1/lfa/load-sharing", + NB_OP_MODIFY, "false"); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, "./fast-reroute/level-2/lfa/load-sharing", + NB_OP_MODIFY, "true"); + } else { + nb_cli_enqueue_change( + vty, "./fast-reroute/level-2/lfa/load-sharing", + NB_OP_MODIFY, "false"); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_frr_lfa_load_sharing(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " fast-reroute load-sharing disable %s\n", + dnode->parent->parent->schema->name); +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-{1,2}/remote-lfa/prefix-list + */ +DEFPY_YANG (isis_frr_remote_lfa_plist, + isis_frr_remote_lfa_plist_cmd, + "fast-reroute remote-lfa prefix-list WORD$plist [$level]", + "Configure Fast ReRoute\n" + "Enable remote LFA related configuration\n" + "Filter PQ node router ID based on prefix list\n" + "Prefix-list name\n" + "Enable router ID filtering for level-1 only\n" + "Enable router ID filtering for level-2 only\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./fast-reroute/level-1/remote-lfa/prefix-list", + NB_OP_MODIFY, plist); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./fast-reroute/level-2/remote-lfa/prefix-list", + NB_OP_MODIFY, plist); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_isis_frr_remote_lfa_plist, + no_isis_frr_remote_lfa_plist_cmd, + "no fast-reroute remote-lfa prefix-list [WORD] [$level]", + NO_STR + "Configure Fast ReRoute\n" + "Enable remote LFA related configuration\n" + "Filter PQ node router ID based on prefix list\n" + "Prefix-list name\n" + "Enable router ID filtering for level-1 only\n" + "Enable router ID filtering for level-2 only\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./fast-reroute/level-1/remote-lfa/prefix-list", + NB_OP_DESTROY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./fast-reroute/level-2/remote-lfa/prefix-list", + NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_frr_remote_lfa_plist(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " fast-reroute remote-lfa prefix-list %s %s\n", + yang_dnode_get_string(dnode, NULL), + dnode->parent->parent->schema->name); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive + */ +DEFPY_YANG(isis_passive, isis_passive_cmd, "[no] isis passive", + NO_STR + "IS-IS routing protocol\n" + "Configure the passive mode for interface\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis/passive", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_passive(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis passive\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/password + */ + +DEFPY_YANG(isis_passwd, isis_passwd_cmd, "isis password $type WORD$pwd", + "IS-IS routing protocol\n" + "Configure the authentication password for a circuit\n" + "HMAC-MD5 authentication\n" + "Cleartext password\n" + "Circuit password\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_CREATE, + NULL); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password", + NB_OP_MODIFY, pwd); + nb_cli_enqueue_change(vty, "./frr-isisd:isis/password/password-type", + NB_OP_MODIFY, type); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_passwd, no_isis_passwd_cmd, "no isis password [ WORD]", + NO_STR + "IS-IS routing protocol\n" + "Configure the authentication password for a circuit\n" + "HMAC-MD5 authentication\n" + "Cleartext password\n" + "Circuit password\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis/password", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_password(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " isis password %s %s\n", + yang_dnode_get_string(dnode, "password-type"), + yang_dnode_get_string(dnode, "password")); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric + */ +DEFPY_YANG(isis_metric, isis_metric_cmd, + "isis metric [level-1|level-2]$level (0-16777215)$met", + "IS-IS routing protocol\n" + "Set default metric for circuit\n" + "Specify metric for level-1 routing\n" + "Specify metric for level-2 routing\n" + "Default metric value\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1", + NB_OP_MODIFY, met_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2", + NB_OP_MODIFY, met_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_metric, no_isis_metric_cmd, + "no isis metric [level-1|level-2]$level [(0-16777215)]", + NO_STR + "IS-IS routing protocol\n" + "Set default metric for circuit\n" + "Specify metric for level-1 routing\n" + "Specify metric for level-2 routing\n" + "Default metric value\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/metric/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_metric(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis metric %s\n", l1); + else { + vty_out(vty, " isis metric level-1 %s\n", l1); + vty_out(vty, " isis metric level-2 %s\n", l2); + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval + */ +DEFPY_YANG(isis_hello_interval, isis_hello_interval_cmd, + "isis hello-interval [level-1|level-2]$level (1-600)$intv", + "IS-IS routing protocol\n" + "Set Hello interval\n" + "Specify hello-interval for level-1 IIHs\n" + "Specify hello-interval for level-2 IIHs\n" + "Holdtime 1 seconds, interval depends on multiplier\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/hello/interval/level-1", + NB_OP_MODIFY, intv_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/hello/interval/level-2", + NB_OP_MODIFY, intv_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_hello_interval, no_isis_hello_interval_cmd, + "no isis hello-interval [level-1|level-2]$level [(1-600)]", + NO_STR + "IS-IS routing protocol\n" + "Set Hello interval\n" + "Specify hello-interval for level-1 IIHs\n" + "Specify hello-interval for level-2 IIHs\n" + "Holdtime 1 second, interval depends on multiplier\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/hello/interval/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/hello/interval/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_hello_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis hello-interval %s\n", l1); + else { + vty_out(vty, " isis hello-interval level-1 %s\n", l1); + vty_out(vty, " isis hello-interval level-2 %s\n", l2); + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier + */ +DEFPY_YANG(isis_hello_multiplier, isis_hello_multiplier_cmd, + "isis hello-multiplier [level-1|level-2]$level (2-100)$mult", + "IS-IS routing protocol\n" + "Set multiplier for Hello holding time\n" + "Specify hello multiplier for level-1 IIHs\n" + "Specify hello multiplier for level-2 IIHs\n" + "Hello multiplier value\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./frr-isisd:isis/hello/multiplier/level-1", + NB_OP_MODIFY, mult_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./frr-isisd:isis/hello/multiplier/level-2", + NB_OP_MODIFY, mult_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_hello_multiplier, no_isis_hello_multiplier_cmd, + "no isis hello-multiplier [level-1|level-2]$level [(2-100)]", + NO_STR + "IS-IS routing protocol\n" + "Set multiplier for Hello holding time\n" + "Specify hello multiplier for level-1 IIHs\n" + "Specify hello multiplier for level-2 IIHs\n" + "Hello multiplier value\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change( + vty, "./frr-isisd:isis/hello/multiplier/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change( + vty, "./frr-isisd:isis/hello/multiplier/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_hello_multi(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis hello-multiplier %s\n", l1); + else { + vty_out(vty, " isis hello-multiplier level-1 %s\n", l1); + vty_out(vty, " isis hello-multiplier level-2 %s\n", l2); + } +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake + */ +DEFPY_YANG(isis_threeway_adj, isis_threeway_adj_cmd, "[no] isis three-way-handshake", + NO_STR + "IS-IS commands\n" + "Enable/Disable three-way handshake\n") +{ + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/disable-three-way-handshake", + NB_OP_MODIFY, no ? "true" : "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_threeway_shake(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis three-way-handshake\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding + */ +DEFPY_YANG(isis_hello_padding, isis_hello_padding_cmd, + "[no] isis hello padding [during-adjacency-formation]$padding_type", + NO_STR + "IS-IS routing protocol\n" + "Type of padding for IS-IS hello packets\n" + "Pad hello packets\n" + "Add padding to hello packets during adjacency formation only.\n") +{ + if (no) { + nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/padding", + NB_OP_MODIFY, "disabled"); + } else { + nb_cli_enqueue_change(vty, "./frr-isisd:isis/hello/padding", + NB_OP_MODIFY, + padding_type ? padding_type : "always"); + } + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_hello_padding(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + int hello_padding_type = yang_dnode_get_enum(dnode, NULL); + if (hello_padding_type == ISIS_HELLO_PADDING_DISABLED) + vty_out(vty, " no"); + vty_out(vty, " isis hello padding"); + if (hello_padding_type == ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION) + vty_out(vty, " during-adjacency-formation"); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval + */ +DEFPY_YANG(csnp_interval, csnp_interval_cmd, + "isis csnp-interval (1-600)$intv [level-1|level-2]$level", + "IS-IS routing protocol\n" + "Set CSNP interval in seconds\n" + "CSNP interval value\n" + "Specify interval for level-1 CSNPs\n" + "Specify interval for level-2 CSNPs\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/csnp-interval/level-1", + NB_OP_MODIFY, intv_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/csnp-interval/level-2", + NB_OP_MODIFY, intv_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_csnp_interval, no_csnp_interval_cmd, + "no isis csnp-interval [(1-600)] [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Set CSNP interval in seconds\n" + "CSNP interval value\n" + "Specify interval for level-1 CSNPs\n" + "Specify interval for level-2 CSNPs\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/csnp-interval/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/csnp-interval/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_csnp_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis csnp-interval %s\n", l1); + else { + vty_out(vty, " isis csnp-interval %s level-1\n", l1); + vty_out(vty, " isis csnp-interval %s level-2\n", l2); + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval + */ +DEFPY_YANG(psnp_interval, psnp_interval_cmd, + "isis psnp-interval (1-120)$intv [level-1|level-2]$level", + "IS-IS routing protocol\n" + "Set PSNP interval in seconds\n" + "PSNP interval value\n" + "Specify interval for level-1 PSNPs\n" + "Specify interval for level-2 PSNPs\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/psnp-interval/level-1", + NB_OP_MODIFY, intv_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/psnp-interval/level-2", + NB_OP_MODIFY, intv_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_psnp_interval, no_psnp_interval_cmd, + "no isis psnp-interval [(1-120)] [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Set PSNP interval in seconds\n" + "PSNP interval value\n" + "Specify interval for level-1 PSNPs\n" + "Specify interval for level-2 PSNPs\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/psnp-interval/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, + "./frr-isisd:isis/psnp-interval/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_psnp_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis psnp-interval %s\n", l1); + else { + vty_out(vty, " isis psnp-interval %s level-1\n", l1); + vty_out(vty, " isis psnp-interval %s level-2\n", l2); + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology + */ +DEFPY_YANG(circuit_topology, circuit_topology_cmd, + "[no] isis topology$topology", + NO_STR + "IS-IS routing protocol\n" + "Configure interface IS-IS topologies\n" + "Standard topology\n" + "IPv4 unicast topology\n" + "IPv4 management topology\n" + "IPv6 unicast topology\n" + "IPv4 multicast topology\n" + "IPv6 multicast topology\n" + "IPv6 management topology\n" + "IPv6 dst-src topology\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_MODIFY, no ? "false" : "true"); + + if (strmatch(topology, "ipv4-mgmt")) + return nb_cli_apply_changes( + vty, "./frr-isisd:isis/multi-topology/ipv4-management"); + else if (strmatch(topology, "ipv6-mgmt")) + return nb_cli_apply_changes( + vty, "./frr-isisd:isis/multi-topology/ipv6-management"); + if (strmatch(topology, "ipv4-unicast")) + return nb_cli_apply_changes( + vty, "./frr-isisd:isis/multi-topology/standard"); + else + return nb_cli_apply_changes( + vty, "./frr-isisd:isis/multi-topology/%s", topology); +} + +void cli_show_ip_isis_mt_standard(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology standard\n"); +} + +void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv4-multicast\n"); +} + +void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv4-mgmt\n"); +} + +void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv6-unicast\n"); +} + +void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv6-multicast\n"); +} + +void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv6-mgmt\n"); +} + +void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " isis topology ipv6-dstsrc\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type + */ +DEFPY_YANG(isis_circuit_type, isis_circuit_type_cmd, + "isis circuit-type $type", + "IS-IS routing protocol\n" + "Configure circuit type for interface\n" + "Level-1 only adjacencies are formed\n" + "Level-1-2 adjacencies are formed\n" + "Level-2 only adjacencies are formed\n") +{ + nb_cli_enqueue_change( + vty, "./frr-isisd:isis/circuit-type", NB_OP_MODIFY, + strmatch(type, "level-2-only") ? "level-2" : type); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_circuit_type, no_isis_circuit_type_cmd, + "no isis circuit-type [level-1|level-1-2|level-2-only]", + NO_STR + "IS-IS routing protocol\n" + "Configure circuit type for interface\n" + "Level-1 only adjacencies are formed\n" + "Level-1-2 adjacencies are formed\n" + "Level-2 only adjacencies are formed\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis/circuit-type", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_circ_type(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int level = yang_dnode_get_enum(dnode, NULL); + + switch (level) { + case IS_LEVEL_1: + vty_out(vty, " isis circuit-type level-1\n"); + break; + case IS_LEVEL_2: + vty_out(vty, " isis circuit-type level-2-only\n"); + break; + case IS_LEVEL_1_AND_2: + vty_out(vty, " isis circuit-type level-1-2\n"); + break; + } +} + +static int ag_change(struct vty *vty, int argc, struct cmd_token **argv, + const char *xpath_base, bool no, int start_idx) +{ + char xpath[XPATH_MAXLEN]; + + for (int i = start_idx; i < argc; i++) { + snprintf(xpath, XPATH_MAXLEN, "%s[.='%s']", xpath_base, + argv[i]->arg); + nb_cli_enqueue_change(vty, xpath, + no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); + } + return nb_cli_apply_changes(vty, NULL); +} + +static int ag_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = (struct vty *)arg; + + vty_out(vty, " %s", yang_dnode_get_string(dnode, ".")); + return YANG_ITER_CONTINUE; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type + */ +DEFPY_YANG(isis_network, isis_network_cmd, "[no] isis network point-to-point", + NO_STR + "IS-IS routing protocol\n" + "Set network type\n" + "point-to-point network type\n") +{ + nb_cli_enqueue_change(vty, "./frr-isisd:isis/network-type", + NB_OP_MODIFY, + no ? "broadcast" : "point-to-point"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_network_type(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_get_enum(dnode, NULL) != CIRCUIT_T_P2P) + vty_out(vty, " no"); + + vty_out(vty, " isis network point-to-point\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority + */ +DEFPY_YANG(isis_priority, isis_priority_cmd, + "isis priority (0-127)$prio [level-1|level-2]$level", + "IS-IS routing protocol\n" + "Set priority for Designated Router election\n" + "Priority value\n" + "Specify priority for level-1 routing\n" + "Specify priority for level-2 routing\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1", + NB_OP_MODIFY, prio_str); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2", + NB_OP_MODIFY, prio_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_isis_priority, no_isis_priority_cmd, + "no isis priority [(0-127)] [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Set priority for Designated Router election\n" + "Priority value\n" + "Specify priority for level-1 routing\n" + "Specify priority for level-2 routing\n") +{ + if (!level || strmatch(level, "level-1")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-1", + NB_OP_MODIFY, NULL); + if (!level || strmatch(level, "level-2")) + nb_cli_enqueue_change(vty, "./frr-isisd:isis/priority/level-2", + NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ip_isis_priority(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *l1 = yang_dnode_get_string(dnode, "level-1"); + const char *l2 = yang_dnode_get_string(dnode, "level-2"); + + if (strmatch(l1, l2)) + vty_out(vty, " isis priority %s\n", l1); + else { + vty_out(vty, " isis priority %s level-1\n", l1); + vty_out(vty, " isis priority %s level-2\n", l2); + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute + */ +void cli_show_ip_isis_frr(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + bool l1_enabled, l2_enabled; + bool l1_node_protection, l2_node_protection; + bool l1_link_fallback, l2_link_fallback; + + /* Classic LFA */ + l1_enabled = yang_dnode_get_bool(dnode, "level-1/lfa/enable"); + l2_enabled = yang_dnode_get_bool(dnode, "level-2/lfa/enable"); + + if (l1_enabled || l2_enabled) { + if (l1_enabled == l2_enabled) { + vty_out(vty, " isis fast-reroute lfa\n"); + vty_out(vty, "\n"); + } else { + if (l1_enabled) + vty_out(vty, + " isis fast-reroute lfa level-1\n"); + if (l2_enabled) + vty_out(vty, + " isis fast-reroute lfa level-2\n"); + } + } + + /* Remote LFA */ + l1_enabled = yang_dnode_get_bool(dnode, "level-1/remote-lfa/enable"); + l2_enabled = yang_dnode_get_bool(dnode, "level-2/remote-lfa/enable"); + + if (l1_enabled || l2_enabled) { + if (l1_enabled == l2_enabled) { + vty_out(vty, + " isis fast-reroute remote-lfa tunnel mpls-ldp\n"); + vty_out(vty, "\n"); + } else { + if (l1_enabled) + vty_out(vty, + " isis fast-reroute remote-lfa tunnel mpls-ldp level-1\n"); + if (l2_enabled) + vty_out(vty, + " isis fast-reroute remote-lfa tunnel mpls-ldp level-2\n"); + } + } + + /* TI-LFA */ + l1_enabled = yang_dnode_get_bool(dnode, "level-1/ti-lfa/enable"); + l2_enabled = yang_dnode_get_bool(dnode, "level-2/ti-lfa/enable"); + l1_node_protection = + yang_dnode_get_bool(dnode, "level-1/ti-lfa/node-protection"); + l2_node_protection = + yang_dnode_get_bool(dnode, "level-2/ti-lfa/node-protection"); + l1_link_fallback = + yang_dnode_get_bool(dnode, "level-1/ti-lfa/link-fallback"); + l2_link_fallback = + yang_dnode_get_bool(dnode, "level-2/ti-lfa/link-fallback"); + + + if (l1_enabled || l2_enabled) { + if (l1_enabled == l2_enabled + && l1_node_protection == l2_node_protection + && l1_link_fallback == l2_link_fallback) { + vty_out(vty, " isis fast-reroute ti-lfa"); + if (l1_node_protection) + vty_out(vty, " node-protection"); + if (l1_link_fallback) + vty_out(vty, " link-fallback"); + vty_out(vty, "\n"); + } else { + if (l1_enabled) { + vty_out(vty, + " isis fast-reroute ti-lfa level-1"); + if (l1_node_protection) + vty_out(vty, " node-protection"); + if (l1_link_fallback) + vty_out(vty, " link-fallback"); + vty_out(vty, "\n"); + } + if (l2_enabled) { + vty_out(vty, + " isis fast-reroute ti-lfa level-2"); + if (l2_node_protection) + vty_out(vty, " node-protection"); + if (l2_link_fallback) + vty_out(vty, " link-fallback"); + vty_out(vty, "\n"); + } + } + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/lfa/enable + */ +DEFPY(isis_lfa, isis_lfa_cmd, + "[no] isis fast-reroute lfa [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Interface IP Fast-reroute configuration\n" + "Enable LFA computation\n" + "Enable LFA computation for Level 1 only\n" + "Enable LFA computation for Level 2 only\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/lfa/enable", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/lfa/enable", + NB_OP_MODIFY, "true"); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/lfa/enable", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/lfa/enable", + NB_OP_MODIFY, "true"); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/lfa/exclude-interface + */ +DEFPY(isis_lfa_exclude_interface, isis_lfa_exclude_interface_cmd, + "[no] isis fast-reroute lfa [level-1|level-2]$level exclude interface IFNAME$ifname", + NO_STR + "IS-IS routing protocol\n" + "Interface IP Fast-reroute configuration\n" + "Enable LFA computation\n" + "Enable LFA computation for Level 1 only\n" + "Enable LFA computation for Level 2 only\n" + "FRR exclusion information\n" + "Exclude an interface from computation\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + if (!level || strmatch(level, "level-1")) { + snprintf(xpath, sizeof(xpath), + "./frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface[.='%s']", + ifname); + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + } + if (!level || strmatch(level, "level-2")) { + snprintf(xpath, sizeof(xpath), + "./frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface[.='%s']", + ifname); + + if (no) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_frr_lfa_exclude_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " isis fast-reroute lfa %s exclude interface %s\n", + dnode->parent->parent->schema->name, + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/remote-lfa/enable + */ +DEFPY(isis_remote_lfa, isis_remote_lfa_cmd, + "[no] isis fast-reroute remote-lfa tunnel mpls-ldp [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Interface IP Fast-reroute configuration\n" + "Enable remote LFA computation\n" + "Enable remote LFA computation using tunnels\n" + "Use MPLS LDP tunnel to reach the remote LFA node\n" + "Enable LFA computation for Level 1 only\n" + "Enable LFA computation for Level 2 only\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable", + NB_OP_MODIFY, "true"); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable", + NB_OP_MODIFY, "true"); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/remote-lfa/maximum-metric + */ +DEFPY(isis_remote_lfa_max_metric, isis_remote_lfa_max_metric_cmd, + "[no] isis fast-reroute remote-lfa maximum-metric (1-16777215)$metric [level-1|level-2]$level", + NO_STR + "IS-IS routing protocol\n" + "Interface IP Fast-reroute configuration\n" + "Enable remote LFA computation\n" + "Limit remote LFA node selection within the metric\n" + "Value of the metric\n" + "Enable LFA computation for Level 1 only\n" + "Enable LFA computation for Level 2 only\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric", + NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric", + NB_OP_MODIFY, metric_str); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric", + NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric", + NB_OP_MODIFY, metric_str); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_frr_remote_lfa_max_metric(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " isis fast-reroute remote-lfa maximum-metric %s %s\n", + yang_dnode_get_string(dnode, NULL), + dnode->parent->parent->schema->name); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-{1,2}/ti-lfa/enable + */ +DEFPY(isis_ti_lfa, isis_ti_lfa_cmd, + "[no] isis fast-reroute ti-lfa [level-1|level-2]$level [node-protection$node_protection [link-fallback$link_fallback]]", + NO_STR + "IS-IS routing protocol\n" + "Interface IP Fast-reroute configuration\n" + "Enable TI-LFA computation\n" + "Enable TI-LFA computation for Level 1 only\n" + "Enable TI-LFA computation for Level 2 only\n" + "Protect against node failures\n" + "Enable link-protection fallback\n") +{ + if (!level || strmatch(level, "level-1")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable", + NB_OP_MODIFY, "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection", + NB_OP_MODIFY, "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable", + NB_OP_MODIFY, "true"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection", + NB_OP_MODIFY, + node_protection ? "true" : "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback", + NB_OP_MODIFY, link_fallback ? "true" : "false"); + } + } + if (!level || strmatch(level, "level-2")) { + if (no) { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable", + NB_OP_MODIFY, "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection", + NB_OP_MODIFY, "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback", + NB_OP_MODIFY, "false"); + } else { + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable", + NB_OP_MODIFY, "true"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection", + NB_OP_MODIFY, + node_protection ? "true" : "false"); + nb_cli_enqueue_change( + vty, + "./frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback", + NB_OP_MODIFY, link_fallback ? "true" : "false"); + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-isisd:isis/instance/log-adjacency-changes + */ +DEFPY_YANG(log_adj_changes, log_adj_changes_cmd, "[no] log-adjacency-changes", + NO_STR "Log changes in adjacency state\n") +{ + nb_cli_enqueue_change(vty, "./log-adjacency-changes", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_log_adjacency(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " log-adjacency-changes\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/log-pdu-drops + */ +DEFPY_YANG(log_pdu_drops, log_pdu_drops_cmd, "[no] log-pdu-drops", + NO_STR "Log any dropped PDUs\n") +{ + nb_cli_enqueue_change(vty, "./log-pdu-drops", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_log_pdu_drops(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " log-pdu-drops\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/mpls/ldp-sync + */ +DEFPY(isis_mpls_ldp_sync, isis_mpls_ldp_sync_cmd, "mpls ldp-sync", + MPLS_STR MPLS_LDP_SYNC_STR) +{ + nb_cli_enqueue_change(vty, "./mpls/ldp-sync", NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_isis_mpls_ldp_sync, no_isis_mpls_ldp_sync_cmd, "no mpls ldp-sync", + NO_STR MPLS_STR NO_MPLS_LDP_SYNC_STR) +{ + nb_cli_enqueue_change(vty, "./mpls/ldp-sync", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_ldp_sync(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " mpls ldp-sync\n"); +} + +DEFPY(isis_mpls_ldp_sync_holddown, isis_mpls_ldp_sync_holddown_cmd, + "mpls ldp-sync holddown (0-10000)", + MPLS_STR MPLS_LDP_SYNC_STR + "Time to wait for LDP-SYNC to occur before restoring interface metric\n" + "Time in seconds\n") +{ + nb_cli_enqueue_change(vty, "./mpls/ldp-sync/holddown", NB_OP_MODIFY, + holddown_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_isis_mpls_ldp_sync_holddown, no_isis_mpls_ldp_sync_holddown_cmd, + "no mpls ldp-sync holddown [<(1-10000)>]", + NO_STR MPLS_STR MPLS_LDP_SYNC_STR NO_MPLS_LDP_SYNC_HOLDDOWN_STR "Time in seconds\n") +{ + nb_cli_enqueue_change(vty, "./mpls/ldp-sync/holddown", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_ldp_sync_holddown(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " mpls ldp-sync holddown %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync + */ +DEFPY(isis_mpls_if_ldp_sync, isis_mpls_if_ldp_sync_cmd, + "[no] isis mpls ldp-sync", + NO_STR "IS-IS routing protocol\n" MPLS_STR MPLS_LDP_SYNC_STR) +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (dnode == NULL) { + vty_out(vty, "ISIS is not enabled on this circuit\n"); + return CMD_SUCCESS; + } + + nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/ldp-sync", + NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + + +void cli_show_isis_mpls_if_ldp_sync(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " isis mpls ldp-sync\n"); +} + +DEFPY(isis_mpls_if_ldp_sync_holddown, isis_mpls_if_ldp_sync_holddown_cmd, + "isis mpls ldp-sync holddown (0-10000)", + "IS-IS routing protocol\n" MPLS_STR MPLS_LDP_SYNC_STR + "Time to wait for LDP-SYNC to occur before restoring interface metric\n" + "Time in seconds\n") +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (dnode == NULL) { + vty_out(vty, "ISIS is not enabled on this circuit\n"); + return CMD_SUCCESS; + } + + nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/holddown", + NB_OP_MODIFY, holddown_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_isis_mpls_if_ldp_sync_holddown, no_isis_mpls_if_ldp_sync_holddown_cmd, + "no isis mpls ldp-sync holddown [<(1-10000)>]", + NO_STR "IS-IS routing protocol\n" MPLS_STR NO_MPLS_LDP_SYNC_STR + NO_MPLS_LDP_SYNC_HOLDDOWN_STR "Time in seconds\n") +{ + const struct lyd_node *dnode; + + dnode = yang_dnode_getf(vty->candidate_config->dnode, + "%s/frr-isisd:isis", VTY_CURR_XPATH); + if (dnode == NULL) { + vty_out(vty, "ISIS is not enabled on this circuit\n"); + return CMD_SUCCESS; + } + + nb_cli_enqueue_change(vty, "./frr-isisd:isis/mpls/holddown", + NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_mpls_if_ldp_sync_holddown(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " isis mpls ldp-sync holddown %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG_NOSH(flex_algo, flex_algo_cmd, "flex-algo (128-255)$algorithm", + "Flexible Algorithm\n" + "Flexible Algorithm Number\n") +{ + int ret; + char xpath[XPATH_MAXLEN + 37]; + + snprintf(xpath, sizeof(xpath), + "%s/flex-algos/flex-algo[flex-algo='%ld']", VTY_CURR_XPATH, + algorithm); + + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes( + vty, "./flex-algos/flex-algo[flex-algo='%ld']", algorithm); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(ISIS_FLEX_ALGO_NODE, xpath); + + return ret; +} + +DEFPY_YANG(no_flex_algo, no_flex_algo_cmd, "no flex-algo (128-255)$algorithm", + NO_STR + "Flexible Algorithm\n" + "Flexible Algorithm Number\n") +{ + char xpath[XPATH_MAXLEN + 37]; + + snprintf(xpath, sizeof(xpath), + "%s/flex-algos/flex-algo[flex-algo='%ld']", VTY_CURR_XPATH, + algorithm); + + if (!yang_dnode_exists(vty->candidate_config->dnode, xpath)) { + vty_out(vty, "ISIS flex-algo %ld isn't exist.\n", algorithm); + return CMD_ERR_NO_MATCH; + } + + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes_clear_pending( + vty, "./flex-algos/flex-algo[flex-algo='%ld']", algorithm); +} + +DEFPY_YANG(advertise_definition, advertise_definition_cmd, + "[no] advertise-definition", + NO_STR "Advertise Local Flexible Algorithm\n") +{ + nb_cli_enqueue_change(vty, "./advertise-definition", + no ? NB_OP_DESTROY : NB_OP_CREATE, + no ? NULL : "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(affinity_include_any, affinity_include_any_cmd, + "[no] affinity include-any NAME...", + NO_STR + "Affinity configuration\n" + "Any Include with\n" + "Include NAME list\n") +{ + const char *xpath = "./affinity-include-anies/affinity-include-any"; + + return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2); +} + +DEFPY_YANG(affinity_include_all, affinity_include_all_cmd, + "[no] affinity include-all NAME...", + NO_STR + "Affinity configuration\n" + "All Include with\n" + "Include NAME list\n") +{ + const char *xpath = "./affinity-include-alls/affinity-include-all"; + + return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2); +} + +DEFPY_YANG(affinity_exclude_any, affinity_exclude_any_cmd, + "[no] affinity exclude-any NAME...", + NO_STR + "Affinity configuration\n" + "Any Exclude with\n" + "Exclude NAME list\n") +{ + const char *xpath = "./affinity-exclude-anies/affinity-exclude-any"; + + return ag_change(vty, argc, argv, xpath, no, no ? 3 : 2); +} + +DEFPY_YANG(prefix_metric, prefix_metric_cmd, "[no] prefix-metric", + NO_STR "Use Flex-Algo Prefix Metric\n") +{ + nb_cli_enqueue_change(vty, "./prefix-metric", + no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(dplane_sr_mpls, dplane_sr_mpls_cmd, "[no] dataplane sr-mpls", + NO_STR + "Advertise and participate in the specified Data-Planes\n" + "Advertise and participate in SR-MPLS data-plane\n") +{ + nb_cli_enqueue_change(vty, "./dplane-sr-mpls", + no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_HIDDEN(dplane_srv6, dplane_srv6_cmd, "[no] dataplane srv6", + NO_STR + "Advertise and participate in the specified Data-Planes\n" + "Advertise and participate in SRv6 data-plane\n") +{ + + nb_cli_enqueue_change(vty, "./dplane-srv6", + no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_HIDDEN(dplane_ip, dplane_ip_cmd, "[no] dataplane ip", + NO_STR + "Advertise and participate in the specified Data-Planes\n" + "Advertise and participate in IP data-plane\n") +{ + nb_cli_enqueue_change(vty, "./dplane-ip", + no ? NB_OP_DESTROY : NB_OP_CREATE, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(metric_type, metric_type_cmd, + "[no] metric-type [igp$igp|te$te|delay$delay]", + NO_STR + "Metric-type used by flex-algo calculation\n" + "Use IGP metric (default)\n" + "Use Delay as metric\n" + "Use Traffic Engineering metric\n") +{ + const char *type = NULL; + + if (igp) { + type = "igp"; + } else if (te) { + type = "te-default"; + } else if (delay) { + type = "min-uni-link-delay"; + } else { + vty_out(vty, "Error: unknown metric type\n"); + return CMD_SUCCESS; + } + + if (!igp) + vty_out(vty, + "Warning: this version can advertise a Flex-Algorithm Definition (FAD) with the %s metric.\n" + "However, participation in a Flex-Algorithm with such a metric is not yet supported.\n", + type); + + nb_cli_enqueue_change(vty, "./metric-type", + no ? NB_OP_DESTROY : NB_OP_MODIFY, + no ? NULL : type); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(priority, priority_cmd, "[no] priority (0-255)$priority", + NO_STR + "Flex-Algo definition priority\n" + "Priority value\n") +{ + nb_cli_enqueue_change(vty, "./priority", + no ? NB_OP_DESTROY : NB_OP_MODIFY, + no ? NULL : priority_str); + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_flex_algo(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + uint32_t algorithm; + enum flex_algo_metric_type metric_type; + uint32_t priority; + char type_str[10]; + + algorithm = yang_dnode_get_uint32(dnode, "flex-algo"); + vty_out(vty, " flex-algo %u\n", algorithm); + + if (yang_dnode_exists(dnode, "advertise-definition")) + vty_out(vty, " advertise-definition\n"); + + if (yang_dnode_exists(dnode, "dplane-sr-mpls")) + vty_out(vty, " dataplane sr-mpls\n"); + if (yang_dnode_exists(dnode, "dplane-srv6")) + vty_out(vty, " dataplane srv6\n"); + if (yang_dnode_exists(dnode, "dplane-ip")) + vty_out(vty, " dataplane ip\n"); + + if (yang_dnode_exists(dnode, "prefix-metric")) + vty_out(vty, " prefix-metric\n"); + + if (yang_dnode_exists(dnode, "metric-type")) { + metric_type = yang_dnode_get_enum(dnode, "metric-type"); + if (metric_type != MT_IGP) { + flex_algo_metric_type_print(type_str, sizeof(type_str), + metric_type); + vty_out(vty, " metric-type %s\n", type_str); + } + } + + if (yang_dnode_exists(dnode, "priority")) { + priority = yang_dnode_get_uint32(dnode, "priority"); + if (priority != FLEX_ALGO_PRIO_DEFAULT) + vty_out(vty, " priority %u\n", priority); + } + + if (yang_dnode_exists(dnode, + "./affinity-include-alls/affinity-include-all")) { + vty_out(vty, " affinity include-all"); + yang_dnode_iterate( + ag_iter_cb, vty, dnode, + "./affinity-include-alls/affinity-include-all"); + vty_out(vty, "\n"); + } + + if (yang_dnode_exists( + dnode, "./affinity-include-anies/affinity-include-any")) { + vty_out(vty, " affinity include-any"); + yang_dnode_iterate( + ag_iter_cb, vty, dnode, + "./affinity-include-anies/affinity-include-any"); + vty_out(vty, "\n"); + } + + if (yang_dnode_exists( + dnode, "./affinity-exclude-anies/affinity-exclude-any")) { + vty_out(vty, " affinity exclude-any"); + yang_dnode_iterate( + ag_iter_cb, vty, dnode, + "./affinity-exclude-anies/affinity-exclude-any"); + vty_out(vty, "\n"); + } +} + +void cli_show_isis_flex_algo_end(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, " !\n"); +} + + +void isis_cli_init(void) +{ + install_element(CONFIG_NODE, &router_isis_cmd); + install_element(CONFIG_NODE, &no_router_isis_cmd); + + install_element(INTERFACE_NODE, &ip_router_isis_cmd); + install_element(INTERFACE_NODE, &ip_router_isis_vrf_cmd); + install_element(INTERFACE_NODE, &ip6_router_isis_cmd); + install_element(INTERFACE_NODE, &ip6_router_isis_vrf_cmd); + install_element(INTERFACE_NODE, &no_ip_router_isis_cmd); + install_element(INTERFACE_NODE, &no_ip_router_isis_vrf_cmd); + install_element(INTERFACE_NODE, &isis_bfd_cmd); + install_element(INTERFACE_NODE, &isis_bfd_profile_cmd); + + install_element(ISIS_NODE, &net_cmd); + + install_element(ISIS_NODE, &is_type_cmd); + install_element(ISIS_NODE, &no_is_type_cmd); + + install_element(ISIS_NODE, &dynamic_hostname_cmd); + + install_element(ISIS_NODE, &set_overload_bit_cmd); + install_element(ISIS_NODE, &set_overload_bit_on_startup_cmd); + install_element(ISIS_NODE, &no_set_overload_bit_on_startup_cmd); + + install_element(ISIS_NODE, &attached_bit_send_cmd); + install_element(ISIS_NODE, &attached_bit_receive_ignore_cmd); + + install_element(ISIS_NODE, &metric_style_cmd); + install_element(ISIS_NODE, &no_metric_style_cmd); + + install_element(ISIS_NODE, &advertise_high_metrics_cmd); + + install_element(ISIS_NODE, &area_passwd_cmd); + install_element(ISIS_NODE, &domain_passwd_cmd); + install_element(ISIS_NODE, &no_area_passwd_cmd); + + install_element(ISIS_NODE, &lsp_gen_interval_cmd); + install_element(ISIS_NODE, &no_lsp_gen_interval_cmd); + install_element(ISIS_NODE, &lsp_refresh_interval_cmd); + install_element(ISIS_NODE, &no_lsp_refresh_interval_cmd); + install_element(ISIS_NODE, &max_lsp_lifetime_cmd); + install_element(ISIS_NODE, &no_max_lsp_lifetime_cmd); + install_element(ISIS_NODE, &lsp_timers_cmd); + install_element(ISIS_NODE, &no_lsp_timers_cmd); + install_element(ISIS_NODE, &area_lsp_mtu_cmd); + install_element(ISIS_NODE, &no_area_lsp_mtu_cmd); + install_element(ISIS_NODE, &advertise_passive_only_cmd); + + install_element(ISIS_NODE, &spf_interval_cmd); + install_element(ISIS_NODE, &no_spf_interval_cmd); + install_element(ISIS_NODE, &spf_prefix_priority_cmd); + install_element(ISIS_NODE, &no_spf_prefix_priority_cmd); + install_element(ISIS_NODE, &spf_delay_ietf_cmd); + install_element(ISIS_NODE, &no_spf_delay_ietf_cmd); + + install_element(ISIS_NODE, &area_purge_originator_cmd); + + install_element(ISIS_NODE, &isis_admin_group_send_zero_cmd); + install_element(ISIS_NODE, &isis_asla_legacy_flag_cmd); + + install_element(ISIS_NODE, &isis_mpls_te_on_cmd); + install_element(ISIS_NODE, &no_isis_mpls_te_on_cmd); + install_element(ISIS_NODE, &isis_mpls_te_router_addr_cmd); + install_element(ISIS_NODE, &no_isis_mpls_te_router_addr_cmd); + install_element(ISIS_NODE, &isis_mpls_te_router_addr_v6_cmd); + install_element(ISIS_NODE, &no_isis_mpls_te_router_addr_v6_cmd); + install_element(ISIS_NODE, &isis_mpls_te_inter_as_cmd); + install_element(ISIS_NODE, &isis_mpls_te_export_cmd); + install_element(ISIS_NODE, &no_isis_mpls_te_export_cmd); + + install_element(ISIS_NODE, &isis_default_originate_cmd); + install_element(ISIS_NODE, &isis_redistribute_cmd); + install_element(ISIS_NODE, &isis_redistribute_table_cmd); + + install_element(ISIS_NODE, &isis_topology_cmd); + + install_element(ISIS_NODE, &isis_sr_enable_cmd); + install_element(ISIS_NODE, &no_isis_sr_enable_cmd); + install_element(ISIS_NODE, &isis_sr_global_block_label_range_cmd); + install_element(ISIS_NODE, &no_isis_sr_global_block_label_range_cmd); + install_element(ISIS_NODE, &isis_sr_node_msd_cmd); + install_element(ISIS_NODE, &no_isis_sr_node_msd_cmd); + install_element(ISIS_NODE, &isis_sr_prefix_sid_cmd); + install_element(ISIS_NODE, &no_isis_sr_prefix_sid_cmd); +#ifndef FABRICD + install_element(ISIS_NODE, &isis_sr_prefix_sid_algorithm_cmd); + install_element(ISIS_NODE, &no_isis_sr_prefix_sid_algorithm_cmd); +#endif /* ifndef FABRICD */ + install_element(ISIS_NODE, &isis_frr_lfa_priority_limit_cmd); + install_element(ISIS_NODE, &isis_frr_lfa_tiebreaker_cmd); + install_element(ISIS_NODE, &isis_frr_lfa_load_sharing_cmd); + install_element(ISIS_NODE, &isis_frr_remote_lfa_plist_cmd); + install_element(ISIS_NODE, &no_isis_frr_remote_lfa_plist_cmd); + + install_element(ISIS_NODE, &isis_srv6_enable_cmd); + install_element(ISIS_NODE, &no_isis_srv6_enable_cmd); + install_element(ISIS_SRV6_NODE, &isis_srv6_locator_cmd); + install_element(ISIS_SRV6_NODE, &isis_srv6_node_msd_cmd); + install_element(ISIS_SRV6_NODE, &isis_srv6_interface_cmd); + install_element(ISIS_SRV6_NODE_MSD_NODE, + &isis_srv6_node_msd_max_segs_left_cmd); + install_element(ISIS_SRV6_NODE_MSD_NODE, + &isis_srv6_node_msd_max_end_pop_cmd); + install_element(ISIS_SRV6_NODE_MSD_NODE, + &isis_srv6_node_msd_max_h_encaps_cmd); + install_element(ISIS_SRV6_NODE_MSD_NODE, + &isis_srv6_node_msd_max_end_d_cmd); + + install_element(INTERFACE_NODE, &isis_passive_cmd); + + install_element(INTERFACE_NODE, &isis_passwd_cmd); + install_element(INTERFACE_NODE, &no_isis_passwd_cmd); + + install_element(INTERFACE_NODE, &isis_metric_cmd); + install_element(INTERFACE_NODE, &no_isis_metric_cmd); + + install_element(INTERFACE_NODE, &isis_hello_interval_cmd); + install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd); + + install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd); + install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd); + + install_element(INTERFACE_NODE, &isis_threeway_adj_cmd); + + install_element(INTERFACE_NODE, &isis_hello_padding_cmd); + + install_element(INTERFACE_NODE, &csnp_interval_cmd); + install_element(INTERFACE_NODE, &no_csnp_interval_cmd); + + install_element(INTERFACE_NODE, &psnp_interval_cmd); + install_element(INTERFACE_NODE, &no_psnp_interval_cmd); + + install_element(INTERFACE_NODE, &circuit_topology_cmd); + + install_element(INTERFACE_NODE, &isis_circuit_type_cmd); + install_element(INTERFACE_NODE, &no_isis_circuit_type_cmd); + + install_element(INTERFACE_NODE, &isis_network_cmd); + + install_element(INTERFACE_NODE, &isis_priority_cmd); + install_element(INTERFACE_NODE, &no_isis_priority_cmd); + + install_element(INTERFACE_NODE, &isis_lfa_cmd); + install_element(INTERFACE_NODE, &isis_lfa_exclude_interface_cmd); + install_element(INTERFACE_NODE, &isis_remote_lfa_cmd); + install_element(INTERFACE_NODE, &isis_remote_lfa_max_metric_cmd); + install_element(INTERFACE_NODE, &isis_ti_lfa_cmd); + + install_element(ISIS_NODE, &log_adj_changes_cmd); + install_element(ISIS_NODE, &log_pdu_drops_cmd); + + install_element(ISIS_NODE, &isis_mpls_ldp_sync_cmd); + install_element(ISIS_NODE, &no_isis_mpls_ldp_sync_cmd); + install_element(ISIS_NODE, &isis_mpls_ldp_sync_holddown_cmd); + install_element(ISIS_NODE, &no_isis_mpls_ldp_sync_holddown_cmd); + install_element(INTERFACE_NODE, &isis_mpls_if_ldp_sync_cmd); + install_element(INTERFACE_NODE, &isis_mpls_if_ldp_sync_holddown_cmd); + install_element(INTERFACE_NODE, &no_isis_mpls_if_ldp_sync_holddown_cmd); + + install_element(ISIS_NODE, &flex_algo_cmd); + install_element(ISIS_NODE, &no_flex_algo_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &advertise_definition_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &affinity_include_any_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &affinity_include_all_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &affinity_exclude_any_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &dplane_sr_mpls_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &dplane_srv6_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &dplane_ip_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &prefix_metric_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &metric_type_cmd); + install_element(ISIS_FLEX_ALGO_NODE, &priority_cmd); +} + +#endif /* ifndef FABRICD */ diff --git a/isisd/isis_common.h b/isisd/isis_common.h new file mode 100644 index 0000000..2ded68f --- /dev/null +++ b/isisd/isis_common.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_common.h + * some common data structures + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef ISIS_COMMON_H +#define ISIS_COMMON_H + +struct isis_passwd { + uint8_t len; +#define ISIS_PASSWD_TYPE_UNUSED 0 +#define ISIS_PASSWD_TYPE_CLEARTXT 1 +#define ISIS_PASSWD_TYPE_HMAC_MD5 54 +#define ISIS_PASSWD_TYPE_PRIVATE 255 + uint8_t type; +/* Authenticate SNPs? */ +#define SNP_AUTH_SEND 0x01 +#define SNP_AUTH_RECV 0x02 + uint8_t snp_auth; + uint8_t passwd[255]; +}; + +/* + * Supported Protocol IDs + */ +struct nlpids { + uint8_t count; + uint8_t nlpids[4]; /* FIXME: enough ? */ +}; + +#endif diff --git a/isisd/isis_constants.h b/isisd/isis_constants.h new file mode 100644 index 0000000..b2be282 --- /dev/null +++ b/isisd/isis_constants.h @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_constants.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef ISIS_CONSTANTS_H +#define ISIS_CONSTANTS_H + +/* + * Architectural constant values from p. 35 of ISO/IEC 10589 + */ + +#define MAX_NARROW_LINK_METRIC 63 +#define MAX_NARROW_PATH_METRIC 1023 +#define MAX_WIDE_LINK_METRIC 0x00FFFFFF /* RFC4444 */ +#define MAX_WIDE_PATH_METRIC 0xFE000000 /* RFC3787 */ +#define ISO_SAP 0xFE +#define INTRADOMAIN_ROUTEING_SELECTOR 0 +#define SEQUENCE_MODULUS 4294967296 + +#define ISO9542_ESIS 0x82 +#define ISO10589_ISIS 0x83 + +/* + * implementation specific jitter values + */ + +#define IIH_JITTER 10 /* % */ +#define MAX_AGE_JITTER 5 /* % */ +#define MAX_LSP_GEN_JITTER 5 /* % */ +#define CSNP_JITTER 10 /* % */ +#define PSNP_JITTER 10 /* % */ + +#define RANDOM_SPREAD 100000.0 + +#define ISIS_LEVELS 2 +#define ISIS_LEVEL1 1 +#define ISIS_LEVEL2 2 + +/* + * Default values + * ISO - 10589 Section 7.3.21 - Parameters + * RFC 4444 + */ +#define MAX_AGE 1200 +#define ZERO_AGE_LIFETIME 60 +#define MIN_LSP_LIFETIME 350 +#define MAX_LSP_LIFETIME 65535 +#define DEFAULT_LSP_LIFETIME 1200 + +#define MIN_MAX_LSP_GEN_INTERVAL 1 +#define MAX_MAX_LSP_GEN_INTERVAL 65235 +#define DEFAULT_MAX_LSP_GEN_INTERVAL 900 + +#define MIN_MIN_LSP_GEN_INTERVAL 1 +#define MAX_MIN_LSP_GEN_INTERVAL 120 /* RFC 4444 says 65535 */ +#define DEFAULT_MIN_LSP_GEN_INTERVAL 30 + +#define MIN_LSP_RETRANS_INTERVAL 5 /* Seconds */ + +#define TRIGGERED_IIH_DELAY 50 /* msec */ + +#define MIN_CSNP_INTERVAL 1 +#define MAX_CSNP_INTERVAL 600 +#define DEFAULT_CSNP_INTERVAL 10 + +#define MIN_PSNP_INTERVAL 1 +#define MAX_PSNP_INTERVAL 120 +#define DEFAULT_PSNP_INTERVAL 2 + +#define MIN_HELLO_INTERVAL 1 +#define MAX_HELLO_INTERVAL 600 +#define DEFAULT_HELLO_INTERVAL 3 + +#define MIN_HELLO_MULTIPLIER 2 +#define MAX_HELLO_MULTIPLIER 100 +#define DEFAULT_HELLO_MULTIPLIER 10 + +#define MIN_PRIORITY 0 +#define MAX_PRIORITY 127 +#define DEFAULT_PRIORITY 64 + +/* min and max metric varies by new vs old metric types */ +#define DEFAULT_CIRCUIT_METRIC 10 + +#define METRICS_UNSUPPORTED 0x80 + +#define MINIMUM_SPF_INTERVAL 1 + +#define ISIS_MAX_PATH_SPLITS 64 + +/* + * NLPID values + */ +#define NLPID_IP 204 +#define NLPID_IPV6 142 +#define NLPID_SNAP 128 +#define NLPID_CLNP 129 +#define NLPID_ESIS 130 + +/* + * Return values for functions + */ +#define ISIS_OK 0 +#define ISIS_WARNING 1 +#define ISIS_ERROR 2 +#define ISIS_CRITICAL 3 + +/* + * IS-IS Circuit Types + */ + +#define IS_LEVEL_1 1 +#define IS_LEVEL_2 2 +#define IS_LEVEL_1_AND_2 3 + +#define SNPA_ADDRSTRLEN 18 +#define ISIS_SYS_ID_LEN 6 +#define ISIS_NSEL_LEN 1 +#define SYSID_STRLEN 24 + +/* + * LSP bit masks + */ +#define LSPBIT_P 0x80 +#define LSPBIT_ATT 0x08 /* only use the Default ATT bit */ +#define LSPBIT_OL 0x04 +#define LSPBIT_IST 0x03 + +/* + * LSP bit masking macros + * taken from tcpdumps + * print-isoclns.c + */ + +#define ISIS_MASK_LSP_OL_BIT(x) ((x)&0x4) +#define ISIS_MASK_LSP_IS_L1_BIT(x) ((x)&0x1) +#define ISIS_MASK_LSP_IS_L2_BIT(x) ((x)&0x2) +#define ISIS_MASK_LSP_PARTITION_BIT(x) ((x)&0x80) +#define ISIS_MASK_LSP_ATT_BITS(x) ((x)&0x78) +#define ISIS_MASK_LSP_ATT_ERROR_BIT(x) ((x)&0x40) +#define ISIS_MASK_LSP_ATT_EXPENSE_BIT(x) ((x)&0x20) +#define ISIS_MASK_LSP_ATT_DELAY_BIT(x) ((x)&0x10) + +#define LLC_LEN 3 + +/* we need to be aware of the fact we are using ISO sized + * packets, using isomtu = mtu - LLC_LEN + */ +#define ISO_MTU(C) \ + ((if_is_broadcast((C)->interface)) ? (C->interface->mtu - LLC_LEN) \ + : (C->interface->mtu)) + +#define MAX_LLC_LEN 0x5ff +#define ETHERTYPE_EXT_LLC 0x8870 + +static inline uint16_t isis_ethertype(size_t len) +{ + if (len > MAX_LLC_LEN) + return ETHERTYPE_EXT_LLC; + return len; +} + +#endif /* ISIS_CONSTANTS_H */ diff --git a/isisd/isis_csm.c b/isisd/isis_csm.c new file mode 100644 index 0000000..9e278d4 --- /dev/null +++ b/isisd/isis_csm.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_csm.c + * IS-IS circuit state machine + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "log.h" +#include "memory.h" +#include "if.h" +#include "linklist.h" +#include "command.h" +#include "frrevent.h" +#include "hash.h" +#include "prefix.h" +#include "stream.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dr.h" +#include "isisd/isisd.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_events.h" +#include "isisd/isis_errors.h" + +static const char *const csm_statestr[] = {"C_STATE_NA", "C_STATE_INIT", + "C_STATE_CONF", "C_STATE_UP"}; + +#define STATE2STR(S) csm_statestr[S] + +static const char *const csm_eventstr[] = { + "NO_STATE", "ISIS_ENABLE", "IF_UP_FROM_Z", + "ISIS_DISABLE", "IF_DOWN_FROM_Z", +}; + +#define EVENT2STR(E) csm_eventstr[E] + +struct isis_circuit *isis_csm_state_change(enum isis_circuit_event event, + struct isis_circuit *circuit, + void *arg) +{ + enum isis_circuit_state old_state; + struct isis_area *area = NULL; + struct interface *ifp; + + assert(circuit); + + old_state = circuit->state; + if (IS_DEBUG_EVENTS) + zlog_debug("CSM_EVENT for %s: %s", circuit->interface->name, + EVENT2STR(event)); + + switch (old_state) { + case C_STATE_NA: + switch (event) { + case ISIS_ENABLE: + area = arg; + + isis_circuit_configure(circuit, area); + circuit->state = C_STATE_CONF; + break; + case IF_UP_FROM_Z: + ifp = arg; + + isis_circuit_if_add(circuit, ifp); + circuit->state = C_STATE_INIT; + break; + case ISIS_DISABLE: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already disabled", + circuit->interface->name); + break; + case IF_DOWN_FROM_Z: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already disconnected", + circuit->interface->name); + break; + } + break; + case C_STATE_INIT: + switch (event) { + case ISIS_ENABLE: + area = arg; + + isis_circuit_configure(circuit, area); + if (isis_circuit_up(circuit) != ISIS_OK) { + isis_circuit_deconfigure(circuit, area); + break; + } + circuit->state = C_STATE_UP; + isis_event_circuit_state_change(circuit, circuit->area, + 1); + break; + case IF_UP_FROM_Z: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already connected", + circuit->interface->name); + break; + case ISIS_DISABLE: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already disabled", + circuit->interface->name); + break; + case IF_DOWN_FROM_Z: + ifp = arg; + + isis_circuit_if_del(circuit, ifp); + circuit->state = C_STATE_NA; + break; + } + break; + case C_STATE_CONF: + switch (event) { + case ISIS_ENABLE: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s is already enabled", + circuit->interface->name); + break; + case IF_UP_FROM_Z: + ifp = arg; + + isis_circuit_if_add(circuit, ifp); + if (isis_circuit_up(circuit) != ISIS_OK) { + isis_circuit_if_del(circuit, ifp); + flog_err( + EC_ISIS_CONFIG, + "Could not bring up %s because of invalid config.", + circuit->interface->name); + break; + } + circuit->state = C_STATE_UP; + isis_event_circuit_state_change(circuit, circuit->area, + 1); + break; + case ISIS_DISABLE: + area = arg; + + isis_circuit_deconfigure(circuit, area); + circuit->state = C_STATE_NA; + break; + case IF_DOWN_FROM_Z: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already disconnected", + circuit->interface->name); + break; + } + break; + case C_STATE_UP: + switch (event) { + case ISIS_ENABLE: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already enabled", + circuit->interface->name); + break; + case IF_UP_FROM_Z: + if (IS_DEBUG_EVENTS) + zlog_debug("circuit %s already connected", + circuit->interface->name); + break; + case ISIS_DISABLE: + area = arg; + + isis_circuit_down(circuit); + isis_circuit_deconfigure(circuit, area); + circuit->state = C_STATE_INIT; + isis_event_circuit_state_change(circuit, area, 0); + break; + case IF_DOWN_FROM_Z: + ifp = arg; + + isis_circuit_down(circuit); + isis_circuit_if_del(circuit, ifp); + circuit->state = C_STATE_CONF; + isis_event_circuit_state_change(circuit, circuit->area, + 0); + break; + } + break; + } + + if (IS_DEBUG_EVENTS) + zlog_debug("CSM_STATE_CHANGE: %s -> %s ", STATE2STR(old_state), + STATE2STR(circuit->state)); + + return circuit; +} diff --git a/isisd/isis_csm.h b/isisd/isis_csm.h new file mode 100644 index 0000000..80e02b8 --- /dev/null +++ b/isisd/isis_csm.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_csm.h + * IS-IS circuit state machine + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * + */ +#ifndef _ZEBRA_ISIS_CSM_H +#define _ZEBRA_ISIS_CSM_H + +/* + * Circuit states + */ +enum isis_circuit_state { + C_STATE_NA, + C_STATE_INIT, /* Connected to interface */ + C_STATE_CONF, /* Configured for ISIS */ + C_STATE_UP, /* CONN | CONF */ +}; + +/* + * Circuit events + */ +enum isis_circuit_event { + ISIS_ENABLE = 1, + IF_UP_FROM_Z, + ISIS_DISABLE, + IF_DOWN_FROM_Z, +}; + +struct isis_circuit *isis_csm_state_change(enum isis_circuit_event event, + struct isis_circuit *circuit, + void *arg); + +#endif /* _ZEBRA_ISIS_CSM_H */ diff --git a/isisd/isis_dlpi.c b/isisd/isis_dlpi.c new file mode 100644 index 0000000..1f09152 --- /dev/null +++ b/isisd/isis_dlpi.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_dlpi.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include +#if ISIS_METHOD == ISIS_METHOD_DLPI +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "network.h" +#include "stream.h" +#include "if.h" +#include "lib_errors.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_flags.h" +#include "isisd/isisd.h" +#include "isisd/isis_network.h" + +#include "privs.h" + +static t_uscalar_t dlpi_ctl[1024]; /* DLPI control messages */ + +/* + * Table 9 - Architectural constants for use with ISO 8802 subnetworks + * ISO 10589 - 8.4.8 + */ + +static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; +static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; +static const uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05}; +static uint8_t sock_buff[16384]; + +static unsigned short pf_filter[] = { + ENF_PUSHWORD + 0, /* Get the SSAP/DSAP values */ + ENF_PUSHLIT | ENF_CAND, /* Check them */ + ISO_SAP | (ISO_SAP << 8), + ENF_PUSHWORD + 1, /* Get the control value */ + ENF_PUSHLIT | ENF_AND, /* Isolate it */ +#ifdef _BIG_ENDIAN + 0xFF00, +#else + 0x00FF, +#endif + ENF_PUSHLIT | ENF_CAND, /* Test for expected value */ +#ifdef _BIG_ENDIAN + 0x0300 +#else + 0x0003 +#endif +}; + +/* + * We would like to use something like libdlpi here, but that's not present on + * all versions of Solaris or on any non-Solaris system, so it's nowhere near + * as portable as we'd like. Thus, we use the standards-conformant DLPI + * interfaces plus the (optional; not needed) Solaris packet filter module. + */ + +static int dlpisend(int fd, const void *cbuf, size_t cbuflen, const void *dbuf, + size_t dbuflen, int flags) +{ + const struct strbuf *ctlptr = NULL; + const struct strbuf *dataptr = NULL; + struct strbuf ctlbuf, databuf; + int rv; + + if (cbuf != NULL) { + memset(&ctlbuf, 0, sizeof(ctlbuf)); + ctlbuf.len = cbuflen; + ctlbuf.buf = (void *)cbuf; + ctlptr = &ctlbuf; + } + + if (dbuf != NULL) { + memset(&databuf, 0, sizeof(databuf)); + databuf.len = dbuflen; + databuf.buf = (void *)dbuf; + dataptr = &databuf; + } + + /* We assume this doesn't happen often and isn't operationally + * significant */ + rv = putmsg(fd, ctlptr, dataptr, flags); + if (rv == -1 && dbuf == NULL) { + /* + * For actual PDU transmission - recognizable buf dbuf != NULL, + * the error is passed upwards and should not be printed here. + */ + zlog_debug("%s: putmsg: %s", __func__, safe_strerror(errno)); + } + return rv; +} + +static ssize_t dlpirctl(int fd) +{ + struct pollfd fds[1]; + struct strbuf ctlbuf, databuf; + int flags, retv; + + do { + /* Poll is used here in case the device doesn't speak DLPI + * correctly */ + memset(fds, 0, sizeof(fds)); + fds[0].fd = fd; + fds[0].events = POLLIN | POLLPRI; + if (poll(fds, 1, 1000) <= 0) + return -1; + + memset(&ctlbuf, 0, sizeof(ctlbuf)); + memset(&databuf, 0, sizeof(databuf)); + ctlbuf.maxlen = sizeof(dlpi_ctl); + ctlbuf.buf = (void *)dlpi_ctl; + databuf.maxlen = sizeof(sock_buff); + databuf.buf = (void *)sock_buff; + flags = 0; + retv = getmsg(fd, &ctlbuf, &databuf, &flags); + + if (retv < 0) + return -1; + } while (ctlbuf.len == 0); + + if (!(retv & MORECTL)) { + while (retv & MOREDATA) { + flags = 0; + retv = getmsg(fd, NULL, &databuf, &flags); + } + return ctlbuf.len; + } + + while (retv & MORECTL) { + flags = 0; + retv = getmsg(fd, &ctlbuf, &databuf, &flags); + } + return -1; +} + +static int dlpiok(int fd, t_uscalar_t oprim) +{ + int retv; + dl_ok_ack_t *doa = (dl_ok_ack_t *)dlpi_ctl; + + retv = dlpirctl(fd); + if (retv < (ssize_t)DL_OK_ACK_SIZE || doa->dl_primitive != DL_OK_ACK + || doa->dl_correct_primitive != oprim) { + return -1; + } else { + return 0; + } +} + +static int dlpiinfo(int fd) +{ + dl_info_req_t dir; + ssize_t retv; + + memset(&dir, 0, sizeof(dir)); + dir.dl_primitive = DL_INFO_REQ; + /* Info_req uses M_PCPROTO. */ + dlpisend(fd, &dir, sizeof(dir), NULL, 0, RS_HIPRI); + retv = dlpirctl(fd); + if (retv < (ssize_t)DL_INFO_ACK_SIZE || dlpi_ctl[0] != DL_INFO_ACK) + return -1; + else + return retv; +} + +static int dlpiopen(const char *devpath, ssize_t *acklen) +{ + int fd, flags; + + fd = open(devpath, O_RDWR | O_NONBLOCK | O_NOCTTY); + if (fd == -1) + return -1; + + /* All that we want is for the open itself to be non-blocking, not I/O. + */ + flags = fcntl(fd, F_GETFL, 0); + if (flags != -1) + fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + + /* After opening, ask for information */ + if ((*acklen = dlpiinfo(fd)) == -1) { + close(fd); + return -1; + } + + return fd; +} + +static int dlpiattach(int fd, int unit) +{ + dl_attach_req_t dar; + + memset(&dar, 0, sizeof(dar)); + dar.dl_primitive = DL_ATTACH_REQ; + dar.dl_ppa = unit; + dlpisend(fd, &dar, sizeof(dar), NULL, 0, 0); + return dlpiok(fd, dar.dl_primitive); +} + +static int dlpibind(int fd) +{ + dl_bind_req_t dbr; + int retv; + dl_bind_ack_t *dba = (dl_bind_ack_t *)dlpi_ctl; + + memset(&dbr, 0, sizeof(dbr)); + dbr.dl_primitive = DL_BIND_REQ; + dbr.dl_service_mode = DL_CLDLS; + dlpisend(fd, &dbr, sizeof(dbr), NULL, 0, 0); + + retv = dlpirctl(fd); + if (retv < (ssize_t)DL_BIND_ACK_SIZE + || dba->dl_primitive != DL_BIND_ACK) + return -1; + else + return 0; +} + +static int dlpimcast(int fd, const uint8_t *mcaddr) +{ + struct { + dl_enabmulti_req_t der; + uint8_t addr[ETHERADDRL]; + } dler; + + memset(&dler, 0, sizeof(dler)); + dler.der.dl_primitive = DL_ENABMULTI_REQ; + dler.der.dl_addr_length = sizeof(dler.addr); + dler.der.dl_addr_offset = dler.addr - (uint8_t *)&dler; + memcpy(dler.addr, mcaddr, sizeof(dler.addr)); + dlpisend(fd, &dler, sizeof(dler), NULL, 0, 0); + return dlpiok(fd, dler.der.dl_primitive); +} + +static int dlpiaddr(int fd, uint8_t *addr) +{ + dl_phys_addr_req_t dpar; + dl_phys_addr_ack_t *dpaa = (dl_phys_addr_ack_t *)dlpi_ctl; + int retv; + + memset(&dpar, 0, sizeof(dpar)); + dpar.dl_primitive = DL_PHYS_ADDR_REQ; + dpar.dl_addr_type = DL_CURR_PHYS_ADDR; + dlpisend(fd, &dpar, sizeof(dpar), NULL, 0, 0); + + retv = dlpirctl(fd); + if (retv < (ssize_t)DL_PHYS_ADDR_ACK_SIZE + || dpaa->dl_primitive != DL_PHYS_ADDR_ACK) + return -1; + + if (dpaa->dl_addr_offset < DL_PHYS_ADDR_ACK_SIZE + || dpaa->dl_addr_length != ETHERADDRL + || dpaa->dl_addr_offset + dpaa->dl_addr_length > (size_t)retv) + return -1; + + bcopy((char *)dpaa + dpaa->dl_addr_offset, addr, ETHERADDRL); + return 0; +} + +static int open_dlpi_dev(struct isis_circuit *circuit) +{ + int fd = -1, unit, retval; + char devpath[MAXPATHLEN]; + dl_info_ack_t *dia = (dl_info_ack_t *)dlpi_ctl; + ssize_t acklen; + + /* Only broadcast-type are supported at the moment */ + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + zlog_warn("%s: non-broadcast interface %s", __func__, + circuit->interface->name); + return ISIS_WARNING; + } + + /* Try the vanity node first, if permitted */ + if (getenv("DLPI_DEVONLY") == NULL) { + (void)snprintf(devpath, sizeof(devpath), "/dev/net/%s", + circuit->interface->name); + fd = dlpiopen(devpath, &acklen); + } + + /* Now try as an ordinary Style 1 node */ + if (fd == -1) { + (void)snprintf(devpath, sizeof(devpath), "/dev/%s", + circuit->interface->name); + unit = -1; + fd = dlpiopen(devpath, &acklen); + } + + /* If that fails, try again as Style 2 */ + if (fd == -1) { + char *cp; + + cp = devpath + strlen(devpath); + while (--cp >= devpath && isdigit(*cp)) + ; + unit = strtol(cp, NULL, 0); + *cp = '\0'; + fd = dlpiopen(devpath, &acklen); + + /* If that too fails, then the device really doesn't exist */ + if (fd == -1) { + zlog_warn("%s: unknown interface %s", __func__, + circuit->interface->name); + return ISIS_WARNING; + } + + /* Double check the DLPI style */ + if (dia->dl_provider_style != DL_STYLE2) { + zlog_warn("%s: interface %s: %s is not style 2", + __func__, circuit->interface->name, devpath); + close(fd); + return ISIS_WARNING; + } + + /* If it succeeds, then we need to attach to the unit specified + */ + dlpiattach(fd, unit); + + /* Reget the information, as it may be different per node */ + if ((acklen = dlpiinfo(fd)) == -1) { + close(fd); + return ISIS_WARNING; + } + } else { + /* Double check the DLPI style */ + if (dia->dl_provider_style != DL_STYLE1) { + zlog_warn("%s: interface %s: %s is not style 1", + __func__, circuit->interface->name, devpath); + close(fd); + return ISIS_WARNING; + } + } + + /* Check that the interface we've got is the kind we expect */ + if ((dia->dl_sap_length != 2 && dia->dl_sap_length != -2) + || dia->dl_service_mode != DL_CLDLS + || dia->dl_addr_length != ETHERADDRL + 2 + || dia->dl_brdcst_addr_length != ETHERADDRL) { + zlog_warn("%s: unsupported interface type for %s", __func__, + circuit->interface->name); + close(fd); + return ISIS_WARNING; + } + switch (dia->dl_mac_type) { + case DL_CSMACD: + case DL_ETHER: + case DL_100VG: + case DL_100VGTPR: + case DL_ETH_CSMA: + case DL_100BT: + break; + default: + zlog_warn("%s: unexpected mac type on %s: %lld", __func__, + circuit->interface->name, + (long long)dia->dl_mac_type); + close(fd); + return ISIS_WARNING; + } + + circuit->sap_length = dia->dl_sap_length; + + /* + * The local hardware address is something that should be provided by + * way of + * sockaddr_dl for the interface, but isn't on Solaris. We set it here + * based + * on DLPI's reported address to avoid roto-tilling the world. + * (Note that isis_circuit_if_add on Solaris doesn't set the snpa.) + * + * Unfortunately, GLD is broken and doesn't provide the address after + * attach, + * so we need to be careful and use DL_PHYS_ADDR_REQ instead. + */ + if (dlpiaddr(fd, circuit->u.bc.snpa) == -1) { + zlog_warn("%s: interface %s: unable to get MAC address", + __func__, circuit->interface->name); + close(fd); + return ISIS_WARNING; + } + + /* Now bind to SAP 0. This gives us 802-type traffic. */ + if (dlpibind(fd) == -1) { + zlog_warn("%s: cannot bind SAP 0 on %s", __func__, + circuit->interface->name); + close(fd); + return ISIS_WARNING; + } + + /* + * Join to multicast groups according to + * 8.4.2 - Broadcast subnetwork IIH PDUs + */ + retval = 0; + retval |= dlpimcast(fd, ALL_L1_ISS); + retval |= dlpimcast(fd, ALL_ISS); + retval |= dlpimcast(fd, ALL_L2_ISS); + + if (retval != 0) { + zlog_warn("%s: unable to join multicast on %s", __func__, + circuit->interface->name); + close(fd); + return ISIS_WARNING; + } + + /* Push on the packet filter to avoid stray 802 packets */ + if (ioctl(fd, I_PUSH, "pfmod") == 0) { + struct packetfilt pfil; + struct strioctl sioc; + + pfil.Pf_Priority = 0; + pfil.Pf_FilterLen = array_size(pf_filter); + memcpy(pfil.Pf_Filter, pf_filter, sizeof(pf_filter)); + /* pfmod does not support transparent ioctls */ + sioc.ic_cmd = PFIOCSETF; + sioc.ic_timout = 5; + sioc.ic_len = sizeof(struct packetfilt); + sioc.ic_dp = (char *)&pfil; + if (ioctl(fd, I_STR, &sioc) == -1) + zlog_warn("%s: could not perform PF_IOCSETF on %s", + __func__, circuit->interface->name); + } + + circuit->fd = fd; + + return ISIS_OK; +} + +/* + * Create the socket and set the tx/rx funcs + */ +int isis_sock_init(struct isis_circuit *circuit) +{ + int retval = ISIS_OK; + + frr_with_privs(&isisd_privs) { + + retval = open_dlpi_dev(circuit); + + if (retval != ISIS_OK) { + zlog_warn("%s: could not initialize the socket", + __func__); + break; + } + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + circuit->tx = isis_send_pdu_bcast; + circuit->rx = isis_recv_pdu_bcast; + } else { + zlog_warn("%s: unknown circuit type", __func__); + retval = ISIS_WARNING; + break; + } + } + + return retval; +} + +int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) +{ + struct pollfd fds[1]; + struct strbuf ctlbuf, databuf; + int flags, retv; + dl_unitdata_ind_t *dui = (dl_unitdata_ind_t *)dlpi_ctl; + + memset(fds, 0, sizeof(fds)); + fds[0].fd = circuit->fd; + fds[0].events = POLLIN | POLLPRI; + if (poll(fds, 1, 0) <= 0) + return ISIS_WARNING; + + memset(&ctlbuf, 0, sizeof(ctlbuf)); + memset(&databuf, 0, sizeof(databuf)); + ctlbuf.maxlen = sizeof(dlpi_ctl); + ctlbuf.buf = (void *)dlpi_ctl; + databuf.maxlen = sizeof(sock_buff); + databuf.buf = (void *)sock_buff; + flags = 0; + retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags); + + if (retv < 0) { + zlog_warn("%s: getmsg failed: %s", __func__, + safe_strerror(errno)); + return ISIS_WARNING; + } + + if (retv & (MORECTL | MOREDATA)) { + while (retv & (MORECTL | MOREDATA)) { + flags = 0; + retv = getmsg(circuit->fd, &ctlbuf, &databuf, &flags); + } + return ISIS_WARNING; + } + + if (ctlbuf.len < (ssize_t)DL_UNITDATA_IND_SIZE + || dui->dl_primitive != DL_UNITDATA_IND) + return ISIS_WARNING; + + if (dui->dl_src_addr_length != ETHERADDRL + 2 + || dui->dl_src_addr_offset < DL_UNITDATA_IND_SIZE + || dui->dl_src_addr_offset + dui->dl_src_addr_length + > (size_t)ctlbuf.len) + return ISIS_WARNING; + + memcpy(ssnpa, + (char *)dui + dui->dl_src_addr_offset + + (circuit->sap_length > 0 ? circuit->sap_length : 0), + ETHERADDRL); + + if (databuf.len < LLC_LEN || sock_buff[0] != ISO_SAP + || sock_buff[1] != ISO_SAP || sock_buff[2] != 3) + return ISIS_WARNING; + + stream_write(circuit->rcv_stream, sock_buff + LLC_LEN, + databuf.len - LLC_LEN); + stream_set_getp(circuit->rcv_stream, 0); + + return ISIS_OK; +} + +int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) +{ + dl_unitdata_req_t *dur = (dl_unitdata_req_t *)dlpi_ctl; + char *dstaddr; + unsigned short *dstsap; + int buflen; + int rv; + + buflen = stream_get_endp(circuit->snd_stream) + LLC_LEN; + if ((size_t)buflen > sizeof(sock_buff)) { + zlog_warn( + "%s: sock_buff size %zu is less than output pdu size %d on circuit %s", + __func__, sizeof(sock_buff), buflen, + circuit->interface->name); + return ISIS_WARNING; + } + + stream_set_getp(circuit->snd_stream, 0); + + memset(dur, 0, sizeof(*dur)); + dur->dl_primitive = DL_UNITDATA_REQ; + dur->dl_dest_addr_length = ETHERADDRL + 2; + dur->dl_dest_addr_offset = sizeof(*dur); + + dstaddr = (char *)(dur + 1); + if (circuit->sap_length < 0) { + dstsap = (unsigned short *)(dstaddr + ETHERADDRL); + } else { + dstsap = (unsigned short *)dstaddr; + dstaddr += circuit->sap_length; + } + if (level == 1) + memcpy(dstaddr, ALL_L1_ISS, ETHERADDRL); + else + memcpy(dstaddr, ALL_L2_ISS, ETHERADDRL); + /* Note: DLPI SAP values are in host byte order */ + *dstsap = buflen; + + sock_buff[0] = ISO_SAP; + sock_buff[1] = ISO_SAP; + sock_buff[2] = 0x03; + memcpy(sock_buff + LLC_LEN, circuit->snd_stream->data, + stream_get_endp(circuit->snd_stream)); + rv = dlpisend(circuit->fd, dur, sizeof(*dur) + dur->dl_dest_addr_length, + sock_buff, buflen, 0); + if (rv < 0) { + zlog_warn("IS-IS dlpi: could not transmit packet on %s: %s", + circuit->interface->name, safe_strerror(errno)); + if (ERRNO_IO_RETRY(errno)) + return ISIS_WARNING; + return ISIS_ERROR; + } + + return ISIS_OK; +} + +#endif /* ISIS_METHOD == ISIS_METHOD_DLPI */ diff --git a/isisd/isis_dr.c b/isisd/isis_dr.c new file mode 100644 index 0000000..3b92160 --- /dev/null +++ b/isisd/isis_dr.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_dr.c + * IS-IS designated router related routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + + +#include + +#include "log.h" +#include "hash.h" +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "stream.h" +#include "if.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_dr.h" +#include "isisd/isis_events.h" + +const char *isis_disflag2string(int disflag) +{ + + switch (disflag) { + case ISIS_IS_NOT_DIS: + return "is not DIS"; + case ISIS_IS_DIS: + return "is DIS"; + case ISIS_WAS_DIS: + return "was DIS"; + default: + return "unknown DIS state"; + } + return NULL; /* not reached */ +} + +void isis_run_dr(struct event *thread) +{ + struct isis_circuit_arg *arg = EVENT_ARG(thread); + + assert(arg); + + struct isis_circuit *circuit = arg->circuit; + int level = arg->level; + + assert(circuit); + + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + zlog_warn("%s: scheduled for non broadcast circuit from %s:%d", + __func__, thread->xref->xref.file, + thread->xref->xref.line); + return; + } + + if (circuit->u.bc.run_dr_elect[level - 1]) + zlog_warn("%s: run_dr_elect already set for l%d", __func__, + level); + + circuit->u.bc.t_run_dr[level - 1] = NULL; + circuit->u.bc.run_dr_elect[level - 1] = 1; +} + +static int isis_check_dr_change(struct isis_adjacency *adj, int level) +{ + int i; + + if (adj->dis_record[level - 1].dis + != adj->dis_record[(1 * ISIS_LEVELS) + level - 1].dis) + /* was there a DIS state transition ? */ + { + adj->dischanges[level - 1]++; + adj->circuit->desig_changes[level - 1]++; + /* ok rotate the history list through */ + for (i = DIS_RECORDS - 1; i > 0; i--) { + adj->dis_record[(i * ISIS_LEVELS) + level - 1].dis = + adj->dis_record[((i - 1) * ISIS_LEVELS) + level + - 1] + .dis; + adj->dis_record[(i * ISIS_LEVELS) + level - 1] + .last_dis_change = + adj->dis_record[((i - 1) * ISIS_LEVELS) + level + - 1] + .last_dis_change; + } + } + return ISIS_OK; +} + +int isis_dr_elect(struct isis_circuit *circuit, int level) +{ + struct list *adjdb; + struct listnode *node; + struct isis_adjacency *adj, *adj_dr = NULL; + struct list *list = list_new(); + uint8_t own_prio; + int biggest_prio = -1; + int cmp_res, retval = ISIS_OK; + + own_prio = circuit->priority[level - 1]; + adjdb = circuit->u.bc.adjdb[level - 1]; + + if (!adjdb) { + zlog_warn("%s adjdb == NULL", __func__); + list_delete(&list); + return ISIS_WARNING; + } + isis_adj_build_up_list(adjdb, list); + + /* + * Loop the adjacencies and find the one with the biggest priority + */ + for (ALL_LIST_ELEMENTS_RO(list, node, adj)) { + /* clear flag for show output */ + adj->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; + adj->dis_record[level - 1].last_dis_change = time(NULL); + + if (adj->prio[level - 1] > biggest_prio) { + biggest_prio = adj->prio[level - 1]; + adj_dr = adj; + } else if (adj->prio[level - 1] == biggest_prio) { + /* + * Comparison of MACs breaks a tie + */ + if (adj_dr) { + cmp_res = memcmp(adj_dr->snpa, adj->snpa, + ETH_ALEN); + if (cmp_res < 0) { + adj_dr = adj; + } + if (cmp_res == 0) + zlog_warn( + "%s: multiple adjacencies with same SNPA", + __func__); + } else { + adj_dr = adj; + } + } + } + + if (!adj_dr) { + /* + * Could not find the DR - means we are alone. Resign if we were + * DR. + */ + if (circuit->u.bc.is_dr[level - 1]) + retval = isis_dr_resign(circuit, level); + list_delete(&list); + return retval; + } + + /* + * Now we have the DR adjacency, compare it to self + */ + if (adj_dr->prio[level - 1] < own_prio + || (adj_dr->prio[level - 1] == own_prio + && memcmp(adj_dr->snpa, circuit->u.bc.snpa, ETH_ALEN) < 0)) { + adj_dr->dis_record[level - 1].dis = ISIS_IS_NOT_DIS; + adj_dr->dis_record[level - 1].last_dis_change = time(NULL); + + /* rotate the history log */ + for (ALL_LIST_ELEMENTS_RO(list, node, adj)) + isis_check_dr_change(adj, level); + + /* We are the DR, commence DR */ + if (circuit->u.bc.is_dr[level - 1] == 0 && listcount(list) > 0) + retval = isis_dr_commence(circuit, level); + } else { + /* ok we have found the DIS - lets mark the adjacency */ + /* set flag for show output */ + adj_dr->dis_record[level - 1].dis = ISIS_IS_DIS; + adj_dr->dis_record[level - 1].last_dis_change = time(NULL); + + /* now loop through a second time to check if there has been a + * DIS change + * if yes rotate the history log + */ + + for (ALL_LIST_ELEMENTS_RO(list, node, adj)) + isis_check_dr_change(adj, level); + + /* + * We are not DR - if we were -> resign + */ + if (circuit->u.bc.is_dr[level - 1]) + retval = isis_dr_resign(circuit, level); + } + list_delete(&list); + return retval; +} + +int isis_dr_resign(struct isis_circuit *circuit, int level) +{ + uint8_t id[ISIS_SYS_ID_LEN + 2]; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s l%d", __func__, level); + + circuit->u.bc.is_dr[level - 1] = 0; + circuit->u.bc.run_dr_elect[level - 1] = 0; + EVENT_OFF(circuit->u.bc.t_run_dr[level - 1]); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + circuit->lsp_regenerate_pending[level - 1] = 0; + + memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(id) = circuit->circuit_id; + LSP_FRAGMENT(id) = 0; + lsp_purge_pseudo(id, circuit, level); + + if (level == 1) { + memset(circuit->u.bc.l1_desig_is, 0, ISIS_SYS_ID_LEN + 1); + + event_add_timer(master, send_l1_psnp, circuit, + isis_jitter(circuit->psnp_interval[level - 1], + PSNP_JITTER), + &circuit->t_send_psnp[0]); + } else { + memset(circuit->u.bc.l2_desig_is, 0, ISIS_SYS_ID_LEN + 1); + + event_add_timer(master, send_l2_psnp, circuit, + isis_jitter(circuit->psnp_interval[level - 1], + PSNP_JITTER), + &circuit->t_send_psnp[1]); + } + + EVENT_OFF(circuit->t_send_csnp[level - 1]); + + event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], + 2 * circuit->hello_interval[level - 1], + &circuit->u.bc.t_run_dr[level - 1]); + + + event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); + + return ISIS_OK; +} + +int isis_dr_commence(struct isis_circuit *circuit, int level) +{ + uint8_t old_dr[ISIS_SYS_ID_LEN + 2]; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s l%d", __func__, level); + + /* Lets keep a pause in DR election */ + circuit->u.bc.run_dr_elect[level - 1] = 0; + circuit->u.bc.is_dr[level - 1] = 1; + + if (level == 1) { + memcpy(old_dr, circuit->u.bc.l1_desig_is, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(old_dr) = 0; + if (LSP_PSEUDO_ID(old_dr)) { + /* there was a dr elected, purge its LSPs from the db */ + lsp_purge_pseudo(old_dr, circuit, level); + } + memcpy(circuit->u.bc.l1_desig_is, circuit->isis->sysid, + ISIS_SYS_ID_LEN); + *(circuit->u.bc.l1_desig_is + ISIS_SYS_ID_LEN) = + circuit->circuit_id; + + assert(circuit->circuit_id); /* must be non-zero */ + lsp_generate_pseudo(circuit, 1); + + event_add_timer(master, send_l1_csnp, circuit, + isis_jitter(circuit->csnp_interval[level - 1], + CSNP_JITTER), + &circuit->t_send_csnp[0]); + + } else { + memcpy(old_dr, circuit->u.bc.l2_desig_is, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(old_dr) = 0; + if (LSP_PSEUDO_ID(old_dr)) { + /* there was a dr elected, purge its LSPs from the db */ + lsp_purge_pseudo(old_dr, circuit, level); + } + memcpy(circuit->u.bc.l2_desig_is, circuit->isis->sysid, + ISIS_SYS_ID_LEN); + *(circuit->u.bc.l2_desig_is + ISIS_SYS_ID_LEN) = + circuit->circuit_id; + + assert(circuit->circuit_id); /* must be non-zero */ + lsp_generate_pseudo(circuit, 2); + + event_add_timer(master, send_l2_csnp, circuit, + isis_jitter(circuit->csnp_interval[level - 1], + CSNP_JITTER), + &circuit->t_send_csnp[1]); + } + + event_add_timer(master, isis_run_dr, &circuit->level_arg[level - 1], + 2 * circuit->hello_interval[level - 1], + &circuit->u.bc.t_run_dr[level - 1]); + event_add_event(master, isis_event_dis_status_change, circuit, 0, NULL); + + return ISIS_OK; +} diff --git a/isisd/isis_dr.h b/isisd/isis_dr.h new file mode 100644 index 0000000..135916a --- /dev/null +++ b/isisd/isis_dr.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_dr.h + * IS-IS designated router related routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_DR_H +#define _ZEBRA_ISIS_DR_H + +void isis_run_dr(struct event *thread); +int isis_dr_elect(struct isis_circuit *circuit, int level); +int isis_dr_resign(struct isis_circuit *circuit, int level); +int isis_dr_commence(struct isis_circuit *circuit, int level); +const char *isis_disflag2string(int disflag); + +enum isis_dis_state { + ISIS_IS_NOT_DIS, + ISIS_IS_DIS, + ISIS_WAS_DIS, + ISIS_UNKNOWN_DIS +}; + +#endif /* _ZEBRA_ISIS_DR_H */ diff --git a/isisd/isis_dynhn.c b/isisd/isis_dynhn.c new file mode 100644 index 0000000..61c49d0 --- /dev/null +++ b/isisd/isis_dynhn.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_dynhn.c + * Dynamic hostname cache + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "vty.h" +#include "linklist.h" +#include "memory.h" +#include "log.h" +#include "stream.h" +#include "command.h" +#include "if.h" +#include "frrevent.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_DYNHN, "ISIS dyn hostname"); + +static void dyn_cache_cleanup(struct event *); + +void dyn_cache_init(struct isis *isis) +{ + isis->dyn_cache = list_new(); + if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + event_add_timer(master, dyn_cache_cleanup, isis, 120, + &isis->t_dync_clean); +} + +void dyn_cache_finish(struct isis *isis) +{ + struct listnode *node, *nnode; + struct isis_dynhn *dyn; + + EVENT_OFF(isis->t_dync_clean); + + for (ALL_LIST_ELEMENTS(isis->dyn_cache, node, nnode, dyn)) { + list_delete_node(isis->dyn_cache, node); + XFREE(MTYPE_ISIS_DYNHN, dyn); + } + + list_delete(&isis->dyn_cache); +} + +static void dyn_cache_cleanup(struct event *thread) +{ + struct listnode *node, *nnode; + struct isis_dynhn *dyn; + time_t now = time(NULL); + struct isis *isis = NULL; + + isis = EVENT_ARG(thread); + + isis->t_dync_clean = NULL; + + for (ALL_LIST_ELEMENTS(isis->dyn_cache, node, nnode, dyn)) { + if ((now - dyn->refresh) < MAX_LSP_LIFETIME) + continue; + list_delete_node(isis->dyn_cache, node); + XFREE(MTYPE_ISIS_DYNHN, dyn); + } + + event_add_timer(master, dyn_cache_cleanup, isis, 120, + &isis->t_dync_clean); +} + +struct isis_dynhn *dynhn_find_by_id(struct isis *isis, const uint8_t *id) +{ + struct listnode *node = NULL; + struct isis_dynhn *dyn = NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) + if (memcmp(dyn->id, id, ISIS_SYS_ID_LEN) == 0) + return dyn; + + return NULL; +} + +struct isis_dynhn *dynhn_find_by_name(struct isis *isis, const char *hostname) +{ + struct listnode *node = NULL; + struct isis_dynhn *dyn = NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) + if (strncmp(dyn->hostname, hostname, 255) == 0) + return dyn; + + return NULL; +} + +void isis_dynhn_insert(struct isis *isis, const uint8_t *id, + const char *hostname, int level) +{ + struct isis_dynhn *dyn; + + dyn = dynhn_find_by_id(isis, id); + if (!dyn) { + dyn = XCALLOC(MTYPE_ISIS_DYNHN, sizeof(struct isis_dynhn)); + memcpy(dyn->id, id, ISIS_SYS_ID_LEN); + dyn->level = level; + listnode_add(isis->dyn_cache, dyn); + } + + snprintf(dyn->hostname, sizeof(dyn->hostname), "%s", hostname); + dyn->refresh = time(NULL); +} + +void isis_dynhn_remove(struct isis *isis, const uint8_t *id) +{ + struct isis_dynhn *dyn; + + dyn = dynhn_find_by_id(isis, id); + if (!dyn) + return; + listnode_delete(isis->dyn_cache, dyn); + XFREE(MTYPE_ISIS_DYNHN, dyn); +} + +/* + * Level System ID Dynamic Hostname (notag) + * 2 0000.0000.0001 foo-gw + * 2 0000.0000.0002 bar-gw + * * 0000.0000.0004 this-gw + */ +void dynhn_print_all(struct vty *vty, struct isis *isis) +{ + struct listnode *node; + struct isis_dynhn *dyn; + + vty_out(vty, "vrf : %s\n", isis->name); + if (!isis->sysid_set) + return; + vty_out(vty, "Level System ID Dynamic Hostname\n"); + for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) { + vty_out(vty, "%-7d", dyn->level); + vty_out(vty, "%pSY %-15s\n", dyn->id, dyn->hostname); + } + + vty_out(vty, " * %pSY %s\n", isis->sysid, cmd_hostname_get()); + return; +} + +struct isis_dynhn *dynhn_snmp_next(struct isis *isis, const uint8_t *id, + int level) +{ + struct listnode *node = NULL; + struct isis_dynhn *dyn = NULL; + struct isis_dynhn *found_dyn = NULL; + int res; + + for (ALL_LIST_ELEMENTS_RO(isis->dyn_cache, node, dyn)) { + res = memcmp(dyn->id, id, ISIS_SYS_ID_LEN); + + if (res < 0) + continue; + + if (res == 0 && dyn->level <= level) + continue; + + if (res == 0) { + /* + * This is the best match, we can stop + * searching + */ + + found_dyn = dyn; + break; + } + + if (found_dyn == NULL + || memcmp(dyn->id, found_dyn->id, ISIS_SYS_ID_LEN) < 0) { + found_dyn = dyn; + } + } + + return found_dyn; +} diff --git a/isisd/isis_dynhn.h b/isisd/isis_dynhn.h new file mode 100644 index 0000000..d4deb55 --- /dev/null +++ b/isisd/isis_dynhn.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_dynhn.h + * Dynamic hostname cache + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#ifndef _ZEBRA_ISIS_DYNHN_H +#define _ZEBRA_ISIS_DYNHN_H + +struct isis_dynhn { + uint8_t id[ISIS_SYS_ID_LEN]; + char hostname[256]; + time_t refresh; + int level; +}; + +void dyn_cache_init(struct isis *isis); +void dyn_cache_finish(struct isis *isis); +void isis_dynhn_insert(struct isis *isis, const uint8_t *id, + const char *hostname, int level); +void isis_dynhn_remove(struct isis *isis, const uint8_t *id); +struct isis_dynhn *dynhn_find_by_id(struct isis *isis, const uint8_t *id); +struct isis_dynhn *dynhn_find_by_name(struct isis *isis, const char *hostname); +void dynhn_print_all(struct vty *vty, struct isis *isis); + +/* Snmp support */ +struct isis_dynhn *dynhn_snmp_next(struct isis *isis, const uint8_t *id, + int level); + +#endif /* _ZEBRA_ISIS_DYNHN_H */ diff --git a/isisd/isis_errors.c b/isisd/isis_errors.c new file mode 100644 index 0000000..0bdff19 --- /dev/null +++ b/isisd/isis_errors.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISIS-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "isis_errors.h" + +/* clang-format off */ +static struct log_ref ferr_isis_err[] = { + { + .code = EC_ISIS_PACKET, + .title = "ISIS Packet Error", + .description = "Isis has detected an error with a packet from a peer", + .suggestion = "Gather log information and open an issue then restart FRR" + }, + { + .code = EC_ISIS_CONFIG, + .title = "ISIS Configuration Error", + .description = "Isis has detected an error within configuration for the router", + .suggestion = "Ensure configuration is correct" + }, + { + .code = EC_ISIS_SID_OVERFLOW, + .title = "SID index overflow", + .description = "Isis has detected that a SID index falls outside of its associated SRGB range", + .suggestion = "Configure a larger SRGB" + }, + { + .code = EC_ISIS_SID_COLLISION, + .title = "SID collision", + .description = "Isis has detected that two different prefixes share the same SID index", + .suggestion = "Identify the routers that are advertising the same SID index and fix the collision accordingly" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void isis_error_init(void) +{ + log_ref_add(ferr_isis_err); +} diff --git a/isisd/isis_errors.h b/isisd/isis_errors.h new file mode 100644 index 0000000..9a21c79 --- /dev/null +++ b/isisd/isis_errors.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISIS-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __ISIS_ERRORS_H__ +#define __ISIS_ERRORS_H__ + +#include "lib/ferr.h" + +enum isis_log_refs { + EC_ISIS_PACKET = ISIS_FERR_START, + EC_ISIS_CONFIG, + EC_ISIS_SID_OVERFLOW, + EC_ISIS_SID_COLLISION, +}; + +extern void isis_error_init(void); + +#endif diff --git a/isisd/isis_events.c b/isisd/isis_events.c new file mode 100644 index 0000000..32231a0 --- /dev/null +++ b/isisd/isis_events.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_events.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#include + +#include "log.h" +#include "memory.h" +#include "if.h" +#include "linklist.h" +#include "command.h" +#include "frrevent.h" +#include "hash.h" +#include "prefix.h" +#include "stream.h" +#include "table.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dr.h" +#include "isisd/isisd.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_events.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_errors.h" + +void isis_event_circuit_state_change(struct isis_circuit *circuit, + struct isis_area *area, int up) +{ + area->circuit_state_changes++; + + if (IS_DEBUG_EVENTS) + zlog_debug("ISIS-Evt (%s) circuit %s", area->area_tag, + up ? "up" : "down"); + + /* + * Regenerate LSPs this affects + */ + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return; +} + +static void circuit_commence_level(struct isis_circuit *circuit, int level) +{ + if (IS_DEBUG_EVENTS) + zlog_debug( + "ISIS-Evt (%s) circuit %u on iface %s commencing on L%d", + circuit->area->area_tag, circuit->circuit_id, + circuit->interface->name, level); + + if (!circuit->is_passive) { + if (level == 1) { + event_add_timer(master, send_l1_psnp, circuit, + isis_jitter(circuit->psnp_interval[0], + PSNP_JITTER), + &circuit->t_send_psnp[0]); + } else { + event_add_timer(master, send_l2_psnp, circuit, + isis_jitter(circuit->psnp_interval[1], + PSNP_JITTER), + &circuit->t_send_psnp[1]); + } + } + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + event_add_timer(master, isis_run_dr, + &circuit->level_arg[level - 1], + 2 * circuit->hello_interval[level - 1], + &circuit->u.bc.t_run_dr[level - 1]); + + send_hello_sched(circuit, level, TRIGGERED_IIH_DELAY); + circuit->u.bc.lan_neighs[level - 1] = list_new(); + } +} + +static void circuit_resign_level(struct isis_circuit *circuit, int level) +{ + int idx = level - 1; + + if (IS_DEBUG_EVENTS) + zlog_debug( + "ISIS-Evt (%s) circuit %u on iface %s resigning on L%d", + circuit->area->area_tag, circuit->circuit_id, + circuit->interface->name, level); + + EVENT_OFF(circuit->t_send_csnp[idx]); + EVENT_OFF(circuit->t_send_psnp[idx]); + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + EVENT_OFF(circuit->u.bc.t_send_lan_hello[idx]); + EVENT_OFF(circuit->u.bc.t_run_dr[idx]); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[idx]); + circuit->lsp_regenerate_pending[idx] = 0; + circuit->u.bc.run_dr_elect[idx] = 0; + circuit->u.bc.is_dr[idx] = 0; + if (circuit->u.bc.lan_neighs[idx] != NULL) + list_delete(&circuit->u.bc.lan_neighs[idx]); + } + + return; +} + +void isis_circuit_is_type_set(struct isis_circuit *circuit, int newtype) +{ + if (!circuit->area) { + circuit->is_type = newtype; + return; + } + + if (IS_DEBUG_EVENTS) + zlog_debug("ISIS-Evt (%s) circuit type change %s -> %s", + circuit->area->area_tag, + circuit_t2string(circuit->is_type), + circuit_t2string(newtype)); + + if (circuit->is_type == newtype) + return; /* No change */ + + if (!(newtype & circuit->area->is_type)) { + flog_err( + EC_ISIS_CONFIG, + "ISIS-Evt (%s) circuit type change - invalid level %s because area is %s", + circuit->area->area_tag, circuit_t2string(newtype), + circuit_t2string(circuit->area->is_type)); + return; + } + + if (circuit->state != C_STATE_UP) { + circuit->is_type = newtype; + return; + } + + if (!circuit->is_passive) { + switch (circuit->is_type) { + case IS_LEVEL_1: + if (newtype == IS_LEVEL_2) + circuit_resign_level(circuit, 1); + circuit_commence_level(circuit, 2); + break; + case IS_LEVEL_1_AND_2: + if (newtype == IS_LEVEL_1) + circuit_resign_level(circuit, 2); + else + circuit_resign_level(circuit, 1); + break; + case IS_LEVEL_2: + if (newtype == IS_LEVEL_1) + circuit_resign_level(circuit, 2); + circuit_commence_level(circuit, 1); + break; + default: + break; + } + } + + circuit->is_type = newtype; + lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return; +} + +/* 04/18/2002 by Gwak. */ +/************************************************************************** + * + * EVENTS for LSP generation + * + * 1) an Adajacency or Circuit Up/Down event + * 2) a chnage in Circuit metric + * 3) a change in Reachable Address metric + * 4) a change in manualAreaAddresses + * 5) a change in systemID + * 6) a change in DIS status + * 7) a chnage in the waiting status + * + * *********************************************************************** + * + * current support event + * + * 1) Adjacency Up/Down event + * 6) a change in DIS status + * + * ***********************************************************************/ + +/* events supporting code */ + +void isis_event_dis_status_change(struct event *thread) +{ + struct isis_circuit *circuit; + + circuit = EVENT_ARG(thread); + + /* invalid arguments */ + if (!circuit || !circuit->area) + return; + if (IS_DEBUG_EVENTS) + zlog_debug("ISIS-Evt (%s) DIS status change", + circuit->area->area_tag); + + /* LSP generation again */ + lsp_regenerate_schedule(circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); +} + +void isis_event_auth_failure(char *area_tag, const char *error_string, + uint8_t *sysid) +{ + if (IS_DEBUG_EVENTS) + zlog_debug("ISIS-Evt (%s) Authentication failure %s from %pSY", + area_tag, error_string, sysid); + + return; +} diff --git a/isisd/isis_events.h b/isisd/isis_events.h new file mode 100644 index 0000000..a0ac964 --- /dev/null +++ b/isisd/isis_events.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_events.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#ifndef _ZEBRA_ISIS_EVENTS_H +#define _ZEBRA_ISIS_EVENTS_H + +/* + * Events related to circuit + */ +void isis_event_circuit_state_change(struct isis_circuit *circuit, + struct isis_area *area, int state); +void isis_event_circuit_type_change(struct isis_circuit *circuit, int newtype); +/* + * Events related to adjacencies + */ +void isis_event_dis_status_change(struct event *thread); + +/* + * Error events + */ +#define AUTH_ERROR_TYPE_LSP 3 +#define AUTH_ERROR_TYPE_SNP 2 +#define AUTH_ERROR_TYPE_HELLO 1 +void isis_event_auth_failure(char *area_tag, const char *error_string, + uint8_t *sysid); + +#endif /* _ZEBRA_ISIS_EVENTS_H */ diff --git a/isisd/isis_flags.c b/isisd/isis_flags.c new file mode 100644 index 0000000..a621b4b --- /dev/null +++ b/isisd/isis_flags.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_flags.c + * Routines for manipulation of SSN and SRM flags + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include +#include "log.h" +#include "linklist.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" + +void flags_initialize(struct flags *flags) +{ + flags->maxindex = 0; + flags->free_idcs = NULL; +} + +long int flags_get_index(struct flags *flags) +{ + struct listnode *node; + long int index; + + if (flags->free_idcs == NULL || flags->free_idcs->count == 0) { + index = flags->maxindex++; + } else { + node = listhead(flags->free_idcs); + index = (long int)listgetdata(node); + listnode_delete(flags->free_idcs, (void *)index); + index--; + } + + return index; +} + +void flags_free_index(struct flags *flags, long int index) +{ + if (index + 1 == flags->maxindex) { + flags->maxindex--; + return; + } + + if (flags->free_idcs == NULL) { + flags->free_idcs = list_new(); + } + + listnode_add(flags->free_idcs, (void *)(index + 1)); + + return; +} + +int flags_any_set(uint32_t *flags) +{ + uint32_t zero[ISIS_MAX_CIRCUITS]; + memset(zero, 0x00, ISIS_MAX_CIRCUITS * 4); + + return bcmp(flags, zero, ISIS_MAX_CIRCUITS * 4); +} diff --git a/isisd/isis_flags.h b/isisd/isis_flags.h new file mode 100644 index 0000000..47a1356 --- /dev/null +++ b/isisd/isis_flags.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_flags.h + * Routines for manipulation of SSN and SRM flags + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_FLAGS_H +#define _ZEBRA_ISIS_FLAGS_H + +/* The grand plan is to support 1024 circuits so we have 32*32 bit flags + * the support will be achived using the newest drafts */ +#define ISIS_MAX_CIRCUITS 32 /* = 1024 */ + +/* + * Flags structure for SSN and SRM flags + */ +struct flags { + int maxindex; + struct list *free_idcs; +}; + +void flags_initialize(struct flags *flags); +long int flags_get_index(struct flags *flags); +void flags_free_index(struct flags *flags, long int index); +int flags_any_set(uint32_t *flags); + +#define _ISIS_SET_FLAG(F, C) \ + { \ + F[(C) >> 5] |= (1 << ((C)&0x1F)); \ + } +#define ISIS_SET_FLAG(F, C) _ISIS_SET_FLAG(F, C->idx) + +#define _ISIS_CLEAR_FLAG(F, C) \ + { \ + F[(C) >> 5] &= ~(1 << ((C)&0x1F)); \ + } +#define ISIS_CLEAR_FLAG(F, C) _ISIS_CLEAR_FLAG(F, C->idx) + +#define _ISIS_CHECK_FLAG(F, C) (F[(C)>>5] & (1<<((C) & 0x1F))) +#define ISIS_CHECK_FLAG(F, C) _ISIS_CHECK_FLAG(F, C->idx) + +/* sets all u_32int_t flags to 1 */ +#define ISIS_FLAGS_SET_ALL(FLAGS) \ + { \ + memset(FLAGS, 0xFF, ISIS_MAX_CIRCUITS * 4); \ + } + +#define ISIS_FLAGS_CLEAR_ALL(FLAGS) \ + { \ + memset(FLAGS, 0x00, ISIS_MAX_CIRCUITS * 4); \ + } + +#endif /* _ZEBRA_ISIS_FLAGS_H */ diff --git a/isisd/isis_flex_algo.c b/isisd/isis_flex_algo.c new file mode 100644 index 0000000..fbe249a --- /dev/null +++ b/isisd/isis_flex_algo.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * isis_flex_algo.c: IS-IS Flexible Algorithm + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#include + +#include "memory.h" +#include "stream.h" +#include "sbuf.h" +#include "network.h" +#include "command.h" +#include "bitfield.h" + +#include "isisd/isisd.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_common.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_te.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_flex_algo.h" + +#ifndef FABRICD +DEFINE_MTYPE_STATIC(ISISD, FLEX_ALGO, "ISIS Flex Algo"); + +void *isis_flex_algo_data_alloc(void *voidarg) +{ + struct isis_flex_algo_alloc_arg *arg = voidarg; + struct isis_flex_algo_data *data; + + data = XCALLOC(MTYPE_FLEX_ALGO, sizeof(struct isis_flex_algo_data)); + + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(arg->area->is_type & level)) + continue; + data->spftree[tree][level - 1] = isis_spftree_new( + arg->area, &arg->area->lspdb[level - 1], + arg->area->isis->sysid, level, tree, + SPF_TYPE_FORWARD, 0, arg->algorithm); + } + } + + return data; +} + +void isis_flex_algo_data_free(void *voiddata) +{ + struct isis_flex_algo_data *data = voiddata; + + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) + if (data->spftree[tree][level - 1]) + isis_spftree_del( + data->spftree[tree][level - 1]); + XFREE(MTYPE_FLEX_ALGO, data); +} + +static struct isis_router_cap_fad * +isis_flex_algo_definition_cmp(struct isis_router_cap_fad *elected, + struct isis_router_cap_fad *fa) +{ + if (!elected || fa->fad.priority > elected->fad.priority || + (fa->fad.priority == elected->fad.priority && + lsp_id_cmp(fa->sysid, elected->sysid) > 0)) + return fa; + + return elected; +} + +/** + * @brief Look up the flex-algo definition with the highest priority in the LSP + * Database (LSDB). If the value of priority is the same, the flex-algo + * definition with the highest sysid will be selected. + * @param algorithm flex-algo algorithm number + * @param area pointer + * @param local router capability Flex-Algo Definition (FAD) double pointer. + * - fad is NULL: use the local router capability FAD from LSDB for the + * election. + * - fad is not NULL and *fad is NULL: use no local router capability FAD for + * the election. + * - fad and *fad are not NULL: uses the *fad local definition instead of the + * local definition from LSDB for the election. + * @return elected flex-algo-definition object if exist, else NULL + */ +static struct isis_router_cap_fad * +_isis_flex_algo_elected(int algorithm, const struct isis_area *area, + struct isis_router_cap_fad **fad) +{ + struct flex_algo *flex_ago; + const struct isis_lsp *lsp; + struct isis_router_cap_fad *fa, *elected = NULL; + + if (!flex_algo_id_valid(algorithm)) + return NULL; + + /* No elected FAD if the algorithm is not locally configured */ + flex_ago = flex_algo_lookup(area->flex_algos, algorithm); + if (!flex_ago) + return NULL; + + /* No elected FAD if no data-plane is enabled + * Currently, only Segment-Routing MPLS is supported. + * Segment-Routing SRv6 and IP will be configured in the future. + */ + if (!CHECK_FLAG(flex_ago->dataplanes, FLEX_ALGO_SR_MPLS)) + return NULL; + + /* + * Perform FAD comparison. First, compare the priority, and if they are + * the same, compare the sys-id. + */ + frr_each (lspdb_const, &area->lspdb[ISIS_LEVEL1 - 1], lsp) { + if (!lsp->tlvs || !lsp->tlvs->router_cap) + continue; + + if (lsp->own_lsp && fad) + continue; + + fa = lsp->tlvs->router_cap->fads[algorithm]; + + if (!fa) + continue; + + assert(algorithm == fa->fad.algorithm); + + memcpy(fa->sysid, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2); + + elected = isis_flex_algo_definition_cmp(elected, fa); + } + + if (fad && *fad) + elected = isis_flex_algo_definition_cmp(elected, *fad); + + return elected; +} + +struct isis_router_cap_fad *isis_flex_algo_elected(int algorithm, + const struct isis_area *area) +{ + return _isis_flex_algo_elected(algorithm, area, NULL); +} + +/** + * @brief Check the Flex-Algo Definition is supported by the current FRR version + * @param flex-algo + * @return true if supported else false + */ +bool isis_flex_algo_supported(struct flex_algo *fad) +{ + if (fad->calc_type != CALC_TYPE_SPF) + return false; + if (fad->metric_type != MT_IGP) + return false; + if (fad->flags != 0) + return false; + if (fad->exclude_srlg) + return false; + if (fad->unsupported_subtlv) + return false; + + return true; +} + +/** + * @brief Look for the elected Flex-Algo Definition and check that it is + * supported by the current FRR version + * @param algorithm flex-algo algorithm number + * @param area pointer + * @param local router capability Flex-Algo Definition (FAD) double pointer. + * @return elected flex-algo-definition object if exist and supported, else NULL + */ +static struct isis_router_cap_fad * +_isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area, + struct isis_router_cap_fad **fad) +{ + struct isis_router_cap_fad *elected_fad; + + elected_fad = _isis_flex_algo_elected(algorithm, area, fad); + if (!elected_fad) + return NULL; + + if (isis_flex_algo_supported(&elected_fad->fad)) + return elected_fad; + + return NULL; +} + +struct isis_router_cap_fad * +isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area) +{ + return _isis_flex_algo_elected_supported(algorithm, area, NULL); +} + +struct isis_router_cap_fad * +isis_flex_algo_elected_supported_local_fad(int algorithm, + const struct isis_area *area, + struct isis_router_cap_fad **fad) +{ + return _isis_flex_algo_elected_supported(algorithm, area, fad); +} + +/** + * Check LSP is participating specified SR Algorithm + * + * @param lsp IS-IS lsp + * @param algorithm SR Algorithm + * @return Return true if sr-algorithm tlv includes specified + * algorithm in router capability tlv + */ +bool sr_algorithm_participated(const struct isis_lsp *lsp, uint8_t algorithm) +{ + if (!lsp || !lsp->tlvs || !lsp->tlvs->router_cap) + return false; + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (lsp->tlvs->router_cap->algo[i] == algorithm) + return true; + return false; +} + +bool isis_flex_algo_constraint_drop(struct isis_spftree *spftree, + struct isis_lsp *lsp, + struct isis_extended_reach *reach) +{ + bool ret; + struct isis_ext_subtlvs *subtlvs = reach->subtlvs; + struct isis_router_cap_fad *fad; + struct isis_asla_subtlvs *asla; + struct listnode *node; + uint32_t *link_admin_group = NULL; + uint32_t link_ext_admin_group_bitmap0; + struct admin_group *link_ext_admin_group = NULL; + + fad = isis_flex_algo_elected_supported(spftree->algorithm, + spftree->area); + if (!fad) + return true; + + for (ALL_LIST_ELEMENTS_RO(subtlvs->aslas, node, asla)) { + if (!CHECK_FLAG(asla->standard_apps, ISIS_SABM_FLAG_X)) + continue; + if (asla->legacy) { + if (IS_SUBTLV(subtlvs, EXT_ADM_GRP)) + link_admin_group = &subtlvs->adm_group; + + if (IS_SUBTLV(subtlvs, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&subtlvs->ext_admin_group) != + 0) + link_ext_admin_group = + &subtlvs->ext_admin_group; + } else { + if (IS_SUBTLV(asla, EXT_ADM_GRP)) + link_admin_group = &asla->admin_group; + if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&asla->ext_admin_group) != 0) + link_ext_admin_group = &asla->ext_admin_group; + } + break; + } + + /* RFC7308 section 2.3.1 + * A receiving node that notices that the AG differs from the first 32 + * bits of the EAG SHOULD report this mismatch to the operator. + */ + if (link_admin_group && link_ext_admin_group) { + link_ext_admin_group_bitmap0 = + admin_group_get_offset(link_ext_admin_group, 0); + if (*link_admin_group != link_ext_admin_group_bitmap0) + zlog_warn( + "ISIS-SPF: LSP from %pPN neighbor %pPN. Admin-group 0x%08x differs from ext admin-group 0x%08x.", + lsp->hdr.lsp_id, reach->id, *link_admin_group, + link_ext_admin_group_bitmap0); + } + + /* + * Exclude Any + */ + if (!admin_group_zero(&fad->fad.admin_group_exclude_any)) { + ret = admin_group_match_any(&fad->fad.admin_group_exclude_any, + link_admin_group, + link_ext_admin_group); + if (ret) + return true; + } + + /* + * Include Any + */ + if (!admin_group_zero(&fad->fad.admin_group_include_any)) { + ret = admin_group_match_any(&fad->fad.admin_group_include_any, + link_admin_group, + link_ext_admin_group); + if (!ret) + return true; + } + + /* + * Include All + */ + if (!admin_group_zero(&fad->fad.admin_group_include_all)) { + ret = admin_group_match_all(&fad->fad.admin_group_include_all, + link_admin_group, + link_ext_admin_group); + if (!ret) + return true; + } + + return false; +} + +#endif /* ifndef FABRICD */ diff --git a/isisd/isis_flex_algo.h b/isisd/isis_flex_algo.h new file mode 100644 index 0000000..c475838 --- /dev/null +++ b/isisd/isis_flex_algo.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * isis_flex_algo.h: IS-IS Flexible Algorithm + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#ifndef ISIS_FLEX_ALGO_H +#define ISIS_FLEX_ALGO_H + +#include "flex_algo.h" +#include "isisd/isis_constants.h" + +#ifndef FABRICD + +struct isis_flex_algo_data { + struct isis_spftree *spftree[SPFTREE_COUNT][ISIS_LEVELS]; + struct isis_area *area; +}; + +struct isis_flex_algo_alloc_arg { + uint8_t algorithm; + struct isis_area *area; +}; + +void *isis_flex_algo_data_alloc(void *arg); +void isis_flex_algo_data_free(void *data); + +struct isis_router_cap_fad * +isis_flex_algo_elected(int algorithm, const struct isis_area *area); +bool isis_flex_algo_supported(struct flex_algo *fad); +struct isis_router_cap_fad * +isis_flex_algo_elected_supported(int algorithm, const struct isis_area *area); +struct isis_router_cap_fad * +isis_flex_algo_elected_supported_local_fad(int algorithm, + const struct isis_area *area, + struct isis_router_cap_fad **fad); +struct isis_lsp; +bool sr_algorithm_participated(const struct isis_lsp *lsp, uint8_t algorithm); + +bool isis_flex_algo_constraint_drop(struct isis_spftree *spftree, + struct isis_lsp *lsp, + struct isis_extended_reach *reach); + +#endif /* ifndef FABRICD */ + +#endif /* ISIS_FLEX_ALGO_H */ diff --git a/isisd/isis_ldp_sync.c b/isisd/isis_ldp_sync.c new file mode 100644 index 0000000..53676ff --- /dev/null +++ b/isisd/isis_ldp_sync.c @@ -0,0 +1,655 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * isis_ldp_sync.c: ISIS LDP-IGP Sync handling routines + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#include +#include + +#include "monotime.h" +#include "memory.h" +#include "frrevent.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "plist.h" +#include "log.h" +#include "zclient.h" +#include +#include "defaults.h" +#include "ldp_sync.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dr.h" +#include "isisd/isisd.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_events.h" +#include "isisd/isis_te.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_errors.h" +#include "isisd/isis_tx_queue.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_ldp_sync.h" + +extern struct zclient *zclient; + +/* + * LDP-SYNC msg between IGP and LDP + */ +int isis_ldp_sync_state_update(struct ldp_igp_sync_if_state state) +{ + struct interface *ifp; + struct isis_circuit *circuit = NULL; + struct isis_area *area; + + /* lookup circuit */ + ifp = if_lookup_by_index(state.ifindex, VRF_DEFAULT); + if (ifp == NULL) + return 0; + + circuit = ifp->info; + if (circuit == NULL) + return 0; + + /* if isis is not enabled or LDP-SYNC is not configured ignore */ + area = circuit->area; + if (area == NULL + || !CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return 0; + + /* received ldp-sync interface state from LDP */ + ils_debug("%s: rcvd %s from LDP if %s", __func__, + state.sync_start ? "sync-start" : "sync-complete", ifp->name); + if (state.sync_start) + isis_ldp_sync_if_start(circuit, false); + else + isis_ldp_sync_if_complete(circuit); + + return 0; +} + +int isis_ldp_sync_announce_update(struct ldp_igp_sync_announce announce) +{ + struct isis_area *area; + struct listnode *anode, *cnode; + struct isis_circuit *circuit; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + /* if isis is not enabled ignore */ + if (!isis) + return 0; + + if (announce.proto != ZEBRA_ROUTE_LDP) + return 0; + + ils_debug("%s: rcvd announce from LDP", __func__); + + /* LDP just started up: + * set cost to LSInfinity + * send request to LDP for LDP-SYNC state for each interface + */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + continue; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + isis_ldp_sync_if_start(circuit, true); + } + + return 0; +} + +void isis_ldp_sync_state_req_msg(struct isis_circuit *circuit) +{ + struct ldp_igp_sync_if_state_req request; + struct interface *ifp = circuit->interface; + + ils_debug("%s: send state request to LDP for %s", __func__, ifp->name); + + memset(&request, 0, sizeof(request)); + strlcpy(request.name, ifp->name, sizeof(ifp->name)); + request.proto = LDP_IGP_SYNC_IF_STATE_REQUEST; + request.ifindex = ifp->ifindex; + + zclient_send_opaque(zclient, LDP_IGP_SYNC_IF_STATE_REQUEST, + (uint8_t *)&request, sizeof(request)); +} + +/* + * LDP-SYNC general interface routines + */ +void isis_ldp_sync_if_start(struct isis_circuit *circuit, + bool send_state_req) +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = circuit->ldp_sync_info; + + /* Start LDP-SYNC on this interface: + * set cost of interface to LSInfinity so traffic will use different + * interface until LDP has learned all labels from peer + * start holddown timer if configured + * send msg to LDP to get LDP-SYNC state + */ + if (ldp_sync_info && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED && + ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) { + ils_debug("%s: start on if %s state: %s", __func__, + circuit->interface->name, "Holding down until Sync"); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + isis_ldp_sync_set_if_metric(circuit, true); + isis_ldp_sync_holddown_timer_add(circuit); + + if (send_state_req) + isis_ldp_sync_state_req_msg(circuit); + } +} + +void isis_ldp_sync_if_complete(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = circuit->ldp_sync_info; + + /* received sync-complete from LDP: + * set state to up + * stop timer + * restore interface cost to original value + */ + if (ldp_sync_info && ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) { + if (ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP) + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP; + + EVENT_OFF(ldp_sync_info->t_holddown); + + isis_ldp_sync_set_if_metric(circuit, true); + } +} + +void isis_ldp_sync_ldp_fail(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = circuit->ldp_sync_info; + + /* LDP client close detected: + * stop holddown timer + * set cost of interface to LSInfinity so traffic will use different + * interface until LDP restarts and has learned all labels from peer + */ + if (ldp_sync_info && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED && + ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) { + EVENT_OFF(ldp_sync_info->t_holddown); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + isis_ldp_sync_set_if_metric(circuit, true); + } +} + +static int isis_ldp_sync_adj_state_change(struct isis_adjacency *adj) +{ + struct isis_circuit *circuit = adj->circuit; + struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info; + struct isis_area *area = circuit->area; + + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE) + || circuit->interface->vrf->vrf_id != VRF_DEFAULT + || if_is_loopback(circuit->interface)) + return 0; + + if (ldp_sync_info->enabled != LDP_IGP_SYNC_ENABLED) + return 0; + + if (adj->adj_state == ISIS_ADJ_UP) { + if (circuit->circ_type == CIRCUIT_T_P2P || + if_is_pointopoint(circuit->interface)) { + /* If LDP-SYNC is configure on interface then start */ + ldp_sync_info->state = + LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + isis_ldp_sync_if_start(circuit, true); + } else { + /* non ptop link so don't run ldp-sync */ + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + isis_ldp_sync_set_if_metric(circuit, true); + } + } else { + /* If LDP-SYNC is configure on this interface then stop it */ + if (circuit->circ_type == CIRCUIT_T_P2P || + if_is_pointopoint(circuit->interface)) + ldp_sync_info->state = + LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + else + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + + ils_debug("%s: down on if %s", __func__, + circuit->interface->name); + ldp_sync_if_down(circuit->ldp_sync_info); + } + + return 0; +} + +bool isis_ldp_sync_if_metric_config(struct isis_circuit *circuit, int level, + int metric) +{ + struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info; + struct isis_area *area = circuit->area; + + /* configured interface metric has been changed: + * if LDP-IGP Sync is running and metric has been set to LSInfinity + * change saved value so when ldp-sync completes proper metric is + * restored + */ + if (area && CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE) + && ldp_sync_info != NULL) { + + if (CHECK_FLAG(ldp_sync_info->flags, + LDP_SYNC_FLAG_SET_METRIC)) { + ldp_sync_info->metric[level-1] = metric; + ldp_sync_info->metric[level-1] = metric; + return false; + } + } + return true; +} + +void isis_ldp_sync_set_if_metric(struct isis_circuit *circuit, bool run_regen) +{ + struct ldp_sync_info *ldp_sync_info; + + /* set interface metric: + * if LDP-IGP Sync is starting set metric so interface + * is used only as last resort + * else restore metric to original value + */ + if (circuit->ldp_sync_info == NULL || circuit->area == NULL) + return; + + ldp_sync_info = circuit->ldp_sync_info; + if (ldp_sync_if_is_enabled(ldp_sync_info)) { + /* if metric already set to LSInfinity just return */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC)) + return; + + SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC); + if (circuit->is_type & IS_LEVEL_1) { + if (circuit->area->newmetric) { + ldp_sync_info->metric[0] = + circuit->te_metric[0]; + circuit->te_metric[0] = + ISIS_WIDE_METRIC_INFINITY; + } else { + ldp_sync_info->metric[0] = circuit->metric[0]; + circuit->metric[0] = + ISIS_NARROW_METRIC_INFINITY; + } + } + if (circuit->is_type & IS_LEVEL_2) { + if (circuit->area->newmetric) { + ldp_sync_info->metric[1] = + circuit->te_metric[1]; + circuit->te_metric[1] = + ISIS_WIDE_METRIC_INFINITY; + } else { + ldp_sync_info->metric[1] = circuit->metric[1]; + circuit->metric[1] = + ISIS_NARROW_METRIC_INFINITY; + } + } + } else { + /* if metric already restored just return */ + if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC)) + return; + + UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_SET_METRIC); + if (circuit->is_type & IS_LEVEL_1) { + circuit->te_metric[0] = ldp_sync_info->metric[0]; + circuit->metric[0] = ldp_sync_info->metric[0]; + } + if (circuit->is_type & IS_LEVEL_2) { + circuit->te_metric[1] = ldp_sync_info->metric[1]; + circuit->metric[1] = ldp_sync_info->metric[1]; + } + } + + if (run_regen) + lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); +} + + +/* + * LDP-SYNC holddown timer routines + */ +static void isis_ldp_sync_holddown_timer(struct event *thread) +{ + struct isis_circuit *circuit; + struct ldp_sync_info *ldp_sync_info; + + /* holddown timer expired: + * didn't receive msg from LDP indicating sync-complete + * restore interface cost to original value + */ + circuit = EVENT_ARG(thread); + if (circuit->ldp_sync_info == NULL) + return; + + ldp_sync_info = circuit->ldp_sync_info; + + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP; + ldp_sync_info->t_holddown = NULL; + + ils_debug("%s: holddown timer expired for %s state:sync achieved", + __func__, circuit->interface->name); + + isis_ldp_sync_set_if_metric(circuit, true); +} + +void isis_ldp_sync_holddown_timer_add(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = circuit->ldp_sync_info; + + /* Start holddown timer: + * this timer is used to keep interface cost at LSInfinity + * once expires returns cost to original value + * if timer is already running or holddown time is off just return + */ + if (ldp_sync_info->t_holddown || + ldp_sync_info->holddown == LDP_IGP_SYNC_HOLDDOWN_DEFAULT) + return; + + ils_debug("%s: start holddown timer for %s time %d", __func__, + circuit->interface->name, ldp_sync_info->holddown); + + event_add_timer(master, isis_ldp_sync_holddown_timer, circuit, + ldp_sync_info->holddown, &ldp_sync_info->t_holddown); +} + +/* + * LDP-SYNC handle client close routine + */ +void isis_ldp_sync_handle_client_close(struct zapi_client_close_info *info) +{ + struct isis_area *area; + struct listnode *anode, *cnode; + struct isis_circuit *circuit; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + /* if isis is not enabled ignore */ + if (!isis) + return; + + /* Check if the LDP main client session closed */ + if (info->proto != ZEBRA_ROUTE_LDP || info->session_id == 0) + return; + + /* Handle the zebra notification that the LDP client session closed. + * set cost to LSInfinity + * send request to LDP for LDP-SYNC state for each interface + */ + zlog_err("%s: LDP down", __func__); + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + continue; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + isis_ldp_sync_ldp_fail(circuit); + } +} + +/* + * LDP-SYNC routes used by set commands. + */ + +void isis_area_ldp_sync_enable(struct isis_area *area) +{ + struct isis_circuit *circuit; + struct listnode *node; + + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + SET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE); + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_if_ldp_sync_enable(circuit); + } +} + +void isis_area_ldp_sync_disable(struct isis_area *area) +{ + struct isis_circuit *circuit; + struct listnode *node; + + if (CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_if_ldp_sync_disable(circuit); + + UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE); + + UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + area->ldp_sync_cmd.holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; + } +} + +void isis_area_ldp_sync_set_holddown(struct isis_area *area, uint16_t holddown) +{ + struct isis_circuit *circuit; + struct listnode *node; + + if (holddown == LDP_IGP_SYNC_HOLDDOWN_DEFAULT) + UNSET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + else + SET_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + + area->ldp_sync_cmd.holddown = holddown; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_if_set_ldp_sync_holddown(circuit); +} + +void isis_if_ldp_sync_enable(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info; + struct isis_area *area = circuit->area; + + /* called when setting LDP-SYNC at the global level: + * specified on interface overrides global config + * if ptop link send msg to LDP indicating ldp-sync enabled + */ + if (if_is_loopback(circuit->interface)) + return; + + if (circuit->interface->vrf->vrf_id != VRF_DEFAULT) + return; + + ils_debug("%s: enable if %s", __func__, circuit->interface->name); + + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return; + + /* config on interface, overrides global config. */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG)) + if (ldp_sync_info->enabled != LDP_IGP_SYNC_ENABLED) + return; + + if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) + ldp_sync_info->holddown = area->ldp_sync_cmd.holddown; + + if (circuit->circ_type == CIRCUIT_T_P2P + || if_is_pointopoint(circuit->interface)) { + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + isis_ldp_sync_state_req_msg(circuit); + } else { + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + ils_debug("%s: Sync only runs on P2P links %s", __func__, + circuit->interface->name); + } +} + +void isis_if_ldp_sync_disable(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info; + struct isis_area *area = circuit->area; + + /* Stop LDP-SYNC on this interface: + * if holddown timer is running stop it + * delete ldp instance on interface + * restore metric + */ + if (if_is_loopback(circuit->interface)) + return; + + ils_debug("%s: remove if %s", __func__, circuit->interface->name); + + if (!CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return; + + EVENT_OFF(ldp_sync_info->t_holddown); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + isis_ldp_sync_set_if_metric(circuit, true); +} + +void isis_if_set_ldp_sync_holddown(struct isis_circuit *circuit) +{ + struct ldp_sync_info *ldp_sync_info = circuit->ldp_sync_info; + struct isis_area *area = circuit->area; + + /* called when setting LDP-SYNC at the global level: + * specified on interface overrides global config. + */ + if (if_is_loopback(circuit->interface)) + return; + + /* config on interface, overrides global config. */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) + return; + if (CHECK_FLAG(area->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN)) + ldp_sync_info->holddown = area->ldp_sync_cmd.holddown; + else + ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; +} + +/* + * LDP-SYNC routines used by show commands. + */ + +static void isis_circuit_ldp_sync_print_vty(struct isis_circuit *circuit, + struct vty *vty) +{ + struct ldp_sync_info *ldp_sync_info; + const char *ldp_state; + + if (circuit->ldp_sync_info == NULL || + if_is_loopback(circuit->interface)) + return; + + ldp_sync_info = circuit->ldp_sync_info; + vty_out(vty, "%-16s\n", circuit->interface->name); + if (circuit->state == C_STATE_CONF) { + vty_out(vty, " Interface down\n"); + return; + } + + vty_out(vty, " LDP-IGP Synchronization enabled: %s\n", + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED + ? "yes" + : "no"); + vty_out(vty, " holddown timer in seconds: %u\n", + ldp_sync_info->holddown); + + switch (ldp_sync_info->state) { + case LDP_IGP_SYNC_STATE_REQUIRED_UP: + vty_out(vty, " State: Sync achieved\n"); + break; + case LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP: + if (ldp_sync_info->t_holddown != NULL) { + struct timeval remain = + event_timer_remain(ldp_sync_info->t_holddown); + vty_out(vty, + " Holddown timer is running %lld.%03lld remaining\n", + (long long)remain.tv_sec, + (long long)remain.tv_usec/1000); + + vty_out(vty, " State: Holding down until Sync\n"); + } else + vty_out(vty, " State: Sync not achieved\n"); + break; + case LDP_IGP_SYNC_STATE_NOT_REQUIRED: + default: + if ((circuit->circ_type != CIRCUIT_T_P2P && + !if_is_pointopoint(circuit->interface)) && + circuit->circ_type != CIRCUIT_T_UNKNOWN) + ldp_state = "Sync not required: non-p2p link"; + else + ldp_state = "Sync not required"; + vty_out(vty, " State: %s\n", ldp_state); + break; + } +} + +DEFUN (show_isis_mpls_ldp_interface, + show_isis_mpls_ldp_interface_cmd, + "show " PROTO_NAME " mpls ldp-sync [interface ]", + SHOW_STR + PROTO_HELP + MPLS_STR + "LDP-IGP Sync information\n" + "Interface information\n" + "Interface name\n" + "All interfaces\n") +{ + char *ifname = NULL; + int idx_intf = 0; + struct listnode *anode, *cnode; + struct isis_area *area; + struct isis_circuit *circuit; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + bool found = false; + + if (!isis) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (argv_find(argv, argc, "INTERFACE", &idx_intf)) + ifname = argv[idx_intf]->arg; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + if (!ifname) + isis_circuit_ldp_sync_print_vty(circuit, vty); + else if (strcmp(circuit->interface->name, ifname) + == 0) { + isis_circuit_ldp_sync_print_vty(circuit, vty); + found = true; + } + } + + if (found == false && ifname) + vty_out(vty, "%-16s\n ISIS not enabled\n", ifname); + + return CMD_SUCCESS; +} + +void isis_ldp_sync_init(void) +{ + + /* "show ip isis mpls ldp interface" commands. */ + install_element(VIEW_NODE, &show_isis_mpls_ldp_interface_cmd); + + /* register for adjacency state changes */ + hook_register(isis_adj_state_change_hook, + isis_ldp_sync_adj_state_change); +} diff --git a/isisd/isis_ldp_sync.h b/isisd/isis_ldp_sync.h new file mode 100644 index 0000000..d593ed3 --- /dev/null +++ b/isisd/isis_ldp_sync.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * isis_ldp_sync.h: ISIS LDP-IGP Sync handling routines + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#ifndef _ZEBRA_ISIS_LDP_SYNC_H +#define _ZEBRA_ISIS_LDP_SYNC_H + +#include "zclient.h" + +/* Macro to log debug message */ +#define ils_debug(...) \ + do { \ + if (IS_DEBUG_LDP_SYNC) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +extern void isis_area_ldp_sync_enable(struct isis_area *area); +extern void isis_area_ldp_sync_disable(struct isis_area *area); +extern void isis_area_ldp_sync_set_holddown(struct isis_area *area, + uint16_t holddown); +extern void isis_if_ldp_sync_enable(struct isis_circuit *circuit); +extern void isis_if_ldp_sync_disable(struct isis_circuit *circuit); +extern void isis_if_set_ldp_sync_holddown(struct isis_circuit *circuit); +extern void isis_ldp_sync_if_start(struct isis_circuit *circuit, + bool send_state_req); +extern void isis_ldp_sync_if_complete(struct isis_circuit *circuit); +extern void isis_ldp_sync_holddown_timer_add(struct isis_circuit *circuit); +extern void +isis_ldp_sync_handle_client_close(struct zapi_client_close_info *info); +extern void isis_ldp_sync_ldp_fail(struct isis_circuit *circuit); +extern int isis_ldp_sync_state_update(struct ldp_igp_sync_if_state state); +extern int isis_ldp_sync_announce_update(struct ldp_igp_sync_announce announce); +extern void isis_ldp_sync_state_req_msg(struct isis_circuit *circuit); +extern void isis_ldp_sync_set_if_metric(struct isis_circuit *circuit, + bool run_regen); +extern bool isis_ldp_sync_if_metric_config(struct isis_circuit *circuit, + int level, int metric); +extern void isis_ldp_sync_init(void); +#endif /* _ZEBRA_ISIS_LDP_SYNC_H */ diff --git a/isisd/isis_lfa.c b/isisd/isis_lfa.c new file mode 100644 index 0000000..dc8f0b9 --- /dev/null +++ b/isisd/isis_lfa.c @@ -0,0 +1,2372 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "linklist.h" +#include "log.h" +#include "memory.h" +#include "vrf.h" +#include "table.h" +#include "srcdest_table.h" +#include "plist.h" +#include "zclient.h" + +#include "isis_common.h" +#include "isisd.h" +#include "isis_misc.h" +#include "isis_adjacency.h" +#include "isis_circuit.h" +#include "isis_lsp.h" +#include "isis_spf.h" +#include "isis_route.h" +#include "isis_mt.h" +#include "isis_tlvs.h" +#include "isis_spf_private.h" +#include "isis_zebra.h" +#include "isis_errors.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_NODE, "ISIS SPF Node"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_LFA_TIEBREAKER, "ISIS LFA Tiebreaker"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_LFA_EXCL_IFACE, "ISIS LFA Excluded Interface"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_RLFA, "ISIS Remote LFA"); +DEFINE_MTYPE(ISISD, ISIS_NEXTHOP_LABELS, "ISIS nexthop MPLS labels"); + +static inline int isis_spf_node_compare(const struct isis_spf_node *a, + const struct isis_spf_node *b) +{ + return memcmp(a->sysid, b->sysid, sizeof(a->sysid)); +} +RB_GENERATE(isis_spf_nodes, isis_spf_node, entry, isis_spf_node_compare) + +/** + * Initialize list of SPF nodes. + * + * @param nodes List of SPF nodes + */ +void isis_spf_node_list_init(struct isis_spf_nodes *nodes) +{ + RB_INIT(isis_spf_nodes, nodes); +} + +/** + * Clear list of SPF nodes, releasing all allocated memory. + * + * @param nodes List of SPF nodes + */ +void isis_spf_node_list_clear(struct isis_spf_nodes *nodes) +{ + while (!RB_EMPTY(isis_spf_nodes, nodes)) { + struct isis_spf_node *node = RB_ROOT(isis_spf_nodes, nodes); + + if (node->adjacencies) + list_delete(&node->adjacencies); + if (node->lfa.spftree) + isis_spftree_del(node->lfa.spftree); + if (node->lfa.spftree_reverse) + isis_spftree_del(node->lfa.spftree_reverse); + isis_spf_node_list_clear(&node->lfa.p_space); + RB_REMOVE(isis_spf_nodes, nodes, node); + XFREE(MTYPE_ISIS_SPF_NODE, node); + } +} + +/** + * Add new node to list of SPF nodes. + * + * @param nodes List of SPF nodes + * @param sysid Node System ID + * + * @return Pointer to new IS-IS SPF node structure. + */ +struct isis_spf_node *isis_spf_node_new(struct isis_spf_nodes *nodes, + const uint8_t *sysid) +{ + struct isis_spf_node *node; + + node = XCALLOC(MTYPE_ISIS_SPF_NODE, sizeof(*node)); + memcpy(node->sysid, sysid, sizeof(node->sysid)); + node->adjacencies = list_new(); + isis_spf_node_list_init(&node->lfa.p_space); + RB_INSERT(isis_spf_nodes, nodes, node); + + return node; +} + +/** + * Lookup SPF node by its System ID on the given list. + * + * @param nodes List of SPF nodes + * @param sysid Node System ID + * + * @return Pointer to SPF node if found, NULL otherwise + */ +struct isis_spf_node *isis_spf_node_find(const struct isis_spf_nodes *nodes, + const uint8_t *sysid) +{ + struct isis_spf_node node = {}; + + memcpy(node.sysid, sysid, sizeof(node.sysid)); + return RB_FIND(isis_spf_nodes, nodes, &node); +} + +/** + * LFA tiebreaker RB-tree comparison function. + * + * @param a First LFA tiebreaker + * @param b Second LFA tiebreaker + * + * @return -1 (a < b), 0 (a == b) or +1 (a > b) + */ +int lfa_tiebreaker_cmp(const struct lfa_tiebreaker *a, + const struct lfa_tiebreaker *b) +{ + if (a->index < b->index) + return -1; + if (a->index > b->index) + return 1; + + return a->type - b->type; +} + +/** + * Initialize list of LFA tie-breakers. + * + * @param area IS-IS area + * @param level IS-IS level + */ +void isis_lfa_tiebreakers_init(struct isis_area *area, int level) +{ + lfa_tiebreaker_tree_init(&area->lfa_tiebreakers[level - 1]); +} + +/** + * Clear list of LFA tie-breakers, releasing all allocated memory. + * + * @param area IS-IS area + * @param level IS-IS level + */ +void isis_lfa_tiebreakers_clear(struct isis_area *area, int level) +{ + while (lfa_tiebreaker_tree_count(&area->lfa_tiebreakers[level - 1]) + > 0) { + struct lfa_tiebreaker *tie_b; + + tie_b = lfa_tiebreaker_tree_first( + &area->lfa_tiebreakers[level - 1]); + isis_lfa_tiebreaker_delete(area, level, tie_b); + } +} + +/** + * Add new LFA tie-breaker to list of LFA tie-breakers. + * + * @param area IS-IS area + * @param level IS-IS level + * @param index LFA tie-breaker index + * @param type LFA tie-breaker type + * + * @return Pointer to new LFA tie-breaker structure. + */ +struct lfa_tiebreaker *isis_lfa_tiebreaker_add(struct isis_area *area, + int level, uint8_t index, + enum lfa_tiebreaker_type type) +{ + struct lfa_tiebreaker *tie_b; + + tie_b = XCALLOC(MTYPE_ISIS_LFA_TIEBREAKER, sizeof(*tie_b)); + tie_b->index = index; + tie_b->type = type; + tie_b->area = area; + lfa_tiebreaker_tree_add(&area->lfa_tiebreakers[level - 1], tie_b); + + return tie_b; +} + +/** + * Remove LFA tie-breaker from list of LFA tie-breakers. + * + * @param area IS-IS area + * @param level IS-IS level + * @param tie_b Pointer to LFA tie-breaker structure + */ +void isis_lfa_tiebreaker_delete(struct isis_area *area, int level, + struct lfa_tiebreaker *tie_b) +{ + lfa_tiebreaker_tree_del(&area->lfa_tiebreakers[level - 1], tie_b); + XFREE(MTYPE_ISIS_LFA_TIEBREAKER, tie_b); +} + +static bool lfa_excl_interface_hash_cmp(const void *value1, const void *value2) +{ + return strmatch(value1, value2); +} + +static unsigned int lfa_excl_interface_hash_make(const void *value) +{ + return string_hash_make(value); +} + +static void *lfa_excl_interface_hash_alloc(void *p) +{ + return XSTRDUP(MTYPE_ISIS_LFA_EXCL_IFACE, p); +} + +static void lfa_excl_interface_hash_free(void *arg) +{ + XFREE(MTYPE_ISIS_LFA_EXCL_IFACE, arg); +} + +/** + * Initialize hash table of LFA excluded interfaces. + * + * @param circuit IS-IS interface + * @param level IS-IS level + */ +void isis_lfa_excluded_ifaces_init(struct isis_circuit *circuit, int level) +{ + circuit->lfa_excluded_ifaces[level - 1] = hash_create( + lfa_excl_interface_hash_make, lfa_excl_interface_hash_cmp, + "LFA Excluded Interfaces"); +} + +/** + * Clear hash table of LFA excluded interfaces, releasing all allocated memory. + * + * @param nodes List of SPF nodes + */ +void isis_lfa_excluded_ifaces_clear(struct isis_circuit *circuit, int level) +{ + hash_clean(circuit->lfa_excluded_ifaces[level - 1], + lfa_excl_interface_hash_free); +} + +/** + * Add new interface to hash table of excluded interfaces. + * + * @param circuit IS-IS interface + * @param level IS-IS level + * @param ifname Excluded interface name + */ +void isis_lfa_excluded_iface_add(struct isis_circuit *circuit, int level, + const char *ifname) +{ + (void)hash_get(circuit->lfa_excluded_ifaces[level - 1], (char *)ifname, + lfa_excl_interface_hash_alloc); +} + +/** + * Remove interface from hash table of excluded interfaces. + * + * @param circuit IS-IS interface + * @param level IS-IS level + * @param ifname Excluded interface name + */ +void isis_lfa_excluded_iface_delete(struct isis_circuit *circuit, int level, + const char *ifname) +{ + char *found; + + found = hash_lookup(circuit->lfa_excluded_ifaces[level - 1], + (char *)ifname); + if (found) { + hash_release(circuit->lfa_excluded_ifaces[level - 1], found); + lfa_excl_interface_hash_free(found); + } +} + +/** + * Lookup excluded interface. + * + * @param circuit IS-IS interface + * @param level IS-IS level + * @param ifname Excluded interface name + */ +bool isis_lfa_excluded_iface_check(struct isis_circuit *circuit, int level, + const char *ifname) +{ + return hash_lookup(circuit->lfa_excluded_ifaces[level - 1], + (char *)ifname); +} + +/** + * Check if a given IS-IS adjacency needs to be excised when computing the SPF + * post-convergence tree. + * + * @param spftree IS-IS SPF tree + * @param id Adjacency System ID (or LAN ID of the designated router + * for broadcast interfaces) + * + * @return true if the adjacency needs to be excised, false + * otherwise + */ +bool isis_lfa_excise_adj_check(const struct isis_spftree *spftree, + const uint8_t *id) +{ + const struct lfa_protected_resource *resource; + + if (spftree->type != SPF_TYPE_RLFA && spftree->type != SPF_TYPE_TI_LFA) + return false; + + /* + * Adjacencies formed over the failed interface should be excised both + * when using link and node protection. + */ + resource = &spftree->lfa.protected_resource; + if (!memcmp(resource->adjacency, id, ISIS_SYS_ID_LEN + 1)) + return true; + + return false; +} + +/** + * Check if a given IS-IS node needs to be excised when computing the SPF + * post-convergence tree. + * + * @param spftree IS-IS SPF tree + * @param id Node System ID + * + * @return true if the node needs to be excised, false otherwise + */ +bool isis_lfa_excise_node_check(const struct isis_spftree *spftree, + const uint8_t *id) +{ + const struct lfa_protected_resource *resource; + + if (spftree->type != SPF_TYPE_TI_LFA) + return false; + + /* + * When using node protection, nodes reachable over the failed interface + * must be excised. + */ + resource = &spftree->lfa.protected_resource; + if (resource->type == LFA_LINK_PROTECTION) + return false; + + if (isis_spf_node_find(&resource->nodes, id)) + return true; + + return false; +} + +struct tilfa_find_pnode_prefix_sid_args { + uint32_t sid_index; + int algorithm; +}; + +static int tilfa_find_pnode_prefix_sid_cb(const struct prefix *prefix, + uint32_t metric, bool external, + struct isis_subtlvs *subtlvs, + void *arg) +{ + struct tilfa_find_pnode_prefix_sid_args *args = arg; + struct isis_prefix_sid *psid; + + if (!subtlvs || subtlvs->prefix_sids.count == 0) + return LSP_ITER_CONTINUE; + + for (psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head; psid; + psid = psid->next) { + /* Require the node flag to be set. */ + if (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NODE)) + continue; + if (psid->algorithm != args->algorithm) + continue; + args->sid_index = psid->value; + return LSP_ITER_STOP; + } + return LSP_ITER_CONTINUE; +} + +/* Find Prefix-SID associated to a System ID. */ +static uint32_t tilfa_find_pnode_prefix_sid(struct isis_spftree *spftree, + const uint8_t *sysid) +{ + struct isis_lsp *lsp; + struct tilfa_find_pnode_prefix_sid_args args; + + lsp = isis_root_system_lsp(spftree->lspdb, sysid); + if (!lsp) + return UINT32_MAX; + + args.algorithm = spftree->algorithm; + + args.sid_index = UINT32_MAX; + isis_lsp_iterate_ip_reach(lsp, spftree->family, spftree->mtid, + tilfa_find_pnode_prefix_sid_cb, &args); + + return args.sid_index; +} + +struct tilfa_find_qnode_adj_sid_args { + const uint8_t *qnode_sysid; + mpls_label_t label; +}; + +static int tilfa_find_qnode_adj_sid_cb(const uint8_t *id, uint32_t metric, + bool oldmetric, + struct isis_ext_subtlvs *subtlvs, + void *arg) +{ + struct tilfa_find_qnode_adj_sid_args *args = arg; + struct isis_adj_sid *adj_sid; + + if (memcmp(id, args->qnode_sysid, ISIS_SYS_ID_LEN)) + return LSP_ITER_CONTINUE; + if (!subtlvs || subtlvs->adj_sid.count == 0) + return LSP_ITER_CONTINUE; + + adj_sid = (struct isis_adj_sid *)subtlvs->adj_sid.head; + args->label = adj_sid->sid; + + return LSP_ITER_STOP; +} + +/* Find Adj-SID associated to a pair of System IDs. */ +static mpls_label_t tilfa_find_qnode_adj_sid(struct isis_spftree *spftree, + const uint8_t *source_sysid, + const uint8_t *qnode_sysid) +{ + struct isis_lsp *lsp; + struct tilfa_find_qnode_adj_sid_args args; + + lsp = isis_root_system_lsp(spftree->lspdb, source_sysid); + if (!lsp) + return MPLS_INVALID_LABEL; + + args.qnode_sysid = qnode_sysid; + args.label = MPLS_INVALID_LABEL; + isis_lsp_iterate_is_reach(lsp, spftree->mtid, + tilfa_find_qnode_adj_sid_cb, &args); + + return args.label; +} + +/* + * Compute the MPLS label stack associated to a TI-LFA repair list. This + * needs to be computed separately for each adjacency since different + * neighbors can have different SRGBs. + */ +static struct mpls_label_stack * +tilfa_compute_label_stack(struct lspdb_head *lspdb, + const struct isis_spf_adj *sadj, + const struct list *repair_list) +{ + struct mpls_label_stack *label_stack; + struct isis_tilfa_sid *sid; + struct listnode *node; + size_t i = 0; + + /* Allocate label stack. */ + label_stack = XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS, + sizeof(struct mpls_label_stack) + + listcount(repair_list) + * sizeof(mpls_label_t)); + label_stack->num_labels = listcount(repair_list); + + for (ALL_LIST_ELEMENTS_RO(repair_list, node, sid)) { + const uint8_t *target_node; + struct isis_sr_block *srgb; + mpls_label_t label; + + switch (sid->type) { + case TILFA_SID_PREFIX: + if (sid->value.index.remote) + target_node = sid->value.index.remote_sysid; + else + target_node = sadj->id; + srgb = isis_sr_find_srgb(lspdb, target_node); + if (!srgb) { + zlog_warn("%s: SRGB not found for node %s", + __func__, + print_sys_hostname(target_node)); + goto error; + } + + /* Check if the SID index falls inside the SRGB. */ + if (sid->value.index.value >= srgb->range_size) { + flog_warn( + EC_ISIS_SID_OVERFLOW, + "%s: SID index %u falls outside remote SRGB range", + __func__, sid->value.index.value); + goto error; + } + + /* + * Prefix-SID: map SID index to label value within the + * SRGB. + */ + label = srgb->lower_bound + sid->value.index.value; + break; + case TILFA_SID_ADJ: + /* Adj-SID: absolute label value can be used directly */ + label = sid->value.label; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown TI-LFA SID type [%u]", __func__, + sid->type); + exit(1); + } + label_stack->label[i++] = label; + } + + return label_stack; + +error: + XFREE(MTYPE_ISIS_NEXTHOP_LABELS, label_stack); + return NULL; +} + +static int tilfa_repair_list_apply(struct isis_spftree *spftree, + struct isis_vertex *vertex_dest, + const struct isis_vertex *vertex_pnode, + const struct list *repair_list) +{ + struct isis_vertex_adj *vadj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) { + struct isis_spf_adj *sadj = vadj->sadj; + struct mpls_label_stack *label_stack; + + /* + * Don't try to apply the repair list if one was already applied + * before (can't have ECMP past the P-node). + */ + if (vadj->label_stack) + continue; + + if (!isis_vertex_adj_exists(spftree, vertex_pnode, sadj)) + continue; + + label_stack = tilfa_compute_label_stack(spftree->lspdb, sadj, + repair_list); + if (!label_stack) { + char buf[VID2STR_BUFFER]; + + vid2string(vertex_dest, buf, sizeof(buf)); + zlog_warn( + "%s: %s %s adjacency %s: failed to compute label stack", + __func__, vtype2string(vertex_dest->type), buf, + print_sys_hostname(sadj->id)); + return -1; + } + + vadj->label_stack = label_stack; + } + + return 0; +} + +/* + * Check if a node belongs to the extended P-space corresponding to a given + * destination. + */ +static bool lfa_ext_p_space_check(const struct isis_spftree *spftree_pc, + const struct isis_vertex *vertex_dest, + const struct isis_vertex *vertex) +{ + struct isis_spftree *spftree_old = spftree_pc->lfa.old.spftree; + struct isis_vertex_adj *vadj; + struct listnode *node; + + /* Check the local P-space first. */ + if (isis_spf_node_find(&spftree_pc->lfa.p_space, vertex->N.id)) + return true; + + /* + * Check the P-space of the adjacent routers used to reach the + * destination. + */ + for (ALL_LIST_ELEMENTS_RO(vertex_dest->Adj_N, node, vadj)) { + struct isis_spf_adj *sadj = vadj->sadj; + struct isis_spf_node *adj_node; + + adj_node = + isis_spf_node_find(&spftree_old->adj_nodes, sadj->id); + if (!adj_node) + continue; + + if (isis_spf_node_find(&adj_node->lfa.p_space, vertex->N.id)) + return true; + } + + return false; +} + +/* Check if a node belongs to the Q-space. */ +static bool lfa_q_space_check(const struct isis_spftree *spftree_pc, + const struct isis_vertex *vertex) +{ + return isis_spf_node_find(&spftree_pc->lfa.q_space, vertex->N.id); +} + +/* This is a recursive function. */ +static int tilfa_build_repair_list(struct isis_spftree *spftree_pc, + struct isis_vertex *vertex_dest, + const struct isis_vertex *vertex, + const struct isis_vertex *vertex_child, + struct isis_spf_nodes *used_pnodes, + struct list *repair_list) +{ + struct isis_vertex *pvertex; + struct listnode *node; + bool is_pnode, is_qnode; + char buf[VID2STR_BUFFER]; + struct isis_tilfa_sid sid_dest = {}, sid_qnode = {}, sid_pnode = {}; + uint32_t sid_index; + mpls_label_t label_qnode; + + if (IS_DEBUG_LFA) { + vid2string(vertex, buf, sizeof(buf)); + zlog_debug("ISIS-LFA: vertex %s %s", vtype2string(vertex->type), + buf); + } + + /* Push original Prefix-SID label when necessary. */ + if (VTYPE_IP(vertex->type) && vertex->N.ip.sr.present) { + pvertex = listnode_head(vertex->parents); + assert(pvertex); + + sid_index = vertex->N.ip.sr.sid.value; + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: pushing Prefix-SID to %pFX (index %u)", + &vertex->N.ip.p.dest, sid_index); + sid_dest.type = TILFA_SID_PREFIX; + sid_dest.value.index.value = sid_index; + sid_dest.value.index.remote = true; + memcpy(sid_dest.value.index.remote_sysid, pvertex->N.id, + sizeof(sid_dest.value.index.remote_sysid)); + listnode_add_head(repair_list, &sid_dest); + } + + if (!vertex_child) + goto parents; + if (vertex->type != VTYPE_NONPSEUDO_IS + && vertex->type != VTYPE_NONPSEUDO_TE_IS) + goto parents; + if (!VTYPE_IS(vertex_child->type)) + vertex_child = NULL; + + /* Check if node is part of the extended P-space and/or Q-space. */ + is_pnode = lfa_ext_p_space_check(spftree_pc, vertex_dest, vertex); + is_qnode = lfa_q_space_check(spftree_pc, vertex); + + /* Push Adj-SID label when necessary. */ + if ((!is_qnode + || spftree_pc->lfa.protected_resource.type == LFA_NODE_PROTECTION) + && vertex_child) { + /* + * If vertex is the penultimate hop router, then pushing an + * Adj-SID towards the final hop means that the No-PHP flag of + * the original Prefix-SID must be honored. We do that by + * removing the previously added Prefix-SID from the repair list + * when those conditions are met. + */ + if (vertex->depth == (vertex_dest->depth - 2) + && VTYPE_IP(vertex_dest->type) + && vertex_dest->N.ip.sr.present + && !CHECK_FLAG(vertex_dest->N.ip.sr.sid.flags, + ISIS_PREFIX_SID_NO_PHP)) { + list_delete_all_node(repair_list); + } + + label_qnode = tilfa_find_qnode_adj_sid(spftree_pc, vertex->N.id, + vertex_child->N.id); + if (label_qnode == MPLS_INVALID_LABEL) { + zlog_warn("ISIS-LFA: failed to find %s->%s Adj-SID", + print_sys_hostname(vertex->N.id), + print_sys_hostname(vertex_child->N.id)); + return -1; + } + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: pushing %s->%s Adj-SID (label %u)", + print_sys_hostname(vertex->N.id), + print_sys_hostname(vertex_child->N.id), + label_qnode); + sid_qnode.type = TILFA_SID_ADJ; + sid_qnode.value.label = label_qnode; + listnode_add_head(repair_list, &sid_qnode); + } + + /* Push Prefix-SID label when necessary. */ + if (is_pnode) { + /* The same P-node can't be used more than once. */ + if (isis_spf_node_find(used_pnodes, vertex->N.id)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: skipping already used P-node"); + return 0; + } + isis_spf_node_new(used_pnodes, vertex->N.id); + + if (!vertex_child) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: destination is within Ext-P-Space"); + return 0; + } + + sid_index = + tilfa_find_pnode_prefix_sid(spftree_pc, vertex->N.id); + if (sid_index == UINT32_MAX) { + zlog_warn( + "ISIS-LFA: failed to find Prefix-SID corresponding to PQ-node %s", + print_sys_hostname(vertex->N.id)); + return -1; + } + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: pushing Node-SID to %s (index %u)", + print_sys_hostname(vertex->N.id), sid_index); + sid_pnode.type = TILFA_SID_PREFIX; + sid_pnode.value.index.value = sid_index; + listnode_add_head(repair_list, &sid_pnode); + + /* Apply repair list. */ + if (spftree_pc->area->srdb.config.msd + && listcount(repair_list) + > spftree_pc->area->srdb.config.msd) { + zlog_warn( + "ISIS-LFA: list of repair segments exceeds locally configured MSD (%u > %u)", + listcount(repair_list), + spftree_pc->area->srdb.config.msd); + return -1; + } + if (tilfa_repair_list_apply(spftree_pc, vertex_dest, vertex, + repair_list) + != 0) + return -1; + return 0; + } + +parents: + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) { + struct list *repair_list_parent; + bool ecmp; + int ret; + + ecmp = (listcount(vertex->parents) > 1) ? true : false; + repair_list_parent = ecmp ? list_dup(repair_list) : repair_list; + ret = tilfa_build_repair_list(spftree_pc, vertex_dest, pvertex, + vertex, used_pnodes, + repair_list_parent); + if (ecmp) + list_delete(&repair_list_parent); + if (ret != 0) + return ret; + } + + return 0; +} + +static const char *lfa_protection_type2str(enum lfa_protection_type type) +{ + switch (type) { + case LFA_LINK_PROTECTION: + return "link protection"; + case LFA_NODE_PROTECTION: + return "node protection"; + default: + return "unknown protection type"; + } +} + +static const char * +lfa_protected_resource2str(const struct lfa_protected_resource *resource) +{ + const uint8_t *fail_id; + static char buffer[128]; + + fail_id = resource->adjacency; + snprintf(buffer, sizeof(buffer), "%s.%u's failure (%s)", + print_sys_hostname(fail_id), LSP_PSEUDO_ID(fail_id), + lfa_protection_type2str(resource->type)); + + return buffer; +} + +static bool +spf_adj_check_is_affected(const struct isis_spf_adj *sadj, + const struct lfa_protected_resource *resource, + const uint8_t *root_sysid, bool reverse) +{ + if (!!CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST) + != !!LSP_PSEUDO_ID(resource->adjacency)) + return false; + + if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)) { + if (!memcmp(sadj->lan.desig_is_id, resource->adjacency, + ISIS_SYS_ID_LEN + 1)) + return true; + } else { + if (!reverse + && !memcmp(sadj->id, resource->adjacency, ISIS_SYS_ID_LEN)) + return true; + if (reverse && !memcmp(sadj->id, root_sysid, ISIS_SYS_ID_LEN)) + return true; + } + + return false; +} + +/* Check if the given vertex is affected by a given local failure. */ +static bool +spf_vertex_check_is_affected(const struct isis_vertex *vertex, + const uint8_t *root_sysid, + const struct lfa_protected_resource *resource) +{ + struct isis_vertex_adj *vadj; + struct listnode *node; + size_t affected_nhs = 0; + + /* Local routes don't need protection. */ + if (VTYPE_IP(vertex->type) && vertex->depth == 1) + return false; + + for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, vadj)) { + struct isis_spf_adj *sadj = vadj->sadj; + + if (spf_adj_check_is_affected(sadj, resource, root_sysid, + false)) + affected_nhs++; + } + + /* + * No need to compute backup paths for ECMP routes, except if all + * primary nexthops share the same broadcast interface. + */ + if (listcount(vertex->Adj_N) == affected_nhs) + return true; + + return false; +} + +/* Check if a given RLFA/TI-LFA post-convergence SPF vertex needs protection. */ +static bool lfa_check_needs_protection(const struct isis_spftree *spftree_pc, + const struct isis_vertex *vertex) +{ + struct isis_vertex *vertex_old; + + /* Only local adjacencies need TI-LFA Adj-SID protection. */ + if (spftree_pc->type == SPF_TYPE_TI_LFA && VTYPE_IS(vertex->type) + && !isis_adj_find(spftree_pc->area, spftree_pc->level, + vertex->N.id)) + return false; + + vertex_old = isis_find_vertex(&spftree_pc->lfa.old.spftree->paths, + &vertex->N, vertex->type); + if (!vertex_old) + return false; + + /* Skip vertex if it's already protected by local LFA. */ + if (CHECK_FLAG(vertex_old->flags, F_ISIS_VERTEX_LFA_PROTECTED)) + return false; + + return spf_vertex_check_is_affected( + vertex_old, spftree_pc->sysid, + &spftree_pc->lfa.protected_resource); +} + +/** + * Check if the given SPF vertex needs protection and, if so, compute and + * install the corresponding repair paths. + * + * @param spftree_pc The post-convergence SPF tree + * @param vertex IS-IS SPF vertex to check + * + * @return 0 if the vertex needs to be protected, -1 otherwise + */ +int isis_tilfa_check(struct isis_spftree *spftree_pc, + struct isis_vertex *vertex) +{ + struct isis_spf_nodes used_pnodes; + char buf[VID2STR_BUFFER]; + struct list *repair_list; + int ret; + + if (!spftree_pc->area->srdb.enabled) + return -1; + + if (!lfa_check_needs_protection(spftree_pc, vertex)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s %s unaffected by %s", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf)), + lfa_protected_resource2str( + &spftree_pc->lfa.protected_resource)); + + return -1; + } + + /* + * Check if the route/adjacency was already covered by node protection. + */ + if (VTYPE_IS(vertex->type)) { + struct isis_adjacency *adj; + + adj = isis_adj_find(spftree_pc->area, spftree_pc->level, + vertex->N.id); + if (adj && isis_sr_adj_sid_find(adj, spftree_pc->family, + ISIS_SR_ADJ_BACKUP)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s %s already covered by node protection", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf))); + + return -1; + } + } + if (VTYPE_IP(vertex->type)) { + struct route_table *route_table; + + route_table = spftree_pc->lfa.old.spftree->route_table_backup; + if (route_node_lookup(route_table, &vertex->N.ip.p.dest)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s %s already covered by node protection", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf))); + + return -1; + } + } + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: computing repair path(s) of %s %s w.r.t %s", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf)), + lfa_protected_resource2str( + &spftree_pc->lfa.protected_resource)); + + /* Create base repair list. */ + repair_list = list_new(); + + isis_spf_node_list_init(&used_pnodes); + ret = tilfa_build_repair_list(spftree_pc, vertex, vertex, NULL, + &used_pnodes, repair_list); + isis_spf_node_list_clear(&used_pnodes); + list_delete(&repair_list); + if (ret != 0) + zlog_warn( + "ISIS-LFA: failed to compute repair path(s) of %s %s w.r.t %s", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf)), + lfa_protected_resource2str( + &spftree_pc->lfa.protected_resource)); + + return ret; +} + +static bool +spf_adj_node_is_affected(struct isis_spf_node *adj_node, + const struct lfa_protected_resource *resource, + const uint8_t *root_sysid) +{ + struct isis_spf_adj *sadj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adj_node->adjacencies, node, sadj)) { + if (sadj->metric != adj_node->best_metric) + continue; + if (spf_adj_check_is_affected(sadj, resource, root_sysid, + false)) + return true; + } + + return false; +} + +static bool vertex_is_affected(struct isis_spftree *spftree_root, + const struct isis_spf_nodes *adj_nodes, + bool p_space, const struct isis_vertex *vertex, + const struct lfa_protected_resource *resource) +{ + struct isis_vertex *pvertex; + struct listnode *node, *vnode; + + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) { + struct isis_spftree *spftree_parent; + struct isis_vertex *vertex_child; + struct isis_vertex_adj *vadj; + bool reverse = false; + + if (p_space && resource->type == LFA_NODE_PROTECTION) { + if (isis_spf_node_find(&resource->nodes, vertex->N.id)) + return true; + goto parents; + } + + /* Check if either the vertex or its parent is the root node. */ + if (memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN) + && memcmp(pvertex->N.id, spftree_root->sysid, + ISIS_SYS_ID_LEN)) + goto parents; + + /* Get SPT of the parent vertex. */ + if (!memcmp(pvertex->N.id, spftree_root->sysid, + ISIS_SYS_ID_LEN)) + spftree_parent = spftree_root; + else { + struct isis_spf_node *adj_node; + + adj_node = isis_spf_node_find(adj_nodes, pvertex->N.id); + assert(adj_node); + spftree_parent = adj_node->lfa.spftree; + assert(spftree_parent); + reverse = true; + } + + /* Get paths pvertex uses to reach vertex. */ + vertex_child = isis_find_vertex(&spftree_parent->paths, + &vertex->N, vertex->type); + if (!vertex_child) + goto parents; + + /* Check if any of these paths use the protected resource. */ + for (ALL_LIST_ELEMENTS_RO(vertex_child->Adj_N, vnode, vadj)) + if (spf_adj_check_is_affected(vadj->sadj, resource, + spftree_root->sysid, + reverse)) + return true; + + parents: + if (vertex_is_affected(spftree_root, adj_nodes, p_space, + pvertex, resource)) + return true; + } + + return false; +} + +/* Calculate set of nodes reachable without using the protected interface. */ +static void lfa_calc_reach_nodes(struct isis_spftree *spftree, + struct isis_spftree *spftree_root, + const struct isis_spf_nodes *adj_nodes, + bool p_space, + const struct lfa_protected_resource *resource, + struct isis_spf_nodes *nodes) +{ + struct isis_vertex *vertex; + struct listnode *node; + + for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, vertex)) { + char buf[VID2STR_BUFFER]; + + if (!VTYPE_IS(vertex->type)) + continue; + + /* Skip root node. */ + if (!memcmp(vertex->N.id, spftree_root->sysid, ISIS_SYS_ID_LEN)) + continue; + + /* Don't add the same node twice. */ + if (isis_spf_node_find(nodes, vertex->N.id)) + continue; + + if (!vertex_is_affected(spftree_root, adj_nodes, p_space, + vertex, resource)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: adding %s", + vid2string(vertex, buf, sizeof(buf))); + + isis_spf_node_new(nodes, vertex->N.id); + } + } +} + +/** + * Helper function used to create an SPF tree structure and run reverse SPF on + * it. + * + * @param spftree IS-IS SPF tree + * + * @return Pointer to new SPF tree structure. + */ +struct isis_spftree *isis_spf_reverse_run(const struct isis_spftree *spftree) +{ + struct isis_spftree *spftree_reverse; + + spftree_reverse = isis_spftree_new( + spftree->area, spftree->lspdb, spftree->sysid, spftree->level, + spftree->tree_id, SPF_TYPE_REVERSE, + F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES, + spftree->algorithm); + isis_run_spf(spftree_reverse); + + return spftree_reverse; +} + +/* + * Calculate the Extended P-space and Q-space associated to a given link + * failure. + */ +static void lfa_calc_pq_spaces(struct isis_spftree *spftree_pc, + const struct lfa_protected_resource *resource) +{ + struct isis_spftree *spftree; + struct isis_spftree *spftree_reverse; + struct isis_spf_nodes *adj_nodes; + struct isis_spf_node *adj_node; + + /* Obtain pre-failure SPTs and list of adjacent nodes. */ + spftree = spftree_pc->lfa.old.spftree; + spftree_reverse = spftree_pc->lfa.old.spftree_reverse; + adj_nodes = &spftree->adj_nodes; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing P-space (self)"); + lfa_calc_reach_nodes(spftree, spftree, adj_nodes, true, resource, + &spftree_pc->lfa.p_space); + + RB_FOREACH (adj_node, isis_spf_nodes, adj_nodes) { + if (spf_adj_node_is_affected(adj_node, resource, + spftree->sysid)) { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing Q-space (%s)", + print_sys_hostname(adj_node->sysid)); + + /* + * Compute the reverse SPF in the behalf of the node + * adjacent to the failure, if we haven't done that + * before + */ + if (!adj_node->lfa.spftree_reverse) + adj_node->lfa.spftree_reverse = + isis_spf_reverse_run( + adj_node->lfa.spftree); + + lfa_calc_reach_nodes(adj_node->lfa.spftree_reverse, + spftree_reverse, adj_nodes, false, + resource, + &spftree_pc->lfa.q_space); + } else { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing P-space (%s)", + print_sys_hostname(adj_node->sysid)); + lfa_calc_reach_nodes(adj_node->lfa.spftree, spftree, + adj_nodes, true, resource, + &adj_node->lfa.p_space); + } + } +} + +/** + * Compute the TI-LFA backup paths for a given protected interface. + * + * @param area IS-IS area + * @param spftree IS-IS SPF tree + * @param spftree_reverse IS-IS Reverse SPF tree + * @param resource Protected resource + * + * @return Pointer to the post-convergence SPF tree + */ +struct isis_spftree *isis_tilfa_compute(struct isis_area *area, + struct isis_spftree *spftree, + struct isis_spftree *spftree_reverse, + struct lfa_protected_resource *resource) +{ + struct isis_spftree *spftree_pc; + struct isis_spf_node *adj_node; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing TI-LFAs for %s", + lfa_protected_resource2str(resource)); + + /* Populate list of nodes affected by link failure. */ + if (resource->type == LFA_NODE_PROTECTION) { + isis_spf_node_list_init(&resource->nodes); + RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) { + if (spf_adj_node_is_affected(adj_node, resource, + spftree->sysid)) + isis_spf_node_new(&resource->nodes, + adj_node->sysid); + } + } + + /* Create post-convergence SPF tree. */ + spftree_pc = isis_spftree_new(area, spftree->lspdb, spftree->sysid, + spftree->level, spftree->tree_id, + SPF_TYPE_TI_LFA, spftree->flags, + spftree->algorithm); + spftree_pc->lfa.old.spftree = spftree; + spftree_pc->lfa.old.spftree_reverse = spftree_reverse; + spftree_pc->lfa.protected_resource = *resource; + + /* Compute the extended P-space and Q-space. */ + lfa_calc_pq_spaces(spftree_pc, resource); + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: computing the post convergence SPT w.r.t. %s", + lfa_protected_resource2str(resource)); + + /* Re-run SPF in the local node to find the post-convergence paths. */ + isis_run_spf(spftree_pc); + + /* Clear list of nodes affeted by link failure. */ + if (resource->type == LFA_NODE_PROTECTION) + isis_spf_node_list_clear(&resource->nodes); + + return spftree_pc; +} + +/** + * Run forward SPF on all adjacent routers. + * + * @param spftree IS-IS SPF tree + * + * @return 0 on success, -1 otherwise + */ +int isis_spf_run_neighbors(struct isis_spftree *spftree) +{ + struct isis_lsp *lsp; + struct isis_spf_node *adj_node; + + lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid); + if (!lsp) + return -1; + + RB_FOREACH (adj_node, isis_spf_nodes, &spftree->adj_nodes) { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: running SPF on neighbor %s", + print_sys_hostname(adj_node->sysid)); + + /* Compute the SPT on behalf of the neighbor. */ + adj_node->lfa.spftree = isis_spftree_new( + spftree->area, spftree->lspdb, adj_node->sysid, + spftree->level, spftree->tree_id, SPF_TYPE_FORWARD, + F_SPFTREE_NO_ADJACENCIES | F_SPFTREE_NO_ROUTES, + spftree->algorithm); + isis_run_spf(adj_node->lfa.spftree); + } + + return 0; +} + +/* Find Router ID of PQ node. */ +static struct in_addr *rlfa_pq_node_rtr_id(struct isis_spftree *spftree, + const struct isis_vertex *vertex_pq) +{ + struct isis_lsp *lsp; + + lsp = isis_root_system_lsp(spftree->lspdb, vertex_pq->N.id); + if (!lsp) + return NULL; + + if (lsp->tlvs->router_cap->router_id.s_addr == INADDR_ANY) + return NULL; + + return &lsp->tlvs->router_cap->router_id; +} + +/* Find PQ node by intersecting the P/Q spaces. This is a recursive function. */ +static const struct in_addr * +rlfa_find_pq_node(struct isis_spftree *spftree_pc, + struct isis_vertex *vertex_dest, + const struct isis_vertex *vertex, + const struct isis_vertex *vertex_child) +{ + struct isis_area *area = spftree_pc->area; + int level = spftree_pc->level; + struct isis_vertex *pvertex; + struct listnode *node; + bool is_pnode, is_qnode; + + if (!vertex_child) + goto parents; + if (vertex->type != VTYPE_NONPSEUDO_IS + && vertex->type != VTYPE_NONPSEUDO_TE_IS) + goto parents; + if (!VTYPE_IS(vertex_child->type)) + vertex_child = NULL; + + /* Check if node is part of the extended P-space and/or Q-space. */ + is_pnode = lfa_ext_p_space_check(spftree_pc, vertex_dest, vertex); + is_qnode = lfa_q_space_check(spftree_pc, vertex); + + if (is_pnode && is_qnode) { + const struct in_addr *rtr_id_pq; + uint32_t max_metric; + struct prefix_list *plist = NULL; + + rtr_id_pq = rlfa_pq_node_rtr_id(spftree_pc, vertex); + if (!rtr_id_pq) { + if (IS_DEBUG_LFA) { + char buf[VID2STR_BUFFER]; + + vid2string(vertex, buf, sizeof(buf)); + zlog_debug( + "ISIS-LFA: tentative PQ node (%s %s) doesn't have a router-ID", + vtype2string(vertex->type), buf); + } + goto parents; + } + + max_metric = spftree_pc->lfa.remote.max_metric; + if (max_metric && vertex->d_N > max_metric) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: skipping PQ node %pI4 (maximum metric)", + rtr_id_pq); + goto parents; + } + + plist = area->rlfa_plist[level - 1]; + if (plist) { + struct prefix p; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = *rtr_id_pq; + if (prefix_list_apply(plist, &p) == PREFIX_DENY) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: PQ node %pI4 filtered by prefix-list", + rtr_id_pq); + goto parents; + } + } + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: found PQ node: %pI4", rtr_id_pq); + + return rtr_id_pq; + } + +parents: + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, pvertex)) { + const struct in_addr *rtr_id_pq; + + rtr_id_pq = rlfa_find_pq_node(spftree_pc, vertex_dest, pvertex, + vertex); + if (rtr_id_pq) + return rtr_id_pq; + } + + return NULL; +} + +int rlfa_cmp(const struct rlfa *a, const struct rlfa *b) +{ + return prefix_cmp(&a->prefix, &b->prefix); +} + +static struct rlfa *rlfa_add(struct isis_spftree *spftree, + struct isis_vertex *vertex, + struct in_addr pq_address) +{ + struct rlfa *rlfa; + + assert(VTYPE_IP(vertex->type)); + rlfa = XCALLOC(MTYPE_ISIS_RLFA, sizeof(*rlfa)); + rlfa->prefix = vertex->N.ip.p.dest; + rlfa->vertex = vertex; + rlfa->pq_address = pq_address; + rlfa_tree_add(&spftree->lfa.remote.rlfas, rlfa); + + return rlfa; +} + +static void rlfa_delete(struct isis_spftree *spftree, struct rlfa *rlfa) +{ + rlfa_tree_del(&spftree->lfa.remote.rlfas, rlfa); + XFREE(MTYPE_ISIS_RLFA, rlfa); +} + +static struct rlfa *rlfa_lookup(struct isis_spftree *spftree, + union prefixconstptr pu) +{ + struct rlfa s = {}; + + s.prefix = *pu.p; + return rlfa_tree_find(&spftree->lfa.remote.rlfas, &s); +} + +static void isis_area_verify_routes_cb(struct event *thread) +{ + struct isis_area *area = EVENT_ARG(thread); + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: updating RLFAs in the RIB"); + + isis_area_verify_routes(area); +} + +static mpls_label_t rlfa_nexthop_label(struct isis_spftree *spftree, + struct isis_vertex_adj *vadj, + struct zapi_rlfa_response *response) +{ + struct isis_spf_adj *sadj = vadj->sadj; + struct isis_adjacency *adj = sadj->adj; + + /* + * Special case to make unit tests work (use implicit-null labels + * instead of artifical ones). + */ + if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) + return MPLS_LABEL_IMPLICIT_NULL; + + for (unsigned int i = 0; i < response->nexthop_num; i++) { + switch (response->nexthops[i].family) { + case AF_INET: + for (unsigned int j = 0; j < adj->ipv4_address_count; + j++) { + struct in_addr addr = adj->ipv4_addresses[j]; + + if (!IPV4_ADDR_SAME( + &addr, + &response->nexthops[i].gate.ipv4)) + continue; + + return response->nexthops[i].label; + } + break; + case AF_INET6: + for (unsigned int j = 0; j < adj->ll_ipv6_count; j++) { + struct in6_addr addr = adj->ll_ipv6_addrs[j]; + + if (!IPV6_ADDR_SAME( + &addr, + &response->nexthops[i].gate.ipv6)) + continue; + + return response->nexthops[i].label; + } + break; + + default: + break; + } + } + + return MPLS_INVALID_LABEL; +} + +int isis_rlfa_activate(struct isis_spftree *spftree, struct rlfa *rlfa, + struct zapi_rlfa_response *response) +{ + struct isis_area *area = spftree->area; + struct isis_vertex *vertex = rlfa->vertex; + struct isis_vertex_adj *vadj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, vadj)) { + mpls_label_t ldp_label; + struct mpls_label_stack *label_stack; + size_t num_labels = 0; + size_t i = 0; + + ldp_label = rlfa_nexthop_label(spftree, vadj, response); + if (ldp_label == MPLS_INVALID_LABEL) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: failed to activate RLFA: missing LDP label to reach PQ node through %pSY", + vadj->sadj->id); + return -1; + } + + if (ldp_label != MPLS_LABEL_IMPLICIT_NULL) + num_labels++; + if (response->pq_label != MPLS_LABEL_IMPLICIT_NULL) + num_labels++; + if (vadj->sr.present + && vadj->sr.label != MPLS_LABEL_IMPLICIT_NULL) + num_labels++; + + /* Allocate label stack. */ + label_stack = + XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS, + sizeof(struct mpls_label_stack) + + num_labels * sizeof(mpls_label_t)); + label_stack->num_labels = num_labels; + + /* Push label allocated by the nexthop (outer label). */ + if (ldp_label != MPLS_LABEL_IMPLICIT_NULL) + label_stack->label[i++] = ldp_label; + /* Push label allocated by the PQ node (inner label). */ + if (response->pq_label != MPLS_LABEL_IMPLICIT_NULL) + label_stack->label[i++] = response->pq_label; + /* Preserve the original Prefix-SID label when it's present. */ + if (vadj->sr.present + && vadj->sr.label != MPLS_LABEL_IMPLICIT_NULL) + label_stack->label[i++] = vadj->sr.label; + + vadj->label_stack = label_stack; + } + + isis_route_create(&vertex->N.ip.p.dest, &vertex->N.ip.p.src, + vertex->d_N, vertex->depth, &vertex->N.ip.sr, + vertex->Adj_N, true, area, + spftree->route_table_backup); + spftree->lfa.protection_counters.rlfa[vertex->N.ip.priority] += 1; + + EVENT_OFF(area->t_rlfa_rib_update); + event_add_timer(master, isis_area_verify_routes_cb, area, 2, + &area->t_rlfa_rib_update); + + return 0; +} + +void isis_rlfa_deactivate(struct isis_spftree *spftree, struct rlfa *rlfa) +{ + struct isis_area *area = spftree->area; + struct isis_vertex *vertex = rlfa->vertex; + struct route_node *rn; + + rn = route_node_lookup(spftree->route_table_backup, &rlfa->prefix); + if (!rn) + return; + isis_route_delete(area, rn, spftree->route_table_backup); + spftree->lfa.protection_counters.rlfa[vertex->N.ip.priority] -= 1; + + EVENT_OFF(area->t_rlfa_rib_update); + event_add_timer(master, isis_area_verify_routes_cb, area, 2, + &area->t_rlfa_rib_update); +} + +void isis_rlfa_list_init(struct isis_spftree *spftree) +{ + rlfa_tree_init(&spftree->lfa.remote.rlfas); +} + +void isis_rlfa_list_clear(struct isis_spftree *spftree) +{ + while (rlfa_tree_count(&spftree->lfa.remote.rlfas) > 0) { + struct rlfa *rlfa; + + rlfa = rlfa_tree_first(&spftree->lfa.remote.rlfas); + isis_rlfa_deactivate(spftree, rlfa); + rlfa_delete(spftree, rlfa); + } +} + +void isis_rlfa_process_ldp_response(struct zapi_rlfa_response *response) +{ + struct isis *isis; + struct isis_area *area; + struct isis_spftree *spftree; + struct rlfa *rlfa; + enum spf_tree_id tree_id; + uint32_t spf_run_id; + int level; + + if (response->igp.protocol != ZEBRA_ROUTE_ISIS) + return; + + isis = isis_lookup_by_vrfid(response->igp.vrf_id); + if (!isis) + return; + + area = isis_area_lookup(response->igp.isis.area_tag, + response->igp.vrf_id); + if (!area) + return; + + tree_id = response->igp.isis.spf.tree_id; + if (tree_id != SPFTREE_IPV4 && tree_id != SPFTREE_IPV6) { + zlog_warn("ISIS-LFA: invalid SPF tree ID received from LDP"); + return; + } + + level = response->igp.isis.spf.level; + if (level != ISIS_LEVEL1 && level != ISIS_LEVEL2) { + zlog_warn("ISIS-LFA: invalid IS-IS level received from LDP"); + return; + } + + spf_run_id = response->igp.isis.spf.run_id; + spftree = area->spftree[tree_id][level - 1]; + if (spftree->runcount != spf_run_id) + /* Outdated RLFA, ignore... */ + return; + + rlfa = rlfa_lookup(spftree, &response->destination); + if (!rlfa) { + zlog_warn( + "ISIS-LFA: couldn't find Remote-LFA %pFX received from LDP", + &response->destination); + return; + } + + if (response->pq_label != MPLS_INVALID_LABEL) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: activating/updating RLFA for %pFX", + &rlfa->prefix); + + if (isis_rlfa_activate(spftree, rlfa, response) != 0) + isis_rlfa_deactivate(spftree, rlfa); + } else { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: deactivating RLFA for %pFX", + &rlfa->prefix); + + isis_rlfa_deactivate(spftree, rlfa); + } +} + +void isis_ldp_rlfa_handle_client_close(struct zapi_client_close_info *info) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct isis_area *area; + struct listnode *node; + + if (!isis) + return; + + /* Check if the LDP main client session closed */ + if (info->proto != ZEBRA_ROUTE_LDP || info->session_id == 0) + return; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: LDP is down, deactivating all RLFAs"); + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) { + struct isis_spftree *spftree; + + if (!(area->is_type & level)) + continue; + if (!area->spftree[tree][level - 1]) + continue; + + spftree = area->spftree[tree][level - 1]; + isis_rlfa_list_clear(spftree); + } + } + } +} + +/** + * Check if the given SPF vertex needs protection and, if so, attempt to + * compute a Remote LFA for it. + * + * @param spftree_pc The post-convergence SPF tree + * @param vertex IS-IS SPF vertex to check + */ +void isis_rlfa_check(struct isis_spftree *spftree_pc, + struct isis_vertex *vertex) +{ + struct isis_spftree *spftree_old = spftree_pc->lfa.old.spftree; + struct rlfa *rlfa; + const struct in_addr *rtr_id_pq; + char buf[VID2STR_BUFFER]; + + if (!lfa_check_needs_protection(spftree_pc, vertex)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s %s unaffected by %s", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf)), + lfa_protected_resource2str( + &spftree_pc->lfa.protected_resource)); + + return; + } + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: computing repair path(s) of %s %s w.r.t %s", + vtype2string(vertex->type), + vid2string(vertex, buf, sizeof(buf)), + lfa_protected_resource2str( + &spftree_pc->lfa.protected_resource)); + + /* Find PQ node. */ + rtr_id_pq = rlfa_find_pq_node(spftree_pc, vertex, vertex, NULL); + if (!rtr_id_pq) { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: no acceptable PQ node found"); + return; + } + + /* Store valid RLFA and store LDP label for the PQ node. */ + rlfa = rlfa_add(spftree_old, vertex, *rtr_id_pq); + + /* Register RLFA with LDP. */ + if (isis_zebra_rlfa_register(spftree_old, rlfa) != 0) + rlfa_delete(spftree_old, rlfa); +} + +/** + * Compute the Remote LFA backup paths for a given protected interface. + * + * @param area IS-IS area + * @param spftree IS-IS SPF tree + * @param spftree_reverse IS-IS Reverse SPF tree + * @param max_metric Remote LFA maximum metric + * @param resource Protected resource + * + * @return Pointer to the post-convergence SPF tree + */ +struct isis_spftree *isis_rlfa_compute(struct isis_area *area, + struct isis_spftree *spftree, + struct isis_spftree *spftree_reverse, + uint32_t max_metric, + struct lfa_protected_resource *resource) +{ + struct isis_spftree *spftree_pc; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing remote LFAs for %s", + lfa_protected_resource2str(resource)); + + /* Create post-convergence SPF tree. */ + spftree_pc = isis_spftree_new(area, spftree->lspdb, spftree->sysid, + spftree->level, spftree->tree_id, + SPF_TYPE_RLFA, spftree->flags, + spftree->algorithm); + spftree_pc->lfa.old.spftree = spftree; + spftree_pc->lfa.old.spftree_reverse = spftree_reverse; + spftree_pc->lfa.remote.max_metric = max_metric; + spftree_pc->lfa.protected_resource = *resource; + + /* Compute the extended P-space and Q-space. */ + lfa_calc_pq_spaces(spftree_pc, resource); + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: computing the post convergence SPT w.r.t. %s", + lfa_protected_resource2str(resource)); + + /* Re-run SPF in the local node to find the post-convergence paths. */ + isis_run_spf(spftree_pc); + + return spftree_pc; +} + +/* Calculate the distance from the root node to the given IP destination. */ +static int lfa_calc_dist_destination(struct isis_spftree *spftree, + const struct isis_vertex *vertex_N, + uint32_t *distance) +{ + struct isis_vertex *vertex, *vertex_best = NULL; + + switch (spftree->family) { + case AF_INET: + for (int vtype = VTYPE_IPREACH_INTERNAL; + vtype <= VTYPE_IPREACH_TE; vtype++) { + vertex = isis_find_vertex( + &spftree->paths, &vertex_N->N.ip.p.dest, vtype); + if (!vertex) + continue; + + /* Pick vertex with the best metric. */ + if (!vertex_best || vertex_best->d_N > vertex->d_N) + vertex_best = vertex; + } + break; + case AF_INET6: + for (int vtype = VTYPE_IP6REACH_INTERNAL; + vtype <= VTYPE_IP6REACH_EXTERNAL; vtype++) { + vertex = isis_find_vertex( + &spftree->paths, &vertex_N->N.ip.p.dest, vtype); + if (!vertex) + continue; + + /* Pick vertex with the best metric. */ + if (!vertex_best || vertex_best->d_N > vertex->d_N) + vertex_best = vertex; + } + break; + default: + break; + } + + if (!vertex_best) + return -1; + + assert(VTYPE_IP(vertex_best->type)); + vertex_best = listnode_head(vertex_best->parents); + *distance = vertex_best->d_N; + + return 0; +} + +/* Calculate the distance from the root node to the given node. */ +static int lfa_calc_dist_node(struct isis_spftree *spftree, + const uint8_t *sysid, uint32_t *distance) +{ + struct isis_vertex *vertex, *vertex_best = NULL; + + for (int vtype = VTYPE_PSEUDO_IS; vtype <= VTYPE_NONPSEUDO_TE_IS; + vtype++) { + vertex = isis_find_vertex(&spftree->paths, sysid, vtype); + if (!vertex) + continue; + + /* Pick vertex with the best metric. */ + if (!vertex_best || vertex_best->d_N > vertex->d_N) + vertex_best = vertex; + } + + if (!vertex_best) + return -1; + + *distance = vertex_best->d_N; + + return 0; +} + +/* + * Check loop-free criterion (RFC 5286's inequality 1): + * - Dist_opt(N, D) < Dist_opt(N, S) + Dist_opt(S, D) + */ +static bool clfa_loop_free_check(struct isis_spftree *spftree, + struct isis_vertex *vertex_S_D, + struct isis_spf_adj *sadj_primary, + struct isis_spf_adj *sadj_N, + uint32_t *path_metric) +{ + struct isis_spf_node *node_N; + uint32_t dist_N_D; + uint32_t dist_N_S; + uint32_t dist_S_D; + + node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id); + assert(node_N); + + /* Distance from N to D. */ + if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D, + &dist_N_D) + != 0) + return false; + + /* Distance from N to S (or PN). */ + if (CHECK_FLAG(sadj_primary->flags, F_ISIS_SPF_ADJ_BROADCAST)) { + static uint8_t pn_sysid[ISIS_SYS_ID_LEN + 1]; + + memcpy(pn_sysid, sadj_primary->id, ISIS_SYS_ID_LEN + 1); + if (lfa_calc_dist_node(node_N->lfa.spftree, pn_sysid, &dist_N_S) + != 0) + return false; + } else { + static uint8_t root_sysid[ISIS_SYS_ID_LEN + 1]; + + memcpy(root_sysid, spftree->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(root_sysid) = 0; + if (lfa_calc_dist_node(node_N->lfa.spftree, root_sysid, + &dist_N_S) + != 0) + return false; + } + + /* Distance from S (or PN) to D. */ + vertex_S_D = listnode_head(vertex_S_D->parents); + dist_S_D = vertex_S_D->d_N; + if (CHECK_FLAG(sadj_primary->flags, F_ISIS_SPF_ADJ_BROADCAST)) + dist_S_D -= sadj_primary->metric; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: loop-free check: %u < %u + %u", dist_N_D, + dist_N_S, dist_S_D); + + if (dist_N_D < (dist_N_S + dist_S_D)) { + *path_metric = sadj_N->metric + dist_N_D; + return true; + } + + return false; +} + +/* + * Check loop-free criterion (RFC 5286's inequality 2): + * - Distance_opt(N, D) < Distance_opt(S, D) + */ +static bool clfa_downstream_check(struct isis_spftree *spftree, + struct isis_vertex *vertex_S_D, + struct isis_spf_adj *sadj_N) +{ + struct isis_spf_node *node_N; + uint32_t dist_N_D; + uint32_t dist_S_D; + + node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id); + assert(node_N); + + /* Distance from N to D. */ + if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D, + &dist_N_D) + != 0) + return false; + + /* Distance from S (or PN) to D. */ + vertex_S_D = listnode_head(vertex_S_D->parents); + dist_S_D = vertex_S_D->d_N; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: downstream check: %u < %u", dist_N_D, + dist_S_D); + + if (dist_N_D < dist_S_D) + return true; + + return false; +} + +/* + * Check loop-free criterion (RFC 5286's inequality 3): + * - Dist_opt(N, D) < Dist_opt(N, E) + Dist_opt(E, D) + */ +static bool clfa_node_protecting_check(struct isis_spftree *spftree, + struct isis_vertex *vertex_S_D, + struct isis_spf_adj *sadj_N, + struct isis_spf_adj *sadj_E) +{ + struct isis_spf_node *node_N, *node_E; + uint32_t dist_N_D; + uint32_t dist_N_E; + uint32_t dist_E_D; + + node_N = isis_spf_node_find(&spftree->adj_nodes, sadj_N->id); + assert(node_N); + node_E = isis_spf_node_find(&spftree->adj_nodes, sadj_E->id); + assert(node_E); + + /* Distance from N to D. */ + if (lfa_calc_dist_destination(node_N->lfa.spftree, vertex_S_D, + &dist_N_D) + != 0) + return false; + + /* Distance from N to E. */ + if (lfa_calc_dist_node(node_N->lfa.spftree, node_E->sysid, &dist_N_E) + != 0) + return false; + + /* Distance from E to D. */ + if (lfa_calc_dist_destination(node_E->lfa.spftree, vertex_S_D, + &dist_E_D) + != 0) + return false; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: node protecting check: %u < %u + %u", + dist_N_D, dist_N_E, dist_E_D); + + return (dist_N_D < (dist_N_E + dist_E_D)); +} + +static struct list * +isis_lfa_tiebreakers(struct isis_area *area, struct isis_spftree *spftree, + struct lfa_protected_resource *resource, + struct isis_vertex *vertex, + struct isis_spf_adj *sadj_primary, struct list *lfa_list) +{ + struct lfa_tiebreaker *tie_b; + int level = spftree->level; + struct list *filtered_lfa_list; + struct list *tent_lfa_list; + + filtered_lfa_list = list_dup(lfa_list); + filtered_lfa_list->del = NULL; + + if (listcount(filtered_lfa_list) == 1) + return filtered_lfa_list; + + /* Check tiebreakers in ascending order by index. */ + frr_each (lfa_tiebreaker_tree, &area->lfa_tiebreakers[level - 1], + tie_b) { + struct isis_vertex_adj *lfa; + struct listnode *node, *nnode; + uint32_t best_metric = UINT32_MAX; + + tent_lfa_list = list_dup(filtered_lfa_list); + + switch (tie_b->type) { + case LFA_TIEBREAKER_DOWNSTREAM: + for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode, + lfa)) { + if (clfa_downstream_check(spftree, vertex, + lfa->sadj)) + continue; + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: LFA %s doesn't satisfy the downstream condition", + print_sys_hostname( + lfa->sadj->id)); + listnode_delete(tent_lfa_list, lfa); + } + break; + case LFA_TIEBREAKER_LOWEST_METRIC: + /* Find the best metric first. */ + for (ALL_LIST_ELEMENTS_RO(tent_lfa_list, node, lfa)) { + if (lfa->lfa_metric < best_metric) + best_metric = lfa->lfa_metric; + } + + /* Remove LFAs that don't have the best metric. */ + for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode, + lfa)) { + if (lfa->lfa_metric == best_metric) + continue; + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: LFA %s doesn't have the lowest cost metric", + print_sys_hostname( + lfa->sadj->id)); + listnode_delete(tent_lfa_list, lfa); + } + break; + case LFA_TIEBREAKER_NODE_PROTECTING: + for (ALL_LIST_ELEMENTS(tent_lfa_list, node, nnode, + lfa)) { + if (clfa_node_protecting_check(spftree, vertex, + lfa->sadj, + sadj_primary)) + continue; + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: LFA %s doesn't provide node protection", + print_sys_hostname( + lfa->sadj->id)); + listnode_delete(tent_lfa_list, lfa); + } + break; + } + + /* + * Decide what to do next based on the number of remaining LFAs. + */ + switch (listcount(tent_lfa_list)) { + case 0: + /* + * Ignore this tie-breaker since it excluded all LFAs. + * Move on to the next one (if any). + */ + list_delete(&tent_lfa_list); + break; + case 1: + /* Finish tie-breaking once we get a single LFA. */ + list_delete(&filtered_lfa_list); + filtered_lfa_list = tent_lfa_list; + return filtered_lfa_list; + default: + /* + * We still have two or more LFAs. Move on to the next + * tie-breaker (if any). + */ + list_delete(&filtered_lfa_list); + filtered_lfa_list = tent_lfa_list; + break; + } + } + + return filtered_lfa_list; +} + +void isis_lfa_compute(struct isis_area *area, struct isis_circuit *circuit, + struct isis_spftree *spftree, + struct lfa_protected_resource *resource) +{ + struct isis_vertex *vertex, *parent_vertex; + struct listnode *vnode, *snode; + int level = spftree->level; + + resource->type = LFA_LINK_PROTECTION; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: computing local LFAs for %s", + lfa_protected_resource2str(resource)); + + for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, vnode, vertex)) { + struct list *lfa_list; + struct list *filtered_lfa_list; + struct isis_spf_adj *sadj_N; + struct isis_vertex_adj *vadj_primary; + struct isis_spf_adj *sadj_primary; + bool allow_ecmp; + uint32_t prefix_metric, best_metric = UINT32_MAX; + char buf[VID2STR_BUFFER]; + + if (!VTYPE_IP(vertex->type)) + continue; + + vid2string(vertex, buf, sizeof(buf)); + + if (!spf_vertex_check_is_affected(vertex, spftree->sysid, + resource)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s %s unaffected by %s", + vtype2string(vertex->type), buf, + lfa_protected_resource2str(resource)); + continue; + } + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: checking %s %s w.r.t %s", + vtype2string(vertex->type), buf, + lfa_protected_resource2str(resource)); + + if (vertex->N.ip.priority + > area->lfa_priority_limit[level - 1]) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: skipping computing LFAs due to low prefix priority"); + continue; + } + + vadj_primary = listnode_head(vertex->Adj_N); + if (!vadj_primary) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: skipping computing LFAs due to no adjacencies"); + continue; + } + sadj_primary = vadj_primary->sadj; + + parent_vertex = listnode_head(vertex->parents); + assert(parent_vertex); + prefix_metric = vertex->d_N - parent_vertex->d_N; + + /* + * Loop over list of SPF adjacencies and compute a list of + * preliminary LFAs. + */ + lfa_list = list_new(); + lfa_list->del = isis_vertex_adj_free; + for (ALL_LIST_ELEMENTS_RO(spftree->sadj_list, snode, sadj_N)) { + uint32_t lfa_metric, path_metric; + struct isis_vertex_adj *lfa; + struct isis_prefix_sid *psid = NULL; + bool last_hop = false; + + /* Skip pseudonodes. */ + if (LSP_PSEUDO_ID(sadj_N->id)) + continue; + + /* + * Skip nexthops that are along a link whose cost is + * infinite. + */ + if (CHECK_FLAG(sadj_N->flags, + F_ISIS_SPF_ADJ_METRIC_INFINITY)) + continue; + + /* Skip nexthops that have the overload bit set. */ + if (spftree->mtid != ISIS_MT_IPV4_UNICAST) { + struct isis_mt_router_info *mt_router_info; + + mt_router_info = + isis_tlvs_lookup_mt_router_info( + sadj_N->lsp->tlvs, + spftree->mtid); + if (mt_router_info && mt_router_info->overload) + continue; + } else if (ISIS_MASK_LSP_OL_BIT( + sadj_N->lsp->hdr.lsp_bits)) + continue; + + /* Skip primary nexthop. */ + if (spf_adj_check_is_affected(sadj_N, resource, NULL, + false)) + continue; + + /* Skip excluded interfaces as per the configuration. */ + if (circuit + && isis_lfa_excluded_iface_check( + circuit, level, + sadj_N->adj->circuit->interface->name)) + continue; + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: checking candidate LFA %s", + print_sys_hostname(sadj_N->id)); + + /* Check loop-free criterion. */ + if (!clfa_loop_free_check(spftree, vertex, sadj_primary, + sadj_N, &path_metric)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: LFA condition not met for %s", + print_sys_hostname(sadj_N->id)); + continue; + } + + lfa_metric = path_metric + prefix_metric; + if (lfa_metric < best_metric) + best_metric = lfa_metric; + + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: %s is a valid loop-free alternate", + print_sys_hostname(sadj_N->id)); + + if (vertex->N.ip.sr.present) { + psid = &vertex->N.ip.sr.sid; + if (path_metric == sadj_N->metric) + last_hop = true; + } + lfa = isis_vertex_adj_add(spftree, vertex, lfa_list, + sadj_N, psid, last_hop); + lfa->lfa_metric = lfa_metric; + } + + if (list_isempty(lfa_list)) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: no valid local LFAs found"); + list_delete(&lfa_list); + continue; + } + + SET_FLAG(vertex->flags, F_ISIS_VERTEX_LFA_PROTECTED); + + /* Check tie-breakers. */ + filtered_lfa_list = + isis_lfa_tiebreakers(area, spftree, resource, vertex, + sadj_primary, lfa_list); + + /* Create backup route using the best LFAs. */ + allow_ecmp = area->lfa_load_sharing[level - 1]; + isis_route_create(&vertex->N.ip.p.dest, &vertex->N.ip.p.src, + best_metric, vertex->depth, &vertex->N.ip.sr, + filtered_lfa_list, allow_ecmp, area, + spftree->route_table_backup); + spftree->lfa.protection_counters.lfa[vertex->N.ip.priority] += + 1; + + list_delete(&filtered_lfa_list); + list_delete(&lfa_list); + } +} + +static void isis_spf_run_tilfa(struct isis_area *area, + struct isis_circuit *circuit, + struct isis_spftree *spftree, + struct isis_spftree *spftree_reverse, + struct lfa_protected_resource *resource) +{ + struct isis_spftree *spftree_pc_link; + struct isis_spftree *spftree_pc_node; + + /* Compute node protecting repair paths first (if necessary). */ + if (circuit->tilfa_node_protection[spftree->level - 1]) { + resource->type = LFA_NODE_PROTECTION; + spftree_pc_node = isis_tilfa_compute(area, spftree, + spftree_reverse, resource); + isis_spftree_del(spftree_pc_node); + + /* don't do link protection unless link-fallback is configured + */ + if (!circuit->tilfa_link_fallback[spftree->level - 1]) + return; + } + + /* Compute link protecting repair paths. */ + resource->type = LFA_LINK_PROTECTION; + spftree_pc_link = + isis_tilfa_compute(area, spftree, spftree_reverse, resource); + isis_spftree_del(spftree_pc_link); +} + +/** + * Run the LFA/RLFA/TI-LFA algorithms for all protected interfaces. + * + * @param area IS-IS area + * @param spftree IS-IS SPF tree + */ +void isis_spf_run_lfa(struct isis_area *area, struct isis_spftree *spftree) +{ + struct isis_spftree *spftree_reverse = NULL; + struct isis_circuit *circuit; + struct listnode *node; + int level = spftree->level; + + /* Run reverse SPF locally. */ + if (area->rlfa_protected_links[level - 1] > 0 + || area->tilfa_protected_links[level - 1] > 0) + spftree_reverse = isis_spf_reverse_run(spftree); + + /* Run forward SPF on all adjacent routers. */ + isis_spf_run_neighbors(spftree); + + /* Check which interfaces are protected. */ + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + struct lfa_protected_resource resource = {}; + struct isis_adjacency *adj; + static uint8_t null_sysid[ISIS_SYS_ID_LEN + 1]; + + if (!(circuit->is_type & level)) + continue; + + if (!circuit->lfa_protection[level - 1] + && !circuit->tilfa_protection[level - 1]) + continue; + + /* Fill in the protected resource. */ + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + if (level == ISIS_LEVEL1) + memcpy(resource.adjacency, + circuit->u.bc.l1_desig_is, + ISIS_SYS_ID_LEN + 1); + else + memcpy(resource.adjacency, + circuit->u.bc.l2_desig_is, + ISIS_SYS_ID_LEN + 1); + /* Do nothing if no DR was elected yet. */ + if (!memcmp(resource.adjacency, null_sysid, + ISIS_SYS_ID_LEN + 1)) + continue; + break; + case CIRCUIT_T_P2P: + adj = circuit->u.p2p.neighbor; + if (!adj) + continue; + memcpy(resource.adjacency, adj->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(resource.adjacency) = 0; + break; + default: + continue; + } + + if (circuit->lfa_protection[level - 1]) { + /* Run local LFA. */ + isis_lfa_compute(area, circuit, spftree, &resource); + + if (circuit->rlfa_protection[level - 1]) { + struct isis_spftree *spftree_pc; + uint32_t max_metric; + + /* Run remote LFA. */ + assert(spftree_reverse); + max_metric = + circuit->rlfa_max_metric[level - 1]; + spftree_pc = isis_rlfa_compute( + area, spftree, spftree_reverse, + max_metric, &resource); + listnode_add(spftree->lfa.remote.pc_spftrees, + spftree_pc); + } + } else if (circuit->tilfa_protection[level - 1]) { + /* Run TI-LFA. */ + assert(spftree_reverse); + isis_spf_run_tilfa(area, circuit, spftree, + spftree_reverse, &resource); + } + } + + if (spftree_reverse) + isis_spftree_del(spftree_reverse); +} diff --git a/isisd/isis_lfa.h b/isisd/isis_lfa.h new file mode 100644 index 0000000..0ba1c1c --- /dev/null +++ b/isisd/isis_lfa.h @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_ISIS_LFA_H +#define _FRR_ISIS_LFA_H + +#include "lib/typesafe.h" +#include "lib/zclient.h" +#include "lib/memory.h" + +DECLARE_MTYPE(ISIS_NEXTHOP_LABELS); + +PREDECL_RBTREE_UNIQ(lfa_tiebreaker_tree); +PREDECL_RBTREE_UNIQ(rlfa_tree); + +enum lfa_tiebreaker_type { + LFA_TIEBREAKER_DOWNSTREAM = 0, + LFA_TIEBREAKER_LOWEST_METRIC, + LFA_TIEBREAKER_NODE_PROTECTING, +}; + +struct lfa_tiebreaker { + struct lfa_tiebreaker_tree_item entry; + uint8_t index; + enum lfa_tiebreaker_type type; + struct isis_area *area; +}; +int lfa_tiebreaker_cmp(const struct lfa_tiebreaker *a, + const struct lfa_tiebreaker *b); +DECLARE_RBTREE_UNIQ(lfa_tiebreaker_tree, struct lfa_tiebreaker, entry, + lfa_tiebreaker_cmp); + +struct rlfa { + struct rlfa_tree_item entry; + struct prefix prefix; + struct isis_vertex *vertex; + struct in_addr pq_address; +}; +int rlfa_cmp(const struct rlfa *a, const struct rlfa *b); +DECLARE_RBTREE_UNIQ(rlfa_tree, struct rlfa, entry, rlfa_cmp); + +enum isis_tilfa_sid_type { + TILFA_SID_PREFIX = 1, + TILFA_SID_ADJ, +}; + +struct isis_tilfa_sid { + enum isis_tilfa_sid_type type; + union { + struct { + uint32_t value; + bool remote; + uint8_t remote_sysid[ISIS_SYS_ID_LEN]; + } index; + mpls_label_t label; + } value; +}; + +enum spf_prefix_priority { + SPF_PREFIX_PRIO_CRITICAL = 0, + SPF_PREFIX_PRIO_HIGH, + SPF_PREFIX_PRIO_MEDIUM, + SPF_PREFIX_PRIO_LOW, + SPF_PREFIX_PRIO_MAX, +}; + +struct spf_prefix_priority_acl { + char *name; + struct access_list *list_v4; + struct access_list *list_v6; +}; + +RB_HEAD(isis_spf_nodes, isis_spf_node); +RB_PROTOTYPE(isis_spf_nodes, isis_spf_node, entry, isis_spf_node_compare) +struct isis_spf_node { + RB_ENTRY(isis_spf_node) entry; + + /* Node's System ID. */ + uint8_t sysid[ISIS_SYS_ID_LEN]; + + /* Local adjacencies over which this node is reachable. */ + struct list *adjacencies; + + /* Best metric of all adjacencies used to reach this node. */ + uint32_t best_metric; + + struct { + /* Node's forward SPT. */ + struct isis_spftree *spftree; + + /* Node's reverse SPT. */ + struct isis_spftree *spftree_reverse; + + /* Node's P-space. */ + struct isis_spf_nodes p_space; + } lfa; +}; + +enum lfa_protection_type { + LFA_LINK_PROTECTION = 1, + LFA_NODE_PROTECTION, +}; + +struct lfa_protected_resource { + /* The protection type. */ + enum lfa_protection_type type; + + /* The protected adjacency (might be a pseudonode). */ + uint8_t adjacency[ISIS_SYS_ID_LEN + 1]; + + /* List of nodes reachable over the protected interface. */ + struct isis_spf_nodes nodes; +}; + +/* Forward declaration(s). */ +struct isis_vertex; + +/* Prototypes. */ +void isis_spf_node_list_init(struct isis_spf_nodes *nodes); +void isis_spf_node_list_clear(struct isis_spf_nodes *nodes); +struct isis_spf_node *isis_spf_node_new(struct isis_spf_nodes *nodes, + const uint8_t *sysid); +struct isis_spf_node *isis_spf_node_find(const struct isis_spf_nodes *nodes, + const uint8_t *sysid); +void isis_lfa_tiebreakers_init(struct isis_area *area, int level); +void isis_lfa_tiebreakers_clear(struct isis_area *area, int level); +struct lfa_tiebreaker *isis_lfa_tiebreaker_add(struct isis_area *area, + int level, uint8_t index, + enum lfa_tiebreaker_type type); +void isis_lfa_tiebreaker_delete(struct isis_area *area, int level, + struct lfa_tiebreaker *tie_b); +void isis_lfa_excluded_ifaces_init(struct isis_circuit *circuit, int level); +void isis_lfa_excluded_ifaces_clear(struct isis_circuit *circuit, int level); +void isis_lfa_excluded_iface_add(struct isis_circuit *circuit, int level, + const char *ifname); +void isis_lfa_excluded_iface_delete(struct isis_circuit *circuit, int level, + const char *ifname); +bool isis_lfa_excluded_iface_check(struct isis_circuit *circuit, int level, + const char *ifname); +bool isis_lfa_excise_adj_check(const struct isis_spftree *spftree, + const uint8_t *id); +bool isis_lfa_excise_node_check(const struct isis_spftree *spftree, + const uint8_t *id); +struct isis_spftree *isis_spf_reverse_run(const struct isis_spftree *spftree); +int isis_spf_run_neighbors(struct isis_spftree *spftree); +int isis_rlfa_activate(struct isis_spftree *spftree, struct rlfa *rlfa, + struct zapi_rlfa_response *response); +void isis_rlfa_deactivate(struct isis_spftree *spftree, struct rlfa *rlfa); +void isis_rlfa_list_init(struct isis_spftree *spftree); +void isis_rlfa_list_clear(struct isis_spftree *spftree); +void isis_rlfa_process_ldp_response(struct zapi_rlfa_response *response); +void isis_ldp_rlfa_handle_client_close(struct zapi_client_close_info *info); +void isis_rlfa_check(struct isis_spftree *spftree, struct isis_vertex *vertex); +struct isis_spftree *isis_rlfa_compute(struct isis_area *area, + struct isis_spftree *spftree, + struct isis_spftree *spftree_reverse, + uint32_t max_metric, + struct lfa_protected_resource *resource); +void isis_lfa_compute(struct isis_area *area, struct isis_circuit *circuit, + struct isis_spftree *spftree, + struct lfa_protected_resource *resource); +void isis_spf_run_lfa(struct isis_area *area, struct isis_spftree *spftree); +int isis_tilfa_check(struct isis_spftree *spftree, struct isis_vertex *vertex); +struct isis_spftree * +isis_tilfa_compute(struct isis_area *area, struct isis_spftree *spftree, + struct isis_spftree *spftree_reverse, + struct lfa_protected_resource *protected_resource); + +#endif /* _FRR_ISIS_LFA_H */ diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c new file mode 100644 index 0000000..c98cee0 --- /dev/null +++ b/isisd/isis_lsp.c @@ -0,0 +1,2504 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_lsp.c + * LSP processing + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2013-2015 Christian Franke + */ + +#include + +#include "linklist.h" +#include "frrevent.h" +#include "vty.h" +#include "stream.h" +#include "memory.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "hash.h" +#include "if.h" +#include "checksum.h" +#include "md5.h" +#include "table.h" +#include "srcdest_table.h" +#include "lib_errors.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_te.h" +#include "isisd/isis_sr.h" +#include "isisd/fabricd.h" +#include "isisd/isis_tx_queue.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_flex_algo.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_LSP, "ISIS LSP"); + +static void lsp_refresh(struct event *thread); +static void lsp_l1_refresh_pseudo(struct event *thread); +static void lsp_l2_refresh_pseudo(struct event *thread); + +static void lsp_destroy(struct isis_lsp *lsp); + +static bool device_startup; + +int lsp_id_cmp(uint8_t *id1, uint8_t *id2) +{ + return memcmp(id1, id2, ISIS_SYS_ID_LEN + 2); +} + +int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b) +{ + return memcmp(a->hdr.lsp_id, b->hdr.lsp_id, sizeof(a->hdr.lsp_id)); +} + +void lsp_db_init(struct lspdb_head *head) +{ + lspdb_init(head); +} + +void lsp_db_fini(struct lspdb_head *head) +{ + struct isis_lsp *lsp; + + while ((lsp = lspdb_pop(head))) + lsp_destroy(lsp); + lspdb_fini(head); +} + +struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id) +{ + struct isis_lsp searchfor; + memcpy(searchfor.hdr.lsp_id, id, sizeof(searchfor.hdr.lsp_id)); + + return lspdb_find(head, &searchfor); +} + +static void lsp_clear_data(struct isis_lsp *lsp) +{ + if (!lsp) + return; + + isis_free_tlvs(lsp->tlvs); + lsp->tlvs = NULL; +} + +static void lsp_remove_frags(struct lspdb_head *head, struct list *frags); + +static void lsp_destroy(struct isis_lsp *lsp) +{ + struct listnode *cnode; + struct isis_circuit *circuit; + + if (!lsp) + return; + + for (ALL_LIST_ELEMENTS_RO(lsp->area->circuit_list, cnode, circuit)) + isis_tx_queue_del(circuit->tx_queue, lsp); + + ISIS_FLAGS_CLEAR_ALL(lsp->SSNflags); + + isis_te_lsp_event(lsp, LSP_DEL); + + lsp_clear_data(lsp); + + if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) { + if (lsp->lspu.frags) { + lsp_remove_frags(&lsp->area->lspdb[lsp->level - 1], + lsp->lspu.frags); + list_delete(&lsp->lspu.frags); + } + } else { + if (lsp->lspu.zero_lsp + && lsp->lspu.zero_lsp->lspu.frags) { + listnode_delete(lsp->lspu.zero_lsp->lspu.frags, lsp); + } + } + + isis_spf_schedule(lsp->area, lsp->level); + + if (lsp->pdu) + stream_free(lsp->pdu); + + fabricd_lsp_free(lsp); + XFREE(MTYPE_ISIS_LSP, lsp); +} + +/* + * Remove all the frags belonging to the given lsp + */ +static void lsp_remove_frags(struct lspdb_head *head, struct list *frags) +{ + struct listnode *lnode, *lnnode; + struct isis_lsp *lsp; + + for (ALL_LIST_ELEMENTS(frags, lnode, lnnode, lsp)) { + lsp = lsp_search(head, lsp->hdr.lsp_id); + lspdb_del(head, lsp); + lsp_destroy(lsp); + } +} + +void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id) +{ + struct isis_lsp *lsp; + + lsp = lsp_search(head, id); + if (lsp) { + lspdb_del(head, lsp); + /* + * If this is a zero lsp, remove all the frags now + */ + if (LSP_FRAGMENT(lsp->hdr.lsp_id) == 0) { + if (lsp->lspu.frags) + lsp_remove_frags(head, lsp->lspu.frags); + } else { + /* + * else just remove this frag, from the zero lsps' frag + * list + */ + if (lsp->lspu.zero_lsp + && lsp->lspu.zero_lsp->lspu.frags) + listnode_delete(lsp->lspu.zero_lsp->lspu.frags, + lsp); + } + lsp_destroy(lsp); + } +} + +/* + * Compares a LSP to given values + * Params are given in net order + */ +int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno, + uint16_t checksum, uint16_t rem_lifetime) +{ + if (lsp->hdr.seqno == seqno && lsp->hdr.checksum == checksum + && ((lsp->hdr.rem_lifetime == 0 && rem_lifetime == 0) + || (lsp->hdr.rem_lifetime != 0 && rem_lifetime != 0))) { + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, lsp->hdr.lsp_id, lsp->hdr.seqno, + lsp->hdr.checksum, lsp->hdr.rem_lifetime); + zlog_debug( + "ISIS-Snp (%s): is equal to ours seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, seqno, checksum, rem_lifetime); + } + return LSP_EQUAL; + } + + /* + * LSPs with identical checksums should only be treated as newer if: + * a) The current LSP has a remaining lifetime != 0 and the other LSP + * has a + * remaining lifetime == 0. In this case, we should participate in + * the purge + * and should not treat the current LSP with remaining lifetime == 0 + * as older. + * b) The LSP has an incorrect checksum. In this case, we need to react + * as given + * in 7.3.16.2. + */ + if (seqno > lsp->hdr.seqno + || (seqno == lsp->hdr.seqno + && ((lsp->hdr.rem_lifetime != 0 && rem_lifetime == 0) + || (lsp->hdr.checksum != checksum + && lsp->hdr.rem_lifetime)))) { + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, lsp->hdr.lsp_id, seqno, checksum, + rem_lifetime); + zlog_debug( + "ISIS-Snp (%s): is newer than ours seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime); + } + return LSP_NEWER; + } + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Compare LSP %pLS seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, lsp->hdr.lsp_id, seqno, checksum, + rem_lifetime); + zlog_debug( + "ISIS-Snp (%s): is older than ours seq 0x%08x, cksum 0x%04hx, lifetime %hus", + areatag, lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime); + } + + return LSP_OLDER; +} + +static void put_lsp_hdr(struct isis_lsp *lsp, size_t *len_pointer, bool keep) +{ + uint8_t pdu_type = + (lsp->level == IS_LEVEL_1) ? L1_LINK_STATE : L2_LINK_STATE; + struct isis_lsp_hdr *hdr = &lsp->hdr; + struct stream *stream = lsp->pdu; + size_t orig_getp = 0, orig_endp = 0; + + if (keep) { + orig_getp = stream_get_getp(lsp->pdu); + orig_endp = stream_get_endp(lsp->pdu); + } + + stream_set_getp(lsp->pdu, 0); + stream_set_endp(lsp->pdu, 0); + + fill_fixed_hdr(pdu_type, stream); + + if (len_pointer) + *len_pointer = stream_get_endp(stream); + stream_putw(stream, hdr->pdu_len); + stream_putw(stream, hdr->rem_lifetime); + stream_put(stream, hdr->lsp_id, sizeof(hdr->lsp_id)); + stream_putl(stream, hdr->seqno); + stream_putw(stream, hdr->checksum); + stream_putc(stream, hdr->lsp_bits); + + if (keep) { + stream_set_endp(lsp->pdu, orig_endp); + stream_set_getp(lsp->pdu, orig_getp); + } +} + +static void lsp_add_auth(struct isis_lsp *lsp) +{ + struct isis_passwd *passwd; + passwd = (lsp->level == IS_LEVEL_1) ? &lsp->area->area_passwd + : &lsp->area->domain_passwd; + isis_tlvs_add_auth(lsp->tlvs, passwd); +} + +static void lsp_pack_pdu(struct isis_lsp *lsp) +{ + if (!lsp->tlvs) + lsp->tlvs = isis_alloc_tlvs(); + + lsp_add_auth(lsp); + + size_t len_pointer; + put_lsp_hdr(lsp, &len_pointer, false); + isis_pack_tlvs(lsp->tlvs, lsp->pdu, len_pointer, false, true); + + lsp->hdr.pdu_len = stream_get_endp(lsp->pdu); + lsp->hdr.checksum = + ntohs(fletcher_checksum(STREAM_DATA(lsp->pdu) + 12, + stream_get_endp(lsp->pdu) - 12, 12)); +} + +void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno) +{ + uint32_t newseq; + + if (seqno == 0 || lsp->hdr.seqno > seqno) + newseq = lsp->hdr.seqno + 1; + else + newseq = seqno + 1; + +#ifndef FABRICD + /* check for overflow */ + if (newseq < lsp->hdr.seqno) { + /* send northbound notification */ + lsp->area->lsp_exceeded_max_counter++; + isis_notif_lsp_exceed_max(lsp->area, lsp->hdr.lsp_id); + } +#endif /* ifndef FABRICD */ + + lsp->hdr.seqno = newseq; + + lsp_pack_pdu(lsp); + isis_spf_schedule(lsp->area, lsp->level); + isis_te_lsp_event(lsp, LSP_INC); +} + +static void lsp_purge_add_poi(struct isis_lsp *lsp, + const uint8_t *sender) +{ + if (lsp->area == NULL) + return; + + if (!lsp->area->purge_originator) + return; + + /* add purge originator identification */ + if (!lsp->tlvs) + lsp->tlvs = isis_alloc_tlvs(); + isis_tlvs_set_purge_originator(lsp->tlvs, lsp->area->isis->sysid, + sender); + isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get()); +} + +static void lsp_purge(struct isis_lsp *lsp, int level, + const uint8_t *sender) +{ + /* reset stream */ + lsp_clear_data(lsp); + stream_reset(lsp->pdu); + + /* update header */ + lsp->hdr.checksum = 0; + lsp->hdr.rem_lifetime = 0; + lsp->level = level; + lsp->age_out = lsp->area->max_lsp_lifetime[level - 1]; + lsp->area->lsp_purge_count[level - 1]++; + + lsp_purge_add_poi(lsp, sender); + + lsp_pack_pdu(lsp); + lsp_flood(lsp, NULL); +} + +/* + * Generates checksum for LSP and its frags + */ +static void lsp_seqno_update(struct isis_lsp *lsp0) +{ + struct isis_lsp *lsp; + struct listnode *node; + + lsp_inc_seqno(lsp0, 0); + + if (!lsp0->lspu.frags) + return; + + for (ALL_LIST_ELEMENTS_RO(lsp0->lspu.frags, node, lsp)) { + if (lsp->tlvs) + lsp_inc_seqno(lsp, 0); + else if (lsp->hdr.rem_lifetime) { + /* Purge should only be applied when the fragment has + * non-zero remaining lifetime. + */ + lsp_purge(lsp, lsp0->level, NULL); + } + } + + return; +} + +bool isis_level2_adj_up(struct isis_area *area) +{ + struct listnode *node, *cnode; + struct isis_circuit *circuit; + struct list *adjdb; + struct isis_adjacency *adj; + + if (area->is_type == IS_LEVEL_1) + return false; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + adjdb = circuit->u.bc.adjdb[1]; + if (!adjdb || !adjdb->count) + continue; + + for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) { + if (adj->level != ISIS_ADJ_LEVEL1 + && adj->adj_state == ISIS_ADJ_UP) + return true; + } + } else if (circuit->circ_type == CIRCUIT_T_P2P + && circuit->u.p2p.neighbor) { + adj = circuit->u.p2p.neighbor; + if (adj->level != ISIS_ADJ_LEVEL1 + && adj->adj_state == ISIS_ADJ_UP) + return true; + } + } + + return false; +} + +/* + * Unset the overload bit after the timer expires + */ +void set_overload_on_start_timer(struct event *thread) +{ + struct isis_area *area = EVENT_ARG(thread); + assert(area); + + area->t_overload_on_startup_timer = NULL; + + /* Check if set-overload-bit is not currently configured */ + if (!area->overload_configured) + isis_area_overload_bit_set(area, false); +} + +static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit, + struct isis_area *area) +{ + uint8_t lsp_bits = 0; + if (area->is_type == IS_LEVEL_1) + lsp_bits = IS_LEVEL_1; + else + lsp_bits = IS_LEVEL_1_AND_2; + if (overload_bit) + lsp_bits |= overload_bit; + + /* only set the attach bit if we are a level-1-2 router and this is + * a level-1 LSP and we have a level-2 adjacency up from another area + */ + if (area->is_type == IS_LEVEL_1_AND_2 && level == IS_LEVEL_1 + && attached_bit && isis_level2_adj_up(area)) + lsp_bits |= LSPBIT_ATT; + return lsp_bits; +} + +static void lsp_update_data(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, + struct isis_tlvs *tlvs, struct stream *stream, + struct isis_area *area, int level) +{ + /* free the old lsp data */ + lsp_clear_data(lsp); + + /* copying only the relevant part of our stream */ + if (lsp->pdu != NULL) + stream_free(lsp->pdu); + lsp->pdu = stream_dup(stream); + + memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr)); + lsp->area = area; + lsp->level = level; + lsp->age_out = ZERO_AGE_LIFETIME; + lsp->installed = time(NULL); + + lsp->tlvs = tlvs; + + if (area->dynhostname && lsp->tlvs->hostname + && lsp->hdr.rem_lifetime) { + isis_dynhn_insert( + area->isis, lsp->hdr.lsp_id, lsp->tlvs->hostname, + (lsp->hdr.lsp_bits & LSPBIT_IST) == IS_LEVEL_1_AND_2 + ? IS_LEVEL_2 + : IS_LEVEL_1); + } + + return; +} + +static void lsp_link_fragment(struct isis_lsp *lsp, struct isis_lsp *lsp0) +{ + if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) { + /* zero lsp -> create list to store fragments */ + lsp->lspu.frags = list_new(); + } else { + /* fragment -> set backpointer and add to zero lsps list */ + assert(lsp0); + lsp->lspu.zero_lsp = lsp0; + listnode_add(lsp0->lspu.frags, lsp); + } +} + +void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, + struct isis_tlvs *tlvs, struct stream *stream, + struct isis_area *area, int level, bool confusion) +{ + if (lsp->own_lsp) { + flog_err( + EC_LIB_DEVELOPMENT, + "ISIS-Upd (%s): BUG updating LSP %pLS still marked as own LSP", + area->area_tag, lsp->hdr.lsp_id); + lsp_clear_data(lsp); + lsp->own_lsp = 0; + } + + if (confusion) { + lsp_purge(lsp, level, NULL); + } else { + lsp_update_data(lsp, hdr, tlvs, stream, area, level); + } + + if (LSP_FRAGMENT(lsp->hdr.lsp_id) && !lsp->lspu.zero_lsp) { + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + struct isis_lsp *lsp0; + + memcpy(lspid, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(lspid) = 0; + lsp0 = lsp_search(&area->lspdb[level - 1], lspid); + if (lsp0) + lsp_link_fragment(lsp, lsp0); + } + + if (lsp->hdr.seqno) { + isis_spf_schedule(lsp->area, lsp->level); + isis_te_lsp_event(lsp, LSP_UPD); + } +} + +/* creation of LSP directly from what we received */ +struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr, + struct isis_tlvs *tlvs, + struct stream *stream, struct isis_lsp *lsp0, + struct isis_area *area, int level) +{ + struct isis_lsp *lsp; + + lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); + lsp_update_data(lsp, hdr, tlvs, stream, area, level); + lsp_link_fragment(lsp, lsp0); + + return lsp; +} + +static void lsp_adjust_stream(struct isis_lsp *lsp) +{ + if (lsp->pdu) { + if (STREAM_SIZE(lsp->pdu) == LLC_LEN + lsp->area->lsp_mtu) + return; + stream_free(lsp->pdu); + } + + lsp->pdu = stream_new(LLC_LEN + lsp->area->lsp_mtu); +} + +struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id, + uint16_t rem_lifetime, uint32_t seqno, + uint8_t lsp_bits, uint16_t checksum, + struct isis_lsp *lsp0, int level) +{ + struct isis_lsp *lsp; + + lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); + lsp->area = area; + + lsp_adjust_stream(lsp); + + /* Minimal LSP PDU size */ + lsp->hdr.pdu_len = ISIS_FIXED_HDR_LEN + ISIS_LSP_HDR_LEN; + memcpy(lsp->hdr.lsp_id, lsp_id, sizeof(lsp->hdr.lsp_id)); + lsp->hdr.checksum = checksum; + lsp->hdr.seqno = seqno; + lsp->hdr.rem_lifetime = rem_lifetime; + lsp->hdr.lsp_bits = lsp_bits; + lsp->level = level; + lsp->age_out = ZERO_AGE_LIFETIME; + lsp_link_fragment(lsp, lsp0); + put_lsp_hdr(lsp, NULL, false); + + if (IS_DEBUG_EVENTS) + zlog_debug("New LSP with ID %pLS len %d seqnum %08x", lsp_id, + lsp->hdr.pdu_len, lsp->hdr.seqno); + + return lsp; +} + +void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp) +{ + lspdb_add(head, lsp); + if (lsp->hdr.seqno) { + isis_spf_schedule(lsp->area, lsp->level); + isis_te_lsp_event(lsp, LSP_ADD); + } +} + +/* + * Build a list of LSPs with non-zero ht and seqno bounded by start and stop ids + */ +void lsp_build_list_nonzero_ht(struct lspdb_head *head, const uint8_t *start_id, + const uint8_t *stop_id, struct list *list) +{ + struct isis_lsp searchfor; + struct isis_lsp *lsp, *start; + + memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id)); + + start = lspdb_find_gteq(head, &searchfor); + frr_each_from (lspdb, head, lsp, start) { + if (memcmp(lsp->hdr.lsp_id, stop_id, + ISIS_SYS_ID_LEN + 2) > 0) + break; + + if (lsp->hdr.rem_lifetime && lsp->hdr.seqno) + listnode_add(list, lsp); + } +} + +static void lsp_set_time(struct isis_lsp *lsp) +{ + assert(lsp); + + if (lsp->hdr.rem_lifetime == 0) { + if (lsp->age_out > 0) + lsp->age_out--; + return; + } + + lsp->hdr.rem_lifetime--; + if (lsp->pdu && stream_get_endp(lsp->pdu) >= 12) + stream_putw_at(lsp->pdu, 10, lsp->hdr.rem_lifetime); +} + +void lspid_print(uint8_t *lsp_id, char *dest, size_t dest_len, char dynhost, + char frag, struct isis *isis) +{ + struct isis_dynhn *dyn = NULL; + char id[SYSID_STRLEN]; + + if (dynhost) + dyn = dynhn_find_by_id(isis, lsp_id); + else + dyn = NULL; + + if (dyn) + snprintf(id, sizeof(id), "%.14s", dyn->hostname); + else if (!memcmp(isis->sysid, lsp_id, ISIS_SYS_ID_LEN) && dynhost) + snprintf(id, sizeof(id), "%.14s", cmd_hostname_get()); + else + snprintfrr(id, sizeof(id), "%pSY", lsp_id); + + if (frag) + snprintf(dest, dest_len, "%s.%02x-%02x", id, + LSP_PSEUDO_ID(lsp_id), LSP_FRAGMENT(lsp_id)); + else + snprintf(dest, dest_len, "%s.%02x", id, LSP_PSEUDO_ID(lsp_id)); +} + +/* Convert the lsp attribute bits to attribute string */ +static const char *lsp_bits2string(uint8_t lsp_bits, char *buf, size_t buf_size) +{ + char *pos = buf; + + if (!lsp_bits) + return " none"; + + if (buf_size < 2 * 3) + return " error"; + + /* we only focus on the default metric */ + pos += snprintf(pos, buf_size, "%d/", + ISIS_MASK_LSP_ATT_BITS(lsp_bits) ? 1 : 0); + + pos += snprintf(pos, buf_size, "%d/", + ISIS_MASK_LSP_PARTITION_BIT(lsp_bits) ? 1 : 0); + + snprintf(pos, buf_size, "%d", ISIS_MASK_LSP_OL_BIT(lsp_bits) ? 1 : 0); + + return buf; +} + +/* this function prints the lsp on show isis database */ +void lsp_print_common(struct isis_lsp *lsp, struct vty *vty, struct json_object *json, + char dynhost, struct isis *isis) +{ + if (json) { + return lsp_print_json(lsp, json, dynhost, isis); + } else { + return lsp_print_vty(lsp, vty, dynhost, isis); + } +} + +#if CONFDATE > 20240916 +CPP_NOTICE("Remove JSON in '-' format") +#endif + +void lsp_print_json(struct isis_lsp *lsp, struct json_object *json, + char dynhost, struct isis *isis) +{ + char LSPid[255]; + char age_out[8]; + char b[200]; + json_object *own_json; + char buf[256]; + + lspid_print(lsp->hdr.lsp_id, LSPid, sizeof(LSPid), dynhost, 1, isis); + own_json = json_object_new_object(); + json_object_object_add(json, "lsp", own_json); + json_object_string_add(own_json, "id", LSPid); +#if CONFDATE > 20240916 + CPP_NOTICE("remove own key") +#endif + json_object_string_add(own_json, "own", lsp->own_lsp ? "*" : " "); + if (lsp->own_lsp) + json_object_boolean_add(own_json, "ownLSP", true); + json_object_int_add(json, "pdu-len", lsp->hdr.pdu_len); + json_object_int_add(json, "pduLen", lsp->hdr.pdu_len); + snprintfrr(buf, sizeof(buf), "0x%08x", lsp->hdr.seqno); +#if CONFDATE > 20240916 + CPP_NOTICE("remove seq-number key") +#endif + json_object_string_add(json, "seq-number", buf); + json_object_string_add(json, "seqNumber", buf); + snprintfrr(buf, sizeof(buf), "0x%04hx", lsp->hdr.checksum); + json_object_string_add(json, "chksum", buf); + if (lsp->hdr.rem_lifetime == 0) { + snprintf(age_out, sizeof(age_out), "(%d)", lsp->age_out); + age_out[7] = '\0'; + json_object_string_add(json, "holdtime", age_out); + } else { + json_object_int_add(json, "holdtime", lsp->hdr.rem_lifetime); + } +#if CONFDATE > 20240916 + CPP_NOTICE("remove att-p-ol key") +#endif + json_object_string_add( + json, "att-p-ol", lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b))); + json_object_string_add(json, "attPOl", + lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b))); +} + +void lsp_print_vty(struct isis_lsp *lsp, struct vty *vty, + char dynhost, struct isis *isis) +{ + char LSPid[255]; + char age_out[8]; + char b[200]; + + lspid_print(lsp->hdr.lsp_id, LSPid, sizeof(LSPid), dynhost, 1, isis); + vty_out(vty, "%-21s%c ", LSPid, lsp->own_lsp ? '*' : ' '); + vty_out(vty, "%5hu ", lsp->hdr.pdu_len); + vty_out(vty, "0x%08x ", lsp->hdr.seqno); + vty_out(vty, "0x%04hx ", lsp->hdr.checksum); + if (lsp->hdr.rem_lifetime == 0) { + snprintf(age_out, sizeof(age_out), "(%d)", lsp->age_out); + age_out[7] = '\0'; + vty_out(vty, "%7s ", age_out); + } else + vty_out(vty, " %5hu ", lsp->hdr.rem_lifetime); + vty_out(vty, "%s\n", lsp_bits2string(lsp->hdr.lsp_bits, b, sizeof(b))); +} + +void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty, + struct json_object *json, char dynhost, + struct isis *isis) +{ + if (json) { + lsp_print_json(lsp, json, dynhost, isis); + if (lsp->tlvs) { + isis_format_tlvs(lsp->tlvs, json); + } + } else { + lsp_print_vty(lsp, vty, dynhost, isis); + if (lsp->tlvs) + vty_multiline(vty, " ", "%s", + isis_format_tlvs(lsp->tlvs, NULL)); + vty_out(vty, "\n"); + } +} + +/* print all the lsps info in the local lspdb */ +int lsp_print_all(struct vty *vty, struct json_object *json, + struct lspdb_head *head, char detail, char dynhost, + struct isis *isis) +{ + struct isis_lsp *lsp; + int lsp_count = 0; + struct json_object *lsp_json = NULL; + + if (detail == ISIS_UI_LEVEL_BRIEF) { + frr_each (lspdb, head, lsp) { + if (json) { + lsp_json = json_object_new_object(); + json_object_array_add(json, lsp_json); + } + lsp_print_common(lsp, vty, lsp_json, dynhost, isis); + lsp_count++; + } + } else if (detail == ISIS_UI_LEVEL_DETAIL) { + frr_each (lspdb, head, lsp) { + if (json) { + lsp_json = json_object_new_object(); + json_object_array_add(json, lsp_json); + } + lsp_print_detail(lsp, vty, lsp_json, dynhost, isis); + lsp_count++; + } + } + + return lsp_count; +} + +static uint16_t lsp_rem_lifetime(struct isis_area *area, int level) +{ + uint16_t rem_lifetime; + + /* Add jitter to configured LSP lifetime */ + rem_lifetime = + isis_jitter(area->max_lsp_lifetime[level - 1], MAX_AGE_JITTER); + + /* No jitter if the max refresh will be less than configure gen interval + */ + /* N.B. this calucation is acceptable since rem_lifetime is in + * [332,65535] at + * this point */ + if (area->lsp_gen_interval[level - 1] > (rem_lifetime - 300)) + rem_lifetime = area->max_lsp_lifetime[level - 1]; + + return rem_lifetime; +} + +static uint16_t lsp_refresh_time(struct isis_lsp *lsp, uint16_t rem_lifetime) +{ + struct isis_area *area = lsp->area; + int level = lsp->level; + uint16_t refresh_time; + + /* Add jitter to LSP refresh time */ + refresh_time = + isis_jitter(area->lsp_refresh[level - 1], MAX_LSP_GEN_JITTER); + + /* RFC 4444 : make sure the refresh time is at least less than 300 + * of the remaining lifetime and more than gen interval */ + if (refresh_time <= area->lsp_gen_interval[level - 1] + || refresh_time > (rem_lifetime - 300)) + refresh_time = rem_lifetime - 300; + + /* In cornercases, refresh_time might be <= lsp_gen_interval, however + * we accept this violation to satisfy refresh_time <= rem_lifetime - + * 300 */ + + return refresh_time; +} + +static void lsp_build_internal_reach_ipv4(struct isis_lsp *lsp, + struct isis_area *area, + struct prefix_ipv4 *ipv4, + uint32_t metric) +{ + struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL}; + + if (area->oldmetric) { + lsp_debug( + "ISIS (%s): Adding old-style IP reachability for %pFX", + area->area_tag, ipv4); + isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4, metric); + } + + if (area->newmetric) { + lsp_debug("ISIS (%s): Adding te-style IP reachability for %pFX", + area->area_tag, ipv4); + + if (area->srdb.enabled) + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { +#ifndef FABRICD + if (flex_algo_id_valid(i) && + !isis_flex_algo_elected_supported(i, area)) + continue; +#endif /* ifndef FABRICD */ + pcfgs[i] = + isis_sr_cfg_prefix_find(area, ipv4, i); + } + + isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric, false, + pcfgs); + } +} + +static void lsp_build_internal_reach_ipv6(struct isis_lsp *lsp, + struct isis_area *area, + struct prefix_ipv6 *ipv6, + uint32_t metric) +{ + struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL}; + + lsp_debug("ISIS (%s): Adding IPv6 reachability for %pFX", + area->area_tag, ipv6); + + if (area->srdb.enabled) + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { +#ifndef FABRICD + if (flex_algo_id_valid(i) && + !isis_flex_algo_elected_supported(i, area)) + continue; +#endif /* ifndef FABRICD */ + pcfgs[i] = isis_sr_cfg_prefix_find(area, ipv6, i); + } + + isis_tlvs_add_ipv6_reach(lsp->tlvs, isis_area_ipv6_topology(area), ipv6, + metric, false, pcfgs); +} + + +static void lsp_build_ext_reach_ipv4(struct isis_lsp *lsp, + struct isis_area *area) +{ + struct route_table *er_table = get_ext_reach(area, AF_INET, lsp->level); + if (!er_table) + return; + + for (struct route_node *rn = route_top(er_table); rn; + rn = route_next(rn)) { + if (!rn->info) + continue; + + struct prefix_ipv4 *ipv4 = (struct prefix_ipv4 *)&rn->p; + struct isis_ext_info *info = rn->info; + + uint32_t metric = info->metric; + if (metric > MAX_WIDE_PATH_METRIC) + metric = MAX_WIDE_PATH_METRIC; + if (area->oldmetric && metric > 0x3f) + metric = 0x3f; + + if (area->oldmetric) + isis_tlvs_add_oldstyle_ip_reach(lsp->tlvs, ipv4, + metric); + if (area->newmetric) { + struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = { + NULL}; + + if (area->srdb.enabled) + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { +#ifndef FABRICD + if (flex_algo_id_valid(i) && + !isis_flex_algo_elected_supported( + i, area)) + continue; +#endif /* ifndef FABRICD */ + pcfgs[i] = isis_sr_cfg_prefix_find( + area, ipv4, i); + } + + isis_tlvs_add_extended_ip_reach(lsp->tlvs, ipv4, metric, + true, pcfgs); + } + } +} + +static void lsp_build_ext_reach_ipv6(struct isis_lsp *lsp, + struct isis_area *area) +{ + struct route_table *er_table = + get_ext_reach(area, AF_INET6, lsp->level); + if (!er_table) + return; + + for (struct route_node *rn = route_top(er_table); rn; + rn = srcdest_route_next(rn)) { + if (!rn->info) + continue; + struct isis_ext_info *info = rn->info; + struct prefix_ipv6 *p, *src_p; + + srcdest_rnode_prefixes(rn, (const struct prefix **)&p, + (const struct prefix **)&src_p); + + uint32_t metric = info->metric; + if (info->metric > MAX_WIDE_PATH_METRIC) + metric = MAX_WIDE_PATH_METRIC; + + if (!src_p || !src_p->prefixlen) { + struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = { + NULL}; + + if (area->srdb.enabled) + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { +#ifndef FABRICD + if (flex_algo_id_valid(i) && + !isis_flex_algo_elected_supported( + i, area)) + continue; +#endif /* ifndef FABRICD */ + pcfgs[i] = isis_sr_cfg_prefix_find( + area, p, i); + } + + isis_tlvs_add_ipv6_reach(lsp->tlvs, + isis_area_ipv6_topology(area), + p, metric, true, pcfgs); + } else if (isis_area_ipv6_dstsrc_enabled(area)) { + isis_tlvs_add_ipv6_dstsrc_reach(lsp->tlvs, + ISIS_MT_IPV6_DSTSRC, + p, src_p, metric); + } + } +} + +static void lsp_build_ext_reach(struct isis_lsp *lsp, struct isis_area *area) +{ + lsp_build_ext_reach_ipv4(lsp, area); + lsp_build_ext_reach_ipv6(lsp, area); +} + +static struct isis_lsp *lsp_next_frag(uint8_t frag_num, struct isis_lsp *lsp0, + struct isis_area *area, int level) +{ + struct isis_lsp *lsp; + uint8_t frag_id[ISIS_SYS_ID_LEN + 2]; + + memcpy(frag_id, lsp0->hdr.lsp_id, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(frag_id) = frag_num; + + lsp = lsp_search(&area->lspdb[level - 1], frag_id); + if (lsp) { + lsp_clear_data(lsp); + if (!lsp->lspu.zero_lsp) + lsp_link_fragment(lsp, lsp0); + return lsp; + } + + lsp = lsp_new(area, frag_id, lsp0->hdr.rem_lifetime, 0, + lsp_bits_generate(level, area->overload_bit, + area->attached_bit_send, area), + 0, lsp0, level); + lsp->own_lsp = 1; + lsp_insert(&area->lspdb[level - 1], lsp); + return lsp; +} + +/* + * Builds the LSP data part. This func creates a new frag whenever + * area->lsp_frag_threshold is exceeded. + */ +static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) +{ + int level = lsp->level; + struct listnode *node; + struct isis_lsp *frag; + + lsp_clear_data(lsp); + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) + lsp_clear_data(frag); + + lsp->tlvs = isis_alloc_tlvs(); + lsp_debug("ISIS (%s): Constructing local system LSP for level %d", + area->area_tag, level); + + lsp->hdr.lsp_bits = lsp_bits_generate(level, area->overload_bit, + area->attached_bit_send, area); + + lsp_add_auth(lsp); + + isis_tlvs_add_area_addresses(lsp->tlvs, area->area_addrs); + + /* Protocols Supported */ + if (area->ip_circuits > 0 || area->ipv6_circuits > 0) { + struct nlpids nlpids = {.count = 0}; + + if (area->ip_circuits > 0) { + lsp_debug( + "ISIS (%s): Found IPv4 circuit, adding IPv4 to NLPIDs", + area->area_tag); + nlpids.nlpids[nlpids.count] = NLPID_IP; + nlpids.count++; + } + if (area->ipv6_circuits > 0) { + lsp_debug( + "ISIS (%s): Found IPv6 circuit, adding IPv6 to NLPIDs", + area->area_tag); + nlpids.nlpids[nlpids.count] = NLPID_IPV6; + nlpids.count++; + } + isis_tlvs_set_protocols_supported(lsp->tlvs, &nlpids); + } + + if (area_is_mt(area)) { + lsp_debug("ISIS (%s): Adding MT router tlv...", area->area_tag); + + struct isis_area_mt_setting **mt_settings; + unsigned int mt_count; + + mt_settings = area_mt_settings(area, &mt_count); + for (unsigned int i = 0; i < mt_count; i++) { + isis_tlvs_add_mt_router_info( + lsp->tlvs, mt_settings[i]->mtid, + mt_settings[i]->overload, false); + lsp_debug("ISIS (%s): MT %s", area->area_tag, + isis_mtid2str(mt_settings[i]->mtid)); + } + } else { + lsp_debug("ISIS (%s): Not adding MT router tlv (disabled)", + area->area_tag); + } + /* Dynamic Hostname */ + if (area->dynhostname) { + isis_tlvs_set_dynamic_hostname(lsp->tlvs, cmd_hostname_get()); + lsp_debug("ISIS (%s): Adding dynamic hostname '%s'", + area->area_tag, cmd_hostname_get()); + } else { + lsp_debug("ISIS (%s): Not adding dynamic hostname (disabled)", + area->area_tag); + } + + /* Add Router Capability TLV. */ + if (area->isis->router_id != 0) { + struct isis_router_cap *rcap; +#ifndef FABRICD + struct isis_router_cap_fad *rcap_fad; + struct listnode *node; + struct flex_algo *fa; +#endif /* ifndef FABRICD */ + + rcap = isis_tlvs_init_router_capability(lsp->tlvs); + + rcap->router_id.s_addr = area->isis->router_id; + +#ifndef FABRICD + /* Set flex-algo definitions */ + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + if (!fa->advertise_definition) + continue; + lsp_debug("ISIS (%s): Flex-Algo Definition %u", + area->area_tag, fa->algorithm); + isis_tlvs_set_router_capability_fad(lsp->tlvs, fa, + fa->algorithm, + area->isis->sysid); + } +#endif /* ifndef FABRICD */ + + /* Add SR Sub-TLVs if SR is enabled. */ + if (area->srdb.enabled) { + struct isis_sr_db *srdb = &area->srdb; + uint32_t range_size; + + /* SRGB first */ + range_size = srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1; + rcap->srgb.flags = ISIS_SUBTLV_SRGB_FLAG_I | + ISIS_SUBTLV_SRGB_FLAG_V; + rcap->srgb.range_size = range_size; + rcap->srgb.lower_bound = srdb->config.srgb_lower_bound; + /* Then Algorithm */ + rcap->algo[0] = SR_ALGORITHM_SPF; + rcap->algo[1] = SR_ALGORITHM_UNSET; +#ifndef FABRICD + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, + node, fa)) { + if (fa->advertise_definition) + rcap_fad = rcap->fads[fa->algorithm]; + else + rcap_fad = NULL; + + if (!isis_flex_algo_elected_supported_local_fad( + fa->algorithm, area, &rcap_fad)) { + fa->state = false; + continue; + } + fa->state = true; + lsp_debug("ISIS (%s): SR Algorithm %u", + area->area_tag, fa->algorithm); + rcap->algo[fa->algorithm] = fa->algorithm; + } +#endif /* ifndef FABRICD */ + /* SRLB */ + rcap->srlb.flags = 0; + range_size = srdb->config.srlb_upper_bound + - srdb->config.srlb_lower_bound + 1; + rcap->srlb.range_size = range_size; + rcap->srlb.lower_bound = srdb->config.srlb_lower_bound; + /* And finally MSD */ + rcap->msd = srdb->config.msd; + } + + /* Add SRv6 Sub-TLVs if SRv6 is enabled */ + if (area->srv6db.config.enabled) { + struct isis_srv6_db *srv6db = &area->srv6db; + + rcap->srv6_cap.is_srv6_capable = true; + + /* SRv6 flags */ + rcap->srv6_cap.flags = 0; + + /* And finally MSDs */ + rcap->srv6_msd.max_seg_left_msd = + srv6db->config.max_seg_left_msd; + rcap->srv6_msd.max_end_pop_msd = + srv6db->config.max_end_pop_msd; + rcap->srv6_msd.max_h_encaps_msd = + srv6db->config.max_h_encaps_msd; + rcap->srv6_msd.max_end_d_msd = + srv6db->config.max_end_d_msd; + } else { + rcap->srv6_cap.is_srv6_capable = false; + } + } + + /* Add SRv6 Locator TLV. */ + if (area->srv6db.config.enabled && + !list_isempty(area->srv6db.srv6_locator_chunks)) { + struct isis_srv6_locator locator = {}; + struct srv6_locator_chunk *chunk; + + /* TODO: support more than one locator */ + chunk = (struct srv6_locator_chunk *)listgetdata( + listhead(area->srv6db.srv6_locator_chunks)); + + locator.metric = 0; + locator.prefix = chunk->prefix; + locator.flags = 0; + locator.algorithm = 0; + + struct listnode *sid_node; + struct isis_srv6_sid *sid; + locator.srv6_sid = list_new(); + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, sid_node, + sid)) { + listnode_add(locator.srv6_sid, sid); + } + + isis_tlvs_add_srv6_locator(lsp->tlvs, 0, &locator); + lsp_debug("ISIS (%s): Adding SRv6 Locator information", + area->area_tag); + + list_delete(&locator.srv6_sid); + + isis_tlvs_add_ipv6_reach(lsp->tlvs, + isis_area_ipv6_topology(area), + &chunk->prefix, 0, false, NULL); + } + + /* IPv4 address and TE router ID TLVs. + * In case of the first one we don't follow "C" vendor, + * but "J" vendor behavior - one IPv4 address is put + * into LSP. TE router ID will be the same if MPLS-TE + * is not activate or MPLS-TE router-id not specified + */ + if (area->isis->router_id != 0) { + struct in_addr id = {.s_addr = area->isis->router_id}; + lsp_debug("ISIS (%s): Adding router ID %pI4 as IPv4 tlv.", + area->area_tag, &id); + isis_tlvs_add_ipv4_address(lsp->tlvs, &id); + + /* If new style TLV's are in use, add TE router ID TLV + * Check if MPLS-TE is activate and mpls-te router-id set + * otherwise add exactly same data as for IPv4 address + */ + if (area->newmetric) { + if (IS_MPLS_TE(area->mta) + && area->mta->router_id.s_addr != INADDR_ANY) + id.s_addr = area->mta->router_id.s_addr; + lsp_debug( + "ISIS (%s): Adding router ID also as TE router ID tlv.", + area->area_tag); + isis_tlvs_set_te_router_id(lsp->tlvs, &id); + } + } else { + lsp_debug("ISIS (%s): Router ID is unset. Not adding tlv.", + area->area_tag); + } + + if (IS_MPLS_TE(area->mta) + && !IN6_IS_ADDR_UNSPECIFIED(&area->mta->router_id_ipv6)) { + lsp_debug("ISIS (%s): Adding IPv6 TE Router ID tlv.", + area->area_tag); + isis_tlvs_set_te_router_id_ipv6(lsp->tlvs, + &area->mta->router_id_ipv6); + } + + lsp_debug("ISIS (%s): Adding circuit specific information.", + area->area_tag); + + if (fabricd) { + lsp_debug( + "ISIS (%s): Adding tier %hhu spine-leaf-extension tlv.", + area->area_tag, fabricd_tier(area)); + isis_tlvs_add_spine_leaf(lsp->tlvs, fabricd_tier(area), true, + false, false, false); + } + + struct isis_circuit *circuit; + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + if (!circuit->interface) + lsp_debug( + "ISIS (%s): Processing %s circuit %p with unknown interface", + area->area_tag, + circuit_type2string(circuit->circ_type), + circuit); + else + lsp_debug("ISIS (%s): Processing %s circuit %s", + area->area_tag, + circuit_type2string(circuit->circ_type), + circuit->interface->name); + + if (circuit->state != C_STATE_UP) { + lsp_debug("ISIS (%s): Circuit is not up, ignoring.", + area->area_tag); + continue; + } + + if (area->advertise_passive_only && !circuit->is_passive) { + lsp_debug( + "ISIS (%s): Circuit is not passive, ignoring.", + area->area_tag); + continue; + } + + uint32_t metric = area->oldmetric + ? circuit->metric[level - 1] + : circuit->te_metric[level - 1]; + + if (circuit->ip_router && circuit->ip_addrs->count > 0) { + lsp_debug( + "ISIS (%s): Circuit has IPv4 active, adding respective TLVs.", + area->area_tag); + struct listnode *ipnode; + struct prefix_ipv4 *ipv4; + for (ALL_LIST_ELEMENTS_RO(circuit->ip_addrs, ipnode, + ipv4)) + lsp_build_internal_reach_ipv4(lsp, area, ipv4, + metric); + } + + if (circuit->ipv6_router && circuit->ipv6_non_link->count > 0) { + struct listnode *ipnode; + struct prefix_ipv6 *ipv6; + + for (ALL_LIST_ELEMENTS_RO(circuit->ipv6_non_link, + ipnode, ipv6)) + lsp_build_internal_reach_ipv6(lsp, area, ipv6, + metric); + } + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + if (level & circuit->is_type) { + uint8_t *ne_id = + (level == IS_LEVEL_1) + ? circuit->u.bc.l1_desig_is + : circuit->u.bc.l2_desig_is; + + if (LSP_PSEUDO_ID(ne_id)) { + if (area->oldmetric) { + lsp_debug( + "ISIS (%s): Adding DIS %pPN as old-style neighbor", + area->area_tag, ne_id); + isis_tlvs_add_oldstyle_reach( + lsp->tlvs, ne_id, + metric); + } + if (area->newmetric) + tlvs_add_mt_bcast( + lsp->tlvs, circuit, + level, ne_id, metric); + } + } else { + lsp_debug( + "ISIS (%s): Circuit is not active for current level. Not adding IS neighbors", + area->area_tag); + } + break; + case CIRCUIT_T_P2P: { + struct isis_adjacency *nei = circuit->u.p2p.neighbor; + if (nei && nei->adj_state == ISIS_ADJ_UP + && (level & nei->circuit_t)) { + uint8_t ne_id[7]; + memcpy(ne_id, nei->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(ne_id) = 0; + + if (area->oldmetric) { + lsp_debug( + "ISIS (%s): Adding old-style is reach for %pSY", + area->area_tag, ne_id); + isis_tlvs_add_oldstyle_reach( + lsp->tlvs, ne_id, metric); + } + if (area->newmetric) { + uint32_t neighbor_metric; + if (fabricd_tier(area) == 0) { + neighbor_metric = 0xffe; + } else { + neighbor_metric = metric; + } + + tlvs_add_mt_p2p(lsp->tlvs, circuit, + ne_id, neighbor_metric); + } + } else { + lsp_debug( + "ISIS (%s): No adjacency for given level on this circuit. Not adding IS neighbors", + area->area_tag); + } + } break; + case CIRCUIT_T_LOOPBACK: + break; + default: + zlog_warn("lsp_area_create: unknown circuit type"); + } + } + + lsp_build_ext_reach(lsp, area); + + struct isis_tlvs *tlvs = lsp->tlvs; + lsp->tlvs = NULL; + + lsp_adjust_stream(lsp); + lsp_pack_pdu(lsp); + size_t tlv_space = STREAM_WRITEABLE(lsp->pdu) - LLC_LEN; + lsp_clear_data(lsp); + + struct list *fragments = isis_fragment_tlvs(tlvs, tlv_space); + if (!fragments) { + zlog_warn("BUG: could not fragment own LSP:"); + log_multiline(LOG_WARNING, " ", "%s", + isis_format_tlvs(tlvs, NULL)); + isis_free_tlvs(tlvs); + return; + } + isis_free_tlvs(tlvs); + + bool fragment_overflow = false; + frag = lsp; + for (ALL_LIST_ELEMENTS_RO(fragments, node, tlvs)) { + if (node != listhead(fragments)) { + if (LSP_FRAGMENT(frag->hdr.lsp_id) == 255) { + if (!fragment_overflow) { + fragment_overflow = true; + zlog_warn( + "ISIS (%s): Too much information for 256 fragments", + area->area_tag); + } + isis_free_tlvs(tlvs); + continue; + } + + frag = lsp_next_frag(LSP_FRAGMENT(frag->hdr.lsp_id) + 1, + lsp, area, level); + lsp_adjust_stream(frag); + } + frag->tlvs = tlvs; + } + + list_delete(&fragments); + lsp_debug("ISIS (%s): LSP construction is complete. Serializing...", + area->area_tag); + return; +} + +/* + * 7.3.7 and 7.3.9 Generation on non-pseudonode LSPs + */ +int lsp_generate(struct isis_area *area, int level) +{ + struct isis_lsp *oldlsp, *newlsp; + uint32_t seq_num = 0; + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + uint16_t rem_lifetime, refresh_time; + uint32_t overload_time; + + if ((area == NULL) || (area->is_type & level) != level) + return ISIS_ERROR; + + /* Check if config is still being processed */ + if (event_is_scheduled(t_isis_cfg)) + return ISIS_OK; + + memset(&lspid, 0, ISIS_SYS_ID_LEN + 2); + + memcpy(&lspid, area->isis->sysid, ISIS_SYS_ID_LEN); + + /* Check if device should be overloaded on startup */ + if (device_startup) { + overload_time = isis_restart_read_overload_time(area); + if (overload_time > 0) { + isis_area_overload_bit_set(area, true); + event_add_timer(master, set_overload_on_start_timer, + area, overload_time, + &area->t_overload_on_startup_timer); + } + device_startup = false; + } + + /* only builds the lsp if the area shares the level */ + oldlsp = lsp_search(&area->lspdb[level - 1], lspid); + if (oldlsp) { + /* FIXME: we should actually initiate a purge */ + seq_num = oldlsp->hdr.seqno; + lsp_search_and_destroy(&area->lspdb[level - 1], + oldlsp->hdr.lsp_id); + } + rem_lifetime = lsp_rem_lifetime(area, level); + newlsp = lsp_new(area, lspid, rem_lifetime, seq_num, + lsp_bits_generate(area->is_type, area->overload_bit, + area->attached_bit_send, area), + 0, NULL, level); + newlsp->area = area; + newlsp->own_lsp = 1; + + lsp_insert(&area->lspdb[level - 1], newlsp); + /* build_lsp_data (newlsp, area); */ + lsp_build(newlsp, area); + /* time to calculate our checksum */ + lsp_seqno_update(newlsp); + newlsp->last_generated = time(NULL); + lsp_flood(newlsp, NULL); + area->lsp_gen_count[level - 1]++; + + refresh_time = lsp_refresh_time(newlsp, rem_lifetime); + + EVENT_OFF(area->t_lsp_refresh[level - 1]); + area->lsp_regenerate_pending[level - 1] = 0; + event_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1], + refresh_time, &area->t_lsp_refresh[level - 1]); + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Building L%d LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus refresh %hus", + area->area_tag, level, newlsp->hdr.lsp_id, + newlsp->hdr.pdu_len, newlsp->hdr.seqno, + newlsp->hdr.checksum, newlsp->hdr.rem_lifetime, + refresh_time); + } + sched_debug( + "ISIS (%s): Built L%d LSP. Set triggered regenerate to non-pending.", + area->area_tag, level); + +#ifndef FABRICD + /* send northbound notification */ + isis_notif_lsp_gen(area, newlsp->hdr.lsp_id, newlsp->hdr.seqno, + newlsp->last_generated); +#endif /* ifndef FABRICD */ + + return ISIS_OK; +} + +/* + * Search own LSPs, update holding time and flood + */ +static int lsp_regenerate(struct isis_area *area, int level) +{ + struct lspdb_head *head; + struct isis_lsp *lsp, *frag; + struct listnode *node; + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + uint16_t rem_lifetime, refresh_time; + + if ((area == NULL) || (area->is_type & level) != level) + return ISIS_ERROR; + + head = &area->lspdb[level - 1]; + memset(lspid, 0, ISIS_SYS_ID_LEN + 2); + memcpy(lspid, area->isis->sysid, ISIS_SYS_ID_LEN); + + lsp = lsp_search(head, lspid); + + if (!lsp) { + flog_err(EC_LIB_DEVELOPMENT, + "ISIS-Upd (%s): lsp_regenerate: no L%d LSP found!", + area->area_tag, level); + return ISIS_ERROR; + } + + lsp_clear_data(lsp); + lsp_build(lsp, area); + rem_lifetime = lsp_rem_lifetime(area, level); + lsp->hdr.rem_lifetime = rem_lifetime; + lsp->last_generated = time(NULL); + lsp_flood(lsp, NULL); + area->lsp_gen_count[level - 1]++; + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) { + if (!frag->tlvs) { + /* Updating and flooding should only affect fragments + * carrying data + */ + continue; + } + + frag->hdr.lsp_bits = + lsp_bits_generate(level, area->overload_bit, + area->attached_bit_send, area); + /* Set the lifetime values of all the fragments to the same + * value, + * so that no fragment expires before the lsp is refreshed. + */ + frag->hdr.rem_lifetime = rem_lifetime; + frag->age_out = ZERO_AGE_LIFETIME; + lsp_flood(frag, NULL); + } + lsp_seqno_update(lsp); + + refresh_time = lsp_refresh_time(lsp, rem_lifetime); + event_add_timer(master, lsp_refresh, &area->lsp_refresh_arg[level - 1], + refresh_time, &area->t_lsp_refresh[level - 1]); + area->lsp_regenerate_pending[level - 1] = 0; + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Refreshed our L%d LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus refresh %hus", + area->area_tag, level, lsp->hdr.lsp_id, + lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime, refresh_time); + } + sched_debug( + "ISIS (%s): Rebuilt L%d LSP. Set triggered regenerate to non-pending.", + area->area_tag, level); + + return ISIS_OK; +} + +/* + * Something has changed or periodic refresh -> regenerate LSP + */ +static void lsp_refresh(struct event *thread) +{ + struct lsp_refresh_arg *arg = EVENT_ARG(thread); + + assert(arg); + + struct isis_area *area = arg->area; + + assert(area); + + int level = arg->level; + + area->t_lsp_refresh[level - 1] = NULL; + area->lsp_regenerate_pending[level - 1] = 0; + + if ((area->is_type & level) == 0) + return; + + /* + * Throttle regeneration of LSPs (but not when BFD signalled a 'down' + * message) + */ + if (monotime_since(&area->last_lsp_refresh_event[level - 1], NULL) + < 100000L + && !(area->bfd_force_spf_refresh)) { + sched_debug("ISIS (%s): Still unstable, postpone LSP L%d refresh", + area->area_tag, level); + _lsp_regenerate_schedule(area, level, 0, false, + __func__, __FILE__, __LINE__); + return; + } + + sched_debug( + "ISIS (%s): LSP L%d refresh timer expired. Refreshing LSP...", + area->area_tag, level); + lsp_regenerate(area, level); +} + +int _lsp_regenerate_schedule(struct isis_area *area, int level, + int all_pseudo, bool postpone, + const char *func, const char *file, + int line) +{ + struct isis_lsp *lsp; + uint8_t id[ISIS_SYS_ID_LEN + 2]; + time_t now, diff; + long timeout; + struct listnode *cnode; + struct isis_circuit *circuit; + int lvl; + + if (area == NULL) + return ISIS_ERROR; + + sched_debug( + "ISIS (%s): Scheduling regeneration of %s LSPs, %sincluding PSNs Caller: %s %s:%d", + area->area_tag, circuit_t2string(level), + all_pseudo ? "" : "not ", + func, file, line); + + memcpy(id, area->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(id) = LSP_FRAGMENT(id) = 0; + now = time(NULL); + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { + if (!((level & lvl) && (area->is_type & lvl))) + continue; + + if (postpone) { + monotime(&area->last_lsp_refresh_event[lvl - 1]); + } + + sched_debug( + "ISIS (%s): Checking whether L%d needs to be scheduled", + area->area_tag, lvl); + + if (area->lsp_regenerate_pending[lvl - 1] + && !(area->bfd_signalled_down)) { + /* + * Note: in case of a BFD 'down' message the refresh is + * scheduled once again just to be sure + */ + struct timeval remain = event_timer_remain( + area->t_lsp_refresh[lvl - 1]); + sched_debug( + "ISIS (%s): Regeneration is already pending, nothing todo. (Due in %lld.%03lld seconds)", + area->area_tag, (long long)remain.tv_sec, + (long long)remain.tv_usec / 1000); + continue; + } + + lsp = lsp_search(&area->lspdb[lvl - 1], id); + if (!lsp) { + sched_debug( + "ISIS (%s): We do not have any LSPs to regenerate, nothing todo.", + area->area_tag); + continue; + } + + /* + * Throttle avoidance + */ + sched_debug( + "ISIS (%s): Will schedule regen timer. Last run was: %lld, Now is: %lld", + area->area_tag, (long long)lsp->last_generated, + (long long)now); + EVENT_OFF(area->t_lsp_refresh[lvl - 1]); + diff = now - lsp->last_generated; + if (diff < area->lsp_gen_interval[lvl - 1] + && !(area->bfd_signalled_down)) { + timeout = + 1000 * (area->lsp_gen_interval[lvl - 1] - diff); + sched_debug( + "ISIS (%s): Scheduling in %ld ms to match configured lsp_gen_interval", + area->area_tag, timeout); + } else { + /* + * Schedule LSP refresh ASAP + */ + if (area->bfd_signalled_down) { + sched_debug( + "ISIS (%s): Scheduling immediately due to BFD 'down' message.", + area->area_tag); + area->bfd_signalled_down = false; + area->bfd_force_spf_refresh = true; + timeout = 0; + } else { + int64_t time_since_last = monotime_since( + &area->last_lsp_refresh_event[lvl - 1], + NULL); + timeout = time_since_last < 100000L + ? (100000L - time_since_last)/1000 + : 0; + if (timeout > 0) + sched_debug( + "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution in %ld ms due to the instability timer.", + area->area_tag, timeout); + else + sched_debug( + "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution now.", + area->area_tag); + } + } + + area->lsp_regenerate_pending[lvl - 1] = 1; + event_add_timer_msec(master, lsp_refresh, + &area->lsp_refresh_arg[lvl - 1], timeout, + &area->t_lsp_refresh[lvl - 1]); + } + + if (all_pseudo) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + lsp_regenerate_schedule_pseudo(circuit, level); + } + + return ISIS_OK; +} + +/* + * Funcs for pseudonode LSPs + */ + +/* + * 7.3.8 and 7.3.10 Generation of level 1 and 2 pseudonode LSPs + */ +static void lsp_build_pseudo(struct isis_lsp *lsp, struct isis_circuit *circuit, + int level) +{ + struct isis_adjacency *adj; + struct list *adj_list; + struct listnode *node; + struct isis_area *area = circuit->area; + uint16_t mtid; + + lsp_clear_data(lsp); + lsp->tlvs = isis_alloc_tlvs(); + lsp_debug( + "ISIS (%s): Constructing pseudo LSP %pLS for interface %s level %d", + area->area_tag, lsp->hdr.lsp_id, circuit->interface->name, + level); + + lsp->level = level; + /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ + lsp->hdr.lsp_bits = lsp_bits_generate( + level, 0, circuit->area->attached_bit_send, area); + + /* + * add self to IS neighbours + */ + uint8_t ne_id[ISIS_SYS_ID_LEN + 1]; + + memcpy(ne_id, area->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(ne_id) = 0; + + if (circuit->area->oldmetric) { + isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0); + lsp_debug("ISIS (%s): Adding %pPN as old-style neighbor (self)", + area->area_tag, ne_id); + } + if (circuit->area->newmetric) { + if (area_is_mt(circuit->area)) + mtid = ISIS_MT_IPV4_UNICAST; + else + mtid = ISIS_MT_DISABLE; + isis_tlvs_add_extended_reach(lsp->tlvs, mtid, ne_id, 0, NULL); + lsp_debug("ISIS (%s): Adding %pPN as te-style neighbor (self)", + area->area_tag, ne_id); + } + + adj_list = list_new(); + isis_adj_build_up_list(circuit->u.bc.adjdb[level - 1], adj_list); + + for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) { + if (!(adj->level & level)) { + lsp_debug( + "ISIS (%s): Ignoring neighbor %pSY, level does not intersect", + area->area_tag, adj->sysid); + continue; + } + + if (!(level == IS_LEVEL_1 + && adj->sys_type == ISIS_SYSTYPE_L1_IS) + && !(level == IS_LEVEL_1 + && adj->sys_type == ISIS_SYSTYPE_L2_IS + && adj->adj_usage == ISIS_ADJ_LEVEL1AND2) + && !(level == IS_LEVEL_2 + && adj->sys_type == ISIS_SYSTYPE_L2_IS)) { + lsp_debug( + "ISIS (%s): Ignoring neighbor %pSY, level does not match", + area->area_tag, adj->sysid); + continue; + } + + memcpy(ne_id, adj->sysid, ISIS_SYS_ID_LEN); + if (circuit->area->oldmetric) { + isis_tlvs_add_oldstyle_reach(lsp->tlvs, ne_id, 0); + lsp_debug( + "ISIS (%s): Adding %pPN as old-style neighbor (peer)", + area->area_tag, ne_id); + } + if (circuit->area->newmetric) { + isis_tlvs_add_extended_reach(lsp->tlvs, + ISIS_MT_IPV4_UNICAST, + ne_id, 0, NULL); + lsp_debug( + "ISIS (%s): Adding %pPN as te-style neighbor (peer)", + area->area_tag, ne_id); + } + } + list_delete(&adj_list); + return; +} + +int lsp_generate_pseudo(struct isis_circuit *circuit, int level) +{ + struct lspdb_head *head = &circuit->area->lspdb[level - 1]; + struct isis_lsp *lsp; + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + uint16_t rem_lifetime, refresh_time; + + if ((circuit->is_type & level) != level + || (circuit->state != C_STATE_UP) + || (circuit->circ_type != CIRCUIT_T_BROADCAST) + || (circuit->u.bc.is_dr[level - 1] == 0)) + return ISIS_ERROR; + + memcpy(lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN); + LSP_FRAGMENT(lsp_id) = 0; + LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; + + /* + * If for some reason have a pseudo LSP in the db already -> regenerate + */ + if (lsp_search(head, lsp_id)) + return lsp_regenerate_schedule_pseudo(circuit, level); + + rem_lifetime = lsp_rem_lifetime(circuit->area, level); + /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ + lsp = lsp_new(circuit->area, lsp_id, rem_lifetime, 1, + lsp_bits_generate(circuit->area->is_type, 0, + circuit->area->attached_bit_send, + circuit->area), + 0, NULL, level); + lsp->area = circuit->area; + + lsp_build_pseudo(lsp, circuit, level); + lsp_pack_pdu(lsp); + lsp->own_lsp = 1; + lsp_insert(head, lsp); + lsp_flood(lsp, NULL); + + refresh_time = lsp_refresh_time(lsp, rem_lifetime); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + circuit->lsp_regenerate_pending[level - 1] = 0; + if (level == IS_LEVEL_1) + event_add_timer(master, lsp_l1_refresh_pseudo, circuit, + refresh_time, + &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + else if (level == IS_LEVEL_2) + event_add_timer(master, lsp_l2_refresh_pseudo, circuit, + refresh_time, + &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Built L%d Pseudo LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus, refresh %hus", + circuit->area->area_tag, level, lsp->hdr.lsp_id, + lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime, refresh_time); + } + + return ISIS_OK; +} + +static int lsp_regenerate_pseudo(struct isis_circuit *circuit, int level) +{ + struct lspdb_head *head = &circuit->area->lspdb[level - 1]; + struct isis_lsp *lsp; + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + uint16_t rem_lifetime, refresh_time; + + if ((circuit->is_type & level) != level + || (circuit->state != C_STATE_UP) + || (circuit->circ_type != CIRCUIT_T_BROADCAST) + || (circuit->u.bc.is_dr[level - 1] == 0)) + return ISIS_ERROR; + + memcpy(lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; + LSP_FRAGMENT(lsp_id) = 0; + + lsp = lsp_search(head, lsp_id); + + if (!lsp) { + flog_err(EC_LIB_DEVELOPMENT, + "lsp_regenerate_pseudo: no l%d LSP %pLS found!", level, + lsp_id); + return ISIS_ERROR; + } + + rem_lifetime = lsp_rem_lifetime(circuit->area, level); + lsp->hdr.rem_lifetime = rem_lifetime; + lsp_build_pseudo(lsp, circuit, level); + lsp_inc_seqno(lsp, 0); + lsp->last_generated = time(NULL); + lsp_flood(lsp, NULL); + + refresh_time = lsp_refresh_time(lsp, rem_lifetime); + if (level == IS_LEVEL_1) + event_add_timer(master, lsp_l1_refresh_pseudo, circuit, + refresh_time, + &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + else if (level == IS_LEVEL_2) + event_add_timer(master, lsp_l2_refresh_pseudo, circuit, + refresh_time, + &circuit->u.bc.t_refresh_pseudo_lsp[level - 1]); + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Refreshed L%d Pseudo LSP %pLS, len %hu, seq 0x%08x, cksum 0x%04hx, lifetime %hus, refresh %hus", + circuit->area->area_tag, level, lsp->hdr.lsp_id, + lsp->hdr.pdu_len, lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime, refresh_time); + } + + return ISIS_OK; +} + +/* + * Something has changed or periodic refresh -> regenerate pseudo LSP + */ +static void lsp_l1_refresh_pseudo(struct event *thread) +{ + struct isis_circuit *circuit; + uint8_t id[ISIS_SYS_ID_LEN + 2]; + + circuit = EVENT_ARG(thread); + + circuit->u.bc.t_refresh_pseudo_lsp[0] = NULL; + circuit->lsp_regenerate_pending[0] = 0; + + if ((circuit->u.bc.is_dr[0] == 0) + || (circuit->is_type & IS_LEVEL_1) == 0) { + memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(id) = circuit->circuit_id; + LSP_FRAGMENT(id) = 0; + lsp_purge_pseudo(id, circuit, IS_LEVEL_1); + return; + } + + lsp_regenerate_pseudo(circuit, IS_LEVEL_1); +} + +static void lsp_l2_refresh_pseudo(struct event *thread) +{ + struct isis_circuit *circuit; + uint8_t id[ISIS_SYS_ID_LEN + 2]; + + circuit = EVENT_ARG(thread); + + circuit->u.bc.t_refresh_pseudo_lsp[1] = NULL; + circuit->lsp_regenerate_pending[1] = 0; + + if ((circuit->u.bc.is_dr[1] == 0) + || (circuit->is_type & IS_LEVEL_2) == 0) { + memcpy(id, circuit->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(id) = circuit->circuit_id; + LSP_FRAGMENT(id) = 0; + lsp_purge_pseudo(id, circuit, IS_LEVEL_2); + return; + } + + lsp_regenerate_pseudo(circuit, IS_LEVEL_2); +} + +int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level) +{ + struct isis_lsp *lsp; + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + time_t now, diff; + long timeout; + int lvl; + struct isis_area *area = circuit->area; + + if (circuit->circ_type != CIRCUIT_T_BROADCAST + || circuit->state != C_STATE_UP) + return ISIS_OK; + + sched_debug( + "ISIS (%s): Scheduling regeneration of %s pseudo LSP for interface %s", + area->area_tag, circuit_t2string(level), + circuit->interface->name); + + memcpy(lsp_id, area->isis->sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(lsp_id) = circuit->circuit_id; + LSP_FRAGMENT(lsp_id) = 0; + now = time(NULL); + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { + sched_debug( + "ISIS (%s): Checking whether L%d pseudo LSP needs to be scheduled", + area->area_tag, lvl); + + if (!((level & lvl) && (circuit->is_type & lvl))) { + sched_debug("ISIS (%s): Level is not active on circuit", + area->area_tag); + continue; + } + + if (circuit->u.bc.is_dr[lvl - 1] == 0) { + sched_debug( + "ISIS (%s): This IS is not DR, nothing to do.", + area->area_tag); + continue; + } + + if (circuit->lsp_regenerate_pending[lvl - 1]) { + struct timeval remain = event_timer_remain( + circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); + sched_debug( + "ISIS (%s): Regenerate is already pending, nothing todo. (Due in %lld.%03lld seconds)", + area->area_tag, (long long)remain.tv_sec, + (long long)remain.tv_usec / 1000); + continue; + } + + lsp = lsp_search(&circuit->area->lspdb[lvl - 1], lsp_id); + if (!lsp) { + sched_debug( + "ISIS (%s): Pseudonode LSP does not exist yet, nothing to regenerate.", + area->area_tag); + continue; + } + + /* + * Throttle avoidance + */ + sched_debug( + "ISIS (%s): Will schedule PSN regen timer. Last run was: %lld, Now is: %lld", + area->area_tag, (long long)lsp->last_generated, + (long long)now); + EVENT_OFF(circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); + diff = now - lsp->last_generated; + if (diff < circuit->area->lsp_gen_interval[lvl - 1]) { + timeout = + 1000 * (circuit->area->lsp_gen_interval[lvl - 1] + - diff); + sched_debug( + "ISIS (%s): Sechduling in %ld ms to match configured lsp_gen_interval", + area->area_tag, timeout); + } else { + timeout = 100; + sched_debug( + "ISIS (%s): Last generation was more than lsp_gen_interval ago. Scheduling for execution in %ld ms.", + area->area_tag, timeout); + } + + circuit->lsp_regenerate_pending[lvl - 1] = 1; + + if (lvl == IS_LEVEL_1) { + event_add_timer_msec( + master, lsp_l1_refresh_pseudo, circuit, timeout, + &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); + } else if (lvl == IS_LEVEL_2) { + event_add_timer_msec( + master, lsp_l2_refresh_pseudo, circuit, timeout, + &circuit->u.bc.t_refresh_pseudo_lsp[lvl - 1]); + } + } + + return ISIS_OK; +} + +/* + * Walk through LSPs for an area + * - set remaining lifetime + */ +void lsp_tick(struct event *thread) +{ + struct isis_area *area; + struct isis_lsp *lsp; + int level; + uint16_t rem_lifetime; + bool fabricd_sync_incomplete = false; + + area = EVENT_ARG(thread); + assert(area); + area->t_tick = NULL; + event_add_timer(master, lsp_tick, area, 1, &area->t_tick); + + struct isis_circuit *fabricd_init_c = fabricd_initial_sync_circuit(area); + + /* + * Remove LSPs which have aged out + */ + for (level = 0; level < ISIS_LEVELS; level++) { + struct isis_lsp *next = lspdb_first(&area->lspdb[level]); + frr_each_from (lspdb, &area->lspdb[level], lsp, next) { + /* + * The lsp rem_lifetime is kept at 0 for MaxAge + * or + * ZeroAgeLifetime depending on explicit purge + * or + * natural age out. So schedule spf only once + * when + * the first time rem_lifetime becomes 0. + */ + rem_lifetime = lsp->hdr.rem_lifetime; + lsp_set_time(lsp); + + /* + * Schedule may run spf which should be done + * only after + * the lsp rem_lifetime becomes 0 for the first + * time. + * ISO 10589 - 7.3.16.4 first paragraph. + */ + if (rem_lifetime == 1 && lsp->hdr.seqno != 0) { + /* 7.3.16.4 a) set SRM flags on all */ + /* 7.3.16.4 b) retain only the header */ + if (lsp->area->purge_originator) + lsp_purge(lsp, lsp->level, NULL); + else + lsp_flood(lsp, NULL); + /* 7.3.16.4 c) record the time to purge + * FIXME */ + isis_spf_schedule(lsp->area, lsp->level); + isis_te_lsp_event(lsp, LSP_TICK); + } + + if (lsp->age_out == 0) { + zlog_debug( + "ISIS-Upd (%s): L%u LSP %pLS seq 0x%08x aged out", + area->area_tag, lsp->level, + lsp->hdr.lsp_id, lsp->hdr.seqno); + + /* if we're aging out fragment 0, lsp_destroy() + * below will delete all other fragments too, + * so we need to skip over those + */ + if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) + while (next && + !memcmp(next->hdr.lsp_id, + lsp->hdr.lsp_id, + ISIS_SYS_ID_LEN + 1)) + next = lspdb_next( + &area->lspdb[level], + next); + + lspdb_del(&area->lspdb[level], lsp); + lsp_destroy(lsp); + lsp = NULL; + } + + if (fabricd_init_c && lsp) { + fabricd_sync_incomplete |= + ISIS_CHECK_FLAG(lsp->SSNflags, + fabricd_init_c); + } + } + } + + if (fabricd_init_c + && !fabricd_sync_incomplete + && !isis_tx_queue_len(fabricd_init_c->tx_queue)) { + fabricd_initial_sync_finish(area); + } +} + +void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level) +{ + struct isis_lsp *lsp; + + lsp = lsp_search(&circuit->area->lspdb[level - 1], id); + if (!lsp) + return; + + lsp_purge(lsp, level, NULL); +} + +/* + * Purge own LSP that is received and we don't have. + * -> Do as in 7.3.16.4 + */ +void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr, + struct isis_area *area) +{ + struct isis_lsp *lsp; + + /* + * We need to create the LSP to be purged + */ + lsp = XCALLOC(MTYPE_ISIS_LSP, sizeof(struct isis_lsp)); + lsp->area = area; + lsp->level = level; + lsp_adjust_stream(lsp); + lsp->age_out = ZERO_AGE_LIFETIME; + lsp->area->lsp_purge_count[level - 1]++; + + memcpy(&lsp->hdr, hdr, sizeof(lsp->hdr)); + lsp->hdr.rem_lifetime = 0; + + lsp_purge_add_poi(lsp, NULL); + + lsp_pack_pdu(lsp); + + lsp_insert(&area->lspdb[lsp->level - 1], lsp); + lsp_flood(lsp, NULL); + + return; +} + +void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set) +{ + struct listnode *node; + struct isis_circuit *circuit; + + assert(lsp); + + if (!lsp->area) + return; + + struct list *circuit_list = lsp->area->circuit_list; + for (ALL_LIST_ELEMENTS_RO(circuit_list, node, circuit)) { + if (set) { + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + } else { + isis_tx_queue_del(circuit->tx_queue, lsp); + } + } +} + +void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit, + const char *func, const char *file, int line) +{ + if (IS_DEBUG_FLOODING) { + zlog_debug("Flooding LSP %pLS%s%s (From %s %s:%d)", + lsp->hdr.lsp_id, circuit ? " except on " : "", + circuit ? circuit->interface->name : "", func, file, + line); + } + + if (!fabricd) + lsp_set_all_srmflags(lsp, true); + else + fabricd_lsp_flood(lsp, circuit); + + if (circuit) + isis_tx_queue_del(circuit->tx_queue, lsp); +} + +static int lsp_handle_adj_state_change(struct isis_adjacency *adj) +{ + lsp_regenerate_schedule(adj->circuit->area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return 0; +} + +/* + * Iterate over all IP reachability TLVs in a LSP (all fragments) of the given + * address-family and MT-ID. + */ +int isis_lsp_iterate_ip_reach(struct isis_lsp *lsp, int family, uint16_t mtid, + lsp_ip_reach_iter_cb cb, void *arg) +{ + bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id); + struct isis_lsp *frag; + struct listnode *node; + + if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0) + return LSP_ITER_CONTINUE; + + /* Parse LSP */ + if (lsp->tlvs) { + if (!fabricd && !pseudo_lsp && family == AF_INET + && mtid == ISIS_MT_IPV4_UNICAST) { + struct isis_item_list *reachs[] = { + &lsp->tlvs->oldstyle_ip_reach, + &lsp->tlvs->oldstyle_ip_reach_ext}; + + for (unsigned int i = 0; i < array_size(reachs); i++) { + struct isis_oldstyle_ip_reach *r; + + for (r = (struct isis_oldstyle_ip_reach *) + reachs[i] + ->head; + r; r = r->next) { + bool external = i ? true : false; + + if ((*cb)((struct prefix *)&r->prefix, + r->metric, external, NULL, + arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + } + } + + if (!pseudo_lsp && family == AF_INET) { + struct isis_item_list *ipv4_reachs; + + if (mtid == ISIS_MT_IPV4_UNICAST) + ipv4_reachs = &lsp->tlvs->extended_ip_reach; + else + ipv4_reachs = isis_lookup_mt_items( + &lsp->tlvs->mt_ip_reach, mtid); + + struct isis_extended_ip_reach *r; + for (r = ipv4_reachs ? (struct isis_extended_ip_reach *) + ipv4_reachs->head + : NULL; + r; r = r->next) { + if ((*cb)((struct prefix *)&r->prefix, + r->metric, false, r->subtlvs, arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + } + + if (!pseudo_lsp && family == AF_INET6) { + struct isis_item_list *ipv6_reachs; + struct isis_ipv6_reach *r; + + if (mtid == ISIS_MT_IPV4_UNICAST) + ipv6_reachs = &lsp->tlvs->ipv6_reach; + else + ipv6_reachs = isis_lookup_mt_items( + &lsp->tlvs->mt_ipv6_reach, mtid); + + for (r = ipv6_reachs ? (struct isis_ipv6_reach *) + ipv6_reachs->head + : NULL; + r; r = r->next) { + if ((*cb)((struct prefix *)&r->prefix, + r->metric, r->external, r->subtlvs, + arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + } + } + + /* Parse LSP fragments if it is not a fragment itself */ + if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) { + if (!frag->tlvs) + continue; + + if (isis_lsp_iterate_ip_reach(frag, family, mtid, cb, + arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + + return LSP_ITER_CONTINUE; +} + +/* + * Iterate over all IS reachability TLVs in a LSP (all fragments) of the given + * MT-ID. + */ +int isis_lsp_iterate_is_reach(struct isis_lsp *lsp, uint16_t mtid, + lsp_is_reach_iter_cb cb, void *arg) +{ + bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id); + struct isis_lsp *frag; + struct listnode *node; + struct isis_item *head; + struct isis_item_list *te_neighs; + + if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0) + return LSP_ITER_CONTINUE; + + /* Parse LSP */ + if (lsp->tlvs) { + if (pseudo_lsp || mtid == ISIS_MT_IPV4_UNICAST) { + head = lsp->tlvs->oldstyle_reach.head; + for (struct isis_oldstyle_reach *reach = + (struct isis_oldstyle_reach *)head; + reach; reach = reach->next) { + if ((*cb)(reach->id, reach->metric, true, NULL, + arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + } + + if (pseudo_lsp || mtid == ISIS_MT_IPV4_UNICAST) + te_neighs = &lsp->tlvs->extended_reach; + else + te_neighs = + isis_get_mt_items(&lsp->tlvs->mt_reach, mtid); + if (te_neighs) { + head = te_neighs->head; + for (struct isis_extended_reach *reach = + (struct isis_extended_reach *)head; + reach; reach = reach->next) { + if ((*cb)(reach->id, reach->metric, false, + reach->subtlvs, arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + } + } + + /* Parse LSP fragments if it not a fragment itself. */ + if (!LSP_FRAGMENT(lsp->hdr.lsp_id)) + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) { + if (!frag->tlvs) + continue; + + if (isis_lsp_iterate_is_reach(frag, mtid, cb, arg) + == LSP_ITER_STOP) + return LSP_ITER_STOP; + } + + return LSP_ITER_CONTINUE; +} + +void lsp_init(void) +{ + device_startup = true; + hook_register(isis_adj_state_change_hook, + lsp_handle_adj_state_change); +} diff --git a/isisd/isis_lsp.h b/isisd/isis_lsp.h new file mode 100644 index 0000000..3839a95 --- /dev/null +++ b/isisd/isis_lsp.h @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_lsp.h + * LSP processing + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_LSP_H +#define _ZEBRA_ISIS_LSP_H + +#include "lib/typesafe.h" +#include "isisd/isis_pdu.h" + +PREDECL_RBTREE_UNIQ(lspdb); + +struct isis; +/* Structure for isis_lsp, this structure will only support the fixed + * System ID (Currently 6) (atleast for now). In order to support more + * We will have to split the header into two parts, and for readability + * sake it should better be avoided */ +struct isis_lsp { + struct lspdb_item dbe; + + struct isis_lsp_hdr hdr; + struct stream *pdu; /* full pdu lsp */ + union { + struct list *frags; + struct isis_lsp *zero_lsp; + } lspu; + uint32_t SSNflags[ISIS_MAX_CIRCUITS]; + int level; /* L1 or L2? */ + int scheduled; /* scheduled for sending */ + time_t installed; + time_t last_generated; + int own_lsp; + /* used for 60 second counting when rem_lifetime is zero */ + int age_out; + struct isis_area *area; + struct isis_tlvs *tlvs; + + time_t flooding_time; + struct list *flooding_neighbors[TX_LSP_CIRCUIT_SCOPED + 1]; + char *flooding_interface; + bool flooding_circuit_scoped; +}; + +extern int lspdb_compare(const struct isis_lsp *a, const struct isis_lsp *b); +DECLARE_RBTREE_UNIQ(lspdb, struct isis_lsp, dbe, lspdb_compare); + +void lsp_db_init(struct lspdb_head *head); +void lsp_db_fini(struct lspdb_head *head); +void lsp_tick(struct event *thread); +void set_overload_on_start_timer(struct event *thread); + +int lsp_generate(struct isis_area *area, int level); +#define lsp_regenerate_schedule(area, level, all_pseudo) \ + _lsp_regenerate_schedule((area), (level), (all_pseudo), true, \ + __func__, __FILE__, __LINE__) +int _lsp_regenerate_schedule(struct isis_area *area, int level, + int all_pseudo, bool postpone, + const char *func, const char *file, int line); +int lsp_generate_pseudo(struct isis_circuit *circuit, int level); +int lsp_regenerate_schedule_pseudo(struct isis_circuit *circuit, int level); + +bool isis_level2_adj_up(struct isis_area *area); + +struct isis_lsp *lsp_new(struct isis_area *area, uint8_t *lsp_id, + uint16_t rem_lifetime, uint32_t seq_num, + uint8_t lsp_bits, uint16_t checksum, + struct isis_lsp *lsp0, int level); +struct isis_lsp *lsp_new_from_recv(struct isis_lsp_hdr *hdr, + struct isis_tlvs *tlvs, + struct stream *stream, struct isis_lsp *lsp0, + struct isis_area *area, int level); +void lsp_insert(struct lspdb_head *head, struct isis_lsp *lsp); +struct isis_lsp *lsp_search(struct lspdb_head *head, const uint8_t *id); + +void lsp_build_list(struct lspdb_head *head, const uint8_t *start_id, + const uint8_t *stop_id, uint8_t num_lsps, + struct list *list); +void lsp_build_list_nonzero_ht(struct lspdb_head *head, + const uint8_t *start_id, + const uint8_t *stop_id, struct list *list); +void lsp_search_and_destroy(struct lspdb_head *head, const uint8_t *id); +void lsp_purge_pseudo(uint8_t *id, struct isis_circuit *circuit, int level); +void lsp_purge_non_exist(int level, struct isis_lsp_hdr *hdr, + struct isis_area *area); + +#define LSP_EQUAL 1 +#define LSP_NEWER 2 +#define LSP_OLDER 3 + +#define LSP_PSEUDO_ID(I) ((I)[ISIS_SYS_ID_LEN]) +#define LSP_FRAGMENT(I) ((I)[ISIS_SYS_ID_LEN + 1]) +#define OWNLSPID(I) \ + memcpy((I), isis->sysid, ISIS_SYS_ID_LEN); \ + (I)[ISIS_SYS_ID_LEN] = 0; \ + (I)[ISIS_SYS_ID_LEN + 1] = 0 +int lsp_id_cmp(uint8_t *id1, uint8_t *id2); +int lsp_compare(char *areatag, struct isis_lsp *lsp, uint32_t seqno, + uint16_t checksum, uint16_t rem_lifetime); +void lsp_update(struct isis_lsp *lsp, struct isis_lsp_hdr *hdr, + struct isis_tlvs *tlvs, struct stream *stream, + struct isis_area *area, int level, bool confusion); +void lsp_inc_seqno(struct isis_lsp *lsp, uint32_t seqno); +void lspid_print(uint8_t *lsp_id, char *dest, size_t dest_len, char dynhost, + char frag, struct isis *isis); +void lsp_print_common(struct isis_lsp *lsp, struct vty *vty, + struct json_object *json, char dynhost, + struct isis *isis); +void lsp_print_vty(struct isis_lsp *lsp, struct vty *vty, char dynhost, + struct isis *isis); +void lsp_print_json(struct isis_lsp *lsp, struct json_object *json, + char dynhost, struct isis *isis); +void lsp_print_detail(struct isis_lsp *lsp, struct vty *vty, + struct json_object *json, char dynhost, + struct isis *isis); +int lsp_print_all(struct vty *vty, struct json_object *json, + struct lspdb_head *head, char detail, char dynhost, + struct isis *isis); +/* sets SRMflags for all active circuits of an lsp */ +void lsp_set_all_srmflags(struct isis_lsp *lsp, bool set); + +#define LSP_ITER_CONTINUE 0 +#define LSP_ITER_STOP -1 + +/* Callback used by isis_lsp_iterate_ip_reach() function. */ +struct isis_subtlvs; +typedef int (*lsp_ip_reach_iter_cb)(const struct prefix *prefix, + uint32_t metric, bool external, + struct isis_subtlvs *subtlvs, void *arg); + +/* Callback used by isis_lsp_iterate_is_reach() function. */ +typedef int (*lsp_is_reach_iter_cb)(const uint8_t *id, uint32_t metric, + bool oldmetric, + struct isis_ext_subtlvs *subtlvs, + void *arg); + +int isis_lsp_iterate_ip_reach(struct isis_lsp *lsp, int family, uint16_t mtid, + lsp_ip_reach_iter_cb cb, void *arg); +int isis_lsp_iterate_is_reach(struct isis_lsp *lsp, uint16_t mtid, + lsp_is_reach_iter_cb cb, void *arg); + +#define lsp_flood(lsp, circuit) \ + _lsp_flood((lsp), (circuit), __func__, __FILE__, __LINE__) +void _lsp_flood(struct isis_lsp *lsp, struct isis_circuit *circuit, + const char *func, const char *file, int line); +void lsp_init(void); + +#endif /* ISIS_LSP */ diff --git a/isisd/isis_main.c b/isisd/isis_main.c new file mode 100644 index 0000000..8dd3a97 --- /dev/null +++ b/isisd/isis_main.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_main.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "getopt.h" +#include "frrevent.h" +#include "log.h" +#include +#include "command.h" +#include "vty.h" +#include "memory.h" +#include "stream.h" +#include "if.h" +#include "privs.h" +#include "sigevent.h" +#include "filter.h" +#include "plist.h" +#include "zclient.h" +#include "vrf.h" +#include "qobj.h" +#include "libfrr.h" +#include "routemap.h" +#include "affinitymap.h" +#include "libagentx.h" + +#include "isisd/isis_affinitymap.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_route.h" +#include "isisd/isis_routemap.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_te.h" +#include "isisd/isis_errors.h" +#include "isisd/isis_bfd.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_mt.h" +#include "isisd/fabricd.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_ldp_sync.h" + +/* Default configuration file name */ +#define ISISD_DEFAULT_CONFIG "isisd.conf" + +#define FABRICD_STATE_NAME "%s/fabricd.json", frr_libstatedir +#define ISISD_STATE_NAME "%s/isisd.json", frr_libstatedir + +/* The typo was there before. Do not fix it! The point is to load mis-saved + * state files from older versions. + * + * Also fabricd was using the same file. Sigh. + */ +#define ISISD_COMPAT_STATE_NAME "%s/isid-restart.json", frr_runstatedir + +/* isisd privileges */ +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; + +struct zebra_privs_t isisd_privs = { +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +/* isisd options */ +static const struct option longopts[] = { + {"int_num", required_argument, NULL, 'I'}, + {0}}; + +/* Master of threads. */ +struct event_loop *master; + +/* + * Prototypes. + */ +void sighup(void); +void sigint(void); +void sigterm(void); +void sigusr1(void); + + +static __attribute__((__noreturn__)) void terminate(int i) +{ + isis_terminate(); + isis_sr_term(); + isis_srv6_term(); + isis_zebra_stop(); + exit(i); +} + +/* + * Signal handlers + */ +#ifdef FABRICD +void sighup(void) +{ + zlog_notice("SIGHUP/reload is not implemented for fabricd"); + return; +} +#else +static struct frr_daemon_info isisd_di; +void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, isisd_di.config_file, config_default); +} + +#endif + +__attribute__((__noreturn__)) void sigint(void) +{ + zlog_notice("Terminating on signal SIGINT"); + terminate(0); +} + +__attribute__((__noreturn__)) void sigterm(void) +{ + zlog_notice("Terminating on signal SIGTERM"); + terminate(0); +} + +void sigusr1(void) +{ + zlog_debug("SIGUSR1 received"); + zlog_rotate(); +} + +struct frr_signal_t isisd_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigterm, + }, +}; + + +/* clang-format off */ +static const struct frr_yang_module_info *const isisd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, +#ifndef FABRICD + &frr_isisd_info, +#endif /* ifndef FABRICD */ + &frr_route_map_info, + &frr_affinity_map_info, + &frr_vrf_info, +}; +/* clang-format on */ + + +/* Max wait time for config to load before generating LSPs */ +#define ISIS_PRE_CONFIG_MAX_WAIT_SECONDS 600 + +static void isis_config_finish(struct event *t) +{ + struct listnode *node, *inode; + struct isis *isis; + struct isis_area *area; + + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + config_end_lsp_generate(area); + } +} + +static void isis_config_end_timeout(struct event *t) +{ + zlog_err("IS-IS configuration end timer expired after %d seconds.", + ISIS_PRE_CONFIG_MAX_WAIT_SECONDS); + isis_config_finish(t); +} + +static void isis_config_start(void) +{ + EVENT_OFF(t_isis_cfg); + event_add_timer(im->master, isis_config_end_timeout, NULL, + ISIS_PRE_CONFIG_MAX_WAIT_SECONDS, &t_isis_cfg); +} + +static void isis_config_end(void) +{ + /* If ISIS config processing thread isn't running, then + * we can return and rely it's properly handled. + */ + if (!event_is_scheduled(t_isis_cfg)) + return; + + EVENT_OFF(t_isis_cfg); + isis_config_finish(t_isis_cfg); +} + +/* actual paths filled in main() */ +static char state_path[512]; +static char state_compat_path[512]; +static char *state_paths[] = { + state_path, + state_compat_path, + NULL, +}; + +/* clang-format off */ +FRR_DAEMON_INFO( +#ifdef FABRICD + fabricd, OPEN_FABRIC, + + .vty_port = FABRICD_VTY_PORT, + .proghelp = "Implementation of the OpenFabric routing protocol.", +#else + isisd, ISIS, + + .vty_port = ISISD_VTY_PORT, + .proghelp = "Implementation of the IS-IS routing protocol.", +#endif + .copyright = "Copyright (c) 2001-2002 Sampo Saaristo, Ofer Wald and Hannes Gredler", + + .signals = isisd_signals, + .n_signals = array_size(isisd_signals), + + .privs = &isisd_privs, + + .yang_modules = isisd_yang_modules, + .n_yang_modules = array_size(isisd_yang_modules), + + .state_paths = state_paths, +); +/* clang-format on */ + +/* + * Main routine of isisd. Parse arguments and handle IS-IS state machine. + */ +int main(int argc, char **argv, char **envp) +{ + int opt; + int instance = 1; + +#ifdef FABRICD + frr_preinit(&fabricd_di, argc, argv); +#else + frr_preinit(&isisd_di, argc, argv); +#endif + frr_opt_add( + "I:", longopts, + " -I, --int_num Set instance number (label-manager)\n"); + + /* Command line argument treatment. */ + while (1) { + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'I': + instance = atoi(optarg); + if (instance < 1 || instance > (unsigned short)-1) + zlog_err("Instance %i out of range (1..%u)", + instance, (unsigned short)-1); + break; + default: + frr_help_exit(1); + } + } + +#ifdef FABRICD + snprintf(state_path, sizeof(state_path), FABRICD_STATE_NAME); +#else + snprintf(state_path, sizeof(state_path), ISISD_STATE_NAME); +#endif + snprintf(state_compat_path, sizeof(state_compat_path), + ISISD_COMPAT_STATE_NAME); + + /* thread master */ + isis_master_init(frr_init()); + master = im->master; + /* + * initializations + */ + libagentx_init(); + cmd_init_config_callbacks(isis_config_start, isis_config_end); + isis_error_init(); + access_list_init(); + access_list_add_hook(isis_filter_update); + access_list_delete_hook(isis_filter_update); + isis_vrf_init(); + prefix_list_init(); + prefix_list_add_hook(isis_prefix_list_update); + prefix_list_delete_hook(isis_prefix_list_update); + isis_init(); + isis_circuit_init(); +#ifdef FABRICD + isis_vty_daemon_init(); +#endif /* FABRICD */ +#ifndef FABRICD + isis_cli_init(); +#endif /* ifndef FABRICD */ + isis_spf_init(); + isis_redist_init(); + isis_route_map_init(); + isis_mpls_te_init(); + isis_sr_init(); + isis_srv6_init(); + lsp_init(); + mt_init(); + +#ifndef FABRICD + isis_affinity_map_init(); +#endif /* ifndef FABRICD */ + + isis_zebra_init(master, instance); + isis_bfd_init(master); + isis_ldp_sync_init(); + fabricd_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + exit(0); +} diff --git a/isisd/isis_misc.c b/isisd/isis_misc.c new file mode 100644 index 0000000..e4ef6c8 --- /dev/null +++ b/isisd/isis_misc.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_misc.h + * Miscellanous routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "printfrr.h" +#include "stream.h" +#include "vty.h" +#include "hash.h" +#include "if.h" +#include "command.h" +#include "network.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_csm.h" +#include "isisd/isisd.h" +#include "isisd/isis_misc.h" + +#include "isisd/isis_lsp.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dynhn.h" + +/* staticly assigned vars for printing purposes */ +static char sys_hostname[ISO_SYSID_STRLEN]; +struct in_addr new_prefix; +/* len of xxYxxMxWxdxxhxxmxxs + place for #0 termination */ +char datestring[20]; +char nlpidstring[30]; + +/* + * Returns 0 on error, length of buff on ok + * extract dot from the dotted str, and insert all the number in a buff + */ +int dotformat2buff(uint8_t *buff, const char *dotted) +{ + int dotlen, len = 0; + const char *pos = dotted; + uint8_t number[3]; + int nextdotpos = 2; + + number[2] = '\0'; + dotlen = strlen(dotted); + if (dotlen > 50) { + /* this can't be an iso net, its too long */ + return 0; + } + + while ((pos - dotted) < dotlen && len < 20) { + if (*pos == '.') { + /* we expect the . at 2, and than every 5 */ + if ((pos - dotted) != nextdotpos) { + len = 0; + break; + } + nextdotpos += 5; + pos++; + continue; + } + /* we must have at least two chars left here */ + if (dotlen - (pos - dotted) < 2) { + len = 0; + break; + } + + if ((isxdigit((unsigned char)*pos)) && + (isxdigit((unsigned char)*(pos + 1)))) { + memcpy(number, pos, 2); + pos += 2; + } else { + len = 0; + break; + } + + *(buff + len) = (char)strtol((char *)number, NULL, 16); + len++; + } + + return len; +} + +/* + * conversion of XXXX.XXXX.XXXX to memory + */ +int sysid2buff(uint8_t *buff, const char *dotted) +{ + int len = 0; + const char *pos = dotted; + uint8_t number[3]; + + number[2] = '\0'; + // surely not a sysid_string if not 14 length + if (strlen(dotted) != 14) { + return 0; + } + + while (len < ISIS_SYS_ID_LEN) { + if (*pos == '.') { + /* the . is not positioned correctly */ + if (((pos - dotted) != 4) && ((pos - dotted) != 9)) { + len = 0; + break; + } + pos++; + continue; + } + if ((isxdigit((unsigned char)*pos)) && + (isxdigit((unsigned char)*(pos + 1)))) { + memcpy(number, pos, 2); + pos += 2; + } else { + len = 0; + break; + } + + *(buff + len) = (char)strtol((char *)number, NULL, 16); + len++; + } + + return len; +} + +const char *nlpid2str(uint8_t nlpid) +{ + static char buf[4]; + switch (nlpid) { + case NLPID_IP: + return "IPv4"; + case NLPID_IPV6: + return "IPv6"; + case NLPID_SNAP: + return "SNAP"; + case NLPID_CLNP: + return "CLNP"; + case NLPID_ESIS: + return "ES-IS"; + default: + snprintf(buf, sizeof(buf), "%hhu", nlpid); + return buf; + } +} + +/* + * converts the nlpids struct (filled by TLV #129) + * into a string + */ + +char *nlpid2string(struct nlpids *nlpids) +{ + int i; + char tbuf[256]; + nlpidstring[0] = '\0'; + + for (i = 0; i < nlpids->count; i++) { + snprintf(tbuf, sizeof(tbuf), "%s", + nlpid2str(nlpids->nlpids[i])); + strlcat(nlpidstring, tbuf, sizeof(nlpidstring)); + if (nlpids->count - i > 1) + strlcat(nlpidstring, ", ", sizeof(nlpidstring)); + } + + return nlpidstring; +} + +/* + * Returns 0 on error, IS-IS Circuit Type on ok + */ +int string2circuit_t(const char *str) +{ + + if (!str) + return 0; + + if (!strcmp(str, "level-1")) + return IS_LEVEL_1; + + if (!strcmp(str, "level-2-only") || !strcmp(str, "level-2")) + return IS_LEVEL_2; + + if (!strcmp(str, "level-1-2")) + return IS_LEVEL_1_AND_2; + + return 0; +} + +const char *circuit_state2string(int state) +{ + + switch (state) { + case C_STATE_INIT: + return "Init"; + case C_STATE_CONF: + return "Config"; + case C_STATE_UP: + return "Up"; + default: + return "Unknown"; + } + return NULL; +} + +const char *circuit_type2string(int type) +{ + + switch (type) { + case CIRCUIT_T_P2P: + return "p2p"; + case CIRCUIT_T_BROADCAST: + return "lan"; + case CIRCUIT_T_LOOPBACK: + return "loopback"; + default: + return "Unknown"; + } + return NULL; +} + +const char *circuit_t2string(int circuit_t) +{ + switch (circuit_t) { + case IS_LEVEL_1: + return "L1"; + case IS_LEVEL_2: + return "L2"; + case IS_LEVEL_1_AND_2: + return "L1L2"; + default: + return "??"; + } + + return NULL; /* not reached */ +} + +const char *syst2string(int type) +{ + switch (type) { + case ISIS_SYSTYPE_ES: + return "ES"; + case ISIS_SYSTYPE_IS: + return "IS"; + case ISIS_SYSTYPE_L1_IS: + return "1"; + case ISIS_SYSTYPE_L2_IS: + return "2"; + default: + return "??"; + } + + return NULL; /* not reached */ +} + +const char *isis_hello_padding2string(int hello_padding_type) +{ + switch (hello_padding_type) { + case ISIS_HELLO_PADDING_DISABLED: + return "no"; + case ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION: + return "during-adjacency-formation"; + case ISIS_HELLO_PADDING_ALWAYS: + return "yes"; + } + return NULL; /* not reached */ +} + +const char *time2string(uint32_t time) +{ + uint32_t rest; + char tbuf[32]; + datestring[0] = '\0'; + + if (time == 0) + return "-"; + + if (time / SECS_PER_YEAR) { + snprintf(tbuf, sizeof(tbuf), "%uY", time / SECS_PER_YEAR); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = time % SECS_PER_YEAR; + if (rest / SECS_PER_MONTH) { + snprintf(tbuf, sizeof(tbuf), "%uM", rest / SECS_PER_MONTH); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = rest % SECS_PER_MONTH; + if (rest / SECS_PER_WEEK) { + snprintf(tbuf, sizeof(tbuf), "%uw", rest / SECS_PER_WEEK); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = rest % SECS_PER_WEEK; + if (rest / SECS_PER_DAY) { + snprintf(tbuf, sizeof(tbuf), "%ud", rest / SECS_PER_DAY); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = rest % SECS_PER_DAY; + if (rest / SECS_PER_HOUR) { + snprintf(tbuf, sizeof(tbuf), "%uh", rest / SECS_PER_HOUR); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = rest % SECS_PER_HOUR; + if (rest / SECS_PER_MINUTE) { + snprintf(tbuf, sizeof(tbuf), "%um", rest / SECS_PER_MINUTE); + strlcat(datestring, tbuf, sizeof(datestring)); + } + rest = rest % SECS_PER_MINUTE; + if (rest) { + snprintf(tbuf, sizeof(tbuf), "%us", rest); + strlcat(datestring, tbuf, sizeof(datestring)); + } + + return datestring; +} + +/* + * routine to decrement a timer by a random + * number + * + * first argument is the timer and the second is + * the jitter + */ +unsigned long isis_jitter(unsigned long timer, unsigned long jitter) +{ + int j, k; + + if (jitter >= 100) + return timer; + + if (timer == 1) + return timer; + /* + * randomizing just the percent value provides + * no good random numbers - hence the spread + * to RANDOM_SPREAD (100000), which is ok as + * most IS-IS timers are no longer than 16 bit + */ + + j = 1 + (int)((RANDOM_SPREAD * frr_weak_random()) / (RAND_MAX + 1.0)); + + k = timer - (timer * (100 - jitter)) / 100; + + timer = timer - (k * j / RANDOM_SPREAD); + + return timer; +} + +struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen) +{ + memset(&new_prefix, 0, sizeof(new_prefix)); + memcpy(&new_prefix, prefix_start, + (prefix_masklen & 0x3F) + ? ((((prefix_masklen & 0x3F) - 1) >> 3) + 1) + : 0); + return new_prefix; +} + +/* + * Returns the dynamic hostname associated with the passed system ID. + * If no dynamic hostname found then returns formatted system ID. + */ +const char *print_sys_hostname(const uint8_t *sysid) +{ + struct isis_dynhn *dyn; + struct isis *isis = NULL; + struct listnode *node; + + if (!sysid) + return "nullsysid"; + + /* For our system ID return our host name */ + isis = isis_lookup_by_sysid(sysid); + if (isis && !CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + return cmd_hostname_get(); + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + dyn = dynhn_find_by_id(isis, sysid); + if (dyn) + return dyn->hostname; + } + + snprintfrr(sys_hostname, ISO_SYSID_STRLEN, "%pSY", sysid); + return sys_hostname; +} + +/* + * This function is a generic utility that logs data of given length. + * Move this to a shared lib so that any protocol can use it. + */ +void zlog_dump_data(void *data, int len) +{ + int i; + unsigned char *p; + unsigned char c; + char bytestr[4]; + char addrstr[10]; + char hexstr[16 * 3 + 5]; + char charstr[16 * 1 + 5]; + + p = data; + memset(bytestr, 0, sizeof(bytestr)); + memset(addrstr, 0, sizeof(addrstr)); + memset(hexstr, 0, sizeof(hexstr)); + memset(charstr, 0, sizeof(charstr)); + + for (i = 1; i <= len; i++) { + c = *p; + if (isalnum(c) == 0) + c = '.'; + + /* store address for this line */ + if ((i % 16) == 1) + snprintf(addrstr, sizeof(addrstr), "%p", p); + + /* store hex str (for left side) */ + snprintf(bytestr, sizeof(bytestr), "%02X ", *p); + strlcat(hexstr, bytestr, sizeof(hexstr) - strlen(hexstr) - 1); + + /* store char str (for right side) */ + snprintf(bytestr, sizeof(bytestr), "%c", c); + strlcat(charstr, bytestr, + sizeof(charstr) - strlen(charstr) - 1); + + if ((i % 16) == 0) { + /* line completed */ + zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr, + charstr); + hexstr[0] = 0; + charstr[0] = 0; + } else if ((i % 8) == 0) { + /* half line: add whitespaces */ + strlcat(hexstr, " ", + sizeof(hexstr) - strlen(hexstr) - 1); + strlcat(charstr, " ", + sizeof(charstr) - strlen(charstr) - 1); + } + p++; /* next byte */ + } + + /* print rest of buffer if not empty */ + if (strlen(hexstr) > 0) + zlog_debug("[%8.8s] %-50.50s %s", addrstr, hexstr, charstr); + return; +} + +void log_multiline(int priority, const char *prefix, const char *format, ...) +{ + char shortbuf[256]; + va_list ap; + char *p; + + va_start(ap, format); + p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); + va_end(ap); + + if (!p) + return; + + char *saveptr = NULL; + for (char *line = strtok_r(p, "\n", &saveptr); line; + line = strtok_r(NULL, "\n", &saveptr)) { + zlog(priority, "%s%s", prefix, line); + } + + if (p != shortbuf) + XFREE(MTYPE_TMP, p); +} + +char *log_uptime(time_t uptime, char *buf, size_t nbuf) +{ + struct tm tm; + time_t difftime = time(NULL); + difftime -= uptime; + gmtime_r(&difftime, &tm); + + if (difftime < ONE_DAY_SECOND) + snprintf(buf, nbuf, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, + tm.tm_sec); + else if (difftime < ONE_WEEK_SECOND) + snprintf(buf, nbuf, "%dd%02dh%02dm", tm.tm_yday, tm.tm_hour, + tm.tm_min); + else + snprintf(buf, nbuf, "%02dw%dd%02dh", tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7), tm.tm_hour); + + return buf; +} + +void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...) +{ + char shortbuf[256]; + va_list ap; + char *p; + + va_start(ap, format); + p = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); + va_end(ap); + + if (!p) + return; + + char *saveptr = NULL; + for (char *line = strtok_r(p, "\n", &saveptr); line; + line = strtok_r(NULL, "\n", &saveptr)) { + vty_out(vty, "%s%s\n", prefix, line); + } + + if (p != shortbuf) + XFREE(MTYPE_TMP, p); +} + +void vty_out_timestr(struct vty *vty, time_t uptime) +{ + time_t difftime = time(NULL); + char buf[MONOTIME_STRLEN]; + + difftime -= uptime; + + frrtime_to_interval(difftime, buf, sizeof(buf)); + + vty_out(vty, "%s ago", buf); +} diff --git a/isisd/isis_misc.h b/isisd/isis_misc.h new file mode 100644 index 0000000..3a1d136 --- /dev/null +++ b/isisd/isis_misc.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_misc.h + * Miscellanous routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_MISC_H +#define _ZEBRA_ISIS_MISC_H + +int string2circuit_t(const char *); +const char *circuit_t2string(int); +const char *circuit_state2string(int state); +const char *circuit_type2string(int type); +const char *syst2string(int); +const char *isis_hello_padding2string(int hello_padding_type); +struct in_addr newprefix2inaddr(uint8_t *prefix_start, uint8_t prefix_masklen); +/* + * Converting input to memory stored format + * return value of 0 indicates wrong input + */ +int dotformat2buff(uint8_t *, const char *); +int sysid2buff(uint8_t *, const char *); + +/* + * Printing functions + */ +const char *time2string(uint32_t); +const char *nlpid2str(uint8_t nlpid); +/* typedef struct nlpids nlpids; */ +char *nlpid2string(struct nlpids *); +const char *print_sys_hostname(const uint8_t *sysid); +void zlog_dump_data(void *data, int len); + +/* + * misc functions + */ +unsigned long isis_jitter(unsigned long timer, unsigned long jitter); + +/* + * macros + */ +#define GETSYSID(A) \ + (A->area_addr + (A->addr_len - (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN))) + +/* used for calculating nice string representation instead of plain seconds */ + +#define SECS_PER_MINUTE 60 +#define SECS_PER_HOUR 3600 +#define SECS_PER_DAY 86400 +#define SECS_PER_WEEK 604800 +#define SECS_PER_MONTH 2628000 +#define SECS_PER_YEAR 31536000 + +enum { ISIS_UI_LEVEL_BRIEF, + ISIS_UI_LEVEL_DETAIL, + ISIS_UI_LEVEL_EXTENSIVE, +}; + +#include "lib/log.h" +void log_multiline(int priority, const char *prefix, const char *format, ...) + PRINTFRR(3, 4); +char *log_uptime(time_t uptime, char *buf, size_t nbuf); +struct vty; +void vty_multiline(struct vty *vty, const char *prefix, const char *format, ...) + PRINTFRR(3, 4); +void vty_out_timestr(struct vty *vty, time_t uptime); +#endif diff --git a/isisd/isis_mt.c b/isisd/isis_mt.c new file mode 100644 index 0000000..d04a24d --- /dev/null +++ b/isisd/isis_mt.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - Multi Topology Support + * + * Copyright (C) 2017 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#include +#include "isisd/isisd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_tlvs.h" + +DEFINE_MTYPE_STATIC(ISISD, MT_AREA_SETTING, "ISIS MT Area Setting"); +DEFINE_MTYPE_STATIC(ISISD, MT_CIRCUIT_SETTING, "ISIS MT Circuit Setting"); +DEFINE_MTYPE_STATIC(ISISD, MT_ADJ_INFO, "ISIS MT Adjacency Info"); + +bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area) +{ + struct isis_area_mt_setting *area_mt_setting; + area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_DSTSRC); + + return (area_mt_setting && area_mt_setting->enabled); +} + +uint16_t isis_area_ipv6_topology(struct isis_area *area) +{ + struct isis_area_mt_setting *area_mt_setting; + area_mt_setting = area_lookup_mt_setting(area, ISIS_MT_IPV6_UNICAST); + + if (area_mt_setting && area_mt_setting->enabled) + return ISIS_MT_IPV6_UNICAST; + return ISIS_MT_IPV4_UNICAST; +} + +/* MT naming api */ +const char *isis_mtid2str(uint16_t mtid) +{ + static char buf[sizeof("65535")]; + + switch (mtid) { + case ISIS_MT_STANDARD: + return "standard"; + case ISIS_MT_IPV4_MGMT: + return "ipv4-mgmt"; + case ISIS_MT_IPV6_UNICAST: + return "ipv6-unicast"; + case ISIS_MT_IPV4_MULTICAST: + return "ipv4-multicast"; + case ISIS_MT_IPV6_MULTICAST: + return "ipv6-multicast"; + case ISIS_MT_IPV6_MGMT: + return "ipv6-mgmt"; + case ISIS_MT_IPV6_DSTSRC: + return "ipv6-dstsrc"; + default: + snprintf(buf, sizeof(buf), "%hu", mtid); + return buf; + } +} + +uint16_t isis_str2mtid(const char *name) +{ + if (!strcmp(name, "ipv4-unicast")) + return ISIS_MT_IPV4_UNICAST; + if (!strcmp(name, "standard")) + return ISIS_MT_STANDARD; + if (!strcmp(name, "ipv4-mgmt")) + return ISIS_MT_IPV4_MGMT; + if (!strcmp(name, "ipv6-unicast")) + return ISIS_MT_IPV6_UNICAST; + if (!strcmp(name, "ipv4-multicast")) + return ISIS_MT_IPV4_MULTICAST; + if (!strcmp(name, "ipv6-multicast")) + return ISIS_MT_IPV6_MULTICAST; + if (!strcmp(name, "ipv6-mgmt")) + return ISIS_MT_IPV6_MGMT; + if (!strcmp(name, "ipv6-dstsrc")) + return ISIS_MT_IPV6_DSTSRC; + return -1; +} + +/* General MT settings api */ + +struct mt_setting { + ISIS_MT_INFO_FIELDS; +}; + +static void *lookup_mt_setting(struct list *mt_list, uint16_t mtid) +{ + struct listnode *node; + struct mt_setting *setting; + + for (ALL_LIST_ELEMENTS_RO(mt_list, node, setting)) { + if (setting->mtid == mtid) + return setting; + } + return NULL; +} + +static void add_mt_setting(struct list **mt_list, void *setting) +{ + if (!*mt_list) + *mt_list = list_new(); + listnode_add(*mt_list, setting); +} + +/* Area specific MT settings api */ + +struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area, + uint16_t mtid) +{ + return lookup_mt_setting(area->mt_settings, mtid); +} + +struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area, + uint16_t mtid) +{ + struct isis_area_mt_setting *setting; + + setting = XCALLOC(MTYPE_MT_AREA_SETTING, sizeof(*setting)); + setting->mtid = mtid; + return setting; +} + +static void area_free_mt_setting(void *setting) +{ + XFREE(MTYPE_MT_AREA_SETTING, setting); +} + +void area_add_mt_setting(struct isis_area *area, + struct isis_area_mt_setting *setting) +{ + add_mt_setting(&area->mt_settings, setting); +} + +void area_mt_init(struct isis_area *area) +{ + struct isis_area_mt_setting *v4_unicast_setting; + + /* MTID 0 is always enabled */ + v4_unicast_setting = area_new_mt_setting(area, ISIS_MT_IPV4_UNICAST); + v4_unicast_setting->enabled = true; + add_mt_setting(&area->mt_settings, v4_unicast_setting); + area->mt_settings->del = area_free_mt_setting; +} + +void area_mt_finish(struct isis_area *area) +{ + list_delete(&area->mt_settings); +} + +struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area, + uint16_t mtid) +{ + struct isis_area_mt_setting *setting; + + setting = area_lookup_mt_setting(area, mtid); + if (!setting) { + setting = area_new_mt_setting(area, mtid); + area_add_mt_setting(area, setting); + } + return setting; +} + +int area_write_mt_settings(struct isis_area *area, struct vty *vty) +{ + int written = 0; + struct listnode *node; + struct isis_area_mt_setting *setting; + + for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { + const char *name = isis_mtid2str(setting->mtid); + if (name && setting->enabled) { + if (setting->mtid == ISIS_MT_IPV4_UNICAST) + continue; /* always enabled, no need to write + out config */ + vty_out(vty, " topology %s%s\n", name, + setting->overload ? " overload" : ""); + written++; + } + } + return written; +} + +bool area_is_mt(struct isis_area *area) +{ + struct listnode *node, *node2; + struct isis_area_mt_setting *setting; + struct isis_circuit *circuit; + struct isis_circuit_mt_setting *csetting; + + for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { + if (setting->enabled && setting->mtid != ISIS_MT_IPV4_UNICAST) + return true; + } + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node2, + csetting)) { + if (!csetting->enabled + && csetting->mtid == ISIS_MT_IPV4_UNICAST) + return true; + } + } + + return false; +} + +struct isis_area_mt_setting **area_mt_settings(struct isis_area *area, + unsigned int *mt_count) +{ + static unsigned int size = 0; + static struct isis_area_mt_setting **rv = NULL; + + unsigned int count = 0; + struct listnode *node; + struct isis_area_mt_setting *setting; + + for (ALL_LIST_ELEMENTS_RO(area->mt_settings, node, setting)) { + if (!setting->enabled) + continue; + + count++; + if (count > size) { + rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv)); + size = count; + } + rv[count - 1] = setting; + } + + *mt_count = count; + return rv; +} + +/* Circuit specific MT settings api */ + +struct isis_circuit_mt_setting * +circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid) +{ + return lookup_mt_setting(circuit->mt_settings, mtid); +} + +struct isis_circuit_mt_setting * +circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid) +{ + struct isis_circuit_mt_setting *setting; + + setting = XCALLOC(MTYPE_MT_CIRCUIT_SETTING, sizeof(*setting)); + setting->mtid = mtid; + setting->enabled = true; /* Enabled is default for circuit */ + return setting; +} + +static void circuit_free_mt_setting(void *setting) +{ + XFREE(MTYPE_MT_CIRCUIT_SETTING, setting); +} + +void circuit_add_mt_setting(struct isis_circuit *circuit, + struct isis_circuit_mt_setting *setting) +{ + add_mt_setting(&circuit->mt_settings, setting); +} + +void circuit_mt_init(struct isis_circuit *circuit) +{ + circuit->mt_settings = list_new(); + circuit->mt_settings->del = circuit_free_mt_setting; +} + +void circuit_mt_finish(struct isis_circuit *circuit) +{ + list_delete(&circuit->mt_settings); +} + +struct isis_circuit_mt_setting * +circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid) +{ + struct isis_circuit_mt_setting *setting; + + setting = circuit_lookup_mt_setting(circuit, mtid); + if (!setting) { + setting = circuit_new_mt_setting(circuit, mtid); + circuit_add_mt_setting(circuit, setting); + } + return setting; +} + +#ifdef FABRICD +static int circuit_write_mt_settings(struct isis_circuit *circuit, + struct vty *vty) +{ + int written = 0; + struct listnode *node; + struct isis_circuit_mt_setting *setting; + + for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node, setting)) { + const char *name = isis_mtid2str(setting->mtid); + if (name && !setting->enabled) { + vty_out(vty, " no " PROTO_NAME " topology %s\n", name); + written++; + } + } + return written; +} +#endif + +struct isis_circuit_mt_setting ** +circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count) +{ + static unsigned int size = 0; + static struct isis_circuit_mt_setting **rv = NULL; + + struct isis_area_mt_setting **area_settings; + unsigned int area_count; + + unsigned int count = 0; + + struct listnode *node; + struct isis_circuit_mt_setting *setting; + + area_settings = area_mt_settings(circuit->area, &area_count); + + for (unsigned int i = 0; i < area_count; i++) { + for (ALL_LIST_ELEMENTS_RO(circuit->mt_settings, node, + setting)) { + if (setting->mtid != area_settings[i]->mtid) + continue; + break; + } + if (!setting) + setting = circuit_get_mt_setting( + circuit, area_settings[i]->mtid); + + if (!setting->enabled) + continue; + + count++; + if (count > size) { + rv = XREALLOC(MTYPE_TMP, rv, count * sizeof(*rv)); + size = count; + } + rv[count - 1] = setting; + } + + *mt_count = count; + return rv; +} + +/* ADJ specific MT API */ +static void adj_mt_set(struct isis_adjacency *adj, unsigned int index, + uint16_t mtid) +{ + if (adj->mt_count < index + 1) { + adj->mt_set = XREALLOC(MTYPE_MT_ADJ_INFO, adj->mt_set, + (index + 1) * sizeof(*adj->mt_set)); + adj->mt_count = index + 1; + } + adj->mt_set[index] = mtid; +} + +bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable, + struct isis_adjacency *adj) +{ + struct isis_circuit_mt_setting **mt_settings; + unsigned int circuit_mt_count; + + unsigned int intersect_count = 0; + + uint16_t *old_mt_set = NULL; + unsigned int old_mt_count; + + old_mt_count = adj->mt_count; + if (old_mt_count) { + old_mt_set = + XCALLOC(MTYPE_TMP, old_mt_count * sizeof(*old_mt_set)); + memcpy(old_mt_set, adj->mt_set, + old_mt_count * sizeof(*old_mt_set)); + } + + mt_settings = circuit_mt_settings(adj->circuit, &circuit_mt_count); + for (unsigned int i = 0; i < circuit_mt_count; i++) { + if (!tlvs->mt_router_info.count + && !tlvs->mt_router_info_empty) { + /* Other end does not have MT enabled */ + if (mt_settings[i]->mtid == ISIS_MT_IPV4_UNICAST + && (v4_usable || v6_usable)) + adj_mt_set(adj, intersect_count++, + ISIS_MT_IPV4_UNICAST); + } else { + struct isis_mt_router_info *info_head; + + info_head = (struct isis_mt_router_info *) + tlvs->mt_router_info.head; + for (struct isis_mt_router_info *info = info_head; info; + info = info->next) { + if (mt_settings[i]->mtid == info->mtid) { + bool usable; + switch (info->mtid) { + case ISIS_MT_IPV4_UNICAST: + case ISIS_MT_IPV4_MGMT: + case ISIS_MT_IPV4_MULTICAST: + usable = v4_usable; + break; + case ISIS_MT_IPV6_UNICAST: + case ISIS_MT_IPV6_MGMT: + case ISIS_MT_IPV6_MULTICAST: + usable = v6_usable; + break; + default: + usable = true; + break; + } + if (usable) + adj_mt_set(adj, + intersect_count++, + info->mtid); + } + } + } + } + adj->mt_count = intersect_count; + + bool changed = false; + + if (adj->mt_count != old_mt_count) + changed = true; + + if (!changed && old_mt_count + && memcmp(adj->mt_set, old_mt_set, + old_mt_count * sizeof(*old_mt_set))) + changed = true; + + if (old_mt_count) + XFREE(MTYPE_TMP, old_mt_set); + + return changed; +} + +bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid) +{ + for (unsigned int i = 0; i < adj->mt_count; i++) + if (adj->mt_set[i] == mtid) + return true; + return false; +} + +void adj_mt_finish(struct isis_adjacency *adj) +{ + XFREE(MTYPE_MT_ADJ_INFO, adj->mt_set); + adj->mt_count = 0; +} + +static void mt_set_add(uint16_t **mt_set, unsigned int *size, + unsigned int *index, uint16_t mtid) +{ + for (unsigned int i = 0; i < *index; i++) { + if ((*mt_set)[i] == mtid) + return; + } + + if (*index >= *size) { + *mt_set = XREALLOC(MTYPE_TMP, *mt_set, + sizeof(**mt_set) * ((*index) + 1)); + *size = (*index) + 1; + } + + (*mt_set)[*index] = mtid; + *index = (*index) + 1; +} + +static uint16_t *circuit_bcast_mt_set(struct isis_circuit *circuit, int level, + unsigned int *mt_count) +{ + static uint16_t *rv; + static unsigned int size; + struct listnode *node; + struct isis_adjacency *adj; + + unsigned int count = 0; + + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + *mt_count = 0; + return NULL; + } + + for (ALL_LIST_ELEMENTS_RO(circuit->u.bc.adjdb[level - 1], node, adj)) { + if (adj->adj_state != ISIS_ADJ_UP) + continue; + for (unsigned int i = 0; i < adj->mt_count; i++) + mt_set_add(&rv, &size, &count, adj->mt_set[i]); + } + + *mt_count = count; + return rv; +} + +static void tlvs_add_mt_set(struct isis_area *area, struct isis_tlvs *tlvs, + unsigned int mt_count, uint16_t *mt_set, + uint8_t *id, uint32_t metric, + struct isis_ext_subtlvs *ext) +{ + /* Check if MT is enable for this area */ + if (!area_is_mt(area)) { + lsp_debug( + "ISIS (%s): Adding %pPN as te-style neighbor (MT disable)", + area->area_tag, id); + isis_tlvs_add_extended_reach(tlvs, ISIS_MT_DISABLE, id, metric, + ext); + return; + } + + /* Process Multi-Topology */ + for (unsigned int i = 0; i < mt_count; i++) { + uint16_t mtid = mt_set[i]; + if (mt_set[i] == ISIS_MT_IPV4_UNICAST) { + lsp_debug("ISIS (%s): Adding %pPN as te-style neighbor", + area->area_tag, id); + } else { + lsp_debug( + "ISIS (%s): Adding %pPN as mt-style neighbor for %s", + area->area_tag, id, isis_mtid2str(mtid)); + } + isis_tlvs_add_extended_reach(tlvs, mtid, id, metric, ext); + } +} + +void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit, + int level, uint8_t *id, uint32_t metric) +{ + unsigned int mt_count; + uint16_t *mt_set = circuit_bcast_mt_set(circuit, level, &mt_count); + + tlvs_add_mt_set(circuit->area, tlvs, mt_count, mt_set, id, metric, + circuit->ext); +} + +void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit, + uint8_t *id, uint32_t metric) +{ + struct isis_adjacency *adj = circuit->u.p2p.neighbor; + + tlvs_add_mt_set(circuit->area, tlvs, adj->mt_count, adj->mt_set, id, + metric, circuit->ext); +} + +void mt_init(void) +{ +#ifdef FABRICD + hook_register(isis_circuit_config_write, + circuit_write_mt_settings); +#endif +} diff --git a/isisd/isis_mt.h b/isisd/isis_mt.h new file mode 100644 index 0000000..9b0df9b --- /dev/null +++ b/isisd/isis_mt.h @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - Multi Topology Support + * + * Copyright (C) 2017 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#ifndef ISIS_MT_H +#define ISIS_MT_H + +#define ISIS_MT_MASK 0x0fff +#define ISIS_MT_OL_MASK 0x8000 +#define ISIS_MT_AT_MASK 0x4000 + +#define ISIS_MT_IPV4_UNICAST 0 +#define ISIS_MT_STANDARD ISIS_MT_IPV4_UNICAST +#define ISIS_MT_IPV4_MGMT 1 +#define ISIS_MT_IPV6_UNICAST 2 +#define ISIS_MT_IPV4_MULTICAST 3 +#define ISIS_MT_IPV6_MULTICAST 4 +#define ISIS_MT_IPV6_MGMT 5 +#define ISIS_MT_IPV6_DSTSRC 3996 /* FIXME: IANA */ +/* Use first Reserved Flag to indicate that there is no MT Topology active */ +#define ISIS_MT_DISABLE 4096 + +#define ISIS_MT_NAMES \ + "" + +#define ISIS_MT_DESCRIPTIONS \ + "IPv4 unicast topology\n" \ + "IPv4 management topology\n" \ + "IPv6 unicast topology\n" \ + "IPv4 multicast topology\n" \ + "IPv6 multicast topology\n" \ + "IPv6 management topology\n" \ + "IPv6 dst-src topology\n" \ + "" + +#define ISIS_MT_INFO_FIELDS uint16_t mtid; + +struct list; + +struct isis_area_mt_setting { + ISIS_MT_INFO_FIELDS + bool enabled; + bool overload; +}; + +struct isis_circuit_mt_setting { + ISIS_MT_INFO_FIELDS + bool enabled; +}; + +const char *isis_mtid2str(uint16_t mtid); +uint16_t isis_str2mtid(const char *name); + +struct isis_adjacency; +struct isis_area; +struct isis_circuit; +struct tlvs; +struct te_is_neigh; +struct isis_tlvs; + +bool isis_area_ipv6_dstsrc_enabled(struct isis_area *area); + +uint16_t isis_area_ipv6_topology(struct isis_area *area); + +struct isis_area_mt_setting *area_lookup_mt_setting(struct isis_area *area, + uint16_t mtid); +struct isis_area_mt_setting *area_new_mt_setting(struct isis_area *area, + uint16_t mtid); +void area_add_mt_setting(struct isis_area *area, + struct isis_area_mt_setting *setting); + +void area_mt_init(struct isis_area *area); +void area_mt_finish(struct isis_area *area); +struct isis_area_mt_setting *area_get_mt_setting(struct isis_area *area, + uint16_t mtid); +int area_write_mt_settings(struct isis_area *area, struct vty *vty); +bool area_is_mt(struct isis_area *area); +struct isis_area_mt_setting **area_mt_settings(struct isis_area *area, + unsigned int *mt_count); + +struct isis_circuit_mt_setting * +circuit_lookup_mt_setting(struct isis_circuit *circuit, uint16_t mtid); +struct isis_circuit_mt_setting * +circuit_new_mt_setting(struct isis_circuit *circuit, uint16_t mtid); +void circuit_add_mt_setting(struct isis_circuit *circuit, + struct isis_circuit_mt_setting *setting); +void circuit_mt_init(struct isis_circuit *circuit); +void circuit_mt_finish(struct isis_circuit *circuit); +struct isis_circuit_mt_setting * +circuit_get_mt_setting(struct isis_circuit *circuit, uint16_t mtid); +struct isis_circuit_mt_setting ** +circuit_mt_settings(struct isis_circuit *circuit, unsigned int *mt_count); +bool tlvs_to_adj_mt_set(struct isis_tlvs *tlvs, bool v4_usable, bool v6_usable, + struct isis_adjacency *adj); +bool adj_has_mt(struct isis_adjacency *adj, uint16_t mtid); +void adj_mt_finish(struct isis_adjacency *adj); +void tlvs_add_mt_bcast(struct isis_tlvs *tlvs, struct isis_circuit *circuit, + int level, uint8_t *id, uint32_t metric); +void tlvs_add_mt_p2p(struct isis_tlvs *tlvs, struct isis_circuit *circuit, + uint8_t *id, uint32_t metric); +void mt_init(void); +#endif diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c new file mode 100644 index 0000000..8608d2b --- /dev/null +++ b/isisd/isis_nb.c @@ -0,0 +1,1469 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#include + +#include "northbound.h" +#include "libfrr.h" + +#include "isisd/isis_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_isisd_info = { + .name = "frr-isisd", + .nodes = { + { + .xpath = "/frr-isisd:isis/instance", + .cbs = { + .cli_show = cli_show_router_isis, + .cli_show_end = cli_show_router_isis_end, + .create = isis_instance_create, + .destroy = isis_instance_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1, + }, + { + .xpath = "/frr-isisd:isis/instance/is-type", + .cbs = { + .cli_show = cli_show_isis_is_type, + .modify = isis_instance_is_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/area-address", + .cbs = { + .cli_show = cli_show_isis_area_address, + .create = isis_instance_area_address_create, + .destroy = isis_instance_area_address_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/dynamic-hostname", + .cbs = { + .cli_show = cli_show_isis_dynamic_hostname, + .modify = isis_instance_dynamic_hostname_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/attach-send", + .cbs = { + .cli_show = cli_show_isis_attached_send, + .modify = isis_instance_attached_send_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/attach-receive-ignore", + .cbs = { + .cli_show = cli_show_isis_attached_receive, + .modify = isis_instance_attached_receive_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/attached", + .cbs = { + .modify = isis_instance_attached_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/overload/enabled", + .cbs = { + .cli_show = cli_show_isis_overload, + .modify = isis_instance_overload_enabled_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/overload/on-startup", + .cbs = { + .cli_show = cli_show_isis_overload_on_startup, + .modify = isis_instance_overload_on_startup_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/advertise-high-metrics", + .cbs = { + .cli_show = cli_show_advertise_high_metrics, + .modify = isis_instance_advertise_high_metrics_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/metric-style", + .cbs = { + .cli_show = cli_show_isis_metric_style, + .modify = isis_instance_metric_style_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/purge-originator", + .cbs = { + .cli_show = cli_show_isis_purge_origin, + .modify = isis_instance_purge_originator_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/admin-group-send-zero", + .cbs = { + .cli_show = cli_show_isis_admin_group_send_zero, + .modify = isis_instance_admin_group_send_zero_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/asla-legacy-flag", + .cbs = { + .cli_show = cli_show_isis_asla_legacy_flag, + .modify = isis_instance_asla_legacy_flag_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/mtu", + .cbs = { + .cli_show = cli_show_isis_lsp_mtu, + .modify = isis_instance_lsp_mtu_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/advertise-passive-only", + .cbs = { + .cli_show = cli_show_advertise_passive_only, + .modify = isis_instance_advertise_passive_only_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers", + .cbs = { + .cli_show = cli_show_isis_lsp_timers, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval", + .cbs = { + .modify = isis_instance_lsp_refresh_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime", + .cbs = { + .modify = isis_instance_lsp_maximum_lifetime_level_1_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-1/generation-interval", + .cbs = { + .modify = isis_instance_lsp_generation_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval", + .cbs = { + .modify = isis_instance_lsp_refresh_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime", + .cbs = { + .modify = isis_instance_lsp_maximum_lifetime_level_2_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/lsp/timers/level-2/generation-interval", + .cbs = { + .modify = isis_instance_lsp_generation_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay", + .cbs = { + .apply_finish = ietf_backoff_delay_apply_finish, + .cli_show = cli_show_isis_spf_ietf_backoff, + .create = isis_instance_spf_ietf_backoff_delay_create, + .destroy = isis_instance_spf_ietf_backoff_delay_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay", + .cbs = { + .modify = isis_instance_spf_ietf_backoff_delay_init_delay_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay", + .cbs = { + .modify = isis_instance_spf_ietf_backoff_delay_short_delay_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay", + .cbs = { + .modify = isis_instance_spf_ietf_backoff_delay_long_delay_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down", + .cbs = { + .modify = isis_instance_spf_ietf_backoff_delay_hold_down_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn", + .cbs = { + .modify = isis_instance_spf_ietf_backoff_delay_time_to_learn_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/minimum-interval", + .cbs = { + .cli_show = cli_show_isis_spf_min_interval, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-1", + .cbs = { + .modify = isis_instance_spf_minimum_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/minimum-interval/level-2", + .cbs = { + .modify = isis_instance_spf_minimum_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/critical/access-list-name", + .cbs = { + .cli_show = cli_show_isis_spf_prefix_priority, + .modify = isis_instance_spf_prefix_priorities_critical_access_list_name_modify, + .destroy = isis_instance_spf_prefix_priorities_critical_access_list_name_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/high/access-list-name", + .cbs = { + .cli_show = cli_show_isis_spf_prefix_priority, + .modify = isis_instance_spf_prefix_priorities_high_access_list_name_modify, + .destroy = isis_instance_spf_prefix_priorities_high_access_list_name_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name", + .cbs = { + .cli_show = cli_show_isis_spf_prefix_priority, + .modify = isis_instance_spf_prefix_priorities_medium_access_list_name_modify, + .destroy = isis_instance_spf_prefix_priorities_medium_access_list_name_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/area-password", + .cbs = { + .apply_finish = area_password_apply_finish, + .cli_show = cli_show_isis_area_pwd, + .create = isis_instance_area_password_create, + .destroy = isis_instance_area_password_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/area-password/password", + .cbs = { + .modify = isis_instance_area_password_password_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/area-password/password-type", + .cbs = { + .modify = isis_instance_area_password_password_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/area-password/authenticate-snp", + .cbs = { + .modify = isis_instance_area_password_authenticate_snp_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/domain-password", + .cbs = { + .apply_finish = domain_password_apply_finish, + .cli_show = cli_show_isis_domain_pwd, + .create = isis_instance_domain_password_create, + .destroy = isis_instance_domain_password_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/domain-password/password", + .cbs = { + .modify = isis_instance_domain_password_password_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/domain-password/password-type", + .cbs = { + .modify = isis_instance_domain_password_password_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/domain-password/authenticate-snp", + .cbs = { + .modify = isis_instance_domain_password_authenticate_snp_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4", + .cbs = { + .apply_finish = default_info_origin_ipv4_apply_finish, + .cli_show = cli_show_isis_def_origin_ipv4, + .create = isis_instance_default_information_originate_ipv4_create, + .destroy = isis_instance_default_information_originate_ipv4_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/always", + .cbs = { + .modify = isis_instance_default_information_originate_ipv4_always_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/route-map", + .cbs = { + .destroy = isis_instance_default_information_originate_ipv4_route_map_destroy, + .modify = isis_instance_default_information_originate_ipv4_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv4/metric", + .cbs = { + .modify = isis_instance_default_information_originate_ipv4_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6", + .cbs = { + .apply_finish = default_info_origin_ipv6_apply_finish, + .cli_show = cli_show_isis_def_origin_ipv6, + .create = isis_instance_default_information_originate_ipv6_create, + .destroy = isis_instance_default_information_originate_ipv6_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/always", + .cbs = { + .modify = isis_instance_default_information_originate_ipv6_always_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/route-map", + .cbs = { + .destroy = isis_instance_default_information_originate_ipv6_route_map_destroy, + .modify = isis_instance_default_information_originate_ipv6_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/default-information-originate/ipv6/metric", + .cbs = { + .modify = isis_instance_default_information_originate_ipv6_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4", + .cbs = { + .apply_finish = redistribute_ipv4_apply_finish, + .cli_show = cli_show_isis_redistribute_ipv4, + .create = isis_instance_redistribute_ipv4_create, + .destroy = isis_instance_redistribute_ipv4_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/route-map", + .cbs = { + .destroy = isis_instance_redistribute_ipv4_route_map_destroy, + .modify = isis_instance_redistribute_ipv4_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/metric", + .cbs = { + .destroy = isis_instance_redistribute_ipv4_metric_destroy, + .modify = isis_instance_redistribute_ipv4_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table", + .cbs = { + .cli_show = cli_show_isis_redistribute_ipv4_table, + .cli_cmp = cli_cmp_isis_redistribute_table, + .create = isis_instance_redistribute_ipv4_table_create, + .destroy = isis_instance_redistribute_ipv4_table_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table/route-map", + .cbs = { + .destroy = isis_instance_redistribute_ipv4_route_map_destroy, + .modify = isis_instance_redistribute_ipv4_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv4/table/metric", + .cbs = { + .modify = isis_instance_redistribute_ipv4_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6", + .cbs = { + .apply_finish = redistribute_ipv6_apply_finish, + .cli_show = cli_show_isis_redistribute_ipv6, + .create = isis_instance_redistribute_ipv6_create, + .destroy = isis_instance_redistribute_ipv6_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/route-map", + .cbs = { + .destroy = isis_instance_redistribute_ipv6_route_map_destroy, + .modify = isis_instance_redistribute_ipv6_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/metric", + .cbs = { + .destroy = isis_instance_redistribute_ipv6_metric_destroy, + .modify = isis_instance_redistribute_ipv6_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table", + .cbs = { + .cli_show = cli_show_isis_redistribute_ipv6_table, + .cli_cmp = cli_cmp_isis_redistribute_table, + .create = isis_instance_redistribute_ipv6_table_create, + .destroy = isis_instance_redistribute_ipv6_table_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table/route-map", + .cbs = { + .destroy = isis_instance_redistribute_ipv6_route_map_destroy, + .modify = isis_instance_redistribute_ipv6_route_map_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/redistribute/ipv6/table/metric", + .cbs = { + .modify = isis_instance_redistribute_ipv6_metric_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast", + .cbs = { + .cli_show = cli_show_isis_mt_ipv4_multicast, + .create = isis_instance_multi_topology_ipv4_multicast_create, + .destroy = isis_instance_multi_topology_ipv4_multicast_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv4_multicast_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management", + .cbs = { + .cli_show = cli_show_isis_mt_ipv4_mgmt, + .create = isis_instance_multi_topology_ipv4_management_create, + .destroy = isis_instance_multi_topology_ipv4_management_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv4-management/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv4_management_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast", + .cbs = { + .cli_show = cli_show_isis_mt_ipv6_unicast, + .create = isis_instance_multi_topology_ipv6_unicast_create, + .destroy = isis_instance_multi_topology_ipv6_unicast_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv6_unicast_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast", + .cbs = { + .cli_show = cli_show_isis_mt_ipv6_multicast, + .create = isis_instance_multi_topology_ipv6_multicast_create, + .destroy = isis_instance_multi_topology_ipv6_multicast_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv6_multicast_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management", + .cbs = { + .cli_show = cli_show_isis_mt_ipv6_mgmt, + .create = isis_instance_multi_topology_ipv6_management_create, + .destroy = isis_instance_multi_topology_ipv6_management_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-management/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv6_management_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc", + .cbs = { + .cli_show = cli_show_isis_mt_ipv6_dstsrc, + .create = isis_instance_multi_topology_ipv6_dstsrc_create, + .destroy = isis_instance_multi_topology_ipv6_dstsrc_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload", + .cbs = { + .modify = isis_instance_multi_topology_ipv6_dstsrc_overload_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_load_sharing, + .modify = isis_instance_fast_reroute_level_1_lfa_load_sharing_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/priority-limit", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_priority_limit, + .modify = isis_instance_fast_reroute_level_1_lfa_priority_limit_modify, + .destroy = isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_tiebreaker, + .create = isis_instance_fast_reroute_level_1_lfa_tiebreaker_create, + .destroy = isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker/type", + .cbs = { + .modify = isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-1/remote-lfa/prefix-list", + .cbs = { + .cli_show = cli_show_isis_frr_remote_lfa_plist, + .modify = isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify, + .destroy = isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_load_sharing, + .modify = isis_instance_fast_reroute_level_2_lfa_load_sharing_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/priority-limit", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_priority_limit, + .modify = isis_instance_fast_reroute_level_2_lfa_priority_limit_modify, + .destroy = isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker", + .cbs = { + .cli_show = cli_show_isis_frr_lfa_tiebreaker, + .create = isis_instance_fast_reroute_level_2_lfa_tiebreaker_create, + .destroy = isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker/type", + .cbs = { + .modify = isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/fast-reroute/level-2/remote-lfa/prefix-list", + .cbs = { + .cli_show = cli_show_isis_frr_remote_lfa_plist, + .modify = isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify, + .destroy = isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy, + } + }, + { + .xpath = "/frr-isisd:isis/instance/log-adjacency-changes", + .cbs = { + .cli_show = cli_show_isis_log_adjacency, + .modify = isis_instance_log_adjacency_changes_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/log-pdu-drops", + .cbs = { + .cli_show = cli_show_isis_log_pdu_drops, + .modify = isis_instance_log_pdu_drops_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/mpls-te", + .cbs = { + .cli_show = cli_show_isis_mpls_te, + .create = isis_instance_mpls_te_create, + .destroy = isis_instance_mpls_te_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/mpls-te/router-address", + .cbs = { + .cli_show = cli_show_isis_mpls_te_router_addr, + .destroy = isis_instance_mpls_te_router_address_destroy, + .modify = isis_instance_mpls_te_router_address_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/mpls-te/router-address-v6", + .cbs = { + .cli_show = cli_show_isis_mpls_te_router_addr_ipv6, + .destroy = isis_instance_mpls_te_router_address_ipv6_destroy, + .modify = isis_instance_mpls_te_router_address_ipv6_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/mpls-te/export", + .cbs = { + .cli_show = cli_show_isis_mpls_te_export, + .modify = isis_instance_mpls_te_export_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/enabled", + .cbs = { + .modify = isis_instance_segment_routing_enabled_modify, + .cli_show = cli_show_isis_sr_enabled, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks", + .cbs = { + .pre_validate = isis_instance_segment_routing_label_blocks_pre_validate, + .cli_show = cli_show_isis_label_blocks, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb", + .cbs = { + .apply_finish = isis_instance_segment_routing_srgb_apply_finish, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb/lower-bound", + .cbs = { + .modify = isis_instance_segment_routing_srgb_lower_bound_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srgb/upper-bound", + .cbs = { + .modify = isis_instance_segment_routing_srgb_upper_bound_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb", + .cbs = { + .apply_finish = isis_instance_segment_routing_srlb_apply_finish, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb/lower-bound", + .cbs = { + .modify = isis_instance_segment_routing_srlb_lower_bound_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/label-blocks/srlb/upper-bound", + .cbs = { + .modify = isis_instance_segment_routing_srlb_upper_bound_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/msd/node-msd", + .cbs = { + .modify = isis_instance_segment_routing_msd_node_msd_modify, + .destroy = isis_instance_segment_routing_msd_node_msd_destroy, + .cli_show = cli_show_isis_node_msd, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid", + .cbs = { + .create = isis_instance_segment_routing_prefix_sid_map_prefix_sid_create, + .destroy = isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy, + .pre_validate = isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate, + .apply_finish = isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish, + .cli_show = cli_show_isis_prefix_sid, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value-type", + .cbs = { + .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value", + .cbs = { + .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/last-hop-behavior", + .cbs = { + .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/n-flag-clear", + .cbs = { + .modify = isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify, + } + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid", + .cbs = { + .create = isis_instance_segment_routing_algorithm_prefix_sid_create, + .destroy = isis_instance_segment_routing_algorithm_prefix_sid_destroy, + .pre_validate = isis_instance_segment_routing_algorithm_prefix_sid_pre_validate, + .apply_finish = isis_instance_segment_routing_algorithm_prefix_sid_apply_finish, + .cli_show = cli_show_isis_prefix_sid_algorithm, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value-type", + .cbs = { + .modify = isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value", + .cbs = { + .modify = isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/last-hop-behavior", + .cbs = { + .modify = isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/n-flag-clear", + .cbs = { + .modify = isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo", + .cbs = { + .cli_show = cli_show_isis_flex_algo, + .cli_show_end = cli_show_isis_flex_algo_end, + .create = isis_instance_flex_algo_create, + .destroy = isis_instance_flex_algo_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/advertise-definition", + .cbs = { + .modify = isis_instance_flex_algo_advertise_definition_modify, + .destroy = isis_instance_flex_algo_advertise_definition_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-alls/affinity-include-all", + .cbs = { + .create = isis_instance_flex_algo_affinity_include_all_create, + .destroy = isis_instance_flex_algo_affinity_include_all_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-anies/affinity-include-any", + .cbs = { + .create = isis_instance_flex_algo_affinity_include_any_create, + .destroy = isis_instance_flex_algo_affinity_include_any_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/affinity-exclude-anies/affinity-exclude-any", + .cbs = { + .create = isis_instance_flex_algo_affinity_exclude_any_create, + .destroy = isis_instance_flex_algo_affinity_exclude_any_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/prefix-metric", + .cbs = { + .create = isis_instance_flex_algo_prefix_metric_create, + .destroy = isis_instance_flex_algo_prefix_metric_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/metric-type", + .cbs = { + .modify = isis_instance_flex_algo_metric_type_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-sr-mpls", + .cbs = { + .create = isis_instance_flex_algo_dplane_sr_mpls_create, + .destroy = isis_instance_flex_algo_dplane_sr_mpls_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-srv6", + .cbs = { + .create = isis_instance_flex_algo_dplane_srv6_create, + .destroy = isis_instance_flex_algo_dplane_srv6_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/dplane-ip", + .cbs = { + .create = isis_instance_flex_algo_dplane_ip_create, + .destroy = isis_instance_flex_algo_dplane_ip_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/flex-algos/flex-algo/priority", + .cbs = { + .modify = isis_instance_flex_algo_priority_modify, + .destroy = isis_instance_flex_algo_priority_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/enabled", + .cbs = { + .modify = isis_instance_segment_routing_srv6_enabled_modify, + .cli_show = cli_show_isis_srv6_enabled, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/locator", + .cbs = { + .modify = isis_instance_segment_routing_srv6_locator_modify, + .destroy = isis_instance_segment_routing_srv6_locator_destroy, + .cli_show = cli_show_isis_srv6_locator, + .cli_show_end = cli_show_isis_srv6_locator_end, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left", + .cbs = { + .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop", + .cbs = { + .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps", + .cbs = { + .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d", + .cbs = { + .modify = isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd", + .cbs = { + .cli_show = cli_show_isis_srv6_node_msd, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/segment-routing-srv6/interface", + .cbs = { + .modify = isis_instance_segment_routing_srv6_interface_modify, + .cli_show = cli_show_isis_srv6_interface, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/mpls/ldp-sync", + .cbs = { + .cli_show = cli_show_isis_mpls_ldp_sync, + .create = isis_instance_mpls_ldp_sync_create, + .destroy = isis_instance_mpls_ldp_sync_destroy, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/mpls/ldp-sync/holddown", + .cbs = { + .cli_show = cli_show_isis_mpls_ldp_sync_holddown, + .modify = isis_instance_mpls_ldp_sync_holddown_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis", + .cbs = { + .create = lib_interface_isis_create, + .destroy = lib_interface_isis_destroy, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/area-tag", + .cbs = { + .modify = lib_interface_isis_area_tag_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/circuit-type", + .cbs = { + .cli_show = cli_show_ip_isis_circ_type, + .modify = lib_interface_isis_circuit_type_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv4-routing", + .cbs = { + .cli_show = cli_show_ip_isis_ipv4, + .modify = lib_interface_isis_ipv4_routing_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/ipv6-routing", + .cbs = { + .cli_show = cli_show_ip_isis_ipv6, + .modify = lib_interface_isis_ipv6_routing_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring", + .cbs = { + .apply_finish = lib_interface_isis_bfd_monitoring_apply_finish, + .cli_show = cli_show_ip_isis_bfd_monitoring, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/enabled", + .cbs = { + .modify = lib_interface_isis_bfd_monitoring_enabled_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile", + .cbs = { + .modify = lib_interface_isis_bfd_monitoring_profile_modify, + .destroy = lib_interface_isis_bfd_monitoring_profile_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval", + .cbs = { + .cli_show = cli_show_ip_isis_csnp_interval, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1", + .cbs = { + .modify = lib_interface_isis_csnp_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2", + .cbs = { + .modify = lib_interface_isis_csnp_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval", + .cbs = { + .cli_show = cli_show_ip_isis_psnp_interval, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1", + .cbs = { + .modify = lib_interface_isis_psnp_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2", + .cbs = { + .modify = lib_interface_isis_psnp_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/padding", + .cbs = { + .cli_show = cli_show_ip_isis_hello_padding, + .modify = lib_interface_isis_hello_padding_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval", + .cbs = { + .cli_show = cli_show_ip_isis_hello_interval, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1", + .cbs = { + .modify = lib_interface_isis_hello_interval_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2", + .cbs = { + .modify = lib_interface_isis_hello_interval_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier", + .cbs = { + .cli_show = cli_show_ip_isis_hello_multi, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1", + .cbs = { + .modify = lib_interface_isis_hello_multiplier_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2", + .cbs = { + .modify = lib_interface_isis_hello_multiplier_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric", + .cbs = { + .cli_show = cli_show_ip_isis_metric, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-1", + .cbs = { + .modify = lib_interface_isis_metric_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/metric/level-2", + .cbs = { + .modify = lib_interface_isis_metric_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority", + .cbs = { + .cli_show = cli_show_ip_isis_priority, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-1", + .cbs = { + .modify = lib_interface_isis_priority_level_1_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/priority/level-2", + .cbs = { + .modify = lib_interface_isis_priority_level_2_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/network-type", + .cbs = { + .cli_show = cli_show_ip_isis_network_type, + .modify = lib_interface_isis_network_type_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/passive", + .cbs = { + .cli_show = cli_show_ip_isis_passive, + .modify = lib_interface_isis_passive_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password", + .cbs = { + .cli_show = cli_show_ip_isis_password, + .create = lib_interface_isis_password_create, + .destroy = lib_interface_isis_password_destroy, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password", + .cbs = { + .modify = lib_interface_isis_password_password_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/password/password-type", + .cbs = { + .modify = lib_interface_isis_password_password_type_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake", + .cbs = { + .cli_show = cli_show_ip_isis_threeway_shake, + .modify = lib_interface_isis_disable_three_way_handshake_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/standard", + .cbs = { + .cli_show = cli_show_ip_isis_mt_standard, + .modify = lib_interface_isis_multi_topology_standard_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv4_multicast, + .modify = lib_interface_isis_multi_topology_ipv4_multicast_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv4_mgmt, + .modify = lib_interface_isis_multi_topology_ipv4_management_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv6_unicast, + .modify = lib_interface_isis_multi_topology_ipv6_unicast_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv6_multicast, + .modify = lib_interface_isis_multi_topology_ipv6_multicast_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv6_mgmt, + .modify = lib_interface_isis_multi_topology_ipv6_management_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc", + .cbs = { + .cli_show = cli_show_ip_isis_mt_ipv6_dstsrc, + .modify = lib_interface_isis_multi_topology_ipv6_dstsrc_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute", + .cbs = { + .cli_show = cli_show_ip_isis_frr, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_1_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface", + .cbs = { + .cli_show = cli_show_frr_lfa_exclude_interface, + .create = lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create, + .destroy = lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric", + .cbs = { + .cli_show = cli_show_frr_remote_lfa_max_metric, + .modify = lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify, + .destroy = lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_2_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface", + .cbs = { + .cli_show = cli_show_frr_lfa_exclude_interface, + .create = lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create, + .destroy = lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric", + .cbs = { + .cli_show = cli_show_frr_remote_lfa_max_metric, + .modify = lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify, + .destroy = lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback", + .cbs = { + .modify = lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis", + .cbs = { + .get_elem = lib_interface_state_isis_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency", + .cbs = { + .get_next = lib_interface_state_isis_adjacencies_adjacency_get_next, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sys-type", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sysid", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-extended-circuit-id", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-snpa", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/hold-timer", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-priority", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/state", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_state_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid", + .cbs = { + .get_next = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/af", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/value", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/weight", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/protection-requested", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid", + .cbs = { + .get_next = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/af", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/value", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/weight", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/protection-requested", + .cbs = { + .get_elem = lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-changes", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_adjacency_changes_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-number", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_adjacency_number_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/init-fails", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_init_fails_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-rejects", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_adjacency_rejects_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/id-len-mismatch", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_id_len_mismatch_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/max-area-addresses-mismatch", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-type-fails", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_authentication_type_fails_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-fails", + .cbs = { + .get_elem = lib_interface_state_isis_event_counters_authentication_fails_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync", + .cbs = { + .cli_show = cli_show_isis_mpls_if_ldp_sync, + .modify = lib_interface_isis_mpls_ldp_sync_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-isisd:isis/mpls/holddown", + .cbs = { + .cli_show = cli_show_isis_mpls_if_ldp_sync_holddown, + .modify = lib_interface_isis_mpls_holddown_modify, + .destroy = lib_interface_isis_mpls_holddown_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h new file mode 100644 index 0000000..1bf95e3 --- /dev/null +++ b/isisd/isis_nb.h @@ -0,0 +1,843 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#ifndef ISISD_ISIS_NB_H_ +#define ISISD_ISIS_NB_H_ + +extern const struct frr_yang_module_info frr_isisd_info; + +/* Forward declaration(s). */ +struct isis_area; +struct isis_circuit; +struct isis_adjacency; + +/* Mandatory callbacks. */ +int isis_instance_create(struct nb_cb_create_args *args); +int isis_instance_destroy(struct nb_cb_destroy_args *args); +int isis_instance_is_type_modify(struct nb_cb_modify_args *args); +int isis_instance_area_address_create(struct nb_cb_create_args *args); +int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args); +int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args); +int isis_instance_attached_send_modify(struct nb_cb_modify_args *args); +int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args); +int isis_instance_attached_modify(struct nb_cb_modify_args *args); +int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args); +int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args); +int isis_instance_advertise_high_metrics_modify(struct nb_cb_modify_args *args); +int isis_instance_metric_style_modify(struct nb_cb_modify_args *args); +int isis_instance_purge_originator_modify(struct nb_cb_modify_args *args); +int isis_instance_admin_group_send_zero_modify(struct nb_cb_modify_args *args); +int isis_instance_asla_legacy_flag_modify(struct nb_cb_modify_args *args); +int isis_instance_lsp_mtu_modify(struct nb_cb_modify_args *args); +int isis_instance_advertise_passive_only_modify(struct nb_cb_modify_args *args); +int isis_instance_lsp_refresh_interval_level_1_modify( + struct nb_cb_modify_args *args); +int isis_instance_lsp_refresh_interval_level_2_modify( + struct nb_cb_modify_args *args); +int isis_instance_lsp_maximum_lifetime_level_1_modify( + struct nb_cb_modify_args *args); +int isis_instance_lsp_maximum_lifetime_level_2_modify( + struct nb_cb_modify_args *args); +int isis_instance_lsp_generation_interval_level_1_modify( + struct nb_cb_modify_args *args); +int isis_instance_lsp_generation_interval_level_2_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_ietf_backoff_delay_create(struct nb_cb_create_args *args); +int isis_instance_spf_ietf_backoff_delay_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_spf_ietf_backoff_delay_init_delay_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_ietf_backoff_delay_short_delay_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_ietf_backoff_delay_long_delay_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_ietf_backoff_delay_hold_down_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_ietf_backoff_delay_time_to_learn_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_minimum_interval_level_1_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_minimum_interval_level_2_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_prefix_priorities_critical_access_list_name_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_prefix_priorities_critical_access_list_name_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_spf_prefix_priorities_high_access_list_name_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_prefix_priorities_high_access_list_name_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_spf_prefix_priorities_medium_access_list_name_modify( + struct nb_cb_modify_args *args); +int isis_instance_spf_prefix_priorities_medium_access_list_name_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_area_password_create(struct nb_cb_create_args *args); +int isis_instance_area_password_destroy(struct nb_cb_destroy_args *args); +int isis_instance_area_password_password_modify(struct nb_cb_modify_args *args); +int isis_instance_area_password_password_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_area_password_authenticate_snp_modify( + struct nb_cb_modify_args *args); +int isis_instance_domain_password_create(struct nb_cb_create_args *args); +int isis_instance_domain_password_destroy(struct nb_cb_destroy_args *args); +int isis_instance_domain_password_password_modify( + struct nb_cb_modify_args *args); +int isis_instance_domain_password_password_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_domain_password_authenticate_snp_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv4_create( + struct nb_cb_create_args *args); +int isis_instance_default_information_originate_ipv4_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_default_information_originate_ipv4_always_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv4_route_map_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv4_route_map_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_default_information_originate_ipv4_metric_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv6_create( + struct nb_cb_create_args *args); +int isis_instance_default_information_originate_ipv6_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_default_information_originate_ipv6_always_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv6_route_map_modify( + struct nb_cb_modify_args *args); +int isis_instance_default_information_originate_ipv6_route_map_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_default_information_originate_ipv6_metric_modify( + struct nb_cb_modify_args *args); +int isis_instance_redistribute_ipv4_create(struct nb_cb_create_args *args); +int isis_instance_redistribute_ipv4_destroy(struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv4_route_map_modify( + struct nb_cb_modify_args *args); +int isis_instance_redistribute_ipv4_route_map_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv4_metric_modify( + struct nb_cb_modify_args *args); +int isis_instance_redistribute_ipv4_metric_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv4_table_create(struct nb_cb_create_args *args); +int isis_instance_redistribute_ipv4_table_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv6_create(struct nb_cb_create_args *args); +int isis_instance_redistribute_ipv6_destroy(struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv6_route_map_modify( + struct nb_cb_modify_args *args); +int isis_instance_redistribute_ipv6_route_map_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv6_metric_modify( + struct nb_cb_modify_args *args); +int isis_instance_redistribute_ipv6_metric_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_redistribute_ipv6_table_create(struct nb_cb_create_args *args); +int isis_instance_redistribute_ipv6_table_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv4_multicast_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv4_multicast_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv4_multicast_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_multi_topology_ipv4_management_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv4_management_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv4_management_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_multi_topology_ipv6_unicast_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv6_unicast_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv6_unicast_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_multi_topology_ipv6_multicast_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv6_multicast_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv6_multicast_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_multi_topology_ipv6_management_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv6_management_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv6_management_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_multi_topology_ipv6_dstsrc_create( + struct nb_cb_create_args *args); +int isis_instance_multi_topology_ipv6_dstsrc_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_multi_topology_ipv6_dstsrc_overload_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_1_lfa_load_sharing_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_1_lfa_priority_limit_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_create( + struct nb_cb_create_args *args); +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_fast_reroute_level_2_lfa_load_sharing_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_2_lfa_priority_limit_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_create( + struct nb_cb_create_args *args); +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify( + struct nb_cb_modify_args *args); +int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_log_adjacency_changes_modify(struct nb_cb_modify_args *args); +int isis_instance_log_pdu_drops_modify(struct nb_cb_modify_args *args); +int isis_instance_mpls_te_create(struct nb_cb_create_args *args); +int isis_instance_mpls_te_destroy(struct nb_cb_destroy_args *args); +int isis_instance_mpls_te_router_address_modify(struct nb_cb_modify_args *args); +int isis_instance_mpls_te_router_address_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_mpls_te_router_address_ipv6_modify( + struct nb_cb_modify_args *args); +int isis_instance_mpls_te_router_address_ipv6_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_mpls_te_export_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_create(struct nb_cb_create_args *args); +int lib_interface_isis_destroy(struct nb_cb_destroy_args *args); +int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_ipv4_routing_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_ipv6_routing_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_circuit_type_modify(struct nb_cb_modify_args *args); +void lib_interface_isis_bfd_monitoring_apply_finish( + struct nb_cb_apply_finish_args *args); +int lib_interface_isis_bfd_monitoring_enabled_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_bfd_monitoring_profile_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_bfd_monitoring_profile_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_segment_routing_enabled_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_enabled_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srgb_lower_bound_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srgb_upper_bound_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srlb_lower_bound_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srlb_upper_bound_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_msd_node_msd_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_msd_node_msd_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create( + struct nb_cb_create_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_create( + struct nb_cb_create_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args); +void isis_instance_segment_routing_algorithm_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify( + struct nb_cb_modify_args *args); +int isis_instance_flex_algo_create(struct nb_cb_create_args *args); +int isis_instance_flex_algo_destroy(struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_advertise_definition_modify( + struct nb_cb_modify_args *args); +int isis_instance_flex_algo_advertise_definition_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_affinity_include_any_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_affinity_include_any_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_affinity_include_all_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_affinity_include_all_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_affinity_exclude_any_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_affinity_exclude_any_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_prefix_metric_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_prefix_metric_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_dplane_sr_mpls_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_dplane_sr_mpls_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_dplane_srv6_create(struct nb_cb_create_args *args); +int isis_instance_flex_algo_dplane_srv6_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_dplane_ip_create(struct nb_cb_create_args *args); +int isis_instance_flex_algo_dplane_ip_destroy(struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_metric_type_modify(struct nb_cb_modify_args *args); +int isis_instance_flex_algo_priority_modify(struct nb_cb_modify_args *args); +int isis_instance_flex_algo_priority_destroy(struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_frr_disable_modify(struct nb_cb_modify_args *args); +int isis_instance_flex_algo_frr_disable_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_affinity_mapping_create( + struct nb_cb_create_args *args); +int isis_instance_flex_algo_affinity_mapping_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_flex_algo_affinity_mapping_value_modify( + struct nb_cb_modify_args *args); +int isis_instance_flex_algo_affinity_mapping_value_destroy( + struct nb_cb_destroy_args *args); +int isis_instance_segment_routing_srv6_enabled_modify( + struct nb_cb_modify_args *args); +void cli_show_isis_srv6_enabled(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +int isis_instance_segment_routing_srv6_locator_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srv6_locator_destroy( + struct nb_cb_destroy_args *args); +void cli_show_isis_srv6_locator(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_srv6_locator_end(struct vty *vty, + const struct lyd_node *dnode); +int isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify( + struct nb_cb_modify_args *args); +int isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify( + struct nb_cb_modify_args *args); +void cli_show_isis_srv6_node_msd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +int isis_instance_segment_routing_srv6_interface_modify( + struct nb_cb_modify_args *args); +void cli_show_isis_srv6_interface(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +int isis_instance_mpls_ldp_sync_destroy(struct nb_cb_destroy_args *args); +int isis_instance_mpls_ldp_sync_create(struct nb_cb_create_args *args); +int isis_instance_mpls_ldp_sync_holddown_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_csnp_interval_level_1_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_csnp_interval_level_2_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_psnp_interval_level_1_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_psnp_interval_level_2_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_hello_padding_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_hello_interval_level_1_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_hello_interval_level_2_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_hello_multiplier_level_1_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_hello_multiplier_level_2_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_metric_level_1_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_metric_level_2_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_priority_level_1_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_priority_level_2_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_network_type_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_passive_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_password_create(struct nb_cb_create_args *args); +int lib_interface_isis_password_destroy(struct nb_cb_destroy_args *args); +int lib_interface_isis_password_password_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_password_password_type_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_disable_three_way_handshake_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_standard_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv4_multicast_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv4_management_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv6_unicast_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv6_multicast_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv6_management_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_multi_topology_ipv6_dstsrc_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_mpls_ldp_sync_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_mpls_holddown_modify(struct nb_cb_modify_args *args); +int lib_interface_isis_mpls_holddown_destroy(struct nb_cb_destroy_args *args); +int lib_interface_isis_fast_reroute_level_1_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create( + struct nb_cb_create_args *args); +int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create( + struct nb_cb_create_args *args); +int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify( + struct nb_cb_modify_args *args); +int lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify( + struct nb_cb_modify_args *args); +struct yang_data * +lib_interface_state_isis_get_elem(struct nb_cb_get_elem_args *args); +const void *lib_interface_state_isis_adjacencies_adjacency_get_next( + struct nb_cb_get_next_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *lib_interface_state_isis_adjacencies_adjacency_state_get_elem( + struct nb_cb_get_elem_args *args); +const void * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next( + struct nb_cb_get_next_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem( + struct nb_cb_get_elem_args *args); +const void * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next( + struct nb_cb_get_next_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_changes_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_number_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *lib_interface_state_isis_event_counters_init_fails_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_rejects_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_id_len_mismatch_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_authentication_type_fails_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_interface_state_isis_event_counters_authentication_fails_get_elem( + struct nb_cb_get_elem_args *args); + +/* Optional 'pre_validate' callbacks. */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args); +int isis_instance_segment_routing_label_blocks_pre_validate( + struct nb_cb_pre_validate_args *args); + +/* Optional 'apply_finish' callbacks. */ +void ietf_backoff_delay_apply_finish(struct nb_cb_apply_finish_args *args); +void area_password_apply_finish(struct nb_cb_apply_finish_args *args); +void domain_password_apply_finish(struct nb_cb_apply_finish_args *args); +void default_info_origin_apply_finish(const struct lyd_node *dnode, int family); +void default_info_origin_ipv4_apply_finish( + struct nb_cb_apply_finish_args *args); +void default_info_origin_ipv6_apply_finish( + struct nb_cb_apply_finish_args *args); +void redistribute_apply_finish(const struct lyd_node *dnode, int family); +void redistribute_ipv4_apply_finish(struct nb_cb_apply_finish_args *args); +void redistribute_ipv6_apply_finish(struct nb_cb_apply_finish_args *args); +void isis_instance_segment_routing_srgb_apply_finish( + struct nb_cb_apply_finish_args *args); +void isis_instance_segment_routing_srlb_apply_finish( + struct nb_cb_apply_finish_args *args); +void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_router_isis(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_router_isis_end(struct vty *vty, const struct lyd_node *dnode); +void cli_show_ip_isis_ipv4(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_ipv6(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_bfd_monitoring(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_area_address(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_is_type(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_dynamic_hostname(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_attached_send(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_attached_receive(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_overload(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_overload_on_startup(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_advertise_high_metrics(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_metric_style(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_area_pwd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_domain_pwd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_lsp_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_lsp_mtu(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_advertise_passive_only(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_spf_min_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_spf_ietf_backoff(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_spf_prefix_priority(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_purge_origin(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_te(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_admin_group_send_zero(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_asla_legacy_flag(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_te_router_addr(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_te_router_addr_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_te_export(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_def_origin_ipv4(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_def_origin_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_redistribute_ipv4(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_redistribute_ipv6(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv4_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_redistribute_ipv4_table(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_redistribute_ipv6_table(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv4_mgmt(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv6_unicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv6_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv6_mgmt(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mt_ipv6_dstsrc(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_sr_enabled(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_label_blocks(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_node_msd(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_prefix_sid(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_prefix_sid_algorithm(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_frr_lfa_priority_limit(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_frr_lfa_tiebreaker(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_frr_lfa_load_sharing(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_frr_remote_lfa_plist(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_passive(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_password(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_metric(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_hello_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_hello_multi(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_threeway_shake(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_hello_padding(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_csnp_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_psnp_interval(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_standard(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv4_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv4_mgmt(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv6_unicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv6_multicast(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv6_mgmt(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_mt_ipv6_dstsrc(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_frr(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_frr_lfa_exclude_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_frr_remote_lfa_max_metric(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_circ_type(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_network_type(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_isis_priority(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_log_adjacency(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_log_pdu_drops(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_ldp_sync(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_ldp_sync_holddown(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_if_ldp_sync(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_mpls_if_ldp_sync_holddown(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_flex_algo(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_flex_algo_end(struct vty *vty, const struct lyd_node *dnode); + +/* Notifications. */ +void isis_notif_db_overload(const struct isis_area *area, bool overload); +void isis_notif_lsp_too_large(const struct isis_circuit *circuit, + uint32_t pdu_size, const uint8_t *lsp_id); +void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down); +void isis_notif_corrupted_lsp(const struct isis_area *area, + const uint8_t *lsp_id); /* currently unused */ +void isis_notif_lsp_exceed_max(const struct isis_area *area, + const uint8_t *lsp_id); +void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit, + uint8_t max_area_addrs, + const char *raw_pdu, size_t raw_pdu_len); +void isis_notif_authentication_type_failure(const struct isis_circuit *circuit, + const char *raw_pdu, + size_t raw_pdu_len); +void isis_notif_authentication_failure(const struct isis_circuit *circuit, + const char *raw_pdu, size_t raw_pdu_len); +void isis_notif_adj_state_change(const struct isis_adjacency *adj, + int new_state, const char *reason); +void isis_notif_reject_adjacency(const struct isis_circuit *circuit, + const char *reason, const char *raw_pdu, + size_t raw_pdu_len); +void isis_notif_area_mismatch(const struct isis_circuit *circuit, + const char *raw_pdu, size_t raw_pdu_len); +void isis_notif_lsp_received(const struct isis_circuit *circuit, + const uint8_t *lsp_id, uint32_t seqno, + uint32_t timestamp, const char *sys_id); +void isis_notif_lsp_gen(const struct isis_area *area, const uint8_t *lsp_id, + uint32_t seqno, uint32_t timestamp); +void isis_notif_id_len_mismatch(const struct isis_circuit *circuit, + uint8_t rcv_id_len, const char *raw_pdu, + size_t raw_pdu_len); +void isis_notif_version_skew(const struct isis_circuit *circuit, + uint8_t version, const char *raw_pdu, + size_t raw_pdu_len); +void isis_notif_lsp_error(const struct isis_circuit *circuit, + const uint8_t *lsp_id, const char *raw_pdu, + size_t raw_pdu_len, uint32_t offset, + uint8_t tlv_type); +void isis_notif_seqno_skipped(const struct isis_circuit *circuit, + const uint8_t *lsp_id); +void isis_notif_own_lsp_purge(const struct isis_circuit *circuit, + const uint8_t *lsp_id); +/* cmp */ +int cli_cmp_isis_redistribute_table(const struct lyd_node *dnode1, + const struct lyd_node *dnode2); + +/* We also declare hook for every notification */ + +DECLARE_HOOK(isis_hook_db_overload, (const struct isis_area *area), (area)); +DECLARE_HOOK(isis_hook_lsp_too_large, + (const struct isis_circuit *circuit, uint32_t pdu_size, + const uint8_t *lsp_id), + (circuit, pdu_size, lsp_id)); +/* Note: no isis_hook_corrupted_lsp - because this notificaiton is not used */ +DECLARE_HOOK(isis_hook_lsp_exceed_max, + (const struct isis_area *area, const uint8_t *lsp_id), + (area, lsp_id)); +DECLARE_HOOK(isis_hook_max_area_addr_mismatch, + (const struct isis_circuit *circuit, uint8_t max_addrs, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, max_addrs, raw_pdu, raw_pdu_len)); +DECLARE_HOOK(isis_hook_authentication_type_failure, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DECLARE_HOOK(isis_hook_authentication_failure, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DECLARE_HOOK(isis_hook_adj_state_change, (const struct isis_adjacency *adj), + (adj)); +DECLARE_HOOK(isis_hook_reject_adjacency, + (const struct isis_circuit *circuit, const char *pdu, + size_t pdu_len), + (circuit, pdu, pdu_len)); +DECLARE_HOOK(isis_hook_area_mismatch, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit)); +DECLARE_HOOK(isis_hook_id_len_mismatch, + (const struct isis_circuit *circuit, uint8_t rcv_id_len, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, rcv_id_len, raw_pdu, raw_pdu_len)); +DECLARE_HOOK(isis_hook_version_skew, + (const struct isis_circuit *circuit, uint8_t version, + const char *raw_pdu, size_t raw_pdu_len), + (circuit)); +DECLARE_HOOK(isis_hook_lsp_error, + (const struct isis_circuit *circuit, const uint8_t *lsp_id, + const char *raw_pdu, size_t raw_pdu_len), + (circuit)); +DECLARE_HOOK(isis_hook_seqno_skipped, + (const struct isis_circuit *circuit, const uint8_t *lsp_id), + (circuit, lsp_id)); +DECLARE_HOOK(isis_hook_own_lsp_purge, + (const struct isis_circuit *circuit, const uint8_t *lsp_id), + (circuit, lsp_id)); + +#endif /* ISISD_ISIS_NB_H_ */ diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c new file mode 100644 index 0000000..763b8b7 --- /dev/null +++ b/isisd/isis_nb_config.c @@ -0,0 +1,4948 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#include + +#include "printfrr.h" +#include "northbound.h" +#include "linklist.h" +#include "log.h" +#include "bfd.h" +#include "filter.h" +#include "plist.h" +#include "spf_backoff.h" +#include "lib_errors.h" +#include "vrf.h" +#include "ldp_sync.h" +#include "link_state.h" +#include "affinitymap.h" + +#include "isisd/isisd.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_common.h" +#include "isisd/isis_bfd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_srv6.h" +#include "isisd/isis_te.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_redist.h" +#include "isisd/isis_ldp_sync.h" +#include "isisd/isis_dr.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_flex_algo.h" +#include "isisd/isis_zebra.h" + +#define AFFINITY_INCLUDE_ANY 0 +#define AFFINITY_INCLUDE_ALL 1 +#define AFFINITY_EXCLUDE_ANY 2 + +/* + * XPath: /frr-isisd:isis/instance + */ +int isis_instance_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + const char *area_tag; + const char *vrf_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + vrf_name = yang_dnode_get_string(args->dnode, "vrf"); + area_tag = yang_dnode_get_string(args->dnode, "area-tag"); + + area = isis_area_lookup_by_vrf(area_tag, vrf_name); + if (area) + return NB_ERR_INCONSISTENCY; + + area = isis_area_create(area_tag, vrf_name); + + /* save area in dnode to avoid looking it up all the time */ + nb_running_set_entry(args->dnode, area); + + return NB_OK; +} + +int isis_instance_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct isis *isis; + + if (args->event != NB_EV_APPLY) + return NB_OK; + area = nb_running_unset_entry(args->dnode); + isis = area->isis; + isis_area_destroy(area); + + if (listcount(isis->area_list) == 0) + isis_finish(isis); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/is-type + */ +int isis_instance_is_type_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, NULL); + isis_area_is_type_set(area, type); + + return NB_OK; +} + +struct sysid_iter { + struct iso_address *addr; + bool same; +}; + +static int sysid_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct sysid_iter *iter = arg; + struct iso_address addr; + const char *net; + + net = yang_dnode_get_string(dnode, NULL); + addr.addr_len = dotformat2buff(addr.area_addr, net); + + if (memcmp(GETSYSID(iter->addr), GETSYSID((&addr)), ISIS_SYS_ID_LEN)) { + iter->same = false; + return YANG_ITER_STOP; + } + + return YANG_ITER_CONTINUE; +} + +/* + * XPath: /frr-isisd:isis/instance/area-address + */ +int isis_instance_area_address_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct iso_address addr, *addrr = NULL, *addrp = NULL; + struct listnode *node; + struct sysid_iter iter; + uint8_t buff[255]; + const char *net_title = yang_dnode_get_string(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + addr.addr_len = dotformat2buff(buff, net_title); + memcpy(addr.area_addr, buff, addr.addr_len); + if (addr.area_addr[addr.addr_len - 1] != 0) { + snprintf( + args->errmsg, args->errmsg_len, + "nsel byte (last byte) in area address must be 0"); + return NB_ERR_VALIDATION; + } + + iter.addr = &addr; + iter.same = true; + + yang_dnode_iterate(sysid_iter_cb, &iter, args->dnode, + "../area-address"); + + if (!iter.same) { + snprintf( + args->errmsg, args->errmsg_len, + "System ID must not change when defining additional area addresses"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + addrr = XMALLOC(MTYPE_ISIS_AREA_ADDR, + sizeof(struct iso_address)); + addrr->addr_len = dotformat2buff(buff, net_title); + memcpy(addrr->area_addr, buff, addrr->addr_len); + args->resource->ptr = addrr; + break; + case NB_EV_ABORT: + XFREE(MTYPE_ISIS_AREA_ADDR, args->resource->ptr); + break; + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + addrr = args->resource->ptr; + assert(area); + + if (area->isis->sysid_set == 0) { + /* + * First area address - get the SystemID for this router + */ + memcpy(area->isis->sysid, GETSYSID(addrr), + ISIS_SYS_ID_LEN); + area->isis->sysid_set = 1; + } else { + /* check that we don't already have this address */ + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, + addrp)) { + if ((addrp->addr_len + ISIS_SYS_ID_LEN + + ISIS_NSEL_LEN) + != (addrr->addr_len)) + continue; + if (!memcmp(addrp->area_addr, addrr->area_addr, + addrr->addr_len)) { + XFREE(MTYPE_ISIS_AREA_ADDR, addrr); + return NB_OK; /* silent fail */ + } + } + } + + /*Forget the systemID part of the address */ + addrr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); + assert(area->area_addrs); /* to silence scan-build sillyness */ + listnode_add(area->area_addrs, addrr); + + /* only now we can safely generate our LSPs for this area */ + if (listcount(area->area_addrs) > 0) { + if (area->is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (area->is_type & IS_LEVEL_2) + lsp_generate(area, IS_LEVEL_2); + } + break; + } + + return NB_OK; +} + +int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args) +{ + struct iso_address addr, *addrp = NULL; + struct listnode *node; + uint8_t buff[255]; + struct isis_area *area; + const char *net_title; + struct listnode *cnode; + struct isis_circuit *circuit; + int lvl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + net_title = yang_dnode_get_string(args->dnode, NULL); + addr.addr_len = dotformat2buff(buff, net_title); + memcpy(addr.area_addr, buff, (int)addr.addr_len); + area = nb_running_get_entry(args->dnode, NULL, true); + + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { + if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len + && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) + break; + } + if (!addrp) + return NB_ERR_INCONSISTENCY; + + listnode_delete(area->area_addrs, addrp); + XFREE(MTYPE_ISIS_AREA_ADDR, addrp); + /* + * Last area address - reset the SystemID for this router + */ + if (listcount(area->area_addrs) == 0) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (circuit->u.bc.is_dr[lvl - 1]) + isis_dr_resign(circuit, lvl); + } + memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); + area->isis->sysid_set = 0; + if (IS_DEBUG_EVENTS) + zlog_debug("Router has no SystemID"); + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/dynamic-hostname + */ +int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_dynhostname_set(area, yang_dnode_get_bool(args->dnode, NULL)); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/attach-send + */ +int isis_instance_attached_send_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool attached; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + attached = yang_dnode_get_bool(args->dnode, NULL); + isis_area_attached_bit_send_set(area, attached); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/attach-receive-ignore + */ +int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool attached; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + attached = yang_dnode_get_bool(args->dnode, NULL); + isis_area_attached_bit_receive_set(area, attached); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/attached + */ +int isis_instance_attached_modify(struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/overload/enabled + */ +int isis_instance_overload_enabled_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool overload; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + overload = yang_dnode_get_bool(args->dnode, NULL); + area->overload_configured = overload; + + isis_area_overload_bit_set(area, overload); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/overload/on-startup + */ +int isis_instance_overload_on_startup_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint32_t overload_time; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + overload_time = yang_dnode_get_uint32(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_overload_on_startup_set(area, overload_time); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/advertise-high-metrics + */ +int isis_instance_advertise_high_metrics_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool advertise_high_metrics; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + advertise_high_metrics = yang_dnode_get_bool(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_advertise_high_metrics_set(area, advertise_high_metrics); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/metric-style + */ +int isis_instance_metric_style_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool old_metric, new_metric; + enum isis_metric_style metric_style = + yang_dnode_get_enum(args->dnode, NULL); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + old_metric = (metric_style == ISIS_WIDE_METRIC) ? false : true; + new_metric = (metric_style == ISIS_NARROW_METRIC) ? false : true; + isis_area_metricstyle_set(area, old_metric, new_metric); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/purge-originator + */ +int isis_instance_purge_originator_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->purge_originator = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + + +/* + * XPath: /frr-isisd:isis/instance/admin-group-send-zero + */ +int isis_instance_admin_group_send_zero_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + struct isis_area *area; + struct listnode *node; + struct flex_algo *fa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->admin_group_send_zero = yang_dnode_get_bool(args->dnode, NULL); + + if (area->admin_group_send_zero) { + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + admin_group_allow_explicit_zero( + &fa->admin_group_exclude_any); + admin_group_allow_explicit_zero( + &fa->admin_group_include_any); + admin_group_allow_explicit_zero( + &fa->admin_group_include_all); + } + } else { + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + admin_group_disallow_explicit_zero( + &fa->admin_group_exclude_any); + admin_group_disallow_explicit_zero( + &fa->admin_group_include_any); + admin_group_disallow_explicit_zero( + &fa->admin_group_include_all); + } + } + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_link_params_update(circuit, circuit->interface); + + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return NB_OK; +} + + +/* + * XPath: /frr-isisd:isis/instance/asla-legacy-flag + */ +int isis_instance_asla_legacy_flag_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->asla_legacy_flag = yang_dnode_get_bool(args->dnode, NULL); + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/mtu + */ +int isis_instance_lsp_mtu_modify(struct nb_cb_modify_args *args) +{ + uint16_t lsp_mtu = yang_dnode_get_uint16(args->dnode, NULL); + struct isis_area *area; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_lsp_mtu_set(area, lsp_mtu); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/advertise-passive-only + */ +int isis_instance_advertise_passive_only_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool advertise_passive_only; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + advertise_passive_only = yang_dnode_get_bool(args->dnode, NULL); + area->advertise_passive_only = advertise_passive_only; + + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval + */ +int isis_instance_lsp_refresh_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t refr_int; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + refr_int = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_lsp_refresh_set(area, IS_LEVEL_1, refr_int); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval + */ +int isis_instance_lsp_refresh_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t refr_int; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + refr_int = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_lsp_refresh_set(area, IS_LEVEL_2, refr_int); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime + */ +int isis_instance_lsp_maximum_lifetime_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t max_lt; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + max_lt = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_max_lsp_lifetime_set(area, IS_LEVEL_1, max_lt); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime + */ +int isis_instance_lsp_maximum_lifetime_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t max_lt; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + max_lt = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_max_lsp_lifetime_set(area, IS_LEVEL_2, max_lt); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-1/generation-interval + */ +int isis_instance_lsp_generation_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t gen_int; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + gen_int = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + area->lsp_gen_interval[0] = gen_int; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/lsp/timers/level-2/generation-interval + */ +int isis_instance_lsp_generation_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t gen_int; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + gen_int = yang_dnode_get_uint16(args->dnode, NULL); + area = nb_running_get_entry(args->dnode, NULL, true); + area->lsp_gen_interval[1] = gen_int; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay + */ +void ietf_backoff_delay_apply_finish(struct nb_cb_apply_finish_args *args) +{ + long init_delay = yang_dnode_get_uint16(args->dnode, "init-delay"); + long short_delay = yang_dnode_get_uint16(args->dnode, "short-delay"); + long long_delay = yang_dnode_get_uint16(args->dnode, "long-delay"); + long holddown = yang_dnode_get_uint16(args->dnode, "hold-down"); + long timetolearn = + yang_dnode_get_uint16(args->dnode, "time-to-learn"); + struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true); + size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx"); + char *buf = XCALLOC(MTYPE_TMP, bufsiz); + + snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag); + spf_backoff_free(area->spf_delay_ietf[0]); + area->spf_delay_ietf[0] = + spf_backoff_new(master, buf, init_delay, short_delay, + long_delay, holddown, timetolearn); + + snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag); + spf_backoff_free(area->spf_delay_ietf[1]); + area->spf_delay_ietf[1] = + spf_backoff_new(master, buf, init_delay, short_delay, + long_delay, holddown, timetolearn); + + XFREE(MTYPE_TMP, buf); +} + +int isis_instance_spf_ietf_backoff_delay_create(struct nb_cb_create_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +int isis_instance_spf_ietf_backoff_delay_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + spf_backoff_free(area->spf_delay_ietf[0]); + spf_backoff_free(area->spf_delay_ietf[1]); + area->spf_delay_ietf[0] = NULL; + area->spf_delay_ietf[1] = NULL; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/init-delay + */ +int isis_instance_spf_ietf_backoff_delay_init_delay_modify( + struct nb_cb_modify_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/short-delay + */ +int isis_instance_spf_ietf_backoff_delay_short_delay_modify( + struct nb_cb_modify_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/long-delay + */ +int isis_instance_spf_ietf_backoff_delay_long_delay_modify( + struct nb_cb_modify_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/hold-down + */ +int isis_instance_spf_ietf_backoff_delay_hold_down_modify( + struct nb_cb_modify_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/ietf-backoff-delay/time-to-learn + */ +int isis_instance_spf_ietf_backoff_delay_time_to_learn_modify( + struct nb_cb_modify_args *args) +{ + /* All the work is done in the apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-1 + */ +int isis_instance_spf_minimum_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->min_spf_interval[0] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/minimum-interval/level-2 + */ +int isis_instance_spf_minimum_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->min_spf_interval[1] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/spf/prefix-priorities/critical/access-list-name + */ +int isis_instance_spf_prefix_priorities_critical_access_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *acl_name; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + acl_name = yang_dnode_get_string(args->dnode, NULL); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_CRITICAL]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name); + ppa->list_v4 = access_list_lookup(AFI_IP, acl_name); + ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_spf_prefix_priorities_critical_access_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_CRITICAL]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->list_v4 = NULL; + ppa->list_v6 = NULL; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/high/access-list-name + */ +int isis_instance_spf_prefix_priorities_high_access_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *acl_name; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + acl_name = yang_dnode_get_string(args->dnode, NULL); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_HIGH]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name); + ppa->list_v4 = access_list_lookup(AFI_IP, acl_name); + ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_spf_prefix_priorities_high_access_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_HIGH]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->list_v4 = NULL; + ppa->list_v6 = NULL; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/spf/prefix-priorities/medium/access-list-name + */ +int isis_instance_spf_prefix_priorities_medium_access_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *acl_name; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + acl_name = yang_dnode_get_string(args->dnode, NULL); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_MEDIUM]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->name = XSTRDUP(MTYPE_ISIS_ACL_NAME, acl_name); + ppa->list_v4 = access_list_lookup(AFI_IP, acl_name); + ppa->list_v6 = access_list_lookup(AFI_IP6, acl_name); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_spf_prefix_priorities_medium_access_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct spf_prefix_priority_acl *ppa; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + + ppa = &area->spf_prefix_priorities[SPF_PREFIX_PRIO_MEDIUM]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + ppa->list_v4 = NULL; + ppa->list_v6 = NULL; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/area-password + */ +void area_password_apply_finish(struct nb_cb_apply_finish_args *args) +{ + const char *password = yang_dnode_get_string(args->dnode, "password"); + struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true); + int pass_type = yang_dnode_get_enum(args->dnode, "password-type"); + uint8_t snp_auth = + yang_dnode_get_enum(args->dnode, "authenticate-snp"); + + switch (pass_type) { + case ISIS_PASSWD_TYPE_CLEARTXT: + isis_area_passwd_cleartext_set(area, IS_LEVEL_1, password, + snp_auth); + break; + case ISIS_PASSWD_TYPE_HMAC_MD5: + isis_area_passwd_hmac_md5_set(area, IS_LEVEL_1, password, + snp_auth); + break; + } +} + +int isis_instance_area_password_create(struct nb_cb_create_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +int isis_instance_area_password_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_passwd_unset(area, IS_LEVEL_1); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/area-password/password + */ +int isis_instance_area_password_password_modify(struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/area-password/password-type + */ +int isis_instance_area_password_password_type_modify( + struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/area-password/authenticate-snp + */ +int isis_instance_area_password_authenticate_snp_modify( + struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/domain-password + */ +void domain_password_apply_finish(struct nb_cb_apply_finish_args *args) +{ + const char *password = yang_dnode_get_string(args->dnode, "password"); + struct isis_area *area = nb_running_get_entry(args->dnode, NULL, true); + int pass_type = yang_dnode_get_enum(args->dnode, "password-type"); + uint8_t snp_auth = + yang_dnode_get_enum(args->dnode, "authenticate-snp"); + + switch (pass_type) { + case ISIS_PASSWD_TYPE_CLEARTXT: + isis_area_passwd_cleartext_set(area, IS_LEVEL_2, password, + snp_auth); + break; + case ISIS_PASSWD_TYPE_HMAC_MD5: + isis_area_passwd_hmac_md5_set(area, IS_LEVEL_2, password, + snp_auth); + break; + } +} + +int isis_instance_domain_password_create(struct nb_cb_create_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +int isis_instance_domain_password_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_passwd_unset(area, IS_LEVEL_2); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/domain-password/password + */ +int isis_instance_domain_password_password_modify( + struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/domain-password/password-type + */ +int isis_instance_domain_password_password_type_modify( + struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/domain-password/authenticate-snp + */ +int isis_instance_domain_password_authenticate_snp_modify( + struct nb_cb_modify_args *args) +{ + /* actual setting is done in apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4 + */ +void default_info_origin_apply_finish(const struct lyd_node *dnode, int family) +{ + int originate_type = DEFAULT_ORIGINATE; + unsigned long metric = 0; + const char *routemap = NULL; + struct isis_area *area = nb_running_get_entry(dnode, NULL, true); + int level = yang_dnode_get_enum(dnode, "level"); + + if (yang_dnode_get_bool(dnode, "always")) { + originate_type = DEFAULT_ORIGINATE_ALWAYS; + } else if (family == AF_INET6) { + zlog_warn( + "%s: Zebra doesn't implement default-originate for IPv6 yet, so use with care or use default-originate always.", + __func__); + } + + if (yang_dnode_exists(dnode, "metric")) + metric = yang_dnode_get_uint32(dnode, "metric"); + if (yang_dnode_exists(dnode, "route-map")) + routemap = yang_dnode_get_string(dnode, "route-map"); + + isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, + originate_type, 0); +} + +void default_info_origin_ipv4_apply_finish(struct nb_cb_apply_finish_args *args) +{ + default_info_origin_apply_finish(args->dnode, AF_INET); +} + +void default_info_origin_ipv6_apply_finish(struct nb_cb_apply_finish_args *args) +{ + default_info_origin_apply_finish(args->dnode, AF_INET6); +} + +int isis_instance_default_information_originate_ipv4_create( + struct nb_cb_create_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +int isis_instance_default_information_originate_ipv4_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + int level; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + level = yang_dnode_get_enum(args->dnode, "level"); + isis_redist_unset(area, level, AF_INET, DEFAULT_ROUTE, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/always + */ +int isis_instance_default_information_originate_ipv4_always_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/route-map + */ +int isis_instance_default_information_originate_ipv4_route_map_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +int isis_instance_default_information_originate_ipv4_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv4/metric + */ +int isis_instance_default_information_originate_ipv4_metric_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6 + */ +int isis_instance_default_information_originate_ipv6_create( + struct nb_cb_create_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +int isis_instance_default_information_originate_ipv6_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + int level; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + level = yang_dnode_get_enum(args->dnode, "level"); + isis_redist_unset(area, level, AF_INET6, DEFAULT_ROUTE, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/always + */ +int isis_instance_default_information_originate_ipv6_always_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/route-map + */ +int isis_instance_default_information_originate_ipv6_route_map_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +int isis_instance_default_information_originate_ipv6_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/default-information-originate/ipv6/metric + */ +int isis_instance_default_information_originate_ipv6_metric_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by default_info_origin_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv4 + */ +void redistribute_apply_finish(const struct lyd_node *dnode, int family) +{ + assert(family == AF_INET || family == AF_INET6); + int type, level; + unsigned long metric = 0; + const char *routemap = NULL; + struct isis_area *area; + + type = yang_dnode_get_enum(dnode, "protocol"); + level = yang_dnode_get_enum(dnode, "level"); + area = nb_running_get_entry(dnode, NULL, true); + + if (yang_dnode_exists(dnode, "metric")) + metric = yang_dnode_get_uint32(dnode, "metric"); + if (yang_dnode_exists(dnode, "route-map")) + routemap = yang_dnode_get_string(dnode, "route-map"); + + isis_redist_set(area, level, family, type, metric, routemap, 0, 0); +} + +void redistribute_ipv4_apply_finish(struct nb_cb_apply_finish_args *args) +{ + redistribute_apply_finish(args->dnode, AF_INET); +} + +void redistribute_ipv6_apply_finish(struct nb_cb_apply_finish_args *args) +{ + redistribute_apply_finish(args->dnode, AF_INET6); +} + +int isis_instance_redistribute_ipv4_create(struct nb_cb_create_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv4_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + int level, type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + level = yang_dnode_get_enum(args->dnode, "level"); + type = yang_dnode_get_enum(args->dnode, "protocol"); + isis_redist_unset(area, level, AF_INET, type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv4/route-map + * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table/route-map + */ +int isis_instance_redistribute_ipv4_route_map_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv4_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv4/metric + * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table/metric + */ +int isis_instance_redistribute_ipv4_metric_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv4_metric_destroy(struct nb_cb_destroy_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv4/table + */ +int isis_instance_redistribute_ipv4_table_create(struct nb_cb_create_args *args) +{ + uint16_t table; + int type, level; + unsigned long metric = 0; + const char *routemap = NULL; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + type = yang_dnode_get_enum(args->dnode, "../protocol"); + level = yang_dnode_get_enum(args->dnode, "../level"); + area = nb_running_get_entry(args->dnode, "../.", true); + + if (yang_dnode_exists(args->dnode, "metric")) + metric = yang_dnode_get_uint32(args->dnode, "metric"); + if (yang_dnode_exists(args->dnode, "route-map")) + routemap = yang_dnode_get_string(args->dnode, "route-map"); + + table = yang_dnode_get_uint16(args->dnode, "table"); + isis_redist_set(area, level, AF_INET, type, metric, routemap, 0, table); + + return NB_OK; +} +int isis_instance_redistribute_ipv4_table_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + int level, type; + uint16_t table; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, "../.", true); + level = yang_dnode_get_enum(args->dnode, "../level"); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + table = yang_dnode_get_uint16(args->dnode, "table"); + isis_redist_unset(area, level, AF_INET, type, table); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv6 + */ +int isis_instance_redistribute_ipv6_create(struct nb_cb_create_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv6_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + int level, type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + level = yang_dnode_get_enum(args->dnode, "level"); + type = yang_dnode_get_enum(args->dnode, "protocol"); + isis_redist_unset(area, level, AF_INET6, type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv6/route-map + */ +int isis_instance_redistribute_ipv6_route_map_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv6_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv6/metric + */ +int isis_instance_redistribute_ipv6_metric_modify( + struct nb_cb_modify_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +int isis_instance_redistribute_ipv6_metric_destroy(struct nb_cb_destroy_args *args) +{ + /* It's all done by redistribute_apply_finish */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/redistribute/ipv6/table + */ +int isis_instance_redistribute_ipv6_table_create(struct nb_cb_create_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* TODO */ + return NB_OK; +} + +int isis_instance_redistribute_ipv6_table_destroy(struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* TODO */ + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast + */ +static int isis_multi_topology_common(enum nb_event event, + const struct lyd_node *dnode, + char *errmsg, size_t errmsg_len, + const char *topology, bool create) +{ + struct isis_area *area; + struct isis_area_mt_setting *setting; + uint16_t mtid = isis_str2mtid(topology); + + switch (event) { + case NB_EV_VALIDATE: + if (mtid == (uint16_t)-1) { + snprintf(errmsg, errmsg_len, "Unknown topology %s", + topology); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + area = nb_running_get_entry(dnode, NULL, true); + setting = area_get_mt_setting(area, mtid); + setting->enabled = create; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + break; + } + + return NB_OK; +} + +static int isis_multi_topology_overload_common(enum nb_event event, + const struct lyd_node *dnode, + const char *topology) +{ + struct isis_area *area; + struct isis_area_mt_setting *setting; + uint16_t mtid = isis_str2mtid(topology); + + /* validation is done in isis_multi_topology_common */ + if (event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(dnode, NULL, true); + setting = area_get_mt_setting(area, mtid); + setting->overload = yang_dnode_get_bool(dnode, NULL); + if (setting->enabled) + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + + return NB_OK; +} + +int isis_instance_multi_topology_ipv4_multicast_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv4-multicast", true); +} + +int isis_instance_multi_topology_ipv4_multicast_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv4-multicast", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-multicast/overload + */ +int isis_instance_multi_topology_ipv4_multicast_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv4-multicast"); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management + */ +int isis_instance_multi_topology_ipv4_management_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv4-mgmt", true); +} + +int isis_instance_multi_topology_ipv4_management_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv4-mgmt", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv4-management/overload + */ +int isis_instance_multi_topology_ipv4_management_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv4-mgmt"); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast + */ +int isis_instance_multi_topology_ipv6_unicast_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-unicast", true); +} + +int isis_instance_multi_topology_ipv6_unicast_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-unicast", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-unicast/overload + */ +int isis_instance_multi_topology_ipv6_unicast_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv6-unicast"); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast + */ +int isis_instance_multi_topology_ipv6_multicast_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-multicast", true); +} + +int isis_instance_multi_topology_ipv6_multicast_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-multicast", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-multicast/overload + */ +int isis_instance_multi_topology_ipv6_multicast_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv6-multicast"); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management + */ +int isis_instance_multi_topology_ipv6_management_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-mgmt", true); +} + +int isis_instance_multi_topology_ipv6_management_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-mgmt", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-management/overload + */ +int isis_instance_multi_topology_ipv6_management_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv6-mgmt"); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc + */ +int isis_instance_multi_topology_ipv6_dstsrc_create( + struct nb_cb_create_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-dstsrc", true); +} + +int isis_instance_multi_topology_ipv6_dstsrc_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_multi_topology_common(args->event, args->dnode, + args->errmsg, args->errmsg_len, + "ipv6-dstsrc", false); +} + +/* + * XPath: /frr-isisd:isis/instance/multi-topology/ipv6-dstsrc/overload + */ +int isis_instance_multi_topology_ipv6_dstsrc_overload_modify( + struct nb_cb_modify_args *args) +{ + return isis_multi_topology_overload_common(args->event, args->dnode, + "ipv6-dstsrc"); +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing + */ +int isis_instance_fast_reroute_level_1_lfa_load_sharing_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_load_sharing[0] = yang_dnode_get_bool(args->dnode, NULL); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/priority-limit + */ +int isis_instance_fast_reroute_level_1_lfa_priority_limit_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_priority_limit[0] = yang_dnode_get_enum(args->dnode, NULL); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_1_lfa_priority_limit_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_priority_limit[0] = SPF_PREFIX_PRIO_LOW; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker + */ +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + uint8_t index; + enum lfa_tiebreaker_type type; + struct lfa_tiebreaker *tie_b; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + index = yang_dnode_get_uint8(args->dnode, "index"); + type = yang_dnode_get_enum(args->dnode, "type"); + + tie_b = isis_lfa_tiebreaker_add(area, ISIS_LEVEL1, index, type); + nb_running_set_entry(args->dnode, tie_b); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_destroy( + struct nb_cb_destroy_args *args) +{ + struct lfa_tiebreaker *tie_b; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + tie_b = nb_running_unset_entry(args->dnode); + area = tie_b->area; + isis_lfa_tiebreaker_delete(area, ISIS_LEVEL1, tie_b); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/lfa/tiebreaker/type + */ +int isis_instance_fast_reroute_level_1_lfa_tiebreaker_type_modify( + struct nb_cb_modify_args *args) +{ + struct lfa_tiebreaker *tie_b; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + tie_b = nb_running_get_entry(args->dnode, NULL, true); + area = tie_b->area; + tie_b->type = yang_dnode_get_enum(args->dnode, NULL); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-1/remote-lfa/prefix-list + */ +int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *plist_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + plist_name = yang_dnode_get_string(args->dnode, NULL); + + area->rlfa_plist_name[0] = XSTRDUP(MTYPE_ISIS_PLIST_NAME, plist_name); + area->rlfa_plist[0] = prefix_list_lookup(AFI_IP, plist_name); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_1_remote_lfa_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + + XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[0]); + area->rlfa_plist[0] = NULL; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing + */ +int isis_instance_fast_reroute_level_2_lfa_load_sharing_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_load_sharing[1] = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/priority-limit + */ +int isis_instance_fast_reroute_level_2_lfa_priority_limit_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_priority_limit[1] = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_2_lfa_priority_limit_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->lfa_priority_limit[1] = SPF_PREFIX_PRIO_LOW; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker + */ +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + uint8_t index; + enum lfa_tiebreaker_type type; + struct lfa_tiebreaker *tie_b; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + index = yang_dnode_get_uint8(args->dnode, "index"); + type = yang_dnode_get_enum(args->dnode, "type"); + + tie_b = isis_lfa_tiebreaker_add(area, ISIS_LEVEL2, index, type); + nb_running_set_entry(args->dnode, tie_b); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_destroy( + struct nb_cb_destroy_args *args) +{ + struct lfa_tiebreaker *tie_b; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + tie_b = nb_running_unset_entry(args->dnode); + area = tie_b->area; + isis_lfa_tiebreaker_delete(area, ISIS_LEVEL2, tie_b); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/lfa/tiebreaker/type + */ +int isis_instance_fast_reroute_level_2_lfa_tiebreaker_type_modify( + struct nb_cb_modify_args *args) +{ + struct lfa_tiebreaker *tie_b; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + tie_b = nb_running_get_entry(args->dnode, NULL, true); + area = tie_b->area; + tie_b->type = yang_dnode_get_enum(args->dnode, NULL); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/fast-reroute/level-2/remote-lfa/prefix-list + */ +int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *plist_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + plist_name = yang_dnode_get_string(args->dnode, NULL); + + area->rlfa_plist_name[1] = XSTRDUP(MTYPE_ISIS_PLIST_NAME, plist_name); + area->rlfa_plist[1] = prefix_list_lookup(AFI_IP, plist_name); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_fast_reroute_level_2_remote_lfa_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + + XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[1]); + area->rlfa_plist[1] = NULL; + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/log-adjacency-changes + */ +int isis_instance_log_adjacency_changes_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool log = yang_dnode_get_bool(args->dnode, NULL); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->log_adj_changes = log ? 1 : 0; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/log-pdu-drops + */ +int isis_instance_log_pdu_drops_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool log = yang_dnode_get_bool(args->dnode, NULL); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->log_pdu_drops = log ? 1 : 0; + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te + */ +int isis_instance_mpls_te_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + isis_mpls_te_create(area); + + /* Reoriginate STD_TE & GMPLS circuits */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_mpls_te_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + isis_mpls_te_disable(area); + + /* Reoriginate STD_TE & GMPLS circuits */ + lsp_regenerate_schedule(area, area->is_type, 0); + + zlog_debug("ISIS-TE(%s): Disabled MPLS Traffic Engineering", + area->area_tag); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/router-address + */ +int isis_instance_mpls_te_router_address_modify(struct nb_cb_modify_args *args) +{ + struct in_addr value; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + /* only proceed if MPLS-TE is enabled */ + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + /* Update Area Router ID */ + yang_dnode_get_ipv4(&value, args->dnode, NULL); + area->mta->router_id.s_addr = value.s_addr; + + /* And re-schedule LSP update */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_mpls_te_router_address_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + /* only proceed if MPLS-TE is enabled */ + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + /* Reset Area Router ID */ + area->mta->router_id.s_addr = INADDR_ANY; + + /* And re-schedule LSP update */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/router-address-v6 + */ +int isis_instance_mpls_te_router_address_ipv6_modify( + struct nb_cb_modify_args *args) +{ + struct in6_addr value; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + /* only proceed if MPLS-TE is enabled */ + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + yang_dnode_get_ipv6(&value, args->dnode, NULL); + /* Update Area IPv6 Router ID if different */ + if (!IPV6_ADDR_SAME(&area->mta->router_id_ipv6, &value)) { + IPV6_ADDR_COPY(&area->mta->router_id_ipv6, &value); + + /* And re-schedule LSP update */ + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +int isis_instance_mpls_te_router_address_ipv6_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + /* only proceed if MPLS-TE is enabled */ + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + /* Reset Area Router ID */ + IPV6_ADDR_COPY(&area->mta->router_id_ipv6, &in6addr_any); + + /* And re-schedule LSP update */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls-te/export + */ +int isis_instance_mpls_te_export_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + /* only proceed if MPLS-TE is enabled */ + if (!IS_MPLS_TE(area->mta)) + return NB_OK; + + area->mta->export = yang_dnode_get_bool(args->dnode, NULL); + if (area->mta->export) { + if (IS_DEBUG_EVENTS) + zlog_debug("MPLS-TE: Enabled Link State export"); + if (isis_zebra_ls_register(true) != 0) + zlog_warn("Unable to register Link State"); + } else { + if (IS_DEBUG_EVENTS) + zlog_debug("MPLS-TE: Disable Link State export"); + if (isis_zebra_ls_register(false) != 0) + zlog_warn("Unable to register Link State"); + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/enabled + */ +int isis_instance_segment_routing_enabled_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.enabled = yang_dnode_get_bool(args->dnode, NULL); + + if (area->srdb.config.enabled) { + if (IS_DEBUG_EVENTS) + zlog_debug("SR: Segment Routing: OFF -> ON"); + + isis_sr_start(area); + } else { + if (IS_DEBUG_EVENTS) + zlog_debug("SR: Segment Routing: ON -> OFF"); + + isis_sr_stop(area); + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks + */ +int isis_instance_segment_routing_label_blocks_pre_validate( + struct nb_cb_pre_validate_args *args) +{ + uint32_t srgb_lbound; + uint32_t srgb_ubound; + uint32_t srlb_lbound; + uint32_t srlb_ubound; + + srgb_lbound = yang_dnode_get_uint32(args->dnode, "srgb/lower-bound"); + srgb_ubound = yang_dnode_get_uint32(args->dnode, "srgb/upper-bound"); + srlb_lbound = yang_dnode_get_uint32(args->dnode, "srlb/lower-bound"); + srlb_ubound = yang_dnode_get_uint32(args->dnode, "srlb/upper-bound"); + + /* Check that the block size does not exceed 65535 */ + if ((srgb_ubound - srgb_lbound + 1) > 65535) { + snprintf( + args->errmsg, args->errmsg_len, + "New SR Global Block (%u/%u) exceed the limit of 65535", + srgb_lbound, srgb_ubound); + return NB_ERR_VALIDATION; + } + if ((srlb_ubound - srlb_lbound + 1) > 65535) { + snprintf(args->errmsg, args->errmsg_len, + "New SR Local Block (%u/%u) exceed the limit of 65535", + srlb_lbound, srlb_ubound); + return NB_ERR_VALIDATION; + } + + /* Validate SRGB against SRLB */ + if (!((srgb_ubound < srlb_lbound) || (srgb_lbound > srlb_ubound))) { + snprintf( + args->errmsg, args->errmsg_len, + "SR Global Block (%u/%u) conflicts with Local Block (%u/%u)", + srgb_lbound, srgb_ubound, srlb_lbound, srlb_ubound); + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb + */ + +void isis_instance_segment_routing_srgb_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct isis_area *area; + uint32_t lower_bound, upper_bound; + + area = nb_running_get_entry(args->dnode, NULL, true); + lower_bound = yang_dnode_get_uint32(args->dnode, "lower-bound"); + upper_bound = yang_dnode_get_uint32(args->dnode, "upper-bound"); + + isis_sr_cfg_srgb_update(area, lower_bound, upper_bound); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb/lower-bound + */ +int isis_instance_segment_routing_srgb_lower_bound_modify( + struct nb_cb_modify_args *args) +{ + uint32_t lower_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(lower_bound)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid SRGB lower bound: %u", lower_bound); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srgb/upper-bound + */ +int isis_instance_segment_routing_srgb_upper_bound_modify( + struct nb_cb_modify_args *args) +{ + uint32_t upper_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(upper_bound)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid SRGB upper bound: %u", upper_bound); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb + */ +void isis_instance_segment_routing_srlb_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct isis_area *area; + uint32_t lower_bound, upper_bound; + + area = nb_running_get_entry(args->dnode, NULL, true); + lower_bound = yang_dnode_get_uint32(args->dnode, "lower-bound"); + upper_bound = yang_dnode_get_uint32(args->dnode, "upper-bound"); + + isis_sr_cfg_srlb_update(area, lower_bound, upper_bound); +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb/lower-bound + */ +int isis_instance_segment_routing_srlb_lower_bound_modify( + struct nb_cb_modify_args *args) +{ + uint32_t lower_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(lower_bound)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid SRLB lower bound: %u", lower_bound); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/label-blocks/srlb/upper-bound + */ +int isis_instance_segment_routing_srlb_upper_bound_modify( + struct nb_cb_modify_args *args) +{ + uint32_t upper_bound = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + if (!IS_MPLS_UNRESERVED_LABEL(upper_bound)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid SRLB upper bound: %u", upper_bound); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/msd/node-msd + */ +int isis_instance_segment_routing_msd_node_msd_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.msd = yang_dnode_get_uint8(args->dnode, NULL); + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_segment_routing_msd_node_msd_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srdb.config.msd = 0; + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid + */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct prefix prefix; + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + + pcfg = isis_sr_cfg_prefix_add(area, &prefix, SR_ALGORITHM_SPF); + nb_running_set_entry(args->dnode, pcfg); + + return NB_OK; +} + +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_destroy( + struct nb_cb_destroy_args *args) +{ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_unset_entry(args->dnode); + area = pcfg->area; + isis_sr_cfg_prefix_del(pcfg); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args) +{ + const struct lyd_node *area_dnode; + struct isis_area *area; + struct prefix prefix; + uint32_t srgb_lbound; + uint32_t srgb_ubound; + uint32_t srgb_range; + uint32_t sid; + enum sr_sid_value_type sid_type; + struct isis_prefix_sid psid = {}; + + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + srgb_lbound = yang_dnode_get_uint32( + args->dnode, "../../label-blocks/srgb/lower-bound"); + srgb_ubound = yang_dnode_get_uint32( + args->dnode, "../../label-blocks/srgb/upper-bound"); + sid = yang_dnode_get_uint32(args->dnode, "sid-value"); + sid_type = yang_dnode_get_enum(args->dnode, "sid-value-type"); + + /* Check for invalid indexes/labels. */ + srgb_range = srgb_ubound - srgb_lbound + 1; + psid.value = sid; + switch (sid_type) { + case SR_SID_VALUE_TYPE_INDEX: + if (sid >= srgb_range) { + snprintf(args->errmsg, args->errmsg_len, + "SID index %u falls outside local SRGB range", + sid); + return NB_ERR_VALIDATION; + } + break; + case SR_SID_VALUE_TYPE_ABSOLUTE: + if (!IS_MPLS_UNRESERVED_LABEL(sid)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid absolute SID %u", sid); + return NB_ERR_VALIDATION; + } + SET_FLAG(psid.flags, ISIS_PREFIX_SID_VALUE); + SET_FLAG(psid.flags, ISIS_PREFIX_SID_LOCAL); + break; + } + + /* Check for Prefix-SID collisions. */ + area_dnode = yang_dnode_get_parent(args->dnode, "instance"); + area = nb_running_get_entry(area_dnode, NULL, false); + if (area) { + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; + level++) { + struct isis_spftree *spftree; + struct isis_vertex *vertex_psid; + + if (!(area->is_type & level)) + continue; + spftree = area->spftree[tree][level - 1]; + if (!spftree) + continue; + + vertex_psid = isis_spf_prefix_sid_lookup( + spftree, &psid); + if (vertex_psid + && !prefix_same(&vertex_psid->N.ip.p.dest, + &prefix)) { + snprintfrr( + args->errmsg, args->errmsg_len, + "Prefix-SID collision detected, SID %s %u is already in use by prefix %pFX (L%u)", + CHECK_FLAG( + psid.flags, + ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + psid.value, + &vertex_psid->N.ip.p.dest, + level); + return NB_ERR_VALIDATION; + } + } + } + } + + return NB_OK; +} + +void isis_instance_segment_routing_prefix_sid_map_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + area = pcfg->area; + lsp_regenerate_schedule(area, area->is_type, 0); +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value-type + */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_type_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid_type = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/sid-value + */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_sid_value_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid = yang_dnode_get_uint32(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/last-hop-behavior + */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_last_hop_behavior_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->last_hop_behavior = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing/prefix-sid-map/prefix-sid/n-flag-clear + */ +int isis_instance_segment_routing_prefix_sid_map_prefix_sid_n_flag_clear_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->n_flag_clear = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid + */ +int isis_instance_segment_routing_algorithm_prefix_sid_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct prefix prefix; + struct sr_prefix_cfg *pcfg; + uint32_t algorithm; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + algorithm = yang_dnode_get_uint32(args->dnode, "algo"); + + pcfg = isis_sr_cfg_prefix_add(area, &prefix, algorithm); + pcfg->algorithm = algorithm; + nb_running_set_entry(args->dnode, pcfg); + + return NB_OK; +} + +int isis_instance_segment_routing_algorithm_prefix_sid_destroy( + struct nb_cb_destroy_args *args) +{ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_unset_entry(args->dnode); + area = pcfg->area; + isis_sr_cfg_prefix_del(pcfg); + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int isis_instance_segment_routing_algorithm_prefix_sid_pre_validate( + struct nb_cb_pre_validate_args *args) +{ + const struct lyd_node *area_dnode; + struct isis_area *area; + struct prefix prefix; + uint32_t srgb_lbound; + uint32_t srgb_ubound; + uint32_t srgb_range; + uint32_t sid; + enum sr_sid_value_type sid_type; + struct isis_prefix_sid psid = {}; + + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + srgb_lbound = yang_dnode_get_uint32( + args->dnode, "../../label-blocks/srgb/lower-bound"); + srgb_ubound = yang_dnode_get_uint32( + args->dnode, "../../label-blocks/srgb/upper-bound"); + sid = yang_dnode_get_uint32(args->dnode, "sid-value"); + sid_type = yang_dnode_get_enum(args->dnode, "sid-value-type"); + + /* Check for invalid indexes/labels. */ + srgb_range = srgb_ubound - srgb_lbound + 1; + psid.value = sid; + switch (sid_type) { + case SR_SID_VALUE_TYPE_INDEX: + if (sid >= srgb_range) { + snprintf(args->errmsg, args->errmsg_len, + "SID index %u falls outside local SRGB range", + sid); + return NB_ERR_VALIDATION; + } + break; + case SR_SID_VALUE_TYPE_ABSOLUTE: + if (!IS_MPLS_UNRESERVED_LABEL(sid)) { + snprintf(args->errmsg, args->errmsg_len, + "Invalid absolute SID %u", sid); + return NB_ERR_VALIDATION; + } + SET_FLAG(psid.flags, ISIS_PREFIX_SID_VALUE); + SET_FLAG(psid.flags, ISIS_PREFIX_SID_LOCAL); + break; + } + + /* Check for Prefix-SID collisions. */ + area_dnode = yang_dnode_get_parent(args->dnode, "instance"); + area = nb_running_get_entry(area_dnode, NULL, false); + if (!area) + return NB_OK; + + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + struct isis_spftree *spftree; + struct isis_vertex *vertex_psid; + + if (!(area->is_type & level)) + continue; + spftree = area->spftree[tree][level - 1]; + if (!spftree) + continue; + + vertex_psid = + isis_spf_prefix_sid_lookup(spftree, &psid); + if (vertex_psid && + !prefix_same(&vertex_psid->N.ip.p.dest, &prefix)) { + snprintfrr( + args->errmsg, args->errmsg_len, + "Prefix-SID collision detected, SID %s %u is already in use by prefix %pFX (L%u)", + CHECK_FLAG(psid.flags, + ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + psid.value, &vertex_psid->N.ip.p.dest, + level); + return NB_ERR_VALIDATION; + } + } + } + + return NB_OK; +} + +void isis_instance_segment_routing_algorithm_prefix_sid_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct sr_prefix_cfg *pcfg; + struct isis_area *area; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + area = pcfg->area; + lsp_regenerate_schedule(area, area->is_type, 0); +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value-type + */ +int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_type_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid_type = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/sid-value + */ +int isis_instance_segment_routing_algorithm_prefix_sid_sid_value_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->sid = yang_dnode_get_uint32(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sid-map/algorithm-prefix-sid/last-hop-behavior + */ +int isis_instance_segment_routing_algorithm_prefix_sid_last_hop_behavior_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->last_hop_behavior = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/segment-routing/algorithm-prefix-sids/algorithm-prefix-sid/n-flag-clear + */ +int isis_instance_segment_routing_algorithm_prefix_sid_n_flag_clear_modify( + struct nb_cb_modify_args *args) +{ + struct sr_prefix_cfg *pcfg; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pcfg = nb_running_get_entry(args->dnode, NULL, true); + pcfg->n_flag_clear = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo + */ +int isis_instance_flex_algo_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct flex_algo *fa; + bool advertise; + uint32_t algorithm; + uint32_t priority = FLEX_ALGO_PRIO_DEFAULT; + struct isis_flex_algo_alloc_arg arg; + + algorithm = yang_dnode_get_uint32(args->dnode, "flex-algo"); + advertise = yang_dnode_exists(args->dnode, "advertise-definition"); + + switch (args->event) { + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + arg.algorithm = algorithm; + arg.area = area; + fa = flex_algo_alloc(area->flex_algos, algorithm, &arg); + fa->priority = priority; + fa->advertise_definition = advertise; + if (area->admin_group_send_zero) { + admin_group_allow_explicit_zero( + &fa->admin_group_exclude_any); + admin_group_allow_explicit_zero( + &fa->admin_group_include_any); + admin_group_allow_explicit_zero( + &fa->admin_group_include_all); + } + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +int isis_instance_flex_algo_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + uint32_t algorithm; + + algorithm = yang_dnode_get_uint32(args->dnode, "flex-algo"); + area = nb_running_get_entry(args->dnode, NULL, true); + + switch (args->event) { + case NB_EV_APPLY: + flex_algo_delete(area->flex_algos, algorithm); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/advertise-definition + */ +int isis_instance_flex_algo_advertise_definition_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct flex_algo *fa; + bool advertise; + uint32_t algorithm; + + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + advertise = yang_dnode_exists(args->dnode, "../advertise-definition"); + + switch (args->event) { + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + fa->advertise_definition = advertise; + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +int isis_instance_flex_algo_advertise_definition_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct flex_algo *fa; + uint32_t algorithm; + + area = nb_running_get_entry(args->dnode, NULL, true); + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + fa->advertise_definition = false; + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +static int isis_instance_flex_algo_affinity_set(struct nb_cb_create_args *args, + int type) +{ + struct affinity_map *map; + struct isis_area *area; + struct admin_group *ag; + struct flex_algo *fa; + uint32_t algorithm; + const char *val; + + algorithm = yang_dnode_get_uint32(args->dnode, "../../flex-algo"); + area = nb_running_get_entry(args->dnode, NULL, true); + val = yang_dnode_get_string(args->dnode, "."); + + switch (args->event) { + case NB_EV_VALIDATE: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + map = affinity_map_get(val); + if (!map) { + snprintf(args->errmsg, args->errmsg_len, + "affinity map %s isn't found", val); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + map = affinity_map_get(val); + if (!map) { + snprintf(args->errmsg, args->errmsg_len, + "affinity map %s isn't found", val); + return NB_ERR_RESOURCE; + } + if (type == AFFINITY_INCLUDE_ANY) + ag = &fa->admin_group_include_any; + else if (type == AFFINITY_INCLUDE_ALL) + ag = &fa->admin_group_include_all; + else if (type == AFFINITY_EXCLUDE_ANY) + ag = &fa->admin_group_exclude_any; + else + break; + + admin_group_set(ag, map->bit_position); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + } + + return NB_OK; +} + +static int +isis_instance_flex_algo_affinity_unset(struct nb_cb_destroy_args *args, + int type) +{ + struct affinity_map *map; + struct isis_area *area; + struct admin_group *ag; + struct flex_algo *fa; + uint32_t algorithm; + const char *val; + + algorithm = yang_dnode_get_uint32(args->dnode, "../../flex-algo"); + area = nb_running_get_entry(args->dnode, NULL, true); + val = yang_dnode_get_string(args->dnode, "."); + + switch (args->event) { + case NB_EV_VALIDATE: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + map = affinity_map_get(val); + if (!map) { + snprintf(args->errmsg, args->errmsg_len, + "affinity map %s isn't found", val); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + map = affinity_map_get(val); + if (!map) { + snprintf(args->errmsg, args->errmsg_len, + "affinity map %s isn't found", val); + return NB_ERR_RESOURCE; + } + if (type == AFFINITY_INCLUDE_ANY) + ag = &fa->admin_group_include_any; + else if (type == AFFINITY_INCLUDE_ALL) + ag = &fa->admin_group_include_all; + else if (type == AFFINITY_EXCLUDE_ANY) + ag = &fa->admin_group_exclude_any; + else + break; + + admin_group_unset(ag, map->bit_position); + if (area->admin_group_send_zero) + admin_group_allow_explicit_zero(ag); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-anies/affinity-include-any + */ +int isis_instance_flex_algo_affinity_include_any_create( + struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_affinity_set(args, AFFINITY_INCLUDE_ANY); +} + +int isis_instance_flex_algo_affinity_include_any_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_affinity_unset(args, + AFFINITY_INCLUDE_ANY); +} + +/* + * XPath: + * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-include-alls/affinity-include-all + */ +int isis_instance_flex_algo_affinity_include_all_create( + struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_affinity_set(args, AFFINITY_INCLUDE_ALL); +} + +int isis_instance_flex_algo_affinity_include_all_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_affinity_unset(args, + AFFINITY_INCLUDE_ALL); +} + +/* + * XPath: + * /frr-isisd:isis/instance/flex-algos/flex-algo/affinity-exclude-anies/affinity-exclude-any + */ +int isis_instance_flex_algo_affinity_exclude_any_create( + struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_affinity_set(args, AFFINITY_EXCLUDE_ANY); +} + +int isis_instance_flex_algo_affinity_exclude_any_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_affinity_unset(args, + AFFINITY_EXCLUDE_ANY); +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/prefix-metric + */ + +int isis_instance_flex_algo_prefix_metric_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + SET_FLAG(fa->flags, FAD_FLAG_M); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +int isis_instance_flex_algo_prefix_metric_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + UNSET_FLAG(fa->flags, FAD_FLAG_M); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +static int isis_instance_flex_algo_dplane_set(struct nb_cb_create_args *args, + int type) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + SET_FLAG(fa->dataplanes, type); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + if (type == FLEX_ALGO_SRV6 || type == FLEX_ALGO_IP) { + snprintf(args->errmsg, args->errmsg_len, + "%s Flex-algo dataplane is not yet supported.", + type == FLEX_ALGO_SRV6 ? "SRv6" : "IP"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +static int isis_instance_flex_algo_dplane_unset(struct nb_cb_destroy_args *args, + int type) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + UNSET_FLAG(fa->dataplanes, type); + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-sr-mpls + */ + +int isis_instance_flex_algo_dplane_sr_mpls_create( + struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_SR_MPLS); +} + +int isis_instance_flex_algo_dplane_sr_mpls_destroy( + struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_SR_MPLS); +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-srv6 + */ + +int isis_instance_flex_algo_dplane_srv6_create(struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_SRV6); +} + +int isis_instance_flex_algo_dplane_srv6_destroy(struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_SRV6); +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/dplane-ip + */ + +int isis_instance_flex_algo_dplane_ip_create(struct nb_cb_create_args *args) +{ + return isis_instance_flex_algo_dplane_set(args, FLEX_ALGO_IP); +} + +int isis_instance_flex_algo_dplane_ip_destroy(struct nb_cb_destroy_args *args) +{ + return isis_instance_flex_algo_dplane_unset(args, FLEX_ALGO_IP); +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/metric-type + */ + +int isis_instance_flex_algo_metric_type_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + enum flex_algo_metric_type metric_type; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + metric_type = yang_dnode_get_enum(args->dnode, NULL); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + fa->metric_type = metric_type; + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/flex-algos/flex-algo/priority + */ + +int isis_instance_flex_algo_priority_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + uint32_t priority; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + priority = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + fa->priority = priority; + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +int isis_instance_flex_algo_priority_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + const char *area_tag; + struct flex_algo *fa; + uint32_t algorithm; + uint32_t priority = FLEX_ALGO_PRIO_DEFAULT; + + area_tag = yang_dnode_get_string(args->dnode, "../../../area-tag"); + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + return NB_ERR_RESOURCE; + + algorithm = yang_dnode_get_uint32(args->dnode, "../flex-algo"); + priority = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_APPLY: + fa = flex_algo_lookup(area->flex_algos, algorithm); + if (!fa) { + snprintf(args->errmsg, args->errmsg_len, + "flex-algo object not found"); + return NB_ERR_RESOURCE; + } + fa->priority = priority; + lsp_regenerate_schedule(area, area->is_type, 0); + break; + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/enabled + */ +int isis_instance_segment_routing_srv6_enabled_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srv6db.config.enabled = yang_dnode_get_bool(args->dnode, NULL); + + if (area->srv6db.config.enabled) { + if (IS_DEBUG_EVENTS) + zlog_debug( + "Segment Routing over IPv6 (SRv6): OFF -> ON"); + } else { + if (IS_DEBUG_EVENTS) + zlog_debug( + "Segment Routing over IPv6 (SRv6): ON -> OFF"); + } + + /* Regenerate LSPs to advertise SRv6 capabilities or signal that the + * node is no longer SRv6-capable. */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/locator + */ +int isis_instance_segment_routing_srv6_locator_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *loc_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL, + true); + + loc_name = yang_dnode_get_string(args->dnode, NULL); + + if (strncmp(loc_name, area->srv6db.config.srv6_locator_name, + sizeof(area->srv6db.config.srv6_locator_name)) == 0) { + snprintf(args->errmsg, args->errmsg_len, + "SRv6 locator %s is already configured", loc_name); + return NB_ERR_NO_CHANGES; + } + + /* Remove previously configured locator */ + if (strncmp(area->srv6db.config.srv6_locator_name, "", + sizeof(area->srv6db.config.srv6_locator_name)) != 0) { + sr_debug("Unsetting previously configured SRv6 locator"); + if (!isis_srv6_locator_unset(area)) { + zlog_warn("Failed to unset SRv6 locator"); + return NB_ERR; + } + } + + strlcpy(area->srv6db.config.srv6_locator_name, loc_name, + sizeof(area->srv6db.config.srv6_locator_name)); + + sr_debug("Configured SRv6 locator %s for IS-IS area %s", loc_name, + area->area_tag); + + sr_debug("Trying to get a chunk from locator %s for IS-IS area %s", + loc_name, area->area_tag); + + if (isis_zebra_srv6_manager_get_locator_chunk(loc_name) < 0) + return NB_ERR; + + return NB_OK; +} + +int isis_instance_segment_routing_srv6_locator_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + const char *loc_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL, + true); + + loc_name = yang_dnode_get_string(args->dnode, NULL); + + sr_debug("Trying to unset SRv6 locator %s", loc_name); + + if (strncmp(loc_name, area->srv6db.config.srv6_locator_name, + sizeof(area->srv6db.config.srv6_locator_name)) != 0) { + sr_debug("SRv6 locator %s is not configured", loc_name); + snprintf(args->errmsg, args->errmsg_len, + "SRv6 locator %s is not configured", loc_name); + return NB_ERR_NO_CHANGES; + } + + if (!isis_srv6_locator_unset(area)) { + zlog_warn("Failed to unset SRv6 locator"); + return NB_ERR; + } + + sr_debug("Deleted SRv6 locator %s for IS-IS area %s", loc_name, + area->area_tag); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-segs-left + */ +int isis_instance_segment_routing_srv6_msd_node_msd_max_segs_left_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srv6db.config.max_seg_left_msd = yang_dnode_get_uint8(args->dnode, + NULL); + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-pop + */ +int isis_instance_segment_routing_srv6_msd_node_msd_max_end_pop_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srv6db.config.max_end_pop_msd = yang_dnode_get_uint8(args->dnode, + NULL); + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-h-encaps + */ +int isis_instance_segment_routing_srv6_msd_node_msd_max_h_encaps_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srv6db.config.max_h_encaps_msd = yang_dnode_get_uint8(args->dnode, + NULL); + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/msd/node-msd/max-end-d + */ +int isis_instance_segment_routing_srv6_msd_node_msd_max_end_d_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + area->srv6db.config.max_end_d_msd = yang_dnode_get_uint8(args->dnode, + NULL); + + /* Update and regenerate LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/segment-routing-srv6/interface + */ +int isis_instance_segment_routing_srv6_interface_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL, + true); + + ifname = yang_dnode_get_string(args->dnode, NULL); + + sr_debug("Changing SRv6 interface for IS-IS area %s to %s", + area->area_tag, ifname); + + isis_srv6_interface_set(area, ifname); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls/ldp-sync + */ +int isis_instance_mpls_ldp_sync_create(struct nb_cb_create_args *args) +{ + struct isis_area *area; + const char *vrfname; + + switch (args->event) { + case NB_EV_VALIDATE: + vrfname = yang_dnode_get_string( + lyd_parent(lyd_parent(args->dnode)), "./vrf"); + + if (strcmp(vrfname, VRF_DEFAULT_NAME)) { + snprintf(args->errmsg, args->errmsg_len, + "LDP-Sync only runs on Default VRF"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_ldp_sync_enable(area); + break; + } + return NB_OK; +} + +int isis_instance_mpls_ldp_sync_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + isis_area_ldp_sync_disable(area); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/mpls/ldp-sync/holddown + */ +int isis_instance_mpls_ldp_sync_holddown_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + uint16_t holddown; + const char *vrfname; + + switch (args->event) { + case NB_EV_VALIDATE: + vrfname = yang_dnode_get_string( + lyd_parent(lyd_parent(lyd_parent(args->dnode))), + "./vrf"); + + if (strcmp(vrfname, VRF_DEFAULT_NAME)) { + snprintf(args->errmsg, args->errmsg_len, + "LDP-Sync only runs on Default VRF"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + area = nb_running_get_entry(args->dnode, NULL, true); + holddown = yang_dnode_get_uint16(args->dnode, NULL); + isis_area_ldp_sync_set_holddown(area, holddown); + break; + } + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis + */ +int lib_interface_isis_create(struct nb_cb_create_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit = NULL; + const char *area_tag = yang_dnode_get_string(args->dnode, "area-tag"); + + switch (args->event) { + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_VALIDATE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + circuit = isis_circuit_new(ifp, area_tag); + nb_running_set_entry(args->dnode, circuit); + break; + } + + return NB_OK; +} + +int lib_interface_isis_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_unset_entry(args->dnode); + + isis_circuit_del(circuit); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/area-tag + */ +int lib_interface_isis_area_tag_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event == NB_EV_VALIDATE) { + circuit = nb_running_get_entry_non_rec(lyd_parent(args->dnode), + NULL, false); + if (circuit) { + snprintf(args->errmsg, args->errmsg_len, + "Changing area tag is not allowed"); + return NB_ERR_VALIDATION; + } + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/circuit-type + */ +int lib_interface_isis_circuit_type_modify(struct nb_cb_modify_args *args) +{ + int circ_type = yang_dnode_get_enum(args->dnode, NULL); + struct isis_circuit *circuit; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->is_type_config = circ_type; + if (!circuit->area || circuit->area->is_type == IS_LEVEL_1_AND_2) + isis_circuit_is_type_set(circuit, circ_type); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv4-routing + */ +int lib_interface_isis_ipv4_routing_modify(struct nb_cb_modify_args *args) +{ + bool ipv4, ipv6; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + ipv4 = yang_dnode_get_bool(args->dnode, NULL); + ipv6 = yang_dnode_get_bool(args->dnode, "../ipv6-routing"); + isis_circuit_af_set(circuit, ipv4, ipv6); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/ipv6-routing + */ +int lib_interface_isis_ipv6_routing_modify(struct nb_cb_modify_args *args) +{ + bool ipv4, ipv6; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + ipv4 = yang_dnode_get_bool(args->dnode, "../ipv4-routing"); + ipv6 = yang_dnode_get_bool(args->dnode, NULL); + isis_circuit_af_set(circuit, ipv4, ipv6); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring + */ +void lib_interface_isis_bfd_monitoring_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct isis_circuit *circuit; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + isis_bfd_circuit_cmd(circuit); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/enabled + */ +int lib_interface_isis_bfd_monitoring_enabled_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->bfd_config.enabled = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/bfd-monitoring/profile + */ +int lib_interface_isis_bfd_monitoring_profile_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_TMP, circuit->bfd_config.profile); + circuit->bfd_config.profile = + XSTRDUP(MTYPE_TMP, yang_dnode_get_string(args->dnode, NULL)); + + return NB_OK; +} + +int lib_interface_isis_bfd_monitoring_profile_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_TMP, circuit->bfd_config.profile); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-1 + */ +int lib_interface_isis_csnp_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->csnp_interval[0] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/csnp-interval/level-2 + */ +int lib_interface_isis_csnp_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->csnp_interval[1] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-1 + */ +int lib_interface_isis_psnp_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->psnp_interval[0] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/psnp-interval/level-2 + */ +int lib_interface_isis_psnp_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->psnp_interval[1] = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/padding + */ +int lib_interface_isis_hello_padding_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->pad_hellos = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-1 + */ +int lib_interface_isis_hello_interval_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + uint32_t interval; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + interval = yang_dnode_get_uint32(args->dnode, NULL); + circuit->hello_interval[0] = interval; + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/interval/level-2 + */ +int lib_interface_isis_hello_interval_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + uint32_t interval; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + interval = yang_dnode_get_uint32(args->dnode, NULL); + circuit->hello_interval[1] = interval; + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-1 + */ +int lib_interface_isis_hello_multiplier_level_1_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + uint16_t multi; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + multi = yang_dnode_get_uint16(args->dnode, NULL); + circuit->hello_multiplier[0] = multi; + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/hello/multiplier/level-2 + */ +int lib_interface_isis_hello_multiplier_level_2_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + uint16_t multi; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + multi = yang_dnode_get_uint16(args->dnode, NULL); + circuit->hello_multiplier[1] = multi; + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-1 + */ +int lib_interface_isis_metric_level_1_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + unsigned int met; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + met = yang_dnode_get_uint32(args->dnode, NULL); + isis_circuit_metric_set(circuit, IS_LEVEL_1, met); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/metric/level-2 + */ +int lib_interface_isis_metric_level_2_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + unsigned int met; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + met = yang_dnode_get_uint32(args->dnode, NULL); + isis_circuit_metric_set(circuit, IS_LEVEL_2, met); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-1 + */ +int lib_interface_isis_priority_level_1_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->priority[0] = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/priority/level-2 + */ +int lib_interface_isis_priority_level_2_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->priority[1] = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/network-type + */ +int lib_interface_isis_network_type_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + int net_type = yang_dnode_get_enum(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + circuit = nb_running_get_entry(args->dnode, NULL, false); + if (!circuit) + break; + if (circuit->circ_type == CIRCUIT_T_LOOPBACK) { + snprintf( + args->errmsg, args->errmsg_len, + "Cannot change network type on loopback interface"); + return NB_ERR_VALIDATION; + } + if (net_type == CIRCUIT_T_BROADCAST + && circuit->state == C_STATE_UP + && !if_is_broadcast(circuit->interface)) { + snprintf( + args->errmsg, args->errmsg_len, + "Cannot configure non-broadcast interface for broadcast operation"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(args->dnode, NULL, true); + isis_circuit_circ_type_set(circuit, net_type); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/passive + */ +int lib_interface_isis_passive_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + bool passive = yang_dnode_get_bool(args->dnode, NULL); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + isis_circuit_passive_set(circuit, passive); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/password + */ +int lib_interface_isis_password_create(struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int lib_interface_isis_password_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + isis_circuit_passwd_unset(circuit); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password + */ +int lib_interface_isis_password_password_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + const char *password; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + password = yang_dnode_get_string(args->dnode, NULL); + circuit = nb_running_get_entry(args->dnode, NULL, true); + + isis_circuit_passwd_set(circuit, circuit->passwd.type, password); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/password/password-type + */ +int lib_interface_isis_password_password_type_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + uint8_t pass_type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pass_type = yang_dnode_get_enum(args->dnode, NULL); + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->passwd.type = pass_type; + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/disable-three-way-handshake + */ +int lib_interface_isis_disable_three_way_handshake_modify( + struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->disable_threeway_adj = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +static int lib_interface_isis_multi_topology_common( + enum nb_event event, const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len, uint16_t mtid) +{ + struct isis_circuit *circuit; + bool value; + + switch (event) { + case NB_EV_VALIDATE: + circuit = nb_running_get_entry(dnode, NULL, false); + if (circuit && circuit->area && circuit->area->oldmetric) { + snprintf( + errmsg, errmsg_len, + "Multi topology IS-IS can only be used with wide metrics"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(dnode, NULL, true); + value = yang_dnode_get_bool(dnode, NULL); + isis_circuit_mt_enabled_set(circuit, mtid, value); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/standard + */ +int lib_interface_isis_multi_topology_standard_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_STANDARD); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-multicast + */ +int lib_interface_isis_multi_topology_ipv4_multicast_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV4_MULTICAST); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv4-management + */ +int lib_interface_isis_multi_topology_ipv4_management_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV4_MGMT); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-unicast + */ +int lib_interface_isis_multi_topology_ipv6_unicast_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV6_UNICAST); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-multicast + */ +int lib_interface_isis_multi_topology_ipv6_multicast_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV6_MULTICAST); +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-management + */ +int lib_interface_isis_multi_topology_ipv6_management_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV6_MGMT); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/multi-topology/ipv6-dstsrc + */ +int lib_interface_isis_multi_topology_ipv6_dstsrc_modify( + struct nb_cb_modify_args *args) +{ + return lib_interface_isis_multi_topology_common( + args->event, args->dnode, args->errmsg, args->errmsg_len, + ISIS_MT_IPV6_DSTSRC); +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/ldp-sync + */ +int lib_interface_isis_mpls_ldp_sync_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + struct ldp_sync_info *ldp_sync_info; + bool ldp_sync_enable; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(args->dnode, NULL, true); + ldp_sync_enable = yang_dnode_get_bool(args->dnode, NULL); + + ldp_sync_info = circuit->ldp_sync_info; + + SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG); + ldp_sync_info->enabled = ldp_sync_enable; + + if (circuit->area) { + if (ldp_sync_enable) + isis_if_ldp_sync_enable(circuit); + else + isis_if_ldp_sync_disable(circuit); + } + break; + } + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-isisd:isis/mpls/holddown + */ +int lib_interface_isis_mpls_holddown_modify(struct nb_cb_modify_args *args) +{ + struct isis_circuit *circuit; + struct ldp_sync_info *ldp_sync_info; + uint16_t holddown; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(args->dnode, NULL, true); + holddown = yang_dnode_get_uint16(args->dnode, NULL); + + ldp_sync_info = circuit->ldp_sync_info; + + SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN); + ldp_sync_info->holddown = holddown; + break; + } + return NB_OK; +} + +int lib_interface_isis_mpls_holddown_destroy(struct nb_cb_destroy_args *args) +{ + struct isis_circuit *circuit; + struct ldp_sync_info *ldp_sync_info; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + circuit = nb_running_get_entry(args->dnode, NULL, true); + ldp_sync_info = circuit->ldp_sync_info; + + UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN); + + if (circuit->area) + isis_if_set_ldp_sync_holddown(circuit); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/enable + */ +int lib_interface_isis_fast_reroute_level_1_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->lfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->lfa_protection[0]) + area->lfa_protected_links[0]++; + else { + assert(area->lfa_protected_links[0] > 0); + area->lfa_protected_links[0]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/lfa/exclude-interface + */ +int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + const char *exclude_ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + exclude_ifname = yang_dnode_get_string(args->dnode, NULL); + + isis_lfa_excluded_iface_add(circuit, ISIS_LEVEL1, exclude_ifname); + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int lib_interface_isis_fast_reroute_level_1_lfa_exclude_interface_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + const char *exclude_ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + exclude_ifname = yang_dnode_get_string(args->dnode, NULL); + + isis_lfa_excluded_iface_delete(circuit, ISIS_LEVEL1, exclude_ifname); + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/enable + */ +int lib_interface_isis_fast_reroute_level_1_remote_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->rlfa_protection[0]) + area->rlfa_protected_links[0]++; + else { + assert(area->rlfa_protected_links[0] > 0); + area->rlfa_protected_links[0]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/remote-lfa/maximum-metric + */ +int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_max_metric[0] = yang_dnode_get_uint32(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int lib_interface_isis_fast_reroute_level_1_remote_lfa_maximum_metric_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_max_metric[0] = 0; + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/enable + */ +int lib_interface_isis_fast_reroute_level_1_ti_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_protection[0] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->tilfa_protection[0]) + area->tilfa_protected_links[0]++; + else { + assert(area->tilfa_protected_links[0] > 0); + area->tilfa_protected_links[0]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/node-protection + */ +int lib_interface_isis_fast_reroute_level_1_ti_lfa_node_protection_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_node_protection[0] = + yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-1/ti-lfa/link-fallback + */ +int lib_interface_isis_fast_reroute_level_1_ti_lfa_link_fallback_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_link_fallback[0] = + yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/enable + */ +int lib_interface_isis_fast_reroute_level_2_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->lfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->lfa_protection[1]) + area->lfa_protected_links[1]++; + else { + assert(area->lfa_protected_links[1] > 0); + area->lfa_protected_links[1]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/lfa/exclude-interface + */ +int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_create( + struct nb_cb_create_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + const char *exclude_ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + exclude_ifname = yang_dnode_get_string(args->dnode, NULL); + + isis_lfa_excluded_iface_add(circuit, ISIS_LEVEL2, exclude_ifname); + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int lib_interface_isis_fast_reroute_level_2_lfa_exclude_interface_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + const char *exclude_ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + exclude_ifname = yang_dnode_get_string(args->dnode, NULL); + + isis_lfa_excluded_iface_delete(circuit, ISIS_LEVEL2, exclude_ifname); + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/enable + */ +int lib_interface_isis_fast_reroute_level_2_remote_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->rlfa_protection[1]) + area->rlfa_protected_links[1]++; + else { + assert(area->rlfa_protected_links[1] > 0); + area->rlfa_protected_links[1]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/remote-lfa/maximum-metric + */ +int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_max_metric[1] = yang_dnode_get_uint32(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +int lib_interface_isis_fast_reroute_level_2_remote_lfa_maximum_metric_destroy( + struct nb_cb_destroy_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->rlfa_max_metric[1] = 0; + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/enable + */ +int lib_interface_isis_fast_reroute_level_2_ti_lfa_enable_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_protection[1] = yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) { + if (circuit->tilfa_protection[1]) + area->tilfa_protected_links[1]++; + else { + assert(area->tilfa_protected_links[1] > 0); + area->tilfa_protected_links[1]--; + } + + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/node-protection + */ +int lib_interface_isis_fast_reroute_level_2_ti_lfa_node_protection_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_node_protection[1] = + yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-isisd:isis/fast-reroute/level-2/ti-lfa/link-fallback + */ +int lib_interface_isis_fast_reroute_level_2_ti_lfa_link_fallback_modify( + struct nb_cb_modify_args *args) +{ + struct isis_area *area; + struct isis_circuit *circuit; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + circuit = nb_running_get_entry(args->dnode, NULL, true); + circuit->tilfa_link_fallback[1] = + yang_dnode_get_bool(args->dnode, NULL); + + area = circuit->area; + if (area) + lsp_regenerate_schedule(area, area->is_type, 0); + + return NB_OK; +} diff --git a/isisd/isis_nb_notifications.c b/isisd/isis_nb_notifications.c new file mode 100644 index 0000000..5a1e312 --- /dev/null +++ b/isisd/isis_nb_notifications.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#include + +#include "northbound.h" +#include "log.h" + +#include "isisd/isisd.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" + +DEFINE_HOOK(isis_hook_lsp_too_large, + (const struct isis_circuit *circuit, uint32_t pdu_size, + const uint8_t *lsp_id), + (circuit, pdu_size, lsp_id)); +DEFINE_HOOK(isis_hook_corrupted_lsp, (const struct isis_area *area), (area)); +DEFINE_HOOK(isis_hook_lsp_exceed_max, + (const struct isis_area *area, const uint8_t *lsp_id), + (area, lsp_id)); +DEFINE_HOOK(isis_hook_max_area_addr_mismatch, + (const struct isis_circuit *circuit, uint8_t max_addrs, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, max_addrs, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_authentication_type_failure, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_authentication_failure, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_adj_state_change, (const struct isis_adjacency *adj), + (adj)); +DEFINE_HOOK(isis_hook_reject_adjacency, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_area_mismatch, + (const struct isis_circuit *circuit, const char *raw_pdu, + size_t raw_pdu_len), + (circuit, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_id_len_mismatch, + (const struct isis_circuit *circuit, uint8_t rcv_id_len, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, rcv_id_len, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_version_skew, + (const struct isis_circuit *circuit, uint8_t version, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, version, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_lsp_error, + (const struct isis_circuit *circuit, const uint8_t *lsp_id, + const char *raw_pdu, size_t raw_pdu_len), + (circuit, lsp_id, raw_pdu, raw_pdu_len)); +DEFINE_HOOK(isis_hook_seqno_skipped, + (const struct isis_circuit *circuit, const uint8_t *lsp_id), + (circuit, lsp_id)); +DEFINE_HOOK(isis_hook_own_lsp_purge, + (const struct isis_circuit *circuit, const uint8_t *lsp_id), + (circuit, lsp_id)); + + +/* + * Helper functions. + */ +static void notif_prep_instance_hdr(const char *xpath, + const struct isis_area *area, + const char *routing_instance, + struct list *args) +{ + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-instance", xpath); + data = yang_data_new_string(xpath_arg, routing_instance); + listnode_add(args, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/routing-protocol-name", + xpath); + data = yang_data_new_string(xpath_arg, area->area_tag); + listnode_add(args, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/isis-level", xpath); + data = yang_data_new_enum(xpath_arg, area->is_type); + listnode_add(args, data); +} + +static void notif_prepr_iface_hdr(const char *xpath, + const struct isis_circuit *circuit, + struct list *args) +{ + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); + data = yang_data_new_string(xpath_arg, circuit->interface->name); + listnode_add(args, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-level", xpath); + data = yang_data_new_enum(xpath_arg, circuit->is_type); + listnode_add(args, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/extended-circuit-id", xpath); + /* we do not seem to have the extended version of the circuit_id */ + data = yang_data_new_uint32(xpath_arg, (uint32_t)circuit->circuit_id); + listnode_add(args, data); +} + +/* + * XPath: /frr-isisd:database-overload + */ +void isis_notif_db_overload(const struct isis_area *area, bool overload) +{ + const char *xpath = "/frr-isisd:database-overload"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/overload", xpath); + data = yang_data_new_enum(xpath_arg, !!overload); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:lsp-too-large + */ +void isis_notif_lsp_too_large(const struct isis_circuit *circuit, + uint32_t pdu_size, const uint8_t *lsp_id) +{ + const char *xpath = "/frr-isisd:lsp-too-large"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-size", xpath); + data = yang_data_new_uint32(xpath_arg, pdu_size); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + hook_call(isis_hook_lsp_too_large, circuit, pdu_size, lsp_id); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:if-state-change + */ +void isis_notif_if_state_change(const struct isis_circuit *circuit, bool down) +{ + const char *xpath = "/frr-isisd:if-state-change"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath); + data = yang_data_new_enum(xpath_arg, !!down); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:corrupted-lsp-detected + */ +void isis_notif_corrupted_lsp(const struct isis_area *area, + const uint8_t *lsp_id) +{ + const char *xpath = "/frr-isisd:corrupted-lsp-detected"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + hook_call(isis_hook_corrupted_lsp, area); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:attempt-to-exceed-max-sequence + */ +void isis_notif_lsp_exceed_max(const struct isis_area *area, + const uint8_t *lsp_id) +{ + const char *xpath = "/frr-isisd:attempt-to-exceed-max-sequence"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + hook_call(isis_hook_lsp_exceed_max, area, lsp_id); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:max-area-addresses-mismatch + */ +void isis_notif_max_area_addr_mismatch(const struct isis_circuit *circuit, + uint8_t max_area_addrs, + const char *raw_pdu, size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:max-area-addresses-mismatch"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/max-area-addresses", xpath); + data = yang_data_new_uint8(xpath_arg, max_area_addrs); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_max_area_addr_mismatch, circuit, max_area_addrs, + raw_pdu, raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:authentication-type-failure + */ +void isis_notif_authentication_type_failure(const struct isis_circuit *circuit, + const char *raw_pdu, + size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:authentication-type-failure"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_authentication_type_failure, circuit, raw_pdu, + raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:authentication-failure + */ +void isis_notif_authentication_failure(const struct isis_circuit *circuit, + const char *raw_pdu, size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:authentication-failure"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_authentication_failure, circuit, raw_pdu, + raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:adjacency-state-change + */ +void isis_notif_adj_state_change(const struct isis_adjacency *adj, + int new_state, const char *reason) +{ + const char *xpath = "/frr-isisd:adjacency-state-change"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_circuit *circuit = adj->circuit; + struct isis_area *area = circuit->area; + struct isis_dynhn *dyn = dynhn_find_by_id(circuit->isis, adj->sysid); + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + if (dyn) { + snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor", xpath); + data = yang_data_new_string(xpath_arg, dyn->hostname); + listnode_add(arguments, data); + } + snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->sysid); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/state", xpath); + data = yang_data_new_string(xpath_arg, isis_adj_yang_state(new_state)); + listnode_add(arguments, data); + if (new_state == ISIS_ADJ_DOWN) { + snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath); + data = yang_data_new_string(xpath_arg, reason); + listnode_add(arguments, data); + } + + hook_call(isis_hook_adj_state_change, adj); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:rejected-adjacency + */ +void isis_notif_reject_adjacency(const struct isis_circuit *circuit, + const char *reason, const char *raw_pdu, + size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:rejected-adjacency"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/reason", xpath); + data = yang_data_new_string(xpath_arg, reason); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_reject_adjacency, circuit, raw_pdu, raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:area-mismatch + */ +void isis_notif_area_mismatch(const struct isis_circuit *circuit, + const char *raw_pdu, size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:area-mismatch"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_area_mismatch, circuit, raw_pdu, raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:lsp-received + */ +void isis_notif_lsp_received(const struct isis_circuit *circuit, + const uint8_t *lsp_id, uint32_t seqno, + uint32_t timestamp, const char *sys_id) +{ + const char *xpath = "/frr-isisd:lsp-received"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath); + data = yang_data_new_uint32(xpath_arg, seqno); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/received-timestamp", xpath); + data = yang_data_new_uint32(xpath_arg, timestamp); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/neighbor-system-id", xpath); + data = yang_data_new_string(xpath_arg, sys_id); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:lsp-generation + */ +void isis_notif_lsp_gen(const struct isis_area *area, const uint8_t *lsp_id, + uint32_t seqno, uint32_t timestamp) +{ + const char *xpath = "/frr-isisd:lsp-generation"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/sequence", xpath); + data = yang_data_new_uint32(xpath_arg, seqno); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/send-timestamp", xpath); + data = yang_data_new_uint32(xpath_arg, timestamp); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:id-len-mismatch + */ +void isis_notif_id_len_mismatch(const struct isis_circuit *circuit, + uint8_t rcv_id_len, const char *raw_pdu, + size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:id-len-mismatch"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/pdu-field-len", xpath); + data = yang_data_new_uint8(xpath_arg, rcv_id_len); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_id_len_mismatch, circuit, rcv_id_len, raw_pdu, + raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:version-skew + */ +void isis_notif_version_skew(const struct isis_circuit *circuit, + uint8_t version, const char *raw_pdu, + size_t raw_pdu_len) +{ + const char *xpath = "/frr-isisd:version-skew"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/protocol-version", xpath); + data = yang_data_new_uint8(xpath_arg, version); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + + hook_call(isis_hook_version_skew, circuit, version, raw_pdu, + raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:lsp-error-detected + */ +void isis_notif_lsp_error(const struct isis_circuit *circuit, + const uint8_t *lsp_id, const char *raw_pdu, + size_t raw_pdu_len, + __attribute__((unused)) uint32_t offset, + __attribute__((unused)) uint8_t tlv_type) +{ + const char *xpath = "/frr-isisd:lsp-error-detected"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/raw-pdu", xpath); + data = yang_data_new_binary(xpath_arg, raw_pdu, raw_pdu_len); + listnode_add(arguments, data); + /* ignore offset and tlv_type which cannot be set properly */ + + hook_call(isis_hook_lsp_error, circuit, lsp_id, raw_pdu, raw_pdu_len); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:sequence-number-skipped + */ +void isis_notif_seqno_skipped(const struct isis_circuit *circuit, + const uint8_t *lsp_id) +{ + const char *xpath = "/frr-isisd:sequence-number-skipped"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + hook_call(isis_hook_seqno_skipped, circuit, lsp_id); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-isisd:own-lsp-purge + */ +void isis_notif_own_lsp_purge(const struct isis_circuit *circuit, + const uint8_t *lsp_id) +{ + const char *xpath = "/frr-isisd:own-lsp-purge"; + struct list *arguments = yang_data_list_new(); + char xpath_arg[XPATH_MAXLEN]; + char xpath_value[ISO_SYSID_STRLEN]; + struct yang_data *data; + struct isis_area *area = circuit->area; + + notif_prep_instance_hdr(xpath, area, "default", arguments); + notif_prepr_iface_hdr(xpath, circuit, arguments); + snprintf(xpath_arg, sizeof(xpath_arg), "%s/lsp-id", xpath); + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pLS", lsp_id); + data = yang_data_new_string(xpath_arg, xpath_value); + listnode_add(arguments, data); + + hook_call(isis_hook_own_lsp_purge, circuit, lsp_id); + + nb_notification_send(xpath, arguments); +} diff --git a/isisd/isis_nb_state.c b/isisd/isis_nb_state.c new file mode 100644 index 0000000..b7c33ed --- /dev/null +++ b/isisd/isis_nb_state.c @@ -0,0 +1,630 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Volta Networks + * Emanuele Di Pascale + */ + +#include + +#include "northbound.h" +#include "linklist.h" + +#include "isisd/isisd.h" +#include "isisd/isis_nb.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_misc.h" + +/* + * XPath: /frr-interface:lib/interface/state/frr-isisd:isis + */ +struct yang_data * +lib_interface_state_isis_get_elem(struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit || !circuit->area) + return NULL; + + return yang_data_new(args->xpath, NULL); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency + */ +const void *lib_interface_state_isis_adjacencies_adjacency_get_next( + struct nb_cb_get_next_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + struct isis_adjacency *adj = NULL, *adj_next = NULL; + struct list *list; + struct listnode *node, *node_next; + + /* Get first adjacency. */ + if (args->list_entry == NULL) { + ifp = (struct interface *)args->parent_list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) { + struct list *adjdb; + + adjdb = circuit->u.bc.adjdb[level - 1]; + if (adjdb) { + adj = listnode_head(adjdb); + if (adj) + break; + } + } + break; + case CIRCUIT_T_P2P: + adj = circuit->u.p2p.neighbor; + break; + default: + break; + } + + return adj; + } + + /* Get next adjacency. */ + adj = (struct isis_adjacency *)args->list_entry; + circuit = adj->circuit; + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + list = circuit->u.bc.adjdb[adj->level - 1]; + node = listnode_lookup(list, adj); + node_next = listnextnode(node); + if (node_next) + adj_next = listgetdata(node_next); + else if (adj->level == ISIS_LEVEL1) { + /* + * Once we finish the L1 adjacencies, move to the L2 + * adjacencies list. + */ + list = circuit->u.bc.adjdb[ISIS_LEVEL2 - 1]; + adj_next = listnode_head(list); + } + break; + case CIRCUIT_T_P2P: + /* P2P circuits have at most one adjacency. */ + default: + break; + } + + return adj_next; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sys-type + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_sys_type_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + + return yang_data_new_enum(args->xpath, adj->level); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-sysid + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_sysid_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + char xpath_value[ISO_SYSID_STRLEN]; + + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->sysid); + + return yang_data_new_string(args->xpath, xpath_value); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-extended-circuit-id + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_extended_circuit_id_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + + return yang_data_new_uint32(args->xpath, adj->circuit->circuit_id); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-snpa + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_snpa_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + char xpath_value[ISO_SYSID_STRLEN]; + + snprintfrr(xpath_value, ISO_SYSID_STRLEN, "%pSY", adj->snpa); + + return yang_data_new_string(args->xpath, xpath_value); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/hold-timer + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_hold_timer_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + + return yang_data_new_uint16(args->xpath, adj->hold_time); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/neighbor-priority + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_neighbor_priority_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + + return yang_data_new_uint8(args->xpath, adj->prio[adj->level - 1]); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/state + */ +struct yang_data *lib_interface_state_isis_adjacencies_adjacency_state_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct isis_adjacency *adj = args->list_entry; + + return yang_data_new_string(args->xpath, + isis_adj_yang_state(adj->adj_state)); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid + */ +const void * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_get_next( + struct nb_cb_get_next_args *args) +{ + const struct isis_adjacency *adj = args->parent_list_entry; + const struct sr_adjacency *sra = args->list_entry, *sra_next = NULL; + struct listnode *node, *node_next; + + if (args->list_entry == NULL) + sra_next = listnode_head(adj->adj_sids); + else { + node = listnode_lookup(adj->adj_sids, sra); + node_next = listnextnode(node); + if (node_next) + sra_next = listgetdata(node_next); + } + + return sra_next; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/af + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_af_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + /* Adjacency SID is not published with circuit type Broadcast */ + return NULL; + case CIRCUIT_T_P2P: + return yang_data_new_uint8(args->xpath, sra->u.adj_sid->family); + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/value + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_value_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + /* Adjacency SID is not published with circuit type Broadcast */ + return NULL; + case CIRCUIT_T_P2P: + return yang_data_new_uint32(args->xpath, sra->u.adj_sid->sid); + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/weight + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_weight_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + /* Adjacency SID is not published with circuit type Broadcast */ + return NULL; + case CIRCUIT_T_P2P: + return yang_data_new_uint8(args->xpath, sra->u.adj_sid->weight); + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/adjacency-sids/adjacency-sid/protection-requested + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_adjacency_sids_adjacency_sid_protection_requested_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + /* Adjacency SID is not published with circuit type Broadcast */ + return NULL; + case CIRCUIT_T_P2P: + return yang_data_new_bool(args->xpath, + sra->u.adj_sid->flags & + EXT_SUBTLV_LINK_ADJ_SID_BFLG); + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid + */ +const void * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_get_next( + struct nb_cb_get_next_args *args) +{ + const struct isis_adjacency *adj = args->parent_list_entry; + const struct sr_adjacency *sra = args->list_entry, *sra_next = NULL; + struct listnode *node, *node_next; + + if (args->list_entry == NULL) + sra_next = listnode_head(adj->adj_sids); + else { + node = listnode_lookup(adj->adj_sids, sra); + node_next = listnextnode(node); + if (node_next) + sra_next = listgetdata(node_next); + } + + return sra_next; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/af + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_af_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + return yang_data_new_uint8(args->xpath, + sra->u.ladj_sid->family); + case CIRCUIT_T_P2P: + /* LAN adjacency SID is not published with circuit type P2P */ + return NULL; + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/value + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_value_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + return yang_data_new_uint32(args->xpath, sra->u.ladj_sid->sid); + case CIRCUIT_T_P2P: + /* LAN adjacency SID is not published with circuit type P2P */ + return NULL; + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/weight + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_weight_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + return yang_data_new_uint8(args->xpath, + sra->u.ladj_sid->weight); + case CIRCUIT_T_P2P: + /* LAN adjacency SID is not published with circuit type P2P */ + return NULL; + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/adjacencies/adjacency/lan-adjacency-sids/lan-adjacency-sid/protection-requested + */ +struct yang_data * +lib_interface_state_isis_adjacencies_adjacency_lan_adjacency_sids_lan_adjacency_sid_protection_requested_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct sr_adjacency *sra = args->list_entry; + + switch (sra->adj->circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + return yang_data_new_bool(args->xpath, + sra->u.ladj_sid->flags & + EXT_SUBTLV_LINK_ADJ_SID_BFLG); + case CIRCUIT_T_P2P: + /* LAN adjacency SID is not published with circuit type P2P */ + return NULL; + } + + return NULL; +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-changes + */ +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_changes_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->adj_state_changes); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-number + */ +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_number_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + struct isis_adjacency *adj; + struct listnode *node; + uint32_t total = 0; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + /* + * TODO: keep track of the number of adjacencies instead of calculating + * it on demand. + */ + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + for (ALL_LIST_ELEMENTS_RO( + circuit->u.bc.adjdb[level - 1], node, adj)) + total++; + } + break; + case CIRCUIT_T_P2P: + adj = circuit->u.p2p.neighbor; + if (adj) + total = 1; + break; + default: + break; + } + + return yang_data_new_uint32(args->xpath, total); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/init-fails + */ +struct yang_data *lib_interface_state_isis_event_counters_init_fails_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->init_failures); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/adjacency-rejects + */ +struct yang_data * +lib_interface_state_isis_event_counters_adjacency_rejects_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->rej_adjacencies); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/id-len-mismatch + */ +struct yang_data * +lib_interface_state_isis_event_counters_id_len_mismatch_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->id_len_mismatches); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/max-area-addresses-mismatch + */ +struct yang_data * +lib_interface_state_isis_event_counters_max_area_addresses_mismatch_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, + circuit->max_area_addr_mismatches); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-type-fails + */ +struct yang_data * +lib_interface_state_isis_event_counters_authentication_type_fails_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->auth_type_failures); +} + +/* + * XPath: + * /frr-interface:lib/interface/state/frr-isisd:isis/event-counters/authentication-fails + */ +struct yang_data * +lib_interface_state_isis_event_counters_authentication_fails_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct interface *ifp; + struct isis_circuit *circuit; + + ifp = (struct interface *)args->list_entry; + if (!ifp) + return NULL; + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return NULL; + + return yang_data_new_uint32(args->xpath, circuit->auth_failures); +} diff --git a/isisd/isis_network.h b/isisd/isis_network.h new file mode 100644 index 0000000..31cad32 --- /dev/null +++ b/isisd/isis_network.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_network.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + + +#ifndef _ZEBRA_ISIS_NETWORK_H +#define _ZEBRA_ISIS_NETWORK_H + +extern uint8_t ALL_L1_ISYSTEMS[]; +extern uint8_t ALL_L2_ISYSTEMS[]; + +int isis_sock_init(struct isis_circuit *circuit); + +int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa); +int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa); +int isis_send_pdu_bcast(struct isis_circuit *circuit, int level); +int isis_send_pdu_p2p(struct isis_circuit *circuit, int level); + +#endif /* _ZEBRA_ISIS_NETWORK_H */ diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c new file mode 100644 index 0000000..23238d3 --- /dev/null +++ b/isisd/isis_pdu.c @@ -0,0 +1,2628 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_pdu.c + * PDU processing + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "memory.h" +#include "frrevent.h" +#include "linklist.h" +#include "log.h" +#include "stream.h" +#include "vty.h" +#include "hash.h" +#include "prefix.h" +#include "if.h" +#include "checksum.h" +#include "md5.h" +#include "lib_errors.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_dr.h" +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/iso_checksum.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_events.h" +#include "isisd/isis_te.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_errors.h" +#include "isisd/fabricd.h" +#include "isisd/isis_tx_queue.h" +#include "isisd/isis_pdu_counter.h" +#include "isisd/isis_nb.h" + +static int ack_lsp(struct isis_lsp_hdr *hdr, struct isis_circuit *circuit, + int level) +{ + unsigned long lenp; + int retval; + uint16_t length; + uint8_t pdu_type = + (level == IS_LEVEL_1) ? L1_PARTIAL_SEQ_NUM : L2_PARTIAL_SEQ_NUM; + + isis_circuit_stream(circuit, &circuit->snd_stream); + + fill_fixed_hdr(pdu_type, circuit->snd_stream); + + lenp = stream_get_endp(circuit->snd_stream); + + stream_putw(circuit->snd_stream, 0); /* PDU length */ + stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN); + stream_putc(circuit->snd_stream, circuit->idx); + stream_putc(circuit->snd_stream, 9); /* code */ + stream_putc(circuit->snd_stream, 16); /* len */ + + stream_putw(circuit->snd_stream, hdr->rem_lifetime); + stream_put(circuit->snd_stream, hdr->lsp_id, ISIS_SYS_ID_LEN + 2); + stream_putl(circuit->snd_stream, hdr->seqno); + stream_putw(circuit->snd_stream, hdr->checksum); + + length = (uint16_t)stream_get_endp(circuit->snd_stream); + /* Update PDU length */ + stream_putw_at(circuit->snd_stream, lenp, length); + + pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); + retval = circuit->tx(circuit, level); + if (retval != ISIS_OK) + flog_err(EC_ISIS_PACKET, + "ISIS-Upd (%s): Send L%d LSP PSNP on %s failed", + circuit->area->area_tag, level, + circuit->interface->name); + + return retval; +} + +/* + * RECEIVE SIDE + */ + +struct iih_info { + struct isis_circuit *circuit; + uint8_t *ssnpa; + int level; + + uint8_t circ_type; + uint8_t sys_id[ISIS_SYS_ID_LEN]; + uint16_t holdtime; + uint16_t pdu_len; + + uint8_t circuit_id; + + uint8_t priority; + uint8_t dis[ISIS_SYS_ID_LEN + 1]; + + bool v4_usable; + bool v6_usable; + + struct isis_tlvs *tlvs; + int calculated_type; +}; + +static int process_p2p_hello(struct iih_info *iih) +{ + struct isis_threeway_adj *tw_adj = iih->tlvs->threeway_adj; + + if (tw_adj) { + if (tw_adj->state > ISIS_THREEWAY_DOWN) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) with invalid three-way state: %d", + iih->circuit->area->area_tag, + iih->circuit->interface->name, + tw_adj->state); + } + return ISIS_WARNING; + } + + if (tw_adj->neighbor_set + && (memcmp(tw_adj->neighbor_id, iih->circuit->isis->sysid, + ISIS_SYS_ID_LEN) + || tw_adj->neighbor_circuit_id + != (uint32_t)iih->circuit->idx)) { + + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s) which lists IS/Circuit different from us as neighbor.", + iih->circuit->area->area_tag, + iih->circuit->interface->name); + } + + return ISIS_WARNING; + } + } + + /* + * My interpertation of the ISO, if no adj exists we will create one for + * the circuit + */ + struct isis_adjacency *adj = iih->circuit->u.p2p.neighbor; + /* If an adjacency exists, check it is with the source of the hello + * packets */ + if (((iih->circuit->area->is_type == IS_LEVEL_1) && + ((iih->circuit->is_type_config == IS_LEVEL_1_AND_2) || + (iih->circuit->is_type_config == IS_LEVEL_1))) || + ((iih->circuit->area->is_type == IS_LEVEL_1_AND_2) && + (iih->circuit->is_type_config == IS_LEVEL_1) && + ((iih->circ_type == IS_LEVEL_1) || + (iih->circ_type == IS_LEVEL_1_AND_2))) || + ((iih->circuit->area->is_type == IS_LEVEL_1_AND_2) && + (iih->circuit->is_type_config == IS_LEVEL_1_AND_2) && + (iih->circ_type == IS_LEVEL_1))) { + if (!isis_tlvs_area_addresses_match(iih->tlvs, + iih->circuit->area + ->area_addrs)) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %u, length %u", + iih->circuit->area->area_tag, + iih->circuit->interface->name, + circuit_t2string( + iih->circuit->is_type), + iih->circuit->circuit_id, + iih->pdu_len); + } + + return ISIS_WARNING; + } + + iih->calculated_type = IS_LEVEL_1; + + } + + else if (((iih->circuit->area->is_type == IS_LEVEL_2) && + ((iih->circuit->is_type_config == IS_LEVEL_1_AND_2) || + (iih->circuit->is_type_config == IS_LEVEL_2))) || + ((iih->circuit->area->is_type == IS_LEVEL_1_AND_2) && + (iih->circuit->is_type_config == IS_LEVEL_2) && + ((iih->circ_type == IS_LEVEL_2) || + (iih->circ_type == IS_LEVEL_1_AND_2))) || + ((iih->circuit->area->is_type == IS_LEVEL_1_AND_2) && + (iih->circuit->is_type_config == IS_LEVEL_1_AND_2) && + (iih->circ_type == IS_LEVEL_2))) { + iih->calculated_type = IS_LEVEL_2; + } + + else if ((iih->circuit->area->is_type == IS_LEVEL_1_AND_2) && + (iih->circuit->is_type_config == IS_LEVEL_1_AND_2) && + (iih->circ_type == IS_LEVEL_1_AND_2)) { + iih->calculated_type = IS_LEVEL_1_AND_2; + + if (!isis_tlvs_area_addresses_match(iih->tlvs, + iih->circuit->area + ->area_addrs)) { + iih->calculated_type = IS_LEVEL_2; + } + } + + else { + if (IS_DEBUG_ADJ_PACKETS) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %u, length %u", + iih->circuit->area->area_tag, + iih->circuit->interface->name, + circuit_t2string( + iih->circuit->is_type), + iih->circuit->circuit_id, + iih->pdu_len); + } + } + return ISIS_WARNING; + } + + if (adj) { + if (memcmp(iih->sys_id, adj->sysid, ISIS_SYS_ID_LEN)) { + zlog_debug( + "hello source and adjacency do not match, set adj down"); + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, + "adj do not exist"); + return ISIS_OK; + } + } + if (!adj || adj->level != iih->calculated_type) { + if (!adj) { + adj = isis_new_adj(iih->sys_id, NULL, + iih->calculated_type, iih->circuit); + + } else { + adj->level = iih->calculated_type; + } + iih->circuit->u.p2p.neighbor = adj; + /* Build lsp with the new neighbor entry when a new + * adjacency is formed. Set adjacency circuit type to + * IIH PDU header circuit type before lsp is regenerated + * when an adjacency is up. This will result in the new + * adjacency entry getting added to the lsp tlv neighbor list. + */ + adj->circuit_t = iih->calculated_type; + isis_adj_state_change(&adj, ISIS_ADJ_INITIALIZING, NULL); + adj->sys_type = ISIS_SYSTYPE_UNKNOWN; + } + + if (tw_adj) + adj->ext_circuit_id = tw_adj->local_circuit_id; + + /* 8.2.6 Monitoring point-to-point adjacencies */ + adj->hold_time = iih->holdtime; + adj->last_upd = time(NULL); + + bool changed; + isis_tlvs_to_adj(iih->tlvs, adj, &changed); + changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable, + adj); + + /* lets take care of the expiry */ + EVENT_OFF(adj->t_expire); + event_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time, + &adj->t_expire); + + /* While fabricds initial sync is in progress, ignore hellos from other + * interfaces than the one we are performing the initial sync on. */ + if (fabricd_initial_sync_is_in_progress(iih->circuit->area) + && fabricd_initial_sync_circuit(iih->circuit->area) != iih->circuit) + return ISIS_OK; + + /* 8.2.5.2 a) a match was detected */ + if (isis_tlvs_area_addresses_match(iih->tlvs, + iih->circuit->area->area_addrs)) { + /* 8.2.5.2 a) 2) If the calculated type is L1 - table 5 */ + if (iih->calculated_type == IS_LEVEL_1) { + switch (iih->circ_type) { + case IS_LEVEL_1: + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + break; + case IS_LEVEL_1_AND_2: + if ((adj->adj_state != ISIS_ADJ_UP) || + (adj->adj_usage == ISIS_ADJ_LEVEL1) || + (adj->adj_usage == ISIS_ADJ_LEVEL1AND2)) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } + break; + } + } + + /* 8.2.5.2 a) 3) If the calculated type is L1L2 - table 6 */ + if (iih->calculated_type == IS_LEVEL_1_AND_2) { + switch (iih->circ_type) { + case IS_LEVEL_1: + if (adj->adj_state != ISIS_ADJ_UP + || adj->adj_usage == ISIS_ADJ_LEVEL1) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } else if ((adj->adj_usage == ISIS_ADJ_LEVEL2) || + (adj->adj_usage == + ISIS_ADJ_LEVEL1AND2)) { + /* (8) down - wrong system */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Wrong System"); + } + break; + case IS_LEVEL_2: + if (adj->adj_state != ISIS_ADJ_UP + || adj->adj_usage == ISIS_ADJ_LEVEL2) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1) || + (adj->adj_usage == + ISIS_ADJ_LEVEL1AND2)) { + /* (8) down - wrong system */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Wrong System"); + } + break; + case IS_LEVEL_1_AND_2: + if (adj->adj_state != ISIS_ADJ_UP + || adj->adj_usage == ISIS_ADJ_LEVEL1AND2) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } else if ((adj->adj_usage == ISIS_ADJ_LEVEL1) || + (adj->adj_usage == ISIS_ADJ_LEVEL2)) { + /* (8) down - wrong system */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Wrong System"); + } + break; + } + } + + /* 8.2.5.2 a) 4) If the system is L2 - table 7 */ + if (iih->calculated_type == IS_LEVEL_2) { + switch (iih->circ_type) { + case IS_LEVEL_1_AND_2: + if (adj->adj_state != ISIS_ADJ_UP || + adj->adj_usage == ISIS_ADJ_LEVEL2 || + adj->adj_usage == ISIS_ADJ_LEVEL1AND2) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } + break; + case IS_LEVEL_2: + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + break; + } + } + } + /* 8.2.5.2 b) if no match was detected */ + else if (listcount(iih->circuit->area->area_addrs) > 0) { + if (iih->calculated_type == IS_LEVEL_1) { + /* 8.2.5.2 b) 1) is_type L1 and adj is not up */ + if (adj->adj_state != ISIS_ADJ_UP) { + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, + "Area Mismatch"); + /* 8.2.5.2 b) 2)is_type L1 and adj is up */ + } else { + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, + "Down - Area Mismatch"); + } + } + /* 8.2.5.2 b 3 If the system is L2 or L1L2 - table 8 */ + else { + switch (iih->circ_type) { + case IS_LEVEL_1: + if (adj->adj_state != ISIS_ADJ_UP) { + /* (6) reject - Area Mismatch event */ + zlog_warn("AreaMismatch"); + return ISIS_WARNING; + } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) { + /* (7) down - area mismatch */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Area Mismatch"); + + } else if ((adj->adj_usage + == ISIS_ADJ_LEVEL1AND2) + || (adj->adj_usage + == ISIS_ADJ_LEVEL2)) { + /* (7) down - wrong system */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Wrong System"); + } + break; + case IS_LEVEL_1_AND_2: + case IS_LEVEL_2: + if (adj->adj_state != ISIS_ADJ_UP + || adj->adj_usage == ISIS_ADJ_LEVEL2) { + isis_adj_process_threeway(&adj, tw_adj, + iih->calculated_type); + } else if (adj->adj_usage == ISIS_ADJ_LEVEL1) { + /* (7) down - wrong system */ + isis_adj_state_change(&adj, + ISIS_ADJ_DOWN, + "Wrong System"); + } else if (adj->adj_usage + == ISIS_ADJ_LEVEL1AND2) { + if (iih->circ_type == IS_LEVEL_2) { + /* (7) down - wrong system */ + isis_adj_state_change( + &adj, ISIS_ADJ_DOWN, + "Wrong System"); + } else { + /* (7) down - area mismatch */ + isis_adj_state_change( + &adj, ISIS_ADJ_DOWN, + "Area Mismatch"); + } + } + break; + } + } + } else { + /* down - area mismatch */ + isis_adj_state_change(&adj, ISIS_ADJ_DOWN, "Area Mismatch"); + } + + if (adj) { + if (adj->adj_state == ISIS_ADJ_UP && changed) { + lsp_regenerate_schedule( + adj->circuit->area, + isis_adj_usage2levels(adj->adj_usage), 0); + } + + /* 8.2.5.2 c) if the action was up - comparing circuit IDs */ + /* FIXME - Missing parts */ + + /* some of my own understanding of the ISO, why the heck does + * it not say what should I change the system_type to... + */ + switch (adj->adj_usage) { + case ISIS_ADJ_LEVEL1: + adj->sys_type = ISIS_SYSTYPE_L1_IS; + break; + case ISIS_ADJ_LEVEL2: + adj->sys_type = ISIS_SYSTYPE_L2_IS; + break; + case ISIS_ADJ_LEVEL1AND2: + adj->sys_type = ISIS_SYSTYPE_L2_IS; + break; + case ISIS_ADJ_NONE: + adj->sys_type = ISIS_SYSTYPE_UNKNOWN; + break; + } + } + + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug( + "ISIS-Adj (%s): Rcvd P2P IIH from (%s), cir type %s, cir id %hhu, length %hu", + iih->circuit->area->area_tag, + iih->circuit->interface->name, + circuit_t2string(iih->circuit->is_type), + iih->circuit->circuit_id, iih->pdu_len); + } + + return ISIS_OK; +} + +static int process_lan_hello(struct iih_info *iih) +{ + struct isis_adjacency *adj; + adj = isis_adj_lookup(iih->sys_id, + iih->circuit->u.bc.adjdb[iih->level - 1]); + if ((adj == NULL) || (memcmp(adj->snpa, iih->ssnpa, ETH_ALEN)) + || (adj->level != iih->level)) { + if (!adj) { + /* Do as in 8.4.2.5 */ + adj = isis_new_adj(iih->sys_id, iih->ssnpa, iih->level, + iih->circuit); + } else { + if (iih->ssnpa) { + memcpy(adj->snpa, iih->ssnpa, 6); + } else { + memset(adj->snpa, ' ', 6); + } + adj->level = iih->level; + } + isis_adj_state_change(&adj, ISIS_ADJ_INITIALIZING, NULL); + + if (iih->level == IS_LEVEL_1) + adj->sys_type = ISIS_SYSTYPE_L1_IS; + else + adj->sys_type = ISIS_SYSTYPE_L2_IS; + list_delete_all_node( + iih->circuit->u.bc.lan_neighs[iih->level - 1]); + isis_adj_build_neigh_list( + iih->circuit->u.bc.adjdb[iih->level - 1], + iih->circuit->u.bc.lan_neighs[iih->level - 1]); + } + + if (adj->dis_record[iih->level - 1].dis == ISIS_IS_DIS) { + uint8_t *dis = (iih->level == 1) + ? iih->circuit->u.bc.l1_desig_is + : iih->circuit->u.bc.l2_desig_is; + + if (memcmp(dis, iih->dis, ISIS_SYS_ID_LEN + 1)) { + event_add_event(master, isis_event_dis_status_change, + iih->circuit, 0, NULL); + memcpy(dis, iih->dis, ISIS_SYS_ID_LEN + 1); + } + } + + adj->circuit_t = iih->circ_type; + adj->hold_time = iih->holdtime; + adj->last_upd = time(NULL); + adj->prio[iih->level - 1] = iih->priority; + memcpy(adj->lanid, iih->dis, ISIS_SYS_ID_LEN + 1); + + bool changed; + isis_tlvs_to_adj(iih->tlvs, adj, &changed); + changed |= tlvs_to_adj_mt_set(iih->tlvs, iih->v4_usable, iih->v6_usable, + adj); + + /* lets take care of the expiry */ + EVENT_OFF(adj->t_expire); + event_add_timer(master, isis_adj_expire, adj, (long)adj->hold_time, + &adj->t_expire); + + /* + * If the snpa for this circuit is found from LAN Neighbours TLV + * we have two-way communication -> adjacency can be put to state "up" + */ + bool own_snpa_found = + isis_tlvs_own_snpa_found(iih->tlvs, iih->circuit->u.bc.snpa); + + if (adj->adj_state != ISIS_ADJ_UP) { + if (own_snpa_found) { + isis_adj_state_change( + &adj, ISIS_ADJ_UP, + "own SNPA found in LAN Neighbours TLV"); + } + } else { + if (!own_snpa_found) { + isis_adj_state_change( + &adj, ISIS_ADJ_INITIALIZING, + "own SNPA not found in LAN Neighbours TLV"); + } + } + + if (adj->adj_state == ISIS_ADJ_UP && changed) + lsp_regenerate_schedule(adj->circuit->area, iih->level, 0); + + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug( + "ISIS-Adj (%s): Rcvd L%d LAN IIH from %pSY on %s, cirType %s, cirID %u, length %zd", + iih->circuit->area->area_tag, iih->level, iih->ssnpa, + iih->circuit->interface->name, + circuit_t2string(iih->circuit->is_type), + iih->circuit->circuit_id, + stream_get_endp(iih->circuit->rcv_stream)); + } + return ISIS_OK; +} + +static int pdu_len_validate(uint16_t pdu_len, struct isis_circuit *circuit) +{ + if (pdu_len < stream_get_getp(circuit->rcv_stream) + || pdu_len > ISO_MTU(circuit) + || pdu_len > stream_get_endp(circuit->rcv_stream)) + return 1; + + if (pdu_len < stream_get_endp(circuit->rcv_stream)) + stream_set_endp(circuit->rcv_stream, pdu_len); + return 0; +} + +static void update_rej_adj_count(struct isis_circuit *circuit) +{ + circuit->rej_adjacencies++; + if (circuit->is_type == IS_LEVEL_1) + circuit->area->rej_adjacencies[0]++; + else if (circuit->is_type == IS_LEVEL_2) + circuit->area->rej_adjacencies[1]++; + else { + circuit->area->rej_adjacencies[0]++; + circuit->area->rej_adjacencies[1]++; + } +} + +static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit, + uint8_t *ssnpa) +{ + /* keep a copy of the raw pdu for NB notifications */ + size_t pdu_start = stream_get_getp(circuit->rcv_stream); + size_t pdu_end = stream_get_endp(circuit->rcv_stream); + char raw_pdu[pdu_end - pdu_start]; + bool p2p_hello = (pdu_type == P2P_HELLO); + int level = p2p_hello ? 0 + : (pdu_type == L1_LAN_HELLO) ? ISIS_LEVEL1 + : ISIS_LEVEL2; + const char *pdu_name = + p2p_hello + ? "P2P IIH" + : (level == ISIS_LEVEL1) ? "L1 LAN IIH" : "L2 LAN IIH"; + + stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, + pdu_end - pdu_start); + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug("ISIS-Adj (%s): Rcvd %s on %s, cirType %s, cirID %u", + circuit->area->area_tag, pdu_name, + circuit->interface->name, + circuit_t2string(circuit->is_type), + circuit->circuit_id); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(circuit->rcv_stream), + stream_get_endp(circuit->rcv_stream)); + } + + if (p2p_hello) { + if (circuit->circ_type != CIRCUIT_T_P2P) { + zlog_warn("p2p hello on non p2p circuit"); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency( + circuit, "p2p hello on non p2p circuit", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_WARNING; + } + } else { + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + zlog_warn("lan hello on non broadcast circuit"); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency( + circuit, "lan hello on non broadcast circuit", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_WARNING; + } + + if (circuit->ext_domain) { + zlog_debug( + "level %d LAN Hello received over circuit with externalDomain = true", + level); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency( + circuit, + "LAN Hello received over circuit with externalDomain = true", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_WARNING; + } + + if (!(circuit->is_type & level)) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug( + "ISIS-Adj (%s): Interface level mismatch, %s", + circuit->area->area_tag, + circuit->interface->name); + } + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, + "Interface level mismatch", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_WARNING; + } + } + + struct iih_info iih = { + .circuit = circuit, .ssnpa = ssnpa, .level = level}; + + /* Generic IIH Header */ + iih.circ_type = stream_getc(circuit->rcv_stream) & 0x03; + stream_get(iih.sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN); + iih.holdtime = stream_getw(circuit->rcv_stream); + iih.pdu_len = stream_getw(circuit->rcv_stream); + + if (p2p_hello) { + iih.circuit_id = stream_getc(circuit->rcv_stream); + } else { + iih.priority = stream_getc(circuit->rcv_stream); + stream_get(iih.dis, circuit->rcv_stream, ISIS_SYS_ID_LEN + 1); + } + + if (pdu_len_validate(iih.pdu_len, circuit)) { + zlog_warn( + "ISIS-Adj (%s): Rcvd %s from (%s) with invalid pdu length %hu", + circuit->area->area_tag, pdu_name, + circuit->interface->name, iih.pdu_len); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, "Invalid PDU length", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_WARNING; + } + + if (!p2p_hello && !(level & iih.circ_type)) { + flog_err(EC_ISIS_PACKET, + "Level %d LAN Hello with Circuit Type %d", level, + iih.circ_type); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, + "LAN Hello with wrong IS-level", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + return ISIS_ERROR; + } + + const char *error_log; + int retval = ISIS_WARNING; + + if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), + circuit->rcv_stream, &iih.tlvs, &error_log)) { + zlog_warn("isis_unpack_tlvs() failed: %s", error_log); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, "Failed to unpack TLVs", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + if (!iih.tlvs->area_addresses.count) { + zlog_warn("No Area addresses TLV in %s", pdu_name); +#ifndef FABRICD + /* send northbound notification */ + isis_notif_area_mismatch(circuit, raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + if (!iih.tlvs->protocols_supported.count) { + zlog_warn("No supported protocols TLV in %s", pdu_name); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, + "No supported protocols TLV", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + int auth_code = isis_tlvs_auth_is_valid(iih.tlvs, &circuit->passwd, + circuit->rcv_stream, false); + if (auth_code != ISIS_AUTH_OK) { + isis_event_auth_failure(circuit->area->area_tag, + "IIH authentication failure", + iih.sys_id); +#ifndef FABRICD + /* send northbound notification */ + stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, + pdu_end - pdu_start); + if (auth_code == ISIS_AUTH_FAILURE) { + update_rej_adj_count(circuit); + isis_notif_authentication_failure(circuit, raw_pdu, + sizeof(raw_pdu)); + } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ + update_rej_adj_count(circuit); + isis_notif_authentication_type_failure(circuit, raw_pdu, + sizeof(raw_pdu)); + } +#endif /* ifndef FABRICD */ + goto out; + } + + if (!memcmp(iih.sys_id, circuit->isis->sysid, ISIS_SYS_ID_LEN)) { + zlog_warn( + "ISIS-Adj (%s): Received IIH with own sysid on %s - discard", + circuit->area->area_tag, circuit->interface->name); + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency(circuit, + "Received IIH with our own sysid", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + if (!p2p_hello + && (listcount(circuit->area->area_addrs) == 0 + || (level == ISIS_LEVEL1 + && !isis_tlvs_area_addresses_match( + iih.tlvs, circuit->area->area_addrs)))) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_debug( + "ISIS-Adj (%s): Area mismatch, level %d IIH on %s", + circuit->area->area_tag, level, + circuit->interface->name); + } +#ifndef FABRICD + /* send northbound notification */ + isis_notif_area_mismatch(circuit, raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + iih.v4_usable = (fabricd_ip_addrs(circuit) + && iih.tlvs->ipv4_address.count); + + iih.v6_usable = + (listcount(circuit->ipv6_link) && iih.tlvs->ipv6_address.count); + + if (!iih.v4_usable && !iih.v6_usable) { + if (IS_DEBUG_ADJ_PACKETS) { + zlog_warn( + "ISIS-Adj (%s): Neither IPv4 nor IPv6 considered usable. Ignoring IIH", + circuit->area->area_tag); + } + update_rej_adj_count(circuit); +#ifndef FABRICD + isis_notif_reject_adjacency( + circuit, "Neither IPv4 not IPv6 considered usable", + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + goto out; + } + + retval = p2p_hello ? process_p2p_hello(&iih) : process_lan_hello(&iih); +out: + isis_free_tlvs(iih.tlvs); + + return retval; +} + +static void lsp_flood_or_update(struct isis_lsp *lsp, + struct isis_circuit *circuit, + bool circuit_scoped) +{ + if (!circuit_scoped) + lsp_flood(lsp, circuit); + else + fabricd_update_lsp_no_flood(lsp, circuit); +} + +/* + * Process Level 1/2 Link State + * ISO - 10589 + * Section 7.3.15.1 - Action on receipt of a link state PDU + */ +static int process_lsp(uint8_t pdu_type, struct isis_circuit *circuit, + const uint8_t *ssnpa, uint8_t max_area_addrs) +{ + int level; + bool circuit_scoped; + size_t pdu_start = stream_get_getp(circuit->rcv_stream); + size_t pdu_end = stream_get_endp(circuit->rcv_stream); + char raw_pdu[pdu_end - pdu_start]; + + stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, + pdu_end - pdu_start); + + if (pdu_type == FS_LINK_STATE) { + if (!fabricd) + return ISIS_ERROR; + if (max_area_addrs != L2_CIRCUIT_FLOODING_SCOPE) + return ISIS_ERROR; + level = ISIS_LEVEL2; + circuit_scoped = true; + + /* The stream is used verbatim for sending out new LSPDUs. + * So make sure we store it as an L2 LSPDU internally. + * (compare for the reverse in `send_lsp`) */ + stream_putc_at(circuit->rcv_stream, 4, L2_LINK_STATE); + stream_putc_at(circuit->rcv_stream, 7, 0); + } else { + if (pdu_type == L1_LINK_STATE) + level = ISIS_LEVEL1; + else + level = ISIS_LEVEL2; + circuit_scoped = false; + } + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Rcvd %sL%d LSP on %s, cirType %s, cirID %u", + circuit->area->area_tag, + circuit_scoped ? "Circuit scoped " : "", level, + circuit->interface->name, + circuit_t2string(circuit->is_type), + circuit->circuit_id); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(circuit->rcv_stream), + stream_get_endp(circuit->rcv_stream)); + } + + struct isis_lsp_hdr hdr = {}; + + hdr.pdu_len = stream_getw(circuit->rcv_stream); + hdr.rem_lifetime = stream_getw(circuit->rcv_stream); + stream_get(hdr.lsp_id, circuit->rcv_stream, sizeof(hdr.lsp_id)); + hdr.seqno = stream_getl(circuit->rcv_stream); + hdr.checksum = stream_getw(circuit->rcv_stream); + hdr.lsp_bits = stream_getc(circuit->rcv_stream); + +#ifndef FABRICD + /* send northbound notification */ + char buf[ISO_SYSID_STRLEN]; + + snprintfrr(buf, ISO_SYSID_STRLEN, "%pSY", hdr.lsp_id); + isis_notif_lsp_received(circuit, hdr.lsp_id, hdr.seqno, time(NULL), + buf); +#endif /* ifndef FABRICD */ + + if (pdu_len_validate(hdr.pdu_len, circuit)) { + zlog_debug("ISIS-Upd (%s): LSP %pLS invalid LSP length %hu", + circuit->area->area_tag, hdr.lsp_id, hdr.pdu_len); + return ISIS_WARNING; + } + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Rcvd L%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus, len %hu, on %s", + circuit->area->area_tag, level, hdr.lsp_id, hdr.seqno, + hdr.checksum, hdr.rem_lifetime, hdr.pdu_len, + circuit->interface->name); + } + + /* lsp is_type check */ + if ((hdr.lsp_bits & IS_LEVEL_1) != IS_LEVEL_1) { + zlog_debug("ISIS-Upd (%s): LSP %pLS invalid LSP is type 0x%x", + circuit->area->area_tag, hdr.lsp_id, + hdr.lsp_bits & IS_LEVEL_1_AND_2); + /* continue as per RFC1122 Be liberal in what you accept, and + * conservative in what you send */ + } + + /* Checksum sanity check - FIXME: move to correct place */ + /* 12 = sysid+pdu+remtime */ + if (iso_csum_verify(STREAM_DATA(circuit->rcv_stream) + 12, + hdr.pdu_len - 12, hdr.checksum, 12)) { + zlog_debug( + "ISIS-Upd (%s): LSP %pLS invalid LSP checksum 0x%04hx", + circuit->area->area_tag, hdr.lsp_id, hdr.checksum); + return ISIS_WARNING; + } + + /* 7.3.15.1 a) 1 - external domain circuit will discard lsps */ + if (circuit->ext_domain) { + zlog_debug( + "ISIS-Upd (%s): LSP %pLS received at level %d over circuit with externalDomain = true", + circuit->area->area_tag, hdr.lsp_id, level); + return ISIS_WARNING; + } + + /* 7.3.15.1 a) 2,3 - manualL2OnlyMode not implemented */ + if (!(circuit->is_type & level)) { + zlog_debug( + "ISIS-Upd (%s): LSP %pLS received at level %d over circuit of type %s", + circuit->area->area_tag, hdr.lsp_id, level, + circuit_t2string(circuit->is_type)); + return ISIS_WARNING; + } + + struct isis_tlvs *tlvs = NULL; + int retval = ISIS_WARNING; + const char *error_log; + + if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), + circuit->rcv_stream, &tlvs, &error_log)) { + zlog_warn("Something went wrong unpacking the LSP: %s", + error_log); +#ifndef FABRICD + /* send northbound notification. Note that the tlv-type and + * offset cannot correctly be set here as they are not returned + * by isis_unpack_tlvs, but in there I cannot fire a + * notification because I have no circuit information. So until + * we change the code above to return those extra fields, we + * will send dummy values which are ignored in the callback + */ + circuit->lsp_error_counter++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->lsp_error_counter[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->lsp_error_counter[1]++; + } else { + circuit->area->lsp_error_counter[0]++; + circuit->area->lsp_error_counter[1]++; + } + + isis_notif_lsp_error(circuit, hdr.lsp_id, raw_pdu, + sizeof(raw_pdu), 0, 0); +#endif /* ifndef FABRICD */ + goto out; + } + + /* 7.3.15.1 a) 4 - need to make sure IDLength matches */ + + /* 7.3.15.1 a) 5 - maximum area match, can be ommited since we only use + * 3 */ + + /* 7.3.15.1 a) 7 - password check */ + struct isis_passwd *passwd = (level == ISIS_LEVEL1) + ? &circuit->area->area_passwd + : &circuit->area->domain_passwd; + int auth_code = isis_tlvs_auth_is_valid(tlvs, passwd, + circuit->rcv_stream, true); + if (auth_code != ISIS_AUTH_OK) { + isis_event_auth_failure(circuit->area->area_tag, + "LSP authentication failure", + hdr.lsp_id); +#ifndef FABRICD + /* send northbound notification */ + if (auth_code == ISIS_AUTH_FAILURE) { + circuit->auth_failures++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->auth_failures[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->auth_failures[1]++; + } else { + circuit->area->auth_failures[0]++; + circuit->area->auth_failures[1]++; + } + isis_notif_authentication_failure(circuit, raw_pdu, + sizeof(raw_pdu)); + } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ + circuit->auth_type_failures++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->auth_type_failures[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->auth_type_failures[1]++; + } else { + circuit->area->auth_type_failures[0]++; + circuit->area->auth_type_failures[1]++; + } + isis_notif_authentication_type_failure(circuit, raw_pdu, + sizeof(raw_pdu)); + } +#endif /* ifndef FABRICD */ + goto out; + } + + /* Find the LSP in our database and compare it to this Link State header + */ + struct isis_lsp *lsp = + lsp_search(&circuit->area->lspdb[level - 1], hdr.lsp_id); + int comp = 0; + if (lsp) + comp = lsp_compare(circuit->area->area_tag, lsp, hdr.seqno, + hdr.checksum, hdr.rem_lifetime); + if (lsp && (lsp->own_lsp)) + goto dontcheckadj; + + /* 7.3.15.1 a) 6 - Must check that we have an adjacency of the same + * level */ + /* for broadcast circuits, snpa should be compared */ + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + if (!isis_adj_lookup_snpa(ssnpa, + circuit->u.bc.adjdb[level - 1])) { + zlog_debug( + "(%s): DS ======= LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s", + circuit->area->area_tag, hdr.lsp_id, hdr.seqno, + hdr.checksum, hdr.rem_lifetime, + circuit->interface->name); + goto out; /* Silently discard */ + } + } + /* for non broadcast, we just need to find same level adj */ + else { + /* If no adj, or no sharing of level */ + if (!circuit->u.p2p.neighbor) { + retval = ISIS_OK; + goto out; + } else { + if (((level == IS_LEVEL_1) + && (circuit->u.p2p.neighbor->adj_usage + == ISIS_ADJ_LEVEL2)) + || ((level == IS_LEVEL_2) + && (circuit->u.p2p.neighbor->adj_usage + == ISIS_ADJ_LEVEL1))) + goto out; + } + } + + bool lsp_confusion; + +dontcheckadj: + /* 7.3.15.1 a) 7 - Passwords for level 1 - not implemented */ + + /* 7.3.15.1 a) 8 - Passwords for level 2 - not implemented */ + + /* 7.3.15.1 a) 9 - OriginatingLSPBufferSize - not implemented FIXME: do + * it */ + + /* 7.3.16.2 - If this is an LSP from another IS with identical seq_num + * but + * wrong checksum, initiate a purge. */ + if (lsp && (lsp->hdr.seqno == hdr.seqno) + && (lsp->hdr.checksum != hdr.checksum) + && hdr.rem_lifetime) { + zlog_warn( + "ISIS-Upd (%s): LSP %pLS seq 0x%08x with confused checksum received.", + circuit->area->area_tag, hdr.lsp_id, hdr.seqno); + hdr.rem_lifetime = 0; + lsp_confusion = true; + } else + lsp_confusion = false; + + /* 7.3.15.1 b) - If the remaining life time is 0, we perform 7.3.16.4 */ + if (hdr.rem_lifetime == 0) { + if (!lsp) { + /* 7.3.16.4 a) 1) No LSP in db -> send an ack, but don't + * save */ + /* only needed on explicit update, eg - p2p */ + if (circuit->circ_type == CIRCUIT_T_P2P) + ack_lsp(&hdr, circuit, level); + goto out; /* FIXME: do we need a purge? */ + } else { + if (memcmp(hdr.lsp_id, circuit->isis->sysid, + ISIS_SYS_ID_LEN)) { + /* LSP by some other system -> do 7.3.16.4 b) */ + /* 7.3.16.4 b) 1) */ + if (comp == LSP_NEWER) { + lsp_update(lsp, &hdr, tlvs, + circuit->rcv_stream, + circuit->area, level, + lsp_confusion); + if (lsp_confusion) + isis_free_tlvs(tlvs); + tlvs = NULL; + /* ii */ + lsp_flood_or_update(lsp, NULL, + circuit_scoped); + /* v */ + ISIS_FLAGS_CLEAR_ALL( + lsp->SSNflags); /* FIXME: + OTHER + than c + */ + + /* For the case of lsp confusion, flood + * the purge back to its + * originator so that it can react. + * Otherwise, don't reflood + * through incoming circuit as usual */ + if (!lsp_confusion) { + isis_tx_queue_del( + circuit->tx_queue, + lsp); + + /* iv */ + if (circuit->circ_type + != CIRCUIT_T_BROADCAST) + ISIS_SET_FLAG( + lsp->SSNflags, + circuit); + } + } /* 7.3.16.4 b) 2) */ + else if (comp == LSP_EQUAL) { + /* i */ + isis_tx_queue_del(circuit->tx_queue, + lsp); + /* ii */ + if (circuit->circ_type + != CIRCUIT_T_BROADCAST) + ISIS_SET_FLAG(lsp->SSNflags, + circuit); + } /* 7.3.16.4 b) 3) */ + else { + isis_tx_queue_add(circuit->tx_queue, + lsp, TX_LSP_NORMAL); + ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); + } + } else if (lsp->hdr.rem_lifetime != 0) { + /* our own LSP -> 7.3.16.4 c) */ + if (comp == LSP_NEWER) { +#ifndef FABRICD + if (lsp->hdr.seqno < hdr.seqno) { + /* send northbound + * notification */ + circuit->area + ->lsp_seqno_skipped_counter++; + isis_notif_seqno_skipped( + circuit, hdr.lsp_id); + } +#endif /* ifndef FABRICD */ + lsp_inc_seqno(lsp, hdr.seqno); + lsp_flood_or_update(lsp, NULL, + circuit_scoped); + } else { + isis_tx_queue_add(circuit->tx_queue, + lsp, TX_LSP_NORMAL); + ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); + } + if (IS_DEBUG_UPDATE_PACKETS) + zlog_debug( + "ISIS-Upd (%s): (1) re-originating LSP %pLS new seq 0x%08x", + circuit->area->area_tag, + hdr.lsp_id, lsp->hdr.seqno); + } else { + /* our own LSP with 0 remaining life time */ +#ifndef FABRICD + /* send northbound notification */ + isis_notif_own_lsp_purge(circuit, hdr.lsp_id); +#endif /* ifndef FABRICD */ + } + } + goto out; + } + /* 7.3.15.1 c) - If this is our own lsp and we don't have it initiate a + * purge */ + if (memcmp(hdr.lsp_id, circuit->isis->sysid, ISIS_SYS_ID_LEN) == 0) { + if (!lsp) { + /* 7.3.16.4: initiate a purge */ + lsp_purge_non_exist(level, &hdr, circuit->area); + retval = ISIS_OK; + goto out; + } + /* 7.3.15.1 d) - If this is our own lsp and we have it */ + + /* In 7.3.16.1, If an Intermediate system R somewhere in the + * domain + * has information that the current sequence number for source S + * is + * "greater" than that held by S, ... */ + + if (comp == LSP_NEWER) { + /* 7.3.16.1 */ + lsp_inc_seqno(lsp, hdr.seqno); +#ifndef FABRICD + /* send northbound notification */ + circuit->area->lsp_seqno_skipped_counter++; + isis_notif_seqno_skipped(circuit, hdr.lsp_id); +#endif /* ifndef FABRICD */ + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): (2) re-originating LSP %pLS new seq 0x%08x", + circuit->area->area_tag, hdr.lsp_id, + lsp->hdr.seqno); + } + lsp_flood(lsp, NULL); + } else if (comp == LSP_EQUAL) { + isis_tx_queue_del(circuit->tx_queue, lsp); + if (circuit->circ_type != CIRCUIT_T_BROADCAST) + ISIS_SET_FLAG(lsp->SSNflags, circuit); + } else { + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); + } + } else { + /* 7.3.15.1 e) - This lsp originated on another system */ + + /* 7.3.15.1 e) 1) LSP newer than the one in db or no LSP in db + */ + if ((!lsp || comp == LSP_NEWER)) { + /* + * If this lsp is a frag, need to see if we have zero + * lsp present + */ + struct isis_lsp *lsp0 = NULL; + if (LSP_FRAGMENT(hdr.lsp_id) != 0) { + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + memcpy(lspid, hdr.lsp_id, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(lspid) = 0; + lsp0 = lsp_search( + &circuit->area->lspdb[level - 1], lspid); + if (!lsp0) { + zlog_debug( + "Got lsp frag, while zero lsp not in database"); + goto out; + } + } + /* i */ + if (!lsp) { + lsp = lsp_new_from_recv( + &hdr, tlvs, circuit->rcv_stream, lsp0, + circuit->area, level); + tlvs = NULL; + lsp_insert(&circuit->area->lspdb[level - 1], + lsp); + } else /* exists, so we overwrite */ + { + lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream, + circuit->area, level, false); + tlvs = NULL; + } + lsp_flood_or_update(lsp, circuit, circuit_scoped); + + /* iv */ + if (circuit->circ_type != CIRCUIT_T_BROADCAST) + ISIS_SET_FLAG(lsp->SSNflags, circuit); + /* FIXME: v) */ + } + /* 7.3.15.1 e) 2) LSP equal to the one in db */ + else if (comp == LSP_EQUAL) { + isis_tx_queue_del(circuit->tx_queue, lsp); + lsp_update(lsp, &hdr, tlvs, circuit->rcv_stream, + circuit->area, level, false); + tlvs = NULL; + if (circuit->circ_type != CIRCUIT_T_BROADCAST) + ISIS_SET_FLAG(lsp->SSNflags, circuit); + } + /* 7.3.15.1 e) 3) LSP older than the one in db */ + else { + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); + } + } + + retval = ISIS_OK; + +out: + fabricd_trigger_csnp(circuit->area, circuit_scoped); + + isis_free_tlvs(tlvs); + return retval; +} + +/* + * Process Sequence Numbers + * ISO - 10589 + * Section 7.3.15.2 - Action on receipt of a sequence numbers PDU + */ + +static int process_snp(uint8_t pdu_type, struct isis_circuit *circuit, + const uint8_t *ssnpa) +{ +#ifndef FABRICD + size_t pdu_start = stream_get_getp(circuit->rcv_stream); + size_t pdu_end = stream_get_endp(circuit->rcv_stream); + char raw_pdu[pdu_end - pdu_start]; +#endif /* ifndef FABRICD */ + + bool is_csnp = (pdu_type == L1_COMPLETE_SEQ_NUM + || pdu_type == L2_COMPLETE_SEQ_NUM); + char typechar = is_csnp ? 'C' : 'P'; + int level = (pdu_type == L1_COMPLETE_SEQ_NUM + || pdu_type == L1_PARTIAL_SEQ_NUM) + ? ISIS_LEVEL1 + : ISIS_LEVEL2; + + uint16_t pdu_len = stream_getw(circuit->rcv_stream); + uint8_t rem_sys_id[ISIS_SYS_ID_LEN]; + + stream_get(rem_sys_id, circuit->rcv_stream, ISIS_SYS_ID_LEN); + stream_forward_getp(circuit->rcv_stream, 1); /* Circuit ID - unused */ + + uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2] = {}; + uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2] = {}; + + if (is_csnp) { + stream_get(start_lsp_id, circuit->rcv_stream, + ISIS_SYS_ID_LEN + 2); + stream_get(stop_lsp_id, circuit->rcv_stream, + ISIS_SYS_ID_LEN + 2); + } + + if (pdu_len_validate(pdu_len, circuit)) { + zlog_warn("Received a CSNP with bogus length %d", pdu_len); + return ISIS_WARNING; + } + + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, cirType %s, cirID %u", + circuit->area->area_tag, level, typechar, + circuit->interface->name, + circuit_t2string(circuit->is_type), + circuit->circuit_id); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(circuit->rcv_stream), + stream_get_endp(circuit->rcv_stream)); + } + + /* 7.3.15.2 a) 1 - external domain circuit will discard snp pdu */ + if (circuit->ext_domain) { + + zlog_debug( + "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, skipping: circuit externalDomain = true", + circuit->area->area_tag, level, typechar, + circuit->interface->name); + + return ISIS_OK; + } + + /* 7.3.15.2 a) 2,3 - manualL2OnlyMode not implemented */ + if (!(circuit->is_type & level)) { + zlog_debug( + "ISIS-Snp (%s): Rcvd L%d %cSNP on %s, skipping: circuit type %s does not match level %d", + circuit->area->area_tag, level, typechar, + circuit->interface->name, + circuit_t2string(circuit->is_type), level); + + return ISIS_OK; + } + + /* 7.3.15.2 a) 4 - not applicable for CSNP only PSNPs on broadcast */ + if (!is_csnp && (circuit->circ_type == CIRCUIT_T_BROADCAST) + && !circuit->u.bc.is_dr[level - 1]) { + zlog_debug( + "ISIS-Snp (%s): Rcvd L%d %cSNP from %pSY on %s, skipping: we are not the DIS", + circuit->area->area_tag, level, typechar, ssnpa, + circuit->interface->name); + + return ISIS_OK; + } + + /* 7.3.15.2 a) 5 - need to make sure IDLength matches - already checked + */ + + /* 7.3.15.2 a) 6 - maximum area match, can be ommited since we only use + * 3 + * - already checked */ + + /* 7.3.15.2 a) 7 - Must check that we have an adjacency of the same + * level */ + /* for broadcast circuits, snpa should be compared */ + /* FIXME : Do we need to check SNPA? */ + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + if (!isis_adj_lookup(rem_sys_id, + circuit->u.bc.adjdb[level - 1])) + return ISIS_OK; /* Silently discard */ + } else { + if (!fabricd && !circuit->u.p2p.neighbor) { + zlog_warn("no p2p neighbor on circuit %s", + circuit->interface->name); + return ISIS_OK; /* Silently discard */ + } + } + + struct isis_tlvs *tlvs; + int retval = ISIS_WARNING; + const char *error_log; + + if (isis_unpack_tlvs(STREAM_READABLE(circuit->rcv_stream), + circuit->rcv_stream, &tlvs, &error_log)) { + zlog_warn("Something went wrong unpacking the SNP: %s", + error_log); + goto out; + } + + struct isis_passwd *passwd = (level == IS_LEVEL_1) + ? &circuit->area->area_passwd + : &circuit->area->domain_passwd; + + if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_RECV)) { + int auth_code = isis_tlvs_auth_is_valid( + tlvs, passwd, circuit->rcv_stream, false); + if (auth_code != ISIS_AUTH_OK) { + isis_event_auth_failure(circuit->area->area_tag, + "SNP authentication failure", + rem_sys_id); +#ifndef FABRICD + /* send northbound notification */ + stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, + pdu_end - pdu_start); + if (auth_code == ISIS_AUTH_FAILURE) { + circuit->auth_failures++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->auth_failures[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->auth_failures[1]++; + } else { + circuit->area->auth_failures[0]++; + circuit->area->auth_failures[1]++; + } + isis_notif_authentication_failure( + circuit, raw_pdu, sizeof(raw_pdu)); + } else { /* AUTH_TYPE_FAILURE or NO_VALIDATOR */ + circuit->auth_type_failures++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->auth_type_failures[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->auth_type_failures[1]++; + } else { + circuit->area->auth_type_failures[0]++; + circuit->area->auth_type_failures[1]++; + } + isis_notif_authentication_type_failure( + circuit, raw_pdu, sizeof(raw_pdu)); + } +#endif /* ifndef FABRICD */ + goto out; + } + } + + struct isis_lsp_entry *entry_head = + (struct isis_lsp_entry *)tlvs->lsp_entries.head; + + /* debug isis snp-packets */ + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug("ISIS-Snp (%s): Rcvd L%d %cSNP from %pSY on %s", + circuit->area->area_tag, level, typechar, ssnpa, + circuit->interface->name); + for (struct isis_lsp_entry *entry = entry_head; entry; + entry = entry->next) { + zlog_debug( + "ISIS-Snp (%s): %cSNP entry %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus", + circuit->area->area_tag, typechar, entry->id, + entry->seqno, entry->checksum, + entry->rem_lifetime); + } + } + + bool resync_needed = false; + + /* 7.3.15.2 b) Actions on LSP_ENTRIES reported */ + for (struct isis_lsp_entry *entry = entry_head; entry; + entry = entry->next) { + struct isis_lsp *lsp = + lsp_search(&circuit->area->lspdb[level - 1], entry->id); + bool own_lsp = !memcmp(entry->id, circuit->isis->sysid, + ISIS_SYS_ID_LEN); + if (lsp) { + /* 7.3.15.2 b) 1) is this LSP newer */ + int cmp = lsp_compare(circuit->area->area_tag, lsp, + entry->seqno, entry->checksum, + entry->rem_lifetime); + /* 7.3.15.2 b) 2) if it equals, clear SRM on p2p */ + if (cmp == LSP_EQUAL) { + /* if (circuit->circ_type != + * CIRCUIT_T_BROADCAST) */ + isis_tx_queue_del(circuit->tx_queue, lsp); + } + /* 7.3.15.2 b) 3) if it is older, clear SSN and set SRM + */ + else if (cmp == LSP_OLDER) { + ISIS_CLEAR_FLAG(lsp->SSNflags, circuit); + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + } + /* 7.3.15.2 b) 4) if it is newer, set SSN and clear SRM + on p2p */ + else { + if (own_lsp) { + lsp_inc_seqno(lsp, entry->seqno); + isis_tx_queue_add(circuit->tx_queue, lsp, + TX_LSP_NORMAL); + } else { + ISIS_SET_FLAG(lsp->SSNflags, circuit); + /* if (circuit->circ_type != + * CIRCUIT_T_BROADCAST) */ + isis_tx_queue_del(circuit->tx_queue, lsp); + resync_needed = true; + } + } + } else { + /* 7.3.15.2 b) 5) if it was not found, and all of those + * are not 0, + * insert it and set SSN on it */ + if (entry->rem_lifetime && entry->checksum + && entry->seqno + && memcmp(entry->id, circuit->isis->sysid, + ISIS_SYS_ID_LEN)) { + struct isis_lsp *lsp0 = NULL; + + if (LSP_FRAGMENT(entry->id)) { + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + + memcpy(lspid, entry->id, + ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(lspid) = 0; + lsp0 = lsp_search( + &circuit->area->lspdb[level - 1], + lspid); + if (!lsp0) { + zlog_debug("Got lsp frag in snp, while zero not in database"); + continue; + } + } + lsp = lsp_new(circuit->area, entry->id, + entry->rem_lifetime, 0, 0, + entry->checksum, lsp0, level); + lsp_insert(&circuit->area->lspdb[level - 1], + lsp); + + lsp_set_all_srmflags(lsp, false); + ISIS_SET_FLAG(lsp->SSNflags, circuit); + resync_needed = true; + } + } + } + + /* 7.3.15.2 c) on CSNP set SRM for all in range which were not reported + */ + if (is_csnp) { + /* + * Build a list from our own LSP db bounded with + * start_lsp_id and stop_lsp_id + */ + struct list *lsp_list = list_new(); + lsp_build_list_nonzero_ht(&circuit->area->lspdb[level - 1], + start_lsp_id, stop_lsp_id, lsp_list); + + /* Fixme: Find a better solution */ + struct listnode *node, *nnode; + struct isis_lsp *lsp; + for (struct isis_lsp_entry *entry = entry_head; entry; + entry = entry->next) { + for (ALL_LIST_ELEMENTS(lsp_list, node, nnode, lsp)) { + if (lsp_id_cmp(lsp->hdr.lsp_id, entry->id) + == 0) { + list_delete_node(lsp_list, node); + break; + } + } + } + + /* on remaining LSPs we set SRM (neighbor knew not of) */ + for (ALL_LIST_ELEMENTS_RO(lsp_list, node, lsp)) { + isis_tx_queue_add(circuit->tx_queue, lsp, TX_LSP_NORMAL); + resync_needed = true; + } + + /* lets free it */ + list_delete(&lsp_list); + } + + if (fabricd_initial_sync_is_complete(circuit->area) && resync_needed) + zlog_warn("OpenFabric: Needed to resync LSPDB using CSNP!"); + + retval = ISIS_OK; +out: + isis_free_tlvs(tlvs); + return retval; +} + +static int pdu_size(uint8_t pdu_type, uint8_t *size) +{ + switch (pdu_type) { + case L1_LAN_HELLO: + case L2_LAN_HELLO: + *size = ISIS_LANHELLO_HDRLEN; + break; + case P2P_HELLO: + *size = ISIS_P2PHELLO_HDRLEN; + break; + case L1_LINK_STATE: + case L2_LINK_STATE: + case FS_LINK_STATE: + *size = ISIS_LSP_HDR_LEN; + break; + case L1_COMPLETE_SEQ_NUM: + case L2_COMPLETE_SEQ_NUM: + *size = ISIS_CSNP_HDRLEN; + break; + case L1_PARTIAL_SEQ_NUM: + case L2_PARTIAL_SEQ_NUM: + *size = ISIS_PSNP_HDRLEN; + break; + default: + return 1; + } + *size += ISIS_FIXED_HDR_LEN; + return 0; +} + +/* + * PDU Dispatcher + */ + +int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa) +{ + int retval = ISIS_OK; + size_t pdu_start = stream_get_getp(circuit->rcv_stream); + size_t pdu_end = stream_get_endp(circuit->rcv_stream); + char raw_pdu[pdu_end - pdu_start]; + + stream_get_from(raw_pdu, circuit->rcv_stream, pdu_start, + pdu_end - pdu_start); + + /* Verify that at least the 8 bytes fixed header have been received */ + if (stream_get_endp(circuit->rcv_stream) < ISIS_FIXED_HDR_LEN) { + flog_err(EC_ISIS_PACKET, "PDU is too short to be IS-IS."); + return ISIS_ERROR; + } + + uint8_t idrp = stream_getc(circuit->rcv_stream); + uint8_t length = stream_getc(circuit->rcv_stream); + uint8_t version1 = stream_getc(circuit->rcv_stream); + uint8_t id_len = stream_getc(circuit->rcv_stream); + uint8_t pdu_type = stream_getc(circuit->rcv_stream) + & 0x1f; /* bits 6-8 are reserved */ + uint8_t version2 = stream_getc(circuit->rcv_stream); + + stream_forward_getp(circuit->rcv_stream, 1); /* reserved */ + uint8_t max_area_addrs = stream_getc(circuit->rcv_stream); + + pdu_counter_count(circuit->area->pdu_rx_counters, pdu_type); + + if (idrp == ISO9542_ESIS) { + flog_err(EC_LIB_DEVELOPMENT, + "No support for ES-IS packet IDRP=%hhx", idrp); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + if (idrp != ISO10589_ISIS) { + flog_err(EC_ISIS_PACKET, "Not an IS-IS packet IDRP=%hhx", + idrp); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + if (version1 != 1) { + zlog_warn("Unsupported ISIS version %hhu", version1); +#ifndef FABRICD + /* send northbound notification */ + isis_notif_version_skew(circuit, version1, raw_pdu, + sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_WARNING; + } + + if (id_len != 0 && id_len != ISIS_SYS_ID_LEN) { + flog_err( + EC_ISIS_PACKET, + "IDFieldLengthMismatch: ID Length field in a received PDU %hhu, while the parameter for this IS is %u", + id_len, ISIS_SYS_ID_LEN); + circuit->id_len_mismatches++; + if (circuit->is_type == IS_LEVEL_1) { + circuit->area->id_len_mismatches[0]++; + } else if (circuit->is_type == IS_LEVEL_2) { + circuit->area->id_len_mismatches[1]++; + } else { + circuit->area->id_len_mismatches[0]++; + circuit->area->id_len_mismatches[1]++; + } + +#ifndef FABRICD + /* send northbound notification */ + isis_notif_id_len_mismatch(circuit, id_len, raw_pdu, + sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + uint8_t expected_length; + if (pdu_size(pdu_type, &expected_length)) { + zlog_warn("Unsupported ISIS PDU %hhu", pdu_type); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_WARNING; + } + + if (length != expected_length) { + flog_err(EC_ISIS_PACKET, + "Expected fixed header length = %hhu but got %hhu", + expected_length, length); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + if (stream_get_endp(circuit->rcv_stream) < length) { + flog_err( + EC_ISIS_PACKET, + "PDU is too short to contain fixed header of given PDU type."); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + if (version2 != 1) { + zlog_warn("Unsupported ISIS PDU version %hhu", version2); +#ifndef FABRICD + /* send northbound notification */ + isis_notif_version_skew(circuit, version2, raw_pdu, + sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_WARNING; + } + + if (circuit->is_passive) { + zlog_warn("Received ISIS PDU on passive circuit %s", + circuit->interface->name); + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_WARNING; + } + + /* either 3 or 0 */ + if (pdu_type != FS_LINK_STATE /* FS PDU doesn't contain max area addr + field */ + && max_area_addrs != 0 + && max_area_addrs != circuit->isis->max_area_addrs) { + flog_err( + EC_ISIS_PACKET, + "maximumAreaAddressesMismatch: maximumAreaAdresses in a received PDU %hhu while the parameter for this IS is %u", + max_area_addrs, circuit->isis->max_area_addrs); + circuit->max_area_addr_mismatches++; +#ifndef FABRICD + /* send northbound notification */ + isis_notif_max_area_addr_mismatch(circuit, max_area_addrs, + raw_pdu, sizeof(raw_pdu)); +#endif /* ifndef FABRICD */ + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + switch (pdu_type) { + case L1_LAN_HELLO: + case L2_LAN_HELLO: + case P2P_HELLO: + if (fabricd && pdu_type != P2P_HELLO) { + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + retval = process_hello(pdu_type, circuit, ssnpa); + break; + case L1_LINK_STATE: + case L2_LINK_STATE: + case FS_LINK_STATE: + if (fabricd && pdu_type != L2_LINK_STATE && + pdu_type != FS_LINK_STATE) { + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + retval = process_lsp(pdu_type, circuit, ssnpa, max_area_addrs); + break; + case L1_COMPLETE_SEQ_NUM: + case L2_COMPLETE_SEQ_NUM: + case L1_PARTIAL_SEQ_NUM: + case L2_PARTIAL_SEQ_NUM: + retval = process_snp(pdu_type, circuit, ssnpa); + break; + default: + pdu_counter_count_drop(circuit->area, pdu_type); + return ISIS_ERROR; + } + + if (retval != ISIS_OK) + pdu_counter_count_drop(circuit->area, pdu_type); + + return retval; +} + +void isis_receive(struct event *thread) +{ + struct isis_circuit *circuit; + uint8_t ssnpa[ETH_ALEN]; + + /* + * Get the circuit + */ + circuit = EVENT_ARG(thread); + assert(circuit); + + circuit->t_read = NULL; + + isis_circuit_stream(circuit, &circuit->rcv_stream); + +#if ISIS_METHOD != ISIS_METHOD_BPF + int retval; + + retval = circuit->rx(circuit, ssnpa); + + if (retval == ISIS_OK) + isis_handle_pdu(circuit, ssnpa); +#else // ISIS_METHOD != ISIS_METHOD_BPF + circuit->rx(circuit, ssnpa); +#endif + + /* + * prepare for next packet. + */ + if (!circuit->is_passive) + isis_circuit_prepare(circuit); +} + +/* + * SEND SIDE + */ +void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream) +{ + uint8_t length; + + if (pdu_size(pdu_type, &length)) + assert(!"Unknown PDU Type"); + + stream_putc(stream, ISO10589_ISIS); /* IDRP */ + stream_putc(stream, length); /* Length of fixed header */ + stream_putc(stream, 1); /* Version/Protocol ID Extension 1 */ + stream_putc(stream, 0); /* ID Length, 0 => 6 */ + stream_putc(stream, pdu_type); + stream_putc(stream, 1); /* Subversion */ + stream_putc(stream, 0); /* Reserved */ + stream_putc(stream, 0); /* Max Area Addresses 0 => 3 */ +} + +static uint8_t hello_pdu_type(struct isis_circuit *circuit, int level) +{ + if (circuit->circ_type == CIRCUIT_T_BROADCAST) + return (level == IS_LEVEL_1) ? L1_LAN_HELLO : L2_LAN_HELLO; + else + return P2P_HELLO; +} + +static void put_hello_hdr(struct isis_circuit *circuit, int level, + size_t *len_pointer) +{ + uint8_t pdu_type = hello_pdu_type(circuit, level); + + isis_circuit_stream(circuit, &circuit->snd_stream); + fill_fixed_hdr(pdu_type, circuit->snd_stream); + + stream_putc(circuit->snd_stream, circuit->is_type); + stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN); + + uint32_t holdtime = circuit->hello_multiplier[level - 1] + * circuit->hello_interval[level - 1]; + + if (holdtime > 0xffff) + holdtime = 0xffff; + + stream_putw(circuit->snd_stream, holdtime); + *len_pointer = stream_get_endp(circuit->snd_stream); + stream_putw(circuit->snd_stream, 0); /* length is filled in later */ + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + uint8_t *desig_is = (level == IS_LEVEL_1) + ? circuit->u.bc.l1_desig_is + : circuit->u.bc.l2_desig_is; + stream_putc(circuit->snd_stream, circuit->priority[level - 1]); + stream_put(circuit->snd_stream, desig_is, ISIS_SYS_ID_LEN + 1); + } else { + stream_putc(circuit->snd_stream, circuit->circuit_id); + } +} + +int send_hello(struct isis_circuit *circuit, int level) +{ + size_t len_pointer; + int retval; + + if (circuit->is_passive) + return ISIS_OK; + + if (circuit->interface->mtu == 0) { + zlog_warn("circuit has zero MTU"); + return ISIS_WARNING; + } + + put_hello_hdr(circuit, level, &len_pointer); + + struct isis_tlvs *tlvs = isis_alloc_tlvs(); + + isis_tlvs_add_auth(tlvs, &circuit->passwd); + + if (!listcount(circuit->area->area_addrs)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + + isis_tlvs_add_area_addresses(tlvs, circuit->area->area_addrs); + + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + isis_tlvs_add_lan_neighbors( + tlvs, circuit->u.bc.lan_neighs[level - 1]); + } else if (circuit->circ_type == CIRCUIT_T_P2P + && !circuit->disable_threeway_adj) { + uint32_t ext_circuit_id = circuit->idx; + if (circuit->u.p2p.neighbor) { + uint8_t threeway_state; + + if (fabricd_initial_sync_is_in_progress(circuit->area) + && fabricd_initial_sync_circuit(circuit->area) != circuit) + threeway_state = ISIS_THREEWAY_DOWN; + else + threeway_state = circuit->u.p2p.neighbor->threeway_state; + isis_tlvs_add_threeway_adj(tlvs, + threeway_state, + ext_circuit_id, + circuit->u.p2p.neighbor->sysid, + circuit->u.p2p.neighbor->ext_circuit_id); + } else { + isis_tlvs_add_threeway_adj(tlvs, + ISIS_THREEWAY_DOWN, + ext_circuit_id, + NULL, 0); + } + } + + isis_tlvs_set_protocols_supported(tlvs, &circuit->nlpids); + + /* + * MT Supported TLV + * + * TLV gets included if no topology is enabled on the interface, + * if one topology other than #0 is enabled, or if multiple topologies + * are enabled. + */ + struct isis_circuit_mt_setting **mt_settings; + unsigned int mt_count; + + mt_settings = circuit_mt_settings(circuit, &mt_count); + if (mt_count == 0 && area_is_mt(circuit->area)) { + tlvs->mt_router_info_empty = true; + } else if ((mt_count == 1 + && mt_settings[0]->mtid != ISIS_MT_IPV4_UNICAST) + || (mt_count > 1)) { + for (unsigned int i = 0; i < mt_count; i++) + isis_tlvs_add_mt_router_info(tlvs, mt_settings[i]->mtid, + false, false); + } + + if (circuit->ip_router) { + struct list *circuit_ip_addrs = fabricd_ip_addrs(circuit); + + if (circuit_ip_addrs) + isis_tlvs_add_ipv4_addresses(tlvs, circuit_ip_addrs); + } + + if (circuit->ipv6_router) + isis_tlvs_add_ipv6_addresses(tlvs, circuit->ipv6_link); + + /* RFC6119 section 4 define TLV 233 to provide Global IPv6 address */ + if (circuit->ipv6_router) + isis_tlvs_add_global_ipv6_addresses(tlvs, + circuit->ipv6_non_link); + + bool should_pad_hello = + circuit->pad_hellos == ISIS_HELLO_PADDING_ALWAYS || + (circuit->pad_hellos == + ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION && + circuit->upadjcount[0] + circuit->upadjcount[1] == 0); + + if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, + should_pad_hello, false)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; /* XXX: Maybe Log TLV structure? */ + } + + if (IS_DEBUG_ADJ_PACKETS) { + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + zlog_debug( + "ISIS-Adj (%s): Sending L%d LAN IIH on %s, length %zd", + circuit->area->area_tag, level, + circuit->interface->name, + stream_get_endp(circuit->snd_stream)); + } else { + zlog_debug( + "ISIS-Adj (%s): Sending P2P IIH on %s, length %zd", + circuit->area->area_tag, + circuit->interface->name, + stream_get_endp(circuit->snd_stream)); + } + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(circuit->snd_stream), + stream_get_endp(circuit->snd_stream)); + } + + isis_free_tlvs(tlvs); + + pdu_counter_count(circuit->area->pdu_tx_counters, + hello_pdu_type(circuit, level)); + retval = circuit->tx(circuit, level); + if (retval != ISIS_OK) + flog_err(EC_ISIS_PACKET, + "ISIS-Adj (%s): Send L%d IIH on %s failed", + circuit->area->area_tag, level, + circuit->interface->name); + + return retval; +} + +static void send_hello_cb(struct event *thread) +{ + struct isis_circuit_arg *arg = EVENT_ARG(thread); + assert(arg); + + struct isis_circuit *circuit = arg->circuit; + int level = arg->level; + + assert(circuit); + + if (circuit->circ_type == CIRCUIT_T_P2P) { + circuit->u.p2p.t_send_p2p_hello = NULL; + send_hello(circuit, 1); + send_hello_sched(circuit, ISIS_LEVEL1, + 1000 * circuit->hello_interval[0]); + return; + } + + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + zlog_warn("ISIS-Hello (%s): Trying to send hello on unknown circuit type %d", + circuit->area->area_tag, circuit->circ_type); + return; + } + + circuit->u.bc.t_send_lan_hello[level - 1] = NULL; + if (!(circuit->is_type & level)) { + zlog_warn("ISIS-Hello (%s): Trying to send L%d IIH in L%d-only circuit", + circuit->area->area_tag, level, 3 - level); + return; + } + + if (circuit->u.bc.run_dr_elect[level - 1]) + isis_dr_elect(circuit, level); + + send_hello(circuit, level); + + /* set next timer thread */ + send_hello_sched(circuit, level, 1000 * circuit->hello_interval[level - 1]); +} + +static void _send_hello_sched(struct isis_circuit *circuit, + struct event **threadp, int level, long delay) +{ + if (*threadp) { + if (event_timer_remain_msec(*threadp) < (unsigned long)delay) + return; + + EVENT_OFF(*threadp); + } + + event_add_timer_msec(master, send_hello_cb, + &circuit->level_arg[level - 1], + isis_jitter(delay, IIH_JITTER), threadp); +} + +void send_hello_sched(struct isis_circuit *circuit, int level, long delay) +{ + if (circuit->circ_type == CIRCUIT_T_P2P) { + _send_hello_sched(circuit, &circuit->u.p2p.t_send_p2p_hello, + ISIS_LEVEL1, delay); + return; + } + + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + zlog_warn("%s: encountered unknown circuit type %d on %s", + __func__, circuit->circ_type, + circuit->interface->name); + return; + } + + for (int loop_level = ISIS_LEVEL1; loop_level <= ISIS_LEVEL2; loop_level++) { + if (!(loop_level & level)) + continue; + + _send_hello_sched( + circuit, + &circuit->u.bc.t_send_lan_hello[loop_level - 1], + loop_level, + delay + ); + } +} + + +/* + * Count the maximum number of lsps that can be accommodated by a given size. + */ +#define LSP_ENTRIES_LEN (10 + ISIS_SYS_ID_LEN) +static uint16_t get_max_lsp_count(uint16_t size) +{ + uint16_t tlv_count; + uint16_t lsp_count; + uint16_t remaining_size; + + /* First count the full size TLVs */ + tlv_count = size / MAX_LSP_ENTRIES_TLV_SIZE; + lsp_count = tlv_count * (MAX_LSP_ENTRIES_TLV_SIZE / LSP_ENTRIES_LEN); + + /* The last TLV, if any */ + remaining_size = size % MAX_LSP_ENTRIES_TLV_SIZE; + if (remaining_size - 2 >= LSP_ENTRIES_LEN) + lsp_count += (remaining_size - 2) / LSP_ENTRIES_LEN; + + return lsp_count; +} + +int send_csnp(struct isis_circuit *circuit, int level) +{ + if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0) + return ISIS_OK; + + uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_COMPLETE_SEQ_NUM + : L2_COMPLETE_SEQ_NUM; + + isis_circuit_stream(circuit, &circuit->snd_stream); + fill_fixed_hdr(pdu_type, circuit->snd_stream); + + size_t len_pointer = stream_get_endp(circuit->snd_stream); + + stream_putw(circuit->snd_stream, 0); + stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN); + /* with zero circuit id - ref 9.10, 9.11 */ + stream_putc(circuit->snd_stream, 0); + + size_t start_pointer = stream_get_endp(circuit->snd_stream); + stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2); + size_t end_pointer = stream_get_endp(circuit->snd_stream); + stream_put(circuit->snd_stream, 0, ISIS_SYS_ID_LEN + 2); + + struct isis_passwd *passwd = (level == ISIS_LEVEL1) + ? &circuit->area->area_passwd + : &circuit->area->domain_passwd; + + struct isis_tlvs *tlvs = isis_alloc_tlvs(); + + if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) + isis_tlvs_add_auth(tlvs, passwd); + + size_t tlv_start = stream_get_endp(circuit->snd_stream); + if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, + false)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + isis_free_tlvs(tlvs); + + uint16_t num_lsps = + get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream)); + + uint8_t start[ISIS_SYS_ID_LEN + 2]; + memset(start, 0x00, ISIS_SYS_ID_LEN + 2); + uint8_t stop[ISIS_SYS_ID_LEN + 2]; + memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); + + bool loop = true; + while (loop) { + tlvs = isis_alloc_tlvs(); + if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) + isis_tlvs_add_auth(tlvs, passwd); + + struct isis_lsp *last_lsp; + isis_tlvs_add_csnp_entries(tlvs, start, stop, num_lsps, + &circuit->area->lspdb[level - 1], + &last_lsp); + /* + * Update the stop lsp_id before encoding this CSNP. + */ + if (tlvs->lsp_entries.count < num_lsps) { + memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); + } else { + memcpy(stop, last_lsp->hdr.lsp_id, sizeof(stop)); + } + + memcpy(STREAM_DATA(circuit->snd_stream) + start_pointer, start, + ISIS_SYS_ID_LEN + 2); + memcpy(STREAM_DATA(circuit->snd_stream) + end_pointer, stop, + ISIS_SYS_ID_LEN + 2); + stream_set_endp(circuit->snd_stream, tlv_start); + if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, + false, false)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Sending L%d CSNP on %s, length %zd", + circuit->area->area_tag, level, + circuit->interface->name, + stream_get_endp(circuit->snd_stream)); + log_multiline(LOG_DEBUG, " ", "%s", + isis_format_tlvs(tlvs, NULL)); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data( + STREAM_DATA(circuit->snd_stream), + stream_get_endp(circuit->snd_stream)); + } + + pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); + int retval = circuit->tx(circuit, level); + if (retval != ISIS_OK) { + flog_err(EC_ISIS_PACKET, + "ISIS-Snp (%s): Send L%d CSNP on %s failed", + circuit->area->area_tag, level, + circuit->interface->name); + isis_free_tlvs(tlvs); + return retval; + } + + /* + * Start lsp_id of the next CSNP should be one plus the + * stop lsp_id in this current CSNP. + */ + memcpy(start, stop, ISIS_SYS_ID_LEN + 2); + loop = false; + for (int i = ISIS_SYS_ID_LEN + 1; i >= 0; --i) { + if (start[i] < (uint8_t)0xff) { + start[i] += 1; + loop = true; + break; + } + } + memset(stop, 0xff, ISIS_SYS_ID_LEN + 2); + isis_free_tlvs(tlvs); + } + + return ISIS_OK; +} + +void send_l1_csnp(struct event *thread) +{ + struct isis_circuit *circuit; + + circuit = EVENT_ARG(thread); + assert(circuit); + + circuit->t_send_csnp[0] = NULL; + + if ((circuit->circ_type == CIRCUIT_T_BROADCAST + && circuit->u.bc.is_dr[0]) + || circuit->circ_type == CIRCUIT_T_P2P) { + send_csnp(circuit, 1); + } + /* set next timer thread */ + event_add_timer(master, send_l1_csnp, circuit, + isis_jitter(circuit->csnp_interval[0], CSNP_JITTER), + &circuit->t_send_csnp[0]); +} + +void send_l2_csnp(struct event *thread) +{ + struct isis_circuit *circuit; + + circuit = EVENT_ARG(thread); + assert(circuit); + + circuit->t_send_csnp[1] = NULL; + + if ((circuit->circ_type == CIRCUIT_T_BROADCAST + && circuit->u.bc.is_dr[1]) + || circuit->circ_type == CIRCUIT_T_P2P) { + send_csnp(circuit, 2); + } + /* set next timer thread */ + event_add_timer(master, send_l2_csnp, circuit, + isis_jitter(circuit->csnp_interval[1], CSNP_JITTER), + &circuit->t_send_csnp[1]); +} + +/* + * 7.3.15.4 action on expiration of partial SNP interval + * level 1 + */ +static int send_psnp(int level, struct isis_circuit *circuit) +{ + if (circuit->circ_type == CIRCUIT_T_BROADCAST + && circuit->u.bc.is_dr[level - 1]) + return ISIS_OK; + + if (lspdb_count(&circuit->area->lspdb[level - 1]) == 0) + return ISIS_OK; + + if (!circuit->snd_stream) + return ISIS_ERROR; + + uint8_t pdu_type = (level == ISIS_LEVEL1) ? L1_PARTIAL_SEQ_NUM + : L2_PARTIAL_SEQ_NUM; + + isis_circuit_stream(circuit, &circuit->snd_stream); + fill_fixed_hdr(pdu_type, circuit->snd_stream); + + size_t len_pointer = stream_get_endp(circuit->snd_stream); + stream_putw(circuit->snd_stream, 0); /* length is filled in later */ + stream_put(circuit->snd_stream, circuit->isis->sysid, ISIS_SYS_ID_LEN); + stream_putc(circuit->snd_stream, circuit->idx); + + struct isis_passwd *passwd = (level == ISIS_LEVEL1) + ? &circuit->area->area_passwd + : &circuit->area->domain_passwd; + + struct isis_tlvs *tlvs = isis_alloc_tlvs(); + + if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) + isis_tlvs_add_auth(tlvs, passwd); + + size_t tlv_start = stream_get_endp(circuit->snd_stream); + if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, false, + false)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + isis_free_tlvs(tlvs); + + uint16_t num_lsps = + get_max_lsp_count(STREAM_WRITEABLE(circuit->snd_stream)); + + while (1) { + struct isis_lsp *lsp; + + tlvs = isis_alloc_tlvs(); + if (CHECK_FLAG(passwd->snp_auth, SNP_AUTH_SEND)) + isis_tlvs_add_auth(tlvs, passwd); + + frr_each (lspdb, &circuit->area->lspdb[level - 1], lsp) { + if (ISIS_CHECK_FLAG(lsp->SSNflags, circuit)) + isis_tlvs_add_lsp_entry(tlvs, lsp); + + if (tlvs->lsp_entries.count == num_lsps) + break; + } + + if (!tlvs->lsp_entries.count) { + isis_free_tlvs(tlvs); + return ISIS_OK; + } + + stream_set_endp(circuit->snd_stream, tlv_start); + if (isis_pack_tlvs(tlvs, circuit->snd_stream, len_pointer, + false, false)) { + isis_free_tlvs(tlvs); + return ISIS_WARNING; + } + + if (IS_DEBUG_SNP_PACKETS) { + zlog_debug( + "ISIS-Snp (%s): Sending L%d PSNP on %s, length %zd", + circuit->area->area_tag, level, + circuit->interface->name, + stream_get_endp(circuit->snd_stream)); + log_multiline(LOG_DEBUG, " ", "%s", + isis_format_tlvs(tlvs, NULL)); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data( + STREAM_DATA(circuit->snd_stream), + stream_get_endp(circuit->snd_stream)); + } + + pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); + int retval = circuit->tx(circuit, level); + if (retval != ISIS_OK) { + flog_err(EC_ISIS_PACKET, + "ISIS-Snp (%s): Send L%d PSNP on %s failed", + circuit->area->area_tag, level, + circuit->interface->name); + isis_free_tlvs(tlvs); + return retval; + } + + /* + * sending succeeded, we can clear SSN flags of this circuit + * for the LSPs in list + */ + struct isis_lsp_entry *entry_head; + entry_head = (struct isis_lsp_entry *)tlvs->lsp_entries.head; + for (struct isis_lsp_entry *entry = entry_head; entry; + entry = entry->next) + ISIS_CLEAR_FLAG(entry->lsp->SSNflags, circuit); + isis_free_tlvs(tlvs); + } + + return ISIS_OK; +} + +void send_l1_psnp(struct event *thread) +{ + + struct isis_circuit *circuit; + + circuit = EVENT_ARG(thread); + assert(circuit); + + circuit->t_send_psnp[0] = NULL; + + send_psnp(1, circuit); + /* set next timer thread */ + event_add_timer(master, send_l1_psnp, circuit, + isis_jitter(circuit->psnp_interval[0], PSNP_JITTER), + &circuit->t_send_psnp[0]); +} + +/* + * 7.3.15.4 action on expiration of partial SNP interval + * level 2 + */ +void send_l2_psnp(struct event *thread) +{ + struct isis_circuit *circuit; + + circuit = EVENT_ARG(thread); + assert(circuit); + + circuit->t_send_psnp[1] = NULL; + + send_psnp(2, circuit); + + /* set next timer thread */ + event_add_timer(master, send_l2_psnp, circuit, + isis_jitter(circuit->psnp_interval[1], PSNP_JITTER), + &circuit->t_send_psnp[1]); +} + +/* + * ISO 10589 - 7.3.14.3 + */ +void send_lsp(struct isis_circuit *circuit, struct isis_lsp *lsp, + enum isis_tx_type tx_type) +{ + int clear_srm = 1; + int retval = ISIS_OK; + + if (circuit->state != C_STATE_UP || circuit->is_passive == 1) + goto out; + + /* + * Do not send if levels do not match + */ + if (!(lsp->level & circuit->is_type)) + goto out; + + /* + * Do not send if we do not have adjacencies in state up on the circuit + */ + if (circuit->upadjcount[lsp->level - 1] == 0) + goto out; + + /* stream_copy will assert and stop program execution if LSP is larger + * than + * the circuit's MTU. So handle and log this case here. */ + if (stream_get_endp(lsp->pdu) > stream_get_size(circuit->snd_stream)) { + flog_err( + EC_ISIS_PACKET, + "ISIS-Upd (%s): Can't send L%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s. LSP Size is %zu while interface stream size is %zu.", + circuit->area->area_tag, lsp->level, lsp->hdr.lsp_id, + lsp->hdr.seqno, lsp->hdr.checksum, + lsp->hdr.rem_lifetime, circuit->interface->name, + stream_get_endp(lsp->pdu), + stream_get_size(circuit->snd_stream)); +#ifndef FABRICD + /* send a northbound notification */ + isis_notif_lsp_too_large(circuit, stream_get_endp(lsp->pdu), + lsp->hdr.lsp_id); +#endif /* ifndef FABRICD */ + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(lsp->pdu), + stream_get_endp(lsp->pdu)); + retval = ISIS_ERROR; + goto out; + } + + /* copy our lsp to the send buffer */ + stream_copy(circuit->snd_stream, lsp->pdu); + + if (tx_type == TX_LSP_CIRCUIT_SCOPED) { + stream_putc_at(circuit->snd_stream, 4, FS_LINK_STATE); + stream_putc_at(circuit->snd_stream, 7, + L2_CIRCUIT_FLOODING_SCOPE); + } + + if (IS_DEBUG_UPDATE_PACKETS) { + zlog_debug( + "ISIS-Upd (%s): Sending %sL%d LSP %pLS, seq 0x%08x, cksum 0x%04hx, lifetime %hus on %s", + circuit->area->area_tag, + (tx_type == TX_LSP_CIRCUIT_SCOPED) ? "Circuit scoped " + : "", + lsp->level, lsp->hdr.lsp_id, lsp->hdr.seqno, + lsp->hdr.checksum, lsp->hdr.rem_lifetime, + circuit->interface->name); + if (IS_DEBUG_PACKET_DUMP) + zlog_dump_data(STREAM_DATA(circuit->snd_stream), + stream_get_endp(circuit->snd_stream)); + } + + uint8_t pdu_type = (tx_type == TX_LSP_CIRCUIT_SCOPED) ? FS_LINK_STATE + : (lsp->level == ISIS_LEVEL1) ? L1_LINK_STATE + : L2_LINK_STATE; + + clear_srm = 0; + pdu_counter_count(circuit->area->pdu_tx_counters, pdu_type); + retval = circuit->tx(circuit, lsp->level); + if (retval != ISIS_OK) { + flog_err(EC_ISIS_PACKET, + "ISIS-Upd (%s): Send L%d LSP on %s failed %s", + circuit->area->area_tag, lsp->level, + circuit->interface->name, + (retval == ISIS_WARNING) ? "temporarily" + : "permanently"); + } + +out: + if (clear_srm + || (retval == ISIS_OK && circuit->circ_type == CIRCUIT_T_BROADCAST) + || (retval != ISIS_OK && retval != ISIS_WARNING)) { + /* SRM flag will trigger retransmission. We will not retransmit + * if we + * encountered a fatal error. + * On success, they should only be cleared if it's a broadcast + * circuit. + * On a P2P circuit, we will wait for the ack from the neighbor + * to clear + * the fag. + */ + isis_tx_queue_del(circuit->tx_queue, lsp); + } +} + +void isis_log_pdu_drops(struct isis_area *area, const char *pdu_type) +{ + uint64_t total_drops = 0; + + for (int i = 0; i < PDU_COUNTER_SIZE; i++) { + if (!area->pdu_drop_counters[i]) + continue; + total_drops += area->pdu_drop_counters[i]; + } + + zlog_info("PDU drop detected of type: %s. %" PRIu64 + " Total Drops; %" PRIu64 " L1 IIH drops; %" PRIu64 + " L2 IIH drops; %" PRIu64 " P2P IIH drops; %" PRIu64 + " L1 LSP drops; %" PRIu64 " L2 LSP drops; %" PRIu64 + " FS LSP drops; %" PRIu64 " L1 CSNP drops; %" PRIu64 + " L2 CSNP drops; %" PRIu64 " L1 PSNP drops; %" PRIu64 + " L2 PSNP drops.", + pdu_type, total_drops, + pdu_counter_get_count(area->pdu_drop_counters, L1_LAN_HELLO), + pdu_counter_get_count(area->pdu_drop_counters, L2_LAN_HELLO), + pdu_counter_get_count(area->pdu_drop_counters, P2P_HELLO), + pdu_counter_get_count(area->pdu_drop_counters, L1_LINK_STATE), + pdu_counter_get_count(area->pdu_drop_counters, L2_LINK_STATE), + pdu_counter_get_count(area->pdu_drop_counters, FS_LINK_STATE), + pdu_counter_get_count(area->pdu_drop_counters, + L1_COMPLETE_SEQ_NUM), + pdu_counter_get_count(area->pdu_drop_counters, + L2_COMPLETE_SEQ_NUM), + pdu_counter_get_count(area->pdu_drop_counters, + L1_PARTIAL_SEQ_NUM), + pdu_counter_get_count(area->pdu_drop_counters, + L2_PARTIAL_SEQ_NUM)); +} diff --git a/isisd/isis_pdu.h b/isisd/isis_pdu.h new file mode 100644 index 0000000..5303c61 --- /dev/null +++ b/isisd/isis_pdu.h @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_pdu.h + * PDU processing + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_PDU_H +#define _ZEBRA_ISIS_PDU_H + +#include "isisd/isis_tx_queue.h" + +#ifdef __SUNPRO_C +#pragma pack(1) +#endif + +/* + * ISO 9542 - 7.5,7.6 + * + * ES to IS Fixed Header + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Intradomain Routeing Protocol Discriminator | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Length Indicator | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Version/Protocol ID extension | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Reserved = 0 | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | 0 | 0 | 0 | PDU Type | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Holding Time | 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Checksum | 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ + +struct esis_fixed_hdr { + uint8_t idrp; + uint8_t length; + uint8_t version; + uint8_t id_len; + uint8_t pdu_type; + uint16_t holdtime; + uint16_t checksum; +} __attribute__((packed)); + +#define ESIS_FIXED_HDR_LEN 9 + +#define ESH_PDU 2 +#define ISH_PDU 4 +#define RD_PDU 5 + +#define ISIS_FIXED_HDR_LEN 8 + +/* + * IS-IS PDU types. + */ + +#define L1_LAN_HELLO 15 +#define L2_LAN_HELLO 16 +/* + * L1 and L2 LAN IS to IS Hello PDU header + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Reserved | Circuit Type | 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Source ID + id_len + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Holding Time | 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | PDU Length | 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | R | Priority | 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | LAN ID | id_len + 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ +struct isis_lan_hello_hdr { + uint8_t circuit_t; + uint8_t source_id[ISIS_SYS_ID_LEN]; + uint16_t hold_time; + uint16_t pdu_len; + uint8_t prio; + uint8_t lan_id[ISIS_SYS_ID_LEN + 1]; +} __attribute__((packed)); +#define ISIS_LANHELLO_HDRLEN 19 + +#define P2P_HELLO 17 +/* + * Point-to-point IS to IS hello PDU header + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Reserved | Circuit Type | 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Source ID + id_len + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Holding Time + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + PDU Length + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | Local Circuit ID | 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ +struct isis_p2p_hello_hdr { + uint8_t circuit_t; + uint8_t source_id[ISIS_SYS_ID_LEN]; + uint16_t hold_time; + uint16_t pdu_len; + uint8_t local_id; +} __attribute__((packed)); +#define ISIS_P2PHELLO_HDRLEN 12 + +#define L1_LINK_STATE 18 +#define L2_LINK_STATE 20 +#define FS_LINK_STATE 10 +#define L2_CIRCUIT_FLOODING_SCOPE 2 +struct isis_lsp_hdr { + uint16_t pdu_len; + uint16_t rem_lifetime; + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + uint32_t seqno; + uint16_t checksum; + uint8_t lsp_bits; +}; +#define ISIS_LSP_HDR_LEN 19 + +/* + * Since the length field of LSP Entries TLV is one byte long, and each LSP + * entry is LSP_ENTRIES_LEN (16) bytes long, the maximum number of LSP entries + * can be accommodated in a TLV is + * 255 / 16 = 15. + * + * Therefore, the maximum length of the LSP Entries TLV is + * 16 * 15 + 2 (header) = 242 bytes. + */ +#define MAX_LSP_ENTRIES_TLV_SIZE 242 + +#define L1_COMPLETE_SEQ_NUM 24 +#define L2_COMPLETE_SEQ_NUM 25 +/* + * L1 and L2 IS to IS complete sequence numbers PDU header + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + PDU Length + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Source ID + id_len + 1 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Start LSP ID + id_len + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + End LSP ID + id_len + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + */ +struct isis_complete_seqnum_hdr { + uint16_t pdu_len; + uint8_t source_id[ISIS_SYS_ID_LEN + 1]; + uint8_t start_lsp_id[ISIS_SYS_ID_LEN + 2]; + uint8_t stop_lsp_id[ISIS_SYS_ID_LEN + 2]; +}; +#define ISIS_CSNP_HDRLEN 25 + +#define L1_PARTIAL_SEQ_NUM 26 +#define L2_PARTIAL_SEQ_NUM 27 +/* + * L1 and L2 IS to IS partial sequence numbers PDU header + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + PDU Length + 2 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + Source ID + id_len + 1 + * +---------------------------------------------------------------+ + */ +struct isis_partial_seqnum_hdr { + uint16_t pdu_len; + uint8_t source_id[ISIS_SYS_ID_LEN + 1]; +}; +#define ISIS_PSNP_HDRLEN 9 + +#ifdef __SUNPRO_C +#pragma pack() +#endif + +/* + * Function for receiving IS-IS PDUs + */ +void isis_receive(struct event *thread); + +/* + * calling arguments for snp_process () + */ +#define ISIS_SNP_PSNP_FLAG 0 +#define ISIS_SNP_CSNP_FLAG 1 + +#define ISIS_AUTH_MD5_SIZE 16U + +/* + * Sending functions + */ +void send_hello_sched(struct isis_circuit *circuit, int level, long delay); +int send_csnp(struct isis_circuit *circuit, int level); +void send_l1_csnp(struct event *thread); +void send_l2_csnp(struct event *thread); +void send_l1_psnp(struct event *thread); +void send_l2_psnp(struct event *thread); +void send_lsp(struct isis_circuit *circuit, + struct isis_lsp *lsp, enum isis_tx_type tx_type); +void fill_fixed_hdr(uint8_t pdu_type, struct stream *stream); +int send_hello(struct isis_circuit *circuit, int level); +int isis_handle_pdu(struct isis_circuit *circuit, uint8_t *ssnpa); +void isis_log_pdu_drops(struct isis_area *area, const char *pdu_type); + +#endif /* _ZEBRA_ISIS_PDU_H */ diff --git a/isisd/isis_pdu_counter.c b/isisd/isis_pdu_counter.c new file mode 100644 index 0000000..a3605a3 --- /dev/null +++ b/isisd/isis_pdu_counter.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Routing protocol - isis_pdu_counter.c + * Copyright (C) 2018 Christian Franke, for NetDEF Inc. + */ + +#include + +#include "vty.h" + +#include "isisd/isisd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_pdu_counter.h" + +static int pdu_type_to_counter_index(uint8_t pdu_type) +{ + switch (pdu_type) { + case L1_LAN_HELLO: + return L1_LAN_HELLO_INDEX; + case L2_LAN_HELLO: + return L2_LAN_HELLO_INDEX; + case P2P_HELLO: + return P2P_HELLO_INDEX; + case L1_LINK_STATE: + return L1_LINK_STATE_INDEX; + case L2_LINK_STATE: + return L2_LINK_STATE_INDEX; + case FS_LINK_STATE: + return FS_LINK_STATE_INDEX; + case L1_COMPLETE_SEQ_NUM: + return L1_COMPLETE_SEQ_NUM_INDEX; + case L2_COMPLETE_SEQ_NUM: + return L2_COMPLETE_SEQ_NUM_INDEX; + case L1_PARTIAL_SEQ_NUM: + return L1_PARTIAL_SEQ_NUM_INDEX; + case L2_PARTIAL_SEQ_NUM: + return L2_PARTIAL_SEQ_NUM_INDEX; + default: + return -1; + } +} + +static const char *pdu_counter_index_to_name(enum pdu_counter_index index) +{ + switch (index) { + case L1_LAN_HELLO_INDEX: + return " L1 IIH"; + case L2_LAN_HELLO_INDEX: + return " L2 IIH"; + case P2P_HELLO_INDEX: + return "P2P IIH"; + case L1_LINK_STATE_INDEX: + return " L1 LSP"; + case L2_LINK_STATE_INDEX: + return " L2 LSP"; + case FS_LINK_STATE_INDEX: + return " FS LSP"; + case L1_COMPLETE_SEQ_NUM_INDEX: + return "L1 CSNP"; + case L2_COMPLETE_SEQ_NUM_INDEX: + return "L2 CSNP"; + case L1_PARTIAL_SEQ_NUM_INDEX: + return "L1 PSNP"; + case L2_PARTIAL_SEQ_NUM_INDEX: + return "L2 PSNP"; + case PDU_COUNTER_SIZE: + return "???????"; + } + + assert(!"Reached end of function where we were not expecting to"); +} + +void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type) +{ + int index = pdu_type_to_counter_index(pdu_type); + + if (index < 0) + return; + + counter[index]++; +} + +void pdu_counter_print(struct vty *vty, const char *prefix, + pdu_counter_t counter) +{ + for (int i = 0; i < PDU_COUNTER_SIZE; i++) { + if (!counter[i]) + continue; + vty_out(vty, "%s%s: %" PRIu64 "\n", prefix, + pdu_counter_index_to_name(i), counter[i]); + } +} + +void pdu_counter_count_drop(struct isis_area *area, uint8_t pdu_type) +{ + pdu_counter_count(area->pdu_drop_counters, pdu_type); + + if (area->log_pdu_drops) { + isis_log_pdu_drops( + area, pdu_counter_index_to_name( + pdu_type_to_counter_index(pdu_type))); + } +} + +uint64_t pdu_counter_get_count(pdu_counter_t counter, uint8_t pdu_type) +{ + int index = pdu_type_to_counter_index(pdu_type); + + if (index < 0) + return -1; + return counter[index]; +} diff --git a/isisd/isis_pdu_counter.h b/isisd/isis_pdu_counter.h new file mode 100644 index 0000000..5c35b4f --- /dev/null +++ b/isisd/isis_pdu_counter.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Routing protocol - isis_pdu_counter.c + * Copyright (C) 2018 Christian Franke, for NetDEF Inc. + */ +#ifndef ISIS_PDU_COUNTER_H +#define ISIS_PDU_COUNTER_H + +enum pdu_counter_index { + L1_LAN_HELLO_INDEX = 0, + L2_LAN_HELLO_INDEX, + P2P_HELLO_INDEX, + L1_LINK_STATE_INDEX, + L2_LINK_STATE_INDEX, + FS_LINK_STATE_INDEX, + L1_COMPLETE_SEQ_NUM_INDEX, + L2_COMPLETE_SEQ_NUM_INDEX, + L1_PARTIAL_SEQ_NUM_INDEX, + L2_PARTIAL_SEQ_NUM_INDEX, + PDU_COUNTER_SIZE +}; +typedef uint64_t pdu_counter_t[PDU_COUNTER_SIZE]; + +void pdu_counter_print(struct vty *vty, const char *prefix, + pdu_counter_t counter); +void pdu_counter_count(pdu_counter_t counter, uint8_t pdu_type); +void pdu_counter_count_drop(struct isis_area *area, uint8_t pdu_type); +uint64_t pdu_counter_get_count(pdu_counter_t counter, uint8_t pdu_type); + +#endif diff --git a/isisd/isis_pfpacket.c b/isisd/isis_pfpacket.c new file mode 100644 index 0000000..af69fac --- /dev/null +++ b/isisd/isis_pfpacket.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_pfpacket.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include +#if ISIS_METHOD == ISIS_METHOD_PFPACKET +#include /* the L2 protocols */ +#include + +#include + +#include "log.h" +#include "network.h" +#include "stream.h" +#include "if.h" +#include "lib_errors.h" +#include "vrf.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_flags.h" +#include "isisd/isisd.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_network.h" + +#include "privs.h" + +/* tcpdump -i eth0 'isis' -dd */ +static const struct sock_filter isisfilter[] = { + /* NB: we're in SOCK_DGRAM, so src/dst mac + length are stripped + * off! */ + /* The following BPF filter accepts IS-IS over LLC and IS-IS over + * ethertype 0x00fe. + * BPF assembly: + * l0: ldh [0] + * l1: jeq #0xfefe, l2, l4 + * l2: ldb [3] + * l3: jmp l7 + * l4: ldh proto + * l5: jeq #0x00fe, l6, l9 + * l6: ldb [0] + * l7: jeq #0x83, l8, l9 + * l8: ret #0x40000 + * l9: ret #0 */ + {0x28, 0, 0, 0000000000}, {0x15, 0, 2, 0x0000fefe}, + {0x30, 0, 0, 0x00000003}, {0x05, 0, 0, 0x00000003}, + {0x28, 0, 0, 0xfffff000}, {0x15, 0, 3, 0x000000fe}, + {0x30, 0, 0, 0000000000}, {0x15, 0, 1, 0x00000083}, + {0x06, 0, 0, 0x00040000}, {0x06, 0, 0, 0000000000}, +}; + +static const struct sock_fprog bpf = { + .len = array_size(isisfilter), + .filter = (struct sock_filter *)isisfilter, +}; + +/* + * Table 9 - Architectural constants for use with ISO 8802 subnetworks + * ISO 10589 - 8.4.8 + */ + +static const uint8_t ALL_L1_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x14}; +static const uint8_t ALL_L2_ISS[6] = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x15}; +static const uint8_t ALL_ISS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x05}; +static const uint8_t ALL_ESS[6] = {0x09, 0x00, 0x2B, 0x00, 0x00, 0x04}; + +static uint8_t discard_buff[8192]; + +/* + * if level is 0 we are joining p2p multicast + * FIXME: and the p2p multicast being ??? + */ +static int isis_multicast_join(int fd, int registerto, int if_num) +{ + struct packet_mreq mreq; + + memset(&mreq, 0, sizeof(mreq)); + mreq.mr_ifindex = if_num; + if (registerto) { + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + if (registerto == 1) + memcpy(&mreq.mr_address, ALL_L1_ISS, ETH_ALEN); + else if (registerto == 2) + memcpy(&mreq.mr_address, ALL_L2_ISS, ETH_ALEN); + else if (registerto == 3) + memcpy(&mreq.mr_address, ALL_ISS, ETH_ALEN); + else + memcpy(&mreq.mr_address, ALL_ESS, ETH_ALEN); + + } else { + mreq.mr_type = PACKET_MR_ALLMULTI; + } +#ifdef EXTREME_DEBUG + if (IS_DEBUG_EVENTS) + zlog_debug( + "%s: fd=%d, reg_to=%d, if_num=%d, address = %02x:%02x:%02x:%02x:%02x:%02x", + __func__, fd, registerto, if_num, mreq.mr_address[0], + mreq.mr_address[1], mreq.mr_address[2], + mreq.mr_address[3], mreq.mr_address[4], + mreq.mr_address[5]); +#endif /* EXTREME_DEBUG */ + if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, + sizeof(struct packet_mreq))) { + zlog_warn("%s: setsockopt(): %s", __func__, + safe_strerror(errno)); + return ISIS_WARNING; + } + + return ISIS_OK; +} + +static int open_packet_socket(struct isis_circuit *circuit) +{ + struct sockaddr_ll s_addr; + int fd, retval = ISIS_OK; + struct vrf *vrf = NULL; + + vrf = circuit->interface->vrf; + + fd = vrf_socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL), vrf->vrf_id, + vrf->name); + + if (fd < 0) { + zlog_warn("%s: socket() failed %s", __func__, + safe_strerror(errno)); + return ISIS_WARNING; + } + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))) { + zlog_warn("%s: SO_ATTACH_FILTER failed: %s", __func__, + safe_strerror(errno)); + } + + /* + * Bind to the physical interface + */ + memset(&s_addr, 0, sizeof(s_addr)); + s_addr.sll_family = AF_PACKET; + s_addr.sll_protocol = htons(ETH_P_ALL); + s_addr.sll_ifindex = circuit->interface->ifindex; + + if (bind(fd, (struct sockaddr *)(&s_addr), sizeof(struct sockaddr_ll)) + < 0) { + zlog_warn("%s: bind() failed: %s", __func__, + safe_strerror(errno)); + close(fd); + return ISIS_WARNING; + } + + circuit->fd = fd; + + if (if_is_broadcast(circuit->interface)) { + /* + * Join to multicast groups + * according to + * 8.4.2 - Broadcast subnetwork IIH PDUs + * FIXME: is there a case only one will fail?? + */ + /* joining ALL_L1_ISS */ + retval |= isis_multicast_join(circuit->fd, 1, + circuit->interface->ifindex); + /* joining ALL_L2_ISS */ + retval |= isis_multicast_join(circuit->fd, 2, + circuit->interface->ifindex); + /* joining ALL_ISS (used in RFC 5309 p2p-over-lan as well) */ + retval |= isis_multicast_join(circuit->fd, 3, + circuit->interface->ifindex); + } else { + retval = isis_multicast_join(circuit->fd, 0, + circuit->interface->ifindex); + } + + return retval; +} + +/* + * Create the socket and set the tx/rx funcs + */ +int isis_sock_init(struct isis_circuit *circuit) +{ + int retval = ISIS_OK; + + frr_with_privs(&isisd_privs) { + + retval = open_packet_socket(circuit); + + if (retval != ISIS_OK) { + zlog_warn("%s: could not initialize the socket", + __func__); + break; + } + + /* Assign Rx and Tx callbacks are based on real if type */ + if (if_is_broadcast(circuit->interface)) { + circuit->tx = isis_send_pdu_bcast; + circuit->rx = isis_recv_pdu_bcast; + } else if (if_is_pointopoint(circuit->interface)) { + circuit->tx = isis_send_pdu_p2p; + circuit->rx = isis_recv_pdu_p2p; + } else { + zlog_warn("%s: unknown circuit type", __func__); + retval = ISIS_WARNING; + break; + } + } + + return retval; +} + +static inline int llc_check(uint8_t *llc) +{ + if (*llc != ISO_SAP || *(llc + 1) != ISO_SAP || *(llc + 2) != 3) + return 0; + + return 1; +} + +int isis_recv_pdu_bcast(struct isis_circuit *circuit, uint8_t *ssnpa) +{ + int bytesread, addr_len; + struct sockaddr_ll s_addr; + uint8_t llc[LLC_LEN]; + + addr_len = sizeof(s_addr); + + memset(&s_addr, 0, sizeof(s_addr)); + + bytesread = + recvfrom(circuit->fd, (void *)&llc, LLC_LEN, MSG_PEEK, + (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); + + if ((bytesread < 0) + || (s_addr.sll_ifindex != (int)circuit->interface->ifindex)) { + if (bytesread < 0) { + zlog_warn( + "%s: ifname %s, fd %d, bytesread %d, recvfrom(): %s", + __func__, circuit->interface->name, circuit->fd, + bytesread, safe_strerror(errno)); + } + if (s_addr.sll_ifindex != (int)circuit->interface->ifindex) { + zlog_warn( + "packet is received on multiple interfaces: socket interface %d, circuit interface %d, packet type %u", + s_addr.sll_ifindex, circuit->interface->ifindex, + s_addr.sll_pkttype); + } + + /* get rid of the packet */ + bytesread = recvfrom(circuit->fd, discard_buff, + sizeof(discard_buff), MSG_DONTWAIT, + (struct sockaddr *)&s_addr, + (socklen_t *)&addr_len); + + if (bytesread < 0) + zlog_warn("%s: recvfrom() failed", __func__); + + return ISIS_WARNING; + } + /* + * Filtering by llc field, discard packets sent by this host (other + * circuit) + */ + if (!llc_check(llc) || s_addr.sll_pkttype == PACKET_OUTGOING) { + /* Read the packet into discard buff */ + bytesread = recvfrom(circuit->fd, discard_buff, + sizeof(discard_buff), MSG_DONTWAIT, + (struct sockaddr *)&s_addr, + (socklen_t *)&addr_len); + if (bytesread < 0) + zlog_warn("%s: recvfrom() failed", __func__); + return ISIS_WARNING; + } + + /* Ensure that we have enough space for a pdu padded to fill the mtu */ + unsigned int max_size = + circuit->interface->mtu > circuit->interface->mtu6 + ? circuit->interface->mtu + : circuit->interface->mtu6; + uint8_t temp_buff[max_size]; + bytesread = + recvfrom(circuit->fd, temp_buff, max_size, MSG_DONTWAIT, + (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); + if (bytesread < 0) { + zlog_warn("%s: recvfrom() failed", __func__); + return ISIS_WARNING; + } + /* then we lose the LLC */ + stream_write(circuit->rcv_stream, temp_buff + LLC_LEN, + bytesread - LLC_LEN); + memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen); + + return ISIS_OK; +} + +int isis_recv_pdu_p2p(struct isis_circuit *circuit, uint8_t *ssnpa) +{ + int bytesread, addr_len; + struct sockaddr_ll s_addr; + + memset(&s_addr, 0, sizeof(s_addr)); + addr_len = sizeof(s_addr); + + /* we can read directly to the stream */ + (void)stream_recvfrom( + circuit->rcv_stream, circuit->fd, circuit->interface->mtu, 0, + (struct sockaddr *)&s_addr, (socklen_t *)&addr_len); + + if (s_addr.sll_pkttype == PACKET_OUTGOING) { + /* Read the packet into discard buff */ + bytesread = recvfrom(circuit->fd, discard_buff, + sizeof(discard_buff), MSG_DONTWAIT, + (struct sockaddr *)&s_addr, + (socklen_t *)&addr_len); + if (bytesread < 0) + zlog_warn("%s: recvfrom() failed", __func__); + return ISIS_WARNING; + } + + /* If we don't have protocol type 0x00FE which is + * ISO over GRE we exit with pain :) + */ + if (ntohs(s_addr.sll_protocol) != 0x00FE) { + zlog_warn("%s: protocol mismatch(): %X", __func__, + ntohs(s_addr.sll_protocol)); + return ISIS_WARNING; + } + + memcpy(ssnpa, &s_addr.sll_addr, s_addr.sll_halen); + + return ISIS_OK; +} + +int isis_send_pdu_bcast(struct isis_circuit *circuit, int level) +{ + struct msghdr msg; + struct iovec iov[2]; + char temp_buff[LLC_LEN]; + + /* we need to do the LLC in here because of P2P circuits, which will + * not need it + */ + struct sockaddr_ll sa; + + stream_set_getp(circuit->snd_stream, 0); + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + + size_t frame_size = stream_get_endp(circuit->snd_stream) + LLC_LEN; + sa.sll_protocol = htons(isis_ethertype(frame_size)); + sa.sll_ifindex = circuit->interface->ifindex; + sa.sll_halen = ETH_ALEN; + /* RFC5309 section 4.1 recommends ALL_ISS */ + if (circuit->circ_type == CIRCUIT_T_P2P) + memcpy(&sa.sll_addr, ALL_ISS, ETH_ALEN); + else if (level == 1) + memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN); + else + memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN); + + /* on a broadcast circuit */ + /* first we put the LLC in */ + temp_buff[0] = 0xFE; + temp_buff[1] = 0xFE; + temp_buff[2] = 0x03; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(struct sockaddr_ll); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + iov[0].iov_base = temp_buff; + iov[0].iov_len = LLC_LEN; + iov[1].iov_base = circuit->snd_stream->data; + iov[1].iov_len = stream_get_endp(circuit->snd_stream); + + if (sendmsg(circuit->fd, &msg, 0) < 0) { + zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s", + circuit->interface->name, safe_strerror(errno)); + if (ERRNO_IO_RETRY(errno)) + return ISIS_WARNING; + return ISIS_ERROR; + } + return ISIS_OK; +} + +int isis_send_pdu_p2p(struct isis_circuit *circuit, int level) +{ + struct sockaddr_ll sa; + ssize_t rv; + + stream_set_getp(circuit->snd_stream, 0); + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_ifindex = circuit->interface->ifindex; + sa.sll_halen = ETH_ALEN; + if (level == 1) + memcpy(&sa.sll_addr, ALL_L1_ISS, ETH_ALEN); + else + memcpy(&sa.sll_addr, ALL_L2_ISS, ETH_ALEN); + + + /* lets try correcting the protocol */ + sa.sll_protocol = htons(0x00FE); + rv = sendto(circuit->fd, circuit->snd_stream->data, + stream_get_endp(circuit->snd_stream), 0, + (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)); + if (rv < 0) { + zlog_warn("IS-IS pfpacket: could not transmit packet on %s: %s", + circuit->interface->name, safe_strerror(errno)); + if (ERRNO_IO_RETRY(errno)) + return ISIS_WARNING; + return ISIS_ERROR; + } + return ISIS_OK; +} + +#endif /* ISIS_METHOD == ISIS_METHOD_PFPACKET */ diff --git a/isisd/isis_redist.c b/isisd/isis_redist.c new file mode 100644 index 0000000..2cb08db --- /dev/null +++ b/isisd/isis_redist.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_redist.c + * + * Copyright (C) 2013-2015 Christian Franke + */ + +#include + +#include "command.h" +#include "if.h" +#include "linklist.h" +#include "memory.h" +#include "prefix.h" +#include "routemap.h" +#include "stream.h" +#include "table.h" +#include "vty.h" +#include "srcdest_table.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_route.h" +#include "isisd/isis_zebra.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_EXT_ROUTE, "ISIS redistributed route"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_EXT_INFO, "ISIS redistributed route info"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_RMAP_NAME, "ISIS redistribute route-map name"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_REDISTRIBUTE, "ISIS redistribute"); + +static int redist_protocol(int family) +{ + if (family == AF_INET) + return 0; + if (family == AF_INET6) + return 1; + + assert(!"Unsupported address family!"); + return 0; +} + +afi_t afi_for_redist_protocol(int protocol) +{ + if (protocol == 0) + return AFI_IP; + if (protocol == 1) + return AFI_IP6; + + assert(!"Unknown redist protocol!"); + return AFI_IP; +} + +static struct route_table *get_ext_info(struct isis *i, int family) +{ + int protocol = redist_protocol(family); + + return i->ext_info[protocol]; +} + +static struct isis_redist *isis_redist_lookup(struct isis_area *area, + int family, int type, int level, + uint16_t table) +{ + int protocol = redist_protocol(family); + struct listnode *node; + struct isis_redist *red; + + if (area->redist_settings[protocol][type][level - 1]) { + for (ALL_LIST_ELEMENTS_RO(area->redist_settings[protocol][type] + [level - 1], + node, red)) + if (red->table == table) + return red; + } + return NULL; +} + +static struct isis_redist *isis_redist_get(struct isis_area *area, int family, + int type, int level, uint16_t table) +{ + struct isis_redist *red; + int protocol; + + red = isis_redist_lookup(area, family, type, level, table); + if (red) + return red; + + protocol = redist_protocol(family); + if (area->redist_settings[protocol][type][level - 1] == NULL) + area->redist_settings[protocol][type][level - 1] = list_new(); + + red = XCALLOC(MTYPE_ISIS_REDISTRIBUTE, sizeof(struct isis_redist)); + red->table = table; + + listnode_add(area->redist_settings[protocol][type][level - 1], red); + return red; +} + +struct route_table *get_ext_reach(struct isis_area *area, int family, int level) +{ + int protocol = redist_protocol(family); + + return area->ext_reach[protocol][level - 1]; +} + +/* Install external reachability information into a + * specific area for a specific level. + * Schedule an lsp regenerate if necessary */ +static void isis_redist_install(struct isis_area *area, int level, + const struct prefix *p, + const struct prefix_ipv6 *src_p, + struct isis_ext_info *info) +{ + int family = p->family; + struct route_table *er_table = get_ext_reach(area, family, level); + struct route_node *er_node; + + if (!er_table) { + zlog_warn( + "%s: External reachability table of area %s is not initialized.", + __func__, area->area_tag); + return; + } + + er_node = srcdest_rnode_get(er_table, p, src_p); + if (er_node->info) { + route_unlock_node(er_node); + + /* Don't update/reschedule lsp generation if nothing changed. */ + if (!memcmp(er_node->info, info, sizeof(*info))) + return; + } else { + er_node->info = XMALLOC(MTYPE_ISIS_EXT_INFO, sizeof(*info)); + } + + memcpy(er_node->info, info, sizeof(*info)); + lsp_regenerate_schedule(area, level, 0); +} + +/* Remove external reachability information from a + * specific area for a specific level. + * Schedule an lsp regenerate if necessary. */ +static void isis_redist_uninstall(struct isis_area *area, int level, + const struct prefix *p, + const struct prefix_ipv6 *src_p) +{ + int family = p->family; + struct route_table *er_table = get_ext_reach(area, family, level); + struct route_node *er_node; + + if (!er_table) { + zlog_warn( + "%s: External reachability table of area %s is not initialized.", + __func__, area->area_tag); + return; + } + + er_node = srcdest_rnode_lookup(er_table, p, src_p); + if (!er_node) + return; + else + route_unlock_node(er_node); + + if (!er_node->info) + return; + + XFREE(MTYPE_ISIS_EXT_INFO, er_node->info); + route_unlock_node(er_node); + lsp_regenerate_schedule(area, level, 0); +} + +/* Update external reachability info of area for a given level + * and prefix, using the given redistribution settings. */ +static void isis_redist_update_ext_reach(struct isis_area *area, int level, + struct isis_redist *redist, + const struct prefix *p, + const struct prefix_ipv6 *src_p, + struct isis_ext_info *info) +{ + struct isis_ext_info area_info; + route_map_result_t map_ret; + + memcpy(&area_info, info, sizeof(area_info)); + area_info.metric = redist->metric; + + if (redist->map_name) { + map_ret = route_map_apply(redist->map, p, &area_info); + if (map_ret == RMAP_DENYMATCH) + area_info.distance = 255; + } + + /* Allow synthesized default routes only on always orignate */ + if (area_info.origin == DEFAULT_ROUTE + && redist->redist != DEFAULT_ORIGINATE_ALWAYS) + area_info.distance = 255; + + if (area_info.distance < 255) + isis_redist_install(area, level, p, src_p, &area_info); + else + isis_redist_uninstall(area, level, p, src_p); +} + +static void isis_redist_ensure_default(struct isis *isis, int family) +{ + struct prefix p; + struct route_table *ei_table = get_ext_info(isis, family); + struct route_node *ei_node; + struct isis_ext_info *info; + + if (family == AF_INET) { + p.family = AF_INET; + p.prefixlen = 0; + memset(&p.u.prefix4, 0, sizeof(p.u.prefix4)); + } else if (family == AF_INET6) { + p.family = AF_INET6; + p.prefixlen = 0; + memset(&p.u.prefix6, 0, sizeof(p.u.prefix6)); + } else + assert(!"Unknown family!"); + + ei_node = srcdest_rnode_get(ei_table, &p, NULL); + if (ei_node->info) { + route_unlock_node(ei_node); + return; + } + + ei_node->info = + XCALLOC(MTYPE_ISIS_EXT_INFO, sizeof(struct isis_ext_info)); + + info = ei_node->info; + info->origin = DEFAULT_ROUTE; + info->distance = 254; + info->metric = MAX_WIDE_PATH_METRIC; +} + +static int _isis_redist_table_is_present(const struct lyd_node *dnode, void *arg) +{ + struct isis_redist_table_present_args *rtda = arg; + + /* This entry is the caller, so skip it. */ + if (yang_dnode_get_uint16(dnode, "table") != + (uint16_t)atoi(rtda->rtda_table)) + return YANG_ITER_CONTINUE; + + /* found */ + rtda->rtda_found = true; + return YANG_ITER_CONTINUE; +} + +static int _isis_redist_table_get_first_cb(const struct lyd_node *dnode, + void *arg) +{ + uint16_t *table = arg; + + *table = yang_dnode_get_uint16(dnode, "table"); + return YANG_ITER_STOP; +} + +uint16_t isis_redist_table_get_first(const struct vty *vty, + struct isis_redist_table_present_args *rtda) +{ + uint16_t table = 0; + + yang_dnode_iterate(_isis_redist_table_get_first_cb, &table, + vty->candidate_config->dnode, + "%s/redistribute/%s[protocol='table'][level='%s']/table", + VTY_CURR_XPATH, rtda->rtda_ip, rtda->rtda_level); + return table; +} + +bool isis_redist_table_is_present(const struct vty *vty, + struct isis_redist_table_present_args *rtda) +{ + rtda->rtda_found = false; + yang_dnode_iterate(_isis_redist_table_is_present, rtda, + vty->candidate_config->dnode, + "%s/redistribute/%s[protocol='table'][level='%s']/table", + VTY_CURR_XPATH, rtda->rtda_ip, rtda->rtda_level); + + return rtda->rtda_found; +} + +/* Handle notification about route being added */ +void isis_redist_add(struct isis *isis, int type, struct prefix *p, + struct prefix_ipv6 *src_p, uint8_t distance, + uint32_t metric, const route_tag_t tag, uint16_t table) +{ + int family = p->family; + struct route_table *ei_table = get_ext_info(isis, family); + struct route_node *ei_node; + struct isis_ext_info *info; + struct listnode *node; + struct isis_area *area; + int level; + struct isis_redist *redist; + + zlog_debug("%s: New route %pFX from %s: distance %d.", __func__, p, + zebra_route_string(type), distance); + + if (!ei_table) { + zlog_warn("%s: External information table not initialized.", + __func__); + return; + } + + ei_node = srcdest_rnode_get(ei_table, p, src_p); + if (ei_node->info) + route_unlock_node(ei_node); + else + ei_node->info = XCALLOC(MTYPE_ISIS_EXT_INFO, + sizeof(struct isis_ext_info)); + + info = ei_node->info; + info->origin = type; + info->distance = distance; + info->metric = metric; + info->tag = tag; + + if (is_default_prefix(p) + && (!src_p || !src_p->prefixlen)) { + type = DEFAULT_ROUTE; + } + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + for (level = 1; level <= ISIS_LEVELS; level++) { + redist = isis_redist_lookup(area, family, type, level, + table); + if (!redist || !redist->redist) + continue; + + isis_redist_update_ext_reach(area, level, redist, p, + src_p, info); + } +} + +void isis_redist_delete(struct isis *isis, int type, struct prefix *p, + struct prefix_ipv6 *src_p, uint16_t table) +{ + int family = p->family; + struct route_table *ei_table = get_ext_info(isis, family); + struct route_node *ei_node; + struct listnode *node; + struct isis_area *area; + int level; + struct isis_redist *redist; + + zlog_debug("%s: Removing route %pFX from %s.", __func__, p, + zebra_route_string(type)); + + if (is_default_prefix(p) + && (!src_p || !src_p->prefixlen)) { + /* Don't remove default route but add synthetic route for use + * by "default-information originate always". Areas without the + * "always" setting will ignore routes with origin + * DEFAULT_ROUTE. */ + isis_redist_add(isis, DEFAULT_ROUTE, p, NULL, 254, + MAX_WIDE_PATH_METRIC, 0, table); + return; + } + + if (!ei_table) { + zlog_warn("%s: External information table not initialized.", + __func__); + return; + } + + ei_node = srcdest_rnode_lookup(ei_table, p, src_p); + if (!ei_node || !ei_node->info) { + zlog_warn( + "%s: Got a delete for %s route %pFX, but that route was never added.", + __func__, zebra_route_string(type), p); + if (ei_node) + route_unlock_node(ei_node); + return; + } + route_unlock_node(ei_node); + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + for (level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + redist = isis_redist_lookup(area, family, type, level, + table); + if (!redist || !redist->redist) + continue; + + isis_redist_uninstall(area, level, p, src_p); + } + + XFREE(MTYPE_ISIS_EXT_INFO, ei_node->info); + route_unlock_node(ei_node); +} + +static void isis_redist_routemap_set(struct isis_redist *redist, + const char *routemap) +{ + if (redist->map_name) { + XFREE(MTYPE_ISIS_RMAP_NAME, redist->map_name); + route_map_counter_decrement(redist->map); + redist->map = NULL; + } + + if (routemap && strlen(routemap)) { + redist->map_name = XSTRDUP(MTYPE_ISIS_RMAP_NAME, routemap); + redist->map = route_map_lookup_by_name(routemap); + route_map_counter_increment(redist->map); + } +} + +void isis_redist_free(struct isis *isis) +{ + struct route_node *rn; + int i; + + for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) { + if (!isis->ext_info[i]) + continue; + + for (rn = route_top(isis->ext_info[i]); rn; + rn = srcdest_route_next(rn)) { + if (rn->info) + XFREE(MTYPE_ISIS_EXT_INFO, rn->info); + } + + route_table_finish(isis->ext_info[i]); + isis->ext_info[i] = NULL; + } +} + +void isis_redist_set(struct isis_area *area, int level, int family, int type, + uint32_t metric, const char *routemap, int originate_type, + uint16_t table) +{ + int protocol = redist_protocol(family); + struct isis_redist *redist = isis_redist_get(area, family, type, level, + table); + int i; + struct route_table *ei_table; + struct route_node *rn; + struct isis_ext_info *info; + + redist->redist = (type == DEFAULT_ROUTE) ? originate_type : 1; + redist->metric = metric; + isis_redist_routemap_set(redist, routemap); + + if (!area->ext_reach[protocol][level - 1]) { + area->ext_reach[protocol][level - 1] = srcdest_table_init(); + } + + for (i = 0; i < REDIST_PROTOCOL_COUNT; i++) { + if (!area->isis->ext_info[i]) { + area->isis->ext_info[i] = srcdest_table_init(); + } + } + + isis_zebra_redistribute_set(afi_for_redist_protocol(protocol), type, + area->isis->vrf_id, redist->table); + + if (type == DEFAULT_ROUTE && originate_type == DEFAULT_ORIGINATE_ALWAYS) + isis_redist_ensure_default(area->isis, family); + + ei_table = get_ext_info(area->isis, family); + for (rn = route_top(ei_table); rn; rn = srcdest_route_next(rn)) { + if (!rn->info) + continue; + info = rn->info; + + const struct prefix *p, *src_p; + + srcdest_rnode_prefixes(rn, &p, &src_p); + + if (type == DEFAULT_ROUTE) { + if (!is_default_prefix(p) + || (src_p && src_p->prefixlen)) { + continue; + } + } else { + if (info->origin != type) + continue; + } + + isis_redist_update_ext_reach(area, level, redist, p, + (const struct prefix_ipv6 *)src_p, + info); + } +} + +void isis_redist_unset(struct isis_area *area, int level, int family, int type, + uint16_t table) +{ + struct isis_redist *redist = isis_redist_lookup(area, family, type, + level, table); + struct route_table *er_table = get_ext_reach(area, family, level); + struct route_node *rn; + struct isis_ext_info *info; + struct list *redist_list; + int protocol = redist_protocol(family); + + if (!redist || !redist->redist) + return; + + redist->redist = 0; + + redist_list = area->redist_settings[protocol][type][level - 1]; + listnode_delete(redist_list, redist); + XFREE(MTYPE_ISIS_REDISTRIBUTE, redist); + + if (!er_table) { + zlog_warn("%s: External reachability table uninitialized.", + __func__); + return; + } + + for (rn = route_top(er_table); rn; rn = srcdest_route_next(rn)) { + if (!rn->info) + continue; + info = rn->info; + + const struct prefix *p, *src_p; + srcdest_rnode_prefixes(rn, &p, &src_p); + + if (type == DEFAULT_ROUTE) { + if (!is_default_prefix(p) + || (src_p && src_p->prefixlen)) { + continue; + } + } else { + if (info->origin != type) + continue; + } + + XFREE(MTYPE_ISIS_EXT_INFO, rn->info); + route_unlock_node(rn); + } + + lsp_regenerate_schedule(area, level, 0); + isis_zebra_redistribute_unset(afi_for_redist_protocol(protocol), type, + area->isis->vrf_id, table); +} + +void isis_redist_area_finish(struct isis_area *area) +{ + struct route_node *rn; + int protocol; + int level; + int type; + struct isis_redist *redist; + struct listnode *node, *nnode; + struct list *redist_list; + + for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++) + for (level = 0; level < ISIS_LEVELS; level++) { + for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) { + redist_list = area->redist_settings[protocol] + [type][level]; + if (!redist_list) + continue; + for (ALL_LIST_ELEMENTS(redist_list, node, nnode, + redist)) { + redist->redist = 0; + XFREE(MTYPE_ISIS_RMAP_NAME, + redist->map_name); + isis_zebra_redistribute_unset( + afi_for_redist_protocol(protocol), + type, area->isis->vrf_id, + redist->table); + listnode_delete(redist_list, redist); + XFREE(MTYPE_ISIS_REDISTRIBUTE, redist); + } + list_delete(&redist_list); + } + if (!area->ext_reach[protocol][level]) + continue; + for (rn = route_top(area->ext_reach[protocol][level]); + rn; rn = srcdest_route_next(rn)) { + if (rn->info) + XFREE(MTYPE_ISIS_EXT_INFO, rn->info); + } + route_table_finish(area->ext_reach[protocol][level]); + area->ext_reach[protocol][level] = NULL; + } +} + +#ifdef FABRICD +DEFUN (isis_redistribute, + isis_redistribute_cmd, + "redistribute " + " [{metric (0-16777215)|route-map RMAP_NAME}]", + REDIST_STR + "Redistribute IPv4 routes\n" + PROTO_IP_REDIST_HELP + "Redistribute IPv6 routes\n" + PROTO_IP6_REDIST_HELP + "Metric for redistributed routes\n" + "ISIS default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + int idx_afi = 1; + int idx_protocol = 2; + int idx_metric_rmap = 1; + VTY_DECLVAR_CONTEXT(isis_area, area); + int family; + int afi; + int type; + int level; + unsigned long metric = 0; + const char *routemap = NULL; + + family = str2family(argv[idx_afi]->text); + if (family < 0) + return CMD_WARNING_CONFIG_FAILED; + + afi = family2afi(family); + if (!afi) + return CMD_WARNING_CONFIG_FAILED; + + type = proto_redistnum(afi, argv[idx_protocol]->text); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + level = 2; + + if ((area->is_type & level) != level) { + vty_out(vty, "Node is not a level-%d IS\n", level); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv_find(argv, argc, "metric", &idx_metric_rmap)) { + metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10); + } + + idx_metric_rmap = 1; + if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) { + routemap = argv[idx_metric_rmap + 1]->arg; + } + + isis_redist_set(area, level, family, type, metric, routemap, 0, 0); + return 0; +} + +DEFUN (no_isis_redistribute, + no_isis_redistribute_cmd, + "no redistribute ", + NO_STR + REDIST_STR + "Redistribute IPv4 routes\n" + PROTO_IP_REDIST_HELP + "Redistribute IPv6 routes\n" + PROTO_IP6_REDIST_HELP) +{ + int idx_afi = 2; + int idx_protocol = 3; + VTY_DECLVAR_CONTEXT(isis_area, area); + int type; + int level; + int family; + int afi; + + family = str2family(argv[idx_afi]->arg); + if (family < 0) + return CMD_WARNING_CONFIG_FAILED; + + afi = family2afi(family); + if (!afi) + return CMD_WARNING_CONFIG_FAILED; + + type = proto_redistnum(afi, argv[idx_protocol]->text); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + level = 2; + + isis_redist_unset(area, level, family, type, 0); + return 0; +} + +DEFUN (isis_default_originate, + isis_default_originate_cmd, + "default-information originate [always] [{metric (0-16777215)|route-map RMAP_NAME}]", + "Control distribution of default information\n" + "Distribute a default route\n" + "Distribute default route for IPv4\n" + "Distribute default route for IPv6\n" + "Always advertise default route\n" + "Metric for default route\n" + "ISIS default metric\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + int idx_afi = 2; + int idx_always = fabricd ? 3 : 4; + int idx_metric_rmap = 1; + VTY_DECLVAR_CONTEXT(isis_area, area); + int family; + int originate_type = DEFAULT_ORIGINATE; + int level; + unsigned long metric = 0; + const char *routemap = NULL; + + family = str2family(argv[idx_afi]->text); + if (family < 0) + return CMD_WARNING_CONFIG_FAILED; + + level = 2; + + if ((area->is_type & level) != level) { + vty_out(vty, "Node is not a level-%d IS\n", level); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc > idx_always && strmatch(argv[idx_always]->text, "always")) { + originate_type = DEFAULT_ORIGINATE_ALWAYS; + idx_metric_rmap++; + } + + if (argv_find(argv, argc, "metric", &idx_metric_rmap)) { + metric = strtoul(argv[idx_metric_rmap + 1]->arg, NULL, 10); + } + + idx_metric_rmap = 1; + if (argv_find(argv, argc, "route-map", &idx_metric_rmap)) { + routemap = argv[idx_metric_rmap + 1]->arg; + } + + if (family == AF_INET6 && originate_type != DEFAULT_ORIGINATE_ALWAYS) { + vty_out(vty, + "Zebra doesn't implement default-originate for IPv6 yet\n"); + vty_out(vty, + "so use with care or use default-originate always.\n"); + } + + isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, + originate_type, 0); + return 0; +} + +DEFUN (no_isis_default_originate, + no_isis_default_originate_cmd, + "no default-information originate ", + NO_STR + "Control distribution of default information\n" + "Distribute a default route\n" + "Distribute default route for IPv4\n" + "Distribute default route for IPv6\n") +{ + int idx_afi = 3; + VTY_DECLVAR_CONTEXT(isis_area, area); + int family; + int level; + + family = str2family(argv[idx_afi]->text); + if (family < 0) + return CMD_WARNING_CONFIG_FAILED; + + level = 2; + + isis_redist_unset(area, level, family, DEFAULT_ROUTE, 0); + return 0; +} +#endif /* ifdef FABRICD */ + +int isis_redist_config_write(struct vty *vty, struct isis_area *area, + int family) +{ + int type; + int level; + int write = 0; + struct isis_redist *redist; + struct list *redist_list; + const char *family_str; + struct listnode *node; + + if (family == AF_INET) + family_str = "ipv4"; + else if (family == AF_INET6) + family_str = "ipv6"; + else + return 0; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + if (type == PROTO_TYPE) + continue; + + for (level = 1; level <= ISIS_LEVELS; level++) { + redist_list = area->redist_settings[redist_protocol( + family)][type][level - 1]; + if (!redist_list) + continue; + for (ALL_LIST_ELEMENTS_RO(redist_list, node, redist)) { + if (!redist->redist) + continue; + vty_out(vty, " redistribute %s %s", family_str, + zebra_route_string(type)); + if (type == ZEBRA_ROUTE_TABLE) + vty_out(vty, " %u", redist->table); + if (!fabricd) + vty_out(vty, " level-%d", level); + if (redist->metric) + vty_out(vty, " metric %u", + redist->metric); + if (redist->map_name) + vty_out(vty, " route-map %s", + redist->map_name); + vty_out(vty, "\n"); + write++; + } + } + } + + for (level = 1; level <= ISIS_LEVELS; level++) { + redist = isis_redist_lookup(area, family, DEFAULT_ROUTE, level, + 0); + if (!redist) + continue; + if (!redist->redist) + continue; + vty_out(vty, " default-information originate %s", + family_str); + if (!fabricd) + vty_out(vty, " level-%d", level); + if (redist->redist == DEFAULT_ORIGINATE_ALWAYS) + vty_out(vty, " always"); + if (redist->metric) + vty_out(vty, " metric %u", redist->metric); + if (redist->map_name) + vty_out(vty, " route-map %s", redist->map_name); + vty_out(vty, "\n"); + write++; + } + + return write; +} + +void isis_redist_init(void) +{ +#ifdef FABRICD + install_element(ROUTER_NODE, &isis_redistribute_cmd); + install_element(ROUTER_NODE, &no_isis_redistribute_cmd); + + install_element(ROUTER_NODE, &isis_default_originate_cmd); + install_element(ROUTER_NODE, &no_isis_default_originate_cmd); +#endif /* ifdef FABRICD */ +} diff --git a/isisd/isis_redist.h b/isisd/isis_redist.h new file mode 100644 index 0000000..688f27e --- /dev/null +++ b/isisd/isis_redist.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_redist.h + * + * Copyright (C) 2013-2015 Christian Franke + */ + +#ifndef ISIS_REDIST_H +#define ISIS_REDIST_H + +#define REDIST_PROTOCOL_COUNT 2 + +#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX +#define DEFAULT_ORIGINATE 1 +#define DEFAULT_ORIGINATE_ALWAYS 2 + +struct isis_ext_info { + int origin; + uint32_t metric; + uint8_t distance; + route_tag_t tag; +}; + +struct isis_redist { + int redist; + uint32_t metric; + char *map_name; + struct route_map *map; + uint16_t table; +}; + +struct isis_redist_table_present_args { + /* from filter.h, struct acl_dup_args */ + const char *rtda_ip; + const char *rtda_level; + const char *rtda_table; + bool rtda_found; +}; + +struct isis; +struct isis_area; +struct prefix; +struct prefix_ipv6; +struct vty; + +afi_t afi_for_redist_protocol(int protocol); + +struct route_table *get_ext_reach(struct isis_area *area, int family, + int level); +void isis_redist_add(struct isis *isis, int type, struct prefix *p, + struct prefix_ipv6 *src_p, uint8_t distance, + uint32_t metric, route_tag_t tag, uint16_t instance); +void isis_redist_delete(struct isis *isis, int type, struct prefix *p, + struct prefix_ipv6 *src_p, uint16_t tableid); +int isis_redist_config_write(struct vty *vty, struct isis_area *area, + int family); +void isis_redist_init(void); +void isis_redist_area_finish(struct isis_area *area); + +void isis_redist_set(struct isis_area *area, int level, int family, int type, + uint32_t metric, const char *routemap, int originate_type, + uint16_t table); +void isis_redist_unset(struct isis_area *area, int level, int family, int type, + uint16_t table); + +void isis_redist_free(struct isis *isis); + +bool isis_redist_table_is_present(const struct vty *vty, + struct isis_redist_table_present_args *rtda); +uint16_t isis_redist_table_get_first(const struct vty *vty, + struct isis_redist_table_present_args *rtda); +#endif diff --git a/isisd/isis_route.c b/isisd/isis_route.c new file mode 100644 index 0000000..b907c96 --- /dev/null +++ b/isisd/isis_route.c @@ -0,0 +1,957 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_route.c + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * + * based on ../ospf6d/ospf6_route.[ch] + * by Yasuhiro Ohara + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "log.h" +#include "lib_errors.h" +#include "memory.h" +#include "prefix.h" +#include "hash.h" +#include "if.h" +#include "table.h" +#include "srcdest_table.h" + +#include "isis_constants.h" +#include "isis_common.h" +#include "isis_flags.h" +#include "isisd.h" +#include "isis_misc.h" +#include "isis_adjacency.h" +#include "isis_circuit.h" +#include "isis_pdu.h" +#include "isis_lsp.h" +#include "isis_spf.h" +#include "isis_spf_private.h" +#include "isis_route.h" +#include "isis_zebra.h" +#include "isis_flex_algo.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_NEXTHOP, "ISIS nexthop"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_ROUTE_INFO, "ISIS route info"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_ROUTE_TABLE_INFO, "ISIS route table info"); + + +DEFINE_HOOK(isis_route_update_hook, + (struct isis_area * area, struct prefix *prefix, + struct isis_route_info *route_info), + (area, prefix, route_info)); + +static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family, + union g_addr *ip, ifindex_t ifindex); +static void isis_route_update(struct isis_area *area, struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info); + +static struct mpls_label_stack * +label_stack_dup(const struct mpls_label_stack *const orig) +{ + struct mpls_label_stack *copy; + int array_size; + + if (orig == NULL) + return NULL; + + array_size = orig->num_labels * sizeof(mpls_label_t); + copy = XCALLOC(MTYPE_ISIS_NEXTHOP_LABELS, + sizeof(struct mpls_label_stack) + array_size); + copy->num_labels = orig->num_labels; + memcpy(copy->label, orig->label, array_size); + return copy; +} + +static struct isis_nexthop * +isis_nexthop_create(int family, const union g_addr *const ip, ifindex_t ifindex) +{ + struct isis_nexthop *nexthop; + + nexthop = XCALLOC(MTYPE_ISIS_NEXTHOP, sizeof(struct isis_nexthop)); + + nexthop->family = family; + nexthop->ifindex = ifindex; + nexthop->ip = *ip; + + return nexthop; +} + +static struct isis_nexthop * +isis_nexthop_dup(const struct isis_nexthop *const orig) +{ + struct isis_nexthop *nexthop; + + nexthop = isis_nexthop_create(orig->family, &orig->ip, orig->ifindex); + memcpy(nexthop->sysid, orig->sysid, ISIS_SYS_ID_LEN); + nexthop->sr = orig->sr; + nexthop->label_stack = label_stack_dup(orig->label_stack); + + return nexthop; +} + +void isis_nexthop_delete(struct isis_nexthop *nexthop) +{ + XFREE(MTYPE_ISIS_NEXTHOP_LABELS, nexthop->label_stack); + XFREE(MTYPE_ISIS_NEXTHOP, nexthop); +} + +static struct list *isis_nexthop_list_dup(const struct list *orig) +{ + struct list *copy; + struct listnode *node; + struct isis_nexthop *nh; + struct isis_nexthop *nhcopy; + + copy = list_new(); + for (ALL_LIST_ELEMENTS_RO(orig, node, nh)) { + nhcopy = isis_nexthop_dup(nh); + listnode_add(copy, nhcopy); + } + return copy; +} + +static struct isis_nexthop *nexthoplookup(struct list *nexthops, int family, + union g_addr *ip, ifindex_t ifindex) +{ + struct listnode *node; + struct isis_nexthop *nh; + + for (ALL_LIST_ELEMENTS_RO(nexthops, node, nh)) { + if (nh->ifindex != ifindex) + continue; + + /* if the IP is unspecified, return the first nexthop found on + * the interface + */ + if (!ip) + return nh; + + if (nh->family != family) + continue; + + switch (family) { + case AF_INET: + if (IPV4_ADDR_CMP(&nh->ip.ipv4, &ip->ipv4)) + continue; + break; + case AF_INET6: + if (IPV6_ADDR_CMP(&nh->ip.ipv6, &ip->ipv6)) + continue; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown address family [%d]", __func__, + family); + exit(1); + } + + return nh; + } + + return NULL; +} + +void adjinfo2nexthop(int family, struct list *nexthops, + struct isis_adjacency *adj, struct isis_sr_psid_info *sr, + struct mpls_label_stack *label_stack) +{ + struct isis_nexthop *nh; + union g_addr ip = {}; + + switch (family) { + case AF_INET: + for (unsigned int i = 0; i < adj->ipv4_address_count; i++) { + ip.ipv4 = adj->ipv4_addresses[i]; + + if (!nexthoplookup(nexthops, AF_INET, &ip, + adj->circuit->interface->ifindex)) { + nh = isis_nexthop_create( + AF_INET, &ip, + adj->circuit->interface->ifindex); + memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid)); + if (sr) + nh->sr = *sr; + nh->label_stack = label_stack; + listnode_add(nexthops, nh); + break; + } + } + break; + case AF_INET6: + for (unsigned int i = 0; i < adj->ll_ipv6_count; i++) { + ip.ipv6 = adj->ll_ipv6_addrs[i]; + + if (!nexthoplookup(nexthops, AF_INET6, &ip, + adj->circuit->interface->ifindex)) { + nh = isis_nexthop_create( + AF_INET6, &ip, + adj->circuit->interface->ifindex); + memcpy(nh->sysid, adj->sysid, sizeof(nh->sysid)); + if (sr) + nh->sr = *sr; + nh->label_stack = label_stack; + listnode_add(nexthops, nh); + break; + } + } + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address family [%d]", + __func__, family); + exit(1); + } +} + +static void isis_route_add_dummy_nexthops(struct isis_route_info *rinfo, + const uint8_t *sysid, + struct isis_sr_psid_info *sr, + struct mpls_label_stack *label_stack) +{ + struct isis_nexthop *nh; + + nh = XCALLOC(MTYPE_ISIS_NEXTHOP, sizeof(struct isis_nexthop)); + memcpy(nh->sysid, sysid, sizeof(nh->sysid)); + nh->sr = *sr; + nh->label_stack = label_stack; + listnode_add(rinfo->nexthops, nh); +} + +static struct isis_route_info * +isis_route_info_new(struct prefix *prefix, struct prefix_ipv6 *src_p, + uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr, + struct list *adjacencies, bool allow_ecmp) +{ + struct isis_route_info *rinfo; + struct isis_vertex_adj *vadj; + struct listnode *node; + + rinfo = XCALLOC(MTYPE_ISIS_ROUTE_INFO, sizeof(struct isis_route_info)); + + rinfo->nexthops = list_new(); + for (ALL_LIST_ELEMENTS_RO(adjacencies, node, vadj)) { + struct isis_spf_adj *sadj = vadj->sadj; + struct isis_adjacency *adj = sadj->adj; + struct isis_sr_psid_info *sr = &vadj->sr; + struct mpls_label_stack *label_stack = vadj->label_stack; + + /* + * Create dummy nexthops when running SPF on a testing + * environment. + */ + if (CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) { + isis_route_add_dummy_nexthops(rinfo, sadj->id, sr, + label_stack); + if (!allow_ecmp) + break; + continue; + } + + /* check for force resync this route */ + if (CHECK_FLAG(adj->circuit->flags, + ISIS_CIRCUIT_FLAPPED_AFTER_SPF)) + SET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC); + + adjinfo2nexthop(prefix->family, rinfo->nexthops, adj, sr, + label_stack); + if (!allow_ecmp) + break; + } + + rinfo->cost = cost; + rinfo->depth = depth; + rinfo->sr_algo[sr->algorithm] = *sr; + rinfo->sr_algo[sr->algorithm].nexthops = rinfo->nexthops; + rinfo->sr_algo[sr->algorithm].nexthops_backup = + rinfo->backup ? rinfo->backup->nexthops : NULL; + + return rinfo; +} + +static void isis_route_info_delete(struct isis_route_info *route_info) +{ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + if (!route_info->sr_algo[i].present) + continue; + + if (route_info->sr_algo[i].nexthops == route_info->nexthops) + continue; + + route_info->sr_algo[i].nexthops->del = + (void (*)(void *))isis_nexthop_delete; + list_delete(&route_info->sr_algo[i].nexthops); + } + + if (route_info->nexthops) { + route_info->nexthops->del = + (void (*)(void *))isis_nexthop_delete; + list_delete(&route_info->nexthops); + } + + XFREE(MTYPE_ISIS_ROUTE_INFO, route_info); +} + +void isis_route_node_cleanup(struct route_table *table, struct route_node *node) +{ + if (node->info) + isis_route_info_delete(node->info); +} + +struct isis_route_table_info *isis_route_table_info_alloc(uint8_t algorithm) +{ + struct isis_route_table_info *info; + + info = XCALLOC(MTYPE_ISIS_ROUTE_TABLE_INFO, sizeof(*info)); + info->algorithm = algorithm; + return info; +} + +void isis_route_table_info_free(void *info) +{ + XFREE(MTYPE_ISIS_ROUTE_TABLE_INFO, info); +} + +uint8_t isis_route_table_algorithm(const struct route_table *table) +{ + const struct isis_route_table_info *info = table->info; + + return info ? info->algorithm : 0; +} + +static bool isis_sr_psid_info_same(struct isis_sr_psid_info *new, + struct isis_sr_psid_info *old) +{ + if (new->present != old->present) + return false; + + if (new->label != old->label) + return false; + + if (new->sid.flags != old->sid.flags + || new->sid.value != old->sid.value) + return false; + + if (new->sid.algorithm != old->sid.algorithm) + return false; + + return true; +} + +static bool isis_label_stack_same(struct mpls_label_stack *new, + struct mpls_label_stack *old) +{ + if (!new && !old) + return true; + if (!new || !old) + return false; + if (new->num_labels != old->num_labels) + return false; + if (memcmp(&new->label, &old->label, + sizeof(mpls_label_t) * new->num_labels)) + return false; + + return true; +} + +static int isis_route_info_same(struct isis_route_info *new, + struct isis_route_info *old, char *buf, + size_t buf_size) +{ + struct listnode *node; + struct isis_nexthop *new_nh, *old_nh; + + if (new->cost != old->cost) { + if (buf) + snprintf(buf, buf_size, "cost (old: %u, new: %u)", + old->cost, new->cost); + return 0; + } + + if (new->depth != old->depth) { + if (buf) + snprintf(buf, buf_size, "depth (old: %u, new: %u)", + old->depth, new->depth); + return 0; + } + + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_sr_psid_info new_sr_algo; + struct isis_sr_psid_info old_sr_algo; + + new_sr_algo = new->sr_algo[i]; + old_sr_algo = old->sr_algo[i]; + + if (!isis_sr_psid_info_same(&new_sr_algo, &old_sr_algo)) { + if (buf) + snprintf( + buf, buf_size, + "SR input label algo-%u (old: %s, new: %s)", + i, old_sr_algo.present ? "yes" : "no", + new_sr_algo.present ? "yes" : "no"); + return 0; + } + } + + if (new->nexthops->count != old->nexthops->count) { + if (buf) + snprintf(buf, buf_size, "nhops num (old: %u, new: %u)", + old->nexthops->count, new->nexthops->count); + return 0; + } + + for (ALL_LIST_ELEMENTS_RO(new->nexthops, node, new_nh)) { + old_nh = nexthoplookup(old->nexthops, new_nh->family, + &new_nh->ip, new_nh->ifindex); + if (!old_nh) { + if (buf) + snprintf(buf, buf_size, + "new nhop"); /* TODO: print nhop */ + return 0; + } + if (!isis_sr_psid_info_same(&new_nh->sr, &old_nh->sr)) { + if (buf) + snprintf(buf, buf_size, "nhop SR label"); + return 0; + } + if (!isis_label_stack_same(new_nh->label_stack, + old_nh->label_stack)) { + if (buf) + snprintf(buf, buf_size, "nhop label stack"); + return 0; + } + } + + /* only the resync flag needs to be checked */ + if (CHECK_FLAG(new->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC) + != CHECK_FLAG(old->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC)) { + if (buf) + snprintf(buf, buf_size, "resync flag"); + return 0; + } + + return 1; +} + +struct isis_route_info * +isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p, + uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr, + struct list *adjacencies, bool allow_ecmp, + struct isis_area *area, struct route_table *table) +{ + struct route_node *route_node; + struct isis_route_info *rinfo_new, *rinfo_old, *route_info = NULL; + char change_buf[64]; + + if (!table) + return NULL; + + rinfo_new = isis_route_info_new(prefix, src_p, cost, depth, sr, + adjacencies, allow_ecmp); + route_node = srcdest_rnode_get(table, prefix, src_p); + + rinfo_old = route_node->info; + if (!rinfo_old) { + if (IS_DEBUG_RTE_EVENTS) + zlog_debug("ISIS-Rte (%s) route created: %pFX", + area->area_tag, prefix); + route_info = rinfo_new; + UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } else { + route_unlock_node(route_node); +#ifdef EXTREME_DEBUG + if (IS_DEBUG_RTE_EVENTS) + zlog_debug("ISIS-Rte (%s) route already exists: %pFX", + area->area_tag, prefix); +#endif /* EXTREME_DEBUG */ + if (isis_route_info_same(rinfo_new, rinfo_old, change_buf, + sizeof(change_buf))) { +#ifdef EXTREME_DEBUG + if (IS_DEBUG_RTE_EVENTS) + zlog_debug( + "ISIS-Rte (%s) route unchanged: %pFX", + area->area_tag, prefix); +#endif /* EXTREME_DEBUG */ + isis_route_info_delete(rinfo_new); + route_info = rinfo_old; + } else { + if (IS_DEBUG_RTE_EVENTS) + zlog_debug( + "ISIS-Rte (%s): route changed: %pFX, change: %s", + area->area_tag, prefix, change_buf); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + rinfo_new->sr_algo_previous[i] = + rinfo_old->sr_algo[i]; + isis_route_info_delete(rinfo_old); + route_info = rinfo_new; + UNSET_FLAG(route_info->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } + } + + SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE); + route_node->info = route_info; + + return route_info; +} + +void isis_route_delete(struct isis_area *area, struct route_node *rode, + struct route_table *table) +{ + struct isis_route_info *rinfo; + char buff[SRCDEST2STR_BUFFER]; + struct prefix *prefix; + struct prefix_ipv6 *src_p; + + /* for log */ + srcdest_rnode2str(rode, buff, sizeof(buff)); + + srcdest_rnode_prefixes(rode, (const struct prefix **)&prefix, + (const struct prefix **)&src_p); + + rinfo = rode->info; + if (rinfo == NULL) { + if (IS_DEBUG_RTE_EVENTS) + zlog_debug( + "ISIS-Rte: tried to delete non-existent route %s", + buff); + return; + } + + if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) { + UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE); + if (IS_DEBUG_RTE_EVENTS) + zlog_debug("ISIS-Rte: route delete %s", buff); + isis_route_update(area, prefix, src_p, rinfo); + } + isis_route_info_delete(rinfo); + rode->info = NULL; + route_unlock_node(rode); +} + +static void isis_route_remove_previous_sid(struct isis_area *area, + struct prefix *prefix, + struct isis_route_info *route_info) +{ + /* + * Explicitly uninstall previous Prefix-SID label if it has + * changed or was removed. + */ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + if (route_info->sr_algo_previous[i].present && + (!route_info->sr_algo[i].present || + route_info->sr_algo_previous[i].label != + route_info->sr_algo[i].label)) + isis_zebra_prefix_sid_uninstall( + area, prefix, route_info, + &route_info->sr_algo_previous[i]); + } +} + +static void set_merge_route_info_sr_algo(struct isis_route_info *mrinfo, + struct isis_route_info *rinfo) +{ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + if (rinfo->sr_algo[i].present) { + assert(i == rinfo->sr_algo[i].algorithm); + assert(rinfo->nexthops); + assert(rinfo->backup ? rinfo->backup->nexthops != NULL + : true); + + if (mrinfo->sr_algo[i].nexthops != NULL && + mrinfo->sr_algo[i].nexthops != mrinfo->nexthops) { + mrinfo->sr_algo[i].nexthops->del = + (void (*)(void *))isis_nexthop_delete; + list_delete(&mrinfo->sr_algo[i].nexthops); + } + + mrinfo->sr_algo[i] = rinfo->sr_algo[i]; + mrinfo->sr_algo[i].nexthops = isis_nexthop_list_dup( + rinfo->sr_algo[i].nexthops); + } + } + + UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + UNSET_FLAG(mrinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); +} + +static void isis_route_update(struct isis_area *area, struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info) +{ + if (area == NULL) + return; + + if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ACTIVE)) { + if (CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) + return; + + isis_route_remove_previous_sid(area, prefix, route_info); + + /* Install route. */ + isis_zebra_route_add_route(area->isis, prefix, src_p, + route_info); + + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_sr_psid_info sr_algo; + + sr_algo = route_info->sr_algo[i]; + + /* + * Install/reinstall Prefix-SID label. + */ + if (sr_algo.present) + isis_zebra_prefix_sid_install(area, prefix, + &sr_algo); + + hook_call(isis_route_update_hook, area, prefix, + route_info); + } + + hook_call(isis_route_update_hook, area, prefix, route_info); + + SET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_RESYNC); + } else { + /* Uninstall Prefix-SID label. */ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (route_info->sr_algo[i].present) + isis_zebra_prefix_sid_uninstall( + area, prefix, route_info, + &route_info->sr_algo[i]); + + /* Uninstall route. */ + isis_zebra_route_del_route(area->isis, prefix, src_p, + route_info); + hook_call(isis_route_update_hook, area, prefix, route_info); + + UNSET_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } +} + +static void _isis_route_verify_table(struct isis_area *area, + struct route_table *table, + struct route_table *table_backup, + struct route_table **tables) +{ + struct route_node *rnode, *drnode; + struct isis_route_info *rinfo; +#ifdef EXTREME_DEBUG + char buff[SRCDEST2STR_BUFFER]; +#endif /* EXTREME_DEBUG */ + uint8_t algorithm = isis_route_table_algorithm(table); + + for (rnode = route_top(table); rnode; + rnode = srcdest_route_next(rnode)) { + if (rnode->info == NULL) + continue; + rinfo = rnode->info; + + struct prefix *dst_p; + struct prefix_ipv6 *src_p; + + srcdest_rnode_prefixes(rnode, + (const struct prefix **)&dst_p, + (const struct prefix **)&src_p); + + /* Link primary route to backup route. */ + if (table_backup) { + struct route_node *rnode_bck; + + rnode_bck = srcdest_rnode_lookup(table_backup, dst_p, + src_p); + if (rnode_bck) { + rinfo->backup = rnode_bck->info; + rinfo->sr_algo[algorithm].nexthops_backup = + rinfo->backup->nexthops; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } else if (rinfo->backup) { + rinfo->backup = NULL; + rinfo->sr_algo[algorithm].nexthops_backup = + NULL; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } + } + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_RTE_EVENTS) { + srcdest2str(dst_p, src_p, buff, sizeof(buff)); + zlog_debug( + "ISIS-Rte (%s): route validate: %s %s %s %s", + area->area_tag, + (CHECK_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED) + ? "synced" + : "not-synced"), + (CHECK_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_RESYNC) + ? "resync" + : "not-resync"), + (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE) + ? "active" + : "inactive"), + buff); + } +#endif /* EXTREME_DEBUG */ + + isis_route_update(area, dst_p, src_p, rinfo); + + if (CHECK_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE)) + continue; + + /* In case the verify is not for a merge, we use a single table + * directly for + * validating => no problems with deleting routes. */ + if (!tables) { + isis_route_delete(area, rnode, table); + continue; + } + + /* If we work on a merged table, + * therefore we must + * delete node from each table as well before deleting + * route info. */ + for (int i = 0; tables[i]; i++) { + drnode = srcdest_rnode_lookup(tables[i], dst_p, src_p); + if (!drnode) + continue; + + route_unlock_node(drnode); + + if (drnode->info != rnode->info) + continue; + + drnode->info = NULL; + route_unlock_node(drnode); + } + + isis_route_delete(area, rnode, table); + } +} + +static void _isis_route_verify_merge(struct isis_area *area, + struct route_table **tables, + struct route_table **tables_backup, + int tree); + +void isis_route_verify_table(struct isis_area *area, struct route_table *table, + struct route_table *table_backup, int tree) +{ + struct route_table *tables[SR_ALGORITHM_COUNT] = {table}; + struct route_table *tables_backup[SR_ALGORITHM_COUNT] = {table_backup}; +#ifndef FABRICD + int tables_next = 1; + int level = area->is_type == IS_LEVEL_1 ? ISIS_LEVEL1 : ISIS_LEVEL2; + struct listnode *node; + struct flex_algo *fa; + struct isis_flex_algo_data *data; + + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, fa)) { + data = fa->data; + tables[tables_next] = + data->spftree[tree][level - 1]->route_table; + tables_backup[tables_next] = + data->spftree[tree][level - 1]->route_table_backup; + _isis_route_verify_table(area, tables[tables_next], + tables_backup[tables_next], NULL); + tables_next++; + } +#endif /* ifndef FABRICD */ + + _isis_route_verify_merge(area, tables, tables_backup, tree); +} + +/* Function to validate route tables for L1L2 areas. In this case we can't use + * level route tables directly, we have to merge them at first. L1 routes are + * preferred over the L2 ones. + * + * Merge algorithm is trivial (at least for now). All L1 paths are copied into + * merge table at first, then L2 paths are added if L1 path for same prefix + * doesn't already exists there. + * + * FIXME: Is it right place to do it at all? Maybe we should push both levels + * to the RIB with different zebra route types and let RIB handle this? */ +void isis_route_verify_merge(struct isis_area *area, + struct route_table *level1_table, + struct route_table *level1_table_backup, + struct route_table *level2_table, + struct route_table *level2_table_backup, int tree) +{ + struct route_table *tables[] = {level1_table, level2_table, NULL}; + struct route_table *tables_backup[] = {level1_table_backup, + level2_table_backup, NULL}; + _isis_route_verify_merge(area, tables, tables_backup, tree); +} + +static void _isis_route_verify_merge(struct isis_area *area, + struct route_table **tables, + struct route_table **tables_backup, + int tree) +{ + struct route_table *merge; + struct route_node *rnode, *mrnode; + + merge = srcdest_table_init(); + + for (int i = 0; tables[i]; i++) { + uint8_t algorithm = isis_route_table_algorithm(tables[i]); + for (rnode = route_top(tables[i]); rnode; + rnode = srcdest_route_next(rnode)) { + struct isis_route_info *rinfo = rnode->info; + struct route_node *rnode_bck; + + if (!rinfo) + continue; + + struct prefix *prefix; + struct prefix_ipv6 *src_p; + + srcdest_rnode_prefixes(rnode, + (const struct prefix **)&prefix, + (const struct prefix **)&src_p); + + /* Link primary route to backup route. */ + rnode_bck = srcdest_rnode_lookup(tables_backup[i], + prefix, src_p); + if (rnode_bck) { + rinfo->backup = rnode_bck->info; + rinfo->sr_algo[algorithm].nexthops_backup = + rinfo->backup->nexthops; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } else if (rinfo->backup) { + rinfo->backup = NULL; + rinfo->sr_algo[algorithm].nexthops_backup = + NULL; + UNSET_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } + + mrnode = srcdest_rnode_get(merge, prefix, src_p); + struct isis_route_info *mrinfo = mrnode->info; + if (mrinfo) { + route_unlock_node(mrnode); + set_merge_route_info_sr_algo(mrinfo, rinfo); + + if (CHECK_FLAG(mrinfo->flag, + ISIS_ROUTE_FLAG_ACTIVE)) { + /* Clear the ZEBRA_SYNCED flag on the + * L2 route when L1 wins, otherwise L2 + * won't get reinstalled when L1 + * disappears. + */ + UNSET_FLAG( + rinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED + ); + continue; + } else if (CHECK_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ACTIVE)) { + /* Clear the ZEBRA_SYNCED flag on the L1 + * route when L2 wins, otherwise L1 + * won't get reinstalled when it + * reappears. + */ + UNSET_FLAG( + mrinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED + ); + } else if ( + CHECK_FLAG( + mrinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) { + continue; + } + } else { + mrnode->info = rnode->info; + } + } + } + + _isis_route_verify_table(area, merge, NULL, tables); + route_table_finish(merge); +} + +void isis_route_invalidate_table(struct isis_area *area, + struct route_table *table) +{ + struct route_node *rode; + struct isis_route_info *rinfo; + uint8_t algorithm = isis_route_table_algorithm(table); + for (rode = route_top(table); rode; rode = srcdest_route_next(rode)) { + if (rode->info == NULL) + continue; + rinfo = rode->info; + + if (rinfo->backup) { + rinfo->backup = NULL; + rinfo->sr_algo[algorithm].nexthops_backup = NULL; + /* + * For now, always force routes that have backup + * nexthops to be reinstalled. + */ + UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + } + UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ACTIVE); + } +} + +void isis_route_switchover_nexthop(struct isis_area *area, + struct route_table *table, int family, + union g_addr *nexthop_addr, + ifindex_t ifindex) +{ + const char *ifname = NULL, *vrfname = NULL; + struct isis_route_info *rinfo; + struct prefix_ipv6 *src_p; + struct route_node *rnode; + vrf_id_t vrf_id; + struct prefix *prefix; + + if (IS_DEBUG_EVENTS) { + if (area && area->isis) { + vrf_id = area->isis->vrf_id; + vrfname = vrf_id_to_name(vrf_id); + ifname = ifindex2ifname(ifindex, vrf_id); + } + zlog_debug("%s: initiating fast-reroute %s on VRF %s iface %s", + __func__, family2str(family), vrfname ? vrfname : "", + ifname ? ifname : ""); + } + + for (rnode = route_top(table); rnode; + rnode = srcdest_route_next(rnode)) { + if (!rnode->info) + continue; + rinfo = rnode->info; + + if (!rinfo->backup) + continue; + + if (!nexthoplookup(rinfo->nexthops, family, nexthop_addr, + ifindex)) + continue; + + srcdest_rnode_prefixes(rnode, (const struct prefix **)&prefix, + (const struct prefix **)&src_p); + + /* Switchover route. */ + isis_route_remove_previous_sid(area, prefix, rinfo); + UNSET_FLAG(rinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED); + isis_route_update(area, prefix, src_p, rinfo->backup); + + isis_route_info_delete(rinfo); + + rnode->info = NULL; + route_unlock_node(rnode); + } +} diff --git a/isisd/isis_route.h b/isisd/isis_route.h new file mode 100644 index 0000000..4d49a5a --- /dev/null +++ b/isisd/isis_route.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_route.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * + * based on ../ospf6d/ospf6_route.[ch] + * by Yasuhiro Ohara + */ +#ifndef _ZEBRA_ISIS_ROUTE_H +#define _ZEBRA_ISIS_ROUTE_H + +#include "lib/nexthop.h" + +struct isis_nexthop { + ifindex_t ifindex; + int family; + union g_addr ip; + uint8_t sysid[ISIS_SYS_ID_LEN]; + struct isis_sr_psid_info sr; + struct mpls_label_stack *label_stack; +}; + +struct isis_route_info { +#define ISIS_ROUTE_FLAG_ACTIVE 0x01 /* active route for the prefix */ +#define ISIS_ROUTE_FLAG_ZEBRA_SYNCED 0x02 /* set when route synced to zebra */ +#define ISIS_ROUTE_FLAG_ZEBRA_RESYNC 0x04 /* set when route needs to sync */ + uint8_t flag; + uint32_t cost; + uint32_t depth; + struct isis_sr_psid_info sr_algo[SR_ALGORITHM_COUNT]; + struct isis_sr_psid_info sr_algo_previous[SR_ALGORITHM_COUNT]; + struct list *nexthops; + struct isis_route_info *backup; +}; + +struct isis_route_table_info { + uint8_t algorithm; +}; + +DECLARE_HOOK(isis_route_update_hook, + (struct isis_area * area, struct prefix *prefix, + struct isis_route_info *route_info), + (area, prefix, route_info)); + +void isis_nexthop_delete(struct isis_nexthop *nexthop); +void adjinfo2nexthop(int family, struct list *nexthops, + struct isis_adjacency *adj, struct isis_sr_psid_info *sr, + struct mpls_label_stack *label_stack); +struct isis_route_info * +isis_route_create(struct prefix *prefix, struct prefix_ipv6 *src_p, + uint32_t cost, uint32_t depth, struct isis_sr_psid_info *sr, + struct list *adjacencies, bool allow_ecmp, + struct isis_area *area, struct route_table *table); +void isis_route_delete(struct isis_area *area, struct route_node *rode, + struct route_table *table); + +/* Walk the given table and install new routes to zebra and remove old ones. + * route status is tracked using ISIS_ROUTE_FLAG_ACTIVE */ +void isis_route_verify_table(struct isis_area *area, struct route_table *table, + struct route_table *table_backup, int tree); + +/* Same as isis_route_verify_table, but merge L1 and L2 routes before */ +void isis_route_verify_merge(struct isis_area *area, + struct route_table *level1_table, + struct route_table *level1_table_backup, + struct route_table *level2_table, + struct route_table *level2_table_backup, int tree); + +/* Unset ISIS_ROUTE_FLAG_ACTIVE on all routes. Used before running spf. */ +void isis_route_invalidate_table(struct isis_area *area, + struct route_table *table); + +/* Cleanup route node when freeing routing table. */ +void isis_route_node_cleanup(struct route_table *table, + struct route_node *node); + + +void isis_route_switchover_nexthop(struct isis_area *area, + struct route_table *table, int family, + union g_addr *nexthop_addr, + ifindex_t ifindex); + +struct isis_route_table_info *isis_route_table_info_alloc(uint8_t algorithm); +void isis_route_table_info_free(void *info); +uint8_t isis_route_table_algorithm(const struct route_table *table); + +#endif /* _ZEBRA_ISIS_ROUTE_H */ diff --git a/isisd/isis_routemap.c b/isisd/isis_routemap.c new file mode 100644 index 0000000..9be133c --- /dev/null +++ b/isisd/isis_routemap.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_routemap.c + * + * Copyright (C) 2013-2015 Christian Franke + */ + +#include + +#include "command.h" +#include "filter.h" +#include "hash.h" +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "memory.h" +#include "prefix.h" +#include "plist.h" +#include "routemap.h" +#include "table.h" +#include "frrevent.h" +#include "vty.h" + +#include "isis_constants.h" +#include "isis_common.h" +#include "isis_flags.h" +#include "isisd.h" +#include "isis_misc.h" +#include "isis_adjacency.h" +#include "isis_circuit.h" +#include "isis_pdu.h" +#include "isis_lsp.h" +#include "isis_spf.h" +#include "isis_route.h" +#include "isis_zebra.h" +#include "isis_routemap.h" + +static enum route_map_cmd_result_t +route_match_ip_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (access_list_apply(alist, prefix) != FILTER_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ip_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ip_address_cmd = { + "ip address", + route_match_ip_address, + route_match_ip_address_compile, + route_match_ip_address_free +}; + +/* ------------------------------------------------------------*/ + +static enum route_map_cmd_result_t +route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (prefix_list_apply(plist, prefix) != PREFIX_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ip_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_address_prefix_list_cmd = { + "ip address prefix-list", + route_match_ip_address_prefix_list, + route_match_ip_address_prefix_list_compile, + route_match_ip_address_prefix_list_free +}; + +/* ------------------------------------------------------------*/ + +/* `match tag TAG' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag; + struct isis_ext_info *info; + route_tag_t info_tag; + + tag = rule; + info = object; + + info_tag = info->tag; + if (info_tag == *tag) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + +/* Route map commands for tag matching. */ +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* ------------------------------------------------------------*/ + +static enum route_map_cmd_result_t +route_match_ipv6_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP6, (char *)rule); + if (access_list_apply(alist, prefix) != FILTER_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ipv6_address_cmd = { + "ipv6 address", + route_match_ipv6_address, + route_match_ipv6_address_compile, + route_match_ipv6_address_free +}; + +/* ------------------------------------------------------------*/ + +static enum route_map_cmd_result_t +route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP6, (char *)rule); + if (prefix_list_apply(plist, prefix) != PREFIX_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ipv6_address_prefix_list_cmd = { + "ipv6 address prefix-list", + route_match_ipv6_address_prefix_list, + route_match_ipv6_address_prefix_list_compile, + route_match_ipv6_address_prefix_list_free +}; + +/* ------------------------------------------------------------*/ + +static enum route_map_cmd_result_t +route_set_metric(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *metric; + struct isis_ext_info *info; + + metric = rule; + info = object; + + info->metric = *metric; + + return RMAP_OKAY; +} + +static void *route_set_metric_compile(const char *arg) +{ + unsigned long metric; + char *endp; + uint32_t *ret; + + metric = strtoul(arg, &endp, 10); + if (arg[0] == '\0' || *endp != '\0' || metric > MAX_WIDE_PATH_METRIC) + return NULL; + + ret = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*ret)); + *ret = metric; + + return ret; +} + +static void route_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_set_metric_compile, + route_set_metric_free +}; + +void isis_route_map_init(void) +{ + route_map_init(); + + route_map_match_ip_address_hook(generic_match_add); + route_map_no_match_ip_address_hook(generic_match_delete); + + route_map_match_ip_address_prefix_list_hook(generic_match_add); + route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); + + route_map_match_ipv6_address_hook(generic_match_add); + route_map_no_match_ipv6_address_hook(generic_match_delete); + + route_map_match_ipv6_address_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_install_match(&route_match_ip_address_cmd); + route_map_install_match(&route_match_ip_address_prefix_list_cmd); + route_map_install_match(&route_match_ipv6_address_cmd); + route_map_install_match(&route_match_ipv6_address_prefix_list_cmd); + route_map_install_match(&route_match_tag_cmd); + route_map_install_set(&route_set_metric_cmd); +} diff --git a/isisd/isis_routemap.h b/isisd/isis_routemap.h new file mode 100644 index 0000000..758043f --- /dev/null +++ b/isisd/isis_routemap.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_routemap.h + * + * Copyright (C) 2013-2015 Christian Franke + */ +#ifndef ISIS_ROUTEMAP_H +#define ISIS_ROUTEMAP_H + +void isis_route_map_init(void); + +#endif diff --git a/isisd/isis_snmp.c b/isisd/isis_snmp.c new file mode 100644 index 0000000..f9e3780 --- /dev/null +++ b/isisd/isis_snmp.c @@ -0,0 +1,3459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISIS SNMP support + * Copyright (C) 2020 Volta Networks, Inc. + * Aleksey Romanov + */ + +/* + * This is minimal read-only implementations providing isisReadOnlyCompliance + */ + +#include + +#include +#include + +#include "vrf.h" +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "memory.h" +#include "smux.h" +#include "libfrr.h" +#include "lib/version.h" +#include "lib/zclient.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_network.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_te.h" +#include "isisd/isis_dr.h" +#include "isisd/isis_nb.h" +#include "isisd/isisd.h" + +/* ISIS-MIB. */ +#define ISIS_MIB 1, 3, 6, 1, 2, 1, 138 + +#define ISIS_OBJECTS 1 +#define ISIS_SYSTEM 1, 1 +#define ISIS_SYSLEVEL 1, 2 +#define ISIS_CIRC 1, 3 +#define ISIS_CIRC_LEVEL_VALUES 1, 4 +#define ISIS_COUNTERS 1, 5 +#define ISIS_ISADJ 1, 6 + +/************************ isisSystemGroup ************************/ + +/* isisSysObject */ +#define ISIS_SYS_OBJECT 1, 1, 1 +#define ISIS_SYS_VERSION 1 +#define ISIS_SYS_LEVELTYPE 2 +#define ISIS_SYS_ID 3 +#define ISIS_SYS_MAXPATHSPLITS 4 +#define ISIS_SYS_MAXLSPGENINT 5 +#define ISIS_SYS_POLLESHELLORATE 6 +#define ISIS_SYS_WAITTIME 7 +#define ISIS_SYS_ADMINSTATE 8 +#define ISIS_SYS_L2TOL1LEAKING 9 +#define ISIS_SYS_MAXAGE 10 +#define ISIS_SYS_RECEIVELSPBUFFERSIZE 11 +#define ISIS_SYS_PROTSUPPORTED 12 +#define ISIS_SYS_NOTIFICATIONENABLE 13 + +/* isisManAreaAddrEntry */ +#define ISIS_MANAREA_ADDRENTRY 1, 1, 2, 1 +#define ISIS_MANAREA_ADDREXISTSTATE 2 + +/* isisAreaAddrEntry */ +#define ISIS_AREA_ADDRENTRY 1, 1, 3, 1 +#define ISIS_AREA_ADDR 1 + +/* isisSummAddrEntry */ +#define ISIS_SUMM_ADDRENTRY 1, 1, 4, 1 +#define ISIS_SUMM_ADDREXISTSTATE 4 +#define ISIS_SUMM_ADDRMETRIC 5 +#define ISIS_SUMM_ADDRFULLMETRIC 6 + +/* isisRedistributeAddrEntry */ +#define ISIS_REDISTRIBUTE_ADDRENTRY 1, 1, 5, 1 +#define ISIS_REDISTRIBUTE_ADDREXISTSTATE 3 + +/* isisRouterEntry */ +#define ISIS_ROUTER_ENTRY 1, 1, 6, 1 +#define ISIS_ROUTER_HOSTNAME 3 +#define ISIS_ROUTER_ID 4 + +/* isisSysLevelTable */ +#define ISIS_SYSLEVEL_ENTRY 1, 2, 1, 1 +#define ISIS_SYSLEVEL_ORIGLSPBUFFSIZE 2 +#define ISIS_SYSLEVEL_MINLSPGENINT 3 +#define ISIS_SYSLEVEL_STATE 4 +#define ISIS_SYSLEVEL_SETOVERLOAD 5 +#define ISIS_SYSLEVEL_SETOVERLOADUNTIL 6 +#define ISIS_SYSLEVEL_METRICSTYLE 7 +#define ISIS_SYSLEVEL_SPFCONSIDERS 8 +#define ISIS_SYSLEVEL_TEENABLED 9 + + +/* isisSystemCounterEntry */ +#define ISIS_SYSTEM_COUNTER_ENTRY 1, 5, 1, 1 +#define ISIS_SYSSTAT_CORRLSPS 2 +#define ISIS_SYSSTAT_AUTHTYPEFAILS 3 +#define ISIS_SYSSTAT_AUTHFAILS 4 +#define ISIS_SYSSTAT_LSPDBASEOLOADS 5 +#define ISIS_SYSSTAT_MANADDRDROPFROMAREAS 6 +#define ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS 7 +#define ISIS_SYSSTAT_SEQNUMSKIPS 8 +#define ISIS_SYSSTAT_OWNLSPPURGES 9 +#define ISIS_SYSSTAT_IDFIELDLENMISMATCHES 10 +#define ISIS_SYSSTAT_PARTCHANGES 11 +#define ISIS_SYSSTAT_SPFRUNS 12 +#define ISIS_SYSSTAT_LSPERRORS 13 + + +/************************ isisCircuitGroup ************************/ + +/* Scalar directly under isisCirc */ +#define ISIS_NEXTCIRC_INDEX 1 + +/* isisCircEntry */ +#define ISIS_CIRC_ENTRY 1, 3, 2, 1 +#define ISIS_CIRC_IFINDEX 2 +#define ISIS_CIRC_ADMINSTATE 3 +#define ISIS_CIRC_EXISTSTATE 4 +#define ISIS_CIRC_TYPE 5 +#define ISIS_CIRC_EXTDOMAIN 6 +#define ISIS_CIRC_LEVELTYPE 7 +#define ISIS_CIRC_PASSIVECIRCUIT 8 +#define ISIS_CIRC_MESHGROUPENABLED 9 +#define ISIS_CIRC_MESHGROUP 10 +#define ISIS_CIRC_SMALLHELLOS 11 +#define ISIS_CIRC_LASTUPTIME 12 +#define ISIS_CIRC_3WAYENABLED 13 +#define ISIS_CIRC_EXTENDEDCIRCID 14 + +/* isisCircLevelEntry */ +#define ISIS_CIRCLEVEL_ENTRY 1, 4, 1, 1 +#define ISIS_CIRCLEVEL_METRIC 2 +#define ISIS_CIRCLEVEL_WIDEMETRIC 3 +#define ISIS_CIRCLEVEL_ISPRIORITY 4 +#define ISIS_CIRCLEVEL_IDOCTET 5 +#define ISIS_CIRCLEVEL_ID 6 +#define ISIS_CIRCLEVEL_DESIS 7 +#define ISIS_CIRCLEVEL_HELLOMULTIPLIER 8 +#define ISIS_CIRCLEVEL_HELLOTIMER 9 +#define ISIS_CIRCLEVEL_DRHELLOTIMER 10 +#define ISIS_CIRCLEVEL_LSPTHROTTLE 11 +#define ISIS_CIRCLEVEL_MINLSPRETRANSINT 12 +#define ISIS_CIRCLEVEL_CSNPINTERVAL 13 +#define ISIS_CIRCLEVEL_PARTSNPINTERVAL 14 + +/* isisCircuitCounterEntry */ +#define ISIS_CIRC_COUNTER_ENTRY 1, 5, 2, 1 +#define ISIS_CIRC_ADJCHANGES 2 +#define ISIS_CIRC_NUMADJ 3 +#define ISIS_CIRC_INITFAILS 4 +#define ISIS_CIRC_REJADJS 5 +#define ISIS_CIRC_IDFIELDLENMISMATCHES 6 +#define ISIS_CIRC_MAXAREAADDRMISMATCHES 7 +#define ISIS_CIRC_AUTHTYPEFAILS 8 +#define ISIS_CIRC_AUTHFAILS 9 +#define ISIS_CIRC_LANDESISCHANGES 10 + + +/************************ isisISAdjGroup ************************/ + +/* isisISAdjEntry */ +#define ISIS_ISADJ_ENTRY 1, 6, 1, 1 +#define ISIS_ISADJ_STATE 2 +#define ISIS_ISADJ_3WAYSTATE 3 +#define ISIS_ISADJ_NEIGHSNPAADDRESS 4 +#define ISIS_ISADJ_NEIGHSYSTYPE 5 +#define ISIS_ISADJ_NEIGHSYSID 6 +#define ISIS_ISADJ_NBREXTENDEDCIRCID 7 +#define ISIS_ISADJ_USAGE 8 +#define ISIS_ISADJ_HOLDTIMER 9 +#define ISIS_ISADJ_NEIGHPRIORITY 10 +#define ISIS_ISADJ_LASTUPTIME 11 + +/* isisISAdjAreadAddrEntry */ +#define ISIS_ISADJAREA_ADDRENTRY 1, 6, 2, 1 +#define ISIS_ISADJAREA_ADDRESS 2 + +/* isisISAdjIPAddrEntry*/ +#define ISIS_ISADJIPADDR_ENTRY 1, 6, 3, 1 +#define ISIS_ISADJIPADDR_TYPE 2 +#define ISIS_ISADJIPADDR_ADDRESS 3 + + +/* isisISAdjProtSuppEntty */ + +#define ISIS_ISADJPROTSUPP_ENTRY 1, 6, 4, 1 +#define ISIS_ISADJPROTSUPP_PROTOCOL 1 + + +/************************ Trap data variables ************************/ +#define ISIS_NOTIFICATION_ENTRY 1, 10, 1 +#define ISIS_NOTIF_SYLELVELINDEX 1 +#define ISIS_NOTIF_CIRCIFINDEX 2 +#define ISIS_PDU_LSPID 3 +#define ISIS_PDU_FRAGMENT 4 +#define ISIS_PDU_FIELDLEN 5 +#define ISIS_PDU_MAXAREAADDR 6 +#define ISIS_PDU_PROTOVER 7 +#define ISIS_PDU_LSPSIZE 8 +#define ISIS_PDU_ORIGBUFFERSIZE 9 +#define ISIS_PDU_BUFFERSIZE 10 +#define ISIS_PDU_PROTSUPP 11 +#define ISIS_ADJ_STATE 12 +#define ISIS_ERROR_OFFSET 13 +#define ISIS_ERROR_TLVTYPE 14 +#define ISIS_NOTIF_AREAADDR 15 + +/************************ Traps ************************/ +#define ISIS_NOTIFICATIONS ISIS_MIB, 0 +#define ISIS_TRAP_DB_OVERLOAD 1 +#define ISIS_TRAP_MAN_ADDR_DROP 2 +#define ISIS_TRAP_CORRUPTED_LSP 3 +#define ISIS_TRAP_LSP_EXCEED_MAX 4 +#define ISIS_TRAP_ID_LEN_MISMATCH 5 +#define ISIS_TRAP_MAX_AREA_ADDR_MISMATCH 6 +#define ISIS_TRAP_OWN_LSP_PURGE 7 +#define ISIS_TRAP_SEQNO_SKIPPED 8 +#define ISIS_TRAP_AUTHEN_TYPE_FAILURE 9 +#define ISIS_TRAP_AUTHEN_FAILURE 10 +#define ISIS_TRAP_VERSION_SKEW 11 +#define ISIS_TRAP_AREA_MISMATCH 12 +#define ISIS_TRAP_REJ_ADJACENCY 13 +#define ISIS_TRAP_LSP_TOO_LARGE 14 +#define ISIS_TRAP_LSP_BUFFSIZE_MISMATCH 15 +#define ISIS_TRAP_PROTSUPP_MISMATCH 16 +#define ISIS_TRAP_ADJ_STATE_CHANGE 17 +#define ISIS_TRAP_LSP_ERROR 18 + +/* Change this definition if number of traps changes */ +#define ISIS_TRAP_LAST_TRAP ISIS_TRAP_LSP_ERROR + 1 + +#define ISIS_SNMP_TRAP_VAR 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 + + +/* SNMP value hack. */ +#define COUNTER32 ASN_COUNTER +#define INTEGER ASN_INTEGER +#define UNSIGNED32 ASN_GAUGE +#define TIMESTAMP ASN_TIMETICKS +#define TIMETICKS ASN_TIMETICKS +#define STRING ASN_OCTET_STR + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +/* + * Define time function, it serves two purposes + * 1. Uses unint32_t for unix time and encapsulates + * sing extension issues in conversion from time_t + * + * 2. I could be replaced in unit test environment + */ + +/* ISIS-MIB instances. */ +static oid isis_oid[] = {ISIS_MIB}; + +/* SNMP trap variable */ +static oid isis_snmp_trap_var[] = {ISIS_SNMP_TRAP_VAR}; + +/* SNMP trap values (others are calculated on the fly */ +static oid isis_snmp_notifications[] = {ISIS_NOTIFICATIONS}; +static oid isis_snmp_trap_val_db_overload[] = {ISIS_NOTIFICATIONS, + ISIS_TRAP_DB_OVERLOAD}; +static oid isis_snmp_trap_val_lsp_exceed_max[] = {ISIS_NOTIFICATIONS, + ISIS_TRAP_LSP_EXCEED_MAX}; +static oid isis_snmp_trap_val_area_mismatch[] = {ISIS_NOTIFICATIONS, + ISIS_TRAP_AREA_MISMATCH}; +static oid isis_snmp_trap_val_lsp_error[] = {ISIS_NOTIFICATIONS, + ISIS_TRAP_LSP_ERROR}; + +/* + * Trap vars under 'isisNotifications': note: we use full names of variables + * scalar index + */ +static oid isis_snmp_trap_data_var_sys_level_index[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_NOTIF_SYLELVELINDEX, 0}; +static oid isis_snmp_trap_data_var_circ_if_index[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_NOTIF_CIRCIFINDEX, 0}; +static oid isis_snmp_trap_data_var_pdu_lsp_id[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_LSPID, 0}; +static oid isis_snmp_trap_data_var_pdu_fragment[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_FRAGMENT, 0}; +static oid isis_snmp_trap_data_var_pdu_field_len[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_FIELDLEN, 0}; +static oid isis_snmp_trap_data_var_pdu_max_area_addr[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_MAXAREAADDR, 0}; +static oid isis_snmp_trap_data_var_pdu_proto_ver[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_PROTOVER, 0}; +static oid isis_snmp_trap_data_var_pdu_lsp_size[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_PDU_LSPSIZE, 0}; +static oid isis_snmp_trap_data_var_adj_state[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ADJ_STATE, 0}; +static oid isis_snmp_trap_data_var_error_offset[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ERROR_OFFSET, 0}; +static oid isis_snmp_trap_data_var_error_tlv_type[] = { + ISIS_MIB, ISIS_NOTIFICATION_ENTRY, ISIS_ERROR_TLVTYPE, 0}; + +/* + * Other variables used by traps: note we use full names of variables and + * reserve space for index + */ +static oid isis_snmp_trap_data_var_sys_level_state[] = { + ISIS_MIB, ISIS_SYSLEVEL_ENTRY, ISIS_SYSLEVEL_STATE, 0}; + +/* Throttle time values for traps */ +static time_t isis_snmp_trap_timestamp[ISIS_TRAP_LAST_TRAP]; /* ?? 1 */ + +/* Max len of raw-pdu in traps */ +#define ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN (64) + +/* + * Just to save on typing we have a shortcut structure + * to specify mib layout as prefix/leaf combination + */ +#define ISIS_SNMP_PREF_LEN_MAX 10 +struct isis_var_prefix { + FindVarMethod *findVar; + uint8_t ivd_pref_len; + oid ivd_pref[ISIS_SNMP_PREF_LEN_MAX]; +}; + + +/* Find-val functions */ +static uint8_t *isis_snmp_find_sys_object(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_man_area(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_area_addr(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_summ_addr(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_redistribute_addr(struct variable *, oid *, + size_t *, int, size_t *, + WriteMethod **); + +static uint8_t *isis_snmp_find_router(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_sys_level(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_system_counter(struct variable *, oid *, + size_t *, int, size_t *, + WriteMethod **); + +static uint8_t *isis_snmp_find_next_circ_index(struct variable *, oid *, + size_t *, int, size_t *, + WriteMethod **); + +static uint8_t *isis_snmp_find_circ(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_circ_level(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_circ_counter(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_isadj(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_isadj_area(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_isadj_ipaddr(struct variable *, oid *, size_t *, + int, size_t *, WriteMethod **); + +static uint8_t *isis_snmp_find_isadj_prot_supp(struct variable *, oid *, + size_t *, int, size_t *, + WriteMethod **); + +/* + * Just to save on typing we have a shortcut structure + * to specify mib layout, we populate the rest of the data + * during initialization + */ +#define ISIS_PREF_LEN_MAX (6) + +struct isis_func_to_prefix { + FindVarMethod *ihtp_func; + oid ihtp_pref_oid[ISIS_PREF_LEN_MAX]; + uint8_t ihtp_pref_len; +}; + +static struct isis_func_to_prefix isis_func_to_prefix_arr[] = { + {isis_snmp_find_sys_object, {ISIS_SYS_OBJECT}, 3}, + {isis_snmp_find_man_area, {ISIS_MANAREA_ADDRENTRY}, 4}, + {isis_snmp_find_area_addr, {ISIS_AREA_ADDRENTRY}, 4}, + {isis_snmp_find_summ_addr, {ISIS_SUMM_ADDRENTRY}, 4}, + {isis_snmp_find_redistribute_addr, {ISIS_REDISTRIBUTE_ADDRENTRY}, 4}, + {isis_snmp_find_router, {ISIS_ROUTER_ENTRY}, 4}, + {isis_snmp_find_sys_level, {ISIS_SYSLEVEL_ENTRY}, 4}, + {isis_snmp_find_system_counter, {ISIS_SYSTEM_COUNTER_ENTRY}, 4}, + {isis_snmp_find_next_circ_index, {ISIS_CIRC}, 2}, + {isis_snmp_find_circ, {ISIS_CIRC_ENTRY}, 4}, + {isis_snmp_find_circ_level, {ISIS_CIRCLEVEL_ENTRY}, 4}, + {isis_snmp_find_circ_counter, {ISIS_CIRC_COUNTER_ENTRY}, 4}, + {isis_snmp_find_isadj, {ISIS_ISADJ_ENTRY}, 4}, + {isis_snmp_find_isadj_area, {ISIS_ISADJAREA_ADDRENTRY}, 4}, + {isis_snmp_find_isadj_ipaddr, {ISIS_ISADJIPADDR_ENTRY}, 4}, + {isis_snmp_find_isadj_prot_supp, {ISIS_ISADJPROTSUPP_ENTRY}, 4}, +}; +static size_t isis_func_to_prefix_count = array_size(isis_func_to_prefix_arr); + +static struct variable isis_var_arr[] = { + {ISIS_SYS_VERSION, INTEGER, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_LEVELTYPE, INTEGER, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_ID, STRING, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_MAXPATHSPLITS, UNSIGNED32, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_MAXLSPGENINT, UNSIGNED32, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_POLLESHELLORATE, UNSIGNED32, RONLY, + isis_snmp_find_sys_object}, + {ISIS_SYS_WAITTIME, UNSIGNED32, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_ADMINSTATE, INTEGER, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_L2TOL1LEAKING, INTEGER, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_MAXAGE, UNSIGNED32, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_RECEIVELSPBUFFERSIZE, UNSIGNED32, RONLY, + isis_snmp_find_sys_object}, + {ISIS_SYS_PROTSUPPORTED, STRING, RONLY, isis_snmp_find_sys_object}, + {ISIS_SYS_NOTIFICATIONENABLE, INTEGER, RONLY, + isis_snmp_find_sys_object}, + {ISIS_MANAREA_ADDREXISTSTATE, INTEGER, RONLY, isis_snmp_find_man_area}, + {ISIS_AREA_ADDR, STRING, RONLY, isis_snmp_find_area_addr}, + {ISIS_SUMM_ADDREXISTSTATE, INTEGER, RONLY, isis_snmp_find_summ_addr}, + {ISIS_SUMM_ADDRMETRIC, UNSIGNED32, RONLY, isis_snmp_find_summ_addr}, + {ISIS_SUMM_ADDRFULLMETRIC, UNSIGNED32, RONLY, isis_snmp_find_summ_addr}, + {ISIS_REDISTRIBUTE_ADDREXISTSTATE, INTEGER, RONLY, + isis_snmp_find_redistribute_addr}, + {ISIS_ROUTER_HOSTNAME, STRING, RONLY, isis_snmp_find_router}, + {ISIS_ROUTER_ID, UNSIGNED32, RONLY, isis_snmp_find_router}, + {ISIS_SYSLEVEL_ORIGLSPBUFFSIZE, UNSIGNED32, RONLY, + isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_MINLSPGENINT, UNSIGNED32, RONLY, + isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_STATE, INTEGER, RONLY, isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_SETOVERLOAD, INTEGER, RONLY, isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_SETOVERLOADUNTIL, UNSIGNED32, RONLY, + isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_METRICSTYLE, INTEGER, RONLY, isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_SPFCONSIDERS, INTEGER, RONLY, isis_snmp_find_sys_level}, + {ISIS_SYSLEVEL_TEENABLED, INTEGER, RONLY, isis_snmp_find_sys_level}, + {ISIS_SYSSTAT_CORRLSPS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_AUTHTYPEFAILS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_AUTHFAILS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_LSPDBASEOLOADS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_MANADDRDROPFROMAREAS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_SEQNUMSKIPS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_OWNLSPPURGES, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_IDFIELDLENMISMATCHES, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_PARTCHANGES, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_SPFRUNS, COUNTER32, RONLY, isis_snmp_find_system_counter}, + {ISIS_SYSSTAT_LSPERRORS, COUNTER32, RONLY, + isis_snmp_find_system_counter}, + {ISIS_NEXTCIRC_INDEX, UNSIGNED32, RONLY, + isis_snmp_find_next_circ_index}, + {ISIS_CIRC_IFINDEX, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_ADMINSTATE, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_EXISTSTATE, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_TYPE, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_EXTDOMAIN, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_LEVELTYPE, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_PASSIVECIRCUIT, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_MESHGROUPENABLED, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_MESHGROUP, UNSIGNED32, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_SMALLHELLOS, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_LASTUPTIME, TIMESTAMP, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_3WAYENABLED, INTEGER, RONLY, isis_snmp_find_circ}, + {ISIS_CIRC_EXTENDEDCIRCID, UNSIGNED32, RONLY, isis_snmp_find_circ}, + {ISIS_CIRCLEVEL_METRIC, UNSIGNED32, RONLY, isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_WIDEMETRIC, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_ISPRIORITY, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_IDOCTET, UNSIGNED32, RONLY, isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_ID, STRING, RONLY, isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_DESIS, STRING, RONLY, isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_HELLOMULTIPLIER, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_HELLOTIMER, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_DRHELLOTIMER, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_LSPTHROTTLE, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_MINLSPRETRANSINT, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_CSNPINTERVAL, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRCLEVEL_PARTSNPINTERVAL, UNSIGNED32, RONLY, + isis_snmp_find_circ_level}, + {ISIS_CIRC_ADJCHANGES, COUNTER32, RONLY, isis_snmp_find_circ_counter}, + {ISIS_CIRC_NUMADJ, UNSIGNED32, RONLY, isis_snmp_find_circ_counter}, + {ISIS_CIRC_INITFAILS, COUNTER32, RONLY, isis_snmp_find_circ_counter}, + {ISIS_CIRC_REJADJS, COUNTER32, RONLY, isis_snmp_find_circ_counter}, + {ISIS_CIRC_IDFIELDLENMISMATCHES, COUNTER32, RONLY, + isis_snmp_find_circ_counter}, + {ISIS_CIRC_MAXAREAADDRMISMATCHES, COUNTER32, RONLY, + isis_snmp_find_circ_counter}, + {ISIS_CIRC_AUTHTYPEFAILS, COUNTER32, RONLY, + isis_snmp_find_circ_counter}, + {ISIS_CIRC_AUTHFAILS, COUNTER32, RONLY, isis_snmp_find_circ_counter}, + {ISIS_CIRC_LANDESISCHANGES, COUNTER32, RONLY, + isis_snmp_find_circ_counter}, + {ISIS_ISADJ_STATE, INTEGER, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_3WAYSTATE, INTEGER, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_NEIGHSNPAADDRESS, STRING, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_NEIGHSYSTYPE, INTEGER, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_NEIGHSYSID, STRING, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_NBREXTENDEDCIRCID, UNSIGNED32, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_USAGE, INTEGER, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_HOLDTIMER, UNSIGNED32, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_NEIGHPRIORITY, UNSIGNED32, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJ_LASTUPTIME, TIMESTAMP, RONLY, isis_snmp_find_isadj}, + {ISIS_ISADJAREA_ADDRESS, STRING, RONLY, isis_snmp_find_isadj_area}, + {ISIS_ISADJIPADDR_TYPE, INTEGER, RONLY, isis_snmp_find_isadj_ipaddr}, + {ISIS_ISADJIPADDR_ADDRESS, STRING, RONLY, isis_snmp_find_isadj_ipaddr}, + {ISIS_ISADJPROTSUPP_PROTOCOL, INTEGER, RONLY, + isis_snmp_find_isadj_prot_supp}, +}; + +static const size_t isis_var_count = array_size(isis_var_arr); + +/* Minimal set of hard-coded data */ +#define ISIS_VERSION (1) + +/* If sys-id is not set use this value */ +static uint8_t isis_null_sysid[ISIS_SYS_ID_LEN]; + +/* OSI addr-len */ +#define ISIS_SNMP_OSI_ADDR_LEN_MAX (20) + +/* + * The implementation has a fixed max-path splits value + * of 64 (see ISIS_MAX_PATH_SPLITS), the max mib value + * is 32. + * + * FIXME(aromanov): should we return 32 or 64? + */ +#define ISIS_SNMP_MAX_PATH_SPLITS (32) + +#define ISIS_SNMP_ADMIN_STATE_ON (1) + +#define ISIS_SNMP_ROW_STATUS_ACTIVE (1) + +#define ISIS_SNMP_LEVEL_STATE_OFF (1) +#define ISIS_SNMP_LEVEL_STATE_ON (2) +#define ISIS_SNMP_LEVEL_STATE_WAITING (3) +#define ISIS_SNMP_LEVEL_STATE_OVERLOADED (4) + +#define ISIS_SNMP_TRUTH_VALUE_TRUE (1) +#define ISIS_SNMP_TRUTH_VALUE_FALSE (2) + +#define ISIS_SNMP_METRIC_STYLE_NARROW (1) +#define ISIS_SNMP_METRIC_STYLE_WIDE (2) +#define ISIS_SNMP_METRIC_STYLE_BOTH (3) + +#define ISIS_SNMP_MESH_GROUP_INACTIVE (1) + +#define ISIS_SNMP_ADJ_STATE_DOWN (1) +#define ISIS_SNMP_ADJ_STATE_INITIALIZING (2) +#define ISIS_SNMP_ADJ_STATE_UP (3) +#define ISIS_SNMP_ADJ_STATE_FAILED (4) + +static inline uint32_t isis_snmp_adj_state(enum isis_adj_state state) +{ + switch (state) { + case ISIS_ADJ_UNKNOWN: + return ISIS_SNMP_ADJ_STATE_DOWN; + case ISIS_ADJ_INITIALIZING: + return ISIS_SNMP_ADJ_STATE_INITIALIZING; + case ISIS_ADJ_UP: + return ISIS_SNMP_ADJ_STATE_UP; + case ISIS_ADJ_DOWN: + return ISIS_SNMP_ADJ_STATE_FAILED; + } + + return 0; /* not reached */ +} + +#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1 (1) +#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L2 (2) +#define ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1_L2 (3) +#define ISIS_SNMP_ADJ_NEIGHTYPE_UNKNOWN (4) + +static inline uint32_t isis_snmp_adj_neightype(enum isis_system_type type) +{ + switch (type) { + case ISIS_SYSTYPE_UNKNOWN: + case ISIS_SYSTYPE_ES: + return ISIS_SNMP_ADJ_NEIGHTYPE_UNKNOWN; + case ISIS_SYSTYPE_IS: + return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1_L2; + case ISIS_SYSTYPE_L1_IS: + return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L1; + case ISIS_SYSTYPE_L2_IS: + return ISIS_SNMP_ADJ_NEIGHTYPE_IS_L2; + } + + return 0; /* not reached */ +} + +#define ISIS_SNMP_INET_TYPE_V4 (1) +#define ISIS_SNMP_INET_TYPE_V6 (2) + +#define ISIS_SNMP_P2P_CIRCUIT (3) + +/* Protocols supported value */ +static uint8_t isis_snmp_protocols_supported = 0x7; /* All: iso, ipv4, ipv6 */ + +#define SNMP_CIRCUITS_MAX (512) + +static struct isis_circuit *snmp_circuits[SNMP_CIRCUITS_MAX]; +static uint32_t snmp_circuit_id_last; + +static int isis_circuit_snmp_id_gen(struct isis_circuit *circuit) +{ + uint32_t id; + uint32_t i; + + id = snmp_circuit_id_last; + id++; + + /* find next unused entry */ + for (i = 0; i < SNMP_CIRCUITS_MAX; i++) { + if (id >= SNMP_CIRCUITS_MAX) { + id = 0; + continue; + } + + if (id == 0) + continue; + + if (snmp_circuits[id] == NULL) + break; + + id++; + } + + if (i == SNMP_CIRCUITS_MAX) { + zlog_warn("Could not allocate a smmp-circuit-id"); + return 0; + } + + snmp_circuits[id] = circuit; + snmp_circuit_id_last = id; + circuit->snmp_id = id; + + return 0; +} + +static int isis_circuit_snmp_id_free(struct isis_circuit *circuit) +{ + snmp_circuits[circuit->snmp_id] = NULL; + circuit->snmp_id = 0; + return 0; +} + +/* + * Convenience function to move to the next circuit, + */ +static struct isis_circuit *isis_snmp_circuit_next(struct isis_circuit *circuit) +{ + uint32_t start; + uint32_t off; + + start = 1; + + if (circuit != NULL) + start = circuit->snmp_id + 1; + + for (off = start; off < SNMP_CIRCUITS_MAX; off++) { + circuit = snmp_circuits[off]; + + if (circuit != NULL) + return circuit; + } + + return NULL; +} + +/* + * Convenience function to get the first matching level + */ +static int isis_snmp_circuit_get_level_lo(struct isis_circuit *circuit) +{ + if (circuit->is_type == IS_LEVEL_2) + return IS_LEVEL_2; + + return IS_LEVEL_1; +} + +/* Check level match */ +static int isis_snmp_get_level_match(int is_type, int level) +{ + if (is_type != IS_LEVEL_1 && is_type != IS_LEVEL_2 + && is_type != IS_LEVEL_1_AND_2) + return 0; + + if (level != IS_LEVEL_1 && level != IS_LEVEL_2) + return 0; + + + if (is_type == IS_LEVEL_1) { + if (level == IS_LEVEL_1) + return 1; + + return 0; + } + + if (is_type == IS_LEVEL_2) { + if (level == IS_LEVEL_2) + return 1; + + return 0; + } + + return 1; +} +/* + * Helper function to convert oid index representing + * octet-string index (e.g. isis-sys-id) to byte string + * representing the same index. + * + * Also we do not fail if idx is longer than max_len, + * so we can use the same function to check compound + * indexes. + */ +static int isis_snmp_conv_exact(uint8_t *buf, size_t max_len, size_t *out_len, + const oid *idx, size_t idx_len) +{ + size_t off; + size_t len; + + /* Oid representation: length followed by bytes */ + if (idx == NULL || idx_len == 0) + return 0; + + len = idx[0]; + + if (len > max_len) + return 0; + + if (idx_len < len + 1) + return 0; + + for (off = 0; off < len; off++) { + if (idx[off + 1] > 0xff) + return 0; + + buf[off] = (uint8_t)(idx[off + 1] & 0xff); + } + + *out_len = len; + + return 1; +} + +static int isis_snmp_conv_next(uint8_t *buf, size_t max_len, size_t *out_len, + int *try_exact, const oid *idx, size_t idx_len) +{ + size_t off; + size_t len; + size_t cmp_len; + + if (idx == NULL || idx_len == 0) { + *out_len = 0; + *try_exact = 1; + return 1; + } + + len = idx[0]; + + if (len > max_len) + return 0; + + cmp_len = len; + + if ((idx_len - 1) < cmp_len) + cmp_len = idx_len - 1; + + for (off = 0; off < cmp_len; off++) { + if (idx[off + 1] > 0xff) { + memset(buf + off, 0xff, len - off); + *out_len = len; + *try_exact = 1; + return 1; + } + + buf[off] = (uint8_t)(idx[off + 1] & 0xff); + } + + if (cmp_len < len) + memset(buf + cmp_len, 0, len - cmp_len); + + *out_len = len; + *try_exact = cmp_len < len ? 1 : 0; + return 1; +} + +/* + * Helper functions to find area address from snmp index + */ +static int isis_snmp_area_addr_lookup_exact(oid *oid_idx, size_t oid_idx_len, + struct isis_area **ret_area, + struct iso_address **ret_addr) +{ + uint8_t cmp_buf[ISIS_SNMP_OSI_ADDR_LEN_MAX]; + size_t addr_len; + struct isis_area *area = NULL; + struct iso_address *addr = NULL; + struct listnode *addr_node; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return 0; + + if (list_isempty(isis->area_list)) { + /* Area is not configured yet */ + return 0; + } + + area = listgetdata(listhead(isis->area_list)); + + int res = isis_snmp_conv_exact(cmp_buf, sizeof(cmp_buf), &addr_len, + oid_idx, oid_idx_len); + + + if (!res || addr_len == 0 || oid_idx_len != (addr_len + 1)) { + /* Bad conversion, empty address or extra oids at the end */ + return 0; + } + + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { + if (addr->addr_len != addr_len) + continue; + + if (memcmp(addr->area_addr, cmp_buf, addr_len) == 0) { + if (ret_area != 0) + *ret_area = area; + + if (ret_addr != 0) + *ret_addr = addr; + + return 1; + } + } + return 0; +} + +static int isis_snmp_area_addr_lookup_next(oid *oid_idx, size_t oid_idx_len, + struct isis_area **ret_area, + struct iso_address **ret_addr) +{ + uint8_t cmp_buf[ISIS_SNMP_OSI_ADDR_LEN_MAX]; + size_t addr_len; + int try_exact = 0; + struct isis_area *found_area = NULL; + struct isis_area *area = NULL; + struct iso_address *found_addr = NULL; + struct iso_address *addr = NULL; + struct listnode *addr_node; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return 0; + + if (list_isempty(isis->area_list)) { + /* Area is not configured yet */ + return 0; + } + + area = listgetdata(listhead(isis->area_list)); + + int res = isis_snmp_conv_next(cmp_buf, sizeof(cmp_buf), &addr_len, + &try_exact, oid_idx, oid_idx_len); + + if (!res) + return 0; + + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, addr_node, addr)) { + if (addr->addr_len < addr_len) + continue; + + if (addr->addr_len == addr_len) { + if (addr_len == 0) + continue; + + res = memcmp(addr->area_addr, cmp_buf, addr_len); + + if (res < 0) + continue; + + if (res == 0 && addr->addr_len == addr_len) { + if (try_exact) { + /* + * This is the best match no point + * to look further + */ + found_area = area; + found_addr = addr; + break; + } + continue; + } + } + + if (found_addr == NULL || addr->addr_len < found_addr->addr_len + || (addr->addr_len == found_addr->addr_len + && memcmp(addr->area_addr, found_addr->area_addr, + addr->addr_len) + < 0)) { + found_area = area; + found_addr = addr; + } + } + + if (found_area == NULL) + return 0; + + if (ret_area != 0) + *ret_area = found_area; + + if (ret_addr != 0) + *ret_addr = found_addr; + + return 1; +} + +/* + * Helper functions to find circuit from + * snmp index + */ +static int isis_snmp_circuit_lookup_exact(oid *oid_idx, size_t oid_idx_len, + struct isis_circuit **ret_circuit) +{ + struct isis_circuit *circuit; + + if (oid_idx == NULL || oid_idx_len < 1 + || oid_idx[0] > SNMP_CIRCUITS_MAX) + return 0; + + circuit = snmp_circuits[oid_idx[0]]; + if (circuit == NULL) + return 0; + + if (ret_circuit != NULL) + *ret_circuit = circuit; + + return 1; +} + +static int isis_snmp_circuit_lookup_next(oid *oid_idx, size_t oid_idx_len, + struct isis_circuit **ret_circuit) +{ + oid off; + oid start; + struct isis_circuit *circuit; + + start = 0; + + if (oid_idx != NULL && oid_idx_len != 0) { + if (oid_idx[0] > SNMP_CIRCUITS_MAX) + return 0; + + start = oid_idx[0]; + } + + for (off = start; off < SNMP_CIRCUITS_MAX; ++off) { + circuit = snmp_circuits[off]; + + if (circuit != NULL && off > start) { + if (ret_circuit != NULL) + *ret_circuit = circuit; + + return 1; + } + } + + return 0; +} + +/* + * Helper functions to find circuit level + * combination from snmp index + */ +static int isis_snmp_circuit_level_lookup_exact( + oid *oid_idx, size_t oid_idx_len, int check_match, + struct isis_circuit **ret_circuit, int *ret_level) +{ + int level; + int res; + struct isis_circuit *circuit; + + /* Minor optimization: check level first */ + if (oid_idx == NULL || oid_idx_len < 2) + return 0; + + if (oid_idx[1] < IS_LEVEL_1 || oid_idx[1] > IS_LEVEL_2) + return 0; + + level = (int)oid_idx[1]; + + res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len, &circuit); + + if (!res) + return 0; + + if (check_match && !isis_snmp_get_level_match(circuit->is_type, level)) + return 0; + + if (ret_circuit != NULL) + *ret_circuit = circuit; + + if (ret_level != NULL) + *ret_level = level; + + return 1; +} + +static int isis_snmp_circuit_level_lookup_next( + oid *oid_idx, size_t oid_idx_len, int check_match, + struct isis_circuit **ret_circuit, int *ret_level) +{ + oid off; + oid start; + struct isis_circuit *circuit = NULL; + int level; + + start = 0; + + if (oid_idx != NULL && oid_idx_len != 0) { + if (oid_idx[0] > SNMP_CIRCUITS_MAX) + return 0; + + start = oid_idx[0]; + } + + for (off = start; off < SNMP_CIRCUITS_MAX; off++) { + circuit = snmp_circuits[off]; + + if (circuit == NULL) + continue; + + if (off > start || oid_idx_len < 2) { + /* Found and can use level 1 */ + level = IS_LEVEL_1; + break; + } + + assert(oid_idx != NULL); + + /* We have to check level specified by index */ + if (oid_idx[1] < IS_LEVEL_1) { + level = IS_LEVEL_1; + break; + } + + if (oid_idx[1] < IS_LEVEL_2) { + level = IS_LEVEL_2; + break; + } + + /* Try next */ + circuit = NULL; + } + + if (circuit == NULL) + return 0; + + if (check_match + && !isis_snmp_get_level_match(circuit->is_type, level)) { + if (level == IS_LEVEL_1) { + /* + * We can simply advance level because + * at least one level should match + */ + level = IS_LEVEL_2; + } else { + /* We have to move to the next circuit */ + circuit = isis_snmp_circuit_next(circuit); + if (circuit == NULL) + return 0; + + level = isis_snmp_circuit_get_level_lo(circuit); + } + } + + if (ret_circuit != NULL) + *ret_circuit = circuit; + + if (ret_level != NULL) + *ret_level = level; + + return 1; +} + +/* + * Helper functions to find adjacency + * from snmp index. + * + * We have 4 tables related to adjacency + * looking up adjacency is quite expensive + * in case of bcast interfaces. + * + * It is pain to have 4 very similar functions + * hence we pass in and out additional data + * we are looking for. + * + * Note: we use data-len value to distinguish + * between ipv4 and ipv6 addresses + */ +#define ISIS_SNMP_ADJ_DATA_NONE (1) +#define ISIS_SNMP_ADJ_DATA_AREA_ADDR (2) +#define ISIS_SNMP_ADJ_DATA_IP_ADDR (3) +#define ISIS_SNMP_ADJ_DATA_PROTO (4) + +/* + * Helper function to process data associated + * with adjacency + */ +static int isis_snmp_adj_helper(struct isis_adjacency *adj, int data_id, + oid data_off, uint8_t **ret_data, + size_t *ret_data_len) +{ + uint8_t *data = NULL; + size_t data_len = 0; + + switch (data_id) { + case ISIS_SNMP_ADJ_DATA_NONE: + break; + + case ISIS_SNMP_ADJ_DATA_AREA_ADDR: + if (data_off >= adj->area_address_count) + return 0; + + data = adj->area_addresses[data_off].area_addr; + data_len = adj->area_addresses[data_off].addr_len; + break; + + case ISIS_SNMP_ADJ_DATA_IP_ADDR: + if (data_off >= (adj->ipv4_address_count + adj->ll_ipv6_count)) + return 0; + + if (data_off >= adj->ipv4_address_count) { + data = (uint8_t *)&adj->ll_ipv6_addrs + [data_off - adj->ipv4_address_count]; + data_len = sizeof(adj->ll_ipv6_addrs[0]); + } else { + data = (uint8_t *)&adj->ipv4_addresses[data_off]; + data_len = sizeof(adj->ipv4_addresses[0]); + } + + break; + + + case ISIS_SNMP_ADJ_DATA_PROTO: + if (data_off >= adj->nlpids.count) + return 0; + + data = &adj->nlpids.nlpids[data_off]; + data_len = sizeof(adj->nlpids.nlpids[0]); + break; + + default: + assert(0); + return 0; + } + + if (ret_data != NULL) + *ret_data = data; + + if (ret_data_len != NULL) + *ret_data_len = data_len; + + return 1; +} + +static int isis_snmp_adj_lookup_exact(oid *oid_idx, size_t oid_idx_len, + int data_id, + struct isis_adjacency **ret_adj, + oid *ret_data_idx, uint8_t **ret_data, + size_t *ret_data_len) +{ + int res; + struct listnode *node; + struct isis_circuit *circuit; + struct isis_adjacency *adj; + struct isis_adjacency *tmp_adj; + oid adj_idx; + oid data_off; + uint8_t *data; + size_t data_len; + + res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len, &circuit); + + if (!res) + return 0; + + if (oid_idx == NULL || oid_idx_len < 2 + || (data_id != ISIS_SNMP_ADJ_DATA_NONE && oid_idx_len < 3)) + return 0; + + adj_idx = oid_idx[1]; + + if (data_id != ISIS_SNMP_ADJ_DATA_NONE) { + if (oid_idx[2] == 0) + return 0; + + data_off = oid_idx[2] - 1; + } else { + /* + * Data-off is not used if data-id is none + * but we set it just for consistency + */ + data_off = 0; + } + + adj = NULL; + data = NULL; + data_len = 0; + + for (ALL_LIST_ELEMENTS_RO(circuit->snmp_adj_list, node, tmp_adj)) { + if (tmp_adj->snmp_idx > adj_idx) { + /* + * Adjacencies are ordered in the list + * no point to look further + */ + break; + } + + if (tmp_adj->snmp_idx == adj_idx) { + res = isis_snmp_adj_helper(tmp_adj, data_id, data_off, + &data, &data_len); + if (res) + adj = tmp_adj; + + break; + } + } + + if (adj == NULL) + return 0; + + if (ret_adj != NULL) + *ret_adj = adj; + + if (ret_data_idx != NULL) + *ret_data_idx = data_off + 1; + + if (ret_data) + *ret_data = data; + + if (ret_data_len) + *ret_data_len = data_len; + + return 1; +} + +static int isis_snmp_adj_lookup_next(oid *oid_idx, size_t oid_idx_len, + int data_id, + struct isis_adjacency **ret_adj, + oid *ret_data_idx, uint8_t **ret_data, + size_t *ret_data_len) +{ + struct listnode *node; + struct isis_circuit *circuit; + struct isis_adjacency *adj; + struct isis_adjacency *tmp_adj; + oid circ_idx; + oid adj_idx; + oid data_idx; + uint8_t *data; + size_t data_len; + + adj = NULL; + data = NULL; + data_len = 0; + + /* + * Note: we rely on the fact that data indexes are consequtive + * starting from 1 + */ + + if (oid_idx == 0 || oid_idx_len == 0) { + circ_idx = 0; + adj_idx = 0; + data_idx = 0; + } else if (oid_idx_len == 1) { + circ_idx = oid_idx[0]; + adj_idx = 0; + data_idx = 0; + } else if (oid_idx_len == 2) { + circ_idx = oid_idx[0]; + adj_idx = oid_idx[1]; + data_idx = 0; + } else { + circ_idx = oid_idx[0]; + adj_idx = oid_idx[1]; + + if (data_id == ISIS_SNMP_ADJ_DATA_NONE) + data_idx = 0; + else + data_idx = oid_idx[2]; + } + + if (!isis_snmp_circuit_lookup_exact(&circ_idx, 1, &circuit) + && !isis_snmp_circuit_lookup_next(&circ_idx, 1, &circuit)) + /* No circuit */ + return 0; + + if (circuit->snmp_id != circ_idx) { + /* Match is not exact */ + circ_idx = 0; + adj_idx = 0; + data_idx = 0; + } + + /* + * Note: the simple loop below will work in all cases + */ + while (circuit != NULL) { + for (ALL_LIST_ELEMENTS_RO(circuit->snmp_adj_list, node, + tmp_adj)) { + if (tmp_adj->snmp_idx < adj_idx) + continue; + + if (tmp_adj->snmp_idx == adj_idx + && data_id == ISIS_SNMP_ADJ_DATA_NONE) + continue; + + if (adj_idx != 0 && tmp_adj->snmp_idx > adj_idx) + data_idx = 0; + + if (isis_snmp_adj_helper(tmp_adj, data_id, data_idx, + &data, &data_len)) { + adj = tmp_adj; + break; + } + } + + if (adj != NULL) + break; + + circuit = isis_snmp_circuit_next(circuit); + circ_idx = 0; + adj_idx = 0; + data_idx = 0; + } + + if (adj == NULL) + return 0; + + if (ret_adj != NULL) + *ret_adj = adj; + + if (ret_data_idx != 0) { + if (data_id == ISIS_SNMP_ADJ_DATA_NONE) + /* + * Value does not matter but let us set + * it to zero for consistency + */ + *ret_data_idx = 0; + else + *ret_data_idx = data_idx + 1; + } + + if (ret_data != 0) + *ret_data = data; + + if (ret_data_len != 0) + *ret_data_len = data_len; + + return 1; +} + +static uint8_t *isis_snmp_find_sys_object(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + struct isis_area *area = NULL; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return NULL; + + if (!list_isempty(isis->area_list)) + area = listgetdata(listhead(isis->area_list)); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + switch (v->magic) { + case ISIS_SYS_VERSION: + return SNMP_INTEGER(ISIS_VERSION); + + case ISIS_SYS_LEVELTYPE: + /* + * If we do not have areas use 1&2 otherwise use settings + * from the first area in the list + */ + if (area == NULL) + return SNMP_INTEGER(IS_LEVEL_1_AND_2); + + return SNMP_INTEGER(area->is_type); + + case ISIS_SYS_ID: + if (!isis->sysid_set) { + *var_len = ISIS_SYS_ID_LEN; + return isis_null_sysid; + } + + *var_len = ISIS_SYS_ID_LEN; + return isis->sysid; + + case ISIS_SYS_MAXPATHSPLITS: + return SNMP_INTEGER(ISIS_SNMP_MAX_PATH_SPLITS); + + case ISIS_SYS_MAXLSPGENINT: + return SNMP_INTEGER(DEFAULT_MAX_LSP_GEN_INTERVAL); + + case ISIS_SYS_POLLESHELLORATE: + return SNMP_INTEGER(DEFAULT_HELLO_INTERVAL); + + case ISIS_SYS_WAITTIME: + /* Note: it seems that we have same fixed delay time */ + return SNMP_INTEGER(DEFAULT_MIN_LSP_GEN_INTERVAL); + + case ISIS_SYS_ADMINSTATE: + /* If daemon is running it admin state is on */ + return SNMP_INTEGER(ISIS_SNMP_ADMIN_STATE_ON); + + + case ISIS_SYS_L2TOL1LEAKING: + /* We do not allow l2-to-l1 leaking */ + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + case ISIS_SYS_MAXAGE: + return SNMP_INTEGER(MAX_AGE); + + case ISIS_SYS_RECEIVELSPBUFFERSIZE: + if (area == NULL) + return SNMP_INTEGER(DEFAULT_LSP_MTU); + + return SNMP_INTEGER(area->lsp_mtu); + + case ISIS_SYS_PROTSUPPORTED: + *var_len = 1; + return &isis_snmp_protocols_supported; + + case ISIS_SYS_NOTIFICATIONENABLE: + if (isis->snmp_notifications) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + default: + break; + } + + return NULL; +} + + +static uint8_t *isis_snmp_find_man_area(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + int res; + struct iso_address *area_addr = NULL; + oid *oid_idx; + size_t oid_idx_len; + size_t off = 0; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + + if (exact) { + res = isis_snmp_area_addr_lookup_exact(oid_idx, oid_idx_len, + NULL, &area_addr); + + if (!res) + return NULL; + + } else { + res = isis_snmp_area_addr_lookup_next(oid_idx, oid_idx_len, + NULL, &area_addr); + + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = area_addr->addr_len; + + for (off = 0; off < area_addr->addr_len; off++) + name[v->namelen + 1 + off] = area_addr->area_addr[off]; + + *length = v->namelen + 1 + area_addr->addr_len; + } + + switch (v->magic) { + case ISIS_MANAREA_ADDREXISTSTATE: + return SNMP_INTEGER(ISIS_SNMP_ROW_STATUS_ACTIVE); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_area_addr(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* + * Area addresses in sense of addresses reported by L1 lsps + * are not supported yet. + */ + (void)v; + (void)name; + (void)length; + (void)exact; + (void)var_len; + + + *write_method = NULL; + + return NULL; +} + +static uint8_t *isis_snmp_find_summ_addr(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* + * So far there is no way to set summary table values through cli + * and snmp operations are read-only, hence there are no entries + */ + (void)v; + (void)name; + (void)length; + (void)exact; + (void)var_len; + *write_method = NULL; + + return NULL; +} + +static uint8_t *isis_snmp_find_redistribute_addr(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* + * It is not clear at the point whether redist code in isis is actually + * used for now we will consider that entries are not present + */ + (void)v; + (void)name; + (void)length; + (void)exact; + (void)var_len; + *write_method = NULL; + + return NULL; +} + +static uint8_t *isis_snmp_find_router(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + uint8_t cmp_buf[ISIS_SYS_ID_LEN]; + size_t cmp_len; + int try_exact; + int cmp_level; + int res; + struct isis_dynhn *dyn = NULL; + oid *oid_idx; + size_t oid_idx_len; + size_t off = 0; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return NULL; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + + if (exact) { + res = isis_snmp_conv_exact(cmp_buf, sizeof(cmp_buf), &cmp_len, + oid_idx, oid_idx_len); + + if (!res || cmp_len != ISIS_SYS_ID_LEN + || oid_idx_len != (cmp_len + 2)) + /* + * Bad conversion, or bad length, + * or extra oids at the end + */ + return NULL; + + if (oid_idx[ISIS_SYS_ID_LEN + 1] < IS_LEVEL_1 + || oid_idx[ISIS_SYS_ID_LEN + 1] > IS_LEVEL_2) + /* Level part of the index is out of range */ + return NULL; + + cmp_level = (int)oid_idx[ISIS_SYS_ID_LEN + 1]; + + dyn = dynhn_find_by_id(isis, cmp_buf); + + if (dyn == NULL || dyn->level != cmp_level) + return NULL; + + switch (v->magic) { + case ISIS_ROUTER_HOSTNAME: + *var_len = strlen(dyn->hostname); + return (uint8_t *)dyn->hostname; + + case ISIS_ROUTER_ID: + /* It seems that we do no know router-id in lsps */ + return SNMP_INTEGER(0); + + default: + break; + } + + return NULL; + } + + res = isis_snmp_conv_next(cmp_buf, sizeof(cmp_buf), &cmp_len, + &try_exact, oid_idx, oid_idx_len); + + + if (!res) + /* Bad conversion */ + return NULL; + + if (cmp_len != ISIS_SYS_ID_LEN) { + /* We do not have valid index oids */ + memset(cmp_buf, 0, sizeof(cmp_buf)); + cmp_level = 0; + } else if (try_exact) + /* + * We have no valid level index. + * Let start from non-existing level 0 and + * hence not need to do exact match + */ + cmp_level = 0; + else if (oid_idx_len < (ISIS_SYS_ID_LEN + 2)) + cmp_level = 0; + else if (oid_idx[ISIS_SYS_ID_LEN + 1] <= IS_LEVEL_2) + cmp_level = (int)oid_idx[ISIS_SYS_ID_LEN + 1]; + else + /* + * Any value greater than 2 will have the same result + * but we can have integer overflows, hence 3 is a reasonable + * choice + */ + cmp_level = (int)(IS_LEVEL_2 + 1); + + dyn = dynhn_snmp_next(isis, cmp_buf, cmp_level); + + if (dyn == NULL) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = ISIS_SYS_ID_LEN; + + for (off = 0; off < ISIS_SYS_ID_LEN; off++) + name[v->namelen + 1 + off] = dyn->id[off]; + + name[v->namelen + 1 + ISIS_SYS_ID_LEN] = (oid)dyn->level; + + /* Set length */ + *length = v->namelen + 1 + ISIS_SYS_ID_LEN + 1; + + switch (v->magic) { + case ISIS_ROUTER_HOSTNAME: + *var_len = strlen(dyn->hostname); + return (uint8_t *)dyn->hostname; + + case ISIS_ROUTER_ID: + /* It seems that we do no know router-id in lsps */ + return SNMP_INTEGER(0); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_sys_level(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + oid *oid_idx; + size_t oid_idx_len; + int level; + int level_match; + struct isis_area *area = NULL; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return NULL; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + + if (exact) { + if (oid_idx == NULL || oid_idx_len != 1) + return NULL; + + if (oid_idx[0] == IS_LEVEL_1) + level = IS_LEVEL_1; + else if (oid_idx[0] == IS_LEVEL_2) + level = IS_LEVEL_2; + else + return NULL; + + } else { + if (oid_idx == NULL) + level = IS_LEVEL_1; + else if (oid_idx_len == 0) + level = IS_LEVEL_1; + else if (oid_idx[0] < IS_LEVEL_1) + level = IS_LEVEL_1; + else if (oid_idx[0] < IS_LEVEL_2) + level = IS_LEVEL_2; + else + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = level; + + /* Set length */ + *length = v->namelen + 1; + } + + area = NULL; + + if (!list_isempty(isis->area_list)) + area = listgetdata(listhead(isis->area_list)); + + level_match = 0; + + if (area != NULL) + level_match = isis_snmp_get_level_match(area->is_type, level); + + switch (v->magic) { + case ISIS_SYSLEVEL_ORIGLSPBUFFSIZE: + if (level_match) + return SNMP_INTEGER(area->lsp_mtu); + + return SNMP_INTEGER(DEFAULT_LSP_MTU); + + case ISIS_SYSLEVEL_MINLSPGENINT: + if (level_match) + return SNMP_INTEGER(area->lsp_gen_interval[level - 1]); + else + return SNMP_INTEGER(DEFAULT_MIN_LSP_GEN_INTERVAL); + + case ISIS_SYSLEVEL_STATE: + if (level_match) { + if (area->overload_bit) + return SNMP_INTEGER( + ISIS_SNMP_LEVEL_STATE_OVERLOADED); + + return SNMP_INTEGER(ISIS_SNMP_LEVEL_STATE_ON); + } + return SNMP_INTEGER(ISIS_SNMP_LEVEL_STATE_OFF); + + case ISIS_SYSLEVEL_SETOVERLOAD: + if (level_match && area->overload_bit) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + case ISIS_SYSLEVEL_SETOVERLOADUNTIL: + /* We do not have automatic cleanup of overload bit */ + return SNMP_INTEGER(0); + + case ISIS_SYSLEVEL_METRICSTYLE: + if (level_match) { + if (area->newmetric && area->oldmetric) + return SNMP_INTEGER( + ISIS_SNMP_METRIC_STYLE_BOTH); + + if (area->newmetric) + return SNMP_INTEGER( + ISIS_SNMP_METRIC_STYLE_WIDE); + + return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_NARROW); + } + return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_NARROW); + + case ISIS_SYSLEVEL_SPFCONSIDERS: + return SNMP_INTEGER(ISIS_SNMP_METRIC_STYLE_BOTH); + + case ISIS_SYSLEVEL_TEENABLED: + if (level_match && IS_MPLS_TE(area->mta)) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_system_counter(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + oid *oid_idx; + size_t oid_idx_len; + int level; + int level_match; + struct isis_area *area = NULL; + uint32_t val; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return NULL; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + + if (exact) { + if (oid_idx == NULL || oid_idx_len != 1) + return 0; + + if (oid_idx[0] == IS_LEVEL_1) + level = IS_LEVEL_1; + else if (oid_idx[0] == IS_LEVEL_2) + level = IS_LEVEL_2; + else + return NULL; + + } else { + if (oid_idx == NULL) + level = IS_LEVEL_1; + else if (oid_idx_len == 0) + level = IS_LEVEL_1; + else if (oid_idx[0] < IS_LEVEL_1) + level = IS_LEVEL_1; + else if (oid_idx[0] < IS_LEVEL_2) + level = IS_LEVEL_2; + else + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = level; + + /* Set length */ + *length = v->namelen + 1; + } + + area = NULL; + + if (!list_isempty(isis->area_list)) + area = listgetdata(listhead(isis->area_list)); + + level_match = 0; + + if (area != NULL) + level_match = isis_snmp_get_level_match(area->is_type, level); + + if (!level_match) + /* If level does not match all counters are zeros */ + return SNMP_INTEGER(0); + + switch (v->magic) { + case ISIS_SYSSTAT_CORRLSPS: + val = 0; + break; + + case ISIS_SYSSTAT_AUTHTYPEFAILS: + val = (uint32_t)area->auth_type_failures[level - 1]; + break; + + case ISIS_SYSSTAT_AUTHFAILS: + val = (uint32_t)area->auth_failures[level - 1]; + break; + + case ISIS_SYSSTAT_LSPDBASEOLOADS: + val = area->overload_counter; + break; + + case ISIS_SYSSTAT_MANADDRDROPFROMAREAS: + /* We do not support manual addresses */ + val = 0; + break; + + case ISIS_SYSSTAT_ATTMPTTOEXMAXSEQNUMS: + val = area->lsp_exceeded_max_counter; + break; + + case ISIS_SYSSTAT_SEQNUMSKIPS: + val = area->lsp_seqno_skipped_counter; + break; + + case ISIS_SYSSTAT_OWNLSPPURGES: + if (!area->purge_originator) + val = 0; + else + val = area->lsp_purge_count[level - 1]; + break; + + case ISIS_SYSSTAT_IDFIELDLENMISMATCHES: + val = (uint32_t)area->id_len_mismatches[level - 1]; + break; + + case ISIS_SYSSTAT_PARTCHANGES: + /* Not supported */ + val = 0; + break; + + case ISIS_SYSSTAT_SPFRUNS: + val = (uint32_t)area->spf_run_count[level - 1]; + break; + + case ISIS_SYSSTAT_LSPERRORS: + val = (uint32_t)area->lsp_error_counter[level - 1]; + break; + + default: + return NULL; + } + + return SNMP_INTEGER(val); +} + +static uint8_t *isis_snmp_find_next_circ_index(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + switch (v->magic) { + case ISIS_NEXTCIRC_INDEX: + /* + * We do not support circuit creation through snmp + */ + return SNMP_INTEGER(0); + + default: + break; + } + + return 0; +} + +static uint8_t *isis_snmp_find_circ(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + /* Index is circuit-id: 1-255 */ + oid *oid_idx; + size_t oid_idx_len; + struct isis_circuit *circuit; + uint32_t up_ticks; + uint32_t delta_ticks; + time_t now_time; + int res; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_circuit_lookup_exact(oid_idx, oid_idx_len, + &circuit); + + if (!res || oid_idx_len != 1) + return NULL; + + } else { + res = isis_snmp_circuit_lookup_next(oid_idx, oid_idx_len, + &circuit); + + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = circuit->snmp_id; + + /* Set length */ + *length = v->namelen + 1; + } + + switch (v->magic) { + case ISIS_CIRC_IFINDEX: + if (circuit->interface == NULL) + return SNMP_INTEGER(0); + + return SNMP_INTEGER(circuit->interface->ifindex); + + case ISIS_CIRC_ADMINSTATE: + return SNMP_INTEGER(ISIS_SNMP_ADMIN_STATE_ON); + + case ISIS_CIRC_EXISTSTATE: + return SNMP_INTEGER(ISIS_SNMP_ROW_STATUS_ACTIVE); + + case ISIS_CIRC_TYPE: + /* + * Note: values do not match 100%: + * + * 1. From isis_circuit.h: + * CIRCUIT_T_UNKNOWN 0 + * CIRCUIT_T_BROADCAST 1 + * CIRCUIT_T_P2P 2 + * CIRCUIT_T_LOOPBACK 3 + * + * 2. From rfc: + * broadcast(1), + * ptToPt(2), + * staticIn(3), + * staticOut(4), + */ + + return SNMP_INTEGER(circuit->circ_type); + + case ISIS_CIRC_EXTDOMAIN: + if (circuit->ext_domain) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + case ISIS_CIRC_LEVELTYPE: + return SNMP_INTEGER(circuit->is_type); + + case ISIS_CIRC_PASSIVECIRCUIT: + if (circuit->is_passive) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + case ISIS_CIRC_MESHGROUPENABLED: + /* Not supported */ + return SNMP_INTEGER(ISIS_SNMP_MESH_GROUP_INACTIVE); + + case ISIS_CIRC_MESHGROUP: + /* Not supported */ + return SNMP_INTEGER(0); + + case ISIS_CIRC_SMALLHELLOS: + /* + * return false if lan hellos must be padded + */ + if (circuit->pad_hellos == ISIS_HELLO_PADDING_ALWAYS || + (circuit->pad_hellos == + ISIS_HELLO_PADDING_DURING_ADJACENCY_FORMATION && + circuit->upadjcount[0] + circuit->upadjcount[1] == 0)) + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_TRUE); + + case ISIS_CIRC_LASTUPTIME: + if (circuit->last_uptime == 0) + return SNMP_INTEGER(0); + + up_ticks = (uint32_t)netsnmp_get_agent_uptime(); + now_time = time(NULL); + + if (circuit->last_uptime >= now_time) + return SNMP_INTEGER(up_ticks); + + delta_ticks = (now_time - circuit->last_uptime) * 10; + + if (up_ticks < delta_ticks) + return SNMP_INTEGER(up_ticks); + + return SNMP_INTEGER(up_ticks - delta_ticks); + + case ISIS_CIRC_3WAYENABLED: + /* Not supported */ + return SNMP_INTEGER(ISIS_SNMP_TRUTH_VALUE_FALSE); + + case ISIS_CIRC_EXTENDEDCIRCID: + /* Used for 3-way hand shake only */ + return SNMP_INTEGER(0); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_circ_level(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + static uint8_t circuit_id_val[ISIS_SYS_ID_LEN + 1]; + /* Index is circuit-id: 1-255 + level: 1-2 */ + oid *oid_idx; + size_t oid_idx_len; + int res; + struct isis_circuit *circuit; + int level; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) + return NULL; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_circuit_level_lookup_exact(oid_idx, oid_idx_len, + 1, &circuit, &level); + + if (!res || oid_idx_len != 2) + return NULL; + + } else { + res = isis_snmp_circuit_level_lookup_next(oid_idx, oid_idx_len, + 1, &circuit, &level); + + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = circuit->snmp_id; + name[v->namelen + 1] = level; + + /* Set length */ + *length = v->namelen + 2; + } + + switch (v->magic) { + case ISIS_CIRCLEVEL_METRIC: + return SNMP_INTEGER(circuit->metric[level - 1]); + + case ISIS_CIRCLEVEL_WIDEMETRIC: + if (circuit->area == NULL || !circuit->area->newmetric) { + /* What should we do if wide metric is not supported? */ + return SNMP_INTEGER(0); + } + return SNMP_INTEGER(circuit->te_metric[level - 1]); + + case ISIS_CIRCLEVEL_ISPRIORITY: + return SNMP_INTEGER(circuit->priority[level - 1]); + + case ISIS_CIRCLEVEL_IDOCTET: + return SNMP_INTEGER(circuit->circuit_id); + + case ISIS_CIRCLEVEL_ID: + if (circuit->circ_type != CIRCUIT_T_P2P) { + /* + * Unless it is point-to-point circuit, the value is and + * empty octet string + */ + *var_len = 0; + return circuit_id_val; + } + + /* !!!!!! Circuit-id is zero for p2p links */ + if (circuit->u.p2p.neighbor == NULL + || circuit->u.p2p.neighbor->adj_state != ISIS_ADJ_UP) { + /* No adjacency or adjacency not fully up yet */ + memcpy(circuit_id_val, isis->sysid, ISIS_SYS_ID_LEN); + circuit_id_val[ISIS_SYS_ID_LEN] = circuit->circuit_id; + *var_len = ISIS_SYS_ID_LEN + 1; + return circuit_id_val; + } + + /* Adjacency fully-up */ + memcpy(circuit_id_val, circuit->u.p2p.neighbor->sysid, + ISIS_SYS_ID_LEN); + circuit_id_val[ISIS_SYS_ID_LEN] = 0; + *var_len = ISIS_SYS_ID_LEN + 1; + return circuit_id_val; + + case ISIS_CIRCLEVEL_DESIS: + if (circuit->circ_type != CIRCUIT_T_BROADCAST + || !circuit->u.bc.is_dr[level - 1]) { + /* + * Unless it is lan circuit participating in dis process + * the value is an empty octet string + */ + *var_len = 0; + return circuit_id_val; + } + + *var_len = ISIS_SYS_ID_LEN + 1; + + if (level == IS_LEVEL_1) + return circuit->u.bc.l1_desig_is; + + return circuit->u.bc.l2_desig_is; + + case ISIS_CIRCLEVEL_HELLOMULTIPLIER: + return SNMP_INTEGER(circuit->hello_multiplier[level - 1]); + + case ISIS_CIRCLEVEL_HELLOTIMER: + return SNMP_INTEGER(circuit->hello_interval[level - 1] * 1000); + + case ISIS_CIRCLEVEL_DRHELLOTIMER: + return SNMP_INTEGER(circuit->hello_interval[level - 1] * 1000); + + case ISIS_CIRCLEVEL_LSPTHROTTLE: + if (circuit->area) + return SNMP_INTEGER( + circuit->area->min_spf_interval[level - 1] + * 1000); + else + return SNMP_INTEGER(0); + + case ISIS_CIRCLEVEL_MINLSPRETRANSINT: + if (circuit->area) + return SNMP_INTEGER( + circuit->area->min_spf_interval[level - 1]); + else + return SNMP_INTEGER(0); + + case ISIS_CIRCLEVEL_CSNPINTERVAL: + return SNMP_INTEGER(circuit->csnp_interval[level - 1]); + + case ISIS_CIRCLEVEL_PARTSNPINTERVAL: + return SNMP_INTEGER(circuit->psnp_interval[level - 1]); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_circ_counter(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* Index circuit-id 1-255 + level */ + oid *oid_idx; + size_t oid_idx_len; + int res; + struct isis_circuit *circuit; + int level; + uint32_t val = 0; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_circuit_level_lookup_exact(oid_idx, oid_idx_len, + 1, &circuit, &level); + + if (!res || oid_idx_len != 2) + return NULL; + + } else { + res = isis_snmp_circuit_level_lookup_next(oid_idx, oid_idx_len, + 1, &circuit, &level); + + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = circuit->snmp_id; + if (circuit->circ_type == CIRCUIT_T_P2P) + name[v->namelen + 1] = ISIS_SNMP_P2P_CIRCUIT; + else + name[v->namelen + 1] = level; + + /* Set length */ + *length = v->namelen + 2; + } + + switch (v->magic) { + case ISIS_CIRC_ADJCHANGES: + val = circuit->adj_state_changes; + break; + + case ISIS_CIRC_NUMADJ: + if (circuit->circ_type == CIRCUIT_T_P2P) { + val = circuit->u.p2p.neighbor == NULL ? 0 : 1; + break; + } + + if (circuit->circ_type != CIRCUIT_T_BROADCAST) { + val = 0; + break; + } + + if (level == IS_LEVEL_1) { + if (circuit->u.bc.adjdb[0] == NULL) + val = 0; + else + val = listcount(circuit->u.bc.adjdb[0]); + break; + } + + if (circuit->u.bc.adjdb[1] == NULL) + val = 0; + else + val = listcount(circuit->u.bc.adjdb[1]); + + break; + + case ISIS_CIRC_INITFAILS: + val = circuit->init_failures; /* counter never incremented */ + break; + + case ISIS_CIRC_REJADJS: + val = circuit->rej_adjacencies; + break; + + case ISIS_CIRC_IDFIELDLENMISMATCHES: + val = circuit->id_len_mismatches; + break; + + case ISIS_CIRC_MAXAREAADDRMISMATCHES: + val = circuit->max_area_addr_mismatches; + break; + + case ISIS_CIRC_AUTHTYPEFAILS: + val = circuit->auth_type_failures; + break; + + case ISIS_CIRC_AUTHFAILS: + val = circuit->auth_failures; + break; + + case ISIS_CIRC_LANDESISCHANGES: + if (circuit->circ_type == CIRCUIT_T_P2P) + val = 0; + else + val = circuit->desig_changes[level - 1]; + break; + + default: + return NULL; + } + + return SNMP_INTEGER(val); +} + +static uint8_t *isis_snmp_find_isadj(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + /* Index is circuit-id: 1-255 + adj-id: 1-... */ + oid *oid_idx; + size_t oid_idx_len; + int res; + time_t val; + struct isis_adjacency *adj; + uint32_t up_ticks; + uint32_t delta_ticks; + time_t now_time; + + /* Ring buffer to print SNPA */ +#define FORMAT_BUF_COUNT 4 + static char snpa[FORMAT_BUF_COUNT][ISO_SYSID_STRLEN]; + static size_t cur_buf = 0; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_NONE, &adj, + NULL, NULL, NULL); + + if (!res || oid_idx_len != 2) + return NULL; + + } else { + res = isis_snmp_adj_lookup_next(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_NONE, &adj, + NULL, NULL, NULL); + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = adj->circuit->snmp_id; + name[v->namelen + 1] = adj->snmp_idx; + + /* Set length */ + *length = v->namelen + 2; + } + + switch (v->magic) { + case ISIS_ISADJ_STATE: + return SNMP_INTEGER(isis_snmp_adj_state(adj->adj_state)); + + case ISIS_ISADJ_3WAYSTATE: + return SNMP_INTEGER(adj->threeway_state); + + case ISIS_ISADJ_NEIGHSNPAADDRESS: { + cur_buf = (cur_buf + 1) % FORMAT_BUF_COUNT; + snprintfrr(snpa[cur_buf], ISO_SYSID_STRLEN, "%pSY", adj->snpa); + *var_len = strlen(snpa[cur_buf]); + return (uint8_t *)snpa[cur_buf]; + } + + case ISIS_ISADJ_NEIGHSYSTYPE: + return SNMP_INTEGER(isis_snmp_adj_neightype(adj->sys_type)); + + case ISIS_ISADJ_NEIGHSYSID: + *var_len = sizeof(adj->sysid); + return adj->sysid; + + case ISIS_ISADJ_NBREXTENDEDCIRCID: + return SNMP_INTEGER(adj->ext_circuit_id != 0 ? 1 : 0); + + case ISIS_ISADJ_USAGE: + /* It seems that no value conversion is required */ + return SNMP_INTEGER(adj->adj_usage); + + case ISIS_ISADJ_HOLDTIMER: + /* + * It seems that we want remaining timer + */ + if (adj->last_upd != 0) { + val = time(NULL); + if (val < (adj->last_upd + adj->hold_time)) + return SNMP_INTEGER(adj->last_upd + + adj->hold_time - val); + } + /* Not running or just expired */ + return SNMP_INTEGER(0); + + case ISIS_ISADJ_NEIGHPRIORITY: + return SNMP_INTEGER(adj->prio[adj->level - 1]); + + case ISIS_ISADJ_LASTUPTIME: + if (adj->flaps == 0) + return SNMP_INTEGER(0); + + up_ticks = (uint32_t)netsnmp_get_agent_uptime(); + + now_time = time(NULL); + + if (adj->last_flap >= now_time) + return SNMP_INTEGER(up_ticks); + + delta_ticks = (now_time - adj->last_flap) * 10; + + if (up_ticks < delta_ticks) + return SNMP_INTEGER(up_ticks); + + return SNMP_INTEGER(up_ticks - delta_ticks); + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_isadj_area(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* Index circuit-id: 1-255 + adj-id: 1-... */ + oid *oid_idx; + size_t oid_idx_len; + int res; + struct isis_adjacency *adj; + oid data_idx; + uint8_t *data; + size_t data_len; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_AREA_ADDR, + &adj, NULL, &data, &data_len); + + if (!res || oid_idx_len != 3) + return NULL; + + } else { + res = isis_snmp_adj_lookup_next( + oid_idx, oid_idx_len, ISIS_SNMP_ADJ_DATA_AREA_ADDR, + &adj, &data_idx, &data, &data_len); + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = adj->circuit->snmp_id; + name[v->namelen + 1] = adj->snmp_idx; + name[v->namelen + 2] = data_idx; + + /* Set length */ + *length = v->namelen + 3; + } + + switch (v->magic) { + case ISIS_ISADJAREA_ADDRESS: + *var_len = data_len; + return data; + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_isadj_ipaddr(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* Index circuit-id 1-255 + adj-id 1-... */ + oid *oid_idx; + size_t oid_idx_len; + int res; + struct isis_adjacency *adj; + oid data_idx; + uint8_t *data; + size_t data_len; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_IP_ADDR, + &adj, NULL, &data, &data_len); + + if (!res || oid_idx_len != 3) + return NULL; + } else { + res = isis_snmp_adj_lookup_next( + oid_idx, oid_idx_len, ISIS_SNMP_ADJ_DATA_IP_ADDR, &adj, + &data_idx, &data, &data_len); + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = adj->circuit->snmp_id; + name[v->namelen + 1] = adj->snmp_idx; + name[v->namelen + 2] = data_idx; + + /* Set length */ + *length = v->namelen + 3; + } + + switch (v->magic) { + case ISIS_ISADJIPADDR_TYPE: + if (data_len == 4) + return SNMP_INTEGER(ISIS_SNMP_INET_TYPE_V4); + + return SNMP_INTEGER(ISIS_SNMP_INET_TYPE_V6); + + case ISIS_ISADJIPADDR_ADDRESS: + *var_len = data_len; + return data; + + default: + break; + } + + return NULL; +} + +static uint8_t *isis_snmp_find_isadj_prot_supp(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + /* Index circuit-id 1-255 + adj-id 1-... */ + oid *oid_idx; + size_t oid_idx_len; + int res; + struct isis_adjacency *adj; + oid data_idx; + uint8_t *data; + size_t data_len; + + *write_method = NULL; + + if (*length <= v->namelen) { + oid_idx = NULL; + oid_idx_len = 0; + } else if (memcmp(name, v->name, v->namelen * sizeof(oid)) != 0) { + oid_idx = NULL; + oid_idx_len = 0; + } else { + oid_idx = name + v->namelen; + oid_idx_len = *length - v->namelen; + } + if (exact) { + res = isis_snmp_adj_lookup_exact(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_PROTO, &adj, + NULL, &data, &data_len); + + if (!res || oid_idx_len != 3) + return NULL; + + } else { + res = isis_snmp_adj_lookup_next(oid_idx, oid_idx_len, + ISIS_SNMP_ADJ_DATA_PROTO, &adj, + &data_idx, &data, &data_len); + if (!res) + return NULL; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + name[v->namelen] = adj->circuit->snmp_id; + name[v->namelen + 1] = adj->snmp_idx; + name[v->namelen + 2] = data_idx; + + /* Set length */ + *length = v->namelen + 3; + } + + switch (v->magic) { + case ISIS_ISADJPROTSUPP_PROTOCOL: + return SNMP_INTEGER(*data); + + default: + break; + } + + return NULL; +} + + +/* Register ISIS-MIB. */ +static int isis_snmp_init(struct event_loop *tm) +{ + struct isis_func_to_prefix *h2f = isis_func_to_prefix_arr; + struct variable *v; + + for (size_t off = 0; off < isis_var_count; off++) { + v = &isis_var_arr[off]; + + if (v->findVar != h2f->ihtp_func) { + /* Next table */ + h2f++; + assert(h2f < (isis_func_to_prefix_arr + + isis_func_to_prefix_count)); + assert(v->findVar == h2f->ihtp_func); + } + + v->namelen = h2f->ihtp_pref_len + 1; + memcpy(v->name, h2f->ihtp_pref_oid, + h2f->ihtp_pref_len * sizeof(oid)); + v->name[h2f->ihtp_pref_len] = v->magic; + } + + + smux_init(tm); + REGISTER_MIB("mibII/isis", isis_var_arr, variable, isis_oid); + return 0; +} + +/* + * ISIS notification functions: we have one function per notification + */ +static int isis_snmp_trap_throttle(oid trap_id) +{ + time_t time_now; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL || !isis->snmp_notifications || !smux_enabled()) + return 0; + + time_now = time(NULL); + + if ((isis_snmp_trap_timestamp[trap_id] + 5) > time_now) + /* Throttle trap rate at 1 in 5 secs */ + return 0; + + isis_snmp_trap_timestamp[trap_id] = time_now; + return 1; +} + +static int isis_snmp_db_overload_update(const struct isis_area *area) +{ + netsnmp_variable_list *notification_vars; + long val; + uint32_t off; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_DB_OVERLOAD)) + return 0; + + notification_vars = NULL; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)&isis_snmp_trap_val_db_overload, + sizeof(isis_snmp_trap_val_db_overload)); + + /* Prepare data */ + val = area->is_type; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_index, + array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER, + (uint8_t *)&val, sizeof(val)); + + /* Patch sys_level_state with proper index */ + off = array_size(isis_snmp_trap_data_var_sys_level_state) - 1; + isis_snmp_trap_data_var_sys_level_state[off] = val; + + /* Prepare data */ + if (area->overload_bit) + val = ISIS_SNMP_LEVEL_STATE_OVERLOADED; + else + val = ISIS_SNMP_LEVEL_STATE_ON; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_state, + array_size(isis_snmp_trap_data_var_sys_level_state), INTEGER, + (uint8_t *)&val, sizeof(val)); + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); + return 0; +} + +static int isis_snmp_lsp_exceed_max_update(const struct isis_area *area, + const uint8_t *lsp_id) +{ + netsnmp_variable_list *notification_vars; + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_EXCEED_MAX)) + return 0; + + notification_vars = NULL; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)&isis_snmp_trap_val_lsp_exceed_max, + sizeof(isis_snmp_trap_val_lsp_exceed_max)); + + /* Prepare data */ + val = area->is_type; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_index, + array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER, + (uint8_t *)&val, sizeof(val)); + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2); + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); + return 0; +} + + +/* + * A common function to handle popular combination of trap objects + * isisNotificationSysLevelIndex, + * optional-object-a + * isisNotificationCircIfIndex, + * optional-object-b + */ +static void isis_snmp_update_worker_a(const struct isis_circuit *circuit, + oid trap_id, const oid *oid_a, + size_t oid_a_len, uint8_t type_a, + const void *data_a, size_t data_a_len, + const oid *oid_b, size_t oid_b_len, + uint8_t type_b, const void *data_b, + size_t data_b_len) +{ + netsnmp_variable_list *notification_vars = NULL; + oid var_name[MAX_OID_LEN]; + size_t var_count; + long val; + + /* Sanity */ + if (trap_id != ISIS_TRAP_ID_LEN_MISMATCH + && trap_id != ISIS_TRAP_MAX_AREA_ADDR_MISMATCH + && trap_id != ISIS_TRAP_OWN_LSP_PURGE + && trap_id != ISIS_TRAP_SEQNO_SKIPPED + && trap_id != ISIS_TRAP_AUTHEN_TYPE_FAILURE + && trap_id != ISIS_TRAP_AUTHEN_FAILURE + && trap_id != ISIS_TRAP_REJ_ADJACENCY) + return; + + /* Put in trap value */ + memcpy(var_name, isis_snmp_notifications, + sizeof(isis_snmp_notifications)); + var_count = array_size(isis_snmp_notifications); + var_name[var_count++] = trap_id; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)var_name, var_count * sizeof(oid)); + + val = circuit->is_type; + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_index, + array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER, + (uint8_t *)&val, sizeof(val)); + + if (oid_a_len != 0) { + if (oid_a == NULL || data_a == NULL || data_a_len == 0) + return; + + snmp_varlist_add_variable(¬ification_vars, oid_a, oid_a_len, + type_a, (uint8_t *)data_a, + data_a_len); + } + + if (circuit->interface == NULL) + val = 0; + else + val = circuit->interface->ifindex; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_circ_if_index, + array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + + if (oid_b_len != 0) { + if (oid_b == NULL || data_b == NULL || data_b_len == 0) + return; + + snmp_varlist_add_variable(¬ification_vars, oid_b, oid_b_len, + type_b, (uint8_t *)data_b, + data_b_len); + } + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); +} + +/* + * A common function to handle popular combination of trap objects + * isisNotificationSysLevelIndex, + * isisNotificationCircIfIndex, + * optional-var-a + * optional-var-b + * + * Note: the only difference with worker_a is order of circ-if-index vs + * optional-var-a + */ +static void isis_snmp_update_worker_b(const struct isis_circuit *circuit, + oid trap_id, const oid *oid_a, + size_t oid_a_len, uint8_t type_a, + const void *data_a, size_t data_a_len, + const oid *oid_b, size_t oid_b_len, + uint8_t type_b, const void *data_b, + size_t data_b_len) +{ + netsnmp_variable_list *notification_vars = NULL; + oid var_name[MAX_OID_LEN]; + size_t var_count; + long val; + + /* Sanity */ + if (trap_id != ISIS_TRAP_VERSION_SKEW + && trap_id != ISIS_TRAP_LSP_TOO_LARGE + && trap_id != ISIS_TRAP_ADJ_STATE_CHANGE) + return; + + /* Put in trap value */ + memcpy(var_name, isis_snmp_notifications, + sizeof(isis_snmp_notifications)); + var_count = array_size(isis_snmp_notifications); + var_name[var_count++] = trap_id; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)var_name, var_count * sizeof(oid)); + + val = circuit->is_type; + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_index, + array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER, + (uint8_t *)&val, sizeof(val)); + + if (circuit->interface == NULL) + val = 0; + else + val = circuit->interface->ifindex; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_circ_if_index, + array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + + if (oid_a_len != 0) { + if (oid_a == NULL || data_a == NULL || data_a_len == 0) + return; + + snmp_varlist_add_variable(¬ification_vars, oid_a, oid_a_len, + type_a, (uint8_t *)data_a, + data_a_len); + } + + if (oid_b_len != 0) { + if (oid_b == NULL || data_b == NULL || data_b_len == 0) + return; + + snmp_varlist_add_variable(¬ification_vars, oid_b, oid_b_len, + type_b, (uint8_t *)data_b, + data_b_len); + } + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); +} + + +static int isis_snmp_id_len_mismatch_update(const struct isis_circuit *circuit, + uint8_t rcv_id, const char *raw_pdu, + size_t raw_pdu_len) +{ + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_ID_LEN_MISMATCH)) + return 0; + + val = rcv_id; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_ID_LEN_MISMATCH, + isis_snmp_trap_data_var_pdu_field_len, + array_size(isis_snmp_trap_data_var_pdu_field_len), UNSIGNED32, + &val, sizeof(val), isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int +isis_snmp_max_area_addr_mismatch_update(const struct isis_circuit *circuit, + uint8_t max_addrs, const char *raw_pdu, + size_t raw_pdu_len) +{ + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_MAX_AREA_ADDR_MISMATCH)) + return 0; + + val = max_addrs; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_MAX_AREA_ADDR_MISMATCH, + isis_snmp_trap_data_var_pdu_max_area_addr, + array_size(isis_snmp_trap_data_var_pdu_max_area_addr), + UNSIGNED32, &val, sizeof(val), + isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int isis_snmp_own_lsp_purge_update(const struct isis_circuit *circuit, + const uint8_t *lsp_id) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_OWN_LSP_PURGE)) + return 0; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_OWN_LSP_PURGE, NULL, 0, STRING, NULL, 0, + isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2); + return 0; +} + +static int isis_snmp_seqno_skipped_update(const struct isis_circuit *circuit, + const uint8_t *lsp_id) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_SEQNO_SKIPPED)) + return 0; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_SEQNO_SKIPPED, NULL, 0, STRING, NULL, 0, + isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2); + return 0; +} + +static int +isis_snmp_authentication_type_failure_update(const struct isis_circuit *circuit, + const char *raw_pdu, + size_t raw_pdu_len) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_AUTHEN_TYPE_FAILURE)) + return 0; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_AUTHEN_TYPE_FAILURE, NULL, 0, STRING, NULL, + 0, isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int +isis_snmp_authentication_failure_update(const struct isis_circuit *circuit, + char const *raw_pdu, size_t raw_pdu_len) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_AUTHEN_FAILURE)) + return 0; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_AUTHEN_FAILURE, NULL, 0, STRING, NULL, 0, + isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int isis_snmp_version_skew_update(const struct isis_circuit *circuit, + uint8_t version, const char *raw_pdu, + size_t raw_pdu_len) +{ + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_VERSION_SKEW)) + return 0; + + val = version; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_b( + circuit, ISIS_TRAP_VERSION_SKEW, + isis_snmp_trap_data_var_pdu_proto_ver, + array_size(isis_snmp_trap_data_var_pdu_proto_ver), UNSIGNED32, + &val, sizeof(val), isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int isis_snmp_area_mismatch_update(const struct isis_circuit *circuit, + const char *raw_pdu, + size_t raw_pdu_len) +{ + /* + * This is a special case because + * it does not include isisNotificationSysLevelIndex + */ + netsnmp_variable_list *notification_vars; + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_AREA_MISMATCH)) + return 0; + + notification_vars = NULL; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)&isis_snmp_trap_val_area_mismatch, + sizeof(isis_snmp_trap_val_area_mismatch)); + + + if (circuit->interface == NULL) + val = 0; + else + val = circuit->interface->ifindex; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_circ_if_index, + array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); + + return 0; +} + +static int isis_snmp_reject_adjacency_update(const struct isis_circuit *circuit, + const char *raw_pdu, + size_t raw_pdu_len) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_REJ_ADJACENCY)) + return 0; + + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + isis_snmp_update_worker_a( + circuit, ISIS_TRAP_REJ_ADJACENCY, NULL, 0, STRING, NULL, 0, + isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + return 0; +} + +static int isis_snmp_lsp_too_large_update(const struct isis_circuit *circuit, + uint32_t pdu_size, + const uint8_t *lsp_id) +{ + if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_TOO_LARGE)) + return 0; + + isis_snmp_update_worker_b( + circuit, ISIS_TRAP_LSP_TOO_LARGE, + isis_snmp_trap_data_var_pdu_lsp_size, + array_size(isis_snmp_trap_data_var_pdu_lsp_size), UNSIGNED32, + &pdu_size, sizeof(pdu_size), isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2); + return 0; +} + + +static int isis_snmp_adj_state_change_update(const struct isis_adjacency *adj) +{ + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + long val; + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL || !isis->snmp_notifications || !smux_enabled()) + return 0; + + /* Prepare data */ + memcpy(lsp_id, adj->sysid, ISIS_SYS_ID_LEN); + lsp_id[ISIS_SYS_ID_LEN] = 0; + lsp_id[ISIS_SYS_ID_LEN + 1] = 0; + + val = isis_snmp_adj_state(adj->adj_state); + + isis_snmp_update_worker_b( + adj->circuit, ISIS_TRAP_ADJ_STATE_CHANGE, + isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2, isis_snmp_trap_data_var_adj_state, + array_size(isis_snmp_trap_data_var_adj_state), INTEGER, &val, + sizeof(val)); + return 0; +} + +static int isis_snmp_lsp_error_update(const struct isis_circuit *circuit, + const uint8_t *lsp_id, + char const *raw_pdu, size_t raw_pdu_len) +{ + /* + * This is a special case because + * it have more variables + */ + netsnmp_variable_list *notification_vars; + long val; + + if (!isis_snmp_trap_throttle(ISIS_TRAP_LSP_ERROR)) + return 0; + + notification_vars = NULL; + + /* Put in trap value */ + snmp_varlist_add_variable(¬ification_vars, isis_snmp_trap_var, + array_size(isis_snmp_trap_var), ASN_OBJECT_ID, + (uint8_t *)&isis_snmp_trap_val_lsp_error, + sizeof(isis_snmp_trap_val_lsp_error)); + + /* Prepare data */ + val = circuit->is_type; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_sys_level_index, + array_size(isis_snmp_trap_data_var_sys_level_index), INTEGER, + (uint8_t *)&val, sizeof(val)); + + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_pdu_lsp_id, + array_size(isis_snmp_trap_data_var_pdu_lsp_id), STRING, lsp_id, + ISIS_SYS_ID_LEN + 2); + + /* Prepare data */ + if (circuit->interface == NULL) + val = 0; + else + val = circuit->interface->ifindex; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_circ_if_index, + array_size(isis_snmp_trap_data_var_circ_if_index), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + /* Prepare data */ + if (raw_pdu_len > ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN) + raw_pdu_len = ISIS_SNMP_TRAP_PDU_FRAGMENT_MAX_LEN; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_pdu_fragment, + array_size(isis_snmp_trap_data_var_pdu_fragment), STRING, + raw_pdu, raw_pdu_len); + + /* Prepare data */ + val = 0; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_error_offset, + array_size(isis_snmp_trap_data_var_error_offset), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + /* Prepare data */ + val = 0; + + snmp_varlist_add_variable( + ¬ification_vars, isis_snmp_trap_data_var_error_tlv_type, + array_size(isis_snmp_trap_data_var_error_tlv_type), UNSIGNED32, + (uint8_t *)&val, sizeof(val)); + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + smux_events_update(); + return 0; +} + + +static int isis_snmp_module_init(void) +{ + hook_register(isis_hook_db_overload, isis_snmp_db_overload_update); + hook_register(isis_hook_lsp_exceed_max, + isis_snmp_lsp_exceed_max_update); + hook_register(isis_hook_id_len_mismatch, + isis_snmp_id_len_mismatch_update); + hook_register(isis_hook_max_area_addr_mismatch, + isis_snmp_max_area_addr_mismatch_update); + hook_register(isis_hook_own_lsp_purge, isis_snmp_own_lsp_purge_update); + hook_register(isis_hook_seqno_skipped, isis_snmp_seqno_skipped_update); + hook_register(isis_hook_authentication_type_failure, + isis_snmp_authentication_type_failure_update); + hook_register(isis_hook_authentication_failure, + isis_snmp_authentication_failure_update); + hook_register(isis_hook_version_skew, isis_snmp_version_skew_update); + hook_register(isis_hook_area_mismatch, isis_snmp_area_mismatch_update); + hook_register(isis_hook_reject_adjacency, + isis_snmp_reject_adjacency_update); + hook_register(isis_hook_lsp_too_large, isis_snmp_lsp_too_large_update); + hook_register(isis_hook_adj_state_change, + isis_snmp_adj_state_change_update); + hook_register(isis_hook_lsp_error, isis_snmp_lsp_error_update); + hook_register(isis_circuit_new_hook, isis_circuit_snmp_id_gen); + hook_register(isis_circuit_del_hook, isis_circuit_snmp_id_free); + + hook_register(frr_late_init, isis_snmp_init); + return 0; +} + +FRR_MODULE_SETUP( + .name = "isis_snmp", + .version = FRR_VERSION, + .description = "isis AgentX SNMP module", + .init = isis_snmp_module_init, +); diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c new file mode 100644 index 0000000..1197f8c --- /dev/null +++ b/isisd/isis_spf.c @@ -0,0 +1,3436 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_spf.c + * The SPT algorithm + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2017 Christian Franke + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "log.h" +#include "command.h" +#include "termtable.h" +#include "memory.h" +#include "prefix.h" +#include "filter.h" +#include "if.h" +#include "hash.h" +#include "table.h" +#include "spf_backoff.h" +#include "srcdest_table.h" +#include "vrf.h" +#include "lib/json.h" + +#include "isis_errors.h" +#include "isis_constants.h" +#include "isis_common.h" +#include "isis_flags.h" +#include "isisd.h" +#include "isis_misc.h" +#include "isis_adjacency.h" +#include "isis_circuit.h" +#include "isis_pdu.h" +#include "isis_lsp.h" +#include "isis_dynhn.h" +#include "isis_spf.h" +#include "isis_route.h" +#include "isis_csm.h" +#include "isis_mt.h" +#include "isis_tlvs.h" +#include "isis_flex_algo.h" +#include "isis_zebra.h" +#include "fabricd.h" +#include "isis_spf_private.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_SPFTREE, "ISIS SPFtree"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_RUN, "ISIS SPF Run Info"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_ADJ, "ISIS SPF Adjacency"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_VERTEX, "ISIS vertex"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_VERTEX_ADJ, "ISIS SPF Vertex Adjacency"); + +static void spf_adj_list_parse_lsp(struct isis_spftree *spftree, + struct list *adj_list, struct isis_lsp *lsp, + const uint8_t *pseudo_nodeid, + uint32_t pseudo_metric); + +/* + * supports the given af ? + */ +static bool speaks(uint8_t *protocols, uint8_t count, int family) +{ + for (uint8_t i = 0; i < count; i++) { + if (family == AF_INET && protocols[i] == NLPID_IP) + return true; + if (family == AF_INET6 && protocols[i] == NLPID_IPV6) + return true; + } + return false; +} + +struct isis_spf_run { + struct isis_area *area; + int level; +}; + +/* 7.2.7 */ +static void remove_excess_adjs(struct list *adjs) +{ + struct listnode *node, *excess = NULL; + struct isis_vertex_adj *vadj, *candidate = NULL; + int comp; + + for (ALL_LIST_ELEMENTS_RO(adjs, node, vadj)) { + struct isis_adjacency *adj, *candidate_adj; + + adj = vadj->sadj->adj; + assert(adj); + + if (excess == NULL) + excess = node; + candidate = listgetdata(excess); + candidate_adj = candidate->sadj->adj; + + if (candidate_adj->sys_type < adj->sys_type) { + excess = node; + continue; + } + if (candidate_adj->sys_type > adj->sys_type) + continue; + + comp = memcmp(candidate_adj->sysid, adj->sysid, + ISIS_SYS_ID_LEN); + if (comp > 0) { + excess = node; + continue; + } + if (comp < 0) + continue; + + if (candidate_adj->circuit->idx > adj->circuit->idx) { + excess = node; + continue; + } + + if (candidate_adj->circuit->idx < adj->circuit->idx) + continue; + + comp = memcmp(candidate_adj->snpa, adj->snpa, ETH_ALEN); + if (comp > 0) { + excess = node; + continue; + } + } + + list_delete_node(adjs, excess); + + return; +} + +const char *vtype2string(enum vertextype vtype) +{ + switch (vtype) { + case VTYPE_PSEUDO_IS: + return "pseudo_IS"; + case VTYPE_PSEUDO_TE_IS: + return "pseudo_TE-IS"; + case VTYPE_NONPSEUDO_IS: + return "IS"; + case VTYPE_NONPSEUDO_TE_IS: + return "TE-IS"; + case VTYPE_ES: + return "ES"; + case VTYPE_IPREACH_INTERNAL: + return "IP internal"; + case VTYPE_IPREACH_EXTERNAL: + return "IP external"; + case VTYPE_IPREACH_TE: + return "IP TE"; + case VTYPE_IP6REACH_INTERNAL: + return "IP6 internal"; + case VTYPE_IP6REACH_EXTERNAL: + return "IP6 external"; + default: + return "UNKNOWN"; + } + return NULL; /* Not reached */ +} + +const char *vid2string(const struct isis_vertex *vertex, char *buff, int size) +{ + if (VTYPE_IS(vertex->type) || VTYPE_ES(vertex->type)) { + const char *hostname = print_sys_hostname(vertex->N.id); + strlcpy(buff, hostname, size); + return buff; + } + + if (VTYPE_IP(vertex->type)) { + srcdest2str(&vertex->N.ip.p.dest, &vertex->N.ip.p.src, buff, + size); + return buff; + } + + return "UNKNOWN"; +} + +static bool prefix_sid_cmp(const void *value1, const void *value2) +{ + const struct isis_vertex *c1 = value1; + const struct isis_vertex *c2 = value2; + + if (CHECK_FLAG(c1->N.ip.sr.sid.flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL) + != CHECK_FLAG(c2->N.ip.sr.sid.flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) + return false; + + return c1->N.ip.sr.sid.value == c2->N.ip.sr.sid.value; +} + +static unsigned int prefix_sid_key_make(const void *value) +{ + const struct isis_vertex *vertex = value; + + return jhash_1word(vertex->N.ip.sr.sid.value, 0); +} + +struct isis_vertex *isis_spf_prefix_sid_lookup(struct isis_spftree *spftree, + struct isis_prefix_sid *psid) +{ + struct isis_vertex lookup = {}; + + lookup.N.ip.sr.sid = *psid; + return hash_lookup(spftree->prefix_sids, &lookup); +} + +void isis_vertex_adj_free(void *arg) +{ + struct isis_vertex_adj *vadj = arg; + + XFREE(MTYPE_ISIS_VERTEX_ADJ, vadj); +} + +static struct isis_vertex *isis_vertex_new(struct isis_spftree *spftree, + void *id, + enum vertextype vtype) +{ + struct isis_vertex *vertex; + + vertex = XCALLOC(MTYPE_ISIS_VERTEX, sizeof(struct isis_vertex)); + + isis_vertex_id_init(vertex, id, vtype); + + vertex->Adj_N = list_new(); + vertex->Adj_N->del = isis_vertex_adj_free; + vertex->parents = list_new(); + + if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) { + vertex->firsthops = hash_create(isis_vertex_queue_hash_key, + isis_vertex_queue_hash_cmp, + NULL); + } + + return vertex; +} + +void isis_vertex_del(struct isis_vertex *vertex) +{ + list_delete(&vertex->Adj_N); + list_delete(&vertex->parents); + hash_clean_and_free(&vertex->firsthops, NULL); + + memset(vertex, 0, sizeof(struct isis_vertex)); + XFREE(MTYPE_ISIS_VERTEX, vertex); +} + +struct isis_vertex_adj * +isis_vertex_adj_add(struct isis_spftree *spftree, struct isis_vertex *vertex, + struct list *vadj_list, struct isis_spf_adj *sadj, + struct isis_prefix_sid *psid, bool last_hop) +{ + struct isis_vertex_adj *vadj; + + vadj = XCALLOC(MTYPE_ISIS_VERTEX_ADJ, sizeof(*vadj)); + vadj->sadj = sadj; + if (spftree->area->srdb.enabled && psid) { + if (vertex->N.ip.sr.present + && vertex->N.ip.sr.sid.value != psid->value) + zlog_warn( + "ISIS-SPF: ignoring different Prefix-SID for route %pFX", + &vertex->N.ip.p.dest); + else { + vadj->sr.sid = *psid; + vadj->sr.label = sr_prefix_out_label( + spftree->lspdb, vertex->N.ip.p.dest.family, + psid, sadj->id, last_hop); + if (vadj->sr.label != MPLS_INVALID_LABEL) + vadj->sr.present = true; + } + } + listnode_add(vadj_list, vadj); + + return vadj; +} + +static void isis_vertex_adj_del(struct isis_vertex *vertex, + struct isis_adjacency *adj) +{ + struct isis_vertex_adj *vadj; + struct listnode *node, *nextnode; + + if (!vertex) + return; + + for (ALL_LIST_ELEMENTS(vertex->Adj_N, node, nextnode, vadj)) { + if (vadj->sadj->adj == adj) { + listnode_delete(vertex->Adj_N, vadj); + isis_vertex_adj_free(vadj); + } + } + return; +} + +bool isis_vertex_adj_exists(const struct isis_spftree *spftree, + const struct isis_vertex *vertex, + const struct isis_spf_adj *sadj) +{ + struct isis_vertex_adj *tmp; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, tmp)) { + if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) { + if (memcmp(sadj->id, tmp->sadj->id, sizeof(sadj->id)) + == 0) + return true; + } else { + if (sadj->adj == tmp->sadj->adj) + return true; + } + } + + return false; +} + +static void isis_spf_adj_free(void *arg) +{ + struct isis_spf_adj *sadj = arg; + + XFREE(MTYPE_ISIS_SPF_ADJ, sadj); +} + +static void _isis_spftree_init(struct isis_spftree *tree) +{ + isis_vertex_queue_init(&tree->tents, "IS-IS SPF tents", true); + isis_vertex_queue_init(&tree->paths, "IS-IS SPF paths", false); + tree->route_table = srcdest_table_init(); + tree->route_table->cleanup = isis_route_node_cleanup; + tree->route_table->info = isis_route_table_info_alloc(tree->algorithm); + tree->route_table_backup = srcdest_table_init(); + tree->route_table_backup->info = + isis_route_table_info_alloc(tree->algorithm); + tree->route_table_backup->cleanup = isis_route_node_cleanup; + tree->prefix_sids = hash_create(prefix_sid_key_make, prefix_sid_cmp, + "SR Prefix-SID Entries"); + tree->sadj_list = list_new(); + tree->sadj_list->del = isis_spf_adj_free; + isis_rlfa_list_init(tree); + tree->lfa.remote.pc_spftrees = list_new(); + tree->lfa.remote.pc_spftrees->del = (void (*)(void *))isis_spftree_del; + if (tree->type == SPF_TYPE_RLFA || tree->type == SPF_TYPE_TI_LFA) { + isis_spf_node_list_init(&tree->lfa.p_space); + isis_spf_node_list_init(&tree->lfa.q_space); + } +} + +struct isis_spftree * +isis_spftree_new(struct isis_area *area, struct lspdb_head *lspdb, + const uint8_t *sysid, int level, enum spf_tree_id tree_id, + enum spf_type type, uint8_t flags, uint8_t algorithm) +{ + struct isis_spftree *tree; + + tree = XCALLOC(MTYPE_ISIS_SPFTREE, sizeof(struct isis_spftree)); + + tree->area = area; + tree->lspdb = lspdb; + tree->last_run_timestamp = 0; + tree->last_run_monotime = 0; + tree->last_run_duration = 0; + tree->runcount = 0; + tree->type = type; + memcpy(tree->sysid, sysid, ISIS_SYS_ID_LEN); + tree->level = level; + tree->tree_id = tree_id; + tree->family = (tree->tree_id == SPFTREE_IPV4) ? AF_INET : AF_INET6; + tree->flags = flags; + tree->algorithm = algorithm; + + _isis_spftree_init(tree); + + return tree; +} + +static void _isis_spftree_del(struct isis_spftree *spftree) +{ + void *info, *backup_info; + + hash_clean_and_free(&spftree->prefix_sids, NULL); + isis_zebra_rlfa_unregister_all(spftree); + isis_rlfa_list_clear(spftree); + list_delete(&spftree->lfa.remote.pc_spftrees); + if (spftree->type == SPF_TYPE_RLFA + || spftree->type == SPF_TYPE_TI_LFA) { + isis_spf_node_list_clear(&spftree->lfa.q_space); + isis_spf_node_list_clear(&spftree->lfa.p_space); + } + isis_spf_node_list_clear(&spftree->adj_nodes); + list_delete(&spftree->sadj_list); + isis_vertex_queue_free(&spftree->tents); + isis_vertex_queue_free(&spftree->paths); + info = spftree->route_table->info; + backup_info = spftree->route_table_backup->info; + route_table_finish(spftree->route_table); + route_table_finish(spftree->route_table_backup); + isis_route_table_info_free(info); + isis_route_table_info_free(backup_info); +} + +void isis_spftree_del(struct isis_spftree *spftree) +{ + _isis_spftree_del(spftree); + + spftree->route_table = NULL; + + XFREE(MTYPE_ISIS_SPFTREE, spftree); + return; +} + +#ifndef FABRICD +static void isis_spftree_clear(struct isis_spftree *spftree) +{ + _isis_spftree_del(spftree); + _isis_spftree_init(spftree); +} +#endif /* ifndef FABRICD */ + +static void isis_spftree_adj_del(struct isis_spftree *spftree, + struct isis_adjacency *adj) +{ + struct listnode *node; + struct isis_vertex *v; + if (!adj) + return; + assert(!isis_vertex_queue_count(&spftree->tents)); + for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, v)) + isis_vertex_adj_del(v, adj); + return; +} + +void spftree_area_init(struct isis_area *area) +{ + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(area->is_type & level)) + continue; + if (area->spftree[tree][level - 1]) + continue; + + area->spftree[tree][level - 1] = isis_spftree_new( + area, &area->lspdb[level - 1], + area->isis->sysid, level, tree, + SPF_TYPE_FORWARD, 0, SR_ALGORITHM_SPF); + } + } +} + +void spftree_area_del(struct isis_area *area) +{ + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(area->is_type & level)) + continue; + if (!area->spftree[tree][level - 1]) + continue; + + isis_spftree_del(area->spftree[tree][level - 1]); + } + } +} + +static int spf_adj_state_change(struct isis_adjacency *adj) +{ + struct isis_area *area = adj->circuit->area; + + if (adj->adj_state == ISIS_ADJ_UP) + return 0; + + /* Remove adjacency from all SPF trees. */ + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(area->is_type & level)) + continue; + if (!area->spftree[tree][level - 1]) + continue; + isis_spftree_adj_del(area->spftree[tree][level - 1], + adj); + } + } + + if (fabricd_spftree(area) != NULL) + isis_spftree_adj_del(fabricd_spftree(area), adj); + + return 0; +} + +/* + * Find the system LSP: returns the LSP in our LSP database + * associated with the given system ID. + */ +struct isis_lsp *isis_root_system_lsp(struct lspdb_head *lspdb, + const uint8_t *sysid) +{ + struct isis_lsp *lsp; + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + + memcpy(lspid, sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(lspid) = 0; + LSP_FRAGMENT(lspid) = 0; + lsp = lsp_search(lspdb, lspid); + if (lsp && lsp->hdr.rem_lifetime != 0) + return lsp; + return NULL; +} + +/* + * Add this IS to the root of SPT + */ +static struct isis_vertex *isis_spf_add_root(struct isis_spftree *spftree) +{ + struct isis_vertex *vertex; +#ifdef EXTREME_DEBUG + char buff[VID2STR_BUFFER]; +#endif /* EXTREME_DEBUG */ + + vertex = isis_vertex_new(spftree, spftree->sysid, + spftree->area->oldmetric + ? VTYPE_NONPSEUDO_IS + : VTYPE_NONPSEUDO_TE_IS); + isis_vertex_queue_append(&spftree->paths, vertex); + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu added this IS %s %s depth %d dist %d to PATHS", + spftree->algorithm, vtype2string(vertex->type), + vid2string(vertex, buff, sizeof(buff)), vertex->depth, + vertex->d_N); +#endif /* EXTREME_DEBUG */ + + return vertex; +} + +static void vertex_add_parent_firsthop(struct hash_bucket *bucket, void *arg) +{ + struct isis_vertex *vertex = arg; + struct isis_vertex *hop = bucket->data; + + (void)hash_get(vertex->firsthops, hop, hash_alloc_intern); +} + +static void vertex_update_firsthops(struct isis_vertex *vertex, + struct isis_vertex *parent) +{ + if (vertex->d_N <= 2) + (void)hash_get(vertex->firsthops, vertex, hash_alloc_intern); + + if (vertex->d_N < 2 || !parent) + return; + + hash_iterate(parent->firsthops, vertex_add_parent_firsthop, vertex); +} + +/* + * Add a vertex to TENT sorted by cost and by vertextype on tie break situation + */ +static struct isis_vertex * +isis_spf_add2tent(struct isis_spftree *spftree, enum vertextype vtype, void *id, + uint32_t cost, int depth, struct isis_spf_adj *sadj, + struct isis_prefix_sid *psid, struct isis_vertex *parent) +{ + struct isis_vertex *vertex; + struct listnode *node; + bool last_hop; + char buff[VID2STR_BUFFER]; + + vertex = isis_find_vertex(&spftree->paths, id, vtype); + if (vertex != NULL) { + zlog_err( + "%s: vertex %s of type %s already in PATH; check for sysId collisions with established neighbors", + __func__, vid2string(vertex, buff, sizeof(buff)), + vtype2string(vertex->type)); + return NULL; + } + vertex = isis_find_vertex(&spftree->tents, id, vtype); + if (vertex != NULL) { + zlog_err( + "%s: vertex %s of type %s already in TENT; check for sysId collisions with established neighbors", + __func__, vid2string(vertex, buff, sizeof(buff)), + vtype2string(vertex->type)); + return NULL; + } + + vertex = isis_vertex_new(spftree, id, vtype); + vertex->d_N = cost; + vertex->depth = depth; + if (VTYPE_IP(vtype) && spftree->area->srdb.enabled && psid) { + struct isis_area *area = spftree->area; + struct isis_vertex *vertex_psid; + + /* + * Check if the Prefix-SID is already in use by another prefix. + */ + vertex_psid = isis_spf_prefix_sid_lookup(spftree, psid); + if (vertex_psid + && !prefix_same(&vertex_psid->N.ip.p.dest, + &vertex->N.ip.p.dest)) { + flog_warn( + EC_ISIS_SID_COLLISION, + "ISIS-Sr (%s): collision detected, prefixes %pFX and %pFX share the same SID %s (%u)", + area->area_tag, &vertex->N.ip.p.dest, + &vertex_psid->N.ip.p.dest, + CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_VALUE) + ? "label" + : "index", + psid->value); + psid = NULL; + } else { + bool local; + + local = (vertex->depth == 1); + vertex->N.ip.sr.sid = *psid; + vertex->N.ip.sr.label = + sr_prefix_in_label(area, psid, local); + vertex->N.ip.sr.algorithm = psid->algorithm; + + if (vertex->N.ip.sr.label != MPLS_INVALID_LABEL) + vertex->N.ip.sr.present = true; + +#ifndef FABRICD + if (flex_algo_id_valid(spftree->algorithm) && + !isis_flex_algo_elected_supported( + spftree->algorithm, spftree->area)) { + vertex->N.ip.sr.present = false; + vertex->N.ip.sr.label = MPLS_INVALID_LABEL; + } +#endif /* ifndef FABRICD */ + + (void)hash_get(spftree->prefix_sids, vertex, + hash_alloc_intern); + } + } + + if (parent) { + listnode_add(vertex->parents, parent); + } + + if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) + vertex_update_firsthops(vertex, parent); + + last_hop = (vertex->depth == 2); + if (parent && parent->Adj_N && listcount(parent->Adj_N) > 0) { + struct isis_vertex_adj *parent_vadj; + + for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, parent_vadj)) + isis_vertex_adj_add(spftree, vertex, vertex->Adj_N, + parent_vadj->sadj, psid, last_hop); + } else if (sadj) { + isis_vertex_adj_add(spftree, vertex, vertex->Adj_N, sadj, psid, + last_hop); + } + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu add to TENT %s %s %s depth %d dist %d adjcount %d", + spftree->algorithm, print_sys_hostname(vertex->N.id), + vtype2string(vertex->type), + vid2string(vertex, buff, sizeof(buff)), vertex->depth, + vertex->d_N, listcount(vertex->Adj_N)); +#endif /* EXTREME_DEBUG */ + + isis_vertex_queue_insert(&spftree->tents, vertex); + return vertex; +} + +static void isis_spf_add_local(struct isis_spftree *spftree, + enum vertextype vtype, void *id, + struct isis_spf_adj *sadj, uint32_t cost, + struct isis_prefix_sid *psid, + struct isis_vertex *parent) +{ + struct isis_vertex *vertex; + + vertex = isis_find_vertex(&spftree->tents, id, vtype); + + if (vertex) { + /* C.2.5 c) */ + if (vertex->d_N == cost) { + if (sadj) { + bool last_hop = (vertex->depth == 2); + + isis_vertex_adj_add(spftree, vertex, + vertex->Adj_N, sadj, psid, + last_hop); + } + /* d) */ + if (!CHECK_FLAG(spftree->flags, + F_SPFTREE_NO_ADJACENCIES) + && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS) + remove_excess_adjs(vertex->Adj_N); + if (parent && (listnode_lookup(vertex->parents, parent) + == NULL)) + listnode_add(vertex->parents, parent); + return; + } else if (vertex->d_N < cost) { + /* e) do nothing */ + return; + } else { /* vertex->d_N > cost */ + /* f) */ + isis_vertex_queue_delete(&spftree->tents, vertex); + hash_release(spftree->prefix_sids, vertex); + isis_vertex_del(vertex); + } + } + + isis_spf_add2tent(spftree, vtype, id, cost, 1, sadj, psid, parent); + return; +} + +static void process_N(struct isis_spftree *spftree, enum vertextype vtype, + void *id, uint32_t dist, uint16_t depth, + struct isis_prefix_sid *psid, struct isis_vertex *parent) +{ + struct isis_vertex *vertex; +#ifdef EXTREME_DEBUG + char buff[VID2STR_BUFFER]; +#endif + + assert(spftree && parent); + + if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC) + && !VTYPE_IS(vtype)) + return; + + struct prefix_pair p; + if (vtype >= VTYPE_IPREACH_INTERNAL) { + memcpy(&p, id, sizeof(p)); + apply_mask(&p.dest); + apply_mask(&p.src); + id = &p; + } + + /* RFC3787 section 5.1 */ + if (spftree->area->newmetric == 1) { + if (dist > MAX_WIDE_PATH_METRIC) + return; + } + /* C.2.6 b) */ + else if (spftree->area->oldmetric == 1) { + if (dist > MAX_NARROW_PATH_METRIC) + return; + } + + /* c) */ + vertex = isis_find_vertex(&spftree->paths, id, vtype); + if (vertex) { +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu process_N %s %s %s dist %d already found from PATH", + spftree->algorithm, + print_sys_hostname(vertex->N.id), + vtype2string(vtype), + vid2string(vertex, buff, sizeof(buff)), dist); +#endif /* EXTREME_DEBUG */ + assert(dist >= vertex->d_N); + return; + } + + vertex = isis_find_vertex(&spftree->tents, id, vtype); + /* d) */ + if (vertex) { +/* 1) */ +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu process_N %s %s %s dist %d parent %s adjcount %d", + spftree->algorithm, + print_sys_hostname(vertex->N.id), + vtype2string(vtype), + vid2string(vertex, buff, sizeof(buff)), dist, + (parent ? print_sys_hostname(parent->N.id) + : "null"), + (parent ? listcount(parent->Adj_N) : 0)); +#endif /* EXTREME_DEBUG */ + if (vertex->d_N == dist) { + struct listnode *node; + struct isis_vertex_adj *parent_vadj; + for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, + parent_vadj)) + if (!isis_vertex_adj_exists( + spftree, vertex, + parent_vadj->sadj)) { + bool last_hop = (vertex->depth == 2); + + isis_vertex_adj_add(spftree, vertex, + vertex->Adj_N, + parent_vadj->sadj, + psid, last_hop); + } + if (CHECK_FLAG(spftree->flags, + F_SPFTREE_HOPCOUNT_METRIC)) + vertex_update_firsthops(vertex, parent); + /* 2) */ + if (!CHECK_FLAG(spftree->flags, + F_SPFTREE_NO_ADJACENCIES) + && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS) + remove_excess_adjs(vertex->Adj_N); + if (listnode_lookup(vertex->parents, parent) == NULL) + listnode_add(vertex->parents, parent); + return; + } else if (vertex->d_N < dist) { + return; + /* 4) */ + } else { + isis_vertex_queue_delete(&spftree->tents, vertex); + hash_release(spftree->prefix_sids, vertex); + isis_vertex_del(vertex); + } + } + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu process_N add2tent %s %s dist %d parent %s", + spftree->algorithm, print_sys_hostname(id), + vtype2string(vtype), dist, + (parent ? print_sys_hostname(parent->N.id) : "null")); +#endif /* EXTREME_DEBUG */ + + isis_spf_add2tent(spftree, vtype, id, dist, depth, NULL, psid, parent); + return; +} + +/* + * C.2.6 Step 1 + */ +static int isis_spf_process_lsp(struct isis_spftree *spftree, + struct isis_lsp *lsp, uint32_t cost, + uint16_t depth, uint8_t *root_sysid, + struct isis_vertex *parent) +{ + bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id); + struct listnode *fragnode = NULL; + uint32_t dist; + enum vertextype vtype; + static const uint8_t null_sysid[ISIS_SYS_ID_LEN]; + struct isis_mt_router_info *mt_router_info = NULL; + struct prefix_pair ip_info; + bool has_valid_psid; + bool loc_is_in_ipv6_reach = false; + + if (isis_lfa_excise_node_check(spftree, lsp->hdr.lsp_id)) { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: excising node %s", + print_sys_hostname(lsp->hdr.lsp_id)); + return ISIS_OK; + } + + if (!lsp->tlvs) + return ISIS_OK; + + if (spftree->mtid != ISIS_MT_IPV4_UNICAST) + mt_router_info = isis_tlvs_lookup_mt_router_info(lsp->tlvs, + spftree->mtid); + + if (!pseudo_lsp && (spftree->mtid == ISIS_MT_IPV4_UNICAST + && !speaks(lsp->tlvs->protocols_supported.protocols, + lsp->tlvs->protocols_supported.count, + spftree->family)) + && !mt_router_info) + return ISIS_OK; + + /* RFC3787 section 4 SHOULD ignore overload bit in pseudo LSPs */ + bool no_overload = (pseudo_lsp + || (spftree->mtid == ISIS_MT_IPV4_UNICAST + && !ISIS_MASK_LSP_OL_BIT(lsp->hdr.lsp_bits)) + || (mt_router_info && !mt_router_info->overload)); + +lspfragloop: + if (!lsp->tlvs) + return ISIS_OK; + + if (lsp->hdr.seqno == 0) { + zlog_warn("%s: lsp with 0 seq_num - ignore", __func__); + return ISIS_WARNING; + } + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug("ISIS-SPF: A:%hhu process_lsp %s", + spftree->algorithm, + print_sys_hostname(lsp->hdr.lsp_id)); +#endif /* EXTREME_DEBUG */ + + if (no_overload) { + if ((pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) + && spftree->area->oldmetric) { + struct isis_oldstyle_reach *r; + for (r = (struct isis_oldstyle_reach *) + lsp->tlvs->oldstyle_reach.head; + r; r = r->next) { + if (fabricd) + continue; + + /* C.2.6 a) */ + /* Two way connectivity */ + if (!LSP_PSEUDO_ID(r->id) + && !memcmp(r->id, root_sysid, + ISIS_SYS_ID_LEN)) + continue; + if (!pseudo_lsp + && !memcmp(r->id, null_sysid, + ISIS_SYS_ID_LEN)) + continue; + dist = cost + r->metric; + process_N(spftree, + LSP_PSEUDO_ID(r->id) + ? VTYPE_PSEUDO_IS + : VTYPE_NONPSEUDO_IS, + (void *)r->id, dist, depth + 1, NULL, + parent); + } + } + + if (spftree->area->newmetric) { + struct isis_item_list *te_neighs = NULL; + if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) + te_neighs = &lsp->tlvs->extended_reach; + else + te_neighs = isis_lookup_mt_items( + &lsp->tlvs->mt_reach, spftree->mtid); + + struct isis_extended_reach *er; + for (er = te_neighs ? (struct isis_extended_reach *) + te_neighs->head + : NULL; + er; er = er->next) { + /* C.2.6 a) */ + /* Two way connectivity */ + if (!LSP_PSEUDO_ID(er->id) + && !memcmp(er->id, root_sysid, + ISIS_SYS_ID_LEN)) + continue; + if (!pseudo_lsp + && !memcmp(er->id, null_sysid, + ISIS_SYS_ID_LEN)) + continue; +#ifndef FABRICD + + if (flex_algo_id_valid(spftree->algorithm) && + (!sr_algorithm_participated( + lsp, spftree->algorithm) || + isis_flex_algo_constraint_drop(spftree, + lsp, er))) + continue; +#endif /* ifndef FABRICD */ + + dist = cost + + (CHECK_FLAG(spftree->flags, + F_SPFTREE_HOPCOUNT_METRIC) + ? 1 + : er->metric); + process_N(spftree, + LSP_PSEUDO_ID(er->id) + ? VTYPE_PSEUDO_TE_IS + : VTYPE_NONPSEUDO_TE_IS, + (void *)er->id, dist, depth + 1, NULL, + parent); + } + } + } + + if (!fabricd && !pseudo_lsp && spftree->family == AF_INET + && spftree->mtid == ISIS_MT_IPV4_UNICAST + && spftree->area->oldmetric) { + struct isis_item_list *reachs[] = { + &lsp->tlvs->oldstyle_ip_reach, + &lsp->tlvs->oldstyle_ip_reach_ext}; + + for (unsigned int i = 0; i < array_size(reachs); i++) { + vtype = i ? VTYPE_IPREACH_EXTERNAL + : VTYPE_IPREACH_INTERNAL; + + memset(&ip_info, 0, sizeof(ip_info)); + ip_info.dest.family = AF_INET; + + struct isis_oldstyle_ip_reach *r; + for (r = (struct isis_oldstyle_ip_reach *)reachs[i] + ->head; + r; r = r->next) { + dist = cost + r->metric; + ip_info.dest.u.prefix4 = r->prefix.prefix; + ip_info.dest.prefixlen = r->prefix.prefixlen; + process_N(spftree, vtype, &ip_info, + dist, depth + 1, NULL, parent); + } + } + } + + /* we can skip all the rest if we're using metric style narrow */ + if (!spftree->area->newmetric) + goto end; + + if (!pseudo_lsp && spftree->family == AF_INET) { + struct isis_item_list *ipv4_reachs; + if (spftree->mtid == ISIS_MT_IPV4_UNICAST) + ipv4_reachs = &lsp->tlvs->extended_ip_reach; + else + ipv4_reachs = isis_lookup_mt_items( + &lsp->tlvs->mt_ip_reach, spftree->mtid); + + memset(&ip_info, 0, sizeof(ip_info)); + ip_info.dest.family = AF_INET; + + struct isis_extended_ip_reach *r; + for (r = ipv4_reachs + ? (struct isis_extended_ip_reach *) + ipv4_reachs->head + : NULL; + r; r = r->next) { + dist = cost + r->metric; + ip_info.dest.u.prefix4 = r->prefix.prefix; + ip_info.dest.prefixlen = r->prefix.prefixlen; + + /* Parse list of Prefix-SID subTLVs if SR is enabled */ + has_valid_psid = false; + if (spftree->area->srdb.enabled && r->subtlvs) { + for (struct isis_item *i = + r->subtlvs->prefix_sids.head; + i; i = i->next) { + struct isis_prefix_sid *psid = + (struct isis_prefix_sid *)i; + + if (psid->algorithm != + spftree->algorithm) + continue; + +#ifndef FABRICD + if (flex_algo_id_valid( + spftree->algorithm) && + (!sr_algorithm_participated( + lsp, spftree->algorithm) || + !isis_flex_algo_elected_supported( + spftree->algorithm, + spftree->area))) + continue; +#endif /* ifndef FABRICD */ + + has_valid_psid = true; + process_N(spftree, VTYPE_IPREACH_TE, + &ip_info, dist, depth + 1, + psid, parent); + /* + * Stop the Prefix-SID iteration since + * we only support the SPF algorithm for + * now. + */ + break; + } + } + if (!has_valid_psid) + process_N(spftree, VTYPE_IPREACH_TE, &ip_info, + dist, depth + 1, NULL, parent); + } + } + + if (!pseudo_lsp && spftree->family == AF_INET6) { + struct isis_item_list *ipv6_reachs; + if (spftree->mtid == ISIS_MT_IPV4_UNICAST) + ipv6_reachs = &lsp->tlvs->ipv6_reach; + else + ipv6_reachs = isis_lookup_mt_items( + &lsp->tlvs->mt_ipv6_reach, spftree->mtid); + + struct isis_ipv6_reach *r; + for (r = ipv6_reachs + ? (struct isis_ipv6_reach *)ipv6_reachs->head + : NULL; + r; r = r->next) { + dist = cost + r->metric; + vtype = r->external ? VTYPE_IP6REACH_EXTERNAL + : VTYPE_IP6REACH_INTERNAL; + memset(&ip_info, 0, sizeof(ip_info)); + ip_info.dest.family = AF_INET6; + ip_info.dest.u.prefix6 = r->prefix.prefix; + ip_info.dest.prefixlen = r->prefix.prefixlen; + + if (spftree->area->srdb.enabled && r->subtlvs && + r->subtlvs->source_prefix && + r->subtlvs->source_prefix->prefixlen) { + if (spftree->tree_id != SPFTREE_DSTSRC) { + char buff[VID2STR_BUFFER]; + zlog_warn("Ignoring dest-src route %s in non dest-src topology", + srcdest2str( + &ip_info.dest, + r->subtlvs->source_prefix, + buff, sizeof(buff) + ) + ); + continue; + } + ip_info.src = *r->subtlvs->source_prefix; + } + + /* Parse list of Prefix-SID subTLVs */ + has_valid_psid = false; + if (r->subtlvs) { + for (struct isis_item *i = + r->subtlvs->prefix_sids.head; + i; i = i->next) { + struct isis_prefix_sid *psid = + (struct isis_prefix_sid *)i; + + if (psid->algorithm != + spftree->algorithm) + continue; + +#ifndef FABRICD + if (flex_algo_id_valid( + spftree->algorithm) && + (!sr_algorithm_participated( + lsp, spftree->algorithm) || + !isis_flex_algo_elected_supported( + spftree->algorithm, + spftree->area))) + continue; +#endif /* ifndef FABRICD */ + + has_valid_psid = true; + process_N(spftree, vtype, &ip_info, + dist, depth + 1, psid, + parent); + /* + * Stop the Prefix-SID iteration since + * we only support the SPF algorithm for + * now. + */ + break; + } + } + if (!has_valid_psid) + process_N(spftree, vtype, &ip_info, dist, + depth + 1, NULL, parent); + } + + /* Process SRv6 Locator TLVs */ + + struct isis_item_list *srv6_locators = isis_lookup_mt_items( + &lsp->tlvs->srv6_locator, spftree->mtid); + + struct isis_srv6_locator_tlv *loc; + for (loc = srv6_locators ? (struct isis_srv6_locator_tlv *) + srv6_locators->head + : NULL; + loc; loc = loc->next) { + + if (loc->algorithm != SR_ALGORITHM_SPF) + continue; + + dist = cost + loc->metric; + vtype = VTYPE_IP6REACH_INTERNAL; + memset(&ip_info, 0, sizeof(ip_info)); + ip_info.dest.family = AF_INET6; + ip_info.dest.u.prefix6 = loc->prefix.prefix; + ip_info.dest.prefixlen = loc->prefix.prefixlen; + + /* An SRv6 Locator can be received in both a Prefix + Reachability TLV and an SRv6 Locator TLV (as per RFC + 9352 section #5). We go through the Prefix Reachability + TLVs and check if the SRv6 Locator is present in some of + them. If we find the SRv6 Locator in some Prefix + Reachbility TLV then it means that we have already + processed it before and we can skip it. */ + for (r = ipv6_reachs ? (struct isis_ipv6_reach *) + ipv6_reachs->head + : NULL; + r; r = r->next) { + if (prefix_same((struct prefix *)&r->prefix, + (struct prefix *)&loc->prefix)) + loc_is_in_ipv6_reach = true; + } + + /* SRv6 locator not present in Prefix Reachability TLV, + * let's process it */ + if (!loc_is_in_ipv6_reach) + process_N(spftree, vtype, &ip_info, dist, + depth + 1, NULL, parent); + } + } + +end: + + /* if attach bit set in LSP, attached-bit receive ignore is + * not configured, we are a level-1 area and we have no other + * level-2 | level1-2 areas then add a default route toward + * this neighbor + */ + if ((lsp->hdr.lsp_bits & LSPBIT_ATT) == LSPBIT_ATT + && !spftree->area->attached_bit_rcv_ignore + && (spftree->area->is_type & IS_LEVEL_1) + && !isis_level2_adj_up(spftree->area)) { + struct prefix_pair ip_info = { {0} }; + if (IS_DEBUG_RTE_EVENTS) + zlog_debug("ISIS-Spf (%pLS): add default %s route", + lsp->hdr.lsp_id, + spftree->family == AF_INET ? "ipv4" + : "ipv6"); + + if (spftree->family == AF_INET) { + ip_info.dest.family = AF_INET; + vtype = VTYPE_IPREACH_INTERNAL; + } else { + ip_info.dest.family = AF_INET6; + vtype = VTYPE_IP6REACH_INTERNAL; + } + process_N(spftree, vtype, &ip_info, cost, depth + 1, NULL, + parent); + } + + if (fragnode == NULL) + fragnode = listhead(lsp->lspu.frags); + else + fragnode = listnextnode(fragnode); + + if (fragnode) { + lsp = listgetdata(fragnode); + goto lspfragloop; + } + + return ISIS_OK; +} + +static struct isis_adjacency *adj_find(struct list *adj_list, const uint8_t *id, + int level, uint16_t mtid, int family) +{ + struct isis_adjacency *adj; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) { + if (!(adj->level & level)) + continue; + if (memcmp(adj->sysid, id, ISIS_SYS_ID_LEN) != 0) + continue; + if (adj->adj_state != ISIS_ADJ_UP) + continue; + if (!adj_has_mt(adj, mtid)) + continue; + if (mtid == ISIS_MT_IPV4_UNICAST + && !speaks(adj->nlpids.nlpids, adj->nlpids.count, family)) + continue; + return adj; + } + + return NULL; +} + +struct spf_preload_tent_ip_reach_args { + struct isis_spftree *spftree; + struct isis_vertex *parent; +}; + +static int isis_spf_preload_tent_ip_reach_cb(const struct prefix *prefix, + uint32_t metric, bool external, + struct isis_subtlvs *subtlvs, + void *arg) +{ + struct spf_preload_tent_ip_reach_args *args = arg; + struct isis_spftree *spftree = args->spftree; + struct isis_vertex *parent = args->parent; + struct prefix_pair ip_info; + enum vertextype vtype; + bool has_valid_psid = false; + + if (external) + return LSP_ITER_CONTINUE; + + assert(spftree->family == prefix->family); + memset(&ip_info, 0, sizeof(ip_info)); + prefix_copy(&ip_info.dest, prefix); + apply_mask(&ip_info.dest); + + if (prefix->family == AF_INET) + vtype = VTYPE_IPREACH_INTERNAL; + else + vtype = VTYPE_IP6REACH_INTERNAL; + + /* Parse list of Prefix-SID subTLVs if SR is enabled */ + if (spftree->area->srdb.enabled && subtlvs) { + for (struct isis_item *i = subtlvs->prefix_sids.head; i; + i = i->next) { + struct isis_prefix_sid *psid = + (struct isis_prefix_sid *)i; + + if (psid->algorithm != spftree->algorithm) + continue; + + has_valid_psid = true; + isis_spf_add_local(spftree, vtype, &ip_info, NULL, 0, + psid, parent); + + /* + * Stop the Prefix-SID iteration since we only support + * the SPF algorithm for now. + */ + break; + } + } + if (!has_valid_psid) + isis_spf_add_local(spftree, vtype, &ip_info, NULL, 0, NULL, + parent); + + return LSP_ITER_CONTINUE; +} + +static void isis_spf_preload_tent(struct isis_spftree *spftree, + uint8_t *root_sysid, + struct isis_lsp *root_lsp, + struct isis_vertex *parent) +{ + struct spf_preload_tent_ip_reach_args ip_reach_args; + struct isis_spf_adj *sadj; + struct listnode *node; + + if (!CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) { + ip_reach_args.spftree = spftree; + ip_reach_args.parent = parent; + isis_lsp_iterate_ip_reach( + root_lsp, spftree->family, spftree->mtid, + isis_spf_preload_tent_ip_reach_cb, &ip_reach_args); + } + + /* Iterate over adjacencies. */ + for (ALL_LIST_ELEMENTS_RO(spftree->sadj_list, node, sadj)) { + const uint8_t *adj_id; + uint32_t metric; + + if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)) + adj_id = sadj->lan.desig_is_id; + else + adj_id = sadj->id; + + if (isis_lfa_excise_adj_check(spftree, adj_id)) { + if (IS_DEBUG_LFA) + zlog_debug("ISIS-SPF: excising adjacency %pPN", + sadj->id); + continue; + } + + metric = CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC) + ? 1 + : sadj->metric; + if (!LSP_PSEUDO_ID(sadj->id)) { + isis_spf_add_local(spftree, + CHECK_FLAG(sadj->flags, + F_ISIS_SPF_ADJ_OLDMETRIC) + ? VTYPE_NONPSEUDO_IS + : VTYPE_NONPSEUDO_TE_IS, + sadj->id, sadj, metric, NULL, + parent); + } else if (sadj->lsp) { + isis_spf_process_lsp(spftree, sadj->lsp, metric, 0, + spftree->sysid, parent); + } + } +} + +struct spf_adj_find_reverse_metric_args { + const uint8_t *id_self; + uint32_t reverse_metric; +}; + +static int spf_adj_find_reverse_metric_cb(const uint8_t *id, uint32_t metric, + bool oldmetric, + struct isis_ext_subtlvs *subtlvs, + void *arg) +{ + struct spf_adj_find_reverse_metric_args *args = arg; + + if (memcmp(id, args->id_self, ISIS_SYS_ID_LEN)) + return LSP_ITER_CONTINUE; + + args->reverse_metric = metric; + + return LSP_ITER_STOP; +} + +/* + * Change all SPF adjacencies to use the link cost in the direction from the + * next hop back towards root in place of the link cost in the direction away + * from root towards the next hop. + */ +static void spf_adj_get_reverse_metrics(struct isis_spftree *spftree) +{ + struct isis_spf_adj *sadj; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(spftree->sadj_list, node, nnode, sadj)) { + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + struct isis_lsp *lsp_adj; + const uint8_t *id_self; + struct spf_adj_find_reverse_metric_args args; + + /* Skip pseudonodes. */ + if (LSP_PSEUDO_ID(sadj->id)) + continue; + + /* Find LSP of the corresponding adjacency. */ + memcpy(lspid, sadj->id, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(lspid) = 0; + LSP_FRAGMENT(lspid) = 0; + lsp_adj = lsp_search(spftree->lspdb, lspid); + if (lsp_adj == NULL || lsp_adj->hdr.rem_lifetime == 0) { + /* Delete one-way adjacency. */ + listnode_delete(spftree->sadj_list, sadj); + isis_spf_adj_free(sadj); + continue; + } + + /* Find root node in the LSP of the adjacent router. */ + if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST)) + id_self = sadj->lan.desig_is_id; + else + id_self = spftree->sysid; + args.id_self = id_self; + args.reverse_metric = UINT32_MAX; + isis_lsp_iterate_is_reach(lsp_adj, spftree->mtid, + spf_adj_find_reverse_metric_cb, + &args); + if (args.reverse_metric == UINT32_MAX) { + /* Delete one-way adjacency. */ + listnode_delete(spftree->sadj_list, sadj); + isis_spf_adj_free(sadj); + continue; + } + sadj->metric = args.reverse_metric; + } +} + +static void spf_adj_list_parse_tlv(struct isis_spftree *spftree, + struct list *adj_list, const uint8_t *id, + const uint8_t *desig_is_id, + uint32_t pseudo_metric, uint32_t metric, + bool oldmetric, + struct isis_ext_subtlvs *subtlvs) +{ + struct isis_spf_adj *sadj; + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + struct isis_lsp *lsp; + uint8_t flags = 0; + + /* Skip self in the pseudonode. */ + if (desig_is_id && !memcmp(id, spftree->sysid, ISIS_SYS_ID_LEN)) + return; + + /* Find LSP from the adjacency. */ + memcpy(lspid, id, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(lspid) = 0; + lsp = lsp_search(spftree->lspdb, lspid); + if (lsp == NULL || lsp->hdr.rem_lifetime == 0) { + zlog_warn("ISIS-SPF: No LSP found from root to L%d %pLS", + spftree->level, lspid); + return; + } + + sadj = XCALLOC(MTYPE_ISIS_SPF_ADJ, sizeof(*sadj)); + memcpy(sadj->id, id, sizeof(sadj->id)); + if (desig_is_id) { + memcpy(sadj->lan.desig_is_id, desig_is_id, + sizeof(sadj->lan.desig_is_id)); + SET_FLAG(flags, F_ISIS_SPF_ADJ_BROADCAST); + sadj->metric = pseudo_metric; + } else + sadj->metric = metric; + if (oldmetric) + SET_FLAG(flags, F_ISIS_SPF_ADJ_OLDMETRIC); + sadj->lsp = lsp; + sadj->subtlvs = subtlvs; + sadj->flags = flags; + + if ((oldmetric && metric == ISIS_NARROW_METRIC_INFINITY) + || (!oldmetric && metric == ISIS_WIDE_METRIC_INFINITY)) + SET_FLAG(flags, F_ISIS_SPF_ADJ_METRIC_INFINITY); + + /* Set real adjacency. */ + if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES) + && !LSP_PSEUDO_ID(id)) { + struct isis_adjacency *adj; + + adj = adj_find(adj_list, id, spftree->level, spftree->mtid, + spftree->family); + if (!adj) { + XFREE(MTYPE_ISIS_SPF_ADJ, sadj); + return; + } + + listnode_delete(adj_list, adj); + sadj->adj = adj; + } + + /* Add adjacency to the list. */ + listnode_add(spftree->sadj_list, sadj); + + if (!LSP_PSEUDO_ID(id)) { + struct isis_spf_node *node; + + node = isis_spf_node_find(&spftree->adj_nodes, id); + if (!node) + node = isis_spf_node_new(&spftree->adj_nodes, id); + if (node->best_metric == 0 || sadj->metric < node->best_metric) + node->best_metric = sadj->metric; + listnode_add(node->adjacencies, sadj); + } + + /* Parse pseudonode LSP too. */ + if (LSP_PSEUDO_ID(id)) + spf_adj_list_parse_lsp(spftree, adj_list, lsp, id, metric); +} + +static void spf_adj_list_parse_lsp(struct isis_spftree *spftree, + struct list *adj_list, struct isis_lsp *lsp, + const uint8_t *pseudo_nodeid, + uint32_t pseudo_metric) +{ + bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id); + struct isis_lsp *frag; + struct listnode *node; + struct isis_item *head; + struct isis_item_list *te_neighs; + + if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0) + return; + + /* Parse LSP. */ + if (lsp->tlvs) { + if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) { + head = lsp->tlvs->oldstyle_reach.head; + for (struct isis_oldstyle_reach *reach = + (struct isis_oldstyle_reach *)head; + reach; reach = reach->next) { + spf_adj_list_parse_tlv( + spftree, adj_list, reach->id, + pseudo_nodeid, pseudo_metric, + reach->metric, true, NULL); + } + } + + if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) + te_neighs = &lsp->tlvs->extended_reach; + else + te_neighs = isis_get_mt_items(&lsp->tlvs->mt_reach, + spftree->mtid); + if (te_neighs) { + head = te_neighs->head; + for (struct isis_extended_reach *reach = + (struct isis_extended_reach *)head; + reach; reach = reach->next) { +#ifndef FABRICD + /* + * cutting out adjacency by flex-algo link + * affinity attribute + */ + if (flex_algo_id_valid(spftree->algorithm) && + (!sr_algorithm_participated( + lsp, spftree->algorithm) || + isis_flex_algo_constraint_drop( + spftree, lsp, reach))) + continue; +#endif /* ifndef FABRICD */ + + spf_adj_list_parse_tlv( + spftree, adj_list, reach->id, + pseudo_nodeid, pseudo_metric, + reach->metric, false, reach->subtlvs); + } + } + } + + if (LSP_FRAGMENT(lsp->hdr.lsp_id)) + return; + + /* Parse LSP fragments. */ + for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) { + if (!frag->tlvs) + continue; + + spf_adj_list_parse_lsp(spftree, adj_list, frag, pseudo_nodeid, + pseudo_metric); + } +} + +static void isis_spf_build_adj_list(struct isis_spftree *spftree, + struct isis_lsp *lsp) +{ + struct list *adj_list = NULL; + + if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) + adj_list = list_dup(spftree->area->adjacency_list); + + spf_adj_list_parse_lsp(spftree, adj_list, lsp, NULL, 0); + + if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) + list_delete(&adj_list); + + if (spftree->type == SPF_TYPE_REVERSE) + spf_adj_get_reverse_metrics(spftree); +} + +/* + * The parent(s) for vertex is set when added to TENT list + * now we just put the child pointer(s) in place + */ +static void add_to_paths(struct isis_spftree *spftree, + struct isis_vertex *vertex) +{ +#ifdef EXTREME_DEBUG + char buff[VID2STR_BUFFER]; +#endif /* EXTREME_DEBUG */ + + if (isis_find_vertex(&spftree->paths, &vertex->N, vertex->type)) + return; + isis_vertex_queue_append(&spftree->paths, vertex); + +#ifdef EXTREME_DEBUG + if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: A:%hhu S:%p added %s %s %s depth %d dist %d to PATHS", + spftree->algorithm, spftree, + print_sys_hostname(vertex->N.id), + vtype2string(vertex->type), + vid2string(vertex, buff, sizeof(buff)), vertex->depth, + vertex->d_N); +#endif /* EXTREME_DEBUG */ +} + +static void init_spt(struct isis_spftree *spftree, int mtid) +{ + /* Clear data from previous run. */ + hash_clean(spftree->prefix_sids, NULL); + isis_spf_node_list_clear(&spftree->adj_nodes); + list_delete_all_node(spftree->sadj_list); + isis_vertex_queue_clear(&spftree->tents); + isis_vertex_queue_clear(&spftree->paths); + isis_zebra_rlfa_unregister_all(spftree); + isis_rlfa_list_clear(spftree); + list_delete_all_node(spftree->lfa.remote.pc_spftrees); + memset(&spftree->lfa.protection_counters, 0, + sizeof(spftree->lfa.protection_counters)); + + spftree->mtid = mtid; +} + +static enum spf_prefix_priority +spf_prefix_priority(struct isis_spftree *spftree, struct isis_vertex *vertex) +{ + struct isis_area *area = spftree->area; + struct prefix *prefix = &vertex->N.ip.p.dest; + + for (int priority = SPF_PREFIX_PRIO_CRITICAL; + priority <= SPF_PREFIX_PRIO_MEDIUM; priority++) { + struct spf_prefix_priority_acl *ppa; + enum filter_type ret = FILTER_PERMIT; + + ppa = &area->spf_prefix_priorities[priority]; + switch (spftree->family) { + case AF_INET: + ret = access_list_apply(ppa->list_v4, prefix); + break; + case AF_INET6: + ret = access_list_apply(ppa->list_v6, prefix); + break; + default: + break; + } + + if (ret == FILTER_PERMIT) + return priority; + } + + /* Assign medium priority to loopback prefixes by default. */ + if (is_host_route(prefix)) + return SPF_PREFIX_PRIO_MEDIUM; + + return SPF_PREFIX_PRIO_LOW; +} + +static void spf_path_process(struct isis_spftree *spftree, + struct isis_vertex *vertex) +{ + struct isis_area *area = spftree->area; + int level = spftree->level; + char buff[VID2STR_BUFFER]; + + if (spftree->type == SPF_TYPE_TI_LFA && VTYPE_IS(vertex->type) + && !CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) { + if (listcount(vertex->Adj_N) > 0) { + struct isis_adjacency *adj; + + if (isis_tilfa_check(spftree, vertex) != 0) + return; + + adj = isis_adj_find(area, level, vertex->N.id); + if (adj) + sr_adj_sid_add_single(adj, spftree->family, + true, vertex->Adj_N); + } else if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: no adjacencies, do not install backup Adj-SID for %s depth %d dist %d", + vid2string(vertex, buff, sizeof(buff)), + vertex->depth, vertex->d_N); + } + + if (VTYPE_IP(vertex->type) + && !CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ROUTES)) { + enum spf_prefix_priority priority; + + priority = spf_prefix_priority(spftree, vertex); + vertex->N.ip.priority = priority; + if (vertex->depth == 1 || listcount(vertex->Adj_N) > 0) { + struct isis_spftree *pre_spftree; + struct route_table *route_table = NULL; + bool allow_ecmp = false; + + switch (spftree->type) { + case SPF_TYPE_RLFA: + case SPF_TYPE_TI_LFA: + if (priority + > area->lfa_priority_limit[level - 1]) { + if (IS_DEBUG_LFA) + zlog_debug( + "ISIS-LFA: skipping %s %s (low prefix priority)", + vtype2string( + vertex->type), + vid2string( + vertex, buff, + sizeof(buff))); + return; + } + break; + case SPF_TYPE_FORWARD: + case SPF_TYPE_REVERSE: + break; + } + + switch (spftree->type) { + case SPF_TYPE_RLFA: + isis_rlfa_check(spftree, vertex); + return; + case SPF_TYPE_TI_LFA: + if (isis_tilfa_check(spftree, vertex) != 0) + return; + + pre_spftree = spftree->lfa.old.spftree; + route_table = pre_spftree->route_table_backup; + allow_ecmp = area->lfa_load_sharing[level - 1]; + pre_spftree->lfa.protection_counters + .tilfa[vertex->N.ip.priority] += 1; + break; + case SPF_TYPE_FORWARD: + case SPF_TYPE_REVERSE: + route_table = spftree->route_table; + allow_ecmp = true; + + /* + * Update LFA protection counters (ignore local + * routes). + */ + if (vertex->depth > 1) { + spftree->lfa.protection_counters + .total[priority] += 1; + if (listcount(vertex->Adj_N) > 1) + spftree->lfa.protection_counters + .ecmp[priority] += 1; + } + break; + } + +#ifdef EXTREME_DEBUG + struct isis_route_info *ri = +#endif /* EXTREME_DEBUG */ + isis_route_create(&vertex->N.ip.p.dest, + &vertex->N.ip.p.src, + vertex->d_N, vertex->depth, + &vertex->N.ip.sr, + vertex->Adj_N, allow_ecmp, + area, route_table); + +#ifdef EXTREME_DEBUG + zlog_debug( + "ISIS-SPF: A:%hhu create route pfx %pFX dist %d, sr.algo %d, table %p, rv %p", + spftree->algorithm, &vertex->N.ip.p.dest, + vertex->d_N, vertex->N.ip.sr.algorithm, + route_table, ri); +#endif /* EXTREME_DEBUG */ + } else if (IS_DEBUG_SPF_EVENTS) + zlog_debug( + "ISIS-SPF: no adjacencies, do not install route for %s depth %d dist %d", + vid2string(vertex, buff, sizeof(buff)), + vertex->depth, vertex->d_N); + } +} + +static void isis_spf_loop(struct isis_spftree *spftree, + uint8_t *root_sysid) +{ + struct isis_vertex *vertex; + struct isis_lsp *lsp; + struct listnode *node; + + while (isis_vertex_queue_count(&spftree->tents)) { + vertex = isis_vertex_queue_pop(&spftree->tents); + +#ifdef EXTREME_DEBUG + zlog_debug( + "ISIS-SPF: A:%hhu get TENT node %s %s depth %d dist %d to PATHS", + spftree->algorithm, print_sys_hostname(vertex->N.id), + vtype2string(vertex->type), vertex->depth, vertex->d_N); +#endif /* EXTREME_DEBUG */ + + add_to_paths(spftree, vertex); + if (!VTYPE_IS(vertex->type)) + continue; + + lsp = lsp_for_vertex(spftree, vertex); + if (!lsp) { + zlog_warn("ISIS-SPF: No LSP found for %pPN", + vertex->N.id); + continue; + } + + isis_spf_process_lsp(spftree, lsp, vertex->d_N, vertex->depth, + root_sysid, vertex); + } + + /* Generate routes once the SPT is formed. */ + for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, vertex)) { + /* New-style TLVs take precedence over the old-style TLVs. */ + switch (vertex->type) { + case VTYPE_IPREACH_INTERNAL: + case VTYPE_IPREACH_EXTERNAL: + if (isis_find_vertex(&spftree->paths, &vertex->N, + VTYPE_IPREACH_TE)) + continue; + break; + case VTYPE_PSEUDO_IS: + case VTYPE_PSEUDO_TE_IS: + case VTYPE_NONPSEUDO_IS: + case VTYPE_NONPSEUDO_TE_IS: + case VTYPE_ES: + case VTYPE_IPREACH_TE: + case VTYPE_IP6REACH_INTERNAL: + case VTYPE_IP6REACH_EXTERNAL: + break; + } + + spf_path_process(spftree, vertex); + } +} + +struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area, + uint8_t *sysid, + struct isis_spftree *spftree) +{ + if (!spftree) + spftree = isis_spftree_new( + area, &area->lspdb[IS_LEVEL_2 - 1], sysid, ISIS_LEVEL2, + SPFTREE_IPV4, SPF_TYPE_FORWARD, + F_SPFTREE_HOPCOUNT_METRIC, SR_ALGORITHM_SPF); + + init_spt(spftree, ISIS_MT_IPV4_UNICAST); + if (!memcmp(sysid, area->isis->sysid, ISIS_SYS_ID_LEN)) { + struct isis_lsp *root_lsp; + struct isis_vertex *root_vertex; + + root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid); + if (root_lsp) { + /* + * If we are running locally, initialize with + * information from adjacencies + */ + root_vertex = isis_spf_add_root(spftree); + + isis_spf_preload_tent(spftree, sysid, root_lsp, + root_vertex); + } + } else { + isis_vertex_queue_insert( + &spftree->tents, + isis_vertex_new(spftree, sysid, VTYPE_NONPSEUDO_TE_IS)); + } + + isis_spf_loop(spftree, sysid); + + return spftree; +} + +void isis_run_spf(struct isis_spftree *spftree) +{ + struct isis_lsp *root_lsp; + struct isis_vertex *root_vertex; + struct timeval time_start; + struct timeval time_end; + struct isis_mt_router_info *mt_router_info; + uint16_t mtid = 0; +#ifndef FABRICD + bool flex_algo_enabled; +#endif /* ifndef FABRICD */ + + /* Get time that can't roll backwards. */ + monotime(&time_start); + + root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid); + if (root_lsp == NULL) { + zlog_err("ISIS-SPF: could not find own l%d LSP!", + spftree->level); + return; + } + + /* Get Multi-Topology ID. */ + switch (spftree->tree_id) { + case SPFTREE_IPV4: + mtid = ISIS_MT_IPV4_UNICAST; + break; + case SPFTREE_IPV6: + mt_router_info = isis_tlvs_lookup_mt_router_info( + root_lsp->tlvs, ISIS_MT_IPV6_UNICAST); + if (mt_router_info) + mtid = ISIS_MT_IPV6_UNICAST; + else + mtid = ISIS_MT_IPV4_UNICAST; + break; + case SPFTREE_DSTSRC: + mtid = ISIS_MT_IPV6_DSTSRC; + break; + case SPFTREE_COUNT: + zlog_err( + "%s should never be called with SPFTREE_COUNT as argument!", + __func__); + exit(1); + } + +#ifndef FABRICD + /* If a node is configured to participate in a particular Flexible- + * Algorithm, but there is no valid Flex-Algorithm definition available + * for it, or the selected Flex-Algorithm definition includes + * calculation-type, metric-type, constraint, flag, or Sub-TLV that is + * not supported by the node, it MUST stop participating in such + * Flexible-Algorithm. + */ + if (flex_algo_id_valid(spftree->algorithm)) { + flex_algo_enabled = isis_flex_algo_elected_supported( + spftree->algorithm, spftree->area); + if (flex_algo_enabled != + flex_algo_get_state(spftree->area->flex_algos, + spftree->algorithm)) { + /* actual state is inconsistent with local LSP */ + lsp_regenerate_schedule(spftree->area, + spftree->area->is_type, 0); + goto out; + } + if (!flex_algo_enabled) { + if (!CHECK_FLAG(spftree->flags, F_SPFTREE_DISABLED)) { + isis_spftree_clear(spftree); + SET_FLAG(spftree->flags, F_SPFTREE_DISABLED); + lsp_regenerate_schedule(spftree->area, + spftree->area->is_type, + 0); + } + goto out; + } + } +#endif /* ifndef FABRICD */ + + /* + * C.2.5 Step 0 + */ + init_spt(spftree, mtid); + /* a) */ + root_vertex = isis_spf_add_root(spftree); + /* b) */ + isis_spf_build_adj_list(spftree, root_lsp); + isis_spf_preload_tent(spftree, spftree->sysid, root_lsp, root_vertex); + + /* + * C.2.7 Step 2 + */ + if (!isis_vertex_queue_count(&spftree->tents) + && (IS_DEBUG_SPF_EVENTS)) { + zlog_warn("ISIS-SPF: TENT is empty SPF-root:%s", + print_sys_hostname(spftree->sysid)); + } + + isis_spf_loop(spftree, spftree->sysid); + + +#ifndef FABRICD + /* flex-algo */ + if (CHECK_FLAG(spftree->flags, F_SPFTREE_DISABLED)) { + UNSET_FLAG(spftree->flags, F_SPFTREE_DISABLED); + lsp_regenerate_schedule(spftree->area, spftree->area->is_type, + 0); + } + +out: +#endif /* ifndef FABRICD */ + spftree->runcount++; + spftree->last_run_timestamp = time(NULL); + spftree->last_run_monotime = monotime(&time_end); + spftree->last_run_duration = + ((time_end.tv_sec - time_start.tv_sec) * 1000000) + + (time_end.tv_usec - time_start.tv_usec); +} + +static void isis_run_spf_with_protection(struct isis_area *area, + struct isis_spftree *spftree) +{ + /* Run forward SPF locally. */ + memcpy(spftree->sysid, area->isis->sysid, ISIS_SYS_ID_LEN); + isis_run_spf(spftree); + + /* Run LFA protection if configured. */ + if (area->lfa_protected_links[spftree->level - 1] > 0 + || area->tilfa_protected_links[spftree->level - 1] > 0) + isis_spf_run_lfa(area, spftree); +} + +void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees, + int tree) +{ + if (area->is_type == IS_LEVEL_1) { + isis_route_verify_table(area, trees[0]->route_table, + trees[0]->route_table_backup, tree); + } else if (area->is_type == IS_LEVEL_2) { + isis_route_verify_table(area, trees[1]->route_table, + trees[1]->route_table_backup, tree); + } else { + isis_route_verify_merge(area, trees[0]->route_table, + trees[0]->route_table_backup, + trees[1]->route_table, + trees[1]->route_table_backup, tree); + } +} + +void isis_spf_invalidate_routes(struct isis_spftree *tree) +{ + struct isis_route_table_info *backup_info; + + isis_route_invalidate_table(tree->area, tree->route_table); + + /* Delete backup routes. */ + + backup_info = tree->route_table_backup->info; + route_table_finish(tree->route_table_backup); + isis_route_table_info_free(backup_info); + tree->route_table_backup = srcdest_table_init(); + tree->route_table_backup->info = + isis_route_table_info_alloc(tree->algorithm); + tree->route_table_backup->cleanup = isis_route_node_cleanup; +} + +void isis_spf_switchover_routes(struct isis_area *area, + struct isis_spftree **trees, int family, + union g_addr *nexthop_ip, ifindex_t ifindex, + int level) +{ + isis_route_switchover_nexthop(area, trees[level - 1]->route_table, + family, nexthop_ip, ifindex); +} + +static void isis_run_spf_cb(struct event *thread) +{ + struct isis_spf_run *run = EVENT_ARG(thread); + struct isis_area *area = run->area; + int level = run->level; + int have_run = 0; + struct listnode *node; + struct isis_circuit *circuit; +#ifndef FABRICD + struct flex_algo *fa; + struct isis_flex_algo_data *data; +#endif /* ifndef FABRICD */ + + XFREE(MTYPE_ISIS_SPF_RUN, run); + + if (!(area->is_type & level)) { + if (IS_DEBUG_SPF_EVENTS) + zlog_warn("ISIS-SPF (%s) area does not share level", + area->area_tag); + return; + } + + isis_area_delete_backup_adj_sids(area, level); + isis_area_invalidate_routes(area, level); + + if (IS_DEBUG_SPF_EVENTS) + zlog_debug("ISIS-SPF (%s) L%d SPF needed, periodic SPF", + area->area_tag, level); + + if (area->ip_circuits) { + isis_run_spf_with_protection( + area, area->spftree[SPFTREE_IPV4][level - 1]); +#ifndef FABRICD + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + data = fa->data; + isis_run_spf_with_protection( + area, data->spftree[SPFTREE_IPV4][level - 1]); + } +#endif /* ifndef FABRICD */ + have_run = 1; + } + if (area->ipv6_circuits) { + isis_run_spf_with_protection( + area, area->spftree[SPFTREE_IPV6][level - 1]); +#ifndef FABRICD + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + data = fa->data; + isis_run_spf_with_protection( + area, data->spftree[SPFTREE_IPV6][level - 1]); + } +#endif /* ifndef FABRICD */ + have_run = 1; + } + if (area->ipv6_circuits && isis_area_ipv6_dstsrc_enabled(area)) { + isis_run_spf_with_protection( + area, area->spftree[SPFTREE_DSTSRC][level - 1]); + have_run = 1; + } + + if (have_run) + area->spf_run_count[level]++; + + isis_area_verify_routes(area); + + /* walk all circuits and reset any spf specific flags */ + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF); + + fabricd_run_spf(area); +} + +static struct isis_spf_run *isis_run_spf_arg(struct isis_area *area, int level) +{ + struct isis_spf_run *run = XMALLOC(MTYPE_ISIS_SPF_RUN, sizeof(*run)); + + run->area = area; + run->level = level; + + return run; +} + +void isis_spf_timer_free(void *run) +{ + XFREE(MTYPE_ISIS_SPF_RUN, run); +} + +int _isis_spf_schedule(struct isis_area *area, int level, + const char *func, const char *file, int line) +{ + struct isis_spftree *spftree; + time_t now; + long tree_diff, diff; + int tree; + + now = monotime(NULL); + diff = 0; + for (tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + spftree = area->spftree[tree][level - 1]; + tree_diff = difftime(now - spftree->last_run_monotime, 0); + if (tree_diff != now && (diff == 0 || tree_diff < diff)) + diff = tree_diff; + } + + if (CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + return 0; + + assert(diff >= 0); + assert(area->is_type & level); + + if (IS_DEBUG_SPF_EVENTS) { + zlog_debug( + "ISIS-SPF (%s) L%d SPF schedule called, lastrun %ld sec ago Caller: %s %s:%d", + area->area_tag, level, diff, func, file, line); + } + + EVENT_OFF(area->t_rlfa_rib_update); + if (area->spf_delay_ietf[level - 1]) { + /* Need to call schedule function also if spf delay is running + * to + * restart holdoff timer - compare + * draft-ietf-rtgwg-backoff-algo-04 */ + long delay = + spf_backoff_schedule(area->spf_delay_ietf[level - 1]); + if (area->spf_timer[level - 1]) + return ISIS_OK; + + event_add_timer_msec(master, isis_run_spf_cb, + isis_run_spf_arg(area, level), delay, + &area->spf_timer[level - 1]); + return ISIS_OK; + } + + if (area->spf_timer[level - 1]) + return ISIS_OK; + + /* wait configured min_spf_interval before doing the SPF */ + long timer; + if (diff >= area->min_spf_interval[level - 1] + || area->bfd_force_spf_refresh) { + /* + * Last run is more than min interval ago or BFD signalled a + * 'down' message, schedule immediate run + */ + timer = 0; + + if (area->bfd_force_spf_refresh) { + zlog_debug( + "ISIS-SPF (%s) L%d SPF scheduled immediately due to BFD 'down' message", + area->area_tag, level); + area->bfd_force_spf_refresh = false; + } + } else { + timer = area->min_spf_interval[level - 1] - diff; + } + + event_add_timer(master, isis_run_spf_cb, isis_run_spf_arg(area, level), + timer, &area->spf_timer[level - 1]); + + if (IS_DEBUG_SPF_EVENTS) + zlog_debug("ISIS-SPF (%s) L%d SPF scheduled %ld sec from now", + area->area_tag, level, timer); + + return ISIS_OK; +} + +static void isis_print_paths(struct vty *vty, struct isis_vertex_queue *queue, + uint8_t *root_sysid) +{ + struct listnode *node; + struct isis_vertex *vertex; + char buff[VID2STR_BUFFER]; + + vty_out(vty, + "Vertex Type Metric Next-Hop Interface Parent\n"); + + for (ALL_QUEUE_ELEMENTS_RO(queue, node, vertex)) { + if (VTYPE_IS(vertex->type) + && memcmp(vertex->N.id, root_sysid, ISIS_SYS_ID_LEN) == 0) { + vty_out(vty, "%-20s %-12s %-6s", + print_sys_hostname(root_sysid), "", ""); + vty_out(vty, "%-30s\n", ""); + continue; + } + + int rows = 0; + struct listnode *anode = listhead(vertex->Adj_N); + struct listnode *pnode = listhead(vertex->parents); + struct isis_vertex_adj *vadj; + struct isis_vertex *pvertex; + + vty_out(vty, "%-20s %-12s %-6u ", + vid2string(vertex, buff, sizeof(buff)), + vtype2string(vertex->type), vertex->d_N); + for (unsigned int i = 0; + i < MAX(vertex->Adj_N ? listcount(vertex->Adj_N) : 0, + vertex->parents ? listcount(vertex->parents) : 0); + i++) { + if (anode) { + vadj = listgetdata(anode); + anode = anode->next; + } else { + vadj = NULL; + } + + if (pnode) { + pvertex = listgetdata(pnode); + pnode = pnode->next; + } else { + pvertex = NULL; + } + + if (rows) { + vty_out(vty, "\n"); + vty_out(vty, "%-20s %-12s %-6s ", "", "", ""); + } + + if (vadj) { + struct isis_spf_adj *sadj = vadj->sadj; + + vty_out(vty, "%-20s %-9s ", + print_sys_hostname(sadj->id), + sadj->adj ? sadj->adj->circuit + ->interface->name + : "-"); + } + + if (pvertex) { + if (!vadj) + vty_out(vty, "%-20s %-9s ", "", ""); + + vty_out(vty, "%s(%d)", + vid2string(pvertex, buff, sizeof(buff)), + pvertex->type); + } + + ++rows; + } + vty_out(vty, "\n"); + } +} + +void isis_print_spftree(struct vty *vty, struct isis_spftree *spftree) +{ + const char *tree_id_text = NULL; + + if (!spftree || !isis_vertex_queue_count(&spftree->paths)) + return; + + switch (spftree->tree_id) { + case SPFTREE_IPV4: + tree_id_text = "that speak IP"; + break; + case SPFTREE_IPV6: + tree_id_text = "that speak IPv6"; + break; + case SPFTREE_DSTSRC: + tree_id_text = "that support IPv6 dst-src routing"; + break; + case SPFTREE_COUNT: + assert(!"isis_print_spftree shouldn't be called with SPFTREE_COUNT as type"); + return; + } + + vty_out(vty, "IS-IS paths to level-%d routers %s\n", spftree->level, + tree_id_text); + isis_print_paths(vty, &spftree->paths, spftree->sysid); + vty_out(vty, "\n"); +} + +static void show_isis_topology_common(struct vty *vty, int levels, + struct isis *isis, uint8_t algo) +{ +#ifndef FABRICD + struct isis_flex_algo_data *fa_data; + struct flex_algo *fa; +#endif /* ifndef FABRICD */ + struct isis_spftree *spftree; + struct listnode *node; + struct isis_area *area; + + if (!isis->area_list || isis->area_list->count == 0) + return; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { +#ifndef FABRICD + /* + * The shapes of the flex algo spftree 2-dimensional array + * and the area spftree 2-dimensional array are not guaranteed + * to be identical. + */ + fa = NULL; + if (flex_algo_id_valid(algo)) { + fa = flex_algo_lookup(area->flex_algos, algo); + if (!fa) + continue; + fa_data = (struct isis_flex_algo_data *)fa->data; + } else + fa_data = NULL; +#endif /* ifndef FABRICD */ + + vty_out(vty, + "Area %s:", area->area_tag ? area->area_tag : "null"); + +#ifndef FABRICD + if (algo != SR_ALGORITHM_SPF) + vty_out(vty, " Algorithm %hhu\n", algo); + else +#endif /* ifndef FABRICD */ + vty_out(vty, "\n"); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((level & levels) == 0) + continue; + + if (area->ip_circuits > 0) { +#ifndef FABRICD + if (fa_data) + spftree = fa_data->spftree[SPFTREE_IPV4] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_IPV4] + [level - 1]; + + isis_print_spftree(vty, spftree); + } + if (area->ipv6_circuits > 0) { +#ifndef FABRICD + if (fa_data) + spftree = fa_data->spftree[SPFTREE_IPV6] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_IPV6] + [level - 1]; + isis_print_spftree(vty, spftree); + } + if (isis_area_ipv6_dstsrc_enabled(area)) { +#ifndef FABRICD + if (fa_data) + spftree = + fa_data->spftree[SPFTREE_DSTSRC] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_DSTSRC] + [level - 1]; + isis_print_spftree(vty, spftree); + } + } + + if (fabricd_spftree(area)) { + vty_out(vty, + "IS-IS paths to level-2 routers with hop-by-hop metric\n"); + isis_print_paths(vty, &fabricd_spftree(area)->paths, isis->sysid); + vty_out(vty, "\n"); + } + + vty_out(vty, "\n"); + } +} + +DEFUN(show_isis_topology, show_isis_topology_cmd, + "show " PROTO_NAME + " [vrf ] topology" +#ifndef FABRICD + " []" + " [algorithm [(128-255)]]" +#endif /* ifndef FABRICD */ + , + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "IS-IS paths to Intermediate Systems\n" +#ifndef FABRICD + "Paths to all level-1 routers in the area\n" + "Paths to all level-2 routers in the domain\n" + "Show Flex-algo routes\n" + "Algorithm number\n" +#endif /* ifndef FABRICD */ +) +{ + int levels = ISIS_LEVELS; + struct listnode *node; + struct isis *isis = NULL; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + bool all_algorithm = false; + int idx_vrf = 0; + uint16_t algorithm = SR_ALGORITHM_SPF; + +#ifndef FABRICD + int idx = 0; + + levels = ISIS_LEVEL1 | ISIS_LEVEL2; + if (argv_find(argv, argc, "level-1", &idx)) + levels = ISIS_LEVEL1; + if (argv_find(argv, argc, "level-2", &idx)) + levels = ISIS_LEVEL2; + if (argv_find(argv, argc, "algorithm", &idx)) { + if (argv_find(argv, argc, "(128-255)", &idx)) + algorithm = (uint16_t)strtoul(argv[idx]->arg, NULL, 10); + else + all_algorithm = true; + } +#endif /* ifndef FABRICD */ + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + if (all_algorithm) { + for (algorithm = SR_ALGORITHM_FLEX_MIN; + algorithm <= SR_ALGORITHM_FLEX_MAX; + algorithm++) + show_isis_topology_common( + vty, levels, isis, + (uint8_t)algorithm); + } else { + show_isis_topology_common(vty, levels, isis, + (uint8_t)algorithm); + } + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis == NULL) + return CMD_SUCCESS; + if (all_algorithm) { + for (algorithm = SR_ALGORITHM_FLEX_MIN; + algorithm <= SR_ALGORITHM_FLEX_MAX; algorithm++) { + show_isis_topology_common(vty, levels, isis, + (uint8_t)algorithm); + } + } else + show_isis_topology_common(vty, levels, isis, (uint8_t)algorithm); + + return CMD_SUCCESS; +} + +#ifndef FABRICD +static void show_isis_flex_algo_display_eag(struct vty *vty, char *buf, + int indent, + struct admin_group *admin_group) +{ + if (admin_group_zero(admin_group)) + vty_out(vty, "not-set\n"); + else { + vty_out(vty, "%s\n", + admin_group_string(buf, ADMIN_GROUP_PRINT_MAX_SIZE, + indent, admin_group)); + admin_group_print(buf, indent, admin_group); + if (buf[0] != '\0') + vty_out(vty, " Bit positions: %s\n", buf); + } +} + +static void show_isis_flex_algo_common(struct vty *vty, struct isis *isis, + uint8_t algorithm) +{ + struct isis_router_cap_fad *router_fad; + char buf[ADMIN_GROUP_PRINT_MAX_SIZE]; + struct admin_group *admin_group; + struct isis_area *area; + struct listnode *node; + struct flex_algo *fa; + int indent, algo; + bool fad_identical, fad_supported; + + if (!isis->area_list || isis->area_list->count == 0) + return; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + /* + * The shapes of the flex algo spftree 2-dimensional array + * and the area spftree 2-dimensional array are not guaranteed + * to be identical. + */ + + for (algo = 0; algo < SR_ALGORITHM_COUNT; algo++) { + if (algorithm != SR_ALGORITHM_UNSET && + algorithm != algo) + continue; + + fa = flex_algo_lookup(area->flex_algos, algo); + if (!fa) + continue; + + vty_out(vty, "Area %s:", + area->area_tag ? area->area_tag : "null"); + + vty_out(vty, " Algorithm %d\n", algo); + vty_out(vty, "\n"); + + vty_out(vty, " Enabled Data-Planes:"); + if (fa->dataplanes == 0) { + vty_out(vty, " None\n\n"); + continue; + } + if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_SR_MPLS)) + vty_out(vty, " SR-MPLS"); + if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_SRV6)) + vty_out(vty, " SRv6"); + if (CHECK_FLAG(fa->dataplanes, FLEX_ALGO_IP)) + vty_out(vty, " IP"); + vty_out(vty, "\n\n"); + + + router_fad = isis_flex_algo_elected(algo, area); + vty_out(vty, + " Elected and running Flexible-Algorithm Definition:\n"); + if (router_fad) + vty_out(vty, " Source: %pSY\n", + router_fad->sysid); + else + vty_out(vty, " Source: Not found\n"); + + if (!router_fad) { + vty_out(vty, "\n"); + continue; + } + + fad_identical = + flex_algo_definition_cmp(fa, &router_fad->fad); + fad_supported = + isis_flex_algo_supported(&router_fad->fad); + vty_out(vty, " Priority: %d\n", + router_fad->fad.priority); + vty_out(vty, " Equal to local: %s\n", + fad_identical ? "yes" : "no"); + vty_out(vty, " Local state: %s\n", + fad_supported + ? "enabled" + : "disabled (unsupported definition)"); + vty_out(vty, " Calculation type: "); + if (router_fad->fad.calc_type == 0) + vty_out(vty, "spf\n"); + else + vty_out(vty, "%d\n", router_fad->fad.calc_type); + vty_out(vty, " Metric type: %s\n", + flex_algo_metric_type_print( + buf, sizeof(buf), + router_fad->fad.metric_type)); + vty_out(vty, " Prefix-metric: %s\n", + CHECK_FLAG(router_fad->fad.flags, FAD_FLAG_M) + ? "enabled" + : "disabled"); + if (router_fad->fad.flags != 0 && + router_fad->fad.flags != FAD_FLAG_M) + vty_out(vty, " Flags: 0x%x\n", + router_fad->fad.flags); + vty_out(vty, " Exclude SRLG: %s\n", + router_fad->fad.exclude_srlg ? "enabled" + : "disabled"); + + admin_group = &router_fad->fad.admin_group_exclude_any; + indent = vty_out(vty, " Exclude-any admin-group: "); + show_isis_flex_algo_display_eag(vty, buf, indent, + admin_group); + + admin_group = &router_fad->fad.admin_group_include_all; + indent = vty_out(vty, " Include-all admin-group: "); + show_isis_flex_algo_display_eag(vty, buf, indent, + admin_group); + + admin_group = &router_fad->fad.admin_group_include_any; + indent = vty_out(vty, " Include-any admin-group: "); + show_isis_flex_algo_display_eag(vty, buf, indent, + admin_group); + + if (router_fad->fad.unsupported_subtlv) + vty_out(vty, + " Unsupported sub-TLV: Present (see logs)"); + + vty_out(vty, "\n"); + } + } +} + +DEFUN(show_isis_flex_algo, show_isis_flex_algo_cmd, + "show " PROTO_NAME + " [vrf ] flex-algo" + " [(128-255)]", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "IS-IS Flex-algo information\n" + "Algorithm number\n") +{ + struct isis *isis; + struct listnode *node; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx = 0; + int idx_vrf = 0; + uint8_t flex_algo; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (argv_find(argv, argc, "flex-algo", &idx) && (idx + 1) < argc) + flex_algo = (uint8_t)strtoul(argv[idx + 1]->arg, NULL, 10); + else + flex_algo = SR_ALGORITHM_UNSET; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + show_isis_flex_algo_common(vty, isis, flex_algo); + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) + show_isis_flex_algo_common(vty, isis, flex_algo); + + return CMD_SUCCESS; +} +#endif /* ifndef FABRICD */ + +static void isis_print_route(struct ttable *tt, const struct prefix *prefix, + struct isis_route_info *rinfo, bool prefix_sid, + bool no_adjacencies, bool json) +{ + struct isis_nexthop *nexthop; + struct listnode *node; + bool first = true; + char buf_prefix[BUFSIZ]; + + (void)prefix2str(prefix, buf_prefix, sizeof(buf_prefix)); + for (int alg = 0; alg < SR_ALGORITHM_COUNT; alg++) { + for (ALL_LIST_ELEMENTS_RO(rinfo->sr_algo[alg].nexthops, node, + nexthop)) { + struct interface *ifp; + char buf_iface[BUFSIZ]; + char buf_nhop[BUFSIZ]; + + if (!no_adjacencies) { + inet_ntop(nexthop->family, &nexthop->ip, + buf_nhop, sizeof(buf_nhop)); + ifp = if_lookup_by_index(nexthop->ifindex, + VRF_DEFAULT); + if (ifp) + strlcpy(buf_iface, ifp->name, + sizeof(buf_iface)); + else + snprintf(buf_iface, sizeof(buf_iface), + "ifindex %u", + nexthop->ifindex); + } else { + strlcpy(buf_nhop, + print_sys_hostname(nexthop->sysid), + sizeof(buf_nhop)); + strlcpy(buf_iface, "-", sizeof(buf_iface)); + } + + if (prefix_sid) { + char buf_sid[BUFSIZ] = {}; + char buf_lblop[BUFSIZ] = {}; + + if (rinfo->sr_algo[alg].present) { + snprintf(buf_sid, sizeof(buf_sid), "%u", + rinfo->sr_algo[alg].sid.value); + sr_op2str(buf_lblop, sizeof(buf_lblop), + rinfo->sr_algo[alg].label, + nexthop->sr.label); + } else if (alg == SR_ALGORITHM_SPF) { + strlcpy(buf_sid, "-", sizeof(buf_sid)); + strlcpy(buf_lblop, "-", + sizeof(buf_lblop)); + } else { + continue; + } + + if (first || json) { + ttable_add_row(tt, + "%s|%u|%s|%s|%s|%s|%d", + buf_prefix, rinfo->cost, + buf_iface, buf_nhop, + buf_sid, buf_lblop, alg); + first = false; + } else + ttable_add_row(tt, "||%s|%s|%s|%s|%d", + buf_iface, buf_nhop, + buf_sid, buf_lblop, alg); + } else { + char buf_labels[BUFSIZ] = {}; + + if (nexthop->label_stack) { + for (int i = 0; + i < + nexthop->label_stack->num_labels; + i++) { + char buf_label[BUFSIZ]; + + label2str(nexthop->label_stack + ->label[i], + 0, buf_label, + sizeof(buf_label)); + if (i != 0) + strlcat(buf_labels, "/", + sizeof(buf_labels)); + strlcat(buf_labels, buf_label, + sizeof(buf_labels)); + } + } else if (nexthop->sr.present) + label2str(nexthop->sr.label, 0, + buf_labels, + sizeof(buf_labels)); + else + strlcpy(buf_labels, "-", + sizeof(buf_labels)); + + if (first || json) { + ttable_add_row(tt, "%s|%u|%s|%s|%s", + buf_prefix, rinfo->cost, + buf_iface, buf_nhop, + buf_labels); + first = false; + } else + ttable_add_row(tt, "||%s|%s|%s", + buf_iface, buf_nhop, + buf_labels); + } + } + } + + if (list_isempty(rinfo->nexthops)) { + if (prefix_sid) { + char buf_sid[BUFSIZ] = {}; + char buf_lblop[BUFSIZ] = {}; + + if (rinfo->sr_algo[SR_ALGORITHM_SPF].present) { + snprintf(buf_sid, sizeof(buf_sid), "%u", + rinfo->sr_algo[SR_ALGORITHM_SPF] + .sid.value); + sr_op2str( + buf_lblop, sizeof(buf_lblop), + rinfo->sr_algo[SR_ALGORITHM_SPF].label, + MPLS_LABEL_IMPLICIT_NULL); + } else { + strlcpy(buf_sid, "-", sizeof(buf_sid)); + strlcpy(buf_lblop, "-", sizeof(buf_lblop)); + } + + ttable_add_row(tt, "%s|%u|%s|%s|%s|%s", buf_prefix, + rinfo->cost, "-", "-", buf_sid, + buf_lblop); + } else + ttable_add_row(tt, "%s|%u|%s|%s|%s", buf_prefix, + rinfo->cost, "-", "-", "-"); + } +} + +void isis_print_routes(struct vty *vty, struct isis_spftree *spftree, + struct json_object **json, bool prefix_sid, bool backup) +{ + struct route_table *route_table; + struct ttable *tt; + struct route_node *rn; + bool no_adjacencies = false; + const char *tree_id_text = NULL; + + if (!spftree) + return; + + switch (spftree->tree_id) { + case SPFTREE_IPV4: + tree_id_text = "IPv4"; + break; + case SPFTREE_IPV6: + tree_id_text = "IPv6"; + break; + case SPFTREE_DSTSRC: + tree_id_text = "IPv6 (dst-src routing)"; + break; + case SPFTREE_COUNT: + assert(!"isis_print_routes shouldn't be called with SPFTREE_COUNT as type"); + return; + } + + if (json == NULL) + vty_out(vty, "IS-IS %s %s routing table:\n\n", + circuit_t2string(spftree->level), tree_id_text); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + if (prefix_sid) + ttable_add_row( + tt, + "Prefix|Metric|Interface|Nexthop|SID|Label Op.|Algo"); + else + ttable_add_row(tt, "Prefix|Metric|Interface|Nexthop|Label(s)"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) + no_adjacencies = true; + + route_table = + (backup) ? spftree->route_table_backup : spftree->route_table; + for (rn = route_top(route_table); rn; rn = route_next(rn)) { + struct isis_route_info *rinfo; + + rinfo = rn->info; + if (!rinfo) + continue; + + isis_print_route(tt, &rn->p, rinfo, prefix_sid, no_adjacencies, + json != NULL); + } + + /* Dump the generated table. */ + if (json == NULL && tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else if (json) { + *json = ttable_json(tt, prefix_sid ? "sdssdsdd" : "sdsss"); + } + ttable_del(tt); +} + +static void show_isis_route_common(struct vty *vty, int levels, + struct isis *isis, bool prefix_sid, + bool backup, uint8_t algo, + json_object **json) +{ + json_object *json_level = NULL, *jstr = NULL, *json_val; +#ifndef FABRICD + struct isis_flex_algo_data *fa_data; + struct flex_algo *fa; +#endif /* ifndef FABRICD */ + struct isis_spftree *spftree; + struct listnode *node; + struct isis_area *area; + char key[18]; + + if (!isis->area_list || isis->area_list->count == 0) + return; + + if (json) + *json = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { +#ifndef FABRICD + /* + * The shapes of the flex algo spftree 2-dimensional array + * and the area spftree 2-dimensional array are not guaranteed + * to be identical. + */ + fa = NULL; + if (flex_algo_id_valid(algo)) { + fa = flex_algo_lookup(area->flex_algos, algo); + if (!fa) + continue; + fa_data = (struct isis_flex_algo_data *)fa->data; + } else { + fa_data = NULL; + } +#endif /* ifndef FABRICD */ + + if (json) { + jstr = json_object_new_string( + area->area_tag ? area->area_tag : "null"); + json_object_object_add(*json, "area", jstr); + json_object_int_add(*json, "algorithm", algo); + } else { + vty_out(vty, "Area %s:", + area->area_tag ? area->area_tag : "null"); +#ifndef FABRICD + if (algo != SR_ALGORITHM_SPF) + vty_out(vty, " Algorithm %hhu\n", algo); + else +#endif /* ifndef FABRICD */ + vty_out(vty, "\n"); + } + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((level & levels) == 0) + continue; + + if (json) { + json_level = json_object_new_object(); + jstr = json_object_new_string( + area->area_tag ? area->area_tag + : "null"); + json_object_object_add(json_level, "area", + jstr); + } + + if (area->ip_circuits > 0) { + json_val = NULL; +#ifndef FABRICD + if (fa_data) + spftree = fa_data->spftree[SPFTREE_IPV4] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_IPV4] + [level - 1]; + + if (!json) + isis_print_spftree(vty, spftree); + + isis_print_routes(vty, spftree, + json ? &json_val : NULL, + prefix_sid, backup); + if (json && json_val) { + json_object_object_add( + json_level, "ipv4", json_val); + } + } + if (area->ipv6_circuits > 0) { + json_val = NULL; +#ifndef FABRICD + if (fa_data) + spftree = fa_data->spftree[SPFTREE_IPV6] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_IPV6] + [level - 1]; + + if (!json) + isis_print_spftree(vty, spftree); + + isis_print_routes(vty, spftree, + json ? &json_val : NULL, + prefix_sid, backup); + if (json && json_val) { + json_object_object_add( + json_level, "ipv6", json_val); + } + } + if (isis_area_ipv6_dstsrc_enabled(area)) { + json_val = NULL; +#ifndef FABRICD + if (fa_data) + spftree = + fa_data->spftree[SPFTREE_DSTSRC] + [level - 1]; + else +#endif /* ifndef FABRICD */ + spftree = area->spftree[SPFTREE_DSTSRC] + [level - 1]; + + if (!json) + isis_print_spftree(vty, spftree); + isis_print_routes(vty, spftree, + json ? &json_val : NULL, + prefix_sid, backup); + if (json && json_val) { + json_object_object_add(json_level, + "ipv6-dstsrc", + json_val); + } + } + if (json) { + snprintf(key, sizeof(key), "level-%d", level); + json_object_object_add(*json, key, json_level); + } + } + } +} + +static void show_isis_route_all_algos(struct vty *vty, int levels, + struct isis *isis, bool prefix_sid, + bool backup, json_object **json) +{ + uint16_t algo; + + json_object *json_algo = NULL, *json_algos = NULL; + + if (json) { + *json = json_object_new_object(); + json_algos = json_object_new_array(); + } + + for (algo = SR_ALGORITHM_FLEX_MIN; algo <= SR_ALGORITHM_FLEX_MAX; + algo++) { + show_isis_route_common(vty, levels, isis, prefix_sid, backup, + (uint8_t)algo, json ? &json_algo : NULL); + if (!json) + continue; + if (json_object_object_length(json_algo) == 0) { + json_object_free(json_algo); + continue; + } + json_object_object_add(json_algo, "algorithm", + json_object_new_int(algo)); + json_object_array_add(json_algos, json_algo); + } + + if (json) + json_object_object_add(*json, "algorithms", json_algos); +} + + +DEFUN(show_isis_route, show_isis_route_cmd, + "show " PROTO_NAME + " [vrf ] route" +#ifndef FABRICD + " []" +#endif /* ifndef FABRICD */ + " []" +#ifndef FABRICD + " [algorithm [(128-255)]]" +#endif /* ifndef FABRICD */ + " [json$uj]", + SHOW_STR PROTO_HELP VRF_FULL_CMD_HELP_STR + "IS-IS routing table\n" +#ifndef FABRICD + "level-1 routes\n" + "level-2 routes\n" +#endif /* ifndef FABRICD */ + "Show Prefix-SID information\n" + "Show backup routes\n" +#ifndef FABRICD + "Show Flex-algo routes\n" + "Algorithm number\n" +#endif /* ifndef FABRICD */ + JSON_STR) +{ + int levels; + struct isis *isis; + struct listnode *node; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + bool all_algorithm = false; + bool prefix_sid = false; + bool backup = false; + bool uj = use_json(argc, argv); + int idx = 0; + json_object *json = NULL, *json_vrf = NULL; + uint8_t algorithm = SR_ALGORITHM_SPF; + + if (argv_find(argv, argc, "level-1", &idx)) + levels = ISIS_LEVEL1; + else if (argv_find(argv, argc, "level-2", &idx)) + levels = ISIS_LEVEL2; + else + levels = ISIS_LEVEL1 | ISIS_LEVEL2; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + ISIS_FIND_VRF_ARGS(argv, argc, idx, vrf_name, all_vrf); + + if (argv_find(argv, argc, "prefix-sid", &idx)) + prefix_sid = true; + if (argv_find(argv, argc, "backup", &idx)) + backup = true; + +#ifndef FABRICD + if (argv_find(argv, argc, "algorithm", &idx)) { + if (argv_find(argv, argc, "(128-255)", &idx)) + algorithm = (uint8_t)strtoul(argv[idx]->arg, NULL, 10); + else + all_algorithm = true; + } +#endif /* ifndef FABRICD */ + + if (uj) + json = json_object_new_array(); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + if (all_algorithm) + show_isis_route_all_algos(vty, levels, isis, + prefix_sid, backup, + uj ? &json_vrf : NULL); + else + show_isis_route_common(vty, levels, isis, + prefix_sid, backup, + algorithm, + uj ? &json_vrf : NULL); + if (uj) { + json_object_object_add(json_vrf, "vrf_id", + json_object_new_int( + isis->vrf_id)); + json_object_array_add(json, json_vrf); + } + } + goto out; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + if (all_algorithm) + show_isis_route_all_algos(vty, levels, isis, prefix_sid, + backup, uj ? &json_vrf : NULL); + else + show_isis_route_common(vty, levels, isis, prefix_sid, + backup, algorithm, + uj ? &json_vrf : NULL); + if (uj) { + json_object_object_add(json_vrf, "vrf_id", + json_object_new_int(isis->vrf_id)); + json_object_array_add(json, json_vrf); + } + } + +out: + if (uj) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + json_object_free(json); + } + + return CMD_SUCCESS; +} + +static void isis_print_frr_summary_line(struct ttable *tt, + const char *protection, + uint32_t counters[SPF_PREFIX_PRIO_MAX]) +{ + uint32_t critical, high, medium, low, total; + + critical = counters[SPF_PREFIX_PRIO_CRITICAL]; + high = counters[SPF_PREFIX_PRIO_HIGH]; + medium = counters[SPF_PREFIX_PRIO_MEDIUM]; + low = counters[SPF_PREFIX_PRIO_LOW]; + total = critical + high + medium + low; + + ttable_add_row(tt, "%s|%u|%u|%u|%u|%u", protection, critical, high, + medium, low, total); +} + +static void +isis_print_frr_summary_line_coverage(struct ttable *tt, const char *protection, + double counters[SPF_PREFIX_PRIO_MAX], + double total) +{ + double critical, high, medium, low; + + critical = counters[SPF_PREFIX_PRIO_CRITICAL] * 100; + high = counters[SPF_PREFIX_PRIO_HIGH] * 100; + medium = counters[SPF_PREFIX_PRIO_MEDIUM] * 100; + low = counters[SPF_PREFIX_PRIO_LOW] * 100; + total *= 100; + + ttable_add_row(tt, "%s|%.2f%%|%.2f%%|%.2f%%|%.2f%%|%.2f%%", protection, + critical, high, medium, low, total); +} + +static void isis_print_frr_summary(struct vty *vty, + struct isis_spftree *spftree) +{ + struct ttable *tt; + char *table; + const char *tree_id_text = NULL; + uint32_t protectd[SPF_PREFIX_PRIO_MAX] = {0}; + uint32_t unprotected[SPF_PREFIX_PRIO_MAX] = {0}; + double coverage[SPF_PREFIX_PRIO_MAX] = {0}; + uint32_t protected_total = 0, grand_total = 0; + double coverage_total; + + if (!spftree) + return; + + switch (spftree->tree_id) { + case SPFTREE_IPV4: + tree_id_text = "IPv4"; + break; + case SPFTREE_IPV6: + tree_id_text = "IPv6"; + break; + case SPFTREE_DSTSRC: + tree_id_text = "IPv6 (dst-src routing)"; + break; + case SPFTREE_COUNT: + assert(!"isis_print_frr_summary shouldn't be called with SPFTREE_COUNT as type"); + return; + } + + vty_out(vty, " IS-IS %s %s Fast ReRoute summary:\n\n", + circuit_t2string(spftree->level), tree_id_text); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Protection \\ Priority|Critical|High |Medium |Low |Total"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + /* Compute unprotected and coverage totals. */ + for (int priority = SPF_PREFIX_PRIO_CRITICAL; + priority < SPF_PREFIX_PRIO_MAX; priority++) { + uint32_t *lfa = spftree->lfa.protection_counters.lfa; + uint32_t *rlfa = spftree->lfa.protection_counters.rlfa; + uint32_t *tilfa = spftree->lfa.protection_counters.tilfa; + uint32_t *ecmp = spftree->lfa.protection_counters.ecmp; + uint32_t *total = spftree->lfa.protection_counters.total; + + protectd[priority] = lfa[priority] + rlfa[priority] + + tilfa[priority] + ecmp[priority]; + /* Safeguard to protect against possible inconsistencies. */ + if (protectd[priority] > total[priority]) + protectd[priority] = total[priority]; + unprotected[priority] = total[priority] - protectd[priority]; + protected_total += protectd[priority]; + grand_total += total[priority]; + + if (!total[priority]) + coverage[priority] = 0; + else + coverage[priority] = + protectd[priority] / (double)total[priority]; + } + + if (!grand_total) + coverage_total = 0; + else + coverage_total = protected_total / (double)grand_total; + + /* Add rows. */ + isis_print_frr_summary_line(tt, "Classic LFA", + spftree->lfa.protection_counters.lfa); + isis_print_frr_summary_line(tt, "Remote LFA", + spftree->lfa.protection_counters.rlfa); + isis_print_frr_summary_line(tt, "Topology Independent LFA", + spftree->lfa.protection_counters.tilfa); + isis_print_frr_summary_line(tt, "ECMP", + spftree->lfa.protection_counters.ecmp); + isis_print_frr_summary_line(tt, "Unprotected", unprotected); + isis_print_frr_summary_line_coverage(tt, "Protection coverage", + coverage, coverage_total); + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); +} + +static void show_isis_frr_summary_common(struct vty *vty, int levels, + struct isis *isis) +{ + struct listnode *node; + struct isis_area *area; + + if (!isis->area_list || isis->area_list->count == 0) + return; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((level & levels) == 0) + continue; + + if (area->ip_circuits > 0) { + isis_print_frr_summary( + vty, + area->spftree[SPFTREE_IPV4][level - 1]); + } + if (area->ipv6_circuits > 0) { + isis_print_frr_summary( + vty, + area->spftree[SPFTREE_IPV6][level - 1]); + } + if (isis_area_ipv6_dstsrc_enabled(area)) { + isis_print_frr_summary( + vty, area->spftree[SPFTREE_DSTSRC] + [level - 1]); + } + } + } +} + +DEFUN(show_isis_frr_summary, show_isis_frr_summary_cmd, + "show " PROTO_NAME + " [vrf ] fast-reroute summary" +#ifndef FABRICD + " []" +#endif + , + SHOW_STR PROTO_HELP VRF_FULL_CMD_HELP_STR + "IS-IS FRR information\n" + "FRR summary\n" +#ifndef FABRICD + "level-1 routes\n" + "level-2 routes\n" +#endif +) +{ + int levels; + struct isis *isis; + struct listnode *node; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx = 0; + + if (argv_find(argv, argc, "level-1", &idx)) + levels = ISIS_LEVEL1; + else if (argv_find(argv, argc, "level-2", &idx)) + levels = ISIS_LEVEL2; + else + levels = ISIS_LEVEL1 | ISIS_LEVEL2; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + ISIS_FIND_VRF_ARGS(argv, argc, idx, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + show_isis_frr_summary_common(vty, levels, isis); + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) + show_isis_frr_summary_common(vty, levels, isis); + + return CMD_SUCCESS; +} + +void isis_spf_init(void) +{ +#ifndef FABRICD + install_element(VIEW_NODE, &show_isis_flex_algo_cmd); +#endif /* ifndef FABRICD */ + install_element(VIEW_NODE, &show_isis_topology_cmd); + install_element(VIEW_NODE, &show_isis_route_cmd); + install_element(VIEW_NODE, &show_isis_frr_summary_cmd); + + /* Register hook(s). */ + hook_register(isis_adj_state_change_hook, spf_adj_state_change); +} + +void isis_spf_print(struct isis_spftree *spftree, struct vty *vty) +{ + uint64_t last_run_duration = spftree->last_run_duration; + + vty_out(vty, " last run elapsed : "); + vty_out_timestr(vty, spftree->last_run_timestamp); + vty_out(vty, "\n"); + + vty_out(vty, " last run duration : %" PRIu64 " usec\n", + last_run_duration); + + vty_out(vty, " run count : %u\n", spftree->runcount); +} +void isis_spf_print_json(struct isis_spftree *spftree, struct json_object *json) +{ + char uptime[MONOTIME_STRLEN]; + time_t cur; + cur = time(NULL); + cur -= spftree->last_run_timestamp; + frrtime_to_interval(cur, uptime, sizeof(uptime)); + json_object_string_add(json, "last-run-elapsed", uptime); + json_object_int_add(json, "last-run-duration-usec", + spftree->last_run_duration); + json_object_int_add(json, "last-run-count", spftree->runcount); +} diff --git a/isisd/isis_spf.h b/isisd/isis_spf.h new file mode 100644 index 0000000..7e9754d --- /dev/null +++ b/isisd/isis_spf.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_spf.h + * IS-IS Shortest Path First algorithm + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef _ZEBRA_ISIS_SPF_H +#define _ZEBRA_ISIS_SPF_H + +#include "isisd/isis_lfa.h" +#include "lib/json.h" + +struct isis_spftree; + +enum spf_type { + SPF_TYPE_FORWARD = 1, + SPF_TYPE_REVERSE, + SPF_TYPE_RLFA, + SPF_TYPE_TI_LFA, +}; + +struct isis_spf_adj { + uint8_t id[ISIS_SYS_ID_LEN + 1]; + struct isis_adjacency *adj; + uint32_t metric; + struct isis_ext_subtlvs *subtlvs; + struct isis_lsp *lsp; + struct { + uint8_t desig_is_id[ISIS_SYS_ID_LEN + 1]; + } lan; + uint8_t flags; +#define F_ISIS_SPF_ADJ_BROADCAST 0x01 +#define F_ISIS_SPF_ADJ_OLDMETRIC 0x02 +#define F_ISIS_SPF_ADJ_METRIC_INFINITY 0x04 +}; + +struct isis_spftree * +isis_spftree_new(struct isis_area *area, struct lspdb_head *lspdb, + const uint8_t *sysid, int level, enum spf_tree_id tree_id, + enum spf_type type, uint8_t flags, uint8_t algorithm); +struct isis_vertex *isis_spf_prefix_sid_lookup(struct isis_spftree *spftree, + struct isis_prefix_sid *psid); +void isis_spf_invalidate_routes(struct isis_spftree *tree); +void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees, + int tree); +void isis_spf_switchover_routes(struct isis_area *area, + struct isis_spftree **trees, int family, + union g_addr *nexthop_ip, ifindex_t ifindex, + int level); +void isis_spftree_del(struct isis_spftree *spftree); +void spftree_area_init(struct isis_area *area); +void spftree_area_del(struct isis_area *area); +struct isis_lsp *isis_root_system_lsp(struct lspdb_head *lspdb, + const uint8_t *sysid); +#define isis_spf_schedule(area, level) \ + _isis_spf_schedule((area), (level), __func__, \ + __FILE__, __LINE__) +int _isis_spf_schedule(struct isis_area *area, int level, + const char *func, const char *file, int line); +void isis_print_spftree(struct vty *vty, struct isis_spftree *spftree); +void isis_print_routes(struct vty *vty, struct isis_spftree *spftree, + json_object **json, bool prefix_sid, bool backup); +void isis_spf_init(void); +void isis_spf_print(struct isis_spftree *spftree, struct vty *vty); +void isis_spf_print_json(struct isis_spftree *spftree, + struct json_object *json); +void isis_run_spf(struct isis_spftree *spftree); +struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area, + uint8_t *sysid, + struct isis_spftree *spftree); + +void isis_spf_timer_free(void *run); +#endif /* _ZEBRA_ISIS_SPF_H */ diff --git a/isisd/isis_spf_private.h b/isisd/isis_spf_private.h new file mode 100644 index 0000000..7636730 --- /dev/null +++ b/isisd/isis_spf_private.h @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_spf_private.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2017 Christian Franke + */ +#ifndef ISIS_SPF_PRIVATE_H +#define ISIS_SPF_PRIVATE_H + +#include "hash.h" +#include "jhash.h" +#include "skiplist.h" +#include "lib_errors.h" + +enum vertextype { + VTYPE_PSEUDO_IS = 1, + VTYPE_PSEUDO_TE_IS, + VTYPE_NONPSEUDO_IS, + VTYPE_NONPSEUDO_TE_IS, + VTYPE_ES, + VTYPE_IPREACH_INTERNAL, + VTYPE_IPREACH_EXTERNAL, + VTYPE_IPREACH_TE, + VTYPE_IP6REACH_INTERNAL, + VTYPE_IP6REACH_EXTERNAL +}; + +#define VTYPE_IS(t) ((t) >= VTYPE_PSEUDO_IS && (t) <= VTYPE_NONPSEUDO_TE_IS) +#define VTYPE_ES(t) ((t) == VTYPE_ES) +#define VTYPE_IP(t) ((t) >= VTYPE_IPREACH_INTERNAL && (t) <= VTYPE_IP6REACH_EXTERNAL) + +struct prefix_pair { + struct prefix dest; + struct prefix_ipv6 src; +}; + +struct isis_vertex_adj { + struct isis_spf_adj *sadj; + struct isis_sr_psid_info sr; + struct mpls_label_stack *label_stack; + uint32_t lfa_metric; +}; + +/* + * Triple + */ +struct isis_vertex { + enum vertextype type; + union { + uint8_t id[ISIS_SYS_ID_LEN + 1]; + struct { + struct prefix_pair p; + struct isis_sr_psid_info sr; + enum spf_prefix_priority priority; + } ip; + } N; + uint32_t d_N; /* d(N) Distance from this IS */ + uint16_t depth; /* The depth in the imaginary tree */ + struct list *Adj_N; /* {Adj(N)} next hop or neighbor list */ + struct list *parents; /* list of parents for ECMP */ + struct hash *firsthops; /* first two hops to neighbor */ + uint64_t insert_counter; + uint8_t flags; +}; +#define F_ISIS_VERTEX_LFA_PROTECTED 0x01 + +/* Vertex Queue and associated functions */ + +struct isis_vertex_queue { + union { + struct skiplist *slist; + struct list *list; + } l; + struct hash *hash; + uint64_t insert_counter; +}; + +__attribute__((__unused__)) +static unsigned isis_vertex_queue_hash_key(const void *vp) +{ + const struct isis_vertex *vertex = vp; + + if (VTYPE_IP(vertex->type)) { + uint32_t key; + + key = prefix_hash_key(&vertex->N.ip.p.dest); + key = jhash_1word(prefix_hash_key(&vertex->N.ip.p.src), key); + return key; + } + + return jhash(vertex->N.id, ISIS_SYS_ID_LEN + 1, 0x55aa5a5a); +} + +__attribute__((__unused__)) +static bool isis_vertex_queue_hash_cmp(const void *a, const void *b) +{ + const struct isis_vertex *va = a, *vb = b; + + if (va->type != vb->type) + return false; + + if (VTYPE_IP(va->type)) { + if (prefix_cmp(&va->N.ip.p.dest, &vb->N.ip.p.dest)) + return false; + + return prefix_cmp((const struct prefix *)&va->N.ip.p.src, + (const struct prefix *)&vb->N.ip.p.src) + == 0; + } + + return memcmp(va->N.id, vb->N.id, ISIS_SYS_ID_LEN + 1) == 0; +} + +/* + * Compares vertizes for sorting in the TENT list. Returns true + * if candidate should be considered before current, false otherwise. + */ +__attribute__((__unused__)) static int isis_vertex_queue_tent_cmp(const void *a, + const void *b) +{ + const struct isis_vertex *va = a; + const struct isis_vertex *vb = b; + + if (va->d_N < vb->d_N) + return -1; + + if (va->d_N > vb->d_N) + return 1; + + if (va->type < vb->type) + return -1; + + if (va->type > vb->type) + return 1; + + if (va->insert_counter < vb->insert_counter) + return -1; + + if (va->insert_counter > vb->insert_counter) + return 1; + + return 0; +} + +__attribute__((__unused__)) +static struct skiplist *isis_vertex_queue_skiplist(void) +{ + return skiplist_new(0, isis_vertex_queue_tent_cmp, NULL); +} + +__attribute__((__unused__)) +static void isis_vertex_queue_init(struct isis_vertex_queue *queue, + const char *name, bool ordered) +{ + if (ordered) { + queue->insert_counter = 1; + queue->l.slist = isis_vertex_queue_skiplist(); + } else { + queue->insert_counter = 0; + queue->l.list = list_new(); + } + queue->hash = hash_create(isis_vertex_queue_hash_key, + isis_vertex_queue_hash_cmp, name); +} + +void isis_vertex_del(struct isis_vertex *vertex); + +bool isis_vertex_adj_exists(const struct isis_spftree *spftree, + const struct isis_vertex *vertex, + const struct isis_spf_adj *sadj); +void isis_vertex_adj_free(void *arg); +struct isis_vertex_adj * +isis_vertex_adj_add(struct isis_spftree *spftree, struct isis_vertex *vertex, + struct list *vadj_list, struct isis_spf_adj *sadj, + struct isis_prefix_sid *psid, bool last_hop); + +__attribute__((__unused__)) +static void isis_vertex_queue_clear(struct isis_vertex_queue *queue) +{ + hash_clean(queue->hash, NULL); + + if (queue->insert_counter) { + struct isis_vertex *vertex; + while (0 == skiplist_first(queue->l.slist, NULL, + (void **)&vertex)) { + isis_vertex_del(vertex); + skiplist_delete_first(queue->l.slist); + } + queue->insert_counter = 1; + } else { + queue->l.list->del = (void (*)(void *))isis_vertex_del; + list_delete_all_node(queue->l.list); + queue->l.list->del = NULL; + } +} + +__attribute__((__unused__)) +static void isis_vertex_queue_free(struct isis_vertex_queue *queue) +{ + isis_vertex_queue_clear(queue); + + hash_free(queue->hash); + queue->hash = NULL; + + if (queue->insert_counter) { + skiplist_free(queue->l.slist); + queue->l.slist = NULL; + } else + list_delete(&queue->l.list); +} + +__attribute__((__unused__)) +static unsigned int isis_vertex_queue_count(struct isis_vertex_queue *queue) +{ + return hashcount(queue->hash); +} + +__attribute__((__unused__)) +static void isis_vertex_queue_append(struct isis_vertex_queue *queue, + struct isis_vertex *vertex) +{ + assert(!queue->insert_counter); + + listnode_add(queue->l.list, vertex); + + struct isis_vertex *inserted; + + inserted = hash_get(queue->hash, vertex, hash_alloc_intern); + assert(inserted == vertex); +} + +__attribute__((__unused__)) +static struct isis_vertex *isis_vertex_queue_last(struct isis_vertex_queue *queue) +{ + struct listnode *tail; + + assert(!queue->insert_counter); + tail = listtail(queue->l.list); + assert(tail); + return listgetdata(tail); +} + +__attribute__((__unused__)) +static void isis_vertex_queue_insert(struct isis_vertex_queue *queue, + struct isis_vertex *vertex) +{ + assert(queue->insert_counter); + vertex->insert_counter = queue->insert_counter++; + assert(queue->insert_counter != (uint64_t)-1); + + skiplist_insert(queue->l.slist, vertex, vertex); + + struct isis_vertex *inserted; + inserted = hash_get(queue->hash, vertex, hash_alloc_intern); + assert(inserted == vertex); +} + +__attribute__((__unused__)) +static struct isis_vertex * +isis_vertex_queue_pop(struct isis_vertex_queue *queue) +{ + assert(queue->insert_counter); + + struct isis_vertex *rv; + + if (skiplist_first(queue->l.slist, NULL, (void **)&rv)) + return NULL; + + skiplist_delete_first(queue->l.slist); + hash_release(queue->hash, rv); + + return rv; +} + +__attribute__((__unused__)) +static void isis_vertex_queue_delete(struct isis_vertex_queue *queue, + struct isis_vertex *vertex) +{ + assert(queue->insert_counter); + + skiplist_delete(queue->l.slist, vertex, vertex); + hash_release(queue->hash, vertex); +} + +#define ALL_QUEUE_ELEMENTS_RO(queue, node, data) \ + ALL_LIST_ELEMENTS_RO((queue)->l.list, node, data) + +/* End of vertex queue definitions */ + +struct isis_spftree { + struct isis_vertex_queue paths; /* the SPT */ + struct isis_vertex_queue tents; /* TENT */ + struct route_table *route_table; + struct route_table *route_table_backup; + struct lspdb_head *lspdb; /* link-state db */ + struct hash *prefix_sids; /* SR Prefix-SIDs. */ + struct list *sadj_list; + struct isis_spf_nodes adj_nodes; + struct isis_area *area; /* back pointer to area */ + unsigned int runcount; /* number of runs since uptime */ + time_t last_run_timestamp; /* last run timestamp as wall time for display */ + time_t last_run_monotime; /* last run as monotime for scheduling */ + time_t last_run_duration; /* last run duration in msec */ + + enum spf_type type; + uint8_t sysid[ISIS_SYS_ID_LEN]; + uint16_t mtid; + int family; + int level; + enum spf_tree_id tree_id; + struct { + /* Original pre-failure local SPTs. */ + struct { + struct isis_spftree *spftree; + struct isis_spftree *spftree_reverse; + } old; + + /* Protected resource. */ + struct lfa_protected_resource protected_resource; + + /* P-space and Q-space. */ + struct isis_spf_nodes p_space; + struct isis_spf_nodes q_space; + + /* Remote LFA related information. */ + struct { + /* List of RLFAs eligible to be installed. */ + struct rlfa_tree_head rlfas; + + /* + * RLFA post-convergence SPTs (needed to activate RLFAs + * once label information is received from LDP). + */ + struct list *pc_spftrees; + + /* RLFA maximum metric (or zero if absent). */ + uint32_t max_metric; + } remote; + + /* Protection counters. */ + struct { + uint32_t lfa[SPF_PREFIX_PRIO_MAX]; + uint32_t rlfa[SPF_PREFIX_PRIO_MAX]; + uint32_t tilfa[SPF_PREFIX_PRIO_MAX]; + uint32_t ecmp[SPF_PREFIX_PRIO_MAX]; + uint32_t total[SPF_PREFIX_PRIO_MAX]; + } protection_counters; + } lfa; + uint8_t algorithm; + uint8_t flags; +}; +#define F_SPFTREE_HOPCOUNT_METRIC 0x01 +#define F_SPFTREE_NO_ROUTES 0x02 +#define F_SPFTREE_NO_ADJACENCIES 0x04 +#ifndef FABRICD +/* flex-algo */ +#define F_SPFTREE_DISABLED 0x08 +#endif /* ifndef FABRICD */ + +__attribute__((__unused__)) +static void isis_vertex_id_init(struct isis_vertex *vertex, const void *id, + enum vertextype vtype) +{ + vertex->type = vtype; + + if (VTYPE_IS(vtype) || VTYPE_ES(vtype)) { + memcpy(vertex->N.id, id, ISIS_SYS_ID_LEN + 1); + } else if (VTYPE_IP(vtype)) { + memcpy(&vertex->N.ip.p, id, sizeof(vertex->N.ip.p)); + } else { + flog_err(EC_LIB_DEVELOPMENT, "Unknown Vertex Type"); + } +} + +__attribute__((__unused__)) +static struct isis_vertex *isis_find_vertex(struct isis_vertex_queue *queue, + const void *id, + enum vertextype vtype) +{ + struct isis_vertex querier; + + isis_vertex_id_init(&querier, id, vtype); + return hash_lookup(queue->hash, &querier); +} + +__attribute__((__unused__)) +static struct isis_lsp *lsp_for_vertex(struct isis_spftree *spftree, + struct isis_vertex *vertex) +{ + uint8_t lsp_id[ISIS_SYS_ID_LEN + 2]; + + assert(VTYPE_IS(vertex->type)); + + memcpy(lsp_id, vertex->N.id, ISIS_SYS_ID_LEN + 1); + LSP_FRAGMENT(lsp_id) = 0; + + struct isis_lsp *lsp = lsp_search(spftree->lspdb, lsp_id); + + if (lsp && lsp->hdr.rem_lifetime != 0) + return lsp; + + return NULL; +} + +#define VID2STR_BUFFER SRCDEST2STR_BUFFER +const char *vtype2string(enum vertextype vtype); +const char *vid2string(const struct isis_vertex *vertex, char *buff, int size); + +#endif diff --git a/isisd/isis_sr.c b/isisd/isis_sr.c new file mode 100644 index 0000000..f783038 --- /dev/null +++ b/isisd/isis_sr.c @@ -0,0 +1,1334 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing for IS-IS as per RFC 8667 + * + * Copyright (C) 2019 Orange http://www.orange.com + * + * Author: Olivier Dugeon + * Contributor: Renato Westphal for NetDEF + */ + +#include + +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "termtable.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "srcdest_table.h" +#include "vty.h" +#include "zclient.h" +#include "lib/lib_errors.h" + +#include "isisd/isisd.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_route.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_errors.h" + +/* Local variables and functions */ +DEFINE_MTYPE_STATIC(ISISD, ISIS_SR_INFO, "ISIS segment routing information"); + +static void sr_local_block_delete(struct isis_area *area); +static int sr_local_block_init(struct isis_area *area); +static void sr_adj_sid_update(struct sr_adjacency *sra, + struct sr_local_block *srlb); +static void sr_adj_sid_del(struct sr_adjacency *sra); + +/* --- RB-Tree Management functions ----------------------------------------- */ + +/** + * Configured SR Prefix comparison for RB-Tree. + * + * @param a First SR prefix + * @param b Second SR prefix + * + * @return -1 (a < b), 0 (a == b) or +1 (a > b) + */ +static inline int sr_prefix_sid_cfg_compare(const struct sr_prefix_cfg *a, + const struct sr_prefix_cfg *b) +{ + int ret; + + ret = prefix_cmp(&a->prefix, &b->prefix); + if (ret != 0) + return ret; + + ret = a->algorithm - b->algorithm; + if (ret != 0) + return ret; + + return 0; +} +DECLARE_RBTREE_UNIQ(srdb_prefix_cfg, struct sr_prefix_cfg, entry, + sr_prefix_sid_cfg_compare); + +/** + * Find SRGB associated to a System ID. + * + * @param area IS-IS LSP database + * @param sysid System ID to lookup + * + * @return Pointer to SRGB if found, NULL otherwise + */ +struct isis_sr_block *isis_sr_find_srgb(struct lspdb_head *lspdb, + const uint8_t *sysid) +{ + struct isis_lsp *lsp; + + lsp = isis_root_system_lsp(lspdb, sysid); + if (!lsp) + return NULL; + + if (!lsp->tlvs->router_cap + || lsp->tlvs->router_cap->srgb.range_size == 0) + return NULL; + + return &lsp->tlvs->router_cap->srgb; +} + +/** + * Compute input label for the given Prefix-SID. + * + * @param area IS-IS area + * @param psid IS-IS Prefix-SID Sub-TLV + * @param local Indicates whether the Prefix-SID is local or not + * + * @return MPLS label or MPLS_INVALID_LABEL in case of SRGB overflow + */ +mpls_label_t sr_prefix_in_label(struct isis_area *area, + struct isis_prefix_sid *psid, bool local) +{ + /* + * No need to assign a label for local Prefix-SIDs unless the no-PHP + * flag is set. + */ + if (local + && (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP) + || CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL))) + return MPLS_INVALID_LABEL; + + /* Return SID value as MPLS label if it is an Absolute SID */ + if (CHECK_FLAG(psid->flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) + return psid->value; + + /* Check that SID index falls inside the SRGB */ + if (psid->value >= (area->srdb.config.srgb_upper_bound + - area->srdb.config.srgb_lower_bound + 1)) { + flog_warn(EC_ISIS_SID_OVERFLOW, + "%s: SID index %u falls outside local SRGB range", + __func__, psid->value); + return MPLS_INVALID_LABEL; + } + + /* Return MPLS label as SID index + SRGB_lower_bound as per RFC 8667 */ + return (area->srdb.config.srgb_lower_bound + psid->value); +} + +/** + * Compute output label for the given Prefix-SID. + * + * @param lspdb IS-IS LSP database + * @param family Prefix-SID address family + * @param psid Prefix-SID Sub-TLV + * @param nh_sysid System ID of the nexthop node + * @param last_hop Indicates whether the nexthop node is the last hop + * + * @return MPLS label or MPLS_INVALID_LABEL in case of error + */ +mpls_label_t sr_prefix_out_label(struct lspdb_head *lspdb, int family, + struct isis_prefix_sid *psid, + const uint8_t *nh_sysid, bool last_hop) +{ + struct isis_sr_block *nh_srgb; + + if (last_hop) { + if (!CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP)) + return MPLS_LABEL_IMPLICIT_NULL; + + if (CHECK_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL)) { + if (family == AF_INET) + return MPLS_LABEL_IPV4_EXPLICIT_NULL; + else + return MPLS_LABEL_IPV6_EXPLICIT_NULL; + } + /* Fallthrough */ + } + + /* Return SID value as MPLS label if it is an Absolute SID */ + if (CHECK_FLAG(psid->flags, + ISIS_PREFIX_SID_VALUE | ISIS_PREFIX_SID_LOCAL)) { + /* + * V/L SIDs have local significance, so only adjacent routers + * can use them (RFC8667 section #2.1.1.1) + */ + if (!last_hop) + return MPLS_INVALID_LABEL; + return psid->value; + } + + /* Check that SID index falls inside the SRGB */ + nh_srgb = isis_sr_find_srgb(lspdb, nh_sysid); + if (!nh_srgb) + return MPLS_INVALID_LABEL; + + /* + * Check if the nexthop can handle SR-MPLS encapsulated IPv4 or + * IPv6 packets. + */ + if ((family == AF_INET && !IS_SR_IPV4(nh_srgb)) + || (family == AF_INET6 && !IS_SR_IPV6(nh_srgb))) + return MPLS_INVALID_LABEL; + + if (psid->value >= nh_srgb->range_size) { + flog_warn(EC_ISIS_SID_OVERFLOW, + "%s: SID index %u falls outside remote SRGB range", + __func__, psid->value); + return MPLS_INVALID_LABEL; + } + + /* Return MPLS label as SID index + SRGB_lower_bound as per RFC 8667 */ + return (nh_srgb->lower_bound + psid->value); +} + +/* --- Functions used for Yang model and CLI to configure Segment Routing --- */ + +/** + * Check if prefix correspond to a Node SID. + * + * @param ifp Interface + * @param prefix Prefix to be checked + * + * @return True if the interface/address pair corresponds to a Node-SID + */ +static bool sr_prefix_is_node_sid(const struct interface *ifp, + const struct prefix *prefix) +{ + return (if_is_loopback(ifp) && is_host_route(prefix)); +} + +/** + * Update local SRGB configuration. SRGB is reserved though Label Manager. + * This function trigger the update of local Prefix-SID installation. + * + * @param area IS-IS area + * @param lower_bound Lower bound of SRGB + * @param upper_bound Upper bound of SRGB + * + * @return 0 on success, -1 otherwise + */ +int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound) +{ + struct isis_sr_db *srdb = &area->srdb; + + sr_debug("ISIS-Sr (%s): Update SRGB with new range [%u/%u]", + area->area_tag, lower_bound, upper_bound); + + /* Just store new SRGB values if Label Manager is not available. + * SRGB will be configured later when SR start */ + if (!isis_zebra_label_manager_ready()) { + srdb->config.srgb_lower_bound = lower_bound; + srdb->config.srgb_upper_bound = upper_bound; + return 0; + } + + /* Label Manager is ready, start by releasing the old SRGB. */ + if (srdb->srgb_active) { + isis_zebra_release_label_range(srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound); + srdb->srgb_active = false; + } + + srdb->config.srgb_lower_bound = lower_bound; + srdb->config.srgb_upper_bound = upper_bound; + + if (srdb->enabled) { + /* then request new SRGB if SR is enabled. */ + if (isis_zebra_request_label_range( + srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1) < 0) { + srdb->srgb_active = false; + return -1; + } else + srdb->srgb_active = true; + + + sr_debug(" |- Got new SRGB [%u/%u]", + srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound); + + lsp_regenerate_schedule(area, area->is_type, 0); + } else if (srdb->config.enabled) { + /* Try to enable SR again using the new SRGB. */ + isis_sr_start(area); + } + + return 0; +} + +/** + * Update Segment Routing Local Block range which is reserved though the + * Label Manager. This function trigger the update of local Adjacency-SID + * installation. + * + * @param area IS-IS area + * @param lower_bound Lower bound of SRLB + * @param upper_bound Upper bound of SRLB + * + * @return 0 on success, -1 otherwise + */ +int isis_sr_cfg_srlb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound) +{ + struct isis_sr_db *srdb = &area->srdb; + struct listnode *node; + struct sr_adjacency *sra; + + sr_debug("ISIS-Sr (%s): Update SRLB with new range [%u/%u]", + area->area_tag, lower_bound, upper_bound); + + /* Just store new SRLB values if Label Manager is not available. + * SRLB will be configured later when SR start */ + if (!isis_zebra_label_manager_ready()) { + srdb->config.srlb_lower_bound = lower_bound; + srdb->config.srlb_upper_bound = upper_bound; + return 0; + } + + /* LM is ready, start by deleting the old SRLB */ + sr_local_block_delete(area); + + srdb->config.srlb_lower_bound = lower_bound; + srdb->config.srlb_upper_bound = upper_bound; + + if (srdb->enabled) { + /* Initialize new SRLB */ + if (sr_local_block_init(area) != 0) + return -1; + + /* Reinstall local Adjacency-SIDs with new labels. */ + for (ALL_LIST_ELEMENTS_RO(area->srdb.adj_sids, node, sra)) + sr_adj_sid_update(sra, &srdb->srlb); + + /* Update and Flood LSP */ + lsp_regenerate_schedule(area, area->is_type, 0); + } else if (srdb->config.enabled) { + /* Try to enable SR again using the new SRLB. */ + isis_sr_start(area); + } + + return 0; +} + +/** + * Add new Prefix-SID configuration to the SRDB. + * + * @param area IS-IS area + * @param prefix Prefix to be added + * + * @return Newly added Prefix-SID configuration structure + */ +struct sr_prefix_cfg *isis_sr_cfg_prefix_add(struct isis_area *area, + const struct prefix *prefix, + uint8_t algorithm) +{ + struct sr_prefix_cfg *pcfg; + struct interface *ifp; + + sr_debug("ISIS-Sr (%s): Add local prefix %pFX", area->area_tag, prefix); + + pcfg = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*pcfg)); + pcfg->prefix = *prefix; + pcfg->area = area; + pcfg->algorithm = algorithm; + + /* Pull defaults from the YANG module. */ + pcfg->sid_type = yang_get_default_enum( + "%s/prefix-sid-map/prefix-sid/sid-value-type", ISIS_SR); + pcfg->last_hop_behavior = yang_get_default_enum( + "%s/prefix-sid-map/prefix-sid/last-hop-behavior", ISIS_SR); + + /* Mark as node Sid if the prefix is host and configured in loopback */ + ifp = if_lookup_prefix(prefix, VRF_DEFAULT); + if (ifp && sr_prefix_is_node_sid(ifp, prefix)) + pcfg->node_sid = true; + + /* Save prefix-sid configuration. */ + srdb_prefix_cfg_add(&area->srdb.config.prefix_sids, pcfg); + + return pcfg; +} + +/** + * Removal of locally configured Prefix-SID. + * + * @param pcfg Configured Prefix-SID + */ +void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg) +{ + struct isis_area *area = pcfg->area; + + sr_debug("ISIS-Sr (%s): Delete local Prefix-SID %pFX %s %u", + area->area_tag, &pcfg->prefix, + pcfg->sid_type == SR_SID_VALUE_TYPE_INDEX ? "index" : "label", + pcfg->sid); + + srdb_prefix_cfg_del(&area->srdb.config.prefix_sids, pcfg); + XFREE(MTYPE_ISIS_SR_INFO, pcfg); +} + +/** + * Lookup for Prefix-SID in the local configuration. + * + * @param area IS-IS area + * @param prefix Prefix to lookup + * + * @return Configured Prefix-SID structure if found, NULL otherwise + */ +struct sr_prefix_cfg *isis_sr_cfg_prefix_find(struct isis_area *area, + union prefixconstptr prefix, + uint8_t algorithm) +{ + struct sr_prefix_cfg pcfg = {}; + + prefix_copy(&pcfg.prefix, prefix.p); + pcfg.algorithm = algorithm; + return srdb_prefix_cfg_find(&area->srdb.config.prefix_sids, &pcfg); +} + +/** + * Fill in Prefix-SID Sub-TLV according to the corresponding configuration. + * + * @param pcfg Prefix-SID configuration + * @param external False if prefix is locally configured, true otherwise + * @param psid Prefix-SID sub-TLV to be updated + */ +void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg, bool external, + struct isis_prefix_sid *psid) +{ + /* Set SID algorithm. */ + psid->algorithm = pcfg->algorithm; + + /* Set SID flags. */ + psid->flags = 0; + switch (pcfg->last_hop_behavior) { + case SR_LAST_HOP_BEHAVIOR_EXP_NULL: + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + SET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + case SR_LAST_HOP_BEHAVIOR_NO_PHP: + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + case SR_LAST_HOP_BEHAVIOR_PHP: + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_NO_PHP); + UNSET_FLAG(psid->flags, ISIS_PREFIX_SID_EXPLICIT_NULL); + break; + } + if (external) + SET_FLAG(psid->flags, ISIS_PREFIX_SID_READVERTISED); + if (pcfg->node_sid && !pcfg->n_flag_clear) + SET_FLAG(psid->flags, ISIS_PREFIX_SID_NODE); + + /* Set SID value. */ + psid->value = pcfg->sid; + if (pcfg->sid_type == SR_SID_VALUE_TYPE_ABSOLUTE) { + SET_FLAG(psid->flags, ISIS_PREFIX_SID_VALUE); + SET_FLAG(psid->flags, ISIS_PREFIX_SID_LOCAL); + } +} + +/** + * Delete all backup Adj-SIDs. + * + * @param area IS-IS area + * @param level IS-IS level + */ +void isis_area_delete_backup_adj_sids(struct isis_area *area, int level) +{ + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(area->srdb.adj_sids, node, nnode, sra)) + if (sra->type == ISIS_SR_ADJ_BACKUP && (sra->adj->level & level)) + sr_adj_sid_del(sra); +} + +/* --- Segment Routing Local Block management functions --------------------- */ + +/** + * Initialize Segment Routing Local Block from SRDB configuration and reserve + * block of bits to manage label allocation. + * + * @param area IS-IS area + */ +static int sr_local_block_init(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct sr_local_block *srlb = &srdb->srlb; + + /* Check if SRLB is not already configured */ + if (srlb->active) + return 0; + + /* + * Request SRLB to the label manager. If the allocation fails, return + * an error to disable SR until a new SRLB is successfully allocated. + */ + if (isis_zebra_request_label_range( + srdb->config.srlb_lower_bound, + srdb->config.srlb_upper_bound + - srdb->config.srlb_lower_bound + 1)) { + srlb->active = false; + return -1; + } + + sr_debug("ISIS-Sr (%s): Got new SRLB [%u/%u]", area->area_tag, + srdb->config.srlb_lower_bound, srdb->config.srlb_upper_bound); + + /* Initialize the SRLB */ + srlb->start = srdb->config.srlb_lower_bound; + srlb->end = srdb->config.srlb_upper_bound; + srlb->current = 0; + /* Compute the needed Used Mark number and allocate them */ + srlb->max_block = (srlb->end - srlb->start + 1) / SRLB_BLOCK_SIZE; + if (((srlb->end - srlb->start + 1) % SRLB_BLOCK_SIZE) != 0) + srlb->max_block++; + srlb->used_mark = XCALLOC(MTYPE_ISIS_SR_INFO, + srlb->max_block * SRLB_BLOCK_SIZE); + srlb->active = true; + + return 0; +} + +/** + * Remove Segment Routing Local Block. + * + * @param area IS-IS area + */ +static void sr_local_block_delete(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct sr_local_block *srlb = &srdb->srlb; + + /* Check if SRLB is not already delete */ + if (!srlb->active) + return; + + sr_debug("ISIS-Sr (%s): Remove SRLB [%u/%u]", area->area_tag, + srlb->start, srlb->end); + + /* First release the label block */ + isis_zebra_release_label_range(srdb->config.srlb_lower_bound, + srdb->config.srlb_upper_bound); + + /* Then reset SRLB structure */ + if (srlb->used_mark != NULL) + XFREE(MTYPE_ISIS_SR_INFO, srlb->used_mark); + srlb->active = false; +} + +/** + * Request a label from the Segment Routing Local Block. + * + * @param srlb Segment Routing Local Block + * + * @return First available label on success or MPLS_INVALID_LABEL if the + * block of labels is full + */ +static mpls_label_t sr_local_block_request_label(struct sr_local_block *srlb) +{ + mpls_label_t label; + uint32_t index; + uint32_t pos; + uint32_t size = srlb->end - srlb->start + 1; + + /* Check if we ran out of available labels */ + if (srlb->current >= size) + return MPLS_INVALID_LABEL; + + /* Get first available label and mark it used */ + label = srlb->current + srlb->start; + index = srlb->current / SRLB_BLOCK_SIZE; + pos = 1ULL << (srlb->current % SRLB_BLOCK_SIZE); + srlb->used_mark[index] |= pos; + + /* Jump to the next free position */ + srlb->current++; + pos = srlb->current % SRLB_BLOCK_SIZE; + while (srlb->current < size) { + if (pos == 0) + index++; + if (!((1ULL << pos) & srlb->used_mark[index])) + break; + else { + srlb->current++; + pos = srlb->current % SRLB_BLOCK_SIZE; + } + } + + if (srlb->current == size) + zlog_warn( + "SR: Warning, SRLB is depleted and next label request will fail"); + + return label; +} + +/** + * Release label in the Segment Routing Local Block. + * + * @param srlb Segment Routing Local Block + * @param label Label to be release + * + * @return 0 on success or -1 if label falls outside SRLB + */ +static int sr_local_block_release_label(struct sr_local_block *srlb, + mpls_label_t label) +{ + uint32_t index; + uint32_t pos; + + /* Check that label falls inside the SRLB */ + if ((label < srlb->start) || (label > srlb->end)) { + flog_warn(EC_ISIS_SID_OVERFLOW, + "%s: Returning label %u is outside SRLB [%u/%u]", + __func__, label, srlb->start, srlb->end); + return -1; + } + + index = (label - srlb->start) / SRLB_BLOCK_SIZE; + pos = 1ULL << ((label - srlb->start) % SRLB_BLOCK_SIZE); + srlb->used_mark[index] &= ~pos; + /* Reset current to the first available position */ + for (index = 0; index < srlb->max_block; index++) { + if (srlb->used_mark[index] != 0xFFFFFFFFFFFFFFFF) { + for (pos = 0; pos < SRLB_BLOCK_SIZE; pos++) + if (!((1ULL << pos) & srlb->used_mark[index])) { + srlb->current = + index * SRLB_BLOCK_SIZE + pos; + break; + } + break; + } + } + + return 0; +} + +/* --- Segment Routing Adjacency-SID management functions ------------------- */ + +/** + * Add new local Adjacency-SID. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param backup True to initialize backup Adjacency SID + * @param nexthops List of backup nexthops (for backup Adj-SIDs only) + */ +void sr_adj_sid_add_single(struct isis_adjacency *adj, int family, bool backup, + struct list *nexthops) +{ + struct isis_circuit *circuit = adj->circuit; + struct isis_area *area = circuit->area; + struct sr_adjacency *sra; + struct isis_adj_sid *adj_sid; + struct isis_lan_adj_sid *ladj_sid; + union g_addr nexthop = {}; + uint8_t flags; + mpls_label_t input_label; + + sr_debug("ISIS-Sr (%s): Add %s Adjacency SID", area->area_tag, + backup ? "Backup" : "Primary"); + + /* Determine nexthop IP address */ + switch (family) { + case AF_INET: + if (!circuit->ip_router || !adj->ipv4_address_count) + return; + + nexthop.ipv4 = adj->ipv4_addresses[0]; + break; + case AF_INET6: + if (!circuit->ipv6_router || !adj->ll_ipv6_count) + return; + + nexthop.ipv6 = adj->ll_ipv6_addrs[0]; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unexpected address-family: %u", __func__, family); + exit(1); + } + + /* Prepare Segment Routing Adjacency as per RFC8667 section #2.2 */ + flags = EXT_SUBTLV_LINK_ADJ_SID_VFLG | EXT_SUBTLV_LINK_ADJ_SID_LFLG; + if (family == AF_INET6) + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_FFLG); + if (backup) + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_BFLG); + + /* Get a label from the SRLB for this Adjacency */ + input_label = sr_local_block_request_label(&area->srdb.srlb); + if (input_label == MPLS_INVALID_LABEL) + return; + + if (circuit->ext == NULL) + circuit->ext = isis_alloc_ext_subtlvs(); + + sra = XCALLOC(MTYPE_ISIS_SR_INFO, sizeof(*sra)); + sra->type = backup ? ISIS_SR_ADJ_BACKUP : ISIS_SR_ADJ_NORMAL; + sra->input_label = input_label; + sra->nexthop.family = family; + sra->nexthop.address = nexthop; + + if (backup && nexthops) { + struct isis_vertex_adj *vadj; + struct listnode *node; + + sra->backup_nexthops = list_new(); + for (ALL_LIST_ELEMENTS_RO(nexthops, node, vadj)) { + struct isis_adjacency *adj = vadj->sadj->adj; + struct mpls_label_stack *label_stack; + + label_stack = vadj->label_stack; + adjinfo2nexthop(family, sra->backup_nexthops, adj, NULL, + label_stack); + } + } + + switch (circuit->circ_type) { + /* LAN Adjacency-SID for Broadcast interface section #2.2.2 */ + case CIRCUIT_T_BROADCAST: + ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid)); + ladj_sid->family = family; + ladj_sid->flags = flags; + ladj_sid->weight = 0; + memcpy(ladj_sid->neighbor_id, adj->sysid, + sizeof(ladj_sid->neighbor_id)); + ladj_sid->sid = input_label; + isis_tlvs_add_lan_adj_sid(circuit->ext, ladj_sid); + sra->u.ladj_sid = ladj_sid; + break; + /* Adjacency-SID for Point to Point interface section #2.2.1 */ + case CIRCUIT_T_P2P: + adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid)); + adj_sid->family = family; + adj_sid->flags = flags; + adj_sid->weight = 0; + adj_sid->sid = input_label; + isis_tlvs_add_adj_sid(circuit->ext, adj_sid); + sra->u.adj_sid = adj_sid; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + + /* Add Adjacency-SID in SRDB */ + sra->adj = adj; + listnode_add(area->srdb.adj_sids, sra); + listnode_add(adj->adj_sids, sra); + + isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_ADD, sra); +} + +/** + * Add Primary and Backup local Adjacency SID. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + */ +static void sr_adj_sid_add(struct isis_adjacency *adj, int family) +{ + sr_adj_sid_add_single(adj, family, false, NULL); +} + +static void sr_adj_sid_update(struct sr_adjacency *sra, + struct sr_local_block *srlb) +{ + struct isis_circuit *circuit = sra->adj->circuit; + + /* First remove the old MPLS Label */ + isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_DELETE, sra); + + /* Got new label in the new SRLB */ + sra->input_label = sr_local_block_request_label(srlb); + if (sra->input_label == MPLS_INVALID_LABEL) + return; + + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + sra->u.ladj_sid->sid = sra->input_label; + break; + case CIRCUIT_T_P2P: + sra->u.adj_sid->sid = sra->input_label; + break; + default: + flog_warn(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + break; + } + + /* Finally configure the new MPLS Label */ + isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_ADD, sra); +} + +/** + * Delete local Adj-SID. + * + * @param sra Segment Routing Adjacency + */ +static void sr_adj_sid_del(struct sr_adjacency *sra) +{ + struct isis_circuit *circuit = sra->adj->circuit; + struct isis_area *area = circuit->area; + + sr_debug("ISIS-Sr (%s): Delete Adjacency SID", area->area_tag); + + isis_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_DELETE, sra); + + /* Release dynamic label and remove subTLVs */ + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + sr_local_block_release_label(&area->srdb.srlb, + sra->u.ladj_sid->sid); + isis_tlvs_del_lan_adj_sid(circuit->ext, sra->u.ladj_sid); + break; + case CIRCUIT_T_P2P: + sr_local_block_release_label(&area->srdb.srlb, + sra->u.adj_sid->sid); + isis_tlvs_del_adj_sid(circuit->ext, sra->u.adj_sid); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + + if (sra->type == ISIS_SR_ADJ_BACKUP && sra->backup_nexthops) { + sra->backup_nexthops->del = + (void (*)(void *))isis_nexthop_delete; + list_delete(&sra->backup_nexthops); + } + + /* Remove Adjacency-SID from the SRDB */ + listnode_delete(area->srdb.adj_sids, sra); + listnode_delete(sra->adj->adj_sids, sra); + XFREE(MTYPE_ISIS_SR_INFO, sra); +} + +/** + * Lookup Segment Routing Adj-SID by family and type. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param type Adjacency SID type + */ +struct sr_adjacency *isis_sr_adj_sid_find(struct isis_adjacency *adj, + int family, enum sr_adj_type type) +{ + struct sr_adjacency *sra; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adj->adj_sids, node, sra)) + if (sra->nexthop.family == family && sra->type == type) + return sra; + + return NULL; +} + +/** + * Remove all Adjacency-SIDs associated to an adjacency that is going down. + * + * @param adj IS-IS Adjacency + * + * @return 0 + */ +static int sr_adj_state_change(struct isis_adjacency *adj) +{ + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srdb.enabled) + return 0; + + if (adj->adj_state == ISIS_ADJ_UP) + return 0; + + for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra)) + sr_adj_sid_del(sra); + + return 0; +} + +/** + * When IS-IS Adjacency got one or more IPv4/IPv6 addresses, add new IPv4 or + * IPv6 address to corresponding Adjacency-SID accordingly. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param global Indicate if it concerns the Local or Global IPv6 addresses + * + * @return 0 + */ +static int sr_adj_ip_enabled(struct isis_adjacency *adj, int family, + bool global) +{ + if (!adj->circuit->area->srdb.enabled || global) + return 0; + + sr_adj_sid_add(adj, family); + + return 0; +} + +/** + * When IS-IS Adjacency doesn't have any IPv4 or IPv6 addresses anymore, + * delete the corresponding Adjacency-SID(s) accordingly. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param global Indicate if it concerns the Local or Global IPv6 addresses + * + * @return 0 + */ +static int sr_adj_ip_disabled(struct isis_adjacency *adj, int family, + bool global) +{ + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srdb.enabled || global) + return 0; + + for (ALL_LIST_ELEMENTS(adj->adj_sids, node, nnode, sra)) + if (sra->nexthop.family == family) + sr_adj_sid_del(sra); + + return 0; +} + +/** + * Update the Node-SID flag of the configured Prefix-SID mappings in response + * to an address addition or removal event. + * + * @param ifp Interface + * + * @return 0 + */ +int sr_if_addr_update(struct interface *ifp) +{ + struct sr_prefix_cfg *pcfgs[SR_ALGORITHM_COUNT] = {NULL}; + struct isis_circuit *circuit; + struct isis_area *area; + struct connected *connected; + bool need_lsp_regenerate = false; + + /* Get corresponding circuit */ + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + return 0; + + area = circuit->area; + if (!area) + return 0; + + frr_each (if_connected, ifp->connected, connected) { + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + pcfgs[i] = isis_sr_cfg_prefix_find( + area, connected->address, i); + + if (!pcfgs[i]) + continue; + + if (sr_prefix_is_node_sid(ifp, &pcfgs[i]->prefix)) { + pcfgs[i]->node_sid = true; + need_lsp_regenerate = true; + } + } + } + + if (need_lsp_regenerate) + lsp_regenerate_schedule(area, area->is_type, 0); + + return 0; +} + +/** + * Show LFIB operation in human readable format. + * + * @param buf Buffer to store string output. Must be pre-allocate + * @param size Size of the buffer + * @param label_in Input Label + * @param label_out Output Label + * + * @return String containing LFIB operation in human readable format + */ +char *sr_op2str(char *buf, size_t size, mpls_label_t label_in, + mpls_label_t label_out) +{ + if (size < 24) + return NULL; + + if (label_in == MPLS_INVALID_LABEL) { + snprintf(buf, size, "no-op."); + return buf; + } + + switch (label_out) { + case MPLS_LABEL_IMPLICIT_NULL: + snprintf(buf, size, "Pop(%u)", label_in); + break; + case MPLS_LABEL_IPV4_EXPLICIT_NULL: + case MPLS_LABEL_IPV6_EXPLICIT_NULL: + snprintf(buf, size, "Swap(%u, null)", label_in); + break; + case MPLS_INVALID_LABEL: + snprintf(buf, size, "no-op."); + break; + default: + snprintf(buf, size, "Swap(%u, %u)", label_in, label_out); + break; + } + return buf; +} + +/** + * Show Segment Routing Node. + * + * @param vty VTY output + * @param area IS-IS area + * @param level IS-IS level + */ +static void show_node(struct vty *vty, struct isis_area *area, int level, + uint8_t algo) +{ + struct isis_lsp *lsp; + struct ttable *tt; + char buf[128]; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "System ID|SRGB|SRLB|Algorithm|MSD"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + frr_each (lspdb, &area->lspdb[level - 1], lsp) { + struct isis_router_cap *cap; + + if (!lsp->tlvs) + continue; + cap = lsp->tlvs->router_cap; + if (!cap) + continue; + if (cap->algo[algo] == SR_ALGORITHM_UNSET) + continue; + + if (cap->algo[algo] == SR_ALGORITHM_SPF) + snprintf(buf, sizeof(buf), "SPF"); + else if (cap->algo[algo] == SR_ALGORITHM_STRICT_SPF) + snprintf(buf, sizeof(buf), "S-SPF"); +#ifndef FABRICD + else + snprintf(buf, sizeof(buf), "Flex-Algo %d", algo); +#endif /* ifndef FABRICD */ + + ttable_add_row(tt, "%pSY|%u - %u|%u - %u|%s|%u", + lsp->hdr.lsp_id, cap->srgb.lower_bound, + cap->srgb.lower_bound + cap->srgb.range_size - 1, + cap->srlb.lower_bound, + cap->srlb.lower_bound + cap->srlb.range_size - 1, + buf, cap->msd); + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + vty_out(vty, " IS-IS %s SR-Nodes:\n\n", circuit_t2string(level)); + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } + ttable_del(tt); +} + +DEFUN(show_sr_node, show_sr_node_cmd, + "show " PROTO_NAME + " segment-routing node" +#ifndef FABRICD + " [algorithm [(128-255)]]" +#endif /* ifndef FABRICD */ + , + SHOW_STR PROTO_HELP + "Segment-Routing\n" + "Segment-Routing node\n" +#ifndef FABRICD + "Show Flex-algo nodes\n" + "Algorithm number\n" +#endif /* ifndef FABRICD */ +) +{ + struct listnode *node, *inode; + struct isis_area *area; + uint16_t algorithm = SR_ALGORITHM_SPF; + bool all_algorithm = false; + struct isis *isis; +#ifndef FABRICD + int idx = 0; + + if (argv_find(argv, argc, "algorithm", &idx)) { + if (argv_find(argv, argc, "(128-255)", &idx)) + algorithm = (uint16_t)strtoul(argv[idx]->arg, NULL, 10); + else + all_algorithm = true; + } +#endif /* ifndef FABRICD */ + + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + if (!area->srdb.enabled) { + vty_out(vty, " Segment Routing is disabled\n"); + continue; + } + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) { + if (all_algorithm) { + for (algorithm = SR_ALGORITHM_FLEX_MIN; + algorithm <= SR_ALGORITHM_FLEX_MAX; + algorithm++) + show_node(vty, area, level, + (uint8_t)algorithm); + } else + show_node(vty, area, level, + (uint8_t)algorithm); + } + } + } + + return CMD_SUCCESS; +} + +/* --- IS-IS Segment Routing Management function ---------------------------- */ + +/** + * Thread function to re-attempt connection to the Label Manager and thus be + * able to start Segment Routing. + * + * @param start Thread structure that contains area as argument + * + * @return 1 on success + */ +static void sr_start_label_manager(struct event *start) +{ + struct isis_area *area; + + area = EVENT_ARG(start); + + /* re-attempt to start SR & Label Manager connection */ + isis_sr_start(area); +} + +/** + * Enable SR on the given IS-IS area. + * + * @param area IS-IS area + * + * @return 0 on success, -1 otherwise + */ +int isis_sr_start(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct isis_adjacency *adj; + struct listnode *node; + + /* First start Label Manager if not ready */ + if (!isis_zebra_label_manager_ready()) + if (isis_zebra_label_manager_connect() < 0) { + /* Re-attempt to connect to Label Manager in 1 sec. */ + event_add_timer(master, sr_start_label_manager, area, 1, + &srdb->t_start_lm); + return -1; + } + + /* Label Manager is ready, initialize the SRLB */ + if (sr_local_block_init(area) < 0) + return -1; + + /* + * Request SGRB to the label manager if not already active. If the + * allocation fails, return an error to disable SR until a new SRGB + * is successfully allocated. + */ + if (!srdb->srgb_active) { + if (isis_zebra_request_label_range( + srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound + - srdb->config.srgb_lower_bound + 1) + < 0) { + srdb->srgb_active = false; + return -1; + } else + srdb->srgb_active = true; + } + + sr_debug("ISIS-Sr: Starting Segment Routing for area %s", + area->area_tag); + + /* Create Adjacency-SIDs from existing IS-IS Adjacencies. */ + for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) { + if (adj->ipv4_address_count > 0) + sr_adj_sid_add(adj, AF_INET); + if (adj->ll_ipv6_count > 0) + sr_adj_sid_add(adj, AF_INET6); + } + + area->srdb.enabled = true; + + /* Regenerate LSPs to advertise Segment Routing capabilities. */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return 0; +} + +/** + * Disable SR on the given IS-IS area. + * + * @param area IS-IS area + */ +void isis_sr_stop(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + struct sr_adjacency *sra; + struct listnode *node, *nnode; + + sr_debug("ISIS-Sr: Stopping Segment Routing for area %s", + area->area_tag); + + /* Disable any re-attempt to connect to Label Manager */ + EVENT_OFF(srdb->t_start_lm); + + /* Uninstall all local Adjacency-SIDs. */ + for (ALL_LIST_ELEMENTS(area->srdb.adj_sids, node, nnode, sra)) + sr_adj_sid_del(sra); + + /* Release SRGB if active. */ + if (srdb->srgb_active) { + isis_zebra_release_label_range(srdb->config.srgb_lower_bound, + srdb->config.srgb_upper_bound); + srdb->srgb_active = false; + } + + /* Delete SRLB */ + sr_local_block_delete(area); + + area->srdb.enabled = false; + + /* Regenerate LSPs to advertise that the Node is no more SR enable. */ + lsp_regenerate_schedule(area, area->is_type, 0); +} + +/** + * IS-IS Segment Routing initialization for given area. + * + * @param area IS-IS area + */ +void isis_sr_area_init(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + + sr_debug("ISIS-Sr (%s): Initialize Segment Routing SRDB", + area->area_tag); + + /* Initialize Segment Routing Data Base */ + memset(srdb, 0, sizeof(*srdb)); + srdb->adj_sids = list_new(); + + /* Pull defaults from the YANG module. */ +#ifndef FABRICD + srdb->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SR); + srdb->config.srgb_lower_bound = yang_get_default_uint32( + "%s/label-blocks/srgb/lower-bound", ISIS_SR); + srdb->config.srgb_upper_bound = yang_get_default_uint32( + "%s/label-blocks/srgb/upper-bound", ISIS_SR); + srdb->config.srlb_lower_bound = yang_get_default_uint32( + "%s/label-blocks/srlb/lower-bound", ISIS_SR); + srdb->config.srlb_upper_bound = yang_get_default_uint32( + "%s/label-blocks/srlb/upper-bound", ISIS_SR); +#else + srdb->config.enabled = false; + srdb->config.srgb_lower_bound = SRGB_LOWER_BOUND; + srdb->config.srgb_upper_bound = SRGB_UPPER_BOUND; + srdb->config.srlb_lower_bound = SRLB_LOWER_BOUND; + srdb->config.srlb_upper_bound = SRLB_UPPER_BOUND; +#endif + srdb->config.msd = 0; + srdb_prefix_cfg_init(&srdb->config.prefix_sids); +} + +/** + * Terminate IS-IS Segment Routing for the given area. + * + * @param area IS-IS area + */ +void isis_sr_area_term(struct isis_area *area) +{ + struct isis_sr_db *srdb = &area->srdb; + + /* Stop Segment Routing */ + if (area->srdb.enabled) + isis_sr_stop(area); + + /* Free Adjacency SID list */ + list_delete(&srdb->adj_sids); + + /* Clear Prefix-SID configuration. */ + while (srdb_prefix_cfg_count(&srdb->config.prefix_sids) > 0) { + struct sr_prefix_cfg *pcfg; + + pcfg = srdb_prefix_cfg_first(&srdb->config.prefix_sids); + isis_sr_cfg_prefix_del(pcfg); + } +} + +/** + * IS-IS Segment Routing global initialization. + */ +void isis_sr_init(void) +{ + install_element(VIEW_NODE, &show_sr_node_cmd); + + /* Register hooks. */ + hook_register(isis_adj_state_change_hook, sr_adj_state_change); + hook_register(isis_adj_ip_enabled_hook, sr_adj_ip_enabled); + hook_register(isis_adj_ip_disabled_hook, sr_adj_ip_disabled); +} + +/** + * IS-IS Segment Routing global terminate. + */ +void isis_sr_term(void) +{ + /* Unregister hooks. */ + hook_unregister(isis_adj_state_change_hook, sr_adj_state_change); + hook_unregister(isis_adj_ip_enabled_hook, sr_adj_ip_enabled); + hook_unregister(isis_adj_ip_disabled_hook, sr_adj_ip_disabled); +} diff --git a/isisd/isis_sr.h b/isisd/isis_sr.h new file mode 100644 index 0000000..76f7768 --- /dev/null +++ b/isisd/isis_sr.h @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing for IS-IS as per RFC 8667 + * + * Copyright (C) 2019 Orange http://www.orange.com + * + * Author: Olivier Dugeon + * Contributor: Renato Westphal for NetDEF + */ + +#ifndef _FRR_ISIS_SR_H +#define _FRR_ISIS_SR_H + +#include "lib/linklist.h" +#include "lib/mpls.h" +#include "lib/nexthop.h" +#include "lib/typesafe.h" + +#include "isisd/isis_tlvs.h" + +/* + * Segment Routing information is transported through the following Sub-TLVs: + * + * Sub-TLV Name Value TLVs + * --------------------------------------------------------------------- + * SID Label 1 + * + * Prefix Segment Identifier 3 135, 235, 236 and 237 + * + * Adjacency Segment Identifier 31 22, 23, 141, 222 and 223 + * LAN Adjacency Segment Identifier 32 22, 23, 141, 222 and 223 + * + * Segment Routing Capability 2 242 + * Segment Routing Algorithm 19 242 + * Node Maximum Stack Depth (MSD) 23 242 + * + * Sub-TLV definitions, serialization and de-serialization are defined + * in isis_tlvs.[c,h]. + */ + +#define SRGB_LOWER_BOUND 16000 +#define SRGB_UPPER_BOUND 23999 +#define SRLB_LOWER_BOUND 15000 +#define SRLB_UPPER_BOUND 15999 + +/* Segment Routing Data Base (SRDB) RB-Tree structure */ +PREDECL_RBTREE_UNIQ(srdb_prefix_cfg); + +/* + * Segment Routing Prefix-SID information. + * + * This structure is intended to be embedded inside other structures that + * might or might not contain Prefix-SID information. + */ +struct isis_sr_psid_info { + /* Prefix-SID Sub-TLV information. */ + struct isis_prefix_sid sid; + + /* Resolved input/output label. */ + mpls_label_t label; + + /* Indicates whether the Prefix-SID is present or not. */ + bool present; + + uint8_t algorithm; + + struct list *nexthops; + struct list *nexthops_backup; +}; + +/* Segment Routing Local Block allocation */ +struct sr_local_block { + bool active; + uint32_t start; + uint32_t end; + uint32_t current; + uint32_t max_block; + uint64_t *used_mark; +}; +#define SRLB_BLOCK_SIZE 64 + +/* Segment Routing Adjacency-SID type. */ +enum sr_adj_type { + ISIS_SR_ADJ_NORMAL = 0, + ISIS_SR_ADJ_BACKUP, +}; + +/* Segment Routing Adjacency. */ +struct sr_adjacency { + /* Adjacency type. */ + enum sr_adj_type type; + + /* Adjacency-SID input label. */ + mpls_label_t input_label; + + /* Adjacency-SID nexthop information. */ + struct { + int family; + union g_addr address; + } nexthop; + + /* Adjacency-SID TI-LFA backup nexthops. */ + struct list *backup_nexthops; + + /* (LAN-)Adjacency-SID Sub-TLV. */ + union { + struct isis_adj_sid *adj_sid; + struct isis_lan_adj_sid *ladj_sid; + } u; + + /* Back pointer to IS-IS adjacency. */ + struct isis_adjacency *adj; +}; + +/* SID type. NOTE: these values must be in sync with the YANG module. */ +enum sr_sid_value_type { + SR_SID_VALUE_TYPE_INDEX = 0, + SR_SID_VALUE_TYPE_ABSOLUTE = 1, +}; + +#define IS_SID_VALUE(flag) CHECK_FLAG(flag, ISIS_PREFIX_SID_VALUE) + +/* Last Hop Behavior. NOTE: these values must be in sync with the YANG module */ +enum sr_last_hop_behavior { + SR_LAST_HOP_BEHAVIOR_EXP_NULL = 0, + SR_LAST_HOP_BEHAVIOR_NO_PHP = 1, + SR_LAST_HOP_BEHAVIOR_PHP = 2, +}; + +/* Segment Routing Prefix-SID configuration. */ +struct sr_prefix_cfg { + /* SRDB RB-tree entry. */ + struct srdb_prefix_cfg_item entry; + + /* IP prefix. */ + struct prefix prefix; + + /* SID value. */ + uint32_t sid; + + /* SID value type. */ + enum sr_sid_value_type sid_type; + + /* SID last hop behavior. */ + enum sr_last_hop_behavior last_hop_behavior; + + /* Indicates whether the node flag must be explicitly unset. */ + bool n_flag_clear; + + /* Does this Prefix-SID refer to a loopback address (Node-SID)? */ + bool node_sid; + + /* Backpointer to IS-IS area. */ + struct isis_area *area; + + /* SR Algorithm number */ + uint8_t algorithm; +}; + +/* Per-area IS-IS Segment Routing Data Base (SRDB). */ +struct isis_sr_db { + /* Global Operational status of Segment Routing. */ + bool enabled; + + /* Thread timer to start Label Manager */ + struct event *t_start_lm; + + /* List of local Adjacency-SIDs. */ + struct list *adj_sids; + + /* Management of SRLB & SRGB allocation */ + struct sr_local_block srlb; + bool srgb_active; + + /* Area Segment Routing configuration. */ + struct { + /* Administrative status of Segment Routing. */ + bool enabled; + + /* Segment Routing Global Block lower & upper bound. */ + uint32_t srgb_lower_bound; + uint32_t srgb_upper_bound; + + /* Segment Routing Local Block lower & upper bound. */ + uint32_t srlb_lower_bound; + uint32_t srlb_upper_bound; + + /* Maximum SID Depth supported by the node. */ + uint8_t msd; + + /* Prefix-SID mappings. */ + struct srdb_prefix_cfg_head prefix_sids; + } config; +}; + +/* Prototypes. */ +extern struct isis_sr_block *isis_sr_find_srgb(struct lspdb_head *lspdb, + const uint8_t *sysid); +extern mpls_label_t sr_prefix_in_label(struct isis_area *area, + struct isis_prefix_sid *psid, + bool local); +extern mpls_label_t sr_prefix_out_label(struct lspdb_head *lspdb, int family, + struct isis_prefix_sid *psid, + const uint8_t *nh_sysid, bool last_hop); +extern int isis_sr_cfg_srgb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound); +extern int isis_sr_cfg_srlb_update(struct isis_area *area, uint32_t lower_bound, + uint32_t upper_bound); +extern struct sr_prefix_cfg *isis_sr_cfg_prefix_add(struct isis_area *area, + const struct prefix *prefix, + uint8_t algorithm); +extern void isis_sr_cfg_prefix_del(struct sr_prefix_cfg *pcfg); +extern struct sr_prefix_cfg * +isis_sr_cfg_prefix_find(struct isis_area *area, union prefixconstptr prefix, + uint8_t algorithm); +extern void isis_sr_prefix_cfg2subtlv(const struct sr_prefix_cfg *pcfg, + bool external, + struct isis_prefix_sid *psid); +extern void sr_adj_sid_add_single(struct isis_adjacency *adj, int family, + bool backup, struct list *nexthops); +extern struct sr_adjacency *isis_sr_adj_sid_find(struct isis_adjacency *adj, + int family, + enum sr_adj_type type); +extern void isis_area_delete_backup_adj_sids(struct isis_area *area, int level); +extern int sr_if_addr_update(struct interface *ifp); +extern char *sr_op2str(char *buf, size_t size, mpls_label_t label_in, + mpls_label_t label_out); +extern int isis_sr_start(struct isis_area *area); +extern void isis_sr_stop(struct isis_area *area); +extern void isis_sr_area_init(struct isis_area *area); +extern void isis_sr_area_term(struct isis_area *area); +extern void isis_sr_init(void); +extern void isis_sr_term(void); + +#endif /* _FRR_ISIS_SR_H */ diff --git a/isisd/isis_srv6.c b/isisd/isis_srv6.c new file mode 100644 index 0000000..44fd599 --- /dev/null +++ b/isisd/isis_srv6.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing over IPv6 (SRv6) for IS-IS + * as per RFC 9352 + * https://datatracker.ietf.org/doc/html/rfc9352 + * + * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata + */ + +#include + +#include "srv6.h" +#include "termtable.h" +#include "lib/lib_errors.h" + +#include "isisd/isisd.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_route.h" +#include "isisd/isis_srv6.h" +#include "isisd/isis_zebra.h" + +/* Local variables and functions */ +DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_SID, "ISIS SRv6 Segment ID"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_SRV6_INFO, "ISIS SRv6 information"); + +/** + * Fill in SRv6 SID Structure Sub-Sub-TLV with information from an SRv6 SID. + * + * @param sid SRv6 SID configuration + * @param structure_subsubtlv SRv6 SID Structure Sub-Sub-TLV to be updated + */ +void isis_srv6_sid_structure2subsubtlv( + const struct isis_srv6_sid *sid, + struct isis_srv6_sid_structure_subsubtlv *structure_subsubtlv) +{ + /* Set Locator Block length */ + structure_subsubtlv->loc_block_len = sid->structure.loc_block_len; + + /* Set Locator Node length */ + structure_subsubtlv->loc_node_len = sid->structure.loc_node_len; + + /* Set Function length */ + structure_subsubtlv->func_len = sid->structure.func_len; + + /* Set Argument length */ + structure_subsubtlv->arg_len = sid->structure.arg_len; +} + +/** + * Fill in SRv6 End SID Sub-TLV with information from an SRv6 SID. + * + * @param sid SRv6 SID configuration + * @param sid_subtlv SRv6 End SID Sub-TLV to be updated + */ +void isis_srv6_end_sid2subtlv(const struct isis_srv6_sid *sid, + struct isis_srv6_end_sid_subtlv *sid_subtlv) +{ + /* Set SRv6 SID flags */ + sid_subtlv->flags = sid->flags; + + /* Set SRv6 SID behavior */ + sid_subtlv->behavior = sid->behavior; + + /* Set SRv6 SID value */ + sid_subtlv->sid = sid->sid; +} + +/** + * Fill in SRv6 Locator TLV with information from an SRv6 locator. + * + * @param loc SRv6 Locator configuration + * @param loc_tlv SRv6 Locator TLV to be updated + */ +void isis_srv6_locator2tlv(const struct isis_srv6_locator *loc, + struct isis_srv6_locator_tlv *loc_tlv) +{ + /* Set SRv6 Locator metric */ + loc_tlv->metric = loc->metric; + + /* Set SRv6 Locator flags */ + loc_tlv->flags = loc->flags; + + /* Set SRv6 Locator algorithm */ + loc_tlv->algorithm = loc->algorithm; + + /* Set SRv6 Locator prefix */ + loc_tlv->prefix = loc->prefix; +} + +/** + * Unset the SRv6 locator for a given IS-IS area. + * + * @param area IS-IS area + * + * @result True on success, False otherwise + */ +bool isis_srv6_locator_unset(struct isis_area *area) +{ + int ret; + struct listnode *node, *nnode; + struct srv6_locator_chunk *chunk; + struct isis_srv6_sid *sid; + struct srv6_adjacency *sra; + + if (strncmp(area->srv6db.config.srv6_locator_name, "", + sizeof(area->srv6db.config.srv6_locator_name)) == 0) { + sr_debug("SRv6 locator not set"); + return true; + } + + /* Delete SRv6 SIDs */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_sids, node, nnode, sid)) { + sr_debug( + "Deleting SRv6 SID (locator %s, sid %pI6) from IS-IS area %s", + area->srv6db.config.srv6_locator_name, &sid->sid, + area->area_tag); + + /* Uninstall the SRv6 SID from the forwarding plane through + * Zebra */ + isis_zebra_srv6_sid_uninstall(area, sid); + + listnode_delete(area->srv6db.srv6_sids, sid); + isis_srv6_sid_free(sid); + } + + /* Uninstall all local Adjacency-SIDs. */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra)) + srv6_endx_sid_del(sra); + + /* Inform Zebra that we are releasing the SRv6 locator */ + ret = isis_zebra_srv6_manager_release_locator_chunk( + area->srv6db.config.srv6_locator_name); + if (ret < 0) + return false; + + /* Delete chunks */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_locator_chunks, node, nnode, + chunk)) { + sr_debug( + "Releasing chunk of locator %s (prefix %pFX) for IS-IS area %s", + area->srv6db.config.srv6_locator_name, &chunk->prefix, + area->area_tag); + + listnode_delete(area->srv6db.srv6_locator_chunks, chunk); + srv6_locator_chunk_free(&chunk); + } + + /* Clear locator name */ + memset(area->srv6db.config.srv6_locator_name, 0, + sizeof(area->srv6db.config.srv6_locator_name)); + + /* Regenerate LSPs to advertise that the SRv6 locator no longer exists + */ + lsp_regenerate_schedule(area, area->is_type, 0); + + return true; +} + +/** + * Set the interface used to install SRv6 SIDs into the data plane. + * + * @param area IS-IS area + */ +void isis_srv6_interface_set(struct isis_area *area, const char *ifname) +{ + struct listnode *node; + struct isis_srv6_sid *sid; + + if (!ifname) + return; + + if (!strncmp(ifname, area->srv6db.config.srv6_ifname, IF_NAMESIZE)) { + /* The interface has not changed, nothing to do */ + return; + } + + sr_debug("SRv6 interface for IS-IS area %s changed (old interface: %s, new interface: %s)", area->area_tag, area->srv6db.config.srv6_ifname, ifname); + + /* Walk through all SIDs and uninstall them from the data plane */ + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) { + sr_debug("Uninstalling SID %pI6 from the data plane", &sid->sid); + isis_zebra_srv6_sid_uninstall(area, sid); + } + + strlcpy(area->srv6db.config.srv6_ifname, ifname, sizeof(area->srv6db.config.srv6_ifname)); + + if (!if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT)) { + sr_debug("Interface %s not yet exist in data plane, deferring SIDs installation until it's created", area->srv6db.config.srv6_ifname); + return; + } + + /* Walk through all SIDs and re-install them into the data plane with the newly configured interface */ + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, sid)) { + sr_debug("Installing SID %pI6 from the data plane", &sid->sid); + isis_zebra_srv6_sid_install(area, sid); + } +} + +/** + * Encode SID function in the SRv6 SID. + * + * @param sid + * @param func + * @param offset + * @param len + */ +static void encode_sid_func(struct in6_addr *sid, uint32_t func, uint8_t offset, + uint8_t len) +{ + for (uint8_t idx = 0; idx < len; idx++) { + uint8_t tidx = offset + idx; + sid->s6_addr[tidx / 8] &= ~(0x1 << (7 - tidx % 8)); + if (func >> (len - 1 - idx) & 0x1) + sid->s6_addr[tidx / 8] |= 0x1 << (7 - tidx % 8); + } +} + +static bool sid_exist(struct isis_area *area, const struct in6_addr *sid) +{ + struct listnode *node; + struct isis_srv6_sid *s; + struct srv6_adjacency *sra; + + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node, s)) + if (sid_same(&s->sid, sid)) + return true; + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_endx_sids, node, sra)) + if (sid_same(&sra->sid, sid)) + return true; + return false; +} + +/** + * Request a SID from the SRv6 locator. + * + * @param area IS-IS area + * @param chunk SRv6 locator chunk + * @param sid_func The FUNCTION part of the SID to be allocated (a negative + * number will allocate the first available SID) + * + * @return First available SID on success or in6addr_any if the SRv6 + * locator chunk is full + */ +static struct in6_addr +srv6_locator_request_sid(struct isis_area *area, + struct srv6_locator_chunk *chunk, int sid_func) +{ + struct in6_addr sid; + uint8_t offset = 0; + uint8_t func_len = 0; + uint32_t func_max; + bool allocated = false; + + if (!area || !chunk) + return in6addr_any; + + sr_debug("ISIS-SRv6 (%s): requested new SID from locator %s", + area->area_tag, chunk->locator_name); + + /* Let's build the SID, step by step. A SID has the following structure + (defined in RFC 8986): LOCATOR:FUNCTION:ARGUMENT.*/ + + /* First, we encode the LOCATOR in the L most significant bits. */ + sid = chunk->prefix.prefix; + + /* The next part of the SID is the FUNCTION. Let's compute the length + * and the offset of the FUNCTION in the SID */ + func_len = chunk->function_bits_length; + offset = chunk->block_bits_length + chunk->node_bits_length; + + /* Then, encode the FUNCTION */ + if (sid_func >= 0) { + /* SID FUNCTION has been specified. We need to allocate a SID + * with the requested FUNCTION. */ + encode_sid_func(&sid, sid_func, offset, func_len); + if (sid_exist(area, &sid)) { + zlog_warn( + "ISIS-SRv6 (%s): the requested SID %pI6 is already used", + area->area_tag, &sid); + return sid; + } + allocated = true; + } else { + /* SID FUNCTION not specified. We need to choose a FUNCTION that + * is not already used. So let's iterate through all possible + * functions and get the first available one. */ + func_max = (1 << func_len) - 1; + for (uint32_t func = 1; func < func_max; func++) { + encode_sid_func(&sid, func, offset, func_len); + if (sid_exist(area, &sid)) + continue; + allocated = true; + break; + } + } + + if (!allocated) { + /* We ran out of available SIDs */ + zlog_warn("ISIS-SRv6 (%s): no SIDs available in locator %s", + area->area_tag, chunk->locator_name); + return in6addr_any; + } + + sr_debug("ISIS-SRv6 (%s): allocating new SID %pI6", area->area_tag, + &sid); + + return sid; +} + +/** + * Allocate an SRv6 SID from an SRv6 locator. + * + * @param area IS-IS area + * @param chunk SRv6 locator chunk + * @param behavior SRv6 Endpoint Behavior bound to the SID + * + * @result the allocated SID on success, NULL otherwise + */ +struct isis_srv6_sid * +isis_srv6_sid_alloc(struct isis_area *area, struct srv6_locator_chunk *chunk, + enum srv6_endpoint_behavior_codepoint behavior, + int sid_func) +{ + struct isis_srv6_sid *sid = NULL; + + if (!area || !chunk) + return NULL; + + sid = XCALLOC(MTYPE_ISIS_SRV6_SID, sizeof(struct isis_srv6_sid)); + + sid->sid = srv6_locator_request_sid(area, chunk, sid_func); + if (IPV6_ADDR_SAME(&sid->sid, &in6addr_any)) { + isis_srv6_sid_free(sid); + return NULL; + } + + sid->behavior = behavior; + sid->structure.loc_block_len = chunk->block_bits_length; + sid->structure.loc_node_len = chunk->node_bits_length; + sid->structure.func_len = chunk->function_bits_length; + sid->structure.arg_len = chunk->argument_bits_length; + sid->locator = chunk; + sid->area = area; + + return sid; +} + +void isis_srv6_sid_free(struct isis_srv6_sid *sid) +{ + XFREE(MTYPE_ISIS_SRV6_SID, sid); +} + +/** + * Delete all backup SRv6 End.X SIDs. + * + * @param area IS-IS area + * @param level IS-IS level + */ +void isis_area_delete_backup_srv6_endx_sids(struct isis_area *area, int level) +{ + struct srv6_adjacency *sra; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, sra)) + if (sra->type == ISIS_SRV6_ADJ_BACKUP && + (sra->adj->level & level)) + srv6_endx_sid_del(sra); +} + +/* --- SRv6 End.X SID management functions ------------------- */ + +/** + * Add new local End.X SID. + * + * @param adj IS-IS Adjacency + * @param backup True to initialize backup Adjacency SID + * @param nexthops List of backup nexthops (for backup End.X SIDs only) + */ +void srv6_endx_sid_add_single(struct isis_adjacency *adj, bool backup, + struct list *nexthops) +{ + struct isis_circuit *circuit = adj->circuit; + struct isis_area *area = circuit->area; + struct srv6_adjacency *sra; + struct isis_srv6_endx_sid_subtlv *adj_sid; + struct isis_srv6_lan_endx_sid_subtlv *ladj_sid; + struct in6_addr nexthop; + uint8_t flags = 0; + struct srv6_locator_chunk *chunk; + uint32_t behavior; + + if (!area || !area->srv6db.srv6_locator_chunks || + list_isempty(area->srv6db.srv6_locator_chunks)) + return; + + sr_debug("ISIS-SRv6 (%s): Add %s End.X SID", area->area_tag, + backup ? "Backup" : "Primary"); + + /* Determine nexthop IP address */ + if (!circuit->ipv6_router || !adj->ll_ipv6_count) + return; + + chunk = (struct srv6_locator_chunk *)listgetdata( + listhead(area->srv6db.srv6_locator_chunks)); + if (!chunk) + return; + + nexthop = adj->ll_ipv6_addrs[0]; + + /* Prepare SRv6 End.X as per RFC9352 section #8.1 */ + if (backup) + SET_FLAG(flags, EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG); + + if (circuit->ext == NULL) + circuit->ext = isis_alloc_ext_subtlvs(); + + behavior = (CHECK_FLAG(chunk->flags, SRV6_LOCATOR_USID)) + ? SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID + : SRV6_ENDPOINT_BEHAVIOR_END_X; + + sra = XCALLOC(MTYPE_ISIS_SRV6_INFO, sizeof(*sra)); + sra->type = backup ? ISIS_SRV6_ADJ_BACKUP : ISIS_SRV6_ADJ_NORMAL; + sra->behavior = behavior; + sra->locator = chunk; + sra->structure.loc_block_len = chunk->block_bits_length; + sra->structure.loc_node_len = chunk->node_bits_length; + sra->structure.func_len = chunk->function_bits_length; + sra->structure.arg_len = chunk->argument_bits_length; + sra->nexthop = nexthop; + + sra->sid = srv6_locator_request_sid(area, chunk, -1); + if (IPV6_ADDR_SAME(&sra->sid, &in6addr_any)) { + XFREE(MTYPE_ISIS_SRV6_INFO, sra); + return; + } + + switch (circuit->circ_type) { + /* SRv6 LAN End.X SID for Broadcast interface section #8.2 */ + case CIRCUIT_T_BROADCAST: + ladj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*ladj_sid)); + memcpy(ladj_sid->neighbor_id, adj->sysid, + sizeof(ladj_sid->neighbor_id)); + ladj_sid->flags = flags; + ladj_sid->algorithm = SR_ALGORITHM_SPF; + ladj_sid->weight = 0; + ladj_sid->behavior = sra->behavior; + ladj_sid->sid = sra->sid; + ladj_sid->subsubtlvs = isis_alloc_subsubtlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID); + ladj_sid->subsubtlvs->srv6_sid_structure = XCALLOC( + MTYPE_ISIS_SUBSUBTLV, + sizeof(*ladj_sid->subsubtlvs->srv6_sid_structure)); + ladj_sid->subsubtlvs->srv6_sid_structure->loc_block_len = + sra->structure.loc_block_len; + ladj_sid->subsubtlvs->srv6_sid_structure->loc_node_len = + sra->structure.loc_node_len; + ladj_sid->subsubtlvs->srv6_sid_structure->func_len = + sra->structure.func_len; + ladj_sid->subsubtlvs->srv6_sid_structure->arg_len = + sra->structure.arg_len; + isis_tlvs_add_srv6_lan_endx_sid(circuit->ext, ladj_sid); + sra->u.lendx_sid = ladj_sid; + break; + /* SRv6 End.X SID for Point to Point interface section #8.1 */ + case CIRCUIT_T_P2P: + adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid)); + adj_sid->flags = flags; + adj_sid->algorithm = SR_ALGORITHM_SPF; + adj_sid->weight = 0; + adj_sid->behavior = sra->behavior; + adj_sid->sid = sra->sid; + adj_sid->subsubtlvs = isis_alloc_subsubtlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID); + adj_sid->subsubtlvs->srv6_sid_structure = XCALLOC( + MTYPE_ISIS_SUBSUBTLV, + sizeof(*adj_sid->subsubtlvs->srv6_sid_structure)); + adj_sid->subsubtlvs->srv6_sid_structure->loc_block_len = + sra->structure.loc_block_len; + adj_sid->subsubtlvs->srv6_sid_structure->loc_node_len = + sra->structure.loc_node_len; + adj_sid->subsubtlvs->srv6_sid_structure->func_len = + sra->structure.func_len; + adj_sid->subsubtlvs->srv6_sid_structure->arg_len = + sra->structure.arg_len; + isis_tlvs_add_srv6_endx_sid(circuit->ext, adj_sid); + sra->u.endx_sid = adj_sid; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + + /* Add Adjacency-SID in SRDB */ + sra->adj = adj; + listnode_add(area->srv6db.srv6_endx_sids, sra); + listnode_add(adj->srv6_endx_sids, sra); + + isis_zebra_srv6_adj_sid_install(sra); +} + +/** + * Add Primary and Backup local SRv6 End.X SID. + * + * @param adj IS-IS Adjacency + */ +void srv6_endx_sid_add(struct isis_adjacency *adj) +{ + srv6_endx_sid_add_single(adj, false, NULL); +} + +/** + * Delete local SRv6 End.X SID. + * + * @param sra SRv6 Adjacency + */ +void srv6_endx_sid_del(struct srv6_adjacency *sra) +{ + struct isis_circuit *circuit = sra->adj->circuit; + struct isis_area *area = circuit->area; + + sr_debug("ISIS-SRv6 (%s): Delete SRv6 End.X SID", area->area_tag); + + isis_zebra_srv6_adj_sid_uninstall(sra); + + /* Release dynamic SRv6 SID and remove subTLVs */ + switch (circuit->circ_type) { + case CIRCUIT_T_BROADCAST: + isis_tlvs_del_srv6_lan_endx_sid(circuit->ext, sra->u.lendx_sid); + break; + case CIRCUIT_T_P2P: + isis_tlvs_del_srv6_endx_sid(circuit->ext, sra->u.endx_sid); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unexpected circuit type: %u", + __func__, circuit->circ_type); + exit(1); + } + + if (sra->type == ISIS_SRV6_ADJ_BACKUP && sra->backup_nexthops) { + sra->backup_nexthops->del = + (void (*)(void *))isis_nexthop_delete; + list_delete(&sra->backup_nexthops); + } + + /* Remove Adjacency-SID from the SRDB */ + listnode_delete(area->srv6db.srv6_endx_sids, sra); + listnode_delete(sra->adj->srv6_endx_sids, sra); + XFREE(MTYPE_ISIS_SRV6_INFO, sra); +} + +/** + * Lookup SRv6 End.X SID by type. + * + * @param adj IS-IS Adjacency + * @param type SRv6 End.X SID type + */ +struct srv6_adjacency *isis_srv6_endx_sid_find(struct isis_adjacency *adj, + enum srv6_adj_type type) +{ + struct srv6_adjacency *sra; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(adj->srv6_endx_sids, node, sra)) + if (sra->type == type) + return sra; + + return NULL; +} + +/** + * Remove all SRv6 End.X SIDs associated to an adjacency that is going down. + * + * @param adj IS-IS Adjacency + * + * @return 0 + */ +static int srv6_adj_state_change(struct isis_adjacency *adj) +{ + struct srv6_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srv6db.config.enabled) + return 0; + + if (adj->adj_state == ISIS_ADJ_UP) + return 0; + + for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra)) + srv6_endx_sid_del(sra); + + return 0; +} + +/** + * When IS-IS Adjacency got one or more IPv6 addresses, add new + * IPv6 address to corresponding SRv6 End.X SID accordingly. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param global Indicate if it concerns the Local or Global IPv6 addresses + * + * @return 0 + */ +static int srv6_adj_ip_enabled(struct isis_adjacency *adj, int family, + bool global) +{ + if (!adj->circuit->area->srv6db.config.enabled || global || + family != AF_INET6) + return 0; + + srv6_endx_sid_add(adj); + + return 0; +} + +/** + * When IS-IS Adjacency doesn't have any IPv6 addresses anymore, + * delete the corresponding SRv6 End.X SID(s) accordingly. + * + * @param adj IS-IS Adjacency + * @param family Inet Family (IPv4 or IPv6) + * @param global Indicate if it concerns the Local or Global IPv6 addresses + * + * @return 0 + */ +static int srv6_adj_ip_disabled(struct isis_adjacency *adj, int family, + bool global) +{ + struct srv6_adjacency *sra; + struct listnode *node, *nnode; + + if (!adj->circuit->area->srv6db.config.enabled || global || + family != AF_INET6) + return 0; + + for (ALL_LIST_ELEMENTS(adj->srv6_endx_sids, node, nnode, sra)) + srv6_endx_sid_del(sra); + + return 0; +} + +/** + * Show Segment Routing over IPv6 (SRv6) Node. + * + * @param vty VTY output + * @param area IS-IS area + * @param level IS-IS level + */ +static void show_node(struct vty *vty, struct isis_area *area, int level) +{ + struct isis_lsp *lsp; + struct ttable *tt; + + vty_out(vty, " IS-IS %s SRv6-Nodes:\n\n", circuit_t2string(level)); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "System ID|Algorithm|SRH Max SL|SRH Max End Pop|SRH Max H.encaps|SRH Max End D"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + frr_each (lspdb, &area->lspdb[level - 1], lsp) { + struct isis_router_cap *cap; + + if (!lsp->tlvs) + continue; + cap = lsp->tlvs->router_cap; + if (!cap) + continue; + + ttable_add_row(tt, "%pSY|%s|%u|%u|%u|%u", lsp->hdr.lsp_id, + cap->algo[0] == SR_ALGORITHM_SPF ? "SPF" + : "S-SPF", + cap->srv6_msd.max_seg_left_msd, + cap->srv6_msd.max_end_pop_msd, + cap->srv6_msd.max_h_encaps_msd, + cap->srv6_msd.max_end_d_msd); + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } + ttable_del(tt); +} + +DEFUN(show_srv6_node, show_srv6_node_cmd, + "show " PROTO_NAME " segment-routing srv6 node", + SHOW_STR + PROTO_HELP + "Segment-Routing\n" + "Segment-Routing over IPv6 (SRv6)\n" + "SRv6 node\n") +{ + struct listnode *node, *inode; + struct isis_area *area; + struct isis *isis; + + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + if (!area->srv6db.config.enabled) { + vty_out(vty, " SRv6 is disabled\n"); + continue; + } + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) + show_node(vty, area, level); + } + } + + return CMD_SUCCESS; +} + +int isis_srv6_ifp_up_notify(struct interface *ifp) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct listnode *node, *node2; + struct isis_area *area; + struct isis_srv6_sid *sid; + + if (!isis) + return 0; + + /* Walk through all areas of the ISIS instance */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + /* Skip area, if SRv6 is not enabled */ + if (!area->srv6db.config.enabled) + continue; + + /* Skip area if the interface is not the one configured for SRv6 */ + if (strncmp(area->srv6db.config.srv6_ifname, ifp->name, IF_NAMESIZE)) + continue; + + sr_debug("Interface %s went up. Installing SIDs for area %s in data plane", ifp->name, area->area_tag); + + /* Walk through all SIDs and re-install them into the data plane with the newly configured interface */ + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_sids, node2, sid)) { + sr_debug("Installing SID %pI6 from the data plane", &sid->sid); + isis_zebra_srv6_sid_install(area, sid); + } + } + + return 0; +} + +/** + * IS-IS SRv6 initialization for given area. + * + * @param area IS-IS area + */ +void isis_srv6_area_init(struct isis_area *area) +{ + struct isis_srv6_db *srv6db; + + if (!area) + return; + + srv6db = &area->srv6db; + + sr_debug("ISIS-SRv6 (%s): Initialize Segment Routing SRv6 DB", + area->area_tag); + + /* Initialize SRv6 Data Base */ + memset(srv6db, 0, sizeof(*srv6db)); + srv6db->srv6_endx_sids = list_new(); + + /* Pull defaults from the YANG module */ +#ifndef FABRICD + srv6db->config.enabled = yang_get_default_bool("%s/enabled", ISIS_SRV6); + srv6db->config.max_seg_left_msd = + yang_get_default_uint8("%s/msd/node-msd/max-segs-left", + ISIS_SRV6); + srv6db->config.max_end_pop_msd = + yang_get_default_uint8("%s/msd/node-msd/max-end-pop", ISIS_SRV6); + srv6db->config.max_h_encaps_msd = + yang_get_default_uint8("%s/msd/node-msd/max-h-encaps", + ISIS_SRV6); + srv6db->config.max_end_d_msd = + yang_get_default_uint8("%s/msd/node-msd/max-end-d", ISIS_SRV6); + strlcpy(srv6db->config.srv6_ifname, yang_get_default_string("%s/interface", ISIS_SRV6), sizeof(srv6db->config.srv6_ifname)); +#else + srv6db->config.enabled = false; + srv6db->config.max_seg_left_msd = ISIS_DEFAULT_SRV6_MAX_SEG_LEFT_MSD; + srv6db->config.max_end_pop_msd = ISIS_DEFAULT_SRV6_MAX_END_POP_MSD; + srv6db->config.max_h_encaps_msd = ISIS_DEFAULT_SRV6_MAX_H_ENCAPS_MSD; + srv6db->config.max_end_d_msd = ISIS_DEFAULT_SRV6_MAX_END_D_MSD; + strlcpy(srv6db->config.srv6_ifname, ISIS_DEFAULT_SRV6_IFNAME, sizeof(srv6db->config.srv6_ifname)); +#endif + + /* Initialize SRv6 Locator chunks list */ + srv6db->srv6_locator_chunks = list_new(); + + /* Initialize SRv6 SIDs list */ + srv6db->srv6_sids = list_new(); + srv6db->srv6_sids->del = (void (*)(void *))isis_srv6_sid_free; +} + +/** + * Terminate IS-IS SRv6 for the given area. + * + * @param area IS-IS area + */ +void isis_srv6_area_term(struct isis_area *area) +{ + struct isis_srv6_db *srv6db = &area->srv6db; + struct srv6_adjacency *sra; + struct listnode *node, *nnode; + struct srv6_locator_chunk *chunk; + + sr_debug("ISIS-SRv6 (%s): Terminate SRv6", area->area_tag); + + /* Uninstall all local SRv6 End.X SIDs */ + if (area->srv6db.config.enabled) + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, + sra)) + srv6_endx_sid_del(sra); + + /* Free SRv6 Locator chunks list */ + for (ALL_LIST_ELEMENTS(srv6db->srv6_locator_chunks, node, nnode, chunk)) + srv6_locator_chunk_free(&chunk); + list_delete(&srv6db->srv6_locator_chunks); + + /* Free SRv6 SIDs list */ + list_delete(&srv6db->srv6_sids); + list_delete(&srv6db->srv6_endx_sids); +} + +/** + * IS-IS SRv6 global initialization. + */ +void isis_srv6_init(void) +{ + install_element(VIEW_NODE, &show_srv6_node_cmd); + + /* Register hooks. */ + hook_register(isis_adj_state_change_hook, srv6_adj_state_change); + hook_register(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled); + hook_register(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled); +} + +/** + * IS-IS SRv6 global terminate. + */ +void isis_srv6_term(void) +{ + /* Unregister hooks. */ + hook_unregister(isis_adj_state_change_hook, srv6_adj_state_change); + hook_unregister(isis_adj_ip_enabled_hook, srv6_adj_ip_enabled); + hook_unregister(isis_adj_ip_disabled_hook, srv6_adj_ip_disabled); +} diff --git a/isisd/isis_srv6.h b/isisd/isis_srv6.h new file mode 100644 index 0000000..7f16712 --- /dev/null +++ b/isisd/isis_srv6.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing over IPv6 (SRv6) for IS-IS + * as per RFC 9352 + * https://datatracker.ietf.org/doc/html/rfc9352 + * + * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata + */ + +#ifndef _FRR_ISIS_SRV6_H +#define _FRR_ISIS_SRV6_H + +#include "lib/srv6.h" +#include "isisd/isis_tlvs.h" + +#define ISIS_DEFAULT_SRV6_MAX_SEG_LEFT_MSD 3 +#define ISIS_DEFAULT_SRV6_MAX_END_POP_MSD 3 +#define ISIS_DEFAULT_SRV6_MAX_H_ENCAPS_MSD 2 +#define ISIS_DEFAULT_SRV6_MAX_END_D_MSD 5 +#define ISIS_DEFAULT_SRV6_IFNAME "sr0" + +/* SRv6 SID structure */ +struct isis_srv6_sid_structure { + uint8_t loc_block_len; + uint8_t loc_node_len; + uint8_t func_len; + uint8_t arg_len; +}; + +/* SRv6 SID not bound to any adjacency */ +struct isis_srv6_sid { + struct isis_srv6_sid *next; + + /* SID flags */ + uint8_t flags; + + /* SID value */ + struct in6_addr sid; + + /* Endpoint behavior bound to the SID */ + enum srv6_endpoint_behavior_codepoint behavior; + + /* SRv6 SID structure */ + struct isis_srv6_sid_structure structure; + + /* Parent SRv6 locator */ + struct srv6_locator_chunk *locator; + + /* Backpointer to IS-IS area */ + struct isis_area *area; +}; + +/* SRv6 Locator */ +struct isis_srv6_locator { + struct isis_srv6_locator *next; + + uint32_t metric; + + uint8_t flags; +#define ISIS_SRV6_LOCATOR_FLAG_D 1 << 7 + + uint8_t algorithm; + struct prefix_ipv6 prefix; + + struct list *srv6_sid; +}; + +/* SRv6 Adjacency-SID type */ +enum srv6_adj_type { + ISIS_SRV6_ADJ_NORMAL = 0, + ISIS_SRV6_ADJ_BACKUP, +}; + +/* SRv6 Adjacency. */ +struct srv6_adjacency { + /* Adjacency type */ + enum srv6_adj_type type; + + /* SID flags */ + uint8_t flags; + + /* SID value */ + struct in6_addr sid; + + /* Endpoint behavior bound to the SID */ + enum srv6_endpoint_behavior_codepoint behavior; + + /* SRv6 SID structure */ + struct isis_srv6_sid_structure structure; + + /* Parent SRv6 locator */ + struct srv6_locator_chunk *locator; + + /* Adjacency-SID nexthop information */ + struct in6_addr nexthop; + + /* End.X SID TI-LFA backup nexthops */ + struct list *backup_nexthops; + + /* SRv6 (LAN) End.X SID Sub-TLV */ + union { + struct isis_srv6_endx_sid_subtlv *endx_sid; + struct isis_srv6_lan_endx_sid_subtlv *lendx_sid; + } u; + + /* Back pointer to IS-IS adjacency. */ + struct isis_adjacency *adj; +}; + +/* Per-area IS-IS SRv6 Data Base (SRv6 DB) */ +struct isis_srv6_db { + + /* List of SRv6 Locator chunks */ + struct list *srv6_locator_chunks; + + /* List of SRv6 SIDs allocated by the IS-IS instance */ + struct list *srv6_sids; + + /* List of SRv6 End.X SIDs allocated by the IS-IS instance */ + struct list *srv6_endx_sids; + + /* Area SRv6 configuration. */ + struct { + /* Administrative status of SRv6 */ + bool enabled; + + /* Name of the SRv6 Locator */ + char srv6_locator_name[SRV6_LOCNAME_SIZE]; + + /* Maximum Segments Left Depth supported by the router */ + uint8_t max_seg_left_msd; + + /* Maximum Maximum End Pop Depth supported by the router */ + uint8_t max_end_pop_msd; + + /* Maximum H.Encaps supported by the router */ + uint8_t max_h_encaps_msd; + + /* Maximum End D MSD supported by the router */ + uint8_t max_end_d_msd; + + /* Interface used for installing SRv6 SIDs into the data plane */ + char srv6_ifname[IF_NAMESIZE]; + } config; +}; + +bool isis_srv6_locator_unset(struct isis_area *area); + +void isis_srv6_interface_set(struct isis_area *area, const char *ifname); + +struct isis_srv6_sid * +isis_srv6_sid_alloc(struct isis_area *area, struct srv6_locator_chunk *chunk, + enum srv6_endpoint_behavior_codepoint behavior, + int sid_func); +extern void isis_srv6_sid_free(struct isis_srv6_sid *sid); + +extern void isis_srv6_area_init(struct isis_area *area); +extern void isis_srv6_area_term(struct isis_area *area); + +void isis_srv6_init(void); +void isis_srv6_term(void); + +void isis_srv6_sid_structure2subsubtlv( + const struct isis_srv6_sid *sid, + struct isis_srv6_sid_structure_subsubtlv *structure_subsubtlv); +void isis_srv6_end_sid2subtlv(const struct isis_srv6_sid *sid, + struct isis_srv6_end_sid_subtlv *sid_subtlv); +void isis_srv6_locator2tlv(const struct isis_srv6_locator *loc, + struct isis_srv6_locator_tlv *loc_tlv); + +void srv6_endx_sid_add_single(struct isis_adjacency *adj, bool backup, + struct list *nexthops); +void srv6_endx_sid_add(struct isis_adjacency *adj); +void srv6_endx_sid_del(struct srv6_adjacency *sra); +struct srv6_adjacency *isis_srv6_endx_sid_find(struct isis_adjacency *adj, + enum srv6_adj_type type); +void isis_area_delete_backup_srv6_endx_sids(struct isis_area *area, int level); + +int isis_srv6_ifp_up_notify(struct interface *ifp); + +#endif /* _FRR_ISIS_SRV6_H */ diff --git a/isisd/isis_te.c b/isisd/isis_te.c new file mode 100644 index 0000000..d854123 --- /dev/null +++ b/isisd/isis_te.c @@ -0,0 +1,2200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_te.c + * + * This is an implementation of RFC5305 & RFC 7810 + * + * Author: Olivier Dugeon + * + * Copyright (C) 2014 - 2019 Orange Labs http://www.orange.com + */ + +#include +#include + +#include "linklist.h" +#include "frrevent.h" +#include "vty.h" +#include "stream.h" +#include "memory.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "hash.h" +#include "if.h" +#include "vrf.h" +#include "checksum.h" +#include "md5.h" +#include "sockunion.h" +#include "network.h" +#include "sbuf.h" +#include "link_state.h" +#include "lib/json.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isisd.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_te.h" +#include "isisd/isis_zebra.h" + +DEFINE_MTYPE_STATIC(ISISD, ISIS_MPLS_TE, "ISIS MPLS_TE parameters"); + +static void isis_mpls_te_circuit_ip_update(struct isis_circuit *circuit); + +/*------------------------------------------------------------------------* + * Following are control functions for MPLS-TE parameters management. + *------------------------------------------------------------------------*/ + +/** + * Create MPLS Traffic Engineering structure which belongs to given area. + * + * @param area IS-IS Area + */ +void isis_mpls_te_create(struct isis_area *area) +{ + struct listnode *node; + struct isis_circuit *circuit; + + if (!area) + return; + + if (area->mta == NULL) { + + struct mpls_te_area *new; + + zlog_debug("ISIS-TE(%s): Initialize MPLS Traffic Engineering", + area->area_tag); + + new = XCALLOC(MTYPE_ISIS_MPLS_TE, sizeof(struct mpls_te_area)); + + /* Initialize MPLS_TE structure */ + new->status = enable; + new->level = 0; + new->inter_as = off; + new->interas_areaid.s_addr = 0; + new->router_id.s_addr = 0; + new->ted = ls_ted_new(1, "ISIS", 0); + if (!new->ted) + zlog_warn("Unable to create Link State Data Base"); + + area->mta = new; + } else { + area->mta->status = enable; + } + + /* Initialize Link State Database */ + if (area->mta->ted) + isis_te_init_ted(area); + + /* Update Extended TLVs according to Interface link parameters + * and neighbor IP addresses + */ + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + isis_link_params_update(circuit, circuit->interface); + isis_mpls_te_circuit_ip_update(circuit); + } +} + +/** + * Disable MPLS Traffic Engineering structure which belongs to given area. + * + * @param area IS-IS Area + */ +void isis_mpls_te_disable(struct isis_area *area) +{ + struct listnode *node; + struct isis_circuit *circuit; + + if (!area->mta) + return; + + area->mta->status = disable; + + /* Remove Link State Database */ + ls_ted_clean(area->mta->ted); + + /* Disable Extended SubTLVs on all circuit */ + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + if (!IS_EXT_TE(circuit->ext)) + continue; + + /* disable MPLS_TE Circuit keeping SR one's */ + if (IS_SUBTLV(circuit->ext, EXT_ADJ_SID)) + circuit->ext->status = EXT_ADJ_SID; + else if (IS_SUBTLV(circuit->ext, EXT_LAN_ADJ_SID)) + circuit->ext->status = EXT_LAN_ADJ_SID; + else + circuit->ext->status = 0; + } +} + +void isis_mpls_te_term(struct isis_area *area) +{ + struct listnode *node; + struct isis_circuit *circuit; + + if (!area->mta) + return; + + zlog_info("TE(%s): Terminate MPLS TE", __func__); + /* Remove Link State Database */ + ls_ted_del_all(&area->mta->ted); + + /* Remove Extended SubTLVs */ + zlog_info(" |- Remove Extended SubTLVS for all circuit"); + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + zlog_info(" |- Call isis_del_ext_subtlvs()"); + isis_del_ext_subtlvs(circuit->ext); + circuit->ext = NULL; + } + + zlog_info(" |- Free MTA structure at %p", area->mta); + XFREE(MTYPE_ISIS_MPLS_TE, area->mta); +} + +static void isis_link_params_update_asla(struct isis_circuit *circuit, + struct interface *ifp) +{ + struct isis_asla_subtlvs *asla; + struct listnode *node, *nnode; + struct isis_ext_subtlvs *ext = circuit->ext; + int i; + + if (!ext) + /* no extended subTLVs - nothing to update */ + return; + + if (!HAS_LINK_PARAMS(ifp)) { + list_delete_all_node(ext->aslas); + return; + } + +#ifndef FABRICD + /* RFC 8919 Application Specific Link-Attributes + * is required by flex-algo application ISIS_SABM_FLAG_X + */ + if (list_isempty(circuit->area->flex_algos->flex_algos)) + isis_tlvs_free_asla(ext, ISIS_SABM_FLAG_X); + else + isis_tlvs_find_alloc_asla(ext, ISIS_SABM_FLAG_X); +#endif /* ifndef FABRICD */ + + if (list_isempty(ext->aslas)) + return; + + for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla)) { + asla->legacy = circuit->area->asla_legacy_flag; + RESET_SUBTLV(asla); + + if (asla->legacy) + continue; + + /* Fulfill ASLA subTLVs from interface link parameters */ + if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) { + asla->admin_group = ifp->link_params->admin_grp; + SET_SUBTLV(asla, EXT_ADM_GRP); + } else + UNSET_SUBTLV(asla, EXT_ADM_GRP); + + if (IS_PARAM_SET(ifp->link_params, LP_EXTEND_ADM_GRP)) { + admin_group_copy(&asla->ext_admin_group, + &ifp->link_params->ext_admin_grp); + SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP); + } else + UNSET_SUBTLV(asla, EXT_EXTEND_ADM_GRP); + + /* Send admin-group zero for better compatibility + * https://www.rfc-editor.org/rfc/rfc7308#section-2.3.2 + */ + if (circuit->area->admin_group_send_zero && + !IS_SUBTLV(asla, EXT_ADM_GRP) && + !IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP)) { + asla->admin_group = 0; + SET_SUBTLV(asla, EXT_ADM_GRP); + admin_group_clear(&asla->ext_admin_group); + admin_group_allow_explicit_zero(&asla->ext_admin_group); + SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP); + } + + if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) { + asla->te_metric = ifp->link_params->te_metric; + SET_SUBTLV(asla, EXT_TE_METRIC); + } else + UNSET_SUBTLV(asla, EXT_TE_METRIC); + + if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) { + asla->delay = ifp->link_params->av_delay; + SET_SUBTLV(asla, EXT_DELAY); + } else + UNSET_SUBTLV(asla, EXT_DELAY); + + if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) { + asla->min_delay = ifp->link_params->min_delay; + asla->max_delay = ifp->link_params->max_delay; + SET_SUBTLV(asla, EXT_MM_DELAY); + } else { + UNSET_SUBTLV(asla, EXT_MM_DELAY); + } + + if (asla->standard_apps == ISIS_SABM_FLAG_X) + /* Flex-Algo ASLA does not need the following TE + * sub-TLVs + */ + continue; + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) { + asla->max_bw = ifp->link_params->max_bw; + SET_SUBTLV(asla, EXT_MAX_BW); + } else + UNSET_SUBTLV(asla, EXT_MAX_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) { + asla->max_rsv_bw = ifp->link_params->max_rsv_bw; + SET_SUBTLV(asla, EXT_MAX_RSV_BW); + } else + UNSET_SUBTLV(asla, EXT_MAX_RSV_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) { + for (i = 0; i < MAX_CLASS_TYPE; i++) + asla->unrsv_bw[i] = + ifp->link_params->unrsv_bw[i]; + SET_SUBTLV(asla, EXT_UNRSV_BW); + } else + UNSET_SUBTLV(asla, EXT_UNRSV_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) { + asla->delay_var = ifp->link_params->delay_var; + SET_SUBTLV(asla, EXT_DELAY_VAR); + } else + UNSET_SUBTLV(asla, EXT_DELAY_VAR); + + if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) { + asla->pkt_loss = ifp->link_params->pkt_loss; + SET_SUBTLV(asla, EXT_PKT_LOSS); + } else + UNSET_SUBTLV(asla, EXT_PKT_LOSS); + + if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) { + asla->res_bw = ifp->link_params->res_bw; + SET_SUBTLV(asla, EXT_RES_BW); + } else + UNSET_SUBTLV(asla, EXT_RES_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) { + asla->ava_bw = ifp->link_params->ava_bw; + SET_SUBTLV(asla, EXT_AVA_BW); + } else + UNSET_SUBTLV(asla, EXT_AVA_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) { + asla->use_bw = ifp->link_params->use_bw; + SET_SUBTLV(asla, EXT_USE_BW); + } else + UNSET_SUBTLV(asla, EXT_USE_BW); + } + + + for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla)) { + if (!asla->legacy && NO_SUBTLV(asla) && + admin_group_nb_words(&asla->ext_admin_group) == 0) + /* remove ASLA without info from the list of ASLAs to + * not send void ASLA + */ + isis_tlvs_del_asla_flex_algo(ext, asla); + } +} + +/* Main initialization / update function of the MPLS TE Circuit context */ +/* Call when interface TE Link parameters are modified */ +void isis_link_params_update(struct isis_circuit *circuit, + struct interface *ifp) +{ + int i; + struct prefix_ipv4 *addr; + struct prefix_ipv6 *addr6; + struct isis_ext_subtlvs *ext; + + /* Check if TE is enable or not */ + if (!circuit->area || !IS_MPLS_TE(circuit->area->mta)) + return; + + /* Sanity Check */ + if (ifp == NULL) + return; + + te_debug("ISIS-TE(%s): Update circuit parameters for interface %s", + circuit->area->area_tag, ifp->name); + + /* Check if MPLS TE Circuit context has not been already created */ + if (circuit->ext == NULL) { + circuit->ext = isis_alloc_ext_subtlvs(); + te_debug(" |- Allocated new Ext-subTLVs for interface %s", + ifp->name); + } + + ext = circuit->ext; + + /* Fulfill Extended subTLVs from interface link parameters */ + if (HAS_LINK_PARAMS(ifp)) { + /* STD_TE metrics */ + if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) { + ext->adm_group = ifp->link_params->admin_grp; + SET_SUBTLV(ext, EXT_ADM_GRP); + } else + UNSET_SUBTLV(ext, EXT_ADM_GRP); + + if (IS_PARAM_SET(ifp->link_params, LP_EXTEND_ADM_GRP)) { + admin_group_copy(&ext->ext_admin_group, + &ifp->link_params->ext_admin_grp); + SET_SUBTLV(ext, EXT_EXTEND_ADM_GRP); + } else + UNSET_SUBTLV(ext, EXT_EXTEND_ADM_GRP); + + /* Send admin-group zero for better compatibility + * https://www.rfc-editor.org/rfc/rfc7308#section-2.3.2 + */ + if (circuit->area->admin_group_send_zero && + !IS_SUBTLV(ext, EXT_ADM_GRP) && + !IS_SUBTLV(ext, EXT_EXTEND_ADM_GRP)) { + ext->adm_group = 0; + SET_SUBTLV(ext, EXT_ADM_GRP); + admin_group_clear(&ext->ext_admin_group); + admin_group_allow_explicit_zero(&ext->ext_admin_group); + SET_SUBTLV(ext, EXT_EXTEND_ADM_GRP); + } + + /* If known, register local IPv4 addr from ip_addr list */ + if (listcount(circuit->ip_addrs) != 0) { + addr = (struct prefix_ipv4 *)listgetdata( + (struct listnode *)listhead(circuit->ip_addrs)); + IPV4_ADDR_COPY(&ext->local_addr, &addr->prefix); + SET_SUBTLV(ext, EXT_LOCAL_ADDR); + } else + UNSET_SUBTLV(ext, EXT_LOCAL_ADDR); + + /* If known, register local IPv6 addr from ip_addr list */ + if (listcount(circuit->ipv6_non_link) != 0) { + addr6 = (struct prefix_ipv6 *)listgetdata( + (struct listnode *)listhead( + circuit->ipv6_non_link)); + IPV6_ADDR_COPY(&ext->local_addr6, &addr6->prefix); + SET_SUBTLV(ext, EXT_LOCAL_ADDR6); + } else + UNSET_SUBTLV(ext, EXT_LOCAL_ADDR6); + + /* + * Remote IPv4 and IPv6 addresses are now added in + * isis_mpls_te_adj_ip_enabled() to get the right IP address + * in particular for IPv6 to get the global IPv6 address and + * not the link-local IPv6 address. + */ + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) { + ext->max_bw = ifp->link_params->max_bw; + SET_SUBTLV(ext, EXT_MAX_BW); + } else + UNSET_SUBTLV(ext, EXT_MAX_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) { + ext->max_rsv_bw = ifp->link_params->max_rsv_bw; + SET_SUBTLV(ext, EXT_MAX_RSV_BW); + } else + UNSET_SUBTLV(ext, EXT_MAX_RSV_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) { + for (i = 0; i < MAX_CLASS_TYPE; i++) + ext->unrsv_bw[i] = + ifp->link_params->unrsv_bw[i]; + SET_SUBTLV(ext, EXT_UNRSV_BW); + } else + UNSET_SUBTLV(ext, EXT_UNRSV_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) { + ext->te_metric = ifp->link_params->te_metric; + SET_SUBTLV(ext, EXT_TE_METRIC); + } else + UNSET_SUBTLV(ext, EXT_TE_METRIC); + + /* TE metric extensions */ + if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) { + ext->delay = ifp->link_params->av_delay; + SET_SUBTLV(ext, EXT_DELAY); + } else + UNSET_SUBTLV(ext, EXT_DELAY); + + if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) { + ext->min_delay = ifp->link_params->min_delay; + ext->max_delay = ifp->link_params->max_delay; + SET_SUBTLV(ext, EXT_MM_DELAY); + } else + UNSET_SUBTLV(ext, EXT_MM_DELAY); + + if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) { + ext->delay_var = ifp->link_params->delay_var; + SET_SUBTLV(ext, EXT_DELAY_VAR); + } else + UNSET_SUBTLV(ext, EXT_DELAY_VAR); + + if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) { + ext->pkt_loss = ifp->link_params->pkt_loss; + SET_SUBTLV(ext, EXT_PKT_LOSS); + } else + UNSET_SUBTLV(ext, EXT_PKT_LOSS); + + if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) { + ext->res_bw = ifp->link_params->res_bw; + SET_SUBTLV(ext, EXT_RES_BW); + } else + UNSET_SUBTLV(ext, EXT_RES_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) { + ext->ava_bw = ifp->link_params->ava_bw; + SET_SUBTLV(ext, EXT_AVA_BW); + } else + UNSET_SUBTLV(ext, EXT_AVA_BW); + + if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) { + ext->use_bw = ifp->link_params->use_bw; + SET_SUBTLV(ext, EXT_USE_BW); + } else + UNSET_SUBTLV(ext, EXT_USE_BW); + + /* INTER_AS */ + if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) { + ext->remote_as = ifp->link_params->rmt_as; + ext->remote_ip = ifp->link_params->rmt_ip; + SET_SUBTLV(ext, EXT_RMT_AS); + SET_SUBTLV(ext, EXT_RMT_IP); + } else { + /* reset inter-as TE params */ + UNSET_SUBTLV(ext, EXT_RMT_AS); + UNSET_SUBTLV(ext, EXT_RMT_IP); + } + te_debug(" |- New MPLS-TE link parameters status 0x%x", + ext->status); + } else { + te_debug(" |- Reset Extended subTLVs status 0x%x", + ext->status); + /* Reset TE subTLVs keeping SR one's */ + if (IS_SUBTLV(ext, EXT_ADJ_SID)) + ext->status = EXT_ADJ_SID; + else if (IS_SUBTLV(ext, EXT_LAN_ADJ_SID)) + ext->status = EXT_LAN_ADJ_SID; + else if (IS_SUBTLV(ext, EXT_SRV6_LAN_ENDX_SID)) + ext->status = EXT_SRV6_LAN_ENDX_SID; + else if (IS_SUBTLV(ext, EXT_SRV6_ENDX_SID)) + ext->status = EXT_SRV6_ENDX_SID; + else + ext->status = 0; + } + + isis_link_params_update_asla(circuit, ifp); + + return; +} + +static int _isis_mpls_te_adj_ip_enabled(struct isis_adjacency *adj, int family, + bool global) +{ + struct isis_circuit *circuit; + struct isis_ext_subtlvs *ext; + + circuit = adj->circuit; + + /* Check that MPLS TE is enabled */ + if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext) + return 0; + + ext = circuit->ext; + + /* Determine nexthop IP address */ + switch (family) { + case AF_INET: + if (!circuit->ip_router || !adj->ipv4_address_count) + UNSET_SUBTLV(ext, EXT_NEIGH_ADDR); + else { + IPV4_ADDR_COPY(&ext->neigh_addr, + &adj->ipv4_addresses[0]); + SET_SUBTLV(ext, EXT_NEIGH_ADDR); + } + break; + case AF_INET6: + /* Nothing to do for link-local addresses - ie. not global. + * https://datatracker.ietf.org/doc/html/rfc6119#section-3.1.1 + * Because the IPv6 traffic engineering TLVs present in LSPs are + * propagated across networks, they MUST NOT use link-local + * addresses. + */ + if (!global) + return 0; + + if (!circuit->ipv6_router || !adj->global_ipv6_count) + UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6); + else { + IPV6_ADDR_COPY(&ext->neigh_addr6, + &adj->global_ipv6_addrs[0]); + SET_SUBTLV(ext, EXT_NEIGH_ADDR6); + } + break; + default: + return 0; + } + + return 0; +} + +static int isis_mpls_te_adj_ip_enabled(struct isis_adjacency *adj, int family, + bool global) +{ + int ret; + + /* Sanity Check */ + if (!adj || !adj->circuit) + return 0; + + ret = _isis_mpls_te_adj_ip_enabled(adj, family, global); + + /* Update LSP */ + lsp_regenerate_schedule(adj->circuit->area, adj->circuit->is_type, 0); + + return ret; +} + +static int _isis_mpls_te_adj_ip_disabled(struct isis_adjacency *adj, int family, + bool global) +{ + struct isis_circuit *circuit; + struct isis_ext_subtlvs *ext; + + circuit = adj->circuit; + + /* Check that MPLS TE is enabled */ + if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext) + return 0; + + ext = circuit->ext; + + /* Update MPLS TE IP address parameters if possible */ + if (!IS_MPLS_TE(circuit->area->mta) || !IS_EXT_TE(ext)) + return 0; + + /* Determine nexthop IP address */ + switch (family) { + case AF_INET: + UNSET_SUBTLV(ext, EXT_NEIGH_ADDR); + break; + case AF_INET6: + if (global) + UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6); + break; + default: + return 0; + } + + return 0; +} + +static int isis_mpls_te_adj_ip_disabled(struct isis_adjacency *adj, int family, + bool global) +{ + int ret; + + /* Sanity Check */ + if (!adj || !adj->circuit || !adj->circuit->ext) + return 0; + + ret = _isis_mpls_te_adj_ip_disabled(adj, family, global); + + /* Update LSP */ + lsp_regenerate_schedule(adj->circuit->area, adj->circuit->is_type, 0); + + return ret; +} + +static void isis_mpls_te_circuit_ip_update(struct isis_circuit *circuit) +{ + struct isis_adjacency *adj; + + /* https://datatracker.ietf.org/doc/html/rfc6119#section-3.2.3 + * This sub-TLV of the Extended IS Reachability TLV is used for point- + * to-point links + */ + if (circuit->circ_type != CIRCUIT_T_P2P) + return; + + adj = circuit->u.p2p.neighbor; + + if (!adj) + return; + + /* Nothing to do for link-local addresses. + * https://datatracker.ietf.org/doc/html/rfc6119#section-3.1.1 + * Because the IPv6 traffic engineering TLVs present in LSPs are + * propagated across networks, they MUST NOT use link-local addresses. + */ + if (adj->ipv4_address_count > 0) + _isis_mpls_te_adj_ip_enabled(adj, AF_INET, false); + else + _isis_mpls_te_adj_ip_disabled(adj, AF_INET, false); + + if (adj->global_ipv6_count > 0) + _isis_mpls_te_adj_ip_enabled(adj, AF_INET6, true); + else + _isis_mpls_te_adj_ip_disabled(adj, AF_INET6, true); +} + + +int isis_mpls_te_update(struct interface *ifp) +{ + struct isis_circuit *circuit; + uint8_t rc = 1; + + /* Sanity Check */ + if (ifp == NULL) + return rc; + + /* Get circuit context from interface */ + circuit = circuit_scan_by_ifp(ifp); + if (circuit == NULL) + return rc; + + /* Update TE TLVs ... */ + isis_link_params_update(circuit, ifp); + + /* ... and LSP */ + if (circuit->area && + (IS_MPLS_TE(circuit->area->mta) +#ifndef FABRICD + || !list_isempty(circuit->area->flex_algos->flex_algos) +#endif /* ifndef FABRICD */ + )) + lsp_regenerate_schedule(circuit->area, circuit->is_type, 0); + + rc = 0; + return rc; +} + + +/** + * Export Link State information to consumer daemon through ZAPI Link State + * Opaque Message. + * + * @param type Type of Link State Element i.e. Vertex, Edge or Subnet + * @param link_state Pointer to Link State Vertex, Edge or Subnet + * + * @return 0 if success, -1 otherwise + */ +static int isis_te_export(uint8_t type, void *link_state) +{ + struct ls_message msg = {}; + int rc = 0; + + switch (type) { + case LS_MSG_TYPE_NODE: + ls_vertex2msg(&msg, (struct ls_vertex *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + case LS_MSG_TYPE_ATTRIBUTES: + ls_edge2msg(&msg, (struct ls_edge *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + case LS_MSG_TYPE_PREFIX: + ls_subnet2msg(&msg, (struct ls_subnet *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + default: + rc = -1; + break; + } + + return rc; +} + +/** + * Parse LSP and build corresponding vertex. If vertex doesn't exist in the + * Link State Database it is created otherwise updated. + * + * @param ted Traffic Engineering Link State Database + * @param lsp IS-IS Link State PDU + * + * @return Link State Vertex or NULL in case of error + */ +static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp) +{ + struct ls_vertex *vertex = NULL; + struct ls_node *old, lnode = {}; + struct isis_tlvs *tlvs; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + /* Sanity check */ + if (!ted || !lsp) + return NULL; + + /* Compute Link State Node ID from IS-IS sysID ... */ + if (lsp->level == ISIS_LEVEL1) + lnode.adv.origin = ISIS_L1; + else + lnode.adv.origin = ISIS_L2; + memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN); + lnode.adv.id.iso.level = lsp->level; + /* ... and search the corresponding vertex */ + vertex = ls_find_vertex_by_id(ted, lnode.adv); + /* Create a new one if not found */ + if (!vertex) { + old = ls_node_new(lnode.adv, inaddr_any, in6addr_any); + old->type = STANDARD; + vertex = ls_vertex_add(ted, old); + } + old = vertex->node; + te_debug(" |- %s Vertex (%" PRIu64 ") for node %s", + vertex->status == NEW ? "Create" : "Found", vertex->key, + print_sys_hostname(old->adv.id.iso.sys_id)); + + /* Fulfill Link State Node information */ + tlvs = lsp->tlvs; + if (tlvs) { + if (tlvs->te_router_id) { + IPV4_ADDR_COPY(&lnode.router_id, tlvs->te_router_id); + SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID); + } + if (tlvs->te_router_id_ipv6) { + IPV6_ADDR_COPY(&lnode.router_id6, + tlvs->te_router_id_ipv6); + SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID6); + } + if (tlvs->hostname) { + strlcpy(lnode.name, tlvs->hostname, MAX_NAME_LENGTH); + SET_FLAG(lnode.flags, LS_NODE_NAME); + } + if (tlvs->router_cap) { + struct isis_router_cap *cap = tlvs->router_cap; + + if (cap->srgb.lower_bound != 0 + && cap->srgb.range_size != 0) { + SET_FLAG(lnode.flags, LS_NODE_SR); + lnode.srgb.flag = cap->srgb.flags; + lnode.srgb.lower_bound = cap->srgb.lower_bound; + lnode.srgb.range_size = cap->srgb.range_size; + for (int i = 0; i < LIB_LS_SR_ALGO_COUNT; i++) + lnode.algo[i] = cap->algo[i]; + } + + if (cap->srlb.lower_bound != 0 + && cap->srlb.range_size != 0) { + lnode.srlb.lower_bound = cap->srlb.lower_bound; + lnode.srlb.range_size = cap->srlb.range_size; + SET_FLAG(lnode.flags, LS_NODE_SRLB); + } + if (cap->msd != 0) { + lnode.msd = cap->msd; + SET_FLAG(lnode.flags, LS_NODE_MSD); + } + if (cap->srv6_cap.is_srv6_capable) { + SET_FLAG(lnode.flags, LS_NODE_SRV6); + lnode.srv6_cap_flags = cap->srv6_cap.flags; + memcpy(&lnode.srv6_msd, &cap->srv6_msd, + sizeof(struct isis_srv6_msd)); + } + } + } + + /* Update Link State Node information */ + if (!ls_node_same(old, &lnode)) { + te_debug(" |- Update Link State Node information"); + memcpy(old, &lnode, sizeof(struct ls_node)); + if (vertex->status != NEW) + vertex->status = UPDATE; + } + + /* Set self TED vertex if LSP corresponds to the own router */ + if (lsp->own_lsp) + ted->self = vertex; + + return vertex; +} + +/** + * Get Link State Edge from Link State Attributes in TE Database. + * Edge structure is dynamically allocated and fulfill with Link State + * Attributes if not found. + * + * @param ted Link State Database + * @param attr Link State Attributes + * + * @return New Link State Edge if success, NULL otherwise + */ +static struct ls_edge *get_edge(struct ls_ted *ted, struct ls_attributes *attr) +{ + struct ls_edge *edge; + struct ls_standard *std; + struct ls_edge_key key; + + /* Check parameters */ + if (!ted || !attr) + return NULL; + + std = &attr->standard; + + /* Compute keys in function of local address (IPv4/v6) or identifier */ + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) { + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &std->local); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) { + key.family = AF_INET6; + IPV6_ADDR_COPY(&key.k.addr6, &std->local6); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) { + key.family = AF_LOCAL; + key.k.link_id = (((uint64_t)std->local_id) & 0xffffffff) | + ((uint64_t)std->remote_id << 32); + } else { + key.family = AF_UNSPEC; + } + + /* Stop here if we don't got a valid key */ + if (key.family == AF_UNSPEC) + return NULL; + + /* Get corresponding Edge by key from Link State Data Base */ + edge = ls_find_edge_by_key(ted, key); + + /* and create new one if not exist */ + if (!edge) { + edge = ls_edge_add(ted, attr); + /* + * Edge could be Null if no local ID is found in Attributes. + * Stop the processing as without any local ID it is not + * possible to store Edge in the TED. + */ + if (!edge) + return NULL; + } + + if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR)) + te_debug(" |- %s Edge (%pI4) from Extended Reach. %pI4", + edge->status == NEW ? "Create" : "Found", + &edge->key.k.addr, &attr->standard.local); + else if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6)) + te_debug(" |- %s Edge (%pI6) from Extended Reach. %pI6", + edge->status == NEW ? "Create" : "Found", + &edge->key.k.addr6, &attr->standard.local6); + else + te_debug(" |- %s Edge (%" PRIu64 ")", + edge->status == NEW ? "Create" : "Found", + edge->key.k.link_id); + + return edge; +} + +/** + * Get Link State Attributes from IS-IS Sub-TLVs. Structure is dynamically + * allocated and should be free once not use anymore. + * + * @param adv Link State Node ID + * @param tlvs IS-IS Sub TLVs + * + * @return New Link State attributes if success, NULL otherwise + */ +static struct ls_attributes *get_attributes(struct ls_node_id adv, + struct isis_ext_subtlvs *tlvs) +{ + struct ls_attributes *attr; + struct in_addr local = {.s_addr = INADDR_ANY}; + struct in6_addr local6 = in6addr_any; + uint32_t local_id = 0; + + /* Got Local identifier */ + if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR)) + local.s_addr = tlvs->local_addr.s_addr; + + if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR6)) + memcpy(&local6, &tlvs->local_addr6, IPV6_MAX_BYTELEN); + + if (CHECK_FLAG(tlvs->status, EXT_LLRI)) + local_id = tlvs->local_llri; + + /* Create LS Attributes */ + attr = ls_attributes_new(adv, local, local6, local_id); + if (!attr) + return NULL; + + /* Browse sub-TLV and fulfill Link State Attributes */ + if (CHECK_FLAG(tlvs->status, EXT_ADM_GRP)) { + attr->standard.admin_group = tlvs->adm_group; + SET_FLAG(attr->flags, LS_ATTR_ADM_GRP); + } + if (CHECK_FLAG(tlvs->status, EXT_EXTEND_ADM_GRP)) { + admin_group_copy(&attr->ext_admin_group, + &tlvs->ext_admin_group); + SET_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP); + } + if (CHECK_FLAG(tlvs->status, EXT_LLRI)) { + attr->standard.local_id = tlvs->local_llri; + attr->standard.remote_id = tlvs->remote_llri; + SET_FLAG(attr->flags, LS_ATTR_LOCAL_ID); + SET_FLAG(attr->flags, LS_ATTR_NEIGH_ID); + } + if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR)) { + attr->standard.remote.s_addr = tlvs->neigh_addr.s_addr; + SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR); + } + if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR6)) { + memcpy(&attr->standard.remote6, &tlvs->neigh_addr6, + IPV6_MAX_BYTELEN); + SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6); + } + if (CHECK_FLAG(tlvs->status, EXT_MAX_BW)) { + attr->standard.max_bw = tlvs->max_bw; + SET_FLAG(attr->flags, LS_ATTR_MAX_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_MAX_RSV_BW)) { + attr->standard.max_rsv_bw = tlvs->max_rsv_bw; + SET_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_UNRSV_BW)) { + memcpy(&attr->standard.unrsv_bw, tlvs->unrsv_bw, + ISIS_SUBTLV_UNRSV_BW_SIZE); + SET_FLAG(attr->flags, LS_ATTR_UNRSV_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_TE_METRIC)) { + attr->standard.te_metric = tlvs->te_metric; + SET_FLAG(attr->flags, LS_ATTR_TE_METRIC); + } + if (CHECK_FLAG(tlvs->status, EXT_RMT_AS)) { + attr->standard.remote_as = tlvs->remote_as; + SET_FLAG(attr->flags, LS_ATTR_REMOTE_AS); + } + if (CHECK_FLAG(tlvs->status, EXT_RMT_IP)) { + attr->standard.remote_addr = tlvs->remote_ip; + SET_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR); + } + if (CHECK_FLAG(tlvs->status, EXT_DELAY)) { + attr->extended.delay = tlvs->delay; + SET_FLAG(attr->flags, LS_ATTR_DELAY); + } + if (CHECK_FLAG(tlvs->status, EXT_MM_DELAY)) { + attr->extended.min_delay = tlvs->min_delay; + attr->extended.max_delay = tlvs->max_delay; + SET_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY); + } + if (CHECK_FLAG(tlvs->status, EXT_DELAY_VAR)) { + attr->extended.jitter = tlvs->delay_var; + SET_FLAG(attr->flags, LS_ATTR_JITTER); + } + if (CHECK_FLAG(tlvs->status, EXT_PKT_LOSS)) { + attr->extended.pkt_loss = tlvs->pkt_loss; + SET_FLAG(attr->flags, LS_ATTR_PACKET_LOSS); + } + if (CHECK_FLAG(tlvs->status, EXT_AVA_BW)) { + attr->extended.ava_bw = tlvs->ava_bw; + SET_FLAG(attr->flags, LS_ATTR_AVA_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_RES_BW)) { + attr->extended.rsv_bw = tlvs->res_bw; + SET_FLAG(attr->flags, LS_ATTR_RSV_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_USE_BW)) { + attr->extended.used_bw = tlvs->use_bw; + SET_FLAG(attr->flags, LS_ATTR_USE_BW); + } + if (CHECK_FLAG(tlvs->status, EXT_ADJ_SID)) { + struct isis_adj_sid *adj = + (struct isis_adj_sid *)tlvs->adj_sid.head; + int i; + for (; adj; adj = adj->next) { + i = adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0; + i += adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0; + attr->adj_sid[i].flags = adj->flags; + attr->adj_sid[i].weight = adj->weight; + attr->adj_sid[i].sid = adj->sid; + switch (i) { + case ADJ_PRI_IPV4: + SET_FLAG(attr->flags, LS_ATTR_ADJ_SID); + break; + case ADJ_BCK_IPV4: + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID); + break; + case ADJ_PRI_IPV6: + SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6); + break; + case ADJ_BCK_IPV6: + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6); + break; + } + } + } + if (CHECK_FLAG(tlvs->status, EXT_LAN_ADJ_SID)) { + struct isis_lan_adj_sid *ladj = + (struct isis_lan_adj_sid *)tlvs->lan_sid.head; + int i; + for (; ladj; ladj = ladj->next) { + i = ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0; + i += ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0; + attr->adj_sid[i].flags = ladj->flags; + attr->adj_sid[i].weight = ladj->weight; + attr->adj_sid[i].sid = ladj->sid; + memcpy(&attr->adj_sid[i].neighbor.sysid, + &ladj->neighbor_id, ISIS_SYS_ID_LEN); + switch (i) { + case ADJ_PRI_IPV4: + SET_FLAG(attr->flags, LS_ATTR_ADJ_SID); + break; + case ADJ_BCK_IPV4: + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID); + break; + case ADJ_PRI_IPV6: + SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6); + break; + case ADJ_BCK_IPV6: + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6); + break; + } + } + } + if (CHECK_FLAG(tlvs->status, EXT_SRV6_ENDX_SID)) { + struct isis_srv6_endx_sid_subtlv *endx = + (struct isis_srv6_endx_sid_subtlv *) + tlvs->srv6_endx_sid.head; + int i; + + for (; endx; endx = endx->next) { + if (endx->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG) { + i = 1; + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID); + } else { + i = 0; + SET_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID); + } + attr->adj_srv6_sid[i].flags = endx->flags; + attr->adj_srv6_sid[i].weight = endx->weight; + memcpy(&attr->adj_srv6_sid[i].sid, &endx->sid, + sizeof(struct in6_addr)); + attr->adj_srv6_sid[i].endpoint_behavior = endx->behavior; + } + } + if (CHECK_FLAG(tlvs->status, EXT_SRV6_LAN_ENDX_SID)) { + struct isis_srv6_lan_endx_sid_subtlv *lendx = + (struct isis_srv6_lan_endx_sid_subtlv *) + tlvs->srv6_lan_endx_sid.head; + int i; + + for (; lendx; lendx = lendx->next) { + if (lendx->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG) { + i = 1; + SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID); + } else { + i = 0; + SET_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID); + } + memcpy(&attr->adj_srv6_sid[i].neighbor.sysid, + &lendx->neighbor_id, ISIS_SYS_ID_LEN); + attr->adj_srv6_sid[i].flags = lendx->flags; + attr->adj_srv6_sid[i].weight = lendx->weight; + memcpy(&attr->adj_srv6_sid[i].sid, &lendx->sid, + sizeof(struct in6_addr)); + attr->adj_srv6_sid[i].endpoint_behavior = + lendx->behavior; + } + } + return attr; +} + +/** + * Parse Extended Reachability TLVs and create or update the corresponding + * Link State Edge and Attributes. Vertex connections are also updated if + * needed based on the remote IP address of the Edge and existing reverse Edge. + * + * @param id ID of Extended IS + * @param metric Metric of the link + * @param old_metric Boolean that indicate if it is an old metric (no TE) + * @param tlvs SubTlvs that contains TE information + * @param arg IS-IS TE argument (TED, Vertex, and export indication) + * + * @return 0 if success, -1 otherwise + */ +static int lsp_to_edge_cb(const uint8_t *id, uint32_t metric, bool old_metric, + struct isis_ext_subtlvs *tlvs, void *arg) +{ + struct isis_te_args *args = (struct isis_te_args *)arg; + struct ls_vertex *vertex; + struct ls_edge *edge, *dst; + struct ls_attributes *attr; + + te_debug(" |- Process Extended IS for %pSY", id); + + /* Check parameters */ + if (old_metric || !args || !tlvs) + return LSP_ITER_CONTINUE; + + /* Initialize Link State Attributes */ + vertex = args->vertex; + attr = get_attributes(vertex->node->adv, tlvs); + /* + * Attributes may be Null if no local ID has been found in the LSP. + * Stop processing here as without any local ID it is not possible to + * create corresponding Edge in the TED. + */ + if (!attr) + return LSP_ITER_CONTINUE; + + attr->metric = metric; + SET_FLAG(attr->flags, LS_ATTR_METRIC); + + /* Get corresponding Edge from Link State Data Base */ + edge = get_edge(args->ted, attr); + /* + * Edge could be Null if no local ID has been found in Attributes. + * Stop processing here as without any local ID it is not possible to + * create corresponding Edge in the TED. + */ + if (!edge) { + ls_attributes_del(attr); + return LSP_ITER_CONTINUE; + } + + /* Update Attribute fields if there are different */ + if (edge->status != NEW) { + if (!ls_attributes_same(edge->attributes, attr)) { + te_debug(" |- Update Edge Attributes information"); + ls_attributes_del(edge->attributes); + edge->attributes = attr; + edge->status = UPDATE; + } else { + if (edge->attributes != attr) + ls_attributes_del(attr); + edge->status = SYNC; + } + } + + /* Try to update remote Link from remote address or reachability ID */ + if (edge->key.family == AF_INET) + te_debug(" |- Link Edge (%pI4) to destination vertex (%s)", + &edge->key.k.addr, print_sys_hostname(id)); + else if (edge->key.family == AF_INET6) + te_debug(" |- Link Edge (%pI6) to destination vertex (%s)", + &edge->key.k.addr6, print_sys_hostname(id)); + else if (edge->key.family == AF_LOCAL) + te_debug(" |- Link Edge (%" PRIu64 + ") to destination vertex (%s)", + edge->key.k.link_id, print_sys_hostname(id)); + else + te_debug( + " |- Link Edge (Unknown) to destination vertex (%s)", + print_sys_hostname(id)); + + dst = ls_find_edge_by_destination(args->ted, edge->attributes); + if (dst) { + /* Attach remote link if not set */ + if (edge->source && dst->destination == NULL) { + vertex = edge->source; + if (vertex->incoming_edges) + listnode_add_sort_nodup(vertex->incoming_edges, + dst); + dst->destination = vertex; + } + /* and destination vertex to this edge if not set */ + if (dst->source && edge->destination == NULL) { + vertex = dst->source; + if (vertex->incoming_edges) + listnode_add_sort_nodup(vertex->incoming_edges, + edge); + edge->destination = vertex; + } + } else { + /* Search dst. Vertex by Extended Reach. ID if not found */ + if (edge->destination == NULL) { + vertex = ls_find_vertex_by_key(args->ted, + sysid_to_key(id)); + if (vertex && vertex->incoming_edges) + listnode_add_sort_nodup(vertex->incoming_edges, + edge); + edge->destination = vertex; + } + } + + /* Update status and Export Link State Edge if needed */ + if (edge->status != SYNC) { + if (args->export) + isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; + } + + return LSP_ITER_CONTINUE; +} + +/** + * Parse Extended IP Reachability or MT IPv6 Reachability TLVs and create or + * update the corresponding Link State Subnet and Prefix. + * + * @param prefix Prefix associated to this subnet + * @param metric Metric of this prefix + * @param external Boolean to indicate if the prefix is external + * @param subtlvs Subtlvs if any (mostly Segment Routing ID) + * @param arg IS-IS TE argument (TED, Vertex, and export indication) + * + * @return 0 if success, -1 otherwise + */ +static int lsp_to_subnet_cb(const struct prefix *prefix, uint32_t metric, + bool external, struct isis_subtlvs *subtlvs, + void *arg) +{ + struct isis_te_args *args = (struct isis_te_args *)arg; + struct ls_vertex *vertex; + struct ls_subnet *subnet; + struct ls_prefix *ls_pref; + struct listnode *node; + struct ls_edge *edge; + struct ls_standard *std = NULL; + struct prefix p; + + /* Sanity Check */ + if (!args || !prefix) + return LSP_ITER_CONTINUE; + + te_debug(" |- Process Extended %s Reachability %pFX", + prefix->family == AF_INET ? "IP" : "IPv6", prefix); + + vertex = args->vertex; + + /* + * Prefix with mask different from /32 or /128 are advertised by at + * least 2 nodes. To avoid subnet attached to undetermined vertex, and + * gives the possibility to send the information to client e.g. BGP for + * Link State advertisement, we adjust the prefix with the corresponding + * IP address of the belonging interface when it is available. Other + * prefixes are kept unchanged. + */ + if (prefix->family == AF_INET && prefix->prefixlen < IPV4_MAX_BITLEN) { + std = NULL; + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) { + if (!CHECK_FLAG(edge->attributes->flags, + LS_ATTR_LOCAL_ADDR)) + continue; + + p.u.prefix4 = edge->attributes->standard.local; + p.family = AF_INET; + p.prefixlen = prefix->prefixlen; + apply_mask_ipv4((struct prefix_ipv4 *)&p); + if (IPV4_ADDR_SAME(&p.u.prefix4, &prefix->u.prefix4)) { + std = &edge->attributes->standard; + break; + } + } + if (std) + p.u.prefix4 = std->local; + + } else if (prefix->family == AF_INET6 + && prefix->prefixlen < IPV6_MAX_BITLEN) { + std = NULL; + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) { + if (!CHECK_FLAG(edge->attributes->flags, + LS_ATTR_LOCAL_ADDR6)) + continue; + + p.u.prefix6 = edge->attributes->standard.local6; + p.family = AF_INET6; + p.prefixlen = prefix->prefixlen; + apply_mask_ipv6((struct prefix_ipv6 *)&p); + if (IPV6_ADDR_SAME(&p.u.prefix6, &prefix->u.prefix6)) { + std = &edge->attributes->standard; + break; + } + } + if (std) + p.u.prefix6 = std->local6; + } + if (!std) + prefix_copy(&p, prefix); + else { + /* Remove old subnet if any before prefix adjustment */ + subnet = ls_find_subnet(args->ted, prefix); + if (subnet) { + if (args->export) { + subnet->status = DELETE; + isis_te_export(LS_MSG_TYPE_PREFIX, subnet); + } + te_debug(" |- Remove subnet with prefix %pFX", + &subnet->key); + ls_subnet_del_all(args->ted, subnet); + } + te_debug(" |- Adjust prefix %pFX with local address to: %pFX", + prefix, &p); + } + + /* Search existing Subnet in TED ... */ + subnet = ls_find_subnet(args->ted, &p); + /* ... and create a new Subnet if not found */ + if (!subnet) { + ls_pref = ls_prefix_new(vertex->node->adv, &p); + subnet = ls_subnet_add(args->ted, ls_pref); + /* Stop processing if we are unable to create a new subnet */ + if (!subnet) + return LSP_ITER_CONTINUE; + } + ls_pref = subnet->ls_pref; + + te_debug(" |- %s Subnet from prefix %pFX", + subnet->status == NEW ? "Create" : "Found", &p); + + /* Update Metric */ + if (!CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC) + || (ls_pref->metric != metric)) { + ls_pref->metric = metric; + SET_FLAG(ls_pref->flags, LS_PREF_METRIC); + if (subnet->status != NEW) + subnet->status = UPDATE; + } else { + if (subnet->status == ORPHAN) + subnet->status = SYNC; + } + + /* Update Prefix SID if any */ + if (subtlvs && subtlvs->prefix_sids.count != 0) { + struct isis_prefix_sid *psid; + struct ls_sid sr = {}; + + psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head; + sr.algo = psid->algorithm; + sr.sid_flag = psid->flags; + sr.sid = psid->value; + + if (!CHECK_FLAG(ls_pref->flags, LS_PREF_SR) + || !memcmp(&ls_pref->sr, &sr, sizeof(struct ls_sid))) { + memcpy(&ls_pref->sr, &sr, sizeof(struct ls_sid)); + SET_FLAG(ls_pref->flags, LS_PREF_SR); + if (subnet->status != NEW) + subnet->status = UPDATE; + } else { + if (subnet->status == ORPHAN) + subnet->status = SYNC; + } + } else { + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) { + UNSET_FLAG(ls_pref->flags, LS_PREF_SR); + if (subnet->status != NEW) + subnet->status = UPDATE; + } else { + if (subnet->status == ORPHAN) + subnet->status = SYNC; + } + } + + /* Update status and Export Link State Edge if needed */ + if (subnet->status != SYNC) { + if (args->export) + isis_te_export(LS_MSG_TYPE_PREFIX, subnet); + subnet->status = SYNC; + } + + return LSP_ITER_CONTINUE; +} + +/** + * Parse ISIS LSP to fulfill the Link State Database + * + * @param ted Link State Database + * @param lsp ISIS Link State PDU + */ +static void isis_te_parse_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp) +{ + struct ls_ted *ted; + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct listnode *node; + struct isis_te_args args; + + /* Sanity Check */ + if (!IS_MPLS_TE(mta) || !mta->ted || !lsp) + return; + + ted = mta->ted; + + te_debug("ISIS-TE(%s): Parse LSP %pSY", lsp->area->area_tag, + lsp->hdr.lsp_id); + + /* First parse LSP to obtain the corresponding Vertex */ + vertex = lsp_to_vertex(ted, lsp); + if (!vertex) { + zlog_warn("Unable to build Vertex from LSP %pSY. Abort!", + lsp->hdr.lsp_id); + return; + } + + /* Check if Vertex has been modified */ + if (vertex->status != SYNC) { + /* Vertex is out of sync: export it if requested */ + if (IS_EXPORT_TE(mta)) + isis_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + } + + /* Mark outgoing Edges and Subnets as ORPHAN to detect deletion */ + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) + edge->status = ORPHAN; + + for (ALL_LIST_ELEMENTS_RO(vertex->prefixes, node, subnet)) + subnet->status = ORPHAN; + + /* Process all Extended Reachability in LSP (all fragments) */ + args.ted = ted; + args.vertex = vertex; + args.export = mta->export; + isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV4_UNICAST, lsp_to_edge_cb, + &args); + + isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV6_UNICAST, lsp_to_edge_cb, + &args); + + /* Process all Extended IP (v4 & v6) in LSP (all fragments) */ + isis_lsp_iterate_ip_reach(lsp, AF_INET, ISIS_MT_IPV4_UNICAST, + lsp_to_subnet_cb, &args); + isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV6_UNICAST, + lsp_to_subnet_cb, &args); + isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV4_UNICAST, + lsp_to_subnet_cb, &args); + + /* Clean remaining Orphan Edges or Subnets */ + if (IS_EXPORT_TE(mta)) + ls_vertex_clean(ted, vertex, zclient); + else + ls_vertex_clean(ted, vertex, NULL); +} + +/** + * Delete Link State Database Vertex, Edge & Prefix that correspond to this + * ISIS Link State PDU + * + * @param ted Link State Database + * @param lsp ISIS Link State PDU + */ +static void isis_te_delete_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp) +{ + struct ls_ted *ted; + struct ls_vertex *vertex = NULL; + struct ls_node lnode = {}; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct listnode *nnode, *node; + + /* Sanity Check */ + if (!IS_MPLS_TE(mta) || !mta->ted || !lsp) + return; + + te_debug("ISIS-TE(%s): Delete Link State TED objects from LSP %pSY", + lsp->area->area_tag, lsp->hdr.lsp_id); + + /* Compute Link State Node ID from IS-IS sysID ... */ + if (lsp->level == ISIS_LEVEL1) + lnode.adv.origin = ISIS_L1; + else + lnode.adv.origin = ISIS_L2; + memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN); + lnode.adv.id.iso.level = lsp->level; + ted = mta->ted; + /* ... and search the corresponding vertex */ + vertex = ls_find_vertex_by_id(ted, lnode.adv); + if (!vertex) + return; + + te_debug(" |- Delete Vertex %s", vertex->node->name); + + /* + * We can't use the ls_vertex_del_all() function if export TE is set, + * as we must first advertise the client daemons of each removal. + */ + /* Remove outgoing Edges */ + for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) { + if (IS_EXPORT_TE(mta)) { + edge->status = DELETE; + isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + } + ls_edge_del_all(ted, edge); + } + + /* Disconnect incoming Edges */ + for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) { + ls_disconnect(vertex, edge, false); + if (edge->source == NULL) { + if (IS_EXPORT_TE(mta)) { + edge->status = DELETE; + isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + } + ls_edge_del_all(ted, edge); + } + } + + /* Remove subnets */ + for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) { + if (IS_EXPORT_TE(mta)) { + subnet->status = DELETE; + isis_te_export(LS_MSG_TYPE_PREFIX, subnet); + } + ls_subnet_del_all(ted, subnet); + } + + /* Then remove Link State Node */ + if (IS_EXPORT_TE(mta)) { + vertex->status = DELETE; + isis_te_export(LS_MSG_TYPE_NODE, vertex); + } + ls_node_del(vertex->node); + + /* Finally, remove Vertex */ + ls_vertex_del(ted, vertex); +} + +/** + * Process ISIS LSP according to the event to add, update or remove + * corresponding vertex, edge and prefix in the Link State database. + * Since LSP could be fragmented, the function starts by searching the root LSP + * to retrieve the complete LSP, including eventual fragment before processing + * all of them. + * + * @param lsp ISIS Link State PDU + * @param event LSP event: ADD, UPD, INC & DEL (TICK are ignored) + * + */ +void isis_te_lsp_event(struct isis_lsp *lsp, enum lsp_event event) +{ + struct isis_area *area; + struct isis_lsp *lsp0; + + /* Sanity check */ + if (!lsp || !lsp->area) + return; + + area = lsp->area; + if (!IS_MPLS_TE(area->mta)) + return; + + /* Adjust LSP0 in case of fragment */ + if (LSP_FRAGMENT(lsp->hdr.lsp_id)) + lsp0 = lsp->lspu.zero_lsp; + else + lsp0 = lsp; + + /* Then process event */ + switch (event) { + case LSP_ADD: + case LSP_UPD: + case LSP_INC: + isis_te_parse_lsp(area->mta, lsp0); + break; + case LSP_DEL: + isis_te_delete_lsp(area->mta, lsp0); + break; + case LSP_UNKNOWN: + case LSP_TICK: + break; + } +} + +/** + * Send the whole Link State Traffic Engineering Database to the consumer that + * request it through a ZAPI Link State Synchronous Opaque Message. + * + * @param info ZAPI Opaque message + * + * @return 0 if success, -1 otherwise + */ +int isis_te_sync_ted(struct zapi_opaque_reg_info dst) +{ + struct listnode *node, *inode; + struct isis *isis; + struct isis_area *area; + struct mpls_te_area *mta; + int rc = -1; + + te_debug("ISIS-TE(%s): Received TED synchro from client %d", __func__, + dst.proto); + /* For each area, send TED if TE distribution is enabled */ + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + mta = area->mta; + if (IS_MPLS_TE(mta) && IS_EXPORT_TE(mta)) { + te_debug(" |- Export TED from area %s", + area->area_tag); + rc = ls_sync_ted(mta->ted, zclient, &dst); + if (rc != 0) + return rc; + } + } + } + + return rc; +} + +/** + * Initialize the Link State database from the LSP already stored for this area + * + * @param area ISIS area + */ +void isis_te_init_ted(struct isis_area *area) +{ + struct isis_lsp *lsp; + + /* Iterate over all lsp. */ + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) + frr_each (lspdb, &area->lspdb[level - 1], lsp) + isis_te_parse_lsp(area->mta, lsp); +} + +/* Following are vty command functions */ +#ifndef FABRICD + +static void show_router_id(struct vty *vty, struct isis_area *area) +{ + bool no_match = true; + + vty_out(vty, "Area %s:\n", area->area_tag); + if (area->mta->router_id.s_addr != 0) { + vty_out(vty, " MPLS-TE IPv4 Router-Address: %pI4\n", + &area->mta->router_id); + no_match = false; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&area->mta->router_id_ipv6)) { + vty_out(vty, " MPLS-TE IPv6 Router-Address: %pI6\n", + &area->mta->router_id_ipv6); + no_match = false; + } + if (no_match) + vty_out(vty, " N/A\n"); +} + +DEFUN(show_isis_mpls_te_router, + show_isis_mpls_te_router_cmd, + "show " PROTO_NAME " [vrf ] mpls-te router", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR "All VRFs\n" + MPLS_TE_STR "Router information\n") +{ + + struct listnode *anode, *inode; + struct isis_area *area; + struct isis *isis = NULL; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + if (!IS_MPLS_TE(area->mta)) + continue; + + show_router_id(vty, area); + } + } + return 0; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + if (!IS_MPLS_TE(area->mta)) + continue; + + show_router_id(vty, area); + } + } + + return CMD_SUCCESS; +} + +static void show_ext_sub(struct vty *vty, char *name, + struct isis_ext_subtlvs *ext) +{ + struct sbuf buf; + char ibuf[PREFIX2STR_BUFFER]; + + sbuf_init(&buf, NULL, 0); + + if (!ext || ext->status == EXT_DISABLE) + return; + + vty_out(vty, "-- MPLS-TE link parameters for %s --\n", name); + + sbuf_reset(&buf); + + if (IS_SUBTLV(ext, EXT_ADM_GRP)) + sbuf_push(&buf, 4, "Administrative Group: 0x%x\n", + ext->adm_group); + if (IS_SUBTLV(ext, EXT_LLRI)) { + sbuf_push(&buf, 4, "Link Local ID: %u\n", + ext->local_llri); + sbuf_push(&buf, 4, "Link Remote ID: %u\n", + ext->remote_llri); + } + if (IS_SUBTLV(ext, EXT_LOCAL_ADDR)) + sbuf_push(&buf, 4, "Local Interface IP Address(es): %pI4\n", + &ext->local_addr); + if (IS_SUBTLV(ext, EXT_NEIGH_ADDR)) + sbuf_push(&buf, 4, "Remote Interface IP Address(es): %pI4\n", + &ext->neigh_addr); + if (IS_SUBTLV(ext, EXT_LOCAL_ADDR6)) + sbuf_push(&buf, 4, "Local Interface IPv6 Address(es): %s\n", + inet_ntop(AF_INET6, &ext->local_addr6, ibuf, + PREFIX2STR_BUFFER)); + if (IS_SUBTLV(ext, EXT_NEIGH_ADDR6)) + sbuf_push(&buf, 4, "Remote Interface IPv6 Address(es): %s\n", + inet_ntop(AF_INET6, &ext->local_addr6, ibuf, + PREFIX2STR_BUFFER)); + if (IS_SUBTLV(ext, EXT_MAX_BW)) + sbuf_push(&buf, 4, "Maximum Bandwidth: %g (Bytes/sec)\n", + ext->max_bw); + if (IS_SUBTLV(ext, EXT_MAX_RSV_BW)) + sbuf_push(&buf, 4, + "Maximum Reservable Bandwidth: %g (Bytes/sec)\n", + ext->max_rsv_bw); + if (IS_SUBTLV(ext, EXT_UNRSV_BW)) { + sbuf_push(&buf, 4, "Unreserved Bandwidth:\n"); + for (int j = 0; j < MAX_CLASS_TYPE; j += 2) { + sbuf_push(&buf, 4 + 2, + "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", + j, ext->unrsv_bw[j], + j + 1, ext->unrsv_bw[j + 1]); + } + } + if (IS_SUBTLV(ext, EXT_TE_METRIC)) + sbuf_push(&buf, 4, "Traffic Engineering Metric: %u\n", + ext->te_metric); + if (IS_SUBTLV(ext, EXT_RMT_AS)) + sbuf_push(&buf, 4, + "Inter-AS TE Remote AS number: %u\n", + ext->remote_as); + if (IS_SUBTLV(ext, EXT_RMT_IP)) + sbuf_push(&buf, 4, + "Inter-AS TE Remote ASBR IP address: %pI4\n", + &ext->remote_ip); + if (IS_SUBTLV(ext, EXT_DELAY)) + sbuf_push(&buf, 4, + "%s Average Link Delay: %u (micro-sec)\n", + IS_ANORMAL(ext->delay) ? "Anomalous" : "Normal", + ext->delay & TE_EXT_MASK); + if (IS_SUBTLV(ext, EXT_MM_DELAY)) { + sbuf_push(&buf, 4, "%s Min/Max Link Delay: %u / %u (micro-sec)\n", + IS_ANORMAL(ext->min_delay) ? "Anomalous" : "Normal", + ext->min_delay & TE_EXT_MASK, + ext->max_delay & TE_EXT_MASK); + } + if (IS_SUBTLV(ext, EXT_DELAY_VAR)) + sbuf_push(&buf, 4, + "Delay Variation: %u (micro-sec)\n", + ext->delay_var & TE_EXT_MASK); + if (IS_SUBTLV(ext, EXT_PKT_LOSS)) + sbuf_push(&buf, 4, "%s Link Packet Loss: %g (%%)\n", + IS_ANORMAL(ext->pkt_loss) ? "Anomalous" : "Normal", + (float)((ext->pkt_loss & TE_EXT_MASK) + * LOSS_PRECISION)); + if (IS_SUBTLV(ext, EXT_RES_BW)) + sbuf_push(&buf, 4, + "Unidirectional Residual Bandwidth: %g (Bytes/sec)\n", + ext->res_bw); + if (IS_SUBTLV(ext, EXT_AVA_BW)) + sbuf_push(&buf, 4, + "Unidirectional Available Bandwidth: %g (Bytes/sec)\n", + ext->ava_bw); + if (IS_SUBTLV(ext, EXT_USE_BW)) + sbuf_push(&buf, 4, + "Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n", + ext->use_bw); + + vty_multiline(vty, "", "%s", sbuf_buf(&buf)); + vty_out(vty, "---------------\n\n"); + + sbuf_free(&buf); + return; +} + +DEFUN (show_isis_mpls_te_interface, + show_isis_mpls_te_interface_cmd, + "show " PROTO_NAME " mpls-te interface [INTERFACE]", + SHOW_STR + PROTO_HELP + MPLS_TE_STR + "Interface information\n" + "Interface name\n") +{ + struct listnode *anode, *cnode, *inode; + struct isis_area *area; + struct isis_circuit *circuit; + struct interface *ifp; + int idx_interface = 4; + struct isis *isis = NULL; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (argc == idx_interface) { + /* Show All Interfaces. */ + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, + area)) { + + if (!IS_MPLS_TE(area->mta)) + continue; + + vty_out(vty, "Area %s:\n", area->area_tag); + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, + cnode, circuit)) + show_ext_sub(vty, + circuit->interface->name, + circuit->ext); + } + } + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(argv[idx_interface]->arg, VRF_DEFAULT); + if (ifp == NULL) + vty_out(vty, "No such interface name\n"); + else { + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) + vty_out(vty, + "ISIS is not enabled on circuit %s\n", + ifp->name); + else + show_ext_sub(vty, ifp->name, circuit->ext); + } + } + + return CMD_SUCCESS; +} + +/** + * Search Vertex in TED that corresponds to the given string that represent + * the ISO system ID in the forms [.-] + * + * @param ted Link State Database + * @param id ISO System ID + * @param isis Main reference to the isis daemon + * + * @return Vertex if found, NULL otherwise + */ +static struct ls_vertex *vertex_for_arg(struct ls_ted *ted, const char *id, + struct isis *isis) +{ + char sysid[255] = {0}; + uint8_t number[3]; + const char *pos; + uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0}; + struct isis_dynhn *dynhn; + uint64_t key = 0; + + if (!id) + return NULL; + + /* + * extract fragment and pseudo id from the string argv + * in the forms: + * (a) .- or + * (b) . or + * (c) or + * Where systemid is in the form: + * xxxx.xxxx.xxxx + */ + strlcpy(sysid, id, sizeof(sysid)); + if (strlen(id) > 3) { + pos = id + strlen(id) - 3; + if (strncmp(pos, "-", 1) == 0) { + memcpy(number, ++pos, 2); + lspid[ISIS_SYS_ID_LEN + 1] = + (uint8_t)strtol((char *)number, NULL, 16); + pos -= 4; + if (strncmp(pos, ".", 1) != 0) + return NULL; + } + if (strncmp(pos, ".", 1) == 0) { + memcpy(number, ++pos, 2); + lspid[ISIS_SYS_ID_LEN] = + (uint8_t)strtol((char *)number, NULL, 16); + sysid[pos - id - 1] = '\0'; + } + } + + /* + * Try to find the lsp-id if the argv + * string is in + * the form + * hostname.- + */ + if (sysid2buff(lspid, sysid)) { + key = sysid_to_key(lspid); + } else if ((dynhn = dynhn_find_by_name(isis, sysid))) { + memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN); + key = sysid_to_key(lspid); + } else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) { + memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN); + key = sysid_to_key(lspid); + } + + if (key == 0) + return NULL; + + return ls_find_vertex_by_key(ted, key); +} + +/** + * Show Link State Traffic Engineering Database extracted from IS-IS LSP. + * + * @param vty VTY output console + * @param argv Command line argument + * @param argc Number of command line argument + * @param ted Traffic Engineering Database + * @param isis isis Main reference to the isis daemon + * + * @return Command Success if OK, Command Warning otherwise + */ +static int show_ted(struct vty *vty, struct cmd_token *argv[], int argc, + struct isis_area *area, struct isis *isis) +{ + int idx; + char *id; + struct in_addr ip_addr; + struct in6_addr ip6_addr; + struct prefix pref; + struct ls_ted *ted; + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct ls_edge_key key; + bool detail = false; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (!IS_MPLS_TE(area->mta) || !area->mta->ted) { + vty_out(vty, "MPLS-TE is disabled for Area %s\n", + area->area_tag ? area->area_tag : "null"); + return CMD_SUCCESS; + } + + ted = area->mta->ted; + + if (uj) + json = json_object_new_object(); + else + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "detail")) + detail = true; + + idx = 4; + if (argv_find(argv, argc, "vertex", &idx)) { + /* Show Vertex */ + id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg + : NULL; + if (!id) + vertex = NULL; + else if (!strncmp(id, "self", 4)) + vertex = ted->self; + else { + vertex = vertex_for_arg(ted, id, isis); + if (!vertex) { + vty_out(vty, "No vertex found for ID %s\n", id); + return CMD_WARNING; + } + } + + if (vertex) + ls_show_vertex(vertex, vty, json, detail); + else + ls_show_vertices(ted, vty, json, detail); + + } else if (argv_find(argv, argc, "edge", &idx)) { + /* Show Edge */ + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_pton(AF_INET, argv[idx]->arg, &ip_addr)) { + vty_out(vty, + "Specified Edge ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Edge from the Link State Database */ + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &ip_addr); + edge = ls_find_edge_by_key(ted, key); + if (!edge) { + vty_out(vty, "No edge found for ID %pI4\n", + &ip_addr); + return CMD_WARNING; + } + } else if (argv_find(argv, argc, "X:X::X:X", &idx)) { + if (!inet_pton(AF_INET6, argv[idx]->arg, &ip6_addr)) { + vty_out(vty, + "Specified Edge ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Edge from the Link State Database */ + key.family = AF_INET6; + IPV6_ADDR_COPY(&key.k.addr6, &ip6_addr); + edge = ls_find_edge_by_key(ted, key); + if (!edge) { + vty_out(vty, "No edge found for ID %pI6\n", + &ip6_addr); + return CMD_WARNING; + } + } else + edge = NULL; + + if (edge) + ls_show_edge(edge, vty, json, detail); + else + ls_show_edges(ted, vty, json, detail); + + } else if (argv_find(argv, argc, "subnet", &idx)) { + /* Show Subnet */ + if (argv_find(argv, argc, "A.B.C.D/M", &idx)) { + if (!str2prefix(argv[idx]->arg, &pref)) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Subnet from the Link State Database */ + subnet = ls_find_subnet(ted, &pref); + if (!subnet) { + vty_out(vty, "No subnet found for ID %pFX\n", + &pref); + return CMD_WARNING; + } + } else if (argv_find(argv, argc, "X:X::X:X/M", &idx)) { + if (!str2prefix(argv[idx]->arg, &pref)) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Subnet from the Link State Database */ + subnet = ls_find_subnet(ted, &pref); + if (!subnet) { + vty_out(vty, "No subnet found for ID %pFX\n", + &pref); + return CMD_WARNING; + } + } else + subnet = NULL; + + if (subnet) + ls_show_subnet(subnet, vty, json, detail); + else + ls_show_subnets(ted, vty, json, detail); + + } else { + /* Show the complete TED */ + ls_show_ted(ted, vty, json, detail); + } + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/** + * Show ISIS Traffic Engineering Database + * + * @param vty VTY output console + * @param argv Command line argument + * @param argc Number of command line argument + * @param isis isis Main reference to the isis daemon + + * @return Command Success if OK, Command Warning otherwise + */ +static int show_isis_ted(struct vty *vty, struct cmd_token *argv[], int argc, + struct isis *isis) +{ + struct listnode *node; + struct isis_area *area; + int rc; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + rc = show_ted(vty, argv, argc, area, isis); + if (rc != CMD_SUCCESS) + return rc; + } + return CMD_SUCCESS; +} + +DEFUN(show_isis_mpls_te_db, + show_isis_mpls_te_db_cmd, + "show " PROTO_NAME " [vrf ] mpls-te database [] [detail|json]", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + MPLS_TE_STR + "MPLS-TE database\n" + "MPLS-TE Vertex\n" + "MPLS-TE Vertex ID (as an ISO ID, hostname or \"self\")\n" + "MPLS-TE Edge\n" + "MPLS-TE Edge ID (as an IPv4 address)\n" + "MPLS-TE Edge ID (as an IPv6 address)\n" + "MPLS-TE Subnet\n" + "MPLS-TE Subnet ID (as an IPv4 prefix)\n" + "MPLS-TE Subnet ID (as an IPv6 prefix)\n" + "Detailed information\n" + JSON_STR) +{ + int idx_vrf = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + struct listnode *node; + struct isis *isis; + int rc = CMD_WARNING; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + rc = show_isis_ted(vty, argv, argc, isis); + if (rc != CMD_SUCCESS) + return rc; + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis) + rc = show_isis_ted(vty, argv, argc, isis); + + return rc; +} + +#endif /* #ifndef FRABRICD */ + +/* Initialize MPLS_TE */ +void isis_mpls_te_init(void) +{ + + /* Register Circuit and Adjacency hook */ + hook_register(isis_if_new_hook, isis_mpls_te_update); + hook_register(isis_adj_ip_enabled_hook, isis_mpls_te_adj_ip_enabled); + hook_register(isis_adj_ip_disabled_hook, isis_mpls_te_adj_ip_disabled); + +#ifndef FABRICD + /* Register new VTY commands */ + install_element(VIEW_NODE, &show_isis_mpls_te_router_cmd); + install_element(VIEW_NODE, &show_isis_mpls_te_interface_cmd); + install_element(VIEW_NODE, &show_isis_mpls_te_db_cmd); +#endif + + return; +} diff --git a/isisd/isis_te.h b/isisd/isis_te.h new file mode 100644 index 0000000..bf1dc2b --- /dev/null +++ b/isisd/isis_te.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_te.c + * + * This is an implementation of RFC5305, RFC 5307 and RFC 7810 + * + * Author: Olivier Dugeon + * + * Copyright (C) 2014 - 2019 Orange Labs http://www.orange.com + */ + +#ifndef _ZEBRA_ISIS_MPLS_TE_H +#define _ZEBRA_ISIS_MPLS_TE_H + +/* + * Traffic Engineering information are transport through LSP: + * - Extended IS Reachability TLV = 22 + * - Traffic Engineering Router ID TLV = 134 + * - Extended IP Reachability TLV = 135 + * - Inter-AS Reachability Information TLV = 141 + * + * and support following sub-TLV: + * + * Name Value Status + * _________________________________________________ + * Administartive group (color) 3 RFC5305 + * Link Local/Remote Identifiers 4 RFC5307 + * IPv4 interface address 6 RFC5305 + * IPv4 neighbor address 8 RFC5305 + * Maximum link bandwidth 9 RFC5305 + * Reservable link bandwidth 10 RFC5305 + * Unreserved bandwidth 11 RFC5305 + * TE Default metric 18 RFC5305 + * Link Protection Type 20 RFC5307 + * Interface Switching Capability 21 RFC5307 + * Remote AS number 24 RFC5316 + * IPv4 Remote ASBR identifier 25 RFC5316 + * + * NOTE: RFC5316 is not fully supported in this version + * only subTLVs decoding is provided + */ + +/* Following define the type of TE link regarding the various RFC */ +#define STD_TE 0x01 +#define GMPLS 0x02 +#define INTER_AS 0x04 +#define FLOOD_L1 0x10 +#define FLOOD_L2 0x20 +#define FLOOD_AS 0x40 +#define EMULATED 0x80 + +#define IS_STD_TE(x) (x & STD_TE) +#define IS_INTER_AS(x) (x & INTER_AS) +#define IS_EMULATED(x) (x & EMULATED) +#define IS_FLOOD_L1(x) (x & FLOOD_L1) +#define IS_FLOOD_L2(x) (x & FLOOD_L2) +#define IS_FLOOD_AS(x) (x & FLOOD_AS) +#define IS_INTER_AS_EMU(x) (x & INTER_AS & EMULATED) +#define IS_INTER_AS_AS(x) (x & INTER_AS & FLOOD_AS) + +/* + * Note (since release 7.2), subTLVs definition, serialization + * and de-serialization have mode to isis_tlvs.[c,h] + */ + +/* Following declaration concerns the MPLS-TE and LINk-TE management */ +typedef enum _status_t { disable, enable, learn } status_t; + +/* Mode for Inter-AS LSP */ /* TODO: Check how if LSP is flooded in RFC5316 */ +typedef enum _interas_mode_t { off, region, as, emulate } interas_mode_t; + +#define IS_EXT_TE(e) \ + (e && e->status != 0 && e->status != EXT_ADJ_SID && \ + e->status != EXT_LAN_ADJ_SID && e->status != EXT_SRV6_ENDX_SID && \ + e->status != EXT_SRV6_LAN_ENDX_SID) +#define IS_MPLS_TE(a) (a && a->status == enable) +#define IS_EXPORT_TE(a) (a->export) + +/* Per area MPLS-TE parameters */ +struct ls_ted; +struct mpls_te_area { + /* Status of MPLS-TE: enable or disable */ + status_t status; + + /* L1, L1-L2, L2-Only */ + uint8_t level; + + /* RFC5316 */ + interas_mode_t inter_as; + struct in_addr interas_areaid; + + /* MPLS_TE IPv4 & IPv6 Router IDs */ + struct in_addr router_id; + struct in6_addr router_id_ipv6; + + /* Link State Database */ + struct ls_ted *ted; + bool export; +}; + +/* Structure to provide parameters to lsp iterate callback function */ +struct isis_te_args { + struct ls_ted *ted; + struct ls_vertex *vertex; + bool export; +}; + +enum lsp_event { LSP_UNKNOWN, LSP_ADD, LSP_UPD, LSP_DEL, LSP_INC, LSP_TICK }; + +/* Prototypes. */ +void isis_mpls_te_init(void); +void isis_mpls_te_create(struct isis_area *area); +void isis_mpls_te_disable(struct isis_area *area); +void isis_mpls_te_term(struct isis_area *area); +void isis_link_params_update(struct isis_circuit *, struct interface *); +int isis_mpls_te_update(struct interface *); +void isis_te_lsp_event(struct isis_lsp *lsp, enum lsp_event event); +int isis_te_sync_ted(struct zapi_opaque_reg_info dst); +void isis_te_init_ted(struct isis_area *area); + +#endif /* _ZEBRA_ISIS_MPLS_TE_H */ diff --git a/isisd/isis_tlvs.c b/isisd/isis_tlvs.c new file mode 100644 index 0000000..4db9728 --- /dev/null +++ b/isisd/isis_tlvs.c @@ -0,0 +1,9204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS TLV Serializer/Deserializer + * + * Copyright (C) 2015,2017 Christian Franke + * + * Copyright (C) 2019 Olivier Dugeon - Orange Labs (for TE and SR) + * + * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata + * (for IS-IS Extensions to Support SRv6 as per RFC 9352) + */ + +#include +#include + +#ifdef CRYPTO_OPENSSL +#include +#include +#endif + +#ifdef CRYPTO_INTERNAL +#include "md5.h" +#endif +#include "memory.h" +#include "stream.h" +#include "sbuf.h" +#include "network.h" + +#include "isisd/isisd.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_common.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_te.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_flex_algo.h" + +#define TLV_SIZE_MISMATCH(log, indent, target) \ + sbuf_push(log, indent, \ + "TLV size does not match expected size for " target "!\n") + +DEFINE_MTYPE_STATIC(ISISD, ISIS_TLV, "ISIS TLVs"); +DEFINE_MTYPE(ISISD, ISIS_SUBTLV, "ISIS Sub-TLVs"); +DEFINE_MTYPE(ISISD, ISIS_SUBSUBTLV, "ISIS Sub-Sub-TLVs"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_MT_ITEM_LIST, "ISIS MT Item Lists"); + +typedef int (*unpack_tlv_func)(enum isis_tlv_context context, uint8_t tlv_type, + uint8_t tlv_len, struct stream *s, + struct sbuf *log, void *dest, int indent); +typedef int (*pack_item_func)(struct isis_item *item, struct stream *s, + size_t *min_length); +typedef void (*free_item_func)(struct isis_item *i); +typedef int (*unpack_item_func)(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent); +typedef void (*format_item_func)(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent); +typedef struct isis_item *(*copy_item_func)(struct isis_item *i); + +struct tlv_ops { + const char *name; + unpack_tlv_func unpack; + + pack_item_func pack_item; + free_item_func free_item; + unpack_item_func unpack_item; + format_item_func format_item; + copy_item_func copy_item; +}; + +enum how_to_pack { + ISIS_ITEMS, + ISIS_MT_ITEMS, +}; + +struct pack_order_entry { + enum isis_tlv_context context; + enum isis_tlv_type type; + enum how_to_pack how_to_pack; + size_t what_to_pack; +}; +#define PACK_ENTRY(t, h, w) \ + { \ + .context = ISIS_CONTEXT_LSP, .type = ISIS_TLV_##t, \ + .how_to_pack = (h), \ + .what_to_pack = offsetof(struct isis_tlvs, w), \ + } + +static const struct pack_order_entry pack_order[] = { + PACK_ENTRY(OLDSTYLE_REACH, ISIS_ITEMS, oldstyle_reach), + PACK_ENTRY(LAN_NEIGHBORS, ISIS_ITEMS, lan_neighbor), + PACK_ENTRY(LSP_ENTRY, ISIS_ITEMS, lsp_entries), + PACK_ENTRY(EXTENDED_REACH, ISIS_ITEMS, extended_reach), + PACK_ENTRY(MT_REACH, ISIS_MT_ITEMS, mt_reach), + PACK_ENTRY(OLDSTYLE_IP_REACH, ISIS_ITEMS, oldstyle_ip_reach), + PACK_ENTRY(OLDSTYLE_IP_REACH_EXT, ISIS_ITEMS, oldstyle_ip_reach_ext), + PACK_ENTRY(IPV4_ADDRESS, ISIS_ITEMS, ipv4_address), + PACK_ENTRY(IPV6_ADDRESS, ISIS_ITEMS, ipv6_address), + PACK_ENTRY(GLOBAL_IPV6_ADDRESS, ISIS_ITEMS, global_ipv6_address), + PACK_ENTRY(EXTENDED_IP_REACH, ISIS_ITEMS, extended_ip_reach), + PACK_ENTRY(MT_IP_REACH, ISIS_MT_ITEMS, mt_ip_reach), + PACK_ENTRY(IPV6_REACH, ISIS_ITEMS, ipv6_reach), + PACK_ENTRY(MT_IPV6_REACH, ISIS_MT_ITEMS, mt_ipv6_reach), + PACK_ENTRY(SRV6_LOCATOR, ISIS_MT_ITEMS, srv6_locator) +}; + +/* This is a forward definition. The table is actually initialized + * in at the bottom. */ +static const struct tlv_ops *const tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX]; + +/* End of _ops forward definition. */ + +/* Prototypes */ +static void append_item(struct isis_item_list *dest, struct isis_item *item); +static void init_item_list(struct isis_item_list *items); + +static struct isis_subsubtlvs * +isis_copy_subsubtlvs(struct isis_subsubtlvs *subsubtlvs); +static void isis_format_subsubtlvs(struct isis_subsubtlvs *subsubtlvs, + struct sbuf *buf, struct json_object *json, + int indent); +static int isis_pack_subsubtlvs(struct isis_subsubtlvs *subsubtlvs, + struct stream *s); +static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len, + struct stream *stream, struct sbuf *log, void *dest, + int indent, bool *unpacked_known_tlvs); +static void isis_free_subsubtlvs(struct isis_subsubtlvs *subsubtlvs); + +/* For tests/isisd, TLV text requires ipv4-unicast instead of standard */ +static const char *isis_mtid2str_fake(uint16_t mtid) +{ + if (mtid == ISIS_MT_STANDARD) + return "ipv4-unicast"; + return isis_mtid2str(mtid); +} + +/* Functions for Extended IS Reachability SubTLVs a.k.a Traffic Engineering */ +struct isis_ext_subtlvs *isis_alloc_ext_subtlvs(void) +{ + struct isis_ext_subtlvs *ext; + + ext = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_ext_subtlvs)); + init_item_list(&ext->adj_sid); + init_item_list(&ext->lan_sid); + ext->aslas = list_new(); + + init_item_list(&ext->srv6_endx_sid); + init_item_list(&ext->srv6_lan_endx_sid); + + admin_group_init(&ext->ext_admin_group); + + return ext; +} + +void isis_del_ext_subtlvs(struct isis_ext_subtlvs *ext) +{ + struct isis_item *item, *next_item; + struct listnode *node, *nnode; + struct isis_asla_subtlvs *asla; + + if (!ext) + return; + + /* First, free Adj SID and LAN Adj SID list if needed */ + for (item = ext->adj_sid.head; item; item = next_item) { + next_item = item->next; + XFREE(MTYPE_ISIS_SUBTLV, item); + } + for (item = ext->lan_sid.head; item; item = next_item) { + next_item = item->next; + XFREE(MTYPE_ISIS_SUBTLV, item); + } + + for (ALL_LIST_ELEMENTS(ext->aslas, node, nnode, asla)) + isis_tlvs_del_asla_flex_algo(ext, asla); + + list_delete(&ext->aslas); + + admin_group_term(&ext->ext_admin_group); + + /* First, free SRv6 End.X SID and SRv6 LAN End.X SID list if needed */ + for (item = ext->srv6_endx_sid.head; item; item = next_item) { + next_item = item->next; + isis_free_subsubtlvs(((struct isis_srv6_endx_sid_subtlv *)item)->subsubtlvs); + XFREE(MTYPE_ISIS_SUBTLV, item); + } + for (item = ext->srv6_lan_endx_sid.head; item; item = next_item) { + next_item = item->next; + isis_free_subsubtlvs(((struct isis_srv6_lan_endx_sid_subtlv *)item)->subsubtlvs); + XFREE(MTYPE_ISIS_SUBTLV, item); + } + + XFREE(MTYPE_ISIS_SUBTLV, ext); +} + +/* + * mtid parameter is used to determine if Adjacency is related to IPv4 or IPv6 + * Multi-Topology. Special 4096 value i.e. first R flag set is used to indicate + * that MT is disabled i.e. IS-IS is working with a Single Topology. + */ +static struct isis_ext_subtlvs * +copy_item_ext_subtlvs(struct isis_ext_subtlvs *exts, uint16_t mtid) +{ + struct isis_ext_subtlvs *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); + struct isis_adj_sid *adj; + struct isis_lan_adj_sid *lan; + struct listnode *node, *nnode; + struct isis_asla_subtlvs *new_asla, *asla; + struct isis_srv6_endx_sid_subtlv *srv6_adj; + struct isis_srv6_lan_endx_sid_subtlv *srv6_lan; + + /* Copy the Extended IS main part */ + memcpy(rv, exts, sizeof(struct isis_ext_subtlvs)); + + /* Disable IPv4 / IPv6 advertisement in function of MTID */ + if (mtid == ISIS_MT_IPV4_UNICAST) { + UNSET_SUBTLV(rv, EXT_LOCAL_ADDR6); + UNSET_SUBTLV(rv, EXT_NEIGH_ADDR6); + } + if (mtid == ISIS_MT_IPV6_UNICAST) { + UNSET_SUBTLV(rv, EXT_LOCAL_ADDR); + UNSET_SUBTLV(rv, EXT_NEIGH_ADDR); + } + + /* Prepare (LAN)-Adjacency Segment Routing ID*/ + init_item_list(&rv->adj_sid); + init_item_list(&rv->lan_sid); + + /* Prepare SRv6 (LAN) End.X SID */ + init_item_list(&rv->srv6_endx_sid); + init_item_list(&rv->srv6_lan_endx_sid); + + UNSET_SUBTLV(rv, EXT_ADJ_SID); + UNSET_SUBTLV(rv, EXT_LAN_ADJ_SID); + + UNSET_SUBTLV(rv, EXT_SRV6_ENDX_SID); + UNSET_SUBTLV(rv, EXT_SRV6_LAN_ENDX_SID); + + /* Copy Adj SID list for IPv4 & IPv6 in function of MT ID */ + for (adj = (struct isis_adj_sid *)exts->adj_sid.head; adj != NULL; + adj = adj->next) { + if ((mtid != ISIS_MT_DISABLE) + && (((mtid == ISIS_MT_IPV4_UNICAST) + && (adj->family != AF_INET)) + || ((mtid == ISIS_MT_IPV6_UNICAST) + && (adj->family != AF_INET6)))) + continue; + + struct isis_adj_sid *new; + + new = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_adj_sid)); + new->family = adj->family; + new->flags = adj->flags; + new->weight = adj->weight; + new->sid = adj->sid; + append_item(&rv->adj_sid, (struct isis_item *)new); + SET_SUBTLV(rv, EXT_ADJ_SID); + } + + /* Same for LAN Adj SID */ + for (lan = (struct isis_lan_adj_sid *)exts->lan_sid.head; lan != NULL; + lan = lan->next) { + if ((mtid != ISIS_MT_DISABLE) + && (((mtid == ISIS_MT_IPV4_UNICAST) + && (lan->family != AF_INET)) + || ((mtid == ISIS_MT_IPV6_UNICAST) + && (lan->family != AF_INET6)))) + continue; + + struct isis_lan_adj_sid *new; + + new = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_lan_adj_sid)); + new->family = lan->family; + new->flags = lan->flags; + new->weight = lan->weight; + memcpy(new->neighbor_id, lan->neighbor_id, 6); + new->sid = lan->sid; + append_item(&rv->lan_sid, (struct isis_item *)new); + SET_SUBTLV(rv, EXT_LAN_ADJ_SID); + } + + /* Copy SRv6 End.X SID list for IPv4 & IPv6 in function of MT ID */ + for (srv6_adj = (struct isis_srv6_endx_sid_subtlv *) + exts->srv6_endx_sid.head; + srv6_adj != NULL; srv6_adj = srv6_adj->next) { + if ((mtid != 65535) && (mtid != ISIS_MT_DISABLE) && + ((mtid != ISIS_MT_IPV6_UNICAST))) + continue; + + struct isis_srv6_endx_sid_subtlv *new; + + new = XCALLOC(MTYPE_ISIS_SUBTLV, + sizeof(struct isis_srv6_endx_sid_subtlv)); + new->flags = srv6_adj->flags; + new->algorithm = srv6_adj->algorithm; + new->weight = srv6_adj->weight; + new->behavior = srv6_adj->behavior; + new->sid = srv6_adj->sid; + new->subsubtlvs = isis_copy_subsubtlvs(srv6_adj->subsubtlvs); + append_item(&rv->srv6_endx_sid, (struct isis_item *)new); + SET_SUBTLV(rv, EXT_SRV6_ENDX_SID); + } + /* Same for SRv6 LAN End.X SID */ + for (srv6_lan = (struct isis_srv6_lan_endx_sid_subtlv *) + exts->srv6_lan_endx_sid.head; + srv6_lan != NULL; srv6_lan = srv6_lan->next) { + if ((mtid != 65535) && (mtid != ISIS_MT_DISABLE) && + ((mtid != ISIS_MT_IPV6_UNICAST))) + continue; + + struct isis_srv6_lan_endx_sid_subtlv *new; + + new = XCALLOC(MTYPE_ISIS_SUBTLV, + sizeof(struct isis_srv6_lan_endx_sid_subtlv)); + memcpy(new->neighbor_id, srv6_lan->neighbor_id, 6); + new->flags = srv6_lan->flags; + new->algorithm = srv6_lan->algorithm; + new->weight = srv6_lan->weight; + new->behavior = srv6_lan->behavior; + new->sid = srv6_lan->sid; + new->subsubtlvs = isis_copy_subsubtlvs(srv6_lan->subsubtlvs); + append_item(&rv->srv6_lan_endx_sid, (struct isis_item *)new); + SET_SUBTLV(rv, EXT_SRV6_LAN_ENDX_SID); + } + + rv->aslas = list_new(); + + for (ALL_LIST_ELEMENTS(exts->aslas, node, nnode, asla)) { + new_asla = XCALLOC(MTYPE_ISIS_SUBTLV, + sizeof(struct isis_asla_subtlvs)); + memcpy(new_asla, asla, sizeof(struct isis_asla_subtlvs)); + + new_asla->ext_admin_group.bitmap.data = NULL; + admin_group_copy(&new_asla->ext_admin_group, + &asla->ext_admin_group); + + listnode_add(rv->aslas, new_asla); + } + + rv->ext_admin_group.bitmap.data = NULL; + admin_group_copy(&rv->ext_admin_group, &exts->ext_admin_group); + + return rv; +} + +static void format_item_asla_subtlvs(struct isis_asla_subtlvs *asla, + struct json_object *ext_json, + struct sbuf *buf, int indent) +{ + char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE]; + struct json_object *json; + char cnt_buf[255]; + size_t i; + int j; + + if (ext_json) { + json = json_object_new_object(); + json_object_object_add(ext_json, "asla", json); + json_object_boolean_add(json, "legacyFlag", asla->legacy); + json_object_string_addf(json, "standardApp", "0x%02x", + asla->standard_apps); + if (IS_SUBTLV(asla, EXT_ADM_GRP)) + json_object_string_addf(json, "adminGroup", "0x%x", + asla->admin_group); + if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&asla->ext_admin_group) != 0) { + struct json_object *ext_adm_grp_json; + + ext_adm_grp_json = json_object_new_object(); + json_object_object_add(json, "extendedAdminGroup", + ext_adm_grp_json); + for (i = 0; + i < admin_group_nb_words(&asla->ext_admin_group); + i++) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%lu", + (unsigned long)i); + json_object_string_addf(ext_adm_grp_json, + cnt_buf, "0x%x", + asla->ext_admin_group + .bitmap.data[i]); + } + } + if (IS_SUBTLV(asla, EXT_MAX_BW)) + json_object_string_addf(json, "maxBandwithBytesSec", + "%g", asla->max_bw); + if (IS_SUBTLV(asla, EXT_MAX_RSV_BW)) + json_object_string_addf(json, "maxResBandwithBytesSec", + "%g", asla->max_rsv_bw); + if (IS_SUBTLV(asla, EXT_UNRSV_BW)) { + struct json_object *unrsv_json = + json_object_new_object(); + + json_object_object_add(json, "unrsvBandwithBytesSec", + unrsv_json); + for (j = 0; j < MAX_CLASS_TYPE; j += 1) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", j); + json_object_string_addf(unrsv_json, cnt_buf, + "%g", asla->unrsv_bw[j]); + } + } + if (IS_SUBTLV(asla, EXT_TE_METRIC)) + json_object_int_add(json, "teMetric", asla->te_metric); + + /* Extended metrics */ + if (IS_SUBTLV(asla, EXT_DELAY)) { + struct json_object *avg_json; + + avg_json = json_object_new_object(); + json_object_object_add(json, "avgDelay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(asla->delay) + ? "Anomalous" + : "Normal"); + json_object_int_add(avg_json, "microSec", asla->delay); + } + if (IS_SUBTLV(asla, EXT_MM_DELAY)) { + struct json_object *avg_json; + + avg_json = json_object_new_object(); + json_object_object_add(json, "maxMinDelay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(asla->min_delay) + ? "Anomalous" + : "Normal"); + json_object_string_addf(avg_json, "microSec", "%u / %u", + asla->min_delay & TE_EXT_MASK, + asla->max_delay & TE_EXT_MASK); + } + if (IS_SUBTLV(asla, EXT_DELAY_VAR)) + json_object_int_add(json, "delayVariationMicroSec", + asla->delay_var & TE_EXT_MASK); + if (IS_SUBTLV(asla, EXT_PKT_LOSS)) { + struct json_object *link_json; + + link_json = json_object_new_object(); + json_object_object_add(json, "linkPacketLoss", + link_json); + json_object_string_add(link_json, "loss", + IS_ANORMAL(asla->pkt_loss) + ? "Anomalous" + : "Normal"); + json_object_string_addf(link_json, "percentage", "%g", + (float)((asla->pkt_loss & + TE_EXT_MASK) * + LOSS_PRECISION)); + } + if (IS_SUBTLV(asla, EXT_RES_BW)) + json_object_string_addf(json, + "unidirResidualBandBytesSec", + "%g", (asla->res_bw)); + if (IS_SUBTLV(asla, EXT_AVA_BW)) + json_object_string_addf(json, + "unidirAvailableBandBytesSec", + "%g", (asla->ava_bw)); + if (IS_SUBTLV(asla, EXT_USE_BW)) + json_object_string_addf(json, + "unidirUtilizedBandBytesSec", + "%g", (asla->use_bw)); + return; + } + + sbuf_push(buf, indent, "Application Specific Link Attributes:\n"); + sbuf_push(buf, indent + 2, + "L flag: %u, SA-Length: %u, UDA-Length: %u\n", asla->legacy, + asla->standard_apps_length, asla->user_def_apps_length); + sbuf_push(buf, indent + 2, "Standard Applications: 0x%02x", + asla->standard_apps); + if (asla->standard_apps) { + uint8_t bit = asla->standard_apps; + if (bit & ISIS_SABM_FLAG_R) + sbuf_push(buf, 0, " RSVP-TE"); + if (bit & ISIS_SABM_FLAG_S) + sbuf_push(buf, 0, " SR-Policy"); + if (bit & ISIS_SABM_FLAG_L) + sbuf_push(buf, 0, " Loop-Free-Alternate"); + if (bit & ISIS_SABM_FLAG_X) + sbuf_push(buf, 0, " Flex-Algo"); + } + sbuf_push(buf, 0, "\n"); + sbuf_push(buf, indent + 2, "User Defined Applications: 0x%02x\n", + asla->user_def_apps); + + if (IS_SUBTLV(asla, EXT_ADM_GRP)) { + sbuf_push(buf, indent + 2, "Admin Group: 0x%08x\n", + asla->admin_group); + sbuf_push(buf, indent + 4, "Bit positions: %s\n", + admin_group_standard_print( + admin_group_buf, + indent + 2 + strlen("Admin Group: "), + asla->admin_group)); + } + if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&asla->ext_admin_group) != 0) { + sbuf_push(buf, indent + 2, "Ext Admin Group: %s\n", + admin_group_string( + admin_group_buf, ADMIN_GROUP_PRINT_MAX_SIZE, + indent + 2 + strlen("Ext Admin Group: "), + &asla->ext_admin_group)); + admin_group_print(admin_group_buf, + indent + 2 + strlen("Ext Admin Group: "), + &asla->ext_admin_group); + if (admin_group_buf[0] != '\0' && + (buf->pos + strlen(admin_group_buf) + + SBUF_DEFAULT_SIZE / 2) < buf->size) + sbuf_push(buf, indent + 4, "Bit positions: %s\n", + admin_group_buf); + } + if (IS_SUBTLV(asla, EXT_MAX_BW)) + sbuf_push(buf, indent + 2, + "Maximum Bandwidth: %g (Bytes/sec)\n", asla->max_bw); + if (IS_SUBTLV(asla, EXT_MAX_RSV_BW)) + sbuf_push(buf, indent + 2, + "Maximum Reservable Bandwidth: %g (Bytes/sec)\n", + asla->max_rsv_bw); + if (IS_SUBTLV(asla, EXT_UNRSV_BW)) { + sbuf_push(buf, indent + 2, "Unreserved Bandwidth:\n"); + for (int j = 0; j < MAX_CLASS_TYPE; j += 2) { + sbuf_push( + buf, indent + 2, + "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", + j, asla->unrsv_bw[j], j + 1, + asla->unrsv_bw[j + 1]); + } + } + if (IS_SUBTLV(asla, EXT_TE_METRIC)) + sbuf_push(buf, indent + 2, "Traffic Engineering Metric: %u\n", + asla->te_metric); + /* Extended metrics */ + if (IS_SUBTLV(asla, EXT_DELAY)) + sbuf_push(buf, indent + 2, + "%s Average Link Delay: %u (micro-sec)\n", + IS_ANORMAL(asla->delay) ? "Anomalous" : "Normal", + asla->delay); + if (IS_SUBTLV(asla, EXT_MM_DELAY)) { + sbuf_push(buf, indent + 2, + "%s Min/Max Link Delay: %u / %u (micro-sec)\n", + IS_ANORMAL(asla->min_delay) ? "Anomalous" : "Normal", + asla->min_delay & TE_EXT_MASK, + asla->max_delay & TE_EXT_MASK); + } + if (IS_SUBTLV(asla, EXT_DELAY_VAR)) { + sbuf_push(buf, indent + 2, "Delay Variation: %u (micro-sec)\n", + asla->delay_var & TE_EXT_MASK); + } + if (IS_SUBTLV(asla, EXT_PKT_LOSS)) + sbuf_push(buf, indent + 2, "%s Link Packet Loss: %g (%%)\n", + IS_ANORMAL(asla->pkt_loss) ? "Anomalous" : "Normal", + (float)((asla->pkt_loss & TE_EXT_MASK) * + LOSS_PRECISION)); + if (IS_SUBTLV(asla, EXT_RES_BW)) + sbuf_push(buf, indent + 2, + "Unidir. Residual Bandwidth: %g (Bytes/sec)\n", + asla->res_bw); + if (IS_SUBTLV(asla, EXT_AVA_BW)) + sbuf_push(buf, indent + 2, + "Unidir. Available Bandwidth: %g (Bytes/sec)\n", + asla->ava_bw); + if (IS_SUBTLV(asla, EXT_USE_BW)) + sbuf_push(buf, indent + 2, + "Unidir. Utilized Bandwidth: %g (Bytes/sec)\n", + asla->use_bw); +} + +#if CONFDATE > 20240916 +CPP_NOTICE("Remove JSON in '-' format") +#endif + +/* mtid parameter is used to manage multi-topology i.e. IPv4 / IPv6 */ +static void format_item_ext_subtlvs(struct isis_ext_subtlvs *exts, + struct sbuf *buf, struct json_object *json, + int indent, uint16_t mtid) +{ + char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE]; + char aux_buf[255]; + char cnt_buf[255]; + struct isis_asla_subtlvs *asla; + struct listnode *node; + + /* Standard metrics */ + if (IS_SUBTLV(exts, EXT_ADM_GRP)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "0x%x", + exts->adm_group); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "adm-group", aux_buf); + json_object_string_add(json, "admGroup", aux_buf); + } else { + sbuf_push(buf, indent, "Admin Group: 0x%08x\n", + exts->adm_group); + sbuf_push(buf, indent + 2, "Bit positions: %s\n", + admin_group_standard_print( + admin_group_buf, + indent + strlen("Admin Group: "), + exts->adm_group)); + } + } + + if (IS_SUBTLV(exts, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&exts->ext_admin_group) != 0) { + if (json) { + struct json_object *ext_adm_grp_json; + size_t i; + ext_adm_grp_json = json_object_new_object(); + json_object_object_add(json, "extendedAdminGroup", + ext_adm_grp_json); + for (i = 0; + i < admin_group_nb_words(&exts->ext_admin_group); + i++) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%lu", + (unsigned long)i); + json_object_string_addf(ext_adm_grp_json, + cnt_buf, "0x%x", + exts->ext_admin_group + .bitmap.data[i]); + } + } else { + sbuf_push(buf, indent, "Ext Admin Group: %s\n", + admin_group_string( + admin_group_buf, + ADMIN_GROUP_PRINT_MAX_SIZE, + indent + strlen("Ext Admin Group: "), + &exts->ext_admin_group)); + admin_group_print(admin_group_buf, + indent + strlen("Ext Admin Group: "), + &exts->ext_admin_group); + if (admin_group_buf[0] != '\0' && + (buf->pos + strlen(admin_group_buf) + + SBUF_DEFAULT_SIZE / 2) < buf->size) + sbuf_push(buf, indent + 2, + "Bit positions: %s\n", + admin_group_buf); + } + } + if (IS_SUBTLV(exts, EXT_LLRI)) { + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_int_add(json, "link-local-id", + exts->local_llri); + json_object_int_add(json, "link-remote-id", + exts->remote_llri); + json_object_int_add(json, "linkLocalId", + exts->local_llri); + json_object_int_add(json, "linkRemoteId", + exts->remote_llri); + } else { + sbuf_push(buf, indent, "Link Local ID: %u\n", + exts->local_llri); + sbuf_push(buf, indent, "Link Remote ID: %u\n", + exts->remote_llri); + } + } + if (IS_SUBTLV(exts, EXT_LOCAL_ADDR)) { + if (json) { + inet_ntop(AF_INET, &exts->local_addr, aux_buf, + sizeof(aux_buf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "local-iface-ip", aux_buf); + json_object_string_add(json, "localIfaceIp", aux_buf); + } else + sbuf_push(buf, indent, + "Local Interface IP Address(es): %pI4\n", + &exts->local_addr); + } + if (IS_SUBTLV(exts, EXT_NEIGH_ADDR)) { + if (json) { + inet_ntop(AF_INET, &exts->neigh_addr, aux_buf, + sizeof(aux_buf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "remote-iface-ip", + aux_buf); + json_object_string_add(json, "remoteIfaceIp", aux_buf); + } else + sbuf_push(buf, indent, + "Remote Interface IP Address(es): %pI4\n", + &exts->neigh_addr); + } + if (IS_SUBTLV(exts, EXT_LOCAL_ADDR6)) { + if (json) { + inet_ntop(AF_INET6, &exts->local_addr6, aux_buf, + sizeof(aux_buf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "local-iface-ipv6", + aux_buf); + json_object_string_add(json, "localIfaceIpv6", aux_buf); + } else + sbuf_push(buf, indent, + "Local Interface IPv6 Address(es): %pI6\n", + &exts->local_addr6); + } + if (IS_SUBTLV(exts, EXT_NEIGH_ADDR6)) { + if (json) { + inet_ntop(AF_INET6, &exts->neigh_addr6, aux_buf, + sizeof(aux_buf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "remote-iface-ipv6", + aux_buf); + json_object_string_add(json, "remoteIfaceIpv6", aux_buf); + } else + sbuf_push(buf, indent, + "Remote Interface IPv6 Address(es): %pI6\n", + &exts->neigh_addr6); + } + if (IS_SUBTLV(exts, EXT_MAX_BW)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + exts->max_bw); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "max-bandwith-bytes-sec", + aux_buf); + json_object_string_add(json, "maxBandwithBytesSec", + aux_buf); + } else + sbuf_push(buf, indent, + "Maximum Bandwidth: %g (Bytes/sec)\n", + exts->max_bw); + } + if (IS_SUBTLV(exts, EXT_MAX_RSV_BW)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + exts->max_rsv_bw); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add( + json, "max-res-bandwith-bytes-sec", aux_buf); + json_object_string_add(json, "maxResBandwithBytesSec", + aux_buf); + } else + sbuf_push( + buf, indent, + "Maximum Reservable Bandwidth: %g (Bytes/sec)\n", + exts->max_rsv_bw); + } + if (IS_SUBTLV(exts, EXT_UNRSV_BW)) { + if (json) { + struct json_object *unrsv_json; + + unrsv_json = json_object_new_object(); + json_object_object_add(json, "unrsvBandwithBytesSec", + unrsv_json); + for (int j = 0; j < MAX_CLASS_TYPE; j += 1) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", j); + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + exts->unrsv_bw[j]); + json_object_string_add(unrsv_json, cnt_buf, + aux_buf); + } + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + unrsv_json = json_object_new_object(); + json_object_object_add(json, "unrsv-bandwith-bytes-sec", + unrsv_json); + for (int j = 0; j < MAX_CLASS_TYPE; j += 1) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", j); + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + exts->unrsv_bw[j]); + json_object_string_add(unrsv_json, cnt_buf, + aux_buf); + } + /* end old deprecated key format */ + } else { + sbuf_push(buf, indent, "Unreserved Bandwidth:\n"); + for (int j = 0; j < MAX_CLASS_TYPE; j += 2) { + sbuf_push( + buf, indent + 2, + "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", + j, exts->unrsv_bw[j], j + 1, + exts->unrsv_bw[j + 1]); + } + } + } + if (IS_SUBTLV(exts, EXT_TE_METRIC)) { + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_int_add(json, "te-metric", exts->te_metric); + json_object_int_add(json, "teMetric", exts->te_metric); + } else + sbuf_push(buf, indent, + "Traffic Engineering Metric: %u\n", + exts->te_metric); + } + if (IS_SUBTLV(exts, EXT_RMT_AS)) { + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_int_add(json, "inter-as-te-remote-as", + exts->remote_as); + json_object_int_add(json, "interAsTeRemoteAs", + exts->remote_as); + } else + sbuf_push(buf, indent, + "Inter-AS TE Remote AS number: %u\n", + exts->remote_as); + } + if (IS_SUBTLV(exts, EXT_RMT_IP)) { + if (json) { + inet_ntop(AF_INET6, &exts->remote_ip, aux_buf, + sizeof(aux_buf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add( + json, "inter-as-te-remote-asbr-ip", aux_buf); + json_object_string_add(json, "interAsTeRemoteAsbrIp", + aux_buf); + } else + sbuf_push(buf, indent, + "Inter-AS TE Remote ASBR IP address: %pI4\n", + &exts->remote_ip); + } + /* Extended metrics */ + if (IS_SUBTLV(exts, EXT_DELAY)) { + if (json) { + struct json_object *avg_json; + avg_json = json_object_new_object(); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_object_add(json, "avg-delay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(exts->delay) + ? "Anomalous" + : "Normal"); + json_object_int_add(avg_json, "micro-sec", exts->delay); + + avg_json = json_object_new_object(); + json_object_object_add(json, "avgDelay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(exts->delay) + ? "Anomalous" + : "Normal"); + json_object_int_add(avg_json, "microSec", exts->delay); + } else + sbuf_push(buf, indent, + "%s Average Link Delay: %u (micro-sec)\n", + IS_ANORMAL(exts->delay) ? "Anomalous" + : "Normal", + exts->delay & TE_EXT_MASK); + } + if (IS_SUBTLV(exts, EXT_MM_DELAY)) { + if (json) { + struct json_object *avg_json; + avg_json = json_object_new_object(); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_object_add(json, "max-min-delay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(exts->min_delay) + ? "Anomalous" + : "Normal"); + snprintfrr(aux_buf, sizeof(aux_buf), "%u / %u", + exts->min_delay & TE_EXT_MASK, + exts->max_delay & TE_EXT_MASK); + json_object_string_add(avg_json, "micro-sec", aux_buf); + + avg_json = json_object_new_object(); + json_object_object_add(json, "maxMinDelay", avg_json); + json_object_string_add(avg_json, "delay", + IS_ANORMAL(exts->min_delay) + ? "Anomalous" + : "Normal"); + snprintfrr(aux_buf, sizeof(aux_buf), "%u / %u", + exts->min_delay & TE_EXT_MASK, + exts->max_delay & TE_EXT_MASK); + json_object_string_add(avg_json, "microSec", aux_buf); + + } else + sbuf_push( + buf, indent, + "%s Min/Max Link Delay: %u / %u (micro-sec)\n", + IS_ANORMAL(exts->min_delay) ? "Anomalous" + : "Normal", + exts->min_delay & TE_EXT_MASK, + exts->max_delay & TE_EXT_MASK); + } + if (IS_SUBTLV(exts, EXT_DELAY_VAR)) { + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_int_add(json, "delay-variation-micro-sec", + exts->delay_var & TE_EXT_MASK); + json_object_int_add(json, "delayVariationMicroSec", + exts->delay_var & TE_EXT_MASK); + } else + sbuf_push(buf, indent, + "Delay Variation: %u (micro-sec)\n", + exts->delay_var & TE_EXT_MASK); + } + if (IS_SUBTLV(exts, EXT_PKT_LOSS)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + (float)((exts->pkt_loss & TE_EXT_MASK) * + LOSS_PRECISION)); + struct json_object *link_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + link_json = json_object_new_object(); + json_object_object_add(json, "link-packet-loss", + link_json); + json_object_string_add(link_json, "loss", + IS_ANORMAL(exts->pkt_loss) + ? "Anomalous" + : "Normal"); + /* typo */ + json_object_string_add(link_json, "percentaje", + aux_buf); + + link_json = json_object_new_object(); + json_object_object_add(json, "linkPacketLoss", + link_json); + json_object_string_add(link_json, "loss", + IS_ANORMAL(exts->pkt_loss) + ? "Anomalous" + : "Normal"); + json_object_string_add(link_json, "percentage", aux_buf); + } else + sbuf_push(buf, indent, "%s Link Packet Loss: %g (%%)\n", + IS_ANORMAL(exts->pkt_loss) ? "Anomalous" + : "Normal", + (float)((exts->pkt_loss & TE_EXT_MASK) * + LOSS_PRECISION)); + } + if (IS_SUBTLV(exts, EXT_RES_BW)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + (exts->res_bw)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, + "unidir-residual-band-bytes-sec", + aux_buf); + json_object_string_add(json, + "unidirResidualBandBytesSec", + aux_buf); + } else + sbuf_push( + buf, indent, + "Unidir. Residual Bandwidth: %g (Bytes/sec)\n", + exts->res_bw); + } + if (IS_SUBTLV(exts, EXT_AVA_BW)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + (exts->ava_bw)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add( + json, "unidir-available-band-bytes-sec", + aux_buf); + json_object_string_add(json, + "unidirAvailableBandBytesSec", + aux_buf); + } else + sbuf_push( + buf, indent, + "Unidir. Available Bandwidth: %g (Bytes/sec)\n", + exts->ava_bw); + } + if (IS_SUBTLV(exts, EXT_USE_BW)) { + if (json) { + snprintfrr(aux_buf, sizeof(aux_buf), "%g", + (exts->use_bw)); + json_object_string_add(json, + "unidir-utilized-band-bytes-sec", + aux_buf); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, + "unidirUtilizedBandBytesSec", + aux_buf); + } else + sbuf_push( + buf, indent, + "Unidir. Utilized Bandwidth: %g (Bytes/sec)\n", + exts->use_bw); + } + /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */ + if (IS_SUBTLV(exts, EXT_ADJ_SID)) { + struct isis_adj_sid *adj; + + if (json) { + struct json_object *arr_adj_json, *flags_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "adj-sid", arr_adj_json); + for (adj = (struct isis_adj_sid *)exts->adj_sid.head; + adj; adj = adj->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", + adj->sid); + flags_json = json_object_new_object(); + json_object_int_add(flags_json, "sid", + adj->sid); + json_object_int_add(flags_json, "weight", + adj->weight); + json_object_string_add( + flags_json, "flag-f", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-b", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-v", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-l", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-s", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-p", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? "1" + : "0"); + json_object_array_add(arr_adj_json, flags_json); + } + /* end old deprecated key format */ + + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "adjSid", arr_adj_json); + for (adj = (struct isis_adj_sid *)exts->adj_sid.head; + adj; adj = adj->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", + adj->sid); + flags_json = json_object_new_object(); + json_object_int_add(flags_json, "sid", adj->sid); + json_object_int_add(flags_json, "weight", + adj->weight); + json_object_boolean_add(flags_json, "flagF", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagB", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagV", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagL", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagS", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagP", + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? true + : false); + json_object_array_add(arr_adj_json, flags_json); + } + } else + for (adj = (struct isis_adj_sid *)exts->adj_sid.head; + adj; adj = adj->next) { + sbuf_push( + buf, indent, + "Adjacency-SID: %u, Weight: %hhu, Flags: F:%c B:%c, V:%c, L:%c, S:%c, P:%c\n", + adj->sid, adj->weight, + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? '1' + : '0'); + } + } + /* Segment Routing LAN-Adjacency as per RFC8667 section #2.2.2 */ + if (IS_SUBTLV(exts, EXT_LAN_ADJ_SID)) { + struct isis_lan_adj_sid *lan; + if (json) { + struct json_object *arr_adj_json, *flags_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "lan-adj-sid", + arr_adj_json); + for (lan = (struct isis_lan_adj_sid *) + exts->adj_sid.head; + lan; lan = lan->next) { + if (((mtid == ISIS_MT_IPV4_UNICAST) && + (lan->family != AF_INET)) || + ((mtid == ISIS_MT_IPV6_UNICAST) && + (lan->family != AF_INET6))) + continue; + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", + lan->sid); + flags_json = json_object_new_object(); + json_object_int_add(flags_json, "sid", + lan->sid); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_string_add( + flags_json, "flag-f", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-b", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-v", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-l", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-s", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-p", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? "1" + : "0"); + json_object_array_add(arr_adj_json, flags_json); + } + /* end old deprecated key format */ + + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "lanAdjSid", arr_adj_json); + for (lan = (struct isis_lan_adj_sid *)exts->adj_sid.head; + lan; lan = lan->next) { + if (((mtid == ISIS_MT_IPV4_UNICAST) && + (lan->family != AF_INET)) || + ((mtid == ISIS_MT_IPV6_UNICAST) && + (lan->family != AF_INET6))) + continue; + snprintfrr(cnt_buf, sizeof(cnt_buf), "%d", + lan->sid); + flags_json = json_object_new_object(); + json_object_int_add(flags_json, "sid", lan->sid); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_boolean_add(flags_json, "flagF", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagB", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagV", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagL", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagS", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? true + : false); + json_object_boolean_add(flags_json, "flagP", + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? true + : false); + json_object_array_add(arr_adj_json, flags_json); + } + } else + + for (lan = (struct isis_lan_adj_sid *) + exts->lan_sid.head; + lan; lan = lan->next) { + if (((mtid == ISIS_MT_IPV4_UNICAST) && + (lan->family != AF_INET)) || + ((mtid == ISIS_MT_IPV6_UNICAST) && + (lan->family != AF_INET6))) + continue; + sbuf_push( + buf, indent, + "Lan-Adjacency-SID: %u, Weight: %hhu, Flags: F:%c B:%c, V:%c, L:%c, S:%c, P:%c\n" + " Neighbor-ID: %pSY\n", + lan->sid, lan->weight, + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_LFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_SFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_ADJ_SID_PFLG + ? '1' + : '0', + lan->neighbor_id); + } + } + /* SRv6 End.X SID as per RFC9352 section #8.1 */ + if (IS_SUBTLV(exts, EXT_SRV6_ENDX_SID)) { + struct isis_srv6_endx_sid_subtlv *adj; + + if (json) { + struct json_object *arr_adj_json, *flags_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "srv6-endx-sid", + arr_adj_json); + for (adj = (struct isis_srv6_endx_sid_subtlv *) + exts->srv6_endx_sid.head; + adj; adj = adj->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6", + &adj->sid); + flags_json = json_object_new_object(); + json_object_string_addf(flags_json, "sid", + "%pI6", &adj->sid); + json_object_string_add( + flags_json, "algorithm", + sr_algorithm_string(adj->algorithm)); + json_object_int_add(flags_json, "weight", + adj->weight); + json_object_string_add( + flags_json, "behavior", + seg6local_action2str(adj->behavior)); + json_object_string_add( + flags_json, "flag-b", + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-s", + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-p", + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG + ? "1" + : "0"); + json_object_array_add(arr_adj_json, flags_json); + if (adj->subsubtlvs) + isis_format_subsubtlvs(adj->subsubtlvs, + NULL, + arr_adj_json, + indent + 4); + } + /* end old deprecated key format */ + + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "srv6EndSID", arr_adj_json); + for (adj = (struct isis_srv6_endx_sid_subtlv *) + exts->srv6_endx_sid.head; + adj; adj = adj->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6", + &adj->sid); + flags_json = json_object_new_object(); + json_object_string_addf(flags_json, "sid", + "%pI6", &adj->sid); + json_object_string_add(flags_json, "algorithm", + sr_algorithm_string( + adj->algorithm)); + json_object_int_add(flags_json, "weight", + adj->weight); + json_object_string_add(flags_json, "behavior", + seg6local_action2str( + adj->behavior)); + json_object_boolean_add( + flags_json, "flagB", + !!(adj->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG)); + json_object_boolean_add( + flags_json, "flagS", + !!(adj->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG)); + json_object_boolean_add( + flags_json, "flagP", + !!(adj->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG)); + json_object_array_add(arr_adj_json, flags_json); + if (adj->subsubtlvs) + isis_format_subsubtlvs(adj->subsubtlvs, + NULL, + arr_adj_json, + indent + 4); + } + } else + for (adj = (struct isis_srv6_endx_sid_subtlv *) + exts->srv6_endx_sid.head; + adj; adj = adj->next) { + sbuf_push( + buf, indent, + "SRv6 End.X SID: %pI6, Algorithm: %s, Weight: %hhu, Endpoint Behavior: %s, Flags: B:%c, S:%c, P:%c\n", + &adj->sid, + sr_algorithm_string(adj->algorithm), + adj->weight, + seg6local_action2str(adj->behavior), + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG + ? '1' + : '0', + adj->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG + ? '1' + : '0'); + if (adj->subsubtlvs) + isis_format_subsubtlvs(adj->subsubtlvs, + buf, NULL, + indent + 4); + } + } + /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */ + if (IS_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID)) { + struct isis_srv6_lan_endx_sid_subtlv *lan; + if (json) { + struct json_object *arr_adj_json, *flags_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "srv6-lan-endx-sid", + arr_adj_json); + for (lan = (struct isis_srv6_lan_endx_sid_subtlv *) + exts->srv6_lan_endx_sid.head; + lan; lan = lan->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6", + &lan->sid); + flags_json = json_object_new_object(); + json_object_string_addf(flags_json, "sid", + "%pI6", &lan->sid); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_string_add( + flags_json, "algorithm", + sr_algorithm_string(lan->algorithm)); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_string_add( + flags_json, "behavior", + seg6local_action2str(lan->behavior)); + json_object_string_add( + flags_json, "flag-b", + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-s", + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG + ? "1" + : "0"); + json_object_string_add( + flags_json, "flag-p", + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG + ? "1" + : "0"); + json_object_string_addf(flags_json, + "neighbor-id", "%pSY", + lan->neighbor_id); + json_object_array_add(arr_adj_json, flags_json); + if (lan->subsubtlvs) + isis_format_subsubtlvs(lan->subsubtlvs, + NULL, + arr_adj_json, + indent + 4); + } + /* end old deprecated key format */ + + arr_adj_json = json_object_new_array(); + json_object_object_add(json, "srv6LanEndxSID", + arr_adj_json); + for (lan = (struct isis_srv6_lan_endx_sid_subtlv *) + exts->srv6_lan_endx_sid.head; + lan; lan = lan->next) { + snprintfrr(cnt_buf, sizeof(cnt_buf), "%pI6", + &lan->sid); + flags_json = json_object_new_object(); + json_object_string_addf(flags_json, "sid", + "%pI6", &lan->sid); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_string_add(flags_json, "algorithm", + sr_algorithm_string( + lan->algorithm)); + json_object_int_add(flags_json, "weight", + lan->weight); + json_object_string_add(flags_json, "behavior", + seg6local_action2str( + lan->behavior)); + json_object_boolean_add( + flags_json, "flagB", + !!(lan->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG)); + json_object_boolean_add( + flags_json, "flagS", + !!(lan->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG)); + json_object_boolean_add( + flags_json, "flagP", + !!(lan->flags & + EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG)); + json_object_string_addf(flags_json, + "neighbor-id", "%pSY", + lan->neighbor_id); + json_object_array_add(arr_adj_json, flags_json); + if (lan->subsubtlvs) + isis_format_subsubtlvs(lan->subsubtlvs, + NULL, + arr_adj_json, + indent + 4); + } + } else + for (lan = (struct isis_srv6_lan_endx_sid_subtlv *) + exts->srv6_lan_endx_sid.head; + lan; lan = lan->next) { + sbuf_push( + buf, indent, + "SRv6 Lan End.X SID: %pI6, Algorithm: %s, Weight: %hhu, Endpoint Behavior: %s, Flags: B:%c, S:%c, P:%c " + "Neighbor-ID: %pSY\n", + &lan->sid, + sr_algorithm_string(lan->algorithm), + lan->weight, + seg6local_action2str(lan->behavior), + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG + ? '1' + : '0', + lan->flags & EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG + ? '1' + : '0', + lan->neighbor_id); + if (lan->subsubtlvs) + isis_format_subsubtlvs(lan->subsubtlvs, + buf, NULL, + indent + 4); + } + } + for (ALL_LIST_ELEMENTS_RO(exts->aslas, node, asla)) + format_item_asla_subtlvs(asla, json, buf, indent); +} + +static void free_item_ext_subtlvs(struct isis_ext_subtlvs *exts) +{ + isis_del_ext_subtlvs(exts); +} + +static int pack_item_ext_subtlv_asla(struct isis_asla_subtlvs *asla, + struct stream *s, size_t *min_len) +{ + size_t subtlv_len; + size_t subtlv_len_pos; + + /* Sub TLV header */ + stream_putc(s, ISIS_SUBTLV_ASLA); + + subtlv_len_pos = stream_get_endp(s); + stream_putc(s, 0); /* length will be filled later */ + + /* SABM Flag/Length */ + if (asla->legacy) + stream_putc(s, ASLA_LEGACY_FLAG | asla->standard_apps_length); + else + stream_putc(s, asla->standard_apps_length); + stream_putc(s, asla->user_def_apps_length); /* UDABM Flag/Length */ + stream_putc(s, asla->standard_apps); + stream_putc(s, asla->user_def_apps); + + /* Administrative Group */ + if (IS_SUBTLV(asla, EXT_ADM_GRP)) { + stream_putc(s, ISIS_SUBTLV_ADMIN_GRP); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, asla->admin_group); + } + + /* Extended Administrative Group */ + if (IS_SUBTLV(asla, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&asla->ext_admin_group) != 0) { + size_t ag_length; + size_t ag_length_pos; + struct admin_group *ag; + + stream_putc(s, ISIS_SUBTLV_EXT_ADMIN_GRP); + ag_length_pos = stream_get_endp(s); + stream_putc(s, 0); /* length will be filled later*/ + + ag = &asla->ext_admin_group; + for (size_t i = 0; i < admin_group_nb_words(ag); i++) + stream_putl(s, ag->bitmap.data[i]); + + ag_length = stream_get_endp(s) - ag_length_pos - 1; + stream_putc_at(s, ag_length_pos, ag_length); + } + + if (IS_SUBTLV(asla, EXT_MAX_BW)) { + stream_putc(s, ISIS_SUBTLV_MAX_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, asla->max_bw); + } + if (IS_SUBTLV(asla, EXT_MAX_RSV_BW)) { + stream_putc(s, ISIS_SUBTLV_MAX_RSV_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, asla->max_rsv_bw); + } + if (IS_SUBTLV(asla, EXT_UNRSV_BW)) { + stream_putc(s, ISIS_SUBTLV_UNRSV_BW); + stream_putc(s, ISIS_SUBTLV_UNRSV_BW_SIZE); + for (int j = 0; j < MAX_CLASS_TYPE; j++) + stream_putf(s, asla->unrsv_bw[j]); + } + if (IS_SUBTLV(asla, EXT_TE_METRIC)) { + stream_putc(s, ISIS_SUBTLV_TE_METRIC); + stream_putc(s, ISIS_SUBTLV_TE_METRIC_SIZE); + stream_put3(s, asla->te_metric); + } + if (IS_SUBTLV(asla, EXT_DELAY)) { + stream_putc(s, ISIS_SUBTLV_AV_DELAY); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, asla->delay); + } + if (IS_SUBTLV(asla, EXT_MM_DELAY)) { + stream_putc(s, ISIS_SUBTLV_MM_DELAY); + stream_putc(s, ISIS_SUBTLV_MM_DELAY_SIZE); + stream_putl(s, asla->min_delay); + stream_putl(s, asla->max_delay); + } + if (IS_SUBTLV(asla, EXT_DELAY_VAR)) { + stream_putc(s, ISIS_SUBTLV_DELAY_VAR); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, asla->delay_var); + } + if (IS_SUBTLV(asla, EXT_PKT_LOSS)) { + stream_putc(s, ISIS_SUBTLV_PKT_LOSS); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, asla->pkt_loss); + } + if (IS_SUBTLV(asla, EXT_RES_BW)) { + stream_putc(s, ISIS_SUBTLV_RES_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, asla->res_bw); + } + if (IS_SUBTLV(asla, EXT_AVA_BW)) { + stream_putc(s, ISIS_SUBTLV_AVA_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, asla->ava_bw); + } + if (IS_SUBTLV(asla, EXT_USE_BW)) { + stream_putc(s, ISIS_SUBTLV_USE_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, asla->use_bw); + } + + subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; + stream_putc_at(s, subtlv_len_pos, subtlv_len); + + return 0; +} + +static int pack_item_ext_subtlvs(struct isis_ext_subtlvs *exts, + struct stream *s, size_t *min_len) +{ + struct isis_asla_subtlvs *asla; + struct listnode *node; + uint8_t size; + int ret; + + if (STREAM_WRITEABLE(s) < ISIS_SUBTLV_MAX_SIZE) { + *min_len = ISIS_SUBTLV_MAX_SIZE; + return 1; + } + + if (IS_SUBTLV(exts, EXT_ADM_GRP)) { + stream_putc(s, ISIS_SUBTLV_ADMIN_GRP); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, exts->adm_group); + } + if (IS_SUBTLV(exts, EXT_EXTEND_ADM_GRP) && + admin_group_nb_words(&exts->ext_admin_group) != 0) { + /* Extended Administrative Group */ + size_t ag_length; + size_t ag_length_pos; + struct admin_group *ag; + + stream_putc(s, ISIS_SUBTLV_EXT_ADMIN_GRP); + ag_length_pos = stream_get_endp(s); + stream_putc(s, 0); /* length will be filled later*/ + + ag = &exts->ext_admin_group; + for (size_t i = 0; i < admin_group_nb_words(ag); i++) + stream_putl(s, ag->bitmap.data[i]); + + ag_length = stream_get_endp(s) - ag_length_pos - 1; + stream_putc_at(s, ag_length_pos, ag_length); + } + if (IS_SUBTLV(exts, EXT_LLRI)) { + stream_putc(s, ISIS_SUBTLV_LLRI); + stream_putc(s, ISIS_SUBTLV_LLRI_SIZE); + stream_putl(s, exts->local_llri); + stream_putl(s, exts->remote_llri); + } + if (IS_SUBTLV(exts, EXT_LOCAL_ADDR)) { + stream_putc(s, ISIS_SUBTLV_LOCAL_IPADDR); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_put(s, &exts->local_addr.s_addr, 4); + } + if (IS_SUBTLV(exts, EXT_NEIGH_ADDR)) { + stream_putc(s, ISIS_SUBTLV_RMT_IPADDR); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_put(s, &exts->neigh_addr.s_addr, 4); + } + if (IS_SUBTLV(exts, EXT_LOCAL_ADDR6)) { + stream_putc(s, ISIS_SUBTLV_LOCAL_IPADDR6); + stream_putc(s, ISIS_SUBTLV_IPV6_ADDR_SIZE); + stream_put(s, &exts->local_addr6, 16); + } + if (IS_SUBTLV(exts, EXT_NEIGH_ADDR6)) { + stream_putc(s, ISIS_SUBTLV_RMT_IPADDR6); + stream_putc(s, ISIS_SUBTLV_IPV6_ADDR_SIZE); + stream_put(s, &exts->neigh_addr6, 16); + } + if (IS_SUBTLV(exts, EXT_MAX_BW)) { + stream_putc(s, ISIS_SUBTLV_MAX_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, exts->max_bw); + } + if (IS_SUBTLV(exts, EXT_MAX_RSV_BW)) { + stream_putc(s, ISIS_SUBTLV_MAX_RSV_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, exts->max_rsv_bw); + } + if (IS_SUBTLV(exts, EXT_UNRSV_BW)) { + stream_putc(s, ISIS_SUBTLV_UNRSV_BW); + stream_putc(s, ISIS_SUBTLV_UNRSV_BW_SIZE); + for (int j = 0; j < MAX_CLASS_TYPE; j++) + stream_putf(s, exts->unrsv_bw[j]); + } + if (IS_SUBTLV(exts, EXT_TE_METRIC)) { + stream_putc(s, ISIS_SUBTLV_TE_METRIC); + stream_putc(s, ISIS_SUBTLV_TE_METRIC_SIZE); + stream_put3(s, exts->te_metric); + } + if (IS_SUBTLV(exts, EXT_RMT_AS)) { + stream_putc(s, ISIS_SUBTLV_RAS); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, exts->remote_as); + } + if (IS_SUBTLV(exts, EXT_RMT_IP)) { + stream_putc(s, ISIS_SUBTLV_RIP); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_put(s, &exts->remote_ip.s_addr, 4); + } + if (IS_SUBTLV(exts, EXT_DELAY)) { + stream_putc(s, ISIS_SUBTLV_AV_DELAY); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, exts->delay); + } + if (IS_SUBTLV(exts, EXT_MM_DELAY)) { + stream_putc(s, ISIS_SUBTLV_MM_DELAY); + stream_putc(s, ISIS_SUBTLV_MM_DELAY_SIZE); + stream_putl(s, exts->min_delay); + stream_putl(s, exts->max_delay); + } + if (IS_SUBTLV(exts, EXT_DELAY_VAR)) { + stream_putc(s, ISIS_SUBTLV_DELAY_VAR); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, exts->delay_var); + } + if (IS_SUBTLV(exts, EXT_PKT_LOSS)) { + stream_putc(s, ISIS_SUBTLV_PKT_LOSS); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putl(s, exts->pkt_loss); + } + if (IS_SUBTLV(exts, EXT_RES_BW)) { + stream_putc(s, ISIS_SUBTLV_RES_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, exts->res_bw); + } + if (IS_SUBTLV(exts, EXT_AVA_BW)) { + stream_putc(s, ISIS_SUBTLV_AVA_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, exts->ava_bw); + } + if (IS_SUBTLV(exts, EXT_USE_BW)) { + stream_putc(s, ISIS_SUBTLV_USE_BW); + stream_putc(s, ISIS_SUBTLV_DEF_SIZE); + stream_putf(s, exts->use_bw); + } + /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */ + if (IS_SUBTLV(exts, EXT_ADJ_SID)) { + struct isis_adj_sid *adj; + + for (adj = (struct isis_adj_sid *)exts->adj_sid.head; adj; + adj = adj->next) { + stream_putc(s, ISIS_SUBTLV_ADJ_SID); + size = ISIS_SUBTLV_ADJ_SID_SIZE; + if (!(adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + size++; + stream_putc(s, size); + stream_putc(s, adj->flags); + stream_putc(s, adj->weight); + if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) + stream_put3(s, adj->sid); + else + stream_putl(s, adj->sid); + + } + } + /* Segment Routing LAN-Adjacency as per RFC8667 section #2.2.2 */ + if (IS_SUBTLV(exts, EXT_LAN_ADJ_SID)) { + struct isis_lan_adj_sid *lan; + + for (lan = (struct isis_lan_adj_sid *)exts->lan_sid.head; lan; + lan = lan->next) { + stream_putc(s, ISIS_SUBTLV_LAN_ADJ_SID); + size = ISIS_SUBTLV_LAN_ADJ_SID_SIZE; + if (!(lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + size++; + stream_putc(s, size); + stream_putc(s, lan->flags); + stream_putc(s, lan->weight); + stream_put(s, lan->neighbor_id, 6); + if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) + stream_put3(s, lan->sid); + else + stream_putl(s, lan->sid); + } + } + /* SRv6 End.X SID as per RFC9352 section #8.1 */ + if (IS_SUBTLV(exts, EXT_SRV6_ENDX_SID)) { + struct isis_srv6_endx_sid_subtlv *adj; + size_t subtlv_len; + size_t subtlv_len_pos; + + for (adj = (struct isis_srv6_endx_sid_subtlv *) + exts->srv6_endx_sid.head; + adj; adj = adj->next) { + stream_putc(s, ISIS_SUBTLV_SRV6_ENDX_SID); + + subtlv_len_pos = stream_get_endp(s); + stream_putc(s, 0); /* length will be filled later */ + + stream_putc(s, adj->flags); + stream_putc(s, adj->algorithm); + stream_putc(s, adj->weight); + stream_putw(s, adj->behavior); + stream_put(s, &adj->sid, IPV6_MAX_BYTELEN); + + if (adj->subsubtlvs) { + /* Pack Sub-Sub-TLVs */ + if (isis_pack_subsubtlvs(adj->subsubtlvs, s)) + return 1; + } else { + /* No Sub-Sub-TLVs */ + if (STREAM_WRITEABLE(s) < 1) { + *min_len = + ISIS_SUBTLV_SRV6_ENDX_SID_SIZE; + return 1; + } + + /* Put 0 as Sub-Sub-TLV length, because we have + * no Sub-Sub-TLVs */ + stream_putc(s, 0); + } + + subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; + stream_putc_at(s, subtlv_len_pos, subtlv_len); + } + } + /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */ + if (IS_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID)) { + struct isis_srv6_lan_endx_sid_subtlv *lan; + size_t subtlv_len; + size_t subtlv_len_pos; + + for (lan = (struct isis_srv6_lan_endx_sid_subtlv *) + exts->srv6_lan_endx_sid.head; + lan; lan = lan->next) { + stream_putc(s, ISIS_SUBTLV_SRV6_LAN_ENDX_SID); + + subtlv_len_pos = stream_get_endp(s); + stream_putc(s, 0); /* length will be filled later */ + + stream_put(s, lan->neighbor_id, 6); + stream_putc(s, lan->flags); + stream_putc(s, lan->algorithm); + stream_putc(s, lan->weight); + stream_putw(s, lan->behavior); + stream_put(s, &lan->sid, IPV6_MAX_BYTELEN); + + if (lan->subsubtlvs) { + /* Pack Sub-Sub-TLVs */ + if (isis_pack_subsubtlvs(lan->subsubtlvs, s)) + return 1; + } else { + /* No Sub-Sub-TLVs */ + if (STREAM_WRITEABLE(s) < 1) { + *min_len = + ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE; + return 1; + } + + /* Put 0 as Sub-Sub-TLV length, because we have + * no Sub-Sub-TLVs */ + stream_putc(s, 0); + } + + subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; + stream_putc_at(s, subtlv_len_pos, subtlv_len); + } + } + + for (ALL_LIST_ELEMENTS_RO(exts->aslas, node, asla)) { + ret = pack_item_ext_subtlv_asla(asla, s, min_len); + if (ret < 0) + return ret; + } + + return 0; +} + +static int unpack_item_ext_subtlv_asla(uint16_t mtid, uint8_t subtlv_len, + struct stream *s, struct sbuf *log, + int indent, + struct isis_ext_subtlvs *exts) +{ + /* Standard App Identifier Bit Flags/Length */ + uint8_t sabm_flag_len; + /* User-defined App Identifier Bit Flags/Length */ + uint8_t uabm_flag_len; + uint8_t sabm[ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH] = { 0 }; + uint8_t uabm[ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH] = { 0 }; + uint8_t readable = subtlv_len; + uint8_t subsubtlv_type; + uint8_t subsubtlv_len; + size_t nb_groups; + struct isis_asla_subtlvs *asla; + + if (subtlv_len < ISIS_SUBSUBTLV_HDR_SIZE) { + TLV_SIZE_MISMATCH(log, indent, "ASLA"); + return -1; + } + + + asla = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*asla)); + + admin_group_init(&asla->ext_admin_group); + + + sabm_flag_len = stream_getc(s); + uabm_flag_len = stream_getc(s); + asla->legacy = CHECK_FLAG(sabm_flag_len, ASLA_LEGACY_FLAG); + asla->standard_apps_length = ASLA_APPS_LENGTH_MASK & sabm_flag_len; + asla->user_def_apps_length = ASLA_APPS_LENGTH_MASK & uabm_flag_len; + + readable -= ISIS_SUBSUBTLV_HDR_SIZE; + if (readable < + asla->standard_apps_length + asla->user_def_apps_length) { + TLV_SIZE_MISMATCH(log, indent, "ASLA"); + return -1; + } + + if ((asla->standard_apps_length > ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH) || + (asla->user_def_apps_length > ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH)) { + zlog_err("Standard or User-Defined Application Identifier Bit Mask Length greater than %u bytes. Received respectively a length of %u and %u bytes.", + ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH, + asla->standard_apps_length, asla->user_def_apps_length); + stream_forward_getp(s, readable); + return -1; + } + + for (int i = 0; i < asla->standard_apps_length; i++) + sabm[i] = stream_getc(s); + for (int i = 0; i < asla->user_def_apps_length; i++) + uabm[i] = stream_getc(s); + + readable -= (asla->standard_apps_length + asla->user_def_apps_length); + + asla->standard_apps = sabm[0]; + asla->user_def_apps = uabm[0]; + + while (readable > 0) { + if (readable < ISIS_SUBSUBTLV_HDR_SIZE) { + TLV_SIZE_MISMATCH(log, indent, "ASLA Sub TLV"); + return -1; + } + + subsubtlv_type = stream_getc(s); + subsubtlv_len = stream_getc(s); + readable -= ISIS_SUBSUBTLV_HDR_SIZE; + + + switch (subsubtlv_type) { + case ISIS_SUBTLV_ADMIN_GRP: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "ASLA Adm Group"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->admin_group = stream_getl(s); + SET_SUBTLV(asla, EXT_ADM_GRP); + } + break; + + case ISIS_SUBTLV_EXT_ADMIN_GRP: + nb_groups = subsubtlv_len / sizeof(uint32_t); + for (size_t i = 0; i < nb_groups; i++) { + uint32_t val = stream_getl(s); + + admin_group_bulk_set(&asla->ext_admin_group, + val, i); + } + SET_SUBTLV(asla, EXT_EXTEND_ADM_GRP); + break; + case ISIS_SUBTLV_MAX_BW: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Maximum Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->max_bw = stream_getf(s); + SET_SUBTLV(asla, EXT_MAX_BW); + } + break; + case ISIS_SUBTLV_MAX_RSV_BW: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Maximum Reservable Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->max_rsv_bw = stream_getf(s); + SET_SUBTLV(asla, EXT_MAX_RSV_BW); + } + break; + case ISIS_SUBTLV_UNRSV_BW: + if (subsubtlv_len != ISIS_SUBTLV_UNRSV_BW_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Unreserved Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + for (int i = 0; i < MAX_CLASS_TYPE; i++) + asla->unrsv_bw[i] = stream_getf(s); + SET_SUBTLV(asla, EXT_UNRSV_BW); + } + break; + case ISIS_SUBTLV_TE_METRIC: + if (subsubtlv_len != ISIS_SUBTLV_TE_METRIC_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Traffic Engineering Metric"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->te_metric = stream_get3(s); + SET_SUBTLV(asla, EXT_TE_METRIC); + } + break; + /* Extended Metrics as defined in RFC 7810 */ + case ISIS_SUBTLV_AV_DELAY: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Average Link Delay"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->delay = stream_getl(s); + SET_SUBTLV(asla, EXT_DELAY); + } + break; + case ISIS_SUBTLV_MM_DELAY: + if (subsubtlv_len != ISIS_SUBTLV_MM_DELAY_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Min/Max Link Delay"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->min_delay = stream_getl(s); + asla->max_delay = stream_getl(s); + SET_SUBTLV(asla, EXT_MM_DELAY); + } + break; + case ISIS_SUBTLV_DELAY_VAR: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Delay Variation"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->delay_var = stream_getl(s); + SET_SUBTLV(asla, EXT_DELAY_VAR); + } + break; + case ISIS_SUBTLV_PKT_LOSS: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Link Packet Loss"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->pkt_loss = stream_getl(s); + SET_SUBTLV(asla, EXT_PKT_LOSS); + } + break; + case ISIS_SUBTLV_RES_BW: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Residual Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->res_bw = stream_getf(s); + SET_SUBTLV(asla, EXT_RES_BW); + } + break; + case ISIS_SUBTLV_AVA_BW: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Available Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->ava_bw = stream_getf(s); + SET_SUBTLV(asla, EXT_AVA_BW); + } + break; + case ISIS_SUBTLV_USE_BW: + if (subsubtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Utilized Bandwidth"); + stream_forward_getp(s, subsubtlv_len); + } else { + asla->use_bw = stream_getf(s); + SET_SUBTLV(asla, EXT_USE_BW); + } + break; + default: + zlog_debug("unknown (t,l)=(%u,%u)", subsubtlv_type, + subsubtlv_len); + stream_forward_getp(s, subsubtlv_len); + break; + } + readable -= subsubtlv_len; + } + + listnode_add(exts->aslas, asla); + + return 0; +} + +static int unpack_item_ext_subtlvs(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + uint8_t sum = 0; + uint8_t subtlv_type; + uint8_t subtlv_len; + uint8_t subsubtlv_len; + size_t nb_groups; + uint32_t val; + + struct isis_extended_reach *rv = dest; + struct isis_ext_subtlvs *exts = isis_alloc_ext_subtlvs(); + + rv->subtlvs = exts; + + /* + * Parse subTLVs until reach subTLV length + * Check that it remains at least 2 bytes: subTLV Type & Length + */ + while (len > sum + 2) { + /* Read SubTLV Type and Length */ + subtlv_type = stream_getc(s); + subtlv_len = stream_getc(s); + if (subtlv_len > len - sum - ISIS_SUBTLV_HDR_SIZE) { + sbuf_push( + log, indent, + "TLV %hhu: Available data %u is less than TLV size %u !\n", + subtlv_type, len - sum - ISIS_SUBTLV_HDR_SIZE, + subtlv_len); + return 1; + } + + switch (subtlv_type) { + /* Standard Metric as defined in RFC5305 */ + case ISIS_SUBTLV_ADMIN_GRP: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Administrative Group"); + stream_forward_getp(s, subtlv_len); + } else { + exts->adm_group = stream_getl(s); + SET_SUBTLV(exts, EXT_ADM_GRP); + } + break; + case ISIS_SUBTLV_EXT_ADMIN_GRP: + nb_groups = subtlv_len / sizeof(uint32_t); + for (size_t i = 0; i < nb_groups; i++) { + val = stream_getl(s); + admin_group_bulk_set(&exts->ext_admin_group, + val, i); + } + SET_SUBTLV(exts, EXT_EXTEND_ADM_GRP); + break; + case ISIS_SUBTLV_LLRI: + if (subtlv_len != ISIS_SUBTLV_LLRI_SIZE) { + TLV_SIZE_MISMATCH(log, indent, "Link ID"); + stream_forward_getp(s, subtlv_len); + } else { + exts->local_llri = stream_getl(s); + exts->remote_llri = stream_getl(s); + SET_SUBTLV(exts, EXT_LLRI); + } + break; + case ISIS_SUBTLV_LOCAL_IPADDR: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Local IP address"); + stream_forward_getp(s, subtlv_len); + } else { + stream_get(&exts->local_addr.s_addr, s, 4); + SET_SUBTLV(exts, EXT_LOCAL_ADDR); + } + break; + case ISIS_SUBTLV_RMT_IPADDR: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Remote IP address"); + stream_forward_getp(s, subtlv_len); + } else { + stream_get(&exts->neigh_addr.s_addr, s, 4); + SET_SUBTLV(exts, EXT_NEIGH_ADDR); + } + break; + case ISIS_SUBTLV_LOCAL_IPADDR6: + if (subtlv_len != ISIS_SUBTLV_IPV6_ADDR_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Local IPv6 address"); + stream_forward_getp(s, subtlv_len); + } else { + stream_get(&exts->local_addr6, s, 16); + SET_SUBTLV(exts, EXT_LOCAL_ADDR6); + } + break; + case ISIS_SUBTLV_RMT_IPADDR6: + if (subtlv_len != ISIS_SUBTLV_IPV6_ADDR_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Remote IPv6 address"); + stream_forward_getp(s, subtlv_len); + } else { + stream_get(&exts->neigh_addr6, s, 16); + SET_SUBTLV(exts, EXT_NEIGH_ADDR6); + } + break; + case ISIS_SUBTLV_MAX_BW: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Maximum Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + exts->max_bw = stream_getf(s); + SET_SUBTLV(exts, EXT_MAX_BW); + } + break; + case ISIS_SUBTLV_MAX_RSV_BW: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Maximum Reservable Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + exts->max_rsv_bw = stream_getf(s); + SET_SUBTLV(exts, EXT_MAX_RSV_BW); + } + break; + case ISIS_SUBTLV_UNRSV_BW: + if (subtlv_len != ISIS_SUBTLV_UNRSV_BW_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Unreserved Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + for (int i = 0; i < MAX_CLASS_TYPE; i++) + exts->unrsv_bw[i] = stream_getf(s); + SET_SUBTLV(exts, EXT_UNRSV_BW); + } + break; + case ISIS_SUBTLV_TE_METRIC: + if (subtlv_len != ISIS_SUBTLV_TE_METRIC_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Traffic Engineering Metric"); + stream_forward_getp(s, subtlv_len); + } else { + exts->te_metric = stream_get3(s); + SET_SUBTLV(exts, EXT_TE_METRIC); + } + break; + case ISIS_SUBTLV_RAS: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Remote AS number"); + stream_forward_getp(s, subtlv_len); + } else { + exts->remote_as = stream_getl(s); + SET_SUBTLV(exts, EXT_RMT_AS); + } + break; + case ISIS_SUBTLV_RIP: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Remote ASBR IP Address"); + stream_forward_getp(s, subtlv_len); + } else { + stream_get(&exts->remote_ip.s_addr, s, 4); + SET_SUBTLV(exts, EXT_RMT_IP); + } + break; + /* Extended Metrics as defined in RFC 7810 */ + case ISIS_SUBTLV_AV_DELAY: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Average Link Delay"); + stream_forward_getp(s, subtlv_len); + } else { + exts->delay = stream_getl(s); + SET_SUBTLV(exts, EXT_DELAY); + } + break; + case ISIS_SUBTLV_MM_DELAY: + if (subtlv_len != ISIS_SUBTLV_MM_DELAY_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Min/Max Link Delay"); + stream_forward_getp(s, subtlv_len); + } else { + exts->min_delay = stream_getl(s); + exts->max_delay = stream_getl(s); + SET_SUBTLV(exts, EXT_MM_DELAY); + } + break; + case ISIS_SUBTLV_DELAY_VAR: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Delay Variation"); + stream_forward_getp(s, subtlv_len); + } else { + exts->delay_var = stream_getl(s); + SET_SUBTLV(exts, EXT_DELAY_VAR); + } + break; + case ISIS_SUBTLV_PKT_LOSS: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Link Packet Loss"); + stream_forward_getp(s, subtlv_len); + } else { + exts->pkt_loss = stream_getl(s); + SET_SUBTLV(exts, EXT_PKT_LOSS); + } + break; + case ISIS_SUBTLV_RES_BW: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Residual Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + exts->res_bw = stream_getf(s); + SET_SUBTLV(exts, EXT_RES_BW); + } + break; + case ISIS_SUBTLV_AVA_BW: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Available Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + exts->ava_bw = stream_getf(s); + SET_SUBTLV(exts, EXT_AVA_BW); + } + break; + case ISIS_SUBTLV_USE_BW: + if (subtlv_len != ISIS_SUBTLV_DEF_SIZE) { + TLV_SIZE_MISMATCH( + log, indent, + "Unidirectional Utilized Bandwidth"); + stream_forward_getp(s, subtlv_len); + } else { + exts->use_bw = stream_getf(s); + SET_SUBTLV(exts, EXT_USE_BW); + } + break; + /* Segment Routing Adjacency as per RFC8667 section #2.2.1 */ + case ISIS_SUBTLV_ADJ_SID: + if (subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE + && subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE + 1) { + TLV_SIZE_MISMATCH(log, indent, "Adjacency SID"); + stream_forward_getp(s, subtlv_len); + } else { + struct isis_adj_sid *adj; + + adj = XCALLOC(MTYPE_ISIS_SUBTLV, + sizeof(struct isis_adj_sid)); + adj->flags = stream_getc(s); + adj->weight = stream_getc(s); + if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + && subtlv_len != ISIS_SUBTLV_ADJ_SID_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "Adjacency SID"); + stream_forward_getp(s, subtlv_len - 2); + XFREE(MTYPE_ISIS_SUBTLV, adj); + break; + } + + if (!(adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) + && subtlv_len + != ISIS_SUBTLV_ADJ_SID_SIZE + + 1) { + TLV_SIZE_MISMATCH(log, indent, + "Adjacency SID"); + stream_forward_getp(s, subtlv_len - 2); + XFREE(MTYPE_ISIS_SUBTLV, adj); + break; + } + + if (adj->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) { + adj->sid = stream_get3(s); + adj->sid &= MPLS_LABEL_VALUE_MASK; + } else { + adj->sid = stream_getl(s); + } + if (mtid == ISIS_MT_IPV4_UNICAST) + adj->family = AF_INET; + if (mtid == ISIS_MT_IPV6_UNICAST) + adj->family = AF_INET6; + append_item(&exts->adj_sid, + (struct isis_item *)adj); + SET_SUBTLV(exts, EXT_ADJ_SID); + } + break; + /* Segment Routing LAN-Adjacency as per RFC8667 section 2.2.2 */ + case ISIS_SUBTLV_LAN_ADJ_SID: + if (subtlv_len != ISIS_SUBTLV_LAN_ADJ_SID_SIZE + && subtlv_len != ISIS_SUBTLV_LAN_ADJ_SID_SIZE + 1) { + TLV_SIZE_MISMATCH(log, indent, + "LAN-Adjacency SID"); + stream_forward_getp(s, subtlv_len); + } else { + struct isis_lan_adj_sid *lan; + + lan = XCALLOC(MTYPE_ISIS_SUBTLV, + sizeof(struct isis_lan_adj_sid)); + lan->flags = stream_getc(s); + lan->weight = stream_getc(s); + stream_get(&(lan->neighbor_id), s, + ISIS_SYS_ID_LEN); + + if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG + && subtlv_len + != ISIS_SUBTLV_LAN_ADJ_SID_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "LAN-Adjacency SID"); + stream_forward_getp( + s, subtlv_len - 2 + - ISIS_SYS_ID_LEN); + XFREE(MTYPE_ISIS_SUBTLV, lan); + break; + } + + if (!(lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) + && subtlv_len + != ISIS_SUBTLV_LAN_ADJ_SID_SIZE + + 1) { + TLV_SIZE_MISMATCH(log, indent, + "LAN-Adjacency SID"); + stream_forward_getp( + s, subtlv_len - 2 + - ISIS_SYS_ID_LEN); + XFREE(MTYPE_ISIS_SUBTLV, lan); + break; + } + + if (lan->flags & EXT_SUBTLV_LINK_ADJ_SID_VFLG) { + lan->sid = stream_get3(s); + lan->sid &= MPLS_LABEL_VALUE_MASK; + } else { + lan->sid = stream_getl(s); + } + if (mtid == ISIS_MT_IPV4_UNICAST) + lan->family = AF_INET; + if (mtid == ISIS_MT_IPV6_UNICAST) + lan->family = AF_INET6; + append_item(&exts->lan_sid, + (struct isis_item *)lan); + SET_SUBTLV(exts, EXT_LAN_ADJ_SID); + } + break; + /* SRv6 End.X SID as per RFC9352 section #8.1 */ + case ISIS_SUBTLV_SRV6_ENDX_SID: + if (subtlv_len < ISIS_SUBTLV_SRV6_ENDX_SID_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "SRv6 End.X SID"); + stream_forward_getp(s, subtlv_len); + } else { + struct isis_srv6_endx_sid_subtlv *adj; + + adj = XCALLOC( + MTYPE_ISIS_SUBTLV, + sizeof(struct + isis_srv6_endx_sid_subtlv)); + adj->flags = stream_getc(s); + adj->algorithm = stream_getc(s); + adj->weight = stream_getc(s); + adj->behavior = stream_getw(s); + stream_get(&adj->sid, s, IPV6_MAX_BYTELEN); + subsubtlv_len = stream_getc(s); + + adj->subsubtlvs = isis_alloc_subsubtlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID); + + bool unpacked_known_tlvs = false; + if (unpack_tlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID, + subsubtlv_len, s, log, + adj->subsubtlvs, indent + 4, + &unpacked_known_tlvs)) { + XFREE(MTYPE_ISIS_SUBTLV, adj); + break; + } + if (!unpacked_known_tlvs) { + isis_free_subsubtlvs(adj->subsubtlvs); + adj->subsubtlvs = NULL; + } + + append_item(&exts->srv6_endx_sid, + (struct isis_item *)adj); + SET_SUBTLV(exts, EXT_SRV6_ENDX_SID); + } + break; + /* SRv6 LAN End.X SID as per RFC9352 section #8.2 */ + case ISIS_SUBTLV_SRV6_LAN_ENDX_SID: + if (subtlv_len < ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE) { + TLV_SIZE_MISMATCH(log, indent, + "SRv6 LAN End.X SID"); + stream_forward_getp(s, subtlv_len); + } else { + struct isis_srv6_lan_endx_sid_subtlv *lan; + + lan = XCALLOC( + MTYPE_ISIS_SUBTLV, + sizeof(struct + isis_srv6_lan_endx_sid_subtlv)); + stream_get(&(lan->neighbor_id), s, + ISIS_SYS_ID_LEN); + lan->flags = stream_getc(s); + lan->algorithm = stream_getc(s); + lan->weight = stream_getc(s); + lan->behavior = stream_getw(s); + stream_get(&lan->sid, s, IPV6_MAX_BYTELEN); + subsubtlv_len = stream_getc(s); + + lan->subsubtlvs = isis_alloc_subsubtlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID); + + bool unpacked_known_tlvs = false; + if (unpack_tlvs( + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID, + subsubtlv_len, s, log, + lan->subsubtlvs, indent + 4, + &unpacked_known_tlvs)) { + XFREE(MTYPE_ISIS_SUBTLV, lan); + break; + } + if (!unpacked_known_tlvs) { + isis_free_subsubtlvs(lan->subsubtlvs); + lan->subsubtlvs = NULL; + } + + append_item(&exts->srv6_lan_endx_sid, + (struct isis_item *)lan); + SET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID); + } + break; + case ISIS_SUBTLV_ASLA: + if (unpack_item_ext_subtlv_asla(mtid, subtlv_len, s, + log, indent, + exts) < 0) { + sbuf_push(log, indent, "TLV parse error"); + } + break; + default: + /* Skip unknown TLV */ + stream_forward_getp(s, subtlv_len); + break; + } + sum += subtlv_len + ISIS_SUBTLV_HDR_SIZE; + } + + return 0; +} + +/* Functions for Sub-TLV 3 SR Prefix-SID as per RFC8667 section 2.1 */ +static struct isis_item *copy_item_prefix_sid(struct isis_item *i) +{ + struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; + struct isis_prefix_sid *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); + + rv->flags = sid->flags; + rv->algorithm = sid->algorithm; + rv->value = sid->value; + return (struct isis_item *)rv; +} + +static void format_item_prefix_sid(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; + + if (json) { + struct json_object *sr_json, *array_json; + + sr_json = json_object_new_object(); + json_object_object_get_ex(json, "sr", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "sr", array_json); + } + json_object_array_add(array_json, sr_json); + if (sid->flags & ISIS_PREFIX_SID_VALUE) { + json_object_int_add(sr_json, "label", sid->value); + } else { + json_object_int_add(sr_json, "index", sid->value); + } + json_object_int_add(sr_json, "alg", sid->algorithm); + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated non boolean json") +#endif + /* old deprecated keys (no booleans) */ + json_object_string_add( + sr_json, "readvertised", + ((sid->flags & ISIS_PREFIX_SID_READVERTISED) ? "yes" + : "")); + json_object_string_add( + sr_json, "node", + ((sid->flags & ISIS_PREFIX_SID_NODE) ? "yes" : "")); + json_object_string_add(sr_json, "php", + ((sid->flags & ISIS_PREFIX_SID_NO_PHP) + ? "no-php" + : "php")); + json_object_string_add( + sr_json, "explicit-null", + ((sid->flags & ISIS_PREFIX_SID_EXPLICIT_NULL) ? "yes" + : "")); + json_object_string_add( + sr_json, "value", + ((sid->flags & ISIS_PREFIX_SID_VALUE) ? "yes" : "")); + json_object_string_add( + sr_json, "local", + ((sid->flags & ISIS_PREFIX_SID_LOCAL) ? "yes" : "")); + /* end deprecated keys (no booleans) */ + + struct json_object *flags_json; + + flags_json = json_object_new_object(); + json_object_object_add(sr_json, "flags", flags_json); + + json_object_boolean_add(flags_json, "readvertised", + !!(sid->flags & + ISIS_PREFIX_SID_READVERTISED)); + json_object_boolean_add(flags_json, "node", + !!(sid->flags & ISIS_PREFIX_SID_NODE)); + json_object_boolean_add(flags_json, "noPHP", + !!(sid->flags & ISIS_PREFIX_SID_NO_PHP)); + json_object_boolean_add(flags_json, "explicitNull", + !!(sid->flags & + ISIS_PREFIX_SID_EXPLICIT_NULL)); + json_object_boolean_add(flags_json, "value", + !!(sid->flags & ISIS_PREFIX_SID_VALUE)); + json_object_boolean_add(flags_json, "local", + !!(sid->flags & ISIS_PREFIX_SID_LOCAL)); + + } else { + sbuf_push(buf, indent, "SR Prefix-SID "); + if (sid->flags & ISIS_PREFIX_SID_VALUE) { + sbuf_push(buf, 0, "Label: %u, ", sid->value); + } else { + sbuf_push(buf, 0, "Index: %u, ", sid->value); + } + sbuf_push(buf, 0, "Algorithm: %hhu, ", sid->algorithm); + sbuf_push(buf, 0, "Flags:%s%s%s%s%s%s\n", + sid->flags & ISIS_PREFIX_SID_READVERTISED + ? " READVERTISED" + : "", + sid->flags & ISIS_PREFIX_SID_NODE ? " NODE" : "", + sid->flags & ISIS_PREFIX_SID_NO_PHP ? " NO-PHP" + : " PHP", + sid->flags & ISIS_PREFIX_SID_EXPLICIT_NULL + ? " EXPLICIT-NULL" + : "", + sid->flags & ISIS_PREFIX_SID_VALUE ? " VALUE" : "", + sid->flags & ISIS_PREFIX_SID_LOCAL ? " LOCAL" : ""); + } +} + +static void free_item_prefix_sid(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_SUBTLV, i); +} + +static int pack_item_prefix_sid(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_prefix_sid *sid = (struct isis_prefix_sid *)i; + + uint8_t size = (sid->flags & ISIS_PREFIX_SID_VALUE) ? 5 : 6; + + if (STREAM_WRITEABLE(s) < size) { + *min_len = size; + return 1; + } + + stream_putc(s, sid->flags); + stream_putc(s, sid->algorithm); + + if (sid->flags & ISIS_PREFIX_SID_VALUE) { + stream_put3(s, sid->value); + } else { + stream_putl(s, sid->value); + } + + return 0; +} + +static int unpack_item_prefix_sid(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + struct isis_subtlvs *subtlvs = dest; + struct isis_prefix_sid sid = { + }; + + sbuf_push(log, indent, "Unpacking SR Prefix-SID...\n"); + + if (len < 5) { + sbuf_push(log, indent, + "Not enough data left. (expected 5 or more bytes, got %hhu)\n", + len); + return 1; + } + + sid.flags = stream_getc(s); + if (!!(sid.flags & ISIS_PREFIX_SID_VALUE) + != !!(sid.flags & ISIS_PREFIX_SID_LOCAL)) { + sbuf_push(log, indent, "Flags implausible: Local Flag needs to match Value Flag\n"); + return 1; + } + + sid.algorithm = stream_getc(s); + + uint8_t expected_size = (sid.flags & ISIS_PREFIX_SID_VALUE) + ? ISIS_SUBTLV_PREFIX_SID_SIZE + : ISIS_SUBTLV_PREFIX_SID_SIZE + 1; + if (len != expected_size) { + sbuf_push(log, indent, + "TLV size differs from expected size. (expected %u but got %hhu)\n", + expected_size, len); + return 1; + } + + if (sid.flags & ISIS_PREFIX_SID_VALUE) { + sid.value = stream_get3(s); + if (!IS_MPLS_UNRESERVED_LABEL(sid.value)) { + sbuf_push(log, indent, "Invalid absolute SID %u\n", + sid.value); + return 1; + } + } else { + sid.value = stream_getl(s); + } + + format_item_prefix_sid(mtid, (struct isis_item *)&sid, log, NULL, indent + 2); + append_item(&subtlvs->prefix_sids, copy_item_prefix_sid((struct isis_item *)&sid)); + return 0; +} + +/* Functions for Sub-TVL ??? IPv6 Source Prefix */ + +static struct prefix_ipv6 *copy_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p) +{ + if (!p) + return NULL; + + struct prefix_ipv6 *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); + rv->family = p->family; + rv->prefixlen = p->prefixlen; + memcpy(&rv->prefix, &p->prefix, sizeof(rv->prefix)); + return rv; +} + +static void format_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p, + struct sbuf *buf, + struct json_object *json, + int indent) +{ + if (!p) + return; + + char prefixbuf[PREFIX2STR_BUFFER]; + if (json) { + prefix2str(p, prefixbuf, sizeof(prefixbuf)); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "ipv6-src-prefix", prefixbuf); + json_object_string_add(json, "ipv6SrcPrefix", prefixbuf); + } else { + sbuf_push(buf, indent, "IPv6 Source Prefix: %s\n", + prefix2str(p, prefixbuf, sizeof(prefixbuf))); + } +} + +static int pack_subtlv_ipv6_source_prefix(struct prefix_ipv6 *p, + struct stream *s) +{ + if (!p) + return 0; + + if (STREAM_WRITEABLE(s) < 3 + (unsigned)PSIZE(p->prefixlen)) + return 1; + + stream_putc(s, ISIS_SUBTLV_IPV6_SOURCE_PREFIX); + stream_putc(s, 1 + PSIZE(p->prefixlen)); + stream_putc(s, p->prefixlen); + stream_put(s, &p->prefix, PSIZE(p->prefixlen)); + return 0; +} + +static int unpack_subtlv_ipv6_source_prefix(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_subtlvs *subtlvs = dest; + struct prefix_ipv6 p = { + .family = AF_INET6, + }; + + sbuf_push(log, indent, "Unpacking IPv6 Source Prefix Sub-TLV...\n"); + + if (tlv_len < 1) { + sbuf_push(log, indent, + "Not enough data left. (expected 1 or more bytes, got %hhu)\n", + tlv_len); + return 1; + } + + p.prefixlen = stream_getc(s); + if (p.prefixlen > IPV6_MAX_BITLEN) { + sbuf_push(log, indent, "Prefixlen %u is implausible for IPv6\n", + p.prefixlen); + return 1; + } + + if (tlv_len != 1 + PSIZE(p.prefixlen)) { + sbuf_push( + log, indent, + "TLV size differs from expected size for the prefixlen. (expected %u but got %hhu)\n", + 1 + PSIZE(p.prefixlen), tlv_len); + return 1; + } + + stream_get(&p.prefix, s, PSIZE(p.prefixlen)); + + if (subtlvs->source_prefix) { + sbuf_push( + log, indent, + "WARNING: source prefix Sub-TLV present multiple times.\n"); + /* Ignore all but first occurrence of the source prefix Sub-TLV + */ + return 0; + } + + subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(p)); + memcpy(subtlvs->source_prefix, &p, sizeof(p)); + return 0; +} + +/* Functions related to Sub-Sub-TLV 1 SRv6 SID Structure + * as per RFC 9352 section #9 */ +static struct isis_srv6_sid_structure_subsubtlv * +copy_subsubtlv_srv6_sid_structure( + struct isis_srv6_sid_structure_subsubtlv *sid_struct) +{ + if (!sid_struct) + return NULL; + + struct isis_srv6_sid_structure_subsubtlv *rv = + XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*rv)); + + rv->loc_block_len = sid_struct->loc_block_len; + rv->loc_node_len = sid_struct->loc_node_len; + rv->func_len = sid_struct->func_len; + rv->arg_len = sid_struct->arg_len; + + return rv; +} + +static void format_subsubtlv_srv6_sid_structure( + struct isis_srv6_sid_structure_subsubtlv *sid_struct, struct sbuf *buf, + struct json_object *json, int indent) +{ + if (!sid_struct) + return; + + if (json) { + struct json_object *sid_struct_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + sid_struct_json = json_object_new_object(); + json_object_object_add(json, "srv6-sid-structure", + sid_struct_json); + json_object_int_add(sid_struct_json, "loc-block-len", + sid_struct->loc_block_len); + json_object_int_add(sid_struct_json, "loc-node-len", + sid_struct->loc_node_len); + json_object_int_add(sid_struct_json, "func-len", + sid_struct->func_len); + json_object_int_add(sid_struct_json, "arg-len", + sid_struct->arg_len); + /* end old deprecated key format */ + + sid_struct_json = json_object_new_object(); + json_object_object_add(json, "srv6SidStructure", + sid_struct_json); + json_object_int_add(sid_struct_json, "locBlockLen", + sid_struct->loc_block_len); + json_object_int_add(sid_struct_json, "locNodeLen", + sid_struct->loc_node_len); + json_object_int_add(sid_struct_json, "funcLen", + sid_struct->func_len); + json_object_int_add(sid_struct_json, "argLen", + sid_struct->arg_len); + } else { + sbuf_push(buf, indent, "SRv6 SID Structure "); + sbuf_push(buf, 0, "Locator Block length: %hhu, ", + sid_struct->loc_block_len); + sbuf_push(buf, 0, "Locator Node length: %hhu, ", + sid_struct->loc_node_len); + sbuf_push(buf, 0, "Function length: %hhu, ", + sid_struct->func_len); + sbuf_push(buf, 0, "Argument length: %hhu, ", + sid_struct->arg_len); + sbuf_push(buf, 0, "\n"); + } +} + +static void free_subsubtlv_srv6_sid_structure( + struct isis_srv6_sid_structure_subsubtlv *sid_struct) +{ + XFREE(MTYPE_ISIS_SUBSUBTLV, sid_struct); +} + +static int pack_subsubtlv_srv6_sid_structure( + struct isis_srv6_sid_structure_subsubtlv *sid_struct, struct stream *s) +{ + if (!sid_struct) + return 0; + + if (STREAM_WRITEABLE(s) < 6) { + return 1; + } + + stream_putc(s, ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE); + stream_putc(s, 4); + stream_putc(s, sid_struct->loc_block_len); + stream_putc(s, sid_struct->loc_node_len); + stream_putc(s, sid_struct->func_len); + stream_putc(s, sid_struct->arg_len); + + return 0; +} + +static int unpack_subsubtlv_srv6_sid_structure( + enum isis_tlv_context context, uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, void *dest, int indent) +{ + struct isis_subsubtlvs *subsubtlvs = dest; + struct isis_srv6_sid_structure_subsubtlv sid_struct = {}; + + sbuf_push(log, indent, "Unpacking SRv6 SID Structure...\n"); + if (tlv_len != 4) { + sbuf_push( + log, indent, + "Invalid SRv6 SID Structure Sub-Sub-TLV size. (Expected 4 bytes, got %hhu)\n", + tlv_len); + return 1; + } + + sid_struct.loc_block_len = stream_getc(s); + sid_struct.loc_node_len = stream_getc(s); + sid_struct.func_len = stream_getc(s); + sid_struct.arg_len = stream_getc(s); + + subsubtlvs->srv6_sid_structure = + copy_subsubtlv_srv6_sid_structure(&sid_struct); + + return 0; +} + +static struct isis_item *copy_item(enum isis_tlv_context context, + enum isis_tlv_type type, + struct isis_item *item); +static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_item_list *src, struct isis_item_list *dest); +static void format_items_(uint16_t mtid, enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_item_list *items, + struct sbuf *buf, struct json_object *json, + int indent); +#define format_items(...) format_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) +static void free_items(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_item_list *items); +static int pack_items_(uint16_t mtid, enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_item_list *items, + struct stream *s, struct isis_tlvs **fragment_tlvs, + const struct pack_order_entry *pe, + struct isis_tlvs *(*new_fragment)(struct list *l), + struct list *new_fragment_arg); +#define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) + +/* Functions related to Sub-Sub-TLVs in general */ + +struct isis_subsubtlvs *isis_alloc_subsubtlvs(enum isis_tlv_context context) +{ + struct isis_subsubtlvs *result; + + result = XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*result)); + result->context = context; + + return result; +} + +static struct isis_subsubtlvs * +isis_copy_subsubtlvs(struct isis_subsubtlvs *subsubtlvs) +{ + if (!subsubtlvs) + return NULL; + + struct isis_subsubtlvs *rv = XCALLOC(MTYPE_ISIS_SUBSUBTLV, sizeof(*rv)); + + rv->context = subsubtlvs->context; + + rv->srv6_sid_structure = copy_subsubtlv_srv6_sid_structure( + subsubtlvs->srv6_sid_structure); + + return rv; +} + +static void isis_format_subsubtlvs(struct isis_subsubtlvs *subsubtlvs, + struct sbuf *buf, struct json_object *json, + int indent) +{ + format_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure, buf, + json, indent); +} + +static void isis_free_subsubtlvs(struct isis_subsubtlvs *subsubtlvs) +{ + if (!subsubtlvs) + return; + + free_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure); + + XFREE(MTYPE_ISIS_SUBSUBTLV, subsubtlvs); +} + +static int isis_pack_subsubtlvs(struct isis_subsubtlvs *subsubtlvs, + struct stream *s) +{ + int rv; + size_t subsubtlv_len_pos = stream_get_endp(s); + + if (STREAM_WRITEABLE(s) < 1) + return 1; + + stream_putc(s, 0); /* Put 0 as Sub-Sub-TLVs length, filled in later */ + + rv = pack_subsubtlv_srv6_sid_structure(subsubtlvs->srv6_sid_structure, + s); + if (rv) + return rv; + + size_t subsubtlv_len = stream_get_endp(s) - subsubtlv_len_pos - 1; + if (subsubtlv_len > 255) + return 1; + + stream_putc_at(s, subsubtlv_len_pos, subsubtlv_len); + return 0; +} + +/* Functions related to subtlvs */ + +static struct isis_subtlvs *isis_alloc_subtlvs(enum isis_tlv_context context) +{ + struct isis_subtlvs *result; + + result = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*result)); + result->context = context; + + init_item_list(&result->prefix_sids); + init_item_list(&result->srv6_end_sids); + + return result; +} + +static struct isis_subtlvs *copy_subtlvs(struct isis_subtlvs *subtlvs) +{ + if (!subtlvs) + return NULL; + + struct isis_subtlvs *rv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); + + rv->context = subtlvs->context; + + copy_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, + &subtlvs->prefix_sids, &rv->prefix_sids); + + rv->source_prefix = + copy_subtlv_ipv6_source_prefix(subtlvs->source_prefix); + + copy_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID, + &subtlvs->srv6_end_sids, &rv->srv6_end_sids); + + return rv; +} + +static void format_subtlvs(struct isis_subtlvs *subtlvs, struct sbuf *buf, + struct json_object *json, int indent) +{ + format_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, + &subtlvs->prefix_sids, buf, json, indent); + + format_subtlv_ipv6_source_prefix(subtlvs->source_prefix, buf, json, indent); + + format_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID, + &subtlvs->srv6_end_sids, buf, json, indent); +} + +static void isis_free_subtlvs(struct isis_subtlvs *subtlvs) +{ + if (!subtlvs) + return; + + free_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, + &subtlvs->prefix_sids); + + XFREE(MTYPE_ISIS_SUBTLV, subtlvs->source_prefix); + + free_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID, + &subtlvs->srv6_end_sids); + + XFREE(MTYPE_ISIS_SUBTLV, subtlvs); +} + +static int pack_subtlvs(struct isis_subtlvs *subtlvs, struct stream *s) +{ + int rv; + size_t subtlv_len_pos = stream_get_endp(s); + + if (STREAM_WRITEABLE(s) < 1) + return 1; + + stream_putc(s, 0); /* Put 0 as subtlvs length, filled in later */ + + rv = pack_items(subtlvs->context, ISIS_SUBTLV_PREFIX_SID, + &subtlvs->prefix_sids, s, NULL, NULL, NULL, NULL); + if (rv) + return rv; + + rv = pack_subtlv_ipv6_source_prefix(subtlvs->source_prefix, s); + if (rv) + return rv; + + rv = pack_items(subtlvs->context, ISIS_SUBTLV_SRV6_END_SID, + &subtlvs->srv6_end_sids, s, NULL, NULL, NULL, NULL); + if (rv) + return rv; + + size_t subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; + if (subtlv_len > 255) + return 1; + + stream_putc_at(s, subtlv_len_pos, subtlv_len); + return 0; +} + +static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len, + struct stream *stream, struct sbuf *log, void *dest, + int indent, bool *unpacked_known_tlvs); + +/* Functions for Sub-TLV 5 SRv6 End SID as per RFC 9352 section #7.2 */ +static struct isis_item *copy_item_srv6_end_sid(struct isis_item *i) +{ + struct isis_srv6_end_sid_subtlv *sid = + (struct isis_srv6_end_sid_subtlv *)i; + struct isis_srv6_end_sid_subtlv *rv = + XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*rv)); + + rv->behavior = sid->behavior; + rv->sid = sid->sid; + rv->subsubtlvs = isis_copy_subsubtlvs(sid->subsubtlvs); + + return (struct isis_item *)rv; +} + +static void format_item_srv6_end_sid(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_srv6_end_sid_subtlv *sid = + (struct isis_srv6_end_sid_subtlv *)i; + + if (json) { + struct json_object *sid_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + sid_json = json_object_new_object(); + json_object_object_add(json, "srv6-end-sid", sid_json); + json_object_string_add(sid_json, "endpoint-behavior", + seg6local_action2str(sid->behavior)); + json_object_string_addf(sid_json, "sid-value", "%pI6", + &sid->sid); + if (sid->subsubtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(sid_json, "subsubtlvs", + subtlvs_json); + isis_format_subsubtlvs(sid->subsubtlvs, NULL, + subtlvs_json, 0); + } + /* end old deprecated key format */ + + sid_json = json_object_new_object(); + json_object_object_add(json, "srv6EndSid", sid_json); + json_object_string_add(sid_json, "endpointBehavior", + seg6local_action2str(sid->behavior)); + json_object_string_addf(sid_json, "sidValue", "%pI6", &sid->sid); + if (sid->subsubtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(sid_json, "subsubtlvs", + subtlvs_json); + isis_format_subsubtlvs(sid->subsubtlvs, NULL, + subtlvs_json, 0); + } + } else { + sbuf_push(buf, indent, "SRv6 End SID "); + sbuf_push(buf, 0, "Endpoint Behavior: %s, ", + seg6local_action2str(sid->behavior)); + sbuf_push(buf, 0, "SID value: %pI6\n", &sid->sid); + + if (sid->subsubtlvs) { + sbuf_push(buf, indent, " Sub-Sub-TLVs:\n"); + isis_format_subsubtlvs(sid->subsubtlvs, buf, NULL, + indent + 4); + } + } +} + +static void free_item_srv6_end_sid(struct isis_item *i) +{ + struct isis_srv6_end_sid_subtlv *item = + (struct isis_srv6_end_sid_subtlv *)i; + + isis_free_subsubtlvs(item->subsubtlvs); + XFREE(MTYPE_ISIS_SUBTLV, i); +} + +static int pack_item_srv6_end_sid(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_srv6_end_sid_subtlv *sid = + (struct isis_srv6_end_sid_subtlv *)i; + + if (STREAM_WRITEABLE(s) < 19) { + *min_len = 19; + return 1; + } + + stream_putc(s, sid->flags); + stream_putw(s, sid->behavior); + stream_put(s, &sid->sid, IPV6_MAX_BYTELEN); + + if (sid->subsubtlvs) { + /* Pack Sub-Sub-TLVs */ + if (isis_pack_subsubtlvs(sid->subsubtlvs, s)) + return 1; + } else { + /* No Sub-Sub-TLVs */ + if (STREAM_WRITEABLE(s) < 1) { + *min_len = 20; + return 1; + } + + /* Put 0 as Sub-Sub-TLV length, because we have no Sub-Sub-TLVs + */ + stream_putc(s, 0); + } + + return 0; +} + +static int unpack_item_srv6_end_sid(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_subtlvs *subtlvs = dest; + struct isis_srv6_end_sid_subtlv *sid = NULL; + size_t consume; + uint8_t subsubtlv_len; + + sbuf_push(log, indent, "Unpacking SRv6 End SID...\n"); + + consume = 19; + if (len < consume) { + sbuf_push( + log, indent, + "Not enough data left. (expected 19 or more bytes, got %hhu)\n", + len); + goto out; + } + + sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*sid)); + + sid->flags = stream_getc(s); + sid->behavior = stream_getw(s); + stream_get(&sid->sid, s, IPV6_MAX_BYTELEN); + + format_item_srv6_end_sid(mtid, (struct isis_item *)sid, log, NULL, + indent + 2); + + /* Process Sub-Sub-TLVs */ + consume += 1; + if (len < consume) { + sbuf_push( + log, indent, + "Expected 1 byte of Sub-Sub-TLV len, but no more data persent.\n"); + goto out; + } + subsubtlv_len = stream_getc(s); + + consume += subsubtlv_len; + if (len < consume) { + sbuf_push(log, indent, + "Expected %hhu bytes of Sub-Sub-TLVs, but only %u bytes available.\n", + subsubtlv_len, len - ((uint8_t)consume - subsubtlv_len)); + goto out; + } + + sid->subsubtlvs = + isis_alloc_subsubtlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID); + + bool unpacked_known_tlvs = false; + if (unpack_tlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID, subsubtlv_len, s, + log, sid->subsubtlvs, indent + 4, + &unpacked_known_tlvs)) { + goto out; + } + if (!unpacked_known_tlvs) { + isis_free_subsubtlvs(sid->subsubtlvs); + sid->subsubtlvs = NULL; + } + + append_item(&subtlvs->srv6_end_sids, (struct isis_item *)sid); + return 0; +out: + if (sid) + free_item_srv6_end_sid((struct isis_item *)sid); + return 1; +} + +/* Functions related to TLVs 1 Area Addresses */ + +static struct isis_item *copy_item_area_address(struct isis_item *i) +{ + struct isis_area_address *addr = (struct isis_area_address *)i; + struct isis_area_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->len = addr->len; + memcpy(rv->addr, addr->addr, addr->len); + return (struct isis_item *)rv; +} + +static void format_item_area_address(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_area_address *addr = (struct isis_area_address *)i; + struct iso_address iso_addr; + + memcpy(iso_addr.area_addr, addr->addr, ISO_ADDR_SIZE); + iso_addr.addr_len = addr->len; + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_addf(json, "area-addr", "%pIS", &iso_addr); + json_object_string_addf(json, "areaAddr", "%pIS", &iso_addr); + } else + sbuf_push(buf, indent, "Area Address: %pIS\n", &iso_addr); +} + +static void free_item_area_address(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_area_address(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_area_address *addr = (struct isis_area_address *)i; + + if (STREAM_WRITEABLE(s) < (unsigned)1 + addr->len) { + *min_len = (unsigned)1 + addr->len; + return 1; + } + stream_putc(s, addr->len); + stream_put(s, addr->addr, addr->len); + return 0; +} + +static int unpack_item_area_address(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_area_address *rv = NULL; + + sbuf_push(log, indent, "Unpack area address...\n"); + if (len < 1) { + sbuf_push( + log, indent, + "Not enough data left. (Expected 1 byte of address length, got %hhu)\n", + len); + goto out; + } + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + rv->len = stream_getc(s); + + if (len < 1 + rv->len) { + sbuf_push(log, indent, "Not enough data left. (Expected %hhu bytes of address, got %u)\n", + rv->len, len - 1); + goto out; + } + + if (rv->len < 1 || rv->len > 20) { + sbuf_push(log, indent, + "Implausible area address length %hhu\n", + rv->len); + goto out; + } + + stream_get(rv->addr, s, rv->len); + + format_item_area_address(ISIS_MT_IPV4_UNICAST, (struct isis_item *)rv, + log, NULL, indent + 2); + append_item(&tlvs->area_addresses, (struct isis_item *)rv); + return 0; +out: + XFREE(MTYPE_ISIS_TLV, rv); + return 1; +} + +/* Functions related to TLV 2 (Old-Style) IS Reach */ +static struct isis_item *copy_item_oldstyle_reach(struct isis_item *i) +{ + struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; + struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + memcpy(rv->id, r->id, 7); + rv->metric = r->metric; + return (struct isis_item *)rv; +} + +static void format_item_oldstyle_reach(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; + char sys_id[ISO_SYSID_STRLEN]; + + snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pPN", r->id); + if (json) { + struct json_object *old_json, *array_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + old_json = json_object_new_object(); + json_object_object_get_ex(json, "old-reach-style", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "old-reach-style", + array_json); + } + json_object_array_add(array_json, old_json); + json_object_string_add(old_json, "is-reach", sys_id); + json_object_int_add(old_json, "metric", r->metric); + /* end old deprecated key format */ + + old_json = json_object_new_object(); + json_object_object_get_ex(json, "oldReachStyle", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "oldReachStyle", + array_json); + } + json_object_array_add(array_json, old_json); + json_object_string_add(old_json, "isReach", sys_id); + json_object_int_add(old_json, "metric", r->metric); + } else + sbuf_push(buf, indent, "IS Reachability: %s (Metric: %hhu)\n", + sys_id, r->metric); +} + +static void free_item_oldstyle_reach(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_oldstyle_reach(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_oldstyle_reach *r = (struct isis_oldstyle_reach *)i; + + if (STREAM_WRITEABLE(s) < 11) { + *min_len = 11; + return 1; + } + + stream_putc(s, r->metric); + stream_putc(s, 0x80); /* delay metric - unsupported */ + stream_putc(s, 0x80); /* expense metric - unsupported */ + stream_putc(s, 0x80); /* error metric - unsupported */ + stream_put(s, r->id, 7); + + return 0; +} + +static int unpack_item_oldstyle_reach(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack oldstyle reach...\n"); + if (len < 11) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 11 bytes of reach information, got %hhu)\n", + len); + return 1; + } + + struct isis_oldstyle_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + rv->metric = stream_getc(s); + if ((rv->metric & 0x3f) != rv->metric) { + sbuf_push(log, indent, "Metric has unplausible format\n"); + rv->metric &= 0x3f; + } + stream_forward_getp(s, 3); /* Skip other metrics */ + stream_get(rv->id, s, 7); + + format_item_oldstyle_reach(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + append_item(&tlvs->oldstyle_reach, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLV 6 LAN Neighbors */ +static struct isis_item *copy_item_lan_neighbor(struct isis_item *i) +{ + struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; + struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + memcpy(rv->mac, n->mac, 6); + return (struct isis_item *)rv; +} + +static void format_item_lan_neighbor(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; + char sys_id[ISO_SYSID_STRLEN]; + + snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pSY", n->mac); + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "lan-neighbor", sys_id); + json_object_string_add(json, "lanNeighbor", sys_id); + } else + sbuf_push(buf, indent, "LAN Neighbor: %s\n", sys_id); +} + +static void free_item_lan_neighbor(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_lan_neighbor(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_lan_neighbor *n = (struct isis_lan_neighbor *)i; + + if (STREAM_WRITEABLE(s) < 6) { + *min_len = 6; + return 1; + } + + stream_put(s, n->mac, 6); + + return 0; +} + +static int unpack_item_lan_neighbor(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack LAN neighbor...\n"); + if (len < 6) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 6 bytes of mac, got %hhu)\n", + len); + return 1; + } + + struct isis_lan_neighbor *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + stream_get(rv->mac, s, 6); + + format_item_lan_neighbor(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + append_item(&tlvs->lan_neighbor, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLV 9 LSP Entry */ +static struct isis_item *copy_item_lsp_entry(struct isis_item *i) +{ + struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; + struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->rem_lifetime = e->rem_lifetime; + memcpy(rv->id, e->id, sizeof(rv->id)); + rv->seqno = e->seqno; + rv->checksum = e->checksum; + + return (struct isis_item *)rv; +} + +static void format_item_lsp_entry(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; + char sys_id[ISO_SYSID_STRLEN]; + + snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pLS", e->id); + if (json) { + char buf[255]; + struct json_object *lsp_json; + lsp_json = json_object_new_object(); +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_object_add(json, "lsp-entry", lsp_json); + json_object_string_add(lsp_json, "id", sys_id); + snprintfrr(buf,sizeof(buf),"0x%08x",e->seqno); + json_object_string_add(lsp_json, "seq", buf); + snprintfrr(buf,sizeof(buf),"0x%04hx",e->checksum); + json_object_string_add(lsp_json, "chksum", buf); + json_object_int_add(lsp_json, "lifetime", e->checksum); + + lsp_json = json_object_new_object(); + json_object_object_add(json, "lspEntry", lsp_json); + json_object_string_add(lsp_json, "id", sys_id); + snprintfrr(buf, sizeof(buf), "0x%08x", e->seqno); + json_object_string_add(lsp_json, "seq", buf); + snprintfrr(buf, sizeof(buf), "0x%04hx", e->checksum); + json_object_string_add(lsp_json, "chksum", buf); + json_object_int_add(lsp_json, "lifetime", e->checksum); + } else + sbuf_push( + buf, indent, + "LSP Entry: %s, seq 0x%08x, cksum 0x%04hx, lifetime %hus\n", + sys_id, e->seqno, e->checksum, e->rem_lifetime); +} + +static void free_item_lsp_entry(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_lsp_entry(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_lsp_entry *e = (struct isis_lsp_entry *)i; + + if (STREAM_WRITEABLE(s) < 16) { + *min_len = 16; + return 1; + } + + stream_putw(s, e->rem_lifetime); + stream_put(s, e->id, 8); + stream_putl(s, e->seqno); + stream_putw(s, e->checksum); + + return 0; +} + +static int unpack_item_lsp_entry(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack LSP entry...\n"); + if (len < 16) { + sbuf_push( + log, indent, + "Not enough data left. (Expected 16 bytes of LSP info, got %hhu", + len); + return 1; + } + + struct isis_lsp_entry *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + rv->rem_lifetime = stream_getw(s); + stream_get(rv->id, s, 8); + rv->seqno = stream_getl(s); + rv->checksum = stream_getw(s); + + format_item_lsp_entry(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + append_item(&tlvs->lsp_entries, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLVs 22/222 Extended Reach/MT Reach */ + +static struct isis_item *copy_item_extended_reach(struct isis_item *i) +{ + struct isis_extended_reach *r = (struct isis_extended_reach *)i; + struct isis_extended_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + memcpy(rv->id, r->id, 7); + rv->metric = r->metric; + + if (r->subtlvs) + rv->subtlvs = copy_item_ext_subtlvs(r->subtlvs, -1); + + return (struct isis_item *)rv; +} + +static void format_item_extended_reach(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_extended_reach *r = (struct isis_extended_reach *)i; + char sys_id[ISO_SYSID_STRLEN]; + + snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pPN", r->id); + if (json) { + struct json_object *reach_json, *array_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + reach_json = json_object_new_object(); + json_object_object_get_ex(json, "ext-reach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "ext-reach", array_json); + } + json_object_array_add(array_json, reach_json); + json_object_string_add( + reach_json, "mt-id", + (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT"); + json_object_string_add(reach_json, "id", sys_id); + json_object_int_add(reach_json, "metric", r->metric); + if (mtid != ISIS_MT_IPV4_UNICAST) + json_object_string_add(reach_json, "mt-name", + isis_mtid2str(mtid)); + + if (r->subtlvs) + format_item_ext_subtlvs(r->subtlvs, NULL, reach_json, + indent + 2, mtid); + /* end old deprecated key format */ + + reach_json = json_object_new_object(); + json_object_object_get_ex(json, "extReach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "extReach", array_json); + } + json_object_array_add(array_json, reach_json); + json_object_string_add(reach_json, "mtId", + (mtid == ISIS_MT_IPV4_UNICAST) + ? "Extended" + : "MT"); + json_object_string_add(reach_json, "id", sys_id); + json_object_int_add(reach_json, "metric", r->metric); + if (mtid != ISIS_MT_IPV4_UNICAST) + json_object_string_add(reach_json, "mtName", + isis_mtid2str(mtid)); + + if (r->subtlvs) + format_item_ext_subtlvs(r->subtlvs, NULL, reach_json, + indent + 2, mtid); + } else { + sbuf_push(buf, indent, "%s Reachability: %s (Metric: %u)", + (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT", + sys_id, r->metric); + if (mtid != ISIS_MT_IPV4_UNICAST) + sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); + sbuf_push(buf, 0, "\n"); + + if (r->subtlvs) + format_item_ext_subtlvs(r->subtlvs, buf, NULL, + indent + 2, mtid); + } +} + +static void free_item_extended_reach(struct isis_item *i) +{ + struct isis_extended_reach *item = (struct isis_extended_reach *)i; + + if (item->subtlvs != NULL) + free_item_ext_subtlvs(item->subtlvs); + XFREE(MTYPE_ISIS_TLV, item); +} + +static int pack_item_extended_reach(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_extended_reach *r = (struct isis_extended_reach *)i; + size_t len; + size_t len_pos; + + if (STREAM_WRITEABLE(s) < 11 + ISIS_SUBTLV_MAX_SIZE) { + *min_len = 11 + ISIS_SUBTLV_MAX_SIZE; + return 1; + } + + stream_put(s, r->id, sizeof(r->id)); + stream_put3(s, r->metric); + len_pos = stream_get_endp(s); + /* Real length will be adjust after adding subTLVs */ + stream_putc(s, 11); + if (r->subtlvs) + pack_item_ext_subtlvs(r->subtlvs, s, min_len); + /* Adjust length */ + len = stream_get_endp(s) - len_pos - 1; + stream_putc_at(s, len_pos, len); + return 0; +} + +static int unpack_item_extended_reach(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_extended_reach *rv = NULL; + uint8_t subtlv_len; + struct isis_item_list *items; + + if (mtid == ISIS_MT_IPV4_UNICAST) { + items = &tlvs->extended_reach; + } else { + items = isis_get_mt_items(&tlvs->mt_reach, mtid); + } + + sbuf_push(log, indent, "Unpacking %s reachability...\n", + (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt"); + + if (len < 11) { + sbuf_push(log, indent, + "Not enough data left. (expected 11 or more bytes, got %hhu)\n", + len); + goto out; + } + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + stream_get(rv->id, s, 7); + rv->metric = stream_get3(s); + subtlv_len = stream_getc(s); + + if ((size_t)len < ((size_t)11) + subtlv_len) { + sbuf_push(log, indent, + "Not enough data left for subtlv size %hhu, there are only %u bytes left.\n", + subtlv_len, len - 11); + goto out; + } + + sbuf_push(log, indent, "Storing %hhu bytes of subtlvs\n", + subtlv_len); + + if (subtlv_len) { + if (unpack_item_ext_subtlvs(mtid, subtlv_len, s, log, rv, + indent + 4)) { + goto out; + } + } + + format_item_extended_reach(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + append_item(items, (struct isis_item *)rv); + return 0; +out: + if (rv) + free_item_extended_reach((struct isis_item *)rv); + + return 1; +} + +/* Functions related to TLV 128 (Old-Style) IP Reach */ +static struct isis_item *copy_item_oldstyle_ip_reach(struct isis_item *i) +{ + struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; + struct isis_oldstyle_ip_reach *rv = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = r->metric; + rv->prefix = r->prefix; + return (struct isis_item *)rv; +} + +static void format_item_oldstyle_ip_reach(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; + char prefixbuf[PREFIX2STR_BUFFER]; + + if (json) { + struct json_object *old_json, *array_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + old_json = json_object_new_object(); + json_object_object_get_ex(json, "old-ip-reach-style", + &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "old-ip-reach-style", + old_json); + } + json_object_array_add(array_json, old_json); + json_object_string_add(old_json, "prefix", + prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf))); + json_object_int_add(old_json, "metric", r->metric); + /* end old deprecated key format */ + + old_json = json_object_new_object(); + json_object_object_get_ex(json, "oldIpReachStyle", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "oldIpReachStyle", + old_json); + } + json_object_array_add(array_json, old_json); + json_object_string_add(old_json, "prefix", + prefix2str(&r->prefix, prefixbuf, + sizeof(prefixbuf))); + json_object_int_add(old_json, "metric", r->metric); + return; + } + sbuf_push(buf, indent, "IP Reachability: %s (Metric: %hhu)\n", + prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), + r->metric); +} + +static void free_item_oldstyle_ip_reach(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_oldstyle_ip_reach(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_oldstyle_ip_reach *r = (struct isis_oldstyle_ip_reach *)i; + + if (STREAM_WRITEABLE(s) < 12) { + *min_len = 12; + return 1; + } + + stream_putc(s, r->metric); + stream_putc(s, 0x80); /* delay metric - unsupported */ + stream_putc(s, 0x80); /* expense metric - unsupported */ + stream_putc(s, 0x80); /* error metric - unsupported */ + stream_put(s, &r->prefix.prefix, 4); + + struct in_addr mask; + masklen2ip(r->prefix.prefixlen, &mask); + stream_put(s, &mask, sizeof(mask)); + + return 0; +} + +static int unpack_item_oldstyle_ip_reach(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + sbuf_push(log, indent, "Unpack oldstyle ip reach...\n"); + if (len < 12) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 12 bytes of reach information, got %hhu)\n", + len); + return 1; + } + + struct isis_oldstyle_ip_reach *rv = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + rv->metric = stream_getc(s); + if ((rv->metric & 0x7f) != rv->metric) { + sbuf_push(log, indent, "Metric has unplausible format\n"); + rv->metric &= 0x7f; + } + stream_forward_getp(s, 3); /* Skip other metrics */ + rv->prefix.family = AF_INET; + stream_get(&rv->prefix.prefix, s, 4); + + struct in_addr mask; + stream_get(&mask, s, 4); + rv->prefix.prefixlen = ip_masklen(mask); + + format_item_oldstyle_ip_reach(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + append_item(dest, (struct isis_item *)rv); + return 0; +} + + +/* Functions related to TLV 129 protocols supported */ + +static void copy_tlv_protocols_supported(struct isis_protocols_supported *src, + struct isis_protocols_supported *dest) +{ + if (!src->protocols || !src->count) + return; + dest->count = src->count; + dest->protocols = XCALLOC(MTYPE_ISIS_TLV, src->count); + memcpy(dest->protocols, src->protocols, src->count); +} + +static void format_tlv_protocols_supported(struct isis_protocols_supported *p, + struct sbuf *buf, + struct json_object *json, int indent) +{ + if (!p || !p->count || !p->protocols) + return; + + if (json) { + struct json_object *protocol_json; + char buf[255]; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + protocol_json = json_object_new_object(); + json_object_object_add(json, "protocols-supported", + protocol_json); + for (uint8_t i = 0; i < p->count; i++) { + snprintfrr(buf, sizeof(buf), "%d", i); + json_object_string_add(protocol_json, buf, + nlpid2str(p->protocols[i])); + } + + protocol_json = json_object_new_object(); + json_object_object_add(json, "supportedProtocols", + protocol_json); + for (uint8_t i = 0; i < p->count; i++) { + snprintfrr(buf, sizeof(buf), "%d", i); + json_object_string_add(protocol_json, buf, + nlpid2str(p->protocols[i])); + } + /* end old deprecated key format */ + } else { + sbuf_push(buf, indent, "Protocols Supported: "); + for (uint8_t i = 0; i < p->count; i++) { + sbuf_push(buf, 0, "%s%s", nlpid2str(p->protocols[i]), + (i + 1 < p->count) ? ", " : ""); + } + sbuf_push(buf, 0, "\n"); + } +} + +static void free_tlv_protocols_supported(struct isis_protocols_supported *p) +{ + XFREE(MTYPE_ISIS_TLV, p->protocols); +} + +static int pack_tlv_protocols_supported(struct isis_protocols_supported *p, + struct stream *s) +{ + if (!p || !p->count || !p->protocols) + return 0; + + if (STREAM_WRITEABLE(s) < (unsigned)(p->count + 2)) + return 1; + + stream_putc(s, ISIS_TLV_PROTOCOLS_SUPPORTED); + stream_putc(s, p->count); + stream_put(s, p->protocols, p->count); + return 0; +} + +static int unpack_tlv_protocols_supported(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking Protocols Supported TLV...\n"); + if (!tlv_len) { + sbuf_push(log, indent, "WARNING: No protocols included\n"); + return 0; + } + if (tlvs->protocols_supported.protocols) { + sbuf_push( + log, indent, + "WARNING: protocols supported TLV present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->protocols_supported.count = tlv_len; + tlvs->protocols_supported.protocols = XCALLOC(MTYPE_ISIS_TLV, tlv_len); + stream_get(tlvs->protocols_supported.protocols, s, tlv_len); + + format_tlv_protocols_supported(&tlvs->protocols_supported, log, NULL, + indent + 2); + return 0; +} + +/* Functions related to TLV 132 IPv4 Interface addresses */ +static struct isis_item *copy_item_ipv4_address(struct isis_item *i) +{ + struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; + struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->addr = a->addr; + return (struct isis_item *)rv; +} + +static void format_item_ipv4_address(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; + char addrbuf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &a->addr, addrbuf, sizeof(addrbuf)); + if (json) { + json_object_string_add(json, "ipv4", addrbuf); + } else { + sbuf_push(buf, indent, "IPv4 Interface Address: %s\n", addrbuf); + } +} + +static void free_item_ipv4_address(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_ipv4_address(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_ipv4_address *a = (struct isis_ipv4_address *)i; + + if (STREAM_WRITEABLE(s) < 4) { + *min_len = 4; + return 1; + } + + stream_put(s, &a->addr, 4); + + return 0; +} + +static int unpack_item_ipv4_address(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack IPv4 Interface address...\n"); + if (len < 4) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 4 bytes of IPv4 address, got %hhu)\n", + len); + return 1; + } + + struct isis_ipv4_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + stream_get(&rv->addr, s, 4); + + format_item_ipv4_address(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + append_item(&tlvs->ipv4_address, (struct isis_item *)rv); + return 0; +} + + +/* Functions related to TLV 232 IPv6 Interface addresses */ +static struct isis_item *copy_item_ipv6_address(struct isis_item *i) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->addr = a->addr; + return (struct isis_item *)rv; +} + +static void format_item_ipv6_address(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + char addrbuf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &a->addr, addrbuf, sizeof(addrbuf)); + if (json) + json_object_string_add(json, "ipv6", addrbuf); + else + sbuf_push(buf, indent, "IPv6 Interface Address: %s\n", addrbuf); +} + +static void free_item_ipv6_address(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_ipv6_address(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + + if (STREAM_WRITEABLE(s) < IPV6_MAX_BYTELEN) { + *min_len = IPV6_MAX_BYTELEN; + return 1; + } + + stream_put(s, &a->addr, IPV6_MAX_BYTELEN); + + return 0; +} + +static int unpack_item_ipv6_address(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack IPv6 Interface address...\n"); + if (len < 16) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 16 bytes of IPv6 address, got %hhu)\n", + len); + return 1; + } + + struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + stream_get(&rv->addr, s, IPV6_MAX_BYTELEN); + + format_item_ipv6_address(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + append_item(&tlvs->ipv6_address, (struct isis_item *)rv); + return 0; +} + + +/* Functions related to TLV 233 Global IPv6 Interface addresses */ +static struct isis_item *copy_item_global_ipv6_address(struct isis_item *i) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->addr = a->addr; + return (struct isis_item *)rv; +} + +static void format_item_global_ipv6_address(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, + int indent) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + char addrbuf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &a->addr, addrbuf, sizeof(addrbuf)); + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "global-ipv6", addrbuf); + json_object_string_add(json, "globalIpv6", addrbuf); + } else + sbuf_push(buf, indent, "Global IPv6 Interface Address: %s\n", + addrbuf); +} + +static void free_item_global_ipv6_address(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_global_ipv6_address(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_ipv6_address *a = (struct isis_ipv6_address *)i; + + if (STREAM_WRITEABLE(s) < IPV6_MAX_BYTELEN) { + *min_len = IPV6_MAX_BYTELEN; + return 1; + } + + stream_put(s, &a->addr, IPV6_MAX_BYTELEN); + + return 0; +} + +static int unpack_item_global_ipv6_address(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack Global IPv6 Interface address...\n"); + if (len < IPV6_MAX_BYTELEN) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 16 bytes of IPv6 address, got %hhu)\n", + len); + return 1; + } + + struct isis_ipv6_address *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + stream_get(&rv->addr, s, IPV6_MAX_BYTELEN); + + format_item_global_ipv6_address(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + append_item(&tlvs->global_ipv6_address, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLV 229 MT Router information */ +static struct isis_item *copy_item_mt_router_info(struct isis_item *i) +{ + struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; + struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->overload = info->overload; + rv->attached = info->attached; + rv->mtid = info->mtid; + return (struct isis_item *)rv; +} + +static void format_item_mt_router_info(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; + + if (json) { + struct json_object *mt_json, *array_json; + mt_json = json_object_new_object(); + json_object_object_get_ex(json, "mt", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "mt", array_json); + } + json_object_array_add(array_json, mt_json); + json_object_int_add(mt_json, "mtid", info->mtid); + json_object_string_add(mt_json, "mt-description", + isis_mtid2str_fake(info->mtid)); + json_object_string_add(mt_json, "mtDescription", + isis_mtid2str(mtid)); + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated non boolean format") +#endif + json_object_string_add(mt_json, "overload", info->overload?"true":"false"); + json_object_string_add(mt_json, "attached", info->attached?"true":"false"); + + json_object_boolean_add(mt_json, "overloadBit", + !!info->overload); + json_object_boolean_add(mt_json, "attachedbit", + !!info->attached); + } else + sbuf_push(buf, indent, "MT Router Info: %s%s%s\n", + isis_mtid2str_fake(info->mtid), + info->overload ? " Overload" : "", + info->attached ? " Attached" : ""); +} + +static void free_item_mt_router_info(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_mt_router_info(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_mt_router_info *info = (struct isis_mt_router_info *)i; + + if (STREAM_WRITEABLE(s) < 2) { + *min_len = 2; + return 1; + } + + uint16_t entry = info->mtid; + + if (info->overload) + entry |= ISIS_MT_OL_MASK; + if (info->attached) + entry |= ISIS_MT_AT_MASK; + + stream_putw(s, entry); + + return 0; +} + +static int unpack_item_mt_router_info(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack MT Router info...\n"); + if (len < 2) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 2 bytes of MT info, got %hhu)\n", + len); + return 1; + } + + struct isis_mt_router_info *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + uint16_t entry = stream_getw(s); + rv->overload = entry & ISIS_MT_OL_MASK; + rv->attached = entry & ISIS_MT_AT_MASK; + rv->mtid = entry & ISIS_MT_MASK; + + format_item_mt_router_info(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + append_item(&tlvs->mt_router_info, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLV 134 TE Router ID */ + +static struct in_addr *copy_tlv_te_router_id(const struct in_addr *id) +{ + if (!id) + return NULL; + + struct in_addr *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + memcpy(rv, id, sizeof(*rv)); + return rv; +} + +static void format_tlv_te_router_id(const struct in_addr *id, struct sbuf *buf, + struct json_object *json, int indent) +{ + if (!id) + return; + + char addrbuf[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, id, addrbuf, sizeof(addrbuf)); + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "te-router-id", addrbuf); + json_object_string_add(json, "teRouterId", addrbuf); + } else + sbuf_push(buf, indent, "TE Router ID: %s\n", addrbuf); +} + +static void free_tlv_te_router_id(struct in_addr *id) +{ + XFREE(MTYPE_ISIS_TLV, id); +} + +static int pack_tlv_te_router_id(const struct in_addr *id, struct stream *s) +{ + if (!id) + return 0; + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + sizeof(*id))) + return 1; + + stream_putc(s, ISIS_TLV_TE_ROUTER_ID); + stream_putc(s, 4); + stream_put(s, id, 4); + return 0; +} + +static int unpack_tlv_te_router_id(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking TE Router ID TLV...\n"); + if (tlv_len != 4) { + sbuf_push(log, indent, "WARNING: Length invalid\n"); + return 1; + } + + if (tlvs->te_router_id) { + sbuf_push(log, indent, + "WARNING: TE Router ID present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, 4); + stream_get(tlvs->te_router_id, s, 4); + format_tlv_te_router_id(tlvs->te_router_id, log, NULL, indent + 2); + return 0; +} + + +/* Functions related to TLVs 135/235 extended IP reach/MT IP Reach */ + +static struct isis_item *copy_item_extended_ip_reach(struct isis_item *i) +{ + struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; + struct isis_extended_ip_reach *rv = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = r->metric; + rv->down = r->down; + rv->prefix = r->prefix; + rv->subtlvs = copy_subtlvs(r->subtlvs); + + return (struct isis_item *)rv; +} + +static void format_item_extended_ip_reach(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; + struct json_object *ext_json, *array_json; + char prefixbuf[PREFIX2STR_BUFFER]; + + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + ext_json = json_object_new_object(); + json_object_object_get_ex(json, "ext-ip-reach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "ext-ip-reach", array_json); + } + json_object_array_add(array_json, ext_json); + json_object_string_add(ext_json, "mt-id", + (mtid == ISIS_MT_IPV4_UNICAST) + ? "Extended" + : "MT"); + json_object_string_add(ext_json, "ip-reach", + prefix2str(&r->prefix, prefixbuf, + sizeof(prefixbuf))); + json_object_int_add(ext_json, "ip-reach-metric", r->metric); + json_object_string_add(ext_json, "down", r->down ? "yes" : ""); + if (mtid != ISIS_MT_IPV4_UNICAST) + json_object_string_add(ext_json, "mt-name", + isis_mtid2str(mtid)); + if (r->subtlvs) { + struct json_object *subtlv_json; + subtlv_json = json_object_new_object(); + json_object_object_add(ext_json, "subtlvs", subtlv_json); + format_subtlvs(r->subtlvs, NULL, subtlv_json, 0); + } + /* end old deprecated key format */ + + ext_json = json_object_new_object(); + json_object_object_get_ex(json, "extIpReach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "extIpReach", array_json); + } + json_object_array_add(array_json, ext_json); + json_object_string_add(ext_json, "mtId", + (mtid == ISIS_MT_IPV4_UNICAST) + ? "Extended" + : "MT"); + json_object_string_add(ext_json, "ipReach", + prefix2str(&r->prefix, prefixbuf, + sizeof(prefixbuf))); + json_object_int_add(ext_json, "ipReachMetric", r->metric); + json_object_boolean_add(ext_json, "down", !!r->down); + if (mtid != ISIS_MT_IPV4_UNICAST) + json_object_string_add(ext_json, "mtName", + isis_mtid2str(mtid)); + if (r->subtlvs) { + struct json_object *subtlv_json; + subtlv_json = json_object_new_object(); + json_object_object_add(ext_json, "subtlvs", subtlv_json); + format_subtlvs(r->subtlvs, NULL, subtlv_json, 0); + } + } else { + sbuf_push(buf, indent, "%s IP Reachability: %s (Metric: %u)%s", + (mtid == ISIS_MT_IPV4_UNICAST) ? "Extended" : "MT", + prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), + r->metric, r->down ? " Down" : ""); + if (mtid != ISIS_MT_IPV4_UNICAST) + sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); + sbuf_push(buf, 0, "\n"); + + if (r->subtlvs) { + sbuf_push(buf, indent, " Subtlvs:\n"); + format_subtlvs(r->subtlvs, buf, NULL, indent + 4); + } + } +} + +static void free_item_extended_ip_reach(struct isis_item *i) +{ + struct isis_extended_ip_reach *item = + (struct isis_extended_ip_reach *)i; + isis_free_subtlvs(item->subtlvs); + XFREE(MTYPE_ISIS_TLV, item); +} + +static int pack_item_extended_ip_reach(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_extended_ip_reach *r = (struct isis_extended_ip_reach *)i; + uint8_t control; + + if (STREAM_WRITEABLE(s) < 5) { + *min_len = 5; + return 1; + } + stream_putl(s, r->metric); + + control = r->down ? ISIS_EXTENDED_IP_REACH_DOWN : 0; + control |= r->prefix.prefixlen; + control |= r->subtlvs ? ISIS_EXTENDED_IP_REACH_SUBTLV : 0; + + stream_putc(s, control); + + if (STREAM_WRITEABLE(s) < (unsigned)PSIZE(r->prefix.prefixlen)) { + *min_len = 5 + (unsigned)PSIZE(r->prefix.prefixlen); + return 1; + } + stream_put(s, &r->prefix.prefix.s_addr, PSIZE(r->prefix.prefixlen)); + + if (r->subtlvs) + return pack_subtlvs(r->subtlvs, s); + return 0; +} + +static int unpack_item_extended_ip_reach(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_extended_ip_reach *rv = NULL; + size_t consume; + uint8_t control, subtlv_len; + struct isis_item_list *items; + + if (mtid == ISIS_MT_IPV4_UNICAST) { + items = &tlvs->extended_ip_reach; + } else { + items = isis_get_mt_items(&tlvs->mt_ip_reach, mtid); + } + + sbuf_push(log, indent, "Unpacking %s IPv4 reachability...\n", + (mtid == ISIS_MT_IPV4_UNICAST) ? "extended" : "mt"); + + consume = 5; + if (len < consume) { + sbuf_push(log, indent, + "Not enough data left. (expected 5 or more bytes, got %hhu)\n", + len); + goto out; + } + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = stream_getl(s); + control = stream_getc(s); + rv->down = (control & ISIS_EXTENDED_IP_REACH_DOWN); + rv->prefix.family = AF_INET; + rv->prefix.prefixlen = control & 0x3f; + if (rv->prefix.prefixlen > IPV4_MAX_BITLEN) { + sbuf_push(log, indent, "Prefixlen %u is implausible for IPv4\n", + rv->prefix.prefixlen); + goto out; + } + + consume += PSIZE(rv->prefix.prefixlen); + if (len < consume) { + sbuf_push(log, indent, + "Expected %u bytes of prefix, but only %u bytes available.\n", + PSIZE(rv->prefix.prefixlen), len - 5); + goto out; + } + stream_get(&rv->prefix.prefix.s_addr, s, PSIZE(rv->prefix.prefixlen)); + in_addr_t orig_prefix = rv->prefix.prefix.s_addr; + apply_mask_ipv4(&rv->prefix); + if (orig_prefix != rv->prefix.prefix.s_addr) + sbuf_push(log, indent + 2, + "WARNING: Prefix had hostbits set.\n"); + format_item_extended_ip_reach(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + + if (control & ISIS_EXTENDED_IP_REACH_SUBTLV) { + consume += 1; + if (len < consume) { + sbuf_push(log, indent, + "Expected 1 byte of subtlv len, but no more data present.\n"); + goto out; + } + subtlv_len = stream_getc(s); + + if (!subtlv_len) { + sbuf_push(log, indent + 2, + " WARNING: subtlv bit is set, but there are no subtlvs.\n"); + } + consume += subtlv_len; + if (len < consume) { + sbuf_push(log, indent, + "Expected %hhu bytes of subtlvs, but only %u bytes available.\n", + subtlv_len, + len - 6 - PSIZE(rv->prefix.prefixlen)); + goto out; + } + + rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IP_REACH); + bool unpacked_known_tlvs = false; + + if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IP_REACH, subtlv_len, s, + log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) { + goto out; + } + if (!unpacked_known_tlvs) { + isis_free_subtlvs(rv->subtlvs); + rv->subtlvs = NULL; + } + } + + append_item(items, (struct isis_item *)rv); + return 0; +out: + if (rv) + free_item_extended_ip_reach((struct isis_item *)rv); + return 1; +} + +/* Functions related to TLV 137 Dynamic Hostname */ + +static char *copy_tlv_dynamic_hostname(const char *hostname) +{ + if (!hostname) + return NULL; + + return XSTRDUP(MTYPE_ISIS_TLV, hostname); +} + +static void format_tlv_dynamic_hostname(const char *hostname, struct sbuf *buf, + struct json_object *json, int indent) +{ + if (!hostname) + return; + + if (json) + json_object_string_add(json, "hostname", hostname); + else + sbuf_push(buf, indent, "Hostname: %s\n", hostname); +} + +static void free_tlv_dynamic_hostname(char *hostname) +{ + XFREE(MTYPE_ISIS_TLV, hostname); +} + +static int pack_tlv_dynamic_hostname(const char *hostname, struct stream *s) +{ + if (!hostname) + return 0; + + uint8_t name_len = strlen(hostname); + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + name_len)) + return 1; + + stream_putc(s, ISIS_TLV_DYNAMIC_HOSTNAME); + stream_putc(s, name_len); + stream_put(s, hostname, name_len); + return 0; +} + +static int unpack_tlv_dynamic_hostname(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking Dynamic Hostname TLV...\n"); + if (!tlv_len) { + sbuf_push(log, indent, "WARNING: No hostname included\n"); + return 0; + } + + if (tlvs->hostname) { + sbuf_push(log, indent, + "WARNING: Hostname present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->hostname = XCALLOC(MTYPE_ISIS_TLV, tlv_len + 1); + stream_get(tlvs->hostname, s, tlv_len); + tlvs->hostname[tlv_len] = '\0'; + + bool sane = true; + for (uint8_t i = 0; i < tlv_len; i++) { + if ((unsigned char)tlvs->hostname[i] > 127 + || !isprint((unsigned char)tlvs->hostname[i])) { + sane = false; + tlvs->hostname[i] = '?'; + } + } + if (!sane) { + sbuf_push( + log, indent, + "WARNING: Hostname contained non-printable/non-ascii characters.\n"); + } + + return 0; +} + +/* Functions related to TLV 140 IPv6 TE Router ID */ + +static struct in6_addr *copy_tlv_te_router_id_ipv6(const struct in6_addr *id) +{ + if (!id) + return NULL; + + struct in6_addr *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + memcpy(rv, id, sizeof(*rv)); + return rv; +} + +static void format_tlv_te_router_id_ipv6(const struct in6_addr *id, + struct sbuf *buf, + struct json_object *json, int indent) +{ + if (!id) + return; + + char addrbuf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, id, addrbuf, sizeof(addrbuf)); + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "ipv6-te-router-id", addrbuf); + json_object_string_add(json, "ipv6TeRouterId", addrbuf); + } else + sbuf_push(buf, indent, "IPv6 TE Router ID: %s\n", addrbuf); +} + +static void free_tlv_te_router_id_ipv6(struct in6_addr *id) +{ + XFREE(MTYPE_ISIS_TLV, id); +} + +static int pack_tlv_te_router_id_ipv6(const struct in6_addr *id, + struct stream *s) +{ + if (!id) + return 0; + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + sizeof(*id))) + return 1; + + stream_putc(s, ISIS_TLV_TE_ROUTER_ID_IPV6); + stream_putc(s, IPV6_MAX_BYTELEN); + stream_put(s, id, IPV6_MAX_BYTELEN); + return 0; +} + +static int unpack_tlv_te_router_id_ipv6(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking IPv6 TE Router ID TLV...\n"); + if (tlv_len != IPV6_MAX_BYTELEN) { + sbuf_push(log, indent, "WARNING: Length invalid\n"); + return 1; + } + + if (tlvs->te_router_id_ipv6) { + sbuf_push( + log, indent, + "WARNING: IPv6 TE Router ID present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->te_router_id_ipv6 = XCALLOC(MTYPE_ISIS_TLV, IPV6_MAX_BYTELEN); + stream_get(tlvs->te_router_id_ipv6, s, IPV6_MAX_BYTELEN); + format_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, log, NULL, indent + 2); + return 0; +} + + +/* Functions related to TLV 150 Spine-Leaf-Extension */ + +static struct isis_spine_leaf *copy_tlv_spine_leaf( + const struct isis_spine_leaf *spine_leaf) +{ + if (!spine_leaf) + return NULL; + + struct isis_spine_leaf *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + memcpy(rv, spine_leaf, sizeof(*rv)); + + return rv; +} + +static void format_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf, + struct sbuf *buf, struct json_object *json, + int indent) +{ + if (!spine_leaf) + return; + + char aux_buf[255]; + + if (json) { + struct json_object *spine_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated format */ + spine_json = json_object_new_object(); + json_object_object_add(json, "spine-leaf-extension", + spine_json); + if (spine_leaf->has_tier) { + snprintfrr(aux_buf, sizeof(aux_buf), "%hhu", + spine_leaf->tier); + json_object_string_add( + spine_json, "tier", + (spine_leaf->tier == ISIS_TIER_UNDEFINED) + ? "undefined" + : aux_buf); + } + json_object_string_add(spine_json, "flag-leaf", + spine_leaf->is_leaf ? "yes" : ""); + json_object_string_add(spine_json, "flag-spine", + spine_leaf->is_spine ? "yes" : ""); + json_object_string_add(spine_json, "flag-backup", + spine_leaf->is_backup ? "yes" : ""); + /* end old deprecated format */ + + spine_json = json_object_new_object(); + json_object_object_add(json, "spineLeafExtension", spine_json); + if (spine_leaf->has_tier) { + snprintfrr(aux_buf, sizeof(aux_buf), "%hhu", + spine_leaf->tier); + json_object_string_add(spine_json, "tier", + (spine_leaf->tier == + ISIS_TIER_UNDEFINED) + ? "undefined" + : aux_buf); + } + json_object_boolean_add(spine_json, "flagLeaf", + spine_leaf->is_leaf ? true : false); + json_object_boolean_add(spine_json, "flagSpine", + spine_leaf->is_spine ? true : false); + json_object_boolean_add(spine_json, "flagBackup", + spine_leaf->is_backup ? true : false); + } else { + sbuf_push(buf, indent, "Spine-Leaf-Extension:\n"); + if (spine_leaf->has_tier) { + if (spine_leaf->tier == ISIS_TIER_UNDEFINED) { + sbuf_push(buf, indent, " Tier: undefined\n"); + } else { + sbuf_push(buf, indent, " Tier: %hhu\n", + spine_leaf->tier); + } + } + + sbuf_push(buf, indent, " Flags:%s%s%s\n", + spine_leaf->is_leaf ? " LEAF" : "", + spine_leaf->is_spine ? " SPINE" : "", + spine_leaf->is_backup ? " BACKUP" : ""); + } +} + +static void free_tlv_spine_leaf(struct isis_spine_leaf *spine_leaf) +{ + XFREE(MTYPE_ISIS_TLV, spine_leaf); +} + +#define ISIS_SPINE_LEAF_FLAG_TIER 0x08 +#define ISIS_SPINE_LEAF_FLAG_BACKUP 0x04 +#define ISIS_SPINE_LEAF_FLAG_SPINE 0x02 +#define ISIS_SPINE_LEAF_FLAG_LEAF 0x01 + +static int pack_tlv_spine_leaf(const struct isis_spine_leaf *spine_leaf, + struct stream *s) +{ + if (!spine_leaf) + return 0; + + uint8_t tlv_len = 2; + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len)) + return 1; + + stream_putc(s, ISIS_TLV_SPINE_LEAF_EXT); + stream_putc(s, tlv_len); + + uint16_t spine_leaf_flags = 0; + + if (spine_leaf->has_tier) { + spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_TIER; + spine_leaf_flags |= spine_leaf->tier << 12; + } + + if (spine_leaf->is_leaf) + spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_LEAF; + + if (spine_leaf->is_spine) + spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_SPINE; + + if (spine_leaf->is_backup) + spine_leaf_flags |= ISIS_SPINE_LEAF_FLAG_BACKUP; + + stream_putw(s, spine_leaf_flags); + + return 0; +} + +static int unpack_tlv_spine_leaf(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking Spine Leaf Extension TLV...\n"); + if (tlv_len < 2) { + sbuf_push(log, indent, "WARNING: Unexpected TLV size\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + if (tlvs->spine_leaf) { + sbuf_push(log, indent, + "WARNING: Spine Leaf Extension TLV present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf)); + + uint16_t spine_leaf_flags = stream_getw(s); + + if (spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_TIER) { + tlvs->spine_leaf->has_tier = true; + tlvs->spine_leaf->tier = spine_leaf_flags >> 12; + } + + tlvs->spine_leaf->is_leaf = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_LEAF; + tlvs->spine_leaf->is_spine = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_SPINE; + tlvs->spine_leaf->is_backup = spine_leaf_flags & ISIS_SPINE_LEAF_FLAG_BACKUP; + + stream_forward_getp(s, tlv_len - 2); + return 0; +} + +/* Functions related to TLV 240 P2P Three-Way Adjacency */ + +const char *isis_threeway_state_name(enum isis_threeway_state state) +{ + switch (state) { + case ISIS_THREEWAY_DOWN: + return "Down"; + case ISIS_THREEWAY_INITIALIZING: + return "Initializing"; + case ISIS_THREEWAY_UP: + return "Up"; + default: + return "Invalid!"; + } +} + +static struct isis_threeway_adj *copy_tlv_threeway_adj( + const struct isis_threeway_adj *threeway_adj) +{ + if (!threeway_adj) + return NULL; + + struct isis_threeway_adj *rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + memcpy(rv, threeway_adj, sizeof(*rv)); + + return rv; +} + +static void +format_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj, + struct sbuf *buf, struct json_object *json, int indent) +{ + char sys_id[ISO_SYSID_STRLEN]; + + if (!threeway_adj) + return; + + snprintfrr(sys_id, ISO_SYSID_STRLEN, "%pSY", threeway_adj->neighbor_id); + if (json) { + struct json_object *three_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + three_json = json_object_new_object(); + json_object_object_add(json, "p2p-three-way-adj", three_json); + json_object_string_add( + three_json, "state-name", + isis_threeway_state_name(threeway_adj->state)); + json_object_int_add(three_json, "state", threeway_adj->state); + json_object_int_add(three_json, "ext-local-circuit-id", + threeway_adj->local_circuit_id); + if (threeway_adj->neighbor_set) { + json_object_string_add(three_json, "neigh-system-id", + sys_id); + json_object_int_add(three_json, "neigh-ext-circuit-id", + threeway_adj->neighbor_circuit_id); + } + /* end old deprecated key format */ + + three_json = json_object_new_object(); + json_object_object_add(json, "p2pThreeWayAdj", three_json); + json_object_string_add(three_json, "stateName", + isis_threeway_state_name( + threeway_adj->state)); + json_object_int_add(three_json, "state", threeway_adj->state); + json_object_int_add(three_json, "extLocalCircuitId", + threeway_adj->local_circuit_id); + if (threeway_adj->neighbor_set) { + json_object_string_add(three_json, "neighSystemId", + sys_id); + json_object_int_add(three_json, "neighExtCircuitId", + threeway_adj->neighbor_circuit_id); + } + } else { + sbuf_push(buf, indent, "P2P Three-Way Adjacency:\n"); + sbuf_push(buf, indent, " State: %s (%d)\n", + isis_threeway_state_name(threeway_adj->state), + threeway_adj->state); + sbuf_push(buf, indent, " Extended Local Circuit ID: %u\n", + threeway_adj->local_circuit_id); + if (!threeway_adj->neighbor_set) + return; + + sbuf_push(buf, indent, " Neighbor System ID: %s\n", sys_id); + sbuf_push(buf, indent, " Neighbor Extended Circuit ID: %u\n", + threeway_adj->neighbor_circuit_id); + } +} + +static void free_tlv_threeway_adj(struct isis_threeway_adj *threeway_adj) +{ + XFREE(MTYPE_ISIS_TLV, threeway_adj); +} + +static int pack_tlv_threeway_adj(const struct isis_threeway_adj *threeway_adj, + struct stream *s) +{ + if (!threeway_adj) + return 0; + + uint8_t tlv_len = (threeway_adj->neighbor_set) ? 15 : 5; + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + tlv_len)) + return 1; + + stream_putc(s, ISIS_TLV_THREE_WAY_ADJ); + stream_putc(s, tlv_len); + stream_putc(s, threeway_adj->state); + stream_putl(s, threeway_adj->local_circuit_id); + + if (threeway_adj->neighbor_set) { + stream_put(s, threeway_adj->neighbor_id, 6); + stream_putl(s, threeway_adj->neighbor_circuit_id); + } + + return 0; +} + +static int unpack_tlv_threeway_adj(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpacking P2P Three-Way Adjacency TLV...\n"); + if (tlv_len != 5 && tlv_len != 15) { + sbuf_push(log, indent, "WARNING: Unexpected TLV size\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + if (tlvs->threeway_adj) { + sbuf_push(log, indent, + "WARNING: P2P Three-Way Adjacency TLV present multiple times.\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj)); + + tlvs->threeway_adj->state = stream_getc(s); + tlvs->threeway_adj->local_circuit_id = stream_getl(s); + + if (tlv_len == 15) { + tlvs->threeway_adj->neighbor_set = true; + stream_get(tlvs->threeway_adj->neighbor_id, s, 6); + tlvs->threeway_adj->neighbor_circuit_id = stream_getl(s); + } + + return 0; +} + +/* Functions related to TLVs 236/237 IPv6/MT-IPv6 reach */ +static struct isis_item *copy_item_ipv6_reach(struct isis_item *i) +{ + struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; + struct isis_ipv6_reach *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = r->metric; + rv->down = r->down; + rv->external = r->external; + rv->prefix = r->prefix; + rv->subtlvs = copy_subtlvs(r->subtlvs); + + return (struct isis_item *)rv; +} + +static void format_item_ipv6_reach(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; + char prefixbuf[PREFIX2STR_BUFFER]; + + if (json) { + struct json_object *reach_json, *array_json; + + reach_json = json_object_new_object(); + json_object_object_get_ex(json, "ipv6Reach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "ipv6Reach", array_json); + } + json_object_array_add(array_json, reach_json); + json_object_string_add(reach_json, "mtId", + (mtid == ISIS_MT_IPV4_UNICAST) ? "" + : "mt"); + json_object_string_add(reach_json, "prefix", + prefix2str(&r->prefix, prefixbuf, + sizeof(prefixbuf))); + json_object_int_add(reach_json, "metric", r->metric); + json_object_boolean_add(reach_json, "down", + r->down ? true : false); + json_object_boolean_add(reach_json, "external", + r->external ? true : false); + if (mtid != ISIS_MT_IPV4_UNICAST) { + json_object_string_add(reach_json, "mt-name", + isis_mtid2str(mtid)); + json_object_string_add(reach_json, "mtName", + isis_mtid2str(mtid)); + } + if (r->subtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(reach_json, "subtlvs", + subtlvs_json); + format_subtlvs(r->subtlvs, NULL, subtlvs_json, 0); + } + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated JSON key format */ + reach_json = json_object_new_object(); + json_object_object_get_ex(json, "ipv6-reach", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "ipv6-reach", array_json); + } + json_object_array_add(array_json, reach_json); + json_object_string_add(reach_json, "mt-id", + (mtid == ISIS_MT_IPV4_UNICAST) ? "" + : "mt"); + json_object_string_add( + reach_json, "prefix", + prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf))); + json_object_int_add(reach_json, "metric", r->metric); + json_object_string_add(reach_json, "down", + r->down ? "yes" : ""); + json_object_string_add(reach_json, "external", + r->external ? "yes" : ""); + if (mtid != ISIS_MT_IPV4_UNICAST) + json_object_string_add(reach_json, "mt-name", + isis_mtid2str(mtid)); + if (r->subtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(reach_json, "subtlvs", + subtlvs_json); + format_subtlvs(r->subtlvs, NULL, subtlvs_json, 0); + } + /* end deprecated key format */ + } else { + sbuf_push(buf, indent, + "%sIPv6 Reachability: %s (Metric: %u)%s%s", + (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "MT ", + prefix2str(&r->prefix, prefixbuf, sizeof(prefixbuf)), + r->metric, r->down ? " Down" : "", + r->external ? " External" : ""); + if (mtid != ISIS_MT_IPV4_UNICAST) + sbuf_push(buf, 0, " %s", isis_mtid2str(mtid)); + sbuf_push(buf, 0, "\n"); + + if (r->subtlvs) { + sbuf_push(buf, indent, " Subtlvs:\n"); + format_subtlvs(r->subtlvs, buf, NULL, indent + 4); + } + } +} + +static void free_item_ipv6_reach(struct isis_item *i) +{ + struct isis_ipv6_reach *item = (struct isis_ipv6_reach *)i; + + isis_free_subtlvs(item->subtlvs); + XFREE(MTYPE_ISIS_TLV, item); +} + +static int pack_item_ipv6_reach(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_ipv6_reach *r = (struct isis_ipv6_reach *)i; + uint8_t control; + + if (STREAM_WRITEABLE(s) < 6 + (unsigned)PSIZE(r->prefix.prefixlen)) { + *min_len = 6 + (unsigned)PSIZE(r->prefix.prefixlen); + return 1; + } + stream_putl(s, r->metric); + + control = r->down ? ISIS_IPV6_REACH_DOWN : 0; + control |= r->external ? ISIS_IPV6_REACH_EXTERNAL : 0; + control |= r->subtlvs ? ISIS_IPV6_REACH_SUBTLV : 0; + + stream_putc(s, control); + stream_putc(s, r->prefix.prefixlen); + + stream_put(s, &r->prefix.prefix.s6_addr, PSIZE(r->prefix.prefixlen)); + + if (r->subtlvs) + return pack_subtlvs(r->subtlvs, s); + + return 0; +} + +static int unpack_item_ipv6_reach(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_ipv6_reach *rv = NULL; + size_t consume; + uint8_t control, subtlv_len; + struct isis_item_list *items; + + if (mtid == ISIS_MT_IPV4_UNICAST) { + items = &tlvs->ipv6_reach; + } else { + items = isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); + } + + sbuf_push(log, indent, "Unpacking %sIPv6 reachability...\n", + (mtid == ISIS_MT_IPV4_UNICAST) ? "" : "mt "); + consume = 6; + if (len < consume) { + sbuf_push(log, indent, + "Not enough data left. (expected 6 or more bytes, got %hhu)\n", + len); + goto out; + } + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = stream_getl(s); + control = stream_getc(s); + rv->down = (control & ISIS_IPV6_REACH_DOWN); + rv->external = (control & ISIS_IPV6_REACH_EXTERNAL); + + rv->prefix.family = AF_INET6; + rv->prefix.prefixlen = stream_getc(s); + if (rv->prefix.prefixlen > IPV6_MAX_BITLEN) { + sbuf_push(log, indent, "Prefixlen %u is implausible for IPv6\n", + rv->prefix.prefixlen); + goto out; + } + + consume += PSIZE(rv->prefix.prefixlen); + if (len < consume) { + sbuf_push(log, indent, + "Expected %u bytes of prefix, but only %u bytes available.\n", + PSIZE(rv->prefix.prefixlen), len - 6); + goto out; + } + stream_get(&rv->prefix.prefix.s6_addr, s, PSIZE(rv->prefix.prefixlen)); + struct in6_addr orig_prefix = rv->prefix.prefix; + + apply_mask_ipv6(&rv->prefix); + if (memcmp(&orig_prefix, &rv->prefix.prefix, sizeof(orig_prefix))) + sbuf_push(log, indent + 2, + "WARNING: Prefix had hostbits set.\n"); + format_item_ipv6_reach(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + + if (control & ISIS_IPV6_REACH_SUBTLV) { + consume += 1; + if (len < consume) { + sbuf_push(log, indent, + "Expected 1 byte of subtlv len, but no more data persent.\n"); + goto out; + } + subtlv_len = stream_getc(s); + + if (!subtlv_len) { + sbuf_push(log, indent + 2, + " WARNING: subtlv bit set, but there are no subtlvs.\n"); + } + consume += subtlv_len; + if (len < consume) { + sbuf_push(log, indent, + "Expected %hhu bytes of subtlvs, but only %u bytes available.\n", + subtlv_len, + len - 6 - PSIZE(rv->prefix.prefixlen)); + goto out; + } + + rv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH); + bool unpacked_known_tlvs = false; + + if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH, subtlv_len, s, + log, rv->subtlvs, indent + 4, &unpacked_known_tlvs)) { + goto out; + } + if (!unpacked_known_tlvs) { + isis_free_subtlvs(rv->subtlvs); + rv->subtlvs = NULL; + } + } + + append_item(items, (struct isis_item *)rv); + return 0; +out: + if (rv) + free_item_ipv6_reach((struct isis_item *)rv); + return 1; +} + +/* Functions related to TLV 242 Router Capability as per RFC7981 */ +static struct isis_router_cap *copy_tlv_router_cap( + const struct isis_router_cap *router_cap) +{ + struct isis_router_cap *rv; + + if (!router_cap) + return NULL; + + rv = XMALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + memcpy(rv, router_cap, sizeof(*rv)); + +#ifndef FABRICD + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_router_cap_fad *sc_fad; + struct isis_router_cap_fad *rv_fad; + + sc_fad = router_cap->fads[i]; + if (!sc_fad) + continue; + rv_fad = XMALLOC(MTYPE_ISIS_TLV, + sizeof(struct isis_router_cap_fad)); + *rv_fad = *sc_fad; + rv_fad->fad.admin_group_exclude_any.bitmap.data = NULL; + rv_fad->fad.admin_group_include_any.bitmap.data = NULL; + rv_fad->fad.admin_group_include_all.bitmap.data = NULL; + + assert(bf_is_inited( + sc_fad->fad.admin_group_exclude_any.bitmap)); + assert(bf_is_inited( + sc_fad->fad.admin_group_include_any.bitmap)); + assert(bf_is_inited( + sc_fad->fad.admin_group_include_all.bitmap)); + + admin_group_copy(&rv_fad->fad.admin_group_exclude_any, + &sc_fad->fad.admin_group_exclude_any); + admin_group_copy(&rv_fad->fad.admin_group_include_any, + &sc_fad->fad.admin_group_include_any); + admin_group_copy(&rv_fad->fad.admin_group_include_all, + &sc_fad->fad.admin_group_include_all); + + rv->fads[i] = rv_fad; + } +#endif /* ifndef FABRICD */ + + return rv; +} + +static void format_tlv_router_cap_json(const struct isis_router_cap *router_cap, + struct json_object *json) +{ + char addrbuf[INET_ADDRSTRLEN]; + + if (!router_cap) + return; + + /* Router ID and Flags */ + struct json_object *cap_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* deprecated JSON key format */ + cap_json = json_object_new_object(); + json_object_object_add(json, "router-capability", cap_json); + inet_ntop(AF_INET, &router_cap->router_id, addrbuf, sizeof(addrbuf)); + json_object_string_add(cap_json, "id", addrbuf); + json_object_string_add( + cap_json, "flag-d", + router_cap->flags & ISIS_ROUTER_CAP_FLAG_D ? "1" : "0"); + json_object_string_add( + cap_json, "flag-s", + router_cap->flags & ISIS_ROUTER_CAP_FLAG_S ? "1" : "0"); + /* end deprecated JSON key format */ + + cap_json = json_object_new_object(); + json_object_object_add(json, "routerCapability", cap_json); + inet_ntop(AF_INET, &router_cap->router_id, addrbuf, sizeof(addrbuf)); + json_object_string_add(cap_json, "id", addrbuf); + json_object_boolean_add(cap_json, "flagD", + !!(router_cap->flags & ISIS_ROUTER_CAP_FLAG_D)); + json_object_boolean_add(cap_json, "flagS", + !!(router_cap->flags & ISIS_ROUTER_CAP_FLAG_S)); + + + /* Segment Routing Global Block as per RFC8667 section #3.1 */ + if (router_cap->srgb.range_size != 0) { + struct json_object *gb_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* deprecated old key format */ + gb_json = json_object_new_object(); + json_object_object_add(json, "segment-routing-gb", gb_json); + json_object_string_add(gb_json, "ipv4", + IS_SR_IPV4(&router_cap->srgb) ? "1" + : "0"); + json_object_string_add(gb_json, "ipv6", + IS_SR_IPV6(&router_cap->srgb) ? "1" + : "0"); + json_object_int_add(gb_json, "global-block-base", + router_cap->srgb.lower_bound); + json_object_int_add(gb_json, "global-block-range", + router_cap->srgb.range_size); + + gb_json = json_object_new_object(); + json_object_object_add(json, "segmentRoutingGb", gb_json); + json_object_boolean_add(gb_json, "ipv4", + !!IS_SR_IPV4(&router_cap->srgb)); + json_object_boolean_add(gb_json, "ipv6", + !!IS_SR_IPV6(&router_cap->srgb)); + json_object_int_add(gb_json, "globalBlockBase", + router_cap->srgb.lower_bound); + json_object_int_add(gb_json, "globalBlockRange", + router_cap->srgb.range_size); + } + + /* Segment Routing Local Block as per RFC8667 section #3.3 */ + if (router_cap->srlb.range_size != 0) { + struct json_object *lb_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + lb_json = json_object_new_object(); + json_object_object_add(json, "segment-routing-lb", lb_json); + json_object_int_add(lb_json, "global-block-base", + router_cap->srlb.lower_bound); + json_object_int_add(lb_json, "global-block-range", + router_cap->srlb.range_size); + /* end old deprecated key format */ + + lb_json = json_object_new_object(); + json_object_object_add(json, "segmentRoutingLb", lb_json); + json_object_int_add(lb_json, "globalBlockBase", + router_cap->srlb.lower_bound); + json_object_int_add(lb_json, "globalBlockRange", + router_cap->srlb.range_size); + } + + /* Segment Routing Algorithms as per RFC8667 section #3.2 */ + if (router_cap->algo[0] != SR_ALGORITHM_UNSET) { + char buf[255]; + struct json_object *alg_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + alg_json = json_object_new_object(); + json_object_object_add(json, "segment-routing-algorithm", + alg_json); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (router_cap->algo[i] != SR_ALGORITHM_UNSET) { + snprintfrr(buf, sizeof(buf), "%d", i); + json_object_string_add(alg_json, buf, + router_cap->algo[i] == 0 + ? "SPF" + : "Strict SPF"); + } + /* end old deprecated key format */ + + alg_json = json_object_new_object(); + json_object_object_add(json, "segmentRoutingAlgorithm", + alg_json); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + if (router_cap->algo[i] != SR_ALGORITHM_UNSET) { + snprintfrr(buf, sizeof(buf), "%d", i); + json_object_string_add(alg_json, buf, + router_cap->algo[i] == 0 + ? "SPF" + : "Strict SPF"); + } + } + } + + /* Segment Routing Node MSD as per RFC8491 section #2 */ + if (router_cap->msd != 0) + json_object_int_add(json, "msd", router_cap->msd); +} + +static void format_tlv_router_cap(const struct isis_router_cap *router_cap, + struct sbuf *buf, int indent) +{ + char addrbuf[INET_ADDRSTRLEN]; + + if (!router_cap) + return; + + /* Router ID and Flags */ + inet_ntop(AF_INET, &router_cap->router_id, addrbuf, sizeof(addrbuf)); + sbuf_push(buf, indent, "Router Capability:"); + sbuf_push(buf, indent, " %s , D:%c, S:%c\n", addrbuf, + router_cap->flags & ISIS_ROUTER_CAP_FLAG_D ? '1' : '0', + router_cap->flags & ISIS_ROUTER_CAP_FLAG_S ? '1' : '0'); + + /* Segment Routing Global Block as per RFC8667 section #3.1 */ + if (router_cap->srgb.range_size != 0) + sbuf_push( + buf, indent, + " Segment Routing: I:%s V:%s, Global Block Base: %u Range: %u\n", + IS_SR_IPV4(&router_cap->srgb) ? "1" : "0", + IS_SR_IPV6(&router_cap->srgb) ? "1" : "0", + router_cap->srgb.lower_bound, + router_cap->srgb.range_size); + + /* Segment Routing Local Block as per RFC8667 section #3.3 */ + if (router_cap->srlb.range_size != 0) + sbuf_push(buf, indent, " SR Local Block Base: %u Range: %u\n", + router_cap->srlb.lower_bound, + router_cap->srlb.range_size); + + /* Segment Routing Algorithms as per RFC8667 section #3.2 */ + if (router_cap->algo[0] != SR_ALGORITHM_UNSET) { + sbuf_push(buf, indent, " SR Algorithm:\n"); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (router_cap->algo[i] != SR_ALGORITHM_UNSET) + sbuf_push(buf, indent, " %u: %s\n", i, + sr_algorithm_string( + router_cap->algo[i])); + } + + /* Segment Routing Node MSD as per RFC8491 section #2 */ + if (router_cap->msd != 0) + sbuf_push(buf, indent, " Node Maximum SID Depth: %u\n", + router_cap->msd); + +#ifndef FABRICD + /* Flex-Algo */ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE]; + int indent2; + struct admin_group *admin_group; + struct isis_router_cap_fad *fad; + + fad = router_cap->fads[i]; + if (!fad) + continue; + + sbuf_push(buf, indent, " Flex-Algo Definition: %d\n", + fad->fad.algorithm); + sbuf_push(buf, indent, " Metric-Type: %d\n", + fad->fad.metric_type); + sbuf_push(buf, indent, " Calc-Type: %d\n", + fad->fad.calc_type); + sbuf_push(buf, indent, " Priority: %d\n", fad->fad.priority); + + indent2 = indent + strlen(" Exclude-Any: "); + admin_group = &fad->fad.admin_group_exclude_any; + sbuf_push(buf, indent, " Exclude-Any: "); + sbuf_push(buf, 0, "%s\n", + admin_group_string(admin_group_buf, + ADMIN_GROUP_PRINT_MAX_SIZE, + indent2, admin_group)); + + indent2 = indent + strlen(" Include-Any: "); + admin_group = &fad->fad.admin_group_include_any; + sbuf_push(buf, indent, " Include-Any: "); + sbuf_push(buf, 0, "%s\n", + admin_group_string(admin_group_buf, + ADMIN_GROUP_PRINT_MAX_SIZE, + indent2, admin_group)); + + indent2 = indent + strlen(" Include-All: "); + admin_group = &fad->fad.admin_group_include_all; + sbuf_push(buf, indent, " Include-All: "); + sbuf_push(buf, 0, "%s\n", + admin_group_string(admin_group_buf, + ADMIN_GROUP_PRINT_MAX_SIZE, + indent2, admin_group)); + + sbuf_push(buf, indent, " M-Flag: %c\n", + CHECK_FLAG(fad->fad.flags, FAD_FLAG_M) ? '1' : '0'); + + if (fad->fad.flags != 0 && fad->fad.flags != FAD_FLAG_M) + sbuf_push(buf, indent, " Flags: 0x%x\n", + fad->fad.flags); + if (fad->fad.exclude_srlg) + sbuf_push(buf, indent, " Exclude SRLG: Enabled\n"); + if (fad->fad.unsupported_subtlv) + sbuf_push(buf, indent, + " Got an unsupported sub-TLV: Yes\n"); + } +#endif /* ifndef FABRICD */ + + /* SRv6 Flags as per RFC 9352 section #2 */ + if (router_cap->srv6_cap.is_srv6_capable) + sbuf_push(buf, indent, " SRv6: O:%s\n", + SUPPORTS_SRV6_OAM(&router_cap->srv6_cap) ? "1" : "0"); +} + +static void free_tlv_router_cap(struct isis_router_cap *router_cap) +{ + if (!router_cap) + return; + +#ifndef FABRICD + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_router_cap_fad *fad; + + fad = router_cap->fads[i]; + if (!fad) + continue; + admin_group_term(&fad->fad.admin_group_exclude_any); + admin_group_term(&fad->fad.admin_group_include_any); + admin_group_term(&fad->fad.admin_group_include_all); + XFREE(MTYPE_ISIS_TLV, fad); + } +#endif /* ifndef FABRICD */ + + XFREE(MTYPE_ISIS_TLV, router_cap); +} + +#ifndef FABRICD +static size_t +isis_router_cap_fad_sub_tlv_len(const struct isis_router_cap_fad *fad) +{ + size_t sz = ISIS_SUBTLV_FAD_MIN_SIZE; + uint32_t admin_group_length; + + admin_group_length = + admin_group_nb_words(&fad->fad.admin_group_exclude_any); + if (admin_group_length) + sz += sizeof(uint32_t) * admin_group_length + 2; + + admin_group_length = + admin_group_nb_words(&fad->fad.admin_group_include_any); + if (admin_group_length) + sz += sizeof(uint32_t) * admin_group_length + 2; + + admin_group_length = + admin_group_nb_words(&fad->fad.admin_group_include_all); + if (admin_group_length) + sz += sizeof(uint32_t) * admin_group_length + 2; + + if (fad->fad.flags != 0) + sz += ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE + 2; + + /* TODO: add exclude SRLG sub-sub-TLV length when supported */ + + return sz; +} +#endif /* ifndef FABRICD */ + +static size_t isis_router_cap_tlv_size(const struct isis_router_cap *router_cap) +{ + size_t sz = 2 + ISIS_ROUTER_CAP_SIZE; +#ifndef FABRICD + size_t fad_sz; +#endif /* ifndef FABRICD */ + int nb_algo, nb_msd; + + if ((router_cap->srgb.range_size != 0) && + (router_cap->srgb.lower_bound != 0)) { + sz += 2 + ISIS_SUBTLV_SID_LABEL_RANGE_SIZE; + sz += 2 + ISIS_SUBTLV_SID_LABEL_SIZE; + + nb_algo = isis_tlvs_sr_algo_count(router_cap); + if (nb_algo != 0) + sz += 2 + nb_algo; + + if ((router_cap->srlb.range_size != 0) && + (router_cap->srlb.lower_bound != 0)) { + sz += 2 + ISIS_SUBTLV_SID_LABEL_RANGE_SIZE; + sz += 2 + ISIS_SUBTLV_SID_LABEL_SIZE; + } + + if (router_cap->msd != 0) + sz += 2 + ISIS_SUBTLV_NODE_MSD_SIZE; + } + +#ifndef FABRICD + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + if (!router_cap->fads[i]) + continue; + fad_sz = 2 + + isis_router_cap_fad_sub_tlv_len(router_cap->fads[i]); + if (((sz + fad_sz) % 256) < (sz % 256)) + sz += 2 + ISIS_ROUTER_CAP_SIZE + fad_sz; + else + sz += fad_sz; + } +#endif /* ifndef FABRICD */ + + if (router_cap->srv6_cap.is_srv6_capable) { + sz += ISIS_SUBTLV_TYPE_FIELD_SIZE + + ISIS_SUBTLV_LENGTH_FIELD_SIZE + + ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE; + + nb_algo = isis_tlvs_sr_algo_count(router_cap); + if (nb_algo != 0) + sz += ISIS_SUBTLV_TYPE_FIELD_SIZE + + ISIS_SUBTLV_LENGTH_FIELD_SIZE + nb_algo; + + nb_msd = router_cap->srv6_msd.max_seg_left_msd + + router_cap->srv6_msd.max_end_pop_msd + + router_cap->srv6_msd.max_h_encaps_msd + + router_cap->srv6_msd.max_end_d_msd; + if (nb_msd != 0) + sz += ISIS_SUBTLV_TYPE_FIELD_SIZE + + ISIS_SUBTLV_LENGTH_FIELD_SIZE + + (ISIS_SUBTLV_NODE_MSD_TYPE_SIZE + + ISIS_SUBTLV_NODE_MSD_VALUE_SIZE) * + nb_msd; + } + + return sz; +} + +static int pack_tlv_router_cap(const struct isis_router_cap *router_cap, + struct stream *s) +{ + size_t tlv_len, len_pos; + uint8_t nb_algo; + size_t subtlv_len, subtlv_len_pos; + bool sr_algo_subtlv_present = false; + + if (!router_cap) + return 0; + + if (STREAM_WRITEABLE(s) < isis_router_cap_tlv_size(router_cap)) + return 1; + + /* Add Router Capability TLV 242 with Router ID and Flags */ + stream_putc(s, ISIS_TLV_ROUTER_CAPABILITY); + len_pos = stream_get_endp(s); + stream_putc(s, 0); /* Real length will be adjusted later */ + stream_put_ipv4(s, router_cap->router_id.s_addr); + stream_putc(s, router_cap->flags); + + /* Add SRGB if set as per RFC8667 section #3.1 */ + if ((router_cap->srgb.range_size != 0) + && (router_cap->srgb.lower_bound != 0)) { + stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE); + stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE_SIZE); + stream_putc(s, router_cap->srgb.flags); + stream_put3(s, router_cap->srgb.range_size); + stream_putc(s, ISIS_SUBTLV_SID_LABEL); + stream_putc(s, ISIS_SUBTLV_SID_LABEL_SIZE); + stream_put3(s, router_cap->srgb.lower_bound); + + /* Then SR Algorithm if set as per RFC8667 section #3.2 */ + nb_algo = isis_tlvs_sr_algo_count(router_cap); + if (nb_algo > 0) { + stream_putc(s, ISIS_SUBTLV_ALGORITHM); + stream_putc(s, nb_algo); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (router_cap->algo[i] != SR_ALGORITHM_UNSET) + stream_putc(s, router_cap->algo[i]); + sr_algo_subtlv_present = true; + } + + /* Local Block if defined as per RFC8667 section #3.3 */ + if ((router_cap->srlb.range_size != 0) + && (router_cap->srlb.lower_bound != 0)) { + stream_putc(s, ISIS_SUBTLV_SRLB); + stream_putc(s, ISIS_SUBTLV_SID_LABEL_RANGE_SIZE); + /* No Flags are defined for SRLB */ + stream_putc(s, 0); + stream_put3(s, router_cap->srlb.range_size); + stream_putc(s, ISIS_SUBTLV_SID_LABEL); + stream_putc(s, ISIS_SUBTLV_SID_LABEL_SIZE); + stream_put3(s, router_cap->srlb.lower_bound); + } + + /* And finish with MSD if set as per RFC8491 section #2 */ + if (router_cap->msd != 0) { + stream_putc(s, ISIS_SUBTLV_NODE_MSD); + stream_putc(s, ISIS_SUBTLV_NODE_MSD_SIZE); + stream_putc(s, MSD_TYPE_BASE_MPLS_IMPOSITION); + stream_putc(s, router_cap->msd); + } + } + +#ifndef FABRICD + /* Flex Algo Definitions */ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_router_cap_fad *fad; + size_t subtlv_len; + struct admin_group *ag; + uint32_t admin_group_length; + + fad = router_cap->fads[i]; + if (!fad) + continue; + + subtlv_len = isis_router_cap_fad_sub_tlv_len(fad); + + if ((stream_get_endp(s) - len_pos - 1) > 250) { + /* Adjust TLV length which depends on subTLVs presence + */ + tlv_len = stream_get_endp(s) - len_pos - 1; + stream_putc_at(s, len_pos, tlv_len); + + /* Add Router Capability TLV 242 with Router ID and + * Flags + */ + stream_putc(s, ISIS_TLV_ROUTER_CAPABILITY); + /* Real length will be adjusted later */ + len_pos = stream_get_endp(s); + stream_putc(s, 0); + stream_put_ipv4(s, router_cap->router_id.s_addr); + stream_putc(s, router_cap->flags); + } + + stream_putc(s, ISIS_SUBTLV_FAD); + stream_putc(s, subtlv_len); /* length will be filled later */ + + stream_putc(s, fad->fad.algorithm); + stream_putc(s, fad->fad.metric_type); + stream_putc(s, fad->fad.calc_type); + stream_putc(s, fad->fad.priority); + + ag = &fad->fad.admin_group_exclude_any; + admin_group_length = admin_group_nb_words(ag); + if (admin_group_length) { + stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG); + stream_putc(s, sizeof(uint32_t) * admin_group_length); + for (size_t i = 0; i < admin_group_length; i++) + stream_putl(s, admin_group_get_offset(ag, i)); + } + + ag = &fad->fad.admin_group_include_any; + admin_group_length = admin_group_nb_words(ag); + if (admin_group_length) { + stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG); + stream_putc(s, sizeof(uint32_t) * admin_group_length); + for (size_t i = 0; i < admin_group_length; i++) + stream_putl(s, admin_group_get_offset(ag, i)); + } + + ag = &fad->fad.admin_group_include_all; + admin_group_length = admin_group_nb_words(ag); + if (admin_group_length) { + stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG); + stream_putc(s, sizeof(uint32_t) * admin_group_length); + for (size_t i = 0; i < admin_group_length; i++) + stream_putl(s, admin_group_get_offset(ag, i)); + } + + if (fad->fad.flags != 0) { + stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS); + stream_putc(s, ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE); + stream_putc(s, fad->fad.flags); + } + } +#endif /* ifndef FABRICD */ + + /* Add SRv6 capabilities if set as per RFC 9352 section #2 */ + if (router_cap->srv6_cap.is_srv6_capable) { + stream_putc(s, ISIS_SUBTLV_SRV6_CAPABILITIES); + stream_putc(s, ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE); + stream_putw(s, router_cap->srv6_cap.flags); + + /* + * Then add SR Algorithm if set and if we haven't already + * added it when we processed SR-MPLS related Sub-TLVs as + * per RFC 9352 section #3 + */ + if (!sr_algo_subtlv_present) { + nb_algo = isis_tlvs_sr_algo_count(router_cap); + if (nb_algo > 0) { + stream_putc(s, ISIS_SUBTLV_ALGORITHM); + stream_putc(s, nb_algo); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (router_cap->algo[i] != + SR_ALGORITHM_UNSET) + stream_putc(s, + router_cap->algo[i]); + } + } + + /* And finish with MSDs if set as per RFC 9352 section #4 */ + if (router_cap->srv6_msd.max_seg_left_msd + + router_cap->srv6_msd.max_end_pop_msd + + router_cap->srv6_msd.max_h_encaps_msd + + router_cap->srv6_msd.max_end_d_msd != + 0) { + stream_putc(s, ISIS_SUBTLV_NODE_MSD); + + subtlv_len_pos = stream_get_endp(s); + /* Put 0 as Sub-TLV length for now, real length will be + * adjusted later */ + stream_putc(s, 0); + + /* RFC 9352 section #4.1 */ + if (router_cap->srv6_msd.max_seg_left_msd != 0) { + stream_putc(s, ISIS_SUBTLV_SRV6_MAX_SL_MSD); + stream_putc( + s, + router_cap->srv6_msd.max_seg_left_msd); + } + + /* RFC 9352 section #4.2 */ + if (router_cap->srv6_msd.max_end_pop_msd != 0) { + stream_putc(s, + ISIS_SUBTLV_SRV6_MAX_END_POP_MSD); + stream_putc( + s, + router_cap->srv6_msd.max_end_pop_msd); + } + + /* RFC 9352 section #4.3 */ + if (router_cap->srv6_msd.max_h_encaps_msd != 0) { + stream_putc(s, + ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD); + stream_putc( + s, + router_cap->srv6_msd.max_h_encaps_msd); + } + + /* RFC 9352 section #4.4 */ + if (router_cap->srv6_msd.max_end_d_msd != 0) { + stream_putc(s, ISIS_SUBTLV_SRV6_MAX_END_D_MSD); + stream_putc(s, + router_cap->srv6_msd.max_end_d_msd); + } + + /* Adjust Node MSD Sub-TLV length which depends on MSDs + * presence */ + subtlv_len = stream_get_endp(s) - subtlv_len_pos - 1; + stream_putc_at(s, subtlv_len_pos, subtlv_len); + } + } + + /* Adjust TLV length which depends on subTLVs presence */ + tlv_len = stream_get_endp(s) - len_pos - 1; + stream_putc_at(s, len_pos, tlv_len); + + return 0; +} + +static int unpack_tlv_router_cap(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, void *dest, + int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_router_cap *rcap; + uint8_t type; + uint8_t length; + uint8_t subtlv_len; + uint8_t size; + int num_msd; + + sbuf_push(log, indent, "Unpacking Router Capability TLV...\n"); + if (tlv_len < ISIS_ROUTER_CAP_SIZE) { + sbuf_push(log, indent, "WARNING: Unexpected TLV size\n"); + stream_forward_getp(s, tlv_len); + return 0; + } + + if (tlvs->router_cap) + /* Multiple Router Capability found */ + rcap = tlvs->router_cap; + else { + /* Allocate router cap structure and initialize SR Algorithms */ + rcap = XCALLOC(MTYPE_ISIS_TLV, sizeof(struct isis_router_cap)); + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + rcap->algo[i] = SR_ALGORITHM_UNSET; + } + + /* Get Router ID and Flags */ + rcap->router_id.s_addr = stream_get_ipv4(s); + rcap->flags = stream_getc(s); + + /* Parse remaining part of the TLV if present */ + subtlv_len = tlv_len - ISIS_ROUTER_CAP_SIZE; + while (subtlv_len > 2) { +#ifndef FABRICD + struct isis_router_cap_fad *fad; + uint8_t subsubtlvs_len; +#endif /* ifndef FABRICD */ + uint8_t msd_type; + + type = stream_getc(s); + length = stream_getc(s); + + if (length > STREAM_READABLE(s) || length > subtlv_len - 2) { + sbuf_push( + log, indent, + "WARNING: Router Capability subTLV length too large compared to expected size\n"); + stream_forward_getp(s, STREAM_READABLE(s)); + XFREE(MTYPE_ISIS_TLV, rcap); + return 0; + } + + switch (type) { + case ISIS_SUBTLV_SID_LABEL_RANGE: + /* Check that SRGB is correctly formated */ + if (length < SUBTLV_RANGE_LABEL_SIZE + || length > SUBTLV_RANGE_INDEX_SIZE) { + stream_forward_getp(s, length); + break; + } + /* Only one SRGB is supported. Skip subsequent one */ + if (rcap->srgb.range_size != 0) { + stream_forward_getp(s, length); + break; + } + rcap->srgb.flags = stream_getc(s); + rcap->srgb.range_size = stream_get3(s); + /* Skip Type and get Length of SID Label */ + stream_getc(s); + size = stream_getc(s); + + if (size == ISIS_SUBTLV_SID_LABEL_SIZE + && length != SUBTLV_RANGE_LABEL_SIZE) { + stream_forward_getp(s, length - 6); + break; + } + + if (size == ISIS_SUBTLV_SID_INDEX_SIZE + && length != SUBTLV_RANGE_INDEX_SIZE) { + stream_forward_getp(s, length - 6); + break; + } + + if (size == ISIS_SUBTLV_SID_LABEL_SIZE) { + rcap->srgb.lower_bound = stream_get3(s); + } else if (size == ISIS_SUBTLV_SID_INDEX_SIZE) { + rcap->srgb.lower_bound = stream_getl(s); + } else { + stream_forward_getp(s, length - 6); + break; + } + + /* SRGB sanity checks. */ + if (rcap->srgb.range_size == 0 + || (rcap->srgb.lower_bound <= MPLS_LABEL_RESERVED_MAX) + || ((rcap->srgb.lower_bound + rcap->srgb.range_size - 1) + > MPLS_LABEL_UNRESERVED_MAX)) { + sbuf_push(log, indent, "Invalid label range. Reset SRGB\n"); + rcap->srgb.lower_bound = 0; + rcap->srgb.range_size = 0; + } + /* Only one range is supported. Skip subsequent one */ + size = length - (size + SUBTLV_SR_BLOCK_SIZE); + if (size > 0) + stream_forward_getp(s, size); + + break; + case ISIS_SUBTLV_ALGORITHM: + if (length == 0) + break; + + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + rcap->algo[i] = SR_ALGORITHM_UNSET; + + for (int i = 0; i < length; i++) { + uint8_t algo; + + algo = stream_getc(s); + rcap->algo[algo] = algo; + } + break; + case ISIS_SUBTLV_SRLB: + /* Check that SRLB is correctly formated */ + if (length < SUBTLV_RANGE_LABEL_SIZE + || length > SUBTLV_RANGE_INDEX_SIZE) { + stream_forward_getp(s, length); + break; + } + /* RFC 8667 section #3.3: Only one SRLB is authorized */ + if (rcap->srlb.range_size != 0) { + stream_forward_getp(s, length); + break; + } + /* Ignore Flags which are not defined */ + stream_getc(s); + rcap->srlb.range_size = stream_get3(s); + /* Skip Type and get Length of SID Label */ + stream_getc(s); + size = stream_getc(s); + + if (size == ISIS_SUBTLV_SID_LABEL_SIZE + && length != SUBTLV_RANGE_LABEL_SIZE) { + stream_forward_getp(s, length - 6); + break; + } + + if (size == ISIS_SUBTLV_SID_INDEX_SIZE + && length != SUBTLV_RANGE_INDEX_SIZE) { + stream_forward_getp(s, length - 6); + break; + } + + if (size == ISIS_SUBTLV_SID_LABEL_SIZE) { + rcap->srlb.lower_bound = stream_get3(s); + } else if (size == ISIS_SUBTLV_SID_INDEX_SIZE) { + rcap->srlb.lower_bound = stream_getl(s); + } else { + stream_forward_getp(s, length - 6); + break; + } + + /* SRLB sanity checks. */ + if (rcap->srlb.range_size == 0 + || (rcap->srlb.lower_bound <= MPLS_LABEL_RESERVED_MAX) + || ((rcap->srlb.lower_bound + rcap->srlb.range_size - 1) + > MPLS_LABEL_UNRESERVED_MAX)) { + sbuf_push(log, indent, "Invalid label range. Reset SRLB\n"); + rcap->srlb.lower_bound = 0; + rcap->srlb.range_size = 0; + } + /* Only one range is supported. Skip subsequent one */ + size = length - (size + SUBTLV_SR_BLOCK_SIZE); + if (size > 0) + stream_forward_getp(s, size); + + break; + case ISIS_SUBTLV_NODE_MSD: + sbuf_push(log, indent, + "Unpacking Node MSD sub-TLV...\n"); + + /* Check that MSD is correctly formated */ + if (length % 2) { + sbuf_push( + log, indent, + "WARNING: Unexpected MSD sub-TLV length\n"); + stream_forward_getp(s, length); + break; + } + + /* Get the number of MSDs carried in the value field of + * the Node MSD sub-TLV. The value field consists of one + * or more pairs of a 1-octet MSD-Type and 1-octet + * MSD-Value */ + num_msd = length / 2; + + /* Unpack MSDs */ + for (int i = 0; i < num_msd; i++) { + msd_type = stream_getc(s); + + switch (msd_type) { + case MSD_TYPE_BASE_MPLS_IMPOSITION: + /* BMI-MSD type as per RFC 8491 */ + rcap->msd = stream_getc(s); + break; + case ISIS_SUBTLV_SRV6_MAX_SL_MSD: + /* SRv6 Maximum Segments Left MSD Type + * as per RFC 9352 section #4.1 */ + rcap->srv6_msd.max_seg_left_msd = + stream_getc(s); + break; + case ISIS_SUBTLV_SRV6_MAX_END_POP_MSD: + /* SRv6 Maximum End Pop MSD Type as per + * RFC 9352 section #4.2 */ + rcap->srv6_msd.max_end_pop_msd = + stream_getc(s); + break; + case ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD: + /* SRv6 Maximum H.Encaps MSD Type as per + * RFC 9352 section #4.3 */ + rcap->srv6_msd.max_h_encaps_msd = + stream_getc(s); + break; + case ISIS_SUBTLV_SRV6_MAX_END_D_MSD: + /* SRv6 Maximum End D MSD Type as per + * RFC 9352 section #4.4 */ + rcap->srv6_msd.max_end_d_msd = + stream_getc(s); + break; + default: + /* Unknown MSD, let's skip it */ + sbuf_push( + log, indent, + "WARNING: Skipping unknown MSD Type %hhu (1 byte)\n", + msd_type); + stream_forward_getp(s, 1); + } + } + break; +#ifndef FABRICD + case ISIS_SUBTLV_FAD: + fad = XCALLOC(MTYPE_ISIS_TLV, + sizeof(struct isis_router_cap_fad)); + fad->fad.algorithm = stream_getc(s); + fad->fad.metric_type = stream_getc(s); + fad->fad.calc_type = stream_getc(s); + fad->fad.priority = stream_getc(s); + rcap->fads[fad->fad.algorithm] = fad; + admin_group_init(&fad->fad.admin_group_exclude_any); + admin_group_init(&fad->fad.admin_group_include_any); + admin_group_init(&fad->fad.admin_group_include_all); + + subsubtlvs_len = length - 4; + while (subsubtlvs_len > 2) { + struct admin_group *ag; + uint8_t subsubtlv_type; + uint8_t subsubtlv_len; + uint32_t v; + int n_ag, i; + + subsubtlv_type = stream_getc(s); + subsubtlv_len = stream_getc(s); + + switch (subsubtlv_type) { + case ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG: + ag = &fad->fad.admin_group_exclude_any; + n_ag = subsubtlv_len / sizeof(uint32_t); + for (i = 0; i < n_ag; i++) { + v = stream_getl(s); + admin_group_bulk_set(ag, v, i); + } + break; + case ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG: + ag = &fad->fad.admin_group_include_any; + n_ag = subsubtlv_len / sizeof(uint32_t); + for (i = 0; i < n_ag; i++) { + v = stream_getl(s); + admin_group_bulk_set(ag, v, i); + } + break; + case ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG: + ag = &fad->fad.admin_group_include_all; + n_ag = subsubtlv_len / sizeof(uint32_t); + for (i = 0; i < n_ag; i++) { + v = stream_getl(s); + admin_group_bulk_set(ag, v, i); + } + break; + case ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS: + if (subsubtlv_len == 0) + break; + + fad->fad.flags = stream_getc(s); + for (i = subsubtlv_len - 1; i > 0; --i) + stream_getc(s); + break; + case ISIS_SUBTLV_FAD_SUBSUBTLV_ESRLG: + fad->fad.exclude_srlg = true; + stream_forward_getp(s, subsubtlv_len); + break; + default: + sbuf_push( + log, indent, + "Received an unsupported Flex-Algo sub-TLV type %u\n", + subsubtlv_type); + fad->fad.unsupported_subtlv = true; + stream_forward_getp(s, subsubtlv_len); + break; + } + subsubtlvs_len -= 2 + subsubtlv_len; + } + break; +#endif /* ifndef FABRICD */ + case ISIS_SUBTLV_SRV6_CAPABILITIES: + sbuf_push(log, indent, + "Unpacking SRv6 Capabilities sub-TLV...\n"); + /* Check that SRv6 capabilities sub-TLV is correctly + * formated */ + if (length < ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE) { + sbuf_push( + log, indent, + "WARNING: Unexpected SRv6 Capabilities sub-TLV size (expected %d or more bytes, got %hhu)\n", + ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE, + length); + stream_forward_getp(s, length); + break; + } + /* Only one SRv6 capabilities is supported. Skip + * subsequent one */ + if (rcap->srv6_cap.is_srv6_capable) { + sbuf_push( + log, indent, + "WARNING: SRv6 Capabilities sub-TLV present multiple times, ignoring.\n"); + stream_forward_getp(s, length); + break; + } + rcap->srv6_cap.is_srv6_capable = true; + rcap->srv6_cap.flags = stream_getw(s); + + /* The SRv6 Capabilities Sub-TLV may contain optional + * Sub-Sub-TLVs, as per RFC 9352 section #2. + * Skip any Sub-Sub-TLV contained in the SRv6 + * Capabilities Sub-TLV that is not currently supported + * by IS-IS. + */ + if (length > ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE) + sbuf_push( + log, indent, + "Skipping unknown sub-TLV (%hhu bytes)\n", + length); + stream_forward_getp( + s, length - ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE); + + break; + default: + stream_forward_getp(s, length); + break; + } + subtlv_len = subtlv_len - length - 2; + } + tlvs->router_cap = rcap; + return 0; +} + +/* Functions related to TLV 10 Authentication */ +static struct isis_item *copy_item_auth(struct isis_item *i) +{ + struct isis_auth *auth = (struct isis_auth *)i; + struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->type = auth->type; + rv->length = auth->length; + memcpy(rv->value, auth->value, sizeof(rv->value)); + return (struct isis_item *)rv; +} + +static void format_item_auth(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_auth *auth = (struct isis_auth *)i; + char obuf[768]; + + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "test-auth", "ok"); + json_object_string_add(json, "testAuth", "ok"); + } else + sbuf_push(buf, indent, "Authentication:\n"); + switch (auth->type) { + case ISIS_PASSWD_TYPE_CLEARTXT: + zlog_sanitize(obuf, sizeof(obuf), auth->value, auth->length); + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "auth-pass", obuf); + json_object_string_add(json, "authPass", obuf); + } else + sbuf_push(buf, indent, " Password: %s\n", obuf); + break; + case ISIS_PASSWD_TYPE_HMAC_MD5: + for (unsigned int j = 0; j < 16; j++) { + snprintf(obuf + 2 * j, sizeof(obuf) - 2 * j, "%02hhx", + auth->value[j]); + } + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "auth-hmac-md5", obuf); + json_object_string_add(json, "authHmacMd5", obuf); + } else + sbuf_push(buf, indent, " HMAC-MD5: %s\n", obuf); + break; + default: + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_int_add(json, "auth-unknown", auth->type); + json_object_int_add(json, "authUnknown", auth->type); + } else + sbuf_push(buf, indent, " Unknown (%hhu)\n", + auth->type); + break; + } +} + +static void free_item_auth(struct isis_item *i) +{ + XFREE(MTYPE_ISIS_TLV, i); +} + +static int pack_item_auth(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_auth *auth = (struct isis_auth *)i; + + if (STREAM_WRITEABLE(s) < 1) { + *min_len = 1; + return 1; + } + stream_putc(s, auth->type); + + switch (auth->type) { + case ISIS_PASSWD_TYPE_CLEARTXT: + if (STREAM_WRITEABLE(s) < auth->length) { + *min_len = 1 + auth->length; + return 1; + } + stream_put(s, auth->passwd, auth->length); + break; + case ISIS_PASSWD_TYPE_HMAC_MD5: + if (STREAM_WRITEABLE(s) < 16) { + *min_len = 1 + 16; + return 1; + } + auth->offset = stream_get_endp(s); + stream_put(s, NULL, 16); + break; + default: + return 1; + } + + return 0; +} + +static int unpack_item_auth(uint16_t mtid, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + + sbuf_push(log, indent, "Unpack Auth TLV...\n"); + if (len < 1) { + sbuf_push( + log, indent, + "Not enough data left.(Expected 1 bytes of auth type, got %hhu)\n", + len); + return 1; + } + + struct isis_auth *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->type = stream_getc(s); + rv->length = len - 1; + + if (rv->type == ISIS_PASSWD_TYPE_HMAC_MD5 && rv->length != 16) { + sbuf_push( + log, indent, + "Unexpected auth length for HMAC-MD5 (expected 16, got %hhu)\n", + rv->length); + XFREE(MTYPE_ISIS_TLV, rv); + return 1; + } + + rv->offset = stream_get_getp(s); + stream_get(rv->value, s, rv->length); + format_item_auth(mtid, (struct isis_item *)rv, log, NULL, indent + 2); + append_item(&tlvs->isis_auth, (struct isis_item *)rv); + return 0; +} + +/* Functions related to TLV 13 Purge Originator */ + +static struct isis_purge_originator *copy_tlv_purge_originator( + struct isis_purge_originator *poi) +{ + if (!poi) + return NULL; + + struct isis_purge_originator *rv; + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + rv->sender_set = poi->sender_set; + memcpy(rv->generator, poi->generator, sizeof(rv->generator)); + if (poi->sender_set) + memcpy(rv->sender, poi->sender, sizeof(rv->sender)); + return rv; +} + +static void format_tlv_purge_originator(struct isis_purge_originator *poi, + struct sbuf *buf, + struct json_object *json, int indent) +{ + char sen_id[ISO_SYSID_STRLEN]; + char gen_id[ISO_SYSID_STRLEN]; + + if (!poi) + return; + + snprintfrr(gen_id, ISO_SYSID_STRLEN, "%pSY", poi->generator); + if (poi->sender_set) + snprintfrr(sen_id, ISO_SYSID_STRLEN, "%pSY", poi->sender); + + if (json) { + struct json_object *purge_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old deprecated key format */ + purge_json = json_object_new_object(); + json_object_object_add(json, "purge_originator", purge_json); + + json_object_string_add(purge_json, "id", gen_id); + if (poi->sender_set) + json_object_string_add(purge_json, "rec-from", sen_id); + /* end old deprecated key format */ + + purge_json = json_object_new_object(); + json_object_object_add(json, "purgeOriginator", purge_json); + + json_object_string_add(purge_json, "id", gen_id); + if (poi->sender_set) + json_object_string_add(purge_json, "recFrom", sen_id); + } else { + sbuf_push(buf, indent, "Purge Originator Identification:\n"); + sbuf_push(buf, indent, " Generator: %s\n", gen_id); + if (poi->sender_set) + sbuf_push(buf, indent, " Received-From: %s\n", sen_id); + } +} + +static void free_tlv_purge_originator(struct isis_purge_originator *poi) +{ + XFREE(MTYPE_ISIS_TLV, poi); +} + +static int pack_tlv_purge_originator(struct isis_purge_originator *poi, + struct stream *s) +{ + if (!poi) + return 0; + + uint8_t data_len = 1 + sizeof(poi->generator); + + if (poi->sender_set) + data_len += sizeof(poi->sender); + + if (STREAM_WRITEABLE(s) < (unsigned)(2 + data_len)) + return 1; + + stream_putc(s, ISIS_TLV_PURGE_ORIGINATOR); + stream_putc(s, data_len); + stream_putc(s, poi->sender_set ? 2 : 1); + stream_put(s, poi->generator, sizeof(poi->generator)); + if (poi->sender_set) + stream_put(s, poi->sender, sizeof(poi->sender)); + return 0; +} + +static int unpack_tlv_purge_originator(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_purge_originator poi = {}; + + sbuf_push(log, indent, "Unpacking Purge Originator Identification TLV...\n"); + if (tlv_len < 7) { + sbuf_push(log, indent, "Not enough data left. (Expected at least 7 bytes, got %hhu)\n", tlv_len); + return 1; + } + + uint8_t number_of_ids = stream_getc(s); + + if (number_of_ids == 1) { + poi.sender_set = false; + } else if (number_of_ids == 2) { + poi.sender_set = true; + } else { + sbuf_push(log, indent, "Got invalid value for number of system IDs: %hhu)\n", number_of_ids); + return 1; + } + + if (tlv_len != 1 + 6 * number_of_ids) { + sbuf_push(log, indent, "Incorrect tlv len for number of IDs.\n"); + return 1; + } + + stream_get(poi.generator, s, sizeof(poi.generator)); + if (poi.sender_set) + stream_get(poi.sender, s, sizeof(poi.sender)); + + if (tlvs->purge_originator) { + sbuf_push(log, indent, + "WARNING: Purge originator present multiple times, ignoring.\n"); + return 0; + } + + tlvs->purge_originator = copy_tlv_purge_originator(&poi); + return 0; +} + + +/* Functions relating to item TLVs */ + +static void init_item_list(struct isis_item_list *items) +{ + items->head = NULL; + items->tail = &items->head; + items->count = 0; +} + +static struct isis_item *copy_item(enum isis_tlv_context context, + enum isis_tlv_type type, + struct isis_item *item) +{ + const struct tlv_ops *ops = tlv_table[context][type]; + + if (ops && ops->copy_item) + return ops->copy_item(item); + + assert(!"Unknown item tlv type!"); + return NULL; +} + +static void copy_items(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_item_list *src, struct isis_item_list *dest) +{ + struct isis_item *item; + + init_item_list(dest); + + for (item = src->head; item; item = item->next) { + append_item(dest, copy_item(context, type, item)); + } +} + +static void format_item(uint16_t mtid, enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_item *i, + struct sbuf *buf, struct json_object *json, int indent) +{ + const struct tlv_ops *ops = tlv_table[context][type]; + + if (ops && ops->format_item) { + ops->format_item(mtid, i, buf, json, indent); + return; + } + + assert(!"Unknown item tlv type!"); +} + +static void format_items_(uint16_t mtid, enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_item_list *items, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_item *i; + + for (i = items->head; i; i = i->next) + format_item(mtid, context, type, i, buf, json, indent); +} + +static void free_item(enum isis_tlv_context tlv_context, + enum isis_tlv_type tlv_type, struct isis_item *item) +{ + const struct tlv_ops *ops = tlv_table[tlv_context][tlv_type]; + + if (ops && ops->free_item) { + ops->free_item(item); + return; + } + + assert(!"Unknown item tlv type!"); +} + +static void free_items(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_item_list *items) +{ + struct isis_item *item, *next_item; + + for (item = items->head; item; item = next_item) { + next_item = item->next; + free_item(context, type, item); + } +} + +static int pack_item(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_item *i, struct stream *s, size_t *min_len, + struct isis_tlvs **fragment_tlvs, + const struct pack_order_entry *pe, uint16_t mtid) +{ + const struct tlv_ops *ops = tlv_table[context][type]; + + if (ops && ops->pack_item) { + return ops->pack_item(i, s, min_len); + } + + assert(!"Unknown item tlv type!"); + return 1; +} + +static void add_item_to_fragment(struct isis_item *i, + const struct pack_order_entry *pe, + struct isis_tlvs *fragment_tlvs, uint16_t mtid) +{ + struct isis_item_list *l; + + if (pe->how_to_pack == ISIS_ITEMS) { + l = (struct isis_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack); + } else { + struct isis_mt_item_list *m; + m = (struct isis_mt_item_list *)(((char *)fragment_tlvs) + pe->what_to_pack); + l = isis_get_mt_items(m, mtid); + } + + append_item(l, copy_item(pe->context, pe->type, i)); +} + +static int pack_items_(uint16_t mtid, enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_item_list *items, + struct stream *s, struct isis_tlvs **fragment_tlvs, + const struct pack_order_entry *pe, + struct isis_tlvs *(*new_fragment)(struct list *l), + struct list *new_fragment_arg) +{ + size_t len_pos, last_len, len; + struct isis_item *item = NULL; + int rv; + size_t min_len = 0; + + if (!items->head) + return 0; + +top: + if (STREAM_WRITEABLE(s) < 2) + goto too_long; + + stream_putc(s, type); + len_pos = stream_get_endp(s); + stream_putc(s, 0); /* Put 0 as length for now */ + + if (context == ISIS_CONTEXT_LSP && IS_COMPAT_MT_TLV(type) + && mtid != ISIS_MT_IPV4_UNICAST) { + if (STREAM_WRITEABLE(s) < 2) + goto too_long; + stream_putw(s, mtid); + } + + /* The SRv6 Locator TLV (RFC 9352 section #7.1) starts with the MTID + * field */ + if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_SRV6_LOCATOR) { + if (STREAM_WRITEABLE(s) < 2) + goto too_long; + stream_putw(s, mtid); + } + + if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_OLDSTYLE_REACH) { + if (STREAM_WRITEABLE(s) < 1) + goto too_long; + stream_putc(s, 0); /* Virtual flag is set to 0 */ + } + + last_len = len = 0; + for (item = item ? item : items->head; item; item = item->next) { + rv = pack_item(context, type, item, s, &min_len, fragment_tlvs, + pe, mtid); + if (rv) + goto too_long; + + len = stream_get_endp(s) - len_pos - 1; + + /* Multiple auths don't go into one TLV, so always break */ + if (context == ISIS_CONTEXT_LSP && type == ISIS_TLV_AUTH) { + item = item->next; + break; + } + + /* Multiple prefix-sids don't go into one TLV, so always break */ + if (type == ISIS_SUBTLV_PREFIX_SID + && (context == ISIS_CONTEXT_SUBTLV_IP_REACH + || context == ISIS_CONTEXT_SUBTLV_IPV6_REACH)) { + item = item->next; + break; + } + + if (len > 255) { + if (!last_len) /* strange, not a single item fit */ + return 1; + /* drop last tlv, otherwise, its too long */ + stream_set_endp(s, len_pos + 1 + last_len); + len = last_len; + break; + } + + if (fragment_tlvs) + add_item_to_fragment(item, pe, *fragment_tlvs, mtid); + + last_len = len; + } + + stream_putc_at(s, len_pos, len); + if (item) + goto top; + + return 0; +too_long: + if (!fragment_tlvs) + return 1; + stream_reset(s); + if (STREAM_WRITEABLE(s) < min_len) + return 1; + *fragment_tlvs = new_fragment(new_fragment_arg); + goto top; +} +#define pack_items(...) pack_items_(ISIS_MT_IPV4_UNICAST, __VA_ARGS__) + +static void append_item(struct isis_item_list *dest, struct isis_item *item) +{ + *dest->tail = item; + dest->tail = &(*dest->tail)->next; + dest->count++; +} + +static void delete_item(struct isis_item_list *dest, struct isis_item *del) +{ + struct isis_item *item, *prev = NULL, *next; + + /* Sanity Check */ + if ((dest == NULL) || (del == NULL)) + return; + + /* + * TODO: delete is tricky because "dest" is a singly linked list. + * We need to switch a doubly linked list. + */ + for (item = dest->head; item; item = next) { + if (item->next == del) { + prev = item; + break; + } + next = item->next; + } + if (prev) + prev->next = del->next; + if (dest->head == del) + dest->head = del->next; + if ((struct isis_item *)dest->tail == del) { + *dest->tail = prev; + if (prev) + dest->tail = &(*dest->tail)->next; + else + dest->tail = &dest->head; + } + dest->count--; +} + +static struct isis_item *last_item(struct isis_item_list *list) +{ + return container_of(list->tail, struct isis_item, next); +} + +static int unpack_item(uint16_t mtid, enum isis_tlv_context context, + uint8_t tlv_type, uint8_t len, struct stream *s, + struct sbuf *log, void *dest, int indent) +{ + const struct tlv_ops *ops = tlv_table[context][tlv_type]; + + if (ops && ops->unpack_item) + return ops->unpack_item(mtid, len, s, log, dest, indent); + + assert(!"Unknown item tlv type!"); + sbuf_push(log, indent, "Unknown item tlv type!\n"); + return 1; +} + +static int unpack_tlv_with_items(enum isis_tlv_context context, + uint8_t tlv_type, uint8_t tlv_len, + struct stream *s, struct sbuf *log, void *dest, + int indent) +{ + size_t tlv_start; + size_t tlv_pos; + int rv; + uint16_t mtid; + + tlv_start = stream_get_getp(s); + tlv_pos = 0; + + if (context == ISIS_CONTEXT_LSP && + (IS_COMPAT_MT_TLV(tlv_type) || tlv_type == ISIS_TLV_SRV6_LOCATOR)) { + if (tlv_len < 2) { + sbuf_push(log, indent, + "TLV is too short to contain MTID\n"); + return 1; + } + mtid = stream_getw(s) & ISIS_MT_MASK; + tlv_pos += 2; + sbuf_push(log, indent, "Unpacking as MT %s item TLV...\n", + isis_mtid2str_fake(mtid)); + } else { + sbuf_push(log, indent, "Unpacking as item TLV...\n"); + mtid = ISIS_MT_IPV4_UNICAST; + } + + if (context == ISIS_CONTEXT_LSP + && tlv_type == ISIS_TLV_OLDSTYLE_REACH) { + if (tlv_len - tlv_pos < 1) { + sbuf_push(log, indent, + "TLV is too short for old style reach\n"); + return 1; + } + stream_forward_getp(s, 1); + tlv_pos += 1; + } + + if (context == ISIS_CONTEXT_LSP + && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH) { + struct isis_tlvs *tlvs = dest; + dest = &tlvs->oldstyle_ip_reach; + } else if (context == ISIS_CONTEXT_LSP + && tlv_type == ISIS_TLV_OLDSTYLE_IP_REACH_EXT) { + struct isis_tlvs *tlvs = dest; + dest = &tlvs->oldstyle_ip_reach_ext; + } + + if (context == ISIS_CONTEXT_LSP + && tlv_type == ISIS_TLV_MT_ROUTER_INFO) { + struct isis_tlvs *tlvs = dest; + tlvs->mt_router_info_empty = (tlv_pos >= (size_t)tlv_len); + } + + while (tlv_pos < (size_t)tlv_len) { + rv = unpack_item(mtid, context, tlv_type, tlv_len - tlv_pos, s, + log, dest, indent + 2); + if (rv) + return rv; + + tlv_pos = stream_get_getp(s) - tlv_start; + } + + return 0; +} + +/* Functions to manipulate mt_item_lists */ + +static int isis_mt_item_list_cmp(const struct isis_item_list *a, + const struct isis_item_list *b) +{ + if (a->mtid < b->mtid) + return -1; + if (a->mtid > b->mtid) + return 1; + return 0; +} + +RB_PROTOTYPE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp); +RB_GENERATE(isis_mt_item_list, isis_item_list, mt_tree, isis_mt_item_list_cmp); + +struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m, + uint16_t mtid) +{ + struct isis_item_list *rv; + + rv = isis_lookup_mt_items(m, mtid); + if (!rv) { + rv = XCALLOC(MTYPE_ISIS_MT_ITEM_LIST, sizeof(*rv)); + init_item_list(rv); + rv->mtid = mtid; + RB_INSERT(isis_mt_item_list, m, rv); + } + + return rv; +} + +struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m, + uint16_t mtid) +{ + struct isis_item_list key = {.mtid = mtid}; + + return RB_FIND(isis_mt_item_list, m, &key); +} + +static void free_mt_items(enum isis_tlv_context context, + enum isis_tlv_type type, struct isis_mt_item_list *m) +{ + struct isis_item_list *n, *nnext; + + RB_FOREACH_SAFE (n, isis_mt_item_list, m, nnext) { + free_items(context, type, n); + RB_REMOVE(isis_mt_item_list, m, n); + XFREE(MTYPE_ISIS_MT_ITEM_LIST, n); + } +} + +static void format_mt_items(enum isis_tlv_context context, + enum isis_tlv_type type, + struct isis_mt_item_list *m, struct sbuf *buf, + struct json_object *json, int indent) +{ + struct isis_item_list *n; + + RB_FOREACH (n, isis_mt_item_list, m) { + format_items_(n->mtid, context, type, n, buf, json, indent); + } +} + +static int pack_mt_items(enum isis_tlv_context context, enum isis_tlv_type type, + struct isis_mt_item_list *m, struct stream *s, + struct isis_tlvs **fragment_tlvs, + const struct pack_order_entry *pe, + struct isis_tlvs *(*new_fragment)(struct list *l), + struct list *new_fragment_arg) +{ + struct isis_item_list *n; + + RB_FOREACH (n, isis_mt_item_list, m) { + int rv; + + rv = pack_items_(n->mtid, context, type, n, s, fragment_tlvs, + pe, new_fragment, new_fragment_arg); + if (rv) + return rv; + } + + return 0; +} + +static void copy_mt_items(enum isis_tlv_context context, + enum isis_tlv_type type, + struct isis_mt_item_list *src, + struct isis_mt_item_list *dest) +{ + struct isis_item_list *n; + + RB_INIT(isis_mt_item_list, dest); + + RB_FOREACH (n, isis_mt_item_list, src) { + copy_items(context, type, n, isis_get_mt_items(dest, n->mtid)); + } +} + +/* Functions related to TLV 27 SRv6 Locator as per RFC 9352 section #7.1*/ +static struct isis_item *copy_item_srv6_locator(struct isis_item *i) +{ + struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i; + struct isis_srv6_locator_tlv *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = loc->metric; + rv->flags = loc->flags; + rv->algorithm = loc->algorithm; + rv->prefix = loc->prefix; + rv->subtlvs = copy_subtlvs(loc->subtlvs); + + return (struct isis_item *)rv; +} + +static void format_item_srv6_locator(uint16_t mtid, struct isis_item *i, + struct sbuf *buf, struct json_object *json, + int indent) +{ + struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i; + + if (json) { + struct json_object *loc_json; + +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + /* old json key format */ + loc_json = json_object_new_object(); + json_object_object_add(json, "srv6-locator", loc_json); + json_object_int_add(loc_json, "mt-id", mtid); + json_object_string_addf(loc_json, "prefix", "%pFX", + &loc->prefix); + json_object_int_add(loc_json, "metric", loc->metric); + json_object_string_add( + loc_json, "d-flag", + CHECK_FLAG(loc->flags, ISIS_TLV_SRV6_LOCATOR_FLAG_D) + ? "yes" + : ""); + json_object_int_add(loc_json, "algorithm", loc->algorithm); + json_object_string_add(loc_json, "mt-name", + isis_mtid2str(mtid)); + if (loc->subtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(loc_json, "subtlvs", + subtlvs_json); + format_subtlvs(loc->subtlvs, NULL, subtlvs_json, 0); + } + /* old deprecated key format */ + + loc_json = json_object_new_object(); + json_object_object_add(json, "srv6Locator", loc_json); + json_object_int_add(loc_json, "mtId", mtid); + json_object_string_addf(loc_json, "prefix", "%pFX", + &loc->prefix); + json_object_int_add(loc_json, "metric", loc->metric); + json_object_boolean_add(loc_json, "flagD", + !!CHECK_FLAG(loc->flags, + ISIS_TLV_SRV6_LOCATOR_FLAG_D)); + json_object_int_add(loc_json, "algorithm", loc->algorithm); + json_object_string_add(loc_json, "MTName", isis_mtid2str(mtid)); + if (loc->subtlvs) { + struct json_object *subtlvs_json; + subtlvs_json = json_object_new_object(); + json_object_object_add(loc_json, "subtlvs", + subtlvs_json); + format_subtlvs(loc->subtlvs, NULL, subtlvs_json, 0); + } + } else { + sbuf_push(buf, indent, "SRv6 Locator: %pFX (Metric: %u)%s", + &loc->prefix, loc->metric, + CHECK_FLAG(loc->flags, ISIS_TLV_SRV6_LOCATOR_FLAG_D) + ? " D-flag" + : ""); + sbuf_push(buf, 0, " %s\n", isis_mtid2str(mtid)); + + if (loc->subtlvs) { + sbuf_push(buf, indent, " Sub-TLVs:\n"); + format_subtlvs(loc->subtlvs, buf, NULL, indent + 4); + } + } +} + +static void free_item_srv6_locator(struct isis_item *i) +{ + struct isis_srv6_locator_tlv *item = (struct isis_srv6_locator_tlv *)i; + + isis_free_subtlvs(item->subtlvs); + XFREE(MTYPE_ISIS_TLV, item); +} + +static int pack_item_srv6_locator(struct isis_item *i, struct stream *s, + size_t *min_len) +{ + struct isis_srv6_locator_tlv *loc = (struct isis_srv6_locator_tlv *)i; + + if (STREAM_WRITEABLE(s) < 7 + (unsigned)PSIZE(loc->prefix.prefixlen)) { + *min_len = 7 + (unsigned)PSIZE(loc->prefix.prefixlen); + return 1; + } + + stream_putl(s, loc->metric); + stream_putc(s, loc->flags); + stream_putc(s, loc->algorithm); + /* Locator size */ + stream_putc(s, loc->prefix.prefixlen); + /* Locator prefix */ + stream_put(s, &loc->prefix.prefix.s6_addr, + PSIZE(loc->prefix.prefixlen)); + + if (loc->subtlvs) { + /* Pack Sub-TLVs */ + if (pack_subtlvs(loc->subtlvs, s)) + return 1; + } else { + /* No Sub-TLVs */ + if (STREAM_WRITEABLE(s) < 1) { + *min_len = 8 + (unsigned)PSIZE(loc->prefix.prefixlen); + return 1; + } + + /* Put 0 as Sub-TLV length, because we have no Sub-TLVs */ + stream_putc(s, 0); + } + + return 0; +} + +static int unpack_item_srv6_locator(uint16_t mtid, uint8_t len, + struct stream *s, struct sbuf *log, + void *dest, int indent) +{ + struct isis_tlvs *tlvs = dest; + struct isis_srv6_locator_tlv *rv = NULL; + size_t consume; + uint8_t subtlv_len; + struct isis_item_list *items; + + items = isis_get_mt_items(&tlvs->srv6_locator, mtid); + + sbuf_push(log, indent, "Unpacking SRv6 Locator...\n"); + consume = 7; + if (len < consume) { + sbuf_push( + log, indent, + "Not enough data left. (expected 7 or more bytes, got %hhu)\n", + len); + goto out; + } + + rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + rv->metric = stream_getl(s); + rv->flags = stream_getc(s); + rv->algorithm = stream_getc(s); + + rv->prefix.family = AF_INET6; + rv->prefix.prefixlen = stream_getc(s); + if (rv->prefix.prefixlen > IPV6_MAX_BITLEN) { + sbuf_push(log, indent, "Loc Size %u is implausible for SRv6\n", + rv->prefix.prefixlen); + goto out; + } + + consume += PSIZE(rv->prefix.prefixlen); + if (len < consume) { + sbuf_push( + log, indent, + "Expected %u bytes of prefix, but only %u bytes available.\n", + PSIZE(rv->prefix.prefixlen), len - 7); + goto out; + } + stream_get(&rv->prefix.prefix.s6_addr, s, PSIZE(rv->prefix.prefixlen)); + + struct in6_addr orig_locator = rv->prefix.prefix; + apply_mask_ipv6(&rv->prefix); + if (memcmp(&orig_locator, &rv->prefix.prefix, sizeof(orig_locator))) + sbuf_push(log, indent + 2, + "WARNING: SRv6 Locator had hostbits set.\n"); + format_item_srv6_locator(mtid, (struct isis_item *)rv, log, NULL, + indent + 2); + + consume += 1; + if (len < consume) { + sbuf_push( + log, indent, + "Expected 1 byte of subtlv len, but no more data persent.\n"); + goto out; + } + subtlv_len = stream_getc(s); + + if (subtlv_len) { + consume += subtlv_len; + if (len < consume) { + sbuf_push( + log, indent, + "Expected %hhu bytes of subtlvs, but only %u bytes available.\n", + subtlv_len, + len - 7 - PSIZE(rv->prefix.prefixlen)); + goto out; + } + + rv->subtlvs = + isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR); + + bool unpacked_known_tlvs = false; + if (unpack_tlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR, subtlv_len, s, + log, rv->subtlvs, indent + 4, + &unpacked_known_tlvs)) { + goto out; + } + if (!unpacked_known_tlvs) { + isis_free_subtlvs(rv->subtlvs); + rv->subtlvs = NULL; + } + } + + append_item(items, (struct isis_item *)rv); + return 0; +out: + if (rv) + free_item_srv6_locator((struct isis_item *)rv); + return 1; +} + +/* Functions related to tlvs in general */ + +struct isis_tlvs *isis_alloc_tlvs(void) +{ + struct isis_tlvs *result; + + result = XCALLOC(MTYPE_ISIS_TLV, sizeof(*result)); + + init_item_list(&result->isis_auth); + init_item_list(&result->area_addresses); + init_item_list(&result->mt_router_info); + init_item_list(&result->oldstyle_reach); + init_item_list(&result->lan_neighbor); + init_item_list(&result->lsp_entries); + init_item_list(&result->extended_reach); + RB_INIT(isis_mt_item_list, &result->mt_reach); + init_item_list(&result->oldstyle_ip_reach); + init_item_list(&result->oldstyle_ip_reach_ext); + init_item_list(&result->ipv4_address); + init_item_list(&result->ipv6_address); + init_item_list(&result->global_ipv6_address); + init_item_list(&result->extended_ip_reach); + RB_INIT(isis_mt_item_list, &result->mt_ip_reach); + init_item_list(&result->ipv6_reach); + RB_INIT(isis_mt_item_list, &result->mt_ipv6_reach); + RB_INIT(isis_mt_item_list, &result->srv6_locator); + + return result; +} + +struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs) +{ + struct isis_tlvs *rv = XCALLOC(MTYPE_ISIS_TLV, sizeof(*rv)); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, + &rv->isis_auth); + + rv->purge_originator = + copy_tlv_purge_originator(tlvs->purge_originator); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, + &tlvs->area_addresses, &rv->area_addresses); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, + &tlvs->mt_router_info, &rv->mt_router_info); + + rv->mt_router_info_empty = tlvs->mt_router_info_empty; + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, + &tlvs->oldstyle_reach, &rv->oldstyle_reach); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, + &tlvs->lan_neighbor, &rv->lan_neighbor); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries, + &rv->lsp_entries); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, + &tlvs->extended_reach, &rv->extended_reach); + + copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach, + &rv->mt_reach); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, + &tlvs->oldstyle_ip_reach, &rv->oldstyle_ip_reach); + + copy_tlv_protocols_supported(&tlvs->protocols_supported, + &rv->protocols_supported); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, + &tlvs->oldstyle_ip_reach_ext, &rv->oldstyle_ip_reach_ext); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, &tlvs->ipv4_address, + &rv->ipv4_address); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, &tlvs->ipv6_address, + &rv->ipv6_address); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS, + &tlvs->global_ipv6_address, &rv->global_ipv6_address); + + rv->te_router_id = copy_tlv_te_router_id(tlvs->te_router_id); + + rv->te_router_id_ipv6 = + copy_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, + &tlvs->extended_ip_reach, &rv->extended_ip_reach); + + copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, + &tlvs->mt_ip_reach, &rv->mt_ip_reach); + + rv->hostname = copy_tlv_dynamic_hostname(tlvs->hostname); + + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach, + &rv->ipv6_reach); + + copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, + &tlvs->mt_ipv6_reach, &rv->mt_ipv6_reach); + + rv->threeway_adj = copy_tlv_threeway_adj(tlvs->threeway_adj); + + rv->router_cap = copy_tlv_router_cap(tlvs->router_cap); + + rv->spine_leaf = copy_tlv_spine_leaf(tlvs->spine_leaf); + + copy_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR, + &tlvs->srv6_locator, &rv->srv6_locator); + + return rv; +} + +static void format_tlvs(struct isis_tlvs *tlvs, struct sbuf *buf, struct json_object *json, int indent) +{ + format_tlv_protocols_supported(&tlvs->protocols_supported, buf, json, + indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth, buf, + json, indent); + + format_tlv_purge_originator(tlvs->purge_originator, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, + &tlvs->area_addresses, buf, json, indent); + + if (tlvs->mt_router_info_empty) { + if (json) { +#if CONFDATE > 20240916 + CPP_NOTICE("remove deprecated key format with -") +#endif + json_object_string_add(json, "mt-router-info", "none"); + json_object_object_add(json, "mtRouterInfo", NULL); + } else + sbuf_push(buf, indent, "MT Router Info: None\n"); + } else { + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, + &tlvs->mt_router_info, buf, json, indent); + } + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, + &tlvs->oldstyle_reach, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, + &tlvs->lan_neighbor, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries, + buf, json, indent); + + format_tlv_dynamic_hostname(tlvs->hostname, buf, json, indent); + format_tlv_te_router_id(tlvs->te_router_id, buf, json, indent); + format_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, buf, json, + indent); + if (json) + format_tlv_router_cap_json(tlvs->router_cap, json); + else + format_tlv_router_cap(tlvs->router_cap, buf, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, + &tlvs->extended_reach, buf, json, indent); + + format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach, + buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, + &tlvs->oldstyle_ip_reach, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, + &tlvs->oldstyle_ip_reach_ext, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, + &tlvs->ipv4_address, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, + &tlvs->ipv6_address, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS, + &tlvs->global_ipv6_address, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, + &tlvs->extended_ip_reach, buf, json, indent); + + format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, + &tlvs->mt_ip_reach, buf, json, indent); + + format_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach, + buf, json, indent); + + format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, + &tlvs->mt_ipv6_reach, buf, json, indent); + + format_tlv_threeway_adj(tlvs->threeway_adj, buf, json, indent); + + format_tlv_spine_leaf(tlvs->spine_leaf, buf, json, indent); + + format_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR, + &tlvs->srv6_locator, buf, json, indent); +} + +const char *isis_format_tlvs(struct isis_tlvs *tlvs, struct json_object *json) +{ + if (json) { + format_tlvs(tlvs, NULL, json, 0); + return NULL; + } else { + static struct sbuf buf; + + if (!sbuf_buf(&buf)) + sbuf_init(&buf, NULL, 0); + + sbuf_reset(&buf); + format_tlvs(tlvs, &buf, NULL, 0); + return sbuf_buf(&buf); + } +} + +void isis_free_tlvs(struct isis_tlvs *tlvs) +{ + if (!tlvs) + return; + + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth); + free_tlv_purge_originator(tlvs->purge_originator); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, + &tlvs->area_addresses); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, + &tlvs->mt_router_info); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_REACH, + &tlvs->oldstyle_reach); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LAN_NEIGHBORS, + &tlvs->lan_neighbor); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_LSP_ENTRY, &tlvs->lsp_entries); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_REACH, + &tlvs->extended_reach); + free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_REACH, &tlvs->mt_reach); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH, + &tlvs->oldstyle_ip_reach); + free_tlv_protocols_supported(&tlvs->protocols_supported); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_OLDSTYLE_IP_REACH_EXT, + &tlvs->oldstyle_ip_reach_ext); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV4_ADDRESS, + &tlvs->ipv4_address); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_ADDRESS, + &tlvs->ipv6_address); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_GLOBAL_IPV6_ADDRESS, + &tlvs->global_ipv6_address); + free_tlv_te_router_id(tlvs->te_router_id); + free_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_EXTENDED_IP_REACH, + &tlvs->extended_ip_reach); + free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IP_REACH, + &tlvs->mt_ip_reach); + free_tlv_dynamic_hostname(tlvs->hostname); + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_IPV6_REACH, &tlvs->ipv6_reach); + free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_IPV6_REACH, + &tlvs->mt_ipv6_reach); + free_tlv_threeway_adj(tlvs->threeway_adj); + free_tlv_router_cap(tlvs->router_cap); + free_tlv_spine_leaf(tlvs->spine_leaf); + free_mt_items(ISIS_CONTEXT_LSP, ISIS_TLV_SRV6_LOCATOR, + &tlvs->srv6_locator); + + XFREE(MTYPE_ISIS_TLV, tlvs); +} + +static void add_padding(struct stream *s) +{ + while (STREAM_WRITEABLE(s)) { + if (STREAM_WRITEABLE(s) == 1) + break; + uint32_t padding_len = STREAM_WRITEABLE(s) - 2; + + if (padding_len > 255) { + if (padding_len == 256) + padding_len = 254; + else + padding_len = 255; + } + + stream_putc(s, ISIS_TLV_PADDING); + stream_putc(s, padding_len); + stream_put(s, NULL, padding_len); + } +} + +#define LSP_REM_LIFETIME_OFF 10 +#define LSP_CHECKSUM_OFF 24 +static void safe_auth_md5(struct stream *s, uint16_t *checksum, + uint16_t *rem_lifetime) +{ + memcpy(rem_lifetime, STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, + sizeof(*rem_lifetime)); + memset(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, 0, sizeof(*rem_lifetime)); + memcpy(checksum, STREAM_DATA(s) + LSP_CHECKSUM_OFF, sizeof(*checksum)); + memset(STREAM_DATA(s) + LSP_CHECKSUM_OFF, 0, sizeof(*checksum)); +} + +static void restore_auth_md5(struct stream *s, uint16_t checksum, + uint16_t rem_lifetime) +{ + memcpy(STREAM_DATA(s) + LSP_REM_LIFETIME_OFF, &rem_lifetime, + sizeof(rem_lifetime)); + memcpy(STREAM_DATA(s) + LSP_CHECKSUM_OFF, &checksum, sizeof(checksum)); +} + +static void update_auth_hmac_md5(struct isis_auth *auth, struct stream *s, + bool is_lsp) +{ + uint8_t digest[16]; + uint16_t checksum, rem_lifetime; + + if (is_lsp) + safe_auth_md5(s, &checksum, &rem_lifetime); + + memset(STREAM_DATA(s) + auth->offset, 0, 16); +#ifdef CRYPTO_OPENSSL + uint8_t *result = (uint8_t *)HMAC(EVP_md5(), auth->passwd, + auth->plength, STREAM_DATA(s), + stream_get_endp(s), NULL, NULL); + + memcpy(digest, result, 16); +#elif CRYPTO_INTERNAL + hmac_md5(STREAM_DATA(s), stream_get_endp(s), auth->passwd, + auth->plength, digest); +#endif + memcpy(auth->value, digest, 16); + memcpy(STREAM_DATA(s) + auth->offset, digest, 16); + + if (is_lsp) + restore_auth_md5(s, checksum, rem_lifetime); +} + +static void update_auth(struct isis_tlvs *tlvs, struct stream *s, bool is_lsp) +{ + struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head; + + for (struct isis_auth *auth = auth_head; auth; auth = auth->next) { + if (auth->type == ISIS_PASSWD_TYPE_HMAC_MD5) + update_auth_hmac_md5(auth, s, is_lsp); + } +} + +static int handle_pack_entry(const struct pack_order_entry *pe, + struct isis_tlvs *tlvs, struct stream *stream, + struct isis_tlvs **fragment_tlvs, + struct isis_tlvs *(*new_fragment)(struct list *l), + struct list *new_fragment_arg) +{ + int rv; + + if (pe->how_to_pack == ISIS_ITEMS) { + struct isis_item_list *l; + l = (struct isis_item_list *)(((char *)tlvs) + + pe->what_to_pack); + rv = pack_items(pe->context, pe->type, l, stream, fragment_tlvs, + pe, new_fragment, new_fragment_arg); + } else { + struct isis_mt_item_list *l; + l = (struct isis_mt_item_list *)(((char *)tlvs) + + pe->what_to_pack); + rv = pack_mt_items(pe->context, pe->type, l, stream, + fragment_tlvs, pe, new_fragment, + new_fragment_arg); + } + + return rv; +} + +static int pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, + struct isis_tlvs *fragment_tlvs, + struct isis_tlvs *(*new_fragment)(struct list *l), + struct list *new_fragment_arg) +{ + int rv; + + /* When fragmenting, don't add auth as it's already accounted for in the + * size we are given. */ + if (!fragment_tlvs) { + rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, + &tlvs->isis_auth, stream, NULL, NULL, NULL, + NULL); + if (rv) + return rv; + } + + rv = pack_tlv_purge_originator(tlvs->purge_originator, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->purge_originator = + copy_tlv_purge_originator(tlvs->purge_originator); + } + + rv = pack_tlv_protocols_supported(&tlvs->protocols_supported, stream); + if (rv) + return rv; + if (fragment_tlvs) { + copy_tlv_protocols_supported( + &tlvs->protocols_supported, + &fragment_tlvs->protocols_supported); + } + + rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, + &tlvs->area_addresses, stream, NULL, NULL, NULL, NULL); + if (rv) + return rv; + if (fragment_tlvs) { + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_AREA_ADDRESSES, + &tlvs->area_addresses, + &fragment_tlvs->area_addresses); + } + + + if (tlvs->mt_router_info_empty) { + if (STREAM_WRITEABLE(stream) < 2) + return 1; + stream_putc(stream, ISIS_TLV_MT_ROUTER_INFO); + stream_putc(stream, 0); + if (fragment_tlvs) + fragment_tlvs->mt_router_info_empty = true; + } else { + rv = pack_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, + &tlvs->mt_router_info, stream, NULL, NULL, NULL, + NULL); + if (rv) + return rv; + if (fragment_tlvs) { + copy_items(ISIS_CONTEXT_LSP, ISIS_TLV_MT_ROUTER_INFO, + &tlvs->mt_router_info, + &fragment_tlvs->mt_router_info); + } + } + + rv = pack_tlv_dynamic_hostname(tlvs->hostname, stream); + if (rv) + return rv; + if (fragment_tlvs) + fragment_tlvs->hostname = + copy_tlv_dynamic_hostname(tlvs->hostname); + + rv = pack_tlv_router_cap(tlvs->router_cap, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->router_cap = + copy_tlv_router_cap(tlvs->router_cap); + } + + rv = pack_tlv_te_router_id(tlvs->te_router_id, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->te_router_id = + copy_tlv_te_router_id(tlvs->te_router_id); + } + + rv = pack_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->te_router_id_ipv6 = + copy_tlv_te_router_id_ipv6(tlvs->te_router_id_ipv6); + } + + rv = pack_tlv_threeway_adj(tlvs->threeway_adj, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->threeway_adj = + copy_tlv_threeway_adj(tlvs->threeway_adj); + } + + rv = pack_tlv_spine_leaf(tlvs->spine_leaf, stream); + if (rv) + return rv; + if (fragment_tlvs) { + fragment_tlvs->spine_leaf = + copy_tlv_spine_leaf(tlvs->spine_leaf); + } + + for (size_t pack_idx = 0; pack_idx < array_size(pack_order); + pack_idx++) { + rv = handle_pack_entry(&pack_order[pack_idx], tlvs, stream, + fragment_tlvs ? &fragment_tlvs : NULL, + new_fragment, new_fragment_arg); + + if (rv) + return rv; + } + + return 0; +} + +int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, + size_t len_pointer, bool pad, bool is_lsp) +{ + int rv; + + rv = pack_tlvs(tlvs, stream, NULL, NULL, NULL); + if (rv) + return rv; + + if (pad) + add_padding(stream); + + if (len_pointer != (size_t)-1) { + stream_putw_at(stream, len_pointer, stream_get_endp(stream)); + } + + update_auth(tlvs, stream, is_lsp); + + return 0; +} + +static struct isis_tlvs *new_fragment(struct list *l) +{ + struct isis_tlvs *rv = isis_alloc_tlvs(); + + listnode_add(l, rv); + return rv; +} + +struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size) +{ + struct stream *dummy_stream = stream_new(size); + struct list *rv = list_new(); + struct isis_tlvs *fragment_tlvs = new_fragment(rv); + + if (pack_tlvs(tlvs, dummy_stream, fragment_tlvs, new_fragment, rv)) { + struct listnode *node; + for (ALL_LIST_ELEMENTS_RO(rv, node, fragment_tlvs)) + isis_free_tlvs(fragment_tlvs); + list_delete(&rv); + } + + stream_free(dummy_stream); + return rv; +} + +static int unpack_tlv_unknown(enum isis_tlv_context context, uint8_t tlv_type, + uint8_t tlv_len, struct stream *s, + struct sbuf *log, int indent) +{ + stream_forward_getp(s, tlv_len); + sbuf_push(log, indent, + "Skipping unknown TLV %hhu (%hhu bytes)\n", + tlv_type, tlv_len); + return 0; +} + +static int unpack_tlv(enum isis_tlv_context context, size_t avail_len, + struct stream *stream, struct sbuf *log, void *dest, + int indent, bool *unpacked_known_tlvs) +{ + uint8_t tlv_type, tlv_len; + const struct tlv_ops *ops; + + sbuf_push(log, indent, "Unpacking TLV...\n"); + + if (avail_len < 2) { + sbuf_push( + log, indent + 2, + "Available data %zu too short to contain a TLV header.\n", + avail_len); + return 1; + } + + tlv_type = stream_getc(stream); + tlv_len = stream_getc(stream); + + sbuf_push(log, indent + 2, + "Found TLV of type %hhu and len %hhu.\n", + tlv_type, tlv_len); + + if (avail_len < ((size_t)tlv_len) + 2) { + sbuf_push(log, indent + 2, + "Available data %zu too short for claimed TLV len %hhu.\n", + avail_len - 2, tlv_len); + return 1; + } + + ops = tlv_table[context][tlv_type]; + if (ops && ops->unpack) { + if (unpacked_known_tlvs) + *unpacked_known_tlvs = true; + return ops->unpack(context, tlv_type, tlv_len, stream, log, + dest, indent + 2); + } + + return unpack_tlv_unknown(context, tlv_type, tlv_len, stream, log, + indent + 2); +} + +static int unpack_tlvs(enum isis_tlv_context context, size_t avail_len, + struct stream *stream, struct sbuf *log, void *dest, + int indent, bool *unpacked_known_tlvs) +{ + int rv; + size_t tlv_start, tlv_pos; + + tlv_start = stream_get_getp(stream); + tlv_pos = 0; + + sbuf_push(log, indent, "Unpacking %zu bytes of %s...\n", avail_len, + (context == ISIS_CONTEXT_LSP) ? "TLVs" : "sub-TLVs"); + + while (tlv_pos < avail_len) { + rv = unpack_tlv(context, avail_len - tlv_pos, stream, log, dest, + indent + 2, unpacked_known_tlvs); + if (rv) + return rv; + + tlv_pos = stream_get_getp(stream) - tlv_start; + } + + return 0; +} + +int isis_unpack_tlvs(size_t avail_len, struct stream *stream, + struct isis_tlvs **dest, const char **log) +{ + static struct sbuf logbuf; + int indent = 0; + int rv; + struct isis_tlvs *result; + + if (!sbuf_buf(&logbuf)) + sbuf_init(&logbuf, NULL, 0); + + sbuf_reset(&logbuf); + if (avail_len > STREAM_READABLE(stream)) { + sbuf_push(&logbuf, indent, + "Stream doesn't contain sufficient data. Claimed %zu, available %zu\n", + avail_len, STREAM_READABLE(stream)); + return 1; + } + + result = isis_alloc_tlvs(); + rv = unpack_tlvs(ISIS_CONTEXT_LSP, avail_len, stream, &logbuf, result, + indent, NULL); + + *log = sbuf_buf(&logbuf); + *dest = result; + + return rv; +} + +#define TLV_OPS(_name_, _desc_) \ + static const struct tlv_ops tlv_##_name_##_ops = { \ + .name = _desc_, .unpack = unpack_tlv_##_name_, \ + } + +#define ITEM_TLV_OPS(_name_, _desc_) \ + static const struct tlv_ops tlv_##_name_##_ops = { \ + .name = _desc_, \ + .unpack = unpack_tlv_with_items, \ + \ + .pack_item = pack_item_##_name_, \ + .free_item = free_item_##_name_, \ + .unpack_item = unpack_item_##_name_, \ + .format_item = format_item_##_name_, \ + .copy_item = copy_item_##_name_} + +#define SUBTLV_OPS(_name_, _desc_) \ + static const struct tlv_ops subtlv_##_name_##_ops = { \ + .name = _desc_, .unpack = unpack_subtlv_##_name_, \ + } + +#define ITEM_SUBTLV_OPS(_name_, _desc_) \ + ITEM_TLV_OPS(_name_, _desc_) + +#define SUBSUBTLV_OPS(_name_, _desc_) \ + static const struct tlv_ops subsubtlv_##_name_##_ops = { \ + .name = _desc_, \ + .unpack = unpack_subsubtlv_##_name_, \ + } + +#define ITEM_SUBSUBTLV_OPS(_name_, _desc_) \ + ITEM_TLV_OPS(_name_, _desc_) + +ITEM_TLV_OPS(area_address, "TLV 1 Area Addresses"); +ITEM_TLV_OPS(oldstyle_reach, "TLV 2 IS Reachability"); +ITEM_TLV_OPS(lan_neighbor, "TLV 6 LAN Neighbors"); +ITEM_TLV_OPS(lsp_entry, "TLV 9 LSP Entries"); +ITEM_TLV_OPS(auth, "TLV 10 IS-IS Auth"); +TLV_OPS(purge_originator, "TLV 13 Purge Originator Identification"); +ITEM_TLV_OPS(extended_reach, "TLV 22 Extended Reachability"); +ITEM_TLV_OPS(oldstyle_ip_reach, "TLV 128/130 IP Reachability"); +TLV_OPS(protocols_supported, "TLV 129 Protocols Supported"); +ITEM_TLV_OPS(ipv4_address, "TLV 132 IPv4 Interface Address"); +TLV_OPS(te_router_id, "TLV 134 TE Router ID"); +ITEM_TLV_OPS(extended_ip_reach, "TLV 135 Extended IP Reachability"); +TLV_OPS(dynamic_hostname, "TLV 137 Dynamic Hostname"); +TLV_OPS(te_router_id_ipv6, "TLV 140 IPv6 TE Router ID"); +TLV_OPS(spine_leaf, "TLV 150 Spine Leaf Extensions"); +ITEM_TLV_OPS(mt_router_info, "TLV 229 MT Router Information"); +TLV_OPS(threeway_adj, "TLV 240 P2P Three-Way Adjacency"); +ITEM_TLV_OPS(ipv6_address, "TLV 232 IPv6 Interface Address"); +ITEM_TLV_OPS(global_ipv6_address, "TLV 233 Global IPv6 Interface Address"); +ITEM_TLV_OPS(ipv6_reach, "TLV 236 IPv6 Reachability"); +TLV_OPS(router_cap, "TLV 242 Router Capability"); + +ITEM_SUBTLV_OPS(prefix_sid, "Sub-TLV 3 SR Prefix-SID"); +SUBTLV_OPS(ipv6_source_prefix, "Sub-TLV 22 IPv6 Source Prefix"); + +ITEM_TLV_OPS(srv6_locator, "TLV 27 SRv6 Locator"); +ITEM_SUBTLV_OPS(srv6_end_sid, "Sub-TLV 5 SRv6 End SID"); +SUBSUBTLV_OPS(srv6_sid_structure, "Sub-Sub-TLV 1 SRv6 SID Structure"); + +static const struct tlv_ops *const tlv_table[ISIS_CONTEXT_MAX][ISIS_TLV_MAX] = { + [ISIS_CONTEXT_LSP] = { + [ISIS_TLV_AREA_ADDRESSES] = &tlv_area_address_ops, + [ISIS_TLV_OLDSTYLE_REACH] = &tlv_oldstyle_reach_ops, + [ISIS_TLV_LAN_NEIGHBORS] = &tlv_lan_neighbor_ops, + [ISIS_TLV_LSP_ENTRY] = &tlv_lsp_entry_ops, + [ISIS_TLV_AUTH] = &tlv_auth_ops, + [ISIS_TLV_PURGE_ORIGINATOR] = &tlv_purge_originator_ops, + [ISIS_TLV_EXTENDED_REACH] = &tlv_extended_reach_ops, + [ISIS_TLV_OLDSTYLE_IP_REACH] = &tlv_oldstyle_ip_reach_ops, + [ISIS_TLV_PROTOCOLS_SUPPORTED] = &tlv_protocols_supported_ops, + [ISIS_TLV_OLDSTYLE_IP_REACH_EXT] = &tlv_oldstyle_ip_reach_ops, + [ISIS_TLV_IPV4_ADDRESS] = &tlv_ipv4_address_ops, + [ISIS_TLV_TE_ROUTER_ID] = &tlv_te_router_id_ops, + [ISIS_TLV_TE_ROUTER_ID_IPV6] = &tlv_te_router_id_ipv6_ops, + [ISIS_TLV_EXTENDED_IP_REACH] = &tlv_extended_ip_reach_ops, + [ISIS_TLV_DYNAMIC_HOSTNAME] = &tlv_dynamic_hostname_ops, + [ISIS_TLV_SPINE_LEAF_EXT] = &tlv_spine_leaf_ops, + [ISIS_TLV_MT_REACH] = &tlv_extended_reach_ops, + [ISIS_TLV_MT_ROUTER_INFO] = &tlv_mt_router_info_ops, + [ISIS_TLV_IPV6_ADDRESS] = &tlv_ipv6_address_ops, + [ISIS_TLV_GLOBAL_IPV6_ADDRESS] = &tlv_global_ipv6_address_ops, + [ISIS_TLV_MT_IP_REACH] = &tlv_extended_ip_reach_ops, + [ISIS_TLV_IPV6_REACH] = &tlv_ipv6_reach_ops, + [ISIS_TLV_MT_IPV6_REACH] = &tlv_ipv6_reach_ops, + [ISIS_TLV_THREE_WAY_ADJ] = &tlv_threeway_adj_ops, + [ISIS_TLV_ROUTER_CAPABILITY] = &tlv_router_cap_ops, + [ISIS_TLV_SRV6_LOCATOR] = &tlv_srv6_locator_ops, + }, + [ISIS_CONTEXT_SUBTLV_NE_REACH] = {}, + [ISIS_CONTEXT_SUBTLV_IP_REACH] = { + [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops, + }, + [ISIS_CONTEXT_SUBTLV_IPV6_REACH] = { + [ISIS_SUBTLV_PREFIX_SID] = &tlv_prefix_sid_ops, + [ISIS_SUBTLV_IPV6_SOURCE_PREFIX] = &subtlv_ipv6_source_prefix_ops, + }, + [ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR] = { + [ISIS_SUBTLV_SRV6_END_SID] = &tlv_srv6_end_sid_ops, + }, + [ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID] = { + [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops, + }, + [ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID] = { + [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops, + }, + [ISIS_CONTEXT_SUBSUBTLV_SRV6_LAN_ENDX_SID] = { + [ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE] = &subsubtlv_srv6_sid_structure_ops, + } +}; + +/* Accessor functions */ + +void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd) +{ + free_items(ISIS_CONTEXT_LSP, ISIS_TLV_AUTH, &tlvs->isis_auth); + init_item_list(&tlvs->isis_auth); + + if (passwd->type == ISIS_PASSWD_TYPE_UNUSED) + return; + + struct isis_auth *auth = XCALLOC(MTYPE_ISIS_TLV, sizeof(*auth)); + + auth->type = passwd->type; + + auth->plength = passwd->len; + memcpy(auth->passwd, passwd->passwd, + MIN(sizeof(auth->passwd), sizeof(passwd->passwd))); + + if (auth->type == ISIS_PASSWD_TYPE_CLEARTXT) { + auth->length = passwd->len; + memcpy(auth->value, passwd->passwd, + MIN(sizeof(auth->value), sizeof(passwd->passwd))); + } + + append_item(&tlvs->isis_auth, (struct isis_item *)auth); +} + +void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, + struct list *addresses) +{ + struct listnode *node; + struct iso_address *area_addr; + + for (ALL_LIST_ELEMENTS_RO(addresses, node, area_addr)) { + struct isis_area_address *a = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); + + a->len = area_addr->addr_len; + memcpy(a->addr, area_addr->area_addr, ISO_ADDR_SIZE); + append_item(&tlvs->area_addresses, (struct isis_item *)a); + } +} + +void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, struct list *neighbors) +{ + struct listnode *node; + uint8_t *snpa; + + for (ALL_LIST_ELEMENTS_RO(neighbors, node, snpa)) { + struct isis_lan_neighbor *n = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*n)); + + memcpy(n->mac, snpa, 6); + append_item(&tlvs->lan_neighbor, (struct isis_item *)n); + } +} + +void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, + struct nlpids *nlpids) +{ + tlvs->protocols_supported.count = nlpids->count; + XFREE(MTYPE_ISIS_TLV, tlvs->protocols_supported.protocols); + if (nlpids->count) { + tlvs->protocols_supported.protocols = + XCALLOC(MTYPE_ISIS_TLV, nlpids->count); + memcpy(tlvs->protocols_supported.protocols, nlpids->nlpids, + nlpids->count); + } else { + tlvs->protocols_supported.protocols = NULL; + } +} + +void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, + bool overload, bool attached) +{ + struct isis_mt_router_info *i = XCALLOC(MTYPE_ISIS_TLV, sizeof(*i)); + + i->overload = overload; + i->attached = attached; + i->mtid = mtid; + append_item(&tlvs->mt_router_info, (struct isis_item *)i); +} + +void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr) +{ + struct isis_ipv4_address *a = XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); + a->addr = *addr; + append_item(&tlvs->ipv4_address, (struct isis_item *)a); +} + + +void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, + struct list *addresses) +{ + struct listnode *node; + struct prefix_ipv4 *ip_addr; + unsigned int addr_count = 0; + + for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) { + isis_tlvs_add_ipv4_address(tlvs, &ip_addr->prefix); + addr_count++; + if (addr_count >= 63) + break; + } +} + +void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs, + struct list *addresses) +{ + struct listnode *node; + struct prefix_ipv6 *ip_addr; + unsigned int addr_count = 0; + + for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) { + if (addr_count >= 15) + break; + + struct isis_ipv6_address *a = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); + + a->addr = ip_addr->prefix; + append_item(&tlvs->ipv6_address, (struct isis_item *)a); + addr_count++; + } +} + +void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs, + struct list *addresses) +{ + struct listnode *node; + struct prefix_ipv6 *ip_addr; + unsigned int addr_count = 0; + + for (ALL_LIST_ELEMENTS_RO(addresses, node, ip_addr)) { + if (addr_count >= 15) + break; + + struct isis_ipv6_address *a = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*a)); + + a->addr = ip_addr->prefix; + append_item(&tlvs->global_ipv6_address, (struct isis_item *)a); + addr_count++; + } +} + +typedef bool (*auth_validator_func)(struct isis_passwd *passwd, + struct stream *stream, + struct isis_auth *auth, bool is_lsp); + +static bool auth_validator_cleartxt(struct isis_passwd *passwd, + struct stream *stream, + struct isis_auth *auth, bool is_lsp) +{ + return (auth->length == passwd->len + && !memcmp(auth->value, passwd->passwd, passwd->len)); +} + +static bool auth_validator_hmac_md5(struct isis_passwd *passwd, + struct stream *stream, + struct isis_auth *auth, bool is_lsp) +{ + uint8_t digest[16]; + uint16_t checksum; + uint16_t rem_lifetime; + + if (is_lsp) + safe_auth_md5(stream, &checksum, &rem_lifetime); + + memset(STREAM_DATA(stream) + auth->offset, 0, 16); +#ifdef CRYPTO_OPENSSL + uint8_t *result = (uint8_t *)HMAC(EVP_md5(), passwd->passwd, + passwd->len, STREAM_DATA(stream), + stream_get_endp(stream), NULL, NULL); + + memcpy(digest, result, 16); +#elif CRYPTO_INTERNAL + hmac_md5(STREAM_DATA(stream), stream_get_endp(stream), passwd->passwd, + passwd->len, digest); +#endif + memcpy(STREAM_DATA(stream) + auth->offset, auth->value, 16); + + bool rv = !memcmp(digest, auth->value, 16); + + if (is_lsp) + restore_auth_md5(stream, checksum, rem_lifetime); + + return rv; +} + +static const auth_validator_func auth_validators[] = { + [ISIS_PASSWD_TYPE_CLEARTXT] = auth_validator_cleartxt, + [ISIS_PASSWD_TYPE_HMAC_MD5] = auth_validator_hmac_md5, +}; + +int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, + struct stream *stream, bool is_lsp) +{ + /* If no auth is set, always pass authentication */ + if (!passwd->type) + return ISIS_AUTH_OK; + + /* If we don't known how to validate the auth, return invalid */ + if (passwd->type >= array_size(auth_validators) + || !auth_validators[passwd->type]) + return ISIS_AUTH_NO_VALIDATOR; + + struct isis_auth *auth_head = (struct isis_auth *)tlvs->isis_auth.head; + struct isis_auth *auth; + for (auth = auth_head; auth; auth = auth->next) { + if (auth->type == passwd->type) + break; + } + + /* If matching auth TLV could not be found, return invalid */ + if (!auth) + return ISIS_AUTH_TYPE_FAILURE; + + + /* Perform validation and return result */ + if (auth_validators[passwd->type](passwd, stream, auth, is_lsp)) + return ISIS_AUTH_OK; + else + return ISIS_AUTH_FAILURE; +} + +bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, + struct list *addresses) +{ + struct isis_area_address *addr_head; + + addr_head = (struct isis_area_address *)tlvs->area_addresses.head; + for (struct isis_area_address *addr = addr_head; addr; + addr = addr->next) { + struct listnode *node; + struct iso_address *a; + + for (ALL_LIST_ELEMENTS_RO(addresses, node, a)) { + if (a->addr_len == addr->len + && !memcmp(a->area_addr, addr->addr, addr->len)) + return true; + } + } + + return false; +} + +static void tlvs_area_addresses_to_adj(struct isis_tlvs *tlvs, + struct isis_adjacency *adj, + bool *changed) +{ + if (adj->area_address_count != tlvs->area_addresses.count) { + uint32_t oc = adj->area_address_count; + + *changed = true; + adj->area_address_count = tlvs->area_addresses.count; + adj->area_addresses = XREALLOC( + MTYPE_ISIS_ADJACENCY_INFO, adj->area_addresses, + adj->area_address_count * sizeof(*adj->area_addresses)); + + for (; oc < adj->area_address_count; oc++) { + adj->area_addresses[oc].addr_len = 0; + memset(&adj->area_addresses[oc].area_addr, 0, + sizeof(adj->area_addresses[oc].area_addr)); + } + } + + struct isis_area_address *addr = NULL; + for (unsigned int i = 0; i < tlvs->area_addresses.count; i++) { + if (!addr) + addr = (struct isis_area_address *) + tlvs->area_addresses.head; + else + addr = addr->next; + + if (adj->area_addresses[i].addr_len == addr->len + && !memcmp(adj->area_addresses[i].area_addr, addr->addr, + addr->len)) { + continue; + } + + *changed = true; + adj->area_addresses[i].addr_len = addr->len; + memcpy(adj->area_addresses[i].area_addr, addr->addr, addr->len); + } +} + +static void tlvs_protocols_supported_to_adj(struct isis_tlvs *tlvs, + struct isis_adjacency *adj, + bool *changed) +{ + bool ipv4_supported = false, ipv6_supported = false; + + for (uint8_t i = 0; i < tlvs->protocols_supported.count; i++) { + if (tlvs->protocols_supported.protocols[i] == NLPID_IP) + ipv4_supported = true; + if (tlvs->protocols_supported.protocols[i] == NLPID_IPV6) + ipv6_supported = true; + } + + struct nlpids reduced = {}; + + if (ipv4_supported && ipv6_supported) { + reduced.count = 2; + reduced.nlpids[0] = NLPID_IP; + reduced.nlpids[1] = NLPID_IPV6; + } else if (ipv4_supported) { + reduced.count = 1; + reduced.nlpids[0] = NLPID_IP; + } else if (ipv6_supported) { + reduced.count = 1; + reduced.nlpids[0] = NLPID_IPV6; + } else { + reduced.count = 0; + } + + if (adj->nlpids.count == reduced.count + && !memcmp(adj->nlpids.nlpids, reduced.nlpids, reduced.count)) + return; + + *changed = true; + adj->nlpids.count = reduced.count; + memcpy(adj->nlpids.nlpids, reduced.nlpids, reduced.count); +} + +DEFINE_HOOK(isis_adj_ip_enabled_hook, + (struct isis_adjacency * adj, int family, bool global), + (adj, family, global)); +DEFINE_HOOK(isis_adj_ip_disabled_hook, + (struct isis_adjacency * adj, int family, bool global), + (adj, family, global)); + +static void tlvs_ipv4_addresses_to_adj(struct isis_tlvs *tlvs, + struct isis_adjacency *adj, + bool *changed) +{ + bool ipv4_enabled = false; + + if (adj->ipv4_address_count == 0 && tlvs->ipv4_address.count > 0) + ipv4_enabled = true; + else if (adj->ipv4_address_count > 0 && tlvs->ipv4_address.count == 0) + hook_call(isis_adj_ip_disabled_hook, adj, AF_INET, false); + + if (adj->ipv4_address_count != tlvs->ipv4_address.count) { + uint32_t oc = adj->ipv4_address_count; + + *changed = true; + adj->ipv4_address_count = tlvs->ipv4_address.count; + adj->ipv4_addresses = XREALLOC( + MTYPE_ISIS_ADJACENCY_INFO, adj->ipv4_addresses, + adj->ipv4_address_count * sizeof(*adj->ipv4_addresses)); + + for (; oc < adj->ipv4_address_count; oc++) { + memset(&adj->ipv4_addresses[oc], 0, + sizeof(adj->ipv4_addresses[oc])); + } + } + + struct isis_ipv4_address *addr = NULL; + for (unsigned int i = 0; i < tlvs->ipv4_address.count; i++) { + if (!addr) + addr = (struct isis_ipv4_address *) + tlvs->ipv4_address.head; + else + addr = addr->next; + + if (!memcmp(&adj->ipv4_addresses[i], &addr->addr, + sizeof(addr->addr))) + continue; + + *changed = true; + adj->ipv4_addresses[i] = addr->addr; + } + + if (ipv4_enabled) + hook_call(isis_adj_ip_enabled_hook, adj, AF_INET, false); +} + +static void tlvs_ipv6_addresses_to_adj(struct isis_tlvs *tlvs, + struct isis_adjacency *adj, + bool *changed) +{ + bool ipv6_enabled = false; + + if (adj->ll_ipv6_count == 0 && tlvs->ipv6_address.count > 0) + ipv6_enabled = true; + else if (adj->ll_ipv6_count > 0 && tlvs->ipv6_address.count == 0) + hook_call(isis_adj_ip_disabled_hook, adj, AF_INET6, false); + + if (adj->ll_ipv6_count != tlvs->ipv6_address.count) { + uint32_t oc = adj->ll_ipv6_count; + + *changed = true; + adj->ll_ipv6_count = tlvs->ipv6_address.count; + adj->ll_ipv6_addrs = XREALLOC( + MTYPE_ISIS_ADJACENCY_INFO, adj->ll_ipv6_addrs, + adj->ll_ipv6_count * sizeof(*adj->ll_ipv6_addrs)); + + for (; oc < adj->ll_ipv6_count; oc++) { + memset(&adj->ll_ipv6_addrs[oc], 0, + sizeof(adj->ll_ipv6_addrs[oc])); + } + } + + struct isis_ipv6_address *addr = NULL; + for (unsigned int i = 0; i < tlvs->ipv6_address.count; i++) { + if (!addr) + addr = (struct isis_ipv6_address *) + tlvs->ipv6_address.head; + else + addr = addr->next; + + if (!memcmp(&adj->ll_ipv6_addrs[i], &addr->addr, + sizeof(addr->addr))) + continue; + + *changed = true; + adj->ll_ipv6_addrs[i] = addr->addr; + } + + if (ipv6_enabled) + hook_call(isis_adj_ip_enabled_hook, adj, AF_INET6, false); +} + + +static void tlvs_global_ipv6_addresses_to_adj(struct isis_tlvs *tlvs, + struct isis_adjacency *adj, + bool *changed) +{ + bool global_ipv6_enabled = false; + + if (adj->global_ipv6_count == 0 && tlvs->global_ipv6_address.count > 0) + global_ipv6_enabled = true; + else if (adj->global_ipv6_count > 0 + && tlvs->global_ipv6_address.count == 0) + hook_call(isis_adj_ip_disabled_hook, adj, AF_INET6, true); + + if (adj->global_ipv6_count != tlvs->global_ipv6_address.count) { + uint32_t oc = adj->global_ipv6_count; + + *changed = true; + adj->global_ipv6_count = tlvs->global_ipv6_address.count; + adj->global_ipv6_addrs = XREALLOC( + MTYPE_ISIS_ADJACENCY_INFO, adj->global_ipv6_addrs, + adj->global_ipv6_count + * sizeof(*adj->global_ipv6_addrs)); + + for (; oc < adj->global_ipv6_count; oc++) { + memset(&adj->global_ipv6_addrs[oc], 0, + sizeof(adj->global_ipv6_addrs[oc])); + } + } + + struct isis_ipv6_address *addr = NULL; + for (unsigned int i = 0; i < tlvs->global_ipv6_address.count; i++) { + if (!addr) + addr = (struct isis_ipv6_address *) + tlvs->global_ipv6_address.head; + else + addr = addr->next; + + if (!memcmp(&adj->global_ipv6_addrs[i], &addr->addr, + sizeof(addr->addr))) + continue; + + *changed = true; + adj->global_ipv6_addrs[i] = addr->addr; + } + + if (global_ipv6_enabled) + hook_call(isis_adj_ip_enabled_hook, adj, AF_INET6, true); +} + +void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, + bool *changed) +{ + *changed = false; + + tlvs_area_addresses_to_adj(tlvs, adj, changed); + tlvs_protocols_supported_to_adj(tlvs, adj, changed); + tlvs_ipv4_addresses_to_adj(tlvs, adj, changed); + tlvs_ipv6_addresses_to_adj(tlvs, adj, changed); + tlvs_global_ipv6_addresses_to_adj(tlvs, adj, changed); +} + +bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa) +{ + struct isis_lan_neighbor *ne_head; + + ne_head = (struct isis_lan_neighbor *)tlvs->lan_neighbor.head; + for (struct isis_lan_neighbor *ne = ne_head; ne; ne = ne->next) { + if (!memcmp(ne->mac, snpa, ETH_ALEN)) + return true; + } + + return false; +} + +void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp) +{ + struct isis_lsp_entry *entry = XCALLOC(MTYPE_ISIS_TLV, sizeof(*entry)); + + entry->rem_lifetime = lsp->hdr.rem_lifetime; + memcpy(entry->id, lsp->hdr.lsp_id, ISIS_SYS_ID_LEN + 2); + entry->checksum = lsp->hdr.checksum; + entry->seqno = lsp->hdr.seqno; + entry->lsp = lsp; + + append_item(&tlvs->lsp_entries, (struct isis_item *)entry); +} + +void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id, + uint8_t *stop_id, uint16_t num_lsps, + struct lspdb_head *head, + struct isis_lsp **last_lsp) +{ + struct isis_lsp searchfor; + struct isis_lsp *first, *lsp; + + memcpy(&searchfor.hdr.lsp_id, start_id, sizeof(searchfor.hdr.lsp_id)); + first = lspdb_find_gteq(head, &searchfor); + if (!first) + return; + + frr_each_from (lspdb, head, lsp, first) { + if (memcmp(lsp->hdr.lsp_id, stop_id, sizeof(lsp->hdr.lsp_id)) + > 0 || tlvs->lsp_entries.count == num_lsps) + break; + + isis_tlvs_add_lsp_entry(tlvs, lsp); + *last_lsp = lsp; + } +} + +void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs, + const char *hostname) +{ + XFREE(MTYPE_ISIS_TLV, tlvs->hostname); + if (hostname) + tlvs->hostname = XSTRDUP(MTYPE_ISIS_TLV, hostname); +} + +/* Init Router Capability TLV parameters */ +struct isis_router_cap *isis_tlvs_init_router_capability(struct isis_tlvs *tlvs) +{ + tlvs->router_cap = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->router_cap)); + + /* init SR algo list content to the default value */ + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + tlvs->router_cap->algo[i] = SR_ALGORITHM_UNSET; + + return tlvs->router_cap; +} + +#ifndef FABRICD +void isis_tlvs_set_router_capability_fad(struct isis_tlvs *tlvs, + struct flex_algo *fa, int algorithm, + uint8_t *sysid) +{ + struct isis_router_cap_fad *rcap_fad; + + assert(tlvs->router_cap); + + rcap_fad = tlvs->router_cap->fads[algorithm]; + + if (!rcap_fad) + rcap_fad = XCALLOC(MTYPE_ISIS_TLV, + sizeof(struct isis_router_cap_fad)); + + memset(rcap_fad->sysid, 0, ISIS_SYS_ID_LEN + 2); + memcpy(rcap_fad->sysid, sysid, ISIS_SYS_ID_LEN); + + memcpy(&rcap_fad->fad, fa, sizeof(struct flex_algo)); + + rcap_fad->fad.admin_group_exclude_any.bitmap.data = NULL; + rcap_fad->fad.admin_group_include_any.bitmap.data = NULL; + rcap_fad->fad.admin_group_include_all.bitmap.data = NULL; + + admin_group_copy(&rcap_fad->fad.admin_group_exclude_any, + &fa->admin_group_exclude_any); + admin_group_copy(&rcap_fad->fad.admin_group_include_any, + &fa->admin_group_include_any); + admin_group_copy(&rcap_fad->fad.admin_group_include_all, + &fa->admin_group_include_all); + + tlvs->router_cap->fads[algorithm] = rcap_fad; +} +#endif /* ifndef FABRICD */ + +int isis_tlvs_sr_algo_count(const struct isis_router_cap *cap) +{ + int count = 0; + + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) + if (cap->algo[i] != SR_ALGORITHM_UNSET) + count++; + return count; +} + +void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs, + const struct in_addr *id) +{ + XFREE(MTYPE_ISIS_TLV, tlvs->te_router_id); + if (!id) + return; + tlvs->te_router_id = XCALLOC(MTYPE_ISIS_TLV, sizeof(*id)); + memcpy(tlvs->te_router_id, id, sizeof(*id)); +} + +void isis_tlvs_set_te_router_id_ipv6(struct isis_tlvs *tlvs, + const struct in6_addr *id) +{ + XFREE(MTYPE_ISIS_TLV, tlvs->te_router_id_ipv6); + if (!id) + return; + tlvs->te_router_id_ipv6 = XCALLOC(MTYPE_ISIS_TLV, sizeof(*id)); + memcpy(tlvs->te_router_id_ipv6, id, sizeof(*id)); +} + +void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs, + struct prefix_ipv4 *dest, uint8_t metric) +{ + struct isis_oldstyle_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); + + r->metric = metric; + memcpy(&r->prefix, dest, sizeof(*dest)); + apply_mask_ipv4(&r->prefix); + append_item(&tlvs->oldstyle_ip_reach, (struct isis_item *)r); +} + +/* Add IS-IS SR Adjacency-SID subTLVs */ +void isis_tlvs_add_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_adj_sid *adj) +{ + append_item(&exts->adj_sid, (struct isis_item *)adj); + SET_SUBTLV(exts, EXT_ADJ_SID); +} + +/* Delete IS-IS SR Adjacency-SID subTLVs */ +void isis_tlvs_del_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_adj_sid *adj) +{ + delete_item(&exts->adj_sid, (struct isis_item *)adj); + XFREE(MTYPE_ISIS_SUBTLV, adj); + if (exts->adj_sid.count == 0) + UNSET_SUBTLV(exts, EXT_ADJ_SID); +} + +/* Add IS-IS SR LAN-Adjacency-SID subTLVs */ +void isis_tlvs_add_lan_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_lan_adj_sid *lan) +{ + append_item(&exts->lan_sid, (struct isis_item *)lan); + SET_SUBTLV(exts, EXT_LAN_ADJ_SID); +} + +/* Delete IS-IS SR LAN-Adjacency-SID subTLVs */ +void isis_tlvs_del_lan_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_lan_adj_sid *lan) +{ + delete_item(&exts->lan_sid, (struct isis_item *)lan); + XFREE(MTYPE_ISIS_SUBTLV, lan); + if (exts->lan_sid.count == 0) + UNSET_SUBTLV(exts, EXT_LAN_ADJ_SID); +} + +/* Add IS-IS SRv6 End.X SID subTLVs */ +void isis_tlvs_add_srv6_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_endx_sid_subtlv *adj) +{ + append_item(&exts->srv6_endx_sid, (struct isis_item *)adj); + SET_SUBTLV(exts, EXT_SRV6_ENDX_SID); +} + +/* Delete IS-IS SRv6 End.X SID subTLVs */ +void isis_tlvs_del_srv6_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_endx_sid_subtlv *adj) +{ + isis_free_subsubtlvs(adj->subsubtlvs); + delete_item(&exts->srv6_endx_sid, (struct isis_item *)adj); + XFREE(MTYPE_ISIS_SUBTLV, adj); + if (exts->srv6_endx_sid.count == 0) + UNSET_SUBTLV(exts, EXT_SRV6_ENDX_SID); +} + +/* Add IS-IS SRv6 LAN End.X SID subTLVs */ +void isis_tlvs_add_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_lan_endx_sid_subtlv *lan) +{ + append_item(&exts->srv6_lan_endx_sid, (struct isis_item *)lan); + SET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID); +} + +/* Delete IS-IS SRv6 LAN End.X SID subTLVs */ +void isis_tlvs_del_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_lan_endx_sid_subtlv *lan) +{ + isis_free_subsubtlvs(lan->subsubtlvs); + delete_item(&exts->srv6_lan_endx_sid, (struct isis_item *)lan); + XFREE(MTYPE_ISIS_SUBTLV, lan); + if (exts->srv6_lan_endx_sid.count == 0) + UNSET_SUBTLV(exts, EXT_SRV6_LAN_ENDX_SID); +} + +void isis_tlvs_del_asla_flex_algo(struct isis_ext_subtlvs *ext, + struct isis_asla_subtlvs *asla) +{ + admin_group_term(&asla->ext_admin_group); + listnode_delete(ext->aslas, asla); + XFREE(MTYPE_ISIS_SUBTLV, asla); +} + +struct isis_asla_subtlvs * +isis_tlvs_find_alloc_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps) +{ + struct isis_asla_subtlvs *asla; + struct listnode *node; + + if (!list_isempty(ext->aslas)) { + for (ALL_LIST_ELEMENTS_RO(ext->aslas, node, asla)) { + if (CHECK_FLAG(asla->standard_apps, standard_apps)) + return asla; + } + } + + asla = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(struct isis_asla_subtlvs)); + admin_group_init(&asla->ext_admin_group); + SET_FLAG(asla->standard_apps, standard_apps); + SET_FLAG(asla->user_def_apps, standard_apps); + asla->standard_apps_length = ASLA_APP_IDENTIFIER_BIT_LENGTH; + asla->user_def_apps_length = ASLA_APP_IDENTIFIER_BIT_LENGTH; + + listnode_add(ext->aslas, asla); + return asla; +} + +void isis_tlvs_free_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps) +{ + struct isis_asla_subtlvs *asla; + struct listnode *node; + + if (!ext) + return; + + for (ALL_LIST_ELEMENTS_RO(ext->aslas, node, asla)) { + if (!CHECK_FLAG(asla->standard_apps, standard_apps)) + continue; + isis_tlvs_del_asla_flex_algo(ext, asla); + break; + } +} + +void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, + struct prefix_ipv4 *dest, uint32_t metric, + bool external, + struct sr_prefix_cfg **pcfgs) +{ + struct isis_extended_ip_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); + + r->metric = metric; + memcpy(&r->prefix, dest, sizeof(*dest)); + apply_mask_ipv4(&r->prefix); + + if (pcfgs) { + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_prefix_sid *psid; + struct sr_prefix_cfg *pcfg = pcfgs[i]; + + if (!pcfg) + continue; + + psid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid)); + isis_sr_prefix_cfg2subtlv(pcfg, external, psid); + + if (!r->subtlvs) + r->subtlvs = isis_alloc_subtlvs( + ISIS_CONTEXT_SUBTLV_IP_REACH); + append_item(&r->subtlvs->prefix_sids, + (struct isis_item *)psid); + } + } + + append_item(&tlvs->extended_ip_reach, (struct isis_item *)r); +} + +void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, + struct prefix_ipv6 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg **pcfgs) +{ + struct isis_ipv6_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); + + r->metric = metric; + memcpy(&r->prefix, dest, sizeof(*dest)); + apply_mask_ipv6(&r->prefix); + if (pcfgs) { + for (int i = 0; i < SR_ALGORITHM_COUNT; i++) { + struct isis_prefix_sid *psid; + struct sr_prefix_cfg *pcfg = pcfgs[i]; + + if (!pcfg) + continue; + + psid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*psid)); + isis_sr_prefix_cfg2subtlv(pcfg, external, psid); + + if (!r->subtlvs) + r->subtlvs = isis_alloc_subtlvs( + ISIS_CONTEXT_SUBTLV_IPV6_REACH); + append_item(&r->subtlvs->prefix_sids, + (struct isis_item *)psid); + } + } + + struct isis_item_list *l; + l = (mtid == ISIS_MT_IPV4_UNICAST) + ? &tlvs->ipv6_reach + : isis_get_mt_items(&tlvs->mt_ipv6_reach, mtid); + append_item(l, (struct isis_item *)r); +} + +void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, + struct prefix_ipv6 *dest, + struct prefix_ipv6 *src, + uint32_t metric) +{ + isis_tlvs_add_ipv6_reach(tlvs, mtid, dest, metric, false, NULL); + struct isis_item_list *l = isis_get_mt_items(&tlvs->mt_ipv6_reach, + mtid); + + struct isis_ipv6_reach *r = (struct isis_ipv6_reach*)last_item(l); + r->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_IPV6_REACH); + r->subtlvs->source_prefix = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*src)); + memcpy(r->subtlvs->source_prefix, src, sizeof(*src)); +} + +void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id, + uint8_t metric) +{ + struct isis_oldstyle_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); + + r->metric = metric; + memcpy(r->id, id, sizeof(r->id)); + append_item(&tlvs->oldstyle_reach, (struct isis_item *)r); +} + +void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid, + uint8_t *id, uint32_t metric, + struct isis_ext_subtlvs *exts) +{ + struct isis_extended_reach *r = XCALLOC(MTYPE_ISIS_TLV, sizeof(*r)); + + memcpy(r->id, id, sizeof(r->id)); + r->metric = metric; + if (exts) + r->subtlvs = copy_item_ext_subtlvs(exts, mtid); + + struct isis_item_list *l; + if ((mtid == ISIS_MT_IPV4_UNICAST) || (mtid == ISIS_MT_DISABLE)) + l = &tlvs->extended_reach; + else + l = isis_get_mt_items(&tlvs->mt_reach, mtid); + append_item(l, (struct isis_item *)r); +} + +void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs, + enum isis_threeway_state state, + uint32_t local_circuit_id, + const uint8_t *neighbor_id, + uint32_t neighbor_circuit_id) +{ + assert(!tlvs->threeway_adj); + + tlvs->threeway_adj = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->threeway_adj)); + tlvs->threeway_adj->state = state; + tlvs->threeway_adj->local_circuit_id = local_circuit_id; + + if (neighbor_id) { + tlvs->threeway_adj->neighbor_set = true; + memcpy(tlvs->threeway_adj->neighbor_id, neighbor_id, 6); + tlvs->threeway_adj->neighbor_circuit_id = neighbor_circuit_id; + } +} + +void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier, + bool has_tier, bool is_leaf, bool is_spine, + bool is_backup) +{ + assert(!tlvs->spine_leaf); + + tlvs->spine_leaf = XCALLOC(MTYPE_ISIS_TLV, sizeof(*tlvs->spine_leaf)); + + if (has_tier) { + tlvs->spine_leaf->tier = tier; + } + + tlvs->spine_leaf->has_tier = has_tier; + tlvs->spine_leaf->is_leaf = is_leaf; + tlvs->spine_leaf->is_spine = is_spine; + tlvs->spine_leaf->is_backup = is_backup; +} + +struct isis_mt_router_info * +isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid) +{ + if (!tlvs || tlvs->mt_router_info_empty) + return NULL; + + struct isis_mt_router_info *rv; + for (rv = (struct isis_mt_router_info *)tlvs->mt_router_info.head; rv; + rv = rv->next) { + if (rv->mtid == mtid) + return rv; + } + + return NULL; +} + +void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs, + const uint8_t *generator, + const uint8_t *sender) +{ + assert(!tlvs->purge_originator); + + tlvs->purge_originator = XCALLOC(MTYPE_ISIS_TLV, + sizeof(*tlvs->purge_originator)); + memcpy(tlvs->purge_originator->generator, generator, + sizeof(tlvs->purge_originator->generator)); + if (sender) { + tlvs->purge_originator->sender_set = true; + memcpy(tlvs->purge_originator->sender, sender, + sizeof(tlvs->purge_originator->sender)); + } +} + +/* Set SRv6 SID Structure Sub-Sub-TLV parameters */ +void isis_subsubtlvs_set_srv6_sid_structure(struct isis_subsubtlvs *subsubtlvs, + struct isis_srv6_sid *sid) +{ + assert(!subsubtlvs->srv6_sid_structure); + + subsubtlvs->srv6_sid_structure = XCALLOC( + MTYPE_ISIS_SUBSUBTLV, sizeof(*subsubtlvs->srv6_sid_structure)); + + isis_srv6_sid_structure2subsubtlv(sid, subsubtlvs->srv6_sid_structure); +} + +/* Add an SRv6 End SID to the SRv6 End SID Sub-TLV */ +void isis_subtlvs_add_srv6_end_sid(struct isis_subtlvs *subtlvs, + struct isis_srv6_sid *sid) +{ + struct isis_srv6_end_sid_subtlv *sid_subtlv; + + if (!sid) + return; + + /* The SRv6 End SID Sub-TLV advertises SRv6 SIDs with Endpoint behaviors + * that do not require a particular neighbor in order to be correctly + * applied (e.g. End, End.DT6, ...). Before proceeding, let's make sure + * we are encoding one of the supported behaviors. */ + if (sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT6 && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT4 && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT46 && + sid->behavior != SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID) + return; + + /* Allocate memory for the Sub-TLV */ + sid_subtlv = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*sid_subtlv)); + + /* Fill in the SRv6 End SID Sub-TLV according to the SRv6 SID + * configuration */ + isis_srv6_end_sid2subtlv(sid, sid_subtlv); + + /* Add the SRv6 SID Structure Sub-Sub-TLV */ + sid_subtlv->subsubtlvs = + isis_alloc_subsubtlvs(ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID); + isis_subsubtlvs_set_srv6_sid_structure(sid_subtlv->subsubtlvs, sid); + + /* Append the SRv6 End SID Sub-TLV to the Sub-TLVs list */ + append_item(&subtlvs->srv6_end_sids, (struct isis_item *)sid_subtlv); +} + +/* Add an SRv6 Locator to the SRv6 Locator TLV */ +void isis_tlvs_add_srv6_locator(struct isis_tlvs *tlvs, uint16_t mtid, + struct isis_srv6_locator *loc) +{ + bool subtlvs_present = false; + struct listnode *node; + struct isis_srv6_sid *sid; + struct isis_srv6_locator_tlv *loc_tlv = + XCALLOC(MTYPE_ISIS_TLV, sizeof(*loc_tlv)); + + /* Fill in the SRv6 Locator TLV according to the SRv6 Locator + * configuration */ + isis_srv6_locator2tlv(loc, loc_tlv); + + /* Add the SRv6 End SID Sub-TLVs */ + loc_tlv->subtlvs = isis_alloc_subtlvs(ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR); + for (ALL_LIST_ELEMENTS_RO(loc->srv6_sid, node, sid)) { + if (sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT6 || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT4 || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT46 || + sid->behavior == SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID) { + isis_subtlvs_add_srv6_end_sid(loc_tlv->subtlvs, sid); + subtlvs_present = true; + } + } + + if (!subtlvs_present) { + isis_free_subtlvs(loc_tlv->subtlvs); + loc_tlv->subtlvs = NULL; + } + + /* Append the SRv6 Locator TLV to the TLVs list */ + struct isis_item_list *l; + l = isis_get_mt_items(&tlvs->srv6_locator, mtid); + append_item(l, (struct isis_item *)loc_tlv); +} diff --git a/isisd/isis_tlvs.h b/isisd/isis_tlvs.h new file mode 100644 index 0000000..c64bbf7 --- /dev/null +++ b/isisd/isis_tlvs.h @@ -0,0 +1,904 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS TLV Serializer/Deserializer + * + * Copyright (C) 2015,2017 Christian Franke + + * Copyright (C) 2019 Olivier Dugeon - Orange Labs (for TE and SR) + * + * Copyright (C) 2023 Carmine Scarpitta - University of Rome Tor Vergata + * (for IS-IS Extensions to Support SRv6 as per RFC 9352) + */ +#ifndef ISIS_TLVS_H +#define ISIS_TLVS_H + +#include "segment_routing.h" +#include "openbsd-tree.h" +#include "prefix.h" +#include "flex_algo.h" +#include "affinitymap.h" + + +#include "lib/srv6.h" + +DECLARE_MTYPE(ISIS_SUBTLV); +DECLARE_MTYPE(ISIS_SUBSUBTLV); + +struct lspdb_head; +struct sr_prefix_cfg; +struct isis_srv6_sid; +struct isis_srv6_locator; + +struct isis_area_address { + struct isis_area_address *next; + + uint8_t addr[20]; + uint8_t len; +}; + +#define ISIS_WIDE_METRIC_INFINITY 0xFFFFFE +#define ISIS_NARROW_METRIC_INFINITY 62 + +struct isis_oldstyle_reach { + struct isis_oldstyle_reach *next; + + uint8_t id[7]; + uint8_t metric; +}; + +struct isis_oldstyle_ip_reach { + struct isis_oldstyle_ip_reach *next; + + uint8_t metric; + struct prefix_ipv4 prefix; +}; + +struct isis_lsp_entry { + struct isis_lsp_entry *next; + + uint16_t rem_lifetime; + uint8_t id[8]; + uint16_t checksum; + uint32_t seqno; + + struct isis_lsp *lsp; +}; + +struct isis_extended_reach { + struct isis_extended_reach *next; + + uint8_t id[7]; + uint32_t metric; + + struct isis_ext_subtlvs *subtlvs; +}; + +struct isis_extended_ip_reach { + struct isis_extended_ip_reach *next; + + uint32_t metric; + bool down; + struct prefix_ipv4 prefix; + + struct isis_subtlvs *subtlvs; +}; + +struct isis_ipv6_reach { + struct isis_ipv6_reach *next; + + uint32_t metric; + bool down; + bool external; + + struct prefix_ipv6 prefix; + + struct isis_subtlvs *subtlvs; +}; + +struct isis_protocols_supported { + uint8_t count; + uint8_t *protocols; +}; + +#define ISIS_TIER_UNDEFINED 15 + +struct isis_spine_leaf { + uint8_t tier; + + bool has_tier; + bool is_leaf; + bool is_spine; + bool is_backup; +}; + +enum isis_threeway_state { + ISIS_THREEWAY_DOWN = 2, + ISIS_THREEWAY_INITIALIZING = 1, + ISIS_THREEWAY_UP = 0, +}; + +struct isis_threeway_adj { + enum isis_threeway_state state; + uint32_t local_circuit_id; + bool neighbor_set; + uint8_t neighbor_id[6]; + uint32_t neighbor_circuit_id; +}; + +/* Segment Routing subTLV's as per RFC8667 */ +#define ISIS_SUBTLV_SRGB_FLAG_I 0x80 +#define ISIS_SUBTLV_SRGB_FLAG_V 0x40 +#define IS_SR_IPV4(srgb) ((srgb)->flags & ISIS_SUBTLV_SRGB_FLAG_I) +#define IS_SR_IPV6(srgb) ((srgb)->flags & ISIS_SUBTLV_SRGB_FLAG_V) +#define SUBTLV_SR_BLOCK_SIZE 6 +#define SUBTLV_RANGE_INDEX_SIZE 10 +#define SUBTLV_RANGE_LABEL_SIZE 9 + +/* Structure aggregating SR Global (SRGB) or Local (SRLB) Block info */ +struct isis_sr_block { + uint8_t flags; + uint32_t range_size; + uint32_t lower_bound; +}; + +/* Prefix-SID sub-TLVs flags */ +#define ISIS_PREFIX_SID_READVERTISED 0x80 +#define ISIS_PREFIX_SID_NODE 0x40 +#define ISIS_PREFIX_SID_NO_PHP 0x20 +#define ISIS_PREFIX_SID_EXPLICIT_NULL 0x10 +#define ISIS_PREFIX_SID_VALUE 0x08 +#define ISIS_PREFIX_SID_LOCAL 0x04 + +struct isis_prefix_sid { + struct isis_prefix_sid *next; + + uint8_t flags; + uint8_t algorithm; + uint32_t value; +}; + +/* Adj-SID and LAN-Ajd-SID sub-TLVs flags */ +#define EXT_SUBTLV_LINK_ADJ_SID_FFLG 0x80 +#define EXT_SUBTLV_LINK_ADJ_SID_BFLG 0x40 +#define EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x20 +#define EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x10 +#define EXT_SUBTLV_LINK_ADJ_SID_SFLG 0x08 +#define EXT_SUBTLV_LINK_ADJ_SID_PFLG 0x04 + +struct isis_adj_sid { + struct isis_adj_sid *next; + + uint8_t family; + uint8_t flags; + uint8_t weight; + uint32_t sid; +}; + +struct isis_lan_adj_sid { + struct isis_lan_adj_sid *next; + + uint8_t family; + uint8_t flags; + uint8_t weight; + uint8_t neighbor_id[ISIS_SYS_ID_LEN]; + uint32_t sid; +}; + +/* RFC 4971 & RFC 7981 */ +#define ISIS_ROUTER_CAP_FLAG_S 0x01 +#define ISIS_ROUTER_CAP_FLAG_D 0x02 +#define ISIS_ROUTER_CAP_SIZE 5 + +#define MSD_TYPE_BASE_MPLS_IMPOSITION 0x01 +#define MSD_TLV_SIZE 2 + +#ifndef FABRICD +struct isis_router_cap_fad; +struct isis_router_cap_fad { + uint8_t sysid[ISIS_SYS_ID_LEN + 2]; + + struct flex_algo fad; +}; +#endif /* ifndef FABRICD */ + +/* SRv6 SID Structure Sub-Sub-TLV as per RFC 9352 section #9 */ +struct isis_srv6_sid_structure_subsubtlv { + uint8_t loc_block_len; + uint8_t loc_node_len; + uint8_t func_len; + uint8_t arg_len; +}; + +/* SRv6 End SID Sub-TLV as per RFC 9352 section #7.2 */ +struct isis_srv6_end_sid_subtlv { + struct isis_srv6_end_sid_subtlv *next; + + uint8_t flags; + enum srv6_endpoint_behavior_codepoint behavior; + struct in6_addr sid; + + struct isis_subsubtlvs *subsubtlvs; +}; + +/* SRv6 End.X SID and SRv6 LAN End.X SID sub-TLVs flags */ +#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_PFLG 0x20 +#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_SFLG 0x40 +#define EXT_SUBTLV_LINK_SRV6_ENDX_SID_BFLG 0x80 + +/* SRv6 End.X SID Sub-TLV as per RFC 9352 section #8.1 */ +struct isis_srv6_endx_sid_subtlv { + struct isis_srv6_endx_sid_subtlv *next; + + uint8_t flags; + uint8_t algorithm; + uint8_t weight; + enum srv6_endpoint_behavior_codepoint behavior; + struct in6_addr sid; + + struct isis_subsubtlvs *subsubtlvs; +}; + +/* SRv6 End.X SID Sub-TLV as per RFC 9352 section #8.2 */ +struct isis_srv6_lan_endx_sid_subtlv { + struct isis_srv6_lan_endx_sid_subtlv *next; + + uint8_t neighbor_id[ISIS_SYS_ID_LEN]; + uint8_t flags; + uint8_t algorithm; + uint8_t weight; + enum srv6_endpoint_behavior_codepoint behavior; + struct in6_addr sid; + + struct isis_subsubtlvs *subsubtlvs; +}; + +/* RFC 9352 section 7.1 */ +struct isis_srv6_locator_tlv { + struct isis_srv6_locator_tlv *next; + + uint32_t metric; + + uint8_t flags; +#define ISIS_TLV_SRV6_LOCATOR_FLAG_D 1 << 7 + + uint8_t algorithm; + struct prefix_ipv6 prefix; + + struct isis_subtlvs *subtlvs; +}; + +#define ISIS_SRV6_LOCATOR_HDR_SIZE 22 + +/* Maximum SRv6 SID Depths (MSD) as per RFC 9352 section #4 */ +struct isis_srv6_msd { + /* RFC 9352 section #4.1 */ + uint8_t max_seg_left_msd; + /* RFC 9352 section #4.2 */ + uint8_t max_end_pop_msd; + /* RFC 9352 section #4.3 */ + uint8_t max_h_encaps_msd; + /* RFC 9352 section #4.4 */ + uint8_t max_end_d_msd; +}; + +/* SRv6 Capabilities as per RFC 9352 section #2 */ +struct isis_srv6_cap { + bool is_srv6_capable; + + uint16_t flags; +#define ISIS_SUBTLV_SRV6_FLAG_O 0x4000 +#define SUPPORTS_SRV6_OAM(srv6) \ + (CHECK_FLAG((srv6)->flags, ISIS_SUBTLV_SRV6_FLAG_O)) +}; + +struct isis_router_cap { + struct in_addr router_id; + uint8_t flags; + + /* RFC 8667 section #3 */ + struct isis_sr_block srgb; + struct isis_sr_block srlb; + uint8_t algo[SR_ALGORITHM_COUNT]; + /* RFC 8491 */ + uint8_t msd; + +#ifndef FABRICD + /* RFC9350 Flex-Algorithm */ + struct isis_router_cap_fad *fads[SR_ALGORITHM_COUNT]; +#endif /* ifndef FABRICD */ + + /* RFC 9352 section #2 */ + struct isis_srv6_cap srv6_cap; + + /* RFC 9352 section #4 */ + struct isis_srv6_msd srv6_msd; +}; + +struct isis_item { + struct isis_item *next; +}; + +struct isis_lan_neighbor { + struct isis_lan_neighbor *next; + + uint8_t mac[6]; +}; + +struct isis_ipv4_address { + struct isis_ipv4_address *next; + + struct in_addr addr; +}; + +struct isis_ipv6_address { + struct isis_ipv6_address *next; + + struct in6_addr addr; +}; + +struct isis_mt_router_info { + struct isis_mt_router_info *next; + + bool overload; + bool attached; + uint16_t mtid; +}; + +struct isis_auth { + struct isis_auth *next; + + uint8_t type; + uint8_t length; + uint8_t value[256]; + + uint8_t plength; + uint8_t passwd[256]; + + size_t offset; /* Only valid after packing */ +}; + +struct isis_item_list { + struct isis_item *head; + struct isis_item **tail; + + RB_ENTRY(isis_item_list) mt_tree; + uint16_t mtid; + unsigned int count; +}; + +struct isis_purge_originator { + bool sender_set; + + uint8_t generator[6]; + uint8_t sender[6]; +}; + +enum isis_auth_result { + ISIS_AUTH_OK = 0, + ISIS_AUTH_TYPE_FAILURE, + ISIS_AUTH_FAILURE, + ISIS_AUTH_NO_VALIDATOR, +}; + +RB_HEAD(isis_mt_item_list, isis_item_list); + +struct isis_item_list *isis_get_mt_items(struct isis_mt_item_list *m, + uint16_t mtid); +struct isis_item_list *isis_lookup_mt_items(struct isis_mt_item_list *m, + uint16_t mtid); + +struct isis_tlvs { + struct isis_item_list isis_auth; + struct isis_purge_originator *purge_originator; + struct isis_item_list area_addresses; + struct isis_item_list oldstyle_reach; + struct isis_item_list lan_neighbor; + struct isis_item_list lsp_entries; + struct isis_item_list extended_reach; + struct isis_mt_item_list mt_reach; + struct isis_item_list oldstyle_ip_reach; + struct isis_protocols_supported protocols_supported; + struct isis_item_list oldstyle_ip_reach_ext; + struct isis_item_list ipv4_address; + struct isis_item_list ipv6_address; + struct isis_item_list global_ipv6_address; + struct isis_item_list mt_router_info; + bool mt_router_info_empty; + struct in_addr *te_router_id; + struct in6_addr *te_router_id_ipv6; + struct isis_item_list extended_ip_reach; + struct isis_mt_item_list mt_ip_reach; + char *hostname; + struct isis_item_list ipv6_reach; + struct isis_mt_item_list mt_ipv6_reach; + struct isis_threeway_adj *threeway_adj; + struct isis_router_cap *router_cap; + struct isis_spine_leaf *spine_leaf; + struct isis_mt_item_list srv6_locator; +}; + +enum isis_tlv_context { + ISIS_CONTEXT_LSP, + ISIS_CONTEXT_SUBTLV_NE_REACH, + ISIS_CONTEXT_SUBTLV_IP_REACH, + ISIS_CONTEXT_SUBTLV_IPV6_REACH, + ISIS_CONTEXT_SUBTLV_SRV6_LOCATOR, + ISIS_CONTEXT_SUBSUBTLV_SRV6_END_SID, + ISIS_CONTEXT_SUBSUBTLV_SRV6_ENDX_SID, + ISIS_CONTEXT_SUBSUBTLV_SRV6_LAN_ENDX_SID, + ISIS_CONTEXT_MAX, +}; + +struct isis_subtlvs { + enum isis_tlv_context context; + + /* draft-baker-ipv6-isis-dst-src-routing-06 */ + struct prefix_ipv6 *source_prefix; + /* RFC 8667 section #2.4 */ + struct isis_item_list prefix_sids; + + /* RFC 9352 section #7.2 */ + struct isis_item_list srv6_end_sids; +}; + +struct isis_subsubtlvs { + enum isis_tlv_context context; + + /* RFC 9352 section #9 */ + struct isis_srv6_sid_structure_subsubtlv *srv6_sid_structure; +}; + +enum isis_tlv_type { + /* TLVs code point */ + ISIS_TLV_AREA_ADDRESSES = 1, + ISIS_TLV_OLDSTYLE_REACH = 2, + ISIS_TLV_LAN_NEIGHBORS = 6, + ISIS_TLV_PADDING = 8, + ISIS_TLV_LSP_ENTRY = 9, + ISIS_TLV_AUTH = 10, + ISIS_TLV_PURGE_ORIGINATOR = 13, + ISIS_TLV_EXTENDED_REACH = 22, + + ISIS_TLV_SRV6_LOCATOR = 27, + + ISIS_TLV_OLDSTYLE_IP_REACH = 128, + ISIS_TLV_PROTOCOLS_SUPPORTED = 129, + ISIS_TLV_OLDSTYLE_IP_REACH_EXT = 130, + ISIS_TLV_IPV4_ADDRESS = 132, + ISIS_TLV_TE_ROUTER_ID = 134, + ISIS_TLV_EXTENDED_IP_REACH = 135, + ISIS_TLV_DYNAMIC_HOSTNAME = 137, + ISIS_TLV_TE_ROUTER_ID_IPV6 = 140, + ISIS_TLV_SPINE_LEAF_EXT = 150, + ISIS_TLV_MT_REACH = 222, + ISIS_TLV_MT_ROUTER_INFO = 229, + ISIS_TLV_IPV6_ADDRESS = 232, + ISIS_TLV_GLOBAL_IPV6_ADDRESS = 233, + ISIS_TLV_MT_IP_REACH = 235, + ISIS_TLV_IPV6_REACH = 236, + ISIS_TLV_MT_IPV6_REACH = 237, + ISIS_TLV_THREE_WAY_ADJ = 240, + ISIS_TLV_ROUTER_CAPABILITY = 242, + ISIS_TLV_MAX = 256, + + /* subTLVs code point */ + ISIS_SUBTLV_IPV6_SOURCE_PREFIX = 22, + + /* RFC 5305 & RFC 6119 */ + ISIS_SUBTLV_ADMIN_GRP = 3, + ISIS_SUBTLV_LOCAL_IPADDR = 6, + ISIS_SUBTLV_RMT_IPADDR = 8, + ISIS_SUBTLV_MAX_BW = 9, + ISIS_SUBTLV_MAX_RSV_BW = 10, + ISIS_SUBTLV_UNRSV_BW = 11, + ISIS_SUBTLV_LOCAL_IPADDR6 = 12, + ISIS_SUBTLV_RMT_IPADDR6 = 13, + ISIS_SUBTLV_TE_METRIC = 18, + + /* RFC 5307 */ + ISIS_SUBTLV_LLRI = 4, + + /* RFC 8491 */ + ISIS_SUBTLV_NODE_MSD = 23, + + /* RFC 5316 */ + ISIS_SUBTLV_RAS = 24, + ISIS_SUBTLV_RIP = 25, + + /* RFC 8667 section #4 IANA allocation */ + ISIS_SUBTLV_SID_LABEL = 1, + ISIS_SUBTLV_SID_LABEL_RANGE = 2, + ISIS_SUBTLV_ALGORITHM = 19, + ISIS_SUBTLV_SRLB = 22, + ISIS_SUBTLV_PREFIX_SID = 3, + ISIS_SUBTLV_ADJ_SID = 31, + ISIS_SUBTLV_LAN_ADJ_SID = 32, + + /* RFC 7810 */ + ISIS_SUBTLV_AV_DELAY = 33, + ISIS_SUBTLV_MM_DELAY = 34, + ISIS_SUBTLV_DELAY_VAR = 35, + ISIS_SUBTLV_PKT_LOSS = 36, + ISIS_SUBTLV_RES_BW = 37, + ISIS_SUBTLV_AVA_BW = 38, + ISIS_SUBTLV_USE_BW = 39, + + /* RFC 7308 */ + ISIS_SUBTLV_EXT_ADMIN_GRP = 14, + + /* RFC 8919 */ + ISIS_SUBTLV_ASLA = 16, + + /* draft-ietf-lsr-isis-srv6-extensions */ + ISIS_SUBTLV_SID_END = 5, + ISIS_SUBTLV_SID_END_X = 43, + + ISIS_SUBTLV_MAX = 40, + + /* RFC 9352 section #2 */ + ISIS_SUBTLV_SRV6_CAPABILITIES = 25, + /* RFC 9352 section #4.1 */ + ISIS_SUBTLV_SRV6_MAX_SL_MSD = 41, + /* RFC 9352 section #4.2 */ + ISIS_SUBTLV_SRV6_MAX_END_POP_MSD = 42, + /* RFC 9352 section #4.3 */ + ISIS_SUBTLV_SRV6_MAX_H_ENCAPS_MSD = 44, + /* RFC 9352 section #4.4 */ + ISIS_SUBTLV_SRV6_MAX_END_D_MSD = 45, + + ISIS_SUBTLV_SRV6_END_SID = 5, + ISIS_SUBTLV_SRV6_ENDX_SID = 43, + ISIS_SUBTLV_SRV6_LAN_ENDX_SID = 44, + + ISIS_SUBSUBTLV_SRV6_SID_STRUCTURE = 1, + + /* draft-ietf-lsr-isis-srv6-extensions */ + ISIS_SUBSUBTLV_SID_STRUCTURE = 1, + + ISIS_SUBSUBTLV_MAX = 256, +}; + +/* subTLVs size for TE and SR */ +enum ext_subtlv_size { + /* Sub-TLV Type and Length fields */ + ISIS_SUBTLV_TYPE_FIELD_SIZE = 1, + ISIS_SUBTLV_LENGTH_FIELD_SIZE = 1, + + /* RFC 5307 */ + ISIS_SUBTLV_LLRI_SIZE = 8, + + /* RFC 5305 & RFC 6119 */ + ISIS_SUBTLV_UNRSV_BW_SIZE = 32, + ISIS_SUBTLV_TE_METRIC_SIZE = 3, + ISIS_SUBTLV_IPV6_ADDR_SIZE = 16, + + /* RFC 8491 */ + ISIS_SUBTLV_NODE_MSD_SIZE = 2, + ISIS_SUBTLV_NODE_MSD_TYPE_SIZE = 1, + ISIS_SUBTLV_NODE_MSD_VALUE_SIZE = 1, + + /* RFC 8667 sections #2 & #3 */ + ISIS_SUBTLV_SID_LABEL_SIZE = 3, + ISIS_SUBTLV_SID_INDEX_SIZE = 4, + ISIS_SUBTLV_SID_LABEL_RANGE_SIZE = 9, + ISIS_SUBTLV_ALGORITHM_SIZE = 4, + ISIS_SUBTLV_ADJ_SID_SIZE = 5, + ISIS_SUBTLV_LAN_ADJ_SID_SIZE = 11, + ISIS_SUBTLV_PREFIX_SID_SIZE = 5, + + /* RFC 7810 */ + ISIS_SUBTLV_MM_DELAY_SIZE = 8, + + /* RFC9350 - Flex-Algorithm */ + ISIS_SUBTLV_FAD = 26, + ISIS_SUBTLV_FAD_MIN_SIZE = 4, + + ISIS_SUBTLV_HDR_SIZE = 2, + ISIS_SUBTLV_DEF_SIZE = 4, + + ISIS_SUBTLV_MAX_SIZE = 180, + + /* RFC 9352 sections #8.1 & #8.2 */ + ISIS_SUBTLV_SRV6_ENDX_SID_SIZE = 21, + ISIS_SUBTLV_SRV6_LAN_ENDX_SID_SIZE = 27, + + /* draft-ietf-lsr-isis-srv6-extensions */ + ISIS_SUBSUBTLV_SID_STRUCTURE_SIZE = 4, + + ISIS_SUBSUBTLV_HDR_SIZE = 2, + ISIS_SUBSUBTLV_MAX_SIZE = 180, + + /* RFC9350 - Flex-Algorithm */ + ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS_SIZE = 1, + + /* RFC 9352 section #2 */ + ISIS_SUBTLV_SRV6_CAPABILITIES_SIZE = 2, +}; + +enum ext_subsubtlv_types { + ISIS_SUBTLV_FAD_SUBSUBTLV_EXCAG = 1, + ISIS_SUBTLV_FAD_SUBSUBTLV_INCANYAG = 2, + ISIS_SUBTLV_FAD_SUBSUBTLV_INCALLAG = 3, + ISIS_SUBTLV_FAD_SUBSUBTLV_FLAGS = 4, + ISIS_SUBTLV_FAD_SUBSUBTLV_ESRLG = 5, +}; + +/* Macros to manage the optional presence of EXT subTLVs */ +#define SET_SUBTLV(s, t) ((s->status) |= (t)) +#define UNSET_SUBTLV(s, t) ((s->status) &= ~(t)) +#define IS_SUBTLV(s, t) (s->status & t) +#define RESET_SUBTLV(s) (s->status = 0) +#define NO_SUBTLV(s) (s->status == 0) + +#define EXT_DISABLE 0x000000 +#define EXT_ADM_GRP 0x000001 +#define EXT_LLRI 0x000002 +#define EXT_LOCAL_ADDR 0x000004 +#define EXT_NEIGH_ADDR 0x000008 +#define EXT_LOCAL_ADDR6 0x000010 +#define EXT_NEIGH_ADDR6 0x000020 +#define EXT_MAX_BW 0x000040 +#define EXT_MAX_RSV_BW 0x000080 +#define EXT_UNRSV_BW 0x000100 +#define EXT_TE_METRIC 0x000200 +#define EXT_RMT_AS 0x000400 +#define EXT_RMT_IP 0x000800 +#define EXT_ADJ_SID 0x001000 +#define EXT_LAN_ADJ_SID 0x002000 +#define EXT_DELAY 0x004000 +#define EXT_MM_DELAY 0x008000 +#define EXT_DELAY_VAR 0x010000 +#define EXT_PKT_LOSS 0x020000 +#define EXT_RES_BW 0x040000 +#define EXT_AVA_BW 0x080000 +#define EXT_USE_BW 0x100000 +#define EXT_EXTEND_ADM_GRP 0x200000 +#define EXT_SRV6_ENDX_SID 0x400000 +#define EXT_SRV6_LAN_ENDX_SID 0x800000 + +/* + * This structure groups all Extended IS Reachability subTLVs. + * + * Each bit of the status field indicates if a subTLVs is valid or not. + * SubTLVs values use following units: + * - Bandwidth in bytes/sec following IEEE format, + * - Delay in micro-seconds with only 24 bits significant + * - Packet Loss in percentage of total traffic with only 24 bits (2^24 - 2) + * + * For Delay and packet Loss, upper bit (A) indicates if the value is + * normal (0) or anomalous (1). + */ +#define IS_ANORMAL(v) (v & TE_EXT_ANORMAL) + +struct isis_ext_subtlvs { + + uint32_t status; + + uint32_t adm_group; /* Resource Class/Color - RFC 5305 */ + struct admin_group ext_admin_group; /* Res. Class/Color - RFC 7308 */ + /* Link Local/Remote Identifiers - RFC 5307 */ + uint32_t local_llri; + uint32_t remote_llri; + struct in_addr local_addr; /* Local IP Address - RFC 5305 */ + struct in_addr neigh_addr; /* Neighbor IP Address - RFC 5305 */ + struct in6_addr local_addr6; /* Local IPv6 Address - RFC 6119 */ + struct in6_addr neigh_addr6; /* Neighbor IPv6 Address - RFC 6119 */ + float max_bw; /* Maximum Bandwidth - RFC 5305 */ + float max_rsv_bw; /* Maximum Reservable Bandwidth - RFC 5305 */ + float unrsv_bw[8]; /* Unreserved Bandwidth - RFC 5305 */ + uint32_t te_metric; /* Traffic Engineering Metric - RFC 5305 */ + uint32_t remote_as; /* Remote AS Number sub-TLV - RFC5316 */ + struct in_addr remote_ip; /* IPv4 Remote ASBR ID Sub-TLV - RFC5316 */ + + uint32_t delay; /* Average Link Delay - RFC 8570 */ + uint32_t min_delay; /* Low Link Delay - RFC 8570 */ + uint32_t max_delay; /* High Link Delay - RFC 8570 */ + uint32_t delay_var; /* Link Delay Variation i.e. Jitter - RFC 8570 */ + uint32_t pkt_loss; /* Unidirectional Link Packet Loss - RFC 8570 */ + float res_bw; /* Unidirectional Residual Bandwidth - RFC 8570 */ + float ava_bw; /* Unidirectional Available Bandwidth - RFC 8570 */ + float use_bw; /* Unidirectional Utilized Bandwidth - RFC 8570 */ + + /* Segment Routing Adjacency & LAN Adjacency Segment ID */ + struct isis_item_list adj_sid; + struct isis_item_list lan_sid; + + struct list *aslas; + + /* SRv6 End.X & LAN End.X SID */ + struct isis_item_list srv6_endx_sid; + struct isis_item_list srv6_lan_endx_sid; +}; + +/* RFC 8919 */ +#define ISIS_SABM_FLAG_R 0x80 /* RSVP-TE */ +#define ISIS_SABM_FLAG_S 0x40 /* Segment Routing Policy */ +#define ISIS_SABM_FLAG_L 0x20 /* Loop-Free Alternate */ +#define ISIS_SABM_FLAG_X 0x10 /* Flex-Algorithm - RFC9350 */ + +#define ASLA_APP_IDENTIFIER_BIT_LENGTH 1 +#define ASLA_APP_IDENTIFIER_BIT_MAX_LENGTH 8 +#define ASLA_LEGACY_FLAG 0x80 +#define ASLA_APPS_LENGTH_MASK 0x7f + +struct isis_asla_subtlvs { + uint32_t status; + + /* Application Specific Link Attribute - RFC 8919 */ + bool legacy; /* L-Flag */ + uint8_t standard_apps_length; + uint8_t user_def_apps_length; + uint8_t standard_apps; + uint8_t user_def_apps; + + /* Sub-TLV list - rfc8919 section-3.1 */ + uint32_t admin_group; + struct admin_group ext_admin_group; /* Res. Class/Color - RFC 7308 */ + float max_bw; /* Maximum Bandwidth - RFC 5305 */ + float max_rsv_bw; /* Maximum Reservable Bandwidth - RFC 5305 */ + float unrsv_bw[8]; /* Unreserved Bandwidth - RFC 5305 */ + uint32_t te_metric; /* Traffic Engineering Metric - RFC 5305 */ + uint32_t delay; /* Average Link Delay - RFC 8570 */ + uint32_t min_delay; /* Low Link Delay - RFC 8570 */ + uint32_t max_delay; /* High Link Delay - RFC 8570 */ + uint32_t delay_var; /* Link Delay Variation i.e. Jitter - RFC 8570 */ + uint32_t pkt_loss; /* Unidirectional Link Packet Loss - RFC 8570 */ + float res_bw; /* Unidirectional Residual Bandwidth - RFC 8570 */ + float ava_bw; /* Unidirectional Available Bandwidth - RFC 8570 */ + float use_bw; /* Unidirectional Utilized Bandwidth - RFC 8570 */ +}; + +#define IS_COMPAT_MT_TLV(tlv_type) \ + ((tlv_type == ISIS_TLV_MT_REACH) || (tlv_type == ISIS_TLV_MT_IP_REACH) \ + || (tlv_type == ISIS_TLV_MT_IPV6_REACH)) + +struct stream; +int isis_pack_tlvs(struct isis_tlvs *tlvs, struct stream *stream, + size_t len_pointer, bool pad, bool is_lsp); +void isis_free_tlvs(struct isis_tlvs *tlvs); +struct isis_tlvs *isis_alloc_tlvs(void); +struct isis_subsubtlvs *isis_alloc_subsubtlvs(enum isis_tlv_context context); +int isis_unpack_tlvs(size_t avail_len, struct stream *stream, + struct isis_tlvs **dest, const char **error_log); +const char *isis_format_tlvs(struct isis_tlvs *tlvs, struct json_object *json); +struct isis_tlvs *isis_copy_tlvs(struct isis_tlvs *tlvs); +struct list *isis_fragment_tlvs(struct isis_tlvs *tlvs, size_t size); + +#define ISIS_EXTENDED_IP_REACH_DOWN 0x80 +#define ISIS_EXTENDED_IP_REACH_SUBTLV 0x40 + +#define ISIS_IPV6_REACH_DOWN 0x80 +#define ISIS_IPV6_REACH_EXTERNAL 0x40 +#define ISIS_IPV6_REACH_SUBTLV 0x20 + +#ifndef ISIS_MT_MASK +#define ISIS_MT_MASK 0x0fff +#define ISIS_MT_OL_MASK 0x8000 +#define ISIS_MT_AT_MASK 0x4000 +#endif + +/* RFC 8919 */ +#define ISIS_SABM_FLAG_R 0x80 /* RSVP-TE */ +#define ISIS_SABM_FLAG_S 0x40 /* Segment Routing Policy */ +#define ISIS_SABM_FLAG_L 0x20 /* Loop-Free Alternate */ +#define ISIS_SABM_FLAG_X 0x10 /* Flex-Algorithm - RFC9350 */ + +void isis_tlvs_add_auth(struct isis_tlvs *tlvs, struct isis_passwd *passwd); +void isis_tlvs_add_area_addresses(struct isis_tlvs *tlvs, + struct list *addresses); +void isis_tlvs_add_lan_neighbors(struct isis_tlvs *tlvs, + struct list *neighbors); +void isis_tlvs_set_protocols_supported(struct isis_tlvs *tlvs, + struct nlpids *nlpids); +void isis_tlvs_add_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid, + bool overload, bool attached); +void isis_tlvs_add_ipv4_address(struct isis_tlvs *tlvs, struct in_addr *addr); +void isis_tlvs_add_ipv4_addresses(struct isis_tlvs *tlvs, + struct list *addresses); +void isis_tlvs_add_ipv6_addresses(struct isis_tlvs *tlvs, + struct list *addresses); +void isis_tlvs_add_global_ipv6_addresses(struct isis_tlvs *tlvs, + struct list *addresses); +int isis_tlvs_auth_is_valid(struct isis_tlvs *tlvs, struct isis_passwd *passwd, + struct stream *stream, bool is_lsp); +bool isis_tlvs_area_addresses_match(struct isis_tlvs *tlvs, + struct list *addresses); +struct isis_adjacency; +void isis_tlvs_to_adj(struct isis_tlvs *tlvs, struct isis_adjacency *adj, + bool *changed); +bool isis_tlvs_own_snpa_found(struct isis_tlvs *tlvs, uint8_t *snpa); +void isis_tlvs_add_lsp_entry(struct isis_tlvs *tlvs, struct isis_lsp *lsp); +void isis_tlvs_add_csnp_entries(struct isis_tlvs *tlvs, uint8_t *start_id, + uint8_t *stop_id, uint16_t num_lsps, + struct lspdb_head *lspdb, + struct isis_lsp **last_lsp); +void isis_tlvs_set_dynamic_hostname(struct isis_tlvs *tlvs, + const char *hostname); +struct isis_router_cap * +isis_tlvs_init_router_capability(struct isis_tlvs *tlvs); + +struct isis_area; +struct isis_flex_algo; +void isis_tlvs_set_router_capability_fad(struct isis_tlvs *tlvs, + struct flex_algo *fa, int algorithm, + uint8_t *sysid); + +struct isis_area; + +int isis_tlvs_sr_algo_count(const struct isis_router_cap *cap); + +void isis_tlvs_set_te_router_id(struct isis_tlvs *tlvs, + const struct in_addr *id); +void isis_tlvs_set_te_router_id_ipv6(struct isis_tlvs *tlvs, + const struct in6_addr *id); +void isis_tlvs_add_oldstyle_ip_reach(struct isis_tlvs *tlvs, + struct prefix_ipv4 *dest, uint8_t metric); +void isis_tlvs_add_extended_ip_reach(struct isis_tlvs *tlvs, + struct prefix_ipv4 *dest, uint32_t metric, + bool external, + struct sr_prefix_cfg **pcfgs); +void isis_tlvs_add_ipv6_reach(struct isis_tlvs *tlvs, uint16_t mtid, + struct prefix_ipv6 *dest, uint32_t metric, + bool external, struct sr_prefix_cfg **pcfgs); +void isis_tlvs_add_ipv6_dstsrc_reach(struct isis_tlvs *tlvs, uint16_t mtid, + struct prefix_ipv6 *dest, + struct prefix_ipv6 *src, + uint32_t metric); +struct isis_ext_subtlvs *isis_alloc_ext_subtlvs(void); +void isis_del_ext_subtlvs(struct isis_ext_subtlvs *ext); +void isis_tlvs_add_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_adj_sid *adj); +void isis_tlvs_del_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_adj_sid *adj); +void isis_tlvs_add_lan_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_lan_adj_sid *lan); +void isis_tlvs_del_lan_adj_sid(struct isis_ext_subtlvs *exts, + struct isis_lan_adj_sid *lan); + +void isis_tlvs_del_asla_flex_algo(struct isis_ext_subtlvs *ext, + struct isis_asla_subtlvs *asla); +struct isis_asla_subtlvs * +isis_tlvs_find_alloc_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps); +void isis_tlvs_free_asla(struct isis_ext_subtlvs *ext, uint8_t standard_apps); + +void isis_tlvs_add_oldstyle_reach(struct isis_tlvs *tlvs, uint8_t *id, + uint8_t metric); +void isis_tlvs_add_extended_reach(struct isis_tlvs *tlvs, uint16_t mtid, + uint8_t *id, uint32_t metric, + struct isis_ext_subtlvs *subtlvs); + +const char *isis_threeway_state_name(enum isis_threeway_state state); + +void isis_tlvs_add_threeway_adj(struct isis_tlvs *tlvs, + enum isis_threeway_state state, + uint32_t local_circuit_id, + const uint8_t *neighbor_id, + uint32_t neighbor_circuit_id); + +void isis_tlvs_add_spine_leaf(struct isis_tlvs *tlvs, uint8_t tier, + bool has_tier, bool is_leaf, bool is_spine, + bool is_backup); + +struct isis_mt_router_info * +isis_tlvs_lookup_mt_router_info(struct isis_tlvs *tlvs, uint16_t mtid); + +void isis_tlvs_set_purge_originator(struct isis_tlvs *tlvs, + const uint8_t *generator, + const uint8_t *sender); + +void isis_subsubtlvs_set_srv6_sid_structure(struct isis_subsubtlvs *subsubtlvs, + struct isis_srv6_sid *sid); +void isis_subtlvs_add_srv6_end_sid(struct isis_subtlvs *subtlvs, + struct isis_srv6_sid *sid); +void isis_tlvs_add_srv6_locator(struct isis_tlvs *tlvs, uint16_t mtid, + struct isis_srv6_locator *loc); + +void isis_tlvs_add_srv6_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_endx_sid_subtlv *adj); +void isis_tlvs_del_srv6_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_endx_sid_subtlv *adj); +void isis_tlvs_add_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_lan_endx_sid_subtlv *lan); +void isis_tlvs_del_srv6_lan_endx_sid(struct isis_ext_subtlvs *exts, + struct isis_srv6_lan_endx_sid_subtlv *lan); +#endif diff --git a/isisd/isis_tx_queue.c b/isisd/isis_tx_queue.c new file mode 100644 index 0000000..caf97f1 --- /dev/null +++ b/isisd/isis_tx_queue.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - LSP TX Queuing logic + * + * Copyright (C) 2018 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#include + +#include "hash.h" +#include "jhash.h" + +#include "isisd/isisd.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_tx_queue.h" + +DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE, "ISIS TX Queue"); +DEFINE_MTYPE_STATIC(ISISD, TX_QUEUE_ENTRY, "ISIS TX Queue Entry"); + +struct isis_tx_queue { + struct isis_circuit *circuit; + void (*send_event)(struct isis_circuit *circuit, + struct isis_lsp *, enum isis_tx_type); + struct hash *hash; +}; + +struct isis_tx_queue_entry { + struct isis_lsp *lsp; + enum isis_tx_type type; + bool is_retry; + struct event *retry; + struct isis_tx_queue *queue; +}; + +static unsigned tx_queue_hash_key(const void *p) +{ + const struct isis_tx_queue_entry *e = p; + + uint32_t id_key = jhash(e->lsp->hdr.lsp_id, + ISIS_SYS_ID_LEN + 2, 0x55aa5a5a); + + return jhash_1word(e->lsp->level, id_key); +} + +static bool tx_queue_hash_cmp(const void *a, const void *b) +{ + const struct isis_tx_queue_entry *ea = a, *eb = b; + + if (ea->lsp->level != eb->lsp->level) + return false; + + if (memcmp(ea->lsp->hdr.lsp_id, eb->lsp->hdr.lsp_id, + ISIS_SYS_ID_LEN + 2)) + return false; + + return true; +} + +struct isis_tx_queue *isis_tx_queue_new( + struct isis_circuit *circuit, + void(*send_event)(struct isis_circuit *circuit, + struct isis_lsp *, + enum isis_tx_type)) +{ + struct isis_tx_queue *rv = XCALLOC(MTYPE_TX_QUEUE, sizeof(*rv)); + + rv->circuit = circuit; + rv->send_event = send_event; + + rv->hash = hash_create(tx_queue_hash_key, tx_queue_hash_cmp, NULL); + return rv; +} + +static void tx_queue_element_free(void *element) +{ + struct isis_tx_queue_entry *e = element; + + EVENT_OFF(e->retry); + + XFREE(MTYPE_TX_QUEUE_ENTRY, e); +} + +void isis_tx_queue_free(struct isis_tx_queue *queue) +{ + hash_clean_and_free(&queue->hash, tx_queue_element_free); + XFREE(MTYPE_TX_QUEUE, queue); +} + +static struct isis_tx_queue_entry *tx_queue_find(struct isis_tx_queue *queue, + struct isis_lsp *lsp) +{ + struct isis_tx_queue_entry e = { + .lsp = lsp + }; + + return hash_lookup(queue->hash, &e); +} + +static void tx_queue_send_event(struct event *thread) +{ + struct isis_tx_queue_entry *e = EVENT_ARG(thread); + struct isis_tx_queue *queue = e->queue; + + event_add_timer(master, tx_queue_send_event, e, 5, &e->retry); + + if (e->is_retry) + queue->circuit->area->lsp_rxmt_count++; + else + e->is_retry = true; + + queue->send_event(queue->circuit, e->lsp, e->type); + /* Don't access e here anymore, send_event might have destroyed it */ +} + +void _isis_tx_queue_add(struct isis_tx_queue *queue, + struct isis_lsp *lsp, + enum isis_tx_type type, + const char *func, const char *file, + int line) +{ + if (!queue) + return; + + if (IS_DEBUG_TX_QUEUE) { + zlog_debug( + "Add LSP %pLS to %s queue as %s LSP. (From %s %s:%d)", + lsp->hdr.lsp_id, queue->circuit->interface->name, + (type == TX_LSP_CIRCUIT_SCOPED) ? "circuit scoped" + : "regular", + func, file, line); + } + + struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp); + if (!e) { + e = XCALLOC(MTYPE_TX_QUEUE_ENTRY, sizeof(*e)); + e->lsp = lsp; + e->queue = queue; + + struct isis_tx_queue_entry *inserted; + inserted = hash_get(queue->hash, e, hash_alloc_intern); + assert(inserted == e); + } + + e->type = type; + + EVENT_OFF(e->retry); + event_add_event(master, tx_queue_send_event, e, 0, &e->retry); + + e->is_retry = false; +} + +void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp, + const char *func, const char *file, int line) +{ + if (!queue) + return; + + struct isis_tx_queue_entry *e = tx_queue_find(queue, lsp); + if (!e) + return; + + if (IS_DEBUG_TX_QUEUE) { + zlog_debug("Remove LSP %pLS from %s queue. (From %s %s:%d)", + lsp->hdr.lsp_id, queue->circuit->interface->name, + func, file, line); + } + + EVENT_OFF(e->retry); + + hash_release(queue->hash, e); + XFREE(MTYPE_TX_QUEUE_ENTRY, e); +} + +unsigned long isis_tx_queue_len(struct isis_tx_queue *queue) +{ + if (!queue) + return 0; + + return hashcount(queue->hash); +} + +void isis_tx_queue_clean(struct isis_tx_queue *queue) +{ + hash_clean(queue->hash, tx_queue_element_free); +} diff --git a/isisd/isis_tx_queue.h b/isisd/isis_tx_queue.h new file mode 100644 index 0000000..c3e36d0 --- /dev/null +++ b/isisd/isis_tx_queue.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - LSP TX Queuing logic + * + * Copyright (C) 2018 Christian Franke + * + * This file is part of FRRouting (FRR) + */ +#ifndef ISIS_TX_QUEUE_H +#define ISIS_TX_QUEUE_H + +enum isis_tx_type { + TX_LSP_NORMAL = 0, + TX_LSP_CIRCUIT_SCOPED +}; + +struct isis_tx_queue; + +struct isis_tx_queue *isis_tx_queue_new( + struct isis_circuit *circuit, + void(*send_event)(struct isis_circuit *circuit, + struct isis_lsp *, + enum isis_tx_type) +); + +void isis_tx_queue_free(struct isis_tx_queue *queue); + +#define isis_tx_queue_add(queue, lsp, type) \ + _isis_tx_queue_add((queue), (lsp), (type), \ + __func__, __FILE__, __LINE__) +void _isis_tx_queue_add(struct isis_tx_queue *queue, struct isis_lsp *lsp, + enum isis_tx_type type, const char *func, + const char *file, int line); + +#define isis_tx_queue_del(queue, lsp) \ + _isis_tx_queue_del((queue), (lsp), __func__, __FILE__, __LINE__) +void _isis_tx_queue_del(struct isis_tx_queue *queue, struct isis_lsp *lsp, + const char *func, const char *file, int line); + +unsigned long isis_tx_queue_len(struct isis_tx_queue *queue); + +void isis_tx_queue_clean(struct isis_tx_queue *queue); + +#endif diff --git a/isisd/isis_vty_fabricd.c b/isisd/isis_vty_fabricd.c new file mode 100644 index 0000000..0d25f66 --- /dev/null +++ b/isisd/isis_vty_fabricd.c @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_vty_fabricd.c + * + * This file contains the CLI that is specific to OpenFabric + * + * Copyright (C) 2018 Christian Franke, for NetDEF, Inc. + */ +#include + +#include "command.h" + +#include "lib/bfd.h" +#include "isisd/isis_bfd.h" +#include "isisd/isisd.h" +#include "isisd/fabricd.h" +#include "isisd/isis_tlvs.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_circuit.h" +#include "lib/spf_backoff.h" +#include "isisd/isis_mt.h" + +static struct isis_circuit *isis_circuit_lookup(struct vty *vty) +{ + struct interface *ifp = VTY_GET_CONTEXT(interface); + struct isis_circuit *circuit; + + if (!ifp) { + vty_out(vty, "Invalid interface \n"); + return NULL; + } + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) { + vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name); + return NULL; + } + + return circuit; +} + +DEFUN (fabric_tier, + fabric_tier_cmd, + "fabric-tier (0-14)", + "Statically configure the tier to advertise\n" + "Tier to advertise\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + uint8_t tier = atoi(argv[1]->arg); + + fabricd_configure_tier(area, tier); + return CMD_SUCCESS; +} + +DEFUN (no_fabric_tier, + no_fabric_tier_cmd, + "no fabric-tier [(0-14)]", + NO_STR + "Statically configure the tier to advertise\n" + "Tier to advertise\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + fabricd_configure_tier(area, ISIS_TIER_UNDEFINED); + return CMD_SUCCESS; +} + +DEFUN (triggered_csnp, + triggered_csnp_cmd, + "triggered-csnp-delay (100-10000) [always]", + "Configure the delay for triggered CSNPs\n" + "Delay in milliseconds\n" + "Trigger CSNP for all LSPs, not only circuit-scoped\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + int csnp_delay = atoi(argv[1]->arg); + bool always_send_csnp = (argc == 3); + + fabricd_configure_triggered_csnp(area, csnp_delay, always_send_csnp); + return CMD_SUCCESS; +} + +DEFUN (no_triggered_csnp, + no_triggered_csnp_cmd, + "no triggered-csnp-delay [(100-10000) [always]]", + NO_STR + "Configure the delay for triggered CSNPs\n" + "Delay in milliseconds\n" + "Trigger CSNP for all LSPs, not only circuit-scoped\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + fabricd_configure_triggered_csnp(area, FABRICD_DEFAULT_CSNP_DELAY, + false); + return CMD_SUCCESS; +} + +static void lsp_print_flooding(struct vty *vty, struct isis_lsp *lsp, + struct isis *isis) +{ + char lspid[255]; + char buf[MONOTIME_STRLEN]; + + lspid_print(lsp->hdr.lsp_id, lspid, sizeof(lspid), true, true, isis); + vty_out(vty, "Flooding information for %s\n", lspid); + + if (!lsp->flooding_neighbors[TX_LSP_NORMAL]) { + vty_out(vty, " Never received.\n"); + return; + } + + vty_out(vty, " Last received on: %s (", + lsp->flooding_interface ? + lsp->flooding_interface : "(null)"); + + time_t uptime = time(NULL) - lsp->flooding_time; + + frrtime_to_interval(uptime, buf, sizeof(buf)); + + vty_out(vty, "%s ago)\n", buf); + + if (lsp->flooding_circuit_scoped) { + vty_out(vty, " Received as circuit-scoped LSP, so not flooded.\n"); + return; + } + + for (enum isis_tx_type type = TX_LSP_NORMAL; + type <= TX_LSP_CIRCUIT_SCOPED; type++) { + struct listnode *node; + uint8_t *neighbor_id; + + vty_out(vty, " %s:\n", + (type == TX_LSP_NORMAL) ? "RF" : "DNR"); + for (ALL_LIST_ELEMENTS_RO(lsp->flooding_neighbors[type], + node, neighbor_id)) { + vty_out(vty, " %s\n", + print_sys_hostname(neighbor_id)); + } + } +} + +DEFUN (show_lsp_flooding, + show_lsp_flooding_cmd, + "show openfabric flooding [WORD]", + SHOW_STR + PROTO_HELP + "Flooding information\n" + "LSP ID\n") +{ + const char *lspid = NULL; + + if (argc == 4) + lspid = argv[3]->arg; + + struct listnode *node; + struct isis_area *area; + struct isis *isis = NULL; + + isis = isis_lookup_by_vrfid(VRF_DEFAULT); + + if (isis == NULL) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + struct lspdb_head *head = &area->lspdb[ISIS_LEVEL2 - 1]; + struct isis_lsp *lsp; + + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + if (lspid) { + lsp = lsp_for_sysid(head, lspid, isis); + if (lsp) + lsp_print_flooding(vty, lsp, isis); + continue; + } + frr_each (lspdb, head, lsp) { + lsp_print_flooding(vty, lsp, isis); + vty_out(vty, "\n"); + } + } + + return CMD_SUCCESS; +} + +DEFUN (ip_router_isis, + ip_router_isis_cmd, + "ip router " PROTO_NAME " WORD", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + PROTO_HELP + "Routing process tag\n") +{ + int idx_afi = 0; + int idx_word = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct isis_circuit *circuit; + struct isis_area *area; + const char *af = argv[idx_afi]->arg; + const char *area_tag = argv[idx_word]->arg; + + /* Prevent more than one area per circuit */ + circuit = circuit_scan_by_ifp(ifp); + if (circuit && circuit->area) { + if (strcmp(circuit->area->area_tag, area_tag)) { + vty_out(vty, "ISIS circuit is already defined on %s\n", + circuit->area->area_tag); + return CMD_ERR_NOTHING_TODO; + } + } + + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) + isis_area_create(area_tag, VRF_DEFAULT_NAME); + + if (!circuit) { + circuit = isis_circuit_new(ifp, area_tag); + + if (circuit->state != C_STATE_CONF + && circuit->state != C_STATE_UP) { + vty_out(vty, + "Couldn't bring up interface, please check log.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router; + if (af[2] != '\0') + ipv6 = true; + else + ip = true; + + isis_circuit_af_set(circuit, ip, ipv6); + return CMD_SUCCESS; +} + +DEFUN (ip6_router_isis, + ip6_router_isis_cmd, + "ipv6 router " PROTO_NAME " WORD", + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + PROTO_HELP + "Routing process tag\n") +{ + return ip_router_isis(self, vty, argc, argv); +} + +DEFUN (no_ip_router_isis, + no_ip_router_isis_cmd, + "no router " PROTO_NAME " WORD", + NO_STR + "Interface Internet Protocol config commands\n" + "IP router interface commands\n" + "IP router interface commands\n" + PROTO_HELP + "Routing process tag\n") +{ + int idx_afi = 1; + int idx_word = 4; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct isis_area *area; + struct isis_circuit *circuit; + const char *af = argv[idx_afi]->arg; + const char *area_tag = argv[idx_word]->arg; + + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (!area) { + vty_out(vty, "Can't find ISIS instance %s\n", + area_tag); + return CMD_ERR_NO_MATCH; + } + + circuit = circuit_scan_by_ifp(ifp); + if (!circuit) { + vty_out(vty, "ISIS is not enabled on circuit %s\n", ifp->name); + return CMD_ERR_NO_MATCH; + } + + bool ip = circuit->ip_router, ipv6 = circuit->ipv6_router; + if (af[2] != '\0') + ipv6 = false; + else + ip = false; + + isis_circuit_af_set(circuit, ip, ipv6); + + if (!ip && !ipv6) + isis_circuit_del(circuit); + + return CMD_SUCCESS; +} + +DEFUN (isis_bfd, + isis_bfd_cmd, + PROTO_NAME " bfd", + PROTO_HELP + "Enable BFD support\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + + if (!circuit) + return CMD_ERR_NO_MATCH; + + if (circuit->bfd_config.enabled) + return CMD_SUCCESS; + + circuit->bfd_config.enabled = true; + isis_bfd_circuit_cmd(circuit); + + return CMD_SUCCESS; +} + +DEFUN (no_isis_bfd, + no_isis_bfd_cmd, + "no " PROTO_NAME " bfd", + NO_STR + PROTO_HELP + "Disables BFD support\n" +) +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + + if (!circuit) + return CMD_ERR_NO_MATCH; + + if (!circuit->bfd_config.enabled) + return CMD_SUCCESS; + + circuit->bfd_config.enabled = false; + isis_bfd_circuit_cmd(circuit); + + return CMD_SUCCESS; +} + +DEFUN (set_overload_bit, + set_overload_bit_cmd, + "set-overload-bit", + "Set overload bit to avoid any transit traffic\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + isis_area_overload_bit_set(area, true); + return CMD_SUCCESS; +} + +DEFUN (no_set_overload_bit, + no_set_overload_bit_cmd, + "no set-overload-bit", + "Reset overload bit to accept transit traffic\n" + "Reset overload bit\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + isis_area_overload_bit_set(area, false); + return CMD_SUCCESS; +} + +static int isis_vty_password_set(struct vty *vty, int argc, + struct cmd_token *argv[], int level) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + int idx_algo = 1; + int idx_password = 2; + int idx_snp_auth = 5; + uint8_t snp_auth = 0; + + const char *passwd = argv[idx_password]->arg; + if (strlen(passwd) > 254) { + vty_out(vty, "Too long area password (>254)\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc > idx_snp_auth) { + snp_auth = SNP_AUTH_SEND; + if (strmatch(argv[idx_snp_auth]->text, "validate")) + snp_auth |= SNP_AUTH_RECV; + } + + if (strmatch(argv[idx_algo]->text, "clear")) { + return isis_area_passwd_cleartext_set(area, level, + passwd, snp_auth); + } else if (strmatch(argv[idx_algo]->text, "md5")) { + return isis_area_passwd_hmac_md5_set(area, level, + passwd, snp_auth); + } + + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (domain_passwd, + domain_passwd_cmd, + "domain-password WORD [authenticate snp ]", + "Set the authentication password for a routing domain\n" + "Authentication type\n" + "Authentication type\n" + "Level-wide password\n" + "Authentication\n" + "SNP PDUs\n" + "Send but do not check PDUs on receiving\n" + "Send and check PDUs on receiving\n") +{ + return isis_vty_password_set(vty, argc, argv, IS_LEVEL_2); +} + +DEFUN (no_domain_passwd, + no_domain_passwd_cmd, + "no domain-password", + NO_STR + "Set the authentication password for a routing domain\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + return isis_area_passwd_unset(area, IS_LEVEL_2); +} + +static int +isis_vty_lsp_gen_interval_set(struct vty *vty, int level, uint16_t interval) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + int lvl; + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (!(lvl & level)) + continue; + + if (interval >= area->lsp_refresh[lvl - 1]) { + vty_out(vty, + "LSP gen interval %us must be less than the LSP refresh interval %us\n", + interval, area->lsp_refresh[lvl - 1]); + return CMD_WARNING_CONFIG_FAILED; + } + } + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (!(lvl & level)) + continue; + area->lsp_gen_interval[lvl - 1] = interval; + } + + return CMD_SUCCESS; +} + +DEFUN (lsp_gen_interval, + lsp_gen_interval_cmd, + "lsp-gen-interval (1-120)", + "Minimum interval between regenerating same LSP\n" + "Minimum interval in seconds\n") +{ + uint16_t interval = atoi(argv[1]->arg); + + return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2, interval); +} + +DEFUN (no_lsp_gen_interval, + no_lsp_gen_interval_cmd, + "no lsp-gen-interval [(1-120)]", + NO_STR + "Minimum interval between regenerating same LSP\n" + "Minimum interval in seconds\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + return isis_vty_lsp_gen_interval_set(vty, IS_LEVEL_1_AND_2, + DEFAULT_MIN_LSP_GEN_INTERVAL); +} + +static int +isis_vty_lsp_refresh_set(struct vty *vty, int level, uint16_t interval) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + int lvl; + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (!(lvl & level)) + continue; + if (interval <= area->lsp_gen_interval[lvl - 1]) { + vty_out(vty, + "LSP refresh interval %us must be greater than the configured LSP gen interval %us\n", + interval, area->lsp_gen_interval[lvl - 1]); + return CMD_WARNING_CONFIG_FAILED; + } + if (interval > (area->max_lsp_lifetime[lvl - 1] - 300)) { + vty_out(vty, + "LSP refresh interval %us must be less than the configured LSP lifetime %us less 300\n", + interval, area->max_lsp_lifetime[lvl - 1]); + return CMD_WARNING_CONFIG_FAILED; + } + } + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; ++lvl) { + if (!(lvl & level)) + continue; + isis_area_lsp_refresh_set(area, lvl, interval); + } + + return CMD_SUCCESS; +} + +DEFUN (lsp_refresh_interval, + lsp_refresh_interval_cmd, + "lsp-refresh-interval (1-65235)", + "LSP refresh interval\n" + "LSP refresh interval in seconds\n") +{ + unsigned int interval = atoi(argv[1]->arg); + return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2, interval); +} + +DEFUN (no_lsp_refresh_interval, + no_lsp_refresh_interval_cmd, + "no lsp-refresh-interval [(1-65235)]", + NO_STR + "LSP refresh interval\n" + "LSP refresh interval in seconds\n") +{ + return isis_vty_lsp_refresh_set(vty, IS_LEVEL_1_AND_2, + DEFAULT_MAX_LSP_GEN_INTERVAL); +} + +static int +isis_vty_max_lsp_lifetime_set(struct vty *vty, int level, uint16_t interval) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + int lvl; + uint16_t refresh_interval = interval - 300; + int set_refresh_interval[ISIS_LEVELS] = {0, 0}; + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { + if (!(lvl & level)) + continue; + + if (refresh_interval < area->lsp_refresh[lvl - 1]) { + vty_out(vty, + "Level %d Max LSP lifetime %us must be 300s greater than the configured LSP refresh interval %us\n", + lvl, interval, area->lsp_refresh[lvl - 1]); + vty_out(vty, + "Automatically reducing level %d LSP refresh interval to %us\n", + lvl, refresh_interval); + set_refresh_interval[lvl - 1] = 1; + + if (refresh_interval + <= area->lsp_gen_interval[lvl - 1]) { + vty_out(vty, + "LSP refresh interval %us must be greater than the configured LSP gen interval %us\n", + refresh_interval, + area->lsp_gen_interval[lvl - 1]); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + for (lvl = IS_LEVEL_1; lvl <= IS_LEVEL_2; lvl++) { + if (!(lvl & level)) + continue; + isis_area_max_lsp_lifetime_set(area, lvl, interval); + if (set_refresh_interval[lvl - 1]) + isis_area_lsp_refresh_set(area, lvl, refresh_interval); + } + + return CMD_SUCCESS; +} + +DEFUN (max_lsp_lifetime, + max_lsp_lifetime_cmd, + "max-lsp-lifetime (350-65535)", + "Maximum LSP lifetime\n" + "LSP lifetime in seconds\n") +{ + int lifetime = atoi(argv[1]->arg); + + return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2, lifetime); +} + + +DEFUN (no_max_lsp_lifetime, + no_max_lsp_lifetime_cmd, + "no max-lsp-lifetime [(350-65535)]", + NO_STR + "Maximum LSP lifetime\n" + "LSP lifetime in seconds\n") +{ + return isis_vty_max_lsp_lifetime_set(vty, IS_LEVEL_1_AND_2, + DEFAULT_LSP_LIFETIME); +} + +DEFUN (spf_interval, + spf_interval_cmd, + "spf-interval (1-120)", + "Minimum interval between SPF calculations\n" + "Minimum interval between consecutive SPFs in seconds\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + uint16_t interval = atoi(argv[1]->arg); + + area->min_spf_interval[0] = interval; + area->min_spf_interval[1] = interval; + + return CMD_SUCCESS; +} + +DEFUN (no_spf_interval, + no_spf_interval_cmd, + "no spf-interval [(1-120)]", + NO_STR + "Minimum interval between SPF calculations\n" + "Minimum interval between consecutive SPFs in seconds\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL; + area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL; + + return CMD_SUCCESS; +} + +static int isis_vty_lsp_mtu_set(struct vty *vty, unsigned int lsp_mtu) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + struct listnode *node; + struct isis_circuit *circuit; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + if (circuit->state != C_STATE_INIT + && circuit->state != C_STATE_UP) + continue; + if (lsp_mtu > isis_circuit_pdu_size(circuit)) { + vty_out(vty, + "ISIS area contains circuit %s, which has a maximum PDU size of %zu.\n", + circuit->interface->name, + isis_circuit_pdu_size(circuit)); + return CMD_WARNING_CONFIG_FAILED; + } + } + + isis_area_lsp_mtu_set(area, lsp_mtu); + return CMD_SUCCESS; +} + +DEFUN (area_lsp_mtu, + area_lsp_mtu_cmd, + "lsp-mtu (128-4352)", + "Configure the maximum size of generated LSPs\n" + "Maximum size of generated LSPs\n") +{ + int idx_number = 1; + unsigned int lsp_mtu; + + lsp_mtu = strtoul(argv[idx_number]->arg, NULL, 10); + + return isis_vty_lsp_mtu_set(vty, lsp_mtu); +} + +DEFUN (no_area_lsp_mtu, + no_area_lsp_mtu_cmd, + "no lsp-mtu [(128-4352)]", + NO_STR + "Configure the maximum size of generated LSPs\n" + "Maximum size of generated LSPs\n") +{ + return isis_vty_lsp_mtu_set(vty, DEFAULT_LSP_MTU); +} + +DEFUN (no_spf_delay_ietf, + no_spf_delay_ietf_cmd, + "no spf-delay-ietf", + NO_STR + "IETF SPF delay algorithm\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + spf_backoff_free(area->spf_delay_ietf[0]); + spf_backoff_free(area->spf_delay_ietf[1]); + area->spf_delay_ietf[0] = NULL; + area->spf_delay_ietf[1] = NULL; + + return CMD_SUCCESS; +} + +DEFUN (spf_delay_ietf, + spf_delay_ietf_cmd, + "spf-delay-ietf init-delay (0-60000) short-delay (0-60000) long-delay (0-60000) holddown (0-60000) time-to-learn (0-60000)", + "IETF SPF delay algorithm\n" + "Delay used while in QUIET state\n" + "Delay used while in QUIET state in milliseconds\n" + "Delay used while in SHORT_WAIT state\n" + "Delay used while in SHORT_WAIT state in milliseconds\n" + "Delay used while in LONG_WAIT\n" + "Delay used while in LONG_WAIT state in milliseconds\n" + "Time with no received IGP events before considering IGP stable\n" + "Time with no received IGP events before considering IGP stable (in milliseconds)\n" + "Maximum duration needed to learn all the events related to a single failure\n" + "Maximum duration needed to learn all the events related to a single failure (in milliseconds)\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + long init_delay = atol(argv[2]->arg); + long short_delay = atol(argv[4]->arg); + long long_delay = atol(argv[6]->arg); + long holddown = atol(argv[8]->arg); + long timetolearn = atol(argv[10]->arg); + + size_t bufsiz = strlen(area->area_tag) + sizeof("IS-IS Lx"); + char *buf = XCALLOC(MTYPE_TMP, bufsiz); + + snprintf(buf, bufsiz, "IS-IS %s L1", area->area_tag); + spf_backoff_free(area->spf_delay_ietf[0]); + area->spf_delay_ietf[0] = + spf_backoff_new(master, buf, init_delay, short_delay, + long_delay, holddown, timetolearn); + + snprintf(buf, bufsiz, "IS-IS %s L2", area->area_tag); + spf_backoff_free(area->spf_delay_ietf[1]); + area->spf_delay_ietf[1] = + spf_backoff_new(master, buf, init_delay, short_delay, + long_delay, holddown, timetolearn); + + XFREE(MTYPE_TMP, buf); + return CMD_SUCCESS; +} + +DEFUN (area_purge_originator, + area_purge_originator_cmd, + "[no] purge-originator", + NO_STR + "Use the RFC 6232 purge-originator\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + area->purge_originator = !!strcmp(argv[0]->text, "no"); + return CMD_SUCCESS; +} + +DEFUN (isis_passive, + isis_passive_cmd, + PROTO_NAME " passive", + PROTO_HELP + "Configure the passive mode for interface\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 1), + "Cannot set passive: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (no_isis_passive, + no_isis_passive_cmd, + "no " PROTO_NAME " passive", + NO_STR + PROTO_HELP + "Configure the passive mode for interface\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + CMD_FERR_RETURN(isis_circuit_passive_set(circuit, 0), + "Cannot set no passive: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (isis_passwd, + isis_passwd_cmd, + PROTO_NAME " password WORD", + PROTO_HELP + "Configure the authentication password for a circuit\n" + "HMAC-MD5 authentication\n" + "Cleartext password\n" + "Circuit password\n") +{ + int idx_encryption = 2; + int idx_word = 3; + struct isis_circuit *circuit = isis_circuit_lookup(vty); + ferr_r rv; + + if (!circuit) + return CMD_ERR_NO_MATCH; + + if (argv[idx_encryption]->arg[0] == 'm') + rv = isis_circuit_passwd_hmac_md5_set(circuit, + argv[idx_word]->arg); + else + rv = isis_circuit_passwd_cleartext_set(circuit, + argv[idx_word]->arg); + + CMD_FERR_RETURN(rv, "Failed to set circuit password: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (no_isis_passwd, + no_isis_passwd_cmd, + "no " PROTO_NAME " password [ WORD]", + NO_STR + PROTO_HELP + "Configure the authentication password for a circuit\n" + "HMAC-MD5 authentication\n" + "Cleartext password\n" + "Circuit password\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + CMD_FERR_RETURN(isis_circuit_passwd_unset(circuit), + "Failed to unset circuit password: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (isis_metric, + isis_metric_cmd, + PROTO_NAME " metric (0-16777215)", + PROTO_HELP + "Set default metric for circuit\n" + "Default metric value\n") +{ + int idx_number = 2; + int met; + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + met = atoi(argv[idx_number]->arg); + + /* RFC3787 section 5.1 */ + if (circuit->area && circuit->area->oldmetric == 1 + && met > MAX_NARROW_LINK_METRIC) { + vty_out(vty, + "Invalid metric %d - should be <0-63> when narrow metric type enabled\n", + met); + return CMD_WARNING_CONFIG_FAILED; + } + + /* RFC4444 */ + if (circuit->area && circuit->area->newmetric == 1 + && met > MAX_WIDE_LINK_METRIC) { + vty_out(vty, + "Invalid metric %d - should be <0-16777215> when wide metric type enabled\n", + met); + return CMD_WARNING_CONFIG_FAILED; + } + + CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1, met), + "Failed to set L1 metric: $ERR"); + CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2, met), + "Failed to set L2 metric: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (no_isis_metric, + no_isis_metric_cmd, + "no " PROTO_NAME " metric [(0-16777215)]", + NO_STR + PROTO_HELP + "Set default metric for circuit\n" + "Default metric value\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_1, + DEFAULT_CIRCUIT_METRIC), + "Failed to set L1 metric: $ERR"); + CMD_FERR_RETURN(isis_circuit_metric_set(circuit, IS_LEVEL_2, + DEFAULT_CIRCUIT_METRIC), + "Failed to set L2 metric: $ERR"); + return CMD_SUCCESS; +} + +DEFUN (isis_hello_interval, + isis_hello_interval_cmd, + PROTO_NAME " hello-interval (1-600)", + PROTO_HELP + "Set Hello interval\n" + "Holdtime 1 seconds, interval depends on multiplier\n") +{ + uint32_t interval = atoi(argv[2]->arg); + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->hello_interval[0] = interval; + circuit->hello_interval[1] = interval; + + return CMD_SUCCESS; +} + +DEFUN (no_isis_hello_interval, + no_isis_hello_interval_cmd, + "no " PROTO_NAME " hello-interval [(1-600)]", + NO_STR + PROTO_HELP + "Set Hello interval\n" + "Holdtime 1 second, interval depends on multiplier\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->hello_interval[0] = DEFAULT_HELLO_INTERVAL; + circuit->hello_interval[1] = DEFAULT_HELLO_INTERVAL; + + return CMD_SUCCESS; +} + +DEFUN (isis_hello_multiplier, + isis_hello_multiplier_cmd, + PROTO_NAME " hello-multiplier (2-100)", + PROTO_HELP + "Set multiplier for Hello holding time\n" + "Hello multiplier value\n") +{ + uint16_t mult = atoi(argv[2]->arg); + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->hello_multiplier[0] = mult; + circuit->hello_multiplier[1] = mult; + + return CMD_SUCCESS; +} + +DEFUN (no_isis_hello_multiplier, + no_isis_hello_multiplier_cmd, + "no " PROTO_NAME " hello-multiplier [(2-100)]", + NO_STR + PROTO_HELP + "Set multiplier for Hello holding time\n" + "Hello multiplier value\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->hello_multiplier[0] = DEFAULT_HELLO_MULTIPLIER; + circuit->hello_multiplier[1] = DEFAULT_HELLO_MULTIPLIER; + + return CMD_SUCCESS; +} + +DEFUN (csnp_interval, + csnp_interval_cmd, + PROTO_NAME " csnp-interval (1-600)", + PROTO_HELP + "Set CSNP interval in seconds\n" + "CSNP interval value\n") +{ + uint16_t interval = atoi(argv[2]->arg); + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->csnp_interval[0] = interval; + circuit->csnp_interval[1] = interval; + + return CMD_SUCCESS; +} + +DEFUN (no_csnp_interval, + no_csnp_interval_cmd, + "no " PROTO_NAME " csnp-interval [(1-600)]", + NO_STR + PROTO_HELP + "Set CSNP interval in seconds\n" + "CSNP interval value\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->csnp_interval[0] = DEFAULT_CSNP_INTERVAL; + circuit->csnp_interval[1] = DEFAULT_CSNP_INTERVAL; + + return CMD_SUCCESS; +} + +DEFUN (psnp_interval, + psnp_interval_cmd, + PROTO_NAME " psnp-interval (1-120)", + PROTO_HELP + "Set PSNP interval in seconds\n" + "PSNP interval value\n") +{ + uint16_t interval = atoi(argv[2]->arg); + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->psnp_interval[0] = interval; + circuit->psnp_interval[1] = interval; + + return CMD_SUCCESS; +} + +DEFUN (no_psnp_interval, + no_psnp_interval_cmd, + "no " PROTO_NAME " psnp-interval [(1-120)]", + NO_STR + PROTO_HELP + "Set PSNP interval in seconds\n" + "PSNP interval value\n") +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + + circuit->psnp_interval[0] = DEFAULT_PSNP_INTERVAL; + circuit->psnp_interval[1] = DEFAULT_PSNP_INTERVAL; + + return CMD_SUCCESS; +} + +DEFUN (circuit_topology, + circuit_topology_cmd, + PROTO_NAME " topology " ISIS_MT_NAMES, + PROTO_HELP + "Configure interface IS-IS topologies\n" + ISIS_MT_DESCRIPTIONS) +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + const char *arg = argv[2]->arg; + uint16_t mtid = isis_str2mtid(arg); + + if (circuit->area && circuit->area->oldmetric) { + vty_out(vty, + "Multi topology IS-IS can only be used with wide metrics\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (mtid == (uint16_t)-1) { + vty_out(vty, "Don't know topology '%s'\n", arg); + return CMD_WARNING_CONFIG_FAILED; + } + + return isis_circuit_mt_enabled_set(circuit, mtid, true); +} + +DEFUN (no_circuit_topology, + no_circuit_topology_cmd, + "no " PROTO_NAME " topology " ISIS_MT_NAMES, + NO_STR + PROTO_HELP + "Configure interface IS-IS topologies\n" + ISIS_MT_DESCRIPTIONS) +{ + struct isis_circuit *circuit = isis_circuit_lookup(vty); + if (!circuit) + return CMD_ERR_NO_MATCH; + const char *arg = argv[3]->arg; + uint16_t mtid = isis_str2mtid(arg); + + if (circuit->area && circuit->area->oldmetric) { + vty_out(vty, + "Multi topology IS-IS can only be used with wide metrics\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (mtid == (uint16_t)-1) { + vty_out(vty, "Don't know topology '%s'\n", arg); + return CMD_WARNING_CONFIG_FAILED; + } + + return isis_circuit_mt_enabled_set(circuit, mtid, false); +} + +void isis_vty_daemon_init(void) +{ + install_element(ROUTER_NODE, &fabric_tier_cmd); + install_element(ROUTER_NODE, &no_fabric_tier_cmd); + install_element(ROUTER_NODE, &triggered_csnp_cmd); + install_element(ROUTER_NODE, &no_triggered_csnp_cmd); + + install_element(ENABLE_NODE, &show_lsp_flooding_cmd); + + install_element(INTERFACE_NODE, &ip_router_isis_cmd); + install_element(INTERFACE_NODE, &ip6_router_isis_cmd); + install_element(INTERFACE_NODE, &no_ip_router_isis_cmd); + install_element(INTERFACE_NODE, &isis_bfd_cmd); + install_element(INTERFACE_NODE, &no_isis_bfd_cmd); + + install_element(ROUTER_NODE, &set_overload_bit_cmd); + install_element(ROUTER_NODE, &no_set_overload_bit_cmd); + + install_element(ROUTER_NODE, &domain_passwd_cmd); + install_element(ROUTER_NODE, &no_domain_passwd_cmd); + + install_element(ROUTER_NODE, &lsp_gen_interval_cmd); + install_element(ROUTER_NODE, &no_lsp_gen_interval_cmd); + + install_element(ROUTER_NODE, &lsp_refresh_interval_cmd); + install_element(ROUTER_NODE, &no_lsp_refresh_interval_cmd); + + install_element(ROUTER_NODE, &max_lsp_lifetime_cmd); + install_element(ROUTER_NODE, &no_max_lsp_lifetime_cmd); + + install_element(ROUTER_NODE, &area_lsp_mtu_cmd); + install_element(ROUTER_NODE, &no_area_lsp_mtu_cmd); + + install_element(ROUTER_NODE, &spf_interval_cmd); + install_element(ROUTER_NODE, &no_spf_interval_cmd); + + install_element(ROUTER_NODE, &spf_delay_ietf_cmd); + install_element(ROUTER_NODE, &no_spf_delay_ietf_cmd); + + install_element(ROUTER_NODE, &area_purge_originator_cmd); + + install_element(INTERFACE_NODE, &isis_passive_cmd); + install_element(INTERFACE_NODE, &no_isis_passive_cmd); + + install_element(INTERFACE_NODE, &isis_passwd_cmd); + install_element(INTERFACE_NODE, &no_isis_passwd_cmd); + + install_element(INTERFACE_NODE, &isis_metric_cmd); + install_element(INTERFACE_NODE, &no_isis_metric_cmd); + + install_element(INTERFACE_NODE, &isis_hello_interval_cmd); + install_element(INTERFACE_NODE, &no_isis_hello_interval_cmd); + + install_element(INTERFACE_NODE, &isis_hello_multiplier_cmd); + install_element(INTERFACE_NODE, &no_isis_hello_multiplier_cmd); + + install_element(INTERFACE_NODE, &csnp_interval_cmd); + install_element(INTERFACE_NODE, &no_csnp_interval_cmd); + + install_element(INTERFACE_NODE, &psnp_interval_cmd); + install_element(INTERFACE_NODE, &no_psnp_interval_cmd); + + install_element(INTERFACE_NODE, &circuit_topology_cmd); + install_element(INTERFACE_NODE, &no_circuit_topology_cmd); +} diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c new file mode 100644 index 0000000..2412ec5 --- /dev/null +++ b/isisd/isis_zebra.c @@ -0,0 +1,1420 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_zebra.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + * Copyright (C) 2013-2015 Christian Franke + */ + +#include + +#include "frrevent.h" +#include "command.h" +#include "memory.h" +#include "log.h" +#include "lib_errors.h" +#include "if.h" +#include "network.h" +#include "prefix.h" +#include "zclient.h" +#include "stream.h" +#include "linklist.h" +#include "nexthop.h" +#include "vrf.h" +#include "libfrr.h" +#include "bfd.h" +#include "link_state.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_circuit.h" +#include "isisd/isisd.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_csm.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" +#include "isisd/isis_route.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_te.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_ldp_sync.h" + +struct zclient *zclient; +static struct zclient *zclient_sync; + +/* Router-id update message from zebra. */ +static int isis_router_id_update_zebra(ZAPI_CALLBACK_ARGS) +{ + struct isis_area *area; + struct listnode *node; + struct prefix router_id; + struct isis *isis = NULL; + + isis = isis_lookup_by_vrfid(vrf_id); + + if (isis == NULL) { + return -1; + } + + zebra_router_id_update_read(zclient->ibuf, &router_id); + if (isis->router_id == router_id.u.prefix4.s_addr) + return 0; + + isis->router_id = router_id.u.prefix4.s_addr; + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + if (listcount(area->area_addrs) > 0) + lsp_regenerate_schedule(area, area->is_type, 0); + + return 0; +} + +static int isis_zebra_if_address_add(ZAPI_CALLBACK_ARGS) +{ + struct isis_circuit *circuit; + struct connected *c; + + c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, + zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + +#ifdef EXTREME_DEBUG + if (c->address->family == AF_INET) + zlog_debug("connected IP address %pFX", c->address); + if (c->address->family == AF_INET6) + zlog_debug("connected IPv6 address %pFX", c->address); +#endif /* EXTREME_DEBUG */ + + if (if_is_operative(c->ifp)) { + circuit = circuit_scan_by_ifp(c->ifp); + if (circuit) + isis_circuit_add_addr(circuit, c); + } + + sr_if_addr_update(c->ifp); + + return 0; +} + +static int isis_zebra_if_address_del(ZAPI_CALLBACK_ARGS) +{ + struct isis_circuit *circuit; + struct connected *c; + + c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, + zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + +#ifdef EXTREME_DEBUG + if (c->address->family == AF_INET) + zlog_debug("disconnected IP address %pFX", c->address); + if (c->address->family == AF_INET6) + zlog_debug("disconnected IPv6 address %pFX", c->address); +#endif /* EXTREME_DEBUG */ + + if (if_is_operative(c->ifp)) { + circuit = circuit_scan_by_ifp(c->ifp); + if (circuit) + isis_circuit_del_addr(circuit, c); + } + + sr_if_addr_update(c->ifp); + + connected_free(&c); + + return 0; +} + +static int isis_zebra_link_params(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + bool changed = false; + + ifp = zebra_interface_link_params_read(zclient->ibuf, vrf_id, &changed); + + if (ifp == NULL || !changed) + return 0; + + /* Update TE TLV */ + isis_mpls_te_update(ifp); + + return 0; +} + +enum isis_zebra_nexthop_type { + ISIS_NEXTHOP_MAIN = 0, + ISIS_NEXTHOP_BACKUP, +}; + +static int isis_zebra_add_nexthops(struct isis *isis, struct list *nexthops, + struct zapi_nexthop zapi_nexthops[], + enum isis_zebra_nexthop_type type, + bool mpls_lsp, uint8_t backup_nhs) +{ + struct isis_nexthop *nexthop; + struct listnode *node; + int count = 0; + + /* Nexthops */ + for (ALL_LIST_ELEMENTS_RO(nexthops, node, nexthop)) { + struct zapi_nexthop *api_nh; + + if (count >= MULTIPATH_NUM) + break; + api_nh = &zapi_nexthops[count]; + if (fabricd) + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); + api_nh->vrf_id = isis->vrf_id; + + switch (nexthop->family) { + case AF_INET: + /* FIXME: can it be ? */ + if (nexthop->ip.ipv4.s_addr != INADDR_ANY) { + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + api_nh->gate.ipv4 = nexthop->ip.ipv4; + } else { + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + break; + case AF_INET6: + if (!IN6_IS_ADDR_LINKLOCAL(&nexthop->ip.ipv6) + && !IN6_IS_ADDR_UNSPECIFIED(&nexthop->ip.ipv6)) { + continue; + } + api_nh->gate.ipv6 = nexthop->ip.ipv6; + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown address family [%d]", __func__, + nexthop->family); + exit(1); + } + + api_nh->ifindex = nexthop->ifindex; + + /* Add MPLS label(s). */ + if (nexthop->label_stack) { + api_nh->label_num = nexthop->label_stack->num_labels; + memcpy(api_nh->labels, nexthop->label_stack->label, + sizeof(mpls_label_t) * api_nh->label_num); + } else if (nexthop->sr.present) { + api_nh->label_num = 1; + api_nh->labels[0] = nexthop->sr.label; + } else if (mpls_lsp) { + switch (type) { + case ISIS_NEXTHOP_MAIN: + /* + * Do not use non-SR enabled nexthops to prevent + * broken LSPs from being formed. + */ + continue; + case ISIS_NEXTHOP_BACKUP: + /* + * This is necessary because zebra requires + * the nexthops of MPLS LSPs to be labeled. + */ + api_nh->label_num = 1; + api_nh->labels[0] = MPLS_LABEL_IMPLICIT_NULL; + break; + } + } + + /* Backup nexthop handling. */ + if (backup_nhs) { + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + /* + * If the backup has multiple nexthops, all of them + * protect the same primary nexthop since ECMP routes + * have no backups. + */ + api_nh->backup_num = backup_nhs; + for (int i = 0; i < backup_nhs; i++) + api_nh->backup_idx[i] = i; + } + count++; + } + + return count; +} + +void isis_zebra_route_add_route(struct isis *isis, struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info) +{ + struct zapi_route api; + int count = 0; + + if (zclient->sock < 0) + return; + + /* Uninstall the route if it doesn't have any valid nexthop. */ + if (list_isempty(route_info->nexthops)) { + isis_zebra_route_del_route(isis, prefix, src_p, route_info); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = isis->vrf_id; + api.type = PROTO_TYPE; + api.safi = SAFI_UNICAST; + api.prefix = *prefix; + if (src_p && src_p->prefixlen) { + api.src_prefix = *src_p; + SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); + } + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = route_info->cost; + + /* Add backup nexthops first. */ + if (route_info->backup) { + count = isis_zebra_add_nexthops( + isis, route_info->backup->nexthops, api.backup_nexthops, + ISIS_NEXTHOP_BACKUP, false, 0); + if (count > 0) { + SET_FLAG(api.message, ZAPI_MESSAGE_BACKUP_NEXTHOPS); + api.backup_nexthop_num = count; + } + } + + /* Add primary nexthops. */ + count = isis_zebra_add_nexthops(isis, route_info->nexthops, + api.nexthops, ISIS_NEXTHOP_MAIN, false, + count); + if (!count) + return; + api.nexthop_num = count; + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); +} + +void isis_zebra_route_del_route(struct isis *isis, + struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info) +{ + struct zapi_route api; + + if (zclient->sock < 0) + return; + + if (!CHECK_FLAG(route_info->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) + return; + + memset(&api, 0, sizeof(api)); + api.vrf_id = isis->vrf_id; + api.type = PROTO_TYPE; + api.safi = SAFI_UNICAST; + api.prefix = *prefix; + if (src_p && src_p->prefixlen) { + api.src_prefix = *src_p; + SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); + } + + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); +} + +/** + * Install Prefix-SID label entry in the forwarding plane through Zebra. + * + * @param area IS-IS area + * @param prefix Route prefix + * @param rinfo Route information + * @param psid Prefix-SID information + */ +void isis_zebra_prefix_sid_install(struct isis_area *area, + struct prefix *prefix, + struct isis_sr_psid_info *psid) +{ + struct zapi_labels zl; + int count = 0; + + sr_debug("ISIS-Sr (%s): update label %u for prefix %pFX algorithm %u", + area->area_tag, psid->label, prefix, psid->algorithm); + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = psid->label; + + /* Local routes don't have any nexthop and require special handling. */ + if (list_isempty(psid->nexthops)) { + struct zapi_nexthop *znh; + struct interface *ifp; + + ifp = if_lookup_by_name("lo", VRF_DEFAULT); + if (!ifp) { + zlog_warn( + "%s: couldn't install Prefix-SID %pFX: loopback interface not found", + __func__, prefix); + return; + } + + znh = &zl.nexthops[zl.nexthop_num++]; + znh->type = NEXTHOP_TYPE_IFINDEX; + znh->ifindex = ifp->ifindex; + znh->label_num = 1; + znh->labels[0] = MPLS_LABEL_IMPLICIT_NULL; + } else { + /* Add backup nexthops first. */ + if (psid->nexthops_backup) { + count = isis_zebra_add_nexthops( + area->isis, psid->nexthops_backup, + zl.backup_nexthops, ISIS_NEXTHOP_BACKUP, true, + 0); + if (count > 0) { + SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS); + zl.backup_nexthop_num = count; + } + } + + /* Add primary nexthops. */ + count = isis_zebra_add_nexthops(area->isis, psid->nexthops, + zl.nexthops, ISIS_NEXTHOP_MAIN, + true, count); + if (!count) + return; + zl.nexthop_num = count; + } + + /* Send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_REPLACE, &zl); +} + +/** + * Uninstall Prefix-SID label entry from the forwarding plane through Zebra. + * + * @param area IS-IS area + * @param prefix Route prefix + * @param rinfo Route information + * @param psid Prefix-SID information + */ +void isis_zebra_prefix_sid_uninstall(struct isis_area *area, + struct prefix *prefix, + struct isis_route_info *rinfo, + struct isis_sr_psid_info *psid) +{ + struct zapi_labels zl; + + sr_debug("ISIS-Sr (%s): delete label %u for prefix %pFX algorithm %u", + area->area_tag, psid->label, prefix, psid->algorithm); + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = psid->label; + + /* Send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_DELETE, &zl); +} + +/** + * Send (LAN)-Adjacency-SID to ZEBRA for installation or deletion. + * + * @param cmd ZEBRA_MPLS_LABELS_ADD or ZEBRA_ROUTE_DELETE + * @param sra Segment Routing Adjacency-SID + */ +void isis_zebra_send_adjacency_sid(int cmd, const struct sr_adjacency *sra) +{ + struct isis *isis = sra->adj->circuit->area->isis; + struct zapi_labels zl; + struct zapi_nexthop *znh; + + if (cmd != ZEBRA_MPLS_LABELS_ADD && cmd != ZEBRA_MPLS_LABELS_DELETE) { + flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong ZEBRA command", + __func__); + return; + } + + sr_debug(" |- %s label %u for interface %s", + cmd == ZEBRA_MPLS_LABELS_ADD ? "Add" : "Delete", + sra->input_label, sra->adj->circuit->interface->name); + + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_ISIS_SR; + zl.local_label = sra->input_label; + zl.nexthop_num = 1; + znh = &zl.nexthops[0]; + znh->gate = sra->nexthop.address; + znh->type = (sra->nexthop.family == AF_INET) + ? NEXTHOP_TYPE_IPV4_IFINDEX + : NEXTHOP_TYPE_IPV6_IFINDEX; + znh->ifindex = sra->adj->circuit->interface->ifindex; + znh->label_num = 1; + znh->labels[0] = MPLS_LABEL_IMPLICIT_NULL; + + /* Set backup nexthops. */ + if (sra->type == ISIS_SR_ADJ_BACKUP) { + int count; + + count = isis_zebra_add_nexthops(isis, sra->backup_nexthops, + zl.backup_nexthops, + ISIS_NEXTHOP_BACKUP, true, 0); + if (count > 0) { + SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS); + zl.backup_nexthop_num = count; + + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + znh->backup_num = count; + for (int i = 0; i < count; i++) + znh->backup_idx[i] = i; + } + } + + (void)zebra_send_mpls_labels(zclient, cmd, &zl); +} + +static int isis_zebra_read(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct isis *isis = NULL; + + isis = isis_lookup_by_vrfid(vrf_id); + + if (isis == NULL) + return -1; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + if (api.prefix.family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) + return 0; + + /* + * Avoid advertising a false default reachability. (A default + * route installed by IS-IS gets redistributed from zebra back + * into IS-IS causing us to start advertising default reachabity + * without this check) + */ + if (api.prefix.prefixlen == 0 + && api.src_prefix.prefixlen == 0 + && api.type == PROTO_TYPE) { + cmd = ZEBRA_REDISTRIBUTE_ROUTE_DEL; + } + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) + isis_redist_add(isis, api.type, &api.prefix, &api.src_prefix, + api.distance, api.metric, api.tag, api.instance); + else + isis_redist_delete(isis, api.type, &api.prefix, &api.src_prefix, + api.instance); + + return 0; +} + +int isis_distribute_list_update(int routetype) +{ + return 0; +} + +void isis_zebra_redistribute_set(afi_t afi, int type, vrf_id_t vrf_id, + uint16_t tableid) +{ + if (type == DEFAULT_ROUTE) + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + zclient, afi, vrf_id); + else + zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, afi, type, + tableid, vrf_id); +} + +void isis_zebra_redistribute_unset(afi_t afi, int type, vrf_id_t vrf_id, + uint16_t tableid) +{ + if (type == DEFAULT_ROUTE) + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, + zclient, afi, vrf_id); + else + zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, + type, tableid, vrf_id); +} + +/** + * Register RLFA with LDP. + */ +int isis_zebra_rlfa_register(struct isis_spftree *spftree, struct rlfa *rlfa) +{ + struct isis_area *area = spftree->area; + struct zapi_rlfa_request zr = {}; + int ret; + + if (!zclient) + return 0; + + zr.igp.vrf_id = area->isis->vrf_id; + zr.igp.protocol = ZEBRA_ROUTE_ISIS; + strlcpy(zr.igp.isis.area_tag, area->area_tag, + sizeof(zr.igp.isis.area_tag)); + zr.igp.isis.spf.tree_id = spftree->tree_id; + zr.igp.isis.spf.level = spftree->level; + zr.igp.isis.spf.run_id = spftree->runcount; + zr.destination = rlfa->prefix; + zr.pq_address = rlfa->pq_address; + + zlog_debug("ISIS-LFA: registering RLFA %pFX@%pI4 with LDP", + &rlfa->prefix, &rlfa->pq_address); + + ret = zclient_send_opaque_unicast(zclient, LDP_RLFA_REGISTER, + ZEBRA_ROUTE_LDP, 0, 0, + (const uint8_t *)&zr, sizeof(zr)); + if (ret == ZCLIENT_SEND_FAILURE) { + zlog_warn("ISIS-LFA: failed to register RLFA with LDP"); + return -1; + } + + return 0; +} + +/** + * Unregister all RLFAs from the given SPF tree with LDP. + */ +void isis_zebra_rlfa_unregister_all(struct isis_spftree *spftree) +{ + struct isis_area *area = spftree->area; + struct zapi_rlfa_igp igp = {}; + int ret; + + if (!zclient || spftree->type != SPF_TYPE_FORWARD + || CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) + return; + + if (IS_DEBUG_LFA) + zlog_debug("ISIS-LFA: unregistering all RLFAs with LDP"); + + igp.vrf_id = area->isis->vrf_id; + igp.protocol = ZEBRA_ROUTE_ISIS; + strlcpy(igp.isis.area_tag, area->area_tag, sizeof(igp.isis.area_tag)); + igp.isis.spf.tree_id = spftree->tree_id; + igp.isis.spf.level = spftree->level; + igp.isis.spf.run_id = spftree->runcount; + + ret = zclient_send_opaque_unicast(zclient, LDP_RLFA_UNREGISTER_ALL, + ZEBRA_ROUTE_LDP, 0, 0, + (const uint8_t *)&igp, sizeof(igp)); + if (ret == ZCLIENT_SEND_FAILURE) + zlog_warn("ISIS-LFA: failed to unregister RLFA with LDP"); +} + +/* Label Manager Functions */ + +/** + * Check if Label Manager is Ready or not. + * + * @return True if Label Manager is ready, False otherwise + */ +bool isis_zebra_label_manager_ready(void) +{ + return (zclient_sync->sock > 0); +} + +/** + * Request Label Range to the Label Manager. + * + * @param base base label of the label range to request + * @param chunk_size size of the label range to request + * + * @return 0 on success, -1 on failure + */ +int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size) +{ + int ret; + uint32_t start, end; + + if (zclient_sync->sock < 0) + return -1; + + ret = lm_get_label_chunk(zclient_sync, 0, base, chunk_size, &start, + &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Release Label Range to the Label Manager. + * + * @param start start of label range to release + * @param end end of label range to release + * + * @return 0 on success, -1 otherwise + */ +int isis_zebra_release_label_range(uint32_t start, uint32_t end) +{ + int ret; + + if (zclient_sync->sock < 0) + return -1; + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) { + zlog_warn("%s: error releasing label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Connect to the Label Manager. + * + * @return 0 on success, -1 otherwise + */ +int isis_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + if (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: failed connecting synchronous zclient!", + __func__); + return -1; + } + /* make socket non-blocking */ + set_nonblocking(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: failed sending hello for synchronous zclient!", + __func__); + close(zclient_sync->sock); + zclient_sync->sock = -1; + return -1; + } + + /* Connect to label manager */ + if (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: failed connecting to label manager!", __func__); + if (zclient_sync->sock > 0) { + close(zclient_sync->sock); + zclient_sync->sock = -1; + } + return -1; + } + + sr_debug("ISIS-Sr: Successfully connected to the Label Manager"); + + return 0; +} + +void isis_zebra_vrf_register(struct isis *isis) +{ + if (!zclient || zclient->sock < 0 || !isis) + return; + + if (isis->vrf_id != VRF_UNKNOWN) { + if (IS_DEBUG_EVENTS) + zlog_debug("%s: Register VRF %s id %u", __func__, + isis->name, isis->vrf_id); + zclient_send_reg_requests(zclient, isis->vrf_id); + } +} + +void isis_zebra_vrf_deregister(struct isis *isis) +{ + if (!zclient || zclient->sock < 0 || !isis) + return; + + if (isis->vrf_id != VRF_UNKNOWN) { + if (IS_DEBUG_EVENTS) + zlog_debug("%s: Deregister VRF %s id %u", __func__, + isis->name, isis->vrf_id); + zclient_send_dereg_requests(zclient, isis->vrf_id); + } +} + +static void isis_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zclient_register_opaque(zclient, LDP_RLFA_LABELS); + zclient_register_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE); + zclient_register_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE); + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); +} + +/** + * Register / unregister Link State ZAPI Opaque Message + * + * @param up True to register, false to unregister + * + * @return 0 if success, -1 otherwise + */ +int isis_zebra_ls_register(bool up) +{ + int rc; + + if (up) + rc = ls_register(zclient, true); + else + rc = ls_unregister(zclient, true); + + return rc; +} + +/* + * opaque messages between processes + */ +static int isis_opaque_msg_handler(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct zapi_opaque_msg info; + struct zapi_opaque_reg_info dst; + struct ldp_igp_sync_if_state state; + struct ldp_igp_sync_announce announce; + struct zapi_rlfa_response rlfa; + int ret = 0; + + s = zclient->ibuf; + if (zclient_opaque_decode(s, &info) != 0) + return -1; + + switch (info.type) { + case LINK_STATE_SYNC: + dst.proto = info.src_proto; + dst.instance = info.src_instance; + dst.session_id = info.src_session_id; + dst.type = LINK_STATE_SYNC; + ret = isis_te_sync_ted(dst); + break; + case LDP_IGP_SYNC_IF_STATE_UPDATE: + STREAM_GET(&state, s, sizeof(state)); + ret = isis_ldp_sync_state_update(state); + break; + case LDP_IGP_SYNC_ANNOUNCE_UPDATE: + STREAM_GET(&announce, s, sizeof(announce)); + ret = isis_ldp_sync_announce_update(announce); + break; + case LDP_RLFA_LABELS: + STREAM_GET(&rlfa, s, sizeof(rlfa)); + isis_rlfa_process_ldp_response(&rlfa); + break; + default: + break; + } + +stream_failure: + + return ret; +} + +static int isis_zebra_client_close_notify(ZAPI_CALLBACK_ARGS) +{ + int ret = 0; + + struct zapi_client_close_info info; + + if (zapi_client_close_notify_decode(zclient->ibuf, &info) < 0) + return -1; + + isis_ldp_sync_handle_client_close(&info); + isis_ldp_rlfa_handle_client_close(&info); + + return ret; +} + +/** + * Send SRv6 SID to ZEBRA for installation or deletion. + * + * @param cmd ZEBRA_ROUTE_ADD or ZEBRA_ROUTE_DELETE + * @param sid SRv6 SID to install or delete + * @param prefixlen Prefix length + * @param oif Outgoing interface + * @param action SID action + * @param context SID context + */ +static void isis_zebra_send_localsid(int cmd, const struct in6_addr *sid, + uint16_t prefixlen, ifindex_t oif, + enum seg6local_action_t action, + const struct seg6local_context *context) +{ + struct prefix_ipv6 p = {}; + struct zapi_route api = {}; + struct zapi_nexthop *znh; + + if (cmd != ZEBRA_ROUTE_ADD && cmd != ZEBRA_ROUTE_DELETE) { + flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong ZEBRA command", + __func__); + return; + } + + if (prefixlen > IPV6_MAX_BITLEN) { + flog_warn(EC_LIB_DEVELOPMENT, "%s: wrong prefixlen %u", + __func__, prefixlen); + return; + } + + sr_debug(" |- %s SRv6 SID %pI6 behavior %s", + cmd == ZEBRA_ROUTE_ADD ? "Add" : "Delete", sid, + seg6local_action2str(action)); + + p.family = AF_INET6; + p.prefixlen = prefixlen; + p.prefix = *sid; + + api.vrf_id = VRF_DEFAULT; + api.type = PROTO_TYPE; + api.instance = 0; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, &p, sizeof(p)); + + if (cmd == ZEBRA_ROUTE_DELETE) + return (void)zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, + &api); + + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + + znh = &api.nexthops[0]; + + memset(znh, 0, sizeof(*znh)); + + znh->type = NEXTHOP_TYPE_IFINDEX; + znh->ifindex = oif; + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL); + znh->seg6local_action = action; + memcpy(&znh->seg6local_ctx, context, sizeof(struct seg6local_context)); + + api.nexthop_num = 1; + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); +} + +/** + * Install SRv6 SID in the forwarding plane through Zebra. + * + * @param area IS-IS area + * @param sid SRv6 SID + */ +void isis_zebra_srv6_sid_install(struct isis_area *area, + struct isis_srv6_sid *sid) +{ + enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + uint16_t prefixlen = IPV6_MAX_BITLEN; + struct seg6local_context ctx = {}; + struct interface *ifp; + + if (!area || !sid) + return; + + sr_debug("ISIS-SRv6 (%s): setting SRv6 SID %pI6", area->area_tag, + &sid->sid); + + switch (sid->behavior) { + case SRV6_ENDPOINT_BEHAVIOR_END: + action = ZEBRA_SEG6_LOCAL_ACTION_END; + prefixlen = IPV6_MAX_BITLEN; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + action = ZEBRA_SEG6_LOCAL_ACTION_END; + prefixlen = sid->locator->block_bits_length + + sid->locator->node_bits_length; + SET_SRV6_FLV_OP(ctx.flv.flv_ops, + ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID); + ctx.flv.lcblock_len = sid->locator->block_bits_length; + ctx.flv.lcnode_func_len = sid->locator->node_bits_length; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_RESERVED: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: + case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + default: + zlog_err( + "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", + area->area_tag, sid->behavior); + return; + } + + /* Attach the SID to the SRv6 interface */ + ifp = if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn( + "Failed to install SRv6 SID %pI6: %s interface not found", + &sid->sid, area->srv6db.config.srv6_ifname); + return; + } + + /* Send the SID to zebra */ + isis_zebra_send_localsid(ZEBRA_ROUTE_ADD, &sid->sid, prefixlen, + ifp->ifindex, action, &ctx); +} + +/** + * Uninstall SRv6 SID from the forwarding plane through Zebra. + * + * @param area IS-IS area + * @param sid SRv6 SID + */ +void isis_zebra_srv6_sid_uninstall(struct isis_area *area, + struct isis_srv6_sid *sid) +{ + enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + struct interface *ifp; + uint16_t prefixlen = IPV6_MAX_BITLEN; + + if (!area || !sid) + return; + + sr_debug("ISIS-SRv6 (%s): delete SID %pI6", area->area_tag, &sid->sid); + + switch (sid->behavior) { + case SRV6_ENDPOINT_BEHAVIOR_END: + prefixlen = IPV6_MAX_BITLEN; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + prefixlen = sid->locator->block_bits_length + + sid->locator->node_bits_length; + break; + case SRV6_ENDPOINT_BEHAVIOR_RESERVED: + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: + case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + default: + zlog_err( + "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", + area->area_tag, sid->behavior); + return; + } + + /* The SID is attached to the SRv6 interface */ + ifp = if_lookup_by_name(area->srv6db.config.srv6_ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn("%s interface not found: nothing to uninstall", + area->srv6db.config.srv6_ifname); + return; + } + + /* Send delete request to zebra */ + isis_zebra_send_localsid(ZEBRA_ROUTE_DELETE, &sid->sid, prefixlen, + ifp->ifindex, action, NULL); +} + +void isis_zebra_srv6_adj_sid_install(struct srv6_adjacency *sra) +{ + enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + struct seg6local_context ctx = {}; + uint16_t prefixlen = IPV6_MAX_BITLEN; + struct interface *ifp; + struct isis_circuit *circuit; + struct isis_area *area; + + if (!sra) + return; + + circuit = sra->adj->circuit; + area = circuit->area; + + sr_debug("ISIS-SRv6 (%s): setting adjacency SID %pI6", area->area_tag, + &sra->sid); + + switch (sra->behavior) { + case SRV6_ENDPOINT_BEHAVIOR_END_X: + action = ZEBRA_SEG6_LOCAL_ACTION_END_X; + prefixlen = IPV6_MAX_BITLEN; + ctx.nh6 = sra->nexthop; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + action = ZEBRA_SEG6_LOCAL_ACTION_END_X; + prefixlen = sra->locator->block_bits_length + + sra->locator->node_bits_length + + sra->locator->function_bits_length; + ctx.nh6 = sra->nexthop; + SET_SRV6_FLV_OP(ctx.flv.flv_ops, + ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID); + ctx.flv.lcblock_len = sra->locator->block_bits_length; + ctx.flv.lcnode_func_len = sra->locator->node_bits_length; + break; + case SRV6_ENDPOINT_BEHAVIOR_RESERVED: + case SRV6_ENDPOINT_BEHAVIOR_END: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: + case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + default: + zlog_err( + "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", + area->area_tag, sra->behavior); + return; + } + + ifp = sra->adj->circuit->interface; + + isis_zebra_send_localsid(ZEBRA_ROUTE_ADD, &sra->sid, prefixlen, + ifp->ifindex, action, &ctx); +} + +void isis_zebra_srv6_adj_sid_uninstall(struct srv6_adjacency *sra) +{ + enum seg6local_action_t action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + struct interface *ifp; + uint16_t prefixlen = IPV6_MAX_BITLEN; + struct isis_circuit *circuit; + struct isis_area *area; + + if (!sra) + return; + + circuit = sra->adj->circuit; + area = circuit->area; + + switch (sra->behavior) { + case SRV6_ENDPOINT_BEHAVIOR_END_X: + prefixlen = IPV6_MAX_BITLEN; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + prefixlen = sra->locator->block_bits_length + + sra->locator->node_bits_length + + sra->locator->function_bits_length; + break; + case SRV6_ENDPOINT_BEHAVIOR_RESERVED: + case SRV6_ENDPOINT_BEHAVIOR_END: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: + case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: + case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + default: + zlog_err( + "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", + area->area_tag, sra->behavior); + return; + } + + ifp = sra->adj->circuit->interface; + + sr_debug("ISIS-SRv6 (%s): delete End.X SID %pI6", area->area_tag, + &sra->sid); + + isis_zebra_send_localsid(ZEBRA_ROUTE_DELETE, &sra->sid, prefixlen, + ifp->ifindex, action, NULL); +} + +/** + * Callback to process an SRv6 locator chunk received from SRv6 Manager (zebra). + * + * @result 0 on success, -1 otherwise + */ +static int isis_zebra_process_srv6_locator_chunk(ZAPI_CALLBACK_ARGS) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct stream *s = NULL; + struct listnode *node; + struct isis_area *area; + struct srv6_locator_chunk *c; + struct srv6_locator_chunk *chunk = srv6_locator_chunk_alloc(); + struct isis_srv6_sid *sid; + struct isis_adjacency *adj; + enum srv6_endpoint_behavior_codepoint behavior; + bool allocated = false; + + if (!isis) { + srv6_locator_chunk_free(&chunk); + return -1; + } + + /* Decode the received zebra message */ + s = zclient->ibuf; + if (zapi_srv6_locator_chunk_decode(s, chunk) < 0) { + srv6_locator_chunk_free(&chunk); + return -1; + } + + sr_debug( + "Received SRv6 locator chunk from zebra: name %s, " + "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u", + chunk->locator_name, &chunk->prefix, chunk->block_bits_length, + chunk->node_bits_length, chunk->function_bits_length, + chunk->argument_bits_length); + + /* Walk through all areas of the ISIS instance */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + if (strncmp(area->srv6db.config.srv6_locator_name, + chunk->locator_name, + sizeof(area->srv6db.config.srv6_locator_name)) != 0) + continue; + + for (ALL_LIST_ELEMENTS_RO(area->srv6db.srv6_locator_chunks, + node, c)) { + if (!prefix_cmp(&c->prefix, &chunk->prefix)) { + srv6_locator_chunk_free(&chunk); + return 0; + } + } + + sr_debug( + "SRv6 locator chunk (locator %s, prefix %pFX) assigned to IS-IS area %s", + chunk->locator_name, &chunk->prefix, area->area_tag); + + /* Add the SRv6 Locator chunk to the per-area chunks list */ + listnode_add(area->srv6db.srv6_locator_chunks, chunk); + + /* Decide which behavior to use,depending on the locator type + * (i.e. uSID vs classic locator) */ + behavior = (CHECK_FLAG(chunk->flags, SRV6_LOCATOR_USID)) + ? SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID + : SRV6_ENDPOINT_BEHAVIOR_END; + + /* Allocate new SRv6 End SID */ + sid = isis_srv6_sid_alloc(area, chunk, behavior, 0); + if (!sid) + return -1; + + /* Install the new SRv6 End SID in the forwarding plane through + * Zebra */ + isis_zebra_srv6_sid_install(area, sid); + + /* Store the SID */ + listnode_add(area->srv6db.srv6_sids, sid); + + /* Create SRv6 End.X SIDs from existing IS-IS Adjacencies */ + for (ALL_LIST_ELEMENTS_RO(area->adjacency_list, node, adj)) { + if (adj->ll_ipv6_count > 0) + srv6_endx_sid_add(adj); + } + + /* Regenerate LSPs to advertise the new locator and the SID */ + lsp_regenerate_schedule(area, area->is_type, 0); + + allocated = true; + break; + } + + if (!allocated) { + sr_debug("No IS-IS area configured for the locator %s", + chunk->locator_name); + srv6_locator_chunk_free(&chunk); + } + + return 0; +} + +/** + * Callback to process an SRv6 locator received from SRv6 Manager (zebra). + * + * @result 0 on success, -1 otherwise + */ +static int isis_zebra_process_srv6_locator_add(ZAPI_CALLBACK_ARGS) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct srv6_locator loc = {}; + struct listnode *node; + struct isis_area *area; + + if (!isis) + return -1; + + /* Decode the SRv6 locator */ + if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) + return -1; + + sr_debug( + "New SRv6 locator allocated in zebra: name %s, " + "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u", + loc.name, &loc.prefix, loc.block_bits_length, + loc.node_bits_length, loc.function_bits_length, + loc.argument_bits_length); + + /* Lookup on the IS-IS areas */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + /* If SRv6 is enabled on this area and the configured locator + * corresponds to the new locator, then request a chunk from the + * locator */ + if (area->srv6db.config.enabled && + strncmp(area->srv6db.config.srv6_locator_name, loc.name, + sizeof(area->srv6db.config.srv6_locator_name)) == 0) { + sr_debug( + "Sending a request to get a chunk from the SRv6 locator %s (%pFX) " + "for IS-IS area %s", + loc.name, &loc.prefix, area->area_tag); + + if (isis_zebra_srv6_manager_get_locator_chunk( + loc.name) < 0) + return -1; + } + } + + return 0; +} + +/** + * Callback to process a notification from SRv6 Manager (zebra) of an SRv6 + * locator deleted. + * + * @result 0 on success, -1 otherwise + */ +static int isis_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) +{ + struct isis *isis = isis_lookup_by_vrfid(VRF_DEFAULT); + struct srv6_locator loc = {}; + struct isis_area *area; + struct listnode *node, *nnode; + struct srv6_locator_chunk *chunk; + struct isis_srv6_sid *sid; + struct srv6_adjacency *sra; + + if (!isis) + return -1; + + /* Decode the received zebra message */ + if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) + return -1; + + sr_debug( + "SRv6 locator deleted in zebra: name %s, " + "prefix %pFX, block_len %u, node_len %u, func_len %u, arg_len %u", + loc.name, &loc.prefix, loc.block_bits_length, + loc.node_bits_length, loc.function_bits_length, + loc.argument_bits_length); + + /* Walk through all areas of the ISIS instance */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + if (strncmp(area->srv6db.config.srv6_locator_name, loc.name, + sizeof(area->srv6db.config.srv6_locator_name)) != 0) + continue; + + /* Delete SRv6 SIDs */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_sids, node, nnode, + sid)) { + + sr_debug( + "Deleting SRv6 SID (locator %s, sid %pI6) from IS-IS area %s", + area->srv6db.config.srv6_locator_name, + &sid->sid, area->area_tag); + + /* Uninstall the SRv6 SID from the forwarding plane + * through Zebra */ + isis_zebra_srv6_sid_uninstall(area, sid); + + listnode_delete(area->srv6db.srv6_sids, sid); + isis_srv6_sid_free(sid); + } + + /* Uninstall all local Adjacency-SIDs. */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_endx_sids, node, nnode, + sra)) + srv6_endx_sid_del(sra); + + /* Free the SRv6 locator chunks */ + for (ALL_LIST_ELEMENTS(area->srv6db.srv6_locator_chunks, node, + nnode, chunk)) { + if (prefix_match((struct prefix *)&loc.prefix, + (struct prefix *)&chunk->prefix)) { + listnode_delete( + area->srv6db.srv6_locator_chunks, + chunk); + srv6_locator_chunk_free(&chunk); + } + } + + /* Regenerate LSPs to advertise that the locator no longer + * exists */ + lsp_regenerate_schedule(area, area->is_type, 0); + } + + return 0; +} + +/** + * Request an SRv6 locator chunk to the SRv6 Manager (zebra) asynchronously. + * + * @param locator_name Name of SRv6 locator + * + * @result 0 on success, -1 otherwise + */ +int isis_zebra_srv6_manager_get_locator_chunk(const char *name) +{ + return srv6_manager_get_locator_chunk(zclient, name); +} + + +/** + * Release an SRv6 locator chunk. + * + * @param locator_name Name of SRv6 locator + * + * @result 0 on success, -1 otherwise + */ +int isis_zebra_srv6_manager_release_locator_chunk(const char *name) +{ + return srv6_manager_release_locator_chunk(zclient, name); +} + +static zclient_handler *const isis_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = isis_router_id_update_zebra, + [ZEBRA_INTERFACE_ADDRESS_ADD] = isis_zebra_if_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = isis_zebra_if_address_del, + [ZEBRA_INTERFACE_LINK_PARAMS] = isis_zebra_link_params, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = isis_zebra_read, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = isis_zebra_read, + + [ZEBRA_OPAQUE_MESSAGE] = isis_opaque_msg_handler, + + [ZEBRA_CLIENT_CLOSE_NOTIFY] = isis_zebra_client_close_notify, + + [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = + isis_zebra_process_srv6_locator_chunk, + [ZEBRA_SRV6_LOCATOR_ADD] = isis_zebra_process_srv6_locator_add, + [ZEBRA_SRV6_LOCATOR_DELETE] = isis_zebra_process_srv6_locator_delete, +}; + +void isis_zebra_init(struct event_loop *master, int instance) +{ + /* Initialize asynchronous zclient. */ + zclient = zclient_new(master, &zclient_options_default, isis_handlers, + array_size(isis_handlers)); + zclient_init(zclient, PROTO_TYPE, 0, &isisd_privs); + zclient->zebra_connected = isis_zebra_connected; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_sync, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_ISIS; + zclient_sync->instance = instance; + /* + * session_id must be different from default value (0) to distinguish + * the asynchronous socket from the synchronous one + */ + zclient_sync->session_id = 1; + zclient_sync->privs = &isisd_privs; +} + +void isis_zebra_stop(void) +{ + zclient_unregister_opaque(zclient, LDP_RLFA_LABELS); + zclient_unregister_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE); + zclient_unregister_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE); + zclient_stop(zclient_sync); + zclient_free(zclient_sync); + zclient_stop(zclient); + zclient_free(zclient); + frr_fini(); +} diff --git a/isisd/isis_zebra.h b/isisd/isis_zebra.h new file mode 100644 index 0000000..f1684b7 --- /dev/null +++ b/isisd/isis_zebra.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isis_zebra.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#ifndef _ZEBRA_ISIS_ZEBRA_H +#define _ZEBRA_ISIS_ZEBRA_H + +#include "isisd.h" + +extern struct zclient *zclient; + +struct label_chunk { + uint32_t start; + uint32_t end; + uint64_t used_mask; +}; +#define CHUNK_SIZE 64 + +void isis_zebra_init(struct event_loop *master, int instance); +void isis_zebra_stop(void); + +struct isis_route_info; +struct sr_adjacency; + +void isis_zebra_route_add_route(struct isis *isis, + struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info); +void isis_zebra_route_del_route(struct isis *isis, + struct prefix *prefix, + struct prefix_ipv6 *src_p, + struct isis_route_info *route_info); +void isis_zebra_prefix_sid_install(struct isis_area *area, + struct prefix *prefix, + struct isis_sr_psid_info *psid); +void isis_zebra_prefix_sid_uninstall(struct isis_area *area, + struct prefix *prefix, + struct isis_route_info *rinfo, + struct isis_sr_psid_info *psid); +void isis_zebra_send_adjacency_sid(int cmd, const struct sr_adjacency *sra); +int isis_distribute_list_update(int routetype); +void isis_zebra_redistribute_set(afi_t afi, int type, vrf_id_t vrf_id, + uint16_t tableid); +void isis_zebra_redistribute_unset(afi_t afi, int type, vrf_id_t vrf_id, + uint16_t tableid); +int isis_zebra_rlfa_register(struct isis_spftree *spftree, struct rlfa *rlfa); +void isis_zebra_rlfa_unregister_all(struct isis_spftree *spftree); +bool isis_zebra_label_manager_ready(void); +int isis_zebra_label_manager_connect(void); +int isis_zebra_request_label_range(uint32_t base, uint32_t chunk_size); +int isis_zebra_release_label_range(uint32_t start, uint32_t end); +void isis_zebra_vrf_register(struct isis *isis); +void isis_zebra_vrf_deregister(struct isis *isis); +int isis_zebra_ls_register(bool up); + +extern void isis_zebra_srv6_sid_install(struct isis_area *area, + struct isis_srv6_sid *sid); +extern void isis_zebra_srv6_sid_uninstall(struct isis_area *area, + struct isis_srv6_sid *sid); + +void isis_zebra_srv6_adj_sid_install(struct srv6_adjacency *sra); +void isis_zebra_srv6_adj_sid_uninstall(struct srv6_adjacency *sra); + +extern int isis_zebra_srv6_manager_get_locator_chunk(const char *name); +extern int isis_zebra_srv6_manager_release_locator_chunk(const char *name); + +#endif /* _ZEBRA_ISIS_ZEBRA_H */ diff --git a/isisd/isisd.c b/isisd/isisd.c new file mode 100644 index 0000000..982df08 --- /dev/null +++ b/isisd/isisd.c @@ -0,0 +1,3929 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isisd.c + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "time.h" +#include "linklist.h" +#include "if.h" +#include "hash.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "prefix.h" +#include "table.h" +#include "qobj.h" +#include "zclient.h" +#include "vrf.h" +#include "spf_backoff.h" +#include "flex_algo.h" +#include "lib/northbound_cli.h" +#include "bfd.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_flags.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_csm.h" +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_adjacency.h" +#include "isisd/isis_pdu.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_constants.h" +#include "isisd/isis_lsp.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_route.h" +#include "isisd/isis_zebra.h" +#include "isisd/isis_events.h" +#include "isisd/isis_te.h" +#include "isisd/isis_mt.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_flex_algo.h" +#include "isisd/fabricd.h" +#include "isisd/isis_nb.h" + +/* For debug statement. */ +unsigned long debug_adj_pkt; +unsigned long debug_snp_pkt; +unsigned long debug_update_pkt; +unsigned long debug_spf_events; +unsigned long debug_rte_events; +unsigned long debug_events; +unsigned long debug_pkt_dump; +unsigned long debug_lsp_gen; +unsigned long debug_lsp_sched; +unsigned long debug_flooding; +unsigned long debug_bfd; +unsigned long debug_tx_queue; +unsigned long debug_sr; +unsigned long debug_ldp_sync; +unsigned long debug_lfa; +unsigned long debug_te; + +DEFINE_MGROUP(ISISD, "isisd"); + +DEFINE_MTYPE_STATIC(ISISD, ISIS, "ISIS process"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_NAME, "ISIS process name"); +DEFINE_MTYPE_STATIC(ISISD, ISIS_AREA, "ISIS area"); +DEFINE_MTYPE(ISISD, ISIS_AREA_ADDR, "ISIS area address"); +DEFINE_MTYPE(ISISD, ISIS_ACL_NAME, "ISIS access-list name"); +DEFINE_MTYPE(ISISD, ISIS_PLIST_NAME, "ISIS prefix-list name"); + +DEFINE_QOBJ_TYPE(isis_area); + +/* ISIS process wide configuration. */ +static struct isis_master isis_master; + +/* ISIS process wide configuration pointer to export. */ +struct isis_master *im; + +/* ISIS config processing thread */ +struct event *t_isis_cfg; + +#ifndef FABRICD +DEFINE_HOOK(isis_hook_db_overload, (const struct isis_area *area), (area)); +#endif /* ifndef FABRICD */ + +/* + * Prototypes. + */ +int isis_area_get(struct vty *, const char *); +int area_net_title(struct vty *, const char *); +int area_clear_net_title(struct vty *, const char *); +int show_isis_interface_common(struct vty *, struct json_object *json, + const char *ifname, char, const char *vrf_name, + bool all_vrf); +int show_isis_interface_common_vty(struct vty *, const char *ifname, char, + const char *vrf_name, bool all_vrf); +int show_isis_interface_common_json(struct json_object *json, + const char *ifname, char, + const char *vrf_name, bool all_vrf); +int show_isis_neighbor_common(struct vty *, struct json_object *json, + const char *id, char, const char *vrf_name, + bool all_vrf); +int clear_isis_neighbor_common(struct vty *, const char *id, + const char *vrf_name, bool all_vrf); + +/* Link ISIS instance to VRF. */ +void isis_vrf_link(struct isis *isis, struct vrf *vrf) +{ + isis->vrf_id = vrf->vrf_id; + if (vrf->info != (void *)isis) + vrf->info = (void *)isis; +} + +/* Unlink ISIS instance to VRF. */ +void isis_vrf_unlink(struct isis *isis, struct vrf *vrf) +{ + if (vrf->info == (void *)isis) + vrf->info = NULL; + isis->vrf_id = VRF_UNKNOWN; +} + +struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id) +{ + struct isis *isis; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + if (isis->vrf_id == vrf_id) + return isis; + + return NULL; +} + +struct isis *isis_lookup_by_vrfname(const char *vrfname) +{ + struct isis *isis; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + if (isis->name && vrfname && strcmp(isis->name, vrfname) == 0) + return isis; + + return NULL; +} + +struct isis *isis_lookup_by_sysid(const uint8_t *sysid) +{ + struct isis *isis; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + if (!memcmp(isis->sysid, sysid, ISIS_SYS_ID_LEN)) + return isis; + + return NULL; +} + +void isis_master_init(struct event_loop *master) +{ + memset(&isis_master, 0, sizeof(isis_master)); + im = &isis_master; + im->isis = list_new(); + im->master = master; +} + +struct isis *isis_new(const char *vrf_name) +{ + struct vrf *vrf; + struct isis *isis; + + isis = XCALLOC(MTYPE_ISIS, sizeof(struct isis)); + + isis->name = XSTRDUP(MTYPE_ISIS_NAME, vrf_name); + + vrf = vrf_lookup_by_name(vrf_name); + + if (vrf) + isis_vrf_link(isis, vrf); + else + isis->vrf_id = VRF_UNKNOWN; + + isis_zebra_vrf_register(isis); + + if (IS_DEBUG_EVENTS) + zlog_debug( + "%s: Create new isis instance with vrf_name %s vrf_id %u", + __func__, isis->name, isis->vrf_id); + + /* + * Default values + */ + isis->max_area_addrs = ISIS_DEFAULT_MAX_AREA_ADDRESSES; + isis->process_id = getpid(); + isis->router_id = 0; + isis->area_list = list_new(); + isis->uptime = time(NULL); + isis->snmp_notifications = 1; + dyn_cache_init(isis); + + listnode_add(im->isis, isis); + + return isis; +} + +void isis_finish(struct isis *isis) +{ + struct isis_area *area; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(isis->area_list, node, nnode, area)) + isis_area_destroy(area); + + struct vrf *vrf = NULL; + + listnode_delete(im->isis, isis); + + isis_zebra_vrf_deregister(isis); + + vrf = vrf_lookup_by_name(isis->name); + if (vrf) + isis_vrf_unlink(isis, vrf); + XFREE(MTYPE_ISIS_NAME, isis->name); + + isis_redist_free(isis); + list_delete(&isis->area_list); + dyn_cache_finish(isis); + XFREE(MTYPE_ISIS, isis); +} + +void isis_area_add_circuit(struct isis_area *area, struct isis_circuit *circuit) +{ + isis_csm_state_change(ISIS_ENABLE, circuit, area); + + area->ip_circuits += circuit->ip_router; + area->ipv6_circuits += circuit->ipv6_router; + + area->lfa_protected_links[0] += circuit->lfa_protection[0]; + area->rlfa_protected_links[0] += circuit->rlfa_protection[0]; + area->tilfa_protected_links[0] += circuit->tilfa_protection[0]; + + area->lfa_protected_links[1] += circuit->lfa_protection[1]; + area->rlfa_protected_links[1] += circuit->rlfa_protection[1]; + area->tilfa_protected_links[1] += circuit->tilfa_protection[1]; +} + +void isis_area_del_circuit(struct isis_area *area, struct isis_circuit *circuit) +{ + area->ip_circuits -= circuit->ip_router; + area->ipv6_circuits -= circuit->ipv6_router; + + area->lfa_protected_links[0] -= circuit->lfa_protection[0]; + area->rlfa_protected_links[0] -= circuit->rlfa_protection[0]; + area->tilfa_protected_links[0] -= circuit->tilfa_protection[0]; + + area->lfa_protected_links[1] -= circuit->lfa_protection[1]; + area->rlfa_protected_links[1] -= circuit->rlfa_protection[1]; + area->tilfa_protected_links[1] -= circuit->tilfa_protection[1]; + + isis_csm_state_change(ISIS_DISABLE, circuit, area); +} + +static void delete_area_addr(void *arg) +{ + struct iso_address *addr = (struct iso_address *)arg; + + XFREE(MTYPE_ISIS_AREA_ADDR, addr); +} + +struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) +{ + struct isis_area *area; + struct isis *isis = NULL; + struct vrf *vrf = NULL; + struct interface *ifp; + struct isis_circuit *circuit; + + area = XCALLOC(MTYPE_ISIS_AREA, sizeof(struct isis_area)); + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(vrf_name); + isis = isis_lookup_by_vrfname(vrf_name); + + if (isis == NULL) + isis = isis_new(vrf_name); + + listnode_add(isis->area_list, area); + area->isis = isis; + + /* + * Fabricd runs only as level-2. + * For IS-IS, the default is level-1-2 + */ + if (fabricd) + area->is_type = IS_LEVEL_2; + else + area->is_type = yang_get_default_enum( + "/frr-isisd:isis/instance/is-type"); + + /* + * intialize the databases + */ + if (area->is_type & IS_LEVEL_1) + lsp_db_init(&area->lspdb[0]); + if (area->is_type & IS_LEVEL_2) + lsp_db_init(&area->lspdb[1]); + +#ifndef FABRICD + /* Flex-Algo */ + area->flex_algos = flex_algos_alloc(isis_flex_algo_data_alloc, + isis_flex_algo_data_free); +#endif /* ifndef FABRICD */ + + spftree_area_init(area); + + area->circuit_list = list_new(); + area->adjacency_list = list_new(); + area->area_addrs = list_new(); + area->area_addrs->del = delete_area_addr; + + if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + event_add_timer(master, lsp_tick, area, 1, &area->t_tick); + flags_initialize(&area->flags); + + isis_sr_area_init(area); + isis_srv6_area_init(area); + + /* + * Default values + */ +#ifndef FABRICD + enum isis_metric_style default_style; + + area->max_lsp_lifetime[0] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-1/maximum-lifetime"); + area->max_lsp_lifetime[1] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-2/maximum-lifetime"); + area->lsp_refresh[0] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-1/refresh-interval"); + area->lsp_refresh[1] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-2/refresh-interval"); + area->lsp_gen_interval[0] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-1/generation-interval"); + area->lsp_gen_interval[1] = yang_get_default_uint16( + "/frr-isisd:isis/instance/lsp/timers/level-2/generation-interval"); + area->min_spf_interval[0] = yang_get_default_uint16( + "/frr-isisd:isis/instance/spf/minimum-interval/level-1"); + area->min_spf_interval[1] = yang_get_default_uint16( + "/frr-isisd:isis/instance/spf/minimum-interval/level-1"); + area->dynhostname = yang_get_default_bool( + "/frr-isisd:isis/instance/dynamic-hostname"); + default_style = + yang_get_default_enum("/frr-isisd:isis/instance/metric-style"); + area->oldmetric = default_style == ISIS_WIDE_METRIC ? 0 : 1; + area->newmetric = default_style == ISIS_NARROW_METRIC ? 0 : 1; + area->lsp_frag_threshold = 90; /* not currently configurable */ + area->lsp_mtu = + yang_get_default_uint16("/frr-isisd:isis/instance/lsp/mtu"); + area->lfa_load_sharing[0] = yang_get_default_bool( + "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing"); + area->lfa_load_sharing[1] = yang_get_default_bool( + "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing"); + area->attached_bit_send = + yang_get_default_bool("/frr-isisd:isis/instance/attach-send"); + area->attached_bit_rcv_ignore = yang_get_default_bool( + "/frr-isisd:isis/instance/attach-receive-ignore"); + +#else + area->max_lsp_lifetime[0] = DEFAULT_LSP_LIFETIME; /* 1200 */ + area->max_lsp_lifetime[1] = DEFAULT_LSP_LIFETIME; /* 1200 */ + area->lsp_refresh[0] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */ + area->lsp_refresh[1] = DEFAULT_MAX_LSP_GEN_INTERVAL; /* 900 */ + area->lsp_gen_interval[0] = DEFAULT_MIN_LSP_GEN_INTERVAL; + area->lsp_gen_interval[1] = DEFAULT_MIN_LSP_GEN_INTERVAL; + area->min_spf_interval[0] = MINIMUM_SPF_INTERVAL; + area->min_spf_interval[1] = MINIMUM_SPF_INTERVAL; + area->dynhostname = 1; + area->oldmetric = 0; + area->newmetric = 1; + area->lsp_frag_threshold = 90; + area->lsp_mtu = DEFAULT_LSP_MTU; + area->lfa_load_sharing[0] = true; + area->lfa_load_sharing[1] = true; + area->attached_bit_send = true; + area->attached_bit_rcv_ignore = false; +#endif /* ifndef FABRICD */ + area->lfa_priority_limit[0] = SPF_PREFIX_PRIO_LOW; + area->lfa_priority_limit[1] = SPF_PREFIX_PRIO_LOW; + isis_lfa_tiebreakers_init(area, ISIS_LEVEL1); + isis_lfa_tiebreakers_init(area, ISIS_LEVEL2); + + area_mt_init(area); + + area->area_tag = strdup(area_tag); + + if (fabricd) + area->fabricd = fabricd_new(area); + + area->lsp_refresh_arg[0].area = area; + area->lsp_refresh_arg[0].level = IS_LEVEL_1; + area->lsp_refresh_arg[1].area = area; + area->lsp_refresh_arg[1].level = IS_LEVEL_2; + + area->bfd_signalled_down = false; + area->bfd_force_spf_refresh = false; + + QOBJ_REG(area, isis_area); + + if (vrf) { + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->ifindex == IFINDEX_INTERNAL) + continue; + + circuit = ifp->info; + if (circuit && strmatch(circuit->tag, area->area_tag)) + isis_area_add_circuit(area, circuit); + } + } + + return area; +} + +struct isis_area *isis_area_lookup_by_vrf(const char *area_tag, + const char *vrf_name) +{ + struct isis_area *area; + struct listnode *node; + struct isis *isis = NULL; + + isis = isis_lookup_by_vrfname(vrf_name); + if (isis == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + if (strcmp(area->area_tag, area_tag) == 0) + return area; + + return NULL; +} + +struct isis_area *isis_area_lookup(const char *area_tag, vrf_id_t vrf_id) +{ + struct isis_area *area; + struct listnode *node; + struct isis *isis; + + isis = isis_lookup_by_vrfid(vrf_id); + if (isis == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + if ((area->area_tag == NULL && area_tag == NULL) + || (area->area_tag && area_tag + && strcmp(area->area_tag, area_tag) == 0)) + return area; + + return NULL; +} + +int isis_area_get(struct vty *vty, const char *area_tag) +{ + struct isis_area *area; + + area = isis_area_lookup(area_tag, VRF_DEFAULT); + + if (area) { + VTY_PUSH_CONTEXT(ROUTER_NODE, area); + return CMD_SUCCESS; + } + + area = isis_area_create(area_tag, VRF_DEFAULT_NAME); + + if (IS_DEBUG_EVENTS) + zlog_debug("New IS-IS area instance %s", area->area_tag); + + VTY_PUSH_CONTEXT(ROUTER_NODE, area); + + return CMD_SUCCESS; +} + +void isis_area_destroy(struct isis_area *area) +{ + struct listnode *node, *nnode; + struct isis_circuit *circuit; + + QOBJ_UNREG(area); + + if (fabricd) + fabricd_finish(area->fabricd); + + if (area->circuit_list) { + for (ALL_LIST_ELEMENTS(area->circuit_list, node, nnode, + circuit)) + isis_area_del_circuit(area, circuit); + + list_delete(&area->circuit_list); + } + if (area->flags.free_idcs) + list_delete(&area->flags.free_idcs); + + list_delete(&area->adjacency_list); + + lsp_db_fini(&area->lspdb[0]); + lsp_db_fini(&area->lspdb[1]); + + /* invalidate and verify to delete all routes from zebra */ + isis_area_invalidate_routes(area, area->is_type); + isis_area_verify_routes(area); + +#ifndef FABRICD + flex_algos_free(area->flex_algos); +#endif /* ifndef FABRICD */ + + isis_sr_area_term(area); + isis_srv6_area_term(area); + + isis_mpls_te_term(area); + + spftree_area_del(area); + + if (area->spf_timer[0]) + isis_spf_timer_free(EVENT_ARG(area->spf_timer[0])); + EVENT_OFF(area->spf_timer[0]); + if (area->spf_timer[1]) + isis_spf_timer_free(EVENT_ARG(area->spf_timer[1])); + EVENT_OFF(area->spf_timer[1]); + + spf_backoff_free(area->spf_delay_ietf[0]); + spf_backoff_free(area->spf_delay_ietf[1]); + + if (!CHECK_FLAG(im->options, F_ISIS_UNIT_TEST)) + isis_redist_area_finish(area); + + list_delete(&area->area_addrs); + + for (int i = SPF_PREFIX_PRIO_CRITICAL; i <= SPF_PREFIX_PRIO_MEDIUM; + i++) { + struct spf_prefix_priority_acl *ppa; + + ppa = &area->spf_prefix_priorities[i]; + XFREE(MTYPE_ISIS_ACL_NAME, ppa->name); + } + isis_lfa_tiebreakers_clear(area, ISIS_LEVEL1); + isis_lfa_tiebreakers_clear(area, ISIS_LEVEL2); + + EVENT_OFF(area->t_tick); + EVENT_OFF(area->t_lsp_refresh[0]); + EVENT_OFF(area->t_lsp_refresh[1]); + EVENT_OFF(area->t_rlfa_rib_update); + + event_cancel_event(master, area); + + listnode_delete(area->isis->area_list, area); + + free(area->area_tag); + + area_mt_finish(area); + + if (area->rlfa_plist_name[0]) + XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[0]); + if (area->rlfa_plist_name[1]) + XFREE(MTYPE_ISIS_PLIST_NAME, area->rlfa_plist_name[1]); + + XFREE(MTYPE_ISIS_AREA, area); + +} + +/* This is hook function for vrf create called as part of vrf_init */ +static int isis_vrf_new(struct vrf *vrf) +{ + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF Created: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +/* This is hook function for vrf delete call as part of vrf_init */ +static int isis_vrf_delete(struct vrf *vrf) +{ + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF Deletion: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static void isis_set_redist_vrf_bitmaps(struct isis *isis, bool set) +{ + struct listnode *node, *lnode; + struct isis_area *area; + int type; + int level; + int protocol; + struct isis_redist *redist; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) + for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++) + for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++) + for (level = 0; level < ISIS_LEVELS; level++) { + if (area->redist_settings[protocol][type] + [level] == NULL) + continue; + for (ALL_LIST_ELEMENTS_RO(area->redist_settings + [protocol] + [type] + [level], + lnode, + redist)) { + if (redist->redist == 0) + continue; + /* This field is actually + * controlling transmission of + * the IS-IS + * routes to Zebra and has + * nothing to do with + * redistribution, + * so skip it. */ + afi_t afi = + afi_for_redist_protocol( + protocol); + + if (type == DEFAULT_ROUTE) { + if (set) + vrf_bitmap_set( + &zclient->default_information + [afi], + isis->vrf_id); + else + vrf_bitmap_unset( + &zclient->default_information + [afi], + isis->vrf_id); + } else { + if (set) + vrf_bitmap_set( + &zclient->redist + [afi] + [type], + isis->vrf_id); + else + vrf_bitmap_unset( + &zclient->redist + [afi] + [type], + isis->vrf_id); + } + } + } +} + +static int isis_vrf_enable(struct vrf *vrf) +{ + struct isis *isis; + vrf_id_t old_vrf_id; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF %s id %u enabled", __func__, vrf->name, + vrf->vrf_id); + + isis = isis_lookup_by_vrfname(vrf->name); + if (isis && isis->vrf_id != vrf->vrf_id) { + old_vrf_id = isis->vrf_id; + /* We have instance configured, link to VRF and make it "up". */ + isis_vrf_link(isis, vrf); + if (IS_DEBUG_EVENTS) + zlog_debug( + "%s: isis linked to vrf %s vrf_id %u (old id %u)", + __func__, vrf->name, isis->vrf_id, old_vrf_id); + /* start zebra redist to us for new vrf */ + isis_set_redist_vrf_bitmaps(isis, true); + + isis_zebra_vrf_register(isis); + } + + return 0; +} + +static int isis_vrf_disable(struct vrf *vrf) +{ + struct isis *isis; + vrf_id_t old_vrf_id = VRF_UNKNOWN; + + if (vrf->vrf_id == VRF_DEFAULT) + return 0; + + if (IS_DEBUG_EVENTS) + zlog_debug("%s: VRF %s id %d disabled.", __func__, vrf->name, + vrf->vrf_id); + isis = isis_lookup_by_vrfname(vrf->name); + if (isis) { + old_vrf_id = isis->vrf_id; + + isis_zebra_vrf_deregister(isis); + + isis_set_redist_vrf_bitmaps(isis, false); + + /* We have instance configured, unlink + * from VRF and make it "down". + */ + isis_vrf_unlink(isis, vrf); + if (IS_DEBUG_EVENTS) + zlog_debug("%s: isis old_vrf_id %d unlinked", __func__, + old_vrf_id); + } + + return 0; +} + +void isis_vrf_init(void) +{ + vrf_init(isis_vrf_new, isis_vrf_enable, isis_vrf_disable, + isis_vrf_delete); + + vrf_cmd_init(NULL); +} + +void isis_terminate(void) +{ + struct isis *isis; + struct listnode *node, *nnode; + + bfd_protocol_integration_set_shutdown(true); + + if (listcount(im->isis) == 0) + return; + + for (ALL_LIST_ELEMENTS(im->isis, node, nnode, isis)) + isis_finish(isis); +} + +void isis_filter_update(struct access_list *access) +{ + struct isis *isis; + struct isis_area *area; + struct listnode *node, *anode; + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + for (int i = SPF_PREFIX_PRIO_CRITICAL; + i <= SPF_PREFIX_PRIO_MEDIUM; i++) { + struct spf_prefix_priority_acl *ppa; + + ppa = &area->spf_prefix_priorities[i]; + ppa->list_v4 = + access_list_lookup(AFI_IP, ppa->name); + ppa->list_v6 = + access_list_lookup(AFI_IP6, ppa->name); + } + lsp_regenerate_schedule(area, area->is_type, 0); + } + } +} + +void isis_prefix_list_update(struct prefix_list *plist) +{ + struct isis *isis; + struct isis_area *area; + struct listnode *node, *anode; + + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; + level++) { + const char *plist_name = + prefix_list_name(plist); + + if (!area->rlfa_plist_name[level - 1]) + continue; + + if (!strmatch(area->rlfa_plist_name[level - 1], + plist_name)) + continue; + + area->rlfa_plist[level - 1] = + prefix_list_lookup(AFI_IP, plist_name); + lsp_regenerate_schedule(area, area->is_type, 0); + } + } + } +} + +#ifdef FABRICD +static void area_set_mt_enabled(struct isis_area *area, uint16_t mtid, + bool enabled) +{ + struct isis_area_mt_setting *setting; + + setting = area_get_mt_setting(area, mtid); + if (setting->enabled != enabled) { + setting->enabled = enabled; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + } +} + +static void area_set_mt_overload(struct isis_area *area, uint16_t mtid, + bool overload) +{ + struct isis_area_mt_setting *setting; + + setting = area_get_mt_setting(area, mtid); + if (setting->overload != overload) { + setting->overload = overload; + if (setting->enabled) + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, + 0); + } +} +#endif /* ifdef FABRICD */ + +int area_net_title(struct vty *vty, const char *net_title) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + struct iso_address *addr; + struct iso_address *addrp; + struct listnode *node; + + uint8_t buff[255]; + + /* We check that we are not over the maximal number of addresses */ + if (listcount(area->area_addrs) >= area->isis->max_area_addrs) { + vty_out(vty, + "Maximum of area addresses (%d) already reached \n", + area->isis->max_area_addrs); + return CMD_ERR_NOTHING_TODO; + } + + addr = XMALLOC(MTYPE_ISIS_AREA_ADDR, sizeof(struct iso_address)); + addr->addr_len = dotformat2buff(buff, net_title); + memcpy(addr->area_addr, buff, addr->addr_len); +#ifdef EXTREME_DEBUG + zlog_debug("added area address %s for area %s (address length %d)", + net_title, area->area_tag, addr->addr_len); +#endif /* EXTREME_DEBUG */ + if (addr->addr_len < ISO_ADDR_MIN || addr->addr_len > ISO_ADDR_SIZE) { + vty_out(vty, + "area address must be at least 8..20 octets long (%d)\n", + addr->addr_len); + XFREE(MTYPE_ISIS_AREA_ADDR, addr); + return CMD_WARNING_CONFIG_FAILED; + } + + if (addr->area_addr[addr->addr_len - 1] != 0) { + vty_out(vty, + "nsel byte (last byte) in area address must be 0\n"); + XFREE(MTYPE_ISIS_AREA_ADDR, addr); + return CMD_WARNING_CONFIG_FAILED; + } + + if (area->isis->sysid_set == 0) { + /* + * First area address - get the SystemID for this router + */ + memcpy(area->isis->sysid, GETSYSID(addr), ISIS_SYS_ID_LEN); + area->isis->sysid_set = 1; + if (IS_DEBUG_EVENTS) + zlog_debug("Router has SystemID %pSY", + area->isis->sysid); + } else { + /* + * Check that the SystemID portions match + */ + if (memcmp(area->isis->sysid, GETSYSID(addr), + ISIS_SYS_ID_LEN)) { + vty_out(vty, + "System ID must not change when defining additional area addresses\n"); + XFREE(MTYPE_ISIS_AREA_ADDR, addr); + return CMD_WARNING_CONFIG_FAILED; + } + + /* now we see that we don't already have this address */ + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) { + if ((addrp->addr_len + ISIS_SYS_ID_LEN + ISIS_NSEL_LEN) + != (addr->addr_len)) + continue; + if (!memcmp(addrp->area_addr, addr->area_addr, + addr->addr_len)) { + XFREE(MTYPE_ISIS_AREA_ADDR, addr); + return CMD_SUCCESS; /* silent fail */ + } + } + } + + /* + * Forget the systemID part of the address + */ + addr->addr_len -= (ISIS_SYS_ID_LEN + ISIS_NSEL_LEN); + listnode_add(area->area_addrs, addr); + + /* only now we can safely generate our LSPs for this area */ + if (listcount(area->area_addrs) > 0) { + if (area->is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (area->is_type & IS_LEVEL_2) + lsp_generate(area, IS_LEVEL_2); + } + + return CMD_SUCCESS; +} + +int area_clear_net_title(struct vty *vty, const char *net_title) +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + struct iso_address addr, *addrp = NULL; + struct listnode *node; + uint8_t buff[255]; + + addr.addr_len = dotformat2buff(buff, net_title); + if (addr.addr_len < ISO_ADDR_MIN || addr.addr_len > ISO_ADDR_SIZE) { + vty_out(vty, + "Unsupported area address length %d, should be 8...20 \n", + addr.addr_len); + return CMD_WARNING_CONFIG_FAILED; + } + + memcpy(addr.area_addr, buff, (int)addr.addr_len); + + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node, addrp)) + if ((addrp->addr_len + ISIS_SYS_ID_LEN + 1) == addr.addr_len + && !memcmp(addrp->area_addr, addr.area_addr, addr.addr_len)) + break; + + if (!addrp) { + vty_out(vty, "No area address %s for area %s \n", net_title, + area->area_tag); + return CMD_ERR_NO_MATCH; + } + + listnode_delete(area->area_addrs, addrp); + XFREE(MTYPE_ISIS_AREA_ADDR, addrp); + + /* + * Last area address - reset the SystemID for this router + */ + if (listcount(area->area_addrs) == 0) { + memset(area->isis->sysid, 0, ISIS_SYS_ID_LEN); + area->isis->sysid_set = 0; + if (IS_DEBUG_EVENTS) + zlog_debug("Router has no SystemID"); + } + + return CMD_SUCCESS; +} + +/* + * 'show isis interface' command + */ +int show_isis_interface_common(struct vty *vty, struct json_object *json, + const char *ifname, char detail, + const char *vrf_name, bool all_vrf) +{ + if (json) { + return show_isis_interface_common_json(json, ifname, detail, + vrf_name, all_vrf); + } else { + return show_isis_interface_common_vty(vty, ifname, detail, + vrf_name, all_vrf); + } +} + +int show_isis_interface_common_json(struct json_object *json, + const char *ifname, char detail, + const char *vrf_name, bool all_vrf) +{ + struct listnode *anode, *cnode, *inode; + struct isis_area *area; + struct isis_circuit *circuit; + struct isis *isis; + struct json_object *areas_json, *area_json; + struct json_object *circuits_json, *circuit_json; + if (!im) { + // IS-IS Routing Process not enabled + json_object_string_add(json, "is-is-routing-process-enabled", + "no"); + return CMD_SUCCESS; + } + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + areas_json = json_object_new_array(); + json_object_object_add(json, "areas", areas_json); + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + area_json = json_object_new_object(); + json_object_string_add(area_json, "area", + area->area_tag + ? area->area_tag + : "null"); + circuits_json = json_object_new_array(); + json_object_object_add(area_json, "circuits", + circuits_json); + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, + cnode, circuit)) { + circuit_json = json_object_new_object(); + json_object_int_add( + circuit_json, "circuit", + circuit->circuit_id); + if (!ifname) + isis_circuit_print_json(circuit, + circuit_json, + detail); + else if (strcmp(circuit->interface->name, + ifname) == 0) + isis_circuit_print_json(circuit, + circuit_json, + detail); + json_object_array_add(circuits_json, + circuit_json); + } + json_object_array_add(areas_json, area_json); + } + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + areas_json = json_object_new_array(); + json_object_object_add(json, "areas", areas_json); + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + area_json = json_object_new_object(); + json_object_string_add(area_json, "area", + area->area_tag ? area->area_tag + : "null"); + + circuits_json = json_object_new_array(); + json_object_object_add(area_json, "circuits", + circuits_json); + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, + circuit)) { + circuit_json = json_object_new_object(); + json_object_int_add(circuit_json, "circuit", + circuit->circuit_id); + if (!ifname) + isis_circuit_print_json(circuit, + circuit_json, + detail); + else if (strcmp(circuit->interface->name, + ifname) == 0) + isis_circuit_print_json(circuit, + circuit_json, + detail); + json_object_array_add(circuits_json, + circuit_json); + } + json_object_array_add(areas_json, area_json); + } + } + + return CMD_SUCCESS; +} + +int show_isis_interface_common_vty(struct vty *vty, const char *ifname, + char detail, const char *vrf_name, + bool all_vrf) +{ + struct listnode *anode, *cnode, *inode; + struct isis_area *area; + struct isis_circuit *circuit; + struct isis *isis; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + vty_out(vty, "Area %s:\n", area->area_tag); + + if (detail == ISIS_UI_LEVEL_BRIEF) + vty_out(vty, + " Interface CircId State Type Level\n"); + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, + cnode, circuit)) + if (!ifname) + isis_circuit_print_vty(circuit, + vty, + detail); + else if (strcmp(circuit->interface->name, + ifname) == 0) + isis_circuit_print_vty(circuit, + vty, + detail); + } + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + vty_out(vty, "Area %s:\n", area->area_tag); + + if (detail == ISIS_UI_LEVEL_BRIEF) + vty_out(vty, + " Interface CircId State Type Level\n"); + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, + circuit)) + if (!ifname) + isis_circuit_print_vty(circuit, vty, + detail); + else if (strcmp(circuit->interface->name, + ifname) == 0) + isis_circuit_print_vty(circuit, vty, + detail); + } + } + + return CMD_SUCCESS; +} + +DEFUN(show_isis_interface, + show_isis_interface_cmd, + "show " PROTO_NAME " [vrf ] interface [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All VRFs\n" + "json output\n" + "IS-IS interface\n") +{ + int res = CMD_SUCCESS; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + res = show_isis_interface_common(vty, json, NULL, ISIS_UI_LEVEL_BRIEF, + vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +DEFUN(show_isis_interface_detail, + show_isis_interface_detail_cmd, + "show " PROTO_NAME " [vrf ] interface detail [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All VRFs\n" + "IS-IS interface\n" + "show detailed information\n" + "json output\n") +{ + int res = CMD_SUCCESS; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + res = show_isis_interface_common(vty, json, NULL, ISIS_UI_LEVEL_DETAIL, + vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +DEFUN(show_isis_interface_arg, + show_isis_interface_arg_cmd, + "show " PROTO_NAME " [vrf ] interface WORD [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All VRFs\n" + "IS-IS interface\n" + "IS-IS interface name\n" + "json output\n") +{ + int res = CMD_SUCCESS; + int idx_word = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + + char *ifname = argv_find(argv, argc, "WORD", &idx_word) + ? argv[idx_word]->arg + : NULL; + res = show_isis_interface_common( + vty, json, ifname, ISIS_UI_LEVEL_DETAIL, vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +static int id_to_sysid(struct isis *isis, const char *id, uint8_t *sysid) +{ + struct isis_dynhn *dynhn; + + memset(sysid, 0, ISIS_SYS_ID_LEN); + if (id) { + if (sysid2buff(sysid, id) == 0) { + dynhn = dynhn_find_by_name(isis, id); + if (dynhn == NULL) + return -1; + memcpy(sysid, dynhn->id, ISIS_SYS_ID_LEN); + } + } + + return 0; +} + +static void isis_neighbor_common_json(struct json_object *json, const char *id, + char detail, struct isis *isis, + uint8_t *sysid) +{ + struct listnode *anode, *cnode, *node; + struct isis_area *area; + struct isis_circuit *circuit; + struct list *adjdb; + struct isis_adjacency *adj; + struct json_object *areas_json, *area_json; + struct json_object *circuits_json, *circuit_json; + int i; + + areas_json = json_object_new_array(); + json_object_object_add(json, "areas", areas_json); + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + area_json = json_object_new_object(); + json_object_string_add(area_json, "area", + area->area_tag ? area->area_tag + : "null"); + circuits_json = json_object_new_array(); + json_object_object_add(area_json, "circuits", circuits_json); + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { + circuit_json = json_object_new_object(); + json_object_int_add(circuit_json, "circuit", + circuit->circuit_id); + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + for (i = 0; i < 2; i++) { + adjdb = circuit->u.bc.adjdb[i]; + if (adjdb && adjdb->count) { + for (ALL_LIST_ELEMENTS_RO( + adjdb, node, adj)) + if (!id || + !memcmp(adj->sysid, + sysid, + ISIS_SYS_ID_LEN)) + isis_adj_print_json( + adj, + circuit_json, + detail); + } + } + } else if (circuit->circ_type == CIRCUIT_T_P2P && + circuit->u.p2p.neighbor) { + adj = circuit->u.p2p.neighbor; + if (!id || + !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) + isis_adj_print_json(adj, circuit_json, + detail); + } + json_object_array_add(circuits_json, circuit_json); + } + json_object_array_add(areas_json, area_json); + } +} + +static void isis_neighbor_common_vty(struct vty *vty, const char *id, + char detail, struct isis *isis, + uint8_t *sysid) +{ + struct listnode *anode, *cnode, *node; + struct isis_area *area; + struct isis_circuit *circuit; + struct list *adjdb; + struct isis_adjacency *adj; + int i; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + vty_out(vty, "Area %s:\n", area->area_tag); + + if (detail == ISIS_UI_LEVEL_BRIEF) + vty_out(vty, + " System Id Interface L State Holdtime SNPA\n"); + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + for (i = 0; i < 2; i++) { + adjdb = circuit->u.bc.adjdb[i]; + if (adjdb && adjdb->count) { + for (ALL_LIST_ELEMENTS_RO( + adjdb, node, adj)) + if (!id || + !memcmp(adj->sysid, + sysid, + ISIS_SYS_ID_LEN)) + isis_adj_print_vty( + adj, + vty, + detail); + } + } + } else if (circuit->circ_type == CIRCUIT_T_P2P && + circuit->u.p2p.neighbor) { + adj = circuit->u.p2p.neighbor; + if (!id || + !memcmp(adj->sysid, sysid, ISIS_SYS_ID_LEN)) + isis_adj_print_vty(adj, vty, detail); + } + } + } +} + +static void isis_neighbor_common(struct vty *vty, struct json_object *json, + const char *id, char detail, struct isis *isis, + uint8_t *sysid) +{ + if (json) { + isis_neighbor_common_json(json, id, detail,isis,sysid); + } else { + isis_neighbor_common_vty(vty, id, detail,isis,sysid); + } +} + +/* + * 'show isis neighbor' command + */ + +int show_isis_neighbor_common(struct vty *vty, struct json_object *json, + const char *id, char detail, const char *vrf_name, + bool all_vrf) +{ + struct listnode *node; + uint8_t sysid[ISIS_SYS_ID_LEN]; + struct isis *isis; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + if (id_to_sysid(isis, id, sysid)) { + vty_out(vty, "Invalid system id %s\n", id); + return CMD_SUCCESS; + } + isis_neighbor_common(vty, json, id, detail, isis, sysid); + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + if (id_to_sysid(isis, id, sysid)) { + vty_out(vty, "Invalid system id %s\n", id); + return CMD_SUCCESS; + } + isis_neighbor_common(vty, json, id, detail, isis, sysid); + } + + return CMD_SUCCESS; +} + +static void isis_neighbor_common_clear(struct vty *vty, const char *id, + uint8_t *sysid, struct isis *isis) +{ + struct listnode *anode, *cnode, *node, *nnode; + struct isis_area *area; + struct isis_circuit *circuit; + struct list *adjdb; + struct isis_adjacency *adj; + int i; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + for (i = 0; i < 2; i++) { + adjdb = circuit->u.bc.adjdb[i]; + if (adjdb && adjdb->count) { + for (ALL_LIST_ELEMENTS( + adjdb, node, nnode, + adj)) + if (!id + || !memcmp( + adj->sysid, + sysid, + ISIS_SYS_ID_LEN)) + isis_adj_state_change( + &adj, + ISIS_ADJ_DOWN, + "clear user request"); + } + } + } else if (circuit->circ_type == CIRCUIT_T_P2P + && circuit->u.p2p.neighbor) { + adj = circuit->u.p2p.neighbor; + if (!id + || !memcmp(adj->sysid, sysid, + ISIS_SYS_ID_LEN)) + isis_adj_state_change( + &adj, ISIS_ADJ_DOWN, + "clear user request"); + } + } + } +} +/* + * 'clear isis neighbor' command + */ +int clear_isis_neighbor_common(struct vty *vty, const char *id, const char *vrf_name, + bool all_vrf) +{ + struct listnode *node; + uint8_t sysid[ISIS_SYS_ID_LEN]; + struct isis *isis; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) { + if (id_to_sysid(isis, id, sysid)) { + vty_out(vty, "Invalid system id %s\n", id); + return CMD_SUCCESS; + } + isis_neighbor_common_clear(vty, id, sysid, isis); + } + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) { + if (id_to_sysid(isis, id, sysid)) { + vty_out(vty, "Invalid system id %s\n", id); + return CMD_SUCCESS; + } + isis_neighbor_common_clear(vty, id, sysid, isis); + } + + return CMD_SUCCESS; +} + +DEFUN(show_isis_neighbor, + show_isis_neighbor_cmd, + "show " PROTO_NAME " [vrf ] neighbor [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All vrfs\n" + "IS-IS neighbor adjacencies\n" + "json output\n") +{ + int res = CMD_SUCCESS; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + res = show_isis_neighbor_common(vty, json, NULL, ISIS_UI_LEVEL_BRIEF, + vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +DEFUN(show_isis_neighbor_detail, + show_isis_neighbor_detail_cmd, + "show " PROTO_NAME " [vrf ] neighbor detail [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "all vrfs\n" + "IS-IS neighbor adjacencies\n" + "show detailed information\n" + "json output\n") +{ + int res = CMD_SUCCESS; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + + res = show_isis_neighbor_common(vty, json, NULL, ISIS_UI_LEVEL_DETAIL, + vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +DEFUN(show_isis_neighbor_arg, + show_isis_neighbor_arg_cmd, + "show " PROTO_NAME " [vrf ] neighbor WORD [json]", + SHOW_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All vrfs\n" + "IS-IS neighbor adjacencies\n" + "System id\n" + "json output\n") +{ + int res = CMD_SUCCESS; + int idx_word = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + char *id = argv_find(argv, argc, "WORD", &idx_word) + ? argv[idx_word]->arg + : NULL; + + res = show_isis_neighbor_common(vty, json, id, ISIS_UI_LEVEL_DETAIL, + vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +DEFUN(clear_isis_neighbor, + clear_isis_neighbor_cmd, + "clear " PROTO_NAME " [vrf ] neighbor", + CLEAR_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All vrfs\n" + "IS-IS neighbor adjacencies\n") +{ + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + return clear_isis_neighbor_common(vty, NULL, vrf_name, all_vrf); +} + +DEFUN(clear_isis_neighbor_arg, + clear_isis_neighbor_arg_cmd, + "clear " PROTO_NAME " [vrf ] neighbor WORD", + CLEAR_STR + PROTO_HELP + VRF_CMD_HELP_STR + "All vrfs\n" + "IS-IS neighbor adjacencies\n" + "System id\n") +{ + int idx_word = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + + char *id = argv_find(argv, argc, "WORD", &idx_word) + ? argv[idx_word]->arg + : NULL; + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + return clear_isis_neighbor_common(vty, id, vrf_name, all_vrf); +} + +/* + * 'isis debug', 'show debugging' + */ +void print_debug(struct vty *vty, int flags, int onoff) +{ + const char *onoffs = onoff ? "on" : "off"; + + if (flags & DEBUG_ADJ_PACKETS) + vty_out(vty, + "IS-IS Adjacency related packets debugging is %s\n", + onoffs); + if (flags & DEBUG_TX_QUEUE) + vty_out(vty, "IS-IS TX queue debugging is %s\n", + onoffs); + if (flags & DEBUG_SNP_PACKETS) + vty_out(vty, "IS-IS CSNP/PSNP packets debugging is %s\n", + onoffs); + if (flags & DEBUG_SPF_EVENTS) + vty_out(vty, "IS-IS SPF events debugging is %s\n", onoffs); + if (flags & DEBUG_SR) + vty_out(vty, "IS-IS Segment Routing events debugging is %s\n", + onoffs); + if (flags & DEBUG_TE) + vty_out(vty, + "IS-IS Traffic Engineering events debugging is %s\n", + onoffs); + if (flags & DEBUG_LFA) + vty_out(vty, "IS-IS LFA events debugging is %s\n", onoffs); + if (flags & DEBUG_UPDATE_PACKETS) + vty_out(vty, "IS-IS Update related packet debugging is %s\n", + onoffs); + if (flags & DEBUG_RTE_EVENTS) + vty_out(vty, "IS-IS Route related debugging is %s\n", onoffs); + if (flags & DEBUG_EVENTS) + vty_out(vty, "IS-IS Event debugging is %s\n", onoffs); + if (flags & DEBUG_PACKET_DUMP) + vty_out(vty, "IS-IS Packet dump debugging is %s\n", onoffs); + if (flags & DEBUG_LSP_GEN) + vty_out(vty, "IS-IS LSP generation debugging is %s\n", onoffs); + if (flags & DEBUG_LSP_SCHED) + vty_out(vty, "IS-IS LSP scheduling debugging is %s\n", onoffs); + if (flags & DEBUG_FLOODING) + vty_out(vty, "IS-IS Flooding debugging is %s\n", onoffs); + if (flags & DEBUG_BFD) + vty_out(vty, "IS-IS BFD debugging is %s\n", onoffs); + if (flags & DEBUG_LDP_SYNC) + vty_out(vty, "IS-IS ldp-sync debugging is %s\n", onoffs); +} + +DEFUN_NOSH (show_debugging, + show_debugging_isis_cmd, + "show debugging [" PROTO_NAME "]", + SHOW_STR + "State of each debugging option\n" + PROTO_HELP) +{ + vty_out(vty, PROTO_NAME " debugging status:\n"); + + if (IS_DEBUG_ADJ_PACKETS) + print_debug(vty, DEBUG_ADJ_PACKETS, 1); + if (IS_DEBUG_TX_QUEUE) + print_debug(vty, DEBUG_TX_QUEUE, 1); + if (IS_DEBUG_SNP_PACKETS) + print_debug(vty, DEBUG_SNP_PACKETS, 1); + if (IS_DEBUG_SPF_EVENTS) + print_debug(vty, DEBUG_SPF_EVENTS, 1); + if (IS_DEBUG_SR) + print_debug(vty, DEBUG_SR, 1); + if (IS_DEBUG_TE) + print_debug(vty, DEBUG_TE, 1); + if (IS_DEBUG_UPDATE_PACKETS) + print_debug(vty, DEBUG_UPDATE_PACKETS, 1); + if (IS_DEBUG_RTE_EVENTS) + print_debug(vty, DEBUG_RTE_EVENTS, 1); + if (IS_DEBUG_EVENTS) + print_debug(vty, DEBUG_EVENTS, 1); + if (IS_DEBUG_PACKET_DUMP) + print_debug(vty, DEBUG_PACKET_DUMP, 1); + if (IS_DEBUG_LSP_GEN) + print_debug(vty, DEBUG_LSP_GEN, 1); + if (IS_DEBUG_LSP_SCHED) + print_debug(vty, DEBUG_LSP_SCHED, 1); + if (IS_DEBUG_FLOODING) + print_debug(vty, DEBUG_FLOODING, 1); + if (IS_DEBUG_BFD) + print_debug(vty, DEBUG_BFD, 1); + if (IS_DEBUG_LDP_SYNC) + print_debug(vty, DEBUG_LDP_SYNC, 1); + if (IS_DEBUG_LFA) + print_debug(vty, DEBUG_LFA, 1); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static int config_write_debug(struct vty *vty); +/* Debug node. */ +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_debug, +}; + +static int config_write_debug(struct vty *vty) +{ + int write = 0; + + if (IS_DEBUG_ADJ_PACKETS) { + vty_out(vty, "debug " PROTO_NAME " adj-packets\n"); + write++; + } + if (IS_DEBUG_TX_QUEUE) { + vty_out(vty, "debug " PROTO_NAME " tx-queue\n"); + write++; + } + if (IS_DEBUG_SNP_PACKETS) { + vty_out(vty, "debug " PROTO_NAME " snp-packets\n"); + write++; + } + if (IS_DEBUG_SPF_EVENTS) { + vty_out(vty, "debug " PROTO_NAME " spf-events\n"); + write++; + } + if (IS_DEBUG_SR) { + vty_out(vty, "debug " PROTO_NAME " sr-events\n"); + write++; + } + if (IS_DEBUG_TE) { + vty_out(vty, "debug " PROTO_NAME " te-events\n"); + write++; + } + if (IS_DEBUG_LFA) { + vty_out(vty, "debug " PROTO_NAME " lfa\n"); + write++; + } + if (IS_DEBUG_UPDATE_PACKETS) { + vty_out(vty, "debug " PROTO_NAME " update-packets\n"); + write++; + } + if (IS_DEBUG_RTE_EVENTS) { + vty_out(vty, "debug " PROTO_NAME " route-events\n"); + write++; + } + if (IS_DEBUG_EVENTS) { + vty_out(vty, "debug " PROTO_NAME " events\n"); + write++; + } + if (IS_DEBUG_PACKET_DUMP) { + vty_out(vty, "debug " PROTO_NAME " packet-dump\n"); + write++; + } + if (IS_DEBUG_LSP_GEN) { + vty_out(vty, "debug " PROTO_NAME " lsp-gen\n"); + write++; + } + if (IS_DEBUG_LSP_SCHED) { + vty_out(vty, "debug " PROTO_NAME " lsp-sched\n"); + write++; + } + if (IS_DEBUG_FLOODING) { + vty_out(vty, "debug " PROTO_NAME " flooding\n"); + write++; + } + if (IS_DEBUG_BFD) { + vty_out(vty, "debug " PROTO_NAME " bfd\n"); + write++; + } + if (IS_DEBUG_LDP_SYNC) { + vty_out(vty, "debug " PROTO_NAME " ldp-sync\n"); + write++; + } + write += spf_backoff_write_config(vty); + + return write; +} + +DEFUN (debug_isis_adj, + debug_isis_adj_cmd, + "debug " PROTO_NAME " adj-packets", + DEBUG_STR + PROTO_HELP + "IS-IS Adjacency related packets\n") +{ + debug_adj_pkt |= DEBUG_ADJ_PACKETS; + print_debug(vty, DEBUG_ADJ_PACKETS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_adj, + no_debug_isis_adj_cmd, + "no debug " PROTO_NAME " adj-packets", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Adjacency related packets\n") +{ + debug_adj_pkt &= ~DEBUG_ADJ_PACKETS; + print_debug(vty, DEBUG_ADJ_PACKETS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_tx_queue, + debug_isis_tx_queue_cmd, + "debug " PROTO_NAME " tx-queue", + DEBUG_STR + PROTO_HELP + "IS-IS TX queues\n") +{ + debug_tx_queue |= DEBUG_TX_QUEUE; + print_debug(vty, DEBUG_TX_QUEUE, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_tx_queue, + no_debug_isis_tx_queue_cmd, + "no debug " PROTO_NAME " tx-queue", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS TX queues\n") +{ + debug_tx_queue &= ~DEBUG_TX_QUEUE; + print_debug(vty, DEBUG_TX_QUEUE, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_flooding, + debug_isis_flooding_cmd, + "debug " PROTO_NAME " flooding", + DEBUG_STR + PROTO_HELP + "Flooding algorithm\n") +{ + debug_flooding |= DEBUG_FLOODING; + print_debug(vty, DEBUG_FLOODING, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_flooding, + no_debug_isis_flooding_cmd, + "no debug " PROTO_NAME " flooding", + NO_STR + UNDEBUG_STR + PROTO_HELP + "Flooding algorithm\n") +{ + debug_flooding &= ~DEBUG_FLOODING; + print_debug(vty, DEBUG_FLOODING, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_snp, + debug_isis_snp_cmd, + "debug " PROTO_NAME " snp-packets", + DEBUG_STR + PROTO_HELP + "IS-IS CSNP/PSNP packets\n") +{ + debug_snp_pkt |= DEBUG_SNP_PACKETS; + print_debug(vty, DEBUG_SNP_PACKETS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_snp, + no_debug_isis_snp_cmd, + "no debug " PROTO_NAME " snp-packets", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS CSNP/PSNP packets\n") +{ + debug_snp_pkt &= ~DEBUG_SNP_PACKETS; + print_debug(vty, DEBUG_SNP_PACKETS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_upd, + debug_isis_upd_cmd, + "debug " PROTO_NAME " update-packets", + DEBUG_STR + PROTO_HELP + "IS-IS Update related packets\n") +{ + debug_update_pkt |= DEBUG_UPDATE_PACKETS; + print_debug(vty, DEBUG_UPDATE_PACKETS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_upd, + no_debug_isis_upd_cmd, + "no debug " PROTO_NAME " update-packets", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Update related packets\n") +{ + debug_update_pkt &= ~DEBUG_UPDATE_PACKETS; + print_debug(vty, DEBUG_UPDATE_PACKETS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_spfevents, + debug_isis_spfevents_cmd, + "debug " PROTO_NAME " spf-events", + DEBUG_STR + PROTO_HELP + "IS-IS Shortest Path First Events\n") +{ + debug_spf_events |= DEBUG_SPF_EVENTS; + print_debug(vty, DEBUG_SPF_EVENTS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_spfevents, + no_debug_isis_spfevents_cmd, + "no debug " PROTO_NAME " spf-events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Shortest Path First Events\n") +{ + debug_spf_events &= ~DEBUG_SPF_EVENTS; + print_debug(vty, DEBUG_SPF_EVENTS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_srevents, + debug_isis_srevents_cmd, + "debug " PROTO_NAME " sr-events", + DEBUG_STR + PROTO_HELP + "IS-IS Segment Routing Events\n") +{ + debug_sr |= DEBUG_SR; + print_debug(vty, DEBUG_SR, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_srevents, + no_debug_isis_srevents_cmd, + "no debug " PROTO_NAME " sr-events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Segment Routing Events\n") +{ + debug_sr &= ~DEBUG_SR; + print_debug(vty, DEBUG_SR, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_teevents, + debug_isis_teevents_cmd, + "debug " PROTO_NAME " te-events", + DEBUG_STR + PROTO_HELP + "IS-IS Traffic Engineering Events\n") +{ + debug_te |= DEBUG_TE; + print_debug(vty, DEBUG_TE, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_teevents, + no_debug_isis_teevents_cmd, + "no debug " PROTO_NAME " te-events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Traffic Engineering Events\n") +{ + debug_te &= ~DEBUG_TE; + print_debug(vty, DEBUG_TE, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_lfa, + debug_isis_lfa_cmd, + "debug " PROTO_NAME " lfa", + DEBUG_STR + PROTO_HELP + "IS-IS LFA Events\n") +{ + debug_lfa |= DEBUG_LFA; + print_debug(vty, DEBUG_LFA, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_lfa, + no_debug_isis_lfa_cmd, + "no debug " PROTO_NAME " lfa", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS LFA Events\n") +{ + debug_lfa &= ~DEBUG_LFA; + print_debug(vty, DEBUG_LFA, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_rtevents, + debug_isis_rtevents_cmd, + "debug " PROTO_NAME " route-events", + DEBUG_STR + PROTO_HELP + "IS-IS Route related events\n") +{ + debug_rte_events |= DEBUG_RTE_EVENTS; + print_debug(vty, DEBUG_RTE_EVENTS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_rtevents, + no_debug_isis_rtevents_cmd, + "no debug " PROTO_NAME " route-events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Route related events\n") +{ + debug_rte_events &= ~DEBUG_RTE_EVENTS; + print_debug(vty, DEBUG_RTE_EVENTS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_events, + debug_isis_events_cmd, + "debug " PROTO_NAME " events", + DEBUG_STR + PROTO_HELP + "IS-IS Events\n") +{ + debug_events |= DEBUG_EVENTS; + print_debug(vty, DEBUG_EVENTS, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_events, + no_debug_isis_events_cmd, + "no debug " PROTO_NAME " events", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS Events\n") +{ + debug_events &= ~DEBUG_EVENTS; + print_debug(vty, DEBUG_EVENTS, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_packet_dump, + debug_isis_packet_dump_cmd, + "debug " PROTO_NAME " packet-dump", + DEBUG_STR + PROTO_HELP + "IS-IS packet dump\n") +{ + debug_pkt_dump |= DEBUG_PACKET_DUMP; + print_debug(vty, DEBUG_PACKET_DUMP, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_packet_dump, + no_debug_isis_packet_dump_cmd, + "no debug " PROTO_NAME " packet-dump", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS packet dump\n") +{ + debug_pkt_dump &= ~DEBUG_PACKET_DUMP; + print_debug(vty, DEBUG_PACKET_DUMP, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_lsp_gen, + debug_isis_lsp_gen_cmd, + "debug " PROTO_NAME " lsp-gen", + DEBUG_STR + PROTO_HELP + "IS-IS generation of own LSPs\n") +{ + debug_lsp_gen |= DEBUG_LSP_GEN; + print_debug(vty, DEBUG_LSP_GEN, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_lsp_gen, + no_debug_isis_lsp_gen_cmd, + "no debug " PROTO_NAME " lsp-gen", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS generation of own LSPs\n") +{ + debug_lsp_gen &= ~DEBUG_LSP_GEN; + print_debug(vty, DEBUG_LSP_GEN, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_lsp_sched, + debug_isis_lsp_sched_cmd, + "debug " PROTO_NAME " lsp-sched", + DEBUG_STR + PROTO_HELP + "IS-IS scheduling of LSP generation\n") +{ + debug_lsp_sched |= DEBUG_LSP_SCHED; + print_debug(vty, DEBUG_LSP_SCHED, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_lsp_sched, + no_debug_isis_lsp_sched_cmd, + "no debug " PROTO_NAME " lsp-sched", + NO_STR + UNDEBUG_STR + PROTO_HELP + "IS-IS scheduling of LSP generation\n") +{ + debug_lsp_sched &= ~DEBUG_LSP_SCHED; + print_debug(vty, DEBUG_LSP_SCHED, 0); + + return CMD_SUCCESS; +} + +DEFUN (debug_isis_bfd, + debug_isis_bfd_cmd, + "debug " PROTO_NAME " bfd", + DEBUG_STR + PROTO_HELP + PROTO_NAME " interaction with BFD\n") +{ + debug_bfd |= DEBUG_BFD; + bfd_protocol_integration_set_debug(true); + print_debug(vty, DEBUG_BFD, 1); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_isis_bfd, + no_debug_isis_bfd_cmd, + "no debug " PROTO_NAME " bfd", + NO_STR + UNDEBUG_STR + PROTO_HELP + PROTO_NAME " interaction with BFD\n") +{ + debug_bfd &= ~DEBUG_BFD; + bfd_protocol_integration_set_debug(false); + print_debug(vty, DEBUG_BFD, 0); + + return CMD_SUCCESS; +} + +DEFUN(debug_isis_ldp_sync, debug_isis_ldp_sync_cmd, + "debug " PROTO_NAME " ldp-sync", + DEBUG_STR PROTO_HELP PROTO_NAME " interaction with LDP-Sync\n") +{ + debug_ldp_sync |= DEBUG_LDP_SYNC; + print_debug(vty, DEBUG_LDP_SYNC, 1); + + return CMD_SUCCESS; +} + +DEFUN(no_debug_isis_ldp_sync, no_debug_isis_ldp_sync_cmd, + "no debug " PROTO_NAME " ldp-sync", + NO_STR UNDEBUG_STR PROTO_HELP PROTO_NAME " interaction with LDP-Sync\n") +{ + debug_ldp_sync &= ~DEBUG_LDP_SYNC; + print_debug(vty, DEBUG_LDP_SYNC, 0); + + return CMD_SUCCESS; +} + +DEFUN (show_hostname, + show_hostname_cmd, + "show " PROTO_NAME " [vrf ] hostname", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "IS-IS Dynamic hostname mapping\n") +{ + struct listnode *node; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int idx_vrf = 0; + struct isis *isis; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + dynhn_print_all(vty, isis); + + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) + dynhn_print_all(vty, isis); + + return CMD_SUCCESS; +} + +static void isis_spf_ietf_common(struct vty *vty, struct isis *isis) +{ + struct listnode *node; + struct isis_area *area; + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + + vty_out(vty, "vrf : %s\n", isis->name); + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((area->is_type & level) == 0) + continue; + + vty_out(vty, " Level-%d:\n", level); + vty_out(vty, " SPF delay status: "); + if (area->spf_timer[level - 1]) { + struct timeval remain = event_timer_remain( + area->spf_timer[level - 1]); + vty_out(vty, "Pending, due in %lld msec\n", + (long long)remain.tv_sec * 1000 + + remain.tv_usec / 1000); + } else { + vty_out(vty, "Not scheduled\n"); + } + + if (area->spf_delay_ietf[level - 1]) { + vty_out(vty, + " Using draft-ietf-rtgwg-backoff-algo-04\n"); + spf_backoff_show( + area->spf_delay_ietf[level - 1], vty, + " "); + } else { + vty_out(vty, " Using legacy backoff algo\n"); + } + } + } +} + +DEFUN(show_isis_spf_ietf, show_isis_spf_ietf_cmd, + "show " PROTO_NAME " [vrf ] spf-delay-ietf", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "SPF delay IETF information\n") +{ + struct listnode *node; + struct isis *isis; + int idx_vrf = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) + + if (!im) { + vty_out(vty, "ISIS is not running\n"); + return CMD_SUCCESS; + } + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + isis_spf_ietf_common(vty, isis); + + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) + isis_spf_ietf_common(vty, isis); + + return CMD_SUCCESS; +} + + +static const char *pdu_counter_index_to_name_json(enum pdu_counter_index index) +{ + switch (index) { + case L1_LAN_HELLO_INDEX: + return "l1-iih"; + case L2_LAN_HELLO_INDEX: + return "l2-iih"; + case P2P_HELLO_INDEX: + return "p2p-iih"; + case L1_LINK_STATE_INDEX: + return "l1-lsp"; + case L2_LINK_STATE_INDEX: + return "l2-lsp"; + case FS_LINK_STATE_INDEX: + return "fs-lsp"; + case L1_COMPLETE_SEQ_NUM_INDEX: + return "l1-csnp"; + case L2_COMPLETE_SEQ_NUM_INDEX: + return "l2-csnp"; + case L1_PARTIAL_SEQ_NUM_INDEX: + return "l1-psnp"; + case L2_PARTIAL_SEQ_NUM_INDEX: + return "l2-psnp"; + case PDU_COUNTER_SIZE: + return "???????"; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +static void common_isis_summary_json(struct json_object *json, + struct isis *isis) +{ + int level; + json_object *areas_json, *area_json, *tx_pdu_json, *rx_pdu_json, + *levels_json, *level_json; + struct listnode *node, *node2; + struct isis_area *area; + time_t cur; + char uptime[MONOTIME_STRLEN]; + char stier[5]; + + json_object_string_add(json, "vrf", isis->name); + json_object_int_add(json, "process-id", isis->process_id); + if (isis->sysid_set) + json_object_string_addf(json, "system-id", "%pSY", isis->sysid); + + cur = time(NULL); + cur -= isis->uptime; + frrtime_to_interval(cur, uptime, sizeof(uptime)); + json_object_string_add(json, "up-time", uptime); + if (isis->area_list) + json_object_int_add(json, "number-areas", + isis->area_list->count); + areas_json = json_object_new_array(); + json_object_object_add(json, "areas", areas_json); + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + area_json = json_object_new_object(); + json_object_string_add(area_json, "area", + area->area_tag ? area->area_tag + : "null"); + + + if (fabricd) { + uint8_t tier = fabricd_tier(area); + snprintfrr(stier, sizeof(stier), "%s", &tier); + json_object_string_add(area_json, "tier", + tier == ISIS_TIER_UNDEFINED + ? "undefined" + : stier); + } + + if (listcount(area->area_addrs) > 0) { + struct iso_address *area_addr; + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, + area_addr)) + json_object_string_addf(area_json, "net", + "%pISl", area_addr); + } + + tx_pdu_json = json_object_new_object(); + json_object_object_add(area_json, "tx-pdu-type", tx_pdu_json); + for (int i = 0; i < PDU_COUNTER_SIZE; i++) { + if (!area->pdu_tx_counters[i]) + continue; + json_object_int_add(tx_pdu_json, + pdu_counter_index_to_name_json(i), + area->pdu_tx_counters[i]); + } + json_object_int_add(tx_pdu_json, "lsp-rxmt", + area->lsp_rxmt_count); + + rx_pdu_json = json_object_new_object(); + json_object_object_add(area_json, "rx-pdu-type", rx_pdu_json); + for (int i = 0; i < PDU_COUNTER_SIZE; i++) { + if (!area->pdu_rx_counters[i]) + continue; + json_object_int_add(rx_pdu_json, + pdu_counter_index_to_name_json(i), + area->pdu_rx_counters[i]); + } + + levels_json = json_object_new_array(); + json_object_object_add(area_json, "levels", levels_json); + for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((area->is_type & level) == 0) + continue; + level_json = json_object_new_object(); + json_object_int_add(level_json, "id", level); + json_object_int_add(level_json, "lsp0-regenerated", + area->lsp_gen_count[level - 1]); + json_object_int_add(level_json, "lsp-purged", + area->lsp_purge_count[level - 1]); + if (area->spf_timer[level - 1]) + json_object_string_add(level_json, "spf", + "pending"); + else + json_object_string_add(level_json, "spf", + "no pending"); + json_object_int_add(level_json, "minimum-interval", + area->min_spf_interval[level - 1]); + if (area->spf_delay_ietf[level - 1]) + json_object_string_add( + level_json, "ietf-spf-delay-activated", + "not used"); + if (area->ip_circuits) { + isis_spf_print_json( + area->spftree[SPFTREE_IPV4][level - 1], + level_json); + } + if (area->ipv6_circuits) { + isis_spf_print_json( + area->spftree[SPFTREE_IPV6][level - 1], + level_json); + } + json_object_array_add(levels_json, level_json); + } + json_object_array_add(areas_json, area_json); + } +} + +static void common_isis_summary_vty(struct vty *vty, struct isis *isis) +{ + struct listnode *node, *node2; + struct isis_area *area; + int level; + + vty_out(vty, "vrf : %s\n", isis->name); + vty_out(vty, "Process Id : %ld\n", isis->process_id); + if (isis->sysid_set) + vty_out(vty, "System Id : %pSY\n", isis->sysid); + + vty_out(vty, "Up time : "); + vty_out_timestr(vty, isis->uptime); + vty_out(vty, "\n"); + + if (isis->area_list) + vty_out(vty, "Number of areas : %d\n", isis->area_list->count); + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + if (fabricd) { + uint8_t tier = fabricd_tier(area); + if (tier == ISIS_TIER_UNDEFINED) + vty_out(vty, " Tier: undefined\n"); + else + vty_out(vty, " Tier: %hhu\n", tier); + } + + if (listcount(area->area_addrs) > 0) { + struct iso_address *area_addr; + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, node2, + area_addr)) + vty_out(vty, " Net: %pISl\n", area_addr); + } + + vty_out(vty, " TX counters per PDU type:\n"); + pdu_counter_print(vty, " ", area->pdu_tx_counters); + vty_out(vty, " LSP RXMT: %" PRIu64 "\n", + area->lsp_rxmt_count); + vty_out(vty, " RX counters per PDU type:\n"); + pdu_counter_print(vty, " ", area->pdu_rx_counters); + + vty_out(vty, " Drop counters per PDU type:\n"); + pdu_counter_print(vty, " ", area->pdu_drop_counters); + + vty_out(vty, " Advertise high metrics: %s\n", + area->advertise_high_metrics ? "Enabled" : "Disabled"); + + for (level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) { + if ((area->is_type & level) == 0) + continue; + + vty_out(vty, " Level-%d:\n", level); + + vty_out(vty, " LSP0 regenerated: %" PRIu64 "\n", + area->lsp_gen_count[level - 1]); + + vty_out(vty, " LSPs purged: %" PRIu64 "\n", + area->lsp_purge_count[level - 1]); + + if (area->spf_timer[level - 1]) + vty_out(vty, " SPF: (pending)\n"); + else + vty_out(vty, " SPF:\n"); + + vty_out(vty, " minimum interval : %d", + area->min_spf_interval[level - 1]); + if (area->spf_delay_ietf[level - 1]) + vty_out(vty, + " (not used, IETF SPF delay activated)"); + vty_out(vty, "\n"); + + if (area->ip_circuits) { + vty_out(vty, " IPv4 route computation:\n"); + isis_spf_print( + area->spftree[SPFTREE_IPV4][level - 1], + vty); + } + + if (area->ipv6_circuits) { + vty_out(vty, " IPv6 route computation:\n"); + isis_spf_print( + area->spftree[SPFTREE_IPV6][level - 1], + vty); + } + + if (area->ipv6_circuits + && isis_area_ipv6_dstsrc_enabled(area)) { + vty_out(vty, + " IPv6 dst-src route computation:\n"); + isis_spf_print(area->spftree[SPFTREE_DSTSRC] + [level - 1], + vty); + } + } + } +} + +static void common_isis_summary(struct vty *vty, struct json_object *json, + struct isis *isis) +{ + if (json) { + common_isis_summary_json(json, isis); + } else { + common_isis_summary_vty(vty, isis); + } +} + +DEFUN(show_isis_summary, show_isis_summary_cmd, + "show " PROTO_NAME " [vrf ] summary [json]", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "json output\n" + "summary\n") +{ + struct listnode *node; + int idx_vrf = 0; + struct isis *isis; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) + if (!im) { + vty_out(vty, PROTO_NAME " is not running\n"); + return CMD_SUCCESS; + } + if (uj) + json = json_object_new_object(); + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + common_isis_summary(vty, json, isis); + + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis != NULL) + common_isis_summary(vty, json, isis); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +struct isis_lsp *lsp_for_sysid(struct lspdb_head *head, const char *sysid_str, + struct isis *isis) +{ + char sysid[255] = {0}; + uint8_t number[3] = {0}; + const char *pos; + uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0}; + struct isis_dynhn *dynhn; + struct isis_lsp *lsp = NULL; + + if (!sysid_str) + return NULL; + + /* + * extract fragment and pseudo id from the string sysid_str + * in the forms: + * (a) .- or + * (b) . or + * (c) or + * Where systemid is in the form: + * xxxx.xxxx.xxxx + */ + strlcpy(sysid, sysid_str, sizeof(sysid)); + + if (strlen(sysid_str) > 3) { + pos = sysid_str + strlen(sysid_str) - 3; + if (strncmp(pos, "-", 1) == 0) { + memcpy(number, ++pos, 2); + lspid[ISIS_SYS_ID_LEN + 1] = + (uint8_t)strtol((char *)number, NULL, 16); + pos -= 4; + if (strncmp(pos, ".", 1) != 0) + return NULL; + } + if (strncmp(pos, ".", 1) == 0) { + memcpy(number, ++pos, 2); + lspid[ISIS_SYS_ID_LEN] = + (uint8_t)strtol((char *)number, NULL, 16); + sysid[pos - sysid_str - 1] = '\0'; + } + } + + /* + * Try to find the lsp-id if the sysid_str + * is in the form + * hostname.- + */ + if (sysid2buff(lspid, sysid)) { + lsp = lsp_search(head, lspid); + } else if ((dynhn = dynhn_find_by_name(isis, sysid))) { + memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN); + lsp = lsp_search(head, lspid); + } else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) { + memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN); + lsp = lsp_search(head, lspid); + } + + return lsp; +} + +void show_isis_database_lspdb_json(struct json_object *json, + struct isis_area *area, int level, + struct lspdb_head *lspdb, + const char *sysid_str, int ui_level) +{ + struct json_object *array_json, *lsp_json; + struct isis_lsp *lsp; + int lsp_count; + struct json_object *lsp_arr_json; + + if (lspdb_count(lspdb) > 0) { + lsp = lsp_for_sysid(lspdb, sysid_str, area->isis); + + if (lsp != NULL || sysid_str == NULL) { + json_object_int_add(json, "id", level + 1); + } + + if (lsp) { + json_object_object_get_ex(json, "lsps", &array_json); + if (!array_json) { + array_json = json_object_new_array(); + json_object_object_add(json, "lsps", array_json); + } + lsp_json = json_object_new_object(); + json_object_array_add(array_json, lsp_json); + + if (ui_level == ISIS_UI_LEVEL_DETAIL) + lsp_print_detail(lsp, NULL, lsp_json, + area->dynhostname, area->isis); + else + lsp_print_json(lsp, lsp_json, area->dynhostname, + area->isis); + } else if (sysid_str == NULL) { + lsp_arr_json = json_object_new_array(); + json_object_object_add(json, "lsps", lsp_arr_json); + + lsp_count = lsp_print_all(NULL, lsp_arr_json, lspdb, + ui_level, area->dynhostname, + area->isis); + + json_object_int_add(json, "count", lsp_count); + } + } +} +void show_isis_database_lspdb_vty(struct vty *vty, struct isis_area *area, + int level, struct lspdb_head *lspdb, + const char *sysid_str, int ui_level) +{ + struct isis_lsp *lsp; + int lsp_count; + + if (lspdb_count(lspdb) > 0) { + lsp = lsp_for_sysid(lspdb, sysid_str, area->isis); + + if (lsp != NULL || sysid_str == NULL) { + vty_out(vty, "IS-IS Level-%d link-state database:\n", + level + 1); + + /* print the title in all cases */ + vty_out(vty, + "LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL\n"); + } + + if (lsp) { + if (ui_level == ISIS_UI_LEVEL_DETAIL) + lsp_print_detail(lsp, vty, NULL, + area->dynhostname, area->isis); + else + lsp_print_vty(lsp, vty, area->dynhostname, + area->isis); + } else if (sysid_str == NULL) { + lsp_count = + lsp_print_all(vty, NULL, lspdb, ui_level, + area->dynhostname, area->isis); + + vty_out(vty, " %u LSPs\n\n", lsp_count); + } + } +} + +static void show_isis_database_json(struct json_object *json, const char *sysid_str, + int ui_level, struct isis *isis) +{ + struct listnode *node; + struct isis_area *area; + int level; + struct json_object *tag_area_json,*area_json, *lsp_json, *area_arr_json, *arr_json; + + if (isis->area_list->count == 0) + return; + + area_arr_json = json_object_new_array(); + json_object_object_add(json, "areas", area_arr_json); + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + area_json = json_object_new_object(); + tag_area_json = json_object_new_object(); + json_object_string_add(tag_area_json, "name", + area->area_tag ? area->area_tag + : "null"); + + arr_json = json_object_new_array(); + json_object_object_add(area_json,"area",tag_area_json); + json_object_object_add(area_json,"levels",arr_json); + for (level = 0; level < ISIS_LEVELS; level++) { + if (lspdb_count(&area->lspdb[level]) == 0) + continue; + lsp_json = json_object_new_object(); + show_isis_database_lspdb_json(lsp_json, area, level, + &area->lspdb[level], + sysid_str, ui_level); + json_object_array_add(arr_json, lsp_json); + } + json_object_array_add(area_arr_json, area_json); + } +} + +static void show_isis_database_vty(struct vty *vty, const char *sysid_str, + int ui_level, struct isis *isis) +{ + struct listnode *node; + struct isis_area *area; + int level; + + if (isis->area_list->count == 0) + return; + + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + vty_out(vty, "Area %s:\n", + area->area_tag ? area->area_tag : "null"); + + for (level = 0; level < ISIS_LEVELS; level++) + show_isis_database_lspdb_vty(vty, area, level, + &area->lspdb[level], sysid_str, + ui_level); + } +} + +static void show_isis_database_common(struct vty *vty, struct json_object *json, const char *sysid_str, + int ui_level, struct isis *isis) +{ + if (json) { + show_isis_database_json(json, sysid_str, ui_level, isis); + } else { + show_isis_database_vty(vty, sysid_str, ui_level, isis); + } +} + +/* + * This function supports following display options: + * [ show isis database [detail] ] + * [ show isis database [detail] ] + * [ show isis database [detail] ] + * [ show isis database . [detail] ] + * [ show isis database . [detail] ] + * [ show isis database .- [detail] ] + * [ show isis database .- [detail] ] + * [ show isis database detail ] + * [ show isis database detail ] + * [ show isis database detail . ] + * [ show isis database detail . ] + * [ show isis database detail .- ] + * [ show isis database detail .- ] + */ +static int show_isis_database(struct vty *vty, struct json_object *json, const char *sysid_str, + int ui_level, const char *vrf_name, bool all_vrf) +{ + struct listnode *node; + struct isis *isis; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis)) + show_isis_database_common(vty, json, sysid_str, + ui_level, isis); + + return CMD_SUCCESS; + } + isis = isis_lookup_by_vrfname(vrf_name); + if (isis) + show_isis_database_common(vty, json, sysid_str, ui_level, isis); + + return CMD_SUCCESS; +} + +DEFUN(show_database, show_database_cmd, + "show " PROTO_NAME " [vrf ] database [detail] [WORD] [json]", + SHOW_STR PROTO_HELP VRF_CMD_HELP_STR + "All VRFs\n" + "Link state database\n" + "Detailed information\n" + "LSP ID\n" + "json output\n") +{ + int res = CMD_SUCCESS; + int idx = 0; + int idx_vrf = 0; + const char *vrf_name = VRF_DEFAULT_NAME; + bool all_vrf = false; + int uilevel = argv_find(argv, argc, "detail", &idx) + ? ISIS_UI_LEVEL_DETAIL + : ISIS_UI_LEVEL_BRIEF; + char *id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg : NULL; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (uj) + json = json_object_new_object(); + + res = show_isis_database(vty, json, id, uilevel, vrf_name, all_vrf); + if (uj) + vty_json(vty, json); + return res; +} + +#ifdef FABRICD +/* + * 'router openfabric' command + */ +DEFUN_NOSH (router_openfabric, + router_openfabric_cmd, + "router openfabric WORD", + ROUTER_STR + PROTO_HELP + "ISO Routing area tag\n") +{ + int idx_word = 2; + return isis_area_get(vty, argv[idx_word]->arg); +} + +/* + *'no router openfabric' command + */ +DEFUN (no_router_openfabric, + no_router_openfabric_cmd, + "no router openfabric WORD", + NO_STR + ROUTER_STR + PROTO_HELP + "ISO Routing area tag\n") +{ + struct isis_area *area; + const char *area_tag; + int idx_word = 3; + + area_tag = argv[idx_word]->arg; + area = isis_area_lookup(area_tag, VRF_DEFAULT); + if (area == NULL) { + zlog_warn("%s: could not find area with area-tag %s", + __func__, area_tag); + return CMD_ERR_NO_MATCH; + } + + isis_area_destroy(area); + return CMD_SUCCESS; +} +#endif /* ifdef FABRICD */ +#ifdef FABRICD +/* + * 'net' command + */ +DEFUN (net, + net_cmd, + "net WORD", + "A Network Entity Title for this process (OSI only)\n" + "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") +{ + int idx_word = 1; + return area_net_title(vty, argv[idx_word]->arg); +} + +/* + * 'no net' command + */ +DEFUN (no_net, + no_net_cmd, + "no net WORD", + NO_STR + "A Network Entity Title for this process (OSI only)\n" + "XX.XXXX. ... .XXX.XX Network entity title (NET)\n") +{ + int idx_word = 2; + return area_clear_net_title(vty, argv[idx_word]->arg); +} +#endif /* ifdef FABRICD */ +#ifdef FABRICD +DEFUN (isis_topology, + isis_topology_cmd, + "topology " ISIS_MT_NAMES " [overload]", + "Configure IS-IS topologies\n" + ISIS_MT_DESCRIPTIONS + "Set overload bit for topology\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + const char *arg = argv[1]->arg; + uint16_t mtid = isis_str2mtid(arg); + + if (area->oldmetric) { + vty_out(vty, + "Multi topology IS-IS can only be used with wide metrics\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (mtid == (uint16_t)-1) { + vty_out(vty, "Don't know topology '%s'\n", arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (mtid == ISIS_MT_IPV4_UNICAST) { + vty_out(vty, "Cannot configure IPv4 unicast topology\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + area_set_mt_enabled(area, mtid, true); + area_set_mt_overload(area, mtid, (argc == 3)); + return CMD_SUCCESS; +} + +DEFUN (no_isis_topology, + no_isis_topology_cmd, + "no topology " ISIS_MT_NAMES " [overload]", + NO_STR + "Configure IS-IS topologies\n" + ISIS_MT_DESCRIPTIONS + "Set overload bit for topology\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + const char *arg = argv[2]->arg; + uint16_t mtid = isis_str2mtid(arg); + + if (area->oldmetric) { + vty_out(vty, + "Multi topology IS-IS can only be used with wide metrics\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (mtid == (uint16_t)-1) { + vty_out(vty, "Don't know topology '%s'\n", arg); + return CMD_WARNING_CONFIG_FAILED; + } + if (mtid == ISIS_MT_IPV4_UNICAST) { + vty_out(vty, "Cannot configure IPv4 unicast topology\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + area_set_mt_enabled(area, mtid, false); + area_set_mt_overload(area, mtid, false); + return CMD_SUCCESS; +} +#endif /* ifdef FABRICD */ + +void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu) +{ + area->lsp_mtu = lsp_mtu; + lsp_regenerate_schedule(area, IS_LEVEL_1_AND_2, 1); +} + +static int isis_area_passwd_set(struct isis_area *area, int level, + uint8_t passwd_type, const char *passwd, + uint8_t snp_auth) +{ + struct isis_passwd *dest; + struct isis_passwd modified; + int len; + + assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); + dest = (level == IS_LEVEL_1) ? &area->area_passwd + : &area->domain_passwd; + memset(&modified, 0, sizeof(modified)); + + if (passwd_type != ISIS_PASSWD_TYPE_UNUSED) { + if (!passwd) + return -1; + + len = strlen(passwd); + if (len > 254) + return -1; + + modified.len = len; + strlcpy((char *)modified.passwd, passwd, + sizeof(modified.passwd)); + modified.type = passwd_type; + modified.snp_auth = snp_auth; + } + + if (memcmp(&modified, dest, sizeof(modified))) { + memcpy(dest, &modified, sizeof(modified)); + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + } + + return 0; +} + +int isis_area_passwd_unset(struct isis_area *area, int level) +{ + return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_UNUSED, NULL, + 0); +} + +int isis_area_passwd_cleartext_set(struct isis_area *area, int level, + const char *passwd, uint8_t snp_auth) +{ + return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_CLEARTXT, + passwd, snp_auth); +} + +int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level, + const char *passwd, uint8_t snp_auth) +{ + return isis_area_passwd_set(area, level, ISIS_PASSWD_TYPE_HMAC_MD5, + passwd, snp_auth); +} + +void isis_area_invalidate_routes(struct isis_area *area, int levels) +{ +#ifndef FABRICD + struct flex_algo *fa; + struct listnode *node; + struct isis_flex_algo_data *data; +#endif /* ifndef FABRICD */ + + for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { + if (!(level & levels)) + continue; + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + isis_spf_invalidate_routes( + area->spftree[tree][level - 1]); + +#ifndef FABRICD + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, + node, fa)) { + data = fa->data; + isis_spf_invalidate_routes( + data->spftree[tree][level - 1]); + } +#endif /* ifndef FABRICD */ + } + } +} + +void isis_area_verify_routes(struct isis_area *area) +{ + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) + isis_spf_verify_routes(area, area->spftree[tree], tree); +} + +void isis_area_switchover_routes(struct isis_area *area, int family, + union g_addr *nexthop_ip, ifindex_t ifindex, + int level) +{ + int tree; + + /* TODO SPFTREE_DSTSRC */ + if (family == AF_INET) + tree = SPFTREE_IPV4; + else if (family == AF_INET6) + tree = SPFTREE_IPV6; + else + return; + + isis_spf_switchover_routes(area, area->spftree[tree], family, + nexthop_ip, ifindex, level); +} + + +static void area_resign_level(struct isis_area *area, int level) +{ +#ifndef FABRICD + struct flex_algo *fa; + struct listnode *node; + struct isis_flex_algo_data *data; +#endif /* ifndef FABRICD */ + + isis_area_invalidate_routes(area, level); + isis_area_verify_routes(area); + + lsp_db_fini(&area->lspdb[level - 1]); + + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + if (area->spftree[tree][level - 1]) { + isis_spftree_del(area->spftree[tree][level - 1]); + area->spftree[tree][level - 1] = NULL; + } + } + +#ifndef FABRICD + for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) { + for (ALL_LIST_ELEMENTS_RO(area->flex_algos->flex_algos, node, + fa)) { + data = fa->data; + if (data->spftree[tree][level - 1]) { + isis_spftree_del( + data->spftree[tree][level - 1]); + data->spftree[tree][level - 1] = NULL; + } + } + } +#endif /* ifndef FABRICD */ + + if (area->spf_timer[level - 1]) + isis_spf_timer_free(EVENT_ARG(area->spf_timer[level - 1])); + + EVENT_OFF(area->spf_timer[level - 1]); + + sched_debug( + "ISIS (%s): Resigned from L%d - canceling LSP regeneration timer.", + area->area_tag, level); + EVENT_OFF(area->t_lsp_refresh[level - 1]); + area->lsp_regenerate_pending[level - 1] = 0; +} + +void isis_area_is_type_set(struct isis_area *area, int is_type) +{ + struct listnode *node; + struct isis_circuit *circuit; + + if (IS_DEBUG_EVENTS) + zlog_debug("ISIS-Evt (%s) system type change %s -> %s", + area->area_tag, circuit_t2string(area->is_type), + circuit_t2string(is_type)); + + if (area->is_type == is_type) + return; /* No change */ + + switch (area->is_type) { + case IS_LEVEL_1: + if (is_type == IS_LEVEL_2) + area_resign_level(area, IS_LEVEL_1); + + lsp_db_init(&area->lspdb[1]); + break; + + case IS_LEVEL_1_AND_2: + if (is_type == IS_LEVEL_1) + area_resign_level(area, IS_LEVEL_2); + else + area_resign_level(area, IS_LEVEL_1); + break; + + case IS_LEVEL_2: + if (is_type == IS_LEVEL_1) + area_resign_level(area, IS_LEVEL_2); + + lsp_db_init(&area->lspdb[0]); + break; + + default: + break; + } + + area->is_type = is_type; + + /* + * If area's IS type is strict Level-1 or Level-2, override circuit's + * IS type. Otherwise use circuit's configured IS type. + */ + if (area->is_type != IS_LEVEL_1_AND_2) { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_circuit_is_type_set(circuit, is_type); + } else { + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) + isis_circuit_is_type_set(circuit, circuit->is_type_config); + } + + spftree_area_init(area); + + if (listcount(area->area_addrs) > 0) { + if (is_type & IS_LEVEL_1) + lsp_generate(area, IS_LEVEL_1); + if (is_type & IS_LEVEL_2) + lsp_generate(area, IS_LEVEL_2); + } + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + + return; +} + +void isis_area_metricstyle_set(struct isis_area *area, bool old_metric, + bool new_metric) +{ + area->oldmetric = old_metric; + area->newmetric = new_metric; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); +} + +void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit) +{ + char new_overload_bit = overload_bit ? LSPBIT_OL : 0; + + if (new_overload_bit != area->overload_bit) { + area->overload_bit = new_overload_bit; + if (new_overload_bit) { + area->overload_counter++; + } else { + /* Cancel overload on startup timer if it's running */ + if (area->t_overload_on_startup_timer) { + EVENT_OFF(area->t_overload_on_startup_timer); + area->t_overload_on_startup_timer = NULL; + } + } + +#ifndef FABRICD + hook_call(isis_hook_db_overload, area); +#endif /* ifndef FABRICD */ + + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + } +#ifndef FABRICD + isis_notif_db_overload(area, overload_bit); +#endif /* ifndef FABRICD */ +} + +void isis_area_overload_on_startup_set(struct isis_area *area, + uint32_t startup_time) +{ + if (area->overload_on_startup_time != startup_time) { + area->overload_on_startup_time = startup_time; + isis_restart_write_overload_time(area, startup_time); + } +} + +void config_end_lsp_generate(struct isis_area *area) +{ + if (listcount(area->area_addrs) > 0) { + if (CHECK_FLAG(area->is_type, IS_LEVEL_1)) + lsp_generate(area, IS_LEVEL_1); + if (CHECK_FLAG(area->is_type, IS_LEVEL_2)) + lsp_generate(area, IS_LEVEL_2); + } +} + +void isis_area_advertise_high_metrics_set(struct isis_area *area, + bool advertise_high_metrics) +{ + struct listnode *node; + struct isis_circuit *circuit; + int max_metric; + char xpath[XPATH_MAXLEN]; + struct lyd_node *dnode; + int configured_metric_l1; + int configured_metric_l2; + + if (area->advertise_high_metrics == advertise_high_metrics) + return; + + if (advertise_high_metrics) { + if (area->oldmetric && area->newmetric) + max_metric = ISIS_NARROW_METRIC_INFINITY; + else if (area->newmetric) + max_metric = MAX_WIDE_LINK_METRIC; + else + max_metric = MAX_NARROW_LINK_METRIC; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + isis_circuit_metric_set(circuit, IS_LEVEL_1, + max_metric); + isis_circuit_metric_set(circuit, IS_LEVEL_2, + max_metric); + } + + area->advertise_high_metrics = true; + } else { + area->advertise_high_metrics = false; + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) { + /* Get configured metric */ + snprintf(xpath, XPATH_MAXLEN, + "/frr-interface:lib/interface[name='%s']", + circuit->interface->name); + dnode = yang_dnode_get(running_config->dnode, xpath); + + configured_metric_l1 = yang_dnode_get_uint32( + dnode, "./frr-isisd:isis/metric/level-1"); + configured_metric_l2 = yang_dnode_get_uint32( + dnode, "./frr-isisd:isis/metric/level-2"); + + isis_circuit_metric_set(circuit, IS_LEVEL_1, + configured_metric_l1); + isis_circuit_metric_set(circuit, IS_LEVEL_2, + configured_metric_l2); + } + } +} + +/* + * Record in non-volatile memory the overload on startup time. + */ +void isis_restart_write_overload_time(struct isis_area *isis_area, + uint32_t overload_time) +{ + const char *area_name; + json_object *json; + json_object *json_areas; + json_object *json_area; + + json = frr_daemon_state_load(); + area_name = isis_area->area_tag; + + json_object_object_get_ex(json, "areas", &json_areas); + if (!json_areas) { + json_areas = json_object_new_object(); + json_object_object_add(json, "areas", json_areas); + } + + json_object_object_get_ex(json_areas, area_name, &json_area); + if (!json_area) { + json_area = json_object_new_object(); + json_object_object_add(json_areas, area_name, json_area); + } + + json_object_int_add(json_area, "overload_time", + isis_area->overload_on_startup_time); + + frr_daemon_state_save(&json); +} + +/* + * Fetch from non-volatile memory the overload on startup time. + */ +uint32_t isis_restart_read_overload_time(struct isis_area *isis_area) +{ + const char *area_name; + json_object *json; + json_object *json_areas; + json_object *json_area; + json_object *json_overload_time; + uint32_t overload_time = 0; + + area_name = isis_area->area_tag; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "areas", &json_areas); + if (!json_areas) { + json_areas = json_object_new_object(); + json_object_object_add(json, "areas", json_areas); + } + + json_object_object_get_ex(json_areas, area_name, &json_area); + if (!json_area) { + json_area = json_object_new_object(); + json_object_object_add(json_areas, area_name, json_area); + } + + json_object_object_get_ex(json_area, "overload_time", + &json_overload_time); + if (json_overload_time) { + overload_time = json_object_get_int(json_overload_time); + } + + json_object_object_del(json_areas, area_name); + + frr_daemon_state_save(&json); + + return overload_time; +} + +void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit) +{ + + if (attached_bit != area->attached_bit_send) { + area->attached_bit_send = attached_bit; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + } +} + +void isis_area_attached_bit_receive_set(struct isis_area *area, + bool attached_bit) +{ + + if (attached_bit != area->attached_bit_rcv_ignore) { + area->attached_bit_rcv_ignore = attached_bit; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + } +} + +void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname) +{ + if (area->dynhostname != dynhostname) { + area->dynhostname = dynhostname; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 0); + } +} + +void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level, + uint16_t max_lsp_lifetime) +{ + assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); + + if (area->max_lsp_lifetime[level - 1] == max_lsp_lifetime) + return; + + area->max_lsp_lifetime[level - 1] = max_lsp_lifetime; + lsp_regenerate_schedule(area, level, 1); +} + +void isis_area_lsp_refresh_set(struct isis_area *area, int level, + uint16_t lsp_refresh) +{ + assert((level == IS_LEVEL_1) || (level == IS_LEVEL_2)); + + if (area->lsp_refresh[level - 1] == lsp_refresh) + return; + + area->lsp_refresh[level - 1] = lsp_refresh; + lsp_regenerate_schedule(area, level, 1); +} + +#ifdef FABRICD +DEFUN (log_adj_changes, + log_adj_changes_cmd, + "log-adjacency-changes", + "Log changes in adjacency state\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + area->log_adj_changes = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_log_adj_changes, + no_log_adj_changes_cmd, + "no log-adjacency-changes", + NO_STR + "Stop logging changes in adjacency state\n") +{ + VTY_DECLVAR_CONTEXT(isis_area, area); + + area->log_adj_changes = 0; + + return CMD_SUCCESS; +} +#endif /* ifdef FABRICD */ +#ifdef FABRICD +/* IS-IS configuration write function */ +static int isis_config_write(struct vty *vty) +{ + int write = 0; + struct isis_area *area; + struct listnode *node, *node2, *inode; + struct isis *isis; + + if (!im) { + vty_out(vty, "IS-IS Routing Process not enabled\n"); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) { + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + /* ISIS - Area name */ + vty_out(vty, "router " PROTO_NAME " %s\n", area->area_tag); + write++; + /* ISIS - Net */ + if (listcount(area->area_addrs) > 0) { + struct iso_address *area_addr; + for (ALL_LIST_ELEMENTS_RO(area->area_addrs, + node2, area_addr)) { + vty_out(vty, " net %pISl\n", area_addr); + write++; + } + } + /* ISIS - Dynamic hostname - Defaults to true so only + * display if + * false. */ + if (!area->dynhostname) { + vty_out(vty, " no hostname dynamic\n"); + write++; + } + /* ISIS - Metric-Style - when true displays wide */ + if (!fabricd) { + if (area->newmetric) { + if (!area->oldmetric) + vty_out(vty, " metric-style wide\n"); + else + vty_out(vty, + " metric-style transition\n"); + write++; + } else { + vty_out(vty, " metric-style narrow\n"); + write++; + } + } + /* ISIS - overload-bit */ + if (area->overload_bit) { + vty_out(vty, " set-overload-bit\n"); + write++; + } + /* ISIS - Area is-type (level-1-2 is default) */ + if (!fabricd) { + if (area->is_type == IS_LEVEL_1) { + vty_out(vty, " is-type level-1\n"); + write++; + } else if (area->is_type == IS_LEVEL_2) { + vty_out(vty, " is-type level-2-only\n"); + write++; + } + } + write += isis_redist_config_write(vty, area, AF_INET); + write += isis_redist_config_write(vty, area, AF_INET6); + /* ISIS - Lsp generation interval */ + if (area->lsp_gen_interval[0] + == area->lsp_gen_interval[1]) { + if (area->lsp_gen_interval[0] + != DEFAULT_MIN_LSP_GEN_INTERVAL) { + vty_out(vty, " lsp-gen-interval %d\n", + area->lsp_gen_interval[0]); + write++; + } + } else { + if (area->lsp_gen_interval[0] + != DEFAULT_MIN_LSP_GEN_INTERVAL) { + vty_out(vty, + " lsp-gen-interval level-1 %d\n", + area->lsp_gen_interval[0]); + write++; + } + if (area->lsp_gen_interval[1] + != DEFAULT_MIN_LSP_GEN_INTERVAL) { + vty_out(vty, + " lsp-gen-interval level-2 %d\n", + area->lsp_gen_interval[1]); + write++; + } + } + /* ISIS - LSP lifetime */ + if (area->max_lsp_lifetime[0] + == area->max_lsp_lifetime[1]) { + if (area->max_lsp_lifetime[0] + != DEFAULT_LSP_LIFETIME) { + vty_out(vty, " max-lsp-lifetime %u\n", + area->max_lsp_lifetime[0]); + write++; + } + } else { + if (area->max_lsp_lifetime[0] + != DEFAULT_LSP_LIFETIME) { + vty_out(vty, + " max-lsp-lifetime level-1 %u\n", + area->max_lsp_lifetime[0]); + write++; + } + if (area->max_lsp_lifetime[1] + != DEFAULT_LSP_LIFETIME) { + vty_out(vty, + " max-lsp-lifetime level-2 %u\n", + area->max_lsp_lifetime[1]); + write++; + } + } + /* ISIS - LSP refresh interval */ + if (area->lsp_refresh[0] == area->lsp_refresh[1]) { + if (area->lsp_refresh[0] + != DEFAULT_MAX_LSP_GEN_INTERVAL) { + vty_out(vty, + " lsp-refresh-interval %u\n", + area->lsp_refresh[0]); + write++; + } + } else { + if (area->lsp_refresh[0] + != DEFAULT_MAX_LSP_GEN_INTERVAL) { + vty_out(vty, + " lsp-refresh-interval level-1 %u\n", + area->lsp_refresh[0]); + write++; + } + if (area->lsp_refresh[1] + != DEFAULT_MAX_LSP_GEN_INTERVAL) { + vty_out(vty, + " lsp-refresh-interval level-2 %u\n", + area->lsp_refresh[1]); + write++; + } + } + if (area->lsp_mtu != DEFAULT_LSP_MTU) { + vty_out(vty, " lsp-mtu %u\n", area->lsp_mtu); + write++; + } + if (area->purge_originator) { + vty_out(vty, " purge-originator\n"); + write++; + } + + /* Minimum SPF interval. */ + if (area->min_spf_interval[0] + == area->min_spf_interval[1]) { + if (area->min_spf_interval[0] + != MINIMUM_SPF_INTERVAL) { + vty_out(vty, " spf-interval %d\n", + area->min_spf_interval[0]); + write++; + } + } else { + if (area->min_spf_interval[0] + != MINIMUM_SPF_INTERVAL) { + vty_out(vty, + " spf-interval level-1 %d\n", + area->min_spf_interval[0]); + write++; + } + if (area->min_spf_interval[1] + != MINIMUM_SPF_INTERVAL) { + vty_out(vty, + " spf-interval level-2 %d\n", + area->min_spf_interval[1]); + write++; + } + } + + /* IETF SPF interval */ + if (area->spf_delay_ietf[0]) { + vty_out(vty, + " spf-delay-ietf init-delay %ld short-delay %ld long-delay %ld holddown %ld time-to-learn %ld\n", + spf_backoff_init_delay( + area->spf_delay_ietf[0]), + spf_backoff_short_delay( + area->spf_delay_ietf[0]), + spf_backoff_long_delay( + area->spf_delay_ietf[0]), + spf_backoff_holddown( + area->spf_delay_ietf[0]), + spf_backoff_timetolearn( + area->spf_delay_ietf[0])); + write++; + } + + /* Authentication passwords. */ + if (area->area_passwd.type + == ISIS_PASSWD_TYPE_HMAC_MD5) { + vty_out(vty, " area-password md5 %s", + area->area_passwd.passwd); + if (CHECK_FLAG(area->area_passwd.snp_auth, + SNP_AUTH_SEND)) { + vty_out(vty, " authenticate snp "); + if (CHECK_FLAG( + area->area_passwd.snp_auth, + SNP_AUTH_RECV)) + vty_out(vty, "validate"); + else + vty_out(vty, "send-only"); + } + vty_out(vty, "\n"); + write++; + } else if (area->area_passwd.type + == ISIS_PASSWD_TYPE_CLEARTXT) { + vty_out(vty, " area-password clear %s", + area->area_passwd.passwd); + if (CHECK_FLAG(area->area_passwd.snp_auth, + SNP_AUTH_SEND)) { + vty_out(vty, " authenticate snp "); + if (CHECK_FLAG( + area->area_passwd.snp_auth, + SNP_AUTH_RECV)) + vty_out(vty, "validate"); + else + vty_out(vty, "send-only"); + } + vty_out(vty, "\n"); + write++; + } + if (area->domain_passwd.type + == ISIS_PASSWD_TYPE_HMAC_MD5) { + vty_out(vty, " domain-password md5 %s", + area->domain_passwd.passwd); + if (CHECK_FLAG(area->domain_passwd.snp_auth, + SNP_AUTH_SEND)) { + vty_out(vty, " authenticate snp "); + if (CHECK_FLAG(area->domain_passwd + .snp_auth, + SNP_AUTH_RECV)) + vty_out(vty, "validate"); + else + vty_out(vty, "send-only"); + } + vty_out(vty, "\n"); + write++; + } else if (area->domain_passwd.type + == ISIS_PASSWD_TYPE_CLEARTXT) { + vty_out(vty, " domain-password clear %s", + area->domain_passwd.passwd); + if (CHECK_FLAG(area->domain_passwd.snp_auth, + SNP_AUTH_SEND)) { + vty_out(vty, " authenticate snp "); + if (CHECK_FLAG(area->domain_passwd + .snp_auth, + SNP_AUTH_RECV)) + vty_out(vty, "validate"); + else + vty_out(vty, "send-only"); + } + vty_out(vty, "\n"); + write++; + } + + if (area->log_adj_changes) { + vty_out(vty, " log-adjacency-changes\n"); + write++; + } + + write += area_write_mt_settings(area, vty); + write += fabricd_write_settings(area, vty); + + vty_out(vty, "exit\n"); + } + } + + return write; +} + +struct cmd_node router_node = { + .name = "openfabric", + .node = OPENFABRIC_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = isis_config_write, +}; +#endif /* ifdef FABRICD */ +#ifndef FABRICD +/* IS-IS configuration write function */ +static int isis_config_write(struct vty *vty) +{ + int write = 0; + struct lyd_node *dnode; + + dnode = yang_dnode_get(running_config->dnode, "/frr-isisd:isis"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + write++; + } + + return write; +} + +struct cmd_node router_node = { + .name = "isis", + .node = ISIS_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = isis_config_write, +}; + +struct cmd_node isis_flex_algo_node = { + .name = "isis-flex-algo", + .node = ISIS_FLEX_ALGO_NODE, + .parent_node = ISIS_NODE, + .prompt = "%s(config-router-flex-algo)# ", +}; +#endif /* ifdnef FABRICD */ + +struct cmd_node isis_srv6_node = { + .name = "isis-srv6", + .node = ISIS_SRV6_NODE, + .parent_node = ISIS_NODE, + .prompt = "%s(config-router-srv6)# ", +}; + +struct cmd_node isis_srv6_node_msd_node = { + .name = "isis-srv6-node-msd", + .node = ISIS_SRV6_NODE_MSD_NODE, + .parent_node = ISIS_SRV6_NODE, + .prompt = "%s(config-router-srv6-node-msd)# ", +}; + +void isis_init(void) +{ + /* Install IS-IS top node */ + install_node(&router_node); + + install_element(VIEW_NODE, &show_isis_summary_cmd); + + install_element(VIEW_NODE, &show_isis_spf_ietf_cmd); + + install_element(VIEW_NODE, &show_isis_interface_cmd); + install_element(VIEW_NODE, &show_isis_interface_detail_cmd); + install_element(VIEW_NODE, &show_isis_interface_arg_cmd); + + install_element(VIEW_NODE, &show_isis_neighbor_cmd); + install_element(VIEW_NODE, &show_isis_neighbor_detail_cmd); + install_element(VIEW_NODE, &show_isis_neighbor_arg_cmd); + install_element(ENABLE_NODE, &clear_isis_neighbor_cmd); + install_element(ENABLE_NODE, &clear_isis_neighbor_arg_cmd); + + install_element(VIEW_NODE, &show_hostname_cmd); + install_element(VIEW_NODE, &show_database_cmd); + + install_element(ENABLE_NODE, &show_debugging_isis_cmd); + + install_node(&debug_node); + + install_element(ENABLE_NODE, &debug_isis_adj_cmd); + install_element(ENABLE_NODE, &no_debug_isis_adj_cmd); + install_element(ENABLE_NODE, &debug_isis_tx_queue_cmd); + install_element(ENABLE_NODE, &no_debug_isis_tx_queue_cmd); + install_element(ENABLE_NODE, &debug_isis_flooding_cmd); + install_element(ENABLE_NODE, &no_debug_isis_flooding_cmd); + install_element(ENABLE_NODE, &debug_isis_snp_cmd); + install_element(ENABLE_NODE, &no_debug_isis_snp_cmd); + install_element(ENABLE_NODE, &debug_isis_upd_cmd); + install_element(ENABLE_NODE, &no_debug_isis_upd_cmd); + install_element(ENABLE_NODE, &debug_isis_spfevents_cmd); + install_element(ENABLE_NODE, &no_debug_isis_spfevents_cmd); + install_element(ENABLE_NODE, &debug_isis_srevents_cmd); + install_element(ENABLE_NODE, &no_debug_isis_srevents_cmd); + install_element(ENABLE_NODE, &debug_isis_teevents_cmd); + install_element(ENABLE_NODE, &no_debug_isis_teevents_cmd); + install_element(ENABLE_NODE, &debug_isis_lfa_cmd); + install_element(ENABLE_NODE, &no_debug_isis_lfa_cmd); + install_element(ENABLE_NODE, &debug_isis_rtevents_cmd); + install_element(ENABLE_NODE, &no_debug_isis_rtevents_cmd); + install_element(ENABLE_NODE, &debug_isis_events_cmd); + install_element(ENABLE_NODE, &no_debug_isis_events_cmd); + install_element(ENABLE_NODE, &debug_isis_packet_dump_cmd); + install_element(ENABLE_NODE, &no_debug_isis_packet_dump_cmd); + install_element(ENABLE_NODE, &debug_isis_lsp_gen_cmd); + install_element(ENABLE_NODE, &no_debug_isis_lsp_gen_cmd); + install_element(ENABLE_NODE, &debug_isis_lsp_sched_cmd); + install_element(ENABLE_NODE, &no_debug_isis_lsp_sched_cmd); + install_element(ENABLE_NODE, &debug_isis_bfd_cmd); + install_element(ENABLE_NODE, &no_debug_isis_bfd_cmd); + install_element(ENABLE_NODE, &debug_isis_ldp_sync_cmd); + install_element(ENABLE_NODE, &no_debug_isis_ldp_sync_cmd); + + install_element(CONFIG_NODE, &debug_isis_adj_cmd); + install_element(CONFIG_NODE, &no_debug_isis_adj_cmd); + install_element(CONFIG_NODE, &debug_isis_tx_queue_cmd); + install_element(CONFIG_NODE, &no_debug_isis_tx_queue_cmd); + install_element(CONFIG_NODE, &debug_isis_flooding_cmd); + install_element(CONFIG_NODE, &no_debug_isis_flooding_cmd); + install_element(CONFIG_NODE, &debug_isis_snp_cmd); + install_element(CONFIG_NODE, &no_debug_isis_snp_cmd); + install_element(CONFIG_NODE, &debug_isis_upd_cmd); + install_element(CONFIG_NODE, &no_debug_isis_upd_cmd); + install_element(CONFIG_NODE, &debug_isis_spfevents_cmd); + install_element(CONFIG_NODE, &no_debug_isis_spfevents_cmd); + install_element(CONFIG_NODE, &debug_isis_srevents_cmd); + install_element(CONFIG_NODE, &no_debug_isis_srevents_cmd); + install_element(CONFIG_NODE, &debug_isis_teevents_cmd); + install_element(CONFIG_NODE, &no_debug_isis_teevents_cmd); + install_element(CONFIG_NODE, &debug_isis_lfa_cmd); + install_element(CONFIG_NODE, &no_debug_isis_lfa_cmd); + install_element(CONFIG_NODE, &debug_isis_rtevents_cmd); + install_element(CONFIG_NODE, &no_debug_isis_rtevents_cmd); + install_element(CONFIG_NODE, &debug_isis_events_cmd); + install_element(CONFIG_NODE, &no_debug_isis_events_cmd); + install_element(CONFIG_NODE, &debug_isis_packet_dump_cmd); + install_element(CONFIG_NODE, &no_debug_isis_packet_dump_cmd); + install_element(CONFIG_NODE, &debug_isis_lsp_gen_cmd); + install_element(CONFIG_NODE, &no_debug_isis_lsp_gen_cmd); + install_element(CONFIG_NODE, &debug_isis_lsp_sched_cmd); + install_element(CONFIG_NODE, &no_debug_isis_lsp_sched_cmd); + install_element(CONFIG_NODE, &debug_isis_bfd_cmd); + install_element(CONFIG_NODE, &no_debug_isis_bfd_cmd); + install_element(CONFIG_NODE, &debug_isis_ldp_sync_cmd); + install_element(CONFIG_NODE, &no_debug_isis_ldp_sync_cmd); + + install_default(ROUTER_NODE); + +#ifdef FABRICD + install_element(CONFIG_NODE, &router_openfabric_cmd); + install_element(CONFIG_NODE, &no_router_openfabric_cmd); + + install_element(ROUTER_NODE, &net_cmd); + install_element(ROUTER_NODE, &no_net_cmd); + + install_element(ROUTER_NODE, &isis_topology_cmd); + install_element(ROUTER_NODE, &no_isis_topology_cmd); + + install_element(ROUTER_NODE, &log_adj_changes_cmd); + install_element(ROUTER_NODE, &no_log_adj_changes_cmd); +#endif /* ifdef FABRICD */ +#ifndef FABRICD + install_node(&isis_flex_algo_node); + install_default(ISIS_FLEX_ALGO_NODE); +#endif /* ifdnef FABRICD */ + + install_node(&isis_srv6_node); + install_default(ISIS_SRV6_NODE); + + install_node(&isis_srv6_node_msd_node); + install_default(ISIS_SRV6_NODE_MSD_NODE); + + spf_backoff_cmd_init(); +} diff --git a/isisd/isisd.h b/isisd/isisd.h new file mode 100644 index 0000000..f5042e4 --- /dev/null +++ b/isisd/isisd.h @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - isisd.h + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#ifndef ISISD_H +#define ISISD_H + +#include "vty.h" +#include "memory.h" + +#include "isisd/isis_constants.h" +#include "isisd/isis_common.h" +#include "isisd/isis_redist.h" +#include "isisd/isis_pdu_counter.h" +#include "isisd/isis_circuit.h" +#include "isisd/isis_sr.h" +#include "isisd/isis_srv6.h" +#include "isis_flags.h" +#include "isis_lsp.h" +#include "isis_lfa.h" +#include "qobj.h" +#include "ldp_sync.h" +#include "iso.h" + +DECLARE_MGROUP(ISISD); + +#ifdef FABRICD +static const bool fabricd = true; +#define PROTO_TYPE ZEBRA_ROUTE_OPENFABRIC +#define PROTO_NAME "openfabric" +#define PROTO_HELP "OpenFabric routing protocol\n" +#define PROTO_REDIST_STR FRR_REDIST_STR_FABRICD +#define PROTO_IP_REDIST_STR FRR_IP_REDIST_STR_FABRICD +#define PROTO_IP6_REDIST_STR FRR_IP6_REDIST_STR_FABRICD +#define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_FABRICD +#define PROTO_IP_REDIST_HELP FRR_IP_REDIST_HELP_STR_FABRICD +#define PROTO_IP6_REDIST_HELP FRR_IP6_REDIST_HELP_STR_FABRICD +#define ROUTER_NODE OPENFABRIC_NODE +#else +static const bool fabricd = false; +#define PROTO_TYPE ZEBRA_ROUTE_ISIS +#define PROTO_NAME "isis" +#define PROTO_HELP "IS-IS routing protocol\n" +#define PROTO_REDIST_STR FRR_REDIST_STR_ISISD +#define PROTO_IP_REDIST_STR FRR_IP_REDIST_STR_ISISD +#define PROTO_IP6_REDIST_STR FRR_IP6_REDIST_STR_ISISD +#define PROTO_REDIST_HELP FRR_REDIST_HELP_STR_ISISD +#define PROTO_IP_REDIST_HELP FRR_IP_REDIST_HELP_STR_ISISD +#define PROTO_IP6_REDIST_HELP FRR_IP6_REDIST_HELP_STR_ISISD +#define ROUTER_NODE ISIS_NODE +extern void isis_cli_init(void); +#endif + +#define ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) \ + if (argv_find(argv, argc, "vrf", &idx_vrf)) { \ + vrf_name = argv[idx_vrf + 1]->arg; \ + all_vrf = strmatch(vrf_name, "all"); \ + } + +extern struct zebra_privs_t isisd_privs; + +/* uncomment if you are a developer in bug hunt */ +/* #define EXTREME_DEBUG */ + +struct fabricd; + +struct isis_master { + /* ISIS instance. */ + struct list *isis; + /* ISIS thread master. */ + struct event_loop *master; + uint8_t options; +}; +#define F_ISIS_UNIT_TEST 0x01 + +#define ISIS_DEFAULT_MAX_AREA_ADDRESSES 3 + +struct isis { + vrf_id_t vrf_id; + char *name; + unsigned long process_id; + int sysid_set; + uint8_t sysid[ISIS_SYS_ID_LEN]; /* SystemID for this IS */ + uint32_t router_id; /* Router ID from zebra */ + struct list *area_list; /* list of IS-IS areas */ + uint8_t max_area_addrs; /* maximumAreaAdresses */ + struct iso_address *man_area_addrs; /* manualAreaAddresses */ + time_t uptime; /* when did we start */ + struct event *t_dync_clean; /* dynamic hostname cache cleanup thread */ + uint32_t circuit_ids_used[8]; /* 256 bits to track circuit ids 1 through 255 */ + int snmp_notifications; + struct list *dyn_cache; + + struct route_table *ext_info[REDIST_PROTOCOL_COUNT]; +}; + +extern struct isis_master *im; + +extern struct event *t_isis_cfg; + +enum spf_tree_id { + SPFTREE_IPV4 = 0, + SPFTREE_IPV6, + SPFTREE_DSTSRC, + SPFTREE_COUNT +}; + +struct lsp_refresh_arg { + struct isis_area *area; + int level; +}; + +/* for yang configuration */ +enum isis_metric_style { + ISIS_NARROW_METRIC = 0, + ISIS_WIDE_METRIC, + ISIS_TRANSITION_METRIC, +}; + +struct isis_area { + struct isis *isis; /* back pointer */ + struct lspdb_head lspdb[ISIS_LEVELS]; /* link-state dbs */ + struct isis_spftree *spftree[SPFTREE_COUNT][ISIS_LEVELS]; +#define DEFAULT_LSP_MTU 1497 + unsigned int lsp_mtu; /* Size of LSPs to generate */ + struct list *circuit_list; /* IS-IS circuits */ + struct list *adjacency_list; /* IS-IS adjacencies */ + struct flags flags; + struct event *t_tick; /* LSP walker */ + struct event *t_lsp_refresh[ISIS_LEVELS]; + struct event *t_overload_on_startup_timer; + struct timeval last_lsp_refresh_event[ISIS_LEVELS]; + struct event *t_rlfa_rib_update; + /* t_lsp_refresh is used in two ways: + * a) regular refresh of LSPs + * b) (possibly throttled) updates to LSPs + * + * The lsp_regenerate_pending flag tracks whether the timer is active + * for the a) or the b) case. + * + * It is of utmost importance to clear this flag when the timer is + * rescheduled for normal refresh, because otherwise, updates will + * be delayed until the next regular refresh. + */ + int lsp_regenerate_pending[ISIS_LEVELS]; + + bool bfd_signalled_down; + bool bfd_force_spf_refresh; + + struct fabricd *fabricd; + + /* + * Configurables + */ + struct isis_passwd area_passwd; + struct isis_passwd domain_passwd; + /* do we support dynamic hostnames? */ + char dynhostname; + /* do we support new style metrics? */ + char newmetric; + char oldmetric; + /* Allow sending the default admin-group value of 0x00000000. */ + bool admin_group_send_zero; + /* Set the legacy flag (aka. L-FLAG) in the ASLA Sub-TLV */ + bool asla_legacy_flag; + /* identifies the routing instance */ + char *area_tag; + /* area addresses for this area */ + struct list *area_addrs; + uint16_t max_lsp_lifetime[ISIS_LEVELS]; + char is_type; /* level-1 level-1-2 or level-2-only */ + /* are we overloaded? */ + char overload_bit; + bool overload_configured; + uint32_t overload_counter; + uint32_t overload_on_startup_time; + /* advertise prefixes of passive interfaces only? */ + bool advertise_passive_only; + /* Are we advertising high metrics? */ + bool advertise_high_metrics; + /* L1/L2 router identifier for inter-area traffic */ + char attached_bit_send; + char attached_bit_rcv_ignore; + uint16_t lsp_refresh[ISIS_LEVELS]; + /* minimum time allowed before lsp retransmission */ + uint16_t lsp_gen_interval[ISIS_LEVELS]; + /* min interval between between consequtive SPFs */ + uint16_t min_spf_interval[ISIS_LEVELS]; + /* the percentage of LSP mtu size used, before generating a new frag */ + int lsp_frag_threshold; + uint64_t lsp_gen_count[ISIS_LEVELS]; + uint64_t lsp_purge_count[ISIS_LEVELS]; + uint32_t lsp_exceeded_max_counter; + uint32_t lsp_seqno_skipped_counter; + uint64_t spf_run_count[ISIS_LEVELS]; + int ip_circuits; + /* logging adjacency changes? */ + uint8_t log_adj_changes; + /* logging pdu drops? */ + uint8_t log_pdu_drops; + /* multi topology settings */ + struct list *mt_settings; + /* MPLS-TE settings */ + struct mpls_te_area *mta; + /* Segment Routing information */ + struct isis_sr_db srdb; + /* Segment Routing over IPv6 (SRv6) information */ + struct isis_srv6_db srv6db; + int ipv6_circuits; + bool purge_originator; + /* SPF prefix priorities. */ + struct spf_prefix_priority_acl + spf_prefix_priorities[SPF_PREFIX_PRIO_MAX]; + /* Fast Re-Route information. */ + size_t lfa_protected_links[ISIS_LEVELS]; + size_t lfa_load_sharing[ISIS_LEVELS]; + enum spf_prefix_priority lfa_priority_limit[ISIS_LEVELS]; + struct lfa_tiebreaker_tree_head lfa_tiebreakers[ISIS_LEVELS]; + char *rlfa_plist_name[ISIS_LEVELS]; + struct prefix_list *rlfa_plist[ISIS_LEVELS]; + size_t rlfa_protected_links[ISIS_LEVELS]; + size_t tilfa_protected_links[ISIS_LEVELS]; + /* MPLS LDP-IGP Sync */ + struct ldp_sync_info_cmd ldp_sync_cmd; +#ifndef FABRICD + /* Flex-Algo */ + struct flex_algos *flex_algos; +#endif /* ifndef FABRICD */ + /* Counters */ + uint32_t circuit_state_changes; + struct list *redist_settings[REDIST_PROTOCOL_COUNT][ZEBRA_ROUTE_MAX + 1] + [ISIS_LEVELS]; + struct route_table *ext_reach[REDIST_PROTOCOL_COUNT][ISIS_LEVELS]; + + struct spf_backoff *spf_delay_ietf[ISIS_LEVELS]; /*Structure with IETF + SPF algo + parameters*/ + struct event *spf_timer[ISIS_LEVELS]; + + struct lsp_refresh_arg lsp_refresh_arg[ISIS_LEVELS]; + + pdu_counter_t pdu_tx_counters; + pdu_counter_t pdu_rx_counters; + pdu_counter_t pdu_drop_counters; + uint64_t lsp_rxmt_count; + + /* Area counters */ + uint64_t rej_adjacencies[2]; + uint64_t auth_type_failures[2]; + uint64_t auth_failures[2]; + uint64_t id_len_mismatches[2]; + uint64_t lsp_error_counter[2]; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(isis_area); + +DECLARE_MTYPE(ISIS_ACL_NAME); /* isis_area->spf_prefix_prioritites */ +DECLARE_MTYPE(ISIS_AREA_ADDR); /* isis_area->area_addrs */ +DECLARE_MTYPE(ISIS_PLIST_NAME); + +DECLARE_HOOK(isis_area_overload_bit_update, (struct isis_area * area), (area)); + +void isis_terminate(void); +void isis_master_init(struct event_loop *master); +void isis_vrf_link(struct isis *isis, struct vrf *vrf); +void isis_vrf_unlink(struct isis *isis, struct vrf *vrf); +struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id); +struct isis *isis_lookup_by_vrfname(const char *vrfname); +struct isis *isis_lookup_by_sysid(const uint8_t *sysid); + +void isis_init(void); +void isis_vrf_init(void); + +struct isis *isis_new(const char *vrf_name); +void isis_finish(struct isis *isis); + +void isis_area_add_circuit(struct isis_area *area, + struct isis_circuit *circuit); +void isis_area_del_circuit(struct isis_area *area, + struct isis_circuit *circuit); + +struct isis_area *isis_area_create(const char *, const char *); +struct isis_area *isis_area_lookup(const char *, vrf_id_t vrf_id); +struct isis_area *isis_area_lookup_by_vrf(const char *area_tag, + const char *vrf_name); +int isis_area_get(struct vty *vty, const char *area_tag); +void isis_area_destroy(struct isis_area *area); +void isis_filter_update(struct access_list *access); +void isis_prefix_list_update(struct prefix_list *plist); +void print_debug(struct vty *, int, int); +struct isis_lsp *lsp_for_sysid(struct lspdb_head *head, const char *sysid_str, + struct isis *isis); + +void isis_area_invalidate_routes(struct isis_area *area, int levels); +void isis_area_verify_routes(struct isis_area *area); +void isis_area_switchover_routes(struct isis_area *area, int family, + union g_addr *nexthop_ip, ifindex_t ifindex, + int level); + +void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit); +void isis_area_overload_on_startup_set(struct isis_area *area, + uint32_t startup_time); +void isis_area_advertise_high_metrics_set(struct isis_area *area, + bool advertise_high_metrics); +void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit); +void isis_area_attached_bit_receive_set(struct isis_area *area, + bool attached_bit); +void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname); +void isis_area_metricstyle_set(struct isis_area *area, bool old_metric, + bool new_metric); +void isis_area_lsp_mtu_set(struct isis_area *area, unsigned int lsp_mtu); +void isis_area_is_type_set(struct isis_area *area, int is_type); +void isis_area_max_lsp_lifetime_set(struct isis_area *area, int level, + uint16_t max_lsp_lifetime); +void isis_area_lsp_refresh_set(struct isis_area *area, int level, + uint16_t lsp_refresh); +/* IS_LEVEL_1 sets area_passwd, IS_LEVEL_2 domain_passwd */ +int isis_area_passwd_unset(struct isis_area *area, int level); +int isis_area_passwd_cleartext_set(struct isis_area *area, int level, + const char *passwd, uint8_t snp_auth); +int isis_area_passwd_hmac_md5_set(struct isis_area *area, int level, + const char *passwd, uint8_t snp_auth); +void show_isis_database_lspdb_json(struct json_object *json, + struct isis_area *area, int level, + struct lspdb_head *lspdb, const char *argv, + int ui_level); +void show_isis_database_lspdb_vty(struct vty *vty, struct isis_area *area, + int level, struct lspdb_head *lspdb, + const char *argv, int ui_level); +char *isis_restart_filepath(void); +void isis_restart_write_overload_time(struct isis_area *isis_area, + uint32_t overload_time); +uint32_t isis_restart_read_overload_time(struct isis_area *isis_area); +void config_end_lsp_generate(struct isis_area *area); + +/* YANG paths */ +#define ISIS_INSTANCE "/frr-isisd:isis/instance" +#define ISIS_SR "/frr-isisd:isis/instance/segment-routing" +#define ISIS_SRV6 "/frr-isisd:isis/instance/segment-routing-srv6" + +/* Master of threads. */ +extern struct event_loop *master; + +extern unsigned long debug_adj_pkt; +extern unsigned long debug_snp_pkt; +extern unsigned long debug_update_pkt; +extern unsigned long debug_spf_events; +extern unsigned long debug_rte_events; +extern unsigned long debug_events; +extern unsigned long debug_pkt_dump; +extern unsigned long debug_lsp_gen; +extern unsigned long debug_lsp_sched; +extern unsigned long debug_flooding; +extern unsigned long debug_bfd; +extern unsigned long debug_tx_queue; +extern unsigned long debug_sr; +extern unsigned long debug_ldp_sync; +extern unsigned long debug_lfa; +extern unsigned long debug_te; + +#define DEBUG_ADJ_PACKETS (1<<0) +#define DEBUG_SNP_PACKETS (1<<1) +#define DEBUG_UPDATE_PACKETS (1<<2) +#define DEBUG_SPF_EVENTS (1<<3) +#define DEBUG_RTE_EVENTS (1<<4) +#define DEBUG_EVENTS (1<<5) +#define DEBUG_PACKET_DUMP (1<<6) +#define DEBUG_LSP_GEN (1<<7) +#define DEBUG_LSP_SCHED (1<<8) +#define DEBUG_FLOODING (1<<9) +#define DEBUG_BFD (1<<10) +#define DEBUG_TX_QUEUE (1<<11) +#define DEBUG_SR (1<<12) +#define DEBUG_LDP_SYNC (1<<13) +#define DEBUG_LFA (1<<14) +#define DEBUG_TE (1<<15) + +/* Debug related macro. */ +#define IS_DEBUG_ADJ_PACKETS (debug_adj_pkt & DEBUG_ADJ_PACKETS) +#define IS_DEBUG_SNP_PACKETS (debug_snp_pkt & DEBUG_SNP_PACKETS) +#define IS_DEBUG_UPDATE_PACKETS (debug_update_pkt & DEBUG_UPDATE_PACKETS) +#define IS_DEBUG_SPF_EVENTS (debug_spf_events & DEBUG_SPF_EVENTS) +#define IS_DEBUG_RTE_EVENTS (debug_rte_events & DEBUG_RTE_EVENTS) +#define IS_DEBUG_EVENTS (debug_events & DEBUG_EVENTS) +#define IS_DEBUG_PACKET_DUMP (debug_pkt_dump & DEBUG_PACKET_DUMP) +#define IS_DEBUG_LSP_GEN (debug_lsp_gen & DEBUG_LSP_GEN) +#define IS_DEBUG_LSP_SCHED (debug_lsp_sched & DEBUG_LSP_SCHED) +#define IS_DEBUG_FLOODING (debug_flooding & DEBUG_FLOODING) +#define IS_DEBUG_BFD (debug_bfd & DEBUG_BFD) +#define IS_DEBUG_TX_QUEUE (debug_tx_queue & DEBUG_TX_QUEUE) +#define IS_DEBUG_SR (debug_sr & DEBUG_SR) +#define IS_DEBUG_LDP_SYNC (debug_ldp_sync & DEBUG_LDP_SYNC) +#define IS_DEBUG_LFA (debug_lfa & DEBUG_LFA) +#define IS_DEBUG_TE (debug_te & DEBUG_TE) + +#define lsp_debug(...) \ + do { \ + if (IS_DEBUG_LSP_GEN) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +#define sched_debug(...) \ + do { \ + if (IS_DEBUG_LSP_SCHED) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +#define sr_debug(...) \ + do { \ + if (IS_DEBUG_SR) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +#define te_debug(...) \ + do { \ + if (IS_DEBUG_TE) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +#endif /* ISISD_H */ diff --git a/isisd/iso_checksum.c b/isisd/iso_checksum.c new file mode 100644 index 0000000..f12c195 --- /dev/null +++ b/isisd/iso_checksum.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - iso_checksum.c + * ISO checksum related routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ + +#include +#include "iso_checksum.h" +#include "checksum.h" + +/* + * Calculations of the OSI checksum. + * ISO/IEC 8473 defines the sum as + * + * L + * sum a (mod 255) = 0 + * 1 i + * + * L + * sum (L-i+1)a (mod 255) = 0 + * 1 i + * + */ + +/* + * Verifies that the checksum is correct. + * Return 0 on correct and 1 on invalid checksum. + * Based on Annex C.4 of ISO/IEC 8473 + */ + +int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset) +{ + uint16_t checksum; + uint32_t c0; + uint32_t c1; + + c0 = csum & 0xff00; + c1 = csum & 0x00ff; + + /* + * If both are zero return correct + */ + if (c0 == 0 && c1 == 0) + return 0; + + /* + * If either, but not both are zero return incorrect + */ + if (c0 == 0 || c1 == 0) + return 1; + + checksum = fletcher_checksum(buffer, len, offset); + if (checksum == htons(csum)) + return 0; + return 1; +} diff --git a/isisd/iso_checksum.h b/isisd/iso_checksum.h new file mode 100644 index 0000000..9dcb039 --- /dev/null +++ b/isisd/iso_checksum.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IS-IS Rout(e)ing protocol - iso_checksum.c + * ISO checksum related routines + * + * Copyright (C) 2001,2002 Sampo Saaristo + * Tampere University of Technology + * Institute of Communications Engineering + */ +#ifndef _ZEBRA_ISO_CSUM_H +#define _ZEBRA_ISO_CSUM_H + +int iso_csum_verify(uint8_t *buffer, int len, uint16_t csum, int offset); + +#endif /* _ZEBRA_ISO_CSUM_H */ diff --git a/isisd/subdir.am b/isisd/subdir.am new file mode 100644 index 0000000..e33cb76 --- /dev/null +++ b/isisd/subdir.am @@ -0,0 +1,140 @@ +# +# isisd +# + +if ISISD +noinst_LIBRARIES += isisd/libisis.a +sbin_PROGRAMS += isisd/isisd +vtysh_daemons += isisd +if SNMP +module_LTLIBRARIES += isisd/isisd_snmp.la +endif +man8 += $(MANBUILD)/frr-isisd.8 +endif + +if FABRICD +noinst_LIBRARIES += isisd/libfabric.a +sbin_PROGRAMS += isisd/fabricd +vtysh_daemons += fabricd +endif + +noinst_HEADERS += \ + isisd/isis_affinitymap.h \ + isisd/isis_adjacency.h \ + isisd/isis_bfd.h \ + isisd/isis_circuit.h \ + isisd/isis_common.h \ + isisd/isis_constants.h \ + isisd/isis_csm.h \ + isisd/isis_dr.h \ + isisd/isis_dynhn.h \ + isisd/isis_errors.h \ + isisd/isis_events.h \ + isisd/isis_flags.h \ + isisd/isis_ldp_sync.h \ + isisd/isis_lfa.h \ + isisd/isis_lsp.h \ + isisd/isis_misc.h \ + isisd/isis_mt.h \ + isisd/isis_nb.h \ + isisd/isis_network.h \ + isisd/isis_pdu.h \ + isisd/isis_pdu_counter.h \ + isisd/isis_redist.h \ + isisd/isis_route.h \ + isisd/isis_routemap.h \ + isisd/isis_spf.h \ + isisd/isis_spf_private.h \ + isisd/isis_sr.h \ + isisd/isis_flex_algo.h \ + isisd/isis_srv6.h \ + isisd/isis_te.h \ + isisd/isis_tlvs.h \ + isisd/isis_tx_queue.h \ + isisd/isis_zebra.h \ + isisd/isisd.h \ + isisd/iso_checksum.h \ + isisd/fabricd.h \ + # end + +LIBISIS_SOURCES = \ + isisd/isis_affinitymap.c \ + isisd/isis_adjacency.c \ + isisd/isis_bfd.c \ + isisd/isis_circuit.c \ + isisd/isis_csm.c \ + isisd/isis_dr.c \ + isisd/isis_dynhn.c \ + isisd/isis_errors.c \ + isisd/isis_events.c \ + isisd/isis_flags.c \ + isisd/isis_ldp_sync.c \ + isisd/isis_lfa.c \ + isisd/isis_lsp.c \ + isisd/isis_misc.c \ + isisd/isis_mt.c \ + isisd/isis_pdu.c \ + isisd/isis_pdu_counter.c \ + isisd/isis_redist.c \ + isisd/isis_route.c \ + isisd/isis_routemap.c \ + isisd/isis_spf.c \ + isisd/isis_sr.c \ + isisd/isis_flex_algo.c \ + isisd/isis_srv6.c \ + isisd/isis_te.c \ + isisd/isis_tlvs.c \ + isisd/isis_tx_queue.c \ + isisd/isis_zebra.c \ + isisd/isisd.c \ + isisd/iso_checksum.c \ + isisd/fabricd.c \ + # end + +ISIS_SOURCES = \ + isisd/isis_bpf.c \ + isisd/isis_dlpi.c \ + isisd/isis_main.c \ + isisd/isis_pfpacket.c \ + # end + +ISIS_LDADD_COMMON = lib/libfrr.la $(LIBCAP) $(LIBYANG_LIBS) + +# Building isisd + +isisd_libisis_a_SOURCES = \ + $(LIBISIS_SOURCES) \ + isisd/isis_nb.c \ + isisd/isis_nb_config.c \ + isisd/isis_nb_notifications.c \ + isisd/isis_nb_state.c \ + isisd/isis_cli.c \ + #end + +clippy_scan += \ + isisd/isis_cli.c \ + # end + +isisd_isisd_LDADD = isisd/libisis.a $(ISIS_LDADD_COMMON) +isisd_isisd_SOURCES = $(ISIS_SOURCES) +nodist_isisd_isisd_SOURCES = \ + yang/frr-isisd.yang.c \ + # end + +isisd_isisd_snmp_la_SOURCES = isisd/isis_snmp.c +isisd_isisd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +isisd_isisd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +isisd_isisd_snmp_la_LIBADD = lib/libfrrsnmp.la + +# Building fabricd + +FABRICD_CPPFLAGS = -DFABRICD=1 $(AM_CPPFLAGS) + +isisd_libfabric_a_SOURCES = \ + $(LIBISIS_SOURCES) \ + isisd/isis_vty_fabricd.c \ + #end +isisd_libfabric_a_CPPFLAGS = $(FABRICD_CPPFLAGS) +isisd_fabricd_LDADD = isisd/libfabric.a $(ISIS_LDADD_COMMON) +isisd_fabricd_SOURCES = $(ISIS_SOURCES) +isisd_fabricd_CPPFLAGS = $(FABRICD_CPPFLAGS) diff --git a/ldpd/.gitignore b/ldpd/.gitignore new file mode 100644 index 0000000..ec8a5c4 --- /dev/null +++ b/ldpd/.gitignore @@ -0,0 +1,2 @@ +ldpd +ldpd.conf diff --git a/ldpd/Makefile b/ldpd/Makefile new file mode 100644 index 0000000..464e02c --- /dev/null +++ b/ldpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ldpd/ldpd +%: ALWAYS + @$(MAKE) -s -C .. ldpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ldpd/accept.c b/ldpd/accept.c new file mode 100644 index 0000000..8e881e7 --- /dev/null +++ b/ldpd/accept.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2012 Claudio Jeker + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +struct accept_ev { + LIST_ENTRY(accept_ev) entry; + struct event *ev; + void (*accept_cb)(struct event *); + void *arg; + int fd; +}; + +struct { + LIST_HEAD(, accept_ev) queue; + struct event *evt; +} accept_queue; + +static void accept_arm(void); +static void accept_unarm(void); +static void accept_cb(struct event *); +static void accept_timeout(struct event *); + +void +accept_init(void) +{ + LIST_INIT(&accept_queue.queue); +} + +int accept_add(int fd, void (*cb)(struct event *), void *arg) +{ + struct accept_ev *av; + + if ((av = calloc(1, sizeof(*av))) == NULL) + return (-1); + av->fd = fd; + av->accept_cb = cb; + av->arg = arg; + LIST_INSERT_HEAD(&accept_queue.queue, av, entry); + + event_add_read(master, accept_cb, av, av->fd, &av->ev); + + log_debug("%s: accepting on fd %d", __func__, fd); + + return (0); +} + +void +accept_del(int fd) +{ + struct accept_ev *av; + + LIST_FOREACH(av, &accept_queue.queue, entry) + if (av->fd == fd) { + log_debug("%s: %d removed from queue", __func__, fd); + EVENT_OFF(av->ev); + LIST_REMOVE(av, entry); + free(av); + return; + } +} + +void +accept_pause(void) +{ + log_debug(__func__); + accept_unarm(); + event_add_timer(master, accept_timeout, NULL, 1, &accept_queue.evt); +} + +void +accept_unpause(void) +{ + if (accept_queue.evt != NULL) { + log_debug(__func__); + EVENT_OFF(accept_queue.evt); + accept_arm(); + } +} + +static void +accept_arm(void) +{ + struct accept_ev *av; + LIST_FOREACH(av, &accept_queue.queue, entry) { + event_add_read(master, accept_cb, av, av->fd, &av->ev); + } +} + +static void +accept_unarm(void) +{ + struct accept_ev *av; + LIST_FOREACH(av, &accept_queue.queue, entry) + EVENT_OFF(av->ev); +} + +static void accept_cb(struct event *thread) +{ + struct accept_ev *av = EVENT_ARG(thread); + event_add_read(master, accept_cb, av, av->fd, &av->ev); + av->accept_cb(thread); +} + +static void accept_timeout(struct event *thread) +{ + accept_queue.evt = NULL; + + log_debug(__func__); + accept_arm(); +} diff --git a/ldpd/address.c b/ldpd/address.c new file mode 100644 index 0000000..107eb5d --- /dev/null +++ b/ldpd/address.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" +#include "ldp_debug.h" + +static void send_address(struct nbr *, int, struct if_addr_head *, + unsigned int, int); +static int gen_address_list_tlv(struct ibuf *, int, struct if_addr_head *, + unsigned int); +static int gen_mac_list_tlv(struct ibuf *, uint8_t *); +static void address_list_add(struct if_addr_head *, struct if_addr *); +static void address_list_clr(struct if_addr_head *); +static void log_msg_address(int, uint16_t, struct nbr *, int, + union ldpd_addr *); +static void log_msg_mac_withdrawal(int, struct nbr *, uint8_t *); + +static void +send_address(struct nbr *nbr, int af, struct if_addr_head *addr_list, + unsigned int addr_count, int withdraw) +{ + struct ibuf *buf; + uint16_t msg_type; + uint8_t addr_size; + struct if_addr *if_addr; + uint16_t size; + unsigned int tlv_addr_count = 0; + int err = 0; + + /* nothing to send */ + if (LIST_EMPTY(addr_list)) + return; + + if (!withdraw) + msg_type = MSG_TYPE_ADDR; + else + msg_type = MSG_TYPE_ADDRWITHDRAW; + + switch (af) { + case AF_INET: + addr_size = sizeof(struct in_addr); + break; + case AF_INET6: + addr_size = sizeof(struct in6_addr); + break; + default: + fatalx("send_address: unknown af"); + } + + while (LIST_FIRST(addr_list) != NULL) { + /* + * Send as many addresses as possible - respect the session's + * negotiated maximum pdu length. + */ + size = LDP_HDR_SIZE + LDP_MSG_SIZE + ADDR_LIST_SIZE; + if (size + addr_count * addr_size <= nbr->max_pdu_len) + tlv_addr_count = addr_count; + else + tlv_addr_count = (nbr->max_pdu_len - size) / addr_size; + size += tlv_addr_count * addr_size; + addr_count -= tlv_addr_count; + + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + SET_FLAG(err, gen_ldp_hdr(buf, size)); + size -= LDP_HDR_SIZE; + SET_FLAG(err, gen_msg_hdr(buf, msg_type, size)); + size -= LDP_MSG_SIZE; + SET_FLAG(err, gen_address_list_tlv(buf, af, addr_list, tlv_addr_count)); + (void)size; + + if (err) { + address_list_clr(addr_list); + ibuf_free(buf); + return; + } + + while ((if_addr = LIST_FIRST(addr_list)) != NULL) { + log_msg_address(1, msg_type, nbr, af, &if_addr->addr); + + LIST_REMOVE(if_addr, entry); + assert(if_addr != LIST_FIRST(addr_list)); + free(if_addr); + if (--tlv_addr_count == 0) + break; + } + + evbuf_enqueue(&nbr->tcp->wbuf, buf); + + /* no errors - update per neighbor message counters */ + switch (msg_type) { + case MSG_TYPE_ADDR: + nbr->stats.addr_sent++; + break; + case MSG_TYPE_ADDRWITHDRAW: + nbr->stats.addrwdraw_sent++; + break; + default: + break; + } + } + + nbr_fsm(nbr, NBR_EVT_PDU_SENT); +} + +void +send_address_single(struct nbr *nbr, struct if_addr *if_addr, int withdraw) +{ + struct if_addr_head addr_list; + + LIST_INIT(&addr_list); + address_list_add(&addr_list, if_addr); + send_address(nbr, if_addr->af, &addr_list, 1, withdraw); +} + +void +send_address_all(struct nbr *nbr, int af) +{ + struct if_addr_head addr_list; + struct if_addr *if_addr; + unsigned int addr_count = 0; + + LIST_INIT(&addr_list); + LIST_FOREACH(if_addr, &global.addr_list, entry) { + if (if_addr->af != af) + continue; + + address_list_add(&addr_list, if_addr); + addr_count++; + } + + send_address(nbr, af, &addr_list, addr_count, 0); +} + +void +send_mac_withdrawal(struct nbr *nbr, struct map *fec, uint8_t *mac) +{ + struct ibuf *buf; + uint16_t size; + int err; + + size = LDP_HDR_SIZE + LDP_MSG_SIZE + ADDR_LIST_SIZE + len_fec_tlv(fec) + + TLV_HDR_SIZE; + if (mac) + size += ETH_ALEN; + + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + err = gen_ldp_hdr(buf, size); + size -= LDP_HDR_SIZE; + + SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_ADDRWITHDRAW, size)); + SET_FLAG(err, gen_address_list_tlv(buf, AF_INET, NULL, 0)); + SET_FLAG(err, gen_fec_tlv(buf, fec)); + SET_FLAG(err, gen_mac_list_tlv(buf, mac)); + + if (err) { + ibuf_free(buf); + return; + } + + log_msg_mac_withdrawal(1, nbr, mac); + + evbuf_enqueue(&nbr->tcp->wbuf, buf); + + nbr_fsm(nbr, NBR_EVT_PDU_SENT); +} + +int +recv_address(struct nbr *nbr, char *buf, uint16_t len) +{ + struct ldp_msg msg; + uint16_t msg_type; + enum imsg_type type; + struct address_list_tlv alt; + uint16_t alt_len; + uint16_t alt_family; + struct lde_addr lde_addr; + + memcpy(&msg, buf, sizeof(msg)); + msg_type = ntohs(msg.type); + switch (msg_type) { + case MSG_TYPE_ADDR: + type = IMSG_ADDRESS_ADD; + break; + case MSG_TYPE_ADDRWITHDRAW: + type = IMSG_ADDRESS_DEL; + break; + default: + fatalx("recv_address: unexpected msg type"); + } + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + /* Address List TLV */ + if (len < ADDR_LIST_SIZE) { + session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); + return (-1); + } + memcpy(&alt, buf, sizeof(alt)); + alt_len = ntohs(alt.length); + alt_family = ntohs(alt.family); + if (alt_len > len - TLV_HDR_SIZE) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + if (ntohs(alt.type) != TLV_TYPE_ADDRLIST) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + return (-1); + } + switch (alt_family) { + case AF_IPV4: + if (!nbr->v4_enabled) + /* just ignore the message */ + return (0); + break; + case AF_IPV6: + if (!nbr->v6_enabled) + /* just ignore the message */ + return (0); + break; + default: + send_notification(nbr->tcp, S_UNSUP_ADDR, msg.id, msg.type); + return (-1); + } + alt_len -= sizeof(alt.family); + buf += sizeof(alt); + len -= sizeof(alt); + + /* Process all received addresses */ + while (alt_len > 0) { + switch (alt_family) { + case AF_IPV4: + if (alt_len < sizeof(struct in_addr)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, + msg.type); + return (-1); + } + + memset(&lde_addr, 0, sizeof(lde_addr)); + lde_addr.af = AF_INET; + memcpy(&lde_addr.addr, buf, sizeof(struct in_addr)); + + buf += sizeof(struct in_addr); + len -= sizeof(struct in_addr); + alt_len -= sizeof(struct in_addr); + break; + case AF_IPV6: + if (alt_len < sizeof(struct in6_addr)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, + msg.type); + return (-1); + } + + memset(&lde_addr, 0, sizeof(lde_addr)); + lde_addr.af = AF_INET6; + memcpy(&lde_addr.addr, buf, sizeof(struct in6_addr)); + + buf += sizeof(struct in6_addr); + len -= sizeof(struct in6_addr); + alt_len -= sizeof(struct in6_addr); + break; + default: + fatalx("recv_address: unknown af"); + } + + log_msg_address(0, msg_type, nbr, lde_addr.af, &lde_addr.addr); + + ldpe_imsg_compose_lde(type, nbr->peerid, 0, &lde_addr, + sizeof(lde_addr)); + } + + /* Optional Parameters */ + while (len > 0) { + struct tlv tlv; + uint16_t tlv_type; + uint16_t tlv_len; + + if (len < sizeof(tlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_type = ntohs(tlv.type); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + + switch (tlv_type) { + default: + if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) + send_notification_rtlvs(nbr, S_UNKNOWN_TLV, + msg.id, msg.type, tlv_type, tlv_len, buf); + /* ignore unknown tlv */ + break; + } + buf += tlv_len; + len -= tlv_len; + } + + return (0); +} + +static int +gen_address_list_tlv(struct ibuf *buf, int af, struct if_addr_head *addr_list, + unsigned int tlv_addr_count) +{ + struct address_list_tlv alt; + uint16_t addr_size; + struct if_addr *if_addr; + int err = 0; + + memset(&alt, 0, sizeof(alt)); + alt.type = htons(TLV_TYPE_ADDRLIST); + + switch (af) { + case AF_INET: + alt.family = htons(AF_IPV4); + addr_size = sizeof(struct in_addr); + break; + case AF_INET6: + alt.family = htons(AF_IPV6); + addr_size = sizeof(struct in6_addr); + break; + default: + fatalx("gen_address_list_tlv: unknown af"); + } + alt.length = htons(sizeof(alt.family) + addr_size * tlv_addr_count); + + SET_FLAG(err, ibuf_add(buf, &alt, sizeof(alt))); + + if (addr_list == NULL) + return (err); + + LIST_FOREACH(if_addr, addr_list, entry) { + SET_FLAG(err, ibuf_add(buf, &if_addr->addr, addr_size)); + + if (--tlv_addr_count == 0) + break; + } + + return (err); +} + +static int +gen_mac_list_tlv(struct ibuf *buf, uint8_t *mac) +{ + struct tlv tlv; + int err; + + memset(&tlv, 0, sizeof(tlv)); + tlv.type = htons(TLV_TYPE_MAC_LIST); + if (mac) + tlv.length = htons(ETH_ALEN); + err = ibuf_add(buf, &tlv, sizeof(tlv)); + if (mac) + SET_FLAG(err, ibuf_add(buf, mac, ETH_ALEN)); + + return (err); +} + +static void +address_list_add(struct if_addr_head *addr_list, struct if_addr *if_addr) +{ + struct if_addr *new; + + new = malloc(sizeof(*new)); + if (new == NULL) + fatal(__func__); + *new = *if_addr; + + LIST_INSERT_HEAD(addr_list, new, entry); +} + +static void +address_list_clr(struct if_addr_head *addr_list) +{ + struct if_addr *if_addr; + + while ((if_addr = LIST_FIRST(addr_list)) != NULL) { + LIST_REMOVE(if_addr, entry); + assert(if_addr != LIST_FIRST(addr_list)); + free(if_addr); + } +} + +static void +log_msg_address(int out, uint16_t msg_type, struct nbr *nbr, int af, + union ldpd_addr *addr) +{ + debug_msg(out, "%s: lsr-id %pI4, address %s", msg_name(msg_type), + &nbr->id, log_addr(af, addr)); +} + +static void +log_msg_mac_withdrawal(int out, struct nbr *nbr, uint8_t *mac) +{ + char buf[ETHER_ADDR_STRLEN]; + + debug_msg(out, "mac withdrawal: lsr-id %pI4, mac %s", &nbr->id, + (mac) ? prefix_mac2str((struct ethaddr *)mac, buf, sizeof(buf)) : + "wildcard"); +} diff --git a/ldpd/adjacency.c b/ldpd/adjacency.c new file mode 100644 index 0000000..0108af8 --- /dev/null +++ b/ldpd/adjacency.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2015 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +static __inline int adj_compare(const struct adj *, const struct adj *); +static void adj_itimer(struct event *); +static __inline int tnbr_compare(const struct tnbr *, const struct tnbr *); +static void tnbr_del(struct ldpd_conf *, struct tnbr *); +static void tnbr_start(struct tnbr *); +static void tnbr_stop(struct tnbr *); +static void tnbr_hello_timer(struct event *); +static void tnbr_start_hello_timer(struct tnbr *); +static void tnbr_stop_hello_timer(struct tnbr *); + +RB_GENERATE(global_adj_head, adj, global_entry, adj_compare) +RB_GENERATE(nbr_adj_head, adj, nbr_entry, adj_compare) +RB_GENERATE(ia_adj_head, adj, ia_entry, adj_compare) +RB_GENERATE(tnbr_head, tnbr, entry, tnbr_compare) + +static __inline int +adj_compare(const struct adj *a, const struct adj *b) +{ + if (adj_get_af(a) < adj_get_af(b)) + return (-1); + if (adj_get_af(a) > adj_get_af(b)) + return (1); + + if (ntohl(a->lsr_id.s_addr) < ntohl(b->lsr_id.s_addr)) + return (-1); + if (ntohl(a->lsr_id.s_addr) > ntohl(b->lsr_id.s_addr)) + return (1); + + if (a->source.type < b->source.type) + return (-1); + if (a->source.type > b->source.type) + return (1); + + switch (a->source.type) { + case HELLO_LINK: + if (if_cmp_name_func(a->source.link.ia->iface->name, + b->source.link.ia->iface->name) < 0) + return (-1); + if (if_cmp_name_func(a->source.link.ia->iface->name, + b->source.link.ia->iface->name) > 0) + return (1); + return (ldp_addrcmp(a->source.link.ia->af, + &a->source.link.src_addr, &b->source.link.src_addr)); + case HELLO_TARGETED: + return (ldp_addrcmp(a->source.target->af, + &a->source.target->addr, &b->source.target->addr)); + default: + fatalx("adj_compare: unknown hello type"); + } + + return (0); +} + +struct adj * +adj_new(struct in_addr lsr_id, struct hello_source *source, + union ldpd_addr *addr) +{ + struct adj *adj; + + log_debug("%s: lsr-id %pI4, %s", __func__, &lsr_id, + log_hello_src(source)); + + if ((adj = calloc(1, sizeof(*adj))) == NULL) + fatal(__func__); + + adj->lsr_id = lsr_id; + adj->nbr = NULL; + adj->source = *source; + adj->trans_addr = *addr; + + RB_INSERT(global_adj_head, &global.adj_tree, adj); + + switch (source->type) { + case HELLO_LINK: + RB_INSERT(ia_adj_head, &source->link.ia->adj_tree, adj); + break; + case HELLO_TARGETED: + source->target->adj = adj; + break; + } + + return (adj); +} + +void +adj_del(struct adj *adj, uint32_t notif_status) +{ + struct nbr *nbr = adj->nbr; + + log_debug("%s: lsr-id %pI4, %s (%s)", __func__, &adj->lsr_id, + log_hello_src(&adj->source), af_name(adj_get_af(adj))); + + adj_stop_itimer(adj); + + RB_REMOVE(global_adj_head, &global.adj_tree, adj); + if (nbr) + RB_REMOVE(nbr_adj_head, &nbr->adj_tree, adj); + switch (adj->source.type) { + case HELLO_LINK: + RB_REMOVE(ia_adj_head, &adj->source.link.ia->adj_tree, adj); + + if (nbr) + ldp_sync_fsm_adj_event(adj, LDP_SYNC_EVT_ADJ_DEL); + break; + case HELLO_TARGETED: + adj->source.target->adj = NULL; + break; + } + + free(adj); + + /* + * If the neighbor still exists but none of its remaining + * adjacencies (if any) are from the preferred address-family, + * then delete it. + */ + if (nbr && nbr_adj_count(nbr, nbr->af) == 0) { + session_shutdown(nbr, notif_status, 0, 0); + nbr_del(nbr); + } +} + +struct adj * +adj_find(struct in_addr lsr_id, struct hello_source *source) +{ + struct adj adj; + adj.lsr_id = lsr_id; + adj.source = *source; + return (RB_FIND(global_adj_head, &global.adj_tree, &adj)); +} + +int +adj_get_af(const struct adj *adj) +{ + switch (adj->source.type) { + case HELLO_LINK: + return (adj->source.link.ia->af); + case HELLO_TARGETED: + return (adj->source.target->af); + default: + fatalx("adj_get_af: unknown hello type"); + } +} + +/* adjacency timers */ + +/* ARGSUSED */ +static void adj_itimer(struct event *thread) +{ + struct adj *adj = EVENT_ARG(thread); + + adj->inactivity_timer = NULL; + + log_debug("%s: lsr-id %pI4", __func__, &adj->lsr_id); + + if (adj->source.type == HELLO_TARGETED) { + if (!CHECK_FLAG(adj->source.target->flags, F_TNBR_CONFIGURED) && + adj->source.target->pw_count == 0 && + adj->source.target->rlfa_count == 0) { + /* remove dynamic targeted neighbor */ + tnbr_del(leconf, adj->source.target); + return; + } + } + + adj_del(adj, S_HOLDTIME_EXP); +} + +void +adj_start_itimer(struct adj *adj) +{ + EVENT_OFF(adj->inactivity_timer); + adj->inactivity_timer = NULL; + event_add_timer(master, adj_itimer, adj, adj->holdtime, + &adj->inactivity_timer); +} + +void +adj_stop_itimer(struct adj *adj) +{ + EVENT_OFF(adj->inactivity_timer); +} + +/* targeted neighbors */ + +static __inline int +tnbr_compare(const struct tnbr *a, const struct tnbr *b) +{ + if (a->af < b->af) + return (-1); + if (a->af > b->af) + return (1); + + return (ldp_addrcmp(a->af, &a->addr, &b->addr)); +} + +struct tnbr * +tnbr_new(int af, union ldpd_addr *addr) +{ + struct tnbr *tnbr; + + if ((tnbr = calloc(1, sizeof(*tnbr))) == NULL) + fatal(__func__); + + tnbr->af = af; + tnbr->addr = *addr; + tnbr->state = TNBR_STA_DOWN; + + return (tnbr); +} + +static void +tnbr_del(struct ldpd_conf *xconf, struct tnbr *tnbr) +{ + tnbr_stop(tnbr); + RB_REMOVE(tnbr_head, &xconf->tnbr_tree, tnbr); + free(tnbr); +} + +struct tnbr * +tnbr_find(struct ldpd_conf *xconf, int af, union ldpd_addr *addr) +{ + struct tnbr tnbr; + tnbr.af = af; + tnbr.addr = *addr; + return (RB_FIND(tnbr_head, &xconf->tnbr_tree, &tnbr)); +} + +struct tnbr * +tnbr_check(struct ldpd_conf *xconf, struct tnbr *tnbr) +{ + if (!CHECK_FLAG(tnbr->flags, (F_TNBR_CONFIGURED|F_TNBR_DYNAMIC)) && + tnbr->pw_count == 0 && tnbr->rlfa_count == 0) { + tnbr_del(xconf, tnbr); + return (NULL); + } + + return (tnbr); +} + +static void +tnbr_start(struct tnbr *tnbr) +{ + send_hello(HELLO_TARGETED, NULL, tnbr); + tnbr_start_hello_timer(tnbr); + tnbr->state = TNBR_STA_ACTIVE; +} + +static void +tnbr_stop(struct tnbr *tnbr) +{ + tnbr_stop_hello_timer(tnbr); + if (tnbr->adj) + adj_del(tnbr->adj, S_SHUTDOWN); + tnbr->state = TNBR_STA_DOWN; +} + +void +tnbr_update(struct tnbr *tnbr) +{ + int socket_ok, rtr_id_ok; + + if ((ldp_af_global_get(&global, tnbr->af))->ldp_edisc_socket != -1) + socket_ok = 1; + else + socket_ok = 0; + + if (ldp_rtr_id_get(leconf) != INADDR_ANY) + rtr_id_ok = 1; + else + rtr_id_ok = 0; + + if (tnbr->state == TNBR_STA_DOWN) { + if (!socket_ok || !rtr_id_ok) + return; + + tnbr_start(tnbr); + } else if (tnbr->state == TNBR_STA_ACTIVE) { + if (socket_ok && rtr_id_ok) + return; + + tnbr_stop(tnbr); + } +} + +void +tnbr_update_all(int af) +{ + struct tnbr *tnbr; + + /* update targeted neighbors */ + RB_FOREACH(tnbr, tnbr_head, &leconf->tnbr_tree) + if (tnbr->af == af || af == AF_UNSPEC) + tnbr_update(tnbr); +} + +uint16_t +tnbr_get_hello_holdtime(struct tnbr *tnbr) +{ + if ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime != 0) + return ((ldp_af_conf_get(leconf, tnbr->af))->thello_holdtime); + + return (leconf->thello_holdtime); +} + +uint16_t +tnbr_get_hello_interval(struct tnbr *tnbr) +{ + if ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval != 0) + return ((ldp_af_conf_get(leconf, tnbr->af))->thello_interval); + + return (leconf->thello_interval); +} + +/* target neighbors timers */ + +/* ARGSUSED */ +static void tnbr_hello_timer(struct event *thread) +{ + struct tnbr *tnbr = EVENT_ARG(thread); + + tnbr->hello_timer = NULL; + send_hello(HELLO_TARGETED, NULL, tnbr); + tnbr_start_hello_timer(tnbr); +} + +static void +tnbr_start_hello_timer(struct tnbr *tnbr) +{ + EVENT_OFF(tnbr->hello_timer); + tnbr->hello_timer = NULL; + event_add_timer(master, tnbr_hello_timer, tnbr, + tnbr_get_hello_interval(tnbr), &tnbr->hello_timer); +} + +static void +tnbr_stop_hello_timer(struct tnbr *tnbr) +{ + EVENT_OFF(tnbr->hello_timer); +} + +struct ctl_adj * +adj_to_ctl(struct adj *adj) +{ + static struct ctl_adj actl; + + actl.af = adj_get_af(adj); + actl.id = adj->lsr_id; + actl.type = adj->source.type; + switch (adj->source.type) { + case HELLO_LINK: + memcpy(actl.ifname, adj->source.link.ia->iface->name, + sizeof(actl.ifname)); + actl.src_addr = adj->source.link.src_addr; + break; + case HELLO_TARGETED: + actl.src_addr = adj->source.target->addr; + break; + } + actl.holdtime = adj->holdtime; + actl.holdtime_remaining = + event_timer_remain_second(adj->inactivity_timer); + actl.trans_addr = adj->trans_addr; + actl.ds_tlv = adj->ds_tlv; + + return (&actl); +} diff --git a/ldpd/control.c b/ldpd/control.c new file mode 100644 index 0000000..a08ce4c --- /dev/null +++ b/ldpd/control.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include +#include +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "control.h" + +#define CONTROL_BACKLOG 5 + +static void control_accept(struct event *); +static struct ctl_conn *control_connbyfd(int); +static struct ctl_conn *control_connbypid(pid_t); +static void control_close(int); +static void control_dispatch_imsg(struct event *); + +struct ctl_conns ctl_conns; + +static int control_fd; + +int +control_init(char *path) +{ + struct sockaddr_un s_un; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + sock_set_nonblock(fd); + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + strlcpy(s_un.sun_path, path, sizeof(s_un.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + log_warn("%s: bind: %s", __func__, path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(path); + return (-1); + } + + control_fd = fd; + + return (0); +} + +int +control_listen(void) +{ + if (listen(control_fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + return (accept_add(control_fd, control_accept, NULL)); +} + +void +control_cleanup(char *path) +{ + accept_del(control_fd); + close(control_fd); + unlink(path); +} + +/* ARGSUSED */ +static void control_accept(struct event *thread) +{ + int connfd; + socklen_t len; + struct sockaddr_un s_un; + struct ctl_conn *c; + + len = sizeof(s_un); + if ((connfd = accept(EVENT_FD(thread), (struct sockaddr *)&s_un, + &len)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) + accept_pause(); + else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) + log_warn("%s: accept", __func__); + return; + } + sock_set_nonblock(connfd); + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn(__func__); + close(connfd); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler_read = control_dispatch_imsg; + c->iev.ev_read = NULL; + event_add_read(master, c->iev.handler_read, &c->iev, c->iev.ibuf.fd, + &c->iev.ev_read); + c->iev.handler_write = ldp_write_handler; + c->iev.ev_write = NULL; + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +static struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +static struct ctl_conn * +control_connbypid(pid_t pid) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.pid == pid) + break; + } + + return (c); +} + +static void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + EVENT_OFF(c->iev.ev_read); + EVENT_OFF(c->iev.ev_write); + close(c->iev.ibuf.fd); + accept_unpause(); + free(c); +} + +/* ARGSUSED */ +static void control_dispatch_imsg(struct event *thread) +{ + int fd = EVENT_FD(thread); + struct ctl_conn *c; + struct imsg imsg; + ssize_t n; + unsigned int ifidx; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warnx("%s: fd %d: not found", __func__, fd); + return; + } + + c->iev.ev_read = NULL; + + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || n == 0) { + control_close(fd); + return; + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CTL_FIB_COUPLE: + case IMSG_CTL_FIB_DECOUPLE: + case IMSG_CTL_RELOAD: + case IMSG_CTL_KROUTE: + case IMSG_CTL_KROUTE_ADDR: + case IMSG_CTL_IFINFO: + /* ignore */ + break; + case IMSG_CTL_SHOW_INTERFACE: + if (imsg.hdr.len == IMSG_HEADER_SIZE + sizeof(ifidx)) { + memcpy(&ifidx, imsg.data, sizeof(ifidx)); + ldpe_iface_ctl(c, ifidx); + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); + } + break; + case IMSG_CTL_SHOW_DISCOVERY: + ldpe_adj_ctl(c); + break; + case IMSG_CTL_SHOW_DISCOVERY_DTL: + ldpe_adj_detail_ctl(c); + break; + case IMSG_CTL_SHOW_LIB: + case IMSG_CTL_SHOW_L2VPN_PW: + case IMSG_CTL_SHOW_L2VPN_BINDING: + c->iev.ibuf.pid = imsg.hdr.pid; + ldpe_imsg_compose_lde(imsg.hdr.type, 0, imsg.hdr.pid, + imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE); + break; + case IMSG_CTL_SHOW_NBR: + ldpe_nbr_ctl(c); + break; + case IMSG_CTL_CLEAR_NBR: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ctl_nbr)) + break; + + nbr_clear_ctl(imsg.data); + break; + case IMSG_CTL_SHOW_LDP_SYNC: + ldpe_ldp_sync_ctl(c); + break; + case IMSG_CTL_LOG_VERBOSE: + /* ignore */ + break; + default: + log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbypid(imsg->hdr.pid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); +} diff --git a/ldpd/control.h b/ldpd/control.h new file mode 100644 index 0000000..f45c97e --- /dev/null +++ b/ldpd/control.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#ifndef _CONTROL_H_ +#define _CONTROL_H_ + +#include "queue.h" + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; +}; +TAILQ_HEAD(ctl_conns, ctl_conn); + +extern struct ctl_conns ctl_conns; + +int control_init(char *); +int control_listen(void); +void control_cleanup(char *); +int control_imsg_relay(struct imsg *); + +#endif /* _CONTROL_H_ */ diff --git a/ldpd/hello.c b/ldpd/hello.c new file mode 100644 index 0000000..0b07f24 --- /dev/null +++ b/ldpd/hello.c @@ -0,0 +1,586 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" + +static int gen_hello_prms_tlv(struct ibuf *buf, uint16_t, uint16_t); +static int gen_opt4_hello_prms_tlv(struct ibuf *, uint16_t, uint32_t); +static int gen_opt16_hello_prms_tlv(struct ibuf *, uint16_t, uint8_t *); +static int gen_ds_hello_prms_tlv(struct ibuf *, uint32_t); +static int tlv_decode_hello_prms(char *, uint16_t, uint16_t *, uint16_t *); +static int tlv_decode_opt_hello_prms(char *, uint16_t, int *, int, + union ldpd_addr *, uint32_t *, uint16_t *); + +int +send_hello(enum hello_type type, struct iface_af *ia, struct tnbr *tnbr) +{ + int af; + union ldpd_addr dst; + uint16_t size, holdtime = 0, flags = 0; + int fd = 0; + struct ibuf *buf; + int err = 0; + + switch (type) { + case HELLO_LINK: + af = ia->af; + holdtime = if_get_hello_holdtime(ia); + flags = 0; + fd = (ldp_af_global_get(&global, af))->ldp_disc_socket; + + /* multicast destination address */ + switch (af) { + case AF_INET: + if (!CHECK_FLAG(leconf->ipv4.flags, F_LDPD_AF_NO_GTSM)) + SET_FLAG(flags, F_HELLO_GTSM); + dst.v4 = global.mcast_addr_v4; + break; + case AF_INET6: + dst.v6 = global.mcast_addr_v6; + break; + default: + fatalx("send_hello: unknown af"); + } + break; + case HELLO_TARGETED: + af = tnbr->af; + holdtime = tnbr_get_hello_holdtime(tnbr); + flags = F_HELLO_TARGETED; + if (CHECK_FLAG(tnbr->flags, F_TNBR_CONFIGURED) || + tnbr->pw_count || + tnbr->rlfa_count) + flags |= F_HELLO_REQ_TARG; + + fd = (ldp_af_global_get(&global, af))->ldp_edisc_socket; + + /* unicast destination address */ + dst = tnbr->addr; + break; + default: + fatalx("send_hello: unknown hello type"); + } + + /* calculate message size */ + size = LDP_HDR_SIZE + LDP_MSG_SIZE + sizeof(struct hello_prms_tlv); + switch (af) { + case AF_INET: + size += sizeof(struct hello_prms_opt4_tlv); + break; + case AF_INET6: + size += sizeof(struct hello_prms_opt16_tlv); + break; + default: + fatalx("send_hello: unknown af"); + } + size += sizeof(struct hello_prms_opt4_tlv); + if (ldp_is_dual_stack(leconf)) + size += sizeof(struct hello_prms_opt4_tlv); + + /* generate message */ + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + SET_FLAG(err, gen_ldp_hdr(buf, size)); + size -= LDP_HDR_SIZE; + SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_HELLO, size)); + SET_FLAG(err, gen_hello_prms_tlv(buf, holdtime, flags)); + + /* + * RFC 7552 - Section 6.1: + * "An LSR MUST include only the transport address whose address + * family is the same as that of the IP packet carrying the Hello + * message". + */ + switch (af) { + case AF_INET: + SET_FLAG(err, gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR, + leconf->ipv4.trans_addr.v4.s_addr)); + break; + case AF_INET6: + SET_FLAG(err, gen_opt16_hello_prms_tlv(buf, TLV_TYPE_IPV6TRANSADDR, + leconf->ipv6.trans_addr.v6.s6_addr)); + break; + default: + fatalx("send_hello: unknown af"); + } + + SET_FLAG(err, gen_opt4_hello_prms_tlv(buf, TLV_TYPE_CONFIG, + htonl(global.conf_seqnum))); + + /* + * RFC 7552 - Section 6.1.1: + * "A Dual-stack LSR (i.e., an LSR supporting Dual-stack LDP for a peer) + * MUST include the Dual-Stack capability TLV in all of its LDP Hellos". + */ + if (ldp_is_dual_stack(leconf)) + SET_FLAG(err, gen_ds_hello_prms_tlv(buf, leconf->trans_pref)); + + if (err) { + ibuf_free(buf); + return (-1); + } + + switch (type) { + case HELLO_LINK: + debug_hello_send("iface %s (%s) holdtime %u", ia->iface->name, + af_name(ia->af), holdtime); + break; + case HELLO_TARGETED: + debug_hello_send("targeted-neighbor %s (%s) holdtime %u", + log_addr(tnbr->af, &tnbr->addr), af_name(tnbr->af), + holdtime); + break; + default: + fatalx("send_hello: unknown hello type"); + } + + send_packet(fd, af, &dst, ia, buf->buf, buf->wpos); + ibuf_free(buf); + + return (0); +} + +void +recv_hello(struct in_addr lsr_id, struct ldp_msg *msg, int af, + union ldpd_addr *src, struct iface *iface, int multicast, char *buf, + uint16_t len) +{ + struct adj *adj = NULL; + struct nbr *nbr, *nbrt; + uint16_t holdtime = 0, flags = 0; + int tlvs_rcvd; + int ds_tlv; + union ldpd_addr trans_addr; + ifindex_t scope_id = 0; + uint32_t conf_seqnum; + uint16_t trans_pref; + int r; + struct hello_source source; + struct iface_af *ia = NULL; + struct tnbr *tnbr = NULL; + + r = tlv_decode_hello_prms(buf, len, &holdtime, &flags); + if (r == -1) { + log_debug("%s: lsr-id %pI4: failed to decode params", __func__, &lsr_id); + return; + } + /* safety checks */ + if (holdtime != 0 && holdtime < MIN_HOLDTIME) { + log_debug("%s: lsr-id %pI4: invalid hello holdtime (%u)", + __func__, &lsr_id, holdtime); + return; + } + if (multicast && CHECK_FLAG(flags, F_HELLO_TARGETED)) { + log_debug("%s: lsr-id %pI4: multicast targeted hello", __func__, &lsr_id); + return; + } + if (!multicast && !CHECK_FLAG(flags, F_HELLO_TARGETED)) { + log_debug("%s: lsr-id %pI4: unicast link hello", __func__, &lsr_id); + return; + } + buf += r; + len -= r; + + r = tlv_decode_opt_hello_prms(buf, len, &tlvs_rcvd, af, &trans_addr, + &conf_seqnum, &trans_pref); + if (r == -1) { + log_debug("%s: lsr-id %pI4: failed to decode optional params", + __func__, &lsr_id); + return; + } + if (r != len) { + log_debug("%s: lsr-id %pI4: unexpected data in message", + __func__, &lsr_id); + return; + } + ds_tlv = CHECK_FLAG(tlvs_rcvd, F_HELLO_TLV_RCVD_DS) ? 1 : 0; + + /* implicit transport address */ + if (!CHECK_FLAG(tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR)) + trans_addr = *src; + if (bad_addr(af, &trans_addr)) { + log_debug("%s: lsr-id %pI4: invalid transport address %s", + __func__, &lsr_id, log_addr(af, &trans_addr)); + return; + } + if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&trans_addr.v6)) { + /* + * RFC 7552 - Section 6.1: + * "An LSR MUST use a global unicast IPv6 address in an IPv6 + * Transport Address optional object of outgoing targeted + * Hellos and check for the same in incoming targeted Hellos + * (i.e., MUST discard the targeted Hello if it failed the + * check)". + */ + if (CHECK_FLAG(flags, F_HELLO_TARGETED)) { + log_debug("%s: lsr-id %pI4: invalid targeted hello transport address %s", __func__, &lsr_id, + log_addr(af, &trans_addr)); + return; + } + scope_id = iface->ifindex; + } + + memset(&source, 0, sizeof(source)); + if (CHECK_FLAG(flags, F_HELLO_TARGETED)) { + /* + * RFC 7552 - Section 5.2: + * "The link-local IPv6 addresses MUST NOT be used as the + * targeted LDP Hello packet's source or destination addresses". + */ + if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&src->v6)) { + log_debug("%s: lsr-id %pI4: targeted hello with link-local source address", __func__, + &lsr_id); + return; + } + + tnbr = tnbr_find(leconf, af, src); + + /* remove the dynamic tnbr if the 'R' bit was cleared */ + if (tnbr && + CHECK_FLAG(tnbr->flags, F_TNBR_DYNAMIC) && + !CHECK_FLAG(flags, F_HELLO_REQ_TARG)) { + UNSET_FLAG(tnbr->flags, F_TNBR_DYNAMIC); + tnbr = tnbr_check(leconf, tnbr); + } + + if (!tnbr) { + struct ldpd_af_conf *af_conf; + + if (!CHECK_FLAG(flags, F_HELLO_REQ_TARG)) + return; + af_conf = ldp_af_conf_get(leconf, af); + if (!CHECK_FLAG(af_conf->flags, F_LDPD_AF_THELLO_ACCEPT)) + return; + if (ldpe_acl_check(af_conf->acl_thello_accept_from, af, + src, (af == AF_INET) ? 32 : 128) != FILTER_PERMIT) + return; + + tnbr = tnbr_new(af, src); + SET_FLAG(tnbr->flags, F_TNBR_DYNAMIC); + tnbr_update(tnbr); + RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr); + } + + source.type = HELLO_TARGETED; + source.target = tnbr; + } else { + ia = iface_af_get(iface, af); + source.type = HELLO_LINK; + source.link.ia = ia; + source.link.src_addr = *src; + } + + debug_hello_recv("%s lsr-id %pI4 transport-address %s holdtime %u%s", + log_hello_src(&source), &lsr_id, log_addr(af, &trans_addr), + holdtime, (ds_tlv) ? " (dual stack TLV present)" : ""); + + adj = adj_find(lsr_id, &source); + if (adj && adj->ds_tlv != ds_tlv) { + /* + * Transient condition, ignore packet and wait until adjacency + * times out. + */ + return; + } + nbr = nbr_find_ldpid(lsr_id.s_addr); + + /* check dual-stack tlv */ + if (ds_tlv && trans_pref != leconf->trans_pref) { + /* + * RFC 7552 - Section 6.1.1: + * "If the Dual-Stack capability TLV is present and the remote + * preference does not match the local preference (or does not + * get recognized), then the LSR MUST discard the Hello message + * and log an error. + * If an LDP session was already in place, then the LSR MUST + * send a fatal Notification message with status code of + * 'Transport Connection Mismatch' and reset the session". + */ + log_debug("%s: lsr-id %pI4: remote transport preference does not match the local preference", __func__, &lsr_id); + if (nbr) + session_shutdown(nbr, S_TRANS_MISMTCH, msg->id, msg->type); + if (adj) + adj_del(adj, S_SHUTDOWN); + return; + } + + /* + * Check for noncompliant dual-stack neighbor according to + * RFC 7552 section 6.1.1. + */ + if (nbr && !ds_tlv) { + switch (af) { + case AF_INET: + if (nbr_adj_count(nbr, AF_INET6) > 0) { + session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type); + return; + } + break; + case AF_INET6: + if (nbr_adj_count(nbr, AF_INET) > 0) { + session_shutdown(nbr, S_DS_NONCMPLNCE, msg->id, msg->type); + return; + } + break; + default: + fatalx("recv_hello: unknown af"); + } + } + + /* + * Protections against misconfigured networks and buggy implementations. + */ + if (nbr && nbr->af == af && + (ldp_addrcmp(af, &nbr->raddr, &trans_addr) || + nbr->raddr_scope != scope_id)) { + log_warnx("%s: lsr-id %pI4: hello packet advertising a different transport address", __func__, &lsr_id); + if (adj) + adj_del(adj, S_SHUTDOWN); + return; + } + if (nbr == NULL) { + nbrt = nbr_find_addr(af, &trans_addr); + if (nbrt) { + log_debug("%s: transport address %s is already being used by lsr-id %pI4", __func__, log_addr(af, + &trans_addr), &nbrt->id); + if (adj) + adj_del(adj, S_SHUTDOWN); + return; + } + } + + if (adj == NULL) { + adj = adj_new(lsr_id, &source, &trans_addr); + if (nbr) { + adj->nbr = nbr; + RB_INSERT(nbr_adj_head, &nbr->adj_tree, adj); + } + ldp_sync_fsm_adj_event(adj, LDP_SYNC_EVT_ADJ_NEW); + } + adj->ds_tlv = ds_tlv; + + /* + * If the hello adjacency's address-family doesn't match the local + * preference, then an adjacency is still created but we don't attempt + * to start an LDP session. + */ + if (nbr == NULL && (!ds_tlv || + ((trans_pref == DUAL_STACK_LDPOV4 && af == AF_INET) || + (trans_pref == DUAL_STACK_LDPOV6 && af == AF_INET6)))) + nbr = nbr_new(lsr_id, af, ds_tlv, &trans_addr, scope_id); + + /* dynamic LDPv4 GTSM negotiation as per RFC 6720 */ + if (nbr) { + if (CHECK_FLAG(flags, F_HELLO_GTSM)) + SET_FLAG(nbr->flags, F_NBR_GTSM_NEGOTIATED); + else + UNSET_FLAG(nbr->flags, F_NBR_GTSM_NEGOTIATED); + } + + /* update neighbor's configuration sequence number */ + if (nbr && (tlvs_rcvd & F_HELLO_TLV_RCVD_CONF)) { + if (conf_seqnum > nbr->conf_seqnum && nbr_pending_idtimer(nbr)) + nbr_stop_idtimer(nbr); + nbr->conf_seqnum = conf_seqnum; + } + + /* always update the holdtime to properly handle runtime changes */ + switch (source.type) { + case HELLO_LINK: + if (holdtime == 0) + holdtime = LINK_DFLT_HOLDTIME; + + adj->holdtime = MIN(if_get_hello_holdtime(ia), holdtime); + break; + case HELLO_TARGETED: + if (holdtime == 0) + holdtime = TARGETED_DFLT_HOLDTIME; + + adj->holdtime = MIN(tnbr_get_hello_holdtime(tnbr), holdtime); + } + if (adj->holdtime != INFINITE_HOLDTIME) + adj_start_itimer(adj); + else + adj_stop_itimer(adj); + + if (nbr && nbr->state == NBR_STA_PRESENT && !nbr_pending_idtimer(nbr) && + nbr_session_active_role(nbr) && !nbr_pending_connect(nbr)) + nbr_establish_connection(nbr); +} + +static int +gen_hello_prms_tlv(struct ibuf *buf, uint16_t holdtime, uint16_t flags) +{ + struct hello_prms_tlv parms; + + memset(&parms, 0, sizeof(parms)); + parms.type = htons(TLV_TYPE_COMMONHELLO); + parms.length = htons(sizeof(parms.holdtime) + sizeof(parms.flags)); + parms.holdtime = htons(holdtime); + parms.flags = htons(flags); + + return (ibuf_add(buf, &parms, sizeof(parms))); +} + +static int +gen_opt4_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint32_t value) +{ + struct hello_prms_opt4_tlv parms; + + memset(&parms, 0, sizeof(parms)); + parms.type = htons(type); + parms.length = htons(sizeof(parms.value)); + parms.value = value; + + return (ibuf_add(buf, &parms, sizeof(parms))); +} + +static int +gen_opt16_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint8_t *value) +{ + struct hello_prms_opt16_tlv parms; + + memset(&parms, 0, sizeof(parms)); + parms.type = htons(type); + parms.length = htons(sizeof(parms.value)); + memcpy(&parms.value, value, sizeof(parms.value)); + + return (ibuf_add(buf, &parms, sizeof(parms))); +} + +static int +gen_ds_hello_prms_tlv(struct ibuf *buf, uint32_t value) +{ + if (CHECK_FLAG(leconf->flags, F_LDPD_DS_CISCO_INTEROP)) + value = htonl(value); + else + value = htonl(value << 28); + + return (gen_opt4_hello_prms_tlv(buf, TLV_TYPE_DUALSTACK, value)); +} + +static int +tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime, + uint16_t *flags) +{ + struct hello_prms_tlv tlv; + + if (len < sizeof(tlv)) + return (-1); + memcpy(&tlv, buf, sizeof(tlv)); + + if (tlv.type != htons(TLV_TYPE_COMMONHELLO)) + return (-1); + if (ntohs(tlv.length) != sizeof(tlv) - TLV_HDR_SIZE) + return (-1); + + *holdtime = ntohs(tlv.holdtime); + *flags = ntohs(tlv.flags); + + return (sizeof(tlv)); +} + +static int +tlv_decode_opt_hello_prms(char *buf, uint16_t len, int *tlvs_rcvd, int af, + union ldpd_addr *addr, uint32_t *conf_number, uint16_t *trans_pref) +{ + struct tlv tlv; + uint16_t tlv_len; + int total = 0; + + *tlvs_rcvd = 0; + memset(addr, 0, sizeof(*addr)); + *conf_number = 0; + *trans_pref = 0; + + /* + * RFC 7552 - Section 6.1: + * "An LSR SHOULD accept the Hello message that contains both IPv4 and + * IPv6 Transport Address optional objects but MUST use only the + * transport address whose address family is the same as that of the + * IP packet carrying the Hello message. An LSR SHOULD accept only + * the first Transport Address optional object for a given address + * family in the received Hello message and ignore the rest if the + * LSR receives more than one Transport Address optional object for a + * given address family". + */ + while (len >= sizeof(tlv)) { + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) + return (-1); + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + total += TLV_HDR_SIZE; + + switch (ntohs(tlv.type)) { + case TLV_TYPE_IPV4TRANSADDR: + if (tlv_len != sizeof(addr->v4)) + return (-1); + if (af != AF_INET) + return (-1); + if (CHECK_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR)) + break; + memcpy(&addr->v4, buf, sizeof(addr->v4)); + SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR); + break; + case TLV_TYPE_IPV6TRANSADDR: + if (tlv_len != sizeof(addr->v6)) + return (-1); + if (af != AF_INET6) + return (-1); + if (CHECK_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR)) + break; + memcpy(&addr->v6, buf, sizeof(addr->v6)); + SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_ADDR); + break; + case TLV_TYPE_CONFIG: + if (tlv_len != sizeof(uint32_t)) + return (-1); + memcpy(conf_number, buf, sizeof(uint32_t)); + SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_CONF); + break; + case TLV_TYPE_DUALSTACK: + if (tlv_len != sizeof(uint32_t)) + return (-1); + /* + * RFC 7552 - Section 6.1: + * "A Single-stack LSR does not need to use the + * Dual-Stack capability in Hello messages and SHOULD + * ignore this capability if received". + */ + if (!ldp_is_dual_stack(leconf)) + break; + /* Shame on you, Cisco! */ + if (CHECK_FLAG(leconf->flags, F_LDPD_DS_CISCO_INTEROP)) { + memcpy(trans_pref, buf + sizeof(uint16_t), sizeof(uint16_t)); + *trans_pref = ntohs(*trans_pref); + } else { + memcpy(trans_pref, buf , sizeof(uint16_t)); + *trans_pref = ntohs(*trans_pref) >> 12; + } + SET_FLAG(*tlvs_rcvd, F_HELLO_TLV_RCVD_DS); + break; + default: + /* if unknown flag set, ignore TLV */ + if (!CHECK_FLAG(ntohs(tlv.type), UNKNOWN_FLAG)) + return (-1); + break; + } + buf += tlv_len; + len -= tlv_len; + total += tlv_len; + } + + return (total); +} diff --git a/ldpd/init.c b/ldpd/init.c new file mode 100644 index 0000000..ef78247 --- /dev/null +++ b/ldpd/init.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" + +static int gen_init_prms_tlv(struct ibuf *, struct nbr *); +static int gen_cap_dynamic_tlv(struct ibuf *); +static int gen_cap_twcard_tlv(struct ibuf *, int); +static int gen_cap_unotif_tlv(struct ibuf *, int); + +void +send_init(struct nbr *nbr) +{ + struct ibuf *buf; + uint16_t size; + int err = 0; + + debug_msg_send("initialization: lsr-id %pI4", &nbr->id); + + size = LDP_HDR_SIZE + LDP_MSG_SIZE + SESS_PRMS_SIZE + + CAP_TLV_DYNAMIC_SIZE + CAP_TLV_TWCARD_SIZE + CAP_TLV_UNOTIF_SIZE; + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + SET_FLAG(err, gen_ldp_hdr(buf, size)); + size -= LDP_HDR_SIZE; + SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_INIT, size)); + SET_FLAG(err, gen_init_prms_tlv(buf, nbr)); + SET_FLAG(err, gen_cap_dynamic_tlv(buf)); + SET_FLAG(err, gen_cap_twcard_tlv(buf, 1)); + SET_FLAG(err, gen_cap_unotif_tlv(buf, 1)); + if (err) { + ibuf_free(buf); + return; + } + + evbuf_enqueue(&nbr->tcp->wbuf, buf); +} + +int +recv_init(struct nbr *nbr, char *buf, uint16_t len) +{ + struct ldp_msg msg; + struct sess_prms_tlv sess; + uint16_t max_pdu_len; + int caps_rcvd = 0; + + debug_msg_recv("initialization: lsr-id %pI4", &nbr->id); + + memcpy(&msg, buf, sizeof(msg)); + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + if (len < SESS_PRMS_SIZE) { + session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); + return (-1); + } + memcpy(&sess, buf, sizeof(sess)); + if (ntohs(sess.length) != SESS_PRMS_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + if (ntohs(sess.proto_version) != LDP_VERSION) { + session_shutdown(nbr, S_BAD_PROTO_VER, msg.id, msg.type); + return (-1); + } + if (ntohs(sess.keepalive_time) < MIN_KEEPALIVE) { + session_shutdown(nbr, S_KEEPALIVE_BAD, msg.id, msg.type); + return (-1); + } + if (sess.lsr_id != ldp_rtr_id_get(leconf) || + ntohs(sess.lspace_id) != 0) { + session_shutdown(nbr, S_NO_HELLO, msg.id, msg.type); + return (-1); + } + + buf += SESS_PRMS_SIZE; + len -= SESS_PRMS_SIZE; + + /* Optional Parameters */ + while (len > 0) { + struct tlv tlv; + uint16_t tlv_type; + uint16_t tlv_len; + + if (len < sizeof(tlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_type = ntohs(tlv.type); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + + /* + * RFC 5561 - Section 6: + * "The S-bit of a Capability Parameter in an Initialization + * message MUST be 1 and SHOULD be ignored on receipt". + */ + switch (tlv_type) { + case TLV_TYPE_ATMSESSIONPAR: + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + case TLV_TYPE_FRSESSION: + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + case TLV_TYPE_DYNAMIC_CAP: + if (tlv_len != CAP_TLV_DYNAMIC_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + if (CHECK_FLAG(caps_rcvd, F_CAP_TLV_RCVD_DYNAMIC)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + SET_FLAG(caps_rcvd, F_CAP_TLV_RCVD_DYNAMIC); + + SET_FLAG(nbr->flags, F_NBR_CAP_DYNAMIC); + + log_debug("%s: lsr-id %pI4 announced the Dynamic Capability Announcement capability", __func__, + &nbr->id); + break; + case TLV_TYPE_TWCARD_CAP: + if (tlv_len != CAP_TLV_TWCARD_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + if (CHECK_FLAG(caps_rcvd, F_CAP_TLV_RCVD_TWCARD)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + SET_FLAG(caps_rcvd, F_CAP_TLV_RCVD_TWCARD); + + SET_FLAG(nbr->flags, F_NBR_CAP_TWCARD); + + log_debug("%s: lsr-id %pI4 announced the Typed Wildcard FEC capability", __func__, &nbr->id); + break; + case TLV_TYPE_UNOTIF_CAP: + if (tlv_len != CAP_TLV_UNOTIF_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + if (CHECK_FLAG(caps_rcvd, F_CAP_TLV_RCVD_UNOTIF)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + SET_FLAG(caps_rcvd, F_CAP_TLV_RCVD_UNOTIF); + + SET_FLAG(nbr->flags, F_NBR_CAP_UNOTIF); + + log_debug("%s: lsr-id %pI4 announced the Unrecognized Notification capability", __func__, + &nbr->id); + break; + default: + if (!CHECK_FLAG(ntohs(tlv.type), UNKNOWN_FLAG)) + send_notification_rtlvs(nbr, S_UNSSUPORTDCAP, + msg.id, msg.type, tlv_type, tlv_len, buf); + /* ignore unknown tlv */ + break; + } + buf += tlv_len; + len -= tlv_len; + } + + nbr->keepalive = MIN(nbr_get_keepalive(nbr->af, nbr->id), + ntohs(sess.keepalive_time)); + + max_pdu_len = ntohs(sess.max_pdu_len); + /* + * RFC 5036 - Section 3.5.3: + * "A value of 255 or less specifies the default maximum length of + * 4096 octets". + */ + if (max_pdu_len <= 255) + max_pdu_len = LDP_MAX_LEN; + nbr->max_pdu_len = MIN(max_pdu_len, LDP_MAX_LEN); + + nbr_fsm(nbr, NBR_EVT_INIT_RCVD); + + return (0); +} + +void +send_capability(struct nbr *nbr, uint16_t capability, int enable) +{ + struct ibuf *buf; + uint16_t size; + int err = 0; + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + size = LDP_HDR_SIZE + LDP_MSG_SIZE + CAP_TLV_DYNAMIC_SIZE; + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + SET_FLAG(err, gen_ldp_hdr(buf, size)); + size -= LDP_HDR_SIZE; + SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_CAPABILITY, size)); + + switch (capability) { + case TLV_TYPE_TWCARD_CAP: + SET_FLAG(err, gen_cap_twcard_tlv(buf, enable)); + break; + case TLV_TYPE_UNOTIF_CAP: + SET_FLAG(err, gen_cap_unotif_tlv(buf, enable)); + break; + case TLV_TYPE_DYNAMIC_CAP: + /* + * RFC 5561 - Section 9: + * "An LDP speaker MUST NOT include the Dynamic Capability + * Announcement Parameter in Capability messages sent to + * its peers". + */ + fatalx("send_capability: An LDP speaker MUST NOT include the Dynamic Capability Announcement Parameter"); + break; + default: + fatalx("send_capability: unsupported capability"); + break; + } + + if (err) { + ibuf_free(buf); + return; + } + + evbuf_enqueue(&nbr->tcp->wbuf, buf); + nbr_fsm(nbr, NBR_EVT_PDU_SENT); + nbr->stats.capability_sent++; +} + +int +recv_capability(struct nbr *nbr, char *buf, uint16_t len) +{ + struct ldp_msg msg; + int enable = 0; + int caps_rcvd = 0; + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + memcpy(&msg, buf, sizeof(msg)); + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + /* Optional Parameters */ + while (len > 0) { + struct tlv tlv; + uint16_t tlv_type; + uint16_t tlv_len; + uint8_t reserved; + + if (len < sizeof(tlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_type = ntohs(tlv.type); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + + switch (tlv_type) { + case TLV_TYPE_TWCARD_CAP: + if (tlv_len != CAP_TLV_TWCARD_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + if (CHECK_FLAG(caps_rcvd, F_CAP_TLV_RCVD_TWCARD)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + SET_FLAG(caps_rcvd, F_CAP_TLV_RCVD_TWCARD); + + memcpy(&reserved, buf, sizeof(reserved)); + enable = reserved & STATE_BIT; + if (enable) + SET_FLAG(nbr->flags, F_NBR_CAP_TWCARD); + else + UNSET_FLAG(nbr->flags, F_NBR_CAP_TWCARD); + + log_debug("%s: lsr-id %pI4 %s the Typed Wildcard FEC capability", __func__, &nbr->id, + (enable) ? "announced" : "withdrew"); + break; + case TLV_TYPE_UNOTIF_CAP: + if (tlv_len != CAP_TLV_UNOTIF_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + if (CHECK_FLAG(caps_rcvd, F_CAP_TLV_RCVD_UNOTIF)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + SET_FLAG(caps_rcvd, F_CAP_TLV_RCVD_UNOTIF); + + memcpy(&reserved, buf, sizeof(reserved)); + enable = reserved & STATE_BIT; + if (enable) + SET_FLAG(nbr->flags, F_NBR_CAP_UNOTIF); + else + UNSET_FLAG(nbr->flags, F_NBR_CAP_UNOTIF); + + log_debug("%s: lsr-id %pI4 %s the Unrecognized Notification capability", __func__, + &nbr->id, (enable) ? "announced" : "withdrew"); + break; + case TLV_TYPE_DYNAMIC_CAP: + /* + * RFC 5561 - Section 9: + * "An LDP speaker that receives a Capability message + * from a peer that includes the Dynamic Capability + * Announcement Parameter SHOULD silently ignore the + * parameter and process any other Capability Parameters + * in the message". + */ + fallthrough; + default: + if (!CHECK_FLAG(ntohs(tlv.type), UNKNOWN_FLAG)) + send_notification_rtlvs(nbr, S_UNSSUPORTDCAP, + msg.id, msg.type, tlv_type, tlv_len, buf); + /* ignore unknown tlv */ + break; + } + buf += tlv_len; + len -= tlv_len; + } + + nbr_fsm(nbr, NBR_EVT_PDU_RCVD); + + return (0); +} + +static int +gen_init_prms_tlv(struct ibuf *buf, struct nbr *nbr) +{ + struct sess_prms_tlv parms; + + memset(&parms, 0, sizeof(parms)); + parms.type = htons(TLV_TYPE_COMMONSESSION); + parms.length = htons(SESS_PRMS_LEN); + parms.proto_version = htons(LDP_VERSION); + parms.keepalive_time = htons(nbr_get_keepalive(nbr->af, nbr->id)); + parms.reserved = 0; + parms.pvlim = 0; + parms.max_pdu_len = 0; + parms.lsr_id = nbr->id.s_addr; + parms.lspace_id = 0; + + return (ibuf_add(buf, &parms, SESS_PRMS_SIZE)); +} + +static int +gen_cap_dynamic_tlv(struct ibuf *buf) +{ + struct capability_tlv cap; + + memset(&cap, 0, sizeof(cap)); + cap.type = htons(TLV_TYPE_DYNAMIC_CAP); + cap.length = htons(CAP_TLV_DYNAMIC_LEN); + /* the S-bit is always 1 for the Dynamic Capability Announcement */ + cap.reserved = STATE_BIT; + + return (ibuf_add(buf, &cap, CAP_TLV_DYNAMIC_SIZE)); +} + +static int +gen_cap_twcard_tlv(struct ibuf *buf, int enable) +{ + struct capability_tlv cap; + + memset(&cap, 0, sizeof(cap)); + cap.type = htons(TLV_TYPE_TWCARD_CAP); + cap.length = htons(CAP_TLV_TWCARD_LEN); + if (enable) + cap.reserved = STATE_BIT; + + return (ibuf_add(buf, &cap, CAP_TLV_TWCARD_SIZE)); +} + +static int +gen_cap_unotif_tlv(struct ibuf *buf, int enable) +{ + struct capability_tlv cap; + + memset(&cap, 0, sizeof(cap)); + cap.type = htons(TLV_TYPE_UNOTIF_CAP); + cap.length = htons(CAP_TLV_UNOTIF_LEN); + if (enable) + cap.reserved = STATE_BIT; + + return (ibuf_add(buf, &cap, CAP_TLV_UNOTIF_SIZE)); +} diff --git a/ldpd/interface.c b/ldpd/interface.c new file mode 100644 index 0000000..f0e70cb --- /dev/null +++ b/ldpd/interface.c @@ -0,0 +1,968 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" + +#include "sockopt.h" + +static __inline int iface_compare(const struct iface *, const struct iface *); +static struct if_addr *if_addr_new(struct kaddr *); +static struct if_addr *if_addr_lookup(struct if_addr_head *, struct kaddr *); +static int if_start(struct iface *, int); +static int if_reset(struct iface *, int); +static void if_update_af(struct iface_af *); +static void if_hello_timer(struct event *thread); +static void if_start_hello_timer(struct iface_af *); +static void if_stop_hello_timer(struct iface_af *); +static int if_join_ipv4_group(struct iface *, struct in_addr *); +static int if_leave_ipv4_group(struct iface *, struct in_addr *); +static int if_join_ipv6_group(struct iface *, struct in6_addr *); +static int if_leave_ipv6_group(struct iface *, struct in6_addr *); + +static int ldp_sync_fsm_init(struct iface *iface, int state); +static int ldp_sync_act_iface_start_sync(struct iface *iface); +static void iface_wait_for_ldp_sync_timer(struct event *thread); +static void start_wait_for_ldp_sync_timer(struct iface *iface); +static void stop_wait_for_ldp_sync_timer(struct iface *iface); +static int ldp_sync_act_ldp_start_sync(struct iface *iface); +static int ldp_sync_act_ldp_complete_sync(struct iface *iface); +static int iface_to_oper_nbr_count(struct iface *iface, unsigned int type); +static void ldp_sync_get_peer_ldp_id(struct iface *iface, + struct in_addr *peer_ldp_id); + +RB_GENERATE(iface_head, iface, entry, iface_compare) + +static __inline int +iface_compare(const struct iface *a, const struct iface *b) +{ + return if_cmp_name_func(a->name, b->name); +} + +struct iface * +if_new(const char *name) +{ + struct iface *iface; + + if ((iface = calloc(1, sizeof(*iface))) == NULL) + fatal("if_new: calloc"); + + strlcpy(iface->name, name, sizeof(iface->name)); + + /* ipv4 */ + iface->ipv4.af = AF_INET; + iface->ipv4.iface = iface; + iface->ipv4.enabled = 0; + + /* ipv6 */ + iface->ipv6.af = AF_INET6; + iface->ipv6.iface = iface; + iface->ipv6.enabled = 0; + + return (iface); +} + +void +ldpe_if_init(struct iface *iface) +{ + log_debug("%s: interface %s", __func__, iface->name); + + LIST_INIT(&iface->addr_list); + + /* ipv4 */ + iface->ipv4.iface = iface; + iface->ipv4.state = IF_STA_DOWN; + RB_INIT(ia_adj_head, &iface->ipv4.adj_tree); + + /* ipv6 */ + iface->ipv6.iface = iface; + iface->ipv6.state = IF_STA_DOWN; + RB_INIT(ia_adj_head, &iface->ipv6.adj_tree); + + /* LGP IGP Sync */ + ldp_sync_fsm_init(iface, LDP_SYNC_STA_NOT_ACH); +} + +void +ldpe_if_exit(struct iface *iface) +{ + struct if_addr *if_addr; + + log_debug("%s: interface %s", __func__, iface->name); + + ldp_sync_fsm(iface, LDP_SYNC_EVT_CONFIG_LDP_OFF); + + if (iface->ipv4.state == IF_STA_ACTIVE) + if_reset(iface, AF_INET); + if (iface->ipv6.state == IF_STA_ACTIVE) + if_reset(iface, AF_INET6); + + while ((if_addr = LIST_FIRST(&iface->addr_list)) != NULL) { + LIST_REMOVE(if_addr, entry); + assert(if_addr != LIST_FIRST(&iface->addr_list)); + free(if_addr); + } +} + +struct iface * +if_lookup(struct ldpd_conf *xconf, ifindex_t ifindex) +{ + struct iface *iface; + + RB_FOREACH(iface, iface_head, &xconf->iface_tree) + if (iface->ifindex == ifindex) + return (iface); + + return (NULL); +} + +struct iface * +if_lookup_name(struct ldpd_conf *xconf, const char *ifname) +{ + struct iface iface; + strlcpy(iface.name, ifname, sizeof(iface.name)); + return (RB_FIND(iface_head, &xconf->iface_tree, &iface)); +} + +void +if_update_info(struct iface *iface, struct kif *kif) +{ + /* get type */ + if (CHECK_FLAG(kif->flags, IFF_POINTOPOINT)) + iface->type = IF_TYPE_POINTOPOINT; + if (CHECK_FLAG(kif->flags, IFF_BROADCAST) && + CHECK_FLAG(kif->flags, IFF_MULTICAST)) + iface->type = IF_TYPE_BROADCAST; + + if (ldpd_process == PROC_LDP_ENGINE && iface->operative && !kif->operative) + ldp_sync_fsm(iface, LDP_SYNC_EVT_IFACE_SHUTDOWN); + + /* get index and flags */ + iface->ifindex = kif->ifindex; + iface->operative = kif->operative; +} + +struct iface_af * +iface_af_get(struct iface *iface, int af) +{ + switch (af) { + case AF_INET: + return (&iface->ipv4); + case AF_INET6: + return (&iface->ipv6); + default: + fatalx("iface_af_get: unknown af"); + } +} + +static struct if_addr * +if_addr_new(struct kaddr *ka) +{ + struct if_addr *if_addr; + + if ((if_addr = calloc(1, sizeof(*if_addr))) == NULL) + fatal(__func__); + + if_addr->af = ka->af; + if_addr->addr = ka->addr; + if_addr->prefixlen = ka->prefixlen; + if_addr->dstbrd = ka->dstbrd; + + return (if_addr); +} + +static struct if_addr * +if_addr_lookup(struct if_addr_head *addr_list, struct kaddr *ka) +{ + struct if_addr *if_addr; + int af = ka->af; + + LIST_FOREACH(if_addr, addr_list, entry) + if (!ldp_addrcmp(af, &if_addr->addr, &ka->addr) && + if_addr->prefixlen == ka->prefixlen && + !ldp_addrcmp(af, &if_addr->dstbrd, &ka->dstbrd)) + return (if_addr); + + return (NULL); +} + +void +if_addr_add(struct kaddr *ka) +{ + struct iface *iface; + struct if_addr *if_addr; + struct nbr *nbr; + + if (if_addr_lookup(&global.addr_list, ka) == NULL) { + if_addr = if_addr_new(ka); + + LIST_INSERT_HEAD(&global.addr_list, if_addr, entry); + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->state != NBR_STA_OPER) + continue; + if (if_addr->af == AF_INET && !nbr->v4_enabled) + continue; + if (if_addr->af == AF_INET6 && !nbr->v6_enabled) + continue; + + send_address_single(nbr, if_addr, 0); + } + } + + iface = if_lookup_name(leconf, ka->ifname); + if (iface) { + if (ka->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&ka->addr.v6)) + iface->linklocal = ka->addr.v6; + + if (if_addr_lookup(&iface->addr_list, ka) == NULL) { + if_addr = if_addr_new(ka); + LIST_INSERT_HEAD(&iface->addr_list, if_addr, entry); + ldp_if_update(iface, if_addr->af); + } + } +} + +void +if_addr_del(struct kaddr *ka) +{ + struct iface *iface; + struct if_addr *if_addr; + struct nbr *nbr; + + iface = if_lookup_name(leconf, ka->ifname); + if (iface) { + if (ka->af == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&iface->linklocal, &ka->addr.v6)) + memset(&iface->linklocal, 0, sizeof(iface->linklocal)); + + if_addr = if_addr_lookup(&iface->addr_list, ka); + if (if_addr) { + LIST_REMOVE(if_addr, entry); + ldp_if_update(iface, if_addr->af); + free(if_addr); + } + } + + if_addr = if_addr_lookup(&global.addr_list, ka); + if (if_addr) { + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->state != NBR_STA_OPER) + continue; + if (if_addr->af == AF_INET && !nbr->v4_enabled) + continue; + if (if_addr->af == AF_INET6 && !nbr->v6_enabled) + continue; + send_address_single(nbr, if_addr, 1); + } + LIST_REMOVE(if_addr, entry); + free(if_addr); + } +} + +static int +if_start(struct iface *iface, int af) +{ + struct iface_af *ia; + struct timeval now; + + log_debug("%s: %s address-family %s", __func__, iface->name, af_name(af)); + + ia = iface_af_get(iface, af); + + gettimeofday(&now, NULL); + ia->uptime = now.tv_sec; + + switch (af) { + case AF_INET: + if (if_join_ipv4_group(iface, &global.mcast_addr_v4)) + return (-1); + break; + case AF_INET6: + if (if_join_ipv6_group(iface, &global.mcast_addr_v6)) + return (-1); + break; + default: + fatalx("if_start: unknown af"); + } + + send_hello(HELLO_LINK, ia, NULL); + if_start_hello_timer(ia); + ia->state = IF_STA_ACTIVE; + + return (0); +} + +static int +if_reset(struct iface *iface, int af) +{ + struct iface_af *ia; + struct adj *adj; + + log_debug("%s: %s address-family %s", __func__, iface->name, + af_name(af)); + + ia = iface_af_get(iface, af); + if_stop_hello_timer(ia); + + while (!RB_EMPTY(ia_adj_head, &ia->adj_tree)) { + adj = RB_ROOT(ia_adj_head, &ia->adj_tree); + + adj_del(adj, S_SHUTDOWN); + } + + /* try to cleanup */ + switch (af) { + case AF_INET: + if (global.ipv4.ldp_disc_socket != -1) + if_leave_ipv4_group(iface, &global.mcast_addr_v4); + break; + case AF_INET6: + if (global.ipv6.ldp_disc_socket != -1) + if_leave_ipv6_group(iface, &global.mcast_addr_v6); + break; + default: + fatalx("if_reset: unknown af"); + } + + ia->state = IF_STA_DOWN; + + return (0); +} + +static void +if_update_af(struct iface_af *ia) +{ + int addr_ok = 0, socket_ok, rtr_id_ok; + struct if_addr *if_addr; + + switch (ia->af) { + case AF_INET: + /* + * NOTE: for LDPv4, each interface should have at least one + * valid IP address otherwise they can not be enabled. + */ + LIST_FOREACH(if_addr, &ia->iface->addr_list, entry) { + if (if_addr->af == AF_INET) { + addr_ok = 1; + break; + } + } + break; + case AF_INET6: + /* for IPv6 the link-local address is enough. */ + if (IN6_IS_ADDR_LINKLOCAL(&ia->iface->linklocal)) + addr_ok = 1; + break; + default: + fatalx("if_update_af: unknown af"); + } + + if ((ldp_af_global_get(&global, ia->af))->ldp_disc_socket != -1) + socket_ok = 1; + else + socket_ok = 0; + + if (ldp_rtr_id_get(leconf) != INADDR_ANY) + rtr_id_ok = 1; + else + rtr_id_ok = 0; + + if (ia->state == IF_STA_DOWN) { + if (!ia->enabled || !ia->iface->operative || !addr_ok || + !socket_ok || !rtr_id_ok) + return; + + if_start(ia->iface, ia->af); + } else if (ia->state == IF_STA_ACTIVE) { + if (ia->enabled && ia->iface->operative && addr_ok && + socket_ok && rtr_id_ok) + return; + + if_reset(ia->iface, ia->af); + } +} + +void +ldp_if_update(struct iface *iface, int af) +{ + if (af == AF_INET || af == AF_UNSPEC) + if_update_af(&iface->ipv4); + if (af == AF_INET6 || af == AF_UNSPEC) + if_update_af(&iface->ipv6); +} + +void +if_update_all(int af) +{ + struct iface *iface; + + RB_FOREACH(iface, iface_head, &leconf->iface_tree) + ldp_if_update(iface, af); +} + +uint16_t +if_get_hello_holdtime(struct iface_af *ia) +{ + if (ia->hello_holdtime != 0) + return (ia->hello_holdtime); + + if ((ldp_af_conf_get(leconf, ia->af))->lhello_holdtime != 0) + return ((ldp_af_conf_get(leconf, ia->af))->lhello_holdtime); + + return (leconf->lhello_holdtime); +} + +uint16_t +if_get_hello_interval(struct iface_af *ia) +{ + if (ia->hello_interval != 0) + return (ia->hello_interval); + + if ((ldp_af_conf_get(leconf, ia->af))->lhello_interval != 0) + return ((ldp_af_conf_get(leconf, ia->af))->lhello_interval); + + return (leconf->lhello_interval); +} + +uint16_t +if_get_wait_for_sync_interval(void) +{ + return (leconf->wait_for_sync_interval); +} + +/* timers */ +/* ARGSUSED */ +static void if_hello_timer(struct event *thread) +{ + struct iface_af *ia = EVENT_ARG(thread); + + ia->hello_timer = NULL; + send_hello(HELLO_LINK, ia, NULL); + if_start_hello_timer(ia); +} + +static void +if_start_hello_timer(struct iface_af *ia) +{ + EVENT_OFF(ia->hello_timer); + event_add_timer(master, if_hello_timer, ia, if_get_hello_interval(ia), + &ia->hello_timer); +} + +static void +if_stop_hello_timer(struct iface_af *ia) +{ + EVENT_OFF(ia->hello_timer); +} + +struct ctl_iface * +if_to_ctl(struct iface_af *ia) +{ + static struct ctl_iface ictl; + struct timeval now; + struct adj *adj; + + ictl.af = ia->af; + memcpy(ictl.name, ia->iface->name, sizeof(ictl.name)); + ictl.ifindex = ia->iface->ifindex; + ictl.state = ia->state; + ictl.type = ia->iface->type; + ictl.hello_holdtime = if_get_hello_holdtime(ia); + ictl.hello_interval = if_get_hello_interval(ia); + + gettimeofday(&now, NULL); + if (ia->state != IF_STA_DOWN && + ia->uptime != 0) { + ictl.uptime = now.tv_sec - ia->uptime; + } else + ictl.uptime = 0; + + ictl.adj_cnt = 0; + RB_FOREACH(adj, ia_adj_head, &ia->adj_tree) + ictl.adj_cnt++; + + return (&ictl); +} + +static void +ldp_sync_get_peer_ldp_id(struct iface *iface, struct in_addr *peer_ldp_id) +{ + struct iface_af *ia; + struct adj *adj; + + if (iface->ipv4.state == IF_STA_ACTIVE) { + ia = iface_af_get(iface, AF_INET); + RB_FOREACH(adj, ia_adj_head, &ia->adj_tree) + if (adj->nbr && adj->nbr->state == NBR_STA_OPER) { + *peer_ldp_id = adj->nbr->id; + return; + } + } + + if (iface->ipv6.state == IF_STA_ACTIVE) { + ia = iface_af_get(iface, AF_INET6); + RB_FOREACH(adj, ia_adj_head, &ia->adj_tree) + if (adj->nbr && adj->nbr->state == NBR_STA_OPER) { + *peer_ldp_id = adj->nbr->id; + return; + } + } +} + +struct ctl_ldp_sync * +ldp_sync_to_ctl(struct iface *iface) +{ + static struct ctl_ldp_sync ictl; + + memcpy(ictl.name, iface->name, sizeof(ictl.name)); + ictl.ifindex = iface->ifindex; + ictl.in_sync = (iface->ldp_sync.state == LDP_SYNC_STA_ACH); + ictl.wait_time = if_get_wait_for_sync_interval(); + ictl.timer_running = iface->ldp_sync.wait_for_sync_timer ? true : false; + + ictl.wait_time_remaining = + event_timer_remain_second(iface->ldp_sync.wait_for_sync_timer); + + memset(&ictl.peer_ldp_id, 0, sizeof(ictl.peer_ldp_id)); + + ldp_sync_get_peer_ldp_id(iface, &ictl.peer_ldp_id); + + return (&ictl); +} + +/* multicast membership sockopts */ +in_addr_t +if_get_ipv4_addr(struct iface *iface) +{ + struct if_addr *if_addr; + + LIST_FOREACH(if_addr, &iface->addr_list, entry) + if (if_addr->af == AF_INET) + return (if_addr->addr.v4.s_addr); + + return (INADDR_ANY); +} + +static int +if_join_ipv4_group(struct iface *iface, struct in_addr *addr) +{ + struct in_addr if_addr; + + log_debug("%s: interface %s addr %pI4", __func__, iface->name, addr); + + if_addr.s_addr = if_get_ipv4_addr(iface); + + if (setsockopt_ipv4_multicast(global.ipv4.ldp_disc_socket, + IP_ADD_MEMBERSHIP, if_addr, addr->s_addr, iface->ifindex) < 0) { + log_warn("%s: error IP_ADD_MEMBERSHIP, interface %s address %pI4", + __func__, iface->name, addr); + return (-1); + } + return (0); +} + +static int +if_leave_ipv4_group(struct iface *iface, struct in_addr *addr) +{ + struct in_addr if_addr; + + log_debug("%s: interface %s addr %pI4", __func__, iface->name, addr); + + if_addr.s_addr = if_get_ipv4_addr(iface); + + if (setsockopt_ipv4_multicast(global.ipv4.ldp_disc_socket, + IP_DROP_MEMBERSHIP, if_addr, addr->s_addr, iface->ifindex) < 0) { + log_warn("%s: error IP_DROP_MEMBERSHIP, interface %s address %pI4", __func__, iface->name, addr); + return (-1); + } + + return (0); +} + +static int +if_join_ipv6_group(struct iface *iface, struct in6_addr *addr) +{ + struct ipv6_mreq mreq; + + log_debug("%s: interface %s addr %s", __func__, iface->name, + log_in6addr(addr)); + + mreq.ipv6mr_multiaddr = *addr; + mreq.ipv6mr_interface = iface->ifindex; + + if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, + IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IPV6_JOIN_GROUP, interface %s address %s", + __func__, iface->name, log_in6addr(addr)); + return (-1); + } + + return (0); +} + +static int +if_leave_ipv6_group(struct iface *iface, struct in6_addr *addr) +{ + struct ipv6_mreq mreq; + + log_debug("%s: interface %s addr %s", __func__, iface->name, + log_in6addr(addr)); + + mreq.ipv6mr_multiaddr = *addr; + mreq.ipv6mr_interface = iface->ifindex; + + if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, + IPV6_LEAVE_GROUP, (void *)&mreq, sizeof(mreq)) < 0) { + log_warn("%s: error IPV6_LEAVE_GROUP, interface %s address %s", + __func__, iface->name, log_in6addr(addr)); + return (-1); + } + + return (0); +} + +const struct { + int state; + enum ldp_sync_event event; + enum ldp_sync_action action; + int new_state; +} ldp_sync_fsm_tbl[] = { + /* current state event that happened action to take resulting state */ +/* LDP IGP Sync not achieved */ + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_LDP_SYNC_START, LDP_SYNC_ACT_LDP_START_SYNC, 0}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_LDP_SYNC_COMPLETE, LDP_SYNC_ACT_LDP_COMPLETE_SYNC, LDP_SYNC_STA_ACH}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_CONFIG_LDP_OFF, LDP_SYNC_ACT_CONFIG_LDP_OFF, 0}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_IFACE_SHUTDOWN, LDP_SYNC_ACT_IFACE_SHUTDOWN, 0}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_SESSION_CLOSE, LDP_SYNC_ACT_NOTHING, 0}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_ADJ_DEL, LDP_SYNC_ACT_NOTHING, 0}, + {LDP_SYNC_STA_NOT_ACH, LDP_SYNC_EVT_ADJ_NEW, LDP_SYNC_ACT_NOTHING, 0}, +/* LDP IGP Sync achieved */ + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_CONFIG_LDP_OFF, LDP_SYNC_ACT_CONFIG_LDP_OFF, LDP_SYNC_STA_NOT_ACH}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_LDP_SYNC_COMPLETE, LDP_SYNC_ACT_NOTHING, 0}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_LDP_SYNC_START, LDP_SYNC_ACT_NOTHING, 0}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_IFACE_SHUTDOWN, LDP_SYNC_ACT_IFACE_SHUTDOWN, LDP_SYNC_STA_NOT_ACH}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_SESSION_CLOSE, LDP_SYNC_ACT_IFACE_START_SYNC, LDP_SYNC_STA_NOT_ACH}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_ADJ_DEL, LDP_SYNC_ACT_IFACE_START_SYNC, LDP_SYNC_STA_NOT_ACH}, + {LDP_SYNC_STA_ACH, LDP_SYNC_EVT_ADJ_NEW, LDP_SYNC_ACT_NOTHING, 0}, + {-1, LDP_SYNC_EVT_NOTHING, LDP_SYNC_ACT_NOTHING, 0}, +}; + +const char * const ldp_sync_event_names[] = { + "NOTHING", + "LDP SYNC START", + "LDP SYNC COMPLETE", + "CONFIG LDP OFF", + "IFACE SYNC START (ADJ DEL)", + "IFACE SYNC START (ADJ NEW)", + "IFACE SYNC START (SESSION CLOSE)", + "IFACE SYNC START (CONFIG LDP ON)", + "IFACE SHUTDOWN", + "N/A" +}; + +const char * const ldp_sync_action_names[] = { + "NOTHING", + "IFACE SYNC START", + "LDP START SYNC", + "LDP COMPLETE SYNC", + "CONFIG LDP OFF", + "IFACE SHUTDOWN", + "N/A" +}; + +const char * +ldp_sync_state_name(int state) +{ + switch (state) { + case LDP_SYNC_STA_NOT_ACH: + return ("NOT ACHIEVED"); + case LDP_SYNC_STA_ACH: + return ("ACHIEVED"); + default: + return ("UNKNOWN"); + } +} + +static int +send_ldp_sync_state_update(char *name, int ifindex, int sync_start) +{ + debug_evt_ldp_sync("%s: interface %s (%d), sync_start=%d", + __func__, name, ifindex, sync_start); + + struct ldp_igp_sync_if_state state; + + state.ifindex = ifindex; + state.sync_start = sync_start; + + return ldpe_imsg_compose_parent(IMSG_LDP_SYNC_IF_STATE_UPDATE, + getpid(), &state, sizeof(state)); +} + +static int +ldp_sync_act_iface_start_sync(struct iface *iface) +{ + send_ldp_sync_state_update(iface->name, iface->ifindex, true); + + return (0); +} + +static void iface_wait_for_ldp_sync_timer(struct event *thread) +{ + struct iface *iface = EVENT_ARG(thread); + + ldp_sync_fsm(iface, LDP_SYNC_EVT_LDP_SYNC_COMPLETE); +} + +static void start_wait_for_ldp_sync_timer(struct iface *iface) +{ + if (iface->ldp_sync.wait_for_sync_timer) + return; + + EVENT_OFF(iface->ldp_sync.wait_for_sync_timer); + event_add_timer(master, iface_wait_for_ldp_sync_timer, iface, + if_get_wait_for_sync_interval(), + &iface->ldp_sync.wait_for_sync_timer); +} + +static void stop_wait_for_ldp_sync_timer(struct iface *iface) +{ + EVENT_OFF(iface->ldp_sync.wait_for_sync_timer); +} + +static int +ldp_sync_act_ldp_start_sync(struct iface *iface) +{ + start_wait_for_ldp_sync_timer(iface); + + return 0; +} + +static int +ldp_sync_act_ldp_complete_sync(struct iface *iface) +{ + send_ldp_sync_state_update(iface->name, iface->ifindex, false); + + return 0; +} + +static int +iface_to_oper_nbr_count(struct iface *iface, unsigned int type) +{ + int oper_nbr_count = 0; + struct adj *adj; + + RB_FOREACH(adj, ia_adj_head, &iface->ipv4.adj_tree) { + if (type == adj->source.type && adj->nbr && + adj->nbr->state == NBR_STA_OPER) + oper_nbr_count++; + } + + RB_FOREACH(adj, ia_adj_head, &iface->ipv6.adj_tree) { + if (type == adj->source.type && adj->nbr && + adj->nbr->state == NBR_STA_OPER) + oper_nbr_count++; + } + + return oper_nbr_count; +} + +int +ldp_sync_fsm_adj_event(struct adj *adj, enum ldp_sync_event event) +{ + if (adj->source.type != HELLO_LINK) + return -1; + + struct iface *iface = adj->source.link.ia->iface; + + if (!iface->operative) + return 0; + + if (event == LDP_SYNC_EVT_ADJ_NEW) { + struct nbr *nbr = adj->nbr; + if (nbr && nbr->state == NBR_STA_OPER) { + event = LDP_SYNC_EVT_LDP_SYNC_START; + } + } else if (event == LDP_SYNC_EVT_ADJ_DEL) { + /* Ignore if an operational neighbor exists. + */ + int oper_nbr_count = iface_to_oper_nbr_count(iface, HELLO_LINK); + if (oper_nbr_count > 0) + return 0; + } + + debug_evt_ldp_sync("%s: event %s, " + "adj iface %s (%d) lsr-id %pI4 " + "source address %s transport address %s", + __func__, ldp_sync_event_names[event], + adj->source.link.ia->iface->name, + adj->source.link.ia->iface->ifindex, + &adj->lsr_id, + log_addr(adj_get_af(adj), &adj->source.link.src_addr), + log_addr(adj_get_af(adj), &adj->trans_addr)); + + return ldp_sync_fsm(iface, event); +} + +int +ldp_sync_fsm_nbr_event(struct nbr *nbr, enum ldp_sync_event event) +{ + struct adj *adj; + struct iface *iface = NULL; + RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) { + if (HELLO_LINK != adj->source.type) + continue; + + iface = adj->source.link.ia->iface; + + if (!iface || !iface->operative) + continue; + + int oper_nbr_count = iface_to_oper_nbr_count(iface, HELLO_LINK); + + if (event == LDP_SYNC_EVT_SESSION_CLOSE && oper_nbr_count > 0) + /* Ignore if an operational neighbor exists. + */ + continue; + + debug_evt_ldp_sync("%s: event %s, iface %s, lsr-id %pI4", + __func__, ldp_sync_event_names[event], + iface->name, &nbr->id); + + ldp_sync_fsm(iface, event); + } + + return 0; +} + +int +ldp_sync_fsm_state_req(struct ldp_igp_sync_if_state_req *state_req) +{ + debug_evt_ldp_sync("%s: interface %s (%d) proto %s", + __func__, state_req->name, state_req->ifindex, + zebra_route_string(state_req->proto)); + + struct iface *iface = if_lookup_name(leconf, state_req->name); + + if (!iface) { + debug_evt_ldp_sync("%s: Warning: Ignoring LDP IGP SYNC " + "interface state request for interface %s (%d). " + "Interface does not exist in LDP.", + __func__, state_req->name, state_req->ifindex); + + return 0; + } + + return send_ldp_sync_state_update(state_req->name, + state_req->ifindex, + (iface->ldp_sync.state != LDP_SYNC_STA_ACH)); +} + +static int +ldp_sync_fsm_init(struct iface *iface, int state) +{ + int old_state = iface->ldp_sync.state; + + iface->ldp_sync.state = state; + stop_wait_for_ldp_sync_timer(iface); + + send_ldp_sync_state_update(iface->name, iface->ifindex, + (iface->ldp_sync.state != LDP_SYNC_STA_ACH)); + + if (old_state != iface->ldp_sync.state) { + debug_evt_ldp_sync("%s: resulted in " + "changing state for interface %s (%d) from %s to %s", + __func__, + iface->name, iface->ifindex, + ldp_sync_state_name(old_state), + ldp_sync_state_name(iface->ldp_sync.state)); + } + + return 0; +} + +int +ldp_sync_fsm(struct iface *iface, enum ldp_sync_event event) +{ + int old_state = iface->ldp_sync.state; + int new_state = 0; + int i; + + for (i = 0; ldp_sync_fsm_tbl[i].state != -1; i++) + if ((ldp_sync_fsm_tbl[i].state & old_state) && + (ldp_sync_fsm_tbl[i].event == event)) { + new_state = ldp_sync_fsm_tbl[i].new_state; + break; + } + + if (ldp_sync_fsm_tbl[i].state == -1) { + /* event outside of the defined fsm, ignore it. */ + log_warnx("%s: interface %s, event %s not expected in " + "state %s ", __func__, iface->name, + ldp_sync_event_names[event], + ldp_sync_state_name(old_state)); + return (0); + } + + if (new_state != 0) + iface->ldp_sync.state = new_state; + + switch (ldp_sync_fsm_tbl[i].action) { + case LDP_SYNC_ACT_IFACE_START_SYNC: + ldp_sync_act_iface_start_sync(iface); + break; + case LDP_SYNC_ACT_LDP_START_SYNC: + ldp_sync_act_ldp_start_sync(iface); + break; + case LDP_SYNC_ACT_LDP_COMPLETE_SYNC: + ldp_sync_act_ldp_complete_sync(iface); + break; + case LDP_SYNC_ACT_CONFIG_LDP_OFF: + ldp_sync_fsm_init(iface, LDP_SYNC_STA_NOT_ACH); + break; + case LDP_SYNC_ACT_IFACE_SHUTDOWN: + ldp_sync_fsm_init(iface, iface->ldp_sync.state); + break; + case LDP_SYNC_ACT_NOTHING: + /* do nothing */ + break; + } + + if (old_state != iface->ldp_sync.state) { + + debug_evt_ldp_sync("%s: event %s resulted in action %s " + "for interface %s, changing state from %s to %s", + __func__, ldp_sync_event_names[event], + ldp_sync_action_names[ldp_sync_fsm_tbl[i].action], + iface->name, ldp_sync_state_name(old_state), + ldp_sync_state_name(iface->ldp_sync.state)); + + } else { + debug_evt_ldp_sync("%s: event %s resulted in action %s " + "for interface %s, remaining in state %s", + __func__, ldp_sync_event_names[event], + ldp_sync_action_names[ldp_sync_fsm_tbl[i].action], + iface->name, + ldp_sync_state_name(iface->ldp_sync.state)); + } + + return (0); +} + +void +ldp_sync_fsm_reset_all(void) +{ + struct iface *iface; + + RB_FOREACH(iface, iface_head, &leconf->iface_tree) + ldp_sync_fsm(iface, LDP_SYNC_EVT_CONFIG_LDP_OFF); +} diff --git a/ldpd/keepalive.c b/ldpd/keepalive.c new file mode 100644 index 0000000..7a71989 --- /dev/null +++ b/ldpd/keepalive.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" + +void +send_keepalive(struct nbr *nbr) +{ + struct ibuf *buf; + uint16_t size; + + size = LDP_HDR_SIZE + LDP_MSG_SIZE; + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + gen_ldp_hdr(buf, size); + size -= LDP_HDR_SIZE; + gen_msg_hdr(buf, MSG_TYPE_KEEPALIVE, size); + + debug_kalive_send("keepalive: lsr-id %pI4", &nbr->id); + + evbuf_enqueue(&nbr->tcp->wbuf, buf); + nbr->stats.kalive_sent++; +} + +int +recv_keepalive(struct nbr *nbr, char *buf, uint16_t len) +{ + struct ldp_msg msg; + + memcpy(&msg, buf, sizeof(msg)); + if (len != LDP_MSG_SIZE) { + session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); + return (-1); + } + + debug_kalive_recv("keepalive: lsr-id %pI4", &nbr->id); + + if (nbr->state != NBR_STA_OPER) + nbr_fsm(nbr, NBR_EVT_KEEPALIVE_RCVD); + + return (0); +} diff --git a/ldpd/l2vpn.c b/ldpd/l2vpn.c new file mode 100644 index 0000000..ce038ac --- /dev/null +++ b/ldpd/l2vpn.c @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2015 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" + +static void l2vpn_pw_fec(struct l2vpn_pw *, struct fec *); +static __inline int l2vpn_compare(const struct l2vpn *, const struct l2vpn *); +static __inline int l2vpn_if_compare(const struct l2vpn_if *, const struct l2vpn_if *); +static __inline int l2vpn_pw_compare(const struct l2vpn_pw *, const struct l2vpn_pw *); + +RB_GENERATE(l2vpn_head, l2vpn, entry, l2vpn_compare) +RB_GENERATE(l2vpn_if_head, l2vpn_if, entry, l2vpn_if_compare) +RB_GENERATE(l2vpn_pw_head, l2vpn_pw, entry, l2vpn_pw_compare) + +static __inline int +l2vpn_compare(const struct l2vpn *a, const struct l2vpn *b) +{ + return (strcmp(a->name, b->name)); +} + +struct l2vpn * +l2vpn_new(const char *name) +{ + struct l2vpn *l2vpn; + + if ((l2vpn = calloc(1, sizeof(*l2vpn))) == NULL) + fatal("l2vpn_new: calloc"); + + strlcpy(l2vpn->name, name, sizeof(l2vpn->name)); + + /* set default values */ + l2vpn->mtu = DEFAULT_L2VPN_MTU; + l2vpn->pw_type = DEFAULT_PW_TYPE; + + RB_INIT(l2vpn_if_head, &l2vpn->if_tree); + RB_INIT(l2vpn_pw_head, &l2vpn->pw_tree); + RB_INIT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); + + return (l2vpn); +} + +struct l2vpn * +l2vpn_find(struct ldpd_conf *xconf, const char *name) +{ + struct l2vpn l2vpn; + strlcpy(l2vpn.name, name, sizeof(l2vpn.name)); + return (RB_FIND(l2vpn_head, &xconf->l2vpn_tree, &l2vpn)); +} + +void +l2vpn_del(struct l2vpn *l2vpn) +{ + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { + lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); + + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); + } + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); + + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + free(pw); + } + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); + + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + free(pw); + } + + free(l2vpn); +} + +void +l2vpn_init(struct l2vpn *l2vpn) +{ + struct l2vpn_pw *pw; + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + l2vpn_pw_init(pw); +} + +void +l2vpn_exit(struct l2vpn *l2vpn) +{ + struct l2vpn_pw *pw; + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + l2vpn_pw_exit(pw); +} + +static __inline int +l2vpn_if_compare(const struct l2vpn_if *a, const struct l2vpn_if *b) +{ + return if_cmp_name_func(a->ifname, b->ifname); +} + +struct l2vpn_if * +l2vpn_if_new(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_if *lif; + + if ((lif = calloc(1, sizeof(*lif))) == NULL) + fatal("l2vpn_if_new: calloc"); + + lif->l2vpn = l2vpn; + strlcpy(lif->ifname, ifname, sizeof(lif->ifname)); + + return (lif); +} + +struct l2vpn_if * +l2vpn_if_find(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_if lif; + strlcpy(lif.ifname, ifname, sizeof(lif.ifname)); + return (RB_FIND(l2vpn_if_head, &l2vpn->if_tree, &lif)); +} + +void +l2vpn_if_update_info(struct l2vpn_if *lif, struct kif *kif) +{ + lif->ifindex = kif->ifindex; + lif->operative = kif->operative; + memcpy(lif->mac, kif->mac, sizeof(lif->mac)); +} + +void +l2vpn_if_update(struct l2vpn_if *lif) +{ + struct l2vpn *l2vpn = lif->l2vpn; + struct l2vpn_pw *pw; + struct map fec; + struct nbr *nbr; + + if (lif->operative) + return; + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { + nbr = nbr_find_ldpid(pw->lsr_id.s_addr); + if (nbr == NULL) + continue; + + memset(&fec, 0, sizeof(fec)); + fec.type = MAP_TYPE_PWID; + fec.fec.pwid.type = l2vpn->pw_type; + fec.fec.pwid.group_id = 0; + SET_FLAG(fec.flags, F_MAP_PW_ID); + fec.fec.pwid.pwid = pw->pwid; + + send_mac_withdrawal(nbr, &fec, lif->mac); + } +} + +static __inline int +l2vpn_pw_compare(const struct l2vpn_pw *a, const struct l2vpn_pw *b) +{ + return if_cmp_name_func(a->ifname, b->ifname); +} + +struct l2vpn_pw * +l2vpn_pw_new(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_pw *pw; + + if ((pw = calloc(1, sizeof(*pw))) == NULL) + fatal("l2vpn_pw_new: calloc"); + + pw->l2vpn = l2vpn; + strlcpy(pw->ifname, ifname, sizeof(pw->ifname)); + + return (pw); +} + +struct l2vpn_pw * +l2vpn_pw_find(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_pw *pw; + struct l2vpn_pw s; + + strlcpy(s.ifname, ifname, sizeof(s.ifname)); + pw = RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s); + if (pw) + return (pw); + return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s)); +} + +struct l2vpn_pw * +l2vpn_pw_find_active(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_pw s; + + strlcpy(s.ifname, ifname, sizeof(s.ifname)); + return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_tree, &s)); +} + +struct l2vpn_pw * +l2vpn_pw_find_inactive(struct l2vpn *l2vpn, const char *ifname) +{ + struct l2vpn_pw s; + + strlcpy(s.ifname, ifname, sizeof(s.ifname)); + return (RB_FIND(l2vpn_pw_head, &l2vpn->pw_inactive_tree, &s)); +} + +void +l2vpn_pw_update_info(struct l2vpn_pw *pw, struct kif *kif) +{ + pw->ifindex = kif->ifindex; +} + +void +l2vpn_pw_init(struct l2vpn_pw *pw) +{ + struct fec fec; + struct zapi_pw zpw; + + l2vpn_pw_reset(pw); + + pw2zpw(pw, &zpw); + lde_imsg_compose_parent(IMSG_KPW_ADD, 0, &zpw, sizeof(zpw)); + + l2vpn_pw_fec(pw, &fec); + lde_kernel_insert(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0, + 0, 0, (void *)pw); + lde_kernel_update(&fec); +} + +void +l2vpn_pw_exit(struct l2vpn_pw *pw) +{ + struct fec fec; + struct zapi_pw zpw; + + l2vpn_pw_fec(pw, &fec); + lde_kernel_remove(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0, 0, 0); + lde_kernel_update(&fec); + + pw2zpw(pw, &zpw); + lde_imsg_compose_parent(IMSG_KPW_DELETE, 0, &zpw, sizeof(zpw)); +} + +static void +l2vpn_pw_fec(struct l2vpn_pw *pw, struct fec *fec) +{ + memset(fec, 0, sizeof(*fec)); + fec->type = FEC_TYPE_PWID; + fec->u.pwid.type = pw->l2vpn->pw_type; + fec->u.pwid.pwid = pw->pwid; + fec->u.pwid.lsr_id = pw->lsr_id; +} + +void +l2vpn_pw_reset(struct l2vpn_pw *pw) +{ + pw->remote_group = 0; + pw->remote_mtu = 0; + pw->local_status = PW_FORWARDING; + pw->remote_status = PW_NOT_FORWARDING; + + if (CHECK_FLAG(pw->flags, F_PW_CWORD_CONF)) + SET_FLAG(pw->flags, F_PW_CWORD); + else + UNSET_FLAG(pw->flags, F_PW_CWORD); + + if (CHECK_FLAG(pw->flags, F_PW_STATUSTLV_CONF)) + SET_FLAG(pw->flags, F_PW_STATUSTLV); + else + UNSET_FLAG(pw->flags, F_PW_STATUSTLV); + + if (CHECK_FLAG(pw->flags, F_PW_STATUSTLV_CONF)) { + struct fec_node *fn; + struct fec fec; + l2vpn_pw_fec(pw, &fec); + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn) + pw->remote_status = fn->pw_remote_status; + } + +} + +int +l2vpn_pw_ok(struct l2vpn_pw *pw, struct fec_nh *fnh) +{ + /* check for a remote label */ + if (fnh->remote_label == NO_LABEL) { + log_warnx("%s: pseudowire %s: no remote label", __func__, pw->ifname); + pw->reason = F_PW_NO_REMOTE_LABEL; + return (0); + } + + /* MTUs must match */ + if (pw->l2vpn->mtu != pw->remote_mtu) { + log_warnx("%s: pseudowire %s: MTU mismatch detected", __func__, + pw->ifname); + pw->reason = F_PW_MTU_MISMATCH; + return (0); + } + + /* check pw status if applicable */ + if (CHECK_FLAG(pw->flags, F_PW_STATUSTLV) && + pw->remote_status != PW_FORWARDING) { + log_warnx("%s: pseudowire %s: remote end is down", __func__, pw->ifname); + pw->reason = F_PW_REMOTE_NOT_FWD; + return (0); + } + + pw->reason = F_PW_NO_ERR; + return (1); +} + +int +l2vpn_pw_negotiate(struct lde_nbr *ln, struct fec_node *fn, struct map *map) +{ + struct l2vpn_pw *pw; + struct status_tlv st; + + /* NOTE: thanks martini & friends for all this mess */ + + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL) + /* + * pseudowire not configured, return and record + * the mapping later + */ + return (0); + + /* RFC4447 - Section 6.2: control word negotiation */ + if (fec_find(&ln->sent_map, &fn->fec)) { + if (CHECK_FLAG(map->flags, F_MAP_PW_CWORD) && + !CHECK_FLAG(pw->flags, F_PW_CWORD_CONF)) { + /* ignore the received label mapping */ + return (1); + } else if (!CHECK_FLAG(map->flags, F_MAP_PW_CWORD) && + CHECK_FLAG(pw->flags, F_PW_CWORD_CONF)) { + /* append a "Wrong C-bit" status code */ + st.status_code = S_WRONG_CBIT; + st.msg_id = map->msg_id; + st.msg_type = htons(MSG_TYPE_LABELMAPPING); + lde_send_labelwithdraw(ln, fn, NULL, &st); + + UNSET_FLAG(pw->flags, F_PW_CWORD); + lde_send_labelmapping(ln, fn, 1); + } + } else if (CHECK_FLAG(map->flags, F_MAP_PW_CWORD)) { + if (CHECK_FLAG(pw->flags, F_PW_CWORD_CONF)) + SET_FLAG(pw->flags, F_PW_CWORD); + else + /* act as if no label mapping had been received */ + return (1); + } else + UNSET_FLAG(pw->flags, F_PW_CWORD); + + /* RFC4447 - Section 5.4.3: pseudowire status negotiation */ + if (fec_find(&ln->recv_map, &fn->fec) == NULL && + !CHECK_FLAG(map->flags, F_MAP_PW_STATUS)) + UNSET_FLAG(pw->flags, F_PW_STATUSTLV); + + return (0); +} + +void +l2vpn_send_pw_status(struct lde_nbr *ln, uint32_t status, struct fec *fec) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = S_PW_STATUS; + nm.pw_status = status; + SET_FLAG(nm.flags, F_NOTIF_PW_STATUS); + lde_fec2map(fec, &nm.fec); + SET_FLAG(nm.flags, F_NOTIF_FEC); + + lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); +} + +void +l2vpn_send_pw_status_wcard(struct lde_nbr *ln, uint32_t status, + uint16_t pw_type, uint32_t group_id) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = S_PW_STATUS; + nm.pw_status = status; + SET_FLAG(nm.flags, F_NOTIF_PW_STATUS); + nm.fec.type = MAP_TYPE_PWID; + nm.fec.fec.pwid.type = pw_type; + nm.fec.fec.pwid.group_id = group_id; + SET_FLAG(nm.flags, F_NOTIF_FEC); + + lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, &nm, sizeof(nm)); +} + +void +l2vpn_recv_pw_status(struct lde_nbr *ln, struct notify_msg *nm) +{ + struct fec fec; + struct fec_node *fn; + struct fec_nh *fnh; + struct l2vpn_pw *pw; + + if (nm->fec.type == MAP_TYPE_TYPED_WCARD || + !CHECK_FLAG(nm->fec.flags, F_MAP_PW_ID)) { + l2vpn_recv_pw_status_wcard(ln, nm); + return; + } + + lde_map2fec(&nm->fec, ln->id, &fec); + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn == NULL) + /* unknown fec */ + return; + + fn->pw_remote_status = nm->pw_status; + + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL) + return; + + fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0, 0, 0); + if (fnh == NULL) + return; + + /* remote status didn't change */ + if (pw->remote_status == nm->pw_status) + return; + pw->remote_status = nm->pw_status; + + if (l2vpn_pw_ok(pw, fnh)) + lde_send_change_klabel(fn, fnh); + else + lde_send_delete_klabel(fn, fnh); +} + +/* RFC4447 PWid group wildcard */ +void +l2vpn_recv_pw_status_wcard(struct lde_nbr *ln, struct notify_msg *nm) +{ + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + struct l2vpn_pw *pw; + struct map *wcard = &nm->fec; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (fn->fec.type != FEC_TYPE_PWID) + continue; + + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL) + continue; + + switch (wcard->type) { + case MAP_TYPE_TYPED_WCARD: + if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD && + wcard->fec.twcard.u.pw_type != fn->fec.u.pwid.type) + continue; + break; + case MAP_TYPE_PWID: + if (wcard->fec.pwid.type != fn->fec.u.pwid.type) + continue; + if (wcard->fec.pwid.group_id != pw->remote_group) + continue; + break; + } + + fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, + 0, 0, 0); + if (fnh == NULL) + continue; + + /* remote status didn't change */ + if (pw->remote_status == nm->pw_status) + continue; + pw->remote_status = nm->pw_status; + + if (l2vpn_pw_ok(pw, fnh)) + lde_send_change_klabel(fn, fnh); + else + lde_send_delete_klabel(fn, fnh); + } +} + +int +l2vpn_pw_status_update(struct zapi_pw_status *zpw) +{ + struct l2vpn *l2vpn; + struct l2vpn_pw *pw = NULL; + struct lde_nbr *ln; + struct fec fec; + uint32_t local_status; + + RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) { + pw = l2vpn_pw_find(l2vpn, zpw->ifname); + if (pw) + break; + } + if (!pw) { + log_warnx("%s: pseudowire %s not found", __func__, zpw->ifname); + return (1); + } + + if (zpw->status == PW_FORWARDING) { + local_status = PW_FORWARDING; + pw->reason = F_PW_NO_ERR; + } else { + local_status = zpw->status; + pw->reason = F_PW_LOCAL_NOT_FWD; + } + + /* local status didn't change */ + if (pw->local_status == local_status) + return (0); + pw->local_status = local_status; + + /* notify remote peer about the status update */ + ln = lde_nbr_find_by_lsrid(pw->lsr_id); + if (ln == NULL) + return (0); + l2vpn_pw_fec(pw, &fec); + if (CHECK_FLAG(pw->flags, F_PW_STATUSTLV)) + l2vpn_send_pw_status(ln, local_status, &fec); + else { + struct fec_node *fn; + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn) { + if (pw->local_status == PW_FORWARDING) + lde_send_labelmapping(ln, fn, 1); + else + lde_send_labelwithdraw(ln, fn, NULL, NULL); + } + } + + return (0); +} + +void +l2vpn_pw_ctl(pid_t pid) +{ + struct l2vpn *l2vpn; + struct l2vpn_pw *pw; + static struct ctl_pw pwctl; + + RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { + memset(&pwctl, 0, sizeof(pwctl)); + strlcpy(pwctl.l2vpn_name, pw->l2vpn->name, + sizeof(pwctl.l2vpn_name)); + strlcpy(pwctl.ifname, pw->ifname, + sizeof(pwctl.ifname)); + pwctl.pwid = pw->pwid; + pwctl.lsr_id = pw->lsr_id; + pwctl.status = PW_NOT_FORWARDING; + if (pw->enabled && + pw->local_status == PW_FORWARDING && + pw->remote_status == PW_FORWARDING) + pwctl.status = PW_FORWARDING; + + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_PW, 0, + pid, &pwctl, sizeof(pwctl)); + } +} + +void +l2vpn_binding_ctl(pid_t pid) +{ + struct fec *f; + struct fec_node *fn; + struct lde_map *me; + struct l2vpn_pw *pw; + static struct ctl_pw pwctl; + + RB_FOREACH(f, fec_tree, &ft) { + if (f->type != FEC_TYPE_PWID) + continue; + + fn = (struct fec_node *)f; + if (fn->local_label == NO_LABEL && + RB_EMPTY(lde_map_head, &fn->downstream)) + continue; + + memset(&pwctl, 0, sizeof(pwctl)); + pwctl.type = f->u.pwid.type; + pwctl.pwid = f->u.pwid.pwid; + pwctl.lsr_id = f->u.pwid.lsr_id; + + pw = (struct l2vpn_pw *) fn->data; + if (pw) { + pwctl.local_label = fn->local_label; + pwctl.local_gid = 0; + pwctl.local_ifmtu = pw->l2vpn->mtu; + pwctl.local_cword = CHECK_FLAG(pw->flags, F_PW_CWORD_CONF) ? 1 : 0; + pwctl.reason = pw->reason; + } else + pwctl.local_label = NO_LABEL; + + RB_FOREACH(me, lde_map_head, &fn->downstream) + if (f->u.pwid.lsr_id.s_addr == me->nexthop->id.s_addr) + break; + + if (me) { + pwctl.remote_label = me->map.label; + pwctl.remote_gid = me->map.fec.pwid.group_id; + if (CHECK_FLAG(me->map.flags, F_MAP_PW_IFMTU)) + pwctl.remote_ifmtu = me->map.fec.pwid.ifmtu; + if (pw) + pwctl.remote_cword = CHECK_FLAG(pw->flags, F_PW_CWORD) ? 1 : 0; + + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING, + 0, pid, &pwctl, sizeof(pwctl)); + } else if (pw) { + pwctl.remote_label = NO_LABEL; + + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING, + 0, pid, &pwctl, sizeof(pwctl)); + } + } +} + +/* ldpe */ + +void +ldpe_l2vpn_init(struct l2vpn *l2vpn) +{ + struct l2vpn_pw *pw; + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + ldpe_l2vpn_pw_init(pw); +} + +void +ldpe_l2vpn_exit(struct l2vpn *l2vpn) +{ + struct l2vpn_pw *pw; + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + ldpe_l2vpn_pw_exit(pw); +} + +void +ldpe_l2vpn_pw_init(struct l2vpn_pw *pw) +{ + struct tnbr *tnbr; + + tnbr = tnbr_find(leconf, pw->af, &pw->addr); + if (tnbr == NULL) { + tnbr = tnbr_new(pw->af, &pw->addr); + tnbr_update(tnbr); + RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr); + } + + tnbr->pw_count++; +} + +void +ldpe_l2vpn_pw_exit(struct l2vpn_pw *pw) +{ + struct tnbr *tnbr; + + tnbr = tnbr_find(leconf, pw->af, &pw->addr); + if (tnbr) { + tnbr->pw_count--; + tnbr_check(leconf, tnbr); + } +} diff --git a/ldpd/labelmapping.c b/ldpd/labelmapping.c new file mode 100644 index 0000000..3c5a5d9 --- /dev/null +++ b/ldpd/labelmapping.c @@ -0,0 +1,866 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2014, 2015 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" + +#include "mpls.h" + +static void enqueue_pdu(struct nbr *, uint16_t, struct ibuf *, uint16_t); +static int gen_label_tlv(struct ibuf *, uint32_t); +static int gen_reqid_tlv(struct ibuf *, uint32_t); +static void log_msg_mapping(int, uint16_t, struct nbr *, struct map *); + +static void +enqueue_pdu(struct nbr *nbr, uint16_t type, struct ibuf *buf, uint16_t size) +{ + struct ldp_hdr *ldp_hdr; + + ldp_hdr = ibuf_seek(buf, 0, sizeof(struct ldp_hdr)); + ldp_hdr->length = htons(size - LDP_HDR_DEAD_LEN); + evbuf_enqueue(&nbr->tcp->wbuf, buf); +} + +/* Generic function that handles all Label Message types */ +void +send_labelmessage(struct nbr *nbr, uint16_t type, struct mapping_head *mh) +{ + struct ibuf *buf = NULL; + struct mapping_entry *me; + uint16_t msg_size, size = 0; + int first = 1; + int err = 0; + + /* nothing to send */ + if (TAILQ_EMPTY(mh)) + return; + + while ((me = TAILQ_FIRST(mh)) != NULL) { + /* generate pdu */ + if (first) { + if ((buf = ibuf_open(nbr->max_pdu_len + LDP_HDR_DEAD_LEN)) == NULL) + fatal(__func__); + + /* real size will be set up later */ + SET_FLAG(err, gen_ldp_hdr(buf, 0)); + + size = LDP_HDR_SIZE; + first = 0; + } + + /* calculate size */ + msg_size = LDP_MSG_SIZE; + msg_size += len_fec_tlv(&me->map); + if (me->map.label != NO_LABEL) + msg_size += LABEL_TLV_SIZE; + if (CHECK_FLAG(me->map.flags, F_MAP_REQ_ID)) + msg_size += REQID_TLV_SIZE; + if (CHECK_FLAG(me->map.flags, F_MAP_STATUS)) + msg_size += STATUS_SIZE; + + /* maximum pdu length exceeded, we need a new ldp pdu */ + if (size + msg_size > nbr->max_pdu_len) { + enqueue_pdu(nbr, type, buf, size); + first = 1; + continue; + } + + size += msg_size; + + /* append message and tlvs */ + SET_FLAG(err, gen_msg_hdr(buf, type, msg_size)); + SET_FLAG(err, gen_fec_tlv(buf, &me->map)); + if (me->map.label != NO_LABEL) + SET_FLAG(err, gen_label_tlv(buf, me->map.label)); + if (CHECK_FLAG(me->map.flags, F_MAP_REQ_ID)) + SET_FLAG(err, gen_reqid_tlv(buf, me->map.requestid)); + if (CHECK_FLAG(me->map.flags, F_MAP_PW_STATUS)) + SET_FLAG(err, gen_pw_status_tlv(buf, me->map.pw_status)); + if (CHECK_FLAG(me->map.flags, F_MAP_STATUS)) + SET_FLAG(err, gen_status_tlv(buf, me->map.st.status_code, + me->map.st.msg_id, me->map.st.msg_type)); + if (err) { + ibuf_free(buf); + mapping_list_clr(mh); + return; + } + + log_msg_mapping(1, type, nbr, &me->map); + + /* no errors - update per neighbor message counters */ + switch (type) { + case MSG_TYPE_LABELMAPPING: + nbr->stats.labelmap_sent++; + break; + case MSG_TYPE_LABELREQUEST: + nbr->stats.labelreq_sent++; + break; + case MSG_TYPE_LABELWITHDRAW: + nbr->stats.labelwdraw_sent++; + break; + case MSG_TYPE_LABELRELEASE: + nbr->stats.labelrel_sent++; + break; + case MSG_TYPE_LABELABORTREQ: + nbr->stats.labelabreq_sent++; + break; + default: + break; + } + + TAILQ_REMOVE(mh, me, entry); + assert(me != TAILQ_FIRST(mh)); + free(me); + } + + enqueue_pdu(nbr, type, buf, size); + + nbr_fsm(nbr, NBR_EVT_PDU_SENT); +} + +/* Generic function that handles all Label Message types */ +int +recv_labelmessage(struct nbr *nbr, char *buf, uint16_t len, uint16_t type) +{ + struct ldp_msg msg; + struct tlv ft; + uint32_t label = NO_LABEL, reqid = 0; + uint32_t pw_status = 0; + uint8_t flags = 0; + int feclen, tlen; + uint16_t current_tlv = 1; + struct mapping_entry *me; + struct mapping_head mh; + struct map map; + + memcpy(&msg, buf, sizeof(msg)); + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + /* FEC TLV */ + if (len < sizeof(ft)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + memcpy(&ft, buf, sizeof(ft)); + if (ntohs(ft.type) != TLV_TYPE_FEC) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + return (-1); + } + feclen = ntohs(ft.length); + if (feclen > len - TLV_HDR_SIZE) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + buf += TLV_HDR_SIZE; /* just advance to the end of the fec header */ + len -= TLV_HDR_SIZE; + + TAILQ_INIT(&mh); + do { + memset(&map, 0, sizeof(map)); + map.msg_id = msg.id; + + if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf, feclen, &map)) == -1) + goto err; + if (map.type == MAP_TYPE_PWID && + !CHECK_FLAG(map.flags, F_MAP_PW_ID) && + type != MSG_TYPE_LABELWITHDRAW && + type != MSG_TYPE_LABELRELEASE) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + goto err; + } + + /* + * The Wildcard FEC Element can be used only in the + * Label Withdraw and Label Release messages. + */ + if (map.type == MAP_TYPE_WILDCARD) { + switch (type) { + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELREQUEST: + case MSG_TYPE_LABELABORTREQ: + session_shutdown(nbr, S_UNKNOWN_FEC, msg.id, msg.type); + goto err; + default: + break; + } + } + + /* + * RFC 5561 - Section 4: + * "An LDP implementation that supports the Typed Wildcard + * FEC Element MUST support its use in Label Request, Label + * Withdraw, and Label Release messages". + */ + if (map.type == MAP_TYPE_TYPED_WCARD) { + switch (type) { + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELABORTREQ: + session_shutdown(nbr, S_UNKNOWN_FEC, msg.id, msg.type); + goto err; + default: + break; + } + } + + /* + * LDP supports the use of multiple FEC Elements per + * FEC for the Label Mapping message only. + */ + if (type != MSG_TYPE_LABELMAPPING && tlen != feclen) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + } + + mapping_list_add(&mh, &map); + + buf += tlen; + len -= tlen; + feclen -= tlen; + } while (feclen > 0); + + /* Optional Parameters */ + while (len > 0) { + struct tlv tlv; + uint16_t tlv_type; + uint16_t tlv_len; + uint32_t reqbuf, labelbuf, statusbuf; + + if (len < sizeof(tlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_type = ntohs(tlv.type); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + + /* + * For Label Mapping messages the Label TLV is mandatory and + * should appear right after the FEC TLV. + */ + if (current_tlv == 1 && + type == MSG_TYPE_LABELMAPPING && + !CHECK_FLAG(tlv_type, TLV_TYPE_GENERICLABEL)) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + goto err; + } + + switch (tlv_type) { + case TLV_TYPE_LABELREQUEST: + switch (type) { + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELREQUEST: + if (tlv_len != REQID_TLV_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + + SET_FLAG(flags, F_MAP_REQ_ID); + memcpy(&reqbuf, buf, sizeof(reqbuf)); + reqid = ntohl(reqbuf); + break; + default: + /* ignore */ + break; + } + break; + case TLV_TYPE_HOPCOUNT: + case TLV_TYPE_PATHVECTOR: + /* ignore */ + break; + case TLV_TYPE_GENERICLABEL: + switch (type) { + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELWITHDRAW: + case MSG_TYPE_LABELRELEASE: + if (tlv_len != LABEL_TLV_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + + memcpy(&labelbuf, buf, sizeof(labelbuf)); + label = ntohl(labelbuf); + /* do not accept invalid labels */ + if (label > MPLS_LABEL_MAX || + (label <= MPLS_LABEL_RESERVED_MAX && + label != MPLS_LABEL_IPV4_EXPLICIT_NULL && + label != MPLS_LABEL_IPV6_EXPLICIT_NULL && + label != MPLS_LABEL_IMPLICIT_NULL)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + } + break; + default: + /* ignore */ + break; + } + break; + case TLV_TYPE_ATMLABEL: + case TLV_TYPE_FRLABEL: + switch (type) { + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELWITHDRAW: + case MSG_TYPE_LABELRELEASE: + /* unsupported */ + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + break; + default: + /* ignore */ + break; + } + break; + case TLV_TYPE_STATUS: + if (tlv_len != STATUS_TLV_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + /* ignore */ + break; + case TLV_TYPE_PW_STATUS: + switch (type) { + case MSG_TYPE_LABELMAPPING: + if (tlv_len != PW_STATUS_TLV_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + goto err; + } + + SET_FLAG(flags, F_MAP_PW_STATUS); + memcpy(&statusbuf, buf, sizeof(statusbuf)); + pw_status = ntohl(statusbuf); + break; + default: + /* ignore */ + break; + } + break; + default: + if (!CHECK_FLAG(ntohs(tlv.type), UNKNOWN_FLAG)) + send_notification_rtlvs(nbr, S_UNKNOWN_TLV, + msg.id, msg.type, tlv_type, tlv_len, buf); + /* ignore unknown tlv */ + break; + } + buf += tlv_len; + len -= tlv_len; + current_tlv++; + } + + /* notify lde about the received message. */ + while ((me = TAILQ_FIRST(&mh)) != NULL) { + int imsg_type = IMSG_NONE; + + SET_FLAG(me->map.flags, flags); + switch (me->map.type) { + case MAP_TYPE_PREFIX: + switch (me->map.fec.prefix.af) { + case AF_INET: + if (label == MPLS_LABEL_IPV6_EXPLICIT_NULL) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + } + if (!nbr->v4_enabled) + goto next; + break; + case AF_INET6: + if (label == MPLS_LABEL_IPV4_EXPLICIT_NULL) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + } + if (!nbr->v6_enabled) + goto next; + break; + default: + fatalx("recv_labelmessage: unknown af"); + } + break; + case MAP_TYPE_PWID: + if (label <= MPLS_LABEL_RESERVED_MAX) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + goto err; + } + if (CHECK_FLAG(me->map.flags, F_MAP_PW_STATUS)) + me->map.pw_status = pw_status; + break; + default: + break; + } + me->map.label = label; + if (CHECK_FLAG(me->map.flags, F_MAP_REQ_ID)) + me->map.requestid = reqid; + + log_msg_mapping(0, type, nbr, &me->map); + + switch (type) { + case MSG_TYPE_LABELMAPPING: + imsg_type = IMSG_LABEL_MAPPING; + break; + case MSG_TYPE_LABELREQUEST: + imsg_type = IMSG_LABEL_REQUEST; + break; + case MSG_TYPE_LABELWITHDRAW: + imsg_type = IMSG_LABEL_WITHDRAW; + break; + case MSG_TYPE_LABELRELEASE: + imsg_type = IMSG_LABEL_RELEASE; + break; + case MSG_TYPE_LABELABORTREQ: + imsg_type = IMSG_LABEL_ABORT; + break; + default: + break; + } + + ldpe_imsg_compose_lde(imsg_type, nbr->peerid, 0, &me->map, + sizeof(struct map)); + + next: + TAILQ_REMOVE(&mh, me, entry); + assert(me != TAILQ_FIRST(&mh)); + free(me); + } + + return (0); + + err: + mapping_list_clr(&mh); + + return (-1); +} + +/* Other TLV related functions */ +static int +gen_label_tlv(struct ibuf *buf, uint32_t label) +{ + struct label_tlv lt; + + lt.type = htons(TLV_TYPE_GENERICLABEL); + lt.length = htons(LABEL_TLV_LEN); + lt.label = htonl(label); + + return (ibuf_add(buf, <, sizeof(lt))); +} + +static int +gen_reqid_tlv(struct ibuf *buf, uint32_t reqid) +{ + struct reqid_tlv rt; + + rt.type = htons(TLV_TYPE_LABELREQUEST); + rt.length = htons(REQID_TLV_LEN); + rt.reqid = htonl(reqid); + + return (ibuf_add(buf, &rt, sizeof(rt))); +} + +int +gen_pw_status_tlv(struct ibuf *buf, uint32_t status) +{ + struct pw_status_tlv st; + + st.type = htons(TLV_TYPE_PW_STATUS); + st.length = htons(PW_STATUS_TLV_LEN); + st.value = htonl(status); + + return (ibuf_add(buf, &st, sizeof(st))); +} + +uint16_t +len_fec_tlv(struct map *map) +{ + uint16_t len = TLV_HDR_SIZE; + + switch (map->type) { + case MAP_TYPE_WILDCARD: + len += FEC_ELM_WCARD_LEN; + break; + case MAP_TYPE_PREFIX: + len += FEC_ELM_PREFIX_MIN_LEN + + PREFIX_SIZE(map->fec.prefix.prefixlen); + break; + case MAP_TYPE_PWID: + len += FEC_PWID_ELM_MIN_LEN; + if (CHECK_FLAG(map->flags, F_MAP_PW_ID)) + len += PW_STATUS_TLV_LEN; + if (CHECK_FLAG(map->flags, F_MAP_PW_IFMTU)) + len += FEC_SUBTLV_IFMTU_SIZE; + if (CHECK_FLAG(map->flags, F_MAP_PW_STATUS)) + len += PW_STATUS_TLV_SIZE; + break; + case MAP_TYPE_TYPED_WCARD: + len += FEC_ELM_TWCARD_MIN_LEN; + switch (map->fec.twcard.type) { + case MAP_TYPE_PREFIX: + case MAP_TYPE_PWID: + len += sizeof(uint16_t); + break; + default: + fatalx("len_fec_tlv: unexpected fec type"); + } + break; + default: + fatalx("len_fec_tlv: unexpected fec type"); + } + + return (len); +} + +int +gen_fec_tlv(struct ibuf *buf, struct map *map) +{ + struct tlv ft; + uint16_t family, len, pw_type, ifmtu; + uint8_t pw_len = 0, twcard_len; + uint32_t group_id, pwid; + int err = 0; + + ft.type = htons(TLV_TYPE_FEC); + + switch (map->type) { + case MAP_TYPE_WILDCARD: + ft.length = htons(sizeof(uint8_t)); + SET_FLAG(err, ibuf_add(buf, &ft, sizeof(ft))); + SET_FLAG(err, ibuf_add(buf, &map->type, sizeof(map->type))); + break; + case MAP_TYPE_PREFIX: + len = PREFIX_SIZE(map->fec.prefix.prefixlen); + ft.length = htons(sizeof(map->type) + sizeof(family) + + sizeof(map->fec.prefix.prefixlen) + len); + SET_FLAG(err, ibuf_add(buf, &ft, sizeof(ft))); + SET_FLAG(err, ibuf_add(buf, &map->type, sizeof(map->type))); + switch (map->fec.prefix.af) { + case AF_INET: + family = htons(AF_IPV4); + break; + case AF_INET6: + family = htons(AF_IPV6); + break; + default: + fatalx("gen_fec_tlv: unknown af"); + break; + } + SET_FLAG(err, ibuf_add(buf, &family, sizeof(family))); + SET_FLAG(err, ibuf_add(buf, &map->fec.prefix.prefixlen, + sizeof(map->fec.prefix.prefixlen))); + if (len) + SET_FLAG(err, ibuf_add(buf, &map->fec.prefix.prefix, len)); + break; + case MAP_TYPE_PWID: + if (CHECK_FLAG(map->flags, F_MAP_PW_ID)) + pw_len += FEC_PWID_SIZE; + if (CHECK_FLAG(map->flags, F_MAP_PW_IFMTU)) + pw_len += FEC_SUBTLV_IFMTU_SIZE; + + len = FEC_PWID_ELM_MIN_LEN + pw_len; + + ft.length = htons(len); + SET_FLAG(err, ibuf_add(buf, &ft, sizeof(ft))); + + SET_FLAG(err, ibuf_add(buf, &map->type, sizeof(uint8_t))); + pw_type = map->fec.pwid.type; + if (CHECK_FLAG(map->flags, F_MAP_PW_CWORD)) + SET_FLAG(pw_type, CONTROL_WORD_FLAG); + pw_type = htons(pw_type); + SET_FLAG(err, ibuf_add(buf, &pw_type, sizeof(uint16_t))); + SET_FLAG(err, ibuf_add(buf, &pw_len, sizeof(uint8_t))); + group_id = htonl(map->fec.pwid.group_id); + SET_FLAG(err, ibuf_add(buf, &group_id, sizeof(uint32_t))); + if (CHECK_FLAG(map->flags, F_MAP_PW_ID)) { + pwid = htonl(map->fec.pwid.pwid); + SET_FLAG(err, ibuf_add(buf, &pwid, sizeof(uint32_t))); + } + if (CHECK_FLAG(map->flags, F_MAP_PW_IFMTU)) { + struct subtlv stlv; + + stlv.type = SUBTLV_IFMTU; + stlv.length = FEC_SUBTLV_IFMTU_SIZE; + SET_FLAG(err, ibuf_add(buf, &stlv, sizeof(uint16_t))); + + ifmtu = htons(map->fec.pwid.ifmtu); + SET_FLAG(err, ibuf_add(buf, &ifmtu, sizeof(uint16_t))); + } + break; + case MAP_TYPE_TYPED_WCARD: + len = FEC_ELM_TWCARD_MIN_LEN; + switch (map->fec.twcard.type) { + case MAP_TYPE_PREFIX: + case MAP_TYPE_PWID: + len += sizeof(uint16_t); + break; + default: + fatalx("gen_fec_tlv: unexpected fec type"); + } + ft.length = htons(len); + SET_FLAG(err, ibuf_add(buf, &ft, sizeof(ft))); + SET_FLAG(err, ibuf_add(buf, &map->type, sizeof(uint8_t))); + SET_FLAG(err, ibuf_add(buf, &map->fec.twcard.type, sizeof(uint8_t))); + + switch (map->fec.twcard.type) { + case MAP_TYPE_PREFIX: + twcard_len = sizeof(uint16_t); + SET_FLAG(err, ibuf_add(buf, &twcard_len, sizeof(uint8_t))); + + switch (map->fec.twcard.u.prefix_af) { + case AF_INET: + family = htons(AF_IPV4); + break; + case AF_INET6: + family = htons(AF_IPV6); + break; + default: + fatalx("gen_fec_tlv: unknown af"); + break; + } + + SET_FLAG(err, ibuf_add(buf, &family, sizeof(uint16_t))); + break; + case MAP_TYPE_PWID: + twcard_len = sizeof(uint16_t); + SET_FLAG(err, ibuf_add(buf, &twcard_len, sizeof(uint8_t))); + pw_type = htons(map->fec.twcard.u.pw_type); + SET_FLAG(err, ibuf_add(buf, &pw_type, sizeof(uint16_t))); + break; + default: + fatalx("gen_fec_tlv: unexpected fec type"); + } + break; + default: + break; + } + + return (err); +} + +int +tlv_decode_fec_elm(struct nbr *nbr, struct ldp_msg *msg, char *buf, + uint16_t len, struct map *map) +{ + uint16_t off = 0; + uint8_t pw_len, twcard_len; + + map->type = *buf; + off += sizeof(uint8_t); + + switch (map->type) { + case MAP_TYPE_WILDCARD: + if (len == FEC_ELM_WCARD_LEN) + return (off); + else { + session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); + return (-1); + } + break; + case MAP_TYPE_PREFIX: + if (len < FEC_ELM_PREFIX_MIN_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + /* Address Family */ + memcpy(&map->fec.prefix.af, buf + off, sizeof(map->fec.prefix.af)); + off += sizeof(map->fec.prefix.af); + map->fec.prefix.af = ntohs(map->fec.prefix.af); + switch (map->fec.prefix.af) { + case AF_IPV4: + map->fec.prefix.af = AF_INET; + break; + case AF_IPV6: + map->fec.prefix.af = AF_INET6; + break; + default: + send_notification(nbr->tcp, S_UNSUP_ADDR, msg->id, msg->type); + return (-1); + } + + /* Prefix Length */ + map->fec.prefix.prefixlen = buf[off]; + off += sizeof(uint8_t); + if ((map->fec.prefix.af == AF_IPV4 + && map->fec.prefix.prefixlen > IPV4_MAX_BITLEN) + || (map->fec.prefix.af == AF_IPV6 + && map->fec.prefix.prefixlen > IPV6_MAX_BITLEN)) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); + return (-1); + } + if (len < off + PREFIX_SIZE(map->fec.prefix.prefixlen)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + /* Prefix */ + memset(&map->fec.prefix.prefix, 0, sizeof(map->fec.prefix.prefix)); + memcpy(&map->fec.prefix.prefix, buf + off, + PREFIX_SIZE(map->fec.prefix.prefixlen)); + + /* Just in case... */ + ldp_applymask(map->fec.prefix.af, &map->fec.prefix.prefix, + &map->fec.prefix.prefix, map->fec.prefix.prefixlen); + + return (off + PREFIX_SIZE(map->fec.prefix.prefixlen)); + case MAP_TYPE_PWID: + if (len < FEC_PWID_ELM_MIN_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + /* PW type */ + memcpy(&map->fec.pwid.type, buf + off, sizeof(uint16_t)); + map->fec.pwid.type = ntohs(map->fec.pwid.type); + if (CHECK_FLAG(map->fec.pwid.type, CONTROL_WORD_FLAG)) { + SET_FLAG(map->flags, F_MAP_PW_CWORD); + UNSET_FLAG(map->fec.pwid.type, CONTROL_WORD_FLAG); + } + off += sizeof(uint16_t); + + /* PW info Length */ + pw_len = buf[off]; + off += sizeof(uint8_t); + + if (len != FEC_PWID_ELM_MIN_LEN + pw_len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + /* Group ID */ + memcpy(&map->fec.pwid.group_id, buf + off, sizeof(uint32_t)); + map->fec.pwid.group_id = ntohl(map->fec.pwid.group_id); + off += sizeof(uint32_t); + + /* PW ID */ + if (pw_len == 0) + return (off); + + if (pw_len < sizeof(uint32_t)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + memcpy(&map->fec.pwid.pwid, buf + off, sizeof(uint32_t)); + map->fec.pwid.pwid = ntohl(map->fec.pwid.pwid); + SET_FLAG(map->flags, F_MAP_PW_ID); + off += sizeof(uint32_t); + pw_len -= sizeof(uint32_t); + + /* Optional Interface Parameter Sub-TLVs */ + while (pw_len > 0) { + struct subtlv stlv; + + if (pw_len < sizeof(stlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + memcpy(&stlv, buf + off, sizeof(stlv)); + if (stlv.length > pw_len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + switch (stlv.type) { + case SUBTLV_IFMTU: + if (stlv.length != FEC_SUBTLV_IFMTU_SIZE) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + memcpy(&map->fec.pwid.ifmtu, buf + off + + SUBTLV_HDR_SIZE, sizeof(uint16_t)); + map->fec.pwid.ifmtu = ntohs(map->fec.pwid.ifmtu); + SET_FLAG(map->flags, F_MAP_PW_IFMTU); + break; + default: + /* ignore */ + break; + } + off += stlv.length; + pw_len -= stlv.length; + } + + return (off); + case MAP_TYPE_TYPED_WCARD: + if (len < FEC_ELM_TWCARD_MIN_LEN) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + memcpy(&map->fec.twcard.type, buf + off, sizeof(uint8_t)); + off += sizeof(uint8_t); + memcpy(&twcard_len, buf + off, sizeof(uint8_t)); + off += sizeof(uint8_t); + if (len != FEC_ELM_TWCARD_MIN_LEN + twcard_len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + switch (map->fec.twcard.type) { + case MAP_TYPE_PREFIX: + if (twcard_len != sizeof(uint16_t)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + memcpy(&map->fec.twcard.u.prefix_af, buf + off, sizeof(uint16_t)); + map->fec.twcard.u.prefix_af = ntohs(map->fec.twcard.u.prefix_af); + off += sizeof(uint16_t); + + switch (map->fec.twcard.u.prefix_af) { + case AF_IPV4: + map->fec.twcard.u.prefix_af = AF_INET; + break; + case AF_IPV6: + map->fec.twcard.u.prefix_af = AF_INET6; + break; + default: + session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type); + return (-1); + } + break; + case MAP_TYPE_PWID: + if (twcard_len != sizeof(uint16_t)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type); + return (-1); + } + + memcpy(&map->fec.twcard.u.pw_type, buf + off, sizeof(uint16_t)); + map->fec.twcard.u.pw_type = ntohs(map->fec.twcard.u.pw_type); + /* ignore the reserved bit as per RFC 6667 */ + map->fec.twcard.u.pw_type &= ~PW_TWCARD_RESERVED_BIT; + off += sizeof(uint16_t); + break; + default: + send_notification(nbr->tcp, S_UNKNOWN_FEC, msg->id, msg->type); + return (-1); + } + + return (off); + default: + send_notification(nbr->tcp, S_UNKNOWN_FEC, msg->id, msg->type); + break; + } + + return (-1); +} + +static void +log_msg_mapping(int out, uint16_t msg_type, struct nbr *nbr, struct map *map) +{ + debug_msg(out, "%s: lsr-id %pI4, fec %s, label %s", msg_name(msg_type), + &nbr->id, log_map(map), log_label(map->label)); +} diff --git a/ldpd/lde.c b/ldpd/lde.c new file mode 100644 index 0000000..876dd41 --- /dev/null +++ b/ldpd/lde.c @@ -0,0 +1,2501 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2004, 2005 Claudio Jeker + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include + +#include "ldp.h" +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" +#include "lde.h" +#include "ldp_debug.h" +#include "rlfa.h" + +#include +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "mpls.h" +#include +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "libfrr.h" +#include "zlog_live.h" + +static void lde_shutdown(void); +static void lde_dispatch_imsg(struct event *thread); +static void lde_dispatch_parent(struct event *thread); +static __inline int lde_nbr_compare(const struct lde_nbr *, + const struct lde_nbr *); +static struct lde_nbr *lde_nbr_new(uint32_t, struct lde_nbr *); +static void lde_nbr_del(struct lde_nbr *); +static struct lde_nbr *lde_nbr_find(uint32_t); +static void lde_nbr_clear(void); +static void lde_nbr_addr_update(struct lde_nbr *, + struct lde_addr *, int); +static __inline int lde_map_compare(const struct lde_map *, + const struct lde_map *); +static void lde_map_free(void *); +static int lde_address_add(struct lde_nbr *, struct lde_addr *); +static int lde_address_del(struct lde_nbr *, struct lde_addr *); +static void lde_address_list_free(struct lde_nbr *); +static void zclient_sync_init(void); +static void lde_label_list_init(void); +static int lde_get_label_chunk(void); +static void on_get_label_chunk_response(uint32_t start, uint32_t end); +static uint32_t lde_get_next_label(void); +static bool lde_fec_connected(const struct fec_node *); +static bool lde_fec_outside_mpls_network(const struct fec_node *); +static void lde_check_filter_af(int, struct ldpd_af_conf *, + const char *); + +RB_GENERATE(nbr_tree, lde_nbr, entry, lde_nbr_compare) +RB_GENERATE(lde_map_head, lde_map, entry, lde_map_compare) + +struct ldpd_conf *ldeconf; +struct nbr_tree lde_nbrs = RB_INITIALIZER(&lde_nbrs); + +static struct imsgev *iev_ldpe; +static struct imsgev iev_main_sync_data; +static struct imsgev *iev_main, *iev_main_sync; + +/* lde privileges */ +static zebra_capabilities_t _caps_p [] = +{ + ZCAP_NET_ADMIN +}; + +static struct zebra_privs_t lde_privs = +{ +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + +/* List of chunks of labels externally assigned by Zebra */ +static struct list *label_chunk_list; +static struct listnode *current_label_chunk; + +/* Synchronous zclient to request labels */ +struct zclient *zclient_sync; + +/* SIGINT / SIGTERM handler. */ +static void +sigint(void) +{ + lde_shutdown(); +} + +static struct frr_signal_t lde_signals[] = +{ + { + .signal = SIGHUP, + /* ignore */ + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +/* label decision engine */ +void +lde(void) +{ + static struct zlog_live_cfg child_log; + +#ifdef HAVE_SETPROCTITLE + setproctitle("label decision engine"); +#endif + ldpd_process = PROC_LDE_ENGINE; + log_procname = log_procnames[PROC_LDE_ENGINE]; + + master = frr_init(); + zlog_live_open_fd(&child_log, LOG_DEBUG, LDPD_FD_LOG); + + /* no frr_config_fork() here, allow frr_pthread to create threads */ + frr_is_after_fork = true; + + /* setup signal handler */ + signal_init(master, array_size(lde_signals), lde_signals); + + /* setup pipes and event handlers to the parent process */ + if ((iev_main = calloc(1, sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_main->ibuf, LDPD_FD_ASYNC); + iev_main->handler_read = lde_dispatch_parent; + event_add_read(master, iev_main->handler_read, iev_main, + iev_main->ibuf.fd, &iev_main->ev_read); + iev_main->handler_write = ldp_write_handler; + + memset(&iev_main_sync_data, 0, sizeof(iev_main_sync_data)); + iev_main_sync = &iev_main_sync_data; + imsg_init(&iev_main_sync->ibuf, LDPD_FD_SYNC); + + /* create base configuration */ + ldeconf = config_new_empty(); + + struct event thread; + while (event_fetch(master, &thread)) + event_call(&thread); + + /* NOTREACHED */ + return; +} + +void +lde_init(struct ldpd_init *init) +{ + /* drop privileges */ + lde_privs.user = init->user; + lde_privs.group = init->group; + zprivs_preinit(&lde_privs); + zprivs_init(&lde_privs); + + /* start the LIB garbage collector */ + lde_gc_start_timer(); + + /* Init synchronous zclient and label list */ + frr_zclient_addr(&zclient_addr, &zclient_addr_len, + init->zclient_serv_path); + zclient_sync_init(); +} + +static void +lde_shutdown(void) +{ + /* close pipes */ + if (iev_ldpe) { + msgbuf_clear(&iev_ldpe->ibuf.w); + close(iev_ldpe->ibuf.fd); + iev_ldpe->ibuf.fd = -1; + } + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + iev_main->ibuf.fd = -1; + msgbuf_clear(&iev_main_sync->ibuf.w); + close(iev_main_sync->ibuf.fd); + iev_main_sync->ibuf.fd = -1; + + lde_gc_stop_timer(); + lde_nbr_clear(); + fec_tree_clear(); + + config_clear(ldeconf); + + if (iev_ldpe) + free(iev_ldpe); + free(iev_main); + + log_info("label decision engine exiting"); + + zlog_fini(); + exit(0); +} + +/* imesg */ +int +lde_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_main->ibuf.fd == -1) + return (0); + return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); +} + +void +lde_imsg_compose_parent_sync(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_main_sync->ibuf.fd == -1) + return; + imsg_compose_event(iev_main_sync, type, 0, pid, -1, data, datalen); + imsg_flush(&iev_main_sync->ibuf); +} + +int +lde_imsg_compose_ldpe(int type, uint32_t peerid, pid_t pid, void *data, + uint16_t datalen) +{ + if (iev_ldpe->ibuf.fd == -1) + return (0); + return (imsg_compose_event(iev_ldpe, type, peerid, pid, + -1, data, datalen)); +} + +/* ARGSUSED */ +static void lde_dispatch_imsg(struct event *thread) +{ + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct lde_nbr *ln; + struct map *map; + struct lde_addr *lde_addr; + struct notify_msg *nm; + ssize_t n; + int shut = 0; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("lde_dispatch_imsg: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_LABEL_MAPPING_FULL: + ln = lde_nbr_find(imsg.hdr.peerid); + if (ln == NULL) { + log_debug("%s: cannot find lde neighbor", __func__); + break; + } + + fec_snap(ln); + break; + case IMSG_LABEL_MAPPING: + case IMSG_LABEL_REQUEST: + case IMSG_LABEL_RELEASE: + case IMSG_LABEL_WITHDRAW: + case IMSG_LABEL_ABORT: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct map)) + fatalx("lde_dispatch_imsg: wrong imsg len"); + map = imsg.data; + + ln = lde_nbr_find(imsg.hdr.peerid); + if (ln == NULL) { + log_debug("%s: cannot find lde neighbor", __func__); + break; + } + + switch (imsg.hdr.type) { + case IMSG_LABEL_MAPPING: + lde_check_mapping(map, ln, 1); + break; + case IMSG_LABEL_REQUEST: + lde_check_request(map, ln); + break; + case IMSG_LABEL_RELEASE: + lde_check_release(map, ln); + break; + case IMSG_LABEL_WITHDRAW: + lde_check_withdraw(map, ln); + break; + case IMSG_LABEL_ABORT: + /* not necessary */ + break; + } + break; + case IMSG_ADDRESS_ADD: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_addr)) + fatalx("lde_dispatch_imsg: wrong imsg len"); + lde_addr = imsg.data; + + ln = lde_nbr_find(imsg.hdr.peerid); + if (ln == NULL) { + log_debug("%s: cannot find lde neighbor", __func__); + break; + } + + if (lde_address_add(ln, lde_addr) < 0) { + log_debug("%s: cannot add address %s, it already exists", __func__, + log_addr(lde_addr->af, &lde_addr->addr)); + } + break; + case IMSG_ADDRESS_DEL: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_addr)) + fatalx("lde_dispatch_imsg: wrong imsg len"); + lde_addr = imsg.data; + + ln = lde_nbr_find(imsg.hdr.peerid); + if (ln == NULL) { + log_debug("%s: cannot find lde neighbor", __func__); + break; + } + + if (lde_address_del(ln, lde_addr) < 0) { + log_debug("%s: cannot delete address %s, it does not exist", __func__, + log_addr(lde_addr->af, &lde_addr->addr)); + } + break; + case IMSG_NOTIFICATION: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct notify_msg)) + fatalx("lde_dispatch_imsg: wrong imsg len"); + nm = imsg.data; + + ln = lde_nbr_find(imsg.hdr.peerid); + if (ln == NULL) { + log_debug("%s: cannot find lde neighbor", __func__); + break; + } + + switch (nm->status_code) { + case S_PW_STATUS: + l2vpn_recv_pw_status(ln, nm); + break; + case S_ENDOFLIB: + /* + * Do nothing for now. Should be useful in + * the future when we implement LDP-IGP + * Synchronization (RFC 5443) and Graceful + * Restart (RFC 3478). + */ + default: + break; + } + break; + case IMSG_NEIGHBOR_UP: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct lde_nbr)) + fatalx("lde_dispatch_imsg: wrong imsg len"); + + if (lde_nbr_find(imsg.hdr.peerid)) + fatalx("lde_dispatch_imsg: neighbor already exists"); + lde_nbr_new(imsg.hdr.peerid, imsg.data); + break; + case IMSG_NEIGHBOR_DOWN: + lde_nbr_del(lde_nbr_find(imsg.hdr.peerid)); + break; + case IMSG_CTL_SHOW_LIB: + rt_dump(imsg.hdr.pid); + + lde_imsg_compose_ldpe(IMSG_CTL_END, 0, + imsg.hdr.pid, NULL, 0); + break; + case IMSG_CTL_SHOW_L2VPN_PW: + l2vpn_pw_ctl(imsg.hdr.pid); + + lde_imsg_compose_ldpe(IMSG_CTL_END, 0, imsg.hdr.pid, NULL, 0); + break; + case IMSG_CTL_SHOW_L2VPN_BINDING: + l2vpn_binding_ctl(imsg.hdr.pid); + + lde_imsg_compose_ldpe(IMSG_CTL_END, 0, imsg.hdr.pid, NULL, 0); + break; + default: + log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + lde_shutdown(); + } +} + +/* ARGSUSED */ +static void lde_dispatch_parent(struct event *thread) +{ + static struct ldpd_conf *nconf; + struct iface *iface, *niface; + struct tnbr *ntnbr; + struct nbr_params *nnbrp; + static struct l2vpn *l2vpn, *nl2vpn; + struct l2vpn_if *lif, *nlif; + struct l2vpn_pw *pw, *npw; + struct imsg imsg; + struct kif *kif; + struct kroute *kr; + int fd; + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + ssize_t n; + int shut = 0; + struct fec fec; + struct ldp_access *laccess; + struct ldp_rlfa_node *rnode, *rntmp; + struct ldp_rlfa_client *rclient; + struct zapi_rlfa_request *rlfa_req; + struct zapi_rlfa_igp *rlfa_igp; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("lde_dispatch_parent: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_IFSTATUS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kif)) + fatalx("IFSTATUS imsg with wrong len"); + kif = imsg.data; + + iface = if_lookup_name(ldeconf, kif->ifname); + if (iface) { + if_update_info(iface, kif); + + /* if up see if any labels need to be updated */ + if (kif->operative) + lde_route_update(iface, AF_UNSPEC); + break; + } + + RB_FOREACH(l2vpn, l2vpn_head, &ldeconf->l2vpn_tree) { + lif = l2vpn_if_find(l2vpn, kif->ifname); + if (lif) { + l2vpn_if_update_info(lif, kif); + break; + } + pw = l2vpn_pw_find(l2vpn, kif->ifname); + if (pw) { + l2vpn_pw_update_info(pw, kif); + break; + } + } + break; + case IMSG_PW_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct zapi_pw_status)) + fatalx("PW_UPDATE imsg with wrong len"); + + if (l2vpn_pw_status_update(imsg.data) != 0) + log_warnx("%s: error updating PW status", __func__); + break; + case IMSG_NETWORK_ADD: + case IMSG_NETWORK_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kroute)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + kr = imsg.data; + + switch (kr->af) { + case AF_INET: + fec.type = FEC_TYPE_IPV4; + fec.u.ipv4.prefix = kr->prefix.v4; + fec.u.ipv4.prefixlen = kr->prefixlen; + break; + case AF_INET6: + fec.type = FEC_TYPE_IPV6; + fec.u.ipv6.prefix = kr->prefix.v6; + fec.u.ipv6.prefixlen = kr->prefixlen; + break; + default: + fatalx("lde_dispatch_parent: unknown af"); + } + + switch (imsg.hdr.type) { + case IMSG_NETWORK_ADD: + lde_kernel_insert(&fec, kr->af, &kr->nexthop, + kr->ifindex, kr->route_type, kr->route_instance, + CHECK_FLAG(kr->flags, F_CONNECTED), NULL); + break; + case IMSG_NETWORK_UPDATE: + lde_kernel_update(&fec); + break; + } + break; + case IMSG_SOCKET_IPC: + if (iev_ldpe) { + log_warnx("%s: received unexpected imsg fd to ldpe", __func__); + break; + } + if ((fd = imsg.fd) == -1) { + log_warnx("%s: expected to receive imsg fd to ldpe but didn't receive any", __func__); + break; + } + + if ((iev_ldpe = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_ldpe->ibuf, fd); + iev_ldpe->handler_read = lde_dispatch_imsg; + event_add_read(master, iev_ldpe->handler_read, iev_ldpe, + iev_ldpe->ibuf.fd, &iev_ldpe->ev_read); + iev_ldpe->handler_write = ldp_write_handler; + iev_ldpe->ev_write = NULL; + break; + case IMSG_INIT: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ldpd_init)) + fatalx("INIT imsg with wrong len"); + + memcpy(&init, imsg.data, sizeof(init)); + lde_init(&init); + break; + case IMSG_AGENTX_ENABLED: + ldp_agentx_enabled(); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct ldpd_conf))) == NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct ldpd_conf)); + + RB_INIT(iface_head, &nconf->iface_tree); + RB_INIT(tnbr_head, &nconf->tnbr_tree); + RB_INIT(nbrp_head, &nconf->nbrp_tree); + RB_INIT(l2vpn_head, &nconf->l2vpn_tree); + break; + case IMSG_RECONF_IFACE: + if ((niface = malloc(sizeof(struct iface))) == NULL) + fatal(NULL); + memcpy(niface, imsg.data, sizeof(struct iface)); + + RB_INSERT(iface_head, &nconf->iface_tree, niface); + break; + case IMSG_RECONF_TNBR: + if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL) + fatal(NULL); + memcpy(ntnbr, imsg.data, sizeof(struct tnbr)); + + RB_INSERT(tnbr_head, &nconf->tnbr_tree, ntnbr); + break; + case IMSG_RECONF_NBRP: + if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL) + fatal(NULL); + memcpy(nnbrp, imsg.data, sizeof(struct nbr_params)); + + RB_INSERT(nbrp_head, &nconf->nbrp_tree, nnbrp); + break; + case IMSG_RECONF_L2VPN: + if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL) + fatal(NULL); + memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn)); + + RB_INIT(l2vpn_if_head, &nl2vpn->if_tree); + RB_INIT(l2vpn_pw_head, &nl2vpn->pw_tree); + RB_INIT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree); + + RB_INSERT(l2vpn_head, &nconf->l2vpn_tree, nl2vpn); + break; + case IMSG_RECONF_L2VPN_IF: + if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL) + fatal(NULL); + memcpy(nlif, imsg.data, sizeof(struct l2vpn_if)); + + RB_INSERT(l2vpn_if_head, &nl2vpn->if_tree, nlif); + break; + case IMSG_RECONF_L2VPN_PW: + if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) + fatal(NULL); + memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); + + RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_tree, npw); + break; + case IMSG_RECONF_L2VPN_IPW: + if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) + fatal(NULL); + memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); + + RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree, npw); + break; + case IMSG_RECONF_END: + merge_config(ldeconf, nconf); + ldp_clear_config(nconf); + nconf = NULL; + break; + case IMSG_DEBUG_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(ldp_debug)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + memcpy(&ldp_debug, imsg.data, sizeof(ldp_debug)); + break; + case IMSG_FILTER_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ldp_access)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + laccess = imsg.data; + lde_check_filter_af(AF_INET, &ldeconf->ipv4, + laccess->name); + lde_check_filter_af(AF_INET6, &ldeconf->ipv6, + laccess->name); + break; + case IMSG_RLFA_REG: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct zapi_rlfa_request)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + rlfa_req = imsg.data; + rnode = rlfa_node_find(&rlfa_req->destination, + rlfa_req->pq_address); + if (!rnode) + rnode = rlfa_node_new(&rlfa_req->destination, + rlfa_req->pq_address); + rclient = rlfa_client_find(rnode, &rlfa_req->igp); + if (rclient) + /* RLFA already registered - do nothing */ + break; + rclient = rlfa_client_new(rnode, &rlfa_req->igp); + lde_rlfa_check(rclient); + break; + case IMSG_RLFA_UNREG_ALL: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct zapi_rlfa_igp)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + rlfa_igp = imsg.data; + + RB_FOREACH_SAFE (rnode, ldp_rlfa_node_head, + &rlfa_node_tree, rntmp) { + rclient = rlfa_client_find(rnode, rlfa_igp); + if (!rclient) + continue; + + rlfa_client_del(rclient); + } + break; + default: + log_debug("%s: unexpected imsg %d", __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + lde_shutdown(); + } +} + +int +lde_acl_check(char *acl_name, int af, union ldpd_addr *addr, uint8_t prefixlen) +{ + return ldp_acl_request(iev_main_sync, acl_name, af, addr, prefixlen); +} + +static bool lde_fec_connected(const struct fec_node *fn) +{ + struct fec_nh *fnh; + + LIST_FOREACH(fnh, &fn->nexthops, entry) + if (CHECK_FLAG(fnh->flags, F_FEC_NH_CONNECTED)) + return true; + + return false; +} + +static bool lde_fec_outside_mpls_network(const struct fec_node *fn) +{ + struct fec_nh *fnh; + + LIST_FOREACH(fnh, &fn->nexthops, entry) + if (!CHECK_FLAG(fnh->flags, F_FEC_NH_NO_LDP)) + return false; + + return true; +} + +uint32_t +lde_update_label(struct fec_node *fn) +{ + + /* should we allocate a label for this fec? */ + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (CHECK_FLAG(ldeconf->ipv4.flags, F_LDPD_AF_ALLOCHOSTONLY) + && fn->fec.u.ipv4.prefixlen != IPV4_MAX_BITLEN) + return (NO_LABEL); + + if (lde_acl_check(ldeconf->ipv4.acl_label_allocate_for, + AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, + fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) + return (NO_LABEL); + break; + case FEC_TYPE_IPV6: + if (CHECK_FLAG(ldeconf->ipv6.flags, F_LDPD_AF_ALLOCHOSTONLY) + && fn->fec.u.ipv6.prefixlen != IPV6_MAX_BITLEN) + return (NO_LABEL); + + if (lde_acl_check(ldeconf->ipv6.acl_label_allocate_for, + AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, + fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) + return (NO_LABEL); + break; + case FEC_TYPE_PWID: + break; + } + + /* + * If connected interface act as egress for fec. + * If LDP is not configured on an interface but there + * are other NHs with interfaces configured with LDP + * then don't act as an egress for the fec, otherwise + * act as an egress for the fec + */ + if (lde_fec_connected(fn) || lde_fec_outside_mpls_network(fn)) { + /* choose implicit or explicit-null depending on configuration */ + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (!CHECK_FLAG(ldeconf->ipv4.flags, F_LDPD_AF_EXPNULL)) + return (MPLS_LABEL_IMPLICIT_NULL); + + if (lde_acl_check(ldeconf->ipv4.acl_label_expnull_for, + AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, + fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) + return (MPLS_LABEL_IMPLICIT_NULL); + return MPLS_LABEL_IPV4_EXPLICIT_NULL; + case FEC_TYPE_IPV6: + if (!CHECK_FLAG(ldeconf->ipv6.flags, F_LDPD_AF_EXPNULL)) + return (MPLS_LABEL_IMPLICIT_NULL); + + if (lde_acl_check(ldeconf->ipv6.acl_label_expnull_for, + AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, + fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) + return (MPLS_LABEL_IMPLICIT_NULL); + return MPLS_LABEL_IPV6_EXPLICIT_NULL; + case FEC_TYPE_PWID: + break; + } + } + + /* preserve current label if there's no need to update it */ + if (fn->local_label != NO_LABEL && + fn->local_label > MPLS_LABEL_RESERVED_MAX) + return (fn->local_label); + + return (lde_get_next_label()); +} + +void +lde_send_change_klabel(struct fec_node *fn, struct fec_nh *fnh) +{ + struct kroute kr; + struct zapi_pw zpw; + struct l2vpn_pw *pw; + + /* + * Ordered Control: don't program label into HW until a + * labelmap msg has been received from upstream router + */ + if (CHECK_FLAG(fnh->flags, F_FEC_NH_DEFER)) + return; + + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + memset(&kr, 0, sizeof(kr)); + kr.af = AF_INET; + kr.prefix.v4 = fn->fec.u.ipv4.prefix; + kr.prefixlen = fn->fec.u.ipv4.prefixlen; + kr.nexthop.v4 = fnh->nexthop.v4; + kr.ifindex = fnh->ifindex; + kr.local_label = fn->local_label; + kr.remote_label = fnh->remote_label; + kr.route_type = fnh->route_type; + kr.route_instance = fnh->route_instance; + lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr, sizeof(kr)); + break; + case FEC_TYPE_IPV6: + memset(&kr, 0, sizeof(kr)); + kr.af = AF_INET6; + kr.prefix.v6 = fn->fec.u.ipv6.prefix; + kr.prefixlen = fn->fec.u.ipv6.prefixlen; + kr.nexthop.v6 = fnh->nexthop.v6; + kr.ifindex = fnh->ifindex; + kr.local_label = fn->local_label; + kr.remote_label = fnh->remote_label; + kr.route_type = fnh->route_type; + kr.route_instance = fnh->route_instance; + + lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr, sizeof(kr)); + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (!pw || fn->local_label == NO_LABEL || + fnh->remote_label == NO_LABEL) + return; + + pw->enabled = true; + pw2zpw(pw, &zpw); + zpw.local_label = fn->local_label; + zpw.remote_label = fnh->remote_label; + lde_imsg_compose_parent(IMSG_KPW_SET, 0, &zpw, sizeof(zpw)); + break; + } +} + +void +lde_send_delete_klabel(struct fec_node *fn, struct fec_nh *fnh) +{ + struct kroute kr; + struct zapi_pw zpw; + struct l2vpn_pw *pw; + + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + memset(&kr, 0, sizeof(kr)); + kr.af = AF_INET; + kr.prefix.v4 = fn->fec.u.ipv4.prefix; + kr.prefixlen = fn->fec.u.ipv4.prefixlen; + kr.nexthop.v4 = fnh->nexthop.v4; + kr.ifindex = fnh->ifindex; + kr.local_label = fn->local_label; + kr.remote_label = fnh->remote_label; + kr.route_type = fnh->route_type; + kr.route_instance = fnh->route_instance; + + lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr, sizeof(kr)); + break; + case FEC_TYPE_IPV6: + memset(&kr, 0, sizeof(kr)); + kr.af = AF_INET6; + kr.prefix.v6 = fn->fec.u.ipv6.prefix; + kr.prefixlen = fn->fec.u.ipv6.prefixlen; + kr.nexthop.v6 = fnh->nexthop.v6; + kr.ifindex = fnh->ifindex; + kr.local_label = fn->local_label; + kr.remote_label = fnh->remote_label; + kr.route_type = fnh->route_type; + kr.route_instance = fnh->route_instance; + + lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr, sizeof(kr)); + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (!pw) + return; + + pw->enabled = false; + pw2zpw(pw, &zpw); + zpw.local_label = fn->local_label; + zpw.remote_label = fnh->remote_label; + lde_imsg_compose_parent(IMSG_KPW_UNSET, 0, &zpw, sizeof(zpw)); + break; + } +} + +void +lde_fec2prefix(const struct fec *fec, struct prefix *prefix) +{ + memset(prefix, 0, sizeof(*prefix)); + switch (fec->type) { + case FEC_TYPE_IPV4: + prefix->family = AF_INET; + prefix->u.prefix4 = fec->u.ipv4.prefix; + prefix->prefixlen = fec->u.ipv4.prefixlen; + break; + case FEC_TYPE_IPV6: + prefix->family = AF_INET6; + prefix->u.prefix6 = fec->u.ipv6.prefix; + prefix->prefixlen = fec->u.ipv6.prefixlen; + break; + case FEC_TYPE_PWID: + prefix->family = AF_UNSPEC; + break; + } +} + +void +lde_prefix2fec(const struct prefix *prefix, struct fec *fec) +{ + memset(fec, 0, sizeof(*fec)); + switch (prefix->family) { + case AF_INET: + fec->type = FEC_TYPE_IPV4; + fec->u.ipv4.prefix = prefix->u.prefix4; + fec->u.ipv4.prefixlen = prefix->prefixlen; + break; + case AF_INET6: + fec->type = FEC_TYPE_IPV6; + fec->u.ipv6.prefix = prefix->u.prefix6; + fec->u.ipv6.prefixlen = prefix->prefixlen; + break; + default: + fatalx("lde_prefix2fec: unknown af"); + break; + } +} + +void +lde_fec2map(struct fec *fec, struct map *map) +{ + memset(map, 0, sizeof(*map)); + + switch (fec->type) { + case FEC_TYPE_IPV4: + map->type = MAP_TYPE_PREFIX; + map->fec.prefix.af = AF_INET; + map->fec.prefix.prefix.v4 = fec->u.ipv4.prefix; + map->fec.prefix.prefixlen = fec->u.ipv4.prefixlen; + break; + case FEC_TYPE_IPV6: + map->type = MAP_TYPE_PREFIX; + map->fec.prefix.af = AF_INET6; + map->fec.prefix.prefix.v6 = fec->u.ipv6.prefix; + map->fec.prefix.prefixlen = fec->u.ipv6.prefixlen; + break; + case FEC_TYPE_PWID: + map->type = MAP_TYPE_PWID; + map->fec.pwid.type = fec->u.pwid.type; + map->fec.pwid.group_id = 0; + SET_FLAG(map->flags, F_MAP_PW_ID); + map->fec.pwid.pwid = fec->u.pwid.pwid; + break; + } +} + +void +lde_map2fec(struct map *map, struct in_addr lsr_id, struct fec *fec) +{ + memset(fec, 0, sizeof(*fec)); + + switch (map->type) { + case MAP_TYPE_PREFIX: + switch (map->fec.prefix.af) { + case AF_INET: + fec->type = FEC_TYPE_IPV4; + fec->u.ipv4.prefix = map->fec.prefix.prefix.v4; + fec->u.ipv4.prefixlen = map->fec.prefix.prefixlen; + break; + case AF_INET6: + fec->type = FEC_TYPE_IPV6; + fec->u.ipv6.prefix = map->fec.prefix.prefix.v6; + fec->u.ipv6.prefixlen = map->fec.prefix.prefixlen; + break; + default: + fatalx("lde_map2fec: unknown af"); + break; + } + break; + case MAP_TYPE_PWID: + fec->type = FEC_TYPE_PWID; + fec->u.pwid.type = map->fec.pwid.type; + fec->u.pwid.pwid = map->fec.pwid.pwid; + fec->u.pwid.lsr_id = lsr_id; + break; + } +} + +void +lde_send_labelmapping(struct lde_nbr *ln, struct fec_node *fn, int single) +{ + struct lde_wdraw *lw; + struct lde_map *me; + struct lde_req *lre; + struct map map; + struct l2vpn_pw *pw; + struct fec_nh *fnh; + bool allow = false; + + /* + * Ordered Control: do not send a labelmap msg until + * a labelmap message is received from downstream router + * and don't send labelmap back to downstream router + */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL)) { + LIST_FOREACH(fnh, &fn->nexthops, entry) { + if (CHECK_FLAG(fnh->flags, F_FEC_NH_DEFER)) + continue; + + if (lde_address_find(ln, fnh->af, &fnh->nexthop)) + return; + allow = true; + break; + } + if (!allow) + return; + } + + /* + * We shouldn't send a new label mapping if we have a pending + * label release to receive. In this case, schedule to send a + * label mapping as soon as a label release is received. + */ + lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); + if (lw) { + if (!fec_find(&ln->sent_map_pending, &fn->fec)) { + debug_evt("%s: FEC %s: scheduling to send label mapping later (waiting for pending label release)", + __func__, log_fec(&fn->fec)); + lde_map_pending_add(ln, fn); + } + return; + } + + /* + * This function skips SL.1 - 3 and SL.9 - 14 because the label + * allocation is done way earlier (because of the merging nature of + * ldpd). + */ + + lde_fec2map(&fn->fec, &map); + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (!ln->v4_enabled) + return; + + if (lde_acl_check(ldeconf->ipv4.acl_label_advertise_to, + AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) + return; + + if (lde_acl_check(ldeconf->ipv4.acl_label_advertise_for, + AF_INET, (union ldpd_addr *)&fn->fec.u.ipv4.prefix, + fn->fec.u.ipv4.prefixlen) != FILTER_PERMIT) + return; + break; + case FEC_TYPE_IPV6: + if (!ln->v6_enabled) + return; + + if (lde_acl_check(ldeconf->ipv6.acl_label_advertise_to, + AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) + return; + + if (lde_acl_check(ldeconf->ipv6.acl_label_advertise_for, + AF_INET6, (union ldpd_addr *)&fn->fec.u.ipv6.prefix, + fn->fec.u.ipv6.prefixlen) != FILTER_PERMIT) + return; + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) + /* not the remote end of the pseudowire */ + return; + + SET_FLAG(map.flags, F_MAP_PW_IFMTU); + map.fec.pwid.ifmtu = pw->l2vpn->mtu; + + if (CHECK_FLAG(pw->flags, F_PW_CWORD)) + SET_FLAG(map.flags, F_MAP_PW_CWORD); + + if (CHECK_FLAG(pw->flags, F_PW_STATUSTLV)) { + SET_FLAG(map.flags, F_MAP_PW_STATUS); + map.pw_status = pw->local_status; + } + break; + } + map.label = fn->local_label; + + /* SL.6: is there a pending request for this mapping? */ + lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); + if (lre) { + /* set label request msg id in the mapping response. */ + map.requestid = lre->msg_id; + map.flags = F_MAP_REQ_ID; + + /* SL.7: delete record of pending request */ + lde_req_del(ln, lre, 0); + } + + /* SL.4: send label mapping */ + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD, ln->peerid, 0, + &map, sizeof(map)); + if (single) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, + NULL, 0); + + /* SL.5: record sent label mapping */ + me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); + if (me == NULL) + me = lde_map_add(ln, fn, 1); + me->map = map; +} + +void +lde_send_labelwithdraw(struct lde_nbr *ln, struct fec_node *fn, + struct map *wcard, struct status_tlv *st) +{ + struct lde_wdraw *lw; + struct map map; + struct fec *f; + struct l2vpn_pw *pw; + + if (fn) { + lde_fec2map(&fn->fec, &map); + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (!ln->v4_enabled) + return; + break; + case FEC_TYPE_IPV6: + if (!ln->v6_enabled) + return; + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) + /* not the remote end of the pseudowire */ + return; + + if (CHECK_FLAG(pw->flags, F_PW_CWORD)) + SET_FLAG(map.flags, F_MAP_PW_CWORD); + break; + } + map.label = fn->local_label; + } else + memcpy(&map, wcard, sizeof(map)); + + if (st) { + map.st.status_code = st->status_code; + map.st.msg_id = st->msg_id; + map.st.msg_type = st->msg_type; + SET_FLAG(map.flags, F_MAP_STATUS); + } + + /* SWd.1: send label withdraw. */ + lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD, ln->peerid, 0, + &map, sizeof(map)); + lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD_END, ln->peerid, 0, NULL, 0); + + /* SWd.2: record label withdraw. */ + if (fn) { + lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); + if (lw == NULL) + lw = lde_wdraw_add(ln, fn); + lw->label = map.label; + } else { + struct lde_map *me; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); + if (lde_wildcard_apply(wcard, &fn->fec, me) == 0) + continue; + + lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); + if (lw == NULL) + lw = lde_wdraw_add(ln, fn); + + lw->label = map.label; + } + } +} + +void +lde_send_labelwithdraw_wcard(struct lde_nbr *ln, uint32_t label) +{ + struct map wcard; + + memset(&wcard, 0, sizeof(wcard)); + wcard.type = MAP_TYPE_WILDCARD; + wcard.label = label; + lde_send_labelwithdraw(ln, NULL, &wcard, NULL); +} + +void +lde_send_labelwithdraw_twcard_prefix(struct lde_nbr *ln, uint16_t af, + uint32_t label) +{ + struct map wcard; + + memset(&wcard, 0, sizeof(wcard)); + wcard.type = MAP_TYPE_TYPED_WCARD; + wcard.fec.twcard.type = MAP_TYPE_PREFIX; + wcard.fec.twcard.u.prefix_af = af; + wcard.label = label; + lde_send_labelwithdraw(ln, NULL, &wcard, NULL); +} + +void +lde_send_labelwithdraw_twcard_pwid(struct lde_nbr *ln, uint16_t pw_type, + uint32_t label) +{ + struct map wcard; + + memset(&wcard, 0, sizeof(wcard)); + wcard.type = MAP_TYPE_TYPED_WCARD; + wcard.fec.twcard.type = MAP_TYPE_PWID; + wcard.fec.twcard.u.pw_type = pw_type; + wcard.label = label; + lde_send_labelwithdraw(ln, NULL, &wcard, NULL); +} + +void +lde_send_labelwithdraw_pwid_wcard(struct lde_nbr *ln, uint16_t pw_type, + uint32_t group_id) +{ + struct map wcard; + + memset(&wcard, 0, sizeof(wcard)); + wcard.type = MAP_TYPE_PWID; + wcard.fec.pwid.type = pw_type; + wcard.fec.pwid.group_id = group_id; + /* we can not append a Label TLV when using PWid group wildcards. */ + wcard.label = NO_LABEL; + lde_send_labelwithdraw(ln, NULL, &wcard, NULL); +} + +void +lde_send_labelrelease(struct lde_nbr *ln, struct fec_node *fn, + struct map *wcard, uint32_t label) +{ + struct map map; + struct l2vpn_pw *pw; + + if (fn) { + lde_fec2map(&fn->fec, &map); + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (!ln->v4_enabled) + return; + break; + case FEC_TYPE_IPV6: + if (!ln->v6_enabled) + return; + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr) + /* not the remote end of the pseudowire */ + return; + + if (CHECK_FLAG(pw->flags, F_PW_CWORD)) + SET_FLAG(map.flags, F_MAP_PW_CWORD); + break; + } + } else + memcpy(&map, wcard, sizeof(map)); + map.label = label; + + lde_imsg_compose_ldpe(IMSG_RELEASE_ADD, ln->peerid, 0, + &map, sizeof(map)); + lde_imsg_compose_ldpe(IMSG_RELEASE_ADD_END, ln->peerid, 0, NULL, 0); +} + +void +lde_send_labelrequest(struct lde_nbr *ln, struct fec_node *fn, + struct map *wcard, int single) +{ + struct map map; + struct fec *f; + struct lde_req *lre; + + if (fn) { + lde_fec2map(&fn->fec, &map); + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + if (!ln->v4_enabled) + return; + break; + case FEC_TYPE_IPV6: + if (!ln->v6_enabled) + return; + break; + case FEC_TYPE_PWID: + fatalx("lde_send_labelrequest: unknown af"); + } + } else + memcpy(&map, wcard, sizeof(map)); + + map.label = NO_LABEL; + + if (fn) { + /* SLR1.1: has label request for FEC been previously sent + * and still outstanding just return, + */ + lre = (struct lde_req *)fec_find(&ln->sent_req, &fn->fec); + if (lre == NULL) { + /* SLRq.3: send label request */ + lde_imsg_compose_ldpe(IMSG_REQUEST_ADD, ln->peerid, 0, + &map, sizeof(map)); + if (single) + lde_imsg_compose_ldpe(IMSG_REQUEST_ADD_END, + ln->peerid, 0, NULL, 0); + + /* SLRq.4: record sent request */ + lde_req_add(ln, &fn->fec, 1); + } + } else { + /* if Wilcard just send label request */ + /* SLRq.3: send label request */ + lde_imsg_compose_ldpe(IMSG_REQUEST_ADD, + ln->peerid, 0, &map, sizeof(map)); + if (single) + lde_imsg_compose_ldpe(IMSG_REQUEST_ADD_END, ln->peerid, 0, NULL, 0); + + /* SLRq.4: record sent request */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + lre = (struct lde_req *)fec_find(&ln->sent_req, &fn->fec); + if (lde_wildcard_apply(wcard, &fn->fec, NULL) == 0) + continue; + if (lre == NULL) + lde_req_add(ln, f, 1); + } + } +} + +void +lde_send_labelrequest_wcard(struct lde_nbr *ln, uint16_t af) +{ + struct map wcard; + + memset(&wcard, 0, sizeof(wcard)); + wcard.type = MAP_TYPE_TYPED_WCARD; + wcard.fec.twcard.type = MAP_TYPE_PREFIX; + wcard.fec.twcard.u.prefix_af = af; + lde_send_labelrequest(ln, NULL, &wcard, 1); +} + +void +lde_send_notification(struct lde_nbr *ln, uint32_t status_code, uint32_t msg_id, + uint16_t msg_type) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = status_code; + /* 'msg_id' and 'msg_type' should be in network byte order */ + nm.msg_id = msg_id; + nm.msg_type = msg_type; + + lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, + &nm, sizeof(nm)); +} + +void +lde_send_notification_eol_prefix(struct lde_nbr *ln, int af) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = S_ENDOFLIB; + nm.fec.type = MAP_TYPE_TYPED_WCARD; + nm.fec.fec.twcard.type = MAP_TYPE_PREFIX; + nm.fec.fec.twcard.u.prefix_af = af; + SET_FLAG(nm.flags, F_NOTIF_FEC); + + lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, + &nm, sizeof(nm)); +} + +void +lde_send_notification_eol_pwid(struct lde_nbr *ln, uint16_t pw_type) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = S_ENDOFLIB; + nm.fec.type = MAP_TYPE_TYPED_WCARD; + nm.fec.fec.twcard.type = MAP_TYPE_PWID; + nm.fec.fec.twcard.u.pw_type = pw_type; + SET_FLAG(nm.flags, F_NOTIF_FEC); + + lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, ln->peerid, 0, + &nm, sizeof(nm)); +} + +static __inline int +lde_nbr_compare(const struct lde_nbr *a, const struct lde_nbr *b) +{ + return (a->peerid - b->peerid); +} + +static struct lde_nbr * +lde_nbr_new(uint32_t peerid, struct lde_nbr *new) +{ + struct lde_nbr *ln; + + if ((ln = calloc(1, sizeof(*ln))) == NULL) + fatal(__func__); + + ln->id = new->id; + ln->v4_enabled = new->v4_enabled; + ln->v6_enabled = new->v6_enabled; + ln->flags = new->flags; + ln->peerid = peerid; + fec_init(&ln->recv_map); + fec_init(&ln->sent_map); + fec_init(&ln->sent_map_pending); + fec_init(&ln->recv_req); + fec_init(&ln->sent_req); + fec_init(&ln->sent_wdraw); + + TAILQ_INIT(&ln->addr_list); + + if (RB_INSERT(nbr_tree, &lde_nbrs, ln) != NULL) + fatalx("lde_nbr_new: RB_INSERT failed"); + + return (ln); +} + +static void +lde_nbr_del(struct lde_nbr *ln) +{ + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + struct l2vpn_pw *pw; + struct lde_nbr *lnbr; + + if (ln == NULL) + return; + + /* uninstall received mappings */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + /* Update RLFA clients. */ + lde_rlfa_update_clients(f, ln, MPLS_INVALID_LABEL); + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + switch (f->type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) + continue; + + /* + * Ordered Control: must mark any non-connected + * NH to wait until we receive a labelmap msg + * before installing in kernel and sending to + * peer, must do this as NHs are not removed + * when lsps go down. Also send label withdraw + * to other neighbors for all fecs from neighbor + * going down + */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL)) { + SET_FLAG(fnh->flags, F_FEC_NH_DEFER); + + RB_FOREACH(lnbr, nbr_tree, &lde_nbrs) { + if (ln->peerid == lnbr->peerid) + continue; + lde_send_labelwithdraw(lnbr, fn, NULL, NULL); + } + } + break; + case FEC_TYPE_PWID: + if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr) + continue; + pw = (struct l2vpn_pw *) fn->data; + if (pw) { + pw->reason = F_PW_NO_REMOTE_LABEL; + l2vpn_pw_reset(pw); + } + break; + default: + break; + } + + lde_send_delete_klabel(fn, fnh); + fnh->remote_label = NO_LABEL; + } + } + + lde_address_list_free(ln); + + fec_clear(&ln->recv_map, lde_map_free); + fec_clear(&ln->sent_map, lde_map_free); + fec_clear(&ln->sent_map_pending, free); + fec_clear(&ln->recv_req, free); + fec_clear(&ln->sent_req, free); + fec_clear(&ln->sent_wdraw, free); + + RB_REMOVE(nbr_tree, &lde_nbrs, ln); + + free(ln); +} + +static struct lde_nbr * +lde_nbr_find(uint32_t peerid) +{ + struct lde_nbr ln; + + ln.peerid = peerid; + + return (RB_FIND(nbr_tree, &lde_nbrs, &ln)); +} + +struct lde_nbr * +lde_nbr_find_by_lsrid(struct in_addr addr) +{ + struct lde_nbr *ln; + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + if (ln->id.s_addr == addr.s_addr) + return (ln); + + return (NULL); +} + +struct lde_nbr * +lde_nbr_find_by_addr(int af, union ldpd_addr *addr) +{ + struct lde_nbr *ln; + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + if (lde_address_find(ln, af, addr) != NULL) + return (ln); + + return (NULL); +} + +static void +lde_nbr_clear(void) +{ + struct lde_nbr *ln; + + while (!RB_EMPTY(nbr_tree, &lde_nbrs)) { + ln = RB_ROOT(nbr_tree, &lde_nbrs); + + lde_nbr_del(ln); + } +} + +static void +lde_nbr_addr_update(struct lde_nbr *ln, struct lde_addr *lde_addr, int removed) +{ + struct fec *fec; + struct fec_node *fn; + struct fec_nh *fnh; + struct lde_map *me; + + RB_FOREACH(fec, fec_tree, &ln->recv_map) { + switch (fec->type) { + case FEC_TYPE_IPV4: + if (lde_addr->af != AF_INET) + continue; + break; + case FEC_TYPE_IPV6: + if (lde_addr->af != AF_INET6) + continue; + break; + case FEC_TYPE_PWID: + continue; + } + + fn = (struct fec_node *)fec_find(&ft, fec); + if (fn == NULL) + /* shouldn't happen */ + continue; + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + if (ldp_addrcmp(fnh->af, &fnh->nexthop, &lde_addr->addr)) + continue; + + if (removed) { + lde_send_delete_klabel(fn, fnh); + fnh->remote_label = NO_LABEL; + } else { + me = (struct lde_map *)fec; + fnh->remote_label = me->map.label; + lde_send_change_klabel(fn, fnh); + } + break; + } + } +} + +void +lde_allow_broken_lsp_update(int new_config) +{ + struct fec_node *fn; + struct fec_nh *fnh; + struct fec *f; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + /* allow-broken-lsp config is changing so + * we need to reprogram labeled routes to + * have proper top-level label + */ + if (!(new_config & F_LDPD_ALLOW_BROKEN_LSP)) + lde_send_delete_klabel(fn, fnh); + + if (fn->local_label != NO_LABEL) + lde_send_change_klabel(fn, fnh); + } + } +} + +static __inline int +lde_map_compare(const struct lde_map *a, const struct lde_map *b) +{ + return (ldp_addrcmp(AF_INET, (union ldpd_addr *)&a->nexthop->id, + (union ldpd_addr *)&b->nexthop->id)); +} + +struct lde_map * +lde_map_add(struct lde_nbr *ln, struct fec_node *fn, int sent) +{ + struct lde_map *me; + + me = calloc(1, sizeof(*me)); + if (me == NULL) + fatal(__func__); + + me->fec = fn->fec; + me->nexthop = ln; + + if (sent) { + RB_INSERT(lde_map_head, &fn->upstream, me); + me->head = &fn->upstream; + if (fec_insert(&ln->sent_map, &me->fec)) + log_warnx("failed to add %s to sent map", log_fec(&me->fec)); + /* XXX on failure more cleanup is needed */ + } else { + RB_INSERT(lde_map_head, &fn->downstream, me); + me->head = &fn->downstream; + if (fec_insert(&ln->recv_map, &me->fec)) + log_warnx("failed to add %s to recv map", log_fec(&me->fec)); + } + + return (me); +} + +void +lde_map_del(struct lde_nbr *ln, struct lde_map *me, int sent) +{ + if (sent) + fec_remove(&ln->sent_map, &me->fec); + else + fec_remove(&ln->recv_map, &me->fec); + + lde_map_free(me); +} + +static void +lde_map_free(void *ptr) +{ + struct lde_map *map = ptr; + + RB_REMOVE(lde_map_head, map->head, map); + free(map); +} + +struct fec * +lde_map_pending_add(struct lde_nbr *ln, struct fec_node *fn) +{ + struct fec *map; + + map = calloc(1, sizeof(*map)); + if (map == NULL) + fatal(__func__); + + *map = fn->fec; + if (fec_insert(&ln->sent_map_pending, map)) + log_warnx("failed to add %s to sent map (pending)", log_fec(map)); + + return (map); +} + +void +lde_map_pending_del(struct lde_nbr *ln, struct fec *map) +{ + fec_remove(&ln->sent_map_pending, map); + free(map); +} + +struct lde_req * +lde_req_add(struct lde_nbr *ln, struct fec *fec, int sent) +{ + struct fec_tree *t; + struct lde_req *lre; + + t = sent ? &ln->sent_req : &ln->recv_req; + + lre = calloc(1, sizeof(*lre)); + if (lre != NULL) { + lre->fec = *fec; + + if (fec_insert(t, &lre->fec)) { + log_warnx("failed to add %s to %s req", + log_fec(&lre->fec), sent ? "sent" : "recv"); + free(lre); + return (NULL); + } + } + + return (lre); +} + +void +lde_req_del(struct lde_nbr *ln, struct lde_req *lre, int sent) +{ + if (sent) + fec_remove(&ln->sent_req, &lre->fec); + else + fec_remove(&ln->recv_req, &lre->fec); + + free(lre); +} + +struct lde_wdraw * +lde_wdraw_add(struct lde_nbr *ln, struct fec_node *fn) +{ + struct lde_wdraw *lw; + + lw = calloc(1, sizeof(*lw)); + if (lw == NULL) + fatal(__func__); + + lw->fec = fn->fec; + + if (fec_insert(&ln->sent_wdraw, &lw->fec)) + log_warnx("failed to add %s to sent wdraw", log_fec(&lw->fec)); + + return (lw); +} + +void +lde_wdraw_del(struct lde_nbr *ln, struct lde_wdraw *lw) +{ + fec_remove(&ln->sent_wdraw, &lw->fec); + free(lw); +} + +void +lde_change_egress_label(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + + /* explicitly withdraw all null labels */ + RB_FOREACH(ln, nbr_tree, &lde_nbrs) { + lde_send_labelwithdraw_wcard(ln, MPLS_LABEL_IMPLICIT_NULL); + if (ln->v4_enabled) + lde_send_labelwithdraw_wcard(ln, MPLS_LABEL_IPV4_EXPLICIT_NULL); + if (ln->v6_enabled) + lde_send_labelwithdraw_wcard(ln, MPLS_LABEL_IPV6_EXPLICIT_NULL); + } + + /* update label of connected routes */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (fn->local_label > MPLS_LABEL_RESERVED_MAX) + continue; + + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + break; + default: + fatalx("lde_change_egress_label: unknown af"); + } + + fn->local_label = lde_update_label(fn); + if (fn->local_label != NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 0); + } + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); +} + +void +lde_change_allocate_filter(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + uint32_t new_label; + + /* reallocate labels for fecs that match this filter */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + break; + default: + fatalx("lde_change_allocate_filter: unknown af"); + } + + /* + * If the local label has changed to NO_LABEL, send a label + * withdraw to all peers. + * If the local label has changed and it's different from + * NO_LABEL, send a label mapping to all peers advertising + * the new label. + * If the local label hasn't changed, do nothing + */ + new_label = lde_update_label(fn); + if (fn->local_label != new_label) { + if (new_label == NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelwithdraw(ln, fn, NULL, NULL); + + fn->local_label = new_label; + if (fn->local_label != NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 0); + } + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, + NULL, 0); +} + +void +lde_change_advertise_filter(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + char *acl_to_filter; + char *acl_for_filter; + union ldpd_addr *prefix; + uint8_t plen; + struct lde_map *me; + + /* advertise label for fecs to neighbors if matches advertise filters */ + switch (af) { + case AF_INET: + acl_to_filter = ldeconf->ipv4.acl_label_advertise_to; + acl_for_filter = ldeconf->ipv4.acl_label_advertise_for; + break; + case AF_INET6: + acl_to_filter = ldeconf->ipv6.acl_label_advertise_to; + acl_for_filter = ldeconf->ipv6.acl_label_advertise_for; + break; + default: + fatalx("lde_change_advertise_filter: unknown af"); + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) { + if (lde_acl_check(acl_to_filter, af, (union ldpd_addr *)&ln->id, + IPV4_MAX_BITLEN) != FILTER_PERMIT) + lde_send_labelwithdraw_wcard(ln, NO_LABEL); + else { + /* This neighbor is allowed in to_filter, so + * send labels if fec also matches for_filter + */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + prefix = (union ldpd_addr *)&fn->fec.u.ipv4.prefix; + plen = fn->fec.u.ipv4.prefixlen; + break; + case FEC_TYPE_IPV6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + prefix = (union ldpd_addr *)&fn->fec.u.ipv6.prefix; + plen = fn->fec.u.ipv6.prefixlen; + break; + default: + continue; + } + + if (lde_acl_check(acl_for_filter, af, + prefix, plen) != FILTER_PERMIT) { + me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); + if (me) + /* fec filtered withdraw */ + lde_send_labelwithdraw(ln, fn, NULL, NULL); + } else + /* fec allowed send map */ + lde_send_labelmapping(ln, fn, 0); + } + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); + } + } +} + + +void +lde_change_accept_filter(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + char *acl_for_filter; + char *acl_from_filter; + union ldpd_addr *prefix; + uint8_t plen; + struct lde_map *me; + enum fec_type type; + + /* accept labels from neighbors specified in the from_filter and for + * fecs defined in the for_filter + */ + switch (af) { + case AF_INET: + acl_for_filter = ldeconf->ipv4.acl_label_accept_for; + acl_from_filter = ldeconf->ipv4.acl_label_accept_from; + type = FEC_TYPE_IPV4; + break; + case AF_INET6: + acl_for_filter = ldeconf->ipv6.acl_label_accept_for; + acl_from_filter = ldeconf->ipv6.acl_label_accept_from; + type = FEC_TYPE_IPV6; + break; + default: + fatalx("lde_change_accept_filter: unknown af"); + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) { + if (lde_acl_check(acl_from_filter, AF_INET, (union ldpd_addr *) + &ln->id, IPV4_MAX_BITLEN) != FILTER_PERMIT) { + /* This neighbor is now filtered so remove fecs from + * recv list + */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (fn->fec.type == type) { + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + if (me) + lde_map_del(ln, me, 0); + } + } + } else if (CHECK_FLAG(ln->flags, F_NBR_CAP_TWCARD)) { + /* This neighbor is allowed and supports type + * wildcard so send a labelrequest + * to get any new labels from neighbor + * and make sure any fecs we currently have + * match for_filter. + */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + prefix = (union ldpd_addr *)&fn->fec.u.ipv4.prefix; + plen = fn->fec.u.ipv4.prefixlen; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + prefix = (union ldpd_addr *)&fn->fec.u.ipv6.prefix; + plen = fn->fec.u.ipv6.prefixlen; + break; + default: + continue; + } + if (lde_acl_check(acl_for_filter, af, + prefix, plen) != FILTER_PERMIT) { + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + if (me) + lde_map_del(ln, me, 0); + } + } + lde_send_labelrequest_wcard(ln, af); + } else + /* Type Wildcard is not supported so restart session */ + lde_imsg_compose_ldpe(IMSG_NBR_SHUTDOWN, ln->peerid, 0, NULL, 0); + } +} + +void +lde_change_expnull_for_filter(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + char *acl_name; + uint32_t exp_label; + union ldpd_addr *prefix; + uint8_t plen; + + /* Configure explicit-null advertisement for all fecs in this filter */ + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + acl_name = ldeconf->ipv4.acl_label_expnull_for; + prefix = (union ldpd_addr *)&fn->fec.u.ipv4.prefix; + plen = fn->fec.u.ipv4.prefixlen; + exp_label = MPLS_LABEL_IPV4_EXPLICIT_NULL; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + acl_name = ldeconf->ipv6.acl_label_expnull_for; + prefix = (union ldpd_addr *)&fn->fec.u.ipv6.prefix; + plen = fn->fec.u.ipv6.prefixlen; + exp_label = MPLS_LABEL_IPV6_EXPLICIT_NULL; + break; + default: + fatalx("lde_change_expnull_for_filter: unknown af"); + } + + if (lde_acl_check(acl_name, af, prefix, plen) == FILTER_PERMIT) { + /* for this fec change any imp-null to exp-null */ + if (fn->local_label == MPLS_LABEL_IMPLICIT_NULL) { + fn->local_label= lde_update_label(fn); + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 0); + } + } else { + /* for this fec change any exp-null back to imp-null */ + if (fn->local_label == exp_label) { + fn->local_label = lde_update_label(fn); + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 0); + } + } + } + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, + NULL, 0); +} + +static int +lde_address_add(struct lde_nbr *ln, struct lde_addr *lde_addr) +{ + struct lde_addr *new; + + if (lde_address_find(ln, lde_addr->af, &lde_addr->addr) != NULL) + return (-1); + + if ((new = calloc(1, sizeof(*new))) == NULL) + fatal(__func__); + + new->af = lde_addr->af; + new->addr = lde_addr->addr; + TAILQ_INSERT_TAIL(&ln->addr_list, new, entry); + + /* reevaluate the previously received mappings from this neighbor */ + lde_nbr_addr_update(ln, lde_addr, 0); + + return (0); +} + +static int +lde_address_del(struct lde_nbr *ln, struct lde_addr *lde_addr) +{ + lde_addr = lde_address_find(ln, lde_addr->af, &lde_addr->addr); + if (lde_addr == NULL) + return (-1); + + /* reevaluate the previously received mappings from this neighbor */ + lde_nbr_addr_update(ln, lde_addr, 1); + + TAILQ_REMOVE(&ln->addr_list, lde_addr, entry); + free(lde_addr); + + return (0); +} + +struct lde_addr * +lde_address_find(struct lde_nbr *ln, int af, union ldpd_addr *addr) +{ + struct lde_addr *lde_addr; + + TAILQ_FOREACH(lde_addr, &ln->addr_list, entry) + if (lde_addr->af == af && + ldp_addrcmp(af, &lde_addr->addr, addr) == 0) + return (lde_addr); + + return (NULL); +} + +static void +lde_address_list_free(struct lde_nbr *ln) +{ + struct lde_addr *lde_addr; + + while ((lde_addr = TAILQ_POP_FIRST(&ln->addr_list, entry)) != NULL) + free(lde_addr); +} + +/* + * Event callback used to retry the label-manager sync zapi session. + */ +static void zclient_sync_retry(struct event *thread) +{ + zclient_sync_init(); +} + +/* + * Initialize and open a synchronous zapi session. This is used by label chunk + * management code, which acquires and releases blocks of labels from the + * zebra label-manager module. + */ +static void zclient_sync_init(void) +{ + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_sync, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_LDP; + zclient_sync->session_id = 1; /* Distinguish from main session */ + zclient_sync->privs = &lde_privs; + + if (zclient_socket_connect(zclient_sync) < 0) { + log_warnx("Error connecting synchronous zclient!"); + goto retry; + } + /* make socket non-blocking */ + sock_set_nonblock(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) { + log_warnx("Error sending hello for synchronous zclient!"); + goto retry; + } + + /* Connect to label manager */ + if (lm_label_manager_connect(zclient_sync, 0) != 0) { + log_warnx("Error connecting to label manager!"); + goto retry; + } + + /* Finish label-manager init once the LM session is running */ + lde_label_list_init(); + + return; + +retry: + + /* Discard failed zclient object */ + zclient_stop(zclient_sync); + zclient_free(zclient_sync); + zclient_sync = NULL; + + /* Retry using a timer */ + event_add_timer(master, zclient_sync_retry, NULL, 1, NULL); +} + +static void +lde_del_label_chunk(void *val) +{ + free(val); +} + +static int +lde_release_label_chunk(uint32_t start, uint32_t end) +{ + int ret; + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) { + log_warnx("Error releasing label chunk!"); + return (-1); + } + return (0); +} + +static int +lde_get_label_chunk(void) +{ + int ret; + uint32_t start, end; + + debug_labels("getting label chunk (size %u)", CHUNK_SIZE); + ret = lm_get_label_chunk(zclient_sync, 0, MPLS_LABEL_BASE_ANY, + CHUNK_SIZE, &start, &end); + if (ret < 0) { + log_warnx("Error getting label chunk!"); + return -1; + } + + on_get_label_chunk_response(start, end); + + return (0); +} + +static void +lde_label_list_init(void) +{ + label_chunk_list = list_new(); + label_chunk_list->del = lde_del_label_chunk; + + /* get first chunk */ + while (lde_get_label_chunk () != 0) { + log_warnx("Error getting first label chunk!"); + sleep(1); + } +} + +static void +on_get_label_chunk_response(uint32_t start, uint32_t end) +{ + struct label_chunk *new_label_chunk; + + debug_labels("label chunk assign: %u - %u", start, end); + + new_label_chunk = calloc(1, sizeof(struct label_chunk)); + if (!new_label_chunk) { + log_warn("Error trying to allocate label chunk %u - %u", start, end); + return; + } + + new_label_chunk->start = start; + new_label_chunk->end = end; + new_label_chunk->used_mask = 0; + + listnode_add(label_chunk_list, (void *)new_label_chunk); + + /* let's update current if needed */ + if (!current_label_chunk) + current_label_chunk = listtail(label_chunk_list); +} + +void +lde_free_label(uint32_t label) +{ + struct listnode *node; + struct label_chunk *label_chunk; + uint64_t pos; + + for (ALL_LIST_ELEMENTS_RO(label_chunk_list, node, label_chunk)) { + if (label <= label_chunk->end && label >= label_chunk->start) { + pos = 1ULL << (label - label_chunk->start); + UNSET_FLAG(label_chunk->used_mask, pos); + /* if nobody is using this chunk and it's not current_label_chunk, then free it */ + if (!label_chunk->used_mask && (current_label_chunk != node)) { + if (lde_release_label_chunk(label_chunk->start, label_chunk->end) != 0) + log_warnx("%s: Error releasing label chunk!", __func__); + else { + listnode_delete(label_chunk_list, label_chunk); + lde_del_label_chunk(label_chunk); + } + } + break; + } + } + + return; +} + +static uint32_t +lde_get_next_label(void) +{ + struct label_chunk *label_chunk; + uint32_t i, size; + uint64_t pos; + uint32_t label = NO_LABEL; + + while (current_label_chunk) { + label_chunk = listgetdata(current_label_chunk); + if (!label_chunk) + goto end; + + /* try to get next free label in currently used label chunk */ + size = label_chunk->end - label_chunk->start + 1; + for (i = 0, pos = 1; i < size; i++, pos <<= 1) { + if (!(pos & label_chunk->used_mask)) { + SET_FLAG(label_chunk->used_mask, pos); + label = label_chunk->start + i; + goto end; + } + } + current_label_chunk = listnextnode(current_label_chunk); + } + +end: + /* we moved till the last chunk, or were not able to find a label, + so let's ask for another one */ + if (!current_label_chunk || + current_label_chunk == listtail(label_chunk_list) || + label == NO_LABEL) { + if (lde_get_label_chunk() != 0) + log_warn("%s: Error getting label chunk!", __func__); + + } + + return (label); +} + +static void +lde_check_filter_af(int af, struct ldpd_af_conf *af_conf, + const char *filter_name) +{ + if (strcmp(af_conf->acl_label_allocate_for, filter_name) == 0) + lde_change_allocate_filter(af); + + if ((strcmp(af_conf->acl_label_advertise_to, filter_name) == 0) + || (strcmp(af_conf->acl_label_advertise_for, filter_name) == 0)) + lde_change_advertise_filter(af); + + if ((strcmp(af_conf->acl_label_accept_for, filter_name) == 0) + || (strcmp(af_conf->acl_label_accept_from, filter_name) == 0)) + lde_change_accept_filter(af); + + if (strcmp(af_conf->acl_label_expnull_for, filter_name) == 0) + lde_change_expnull_for_filter(af); +} + +void lde_route_update(struct iface *iface, int af) +{ + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + struct lde_nbr *ln; + + /* update label of non-connected routes */ + log_debug("update labels for interface %s", iface->name); + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (IS_MPLS_UNRESERVED_LABEL(fn->local_label)) + continue; + + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + break; + default: + /* unspecified so process both address families */ + break; + } + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + /* + * If connected leave existing label. If LDP + * configured on interface or a static route + * may need new label. If no LDP configured + * treat fec as a connected route + */ + if (CHECK_FLAG(fnh->flags, F_FEC_NH_CONNECTED)) + break; + + if (fnh->ifindex != iface->ifindex) + continue; + + UNSET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + if (IS_MPLS_RESERVED_LABEL(fn->local_label)) { + fn->local_label = NO_LABEL; + fn->local_label = lde_update_label(fn); + if (fn->local_label != NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping( + ln, fn, 0); + } + break; + } + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, + 0, NULL, 0); +} + +void lde_route_update_release(struct iface *iface, int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + + /* update label of interfaces no longer running LDP */ + log_debug("release all labels for interface %s af %s", iface->name, + af == AF_INET ? "ipv4" : "ipv6"); + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + break; + default: + fatalx("lde_route_update_release: unknown af"); + } + + if (fn->local_label == NO_LABEL) + continue; + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + /* + * If connected leave existing label. If LDP + * removed from interface may need new label + * and would be treated as a connected route + */ + if (CHECK_FLAG(fnh->flags, F_FEC_NH_CONNECTED)) + break; + + if (fnh->ifindex != iface->ifindex) + continue; + + SET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelwithdraw(ln, fn, NULL, NULL); + lde_free_label(fn->local_label); + fn->local_label = NO_LABEL; + fn->local_label = lde_update_label(fn); + if (fn->local_label != NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 0); + break; + } + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, + 0, NULL, 0); +} + +void lde_route_update_release_all(int af) +{ + struct lde_nbr *ln; + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + + /* remove labels from all interfaces as LDP is no longer running for + * this address family + */ + log_debug("release all labels for address family %s", + af == AF_INET ? "ipv4" : "ipv6"); + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + switch (af) { + case AF_INET: + if (fn->fec.type != FEC_TYPE_IPV4) + continue; + break; + case AF_INET6: + if (fn->fec.type != FEC_TYPE_IPV6) + continue; + break; + default: + fatalx("lde_route_update_release: unknown af"); + } + + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelwithdraw(ln, fn, NULL, NULL); + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + SET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + lde_send_delete_klabel(fn, fnh); + } + } +} diff --git a/ldpd/lde.h b/ldpd/lde.h new file mode 100644 index 0000000..2688b6a --- /dev/null +++ b/ldpd/lde.h @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2004, 2005 Esben Norby + */ + +#ifndef _LDE_H_ +#define _LDE_H_ + +#include "queue.h" +#include "openbsd-tree.h" +#include "if.h" + +enum fec_type { + FEC_TYPE_IPV4, + FEC_TYPE_IPV6, + FEC_TYPE_PWID +}; + +struct fec { + RB_ENTRY(fec) entry; + enum fec_type type; + union { + struct { + struct in_addr prefix; + uint8_t prefixlen; + } ipv4; + struct { + struct in6_addr prefix; + uint8_t prefixlen; + } ipv6; + struct { + uint16_t type; + uint32_t pwid; + struct in_addr lsr_id; + } pwid; + } u; +}; +RB_HEAD(fec_tree, fec); +RB_PROTOTYPE(fec_tree, fec, entry, fec_compare) + +/* request entries */ +struct lde_req { + struct fec fec; + uint32_t msg_id; +}; + +/* mapping entries */ +struct lde_map { + struct fec fec; + struct lde_map_head *head; /* fec_node's upstream/downstream */ + RB_ENTRY(lde_map) entry; + struct lde_nbr *nexthop; + struct map map; +}; +RB_HEAD(lde_map_head, lde_map); +RB_PROTOTYPE(lde_map_head, lde_map, entry, lde_map_cmp); + +/* withdraw entries */ +struct lde_wdraw { + struct fec fec; + uint32_t label; +}; + +/* Addresses belonging to neighbor */ +struct lde_addr { + TAILQ_ENTRY(lde_addr) entry; + int af; + union ldpd_addr addr; +}; + +/* just the info LDE needs */ +struct lde_nbr { + RB_ENTRY(lde_nbr) entry; + uint32_t peerid; + struct in_addr id; + int v4_enabled; /* announce/process v4 msgs */ + int v6_enabled; /* announce/process v6 msgs */ + int flags; /* capabilities */ + struct fec_tree recv_req; + struct fec_tree sent_req; + struct fec_tree recv_map; + struct fec_tree sent_map; + struct fec_tree sent_map_pending; + struct fec_tree sent_wdraw; + TAILQ_HEAD(, lde_addr) addr_list; +}; +RB_HEAD(nbr_tree, lde_nbr); +RB_PROTOTYPE(nbr_tree, lde_nbr, entry, lde_nbr_compare) + +struct fec_nh { + LIST_ENTRY(fec_nh) entry; + int af; + union ldpd_addr nexthop; + ifindex_t ifindex; + uint32_t remote_label; + uint8_t route_type; + unsigned short route_instance; + uint8_t flags; +}; +#define F_FEC_NH_NEW 0x01 +#define F_FEC_NH_CONNECTED 0x02 +#define F_FEC_NH_DEFER 0x04 /* running ordered control */ +#define F_FEC_NH_NO_LDP 0x08 /* no ldp on this interface */ + +struct fec_node { + struct fec fec; + + LIST_HEAD(, fec_nh) nexthops; /* fib nexthops */ + struct lde_map_head downstream; /* recv mappings */ + struct lde_map_head upstream; /* sent mappings */ + + uint32_t local_label; + + uint32_t pw_remote_status; + + void *data; /* fec specific data */ +}; + +#define CHUNK_SIZE 64 +struct label_chunk { + uint32_t start; + uint32_t end; + uint64_t used_mask; +}; + +#define LDE_GC_INTERVAL 300 + +extern struct ldpd_conf *ldeconf; +extern struct fec_tree ft; +extern struct nbr_tree lde_nbrs; +extern struct event *gc_timer; + +/* lde.c */ +void lde(void); +void lde_init(struct ldpd_init *); +int lde_imsg_compose_parent(int, pid_t, void *, uint16_t); +void lde_imsg_compose_parent_sync(int, pid_t, void *, uint16_t); +int lde_imsg_compose_ldpe(int, uint32_t, pid_t, void *, uint16_t); +int lde_acl_check(char *, int, union ldpd_addr *, uint8_t); +uint32_t lde_update_label(struct fec_node *); +void lde_free_label(uint32_t label); +void lde_send_change_klabel(struct fec_node *, struct fec_nh *); +void lde_send_delete_klabel(struct fec_node *, struct fec_nh *); +void lde_fec2prefix(const struct fec *fec, struct prefix *prefix); +void lde_prefix2fec(const struct prefix *prefix, struct fec *fec); +void lde_fec2map(struct fec *, struct map *); +void lde_map2fec(struct map *, struct in_addr, struct fec *); +void lde_send_labelmapping(struct lde_nbr *, struct fec_node *, + int); +void lde_send_labelwithdraw(struct lde_nbr *, struct fec_node *, + struct map *, struct status_tlv *); +void lde_send_labelwithdraw_wcard(struct lde_nbr *, uint32_t); +void lde_send_labelwithdraw_twcard_prefix(struct lde_nbr *, + uint16_t, uint32_t); +void lde_send_labelwithdraw_twcard_pwid(struct lde_nbr *, uint16_t, + uint32_t); +void lde_send_labelwithdraw_pwid_wcard(struct lde_nbr *, uint16_t, + uint32_t); +void lde_send_labelrelease(struct lde_nbr *, struct fec_node *, + struct map *, uint32_t); +void lde_send_labelrequest(struct lde_nbr *, struct fec_node *, + struct map *, int); +void lde_send_labelrequest_wcard(struct lde_nbr *, uint16_t af); +void lde_send_notification(struct lde_nbr *, uint32_t, uint32_t, + uint16_t); +void lde_send_notification_eol_prefix(struct lde_nbr *, int); +void lde_send_notification_eol_pwid(struct lde_nbr *, uint16_t); +struct lde_nbr *lde_nbr_find_by_lsrid(struct in_addr); +struct lde_nbr *lde_nbr_find_by_addr(int, union ldpd_addr *); +struct lde_map *lde_map_add(struct lde_nbr *, struct fec_node *, int); +void lde_map_del(struct lde_nbr *, struct lde_map *, int); +struct fec *lde_map_pending_add(struct lde_nbr *, struct fec_node *); +void lde_map_pending_del(struct lde_nbr *, struct fec *); +struct lde_req *lde_req_add(struct lde_nbr *, struct fec *, int); +void lde_req_del(struct lde_nbr *, struct lde_req *, int); +struct lde_wdraw *lde_wdraw_add(struct lde_nbr *, struct fec_node *); +void lde_wdraw_del(struct lde_nbr *, struct lde_wdraw *); +void lde_change_egress_label(int); +void lde_change_allocate_filter(int); +void lde_change_advertise_filter(int); +void lde_change_accept_filter(int); +void lde_change_expnull_for_filter(int); +void lde_route_update(struct iface *, int); +void lde_route_update_release(struct iface *, int); +void lde_route_update_release_all(int); +struct lde_addr *lde_address_find(struct lde_nbr *, int, + union ldpd_addr *); +void lde_allow_broken_lsp_update(int new_config); + +/* lde_lib.c */ +void fec_init(struct fec_tree *); +struct fec *fec_find(struct fec_tree *, struct fec *); +int fec_insert(struct fec_tree *, struct fec *); +int fec_remove(struct fec_tree *, struct fec *); +void fec_clear(struct fec_tree *, void (*)(void *)); +void rt_dump(pid_t); +void fec_snap(struct lde_nbr *); +void fec_tree_clear(void); +struct fec_nh *fec_nh_find(struct fec_node *, int, union ldpd_addr *, + ifindex_t, uint8_t, unsigned short); +void lde_kernel_insert(struct fec *, int, union ldpd_addr *, + ifindex_t, uint8_t, unsigned short, int, void *); +void lde_kernel_remove(struct fec *, int, union ldpd_addr *, + ifindex_t, uint8_t, unsigned short); +void lde_kernel_update(struct fec *); +void lde_check_mapping(struct map *, struct lde_nbr *, int); +void lde_check_request(struct map *, struct lde_nbr *); +void lde_check_request_wcard(struct map *, struct lde_nbr *); +void lde_check_release(struct map *, struct lde_nbr *); +void lde_check_release_wcard(struct map *, struct lde_nbr *); +void lde_check_withdraw(struct map *, struct lde_nbr *); +void lde_check_withdraw_wcard(struct map *, struct lde_nbr *); +int lde_wildcard_apply(struct map *, struct fec *, + struct lde_map *); +void lde_gc_timer(struct event *thread); +void lde_gc_start_timer(void); +void lde_gc_stop_timer(void); + +/* l2vpn.c */ +struct l2vpn *l2vpn_new(const char *); +struct l2vpn *l2vpn_find(struct ldpd_conf *, const char *); +void l2vpn_del(struct l2vpn *); +void l2vpn_init(struct l2vpn *); +void l2vpn_exit(struct l2vpn *); +struct l2vpn_if *l2vpn_if_new(struct l2vpn *, const char *); +struct l2vpn_if *l2vpn_if_find(struct l2vpn *, const char *); +void l2vpn_if_update_info(struct l2vpn_if *, struct kif *); +void l2vpn_if_update(struct l2vpn_if *); +struct l2vpn_pw *l2vpn_pw_new(struct l2vpn *, const char *); +struct l2vpn_pw *l2vpn_pw_find(struct l2vpn *, const char *); +struct l2vpn_pw *l2vpn_pw_find_active(struct l2vpn *, const char *); +struct l2vpn_pw *l2vpn_pw_find_inactive(struct l2vpn *, const char *); +void l2vpn_pw_update_info(struct l2vpn_pw *, struct kif *); +void l2vpn_pw_init(struct l2vpn_pw *); +void l2vpn_pw_exit(struct l2vpn_pw *); +void l2vpn_pw_reset(struct l2vpn_pw *); +int l2vpn_pw_ok(struct l2vpn_pw *, struct fec_nh *); +int l2vpn_pw_negotiate(struct lde_nbr *, struct fec_node *, + struct map *); +void l2vpn_send_pw_status(struct lde_nbr *, uint32_t, struct fec *); +void l2vpn_send_pw_status_wcard(struct lde_nbr *, uint32_t, + uint16_t, uint32_t); +void l2vpn_recv_pw_status(struct lde_nbr *, struct notify_msg *); +void l2vpn_recv_pw_status_wcard(struct lde_nbr *, + struct notify_msg *); +int l2vpn_pw_status_update(struct zapi_pw_status *); +void l2vpn_pw_ctl(pid_t); +void l2vpn_binding_ctl(pid_t); + +#endif /* _LDE_H_ */ diff --git a/ldpd/lde_lib.c b/ldpd/lde_lib.c new file mode 100644 index 0000000..04bff90 --- /dev/null +++ b/ldpd/lde_lib.c @@ -0,0 +1,1055 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" +#include "rlfa.h" + +#include "mpls.h" + +static __inline int fec_compare(const struct fec *, const struct fec *); +static int lde_nbr_is_nexthop(struct fec_node *, struct lde_nbr *); +static void fec_free(void *); +static struct fec_node *fec_add(struct fec *fec); +static struct fec_nh *fec_nh_add(struct fec_node *, int, union ldpd_addr *, + ifindex_t, uint8_t, unsigned short); +static void fec_nh_del(struct fec_nh *); + +RB_GENERATE(fec_tree, fec, entry, fec_compare) + +struct fec_tree ft = RB_INITIALIZER(&ft); +struct event *gc_timer; + +/* FEC tree functions */ +void +fec_init(struct fec_tree *fh) +{ + RB_INIT(fec_tree, fh); +} + +static __inline int +fec_compare(const struct fec *a, const struct fec *b) +{ + if (a->type < b->type) + return (-1); + if (a->type > b->type) + return (1); + + switch (a->type) { + case FEC_TYPE_IPV4: + if (ntohl(a->u.ipv4.prefix.s_addr) < ntohl(b->u.ipv4.prefix.s_addr)) + return (-1); + if (ntohl(a->u.ipv4.prefix.s_addr) > ntohl(b->u.ipv4.prefix.s_addr)) + return (1); + if (a->u.ipv4.prefixlen < b->u.ipv4.prefixlen) + return (-1); + if (a->u.ipv4.prefixlen > b->u.ipv4.prefixlen) + return (1); + return (0); + case FEC_TYPE_IPV6: + if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix, + sizeof(struct in6_addr)) < 0) + return (-1); + if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix, + sizeof(struct in6_addr)) > 0) + return (1); + if (a->u.ipv6.prefixlen < b->u.ipv6.prefixlen) + return (-1); + if (a->u.ipv6.prefixlen > b->u.ipv6.prefixlen) + return (1); + return (0); + case FEC_TYPE_PWID: + if (a->u.pwid.type < b->u.pwid.type) + return (-1); + if (a->u.pwid.type > b->u.pwid.type) + return (1); + if (a->u.pwid.pwid < b->u.pwid.pwid) + return (-1); + if (a->u.pwid.pwid > b->u.pwid.pwid) + return (1); + if (ntohl(a->u.pwid.lsr_id.s_addr) < ntohl(b->u.pwid.lsr_id.s_addr)) + return (-1); + if (ntohl(a->u.pwid.lsr_id.s_addr) > ntohl(b->u.pwid.lsr_id.s_addr)) + return (1); + return (0); + } + + return (-1); +} + +struct fec * +fec_find(struct fec_tree *fh, struct fec *f) +{ + return (RB_FIND(fec_tree, fh, f)); +} + +int +fec_insert(struct fec_tree *fh, struct fec *f) +{ + if (RB_INSERT(fec_tree, fh, f) != NULL) + return (-1); + return (0); +} + +int +fec_remove(struct fec_tree *fh, struct fec *f) +{ + if (RB_REMOVE(fec_tree, fh, f) == NULL) { + log_warnx("%s failed for %s", __func__, log_fec(f)); + return (-1); + } + return (0); +} + +void +fec_clear(struct fec_tree *fh, void (*free_cb)(void *)) +{ + struct fec *f; + + while (!RB_EMPTY(fec_tree, fh)) { + f = RB_ROOT(fec_tree, fh); + + fec_remove(fh, f); + free_cb(f); + } +} + +/* routing table functions */ +static int +lde_nbr_is_nexthop(struct fec_node *fn, struct lde_nbr *ln) +{ + struct fec_nh *fnh; + + LIST_FOREACH(fnh, &fn->nexthops, entry) + if (lde_address_find(ln, fnh->af, &fnh->nexthop)) + return (1); + + return (0); +} + +void +rt_dump(pid_t pid) +{ + struct fec *f; + struct fec_node *fn; + struct lde_map *me; + static struct ctl_rt rtctl; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (fn->local_label == NO_LABEL && + RB_EMPTY(lde_map_head, &fn->downstream)) + continue; + + memset(&rtctl, 0, sizeof(rtctl)); + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + rtctl.af = AF_INET; + rtctl.prefix.v4 = fn->fec.u.ipv4.prefix; + rtctl.prefixlen = fn->fec.u.ipv4.prefixlen; + break; + case FEC_TYPE_IPV6: + rtctl.af = AF_INET6; + rtctl.prefix.v6 = fn->fec.u.ipv6.prefix; + rtctl.prefixlen = fn->fec.u.ipv6.prefixlen; + break; + case FEC_TYPE_PWID: + continue; + } + + rtctl.local_label = fn->local_label; + if (RB_EMPTY(lde_map_head, &fn->downstream)) { + rtctl.in_use = 0; + rtctl.nexthop.s_addr = INADDR_ANY; + rtctl.remote_label = NO_LABEL; + rtctl.no_downstream = 1; + } + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_BEGIN, 0, pid, &rtctl, + sizeof(rtctl)); + + RB_FOREACH(me, lde_map_head, &fn->upstream) { + rtctl.nexthop = me->nexthop->id; + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_SENT, 0, pid, + &rtctl, sizeof(rtctl)); + } + + RB_FOREACH(me, lde_map_head, &fn->downstream) { + rtctl.in_use = lde_nbr_is_nexthop(fn, me->nexthop); + rtctl.nexthop = me->nexthop->id; + rtctl.remote_label = me->map.label; + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_RCVD, 0, pid, + &rtctl, sizeof(rtctl)); + } + lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB_END, 0, pid, &rtctl, + sizeof(rtctl)); + } +} + +void +fec_snap(struct lde_nbr *ln) +{ + struct fec *f; + struct fec_node *fn; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + if (fn->local_label == NO_LABEL) + continue; + + lde_send_labelmapping(ln, fn, 0); + } + + lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0); +} + +static void +fec_free(void *arg) +{ + struct fec_node *fn = arg; + struct fec_nh *fnh; + + while ((fnh = LIST_FIRST(&fn->nexthops))) { + fec_nh_del(fnh); + assert(fnh != LIST_FIRST(&fn->nexthops)); + } + if (!RB_EMPTY(lde_map_head, &fn->downstream)) + log_warnx("%s: fec %s downstream list not empty", __func__, + log_fec(&fn->fec)); + if (!RB_EMPTY(lde_map_head, &fn->upstream)) + log_warnx("%s: fec %s upstream list not empty", __func__, + log_fec(&fn->fec)); + + free(fn); +} + +void +fec_tree_clear(void) +{ + fec_clear(&ft, fec_free); +} + +static struct fec_node * +fec_add(struct fec *fec) +{ + struct fec_node *fn; + + fn = calloc(1, sizeof(*fn)); + if (fn == NULL) + fatal(__func__); + + fn->fec = *fec; + fn->local_label = NO_LABEL; + RB_INIT(lde_map_head, &fn->upstream); + RB_INIT(lde_map_head, &fn->downstream); + LIST_INIT(&fn->nexthops); + + if (fec->type == FEC_TYPE_PWID) + fn->pw_remote_status = PW_FORWARDING; + + if (fec_insert(&ft, &fn->fec)) + log_warnx("failed to add %s to ft tree", log_fec(&fn->fec)); + + return (fn); +} + +struct fec_nh * +fec_nh_find(struct fec_node *fn, int af, union ldpd_addr *nexthop, + ifindex_t ifindex, uint8_t route_type, unsigned short route_instance) +{ + struct fec_nh *fnh; + + LIST_FOREACH(fnh, &fn->nexthops, entry) + if (fnh->af == af && + ldp_addrcmp(af, &fnh->nexthop, nexthop) == 0 && + fnh->ifindex == ifindex && + fnh->route_type == route_type && + fnh->route_instance == route_instance) + return (fnh); + + return (NULL); +} + +static struct fec_nh * +fec_nh_add(struct fec_node *fn, int af, union ldpd_addr *nexthop, + ifindex_t ifindex, uint8_t route_type, unsigned short route_instance) +{ + struct fec_nh *fnh; + + fnh = calloc(1, sizeof(*fnh)); + if (fnh == NULL) + fatal(__func__); + + fnh->af = af; + fnh->nexthop = *nexthop; + fnh->ifindex = ifindex; + fnh->remote_label = NO_LABEL; + fnh->route_type = route_type; + fnh->route_instance = route_instance; + LIST_INSERT_HEAD(&fn->nexthops, fnh, entry); + + return (fnh); +} + +static void +fec_nh_del(struct fec_nh *fnh) +{ + LIST_REMOVE(fnh, entry); + free(fnh); +} + +void +lde_kernel_insert(struct fec *fec, int af, union ldpd_addr *nexthop, + ifindex_t ifindex, uint8_t route_type, unsigned short route_instance, + int connected, void *data) +{ + struct fec_node *fn; + struct fec_nh *fnh; + struct iface *iface; + + fn = (struct fec_node *)fec_find(&ft, fec); + if (fn == NULL) + fn = fec_add(fec); + if (data) + fn->data = data; + + fnh = fec_nh_find(fn, af, nexthop, ifindex, route_type, route_instance); + if (fnh == NULL) { + fnh = fec_nh_add(fn, af, nexthop, ifindex, route_type, + route_instance); + /* + * Ordered Control: if not a connected route and not a route + * learned over an interface not running LDP and not a PW + * then mark to wait until we receive labelmap msg before + * installing in kernel and sending to peer + */ + iface = if_lookup(ldeconf, ifindex); + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL) && + !connected && iface != NULL && fec->type != FEC_TYPE_PWID) + SET_FLAG(fnh->flags, F_FEC_NH_DEFER); + } + + SET_FLAG(fnh->flags, F_FEC_NH_NEW); + if (connected) + SET_FLAG(fnh->flags, F_FEC_NH_CONNECTED); +} + +void +lde_kernel_remove(struct fec *fec, int af, union ldpd_addr *nexthop, + ifindex_t ifindex, uint8_t route_type, unsigned short route_instance) +{ + struct fec_node *fn; + struct fec_nh *fnh; + + fn = (struct fec_node *)fec_find(&ft, fec); + if (fn == NULL) + /* route lost */ + return; + fnh = fec_nh_find(fn, af, nexthop, ifindex, route_type, route_instance); + if (fnh == NULL) + /* route lost */ + return; + + lde_send_delete_klabel(fn, fnh); + fec_nh_del(fnh); +} + +/* + * Whenever a route is changed, zebra advertises its new version without + * withdrawing the old one. So, after processing a ZEBRA_REDISTRIBUTE_IPV[46]_ADD + * message, we need to check for nexthops that were removed and, for each of + * them (if any), withdraw the associated labels from zebra. + */ +void +lde_kernel_update(struct fec *fec) +{ + struct fec_node *fn; + struct fec_nh *fnh, *safe; + struct lde_nbr *ln; + struct lde_map *me; + struct iface *iface; + + fn = (struct fec_node *)fec_find(&ft, fec); + if (fn == NULL) + return; + + LIST_FOREACH_SAFE(fnh, &fn->nexthops, entry, safe) { + if (CHECK_FLAG(fnh->flags, F_FEC_NH_NEW)) { + UNSET_FLAG(fnh->flags, F_FEC_NH_NEW); + /* + * if LDP configured on interface or a static route + * clear flag else treat fec as a connected route + */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ENABLED)) { + iface = if_lookup(ldeconf,fnh->ifindex); + if (CHECK_FLAG(fnh->flags, F_FEC_NH_CONNECTED) || + iface || + fnh->route_type == ZEBRA_ROUTE_STATIC) + UNSET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + else + SET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + } else + SET_FLAG(fnh->flags, F_FEC_NH_NO_LDP); + } else { + lde_send_delete_klabel(fn, fnh); + fec_nh_del(fnh); + } + } + + if (LIST_EMPTY(&fn->nexthops)) { + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelwithdraw(ln, fn, NULL, NULL); + fn->data = NULL; + + /* + * Do not deallocate the local label now, do that only in the + * LIB garbage collector. This will prevent ldpd from changing + * the input label of some prefixes too often when running on + * an unstable network. Also, restart the garbage collector + * timer so that labels are deallocated only when the network + * is stabilized. + */ + lde_gc_start_timer(); + } else { + fn->local_label = lde_update_label(fn); + if (fn->local_label != NO_LABEL) + /* FEC.1: perform lsr label distribution procedure */ + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 1); + } + + /* if no label created yet then don't try to program labeled route */ + if (fn->local_label == NO_LABEL) + return; + + LIST_FOREACH(fnh, &fn->nexthops, entry) { + lde_send_change_klabel(fn, fnh); + + switch (fn->fec.type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + ln = lde_nbr_find_by_addr(fnh->af, &fnh->nexthop); + break; + case FEC_TYPE_PWID: + ln = lde_nbr_find_by_lsrid(fn->fec.u.pwid.lsr_id); + break; + default: + ln = NULL; + break; + } + + if (ln) { + /* FEC.2 */ + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + if (me) + /* FEC.5 */ + lde_check_mapping(&me->map, ln, 0); + } + } +} + +void +lde_check_mapping(struct map *map, struct lde_nbr *ln, int rcvd_label_mapping) +{ + struct fec fec; + struct fec_node *fn; + struct fec_nh *fnh; + struct lde_req *lre; + struct lde_map *me; + struct l2vpn_pw *pw; + bool send_map = false; + + lde_map2fec(map, ln->id, &fec); + + switch (fec.type) { + case FEC_TYPE_IPV4: + if (lde_acl_check(ldeconf->ipv4.acl_label_accept_from, + AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) + return; + if (lde_acl_check(ldeconf->ipv4.acl_label_accept_for, + AF_INET, (union ldpd_addr *)&fec.u.ipv4.prefix, + fec.u.ipv4.prefixlen) != FILTER_PERMIT) + return; + break; + case FEC_TYPE_IPV6: + if (lde_acl_check(ldeconf->ipv6.acl_label_accept_from, + AF_INET, (union ldpd_addr *)&ln->id, 32) != FILTER_PERMIT) + return; + if (lde_acl_check(ldeconf->ipv6.acl_label_accept_for, + AF_INET6, (union ldpd_addr *)&fec.u.ipv6.prefix, + fec.u.ipv6.prefixlen) != FILTER_PERMIT) + return; + break; + case FEC_TYPE_PWID: + break; + } + + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn == NULL) + fn = fec_add(&fec); + + /* LMp.1: first check if we have a pending request running */ + lre = (struct lde_req *)fec_find(&ln->sent_req, &fn->fec); + if (lre) + /* LMp.2: delete record of outstanding label request */ + lde_req_del(ln, lre, 1); + + /* RFC 4447 control word and status tlv negotiation */ + if (map->type == MAP_TYPE_PWID && l2vpn_pw_negotiate(ln, fn, map)) { + if (rcvd_label_mapping && CHECK_FLAG(map->flags, F_MAP_PW_STATUS)) + fn->pw_remote_status = map->pw_status; + + return; + } + + /* + * LMp.3 - LMp.8: loop detection - unnecessary for frame-mode + * mpls networks. + */ + + /* LMp.9 */ + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + if (me) { + /* LMp.10 */ + if (me->map.label != map->label && lre == NULL) { + /* LMp.10a */ + lde_send_labelrelease(ln, fn, NULL, me->map.label); + + /* + * Can not use lde_nbr_find_by_addr() because there's + * the possibility of multipath. + */ + LIST_FOREACH(fnh, &fn->nexthops, entry) { + if (lde_address_find(ln, fnh->af, &fnh->nexthop) == NULL) + continue; + + lde_send_delete_klabel(fn, fnh); + fnh->remote_label = NO_LABEL; + } + } + } + + /* + * LMp.11 - 12: consider multiple nexthops in order to + * support multipath + */ + LIST_FOREACH(fnh, &fn->nexthops, entry) { + /* LMp.15: install FEC in FIB */ + switch (fec.type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) + continue; + + /* + * Ordered Control: labelmap msg received from + * NH so clear flag and send labelmap msg to + * peer + */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL)) { + send_map = true; + UNSET_FLAG(fnh->flags, F_FEC_NH_DEFER); + } + fnh->remote_label = map->label; + if (fn->local_label != NO_LABEL) + lde_send_change_klabel(fn, fnh); + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL) + continue; + + pw->remote_group = map->fec.pwid.group_id; + if (CHECK_FLAG(map->flags, F_MAP_PW_IFMTU)) + pw->remote_mtu = map->fec.pwid.ifmtu; + if (rcvd_label_mapping && CHECK_FLAG(map->flags, F_MAP_PW_STATUS)) { + pw->remote_status = map->pw_status; + fn->pw_remote_status = map->pw_status; + } + else + pw->remote_status = PW_FORWARDING; + fnh->remote_label = map->label; + if (l2vpn_pw_ok(pw, fnh)) + lde_send_change_klabel(fn, fnh); + break; + default: + break; + } + } + + /* Update RLFA clients. */ + lde_rlfa_update_clients(&fec, ln, map->label); + + /* LMp.13 & LMp.16: Record the mapping from this peer */ + if (me == NULL) + me = lde_map_add(ln, fn, 0); + me->map = *map; + + /* + * LMp.17 - LMp.27 are unnecessary since we don't need to implement + * loop detection. LMp.28 - LMp.30 are unnecessary because we are + * merging capable. + */ + + /* + * Ordered Control: just received a labelmap for this fec from NH so + * need to send labelmap to all peers + * LMp.20 - LMp21 Execute procedure to send Label Mapping + */ + if (send_map && fn->local_label != NO_LABEL) + RB_FOREACH(ln, nbr_tree, &lde_nbrs) + lde_send_labelmapping(ln, fn, 1); +} + +void +lde_check_request(struct map *map, struct lde_nbr *ln) +{ + struct fec fec; + struct lde_req *lre; + struct fec_node *fn; + struct fec_nh *fnh; + + /* wildcard label request */ + if (map->type == MAP_TYPE_TYPED_WCARD) { + lde_check_request_wcard(map, ln); + return; + } + + /* LRq.1: skip loop detection (not necessary) */ + + /* LRq.2: is there a next hop for fec? */ + lde_map2fec(map, ln->id, &fec); + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn == NULL || LIST_EMPTY(&fn->nexthops)) { + /* LRq.5: send No Route notification */ + lde_send_notification(ln, S_NO_ROUTE, map->msg_id, + htons(MSG_TYPE_LABELREQUEST)); + return; + } + + /* LRq.3: is MsgSource the next hop? */ + LIST_FOREACH(fnh, &fn->nexthops, entry) { + switch (fec.type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) + continue; + + /* LRq.4: send Loop Detected notification */ + lde_send_notification(ln, S_LOOP_DETECTED, map->msg_id, + htons(MSG_TYPE_LABELREQUEST)); + return; + case FEC_TYPE_PWID: + break; + } + } + + /* LRq.6: first check if we have a pending request running */ + lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); + if (lre != NULL) + /* LRq.7: duplicate request */ + return; + + /* LRq.8: record label request */ + lre = lde_req_add(ln, &fn->fec, 0); + if (lre != NULL) + lre->msg_id = ntohl(map->msg_id); + + /* LRq.9: perform LSR label distribution */ + lde_send_labelmapping(ln, fn, 1); + + /* + * LRq.10: do nothing (Request Never) since we use liberal + * label retention. + * LRq.11 - 12 are unnecessary since we are merging capable. + */ +} + +void +lde_check_request_wcard(struct map *map, struct lde_nbr *ln) +{ + struct fec *f; + struct fec_node *fn; + struct lde_req *lre; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + + /* only a typed wildcard is possible here */ + if (lde_wildcard_apply(map, &fn->fec, NULL) == 0) + continue; + + /* LRq.2: is there a next hop for fec? */ + if (LIST_EMPTY(&fn->nexthops)) + continue; + + /* LRq.6: first check if we have a pending request running */ + lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec); + if (lre != NULL) + /* LRq.7: duplicate request */ + continue; + + /* LRq.8: record label request */ + lre = lde_req_add(ln, &fn->fec, 0); + if (lre != NULL) + lre->msg_id = ntohl(map->msg_id); + + /* LRq.9: perform LSR label distribution */ + lde_send_labelmapping(ln, fn, 1); + } +} + +void +lde_check_release(struct map *map, struct lde_nbr *ln) +{ + struct fec fec; + struct fec_node *fn; + struct lde_wdraw *lw; + struct lde_map *me; + struct fec *pending_map; + + /* wildcard label release */ + if (map->type == MAP_TYPE_WILDCARD || + map->type == MAP_TYPE_TYPED_WCARD || + (map->type == MAP_TYPE_PWID && !CHECK_FLAG(map->flags, F_MAP_PW_ID))) { + lde_check_release_wcard(map, ln); + return; + } + + lde_map2fec(map, ln->id, &fec); + fn = (struct fec_node *)fec_find(&ft, &fec); + /* LRl.1: does FEC match a known FEC? */ + if (fn == NULL) + return; + + /* LRl.6: check sent map list and remove it if available */ + me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); + if (me && (map->label == NO_LABEL || map->label == me->map.label)) + lde_map_del(ln, me, 1); + + /* LRl.3: first check if we have a pending withdraw running */ + lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); + if (lw && (map->label == NO_LABEL || map->label == lw->label)) { + /* LRl.4: delete record of outstanding label withdraw */ + lde_wdraw_del(ln, lw); + + /* send pending label mapping if any */ + pending_map = fec_find(&ln->sent_map_pending, &fn->fec); + if (pending_map) { + lde_send_labelmapping(ln, fn, 1); + lde_map_pending_del(ln, pending_map); + } + } + + /* + * LRl.11 - 13 are unnecessary since we remove the label from + * forwarding/switching as soon as the FEC is unreachable. + */ +} + +void +lde_check_release_wcard(struct map *map, struct lde_nbr *ln) +{ + struct fec *f; + struct fec_node *fn; + struct lde_wdraw *lw; + struct lde_map *me; + struct fec *pending_map; + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec); + + /* LRl.1: does FEC match a known FEC? */ + if (lde_wildcard_apply(map, &fn->fec, me) == 0) + continue; + + /* LRl.6: check sent map list and remove it if available */ + if (me && + (map->label == NO_LABEL || map->label == me->map.label)) + lde_map_del(ln, me, 1); + + /* LRl.3: first check if we have a pending withdraw running */ + lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec); + if (lw && (map->label == NO_LABEL || map->label == lw->label)) { + /* LRl.4: delete record of outstanding lbl withdraw */ + lde_wdraw_del(ln, lw); + + /* send pending label mapping if any */ + pending_map = fec_find(&ln->sent_map_pending, &fn->fec); + if (pending_map) { + lde_send_labelmapping(ln, fn, 1); + lde_map_pending_del(ln, pending_map); + } + } + + /* + * LRl.11 - 13 are unnecessary since we remove the label from + * forwarding/switching as soon as the FEC is unreachable. + */ + } +} + +void +lde_check_withdraw(struct map *map, struct lde_nbr *ln) +{ + struct fec fec; + struct fec_node *fn; + struct fec_nh *fnh; + struct lde_map *me; + struct l2vpn_pw *pw; + struct lde_nbr *lnbr; + + /* wildcard label withdraw */ + if (map->type == MAP_TYPE_WILDCARD || + map->type == MAP_TYPE_TYPED_WCARD || + (map->type == MAP_TYPE_PWID && !CHECK_FLAG(map->flags, F_MAP_PW_ID))) { + lde_check_withdraw_wcard(map, ln); + return; + } + + lde_map2fec(map, ln->id, &fec); + fn = (struct fec_node *)fec_find(&ft, &fec); + if (fn == NULL) + fn = fec_add(&fec); + + /* LWd.1: remove label from forwarding/switching use */ + LIST_FOREACH(fnh, &fn->nexthops, entry) { + switch (fec.type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) + continue; + break; + case FEC_TYPE_PWID: + pw = (struct l2vpn_pw *) fn->data; + if (pw == NULL) + continue; + pw->remote_status = PW_NOT_FORWARDING; + break; + default: + break; + } + if (map->label != NO_LABEL && map->label != fnh->remote_label) + continue; + + lde_send_delete_klabel(fn, fnh); + fnh->remote_label = NO_LABEL; + } + + /* Update RLFA clients. */ + lde_rlfa_update_clients(&fec, ln, MPLS_INVALID_LABEL); + + /* LWd.2: send label release */ + lde_send_labelrelease(ln, fn, NULL, map->label); + + /* LWd.3: check previously received label mapping */ + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + if (me && (map->label == NO_LABEL || map->label == me->map.label)) + /* LWd.4: remove record of previously received lbl mapping */ + lde_map_del(ln, me, 0); + else + /* LWd.13 done */ + return; + + /* Ordered Control: additional withdraw steps */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL)) { + /* LWd.8: for each neighbor other that src of withdraw msg */ + RB_FOREACH(lnbr, nbr_tree, &lde_nbrs) { + if (ln->peerid == lnbr->peerid) + continue; + + /* LWd.9: check if previously sent a label mapping */ + me = (struct lde_map *)fec_find(&lnbr->sent_map, &fn->fec); + + /* + * LWd.10: does label sent to peer "map" to withdraw + * label + */ + if (me && lde_nbr_is_nexthop(fn, lnbr)) + /* LWd.11: send label withdraw */ + lde_send_labelwithdraw(lnbr, fn, NULL, NULL); + } + } + +} + +void +lde_check_withdraw_wcard(struct map *map, struct lde_nbr *ln) +{ + struct fec *f; + struct fec_node *fn; + struct fec_nh *fnh; + struct lde_map *me; + struct l2vpn_pw *pw; + struct lde_nbr *lnbr; + + /* LWd.2: send label release */ + lde_send_labelrelease(ln, NULL, map, map->label); + + RB_FOREACH(f, fec_tree, &ft) { + fn = (struct fec_node *)f; + me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec); + + if (lde_wildcard_apply(map, &fn->fec, me) == 0) + continue; + + /* LWd.1: remove label from forwarding/switching use */ + LIST_FOREACH(fnh, &fn->nexthops, entry) { + switch (f->type) { + case FEC_TYPE_IPV4: + case FEC_TYPE_IPV6: + if (!lde_address_find(ln, fnh->af, &fnh->nexthop)) + continue; + break; + case FEC_TYPE_PWID: + if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr) + continue; + pw = (struct l2vpn_pw *) fn->data; + if (pw) + pw->remote_status = PW_NOT_FORWARDING; + break; + default: + break; + } + if (map->label != NO_LABEL && map->label != fnh->remote_label) + continue; + + lde_send_delete_klabel(fn, fnh); + fnh->remote_label = NO_LABEL; + } + + /* Update RLFA clients. */ + lde_rlfa_update_clients(f, ln, MPLS_INVALID_LABEL); + + /* LWd.3: check previously received label mapping */ + if (me && (map->label == NO_LABEL || map->label == me->map.label)) + /* + * LWd.4: remove record of previously received + * label mapping + */ + lde_map_del(ln, me, 0); + else + /* LWd.13 done */ + continue; + + /* Ordered Control: additional withdraw steps */ + if (CHECK_FLAG(ldeconf->flags, F_LDPD_ORDERED_CONTROL)) { + /* + * LWd.8: for each neighbor other that src of + * withdraw msg + */ + RB_FOREACH(lnbr, nbr_tree, &lde_nbrs) { + if (ln->peerid == lnbr->peerid) + continue; + + /* LWd.9: check if previously sent a label + * mapping + */ + me = (struct lde_map *)fec_find(&lnbr->sent_map, &fn->fec); + /* + * LWd.10: does label sent to peer "map" to + * withdraw label + */ + if (me && lde_nbr_is_nexthop(fn, lnbr)) + /* LWd.11: send label withdraw */ + lde_send_labelwithdraw(lnbr, fn, NULL, NULL); + } + } + } +} + +int +lde_wildcard_apply(struct map *wcard, struct fec *fec, struct lde_map *me) +{ + switch (wcard->type) { + case MAP_TYPE_WILDCARD: + /* full wildcard */ + return (1); + case MAP_TYPE_TYPED_WCARD: + switch (wcard->fec.twcard.type) { + case MAP_TYPE_PREFIX: + if (wcard->fec.twcard.u.prefix_af == AF_INET && + fec->type != FEC_TYPE_IPV4) + return (0); + if (wcard->fec.twcard.u.prefix_af == AF_INET6 && + fec->type != FEC_TYPE_IPV6) + return (0); + return (1); + case MAP_TYPE_PWID: + if (fec->type != FEC_TYPE_PWID) + return (0); + if (wcard->fec.twcard.u.pw_type != PW_TYPE_WILDCARD && + wcard->fec.twcard.u.pw_type != fec->u.pwid.type) + return (0); + return (1); + default: + fatalx("lde_wildcard_apply: unexpected fec type"); + } + break; + case MAP_TYPE_PWID: + /* RFC4447 pw-id group wildcard */ + if (fec->type != FEC_TYPE_PWID) + return (0); + if (fec->u.pwid.type != wcard->fec.pwid.type) + return (0); + if (me == NULL || (me->map.fec.pwid.group_id != + wcard->fec.pwid.group_id)) + return (0); + return (1); + default: + fatalx("lde_wildcard_apply: unexpected fec type"); + } +} + +/* gabage collector timer: timer to remove dead entries from the LIB */ + +/* ARGSUSED */ +void lde_gc_timer(struct event *thread) +{ + struct fec *fec, *safe; + struct fec_node *fn; + int count = 0; + + RB_FOREACH_SAFE(fec, fec_tree, &ft, safe) { + fn = (struct fec_node *) fec; + + if (!LIST_EMPTY(&fn->nexthops) || + !RB_EMPTY(lde_map_head, &fn->downstream) || + !RB_EMPTY(lde_map_head, &fn->upstream)) + continue; + + if (fn->local_label != NO_LABEL) + lde_free_label(fn->local_label); + + fec_remove(&ft, &fn->fec); + free(fn); + count++; + } + + if (count > 0) + log_debug("%s: %u entries removed", __func__, count); + + lde_gc_start_timer(); +} + +void +lde_gc_start_timer(void) +{ + EVENT_OFF(gc_timer); + event_add_timer(master, lde_gc_timer, NULL, LDE_GC_INTERVAL, &gc_timer); +} + +void +lde_gc_stop_timer(void) +{ + EVENT_OFF(gc_timer); +} diff --git a/ldpd/ldp.h b/ldpd/ldp.h new file mode 100644 index 0000000..1f0fdb5 --- /dev/null +++ b/ldpd/ldp.h @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +/* LDP protocol definitions */ + +#ifndef _LDP_H_ +#define _LDP_H_ + +/* this does not include "%s/", frr_runstatedir because the command-line + * override option specifies a *directory* rather than a full file name. + * Therefore the final part is needed on its own. + */ +#define LDPD_SOCK_NAME "ldpd.sock" + +/* misc */ +#define LDP_VERSION 1 +#define LDP_PORT 646 +#define LDP_MAX_LEN 4096 + +/* All Routers on this Subnet group multicast addresses */ +#define AllRouters_v4 "224.0.0.2" +#define AllRouters_v6 "ff02::2" + +#define LINK_DFLT_HOLDTIME 15 +#define TARGETED_DFLT_HOLDTIME 45 +#define MIN_HOLDTIME 3 +#define MAX_HOLDTIME 0xffff +#define INFINITE_HOLDTIME 0xffff + +#define DEFAULT_KEEPALIVE 180 +#define MIN_KEEPALIVE 3 +#define MAX_KEEPALIVE 0xffff +#define KEEPALIVE_PER_PERIOD 3 +#define INIT_FSM_TIMEOUT 15 + +#define DEFAULT_HELLO_INTERVAL 5 +#define MIN_HELLO_INTERVAL 1 +#define MAX_HELLO_INTERVAL 0xffff + +#define INIT_DELAY_TMR 15 +#define MAX_DELAY_TMR 120 + +#define DFLT_WAIT_FOR_SYNC 10 + +#define MIN_PWID_ID 1 +#define MAX_PWID_ID 0xffffffff + +#define DEFAULT_L2VPN_MTU 1500 +#define MIN_L2VPN_MTU 512 +#define MAX_L2VPN_MTU 0xffff + +/* LDP message types */ +#define MSG_TYPE_NOTIFICATION 0x0001 +#define MSG_TYPE_HELLO 0x0100 +#define MSG_TYPE_INIT 0x0200 +#define MSG_TYPE_KEEPALIVE 0x0201 +#define MSG_TYPE_CAPABILITY 0x0202 /* RFC 5561 */ +#define MSG_TYPE_ADDR 0x0300 +#define MSG_TYPE_ADDRWITHDRAW 0x0301 +#define MSG_TYPE_LABELMAPPING 0x0400 +#define MSG_TYPE_LABELREQUEST 0x0401 +#define MSG_TYPE_LABELWITHDRAW 0x0402 +#define MSG_TYPE_LABELRELEASE 0x0403 +#define MSG_TYPE_LABELABORTREQ 0x0404 + +/* LDP TLV types */ +#define TLV_TYPE_FEC 0x0100 +#define TLV_TYPE_ADDRLIST 0x0101 +#define TLV_TYPE_HOPCOUNT 0x0103 +#define TLV_TYPE_PATHVECTOR 0x0104 +#define TLV_TYPE_GENERICLABEL 0x0200 +#define TLV_TYPE_ATMLABEL 0x0201 +#define TLV_TYPE_FRLABEL 0x0202 +#define TLV_TYPE_STATUS 0x0300 +#define TLV_TYPE_EXTSTATUS 0x0301 +#define TLV_TYPE_RETURNEDPDU 0x0302 +#define TLV_TYPE_RETURNEDMSG 0x0303 +#define TLV_TYPE_COMMONHELLO 0x0400 +#define TLV_TYPE_IPV4TRANSADDR 0x0401 +#define TLV_TYPE_CONFIG 0x0402 +#define TLV_TYPE_IPV6TRANSADDR 0x0403 +#define TLV_TYPE_COMMONSESSION 0x0500 +#define TLV_TYPE_ATMSESSIONPAR 0x0501 +#define TLV_TYPE_FRSESSION 0x0502 +#define TLV_TYPE_LABELREQUEST 0x0600 +/* RFC 4447 */ +#define TLV_TYPE_MAC_LIST 0x8404 +#define TLV_TYPE_PW_STATUS 0x896A +#define TLV_TYPE_PW_IF_PARAM 0x096B +#define TLV_TYPE_PW_GROUP_ID 0x096C +/* RFC 5561 */ +#define TLV_TYPE_RETURNED_TLVS 0x8304 +#define TLV_TYPE_DYNAMIC_CAP 0x8506 +/* RFC 5918 */ +#define TLV_TYPE_TWCARD_CAP 0x850B +/* RFC 5919 */ +#define TLV_TYPE_UNOTIF_CAP 0x8603 +/* RFC 7552 */ +#define TLV_TYPE_DUALSTACK 0x8701 + +/* LDP header */ +struct ldp_hdr { + uint16_t version; + uint16_t length; + uint32_t lsr_id; + uint16_t lspace_id; +} __attribute__ ((packed)); + +#define LDP_HDR_SIZE 10 /* actual size of the LDP header */ +#define LDP_HDR_PDU_LEN 6 /* minimum "PDU Length" */ +#define LDP_HDR_DEAD_LEN 4 + +/* TLV record */ +struct tlv { + uint16_t type; + uint16_t length; +}; +#define TLV_HDR_SIZE 4 + +struct ldp_msg { + uint16_t type; + uint16_t length; + uint32_t id; + /* Mandatory Parameters */ + /* Optional Parameters */ +} __attribute__ ((packed)); + +#define LDP_MSG_SIZE 8 /* minimum size of LDP message */ +#define LDP_MSG_LEN 4 /* minimum "Message Length" */ +#define LDP_MSG_DEAD_LEN 4 + +#define UNKNOWN_FLAG 0x8000 +#define FORWARD_FLAG 0xc000 + +struct hello_prms_tlv { + uint16_t type; + uint16_t length; + uint16_t holdtime; + uint16_t flags; +}; +#define F_HELLO_TARGETED 0x8000 +#define F_HELLO_REQ_TARG 0x4000 +#define F_HELLO_GTSM 0x2000 + +struct hello_prms_opt4_tlv { + uint16_t type; + uint16_t length; + uint32_t value; +}; + +struct hello_prms_opt16_tlv { + uint16_t type; + uint16_t length; + uint8_t value[16]; +}; + +#define DUAL_STACK_LDPOV4 4 +#define DUAL_STACK_LDPOV6 6 + +#define F_HELLO_TLV_RCVD_ADDR 0x01 +#define F_HELLO_TLV_RCVD_CONF 0x02 +#define F_HELLO_TLV_RCVD_DS 0x04 + +#define S_SUCCESS 0x00000000 +#define S_BAD_LDP_ID 0x80000001 +#define S_BAD_PROTO_VER 0x80000002 +#define S_BAD_PDU_LEN 0x80000003 +#define S_UNKNOWN_MSG 0x00000004 +#define S_BAD_MSG_LEN 0x80000005 +#define S_UNKNOWN_TLV 0x00000006 +#define S_BAD_TLV_LEN 0x80000007 +#define S_BAD_TLV_VAL 0x80000008 +#define S_HOLDTIME_EXP 0x80000009 +#define S_SHUTDOWN 0x8000000A +#define S_LOOP_DETECTED 0x0000000B +#define S_UNKNOWN_FEC 0x0000000C +#define S_NO_ROUTE 0x0000000D +#define S_NO_LABEL_RES 0x0000000E +#define S_AVAILABLE 0x0000000F +#define S_NO_HELLO 0x80000010 +#define S_PARM_ADV_MODE 0x80000011 +#define S_MAX_PDU_LEN 0x80000012 +#define S_PARM_L_RANGE 0x80000013 +#define S_KEEPALIVE_TMR 0x80000014 +#define S_LAB_REQ_ABRT 0x00000015 +#define S_MISS_MSG 0x00000016 +#define S_UNSUP_ADDR 0x00000017 +#define S_KEEPALIVE_BAD 0x80000018 +#define S_INTERN_ERR 0x80000019 +/* RFC 4447 */ +#define S_ILLEGAL_CBIT 0x00000024 +#define S_WRONG_CBIT 0x00000025 +#define S_INCPT_BITRATE 0x00000026 +#define S_CEP_MISCONF 0x00000027 +#define S_PW_STATUS 0x00000028 +#define S_UNASSIGN_TAI 0x00000029 +#define S_MISCONF_ERR 0x0000002A +#define S_WITHDRAW_MTHD 0x0000002B +/* RFC 5561 */ +#define S_UNSSUPORTDCAP 0x0000002E +/* RFC 5919 */ +#define S_ENDOFLIB 0x0000002F +/* RFC 7552 */ +#define S_TRANS_MISMTCH 0x80000032 +#define S_DS_NONCMPLNCE 0x80000033 + +struct sess_prms_tlv { + uint16_t type; + uint16_t length; + uint16_t proto_version; + uint16_t keepalive_time; + uint8_t reserved; + uint8_t pvlim; + uint16_t max_pdu_len; + uint32_t lsr_id; + uint16_t lspace_id; +} __attribute__ ((packed)); + +#define SESS_PRMS_SIZE 18 +#define SESS_PRMS_LEN 14 + +struct status_tlv { + uint16_t type; + uint16_t length; + uint32_t status_code; + uint32_t msg_id; + uint16_t msg_type; +} __attribute__ ((packed)); + +#define STATUS_SIZE 14 +#define STATUS_TLV_LEN 10 +#define STATUS_FATAL 0x80000000 + +struct capability_tlv { + uint16_t type; + uint16_t length; + uint8_t reserved; +}; +#define STATE_BIT 0x80 + +#define F_CAP_TLV_RCVD_DYNAMIC 0x01 +#define F_CAP_TLV_RCVD_TWCARD 0x02 +#define F_CAP_TLV_RCVD_UNOTIF 0x04 + +#define CAP_TLV_DYNAMIC_SIZE 5 +#define CAP_TLV_DYNAMIC_LEN 1 + +#define CAP_TLV_TWCARD_SIZE 5 +#define CAP_TLV_TWCARD_LEN 1 + +#define CAP_TLV_UNOTIF_SIZE 5 +#define CAP_TLV_UNOTIF_LEN 1 + +#define AF_IPV4 0x1 +#define AF_IPV6 0x2 + +struct address_list_tlv { + uint16_t type; + uint16_t length; + uint16_t family; + /* address entries */ +} __attribute__ ((packed)); + +#define ADDR_LIST_SIZE 6 + +#define FEC_ELM_WCARD_LEN 1 +#define FEC_ELM_PREFIX_MIN_LEN 4 +#define FEC_PWID_ELM_MIN_LEN 8 +#define FEC_PWID_SIZE 4 +#define FEC_ELM_TWCARD_MIN_LEN 3 + +#define MAP_TYPE_WILDCARD 0x01 +#define MAP_TYPE_PREFIX 0x02 +#define MAP_TYPE_TYPED_WCARD 0x05 +#define MAP_TYPE_PWID 0x80 +#define MAP_TYPE_GENPWID 0x81 + +#define CONTROL_WORD_FLAG 0x8000 +#define DEFAULT_PW_TYPE PW_TYPE_ETHERNET + +#define PW_TWCARD_RESERVED_BIT 0x8000 + +/* RFC 4447 Sub-TLV record */ +struct subtlv { + uint8_t type; + uint8_t length; +}; +#define SUBTLV_HDR_SIZE 2 + +#define SUBTLV_IFMTU 0x01 +#define SUBTLV_VLANID 0x06 + +#define FEC_SUBTLV_IFMTU_SIZE 4 +#define FEC_SUBTLV_VLANID_SIZE 4 + +struct label_tlv { + uint16_t type; + uint16_t length; + uint32_t label; +}; +#define LABEL_TLV_SIZE 8 +#define LABEL_TLV_LEN 4 + +struct reqid_tlv { + uint16_t type; + uint16_t length; + uint32_t reqid; +}; +#define REQID_TLV_SIZE 8 +#define REQID_TLV_LEN 4 + +struct pw_status_tlv { + uint16_t type; + uint16_t length; + uint32_t value; +}; +#define PW_STATUS_TLV_SIZE 8 +#define PW_STATUS_TLV_LEN 4 + +#define NO_LABEL UINT32_MAX + +#endif /* !_LDP_H_ */ diff --git a/ldpd/ldp_debug.c b/ldpd/ldp_debug.c new file mode 100644 index 0000000..957fb8e --- /dev/null +++ b/ldpd/ldp_debug.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#include + +#include "command.h" +#include "vty.h" + +#include "ldpd.h" +#include "ldp_debug.h" +#include "ldp_vty.h" + +struct ldp_debug conf_ldp_debug; +struct ldp_debug ldp_debug; + +static int ldp_debug_config_write(struct vty *); + +/* Debug node. */ +struct cmd_node ldp_debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = ldp_debug_config_write, +}; + +int +ldp_vty_debug(struct vty *vty, const char *negate, const char *type_str, + const char *dir_str, const char *all) +{ + if (type_str == NULL) + return (CMD_WARNING_CONFIG_FAILED); + + if (strcmp(type_str, "discovery") == 0) { + if (dir_str == NULL) + return (CMD_WARNING_CONFIG_FAILED); + + if (dir_str[0] == 'r') { + if (negate) + DEBUG_OFF(hello, LDP_DEBUG_HELLO_RECV); + else + DEBUG_ON(hello, LDP_DEBUG_HELLO_RECV); + } else { + if (negate) + DEBUG_OFF(hello, LDP_DEBUG_HELLO_SEND); + else + DEBUG_ON(hello, LDP_DEBUG_HELLO_SEND); + } + } else if (strcmp(type_str, "errors") == 0) { + if (negate) + DEBUG_OFF(errors, LDP_DEBUG_ERRORS); + else + DEBUG_ON(errors, LDP_DEBUG_ERRORS); + } else if (strcmp(type_str, "event") == 0) { + if (negate) + DEBUG_OFF(event, LDP_DEBUG_EVENT); + else + DEBUG_ON(event, LDP_DEBUG_EVENT); + } else if (strcmp(type_str, "labels") == 0) { + if (negate) + DEBUG_OFF(labels, LDP_DEBUG_LABELS); + else + DEBUG_ON(labels, LDP_DEBUG_LABELS); + } else if (strcmp(type_str, "messages") == 0) { + if (dir_str == NULL) + return (CMD_WARNING_CONFIG_FAILED); + + if (dir_str[0] == 'r') { + if (negate) { + DEBUG_OFF(msg, LDP_DEBUG_MSG_RECV); + DEBUG_OFF(msg, LDP_DEBUG_MSG_RECV_ALL); + } else { + DEBUG_ON(msg, LDP_DEBUG_MSG_RECV); + if (all) + DEBUG_ON(msg, LDP_DEBUG_MSG_RECV_ALL); + } + } else { + if (negate) { + DEBUG_OFF(msg, LDP_DEBUG_MSG_SEND); + DEBUG_OFF(msg, LDP_DEBUG_MSG_SEND_ALL); + } else { + DEBUG_ON(msg, LDP_DEBUG_MSG_SEND); + if (all) + DEBUG_ON(msg, LDP_DEBUG_MSG_SEND_ALL); + } + } + } else if (strcmp(type_str, "sync") == 0) { + if (negate) + DEBUG_OFF(sync, LDP_DEBUG_SYNC); + else + DEBUG_ON(sync, LDP_DEBUG_SYNC); + } else if (strcmp(type_str, "zebra") == 0) { + if (negate) + DEBUG_OFF(zebra, LDP_DEBUG_ZEBRA); + else + DEBUG_ON(zebra, LDP_DEBUG_ZEBRA); + } + + main_imsg_compose_both(IMSG_DEBUG_UPDATE, &ldp_debug, sizeof(ldp_debug)); + + return (CMD_SUCCESS); +} + +int +ldp_vty_show_debugging(struct vty *vty) +{ + vty_out (vty, "LDP debugging status:\n"); + + if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) + vty_out (vty," LDP discovery debugging is on (inbound)\n"); + if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) + vty_out (vty," LDP discovery debugging is on (outbound)\n"); + if (LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) + vty_out (vty, " LDP errors debugging is on\n"); + if (LDP_DEBUG(event, LDP_DEBUG_EVENT)) + vty_out (vty, " LDP events debugging is on\n"); + if (LDP_DEBUG(labels, LDP_DEBUG_LABELS)) + vty_out (vty, " LDP labels debugging is on\n"); + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) + vty_out (vty, " LDP detailed messages debugging is on (inbound)\n"); + else if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) + vty_out (vty," LDP messages debugging is on (inbound)\n"); + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) + vty_out (vty, " LDP detailed messages debugging is on (outbound)\n"); + else if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) + vty_out (vty," LDP messages debugging is on (outbound)\n"); + if (LDP_DEBUG(sync, LDP_DEBUG_SYNC)) + vty_out (vty, " LDP sync debugging is on\n"); + if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) + vty_out (vty, " LDP zebra debugging is on\n"); + vty_out (vty, "\n"); + + return (CMD_SUCCESS); +} + +static int +ldp_debug_config_write(struct vty *vty) +{ + int write = 0; + + if (CONF_LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) { + vty_out (vty,"debug mpls ldp discovery hello recv\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) { + vty_out (vty,"debug mpls ldp discovery hello sent\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) { + vty_out (vty, "debug mpls ldp errors\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(event, LDP_DEBUG_EVENT)) { + vty_out (vty, "debug mpls ldp event\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(labels, LDP_DEBUG_LABELS)) { + vty_out (vty, "debug mpls ldp labels\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) { + vty_out (vty, "debug mpls ldp messages recv all\n"); + write = 1; + } else if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) { + vty_out (vty, "debug mpls ldp messages recv\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) { + vty_out (vty, "debug mpls ldp messages sent all\n"); + write = 1; + } else if (CONF_LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) { + vty_out (vty, "debug mpls ldp messages sent\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) { + vty_out (vty, "debug mpls ldp zebra\n"); + write = 1; + } + + if (CONF_LDP_DEBUG(sync, LDP_DEBUG_SYNC)) { + vty_out (vty, "debug mpls ldp sync\n"); + write = 1; + } + + return (write); +} diff --git a/ldpd/ldp_debug.h b/ldpd/ldp_debug.h new file mode 100644 index 0000000..09fa711 --- /dev/null +++ b/ldpd/ldp_debug.h @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#ifndef _LDP_DEBUG_H_ +#define _LDP_DEBUG_H_ + +struct ldp_debug { + int hello; +#define LDP_DEBUG_HELLO_RECV 0x01 +#define LDP_DEBUG_HELLO_SEND 0x02 + + int errors; +#define LDP_DEBUG_ERRORS 0x01 + + int event; +#define LDP_DEBUG_EVENT 0x01 + + int labels; +#define LDP_DEBUG_LABELS 0x01 + + int msg; +#define LDP_DEBUG_MSG_RECV 0x01 +#define LDP_DEBUG_MSG_RECV_ALL 0x02 +#define LDP_DEBUG_MSG_SEND 0x04 +#define LDP_DEBUG_MSG_SEND_ALL 0x08 + + int zebra; +#define LDP_DEBUG_ZEBRA 0x01 + + int sync; +#define LDP_DEBUG_SYNC 0x01 + +}; +extern struct ldp_debug conf_ldp_debug; +extern struct ldp_debug ldp_debug; + +#define CONF_DEBUG_ON(a, b) (conf_ldp_debug.a |= (b)) +#define CONF_DEBUG_OFF(a, b) (conf_ldp_debug.a &= ~(b)) + +#define TERM_DEBUG_ON(a, b) (ldp_debug.a |= (b)) +#define TERM_DEBUG_OFF(a, b) (ldp_debug.a &= ~(b)) + +#define DEBUG_ON(a, b) \ + do { \ + if (vty->node == CONFIG_NODE) { \ + CONF_DEBUG_ON(a, b); \ + TERM_DEBUG_ON(a, b); \ + } else \ + TERM_DEBUG_ON(a, b); \ + } while (0) +#define DEBUG_OFF(a, b) \ + do { \ + CONF_DEBUG_OFF(a, b); \ + TERM_DEBUG_OFF(a, b); \ + } while (0) + +#define LDP_DEBUG(a, b) (ldp_debug.a & b) +#define CONF_LDP_DEBUG(a, b) (conf_ldp_debug.a & b) + +#define debug_hello_recv(emsg, ...) \ +do { \ + if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_RECV)) \ + log_debug("discovery[recv]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_hello_send(emsg, ...) \ +do { \ + if (LDP_DEBUG(hello, LDP_DEBUG_HELLO_SEND)) \ + log_debug("discovery[send]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_err(emsg, ...) \ +do { \ + if (LDP_DEBUG(errors, LDP_DEBUG_ERRORS)) \ + log_debug("error: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_evt(emsg, ...) \ +do { \ + if (LDP_DEBUG(event, LDP_DEBUG_EVENT)) \ + log_debug("event: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_labels(emsg, ...) \ +do { \ + if (LDP_DEBUG(labels, LDP_DEBUG_LABELS)) \ + log_debug("labels: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_msg_recv(emsg, ...) \ +do { \ + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV)) \ + log_debug("msg[in]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_msg_send(emsg, ...) \ +do { \ + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND)) \ + log_debug("msg[out]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_msg(out, emsg, ...) \ +do { \ + if (out) \ + debug_msg_send(emsg, __VA_ARGS__); \ + else \ + debug_msg_recv(emsg, __VA_ARGS__); \ +} while (0) + +#define debug_kalive_recv(emsg, ...) \ +do { \ + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_RECV_ALL)) \ + log_debug("kalive[in]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_kalive_send(emsg, ...) \ +do { \ + if (LDP_DEBUG(msg, LDP_DEBUG_MSG_SEND_ALL)) \ + log_debug("kalive[out]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_zebra_in(emsg, ...) \ +do { \ + if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) \ + log_debug("zebra[in]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_zebra_out(emsg, ...) \ +do { \ + if (LDP_DEBUG(zebra, LDP_DEBUG_ZEBRA)) \ + log_debug("zebra[out]: " emsg, __VA_ARGS__); \ +} while (0) + +#define debug_evt_ldp_sync(emsg, ...) \ +do { \ + if (LDP_DEBUG(sync, LDP_DEBUG_SYNC)) \ + log_debug("sync: " emsg, __VA_ARGS__); \ +} while (0) + +#endif /* _LDP_DEBUG_H_ */ diff --git a/ldpd/ldp_snmp.c b/ldpd/ldp_snmp.c new file mode 100644 index 0000000..ed391ac --- /dev/null +++ b/ldpd/ldp_snmp.c @@ -0,0 +1,1224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LDP SNMP support + * Copyright (C) 2020 Volta Networks, Inc. + */ + +/* + * This is minimal read-only implementations providing + * mplsLdpModuleReadOnlyCompliance as described in RFC 3815. + */ + +#include + +#include +#include + +#include "vrf.h" +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "memory.h" +#include "smux.h" +#include "libfrr.h" +#include "lib/version.h" +#include "ldpd.h" +#include "ldpe.h" + +/* SNMP value hack. */ +#define COUNTER32 ASN_COUNTER +#define INTEGER ASN_INTEGER +#define UNSIGNED32 ASN_GAUGE +#define TIMESTAMP ASN_TIMETICKS +#define TIMETICKS ASN_TIMETICKS +#define STRING ASN_OCTET_STR +#define IPADDRESS ASN_IPADDRESS + +#define LDP_LSRID_IDX_LEN 6 +#define LDP_ENTITY_IDX_LEN 1 +#define LDP_ADJACENCY_IDX_LEN 1 + +/* MPLS-LDP-STD-MIB. */ +#define MPLS_LDP_STD_MIB 1, 3, 6, 1, 2, 1, 10, 166, 4 + +#define MPLS_LDP_LSR_ID 0 +#define MPLS_LDP_LSR_LOOP_DETECTION_CAPABLE 0 +#define MPLS_LDP_ENTITY_LAST_CHANGE 0 +#define MPLS_LDP_ENTITY_INDEX_NEXT 0 + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +/* LDP-MIB instances. */ +static oid ldp_oid[] = {MPLS_LDP_STD_MIB}; +static oid ldp_trap_oid[] = {MPLS_LDP_STD_MIB, 0}; + +static uint8_t snmp_ldp_rtrid[6] = {0, 0, 0, 0, 0}; + +#define LDP_DEFAULT_ENTITY_INDEX 1 + +#define MPLSLDPLSRLOOPDETECTIONCAPABLE_NONE 1 +#define MPLSLDPLSRLOOPDETECTIONCAPABLE_OTHER 2 +#define MPLSLDPLSRLOOPDETECTIONCAPABLE_HOPCOUNT 3 +#define MPLSLDPLSRLOOPDETECTIONCAPABLE_PATHVECTOR 4 +#define MPLSLDPLSRLOOPDETECTIONCAPABLE_HOPCOUNTANDPATHVECTOR 5 + +/* MPLS LDP mplsLdpHelloAdjacencyTable. */ +#define MPLSLDPHELLOADJACENCYINDEX 1 +#define MPLSLDPHELLOADJACENCYHOLDTIMEREM 2 +#define MPLSLDPHELLOADJACENCYHOLDTIME 3 +#define MPLSLDPHELLOADJACENCYTYPE 4 + +/* enums for column mplsLdpHelloAdjacencyType */ +#define MPLSLDPHELLOADJACENCYTYPE_LINK 1 +#define MPLSLDPHELLOADJACENCYTYPE_TARGETED 2 + +#define MPLSLDPPEERTRANSPORTADDRTYPE_UNKNOWN 0 +#define MPLSLDPPEERTRANSPORTADDRTYPE_IPV4 1 +#define MPLSLDPPEERTRANSPORTADDRTYPE_IPV6 2 +#define MPLSLDPPEERTRANSPORTADDRTYPE_IPV4Z 3 +#define MPLSLDPPEERTRANSPORTADDRTYPE_IPV6Z 4 +#define MPLSLDPPEERTRANSPORTADDRTYPE_DNS 16 + +#define DOWNSTREAMONDEMAND 1 +#define DOWNSTREAMUNSOLICITED 2 + +#define CONSERVATIVERETENTION 1 +#define LIBERALRETENTION 2 + +#define TRANSPORTADDRINTERFACE 1 +#define TRANSPORTADDRLOOPBACK 2 + +#define LABELTYPEGENERIC 1 + +#define STORAGETYPENONVOLATILE 3 + +#define ROWSTATUSACTIVE 4 + +#define ADMINSTATUSENABLED 1 + +#define OPERSTATUSENABLED 2 + +/* MPLS LDP mplsLdpPeerTable */ +#define MPLSLDPPEERLDPID 1 +#define MPLSLDPPEERLABELDISTMETHOD 2 +#define MPLSLDPPEERPATHVECTORLIMIT 3 +#define MPLSLDPPEERTRANSPORTADDRTYPE 4 +#define MPLSLDPPEERTRANSPORTADDR 5 + +#define MPLSLDPSESSIONROLE_UNKNOWN 1 +#define MPLSLDPSESSIONROLE_ACTIVE 2 +#define MPLSLDPSESSIONROLE_PASSIVE 3 + +#define MPLSLDPSESSIONSTATE_NONEXISTENT 1 +#define MPLSLDPSESSIONSTATE_INITIALIZED 2 +#define MPLSLDPSESSIONSTATE_OPENREC 3 +#define MPLSLDPSESSIONSTATE_OPENSENT 4 +#define MPLSLDPSESSIONSTATE_OPERATIONAL 5 + +/* MPLS LDP mplsLdpSessionTable */ +#define MPLSLDPSESSIONSTATELASTCHANGE 1 +#define MPLSLDPSESSIONSTATE 2 +#define MPLSLDPSESSIONROLE 3 +#define MPLSLDPSESSIONPROTOCOLVERSION 4 +#define MPLSLDPSESSIONKEEPALIVEHOLDTIMEREM 5 +#define MPLSLDPSESSIONKEEPALIVETIME 6 +#define MPLSLDPSESSIONMAXPDULENGTH 7 +#define MPLSLDPSESSIONDISCONTINUITYTIME 8 + +/* MPLS LDP mplsLdpEntityTable */ +#define MPLSLDPENTITYLDPID 1 +#define MPLSLDPENTITYINDEX 2 +#define MPLSLDPENTITYPROTOCOLVERSION 3 +#define MPLSLDPENTITYADMINSTATUS 4 +#define MPLSLDPENTITYOPERSTATUS 5 +#define MPLSLDPENTITYTCPPORT 6 +#define MPLSLDPENTITYUDPDSCPORT 7 +#define MPLSLDPENTITYMAXPDULENGTH 8 +#define MPLSLDPENTITYKEEPALIVEHOLDTIMER 9 +#define MPLSLDPENTITYHELLOHOLDTIMER 10 +#define MPLSLDPENTITYINITSESSIONTHRESHOLD 11 +#define MPLSLDPENTITYLABELDISTMETHOD 12 +#define MPLSLDPENTITYLABELRETENTIONMODE 13 +#define MPLSLDPENTITYPATHVECTORLIMIT 14 +#define MPLSLDPENTITYHOPCOUNTLIMIT 15 +#define MPLSLDPENTITYTRANSPORTADDRKIND 16 +#define MPLSLDPENTITYTARGETPEER 17 +#define MPLSLDPENTITYTARGETPEERADDRTYPE 18 +#define MPLSLDPENTITYTARGETPEERADDR 19 +#define MPLSLDPENTITYLABELTYPE 20 +#define MPLSLDPENTITYDISCONTINUITYTIME 21 +#define MPLSLDPENTITYSTORAGETYPE 22 +#define MPLSLDPENTITYROWSTATUS 23 + +/* MPLS LDP mplsLdpEntityStatsTable */ +#define MPLSLDPENTITYSTATSSESSIONATTEMPTS 1 +#define MPLSLDPENTITYSTATSSESSIONREJHELLO 2 +#define MPLSLDPENTITYSTATSSESSIONREJAD 3 +#define MPLSLDPENTITYSTATSSESSIONREJMAXPDU 4 +#define MPLSLDPENTITYSTATSSESSIONREJLR 5 +#define MPLSLDPENTITYSTATSBADLDPID 6 +#define MPLSLDPENTITYSTATSBADPDULENGTH 7 +#define MPLSLDPENTITYSTATSBADMSGLENGTH 8 +#define MPLSLDPENTITYSTATSBADTLVLENGTH 9 +#define MPLSLDPENTITYSTATSMALFORMEDTLV 10 +#define MPLSLDPENTITYSTATSKEEPALIVEEXP 11 +#define MPLSLDPENTITYSTATSSHUTDOWNRCVNOTIFY 12 +#define MPLSLDPENTITYSTATSSHUTDOWNSENTNOTIFY 13 + +#define MPLSLDPSESSIONSTATSUNKNOWNMESTYPEERRORS 1 +#define MPLSLDPSESSIONSTATSUNKNOWNTLVERRORS 2 + +static uint8_t *ldpLsrId(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + *var_len = 4; + return (uint8_t *)&leconf->rtr_id.s_addr; +} + +static uint8_t *ldpLoopDetectCap(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + return SNMP_INTEGER(MPLSLDPLSRLOOPDETECTIONCAPABLE_NONE); +} + +extern uint32_t ldp_start_time; +static uint8_t *ldpEntityLastChange(struct variable *v, oid name[], + size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + *var_len = sizeof(time_t); + return (uint8_t *) &(leconf->config_change_time); + +} + +static uint8_t *ldpEntityIndexNext(struct variable *v, oid name[], + size_t *length,int exact, size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + return SNMP_INTEGER(0); +} + +#define LDP_ENTITY_TOTAL_LEN 21 +#define LDP_ENTITY_MAX_IDX_LEN 6 + +static struct ldpd_af_conf *ldpEntityTable_lookup(struct variable *v, oid *name, + size_t *length, int exact, + uint32_t *index) +{ + int len; + struct ldpd_af_conf *af_v4, *af_v6; + + af_v4 = &leconf->ipv4; + af_v6 = &leconf->ipv6; + + if (exact) { + if (*length != LDP_ENTITY_TOTAL_LEN) + return NULL; + + if (leconf->trans_pref == DUAL_STACK_LDPOV6 && + af_v6->flags & F_LDPD_AF_ENABLED) { + *index = 2; + return af_v6; + } else { + *index = 1; + return af_v4; + } + } else { + /* only support one router id so can just skip */ + len = *length - v->namelen - LDP_ENTITY_MAX_IDX_LEN; + if (len <= 0) { + if (leconf->trans_pref == DUAL_STACK_LDPOV6 && + af_v6->flags & F_LDPD_AF_ENABLED) { + *index = 2; + return af_v6; + } else { + *index = 1; + return af_v4; + } + } + } + return NULL; +} + +static uint8_t *ldpEntityTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ldpd_af_conf *af; + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t index = 0; + + *write_method = NULL; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + af = ldpEntityTable_lookup(v, name, length, exact, &index); + if (af == NULL) + return NULL; + + if (!exact) { + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + *length = LDP_ENTITY_TOTAL_LEN; + oid_copy_in_addr(name + v->namelen, &entityLdpId); + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = LDP_DEFAULT_ENTITY_INDEX; + } + + /* Return the current value of the variable */ + switch (v->magic) { + case MPLSLDPENTITYLDPID: + *var_len = 6; + memcpy (snmp_ldp_rtrid, &entityLdpId, IN_ADDR_SIZE); + return (uint8_t *)snmp_ldp_rtrid; + case MPLSLDPENTITYINDEX: + return SNMP_INTEGER(LDP_DEFAULT_ENTITY_INDEX); + case MPLSLDPENTITYPROTOCOLVERSION: + return SNMP_INTEGER(LDP_VERSION); + case MPLSLDPENTITYADMINSTATUS: + return SNMP_INTEGER(ADMINSTATUSENABLED); + case MPLSLDPENTITYOPERSTATUS: + return SNMP_INTEGER(OPERSTATUSENABLED); + case MPLSLDPENTITYTCPPORT: + return SNMP_INTEGER(LDP_PORT); + case MPLSLDPENTITYUDPDSCPORT: + return SNMP_INTEGER(LDP_PORT); + case MPLSLDPENTITYMAXPDULENGTH: + return SNMP_INTEGER(LDP_MAX_LEN); + case MPLSLDPENTITYKEEPALIVEHOLDTIMER: + return SNMP_INTEGER(af->keepalive); + case MPLSLDPENTITYHELLOHOLDTIMER: + return SNMP_INTEGER(af->lhello_holdtime); + case MPLSLDPENTITYINITSESSIONTHRESHOLD: + return SNMP_INTEGER(0); /* not supported */ + case MPLSLDPENTITYLABELDISTMETHOD: + return SNMP_INTEGER(DOWNSTREAMUNSOLICITED); + case MPLSLDPENTITYLABELRETENTIONMODE: + return SNMP_INTEGER(LIBERALRETENTION); + case MPLSLDPENTITYPATHVECTORLIMIT: + return SNMP_INTEGER(0); /* not supported */ + case MPLSLDPENTITYHOPCOUNTLIMIT: + return SNMP_INTEGER(0); + case MPLSLDPENTITYTRANSPORTADDRKIND: + return SNMP_INTEGER(TRANSPORTADDRLOOPBACK); + case MPLSLDPENTITYTARGETPEER: + return SNMP_INTEGER(1); + case MPLSLDPENTITYTARGETPEERADDRTYPE: + if (index == 1) + return SNMP_INTEGER(MPLSLDPPEERTRANSPORTADDRTYPE_IPV4); + else + return SNMP_INTEGER(MPLSLDPPEERTRANSPORTADDRTYPE_IPV6); + case MPLSLDPENTITYTARGETPEERADDR: + if (index == 1) { + *var_len = sizeof(af->trans_addr.v4); + return ((uint8_t *)&af->trans_addr.v4); + }else { + *var_len = sizeof(af->trans_addr.v6); + return ((uint8_t *)&af->trans_addr.v6); + } + case MPLSLDPENTITYLABELTYPE: + return SNMP_INTEGER(LABELTYPEGENERIC); + case MPLSLDPENTITYDISCONTINUITYTIME: + return SNMP_INTEGER(0); + case MPLSLDPENTITYSTORAGETYPE: + return SNMP_INTEGER(STORAGETYPENONVOLATILE); + case MPLSLDPENTITYROWSTATUS: + return SNMP_INTEGER(ROWSTATUSACTIVE); + default: + return NULL; + } + + return NULL; +} + +static uint8_t *ldpEntityStatsTable(struct variable *v, oid name[], + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr entityLdpId = {.s_addr = 0}; + int len; + + *write_method = NULL; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + if (exact) { + if (*length != LDP_ENTITY_TOTAL_LEN) + return NULL; + } else { + len = *length - v->namelen - LDP_ENTITY_MAX_IDX_LEN; + if (len > 0) + return NULL; + + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + *length = LDP_ENTITY_TOTAL_LEN; + oid_copy_in_addr(name + v->namelen, &entityLdpId); + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = LDP_DEFAULT_ENTITY_INDEX; + } + + /* Return the current value of the variable */ + switch (v->magic) { + case MPLSLDPENTITYSTATSSESSIONATTEMPTS: + return SNMP_INTEGER(leconf->stats.session_attempts); + case MPLSLDPENTITYSTATSSESSIONREJHELLO: + return SNMP_INTEGER(leconf->stats.session_rejects_hello); + case MPLSLDPENTITYSTATSSESSIONREJAD: + return SNMP_INTEGER(leconf->stats.session_rejects_ad); + case MPLSLDPENTITYSTATSSESSIONREJMAXPDU: + return SNMP_INTEGER(leconf->stats.session_rejects_max_pdu); + case MPLSLDPENTITYSTATSSESSIONREJLR: + return SNMP_INTEGER(leconf->stats.session_rejects_lr); + case MPLSLDPENTITYSTATSBADLDPID: + return SNMP_INTEGER(leconf->stats.bad_ldp_id); + case MPLSLDPENTITYSTATSBADPDULENGTH: + return SNMP_INTEGER(leconf->stats.bad_pdu_len); + case MPLSLDPENTITYSTATSBADMSGLENGTH: + return SNMP_INTEGER(leconf->stats.bad_msg_len); + case MPLSLDPENTITYSTATSBADTLVLENGTH: + return SNMP_INTEGER(leconf->stats.bad_tlv_len); + case MPLSLDPENTITYSTATSMALFORMEDTLV: + return SNMP_INTEGER(leconf->stats.malformed_tlv); + case MPLSLDPENTITYSTATSKEEPALIVEEXP: + return SNMP_INTEGER(leconf->stats.keepalive_timer_exp); + case MPLSLDPENTITYSTATSSHUTDOWNRCVNOTIFY: + return SNMP_INTEGER(leconf->stats.shutdown_rcv_notify); + case MPLSLDPENTITYSTATSSHUTDOWNSENTNOTIFY: + return SNMP_INTEGER(leconf->stats.shutdown_send_notify); + default: + return NULL; + } + + return NULL; +} + +#define LDP_ADJACENCY_ENTRY_MAX_IDX_LEN 14 + +static void ldpHelloAdjacencyTable_oid_to_index( + struct variable *v, oid name[], + size_t *length, + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId, + uint32_t *adjacencyIndex) +{ + oid *offset = name + v->namelen; + int offsetlen = *length - v->namelen; + int len = offsetlen; + + if (len > LDP_ADJACENCY_ENTRY_MAX_IDX_LEN) + len = LDP_ADJACENCY_ENTRY_MAX_IDX_LEN; + + if (len >= LDP_LSRID_IDX_LEN) + oid2in_addr(offset, sizeof(struct in_addr), entityLdpId); + + offset += LDP_LSRID_IDX_LEN; + offsetlen -= LDP_LSRID_IDX_LEN; + len = offsetlen; + + if (len > LDP_ENTITY_IDX_LEN) + len = LDP_ENTITY_IDX_LEN; + + if (len >= LDP_ENTITY_IDX_LEN) + *entityIndex = offset[0]; + + offset += LDP_ENTITY_IDX_LEN; + offsetlen -= LDP_ENTITY_IDX_LEN; + len = offsetlen; + + if (len > LDP_LSRID_IDX_LEN) + len = LDP_LSRID_IDX_LEN; + + if (len >= LDP_LSRID_IDX_LEN) + oid2in_addr(offset, sizeof(struct in_addr), peerLdpId); + + offset += LDP_LSRID_IDX_LEN; + offsetlen -= LDP_LSRID_IDX_LEN; + len = offsetlen; + + if (len > LDP_ADJACENCY_IDX_LEN) + len = LDP_ADJACENCY_IDX_LEN; + + if (len >= LDP_ADJACENCY_IDX_LEN) + *adjacencyIndex = offset[0]; +} + +static struct adj * +nbr_get_adj_by_index(struct nbr *nbr, uint32_t adjacencyIndex) +{ + struct adj *adj; + uint32_t i = 0; + + RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) + if (++i == adjacencyIndex) + return adj; + + return NULL; +} + +static struct ctl_adj * +ldpHelloAdjacencyTable_lookup_helper( + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId, + uint32_t *adjacencyIndex) +{ + struct ctl_adj *ctl_adj = NULL; + struct adj *adj = NULL; + struct nbr *cur_nbr = nbr_find_ldpid(peerLdpId->s_addr); + + if (cur_nbr) + /* If found nbr, then look to see if the + * adjacency exists + */ + adj = nbr_get_adj_by_index(cur_nbr, *adjacencyIndex); + + if (adj) + ctl_adj = adj_to_ctl(adj); + + return ctl_adj; +} + +static struct ctl_adj * +ldpHelloAdjacencyTable_next_helper( + int first, + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId, + uint32_t *adjacencyIndex) +{ + struct ctl_adj *ctl_adj = NULL; + struct nbr *nbr = NULL; + struct adj *adj = NULL; + + if (first) + nbr = nbr_get_first_ldpid(); + else { + struct nbr *cur_nbr = nbr_find_ldpid(peerLdpId->s_addr); + if (cur_nbr) + /* If found nbr, then look to see if the + * adjacency exists + */ + adj = nbr_get_adj_by_index(cur_nbr, *adjacencyIndex + 1); + if (adj) + *adjacencyIndex += 1; + else + nbr = nbr_get_next_ldpid(peerLdpId->s_addr); + } + + if (!adj && nbr) { + adj = RB_MIN(nbr_adj_head, &nbr->adj_tree); + *adjacencyIndex = 1; + } + + if (adj) + ctl_adj = adj_to_ctl(adj); + + return ctl_adj; +} + +#define HELLO_ADJ_MAX_IDX_LEN 14 + +static struct ctl_adj * +ldpHelloAdjacencyTable_lookup(struct variable *v, oid name[], + size_t *length, int exact, + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId, + uint32_t *adjacencyIndex) +{ + struct ctl_adj *hello_adj = NULL; + + if (exact) { + if (*length < HELLO_ADJ_MAX_IDX_LEN) + return NULL; + + ldpHelloAdjacencyTable_oid_to_index( + v, name, length, + entityLdpId, entityIndex, peerLdpId, adjacencyIndex); + + hello_adj = ldpHelloAdjacencyTable_lookup_helper( + entityLdpId, entityIndex, peerLdpId, adjacencyIndex); + } else { + int first = 0; + int offsetlen = *length - v->namelen; + + if (offsetlen < HELLO_ADJ_MAX_IDX_LEN) + first = 1; + + ldpHelloAdjacencyTable_oid_to_index( + v, name, length, + entityLdpId, entityIndex, peerLdpId, adjacencyIndex); + + hello_adj = ldpHelloAdjacencyTable_next_helper(first, + entityLdpId, entityIndex, peerLdpId, adjacencyIndex); + + } + return hello_adj; +} + +static uint8_t *ldpHelloAdjacencyTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t entityIndex = 0; + struct in_addr peerLdpId = {.s_addr = 0}; + uint32_t adjacencyIndex = 0; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + struct ctl_adj *ctl_adj = ldpHelloAdjacencyTable_lookup(v, name, + length, exact, + &entityLdpId, &entityIndex, &peerLdpId, &adjacencyIndex); + + if (!ctl_adj) + return NULL; + + if (!exact) { + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + struct in_addr entityLdpId = {.s_addr = 0}; + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + + struct in_addr peerLdpId = ctl_adj->id; + + oid_copy_in_addr(name + v->namelen, &entityLdpId); + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = LDP_DEFAULT_ENTITY_INDEX; + oid_copy_in_addr(name + v->namelen + 7, &peerLdpId); + name[v->namelen + 11] = 0; + name[v->namelen + 12] = 0; + name[v->namelen + 13] = adjacencyIndex; + + /* Set length */ + *length = v->namelen + HELLO_ADJ_MAX_IDX_LEN; + } + + switch (v->magic) { + case MPLSLDPHELLOADJACENCYINDEX: + return SNMP_INTEGER(adjacencyIndex); + case MPLSLDPHELLOADJACENCYHOLDTIMEREM: + return SNMP_INTEGER(ctl_adj->holdtime_remaining); + case MPLSLDPHELLOADJACENCYHOLDTIME: + return SNMP_INTEGER(ctl_adj->holdtime); + case MPLSLDPHELLOADJACENCYTYPE: + if (ctl_adj->type == HELLO_LINK) + return SNMP_INTEGER(MPLSLDPHELLOADJACENCYTYPE_LINK); + return SNMP_INTEGER(MPLSLDPHELLOADJACENCYTYPE_TARGETED); + default: + return NULL; + } + + return NULL; +} + +#define LDP_LSRID_IDX_LEN 6 +#define LDP_ENTITY_IDX_LEN 1 +#define LDP_PEER_ENTRY_MAX_IDX_LEN 13 + +static void ldpPeerTable_oid_to_index( + struct variable *v, oid name[], + size_t *length, + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId) +{ + oid *offset = name + v->namelen; + int offsetlen = *length - v->namelen; + int len = offsetlen; + + if (len > LDP_PEER_ENTRY_MAX_IDX_LEN) + len = LDP_PEER_ENTRY_MAX_IDX_LEN; + + if (len >= LDP_LSRID_IDX_LEN) + oid2in_addr(offset, sizeof(struct in_addr), entityLdpId); + + offset += LDP_LSRID_IDX_LEN; + offsetlen -= LDP_LSRID_IDX_LEN; + len = offsetlen; + + if (len > LDP_ENTITY_IDX_LEN) + len = LDP_ENTITY_IDX_LEN; + + if (len >= LDP_ENTITY_IDX_LEN) + *entityIndex = offset[0]; + + offset += LDP_ENTITY_IDX_LEN; + offsetlen -= LDP_ENTITY_IDX_LEN; + len = offsetlen; + + if (len > LDP_LSRID_IDX_LEN) + len = LDP_LSRID_IDX_LEN; + + if (len >= LDP_LSRID_IDX_LEN) + oid2in_addr(offset, sizeof(struct in_addr), peerLdpId); +} + +static struct ctl_nbr * +ldpPeerTable_lookup_next(int first, + struct in_addr peerLdpId) +{ + struct nbr *nbr = NULL; + struct ctl_nbr *ctl_nbr = NULL;; + + if (first) + nbr = nbr_get_first_ldpid(); + else + nbr = nbr_get_next_ldpid(peerLdpId.s_addr); + + if (nbr) + ctl_nbr = nbr_to_ctl(nbr); + + return ctl_nbr; +} + +static struct ctl_nbr * +ldpPeerTable_lookup(struct variable *v, oid name[], + size_t *length, int exact, + struct in_addr *entityLdpId, + uint32_t *entityIndex, + struct in_addr *peerLdpId) +{ + struct ctl_nbr *ctl_nbr = NULL; + struct nbr *nbr = NULL; + int first = 0; + + if (exact) { + if (*length < (long unsigned int)v->namelen + + LDP_PEER_ENTRY_MAX_IDX_LEN) + return NULL; + + ldpPeerTable_oid_to_index( + v, name, length, + entityLdpId, entityIndex, peerLdpId); + + nbr = nbr_find_ldpid(peerLdpId->s_addr); + if (nbr) + ctl_nbr = nbr_to_ctl(nbr); + + return ctl_nbr; + } else { + + int offsetlen = *length - v->namelen; + if (offsetlen < LDP_LSRID_IDX_LEN) + first = 1; + + ldpPeerTable_oid_to_index( + v, name, length, + entityLdpId, entityIndex, peerLdpId); + + ctl_nbr = ldpPeerTable_lookup_next(first, *peerLdpId); + return ctl_nbr; + } + return NULL; +} + +static uint8_t *ldpPeerTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t entityIndex = 0; + struct in_addr peerLdpId = {.s_addr = 0}; + struct ctl_nbr *ctl_nbr; + + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + ctl_nbr = ldpPeerTable_lookup(v, name, length, exact, &entityLdpId, + &entityIndex, &peerLdpId); + + if (!ctl_nbr) + return NULL; + + if (!exact) { + + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + entityIndex = LDP_DEFAULT_ENTITY_INDEX; + peerLdpId = ctl_nbr->id; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + oid_copy_in_addr(name + v->namelen, &entityLdpId); + + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = entityIndex; + oid_copy_in_addr(name + v->namelen + 7, &peerLdpId); + name[v->namelen + 11] = 0; + name[v->namelen + 12] = 0; + + /* Set length */ + *length = v->namelen + LDP_PEER_ENTRY_MAX_IDX_LEN; + } + + switch (v->magic) { + case MPLSLDPPEERLDPID: + *var_len = 6; + memcpy(snmp_ldp_rtrid, &ctl_nbr->id, IN_ADDR_SIZE); + return snmp_ldp_rtrid; + case MPLSLDPPEERLABELDISTMETHOD: + return SNMP_INTEGER(DOWNSTREAMUNSOLICITED); + case MPLSLDPPEERPATHVECTORLIMIT: + return SNMP_INTEGER(0); + case MPLSLDPPEERTRANSPORTADDRTYPE: + if (ctl_nbr->af == AF_INET) + return SNMP_INTEGER(MPLSLDPPEERTRANSPORTADDRTYPE_IPV4); + else + return SNMP_INTEGER(MPLSLDPPEERTRANSPORTADDRTYPE_IPV6); + case MPLSLDPPEERTRANSPORTADDR: + if (ctl_nbr->af == AF_INET) { + *var_len = sizeof(ctl_nbr->raddr.v4); + return ((uint8_t *)&ctl_nbr->raddr.v4); + } else { + *var_len = sizeof(ctl_nbr->raddr.v6); + return ((uint8_t *)&ctl_nbr->raddr.v6); + } + default: + return NULL; + } + + return NULL; +} +static uint8_t *ldpSessionTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t entityIndex = 0; + struct in_addr peerLdpId = {.s_addr = 0}; + struct ctl_nbr *ctl_nbr; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + ctl_nbr = ldpPeerTable_lookup(v, name, length, exact, &entityLdpId, + &entityIndex, &peerLdpId); + + if (!ctl_nbr) + return NULL; + + if (!exact) { + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + entityIndex = LDP_DEFAULT_ENTITY_INDEX; + peerLdpId = ctl_nbr->id; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + oid_copy_in_addr(name + v->namelen, &entityLdpId); + + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = entityIndex; + oid_copy_in_addr(name + v->namelen + 7, &peerLdpId); + name[v->namelen + 11] = 0; + name[v->namelen + 12] = 0; + + /* Set length */ + *length = v->namelen + LDP_PEER_ENTRY_MAX_IDX_LEN; + } + + switch (v->magic) { + case MPLSLDPSESSIONSTATELASTCHANGE: + *var_len = sizeof(time_t); + return (uint8_t *) &(ctl_nbr->uptime); + case MPLSLDPSESSIONSTATE: + switch (ctl_nbr->nbr_state) { + case NBR_STA_INITIAL: + return SNMP_INTEGER(MPLSLDPSESSIONSTATE_INITIALIZED); + case NBR_STA_OPENREC: + return SNMP_INTEGER(MPLSLDPSESSIONSTATE_OPENREC); + case NBR_STA_OPENSENT: + return SNMP_INTEGER(MPLSLDPSESSIONSTATE_OPENSENT); + case NBR_STA_OPER: + return SNMP_INTEGER(MPLSLDPSESSIONSTATE_OPERATIONAL); + default: + return SNMP_INTEGER(MPLSLDPSESSIONSTATE_NONEXISTENT); + } + case MPLSLDPSESSIONROLE: + if (ldp_addrcmp(ctl_nbr->af, &ctl_nbr->laddr, &ctl_nbr->raddr) + > 0) + return SNMP_INTEGER(MPLSLDPSESSIONROLE_ACTIVE); + else + return SNMP_INTEGER(MPLSLDPSESSIONROLE_PASSIVE); + case MPLSLDPSESSIONPROTOCOLVERSION: + return SNMP_INTEGER(LDP_VERSION); + case MPLSLDPSESSIONKEEPALIVEHOLDTIMEREM: + return SNMP_INTEGER(ctl_nbr->hold_time_remaining); + case MPLSLDPSESSIONKEEPALIVETIME: + return SNMP_INTEGER(ctl_nbr->holdtime); + case MPLSLDPSESSIONMAXPDULENGTH: + if (ctl_nbr->nbr_state == NBR_STA_OPER) + return SNMP_INTEGER(ctl_nbr->max_pdu_len); + else + return SNMP_INTEGER(LDP_MAX_LEN); + case MPLSLDPSESSIONDISCONTINUITYTIME: + return SNMP_INTEGER(0); /* not supported */ + default: + return NULL; + } + + return NULL; +} + +static uint8_t *ldpSessionStatsTable(struct variable *v, oid name[], + size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t entityIndex = 0; + struct in_addr peerLdpId = {.s_addr = 0}; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + struct ctl_nbr *ctl_nbr = ldpPeerTable_lookup(v, name, length, exact, + &entityLdpId, &entityIndex, &peerLdpId); + + if (!ctl_nbr) + return NULL; + + if (!exact) { + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + entityIndex = LDP_DEFAULT_ENTITY_INDEX; + peerLdpId = ctl_nbr->id; + + /* Copy the name out */ + memcpy(name, v->name, v->namelen * sizeof(oid)); + + /* Append index */ + oid_copy_in_addr(name + v->namelen, &entityLdpId); + name[v->namelen + 4] = 0; + name[v->namelen + 5] = 0; + name[v->namelen + 6] = entityIndex; + oid_copy_in_addr(name + v->namelen + 7, &peerLdpId); + name[v->namelen + 11] = 0; + name[v->namelen + 12] = 0; + + *length = v->namelen + LDP_PEER_ENTRY_MAX_IDX_LEN; + } + + switch (v->magic) { + case MPLSLDPSESSIONSTATSUNKNOWNMESTYPEERRORS: + return SNMP_INTEGER(ctl_nbr->stats.unknown_msg); + case MPLSLDPSESSIONSTATSUNKNOWNTLVERRORS: + return SNMP_INTEGER(ctl_nbr->stats.unknown_tlv); + default: + return NULL; + } + + return NULL; +} + +static struct variable ldpe_variables[] = { + {MPLS_LDP_LSR_ID, STRING, RONLY, ldpLsrId, 3, {1, 1, 1}}, + {MPLS_LDP_LSR_LOOP_DETECTION_CAPABLE, INTEGER, RONLY, + ldpLoopDetectCap, 3, {1, 1, 2}}, + {MPLS_LDP_ENTITY_LAST_CHANGE, TIMESTAMP, RONLY, ldpEntityLastChange, + 3, {1, 2, 1}}, + {MPLS_LDP_ENTITY_INDEX_NEXT, UNSIGNED32, RONLY, ldpEntityIndexNext, + 3, {1, 2, 2}}, + + /* MPLS LDP mplsLdpEntityTable. */ + {MPLSLDPENTITYLDPID, STRING, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 1}}, + {MPLSLDPENTITYINDEX, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 2}}, + {MPLSLDPENTITYPROTOCOLVERSION, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 3}}, + {MPLSLDPENTITYADMINSTATUS, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 4}}, + {MPLSLDPENTITYOPERSTATUS, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 5}}, + {MPLSLDPENTITYTCPPORT, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 6}}, + {MPLSLDPENTITYUDPDSCPORT, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 7}}, + {MPLSLDPENTITYMAXPDULENGTH, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 8}}, + {MPLSLDPENTITYKEEPALIVEHOLDTIMER, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 9}}, + {MPLSLDPENTITYHELLOHOLDTIMER, UNSIGNED32, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 10}}, + {MPLSLDPENTITYINITSESSIONTHRESHOLD, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 11}}, + {MPLSLDPENTITYLABELDISTMETHOD, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 12}}, + {MPLSLDPENTITYLABELRETENTIONMODE, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 13}}, + {MPLSLDPENTITYPATHVECTORLIMIT, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 14}}, + {MPLSLDPENTITYHOPCOUNTLIMIT, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 15}}, + {MPLSLDPENTITYTRANSPORTADDRKIND, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 16}}, + {MPLSLDPENTITYTARGETPEER, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 17}}, + {MPLSLDPENTITYTARGETPEERADDRTYPE, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 18}}, + {MPLSLDPENTITYTARGETPEERADDR, STRING, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 19}}, + {MPLSLDPENTITYLABELTYPE, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 20}}, + {MPLSLDPENTITYDISCONTINUITYTIME, TIMESTAMP, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 21}}, + {MPLSLDPENTITYSTORAGETYPE, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 22}}, + {MPLSLDPENTITYROWSTATUS, INTEGER, RONLY, ldpEntityTable, + 5, {1, 2, 3, 1, 23}}, + + /* MPLS LDP mplsLdpEntityStatsTable. */ + { MPLSLDPENTITYSTATSSESSIONATTEMPTS, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 1}}, + { MPLSLDPENTITYSTATSSESSIONREJHELLO, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 2}}, + { MPLSLDPENTITYSTATSSESSIONREJAD, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 3}}, + { MPLSLDPENTITYSTATSSESSIONREJMAXPDU, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 4}}, + { MPLSLDPENTITYSTATSSESSIONREJLR, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 5}}, + { MPLSLDPENTITYSTATSBADLDPID, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 6}}, + { MPLSLDPENTITYSTATSBADPDULENGTH, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 7}}, + { MPLSLDPENTITYSTATSBADMSGLENGTH, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 8}}, + { MPLSLDPENTITYSTATSBADTLVLENGTH, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 9}}, + { MPLSLDPENTITYSTATSMALFORMEDTLV, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 10}}, + { MPLSLDPENTITYSTATSKEEPALIVEEXP, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 11}}, + { MPLSLDPENTITYSTATSSHUTDOWNRCVNOTIFY, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 12}}, + { MPLSLDPENTITYSTATSSHUTDOWNSENTNOTIFY, COUNTER32, RONLY, + ldpEntityStatsTable, 5, {1, 2, 4, 1, 13}}, + + /* MPLS LDP mplsLdpPeerTable */ + {MPLSLDPPEERLDPID, STRING, RONLY, ldpPeerTable, 5, {1, 3, 2, 1, 1}}, + {MPLSLDPPEERLABELDISTMETHOD, INTEGER, RONLY, ldpPeerTable, + 5, {1, 3, 2, 1, 2}}, + {MPLSLDPPEERPATHVECTORLIMIT, INTEGER, RONLY, ldpPeerTable, + 5, {1, 3, 2, 1, 3}}, + {MPLSLDPPEERTRANSPORTADDRTYPE, INTEGER, RONLY, ldpPeerTable, + 5, {1, 3, 2, 1, 4}}, + {MPLSLDPPEERTRANSPORTADDR, STRING, RONLY, ldpPeerTable, + 5, {1, 3, 2, 1, 5}}, + + /* MPLS LDP mplsLdpSessionTable */ + {MPLSLDPSESSIONSTATELASTCHANGE, TIMESTAMP, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 1}}, + {MPLSLDPSESSIONSTATE, INTEGER, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 2}}, + {MPLSLDPSESSIONROLE, INTEGER, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 3}}, + {MPLSLDPSESSIONPROTOCOLVERSION, UNSIGNED32, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 4}}, + {MPLSLDPSESSIONKEEPALIVEHOLDTIMEREM, INTEGER, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 5}}, + {MPLSLDPSESSIONKEEPALIVETIME, UNSIGNED32, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 6}}, + {MPLSLDPSESSIONMAXPDULENGTH, UNSIGNED32, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 7}}, + {MPLSLDPSESSIONDISCONTINUITYTIME, TIMESTAMP, RONLY, ldpSessionTable, + 5, {1, 3, 3, 1, 8}}, + + /* MPLS LDP mplsLdpSessionStatsTable */ + {MPLSLDPSESSIONSTATSUNKNOWNMESTYPEERRORS, COUNTER32, RONLY, + ldpSessionStatsTable, 5, {1, 3, 4, 1, 1}}, + {MPLSLDPSESSIONSTATSUNKNOWNTLVERRORS, COUNTER32, RONLY, + ldpSessionStatsTable, 5, {1, 3, 4, 1, 2}}, + + /* MPLS LDP mplsLdpHelloAdjacencyTable. */ + {MPLSLDPHELLOADJACENCYINDEX, UNSIGNED32, RONLY, + ldpHelloAdjacencyTable, 6, {1, 3, 5, 1, 1, 1}}, + {MPLSLDPHELLOADJACENCYHOLDTIMEREM, INTEGER, RONLY, + ldpHelloAdjacencyTable, 6, {1, 3, 5, 1, 1, 2}}, + {MPLSLDPHELLOADJACENCYHOLDTIME, UNSIGNED32, RONLY, + ldpHelloAdjacencyTable, 6, {1, 3, 5, 1, 1, 3}}, + {MPLSLDPHELLOADJACENCYTYPE, INTEGER, RONLY, + ldpHelloAdjacencyTable, 6, {1, 3, 5, 1, 1, 4}}, +}; + +static struct variable lde_variables[] = { +}; + +static struct trap_object ldpSessionTrapList[] = { + {5, {1, 3, 3, 1, MPLSLDPSESSIONSTATE}}, + {5, {1, 3, 3, 1, MPLSLDPSESSIONDISCONTINUITYTIME}}, + {5, {1, 3, 4, 1, MPLSLDPSESSIONSTATSUNKNOWNMESTYPEERRORS}}, + {5, {1, 3, 4, 1, MPLSLDPSESSIONSTATSUNKNOWNTLVERRORS}}}; + +/* LDP TRAP. */ +#define LDPINITSESSIONTHRESHOLDEXCEEDED 1 +#define LDPPATHVECTORLIMITMISMATCH 2 +#define LDPSESSIONUP 3 +#define LDPSESSIONDOWN 4 + +static void +ldpTrapSession(struct nbr * nbr, unsigned int sptrap) +{ + oid index[sizeof(oid) * (LDP_PEER_ENTRY_MAX_IDX_LEN + 1)]; + + struct in_addr entityLdpId = {.s_addr = 0}; + uint32_t entityIndex = 0; + struct in_addr peerLdpId = {.s_addr = 0}; + + struct ctl_nbr *ctl_nbr = nbr_to_ctl(nbr); + + entityLdpId.s_addr = ldp_rtr_id_get(leconf); + entityIndex = LDP_DEFAULT_ENTITY_INDEX; + peerLdpId = ctl_nbr->id; + + oid_copy_in_addr(index, &entityLdpId); + index[4] = 0; + index[5] = 0; + index[6] = entityIndex; + oid_copy_in_addr(&index[7], &peerLdpId); + index[11] = 0; + index[12] = 0; + + index[LDP_PEER_ENTRY_MAX_IDX_LEN] = 0; + + smux_trap(ldpe_variables, array_size(ldpe_variables), ldp_trap_oid, + array_size(ldp_trap_oid), ldp_oid, + sizeof(ldp_oid) / sizeof(oid), index, + LDP_PEER_ENTRY_MAX_IDX_LEN + 1, + ldpSessionTrapList, array_size(ldpSessionTrapList), sptrap); +} + +static void +ldpTrapSessionUp(struct nbr * nbr) +{ + ldpTrapSession(nbr, LDPSESSIONUP); +} + +static void +ldpTrapSessionDown(struct nbr * nbr) +{ + ldpTrapSession(nbr, LDPSESSIONDOWN); +} + +static int ldp_snmp_agentx_enabled(void) +{ + main_imsg_compose_both(IMSG_AGENTX_ENABLED, NULL, 0); + + return 0; +} + +static int ldp_snmp_nbr_state_change(struct nbr * nbr, int old_state) +{ + if (old_state == nbr->state) + return 0; + + if (nbr->state == NBR_STA_OPER) + ldpTrapSessionUp(nbr); + else if (old_state == NBR_STA_OPER) + ldpTrapSessionDown(nbr); + + return 0; +} + +static int ldp_snmp_init(struct event_loop *tm) +{ + hook_register(agentx_enabled, ldp_snmp_agentx_enabled); + + smux_init(tm); + + return 0; +} + +static int ldp_snmp_register_mib(struct event_loop *tm) +{ + static int registered = 0; + + if (registered) + return 0; + + registered = 1; + + smux_init(tm); + + smux_agentx_enable(); + + if (ldpd_process == PROC_LDE_ENGINE) + REGISTER_MIB("mibII/ldp", lde_variables, variable, ldp_oid); + else if (ldpd_process == PROC_LDP_ENGINE) { + REGISTER_MIB("mibII/ldp", ldpe_variables, variable, ldp_oid); + + hook_register(ldp_nbr_state_change, ldp_snmp_nbr_state_change); + } + + return 0; +} + +static int ldp_snmp_module_init(void) +{ + if (ldpd_process == PROC_MAIN) + hook_register(frr_late_init, ldp_snmp_init); + else + hook_register(ldp_register_mib, ldp_snmp_register_mib); + + return 0; +} + +FRR_MODULE_SETUP( + .name = "ldp_snmp", + .version = FRR_VERSION, + .description = "ldp AgentX SNMP module", + .init = ldp_snmp_module_init, +); diff --git a/ldpd/ldp_vty.h b/ldpd/ldp_vty.h new file mode 100644 index 0000000..5c83d1c --- /dev/null +++ b/ldpd/ldp_vty.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#ifndef _LDP_VTY_H_ +#define _LDP_VTY_H_ + +#include "vty.h" + +extern struct cmd_node ldp_node; +extern struct cmd_node ldp_ipv4_node; +extern struct cmd_node ldp_ipv6_node; +extern struct cmd_node ldp_ipv4_iface_node; +extern struct cmd_node ldp_ipv6_iface_node; +extern struct cmd_node ldp_l2vpn_node; +extern struct cmd_node ldp_pseudowire_node; +extern struct cmd_node ldp_debug_node; + +union ldpd_addr; +int ldp_get_address(const char *, int *, union ldpd_addr *); +int ldp_vty_mpls_ldp (struct vty *, const char *); +int ldp_vty_allow_broken_lsp(struct vty *, const char *); +int ldp_vty_address_family (struct vty *, const char *, const char *); +int ldp_vty_disc_holdtime(struct vty *, const char *, enum hello_type, long); +int ldp_vty_disc_interval(struct vty *, const char *, enum hello_type, long); +int ldp_vty_targeted_hello_accept(struct vty *, const char *, const char *); +int ldp_vty_nbr_session_holdtime(struct vty *, const char *, struct in_addr, long); +int ldp_vty_af_session_holdtime(struct vty *, const char *, long); +int ldp_vty_interface(struct vty *, const char *, const char *); +int ldp_vty_trans_addr(struct vty *, const char *, const char *); +int ldp_vty_neighbor_targeted(struct vty *, const char *, const char *); +int ldp_vty_label_advertise(struct vty *, const char *, const char *, const char *); +int ldp_vty_label_allocate(struct vty *, const char *, const char *, const char *); +int ldp_vty_label_expnull(struct vty *, const char *, const char *); +int ldp_vty_label_accept(struct vty *, const char *, const char *, const char *); +int ldp_vty_ttl_security(struct vty *, const char *); +int ldp_vty_router_id(struct vty *, const char *, struct in_addr); +int ldp_vty_ordered_control(struct vty *, const char *); +int ldp_vty_wait_for_sync_interval(struct vty *, const char *, long); +int ldp_vty_ds_cisco_interop(struct vty *, const char *); +int ldp_vty_trans_pref_ipv4(struct vty *, const char *); +int ldp_vty_neighbor_password(struct vty *, const char *, struct in_addr, const char *); +int ldp_vty_neighbor_ttl_security(struct vty *, const char *, struct in_addr, const char *); +int ldp_vty_l2vpn(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_bridge(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_mtu(struct vty *, const char *, long); +int ldp_vty_l2vpn_pwtype(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_interface(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_pseudowire(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_pw_cword(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_pw_nbr_addr(struct vty *, const char *, const char *); +int ldp_vty_l2vpn_pw_nbr_id(struct vty *, const char *, struct in_addr); +int ldp_vty_l2vpn_pw_pwid(struct vty *, const char *, long); +int ldp_vty_l2vpn_pw_pwstatus(struct vty *, const char *); +int ldp_vty_clear_nbr(struct vty *, const char *); +int ldp_vty_debug(struct vty *, const char *, const char *, const char *, const char *); +int ldp_vty_show_binding(struct vty *, const char *, const char *, int, + const char *, unsigned long, unsigned long, const char *, const char *); +int ldp_vty_show_discovery(struct vty *, const char *, const char *, const char *); +int ldp_vty_show_interface(struct vty *, const char *, const char *); +int ldp_vty_show_capabilities(struct vty *, const char *); +int ldp_vty_show_neighbor(struct vty *, const char *, int, const char *, const char *); +int ldp_vty_show_ldp_sync(struct vty *, const char *); +int ldp_vty_show_atom_binding(struct vty *, const char *, unsigned long, + unsigned long, const char *); +int ldp_vty_show_atom_vc(struct vty *, const char *, const char *, + const char *, const char *); +int ldp_vty_show_debugging(struct vty *); + +void ldp_vty_init(void); + +#endif /* _LDP_VTY_H_ */ diff --git a/ldpd/ldp_vty_cmds.c b/ldpd/ldp_vty_cmds.c new file mode 100644 index 0000000..e046ae9 --- /dev/null +++ b/ldpd/ldp_vty_cmds.c @@ -0,0 +1,902 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#include + +#include "command.h" +#include "vty.h" +#include "json.h" + +#include "ldpd/ldpd.h" +#include "ldpd/ldp_vty.h" +#include "ldpd/ldp_vty_cmds_clippy.c" + +DEFPY_NOSH(ldp_mpls_ldp, + ldp_mpls_ldp_cmd, + "mpls ldp", + "Global MPLS configuration subcommands\n" + "Label Distribution Protocol\n") +{ + return (ldp_vty_mpls_ldp(vty, NULL)); +} + +DEFPY (no_ldp_mpls_ldp, + no_ldp_mpls_ldp_cmd, + "no mpls ldp", + NO_STR + "Global MPLS configuration subcommands\n" + "Label Distribution Protocol\n") +{ + return (ldp_vty_mpls_ldp(vty, "no")); +} + +DEFPY_NOSH(ldp_l2vpn, + ldp_l2vpn_cmd, + "l2vpn WORD$l2vpn_name type vpls", + "Configure l2vpn commands\n" + "L2VPN name\n" + "L2VPN type\n" + "Virtual Private LAN Service\n") +{ + return (ldp_vty_l2vpn(vty, NULL, l2vpn_name)); +} + +DEFPY (no_ldp_l2vpn, + no_ldp_l2vpn_cmd, + "no l2vpn WORD$l2vpn_name type vpls", + NO_STR + "Configure l2vpn commands\n" + "L2VPN name\n" + "L2VPN type\n" + "Virtual Private LAN Service\n") +{ + return (ldp_vty_l2vpn(vty, "no", l2vpn_name)); +} + +DEFPY_NOSH(ldp_address_family, + ldp_address_family_cmd, + "address-family $af", + "Configure Address Family and its parameters\n" + "IPv4\n" + "IPv6\n") +{ + return (ldp_vty_address_family(vty, NULL, af)); +} + +DEFPY (no_ldp_address_family, + no_ldp_address_family_cmd, + "no address-family $af", + NO_STR + "Configure Address Family and its parameters\n" + "IPv4\n" + "IPv6\n") +{ + return (ldp_vty_address_family(vty, "no", af)); +} + +DEFPY_NOSH(ldp_exit_address_family, + ldp_exit_address_family_cmd, + "exit-address-family", + "Exit from Address Family configuration mode\n") +{ + if (vty->node == LDP_IPV4_NODE || vty->node == LDP_IPV6_NODE) + vty->node = LDP_NODE; + return CMD_SUCCESS; +} + +DEFPY (ldp_discovery_link_holdtime, + ldp_discovery_link_holdtime_cmd, + "[no] discovery hello holdtime (1-65535)$holdtime", + NO_STR + "Configure discovery parameters\n" + "LDP Link Hellos\n" + "Hello holdtime\n" + "Time (seconds) - 65535 implies infinite\n") +{ + return (ldp_vty_disc_holdtime(vty, no, HELLO_LINK, holdtime)); +} + +DEFPY (ldp_discovery_targeted_holdtime, + ldp_discovery_targeted_holdtime_cmd, + "[no] discovery targeted-hello holdtime (1-65535)$holdtime", + NO_STR + "Configure discovery parameters\n" + "LDP Targeted Hellos\n" + "Hello holdtime\n" + "Time (seconds) - 65535 implies infinite\n") +{ + return (ldp_vty_disc_holdtime(vty, no, HELLO_TARGETED, holdtime)); +} + +DEFPY (ldp_discovery_link_interval, + ldp_discovery_link_interval_cmd, + "[no] discovery hello interval (1-65535)$interval", + NO_STR + "Configure discovery parameters\n" + "LDP Link Hellos\n" + "Hello interval\n" + "Time (seconds)\n") +{ + return (ldp_vty_disc_interval(vty, no, HELLO_LINK, interval)); +} + +DEFPY (ldp_discovery_targeted_interval, + ldp_discovery_targeted_interval_cmd, + "[no] discovery targeted-hello interval (1-65535)$interval", + NO_STR + "Configure discovery parameters\n" + "LDP Targeted Hellos\n" + "Hello interval\n" + "Time (seconds)\n") +{ + return (ldp_vty_disc_interval(vty, no, HELLO_TARGETED, interval)); +} + +DEFPY (ldp_dual_stack_transport_connection_prefer_ipv4, + ldp_dual_stack_transport_connection_prefer_ipv4_cmd, + "[no] dual-stack transport-connection prefer ipv4", + NO_STR + "Configure dual stack parameters\n" + "Configure TCP transport parameters\n" + "Configure preferred address family for TCP transport connection with neighbor\n" + "IPv4\n") +{ + return (ldp_vty_trans_pref_ipv4(vty, no)); +} + +DEFPY (ldp_dual_stack_cisco_interop, + ldp_dual_stack_cisco_interop_cmd, + "[no] dual-stack cisco-interop", + NO_STR + "Configure dual stack parameters\n" + "Use Cisco non-compliant format to send and interpret the Dual-Stack capability TLV\n") +{ + return (ldp_vty_ds_cisco_interop(vty, no)); +} + +DEFPY (ldp_neighbor_password, + ldp_neighbor_password_cmd, + "[no] neighbor A.B.C.D$neighbor password WORD$password", + NO_STR + "Configure neighbor parameters\n" + "LDP Id of neighbor\n" + "Configure password for MD5 authentication\n" + "The password\n") +{ + return (ldp_vty_neighbor_password(vty, no, neighbor, password)); +} + +DEFPY (ldp_neighbor_session_holdtime, + ldp_neighbor_session_holdtime_cmd, + "[no] neighbor A.B.C.D$neighbor session holdtime (15-65535)$holdtime", + NO_STR + "Configure neighbor parameters\n" + "LDP Id of neighbor\n" + "Configure session parameters\n" + "Configure session holdtime\n" + "Time (seconds)\n") +{ + return (ldp_vty_nbr_session_holdtime(vty, no, neighbor, holdtime)); +} + +DEFPY (ldp_neighbor_ttl_security, + ldp_neighbor_ttl_security_cmd, + "[no] neighbor A.B.C.D$neighbor ttl-security ", + NO_STR + "Configure neighbor parameters\n" + "LDP Id of neighbor\n" + "LDP ttl security check\n" + "Disable ttl security\n" + "IP hops\n" + "maximum number of hops\n") +{ + return (ldp_vty_neighbor_ttl_security(vty, no, neighbor, hops_str)); +} + +DEFPY (ldp_router_id, + ldp_router_id_cmd, + "[no] router-id A.B.C.D$address", + NO_STR + "Configure router Id\n" + "LSR Id (in form of an IPv4 address)\n") +{ + return (ldp_vty_router_id(vty, no, address)); +} + +DEFPY (ldp_ordered_control, + ldp_ordered_control_cmd, + "[no] ordered-control", + NO_STR + "Configure LDP ordered label distribution control mode\n") +{ + return (ldp_vty_ordered_control(vty, no)); +} + +DEFPY (ldp_wait_for_sync, + ldp_wait_for_sync_cmd, + "[no] wait-for-sync (1-10000)$waitforsync", + NO_STR + "Time to wait for LDP-IGP Sync to complete label exchange\n" + "Time (seconds)\n") +{ + return (ldp_vty_wait_for_sync_interval(vty, no, waitforsync)); + +} + +DEFPY (ldp_allow_broken_lsps, + ldp_allow_broken_lsps_cmd, + "[no] install allow-broken-lsps", + NO_STR + "install lsps\n" + "if no remote-label install with imp-null\n") +{ + return (ldp_vty_allow_broken_lsp(vty, no)); +} + +DEFPY (ldp_discovery_targeted_hello_accept, + ldp_discovery_targeted_hello_accept_cmd, + "[no] discovery targeted-hello accept [from ACCESSLIST_NAME$from_acl]", + NO_STR + "Configure discovery parameters\n" + "LDP Targeted Hellos\n" + "Accept and respond to targeted hellos\n" + "Access list to specify acceptable targeted hello source\n" + "IP access-list name\n") +{ + return (ldp_vty_targeted_hello_accept(vty, no, from_acl)); +} + +DEFPY (ldp_discovery_transport_address_ipv4, + ldp_discovery_transport_address_ipv4_cmd, + "[no] discovery transport-address A.B.C.D$address", + NO_STR + "Configure discovery parameters\n" + "Specify transport address for TCP connection\n" + "IP address to be used as transport address\n") +{ + return (ldp_vty_trans_addr(vty, no, address_str)); +} + +DEFPY (ldp_discovery_transport_address_ipv6, + ldp_discovery_transport_address_ipv6_cmd, + "[no] discovery transport-address X:X::X:X$address", + NO_STR + "Configure discovery parameters\n" + "Specify transport address for TCP connection\n" + "IPv6 address to be used as transport address\n") +{ + return (ldp_vty_trans_addr(vty, no, address_str)); +} + +DEFPY (ldp_label_local_advertise, + ldp_label_local_advertise_cmd, + "[no] label local advertise [{to ACCESSLIST_NAME$to_acl|for ACCESSLIST_NAME$for_acl}]", + NO_STR + "Configure label control and policies\n" + "Configure local label control and policies\n" + "Configure outbound label advertisement control\n" + "IP Access-list specifying controls on LDP Peers\n" + "IP access-list name\n" + "IP access-list for destination prefixes\n" + "IP access-list name\n") +{ + return (ldp_vty_label_advertise(vty, no, to_acl, for_acl)); +} + +DEFPY (ldp_label_local_advertise_explicit_null, + ldp_label_local_advertise_explicit_null_cmd, + "[no] label local advertise explicit-null [for ACCESSLIST_NAME$for_acl]", + NO_STR + "Configure label control and policies\n" + "Configure local label control and policies\n" + "Configure outbound label advertisement control\n" + "Configure explicit-null advertisement\n" + "IP access-list for destination prefixes\n" + "IP access-list name\n") +{ + return (ldp_vty_label_expnull(vty, no, for_acl)); +} + +DEFPY (ldp_label_local_allocate, + ldp_label_local_allocate_cmd, + "[no] label local allocate ", + NO_STR + "Configure label control and policies\n" + "Configure local label control and policies\n" + "Configure label allocation control\n" + "allocate local label for host routes only\n" + "IP access-list\n" + "IP access-list name\n") +{ + return (ldp_vty_label_allocate(vty, no, host_routes, for_acl)); +} + +DEFPY (ldp_label_remote_accept, + ldp_label_remote_accept_cmd, + "[no] label remote accept {from ACCESSLIST_NAME$from_acl|for ACCESSLIST_NAME$for_acl}", + NO_STR + "Configure label control and policies\n" + "Configure remote/peer label control and policies\n" + "Configure inbound label acceptance control\n" + "Neighbor from whom to accept label advertisement\n" + "IP access-list name\n" + "IP access-list for destination prefixes\n" + "IP access-list name\n") +{ + return (ldp_vty_label_accept(vty, no, from_acl, for_acl)); +} + +DEFPY (ldp_ttl_security_disable, + ldp_ttl_security_disable_cmd, + "[no] ttl-security disable", + NO_STR + "LDP ttl security check\n" + "Disable ttl security\n") +{ + return (ldp_vty_ttl_security(vty, no)); +} + +DEFPY (ldp_session_holdtime, + ldp_session_holdtime_cmd, + "[no] session holdtime (15-65535)$holdtime", + NO_STR + "Configure session parameters\n" + "Configure session holdtime\n" + "Time (seconds)\n") +{ + return (ldp_vty_af_session_holdtime(vty, no, holdtime)); +} + +DEFPY_NOSH(ldp_interface, + ldp_interface_cmd, + "interface IFNAME$ifname", + "Enable LDP on an interface and enter interface submode\n" + "Interface's name\n") +{ + return (ldp_vty_interface(vty, NULL, ifname)); +} + +DEFPY (no_ldp_interface, + no_ldp_interface_cmd, + "no interface IFNAME$ifname", + NO_STR + "Enable LDP on an interface and enter interface submode\n" + "Interface's name\n") +{ + return (ldp_vty_interface(vty, "no", ifname)); +} + +DEFPY (ldp_neighbor_ipv4_targeted, + ldp_neighbor_ipv4_targeted_cmd, + "[no] neighbor A.B.C.D$address targeted", + NO_STR + "Configure neighbor parameters\n" + "IP address of neighbor\n" + "Establish targeted session\n") +{ + return (ldp_vty_neighbor_targeted(vty, no, address_str)); +} + +DEFPY (ldp_neighbor_ipv6_targeted, + ldp_neighbor_ipv6_targeted_cmd, + "[no] neighbor X:X::X:X$address targeted", + NO_STR + "Configure neighbor parameters\n" + "IPv6 address of neighbor\n" + "Establish targeted session\n") +{ + return (ldp_vty_neighbor_targeted(vty, no, address_str)); +} + +DEFPY (ldp_bridge, + ldp_bridge_cmd, + "[no] bridge IFNAME$ifname", + NO_STR + "Bridge interface\n" + "Interface's name\n") +{ + return (ldp_vty_l2vpn_bridge(vty, no, ifname)); +} + +DEFPY (ldp_mtu, + ldp_mtu_cmd, + "[no] mtu (1500-9180)$mtu", + NO_STR + "Set Maximum Transmission Unit\n" + "Maximum Transmission Unit value\n") +{ + return (ldp_vty_l2vpn_mtu(vty, no, mtu)); +} + +DEFPY (ldp_member_interface, + ldp_member_interface_cmd, + "[no] member interface IFNAME$ifname", + NO_STR + "L2VPN member configuration\n" + "Local interface\n" + "Interface's name\n") +{ + return (ldp_vty_l2vpn_interface(vty, no, ifname)); +} + +DEFPY_NOSH(ldp_member_pseudowire, + ldp_member_pseudowire_cmd, + "member pseudowire IFNAME$ifname", + "L2VPN member configuration\n" + "Pseudowire interface\n" + "Interface's name\n") +{ + return (ldp_vty_l2vpn_pseudowire(vty, NULL, ifname)); +} + +DEFPY (no_ldp_member_pseudowire, + no_ldp_member_pseudowire_cmd, + "no member pseudowire IFNAME$ifname", + NO_STR + "L2VPN member configuration\n" + "Pseudowire interface\n" + "Interface's name\n") +{ + return (ldp_vty_l2vpn_pseudowire(vty, "no", ifname)); +} + +DEFPY (ldp_vc_type, + ldp_vc_type_cmd, + "[no] vc type $vc_type", + NO_STR + "Virtual Circuit options\n" + "Virtual Circuit type to use\n" + "Ethernet (type 5)\n" + "Ethernet-tagged (type 4)\n") +{ + return (ldp_vty_l2vpn_pwtype(vty, no, vc_type)); +} + +DEFPY (ldp_control_word, + ldp_control_word_cmd, + "[no] control-word $preference", + NO_STR + "Control-word options\n" + "Exclude control-word in pseudowire packets\n" + "Include control-word in pseudowire packets\n") +{ + return (ldp_vty_l2vpn_pw_cword(vty, no, preference)); +} + +DEFPY (ldp_neighbor_address, + ldp_neighbor_address_cmd, + "[no] neighbor address $pw_address", + NO_STR + "Remote endpoint configuration\n" + "Specify the IPv4 or IPv6 address of the remote endpoint\n" + "IPv4 address\n" + "IPv6 address\n") +{ + return (ldp_vty_l2vpn_pw_nbr_addr(vty, no, pw_address_str)); +} + +DEFPY (ldp_neighbor_lsr_id, + ldp_neighbor_lsr_id_cmd, + "[no] neighbor lsr-id A.B.C.D$address", + NO_STR + "Remote endpoint configuration\n" + "Specify the LSR-ID of the remote endpoint\n" + "IPv4 address\n") +{ + return (ldp_vty_l2vpn_pw_nbr_id(vty, no, address)); +} + +DEFPY (ldp_pw_id, + ldp_pw_id_cmd, + "[no] pw-id (1-4294967295)$pwid", + NO_STR + "Set the Virtual Circuit ID\n" + "Virtual Circuit ID value\n") +{ + return (ldp_vty_l2vpn_pw_pwid(vty, no, pwid)); +} + +DEFPY (ldp_pw_status_disable, + ldp_pw_status_disable_cmd, + "[no] pw-status disable", + NO_STR + "Configure PW status\n" + "Disable PW status\n") +{ + return (ldp_vty_l2vpn_pw_pwstatus(vty, no)); +} + +DEFPY (ldp_clear_mpls_ldp_neighbor, + ldp_clear_mpls_ldp_neighbor_cmd, + "clear mpls ldp neighbor []$address", + "Reset functions\n" + "Reset MPLS statistical information\n" + "Clear LDP state\n" + "Clear LDP neighbor sessions\n" + "IPv4 address\n" + "IPv6 address\n") +{ + return (ldp_vty_clear_nbr(vty, address_str)); +} + +DEFPY (ldp_debug_mpls_ldp_discovery_hello, + ldp_debug_mpls_ldp_discovery_hello_cmd, + "[no] debug mpls ldp discovery hello $dir", + NO_STR + "Debugging functions\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Discovery messages\n" + "Discovery hello message\n" + "Received messages\n" + "Sent messages\n") +{ + return (ldp_vty_debug(vty, no, "discovery", dir, NULL)); +} + +DEFPY (ldp_debug_mpls_ldp_type, + ldp_debug_mpls_ldp_type_cmd, + "[no] debug mpls ldp $type", + NO_STR + "Debugging functions\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Errors\n" + "LDP event information\n" + "LDP label allocation information\n" + "LDP sync information\n" + "LDP zebra information\n") +{ + return (ldp_vty_debug(vty, no, type, NULL, NULL)); +} + +DEFPY (ldp_debug_mpls_ldp_messages_recv, + ldp_debug_mpls_ldp_messages_recv_cmd, + "[no] debug mpls ldp messages recv [all]$all", + NO_STR + "Debugging functions\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Messages\n" + "Received messages, excluding periodic Keep Alives\n" + "Received messages, including periodic Keep Alives\n") +{ + return (ldp_vty_debug(vty, no, "messages", "recv", all)); +} + +DEFPY (ldp_debug_mpls_ldp_messages_sent, + ldp_debug_mpls_ldp_messages_sent_cmd, + "[no] debug mpls ldp messages sent [all]$all", + NO_STR + "Debugging functions\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Messages\n" + "Sent messages, excluding periodic Keep Alives\n" + "Sent messages, including periodic Keep Alives\n") +{ + return (ldp_vty_debug(vty, no, "messages", "sent", all)); +} + +DEFPY (ldp_show_mpls_ldp_binding, + ldp_show_mpls_ldp_binding_cmd, + "show mpls ldp []$af binding\ + [$prefix [longer-prefixes$longer_prefixes]]\ + [{\ + neighbor A.B.C.D$nbr\ + |local-label (0-1048575)$local_label\ + |remote-label (0-1048575)$remote_label\ + }]\ + [detail]$detail [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "IPv4 Address Family\n" + "IPv6 Address Family\n" + "Label Information Base (LIB) information\n" + "Destination prefix (IPv4)\n" + "Destination prefix (IPv6)\n" + "Include longer matches\n" + "Display labels from LDP neighbor\n" + "Neighbor LSR-ID\n" + "Match locally assigned label values\n" + "Locally assigned label value\n" + "Match remotely assigned label values\n" + "Remotely assigned label value\n" + "Show detailed information\n" + JSON_STR) +{ + if (!(ldpd_conf->flags & F_LDPD_ENABLED)) + return CMD_SUCCESS; + if (!local_label_str) + local_label = NO_LABEL; + if (!remote_label_str) + remote_label = NO_LABEL; + return (ldp_vty_show_binding(vty, af, prefix_str, !!longer_prefixes, + nbr_str, local_label, remote_label, detail, json)); +} + +DEFPY (ldp_show_mpls_ldp_discovery, + ldp_show_mpls_ldp_discovery_cmd, + "show mpls ldp []$af discovery [detail]$detail [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "IPv4 Address Family\n" + "IPv6 Address Family\n" + "Discovery Hello Information\n" + "Show detailed information\n" + JSON_STR) +{ + return (ldp_vty_show_discovery(vty, af, detail, json)); +} + +DEFPY (ldp_show_mpls_ldp_interface, + ldp_show_mpls_ldp_interface_cmd, + "show mpls ldp []$af interface [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "IPv4 Address Family\n" + "IPv6 Address Family\n" + "interface information\n" + JSON_STR) +{ + return (ldp_vty_show_interface(vty, af, json)); +} + +DEFPY (ldp_show_mpls_ldp_capabilities, + ldp_show_mpls_ldp_capabilities_cmd, + "show mpls ldp capabilities [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Display LDP Capabilities information\n" + JSON_STR) +{ + return (ldp_vty_show_capabilities(vty, json)); +} + +DEFPY (ldp_show_mpls_ldp_neighbor, + ldp_show_mpls_ldp_neighbor_cmd, + "show mpls ldp neighbor [A.B.C.D]$lsr_id [detail]$detail [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Neighbor information\n" + "Neighbor LSR-ID\n" + "Show detailed information\n" + JSON_STR) +{ + return (ldp_vty_show_neighbor(vty, lsr_id_str, 0, detail, json)); +} + +DEFPY (ldp_show_mpls_ldp_neighbor_capabilities, + ldp_show_mpls_ldp_neighbor_capabilities_cmd, + "show mpls ldp neighbor [A.B.C.D]$lsr_id capabilities [json]$json", + "Show running system information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "Neighbor information\n" + "Neighbor LSR-ID\n" + "Display neighbor capability information\n" + JSON_STR) +{ + return (ldp_vty_show_neighbor(vty, lsr_id_str, 1, NULL, json)); +} + +DEFPY (ldp_show_mpls_ldp_igp_sync, + ldp_show_mpls_ldp_igp_sync_cmd, + "show mpls ldp igp-sync [json]$json", + "Show mpls ldp ldp-sync information\n" + "MPLS information\n" + "Label Distribution Protocol\n" + "LDP-IGP Sync information\n" + JSON_STR) +{ + return (ldp_vty_show_ldp_sync(vty, json)); +} + +DEFPY (ldp_show_l2vpn_atom_binding, + ldp_show_l2vpn_atom_binding_cmd, + "show l2vpn atom binding\ + [{\ + A.B.C.D$peer\ + |local-label (16-1048575)$local_label\ + |remote-label (16-1048575)$remote_label\ + }]\ + [json]$json", + "Show running system information\n" + "Show information about Layer2 VPN\n" + "Show Any Transport over MPLS information\n" + "Show AToM label binding information\n" + "Destination address of the VC\n" + "Match locally assigned label values\n" + "Locally assigned label value\n" + "Match remotely assigned label values\n" + "Remotely assigned label value\n" + JSON_STR) +{ + if (!local_label_str) + local_label = NO_LABEL; + if (!remote_label_str) + remote_label = NO_LABEL; + return (ldp_vty_show_atom_binding(vty, peer_str, local_label, + remote_label, json)); +} + +DEFPY (ldp_show_l2vpn_atom_vc, + ldp_show_l2vpn_atom_vc_cmd, + "show l2vpn atom vc\ + [{\ + A.B.C.D$peer\ + |interface IFNAME$ifname\ + |vc-id (1-4294967295)$vcid\ + }]\ + [json]$json", + "Show running system information\n" + "Show information about Layer2 VPN\n" + "Show Any Transport over MPLS information\n" + "Show AToM virtual circuit information\n" + "Destination address of the VC\n" + "Local interface of the pseudowire\n" + "Interface's name\n" + "VC ID\n" + "VC ID\n" + JSON_STR) +{ + return (ldp_vty_show_atom_vc(vty, peer_str, ifname, vcid_str, json)); +} + +DEFPY_NOSH (ldp_show_debugging_mpls_ldp, + ldp_show_debugging_mpls_ldp_cmd, + "show debugging [mpls ldp]", + "Show running system information\n" + "Debugging functions\n" + "MPLS information\n" + "Label Distribution Protocol\n") +{ + ldp_vty_show_debugging(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static void +l2vpn_autocomplete(vector comps, struct cmd_token *token) +{ + struct l2vpn *l2vpn; + + RB_FOREACH(l2vpn, l2vpn_head, &vty_conf->l2vpn_tree) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, l2vpn->name)); +} + +static const struct cmd_variable_handler l2vpn_var_handlers[] = { + { + .varname = "l2vpn_name", + .completions = l2vpn_autocomplete + }, + { + .completions = NULL + } +}; + +void +ldp_vty_init (void) +{ + cmd_variable_handler_register(l2vpn_var_handlers); + + install_node(&ldp_node); + install_node(&ldp_ipv4_node); + install_node(&ldp_ipv6_node); + install_node(&ldp_ipv4_iface_node); + install_node(&ldp_ipv6_iface_node); + install_node(&ldp_l2vpn_node); + install_node(&ldp_pseudowire_node); + install_node(&ldp_debug_node); + install_default(LDP_NODE); + install_default(LDP_IPV4_NODE); + install_default(LDP_IPV6_NODE); + install_default(LDP_IPV4_IFACE_NODE); + install_default(LDP_IPV6_IFACE_NODE); + install_default(LDP_L2VPN_NODE); + install_default(LDP_PSEUDOWIRE_NODE); + + install_element(CONFIG_NODE, &ldp_mpls_ldp_cmd); + install_element(CONFIG_NODE, &no_ldp_mpls_ldp_cmd); + install_element(CONFIG_NODE, &ldp_l2vpn_cmd); + install_element(CONFIG_NODE, &no_ldp_l2vpn_cmd); + install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_discovery_hello_cmd); + install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_type_cmd); + install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_messages_recv_cmd); + install_element(CONFIG_NODE, &ldp_debug_mpls_ldp_messages_sent_cmd); + + install_element(LDP_NODE, &ldp_address_family_cmd); + install_element(LDP_NODE, &no_ldp_address_family_cmd); + install_element(LDP_NODE, &ldp_discovery_link_holdtime_cmd); + install_element(LDP_NODE, &ldp_discovery_targeted_holdtime_cmd); + install_element(LDP_NODE, &ldp_discovery_link_interval_cmd); + install_element(LDP_NODE, &ldp_discovery_targeted_interval_cmd); + install_element(LDP_NODE, &ldp_dual_stack_transport_connection_prefer_ipv4_cmd); + install_element(LDP_NODE, &ldp_dual_stack_cisco_interop_cmd); + install_element(LDP_NODE, &ldp_neighbor_password_cmd); + install_element(LDP_NODE, &ldp_neighbor_session_holdtime_cmd); + install_element(LDP_NODE, &ldp_neighbor_ttl_security_cmd); + install_element(LDP_NODE, &ldp_router_id_cmd); + install_element(LDP_NODE, &ldp_ordered_control_cmd); + install_element(LDP_NODE, &ldp_wait_for_sync_cmd); + install_element(LDP_NODE, &ldp_allow_broken_lsps_cmd); + + install_element(LDP_IPV4_NODE, &ldp_discovery_link_holdtime_cmd); + install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_holdtime_cmd); + install_element(LDP_IPV4_NODE, &ldp_discovery_link_interval_cmd); + install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_interval_cmd); + install_element(LDP_IPV4_NODE, &ldp_discovery_targeted_hello_accept_cmd); + install_element(LDP_IPV4_NODE, &ldp_discovery_transport_address_ipv4_cmd); + install_element(LDP_IPV4_NODE, &ldp_label_local_advertise_cmd); + install_element(LDP_IPV4_NODE, &ldp_label_local_advertise_explicit_null_cmd); + install_element(LDP_IPV4_NODE, &ldp_label_local_allocate_cmd); + install_element(LDP_IPV4_NODE, &ldp_label_remote_accept_cmd); + install_element(LDP_IPV4_NODE, &ldp_ttl_security_disable_cmd); + install_element(LDP_IPV4_NODE, &ldp_interface_cmd); + install_element(LDP_IPV4_NODE, &no_ldp_interface_cmd); + install_element(LDP_IPV4_NODE, &ldp_session_holdtime_cmd); + install_element(LDP_IPV4_NODE, &ldp_neighbor_ipv4_targeted_cmd); + install_element(LDP_IPV4_NODE, &ldp_exit_address_family_cmd); + + install_element(LDP_IPV6_NODE, &ldp_discovery_link_holdtime_cmd); + install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_holdtime_cmd); + install_element(LDP_IPV6_NODE, &ldp_discovery_link_interval_cmd); + install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_interval_cmd); + install_element(LDP_IPV6_NODE, &ldp_discovery_targeted_hello_accept_cmd); + install_element(LDP_IPV6_NODE, &ldp_discovery_transport_address_ipv6_cmd); + install_element(LDP_IPV6_NODE, &ldp_label_local_advertise_cmd); + install_element(LDP_IPV6_NODE, &ldp_label_local_advertise_explicit_null_cmd); + install_element(LDP_IPV6_NODE, &ldp_label_local_allocate_cmd); + install_element(LDP_IPV6_NODE, &ldp_label_remote_accept_cmd); + install_element(LDP_IPV6_NODE, &ldp_ttl_security_disable_cmd); + install_element(LDP_IPV6_NODE, &ldp_interface_cmd); + install_element(LDP_IPV6_NODE, &no_ldp_interface_cmd); + install_element(LDP_IPV6_NODE, &ldp_session_holdtime_cmd); + install_element(LDP_IPV6_NODE, &ldp_neighbor_ipv6_targeted_cmd); + install_element(LDP_IPV6_NODE, &ldp_exit_address_family_cmd); + + install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); + install_element(LDP_IPV4_IFACE_NODE, &ldp_discovery_link_interval_cmd); + + install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_holdtime_cmd); + install_element(LDP_IPV6_IFACE_NODE, &ldp_discovery_link_interval_cmd); + + install_element(LDP_L2VPN_NODE, &ldp_bridge_cmd); + install_element(LDP_L2VPN_NODE, &ldp_mtu_cmd); + install_element(LDP_L2VPN_NODE, &ldp_member_interface_cmd); + install_element(LDP_L2VPN_NODE, &ldp_member_pseudowire_cmd); + install_element(LDP_L2VPN_NODE, &no_ldp_member_pseudowire_cmd); + install_element(LDP_L2VPN_NODE, &ldp_vc_type_cmd); + + install_element(LDP_PSEUDOWIRE_NODE, &ldp_control_word_cmd); + install_element(LDP_PSEUDOWIRE_NODE, &ldp_neighbor_address_cmd); + install_element(LDP_PSEUDOWIRE_NODE, &ldp_neighbor_lsr_id_cmd); + install_element(LDP_PSEUDOWIRE_NODE, &ldp_pw_id_cmd); + install_element(LDP_PSEUDOWIRE_NODE, &ldp_pw_status_disable_cmd); + + install_element(ENABLE_NODE, &ldp_clear_mpls_ldp_neighbor_cmd); + install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_discovery_hello_cmd); + install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_type_cmd); + install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_messages_recv_cmd); + install_element(ENABLE_NODE, &ldp_debug_mpls_ldp_messages_sent_cmd); + + install_element(VIEW_NODE, &ldp_show_mpls_ldp_binding_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_discovery_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_interface_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_capabilities_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_neighbor_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_neighbor_capabilities_cmd); + install_element(VIEW_NODE, &ldp_show_l2vpn_atom_binding_cmd); + install_element(VIEW_NODE, &ldp_show_l2vpn_atom_vc_cmd); + install_element(VIEW_NODE, &ldp_show_debugging_mpls_ldp_cmd); + install_element(VIEW_NODE, &ldp_show_mpls_ldp_igp_sync_cmd); +} diff --git a/ldpd/ldp_vty_conf.c b/ldpd/ldp_vty_conf.c new file mode 100644 index 0000000..ffff676 --- /dev/null +++ b/ldpd/ldp_vty_conf.c @@ -0,0 +1,1644 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" + +#include "command.h" +#include "vrf.h" +#include "if.h" +#include "vty.h" +#include "ldp_vty.h" + +static int ldp_config_write(struct vty *); +static void ldp_af_iface_config_write(struct vty *, int); +static void ldp_af_config_write(struct vty *, int, struct ldpd_conf *, + struct ldpd_af_conf *); +static int ldp_l2vpn_config_write(struct vty *); +static void ldp_l2vpn_pw_config_write(struct vty *, struct l2vpn_pw *); +static int ldp_vty_get_af(struct vty *); +static int ldp_iface_is_configured(struct ldpd_conf *, const char *); + +struct cmd_node ldp_node = { + .name = "ldp", + .node = LDP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-ldp)# ", + .config_write = ldp_config_write, +}; + +struct cmd_node ldp_ipv4_node = { + .name = "ldp ipv4", + .node = LDP_IPV4_NODE, + .parent_node = LDP_NODE, + .prompt = "%s(config-ldp-af)# ", +}; + +struct cmd_node ldp_ipv6_node = { + .name = "ldp ipv6", + .node = LDP_IPV6_NODE, + .parent_node = LDP_NODE, + .prompt = "%s(config-ldp-af)# ", +}; + +struct cmd_node ldp_ipv4_iface_node = { + .name = "ldp ipv4 interface", + .node = LDP_IPV4_IFACE_NODE, + .parent_node = LDP_IPV4_NODE, + .prompt = "%s(config-ldp-af-if)# ", +}; + +struct cmd_node ldp_ipv6_iface_node = { + .name = "ldp ipv6 interface", + .node = LDP_IPV6_IFACE_NODE, + .parent_node = LDP_IPV6_NODE, + .prompt = "%s(config-ldp-af-if)# ", +}; + +struct cmd_node ldp_l2vpn_node = { + .name = "ldp l2vpn", + .node = LDP_L2VPN_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-l2vpn)# ", + .config_write = ldp_l2vpn_config_write, +}; + +struct cmd_node ldp_pseudowire_node = { + .name = "ldp", + .node = LDP_PSEUDOWIRE_NODE, + .parent_node = LDP_L2VPN_NODE, + .prompt = "%s(config-l2vpn-pw)# ", +}; + +int +ldp_get_address(const char *str, int *af, union ldpd_addr *addr) +{ + if (!str || !af || !addr) + return (-1); + + memset(addr, 0, sizeof(*addr)); + + if (inet_pton(AF_INET, str, &addr->v4) == 1) { + *af = AF_INET; + return (0); + } + + if (inet_pton(AF_INET6, str, &addr->v6) == 1) { + *af = AF_INET6; + return (0); + } + + return (-1); +} + +static void +ldp_af_iface_config_write(struct vty *vty, int af) +{ + struct iface *iface; + struct iface_af *ia; + + RB_FOREACH(iface, iface_head, &ldpd_conf->iface_tree) { + ia = iface_af_get(iface, af); + if (!ia->enabled) + continue; + + vty_out (vty, " !\n"); + vty_out (vty, " interface %s\n", iface->name); + + if (ia->hello_holdtime != LINK_DFLT_HOLDTIME && + ia->hello_holdtime != 0) + vty_out (vty, " discovery hello holdtime %u\n", + ia->hello_holdtime); + if (ia->hello_interval != DEFAULT_HELLO_INTERVAL && + ia->hello_interval != 0) + vty_out (vty, " discovery hello interval %u\n", + ia->hello_interval); + + vty_out (vty, " exit\n"); + } +} + +static void +ldp_af_config_write(struct vty *vty, int af, struct ldpd_conf *conf, + struct ldpd_af_conf *af_conf) +{ + struct tnbr *tnbr; + + if (!(af_conf->flags & F_LDPD_AF_ENABLED)) + return; + + vty_out (vty, " !\n"); + vty_out (vty, " address-family %s\n", af_name(af)); + + if (af_conf->lhello_holdtime != LINK_DFLT_HOLDTIME && + af_conf->lhello_holdtime != 0 ) + vty_out (vty, " discovery hello holdtime %u\n", + af_conf->lhello_holdtime); + if (af_conf->lhello_interval != DEFAULT_HELLO_INTERVAL && + af_conf->lhello_interval != 0) + vty_out (vty, " discovery hello interval %u\n", + af_conf->lhello_interval); + + if (af_conf->flags & F_LDPD_AF_THELLO_ACCEPT) { + vty_out(vty, " discovery targeted-hello accept"); + if (af_conf->acl_thello_accept_from[0] != '\0') + vty_out(vty, " from %s", + af_conf->acl_thello_accept_from); + vty_out (vty, "\n"); + } + + if (af_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME && + af_conf->thello_holdtime != 0) + vty_out (vty, " discovery targeted-hello holdtime %u\n", + af_conf->thello_holdtime); + if (af_conf->thello_interval != DEFAULT_HELLO_INTERVAL && + af_conf->thello_interval != 0) + vty_out (vty, " discovery targeted-hello interval %u\n", + af_conf->thello_interval); + + if (ldp_addrisset(af, &af_conf->trans_addr)) + vty_out (vty, " discovery transport-address %s\n", + log_addr(af, &af_conf->trans_addr)); + else + vty_out (vty, + " ! Incomplete config, specify a discovery transport-address\n"); + + if ((af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY) || + af_conf->acl_label_allocate_for[0] != '\0') { + vty_out(vty, " label local allocate"); + if (af_conf->flags & F_LDPD_AF_ALLOCHOSTONLY) + vty_out(vty, " host-routes"); + else + vty_out(vty, " for %s", + af_conf->acl_label_allocate_for); + vty_out (vty, "\n"); + } + + if (af_conf->acl_label_advertise_for[0] != '\0' || + af_conf->acl_label_advertise_to[0] != '\0') { + vty_out(vty, " label local advertise"); + if (af_conf->acl_label_advertise_to[0] != '\0') + vty_out(vty, " to %s", + af_conf->acl_label_advertise_to); + if (af_conf->acl_label_advertise_for[0] != '\0') + vty_out(vty, " for %s", + af_conf->acl_label_advertise_for); + vty_out (vty, "\n"); + } + + if (af_conf->flags & F_LDPD_AF_EXPNULL) { + vty_out(vty, " label local advertise explicit-null"); + if (af_conf->acl_label_expnull_for[0] != '\0') + vty_out(vty, " for %s", + af_conf->acl_label_expnull_for); + vty_out (vty, "\n"); + } + + if (af_conf->acl_label_accept_for[0] != '\0' || + af_conf->acl_label_accept_from[0] != '\0') { + vty_out(vty, " label remote accept"); + if (af_conf->acl_label_accept_from[0] != '\0') + vty_out(vty, " from %s", + af_conf->acl_label_accept_from); + if (af_conf->acl_label_accept_for[0] != '\0') + vty_out(vty, " for %s", + af_conf->acl_label_accept_for); + vty_out (vty, "\n"); + } + + if (af_conf->flags & F_LDPD_AF_NO_GTSM) + vty_out (vty, " ttl-security disable\n"); + + if (af_conf->keepalive != DEFAULT_KEEPALIVE) + vty_out (vty, " session holdtime %u\n",af_conf->keepalive); + + RB_FOREACH(tnbr, tnbr_head, &ldpd_conf->tnbr_tree) { + if (tnbr->af == af) { + vty_out (vty, " !\n"); + vty_out (vty, " neighbor %s targeted\n", + log_addr(tnbr->af, &tnbr->addr)); + } + } + + ldp_af_iface_config_write(vty, af); + + vty_out(vty, " !\n"); + vty_out(vty, " exit-address-family\n"); +} + +static int +ldp_config_write(struct vty *vty) +{ + struct nbr_params *nbrp; + + if (!(ldpd_conf->flags & F_LDPD_ENABLED)) + return (0); + + vty_out (vty, "mpls ldp\n"); + + if (ldpd_conf->rtr_id.s_addr != INADDR_ANY) + vty_out(vty, " router-id %pI4\n", &ldpd_conf->rtr_id); + + if (ldpd_conf->lhello_holdtime != LINK_DFLT_HOLDTIME && + ldpd_conf->lhello_holdtime != 0) + vty_out (vty, " discovery hello holdtime %u\n", + ldpd_conf->lhello_holdtime); + if (ldpd_conf->lhello_interval != DEFAULT_HELLO_INTERVAL && + ldpd_conf->lhello_interval != 0) + vty_out (vty, " discovery hello interval %u\n", + ldpd_conf->lhello_interval); + + if (ldpd_conf->thello_holdtime != TARGETED_DFLT_HOLDTIME && + ldpd_conf->thello_holdtime != 0) + vty_out (vty, " discovery targeted-hello holdtime %u\n", + ldpd_conf->thello_holdtime); + if (ldpd_conf->thello_interval != DEFAULT_HELLO_INTERVAL && + ldpd_conf->thello_interval != 0) + vty_out (vty, " discovery targeted-hello interval %u\n", + ldpd_conf->thello_interval); + + if (ldpd_conf->trans_pref == DUAL_STACK_LDPOV4) + vty_out (vty, + " dual-stack transport-connection prefer ipv4\n"); + + if (ldpd_conf->flags & F_LDPD_DS_CISCO_INTEROP) + vty_out (vty, " dual-stack cisco-interop\n"); + + if (ldpd_conf->flags & F_LDPD_ORDERED_CONTROL) + vty_out (vty, " ordered-control\n"); + + if (ldpd_conf->wait_for_sync_interval != DFLT_WAIT_FOR_SYNC && + ldpd_conf->wait_for_sync_interval != 0) + vty_out (vty, " wait-for-sync %u\n", + ldpd_conf->wait_for_sync_interval); + + if (ldpd_conf->flags & F_LDPD_ALLOW_BROKEN_LSP) + vty_out(vty, " install allow-broken-lsp\n"); + + RB_FOREACH(nbrp, nbrp_head, &ldpd_conf->nbrp_tree) { + if (nbrp->flags & F_NBRP_KEEPALIVE) + vty_out (vty, " neighbor %pI4 session holdtime %u\n", + &nbrp->lsr_id,nbrp->keepalive); + + if (nbrp->flags & F_NBRP_GTSM) { + if (nbrp->gtsm_enabled) + vty_out (vty, " neighbor %pI4 ttl-security hops %u\n", &nbrp->lsr_id, + nbrp->gtsm_hops); + else + vty_out (vty, " neighbor %pI4 ttl-security disable\n",&nbrp->lsr_id); + } + + if (nbrp->auth.method == AUTH_MD5SIG) + vty_out (vty, " neighbor %pI4 password %s\n", + &nbrp->lsr_id,nbrp->auth.md5key); + } + + ldp_af_config_write(vty, AF_INET, ldpd_conf, &ldpd_conf->ipv4); + ldp_af_config_write(vty, AF_INET6, ldpd_conf, &ldpd_conf->ipv6); + vty_out (vty, " !\n"); + vty_out (vty, "exit\n"); + vty_out (vty, "!\n"); + + return (1); +} + +static void +ldp_l2vpn_pw_config_write(struct vty *vty, struct l2vpn_pw *pw) +{ + int missing_lsrid = 0; + int missing_pwid = 0; + + vty_out (vty, " !\n"); + vty_out (vty, " member pseudowire %s\n", pw->ifname); + + if (pw->lsr_id.s_addr != INADDR_ANY) + vty_out (vty, " neighbor lsr-id %pI4\n",&pw->lsr_id); + else + missing_lsrid = 1; + + if (pw->flags & F_PW_STATIC_NBR_ADDR) + vty_out (vty, " neighbor address %s\n", + log_addr(pw->af, &pw->addr)); + + if (pw->pwid != 0) + vty_out (vty, " pw-id %u\n", pw->pwid); + else + missing_pwid = 1; + + if (!(pw->flags & F_PW_CWORD_CONF)) + vty_out (vty, " control-word exclude\n"); + + if (!(pw->flags & F_PW_STATUSTLV_CONF)) + vty_out (vty, " pw-status disable\n"); + + if (missing_lsrid) + vty_out (vty, + " ! Incomplete config, specify a neighbor lsr-id\n"); + if (missing_pwid) + vty_out (vty," ! Incomplete config, specify a pw-id\n"); + + vty_out (vty, " exit\n"); +} + +static int +ldp_l2vpn_config_write(struct vty *vty) +{ + struct l2vpn *l2vpn; + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + RB_FOREACH(l2vpn, l2vpn_head, &ldpd_conf->l2vpn_tree) { + vty_out (vty, "l2vpn %s type vpls\n", l2vpn->name); + + if (l2vpn->pw_type != DEFAULT_PW_TYPE) + vty_out (vty, " vc type ethernet-tagged\n"); + + if (l2vpn->mtu != DEFAULT_L2VPN_MTU) + vty_out (vty, " mtu %u\n", l2vpn->mtu); + + if (l2vpn->br_ifname[0] != '\0') + vty_out (vty, " bridge %s\n",l2vpn->br_ifname); + + RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) + vty_out (vty, " member interface %s\n",lif->ifname); + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + ldp_l2vpn_pw_config_write(vty, pw); + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) + ldp_l2vpn_pw_config_write(vty, pw); + + vty_out (vty, " !\n"); + vty_out (vty, "exit\n"); + vty_out (vty, "!\n"); + } + + return (0); +} + +static int +ldp_vty_get_af(struct vty *vty) +{ + switch (vty->node) { + case LDP_IPV4_NODE: + case LDP_IPV4_IFACE_NODE: + return (AF_INET); + case LDP_IPV6_NODE: + case LDP_IPV6_IFACE_NODE: + return (AF_INET6); + default: + fatalx("ldp_vty_get_af: unexpected node"); + } +} + +static int +ldp_iface_is_configured(struct ldpd_conf *xconf, const char *ifname) +{ + struct l2vpn *l2vpn; + + if (if_lookup_name(xconf, ifname)) + return (1); + + RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { + if (l2vpn_if_find(l2vpn, ifname)) + return (1); + if (l2vpn_pw_find(l2vpn, ifname)) + return (1); + } + + return (0); +} + +int +ldp_vty_mpls_ldp(struct vty *vty, const char *negate) +{ + if (negate) + vty_conf->flags &= ~F_LDPD_ENABLED; + else { + vty->node = LDP_NODE; + vty_conf->flags |= F_LDPD_ENABLED; + } + + /* register / de-register to recv info from zebra */ + ldp_zebra_regdereg_zebra_info(!negate); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_address_family(struct vty *vty, const char *negate, const char *af_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + if (af_str == NULL) + return (CMD_WARNING_CONFIG_FAILED); + + if (strcmp(af_str, "ipv4") == 0) { + af = AF_INET; + af_conf = &vty_conf->ipv4; + } else if (strcmp(af_str, "ipv6") == 0) { + af = AF_INET6; + af_conf = &vty_conf->ipv6; + } else + return (CMD_WARNING_CONFIG_FAILED); + + if (negate) { + af_conf->flags &= ~F_LDPD_AF_ENABLED; + ldp_config_apply(vty, vty_conf); + return (CMD_SUCCESS); + } + + switch (af) { + case AF_INET: + vty->node = LDP_IPV4_NODE; + break; + case AF_INET6: + vty->node = LDP_IPV6_NODE; + break; + default: + fatalx("ldp_vty_address_family: unknown af"); + } + af_conf->flags |= F_LDPD_AF_ENABLED; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int ldp_vty_disc_holdtime(struct vty *vty, const char *negate, + enum hello_type hello_type, long secs) +{ + struct ldpd_af_conf *af_conf; + struct iface *iface; + struct iface_af *ia; + int af; + switch (vty->node) { + case LDP_NODE: + if (negate) { + switch (hello_type) { + case HELLO_LINK: + vty_conf->lhello_holdtime = LINK_DFLT_HOLDTIME; + break; + case HELLO_TARGETED: + vty_conf->thello_holdtime = + TARGETED_DFLT_HOLDTIME; + break; + } + } else { + switch (hello_type) { + case HELLO_LINK: + vty_conf->lhello_holdtime = secs; + break; + case HELLO_TARGETED: + vty_conf->thello_holdtime = secs; + break; + } + } + ldp_config_apply(vty, vty_conf); + break; + case LDP_IPV4_NODE: + case LDP_IPV6_NODE: + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + switch (hello_type) { + case HELLO_LINK: + af_conf->lhello_holdtime = 0; + break; + case HELLO_TARGETED: + af_conf->thello_holdtime = 0; + break; + } + } else { + switch (hello_type) { + case HELLO_LINK: + af_conf->lhello_holdtime = secs; + break; + case HELLO_TARGETED: + af_conf->thello_holdtime = secs; + break; + } + } + ldp_config_apply(vty, vty_conf); + break; + case LDP_IPV4_IFACE_NODE: + case LDP_IPV6_IFACE_NODE: + af = ldp_vty_get_af(vty); + iface = VTY_GET_CONTEXT(iface); + VTY_CHECK_CONTEXT(iface); + + ia = iface_af_get(iface, af); + if (negate) + ia->hello_holdtime = 0; + else + ia->hello_holdtime = secs; + + ldp_config_apply(vty, vty_conf); + break; + default: + fatalx("ldp_vty_disc_holdtime: unexpected node"); + } + + return (CMD_SUCCESS); +} + +int +ldp_vty_disc_interval(struct vty *vty, const char *negate, + enum hello_type hello_type, long secs) +{ + struct ldpd_af_conf *af_conf; + struct iface *iface; + struct iface_af *ia; + int af; + + switch (vty->node) { + case LDP_NODE: + if (negate) { + switch (hello_type) { + case HELLO_LINK: + vty_conf->lhello_interval = + DEFAULT_HELLO_INTERVAL; + break; + case HELLO_TARGETED: + vty_conf->thello_interval = + DEFAULT_HELLO_INTERVAL; + break; + } + } else { + switch (hello_type) { + case HELLO_LINK: + vty_conf->lhello_interval = secs; + break; + case HELLO_TARGETED: + vty_conf->thello_interval = secs; + break; + } + } + ldp_config_apply(vty, vty_conf); + break; + case LDP_IPV4_NODE: + case LDP_IPV6_NODE: + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + switch (hello_type) { + case HELLO_LINK: + af_conf->lhello_interval = 0; + break; + case HELLO_TARGETED: + af_conf->thello_interval = 0; + break; + } + } else { + switch (hello_type) { + case HELLO_LINK: + af_conf->lhello_interval = secs; + break; + case HELLO_TARGETED: + af_conf->thello_interval = secs; + break; + } + } + ldp_config_apply(vty, vty_conf); + break; + case LDP_IPV4_IFACE_NODE: + case LDP_IPV6_IFACE_NODE: + af = ldp_vty_get_af(vty); + iface = VTY_GET_CONTEXT(iface); + VTY_CHECK_CONTEXT(iface); + + ia = iface_af_get(iface, af); + if (negate) + ia->hello_interval = 0; + else + ia->hello_interval = secs; + + ldp_config_apply(vty, vty_conf); + break; + default: + fatalx("ldp_vty_disc_interval: unexpected node"); + } + + return (CMD_SUCCESS); +} + +int +ldp_vty_targeted_hello_accept(struct vty *vty, const char *negate, + const char *acl_from_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + af_conf->flags &= ~F_LDPD_AF_THELLO_ACCEPT; + af_conf->acl_thello_accept_from[0] = '\0'; + } else { + af_conf->flags |= F_LDPD_AF_THELLO_ACCEPT; + if (acl_from_str) + strlcpy(af_conf->acl_thello_accept_from, acl_from_str, + sizeof(af_conf->acl_thello_accept_from)); + else + af_conf->acl_thello_accept_from[0] = '\0'; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_nbr_session_holdtime(struct vty *vty, const char *negate, + struct in_addr lsr_id, long secs) +{ + struct nbr_params *nbrp; + + if (bad_addr_v4(lsr_id)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + nbrp = nbr_params_find(vty_conf, lsr_id); + + if (negate) { + if (nbrp == NULL) + return (CMD_SUCCESS); + + nbrp->keepalive = 0; + nbrp->flags &= ~F_NBRP_KEEPALIVE; + } else { + if (nbrp == NULL) { + nbrp = nbr_params_new(lsr_id); + RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); + QOBJ_REG(nbrp, nbr_params); + } else if (nbrp->keepalive == secs) + return (CMD_SUCCESS); + + nbrp->keepalive = secs; + nbrp->flags |= F_NBRP_KEEPALIVE; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_af_session_holdtime(struct vty *vty, const char *negate, long secs) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) + af_conf->keepalive = DEFAULT_KEEPALIVE; + else + af_conf->keepalive = secs; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_interface(struct vty *vty, const char *negate, const char *ifname) +{ + int af; + struct iface *iface; + struct iface_af *ia; + + if (ifname == NULL) { + vty_out (vty, "%% Missing IF name\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + af = ldp_vty_get_af(vty); + iface = if_lookup_name(vty_conf, ifname); + + if (negate) { + if (iface == NULL) + return (CMD_SUCCESS); + + ia = iface_af_get(iface, af); + if (ia->enabled == 0) + return (CMD_SUCCESS); + + ia->enabled = 0; + ia->hello_holdtime = 0; + ia->hello_interval = 0; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); + } + + if (iface == NULL) { + if (ldp_iface_is_configured(vty_conf, ifname)) { + vty_out (vty,"%% Interface is already in use\n"); + return (CMD_SUCCESS); + } + + iface = if_new(ifname); + ia = iface_af_get(iface, af); + ia->enabled = 1; + RB_INSERT(iface_head, &vty_conf->iface_tree, iface); + QOBJ_REG(iface, iface); + + ldp_config_apply(vty, vty_conf); + } else { + ia = iface_af_get(iface, af); + if (!ia->enabled) { + ia->enabled = 1; + ldp_config_apply(vty, vty_conf); + } + } + + switch (af) { + case AF_INET: + VTY_PUSH_CONTEXT(LDP_IPV4_IFACE_NODE, iface); + break; + case AF_INET6: + VTY_PUSH_CONTEXT(LDP_IPV6_IFACE_NODE, iface); + break; + default: + break; + } + + return (CMD_SUCCESS); +} + +int +ldp_vty_trans_addr(struct vty *vty, const char *negate, const char *addr_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) + memset(&af_conf->trans_addr, 0, sizeof(af_conf->trans_addr)); + else { + if (addr_str == NULL + || inet_pton(af, addr_str, &af_conf->trans_addr) != 1 + || bad_addr(af, &af_conf->trans_addr)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_neighbor_targeted(struct vty *vty, const char *negate, const char *addr_str) +{ + int af; + union ldpd_addr addr; + struct tnbr *tnbr; + + af = ldp_vty_get_af(vty); + + if (addr_str == NULL || inet_pton(af, addr_str, &addr) != 1 || + bad_addr(af, &addr)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr.v6)) { + vty_out (vty, "%% Address can not be link-local\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + tnbr = tnbr_find(vty_conf, af, &addr); + + if (negate) { + if (tnbr == NULL) + return (CMD_SUCCESS); + + QOBJ_UNREG(tnbr); + RB_REMOVE(tnbr_head, &vty_conf->tnbr_tree, tnbr); + free(tnbr); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); + } + + if (tnbr) + return (CMD_SUCCESS); + + tnbr = tnbr_new(af, &addr); + tnbr->flags |= F_TNBR_CONFIGURED; + RB_INSERT(tnbr_head, &vty_conf->tnbr_tree, tnbr); + QOBJ_REG(tnbr, tnbr); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_label_advertise(struct vty *vty, const char *negate, const char *acl_to_str, + const char *acl_for_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + af_conf->acl_label_advertise_to[0] = '\0'; + af_conf->acl_label_advertise_for[0] = '\0'; + } else { + if (acl_to_str) + strlcpy(af_conf->acl_label_advertise_to, acl_to_str, + sizeof(af_conf->acl_label_advertise_to)); + else + af_conf->acl_label_advertise_to[0] = '\0'; + if (acl_for_str) + strlcpy(af_conf->acl_label_advertise_for, acl_for_str, + sizeof(af_conf->acl_label_advertise_for)); + else + af_conf->acl_label_advertise_for[0] = '\0'; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_label_allocate(struct vty *vty, const char *negate, const char *host_routes, + const char *acl_for_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + af_conf->flags &= ~F_LDPD_AF_ALLOCHOSTONLY; + af_conf->acl_label_allocate_for[0] = '\0'; + if (!negate) { + if (host_routes) + af_conf->flags |= F_LDPD_AF_ALLOCHOSTONLY; + else + strlcpy(af_conf->acl_label_allocate_for, acl_for_str, + sizeof(af_conf->acl_label_allocate_for)); + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_label_expnull(struct vty *vty, const char *negate, const char *acl_for_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + af_conf->flags &= ~F_LDPD_AF_EXPNULL; + af_conf->acl_label_expnull_for[0] = '\0'; + } else { + af_conf->flags |= F_LDPD_AF_EXPNULL; + if (acl_for_str) + strlcpy(af_conf->acl_label_expnull_for, acl_for_str, + sizeof(af_conf->acl_label_expnull_for)); + else + af_conf->acl_label_expnull_for[0] = '\0'; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_label_accept(struct vty *vty, const char *negate, const char *acl_from_str, + const char *acl_for_str) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) { + af_conf->acl_label_accept_from[0] = '\0'; + af_conf->acl_label_accept_for[0] = '\0'; + } else { + if (acl_from_str) + strlcpy(af_conf->acl_label_accept_from, acl_from_str, + sizeof(af_conf->acl_label_accept_from)); + else + af_conf->acl_label_accept_from[0] = '\0'; + if (acl_for_str) + strlcpy(af_conf->acl_label_accept_for, acl_for_str, + sizeof(af_conf->acl_label_accept_for)); + else + af_conf->acl_label_accept_for[0] = '\0'; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_ttl_security(struct vty *vty, const char *negate) +{ + struct ldpd_af_conf *af_conf; + int af; + + af = ldp_vty_get_af(vty); + af_conf = ldp_af_conf_get(vty_conf, af); + + if (negate) + af_conf->flags &= ~F_LDPD_AF_NO_GTSM; + else + af_conf->flags |= F_LDPD_AF_NO_GTSM; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_router_id(struct vty *vty, const char *negate, struct in_addr address) +{ + if (negate) + vty_conf->rtr_id.s_addr = INADDR_ANY; + else { + if (bad_addr_v4(address)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + vty_conf->rtr_id = address; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_ordered_control(struct vty *vty, const char *negate) +{ + if (negate) + vty_conf->flags &= ~F_LDPD_ORDERED_CONTROL; + else + vty_conf->flags |= F_LDPD_ORDERED_CONTROL; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int ldp_vty_wait_for_sync_interval(struct vty *vty, const char *negate, + long secs) +{ + switch (vty->node) { + case LDP_NODE: + if (negate) + vty_conf->wait_for_sync_interval = DFLT_WAIT_FOR_SYNC; + else + vty_conf->wait_for_sync_interval = secs; + + ldp_config_apply(vty, vty_conf); + break; + default: + fatalx("ldp_vty_wait_for_sync_interval: unexpected node"); + } + return (CMD_SUCCESS); +} + +int +ldp_vty_allow_broken_lsp(struct vty *vty, const char *negate) +{ + if (negate) + vty_conf->flags &= ~F_LDPD_ALLOW_BROKEN_LSP; + else + vty_conf->flags |= F_LDPD_ALLOW_BROKEN_LSP; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_ds_cisco_interop(struct vty *vty, const char * negate) +{ + if (negate) + vty_conf->flags &= ~F_LDPD_DS_CISCO_INTEROP; + else + vty_conf->flags |= F_LDPD_DS_CISCO_INTEROP; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_trans_pref_ipv4(struct vty *vty, const char *negate) +{ + if (negate) + vty_conf->trans_pref = DUAL_STACK_LDPOV6; + else + vty_conf->trans_pref = DUAL_STACK_LDPOV4; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_neighbor_password(struct vty *vty, const char *negate, struct in_addr lsr_id, + const char *password_str) +{ + size_t password_len; + struct nbr_params *nbrp; + + if (password_str == NULL) { + vty_out (vty, "%% Missing password\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + if (bad_addr_v4(lsr_id)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + nbrp = nbr_params_find(vty_conf, lsr_id); + + if (negate) { + if (nbrp == NULL) + return (CMD_SUCCESS); + + memset(&nbrp->auth, 0, sizeof(nbrp->auth)); + nbrp->auth.method = AUTH_NONE; + } else { + if (nbrp == NULL) { + nbrp = nbr_params_new(lsr_id); + RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); + QOBJ_REG(nbrp, nbr_params); + } else if (nbrp->auth.method == AUTH_MD5SIG && + strcmp(nbrp->auth.md5key, password_str) == 0) + return (CMD_SUCCESS); + + password_len = strlcpy(nbrp->auth.md5key, password_str, + sizeof(nbrp->auth.md5key)); + if (password_len >= sizeof(nbrp->auth.md5key)) + vty_out(vty, "%% password has been truncated to %zu characters.", sizeof(nbrp->auth.md5key) - 1); + nbrp->auth.md5key_len = strlen(nbrp->auth.md5key); + nbrp->auth.method = AUTH_MD5SIG; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_neighbor_ttl_security(struct vty *vty, const char *negate, + struct in_addr lsr_id, const char *hops_str) +{ + struct nbr_params *nbrp; + long int hops = 0; + char *ep; + + if (bad_addr_v4(lsr_id)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + if (hops_str) { + hops = strtol(hops_str, &ep, 10); + if (*ep != '\0' || hops < 1 || hops > 254) { + vty_out (vty, "%% Invalid hop count\n"); + return (CMD_SUCCESS); + } + } + + nbrp = nbr_params_find(vty_conf, lsr_id); + + if (negate) { + if (nbrp == NULL) + return (CMD_SUCCESS); + + nbrp->flags &= ~(F_NBRP_GTSM|F_NBRP_GTSM_HOPS); + nbrp->gtsm_enabled = 0; + nbrp->gtsm_hops = 0; + } else { + if (nbrp == NULL) { + nbrp = nbr_params_new(lsr_id); + RB_INSERT(nbrp_head, &vty_conf->nbrp_tree, nbrp); + QOBJ_REG(nbrp, nbr_params); + } + + nbrp->flags |= F_NBRP_GTSM; + nbrp->flags &= ~F_NBRP_GTSM_HOPS; + if (hops_str) { + nbrp->gtsm_enabled = 1; + nbrp->gtsm_hops = hops; + nbrp->flags |= F_NBRP_GTSM_HOPS; + } else + nbrp->gtsm_enabled = 0; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn(struct vty *vty, const char *negate, const char *name_str) +{ + struct l2vpn *l2vpn; + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + if (name_str == NULL) { + vty_out (vty, "%% Missing name\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + l2vpn = l2vpn_find(vty_conf, name_str); + + if (negate) { + if (l2vpn == NULL) + return (CMD_SUCCESS); + + RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) + QOBJ_UNREG(lif); + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) + QOBJ_UNREG(pw); + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) + QOBJ_UNREG(pw); + QOBJ_UNREG(l2vpn); + RB_REMOVE(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn); + l2vpn_del(l2vpn); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); + } + + if (l2vpn) { + VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn); + return (CMD_SUCCESS); + } + + l2vpn = l2vpn_new(name_str); + l2vpn->type = L2VPN_TYPE_VPLS; + RB_INSERT(l2vpn_head, &vty_conf->l2vpn_tree, l2vpn); + QOBJ_REG(l2vpn, l2vpn); + + VTY_PUSH_CONTEXT(LDP_L2VPN_NODE, l2vpn); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_bridge(struct vty *vty, const char *negate, const char *ifname) +{ + VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); + + if (negate) + memset(l2vpn->br_ifname, 0, sizeof(l2vpn->br_ifname)); + else { + if (ifname == NULL) { + vty_out (vty, "%% Missing IF name\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + strlcpy(l2vpn->br_ifname, ifname, sizeof(l2vpn->br_ifname)); + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_mtu(struct vty *vty, const char *negate, long mtu) +{ + VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); + + if (negate) + l2vpn->mtu = DEFAULT_L2VPN_MTU; + else + l2vpn->mtu = mtu; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pwtype(struct vty *vty, const char *negate, const char *type_str) +{ + VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); + int pw_type; + + if (type_str == NULL) { + vty_out (vty, "%% Missing type\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + if (strcmp(type_str, "ethernet") == 0) + pw_type = PW_TYPE_ETHERNET; + else + pw_type = PW_TYPE_ETHERNET_TAGGED; + + if (negate) + l2vpn->pw_type = DEFAULT_PW_TYPE; + else + l2vpn->pw_type = pw_type; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_interface(struct vty *vty, const char *negate, const char *ifname) +{ + VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); + struct l2vpn_if *lif; + + if (ifname == NULL) { + vty_out (vty, "%% Missing IF name\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + lif = l2vpn_if_find(l2vpn, ifname); + + if (negate) { + if (lif == NULL) + return (CMD_SUCCESS); + + QOBJ_UNREG(lif); + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); + } + + if (lif) + return (CMD_SUCCESS); + + if (ldp_iface_is_configured(vty_conf, ifname)) { + vty_out (vty, "%% Interface is already in use\n"); + return (CMD_SUCCESS); + } + + lif = l2vpn_if_new(l2vpn, ifname); + RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); + QOBJ_REG(lif, l2vpn_if); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pseudowire(struct vty *vty, const char *negate, const char *ifname) +{ + VTY_DECLVAR_CONTEXT(l2vpn, l2vpn); + struct l2vpn_pw *pw; + + if (ifname == NULL) { + vty_out (vty, "%% Missing IF name\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + pw = l2vpn_pw_find(l2vpn, ifname); + + if (negate) { + if (pw == NULL) + return (CMD_SUCCESS); + + QOBJ_UNREG(pw); + if (pw->lsr_id.s_addr == INADDR_ANY || pw->pwid == 0) + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + else + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + free(pw); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); + } + + if (pw) { + VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw); + return (CMD_SUCCESS); + } + + if (ldp_iface_is_configured(vty_conf, ifname)) { + vty_out (vty, "%% Interface is already in use\n"); + return (CMD_SUCCESS); + } + + pw = l2vpn_pw_new(l2vpn, ifname); + pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF; + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + QOBJ_REG(pw, l2vpn_pw); + + VTY_PUSH_CONTEXT_SUB(LDP_PSEUDOWIRE_NODE, pw); + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pw_cword(struct vty *vty, const char *negate, const char *preference_str) +{ + VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); + + if (negate) + pw->flags |= F_PW_CWORD_CONF; + else { + if (!preference_str) { + vty_out (vty, "%% Missing preference\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + if (preference_str[0] == 'e') + pw->flags &= ~F_PW_CWORD_CONF; + else + pw->flags |= F_PW_CWORD_CONF; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pw_nbr_addr(struct vty *vty, const char *negate, const char *addr_str) +{ + VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); + int af; + union ldpd_addr addr; + + if (ldp_get_address(addr_str, &af, &addr) == -1 || + bad_addr(af, &addr)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + if (negate) { + pw->af = AF_UNSPEC; + memset(&pw->addr, 0, sizeof(pw->addr)); + pw->flags &= ~F_PW_STATIC_NBR_ADDR; + } else { + pw->af = af; + pw->addr = addr; + pw->flags |= F_PW_STATIC_NBR_ADDR; + } + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pw_nbr_id(struct vty *vty, const char *negate, struct in_addr lsr_id) +{ + VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); + + if (bad_addr_v4(lsr_id)) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING_CONFIG_FAILED); + } + + if (negate) + pw->lsr_id.s_addr = INADDR_ANY; + else + pw->lsr_id = lsr_id; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pw_pwid(struct vty *vty, const char *negate, long pwid) +{ + VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); + + if (negate) + pw->pwid = 0; + else + pw->pwid = pwid; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +int +ldp_vty_l2vpn_pw_pwstatus(struct vty *vty, const char *negate) +{ + VTY_DECLVAR_CONTEXT_SUB(l2vpn_pw, pw); + + if (negate) + pw->flags |= F_PW_STATUSTLV_CONF; + else + pw->flags &= ~F_PW_STATUSTLV_CONF; + + ldp_config_apply(vty, vty_conf); + + return (CMD_SUCCESS); +} + +struct iface * +iface_new_api(struct ldpd_conf *conf, const char *name) +{ + const char *ifname = name; + struct iface *iface; + + if (ldp_iface_is_configured(conf, ifname)) + return (NULL); + + iface = if_new(name); + RB_INSERT(iface_head, &conf->iface_tree, iface); + QOBJ_REG(iface, iface); + return (iface); +} + +void +iface_del_api(struct ldpd_conf *conf, struct iface *iface) +{ + QOBJ_UNREG(iface); + RB_REMOVE(iface_head, &conf->iface_tree, iface); + free(iface); +} + +struct tnbr * +tnbr_new_api(struct ldpd_conf *conf, int af, union ldpd_addr *addr) +{ + struct tnbr *tnbr; + + if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&addr->v6)) + return (NULL); + + if (tnbr_find(conf, af, addr)) + return (NULL); + + tnbr = tnbr_new(af, addr); + tnbr->flags |= F_TNBR_CONFIGURED; + RB_INSERT(tnbr_head, &conf->tnbr_tree, tnbr); + QOBJ_REG(tnbr, tnbr); + return (tnbr); +} + +void +tnbr_del_api(struct ldpd_conf *conf, struct tnbr *tnbr) +{ + QOBJ_UNREG(tnbr); + RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); + free(tnbr); +} + +struct nbr_params * +nbrp_new_api(struct ldpd_conf *conf, struct in_addr lsr_id) +{ + struct nbr_params *nbrp; + + if (nbr_params_find(conf, lsr_id)) + return (NULL); + + nbrp = nbr_params_new(lsr_id); + RB_INSERT(nbrp_head, &conf->nbrp_tree, nbrp); + QOBJ_REG(nbrp, nbr_params); + return (nbrp); +} + +void +nbrp_del_api(struct ldpd_conf *conf, struct nbr_params *nbrp) +{ + QOBJ_UNREG(nbrp); + RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); + free(nbrp); +} + +struct l2vpn * +l2vpn_new_api(struct ldpd_conf *conf, const char *name) +{ + struct l2vpn *l2vpn; + + if (l2vpn_find(conf, name)) + return (NULL); + + l2vpn = l2vpn_new(name); + l2vpn->type = L2VPN_TYPE_VPLS; + RB_INSERT(l2vpn_head, &conf->l2vpn_tree, l2vpn); + QOBJ_REG(l2vpn, l2vpn); + return (l2vpn); +} + +void +l2vpn_del_api(struct ldpd_conf *conf, struct l2vpn *l2vpn) +{ + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { + lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); + + QOBJ_UNREG(lif); + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); + } + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); + + QOBJ_UNREG(pw); + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + free(pw); + } + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); + + QOBJ_UNREG(pw); + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + free(pw); + } + QOBJ_UNREG(l2vpn); + RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); + free(l2vpn); +} + +struct l2vpn_if * +l2vpn_if_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, + const char *ifname) +{ + struct l2vpn_if *lif; + + if (ldp_iface_is_configured(conf, ifname)) + return (NULL); + + lif = l2vpn_if_new(l2vpn, ifname); + RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); + QOBJ_REG(lif, l2vpn_if); + return (lif); +} + +void +l2vpn_if_del_api(struct l2vpn *l2vpn, struct l2vpn_if *lif) +{ + QOBJ_UNREG(lif); + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); +} + +struct l2vpn_pw * +l2vpn_pw_new_api(struct ldpd_conf *conf, struct l2vpn *l2vpn, + const char *ifname) +{ + struct l2vpn_pw *pw; + + if (ldp_iface_is_configured(conf, ifname)) + return (NULL); + + pw = l2vpn_pw_new(l2vpn, ifname); + pw->flags = F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF; + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + QOBJ_REG(pw, l2vpn_pw); + return (pw); +} + +void +l2vpn_pw_del_api(struct l2vpn *l2vpn, struct l2vpn_pw *pw) +{ + QOBJ_UNREG(pw); + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + free(pw); +} diff --git a/ldpd/ldp_vty_exec.c b/ldpd/ldp_vty_exec.c new file mode 100644 index 0000000..f3bcd1b --- /dev/null +++ b/ldpd/ldp_vty_exec.c @@ -0,0 +1,2145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#include +#include +#include "lib/printfrr.h" + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" +#include "ldp_vty.h" +#include "lib/json.h" + +#include "command.h" +#include "vty.h" +#include "mpls.h" + +enum show_command { + SHOW_DISC, + SHOW_IFACE, + SHOW_NBR, + SHOW_LIB, + SHOW_L2VPN_PW, + SHOW_L2VPN_BINDING, + SHOW_LDP_SYNC +}; + +struct show_params { + int family; + union ldpd_addr addr; + uint8_t prefixlen; + int detail; + int json; + union { + struct { + struct in_addr lsr_id; + int capabilities; + } neighbor; + struct { + struct prefix prefix; + int longer_prefixes; + struct in_addr neighbor; + uint32_t local_label; + uint32_t remote_label; + } lib; + struct { + struct in_addr peer; + uint32_t local_label; + uint32_t remote_label; + char ifname[IFNAMSIZ]; + uint32_t vcid; + } l2vpn; + }; +}; + +#define LDPBUFSIZ 65535 + +static int show_interface_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_interface_msg_json(struct imsg *, + struct show_params *, json_object *); +static int show_discovery_msg(struct vty *, struct imsg *, + struct show_params *); +static void show_discovery_detail_adj(struct vty *, char *, + struct ctl_adj *); +static int show_discovery_detail_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_discovery_msg_json(struct imsg *, + struct show_params *, json_object *); +static void show_discovery_detail_adj_json(json_object *, + struct ctl_adj *); +static int show_discovery_detail_msg_json(struct imsg *, + struct show_params *, json_object *); +static int show_ldp_sync_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_ldp_sync_msg_json(struct imsg *, + struct show_params *, json_object *); + +static int show_nbr_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_nbr_msg_json(struct imsg *, struct show_params *, + json_object *); +static void show_nbr_detail_adj(struct vty *, char *, + struct ctl_adj *); +static int show_nbr_detail_msg(struct vty *, struct imsg *, + struct show_params *); +static void show_nbr_detail_adj_json(struct ctl_adj *, + json_object *); +static int show_nbr_detail_msg_json(struct imsg *, + struct show_params *, json_object *); +static void show_nbr_capabilities(struct vty *, struct ctl_nbr *); +static int show_nbr_capabilities_msg(struct vty *, struct imsg *, + struct show_params *); +static void show_nbr_capabilities_json(struct ctl_nbr *, + json_object *); +static int show_nbr_capabilities_msg_json(struct imsg *, + struct show_params *, json_object *); +static int show_lib_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_lib_detail_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_lib_msg_json(struct imsg *, struct show_params *, + json_object *); +static int show_lib_detail_msg_json(struct imsg *, + struct show_params *, json_object *); +static int show_l2vpn_binding_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_l2vpn_binding_msg_json(struct imsg *, + struct show_params *, json_object *); +static int show_l2vpn_pw_msg(struct vty *, struct imsg *, + struct show_params *); +static int show_l2vpn_pw_msg_json(struct imsg *, + struct show_params *, json_object *); +static int ldp_vty_dispatch_msg(struct vty *, struct imsg *, + enum show_command, struct show_params *, + json_object *); +static int ldp_vty_dispatch(struct vty *, struct imsgbuf *, + enum show_command, struct show_params *); +static int ldp_vty_get_af(const char *, int *); + +static int +show_interface_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_iface *iface; + char timers[BUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_INTERFACE: + iface = imsg->data; + + if (params->family != AF_UNSPEC && params->family != iface->af) + break; + + snprintf(timers, sizeof(timers), "%u/%u", + iface->hello_interval, iface->hello_holdtime); + + vty_out (vty, "%-4s %-11s %-6s %-8s %-12s %3u\n", + af_name(iface->af), iface->name, + if_state_name(iface->state), iface->uptime == 0 ? + "00:00:00" : log_time(iface->uptime), timers, + iface->adj_cnt); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_interface_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_iface *iface; + json_object *json_iface; + char key_name[64]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_INTERFACE: + iface = imsg->data; + + if (params->family != AF_UNSPEC && params->family != iface->af) + break; + + json_iface = json_object_new_object(); + json_object_string_add(json_iface, "name", iface->name); + json_object_string_add(json_iface, "addressFamily", + af_name(iface->af)); + json_object_string_add(json_iface, "state", + if_state_name(iface->state)); + json_object_string_add(json_iface, "upTime", + log_time(iface->uptime)); + json_object_int_add(json_iface, "helloInterval", + iface->hello_interval); + json_object_int_add(json_iface, "helloHoldtime", + iface->hello_holdtime); + json_object_int_add(json_iface, "adjacencyCount", + iface->adj_cnt); + + snprintf(key_name, sizeof(key_name), "%s: %s", iface->name, + af_name(iface->af)); + json_object_object_add(json, key_name, json_iface); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_ldp_sync_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_ldp_sync *iface; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LDP_SYNC: + iface = imsg->data; + + vty_out (vty, "%s:\n", iface->name); + if (iface->in_sync) + vty_out (vty, " Status: initial label exchange complete\n"); + else + vty_out (vty, " Status: label exchange not complete\n"); + + if (iface->timer_running) { + vty_out (vty, " Wait time: %d seconds (%d seconds left)\n", + iface->wait_time, iface->wait_time_remaining); + vty_out (vty, " Timer is running\n"); + } else { + vty_out (vty, " Wait time: %d seconds\n", + iface->wait_time); + vty_out (vty, " Timer is not running\n"); + } + + if (iface->peer_ldp_id.s_addr) + vty_out (vty, " Peer LDP Identifier: %pI4:0\n", + &iface->peer_ldp_id); + + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_ldp_sync_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_ldp_sync *iface; + json_object *json_iface; + char buf[PREFIX_STRLEN]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LDP_SYNC: + iface = imsg->data; + + json_iface = json_object_new_object(); + json_object_string_add(json_iface, "state", + iface->in_sync + ? "labelExchangeComplete" + : "labelExchangeNotComplete"); + json_object_int_add(json_iface, "waitTime", + iface->wait_time); + json_object_int_add(json_iface, "waitTimeRemaining", + iface->wait_time_remaining); + + if (iface->timer_running) + json_object_boolean_true_add(json_iface, "timerRunning"); + else + json_object_boolean_false_add(json_iface, "timerRunning"); + + json_object_string_add(json_iface, "peerLdpId", + iface->peer_ldp_id.s_addr ? + inet_ntop(AF_INET, &iface->peer_ldp_id, buf, sizeof(buf)) : + ""); + + json_object_object_add(json, iface->name, json_iface); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_discovery_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_adj *adj; + const char *addr; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_DISCOVERY: + adj = imsg->data; + + if (params->family != AF_UNSPEC && params->family != adj->af) + break; + + vty_out(vty, "%-4s %-15pI4 ", af_name(adj->af), &adj->id); + switch(adj->type) { + case HELLO_LINK: + vty_out(vty, "%-8s %-15s ", "Link", adj->ifname); + break; + case HELLO_TARGETED: + addr = log_addr(adj->af, &adj->src_addr); + + vty_out(vty, "%-8s %-15s ", "Targeted", addr); + if (strlen(addr) > 15) + vty_out(vty, "\n%46s", " "); + break; + } + vty_out (vty, "%9u\n", adj->holdtime); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static void +show_discovery_detail_adj(struct vty *vty, char *buffer, struct ctl_adj *adj) +{ + size_t buflen = strlen(buffer); + + snprintfrr(buffer + buflen, LDPBUFSIZ - buflen, + " LSR Id: %pI4:0\n", &adj->id); + buflen = strlen(buffer); + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Source address: %s\n", + log_addr(adj->af, &adj->src_addr)); + buflen = strlen(buffer); + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Transport address: %s\n", + log_addr(adj->af, &adj->trans_addr)); + buflen = strlen(buffer); + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Hello hold time: %u secs (due in %u secs)\n", + adj->holdtime, adj->holdtime_remaining); + buflen = strlen(buffer); + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Dual-stack capability TLV: %s\n", + (adj->ds_tlv) ? "yes" : "no"); +} + +static int +show_discovery_detail_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_adj *adj; + struct ctl_disc_if *iface; + struct ctl_disc_tnbr *tnbr; + struct in_addr rtr_id; + union ldpd_addr *trans_addr; + size_t buflen; + static char ifaces_buffer[LDPBUFSIZ]; + static char tnbrs_buffer[LDPBUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_DISCOVERY: + ifaces_buffer[0] = '\0'; + tnbrs_buffer[0] = '\0'; + break; + case IMSG_CTL_SHOW_DISC_IFACE: + iface = imsg->data; + + if (params->family != AF_UNSPEC && + ((params->family == AF_INET && !iface->active_v4) || + (params->family == AF_INET6 && !iface->active_v6))) + break; + + buflen = strlen(ifaces_buffer); + snprintf(ifaces_buffer + buflen, LDPBUFSIZ - buflen, + " %s: %s\n", iface->name, (iface->no_adj) ? + "(no adjacencies)" : ""); + break; + case IMSG_CTL_SHOW_DISC_TNBR: + tnbr = imsg->data; + + if (params->family != AF_UNSPEC && params->family != tnbr->af) + break; + + trans_addr = &(ldp_af_conf_get(ldpd_conf, + tnbr->af))->trans_addr; + buflen = strlen(tnbrs_buffer); + snprintf(tnbrs_buffer + buflen, LDPBUFSIZ - buflen, + " %s -> %s: %s\n", log_addr(tnbr->af, trans_addr), + log_addr(tnbr->af, &tnbr->addr), (tnbr->no_adj) ? + "(no adjacencies)" : ""); + break; + case IMSG_CTL_SHOW_DISC_ADJ: + adj = imsg->data; + + if (params->family != AF_UNSPEC && params->family != adj->af) + break; + + switch(adj->type) { + case HELLO_LINK: + show_discovery_detail_adj(vty, ifaces_buffer, adj); + break; + case HELLO_TARGETED: + show_discovery_detail_adj(vty, tnbrs_buffer, adj); + break; + } + break; + case IMSG_CTL_END: + rtr_id.s_addr = ldp_rtr_id_get(ldpd_conf); + vty_out (vty, "Local:\n"); + vty_out (vty, " LSR Id: %pI4:0\n",&rtr_id); + if (CHECK_FLAG(ldpd_conf->ipv4.flags, F_LDPD_AF_ENABLED)) + vty_out (vty, " Transport Address (IPv4): %s\n", + log_addr(AF_INET, &ldpd_conf->ipv4.trans_addr)); + if (CHECK_FLAG(ldpd_conf->ipv6.flags, F_LDPD_AF_ENABLED)) + vty_out (vty, " Transport Address (IPv6): %s\n", + log_addr(AF_INET6, &ldpd_conf->ipv6.trans_addr)); + vty_out (vty, "Discovery Sources:\n"); + vty_out (vty, " Interfaces:\n"); + vty_out(vty, "%s", ifaces_buffer); + vty_out (vty, " Targeted Hellos:\n"); + vty_out(vty, "%s", tnbrs_buffer); + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_discovery_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_adj *adj; + json_object *json_array; + json_object *json_adj; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_DISCOVERY: + adj = imsg->data; + + if (params->family != AF_UNSPEC && params->family != adj->af) + break; + + json_object_object_get_ex(json, "adjacencies", &json_array); + if (!json_array) { + json_array = json_object_new_array(); + json_object_object_add(json, "adjacencies", json_array); + } + + json_adj = json_object_new_object(); + json_object_string_add(json_adj, "addressFamily", + af_name(adj->af)); + json_object_string_addf(json_adj, "neighborId", "%pI4", + &adj->id); + switch(adj->type) { + case HELLO_LINK: + json_object_string_add(json_adj, "type", "link"); + json_object_string_add(json_adj, "interface", + adj->ifname); + break; + case HELLO_TARGETED: + json_object_string_add(json_adj, "type", "targeted"); + json_object_string_add(json_adj, "peer", + log_addr(adj->af, &adj->src_addr)); + break; + } + json_object_int_add(json_adj, "helloHoldtime", adj->holdtime); + + json_object_array_add(json_array, json_adj); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static void +show_discovery_detail_adj_json(json_object *json, struct ctl_adj *adj) +{ + json_object *json_adj; + json_object *json_array; + + json_object_object_get_ex(json, "adjacencies", &json_array); + if (!json_array) { + json_array = json_object_new_array(); + json_object_object_add(json, "adjacencies", json_array); + } + + json_adj = json_object_new_object(); + json_object_string_addf(json_adj, "lsrId", "%pI4", &adj->id); + json_object_string_add(json_adj, "sourceAddress", log_addr(adj->af, + &adj->src_addr)); + json_object_string_add(json_adj, "transportAddress", log_addr(adj->af, + &adj->trans_addr)); + json_object_int_add(json_adj, "helloHoldtime", adj->holdtime); + json_object_int_add(json_adj, "helloHoldtimeRemaining", + adj->holdtime_remaining); + json_object_int_add(json_adj, "dualStackCapabilityTlv", + adj->ds_tlv); + json_object_array_add(json_array, json_adj); +} + +static int +show_discovery_detail_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_adj *adj; + struct ctl_disc_if *iface; + struct ctl_disc_tnbr *tnbr; + struct in_addr rtr_id; + union ldpd_addr *trans_addr; + json_object *json_interface; + json_object *json_target; + static json_object *json_interfaces; + static json_object *json_targets; + static json_object *json_container; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_DISCOVERY: + rtr_id.s_addr = ldp_rtr_id_get(ldpd_conf); + json_object_string_addf(json, "lsrId", "%pI4", &rtr_id); + if (CHECK_FLAG(ldpd_conf->ipv4.flags, F_LDPD_AF_ENABLED)) + json_object_string_add(json, "transportAddressIPv4", + log_addr(AF_INET, &ldpd_conf->ipv4.trans_addr)); + if (CHECK_FLAG(ldpd_conf->ipv6.flags, F_LDPD_AF_ENABLED)) + json_object_string_add(json, "transportAddressIPv6", + log_addr(AF_INET6, &ldpd_conf->ipv6.trans_addr)); + json_interfaces = json_object_new_object(); + json_object_object_add(json, "interfaces", json_interfaces); + json_targets = json_object_new_object(); + json_object_object_add(json, "targetedHellos", json_targets); + json_container = NULL; + break; + case IMSG_CTL_SHOW_DISC_IFACE: + iface = imsg->data; + + if (params->family != AF_UNSPEC && + ((params->family == AF_INET && !iface->active_v4) || + (params->family == AF_INET6 && !iface->active_v6))) + break; + + json_interface = json_object_new_object(); + json_object_object_add(json_interfaces, iface->name, + json_interface); + json_container = json_interface; + break; + case IMSG_CTL_SHOW_DISC_TNBR: + tnbr = imsg->data; + + if (params->family != AF_UNSPEC && params->family != tnbr->af) + break; + + trans_addr = &(ldp_af_conf_get(ldpd_conf, tnbr->af))->trans_addr; + + json_target = json_object_new_object(); + json_object_string_add(json_target, "sourceAddress", + log_addr(tnbr->af, trans_addr)); + json_object_object_add(json_targets, log_addr(tnbr->af, + &tnbr->addr), json_target); + json_container = json_target; + break; + case IMSG_CTL_SHOW_DISC_ADJ: + adj = imsg->data; + + if (params->family != AF_UNSPEC && params->family != adj->af) + break; + + switch(adj->type) { + case HELLO_LINK: + show_discovery_detail_adj_json(json_container, adj); + break; + case HELLO_TARGETED: + show_discovery_detail_adj_json(json_container, adj); + break; + } + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_nbr_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) +{ + struct ctl_nbr *nbr; + const char *addr; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + addr = log_addr(nbr->af, &nbr->raddr); + + vty_out(vty, "%-4s %-15pI4 %-11s %-15s", + af_name(nbr->af), &nbr->id, + nbr_state_name(nbr->nbr_state), addr); + if (strlen(addr) > 15) + vty_out(vty, "\n%48s", " "); + vty_out (vty, " %8s\n", log_time(nbr->uptime)); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static void +show_nbr_detail_adj(struct vty *vty, char *buffer, struct ctl_adj *adj) +{ + size_t buflen = strlen(buffer); + + switch (adj->type) { + case HELLO_LINK: + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Interface: %s\n", adj->ifname); + break; + case HELLO_TARGETED: + snprintf(buffer + buflen, LDPBUFSIZ - buflen, + " Targeted Hello: %s\n", log_addr(adj->af, + &adj->src_addr)); + break; + } +} + +static int +show_nbr_detail_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_nbr *nbr; + struct ldp_stats *stats; + struct ctl_adj *adj; + static char v4adjs_buffer[LDPBUFSIZ]; + static char v6adjs_buffer[LDPBUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + v4adjs_buffer[0] = '\0'; + v6adjs_buffer[0] = '\0'; + vty_out (vty, "Peer LDP Identifier: %pI4:0\n", + &nbr->id); + vty_out (vty, " TCP connection: %s:%u - %s:%u\n", + log_addr(nbr->af, &nbr->laddr), ntohs(nbr->lport), + log_addr(nbr->af, &nbr->raddr),ntohs(nbr->rport)); + vty_out (vty, " Authentication: %s\n", + (nbr->auth_method == AUTH_MD5SIG) ? "TCP MD5 Signature" : "none"); + vty_out(vty, " Session Holdtime: %u secs; KeepAlive interval: %u secs\n", nbr->holdtime, + nbr->holdtime / KEEPALIVE_PER_PERIOD); + vty_out(vty, " State: %s; Downstream-Unsolicited\n", + nbr_state_name(nbr->nbr_state)); + vty_out (vty, " Up time: %s\n",log_time(nbr->uptime)); + + stats = &nbr->stats; + vty_out (vty, " Messages sent/rcvd:\n"); + vty_out (vty, " - Keepalive Messages: %u/%u\n", + stats->kalive_sent, stats->kalive_rcvd); + vty_out (vty, " - Address Messages: %u/%u\n", + stats->addr_sent, stats->addr_rcvd); + vty_out (vty, " - Address Withdraw Messages: %u/%u\n", + stats->addrwdraw_sent, stats->addrwdraw_rcvd); + vty_out (vty, " - Notification Messages: %u/%u\n", + stats->notif_sent, stats->notif_rcvd); + vty_out (vty, " - Capability Messages: %u/%u\n", + stats->capability_sent, stats->capability_rcvd); + vty_out (vty, " - Label Mapping Messages: %u/%u\n", + stats->labelmap_sent, stats->labelmap_rcvd); + vty_out (vty, " - Label Request Messages: %u/%u\n", + stats->labelreq_sent, stats->labelreq_rcvd); + vty_out (vty, " - Label Withdraw Messages: %u/%u\n", + stats->labelwdraw_sent, stats->labelwdraw_rcvd); + vty_out (vty, " - Label Release Messages: %u/%u\n", + stats->labelrel_sent, stats->labelrel_rcvd); + vty_out (vty, " - Label Abort Request Messages: %u/%u\n", + stats->labelabreq_sent, stats->labelabreq_rcvd); + + show_nbr_capabilities(vty, nbr); + break; + case IMSG_CTL_SHOW_NBR_DISC: + adj = imsg->data; + + switch (adj->af) { + case AF_INET: + show_nbr_detail_adj(vty, v4adjs_buffer, adj); + break; + case AF_INET6: + show_nbr_detail_adj(vty, v6adjs_buffer, adj); + break; + default: + fatalx("show_nbr_detail_msg: unknown af"); + } + break; + case IMSG_CTL_SHOW_NBR_END: + vty_out (vty, " LDP Discovery Sources:\n"); + if (v4adjs_buffer[0] != '\0') { + vty_out (vty, " IPv4:\n"); + vty_out(vty, "%s", v4adjs_buffer); + } + if (v6adjs_buffer[0] != '\0') { + vty_out (vty, " IPv6:\n"); + vty_out(vty, "%s", v6adjs_buffer); + } + vty_out (vty, "\n"); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_nbr_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_nbr *nbr; + json_object *json_array; + json_object *json_nbr; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + json_object_object_get_ex(json, "neighbors", &json_array); + if (!json_array) { + json_array = json_object_new_array(); + json_object_object_add(json, "neighbors", json_array); + } + + json_nbr = json_object_new_object(); + json_object_string_add(json_nbr, "addressFamily", + af_name(nbr->af)); + json_object_string_addf(json_nbr, "neighborId", "%pI4", + &nbr->id); + json_object_string_add(json_nbr, "state", + nbr_state_name(nbr->nbr_state)); + json_object_string_add(json_nbr, "transportAddress", + log_addr(nbr->af, &nbr->raddr)); + json_object_string_add(json_nbr, "upTime", + log_time(nbr->uptime)); + + json_object_array_add(json_array, json_nbr); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static void +show_nbr_detail_adj_json(struct ctl_adj *adj, json_object *adj_list) +{ + char adj_string[128]; + + switch (adj->type) { + case HELLO_LINK: + strlcpy(adj_string, "interface: ", sizeof(adj_string)); + strlcat(adj_string, adj->ifname, sizeof(adj_string)); + break; + case HELLO_TARGETED: + strlcpy(adj_string, "targetedHello: ", sizeof(adj_string)); + strlcat(adj_string, log_addr(adj->af, &adj->src_addr), + sizeof(adj_string)); + break; + } + + json_object_array_add(adj_list, json_object_new_string(adj_string)); +} + +static int +show_nbr_detail_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_nbr *nbr; + struct ldp_stats *stats; + struct ctl_adj *adj; + char buf[PREFIX_STRLEN]; + json_object *json_nbr; + json_object *json_array; + json_object *json_counter; + static json_object *json_nbr_sources; + static json_object *json_v4adjs; + static json_object *json_v6adjs; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + json_nbr = json_object_new_object(); + json_object_object_add(json, + inet_ntop(AF_INET, &nbr->id, buf, + sizeof(buf)), json_nbr); + json_object_string_addf(json_nbr, "peerId", "%pI4", &nbr->id); + json_object_string_add(json_nbr, "tcpLocalAddress", + log_addr(nbr->af, &nbr->laddr)); + json_object_int_add(json_nbr, "tcpLocalPort", + ntohs(nbr->lport)); + json_object_string_add(json_nbr, "tcpRemoteAddress", + log_addr(nbr->af, &nbr->raddr)); + json_object_int_add(json_nbr, "tcpRemotePort", + ntohs(nbr->rport)); + json_object_string_add(json_nbr, "authentication", + (nbr->auth_method == AUTH_MD5SIG) ? "TCP MD5 Signature" : + "none"); + json_object_int_add(json_nbr, "sessionHoldtime", nbr->holdtime); + json_object_int_add(json_nbr, "keepAliveInterval", + nbr->holdtime / KEEPALIVE_PER_PERIOD); + json_object_string_add(json_nbr, "state", + nbr_state_name(nbr->nbr_state)); + json_object_string_add(json_nbr, "upTime", + log_time(nbr->uptime)); + + /* message_counters */ + stats = &nbr->stats; + json_array = json_object_new_array(); + json_object_object_add(json_nbr, "sentMessages", json_array); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "keepalive", + stats->kalive_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "address", + stats->addr_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "addressWithdraw", + stats->addrwdraw_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "notification", + stats->notif_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "capability", + stats->capability_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelMapping", + stats->labelmap_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelRequest", + stats->labelreq_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelWithdraw", + stats->labelwdraw_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelRelease", + stats->labelrel_sent); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelAbortRequest", + stats->labelabreq_sent); + json_object_array_add(json_array, json_counter); + + json_array = json_object_new_array(); + json_object_object_add(json_nbr, "receivedMessages", json_array); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "keepalive", + stats->kalive_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "address", + stats->addr_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "addressWithdraw", + stats->addrwdraw_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "notification", + stats->notif_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "capability", + stats->capability_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelMapping", + stats->labelmap_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelRequest", + stats->labelreq_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelWithdraw", + stats->labelwdraw_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelRelease", + stats->labelrel_rcvd); + json_object_array_add(json_array, json_counter); + json_counter = json_object_new_object(); + json_object_int_add(json_counter, "labelAbortRequest", + stats->labelabreq_rcvd); + json_object_array_add(json_array, json_counter); + + /* capabilities */ + show_nbr_capabilities_json(nbr, json_nbr); + + /* discovery sources */ + json_nbr_sources = json_object_new_object(); + json_object_object_add(json_nbr, "discoverySources", + json_nbr_sources); + json_v4adjs = NULL; + json_v6adjs = NULL; + break; + case IMSG_CTL_SHOW_NBR_DISC: + adj = imsg->data; + + switch (adj->af) { + case AF_INET: + if (!json_v4adjs) { + json_v4adjs = json_object_new_array(); + json_object_object_add(json_nbr_sources, "ipv4", + json_v4adjs); + } + show_nbr_detail_adj_json(adj, json_v4adjs); + break; + case AF_INET6: + if (!json_v6adjs) { + json_v6adjs = json_object_new_array(); + json_object_object_add(json_nbr_sources, "ipv6", + json_v6adjs); + } + show_nbr_detail_adj_json(adj, json_v6adjs); + break; + default: + fatalx("show_nbr_detail_msg_json: unknown af"); + } + break; + case IMSG_CTL_SHOW_NBR_END: + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +void +show_nbr_capabilities(struct vty *vty, struct ctl_nbr *nbr) +{ + vty_out (vty, " Capabilities Sent:\n" + " - Dynamic Announcement (0x0506)\n" + " - Typed Wildcard (0x050B)\n" + " - Unrecognized Notification (0x0603)\n"); + vty_out (vty, " Capabilities Received:\n"); + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_DYNAMIC)) + vty_out (vty," - Dynamic Announcement (0x0506)\n"); + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_TWCARD)) + vty_out (vty, " - Typed Wildcard (0x050B)\n"); + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_UNOTIF)) + vty_out (vty," - Unrecognized Notification (0x0603)\n"); +} + +static int +show_nbr_capabilities_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) +{ + struct ctl_nbr *nbr; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + if (nbr->nbr_state != NBR_STA_OPER) + break; + + vty_out (vty, "Peer LDP Identifier: %pI4:0\n", + &nbr->id); + show_nbr_capabilities(vty, nbr); + vty_out (vty, "\n"); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static void +show_nbr_capabilities_json(struct ctl_nbr *nbr, json_object *json_nbr) +{ + json_object *json_array; + json_object *json_cap; + + /* sent capabilities */ + json_array = json_object_new_array(); + json_object_object_add(json_nbr, "sentCapabilities", json_array); + + /* Dynamic Announcement (0x0506) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", "Dynamic Announcement"); + json_object_string_add(json_cap, "tlvType", "0x0506"); + json_object_array_add(json_array, json_cap); + + /* Typed Wildcard (0x050B) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", "Typed Wildcard"); + json_object_string_add(json_cap, "tlvType", "0x050B"); + json_object_array_add(json_array, json_cap); + + /* Unrecognized Notification (0x0603) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Unrecognized Notification"); + json_object_string_add(json_cap, "tlvType", "0x0603"); + json_object_array_add(json_array, json_cap); + + /* received capabilities */ + json_array = json_object_new_array(); + json_object_object_add(json_nbr, "receivedCapabilities", json_array); + + /* Dynamic Announcement (0x0506) */ + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_DYNAMIC)) { + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Dynamic Announcement"); + json_object_string_add(json_cap, "tlvType", "0x0506"); + json_object_array_add(json_array, json_cap); + } + + /* Typed Wildcard (0x050B) */ + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_TWCARD)) { + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Typed Wildcard"); + json_object_string_add(json_cap, "tlvType", "0x050B"); + json_object_array_add(json_array, json_cap); + } + + /* Unrecognized Notification (0x0603) */ + if (CHECK_FLAG(nbr->flags, F_NBR_CAP_UNOTIF)) { + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Unrecognized Notification"); + json_object_string_add(json_cap, "tlvType", "0x0603"); + json_object_array_add(json_array, json_cap); + } +} + +static int +show_nbr_capabilities_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_nbr *nbr; + char buf[PREFIX_STRLEN]; + json_object *json_nbr; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + nbr = imsg->data; + + if (nbr->nbr_state != NBR_STA_OPER) + break; + + json_nbr = json_object_new_object(); + json_object_object_add(json, inet_ntop(AF_INET, &nbr->id, buf, + sizeof(buf)), json_nbr); + show_nbr_capabilities_json(nbr, json_nbr); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_lib_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) +{ + struct ctl_rt *rt; + char dstnet[BUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + rt = imsg->data; + + if (params->lib.remote_label != NO_LABEL && + params->lib.remote_label != rt->remote_label) + return (0); + fallthrough; + case IMSG_CTL_SHOW_LIB_RCVD: + rt = imsg->data; + + if (imsg->hdr.type == IMSG_CTL_SHOW_LIB_BEGIN && + !rt->no_downstream) + break; + + snprintf(dstnet, sizeof(dstnet), "%s/%d", + log_addr(rt->af, &rt->prefix), rt->prefixlen); + + vty_out(vty, "%-4s %-20s", af_name(rt->af), dstnet); + if (strlen(dstnet) > 20) + vty_out(vty, "\n%25s", " "); + vty_out (vty, " %-15pI4 %-11s %-13s %6s\n", + &rt->nexthop, log_label(rt->local_label), + log_label(rt->remote_label), + rt->in_use ? "yes" : "no"); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_lib_detail_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) +{ + struct ctl_rt *rt = NULL; + static char dstnet[BUFSIZ]; + static int upstream, downstream; + size_t buflen; + static char sent_buffer[LDPBUFSIZ]; + static char rcvd_buffer[LDPBUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + rt = imsg->data; + + upstream = 0; + downstream = 0; + sent_buffer[0] = '\0'; + rcvd_buffer[0] = '\0'; + snprintf(dstnet, sizeof(dstnet), "%s/%d", + log_addr(rt->af, &rt->prefix), rt->prefixlen); + break; + case IMSG_CTL_SHOW_LIB_SENT: + rt = imsg->data; + + upstream = 1; + buflen = strlen(sent_buffer); + snprintfrr(sent_buffer + buflen, LDPBUFSIZ - buflen, + "%12s%pI4:0\n", "", &rt->nexthop); + break; + case IMSG_CTL_SHOW_LIB_RCVD: + rt = imsg->data; + downstream = 1; + buflen = strlen(rcvd_buffer); + snprintfrr(rcvd_buffer + buflen, LDPBUFSIZ - buflen, + "%12s%pI4:0, label %s%s\n", "", &rt->nexthop, + log_label(rt->remote_label), + rt->in_use ? " (in use)" : ""); + break; + case IMSG_CTL_SHOW_LIB_END: + rt = imsg->data; + + if (params->lib.remote_label != NO_LABEL && + !downstream) + break; + vty_out(vty, "%s\n", dstnet); + vty_out(vty, "%-8sLocal binding: label: %s\n", "", + log_label(rt->local_label)); + if (upstream) { + vty_out (vty, "%-8sAdvertised to:\n", ""); + vty_out(vty, "%s", sent_buffer); + } + if (downstream) { + vty_out (vty, "%-8sRemote bindings:\n", ""); + vty_out(vty, "%s", rcvd_buffer); + } else + vty_out (vty, "%-8sNo remote bindings\n",""); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_lib_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_rt *rt; + json_object *json_array; + json_object *json_lib_entry; + char dstnet[BUFSIZ]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + case IMSG_CTL_SHOW_LIB_RCVD: + rt = imsg->data; + + if (imsg->hdr.type == IMSG_CTL_SHOW_LIB_BEGIN && + !rt->no_downstream) + break; + + json_object_object_get_ex(json, "bindings", &json_array); + if (!json_array) { + json_array = json_object_new_array(); + json_object_object_add(json, "bindings", json_array); + } + + json_lib_entry = json_object_new_object(); + json_object_string_add(json_lib_entry, "addressFamily", + af_name(rt->af)); + snprintf(dstnet, sizeof(dstnet), "%s/%d", + log_addr(rt->af, &rt->prefix), rt->prefixlen); + json_object_string_add(json_lib_entry, "prefix", dstnet); + json_object_string_addf(json_lib_entry, "neighborId", "%pI4", + &rt->nexthop); + json_object_string_add(json_lib_entry, "localLabel", + log_label(rt->local_label)); + json_object_string_add(json_lib_entry, "remoteLabel", + log_label(rt->remote_label)); + json_object_int_add(json_lib_entry, "inUse", rt->in_use); + + json_object_array_add(json_array, json_lib_entry); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_lib_detail_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_rt *rt = NULL; + char dstnet[BUFSIZ]; + static json_object *json_lib_entry; + static json_object *json_adv_labels; + json_object *json_adv_label; + static json_object *json_remote_labels; + json_object *json_remote_label; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + rt = imsg->data; + + snprintf(dstnet, sizeof(dstnet), "%s/%d", + log_addr(rt->af, &rt->prefix), rt->prefixlen); + + json_lib_entry = json_object_new_object(); + json_object_string_add(json_lib_entry, "localLabel", + log_label(rt->local_label)); + + json_adv_labels = json_object_new_array(); + json_object_object_add(json_lib_entry, "advertisedTo", + json_adv_labels); + + json_remote_labels = json_object_new_array(); + json_object_object_add(json_lib_entry, "remoteLabels", + json_remote_labels); + + json_object_object_add(json, dstnet, json_lib_entry); + break; + case IMSG_CTL_SHOW_LIB_SENT: + rt = imsg->data; + + json_adv_label = json_object_new_object(); + json_object_string_addf(json_adv_label, "neighborId", "%pI4", + &rt->nexthop); + json_object_array_add(json_adv_labels, json_adv_label); + break; + case IMSG_CTL_SHOW_LIB_RCVD: + rt = imsg->data; + + json_remote_label = json_object_new_object(); + json_object_string_addf(json_remote_label, "neighborId", "%pI4", + &rt->nexthop); + json_object_string_add(json_remote_label, "label", + log_label(rt->remote_label)); + json_object_int_add(json_remote_label, "inUse", rt->in_use); + json_object_array_add(json_remote_labels, json_remote_label); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_l2vpn_binding_msg(struct vty *vty, struct imsg *imsg, + struct show_params *params) +{ + struct ctl_pw *pw; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_BINDING: + pw = imsg->data; + + vty_out (vty, " Destination Address: %pI4, VC ID: %u\n", + &pw->lsr_id, pw->pwid); + + /* local binding */ + if (pw->local_label != NO_LABEL) { + vty_out (vty, " Local Label: %u\n", + pw->local_label); + vty_out (vty, "%-8sCbit: %u, VC Type: %s, GroupID: %u\n", "", pw->local_cword, + pw_type_name(pw->type),pw->local_gid); + vty_out (vty, "%-8sMTU: %u\n", "",pw->local_ifmtu); + vty_out (vty, "%-8sLast failure: %s\n", "", + pw_error_code(pw->reason)); + } else + vty_out (vty," Local Label: unassigned\n"); + + /* remote binding */ + if (pw->remote_label != NO_LABEL) { + vty_out (vty, " Remote Label: %u\n", + pw->remote_label); + vty_out (vty, "%-8sCbit: %u, VC Type: %s, GroupID: %u\n", "", pw->remote_cword, + pw_type_name(pw->type),pw->remote_gid); + vty_out (vty, "%-8sMTU: %u\n", "",pw->remote_ifmtu); + } else + vty_out (vty," Remote Label: unassigned\n"); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_l2vpn_binding_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_pw *pw; + json_object *json_pw; + char key_name[64]; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_BINDING: + pw = imsg->data; + + json_pw = json_object_new_object(); + json_object_string_addf(json_pw, "destination", "%pI4", + &pw->lsr_id); + json_object_int_add(json_pw, "vcId", pw->pwid); + + /* local binding */ + if (pw->local_label != NO_LABEL) { + json_object_int_add(json_pw, "localLabel", + pw->local_label); + json_object_int_add(json_pw, "localControlWord", + pw->local_cword); + json_object_string_add(json_pw, "localVcType", + pw_type_name(pw->type)); + json_object_int_add(json_pw, "localGroupID", + pw->local_gid); + json_object_int_add(json_pw, "localIfMtu", + pw->local_ifmtu); + json_object_string_add(json_pw, "lastFailureReason", + pw_error_code(pw->reason)); + } else + json_object_string_add(json_pw, "localLabel", + "unassigned"); + + /* remote binding */ + if (pw->remote_label != NO_LABEL) { + json_object_int_add(json_pw, "remoteLabel", + pw->remote_label); + json_object_int_add(json_pw, "remoteControlWord", + pw->remote_cword); + json_object_string_add(json_pw, "remoteVcType", + pw_type_name(pw->type)); + json_object_int_add(json_pw, "remoteGroupID", + pw->remote_gid); + json_object_int_add(json_pw, "remoteIfMtu", + pw->remote_ifmtu); + } else + json_object_string_add(json_pw, "remoteLabel", + "unassigned"); + + snprintfrr(key_name, sizeof(key_name), "%pI4: %u", + &pw->lsr_id, pw->pwid); + json_object_object_add(json, key_name, json_pw); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +show_l2vpn_pw_msg(struct vty *vty, struct imsg *imsg, struct show_params *params) +{ + struct ctl_pw *pw; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_PW: + pw = imsg->data; + + vty_out (vty, "%-9s %-15pI4 %-10u %-16s %-10s\n", pw->ifname, + &pw->lsr_id, pw->pwid, pw->l2vpn_name, + (pw->status == PW_FORWARDING ? "UP" : "DOWN")); + break; + case IMSG_CTL_END: + vty_out (vty, "\n"); + return (1); + default: + break; + } + + return (0); +} + +static int +show_l2vpn_pw_msg_json(struct imsg *imsg, struct show_params *params, + json_object *json) +{ + struct ctl_pw *pw; + json_object *json_pw; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_PW: + pw = imsg->data; + + json_pw = json_object_new_object(); + json_object_string_addf(json_pw, "peerId", "%pI4", &pw->lsr_id); + json_object_int_add(json_pw, "vcId", pw->pwid); + json_object_string_add(json_pw, "vpnName", pw->l2vpn_name); + if (pw->status == PW_FORWARDING) + json_object_string_add(json_pw, "status", "up"); + else + json_object_string_add(json_pw, "status", "down"); + json_object_object_add(json, pw->ifname, json_pw); + break; + case IMSG_CTL_END: + return (1); + default: + break; + } + + return (0); +} + +static int +ldp_vty_connect(struct imsgbuf *ibuf) +{ + struct sockaddr_un s_un; + int ctl_sock; + + /* connect to ldpd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + strlcpy(s_un.sun_path, ctl_sock_path, sizeof(s_un.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + log_warn("%s: connect: %s", __func__, ctl_sock_path); + close(ctl_sock); + return (-1); + } + + imsg_init(ibuf, ctl_sock); + + return (0); +} + +static int +ldp_vty_dispatch_iface(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + int ret; + + if (params->json) + ret = show_interface_msg_json(imsg, params, json); + else + ret = show_interface_msg(vty, imsg, params); + + return (ret); +} + +static int +ldp_vty_dispatch_ldp_sync(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + int ret; + + if (params->json) + ret = show_ldp_sync_msg_json(imsg, params, json); + else + ret = show_ldp_sync_msg(vty, imsg, params); + + return (ret); +} + +static int +ldp_vty_dispatch_disc(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + int ret; + + if (params->detail) { + if (params->json) + ret = show_discovery_detail_msg_json(imsg, params, + json); + else + ret = show_discovery_detail_msg(vty, imsg, params); + } else { + if (params->json) + ret = show_discovery_msg_json(imsg, params, json); + else + ret = show_discovery_msg(vty, imsg, params); + } + + return (ret); +} + +static int +ldp_vty_dispatch_nbr(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + static bool filtered = false; + struct ctl_nbr *nbr; + int ret; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_NBR: + filtered = false; + nbr = imsg->data; + + if (params->neighbor.lsr_id.s_addr != INADDR_ANY && + params->neighbor.lsr_id.s_addr != nbr->id.s_addr) { + filtered = true; + return (0); + } + break; + case IMSG_CTL_SHOW_NBR_DISC: + case IMSG_CTL_SHOW_NBR_END: + if (filtered) + return (0); + break; + default: + break; + } + + if (params->neighbor.capabilities) { + if (params->json) + ret = show_nbr_capabilities_msg_json(imsg, params, + json); + else + ret = show_nbr_capabilities_msg(vty, imsg, params); + } else if (params->detail) { + if (params->json) + ret = show_nbr_detail_msg_json(imsg, params, json); + else + ret = show_nbr_detail_msg(vty, imsg, params); + } else { + if (params->json) + ret = show_nbr_msg_json(imsg, params, json); + else + ret = show_nbr_msg(vty, imsg, params); + } + + return (ret); +} + +static int +ldp_vty_dispatch_lib(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + static bool filtered = false; + struct ctl_rt *rt = NULL; + struct prefix prefix; + int ret; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + filtered = false; + break; + case IMSG_CTL_SHOW_LIB_SENT: + case IMSG_CTL_SHOW_LIB_RCVD: + case IMSG_CTL_SHOW_LIB_END: + if (filtered) + return (0); + break; + default: + break; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_BEGIN: + case IMSG_CTL_SHOW_LIB_SENT: + case IMSG_CTL_SHOW_LIB_RCVD: + case IMSG_CTL_SHOW_LIB_END: + rt = imsg->data; + + if (params->family != AF_UNSPEC && params->family != rt->af) { + filtered = true; + return (0); + } + + prefix.family = rt->af; + prefix.prefixlen = rt->prefixlen; + memcpy(&prefix.u.val, &rt->prefix, sizeof(prefix.u.val)); + if (params->lib.prefix.family != AF_UNSPEC) { + if (!params->lib.longer_prefixes && + !prefix_same(¶ms->lib.prefix, &prefix)) { + filtered = true; + return (0); + } else if (params->lib.longer_prefixes && + !prefix_match(¶ms->lib.prefix, &prefix)) { + filtered = true; + return (0); + } + } + + if (params->lib.local_label != NO_LABEL && + params->lib.local_label != rt->local_label) { + filtered = true; + return (0); + } + break; + default: + break; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_SENT: + case IMSG_CTL_SHOW_LIB_RCVD: + if (params->lib.neighbor.s_addr != INADDR_ANY && + params->lib.neighbor.s_addr != rt->nexthop.s_addr) + return (0); + break; + default: + break; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_LIB_RCVD: + if (params->lib.remote_label != NO_LABEL && + params->lib.remote_label != rt->remote_label) + return (0); + break; + default: + break; + } + + if (params->detail) { + if (params->json) + ret = show_lib_detail_msg_json(imsg, params, json); + else + ret = show_lib_detail_msg(vty, imsg, params); + } else { + if (params->json) + ret = show_lib_msg_json(imsg, params, json); + else + ret = show_lib_msg(vty, imsg, params); + } + + return (ret); +} + +static int +ldp_vty_dispatch_l2vpn_pw(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + struct ctl_pw *pw; + int ret; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_PW: + pw = imsg->data; + if (params->l2vpn.peer.s_addr != INADDR_ANY && + params->l2vpn.peer.s_addr != pw->lsr_id.s_addr) + return (0); + if (params->l2vpn.ifname[0] != '\0' && + strcmp(params->l2vpn.ifname, pw->ifname)) + return (0); + if (params->l2vpn.vcid && params->l2vpn.vcid != pw->pwid) + return (0); + break; + default: + break; + } + + if (params->json) + ret = show_l2vpn_pw_msg_json(imsg, params, json); + else + ret = show_l2vpn_pw_msg(vty, imsg, params); + + return (ret); +} + +static int +ldp_vty_dispatch_l2vpn_binding(struct vty *vty, struct imsg *imsg, + struct show_params *params, json_object *json) +{ + struct ctl_pw *pw; + int ret; + + switch (imsg->hdr.type) { + case IMSG_CTL_SHOW_L2VPN_BINDING: + pw = imsg->data; + if (params->l2vpn.peer.s_addr != INADDR_ANY && + params->l2vpn.peer.s_addr != pw->lsr_id.s_addr) + return (0); + if (params->l2vpn.local_label != NO_LABEL && + params->l2vpn.local_label != pw->local_label) + return (0); + if (params->l2vpn.remote_label != NO_LABEL && + params->l2vpn.remote_label != pw->remote_label) + return (0); + break; + default: + break; + } + + if (params->json) + ret = show_l2vpn_binding_msg_json(imsg, params, json); + else + ret = show_l2vpn_binding_msg(vty, imsg, params); + + return (ret); +} + +static int +ldp_vty_dispatch_msg(struct vty *vty, struct imsg *imsg, enum show_command cmd, + struct show_params *params, json_object *json) +{ + switch (cmd) { + case SHOW_IFACE: + return (ldp_vty_dispatch_iface(vty, imsg, params, json)); + case SHOW_DISC: + return (ldp_vty_dispatch_disc(vty, imsg, params, json)); + case SHOW_NBR: + return (ldp_vty_dispatch_nbr(vty, imsg, params, json)); + case SHOW_LIB: + return (ldp_vty_dispatch_lib(vty, imsg, params, json)); + case SHOW_L2VPN_PW: + return (ldp_vty_dispatch_l2vpn_pw(vty, imsg, params, json)); + case SHOW_L2VPN_BINDING: + return (ldp_vty_dispatch_l2vpn_binding(vty, imsg, params, + json)); + case SHOW_LDP_SYNC: + return (ldp_vty_dispatch_ldp_sync(vty, imsg, params, json)); + default: + return (0); + } +} + +static int +ldp_vty_dispatch(struct vty *vty, struct imsgbuf *ibuf, enum show_command cmd, + struct show_params *params) +{ + struct imsg imsg; + int n, done = 0, ret = CMD_SUCCESS; + json_object *json = NULL; + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) { + log_warn("write error"); + close(ibuf->fd); + return (CMD_WARNING); + } + + if (params->json) + json = json_object_new_object(); + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) { + log_warnx("imsg_read error"); + ret = CMD_WARNING; + goto done; + } + if (n == 0) { + log_warnx("pipe closed"); + ret = CMD_WARNING; + goto done; + } + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) { + log_warnx("imsg_get error"); + ret = CMD_WARNING; + goto done; + } + if (n == 0) + break; + done = ldp_vty_dispatch_msg(vty, &imsg, cmd, params, + json); + imsg_free(&imsg); + } + } + + done: + close(ibuf->fd); + if (json) { + vty_json(vty, json); + } + + return (ret); +} + +static int +ldp_vty_get_af(const char *str, int *af) +{ + if (str == NULL) { + *af = AF_UNSPEC; + return (0); + } else if (strcmp(str, "ipv4") == 0) { + *af = AF_INET; + return (0); + } else if (strcmp(str, "ipv6") == 0) { + *af = AF_INET6; + return (0); + } + + return (-1); +} + +int +ldp_vty_show_binding(struct vty *vty, const char *af_str, const char *prefix, + int longer_prefixes, const char *neighbor, unsigned long local_label, + unsigned long remote_label, const char *detail, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + int af; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + if (ldp_vty_get_af(af_str, &af) < 0) + return (CMD_ERR_NO_MATCH); + + memset(¶ms, 0, sizeof(params)); + params.family = af; + params.detail = (detail) ? 1 : 0; + params.json = (json) ? 1 : 0; + if (prefix) { + (void)str2prefix(prefix, ¶ms.lib.prefix); + params.lib.longer_prefixes = longer_prefixes; + } + if (neighbor && + (inet_pton(AF_INET, neighbor, ¶ms.lib.neighbor) != 1 || + bad_addr_v4(params.lib.neighbor))) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + params.lib.local_label = local_label; + params.lib.remote_label = remote_label; + + if (!params.detail && !params.json) + vty_out (vty, "%-4s %-20s %-15s %-11s %-13s %6s\n", "AF", + "Destination", "Nexthop", "Local Label", "Remote Label", + "In Use"); + + imsg_compose(&ibuf, IMSG_CTL_SHOW_LIB, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_LIB, ¶ms)); +} + +int +ldp_vty_show_discovery(struct vty *vty, const char *af_str, const char *detail, + const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + int af; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + if (ldp_vty_get_af(af_str, &af) < 0) + return (CMD_ERR_NO_MATCH); + + memset(¶ms, 0, sizeof(params)); + params.family = af; + params.detail = (detail) ? 1 : 0; + params.json = (json) ? 1 : 0; + + if (!params.detail && !params.json) + vty_out (vty, "%-4s %-15s %-8s %-15s %9s\n", + "AF", "ID", "Type", "Source", "Holdtime"); + + if (params.detail) + imsg_compose(&ibuf, IMSG_CTL_SHOW_DISCOVERY_DTL, 0, 0, -1, + NULL, 0); + else + imsg_compose(&ibuf, IMSG_CTL_SHOW_DISCOVERY, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_DISC, ¶ms)); +} + +int +ldp_vty_show_interface(struct vty *vty, const char *af_str, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + unsigned int ifidx = 0; + int af; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + if (ldp_vty_get_af(af_str, &af) < 0) + return (CMD_ERR_NO_MATCH); + + memset(¶ms, 0, sizeof(params)); + params.family = af; + params.json = (json) ? 1 : 0; + + /* header */ + if (!params.json) { + vty_out (vty, "%-4s %-11s %-6s %-8s %-12s %3s\n", "AF", + "Interface", "State", "Uptime", "Hello Timers","ac"); + } + + imsg_compose(&ibuf, IMSG_CTL_SHOW_INTERFACE, 0, 0, -1, &ifidx, + sizeof(ifidx)); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_IFACE, ¶ms)); +} + +int +ldp_vty_show_capabilities(struct vty *vty, const char *json) +{ + if (json) { + json_object *json; + json_object *json_array; + json_object *json_cap; + + json = json_object_new_object(); + json_array = json_object_new_array(); + json_object_object_add(json, "capabilities", json_array); + + /* Dynamic Announcement (0x0506) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Dynamic Announcement"); + json_object_string_add(json_cap, "tlvType", + "0x0506"); + json_object_array_add(json_array, json_cap); + + /* Typed Wildcard (0x050B) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Typed Wildcard"); + json_object_string_add(json_cap, "tlvType", + "0x050B"); + json_object_array_add(json_array, json_cap); + + /* Unrecognized Notification (0x0603) */ + json_cap = json_object_new_object(); + json_object_string_add(json_cap, "description", + "Unrecognized Notification"); + json_object_string_add(json_cap, "tlvType", + "0x0603"); + json_object_array_add(json_array, json_cap); + + vty_json(vty, json); + return (0); + } + + vty_out (vty, + "Supported LDP Capabilities\n" + " * Dynamic Announcement (0x0506)\n" + " * Typed Wildcard (0x050B)\n" + " * Unrecognized Notification (0x0603)\n\n"); + + return (0); +} + +int +ldp_vty_show_neighbor(struct vty *vty, const char *lsr_id, int capabilities, + const char *detail, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + memset(¶ms, 0, sizeof(params)); + params.detail = (detail) ? 1 : 0; + params.json = (json) ? 1 : 0; + params.neighbor.capabilities = capabilities; + if (lsr_id && + (inet_pton(AF_INET, lsr_id, ¶ms.neighbor.lsr_id) != 1 || + bad_addr_v4(params.neighbor.lsr_id))) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + + if (params.neighbor.capabilities) + params.detail = 1; + + if (!params.detail && !params.json) + vty_out (vty, "%-4s %-15s %-11s %-15s %8s\n", + "AF", "ID", "State", "Remote Address","Uptime"); + + imsg_compose(&ibuf, IMSG_CTL_SHOW_NBR, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_NBR, ¶ms)); +} + +int +ldp_vty_show_ldp_sync(struct vty *vty, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + memset(¶ms, 0, sizeof(params)); + params.json = (json) ? 1 : 0; + + imsg_compose(&ibuf, IMSG_CTL_SHOW_LDP_SYNC, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_LDP_SYNC, ¶ms)); +} + +int +ldp_vty_show_atom_binding(struct vty *vty, const char *peer, + unsigned long local_label, unsigned long remote_label, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + memset(¶ms, 0, sizeof(params)); + params.json = (json) ? 1 : 0; + if (peer && + (inet_pton(AF_INET, peer, ¶ms.l2vpn.peer) != 1 || + bad_addr_v4(params.l2vpn.peer))) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + params.l2vpn.local_label = local_label; + params.l2vpn.remote_label = remote_label; + + imsg_compose(&ibuf, IMSG_CTL_SHOW_L2VPN_BINDING, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_L2VPN_BINDING, ¶ms)); +} + +int +ldp_vty_show_atom_vc(struct vty *vty, const char *peer, const char *ifname, + const char *vcid, const char *json) +{ + struct imsgbuf ibuf; + struct show_params params; + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + memset(¶ms, 0, sizeof(params)); + params.json = (json) ? 1 : 0; + if (peer && + (inet_pton(AF_INET, peer, ¶ms.l2vpn.peer) != 1 || + bad_addr_v4(params.l2vpn.peer))) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_SUCCESS); + } + if (ifname) + strlcpy(params.l2vpn.ifname, ifname, + sizeof(params.l2vpn.ifname)); + if (vcid) + params.l2vpn.vcid = atoi(vcid); + + if (!params.json) { + /* header */ + vty_out (vty, "%-9s %-15s %-10s %-16s %-10s\n", + "Interface", "Peer ID", "VC ID", "Name","Status"); + vty_out (vty, "%-9s %-15s %-10s %-16s %-10s\n", + "---------", "---------------", "----------", + "----------------", "----------"); + } + + imsg_compose(&ibuf, IMSG_CTL_SHOW_L2VPN_PW, 0, 0, -1, NULL, 0); + return (ldp_vty_dispatch(vty, &ibuf, SHOW_L2VPN_PW, ¶ms)); +} + +int +ldp_vty_clear_nbr(struct vty *vty, const char *addr_str) +{ + struct imsgbuf ibuf; + struct ctl_nbr nbr; + + memset(&nbr, 0, sizeof(nbr)); + if (addr_str && + (ldp_get_address(addr_str, &nbr.af, &nbr.raddr) == -1 || + bad_addr(nbr.af, &nbr.raddr))) { + vty_out (vty, "%% Malformed address\n"); + return (CMD_WARNING); + } + + if (ldp_vty_connect(&ibuf) < 0) + return (CMD_WARNING); + + imsg_compose(&ibuf, IMSG_CTL_CLEAR_NBR, 0, 0, -1, &nbr, sizeof(nbr)); + + while (ibuf.w.queued) + if (msgbuf_write(&ibuf.w) <= 0 && errno != EAGAIN) { + log_warn("write error"); + close(ibuf.fd); + return (CMD_WARNING); + } + + close(ibuf.fd); + + return (CMD_SUCCESS); +} diff --git a/ldpd/ldp_zebra.c b/ldpd/ldp_zebra.c new file mode 100644 index 0000000..df682a1 --- /dev/null +++ b/ldpd/ldp_zebra.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016 by Open Source Routing. + */ + +#include + +#include "prefix.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "command.h" +#include "network.h" +#include "linklist.h" +#include "mpls.h" + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "ldp_sync.h" +#include "log.h" +#include "ldp_debug.h" + +static void ifp2kif(struct interface *, struct kif *); +static void ifc2kaddr(struct interface *, struct connected *, struct kaddr *); +static int ldp_zebra_send_mpls_labels(int, struct kroute *); +static int ldp_router_id_update(ZAPI_CALLBACK_ARGS); +static int ldp_interface_address_add(ZAPI_CALLBACK_ARGS); +static int ldp_interface_address_delete(ZAPI_CALLBACK_ARGS); +static int ldp_zebra_read_route(ZAPI_CALLBACK_ARGS); +static int ldp_zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS); +static void ldp_zebra_connected(struct zclient *); +static void ldp_zebra_filter_update(struct access_list *access); + +static void ldp_zebra_opaque_register(void); +static void ldp_zebra_opaque_unregister(void); +static int ldp_sync_zebra_send_announce(void); +static int ldp_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS); +static void ldp_sync_zebra_init(void); + +static struct zclient *zclient; +extern struct zclient *zclient_sync; +static bool zebra_registered = false; + +static void +ifp2kif(struct interface *ifp, struct kif *kif) +{ + memset(kif, 0, sizeof(*kif)); + strlcpy(kif->ifname, ifp->name, sizeof(kif->ifname)); + kif->ifindex = ifp->ifindex; + kif->operative = if_is_operative(ifp); + if (ifp->ll_type == ZEBRA_LLT_ETHER) + memcpy(kif->mac, ifp->hw_addr, ETH_ALEN); +} + +static void +ifc2kaddr(struct interface *ifp, struct connected *ifc, struct kaddr *ka) +{ + memset(ka, 0, sizeof(*ka)); + strlcpy(ka->ifname, ifp->name, sizeof(ka->ifname)); + ka->ifindex = ifp->ifindex; + ka->af = ifc->address->family; + ka->prefixlen = ifc->address->prefixlen; + + switch (ka->af) { + case AF_INET: + ka->addr.v4 = ifc->address->u.prefix4; + if (ifc->destination) + ka->dstbrd.v4 = ifc->destination->u.prefix4; + break; + case AF_INET6: + ka->addr.v6 = ifc->address->u.prefix6; + if (ifc->destination) + ka->dstbrd.v6 = ifc->destination->u.prefix6; + break; + default: + break; + } +} + +void +pw2zpw(struct l2vpn_pw *pw, struct zapi_pw *zpw) +{ + memset(zpw, 0, sizeof(*zpw)); + strlcpy(zpw->ifname, pw->ifname, sizeof(zpw->ifname)); + zpw->ifindex = pw->ifindex; + zpw->type = pw->l2vpn->pw_type; + zpw->af = pw->af; + zpw->nexthop.ipv6 = pw->addr.v6; + zpw->local_label = NO_LABEL; + zpw->remote_label = NO_LABEL; + if (CHECK_FLAG(pw->flags, F_PW_CWORD)) + zpw->flags = F_PSEUDOWIRE_CWORD; + zpw->data.ldp.lsr_id = pw->lsr_id; + zpw->data.ldp.pwid = pw->pwid; + strlcpy(zpw->data.ldp.vpn_name, pw->l2vpn->name, + sizeof(zpw->data.ldp.vpn_name)); +} + +static void +ldp_zebra_opaque_register(void) +{ + zclient_register_opaque(zclient, LDP_IGP_SYNC_IF_STATE_REQUEST); + zclient_register_opaque(zclient, LDP_RLFA_REGISTER); + zclient_register_opaque(zclient, LDP_RLFA_UNREGISTER_ALL); +} + +static void +ldp_zebra_opaque_unregister(void) +{ + zclient_unregister_opaque(zclient, LDP_IGP_SYNC_IF_STATE_REQUEST); + zclient_unregister_opaque(zclient, LDP_RLFA_REGISTER); + zclient_unregister_opaque(zclient, LDP_RLFA_UNREGISTER_ALL); +} + +int +ldp_sync_zebra_send_state_update(struct ldp_igp_sync_if_state *state) +{ + if (zclient_send_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE, + (const uint8_t *)state, sizeof(*state)) + == ZCLIENT_SEND_FAILURE) + return -1; + else + return 0; +} + +static int +ldp_sync_zebra_send_announce(void) +{ + struct ldp_igp_sync_announce announce; + announce.proto = ZEBRA_ROUTE_LDP; + + if (zclient_send_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE, + (const uint8_t *)&announce, sizeof(announce)) + == ZCLIENT_SEND_FAILURE) + return -1; + else + return 0; +} + +int ldp_zebra_send_rlfa_labels(struct zapi_rlfa_response *rlfa_labels) +{ + int ret; + + ret = zclient_send_opaque(zclient, LDP_RLFA_LABELS, + (const uint8_t *)rlfa_labels, + sizeof(*rlfa_labels)); + if (ret == ZCLIENT_SEND_FAILURE) { + log_warn("failed to send RLFA labels to IGP"); + return -1; + } + + return 0; +} + +static int +ldp_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct zapi_opaque_msg info; + struct ldp_igp_sync_if_state_req state_req; + struct zapi_rlfa_igp igp; + struct zapi_rlfa_request rlfa; + + s = zclient->ibuf; + + if(zclient_opaque_decode(s, &info) != 0) + return -1; + + switch (info.type) { + case LDP_IGP_SYNC_IF_STATE_REQUEST: + STREAM_GET(&state_req, s, sizeof(state_req)); + main_imsg_compose_ldpe(IMSG_LDP_SYNC_IF_STATE_REQUEST, 0, &state_req, + sizeof(state_req)); + break; + case LDP_RLFA_REGISTER: + STREAM_GET(&rlfa, s, sizeof(rlfa)); + main_imsg_compose_both(IMSG_RLFA_REG, &rlfa, sizeof(rlfa)); + break; + case LDP_RLFA_UNREGISTER_ALL: + STREAM_GET(&igp, s, sizeof(igp)); + main_imsg_compose_both(IMSG_RLFA_UNREG_ALL, &igp, sizeof(igp)); + break; + default: + break; + } + +stream_failure: + return 0; +} + +static void +ldp_sync_zebra_init(void) +{ + ldp_sync_zebra_send_announce(); +} + +static int +ldp_zebra_send_mpls_labels(int cmd, struct kroute *kr) +{ + struct zapi_labels zl = {}; + struct zapi_nexthop *znh; + + if (kr->local_label < MPLS_LABEL_RESERVED_MAX) + return (0); + + debug_zebra_out("prefix %s/%u nexthop %s ifindex %u labels %s/%s (%s)", + log_addr(kr->af, &kr->prefix), kr->prefixlen, + log_addr(kr->af, &kr->nexthop), kr->ifindex, + log_label(kr->local_label), log_label(kr->remote_label), + (cmd == ZEBRA_MPLS_LABELS_ADD) ? "add" : "delete"); + + zl.type = ZEBRA_LSP_LDP; + zl.local_label = kr->local_label; + + /* Set prefix. */ + if (kr->remote_label != NO_LABEL) { + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + zl.route.prefix.family = kr->af; + switch (kr->af) { + case AF_INET: + zl.route.prefix.u.prefix4 = kr->prefix.v4; + break; + case AF_INET6: + zl.route.prefix.u.prefix6 = kr->prefix.v6; + break; + default: + fatalx("ldp_zebra_send_mpls_labels: unknown af"); + } + zl.route.prefix.prefixlen = kr->prefixlen; + zl.route.type = kr->route_type; + zl.route.instance = kr->route_instance; + } + + /* If allow-broken-lsps is enabled then if an lsp is received with + * no remote label, instruct the forwarding plane to pop the top-level + * label and forward packets normally. This is a best-effort attempt + * to deliver labeled IP packets to their final destination (instead of + * dropping them). + */ + if (kr->remote_label == NO_LABEL + && !CHECK_FLAG(ldpd_conf->flags, F_LDPD_ALLOW_BROKEN_LSP) + && cmd == ZEBRA_MPLS_LABELS_ADD) + return 0; + + if (kr->remote_label == NO_LABEL) + kr->remote_label = MPLS_LABEL_IMPLICIT_NULL; + + /* Set nexthop. */ + zl.nexthop_num = 1; + znh = &zl.nexthops[0]; + switch (kr->af) { + case AF_INET: + znh->gate.ipv4 = kr->nexthop.v4; + if (kr->ifindex) + znh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + znh->type = NEXTHOP_TYPE_IPV4; + break; + case AF_INET6: + znh->gate.ipv6 = kr->nexthop.v6; + if (kr->ifindex) + znh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + else + znh->type = NEXTHOP_TYPE_IPV6; + break; + default: + break; + } + znh->ifindex = kr->ifindex; + znh->label_num = 1; + znh->labels[0] = kr->remote_label; + + if (zebra_send_mpls_labels(zclient, cmd, &zl) == ZCLIENT_SEND_FAILURE) + return -1; + + return 0; +} + +int +kr_change(struct kroute *kr) +{ + return (ldp_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_ADD, kr)); +} + +int +kr_delete(struct kroute *kr) +{ + return (ldp_zebra_send_mpls_labels(ZEBRA_MPLS_LABELS_DELETE, kr)); +} + +int +kmpw_add(struct zapi_pw *zpw) +{ + debug_zebra_out("pseudowire %s nexthop %s (add)", + zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); + + return zebra_send_pw(zclient, ZEBRA_PW_ADD, zpw) == ZCLIENT_SEND_FAILURE; +} + +int +kmpw_del(struct zapi_pw *zpw) +{ + debug_zebra_out("pseudowire %s nexthop %s (del)", + zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); + + return zebra_send_pw(zclient, ZEBRA_PW_DELETE, zpw) == ZCLIENT_SEND_FAILURE; +} + +int +kmpw_set(struct zapi_pw *zpw) +{ + debug_zebra_out("pseudowire %s nexthop %s labels %u/%u (set)", + zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop), + zpw->local_label, zpw->remote_label); + + return zebra_send_pw(zclient, ZEBRA_PW_SET, zpw) == ZCLIENT_SEND_FAILURE; +} + +int +kmpw_unset(struct zapi_pw *zpw) +{ + debug_zebra_out("pseudowire %s nexthop %s (unset)", + zpw->ifname, log_addr(zpw->af, (union ldpd_addr *)&zpw->nexthop)); + + return zebra_send_pw(zclient, ZEBRA_PW_UNSET, zpw) == ZCLIENT_SEND_FAILURE; +} + +void +kif_redistribute(const char *ifname) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct connected *ifc; + struct kif kif; + struct kaddr ka; + + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifname && strcmp(ifname, ifp->name) != 0) + continue; + + ifp2kif(ifp, &kif); + main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); + + frr_each (if_connected, ifp->connected, ifc) { + ifc2kaddr(ifp, ifc, &ka); + main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); + } + } +} + +static int +ldp_router_id_update(ZAPI_CALLBACK_ARGS) +{ + struct prefix router_id; + + zebra_router_id_update_read(zclient->ibuf, &router_id); + + if (bad_addr_v4(router_id.u.prefix4)) + return (0); + + debug_zebra_in("router-id update %pI4", &router_id.u.prefix4); + + global.rtr_id.s_addr = router_id.u.prefix4.s_addr; + main_imsg_compose_ldpe(IMSG_RTRID_UPDATE, 0, &global.rtr_id, + sizeof(global.rtr_id)); + + return (0); +} + +static int +ldp_ifp_create(struct interface *ifp) +{ + struct kif kif; + + debug_zebra_in("interface add %s index %d mtu %d", ifp->name, + ifp->ifindex, ifp->mtu); + + ifp2kif(ifp, &kif); + main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); + + return 0; +} + +static int +ldp_ifp_destroy(struct interface *ifp) +{ + struct kif kif; + + debug_zebra_in("interface delete %s index %d mtu %d", ifp->name, + ifp->ifindex, ifp->mtu); + + ifp2kif(ifp, &kif); + main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); + + return (0); +} + +static int +ldp_interface_status_change(struct interface *ifp) +{ + struct connected *ifc; + struct kif kif; + struct kaddr ka; + + debug_zebra_in("interface %s state update", ifp->name); + + ifp2kif(ifp, &kif); + main_imsg_compose_both(IMSG_IFSTATUS, &kif, sizeof(kif)); + + if (if_is_operative(ifp)) { + frr_each (if_connected, ifp->connected, ifc) { + ifc2kaddr(ifp, ifc, &ka); + main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); + } + } else { + frr_each (if_connected, ifp->connected, ifc) { + ifc2kaddr(ifp, ifc, &ka); + main_imsg_compose_ldpe(IMSG_DELADDR, 0, &ka, sizeof(ka)); + } + } + + return (0); +} + +static int ldp_ifp_up(struct interface *ifp) +{ + return ldp_interface_status_change(ifp); +} + +static int ldp_ifp_down(struct interface *ifp) +{ + return ldp_interface_status_change(ifp); +} + +static int +ldp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct interface *ifp; + struct kaddr ka; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return (0); + + ifp = ifc->ifp; + ifc2kaddr(ifp, ifc, &ka); + + /* Filter invalid addresses. */ + if (bad_addr(ka.af, &ka.addr)) + return (0); + + debug_zebra_in("address add %s/%u interface %s", + log_addr(ka.af, &ka.addr), ka.prefixlen, ifp->name); + + /* notify ldpe about new address */ + main_imsg_compose_ldpe(IMSG_NEWADDR, 0, &ka, sizeof(ka)); + + return (0); +} + +static int +ldp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct interface *ifp; + struct kaddr ka; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return (0); + + ifp = ifc->ifp; + ifc2kaddr(ifp, ifc, &ka); + connected_free(&ifc); + + /* Filter invalid addresses. */ + if (bad_addr(ka.af, &ka.addr)) + return (0); + + debug_zebra_in("address delete %s/%u interface %s", + log_addr(ka.af, &ka.addr), ka.prefixlen, ifp->name); + + /* notify ldpe about removed address */ + main_imsg_compose_ldpe(IMSG_DELADDR, 0, &ka, sizeof(ka)); + + return (0); +} + +static int +ldp_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct kroute kr; + int i, add = 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return (0); + + memset(&kr, 0, sizeof(kr)); + kr.af = api.prefix.family; + switch (kr.af) { + case AF_INET: + kr.prefix.v4 = api.prefix.u.prefix4; + break; + case AF_INET6: + kr.prefix.v6 = api.prefix.u.prefix6; + break; + default: + break; + } + kr.prefixlen = api.prefix.prefixlen; + kr.route_type = api.type; + kr.route_instance = api.instance; + + switch (api.type) { + case ZEBRA_ROUTE_CONNECT: + SET_FLAG(kr.flags, F_CONNECTED); + break; + case ZEBRA_ROUTE_BGP: + /* LDP should follow the IGP and ignore BGP routes */ + return (0); + default: + break; + } + + if (bad_addr(kr.af, &kr.prefix) || + (kr.af == AF_INET6 && IN6_IS_SCOPE_EMBED(&kr.prefix.v6))) + return (0); + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) + add = 1; + + if (api.nexthop_num == 0) + debug_zebra_in("route %s %s/%d (%s)", (add) ? "add" : "delete", + log_addr(kr.af, &kr.prefix), kr.prefixlen, + zebra_route_string(api.type)); + + /* loop through all the nexthops */ + for (i = 0; i < api.nexthop_num; i++) { + api_nh = &api.nexthops[i]; + switch (api_nh->type) { + case NEXTHOP_TYPE_IPV4: + if (kr.af != AF_INET) + continue; + kr.nexthop.v4 = api_nh->gate.ipv4; + kr.ifindex = 0; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + if (kr.af != AF_INET) + continue; + kr.nexthop.v4 = api_nh->gate.ipv4; + kr.ifindex = api_nh->ifindex; + break; + case NEXTHOP_TYPE_IPV6: + if (kr.af != AF_INET6) + continue; + kr.nexthop.v6 = api_nh->gate.ipv6; + kr.ifindex = 0; + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (kr.af != AF_INET6) + continue; + kr.nexthop.v6 = api_nh->gate.ipv6; + kr.ifindex = api_nh->ifindex; + break; + case NEXTHOP_TYPE_IFINDEX: + if (!CHECK_FLAG(kr.flags, F_CONNECTED)) + continue; + break; + case NEXTHOP_TYPE_BLACKHOLE: + continue; + } + + debug_zebra_in("route %s %s/%d nexthop %s ifindex %u (%s)", + (add) ? "add" : "delete", log_addr(kr.af, &kr.prefix), + kr.prefixlen, log_addr(kr.af, &kr.nexthop), kr.ifindex, + zebra_route_string(api.type)); + + if (add) + main_imsg_compose_lde(IMSG_NETWORK_ADD, 0, &kr, sizeof(kr)); + } + + main_imsg_compose_lde(IMSG_NETWORK_UPDATE, 0, &kr, sizeof(kr)); + + return (0); +} + +/* + * Receive PW status update from Zebra and send it to LDE process. + */ +static int +ldp_zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS) +{ + struct zapi_pw_status zpw; + + zebra_read_pw_status_update(cmd, zclient, length, vrf_id, &zpw); + + debug_zebra_in("pseudowire %s status %s 0x%x", zpw.ifname, + (zpw.status == PW_FORWARDING) ? "up" : "down", + zpw.status); + + main_imsg_compose_lde(IMSG_PW_UPDATE, 0, &zpw, sizeof(zpw)); + + return (0); +} + +void ldp_zebra_regdereg_zebra_info(bool want_register) +{ + if (zebra_registered == want_register) + return; + + log_debug("%s to receive default VRF information", + want_register ? "Register" : "De-register"); + + if (want_register) { + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, + AFI_IP6, ZEBRA_ROUTE_ALL, 0, + VRF_DEFAULT); + } else { + zclient_send_dereg_requests(zclient, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP, ZEBRA_ROUTE_ALL, 0, + VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP6, ZEBRA_ROUTE_ALL, 0, + VRF_DEFAULT); + } + zebra_registered = want_register; +} + +static void +ldp_zebra_connected(struct zclient *zclient) +{ + zebra_registered = false; + + /* if MPLS was already enabled and we are re-connecting, register again + */ + if (CHECK_FLAG(vty_conf->flags, F_LDPD_ENABLED)) + ldp_zebra_regdereg_zebra_info(true); + + ldp_zebra_opaque_register(); + + ldp_sync_zebra_init(); +} + +static void +ldp_zebra_filter_update(struct access_list *access) +{ + struct ldp_access laccess; + + if (access && access->name[0] != '\0') { + strlcpy(laccess.name, access->name, sizeof(laccess.name)); + debug_evt("%s ACL update filter name %s", __func__, access->name); + + main_imsg_compose_both(IMSG_FILTER_UPDATE, &laccess, sizeof(laccess)); + } +} + +extern struct zebra_privs_t ldpd_privs; + +static zclient_handler *const ldp_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = ldp_router_id_update, + [ZEBRA_INTERFACE_ADDRESS_ADD] = ldp_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = ldp_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = ldp_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = ldp_zebra_read_route, + [ZEBRA_PW_STATUS_UPDATE] = ldp_zebra_read_pw_status_update, + [ZEBRA_OPAQUE_MESSAGE] = ldp_zebra_opaque_msg_handler, +}; + +void ldp_zebra_init(struct event_loop *master) +{ + hook_register_prio(if_real, 0, ldp_ifp_create); + hook_register_prio(if_up, 0, ldp_ifp_up); + hook_register_prio(if_down, 0, ldp_ifp_down); + hook_register_prio(if_unreal, 0, ldp_ifp_destroy); + + /* Set default values. */ + zclient = zclient_new(master, &zclient_options_default, ldp_handlers, + array_size(ldp_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_LDP, 0, &ldpd_privs); + + /* set callbacks */ + zclient->zebra_connected = ldp_zebra_connected; + + /* Access list initialize. */ + access_list_add_hook(ldp_zebra_filter_update); + access_list_delete_hook(ldp_zebra_filter_update); +} + +void +ldp_zebra_destroy(void) +{ + ldp_zebra_opaque_unregister(); + zclient_stop(zclient); + zclient_free(zclient); + zclient = NULL; + + if (zclient_sync == NULL) + return; + zclient_stop(zclient_sync); + zclient_free(zclient_sync); + zclient_sync = NULL; +} diff --git a/ldpd/ldpd.c b/ldpd/ldpd.c new file mode 100644 index 0000000..4d38fdc --- /dev/null +++ b/ldpd/ldpd.c @@ -0,0 +1,2042 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2008 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include + +#include +#include +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" +#include "ldp_vty.h" +#include "ldp_debug.h" + +#include +#include +#include "getopt.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "vrf.h" +#include "filter.h" +#include "qobj.h" +#include "libfrr.h" +#include "lib_errors.h" +#include "zlog_recirculate.h" +#include "libagentx.h" + +static void ldpd_shutdown(void); +static pid_t start_child(enum ldpd_process, char *, int, int, int); +static void main_dispatch_ldpe(struct event *thread); +static void main_dispatch_lde(struct event *thread); +static int main_imsg_send_ipc_sockets(struct imsgbuf *, + struct imsgbuf *); +static void main_imsg_send_net_sockets(int); +static void main_imsg_send_net_socket(int, enum socket_type); +static int main_imsg_send_config(struct ldpd_conf *); +static void ldp_config_normalize(struct ldpd_conf *); +static void ldp_config_reset(struct ldpd_conf *); +static void ldp_config_reset_main(struct ldpd_conf *); +static void ldp_config_reset_af(struct ldpd_conf *, int); +static void ldp_config_reset_l2vpns(struct ldpd_conf *); +static void merge_global(struct ldpd_conf *, struct ldpd_conf *); +static void merge_af(int, struct ldpd_af_conf *, + struct ldpd_af_conf *); +static void merge_ifaces(struct ldpd_conf *, struct ldpd_conf *); +static void merge_iface_af(struct iface_af *, struct iface_af *); +static void merge_tnbrs(struct ldpd_conf *, struct ldpd_conf *); +static void merge_nbrps(struct ldpd_conf *, struct ldpd_conf *); +static void merge_l2vpns(struct ldpd_conf *, struct ldpd_conf *); +static void merge_l2vpn(struct ldpd_conf *, struct l2vpn *, + struct l2vpn *); + +DEFINE_QOBJ_TYPE(iface); +DEFINE_QOBJ_TYPE(tnbr); +DEFINE_QOBJ_TYPE(nbr_params); +DEFINE_QOBJ_TYPE(l2vpn_if); +DEFINE_QOBJ_TYPE(l2vpn_pw); +DEFINE_QOBJ_TYPE(l2vpn); +DEFINE_QOBJ_TYPE(ldpd_conf); + +const char *log_procname; + +struct ldpd_global global; +struct ldpd_init init; +struct ldpd_conf *ldpd_conf, *vty_conf; + +static struct imsgev *iev_ldpe, *iev_ldpe_sync; +static struct imsgev *iev_lde, *iev_lde_sync; +static pid_t ldpe_pid; +static pid_t lde_pid; + +static struct frr_daemon_info ldpd_di; + +DEFINE_HOOK(ldp_register_mib, (struct event_loop * tm), (tm)); + +static void ldp_load_module(const char *name) +{ + const char *dir; + dir = ldpd_di.module_path ? ldpd_di.module_path : frr_moduledir; + struct frrmod_runtime *module; + + module = frrmod_load(name, dir, NULL,NULL); + if (!module) { + fprintf(stderr, "%s: failed to load %s", __func__, name); + log_warnx("%s: failed to load %s", __func__, name); + } +} + +void ldp_agentx_enabled(void) +{ + ldp_load_module("snmp"); + hook_call(ldp_register_mib, master); +} + +enum ldpd_process ldpd_process; + +#define LDP_DEFAULT_CONFIG "ldpd.conf" + +/* Master of threads. */ +struct event_loop *master; + +/* ldpd privileges */ +static zebra_capabilities_t _caps_p [] = +{ + ZCAP_BIND, + ZCAP_NET_ADMIN +}; + +struct zebra_privs_t ldpd_privs = +{ +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + +/* CTL Socket path */ +char ctl_sock_path[MAXPATHLEN]; + +/* LDPd options. */ +#define OPTION_CTLSOCK 1001 +static const struct option longopts[] = +{ + { "ctl_socket", required_argument, NULL, OPTION_CTLSOCK}, + { "instance", required_argument, NULL, 'n'}, + { 0 } +}; + +/* SIGHUP handler. */ +static void +sighup(void) +{ + log_info("SIGHUP received"); + + /* + * Do a full configuration reload. In other words, reset vty_conf + * and build a new configuartion from scratch. + */ + ldp_config_reset(vty_conf); + vty_read_config(NULL, ldpd_di.config_file, config_default); + ldp_config_apply(NULL, vty_conf); +} + +/* SIGINT / SIGTERM handler. */ +static void +sigint(void) +{ + log_info("SIGINT received"); + ldpd_shutdown(); +} + +/* SIGUSR1 handler. */ +static void +sigusr1(void) +{ + zlog_rotate(); +} + +static struct frr_signal_t ldp_signals[] = +{ + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + } +}; + +static const struct frr_yang_module_info *const ldpd_yang_modules[] = { + &frr_filter_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(ldpd, LDP, + .vty_port = LDP_VTY_PORT, + + .proghelp = "Implementation of the LDP protocol.", + + .signals = ldp_signals, + .n_signals = array_size(ldp_signals), + + .privs = &ldpd_privs, + + .yang_modules = ldpd_yang_modules, + .n_yang_modules = array_size(ldpd_yang_modules), +); +/* clang-format on */ + +static void ldp_config_fork_apply(struct event *t) +{ + /* + * So the frr_config_fork() function schedules + * the read of the vty config( if there is a + * non-integrated config ) to be after the + * end of startup and we are starting the + * main process loop. We need to schedule + * the application of this if necessary + * after the read in of the config. + */ + ldp_config_apply(NULL, vty_conf); +} + +int +main(int argc, char *argv[]) +{ + char *saved_argv0; + int lflag = 0, eflag = 0; + int pipe_parent2ldpe[2]; + int pipe_parent2ldpe_sync[2]; + int pipe_ldpe_log[2]; + int pipe_parent2lde[2]; + int pipe_parent2lde_sync[2]; + int pipe_lde_log[2]; + bool ctl_sock_used = false; + + ldpd_process = PROC_MAIN; + log_procname = log_procnames[ldpd_process]; + + saved_argv0 = argv[0]; + if (saved_argv0 == NULL) + saved_argv0 = (char *)"ldpd"; + + frr_preinit(&ldpd_di, argc, argv); + frr_opt_add("LEn:", longopts, + " --ctl_socket Override ctl socket path\n" + " -n, --instance Instance id\n"); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case OPTION_CTLSOCK: + ctl_sock_used = true; + snprintf(ctl_sock_path, sizeof(ctl_sock_path), + "%s/" LDPD_SOCK_NAME, optarg); + break; + case 'n': + init.instance = atoi(optarg); + if (init.instance < 1) + exit(0); + break; + case 'L': + lflag = 1; + break; + case 'E': + eflag = 1; + break; + default: + frr_help_exit(1); + } + } + + if (!ctl_sock_used) + snprintf(ctl_sock_path, sizeof(ctl_sock_path), + "%s/" LDPD_SOCK_NAME, frr_runstatedir); + + strlcpy(init.user, ldpd_privs.user, sizeof(init.user)); + strlcpy(init.group, ldpd_privs.group, sizeof(init.group)); + strlcpy(init.ctl_sock_path, ctl_sock_path, sizeof(init.ctl_sock_path)); + strlcpy(init.zclient_serv_path, frr_zclientpath, + sizeof(init.zclient_serv_path)); + + argc -= optind; + if (argc > 0 || (lflag && eflag)) + frr_help_exit(1); + + /* check for root privileges */ + if (geteuid() != 0) { + errno = EPERM; + perror(ldpd_di.progname); + exit(1); + } + + if (lflag) + lde(); + else if (eflag) + ldpe(); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2ldpe) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, + pipe_parent2ldpe_sync) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_DGRAM, PF_UNSPEC, pipe_ldpe_log) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_parent2lde) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, + pipe_parent2lde_sync) == -1) + fatal("socketpair"); + + if (socketpair(AF_UNIX, SOCK_DGRAM, PF_UNSPEC, pipe_lde_log) == -1) + fatal("socketpair"); + + sock_set_nonblock(pipe_parent2ldpe[0]); + sock_set_cloexec(pipe_parent2ldpe[0]); + sock_set_nonblock(pipe_parent2ldpe[1]); + sock_set_cloexec(pipe_parent2ldpe[1]); + sock_set_nonblock(pipe_parent2ldpe_sync[0]); + sock_set_cloexec(pipe_parent2ldpe_sync[0]); + sock_set_cloexec(pipe_parent2ldpe_sync[1]); + sock_set_nonblock(pipe_ldpe_log[0]); + sock_set_cloexec(pipe_ldpe_log[0]); + sock_set_nonblock(pipe_ldpe_log[1]); + sock_set_cloexec(pipe_ldpe_log[1]); + + sock_set_nonblock(pipe_parent2lde[0]); + sock_set_cloexec(pipe_parent2lde[0]); + sock_set_nonblock(pipe_parent2lde[1]); + sock_set_cloexec(pipe_parent2lde[1]); + sock_set_nonblock(pipe_parent2lde_sync[0]); + sock_set_cloexec(pipe_parent2lde_sync[0]); + sock_set_cloexec(pipe_parent2lde_sync[1]); + sock_set_nonblock(pipe_lde_log[0]); + sock_set_cloexec(pipe_lde_log[0]); + sock_set_nonblock(pipe_lde_log[1]); + sock_set_cloexec(pipe_lde_log[1]); + + /* start children */ + lde_pid = start_child(PROC_LDE_ENGINE, saved_argv0, + pipe_parent2lde[1], pipe_parent2lde_sync[1], pipe_lde_log[1]); + ldpe_pid = start_child(PROC_LDP_ENGINE, saved_argv0, + pipe_parent2ldpe[1], pipe_parent2ldpe_sync[1], pipe_ldpe_log[1]); + + master = frr_init(); + /* The two child processes use the zlog_live backend to send their + * messages here, where the actual logging config is then applied. + * Look for zlog_live_open_fd() to find the other end of this. + */ + zlog_recirculate_subscribe(master, pipe_lde_log[0]); + zlog_recirculate_subscribe(master, pipe_ldpe_log[0]); + + libagentx_init(); + vrf_init(NULL, NULL, NULL, NULL); + access_list_init(); + ldp_vty_init(); + ldp_zebra_init(master); + + /* + * Create base configuration with sane defaults. All configuration + * requests (e.g. CLI) act on vty_conf and then call ldp_config_apply() + * to merge the changes into ldpd_conf, which contains the actual + * running configuration. + */ + ldpd_conf = config_new_empty(); + vty_conf = config_new_empty(); + QOBJ_REG(vty_conf, ldpd_conf); + + /* read configuration file and daemonize */ + frr_config_fork(); + + /* apply configuration */ + event_add_event(master, ldp_config_fork_apply, NULL, 0, NULL); + + /* setup pipes to children */ + if ((iev_ldpe = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_ldpe_sync = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_lde = calloc(1, sizeof(struct imsgev))) == NULL || + (iev_lde_sync = calloc(1, sizeof(struct imsgev))) == NULL) + fatal(NULL); + + imsg_init(&iev_ldpe->ibuf, pipe_parent2ldpe[0]); + iev_ldpe->handler_read = main_dispatch_ldpe; + event_add_read(master, iev_ldpe->handler_read, iev_ldpe, + iev_ldpe->ibuf.fd, &iev_ldpe->ev_read); + iev_ldpe->handler_write = ldp_write_handler; + + imsg_init(&iev_ldpe_sync->ibuf, pipe_parent2ldpe_sync[0]); + iev_ldpe_sync->handler_read = main_dispatch_ldpe; + event_add_read(master, iev_ldpe_sync->handler_read, iev_ldpe_sync, + iev_ldpe_sync->ibuf.fd, &iev_ldpe_sync->ev_read); + iev_ldpe_sync->handler_write = ldp_write_handler; + + imsg_init(&iev_lde->ibuf, pipe_parent2lde[0]); + iev_lde->handler_read = main_dispatch_lde; + event_add_read(master, iev_lde->handler_read, iev_lde, iev_lde->ibuf.fd, + &iev_lde->ev_read); + iev_lde->handler_write = ldp_write_handler; + + imsg_init(&iev_lde_sync->ibuf, pipe_parent2lde_sync[0]); + iev_lde_sync->handler_read = main_dispatch_lde; + event_add_read(master, iev_lde_sync->handler_read, iev_lde_sync, + iev_lde_sync->ibuf.fd, &iev_lde_sync->ev_read); + iev_lde_sync->handler_write = ldp_write_handler; + + if (main_imsg_send_ipc_sockets(&iev_ldpe->ibuf, &iev_lde->ibuf)) + fatal("could not establish imsg links"); + + main_imsg_compose_both(IMSG_DEBUG_UPDATE, &ldp_debug, sizeof(ldp_debug)); + main_imsg_compose_both(IMSG_INIT, &init, sizeof(init)); + main_imsg_send_config(ldpd_conf); + + if (CHECK_FLAG(ldpd_conf->ipv4.flags, F_LDPD_AF_ENABLED)) + main_imsg_send_net_sockets(AF_INET); + + if (CHECK_FLAG(ldpd_conf->ipv6.flags, F_LDPD_AF_ENABLED)) + main_imsg_send_net_sockets(AF_INET6); + + frr_run(master); + + /* NOTREACHED */ + return (0); +} + +static void +ldpd_shutdown(void) +{ + pid_t pid; + int status; + + frr_early_fini(); + + /* close pipes */ + msgbuf_clear(&iev_ldpe->ibuf.w); + close(iev_ldpe->ibuf.fd); + msgbuf_clear(&iev_lde->ibuf.w); + close(iev_lde->ibuf.fd); + + config_clear(ldpd_conf); + + ldp_config_reset(vty_conf); + QOBJ_UNREG(vty_conf); + free(vty_conf); + + log_debug("waiting for children to terminate"); + + while (true) { + /* Wait for child process. */ + pid = wait(&status); + if (pid == -1) { + /* We got interrupted, try again. */ + if (errno == EINTR) + continue; + /* No more processes were found. */ + if (errno == ECHILD) + break; + + /* Unhandled errno condition. */ + fatal("wait"); + /* UNREACHABLE */ + } + + /* We found something, lets announce it. */ + if (WIFSIGNALED(status)) + log_warnx("%s terminated; signal %d", + (pid == lde_pid ? "label decision engine" + : "ldp engine"), + WTERMSIG(status)); + + /* Repeat until there are no more child processes. */ + } + + free(iev_ldpe); + free(iev_lde); + + log_info("terminating"); + + vrf_terminate(); + access_list_reset(); + ldp_zebra_destroy(); + + frr_fini(); + exit(0); +} + +static pid_t +start_child(enum ldpd_process p, char *argv0, int fd_async, int fd_sync, + int fd_log) +{ + char *argv[7]; + int argc = 0, nullfd; + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + close(fd_async); + close(fd_sync); + close(fd_log); + return (pid); + } + + nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); + if (nullfd == -1) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: failed to open /dev/null: %s", __func__, + safe_strerror(errno)); + } else { + dup2(nullfd, 0); + dup2(nullfd, 1); + dup2(nullfd, 2); + close(nullfd); + } + + if (dup2(fd_async, LDPD_FD_ASYNC) == -1) + fatal("cannot setup imsg async fd"); + + if (dup2(fd_sync, LDPD_FD_SYNC) == -1) + fatal("cannot setup imsg sync fd"); + + if (dup2(fd_log, LDPD_FD_LOG) == -1) + fatal("cannot setup zlog fd"); + + argv[argc++] = argv0; + switch (p) { + case PROC_MAIN: + fatalx("Can not start main process"); + case PROC_LDE_ENGINE: + argv[argc++] = (char *)"-L"; + break; + case PROC_LDP_ENGINE: + argv[argc++] = (char *)"-E"; + break; + } + + argv[argc++] = (char *)"-u"; + argv[argc++] = (char *)ldpd_privs.user; + argv[argc++] = (char *)"-g"; + argv[argc++] = (char *)ldpd_privs.group; + argv[argc++] = NULL; + + execvp(argv0, argv); + fatal("execvp"); +} + +/* imsg handling */ +/* ARGSUSED */ +static void main_dispatch_ldpe(struct event *thread) +{ + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + int af; + ssize_t n; + int shut = 0; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_REQUEST_SOCKETS: + af = imsg.hdr.pid; + main_imsg_send_net_sockets(af); + break; + case IMSG_ACL_CHECK: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct acl_check)) + fatalx("IMSG_ACL_CHECK imsg with wrong len"); + ldp_acl_reply(iev, (struct acl_check *)imsg.data); + break; + case IMSG_LDP_SYNC_IF_STATE_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ldp_igp_sync_if_state)) + fatalx("IMSG_LDP_SYNC_IF_STATE_UPDATE imsg with wrong len"); + + ldp_sync_zebra_send_state_update((struct ldp_igp_sync_if_state *)imsg.data); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + ldpe_pid = 0; + + if (lde_pid == 0) + ldpd_shutdown(); + else + kill(lde_pid, SIGTERM); + } +} + +/* ARGSUSED */ +static void main_dispatch_lde(struct event *thread) +{ + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + ssize_t n; + int shut = 0; + struct zapi_rlfa_response *rlfa_labels; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("imsg_get"); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_KLABEL_CHANGE: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct kroute)) + fatalx("invalid size of IMSG_KLABEL_CHANGE"); + if (kr_change(imsg.data)) + log_warnx("%s: error changing route", __func__); + break; + case IMSG_KLABEL_DELETE: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct kroute)) + fatalx("invalid size of IMSG_KLABEL_DELETE"); + if (kr_delete(imsg.data)) + log_warnx("%s: error deleting route", __func__); + break; + case IMSG_KPW_ADD: + case IMSG_KPW_DELETE: + case IMSG_KPW_SET: + case IMSG_KPW_UNSET: + if (imsg.hdr.len - IMSG_HEADER_SIZE != + sizeof(struct zapi_pw)) + fatalx("invalid size of IMSG_KPWLABEL_CHANGE"); + + switch (imsg.hdr.type) { + case IMSG_KPW_ADD: + if (kmpw_add(imsg.data)) + log_warnx("%s: error adding pseudowire", __func__); + break; + case IMSG_KPW_DELETE: + if (kmpw_del(imsg.data)) + log_warnx("%s: error deleting pseudowire", __func__); + break; + case IMSG_KPW_SET: + if (kmpw_set(imsg.data)) + log_warnx("%s: error setting pseudowire", __func__); + break; + case IMSG_KPW_UNSET: + if (kmpw_unset(imsg.data)) + log_warnx("%s: error unsetting pseudowire", __func__); + break; + } + break; + case IMSG_ACL_CHECK: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct acl_check)) + fatalx("IMSG_ACL_CHECK imsg with wrong len"); + ldp_acl_reply(iev, (struct acl_check *)imsg.data); + break; + case IMSG_RLFA_LABELS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct zapi_rlfa_response)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + rlfa_labels = imsg.data; + ldp_zebra_send_rlfa_labels(rlfa_labels); + break; + default: + log_debug("%s: error handling imsg %d", __func__, + imsg.hdr.type); + break; + } + + imsg_free(&imsg); + } + + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + lde_pid = 0; + if (ldpe_pid == 0) + ldpd_shutdown(); + else + kill(ldpe_pid, SIGTERM); + } +} + +/* ARGSUSED */ +void ldp_write_handler(struct event *thread) +{ + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + ssize_t n; + + iev->ev_write = NULL; + + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) { + /* this pipe is dead, so remove the event handlers */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + return; + } + + imsg_event_add(iev); +} + +void +main_imsg_compose_ldpe(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_ldpe == NULL) + return; + + imsg_compose_event(iev_ldpe, type, 0, pid, -1, data, datalen); +} + +void +main_imsg_compose_lde(int type, pid_t pid, void *data, uint16_t datalen) +{ + imsg_compose_event(iev_lde, type, 0, pid, -1, data, datalen); +} + +int +main_imsg_compose_both(enum imsg_type type, void *buf, uint16_t len) +{ + if (iev_ldpe == NULL || iev_lde == NULL) + return (0); + + if (imsg_compose_event(iev_ldpe, type, 0, 0, -1, buf, len) == -1) + return (-1); + + if (imsg_compose_event(iev_lde, type, 0, 0, -1, buf, len) == -1) + return (-1); + + return (0); +} + +void +imsg_event_add(struct imsgev *iev) +{ + if (iev->handler_read) + event_add_read(master, iev->handler_read, iev, iev->ibuf.fd, + &iev->ev_read); + + if (iev->handler_write && iev->ibuf.w.queued) + event_add_write(master, iev->handler_write, iev, iev->ibuf.fd, + &iev->ev_write); +} + +int +imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, + pid_t pid, int fd, void *data, uint16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) != -1) + imsg_event_add(iev); + + return (ret); +} + +void +evbuf_enqueue(struct evbuf *eb, struct ibuf *buf) +{ + ibuf_close(&eb->wbuf, buf); + evbuf_event_add(eb); +} + +void +evbuf_event_add(struct evbuf *eb) +{ + if (eb->wbuf.queued) + event_add_write(master, eb->handler, eb->arg, eb->wbuf.fd, + &eb->ev); +} + +void evbuf_init(struct evbuf *eb, int fd, void (*handler)(struct event *), + void *arg) +{ + msgbuf_init(&eb->wbuf); + eb->wbuf.fd = fd; + eb->handler = handler; + eb->arg = arg; +} + +void +evbuf_clear(struct evbuf *eb) +{ + EVENT_OFF(eb->ev); + msgbuf_clear(&eb->wbuf); + eb->wbuf.fd = -1; +} + +static int +main_imsg_send_ipc_sockets(struct imsgbuf *ldpe_buf, struct imsgbuf *lde_buf) +{ + int pipe_ldpe2lde[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_ldpe2lde) == -1) + return (-1); + sock_set_nonblock(pipe_ldpe2lde[0]); + sock_set_nonblock(pipe_ldpe2lde[1]); + + if (imsg_compose(ldpe_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[0], + NULL, 0) == -1) + return (-1); + + if (imsg_compose(lde_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[1], + NULL, 0) == -1) + return (-1); + + return (0); +} + +static void +main_imsg_send_net_sockets(int af) +{ + if (!ldp_addrisset(af, &(ldp_af_conf_get(ldpd_conf, af))->trans_addr)) + return; + + main_imsg_send_net_socket(af, LDP_SOCKET_DISC); + main_imsg_send_net_socket(af, LDP_SOCKET_EDISC); + main_imsg_send_net_socket(af, LDP_SOCKET_SESSION); + imsg_compose_event(iev_ldpe, IMSG_SETUP_SOCKETS, af, 0, -1, NULL, 0); +} + +static void +main_imsg_send_net_socket(int af, enum socket_type type) +{ + int fd; + + fd = ldp_create_socket(af, type); + if (fd == -1) { + log_warnx("%s: failed to create %s socket for address-family %s", __func__, socket_name(type), af_name(af)); + return; + } + + imsg_compose_event(iev_ldpe, IMSG_SOCKET_NET, af, 0, fd, &type, + sizeof(type)); +} + +int +ldp_acl_request(struct imsgev *iev, char *acl_name, int af, + union ldpd_addr *addr, uint8_t prefixlen) +{ + struct imsg imsg; + struct acl_check acl_check; + + if (acl_name[0] == '\0') + return FILTER_PERMIT; + + /* build request */ + strlcpy(acl_check.acl, acl_name, sizeof(acl_check.acl)); + acl_check.af = af; + acl_check.addr = *addr; + acl_check.prefixlen = prefixlen; + + /* send (blocking) */ + imsg_compose_event(iev, IMSG_ACL_CHECK, 0, 0, -1, &acl_check, + sizeof(acl_check)); + imsg_flush(&iev->ibuf); + + /* receive (blocking) and parse result */ + if (imsg_read(&iev->ibuf) == -1) + fatal("imsg_read error"); + + if (imsg_get(&iev->ibuf, &imsg) == -1) + fatal("imsg_get"); + + if (imsg.hdr.type != IMSG_ACL_CHECK || + imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(int)) + fatalx("ldp_acl_request: invalid response"); + + return (*((int *)imsg.data)); +} + +void +ldp_acl_reply(struct imsgev *iev, struct acl_check *acl_check) +{ + struct access_list *alist; + struct prefix prefix; + int result; + + alist = access_list_lookup(family2afi(acl_check->af), acl_check->acl); + if (alist == NULL) + result = FILTER_DENY; + else { + prefix.family = acl_check->af; + switch (prefix.family) { + case AF_INET: + prefix.u.prefix4 = acl_check->addr.v4; + break; + case AF_INET6: + prefix.u.prefix6 = acl_check->addr.v6; + break; + default: + fatalx("ldp_acl_reply: unknown af"); + } + prefix.prefixlen = acl_check->prefixlen; + result = access_list_apply(alist, &prefix); + } + + imsg_compose_event(iev, IMSG_ACL_CHECK, 0, 0, -1, &result, + sizeof(result)); +} + +struct ldpd_af_conf * +ldp_af_conf_get(struct ldpd_conf *xconf, int af) +{ + switch (af) { + case AF_INET: + return (&xconf->ipv4); + case AF_INET6: + return (&xconf->ipv6); + default: + fatalx("ldp_af_conf_get: unknown af"); + } +} + +struct ldpd_af_global * +ldp_af_global_get(struct ldpd_global *xglobal, int af) +{ + switch (af) { + case AF_INET: + return (&xglobal->ipv4); + case AF_INET6: + return (&xglobal->ipv6); + default: + fatalx("ldp_af_global_get: unknown af"); + } +} + +int +ldp_is_dual_stack(struct ldpd_conf *xconf) +{ + return (CHECK_FLAG(xconf->ipv4.flags, F_LDPD_AF_ENABLED) + && CHECK_FLAG(xconf->ipv6.flags, F_LDPD_AF_ENABLED)); +} + +in_addr_t +ldp_rtr_id_get(struct ldpd_conf *xconf) +{ + if (xconf->rtr_id.s_addr != INADDR_ANY) + return (xconf->rtr_id.s_addr); + else + return (global.rtr_id.s_addr); +} + +static int +main_imsg_send_config(struct ldpd_conf *xconf) +{ + struct iface *iface; + struct tnbr *tnbr; + struct nbr_params *nbrp; + struct l2vpn *l2vpn; + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + if (main_imsg_compose_both(IMSG_RECONF_CONF, xconf, + sizeof(*xconf)) == -1) + return (-1); + + RB_FOREACH(iface, iface_head, &xconf->iface_tree) { + if (main_imsg_compose_both(IMSG_RECONF_IFACE, iface, + sizeof(*iface)) == -1) + return (-1); + } + + RB_FOREACH(tnbr, tnbr_head, &xconf->tnbr_tree) { + if (main_imsg_compose_both(IMSG_RECONF_TNBR, tnbr, + sizeof(*tnbr)) == -1) + return (-1); + } + + RB_FOREACH(nbrp, nbrp_head, &xconf->nbrp_tree) { + if (main_imsg_compose_both(IMSG_RECONF_NBRP, nbrp, + sizeof(*nbrp)) == -1) + return (-1); + } + + RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { + if (main_imsg_compose_both(IMSG_RECONF_L2VPN, l2vpn, + sizeof(*l2vpn)) == -1) + return (-1); + + RB_FOREACH(lif, l2vpn_if_head, &l2vpn->if_tree) { + if (main_imsg_compose_both(IMSG_RECONF_L2VPN_IF, lif, + sizeof(*lif)) == -1) + return (-1); + } + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_tree) { + if (main_imsg_compose_both(IMSG_RECONF_L2VPN_PW, pw, + sizeof(*pw)) == -1) + return (-1); + } + + RB_FOREACH(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree) { + if (main_imsg_compose_both(IMSG_RECONF_L2VPN_IPW, pw, + sizeof(*pw)) == -1) + return (-1); + } + } + + if (main_imsg_compose_both(IMSG_RECONF_END, NULL, 0) == -1) + return (-1); + + return (0); +} + +int +ldp_config_apply(struct vty *vty, struct ldpd_conf *xconf) +{ + /* + * When reading from a configuration file (startup and sighup), we + * call merge_config() only once after the whole config has been read. + * This is the optimal and least disruptive way to update the running + * configuration. + */ + if (vty && vty->type == VTY_FILE) + return (0); + + ldp_config_normalize(xconf); + + if (main_imsg_send_config(xconf) == -1) + return (-1); + + merge_config(ldpd_conf, xconf); + + return (0); +} + +static void +ldp_config_normalize(struct ldpd_conf *xconf) +{ + struct iface *iface, *itmp; + struct nbr_params *nbrp, *ntmp; + struct l2vpn *l2vpn; + struct l2vpn_pw *pw, *ptmp; + + if (!CHECK_FLAG(xconf->flags, F_LDPD_ENABLED)) + ldp_config_reset_main(xconf); + else { + if (!CHECK_FLAG(xconf->ipv4.flags, F_LDPD_AF_ENABLED)) + ldp_config_reset_af(xconf, AF_INET); + if (!CHECK_FLAG(xconf->ipv6.flags, F_LDPD_AF_ENABLED)) + ldp_config_reset_af(xconf, AF_INET6); + + RB_FOREACH_SAFE(iface, iface_head, &xconf->iface_tree, itmp) { + if (iface->ipv4.enabled || iface->ipv6.enabled) + continue; + + QOBJ_UNREG(iface); + RB_REMOVE(iface_head, &vty_conf->iface_tree, iface); + free(iface); + } + + RB_FOREACH_SAFE(nbrp, nbrp_head, &xconf->nbrp_tree, ntmp) { + if (CHECK_FLAG(nbrp->flags, (F_NBRP_KEEPALIVE|F_NBRP_GTSM))) + continue; + if (nbrp->auth.method != AUTH_NONE) + continue; + + QOBJ_UNREG(nbrp); + RB_REMOVE(nbrp_head, &vty_conf->nbrp_tree, nbrp); + free(nbrp); + } + } + + RB_FOREACH(l2vpn, l2vpn_head, &xconf->l2vpn_tree) { + RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_tree, ptmp) { + if (!CHECK_FLAG(pw->flags, F_PW_STATIC_NBR_ADDR)) { + pw->af = AF_INET; + pw->addr.v4 = pw->lsr_id; + } + + if (pw->lsr_id.s_addr != INADDR_ANY && pw->pwid != 0) + continue; + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + } + + RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree, + ptmp) { + if (!CHECK_FLAG(pw->flags, F_PW_STATIC_NBR_ADDR)) { + pw->af = AF_INET; + pw->addr.v4 = pw->lsr_id; + } + + if (pw->lsr_id.s_addr == INADDR_ANY || pw->pwid == 0) + continue; + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_tree, pw); + } + } +} + +static void +ldp_config_reset(struct ldpd_conf *conf) +{ + ldp_config_reset_main(conf); + ldp_config_reset_l2vpns(conf); +} + +static void +ldp_config_reset_main(struct ldpd_conf *conf) +{ + struct iface *iface; + struct nbr_params *nbrp; + + while (!RB_EMPTY(iface_head, &conf->iface_tree)) { + iface = RB_ROOT(iface_head, &conf->iface_tree); + + QOBJ_UNREG(iface); + RB_REMOVE(iface_head, &conf->iface_tree, iface); + free(iface); + } + + while (!RB_EMPTY(nbrp_head, &conf->nbrp_tree)) { + nbrp = RB_ROOT(nbrp_head, &conf->nbrp_tree); + + QOBJ_UNREG(nbrp); + RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); + free(nbrp); + } + + conf->rtr_id.s_addr = INADDR_ANY; + ldp_config_reset_af(conf, AF_INET); + ldp_config_reset_af(conf, AF_INET6); + conf->lhello_holdtime = LINK_DFLT_HOLDTIME; + conf->lhello_interval = DEFAULT_HELLO_INTERVAL; + conf->thello_holdtime = TARGETED_DFLT_HOLDTIME; + conf->thello_interval = DEFAULT_HELLO_INTERVAL; + conf->wait_for_sync_interval = DFLT_WAIT_FOR_SYNC; + conf->trans_pref = DUAL_STACK_LDPOV6; + conf->flags = 0; +} + +static void +ldp_config_reset_af(struct ldpd_conf *conf, int af) +{ + struct ldpd_af_conf *af_conf; + struct iface *iface; + struct iface_af *ia; + struct tnbr *tnbr, *ttmp; + + RB_FOREACH(iface, iface_head, &conf->iface_tree) { + ia = iface_af_get(iface, af); + ia->enabled = 0; + } + + RB_FOREACH_SAFE(tnbr, tnbr_head, &conf->tnbr_tree, ttmp) { + if (tnbr->af != af) + continue; + + QOBJ_UNREG(tnbr); + RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); + free(tnbr); + } + + af_conf = ldp_af_conf_get(conf, af); + af_conf->keepalive = 180; + af_conf->lhello_holdtime = 0; + af_conf->lhello_interval = 0; + af_conf->thello_holdtime = 0; + af_conf->thello_interval = 0; + memset(&af_conf->trans_addr, 0, sizeof(af_conf->trans_addr)); + af_conf->flags = 0; +} + +static void +ldp_config_reset_l2vpns(struct ldpd_conf *conf) +{ + struct l2vpn *l2vpn; + struct l2vpn_if *lif; + struct l2vpn_pw *pw; + + while (!RB_EMPTY(l2vpn_head, &conf->l2vpn_tree)) { + l2vpn = RB_ROOT(l2vpn_head, &conf->l2vpn_tree); + while (!RB_EMPTY(l2vpn_if_head, &l2vpn->if_tree)) { + lif = RB_ROOT(l2vpn_if_head, &l2vpn->if_tree); + + QOBJ_UNREG(lif); + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); + } + + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_tree); + + QOBJ_UNREG(pw); + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + free(pw); + } + + while (!RB_EMPTY(l2vpn_pw_head, &l2vpn->pw_inactive_tree)) { + pw = RB_ROOT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); + + QOBJ_UNREG(pw); + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + free(pw); + } + + QOBJ_UNREG(l2vpn); + RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); + free(l2vpn); + } +} + +void +ldp_clear_config(struct ldpd_conf *xconf) +{ + struct iface *iface; + struct tnbr *tnbr; + struct nbr_params *nbrp; + struct l2vpn *l2vpn; + + while (!RB_EMPTY(iface_head, &xconf->iface_tree)) { + iface = RB_ROOT(iface_head, &xconf->iface_tree); + + RB_REMOVE(iface_head, &xconf->iface_tree, iface); + free(iface); + } + + while (!RB_EMPTY(tnbr_head, &xconf->tnbr_tree)) { + tnbr = RB_ROOT(tnbr_head, &xconf->tnbr_tree); + + RB_REMOVE(tnbr_head, &xconf->tnbr_tree, tnbr); + free(tnbr); + } + + while (!RB_EMPTY(nbrp_head, &xconf->nbrp_tree)) { + nbrp = RB_ROOT(nbrp_head, &xconf->nbrp_tree); + + RB_REMOVE(nbrp_head, &xconf->nbrp_tree, nbrp); + free(nbrp); + } + + while (!RB_EMPTY(l2vpn_head, &xconf->l2vpn_tree)) { + l2vpn = RB_ROOT(l2vpn_head, &xconf->l2vpn_tree); + + RB_REMOVE(l2vpn_head, &xconf->l2vpn_tree, l2vpn); + l2vpn_del(l2vpn); + } + + free(xconf); +} + +#define COPY(a, b) do { \ + a = malloc(sizeof(*a)); \ + if (a == NULL) \ + fatal(__func__); \ + *a = *b; \ + } while (0) + +void +merge_config(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + merge_global(conf, xconf); + merge_af(AF_INET, &conf->ipv4, &xconf->ipv4); + merge_af(AF_INET6, &conf->ipv6, &xconf->ipv6); + merge_ifaces(conf, xconf); + merge_tnbrs(conf, xconf); + merge_nbrps(conf, xconf); + merge_l2vpns(conf, xconf); +} + +static void +merge_global(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + /* Removing global LDP config requires resetting LDP IGP Sync FSM */ + if (CHECK_FLAG(conf->flags, F_LDPD_ENABLED) + && (!CHECK_FLAG(xconf->flags, F_LDPD_ENABLED))) + { + if (ldpd_process == PROC_LDP_ENGINE) + ldp_sync_fsm_reset_all(); + } + + /* change of router-id requires resetting all neighborships */ + if (conf->rtr_id.s_addr != xconf->rtr_id.s_addr) { + if (ldpd_process == PROC_LDP_ENGINE) { + ldpe_reset_nbrs(AF_UNSPEC); + if (conf->rtr_id.s_addr == INADDR_ANY || + xconf->rtr_id.s_addr == INADDR_ANY) { + if_update_all(AF_UNSPEC); + tnbr_update_all(AF_UNSPEC); + } + } + conf->rtr_id = xconf->rtr_id; + } + + /* + * Configuration of ordered-control or independent-control + * requires resetting all neighborships. + */ + if (CHECK_FLAG(conf->flags, F_LDPD_ORDERED_CONTROL) != + CHECK_FLAG(xconf->flags, F_LDPD_ORDERED_CONTROL)) + ldpe_reset_nbrs(AF_UNSPEC); + + conf->lhello_holdtime = xconf->lhello_holdtime; + conf->lhello_interval = xconf->lhello_interval; + conf->thello_holdtime = xconf->thello_holdtime; + conf->thello_interval = xconf->thello_interval; + conf->wait_for_sync_interval = xconf->wait_for_sync_interval; + + if (conf->trans_pref != xconf->trans_pref) { + if (ldpd_process == PROC_LDP_ENGINE) + ldpe_reset_ds_nbrs(); + conf->trans_pref = xconf->trans_pref; + } + + if (CHECK_FLAG(conf->flags, F_LDPD_DS_CISCO_INTEROP) != + CHECK_FLAG(xconf->flags, F_LDPD_DS_CISCO_INTEROP)) { + if (ldpd_process == PROC_LDP_ENGINE) + ldpe_reset_ds_nbrs(); + } + + /* + * Configuration of allow-broken-lsp requires reprograming all + * labeled routes + */ + if (CHECK_FLAG(conf->flags, F_LDPD_ALLOW_BROKEN_LSP) != + CHECK_FLAG(xconf->flags, F_LDPD_ALLOW_BROKEN_LSP)) { + if (ldpd_process == PROC_LDE_ENGINE) + lde_allow_broken_lsp_update(xconf->flags); + } + + if (ldpd_process == PROC_LDP_ENGINE) + ldpe_set_config_change_time(); + + conf->flags = xconf->flags; +} + +static void +merge_af(int af, struct ldpd_af_conf *af_conf, struct ldpd_af_conf *xa) +{ + int stop_init_backoff = 0; + int remove_dynamic_tnbrs = 0; + int change_egress_label = 0; + int change_host_label = 0; + int reset_nbrs_ipv4 = 0; + int reset_nbrs = 0; + int update_sockets = 0; + int change_ldp_disabled = 0; + + /* update timers */ + if (af_conf->keepalive != xa->keepalive) { + af_conf->keepalive = xa->keepalive; + stop_init_backoff = 1; + } + + af_conf->lhello_holdtime = xa->lhello_holdtime; + af_conf->lhello_interval = xa->lhello_interval; + af_conf->thello_holdtime = xa->thello_holdtime; + af_conf->thello_interval = xa->thello_interval; + + /* update flags */ + if (CHECK_FLAG(af_conf->flags, F_LDPD_AF_THELLO_ACCEPT) && + !CHECK_FLAG(xa->flags, F_LDPD_AF_THELLO_ACCEPT)) + remove_dynamic_tnbrs = 1; + + if (CHECK_FLAG(af_conf->flags, F_LDPD_AF_NO_GTSM) != + CHECK_FLAG(xa->flags, F_LDPD_AF_NO_GTSM)) { + if (af == AF_INET6) + /* need to set/unset IPV6_MINHOPCOUNT */ + update_sockets = 1; + else + /* for LDPv4 just resetting the neighbors is enough */ + reset_nbrs_ipv4 = 1; + } + if (CHECK_FLAG(af_conf->flags, F_LDPD_AF_EXPNULL) != + CHECK_FLAG(xa->flags, F_LDPD_AF_EXPNULL)) + change_egress_label = 1; + + /* changing config of host only fec filtering */ + if (CHECK_FLAG(af_conf->flags, F_LDPD_AF_ALLOCHOSTONLY) + != CHECK_FLAG(xa->flags, F_LDPD_AF_ALLOCHOSTONLY)) + change_host_label = 1; + + /* disabling LDP for address family */ + if (CHECK_FLAG(af_conf->flags, F_LDPD_AF_ENABLED) && + !CHECK_FLAG(xa->flags, F_LDPD_AF_ENABLED)) + change_ldp_disabled = 1; + + af_conf->flags = xa->flags; + + /* update the transport address */ + if (ldp_addrcmp(af, &af_conf->trans_addr, &xa->trans_addr)) { + af_conf->trans_addr = xa->trans_addr; + update_sockets = 1; + } + + /* update ACLs */ + if (strcmp(af_conf->acl_label_allocate_for, xa->acl_label_allocate_for)) + change_host_label = 1; + + if (strcmp(af_conf->acl_label_advertise_to, xa->acl_label_advertise_to) || + strcmp(af_conf->acl_label_advertise_for, xa->acl_label_advertise_for) || + strcmp(af_conf->acl_label_accept_from, xa->acl_label_accept_from) || + strcmp(af_conf->acl_label_accept_for, xa->acl_label_accept_for)) + reset_nbrs = 1; + + if (strcmp(af_conf->acl_thello_accept_from, xa->acl_thello_accept_from)) + remove_dynamic_tnbrs = 1; + + if (strcmp(af_conf->acl_label_expnull_for, xa->acl_label_expnull_for)) + change_egress_label = 1; + + strlcpy(af_conf->acl_thello_accept_from, xa->acl_thello_accept_from, + sizeof(af_conf->acl_thello_accept_from)); + + strlcpy(af_conf->acl_label_allocate_for, xa->acl_label_allocate_for, + sizeof(af_conf->acl_label_allocate_for)); + + strlcpy(af_conf->acl_label_advertise_to, xa->acl_label_advertise_to, + sizeof(af_conf->acl_label_advertise_to)); + + strlcpy(af_conf->acl_label_advertise_for, xa->acl_label_advertise_for, + sizeof(af_conf->acl_label_advertise_for)); + + strlcpy(af_conf->acl_label_accept_from, xa->acl_label_accept_from, + sizeof(af_conf->acl_label_accept_from)); + + strlcpy(af_conf->acl_label_accept_for, xa->acl_label_accept_for, + sizeof(af_conf->acl_label_accept_for)); + + strlcpy(af_conf->acl_label_expnull_for, xa->acl_label_expnull_for, + sizeof(af_conf->acl_label_expnull_for)); + + /* apply the new configuration */ + switch (ldpd_process) { + case PROC_LDE_ENGINE: + if (change_egress_label) + lde_change_egress_label(af); + + if (change_host_label) + lde_change_allocate_filter(af); + + if (change_ldp_disabled) + lde_route_update_release_all(af); + + break; + case PROC_LDP_ENGINE: + if (stop_init_backoff) + ldpe_stop_init_backoff(af); + if (remove_dynamic_tnbrs) + ldpe_remove_dynamic_tnbrs(af); + if (reset_nbrs) + ldpe_reset_nbrs(AF_UNSPEC); + else if (reset_nbrs_ipv4) + ldpe_reset_nbrs(AF_INET); + break; + case PROC_MAIN: + if (update_sockets && iev_ldpe) + imsg_compose_event(iev_ldpe, IMSG_CLOSE_SOCKETS, af, + 0, -1, NULL, 0); + break; + } +} + +static void +merge_ifaces(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + struct iface *iface, *itmp, *xi; + + RB_FOREACH_SAFE(iface, iface_head, &conf->iface_tree, itmp) { + /* find deleted interfaces, which occurs when LDP is removed + * for all address families + */ + if (if_lookup_name(xconf, iface->name) == NULL) { + switch (ldpd_process) { + case PROC_LDP_ENGINE: + ldpe_if_exit(iface); + break; + case PROC_LDE_ENGINE: + if (iface->ipv4.enabled) + lde_route_update_release(iface, + AF_INET); + if (iface->ipv6.enabled) + lde_route_update_release(iface, + AF_INET6); + break; + case PROC_MAIN: + break; + } + RB_REMOVE(iface_head, &conf->iface_tree, iface); + free(iface); + } + } + RB_FOREACH_SAFE(xi, iface_head, &xconf->iface_tree, itmp) { + /* find new interfaces */ + if ((iface = if_lookup_name(conf, xi->name)) == NULL) { + COPY(iface, xi); + RB_INSERT(iface_head, &conf->iface_tree, iface); + + switch (ldpd_process) { + case PROC_LDP_ENGINE: + ldpe_if_init(iface); + break; + case PROC_LDE_ENGINE: + break; + case PROC_MAIN: + /* resend addresses to activate new interfaces */ + kif_redistribute(iface->name); + break; + } + continue; + } + + /* update labels when adding or removing ldp on an + * interface + */ + if (ldpd_process == PROC_LDE_ENGINE) { + /* if we are removing lpd config for an address + * family on an interface then advertise routes + * learned over this interface as if they were + * connected routes + */ + if (iface->ipv4.enabled && !xi->ipv4.enabled) + lde_route_update_release(iface, AF_INET); + + if (iface->ipv6.enabled && !xi->ipv6.enabled) + lde_route_update_release(iface, AF_INET6); + + /* if we are adding lpd config for an address + * family on an interface then add proper labels + */ + if (!iface->ipv4.enabled && xi->ipv4.enabled) + lde_route_update(iface, AF_INET); + + if (!iface->ipv6.enabled && xi->ipv6.enabled) + lde_route_update(iface, AF_INET6); + } + + /* update existing interfaces */ + merge_iface_af(&iface->ipv4, &xi->ipv4); + merge_iface_af(&iface->ipv6, &xi->ipv6); + } +} + +static void +merge_iface_af(struct iface_af *ia, struct iface_af *xi) +{ + if (ia->enabled != xi->enabled) { + ia->enabled = xi->enabled; + if (ldpd_process == PROC_LDP_ENGINE) + ldp_if_update(ia->iface, ia->af); + } + ia->hello_holdtime = xi->hello_holdtime; + ia->hello_interval = xi->hello_interval; +} + +static void +merge_tnbrs(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + struct tnbr *tnbr, *ttmp, *xt; + + RB_FOREACH_SAFE(tnbr, tnbr_head, &conf->tnbr_tree, ttmp) { + if (!CHECK_FLAG(tnbr->flags, F_TNBR_CONFIGURED)) + continue; + + /* find deleted tnbrs */ + if (tnbr_find(xconf, tnbr->af, &tnbr->addr) == NULL) { + switch (ldpd_process) { + case PROC_LDP_ENGINE: + UNSET_FLAG(tnbr->flags, F_TNBR_CONFIGURED); + tnbr_check(conf, tnbr); + break; + case PROC_LDE_ENGINE: + case PROC_MAIN: + RB_REMOVE(tnbr_head, &conf->tnbr_tree, tnbr); + free(tnbr); + break; + } + } + } + RB_FOREACH_SAFE(xt, tnbr_head, &xconf->tnbr_tree, ttmp) { + /* find new tnbrs */ + if ((tnbr = tnbr_find(conf, xt->af, &xt->addr)) == NULL) { + COPY(tnbr, xt); + RB_INSERT(tnbr_head, &conf->tnbr_tree, tnbr); + + switch (ldpd_process) { + case PROC_LDP_ENGINE: + tnbr_update(tnbr); + break; + case PROC_LDE_ENGINE: + case PROC_MAIN: + break; + } + continue; + } + + /* update existing tnbrs */ + if (!CHECK_FLAG(tnbr->flags, F_TNBR_CONFIGURED)) + SET_FLAG(tnbr->flags, F_TNBR_CONFIGURED); + } +} + +static void +merge_nbrps(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + struct nbr_params *nbrp, *ntmp, *xn; + struct nbr *nbr; + int nbrp_changed; + + RB_FOREACH_SAFE(nbrp, nbrp_head, &conf->nbrp_tree, ntmp) { + /* find deleted nbrps */ + if (nbr_params_find(xconf, nbrp->lsr_id) != NULL) + continue; + + switch (ldpd_process) { + case PROC_LDP_ENGINE: + nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); + if (nbr) { + session_shutdown(nbr, S_SHUTDOWN, 0, 0); +#ifdef __OpenBSD__ + pfkey_remove(nbr); +#else + sock_set_md5sig( + (ldp_af_global_get(&global, nbr->af)) + ->ldp_session_socket, + nbr->af, &nbr->raddr, NULL); +#endif + nbr->auth.method = AUTH_NONE; + if (nbr_session_active_role(nbr)) + nbr_establish_connection(nbr); + } + break; + case PROC_LDE_ENGINE: + case PROC_MAIN: + break; + } + RB_REMOVE(nbrp_head, &conf->nbrp_tree, nbrp); + free(nbrp); + } + + RB_FOREACH_SAFE(xn, nbrp_head, &xconf->nbrp_tree, ntmp) { + /* find new nbrps */ + if ((nbrp = nbr_params_find(conf, xn->lsr_id)) == NULL) { + COPY(nbrp, xn); + RB_INSERT(nbrp_head, &conf->nbrp_tree, nbrp); + + switch (ldpd_process) { + case PROC_LDP_ENGINE: + nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); + if (nbr) { + session_shutdown(nbr, S_SHUTDOWN, 0, 0); + nbr->auth.method = nbrp->auth.method; +#ifdef __OpenBSD__ + if (pfkey_establish(nbr, nbrp) == -1) + fatalx("pfkey setup failed"); +#else + sock_set_md5sig( + (ldp_af_global_get(&global, + nbr->af))->ldp_session_socket, + nbr->af, &nbr->raddr, + nbrp->auth.md5key); +#endif + if (nbr_session_active_role(nbr)) + nbr_establish_connection(nbr); + } + break; + case PROC_LDE_ENGINE: + case PROC_MAIN: + break; + } + continue; + } + + /* update existing nbrps */ + if (nbrp->flags != xn->flags || + nbrp->keepalive != xn->keepalive || + nbrp->gtsm_enabled != xn->gtsm_enabled || + nbrp->gtsm_hops != xn->gtsm_hops || + nbrp->auth.method != xn->auth.method || + strcmp(nbrp->auth.md5key, xn->auth.md5key) != 0) + nbrp_changed = 1; + else + nbrp_changed = 0; + + nbrp->keepalive = xn->keepalive; + nbrp->gtsm_enabled = xn->gtsm_enabled; + nbrp->gtsm_hops = xn->gtsm_hops; + nbrp->auth.method = xn->auth.method; + strlcpy(nbrp->auth.md5key, xn->auth.md5key, + sizeof(nbrp->auth.md5key)); + nbrp->auth.md5key_len = xn->auth.md5key_len; + nbrp->flags = xn->flags; + + if (ldpd_process == PROC_LDP_ENGINE) { + nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr); + if (nbr && nbrp_changed) { + session_shutdown(nbr, S_SHUTDOWN, 0, 0); +#ifdef __OpenBSD__ + pfkey_remove(nbr); + nbr->auth.method = nbrp->auth.method; + if (pfkey_establish(nbr, nbrp) == -1) + fatalx("pfkey setup failed"); +#else + nbr->auth.method = nbrp->auth.method; + sock_set_md5sig((ldp_af_global_get(&global, + nbr->af))->ldp_session_socket, nbr->af, + &nbr->raddr, nbrp->auth.md5key); +#endif + if (nbr_session_active_role(nbr)) + nbr_establish_connection(nbr); + } + } + } +} + +static void +merge_l2vpns(struct ldpd_conf *conf, struct ldpd_conf *xconf) +{ + struct l2vpn *l2vpn, *ltmp, *xl; + + RB_FOREACH_SAFE(l2vpn, l2vpn_head, &conf->l2vpn_tree, ltmp) { + /* find deleted l2vpns */ + if (l2vpn_find(xconf, l2vpn->name) == NULL) { + switch (ldpd_process) { + case PROC_LDE_ENGINE: + l2vpn_exit(l2vpn); + break; + case PROC_LDP_ENGINE: + ldpe_l2vpn_exit(l2vpn); + break; + case PROC_MAIN: + break; + } + RB_REMOVE(l2vpn_head, &conf->l2vpn_tree, l2vpn); + l2vpn_del(l2vpn); + } + } + RB_FOREACH_SAFE(xl, l2vpn_head, &xconf->l2vpn_tree, ltmp) { + /* find new l2vpns */ + if ((l2vpn = l2vpn_find(conf, xl->name)) == NULL) { + COPY(l2vpn, xl); + RB_INSERT(l2vpn_head, &conf->l2vpn_tree, l2vpn); + RB_INIT(l2vpn_if_head, &l2vpn->if_tree); + RB_INIT(l2vpn_pw_head, &l2vpn->pw_tree); + RB_INIT(l2vpn_pw_head, &l2vpn->pw_inactive_tree); + + switch (ldpd_process) { + case PROC_LDE_ENGINE: + l2vpn_init(l2vpn); + break; + case PROC_LDP_ENGINE: + ldpe_l2vpn_init(l2vpn); + break; + case PROC_MAIN: + break; + } + } + + /* update existing l2vpns */ + merge_l2vpn(conf, l2vpn, xl); + } +} + +static void +merge_l2vpn(struct ldpd_conf *xconf, struct l2vpn *l2vpn, struct l2vpn *xl) +{ + struct l2vpn_if *lif, *ftmp, *xf; + struct l2vpn_pw *pw, *ptmp, *xp; + struct nbr *nbr; + int reset_nbr, reinstall_pwfec, reinstall_tnbr; + int previous_pw_type, previous_mtu; + + previous_pw_type = l2vpn->pw_type; + previous_mtu = l2vpn->mtu; + + /* merge interfaces */ + RB_FOREACH_SAFE(lif, l2vpn_if_head, &l2vpn->if_tree, ftmp) { + /* find deleted interfaces */ + if (l2vpn_if_find(xl, lif->ifname) == NULL) { + RB_REMOVE(l2vpn_if_head, &l2vpn->if_tree, lif); + free(lif); + } + } + RB_FOREACH_SAFE(xf, l2vpn_if_head, &xl->if_tree, ftmp) { + /* find new interfaces */ + if (l2vpn_if_find(l2vpn, xf->ifname) == NULL) { + COPY(lif, xf); + RB_INSERT(l2vpn_if_head, &l2vpn->if_tree, lif); + lif->l2vpn = l2vpn; + + switch (ldpd_process) { + case PROC_LDP_ENGINE: + case PROC_LDE_ENGINE: + break; + case PROC_MAIN: + kif_redistribute(lif->ifname); + break; + } + } + } + + /* merge active pseudowires */ + RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_tree, ptmp) { + /* find deleted active pseudowires */ + if (l2vpn_pw_find_active(xl, pw->ifname) == NULL) { + switch (ldpd_process) { + case PROC_LDE_ENGINE: + l2vpn_pw_exit(pw); + break; + case PROC_LDP_ENGINE: + ldpe_l2vpn_pw_exit(pw); + break; + case PROC_MAIN: + break; + } + + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_tree, pw); + free(pw); + } + } + RB_FOREACH_SAFE(xp, l2vpn_pw_head, &xl->pw_tree, ptmp) { + /* find new active pseudowires */ + if ((pw = l2vpn_pw_find_active(l2vpn, xp->ifname)) == NULL) { + COPY(pw, xp); + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_tree, pw); + pw->l2vpn = l2vpn; + + switch (ldpd_process) { + case PROC_LDE_ENGINE: + l2vpn_pw_init(pw); + break; + case PROC_LDP_ENGINE: + ldpe_l2vpn_pw_init(pw); + break; + case PROC_MAIN: + kif_redistribute(pw->ifname); + break; + } + continue; + } + + /* update existing active pseudowire */ + if (pw->af != xp->af || + ldp_addrcmp(pw->af, &pw->addr, &xp->addr)) + reinstall_tnbr = 1; + else + reinstall_tnbr = 0; + + /* changes that require a session restart */ + if (CHECK_FLAG(pw->flags, (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF)) != + CHECK_FLAG(xp->flags, (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF))) + reset_nbr = 1; + else + reset_nbr = 0; + + if (l2vpn->pw_type != xl->pw_type || l2vpn->mtu != xl->mtu || + pw->pwid != xp->pwid || reinstall_tnbr || reset_nbr || + pw->lsr_id.s_addr != xp->lsr_id.s_addr) + reinstall_pwfec = 1; + else + reinstall_pwfec = 0; + + if (ldpd_process == PROC_LDP_ENGINE) { + if (reinstall_tnbr) + ldpe_l2vpn_pw_exit(pw); + if (reset_nbr) { + nbr = nbr_find_ldpid(pw->lsr_id.s_addr); + if (nbr && nbr->state == NBR_STA_OPER) + session_shutdown(nbr, S_SHUTDOWN, 0, 0); + } + } + if (ldpd_process == PROC_LDE_ENGINE && reinstall_pwfec) + l2vpn_pw_exit(pw); + pw->lsr_id = xp->lsr_id; + pw->af = xp->af; + pw->addr = xp->addr; + pw->pwid = xp->pwid; + strlcpy(pw->ifname, xp->ifname, sizeof(pw->ifname)); + pw->ifindex = xp->ifindex; + if (CHECK_FLAG(xp->flags, F_PW_CWORD_CONF)) + SET_FLAG(pw->flags, F_PW_CWORD_CONF); + else + UNSET_FLAG(pw->flags, F_PW_CWORD_CONF); + + if (CHECK_FLAG(xp->flags, F_PW_STATUSTLV_CONF)) + SET_FLAG(pw->flags, F_PW_STATUSTLV_CONF); + else + UNSET_FLAG(pw->flags, F_PW_STATUSTLV_CONF); + + if (CHECK_FLAG(xp->flags, F_PW_STATIC_NBR_ADDR)) + SET_FLAG(pw->flags, F_PW_STATIC_NBR_ADDR); + else + UNSET_FLAG(pw->flags, F_PW_STATIC_NBR_ADDR); + + if (ldpd_process == PROC_LDP_ENGINE && reinstall_tnbr) + ldpe_l2vpn_pw_init(pw); + + if (ldpd_process == PROC_LDE_ENGINE && reinstall_pwfec) { + l2vpn->pw_type = xl->pw_type; + l2vpn->mtu = xl->mtu; + l2vpn_pw_init(pw); + l2vpn->pw_type = previous_pw_type; + l2vpn->mtu = previous_mtu; + } + } + + /* merge inactive pseudowires */ + RB_FOREACH_SAFE(pw, l2vpn_pw_head, &l2vpn->pw_inactive_tree, ptmp) { + /* find deleted inactive pseudowires */ + if (l2vpn_pw_find_inactive(xl, pw->ifname) == NULL) { + RB_REMOVE(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + free(pw); + } + } + RB_FOREACH_SAFE(xp, l2vpn_pw_head, &xl->pw_inactive_tree, ptmp) { + /* find new inactive pseudowires */ + if ((pw = l2vpn_pw_find_inactive(l2vpn, xp->ifname)) == NULL) { + COPY(pw, xp); + RB_INSERT(l2vpn_pw_head, &l2vpn->pw_inactive_tree, pw); + pw->l2vpn = l2vpn; + + switch (ldpd_process) { + case PROC_LDE_ENGINE: + case PROC_LDP_ENGINE: + break; + case PROC_MAIN: + kif_redistribute(pw->ifname); + break; + } + continue; + } + + /* update existing inactive pseudowire */ + pw->lsr_id.s_addr = xp->lsr_id.s_addr; + pw->af = xp->af; + pw->addr = xp->addr; + pw->pwid = xp->pwid; + strlcpy(pw->ifname, xp->ifname, sizeof(pw->ifname)); + pw->ifindex = xp->ifindex; + pw->flags = xp->flags; + } + + l2vpn->pw_type = xl->pw_type; + l2vpn->mtu = xl->mtu; + strlcpy(l2vpn->br_ifname, xl->br_ifname, sizeof(l2vpn->br_ifname)); + l2vpn->br_ifindex = xl->br_ifindex; +} + +struct ldpd_conf * +config_new_empty(void) +{ + struct ldpd_conf *xconf; + + xconf = calloc(1, sizeof(*xconf)); + if (xconf == NULL) + fatal(NULL); + + RB_INIT(iface_head, &xconf->iface_tree); + RB_INIT(tnbr_head, &xconf->tnbr_tree); + RB_INIT(nbrp_head, &xconf->nbrp_tree); + RB_INIT(l2vpn_head, &xconf->l2vpn_tree); + + /* set default values */ + ldp_config_reset(xconf); + + return (xconf); +} + +void +config_clear(struct ldpd_conf *conf) +{ + struct ldpd_conf *xconf; + + /* + * Merge current config with an empty config, this will deactivate + * and deallocate all the interfaces, pseudowires and so on. Before + * merging, copy the router-id and other variables to avoid some + * unnecessary operations, like trying to reset the neighborships. + */ + xconf = config_new_empty(); + xconf->ipv4 = conf->ipv4; + xconf->ipv6 = conf->ipv6; + xconf->rtr_id = conf->rtr_id; + xconf->trans_pref = conf->trans_pref; + xconf->flags = conf->flags; + merge_config(conf, xconf); + free(xconf); + free(conf); +} diff --git a/ldpd/ldpd.h b/ldpd/ldpd.h new file mode 100644 index 0000000..ad831a6 --- /dev/null +++ b/ldpd/ldpd.h @@ -0,0 +1,911 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#ifndef _LDPD_H_ +#define _LDPD_H_ + +#include "queue.h" +#include "openbsd-tree.h" +#include "imsg.h" +#include "frrevent.h" +#include "qobj.h" +#include "prefix.h" +#include "filter.h" +#include "vty.h" +#include "pw.h" +#include "zclient.h" + +#include "ldp.h" +#include "lib/ldp_sync.h" + +#define CONF_FILE "/etc/ldpd.conf" +#define LDPD_USER "_ldpd" + +#define LDPD_FD_ASYNC 3 +#define LDPD_FD_SYNC 4 +#define LDPD_FD_LOG 5 + +#define LDPD_OPT_VERBOSE 0x00000001 +#define LDPD_OPT_VERBOSE2 0x00000002 +#define LDPD_OPT_NOACTION 0x00000004 + +#define TCP_MD5_KEY_LEN 80 + +#define RT_BUF_SIZE 16384 +#define MAX_RTSOCK_BUF 128 * 1024 +#define LDP_BACKLOG 128 + +#define F_LDPD_INSERTED 0x0001 +#define F_CONNECTED 0x0002 +#define F_STATIC 0x0004 +#define F_DYNAMIC 0x0008 +#define F_REJECT 0x0010 +#define F_BLACKHOLE 0x0020 +#define F_REDISTRIBUTED 0x0040 + +struct evbuf { + struct msgbuf wbuf; + struct event *ev; + void (*handler)(struct event *); + void *arg; +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler_write)(struct event *); + struct event *ev_write; + void (*handler_read)(struct event *); + struct event *ev_read; +}; + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_RELOAD, + IMSG_CTL_SHOW_INTERFACE, + IMSG_CTL_SHOW_DISCOVERY, + IMSG_CTL_SHOW_DISCOVERY_DTL, + IMSG_CTL_SHOW_DISC_IFACE, + IMSG_CTL_SHOW_DISC_TNBR, + IMSG_CTL_SHOW_DISC_ADJ, + IMSG_CTL_SHOW_NBR, + IMSG_CTL_SHOW_NBR_DISC, + IMSG_CTL_SHOW_NBR_END, + IMSG_CTL_SHOW_LIB, + IMSG_CTL_SHOW_LIB_BEGIN, + IMSG_CTL_SHOW_LIB_SENT, + IMSG_CTL_SHOW_LIB_RCVD, + IMSG_CTL_SHOW_LIB_END, + IMSG_CTL_SHOW_L2VPN_PW, + IMSG_CTL_SHOW_L2VPN_BINDING, + IMSG_CTL_SHOW_LDP_SYNC, + IMSG_CTL_CLEAR_NBR, + IMSG_CTL_FIB_COUPLE, + IMSG_CTL_FIB_DECOUPLE, + IMSG_CTL_KROUTE, + IMSG_CTL_KROUTE_ADDR, + IMSG_CTL_IFINFO, + IMSG_CTL_END, + IMSG_CTL_LOG_VERBOSE, + IMSG_KLABEL_CHANGE, + IMSG_KLABEL_DELETE, + IMSG_KPW_ADD, + IMSG_KPW_DELETE, + IMSG_KPW_SET, + IMSG_KPW_UNSET, + IMSG_IFSTATUS, + IMSG_NEWADDR, + IMSG_DELADDR, + IMSG_RTRID_UPDATE, + IMSG_LABEL_MAPPING, + IMSG_LABEL_MAPPING_FULL, + IMSG_LABEL_REQUEST, + IMSG_LABEL_RELEASE, + IMSG_LABEL_WITHDRAW, + IMSG_LABEL_ABORT, + IMSG_REQUEST_ADD, + IMSG_REQUEST_ADD_END, + IMSG_MAPPING_ADD, + IMSG_MAPPING_ADD_END, + IMSG_RELEASE_ADD, + IMSG_RELEASE_ADD_END, + IMSG_WITHDRAW_ADD, + IMSG_WITHDRAW_ADD_END, + IMSG_ADDRESS_ADD, + IMSG_ADDRESS_DEL, + IMSG_NOTIFICATION, + IMSG_NOTIFICATION_SEND, + IMSG_NEIGHBOR_UP, + IMSG_NEIGHBOR_DOWN, + IMSG_NETWORK_ADD, + IMSG_NETWORK_UPDATE, + IMSG_SOCKET_IPC, + IMSG_SOCKET_NET, + IMSG_CLOSE_SOCKETS, + IMSG_REQUEST_SOCKETS, + IMSG_SETUP_SOCKETS, + IMSG_RECONF_CONF, + IMSG_RECONF_IFACE, + IMSG_RECONF_TNBR, + IMSG_RECONF_NBRP, + IMSG_RECONF_L2VPN, + IMSG_RECONF_L2VPN_IF, + IMSG_RECONF_L2VPN_PW, + IMSG_RECONF_L2VPN_IPW, + IMSG_RECONF_END, + IMSG_DEBUG_UPDATE, + IMSG_ACL_CHECK, + IMSG_INIT, + IMSG_PW_UPDATE, + IMSG_FILTER_UPDATE, + IMSG_NBR_SHUTDOWN, + IMSG_LDP_SYNC_IF_STATE_REQUEST, + IMSG_LDP_SYNC_IF_STATE_UPDATE, + IMSG_RLFA_REG, + IMSG_RLFA_UNREG_ALL, + IMSG_RLFA_LABELS, + IMSG_AGENTX_ENABLED, +}; + +struct ldpd_init { + char user[256]; + char group[256]; + char ctl_sock_path[MAXPATHLEN]; + char zclient_serv_path[MAXPATHLEN]; + unsigned short instance; +}; + +struct ldp_access { + char name[ACL_NAMSIZ]; +}; + +union ldpd_addr { + struct in_addr v4; + struct in6_addr v6; +}; + +#define IN6_IS_SCOPE_EMBED(a) \ + ((IN6_IS_ADDR_LINKLOCAL(a)) || \ + (IN6_IS_ADDR_MC_LINKLOCAL(a)) || \ + (IN6_IS_ADDR_MC_INTFACELOCAL(a))) + +/* interface states */ +#define IF_STA_DOWN 0x01 +#define IF_STA_ACTIVE 0x02 + +/* targeted neighbor states */ +#define TNBR_STA_DOWN 0x01 +#define TNBR_STA_ACTIVE 0x02 + +/* interface types */ +enum iface_type { + IF_TYPE_POINTOPOINT, + IF_TYPE_BROADCAST +}; + +/* neighbor states */ +#define NBR_STA_PRESENT 0x0001 +#define NBR_STA_INITIAL 0x0002 +#define NBR_STA_OPENREC 0x0004 +#define NBR_STA_OPENSENT 0x0008 +#define NBR_STA_OPER 0x0010 +#define NBR_STA_SESSION (NBR_STA_INITIAL | NBR_STA_OPENREC | \ + NBR_STA_OPENSENT | NBR_STA_OPER) + +/* neighbor events */ +enum nbr_event { + NBR_EVT_NOTHING, + NBR_EVT_MATCH_ADJ, + NBR_EVT_CONNECT_UP, + NBR_EVT_CLOSE_SESSION, + NBR_EVT_INIT_RCVD, + NBR_EVT_KEEPALIVE_RCVD, + NBR_EVT_PDU_RCVD, + NBR_EVT_PDU_SENT, + NBR_EVT_INIT_SENT +}; + +/* neighbor actions */ +enum nbr_action { + NBR_ACT_NOTHING, + NBR_ACT_RST_KTIMEOUT, + NBR_ACT_SESSION_EST, + NBR_ACT_RST_KTIMER, + NBR_ACT_CONNECT_SETUP, + NBR_ACT_PASSIVE_INIT, + NBR_ACT_KEEPALIVE_SEND, + NBR_ACT_CLOSE_SESSION +}; + +/* LDP IGP Sync states */ +#define LDP_SYNC_STA_UNKNOWN 0x0000 +#define LDP_SYNC_STA_NOT_ACH 0x0001 +#define LDP_SYNC_STA_ACH 0x0002 + +/* LDP IGP Sync events */ +enum ldp_sync_event { + LDP_SYNC_EVT_NOTHING, + LDP_SYNC_EVT_LDP_SYNC_START, + LDP_SYNC_EVT_LDP_SYNC_COMPLETE, + LDP_SYNC_EVT_CONFIG_LDP_OFF, + LDP_SYNC_EVT_ADJ_DEL, + LDP_SYNC_EVT_ADJ_NEW, + LDP_SYNC_EVT_SESSION_CLOSE, + LDP_SYNC_EVT_CONFIG_LDP_ON, + LDP_SYNC_EVT_IFACE_SHUTDOWN +}; + +/* LDP IGP Sync actions */ +enum ldp_sync_action { + LDP_SYNC_ACT_NOTHING, + LDP_SYNC_ACT_IFACE_START_SYNC, + LDP_SYNC_ACT_LDP_START_SYNC, + LDP_SYNC_ACT_LDP_COMPLETE_SYNC, + LDP_SYNC_ACT_CONFIG_LDP_OFF, + LDP_SYNC_ACT_IFACE_SHUTDOWN +}; + +/* forward declarations */ +RB_HEAD(global_adj_head, adj); +RB_HEAD(nbr_adj_head, adj); +RB_HEAD(ia_adj_head, adj); + +struct map { + uint8_t type; + uint32_t msg_id; + union { + struct { + uint16_t af; + union ldpd_addr prefix; + uint8_t prefixlen; + } prefix; + struct { + uint16_t type; + uint32_t pwid; + uint32_t group_id; + uint16_t ifmtu; + } pwid; + struct { + uint8_t type; + union { + uint16_t prefix_af; + uint16_t pw_type; + } u; + } twcard; + } fec; + struct { + uint32_t status_code; + uint32_t msg_id; + uint16_t msg_type; + } st; + uint32_t label; + uint32_t requestid; + uint32_t pw_status; + uint8_t flags; +}; +#define F_MAP_REQ_ID 0x01 /* optional request message id present */ +#define F_MAP_STATUS 0x02 /* status */ +#define F_MAP_PW_CWORD 0x04 /* pseudowire control word */ +#define F_MAP_PW_ID 0x08 /* pseudowire connection id */ +#define F_MAP_PW_IFMTU 0x10 /* pseudowire interface parameter */ +#define F_MAP_PW_STATUS 0x20 /* pseudowire status */ + +struct notify_msg { + uint32_t status_code; + uint32_t msg_id; /* network byte order */ + uint16_t msg_type; /* network byte order */ + uint32_t pw_status; + struct map fec; + struct { + uint16_t type; + uint16_t length; + char *data; + } rtlvs; + uint8_t flags; +}; +#define F_NOTIF_PW_STATUS 0x01 /* pseudowire status tlv present */ +#define F_NOTIF_FEC 0x02 /* fec tlv present */ +#define F_NOTIF_RETURNED_TLVS 0x04 /* returned tlvs present */ + +struct if_addr { + LIST_ENTRY(if_addr) entry; + int af; + union ldpd_addr addr; + uint8_t prefixlen; + union ldpd_addr dstbrd; +}; +LIST_HEAD(if_addr_head, if_addr); + +struct iface_af { + struct iface *iface; + int af; + int enabled; + int state; + struct ia_adj_head adj_tree; + time_t uptime; + struct event *hello_timer; + uint16_t hello_holdtime; + uint16_t hello_interval; +}; + +struct iface_ldp_sync { + int state; + struct event *wait_for_sync_timer; +}; + +struct iface { + RB_ENTRY(iface) entry; + char name[IFNAMSIZ]; + ifindex_t ifindex; + struct if_addr_head addr_list; + struct in6_addr linklocal; + enum iface_type type; + int operative; + struct iface_af ipv4; + struct iface_af ipv6; + struct iface_ldp_sync ldp_sync; + QOBJ_FIELDS; +}; +RB_HEAD(iface_head, iface); +RB_PROTOTYPE(iface_head, iface, entry, iface_compare); +DECLARE_QOBJ_TYPE(iface); + +/* source of targeted hellos */ +struct tnbr { + RB_ENTRY(tnbr) entry; + struct event *hello_timer; + struct adj *adj; + int af; + union ldpd_addr addr; + int state; + uint16_t pw_count; + uint32_t rlfa_count; + uint8_t flags; + QOBJ_FIELDS; +}; +RB_HEAD(tnbr_head, tnbr); +RB_PROTOTYPE(tnbr_head, tnbr, entry, tnbr_compare); +DECLARE_QOBJ_TYPE(tnbr); +#define F_TNBR_CONFIGURED 0x01 +#define F_TNBR_DYNAMIC 0x02 + +enum auth_method { + AUTH_NONE, + AUTH_MD5SIG +}; + +/* neighbor specific parameters */ +struct nbr_params { + RB_ENTRY(nbr_params) entry; + struct in_addr lsr_id; + uint16_t keepalive; + int gtsm_enabled; + uint8_t gtsm_hops; + struct { + enum auth_method method; + char md5key[TCP_MD5_KEY_LEN]; + uint8_t md5key_len; + } auth; + uint8_t flags; + QOBJ_FIELDS; +}; +RB_HEAD(nbrp_head, nbr_params); +RB_PROTOTYPE(nbrp_head, nbr_params, entry, nbr_params_compare); +DECLARE_QOBJ_TYPE(nbr_params); +#define F_NBRP_KEEPALIVE 0x01 +#define F_NBRP_GTSM 0x02 +#define F_NBRP_GTSM_HOPS 0x04 + +struct ldp_stats { + uint32_t kalive_sent; + uint32_t kalive_rcvd; + uint32_t addr_sent; + uint32_t addr_rcvd; + uint32_t addrwdraw_sent; + uint32_t addrwdraw_rcvd; + uint32_t notif_sent; + uint32_t notif_rcvd; + uint32_t capability_sent; + uint32_t capability_rcvd; + uint32_t labelmap_sent; + uint32_t labelmap_rcvd; + uint32_t labelreq_sent; + uint32_t labelreq_rcvd; + uint32_t labelwdraw_sent; + uint32_t labelwdraw_rcvd; + uint32_t labelrel_sent; + uint32_t labelrel_rcvd; + uint32_t labelabreq_sent; + uint32_t labelabreq_rcvd; + uint32_t unknown_tlv; + uint32_t unknown_msg; + +}; + +struct ldp_entity_stats { + uint32_t session_attempts; + uint32_t session_rejects_hello; + uint32_t session_rejects_ad; + uint32_t session_rejects_max_pdu; + uint32_t session_rejects_lr; + uint32_t bad_ldp_id; + uint32_t bad_pdu_len; + uint32_t bad_msg_len; + uint32_t bad_tlv_len; + uint32_t malformed_tlv; + uint32_t keepalive_timer_exp; + uint32_t shutdown_rcv_notify; + uint32_t shutdown_send_notify; +}; + +struct l2vpn_if { + RB_ENTRY(l2vpn_if) entry; + struct l2vpn *l2vpn; + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + int operative; + uint8_t mac[ETH_ALEN]; + QOBJ_FIELDS; +}; +RB_HEAD(l2vpn_if_head, l2vpn_if); +RB_PROTOTYPE(l2vpn_if_head, l2vpn_if, entry, l2vpn_if_compare); +DECLARE_QOBJ_TYPE(l2vpn_if); + +struct l2vpn_pw { + RB_ENTRY(l2vpn_pw) entry; + struct l2vpn *l2vpn; + struct in_addr lsr_id; + int af; + union ldpd_addr addr; + uint32_t pwid; + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + bool enabled; + uint32_t remote_group; + uint16_t remote_mtu; + uint32_t local_status; + uint32_t remote_status; + uint8_t flags; + uint8_t reason; + QOBJ_FIELDS; +}; +RB_HEAD(l2vpn_pw_head, l2vpn_pw); +RB_PROTOTYPE(l2vpn_pw_head, l2vpn_pw, entry, l2vpn_pw_compare); +DECLARE_QOBJ_TYPE(l2vpn_pw); +#define F_PW_STATUSTLV_CONF 0x01 /* status tlv configured */ +#define F_PW_STATUSTLV 0x02 /* status tlv negotiated */ +#define F_PW_CWORD_CONF 0x04 /* control word configured */ +#define F_PW_CWORD 0x08 /* control word negotiated */ +#define F_PW_STATIC_NBR_ADDR 0x10 /* static neighbor address configured */ + +#define F_PW_NO_ERR 0x00 /* no error reported */ +#define F_PW_LOCAL_NOT_FWD 0x01 /* locally can't forward over PW */ +#define F_PW_REMOTE_NOT_FWD 0x02 /* remote end of PW reported fwd error*/ +#define F_PW_NO_REMOTE_LABEL 0x03 /* have not recvd label from peer */ +#define F_PW_MTU_MISMATCH 0x04 /* mtu mismatch between peers */ + +struct l2vpn { + RB_ENTRY(l2vpn) entry; + char name[L2VPN_NAME_LEN]; + int type; + int pw_type; + int mtu; + char br_ifname[IFNAMSIZ]; + ifindex_t br_ifindex; + struct l2vpn_if_head if_tree; + struct l2vpn_pw_head pw_tree; + struct l2vpn_pw_head pw_inactive_tree; + QOBJ_FIELDS; +}; +RB_HEAD(l2vpn_head, l2vpn); +RB_PROTOTYPE(l2vpn_head, l2vpn, entry, l2vpn_compare); +DECLARE_QOBJ_TYPE(l2vpn); +#define L2VPN_TYPE_VPWS 1 +#define L2VPN_TYPE_VPLS 2 + +/* ldp_conf */ +extern enum ldpd_process { + PROC_MAIN, + PROC_LDP_ENGINE, + PROC_LDE_ENGINE +} ldpd_process; + +static const char * const log_procnames[] = { + "parent", + "ldpe", + "lde" +}; + +enum socket_type { + LDP_SOCKET_DISC, + LDP_SOCKET_EDISC, + LDP_SOCKET_SESSION +}; + +enum hello_type { + HELLO_LINK, + HELLO_TARGETED +}; + +struct ldpd_af_conf { + uint16_t keepalive; + uint16_t lhello_holdtime; + uint16_t lhello_interval; + uint16_t thello_holdtime; + uint16_t thello_interval; + union ldpd_addr trans_addr; + char acl_thello_accept_from[ACL_NAMSIZ]; + char acl_label_allocate_for[ACL_NAMSIZ]; + char acl_label_advertise_to[ACL_NAMSIZ]; + char acl_label_advertise_for[ACL_NAMSIZ]; + char acl_label_expnull_for[ACL_NAMSIZ]; + char acl_label_accept_from[ACL_NAMSIZ]; + char acl_label_accept_for[ACL_NAMSIZ]; + int flags; +}; +#define F_LDPD_AF_ENABLED 0x0001 +#define F_LDPD_AF_THELLO_ACCEPT 0x0002 +#define F_LDPD_AF_EXPNULL 0x0004 +#define F_LDPD_AF_NO_GTSM 0x0008 +#define F_LDPD_AF_ALLOCHOSTONLY 0x0010 + +struct ldpd_conf { + struct in_addr rtr_id; + struct ldpd_af_conf ipv4; + struct ldpd_af_conf ipv6; + struct iface_head iface_tree; + struct tnbr_head tnbr_tree; + struct nbrp_head nbrp_tree; + struct l2vpn_head l2vpn_tree; + uint16_t lhello_holdtime; + uint16_t lhello_interval; + uint16_t thello_holdtime; + uint16_t thello_interval; + uint16_t trans_pref; + uint16_t wait_for_sync_interval; + int flags; + time_t config_change_time; + struct ldp_entity_stats stats; + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(ldpd_conf); +#define F_LDPD_NO_FIB_UPDATE 0x0001 +#define F_LDPD_DS_CISCO_INTEROP 0x0002 +#define F_LDPD_ENABLED 0x0004 +#define F_LDPD_ORDERED_CONTROL 0x0008 +#define F_LDPD_ALLOW_BROKEN_LSP 0x0010 + +struct ldpd_af_global { + struct event *disc_ev; + struct event *edisc_ev; + int ldp_disc_socket; + int ldp_edisc_socket; + int ldp_session_socket; +}; + +struct ldpd_global { + int cmd_opts; + struct in_addr rtr_id; + struct ldpd_af_global ipv4; + struct ldpd_af_global ipv6; + uint32_t conf_seqnum; + int pfkeysock; + struct if_addr_head addr_list; + struct global_adj_head adj_tree; + struct in_addr mcast_addr_v4; + struct in6_addr mcast_addr_v6; + TAILQ_HEAD(, pending_conn) pending_conns; +}; + +/* kroute */ +struct kroute { + int af; + union ldpd_addr prefix; + uint8_t prefixlen; + union ldpd_addr nexthop; + uint32_t local_label; + uint32_t remote_label; + ifindex_t ifindex; + uint8_t route_type; + uint8_t route_instance; + uint16_t flags; +}; + +struct kaddr { + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + int af; + union ldpd_addr addr; + uint8_t prefixlen; + union ldpd_addr dstbrd; +}; + +struct kif { + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + int flags; + int operative; + uint8_t mac[ETH_ALEN]; + int mtu; +}; + +struct acl_check { + char acl[ACL_NAMSIZ]; + int af; + union ldpd_addr addr; + uint8_t prefixlen; +}; + +/* control data structures */ +struct ctl_iface { + int af; + char name[IFNAMSIZ]; + ifindex_t ifindex; + int state; + enum iface_type type; + uint16_t hello_holdtime; + uint16_t hello_interval; + time_t uptime; + uint16_t adj_cnt; +}; + +struct ctl_disc_if { + char name[IFNAMSIZ]; + int active_v4; + int active_v6; + int no_adj; +}; + +struct ctl_disc_tnbr { + int af; + union ldpd_addr addr; + int no_adj; +}; + +struct ctl_adj { + int af; + struct in_addr id; + enum hello_type type; + char ifname[IFNAMSIZ]; + union ldpd_addr src_addr; + uint16_t holdtime; + uint16_t holdtime_remaining; + union ldpd_addr trans_addr; + int ds_tlv; +}; + +struct ctl_nbr { + int af; + struct in_addr id; + union ldpd_addr laddr; + in_port_t lport; + union ldpd_addr raddr; + in_port_t rport; + enum auth_method auth_method; + uint16_t holdtime; + time_t uptime; + int nbr_state; + struct ldp_stats stats; + int flags; + uint16_t max_pdu_len; + uint16_t hold_time_remaining; +}; + +struct ctl_rt { + int af; + union ldpd_addr prefix; + uint8_t prefixlen; + struct in_addr nexthop; /* lsr-id */ + uint32_t local_label; + uint32_t remote_label; + uint8_t flags; + uint8_t in_use; + int no_downstream; +}; + +struct ctl_pw { + uint16_t type; + char l2vpn_name[L2VPN_NAME_LEN]; + char ifname[IFNAMSIZ]; + uint32_t pwid; + struct in_addr lsr_id; + uint32_t local_label; + uint32_t local_gid; + uint16_t local_ifmtu; + uint8_t local_cword; + uint32_t remote_label; + uint32_t remote_gid; + uint16_t remote_ifmtu; + uint8_t remote_cword; + uint32_t status; + uint8_t reason; +}; + +struct ctl_ldp_sync { + char name[IFNAMSIZ]; + ifindex_t ifindex; + bool in_sync; + bool timer_running; + uint16_t wait_time; + uint16_t wait_time_remaining; + struct in_addr peer_ldp_id; +}; + +extern struct ldpd_conf *ldpd_conf, *vty_conf; +extern struct ldpd_global global; +extern struct ldpd_init init; + +/* parse.y */ +struct ldpd_conf *parse_config(char *); +int cmdline_symset(char *); + +/* kroute.c */ +void pw2zpw(struct l2vpn_pw *, struct zapi_pw *); +void kif_redistribute(const char *); +int kr_change(struct kroute *); +int kr_delete(struct kroute *); +int kmpw_add(struct zapi_pw *); +int kmpw_del(struct zapi_pw *); +int kmpw_set(struct zapi_pw *); +int kmpw_unset(struct zapi_pw *); + +/* util.c */ +uint8_t mask2prefixlen(in_addr_t); +uint8_t mask2prefixlen6(struct sockaddr_in6 *); +in_addr_t prefixlen2mask(uint8_t); +struct in6_addr *prefixlen2mask6(uint8_t); +void ldp_applymask(int, union ldpd_addr *, + const union ldpd_addr *, int); +int ldp_addrcmp(int, const union ldpd_addr *, + const union ldpd_addr *); +int ldp_addrisset(int, const union ldpd_addr *); +int ldp_prefixcmp(int, const union ldpd_addr *, + const union ldpd_addr *, uint8_t); +int bad_addr_v4(struct in_addr); +int bad_addr_v6(struct in6_addr *); +int bad_addr(int, union ldpd_addr *); +void embedscope(struct sockaddr_in6 *); +void recoverscope(struct sockaddr_in6 *); +void addscope(struct sockaddr_in6 *, uint32_t); +void clearscope(struct in6_addr *); +void addr2sa(int af, const union ldpd_addr *, uint16_t, + union sockunion *su); +void sa2addr(struct sockaddr *, int *, union ldpd_addr *, + in_port_t *); +socklen_t sockaddr_len(struct sockaddr *); + +/* ldpd.c */ +void ldp_write_handler(struct event *thread); +void main_imsg_compose_ldpe(int, pid_t, void *, uint16_t); +void main_imsg_compose_lde(int, pid_t, void *, uint16_t); +int main_imsg_compose_both(enum imsg_type, void *, + uint16_t); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, + pid_t, int, void *, uint16_t); +void evbuf_enqueue(struct evbuf *, struct ibuf *); +void evbuf_event_add(struct evbuf *); +void evbuf_init(struct evbuf *, int, void (*)(struct event *), void *); +void evbuf_clear(struct evbuf *); +int ldp_acl_request(struct imsgev *, char *, int, + union ldpd_addr *, uint8_t); +void ldp_acl_reply(struct imsgev *, struct acl_check *); +struct ldpd_af_conf *ldp_af_conf_get(struct ldpd_conf *, int); +struct ldpd_af_global *ldp_af_global_get(struct ldpd_global *, int); +int ldp_is_dual_stack(struct ldpd_conf *); +in_addr_t ldp_rtr_id_get(struct ldpd_conf *); +int ldp_config_apply(struct vty *, struct ldpd_conf *); +void ldp_clear_config(struct ldpd_conf *); +void merge_config(struct ldpd_conf *, struct ldpd_conf *); +struct ldpd_conf *config_new_empty(void); +void config_clear(struct ldpd_conf *); + +/* ldp_vty_conf.c */ +/* NOTE: the parameters' names should be preserved because of codegen */ +struct iface *iface_new_api(struct ldpd_conf *conf, + const char *name); +void iface_del_api(struct ldpd_conf *conf, + struct iface *iface); +struct tnbr *tnbr_new_api(struct ldpd_conf *conf, int af, + union ldpd_addr *addr); +void tnbr_del_api(struct ldpd_conf *conf, struct tnbr *tnbr); +struct nbr_params *nbrp_new_api(struct ldpd_conf *conf, + struct in_addr lsr_id); +void nbrp_del_api(struct ldpd_conf *conf, + struct nbr_params *nbrp); +struct l2vpn *l2vpn_new_api(struct ldpd_conf *conf, const char *name); +void l2vpn_del_api(struct ldpd_conf *conf, + struct l2vpn *l2vpn); +struct l2vpn_if *l2vpn_if_new_api(struct ldpd_conf *conf, + struct l2vpn *l2vpn, const char *ifname); +void l2vpn_if_del_api(struct l2vpn *l2vpn, + struct l2vpn_if *lif); +struct l2vpn_pw *l2vpn_pw_new_api(struct ldpd_conf *conf, + struct l2vpn *l2vpn, const char *ifname); +void l2vpn_pw_del_api(struct l2vpn *l2vpn, + struct l2vpn_pw *pw); + +/* socket.c */ +int ldp_create_socket(int, enum socket_type); +void sock_set_nonblock(int); +void sock_set_cloexec(int); +void sock_set_recvbuf(int); +int sock_set_reuse(int, int); +int sock_set_bindany(int, int); +int sock_set_md5sig(int, int, union ldpd_addr *, const char *); +int sock_set_ipv4_tos(int, int); +int sock_set_ipv4_pktinfo(int, int); +int sock_set_ipv4_recvdstaddr(int fd, ifindex_t ifindex); +int sock_set_ipv4_recvif(int, int); +int sock_set_ipv4_minttl(int, int); +int sock_set_ipv4_ucast_ttl(int fd, int); +int sock_set_ipv4_mcast_ttl(int, uint8_t); +int sock_set_ipv4_mcast(struct iface *); +int sock_set_ipv4_mcast_loop(int); +int sock_set_ipv6_dscp(int, int); +int sock_set_ipv6_pktinfo(int, int); +int sock_set_ipv6_minhopcount(int, int); +int sock_set_ipv6_ucast_hops(int, int); +int sock_set_ipv6_mcast_hops(int, int); +int sock_set_ipv6_mcast(struct iface *); +int sock_set_ipv6_mcast_loop(int); + +/* logmsg.h */ +struct in6_addr; +union ldpd_addr; +struct hello_source; +struct fec; + +const char *log_sockaddr(void *); +const char *log_in6addr(const struct in6_addr *); +const char *log_in6addr_scope(const struct in6_addr *addr, + ifindex_t ifidx); +const char *log_addr(int, const union ldpd_addr *); +char *log_label(uint32_t); +const char *log_time(time_t); +char *log_hello_src(const struct hello_source *); +const char *log_map(const struct map *); +const char *log_fec(const struct fec *); +const char *af_name(int); +const char *socket_name(int); +const char *nbr_state_name(int); +const char *if_state_name(int); +const char *if_type_name(enum iface_type); +const char *msg_name(uint16_t); +const char *status_code_name(uint32_t); +const char *pw_type_name(uint16_t); +const char *pw_error_code(uint8_t); + +/* quagga */ +extern struct event_loop *master; +extern char ctl_sock_path[MAXPATHLEN]; + +/* ldp_zebra.c */ +void ldp_zebra_init(struct event_loop *m); +void ldp_zebra_destroy(void); +int ldp_sync_zebra_send_state_update(struct ldp_igp_sync_if_state *); +int ldp_zebra_send_rlfa_labels(struct zapi_rlfa_response * + rlfa_labels); + +void ldp_zebra_regdereg_zebra_info(bool want_register); + +/* compatibility */ +#ifndef __OpenBSD__ +#define __IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f) +#define __IPV6_ADDR_SCOPE_INTFACELOCAL 0x01 +#define IN6_IS_ADDR_MC_INTFACELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_INTFACELOCAL)) +#endif + +DECLARE_HOOK(ldp_register_mib, (struct event_loop * tm), (tm)); + +extern void ldp_agentx_enabled(void); + +#endif /* _LDPD_H_ */ diff --git a/ldpd/ldpe.c b/ldpd/ldpe.c new file mode 100644 index 0000000..6e844c0 --- /dev/null +++ b/ldpd/ldpe.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2008 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "control.h" +#include "log.h" +#include "ldp_debug.h" +#include "rlfa.h" + +#include +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "libfrr.h" +#include "zlog_live.h" + +static void ldpe_shutdown(void); +static void ldpe_dispatch_main(struct event *thread); +static void ldpe_dispatch_lde(struct event *thread); +#ifdef __OpenBSD__ +static void ldpe_dispatch_pfkey(struct event *thread); +#endif +static void ldpe_setup_sockets(int, int, int, int); +static void ldpe_close_sockets(int); +static void ldpe_iface_af_ctl(struct ctl_conn *c, int af, ifindex_t ifidx); +static void ldpe_check_filter_af(int, struct ldpd_af_conf *, const char *); + +struct ldpd_conf *leconf; +#ifdef __OpenBSD__ +struct ldpd_sysdep sysdep; +#endif + +static struct imsgev iev_main_data; +static struct imsgev *iev_main, *iev_main_sync; +static struct imsgev *iev_lde; +#ifdef __OpenBSD__ +static struct event *pfkey_ev; +#endif + +/* ldpe privileges */ +static zebra_capabilities_t _caps_p [] = +{ + ZCAP_BIND, + ZCAP_NET_ADMIN +}; + +struct zebra_privs_t ldpe_privs = +{ +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + +/* SIGINT / SIGTERM handler. */ +static void +sigint(void) +{ + ldpe_shutdown(); +} + +static struct frr_signal_t ldpe_signals[] = +{ + { + .signal = SIGHUP, + /* ignore */ + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +char *pkt_ptr; /* packet buffer */ + +/* label distribution protocol engine */ +void +ldpe(void) +{ + static struct zlog_live_cfg child_log; + +#ifdef HAVE_SETPROCTITLE + setproctitle("ldp engine"); +#endif + ldpd_process = PROC_LDP_ENGINE; + log_procname = log_procnames[ldpd_process]; + + master = frr_init(); + zlog_live_open_fd(&child_log, LOG_DEBUG, LDPD_FD_LOG); + + /* no frr_config_fork() here, allow frr_pthread to create threads */ + frr_is_after_fork = true; + + /* setup signal handler */ + signal_init(master, array_size(ldpe_signals), ldpe_signals); + + /* setup pipes and event handlers to the parent process */ + if ((iev_main = calloc(1, sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_main->ibuf, LDPD_FD_ASYNC); + iev_main->handler_read = ldpe_dispatch_main; + event_add_read(master, iev_main->handler_read, iev_main, + iev_main->ibuf.fd, &iev_main->ev_read); + iev_main->handler_write = ldp_write_handler; + + memset(&iev_main_data, 0, sizeof(iev_main_data)); + iev_main_sync = &iev_main_data; + imsg_init(&iev_main_sync->ibuf, LDPD_FD_SYNC); + + /* create base configuration */ + leconf = config_new_empty(); + + struct event thread; + while (event_fetch(master, &thread)) + event_call(&thread); + + /* NOTREACHED */ + return; +} + +void +ldpe_init(struct ldpd_init *init) +{ +#ifdef __OpenBSD__ + /* This socket must be open before dropping privileges. */ + global.pfkeysock = pfkey_init(); + if (sysdep.no_pfkey == 0) { + event_add_read(master, ldpe_dispatch_pfkey, NULL, + global.pfkeysock, &pfkey_ev); + } +#endif + + /* drop privileges */ + ldpe_privs.user = init->user; + ldpe_privs.group = init->group; + zprivs_preinit(&ldpe_privs); + zprivs_init(&ldpe_privs); + + /* listen on ldpd control socket */ + strlcpy(ctl_sock_path, init->ctl_sock_path, sizeof(ctl_sock_path)); + if (control_init(ctl_sock_path) == -1) + fatalx("control socket setup failed"); + TAILQ_INIT(&ctl_conns); + control_listen(); + + LIST_INIT(&global.addr_list); + RB_INIT(global_adj_head, &global.adj_tree); + TAILQ_INIT(&global.pending_conns); + if (inet_pton(AF_INET, AllRouters_v4, &global.mcast_addr_v4) != 1) + fatal("inet_pton"); + if (inet_pton(AF_INET6, AllRouters_v6, &global.mcast_addr_v6) != 1) + fatal("inet_pton"); + + /* mark sockets as closed */ + global.ipv4.ldp_disc_socket = -1; + global.ipv4.ldp_edisc_socket = -1; + global.ipv4.ldp_session_socket = -1; + global.ipv6.ldp_disc_socket = -1; + global.ipv6.ldp_edisc_socket = -1; + global.ipv6.ldp_session_socket = -1; + + if ((pkt_ptr = calloc(1, IBUF_READ_SIZE)) == NULL) + fatal(__func__); + + accept_init(); +} + +static void +ldpe_shutdown(void) +{ + struct if_addr *if_addr; + struct adj *adj; + + /* close pipes */ + if (iev_lde) { + msgbuf_clear(&iev_lde->ibuf.w); + close(iev_lde->ibuf.fd); + iev_lde->ibuf.fd = -1; + } + msgbuf_clear(&iev_main->ibuf.w); + close(iev_main->ibuf.fd); + iev_main->ibuf.fd = -1; + msgbuf_clear(&iev_main_sync->ibuf.w); + close(iev_main_sync->ibuf.fd); + iev_main_sync->ibuf.fd = -1; + + control_cleanup(ctl_sock_path); + +#ifdef __OpenBSD__ + if (sysdep.no_pfkey == 0) { + EVENT_OFF(pfkey_ev); + close(global.pfkeysock); + } +#endif + ldpe_close_sockets(AF_INET); + ldpe_close_sockets(AF_INET6); + + /* remove addresses from global list */ + while ((if_addr = LIST_FIRST(&global.addr_list)) != NULL) { + LIST_REMOVE(if_addr, entry); + assert(if_addr != LIST_FIRST(&global.addr_list)); + free(if_addr); + } + while (!RB_EMPTY(global_adj_head, &global.adj_tree)) { + adj = RB_ROOT(global_adj_head, &global.adj_tree); + + adj_del(adj, S_SHUTDOWN); + } + + config_clear(leconf); + /* clean up */ + if (iev_lde) + free(iev_lde); + free(iev_main); + free(pkt_ptr); + + log_info("ldp engine exiting"); + + zlog_fini(); + + exit(0); +} + +/* imesg */ +int +ldpe_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_main->ibuf.fd == -1) + return (0); + return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen)); +} + +void +ldpe_imsg_compose_parent_sync(int type, pid_t pid, void *data, uint16_t datalen) +{ + if (iev_main_sync->ibuf.fd == -1) + return; + imsg_compose_event(iev_main_sync, type, 0, pid, -1, data, datalen); + imsg_flush(&iev_main_sync->ibuf); +} + +int +ldpe_imsg_compose_lde(int type, uint32_t peerid, pid_t pid, void *data, + uint16_t datalen) +{ + if (iev_lde->ibuf.fd == -1) + return (0); + return (imsg_compose_event(iev_lde, type, peerid, pid, -1, data, datalen)); +} + +/* ARGSUSED */ +static void ldpe_dispatch_main(struct event *thread) +{ + static struct ldpd_conf *nconf; + struct iface *niface; + struct tnbr *ntnbr; + struct nbr_params *nnbrp; + static struct l2vpn *l2vpn, *nl2vpn; + struct l2vpn_if *lif, *nlif; + struct l2vpn_pw *pw, *npw; + struct imsg imsg; + int fd; + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + struct iface *iface = NULL; + struct kif *kif; + int af; + enum socket_type *socket_type; + static int disc_socket = -1; + static int edisc_socket = -1; + static int session_socket = -1; + struct nbr *nbr; +#ifdef __OpenBSD__ + struct nbr_params *nbrp; +#endif + int n, shut = 0; + struct ldp_access *laccess; + struct ldp_igp_sync_if_state_req *ldp_sync_if_state_req; + struct ldp_rlfa_node *rnode, *rntmp; + struct ldp_rlfa_client *rclient; + struct zapi_rlfa_request *rlfa_req; + struct zapi_rlfa_igp *rlfa_igp; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("ldpe_dispatch_main: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_IFSTATUS: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kif)) + fatalx("IFSTATUS imsg with wrong len"); + kif = imsg.data; + + iface = if_lookup_name(leconf, kif->ifname); + if (iface) { + if_update_info(iface, kif); + ldp_if_update(iface, AF_UNSPEC); + break; + } + + RB_FOREACH(l2vpn, l2vpn_head, &leconf->l2vpn_tree) { + lif = l2vpn_if_find(l2vpn, kif->ifname); + if (lif) { + l2vpn_if_update_info(lif, kif); + l2vpn_if_update(lif); + break; + } + pw = l2vpn_pw_find(l2vpn, kif->ifname); + if (pw) { + l2vpn_pw_update_info(pw, kif); + break; + } + } + break; + case IMSG_NEWADDR: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kaddr)) + fatalx("NEWADDR imsg with wrong len"); + + if_addr_add(imsg.data); + break; + case IMSG_DELADDR: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct kaddr)) + fatalx("DELADDR imsg with wrong len"); + + if_addr_del(imsg.data); + break; + case IMSG_SOCKET_IPC: + if (iev_lde) { + log_warnx("%s: received unexpected imsg fd to lde", __func__); + break; + } + if ((fd = imsg.fd) == -1) { + log_warnx("%s: expected to receive imsg fd to lde but didn't receive any", __func__); + break; + } + + if ((iev_lde = malloc(sizeof(struct imsgev))) == NULL) + fatal(NULL); + imsg_init(&iev_lde->ibuf, fd); + iev_lde->handler_read = ldpe_dispatch_lde; + event_add_read(master, iev_lde->handler_read, iev_lde, + iev_lde->ibuf.fd, &iev_lde->ev_read); + iev_lde->handler_write = ldp_write_handler; + iev_lde->ev_write = NULL; + break; + case IMSG_INIT: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ldpd_init)) + fatalx("INIT imsg with wrong len"); + + memcpy(&init, imsg.data, sizeof(init)); + ldpe_init(&init); + break; + case IMSG_AGENTX_ENABLED: + ldp_agentx_enabled(); + break; + case IMSG_CLOSE_SOCKETS: + af = imsg.hdr.peerid; + + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->af != af) + continue; + session_shutdown(nbr, S_SHUTDOWN, 0, 0); +#ifdef __OpenBSD__ + pfkey_remove(nbr); +#endif + nbr->auth.method = AUTH_NONE; + } + ldpe_close_sockets(af); + if_update_all(af); + tnbr_update_all(af); + + disc_socket = -1; + edisc_socket = -1; + session_socket = -1; + if (CHECK_FLAG((ldp_af_conf_get(leconf, af))->flags, F_LDPD_AF_ENABLED)) + ldpe_imsg_compose_parent(IMSG_REQUEST_SOCKETS, af, NULL, 0); + break; + case IMSG_SOCKET_NET: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(enum socket_type)) + fatalx("SOCKET_NET imsg with wrong len"); + socket_type = imsg.data; + + switch (*socket_type) { + case LDP_SOCKET_DISC: + disc_socket = imsg.fd; + break; + case LDP_SOCKET_EDISC: + edisc_socket = imsg.fd; + break; + case LDP_SOCKET_SESSION: + session_socket = imsg.fd; + break; + } + break; + case IMSG_SETUP_SOCKETS: + af = imsg.hdr.peerid; + if (disc_socket == -1 || edisc_socket == -1 || + session_socket == -1) { + if (disc_socket != -1) + close(disc_socket); + if (edisc_socket != -1) + close(edisc_socket); + if (session_socket != -1) + close(session_socket); + break; + } + + ldpe_setup_sockets(af, disc_socket, edisc_socket, session_socket); + if_update_all(af); + tnbr_update_all(af); + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->af != af) + continue; + nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr; +#ifdef __OpenBSD__ + nbrp = nbr_params_find(leconf, nbr->id); + if (nbrp) { + nbr->auth.method = nbrp->auth.method; + if (pfkey_establish(nbr, nbrp) == -1) + fatalx("pfkey setup failed"); + } +#endif + if (nbr_session_active_role(nbr)) + nbr_establish_connection(nbr); + } + break; + case IMSG_RTRID_UPDATE: + memcpy(&global.rtr_id, imsg.data, sizeof(global.rtr_id)); + if (leconf->rtr_id.s_addr == INADDR_ANY) { + ldpe_reset_nbrs(AF_UNSPEC); + } + if_update_all(AF_UNSPEC); + tnbr_update_all(AF_UNSPEC); + break; + case IMSG_RECONF_CONF: + if ((nconf = malloc(sizeof(struct ldpd_conf))) == NULL) + fatal(NULL); + memcpy(nconf, imsg.data, sizeof(struct ldpd_conf)); + + RB_INIT(iface_head, &nconf->iface_tree); + RB_INIT(tnbr_head, &nconf->tnbr_tree); + RB_INIT(nbrp_head, &nconf->nbrp_tree); + RB_INIT(l2vpn_head, &nconf->l2vpn_tree); + break; + case IMSG_RECONF_IFACE: + if ((niface = malloc(sizeof(struct iface))) == NULL) + fatal(NULL); + memcpy(niface, imsg.data, sizeof(struct iface)); + + RB_INSERT(iface_head, &nconf->iface_tree, niface); + break; + case IMSG_RECONF_TNBR: + if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL) + fatal(NULL); + memcpy(ntnbr, imsg.data, sizeof(struct tnbr)); + + RB_INSERT(tnbr_head, &nconf->tnbr_tree, ntnbr); + break; + case IMSG_RECONF_NBRP: + if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL) + fatal(NULL); + memcpy(nnbrp, imsg.data, sizeof(struct nbr_params)); + + RB_INSERT(nbrp_head, &nconf->nbrp_tree, nnbrp); + break; + case IMSG_RECONF_L2VPN: + if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL) + fatal(NULL); + memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn)); + + RB_INIT(l2vpn_if_head, &nl2vpn->if_tree); + RB_INIT(l2vpn_pw_head, &nl2vpn->pw_tree); + RB_INIT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree); + + RB_INSERT(l2vpn_head, &nconf->l2vpn_tree, nl2vpn); + break; + case IMSG_RECONF_L2VPN_IF: + if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL) + fatal(NULL); + memcpy(nlif, imsg.data, sizeof(struct l2vpn_if)); + + RB_INSERT(l2vpn_if_head, &nl2vpn->if_tree, nlif); + break; + case IMSG_RECONF_L2VPN_PW: + if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) + fatal(NULL); + memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); + + RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_tree, npw); + break; + case IMSG_RECONF_L2VPN_IPW: + if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL) + fatal(NULL); + memcpy(npw, imsg.data, sizeof(struct l2vpn_pw)); + + RB_INSERT(l2vpn_pw_head, &nl2vpn->pw_inactive_tree, npw); + break; + case IMSG_RECONF_END: + merge_config(leconf, nconf); + ldp_clear_config(nconf); + nconf = NULL; + global.conf_seqnum++; + break; + case IMSG_CTL_END: + control_imsg_relay(&imsg); + break; + case IMSG_DEBUG_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(ldp_debug)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + memcpy(&ldp_debug, imsg.data, sizeof(ldp_debug)); + break; + case IMSG_FILTER_UPDATE: + if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(struct ldp_access)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + laccess = imsg.data; + ldpe_check_filter_af(AF_INET, &leconf->ipv4, laccess->name); + ldpe_check_filter_af(AF_INET6, &leconf->ipv6, laccess->name); + break; + case IMSG_LDP_SYNC_IF_STATE_REQUEST: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct ldp_igp_sync_if_state_req)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + ldp_sync_if_state_req = imsg.data; + ldp_sync_fsm_state_req(ldp_sync_if_state_req); + break; + case IMSG_RLFA_REG: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct zapi_rlfa_request)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + rlfa_req = imsg.data; + + rnode = rlfa_node_find(&rlfa_req->destination, + rlfa_req->pq_address); + if (!rnode) + rnode = rlfa_node_new(&rlfa_req->destination, + rlfa_req->pq_address); + rclient = rlfa_client_find(rnode, &rlfa_req->igp); + if (rclient) + /* RLFA already registered - do nothing */ + break; + rclient = rlfa_client_new(rnode, &rlfa_req->igp); + ldpe_rlfa_init(rclient); + break; + case IMSG_RLFA_UNREG_ALL: + if (imsg.hdr.len != IMSG_HEADER_SIZE + + sizeof(struct zapi_rlfa_igp)) { + log_warnx("%s: wrong imsg len", __func__); + break; + } + rlfa_igp = imsg.data; + + RB_FOREACH_SAFE (rnode, ldp_rlfa_node_head, + &rlfa_node_tree, rntmp) { + rclient = rlfa_client_find(rnode, rlfa_igp); + if (!rclient) + continue; + + ldpe_rlfa_exit(rclient); + rlfa_client_del(rclient); + } + break; + default: + log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + ldpe_shutdown(); + } +} + +/* ARGSUSED */ +static void ldpe_dispatch_lde(struct event *thread) +{ + struct imsgev *iev = EVENT_ARG(thread); + struct imsgbuf *ibuf = &iev->ibuf; + struct imsg imsg; + struct map *map; + struct notify_msg *nm; + struct nbr *nbr; + int n, shut = 0; + + iev->ev_read = NULL; + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) /* connection closed */ + shut = 1; + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("ldpe_dispatch_lde: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_MAPPING_ADD: + case IMSG_RELEASE_ADD: + case IMSG_REQUEST_ADD: + case IMSG_WITHDRAW_ADD: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct map)) + fatalx("invalid size of map request"); + map = imsg.data; + + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) + break; + if (nbr->state != NBR_STA_OPER) + break; + + switch (imsg.hdr.type) { + case IMSG_MAPPING_ADD: + mapping_list_add(&nbr->mapping_list, map); + break; + case IMSG_RELEASE_ADD: + mapping_list_add(&nbr->release_list, map); + break; + case IMSG_REQUEST_ADD: + mapping_list_add(&nbr->request_list, map); + break; + case IMSG_WITHDRAW_ADD: + mapping_list_add(&nbr->withdraw_list, map); + break; + } + break; + case IMSG_MAPPING_ADD_END: + case IMSG_RELEASE_ADD_END: + case IMSG_REQUEST_ADD_END: + case IMSG_WITHDRAW_ADD_END: + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) + break; + if (nbr->state != NBR_STA_OPER) + break; + + switch (imsg.hdr.type) { + case IMSG_MAPPING_ADD_END: + send_labelmessage(nbr, MSG_TYPE_LABELMAPPING, + &nbr->mapping_list); + break; + case IMSG_RELEASE_ADD_END: + send_labelmessage(nbr, MSG_TYPE_LABELRELEASE, + &nbr->release_list); + break; + case IMSG_REQUEST_ADD_END: + send_labelmessage(nbr, MSG_TYPE_LABELREQUEST, + &nbr->request_list); + break; + case IMSG_WITHDRAW_ADD_END: + send_labelmessage(nbr, MSG_TYPE_LABELWITHDRAW, + &nbr->withdraw_list); + break; + } + break; + case IMSG_NOTIFICATION_SEND: + if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(struct notify_msg)) + fatalx("invalid size of OE request"); + nm = imsg.data; + + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find neighbor", __func__); + break; + } + if (nbr->state != NBR_STA_OPER) + break; + + send_notification_full(nbr->tcp, nm); + break; + case IMSG_CTL_END: + case IMSG_CTL_SHOW_LIB_BEGIN: + case IMSG_CTL_SHOW_LIB_RCVD: + case IMSG_CTL_SHOW_LIB_SENT: + case IMSG_CTL_SHOW_LIB_END: + case IMSG_CTL_SHOW_L2VPN_PW: + case IMSG_CTL_SHOW_L2VPN_BINDING: + control_imsg_relay(&imsg); + break; + case IMSG_NBR_SHUTDOWN: + nbr = nbr_find_peerid(imsg.hdr.peerid); + if (nbr == NULL) { + log_debug("%s: cannot find neighbor", __func__); + break; + } + if (nbr->state != NBR_STA_OPER) + break; + session_shutdown(nbr,S_SHUTDOWN,0,0); + break; + default: + log_debug("%s: error handling imsg %d", __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handlers and exit */ + EVENT_OFF(iev->ev_read); + EVENT_OFF(iev->ev_write); + ldpe_shutdown(); + } +} + +#ifdef __OpenBSD__ +/* ARGSUSED */ +static void ldpe_dispatch_pfkey(struct event *thread) +{ + int fd = EVENT_FD(thread); + + event_add_read(master, ldpe_dispatch_pfkey, NULL, global.pfkeysock, + &pfkey_ev); + + if (pfkey_read(fd, NULL) == -1) + fatal("pfkey_read failed, exiting..."); +} +#endif /* __OpenBSD__ */ + +static void +ldpe_setup_sockets(int af, int disc_socket, int edisc_socket, + int session_socket) +{ + struct ldpd_af_global *af_global; + + af_global = ldp_af_global_get(&global, af); + + /* discovery socket */ + af_global->ldp_disc_socket = disc_socket; + event_add_read(master, disc_recv_packet, &af_global->disc_ev, + af_global->ldp_disc_socket, &af_global->disc_ev); + + /* extended discovery socket */ + af_global->ldp_edisc_socket = edisc_socket; + event_add_read(master, disc_recv_packet, &af_global->edisc_ev, + af_global->ldp_edisc_socket, &af_global->edisc_ev); + + /* session socket */ + af_global->ldp_session_socket = session_socket; + accept_add(af_global->ldp_session_socket, session_accept, NULL); +} + +static void +ldpe_close_sockets(int af) +{ + struct ldpd_af_global *af_global; + + af_global = ldp_af_global_get(&global, af); + + /* discovery socket */ + EVENT_OFF(af_global->disc_ev); + if (af_global->ldp_disc_socket != -1) { + close(af_global->ldp_disc_socket); + af_global->ldp_disc_socket = -1; + } + + /* extended discovery socket */ + EVENT_OFF(af_global->edisc_ev); + if (af_global->ldp_edisc_socket != -1) { + close(af_global->ldp_edisc_socket); + af_global->ldp_edisc_socket = -1; + } + + /* session socket */ + if (af_global->ldp_session_socket != -1) { + accept_del(af_global->ldp_session_socket); + close(af_global->ldp_session_socket); + af_global->ldp_session_socket = -1; + } +} + +int +ldpe_acl_check(char *acl_name, int af, union ldpd_addr *addr, uint8_t prefixlen) +{ + return ldp_acl_request(iev_main_sync, acl_name, af, addr, prefixlen); +} + +void +ldpe_reset_nbrs(int af) +{ + struct nbr *nbr; + + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (af == AF_UNSPEC || nbr->af == af) + session_shutdown(nbr, S_SHUTDOWN, 0, 0); + } +} + +void +ldpe_reset_ds_nbrs(void) +{ + struct nbr *nbr; + + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->ds_tlv) + session_shutdown(nbr, S_SHUTDOWN, 0, 0); + } +} + +void +ldpe_remove_dynamic_tnbrs(int af) +{ + struct tnbr *tnbr, *safe; + + RB_FOREACH_SAFE(tnbr, tnbr_head, &leconf->tnbr_tree, safe) { + if (tnbr->af != af) + continue; + + UNSET_FLAG(tnbr->flags, F_TNBR_DYNAMIC); + tnbr_check(leconf, tnbr); + } +} + +void +ldpe_stop_init_backoff(int af) +{ + struct nbr *nbr; + + RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) { + if (nbr->af == af && nbr_pending_idtimer(nbr)) { + nbr_stop_idtimer(nbr); + nbr_establish_connection(nbr); + } + } +} + +static void +ldpe_iface_af_ctl(struct ctl_conn *c, int af, ifindex_t idx) +{ + struct iface *iface; + struct iface_af *ia; + struct ctl_iface *ictl; + + RB_FOREACH(iface, iface_head, &leconf->iface_tree) { + if (idx == 0 || idx == iface->ifindex) { + ia = iface_af_get(iface, af); + if (!ia->enabled) + continue; + + ictl = if_to_ctl(ia); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_INTERFACE, + 0, 0, -1, ictl, sizeof(struct ctl_iface)); + } + } +} + +void +ldpe_iface_ctl(struct ctl_conn *c, ifindex_t idx) +{ + ldpe_iface_af_ctl(c, AF_INET, idx); + ldpe_iface_af_ctl(c, AF_INET6, idx); +} + +void +ldpe_adj_ctl(struct ctl_conn *c) +{ + struct adj *adj; + struct ctl_adj *actl; + + RB_FOREACH(adj, global_adj_head, &global.adj_tree) { + actl = adj_to_ctl(adj); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY, 0, 0, + -1, actl, sizeof(struct ctl_adj)); + } + + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +void +ldpe_adj_detail_ctl(struct ctl_conn *c) +{ + struct iface *iface; + struct tnbr *tnbr; + struct adj *adj; + struct ctl_adj *actl; + struct ctl_disc_if ictl; + struct ctl_disc_tnbr tctl; + + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY, 0, 0, -1, NULL, 0); + + RB_FOREACH(iface, iface_head, &leconf->iface_tree) { + memset(&ictl, 0, sizeof(ictl)); + ictl.active_v4 = (iface->ipv4.state == IF_STA_ACTIVE); + ictl.active_v6 = (iface->ipv6.state == IF_STA_ACTIVE); + + if (!ictl.active_v4 && !ictl.active_v6) + continue; + + strlcpy(ictl.name, iface->name, sizeof(ictl.name)); + if (RB_EMPTY(ia_adj_head, &iface->ipv4.adj_tree) && + RB_EMPTY(ia_adj_head, &iface->ipv6.adj_tree)) + ictl.no_adj = 1; + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_IFACE, 0, 0, + -1, &ictl, sizeof(ictl)); + + RB_FOREACH(adj, ia_adj_head, &iface->ipv4.adj_tree) { + actl = adj_to_ctl(adj); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, + 0, 0, -1, actl, sizeof(struct ctl_adj)); + } + RB_FOREACH(adj, ia_adj_head, &iface->ipv6.adj_tree) { + actl = adj_to_ctl(adj); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, + 0, 0, -1, actl, sizeof(struct ctl_adj)); + } + } + + RB_FOREACH(tnbr, tnbr_head, &leconf->tnbr_tree) { + memset(&tctl, 0, sizeof(tctl)); + tctl.af = tnbr->af; + tctl.addr = tnbr->addr; + if (tnbr->adj == NULL) + tctl.no_adj = 1; + + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_TNBR, 0, 0, + -1, &tctl, sizeof(tctl)); + + if (tnbr->adj == NULL) + continue; + + actl = adj_to_ctl(tnbr->adj); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISC_ADJ, 0, 0, + -1, actl, sizeof(struct ctl_adj)); + } + + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +void +ldpe_nbr_ctl(struct ctl_conn *c) +{ + struct adj *adj; + struct ctl_adj *actl; + struct nbr *nbr; + struct ctl_nbr *nctl; + + RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) { + if (nbr->state == NBR_STA_PRESENT) + continue; + + nctl = nbr_to_ctl(nbr); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR, 0, 0, -1, nctl, + sizeof(struct ctl_nbr)); + + RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) { + actl = adj_to_ctl(adj); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR_DISC, + 0, 0, -1, actl, sizeof(struct ctl_adj)); + } + + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR_END, 0, 0, -1, + NULL, 0); + } + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +void +ldpe_ldp_sync_ctl(struct ctl_conn *c) +{ + struct iface *iface; + struct ctl_ldp_sync *ictl; + + RB_FOREACH(iface, iface_head, &leconf->iface_tree) { + ictl = ldp_sync_to_ctl(iface); + imsg_compose_event(&c->iev, IMSG_CTL_SHOW_LDP_SYNC, + 0, 0, -1, ictl, sizeof(struct ctl_ldp_sync)); + } + imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0); +} + +void +mapping_list_add(struct mapping_head *mh, struct map *map) +{ + struct mapping_entry *me; + + me = calloc(1, sizeof(*me)); + if (me == NULL) + fatal(__func__); + me->map = *map; + + TAILQ_INSERT_TAIL(mh, me, entry); +} + +void +mapping_list_clr(struct mapping_head *mh) +{ + struct mapping_entry *me; + + while ((me = TAILQ_FIRST(mh)) != NULL) { + TAILQ_REMOVE(mh, me, entry); + assert(me != TAILQ_FIRST(mh)); + free(me); + } +} + +void +ldpe_check_filter_af(int af, struct ldpd_af_conf *af_conf, + const char *filter_name) +{ + if (strcmp(af_conf->acl_thello_accept_from, filter_name) == 0) + ldpe_remove_dynamic_tnbrs(af); +} + +void +ldpe_set_config_change_time(void) +{ + /* SNMP update time when ever there is a config change */ + leconf->config_change_time = time(NULL); +} diff --git a/ldpd/ldpe.h b/ldpd/ldpe.h new file mode 100644 index 0000000..f310ba5 --- /dev/null +++ b/ldpd/ldpe.h @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#ifndef _LDPE_H_ +#define _LDPE_H_ + +#include "queue.h" +#include "openbsd-tree.h" +#ifdef __OpenBSD__ +#include +#endif + +#include "ldpd.h" +#include "lib/ldp_sync.h" + +/* forward declarations */ +TAILQ_HEAD(mapping_head, mapping_entry); + +struct hello_source { + enum hello_type type; + struct { + struct iface_af *ia; + union ldpd_addr src_addr; + } link; + struct tnbr *target; +}; + +struct adj { + RB_ENTRY(adj) global_entry, nbr_entry, ia_entry; + struct in_addr lsr_id; + struct nbr *nbr; + int ds_tlv; + struct hello_source source; + struct event *inactivity_timer; + uint16_t holdtime; + union ldpd_addr trans_addr; +}; +RB_PROTOTYPE(global_adj_head, adj, global_entry, adj_compare) +RB_PROTOTYPE(nbr_adj_head, adj, nbr_entry, adj_compare) +RB_PROTOTYPE(ia_adj_head, adj, ia_entry, adj_compare) + +struct tcp_conn { + struct nbr *nbr; + int fd; + struct ibuf_read *rbuf; + struct evbuf wbuf; + struct event *rev; + in_port_t lport; + in_port_t rport; +}; + +struct nbr { + RB_ENTRY(nbr) id_tree, addr_tree, pid_tree; + struct tcp_conn *tcp; + struct nbr_adj_head adj_tree; /* adjacencies */ + struct event *ev_connect; + struct event *keepalive_timer; + struct event *keepalive_timeout; + struct event *init_timeout; + struct event *initdelay_timer; + + struct mapping_head mapping_list; + struct mapping_head withdraw_list; + struct mapping_head request_list; + struct mapping_head release_list; + struct mapping_head abortreq_list; + + uint32_t peerid; /* unique ID in DB */ + int af; + int ds_tlv; + int v4_enabled; /* announce/process v4 msgs */ + int v6_enabled; /* announce/process v6 msgs */ + struct in_addr id; /* lsr id */ + union ldpd_addr laddr; /* local address */ + union ldpd_addr raddr; /* remote address */ + ifindex_t raddr_scope; /* remote address scope (v6) */ + time_t uptime; + int fd; + int state; + uint32_t conf_seqnum; + int idtimer_cnt; + uint16_t keepalive; + uint16_t max_pdu_len; + struct ldp_stats stats; + + struct { + uint8_t established; + uint32_t spi_in; + uint32_t spi_out; + enum auth_method method; + char md5key[TCP_MD5_KEY_LEN]; + } auth; + int flags; +}; +#define F_NBR_GTSM_NEGOTIATED 0x01 +#define F_NBR_CAP_DYNAMIC 0x02 +#define F_NBR_CAP_TWCARD 0x04 +#define F_NBR_CAP_UNOTIF 0x08 + +RB_HEAD(nbr_id_head, nbr); +RB_PROTOTYPE(nbr_id_head, nbr, id_tree, nbr_id_compare) +RB_HEAD(nbr_addr_head, nbr); +RB_PROTOTYPE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare) +RB_HEAD(nbr_pid_head, nbr); +RB_PROTOTYPE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) + +struct pending_conn { + TAILQ_ENTRY(pending_conn) entry; + int fd; + int af; + union ldpd_addr addr; + struct event *ev_timeout; +}; +#define PENDING_CONN_TIMEOUT 5 + +struct mapping_entry { + TAILQ_ENTRY(mapping_entry) entry; + struct map map; +}; + +struct ldpd_sysdep { + uint8_t no_pfkey; + uint8_t no_md5sig; +}; + +extern struct ldpd_conf *leconf; +extern struct ldpd_sysdep sysdep; +extern struct nbr_id_head nbrs_by_id; +extern struct nbr_addr_head nbrs_by_addr; +extern struct nbr_pid_head nbrs_by_pid; + +/* accept.c */ +void accept_init(void); +int accept_add(int, void (*)(struct event *), void *); +void accept_del(int); +void accept_pause(void); +void accept_unpause(void); + +/* hello.c */ +int send_hello(enum hello_type, struct iface_af *, struct tnbr *); +void recv_hello(struct in_addr, struct ldp_msg *, int, union ldpd_addr *, + struct iface *, int, char *, uint16_t); + +/* init.c */ +void send_init(struct nbr *); +int recv_init(struct nbr *, char *, uint16_t); +void send_capability(struct nbr *, uint16_t, int); +int recv_capability(struct nbr *, char *, uint16_t); + +/* keepalive.c */ +void send_keepalive(struct nbr *); +int recv_keepalive(struct nbr *, char *, uint16_t); + +/* notification.c */ +void send_notification_full(struct tcp_conn *, struct notify_msg *); +void send_notification(struct tcp_conn *, uint32_t, uint32_t, uint16_t); +void send_notification_rtlvs(struct nbr *, uint32_t, uint32_t, uint16_t, + uint16_t, uint16_t, char *); +int recv_notification(struct nbr *, char *, uint16_t); +int gen_status_tlv(struct ibuf *, uint32_t, uint32_t, uint16_t); + +/* address.c */ +void send_address_single(struct nbr *, struct if_addr *, int); +void send_address_all(struct nbr *, int); +void send_mac_withdrawal(struct nbr *, struct map *, uint8_t *); +int recv_address(struct nbr *, char *, uint16_t); + +/* labelmapping.c */ +#define PREFIX_SIZE(x) (((x) + 7) / 8) +void send_labelmessage(struct nbr *, uint16_t, struct mapping_head *); +int recv_labelmessage(struct nbr *, char *, uint16_t, uint16_t); +int gen_pw_status_tlv(struct ibuf *, uint32_t); +uint16_t len_fec_tlv(struct map *); +int gen_fec_tlv(struct ibuf *, struct map *); +int tlv_decode_fec_elm(struct nbr *, struct ldp_msg *, char *, + uint16_t, struct map *); + +/* ldpe.c */ +void ldpe(void); +void ldpe_init(struct ldpd_init *); +int ldpe_imsg_compose_parent(int, pid_t, void *, + uint16_t); +void ldpe_imsg_compose_parent_sync(int, pid_t, void *, uint16_t); +int ldpe_imsg_compose_lde(int, uint32_t, pid_t, void *, + uint16_t); +int ldpe_acl_check(char *, int, union ldpd_addr *, uint8_t); +void ldpe_reset_nbrs(int); +void ldpe_reset_ds_nbrs(void); +void ldpe_remove_dynamic_tnbrs(int); +void ldpe_stop_init_backoff(int); +struct ctl_conn; +void ldpe_iface_ctl(struct ctl_conn *c, ifindex_t ifidx); +void ldpe_adj_ctl(struct ctl_conn *); +void ldpe_adj_detail_ctl(struct ctl_conn *); +void ldpe_nbr_ctl(struct ctl_conn *); +void ldpe_ldp_sync_ctl(struct ctl_conn *); +void mapping_list_add(struct mapping_head *, struct map *); +void mapping_list_clr(struct mapping_head *); +void ldpe_set_config_change_time(void); + +/* interface.c */ +struct iface *if_new(const char *); +void ldpe_if_init(struct iface *); +void ldpe_if_exit(struct iface *); +struct iface *if_lookup(struct ldpd_conf *c, ifindex_t ifidx); +struct iface *if_lookup_name(struct ldpd_conf *, const char *); +void if_update_info(struct iface *, struct kif *); +struct iface_af *iface_af_get(struct iface *, int); +void if_addr_add(struct kaddr *); +void if_addr_del(struct kaddr *); +void ldp_if_update(struct iface *, int); +void if_update_all(int); +uint16_t if_get_hello_holdtime(struct iface_af *); +uint16_t if_get_hello_interval(struct iface_af *); +uint16_t if_get_wait_for_sync_interval(void); +struct ctl_iface *if_to_ctl(struct iface_af *); +in_addr_t if_get_ipv4_addr(struct iface *); +int ldp_sync_fsm_adj_event(struct adj *, enum ldp_sync_event); +int ldp_sync_fsm_nbr_event(struct nbr *, enum ldp_sync_event); +int ldp_sync_fsm_state_req(struct ldp_igp_sync_if_state_req *); +int ldp_sync_fsm(struct iface *, enum ldp_sync_event); +void ldp_sync_fsm_reset_all(void); +const char *ldp_sync_state_name(int); +const char *ldp_sync_event_name(int); +struct ctl_ldp_sync *ldp_sync_to_ctl(struct iface *); + +/* adjacency.c */ +struct adj *adj_new(struct in_addr, struct hello_source *, + union ldpd_addr *); +void adj_del(struct adj *, uint32_t); +struct adj *adj_find(struct in_addr, struct hello_source *); +int adj_get_af(const struct adj *adj); +void adj_start_itimer(struct adj *); +void adj_stop_itimer(struct adj *); +struct tnbr *tnbr_new(int, union ldpd_addr *); +struct tnbr *tnbr_find(struct ldpd_conf *, int, union ldpd_addr *); +struct tnbr *tnbr_check(struct ldpd_conf *, struct tnbr *); +void tnbr_update(struct tnbr *); +void tnbr_update_all(int); +uint16_t tnbr_get_hello_holdtime(struct tnbr *); +uint16_t tnbr_get_hello_interval(struct tnbr *); +struct ctl_adj *adj_to_ctl(struct adj *); + +/* neighbor.c */ +int nbr_fsm(struct nbr *, enum nbr_event); +struct nbr *nbr_new(struct in_addr, int, int, union ldpd_addr *, + uint32_t); +void nbr_del(struct nbr *); +struct nbr *nbr_find_ldpid(uint32_t); +struct nbr *nbr_get_first_ldpid(void); +struct nbr *nbr_get_next_ldpid(uint32_t); +struct nbr *nbr_find_addr(int, union ldpd_addr *); +struct nbr *nbr_find_peerid(uint32_t); +int nbr_adj_count(struct nbr *, int); +int nbr_session_active_role(struct nbr *); +void nbr_stop_ktimer(struct nbr *); +void nbr_stop_ktimeout(struct nbr *); +void nbr_stop_itimeout(struct nbr *); +void nbr_start_idtimer(struct nbr *); +void nbr_stop_idtimer(struct nbr *); +int nbr_pending_idtimer(struct nbr *); +int nbr_pending_connect(struct nbr *); +int nbr_establish_connection(struct nbr *); +int nbr_gtsm_enabled(struct nbr *, struct nbr_params *); +int nbr_gtsm_setup(int, int, struct nbr_params *); +int nbr_gtsm_check(int, struct nbr *, struct nbr_params *); +struct nbr_params *nbr_params_new(struct in_addr); +struct nbr_params *nbr_params_find(struct ldpd_conf *, struct in_addr); +uint16_t nbr_get_keepalive(int, struct in_addr); +struct ctl_nbr *nbr_to_ctl(struct nbr *); +void nbr_clear_ctl(struct ctl_nbr *); + +/* packet.c */ +int gen_ldp_hdr(struct ibuf *, uint16_t); +int gen_msg_hdr(struct ibuf *, uint16_t, uint16_t); +int send_packet(int, int, union ldpd_addr *, + struct iface_af *, void *, size_t); +void disc_recv_packet(struct event *thread); +void session_accept(struct event *thread); +void session_accept_nbr(struct nbr *, int); +void session_shutdown(struct nbr *, uint32_t, uint32_t, + uint32_t); +void session_close(struct nbr *); +struct tcp_conn *tcp_new(int, struct nbr *); +void pending_conn_del(struct pending_conn *); +struct pending_conn *pending_conn_find(int, union ldpd_addr *); + +extern char *pkt_ptr; /* packet buffer */ + +/* pfkey.c */ +#ifdef __OpenBSD__ +int pfkey_read(int, struct sadb_msg *); +int pfkey_establish(struct nbr *, struct nbr_params *); +int pfkey_remove(struct nbr *); +int pfkey_init(void); +#endif + +/* l2vpn.c */ +void ldpe_l2vpn_init(struct l2vpn *); +void ldpe_l2vpn_exit(struct l2vpn *); +void ldpe_l2vpn_pw_init(struct l2vpn_pw *); +void ldpe_l2vpn_pw_exit(struct l2vpn_pw *); + +DECLARE_HOOK(ldp_nbr_state_change, (struct nbr * nbr, int old_state), + (nbr, old_state)); + +#endif /* _LDPE_H_ */ diff --git a/ldpd/log.h b/ldpd/log.h new file mode 100644 index 0000000..aa6f700 --- /dev/null +++ b/ldpd/log.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#ifndef LOG_H +#define LOG_H + +#include "log.h" +#include "assert.h" + +extern const char *log_procname; + +#define log_warnx zlog_err /* yes this is poorly named */ +#define log_warn zlog_warn +#define log_info zlog_info +#define log_notice zlog_notice /* not used anywhere */ +#define log_debug zlog_debug + +#define fatal(msg) \ + do { \ + assertf(0, "fatal in %s: %pSQq (%m)", log_procname, \ + (const char *)msg); \ + __builtin_unreachable(); \ + } while (0) \ + /* end */ +#define fatalx(msg) \ + do { \ + assertf(0, "fatal in %s: %pSQq", log_procname, \ + (const char *)msg); \ + __builtin_unreachable(); \ + } while (0) \ + /* end */ + +#endif /* LOG_H */ diff --git a/ldpd/logmsg.c b/ldpd/logmsg.c new file mode 100644 index 0000000..75f4293 --- /dev/null +++ b/ldpd/logmsg.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include +#include "lib/printfrr.h" + +#include "mpls.h" + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" + +#define NUM_LOGS 4 +const char * +log_sockaddr(void *vp) +{ + static char buf[NUM_LOGS][NI_MAXHOST]; + static int round = 0; + struct sockaddr *sa = vp; + + round = (round + 1) % NUM_LOGS; + + if (getnameinfo(sa, sockaddr_len(sa), buf[round], NI_MAXHOST, NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf[round]); +} + +const char * +log_in6addr(const struct in6_addr *addr) +{ + struct sockaddr_in6 sa_in6; + + memset(&sa_in6, 0, sizeof(sa_in6)); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_in6.sin6_len = sizeof(sa_in6); +#endif + sa_in6.sin6_family = AF_INET6; + sa_in6.sin6_addr = *addr; + + recoverscope(&sa_in6); + + return (log_sockaddr(&sa_in6)); +} + +const char * +log_in6addr_scope(const struct in6_addr *addr, ifindex_t ifindex) +{ + struct sockaddr_in6 sa_in6; + + memset(&sa_in6, 0, sizeof(sa_in6)); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_in6.sin6_len = sizeof(sa_in6); +#endif + sa_in6.sin6_family = AF_INET6; + sa_in6.sin6_addr = *addr; + + addscope(&sa_in6, ifindex); + + return (log_sockaddr(&sa_in6)); +} + +const char * +log_addr(int af, const union ldpd_addr *addr) +{ + static char buf[NUM_LOGS][INET6_ADDRSTRLEN]; + static int round = 0; + + switch (af) { + case AF_INET: + round = (round + 1) % NUM_LOGS; + if (inet_ntop(AF_INET, &addr->v4, buf[round], sizeof(buf[round])) == NULL) + return ("???"); + return (buf[round]); + case AF_INET6: + return (log_in6addr(&addr->v6)); + default: + break; + } + + return ("???"); +} + +#define TF_BUFS 4 +#define TF_LEN 32 + +char * +log_label(uint32_t label) +{ + char *buf; + static char tfbuf[TF_BUFS][TF_LEN]; /* ring buffer */ + static int idx = 0; + + buf = tfbuf[idx++]; + if (idx == TF_BUFS) + idx = 0; + + switch (label) { + case NO_LABEL: + snprintf(buf, TF_LEN, "-"); + break; + case MPLS_LABEL_IMPLICIT_NULL: + snprintf(buf, TF_LEN, "imp-null"); + break; + case MPLS_LABEL_IPV4_EXPLICIT_NULL: + case MPLS_LABEL_IPV6_EXPLICIT_NULL: + snprintf(buf, TF_LEN, "exp-null"); + break; + default: + snprintf(buf, TF_LEN, "%u", label); + break; + } + + return (buf); +} + +const char * +log_time(time_t t) +{ + char *buf; + static char tfbuf[TF_BUFS][TF_LEN]; /* ring buffer */ + static int idx = 0; + uint64_t sec, min, hrs, day, week; + + buf = tfbuf[idx++]; + if (idx == TF_BUFS) + idx = 0; + + week = t; + + sec = week % 60; + week /= 60; + min = week % 60; + week /= 60; + hrs = week % 24; + week /= 24; + day = week % 7; + week /= 7; + + if (week > 0) + snprintfrr(buf, TF_LEN, + "%02" PRIu64 "w%01" PRIu64 "d%02" PRIu64 "h", week, + day, hrs); + else if (day > 0) + snprintfrr(buf, TF_LEN, + "%01" PRIu64 "d%02" PRIu64 "h%02" PRIu64 "m", day, + hrs, min); + else + snprintfrr(buf, TF_LEN, + "%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64, hrs, min, + sec); + + return (buf); +} + +char * +log_hello_src(const struct hello_source *src) +{ + static char buf[64]; + + switch (src->type) { + case HELLO_LINK: + snprintf(buf, sizeof(buf), "iface %s", src->link.ia->iface->name); + break; + case HELLO_TARGETED: + snprintf(buf, sizeof(buf), "source %s", + log_addr(src->target->af, &src->target->addr)); + break; + } + + return (buf); +} + +const char * +log_map(const struct map *map) +{ + static char buf[128]; + + switch (map->type) { + case MAP_TYPE_WILDCARD: + if (snprintf(buf, sizeof(buf), "wildcard") < 0) + return ("???"); + break; + case MAP_TYPE_PREFIX: + if (snprintf(buf, sizeof(buf), "%s/%u", + log_addr(map->fec.prefix.af, &map->fec.prefix.prefix), + map->fec.prefix.prefixlen) == -1) + return ("???"); + break; + case MAP_TYPE_PWID: + if (snprintf(buf, sizeof(buf), "pw-id %u group-id %u (%s)", + map->fec.pwid.pwid, map->fec.pwid.group_id, + pw_type_name(map->fec.pwid.type)) == -1) + return ("???"); + break; + case MAP_TYPE_TYPED_WCARD: + if (snprintf(buf, sizeof(buf), "typed wildcard") < 0) + return ("???"); + switch (map->fec.twcard.type) { + case MAP_TYPE_PREFIX: + if (snprintf(buf + strlen(buf), sizeof(buf) - + strlen(buf), " (prefix, address-family %s)", + af_name(map->fec.twcard.u.prefix_af)) < 0) + return ("???"); + break; + case MAP_TYPE_PWID: + if (snprintf(buf + strlen(buf), sizeof(buf) - + strlen(buf), " (pwid, type %s)", + pw_type_name(map->fec.twcard.u.pw_type)) < 0) + return ("???"); + break; + default: + if (snprintf(buf + strlen(buf), sizeof(buf) - + strlen(buf), " (unknown type)") < 0) + return ("???"); + break; + } + break; + default: + return ("???"); + } + + return (buf); +} + +const char * +log_fec(const struct fec *fec) +{ + static char buf[64]; + union ldpd_addr addr; + + switch (fec->type) { + case FEC_TYPE_IPV4: + addr.v4 = fec->u.ipv4.prefix; + if (snprintf(buf, sizeof(buf), "ipv4 %s/%u", + log_addr(AF_INET, &addr), fec->u.ipv4.prefixlen) == -1) + return ("???"); + break; + case FEC_TYPE_IPV6: + addr.v6 = fec->u.ipv6.prefix; + if (snprintf(buf, sizeof(buf), "ipv6 %s/%u", + log_addr(AF_INET6, &addr), fec->u.ipv6.prefixlen) == -1) + return ("???"); + break; + case FEC_TYPE_PWID: + if (snprintfrr(buf, sizeof(buf), + "pwid %u (%s) - %pI4", + fec->u.pwid.pwid, pw_type_name(fec->u.pwid.type), + &fec->u.pwid.lsr_id) == -1) + return ("???"); + break; + default: + return ("???"); + } + + return (buf); +} + +/* names */ +const char * +af_name(int af) +{ + switch (af) { + case AF_INET: + return ("ipv4"); + case AF_INET6: + return ("ipv6"); +#ifdef AF_MPLS + case AF_MPLS: + return ("mpls"); +#endif + default: + return ("UNKNOWN"); + } +} + +const char * +socket_name(int type) +{ + switch (type) { + case LDP_SOCKET_DISC: + return ("discovery"); + case LDP_SOCKET_EDISC: + return ("extended discovery"); + case LDP_SOCKET_SESSION: + return ("session"); + default: + return ("UNKNOWN"); + } +} + +const char * +nbr_state_name(int state) +{ + switch (state) { + case NBR_STA_PRESENT: + return ("PRESENT"); + case NBR_STA_INITIAL: + return ("INITIALIZED"); + case NBR_STA_OPENREC: + return ("OPENREC"); + case NBR_STA_OPENSENT: + return ("OPENSENT"); + case NBR_STA_OPER: + return ("OPERATIONAL"); + default: + return ("UNKNOWN"); + } +} + +const char * +if_state_name(int state) +{ + switch (state) { + case IF_STA_DOWN: + return ("DOWN"); + case IF_STA_ACTIVE: + return ("ACTIVE"); + default: + return ("UNKNOWN"); + } +} + +const char * +if_type_name(enum iface_type type) +{ + switch (type) { + case IF_TYPE_POINTOPOINT: + return ("POINTOPOINT"); + case IF_TYPE_BROADCAST: + return ("BROADCAST"); + } + /* NOTREACHED */ + return ("UNKNOWN"); +} + +const char * +msg_name(uint16_t msg) +{ + static char buf[16]; + + switch (msg) { + case MSG_TYPE_NOTIFICATION: + return ("notification"); + case MSG_TYPE_HELLO: + return ("hello"); + case MSG_TYPE_INIT: + return ("initialization"); + case MSG_TYPE_KEEPALIVE: + return ("keepalive"); + case MSG_TYPE_CAPABILITY: + return ("capability"); + case MSG_TYPE_ADDR: + return ("address"); + case MSG_TYPE_ADDRWITHDRAW: + return ("address withdraw"); + case MSG_TYPE_LABELMAPPING: + return ("label mapping"); + case MSG_TYPE_LABELREQUEST: + return ("label request"); + case MSG_TYPE_LABELWITHDRAW: + return ("label withdraw"); + case MSG_TYPE_LABELRELEASE: + return ("label release"); + case MSG_TYPE_LABELABORTREQ: + default: + snprintf(buf, sizeof(buf), "[%08x]", msg); + return (buf); + } +} + +const char * +status_code_name(uint32_t status) +{ + static char buf[16]; + + switch (status) { + case S_SUCCESS: + return ("Success"); + case S_BAD_LDP_ID: + return ("Bad LDP Identifier"); + case S_BAD_PROTO_VER: + return ("Bad Protocol Version"); + case S_BAD_PDU_LEN: + return ("Bad PDU Length"); + case S_UNKNOWN_MSG: + return ("Unknown Message Type"); + case S_BAD_MSG_LEN: + return ("Bad Message Length"); + case S_UNKNOWN_TLV: + return ("Unknown TLV"); + case S_BAD_TLV_LEN: + return ("Bad TLV Length"); + case S_BAD_TLV_VAL: + return ("Malformed TLV Value"); + case S_HOLDTIME_EXP: + return ("Hold Timer Expired"); + case S_SHUTDOWN: + return ("Shutdown"); + case S_LOOP_DETECTED: + return ("Loop Detected"); + case S_UNKNOWN_FEC: + return ("Unknown FEC"); + case S_NO_ROUTE: + return ("No Route"); + case S_NO_LABEL_RES: + return ("No Label Resources"); + case S_AVAILABLE: + return ("Label Resources Available"); + case S_NO_HELLO: + return ("Session Rejected, No Hello"); + case S_PARM_ADV_MODE: + return ("Rejected Advertisement Mode Parameter"); + case S_MAX_PDU_LEN: + return ("Rejected Max PDU Length Parameter"); + case S_PARM_L_RANGE: + return ("Rejected Label Range Parameter"); + case S_KEEPALIVE_TMR: + return ("KeepAlive Timer Expired"); + case S_LAB_REQ_ABRT: + return ("Label Request Aborted"); + case S_MISS_MSG: + return ("Missing Message Parameters"); + case S_UNSUP_ADDR: + return ("Unsupported Address Family"); + case S_KEEPALIVE_BAD: + return ("Bad KeepAlive Time"); + case S_INTERN_ERR: + return ("Internal Error"); + case S_ILLEGAL_CBIT: + return ("Illegal C-Bit"); + case S_WRONG_CBIT: + return ("Wrong C-Bit"); + case S_INCPT_BITRATE: + return ("Incompatible bit-rate"); + case S_CEP_MISCONF: + return ("CEP-TDM mis-configuration"); + case S_PW_STATUS: + return ("PW Status"); + case S_UNASSIGN_TAI: + return ("Unassigned/Unrecognized TAI"); + case S_MISCONF_ERR: + return ("Generic Misconfiguration Error"); + case S_WITHDRAW_MTHD: + return ("Label Withdraw PW Status Method"); + case S_UNSSUPORTDCAP: + return ("Unsupported Capability"); + case S_ENDOFLIB: + return ("End-of-LIB"); + case S_TRANS_MISMTCH: + return ("Transport Connection Mismatch"); + case S_DS_NONCMPLNCE: + return ("Dual-Stack Noncompliance"); + default: + snprintf(buf, sizeof(buf), "[%08x]", status); + return (buf); + } +} + +const char * +pw_type_name(uint16_t pw_type) +{ + static char buf[64]; + + switch (pw_type) { + case PW_TYPE_ETHERNET_TAGGED: + return ("Eth Tagged"); + case PW_TYPE_ETHERNET: + return ("Ethernet"); + case PW_TYPE_WILDCARD: + return ("Wildcard"); + default: + snprintf(buf, sizeof(buf), "[%0x]", pw_type); + return (buf); + } +} + +const char * +pw_error_code(uint8_t status) +{ + static char buf[16]; + + switch (status) { + case F_PW_NO_ERR: + return ("No Error"); + case F_PW_LOCAL_NOT_FWD: + return ("local not forwarding"); + case F_PW_REMOTE_NOT_FWD: + return ("remote not forwarding"); + case F_PW_NO_REMOTE_LABEL: + return ("no remote label"); + case F_PW_MTU_MISMATCH: + return ("mtu mismatch between peers"); + default: + snprintf(buf, sizeof(buf), "[%0x]", status); + return (buf); + } +} diff --git a/ldpd/neighbor.c b/ldpd/neighbor.c new file mode 100644 index 0000000..2596c79 --- /dev/null +++ b/ldpd/neighbor.c @@ -0,0 +1,858 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "lde.h" +#include "log.h" + +DEFINE_HOOK(ldp_nbr_state_change, (struct nbr * nbr, int old_state), + (nbr, old_state)); + +static __inline int nbr_id_compare(const struct nbr *, const struct nbr *); +static __inline int nbr_addr_compare(const struct nbr *, const struct nbr *); +static __inline int nbr_pid_compare(const struct nbr *, const struct nbr *); +static void nbr_update_peerid(struct nbr *); +static void nbr_ktimer(struct event *thread); +static void nbr_start_ktimer(struct nbr *); +static void nbr_ktimeout(struct event *thread); +static void nbr_start_ktimeout(struct nbr *); +static void nbr_itimeout(struct event *thread); +static void nbr_start_itimeout(struct nbr *); +static void nbr_idtimer(struct event *thread); +static int nbr_act_session_operational(struct nbr *); +static void nbr_send_labelmappings(struct nbr *); +static __inline int nbr_params_compare(const struct nbr_params *, + const struct nbr_params *); + +RB_GENERATE(nbr_id_head, nbr, id_tree, nbr_id_compare) +RB_GENERATE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare) +RB_GENERATE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare) +RB_GENERATE(nbrp_head, nbr_params, entry, nbr_params_compare) + +const struct { + int state; + enum nbr_event event; + enum nbr_action action; + int new_state; +} nbr_fsm_tbl[] = { + /* current state event that happened action to take resulting state */ +/* Passive Role */ + {NBR_STA_PRESENT, NBR_EVT_MATCH_ADJ, NBR_ACT_NOTHING, NBR_STA_INITIAL}, + {NBR_STA_INITIAL, NBR_EVT_INIT_RCVD, NBR_ACT_PASSIVE_INIT, NBR_STA_OPENREC}, + {NBR_STA_OPENREC, NBR_EVT_KEEPALIVE_RCVD, NBR_ACT_SESSION_EST, NBR_STA_OPER}, +/* Active Role */ + {NBR_STA_PRESENT, NBR_EVT_CONNECT_UP, NBR_ACT_CONNECT_SETUP, NBR_STA_INITIAL}, + {NBR_STA_INITIAL, NBR_EVT_INIT_SENT, NBR_ACT_NOTHING, NBR_STA_OPENSENT}, + {NBR_STA_OPENSENT, NBR_EVT_INIT_RCVD, NBR_ACT_KEEPALIVE_SEND, NBR_STA_OPENREC}, +/* Session Maintenance */ + {NBR_STA_OPER, NBR_EVT_PDU_RCVD, NBR_ACT_RST_KTIMEOUT, 0}, + {NBR_STA_SESSION, NBR_EVT_PDU_RCVD, NBR_ACT_NOTHING, 0}, + {NBR_STA_OPER, NBR_EVT_PDU_SENT, NBR_ACT_RST_KTIMER, 0}, + {NBR_STA_SESSION, NBR_EVT_PDU_SENT, NBR_ACT_NOTHING, 0}, +/* Session Close */ + {NBR_STA_PRESENT, NBR_EVT_CLOSE_SESSION, NBR_ACT_NOTHING, 0}, + {NBR_STA_SESSION, NBR_EVT_CLOSE_SESSION, NBR_ACT_CLOSE_SESSION, NBR_STA_PRESENT}, + {-1, NBR_EVT_NOTHING, NBR_ACT_NOTHING, 0}, +}; + +const char * const nbr_event_names[] = { + "NOTHING", + "ADJACENCY MATCHED", + "CONNECTION UP", + "SESSION CLOSE", + "INIT RECEIVED", + "KEEPALIVE RECEIVED", + "PDU RECEIVED", + "PDU SENT", + "INIT SENT" +}; + +const char * const nbr_action_names[] = { + "NOTHING", + "RESET KEEPALIVE TIMEOUT", + "START NEIGHBOR SESSION", + "RESET KEEPALIVE TIMER", + "SETUP NEIGHBOR CONNECTION", + "SEND INIT AND KEEPALIVE", + "SEND KEEPALIVE", + "CLOSE SESSION" +}; + +struct nbr_id_head nbrs_by_id = RB_INITIALIZER(&nbrs_by_id); +struct nbr_addr_head nbrs_by_addr = RB_INITIALIZER(&nbrs_by_addr); +struct nbr_pid_head nbrs_by_pid = RB_INITIALIZER(&nbrs_by_pid); + +static __inline int +nbr_id_compare(const struct nbr *a, const struct nbr *b) +{ + return (ntohl(a->id.s_addr) - ntohl(b->id.s_addr)); +} + +static __inline int +nbr_addr_compare(const struct nbr *a, const struct nbr *b) +{ + if (a->af < b->af) + return (-1); + if (a->af > b->af) + return (1); + + return (ldp_addrcmp(a->af, &a->raddr, &b->raddr)); +} + +static __inline int +nbr_pid_compare(const struct nbr *a, const struct nbr *b) +{ + return (a->peerid - b->peerid); +} + +int +nbr_fsm(struct nbr *nbr, enum nbr_event event) +{ + struct timeval now; + int old_state; + int new_state = 0; + int i; + + old_state = nbr->state; + for (i = 0; nbr_fsm_tbl[i].state != -1; i++) + if (CHECK_FLAG(nbr_fsm_tbl[i].state, old_state) && + (nbr_fsm_tbl[i].event == event)) { + new_state = nbr_fsm_tbl[i].new_state; + break; + } + + if (nbr_fsm_tbl[i].state == -1) { + /* event outside of the defined fsm, ignore it. */ + log_warnx("%s: lsr-id %pI4, event %s not expected in state %s", __func__, &nbr->id, + nbr_event_names[event], nbr_state_name(old_state)); + return (0); + } + + if (new_state != 0) + nbr->state = new_state; + + if (old_state != nbr->state) { + log_debug("%s: event %s resulted in action %s and changing state for lsr-id %pI4 from %s to %s", + __func__, nbr_event_names[event], + nbr_action_names[nbr_fsm_tbl[i].action], + &nbr->id, nbr_state_name(old_state), + nbr_state_name(nbr->state)); + + hook_call(ldp_nbr_state_change, nbr, old_state); + + if (nbr->state == NBR_STA_OPER) { + gettimeofday(&now, NULL); + nbr->uptime = now.tv_sec; + } + } + + if (nbr->state == NBR_STA_OPER || nbr->state == NBR_STA_PRESENT) + nbr_stop_itimeout(nbr); + else + nbr_start_itimeout(nbr); + + switch (nbr_fsm_tbl[i].action) { + case NBR_ACT_RST_KTIMEOUT: + nbr_start_ktimeout(nbr); + break; + case NBR_ACT_RST_KTIMER: + nbr_start_ktimer(nbr); + break; + case NBR_ACT_SESSION_EST: + nbr_act_session_operational(nbr); + nbr_start_ktimer(nbr); + nbr_start_ktimeout(nbr); + if (nbr->v4_enabled) + send_address_all(nbr, AF_INET); + if (nbr->v6_enabled) + send_address_all(nbr, AF_INET6); + nbr_send_labelmappings(nbr); + break; + case NBR_ACT_CONNECT_SETUP: + nbr->tcp = tcp_new(nbr->fd, nbr); + + /* trigger next state */ + send_init(nbr); + nbr_fsm(nbr, NBR_EVT_INIT_SENT); + break; + case NBR_ACT_PASSIVE_INIT: + send_init(nbr); + send_keepalive(nbr); + break; + case NBR_ACT_KEEPALIVE_SEND: + nbr_start_ktimeout(nbr); + send_keepalive(nbr); + break; + case NBR_ACT_CLOSE_SESSION: + ldpe_imsg_compose_lde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0, NULL, 0); + session_close(nbr); + break; + case NBR_ACT_NOTHING: + /* do nothing */ + break; + } + + return (0); +} + +struct nbr * +nbr_new(struct in_addr id, int af, int ds_tlv, union ldpd_addr *addr, + uint32_t scope_id) +{ + struct nbr *nbr; + struct nbr_params *nbrp; + struct adj *adj; + struct pending_conn *pconn; + + log_debug("%s: lsr-id %pI4 transport-address %s", __func__, + &id, log_addr(af, addr)); + + if ((nbr = calloc(1, sizeof(*nbr))) == NULL) + fatal(__func__); + + RB_INIT(nbr_adj_head, &nbr->adj_tree); + nbr->state = NBR_STA_PRESENT; + nbr->peerid = 0; + nbr->af = af; + nbr->ds_tlv = ds_tlv; + if (af == AF_INET || ds_tlv) + nbr->v4_enabled = 1; + if (af == AF_INET6 || ds_tlv) + nbr->v6_enabled = 1; + nbr->id = id; + nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr; + nbr->raddr = *addr; + nbr->raddr_scope = scope_id; + nbr->conf_seqnum = 0; + + RB_FOREACH(adj, global_adj_head, &global.adj_tree) { + if (adj->lsr_id.s_addr == nbr->id.s_addr) { + adj->nbr = nbr; + RB_INSERT(nbr_adj_head, &nbr->adj_tree, adj); + } + } + + if (RB_INSERT(nbr_id_head, &nbrs_by_id, nbr) != NULL) + fatalx("nbr_new: RB_INSERT(nbrs_by_id) failed"); + if (RB_INSERT(nbr_addr_head, &nbrs_by_addr, nbr) != NULL) + fatalx("nbr_new: RB_INSERT(nbrs_by_addr) failed"); + + TAILQ_INIT(&nbr->mapping_list); + TAILQ_INIT(&nbr->withdraw_list); + TAILQ_INIT(&nbr->request_list); + TAILQ_INIT(&nbr->release_list); + TAILQ_INIT(&nbr->abortreq_list); + + nbrp = nbr_params_find(leconf, nbr->id); + if (nbrp) { + nbr->auth.method = nbrp->auth.method; +#ifdef __OpenBSD__ + if (pfkey_establish(nbr, nbrp) == -1) + fatalx("pfkey setup failed"); +#else + sock_set_md5sig( + (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, + nbr->af, &nbr->raddr, nbrp->auth.md5key); +#endif + } + + pconn = pending_conn_find(nbr->af, &nbr->raddr); + if (pconn) { + session_accept_nbr(nbr, pconn->fd); + pending_conn_del(pconn); + } + + return (nbr); +} + +void +nbr_del(struct nbr *nbr) +{ + struct adj *adj; + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); +#ifdef __OpenBSD__ + pfkey_remove(nbr); +#else + sock_set_md5sig( + (ldp_af_global_get(&global, nbr->af))->ldp_session_socket, + nbr->af, &nbr->raddr, NULL); +#endif + nbr->auth.method = AUTH_NONE; + + if (nbr_pending_connect(nbr)) + EVENT_OFF(nbr->ev_connect); + nbr_stop_ktimer(nbr); + nbr_stop_ktimeout(nbr); + nbr_stop_itimeout(nbr); + nbr_stop_idtimer(nbr); + + mapping_list_clr(&nbr->mapping_list); + mapping_list_clr(&nbr->withdraw_list); + mapping_list_clr(&nbr->request_list); + mapping_list_clr(&nbr->release_list); + mapping_list_clr(&nbr->abortreq_list); + + while (!RB_EMPTY(nbr_adj_head, &nbr->adj_tree)) { + adj = RB_ROOT(nbr_adj_head, &nbr->adj_tree); + + adj->nbr = NULL; + RB_REMOVE(nbr_adj_head, &nbr->adj_tree, adj); + } + + if (nbr->peerid) + RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); + RB_REMOVE(nbr_id_head, &nbrs_by_id, nbr); + RB_REMOVE(nbr_addr_head, &nbrs_by_addr, nbr); + + free(nbr); +} + +static void +nbr_update_peerid(struct nbr *nbr) +{ + static uint32_t peercnt = 1; + + if (nbr->peerid) + RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr); + + /* get next unused peerid */ + while (nbr_find_peerid(++peercnt)) + ; + nbr->peerid = peercnt; + + if (RB_INSERT(nbr_pid_head, &nbrs_by_pid, nbr) != NULL) + fatalx("nbr_update_peerid: RB_INSERT(nbrs_by_pid) failed"); +} + +struct nbr * +nbr_find_ldpid(uint32_t lsr_id) +{ + struct nbr n; + n.id.s_addr = lsr_id; + return (RB_FIND(nbr_id_head, &nbrs_by_id, &n)); +} + +struct nbr *nbr_get_first_ldpid(void) +{ + return (RB_MIN(nbr_id_head, &nbrs_by_id)); +} + +struct nbr * +nbr_get_next_ldpid(uint32_t lsr_id) +{ + struct nbr *nbr; + nbr = nbr_find_ldpid(lsr_id); + if (nbr) + return (RB_NEXT(nbr_id_head, nbr)); + return NULL; +} + + +struct nbr * +nbr_find_addr(int af, union ldpd_addr *addr) +{ + struct nbr n; + n.af = af; + n.raddr = *addr; + return (RB_FIND(nbr_addr_head, &nbrs_by_addr, &n)); +} + +struct nbr * +nbr_find_peerid(uint32_t peerid) +{ + struct nbr n; + n.peerid = peerid; + return (RB_FIND(nbr_pid_head, &nbrs_by_pid, &n)); +} + +int +nbr_adj_count(struct nbr *nbr, int af) +{ + struct adj *adj; + int total = 0; + + RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) + if (adj_get_af(adj) == af) + total++; + + return (total); +} + +int +nbr_session_active_role(struct nbr *nbr) +{ + if (ldp_addrcmp(nbr->af, &nbr->laddr, &nbr->raddr) > 0) + return (1); + + return (0); +} + +/* timers */ + +/* Keepalive timer: timer to send keepalive message to neighbors */ + +static void nbr_ktimer(struct event *thread) +{ + struct nbr *nbr = EVENT_ARG(thread); + + nbr->keepalive_timer = NULL; + send_keepalive(nbr); + nbr_start_ktimer(nbr); +} + +static void +nbr_start_ktimer(struct nbr *nbr) +{ + int secs; + + /* send three keepalives per period */ + secs = nbr->keepalive / KEEPALIVE_PER_PERIOD; + EVENT_OFF(nbr->keepalive_timer); + nbr->keepalive_timer = NULL; + event_add_timer(master, nbr_ktimer, nbr, secs, &nbr->keepalive_timer); +} + +void +nbr_stop_ktimer(struct nbr *nbr) +{ + EVENT_OFF(nbr->keepalive_timer); +} + +/* Keepalive timeout: if the nbr hasn't sent keepalive */ + +static void nbr_ktimeout(struct event *thread) +{ + struct nbr *nbr = EVENT_ARG(thread); + + nbr->keepalive_timeout = NULL; + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + session_shutdown(nbr, S_KEEPALIVE_TMR, 0, 0); +} + +static void +nbr_start_ktimeout(struct nbr *nbr) +{ + EVENT_OFF(nbr->keepalive_timeout); + nbr->keepalive_timeout = NULL; + event_add_timer(master, nbr_ktimeout, nbr, nbr->keepalive, + &nbr->keepalive_timeout); +} + +void +nbr_stop_ktimeout(struct nbr *nbr) +{ + EVENT_OFF(nbr->keepalive_timeout); +} + +/* Session initialization timeout: if nbr got stuck in the initialization FSM */ + +static void nbr_itimeout(struct event *thread) +{ + struct nbr *nbr = EVENT_ARG(thread); + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); +} + +static void +nbr_start_itimeout(struct nbr *nbr) +{ + int secs; + + secs = INIT_FSM_TIMEOUT; + EVENT_OFF(nbr->init_timeout); + nbr->init_timeout = NULL; + event_add_timer(master, nbr_itimeout, nbr, secs, &nbr->init_timeout); +} + +void +nbr_stop_itimeout(struct nbr *nbr) +{ + EVENT_OFF(nbr->init_timeout); +} + +/* Init delay timer: timer to retry to iniziatize session */ + +static void nbr_idtimer(struct event *thread) +{ + struct nbr *nbr = EVENT_ARG(thread); + + nbr->initdelay_timer = NULL; + + log_debug("%s: lsr-id %pI4", __func__, &nbr->id); + + nbr_establish_connection(nbr); +} + +void +nbr_start_idtimer(struct nbr *nbr) +{ + int secs; + + if (nbr->idtimer_cnt > 2) { + /* do not further increase the counter */ + secs = MAX_DELAY_TMR; + } else { + secs = INIT_DELAY_TMR * (1 << nbr->idtimer_cnt); + nbr->idtimer_cnt++; + } + + EVENT_OFF(nbr->initdelay_timer); + nbr->initdelay_timer = NULL; + event_add_timer(master, nbr_idtimer, nbr, secs, &nbr->initdelay_timer); +} + +void +nbr_stop_idtimer(struct nbr *nbr) +{ + EVENT_OFF(nbr->initdelay_timer); +} + +int +nbr_pending_idtimer(struct nbr *nbr) +{ + return (nbr->initdelay_timer != NULL); +} + +int +nbr_pending_connect(struct nbr *nbr) +{ + return (nbr->ev_connect != NULL); +} + +static void nbr_connect_cb(struct event *thread) +{ + struct nbr *nbr = EVENT_ARG(thread); + int error; + socklen_t len; + + nbr->ev_connect = NULL; + + len = sizeof(error); + if (getsockopt(nbr->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + log_warn("%s: getsockopt SOL_SOCKET SO_ERROR", __func__); + return; + } + + if (error) { + close(nbr->fd); + errno = error; + log_debug("%s: error while connecting to %s: %s", __func__, + log_addr(nbr->af, &nbr->raddr), strerror(errno)); + return; + } + + nbr_fsm(nbr, NBR_EVT_CONNECT_UP); +} + +int +nbr_establish_connection(struct nbr *nbr) +{ + union sockunion local_su; + union sockunion remote_su; + struct adj *adj; + struct nbr_params *nbrp; +#ifdef __OpenBSD__ + int opt = 1; +#endif + + nbr->fd = socket(nbr->af, SOCK_STREAM, 0); + if (nbr->fd == -1) { + log_warn("%s: error while creating socket", __func__); + return (-1); + } + sock_set_nonblock(nbr->fd); + + nbrp = nbr_params_find(leconf, nbr->id); + if (nbrp && nbrp->auth.method == AUTH_MD5SIG) { +#ifdef __OpenBSD__ + if (sysdep.no_pfkey || sysdep.no_md5sig) { + log_warnx("md5sig configured but not available"); + close(nbr->fd); + return (-1); + } + if (setsockopt(nbr->fd, IPPROTO_TCP, TCP_MD5SIG, + &opt, sizeof(opt)) == -1) { + log_warn("setsockopt md5sig"); + close(nbr->fd); + return (-1); + } +#else + sock_set_md5sig(nbr->fd, nbr->af, &nbr->raddr, nbrp->auth.md5key); +#endif + } + + if (nbr->af == AF_INET) { + if (sock_set_ipv4_tos(nbr->fd, IPTOS_PREC_INTERNETCONTROL) == -1) + log_warn("%s: lsr-id %pI4, sock_set_ipv4_tos error", + __func__, &nbr->id); + } else if (nbr->af == AF_INET6) { + if (sock_set_ipv6_dscp(nbr->fd, IPTOS_PREC_INTERNETCONTROL) == -1) + log_warn("%s: lsr-id %pI4, sock_set_ipv6_dscp error", + __func__, &nbr->id); + } + + addr2sa(nbr->af, &nbr->laddr, 0, &local_su); + addr2sa(nbr->af, &nbr->raddr, LDP_PORT, &remote_su); + if (nbr->af == AF_INET6 && nbr->raddr_scope) + addscope(&remote_su.sin6, nbr->raddr_scope); + + if (bind(nbr->fd, &local_su.sa, sockaddr_len(&local_su.sa)) == -1) { + log_warn("%s: error while binding socket to %s", __func__, + log_sockaddr(&local_su.sa)); + close(nbr->fd); + return (-1); + } + + if (nbr_gtsm_check(nbr->fd, nbr, nbrp)) { + close(nbr->fd); + return (-1); + } + + /* + * Send an extra hello to guarantee that the remote peer has formed + * an adjacency as well. + */ + RB_FOREACH(adj, nbr_adj_head, &nbr->adj_tree) + send_hello(adj->source.type, adj->source.link.ia, + adj->source.target); + + if (connect(nbr->fd, &remote_su.sa, sockaddr_len(&remote_su.sa)) == -1) { + if (errno == EINPROGRESS) { + event_add_write(master, nbr_connect_cb, nbr, nbr->fd, + &nbr->ev_connect); + return (0); + } + log_warn("%s: error while connecting to %s", __func__, + log_sockaddr(&remote_su.sa)); + close(nbr->fd); + return (-1); + } + + /* connection completed immediately */ + nbr_fsm(nbr, NBR_EVT_CONNECT_UP); + + return (0); +} + +int +nbr_gtsm_enabled(struct nbr *nbr, struct nbr_params *nbrp) +{ + /* + * RFC 6720 - Section 3: + * "This document allows for the implementation to provide an option to + * statically (e.g., via configuration) and/or dynamically override the + * default behavior and enable/disable GTSM on a per-peer basis". + */ + if (nbrp && CHECK_FLAG(nbrp->flags, F_NBRP_GTSM)) + return (nbrp->gtsm_enabled); + + if (CHECK_FLAG((ldp_af_conf_get(leconf, nbr->af))->flags, F_LDPD_AF_NO_GTSM)) + return (0); + + /* By default, GTSM support has to be negotiated for LDPv4 */ + if (nbr->af == AF_INET && !CHECK_FLAG(nbr->flags, F_NBR_GTSM_NEGOTIATED)) + return (0); + + return (1); +} + +int +nbr_gtsm_setup(int fd, int af, struct nbr_params *nbrp) +{ + int ttl = 255; + + if (nbrp && CHECK_FLAG(nbrp->flags, F_NBRP_GTSM_HOPS)) + ttl = 256 - nbrp->gtsm_hops; + + /* + * In linux networking stack, the received mpls packets + * will be processed by the host twice, one as mpls packet, + * the other as ip packet, so its ttl will be decreased 1. + * This behavior is based on the new kernel (5.10 and 6.1), + * and older versions may behave differently. + * + * Here, decrease 1 for IP_MINTTL if GTSM is enabled. + * And this workaround makes the GTSM mechanism a bit deviation. + */ + ttl -= 1; + + switch (af) { + case AF_INET: + if (sock_set_ipv4_minttl(fd, ttl) == -1) + return (-1); + ttl = 255; + if (sock_set_ipv4_ucast_ttl(fd, ttl) == -1) + return (-1); + break; + case AF_INET6: + /* ignore any possible error */ + sock_set_ipv6_minhopcount(fd, ttl); + ttl = 255; + if (sock_set_ipv6_ucast_hops(fd, ttl) == -1) + return (-1); + break; + default: + fatalx("nbr_gtsm_setup: unknown af"); + } + + return (0); +} + +int +nbr_gtsm_check(int fd, struct nbr *nbr, struct nbr_params *nbrp) +{ + if (!nbr_gtsm_enabled(nbr, nbrp)) { + switch (nbr->af) { + case AF_INET: + sock_set_ipv4_ucast_ttl(fd, -1); + break; + case AF_INET6: + /* + * Send packets with a Hop Limit of 255 even when GSTM + * is disabled to guarantee interoperability. + */ + sock_set_ipv6_ucast_hops(fd, 255); + break; + default: + fatalx("nbr_gtsm_check: unknown af"); + break; + } + return (0); + } + + if (nbr_gtsm_setup(fd, nbr->af, nbrp) == -1) { + log_warnx("%s: error enabling GTSM for lsr-id %pI4", __func__, &nbr->id); + return (-1); + } + + return (0); +} + +static int +nbr_act_session_operational(struct nbr *nbr) +{ + struct lde_nbr lde_nbr; + + nbr->idtimer_cnt = 0; + + /* this is necessary to avoid ipc synchronization issues */ + nbr_update_peerid(nbr); + + ldp_sync_fsm_nbr_event(nbr, LDP_SYNC_EVT_LDP_SYNC_START); + + memset(&lde_nbr, 0, sizeof(lde_nbr)); + lde_nbr.id = nbr->id; + lde_nbr.v4_enabled = nbr->v4_enabled; + lde_nbr.v6_enabled = nbr->v6_enabled; + lde_nbr.flags = nbr->flags; + return (ldpe_imsg_compose_lde(IMSG_NEIGHBOR_UP, nbr->peerid, 0, + &lde_nbr, sizeof(lde_nbr))); +} + +static void +nbr_send_labelmappings(struct nbr *nbr) +{ + ldpe_imsg_compose_lde(IMSG_LABEL_MAPPING_FULL, nbr->peerid, 0, NULL, 0); +} + +static __inline int +nbr_params_compare(const struct nbr_params *a, const struct nbr_params *b) +{ + return (ntohl(a->lsr_id.s_addr) - ntohl(b->lsr_id.s_addr)); +} + +struct nbr_params * +nbr_params_new(struct in_addr lsr_id) +{ + struct nbr_params *nbrp; + + if ((nbrp = calloc(1, sizeof(*nbrp))) == NULL) + fatal(__func__); + + nbrp->lsr_id = lsr_id; + nbrp->auth.method = AUTH_NONE; + + return (nbrp); +} + +struct nbr_params * +nbr_params_find(struct ldpd_conf *xconf, struct in_addr lsr_id) +{ + struct nbr_params nbrp; + nbrp.lsr_id = lsr_id; + return (RB_FIND(nbrp_head, &xconf->nbrp_tree, &nbrp)); +} + +uint16_t +nbr_get_keepalive(int af, struct in_addr lsr_id) +{ + struct nbr_params *nbrp; + + nbrp = nbr_params_find(leconf, lsr_id); + if (nbrp && CHECK_FLAG(nbrp->flags, F_NBRP_KEEPALIVE)) + return (nbrp->keepalive); + + return ((ldp_af_conf_get(leconf, af))->keepalive); +} + +struct ctl_nbr * +nbr_to_ctl(struct nbr *nbr) +{ + static struct ctl_nbr nctl; + struct timeval now; + + nctl.af = nbr->af; + nctl.id = nbr->id; + nctl.laddr = nbr->laddr; + nctl.lport = nbr->tcp ? nbr->tcp->lport : 0; + nctl.raddr = nbr->raddr; + nctl.rport = nbr->tcp ? nbr->tcp->rport : 0; + nctl.auth_method = nbr->auth.method; + nctl.holdtime = nbr->keepalive; + nctl.nbr_state = nbr->state; + nctl.stats = nbr->stats; + nctl.flags = nbr->flags; + nctl.max_pdu_len = nbr->max_pdu_len; + nctl.hold_time_remaining = event_timer_remain_second(nbr->keepalive_timer); + + gettimeofday(&now, NULL); + if (nbr->state == NBR_STA_OPER) { + nctl.uptime = now.tv_sec - nbr->uptime; + } else + nctl.uptime = 0; + + return (&nctl); +} + +void +nbr_clear_ctl(struct ctl_nbr *nctl) +{ + struct nbr *nbr; + + RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) { + if (ldp_addrisset(nctl->af, &nctl->raddr) && + ldp_addrcmp(nctl->af, &nctl->raddr, &nbr->raddr)) + continue; + + log_debug("%s: neighbor %s manually cleared", __func__, + log_addr(nbr->af, &nbr->raddr)); + session_shutdown(nbr, S_SHUTDOWN, 0, 0); + } +} diff --git a/ldpd/notification.c b/ldpd/notification.c new file mode 100644 index 0000000..1709098 --- /dev/null +++ b/ldpd/notification.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2009 Michele Marchetto + */ + +#include + +#include "ldpd.h" +#include "ldp.h" +#include "log.h" +#include "ldpe.h" +#include "ldp_debug.h" + +static int gen_returned_tlvs(struct ibuf *, uint16_t, uint16_t, char *); +static void log_msg_notification(int, struct nbr *, struct notify_msg *); + +void +send_notification_full(struct tcp_conn *tcp, struct notify_msg *nm) +{ + struct ibuf *buf; + uint16_t size; + int err = 0; + + /* calculate size */ + size = LDP_HDR_SIZE + LDP_MSG_SIZE + STATUS_SIZE; + if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS)) + size += PW_STATUS_TLV_SIZE; + if (CHECK_FLAG(nm->flags, F_NOTIF_FEC)) + size += len_fec_tlv(&nm->fec); + if (CHECK_FLAG(nm->flags, F_NOTIF_RETURNED_TLVS)) + size += TLV_HDR_SIZE * 2 + nm->rtlvs.length; + + if ((buf = ibuf_open(size)) == NULL) + fatal(__func__); + + SET_FLAG(err, gen_ldp_hdr(buf, size)); + size -= LDP_HDR_SIZE; + SET_FLAG(err, gen_msg_hdr(buf, MSG_TYPE_NOTIFICATION, size)); + SET_FLAG(err, gen_status_tlv(buf, nm->status_code, nm->msg_id, nm->msg_type)); + /* optional tlvs */ + if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS)) + SET_FLAG(err, gen_pw_status_tlv(buf, nm->pw_status)); + if (CHECK_FLAG(nm->flags, F_NOTIF_FEC)) + SET_FLAG(err, gen_fec_tlv(buf, &nm->fec)); + if (CHECK_FLAG(nm->flags, F_NOTIF_RETURNED_TLVS)) + SET_FLAG(err, gen_returned_tlvs(buf, nm->rtlvs.type, nm->rtlvs.length, + nm->rtlvs.data)); + if (err) { + ibuf_free(buf); + return; + } + + if (tcp->nbr) { + log_msg_notification(1, tcp->nbr, nm); + nbr_fsm(tcp->nbr, NBR_EVT_PDU_SENT); + tcp->nbr->stats.notif_sent++; + } + + /* update SNMP session counters */ + switch (nm->status_code) { + case S_NO_HELLO: + leconf->stats.session_rejects_hello++; + break; + case S_BAD_LDP_ID: + leconf->stats.bad_ldp_id++; + break; + case S_BAD_PDU_LEN: + leconf->stats.bad_pdu_len++; + break; + case S_BAD_MSG_LEN: + leconf->stats.bad_msg_len++; + break; + case S_BAD_TLV_LEN: + leconf->stats.bad_tlv_len++; + break; + case S_BAD_TLV_VAL: + leconf->stats.malformed_tlv++; + break; + case S_KEEPALIVE_TMR: + leconf->stats.keepalive_timer_exp++; + break; + case S_SHUTDOWN: + leconf->stats.shutdown_send_notify++; + break; + default: + break; + } + + evbuf_enqueue(&tcp->wbuf, buf); +} + +/* send a notification without optional tlvs */ +void +send_notification(struct tcp_conn *tcp, uint32_t status_code, uint32_t msg_id, + uint16_t msg_type) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = status_code; + nm.msg_id = msg_id; + nm.msg_type = msg_type; + + send_notification_full(tcp, &nm); +} + +void +send_notification_rtlvs(struct nbr *nbr, uint32_t status_code, uint32_t msg_id, + uint16_t msg_type, uint16_t tlv_type, uint16_t tlv_len, char *tlv_data) +{ + struct notify_msg nm; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = status_code; + nm.msg_id = msg_id; + nm.msg_type = msg_type; + /* do not append the given TLV if it's too big (shouldn't happen) */ + if (tlv_len < 1024) { + nm.rtlvs.type = tlv_type; + nm.rtlvs.length = tlv_len; + nm.rtlvs.data = tlv_data; + SET_FLAG(nm.flags, F_NOTIF_RETURNED_TLVS); + } + + send_notification_full(nbr->tcp, &nm); +} + +int +recv_notification(struct nbr *nbr, char *buf, uint16_t len) +{ + struct ldp_msg msg; + struct status_tlv st; + struct notify_msg nm; + int tlen; + + memcpy(&msg, buf, sizeof(msg)); + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + if (len < STATUS_SIZE) { + session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type); + leconf->stats.bad_msg_len++; + return (-1); + } + memcpy(&st, buf, sizeof(st)); + + if (ntohs(st.length) > STATUS_SIZE - TLV_HDR_SIZE || + ntohs(st.length) > len - TLV_HDR_SIZE) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + leconf->stats.bad_tlv_len++; + return (-1); + } + buf += STATUS_SIZE; + len -= STATUS_SIZE; + + memset(&nm, 0, sizeof(nm)); + nm.status_code = ntohl(st.status_code); + + /* Optional Parameters */ + while (len > 0) { + struct tlv tlv; + uint16_t tlv_type; + uint16_t tlv_len; + + if (len < sizeof(tlv)) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + leconf->stats.bad_tlv_len++; + return (-1); + } + + memcpy(&tlv, buf, TLV_HDR_SIZE); + tlv_type = ntohs(tlv.type); + tlv_len = ntohs(tlv.length); + if (tlv_len + TLV_HDR_SIZE > len) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + leconf->stats.bad_tlv_len++; + return (-1); + } + buf += TLV_HDR_SIZE; + len -= TLV_HDR_SIZE; + + switch (tlv_type) { + case TLV_TYPE_EXTSTATUS: + case TLV_TYPE_RETURNEDPDU: + case TLV_TYPE_RETURNEDMSG: + /* TODO is there any use for this? */ + break; + case TLV_TYPE_PW_STATUS: + if (tlv_len != 4) { + session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type); + return (-1); + } + + nm.pw_status = ntohl(*(uint32_t *)buf); + SET_FLAG(nm.flags, F_NOTIF_PW_STATUS); + break; + case TLV_TYPE_FEC: + if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf, + tlv_len, &nm.fec)) == -1) + return (-1); + /* allow only one fec element */ + if (tlen != tlv_len) { + session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type); + leconf->stats.bad_tlv_len++; + return (-1); + } + SET_FLAG(nm.flags, F_NOTIF_FEC); + break; + default: + if (!(ntohs(tlv.type) & UNKNOWN_FLAG)) { + nbr->stats.unknown_tlv++; + send_notification_rtlvs(nbr, S_UNKNOWN_TLV, + msg.id, msg.type, tlv_type, tlv_len, buf); + } + /* ignore unknown tlv */ + break; + } + buf += tlv_len; + len -= tlv_len; + } + + /* sanity checks */ + switch (nm.status_code) { + case S_PW_STATUS: + if (!CHECK_FLAG(nm.flags, (F_NOTIF_PW_STATUS|F_NOTIF_FEC))) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + return (-1); + } + + switch (nm.fec.type) { + case MAP_TYPE_PWID: + break; + default: + send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + break; + case S_ENDOFLIB: + if (!CHECK_FLAG(nm.flags, F_NOTIF_FEC)) { + send_notification(nbr->tcp, S_MISS_MSG, msg.id, msg.type); + return (-1); + } + if (nm.fec.type != MAP_TYPE_TYPED_WCARD) { + send_notification(nbr->tcp, S_BAD_TLV_VAL, msg.id, msg.type); + return (-1); + } + break; + default: + break; + } + + log_msg_notification(0, nbr, &nm); + + if (CHECK_FLAG(st.status_code, htonl(STATUS_FATAL))) { + if (nbr->state == NBR_STA_OPENSENT) + nbr_start_idtimer(nbr); + + /* + * RFC 5036 - Section 3.5.1.1: + * "When an LSR receives a Shutdown message during session + * initialization, it SHOULD transmit a Shutdown message and + * then close the transport connection". + */ + if (nbr->state != NBR_STA_OPER && nm.status_code == S_SHUTDOWN) { + leconf->stats.session_attempts++; + send_notification(nbr->tcp, S_SHUTDOWN, msg.id, msg.type); + } + + leconf->stats.shutdown_rcv_notify++; + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + return (-1); + } + + /* lde needs to know about a few notification messages + * and update SNMP session counters + */ + switch (nm.status_code) { + case S_PW_STATUS: + case S_ENDOFLIB: + ldpe_imsg_compose_lde(IMSG_NOTIFICATION, nbr->peerid, 0, &nm, sizeof(nm)); + break; + case S_NO_HELLO: + leconf->stats.session_rejects_hello++; + break; + case S_PARM_ADV_MODE: + leconf->stats.session_rejects_ad++; + break; + case S_MAX_PDU_LEN: + leconf->stats.session_rejects_max_pdu++; + break; + case S_PARM_L_RANGE: + leconf->stats.session_rejects_lr++; + break; + case S_BAD_LDP_ID: + leconf->stats.bad_ldp_id++; + break; + case S_BAD_PDU_LEN: + leconf->stats.bad_pdu_len++; + break; + case S_BAD_MSG_LEN: + leconf->stats.bad_msg_len++; + break; + case S_BAD_TLV_LEN: + leconf->stats.bad_tlv_len++; + break; + case S_BAD_TLV_VAL: + leconf->stats.malformed_tlv++; + break; + case S_SHUTDOWN: + leconf->stats.shutdown_rcv_notify++; + break; + default: + break; + } + + return (0); +} + +int +gen_status_tlv(struct ibuf *buf, uint32_t status_code, uint32_t msg_id, + uint16_t msg_type) +{ + struct status_tlv st; + + memset(&st, 0, sizeof(st)); + st.type = htons(TLV_TYPE_STATUS); + st.length = htons(STATUS_TLV_LEN); + st.status_code = htonl(status_code); + /* + * For convenience, msg_id and msg_type are already in network + * byte order. + */ + st.msg_id = msg_id; + st.msg_type = msg_type; + + return (ibuf_add(buf, &st, STATUS_SIZE)); +} + +static int +gen_returned_tlvs(struct ibuf *buf, uint16_t type, uint16_t length, + char *tlv_data) +{ + struct tlv rtlvs; + struct tlv tlv; + int err; + + rtlvs.type = htons(TLV_TYPE_RETURNED_TLVS); + rtlvs.length = htons(length + TLV_HDR_SIZE); + tlv.type = htons(type); + tlv.length = htons(length); + + err = ibuf_add(buf, &rtlvs, sizeof(rtlvs)); + SET_FLAG(err, ibuf_add(buf, &tlv, sizeof(tlv))); + SET_FLAG(err, ibuf_add(buf, tlv_data, length)); + + return (err); +} + +void +log_msg_notification(int out, struct nbr *nbr, struct notify_msg *nm) +{ + if (nm->status_code & STATUS_FATAL) { + debug_msg(out, "notification: lsr-id %pI4, status %s (fatal error)", &nbr->id, + status_code_name(nm->status_code)); + return; + } + + debug_msg(out, "notification: lsr-id %pI4, status %s", + &nbr->id, status_code_name(nm->status_code)); + if (CHECK_FLAG(nm->flags, F_NOTIF_FEC)) + debug_msg(out, "notification: fec %s", log_map(&nm->fec)); + if (CHECK_FLAG(nm->flags, F_NOTIF_PW_STATUS)) + debug_msg(out, "notification: pw-status %s", + (nm->pw_status == PW_FORWARDING) ? "forwarding" : "not forwarding"); +} diff --git a/ldpd/packet.c b/ldpd/packet.c new file mode 100644 index 0000000..d2d9305 --- /dev/null +++ b/ldpd/packet.c @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013, 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2004, 2005, 2008 Esben Norby + */ + +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +#include "sockopt.h" + +static struct iface *disc_find_iface(unsigned int, int, union ldpd_addr *); +static void session_read(struct event *thread); +static void session_write(struct event *thread); +static ssize_t session_get_pdu(struct ibuf_read *, char **); +static void tcp_close(struct tcp_conn *); +static struct pending_conn *pending_conn_new(int, int, union ldpd_addr *); +static void pending_conn_timeout(struct event *thread); + +int +gen_ldp_hdr(struct ibuf *buf, uint16_t size) +{ + struct ldp_hdr ldp_hdr; + + memset(&ldp_hdr, 0, sizeof(ldp_hdr)); + ldp_hdr.version = htons(LDP_VERSION); + /* exclude the 'Version' and 'PDU Length' fields from the total */ + ldp_hdr.length = htons(size - LDP_HDR_DEAD_LEN); + ldp_hdr.lsr_id = ldp_rtr_id_get(leconf); + ldp_hdr.lspace_id = 0; + + return (ibuf_add(buf, &ldp_hdr, LDP_HDR_SIZE)); +} + +int +gen_msg_hdr(struct ibuf *buf, uint16_t type, uint16_t size) +{ + static int msgcnt = 0; + struct ldp_msg msg; + + memset(&msg, 0, sizeof(msg)); + msg.type = htons(type); + /* exclude the 'Type' and 'Length' fields from the total */ + msg.length = htons(size - LDP_MSG_DEAD_LEN); + msg.id = htonl(++msgcnt); + + return (ibuf_add(buf, &msg, sizeof(msg))); +} + +/* send packets */ +int +send_packet(int fd, int af, union ldpd_addr *dst, struct iface_af *ia, + void *pkt, size_t len) +{ + union sockunion su; + + switch (af) { + case AF_INET: + if (ia && IN_MULTICAST(ntohl(dst->v4.s_addr))) { + /* set outgoing interface for multicast traffic */ + if (sock_set_ipv4_mcast(ia->iface) == -1) { + log_debug("%s: error setting multicast interface, %s", __func__, ia->iface->name); + return (-1); + } + } + break; + case AF_INET6: + if (ia && IN6_IS_ADDR_MULTICAST(&dst->v6)) { + /* set outgoing interface for multicast traffic */ + if (sock_set_ipv6_mcast(ia->iface) == -1) { + log_debug("%s: error setting multicast interface, %s", __func__, ia->iface->name); + return (-1); + } + } + break; + default: + fatalx("send_packet: unknown af"); + } + + addr2sa(af, dst, LDP_PORT, &su); + if (sendto(fd, pkt, len, 0, &su.sa, sockaddr_len(&su.sa)) == -1) { + log_warn("%s: error sending packet to %s", __func__, + log_sockaddr(&su.sa)); + return (-1); + } + + return (0); +} + +/* Discovery functions */ +void disc_recv_packet(struct event *thread) +{ + int fd = EVENT_FD(thread); + struct event **threadp = EVENT_ARG(thread); + + union { + struct cmsghdr hdr; +#ifdef HAVE_STRUCT_SOCKADDR_DL + char buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#else + char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; +#endif + } cmsgbuf; + struct msghdr m; + struct sockaddr_storage from; + struct iovec iov; + char *buf; +#ifndef MSG_MCAST + struct cmsghdr *cmsg; +#endif + ssize_t r; + int multicast; + int af; + union ldpd_addr src; + unsigned int ifindex = 0; + struct iface *iface = NULL; + uint16_t len; + struct ldp_hdr ldp_hdr; + uint16_t pdu_len; + struct ldp_msg msg; + uint16_t msg_len; + struct in_addr lsr_id; + + /* reschedule read */ + event_add_read(master, disc_recv_packet, threadp, fd, threadp); + + /* setup buffer */ + memset(&m, 0, sizeof(m)); + iov.iov_base = buf = pkt_ptr; + iov.iov_len = IBUF_READ_SIZE; + m.msg_name = &from; + m.msg_namelen = sizeof(from); + m.msg_iov = &iov; + m.msg_iovlen = 1; + m.msg_control = &cmsgbuf.buf; + m.msg_controllen = sizeof(cmsgbuf.buf); + + if ((r = recvmsg(fd, &m, 0)) == -1) { + if (errno != EAGAIN && errno != EINTR) + log_debug("%s: read error: %s", __func__, strerror(errno)); + return; + } + + sa2addr((struct sockaddr *)&from, &af, &src, NULL); +#ifdef MSG_MCAST + multicast = (m.msg_flags & MSG_MCAST) ? 1 : 0; +#else + multicast = 0; + for (cmsg = CMSG_FIRSTHDR(&m); cmsg != NULL; cmsg = CMSG_NXTHDR(&m, cmsg)) { +#if defined(HAVE_IP_PKTINFO) + if (af == AF_INET && + cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_PKTINFO) { + struct in_pktinfo *pktinfo; + + pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); + if (IN_MULTICAST(ntohl(pktinfo->ipi_addr.s_addr))) + multicast = 1; + break; + } +#elif defined(HAVE_IP_RECVDSTADDR) + if (af == AF_INET && + cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_RECVDSTADDR) { + struct in_addr *addr; + + addr = (struct in_addr *)CMSG_DATA(cmsg); + if (IN_MULTICAST(ntohl(addr->s_addr))) + multicast = 1; + break; + } +#else +#error "Unsupported socket API" +#endif + if (af == AF_INET6 && + cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *pktinfo; + + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + if (IN6_IS_ADDR_MULTICAST(&pktinfo->ipi6_addr)) + multicast = 1; + break; + } + } +#endif /* MSG_MCAST */ + if (bad_addr(af, &src)) { + log_debug("%s: invalid source address: %s", __func__, log_addr(af, &src)); + return; + } + ifindex = getsockopt_ifindex(af, &m); + + /* find a matching interface */ + if (multicast) { + iface = disc_find_iface(ifindex, af, &src); + if (iface == NULL) + return; + } + + /* check packet size */ + len = (uint16_t)r; + if (len < (LDP_HDR_SIZE + LDP_MSG_SIZE) || len > LDP_MAX_LEN) { + log_debug("%s: bad packet size, source %s", __func__, log_addr(af, &src)); + return; + } + + /* LDP header sanity checks */ + memcpy(&ldp_hdr, buf, sizeof(ldp_hdr)); + if (ntohs(ldp_hdr.version) != LDP_VERSION) { + log_debug("%s: invalid LDP version %d, source %s", __func__, + ntohs(ldp_hdr.version), log_addr(af, &src)); + return; + } + if (ntohs(ldp_hdr.lspace_id) != 0) { + log_debug("%s: invalid label space %u, source %s", __func__, + ntohs(ldp_hdr.lspace_id), log_addr(af, &src)); + return; + } + /* check "PDU Length" field */ + pdu_len = ntohs(ldp_hdr.length); + if ((pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE)) || + (pdu_len > (len - LDP_HDR_DEAD_LEN))) { + log_debug("%s: invalid LDP packet length %u, source %s", + __func__, ntohs(ldp_hdr.length), log_addr(af, &src)); + return; + } + buf += LDP_HDR_SIZE; + len -= LDP_HDR_SIZE; + + lsr_id.s_addr = ldp_hdr.lsr_id; + + /* + * For UDP, we process only the first message of each packet. This does + * not impose any restrictions since LDP uses UDP only for sending Hello + * packets. + */ + memcpy(&msg, buf, sizeof(msg)); + + /* check "Message Length" field */ + msg_len = ntohs(msg.length); + if (msg_len < LDP_MSG_LEN || ((msg_len + LDP_MSG_DEAD_LEN) > pdu_len)) { + log_debug("%s: invalid LDP message length %u, source %s", + __func__, ntohs(msg.length), log_addr(af, &src)); + return; + } + buf += LDP_MSG_SIZE; + len -= LDP_MSG_SIZE; + + /* switch LDP packet type */ + switch (ntohs(msg.type)) { + case MSG_TYPE_HELLO: + recv_hello(lsr_id, &msg, af, &src, iface, multicast, buf, len); + break; + default: + log_debug("%s: unknown LDP packet type, source %s", __func__, + log_addr(af, &src)); + } +} + +static struct iface * +disc_find_iface(unsigned int ifindex, int af, union ldpd_addr *src) +{ + struct iface *iface; + struct iface_af *ia; + + iface = if_lookup(leconf, ifindex); + if (iface == NULL) + return (NULL); + + ia = iface_af_get(iface, af); + if (!ia->enabled) + return (NULL); + + /* + * RFC 7552 - Section 5.1: + * "Link-local IPv6 address MUST be used as the source IP address in + * IPv6 LDP Link Hellos". + */ + if (af == AF_INET6 && !IN6_IS_ADDR_LINKLOCAL(&src->v6)) + return (NULL); + + return (iface); +} + +void session_accept(struct event *thread) +{ + int fd = EVENT_FD(thread); + struct sockaddr_storage src; + socklen_t len = sizeof(src); + int newfd; + int af; + union ldpd_addr addr; + struct nbr *nbr; + struct pending_conn *pconn; + + newfd = accept(fd, (struct sockaddr *)&src, &len); + if (newfd == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + accept_pause(); + } else if (errno != EWOULDBLOCK && errno != EINTR && errno != ECONNABORTED) + log_debug("%s: accept error: %s", __func__, strerror(errno)); + return; + } + sock_set_nonblock(newfd); + + sa2addr((struct sockaddr *)&src, &af, &addr, NULL); + + /* + * Since we don't support label spaces, we can identify this neighbor + * just by its source address. This way we don't need to wait for its + * Initialization message to know who we are talking to. + */ + nbr = nbr_find_addr(af, &addr); + if (nbr == NULL) { + /* + * According to RFC 5036, we would need to send a No Hello + * Error Notification message and close this TCP connection + * right now. But doing so would trigger the backoff exponential + * timer in the remote peer, which would considerably slow down + * the session establishment process. The trick here is to wait + * five seconds before sending the Notification Message. There's + * a good chance that the remote peer will send us a Hello + * message within this interval, so it's worth waiting before + * taking a more drastic measure. + */ + pconn = pending_conn_find(af, &addr); + if (pconn) + close(newfd); + else + pending_conn_new(newfd, af, &addr); + return; + } + /* protection against buggy implementations */ + if (nbr_session_active_role(nbr)) { + close(newfd); + return; + } + if (nbr->state != NBR_STA_PRESENT) { + log_debug("%s: lsr-id %pI4: rejecting additional transport connection", __func__, &nbr->id); + close(newfd); + return; + } + + session_accept_nbr(nbr, newfd); +} + +void +session_accept_nbr(struct nbr *nbr, int fd) +{ +#ifdef __OpenBSD__ + struct nbr_params *nbrp; + int opt; + socklen_t len; + + nbrp = nbr_params_find(leconf, nbr->id); + if (nbr_gtsm_check(fd, nbr, nbrp)) { + close(fd); + return; + } + + if (nbrp && nbrp->auth.method == AUTH_MD5SIG) { + if (sysdep.no_pfkey || sysdep.no_md5sig) { + log_warnx("md5sig configured but not available"); + close(fd); + return; + } + + len = sizeof(opt); + if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, &len) == -1) + fatal("getsockopt TCP_MD5SIG"); + if (!opt) { /* non-md5'd connection! */ + log_warnx("connection attempt without md5 signature"); + close(fd); + return; + } + } +#endif + + nbr->tcp = tcp_new(fd, nbr); + nbr_fsm(nbr, NBR_EVT_MATCH_ADJ); +} + +static void session_read(struct event *thread) +{ + int fd = EVENT_FD(thread); + struct nbr *nbr = EVENT_ARG(thread); + struct tcp_conn *tcp = nbr->tcp; + struct ldp_hdr *ldp_hdr; + struct ldp_msg *msg; + char *buf = NULL, *pdu; + ssize_t n, len; + uint16_t pdu_len, msg_len, msg_size, max_pdu_len; + int ret; + + event_add_read(master, session_read, nbr, fd, &tcp->rev); + + if ((n = read(fd, tcp->rbuf->buf + tcp->rbuf->wpos, + sizeof(tcp->rbuf->buf) - tcp->rbuf->wpos)) == -1) { + if (errno != EINTR && errno != EAGAIN) { + log_warn("%s: read error", __func__); + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + return; + } + /* retry read */ + return; + } + if (n == 0) { + /* connection closed */ + log_debug("%s: connection closed by remote end", __func__); + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + return; + } + tcp->rbuf->wpos += n; + + while ((len = session_get_pdu(tcp->rbuf, &buf)) > 0) { + pdu = buf; + ldp_hdr = (struct ldp_hdr *)pdu; + if (ntohs(ldp_hdr->version) != LDP_VERSION) { + session_shutdown(nbr, S_BAD_PROTO_VER, 0, 0); + free(buf); + return; + } + + pdu_len = ntohs(ldp_hdr->length); + /* + * RFC 5036 - Section 3.5.3: + * "Prior to completion of the negotiation, the maximum + * allowable length is 4096 bytes". + */ + if (nbr->state == NBR_STA_OPER) + max_pdu_len = nbr->max_pdu_len; + else + max_pdu_len = LDP_MAX_LEN; + if (pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE) || pdu_len > max_pdu_len) { + session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); + free(buf); + return; + } + pdu_len -= LDP_HDR_PDU_LEN; + if (ldp_hdr->lsr_id != nbr->id.s_addr || ldp_hdr->lspace_id != 0) { + session_shutdown(nbr, S_BAD_LDP_ID, 0, 0); + free(buf); + return; + } + pdu += LDP_HDR_SIZE; + len -= LDP_HDR_SIZE; + + nbr_fsm(nbr, NBR_EVT_PDU_RCVD); + + while (len >= LDP_MSG_SIZE) { + uint16_t type; + + msg = (struct ldp_msg *)pdu; + type = ntohs(msg->type); + msg_len = ntohs(msg->length); + if (msg_len < LDP_MSG_LEN || (msg_len + LDP_MSG_DEAD_LEN) > pdu_len) { + session_shutdown(nbr, S_BAD_MSG_LEN, msg->id, msg->type); + free(buf); + return; + } + msg_size = msg_len + LDP_MSG_DEAD_LEN; + pdu_len -= msg_size; + + /* check for error conditions earlier */ + switch (type) { + case MSG_TYPE_INIT: + if ((nbr->state != NBR_STA_INITIAL) && + (nbr->state != NBR_STA_OPENSENT)) { + session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); + free(buf); + return; + } + break; + case MSG_TYPE_KEEPALIVE: + if ((nbr->state == NBR_STA_INITIAL) || + (nbr->state == NBR_STA_OPENSENT)) { + session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); + free(buf); + return; + } + break; + case MSG_TYPE_NOTIFICATION: + break; + default: + if (nbr->state != NBR_STA_OPER) { + session_shutdown(nbr, S_SHUTDOWN, msg->id, msg->type); + free(buf); + return; + } + break; + } + + /* switch LDP packet type */ + switch (type) { + case MSG_TYPE_NOTIFICATION: + ret = recv_notification(nbr, pdu, msg_size); + break; + case MSG_TYPE_INIT: + ret = recv_init(nbr, pdu, msg_size); + break; + case MSG_TYPE_KEEPALIVE: + ret = recv_keepalive(nbr, pdu, msg_size); + break; + case MSG_TYPE_CAPABILITY: + ret = recv_capability(nbr, pdu, msg_size); + break; + case MSG_TYPE_ADDR: + case MSG_TYPE_ADDRWITHDRAW: + ret = recv_address(nbr, pdu, msg_size); + break; + case MSG_TYPE_LABELMAPPING: + case MSG_TYPE_LABELREQUEST: + case MSG_TYPE_LABELWITHDRAW: + case MSG_TYPE_LABELRELEASE: + case MSG_TYPE_LABELABORTREQ: + ret = recv_labelmessage(nbr, pdu, msg_size, type); + break; + default: + log_debug("%s: unknown LDP message from nbr %pI4", + __func__, &nbr->id); + if (!(ntohs(msg->type) & UNKNOWN_FLAG)) { + nbr->stats.unknown_msg++; + send_notification(nbr->tcp, S_UNKNOWN_MSG, msg->id, msg->type); + } + /* ignore the message */ + ret = 0; + break; + } + + if (ret == -1) { + /* parser failed, giving up */ + free(buf); + return; + } + + /* no errors - update per neighbor message counters */ + switch (type) { + case MSG_TYPE_NOTIFICATION: + nbr->stats.notif_rcvd++; + break; + case MSG_TYPE_KEEPALIVE: + nbr->stats.kalive_rcvd++; + break; + case MSG_TYPE_CAPABILITY: + nbr->stats.capability_rcvd++; + break; + case MSG_TYPE_ADDR: + nbr->stats.addr_rcvd++; + break; + case MSG_TYPE_ADDRWITHDRAW: + nbr->stats.addrwdraw_rcvd++; + break; + case MSG_TYPE_LABELMAPPING: + nbr->stats.labelmap_rcvd++; + break; + case MSG_TYPE_LABELREQUEST: + nbr->stats.labelreq_rcvd++; + break; + case MSG_TYPE_LABELWITHDRAW: + nbr->stats.labelwdraw_rcvd++; + break; + case MSG_TYPE_LABELRELEASE: + nbr->stats.labelrel_rcvd++; + break; + case MSG_TYPE_LABELABORTREQ: + nbr->stats.labelabreq_rcvd++; + break; + default: + break; + } + + /* Analyse the next message */ + pdu += msg_size; + len -= msg_size; + } + free(buf); + buf = NULL; + if (len != 0) { + session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0); + return; + } + } + + /* shouldn't happen, session_get_pdu should be > 0 if buf was + * allocated - but let's get rid of the SA warning. + */ + free(buf); +} + +static void session_write(struct event *thread) +{ + struct tcp_conn *tcp = EVENT_ARG(thread); + struct nbr *nbr = tcp->nbr; + + tcp->wbuf.ev = NULL; + + if (msgbuf_write(&tcp->wbuf.wbuf) <= 0) + if (errno != EAGAIN && nbr) + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + + if (nbr == NULL && !tcp->wbuf.wbuf.queued) { + /* + * We are done sending the notification message, now we can + * close the socket. + */ + tcp_close(tcp); + return; + } + + evbuf_event_add(&tcp->wbuf); +} + +void +session_shutdown(struct nbr *nbr, uint32_t status, uint32_t msg_id, + uint32_t msg_type) +{ + switch (nbr->state) { + case NBR_STA_PRESENT: + if (nbr_pending_connect(nbr)) + EVENT_OFF(nbr->ev_connect); + break; + case NBR_STA_INITIAL: + case NBR_STA_OPENREC: + case NBR_STA_OPENSENT: + /* update SNMP session counters during initialization */ + leconf->stats.session_attempts++; + send_notification(nbr->tcp, status, msg_id, msg_type); + + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + break; + case NBR_STA_OPER: + send_notification(nbr->tcp, status, msg_id, msg_type); + + nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION); + break; + default: + fatalx("session_shutdown: unknown neighbor state"); + } +} + +void +session_close(struct nbr *nbr) +{ + log_debug("%s: closing session with lsr-id %pI4", __func__, &nbr->id); + + ldp_sync_fsm_nbr_event(nbr, LDP_SYNC_EVT_SESSION_CLOSE); + + tcp_close(nbr->tcp); + nbr_stop_ktimer(nbr); + nbr_stop_ktimeout(nbr); + nbr_stop_itimeout(nbr); +} + +static ssize_t +session_get_pdu(struct ibuf_read *r, char **b) +{ + struct ldp_hdr l; + size_t av, dlen, left; + + av = r->wpos; + if (av < sizeof(l)) + return (0); + + memcpy(&l, r->buf, sizeof(l)); + dlen = ntohs(l.length) + LDP_HDR_DEAD_LEN; + if (dlen > av) + return (0); + + if ((*b = malloc(dlen)) == NULL) + return (-1); + + memcpy(*b, r->buf, dlen); + if (dlen < av) { + left = av - dlen; + memmove(r->buf, r->buf + dlen, left); + r->wpos = left; + } else + r->wpos = 0; + + return (dlen); +} + +struct tcp_conn * +tcp_new(int fd, struct nbr *nbr) +{ + struct tcp_conn *tcp; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + + if ((tcp = calloc(1, sizeof(*tcp))) == NULL) + fatal(__func__); + + tcp->fd = fd; + evbuf_init(&tcp->wbuf, tcp->fd, session_write, tcp); + + if (nbr) { + if ((tcp->rbuf = calloc(1, sizeof(struct ibuf_read))) == NULL) + fatal(__func__); + + event_add_read(master, session_read, nbr, tcp->fd, &tcp->rev); + tcp->nbr = nbr; + } + + if (getsockname(fd, (struct sockaddr *)&ss, &len) != 0) + log_warn("%s: getsockname", __func__); + else + sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->lport); + if (getpeername(fd, (struct sockaddr *)&ss, &len) != 0) + log_warn("%s: getpeername", __func__); + else + sa2addr((struct sockaddr *)&ss, NULL, NULL, &tcp->rport); + + return (tcp); +} + +static void +tcp_close(struct tcp_conn *tcp) +{ + /* try to flush write buffer */ + msgbuf_write(&tcp->wbuf.wbuf); + evbuf_clear(&tcp->wbuf); + + if (tcp->nbr) { + EVENT_OFF(tcp->rev); + free(tcp->rbuf); + tcp->nbr->tcp = NULL; + } + + close(tcp->fd); + accept_unpause(); + free(tcp); +} + +static struct pending_conn * +pending_conn_new(int fd, int af, union ldpd_addr *addr) +{ + struct pending_conn *pconn; + + if ((pconn = calloc(1, sizeof(*pconn))) == NULL) + fatal(__func__); + + pconn->fd = fd; + pconn->af = af; + pconn->addr = *addr; + TAILQ_INSERT_TAIL(&global.pending_conns, pconn, entry); + pconn->ev_timeout = NULL; + event_add_timer(master, pending_conn_timeout, pconn, + PENDING_CONN_TIMEOUT, &pconn->ev_timeout); + + return (pconn); +} + +void +pending_conn_del(struct pending_conn *pconn) +{ + EVENT_OFF(pconn->ev_timeout); + TAILQ_REMOVE(&global.pending_conns, pconn, entry); + free(pconn); +} + +struct pending_conn * +pending_conn_find(int af, union ldpd_addr *addr) +{ + struct pending_conn *pconn; + + TAILQ_FOREACH(pconn, &global.pending_conns, entry) + if (af == pconn->af && ldp_addrcmp(af, addr, &pconn->addr) == 0) + return (pconn); + + return (NULL); +} + +static void pending_conn_timeout(struct event *thread) +{ + struct pending_conn *pconn = EVENT_ARG(thread); + struct tcp_conn *tcp; + + pconn->ev_timeout = NULL; + + log_debug("%s: no adjacency with remote end: %s", __func__, + log_addr(pconn->af, &pconn->addr)); + + /* + * Create a write buffer detached from any neighbor to send a + * notification message reliably. + */ + tcp = tcp_new(pconn->fd, NULL); + send_notification(tcp, S_NO_HELLO, 0, 0); + msgbuf_write(&tcp->wbuf.wbuf); + + pending_conn_del(pconn); +} diff --git a/ldpd/pfkey.c b/ldpd/pfkey.c new file mode 100644 index 0000000..ae771ca --- /dev/null +++ b/ldpd/pfkey.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2003, 2004 Markus Friedl + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __OpenBSD__ +#include +#include +#include +#include +#include +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +static int pfkey_send(int, uint8_t, uint8_t, uint8_t, + int, union ldpd_addr *, union ldpd_addr *, + uint32_t, uint8_t, int, char *, uint8_t, int, char *, + uint16_t, uint16_t); +static int pfkey_reply(int, uint32_t *); +static int pfkey_sa_add(int, union ldpd_addr *, union ldpd_addr *, + uint8_t, char *, uint32_t *); +static int pfkey_sa_remove(int, union ldpd_addr *, union ldpd_addr *, + uint32_t *); +static int pfkey_md5sig_establish(struct nbr *, struct nbr_params *nbrp); +static int pfkey_md5sig_remove(struct nbr *); + +#define PFKEY2_CHUNK sizeof(uint64_t) +#define ROUNDUP(x) (((x) + (PFKEY2_CHUNK - 1)) & ~(PFKEY2_CHUNK - 1)) +#define IOV_CNT 20 + +static uint32_t sadb_msg_seq; +static uint32_t pid; /* should pid_t but pfkey needs uint32_t */ +static int fd; + +static int +pfkey_send(int sd, uint8_t satype, uint8_t mtype, uint8_t dir, + int af, union ldpd_addr *src, union ldpd_addr *dst, uint32_t spi, + uint8_t aalg, int alen, char *akey, uint8_t ealg, int elen, char *ekey, + uint16_t sport, uint16_t dport) +{ + struct sadb_msg smsg; + struct sadb_sa sa; + struct sadb_address sa_src, sa_dst; + struct sadb_key sa_akey, sa_ekey; + struct sadb_spirange sa_spirange; + struct iovec iov[IOV_CNT]; + ssize_t n; + int len = 0; + int iov_cnt; + struct sockaddr_storage smask, dmask; + union sockunion su_src, su_dst; + + if (!pid) + pid = getpid(); + + /* we need clean sockaddr... no ports set */ + memset(&smask, 0, sizeof(smask)); + + addr2sa(af, src, 0, &su_src); + + switch (af) { + case AF_INET: + memset(&((struct sockaddr_in *)&smask)->sin_addr, 0xff, 32/8); + break; + case AF_INET6: + memset(&((struct sockaddr_in6 *)&smask)->sin6_addr, 0xff, + 128/8); + break; + default: + return (-1); + } + smask.ss_family = su_src.sa.sa_family; + smask.ss_len = sockaddr_len(&su_src.sa); + + memset(&dmask, 0, sizeof(dmask)); + + addr2sa(af, dst, 0, &su_dst); + + switch (af) { + case AF_INET: + memset(&((struct sockaddr_in *)&dmask)->sin_addr, 0xff, 32/8); + break; + case AF_INET6: + memset(&((struct sockaddr_in6 *)&dmask)->sin6_addr, 0xff, + 128/8); + break; + default: + return (-1); + } + dmask.ss_family = su_dst.sa.sa_family; + dmask.ss_len = sockaddr_len(&su_dst.sa); + + memset(&smsg, 0, sizeof(smsg)); + smsg.sadb_msg_version = PF_KEY_V2; + smsg.sadb_msg_seq = ++sadb_msg_seq; + smsg.sadb_msg_pid = pid; + smsg.sadb_msg_len = sizeof(smsg) / 8; + smsg.sadb_msg_type = mtype; + smsg.sadb_msg_satype = satype; + + switch (mtype) { + case SADB_GETSPI: + memset(&sa_spirange, 0, sizeof(sa_spirange)); + sa_spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE; + sa_spirange.sadb_spirange_len = sizeof(sa_spirange) / 8; + sa_spirange.sadb_spirange_min = 0x100; + sa_spirange.sadb_spirange_max = 0xffffffff; + sa_spirange.sadb_spirange_reserved = 0; + break; + case SADB_ADD: + case SADB_UPDATE: + case SADB_DELETE: + memset(&sa, 0, sizeof(sa)); + sa.sadb_sa_exttype = SADB_EXT_SA; + sa.sadb_sa_len = sizeof(sa) / 8; + sa.sadb_sa_replay = 0; + sa.sadb_sa_spi = htonl(spi); + sa.sadb_sa_state = SADB_SASTATE_MATURE; + break; + } + + memset(&sa_src, 0, sizeof(sa_src)); + sa_src.sadb_address_exttype = SADB_EXT_ADDRESS_SRC; + sa_src.sadb_address_len = + (sizeof(sa_src) + ROUNDUP(sockaddr_len(&su_src.sa))) / 8; + + memset(&sa_dst, 0, sizeof(sa_dst)); + sa_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST; + sa_dst.sadb_address_len = + (sizeof(sa_dst) + ROUNDUP(sockaddr_len(&su_dst.sa))) / 8; + + sa.sadb_sa_auth = aalg; + sa.sadb_sa_encrypt = SADB_X_EALG_AES; /* XXX */ + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + memset(&sa_akey, 0, sizeof(sa_akey)); + sa_akey.sadb_key_exttype = SADB_EXT_KEY_AUTH; + sa_akey.sadb_key_len = (sizeof(sa_akey) + + ((alen + 7) / 8) * 8) / 8; + sa_akey.sadb_key_bits = 8 * alen; + + memset(&sa_ekey, 0, sizeof(sa_ekey)); + sa_ekey.sadb_key_exttype = SADB_EXT_KEY_ENCRYPT; + sa_ekey.sadb_key_len = (sizeof(sa_ekey) + + ((elen + 7) / 8) * 8) / 8; + sa_ekey.sadb_key_bits = 8 * elen; + + break; + } + + iov_cnt = 0; + + /* msghdr */ + iov[iov_cnt].iov_base = &smsg; + iov[iov_cnt].iov_len = sizeof(smsg); + iov_cnt++; + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + case SADB_DELETE: + /* SA hdr */ + iov[iov_cnt].iov_base = &sa; + iov[iov_cnt].iov_len = sizeof(sa); + smsg.sadb_msg_len += sa.sadb_sa_len; + iov_cnt++; + break; + case SADB_GETSPI: + /* SPI range */ + iov[iov_cnt].iov_base = &sa_spirange; + iov[iov_cnt].iov_len = sizeof(sa_spirange); + smsg.sadb_msg_len += sa_spirange.sadb_spirange_len; + iov_cnt++; + break; + } + + /* dest addr */ + iov[iov_cnt].iov_base = &sa_dst; + iov[iov_cnt].iov_len = sizeof(sa_dst); + iov_cnt++; + iov[iov_cnt].iov_base = &su_dst; + iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_dst.sa)); + smsg.sadb_msg_len += sa_dst.sadb_address_len; + iov_cnt++; + + /* src addr */ + iov[iov_cnt].iov_base = &sa_src; + iov[iov_cnt].iov_len = sizeof(sa_src); + iov_cnt++; + iov[iov_cnt].iov_base = &su_src; + iov[iov_cnt].iov_len = ROUNDUP(sockaddr_len(&su_src.sa)); + smsg.sadb_msg_len += sa_src.sadb_address_len; + iov_cnt++; + + switch (mtype) { + case SADB_ADD: + case SADB_UPDATE: + if (alen) { + /* auth key */ + iov[iov_cnt].iov_base = &sa_akey; + iov[iov_cnt].iov_len = sizeof(sa_akey); + iov_cnt++; + iov[iov_cnt].iov_base = akey; + iov[iov_cnt].iov_len = ((alen + 7) / 8) * 8; + smsg.sadb_msg_len += sa_akey.sadb_key_len; + iov_cnt++; + } + if (elen) { + /* encryption key */ + iov[iov_cnt].iov_base = &sa_ekey; + iov[iov_cnt].iov_len = sizeof(sa_ekey); + iov_cnt++; + iov[iov_cnt].iov_base = ekey; + iov[iov_cnt].iov_len = ((elen + 7) / 8) * 8; + smsg.sadb_msg_len += sa_ekey.sadb_key_len; + iov_cnt++; + } + break; + } + + len = smsg.sadb_msg_len * 8; + do { + n = writev(sd, iov, iov_cnt); + } while (n == -1 && (errno == EAGAIN || errno == EINTR)); + + if (n == -1) { + log_warn("writev (%d/%d)", iov_cnt, len); + return (-1); + } + + return (0); +} + +int +pfkey_read(int sd, struct sadb_msg *h) +{ + struct sadb_msg hdr; + + if (recv(sd, &hdr, sizeof(hdr), MSG_PEEK) != sizeof(hdr)) { + if (errno == EAGAIN || errno == EINTR) + return (0); + log_warn("pfkey peek"); + return (-1); + } + + /* XXX: Only one message can be outstanding. */ + if (hdr.sadb_msg_seq == sadb_msg_seq && hdr.sadb_msg_pid == pid) { + if (h) + *h = hdr; + return (0); + } + + /* not ours, discard */ + if (read(sd, &hdr, sizeof(hdr)) == -1) { + if (errno == EAGAIN || errno == EINTR) + return (0); + log_warn("pfkey read"); + return (-1); + } + + return (1); +} + +static int +pfkey_reply(int sd, uint32_t *spi) +{ + struct sadb_msg hdr, *msg; + struct sadb_ext *ext; + struct sadb_sa *sa; + uint8_t *data; + ssize_t len; + int rv; + + do { + rv = pfkey_read(sd, &hdr); + if (rv == -1) + return (-1); + } while (rv); + + if (hdr.sadb_msg_errno != 0) { + errno = hdr.sadb_msg_errno; + if (errno == ESRCH) + return (0); + else { + log_warn("pfkey"); + return (-1); + } + } + if ((data = reallocarray(NULL, hdr.sadb_msg_len, PFKEY2_CHUNK)) == NULL) { + log_warn("pfkey malloc"); + return (-1); + } + len = hdr.sadb_msg_len * PFKEY2_CHUNK; + if (read(sd, data, len) != len) { + log_warn("pfkey read"); + explicit_bzero(data, len); + free(data); + return (-1); + } + + if (hdr.sadb_msg_type == SADB_GETSPI) { + if (spi == NULL) { + explicit_bzero(data, len); + free(data); + return (0); + } + + msg = (struct sadb_msg *)data; + for (ext = (struct sadb_ext *)(msg + 1); + (size_t)((uint8_t *)ext - (uint8_t *)msg) < + msg->sadb_msg_len * PFKEY2_CHUNK; + ext = (struct sadb_ext *)((uint8_t *)ext + + ext->sadb_ext_len * PFKEY2_CHUNK)) { + if (ext->sadb_ext_type == SADB_EXT_SA) { + sa = (struct sadb_sa *) ext; + *spi = ntohl(sa->sadb_sa_spi); + break; + } + } + } + explicit_bzero(data, len); + free(data); + return (0); +} + +static int +pfkey_sa_add(int af, union ldpd_addr *src, union ldpd_addr *dst, uint8_t keylen, + char *key, uint32_t *spi) +{ + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_GETSPI, 0, + af, src, dst, 0, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, spi) < 0) + return (-1); + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_UPDATE, 0, + af, src, dst, *spi, 0, keylen, key, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, NULL) < 0) + return (-1); + return (0); +} + +static int +pfkey_sa_remove(int af, union ldpd_addr *src, union ldpd_addr *dst, + uint32_t *spi) +{ + if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_DELETE, 0, + af, src, dst, *spi, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0) + return (-1); + if (pfkey_reply(fd, NULL) < 0) + return (-1); + *spi = 0; + return (0); +} + +static int +pfkey_md5sig_establish(struct nbr *nbr, struct nbr_params *nbrp) +{ + sleep(1); + + if (!nbr->auth.spi_out) + if (pfkey_sa_add(nbr->af, &nbr->laddr, &nbr->raddr, + nbrp->auth.md5key_len, nbrp->auth.md5key, + &nbr->auth.spi_out) == -1) + return (-1); + if (!nbr->auth.spi_in) + if (pfkey_sa_add(nbr->af, &nbr->raddr, &nbr->laddr, + nbrp->auth.md5key_len, nbrp->auth.md5key, + &nbr->auth.spi_in) == -1) + return (-1); + + nbr->auth.established = 1; + return (0); +} + +static int +pfkey_md5sig_remove(struct nbr *nbr) +{ + if (nbr->auth.spi_out) + if (pfkey_sa_remove(nbr->af, &nbr->laddr, &nbr->raddr, + &nbr->auth.spi_out) == -1) + return (-1); + if (nbr->auth.spi_in) + if (pfkey_sa_remove(nbr->af, &nbr->raddr, &nbr->laddr, + &nbr->auth.spi_in) == -1) + return (-1); + + nbr->auth.established = 0; + nbr->auth.spi_in = 0; + nbr->auth.spi_out = 0; + nbr->auth.method = AUTH_NONE; + memset(nbr->auth.md5key, 0, sizeof(nbr->auth.md5key)); + + return (0); +} + +int +pfkey_establish(struct nbr *nbr, struct nbr_params *nbrp) +{ + switch (nbr->auth.method) { + case AUTH_MD5SIG: + strlcpy(nbr->auth.md5key, nbrp->auth.md5key, sizeof(nbr->auth.md5key)); + return pfkey_md5sig_establish(nbr, nbrp); + case AUTH_NONE: + return 0; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +int +pfkey_remove(struct nbr *nbr) +{ + if (!nbr->auth.established) + return 0; + + switch (nbr->auth.method) { + case AUTH_MD5SIG: + return pfkey_md5sig_remove(nbr); + case AUTH_NONE: + return 0; + break; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +int +pfkey_init(void) +{ + if ((fd = socket(PF_KEY, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + PF_KEY_V2)) == -1) { + if (errno == EPROTONOSUPPORT) { + log_warnx("PF_KEY not available"); + sysdep.no_pfkey = 1; + return (-1); + } else + fatal("pfkey setup failed"); + } + return (fd); +} +#endif /* __OpenBSD__ */ diff --git a/ldpd/rlfa.c b/ldpd/rlfa.c new file mode 100644 index 0000000..d898a13 --- /dev/null +++ b/ldpd/rlfa.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "ldpd.h" +#include "lde.h" +#include "ldpe.h" +#include "log.h" +#include "ldp_debug.h" +#include "rlfa.h" + +#include + +struct ldp_rlfa_node_head rlfa_node_tree; + +static int ldp_rlfa_client_compare(const struct ldp_rlfa_client *a, + const struct ldp_rlfa_client *b) +{ + if (a->igp.vrf_id < b->igp.vrf_id) + return -1; + if (a->igp.vrf_id > b->igp.vrf_id) + return 1; + + if (a->igp.protocol < b->igp.protocol) + return -1; + if (a->igp.protocol > b->igp.protocol) + return 1; + + if (a->igp.isis.spf.tree_id < b->igp.isis.spf.tree_id) + return -1; + if (a->igp.isis.spf.tree_id > b->igp.isis.spf.tree_id) + return 1; + + if (a->igp.isis.spf.level < b->igp.isis.spf.level) + return -1; + if (a->igp.isis.spf.level > b->igp.isis.spf.level) + return 1; + + return 0; +} +RB_GENERATE(ldp_rlfa_client_head, ldp_rlfa_client, entry, + ldp_rlfa_client_compare) + +static int ldp_rlfa_node_compare(const struct ldp_rlfa_node *a, + const struct ldp_rlfa_node *b) +{ + if (ntohl(a->pq_address.s_addr) < ntohl(b->pq_address.s_addr)) + return -1; + if (ntohl(a->pq_address.s_addr) > ntohl(b->pq_address.s_addr)) + return 1; + + return prefix_cmp(&a->destination, &b->destination); +} +RB_GENERATE(ldp_rlfa_node_head, ldp_rlfa_node, entry, ldp_rlfa_node_compare) + +struct ldp_rlfa_client *rlfa_client_new(struct ldp_rlfa_node *rnode, + struct zapi_rlfa_igp *igp) +{ + struct ldp_rlfa_client *rclient; + + if ((rclient = calloc(1, sizeof(*rclient))) == NULL) + fatal(__func__); + + rclient->igp = *igp; + rclient->node = rnode; + RB_INSERT(ldp_rlfa_client_head, &rnode->clients, rclient); + + return rclient; +} + +void rlfa_client_del(struct ldp_rlfa_client *rclient) +{ + struct ldp_rlfa_node *rnode = rclient->node; + + RB_REMOVE(ldp_rlfa_client_head, &rnode->clients, rclient); + free(rclient); + + /* Delete RLFA node if it's empty. */ + if (RB_EMPTY(ldp_rlfa_client_head, &rnode->clients)) + rlfa_node_del(rnode); +} + +struct ldp_rlfa_client *rlfa_client_find(struct ldp_rlfa_node *rnode, + struct zapi_rlfa_igp *igp) +{ + struct ldp_rlfa_client rclient; + + rclient.igp = *igp; + return RB_FIND(ldp_rlfa_client_head, &rnode->clients, &rclient); +} + +struct ldp_rlfa_node *rlfa_node_new(const struct prefix *destination, + struct in_addr pq_address) +{ + struct ldp_rlfa_node *rnode; + + if ((rnode = calloc(1, sizeof(*rnode))) == NULL) + fatal(__func__); + + rnode->destination = *destination; + rnode->pq_address = pq_address; + rnode->pq_label = MPLS_INVALID_LABEL; + RB_INIT(ldp_rlfa_client_head, &rnode->clients); + RB_INSERT(ldp_rlfa_node_head, &rlfa_node_tree, rnode); + + return rnode; +} + +void rlfa_node_del(struct ldp_rlfa_node *rnode) +{ + /* Delete RLFA clients. */ + while (!RB_EMPTY(ldp_rlfa_client_head, &rnode->clients)) { + struct ldp_rlfa_client *rclient; + + rclient = RB_ROOT(ldp_rlfa_client_head, &rnode->clients); + rlfa_client_del(rclient); + } + + RB_REMOVE(ldp_rlfa_node_head, &rlfa_node_tree, rnode); + free(rnode); +} + +struct ldp_rlfa_node *rlfa_node_find(const struct prefix *destination, + struct in_addr pq_address) +{ + struct ldp_rlfa_node rnode = {}; + + rnode.destination = *destination; + rnode.pq_address = pq_address; + return RB_FIND(ldp_rlfa_node_head, &rlfa_node_tree, &rnode); +} + +void lde_rlfa_client_send(struct ldp_rlfa_client *rclient) +{ + struct ldp_rlfa_node *rnode = rclient->node; + struct zapi_rlfa_response rlfa_labels = {}; + struct fec fec; + struct fec_node *fn; + struct fec_nh *fnh; + int i = 0; + + /* Fill in inner label (allocated by PQ node). */ + rlfa_labels.igp = rclient->igp; + rlfa_labels.destination = rnode->destination; + rlfa_labels.pq_label = rnode->pq_label; + + /* Fill in outer label(s) (allocated by the nexthop routers). */ + fec.type = FEC_TYPE_IPV4; + fec.u.ipv4.prefix = rnode->pq_address; + fec.u.ipv4.prefixlen = IPV4_MAX_BITLEN; + fn = (struct fec_node *)fec_find(&ft, &fec); + if (!fn) + return; + LIST_FOREACH(fnh, &fn->nexthops, entry) { + if (fnh->remote_label == NO_LABEL) + continue; + + rlfa_labels.nexthops[i].family = fnh->af; + switch (fnh->af) { + case AF_INET: + rlfa_labels.nexthops[i].gate.ipv4 = fnh->nexthop.v4; + break; + case AF_INET6: + rlfa_labels.nexthops[i].gate.ipv6 = fnh->nexthop.v6; + break; + default: + continue; + } + rlfa_labels.nexthops[i].label = fnh->remote_label; + i++; + } + rlfa_labels.nexthop_num = i; + + lde_imsg_compose_parent(IMSG_RLFA_LABELS, 0, &rlfa_labels, + sizeof(rlfa_labels)); +} + +void lde_rlfa_label_update(const struct fec *fec) +{ + struct ldp_rlfa_node *rnode; + + if (fec->type != FEC_TYPE_IPV4 + || fec->u.ipv4.prefixlen != IPV4_MAX_BITLEN) + return; + + /* + * TODO: use an rb-tree lookup to restrict the iteration to the RLFAs + * that were effectivelly affected by the label update. + */ + RB_FOREACH (rnode, ldp_rlfa_node_head, &rlfa_node_tree) { + struct ldp_rlfa_client *rclient; + + if (!IPV4_ADDR_SAME(&rnode->pq_address, &fec->u.ipv4.prefix)) + continue; + + RB_FOREACH (rclient, ldp_rlfa_client_head, &rnode->clients) + lde_rlfa_client_send(rclient); + } +} + +void lde_rlfa_check(struct ldp_rlfa_client *rclient) +{ + struct lde_nbr *ln; + struct lde_map *me; + struct fec fec; + union ldpd_addr pq_address = {}; + + pq_address.v4 = rclient->node->pq_address; + ln = lde_nbr_find_by_addr(AF_INET, &pq_address); + if (!ln) + return; + + lde_prefix2fec(&rclient->node->destination, &fec); + me = (struct lde_map *)fec_find(&ln->recv_map, &fec); + if (!me) + return; + + rclient->node->pq_label = me->map.label; + lde_rlfa_client_send(rclient); +} + +/* + * Check if there's any registered RLFA client for this prefix/neighbor (PQ + * node) and notify about the updated label. + */ +void lde_rlfa_update_clients(struct fec *fec, struct lde_nbr *ln, + uint32_t label) +{ + struct prefix rlfa_dest; + struct ldp_rlfa_node *rnode; + + lde_fec2prefix(fec, &rlfa_dest); + rnode = rlfa_node_find(&rlfa_dest, ln->id); + if (rnode) { + struct ldp_rlfa_client *rclient; + + rnode->pq_label = label; + RB_FOREACH (rclient, ldp_rlfa_client_head, &rnode->clients) + lde_rlfa_client_send(rclient); + } else + lde_rlfa_label_update(fec); +} + +void ldpe_rlfa_init(struct ldp_rlfa_client *rclient) +{ + struct tnbr *tnbr; + union ldpd_addr pq_address = {}; + + pq_address.v4 = rclient->node->pq_address; + tnbr = tnbr_find(leconf, AF_INET, &pq_address); + if (tnbr == NULL) { + tnbr = tnbr_new(AF_INET, &pq_address); + tnbr_update(tnbr); + RB_INSERT(tnbr_head, &leconf->tnbr_tree, tnbr); + } + + tnbr->rlfa_count++; +} + +void ldpe_rlfa_exit(struct ldp_rlfa_client *rclient) +{ + struct tnbr *tnbr; + union ldpd_addr pq_address = {}; + + pq_address.v4 = rclient->node->pq_address; + tnbr = tnbr_find(leconf, AF_INET, &pq_address); + if (tnbr) { + tnbr->rlfa_count--; + tnbr_check(leconf, tnbr); + } +} diff --git a/ldpd/rlfa.h b/ldpd/rlfa.h new file mode 100644 index 0000000..4083742 --- /dev/null +++ b/ldpd/rlfa.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _LDPD_RLFA_H_ +#define _LDPD_RLFA_H_ + +#include "openbsd-tree.h" +#include "zclient.h" + +struct ldp_rlfa_client { + RB_ENTRY(ldp_rlfa_client) entry; + + /* IGP instance data. */ + struct zapi_rlfa_igp igp; + + /* Backpointer to RLFA node. */ + struct ldp_rlfa_node *node; +}; +RB_HEAD(ldp_rlfa_client_head, ldp_rlfa_client); +RB_PROTOTYPE(ldp_rlfa_client_head, ldp_rlfa_client, entry, + ldp_rlfa_client_compare); + +struct ldp_rlfa_node { + RB_ENTRY(ldp_rlfa_node) entry; + + /* Destination prefix. */ + struct prefix destination; + + /* PQ node address. */ + struct in_addr pq_address; + + /* RLFA clients. */ + struct ldp_rlfa_client_head clients; + + /* Label allocated by the PQ node to the RLFA destination. */ + mpls_label_t pq_label; +}; +RB_HEAD(ldp_rlfa_node_head, ldp_rlfa_node); +RB_PROTOTYPE(ldp_rlfa_node_head, ldp_rlfa_node, entry, ldp_rlfa_node_compare); + +extern struct ldp_rlfa_node_head rlfa_node_tree; + +/* prototypes */ +struct ldp_rlfa_client *rlfa_client_new(struct ldp_rlfa_node *rnode, + struct zapi_rlfa_igp *igp); +void rlfa_client_del(struct ldp_rlfa_client *rclient); +struct ldp_rlfa_client *rlfa_client_find(struct ldp_rlfa_node *rnode, + struct zapi_rlfa_igp *igp); +struct ldp_rlfa_node *rlfa_node_new(const struct prefix *destination, + struct in_addr pq_address); +void rlfa_node_del(struct ldp_rlfa_node *rnode); +struct ldp_rlfa_node *rlfa_node_find(const struct prefix *destination, + struct in_addr pq_address); +void lde_rlfa_check(struct ldp_rlfa_client *rclient); +void lde_rlfa_client_send(struct ldp_rlfa_client *rclient); +void lde_rlfa_label_update(const struct fec *fec); +void lde_rlfa_update_clients(struct fec *fec, struct lde_nbr *ln, + uint32_t label); +void ldpe_rlfa_init(struct ldp_rlfa_client *rclient); +void ldpe_rlfa_exit(struct ldp_rlfa_client *rclient); + +#endif /* _LDPD_RLFA_H_ */ diff --git a/ldpd/socket.c b/ldpd/socket.c new file mode 100644 index 0000000..71d5c21 --- /dev/null +++ b/ldpd/socket.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2016 Renato Westphal + * Copyright (c) 2009 Michele Marchetto + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include +#include + +#include "ldpd.h" +#include "ldpe.h" +#include "log.h" + +#include "lib/log.h" +#include "privs.h" +#include "sockopt.h" + +extern struct zebra_privs_t ldpd_privs; +extern struct zebra_privs_t ldpe_privs; + +int +ldp_create_socket(int af, enum socket_type type) +{ + int fd, domain, proto; + union ldpd_addr addr; + union sockunion local_su; +#ifdef __OpenBSD__ + int opt; +#endif + + /* create socket */ + switch (type) { + case LDP_SOCKET_DISC: + case LDP_SOCKET_EDISC: + domain = SOCK_DGRAM; + proto = IPPROTO_UDP; + break; + case LDP_SOCKET_SESSION: + domain = SOCK_STREAM; + proto = IPPROTO_TCP; + break; + default: + fatalx("ldp_create_socket: unknown socket type"); + } + fd = socket(af, domain, proto); + if (fd == -1) { + log_warn("%s: error creating socket", __func__); + return (-1); + } + sock_set_nonblock(fd); + sockopt_v6only(af, fd); + + /* bind to a local address/port */ + switch (type) { + case LDP_SOCKET_DISC: + /* listen on all addresses */ + memset(&addr, 0, sizeof(addr)); + addr2sa(af, &addr, LDP_PORT, &local_su); + break; + case LDP_SOCKET_EDISC: + case LDP_SOCKET_SESSION: + addr = (ldp_af_conf_get(ldpd_conf, af))->trans_addr; + addr2sa(af, &addr, LDP_PORT, &local_su); + /* ignore any possible error */ + sock_set_bindany(fd, 1); + break; + } + frr_with_privs(&ldpd_privs) { + if (sock_set_reuse(fd, 1) == -1) { + close(fd); + return (-1); + } + if (bind(fd, &local_su.sa, sockaddr_len(&local_su.sa)) == -1) { + log_warnx("%s: error binding socket: %s", __func__, + safe_strerror(errno)); + close(fd); + return (-1); + } + } + + /* set options */ + switch (af) { + case AF_INET: + if (sock_set_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { + close(fd); + return (-1); + } + if (type == LDP_SOCKET_DISC) { + if (sock_set_ipv4_mcast_ttl(fd, IP_DEFAULT_MULTICAST_TTL) == -1) { + close(fd); + return (-1); + } + if (sock_set_ipv4_mcast_loop(fd) == -1) { + close(fd); + return (-1); + } + } + if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { + if (sock_set_ipv4_recvif(fd, 1) == -1) { + close(fd); + return (-1); + } +#ifndef MSG_MCAST +#if defined(HAVE_IP_PKTINFO) + if (sock_set_ipv4_pktinfo(fd, 1) == -1) { + close(fd); + return (-1); + } +#elif defined(HAVE_IP_RECVDSTADDR) + if (sock_set_ipv4_recvdstaddr(fd, 1) == -1) { + close(fd); + return (-1); + } +#else +#error "Unsupported socket API" +#endif +#endif /* MSG_MCAST */ + } + if (type == LDP_SOCKET_SESSION) { + if (sock_set_ipv4_ucast_ttl(fd, 255) == -1) { + close(fd); + return (-1); + } + } + break; + case AF_INET6: + if (sock_set_ipv6_dscp(fd, IPTOS_PREC_INTERNETCONTROL) == -1) { + close(fd); + return (-1); + } + if (type == LDP_SOCKET_DISC) { + if (sock_set_ipv6_mcast_loop(fd) == -1) { + close(fd); + return (-1); + } + if (sock_set_ipv6_mcast_hops(fd, 255) == -1) { + close(fd); + return (-1); + } + if (!CHECK_FLAG(ldpd_conf->ipv6.flags, F_LDPD_AF_NO_GTSM)) { + /* ignore any possible error */ + sock_set_ipv6_minhopcount(fd, 255); + } + } + if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) { + if (sock_set_ipv6_pktinfo(fd, 1) == -1) { + close(fd); + return (-1); + } + } + if (type == LDP_SOCKET_SESSION) { + if (sock_set_ipv6_ucast_hops(fd, 255) == -1) { + close(fd); + return (-1); + } + } + break; + } + switch (type) { + case LDP_SOCKET_DISC: + case LDP_SOCKET_EDISC: + sock_set_recvbuf(fd); + break; + case LDP_SOCKET_SESSION: + if (listen(fd, LDP_BACKLOG) == -1) + log_warn("%s: error listening on socket", __func__); + +#ifdef __OpenBSD__ + opt = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, sizeof(opt)) == -1) { + if (errno == ENOPROTOOPT) { /* system w/o md5sig */ + log_warnx("md5sig not available, disabling"); + sysdep.no_md5sig = 1; + } else { + close(fd); + return (-1); + } + } +#endif + break; + } + + return (fd); +} + +void +sock_set_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fcntl F_GETFL"); + + SET_FLAG(flags, O_NONBLOCK); + + if (fcntl(fd, F_SETFL, flags) == -1) + fatal("fcntl F_SETFL"); +} + +void +sock_set_cloexec(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFD, 0)) == -1) + fatal("fcntl F_GETFD"); + + SET_FLAG(flags, FD_CLOEXEC); + + if (fcntl(fd, F_SETFD, flags) == -1) + fatal("fcntl F_SETFD"); +} + +void +sock_set_recvbuf(int fd) +{ + int bsize; + + bsize = 65535; + while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize)) == -1) + bsize /= 2; +} + +int +sock_set_reuse(int fd, int enable) +{ + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + log_warn("%s: error setting SO_REUSEADDR", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_bindany(int fd, int enable) +{ +#ifdef HAVE_SO_BINDANY + frr_with_privs(&ldpd_privs) { + if (setsockopt(fd, SOL_SOCKET, SO_BINDANY, &enable, sizeof(int)) < 0) { + log_warn("%s: error setting SO_BINDANY", __func__); + return (-1); + } + } + return (0); +#elif defined(HAVE_IP_FREEBIND) + if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &enable, sizeof(int)) < 0) { + log_warn("%s: error setting IP_FREEBIND", __func__); + return (-1); + } + return (0); +#elif defined(IP_BINDANY) + frr_with_privs(&ldpd_privs) { + if (setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(int)) < 0) { + log_warn("%s: error setting IP_BINDANY", __func__); + return (-1); + } + } + return (0); +#else + log_warnx( + "%s: missing SO_BINDANY, IP_FREEBIND and IP_BINDANY, unable to bind to a nonlocal IP address", + __func__); + return (-1); +#endif /* HAVE_SO_BINDANY */ +} + +#ifndef __OpenBSD__ +/* + * Set MD5 key for the socket, for the given peer address. If the password + * is NULL or zero-length, the option will be disabled. + */ +int +sock_set_md5sig(int fd, int af, union ldpd_addr *addr, const char *password) +{ + int ret = -1; + int save_errno = ENOSYS; +#if HAVE_DECL_TCP_MD5SIG + union sockunion su; +#endif + + if (fd == -1) + return (0); +#if HAVE_DECL_TCP_MD5SIG + addr2sa(af, addr, 0, &su); + + frr_with_privs(&ldpe_privs) { + ret = sockopt_tcp_signature(fd, &su, password); + save_errno = errno; + } +#endif /* HAVE_TCP_MD5SIG */ + if (ret < 0) + log_warnx("%s: can't set TCP_MD5SIG option on fd %d: %s", + __func__, fd, safe_strerror(save_errno)); + + return (ret); +} +#endif + +int +sock_set_ipv4_tos(int fd, int tos) +{ + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) { + log_warn("%s: error setting IP_TOS to 0x%x", __func__, tos); + return (-1); + } + + return (0); +} + +int +sock_set_ipv4_recvif(int fd, ifindex_t enable) +{ + return (setsockopt_ifindex(AF_INET, fd, enable)); +} + +int +sock_set_ipv4_minttl(int fd, int ttl) +{ + return (sockopt_minttl(AF_INET, fd, ttl)); +} + +int +sock_set_ipv4_ucast_ttl(int fd, int ttl) +{ + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) { + log_warn("%s: error setting IP_TTL", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_ipv4_mcast_ttl(int fd, uint8_t ttl) +{ + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)) < 0) { + log_warn("%s: error setting IP_MULTICAST_TTL to %d", __func__, ttl); + return (-1); + } + + return (0); +} + +#ifndef MSG_MCAST +#if defined(HAVE_IP_PKTINFO) +int +sock_set_ipv4_pktinfo(int fd, int enable) +{ + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)) < 0) { + log_warn("%s: error setting IP_PKTINFO", __func__); + return (-1); + } + + return (0); +} +#elif defined(HAVE_IP_RECVDSTADDR) +int +sock_set_ipv4_recvdstaddr(int fd, int enable) +{ + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &enable, sizeof(enable)) < 0) { + log_warn("%s: error setting IP_RECVDSTADDR", __func__); + return (-1); + } + + return (0); +} +#else +#error "Unsupported socket API" +#endif +#endif /* MSG_MCAST */ + +int +sock_set_ipv4_mcast(struct iface *iface) +{ + struct in_addr if_addr; + + if_addr.s_addr = if_get_ipv4_addr(iface); + + if (setsockopt_ipv4_multicast_if(global.ipv4.ldp_disc_socket, + if_addr, iface->ifindex) < 0) { + log_warn("%s: error setting IP_MULTICAST_IF, interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +int +sock_set_ipv4_mcast_loop(int fd) +{ + return (setsockopt_ipv4_multicast_loop(fd, 0)); +} + +int +sock_set_ipv6_dscp(int fd, int dscp) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp)) < 0) { + log_warn("%s: error setting IPV6_TCLASS", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_ipv6_pktinfo(int fd, int enable) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable, sizeof(enable)) < 0) { + log_warn("%s: error setting IPV6_RECVPKTINFO", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_ipv6_minhopcount(int fd, int hoplimit) +{ + return (sockopt_minttl(AF_INET6, fd, hoplimit)); +} + +int +sock_set_ipv6_ucast_hops(int fd, int hoplimit) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &hoplimit, sizeof(hoplimit)) < 0) { + log_warn("%s: error setting IPV6_UNICAST_HOPS", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_ipv6_mcast_hops(int fd, int hoplimit) +{ + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hoplimit, sizeof(hoplimit)) < 0) { + log_warn("%s: error setting IPV6_MULTICAST_HOPS", __func__); + return (-1); + } + + return (0); +} + +int +sock_set_ipv6_mcast(struct iface *iface) +{ + if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6, + IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)) < 0) { + log_warn("%s: error setting IPV6_MULTICAST_IF, interface %s", + __func__, iface->name); + return (-1); + } + + return (0); +} + +int +sock_set_ipv6_mcast_loop(int fd) +{ + unsigned int loop = 0; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &loop, sizeof(loop)) < 0) { + log_warn("%s: error setting IPV6_MULTICAST_LOOP", __func__); + return (-1); + } + + return (0); +} diff --git a/ldpd/subdir.am b/ldpd/subdir.am new file mode 100644 index 0000000..ad5933f --- /dev/null +++ b/ldpd/subdir.am @@ -0,0 +1,67 @@ +# +# ldpd +# + +if LDPD +noinst_LIBRARIES += ldpd/libldp.a +sbin_PROGRAMS += ldpd/ldpd +vtysh_daemons += ldpd +man8 += $(MANBUILD)/frr-ldpd.8 +endif + +ldpd_libldp_a_SOURCES = \ + ldpd/accept.c \ + ldpd/address.c \ + ldpd/adjacency.c \ + ldpd/control.c \ + ldpd/hello.c \ + ldpd/init.c \ + ldpd/interface.c \ + ldpd/keepalive.c \ + ldpd/l2vpn.c \ + ldpd/labelmapping.c \ + ldpd/lde.c \ + ldpd/lde_lib.c \ + ldpd/ldp_debug.c \ + ldpd/ldp_vty_cmds.c \ + ldpd/ldp_vty_conf.c \ + ldpd/ldp_vty_exec.c \ + ldpd/ldp_zebra.c \ + ldpd/ldpe.c \ + ldpd/logmsg.c \ + ldpd/neighbor.c \ + ldpd/notification.c \ + ldpd/packet.c \ + ldpd/pfkey.c \ + ldpd/rlfa.c \ + ldpd/socket.c \ + ldpd/util.c \ + # end + +if SNMP +module_LTLIBRARIES += ldpd/ldpd_snmp.la +endif + +clippy_scan += \ + ldpd/ldp_vty_cmds.c \ + # end + +noinst_HEADERS += \ + ldpd/control.h \ + ldpd/lde.h \ + ldpd/ldp.h \ + ldpd/ldp_debug.h \ + ldpd/ldp_vty.h \ + ldpd/ldpd.h \ + ldpd/ldpe.h \ + ldpd/log.h \ + ldpd/rlfa.h \ + # end + +ldpd_ldpd_SOURCES = ldpd/ldpd.c +ldpd_ldpd_LDADD = ldpd/libldp.a lib/libfrr.la $(LIBCAP) + +ldpd_ldpd_snmp_la_SOURCES = ldpd/ldp_snmp.c +ldpd_ldpd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +ldpd_ldpd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +ldpd_ldpd_snmp_la_LIBADD = lib/libfrrsnmp.la diff --git a/ldpd/util.c b/ldpd/util.c new file mode 100644 index 0000000..85ba25c --- /dev/null +++ b/ldpd/util.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2015 Renato Westphal + * Copyright (c) 2012 Alexander Bluhm + * Copyright (c) 2004 Esben Norby + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include + +#include "ldpd.h" +#include "log.h" + +uint8_t +mask2prefixlen(in_addr_t ina) +{ + if (ina == 0) + return (0); + else + return (33 - ffs(ntohl(ina))); +} + +uint8_t +mask2prefixlen6(struct sockaddr_in6 *sa_in6) +{ + uint8_t l = 0, *ap, *ep; + + /* + * sin6_len is the size of the sockaddr so substract the offset of + * the possibly truncated sin6_addr struct. + */ + ap = (uint8_t *)&sa_in6->sin6_addr; + ep = (uint8_t *)sa_in6 + sockaddr_len((struct sockaddr *)sa_in6); + for (; ap < ep; ap++) { + /* this "beauty" is adopted from sbin/route/show.c ... */ + switch (*ap) { + case 0xff: + l += 8; + break; + case 0xfe: + l += 7; + return (l); + case 0xfc: + l += 6; + return (l); + case 0xf8: + l += 5; + return (l); + case 0xf0: + l += 4; + return (l); + case 0xe0: + l += 3; + return (l); + case 0xc0: + l += 2; + return (l); + case 0x80: + l += 1; + return (l); + case 0x00: + return (l); + default: + fatalx("non contiguous inet6 netmask"); + } + } + + return (l); +} + +in_addr_t +prefixlen2mask(uint8_t prefixlen) +{ + if (prefixlen == 0) + return (0); + + return (htonl(0xffffffff << (32 - prefixlen))); +} + +struct in6_addr * +prefixlen2mask6(uint8_t prefixlen) +{ + static struct in6_addr mask; + int i; + + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < prefixlen / 8; i++) + mask.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + mask.s6_addr[prefixlen / 8] = 0xff00 >> i; + + return (&mask); +} + +void +ldp_applymask(int af, union ldpd_addr *dest, const union ldpd_addr *src, + int prefixlen) +{ + struct in6_addr mask; + int i; + + switch (af) { + case AF_INET: + dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen); + break; + case AF_INET6: + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < prefixlen / 8; i++) + mask.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + mask.s6_addr[prefixlen / 8] = 0xff00 >> i; + + for (i = 0; i < 16; i++) + dest->v6.s6_addr[i] = src->v6.s6_addr[i] & + mask.s6_addr[i]; + break; + default: + fatalx("ldp_applymask: unknown af"); + } +} + +int +ldp_addrcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b) +{ + switch (af) { + case AF_INET: + if (a->v4.s_addr == b->v4.s_addr) + return (0); + return ((ntohl(a->v4.s_addr) > ntohl(b->v4.s_addr)) ? 1 : -1); + case AF_INET6: + return (memcmp(&a->v6, &b->v6, sizeof(struct in6_addr))); + default: + fatalx("ldp_addrcmp: unknown af"); + } +} + +int +ldp_addrisset(int af, const union ldpd_addr *addr) +{ + switch (af) { + case AF_UNSPEC: + return (0); + case AF_INET: + if (addr->v4.s_addr != INADDR_ANY) + return (1); + break; + case AF_INET6: + if (!IN6_IS_ADDR_UNSPECIFIED(&addr->v6)) + return (1); + break; + default: + fatalx("ldp_addrisset: unknown af"); + } + + return (0); +} + +int +ldp_prefixcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b, + uint8_t prefixlen) +{ + in_addr_t mask, aa, ba; + int i; + uint8_t m; + + switch (af) { + case AF_INET: + if (prefixlen == 0) + return (0); + if (prefixlen > IPV4_MAX_BITLEN) + fatalx("ldp_prefixcmp: bad IPv4 prefixlen"); + mask = htonl(prefixlen2mask(prefixlen)); + aa = htonl(a->v4.s_addr) & mask; + ba = htonl(b->v4.s_addr) & mask; + return (aa - ba); + case AF_INET6: + if (prefixlen == 0) + return (0); + if (prefixlen > IPV6_MAX_BITLEN) + fatalx("ldp_prefixcmp: bad IPv6 prefixlen"); + for (i = 0; i < prefixlen / 8; i++) + if (a->v6.s6_addr[i] != b->v6.s6_addr[i]) + return (a->v6.s6_addr[i] - b->v6.s6_addr[i]); + i = prefixlen % 8; + if (i) { + m = 0xff00 >> i; + if ((a->v6.s6_addr[prefixlen / 8] & m) != + (b->v6.s6_addr[prefixlen / 8] & m)) + return ((a->v6.s6_addr[prefixlen / 8] & m) - + (b->v6.s6_addr[prefixlen / 8] & m)); + } + return (0); + default: + fatalx("ldp_prefixcmp: unknown af"); + } + return (-1); +} + +int +bad_addr_v4(struct in_addr addr) +{ + uint32_t a = ntohl(addr.s_addr); + + if (((a >> IN_CLASSA_NSHIFT) == 0) || + ((a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) || + IN_MULTICAST(a) || IN_BADCLASS(a)) + return (1); + + return (0); +} + +int +bad_addr_v6(struct in6_addr *addr) +{ + if (IN6_IS_ADDR_UNSPECIFIED(addr) || + IN6_IS_ADDR_LOOPBACK(addr) || + IN6_IS_ADDR_MULTICAST(addr) || + IN6_IS_ADDR_SITELOCAL(addr) || + IN6_IS_ADDR_V4MAPPED(addr) || + IN6_IS_ADDR_V4COMPAT(addr)) + return (1); + + return (0); +} + +int +bad_addr(int af, union ldpd_addr *addr) +{ + switch (af) { + case AF_INET: + return (bad_addr_v4(addr->v4)); + case AF_INET6: + return (bad_addr_v6(&addr->v6)); + default: + fatalx("bad_addr: unknown af"); + } +} + +void +embedscope(struct sockaddr_in6 *sin6) +{ + uint16_t tmp16; + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { + memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); + if (tmp16 != 0) { + log_warnx("%s: address %s already has embedded scope %u", + __func__, log_sockaddr(sin6), ntohs(tmp16)); + } + tmp16 = htons(sin6->sin6_scope_id); + memcpy(&sin6->sin6_addr.s6_addr[2], &tmp16, sizeof(tmp16)); + sin6->sin6_scope_id = 0; + } +} + +void +recoverscope(struct sockaddr_in6 *sin6) +{ + uint16_t tmp16; + + if (sin6->sin6_scope_id != 0) + log_warnx("%s: address %s already has scope id %u", + __func__, log_sockaddr(sin6), sin6->sin6_scope_id); + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) { + memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16)); + sin6->sin6_scope_id = ntohs(tmp16); + sin6->sin6_addr.s6_addr[2] = 0; + sin6->sin6_addr.s6_addr[3] = 0; + } +} + +void +addscope(struct sockaddr_in6 *sin6, uint32_t id) +{ + if (sin6->sin6_scope_id != 0) + log_warnx("%s: address %s already has scope id %u", __func__, + log_sockaddr(sin6), sin6->sin6_scope_id); + + if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) + sin6->sin6_scope_id = id; +} + +void +clearscope(struct in6_addr *in6) +{ + if (IN6_IS_SCOPE_EMBED(in6)) { + in6->s6_addr[2] = 0; + in6->s6_addr[3] = 0; + } +} + +void +addr2sa(int af, const union ldpd_addr *addr, uint16_t port, union sockunion *su) +{ + struct sockaddr_in *sa_in = &su->sin; + struct sockaddr_in6 *sa_in6 = &su->sin6; + + memset(su, 0, sizeof(*su)); + switch (af) { + case AF_INET: + sa_in->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_in->sin_len = sizeof(struct sockaddr_in); +#endif + sa_in->sin_addr = addr->v4; + sa_in->sin_port = htons(port); + break; + case AF_INET6: + sa_in6->sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_in6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sa_in6->sin6_addr = addr->v6; + sa_in6->sin6_port = htons(port); + break; + default: + fatalx("addr2sa: unknown af"); + } +} + +void +sa2addr(struct sockaddr *sa, int *af, union ldpd_addr *addr, in_port_t *port) +{ + struct sockaddr_in *sa_in = (struct sockaddr_in *)sa; + struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)sa; + + if (addr) + memset(addr, 0, sizeof(*addr)); + switch (sa->sa_family) { + case AF_INET: + if (af) + *af = AF_INET; + if (addr) + addr->v4 = sa_in->sin_addr; + if (port) + *port = sa_in->sin_port; + break; + case AF_INET6: + if (af) + *af = AF_INET6; + if (addr) + addr->v6 = sa_in6->sin6_addr; + if (port) + *port = sa_in6->sin6_port; + break; + default: + fatalx("sa2addr: unknown af"); + } +} + +socklen_t +sockaddr_len(struct sockaddr *sa) +{ +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + return (sa->sa_len); +#else + switch (sa->sa_family) { + case AF_INET: + return (sizeof(struct sockaddr_in)); + case AF_INET6: + return (sizeof(struct sockaddr_in6)); + default: + fatalx("sockaddr_len: unknown af"); + } +#endif +} diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..1c9314b --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,15 @@ +/config_paths.h +/version.c +/version.h +/gitversion.h +/gitversion.h.tmp +/route_types.h +/memtypes.h +/command_lex.c +/command_lex.h +/command_parse.c +/command_parse.h +/grammar_sandbox +/clippy +/defun_lex.c +vtysh_daemons.h diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..62051ac --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. lib/libfrr.la +%: ALWAYS + @$(MAKE) -s -C .. lib/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/lib/admin_group.c b/lib/admin_group.c new file mode 100644 index 0000000..9c2c2c0 --- /dev/null +++ b/lib/admin_group.c @@ -0,0 +1,402 @@ +/* + * Administrative-group library (RFC3630, RFC5305, RFC5329, RFC7308) + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "admin_group.h" +#include "bitfield.h" + +char *admin_group_string(char *out, size_t sz, int indent, + const struct admin_group *ag) +{ + bool printed = false; + size_t index = 2; + int nb_print = 0; + + if (sz < index) + return out; + + if (admin_group_explicit_zero(ag)) { + snprintf(out, sz, "0x00000000"); + return out; + } + + if (admin_group_zero(ag)) { + snprintf(out, sz, "not-set"); + return out; + } + + snprintf(out, sz, "0x"); + for (ssize_t i = ag->bitmap.m - 1; i >= 0; i--) { + if (sz - index <= 0) + break; + if (ag->bitmap.data[i] == 0 && !printed) + continue; + if (nb_print != 0 && (nb_print % 4) == 0) { + snprintf(&out[index], sz - index, "\n%*s", indent, ""); + index += indent + 1; + snprintf(&out[index], sz - index, "0x%08x ", + ag->bitmap.data[i]); + index += 2; + } else + snprintf(&out[index], sz - index, "%08x ", + ag->bitmap.data[i]); + index += 9; + nb_print++; + printed = true; + } + return out; +} + +char *admin_group_standard_print(char *out, int indent, uint32_t bitmap) +{ + bool first = true; + int bit, i; + size_t ret, line_sz = 0, line_max_sz; + + out[0] = '\0'; + + if (bitmap == 0) { + snprintf(out, ADMIN_GROUP_PRINT_MAX_SIZE, "not-set"); + return out; + } + + line_max_sz = strlen("0xffffffff ffffffff ffffffff ffffffff"); + + for (i = 0; i < 32; i++) { + bit = bitmap >> i & 1; + if (bit == 0) + continue; + if (!first) { + ret = snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), + ", "); + line_sz += ret; + } + if (line_sz >= line_max_sz) { + snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), + "\n%*s", indent, ""); + + line_sz = 0; + } + ret = snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), "%d", + i); + line_sz += ret; + first = false; + } + + return out; +} + +char *admin_group_print(char *out, int indent, const struct admin_group *ag) +{ + bool first = true; + uint32_t i; + size_t ret, line_sz = 0, line_max_sz; + + out[0] = '\0'; + + if (admin_group_size(ag) == 0) { + snprintf(out, ADMIN_GROUP_PRINT_MAX_SIZE, "not-set"); + return out; + } + + line_max_sz = strlen("0xffffffff ffffffff ffffffff ffffffff"); + + for (i = 0; i < (admin_group_size(ag) * WORD_SIZE); i++) { + if (!admin_group_get(ag, i)) + continue; + if (!first) { + ret = snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), + ", "); + line_sz += ret; + } + if (line_sz >= line_max_sz) { + snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), + "\n%*s", indent, ""); + + line_sz = 0; + } + ret = snprintf(&out[strlen(out)], + ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out), "%d", + i); + line_sz += ret; + if (ret >= (ADMIN_GROUP_PRINT_MAX_SIZE - strlen(out))) { + out[0] = '\0'; + return out; + } + first = false; + } + + return out; +} + +bool admin_group_cmp(const struct admin_group *ag1, + const struct admin_group *ag2) +{ + size_t i; + + for (i = 0; i < ag1->bitmap.m || i < ag2->bitmap.m; i++) { + if (i >= ag1->bitmap.m) { + if (ag2->bitmap.data[i] != 0) + return false; + } else if (i >= ag2->bitmap.m) { + if (ag1->bitmap.data[i] != 0) + return false; + } else if (memcmp(&ag1->bitmap.data[i], &ag2->bitmap.data[i], + sizeof(word_t)) != 0) + return false; + } + + return true; +} + +void admin_group_copy(struct admin_group *dst, const struct admin_group *src) +{ + assert(bf_is_inited(src->bitmap)); + if (bf_is_inited(dst->bitmap)) + bf_free(dst->bitmap); + dst->bitmap = bf_copy(src->bitmap); +} + +void admin_group_init(struct admin_group *ag) +{ + assert(!bf_is_inited(ag->bitmap)); + bf_init(ag->bitmap, WORD_SIZE); +} + +void admin_group_term(struct admin_group *ag) +{ + assert(bf_is_inited(ag->bitmap)); + bf_free(ag->bitmap); +} + +word_t admin_group_get_offset(const struct admin_group *ag, size_t oct_offset) +{ + assert(bf_is_inited(ag->bitmap)); + if (ag->bitmap.m < oct_offset) + return 0; + return ag->bitmap.data[oct_offset]; +} + +static void admin_group_extend(struct admin_group *ag, size_t idx) +{ + size_t old_m, m; + + old_m = ag->bitmap.m; + m = idx + 1; + ag->bitmap.m = m; + ag->bitmap.data = + XREALLOC(MTYPE_BITFIELD, ag->bitmap.data, m * sizeof(word_t)); + memset(&ag->bitmap.data[old_m], 0, (m - old_m) * sizeof(word_t)); +} + +void admin_group_set(struct admin_group *ag, size_t pos) +{ + size_t idx = bf_index(pos); + + if (idx >= ag->bitmap.m) + admin_group_extend(ag, idx); + + ag->bitmap.data[idx] |= 1 << (bf_offset(pos)); + + if (idx >= ag->bitmap.n) + ag->bitmap.n = idx + 1; +} + +void admin_group_unset(struct admin_group *ag, size_t pos) +{ + if (bf_index(pos) > (ag->bitmap.m - 1)) + return; + bf_release_index(ag->bitmap, pos); + ag->bitmap.n = admin_group_size(ag); +} + +int admin_group_get(const struct admin_group *ag, size_t pos) +{ + size_t admin_group_length = admin_group_size(ag); + uint32_t oct_offset; + size_t idx; + + if (admin_group_length == 0) + return 0; + + idx = bf_index(pos); + + if (idx >= admin_group_length) + return 0; + + oct_offset = admin_group_get_offset(ag, idx); + return oct_offset >> pos & 1; +} + +void admin_group_bulk_set(struct admin_group *ag, uint32_t bitmap, + size_t oct_offset) +{ + + if (bitmap == 0 && oct_offset == 0) { + admin_group_allow_explicit_zero(ag); + return; + } + + if (oct_offset >= ag->bitmap.m) + admin_group_extend(ag, oct_offset); + + ag->bitmap.data[oct_offset] = bitmap; + + if (oct_offset >= ag->bitmap.n) + ag->bitmap.n = oct_offset + 1; +} + +size_t admin_group_size(const struct admin_group *ag) +{ + size_t size = 0; + + for (size_t i = 0; i < ag->bitmap.m; i++) + if (ag->bitmap.data[i] != 0) + size = i + 1; + return size; +} + +size_t admin_group_nb_words(const struct admin_group *ag) +{ + return ag->bitmap.n; +} + +void admin_group_clear(struct admin_group *ag) +{ + for (size_t i = 0; i < ag->bitmap.m; i++) + ag->bitmap.data[i] = 0; + ag->bitmap.n = 0; +} + +bool admin_group_zero(const struct admin_group *ag) +{ + for (size_t i = 0; i < ag->bitmap.m; i++) + if (ag->bitmap.data[i] != 0) + return false; + return true; +} + + +bool admin_group_explicit_zero(const struct admin_group *ag) +{ + return ag->bitmap.n == 1 && ag->bitmap.data[0] == 0; +} + +void admin_group_allow_explicit_zero(struct admin_group *ag) +{ + if (admin_group_zero(ag)) + ag->bitmap.n = 1; +} + +void admin_group_disallow_explicit_zero(struct admin_group *ag) +{ + if (admin_group_zero(ag)) + ag->bitmap.n = 0; +} + +/* link_std_ag: admin-group in the RFC5305 section 3.1 format + * link_ext_ag: admin-group in the RFC7308 format + * RFC7308 specifies in section 2.3.1 that: + * "If both an AG and EAG are present, a receiving node MUST use the AG + * as the first 32 bits (0-31) of administrative color and use the EAG + * for bits 32 and higher, if present." + */ +bool admin_group_match_any(const struct admin_group *fad_ag, + const uint32_t *link_std_ag, + const struct admin_group *link_ext_ag) +{ + size_t fad_ag_sz, link_ag_sz, i; + uint32_t link_ag_bitmap, fad_ag_bitmap; + + assert(fad_ag); + + /* get the size of admin-groups: i.e. number of used words */ + fad_ag_sz = admin_group_size(fad_ag); + if (link_std_ag && link_ext_ag) { + link_ag_sz = admin_group_size(link_ext_ag); + if (link_ag_sz == 0) + link_ag_sz = 1; + } else if (link_std_ag && !link_ext_ag) + link_ag_sz = 1; + else if (!link_std_ag && link_ext_ag) + link_ag_sz = admin_group_size(link_ext_ag); + else + link_ag_sz = 0; + + for (i = 0; i < fad_ag_sz && i < link_ag_sz; i++) { + fad_ag_bitmap = fad_ag->bitmap.data[i]; + if (i == 0 && link_std_ag) + link_ag_bitmap = *link_std_ag; + else + link_ag_bitmap = link_ext_ag->bitmap.data[i]; + + if (fad_ag_bitmap & link_ag_bitmap) + return true; + } + return false; +} + +/* same comments as admin_group_match_any() */ +bool admin_group_match_all(const struct admin_group *fad_ag, + const uint32_t *link_std_ag, + const struct admin_group *link_ext_ag) +{ + size_t fad_ag_sz, link_ag_sz, i; + uint32_t link_ag_bitmap, fad_ag_bitmap; + + assert(fad_ag); + + /* get the size of admin-groups: i.e. number of used words */ + fad_ag_sz = admin_group_size(fad_ag); + if (link_std_ag && link_ext_ag) { + link_ag_sz = admin_group_size(link_ext_ag); + if (link_ag_sz == 0) + link_ag_sz = 1; + } else if (link_std_ag && !link_ext_ag) + link_ag_sz = 1; + else if (!link_std_ag && link_ext_ag) + link_ag_sz = admin_group_size(link_ext_ag); + else + link_ag_sz = 0; + + if (fad_ag_sz > link_ag_sz) + return false; + + for (i = 0; i < fad_ag_sz; i++) { + fad_ag_bitmap = fad_ag->bitmap.data[i]; + if (fad_ag_bitmap == 0) + continue; + + if (i == 0 && link_std_ag) + link_ag_bitmap = *link_std_ag; + else + link_ag_bitmap = link_ext_ag->bitmap.data[i]; + + if ((fad_ag_bitmap & link_ag_bitmap) != fad_ag_bitmap) + return false; + } + return true; +} diff --git a/lib/admin_group.h b/lib/admin_group.h new file mode 100644 index 0000000..60f4a05 --- /dev/null +++ b/lib/admin_group.h @@ -0,0 +1,68 @@ +/* + * Administrative-group library (RFC3630, RFC5305, RFC5329, RFC7308) + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_ADMIN_GROUP_H +#define _FRR_ADMIN_GROUP_H + +#include "zebra.h" +#include "memory.h" +#include "bitfield.h" + +#define ADMIN_GROUP_PRINT_MAX_SIZE 2048 +#define EXT_ADMIN_GROUP_MAX_POSITIONS 1024 + +struct admin_group { + bitfield_t bitmap; +}; + +char *admin_group_string(char *out, size_t sz, int indent, + const struct admin_group *ag); +char *admin_group_standard_print(char *out, int indent, uint32_t bitmap); +char *admin_group_print(char *out, int indent, const struct admin_group *ag); +bool admin_group_cmp(const struct admin_group *ag1, + const struct admin_group *ag2); +void admin_group_copy(struct admin_group *dst, const struct admin_group *src); +void admin_group_init(struct admin_group *ag); +void admin_group_term(struct admin_group *ag); +uint32_t admin_group_get_offset(const struct admin_group *ag, + size_t oct_offset); +void admin_group_set(struct admin_group *ag, size_t pos); +void admin_group_unset(struct admin_group *ag, size_t pos); +int admin_group_get(const struct admin_group *ag, size_t pos); +void admin_group_bulk_set(struct admin_group *ag, uint32_t bitmap, + size_t oct_offset); +size_t admin_group_size(const struct admin_group *ag); +size_t admin_group_nb_words(const struct admin_group *ag); +void admin_group_clear(struct admin_group *ag); +bool admin_group_zero(const struct admin_group *ag); +bool admin_group_explicit_zero(const struct admin_group *ag); +void admin_group_allow_explicit_zero(struct admin_group *ag); +void admin_group_disallow_explicit_zero(struct admin_group *ag); + +bool admin_group_match_any(const struct admin_group *fad_ag, + const uint32_t *link_std_ag, + const struct admin_group *link_ag); +bool admin_group_match_all(const struct admin_group *fad_ag, + const uint32_t *link_std_ag, + const struct admin_group *link_ag); + +#endif /* _FRR_ADMIN_GROUP_H */ diff --git a/lib/affinitymap.c b/lib/affinitymap.c new file mode 100644 index 0000000..6ff8e83 --- /dev/null +++ b/lib/affinitymap.c @@ -0,0 +1,129 @@ +/* + * Affinity map function. + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "linklist.h" +#include "memory.h" +#include "command.h" +#include "vector.h" +#include "prefix.h" +#include "vty.h" +#include "affinitymap.h" +#include "command.h" +#include "log.h" +#include "hash.h" +#include "libfrr.h" +#include "lib_errors.h" +#include "table.h" +#include "json.h" +#include "jhash.h" + +DEFINE_MTYPE_STATIC(LIB, AFFINITY_MAP, "Affinity map"); + +DEFINE_QOBJ_TYPE(affinity_maps); +DEFINE_QOBJ_TYPE(affinity_map); + +struct affinity_maps affinity_map_master = {NULL, NULL}; + +static void affinity_map_free(struct affinity_map *map) +{ + XFREE(MTYPE_AFFINITY_MAP, map); +} + +void affinity_map_set(const char *name, int pos) +{ + struct listnode *node; + struct affinity_map *map; + + if (!affinity_map_master.maps) + affinity_map_master.maps = list_new(); + + for (ALL_LIST_ELEMENTS_RO(affinity_map_master.maps, node, map)) { + if (strncmp(name, map->name, AFFINITY_NAME_SIZE) != 0) + continue; + map->bit_position = pos; + return; + } + + map = XCALLOC(MTYPE_AFFINITY_MAP, sizeof(*map)); + map->bit_position = pos; + snprintf(map->name, sizeof(map->name), "%s", name); + listnode_add(affinity_map_master.maps, map); +} + +void affinity_map_unset(const char *name) +{ + struct listnode *node, *nnode; + struct affinity_map *map; + + if (!affinity_map_master.maps) + return; + + for (ALL_LIST_ELEMENTS(affinity_map_master.maps, node, nnode, map)) { + if (strncmp(name, map->name, AFFINITY_NAME_SIZE) != 0) + continue; + listnode_delete(affinity_map_master.maps, map); + affinity_map_free(map); + return; + } +} + +struct affinity_map *affinity_map_get(const char *name) +{ + struct listnode *node; + struct affinity_map *map; + + if (!affinity_map_master.maps) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(affinity_map_master.maps, node, map)) + if (strncmp(name, map->name, AFFINITY_NAME_SIZE) == 0) + return map; + return NULL; +} + +void affinity_map_update_hook(const char *affmap_name, uint16_t new_pos) +{ + struct affinity_map *map; + + if (!affinity_map_master.update_hook) + return; + + map = affinity_map_get(affmap_name); + + if (!map) + /* Affinity-map creation */ + return; + + (*affinity_map_master.update_hook)(affmap_name, map->bit_position, + new_pos); +} + +void affinity_map_set_update_hook(void (*func)(const char *affmap_name, + uint16_t old_pos, + uint16_t new_pos)) +{ + affinity_map_master.update_hook = func; +} diff --git a/lib/affinitymap.h b/lib/affinitymap.h new file mode 100644 index 0000000..ebe2659 --- /dev/null +++ b/lib/affinitymap.h @@ -0,0 +1,80 @@ +/* + * Affinity-map function. + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _ZEBRA_AFFINITYMAP_H +#define _ZEBRA_AFFINITYMAP_H + +#include "typesafe.h" +#include "prefix.h" +#include "memory.h" +#include "qobj.h" +#include "vty.h" +#include "lib/plist.h" +#include "lib/plist_int.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define AFFINITY_NAME_SIZE 32 + +struct affinity_map { + char name[AFFINITY_NAME_SIZE]; + uint16_t bit_position; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(affinity_map); + +struct affinity_maps { + struct list *maps; + + void (*update_hook)(const char *affmap_name, uint16_t old_pos, + uint16_t new_pos); + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(affinity_maps); + +extern const struct frr_yang_module_info frr_affinity_map_info; +extern const struct frr_yang_module_info frr_affinity_map_cli_info; + +void affinity_map_set(const char *name, int pos); +void affinity_map_unset(const char *name); +struct affinity_map *affinity_map_get(const char *name); + +void affinity_map_update_hook(const char *affmap_name, uint16_t new_pos); + +void affinity_map_set_update_hook(void (*func)(const char *affmap_name, + uint16_t old_pos, + uint16_t new_pos)); + +void affinity_map_init(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_AFFINITYMAP_H */ diff --git a/lib/affinitymap_cli.c b/lib/affinitymap_cli.c new file mode 100644 index 0000000..73b91e7 --- /dev/null +++ b/lib/affinitymap_cli.c @@ -0,0 +1,97 @@ +/* + * Affinity map northbound CLI implementation. + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "lib/command.h" +#include "lib/northbound_cli.h" +#include "lib/affinitymap.h" +#include "lib/affinitymap_cli_clippy.c" + +/* max value is EXT_ADMIN_GROUP_MAX_POSITIONS - 1 */ +DEFPY_YANG_NOSH(affinity_map, affinity_map_cmd, + "affinity-map NAME$name bit-position (0-1023)$position", + "Affinity map configuration\n" + "Affinity attribute name\n" + "Bit position for affinity attribute value\n" + "Bit position\n") +{ + char xpathr[XPATH_MAXLEN]; + + snprintf( + xpathr, sizeof(xpathr), + "/frr-affinity-map:lib/affinity-maps/affinity-map[name='%s']/value", + name); + nb_cli_enqueue_change(vty, xpathr, NB_OP_MODIFY, position_str); + return nb_cli_apply_changes(vty, NULL); +} + +/* max value is EXT_ADMIN_GROUP_MAX_POSITIONS - 1 */ +DEFPY_YANG_NOSH(no_affinity_map, no_affinity_map_cmd, + "no affinity-map NAME$name [bit-position (0-1023)$position]", + NO_STR + "Affinity map configuration\n" + "Affinity attribute name\n" + "Bit position for affinity attribute value\n" + "Bit position\n") +{ + char xpathr[XPATH_MAXLEN]; + + snprintf(xpathr, sizeof(xpathr), + "/frr-affinity-map:lib/affinity-maps/affinity-map[name='%s']", + name); + nb_cli_enqueue_change(vty, xpathr, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static void cli_show_affinity_map(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults __attribute__((__unused__))) +{ + vty_out(vty, "affinity-map %s bit-position %u\n", + yang_dnode_get_string(dnode, "name"), + yang_dnode_get_uint16(dnode, "value")); +} + +const struct frr_yang_module_info frr_affinity_map_cli_info = { + .name = "frr-affinity-map", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-affinity-map:lib/affinity-maps/affinity-map", + .cbs.cli_show = cli_show_affinity_map, + }, + { + .xpath = NULL, + }, + } +}; + +/* Initialization of affinity map vector. */ +void affinity_map_init(void) +{ + /* CLI commands. */ + install_element(CONFIG_NODE, &affinity_map_cmd); + install_element(CONFIG_NODE, &no_affinity_map_cmd); +} diff --git a/lib/affinitymap_northbound.c b/lib/affinitymap_northbound.c new file mode 100644 index 0000000..003e0c1 --- /dev/null +++ b/lib/affinitymap_northbound.c @@ -0,0 +1,110 @@ +/* + * affinity map northbound implementation. + * + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/affinitymap.h" + +/* + * XPath: /frr-affinity-map:lib/affinity-maps/affinity-map + */ + +static int lib_affinity_map_create(struct nb_cb_create_args *args) +{ + return NB_OK; +} + +static int lib_affinity_map_destroy(struct nb_cb_destroy_args *args) +{ + const char *name; + + name = yang_dnode_get_string((const struct lyd_node *)args->dnode, + "./name"); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + affinity_map_unset(name); + break; + } + return NB_OK; +} + +/* + * XPath: /frr-affinity-map:lib/affinity-maps/affinity-map/value + */ +static int lib_affinity_map_value_modify(struct nb_cb_modify_args *args) +{ + const char *name; + uint16_t pos; + + name = yang_dnode_get_string( + (const struct lyd_node *)args->dnode->parent, "./name"); + + pos = yang_dnode_get_uint16( + (const struct lyd_node *)args->dnode->parent, "./value"); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + affinity_map_update_hook(name, pos); + affinity_map_set(name, pos); + break; + } + + return NB_OK; +} + +/* clang-format off */ +const struct frr_yang_module_info frr_affinity_map_info = { + .name = "frr-affinity-map", + .nodes = { + { + .xpath = "/frr-affinity-map:lib/affinity-maps/affinity-map", + .cbs = { + .create = lib_affinity_map_create, + .destroy = lib_affinity_map_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1, + }, + { + .xpath = "/frr-affinity-map:lib/affinity-maps/affinity-map/value", + .cbs = { + .modify = lib_affinity_map_value_modify, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/lib/agentx.c b/lib/agentx.c new file mode 100644 index 0000000..19f2a6b --- /dev/null +++ b/lib/agentx.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* SNMP support + * Copyright (C) 2012 Vincent Bernat + */ + +#include +#include + +#ifdef SNMP_AGENTX +#include +#include +#include +#include +#include + +#include "command.h" +#include "smux.h" +#include "memory.h" +#include "linklist.h" +#include "lib/version.h" +#include "lib_errors.h" +#include "hook.h" +#include "libfrr.h" +#include "xref.h" +#include "lib/libagentx.h" + +XREF_SETUP(); + +DEFINE_HOOK(agentx_enabled, (), ()); + +//bool agentx_enabled = false; + +static struct event_loop *agentx_tm; +static struct event *timeout_thr = NULL; +static struct list *events = NULL; + +static void agentx_events_update(void); + +static void agentx_timeout(struct event *t) +{ + snmp_timeout(); + run_alarms(); + netsnmp_check_outstanding_agent_requests(); + agentx_events_update(); +} + +static void agentx_read(struct event *t) +{ + netsnmp_large_fd_set lfds; + int flags, new_flags = 0; + int nonblock = false; + struct listnode *ln = EVENT_ARG(t); + struct event **thr = listgetdata(ln); + XFREE(MTYPE_TMP, thr); + list_delete_node(events, ln); + + /* fix for non blocking socket */ + flags = fcntl(EVENT_FD(t), F_GETFL, 0); + if (-1 == flags) { + flog_err(EC_LIB_SYSTEM_CALL, "Failed to get FD settings fcntl: %s(%d)", + strerror(errno), errno); + return; + } + + if (flags & O_NONBLOCK) + nonblock = true; + else + new_flags = fcntl(EVENT_FD(t), F_SETFL, flags | O_NONBLOCK); + + if (new_flags == -1) + flog_err(EC_LIB_SYSTEM_CALL, "Failed to set snmp fd non blocking: %s(%d)", + strerror(errno), errno); + + netsnmp_large_fd_set_init(&lfds, FD_SETSIZE); + netsnmp_large_fd_setfd(t->u.fd, &lfds); + snmp_read2(&lfds); + + /* Reset the flag */ + if (!nonblock) { + new_flags = fcntl(EVENT_FD(t), F_SETFL, flags); + + if (new_flags == -1) + flog_err( + EC_LIB_SYSTEM_CALL, + "Failed to set snmp fd back to original settings: %s(%d)", + strerror(errno), errno); + } + + netsnmp_check_outstanding_agent_requests(); + agentx_events_update(); + netsnmp_large_fd_set_cleanup(&lfds); +} + +static void agentx_events_update(void) +{ + int maxfd = 0; + int block = 1; + struct timeval timeout = {.tv_sec = 0, .tv_usec = 0}; + netsnmp_large_fd_set lfds; + struct listnode *ln; + struct event **thr; + int fd, thr_fd; + + event_cancel(&timeout_thr); + + netsnmp_large_fd_set_init(&lfds, FD_SETSIZE); + snmp_select_info2(&maxfd, &lfds, &timeout, &block); + + if (!block) { + event_add_timer_tv(agentx_tm, agentx_timeout, NULL, &timeout, + &timeout_thr); + } + + ln = listhead(events); + thr = ln ? listgetdata(ln) : NULL; + thr_fd = thr ? EVENT_FD(*thr) : -1; + + /* "two-pointer" / two-list simultaneous iteration + * ln/thr/thr_fd point to the next existing event listener to hit while + * fd counts to catch up */ + for (fd = 0; fd < maxfd; fd++) { + /* caught up */ + if (thr_fd == fd) { + struct listnode *nextln = listnextnode(ln); + if (!netsnmp_large_fd_is_set(fd, &lfds)) { + event_cancel(thr); + XFREE(MTYPE_TMP, thr); + list_delete_node(events, ln); + } + ln = nextln; + thr = ln ? listgetdata(ln) : NULL; + thr_fd = thr ? EVENT_FD(*thr) : -1; + } + /* need listener, but haven't hit one where it would be */ + else if (netsnmp_large_fd_is_set(fd, &lfds)) { + struct listnode *newln; + + thr = XCALLOC(MTYPE_TMP, sizeof(struct event *)); + newln = listnode_add_before(events, ln, thr); + event_add_read(agentx_tm, agentx_read, newln, fd, thr); + } + } + + /* leftover event listeners at this point have fd > maxfd, delete them + */ + while (ln) { + struct listnode *nextln = listnextnode(ln); + thr = listgetdata(ln); + event_cancel(thr); + XFREE(MTYPE_TMP, thr); + list_delete_node(events, ln); + ln = nextln; + } + netsnmp_large_fd_set_cleanup(&lfds); +} + +/* Logging NetSNMP messages */ +static int agentx_log_callback(int major, int minor, void *serverarg, + void *clientarg) +{ + struct snmp_log_message *slm = (struct snmp_log_message *)serverarg; + char *msg = XSTRDUP(MTYPE_TMP, slm->msg); + if (msg) + msg[strlen(msg) - 1] = '\0'; + switch (slm->priority) { + case LOG_EMERG: + flog_err(EC_LIB_SNMP, "snmp[emerg]: %s", msg ? msg : slm->msg); + break; + case LOG_ALERT: + flog_err(EC_LIB_SNMP, "snmp[alert]: %s", msg ? msg : slm->msg); + break; + case LOG_CRIT: + flog_err(EC_LIB_SNMP, "snmp[crit]: %s", msg ? msg : slm->msg); + break; + case LOG_ERR: + flog_err(EC_LIB_SNMP, "snmp[err]: %s", msg ? msg : slm->msg); + break; + case LOG_WARNING: + flog_warn(EC_LIB_SNMP, "snmp[warning]: %s", + msg ? msg : slm->msg); + break; + case LOG_NOTICE: + zlog_notice("snmp[notice]: %s", msg ? msg : slm->msg); + break; + case LOG_INFO: + zlog_info("snmp[info]: %s", msg ? msg : slm->msg); + break; + case LOG_DEBUG: + zlog_debug("snmp[debug]: %s", msg ? msg : slm->msg); + break; + } + XFREE(MTYPE_TMP, msg); + return SNMP_ERR_NOERROR; +} + +static int agentx_cli_on(void) +{ + if (!agentx_enabled) { + init_snmp(FRR_SMUX_NAME); + events = list_new(); + agentx_events_update(); + agentx_enabled = true; + hook_call(agentx_enabled); + } + + return 1; +} + +static int agentx_cli_off(void) +{ + if (!agentx_enabled) + return 1; + return 0; +} + +static int smux_disable(void) +{ + agentx_enabled = false; + + return 0; +} + +bool smux_enabled(void) +{ + return agentx_enabled; +} + +void smux_init(struct event_loop *tm) +{ + agentx_tm = tm; + + hook_register(agentx_cli_enabled, agentx_cli_on); + hook_register(agentx_cli_disabled, agentx_cli_off); + + netsnmp_enable_subagent(); + snmp_disable_log(); + snmp_enable_calllog(); + snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING, + agentx_log_callback, NULL); + init_agent(FRR_SMUX_NAME); + + hook_register(frr_early_fini, smux_disable); +} + +void smux_agentx_enable(void) +{ + if (!agentx_enabled) { + init_snmp(FRR_SMUX_NAME); + events = list_new(); + agentx_events_update(); + agentx_enabled = true; + } +} + +void smux_register_mib(const char *descr, struct variable *var, size_t width, + int num, oid name[], size_t namelen) +{ + register_mib(descr, var, width, num, name, namelen); +} + +void smux_trap(struct variable *vp, size_t vp_len, const oid *ename, + size_t enamelen, const oid *name, size_t namelen, + const oid *iname, size_t inamelen, + const struct trap_object *trapobj, size_t trapobjlen, + uint8_t sptrap) +{ + struct index_oid trap_index[1]; + + /* copy the single index into the multi-index format */ + oid_copy(trap_index[0].indexname, iname, inamelen); + trap_index[0].indexlen = inamelen; + + smux_trap_multi_index(vp, vp_len, ename, enamelen, name, namelen, + trap_index, array_size(trap_index), trapobj, + trapobjlen, sptrap); +} + +int smux_trap_multi_index(struct variable *vp, size_t vp_len, const oid *ename, + size_t enamelen, const oid *name, size_t namelen, + struct index_oid *iname, size_t index_len, + const struct trap_object *trapobj, size_t trapobjlen, + uint8_t sptrap) +{ + oid objid_snmptrap[] = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0}; + size_t objid_snmptrap_len = sizeof(objid_snmptrap) / sizeof(oid); + oid notification_oid[MAX_OID_LEN]; + size_t notification_oid_len; + unsigned int i; + + netsnmp_variable_list *notification_vars = NULL; + if (!agentx_enabled) + return 0; + + /* snmpTrapOID */ + oid_copy(notification_oid, ename, enamelen); + notification_oid[enamelen] = sptrap; + notification_oid_len = enamelen + 1; + snmp_varlist_add_variable(¬ification_vars, objid_snmptrap, + objid_snmptrap_len, ASN_OBJECT_ID, + (uint8_t *)notification_oid, + notification_oid_len * sizeof(oid)); + + /* Provided bindings */ + for (i = 0; i < trapobjlen; i++) { + unsigned int j; + oid oid[MAX_OID_LEN]; + size_t oid_len, onamelen; + uint8_t *val; + size_t val_len; + WriteMethod *wm = NULL; + struct variable cvp; + unsigned int iindex; + /* + * this allows the behaviour of smux_trap with a singe index + * for all objects to be maintained whilst allowing traps which + * have different indices per object to be supported + */ + iindex = (index_len == 1) ? 0 : i; + + /* Make OID. */ + if (trapobj[i].namelen > 0) { + /* Columnar object */ + onamelen = trapobj[i].namelen; + oid_copy(oid, name, namelen); + oid_copy(oid + namelen, trapobj[i].name, onamelen); + oid_copy(oid + namelen + onamelen, + iname[iindex].indexname, + iname[iindex].indexlen); + oid_len = namelen + onamelen + iname[iindex].indexlen; + } else { + /* Scalar object */ + onamelen = trapobj[i].namelen * (-1); + oid_copy(oid, name, namelen); + oid_copy(oid + namelen, trapobj[i].name, onamelen); + oid[onamelen + namelen] = 0; + oid_len = namelen + onamelen + 1; + } + + /* Locate the appropriate function and type in the MIB registry. + */ + for (j = 0; j < vp_len; j++) { + if (oid_compare(trapobj[i].name, onamelen, vp[j].name, + vp[j].namelen) + != 0) + continue; + /* We found the appropriate variable in the MIB + * registry. */ + oid_copy(cvp.name, name, namelen); + oid_copy(cvp.name + namelen, vp[j].name, vp[j].namelen); + cvp.namelen = namelen + vp[j].namelen; + cvp.type = vp[j].type; + cvp.magic = vp[j].magic; + cvp.acl = vp[j].acl; + cvp.findVar = vp[j].findVar; + + /* Grab the result. */ + val = cvp.findVar(&cvp, oid, &oid_len, 1, &val_len, + &wm); + if (!val) + break; + snmp_varlist_add_variable(¬ification_vars, oid, + oid_len, vp[j].type, val, + val_len); + break; + } + } + + + send_v2trap(notification_vars); + snmp_free_varbind(notification_vars); + agentx_events_update(); + return 1; +} + +void smux_events_update(void) +{ + agentx_events_update(); +} + +#endif /* SNMP_AGENTX */ diff --git a/lib/agg_table.c b/lib/agg_table.c new file mode 100644 index 0000000..9cf277f --- /dev/null +++ b/lib/agg_table.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Aggregate Route + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include "zebra.h" + +#include "agg_table.h" + + +static struct route_node *agg_node_create(route_table_delegate_t *delegate, + struct route_table *table) +{ + struct agg_node *node; + + node = XCALLOC(MTYPE_TMP, sizeof(struct agg_node)); + + return agg_node_to_rnode(node); +} + +static void agg_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, struct route_node *node) + +{ + struct agg_node *anode = agg_node_from_rnode(node); + + XFREE(MTYPE_TMP, anode); +} + +static route_table_delegate_t agg_table_delegate = { + .create_node = agg_node_create, + .destroy_node = agg_node_destroy, +}; + +struct agg_table *agg_table_init(void) +{ + struct agg_table *at; + + at = XCALLOC(MTYPE_TMP, sizeof(struct agg_table)); + + at->route_table = route_table_init_with_delegate(&agg_table_delegate); + route_table_set_info(at->route_table, at); + + return at; +} diff --git a/lib/agg_table.h b/lib/agg_table.h new file mode 100644 index 0000000..af1e6b6 --- /dev/null +++ b/lib/agg_table.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * agg_table - Aggregate Table Header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __AGG_TABLE_H__ +#define __AGG_TABLE_H__ + +#include "prefix.h" +#include "table.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct agg_table { + struct route_table *route_table; + + void *info; +}; + +struct agg_node { + /* + * Caution these must be the very first fields + * @see agg_node_to_rnode + * @see agg_node_from_rnode + */ + ROUTE_NODE_FIELDS + + /* Aggregation. */ + void *aggregate; +}; + +static inline struct route_node *agg_node_to_rnode(struct agg_node *node) +{ + return (struct route_node *)node; +} + +static inline struct agg_node *agg_node_from_rnode(struct route_node *node) +{ + return (struct agg_node *)node; +} + +static inline struct agg_node *agg_lock_node(struct agg_node *node) +{ + return (struct agg_node *)route_lock_node(agg_node_to_rnode(node)); +} + +static inline void agg_unlock_node(struct agg_node *node) +{ + route_unlock_node(agg_node_to_rnode(node)); +} + +static inline void agg_set_table_info(struct agg_table *atable, void *data) +{ + atable->info = data; +} + +static inline void *agg_get_table_info(struct agg_table *atable) +{ + return atable->info; +} + +static inline struct agg_node *agg_route_top(struct agg_table *table) +{ + return agg_node_from_rnode(route_top(table->route_table)); +} + +static inline struct agg_node *agg_route_next(struct agg_node *node) +{ + return agg_node_from_rnode(route_next(agg_node_to_rnode(node))); +} + +static inline struct agg_node *agg_node_get(struct agg_table *table, + const struct prefix *p) +{ + return agg_node_from_rnode(route_node_get(table->route_table, p)); +} + +static inline struct agg_node * +agg_node_lookup(const struct agg_table *const table, const struct prefix *p) +{ + return agg_node_from_rnode(route_node_lookup(table->route_table, p)); +} + +static inline struct agg_node *agg_route_next_until(struct agg_node *node, + struct agg_node *limit) +{ + struct route_node *rnode; + + rnode = route_next_until(agg_node_to_rnode(node), + agg_node_to_rnode(limit)); + + return agg_node_from_rnode(rnode); +} + +static inline struct agg_node *agg_node_match(struct agg_table *table, + const struct prefix *p) +{ + return agg_node_from_rnode(route_node_match(table->route_table, p)); +} + +static inline struct agg_node *agg_node_parent(struct agg_node *node) +{ + struct route_node *rn = agg_node_to_rnode(node); + + return agg_node_from_rnode(rn->parent); +} + +static inline struct agg_node *agg_node_left(struct agg_node *node) +{ + struct route_node *rn = agg_node_to_rnode(node); + + return agg_node_from_rnode(rn->l_left); +} + +static inline struct agg_node *agg_node_right(struct agg_node *node) +{ + struct route_node *rn = agg_node_to_rnode(node); + + return agg_node_from_rnode(rn->l_right); +} + +extern struct agg_table *agg_table_init(void); + +static inline void agg_table_finish(struct agg_table *atable) +{ + route_table_finish(atable->route_table); + atable->route_table = NULL; + + XFREE(MTYPE_TMP, atable); +} + +static inline struct agg_node *agg_route_table_top(struct agg_node *node) +{ + return (struct agg_node *)route_top(node->table); +} + +static inline struct agg_table *agg_get_table(struct agg_node *node) +{ + return (struct agg_table *)route_table_get_info(node->table); +} + +static inline const struct prefix * +agg_node_get_prefix(const struct agg_node *node) +{ + return &node->p; +} + +static inline unsigned int agg_node_get_lock_count(const struct agg_node *node) +{ + return node->lock; +} + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pRN" (struct agg_node *) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/asn.c b/lib/asn.c new file mode 100644 index 0000000..4042f58 --- /dev/null +++ b/lib/asn.c @@ -0,0 +1,270 @@ +/* + * ASN functions + * + * Copyright 2022 6WIND + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include "log.h" +#include "asn.h" + +static bool relax_as_zero; + +static const struct message asnotation_mode_msg[] = { + {ASNOTATION_PLAIN, "plain"}, + {ASNOTATION_DOT, "dot"}, + {ASNOTATION_DOTPLUS, "dot+"}, + {ASNOTATION_UNDEFINED, "undefined"}, + {0} +}; + +/* converts a string into an Autonomous system number + * "1.1" => 65536 + * "65500" => 65500 + */ +static bool asn_str2asn_internal(const char *asstring, as_t *asn, + const char **next, bool *partial, + enum asnotation_mode *mode) +{ + uint32_t high = 0, low = 0; + uint64_t temp_val; + const char *p = asstring; + bool ret = false; + uint32_t digit; + enum asnotation_mode val = ASNOTATION_PLAIN; + + if (!asstring) + goto end; + + if (!isdigit((unsigned char)*p)) + goto end; + + /* leading zero is forbidden */ + if (*p == '0' && isdigit((unsigned char)*(p + 1))) + goto end; + + temp_val = 0; + while (isdigit((unsigned char)*p)) { + digit = (*p) - '0'; + temp_val *= 10; + temp_val += digit; + if (temp_val > UINT32_MAX) + /* overflow */ + goto end; + p++; + } + high = (uint32_t)temp_val; + if (*p == '.') { /* dot format */ + p++; + + if (*p == '\0' && partial) { + *partial = true; + goto end; + } + + /* leading zero is forbidden */ + if (*p == '0' && isdigit((unsigned char)*(p + 1))) + goto end; + + temp_val = 0; + while (isdigit((unsigned char)*p)) { + digit = (*p) - '0'; + temp_val *= 10; + temp_val += digit; + if (temp_val > UINT16_MAX) + /* overflow */ + goto end; + p++; + } + low = (uint32_t)temp_val; + + if (!next && *p != '\0' && !isdigit((unsigned char)*p)) + goto end; + /* AS . is forbidden */ + if (high > UINT16_MAX) + goto end; + /* AS 0.0 is authorised for some case only */ + if (!relax_as_zero && high == 0 && low == 0) { + if (partial) + *partial = true; + goto end; + } + if (asn) + *asn = (high << 16) + low; + ret = true; + if (high == 0) + val = ASNOTATION_DOTPLUS; + else + val = ASNOTATION_DOT; + goto end; + } + /* AS 0 is forbidden */ + if (!relax_as_zero && high == 0) + goto end; + if (!asn) { + ret = true; + goto end; + } + *asn = high; + ret = true; + end: + if (next) + *next = p; + if (mode) + *mode = val; + return ret; +} + +static void asn_asn2asdot(as_t asn, char *asstring, size_t len) +{ + uint16_t low, high; + + high = (asn >> 16) & 0xffff; + low = asn & 0xffff; + snprintf(asstring, len, "%hu.%hu", high, low); +} + +bool asn_str2asn(const char *asstring, as_t *asn) +{ + return asn_str2asn_internal(asstring, asn, NULL, NULL, NULL); +} + +const char *asn_asn2asplain(as_t asn) +{ + static char buf[ASN_STRING_MAX_SIZE]; + + snprintf(buf, sizeof(buf), "%u", asn); + return buf; +} + +const char *asn_str2asn_parse(const char *asstring, as_t *asn, bool *found_ptr) +{ + const char *p = NULL; + const char **next = &p; + bool found; + + found = asn_str2asn_internal(asstring, asn, next, NULL, NULL); + if (found_ptr) + *found_ptr = found; + return *next; +} + +void asn_relax_as_zero(bool relax) +{ + relax_as_zero = relax; +} + +enum match_type asn_str2asn_match(const char *str) +{ + bool found, partial = false; + + found = asn_str2asn_internal(str, NULL, NULL, &partial, NULL); + if (found && !partial) + return exact_match; + + if (partial) + return partly_match; + + return no_match; +} + +bool asn_str2asn_notation(const char *asstring, as_t *asn, + enum asnotation_mode *asnotation) +{ + return asn_str2asn_internal(asstring, asn, NULL, NULL, asnotation); +} + +const char *asn_mode2str(enum asnotation_mode asnotation) +{ + return lookup_msg(asnotation_mode_msg, asnotation, + "Unrecognized AS notation mode"); +} + +void asn_asn2json(json_object *json, const char *attr, + as_t asn, enum asnotation_mode asnotation) +{ + static char as_str[ASN_STRING_MAX_SIZE]; + + if ((asnotation == ASNOTATION_PLAIN) || + ((asnotation == ASNOTATION_DOT) && asn < UINT16_MAX)) + json_object_int_add(json, attr, asn); + else { + asn_asn2asdot(asn, as_str, sizeof(as_str)); + json_object_string_add(json, attr, as_str); + } +} + +void asn_asn2json_array(json_object *jseg_list, as_t asn, + enum asnotation_mode asnotation) +{ + static char as_str[ASN_STRING_MAX_SIZE]; + + if ((asnotation == ASNOTATION_PLAIN) || + ((asnotation == ASNOTATION_DOT) && asn < UINT16_MAX)) + json_object_array_add(jseg_list, + json_object_new_int64(asn)); + else { + asn_asn2asdot(asn, as_str, sizeof(as_str)); + json_array_string_add(jseg_list, as_str); + } +} + +char *asn_asn2string(const as_t *asn, char *buf, size_t len, + enum asnotation_mode asnotation) +{ + if ((asnotation == ASNOTATION_PLAIN) || + ((asnotation == ASNOTATION_DOT) && *asn < UINT16_MAX)) + snprintf(buf, len, "%u", *asn); + else + asn_asn2asdot(*asn, buf, len); + return buf; +} + +static ssize_t printfrr_asnotation(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr, + enum asnotation_mode asnotation) +{ + /* for alignemnt up to 33 chars - %33pASD for instance - */ + char as_str[ASN_STRING_MAX_SIZE*3]; + const as_t *asn; + + if (!ptr) + return bputs(buf, "(null)"); + asn = ptr; + asn_asn2string(asn, as_str, sizeof(as_str), asnotation); + return bputs(buf, as_str); +} + +printfrr_ext_autoreg_p("ASP", printfrr_asplain); +static ssize_t printfrr_asplain(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_asnotation(buf, ea, ptr, ASNOTATION_PLAIN); +} + +printfrr_ext_autoreg_p("ASD", printfrr_asdot); +static ssize_t printfrr_asdot(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_asnotation(buf, ea, ptr, ASNOTATION_DOT); +} + +printfrr_ext_autoreg_p("ASE", printfrr_asdotplus); +static ssize_t printfrr_asdotplus(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + return printfrr_asnotation(buf, ea, ptr, ASNOTATION_DOTPLUS); +} diff --git a/lib/asn.h b/lib/asn.h new file mode 100644 index 0000000..a7394fa --- /dev/null +++ b/lib/asn.h @@ -0,0 +1,81 @@ +/* + * AS number structure + * Copyright 2022 6WIND + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FRR_ASN_H +#define _FRR_ASN_H + +#include "zebra.h" +#include "command_match.h" +#include "json.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ASN_STRING_MAX_SIZE 12 + +enum asnotation_mode { + ASNOTATION_PLAIN = 0, + ASNOTATION_DOT, + ASNOTATION_DOTPLUS, + ASNOTATION_UNDEFINED, +}; + +typedef uint32_t as_t; + +extern bool asn_str2asn(const char *asstring, as_t *asn); +extern const char *asn_asn2asplain(as_t asn); +extern const char *asn_str2asn_parse(const char *asstring, as_t *asn, + bool *found_ptr); +extern enum match_type asn_str2asn_match(const char *str); +extern bool asn_str2asn_notation(const char *asstring, as_t *asn, + enum asnotation_mode *asnotation); +extern const char *asn_mode2str(enum asnotation_mode asnotation); +void asn_asn2json_array(json_object *jseg_list, as_t asn, + enum asnotation_mode asnotation); +void asn_asn2json(json_object *jseg_list, const char *attr, + as_t asn, enum asnotation_mode asnotation); +extern char *asn_asn2string(const as_t *as, char *buf, size_t len, + enum asnotation_mode asnotation); +/* display AS in appropriate format */ +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pASP" (as_t *) +#pragma FRR printfrr_ext "%pASD" (as_t *) +#pragma FRR printfrr_ext "%pASE" (as_t *) +#endif + +#define ASN_FORMAT(mode) \ + ((mode == ASNOTATION_DOT) ? "%pASD" : \ + ((mode == ASNOTATION_DOTPLUS) ? "%pASE" : \ + "%pASP")) +#define ASN_FORMAT_SPACE(mode) \ + ((mode == ASNOTATION_DOT) \ + ? "%11pASD" \ + : ((mode == ASNOTATION_DOTPLUS) ? "%11pASE" : "%11pASP")) + +/* for test */ +extern void asn_relax_as_zero(bool relax); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ASN_H */ diff --git a/lib/assert/assert.h b/lib/assert/assert.h new file mode 100644 index 0000000..97c7460 --- /dev/null +++ b/lib/assert/assert.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2021 David Lamparter, for NetDEF, Inc. + */ + +/* WARNING: this file is "special" in that it overrides the system-provided + * assert.h by being on the include path before it. That means it should + * provide the functional equivalent. + * + * This is intentional because FRR extends assert() to write to the log and + * add backtraces. Overriding the entire file is the simplest and most + * reliable way to get this to work; there were problems previously with the + * system assert.h getting included afterwards and redefining assert() back to + * the system variant. + */ + +#ifndef _FRR_ASSERT_H +#define _FRR_ASSERT_H + +#include "xref.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __cplusplus +/* C++ has this built-in, but C provides it in assert.h for >=C11. Since we + * replace assert.h entirely, we need to provide it here too. + */ +#define static_assert _Static_assert +#endif + +struct xref_assert { + struct xref xref; + + const char *expr; + const char *extra, *args; +}; + +extern void _zlog_assert_failed(const struct xref_assert *xref, + const char *extra, ...) PRINTFRR(2, 3) + __attribute__((noreturn)); + +/* the "do { } while (expr_)" is there to get a warning for assignments inside + * the assert expression aka "assert(x = 1)". The (necessary) braces around + * expr_ in the if () statement would suppress these warnings. Since + * _zlog_assert_failed() is noreturn, the while condition will never be + * checked. + */ +#define assert(expr_) \ + ({ \ + static const struct xref_assert _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_ASSERT, NULL, __func__), \ + .expr = #expr_, \ + }; \ + XREF_LINK(_xref.xref); \ + if (__builtin_expect((expr_) ? 0 : 1, 0)) \ + do { \ + _zlog_assert_failed(&_xref, NULL); \ + } while (expr_); \ + }) + +#define assertf(expr_, extra_, ...) \ + ({ \ + static const struct xref_assert _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_ASSERT, NULL, __func__), \ + .expr = #expr_, \ + .extra = extra_, \ + .args = #__VA_ARGS__, \ + }; \ + XREF_LINK(_xref.xref); \ + if (__builtin_expect((expr_) ? 0 : 1, 0)) \ + do { \ + _zlog_assert_failed(&_xref, extra_, \ + ##__VA_ARGS__); \ + } while (expr_); \ + }) + +#define zassert assert + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ASSERT_H */ diff --git a/lib/atomlist.c b/lib/atomlist.c new file mode 100644 index 0000000..5c361d3 --- /dev/null +++ b/lib/atomlist.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "atomlist.h" + +void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item) +{ + atomptr_t prevval; + atomptr_t i = atomptr_i(item); + + atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); + + /* updating ->last is possible here, but makes the code considerably + * more complicated... let's not. + */ + prevval = ATOMPTR_NULL; + item->next = ATOMPTR_NULL; + + /* head-insert atomically + * release barrier: item + item->next writes must be completed + */ + while (!atomic_compare_exchange_weak_explicit(&h->first, &prevval, i, + memory_order_release, memory_order_relaxed)) + atomic_store_explicit(&item->next, prevval, + memory_order_relaxed); +} + +void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item) +{ + atomptr_t prevval = ATOMPTR_NULL; + atomptr_t i = atomptr_i(item); + atomptr_t hint; + struct atomlist_item *prevptr; + _Atomic atomptr_t *prev; + + item->next = ATOMPTR_NULL; + + atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); + + /* place new item into ->last + * release: item writes completed; acquire: DD barrier on hint + */ + hint = atomic_exchange_explicit(&h->last, i, memory_order_acq_rel); + + while (1) { + if (atomptr_p(hint) == NULL) + prev = &h->first; + else + prev = &atomlist_itemp(hint)->next; + + do { + prevval = atomic_load_explicit(prev, + memory_order_consume); + prevptr = atomlist_itemp(prevval); + if (prevptr == NULL) + break; + + prev = &prevptr->next; + } while (prevptr); + + /* last item is being deleted - start over */ + if (atomptr_l(prevval)) { + hint = ATOMPTR_NULL; + continue; + } + + /* no barrier - item->next is NULL and was so in xchg above */ + if (!atomic_compare_exchange_strong_explicit(prev, &prevval, i, + memory_order_consume, + memory_order_consume)) { + hint = prevval; + continue; + } + break; + } +} + +static void atomlist_del_core(struct atomlist_head *h, + struct atomlist_item *item, + _Atomic atomptr_t *hint, + atomptr_t next) +{ + _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd; + atomptr_t prevval, updval; + struct atomlist_item *prevptr; + + /* drop us off "last" if needed. no r/w to barrier. */ + prevval = atomptr_i(item); + atomic_compare_exchange_strong_explicit(&h->last, &prevval, + ATOMPTR_NULL, + memory_order_relaxed, memory_order_relaxed); + + atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed); + + /* the following code should be identical (except sort<>list) to + * atomsort_del_hint() + */ + while (1) { + upd = NULL; + updval = ATOMPTR_LOCK; + + do { + prevval = atomic_load_explicit(prev, + memory_order_consume); + + /* track the beginning of a chain of deleted items + * this is necessary to make this lock-free; we can + * complete deletions started by other threads. + */ + if (!atomptr_l(prevval)) { + updval = prevval; + upd = prev; + } + + prevptr = atomlist_itemp(prevval); + if (prevptr == item) + break; + + prev = &prevptr->next; + } while (prevptr); + + if (prevptr != item) + /* another thread completed our deletion */ + return; + + if (!upd || atomptr_l(updval)) { + /* failed to find non-deleted predecessor... + * have to try again + */ + prev = &h->first; + continue; + } + + if (!atomic_compare_exchange_strong_explicit(upd, &updval, + next, memory_order_consume, + memory_order_consume)) { + /* prev doesn't point to item anymore, something + * was inserted. continue at same position forward. + */ + continue; + } + break; + } +} + +void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item, + _Atomic atomptr_t *hint) +{ + atomptr_t next; + + /* mark ourselves in-delete - full barrier */ + next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, + memory_order_acquire); + assert(!atomptr_l(next)); /* delete race on same item */ + + atomlist_del_core(h, item, hint, next); +} + +struct atomlist_item *atomlist_pop(struct atomlist_head *h) +{ + struct atomlist_item *item; + atomptr_t next; + + /* grab head of the list - and remember it in replval for the + * actual delete below. No matter what, the head of the list is + * where we start deleting because either it's our item, or it's + * some delete-marked items and then our item. + */ + next = atomic_load_explicit(&h->first, memory_order_consume); + + do { + item = atomlist_itemp(next); + if (!item) + return NULL; + + /* try to mark deletion */ + next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, + memory_order_acquire); + + } while (atomptr_l(next)); + /* if loop is taken: delete race on same item (another pop or del) + * => proceed to next item + * if loop exited here: we have our item selected and marked + */ + atomlist_del_core(h, item, &h->first, next); + return item; +} + +struct atomsort_item *atomsort_add(struct atomsort_head *h, + struct atomsort_item *item, int (*cmpfn)( + const struct atomsort_item *, + const struct atomsort_item *)) +{ + _Atomic atomptr_t *prev; + atomptr_t prevval; + atomptr_t i = atomptr_i(item); + struct atomsort_item *previtem; + int cmpval; + + do { + prev = &h->first; + + do { + prevval = atomic_load_explicit(prev, + memory_order_acquire); + previtem = atomptr_p(prevval); + + if (!previtem || (cmpval = cmpfn(previtem, item)) > 0) + break; + if (cmpval == 0) + return previtem; + + prev = &previtem->next; + } while (1); + + if (atomptr_l(prevval)) + continue; + + item->next = prevval; + if (atomic_compare_exchange_strong_explicit(prev, &prevval, i, + memory_order_release, memory_order_relaxed)) + break; + } while (1); + + atomic_fetch_add_explicit(&h->count, 1, memory_order_relaxed); + return NULL; +} + +static void atomsort_del_core(struct atomsort_head *h, + struct atomsort_item *item, _Atomic atomptr_t *hint, + atomptr_t next) +{ + _Atomic atomptr_t *prev = hint ? hint : &h->first, *upd; + atomptr_t prevval, updval; + struct atomsort_item *prevptr; + + atomic_fetch_sub_explicit(&h->count, 1, memory_order_relaxed); + + /* the following code should be identical (except sort<>list) to + * atomlist_del_core() + */ + while (1) { + upd = NULL; + updval = ATOMPTR_LOCK; + + do { + prevval = atomic_load_explicit(prev, + memory_order_consume); + + /* track the beginning of a chain of deleted items + * this is necessary to make this lock-free; we can + * complete deletions started by other threads. + */ + if (!atomptr_l(prevval)) { + updval = prevval; + upd = prev; + } + + prevptr = atomsort_itemp(prevval); + if (prevptr == item) + break; + + prev = &prevptr->next; + } while (prevptr); + + if (prevptr != item) + /* another thread completed our deletion */ + return; + + if (!upd || atomptr_l(updval)) { + /* failed to find non-deleted predecessor... + * have to try again + */ + prev = &h->first; + continue; + } + + if (!atomic_compare_exchange_strong_explicit(upd, &updval, + next, memory_order_relaxed, + memory_order_relaxed)) { + /* prev doesn't point to item anymore, something + * was inserted. continue at same position forward. + */ + continue; + } + break; + } +} + +void atomsort_del_hint(struct atomsort_head *h, struct atomsort_item *item, + _Atomic atomptr_t *hint) +{ + atomptr_t next; + + /* mark ourselves in-delete - full barrier */ + next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, + memory_order_seq_cst); + assert(!atomptr_l(next)); /* delete race on same item */ + + atomsort_del_core(h, item, hint, next); +} + +struct atomsort_item *atomsort_pop(struct atomsort_head *h) +{ + struct atomsort_item *item; + atomptr_t next; + + /* grab head of the list - and remember it in replval for the + * actual delete below. No matter what, the head of the list is + * where we start deleting because either it's our item, or it's + * some delete-marked items and then our item. + */ + next = atomic_load_explicit(&h->first, memory_order_consume); + + do { + item = atomsort_itemp(next); + if (!item) + return NULL; + + /* try to mark deletion */ + next = atomic_fetch_or_explicit(&item->next, ATOMPTR_LOCK, + memory_order_acquire); + + } while (atomptr_l(next)); + /* if loop is taken: delete race on same item (another pop or del) + * => proceed to next item + * if loop exited here: we have our item selected and marked + */ + atomsort_del_core(h, item, &h->first, next); + return item; +} diff --git a/lib/atomlist.h b/lib/atomlist.h new file mode 100644 index 0000000..3eb498a --- /dev/null +++ b/lib/atomlist.h @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ATOMLIST_H +#define _FRR_ATOMLIST_H + +#include "typesafe.h" +#ifndef _TYPESAFE_EXPAND_MACROS +#include "frratomic.h" +#endif /* _TYPESAFE_EXPAND_MACROS */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* pointer with lock/deleted/invalid bit in lowest bit + * + * for atomlist/atomsort, "locked" means "this pointer can't be updated, the + * item is being deleted". it is permissible to assume the item will indeed + * be deleted (as there are no replace/etc. ops in this). + * + * in general, lowest 2/3 bits on 32/64bit architectures are available for + * uses like this; the only thing that will really break this is putting an + * atomlist_item in a struct with "packed" attribute. (it'll break + * immediately and consistently.) -- don't do that. + * + * ATOMPTR_USER is currently unused (and available for atomic hash or skiplist + * implementations.) + */ + +/* atomic_atomptr_t may look a bit odd, it's for the sake of C++ compat */ +typedef uintptr_t atomptr_t; +typedef atomic_uintptr_t atomic_atomptr_t; + +#define ATOMPTR_MASK (UINTPTR_MAX - 3) +#define ATOMPTR_LOCK (1) +#define ATOMPTR_USER (2) +#define ATOMPTR_NULL (0) + +static inline atomptr_t atomptr_i(void *val) +{ + atomptr_t atomval = (atomptr_t)val; + + assert(!(atomval & ATOMPTR_LOCK)); + return atomval; +} +static inline void *atomptr_p(atomptr_t val) +{ + return (void *)(val & ATOMPTR_MASK); +} +static inline bool atomptr_l(atomptr_t val) +{ + return (bool)(val & ATOMPTR_LOCK); +} +static inline bool atomptr_u(atomptr_t val) +{ + return (bool)(val & ATOMPTR_USER); +} + + +/* the problem with, find(), find_gteq() and find_lt() on atomic lists is that + * they're neither an "acquire" nor a "release" operation; the element that + * was found is still on the list and doesn't change ownership. Therefore, + * an atomic transition in ownership state can't be implemented. + * + * Contrast this with add() or pop(): both function calls atomically transfer + * ownership of an item to or from the list, which makes them "acquire" / + * "release" operations. + * + * What can be implemented atomically is a "find_pop()", i.e. try to locate an + * item and atomically try to remove it if found. It's not currently + * implemented but can be added when needed. + * + * Either way - for find(), generally speaking, if you need to use find() on + * a list then the whole thing probably isn't well-suited to atomic + * implementation and you'll need to have extra locks around to make it work + * correctly. + */ +#ifdef WNO_ATOMLIST_UNSAFE_FIND +# define atomic_find_warn +#else +# define atomic_find_warn __attribute__((_DEPRECATED( \ + "WARNING: find() on atomic lists cannot be atomic by principle; " \ + "check code to make sure usage pattern is OK and if it is, use " \ + "#define WNO_ATOMLIST_UNSAFE_FIND"))) +#endif + + +/* single-linked list, unsorted/arbitrary. + * can be used as queue with add_tail / pop + * + * all operations are lock-free, but not necessarily wait-free. this means + * that there is no state where the system as a whole stops making process, + * but it *is* possible that a *particular* thread is delayed by some time. + * + * the only way for this to happen is for other threads to continuously make + * updates. an inactive / blocked / deadlocked other thread cannot cause such + * delays, and to cause such delays a thread must be heavily hitting the list - + * it's a rather theoretical concern. + */ + +/* don't use these structs directly */ +struct atomlist_item { + atomic_uintptr_t next; +}; +#define atomlist_itemp(val) ((struct atomlist_item *)atomptr_p(val)) + +struct atomlist_head { + atomic_uintptr_t first, last; + atomic_size_t count; +}; + +/* use as: + * + * PREDECL_ATOMLIST(namelist); + * struct name { + * struct namelist_item nlitem; + * } + * DECLARE_ATOMLIST(namelist, struct name, nlitem); + */ +#define PREDECL_ATOMLIST(prefix) \ +struct prefix ## _head { struct atomlist_head ah; }; \ +struct prefix ## _item { struct atomlist_item ai; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_ATOMLIST(var) { } + +#define DECLARE_ATOMLIST(prefix, type, field) \ +macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ +{ atomlist_add_head(&h->ah, &item->field.ai); } \ +macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ +{ atomlist_add_tail(&h->ah, &item->field.ai); } \ +macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \ + atomic_atomptr_t *hint) \ +{ atomlist_del_hint(&h->ah, &item->field.ai, hint); } \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ atomlist_del_hint(&h->ah, &item->field.ai, NULL); \ + /* TODO: Return NULL if not found */ \ + return item; } \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ char *p = (char *)atomlist_pop(&h->ah); \ + return p ? (type *)(p - offsetof(type, field)) : NULL; } \ +macro_inline type *prefix ## _first(struct prefix##_head *h) \ +{ char *p = atomptr_p(atomic_load_explicit(&h->ah.first, \ + memory_order_acquire)); \ + return p ? (type *)(p - offsetof(type, field)) : NULL; } \ +macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \ +{ char *p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \ + memory_order_acquire)); \ + return p ? (type *)(p - offsetof(type, field)) : NULL; } \ +macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ return item ? prefix##_next(h, item) : NULL; } \ +macro_inline size_t prefix ## _count(struct prefix##_head *h) \ +{ return atomic_load_explicit(&h->ah.count, memory_order_relaxed); } \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + assert(prefix ## _count(h) == 0); \ + memset(h, 0, sizeof(*h)); \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +/* add_head: + * - contention on ->first pointer + * - return implies completion + */ +void atomlist_add_head(struct atomlist_head *h, struct atomlist_item *item); + +/* add_tail: + * - concurrent add_tail can cause wait but has progress guarantee + * - return does NOT imply completion. completion is only guaranteed after + * all other add_tail operations that started before this add_tail have + * completed as well. + */ +void atomlist_add_tail(struct atomlist_head *h, struct atomlist_item *item); + +/* del/del_hint: + * + * OWNER MUST HOLD REFERENCE ON ITEM TO BE DELETED, ENSURING NO OTHER THREAD + * WILL TRY TO DELETE THE SAME ITEM. DELETING INCLUDES pop(). + * + * as with all deletions, threads that started reading earlier may still hold + * pointers to the deleted item. completion is however guaranteed for all + * reads starting later. + */ +void atomlist_del_hint(struct atomlist_head *h, struct atomlist_item *item, + atomic_atomptr_t *hint); + +/* pop: + * + * as with all deletions, threads that started reading earlier may still hold + * pointers to the deleted item. completion is however guaranteed for all + * reads starting later. + */ +struct atomlist_item *atomlist_pop(struct atomlist_head *h); + + + +struct atomsort_item { + atomic_atomptr_t next; +}; +#define atomsort_itemp(val) ((struct atomsort_item *)atomptr_p(val)) + +struct atomsort_head { + atomic_atomptr_t first; + atomic_size_t count; +}; + +#define _PREDECL_ATOMSORT(prefix) \ +struct prefix ## _head { struct atomsort_head ah; }; \ +struct prefix ## _item { struct atomsort_item ai; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_ATOMSORT_UNIQ(var) { } +#define INIT_ATOMSORT_NONUNIQ(var) { } + +#define _DECLARE_ATOMSORT(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + assert(h->ah.count == 0); \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + struct atomsort_item *p; \ + p = atomsort_add(&h->ah, &item->field.ai, cmpfn_uq); \ + return container_of_null(p, type, field.ai); \ +} \ +macro_inline type *prefix ## _first(struct prefix##_head *h) \ +{ \ + struct atomsort_item *p; \ + p = atomptr_p(atomic_load_explicit(&h->ah.first, \ + memory_order_acquire)); \ + return container_of_null(p, type, field.ai); \ +} \ +macro_inline type *prefix ## _next(struct prefix##_head *h, type *item) \ +{ \ + struct atomsort_item *p; \ + p = atomptr_p(atomic_load_explicit(&item->field.ai.next, \ + memory_order_acquire)); \ + return container_of_null(p, type, field.ai); \ +} \ +macro_inline type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + return item ? prefix##_next(h, item) : NULL; \ +} \ +atomic_find_warn \ +macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ + const type *item) \ +{ \ + type *p = prefix ## _first(h); \ + while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \ + p = prefix ## _next(h, p); \ + return p; \ +} \ +atomic_find_warn \ +macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ + const type *item) \ +{ \ + type *p = prefix ## _first(h), *prev = NULL; \ + while (p && cmpfn_nuq(&p->field.ai, &item->field.ai) < 0) \ + p = prefix ## _next(h, (prev = p)); \ + return prev; \ +} \ +macro_inline void prefix ## _del_hint(struct prefix##_head *h, type *item, \ + atomic_atomptr_t *hint) \ +{ \ + atomsort_del_hint(&h->ah, &item->field.ai, hint); \ +} \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + atomsort_del_hint(&h->ah, &item->field.ai, NULL); \ + /* TODO: Return NULL if not found */ \ + return item; \ +} \ +macro_inline size_t prefix ## _count(struct prefix##_head *h) \ +{ \ + return atomic_load_explicit(&h->ah.count, memory_order_relaxed); \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct atomsort_item *p = atomsort_pop(&h->ah); \ + return p ? container_of(p, type, field.ai) : NULL; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_ATOMSORT_UNIQ(prefix) \ + _PREDECL_ATOMSORT(prefix) +#define DECLARE_ATOMSORT_UNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct atomsort_item *a, \ + const struct atomsort_item *b) \ +{ \ + return cmpfn(container_of(a, type, field.ai), \ + container_of(b, type, field.ai)); \ +} \ + \ +_DECLARE_ATOMSORT(prefix, type, field, \ + prefix ## __cmp, prefix ## __cmp); \ + \ +atomic_find_warn \ +macro_inline type *prefix ## _find(struct prefix##_head *h, const type *item) \ +{ \ + type *p = prefix ## _first(h); \ + int cmpval = 0; \ + while (p && (cmpval = cmpfn(p, item)) < 0) \ + p = prefix ## _next(h, p); \ + if (!p || cmpval > 0) \ + return NULL; \ + return p; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_ATOMSORT_NONUNIQ(prefix) \ + _PREDECL_ATOMSORT(prefix) +#define DECLARE_ATOMSORT_NONUNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct atomsort_item *a, \ + const struct atomsort_item *b) \ +{ \ + return cmpfn(container_of(a, type, field.ai), \ + container_of(b, type, field.ai)); \ +} \ +macro_inline int prefix ## __cmp_uq(const struct atomsort_item *a, \ + const struct atomsort_item *b) \ +{ \ + int cmpval = cmpfn(container_of(a, type, field.ai), \ + container_of(b, type, field.ai)); \ + if (cmpval) \ + return cmpval; \ + if (a < b) \ + return -1; \ + if (a > b) \ + return 1; \ + return 0; \ +} \ + \ +_DECLARE_ATOMSORT(prefix, type, field, \ + prefix ## __cmp, prefix ## __cmp_uq); \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +struct atomsort_item *atomsort_add(struct atomsort_head *h, + struct atomsort_item *item, int (*cmpfn)( + const struct atomsort_item *, + const struct atomsort_item *)); + +void atomsort_del_hint(struct atomsort_head *h, + struct atomsort_item *item, atomic_atomptr_t *hint); + +struct atomsort_item *atomsort_pop(struct atomsort_head *h); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ATOMLIST_H */ diff --git a/lib/base64.c b/lib/base64.c new file mode 100644 index 0000000..00dd35f --- /dev/null +++ b/lib/base64.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: NONE +/* + * This is part of the libb64 project, and has been placed in the public domain. + * For details, see http://sourceforge.net/projects/libb64 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "base64.h" +#include "compiler.h" + +static const char *ENCODING = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +void base64_init_encodestate(struct base64_encodestate *state_in) +{ + state_in->step = step_A; + state_in->result = 0; +} + +char base64_encode_value(char value_in) +{ + if (value_in > 63) + return '='; + return ENCODING[(int)value_in]; +} + +int base64_encode_block(const char *plaintext_in, int length_in, char *code_out, + struct base64_encodestate *state_in) +{ + const char *plainchar = plaintext_in; + const char *const plaintextend = plaintext_in + length_in; + char *codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) { + while (1) { + fallthrough; + case step_A: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + fallthrough; + case step_B: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + fallthrough; + case step_C: + if (plainchar == plaintextend) { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char *code_out, struct base64_encodestate *state_in) +{ + char *codechar = code_out; + + switch (state_in->step) { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + + return codechar - code_out; +} + + +signed char base64_decode_value(signed char value_in) +{ + static const signed char decoding[] = { + 62, -1, -1, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, -2, -1, -1, -1, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51 + }; + value_in -= 43; + if (value_in < 0 || value_in >= 80) + return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(struct base64_decodestate *state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char *code_in, int length_in, char *plaintext_out, + struct base64_decodestate *state_in) +{ + const char *codec = code_in; + char *plainc = plaintext_out; + signed char fragmt; + + *plainc = state_in->plainchar; + + switch (state_in->step) { + while (1) { + fallthrough; + case step_a: + do { + if (codec == code_in+length_in) { + state_in->step = step_a; + state_in->plainchar = *plainc; + return plainc - plaintext_out; + } + fragmt = base64_decode_value(*codec++); + } while (fragmt < 0); + *plainc = (fragmt & 0x03f) << 2; + fallthrough; + case step_b: + do { + if (codec == code_in+length_in) { + state_in->step = step_b; + state_in->plainchar = *plainc; + return plainc - plaintext_out; + } + fragmt = base64_decode_value(*codec++); + } while (fragmt < 0); + *plainc++ |= (fragmt & 0x030) >> 4; + *plainc = (fragmt & 0x00f) << 4; + fallthrough; + case step_c: + do { + if (codec == code_in+length_in) { + state_in->step = step_c; + state_in->plainchar = *plainc; + return plainc - plaintext_out; + } + fragmt = base64_decode_value(*codec++); + } while (fragmt < 0); + *plainc++ |= (fragmt & 0x03c) >> 2; + *plainc = (fragmt & 0x003) << 6; + fallthrough; + case step_d: + do { + if (codec == code_in+length_in) { + state_in->step = step_d; + state_in->plainchar = *plainc; + return plainc - plaintext_out; + } + fragmt = base64_decode_value(*codec++); + } while (fragmt < 0); + *plainc++ |= (fragmt & 0x03f); + } + } + /* control should not reach here */ + return plainc - plaintext_out; +} diff --git a/lib/base64.h b/lib/base64.h new file mode 100644 index 0000000..9bf4ace --- /dev/null +++ b/lib/base64.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: NONE +/* + * This is part of the libb64 project, and has been placed in the public domain. + * For details, see http://sourceforge.net/projects/libb64 + */ + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +enum base64_encodestep { + step_A, step_B, step_C +}; + +struct base64_encodestate { + enum base64_encodestep step; + char result; +}; + +void base64_init_encodestate(struct base64_encodestate *state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char *plaintext_in, int length_in, char *code_out, + struct base64_encodestate *state_in); + +int base64_encode_blockend(char *code_out, struct base64_encodestate *state_in); + + +enum base64_decodestep { + step_a, step_b, step_c, step_d +}; + +struct base64_decodestate { + enum base64_decodestep step; + char plainchar; +}; + +void base64_init_decodestate(struct base64_decodestate *state_in); + +signed char base64_decode_value(signed char value_in); + +int base64_decode_block(const char *code_in, int length_in, char *plaintext_out, + struct base64_decodestate *state_in); + +#endif /* _BASE64_H_ */ diff --git a/lib/bfd.c b/lib/bfd.c new file mode 100644 index 0000000..2222bb9 --- /dev/null +++ b/lib/bfd.c @@ -0,0 +1,1336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bfd.c: BFD handling routines + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "frrevent.h" +#include "stream.h" +#include "vrf.h" +#include "zclient.h" +#include "libfrr.h" +#include "table.h" +#include "vty.h" +#include "bfd.h" + +DEFINE_MTYPE_STATIC(LIB, BFD_INFO, "BFD info"); +DEFINE_MTYPE_STATIC(LIB, BFD_SOURCE, "BFD source cache"); + +/** + * BFD protocol integration configuration. + */ + +/** Events definitions. */ +enum bfd_session_event { + /** Remove the BFD session configuration. */ + BSE_UNINSTALL, + /** Install the BFD session configuration. */ + BSE_INSTALL, +}; + +/** + * BFD source selection result cache. + * + * This structure will keep track of the result based on the destination + * prefix. When the result changes all related BFD sessions with automatic + * source will be updated. + */ +struct bfd_source_cache { + /** Address VRF belongs. */ + vrf_id_t vrf_id; + /** Destination network address. */ + struct prefix address; + /** Source selected. */ + struct prefix source; + /** Is the source address valid? */ + bool valid; + /** BFD sessions using this. */ + size_t refcount; + + SLIST_ENTRY(bfd_source_cache) entry; +}; +SLIST_HEAD(bfd_source_list, bfd_source_cache); + +/** + * Data structure to do the necessary tricks to hide the BFD protocol + * integration internals. + */ +struct bfd_session_params { + /** Contains the session parameters and more. */ + struct bfd_session_arg args; + /** Contains the session state. */ + struct bfd_session_status bss; + /** Protocol implementation status update callback. */ + bsp_status_update updatecb; + /** Protocol implementation custom data pointer. */ + void *arg; + + /** + * Next event. + * + * This variable controls what action to execute when the command batch + * finishes. Normally we'd use `event_add_event` value, however since + * that function is going to be called multiple times and the value + * might be different we'll use this variable to keep track of it. + */ + enum bfd_session_event lastev; + /** + * BFD session configuration event. + * + * Multiple actions might be asked during a command batch (either via + * configuration load or northbound batch), so we'll use this to + * install/uninstall the BFD session parameters only once. + */ + struct event *installev; + + /** BFD session installation state. */ + bool installed; + + /** Automatic source selection. */ + bool auto_source; + /** Currently selected source. */ + struct bfd_source_cache *source_cache; + + /** Global BFD paramaters list. */ + TAILQ_ENTRY(bfd_session_params) entry; +}; + +struct bfd_sessions_global { + /** + * Global BFD session parameters list for (re)installation and update + * without code duplication among daemons. + */ + TAILQ_HEAD(bsplist, bfd_session_params) bsplist; + /** BFD automatic source selection cache. */ + struct bfd_source_list source_list; + + /** Pointer to FRR's event manager. */ + struct event_loop *tm; + /** Pointer to zebra client data structure. */ + struct zclient *zc; + + /** Debugging state. */ + bool debugging; + /** Is shutting down? */ + bool shutting_down; +}; + +/** Global configuration variable. */ +static struct bfd_sessions_global bsglobal; + +/** Global empty address for IPv4/IPv6. */ +static const struct in6_addr i6a_zero; + +/* + * Prototypes + */ + +static void bfd_source_cache_get(struct bfd_session_params *session); +static void bfd_source_cache_put(struct bfd_session_params *session); + +/* + * bfd_get_peer_info - Extract the Peer information for which the BFD session + * went down from the message sent from Zebra to clients. + */ +static struct interface *bfd_get_peer_info(struct stream *s, struct prefix *dp, + struct prefix *sp, int *status, + int *remote_cbit, vrf_id_t vrf_id) +{ + unsigned int ifindex; + struct interface *ifp = NULL; + int plen; + int local_remote_cbit; + + /* + * If the ifindex lookup fails the + * rest of the data in the stream is + * not read. All examples of this function + * call immediately use the dp->family which + * is not good. Ensure we are not using + * random data + */ + memset(dp, 0, sizeof(*dp)); + memset(sp, 0, sizeof(*sp)); + + /* Get interface index. */ + STREAM_GETL(s, ifindex); + + /* Lookup index. */ + if (ifindex != 0) { + ifp = if_lookup_by_index(ifindex, vrf_id); + if (ifp == NULL) { + if (bsglobal.debugging) + zlog_debug( + "%s: Can't find interface by ifindex: %d ", + __func__, ifindex); + return NULL; + } + } + + /* Fetch destination address. */ + STREAM_GETC(s, dp->family); + + plen = prefix_blen(dp); + STREAM_GET(&dp->u.prefix, s, plen); + STREAM_GETC(s, dp->prefixlen); + + /* Get BFD status. */ + STREAM_GETL(s, (*status)); + + STREAM_GETC(s, sp->family); + + plen = prefix_blen(sp); + STREAM_GET(&sp->u.prefix, s, plen); + STREAM_GETC(s, sp->prefixlen); + + STREAM_GETC(s, local_remote_cbit); + if (remote_cbit) + *remote_cbit = local_remote_cbit; + return ifp; + +stream_failure: + /* + * Clean dp and sp because caller + * will immediately check them valid or not + */ + memset(dp, 0, sizeof(*dp)); + memset(sp, 0, sizeof(*sp)); + return NULL; +} + +/* + * bfd_get_status_str - Convert BFD status to a display string. + */ +const char *bfd_get_status_str(int status) +{ + switch (status) { + case BFD_STATUS_DOWN: + return "Down"; + case BFD_STATUS_UP: + return "Up"; + case BFD_STATUS_ADMIN_DOWN: + return "Admin Down"; + case BFD_STATUS_UNKNOWN: + default: + return "Unknown"; + } +} + +/* + * bfd_last_update - Calculate the last BFD update time and convert it + * into a dd:hh:mm:ss display format. + */ +static void bfd_last_update(time_t last_update, char *buf, size_t len) +{ + time_t curr; + time_t diff; + struct tm tm; + struct timeval tv; + + /* If no BFD status update has ever been received, print `never'. */ + if (last_update == 0) { + snprintf(buf, len, "never"); + return; + } + + /* Get current time. */ + monotime(&tv); + curr = tv.tv_sec; + diff = curr - last_update; + gmtime_r(&diff, &tm); + + snprintf(buf, len, "%d:%02d:%02d:%02d", tm.tm_yday, tm.tm_hour, + tm.tm_min, tm.tm_sec); +} + +/* + * bfd_client_sendmsg - Format and send a client register + * command to Zebra to be forwarded to BFD + */ +void bfd_client_sendmsg(struct zclient *zclient, int command, + vrf_id_t vrf_id) +{ + struct stream *s; + enum zclient_send_status ret; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) { + if (bsglobal.debugging) + zlog_debug( + "%s: Can't send BFD client register, Zebra client not established", + __func__); + return; + } + + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, command, vrf_id); + + stream_putl(s, getpid()); + + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = zclient_send_message(zclient); + + if (ret == ZCLIENT_SEND_FAILURE) { + if (bsglobal.debugging) + zlog_debug( + "%s: %ld: zclient_send_message() failed", + __func__, (long)getpid()); + return; + } + + return; +} + +int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args) +{ + struct stream *s; + size_t addrlen; + + /* Individual reg/dereg messages are suppressed during shutdown. */ + if (bsglobal.shutting_down) { + if (bsglobal.debugging) + zlog_debug( + "%s: Suppressing BFD peer reg/dereg messages", + __func__); + return -1; + } + + /* Check socket. */ + if (!zc || zc->sock < 0) { + if (bsglobal.debugging) + zlog_debug("%s: zclient unavailable", __func__); + return -1; + } + + s = zc->obuf; + stream_reset(s); + + /* Create new message. */ + zclient_create_header(s, args->command, args->vrf_id); + stream_putl(s, getpid()); + + /* Encode destination address. */ + stream_putw(s, args->family); + addrlen = (args->family == AF_INET) ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + stream_put(s, &args->dst, addrlen); + + /* + * For more BFD integration protocol details, see function + * `_ptm_msg_read` in `bfdd/ptm_adapter.c`. + */ +#if HAVE_BFDD > 0 + /* Session timers. */ + stream_putl(s, args->min_rx); + stream_putl(s, args->min_tx); + stream_putc(s, args->detection_multiplier); + + /* Is multi hop? */ + stream_putc(s, args->mhop != 0); + + /* Source address. */ + stream_putw(s, args->family); + stream_put(s, &args->src, addrlen); + + /* Send the expected hops. */ + stream_putc(s, args->hops); + + /* Send interface name if any. */ + if (args->mhop) { + /* Don't send interface. */ + stream_putc(s, 0); + if (bsglobal.debugging && args->ifnamelen) + zlog_debug("%s: multi hop is configured, not sending interface", + __func__); + } else { + stream_putc(s, args->ifnamelen); + if (args->ifnamelen) + stream_put(s, args->ifname, args->ifnamelen); + } + + /* Send the C bit indicator. */ + stream_putc(s, args->cbit); + + /* Send profile name if any. */ + stream_putc(s, args->profilelen); + if (args->profilelen) + stream_put(s, args->profile, args->profilelen); +#else /* PTM BFD */ + /* Encode timers if this is a registration message. */ + if (args->command != ZEBRA_BFD_DEST_DEREGISTER) { + stream_putl(s, args->min_rx); + stream_putl(s, args->min_tx); + stream_putc(s, args->detection_multiplier); + } + + if (args->mhop) { + /* Multi hop indicator. */ + stream_putc(s, 1); + + /* Multi hop always sends the source address. */ + stream_putw(s, args->family); + stream_put(s, &args->src, addrlen); + + /* Send the expected hops. */ + stream_putc(s, args->hops); + } else { + /* Multi hop indicator. */ + stream_putc(s, 0); + + /* Single hop only sends the source address when IPv6. */ + if (args->family == AF_INET6) { + stream_putw(s, args->family); + stream_put(s, &args->src, addrlen); + } + + /* Send interface name if any. */ + stream_putc(s, args->ifnamelen); + if (args->ifnamelen) + stream_put(s, args->ifname, args->ifnamelen); + } + + /* Send the C bit indicator. */ + stream_putc(s, args->cbit); +#endif /* HAVE_BFDD */ + + /* Finish the message by writing the size. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + /* Send message to zebra. */ + if (zclient_send_message(zc) == ZCLIENT_SEND_FAILURE) { + if (bsglobal.debugging) + zlog_debug("%s: zclient_send_message failed", __func__); + return -1; + } + + return 0; +} + +struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *arg) +{ + struct bfd_session_params *bsp; + + bsp = XCALLOC(MTYPE_BFD_INFO, sizeof(*bsp)); + + /* Save application data. */ + bsp->updatecb = updatecb; + bsp->arg = arg; + + /* Set defaults. */ + bsp->args.detection_multiplier = BFD_DEF_DETECT_MULT; + bsp->args.hops = 1; + bsp->args.min_rx = BFD_DEF_MIN_RX; + bsp->args.min_tx = BFD_DEF_MIN_TX; + bsp->args.vrf_id = VRF_DEFAULT; + + /* Register in global list. */ + TAILQ_INSERT_TAIL(&bsglobal.bsplist, bsp, entry); + + return bsp; +} + +static bool _bfd_sess_valid(const struct bfd_session_params *bsp) +{ + /* Peer/local address not configured. */ + if (bsp->args.family == 0) + return false; + + /* Address configured but invalid. */ + if (bsp->args.family != AF_INET && bsp->args.family != AF_INET6) { + if (bsglobal.debugging) + zlog_debug("%s: invalid session family: %d", __func__, + bsp->args.family); + return false; + } + + /* Invalid address. */ + if (memcmp(&bsp->args.dst, &i6a_zero, sizeof(i6a_zero)) == 0) { + if (bsglobal.debugging) { + if (bsp->args.family == AF_INET) + zlog_debug("%s: invalid address: %pI4", + __func__, + (struct in_addr *)&bsp->args.dst); + else + zlog_debug("%s: invalid address: %pI6", + __func__, &bsp->args.dst); + } + return false; + } + + /* Multi hop requires local address. */ + if (bsp->args.mhop + && memcmp(&i6a_zero, &bsp->args.src, sizeof(i6a_zero)) == 0) { + if (bsglobal.debugging) + zlog_debug( + "%s: multi hop but no local address provided", + __func__); + return false; + } + + /* Check VRF ID. */ + if (bsp->args.vrf_id == VRF_UNKNOWN) { + if (bsglobal.debugging) + zlog_debug("%s: asked for unknown VRF", __func__); + return false; + } + + return true; +} + +static void _bfd_sess_send(struct event *t) +{ + struct bfd_session_params *bsp = EVENT_ARG(t); + int rv; + + /* Validate configuration before trying to send bogus data. */ + if (!_bfd_sess_valid(bsp)) + return; + + if (bsp->lastev == BSE_INSTALL) { + bsp->args.command = bsp->installed ? ZEBRA_BFD_DEST_UPDATE + : ZEBRA_BFD_DEST_REGISTER; + } else + bsp->args.command = ZEBRA_BFD_DEST_DEREGISTER; + + /* If not installed and asked for uninstall, do nothing. */ + if (!bsp->installed && bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER) + return; + + rv = zclient_bfd_command(bsglobal.zc, &bsp->args); + /* Command was sent successfully. */ + if (rv == 0) { + /* Update installation status. */ + if (bsp->args.command == ZEBRA_BFD_DEST_DEREGISTER) + bsp->installed = false; + else if (bsp->args.command == ZEBRA_BFD_DEST_REGISTER) + bsp->installed = true; + } else { + struct ipaddr src, dst; + + src.ipa_type = bsp->args.family; + src.ipaddr_v6 = bsp->args.src; + dst.ipa_type = bsp->args.family; + dst.ipaddr_v6 = bsp->args.dst; + + zlog_err( + "%s: BFD session %pIA -> %pIA interface %s VRF %s(%u) was not %s", + __func__, &src, &dst, + bsp->args.ifnamelen ? bsp->args.ifname : "*", + vrf_id_to_name(bsp->args.vrf_id), bsp->args.vrf_id, + bsp->lastev == BSE_INSTALL ? "installed" + : "uninstalled"); + } +} + +static void _bfd_sess_remove(struct bfd_session_params *bsp) +{ + /* Cancel any pending installation request. */ + EVENT_OFF(bsp->installev); + + /* Not installed, nothing to do. */ + if (!bsp->installed) + return; + + /* Send request to remove any session. */ + bsp->lastev = BSE_UNINSTALL; + event_execute(bsglobal.tm, _bfd_sess_send, bsp, 0, NULL); +} + +void bfd_sess_free(struct bfd_session_params **bsp) +{ + if (*bsp == NULL) + return; + + /* Remove any installed session. */ + _bfd_sess_remove(*bsp); + + /* Remove from global list. */ + TAILQ_REMOVE(&bsglobal.bsplist, (*bsp), entry); + + bfd_source_cache_put(*bsp); + + /* Free the memory and point to NULL. */ + XFREE(MTYPE_BFD_INFO, (*bsp)); +} + +static bool bfd_sess_address_changed(const struct bfd_session_params *bsp, + uint32_t family, + const struct in6_addr *src, + const struct in6_addr *dst) +{ + size_t addrlen; + + if (bsp->args.family != family) + return true; + + addrlen = (family == AF_INET) ? sizeof(struct in_addr) + : sizeof(struct in6_addr); + if ((src == NULL && memcmp(&bsp->args.src, &i6a_zero, addrlen)) + || (src && memcmp(src, &bsp->args.src, addrlen)) + || memcmp(dst, &bsp->args.dst, addrlen)) + return true; + + return false; +} + +void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp, + const struct in_addr *src, + const struct in_addr *dst) +{ + if (!bfd_sess_address_changed(bsp, AF_INET, (struct in6_addr *)src, + (struct in6_addr *)dst)) + return; + + /* If already installed, remove the old setting. */ + _bfd_sess_remove(bsp); + /* Address changed so we must reapply auto source. */ + bfd_source_cache_put(bsp); + + bsp->args.family = AF_INET; + + /* Clean memory, set zero value and avoid static analyser warnings. */ + memset(&bsp->args.src, 0, sizeof(bsp->args.src)); + memset(&bsp->args.dst, 0, sizeof(bsp->args.dst)); + + /* Copy the equivalent of IPv4 to arguments structure. */ + if (src) + memcpy(&bsp->args.src, src, sizeof(struct in_addr)); + + assert(dst); + memcpy(&bsp->args.dst, dst, sizeof(struct in_addr)); + + if (bsp->auto_source) + bfd_source_cache_get(bsp); +} + +void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp, + const struct in6_addr *src, + const struct in6_addr *dst) +{ + if (!bfd_sess_address_changed(bsp, AF_INET6, src, dst)) + return; + + /* If already installed, remove the old setting. */ + _bfd_sess_remove(bsp); + /* Address changed so we must reapply auto source. */ + bfd_source_cache_put(bsp); + + bsp->args.family = AF_INET6; + + /* Clean memory, set zero value and avoid static analyser warnings. */ + memset(&bsp->args.src, 0, sizeof(bsp->args.src)); + + if (src) + bsp->args.src = *src; + + assert(dst); + bsp->args.dst = *dst; + + if (bsp->auto_source) + bfd_source_cache_get(bsp); +} + +void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname) +{ + if ((ifname == NULL && bsp->args.ifnamelen == 0) + || (ifname && strcmp(bsp->args.ifname, ifname) == 0)) + return; + + /* If already installed, remove the old setting. */ + _bfd_sess_remove(bsp); + + if (ifname == NULL) { + bsp->args.ifname[0] = 0; + bsp->args.ifnamelen = 0; + return; + } + + if (strlcpy(bsp->args.ifname, ifname, sizeof(bsp->args.ifname)) + > sizeof(bsp->args.ifname)) + zlog_warn("%s: interface name truncated: %s", __func__, ifname); + + bsp->args.ifnamelen = strlen(bsp->args.ifname); +} + +void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile) +{ + if (profile == NULL) { + bsp->args.profile[0] = 0; + bsp->args.profilelen = 0; + return; + } + + if (strlcpy(bsp->args.profile, profile, sizeof(bsp->args.profile)) + > sizeof(bsp->args.profile)) + zlog_warn("%s: profile name truncated: %s", __func__, profile); + + bsp->args.profilelen = strlen(bsp->args.profile); +} + +void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id) +{ + if (bsp->args.vrf_id == vrf_id) + return; + + /* If already installed, remove the old setting. */ + _bfd_sess_remove(bsp); + /* Address changed so we must reapply auto source. */ + bfd_source_cache_put(bsp); + + bsp->args.vrf_id = vrf_id; + + if (bsp->auto_source) + bfd_source_cache_get(bsp); +} + +void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops) +{ + if (bsp->args.hops == hops) + return; + + /* If already installed, remove the old setting. */ + _bfd_sess_remove(bsp); + + bsp->args.hops = hops; + bsp->args.mhop = (hops > 1); +} + + +void bfd_sess_set_cbit(struct bfd_session_params *bsp, bool enable) +{ + bsp->args.cbit = enable; +} + +void bfd_sess_set_timers(struct bfd_session_params *bsp, + uint8_t detection_multiplier, uint32_t min_rx, + uint32_t min_tx) +{ + bsp->args.detection_multiplier = detection_multiplier; + bsp->args.min_rx = min_rx; + bsp->args.min_tx = min_tx; +} + +void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable) +{ + if (bsp->auto_source == enable) + return; + + bsp->auto_source = enable; + if (enable) + bfd_source_cache_get(bsp); + else + bfd_source_cache_put(bsp); +} + +void bfd_sess_install(struct bfd_session_params *bsp) +{ + bsp->lastev = BSE_INSTALL; + event_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev); +} + +void bfd_sess_uninstall(struct bfd_session_params *bsp) +{ + bsp->lastev = BSE_UNINSTALL; + event_add_event(bsglobal.tm, _bfd_sess_send, bsp, 0, &bsp->installev); +} + +enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp) +{ + return bsp->bss.state; +} + +uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp) +{ + return bsp->args.hops; +} + +const char *bfd_sess_profile(const struct bfd_session_params *bsp) +{ + return bsp->args.profilelen ? bsp->args.profile : NULL; +} + +void bfd_sess_addresses(const struct bfd_session_params *bsp, int *family, + struct in6_addr *src, struct in6_addr *dst) +{ + *family = bsp->args.family; + if (src) + *src = bsp->args.src; + if (dst) + *dst = bsp->args.dst; +} + +const char *bfd_sess_interface(const struct bfd_session_params *bsp) +{ + if (bsp->args.ifnamelen) + return bsp->args.ifname; + + return NULL; +} + +const char *bfd_sess_vrf(const struct bfd_session_params *bsp) +{ + return vrf_id_to_name(bsp->args.vrf_id); +} + +vrf_id_t bfd_sess_vrf_id(const struct bfd_session_params *bsp) +{ + return bsp->args.vrf_id; +} + +bool bfd_sess_cbit(const struct bfd_session_params *bsp) +{ + return bsp->args.cbit; +} + +void bfd_sess_timers(const struct bfd_session_params *bsp, + uint8_t *detection_multiplier, uint32_t *min_rx, + uint32_t *min_tx) +{ + *detection_multiplier = bsp->args.detection_multiplier; + *min_rx = bsp->args.min_rx; + *min_tx = bsp->args.min_tx; +} + +bool bfd_sess_auto_source(const struct bfd_session_params *bsp) +{ + return bsp->auto_source; +} + +void bfd_sess_show(struct vty *vty, struct json_object *json, + struct bfd_session_params *bsp) +{ + json_object *json_bfd = NULL; + char time_buf[64]; + + if (!bsp) + return; + + /* Show type. */ + if (json) { + json_bfd = json_object_new_object(); + if (bsp->args.mhop) + json_object_string_add(json_bfd, "type", "multi hop"); + else + json_object_string_add(json_bfd, "type", "single hop"); + } else + vty_out(vty, " BFD: Type: %s\n", + bsp->args.mhop ? "multi hop" : "single hop"); + + /* Show configuration. */ + if (json) { + json_object_int_add(json_bfd, "detectMultiplier", + bsp->args.detection_multiplier); + json_object_int_add(json_bfd, "rxMinInterval", + bsp->args.min_rx); + json_object_int_add(json_bfd, "txMinInterval", + bsp->args.min_tx); + } else { + vty_out(vty, + " Detect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n", + bsp->args.detection_multiplier, bsp->args.min_rx, + bsp->args.min_tx); + } + + bfd_last_update(bsp->bss.last_event, time_buf, sizeof(time_buf)); + if (json) { + json_object_string_add(json_bfd, "status", + bfd_get_status_str(bsp->bss.state)); + json_object_string_add(json_bfd, "lastUpdate", time_buf); + } else + vty_out(vty, " Status: %s, Last update: %s\n", + bfd_get_status_str(bsp->bss.state), time_buf); + + if (json) + json_object_object_add(json, "peerBfdInfo", json_bfd); + else + vty_out(vty, "\n"); +} + +/* + * Zebra communication related. + */ + +/** + * Callback for reinstallation of all registered BFD sessions. + * + * Use this as `zclient` `bfd_dest_replay` callback. + */ +int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS) +{ + struct bfd_session_params *bsp; + + if (!zclient->bfd_integration) + return 0; + + /* Do nothing when shutting down. */ + if (bsglobal.shutting_down) + return 0; + + if (bsglobal.debugging) + zlog_debug("%s: sending all sessions registered", __func__); + + /* Send the client registration */ + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); + + /* Replay all activated peers. */ + TAILQ_FOREACH (bsp, &bsglobal.bsplist, entry) { + /* Skip not installed sessions. */ + if (!bsp->installed) + continue; + + /* We are reconnecting, so we must send installation. */ + bsp->installed = false; + + /* Cancel any pending installation request. */ + EVENT_OFF(bsp->installev); + + /* Ask for installation. */ + bsp->lastev = BSE_INSTALL; + event_execute(bsglobal.tm, _bfd_sess_send, bsp, 0, NULL); + } + + return 0; +} + +int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS) +{ + struct bfd_session_params *bsp, *bspn; + size_t sessions_updated = 0; + struct interface *ifp; + int remote_cbit = false; + int state = BFD_STATUS_UNKNOWN; + time_t now; + size_t addrlen; + struct prefix dp; + struct prefix sp; + char ifstr[128], cbitstr[32]; + + if (!zclient->bfd_integration) + return 0; + + /* Do nothing when shutting down. */ + if (bsglobal.shutting_down) + return 0; + + ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &state, &remote_cbit, + vrf_id); + /* + * When interface lookup fails or an invalid stream is read, we must + * not proceed otherwise it will trigger an assertion while checking + * family type below. + */ + if (dp.family == 0 || sp.family == 0) + return 0; + + if (bsglobal.debugging) { + ifstr[0] = 0; + if (ifp) + snprintf(ifstr, sizeof(ifstr), " (interface %s)", + ifp->name); + + snprintf(cbitstr, sizeof(cbitstr), " (CPI bit %s)", + remote_cbit ? "yes" : "no"); + + zlog_debug("%s: %pFX -> %pFX%s VRF %s(%u)%s: %s", __func__, &sp, + &dp, ifstr, vrf_id_to_name(vrf_id), vrf_id, cbitstr, + bfd_get_status_str(state)); + } + + switch (dp.family) { + case AF_INET: + addrlen = sizeof(struct in_addr); + break; + case AF_INET6: + addrlen = sizeof(struct in6_addr); + break; + + default: + /* Unexpected value. */ + assert(0); + break; + } + + /* Cache current time to avoid multiple monotime clock calls. */ + now = monotime(NULL); + + /* Notify all matching sessions about update. */ + TAILQ_FOREACH_SAFE (bsp, &bsglobal.bsplist, entry, bspn) { + /* Skip not installed entries. */ + if (!bsp->installed) + continue; + /* Skip different VRFs. */ + if (bsp->args.vrf_id != vrf_id) + continue; + /* Skip different families. */ + if (bsp->args.family != dp.family) + continue; + /* Skip different interface. */ + if (bsp->args.ifnamelen && ifp + && strcmp(bsp->args.ifname, ifp->name) != 0) + continue; + /* Skip non matching destination addresses. */ + if (memcmp(&bsp->args.dst, &dp.u, addrlen) != 0) + continue; + /* + * Source comparison test: + * We will only compare source if BFD daemon provided the + * source address and the protocol set a source address in + * the configuration otherwise we'll just skip it. + */ + if (sp.family && memcmp(&bsp->args.src, &i6a_zero, addrlen) != 0 + && memcmp(&sp.u, &i6a_zero, addrlen) != 0 + && memcmp(&bsp->args.src, &sp.u, addrlen) != 0) + continue; + /* No session state change. */ + if ((int)bsp->bss.state == state) + continue; + + bsp->bss.last_event = now; + bsp->bss.previous_state = bsp->bss.state; + bsp->bss.state = state; + bsp->bss.remote_cbit = remote_cbit; + bsp->updatecb(bsp, &bsp->bss, bsp->arg); + sessions_updated++; + } + + if (bsglobal.debugging) + zlog_debug("%s: sessions updated: %zu", __func__, + sessions_updated); + + return 0; +} + +/** + * Frees all allocated resources and stops any activity. + * + * Must be called after every BFD session has been successfully + * unconfigured otherwise this function will `free()` any available + * session causing existing pointers to dangle. + * + * This is just a comment, in practice it will be called by the FRR + * library late finish hook. \see `bfd_protocol_integration_init`. + */ +static int bfd_protocol_integration_finish(void) +{ + if (bsglobal.zc == NULL) + return 0; + + while (!TAILQ_EMPTY(&bsglobal.bsplist)) { + struct bfd_session_params *session = + TAILQ_FIRST(&bsglobal.bsplist); + bfd_sess_free(&session); + } + + /* + * BFD source cache is linked to sessions, if all sessions are gone + * then the source cache must be empty. + */ + if (!SLIST_EMPTY(&bsglobal.source_list)) + zlog_warn("BFD integration source cache not empty"); + + return 0; +} + +void bfd_protocol_integration_init(struct zclient *zc, struct event_loop *tm) +{ + /* Initialize data structure. */ + TAILQ_INIT(&bsglobal.bsplist); + SLIST_INIT(&bsglobal.source_list); + + /* Copy pointers. */ + bsglobal.zc = zc; + bsglobal.tm = tm; + + /* Enable BFD callbacks. */ + zc->bfd_integration = true; + + /* Send the client registration */ + bfd_client_sendmsg(zc, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); + + hook_register(frr_fini, bfd_protocol_integration_finish); +} + +void bfd_protocol_integration_set_debug(bool enable) +{ + bsglobal.debugging = enable; +} + +void bfd_protocol_integration_set_shutdown(bool enable) +{ + bsglobal.shutting_down = enable; +} + +bool bfd_protocol_integration_debug(void) +{ + return bsglobal.debugging; +} + +bool bfd_protocol_integration_shutting_down(void) +{ + return bsglobal.shutting_down; +} + +/* + * BFD automatic source selection + * + * This feature will use the next hop tracking (NHT) provided by zebra + * to find out the source address by looking at the output interface. + * + * When the interface address / routing table change we'll be notified + * and be able to update the source address accordingly. + * + * zebra + * | + * +-----------------+ + * | BFD session set | + * | to auto source | + * +-----------------+ + * | + * \ +-----------------+ + * --------------> | Resolves | + * | destination | + * | address | + * +-----------------+ + * | + * +-----------------+ / + * | Sets resolved | <---------- + * | source address | + * +-----------------+ + */ +static bool +bfd_source_cache_session_match(const struct bfd_source_cache *source, + const struct bfd_session_params *session) +{ + const struct in_addr *address; + const struct in6_addr *address_v6; + + if (session->args.vrf_id != source->vrf_id) + return false; + if (session->args.family != source->address.family) + return false; + + switch (session->args.family) { + case AF_INET: + address = (const struct in_addr *)&session->args.dst; + if (address->s_addr != source->address.u.prefix4.s_addr) + return false; + break; + case AF_INET6: + address_v6 = &session->args.dst; + if (memcmp(address_v6, &source->address.u.prefix6, + sizeof(struct in6_addr))) + return false; + break; + default: + return false; + } + + return true; +} + +static struct bfd_source_cache * +bfd_source_cache_find(vrf_id_t vrf_id, const struct prefix *prefix) +{ + struct bfd_source_cache *source; + + SLIST_FOREACH (source, &bsglobal.source_list, entry) { + if (source->vrf_id != vrf_id) + continue; + if (!prefix_same(&source->address, prefix)) + continue; + + return source; + } + + return NULL; +} + +static void bfd_source_cache_get(struct bfd_session_params *session) +{ + struct bfd_source_cache *source; + struct prefix target = {}; + + switch (session->args.family) { + case AF_INET: + target.family = AF_INET; + target.prefixlen = IPV4_MAX_BITLEN; + memcpy(&target.u.prefix4, &session->args.dst, + sizeof(struct in_addr)); + break; + case AF_INET6: + target.family = AF_INET6; + target.prefixlen = IPV6_MAX_BITLEN; + memcpy(&target.u.prefix6, &session->args.dst, + sizeof(struct in6_addr)); + break; + default: + return; + } + + source = bfd_source_cache_find(session->args.vrf_id, &target); + if (source) { + if (session->source_cache == source) + return; + + bfd_source_cache_put(session); + session->source_cache = source; + source->refcount++; + return; + } + + source = XCALLOC(MTYPE_BFD_SOURCE, sizeof(*source)); + prefix_copy(&source->address, &target); + source->vrf_id = session->args.vrf_id; + SLIST_INSERT_HEAD(&bsglobal.source_list, source, entry); + + bfd_source_cache_put(session); + session->source_cache = source; + source->refcount = 1; + + return; +} + +static void bfd_source_cache_put(struct bfd_session_params *session) +{ + if (session->source_cache == NULL) + return; + + session->source_cache->refcount--; + if (session->source_cache->refcount > 0) { + session->source_cache = NULL; + return; + } + + SLIST_REMOVE(&bsglobal.source_list, session->source_cache, + bfd_source_cache, entry); + XFREE(MTYPE_BFD_SOURCE, session->source_cache); +} + +/** Updates BFD running session if source address has changed. */ +static void +bfd_source_cache_update_session(const struct bfd_source_cache *source, + struct bfd_session_params *session) +{ + const struct in_addr *address; + const struct in6_addr *address_v6; + + switch (session->args.family) { + case AF_INET: + address = (const struct in_addr *)&session->args.src; + if (memcmp(address, &source->source.u.prefix4, + sizeof(struct in_addr)) == 0) + return; + + _bfd_sess_remove(session); + memcpy(&session->args.src, &source->source.u.prefix4, + sizeof(struct in_addr)); + break; + case AF_INET6: + address_v6 = &session->args.src; + if (memcmp(address_v6, &source->source.u.prefix6, + sizeof(struct in6_addr)) == 0) + return; + + _bfd_sess_remove(session); + memcpy(&session->args.src, &source->source.u.prefix6, + sizeof(struct in6_addr)); + break; + default: + return; + } + + bfd_sess_install(session); +} + +static void +bfd_source_cache_update_sessions(const struct bfd_source_cache *source) +{ + struct bfd_session_params *session; + + if (!source->valid) + return; + + TAILQ_FOREACH (session, &bsglobal.bsplist, entry) { + if (!session->auto_source) + continue; + if (!bfd_source_cache_session_match(source, session)) + continue; + + bfd_source_cache_update_session(source, session); + } +} + +/** + * Try to translate next hop information into source address. + * + * \returns `true` if source changed otherwise `false`. + */ +static bool bfd_source_cache_update(struct bfd_source_cache *source, + const struct zapi_route *route) +{ + size_t nh_index; + + for (nh_index = 0; nh_index < route->nexthop_num; nh_index++) { + const struct zapi_nexthop *nh = &route->nexthops[nh_index]; + const struct interface *interface; + const struct connected *connected; + + interface = if_lookup_by_index(nh->ifindex, nh->vrf_id); + if (interface == NULL) { + zlog_err("next hop interface not found (index %d)", + nh->ifindex); + continue; + } + + frr_each (if_connected_const, interface->connected, connected) { + if (source->address.family != + connected->address->family) + continue; + if (prefix_same(connected->address, &source->source)) + return false; + /* + * Skip link-local as it is only useful for single hop + * and in that case no source is specified usually. + */ + if (source->address.family == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL( + &connected->address->u.prefix6)) + continue; + + prefix_copy(&source->source, connected->address); + source->valid = true; + return true; + } + } + + memset(&source->source, 0, sizeof(source->source)); + source->valid = false; + return false; +} + +int bfd_nht_update(const struct prefix *match, const struct zapi_route *route) +{ + struct bfd_source_cache *source; + + if (bsglobal.debugging) + zlog_debug("BFD NHT update for %pFX", &route->prefix); + + SLIST_FOREACH (source, &bsglobal.source_list, entry) { + if (source->vrf_id != route->vrf_id) + continue; + if (!prefix_same(match, &source->address)) + continue; + if (bfd_source_cache_update(source, route)) + bfd_source_cache_update_sessions(source); + } + + return 0; +} diff --git a/lib/bfd.h b/lib/bfd.h new file mode 100644 index 0000000..bfa5287 --- /dev/null +++ b/lib/bfd.h @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * bfd.h: BFD definitions and structures + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#ifndef _ZEBRA_BFD_H +#define _ZEBRA_BFD_H + +#include "lib/json.h" +#include "lib/zclient.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BFD_DEF_MIN_RX 300 +#define BFD_MIN_MIN_RX 50 +#define BFD_MAX_MIN_RX 60000 +#define BFD_DEF_MIN_TX 300 +#define BFD_MIN_MIN_TX 50 +#define BFD_MAX_MIN_TX 60000 +#define BFD_DEF_DETECT_MULT 3 +#define BFD_MIN_DETECT_MULT 2 +#define BFD_MAX_DETECT_MULT 255 + +#define BFD_STATUS_UNKNOWN (1 << 0) /* BFD session status never received */ +#define BFD_STATUS_DOWN (1 << 1) /* BFD session status is down */ +#define BFD_STATUS_UP (1 << 2) /* BFD session status is up */ +#define BFD_STATUS_ADMIN_DOWN (1 << 3) /* BFD session is admin down */ + +#define BFD_PROFILE_NAME_LEN 64 + +const char *bfd_get_status_str(int status); + +extern void bfd_client_sendmsg(struct zclient *zclient, int command, + vrf_id_t vrf_id); + +/* + * BFD new API. + */ + +/* Forward declaration of argument struct. */ +struct bfd_session_params; + +/** Session state definitions. */ +enum bfd_session_state { + /** Session state is unknown or not initialized. */ + BSS_UNKNOWN = BFD_STATUS_UNKNOWN, + /** Local or remote peer administratively shutdown the session. */ + BSS_ADMIN_DOWN = BFD_STATUS_ADMIN_DOWN, + /** Session is down. */ + BSS_DOWN = BFD_STATUS_DOWN, + /** Session is up and working correctly. */ + BSS_UP = BFD_STATUS_UP, +}; + +/** BFD session status information */ +struct bfd_session_status { + /** Current session state. */ + enum bfd_session_state state; + /** Previous session state. */ + enum bfd_session_state previous_state; + /** Remote Control Plane Independent bit state. */ + bool remote_cbit; + /** Last event occurrence. */ + time_t last_event; +}; + +/** + * Session status update callback. + * + * \param bsp BFD session parameters. + * \param bss BFD session status. + * \param arg application independent data. + */ +typedef void (*bsp_status_update)(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, + void *arg); + +/** + * Allocates and initializes the session parameters. + * + * \param updatecb status update notification callback. + * \param args application independent data. + * + * \returns pointer to configuration storage. + */ +struct bfd_session_params *bfd_sess_new(bsp_status_update updatecb, void *args); + +/** + * Uninstall session if installed and free resources allocated by the + * parameters. Already sets pointer to `NULL` to avoid dangling references. + * + * \param bsp session parameters. + */ +void bfd_sess_free(struct bfd_session_params **bsp); + +/** + * Set the local and peer address of the BFD session. + * + * NOTE: + * If the address changed the session is removed and must be installed again + * with `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param src local address (optional, can be `NULL`). + * \param dst remote address (mandatory). + */ +void bfd_sess_set_ipv4_addrs(struct bfd_session_params *bsp, + const struct in_addr *src, + const struct in_addr *dst); + +/** + * Set the local and peer address of the BFD session. + * + * NOTE: + * If the address changed the session is removed and must be installed again + * with `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param src local address (optional, can be `NULL`). + * \param dst remote address (mandatory). + */ +void bfd_sess_set_ipv6_addrs(struct bfd_session_params *bsp, + const struct in6_addr *src, + const struct in6_addr *dst); + +/** + * Configure the BFD session interface. + * + * NOTE: + * If the interface changed the session is removed and must be installed again + * with `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param ifname interface name (or `NULL` to remove it). + */ +void bfd_sess_set_interface(struct bfd_session_params *bsp, const char *ifname); + +/** + * Configure the BFD session profile name. + * + * NOTE: + * Session profile will only change after a `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param profile profile name (or `NULL` to remove it). + */ +void bfd_sess_set_profile(struct bfd_session_params *bsp, const char *profile); + +/** + * Configure the BFD session VRF. + * + * NOTE: + * If the VRF changed the session is removed and must be installed again + * with `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param vrf_id the VRF identification number. + */ +void bfd_sess_set_vrf(struct bfd_session_params *bsp, vrf_id_t vrf_id); + +/** + * Configure the BFD session single/multi hop setting. + * + * NOTE: + * If the number of hops is changed the session is removed and must be + * installed again with `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param hops maximum amount of hops expected (1 for single hop, 2 or + * more for multi hop). + */ +void bfd_sess_set_hop_count(struct bfd_session_params *bsp, uint8_t hops); + +/** + * Configure the BFD session to set the Control Plane Independent bit. + * + * NOTE: + * Session CPI bit will only change after a `bfd_sess_install`. + * + * \param bsp BFD session parameters. + * \param enable BFD Control Plane Independent state. + */ +void bfd_sess_set_cbit(struct bfd_session_params *bsp, bool enable); + +/** + * DEPRECATED: please avoid using timers directly and use profiles instead. + * + * Configures the BFD session timers to use. This is specially useful with + * `ptm-bfd` which does not support timers. + * + * NOTE: + * Session timers will only apply if the session has not been created yet. + * If the session is already installed you must uninstall and install again + * to take effect. + * + * \param bsp BFD session parameters. + * \param detection_multiplier the detection multiplier value. + * \param min_rx minimum required receive period. + * \param min_tx minimum required transmission period. + */ +void bfd_sess_set_timers(struct bfd_session_params *bsp, + uint8_t detection_multiplier, uint32_t min_rx, + uint32_t min_tx); + +/** + * Configures the automatic source selection for the BFD session. + * + * NOTE: + * Setting this configuration will override the IP source value set by + * `bfd_sess_set_ipv4_addrs` or `bfd_sess_set_ipv6_addrs`. + * + * \param bsp BFD session parameters + * \param enable BFD automatic source selection state. + */ +void bfd_sess_set_auto_source(struct bfd_session_params *bsp, bool enable); + +/** + * Installs or updates the BFD session based on the saved session arguments. + * + * NOTE: + * This function has a delayed effect: it will only install/update after + * all northbound/CLI command batch finishes. + * + * \param bsp session parameters. + */ +void bfd_sess_install(struct bfd_session_params *bsp); + +/** + * Uninstall the BFD session based on the saved session arguments. + * + * NOTE: + * This function uninstalls the session immediately (if installed) and cancels + * any previous `bfd_sess_install` calls. + * + * \param bsp session parameters. + */ +void bfd_sess_uninstall(struct bfd_session_params *bsp); + +/** + * Get BFD session current status. + * + * \param bsp session parameters. + * + * \returns BFD session status data structure. + */ +enum bfd_session_state bfd_sess_status(const struct bfd_session_params *bsp); + +/** + * Get BFD session amount of hops configured value. + * + * \param bsp session parameters. + * + * \returns configured amount of hops. + */ +uint8_t bfd_sess_hop_count(const struct bfd_session_params *bsp); + +/** + * Get BFD session profile configured value. + * + * \param bsp session parameters. + * + * \returns configured profile name (or `NULL` if empty). + */ +const char *bfd_sess_profile(const struct bfd_session_params *bsp); + +/** + * Get BFD session addresses. + * + * \param bsp session parameters. + * \param family the address family being used (AF_INET or AF_INET6). + * \param src source address (optional, may be `NULL`). + * \param dst peer address (optional, may be `NULL`). + */ +void bfd_sess_addresses(const struct bfd_session_params *bsp, int *family, + struct in6_addr *src, struct in6_addr *dst); +/** + * Get BFD session interface name. + * + * \param bsp session parameters. + * + * \returns `NULL` if not set otherwise the interface name. + */ +const char *bfd_sess_interface(const struct bfd_session_params *bsp); + +/** + * Get BFD session VRF name. + * + * \param bsp session parameters. + * + * \returns the VRF name. + */ +const char *bfd_sess_vrf(const struct bfd_session_params *bsp); + +/** + * Get BFD session VRF ID. + * + * \param bsp session parameters. + * + * \returns the VRF name. + */ +vrf_id_t bfd_sess_vrf_id(const struct bfd_session_params *bsp); + +/** + * Get BFD session control plane independent bit configuration state. + * + * \param bsp session parameters. + * + * \returns `true` if enabled otherwise `false`. + */ +bool bfd_sess_cbit(const struct bfd_session_params *bsp); + +/** + * DEPRECATED: please avoid using timers directly and use profiles instead. + * + * Gets the configured timers. + * + * \param bsp BFD session parameters. + * \param detection_multiplier the detection multiplier value. + * \param min_rx minimum required receive period. + * \param min_tx minimum required transmission period. + */ +void bfd_sess_timers(const struct bfd_session_params *bsp, + uint8_t *detection_multiplier, uint32_t *min_rx, + uint32_t *min_tx); + +/** + * Gets the automatic source selection state. + */ +bool bfd_sess_auto_source(const struct bfd_session_params *bsp); + +/** + * Show BFD session configuration and status. If `json` is provided (e.g. not + * `NULL`) then information will be inserted in object, otherwise printed to + * `vty`. + * + * \param vty Pointer to `vty` for outputting text. + * \param json (optional) JSON object pointer. + * \param bsp session parameters. + */ +void bfd_sess_show(struct vty *vty, struct json_object *json, + struct bfd_session_params *bsp); + +/** + * Initializes the BFD integration library. This function executes the + * following actions: + * + * - Copy the `struct event_loop` pointer to use as "thread" to execute + * the BFD session parameters installation. + * - Copy the `struct zclient` pointer to install its callbacks. + * - Initializes internal data structures. + * + * \param tm normally the daemon main thread event manager. + * \param zc the zebra client of the daemon. + */ +void bfd_protocol_integration_init(struct zclient *zc, struct event_loop *tm); + +/** + * BFD session registration arguments. + */ +struct bfd_session_arg { + /** + * BFD command. + * + * Valid commands: + * - `ZEBRA_BFD_DEST_REGISTER` + * - `ZEBRA_BFD_DEST_DEREGISTER` + */ + int32_t command; + + /** + * BFD family type. + * + * Supported types: + * - `AF_INET` + * - `AF_INET6`. + */ + uint32_t family; + /** Source address. */ + struct in6_addr src; + /** Source address. */ + struct in6_addr dst; + + /** Multi hop indicator. */ + uint8_t mhop; + /** Expected hops. */ + uint8_t hops; + /** C bit (Control Plane Independent bit) indicator. */ + uint8_t cbit; + + /** Interface name size. */ + uint8_t ifnamelen; + /** Interface name. */ + char ifname[64]; + + /** Daemon or session VRF. */ + vrf_id_t vrf_id; + + /** Profile name length. */ + uint8_t profilelen; + /** Profile name. */ + char profile[BFD_PROFILE_NAME_LEN]; + + /* + * Deprecation fields: these fields should be removed once `ptm-bfd` + * no longer uses this interface. + */ + + /** Minimum required receive interval (in microseconds). */ + uint32_t min_rx; + /** Minimum desired transmission interval (in microseconds). */ + uint32_t min_tx; + /** Detection multiplier. */ + uint32_t detection_multiplier; +}; + +/** + * Send a message to BFD daemon through the zebra client. + * + * \param zc the zebra client context. + * \param arg the BFD session command arguments. + * + * \returns `-1` on failure otherwise `0`. + * + * \see bfd_session_arg. + */ +extern int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *arg); + +/** + * Enables or disables BFD protocol integration API debugging. + * + * \param enable new API debug state. + */ +extern void bfd_protocol_integration_set_debug(bool enable); + +/** + * Sets shutdown mode so no more events are processed. + * + * This is useful to avoid the event storm that happens caused by network, + * interfaces or VRFs removal. It should also avoid some crashes due hanging + * pointers left overs by protocol. + * + * \param enable new API shutdown state. + */ +extern void bfd_protocol_integration_set_shutdown(bool enable); + +/** + * Get API debugging state. + */ +extern bool bfd_protocol_integration_debug(void); + +/** + * Get API shutdown state. + */ +extern bool bfd_protocol_integration_shutting_down(void); + +/* Update nexthop-tracking (nht) information for BFD auto source selection. + * The function must be called from the daemon callback function + * that deals with the ZEBRA_NEXTHOP_UPDATE zclient command + */ +extern int bfd_nht_update(const struct prefix *match, + const struct zapi_route *route); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_BFD_H */ diff --git a/lib/bitfield.h b/lib/bitfield.h new file mode 100644 index 0000000..3fda627 --- /dev/null +++ b/lib/bitfield.h @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Bitfields + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +/** + * A simple bit array implementation to allocate and free IDs. An example + * of its usage is in allocating link state IDs for OSPFv3 as OSPFv3 has + * removed all address semantics from LS ID. Another usage can be in + * allocating IDs for BGP neighbors (and dynamic update groups) for + * efficient storage of adj-rib-out. + * + * An example: + * #include "bitfield.h" + * + * bitfield_t bitfield; + * + * bf_init(bitfield, 32); + * ... + * bf_assign_index(bitfield, id1); + * bf_assign_index(bitfield, id2); + * ... + * bf_release_index(bitfield, id1); + */ + +#ifndef _BITFIELD_H +#define _BITFIELD_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int word_t; +#define WORD_MAX 0xFFFFFFFF +#define WORD_SIZE (sizeof(word_t) * 8) + +/** + * The bitfield structure. + * @data: the bits to manage. + * @n: The current word number that is being used. + * @m: total number of words in 'data' + */ +typedef struct {word_t *data; size_t n, m; } bitfield_t; + +DECLARE_MTYPE(BITFIELD); + +/** + * Initialize the bits. + * @v: an instance of bitfield_t struct. + * @N: number of bits to start with, which equates to how many + * IDs can be allocated. + */ +#define bf_init(v, N) \ + do { \ + (v).n = 0; \ + (v).m = ((N) / WORD_SIZE + 1); \ + (v).data = (word_t *)XCALLOC(MTYPE_BITFIELD, \ + ((v).m * sizeof(word_t))); \ + } while (0) + +/** + * allocate and assign an id from bitfield v. + */ +#define bf_assign_index(v, id) \ + do { \ + bf_find_bit(v, id); \ + bf_set_bit(v, id); \ + } while (0) + +/* + * allocate and assign 0th bit in the bitfiled. + */ +#define bf_assign_zero_index(v) \ + do { \ + int id = 0; \ + bf_assign_index(v, id); \ + } while (0) + +/* + * return an id to bitfield v + */ +#define bf_release_index(v, id) \ + (v).data[bf_index(id)] &= ~(1 << (bf_offset(id))) + +/* check if an id is in use */ +#define bf_test_index(v, id) \ + ((v).data[bf_index(id)] & (1 << (bf_offset(id)))) + +/* check if the bit field has been setup */ +#define bf_is_inited(v) ((v).data) + +/* compare two bitmaps of the same length */ +#define bf_cmp(v1, v2) (memcmp((v1).data, (v2).data, ((v1).m * sizeof(word_t)))) + +/* + * return 0th index back to bitfield + */ +#define bf_release_zero_index(v) bf_release_index(v, 0) + +#define bf_index(b) ((b) / WORD_SIZE) +#define bf_offset(b) ((b) % WORD_SIZE) + +/** + * Set a bit in the array. If it fills up that word and we are + * out of words, extend it by one more word. + */ +#define bf_set_bit(v, b) \ + do { \ + size_t w = bf_index(b); \ + (v).data[w] |= 1 << (bf_offset(b)); \ + (v).n += ((v).data[w] == WORD_MAX); \ + if ((v).n == (v).m) { \ + (v).m = (v).m + 1; \ + (v).data = XREALLOC(MTYPE_BITFIELD, (v).data, \ + (v).m * sizeof(word_t)); \ + (v).data[(v).m - 1] = 0; \ + } \ + } while (0) + +/* Find a clear bit in v and assign it to b. */ +#define bf_find_bit(v, b) \ + do { \ + word_t word = 0; \ + unsigned int w, sh; \ + for (w = 0; w <= (v).n; w++) { \ + if ((word = (v).data[w]) != WORD_MAX) \ + break; \ + } \ + (b) = ((word & 0xFFFF) == 0xFFFF) << 4; \ + word >>= (b); \ + sh = ((word & 0xFF) == 0xFF) << 3; \ + word >>= sh; \ + (b) |= sh; \ + sh = ((word & 0xF) == 0xF) << 2; \ + word >>= sh; \ + (b) |= sh; \ + sh = ((word & 0x3) == 0x3) << 1; \ + word >>= sh; \ + (b) |= sh; \ + sh = ((word & 0x1) == 0x1) << 0; \ + word >>= sh; \ + (b) |= sh; \ + (b) += (w * WORD_SIZE); \ + } while (0) + +/* + * Find a clear bit in v and return it + * Start looking in the word containing bit position start_index. + * If necessary, wrap around after bit position max_index. + */ +static inline unsigned int +bf_find_next_clear_bit_wrap(bitfield_t *v, word_t start_index, word_t max_index) +{ + int start_bit; + unsigned long i, offset, scanbits, wordcount_max, index_max; + + if (start_index > max_index) + start_index = 0; + + start_bit = start_index & (WORD_SIZE - 1); + wordcount_max = bf_index(max_index) + 1; + + scanbits = WORD_SIZE; + for (i = bf_index(start_index); i < v->m; ++i) { + if (v->data[i] == WORD_MAX) { + /* if the whole word is full move to the next */ + start_bit = 0; + continue; + } + /* scan one word for clear bits */ + if ((i == v->m - 1) && (v->m >= wordcount_max)) + /* max index could be only part of word */ + scanbits = (max_index % WORD_SIZE) + 1; + for (offset = start_bit; offset < scanbits; ++offset) { + if (!((v->data[i] >> offset) & 1)) + return ((i * WORD_SIZE) + offset); + } + /* move to the next word */ + start_bit = 0; + } + + if (v->m < wordcount_max) { + /* + * We can expand bitfield, so no need to wrap. + * Return the index of the first bit of the next word. + * Assumption is that caller will call bf_set_bit which + * will allocate additional space. + */ + v->m += 1; + v->data = (word_t *)XREALLOC(MTYPE_BITFIELD, v->data, + v->m * sizeof(word_t)); + v->data[v->m - 1] = 0; + return v->m * WORD_SIZE; + } + + /* + * start looking for a clear bit at the start of the bitfield and + * stop when we reach start_index + */ + scanbits = WORD_SIZE; + index_max = bf_index(start_index - 1); + for (i = 0; i <= index_max; ++i) { + if (i == index_max) + scanbits = ((start_index - 1) % WORD_SIZE) + 1; + for (offset = start_bit; offset < scanbits; ++offset) { + if (!((v->data[i] >> offset) & 1)) + return ((i * WORD_SIZE) + offset); + } + /* move to the next word */ + start_bit = 0; + } + + return WORD_MAX; +} + +static inline unsigned int bf_find_next_set_bit(bitfield_t v, + word_t start_index) +{ + int start_bit; + unsigned long i, offset; + + start_bit = start_index & (WORD_SIZE - 1); + + for (i = bf_index(start_index); i < v.m; ++i) { + if (v.data[i] == 0) { + /* if the whole word is empty move to the next */ + start_bit = 0; + continue; + } + /* scan one word for set bits */ + for (offset = start_bit; offset < WORD_SIZE; ++offset) { + if ((v.data[i] >> offset) & 1) + return ((i * WORD_SIZE) + offset); + } + /* move to the next word */ + start_bit = 0; + } + return WORD_MAX; +} + +/* iterate through all the set bits */ +#define bf_for_each_set_bit(v, b, max) \ + for ((b) = bf_find_next_set_bit((v), 0); \ + (b) < max; \ + (b) = bf_find_next_set_bit((v), (b) + 1)) + +/* + * Free the allocated memory for data + * @v: an instance of bitfield_t struct. + */ +#define bf_free(v) \ + do { \ + XFREE(MTYPE_BITFIELD, (v).data); \ + (v).data = NULL; \ + } while (0) + +static inline bitfield_t bf_copy(bitfield_t src) +{ + bitfield_t dst; + + assert(bf_is_inited(src)); + bf_init(dst, WORD_SIZE * (src.m - 1)); + for (size_t i = 0; i < src.m; i++) + dst.data[i] = src.data[i]; + dst.n = src.n; + return dst; +} + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/buffer.c b/lib/buffer.c new file mode 100644 index 0000000..63df56a --- /dev/null +++ b/lib/buffer.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Buffering of output and input. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#include + +#include "memory.h" +#include "buffer.h" +#include "log.h" +#include "network.h" +#include "lib_errors.h" + +#include + +DEFINE_MTYPE_STATIC(LIB, BUFFER, "Buffer"); +DEFINE_MTYPE_STATIC(LIB, BUFFER_DATA, "Buffer data"); + +/* Buffer master. */ +struct buffer { + /* Data list. */ + struct buffer_data *head; + struct buffer_data *tail; + + /* Size of each buffer_data chunk. */ + size_t size; +}; + +/* Data container. */ +struct buffer_data { + struct buffer_data *next; + + /* Location to add new data. */ + size_t cp; + + /* Pointer to data not yet flushed. */ + size_t sp; + + /* Actual data stream (variable length). */ + unsigned char data[]; /* real dimension is buffer->size */ +}; + +/* It should always be true that: 0 <= sp <= cp <= size */ + +/* Default buffer size (used if none specified). It is rounded up to the + next page boundary. */ +#define BUFFER_SIZE_DEFAULT 4096 + +#define BUFFER_DATA_FREE(D) XFREE(MTYPE_BUFFER_DATA, (D)) + +/* Make new buffer. */ +struct buffer *buffer_new(size_t size) +{ + struct buffer *b; + + b = XCALLOC(MTYPE_BUFFER, sizeof(struct buffer)); + + if (size) + b->size = size; + else { + static size_t default_size; + if (!default_size) { + long pgsz = sysconf(_SC_PAGESIZE); + default_size = ((((BUFFER_SIZE_DEFAULT - 1) / pgsz) + 1) + * pgsz); + } + b->size = default_size; + } + + return b; +} + +/* Free buffer. */ +void buffer_free(struct buffer *b) +{ + buffer_reset(b); + XFREE(MTYPE_BUFFER, b); +} + +/* Make string clone. */ +char *buffer_getstr(struct buffer *b) +{ + size_t totlen = 0; + struct buffer_data *data; + char *s; + char *p; + + for (data = b->head; data; data = data->next) + totlen += data->cp - data->sp; + if (!(s = XMALLOC(MTYPE_TMP, totlen + 1))) + return NULL; + p = s; + for (data = b->head; data; data = data->next) { + memcpy(p, data->data + data->sp, data->cp - data->sp); + p += data->cp - data->sp; + } + *p = '\0'; + return s; +} + +/* Clear and free all allocated data. */ +void buffer_reset(struct buffer *b) +{ + struct buffer_data *data; + struct buffer_data *next; + + for (data = b->head; data; data = next) { + next = data->next; + BUFFER_DATA_FREE(data); + } + b->head = b->tail = NULL; +} + +/* Add buffer_data to the end of buffer. */ +static struct buffer_data *buffer_add(struct buffer *b) +{ + struct buffer_data *d; + + d = XMALLOC(MTYPE_BUFFER_DATA, + offsetof(struct buffer_data, data) + b->size); + d->cp = d->sp = 0; + d->next = NULL; + + if (b->tail) + b->tail->next = d; + else + b->head = d; + b->tail = d; + + return d; +} + +/* Write data to buffer. */ +void buffer_put(struct buffer *b, const void *p, size_t size) +{ + struct buffer_data *data = b->tail; + const char *ptr = p; + + /* We use even last one byte of data buffer. */ + while (size) { + size_t chunk; + + /* If there is no data buffer add it. */ + if (data == NULL || data->cp == b->size) + data = buffer_add(b); + + chunk = ((size <= (b->size - data->cp)) ? size + : (b->size - data->cp)); + memcpy((data->data + data->cp), ptr, chunk); + size -= chunk; + ptr += chunk; + data->cp += chunk; + } +} + +/* Insert character into the buffer. */ +void buffer_putc(struct buffer *b, uint8_t c) +{ + buffer_put(b, &c, 1); +} + +/* Put string to the buffer. */ +void buffer_putstr(struct buffer *b, const char *c) +{ + buffer_put(b, c, strlen(c)); +} + +/* Expand \n to \r\n */ +void buffer_put_crlf(struct buffer *b, const void *origp, size_t origsize) +{ + struct buffer_data *data = b->tail; + const char *p = origp, *end = p + origsize, *lf; + size_t size; + + lf = memchr(p, '\n', end - p); + + /* We use even last one byte of data buffer. */ + while (p < end) { + size_t avail, chunk; + + /* If there is no data buffer add it. */ + if (data == NULL || data->cp == b->size) + data = buffer_add(b); + + size = (lf ? lf : end) - p; + avail = b->size - data->cp; + + chunk = (size <= avail) ? size : avail; + memcpy(data->data + data->cp, p, chunk); + + p += chunk; + data->cp += chunk; + + if (lf && size <= avail) { + /* we just copied up to (including) a '\n' */ + if (data->cp == b->size) + data = buffer_add(b); + data->data[data->cp++] = '\r'; + if (data->cp == b->size) + data = buffer_add(b); + data->data[data->cp++] = '\n'; + + p++; + lf = memchr(p, '\n', end - p); + } + } +} + +/* Keep flushing data to the fd until the buffer is empty or an error is + encountered or the operation would block. */ +buffer_status_t buffer_flush_all(struct buffer *b, int fd) +{ + buffer_status_t ret; + struct buffer_data *head; + size_t head_sp; + + if (!b->head) + return BUFFER_EMPTY; + head_sp = (head = b->head)->sp; + /* Flush all data. */ + while ((ret = buffer_flush_available(b, fd)) == BUFFER_PENDING) { + if ((b->head == head) && (head_sp == head->sp) + && (errno != EINTR)) + /* No data was flushed, so kernel buffer must be full. + */ + return ret; + head_sp = (head = b->head)->sp; + } + + return ret; +} + +/* Flush enough data to fill a terminal window of the given scene (used only + by vty telnet interface). */ +buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width, + int height, int erase_flag, + int no_more_flag) +{ + int nbytes; + int iov_alloc; + int iov_index; + struct iovec *iov; + struct iovec small_iov[3]; + char more[] = " --More-- "; + char erase[] = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; + struct buffer_data *data; + int column; + + if (!b->head) + return BUFFER_EMPTY; + + if (height < 1) + height = 1; + else if (height >= 2) + height--; + if (width < 1) + width = 1; + + /* For erase and more data add two to b's buffer_data count.*/ + if (b->head->next == NULL) { + iov_alloc = array_size(small_iov); + iov = small_iov; + } else { + iov_alloc = ((height * (width + 2)) / b->size) + 10; + iov = XMALLOC(MTYPE_TMP, iov_alloc * sizeof(*iov)); + } + iov_index = 0; + + /* Previously print out is performed. */ + if (erase_flag) { + iov[iov_index].iov_base = erase; + iov[iov_index].iov_len = sizeof(erase); + iov_index++; + } + + /* Output data. */ + column = 1; /* Column position of next character displayed. */ + for (data = b->head; data && (height > 0); data = data->next) { + size_t cp; + + cp = data->sp; + while ((cp < data->cp) && (height > 0)) { + /* Calculate lines remaining and column position after + displaying + this character. */ + if (data->data[cp] == '\r') + column = 1; + else if ((data->data[cp] == '\n') + || (column == width)) { + column = 1; + height--; + } else + column++; + cp++; + } + iov[iov_index].iov_base = (char *)(data->data + data->sp); + iov[iov_index++].iov_len = cp - data->sp; + data->sp = cp; + + if (iov_index == iov_alloc) + /* This should not ordinarily happen. */ + { + iov_alloc *= 2; + if (iov != small_iov) { + iov = XREALLOC(MTYPE_TMP, iov, + iov_alloc * sizeof(*iov)); + } else { + /* This should absolutely never occur. */ + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "%s: corruption detected: iov_small overflowed; head %p, tail %p, head->next %p", + __func__, (void *)b->head, + (void *)b->tail, (void *)b->head->next); + iov = XMALLOC(MTYPE_TMP, + iov_alloc * sizeof(*iov)); + memcpy(iov, small_iov, sizeof(small_iov)); + } + } + } + + /* In case of `more' display need. */ + if (b->tail && (b->tail->sp < b->tail->cp) && !no_more_flag) { + iov[iov_index].iov_base = more; + iov[iov_index].iov_len = sizeof(more); + iov_index++; + } + + +#ifdef IOV_MAX + /* IOV_MAX are normally defined in , Posix.1g. + example: Solaris2.6 are defined IOV_MAX size at 16. */ + { + struct iovec *c_iov = iov; + nbytes = 0; /* Make sure it's initialized. */ + + while (iov_index > 0) { + int iov_size; + + iov_size = + ((iov_index > IOV_MAX) ? IOV_MAX : iov_index); + nbytes = writev(fd, c_iov, iov_size); + if (nbytes < 0) { + flog_err(EC_LIB_SOCKET, + "%s: writev to fd %d failed: %s", + __func__, fd, safe_strerror(errno)); + break; + } + + /* move pointer io-vector */ + c_iov += iov_size; + iov_index -= iov_size; + } + } +#else /* IOV_MAX */ + nbytes = writev(fd, iov, iov_index); + if (nbytes < 0) + flog_err(EC_LIB_SOCKET, "%s: writev to fd %d failed: %s", + __func__, fd, safe_strerror(errno)); +#endif /* IOV_MAX */ + + /* Free printed buffer data. */ + while (b->head && (b->head->sp == b->head->cp)) { + struct buffer_data *del; + if (!(b->head = (del = b->head)->next)) + b->tail = NULL; + BUFFER_DATA_FREE(del); + } + + if (iov != small_iov) + XFREE(MTYPE_TMP, iov); + + return (nbytes < 0) ? BUFFER_ERROR + : (b->head ? BUFFER_PENDING : BUFFER_EMPTY); +} + +/* This function (unlike other buffer_flush* functions above) is designed +to work with non-blocking sockets. It does not attempt to write out +all of the queued data, just a "big" chunk. It returns 0 if it was +able to empty out the buffers completely, 1 if more flushing is +required later, or -1 on a fatal write error. */ +buffer_status_t buffer_flush_available(struct buffer *b, int fd) +{ + +/* These are just reasonable values to make sure a significant amount of +data is written. There's no need to go crazy and try to write it all +in one shot. */ +#ifdef IOV_MAX +#define MAX_CHUNKS ((IOV_MAX >= 16) ? 16 : IOV_MAX) +#else +#define MAX_CHUNKS 16 +#endif +#define MAX_FLUSH 131072 + + struct buffer_data *d; + size_t written; + struct iovec iov[MAX_CHUNKS]; + size_t iovcnt = 0; + size_t nbyte = 0; + + if (fd < 0) + return BUFFER_ERROR; + + for (d = b->head; d && (iovcnt < MAX_CHUNKS) && (nbyte < MAX_FLUSH); + d = d->next, iovcnt++) { + iov[iovcnt].iov_base = d->data + d->sp; + nbyte += (iov[iovcnt].iov_len = d->cp - d->sp); + } + + if (!nbyte) + /* No data to flush: should we issue a warning message? */ + return BUFFER_EMPTY; + + /* only place where written should be sign compared */ + if ((ssize_t)(written = writev(fd, iov, iovcnt)) < 0) { + if (ERRNO_IO_RETRY(errno)) + /* Calling code should try again later. */ + return BUFFER_PENDING; + flog_err(EC_LIB_SOCKET, "%s: write error on fd %d: %s", + __func__, fd, safe_strerror(errno)); + return BUFFER_ERROR; + } + + /* Free printed buffer data. */ + while (written > 0) { + if (!(d = b->head)) { + flog_err( + EC_LIB_DEVELOPMENT, + "%s: corruption detected: buffer queue empty, but written is %lu", + __func__, (unsigned long)written); + break; + } + if (written < d->cp - d->sp) { + d->sp += written; + return BUFFER_PENDING; + } + + written -= (d->cp - d->sp); + if (!(b->head = d->next)) + b->tail = NULL; + BUFFER_DATA_FREE(d); + } + + return b->head ? BUFFER_PENDING : BUFFER_EMPTY; + +#undef MAX_CHUNKS +#undef MAX_FLUSH +} + +buffer_status_t buffer_write(struct buffer *b, int fd, const void *p, + size_t size) +{ + ssize_t nbytes; + + if (b->head) + /* Buffer is not empty, so do not attempt to write the new data. + */ + nbytes = 0; + else { + nbytes = write(fd, p, size); + if (nbytes < 0) { + if (ERRNO_IO_RETRY(errno)) + nbytes = 0; + else { + flog_err(EC_LIB_SOCKET, + "%s: write error on fd %d: %s", + __func__, fd, safe_strerror(errno)); + return BUFFER_ERROR; + } + } + } + /* Add any remaining data to the buffer. */ + { + size_t written = nbytes; + if (written < size) + buffer_put(b, ((const char *)p) + written, + size - written); + } + return b->head ? BUFFER_PENDING : BUFFER_EMPTY; +} diff --git a/lib/buffer.h b/lib/buffer.h new file mode 100644 index 0000000..a0b82d2 --- /dev/null +++ b/lib/buffer.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Buffering to output and input. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_BUFFER_H +#define _ZEBRA_BUFFER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Create a new buffer. Memory will be allocated in chunks of the given + size. If the argument is 0, the library will supply a reasonable + default size suitable for buffering socket I/O. */ +extern struct buffer *buffer_new(size_t size); + +/* Free all data in the buffer. */ +extern void buffer_reset(struct buffer *b); + +/* This function first calls buffer_reset to release all buffered data. + Then it frees the struct buffer itself. */ +extern void buffer_free(struct buffer *b); + +/* Add the given data to the end of the buffer. */ +extern void buffer_put(struct buffer *b, const void *p, size_t size); +/* Add a single character to the end of the buffer. */ +extern void buffer_putc(struct buffer *b, uint8_t c); +/* Add a NUL-terminated string to the end of the buffer. */ +extern void buffer_putstr(struct buffer *b, const char *str); +/* Add given data, inline-expanding \n to \r\n */ +extern void buffer_put_crlf(struct buffer *b, const void *p, size_t size); + +/* Combine all accumulated (and unflushed) data inside the buffer into a + single NUL-terminated string allocated using XMALLOC(MTYPE_TMP). Note + that this function does not alter the state of the buffer, so the data + is still inside waiting to be flushed. */ +char *buffer_getstr(struct buffer *b); + +/* Returns 1 if there is no pending data in the buffer. Otherwise returns 0. */ +int buffer_empty(struct buffer *b); + +typedef enum { + /* An I/O error occurred. The buffer should be destroyed and the + file descriptor should be closed. */ + BUFFER_ERROR = -1, + + /* The data was written successfully, and the buffer is now empty + (there is no pending data waiting to be flushed). */ + BUFFER_EMPTY = 0, + + /* There is pending data in the buffer waiting to be flushed. Please + try flushing the buffer when select indicates that the file + descriptor + is writeable. */ + BUFFER_PENDING = 1 +} buffer_status_t; + +/* Try to write this data to the file descriptor. Any data that cannot + be written immediately is added to the buffer queue. */ +extern buffer_status_t buffer_write(struct buffer *b, int fd, const void *p, + size_t size); + +/* This function attempts to flush some (but perhaps not all) of + the queued data to the given file descriptor. */ +extern buffer_status_t buffer_flush_available(struct buffer *b, int fd); + +/* The following 2 functions (buffer_flush_all and buffer_flush_window) + are for use in lib/vty.c only. They should not be used elsewhere. */ + +/* Call buffer_flush_available repeatedly until either all data has been + flushed, or an I/O error has been encountered, or the operation would + block. */ +extern buffer_status_t buffer_flush_all(struct buffer *b, int fd); + +/* Attempt to write enough data to the given fd to fill a window of the + given width and height (and remove the data written from the buffer). + + If !no_more, then a message saying " --More-- " is appended. + If erase is true, then first overwrite the previous " --More-- " message + with spaces. + + Any write error (including EAGAIN or EINTR) will cause this function + to return -1 (because the logic for handling the erase and more features + is too complicated to retry the write later). +*/ +extern buffer_status_t buffer_flush_window(struct buffer *b, int fd, int width, + int height, int erase, int no_more); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_BUFFER_H */ diff --git a/lib/checksum.c b/lib/checksum.c new file mode 100644 index 0000000..b1ad813 --- /dev/null +++ b/lib/checksum.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Checksum routine for Internet Protocol family headers (C Version). + * + * Refer to "Computing the Internet Checksum" by R. Braden, D. Borman and + * C. Partridge, Computer Communication Review, Vol. 19, No. 2, April 1989, + * pp. 86-101, for additional details on computing this checksum. + */ + +#include +#include "checksum.h" + +#define add_carry(dst, add) \ + do { \ + typeof(dst) _add = (add); \ + dst += _add; \ + if (dst < _add) \ + dst++; \ + } while (0) + +uint16_t in_cksumv(const struct iovec *iov, size_t iov_len) +{ + const struct iovec *iov_end; + uint32_t sum = 0; + + union { + uint8_t bytes[2]; + uint16_t word; + } wordbuf; + bool have_oddbyte = false; + + /* + * Our algorithm is simple, using a 32-bit accumulator (sum), + * we add sequential 16-bit words to it, and at the end, fold back + * all the carry bits from the top 16 bits into the lower 16 bits. + */ + + for (iov_end = iov + iov_len; iov < iov_end; iov++) { + const uint8_t *ptr, *end; + + ptr = (const uint8_t *)iov->iov_base; + end = ptr + iov->iov_len; + if (ptr == end) + continue; + + if (have_oddbyte) { + have_oddbyte = false; + wordbuf.bytes[1] = *ptr++; + + add_carry(sum, wordbuf.word); + } + + while (ptr + 8 <= end) { + add_carry(sum, *(const uint32_t *)(ptr + 0)); + add_carry(sum, *(const uint32_t *)(ptr + 4)); + ptr += 8; + } + + while (ptr + 2 <= end) { + add_carry(sum, *(const uint16_t *)ptr); + ptr += 2; + } + + if (ptr + 1 <= end) { + wordbuf.bytes[0] = *ptr++; + have_oddbyte = true; + } + } + + /* mop up an odd byte, if necessary */ + if (have_oddbyte) { + wordbuf.bytes[1] = 0; + add_carry(sum, wordbuf.word); + } + + /* + * Add back carry outs from top 16 bits to low 16 bits. + */ + + sum = (sum >> 16) + (sum & 0xffff); /* add high-16 to low-16 */ + sum += (sum >> 16); /* add carry */ + return ~sum; +} + +/* Fletcher Checksum -- Refer to RFC1008. */ +#define MODX 4102U /* 5802 should be fine */ + +/* To be consistent, offset is 0-based index, rather than the 1-based + index required in the specification ISO 8473, Annex C.1 */ +/* calling with offset == FLETCHER_CHECKSUM_VALIDATE will validate the checksum + without modifying the buffer; a valid checksum returns 0 */ +uint16_t fletcher_checksum(uint8_t *buffer, const size_t len, + const uint16_t offset) +{ + uint8_t *p; + int x, y, c0, c1; + uint16_t checksum = 0; + uint16_t *csum; + size_t partial_len, i, left = len; + + if (offset != FLETCHER_CHECKSUM_VALIDATE) + /* Zero the csum in the packet. */ + { + assert(offset + < (len - 1)); /* account for two bytes of checksum */ + csum = (uint16_t *)(buffer + offset); + *(csum) = 0; + } + + p = buffer; + c0 = 0; + c1 = 0; + + while (left != 0) { + partial_len = MIN(left, MODX); + + for (i = 0; i < partial_len; i++) { + c0 = c0 + *(p++); + c1 += c0; + } + + c0 = c0 % 255; + c1 = c1 % 255; + + left -= partial_len; + } + + /* The cast is important, to ensure the mod is taken as a signed value. + */ + x = (int)((len - offset - 1) * c0 - c1) % 255; + + if (x <= 0) + x += 255; + y = 510 - c0 - x; + if (y > 255) + y -= 255; + + if (offset == FLETCHER_CHECKSUM_VALIDATE) { + checksum = (c1 << 8) + c0; + } else { + /* + * Now we write this to the packet. + * We could skip this step too, since the checksum returned + * would + * be stored into the checksum field by the caller. + */ + buffer[offset] = x; + buffer[offset + 1] = y; + + /* Take care of the endian issue */ + checksum = htons((x << 8) | (y & 0xFF)); + } + + return checksum; +} diff --git a/lib/checksum.h b/lib/checksum.h new file mode 100644 index 0000000..2856a0d --- /dev/null +++ b/lib/checksum.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef _FRR_CHECKSUM_H +#define _FRR_CHECKSUM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* IPv4 pseudoheader */ +struct ipv4_ph { + struct in_addr src; + struct in_addr dst; + uint8_t rsvd; + uint8_t proto; + uint16_t len; +} __attribute__((packed)); + +/* IPv6 pseudoheader */ +struct ipv6_ph { + struct in6_addr src; + struct in6_addr dst; + uint32_t ulpl; + uint8_t zero[3]; + uint8_t next_hdr; +} __attribute__((packed)); + + +extern uint16_t in_cksumv(const struct iovec *iov, size_t iov_len); + +static inline uint16_t in_cksum(const void *data, size_t nbytes) +{ + struct iovec iov[1]; + + iov[0].iov_base = (void *)data; + iov[0].iov_len = nbytes; + return in_cksumv(iov, array_size(iov)); +} + +static inline uint16_t in_cksum_with_ph4(const struct ipv4_ph *ph, + const void *data, size_t nbytes) +{ + struct iovec iov[2]; + + iov[0].iov_base = (void *)ph; + iov[0].iov_len = sizeof(*ph); + iov[1].iov_base = (void *)data; + iov[1].iov_len = nbytes; + return in_cksumv(iov, array_size(iov)); +} + +static inline uint16_t in_cksum_with_ph6(const struct ipv6_ph *ph, + const void *data, size_t nbytes) +{ + struct iovec iov[2]; + + iov[0].iov_base = (void *)ph; + iov[0].iov_len = sizeof(*ph); + iov[1].iov_base = (void *)data; + iov[1].iov_len = nbytes; + return in_cksumv(iov, array_size(iov)); +} + +#define FLETCHER_CHECKSUM_VALIDATE 0xffff +extern uint16_t fletcher_checksum(uint8_t *, const size_t len, + const uint16_t offset); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_CHECKSUM_H */ diff --git a/lib/clippy.c b/lib/clippy.c new file mode 100644 index 0000000..d414053 --- /dev/null +++ b/lib/clippy.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * clippy (CLI preparator in python) main executable + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +#include "config.h" +#include +#include +#include +#include +#include "getopt.h" + +#include "command_graph.h" +#include "clippy.h" + +#if PY_VERSION_HEX >= 0x03080000 +/* new python init/config API added in Python 3.8 */ +int main(int argc, char **argv) +{ + PyStatus status; + PyPreConfig preconfig[1]; + PyConfig config[1]; + + PyPreConfig_InitPythonConfig(preconfig); + preconfig->configure_locale = 0; + preconfig->coerce_c_locale = 1; + preconfig->coerce_c_locale_warn = 0; + preconfig->isolated = 0; + preconfig->utf8_mode = 1; + preconfig->parse_argv = 0; + + status = Py_PreInitializeFromBytesArgs(preconfig, argc, argv); + if (PyStatus_Exception(status)) + Py_ExitStatusException(status); + + PyConfig_InitPythonConfig(config); +#if PY_VERSION_HEX >= 0x030b0000 /* 3.11 */ + config->safe_path = 0; +#endif + + status = PyConfig_SetBytesArgv(config, argc, argv); + if (PyStatus_Exception(status)) + Py_ExitStatusException(status); + + PyConfig_SetBytesString(config, &config->program_name, + argc > 0 ? argv[0] : "clippy"); + if (argc > 1) + PyConfig_SetBytesString(config, &config->run_filename, argv[1]); + + PyImport_AppendInittab("_clippy", command_py_init); + + status = Py_InitializeFromConfig(config); + if (PyStatus_Exception(status)) + Py_ExitStatusException(status); + + PyConfig_Clear(config); + + return Py_RunMain(); +} + +#else /* Python < 3.8 */ +/* old python init/config API, deprecated in Python 3.11 */ +#if PY_MAJOR_VERSION >= 3 +#define pychar wchar_t +static wchar_t *wconv(const char *s) +{ + size_t outlen = s ? mbstowcs(NULL, s, 0) : 0; + wchar_t *out = malloc((outlen + 1) * sizeof(wchar_t)); + + if (outlen > 0) + mbstowcs(out, s, outlen); + out[outlen] = 0; + return out; +} +#else +#define pychar char +#define wconv(x) x +#endif + +int main(int argc, char **argv) +{ + pychar **wargv; + +#if PY_VERSION_HEX >= 0x03040000 /* 3.4 */ + Py_SetStandardStreamEncoding("UTF-8", NULL); +#endif + wchar_t *name = wconv(argv[0]); + Py_SetProgramName(name); + PyImport_AppendInittab("_clippy", command_py_init); + + Py_Initialize(); + + wargv = malloc(argc * sizeof(pychar *)); + for (int i = 1; i < argc; i++) + wargv[i - 1] = wconv(argv[i]); + PySys_SetArgv(argc - 1, wargv); + + const char *pyfile = argc > 1 ? argv[1] : NULL; + FILE *fp; + if (pyfile) { + fp = fopen(pyfile, "r"); + if (!fp) { + fprintf(stderr, "%s: %s\n", pyfile, strerror(errno)); + + free(name); + return 1; + } + } else { + fp = stdin; + char *ver = strdup(Py_GetVersion()); + char *cr = strchr(ver, '\n'); + if (cr) + *cr = ' '; + fprintf(stderr, "clippy interactive shell\n(Python %s)\n", ver); + free(ver); + PyRun_SimpleString( + "import rlcompleter, readline\n" + "readline.parse_and_bind('tab: complete')"); + } + + if (PyRun_AnyFile(fp, pyfile)) { + if (PyErr_Occurred()) + PyErr_Print(); + + free(name); + return 1; + } + Py_Finalize(); + +#if PY_MAJOR_VERSION >= 3 + for (int i = 1; i < argc; i++) + free(wargv[i - 1]); +#endif + free(name); + free(wargv); + return 0; +} +#endif /* Python < 3.8 */ + +/* and now for the ugly part... provide simplified logging functions so we + * don't need to link libzebra (which would be a circular build dep) */ + +#include "log.h" + +PRINTFRR(3, 0) +void vzlogx(const struct xref_logmsg *xref, int prio, + const char *format, va_list args) +{ + vfprintf(stderr, format, args); + fputs("\n", stderr); +} + +void memory_oom(size_t size, const char *name) +{ + abort(); +} diff --git a/lib/clippy.h b/lib/clippy.h new file mode 100644 index 0000000..d6aa642 --- /dev/null +++ b/lib/clippy.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * clippy (CLI preparator in python) + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +#ifndef _FRR_CLIPPY_H +#define _FRR_CLIPPY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyObject *clippy_parse(PyObject *self, PyObject *args); +extern PyMODINIT_FUNC command_py_init(void); +extern bool elf_py_init(PyObject *pymod); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_CLIPPY_H */ diff --git a/lib/command.c b/lib/command.c new file mode 100644 index 0000000..51f2529 --- /dev/null +++ b/lib/command.c @@ -0,0 +1,2648 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLI backend interface. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + */ + +#include +#include +#include +#include + +#include + +#include "command.h" +#include "frrstr.h" +#include "memory.h" +#include "log.h" +#include "log_vty.h" +#include "frrevent.h" +#include "vector.h" +#include "linklist.h" +#include "vty.h" +#include "workqueue.h" +#include "vrf.h" +#include "command_match.h" +#include "command_graph.h" +#include "qobj.h" +#include "defaults.h" +#include "libfrr.h" +#include "jhash.h" +#include "hook.h" +#include "lib_errors.h" +#include "mgmt_be_client.h" +#include "mgmt_fe_client.h" +#include "northbound_cli.h" +#include "network.h" +#include "routemap.h" + +#include "frrscript.h" + +#include "lib/config_paths.h" + +DEFINE_MTYPE_STATIC(LIB, HOST, "Host config"); +DEFINE_MTYPE(LIB, COMPLETION, "Completion item"); + +#define item(x) \ + { \ + x, #x \ + } + +/* clang-format off */ +const struct message tokennames[] = { + item(WORD_TKN), + item(VARIABLE_TKN), + item(RANGE_TKN), + item(IPV4_TKN), + item(IPV4_PREFIX_TKN), + item(IPV6_TKN), + item(IPV6_PREFIX_TKN), + item(MAC_TKN), + item(MAC_PREFIX_TKN), + item(ASNUM_TKN), + item(FORK_TKN), + item(JOIN_TKN), + item(START_TKN), + item(END_TKN), + item(NEG_ONLY_TKN), + {0}, +}; +/* clang-format on */ + +/* Command vector which includes some level of command lists. Normally + each daemon maintains each own cmdvec. */ +vector cmdvec = NULL; + +/* Host information structure. */ +struct host host; + +/* for vtysh, put together CLI trees only when switching into node */ +static bool defer_cli_tree; + +/* + * Returns host.name if any, otherwise + * it returns the system hostname. + */ +const char *cmd_hostname_get(void) +{ + return host.name; +} + +/* + * Returns unix domainname + */ +const char *cmd_domainname_get(void) +{ + return host.domainname; +} + +const char *cmd_system_get(void) +{ + return host.system; +} + +const char *cmd_release_get(void) +{ + return host.release; +} + +const char *cmd_version_get(void) +{ + return host.version; +} + +bool cmd_allow_reserved_ranges_get(void) +{ + return host.allow_reserved_ranges; +} + +const char *cmd_software_version_get(void) +{ + return FRR_FULL_NAME "/" FRR_VERSION; +} + +static int root_on_exit(struct vty *vty); + +/* Standard command node structures. */ +static struct cmd_node auth_node = { + .name = "auth", + .node = AUTH_NODE, + .prompt = "Password: ", +}; + +static struct cmd_node view_node = { + .name = "view", + .node = VIEW_NODE, + .prompt = "%s> ", + .node_exit = root_on_exit, +}; + +static struct cmd_node auth_enable_node = { + .name = "auth enable", + .node = AUTH_ENABLE_NODE, + .prompt = "Password: ", +}; + +static struct cmd_node enable_node = { + .name = "enable", + .node = ENABLE_NODE, + .prompt = "%s# ", + .node_exit = root_on_exit, +}; + +static int config_write_host(struct vty *vty); +static struct cmd_node config_node = { + .name = "config", + .node = CONFIG_NODE, + .parent_node = ENABLE_NODE, + .prompt = "%s(config)# ", + .config_write = config_write_host, + .node_exit = vty_config_node_exit, +}; + +/* This is called from main when a daemon is invoked with -v or --version. */ +void print_version(const char *progname) +{ + printf("%s version %s\n", progname, FRR_VERSION); + printf("%s\n", FRR_COPYRIGHT); +#ifdef ENABLE_VERSION_BUILD_CONFIG + printf("configured with:\n\t%s\n", FRR_CONFIG_ARGS); +#endif +} + +char *argv_concat(struct cmd_token **argv, int argc, int shift) +{ + int cnt = MAX(argc - shift, 0); + const char *argstr[cnt + 1]; + + if (!cnt) + return NULL; + + for (int i = 0; i < cnt; i++) + argstr[i] = argv[i + shift]->arg; + + return frrstr_join(argstr, cnt, " "); +} + +vector cmd_make_strvec(const char *string) +{ + if (!string) + return NULL; + + const char *copy = string; + + /* skip leading whitespace */ + while (isspace((unsigned char)*copy) && *copy != '\0') + copy++; + + /* if the entire string was whitespace or a comment, return */ + if (*copy == '\0' || *copy == '!' || *copy == '#') + return NULL; + + vector result = frrstr_split_vec(copy, "\n\r\t "); + + for (unsigned int i = 0; i < vector_active(result); i++) { + if (strlen(vector_slot(result, i)) == 0) { + XFREE(MTYPE_TMP, vector_slot(result, i)); + vector_unset(result, i); + } + } + + vector_compact(result); + + return result; +} + +void cmd_free_strvec(vector v) +{ + frrstr_strvec_free(v); +} + +/** + * Convenience function for accessing argv data. + * + * @param argc + * @param argv + * @param text definition snippet of the desired token + * @param index the starting index, and where to store the + * index of the found token if it exists + * @return 1 if found, 0 otherwise + */ +int argv_find(struct cmd_token **argv, int argc, const char *text, int *index) +{ + int found = 0; + for (int i = *index; i < argc && found == 0; i++) + if ((found = strmatch(text, argv[i]->text))) + *index = i; + return found; +} + +static unsigned int cmd_hash_key(const void *p) +{ + int size = sizeof(p); + + return jhash(p, size, 0); +} + +static bool cmd_hash_cmp(const void *a, const void *b) +{ + return a == b; +} + +/* Install top node of command vector. */ +void install_node(struct cmd_node *node) +{ +#define CMD_HASH_STR_SIZE 256 + char hash_name[CMD_HASH_STR_SIZE]; + + vector_set_index(cmdvec, node->node, node); + node->cmdgraph = graph_new(); + node->cmd_vector = vector_init(VECTOR_MIN_SIZE); + // add start node + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(node->cmdgraph, token, + (void (*)(void *)) & cmd_token_del); + + snprintf(hash_name, sizeof(hash_name), "Command Hash: %s", node->name); + node->cmd_hash = + hash_create_size(16, cmd_hash_key, cmd_hash_cmp, hash_name); +} + +/* Return prompt character of specified node. */ +const char *cmd_prompt(enum node_type node) +{ + struct cmd_node *cnode; + + cnode = vector_slot(cmdvec, node); + return cnode->prompt; +} + +void cmd_defer_tree(bool val) +{ + defer_cli_tree = val; +} + +/* Install a command into a node. */ +void _install_element(enum node_type ntype, const struct cmd_element *cmd) +{ + struct cmd_node *cnode; + + /* cmd_init hasn't been called */ + if (!cmdvec) { + fprintf(stderr, "%s called before cmd_init, breakage likely\n", + __func__); + return; + } + + cnode = vector_lookup(cmdvec, ntype); + + if (cnode == NULL) { + fprintf(stderr, + "%s[%s]:\n" + "\tnode %d does not exist.\n" + "\tplease call install_node() before install_element()\n", + cmd->name, cmd->string, ntype); + exit(EXIT_FAILURE); + } + + if (hash_lookup(cnode->cmd_hash, (void *)cmd) != NULL) { + fprintf(stderr, + "%s[%s]:\n" + "\tnode %d (%s) already has this command installed.\n" + "\tduplicate install_element call?\n", + cmd->name, cmd->string, ntype, cnode->name); + return; + } + + (void)hash_get(cnode->cmd_hash, (void *)cmd, hash_alloc_intern); + + if (cnode->graph_built || !defer_cli_tree) { + struct graph *graph = graph_new(); + struct cmd_token *token = + cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(graph, token, + (void (*)(void *)) & cmd_token_del); + + cmd_graph_parse(graph, cmd); + cmd_graph_names(graph); + cmd_graph_merge(cnode->cmdgraph, graph, +1); + graph_delete_graph(graph); + + cnode->graph_built = true; + } + + vector_set(cnode->cmd_vector, (void *)cmd); + + if (ntype == VIEW_NODE) + _install_element(ENABLE_NODE, cmd); +} + +static void cmd_finalize_iter(struct hash_bucket *hb, void *arg) +{ + struct cmd_node *cnode = arg; + const struct cmd_element *cmd = hb->data; + struct graph *graph = graph_new(); + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); + + cmd_graph_parse(graph, cmd); + cmd_graph_names(graph); + cmd_graph_merge(cnode->cmdgraph, graph, +1); + graph_delete_graph(graph); +} + +void cmd_finalize_node(struct cmd_node *cnode) +{ + if (cnode->graph_built) + return; + + hash_iterate(cnode->cmd_hash, cmd_finalize_iter, cnode); + cnode->graph_built = true; +} + +void uninstall_element(enum node_type ntype, const struct cmd_element *cmd) +{ + struct cmd_node *cnode; + + /* cmd_init hasn't been called */ + if (!cmdvec) { + fprintf(stderr, "%s called before cmd_init, breakage likely\n", + __func__); + return; + } + + cnode = vector_lookup(cmdvec, ntype); + + if (cnode == NULL) { + fprintf(stderr, + "%s[%s]:\n" + "\tnode %d does not exist.\n" + "\tplease call install_node() before uninstall_element()\n", + cmd->name, cmd->string, ntype); + exit(EXIT_FAILURE); + } + + if (hash_release(cnode->cmd_hash, (void *)cmd) == NULL) { + fprintf(stderr, + "%s[%s]:\n" + "\tnode %d (%s) does not have this command installed.\n" + "\tduplicate uninstall_element call?\n", + cmd->name, cmd->string, ntype, cnode->name); + return; + } + + vector_unset_value(cnode->cmd_vector, (void *)cmd); + + if (cnode->graph_built) { + struct graph *graph = graph_new(); + struct cmd_token *token = + cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(graph, token, + (void (*)(void *)) & cmd_token_del); + + cmd_graph_parse(graph, cmd); + cmd_graph_names(graph); + cmd_graph_merge(cnode->cmdgraph, graph, -1); + graph_delete_graph(graph); + } + + if (ntype == VIEW_NODE) + uninstall_element(ENABLE_NODE, cmd); +} + + +static const unsigned char itoa64[] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void to64(char *s, long v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} + +static char *zencrypt(const char *passwd) +{ + char salt[6]; + struct timeval tv; + + gettimeofday(&tv, 0); + + to64(&salt[0], frr_weak_random(), 3); + to64(&salt[3], tv.tv_usec, 3); + salt[5] = '\0'; + + return crypt(passwd, salt); +} + +static bool full_cli; + +/* This function write configuration of this host. */ +static int config_write_host(struct vty *vty) +{ + const char *name; + + name = cmd_hostname_get(); + if (name && name[0] != '\0') + vty_out(vty, "hostname %s\n", name); + + name = cmd_domainname_get(); + if (name && name[0] != '\0') + vty_out(vty, "domainname %s\n", name); + + if (cmd_allow_reserved_ranges_get()) + vty_out(vty, "allow-reserved-ranges\n"); + + /* The following are all configuration commands that are not sent to + * watchfrr. For instance watchfrr is hardcoded to log to syslog so + * we would always display 'log syslog informational' in the config + * which would cause other daemons to then switch to syslog when they + * parse frr.conf. + */ + if (full_cli) { + if (host.encrypt) { + if (host.password_encrypt) + vty_out(vty, "password 8 %s\n", + host.password_encrypt); + if (host.enable_encrypt) + vty_out(vty, "enable password 8 %s\n", + host.enable_encrypt); + } else { + if (host.password) + vty_out(vty, "password %s\n", host.password); + if (host.enable) + vty_out(vty, "enable password %s\n", + host.enable); + } + log_config_write(vty); + + if (!cputime_enabled) + vty_out(vty, "no service cputime-stats\n"); + + if (!cputime_threshold) + vty_out(vty, "no service cputime-warning\n"); + else if (cputime_threshold != CONSUMED_TIME_CHECK) + vty_out(vty, "service cputime-warning %lu\n", + cputime_threshold / 1000); + + if (!walltime_threshold) + vty_out(vty, "no service walltime-warning\n"); + else if (walltime_threshold != CONSUMED_TIME_CHECK) + vty_out(vty, "service walltime-warning %lu\n", + walltime_threshold / 1000); + + if (host.advanced) + vty_out(vty, "service advanced-vty\n"); + + if (host.encrypt) + vty_out(vty, "service password-encryption\n"); + + if (host.lines >= 0) + vty_out(vty, "service terminal-length %d\n", + host.lines); + + if (host.motdfile) + vty_out(vty, "banner motd file %s\n", host.motdfile); + else if (host.motd + && strncmp(host.motd, FRR_DEFAULT_MOTD, + strlen(host.motd))) + vty_out(vty, "banner motd line %s\n", host.motd); + else if (!host.motd) + vty_out(vty, "no banner motd\n"); + } + + if (debug_memstats_at_exit) + vty_out(vty, "!\ndebug memstats-at-exit\n"); + + return 1; +} + +/* Utility function for getting command graph. */ +static struct graph *cmd_node_graph(vector v, enum node_type ntype) +{ + struct cmd_node *cnode = vector_slot(v, ntype); + + cmd_finalize_node(cnode); + return cnode->cmdgraph; +} + +static int cmd_try_do_shortcut(enum node_type node, char *first_word) +{ + if (first_word != NULL && node != AUTH_NODE && node != VIEW_NODE + && node != AUTH_ENABLE_NODE && 0 == strcmp("do", first_word)) + return 1; + return 0; +} + +/** + * Compare function for cmd_token. + * Used with qsort to sort command completions. + */ +static int compare_completions(const void *fst, const void *snd) +{ + const struct cmd_token *first = *(const struct cmd_token * const *)fst, + *secnd = *(const struct cmd_token * const *)snd; + return strcmp(first->text, secnd->text); +} + +/** + * Takes a list of completions returned by command_complete, + * dedeuplicates them based on both text and description, + * sorts them, and returns them as a vector. + * + * @param completions linked list of cmd_token + * @return deduplicated and sorted vector with + */ +vector completions_to_vec(struct list *completions) +{ + vector comps = vector_init(VECTOR_MIN_SIZE); + + struct listnode *ln; + struct cmd_token *token, *cr = NULL; + unsigned int i, exists; + for (ALL_LIST_ELEMENTS_RO(completions, ln, token)) { + if (token->type == END_TKN && (cr = token)) + continue; + + // linear search for token in completions vector + exists = 0; + for (i = 0; i < vector_active(comps) && !exists; i++) { + struct cmd_token *curr = vector_slot(comps, i); +#ifdef VTYSH_DEBUG + exists = !strcmp(curr->text, token->text) + && !strcmp(curr->desc, token->desc); +#else + exists = !strcmp(curr->text, token->text); +#endif /* VTYSH_DEBUG */ + } + + if (!exists) + vector_set(comps, token); + } + + // sort completions + qsort(comps->index, vector_active(comps), sizeof(void *), + &compare_completions); + + // make the first element, if it is present + if (cr) { + vector_set_index(comps, vector_active(comps), NULL); + memmove(comps->index + 1, comps->index, + (comps->alloced - 1) * sizeof(void *)); + vector_set_index(comps, 0, cr); + } + + return comps; +} +/** + * Generates a vector of cmd_token representing possible completions + * on the current input. + * + * @param vline the vectorized input line + * @param vty the vty with the node to match on + * @param status pointer to matcher status code + * @return vector of struct cmd_token * with possible completions + */ +static vector cmd_complete_command_real(vector vline, struct vty *vty, + int *status) +{ + struct list *completions; + struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node); + + enum matcher_rv rv = command_complete(cmdgraph, vline, &completions); + + if (MATCHER_ERROR(rv)) { + *status = CMD_ERR_NO_MATCH; + return NULL; + } + + vector comps = completions_to_vec(completions); + list_delete(&completions); + + // set status code appropriately + switch (vector_active(comps)) { + case 0: + *status = CMD_ERR_NO_MATCH; + break; + case 1: + *status = CMD_COMPLETE_FULL_MATCH; + break; + default: + *status = CMD_COMPLETE_LIST_MATCH; + } + + return comps; +} + +vector cmd_describe_command(vector vline, struct vty *vty, int *status) +{ + vector ret; + + if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) { + enum node_type onode; + int orig_xpath_index; + vector shifted_vline; + unsigned int index; + + onode = vty->node; + orig_xpath_index = vty->xpath_index; + vty->node = ENABLE_NODE; + vty->xpath_index = 0; + /* We can try it on enable node, cos' the vty is authenticated + */ + + shifted_vline = vector_init(vector_count(vline)); + /* use memcpy? */ + for (index = 1; index < vector_active(vline); index++) { + vector_set_index(shifted_vline, index - 1, + vector_lookup(vline, index)); + } + + ret = cmd_complete_command_real(shifted_vline, vty, status); + + vector_free(shifted_vline); + vty->node = onode; + vty->xpath_index = orig_xpath_index; + return ret; + } + + return cmd_complete_command_real(vline, vty, status); +} + +static struct list *varhandlers = NULL; + +static int __add_key_comp(const struct lyd_node *dnode, void *arg) +{ + const char *key_value = yang_dnode_get_string(dnode, NULL); + + vector_set((vector)arg, XSTRDUP(MTYPE_COMPLETION, key_value)); + + return YANG_ITER_CONTINUE; +} + +static void __get_list_keys(vector comps, const char *xpath) +{ + yang_dnode_iterate(__add_key_comp, comps, + vty_shared_candidate_config->dnode, "%s", xpath); +} + +void cmd_variable_complete(struct cmd_token *token, const char *arg, + vector comps) +{ + struct listnode *ln; + const struct cmd_variable_handler *cvh; + size_t i, argsz; + vector tmpcomps; + + tmpcomps = arg ? vector_init(VECTOR_MIN_SIZE) : comps; + + for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) { + if (cvh->tokenname && strcmp(cvh->tokenname, token->text)) + continue; + if (cvh->varname && (!token->varname + || strcmp(cvh->varname, token->varname))) + continue; + if (cvh->xpath) + __get_list_keys(tmpcomps, cvh->xpath); + if (cvh->completions) + cvh->completions(tmpcomps, token); + break; + } + + if (!arg) + return; + + argsz = strlen(arg); + for (i = vector_active(tmpcomps); i; i--) { + char *item = vector_slot(tmpcomps, i - 1); + if (strlen(item) >= argsz && !strncmp(item, arg, argsz)) + vector_set(comps, item); + else + XFREE(MTYPE_COMPLETION, item); + } + vector_free(tmpcomps); +} + +#define AUTOCOMP_INDENT 5 + +char *cmd_variable_comp2str(vector comps, unsigned short cols) +{ + size_t bsz = 16; + char *buf = XCALLOC(MTYPE_TMP, bsz); + int lc = AUTOCOMP_INDENT; + size_t cs = AUTOCOMP_INDENT; + size_t itemlen; + snprintf(buf, bsz, "%*s", AUTOCOMP_INDENT, ""); + for (size_t j = 0; j < vector_active(comps); j++) { + char *item = vector_slot(comps, j); + itemlen = strlen(item); + + size_t next_sz = cs + itemlen + AUTOCOMP_INDENT + 3; + + if (next_sz > bsz) { + /* Make sure the buf size is large enough */ + bsz = next_sz; + buf = XREALLOC(MTYPE_TMP, buf, bsz); + } + if (lc + itemlen + 1 >= cols) { + cs += snprintf(&buf[cs], bsz - cs, "\n%*s", + AUTOCOMP_INDENT, ""); + lc = AUTOCOMP_INDENT; + } + + size_t written = snprintf(&buf[cs], bsz - cs, "%s ", item); + lc += written; + cs += written; + XFREE(MTYPE_COMPLETION, item); + vector_set_index(comps, j, NULL); + } + return buf; +} + +void cmd_variable_handler_register(const struct cmd_variable_handler *cvh) +{ + if (!varhandlers) + return; + + for (; cvh->completions || cvh->xpath; cvh++) + listnode_add(varhandlers, (void *)cvh); +} + +DEFUN_HIDDEN (autocomplete, + autocomplete_cmd, + "autocomplete TYPE TEXT VARNAME", + "Autocompletion handler (internal, for vtysh)\n" + "cmd_token->type\n" + "cmd_token->text\n" + "cmd_token->varname\n") +{ + struct cmd_token tok; + vector comps = vector_init(32); + size_t i; + + memset(&tok, 0, sizeof(tok)); + tok.type = atoi(argv[1]->arg); + tok.text = argv[2]->arg; + tok.varname = argv[3]->arg; + if (!strcmp(tok.varname, "-")) + tok.varname = NULL; + + cmd_variable_complete(&tok, NULL, comps); + + for (i = 0; i < vector_active(comps); i++) { + char *text = vector_slot(comps, i); + vty_out(vty, "%s\n", text); + XFREE(MTYPE_COMPLETION, text); + } + + vector_free(comps); + return CMD_SUCCESS; +} + +/** + * Generate possible tab-completions for the given input. This function only + * returns results that would result in a valid command if used as Readline + * completions (as is the case in vtysh). For instance, if the passed vline ends + * with '4.3.2', the strings 'A.B.C.D' and 'A.B.C.D/M' will _not_ be returned. + * + * @param vline vectorized input line + * @param vty the vty + * @param status location to store matcher status code in + * @return set of valid strings for use with Readline as tab-completions. + */ + +char **cmd_complete_command(vector vline, struct vty *vty, int *status) +{ + char **ret = NULL; + int original_node = vty->node; + vector input_line = vector_init(vector_count(vline)); + + // if the first token is 'do' we'll want to execute the command in the + // enable node + int do_shortcut = cmd_try_do_shortcut(vty->node, vector_slot(vline, 0)); + vty->node = do_shortcut ? ENABLE_NODE : original_node; + + // construct the input line we'll be matching on + unsigned int offset = (do_shortcut) ? 1 : 0; + for (unsigned index = 0; index + offset < vector_active(vline); index++) + vector_set_index(input_line, index, + vector_lookup(vline, index + offset)); + + // get token completions -- this is a copying operation + vector comps = NULL, initial_comps; + initial_comps = cmd_complete_command_real(input_line, vty, status); + + if (!MATCHER_ERROR(*status)) { + assert(initial_comps); + // filter out everything that is not suitable for a + // tab-completion + comps = vector_init(VECTOR_MIN_SIZE); + for (unsigned int i = 0; i < vector_active(initial_comps); + i++) { + struct cmd_token *token = vector_slot(initial_comps, i); + if (token->type == WORD_TKN) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, + token->text)); + else if (IS_VARYING_TOKEN(token->type)) { + const char *ref = vector_lookup( + vline, vector_active(vline) - 1); + cmd_variable_complete(token, ref, comps); + } + } + vector_free(initial_comps); + + // since we filtered results, we need to re-set status code + switch (vector_active(comps)) { + case 0: + *status = CMD_ERR_NO_MATCH; + break; + case 1: + *status = CMD_COMPLETE_FULL_MATCH; + break; + default: + *status = CMD_COMPLETE_LIST_MATCH; + } + + // copy completions text into an array of char* + ret = XMALLOC(MTYPE_TMP, + (vector_active(comps) + 1) * sizeof(char *)); + unsigned int i; + for (i = 0; i < vector_active(comps); i++) { + ret[i] = vector_slot(comps, i); + } + // set the last element to NULL, because this array is used in + // a Readline completion_generator function which expects NULL + // as a sentinel value + ret[i] = NULL; + vector_free(comps); + comps = NULL; + } else if (initial_comps) + vector_free(initial_comps); + + // comps should always be null here + assert(!comps); + + // free the adjusted input line + vector_free(input_line); + + // reset vty->node to its original value + vty->node = original_node; + + return ret; +} + +/* return parent node */ +/* MUST eventually converge on CONFIG_NODE */ +enum node_type node_parent(enum node_type node) +{ + struct cmd_node *cnode; + + assert(node > CONFIG_NODE); + + cnode = vector_lookup(cmdvec, node); + + return cnode->parent_node; +} + +/* Execute command by argument vline vector. */ +static int cmd_execute_command_real(vector vline, struct vty *vty, + const struct cmd_element **cmd, + unsigned int up_level) +{ + struct list *argv_list; + enum matcher_rv status; + const struct cmd_element *matched_element = NULL; + unsigned int i; + int xpath_index = vty->xpath_index; + int node = vty->node; + + /* only happens for legacy split config file load; need to check for + * a match before calling node_exit handlers below + */ + for (i = 0; i < up_level; i++) { + struct cmd_node *cnode; + + if (node <= CONFIG_NODE) + return CMD_NO_LEVEL_UP; + + cnode = vector_slot(cmdvec, node); + node = node_parent(node); + + if (xpath_index > 0 && !cnode->no_xpath) + xpath_index--; + } + + struct graph *cmdgraph = cmd_node_graph(cmdvec, node); + status = command_match(cmdgraph, vline, &argv_list, &matched_element); + + if (cmd) + *cmd = matched_element; + + // if matcher error, return corresponding CMD_ERR + if (MATCHER_ERROR(status)) { + if (argv_list) + list_delete(&argv_list); + switch (status) { + case MATCHER_INCOMPLETE: + return CMD_ERR_INCOMPLETE; + case MATCHER_AMBIGUOUS: + return CMD_ERR_AMBIGUOUS; + case MATCHER_NO_MATCH: + case MATCHER_OK: + return CMD_ERR_NO_MATCH; + } + } + + for (i = 0; i < up_level; i++) + cmd_exit(vty); + + // build argv array from argv list + struct cmd_token **argv = XMALLOC( + MTYPE_TMP, argv_list->count * sizeof(struct cmd_token *)); + struct listnode *ln; + struct cmd_token *token; + + i = 0; + for (ALL_LIST_ELEMENTS_RO(argv_list, ln, token)) + argv[i++] = token; + + int argc = argv_list->count; + + int ret; + if (matched_element->daemon) + ret = CMD_SUCCESS_DAEMON; + else { + if (vty->config) { + /* Clear array of enqueued configuration changes. */ + vty->num_cfg_changes = 0; + memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes)); + + /* Regenerate candidate configuration if necessary. */ + if (frr_get_cli_mode() == FRR_CLI_CLASSIC + && running_config->version + > vty->candidate_config->version) + nb_config_replace(vty->candidate_config, + running_config, true); + + /* + * Perform pending commit (if any) before executing + * non-YANG command. + */ + if (!(matched_element->attr & CMD_ATTR_YANG)) + (void)nb_cli_pending_commit_check(vty); + } + + ret = matched_element->func(matched_element, vty, argc, argv); + } + + // delete list and cmd_token's in it + list_delete(&argv_list); + XFREE(MTYPE_TMP, argv); + + return ret; +} + +/** + * Execute a given command, handling things like "do ..." and checking + * whether the given command might apply at a parent node if doesn't + * apply for the current node. + * + * @param vline Command line input, vector of char* where each element is + * one input token. + * @param vty The vty context in which the command should be executed. + * @param cmd Pointer where the struct cmd_element of the matched command + * will be stored, if any. May be set to NULL if this info is + * not needed. + * @param vtysh If set != 0, don't lookup the command at parent nodes. + * @return The status of the command that has been executed or an error code + * as to why no command could be executed. + */ +int cmd_execute_command(vector vline, struct vty *vty, + const struct cmd_element **cmd, int vtysh) +{ + int ret, saved_ret = 0; + enum node_type onode, try_node; + int orig_xpath_index; + + onode = try_node = vty->node; + orig_xpath_index = vty->xpath_index; + + if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) { + vector shifted_vline; + unsigned int index; + + vty->node = ENABLE_NODE; + vty->xpath_index = 0; + /* We can try it on enable node, cos' the vty is authenticated + */ + + shifted_vline = vector_init(vector_count(vline)); + /* use memcpy? */ + for (index = 1; index < vector_active(vline); index++) + vector_set_index(shifted_vline, index - 1, + vector_lookup(vline, index)); + + ret = cmd_execute_command_real(shifted_vline, vty, cmd, 0); + + vector_free(shifted_vline); + vty->node = onode; + vty->xpath_index = orig_xpath_index; + return ret; + } + + saved_ret = ret = + cmd_execute_command_real(vline, vty, cmd, 0); + + if (vtysh) + return saved_ret; + + if (ret != CMD_SUCCESS && ret != CMD_WARNING + && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE + && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED) { + /* This assumes all nodes above CONFIG_NODE are childs of + * CONFIG_NODE */ + while (vty->node > CONFIG_NODE) { + struct cmd_node *cnode = vector_slot(cmdvec, try_node); + + try_node = node_parent(try_node); + vty->node = try_node; + if (vty->xpath_index > 0 && !cnode->no_xpath) + vty->xpath_index--; + + ret = cmd_execute_command_real(vline, vty, cmd, 0); + if (ret == CMD_SUCCESS || ret == CMD_WARNING + || ret == CMD_ERR_AMBIGUOUS || ret == CMD_ERR_INCOMPLETE + || ret == CMD_NOT_MY_INSTANCE + || ret == CMD_WARNING_CONFIG_FAILED) + return ret; + } + /* no command succeeded, reset the vty to the original node */ + vty->node = onode; + vty->xpath_index = orig_xpath_index; + } + + /* return command status for original node */ + return saved_ret; +} + +/** + * Execute a given command, matching it strictly against the current node. + * This mode is used when reading config files. + * + * @param vline Command line input, vector of char* where each element is + * one input token. + * @param vty The vty context in which the command should be executed. + * @param cmd Pointer where the struct cmd_element* of the matched command + * will be stored, if any. May be set to NULL if this info is + * not needed. + * @return The status of the command that has been executed or an error code + * as to why no command could be executed. + */ +int cmd_execute_command_strict(vector vline, struct vty *vty, + const struct cmd_element **cmd) +{ + return cmd_execute_command_real(vline, vty, cmd, 0); +} + +/* + * Hook for preprocessing command string before executing. + * + * All subscribers are called with the raw command string that is to be + * executed. If any changes are to be made, a new string should be allocated + * with MTYPE_TMP and *cmd_out updated to point to this new string. The caller + * is then responsible for freeing this string. + * + * All processing functions must be mutually exclusive in their action, i.e. if + * one subscriber decides to modify the command, all others must not modify it + * when called. Feeding the output of one processing command into a subsequent + * one is not supported. + * + * This hook is intentionally internal to the command processing system. + * + * cmd_in + * The raw command string. + * + * cmd_out + * The result of any processing. + */ +DECLARE_HOOK(cmd_execute, + (struct vty *vty, const char *cmd_in, char **cmd_out), + (vty, cmd_in, cmd_out)); +DEFINE_HOOK(cmd_execute, (struct vty *vty, const char *cmd_in, char **cmd_out), + (vty, cmd_in, cmd_out)); + +/* Hook executed after a CLI command. */ +DECLARE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec), + (vty, cmd_exec)); +DEFINE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec), + (vty, cmd_exec)); + +/* + * cmd_execute hook subscriber to handle `|` actions. + */ +static int handle_pipe_action(struct vty *vty, const char *cmd_in, + char **cmd_out) +{ + /* look for `|` */ + char *orig, *working, *token, *u; + char *pipe = strstr(cmd_in, "| "); + int ret = 0; + + if (!pipe) + return 0; + + /* duplicate string for processing purposes, not including pipe */ + orig = working = XSTRDUP(MTYPE_TMP, pipe + 2); + + /* retrieve action */ + token = strsep(&working, " "); + assert(token); + + /* match result to known actions */ + if (strmatch(token, "include")) { + /* the remaining text should be a regexp */ + char *regexp = working; + + if (!regexp) { + vty_out(vty, "%% Need a regexp to filter with\n"); + ret = 1; + goto fail; + } + + bool succ = vty_set_include(vty, regexp); + + if (!succ) { + vty_out(vty, "%% Bad regexp '%s'\n", regexp); + ret = 1; + goto fail; + } + *cmd_out = XSTRDUP(MTYPE_TMP, cmd_in); + u = *cmd_out; + strsep(&u, "|"); + } else { + vty_out(vty, "%% Unknown action '%s'\n", token); + ret = 1; + goto fail; + } + +fail: + XFREE(MTYPE_TMP, orig); + return ret; +} + +static int handle_pipe_action_done(struct vty *vty, const char *cmd_exec) +{ + if (vty->filter) + vty_set_include(vty, NULL); + + return 0; +} + +int cmd_execute(struct vty *vty, const char *cmd, + const struct cmd_element **matched, int vtysh) +{ + int ret; + char *cmd_out = NULL; + const char *cmd_exec = NULL; + vector vline; + + ret = hook_call(cmd_execute, vty, cmd, &cmd_out); + if (ret) { + ret = CMD_WARNING; + goto free; + } + + cmd_exec = cmd_out ? (const char *)cmd_out : cmd; + + vline = cmd_make_strvec(cmd_exec); + + if (vline) { + ret = cmd_execute_command(vline, vty, matched, vtysh); + cmd_free_strvec(vline); + } else { + ret = CMD_SUCCESS; + } + +free: + hook_call(cmd_execute_done, vty, cmd_exec); + + XFREE(MTYPE_TMP, cmd_out); + + return ret; +} + + +/** + * Parse one line of config, walking up the parse tree attempting to find a + * match + * + * @param vty The vty context in which the command should be executed. + * @param cmd Pointer where the struct cmd_element* of the match command + * will be stored, if any. May be set to NULL if this info is + * not needed. + * @param use_daemon Boolean to control whether or not we match on + * CMD_SUCCESS_DAEMON + * or not. + * @return The status of the command that has been executed or an error code + * as to why no command could be executed. + */ +int command_config_read_one_line(struct vty *vty, + const struct cmd_element **cmd, + uint32_t line_num, int use_daemon) +{ + vector vline; + int ret; + unsigned up_level = 0; + + vline = cmd_make_strvec(vty->buf); + + /* In case of comment line */ + if (vline == NULL) + return CMD_SUCCESS; + + /* Execute configuration command : this is strict match */ + ret = cmd_execute_command_strict(vline, vty, cmd); + + /* The logic for trying parent nodes is in cmd_execute_command_real() + * since calling ->node_exit() correctly is a bit involved. This is + * also the only reason CMD_NO_LEVEL_UP exists. + */ + while (!(use_daemon && ret == CMD_SUCCESS_DAEMON) + && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO) + && ret != CMD_SUCCESS && ret != CMD_WARNING + && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE + && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED + && ret != CMD_NO_LEVEL_UP) + ret = cmd_execute_command_real(vline, vty, cmd, ++up_level); + + if (ret == CMD_NO_LEVEL_UP) + ret = CMD_ERR_NO_MATCH; + + if (ret != CMD_SUCCESS && + ret != CMD_WARNING && + ret != CMD_SUCCESS_DAEMON) { + struct vty_error *ve = XCALLOC(MTYPE_TMP, sizeof(*ve)); + + memcpy(ve->error_buf, vty->buf, VTY_BUFSIZ); + ve->line_num = line_num; + ve->cmd_ret = ret; + if (!vty->error) + vty->error = list_new(); + + listnode_add(vty->error, ve); + } + + cmd_free_strvec(vline); + + return ret; +} + +/* Configuration make from file. */ +int config_from_file(struct vty *vty, FILE *fp, unsigned int *line_num) +{ + int ret, error_ret = 0; + *line_num = 0; + + while (fgets(vty->buf, VTY_BUFSIZ, fp)) { + ++(*line_num); + + if (vty_log_commands) { + int len = strlen(vty->buf); + + /* now log the command */ + zlog_notice("config-from-file# %.*s", len ? len - 1 : 0, + vty->buf); + } + + ret = command_config_read_one_line(vty, NULL, *line_num, 0); + + if (ret != CMD_SUCCESS && ret != CMD_WARNING + && ret != CMD_ERR_NOTHING_TODO) + error_ret = ret; + } + + if (error_ret) { + return error_ret; + } + + return CMD_SUCCESS; +} + +/* Configuration from terminal */ +DEFUN (config_terminal, + config_terminal_cmd, + "configure [terminal [file-lock]]", + "Configuration from vty interface\n" + "Configuration terminal\n" + "Configuration with locked datastores\n") +{ + return vty_config_enter(vty, false, false, argc == 3); +} + +/* Enable command */ +DEFUN (enable, + config_enable_cmd, + "enable", + "Turn on privileged mode command\n") +{ + /* If enable password is NULL, change to ENABLE_NODE */ + if ((host.enable == NULL && host.enable_encrypt == NULL) + || vty->type == VTY_SHELL_SERV) + vty->node = ENABLE_NODE; + else + vty->node = AUTH_ENABLE_NODE; + + return CMD_SUCCESS; +} + +/* Disable command */ +DEFUN (disable, + config_disable_cmd, + "disable", + "Turn off privileged mode command\n") +{ + if (vty->node == ENABLE_NODE) + vty->node = VIEW_NODE; + return CMD_SUCCESS; +} + +/* Down vty node level. */ +DEFUN_YANG (config_exit, + config_exit_cmd, + "exit", + "Exit current mode and down to previous mode\n") +{ + cmd_exit(vty); + return CMD_SUCCESS; +} + +static int root_on_exit(struct vty *vty) +{ + if (vty_shell(vty)) + exit(0); + else + vty->status = VTY_CLOSE; + return 0; +} + +void cmd_exit(struct vty *vty) +{ + struct cmd_node *cnode = vector_lookup(cmdvec, vty->node); + + if (cnode->node_exit) { + if (!cnode->node_exit(vty)) + return; + } + if (cnode->parent_node) + vty->node = cnode->parent_node; + if (vty->xpath_index > 0 && !cnode->no_xpath) + vty->xpath_index--; +} + +/* ALIAS_FIXME */ +DEFUN (config_quit, + config_quit_cmd, + "quit", + "Exit current mode and down to previous mode\n") +{ + return config_exit(self, vty, argc, argv); +} + + +/* End of configuration. */ +DEFUN (config_end, + config_end_cmd, + "end", + "End current mode and change to enable mode.\n") +{ + if (vty->config) { + vty_config_exit(vty); + vty->node = ENABLE_NODE; + } + return CMD_SUCCESS; +} + +/* Show version. */ +DEFUN (show_version, + show_version_cmd, + "show version", + SHOW_STR + "Displays zebra version\n") +{ + vty_out(vty, "%s %s (%s) on %s(%s).\n", FRR_FULL_NAME, FRR_VERSION, + cmd_hostname_get() ? cmd_hostname_get() : "", cmd_system_get(), + cmd_release_get()); + vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO); +#ifdef ENABLE_VERSION_BUILD_CONFIG + vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS); +#endif + return CMD_SUCCESS; +} + +/* Help display function for all node. */ +DEFUN (config_help, + config_help_cmd, + "help", + "Description of the interactive help system\n") +{ + vty_out(vty, + "Quagga VTY provides advanced help feature. When you need help,\n\ +anytime at the command line please press '?'.\n\ +\n\ +If nothing matches, the help list will be empty and you must backup\n\ + until entering a '?' shows the available options.\n\ +Two styles of help are provided:\n\ +1. Full help is available when you are ready to enter a\n\ +command argument (e.g. 'show ?') and describes each possible\n\ +argument.\n\ +2. Partial help is provided when an abbreviated argument is entered\n\ + and you want to know what arguments match the input\n\ + (e.g. 'show me?'.)\n\n"); + return CMD_SUCCESS; +} + +static void permute(struct graph_node *start, struct vty *vty) +{ + static struct list *position = NULL; + if (!position) + position = list_new(); + + struct cmd_token *stok = start->data; + struct graph_node *gnn; + struct listnode *ln; + + // recursive dfs + listnode_add(position, start); + for (unsigned int i = 0; i < vector_active(start->to); i++) { + struct graph_node *gn = vector_slot(start->to, i); + struct cmd_token *tok = gn->data; + if (tok->attr & CMD_ATTR_HIDDEN) + continue; + else if (tok->type == END_TKN || gn == start) { + vty_out(vty, " "); + for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) { + struct cmd_token *tt = gnn->data; + if (tt->type < SPECIAL_TKN) + vty_out(vty, " %s", tt->text); + } + if (gn == start) + vty_out(vty, "..."); + vty_out(vty, "\n"); + } else { + bool skip = false; + if (stok->type == FORK_TKN && tok->type != FORK_TKN) + for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) + if (gnn == gn) { + skip = true; + break; + } + if (!skip) + permute(gn, vty); + } + } + list_delete_node(position, listtail(position)); +} + +static void print_cmd(struct vty *vty, const char *cmd) +{ + int i, j, len = strlen(cmd); + char buf[len + 1]; + bool skip = false; + + j = 0; + for (i = 0; i < len; i++) { + /* skip varname */ + if (cmd[i] == '$') + skip = true; + else if (strchr(" ()<>[]{}|", cmd[i])) + skip = false; + + if (skip) + continue; + + if (isspace(cmd[i])) { + /* skip leading whitespace */ + if (i == 0) + continue; + /* skip trailing whitespace */ + if (i == len - 1) + continue; + /* skip all whitespace after opening brackets or pipe */ + if (strchr("(<[{|", cmd[i - 1])) { + while (isspace(cmd[i + 1])) + i++; + continue; + } + /* skip repeated whitespace */ + if (isspace(cmd[i + 1])) + continue; + /* skip whitespace before closing brackets or pipe */ + if (strchr(")>]}|", cmd[i + 1])) + continue; + /* convert tabs to spaces */ + if (cmd[i] == '\t') { + buf[j++] = ' '; + continue; + } + } + + buf[j++] = cmd[i]; + } + buf[j] = 0; + + vty_out(vty, "%s\n", buf); +} + +int cmd_list_cmds(struct vty *vty, int do_permute) +{ + struct cmd_node *node = vector_slot(cmdvec, vty->node); + + if (do_permute) { + cmd_finalize_node(node); + permute(vector_slot(node->cmdgraph->nodes, 0), vty); + } else { + /* loop over all commands at this node */ + const struct cmd_element *element = NULL; + for (unsigned int i = 0; i < vector_active(node->cmd_vector); + i++) + if ((element = vector_slot(node->cmd_vector, i)) && + !(element->attr & CMD_ATTR_HIDDEN)) { + vty_out(vty, " "); + print_cmd(vty, element->string); + } + } + return CMD_SUCCESS; +} + +/* Help display function for all node. */ +DEFUN (config_list, + config_list_cmd, + "list [permutations]", + "Print command list\n" + "Print all possible command permutations\n") +{ + return cmd_list_cmds(vty, argc == 2); +} + +DEFUN (show_commandtree, + show_commandtree_cmd, + "show commandtree [permutations]", + SHOW_STR + "Show command tree\n" + "Permutations that we are interested in\n") +{ + return cmd_list_cmds(vty, argc == 3); +} + +DEFUN_HIDDEN(show_cli_graph, + show_cli_graph_cmd, + "show cli graph", + SHOW_STR + "CLI reflection\n" + "Dump current command space as DOT graph\n") +{ + struct cmd_node *cn = vector_slot(cmdvec, vty->node); + char *dot; + + cmd_finalize_node(cn); + dot = cmd_graph_dump_dot(cn->cmdgraph); + + vty_out(vty, "%s\n", dot); + XFREE(MTYPE_TMP, dot); + return CMD_SUCCESS; +} + +static int vty_write_config(struct vty *vty) +{ + size_t i; + struct cmd_node *node; + + if (host.noconfig) + return CMD_SUCCESS; + + nb_cli_show_config_prepare(running_config, false); + + if (vty->type == VTY_TERM) { + vty_out(vty, "\nCurrent configuration:\n"); + vty_out(vty, "!\n"); + } + + if (strcmp(frr_defaults_version(), FRR_VER_SHORT)) + vty_out(vty, "! loaded from %s\n", frr_defaults_version()); + vty_out(vty, "frr version %s\n", FRR_VER_SHORT); + vty_out(vty, "frr defaults %s\n", frr_defaults_profile()); + vty_out(vty, "!\n"); + + for (i = 0; i < vector_active(cmdvec); i++) + if ((node = vector_slot(cmdvec, i)) && node->config_write) { + if ((*node->config_write)(vty)) + vty_out(vty, "!\n"); + } + + if (vty->type == VTY_TERM) { + vty_out(vty, "end\n"); + } + + return CMD_SUCCESS; +} + +/* cross-reference frr_daemon_state_save in libfrr.c + * the code there is similar but not identical (state files always use the same + * name for the new write, and don't keep a backup of previous state.) + */ +static int file_write_config(struct vty *vty) +{ + int fd, dirfd; + char *config_file, *slash; + char *config_file_tmp = NULL; + char *config_file_sav = NULL; + int ret = CMD_WARNING; + struct vty *file_vty; + struct stat conf_stat; + + if (host.noconfig) + return CMD_SUCCESS; + + /* Check and see if we are operating under vtysh configuration */ + if (host.config == NULL) { + vty_out(vty, + "Can't save to configuration file, using vtysh.\n"); + return CMD_WARNING; + } + + /* Get filename. */ + config_file = host.config; + +#ifndef O_DIRECTORY +#define O_DIRECTORY 0 +#endif + slash = strrchr(config_file, '/'); + if (slash) { + char *config_dir = XSTRDUP(MTYPE_TMP, config_file); + config_dir[slash - config_file] = '\0'; + dirfd = open(config_dir, O_DIRECTORY | O_RDONLY); + XFREE(MTYPE_TMP, config_dir); + } else + dirfd = open(".", O_DIRECTORY | O_RDONLY); + /* if dirfd is invalid, directory sync fails, but we're still OK */ + + size_t config_file_sav_sz = strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1; + config_file_sav = XMALLOC(MTYPE_TMP, config_file_sav_sz); + strlcpy(config_file_sav, config_file, config_file_sav_sz); + strlcat(config_file_sav, CONF_BACKUP_EXT, config_file_sav_sz); + + + config_file_tmp = XMALLOC(MTYPE_TMP, strlen(config_file) + 8); + snprintf(config_file_tmp, strlen(config_file) + 8, "%s.XXXXXX", + config_file); + + /* Open file to configuration write. */ + fd = mkstemp(config_file_tmp); + if (fd < 0) { + vty_out(vty, "Can't open configuration file %s.\n", + config_file_tmp); + goto finished; + } + if (fchmod(fd, CONFIGFILE_MASK) != 0) { + vty_out(vty, "Can't chmod configuration file %s: %s (%d).\n", + config_file_tmp, safe_strerror(errno), errno); + goto finished; + } + + /* Make vty for configuration file. */ + file_vty = vty_new(); + file_vty->wfd = fd; + file_vty->type = VTY_FILE; + + /* Config file header print. */ + vty_out(file_vty, "!\n! Zebra configuration saved from vty\n! "); + vty_time_print(file_vty, 1); + vty_out(file_vty, "!\n"); + vty_write_config(file_vty); + vty_close(file_vty); + + if (stat(config_file, &conf_stat) >= 0) { + if (unlink(config_file_sav) != 0) + if (errno != ENOENT) { + vty_out(vty, + "Can't unlink backup configuration file %s.\n", + config_file_sav); + goto finished; + } + if (link(config_file, config_file_sav) != 0) { + vty_out(vty, + "Can't backup old configuration file %s.\n", + config_file_sav); + goto finished; + } + if (dirfd >= 0) + fsync(dirfd); + } + if (rename(config_file_tmp, config_file) != 0) { + vty_out(vty, "Can't save configuration file %s.\n", + config_file); + goto finished; + } + if (dirfd >= 0) + fsync(dirfd); + + vty_out(vty, "Configuration saved to %s\n", config_file); + ret = CMD_SUCCESS; + +finished: + if (ret != CMD_SUCCESS) + unlink(config_file_tmp); + if (dirfd >= 0) + close(dirfd); + XFREE(MTYPE_TMP, config_file_tmp); + XFREE(MTYPE_TMP, config_file_sav); + return ret; +} + +/* Write current configuration into file. */ + +DEFUN (config_write, + config_write_cmd, + "write []", + "Write running configuration to memory, network, or terminal\n" + "Write to configuration file\n" + "Write configuration currently in memory\n" + "Write configuration to terminal\n") +{ + const int idx_type = 1; + + // if command was 'write terminal' or 'write memory' + if (argc == 2 && (!strcmp(argv[idx_type]->text, "terminal"))) { + return vty_write_config(vty); + } + + return file_write_config(vty); +} + +/* ALIAS_FIXME for 'write ' */ +DEFUN (show_running_config, + show_running_config_cmd, + "show running-config", + SHOW_STR + "running configuration (same as write terminal)\n") +{ + return vty_write_config(vty); +} + +/* ALIAS_FIXME for 'write file' */ +DEFUN (copy_runningconf_startupconf, + copy_runningconf_startupconf_cmd, + "copy running-config startup-config", + "Copy configuration\n" + "Copy running config to... \n" + "Copy running config to startup config (same as write file/memory)\n") +{ + return file_write_config(vty); +} +/** -- **/ + +/* Write startup configuration into the terminal. */ +DEFUN (show_startup_config, + show_startup_config_cmd, + "show startup-config", + SHOW_STR + "Contents of startup configuration\n") +{ + char buf[BUFSIZ]; + FILE *confp; + + if (host.noconfig) + return CMD_SUCCESS; + if (host.config == NULL) + return CMD_WARNING; + + confp = fopen(host.config, "r"); + if (confp == NULL) { + vty_out(vty, "Can't open configuration file [%s] due to '%s'\n", + host.config, safe_strerror(errno)); + return CMD_WARNING; + } + + while (fgets(buf, BUFSIZ, confp)) { + char *cp = buf; + + while (*cp != '\r' && *cp != '\n' && *cp != '\0') + cp++; + *cp = '\0'; + + vty_out(vty, "%s\n", buf); + } + + fclose(confp); + + return CMD_SUCCESS; +} + +int cmd_domainname_set(const char *domainname) +{ + XFREE(MTYPE_HOST, host.domainname); + host.domainname = domainname ? XSTRDUP(MTYPE_HOST, domainname) : NULL; + return CMD_SUCCESS; +} + +/* Hostname configuration */ +DEFUN(config_domainname, + domainname_cmd, + "domainname WORD", + "Set system's domain name\n" + "This system's domain name\n") +{ + struct cmd_token *word = argv[1]; + + if (!isalpha((unsigned char)word->arg[0])) { + vty_out(vty, "Please specify string starting with alphabet\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return cmd_domainname_set(word->arg); +} + +DEFUN(config_no_domainname, + no_domainname_cmd, + "no domainname [DOMAINNAME]", + NO_STR + "Reset system's domain name\n" + "domain name of this router\n") +{ + return cmd_domainname_set(NULL); +} + +int cmd_hostname_set(const char *hostname) +{ + XFREE(MTYPE_HOST, host.name); + host.name = hostname ? XSTRDUP(MTYPE_HOST, hostname) : NULL; + return CMD_SUCCESS; +} + +/* Hostname configuration */ +DEFUN (config_hostname, + hostname_cmd, + "hostname WORD", + "Set system's network name\n" + "This system's network name\n") +{ + struct cmd_token *word = argv[1]; + + if (!isalnum((unsigned char)word->arg[0])) { + vty_out(vty, + "Please specify string starting with alphabet or number\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* With reference to RFC 1123 Section 2.1 */ + if (strlen(word->arg) > HOSTNAME_LEN) { + vty_out(vty, "Hostname length should be less than %d chars\n", + HOSTNAME_LEN); + return CMD_WARNING_CONFIG_FAILED; + } + + return cmd_hostname_set(word->arg); +} + +DEFUN (config_no_hostname, + no_hostname_cmd, + "no hostname [HOSTNAME]", + NO_STR + "Reset system's network name\n" + "Host name of this router\n") +{ + return cmd_hostname_set(NULL); +} + +/* VTY interface password set. */ +DEFUN (config_password, + password_cmd, + "password [(8-8)] WORD", + "Modify the terminal connection password\n" + "Specifies a HIDDEN password will follow\n" + "The password string\n") +{ + int idx_8 = 1; + int idx_word = 2; + if (argc == 3) // '8' was specified + { + if (host.password) + XFREE(MTYPE_HOST, host.password); + host.password = NULL; + if (host.password_encrypt) + XFREE(MTYPE_HOST, host.password_encrypt); + host.password_encrypt = + XSTRDUP(MTYPE_HOST, argv[idx_word]->arg); + return CMD_SUCCESS; + } + + if (!isalnum((unsigned char)argv[idx_8]->arg[0])) { + vty_out(vty, + "Please specify string starting with alphanumeric\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (host.password) + XFREE(MTYPE_HOST, host.password); + host.password = NULL; + + if (host.encrypt) { + if (host.password_encrypt) + XFREE(MTYPE_HOST, host.password_encrypt); + host.password_encrypt = + XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg)); + } else + host.password = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg); + + return CMD_SUCCESS; +} + +/* VTY interface password delete. */ +DEFUN (no_config_password, + no_password_cmd, + "no password", + NO_STR + "Modify the terminal connection password\n") +{ + bool warned = false; + + if (host.password) { + if (!vty_shell_serv(vty)) { + vty_out(vty, NO_PASSWD_CMD_WARNING); + warned = true; + } + XFREE(MTYPE_HOST, host.password); + } + host.password = NULL; + + if (host.password_encrypt) { + if (!warned && !vty_shell_serv(vty)) + vty_out(vty, NO_PASSWD_CMD_WARNING); + XFREE(MTYPE_HOST, host.password_encrypt); + } + host.password_encrypt = NULL; + + return CMD_SUCCESS; +} + +/* VTY enable password set. */ +DEFUN (config_enable_password, + enable_password_cmd, + "enable password [(8-8)] WORD", + "Modify enable password parameters\n" + "Assign the privileged level password\n" + "Specifies a HIDDEN password will follow\n" + "The HIDDEN 'enable' password string\n") +{ + int idx_8 = 2; + int idx_word = 3; + + /* Crypt type is specified. */ + if (argc == 4) { + if (argv[idx_8]->arg[0] == '8') { + if (host.enable) + XFREE(MTYPE_HOST, host.enable); + host.enable = NULL; + + if (host.enable_encrypt) + XFREE(MTYPE_HOST, host.enable_encrypt); + host.enable_encrypt = + XSTRDUP(MTYPE_HOST, argv[idx_word]->arg); + + return CMD_SUCCESS; + } else { + vty_out(vty, "Unknown encryption type.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (!isalnum((unsigned char)argv[idx_8]->arg[0])) { + vty_out(vty, + "Please specify string starting with alphanumeric\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (host.enable) + XFREE(MTYPE_HOST, host.enable); + host.enable = NULL; + + /* Plain password input. */ + if (host.encrypt) { + if (host.enable_encrypt) + XFREE(MTYPE_HOST, host.enable_encrypt); + host.enable_encrypt = + XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg)); + } else + host.enable = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg); + + return CMD_SUCCESS; +} + +/* VTY enable password delete. */ +DEFUN (no_config_enable_password, + no_enable_password_cmd, + "no enable password", + NO_STR + "Modify enable password parameters\n" + "Assign the privileged level password\n") +{ + bool warned = false; + + if (host.enable) { + if (!vty_shell_serv(vty)) { + vty_out(vty, NO_PASSWD_CMD_WARNING); + warned = true; + } + XFREE(MTYPE_HOST, host.enable); + } + host.enable = NULL; + + if (host.enable_encrypt) { + if (!warned && !vty_shell_serv(vty)) + vty_out(vty, NO_PASSWD_CMD_WARNING); + XFREE(MTYPE_HOST, host.enable_encrypt); + } + host.enable_encrypt = NULL; + + return CMD_SUCCESS; +} + +DEFUN (service_password_encrypt, + service_password_encrypt_cmd, + "service password-encryption", + "Set up miscellaneous service\n" + "Enable encrypted passwords\n") +{ + if (host.encrypt) + return CMD_SUCCESS; + + host.encrypt = 1; + + if (host.password) { + if (host.password_encrypt) + XFREE(MTYPE_HOST, host.password_encrypt); + host.password_encrypt = + XSTRDUP(MTYPE_HOST, zencrypt(host.password)); + } + if (host.enable) { + if (host.enable_encrypt) + XFREE(MTYPE_HOST, host.enable_encrypt); + host.enable_encrypt = + XSTRDUP(MTYPE_HOST, zencrypt(host.enable)); + } + + return CMD_SUCCESS; +} + +DEFUN (no_service_password_encrypt, + no_service_password_encrypt_cmd, + "no service password-encryption", + NO_STR + "Set up miscellaneous service\n" + "Enable encrypted passwords\n") +{ + if (!host.encrypt) + return CMD_SUCCESS; + + host.encrypt = 0; + + if (host.password_encrypt) + XFREE(MTYPE_HOST, host.password_encrypt); + host.password_encrypt = NULL; + + if (host.enable_encrypt) + XFREE(MTYPE_HOST, host.enable_encrypt); + host.enable_encrypt = NULL; + + return CMD_SUCCESS; +} + +DEFUN (config_terminal_length, + config_terminal_length_cmd, + "terminal length (0-512)", + "Set terminal line parameters\n" + "Set number of lines on a screen\n" + "Number of lines on screen (0 for no pausing)\n") +{ + int idx_number = 2; + + vty->lines = atoi(argv[idx_number]->arg); + + return CMD_SUCCESS; +} + +DEFUN (config_terminal_no_length, + config_terminal_no_length_cmd, + "terminal no length", + "Set terminal line parameters\n" + NO_STR + "Set number of lines on a screen\n") +{ + vty->lines = -1; + return CMD_SUCCESS; +} + +DEFUN (service_terminal_length, + service_terminal_length_cmd, + "service terminal-length (0-512)", + "Set up miscellaneous service\n" + "System wide terminal length configuration\n" + "Number of lines of VTY (0 means no line control)\n") +{ + int idx_number = 2; + + host.lines = atoi(argv[idx_number]->arg); + + return CMD_SUCCESS; +} + +DEFUN (no_service_terminal_length, + no_service_terminal_length_cmd, + "no service terminal-length [(0-512)]", + NO_STR + "Set up miscellaneous service\n" + "System wide terminal length configuration\n" + "Number of lines of VTY (0 means no line control)\n") +{ + host.lines = -1; + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (do_echo, + echo_cmd, + "echo MESSAGE...", + "Echo a message back to the vty\n" + "The message to echo\n") +{ + char *message; + + vty_out(vty, "%s\n", + ((message = argv_concat(argv, argc, 1)) ? message : "")); + if (message) + XFREE(MTYPE_TMP, message); + return CMD_SUCCESS; +} + +DEFUN (config_logmsg, + config_logmsg_cmd, + "logmsg MESSAGE...", + "Send a message to enabled logging destinations\n" + LOG_LEVEL_DESC + "The message to send\n") +{ + int idx_log_level = 1; + int idx_message = 2; + int level; + char *message; + + level = log_level_match(argv[idx_log_level]->arg); + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + + zlog(level, "%s", + ((message = argv_concat(argv, argc, idx_message)) ? message : "")); + if (message) + XFREE(MTYPE_TMP, message); + + return CMD_SUCCESS; +} + +DEFUN (debug_memstats, + debug_memstats_cmd, + "[no] debug memstats-at-exit", + NO_STR + DEBUG_STR + "Print memory type statistics at exit\n") +{ + debug_memstats_at_exit = !!strcmp(argv[0]->text, "no"); + return CMD_SUCCESS; +} + +int cmd_banner_motd_file(const char *file) +{ + int success = CMD_SUCCESS; + char p[PATH_MAX]; + char *rpath; + char *in; + + rpath = realpath(file, p); + if (!rpath) + return CMD_ERR_NO_FILE; + in = strstr(rpath, SYSCONFDIR); + if (in == rpath) { + XFREE(MTYPE_HOST, host.motdfile); + host.motdfile = XSTRDUP(MTYPE_HOST, file); + } else + success = CMD_WARNING_CONFIG_FAILED; + + return success; +} + +void cmd_banner_motd_line(const char *line) +{ + XFREE(MTYPE_HOST, host.motd); + host.motd = XSTRDUP(MTYPE_HOST, line); +} + +DEFUN (banner_motd_file, + banner_motd_file_cmd, + "banner motd file FILE", + "Set banner\n" + "Banner for motd\n" + "Banner from a file\n" + "Filename\n") +{ + int idx_file = 3; + const char *filename = argv[idx_file]->arg; + int cmd = cmd_banner_motd_file(filename); + + if (cmd == CMD_ERR_NO_FILE) + vty_out(vty, "%s does not exist\n", filename); + else if (cmd == CMD_WARNING_CONFIG_FAILED) + vty_out(vty, "%s must be in %s\n", filename, SYSCONFDIR); + + return cmd; +} + +DEFUN (banner_motd_line, + banner_motd_line_cmd, + "banner motd line LINE...", + "Set banner\n" + "Banner for motd\n" + "Banner from an input\n" + "Text\n") +{ + int idx = 0; + char *motd; + + argv_find(argv, argc, "LINE", &idx); + motd = argv_concat(argv, argc, idx); + + cmd_banner_motd_line(motd); + XFREE(MTYPE_TMP, motd); + + return CMD_SUCCESS; +} + +DEFUN (banner_motd_default, + banner_motd_default_cmd, + "banner motd default", + "Set banner string\n" + "Strings for motd\n" + "Default string\n") +{ + cmd_banner_motd_line(FRR_DEFAULT_MOTD); + return CMD_SUCCESS; +} + +DEFUN (no_banner_motd, + no_banner_motd_cmd, + "no banner motd", + NO_STR + "Set banner string\n" + "Strings for motd\n") +{ + host.motd = NULL; + if (host.motdfile) + XFREE(MTYPE_HOST, host.motdfile); + host.motdfile = NULL; + return CMD_SUCCESS; +} + +DEFUN(allow_reserved_ranges, allow_reserved_ranges_cmd, "allow-reserved-ranges", + "Allow using IPv4 (Class E) reserved IP space\n") +{ + host.allow_reserved_ranges = true; + return CMD_SUCCESS; +} + +DEFUN(no_allow_reserved_ranges, no_allow_reserved_ranges_cmd, + "no allow-reserved-ranges", + NO_STR "Allow using IPv4 (Class E) reserved IP space\n") +{ + host.allow_reserved_ranges = false; + return CMD_SUCCESS; +} + +int cmd_find_cmds(struct vty *vty, struct cmd_token **argv, int argc) +{ + const struct cmd_node *node; + const struct cmd_element *cli; + vector clis; + + regex_t exp = {}; + + char *pattern = argv_concat(argv, argc, 1); + int cr = regcomp(&exp, pattern, REG_NOSUB | REG_EXTENDED); + XFREE(MTYPE_TMP, pattern); + + if (cr != 0) { + switch (cr) { + case REG_BADBR: + vty_out(vty, "%% Invalid {...} expression\n"); + break; + case REG_BADRPT: + vty_out(vty, "%% Bad repetition operator\n"); + break; + case REG_BADPAT: + vty_out(vty, "%% Regex syntax error\n"); + break; + case REG_ECOLLATE: + vty_out(vty, "%% Invalid collating element\n"); + break; + case REG_ECTYPE: + vty_out(vty, "%% Invalid character class name\n"); + break; + case REG_EESCAPE: + vty_out(vty, + "%% Regex ended with escape character (\\)\n"); + break; + case REG_ESUBREG: + vty_out(vty, + "%% Invalid number in \\digit construction\n"); + break; + case REG_EBRACK: + vty_out(vty, "%% Unbalanced square brackets\n"); + break; + case REG_EPAREN: + vty_out(vty, "%% Unbalanced parentheses\n"); + break; + case REG_EBRACE: + vty_out(vty, "%% Unbalanced braces\n"); + break; + case REG_ERANGE: + vty_out(vty, + "%% Invalid endpoint in range expression\n"); + break; + case REG_ESPACE: + vty_out(vty, "%% Failed to compile (out of memory)\n"); + break; + } + + goto done; + } + + + for (unsigned int i = 0; i < vector_active(cmdvec); i++) { + node = vector_slot(cmdvec, i); + if (!node) + continue; + clis = node->cmd_vector; + for (unsigned int j = 0; j < vector_active(clis); j++) { + cli = vector_slot(clis, j); + + if (regexec(&exp, cli->string, 0, NULL, 0) == 0) { + vty_out(vty, " (%s) ", node->name); + print_cmd(vty, cli->string); + } + } + } + +done: + regfree(&exp); + return CMD_SUCCESS; +} + +DEFUN(find, + find_cmd, + "find REGEX...", + "Find CLI command matching a regular expression\n" + "Search pattern (POSIX regex)\n") +{ + return cmd_find_cmds(vty, argv, argc); +} + +#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING) +DEFUN(script, script_cmd, "script SCRIPT FUNCTION", + "Test command - execute a function in a script\n" + "Script name (same as filename in /etc/frr/scripts/)\n" + "Function name (in the script)\n") +{ + struct prefix p; + + (void)str2prefix("1.2.3.4/24", &p); + struct frrscript *fs = frrscript_new(argv[1]->arg); + + if (frrscript_load(fs, argv[2]->arg, NULL)) { + vty_out(vty, + "/etc/frr/scripts/%s.lua or function '%s' not found\n", + argv[1]->arg, argv[2]->arg); + } + + int ret = frrscript_call(fs, argv[2]->arg, ("p", &p)); + char buf[40]; + prefix2str(&p, buf, sizeof(buf)); + vty_out(vty, "p: %s\n", buf); + vty_out(vty, "Script result: %d\n", ret); + + frrscript_delete(fs); + + return CMD_SUCCESS; +} +#endif + +/* Set config filename. Called from vty.c */ +void host_config_set(const char *filename) +{ + XFREE(MTYPE_HOST, host.config); + host.config = XSTRDUP(MTYPE_HOST, filename); +} + +const char *host_config_get(void) +{ + return host.config; +} + +void cmd_show_lib_debugs(struct vty *vty) +{ + route_map_show_debug(vty); + mgmt_debug_be_client_show_debug(vty); + mgmt_debug_fe_client_show_debug(vty); +} + +void install_default(enum node_type node) +{ + _install_element(node, &config_exit_cmd); + _install_element(node, &config_quit_cmd); + _install_element(node, &config_end_cmd); + _install_element(node, &config_help_cmd); + _install_element(node, &config_list_cmd); + _install_element(node, &show_cli_graph_cmd); + _install_element(node, &find_cmd); + + _install_element(node, &config_write_cmd); + _install_element(node, &show_running_config_cmd); + + _install_element(node, &autocomplete_cmd); + + nb_cli_install_default(node); +} + +/* Initialize command interface. Install basic nodes and commands. + * + * terminal = 0 -- vtysh / no logging, no config control + * terminal = 1 -- normal daemon + * terminal = -1 -- watchfrr / no logging, but minimal config control */ +void cmd_init(int terminal) +{ + struct utsname names; + + uname(&names); + qobj_init(); + + /* register command preprocessors */ + hook_register(cmd_execute, handle_pipe_action); + hook_register(cmd_execute_done, handle_pipe_action_done); + + varhandlers = list_new(); + + /* Allocate initial top vector of commands. */ + cmdvec = vector_init(VECTOR_MIN_SIZE); + + /* Default host value settings. */ + host.name = XSTRDUP(MTYPE_HOST, names.nodename); + host.system = XSTRDUP(MTYPE_HOST, names.sysname); + host.release = XSTRDUP(MTYPE_HOST, names.release); + host.version = XSTRDUP(MTYPE_HOST, names.version); + +#ifdef HAVE_STRUCT_UTSNAME_DOMAINNAME + if ((strcmp(names.domainname, "(none)") == 0)) + host.domainname = NULL; + else + host.domainname = XSTRDUP(MTYPE_HOST, names.domainname); +#else + host.domainname = NULL; +#endif + host.password = NULL; + host.enable = NULL; + host.config = NULL; + host.noconfig = (terminal < 0); + host.lines = -1; + cmd_banner_motd_line(FRR_DEFAULT_MOTD); + host.motdfile = NULL; + host.allow_reserved_ranges = false; + + /* Install top nodes. */ + install_node(&view_node); + install_node(&enable_node); + install_node(&auth_node); + install_node(&auth_enable_node); + install_node(&config_node); + + /* Each node's basic commands. */ + install_element(VIEW_NODE, &show_version_cmd); + install_element(ENABLE_NODE, &show_startup_config_cmd); + + if (terminal) { + install_element(ENABLE_NODE, &debug_memstats_cmd); + + install_element(VIEW_NODE, &config_list_cmd); + install_element(VIEW_NODE, &config_exit_cmd); + install_element(VIEW_NODE, &config_quit_cmd); + install_element(VIEW_NODE, &config_help_cmd); + install_element(VIEW_NODE, &config_enable_cmd); + install_element(VIEW_NODE, &config_terminal_length_cmd); + install_element(VIEW_NODE, &config_terminal_no_length_cmd); + install_element(VIEW_NODE, &show_commandtree_cmd); + install_element(VIEW_NODE, &echo_cmd); + install_element(VIEW_NODE, &autocomplete_cmd); + install_element(VIEW_NODE, &find_cmd); +#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING) + install_element(VIEW_NODE, &script_cmd); +#endif + + + install_element(ENABLE_NODE, &config_end_cmd); + install_element(ENABLE_NODE, &config_disable_cmd); + install_element(ENABLE_NODE, &config_terminal_cmd); + install_element(ENABLE_NODE, ©_runningconf_startupconf_cmd); + install_element(ENABLE_NODE, &config_write_cmd); + install_element(ENABLE_NODE, &show_running_config_cmd); + install_element(ENABLE_NODE, &config_logmsg_cmd); + + install_default(CONFIG_NODE); + + event_cmd_init(); + workqueue_cmd_init(); + hash_cmd_init(); + } + + install_element(CONFIG_NODE, &hostname_cmd); + install_element(CONFIG_NODE, &no_hostname_cmd); + install_element(CONFIG_NODE, &domainname_cmd); + install_element(CONFIG_NODE, &no_domainname_cmd); + + if (terminal > 0) { + full_cli = true; + + install_element(CONFIG_NODE, &debug_memstats_cmd); + + install_element(CONFIG_NODE, &password_cmd); + install_element(CONFIG_NODE, &no_password_cmd); + install_element(CONFIG_NODE, &enable_password_cmd); + install_element(CONFIG_NODE, &no_enable_password_cmd); + + install_element(CONFIG_NODE, &service_password_encrypt_cmd); + install_element(CONFIG_NODE, &no_service_password_encrypt_cmd); + install_element(CONFIG_NODE, &banner_motd_default_cmd); + install_element(CONFIG_NODE, &banner_motd_file_cmd); + install_element(CONFIG_NODE, &banner_motd_line_cmd); + install_element(CONFIG_NODE, &no_banner_motd_cmd); + install_element(CONFIG_NODE, &service_terminal_length_cmd); + install_element(CONFIG_NODE, &no_service_terminal_length_cmd); + install_element(CONFIG_NODE, &allow_reserved_ranges_cmd); + install_element(CONFIG_NODE, &no_allow_reserved_ranges_cmd); + + log_cmd_init(); + vrf_install_commands(); + } + +#ifdef DEV_BUILD + grammar_sandbox_init(); +#endif +} + +void cmd_terminate(void) +{ + struct cmd_node *cmd_node; + + hook_unregister(cmd_execute, handle_pipe_action); + hook_unregister(cmd_execute_done, handle_pipe_action_done); + + if (cmdvec) { + for (unsigned int i = 0; i < vector_active(cmdvec); i++) + if ((cmd_node = vector_slot(cmdvec, i)) != NULL) { + // deleting the graph delets the cmd_element as + // well + graph_delete_graph(cmd_node->cmdgraph); + vector_free(cmd_node->cmd_vector); + hash_clean_and_free(&cmd_node->cmd_hash, NULL); + } + + vector_free(cmdvec); + cmdvec = NULL; + } + + XFREE(MTYPE_HOST, host.name); + XFREE(MTYPE_HOST, host.system); + XFREE(MTYPE_HOST, host.release); + XFREE(MTYPE_HOST, host.version); + XFREE(MTYPE_HOST, host.domainname); + XFREE(MTYPE_HOST, host.password); + XFREE(MTYPE_HOST, host.password_encrypt); + XFREE(MTYPE_HOST, host.enable); + XFREE(MTYPE_HOST, host.enable_encrypt); + XFREE(MTYPE_HOST, host.motdfile); + XFREE(MTYPE_HOST, host.config); + XFREE(MTYPE_HOST, host.motd); + + list_delete(&varhandlers); + qobj_finish(); +} diff --git a/lib/command.h b/lib/command.h new file mode 100644 index 0000000..e4c575e --- /dev/null +++ b/lib/command.h @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra configuration command interface routine + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_COMMAND_H +#define _ZEBRA_COMMAND_H + +#include "vector.h" +#include "vty.h" +#include "lib/route_types.h" +#include "graph.h" +#include "memory.h" +#include "hash.h" +#include "command_graph.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(COMPLETION); + +/* + * From RFC 1123 (Requirements for Internet Hosts), Section 2.1 on hostnames: + * One aspect of host name syntax is hereby changed: the restriction on + * the first character is relaxed to allow either a letter or a digit. + * Host software MUST support this more liberal syntax. + * + * Host software MUST handle host names of up to 63 characters and + * SHOULD handle host names of up to 255 characters. + */ +#define HOSTNAME_LEN 255 + +/* Host configuration variable */ +struct host { + /* Host name of this router. */ + char *name; + + /* Domainname of this router */ + char *domainname; + + /* + * Some extra system data that is useful + */ + char *system; + char *release; + char *version; + + /* Password for vty interface. */ + char *password; + char *password_encrypt; + + /* Enable password */ + char *enable; + char *enable_encrypt; + + /* System wide terminal lines. */ + int lines; + + /* config file name of this host */ + char *config; + int noconfig; + + /* Flags for services */ + int advanced; + int encrypt; + + /* Banner configuration. */ + char *motd; + char *motdfile; + + /* Allow using IPv4 (Class E) reserved IP space */ + bool allow_reserved_ranges; +}; + +/* List of CLI nodes. Please remember to update the name array in command.c. */ +/* clang-format off */ +enum node_type { + AUTH_NODE, /* Authentication mode of vty interface. */ + VIEW_NODE, /* View node. Default mode of vty interface. */ + AUTH_ENABLE_NODE, /* Authentication mode for change enable. */ + ENABLE_NODE, /* Enable node. */ + CONFIG_NODE, /* Config node. Default mode of config file. */ + PREFIX_NODE, /* ip prefix-list node. */ + PREFIX_IPV6_NODE, /* ipv6 prefix-list node. */ + DEBUG_NODE, /* Debug node. */ + VRF_DEBUG_NODE, /* Vrf Debug node. */ + NORTHBOUND_DEBUG_NODE, /* Northbound Debug node. */ + DEBUG_VNC_NODE, /* Debug VNC node. */ + RMAP_DEBUG_NODE, /* Route-map debug node */ + RESOLVER_DEBUG_NODE, /* Resolver debug node */ + MGMT_BE_DEBUG_NODE, /* mgmtd backend-client debug node */ + MGMT_FE_DEBUG_NODE, /* mgmtd frontend-client debug node */ + AAA_NODE, /* AAA node. */ + EXTLOG_NODE, /* RFC5424 & co. extended syslog */ + KEYCHAIN_NODE, /* Key-chain node. */ + KEYCHAIN_KEY_NODE, /* Key-chain key node. */ + AFFMAP_NODE, /* Affinity map node. */ + IP_NODE, /* Static ip route node. */ + VRF_NODE, /* VRF mode node. */ + INTERFACE_NODE, /* Interface mode node. */ + NH_GROUP_NODE, /* Nexthop-Group mode node. */ + ZEBRA_NODE, /* zebra connection node. */ + TABLE_NODE, /* rtm_table selection node. */ + RIP_NODE, /* RIP protocol mode node. */ + RIPNG_NODE, /* RIPng protocol mode node. */ + BABEL_NODE, /* BABEL protocol mode node. */ + EIGRP_NODE, /* EIGRP protocol mode node. */ + BGP_NODE, /* BGP protocol mode which includes BGP4+ */ + BGP_VPNV4_NODE, /* BGP MPLS-VPN PE exchange. */ + BGP_VPNV6_NODE, /* BGP MPLS-VPN PE exchange. */ + BGP_IPV4_NODE, /* BGP IPv4 unicast address family. */ + BGP_IPV4M_NODE, /* BGP IPv4 multicast address family. */ + BGP_IPV4L_NODE, /* BGP IPv4 labeled unicast address family. */ + BGP_IPV6_NODE, /* BGP IPv6 address family */ + BGP_IPV6M_NODE, /* BGP IPv6 multicast address family. */ + BGP_IPV6L_NODE, /* BGP IPv6 labeled unicast address family. */ + BGP_VRF_POLICY_NODE, /* BGP VRF policy */ + BGP_VNC_DEFAULTS_NODE, /* BGP VNC nve defaults */ + BGP_VNC_NVE_GROUP_NODE, /* BGP VNC nve group */ + BGP_VNC_L2_GROUP_NODE, /* BGP VNC L2 group */ + RFP_DEFAULTS_NODE, /* RFP defaults node */ + BGP_EVPN_NODE, /* BGP EVPN node. */ + BGP_SRV6_NODE, /* BGP SRv6 node. */ + OSPF_NODE, /* OSPF protocol mode */ + OSPF6_NODE, /* OSPF protocol for IPv6 mode */ + LDP_NODE, /* LDP protocol mode */ + LDP_IPV4_NODE, /* LDP IPv4 address family */ + LDP_IPV6_NODE, /* LDP IPv6 address family */ + LDP_IPV4_IFACE_NODE, /* LDP IPv4 Interface */ + LDP_IPV6_IFACE_NODE, /* LDP IPv6 Interface */ + LDP_L2VPN_NODE, /* LDP L2VPN node */ + LDP_PSEUDOWIRE_NODE, /* LDP Pseudowire node */ + ISIS_NODE, /* ISIS protocol mode */ + ISIS_FLEX_ALGO_NODE, /* ISIS Flex Algo mode */ + ACCESS_NODE, /* Access list node. */ + ACCESS_IPV6_NODE, /* Access list node. */ + ACCESS_MAC_NODE, /* MAC access list node*/ + AS_LIST_NODE, /* AS list node. */ + COMMUNITY_LIST_NODE, /* Community list node. */ + COMMUNITY_ALIAS_NODE, /* Community alias node. */ + RMAP_NODE, /* Route map node. */ + PBRMAP_NODE, /* PBR map node. */ + SMUX_NODE, /* SNMP configuration node. */ + DUMP_NODE, /* Packet dump node. */ + FORWARDING_NODE, /* IP forwarding node. */ + PROTOCOL_NODE, /* protocol filtering node */ + MPLS_NODE, /* MPLS config node */ + PW_NODE, /* Pseudowire config node */ + SEGMENT_ROUTING_NODE, /* Segment routing root node */ + SR_TRAFFIC_ENG_NODE, /* SR Traffic Engineering node */ + SR_SEGMENT_LIST_NODE, /* SR segment list config node */ + SR_POLICY_NODE, /* SR policy config node */ + SR_CANDIDATE_DYN_NODE, /* SR dynamic candidate path config node */ + PCEP_NODE, /* PCEP node */ + PCEP_PCE_CONFIG_NODE, /* PCE shared configuration node */ + PCEP_PCE_NODE, /* PCE configuration node */ + PCEP_PCC_NODE, /* PCC configuration node */ + SRV6_NODE, /* SRv6 node */ + SRV6_LOCS_NODE, /* SRv6 locators node */ + SRV6_LOC_NODE, /* SRv6 locator node */ + SRV6_ENCAP_NODE, /* SRv6 encapsulation node */ + VTY_NODE, /* Vty node. */ + FPM_NODE, /* Dataplane FPM node. */ + LINK_PARAMS_NODE, /* Link-parameters node */ + BGP_EVPN_VNI_NODE, /* BGP EVPN VNI */ + RPKI_NODE, /* RPKI node for configuration of RPKI cache server + connections.*/ + BGP_FLOWSPECV4_NODE, /* BGP IPv4 FLOWSPEC Address-Family */ + BGP_FLOWSPECV6_NODE, /* BGP IPv6 FLOWSPEC Address-Family */ + BFD_NODE, /* BFD protocol mode. */ + BFD_PEER_NODE, /* BFD peer configuration mode. */ + BFD_PROFILE_NODE, /* BFD profile configuration mode. */ + OPENFABRIC_NODE, /* OpenFabric router configuration node */ + VRRP_NODE, /* VRRP node */ + BMP_NODE, /* BMP config under router bgp */ + ISIS_SRV6_NODE, /* ISIS SRv6 node */ + ISIS_SRV6_NODE_MSD_NODE, /* ISIS SRv6 Node MSDs node */ + MGMTD_NODE, /* MGMTD node. */ + RPKI_VRF_NODE, /* RPKI node for VRF */ + NODE_TYPE_MAX, /* maximum */ +}; +/* clang-format on */ + +extern vector cmdvec; +extern const struct message tokennames[]; + +/* for external users depending on struct layout */ +#define FRR_CMD_NODE_20200416 + +/* Node which has some commands and prompt string and configuration + function pointer . */ +struct cmd_node { + const char *name; + + /* Node index. */ + enum node_type node; + enum node_type parent_node; + + /* Prompt character at vty interface. */ + const char *prompt; + + /* Node's configuration write function */ + int (*config_write)(struct vty *); + + /* called when leaving the node on a VTY session. + * return 1 if normal exit processing should happen, 0 to suppress + */ + int (*node_exit)(struct vty *); + + /* Node's command graph */ + struct graph *cmdgraph; + + /* Vector of this node's command list. */ + vector cmd_vector; + + /* Hashed index of command node list, for de-dupping primarily */ + struct hash *cmd_hash; + + /* set as soon as any command is in cmdgraph */ + bool graph_built; + + /* don't decrement vty->xpath_index on leaving this node */ + bool no_xpath; +}; + +/* Return value of the commands. */ +#define CMD_SUCCESS 0 +#define CMD_WARNING 1 +#define CMD_ERR_NO_MATCH 2 +#define CMD_ERR_AMBIGUOUS 3 +#define CMD_ERR_INCOMPLETE 4 +#define CMD_ERR_EXEED_ARGC_MAX 5 +#define CMD_ERR_NOTHING_TODO 6 +#define CMD_COMPLETE_FULL_MATCH 7 +#define CMD_COMPLETE_MATCH 8 +#define CMD_COMPLETE_LIST_MATCH 9 +#define CMD_SUCCESS_DAEMON 10 +#define CMD_ERR_NO_FILE 11 +#define CMD_SUSPEND 12 +#define CMD_WARNING_CONFIG_FAILED 13 +#define CMD_NOT_MY_INSTANCE 14 +#define CMD_NO_LEVEL_UP 15 +#define CMD_ERR_NO_DAEMON 16 + +/* Argc max counts. */ +#define CMD_ARGC_MAX 256 + +/* helper defines for end-user DEFUN* macros */ +#define DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attrs, dnum) \ + static const struct cmd_element cmdname = { \ + .string = cmdstr, \ + .func = funcname, \ + .doc = helpstr, \ + .attr = attrs, \ + .daemon = dnum, \ + .name = #cmdname, \ + .xref = XREF_INIT(XREFT_DEFUN, NULL, #funcname), \ + }; \ + XREF_LINK(cmdname.xref); \ + /* end */ + + +#define DEFUN_CMD_FUNC_DECL(funcname) \ + static int funcname(const struct cmd_element *, struct vty *, int, \ + struct cmd_token *[]); + +#define DEFUN_CMD_FUNC_TEXT(funcname) \ + static int funcname(const struct cmd_element *self \ + __attribute__((unused)), \ + struct vty *vty __attribute__((unused)), \ + int argc __attribute__((unused)), \ + struct cmd_token *argv[] __attribute__((unused))) + +/* DEFPY variants */ + +#define DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ + DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ + funcdecl_##funcname + +#define DEFPY(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, 0) + +#define DEFPY_NOSH(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_NOSH) + +#define DEFPY_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) + +#define DEFPY_YANG(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG) + +#define DEFPY_YANG_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_YANG | CMD_ATTR_HIDDEN) + +#define DEFPY_YANG_NOSH(funcname, cmdname, cmdstr, helpstr) \ + DEFPY_ATTR(funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_YANG | CMD_ATTR_NOSH) + +/* DEFUN variants */ + +#define DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ + DEFUN_CMD_FUNC_DECL(funcname) \ + DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) \ + DEFUN_CMD_FUNC_TEXT(funcname) + +#define DEFUN(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, 0) + +#define DEFUN_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) + +#define DEFUN_YANG(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG) + +/* DEFUN_NOSH for commands that vtysh should ignore */ +#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_NOSH) + +#define DEFUN_YANG_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_YANG | CMD_ATTR_HIDDEN) + +#define DEFUN_YANG_NOSH(funcname, cmdname, cmdstr, helpstr) \ + DEFUN_ATTR(funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_YANG | CMD_ATTR_NOSH) + +/* DEFSH for vtysh. */ +#define DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, attr) \ + DEFUN_CMD_ELEMENT(NULL, cmdname, cmdstr, helpstr, attr, daemon) + +#define DEFSH(daemon, cmdname, cmdstr, helpstr) \ + DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, 0) + +#define DEFSH_HIDDEN(daemon, cmdname, cmdstr, helpstr) \ + DEFSH_ATTR(daemon, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) + +/* DEFUN + DEFSH */ +#define DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, attr) \ + DEFUN_CMD_FUNC_DECL(funcname) \ + DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, daemon) \ + DEFUN_CMD_FUNC_TEXT(funcname) + +#define DEFUNSH(daemon, funcname, cmdname, cmdstr, helpstr) \ + DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, 0) + +#define DEFUNSH_HIDDEN(daemon, funcname, cmdname, cmdstr, helpstr) \ + DEFUNSH_ATTR(daemon, funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_HIDDEN) + +/* ALIAS macro which define existing command's alias. */ +#define ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, attr) \ + DEFUN_CMD_ELEMENT(funcname, cmdname, cmdstr, helpstr, attr, 0) + +#define ALIAS(funcname, cmdname, cmdstr, helpstr) \ + ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, 0) + +#define ALIAS_HIDDEN(funcname, cmdname, cmdstr, helpstr) \ + ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN) + +/* note: DEPRECATED implies HIDDEN, and other than that there is currently no + * difference. It's purely for expressing intent in the source code - a + * DEPRECATED command is supposed to go away, a HIDDEN one is likely to stay. + */ +#define ALIAS_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \ + ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, \ + CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN) + +#define ALIAS_YANG(funcname, cmdname, cmdstr, helpstr) \ + ALIAS_ATTR(funcname, cmdname, cmdstr, helpstr, CMD_ATTR_YANG) + +/* Some macroes */ + +/* + * Sometimes #defines create maximum values that + * need to have strings created from them that + * allow the parser to match against them. + * These macros allow that. + */ +#define CMD_CREATE_STR(s) CMD_CREATE_STR_HELPER(s) +#define CMD_CREATE_STR_HELPER(s) #s +#define CMD_RANGE_STR(a,s) "(" CMD_CREATE_STR(a) "-" CMD_CREATE_STR(s) ")" + +/* Common descriptions. */ +#define SHOW_STR "Show running system information\n" +#define IP_STR "IP information\n" +#define IPV6_STR "IPv6 information\n" +#define IP_ADDR_STR "IPv4 Address\n" +#define IP6_ADDR_STR "IPv6 Address\n" +#define SRTE_STR "SR-TE information\n" +#define SRTE_COLOR_STR "SR-TE Color information\n" +#define NO_STR "Negate a command or set its defaults\n" +#define IGNORED_IN_NO_STR "Ignored value in no form\n" +#define REDIST_STR "Redistribute information from another routing protocol\n" +#define CLEAR_STR "Reset functions\n" +#define RIP_STR "RIP information\n" +#define EIGRP_STR "EIGRP information\n" +#define BGP_STR "BGP information\n" +#define BGP_SOFT_STR "Soft reconfig inbound and outbound updates\n" +#define BGP_SOFT_IN_STR "Send route-refresh unless using 'soft-reconfiguration inbound'\n" +#define BGP_SOFT_OUT_STR "Resend all outbound updates\n" +#define BGP_SOFT_RSCLIENT_RIB_STR "Soft reconfig for rsclient RIB\n" +#define OSPF_STR "OSPF information\n" +#define NEIGHBOR_STR "Specify neighbor router\n" +#define DEBUG_STR "Debugging functions\n" +#define UNDEBUG_STR "Disable debugging functions (see also 'debug')\n" +#define ROUTER_STR "Enable a routing process\n" +#define AS_STR \ + "AS number in plain <1-4294967295> or dotted <0-65535>.<0-65535> format\n" +#define MAC_STR "MAC address\n" +#define MBGP_STR "MBGP information\n" +#define MATCH_STR "Match values from routing table\n" +#define SET_STR "Set values in destination routing protocol\n" +#define OUT_STR "Filter outgoing routing updates\n" +#define IN_STR "Filter incoming routing updates\n" +#define V4NOTATION_STR "specify by IPv4 address notation(e.g. 0.0.0.0)\n" +#define OSPF6_NUMBER_STR "Specify by number\n" +#define INTERFACE_STR "Interface information\n" +#define IFNAME_STR "Interface name(e.g. ep0)\n" +#define IP6_STR "IPv6 Information\n" +#define OSPF6_STR "Open Shortest Path First (OSPF) for IPv6\n" +#define OSPF6_INSTANCE_STR "(1-65535) Instance ID\n" +#define SECONDS_STR "Seconds\n" +#define ROUTE_STR "Routing Table\n" +#define PREFIX_LIST_STR "Build a prefix list\n" +#define OSPF6_DUMP_TYPE_LIST \ + "" +#define AREA_TAG_STR "[area tag]\n" +#define COMMUNITY_AANN_STR "Community number where AA and NN are (0-65535)\n" +#define COMMUNITY_VAL_STR \ + "Community number in AA:NN format (where AA and NN are (0-65535)) or local-AS|no-advertise|no-export|internet|graceful-shutdown|accept-own-nexthop|accept-own|route-filter-translated-v4|route-filter-v4|route-filter-translated-v6|route-filter-v6|llgr-stale|no-llgr|blackhole|no-peer or additive\n" +#define EXTCOMM_LIST_CMD_STR "<(1-99)|(100-500)|EXTCOMMUNITY_LIST_NAME>" +#define EXTCOMM_STD_LIST_NUM_STR "Extended community-list number (standard)\n" +#define EXTCOMM_EXP_LIST_NUM_STR "Extended community-list number (expanded)\n" +#define EXTCOMM_LIST_NAME_STR "Extended community-list name\n" +#define MPLS_TE_STR "MPLS-TE specific commands\n" +#define LINK_PARAMS_STR "Configure interface link parameters\n" +#define OSPF_RI_STR "OSPF Router Information specific commands\n" +#define PCE_STR "PCE Router Information specific commands\n" +#define MPLS_STR "MPLS information\n" +#define SR_STR "Segment-Routing specific commands\n" +#define WATCHFRR_STR "watchfrr information\n" +#define ZEBRA_STR "Zebra information\n" +#define FILTER_LOG_STR "Filter Logs\n" +#define BFD_PROFILE_STR "BFD profile.\n" +#define BFD_PROFILE_NAME_STR "BFD profile name.\n" +#define SHARP_STR "Sharp Routing Protocol\n" +#define OSPF_GR_STR \ + "OSPF non-stop forwarding (NSF) also known as OSPF Graceful Restart\n" +#define MGMTD_STR "Management Daemon (MGMTD) information\n" +#define MGMTD_BE_ADAPTER_STR "MGMTD Backend Adapter information\n" +#define MGMTD_FE_ADAPTER_STR "MGMTD Frontend Adapter information\n" +#define MGMTD_TXN_STR "MGMTD Transaction information\n" +#define MGMTD_DS_STR "MGMTD Datastore information\n" + +#define CMD_VNI_RANGE "(1-16777215)" +#define CONF_BACKUP_EXT ".sav" +#define MPLS_LDP_SYNC_STR "Enable MPLS LDP-SYNC\n" +#define NO_MPLS_LDP_SYNC_STR "Disable MPLS LDP-SYNC\n" +#define MPLS_LDP_SYNC_HOLDDOWN_STR \ + "Time to wait for LDP-SYNC to occur before restoring if cost\n" +#define NO_MPLS_LDP_SYNC_HOLDDOWN_STR "holddown timer disable\n" +#define BGP_AF_STR "Address Family\n" +#define BGP_AF_MODIFIER_STR "Address Family modifier\n" + +/* Command warnings. */ +#define NO_PASSWD_CMD_WARNING \ + "Please be aware that removing the password is a security risk and you should think twice about this command.\n" + +/* IPv4 only machine should not accept IPv6 address for peer's IP + address. So we replace VTY command string like below. */ +#define NEIGHBOR_ADDR_STR "Neighbor address\nIPv6 address\n" +#define NEIGHBOR_ADDR_STR2 "Neighbor address\nNeighbor IPv6 address\nInterface name or neighbor tag\n" +#define NEIGHBOR_ADDR_STR3 "Neighbor address\nIPv6 address\nInterface name\n" + +/* Graceful Restart cli help strings */ +#define GR_CMD "Global Graceful Restart command\n" +#define NO_GR_CMD "Undo Global Graceful Restart command\n" +#define GR "Global Graceful Restart - GR Mode\n" +#define GR_DISABLE "Global Graceful Restart - Disable Mode\n" +#define NO_GR_DISABLE "Undo Global Graceful Restart - Disable Mode\n" +#define GR_DEBUG "Graceful Restart - Enable Debug Logs\n" +#define GR_SHOW "Graceful Restart - Show command for Global and all neighbor mode\n" +#define GR_NEIGHBOR_CMD "Graceful Restart command for a neighbor\n" +#define NO_GR_NEIGHBOR_CMD "Undo Graceful Restart command for a neighbor\n" +#define GR_NEIGHBOR_DISABLE_CMD "Graceful Restart Disable command for a neighbor\n" +#define NO_GR_NEIGHBOR_DISABLE_CMD "Undo Graceful Restart Disable command for a neighbor\n" +#define GR_NEIGHBOR_HELPER_CMD "Graceful Restart Helper command for a neighbor\n" +#define NO_GR_NEIGHBOR_HELPER_CMD "Undo Graceful Restart Helper command for a neighbor\n" + +/* EVPN help Strings */ +#define EVPN_RT_HELP_STR "EVPN route information\n" +#define EVPN_RT_DIST_HELP_STR "Route Distinguisher\n" +#define EVPN_ASN_IP_HELP_STR "ASN:XX or A.B.C.D:XX\n" +#define EVPN_TYPE_HELP_STR "Specify Route type\n" +#define EVPN_TYPE_1_HELP_STR "EAD (Type-1) route\n" +#define EVPN_TYPE_2_HELP_STR "MAC-IP (Type-2) route\n" +#define EVPN_TYPE_3_HELP_STR "Multicast (Type-3) route\n" +#define EVPN_TYPE_4_HELP_STR "Ethernet Segment (Type-4) route\n" +#define EVPN_TYPE_5_HELP_STR "Prefix (Type-5) route\n" +#define EVPN_TYPE_ALL_LIST "" +#define EVPN_TYPE_ALL_LIST_HELP_STR \ + EVPN_TYPE_1_HELP_STR EVPN_TYPE_1_HELP_STR \ + EVPN_TYPE_2_HELP_STR EVPN_TYPE_2_HELP_STR \ + EVPN_TYPE_3_HELP_STR EVPN_TYPE_3_HELP_STR \ + EVPN_TYPE_4_HELP_STR EVPN_TYPE_4_HELP_STR \ + EVPN_TYPE_5_HELP_STR EVPN_TYPE_5_HELP_STR + +/* Describing roles */ +#define ROLE_STR \ + "Providing transit\nRoute server\nRS client\nUsing transit\nPublic/private peering\n" + +/* BFD protocol integration strings. */ +#define BFD_INTEGRATION_STR "BFD monitoring\n" +#define BFD_INTEGRATION_MULTI_HOP_STR "Use BFD multi hop session\n" +#define BFD_INTEGRATION_SOURCE_STR "Use source for BFD session\n" +#define BFD_INTEGRATION_SOURCEV4_STR "Use IPv4 source for BFD session\n" +#define BFD_INTEGRATION_SOURCEV6_STR "Use IPv4 source for BFD session\n" + +/* Prototypes. */ +extern void install_node(struct cmd_node *node); +extern void install_default(enum node_type); + +struct xref_install_element { + struct xref xref; + + const struct cmd_element *cmd_element; + enum node_type node_type; +}; + +#define install_element(node_type_, cmd_element_) do { \ + static const struct xref_install_element _xref \ + __attribute__((used)) = { \ + .xref = XREF_INIT(XREFT_INSTALL_ELEMENT, NULL, \ + __func__), \ + .cmd_element = cmd_element_, \ + .node_type = node_type_, \ + }; \ + XREF_LINK(_xref.xref); \ + _install_element(node_type_, cmd_element_); \ + } while (0) + +extern void _install_element(enum node_type, const struct cmd_element *); + +/* known issue with uninstall_element: changes to cmd_token->attr (i.e. + * deprecated/hidden) are not reversed. */ +extern void uninstall_element(enum node_type, const struct cmd_element *); + +/* construct CLI tree only when entering nodes */ +extern void cmd_defer_tree(bool val); + +/* finish CLI tree for node when above is true (noop otherwise) */ +extern void cmd_finalize_node(struct cmd_node *node); + +/* Concatenates argv[shift] through argv[argc-1] into a single NUL-terminated + string with a space between each element (allocated using + XMALLOC(MTYPE_TMP)). Returns NULL if shift >= argc. */ +extern char *argv_concat(struct cmd_token **argv, int argc, int shift); + +/* + * It is preferred that you set the index initial value + * to a 0. This way in the future if you modify the + * cli then there is no need to modify the initial + * value of the index + */ +extern int argv_find(struct cmd_token **argv, int argc, const char *text, + int *index); + +extern vector cmd_make_strvec(const char *); +extern void cmd_free_strvec(vector); +extern vector cmd_describe_command(vector, struct vty *, int *status); +extern char **cmd_complete_command(vector, struct vty *, int *status); +extern const char *cmd_prompt(enum node_type); +extern int command_config_read_one_line(struct vty *vty, + const struct cmd_element **, + uint32_t line_num, int use_config_node); +extern int config_from_file(struct vty *, FILE *, unsigned int *line_num); +extern enum node_type node_parent(enum node_type); +/* + * Execute command under the given vty context. + * + * vty + * The vty context to execute under. + * + * cmd + * The command string to execute. + * + * matched + * If non-null and a match was found, the address of the matched command is + * stored here. No action otherwise. + * + * vtysh + * Whether or not this is being called from vtysh. If this is nonzero, + * XXX: then what? + * + * Returns: + * XXX: what does it return + */ +extern int cmd_execute(struct vty *vty, const char *cmd, + const struct cmd_element **matched, int vtysh); +extern int cmd_execute_command(vector, struct vty *, + const struct cmd_element **, int); +extern int cmd_execute_command_strict(vector, struct vty *, + const struct cmd_element **); +extern void cmd_init(int terminal); +extern void cmd_init_config_callbacks(void (*start_config_cb)(void), + void (*end_config_cb)(void)); +extern void cmd_terminate(void); +extern void cmd_exit(struct vty *vty); +extern int cmd_list_cmds(struct vty *vty, int do_permute); +extern int cmd_find_cmds(struct vty *vty, struct cmd_token **argv, int argc); + +extern int cmd_domainname_set(const char *domainname); +extern int cmd_hostname_set(const char *hostname); +extern const char *cmd_hostname_get(void); +extern const char *cmd_domainname_get(void); +extern const char *cmd_system_get(void); +extern const char *cmd_release_get(void); +extern const char *cmd_version_get(void); +extern const char *cmd_software_version_get(void); +extern bool cmd_allow_reserved_ranges_get(void); + +/* NOT safe for general use; call this only if DEV_BUILD! */ +extern void grammar_sandbox_init(void); + +extern vector completions_to_vec(struct list *completions); + +/* Export typical functions. */ +extern const char *host_config_get(void); +extern void host_config_set(const char *); + +extern void print_version(const char *); + +extern int cmd_banner_motd_file(const char *); +extern void cmd_banner_motd_line(const char *line); + +struct cmd_variable_handler { + const char *tokenname, *varname; + const char *xpath; /* fill comps from set of values at xpath */ + void (*completions)(vector out, struct cmd_token *token); +}; + +extern void cmd_variable_complete(struct cmd_token *token, const char *arg, + vector comps); +extern void +cmd_variable_handler_register(const struct cmd_variable_handler *cvh); +extern char *cmd_variable_comp2str(vector comps, unsigned short cols); + +extern void command_setup_early_logging(const char *dest, const char *level); + +/* + * Allow a mechanism for `debug XXX` commands that live + * under the lib directory to output their debug status + */ +extern void cmd_show_lib_debugs(struct vty *vty); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_COMMAND_H */ diff --git a/lib/command_graph.c b/lib/command_graph.c new file mode 100644 index 0000000..ff3c11d --- /dev/null +++ b/lib/command_graph.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLI graph handling + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + */ + +#include + +#include "command_graph.h" + +DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens"); +DEFINE_MTYPE_STATIC(LIB, CMD_DESC, "Command Token Text"); +DEFINE_MTYPE_STATIC(LIB, CMD_TEXT, "Command Token Help"); +DEFINE_MTYPE(LIB, CMD_ARG, "Command Argument"); +DEFINE_MTYPE_STATIC(LIB, CMD_VAR, "Command Argument Name"); + +struct cmd_token *cmd_token_new(enum cmd_token_type type, uint8_t attr, + const char *text, const char *desc) +{ + struct cmd_token *token = + XCALLOC(MTYPE_CMD_TOKENS, sizeof(struct cmd_token)); + token->type = type; + token->attr = attr; + token->text = text ? XSTRDUP(MTYPE_CMD_TEXT, text) : NULL; + token->desc = desc ? XSTRDUP(MTYPE_CMD_DESC, desc) : NULL; + token->refcnt = 1; + token->arg = NULL; + token->allowrepeat = false; + token->varname = NULL; + + return token; +} + +void cmd_token_del(struct cmd_token *token) +{ + if (!token) + return; + + XFREE(MTYPE_CMD_TEXT, token->text); + XFREE(MTYPE_CMD_DESC, token->desc); + XFREE(MTYPE_CMD_ARG, token->arg); + XFREE(MTYPE_CMD_VAR, token->varname); + + XFREE(MTYPE_CMD_TOKENS, token); +} + +struct cmd_token *cmd_token_dup(struct cmd_token *token) +{ + struct cmd_token *copy = + cmd_token_new(token->type, token->attr, NULL, NULL); + copy->max = token->max; + copy->min = token->min; + copy->text = token->text ? XSTRDUP(MTYPE_CMD_TEXT, token->text) : NULL; + copy->desc = token->desc ? XSTRDUP(MTYPE_CMD_DESC, token->desc) : NULL; + copy->arg = token->arg ? XSTRDUP(MTYPE_CMD_ARG, token->arg) : NULL; + copy->varname = + token->varname ? XSTRDUP(MTYPE_CMD_VAR, token->varname) : NULL; + + return copy; +} + +static void cmd_token_varname_do(struct cmd_token *token, const char *varname, + uint8_t varname_src) +{ + if (token->varname_src >= varname_src) + return; + + XFREE(MTYPE_CMD_VAR, token->varname); + + size_t len = strlen(varname), i; + token->varname = XMALLOC(MTYPE_CMD_VAR, len + 1); + token->varname_src = varname_src; + + for (i = 0; i < len; i++) + switch (varname[i]) { + case '-': + case '+': + case '*': + case ':': + token->varname[i] = '_'; + break; + default: + token->varname[i] = tolower((unsigned char)varname[i]); + } + token->varname[len] = '\0'; +} + +void cmd_token_varname_set(struct cmd_token *token, const char *varname) +{ + if (varname) { + cmd_token_varname_do(token, varname, VARNAME_EXPLICIT); + return; + } + if (token->type == VARIABLE_TKN) { + if (strcmp(token->text, "WORD") && strcmp(token->text, "NAME")) + cmd_token_varname_do(token, token->text, VARNAME_TEXT); + } +} + +static void cmd_token_varname_fork(struct graph_node *node, + struct cmd_token *prevtoken) +{ + for (size_t i = 0; i < vector_active(node->to); i++) { + struct graph_node *next = vector_slot(node->to, i); + struct cmd_token *nexttoken = next->data; + + if (nexttoken->type == FORK_TKN) { + cmd_token_varname_fork(next, prevtoken); + continue; + } + if (nexttoken->varname) + continue; + if (!IS_VARYING_TOKEN(nexttoken->type)) + continue; + + cmd_token_varname_do(nexttoken, prevtoken->text, VARNAME_TEXT); + } +} + +void cmd_token_varname_join(struct graph_node *join, const char *varname) +{ + if (!varname) + return; + + for (size_t i = 0; i < vector_active(join->from); i++) { + struct graph_node *prev = vector_slot(join->from, i); + struct cmd_token *token = prev->data; + + if (token->type == JOIN_TKN) + cmd_token_varname_join(prev, varname); + else if (token->type < SPECIAL_TKN) + cmd_token_varname_do(token, varname, VARNAME_EXPLICIT); + } +} + +void cmd_token_varname_seqappend(struct graph_node *node) +{ + struct graph_node *prevnode = node; + struct cmd_token *token = node->data; + struct cmd_token *prevtoken; + + if (token->type == WORD_TKN) + return; + + do { + if (vector_active(prevnode->from) != 1) + return; + + prevnode = vector_slot(prevnode->from, 0); + prevtoken = prevnode->data; + } while (prevtoken->type == FORK_TKN); + + if (prevtoken->type != WORD_TKN) + return; + + if (token->type == FORK_TKN) + cmd_token_varname_fork(node, prevtoken); + else + cmd_token_varname_do(token, prevtoken->text, VARNAME_TEXT); +} + +static bool cmd_nodes_link(struct graph_node *from, struct graph_node *to) +{ + for (size_t i = 0; i < vector_active(from->to); i++) + if (vector_slot(from->to, i) == to) + return true; + return false; +} + +static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb); + +/* returns a single node to be excluded as "next" from iteration + * - for JOIN_TKN, never continue back to the FORK_TKN + * - in all other cases, don't try the node itself (in case of "...") + */ +static inline struct graph_node *cmd_loopstop(struct graph_node *gn) +{ + struct cmd_token *tok = gn->data; + if (tok->type == JOIN_TKN) + return tok->forkjoin; + else + return gn; +} + +static bool cmd_subgraph_equal(struct graph_node *ga, struct graph_node *gb, + struct graph_node *a_join) +{ + size_t i, j; + struct graph_node *a_fork, *b_fork; + a_fork = cmd_loopstop(ga); + b_fork = cmd_loopstop(gb); + + if (vector_active(ga->to) != vector_active(gb->to)) + return false; + for (i = 0; i < vector_active(ga->to); i++) { + struct graph_node *cga = vector_slot(ga->to, i); + + for (j = 0; j < vector_active(gb->to); j++) { + struct graph_node *cgb = vector_slot(gb->to, i); + + if (cga == a_fork && cgb != b_fork) + continue; + if (cga == a_fork && cgb == b_fork) + break; + + if (cmd_nodes_equal(cga, cgb)) { + if (cga == a_join) + break; + if (cmd_subgraph_equal(cga, cgb, a_join)) + break; + } + } + if (j == vector_active(gb->to)) + return false; + } + return true; +} + +/* deep compare -- for FORK_TKN, the entire subgraph is compared. + * this is what's needed since we're not currently trying to partially + * merge subgraphs */ +static bool cmd_nodes_equal(struct graph_node *ga, struct graph_node *gb) +{ + struct cmd_token *a = ga->data, *b = gb->data; + + if (a->type != b->type || a->allowrepeat != b->allowrepeat) + return false; + if (a->type < SPECIAL_TKN && strcmp(a->text, b->text)) + return false; + /* one a ..., the other not. */ + if (cmd_nodes_link(ga, ga) != cmd_nodes_link(gb, gb)) + return false; + if (!a->varname != !b->varname) + return false; + if (a->varname && strcmp(a->varname, b->varname)) + return false; + + switch (a->type) { + case RANGE_TKN: + return a->min == b->min && a->max == b->max; + + case FORK_TKN: + /* one is keywords, the other just option or selector ... */ + if (cmd_nodes_link(a->forkjoin, ga) + != cmd_nodes_link(b->forkjoin, gb)) + return false; + if (cmd_nodes_link(ga, a->forkjoin) + != cmd_nodes_link(gb, b->forkjoin)) + return false; + return cmd_subgraph_equal(ga, gb, a->forkjoin); + + case VARIABLE_TKN: + case IPV4_TKN: + case IPV4_PREFIX_TKN: + case IPV6_PREFIX_TKN: + case IPV6_TKN: + case MAC_TKN: + case MAC_PREFIX_TKN: + case JOIN_TKN: + case START_TKN: + case END_TKN: + case NEG_ONLY_TKN: + case WORD_TKN: + case ASNUM_TKN: + return true; + } + + assert(!"Reached end of function we should never hit"); +} + +static void cmd_fork_bump_attr(struct graph_node *gn, struct graph_node *join, + uint8_t attr) +{ + size_t i; + struct cmd_token *tok = gn->data; + struct graph_node *stop = cmd_loopstop(gn); + + tok->attr = attr; + for (i = 0; i < vector_active(gn->to); i++) { + struct graph_node *next = vector_slot(gn->to, i); + if (next == stop || next == join) + continue; + cmd_fork_bump_attr(next, join, attr); + } +} + +/* move an entire subtree from the temporary graph resulting from + * parse() into the permanent graph for the command node. + * + * this touches rather deeply into the graph code unfortunately. + */ +static void cmd_reparent_tree(struct graph *fromgraph, struct graph *tograph, + struct graph_node *node) +{ + struct graph_node *stop = cmd_loopstop(node); + size_t i; + + for (i = 0; i < vector_active(fromgraph->nodes); i++) + if (vector_slot(fromgraph->nodes, i) == node) { + /* agressive iteration punching through subgraphs - may + * hit some + * nodes twice. reparent only if found on old graph */ + vector_unset(fromgraph->nodes, i); + vector_set(tograph->nodes, node); + break; + } + + for (i = 0; i < vector_active(node->to); i++) { + struct graph_node *next = vector_slot(node->to, i); + if (next != stop) + cmd_reparent_tree(fromgraph, tograph, next); + } +} + +static void cmd_free_recur(struct graph *graph, struct graph_node *node, + struct graph_node *stop) +{ + struct graph_node *next, *nstop; + + for (size_t i = vector_active(node->to); i; i--) { + next = vector_slot(node->to, i - 1); + if (next == stop) + continue; + nstop = cmd_loopstop(next); + if (nstop != next) + cmd_free_recur(graph, next, nstop); + cmd_free_recur(graph, nstop, stop); + } + graph_delete_node(graph, node); +} + +static void cmd_free_node(struct graph *graph, struct graph_node *node) +{ + struct cmd_token *tok = node->data; + if (tok->type == JOIN_TKN) + cmd_free_recur(graph, tok->forkjoin, node); + graph_delete_node(graph, node); +} + +/* recursive graph merge. call with + * old ~= new + * (which holds true for old == START_TKN, new == START_TKN) + */ +static void cmd_merge_nodes(struct graph *oldgraph, struct graph *newgraph, + struct graph_node *old, struct graph_node *new, + int direction) +{ + struct cmd_token *tok; + struct graph_node *old_skip, *new_skip; + old_skip = cmd_loopstop(old); + new_skip = cmd_loopstop(new); + + assert(direction == 1 || direction == -1); + + tok = old->data; + tok->refcnt += direction; + + size_t j, i; + for (j = 0; j < vector_active(new->to); j++) { + struct graph_node *cnew = vector_slot(new->to, j); + if (cnew == new_skip) + continue; + + for (i = 0; i < vector_active(old->to); i++) { + struct graph_node *cold = vector_slot(old->to, i); + if (cold == old_skip) + continue; + + if (cmd_nodes_equal(cold, cnew)) { + struct cmd_token *told = cold->data, + *tnew = cnew->data; + + if (told->type == END_TKN) { + if (direction < 0) { + graph_delete_node( + oldgraph, + vector_slot(cold->to, + 0)); + graph_delete_node(oldgraph, + cold); + } else + /* force no-match handling to + * install END_TKN */ + i = vector_active(old->to); + break; + } + + /* the entire fork compared as equal, we + * continue after it. */ + if (told->type == FORK_TKN) { + if (tnew->attr < told->attr + && direction > 0) + cmd_fork_bump_attr( + cold, told->forkjoin, + tnew->attr); + /* XXX: no reverse bump on uninstall */ + told = (cold = told->forkjoin)->data; + tnew = (cnew = tnew->forkjoin)->data; + } + if (tnew->attr < told->attr) + told->attr = tnew->attr; + + cmd_merge_nodes(oldgraph, newgraph, cold, cnew, + direction); + break; + } + } + /* nothing found => add new to old */ + if (i == vector_active(old->to) && direction > 0) { + graph_remove_edge(new, cnew); + + cmd_reparent_tree(newgraph, oldgraph, cnew); + + graph_add_edge(old, cnew); + } + } + + if (!tok->refcnt) + cmd_free_node(oldgraph, old); +} + +void cmd_graph_merge(struct graph *old, struct graph *new, int direction) +{ + assert(vector_active(old->nodes) >= 1); + assert(vector_active(new->nodes) >= 1); + + cmd_merge_nodes(old, new, vector_slot(old->nodes, 0), + vector_slot(new->nodes, 0), direction); +} + +void cmd_graph_names(struct graph *graph) +{ + struct graph_node *start; + + assert(vector_active(graph->nodes) >= 1); + start = vector_slot(graph->nodes, 0); + + /* apply varname on initial "[no]" */ + do { + if (vector_active(start->to) != 1) + break; + + struct graph_node *first = vector_slot(start->to, 0); + struct cmd_token *tok = first->data; + /* looking for an option with 2 choices, nothing or "no" */ + if (tok->type != FORK_TKN || vector_active(first->to) != 2) + break; + + struct graph_node *next0 = vector_slot(first->to, 0); + struct graph_node *next1 = vector_slot(first->to, 1); + /* one needs to be empty */ + if (next0 != tok->forkjoin && next1 != tok->forkjoin) + break; + + struct cmd_token *tok0 = next0->data; + struct cmd_token *tok1 = next1->data; + /* the other one needs to be "no" (only one will match here) */ + if ((tok0->type == WORD_TKN && !strcmp(tok0->text, "no"))) + cmd_token_varname_do(tok0, "no", VARNAME_AUTO); + if ((tok1->type == WORD_TKN && !strcmp(tok1->text, "no"))) + cmd_token_varname_do(tok1, "no", VARNAME_AUTO); + } while (0); +} + +#ifndef BUILDING_CLIPPY + +#include "command.h" +#include "log.h" + +void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf) +{ + static bool wasend; + + char nbuf[512]; + struct cmd_token *tok = gn->data; + const char *color = NULL; + + if (wasend) { + wasend = false; + return; + } + + if (tok->type == END_TKN) { + wasend = true; + return; + } + + snprintf(nbuf, sizeof(nbuf), " n%p [ shape=box, label=<", gn); + buffer_putstr(buf, nbuf); + snprintf(nbuf, sizeof(nbuf), "%s", + lookup_msg(tokennames, tok->type, NULL)); + buffer_putstr(buf, nbuf); + if (tok->attr & CMD_ATTR_DEPRECATED) + buffer_putstr(buf, " (d)"); + /* DEPRECATED implies HIDDEN, don't print both */ + else if (tok->attr & CMD_ATTR_HIDDEN) + buffer_putstr(buf, " (h)"); + if (tok->text) { + if (tok->type == WORD_TKN) + snprintf( + nbuf, sizeof(nbuf), + "
\"%s\"", + tok->text); + else + snprintf(nbuf, sizeof(nbuf), "
%s", tok->text); + buffer_putstr(buf, nbuf); + } + + switch (tok->type) { + case START_TKN: + color = "#ccffcc"; + break; + case FORK_TKN: + color = "#aaddff"; + break; + case JOIN_TKN: + color = "#ddaaff"; + break; + case NEG_ONLY_TKN: + color = "#ffddaa"; + break; + case WORD_TKN: + color = "#ffffff"; + break; + case RANGE_TKN: + case IPV4_TKN: + case IPV4_PREFIX_TKN: + case IPV6_TKN: + case IPV6_PREFIX_TKN: + case MAC_TKN: + case MAC_PREFIX_TKN: + case END_TKN: + case VARIABLE_TKN: + case ASNUM_TKN: + color = "#ffffff"; + break; + } + + /* + * Some compilers have the mistaken belief that we can + * get here without initializing color. + */ + snprintf(nbuf, sizeof(nbuf), + ">, style = filled, fillcolor = \"%s\" ];\n", color); + buffer_putstr(buf, nbuf); + + for (unsigned int i = 0; i < vector_active(gn->to); i++) { + struct graph_node *adj = vector_slot(gn->to, i); + + if (((struct cmd_token *)adj->data)->type == END_TKN) { + snprintf(nbuf, sizeof(nbuf), " n%p -> end%p;\n", gn, + adj); + buffer_putstr(buf, nbuf); + snprintf( + nbuf, sizeof(nbuf), + " end%p [ shape=box, label=, style = filled, fillcolor = \"#ffddaa\" ];\n", + adj); + } else + snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, + adj); + + buffer_putstr(buf, nbuf); + } +} + +char *cmd_graph_dump_dot(struct graph *cmdgraph) +{ + struct graph_node *start = vector_slot(cmdgraph->nodes, 0); + + return graph_dump_dot(cmdgraph, start, cmd_graph_node_print_cb); +} + +#endif /* BUILDING_CLIPPY */ diff --git a/lib/command_graph.h b/lib/command_graph.h new file mode 100644 index 0000000..25aa47d --- /dev/null +++ b/lib/command_graph.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLI graph handling + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + */ + +#ifndef _FRR_COMMAND_GRAPH_H +#define _FRR_COMMAND_GRAPH_H + +#include +#include + +#include "memory.h" +#include "vector.h" +#include "graph.h" +#include "xref.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(CMD_ARG); + +struct vty; + +/** + * Types for tokens. + * + * The type determines what kind of data the token can match (in the + * matching use case) or hold (in the argv use case). + */ +/* clang-format off */ +enum cmd_token_type { + WORD_TKN, // words + VARIABLE_TKN, // almost anything + RANGE_TKN, // integer range + IPV4_TKN, // IPV4 addresses + IPV4_PREFIX_TKN, // IPV4 network prefixes + IPV6_TKN, // IPV6 prefixes + IPV6_PREFIX_TKN, // IPV6 network prefixes + MAC_TKN, // Ethernet address + MAC_PREFIX_TKN, // Ethernet address w/ CIDR mask + ASNUM_TKN, // AS dot format + + /* plumbing types */ + FORK_TKN, // marks subgraph beginning + JOIN_TKN, // marks subgraph end + START_TKN, // first token in line + END_TKN, // last token in line + NEG_ONLY_TKN, // filter token, match if "no ..." command + + SPECIAL_TKN = FORK_TKN, +}; +/* clang-format on */ + +#define IS_VARYING_TOKEN(x) ((x) >= VARIABLE_TKN && (x) < FORK_TKN) + +/* Command attributes */ +enum { + CMD_ATTR_YANG = (1 << 0), + CMD_ATTR_HIDDEN = (1 << 1), + CMD_ATTR_DEPRECATED = (1 << 2), + CMD_ATTR_NOSH = (1 << 3), +}; + +enum varname_src { + VARNAME_NONE = 0, + VARNAME_AUTO, + VARNAME_VAR, + VARNAME_TEXT, + VARNAME_EXPLICIT, +}; + +/* Command token struct. */ +struct cmd_token { + enum cmd_token_type type; // token type + uint8_t attr; // token attributes + bool allowrepeat; // matcher allowed to match token repetitively? + uint8_t varname_src; + uint32_t refcnt; + + char *text; // token text + char *desc; // token description + long long min, max; // for ranges + char *arg; // user input that matches this token + char *varname; + + struct graph_node *forkjoin; // paired FORK/JOIN for JOIN/FORK +}; + +/* Structure of command element. */ +struct cmd_element { + const char *string; /* Command specification by string. */ + const char *doc; /* Documentation of this command. */ + int daemon; /* Daemon to which this command belong. */ + uint32_t attr; /* Command attributes */ + + /* handler function for command */ + int (*func)(const struct cmd_element *, struct vty *, int, + struct cmd_token *[]); + + const char *name; /* symbol name for debugging */ + struct xref xref; +}; + +/* text for command */ +#define CMD_CR_TEXT "" + +/* memory management for cmd_token */ +extern struct cmd_token *cmd_token_new(enum cmd_token_type, uint8_t attr, + const char *text, const char *desc); +extern struct cmd_token *cmd_token_dup(struct cmd_token *); +extern void cmd_token_del(struct cmd_token *); +extern void cmd_token_varname_set(struct cmd_token *token, const char *varname); +extern void cmd_token_varname_seqappend(struct graph_node *n); +extern void cmd_token_varname_join(struct graph_node *n, const char *varname); + +extern void cmd_graph_parse(struct graph *graph, const struct cmd_element *cmd); +extern void cmd_graph_names(struct graph *graph); +extern void cmd_graph_merge(struct graph *old, struct graph *n, + int direction); +/* + * Print callback for DOT dumping. + * + * See graph.h for more details. + */ +extern void cmd_graph_node_print_cb(struct graph_node *gn, struct buffer *buf); +/* + * Dump command graph to DOT. + * + * cmdgraph + * A command graph to dump + * + * Returns: + * String allocated with MTYPE_TMP representing this graph + */ +char *cmd_graph_dump_dot(struct graph *cmdgraph); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_COMMAND_GRAPH_H */ diff --git a/lib/command_lex.l b/lib/command_lex.l new file mode 100644 index 0000000..dc89191 --- /dev/null +++ b/lib/command_lex.l @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Command format string lexer for CLI backend. + * + * -- + * Copyright (C) 2015 Cumulus Networks, Inc. + */ + +%top{ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +} +%{ +/* ignore flex generated code in static analyzer */ +#ifndef __clang_analyzer__ + +/* ignore harmless bugs in old versions of flex */ +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +#include "lib/command_parse.h" + +#define YY_USER_ACTION yylloc->last_column += yyleng; +#define LOC_STEP do { if (yylloc) { \ + yylloc->first_column = yylloc->last_column; \ + yylloc->first_line = yylloc->last_line; \ + } } while(0) +%} + +IPV4 A\.B\.C\.D +IPV4_PREFIX A\.B\.C\.D\/M +IPV6 X:X::X:X +IPV6_PREFIX X:X::X:X\/M +MAC X:X:X:X:X:X +MAC_PREFIX X:X:X:X:X:X\/M +VARIABLE [A-Z][-_A-Z:0-9]+ +WORD (\-|\+)?[a-zA-Z0-9\*][-+_a-zA-Z0-9\*]* +NUMBER (\-|\+)?[0-9]{1,20} +RANGE \({NUMBER}[ ]?\-[ ]?{NUMBER}\) +ASNUM ASNUM + +/* yytext shall be a pointer */ +%pointer +%option noyywrap +%option nounput +%option noinput +%option outfile="lib/command_lex.c" +%option header-file="lib/command_lex.h" +%option prefix="cmd_yy" +%option reentrant +%option bison-bridge +%option bison-locations + +%% +%{ + LOC_STEP; +%} + +[ \t]+ LOC_STEP /* ignore whitespace */; +{ASNUM} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return ASNUM;} +{IPV4} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4;} +{IPV4_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV4_PREFIX;} +{IPV6} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6;} +{IPV6_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return IPV6_PREFIX;} +{MAC} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC;} +{MAC_PREFIX} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return MAC_PREFIX;} +{VARIABLE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return VARIABLE;} +{WORD} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return WORD;} +{RANGE} {yylval->string = XSTRDUP(MTYPE_LEX, yytext); return RANGE;} +!\[ {yylval->string = NULL; return EXCL_BRACKET;} +. {return yytext[0];} +%% + +static YY_BUFFER_STATE buffer; + +void set_lexer_string (yyscan_t *scn, const char *string) +{ + *scn = NULL; + yylex_init(scn); + buffer = yy_scan_string (string, *scn); +} + +void cleanup_lexer (yyscan_t *scn) +{ + // yy_delete_buffer (buffer, *scn); + yylex_destroy(*scn); +} + +#endif /* __clang_analyzer__ */ diff --git a/lib/command_match.c b/lib/command_match.c new file mode 100644 index 0000000..97e6aeb --- /dev/null +++ b/lib/command_match.c @@ -0,0 +1,1072 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Input matching routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +#include + +#include "command_match.h" +#include "memory.h" +#include "asn.h" + +DEFINE_MTYPE_STATIC(LIB, CMD_MATCHSTACK, "Command Match Stack"); + +#ifdef TRACE_MATCHER +#define TM 1 +#else +#define TM 0 +#endif + +#define trace_matcher(...) \ + do { \ + if (TM) \ + fprintf(stderr, __VA_ARGS__); \ + } while (0); + +/* matcher helper prototypes */ +static int add_nexthops(struct list *, struct graph_node *, + struct graph_node **, size_t, bool); + +static enum matcher_rv command_match_r(struct graph_node *, vector, + unsigned int, struct graph_node **, + struct list **); + +static int score_precedence(enum cmd_token_type); + +static enum match_type min_match_level(enum cmd_token_type); + +static void del_arglist(struct list *); + +static struct cmd_token *disambiguate_tokens(struct cmd_token *, + struct cmd_token *, char *); + +static struct list *disambiguate(struct list *, struct list *, vector, + unsigned int); + +int compare_completions(const void *, const void *); + +/* token matcher prototypes */ +static enum match_type match_token(struct cmd_token *, char *); + +static enum match_type match_ipv4(const char *); + +static enum match_type match_ipv4_prefix(const char *); + +static enum match_type match_ipv6_prefix(const char *, bool); + +static enum match_type match_range(struct cmd_token *, const char *); + +static enum match_type match_word(struct cmd_token *, const char *); + +static enum match_type match_variable(struct cmd_token *, const char *); + +static enum match_type match_mac(const char *, bool); + +static bool is_neg(vector vline, size_t idx) +{ + if (idx >= vector_active(vline) || !vector_slot(vline, idx)) + return false; + return !strcmp(vector_slot(vline, idx), "no"); +} + +enum matcher_rv command_match(struct graph *cmdgraph, vector vline, + struct list **argv, const struct cmd_element **el) +{ + struct graph_node *stack[CMD_ARGC_MAX]; + enum matcher_rv status; + *argv = NULL; + + // prepend a dummy token to match that pesky start node + vector vvline = vector_init(vline->alloced + 1); + vector_set_index(vvline, 0, XSTRDUP(MTYPE_TMP, "dummy")); + memcpy(vvline->index + 1, vline->index, + sizeof(void *) * vline->alloced); + vvline->active = vline->active + 1; + + struct graph_node *start = vector_slot(cmdgraph->nodes, 0); + status = command_match_r(start, vvline, 0, stack, argv); + if (status == MATCHER_OK) { // successful match + struct listnode *head = listhead(*argv); + struct listnode *tail = listtail(*argv); + + assert(head); + assert(tail); + + // delete dummy start node + cmd_token_del((struct cmd_token *)head->data); + list_delete_node(*argv, head); + + // get cmd_element out of list tail + *el = listgetdata(tail); + list_delete_node(*argv, tail); + + // now argv is an ordered list of cmd_token matching the user + // input, with each cmd_token->arg holding the corresponding + // input + assert(*el); + } else if (*argv) { + del_arglist(*argv); + *argv = NULL; + } + + if (!*el) { + trace_matcher("No match\n"); + } else { + trace_matcher("Matched command\n->string %s\n->desc %s\n", + (*el)->string, (*el)->doc); + } + + // free the leader token we alloc'd + XFREE(MTYPE_TMP, vector_slot(vvline, 0)); + // free vector + vector_free(vvline); + + return status; +} + +/** + * Builds an argument list given a DFA and a matching input line. + * + * First the function determines if the node it is passed matches the first + * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it + * does match, then it saves the input token as the head of an argument list. + * + * The next step is to see if there is further input in the input line. If + * there is not, the current node's children are searched to see if any of them + * are leaves (type END_TKN). If this is the case, then the bottom of the + * recursion stack has been reached, the leaf is pushed onto the argument list, + * the current node is pushed, and the resulting argument list is + * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating + * that there is no match for the input along this path (MATCHER_INCOMPLETE). + * + * If there is further input, then the function recurses on each of the current + * node's children, passing them the input line minus the token that was just + * matched. For each child, the return value of the recursive call is + * inspected. If it is null, then there is no match for the input along the + * subgraph headed by that child. If it is not null, then there is at least one + * input match in that subgraph (more on this in a moment). + * + * If a recursive call on a child returns a non-null value, then it has matched + * the input given it on the subgraph that starts with that child. However, due + * to the flexibility of the grammar, it is sometimes the case that two or more + * child graphs match the same input (two or more of the recursive calls have + * non-NULL return values). This is not a valid state, since only one true + * match is possible. In order to resolve this conflict, the function keeps a + * reference to the child node that most specifically matches the input. This + * is done by assigning each node type a precedence. If a child is found to + * match the remaining input, then the precedence values of the current + * best-matching child and this new match are compared. The node with higher + * precedence is kept, and the other match is discarded. Due to the recursive + * nature of this function, it is only necessary to compare the precedence of + * immediate children, since all subsequent children will already have been + * disambiguated in this way. + * + * In the event that two children are found to match with the same precedence, + * then the input is ambiguous for the passed cmd_element and NULL is returned. + * + * @param[in] start the start node. + * @param[in] vline the vectorized input line. + * @param[in] n the index of the first input token. + * @return A linked list of n elements. The first n-1 elements are pointers to + * struct cmd_token and represent the sequence of tokens matched by the input. + * The ->arg field of each token points to a copy of the input matched on it. + * The final nth element is a pointer to struct cmd_element, which is the + * command that was matched. + * + * If no match was found, the return value is NULL. + */ +static enum matcher_rv command_match_r(struct graph_node *start, vector vline, + unsigned int n, + struct graph_node **stack, + struct list **currbest) +{ + assert(n < vector_active(vline)); + + enum matcher_rv status = MATCHER_NO_MATCH; + + // get the minimum match level that can count as a full match + struct cmd_token *copy, *token = start->data; + enum match_type minmatch = min_match_level(token->type); + + /* check history/stack of tokens + * this disallows matching the same one more than once if there is a + * circle in the graph (used for keyword arguments) */ + if (n == CMD_ARGC_MAX) + return MATCHER_NO_MATCH; + if (!token->allowrepeat) + for (size_t s = 0; s < n; s++) + if (stack[s] == start) + return MATCHER_NO_MATCH; + + // get the current operating input token + char *input_token = vector_slot(vline, n); + +#ifdef TRACE_MATCHER + fprintf(stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token, + token->text); + enum match_type mt = match_token(token, input_token); + fprintf(stdout, "type: %d ", token->type); + fprintf(stdout, "min: %d - ", minmatch); + switch (mt) { + case trivial_match: + fprintf(stdout, "trivial_match "); + break; + case no_match: + fprintf(stdout, "no_match "); + break; + case partly_match: + fprintf(stdout, "partly_match "); + break; + case exact_match: + fprintf(stdout, "exact_match "); + break; + } + if (mt >= minmatch) + fprintf(stdout, " MATCH"); + fprintf(stdout, "\n"); +#endif + + // if we don't match this node, die + if (match_token(token, input_token) < minmatch) + return MATCHER_NO_MATCH; + + stack[n] = start; + + // pointers for iterating linklist + struct listnode *ln; + struct graph_node *gn; + + // get all possible nexthops + struct list *next = list_new(); + add_nexthops(next, start, NULL, 0, is_neg(vline, 1)); + + // determine the best match + for (ALL_LIST_ELEMENTS_RO(next, ln, gn)) { + // if we've matched all input we're looking for END_TKN + if (n + 1 == vector_active(vline)) { + struct cmd_token *tok = gn->data; + if (tok->type == END_TKN) { + // if more than one END_TKN in the follow set + if (*currbest) { + status = MATCHER_AMBIGUOUS; + break; + } else { + status = MATCHER_OK; + } + *currbest = list_new(); + // node should have one child node with the + // element + struct graph_node *leaf = + vector_slot(gn->to, 0); + // last node in the list will hold the + // cmd_element; this is important because + // list_delete() expects that all nodes have + // the same data type, so when deleting this + // list the last node must be manually deleted + struct cmd_element *el = leaf->data; + listnode_add(*currbest, el); + (*currbest)->del = + (void (*)(void *)) & cmd_token_del; + // do not break immediately; continue walking + // through the follow set to ensure that there + // is exactly one END_TKN + } + continue; + } + + // else recurse on candidate child node + struct list *result = NULL; + enum matcher_rv rstat = + command_match_r(gn, vline, n + 1, stack, &result); + + // save the best match + if (result && *currbest) { + // pick the best of two matches + struct list *newbest = + disambiguate(*currbest, result, vline, n + 1); + + // current best and result are ambiguous + if (!newbest) + status = MATCHER_AMBIGUOUS; + // current best is still the best, but ambiguous + else if (newbest == *currbest + && status == MATCHER_AMBIGUOUS) + status = MATCHER_AMBIGUOUS; + // result is better, but also ambiguous + else if (newbest == result + && rstat == MATCHER_AMBIGUOUS) + status = MATCHER_AMBIGUOUS; + // one or the other is superior and not ambiguous + else + status = MATCHER_OK; + + // delete the unnecessary result + struct list *todelete = + ((newbest && newbest == result) ? *currbest + : result); + del_arglist(todelete); + + *currbest = newbest ? newbest : *currbest; + } else if (result) { + status = rstat; + *currbest = result; + } else if (!*currbest) { + status = MAX(rstat, status); + } + } + if (*currbest) { + // copy token, set arg and prepend to currbest + token = start->data; + copy = cmd_token_dup(token); + copy->arg = XSTRDUP(MTYPE_CMD_ARG, input_token); + listnode_add_before(*currbest, (*currbest)->head, copy); + } else if (n + 1 == vector_active(vline) && status == MATCHER_NO_MATCH) + status = MATCHER_INCOMPLETE; + + // cleanup + list_delete(&next); + + return status; +} + +static void stack_del(void *val) +{ + XFREE(MTYPE_CMD_MATCHSTACK, val); +} + +enum matcher_rv command_complete(struct graph *graph, vector vline, + struct list **completions) +{ + // pointer to next input token to match + char *input_token; + bool neg = is_neg(vline, 0); + + struct list * + current = + list_new(), // current nodes to match input token against + *next = list_new(); // possible next hops after current input + // token + current->del = next->del = stack_del; + + // pointers used for iterating lists + struct graph_node **gstack, **newstack; + struct listnode *node; + + // add all children of start node to list + struct graph_node *start = vector_slot(graph->nodes, 0); + add_nexthops(next, start, &start, 0, neg); + + unsigned int idx; + for (idx = 0; idx < vector_active(vline) && next->count > 0; idx++) { + list_delete(¤t); + current = next; + next = list_new(); + next->del = stack_del; + + input_token = vector_slot(vline, idx); + + int exact_match_exists = 0; + for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) + if (!exact_match_exists) + exact_match_exists = + (match_token(gstack[0]->data, + input_token) + == exact_match); + else + break; + + for (ALL_LIST_ELEMENTS_RO(current, node, gstack)) { + struct cmd_token *token = gstack[0]->data; + + if (token->attr & CMD_ATTR_HIDDEN) + continue; + + enum match_type minmatch = min_match_level(token->type); + trace_matcher("\"%s\" matches \"%s\" (%d) ? ", + input_token, token->text, token->type); + + unsigned int last_token = + (vector_active(vline) - 1 == idx); + enum match_type matchtype = + match_token(token, input_token); + switch (matchtype) { + // occurs when last token is whitespace + case trivial_match: + trace_matcher("trivial_match\n"); + assert(last_token); + newstack = XMALLOC(MTYPE_CMD_MATCHSTACK, + sizeof(struct graph_node *)); + /* we're not recursing here, just the first + * element is OK */ + newstack[0] = gstack[0]; + listnode_add(next, newstack); + break; + case partly_match: + trace_matcher("partly_match\n"); + if (exact_match_exists && !last_token) + break; + fallthrough; + case exact_match: + trace_matcher("exact_match\n"); + if (last_token) { + newstack = XMALLOC( + MTYPE_CMD_MATCHSTACK, + sizeof(struct graph_node *)); + /* same as above, not recursing on this + */ + newstack[0] = gstack[0]; + listnode_add(next, newstack); + } else if (matchtype >= minmatch) + add_nexthops(next, gstack[0], gstack, + idx + 1, neg); + break; + case no_match: + trace_matcher("no_match\n"); + break; + } + } + } + + /* Variable summary + * ----------------------------------------------------------------- + * token = last input token processed + * idx = index in `command` of last token processed + * current = set of all transitions from the previous input token + * next = set of all nodes reachable from all nodes in `matched` + */ + + enum matcher_rv mrv = idx == vector_active(vline) && next->count + ? MATCHER_OK + : MATCHER_NO_MATCH; + + *completions = NULL; + if (!MATCHER_ERROR(mrv)) { + // extract cmd_token into list + *completions = list_new(); + for (ALL_LIST_ELEMENTS_RO(next, node, gstack)) { + listnode_add(*completions, gstack[0]->data); + } + } + + list_delete(¤t); + list_delete(&next); + + return mrv; +} + +/** + * Adds all children that are reachable by one parser hop to the given list. + * special tokens except END_TKN are treated as transparent. + * + * @param[in] list to add the nexthops to + * @param[in] node to start calculating nexthops from + * @param[in] stack listing previously visited nodes, if non-NULL. + * @param[in] stackpos how many valid entries are in stack + * @return the number of children added to the list + * + * NB: non-null "stack" means that new stacks will be added to "list" as + * output, instead of direct node pointers! + */ +static int add_nexthops(struct list *list, struct graph_node *node, + struct graph_node **stack, size_t stackpos, bool neg) +{ + int added = 0; + struct graph_node *child; + struct graph_node **nextstack; + for (unsigned int i = 0; i < vector_active(node->to); i++) { + child = vector_slot(node->to, i); + size_t j; + struct cmd_token *token = child->data; + if (!token->allowrepeat && stack) { + for (j = 0; j < stackpos; j++) + if (child == stack[j]) + break; + if (j != stackpos) + continue; + } + + if (token->type == NEG_ONLY_TKN && !neg) + continue; + + if (token->type >= SPECIAL_TKN && token->type != END_TKN) { + added += + add_nexthops(list, child, stack, stackpos, neg); + } else { + if (stack) { + nextstack = XMALLOC( + MTYPE_CMD_MATCHSTACK, + (stackpos + 1) + * sizeof(struct graph_node *)); + nextstack[0] = child; + memcpy(nextstack + 1, stack, + stackpos * sizeof(struct graph_node *)); + + listnode_add(list, nextstack); + } else + listnode_add(list, child); + added++; + } + } + + return added; +} + +/** + * Determines the node types for which a partial match may count as a full + * match. Enables command abbrevations. + * + * @param[in] type node type + * @return minimum match level needed to for a token to fully match + */ +static enum match_type min_match_level(enum cmd_token_type type) +{ + switch (type) { + // anything matches a start node, for the sake of recursion + case START_TKN: + return no_match; + // allowing words to partly match enables command abbreviation + case WORD_TKN: + return partly_match; + case RANGE_TKN: + case IPV4_TKN: + case IPV4_PREFIX_TKN: + case IPV6_TKN: + case IPV6_PREFIX_TKN: + case MAC_TKN: + case MAC_PREFIX_TKN: + case FORK_TKN: + case JOIN_TKN: + case END_TKN: + case NEG_ONLY_TKN: + case VARIABLE_TKN: + case ASNUM_TKN: + return exact_match; + } + + assert(!"Reached end of function we should never hit"); +} + +/** + * Assigns precedence scores to node types. + * + * @param[in] type node type to score + * @return precedence score + */ +static int score_precedence(enum cmd_token_type type) +{ + switch (type) { + // some of these are mutually exclusive, so they share + // the same precedence value + case IPV4_TKN: + case IPV4_PREFIX_TKN: + case IPV6_TKN: + case IPV6_PREFIX_TKN: + case MAC_TKN: + case MAC_PREFIX_TKN: + case RANGE_TKN: + return 2; + case ASNUM_TKN: + case WORD_TKN: + return 3; + case VARIABLE_TKN: + return 4; + case JOIN_TKN: + case START_TKN: + case END_TKN: + case NEG_ONLY_TKN: + case SPECIAL_TKN: + return 10; + } + + assert(!"Reached end of function we should never hit"); +} + +/** + * Picks the better of two possible matches for a token. + * + * @param[in] first candidate node matching token + * @param[in] second candidate node matching token + * @param[in] token the token being matched + * @return the best-matching node, or NULL if the two are entirely ambiguous + */ +static struct cmd_token *disambiguate_tokens(struct cmd_token *first, + struct cmd_token *second, + char *input_token) +{ + // if the types are different, simply go off of type precedence + if (first->type != second->type) { + int firstprec = score_precedence(first->type); + int secndprec = score_precedence(second->type); + if (firstprec != secndprec) + return firstprec < secndprec ? first : second; + else + return NULL; + } + + // if they're the same, return the more exact match + enum match_type fmtype = match_token(first, input_token); + enum match_type smtype = match_token(second, input_token); + if (fmtype != smtype) + return fmtype > smtype ? first : second; + + return NULL; +} + +/** + * Picks the better of two possible matches for an input line. + * + * @param[in] first candidate list of cmd_token matching vline + * @param[in] second candidate list of cmd_token matching vline + * @param[in] vline the input line being matched + * @param[in] n index into vline to start comparing at + * @return the best-matching list, or NULL if the two are entirely ambiguous + */ +static struct list *disambiguate(struct list *first, struct list *second, + vector vline, unsigned int n) +{ + assert(first != NULL); + assert(second != NULL); + // doesn't make sense for these to be inequal length + assert(first->count == second->count); + assert(first->count == vector_active(vline) - n + 1); + + struct listnode *fnode = listhead_unchecked(first), + *snode = listhead_unchecked(second); + struct cmd_token *ftok = listgetdata(fnode), *stok = listgetdata(snode), + *best = NULL; + + // compare each token, if one matches better use that one + for (unsigned int i = n; i < vector_active(vline); i++) { + char *token = vector_slot(vline, i); + if ((best = disambiguate_tokens(ftok, stok, token))) + return best == ftok ? first : second; + fnode = listnextnode(fnode); + snode = listnextnode(snode); + ftok = listgetdata(fnode); + stok = listgetdata(snode); + } + + return NULL; +} + +/* + * Deletion function for arglist. + * + * Since list->del for arglists expects all listnode->data to hold cmd_token, + * but arglists have cmd_element as the data for the tail, this function + * manually deletes the tail before deleting the rest of the list as usual. + * + * The cmd_element at the end is *not* a copy. It is the one and only. + * + * @param list the arglist to delete + */ +static void del_arglist(struct list *list) +{ + // manually delete last node + struct listnode *tail = listtail(list); + tail->data = NULL; + list_delete_node(list, tail); + + // delete the rest of the list as usual + list_delete(&list); +} + +/*---------- token level matching functions ----------*/ + +static enum match_type match_token(struct cmd_token *token, char *input_token) +{ + // nothing trivially matches everything + if (!input_token) + return trivial_match; + + switch (token->type) { + case WORD_TKN: + return match_word(token, input_token); + case IPV4_TKN: + return match_ipv4(input_token); + case IPV4_PREFIX_TKN: + return match_ipv4_prefix(input_token); + case IPV6_TKN: + return match_ipv6_prefix(input_token, false); + case IPV6_PREFIX_TKN: + return match_ipv6_prefix(input_token, true); + case RANGE_TKN: + return match_range(token, input_token); + case VARIABLE_TKN: + return match_variable(token, input_token); + case MAC_TKN: + return match_mac(input_token, false); + case MAC_PREFIX_TKN: + return match_mac(input_token, true); + case ASNUM_TKN: + return asn_str2asn_match(input_token); + case END_TKN: + case FORK_TKN: + case JOIN_TKN: + case START_TKN: + case NEG_ONLY_TKN: + return no_match; + } + + assert(!"Reached end of function we should never hit"); +} + +#define IPV4_ADDR_STR "0123456789." +#define IPV4_PREFIX_STR "0123456789./" + +static enum match_type match_ipv4(const char *str) +{ + const char *sp; + int dots = 0, nums = 0; + char buf[4]; + + for (;;) { + memset(buf, 0, sizeof(buf)); + sp = str; + while (*str != '\0') { + if (*str == '.') { + if (dots >= 3) + return no_match; + + if (*(str + 1) == '.') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + if (!isdigit((unsigned char)*str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + memcpy(buf, sp, str - sp); + + int v = atoi(buf); + + if (v > 255) + return no_match; + if (v > 0 && buf[0] == '0') + return no_match; + + nums++; + + if (*str == '\0') + break; + + str++; + } + + if (nums < 4) + return partly_match; + + return exact_match; +} + +static enum match_type match_ipv4_prefix(const char *str) +{ + const char *sp; + int dots = 0; + char buf[4]; + + for (;;) { + memset(buf, 0, sizeof(buf)); + sp = str; + while (*str != '\0' && *str != '/') { + if (*str == '.') { + if (dots == 3) + return no_match; + + if (*(str + 1) == '.' || *(str + 1) == '/') + return no_match; + + if (*(str + 1) == '\0') + return partly_match; + + dots++; + break; + } + + if (!isdigit((unsigned char)*str)) + return no_match; + + str++; + } + + if (str - sp > 3) + return no_match; + + memcpy(buf, sp, str - sp); + + int v = atoi(buf); + + if (v > 255) + return no_match; + if (v > 0 && buf[0] == '0') + return no_match; + + if (dots == 3) { + if (*str == '/') { + if (*(str + 1) == '\0') + return partly_match; + + str++; + break; + } else if (*str == '\0') + return partly_match; + } + + if (*str == '\0') + return partly_match; + + str++; + } + + sp = str; + while (*str != '\0') { + if (!isdigit((unsigned char)*str)) + return no_match; + + str++; + } + + if (atoi(sp) > IPV4_MAX_BITLEN) + return no_match; + + return exact_match; +} + +#define IPV6_ADDR_STR "0123456789abcdefABCDEF:." +#define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./" +#define STATE_START 1 +#define STATE_COLON 2 +#define STATE_DOUBLE 3 +#define STATE_ADDR 4 +#define STATE_DOT 5 +#define STATE_SLASH 6 +#define STATE_MASK 7 + +static enum match_type match_ipv6_prefix(const char *str, bool prefix) +{ + int state = STATE_START; + int colons = 0, nums = 0, double_colon = 0; + int mask; + const char *sp = NULL, *start = str; + char *endptr = NULL; + + if (str == NULL) + return partly_match; + + if (strspn(str, prefix ? IPV6_PREFIX_STR : IPV6_ADDR_STR) + != strlen(str)) + return no_match; + + while (*str != '\0' && state != STATE_MASK) { + switch (state) { + case STATE_START: + if (*str == ':') { + if (*(str + 1) != ':' && *(str + 1) != '\0') + return no_match; + colons--; + state = STATE_COLON; + } else { + sp = str; + state = STATE_ADDR; + } + + continue; + case STATE_COLON: + colons++; + if (*(str + 1) == '/') + return no_match; + else if (*(str + 1) == ':') + state = STATE_DOUBLE; + else { + sp = str + 1; + state = STATE_ADDR; + } + break; + case STATE_DOUBLE: + if (double_colon) + return no_match; + + if (*(str + 1) == ':') + return no_match; + else { + if (*(str + 1) != '\0' && *(str + 1) != '/') + colons++; + sp = str + 1; + + if (*(str + 1) == '/') + state = STATE_SLASH; + else + state = STATE_ADDR; + } + + double_colon++; + nums += 1; + break; + case STATE_ADDR: + if (*(str + 1) == ':' || *(str + 1) == '.' + || *(str + 1) == '\0' || *(str + 1) == '/') { + if (str - sp > 3) + return no_match; + + for (; sp <= str; sp++) + if (*sp == '/') + return no_match; + + nums++; + + if (*(str + 1) == ':') + state = STATE_COLON; + else if (*(str + 1) == '.') { + if (colons || double_colon) + state = STATE_DOT; + else + return no_match; + } else if (*(str + 1) == '/') + state = STATE_SLASH; + } + break; + case STATE_DOT: + state = STATE_ADDR; + break; + case STATE_SLASH: + if (*(str + 1) == '\0') + return partly_match; + + state = STATE_MASK; + break; + default: + break; + } + + if (nums > 11) + return no_match; + + if (colons > 7) + return no_match; + + str++; + } + + if (!prefix) { + struct sockaddr_in6 sin6_dummy; + int ret = inet_pton(AF_INET6, start, &sin6_dummy.sin6_addr); + return ret == 1 ? exact_match : partly_match; + } + + if (state < STATE_MASK) + return partly_match; + + mask = strtol(str, &endptr, 10); + if (*endptr != '\0') + return no_match; + + if (mask < 0 || mask > IPV6_MAX_BITLEN) + return no_match; + + return exact_match; +} + +static enum match_type match_range(struct cmd_token *token, const char *str) +{ + assert(token->type == RANGE_TKN); + + char *endptr = NULL; + long long val; + + val = strtoll(str, &endptr, 10); + if (*endptr != '\0') + return no_match; + + if (val < token->min || val > token->max) + return no_match; + else + return exact_match; +} + +static enum match_type match_word(struct cmd_token *token, const char *word) +{ + assert(token->type == WORD_TKN); + + // if the passed token is 0 length, partly match + if (!strlen(word)) + return partly_match; + + // if the passed token is strictly a prefix of the full word, partly + // match + if (strlen(word) < strlen(token->text)) + return !strncmp(token->text, word, strlen(word)) ? partly_match + : no_match; + + // if they are the same length and exactly equal, exact match + else if (strlen(word) == strlen(token->text)) + return !strncmp(token->text, word, strlen(word)) ? exact_match + : no_match; + + return no_match; +} + +static enum match_type match_variable(struct cmd_token *token, const char *word) +{ + assert(token->type == VARIABLE_TKN); + return exact_match; +} + +#define MAC_CHARS "ABCDEFabcdef0123456789:" + +static enum match_type match_mac(const char *word, bool prefix) +{ + /* 6 2-digit hex numbers separated by 5 colons */ + size_t mac_explen = 6 * 2 + 5; + /* '/' + 2-digit integer */ + size_t mask_len = 1 + 2; + unsigned int i; + char *eptr; + unsigned int maskval; + + /* length check */ + if (strlen(word) > mac_explen + (prefix ? mask_len : 0)) + return no_match; + + /* address check */ + for (i = 0; i < mac_explen; i++) { + if (word[i] == '\0' || !strchr(MAC_CHARS, word[i])) + break; + if (((i + 1) % 3 == 0) != (word[i] == ':')) + return no_match; + } + + /* incomplete address */ + if (i < mac_explen && word[i] == '\0') + return partly_match; + else if (i < mac_explen) + return no_match; + + /* mask check */ + if (prefix && word[i] == '/') { + if (word[++i] == '\0') + return partly_match; + + maskval = strtoul(&word[i], &eptr, 10); + if (*eptr != '\0' || maskval > 48) + return no_match; + } else if (prefix && word[i] == '\0') { + return partly_match; + } else if (prefix) { + return no_match; + } + + return exact_match; +} diff --git a/lib/command_match.h b/lib/command_match.h new file mode 100644 index 0000000..3e7a549 --- /dev/null +++ b/lib/command_match.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Input matching routines for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +#ifndef _ZEBRA_COMMAND_MATCH_H +#define _ZEBRA_COMMAND_MATCH_H + +#include "graph.h" +#include "linklist.h" +#include "command.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* matcher result value */ +enum matcher_rv { + MATCHER_NO_MATCH, + MATCHER_INCOMPLETE, + MATCHER_AMBIGUOUS, + MATCHER_OK, +}; + +/* completion match types */ +enum match_type { + trivial_match, // the input is null + no_match, // the input does not match + partly_match, // the input matches but is incomplete + exact_match // the input matches and is complete +}; + +/* Defines which matcher_rv values constitute an error. Should be used with + * matcher_rv return values to do basic error checking. + */ +#define MATCHER_ERROR(matcher_rv) \ + ((matcher_rv) == MATCHER_INCOMPLETE \ + || (matcher_rv) == MATCHER_NO_MATCH \ + || (matcher_rv) == MATCHER_AMBIGUOUS) + +/** + * Attempt to find an exact command match for a line of user input. + * + * @param[in] cmdgraph command graph to match against + * @param[in] vline vectorized input string + * @param[out] argv pointer to argument list if successful match, NULL + * otherwise. The elements of this list are pointers to struct cmd_token + * and represent the sequence of tokens matched by the input. The ->arg + * field of each token points to a copy of the input matched on it. These + * may be safely deleted or modified. + * @param[out] element pointer to matched cmd_element if successful match, + * or NULL when MATCHER_ERROR(rv) is true. The cmd_element may *not* be + * safely deleted or modified; it is the instance initialized on startup. + * @return matcher status + */ +enum matcher_rv command_match(struct graph *cmdgraph, vector vline, + struct list **argv, + const struct cmd_element **element); + +/** + * Compiles possible completions for a given line of user input. + * + * @param[in] start the start node of the DFA to match against + * @param[in] vline vectorized input string + * @param[out] completions pointer to list of cmd_token representing + * acceptable next inputs, or NULL when MATCHER_ERROR(rv) is true. + * The elements of this list are pointers to struct cmd_token and take on a + * variety of forms depending on the passed vline. If the last element in vline + * is NULL, all previous elements are considered to be complete words (the case + * when a space is the last token of the line) and completions are generated + * based on what could follow that input. If the last element in vline is not + * NULL and each sequential element matches the corresponding tokens of one or + * more commands exactly (e.g. 'encapv4' and not 'en') the same result is + * generated. If the last element is not NULL and the best possible match is a + * partial match, then the result generated will be all possible continuations + * of that element (e.g. 'encapv4', 'encapv6', etc for input 'en'). + * @return matcher status + */ +enum matcher_rv command_complete(struct graph *cmdgraph, vector vline, + struct list **completions); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_COMMAND_MATCH_H */ diff --git a/lib/command_parse.y b/lib/command_parse.y new file mode 100644 index 0000000..8867e98 --- /dev/null +++ b/lib/command_parse.y @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Command format string parser for CLI backend. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +%{ +// compile with debugging facilities +#define YYDEBUG 1 +%} + +%locations +/* define parse.error verbose */ +%define api.pure full +/* define api.prefix {cmd_yy} */ + +/* names for generated header and parser files */ +%defines "lib/command_parse.h" +%output "lib/command_parse.c" + +/* note: code blocks are output in order, to both .c and .h: + * 1. %code requires + * 2. %union + bison forward decls + * 3. %code provides + * command_lex.h needs to be included at 3.; it needs the union and YYSTYPE. + * struct parser_ctx is needed for the bison forward decls. + */ +%code requires { + #include "config.h" + + #include + #include + #include + #include + + #include "command_graph.h" + #include "log.h" + + DECLARE_MTYPE(LEX); + + #define YYSTYPE CMD_YYSTYPE + #define YYLTYPE CMD_YYLTYPE + struct parser_ctx; + + /* subgraph semantic value */ + struct subgraph { + struct graph_node *start, *end; + }; +} + +%union { + long long number; + char *string; + struct graph_node *node; + struct subgraph subgraph; +} + +%code provides { + #ifndef FLEX_SCANNER + #include "lib/command_lex.h" + #endif + + extern void set_lexer_string (yyscan_t *scn, const char *string); + extern void cleanup_lexer (yyscan_t *scn); + + struct parser_ctx { + yyscan_t scanner; + + const struct cmd_element *el; + + struct graph *graph; + struct graph_node *currnode; + + /* pointers to copy of command docstring */ + char *docstr_start, *docstr; + }; +} + +/* union types for lexed tokens */ +%token WORD +%token IPV4 +%token IPV4_PREFIX +%token IPV6 +%token IPV6_PREFIX +%token VARIABLE +%token RANGE +%token MAC +%token MAC_PREFIX +%token ASNUM + +/* special syntax, value is irrelevant */ +%token EXCL_BRACKET + +/* union types for parsed rules */ +%type start +%type literal_token +%type placeholder_token +%type placeholder_token_real +%type simple_token +%type selector +%type selector_token +%type selector_token_seq +%type selector_seq_seq + +%type varname_token + +%code { + + /* bison declarations */ + void + cmd_yyerror (CMD_YYLTYPE *locp, struct parser_ctx *ctx, char const *msg); + + /* helper functions for parser */ + static const char * + doc_next (struct parser_ctx *ctx); + + static struct graph_node * + new_token_node (struct parser_ctx *, + enum cmd_token_type type, + const char *text, + const char *doc); + + static void + terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, + struct graph_node *); + + static void + cleanup (struct parser_ctx *ctx); + + static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg); + + #define scanner ctx->scanner +} + +/* yyparse parameters */ +%lex-param {yyscan_t scanner} +%parse-param {struct parser_ctx *ctx} + +/* called automatically before yyparse */ +%initial-action { + /* clear state pointers */ + ctx->currnode = vector_slot (ctx->graph->nodes, 0); + + /* copy docstring and keep a pointer to the copy */ + if (ctx->el->doc) + { + // allocate a new buffer, making room for a flag + size_t length = (size_t) strlen (ctx->el->doc) + 2; + ctx->docstr = malloc (length); + memcpy (ctx->docstr, ctx->el->doc, strlen (ctx->el->doc)); + // set the flag so doc_next knows when to print a warning + ctx->docstr[length - 2] = 0x03; + // null terminate + ctx->docstr[length - 1] = 0x00; + } + ctx->docstr_start = ctx->docstr; +} + +%% + +start: + cmd_token_seq +{ + // tack on the command element + terminate_graph (&@1, ctx, ctx->currnode); +} +| cmd_token_seq placeholder_token '.' '.' '.' +{ + if ((ctx->currnode = graph_add_edge (ctx->currnode, $2)) != $2) + graph_delete_node (ctx->graph, $2); + + ((struct cmd_token *)ctx->currnode->data)->allowrepeat = 1; + + // adding a node as a child of itself accepts any number + // of the same token, which is what we want for variadics + graph_add_edge (ctx->currnode, ctx->currnode); + + // tack on the command element + terminate_graph (&@1, ctx, ctx->currnode); +} +; + +varname_token: '$' WORD +{ + $$ = $2; +} +| /* empty */ +{ + $$ = NULL; +} +; + +cmd_token_seq: + /* empty */ +| cmd_token_seq cmd_token +; + +cmd_token: + simple_token +{ + if ((ctx->currnode = graph_add_edge (ctx->currnode, $1)) != $1) + graph_delete_node (ctx->graph, $1); + cmd_token_varname_seqappend($1); +} +| selector +{ + graph_add_edge (ctx->currnode, $1.start); + cmd_token_varname_seqappend($1.start); + ctx->currnode = $1.end; +} +; + +simple_token: + literal_token +| placeholder_token +; + +literal_token: WORD varname_token +{ + $$ = new_token_node (ctx, WORD_TKN, $1, doc_next(ctx)); + cmd_token_varname_set ($$->data, $2); + XFREE (MTYPE_LEX, $2); + XFREE (MTYPE_LEX, $1); +} +; + +placeholder_token_real: + IPV4 +{ + $$ = new_token_node (ctx, IPV4_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| IPV4_PREFIX +{ + $$ = new_token_node (ctx, IPV4_PREFIX_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| IPV6 +{ + $$ = new_token_node (ctx, IPV6_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| IPV6_PREFIX +{ + $$ = new_token_node (ctx, IPV6_PREFIX_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| VARIABLE +{ + $$ = new_token_node (ctx, VARIABLE_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| RANGE +{ + $$ = new_token_node (ctx, RANGE_TKN, $1, doc_next(ctx)); + struct cmd_token *token = $$->data; + + // get the numbers out + yylval.string++; + token->min = strtoll (yylval.string, &yylval.string, 10); + strsep (&yylval.string, "-"); + token->max = strtoll (yylval.string, &yylval.string, 10); + + // validate range + if (token->min > token->max) cmd_yyerror (&@1, ctx, "Invalid range."); + + XFREE (MTYPE_LEX, $1); +} +| MAC +{ + $$ = new_token_node (ctx, MAC_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| MAC_PREFIX +{ + $$ = new_token_node (ctx, MAC_PREFIX_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} +| ASNUM +{ + $$ = new_token_node (ctx, ASNUM_TKN, $1, doc_next(ctx)); + XFREE (MTYPE_LEX, $1); +} + +placeholder_token: + placeholder_token_real varname_token +{ + $$ = $1; + cmd_token_varname_set ($$->data, $2); + XFREE (MTYPE_LEX, $2); +}; + + +/* productions */ +selector: '<' selector_seq_seq '>' varname_token +{ + $$ = $2; + cmd_token_varname_join ($2.end, $4); + XFREE (MTYPE_LEX, $4); +}; + +selector_seq_seq: + selector_seq_seq '|' selector_token_seq +{ + $$ = $1; + graph_add_edge ($$.start, $3.start); + graph_add_edge ($3.end, $$.end); +} +| selector_token_seq +{ + $$.start = new_token_node (ctx, FORK_TKN, NULL, NULL); + $$.end = new_token_node (ctx, JOIN_TKN, NULL, NULL); + ((struct cmd_token *)$$.start->data)->forkjoin = $$.end; + ((struct cmd_token *)$$.end->data)->forkjoin = $$.start; + + graph_add_edge ($$.start, $1.start); + graph_add_edge ($1.end, $$.end); +} +; + +/* {keyword} productions */ +selector: '{' selector_seq_seq '}' varname_token +{ + $$ = $2; + graph_add_edge ($$.end, $$.start); + /* there is intentionally no start->end link, for two reasons: + * 1) this allows "at least 1 of" semantics, which are otherwise impossible + * 2) this would add a start->end->start loop in the graph that the current + * loop-avoidal fails to handle + * just use [{a|b}] if necessary, that will work perfectly fine, and reason + * #1 is good enough to keep it this way. */ + + loopcheck(ctx, &$$); + cmd_token_varname_join ($2.end, $4); + XFREE (MTYPE_LEX, $4); +}; + + +selector_token: + simple_token +{ + $$.start = $$.end = $1; +} +| selector +; + +selector_token_seq: + selector_token_seq selector_token +{ + graph_add_edge ($1.end, $2.start); + cmd_token_varname_seqappend($2.start); + $$.start = $1.start; + $$.end = $2.end; +} +| selector_token +; + +/* [option] productions */ +selector: '[' selector_seq_seq ']' varname_token +{ + $$ = $2; + graph_add_edge ($$.start, $$.end); + cmd_token_varname_join ($2.end, $4); + XFREE (MTYPE_LEX, $4); +} +; + +/* ![option] productions */ +selector: EXCL_BRACKET selector_seq_seq ']' varname_token +{ + struct graph_node *neg_only = new_token_node (ctx, NEG_ONLY_TKN, NULL, NULL); + + $$ = $2; + graph_add_edge ($$.start, neg_only); + graph_add_edge (neg_only, $$.end); + cmd_token_varname_join ($2.end, $4); + XFREE (MTYPE_LEX, $4); +} +; + +%% + +#undef scanner + +DEFINE_MTYPE(LIB, LEX, "Lexer token (temporary)"); + +void +cmd_graph_parse (struct graph *graph, const struct cmd_element *cmd) +{ + struct parser_ctx ctx = { .graph = graph, .el = cmd }; + + // set to 1 to enable parser traces + yydebug = 0; + + set_lexer_string (&ctx.scanner, cmd->string); + + // parse command into DFA + cmd_yyparse (&ctx); + + /* cleanup lexer */ + cleanup_lexer (&ctx.scanner); + + // cleanup + cleanup (&ctx); +} + +/* parser helper functions */ + +static bool loopcheck_inner(struct graph_node *start, struct graph_node *node, + struct graph_node *end, size_t depth) +{ + size_t i; + bool ret; + + /* safety check */ + if (depth++ == 64) + return true; + + for (i = 0; i < vector_active(node->to); i++) { + struct graph_node *next = vector_slot(node->to, i); + struct cmd_token *tok = next->data; + + if (next == end || next == start) + return true; + if (tok->type < SPECIAL_TKN) + continue; + ret = loopcheck_inner(start, next, end, depth); + if (ret) + return true; + } + return false; +} + +static void loopcheck(struct parser_ctx *ctx, struct subgraph *sg) +{ + if (loopcheck_inner(sg->start, sg->start, sg->end, 0)) + zlog_err("FATAL: '%s': {} contains an empty path! Use [{...}]", + ctx->el->string); +} + +void +yyerror (CMD_YYLTYPE *loc, struct parser_ctx *ctx, char const *msg) +{ + char *tmpstr = strdup(ctx->el->string); + char *line, *eol; + char spacing[256]; + int lineno = 0; + + zlog_notice ("%s: FATAL parse error: %s", __func__, msg); + zlog_notice ("%s: %d:%d-%d of this command definition:", __func__, loc->first_line, loc->first_column, loc->last_column); + + line = tmpstr; + do { + lineno++; + eol = strchr(line, '\n'); + if (eol) + *eol++ = '\0'; + + zlog_notice ("%s: | %s", __func__, line); + if (lineno == loc->first_line && lineno == loc->last_line + && loc->first_column < (int)sizeof(spacing) - 1 + && loc->last_column < (int)sizeof(spacing) - 1) { + + int len = loc->last_column - loc->first_column; + if (len == 0) + len = 1; + + memset(spacing, ' ', loc->first_column - 1); + memset(spacing + loc->first_column - 1, '^', len); + spacing[loc->first_column - 1 + len] = '\0'; + zlog_notice ("%s: | %s", __func__, spacing); + } + } while ((line = eol)); + free(tmpstr); +} + +static void +cleanup (struct parser_ctx *ctx) +{ + /* free resources */ + free (ctx->docstr_start); + + /* clear state pointers */ + ctx->currnode = NULL; + ctx->docstr_start = ctx->docstr = NULL; +} + +static void +terminate_graph (CMD_YYLTYPE *locp, struct parser_ctx *ctx, + struct graph_node *finalnode) +{ + // end of graph should look like this + // * -> finalnode -> END_TKN -> cmd_element + const struct cmd_element *element = ctx->el; + struct graph_node *end_token_node = + new_token_node (ctx, END_TKN, CMD_CR_TEXT, ""); + struct graph_node *end_element_node = + graph_new_node (ctx->graph, (void *)element, NULL); + + if (ctx->docstr && strlen (ctx->docstr) > 1) { + zlog_err ("Excessive docstring while parsing '%s'", ctx->el->string); + zlog_err ("----------"); + while (ctx->docstr && ctx->docstr[1] != '\0') + zlog_err ("%s", strsep(&ctx->docstr, "\n")); + zlog_err ("----------"); + } + + graph_add_edge (finalnode, end_token_node); + graph_add_edge (end_token_node, end_element_node); +} + +static const char * +doc_next (struct parser_ctx *ctx) +{ + const char *piece = ctx->docstr ? strsep (&ctx->docstr, "\n") : ""; + if (*piece == 0x03) + { + zlog_err ("Ran out of docstring while parsing '%s'", ctx->el->string); + piece = ""; + } + + return piece; +} + +static struct graph_node * +new_token_node (struct parser_ctx *ctx, enum cmd_token_type type, + const char *text, const char *doc) +{ + struct cmd_token *token = cmd_token_new (type, ctx->el->attr, text, doc); + return graph_new_node (ctx->graph, token, (void (*)(void *)) &cmd_token_del); +} diff --git a/lib/command_py.c b/lib/command_py.c new file mode 100644 index 0000000..f8abcf8 --- /dev/null +++ b/lib/command_py.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * clippy (CLI preparator in python) wrapper for FRR command_graph + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +/* note: this wrapper is intended to be used as build-time helper. while + * it should be generally correct and proper, there may be the occasional + * memory leak or SEGV for things that haven't been well-tested. + */ + +/* This file is "exempt" from having +#include "config.h" + * as the first include statement because Python.h also does environment + * setup & these trample over each other. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include "structmember.h" +#include +#include + +#include "command_graph.h" +#include "clippy.h" + +struct wrap_graph; +static PyObject *graph_to_pyobj(struct wrap_graph *graph, + struct graph_node *gn); + +/* + * nodes are wrapped as follows: + * - instances can only be acquired from a graph + * - the same node will return the same wrapper object (they're buffered + * through "idx") + * - a reference is held onto the graph + * - fields are copied for easy access with PyMemberDef + */ +struct wrap_graph_node { + PyObject_HEAD + + bool allowrepeat; + const char *type; + + bool deprecated; + bool hidden; + const char *text; + const char *desc; + const char *varname; + long long min, max; + + struct graph_node *node; + struct wrap_graph *wgraph; + size_t idx; +}; + +/* + * graphs are wrapped as follows: + * - they can only be created by parsing a definition string + * - there's a table here for the wrapped nodes (nodewrappers), indexed + * by "idx" (corresponds to node's position in graph's table of nodes) + * - graphs do NOT hold references to nodes (would be circular) + */ +struct wrap_graph { + PyObject_HEAD + + char *definition; + struct graph *graph; + struct wrap_graph_node **nodewrappers; +}; + +static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_ValueError, + "cannot create instances of this type"); + return NULL; +} + +#define member(name, type) \ + { \ + (char *)#name, type, offsetof(struct wrap_graph_node, name), \ + READONLY, (char *)#name " (" #type ")" \ + } +static PyMemberDef members_graph_node[] = { + member(allowrepeat, T_BOOL), member(type, T_STRING), + member(deprecated, T_BOOL), member(hidden, T_BOOL), + member(text, T_STRING), member(desc, T_STRING), + member(min, T_LONGLONG), member(max, T_LONGLONG), + member(varname, T_STRING), {}, +}; +#undef member + +/* + * node.next() -- returns list of all "next" nodes. + * this will include circles if the graph has them. + */ +static PyObject *graph_node_next(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + PyObject *pylist; + + if (wrap->node->data + && ((struct cmd_token *)wrap->node->data)->type == END_TKN) + return PyList_New(0); + pylist = PyList_New(vector_active(wrap->node->to)); + for (size_t i = 0; i < vector_active(wrap->node->to); i++) { + struct graph_node *gn = vector_slot(wrap->node->to, i); + PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn)); + } + return pylist; +}; + +/* + * node.join() -- return FORK's JOIN node or None + */ +static PyObject *graph_node_join(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + + if (!wrap->node->data + || ((struct cmd_token *)wrap->node->data)->type == END_TKN) + Py_RETURN_NONE; + + struct cmd_token *tok = wrap->node->data; + if (tok->type != FORK_TKN) + Py_RETURN_NONE; + + return graph_to_pyobj(wrap->wgraph, tok->forkjoin); +}; + +static PyMethodDef methods_graph_node[] = { + {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"}, + {"join", graph_node_join, METH_NOARGS, "outbound join node"}, + {}}; + +static void graph_node_wrap_free(void *arg) +{ + struct wrap_graph_node *wrap = arg; + wrap->wgraph->nodewrappers[wrap->idx] = NULL; + Py_DECREF(wrap->wgraph); +} + +static PyTypeObject typeobj_graph_node = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode", + .tp_basicsize = sizeof(struct wrap_graph_node), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "struct graph_node *", + .tp_new = refuse_new, + .tp_free = graph_node_wrap_free, + .tp_members = members_graph_node, + .tp_methods = methods_graph_node, +}; + +static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, + struct graph_node *gn) +{ + struct wrap_graph_node *wrap; + size_t i; + + for (i = 0; i < vector_active(wgraph->graph->nodes); i++) + if (vector_slot(wgraph->graph->nodes, i) == gn) + break; + if (i == vector_active(wgraph->graph->nodes)) { + PyErr_SetString(PyExc_ValueError, "cannot find node in graph"); + return NULL; + } + if (wgraph->nodewrappers[i]) { + PyObject *obj = (PyObject *)wgraph->nodewrappers[i]; + Py_INCREF(obj); + return obj; + } + + wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc( + &typeobj_graph_node, 0); + if (!wrap) + return NULL; + wgraph->nodewrappers[i] = wrap; + Py_INCREF(wgraph); + + wrap->idx = i; + wrap->wgraph = wgraph; + wrap->node = gn; + wrap->type = "NULL"; + wrap->allowrepeat = false; + if (gn->data) { + struct cmd_token *tok = gn->data; + switch (tok->type) { +#define item(x) \ + case x: \ + wrap->type = #x; \ + break /* no semicolon */ + + item(WORD_TKN); // words + item(VARIABLE_TKN); // almost anything + item(RANGE_TKN); // integer range + item(IPV4_TKN); // IPV4 addresses + item(IPV4_PREFIX_TKN); // IPV4 network prefixes + item(IPV6_TKN); // IPV6 prefixes + item(IPV6_PREFIX_TKN); // IPV6 network prefixes + item(MAC_TKN); // MAC address + item(MAC_PREFIX_TKN); // MAC address with mask + item(ASNUM_TKN); // ASNUM + + /* plumbing types */ + item(FORK_TKN); + item(JOIN_TKN); + item(START_TKN); + item(END_TKN); + item(NEG_ONLY_TKN); +#undef item + default: + wrap->type = "???"; + } + + wrap->deprecated = !!(tok->attr & CMD_ATTR_DEPRECATED); + wrap->hidden = !!(tok->attr & CMD_ATTR_HIDDEN); + wrap->text = tok->text; + wrap->desc = tok->desc; + wrap->varname = tok->varname; + wrap->min = tok->min; + wrap->max = tok->max; + wrap->allowrepeat = tok->allowrepeat; + } + + return (PyObject *)wrap; +} + +#define member(name, type) \ + { \ + (char *)#name, type, offsetof(struct wrap_graph, name), \ + READONLY, (char *)#name " (" #type ")" \ + } +static PyMemberDef members_graph[] = { + member(definition, T_STRING), + {}, +}; +#undef member + +/* graph.first() - root node */ +static PyObject *graph_first(PyObject *self, PyObject *args) +{ + struct wrap_graph *gwrap = (struct wrap_graph *)self; + struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0); + return graph_to_pyobj(gwrap, gn); +}; + +static PyMethodDef methods_graph[] = { + {"first", graph_first, METH_NOARGS, "first graph node"}, + {}}; + +static PyObject *graph_parse(PyTypeObject *type, PyObject *args, + PyObject *kwds); + +static void graph_wrap_free(void *arg) +{ + struct wrap_graph *wgraph = arg; + + graph_delete_graph(wgraph->graph); + free(wgraph->nodewrappers); + free(wgraph->definition); +} + +static PyTypeObject typeobj_graph = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph", + .tp_basicsize = sizeof(struct wrap_graph), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "struct graph *", + .tp_new = graph_parse, + .tp_free = graph_wrap_free, + .tp_members = members_graph, + .tp_methods = methods_graph, +}; + +/* top call / entrypoint for python code */ +static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + const char *def, *doc = NULL; + struct wrap_graph *gwrap; + static const char *kwnames[] = {"cmddef", "doc", NULL}; + + gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0); + if (!gwrap) + return NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames, + &def, &doc)) + return NULL; + + struct graph *graph = graph_new(); + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); + + struct cmd_element cmd = {.string = def, .doc = doc}; + cmd_graph_parse(graph, &cmd); + cmd_graph_names(graph); + + gwrap->graph = graph; + gwrap->definition = strdup(def); + gwrap->nodewrappers = calloc(vector_active(graph->nodes), + sizeof(gwrap->nodewrappers[0])); + return (PyObject *)gwrap; +} + +static PyMethodDef clippy_methods[] = { + {"parse", clippy_parse, METH_VARARGS, "Parse a C file"}, + {NULL, NULL, 0, NULL}}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef pymoddef_clippy = { + PyModuleDef_HEAD_INIT, + "_clippy", + NULL, /* docstring */ + -1, + clippy_methods, +}; +#define modcreate() PyModule_Create(&pymoddef_clippy) +#define initret(val) return val; +#else +#define modcreate() Py_InitModule("_clippy", clippy_methods) +#define initret(val) \ + do { \ + if (!val) \ + Py_FatalError("initialization failure"); \ + return; \ + } while (0) +#endif + +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +PyMODINIT_FUNC command_py_init(void) +{ + PyObject *pymod; + + if (PyType_Ready(&typeobj_graph_node) < 0) + initret(NULL); + if (PyType_Ready(&typeobj_graph) < 0) + initret(NULL); + + pymod = modcreate(); + if (!pymod) + initret(NULL); + + if (PyModule_AddIntMacro(pymod, CMD_ATTR_YANG) + || PyModule_AddIntMacro(pymod, CMD_ATTR_HIDDEN) + || PyModule_AddIntMacro(pymod, CMD_ATTR_DEPRECATED) + || PyModule_AddIntMacro(pymod, CMD_ATTR_NOSH)) + initret(NULL); + + Py_INCREF(&typeobj_graph_node); + PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node); + Py_INCREF(&typeobj_graph); + PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph); + if (!elf_py_init(pymod)) + initret(NULL); + initret(pymod); +} diff --git a/lib/compiler.h b/lib/compiler.h new file mode 100644 index 0000000..9d39026 --- /dev/null +++ b/lib/compiler.h @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-2017 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_COMPILER_H +#define _FRR_COMPILER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +# if __cplusplus < 201103L +# error FRRouting headers must be compiled in C++11 mode or newer +# endif +/* C++ defines static_assert(), but not _Static_assert(). C defines + * _Static_assert() and has static_assert() in . However, we mess + * with assert() in zassert.h so let's not include here. + */ +# define _Static_assert static_assert +#else +# if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L +# error FRRouting must be compiled with min. -std=gnu11 (GNU ISO C11 dialect) +# endif +#endif + +/* function attributes, use like + * void prototype(void) __attribute__((_CONSTRUCTOR(100))); + */ +#if defined(__clang__) +#if __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 5) +# define _RET_NONNULL , returns_nonnull +#endif +#if __has_attribute(fallthrough) && !defined(__cplusplus) +# define fallthrough __attribute__((fallthrough)); +#endif +# define _CONSTRUCTOR(x) constructor(x) +# define _DEPRECATED(x) deprecated(x) +# if __has_builtin(assume) +# define assume(x) __builtin_assume(x) +# endif +#elif defined(__GNUC__) +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9) +# define _RET_NONNULL , returns_nonnull +#endif +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) +# define _CONSTRUCTOR(x) constructor(x) +# define _DESTRUCTOR(x) destructor(x) +# define _ALLOC_SIZE(x) alloc_size(x) +#endif +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) +# define _DEPRECATED(x) deprecated(x) +# define assume(x) do { if (!(x)) __builtin_unreachable(); } while (0) +#endif +#if __GNUC__ < 5 +# define __has_attribute(x) 0 +#endif +#if __GNUC__ >= 7 && !defined(__cplusplus) +# define fallthrough __attribute__((fallthrough)); +#endif +#endif + +#ifdef __INTELLISENSE__ +/* + * Fix Visual Studio Code error: attribute "constructor" does not take + * arguments. + * + * Caused by the macro `DEFINE_MTYPE_ATTR` in `lib/memory.h`. + */ +#pragma diag_suppress 1094 +#endif /* __INTELISENSE__ */ + +#if __has_attribute(hot) +# define _OPTIMIZE_HOT __attribute__((hot)) +#else +# define _OPTIMIZE_HOT +#endif +#if __has_attribute(optimize) +# define _OPTIMIZE_O3 __attribute__((optimize("3"))) +#else +# define _OPTIMIZE_O3 +#endif +#define OPTIMIZE _OPTIMIZE_O3 _OPTIMIZE_HOT + +#if !defined(__GNUC__) +#error module code needs GCC visibility extensions +#elif __GNUC__ < 4 +#error module code needs GCC visibility extensions +#else +# define DSO_PUBLIC __attribute__ ((visibility ("default"))) +# define DSO_SELF __attribute__ ((visibility ("protected"))) +# define DSO_LOCAL __attribute__ ((visibility ("hidden"))) +#endif + +#ifdef __sun +/* Solaris doesn't do constructor priorities due to linker restrictions */ +#undef _CONSTRUCTOR +#undef _DESTRUCTOR +#endif + +/* fallback versions */ +#ifndef _RET_NONNULL +# define _RET_NONNULL +#endif +#ifndef _CONSTRUCTOR +# define _CONSTRUCTOR(x) constructor +#endif +#ifndef _DESTRUCTOR +# define _DESTRUCTOR(x) destructor +#endif +#ifndef _ALLOC_SIZE +# define _ALLOC_SIZE(x) +#endif +#if !defined(fallthrough) && !defined(__cplusplus) +#define fallthrough +#endif +#ifndef _DEPRECATED +#define _DEPRECATED(x) deprecated +#endif +#ifndef assume +#define assume(x) +#endif + +#ifdef __COVERITY__ +/* __coverity_panic__() is named a bit poorly, it's essentially the same as + * __builtin_unreachable(). Used to eliminate false positives. + */ +#undef assume +#define assume(x) do { if (!(x)) __coverity_panic__(); } while (0) +#endif + +/* for helper functions defined inside macros */ +#define macro_inline static inline __attribute__((unused)) +#define macro_pure static inline __attribute__((unused, pure)) + +/* if the macro ends with a function definition */ +#define MACRO_REQUIRE_SEMICOLON() \ + _Static_assert(1, "please add a semicolon after this macro") + +/* variadic macros, use like: + * #define V_0() ... + * #define V_1(x) ... + * #define V(...) MACRO_VARIANT(V, ##__VA_ARGS__)(__VA_ARGS__) + */ +#define _MACRO_VARIANT(A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10, N, ...) N + +#define _CONCAT2(a, b) a ## b +#define _CONCAT(a, b) _CONCAT2(a,b) + +#define MACRO_VARIANT(NAME, ...) \ + _CONCAT(NAME, _MACRO_VARIANT(0, ##__VA_ARGS__, \ + _10, _9, _8, _7, _6, _5, _4, _3, _2, _1, _0)) + +#define NAMECTR(name) _CONCAT(name, __COUNTER__) + +/* per-arg repeat macros, use like: + * #define PERARG(n) ...n... + * #define FOO(...) MACRO_REPEAT(PERARG, ##__VA_ARGS__) + */ + +#define _MACRO_REPEAT_0(NAME) +#define _MACRO_REPEAT_1(NAME, A1) \ + NAME(A1) +#define _MACRO_REPEAT_2(NAME, A1, A2) \ + NAME(A1) NAME(A2) +#define _MACRO_REPEAT_3(NAME, A1, A2, A3) \ + NAME(A1) NAME(A2) NAME(A3) +#define _MACRO_REPEAT_4(NAME, A1, A2, A3, A4) \ + NAME(A1) NAME(A2) NAME(A3) NAME(A4) +#define _MACRO_REPEAT_5(NAME, A1, A2, A3, A4, A5) \ + NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) +#define _MACRO_REPEAT_6(NAME, A1, A2, A3, A4, A5, A6) \ + NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) +#define _MACRO_REPEAT_7(NAME, A1, A2, A3, A4, A5, A6, A7) \ + NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) +#define _MACRO_REPEAT_8(NAME, A1, A2, A3, A4, A5, A6, A7, A8) \ + NAME(A1) NAME(A2) NAME(A3) NAME(A4) NAME(A5) NAME(A6) NAME(A7) NAME(A8) + +#define MACRO_REPEAT(NAME, ...) \ + MACRO_VARIANT(_MACRO_REPEAT, ##__VA_ARGS__)(NAME, ##__VA_ARGS__) + +/* per-arglist repeat macro, use like this: + * #define foo(...) MAP_LISTS(F, ##__VA_ARGS__) + * where F is a n-ary function where n is the number of args in each arglist. + * e.g.: MAP_LISTS(f, (a, b), (c, d)) + * expands to: f(a, b); f(c, d) + */ + +#define ESC(...) __VA_ARGS__ +#define MAP_LISTS(M, ...) \ + _CONCAT(_MAP_LISTS_, PP_NARG(__VA_ARGS__))(M, ##__VA_ARGS__) +#define _MAP_LISTS_0(M) +#define _MAP_LISTS_1(M, _1) ESC(M _1) +#define _MAP_LISTS_2(M, _1, _2) ESC(M _1; M _2) +#define _MAP_LISTS_3(M, _1, _2, _3) ESC(M _1; M _2; M _3) +#define _MAP_LISTS_4(M, _1, _2, _3, _4) ESC(M _1; M _2; M _3; M _4) +#define _MAP_LISTS_5(M, _1, _2, _3, _4, _5) ESC(M _1; M _2; M _3; M _4; M _5) +#define _MAP_LISTS_6(M, _1, _2, _3, _4, _5, _6) \ + ESC(M _1; M _2; M _3; M _4; M _5; M _6) +#define _MAP_LISTS_7(M, _1, _2, _3, _4, _5, _6, _7) \ + ESC(M _1; M _2; M _3; M _4; M _5; M _6; M _7) +#define _MAP_LISTS_8(M, _1, _2, _3, _4, _5, _6, _7, _8) \ + ESC(M _1; M _2; M _3; M _4; M _5; M _6; M _7; M _8) + +/* + * for warnings on macros, put in the macro content like this: + * #define MACRO BLA CPP_WARN("MACRO has been deprecated") + */ +#define CPP_STR(X) #X + +#if defined(__ICC) +#define CPP_NOTICE(text) _Pragma(CPP_STR(message __FILE__ ": " text)) +#define CPP_WARN(text) CPP_NOTICE(text) + +#elif (defined(__GNUC__) \ + && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) \ + || (defined(__clang__) \ + && (__clang_major__ >= 4 \ + || (__clang_major__ == 3 && __clang_minor__ >= 5))) +#define CPP_WARN(text) _Pragma(CPP_STR(GCC warning text)) +#define CPP_NOTICE(text) _Pragma(CPP_STR(message text)) + +#else +#define CPP_WARN(text) +#define CPP_NOTICE(text) +#endif + +/* MAX / MIN are not commonly defined, but useful */ +/* note: glibc sys/param.h has #define MIN(a,b) (((a)<(b))?(a):(b)) */ +#ifdef MAX +#undef MAX +#endif +#define MAX(a, b) \ + ({ \ + typeof(a) _max_a = (a); \ + typeof(b) _max_b = (b); \ + _max_a > _max_b ? _max_a : _max_b; \ + }) +#ifdef MIN +#undef MIN +#endif +#define MIN(a, b) \ + ({ \ + typeof(a) _min_a = (a); \ + typeof(b) _min_b = (b); \ + _min_a < _min_b ? _min_a : _min_b; \ + }) + +#define numcmp(a, b) \ + ({ \ + typeof(a) _cmp_a = (a); \ + typeof(b) _cmp_b = (b); \ + (_cmp_a < _cmp_b) ? -1 : ((_cmp_a > _cmp_b) ? 1 : 0); \ + }) + +#ifndef offsetof +#ifdef __compiler_offsetof +#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE,MEMBER) +#else +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif +#endif + +#ifdef container_of +#undef container_of +#endif + +#if !(defined(__cplusplus) || defined(test__cplusplus)) +/* this variant of container_of() retains 'const' on pointers without needing + * to be told to do so. The following will all work without warning: + * + * struct member *p; + * const struct member *cp; + * + * const struct cont *x = container_of(cp, struct cont, member); + * const struct cont *x = container_of(cp, const struct cont, member); + * const struct cont *x = container_of(p, struct cont, member); + * const struct cont *x = container_of(p, const struct cont, member); + * struct cont *x = container_of(p, struct cont, member); + * + * but the following will generate warnings about stripping const: + * + * struct cont *x = container_of(cp, struct cont, member); + * struct cont *x = container_of(cp, const struct cont, member); + * struct cont *x = container_of(p, const struct cont, member); + */ +#define container_of(ptr, type, member) \ + (__builtin_choose_expr( \ + __builtin_types_compatible_p(typeof(&((type *)0)->member), \ + typeof(ptr)) \ + || __builtin_types_compatible_p(void *, typeof(ptr)), \ + ({ \ + typeof(((type *)0)->member) *__mptr = (void *)(ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); \ + }), \ + ({ \ + typeof(((const type *)0)->member) *__mptr = (ptr); \ + (const type *)((const char *)__mptr - \ + offsetof(type, member)); \ + }) \ + )) +#else +/* current C++ compilers don't have the builtins used above; so this version + * of the macro doesn't do the const check. */ +#define container_of(ptr, type, member) \ + ({ \ + const typeof(((type *)0)->member) *__mptr = (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); \ + }) +#endif + +#define container_of_null(ptr, type, member) \ + ({ \ + typeof(ptr) _tmp = (ptr); \ + _tmp ? container_of(_tmp, type, member) : NULL; \ + }) + +#define array_size(ar) (sizeof(ar) / sizeof(ar[0])) + +/* Some insane macros to count number of varargs to a functionlike macro */ +#define PP_ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \ + _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \ + _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \ + _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \ + _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \ + _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \ + _61, _62, _63, N, ...) N + +#define PP_RSEQ_N() \ + 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define PP_NARG_(...) PP_ARG_N(__VA_ARGS__) +#define PP_NARG(...) PP_NARG_(_, ##__VA_ARGS__, PP_RSEQ_N()) + + +/* sigh. this is so ugly, it overflows and wraps to being nice again. + * + * printfrr() supports "%Ld" for , whatever that is typedef'd to. + * However, gcc & clang think that "%Ld" is , which doesn't quite + * match up since int64_t is on a lot of 64-bit systems. + * + * If we have _FRR_ATTRIBUTE_PRINTFRR, we loaded a compiler plugin that + * replaces the whole format checking bits with a custom version that + * understands "%Ld" (along with "%pI4" and co.), so we don't need to do + * anything. + * + * If we don't have that attribute... we still want -Wformat to work. So, + * this is the "f*ck it" approach and we just redefine int64_t to always be + * . This should work until such a time that is + * something else (e.g. 128-bit integer)... let's just guard against that + * with the _Static_assert below and work with the world we have right now, + * where is always 64-bit. + */ + +/* these need to be included before any of the following, so we can + * "overwrite" things. + */ +#include +#include + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#define PRINTFRR(a, b) __attribute__((frr_format("frr_printf", a, b))) + +#undef PRIu64 +#undef PRId64 +#undef PRIx64 +#define PRIu64 "Lu" +#define PRId64 "Ld" +#define PRIx64 "Lx" + +#else /* !_FRR_ATTRIBUTE_PRINTFRR */ +#ifdef __NetBSD__ +#define PRINTFRR(a, b) __attribute__((format(gnu_syslog, a, b))) +#else +#define PRINTFRR(a, b) __attribute__((format(printf, a, b))) +#endif + +/* frr-format plugin is C-only for now, so no point in doing these shenanigans + * for C++... (also they can break some C++ stuff...) + */ +#ifndef __cplusplus +/* these should be typedefs, but might also be #define */ +#ifdef uint64_t +#undef uint64_t +#endif +#ifdef int64_t +#undef int64_t +#endif + +/* can't overwrite the typedef, but we can replace int64_t with _int64_t */ +typedef unsigned long long _uint64_t; +#define uint64_t _uint64_t +typedef signed long long _int64_t; +#define int64_t _int64_t + +/* if this breaks, 128-bit machines may have entered reality (or + * is something weird) + */ +_Static_assert(sizeof(_uint64_t) == 8 && sizeof(_int64_t) == 8, + "nobody expects the spanish intquisition"); + +/* since we redefined int64_t, we also need to redefine PRI*64 */ +#undef PRIu64 +#undef PRId64 +#undef PRIx64 +#define PRIu64 "llu" +#define PRId64 "lld" +#define PRIx64 "llx" + +#endif /* !__cplusplus */ +#endif /* !_FRR_ATTRIBUTE_PRINTFRR */ + +/* helper to get type safety/avoid casts on calls + * (w/o this, functions accepting all prefix types need casts on the caller + * side, which strips type safety since the cast will accept any pointer + * type.) + */ +#ifndef __cplusplus +#define uniontype(uname, typename, fieldname) typename *fieldname; +#define TRANSPARENT_UNION __attribute__((transparent_union)) +#else +#define uniontype(uname, typename, fieldname) \ + typename *fieldname; \ + uname(typename *x) \ + { \ + this->fieldname = x; \ + } +#define TRANSPARENT_UNION +#endif + +#ifdef __INTELLISENSE__ +/* + * Fix Visual Studio Code error: argument of type "struct prefix *" is + * incompatible with parameter of type "union prefixptr". + * + * This is caused by all functions having the transparent unions in the + * prototype. Example: `prefixptr` and `prefixconstptr` from `lib/prefix.h`. + */ +#pragma diag_suppress 167 +#endif /* __INTELISENSE__ */ + +#if defined(__GNUC__) && (__GNUC__ >= 3) +#define likely(_x) __builtin_expect(!!(_x), 1) +#define unlikely(_x) __builtin_expect(!!(_x), 0) +#else +#define likely(_x) !!(_x) +#define unlikely(_x) !!(_x) +#endif + +#ifdef __MACH__ +#define _DATA_SECTION(name) __attribute__((section("__DATA," name))) +#else +#define _DATA_SECTION(name) __attribute__((section(".data." name))) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_COMPILER_H */ diff --git a/lib/config_paths.h.in b/lib/config_paths.h.in new file mode 100644 index 0000000..cc40905 --- /dev/null +++ b/lib/config_paths.h.in @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* autogenerated by configure / config.status */ + +/* IF YOU ARE INCLUDING THIS FILE FROM A DAEMON OR ZEBRA, YOU ARE PROBABLY + * DOING SOMETHING WRONG. Check for / add a library function that retrieves + * the path you need. + * + * Only libfrr and watchfrr should be including this file. + */ + +/* the replacements for these are emitted by AX_SUBST_EXPANDED, which also + * adds the e_ prefix + */ +#define FRR_RUNSTATE_PATH "@e_frr_runstatedir@" +#define FRR_LIBSTATE_PATH "@e_frr_libstatedir@" +#define YANG_MODELS_PATH "@e_yangmodelsdir@" +#define MODULE_PATH "@e_moduledir@" +#define SCRIPT_PATH "@e_scriptdir@" + +/* for extra footgunning, this one has a trailing slash */ +#define SYSCONFDIR "@e_frr_sysconfdir@/" + +#define VTYSH_BIN_PATH "@e_vtysh_bin@" +#define WATCHFRR_SH_PATH "@e_watchfrr_sh@" diff --git a/lib/cspf.c b/lib/cspf.c new file mode 100644 index 0000000..c17d8e0 --- /dev/null +++ b/lib/cspf.c @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Constraints Shortest Path First algorithms - cspf.c + * + * Author: Olivier Dugeon + * + * Copyright (C) 2022 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + */ + +#include + +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "hash.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "stream.h" +#include "printfrr.h" +#include "link_state.h" +#include "cspf.h" + +/* Link State Memory allocation */ +DEFINE_MTYPE_STATIC(LIB, PCA, "Path Computation Algorithms"); + +/** + * Create new Constrained Path. Memory is dynamically allocated. + * + * @param key Vertex key of the destination of this path + * + * @return Pointer to a new Constrained Path structure + */ +static struct c_path *cpath_new(uint64_t key) +{ + struct c_path *path; + + /* Sanity Check */ + if (key == 0) + return NULL; + + path = XCALLOC(MTYPE_PCA, sizeof(struct c_path)); + path->dst = key; + path->status = IN_PROGRESS; + path->edges = list_new(); + path->weight = MAX_COST; + + return path; +} + +/** + * Copy src Constrained Path into dst Constrained Path. A new Constrained Path + * structure is dynamically allocated if dst is NULL. If src is NULL, the + * function return the dst disregarding if it is NULL or not. + * + * @param dest Destination Constrained Path structure + * @param src Source Constrained Path structure + * + * @return Pointer to the destination Constrained Path structure + */ +static struct c_path *cpath_copy(struct c_path *dest, const struct c_path *src) +{ + struct c_path *new_path; + + if (!src) + return dest; + + if (!dest) { + new_path = XCALLOC(MTYPE_PCA, sizeof(struct c_path)); + } else { + new_path = dest; + if (dest->edges) + list_delete(&new_path->edges); + } + + new_path->dst = src->dst; + new_path->weight = src->weight; + new_path->edges = list_dup(src->edges); + new_path->status = src->status; + + return new_path; +} + +/** + * Delete Constrained Path structure. Previous allocated memory is freed. + * + * @param path Constrained Path structure to be deleted + */ +void cpath_del(struct c_path *path) +{ + if (!path) + return; + + if (path->edges) + list_delete(&path->edges); + + XFREE(MTYPE_PCA, path); + path = NULL; +} + +/** + * Replace the list of edges in the next Constrained Path by the list of edges + * in the current Constrained Path. + * + * @param next_path next Constrained Path structure + * @param cur_path current Constrained Path structure + */ +static void cpath_replace(struct c_path *next_path, struct c_path *cur_path) +{ + + if (next_path->edges) + list_delete(&next_path->edges); + + next_path->edges = list_dup(cur_path->edges); +} + +/** + * Create a new Visited Node structure from the provided Vertex. Structure is + * dynamically allocated. + * + * @param vertex Vertex structure + * + * @return Pointer to the new Visited Node structure + */ +static struct v_node *vnode_new(struct ls_vertex *vertex) +{ + struct v_node *vnode; + + if (!vertex) + return NULL; + + vnode = XCALLOC(MTYPE_PCA, sizeof(struct v_node)); + vnode->vertex = vertex; + vnode->key = vertex->key; + + return vnode; +} + +/** + * Delete Visited Node structure. Previous allocated memory is freed. + * + * @param vnode Visited Node structure to be deleted + */ +static void vnode_del(struct v_node *vnode) +{ + if (!vnode) + return; + + XFREE(MTYPE_PCA, vnode); + vnode = NULL; +} + +/** + * Search Vertex in TED by IPv4 address. The function search vertex by browsing + * the subnets table. It allows to find not only vertex by router ID, but also + * vertex by interface IPv4 address. + * + * @param ted Traffic Engineering Database + * @param ipv4 IPv4 address + * + * @return Vertex if found, NULL otherwise + */ +static struct ls_vertex *get_vertex_by_ipv4(struct ls_ted *ted, + struct in_addr ipv4) +{ + struct ls_subnet *subnet; + struct prefix p; + + p.family = AF_INET; + p.u.prefix4 = ipv4; + + frr_each (subnets, &ted->subnets, subnet) { + if (subnet->key.family != AF_INET) + continue; + p.prefixlen = subnet->key.prefixlen; + if (prefix_same(&subnet->key, &p)) + return subnet->vertex; + } + + return NULL; +} + +/** + * Search Vertex in TED by IPv6 address. The function search vertex by browsing + * the subnets table. It allows to find not only vertex by router ID, but also + * vertex by interface IPv6 address. + * + * @param ted Traffic Engineering Database + * @param ipv6 IPv6 address + * + * @return Vertex if found, NULL otherwise + */ +static struct ls_vertex *get_vertex_by_ipv6(struct ls_ted *ted, + struct in6_addr ipv6) +{ + struct ls_subnet *subnet; + struct prefix p; + + p.family = AF_INET6; + p.u.prefix6 = ipv6; + + frr_each (subnets, &ted->subnets, subnet) { + if (subnet->key.family != AF_INET6) + continue; + p.prefixlen = subnet->key.prefixlen; + if (prefix_cmp(&subnet->key, &p) == 0) + return subnet->vertex; + } + + return NULL; +} + +struct cspf *cspf_new(void) +{ + struct cspf *algo; + + /* Allocate New CSPF structure */ + algo = XCALLOC(MTYPE_PCA, sizeof(struct cspf)); + + /* Initialize RB-Trees */ + processed_init(&algo->processed); + visited_init(&algo->visited); + pqueue_init(&algo->pqueue); + + algo->path = NULL; + algo->pdst = NULL; + + return algo; +} + +struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src, + const struct ls_vertex *dst, struct constraints *csts) +{ + struct cspf *new_algo; + struct c_path *psrc; + + if (!csts) + return NULL; + + if (!algo) + new_algo = cspf_new(); + else + new_algo = algo; + + /* Initialize Processed Path and Priority Queue with Src & Dst */ + if (src) { + psrc = cpath_new(src->key); + psrc->weight = 0; + processed_add(&new_algo->processed, psrc); + pqueue_add(&new_algo->pqueue, psrc); + new_algo->path = psrc; + } + if (dst) { + new_algo->pdst = cpath_new(dst->key); + processed_add(&new_algo->processed, new_algo->pdst); + } + + memcpy(&new_algo->csts, csts, sizeof(struct constraints)); + + return new_algo; +} + +struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted, + const struct in_addr src, const struct in_addr dst, + struct constraints *csts) +{ + struct ls_vertex *vsrc; + struct ls_vertex *vdst; + struct cspf *new_algo; + + /* Sanity Check */ + if (!ted) + return algo; + + if (!algo) + new_algo = cspf_new(); + else + new_algo = algo; + + /* Got Source and Destination Vertex from TED */ + vsrc = get_vertex_by_ipv4(ted, src); + vdst = get_vertex_by_ipv4(ted, dst); + csts->family = AF_INET; + + return cspf_init(new_algo, vsrc, vdst, csts); +} + +struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted, + const struct in6_addr src, const struct in6_addr dst, + struct constraints *csts) +{ + struct ls_vertex *vsrc; + struct ls_vertex *vdst; + struct cspf *new_algo; + + /* Sanity Check */ + if (!ted) + return algo; + + if (!algo) + new_algo = cspf_new(); + else + new_algo = algo; + + /* Got Source and Destination Vertex from TED */ + vsrc = get_vertex_by_ipv6(ted, src); + vdst = get_vertex_by_ipv6(ted, dst); + csts->family = AF_INET6; + + return cspf_init(new_algo, vsrc, vdst, csts); +} + +void cspf_clean(struct cspf *algo) +{ + struct c_path *path; + struct v_node *vnode; + + if (!algo) + return; + + /* Normally, Priority Queue is empty. Clean it in case of. */ + if (pqueue_count(&algo->pqueue)) { + frr_each_safe (pqueue, &algo->pqueue, path) { + pqueue_del(&algo->pqueue, path); + } + } + + /* Empty Processed Path tree and associated Path */ + if (processed_count(&algo->processed)) { + frr_each_safe (processed, &algo->processed, path) { + processed_del(&algo->processed, path); + if (path == algo->pdst) + algo->pdst = NULL; + cpath_del(path); + } + } + + /* Empty visited Vertex tree and associated Node */ + if (visited_count(&algo->visited)) { + frr_each_safe (visited, &algo->visited, vnode) { + visited_del(&algo->visited, vnode); + vnode_del(vnode); + } + } + + if (algo->pdst) + cpath_del(algo->pdst); + + memset(&algo->csts, 0, sizeof(struct constraints)); + algo->path = NULL; + algo->pdst = NULL; +} + +void cspf_del(struct cspf *algo) +{ + if (!algo) + return; + + /* Empty Priority Queue and Processes Path */ + cspf_clean(algo); + + /* Then, reset Priority Queue, Processed Path and Visited RB-Tree */ + pqueue_fini(&algo->pqueue); + processed_fini(&algo->processed); + visited_fini(&algo->visited); + + XFREE(MTYPE_PCA, algo); + algo = NULL; +} + +/** + * Prune Edge if constraints are not met by testing Edge Attributes against + * given constraints and cumulative cost of the given constrained path. + * + * @param path On-going Computed Path with cumulative cost constraints + * @param edge Edge to be validate against Constraints + * @param csts Constraints for this path + * + * @return True if Edge should be prune, false if Edge is valid + */ +static bool prune_edge(const struct c_path *path, const struct ls_edge *edge, + const struct constraints *csts) +{ + struct ls_vertex *dst; + struct ls_attributes *attr; + + /* Check that Path, Edge and Constraints are valid */ + if (!path || !edge || !csts) + return true; + + /* Check that Edge has a valid destination */ + if (!edge->destination) + return true; + dst = edge->destination; + + /* Check that Edge has valid attributes */ + if (!edge->attributes) + return true; + attr = edge->attributes; + + /* Check that Edge belongs to the requested Address Family and type */ + if (csts->family == AF_INET) { + if (IPV4_NET0(attr->standard.local.s_addr)) + return true; + if (csts->type == SR_TE) + if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID) || + !CHECK_FLAG(dst->node->flags, LS_NODE_SR)) + return true; + } + if (csts->family == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&attr->standard.local6)) + return true; + if (csts->type == SR_TE) + if (!CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6) || + !CHECK_FLAG(dst->node->flags, LS_NODE_SR)) + return true; + } + + /* + * Check that total cost, up to this edge, respects the initial + * constraints + */ + switch (csts->ctype) { + case CSPF_METRIC: + if (!CHECK_FLAG(attr->flags, LS_ATTR_METRIC)) + return true; + if ((attr->metric + path->weight) > csts->cost) + return true; + break; + + case CSPF_TE_METRIC: + if (!CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + return true; + if ((attr->standard.te_metric + path->weight) > csts->cost) + return true; + break; + + case CSPF_DELAY: + if (!CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + return true; + if ((attr->extended.delay + path->weight) > csts->cost) + return true; + break; + } + + /* If specified, check that Edge meet Bandwidth constraint */ + if (csts->bw > 0.0) { + if (attr->standard.max_bw < csts->bw || + attr->standard.max_rsv_bw < csts->bw || + attr->standard.unrsv_bw[csts->cos] < csts->bw) + return true; + } + + /* All is fine. We can consider this Edge valid, so not to be prune */ + return false; +} + +/** + * Relax constraints of the current path up to the destination vertex of the + * provided Edge. This function progress in the network topology by validating + * the next vertex on the computed path. If Vertex has not already been visited, + * list of edges of the current path is augmented with this edge if the new cost + * is lower than prior path up to this vertex. Current path is re-inserted in + * the Priority Queue with its new cost i.e. current cost + edge cost. + * + * @param algo CSPF structure + * @param edge Next Edge to be added to the current computed path + * + * @return True if current path reach destination, false otherwise + */ +static bool relax_constraints(struct cspf *algo, struct ls_edge *edge) +{ + + struct c_path pkey = {}; + struct c_path *next_path; + struct v_node vnode = {}; + uint32_t total_cost = MAX_COST; + + /* Verify that we have a current computed path */ + if (!algo->path) + return false; + + /* Verify if we have not visited the next Vertex to avoid loop */ + vnode.key = edge->destination->key; + if (visited_member(&algo->visited, &vnode)) { + return false; + } + + /* + * Get Next Computed Path from next vertex key + * or create a new one if it has not yet computed. + */ + pkey.dst = edge->destination->key; + next_path = processed_find(&algo->processed, &pkey); + if (!next_path) { + next_path = cpath_new(pkey.dst); + processed_add(&algo->processed, next_path); + } + + /* + * Add or update the Computed Path in the Priority Queue if total cost + * is lower than cost associated to this next Vertex. This could occurs + * if we process a Vertex that as not yet been visited in the Graph + * or if we found a shortest path up to this Vertex. + */ + switch (algo->csts.ctype) { + case CSPF_METRIC: + total_cost = edge->attributes->metric + algo->path->weight; + break; + case CSPF_TE_METRIC: + total_cost = edge->attributes->standard.te_metric + + algo->path->weight; + break; + case CSPF_DELAY: + total_cost = + edge->attributes->extended.delay + algo->path->weight; + break; + default: + break; + } + if (total_cost < next_path->weight) { + /* + * It is not possible to directly update the q_path in the + * Priority Queue. Indeed, if we modify the path weight, the + * Priority Queue must be re-ordered. So, we need fist to remove + * the q_path if it is present in the Priority Queue, then, + * update the Path, in particular the Weight, and finally + * (re-)insert it in the Priority Queue. + */ + struct c_path *path; + frr_each_safe (pqueue, &algo->pqueue, path) { + if (path->dst == pkey.dst) { + pqueue_del(&algo->pqueue, path); + break; + } + } + next_path->weight = total_cost; + cpath_replace(next_path, algo->path); + listnode_add(next_path->edges, edge); + pqueue_add(&algo->pqueue, next_path); + } + + /* Return True if we reach the destination */ + return (next_path->dst == algo->pdst->dst); +} + +struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted) +{ + struct listnode *node; + struct ls_vertex *vertex; + struct ls_edge *edge; + struct c_path *optim_path; + struct v_node *vnode; + uint32_t cur_cost; + + optim_path = cpath_new(0xFFFFFFFFFFFFFFFF); + optim_path->status = FAILED; + + /* Check that all is correctly initialized */ + if (!algo) + return optim_path; + + if (!algo->csts.ctype) + return optim_path; + + if (!algo->pdst) { + optim_path->status = NO_DESTINATION; + return optim_path; + } + + if (!algo->path) { + optim_path->status = NO_SOURCE; + return optim_path; + } + + if (algo->pdst->dst == algo->path->dst) { + optim_path->status = SAME_SRC_DST; + return optim_path; + } + + optim_path->dst = algo->pdst->dst; + optim_path->status = IN_PROGRESS; + + /* + * Process all Connected Vertex until priority queue becomes empty. + * Connected Vertices are added into the priority queue when + * processing the next Connected Vertex: see relax_constraints() + */ + cur_cost = MAX_COST; + while (pqueue_count(&algo->pqueue) != 0) { + /* Got shortest current Path from the Priority Queue */ + algo->path = pqueue_pop(&algo->pqueue); + + /* Add destination Vertex of this path to the visited RB Tree */ + vertex = ls_find_vertex_by_key(ted, algo->path->dst); + if (!vertex) + continue; + vnode = vnode_new(vertex); + visited_add(&algo->visited, vnode); + + /* Process all outgoing links from this Vertex */ + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) { + /* + * Skip Connected Edges that must be prune i.e. + * Edges that not satisfy the given constraints, + * in particular the Bandwidth, TE Metric and Delay. + */ + if (prune_edge(algo->path, edge, &algo->csts)) + continue; + + /* + * Relax constraints and check if we got a shorter + * candidate path + */ + if (relax_constraints(algo, edge) && + algo->pdst->weight < cur_cost) { + cur_cost = algo->pdst->weight; + cpath_copy(optim_path, algo->pdst); + optim_path->status = SUCCESS; + } + } + } + + /* + * The priority queue is empty => all the possible (vertex, path) + * elements have been explored. The optim_path contains the optimal + * path if it exists. Otherwise an empty path with status failed is + * returned. + */ + if (optim_path->status == IN_PROGRESS || + listcount(optim_path->edges) == 0) + optim_path->status = FAILED; + cspf_clean(algo); + + return optim_path; +} diff --git a/lib/cspf.h b/lib/cspf.h new file mode 100644 index 0000000..bba685a --- /dev/null +++ b/lib/cspf.h @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Constraints Shortest Path First algorithms definition - cspf.h + * + * Author: Olivier Dugeon + * + * Copyright (C) 2022 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + */ + +#ifndef _FRR_CSPF_H_ +#define _FRR_CSPF_H_ + +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This file defines the different structure used for Path Computation with + * various constrained. Up to now, standard metric, TE metric, delay and + * bandwidth constraints are supported. + * All proposed algorithms used the same principle: + * - A pruning function that keeps only links that meet constraints + * - A priority Queue that keeps the shortest on-going computed path + * - A main loop over all vertices to find the shortest path + */ + +#define MAX_COST 0xFFFFFFFF + +/* Status of the path */ +enum path_status { + FAILED = 0, + NO_SOURCE, + NO_DESTINATION, + SAME_SRC_DST, + IN_PROGRESS, + SUCCESS +}; +enum path_type {RSVP_TE = 1, SR_TE, SRV6_TE}; +enum metric_type {CSPF_METRIC = 1, CSPF_TE_METRIC, CSPF_DELAY}; + +/* Constrained metrics structure */ +struct constraints { + uint32_t cost; /* total cost (metric) of the path */ + enum metric_type ctype; /* Metric Type: standard, TE or Delay */ + float bw; /* bandwidth of the path */ + uint8_t cos; /* Class of Service of the path */ + enum path_type type; /* RSVP-TE or SR-TE path */ + uint8_t family; /* AF_INET or AF_INET6 address family */ +}; + +/* Priority Queue for Constrained Path Computation */ +PREDECL_RBTREE_NONUNIQ(pqueue); + +/* Processed Path for Constrained Path Computation */ +PREDECL_RBTREE_UNIQ(processed); + +/* Constrained Path structure */ +struct c_path { + struct pqueue_item q_itm; /* entry in the Priority Queue */ + uint32_t weight; /* Weight to sort path in Priority Queue */ + struct processed_item p_itm; /* entry in the Processed RB Tree */ + uint64_t dst; /* Destination vertex key of this path */ + struct list *edges; /* List of Edges that compose this path */ + enum path_status status; /* status of the computed path */ +}; + +macro_inline int q_cmp(const struct c_path *p1, const struct c_path *p2) +{ + return numcmp(p1->weight, p2->weight); +} +DECLARE_RBTREE_NONUNIQ(pqueue, struct c_path, q_itm, q_cmp); + +macro_inline int p_cmp(const struct c_path *p1, const struct c_path *p2) +{ + return numcmp(p1->dst, p2->dst); +} +DECLARE_RBTREE_UNIQ(processed, struct c_path, p_itm, p_cmp); + +/* List of visited node */ +PREDECL_RBTREE_UNIQ(visited); +struct v_node { + struct visited_item item; /* entry in the Processed RB Tree */ + uint64_t key; + struct ls_vertex *vertex; +}; + +macro_inline int v_cmp(const struct v_node *p1, const struct v_node *p2) +{ + return numcmp(p1->key, p2->key); +} +DECLARE_RBTREE_UNIQ(visited, struct v_node, item, v_cmp); + +/* Path Computation algorithms structure */ +struct cspf { + struct pqueue_head pqueue; /* Priority Queue */ + struct processed_head processed; /* Paths that have been processed */ + struct visited_head visited; /* Vertices that have been visited */ + struct constraints csts; /* Constraints of the path */ + struct c_path *path; /* Current Computed Path */ + struct c_path *pdst; /* Computed Path to the destination */ +}; + +/** + * Create a new CSPF structure. Memory is dynamically allocated. + * + * @return pointer to the new cspf structure + */ +extern struct cspf *cspf_new(void); + +/** + * Initialize CSPF structure prior to compute a constrained path. If CSPF + * structure is NULL, a new CSPF is dynamically allocated prior to the + * configuration itself. + * + * @param algo CSPF structure, may be null if a new CSPF must be created + * @param src Source vertex of the requested path + * @param dst Destination vertex of the requested path + * @param csts Constraints of the requested path + * + * @return pointer to the initialized CSPF structure + */ +extern struct cspf *cspf_init(struct cspf *algo, const struct ls_vertex *src, + const struct ls_vertex *dst, + struct constraints *csts); + +/** + * Initialize CSPF structure prior to compute a constrained path. If CSPF + * structure is NULL, a new CSPF is dynamically allocated prior to the + * configuration itself. This function starts by searching source and + * destination vertices from the IPv4 addresses in the provided TED. + * + * @param algo CSPF structure, may be null if a new CSPF must be created + * @param ted Traffic Engineering Database + * @param src Source IPv4 address of the requested path + * @param dst Destination IPv4 address of the requested path + * @param csts Constraints of the requested path + * + * @return pointer to the initialized CSPF structure + */ +extern struct cspf *cspf_init_v4(struct cspf *algo, struct ls_ted *ted, + const struct in_addr src, + const struct in_addr dst, + struct constraints *csts); + +/** + * Initialize CSPF structure prior to compute a constrained path. If CSPF + * structure is NULL, a new CSPF is dynamically allocated prior to the + * configuration itself. This function starts by searching source and + * destination vertices from the IPv6 addresses in the provided TED. + * + * @param algo CSPF structure, may be null if a new CSPF must be created + * @param ted Traffic Engineering Database + * @param src Source IPv6 address of the requested path + * @param dst Destination IPv6 address of the requested path + * @param csts Constraints of the requested path + * + * @return pointer to the initialized CSPF structure + */ +extern struct cspf *cspf_init_v6(struct cspf *algo, struct ls_ted *ted, + const struct in6_addr src, + const struct in6_addr dst, + struct constraints *csts); + +/** + * Clean CSPF structure. Reset all internal list and priority queue for latter + * initialization of the CSPF structure and new path computation. + * + * @param algo CSPF structure + */ +extern void cspf_clean(struct cspf *algo); + +/** + * Delete CSPF structure, internal list and priority queue. + * + * @param algo CSPF structure + */ +extern void cspf_del(struct cspf *algo); + +/** + * Compute point-to-point constrained path. cspf_init() function must be call + * prior to call this function. + * + * @param algo CSPF structure + * @param ted Traffic Engineering Database + * + * @return Constrained Path with status to indicate computation success + */ +extern struct c_path *compute_p2p_path(struct cspf *algo, struct ls_ted *ted); + +extern void cpath_del(struct c_path *path); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_CSPF_H_ */ diff --git a/lib/csv.c b/lib/csv.c new file mode 100644 index 0000000..fdd89a0 --- /dev/null +++ b/lib/csv.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* CSV + * Copyright (C) 2013,2020 Cumulus Networks, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "csv.h" + +#define DEBUG_E 1 +#define DEBUG_V 1 + +#define log_error(fmt, ...) \ + do { \ + if (DEBUG_E) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); \ + } while (0) + +#define log_verbose(fmt, ...) \ + do { \ + if (DEBUG_V) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, __VA_ARGS__); \ + } while (0) + +struct _csv_field_t_ { + TAILQ_ENTRY(_csv_field_t_) next_field; + char *field; + int field_len; +}; + +struct _csv_record_t_ { + TAILQ_HEAD(, _csv_field_t_) fields; + TAILQ_ENTRY(_csv_record_t_) next_record; + char *record; + int rec_len; +}; + +struct _csv_t_ { + TAILQ_HEAD(, _csv_record_t_) records; + char *buf; + int buflen; + int csv_len; + int pointer; + int num_recs; +}; + + +int csvlen(csv_t *csv) +{ + return (csv->csv_len); +} + +csv_t *csv_init(csv_t *csv, char *buf, int buflen) +{ + if (csv == NULL) { + csv = malloc(sizeof(csv_t)); + if (csv == NULL) { + log_error("CSV Malloc failed\n"); + return NULL; + } + } + memset(csv, 0, sizeof(csv_t)); + + csv->buf = buf; + csv->buflen = buflen; + TAILQ_INIT(&(csv->records)); + return (csv); +} + +void csv_clean(csv_t *csv) +{ + csv_record_t *rec; + csv_record_t *rec_n; + + rec = TAILQ_FIRST(&(csv->records)); + while (rec != NULL) { + rec_n = TAILQ_NEXT(rec, next_record); + csv_remove_record(csv, rec); + rec = rec_n; + } +} + +void csv_free(csv_t *csv) +{ + if (csv != NULL) { + free(csv); + } +} + +static void csv_init_record(csv_record_t *record) +{ + TAILQ_INIT(&(record->fields)); + record->rec_len = 0; +} + +csv_record_t *csv_record_iter(csv_t *csv) +{ + return (TAILQ_FIRST(&(csv->records))); +} + +csv_record_t *csv_record_iter_next(csv_record_t *rec) +{ + if (!rec) + return NULL; + return (TAILQ_NEXT(rec, next_record)); +} + +char *csv_field_iter(csv_record_t *rec, csv_field_t **fld) +{ + if (!rec) + return NULL; + *fld = TAILQ_FIRST(&(rec->fields)); + return ((*fld)->field); +} + +char *csv_field_iter_next(csv_field_t **fld) +{ + *fld = TAILQ_NEXT(*fld, next_field); + if ((*fld) == NULL) { + return NULL; + } + return ((*fld)->field); +} + +int csv_field_len(csv_field_t *fld) +{ + if (fld) { + return fld->field_len; + } + return 0; +} + +static void csv_decode_record(csv_record_t *rec) +{ + char *curr = rec->record; + char *field; + csv_field_t *fld; + + field = strpbrk(curr, ","); + while (field != NULL) { + fld = malloc(sizeof(csv_field_t)); + if (fld) { + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + fld->field = curr; + fld->field_len = field - curr; + } + curr = field + 1; + field = strpbrk(curr, ","); + } + field = strstr(curr, "\n"); + if (!field) + return; + + fld = malloc(sizeof(csv_field_t)); + if (fld) { + fld->field = curr; + fld->field_len = field - curr; + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + } +} + +static csv_field_t *csv_add_field_to_record(csv_t *csv, csv_record_t *rec, + char *col) +{ + csv_field_t *fld; + char *str = rec->record; + int rlen = rec->rec_len; + int blen = csv->buflen; + + fld = malloc(sizeof(csv_field_t)); + if (!fld) { + log_error("field malloc failed\n"); + /* more cleanup needed */ + return NULL; + } + TAILQ_INSERT_TAIL(&(rec->fields), fld, next_field); + fld->field = str + rlen; + fld->field_len = snprintf((str + rlen), (blen - rlen), "%s", col); + rlen += fld->field_len; + rec->rec_len = rlen; + return fld; +} + +csv_record_t *csv_encode(csv_t *csv, int count, ...) +{ + int tempc; + va_list list; + char *buf = csv->buf; + int len = csv->buflen; + int pointer = csv->pointer; + char *str = NULL; + char *col; + csv_record_t *rec; + csv_field_t *fld; + + if (buf) { + str = buf + pointer; + } else { + /* allocate sufficient buffer */ + str = (char *)malloc(csv->buflen); + if (!str) { + log_error("field str malloc failed\n"); + return NULL; + } + } + + va_start(list, count); + rec = malloc(sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + if (!buf) + free(str); + va_end(list); + return NULL; + } + csv_init_record(rec); + rec->record = str; + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + + /** + * Iterate through the fields passed as a variable list and add them + */ + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + fld = csv_add_field_to_record(csv, rec, col); + if (!fld) { + log_error("fld malloc failed\n"); + csv_remove_record(csv, rec); + va_end(list); + return NULL; + } + if (tempc < (count - 1)) { + rec->rec_len += snprintf((str + rec->rec_len), + (len - rec->rec_len), ","); + } + } + rec->rec_len += + snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); + va_end(list); + csv->csv_len += rec->rec_len; + csv->pointer += rec->rec_len; + return (rec); +} + +int csv_num_records(csv_t *csv) +{ + if (csv) { + return csv->num_recs; + } + return 0; +} + +csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...) +{ + int tempc; + va_list list; + char *str; + char *col; + csv_field_t *fld = NULL; + int i; + + va_start(list, count); + str = csv_field_iter(rec, &fld); + if (!fld) { + va_end(list); + return NULL; + } + + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + for (i = 0; i < fld->field_len; i++) { + str[i] = col[i]; + } + str = csv_field_iter_next(&fld); + } + va_end(list); + return (rec); +} + +csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...) +{ + int tempc; + va_list list; + int len = csv->buflen, tlen; + char *str; + csv_field_t *fld; + char *col; + + if (csv->buf) { + /* not only works with discrete bufs */ + return NULL; + } + + if (!rec) { + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return NULL; + } + csv_init_record(rec); + rec->record = calloc(1, csv->buflen); + if (!rec->record) { + log_error("field str malloc failed\n"); + free(rec); + return NULL; + } + csv_insert_record(csv, rec); + } + + str = rec->record; + + va_start(list, count); + + if (rec->rec_len && (str[rec->rec_len - 1] == '\n')) + str[rec->rec_len - 1] = ','; + + /** + * Iterate through the fields passed as a variable list and add them + */ + tlen = rec->rec_len; + for (tempc = 0; tempc < count; tempc++) { + col = va_arg(list, char *); + fld = csv_add_field_to_record(csv, rec, col); + if (!fld) { + log_error("fld malloc failed\n"); + break; + } + if (tempc < (count - 1)) { + rec->rec_len += snprintf((str + rec->rec_len), + (len - rec->rec_len), ","); + } + } + rec->rec_len += + snprintf((str + rec->rec_len), (len - rec->rec_len), "\n"); + va_end(list); + csv->csv_len += (rec->rec_len - tlen); + csv->pointer += (rec->rec_len - tlen); + return (rec); +} + +int csv_serialize(csv_t *csv, char *msgbuf, int msglen) +{ + csv_record_t *rec; + int offset = 0; + + if (!csv || !msgbuf) + return -1; + + rec = csv_record_iter(csv); + while (rec != NULL) { + if ((offset + rec->rec_len) >= msglen) + return -1; + offset += sprintf(&msgbuf[offset], "%s", rec->record); + rec = csv_record_iter_next(rec); + } + + return 0; +} + +void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec) +{ + char *curr; + csv_record_t *rec; + + /* first check if rec belongs to this csv */ + if (!csv_is_record_valid(csv, in_rec)) { + log_error("rec not in this csv\n"); + return; + } + + /* only works with csv with discrete bufs */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return; + } + + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return; + } + csv_init_record(rec); + curr = calloc(1, csv->buflen); + if (!curr) { + log_error("field str malloc failed\n"); + free(rec); + return; + } + rec->record = curr; + rec->rec_len = in_rec->rec_len; + strlcpy(rec->record, in_rec->record, csv->buflen); + + /* decode record into fields */ + csv_decode_record(rec); + + *out_rec = rec; +} + +void csv_remove_record(csv_t *csv, csv_record_t *rec) +{ + csv_field_t *fld = NULL, *p_fld; + + /* first check if rec belongs to this csv */ + if (!csv_is_record_valid(csv, rec)) { + log_error("rec not in this csv\n"); + return; + } + + /* remove fields */ + csv_field_iter(rec, &fld); + while (fld) { + p_fld = fld; + csv_field_iter_next(&fld); + TAILQ_REMOVE(&(rec->fields), p_fld, next_field); + free(p_fld); + } + + TAILQ_REMOVE(&(csv->records), rec, next_record); + + csv->num_recs--; + csv->csv_len -= rec->rec_len; + csv->pointer -= rec->rec_len; + if (!csv->buf) + free(rec->record); + free(rec); +} + +void csv_insert_record(csv_t *csv, csv_record_t *rec) +{ + /* first check if rec already in csv */ + if (csv_is_record_valid(csv, rec)) { + log_error("rec already in this csv\n"); + return; + } + + /* we can only insert records if no buf was supplied during csv init */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return; + } + + /* do we go beyond the max buf set for this csv ?*/ + if ((csv->csv_len + rec->rec_len) > csv->buflen) { + log_error("cannot insert - exceeded buf size\n"); + return; + } + + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + csv->csv_len += rec->rec_len; + csv->pointer += rec->rec_len; +} + +csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1, + csv_record_t *rec2) +{ + char *curr; + char *ret; + csv_record_t *rec; + + /* first check if rec1 and rec2 belong to this csv */ + if (!csv_is_record_valid(csv, rec1) + || !csv_is_record_valid(csv, rec2)) { + log_error("rec1 and/or rec2 invalid\n"); + return NULL; + } + + /* we can only concat records if no buf was supplied during csv init */ + if (csv->buf) { + log_error( + "un-supported for this csv type - single buf detected\n"); + return NULL; + } + + /* create a new rec */ + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) { + log_error("record malloc failed\n"); + return NULL; + } + csv_init_record(rec); + + curr = (char *)calloc(1, csv->buflen); + if (!curr) { + log_error("field str malloc failed\n"); + goto out_rec; + } + rec->record = curr; + + /* concat the record string */ + ret = strstr(rec1->record, "\n"); + if (!ret) { + log_error("rec1 str not properly formatted\n"); + goto out_curr; + } + + snprintf(curr, (int)(ret - rec1->record + 1), "%s", rec1->record); + strcat(curr, ","); + + ret = strstr(rec2->record, "\n"); + if (!ret) { + log_error("rec2 str not properly formatted\n"); + goto out_curr; + } + + snprintf((curr + strlen(curr)), (int)(ret - rec2->record + 1), "%s", + rec2->record); + strcat(curr, "\n"); + rec->rec_len = strlen(curr); + + /* paranoia */ + assert(csv->buflen + > (csv->csv_len - rec1->rec_len - rec2->rec_len + rec->rec_len)); + + /* decode record into fields */ + csv_decode_record(rec); + + /* now remove rec1 and rec2 and insert rec into this csv */ + csv_remove_record(csv, rec1); + csv_remove_record(csv, rec2); + csv_insert_record(csv, rec); + + return rec; + +out_curr: + free(curr); +out_rec: + free(rec); + return NULL; +} + +void csv_decode(csv_t *csv, char *inbuf) +{ + char *buf; + char *pos; + csv_record_t *rec; + + buf = (inbuf) ? inbuf : csv->buf; + assert(buf); + + pos = strpbrk(buf, "\n"); + while (pos != NULL) { + rec = calloc(1, sizeof(csv_record_t)); + if (!rec) + return; + csv_init_record(rec); + TAILQ_INSERT_TAIL(&(csv->records), rec, next_record); + csv->num_recs++; + if (csv->buf) + rec->record = buf; + else { + rec->record = calloc(1, csv->buflen); + if (!rec->record) { + log_error("field str malloc failed\n"); + return; + } + strncpy(rec->record, buf, pos - buf + 1); + } + rec->rec_len = pos - buf + 1; + /* decode record into fields */ + csv_decode_record(rec); + buf = pos + 1; + pos = strpbrk(buf, "\n"); + } +} + +int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec) +{ + csv_record_t *rec; + int valid = 0; + + rec = csv_record_iter(csv); + while (rec) { + if (rec == in_rec) { + valid = 1; + break; + } + rec = csv_record_iter_next(rec); + } + + return valid; +} + +void csv_dump(csv_t *csv) +{ + csv_record_t *rec; + csv_field_t *fld; + char *str; + + rec = csv_record_iter(csv); + while (rec != NULL) { + str = csv_field_iter(rec, &fld); + while (str != NULL) { + fprintf(stderr, "%s\n", str); + str = csv_field_iter_next(&fld); + } + rec = csv_record_iter_next(rec); + } +} + +#ifdef TEST_CSV + +static int get_memory_usage(pid_t pid) +{ + int fd, data, stack; + char buf[4096], status_child[PATH_MAX]; + char *vm; + + snprintf(status_child, sizeof(status_child), "/proc/%d/status", pid); + fd = open(status_child, O_RDONLY); + if (fd < 0) + return -1; + + read(fd, buf, 4095); + buf[4095] = '\0'; + close(fd); + + data = stack = 0; + + vm = strstr(buf, "VmData:"); + if (vm) { + sscanf(vm, "%*s %d", &data); + } + vm = strstr(buf, "VmStk:"); + if (vm) { + sscanf(vm, "%*s %d", &stack); + } + + return data + stack; +} + +int main() +{ + char buf[10000]; + csv_t csv; + int i; + csv_record_t *rec; + char hdr1[32], hdr2[32]; + + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_init(&csv, buf, 256); + snprintf(hdr1, sizeof(hdr1), "%4d", 0); + snprintf(hdr2, sizeof(hdr2), "%4d", 1); + log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), + atoi(hdr2)); + rec = csv_encode(&csv, 2, hdr1, hdr2); + csv_encode(&csv, 4, "name", "age", "sex", "hei"); + csv_encode(&csv, 3, NULL, "0", NULL); + csv_encode(&csv, 2, "p", "35"); + for (i = 0; i < 50; i++) { + csv_encode(&csv, 2, "p", "10"); + } + csv_encode(&csv, 2, "pdfadfadfadsadsaddfdfdsfdsd", "35444554545454545"); + log_verbose("%s\n", buf); + snprintf(hdr1, sizeof(hdr1), "%4d", csv.csv_len); + snprintf(hdr2, sizeof(hdr2), "%4d", 1); + log_verbose("(%zu/%zu/%d/%d)\n", strlen(hdr1), strlen(hdr2), atoi(hdr1), + atoi(hdr2)); + rec = csv_encode_record(&csv, rec, 2, hdr1, hdr2); + log_verbose("(%d/%d)\n%s\n", rec->rec_len, csv.csv_len, buf); + + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_clean(&csv); + log_verbose("Mem: %d\n", get_memory_usage(getpid())); + csv_init(&csv, buf, 256); + csv_decode(&csv, NULL); + log_verbose("%s", "AFTER DECODE\n"); + csv_dump(&csv); + csv_clean(&csv); + log_verbose("Mem: %d\n", get_memory_usage(getpid())); +} +#endif diff --git a/lib/csv.h b/lib/csv.h new file mode 100644 index 0000000..c780ab6 --- /dev/null +++ b/lib/csv.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* CSV + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#ifndef __CSV_H__ +#define __CSV_H__ + +/* + * CSV encoding and decoding routines. + * + * Example: + * Encoding side: + * + * csv_t *csv; + * csv_record_t *fstrec; + * csv_record_t *rec; + * char buf[BUFSIZ]; + * + * csv = csv_init(csv, buf, BUFSIZ); + * ... + * fstrec = csv_encode(csv, 2, "hello", "world"); + * rec = csv_encode(csv, 2, "foo", "bar"); + * ... + * fstrec = csv_encode_record(csv, fstrec, 2, "HELLO", "WORLD"); + * ... + * csv_clean(csv); + * + * Decoding side: + * + * csv_t *csv; + * csv_record_t *rec; + * csv_field_t *fld; + * char *rcvdbuf; + * + * csv = csv_init(csv, rcvdbuf, BUFSIZ); + * ... + * csv_decode(csv); + * csv_dump(csv); + * + * for (rec = csv_record_iter(csv); rec; + * rec = csv_record_iter_next(rec)) { + * ... + * for (str = csv_field_iter(rec, &fld); str; + * str = csv_field_iter_next(&fld)) { + * ... + * } + * } + * ... + * csv_clean(csv); + */ +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _csv_field_t_ csv_field_t; +typedef struct _csv_record_t_ csv_record_t; +typedef struct _csv_t_ csv_t; + +/** + * Initialize the CSV structure (if necessary, allocate first). Point to + * the passed string buffer. + */ +csv_t *csv_init(csv_t *csv, char *buf, int buflen); + +/** + * Encode the variable list of arguments as CSV fields. The csv structure + * should have been initialized (with the string buffer). The fields get + * concatenated into the string. + */ +csv_record_t *csv_encode(csv_t *csv, int count, ...); + +/** + * Encode the variable list arguments into an existing record, essentially + * overwriting the record. No checking is done for consistency. The number + * of fields should be the same as what was encoded and the length of each + * field should also be the same as what was encoded before. The "rec" + * parameter should be the same as what was returned from a previous call + * to csv_encode(). + * + * Useful for message encoding/decoding that get passed around between + * processes/nodes - e.g. the message header record can be rewritten AFTER + * encoding all other records, with new information such as total length. + */ +csv_record_t *csv_encode_record(csv_t *csv, csv_record_t *rec, int count, ...); + +/** + * Decode a CSV formatted string. The csv structure should have been + * initialized (with the string). The function creates a LIST of records + * (csv_record_t structure) where each record is in turn a LIST of fields + * (csv_field_t structure). The record points to the string containing the + * list of fields. Similarly, the field points to the field string. + * NB: csv initialized for discrete buf , caller will pass inbuf + */ +void csv_decode(csv_t *csv, char *inbuf); + +/** + * Dump all fields of a decoded CSV to stderr + */ +void csv_dump(csv_t *csv); + +/** + * Total length of all characters encoded in the CSV. + */ +int csvlen(csv_t *csv); + +void csv_clean(csv_t *csv); +void csv_free(csv_t *csv); + +/** + * Iterate through the records and fields of an encoded/decoded CSV. + */ +csv_record_t *csv_record_iter(csv_t *csv); +csv_record_t *csv_record_iter_next(csv_record_t *rec); +char *csv_field_iter(csv_record_t *rec, csv_field_t **fld); +char *csv_field_iter_next(csv_field_t **fld); + +/** + * Return the length of field + */ +int csv_field_len(csv_field_t *fld); + +/** + * Checks to see if a record belongs to a csv + */ +int csv_is_record_valid(csv_t *csv, csv_record_t *in_rec); + +/** + * concat two records in a csv + * Returns the newly formed record which includes fields from rec1 and rec2 + * rec1 and rec2 are removed + */ +csv_record_t *csv_concat_record(csv_t *csv, csv_record_t *rec1, + csv_record_t *rec2); + +/** + * Remove a record from csv + * Only works when csv has discrete record bufs + */ +void csv_remove_record(csv_t *csv, csv_record_t *rec); + +/** + * Insert a record into csv + * Only works when csv has discrete record bufs + */ +void csv_insert_record(csv_t *csv, csv_record_t *rec); + +/** + * append fields to a record + * Only works when csv has discrete record bufs + */ +csv_record_t *csv_append_record(csv_t *csv, csv_record_t *rec, int count, ...); + +/** + * Serialize contents of csv into string + * Only works when csv has discrete record bufs + */ +int csv_serialize(csv_t *csv, char *msgbuf, int msglen); + +/** + * Clone a record. + * Only works when csv has discrete record bufs + */ +void csv_clone_record(csv_t *csv, csv_record_t *in_rec, csv_record_t **out_rec); + +/** + * Return number of records + */ +int csv_num_records(csv_t *csv); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/darr.c b/lib/darr.c new file mode 100644 index 0000000..7a01274 --- /dev/null +++ b/lib/darr.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * June 23 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ +#include +#include "darr.h" +#include "memory.h" + +DEFINE_MTYPE(LIB, DARR, "Dynamic Array"); +DEFINE_MTYPE(LIB, DARR_STR, "Dynamic Array String"); + +static uint _msb(uint count) +{ + uint bit = 0; + int msb = 0; + + while (count) { + if (count & 1) + msb = bit; + count >>= 1; + bit += 1; + } + return msb; +} + +static uint darr_next_count(uint count, size_t esize) +{ + uint ncount; + + if (esize > sizeof(long long) && count == 1) + /* treat like a pointer */ + ncount = 1; + else { + uint msb = _msb(count); + + ncount = 1ull << msb; + /* if the users count wasn't a pow2 make it the next pow2. */ + if (ncount != count) { + assert(ncount < count); + ncount <<= 1; + if (esize < sizeof(long long) && ncount < 8) + ncount = 8; + } + } + return ncount; +} + +static size_t darr_size(uint count, size_t esize) +{ + return count * esize + sizeof(struct darr_metadata); +} + +char *__darr_in_vsprintf(char **sp, bool concat, const char *fmt, va_list ap) +{ + size_t inlen = concat ? darr_strlen(*sp) : 0; + size_t capcount = strlen(fmt) + MIN(inlen + 64, 128); + ssize_t len; + va_list ap_copy; + + darr_ensure_cap(*sp, capcount); + + if (!concat) + darr_reset(*sp); + + /* code below counts on having a NUL terminated string */ + if (darr_len(*sp) == 0) + *darr_append(*sp) = 0; +again: + va_copy(ap_copy, ap); + len = vsnprintf(darr_last(*sp), darr_avail(*sp) + 1, fmt, ap_copy); + va_end(ap_copy); + if (len < 0) + darr_in_strcat(*sp, fmt); + else if ((size_t)len <= darr_avail(*sp)) + _darr_len(*sp) += len; + else { + darr_ensure_cap(*sp, darr_len(*sp) + (size_t)len); + goto again; + } + return *sp; +} + +char *__darr_in_sprintf(char **sp, bool concat, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)__darr_in_vsprintf(sp, concat, fmt, ap); + va_end(ap); + return *sp; +} + + +void *__darr_resize(void *a, uint count, size_t esize, struct memtype *mtype) +{ + uint ncount = darr_next_count(count, esize); + size_t osz = (a == NULL) ? 0 : darr_size(darr_cap(a), esize); + size_t sz = darr_size(ncount, esize); + struct darr_metadata *dm; + + if (a) { + dm = XREALLOC(_darr_meta(a)->mtype, _darr_meta(a), sz); + if (sz > osz) + memset((char *)dm + osz, 0, sz - osz); + } else { + dm = XCALLOC(mtype, sz); + dm->mtype = mtype; + } + dm->cap = ncount; + return (void *)(dm + 1); +} + + +void *__darr_insert_n(void *a, uint at, uint count, size_t esize, bool zero, + struct memtype *mtype) +{ + struct darr_metadata *dm; + uint olen, nlen; + + if (!a) + a = __darr_resize(NULL, at + count, esize, mtype); + dm = (struct darr_metadata *)a - 1; + olen = dm->len; + + // at == 1 + // count == 100 + // olen == 2 + + /* see if the user is expanding first using `at` */ + if (at >= olen) + nlen = at + count; + else + nlen = olen + count; + + if (nlen > dm->cap) { + a = __darr_resize(a, nlen, esize, mtype); + dm = (struct darr_metadata *)a - 1; + } + +#define _a_at(i) ((char *)a + ((i)*esize)) + if (at < olen) + memmove(_a_at(at + count), _a_at(at), esize * (olen - at)); + + dm->len = nlen; + + if (zero) { + if (at >= olen) { + at -= olen; + count += olen; + } + memset(_a_at(at), 0, esize * count); + } + + return a; +#undef _a_at +} diff --git a/lib/darr.h b/lib/darr.h new file mode 100644 index 0000000..404869d --- /dev/null +++ b/lib/darr.h @@ -0,0 +1,753 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * June 23 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ +#ifndef _FRR_DARR_H_ +#define _FRR_DARR_H_ + +/* + * API functions: + * ============== + * - darr_append + * - darr_append_mt + * - darr_append_n + * - darr_append_n_mt + * - darr_append_nz + * - darr_append_nz_mt + * - darr_cap + * - darr_ensure_avail + * - darr_ensure_avail_mt + * - darr_ensure_cap + * - darr_ensure_cap_mt + * - darr_ensure_i + * - darr_ensure_i_mt + * - darr_free + * - darr_insert + * - darr_insert_mt + * - darr_insertz + * - darr_insertz_mt + * - darr_insert_n + * - darr_insert_n_mt + * - darr_insert_nz + * - darr_insert_nz_mt + * - darr_last + * - darr_lasti + * - darr_len + * - darr_maxi + * - darr_pop + * - darr_push + * - darr_pushz + * - darr_remove + * - darr_remove_n + * - darr_reset + * - darr_setlen + * + * Iteration + * --------- + * - darr_foreach_i + * - darr_foreach_p + * + * String Utilities + * ---------------- + * - darr_in_strcat_tail + * - darr_in_strcatf, darr_in_vstrcatf + * - darr_in_strdup + * - darr_in_strdup_cap + * - darr_in_sprintf, darr_in_vsprintf + * - darr_set_strlen + * - darr_strdup + * - darr_strdup_cap + * - darr_strlen + * - darr_strnul + * - darr_sprintf, darr_vsprintf + */ +/* + * A few assured items + * + * - DAs will never have capacity 0 unless they are NULL pointers. + */ + +/* + * NOTE: valgrind by default enables a "length64" heuristic (among others) which + * identifies "interior-pointer" 8 bytes forward of a "start-pointer" as a + * "start-pointer". This should cause what normally would be "possibly-lost" + * errors to instead be definite for dynamic arrays. This is b/c the header is 8 bytes + */ + +#include +#include +#include "memory.h" + +DECLARE_MTYPE(DARR); +DECLARE_MTYPE(DARR_STR); + +struct darr_metadata { + uint32_t len; + uint32_t cap; + struct memtype *mtype; +}; + +void *__darr_insert_n(void *a, uint at, uint count, size_t esize, bool zero, + struct memtype *mt); +char *__darr_in_sprintf(char **sp, bool concat, const char *fmt, ...) + PRINTFRR(3, 4); +char *__darr_in_vsprintf(char **sp, bool concat, const char *fmt, va_list ap) + PRINTFRR(3, 0); +void *__darr_resize(void *a, uint count, size_t esize, struct memtype *mt); + + +#define _darr_esize(A) sizeof((A)[0]) +#define darr_esize(A) sizeof((A)[0]) +#define _darr_len(A) _darr_meta(A)->len +#define _darr_meta(A) (((struct darr_metadata *)(A)) - 1) +#define _darr_resize_mt(A, C, MT) \ + ({ (A) = __darr_resize(A, C, _darr_esize(A), MT); }) +#define _darr_resize(A, C) _darr_resize_mt(A, C, MTYPE_DARR) + +/* Get the current capacity of the array */ +#define darr_cap(A) (((A) == NULL) ? 0 : _darr_meta(A)->cap) + +/* Get the current available expansion space */ +#define darr_avail(A) (((A) == NULL) ? 0 : (darr_cap(A) - darr_len(A))) + +/* Get the largest possible index one can `darr_ensure_i` w/o resizing */ +#define darr_maxi(A) ((int)darr_cap(A) - 1) + +/** + * darr_len() - Get the current length of the array as a unsigned int. + * darr_ilen() - Get the current length of the array as an int. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * The current length of the array. + */ +#define darr_len(A) (((A) == NULL) ? 0 : _darr_meta(A)->len) +#define darr_ilen(A) (((A) == NULL) ? 0 : (ssize_t)_darr_meta(A)->len) + +/** + * darr_lasti() - Get the last element's index. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * The current last element index, or -1 for none. + */ +#define darr_lasti(A) (darr_ilen(A) - 1) + +/** + * Set the current length of the array `A` to 0. + * + * Args: + * A: The dynamic array, can be NULL. + */ +#define darr_reset(A) \ + do { \ + if ((A)) \ + _darr_len(A) = 0; \ + } while (0) + +/** + * Set the current length of the array `A` to `L`. + * + * This function does *not* guarantee the memory is valid to L, + * use `darr_ensure` or `darr_ensure_cap` for that. + * + * Args: + * A: The dynamic array, can only be NULL if (L) == 0. + * L: The new length of the array. + */ +#define darr_setlen(A, L) \ + do { \ + assert((A) || !(L)); \ + if ((A)) { \ + /* have to cast to avoid compiler warning for "0" */ \ + assert((long long)darr_cap(A) >= (long long)(L)); \ + _darr_len(A) = (L); \ + } \ + } while (0) + +/** + * Set the string length of the array `S` to `L`, and NUL + * terminate the string at L. The dynamic array length will be `L` + 1. + * + * Thus after calling: + * + * darr_len(S) == L + 1 + * darr_strlen(S) == L + * S[L] == 0 + * + * This function does *not* guarantee the `L` + 1 memory is allocated to + * the array, use `darr_ensure` or `*_cap` functions for that. + * + * Args: + * S: The dynamic array, cannot be NULL. + * L: The new str length of the array, will set + * + * Return: + * A pointer to the end of S (i.e., pointing to the NUL byte). + */ +#define darr_set_strlen(S, L) \ + ({ \ + assert((S)); \ + /* have to cast to avoid compiler warning for "0" */ \ + assert((long long)darr_cap(S) >= (long long)(L)); \ + _darr_len(S) = (L) + 1; \ + *darr_last(S) = 0; \ + darr_last(S); \ + }) + +/** + * Free memory allocated for the dynamic array `A` + * + * Args: + * A: The dynamic array, can be NULL. + */ + +#define darr_free(A) \ + do { \ + if ((A)) { \ + struct darr_metadata *__meta = _darr_meta(A); \ + XFREE(__meta->mtype, __meta); \ + (A) = NULL; \ + } \ + } while (0) + +/** + * Make sure that there is room in the dynamic array `A` to add `C` elements. + * + * Available space is `darr_cap(a) - darr_len(a)`. + * + * The value `A` may be changed as a result of this call in which case any + * pointers into the previous memory block are no longer valid. The `A` value + * is guaranteed not to change if there is sufficient capacity in the array. + * + * Args: + * A: (IN/OUT) the dynamic array, can be NULL. + * S: Amount of free space to guarantee. + * + * Return: + * A pointer to the (possibly moved) array. + */ +#define darr_ensure_avail_mt(A, S, MT) \ + ({ \ + ssize_t need = (ssize_t)(S) - \ + (ssize_t)(darr_cap(A) - darr_len(A)); \ + if (need > 0) \ + _darr_resize_mt((A), darr_cap(A) + need, MT); \ + (A); \ + }) +#define darr_ensure_avail(A, S) darr_ensure_avail_mt(A, S, MTYPE_DARR) + +/** + * Make sure that there is room in the dynamic array `A` for `C` elements. + * + * The value `A` may be changed as a result of this call in which case any + * pointers into the previous memory block are no longer valid. The `A` value + * is guaranteed not to change if there is sufficient capacity in the array. + * + * The exception to the no-change rule is if @C is passed as 0, it will be + * considered 1 so that an array is always allocated if currently NULL, + * i.e., @A will never be NULL after a call to darr_ensure_cap_mt() + * + * Args: + * A: (IN/OUT) the dynamic array, can be NULL. + * C: Total capacity to guarantee. + * + * Return: + * A pointer to the (possibly moved) array. + */ +#define darr_ensure_cap_mt(A, C, MT) \ + ({ \ + /* Cast to avoid warning when C == 0 */ \ + uint _c = (C) > 0 ? (C) : 1; \ + if ((size_t)darr_cap(A) < _c) \ + _darr_resize_mt((A), _c, MT); \ + (A); \ + }) +#define darr_ensure_cap(A, C) darr_ensure_cap_mt(A, C, MTYPE_DARR) + +/** + * Return a pointer to the (I)th element of array `A`, making sure there is + * room for the element. + * + * If the array length is less than `I + 1` then the length is set to `I + 1`. + * + * The value `A` may be changed as a result of this call in which case any + * pointers into the previous memory block are no longer valid. The `A` value + * is guaranteed not to change if there is sufficient capacity in the array. + * + * Args: + * + * A: (IN/OUT) the dynamic array, can be NULL. + * I: the index to guarantee memory exists for + * + * Return: + * A pointer to the (I)th element in `A` + */ +#define darr_ensure_i_mt(A, I, MT) \ + ({ \ + assert((int)(I) >= 0 && (uint)(I) <= INT_MAX); \ + int _i = (int)(I); \ + if (_i > darr_maxi(A)) \ + _darr_resize_mt((A), _i + 1, MT); \ + assert((A) != NULL); \ + if ((uint)_i + 1 > _darr_len(A)) \ + _darr_len(A) = _i + 1; \ + &(A)[_i]; \ + }) +#define darr_ensure_i(A, I) darr_ensure_i_mt(A, I, MTYPE_DARR) + +#define _darr_insert_n(A, I, N, Z, MT) \ + ({ \ + (A) = __darr_insert_n(A, I, N, _darr_esize(A), Z, MT); \ + &(A)[I]; \ + }) +/** + * Insert N uninitialized elements in the array at index `I`. + * + * Previous elements from `I` are shifted right by `N`. Array length is + * increased by `N`. + * + * The value `A` may be changed as a result of this call in which case any + * pointers into the previous memory block are no longer valid. The `A` value + * is guaranteed not to change if there is sufficient capacity in the array. + * + * The `z` variant zeros new elements. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * A pointer to the first inserted element in the array. + */ +#define darr_insert_n(A, I, N) _darr_insert_n(A, I, N, false, MTYPE_DARR) +#define darr_insert_n_mt(A, I, N) _darr_insert_n(A, I, N, false, MT) +#define darr_insert_nz(A, I, N) _darr_insert_n(A, I, N, true, MTYPE_DARR) +#define darr_insert_nz_mt(A, I, N) _darr_insert_n(A, I, N, true, MT) + +/** + * Insert an uninitialized element in the array at index `I`. + * + * Previous elements from `I` are shifted right by 1. Array length is + * increased by 1. + * + * The value `A` may be changed as a result of this call in which case any + * pointers into the previous memory block are no longer valid. The `A` value + * is guaranteed not to change if there is sufficient capacity in the array. + * + * The `z` variant zeros the new element. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * A pointer to the element in the array. + */ +#define darr_insert(A, I) _darr_insert_n(A, I, 1, false, MTYPE_DARR) +#define darr_insert_mt(A, I) _darr_insert_n(A, I, 1, false, MT) +#define darr_insertz(A, I) _darr_insert_n(A, I, 1, true, MTYPE_DARR) +#define darr_insertz_mt(A, I) _darr_insert_n(A, I, 1, true, MT) + +/** + * Remove `N` elements from the array starting at index `I`. + * + * Elements from `I` + `N` are shifted left by `N`. Array length is reduced by + * `N`. + * + * Args: + * A: The dynamic array, can be NULL. + */ +#define darr_remove_n(A, I, N) \ + do { \ + uint __i = (I); \ + uint __n = (N); \ + uint __len = darr_len(A); \ + if (!__len) \ + break; \ + else if (__i + __n < __len) { \ + memmove(&(A)[__i], &(A)[__i + __n], \ + _darr_esize(A) * (__len - (__i + __n))); \ + _darr_len(A) = __len - __n; \ + } else \ + _darr_len(A) = __i; \ + } while (0) + +/** + * Remove the `I`th element from the array. + * + * Previous elements from `I` + 1 are shifted left by 1, Array length is reduced + * by 1. + * + * Args: + * A: The dynamic array, can be NULL. + */ +#define darr_remove(A, I) darr_remove_n(A, I, 1) + + +#define _darr_append_n(A, N, Z, MT) \ + ({ \ + uint __len = darr_len(A); \ + darr_ensure_cap_mt(A, __len + (N), MT); \ + _darr_len(A) = __len + (N); \ + if (Z) \ + memset(&(A)[__len], 0, (N)*_darr_esize(A)); \ + &(A)[__len]; \ + }) +/** + * Extending the array's length by N. + * + * Args: + * A: The dynamic array, can be NULL. + * + * The `z` variant zeros new elements. + * + * Return: + * A pointer to the first of the added elements at the end of the array. + */ +#define darr_append_n(A, N) _darr_append_n(A, N, false, MTYPE_DARR) +#define darr_append_n_mt(A, N, MT) _darr_append_n(A, N, false, MT) +#define darr_append_nz(A, N) _darr_append_n(A, N, true, MTYPE_DARR) +#define darr_append_nz_mt(A, N, MT) _darr_append_n(A, N, true, MT) + +/** + * Extending the array's length by 1. + * + * Args: + * A: The dynamic array, can be NULL. + * + * The `z` variant zeros the new element. + * + * Return: + * A pointer to the new element at the end of the array. + */ +#define darr_append(A) _darr_append_n(A, 1, false, MTYPE_DARR) +#define darr_append_mt(A, MT) _darr_append_n(A, 1, false, MT) +#define darr_appendz(A) _darr_append_n(A, 1, true, MTYPE_DARR) +#define darr_appendz_mt(A, MT) _darr_append_n(A, 1, true, MT) + +/** + * Append an element `E` onto the array `A`, extending it's length by 1. + * + * The `z` variant zeros the new element. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * A pointer to the element in the array. + */ +#define darr_push(A, E) (*darr_append(A) = (E)) +#define darr_push_mt(A, E, MT) (*darr_append_mt(A, MT) = (E)) +#define darr_pushz(A) (darr_appendz(A)) +#define darr_pushz_mt(A, MT) (darr_appendz_mt(A, MT)) + + +/** + * Pop the last `N` elements from the array decrementing the length by `N`. + * + * Args: + * A: The dynamic array, can be NULL. + */ +#define darr_pop_n(A, N) \ + do { \ + if ((A) && (N) >= _darr_len(A)) \ + darr_reset(A); \ + else \ + _darr_len(A) -= (N); \ + } while (0) + + +/** + * Pop the last element from the array decrementing the length by 1. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * The element just popped. + */ +#define darr_pop(A) \ + ({ \ + uint __len = _darr_len(A); \ + assert(__len); \ + darr_remove(A, __len - 1); \ + /* count on fact that we don't resize */ \ + (A)[__len - 1]; \ + }) + +/** + * Return the address at the end of the array -- useful for iterating + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * The address of the end of the array (past the last elment) or NULL + * if `A` is NULL. + */ +#define darr_end(A) ((A) + darr_len(A)) + +/** + * darr_last() - Get a pointer to the last element of the array. + * darr_strnul() - Get a pointer to the NUL byte of the darr string or NULL. + * + * Args: + * A: The dynamic array, can be NULL. + * + * Return: + * A pointer to the last element of the array or NULL if the array is + * empty. + */ +#define darr_last(A) \ + ({ \ + uint __len = darr_len(A); \ + ((__len > 0) ? &(A)[__len - 1] : NULL); \ + }) +#define darr_strnul(S) darr_last(S) + +/** + * darr_in_sprintf() - sprintf into D. + * + * Args: + * D: The destination darr, D's value may be NULL. + * F: The format string + * ...: variable arguments for format string. + * + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_in_sprintf(D, F, ...) __darr_in_sprintf(&(D), 0, F, __VA_ARGS__) + + +/** + * darr_in_strcat() - concat a string into a darr string. + * + * Args: + * D: The destination darr, D's value may be NULL. + * S: The string to concat onto D. + * + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_in_strcat(D, S) \ + ({ \ + uint __dlen = darr_strlen(D); \ + uint __slen = strlen(S); \ + darr_ensure_cap_mt(D, __dlen + __slen + 1, MTYPE_DARR_STR); \ + if (darr_len(D) == 0) \ + *darr_append(D) = 0; \ + memcpy(darr_last(D), (S), __slen + 1); \ + _darr_len(D) += __slen; \ + D; \ + }) + +/** + * darr_in_strcatf() - concat a formatted string into a darr string. + * + * Args: + * D: The destination darr, D's value may be NULL. + * F: The format string to concat onto D after adding arguments. + * ...: The arguments for the format string. + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_in_strcatf(D, F, ...) \ + __darr_in_sprintf(&(D), true, (F), __VA_ARGS__) + +/** + * darr_in_strcat_tail() - copies end of one darr str to another. + * + * This is a rather specialized function, it takes 2 darr's, a destination and a + * source. If the source is not longer than the destination nothing is done. + * Otherwise the characters in the source that lie beyond the length of the dest + * are added to the dest. No checking is done to make sure the common prefix + * matches. For example: + * + * D: "/foo" + * S: "/foo/bar" + * -> D: "/foo/bar" + * + * perhaps surprising results: + * D: "/foo" + * S: "/zoo/bar" + * -> D: "/foo/bar" + * + * Args: + * D: The destination darr, D's value may be NULL. + * S: The string to copy the tail from. + * + * Return: + * The dynamic_array D with the extended string content. + */ +#define darr_in_strcat_tail(D, S) \ + ({ \ + int __dsize, __ssize, __extra; \ + \ + if (darr_len(D) == 0) \ + *darr_append(D) = 0; \ + __dsize = darr_ilen(D); \ + __ssize = darr_ilen(S); \ + __extra = __ssize - __dsize; \ + if (__extra > 0) { \ + darr_ensure_cap_mt(D, (uint)__ssize, MTYPE_DARR_STR); \ + memcpy(darr_last(D), (S) + __dsize - 1, __extra + 1); \ + _darr_len(D) += __extra; \ + } \ + D; \ + }) + +/** + * darr_in_strdup_cap() - duplicate the string into a darr reserving capacity. + * darr_in_strdup() - duplicate the string into a darr. + * + * Args: + * D: The destination darr, D's value may be NULL. + * S: The string to duplicate. + * C: The capacity to reserve. + * + * Return: + * The dynamic_array D with the duplicated string. + */ +#define darr_in_strdup_cap(D, S, C) \ + ({ \ + size_t __size = strlen(S) + 1; \ + darr_reset(D); \ + darr_ensure_cap_mt(D, \ + ((size_t)(C) > __size) ? (size_t)(C) \ + : __size, \ + MTYPE_DARR_STR); \ + strlcpy(D, (S), darr_cap(D)); \ + darr_setlen((D), (size_t)__size); \ + D; \ + }) +#define darr_in_strdup(D, S) darr_in_strdup_cap(D, S, 1) + +/** + * darr_in_vsprintf() - vsprintf into D. + * + * Args: + * D: The destination darr, D's value may be NULL. + * F: The format string + * A: Varargs + * + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_in_vsprintf(D, F, A) __darr_in_vsprintf(&(D), 0, F, A) + +/** + * darr_in_vstrcatf() - concat a formatted string into a darr string. + * + * Args: + * D: The destination darr, D's value may be NULL. + * F: The format string to concat onto D after adding arguments. + * A: Varargs + * + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_in_vstrcatf(D, F, A) __darr_in_vsprintf(&(D), true, (F), (A)) + +/** + * darr_sprintf() - sprintf into a new dynamic array. + * + * Args: + * F: The format string + * ...: variable arguments for format string. + * + * Return: + * A char * dynamic_array with the new string content. + */ +#define darr_sprintf(F, ...) \ + ({ \ + char *d = NULL; \ + __darr_in_sprintf(&d, false, F, __VA_ARGS__); \ + d; \ + }) + +/** + * darr_strdup_cap() - duplicate the string reserving capacity. + * darr_strdup() - duplicate the string into a dynamic array. + * + * Args: + * S: The string to duplicate. + * C: The capacity to reserve. + * + * Return: + * The dynamic_array with the duplicated string. + */ +#define darr_strdup_cap(S, C) \ + ({ \ + size_t __size = strlen(S) + 1; \ + char *__s = NULL; \ + /* Cast to ssize_t to avoid warning when C == 0 */ \ + darr_ensure_cap_mt(__s, \ + ((ssize_t)(C) > (ssize_t)__size) \ + ? (size_t)(C) \ + : __size, \ + MTYPE_DARR_STR); \ + strlcpy(__s, (S), darr_cap(__s)); \ + darr_setlen(__s, (size_t)__size); \ + __s; \ + }) +#define darr_strdup(S) darr_strdup_cap(S, 0) + +/** + * darr_strlen() - get the length of the NUL terminated string in a darr. + * + * Args: + * S: The string to measure, value may be NULL. + * + * Return: + * The length of the NUL terminated string in @S + */ +#define darr_strlen(S) \ + ({ \ + uint __size = darr_len(S); \ + if (__size) \ + __size -= 1; \ + assert(!(S) || ((char *)(S))[__size] == 0); \ + __size; \ + }) + +/** + * darr_vsprintf() - vsprintf into a new dynamic array. + * + * Args: + * F: The format string + * A: Varargs + * + * Return: + * The dynamic_array D with the new string content. + */ +#define darr_vsprintf(F, A) \ + ({ \ + char *d = NULL; \ + darr_in_vsprintf(d, F, A); \ + d; \ + }) + +/** + * Iterate over array `A` using a pointer to each element in `P`. + * + * Args: + * A: The dynamic array, can be NULL. + * P: A variable with the same type as A used as the iterator. + */ +#define darr_foreach_p(A, P) for ((P) = (A); (P) < darr_end(A); (P)++) + +/** + * Iterate over array `A`s indices. + * + * Args: + * A: The dynamic array, can be NULL. + * I: A uint variable to store the current element index in. + */ +#define darr_foreach_i(A, I) for ((I) = 0; (I) < darr_len(A); (I)++) + +#endif /* _FRR_DARR_H_ */ diff --git a/lib/db.c b/lib/db.c new file mode 100644 index 0000000..55f29ad --- /dev/null +++ b/lib/db.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: ISC AND GPL-2.0-or-later +/* + * Copyright (c) 2018 Rafael Zalamena + */ + +/* + * Copyright (c) 2016 Rafael Zalamena + */ + +#include + +#include "db.h" +#include "log.h" + +static struct sqlite3 *dbp; + +/* + * Initialize the database in path. + * + * It's possible to use in memory database with ':memory:' path. + */ +int db_init(const char *path_fmt, ...) +{ + char path[BUFSIZ]; + va_list ap; + + if (dbp) + return -1; + + va_start(ap, path_fmt); + vsnprintf(path, sizeof(path), path_fmt, ap); + va_end(ap); + + if (sqlite3_open_v2(path, &dbp, + (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE), NULL) + != SQLITE_OK) { + if (dbp == NULL) { + zlog_warn("%s: failed to open database '%s'", __func__, + path); + return -1; + } + + zlog_warn("%s: failed to open database '%s': %s", __func__, + path, sqlite3_errmsg(dbp)); + if (sqlite3_close_v2(dbp) != SQLITE_OK) + zlog_warn("%s: failed to terminate database", __func__); + dbp = NULL; + return -1; + } + + return 0; +} + +/* Closes the database if open. */ +int db_close(void) +{ + if (dbp == NULL) + return 0; + + if (sqlite3_close_v2(dbp) != SQLITE_OK) { + zlog_warn("%s: failed to terminate database", __func__); + return -1; + } + return 0; +} + +/* Helper function to handle formating. */ +static int db_vbindf(struct sqlite3_stmt *ss, const char *fmt, va_list vl) +{ + const char *sptr = fmt; + int column = 1; + const char *str; + void *blob; + uint64_t uinteger64; + uint32_t uinteger; + int vlen; + + while (*sptr) { + if (*sptr != '%') { + sptr++; + continue; + } + if (sptr++ && *sptr == 0) + break; + + switch (*sptr) { + case 'i': + uinteger = va_arg(vl, uint32_t); + if (sqlite3_bind_int(ss, column++, uinteger) + != SQLITE_OK) + return -1; + break; + case 'd': + uinteger64 = va_arg(vl, uint64_t); + if (sqlite3_bind_int64(ss, column++, uinteger64) + != SQLITE_OK) + return -1; + break; + case 's': + str = va_arg(vl, const char *); + vlen = va_arg(vl, int); + if (sqlite3_bind_text(ss, column++, str, vlen, + SQLITE_STATIC) + != SQLITE_OK) + return -1; + break; + case 'b': + blob = va_arg(vl, void *); + vlen = va_arg(vl, int); + if (sqlite3_bind_blob(ss, column++, blob, vlen, + SQLITE_STATIC) + != SQLITE_OK) + return -1; + break; + case 'n': + if (sqlite3_bind_null(ss, column++) != SQLITE_OK) + return -1; + break; + default: + zlog_warn("%s: invalid format '%c'", __func__, *sptr); + return -1; + } + } + + return 0; +} + +/* + * Binds values using format to the database query. + * + * Might be used to bind variables to a query, insert or update. + */ +int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...) +{ + va_list vl; + int result; + + va_start(vl, fmt); + result = db_vbindf(ss, fmt, vl); + va_end(vl); + + return result; +} + +/* Prepares an statement to the database with the statement length. */ +struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen) +{ + struct sqlite3_stmt *ss; + int c; + + if (dbp == NULL) + return NULL; + + c = sqlite3_prepare_v2(dbp, stmt, stmtlen, &ss, NULL); + if (ss == NULL) { + zlog_warn("%s: failed to prepare (%d:%s)", __func__, c, + sqlite3_errmsg(dbp)); + return NULL; + } + + return ss; +} + +/* Prepares an statement to the database. */ +struct sqlite3_stmt *db_prepare(const char *stmt) +{ + return db_prepare_len(stmt, strlen(stmt)); +} + +/* Run a prepared statement. */ +int db_run(struct sqlite3_stmt *ss) +{ + int result; + + result = sqlite3_step(ss); + switch (result) { + case SQLITE_BUSY: + /* TODO handle busy database. */ + break; + + case SQLITE_OK: + /* + * SQLITE_DONE just causes confusion since it means the query went OK, + * but it has a different value. + */ + case SQLITE_DONE: + result = SQLITE_OK; + break; + + case SQLITE_ROW: + /* NOTHING */ + /* It is expected to receive SQLITE_ROW on search queries. */ + break; + + default: + zlog_warn("%s: step failed (%d:%s)", __func__, result, + sqlite3_errstr(result)); + } + + return result; +} + +/* Helper function to load format to variables. */ +static int db_vloadf(struct sqlite3_stmt *ss, const char *fmt, va_list vl) +{ + const char *sptr = fmt; + int column = 0; + const char **str; + void *blob; + const void *blobsrc; + uint64_t *uinteger64; + uint32_t *uinteger; + int vlen; + int dlen; + int columncount; + + columncount = sqlite3_column_count(ss); + if (columncount == 0) + return -1; + + while (*sptr) { + if (*sptr != '%') { + sptr++; + continue; + } + if (sptr++ && *sptr == 0) + break; + + switch (*sptr) { + case 'i': + uinteger = va_arg(vl, uint32_t *); + *uinteger = sqlite3_column_int(ss, column); + break; + case 'd': + uinteger64 = va_arg(vl, uint64_t *); + *uinteger64 = sqlite3_column_int64(ss, column); + break; + case 's': + str = va_arg(vl, const char **); + *str = (const char *)sqlite3_column_text(ss, column); + break; + case 'b': + blob = va_arg(vl, void *); + vlen = va_arg(vl, int); + dlen = sqlite3_column_bytes(ss, column); + blobsrc = sqlite3_column_blob(ss, column); + memcpy(blob, blobsrc, MIN(vlen, dlen)); + break; + default: + zlog_warn("%s: invalid format '%c'", __func__, *sptr); + return -1; + } + + column++; + } + + return 0; +} + +/* Function to load format from database row. */ +int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...) +{ + va_list vl; + int result; + + va_start(vl, fmt); + result = db_vloadf(ss, fmt, vl); + va_end(vl); + + return result; +} + +/* Finalize query and return memory. */ +void db_finalize(struct sqlite3_stmt **ss) +{ + sqlite3_finalize(*ss); + *ss = NULL; +} + +/* Execute one or more statements. */ +int db_execute(const char *stmt_fmt, ...) +{ + char stmt[BUFSIZ]; + va_list ap; + + if (dbp == NULL) + return -1; + + va_start(ap, stmt_fmt); + vsnprintf(stmt, sizeof(stmt), stmt_fmt, ap); + va_end(ap); + + if (sqlite3_exec(dbp, stmt, NULL, 0, NULL) != SQLITE_OK) { + zlog_warn("%s: failed to execute statement(s): %s", __func__, + sqlite3_errmsg(dbp)); + return -1; + } + + return 0; +} diff --git a/lib/db.h b/lib/db.h new file mode 100644 index 0000000..887af0e --- /dev/null +++ b/lib/db.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: ISC AND GPL-2.0-or-later +/* + * Copyright (c) 2018 Rafael Zalamena + */ + +/* + * Copyright (c) 2016 Rafael Zalamena + */ + +#ifndef _FRR_DB_H_ +#define _FRR_DB_H_ +#ifdef HAVE_SQLITE3 + +#include "compiler.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int db_init(const char *path_fmt, ...) PRINTFRR(1, 2); +extern int db_close(void); +/* WARNING: sqlite format string! not printf compatible! */ +extern int db_bindf(struct sqlite3_stmt *ss, const char *fmt, ...); +extern struct sqlite3_stmt *db_prepare_len(const char *stmt, int stmtlen); +extern struct sqlite3_stmt *db_prepare(const char *stmt); +extern int db_run(struct sqlite3_stmt *ss); +/* WARNING: sqlite format string! not scanf compatible! */ +extern int db_loadf(struct sqlite3_stmt *ss, const char *fmt, ...); +extern void db_finalize(struct sqlite3_stmt **ss); +extern int db_execute(const char *stmt_fmt, ...) PRINTFRR(1, 2); + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_SQLITE3 */ +#endif /* _FRR_DB_H_ */ diff --git a/lib/debug.c b/lib/debug.c new file mode 100644 index 0000000..757a47a --- /dev/null +++ b/lib/debug.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Debugging utilities. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#include +#include "typesafe.h" +#include "debug.h" +#include "command.h" + +static struct debug_cb_list_head cb_head; + +DECLARE_LIST(debug_cb_list, struct debug_callbacks, item); + +/* All code in this section should be reentrant and MT-safe */ + +DEFUN_NOSH(debug_all, debug_all_cmd, "[no] debug all", + NO_STR DEBUG_STR "Toggle all debugging output\n") +{ + struct debug_callbacks *cb; + + bool set = !strmatch(argv[0]->text, "no"); + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + frr_each (debug_cb_list, &cb_head, cb) + cb->debug_set_all(mode, set); + + return CMD_SUCCESS; +} + +/* ------------------------------------------------------------------------- */ + +void debug_init(struct debug_callbacks *cb) +{ + static bool inited = false; + + if (!inited) { + inited = true; + debug_cb_list_init(&cb_head); + } + + debug_cb_list_add_head(&cb_head, cb); +} + +void debug_init_cli(void) +{ + install_element(ENABLE_NODE, &debug_all_cmd); + install_element(CONFIG_NODE, &debug_all_cmd); +} diff --git a/lib/debug.h b/lib/debug.h new file mode 100644 index 0000000..e9d8a31 --- /dev/null +++ b/lib/debug.h @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Debugging utilities. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#ifndef _FRRDEBUG_H +#define _FRRDEBUG_H + +#include +#include "command.h" +#include "frratomic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Debugging modes. + * + * FRR's convention is that a debug statement issued under the vty CONFIG_NODE + * persists to the config file, whereas the same debug statement issued from + * the ENABLE_NODE only persists for the current session. These are mapped to + * DEBUG_MODE_CONF and DEBUG_MODE_TERM respectively. + * + * They are not mutually exclusive and are placed in the MSB of the flags + * field in a debugging record. + */ +#define DEBUG_MODE_TERM 0x01000000 +#define DEBUG_MODE_CONF 0x02000000 +#define DEBUG_MODE_ALL (DEBUG_MODE_TERM | DEBUG_MODE_CONF) +#define DEBUG_MODE_NONE 0x00000000 +#define DEBUG_OPT_ALL 0x00FFFFFF +#define DEBUG_OPT_NONE 0x00000000 + + +/* + * Debugging record. + * + * All operations on this record exposed in this header are MT-safe. + * + * flags + * A bitfield with the following format (bytes high to low) + * - [0] Debugging mode field (MSB) | Mode + * - [1] Arbitrary flag field | Option + * - [2] Arbitrary flag field | Option + * - [3] Arbitrary flag field (LSB) | Option + * + * ALL THESE BYTES ARE YOURS - EXCEPT MODE. + * ATTEMPT NO BIT OPS THERE. + * + * The MSB of this field determines the debug mode, Use the DEBUG_MODE* + * macros to manipulate this byte. + * + * The low 3 bytes of this field may be used to store arbitrary information. + * Usually they are used to store flags that tune how detailed the logging + * for a particular debug record is. Use the DEBUG_OPT* macros to manipulate + * those bytes. + * + * All operations performed on this field should be done using the macros + * later in this header file. They are guaranteed to be atomic operations + * with respect to this field. Using anything except the macros to + * manipulate the flags field in a multithreaded environment results in + * undefined behavior. + * + * desc + * Human-readable description of this debugging record. + */ +struct debug { + atomic_uint_fast32_t flags; + const char *desc; +}; + +PREDECL_LIST(debug_cb_list); +/* + * Callback set for debugging code. + * + * debug_set_all + * Function pointer to call when the user requests that all debugs have a + * mode set. + */ +struct debug_callbacks { + /* + * Linked list of Callbacks to call + */ + struct debug_cb_list_item item; + + /* + * flags + * flags to set on debug flag fields + * + * set + * true: set flags + * false: unset flags + */ + void (*debug_set_all)(uint32_t flags, bool set); +}; + +/* + * Check if a mode is set for a debug. + * + * MT-Safe + */ +#define DEBUG_MODE_CHECK(name, mode) \ + CHECK_FLAG_ATOMIC(&(name)->flags, (mode)&DEBUG_MODE_ALL) + +/* + * Check if an option bit is set for a debug. + * + * MT-Safe + */ +#define DEBUG_OPT_CHECK(name, opt) \ + CHECK_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL) + +/* + * Check if bits are set for a debug. + * + * MT-Safe + */ +#define DEBUG_FLAGS_CHECK(name, fl) CHECK_FLAG_ATOMIC(&(name)->flags, (fl)) + +/* + * Set modes on a debug. + * + * MT-Safe + */ +#define DEBUG_MODE_SET(name, mode, onoff) \ + do { \ + if (onoff) \ + SET_FLAG_ATOMIC(&(name)->flags, \ + (mode)&DEBUG_MODE_ALL); \ + else \ + UNSET_FLAG_ATOMIC(&(name)->flags, \ + (mode)&DEBUG_MODE_ALL); \ + } while (0) + +/* Convenience macros for specific set operations. */ +#define DEBUG_MODE_ON(name, mode) DEBUG_MODE_SET(name, mode, true) +#define DEBUG_MODE_OFF(name, mode) DEBUG_MODE_SET(name, mode, false) + +/* + * Set options on a debug. + * + * MT-Safe + */ +#define DEBUG_OPT_SET(name, opt, onoff) \ + do { \ + if (onoff) \ + SET_FLAG_ATOMIC(&(name)->flags, (opt)&DEBUG_OPT_ALL); \ + else \ + UNSET_FLAG_ATOMIC(&(name)->flags, \ + (opt)&DEBUG_OPT_ALL); \ + } while (0) + +/* Convenience macros for specific set operations. */ +#define DEBUG_OPT_ON(name, opt) DEBUG_OPT_SET(name, opt, true) +#define DEBUG_OPT_OFF(name, opt) DEBUG_OPT_SET(name, opt, true) + +/* + * Set bits on a debug. + * + * MT-Safe + */ +#define DEBUG_FLAGS_SET(name, fl, onoff) \ + do { \ + if (onoff) \ + SET_FLAG_ATOMIC(&(name)->flags, (fl)); \ + else \ + UNSET_FLAG_ATOMIC(&(name)->flags, (fl)); \ + } while (0) + +/* Convenience macros for specific set operations. */ +#define DEBUG_FLAGS_ON(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), true) +#define DEBUG_FLAGS_OFF(name, fl) DEBUG_FLAGS_SET(&(name)->flags, (type), false) + +/* + * Unset all modes and options on a debug. + * + * MT-Safe + */ +#define DEBUG_CLEAR(name) RESET_FLAG_ATOMIC(&(name)->flags) + +/* + * Set all modes and options on a debug. + * + * MT-Safe + */ +#define DEBUG_ON(name) \ + SET_FLAG_ATOMIC(&(name)->flags, DEBUG_MODE_ALL | DEBUG_OPT_ALL) + +/* + * Map a vty node to the correct debugging mode flags. FRR behaves such that a + * debug statement issued under the config node persists to the config file, + * whereas the same debug statement issued from the enable node only persists + * for the current session. + * + * MT-Safe + */ +#define DEBUG_NODE2MODE(vtynode) \ + (((vtynode) == CONFIG_NODE) ? DEBUG_MODE_ALL : DEBUG_MODE_TERM) + +/* + * Debug at the given level to the default logging destination. + * + * MT-Safe + */ +#define DEBUG(level, name, fmt, ...) \ + do { \ + if (DEBUG_MODE_CHECK(name, DEBUG_MODE_ALL)) \ + zlog_##level(fmt, ##__VA_ARGS__); \ + } while (0) + +/* Convenience macros for the various levels. */ +#define DEBUGE(name, fmt, ...) DEBUG(err, name, fmt, ##__VA_ARGS__) +#define DEBUGW(name, fmt, ...) DEBUG(warn, name, fmt, ##__VA_ARGS__) +#define DEBUGI(name, fmt, ...) DEBUG(info, name, fmt, ##__VA_ARGS__) +#define DEBUGN(name, fmt, ...) DEBUG(notice, name, fmt, ##__VA_ARGS__) +#define DEBUGD(name, fmt, ...) DEBUG(debug, name, fmt, ##__VA_ARGS__) + +/* + * Optional initializer for debugging. Highly recommended. + * + * This function installs common debugging commands and allows the caller to + * specify callbacks to take when these commands are issued, allowing the + * caller to respond to events such as a request to turn off all debugs. + * + * MT-Safe + */ +void debug_init(struct debug_callbacks *cb); + +/* + * Turn on the cli to turn on/off debugs. + * Should only be called by libfrr + */ +void debug_init_cli(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRRDEBUG_H */ diff --git a/lib/defaults.c b/lib/defaults.c new file mode 100644 index 0000000..04b5fd3 --- /dev/null +++ b/lib/defaults.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: ISC +/* + * FRR switchable defaults. + * Copyright (c) 2017-2019 David Lamparter, for NetDEF, Inc. + */ + +#include + +#include "defaults.h" +#include "lib/version.h" + +static char df_version[128] = FRR_VER_SHORT, df_profile[128] = DFLT_NAME; +static struct frr_default *dflt_first = NULL, **dflt_next = &dflt_first; + +/* these are global for all FRR daemons. they have to be, since we write an + * integrated config with the same value for all daemons. + */ +const char *frr_defaults_profiles[] = { + "traditional", + "datacenter", + NULL, +}; + +static int version_value(int ch) +{ + /* non-ASCII shouldn't happen */ + if (ch < 0 || ch >= 128) + return 2; + + /* ~foo sorts older than nothing */ + if (ch == '~') + return 0; + if (ch == '\0') + return 1; + if (isalpha(ch)) + return 0x100 + tolower(ch); + + /* punctuation and digits (and everything else) */ + return 0x200 + ch; +} + +int frr_version_cmp(const char *aa, const char *bb) +{ + const char *apos = aa, *bpos = bb; + + /* || is correct, we won't scan past the end of a string since that + * doesn't compare equal to anything else */ + while (apos[0] || bpos[0]) { + if (isdigit((unsigned char)apos[0]) && + isdigit((unsigned char)bpos[0])) { + unsigned long av, bv; + char *aend = NULL, *bend = NULL; + + av = strtoul(apos, &aend, 10); + bv = strtoul(bpos, &bend, 10); + if (av < bv) + return -1; + if (av > bv) + return 1; + + apos = aend; + bpos = bend; + continue; + } + + int a = version_value(*apos++); + int b = version_value(*bpos++); + + if (a < b) + return -1; + if (a > b) + return 1; + } + return 0; +} + +static void frr_default_apply_one(struct frr_default *dflt, bool check); + +void frr_default_add(struct frr_default *dflt) +{ + dflt->next = NULL; + *dflt_next = dflt; + dflt_next = &dflt->next; + + frr_default_apply_one(dflt, true); +} + +static bool frr_match_version(const char *name, const char *vspec, + const char *version, bool check) +{ + int cmp; + static const struct spec { + const char *str; + int dir, eq; + } specs[] = { + {"<=", -1, 1}, + {">=", 1, 1}, + {"==", 0, 1}, + {"<", -1, 0}, + {">", 1, 0}, + {"=", 0, 1}, + {NULL, 0, 0}, + }; + const struct spec *s; + + if (!vspec) + /* NULL = all versions */ + return true; + + for (s = specs; s->str; s++) + if (!strncmp(s->str, vspec, strlen(s->str))) + break; + if (!s->str) { + if (check) + fprintf(stderr, "invalid version specifier for %s: %s", + name, vspec); + /* invalid version spec, never matches */ + return false; + } + + vspec += strlen(s->str); + while (isspace((unsigned char)*vspec)) + vspec++; + + cmp = frr_version_cmp(version, vspec); + if (cmp == s->dir || (s->eq && cmp == 0)) + return true; + + return false; +} + +static void frr_default_apply_one(struct frr_default *dflt, bool check) +{ + struct frr_default_entry *entry = dflt->entries; + struct frr_default_entry *dfltentry = NULL, *saveentry = NULL; + + for (; entry->match_version || entry->match_profile; entry++) { + if (entry->match_profile + && strcmp(entry->match_profile, df_profile)) + continue; + + if (!dfltentry && frr_match_version(dflt->name, + entry->match_version, df_version, check)) + dfltentry = entry; + if (!saveentry && frr_match_version(dflt->name, + entry->match_version, FRR_VER_SHORT, check)) + saveentry = entry; + + if (dfltentry && saveentry && !check) + break; + } + /* found default or arrived at last entry that has NULL,NULL spec */ + + if (!dfltentry) + dfltentry = entry; + if (!saveentry) + saveentry = entry; + + if (dflt->dflt_bool) + *dflt->dflt_bool = dfltentry->val_bool; + if (dflt->dflt_str) + *dflt->dflt_str = dfltentry->val_str; + if (dflt->dflt_long) + *dflt->dflt_long = dfltentry->val_long; + if (dflt->dflt_ulong) + *dflt->dflt_ulong = dfltentry->val_ulong; + if (dflt->dflt_float) + *dflt->dflt_float = dfltentry->val_float; + if (dflt->save_bool) + *dflt->save_bool = saveentry->val_bool; + if (dflt->save_str) + *dflt->save_str = saveentry->val_str; + if (dflt->save_long) + *dflt->save_long = saveentry->val_long; + if (dflt->save_ulong) + *dflt->save_ulong = saveentry->val_ulong; + if (dflt->save_float) + *dflt->save_float = saveentry->val_float; +} + +void frr_defaults_apply(void) +{ + struct frr_default *dflt; + + for (dflt = dflt_first; dflt; dflt = dflt->next) + frr_default_apply_one(dflt, false); +} + +bool frr_defaults_profile_valid(const char *profile) +{ + const char **p; + + for (p = frr_defaults_profiles; *p; p++) + if (!strcmp(profile, *p)) + return true; + return false; +} + +const char *frr_defaults_version(void) +{ + return df_version; +} + +const char *frr_defaults_profile(void) +{ + return df_profile; +} + +void frr_defaults_version_set(const char *version) +{ + strlcpy(df_version, version, sizeof(df_version)); + frr_defaults_apply(); +} + +void frr_defaults_profile_set(const char *profile) +{ + strlcpy(df_profile, profile, sizeof(df_profile)); + frr_defaults_apply(); +} diff --git a/lib/defaults.h b/lib/defaults.h new file mode 100644 index 0000000..afb3223 --- /dev/null +++ b/lib/defaults.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: ISC +/* + * FRR switchable defaults. + * Copyright (C) 2017-2019 David Lamparter for NetDEF, Inc. + */ + +#ifndef _FRR_DEFAULTS_H +#define _FRR_DEFAULTS_H + +#include + +#include "compiler.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* frr_default wraps information about a default that has different + * values depending on FRR version or default-set + * + * frr_default_entry describes one match rule and the resulting value; + * entries are evaluated in order and the first matching is used. + * + * If both match_version and match_profile are specified, they must both + * match. A NULL value matches everything. + */ +struct frr_default_entry { + /* syntax: "(<|<=|==|>=|>) [whitespace] version", e.g. + * ">= 6.1-dev" "<6.0" + */ + const char *match_version; + /* exact profile string to compare against */ + const char *match_profile; + + /* value to use */ + bool val_bool; + const char *val_str; + long val_long; + unsigned long val_ulong; + float val_float; +}; + +/* one struct frr_default exists for each malleable default value */ +struct frr_default { + struct frr_default *next; + + /* for UI/debug use */ + const char *name; + + /* the following two sets of variables differ because the written + * config always targets the *current* FRR version + * + * e.g. if you load a config that has "frr version 5.0" on 6.0 + * *dflt_long => set to the default value in 5.0 + * *save_long => set to the default value in 6.0 + * config save will write "frr version 6.0" with 6.0 defaults + */ + + /* variable holding the default value for reading/use */ + bool *dflt_bool; + const char **dflt_str; + long *dflt_long; + unsigned long *dflt_ulong; + float *dflt_float; + + /* variable to use when comparing for config save */ + bool *save_bool; + const char **save_str; + long *save_long; + unsigned long *save_ulong; + float *save_float; + + struct frr_default_entry entries[]; +}; + +#define _FRR_CFG_DEFAULT(type, typname, varname, ...) \ + static type DFLT_##varname; \ + static type SAVE_##varname; \ + static struct frr_default _dflt_##varname = { \ + .name = #varname, \ + .dflt_##typname = &DFLT_##varname, \ + .save_##typname = &SAVE_##varname, \ + .entries = { __VA_ARGS__ }, \ + }; \ + static void _dfltinit_##varname(void) \ + __attribute__((_CONSTRUCTOR(1000))); \ + static void _dfltinit_##varname(void) \ + { \ + frr_default_add(&_dflt_##varname); \ + } \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +/* use: + * FRR_CFG_DEFAULT_LONG(SHARP_BLUNTNESS, + * { .val_long = 2, .match_version = ">= 10.0" }, + * { .val_long = 1, .match_profile = "datacenter" }, + * { .val_long = 0 }, + * ) + * + * This will create DFLT_SHARP_BLUNTNESS and SAVE_SHARP_BLUNTNESS variables. + * + * Note: preprocessor defines cannot be used as variable names because they + * will be expanded and blow up with a compile error. Use an enum or add an + * extra _ at the beginning (e.g. _SHARP_BLUNTNESS => DFLT__SHARP_BLUNTNESS) + */ +#define FRR_CFG_DEFAULT_BOOL(varname, ...) \ + _FRR_CFG_DEFAULT(bool, bool, varname, ## __VA_ARGS__) +#define FRR_CFG_DEFAULT_LONG(varname, ...) \ + _FRR_CFG_DEFAULT(long, long, varname, ## __VA_ARGS__) +#define FRR_CFG_DEFAULT_ULONG(varname, ...) \ + _FRR_CFG_DEFAULT(unsigned long, ulong, varname, ## __VA_ARGS__) +#define FRR_CFG_DEFAULT_FLOAT(varname, ...) \ + _FRR_CFG_DEFAULT(float, float, varname, ## __VA_ARGS__) +#define FRR_CFG_DEFAULT_STR(varname, ...) \ + _FRR_CFG_DEFAULT(const char *, str, varname, ## __VA_ARGS__) + + +/* daemons don't need to call any of these, libfrr handles that */ +extern void frr_default_add(struct frr_default *dflt); +extern void frr_defaults_version_set(const char *version); +extern void frr_defaults_profile_set(const char *profile); +extern const char *frr_defaults_version(void); +extern const char *frr_defaults_profile(void); +extern void frr_defaults_apply(void); + +extern const char *frr_defaults_profiles[]; +extern bool frr_defaults_profile_valid(const char *profile); + +/* like strcmp(), but with version ordering */ +extern int frr_version_cmp(const char *aa, const char *bb); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_DEFAULTS_H */ diff --git a/lib/defun_lex.l b/lib/defun_lex.l new file mode 100644 index 0000000..3104e48 --- /dev/null +++ b/lib/defun_lex.l @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * clippy (CLI preparator in python) C pseudo-lexer + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +/* This is just enough of a lexer to make rough sense of a C source file. + * It handles C preprocessor directives, strings, and looks for FRR-specific + * idioms (aka DEFUN). + * + * There is some preliminary support for documentation comments for DEFUNs. + * They would look like this (note the ~): (replace \ by /) + * + * \*~ documentation for foobar_cmd + * * parameter does xyz + * *\ + * DEFUN(foobar_cmd, ...) + * + * This is intended for user documentation / command reference. Don't put + * code documentation in it. + */ + +%top{ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +} +%{ +/* ignore harmless bugs in old versions of flex */ +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-value" + +#include "config.h" +#include +#include +#include + +#include "command_graph.h" +#include "clippy.h" + +#define ID 258 +#define PREPROC 259 +#define OPERATOR 260 +#define STRING 261 +#define COMMENT 262 +#define SPECIAL 263 + +#define DEFUNNY 270 +#define INSTALL 271 +#define AUXILIARY 272 + +int comment_link; +char string_end; + +char *value; +static const char *yyfilename; + +static void extendbuf(char **what, const char *arg) +{ + if (!*what) + *what = strdup(arg); + else { + size_t vall = strlen(*what), argl = strlen(arg); + *what = realloc(*what, vall + argl + 1); + memcpy(*what + vall, arg, argl); + (*what)[vall + argl] = '\0'; + } +} +#define extend(x) extendbuf(&value, x) + +#ifndef __clang_analyzer__ + +%} + +ID [A-Za-z0-9_]+ +OPERATOR [!%&/\[\]{}=?:^|\*.;><~'\\+-] +SPECIAL [(),] + +%pointer +%option yylineno +%option noyywrap +%option noinput +%option nounput +%option outfile="lib/defun_lex.c" +%option prefix="def_yy" +%option 8bit + +%s linestart +%x comment +%x linecomment +%x preproc +%x rstring +%% + BEGIN(linestart); + +\n BEGIN(linestart); + +"/*" comment_link = YY_START; extend(yytext); BEGIN(comment); +[^*\n]* extend(yytext); +"*"+[^*/\n]* extend(yytext); +\n extend(yytext); +"*"+"/" extend(yytext); BEGIN(comment_link); return COMMENT; + +"//" comment_link = YY_START; extend(yytext); BEGIN(linecomment); +[^\n]* extend(yytext); +\n BEGIN((comment_link == INITIAL) ? linestart : comment_link); return COMMENT; + +# BEGIN(preproc); +\n BEGIN(INITIAL); return PREPROC; +[^\n\\]+ extend(yytext); +\\\n extend(yytext); +\\+[^\n] extend(yytext); + +[\"\'] string_end = yytext[0]; extend(yytext); BEGIN(rstring); +[\"\'] { + extend(yytext); + if (yytext[0] == string_end) { + BEGIN(INITIAL); + return STRING; + } + } +\\\n /* ignore */ +\n { + fprintf(stderr, + "%s:%d: string continues past the end of the line\n", + yyfilename, yylineno); + free(value); + value = NULL; + BEGIN(INITIAL); + return STRING; + } +\\. extend(yytext); +[^\\\"\'\n]+ extend(yytext); + +"DEFUN" value = strdup(yytext); return DEFUNNY; +"DEFUN_NOSH" value = strdup(yytext); return DEFUNNY; +"DEFUN_HIDDEN" value = strdup(yytext); return DEFUNNY; +"DEFPY" value = strdup(yytext); return DEFUNNY; +"DEFPY_NOSH" value = strdup(yytext); return DEFUNNY; +"DEFPY_ATTR" value = strdup(yytext); return DEFUNNY; +"DEFPY_HIDDEN" value = strdup(yytext); return DEFUNNY; +"DEFPY_YANG" value = strdup(yytext); return DEFUNNY; +"DEFPY_YANG_HIDDEN" value = strdup(yytext); return DEFUNNY; +"DEFPY_YANG_NOSH" value = strdup(yytext); return DEFUNNY; +"ALIAS" value = strdup(yytext); return DEFUNNY; +"ALIAS_HIDDEN" value = strdup(yytext); return DEFUNNY; +"install_element" value = strdup(yytext); return INSTALL; +"VTYSH_TARGETS" value = strdup(yytext); return AUXILIARY; +"VTYSH_NODESWITCH" value = strdup(yytext); return AUXILIARY; + +[ \t\n]+ /* ignore */ +\\ /* ignore */ +{ID} BEGIN(INITIAL); value = strdup(yytext); return ID; +{OPERATOR} BEGIN(INITIAL); value = strdup(yytext); return OPERATOR; +{SPECIAL} BEGIN(INITIAL); value = strdup(yytext); return SPECIAL; +. /* printf("-- '%s' in init\n", yytext); */ BEGIN(INITIAL); return yytext[0]; + +%% + +#endif /* __clang_analyzer__ */ + +static int yylex_clr(char **retbuf) +{ + int rv = def_yylex(); + *retbuf = value; + value = NULL; + return rv; +} + +static PyObject *get_args(const char *filename, int lineno) +{ + PyObject *pyObj = PyList_New(0); + PyObject *pyArg = NULL; + + char *tval; + int depth = 1; + int token; + + while ((token = yylex_clr(&tval)) != YY_NULL) { + if (token == SPECIAL && tval[0] == '(') { + free(tval); + break; + } + if (token == COMMENT) { + free(tval); + continue; + } + fprintf(stderr, "invalid input!\n"); + exit(1); + } + + while ((token = yylex_clr(&tval)) != YY_NULL) { + if (token == COMMENT) { + free(tval); + continue; + } + if (token == PREPROC) { + free(tval); + Py_DECREF(pyObj); + return PyErr_Format(PyExc_ValueError, + "%s:%d: cannot process CPP directive within argument list", + filename, lineno); + } + if (token == SPECIAL) { + if (depth == 1 && (tval[0] == ',' || tval[0] == ')')) { + if (pyArg) + PyList_Append(pyObj, pyArg); + pyArg = NULL; + if (tval[0] == ')') { + free(tval); + break; + } + free(tval); + continue; + } + if (tval[0] == '(') + depth++; + if (tval[0] == ')') + depth--; + } + if (!tval) + return PyErr_Format(PyExc_ValueError, + "%s:%d: invalid token in DEFPY parameters", + filename, lineno); + if (!pyArg) + pyArg = PyList_New(0); + PyList_Append(pyArg, PyUnicode_FromString(tval)); + free(tval); + } + return pyObj; +} + +/* _clippy.parse() -- read a C file, returning a list of interesting bits. + * note this ditches most of the actual C code. */ +PyObject *clippy_parse(PyObject *self, PyObject *args) +{ + const char *filename; + if (!PyArg_ParseTuple(args, "s", &filename)) + return NULL; + + FILE *fd = fopen(filename, "r"); + if (!fd) + return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); + + char *tval; + int token; + yyin = fd; + value = NULL; + yyfilename = filename; + + PyObject *pyCont = PyDict_New(); + PyObject *pyObj = PyList_New(0); + PyDict_SetItemString(pyCont, "filename", PyUnicode_FromString(filename)); + PyDict_SetItemString(pyCont, "data", pyObj); + + while ((token = yylex_clr(&tval)) != YY_NULL) { + int lineno = yylineno; + PyObject *pyItem = NULL, *pyArgs; + switch (token) { + case DEFUNNY: + case INSTALL: + case AUXILIARY: + pyArgs = get_args(filename, lineno); + if (!pyArgs) { + free(tval); + Py_DECREF(pyCont); + yyfilename = NULL; + return NULL; + } + pyItem = PyDict_New(); + PyDict_SetItemString(pyItem, "type", PyUnicode_FromString(tval)); + PyDict_SetItemString(pyItem, "args", pyArgs); + break; + case COMMENT: + if (strncmp(tval, "//~", 3) && strncmp(tval, "/*~", 3)) + break; + pyItem = PyDict_New(); + PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("COMMENT")); + PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); + break; + case PREPROC: + pyItem = PyDict_New(); + PyDict_SetItemString(pyItem, "type", PyUnicode_FromString("PREPROC")); + PyDict_SetItemString(pyItem, "line", PyUnicode_FromString(tval)); + lineno--; + break; + } + if (pyItem) { + PyDict_SetItemString(pyItem, "lineno", PyLong_FromLong(lineno)); + PyList_Append(pyObj, pyItem); + } + free(tval); + } + def_yylex_destroy(); + fclose(fd); + yyfilename = NULL; + return pyCont; +} diff --git a/lib/distribute.c b/lib/distribute.c new file mode 100644 index 0000000..c0693b0 --- /dev/null +++ b/lib/distribute.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Distribute list functions + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "hash.h" +#include "if.h" +#include "filter.h" +#include "command.h" +#include "distribute.h" +#include "memory.h" + +DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_CTX, "Distribute ctx"); +DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE, "Distribute list"); +DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_IFNAME, "Dist-list ifname"); +DEFINE_MTYPE_STATIC(LIB, DISTRIBUTE_NAME, "Dist-list name"); + +static struct distribute *distribute_new(void) +{ + return XCALLOC(MTYPE_DISTRIBUTE, sizeof(struct distribute)); +} + +/* Free distribute object. */ +static void distribute_free(struct distribute *dist) +{ + int i = 0; + + XFREE(MTYPE_DISTRIBUTE_IFNAME, dist->ifname); + + for (i = 0; i < DISTRIBUTE_MAX; i++) { + XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[i]); + } + + for (i = 0; i < DISTRIBUTE_MAX; i++) { + XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[i]); + } + + XFREE(MTYPE_DISTRIBUTE, dist); +} + +static void distribute_free_if_empty(struct distribute_ctx *ctx, + struct distribute *dist) +{ + int i; + + for (i = 0; i < DISTRIBUTE_MAX; i++) + if (dist->list[i] != NULL || dist->prefix[i] != NULL) + return; + + hash_release(ctx->disthash, dist); + distribute_free(dist); +} + +/* Lookup interface's distribute list. */ +struct distribute *distribute_lookup(struct distribute_ctx *ctx, + const char *ifname) +{ + struct distribute key; + struct distribute *dist; + + /* temporary reference */ + key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL; + + dist = hash_lookup(ctx->disthash, &key); + + XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname); + + return dist; +} + +void distribute_list_add_hook(struct distribute_ctx *ctx, + void (*func)(struct distribute_ctx *ctx, + struct distribute *)) +{ + ctx->distribute_add_hook = func; +} + +void distribute_list_delete_hook(struct distribute_ctx *ctx, + void (*func)(struct distribute_ctx *ctx, + struct distribute *)) +{ + ctx->distribute_delete_hook = func; +} + +static void *distribute_hash_alloc(struct distribute *arg) +{ + struct distribute *dist; + + dist = distribute_new(); + if (arg->ifname) + dist->ifname = XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, arg->ifname); + else + dist->ifname = NULL; + return dist; +} + +/* Make new distribute list and push into hash. */ +static struct distribute *distribute_get(struct distribute_ctx *ctx, + const char *ifname) +{ + struct distribute key; + struct distribute *ret; + + /* temporary reference */ + key.ifname = (ifname) ? XSTRDUP(MTYPE_DISTRIBUTE_IFNAME, ifname) : NULL; + + ret = hash_get(ctx->disthash, &key, + (void *(*)(void *))distribute_hash_alloc); + + XFREE(MTYPE_DISTRIBUTE_IFNAME, key.ifname); + + return ret; +} + +static unsigned int distribute_hash_make(const void *arg) +{ + const struct distribute *dist = arg; + + return dist->ifname ? string_hash_make(dist->ifname) : 0; +} + +/* If two distribute-list have same value then return 1 else return + 0. This function is used by hash package. */ +static bool distribute_cmp(const struct distribute *dist1, + const struct distribute *dist2) +{ + if (dist1->ifname && dist2->ifname) + if (strcmp(dist1->ifname, dist2->ifname) == 0) + return true; + if (!dist1->ifname && !dist2->ifname) + return true; + return false; +} + +/* Set access-list name to the distribute list. */ +static void distribute_list_set(struct distribute_ctx *ctx, + const char *ifname, enum distribute_type type, + const char *alist_name) +{ + struct distribute *dist; + + dist = distribute_get(ctx, ifname); + + XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]); + dist->list[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, alist_name); + + /* Apply this distribute-list to the interface. */ + (ctx->distribute_add_hook)(ctx, dist); +} + +/* Unset distribute-list. If matched distribute-list exist then + return 1. */ +static int distribute_list_unset(struct distribute_ctx *ctx, + const char *ifname, + enum distribute_type type, + const char *alist_name) +{ + struct distribute *dist; + + dist = distribute_lookup(ctx, ifname); + if (!dist) + return 0; + + if (!dist->list[type]) + return 0; + if (strcmp(dist->list[type], alist_name) != 0) + return 0; + + XFREE(MTYPE_DISTRIBUTE_NAME, dist->list[type]); + + /* Apply this distribute-list to the interface. */ + (ctx->distribute_delete_hook)(ctx, dist); + + /* If all dist are NULL, then free distribute list. */ + distribute_free_if_empty(ctx, dist); + return 1; +} + +/* Set access-list name to the distribute list. */ +static void distribute_list_prefix_set(struct distribute_ctx *ctx, + const char *ifname, + enum distribute_type type, + const char *plist_name) +{ + struct distribute *dist; + + dist = distribute_get(ctx, ifname); + + XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]); + dist->prefix[type] = XSTRDUP(MTYPE_DISTRIBUTE_NAME, plist_name); + + /* Apply this distribute-list to the interface. */ + (ctx->distribute_add_hook)(ctx, dist); +} + +/* Unset distribute-list. If matched distribute-list exist then + return 1. */ +static int distribute_list_prefix_unset(struct distribute_ctx *ctx, + const char *ifname, + enum distribute_type type, + const char *plist_name) +{ + struct distribute *dist; + + dist = distribute_lookup(ctx, ifname); + if (!dist) + return 0; + + if (!dist->prefix[type]) + return 0; + if (strcmp(dist->prefix[type], plist_name) != 0) + return 0; + + XFREE(MTYPE_DISTRIBUTE_NAME, dist->prefix[type]); + + /* Apply this distribute-list to the interface. */ + (ctx->distribute_delete_hook)(ctx, dist); + + /* If all dist are NULL, then free distribute list. */ + distribute_free_if_empty(ctx, dist); + return 1; +} + +static enum distribute_type distribute_direction(const char *dir, bool v4) +{ + if (dir[0] == 'i') { + if (v4) + return DISTRIBUTE_V4_IN; + else + return DISTRIBUTE_V6_IN; + } else if (dir[0] == 'o') { + if (v4) + return DISTRIBUTE_V4_OUT; + else + return DISTRIBUTE_V6_OUT; + } + + assert(!"Expecting in or out only, fix your code"); + + __builtin_unreachable(); +} + +int distribute_list_parser(struct distribute_ctx *ctx, bool prefix, bool v4, + const char *dir, const char *list, const char *ifname) +{ + enum distribute_type type = distribute_direction(dir, v4); + + void (*distfn)(struct distribute_ctx *, const char *, + enum distribute_type, const char *) = + prefix ? &distribute_list_prefix_set : &distribute_list_set; + + distfn(ctx, ifname, type, list); + + return CMD_SUCCESS; +} + + +int distribute_list_no_parser(struct distribute_ctx *ctx, struct vty *vty, + bool prefix, bool v4, const char *dir, + const char *list, const char *ifname) +{ + enum distribute_type type = distribute_direction(dir, v4); + int ret; + + int (*distfn)(struct distribute_ctx *, const char *, + enum distribute_type, const char *) = + prefix ? &distribute_list_prefix_unset : &distribute_list_unset; + + + ret = distfn(ctx, ifname, type, list); + if (!ret) { + if (vty) + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +static int distribute_print(struct vty *vty, char *tab[], int is_prefix, + enum distribute_type type, int has_print) +{ + if (tab[type]) { + vty_out(vty, "%s %s%s", has_print ? "," : "", + is_prefix ? "(prefix-list) " : "", tab[type]); + return 1; + } + return has_print; +} + +int config_show_distribute(struct vty *vty, struct distribute_ctx *dist_ctxt) +{ + unsigned int i; + int has_print = 0; + struct hash_bucket *mp; + struct distribute *dist; + + /* Output filter configuration. */ + dist = distribute_lookup(dist_ctxt, NULL); + vty_out(vty, " Outgoing update filter list for all interface is"); + has_print = 0; + if (dist) { + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V4_OUT, has_print); + has_print = distribute_print(vty, dist->prefix, 1, + DISTRIBUTE_V4_OUT, has_print); + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V6_OUT, has_print); + has_print = distribute_print(vty, dist->prefix, 1, + DISTRIBUTE_V6_OUT, has_print); + } + if (has_print) + vty_out(vty, "\n"); + else + vty_out(vty, " not set\n"); + + for (i = 0; i < dist_ctxt->disthash->size; i++) + for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { + dist = mp->data; + if (dist->ifname) { + vty_out(vty, " %s filtered by", + dist->ifname); + has_print = 0; + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V4_OUT, + has_print); + has_print = distribute_print( + vty, dist->prefix, 1, DISTRIBUTE_V4_OUT, + has_print); + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V6_OUT, + has_print); + has_print = distribute_print( + vty, dist->prefix, 1, DISTRIBUTE_V6_OUT, + has_print); + if (has_print) + vty_out(vty, "\n"); + else + vty_out(vty, " nothing\n"); + } + } + + + /* Input filter configuration. */ + dist = distribute_lookup(dist_ctxt, NULL); + vty_out(vty, " Incoming update filter list for all interface is"); + has_print = 0; + if (dist) { + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V4_IN, has_print); + has_print = distribute_print(vty, dist->prefix, 1, + DISTRIBUTE_V4_IN, has_print); + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V6_IN, has_print); + has_print = distribute_print(vty, dist->prefix, 1, + DISTRIBUTE_V6_IN, has_print); + } + if (has_print) + vty_out(vty, "\n"); + else + vty_out(vty, " not set\n"); + + for (i = 0; i < dist_ctxt->disthash->size; i++) + for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { + dist = mp->data; + if (dist->ifname) { + vty_out(vty, " %s filtered by", + dist->ifname); + has_print = 0; + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V4_IN, + has_print); + has_print = distribute_print( + vty, dist->prefix, 1, DISTRIBUTE_V4_IN, + has_print); + has_print = distribute_print(vty, dist->list, 0, + DISTRIBUTE_V6_IN, + has_print); + has_print = distribute_print( + vty, dist->prefix, 1, DISTRIBUTE_V6_IN, + has_print); + if (has_print) + vty_out(vty, "\n"); + else + vty_out(vty, " nothing\n"); + } + } + return 0; +} + +/* Configuration write function. */ +int config_write_distribute(struct vty *vty, + struct distribute_ctx *dist_ctxt) +{ + unsigned int i; + int j; + int output, v6; + struct hash_bucket *mp; + int write = 0; + + for (i = 0; i < dist_ctxt->disthash->size; i++) + for (mp = dist_ctxt->disthash->index[i]; mp; mp = mp->next) { + struct distribute *dist; + + dist = mp->data; + + for (j = 0; j < DISTRIBUTE_MAX; j++) + if (dist->list[j]) { + output = j == DISTRIBUTE_V4_OUT + || j == DISTRIBUTE_V6_OUT; + v6 = j == DISTRIBUTE_V6_IN + || j == DISTRIBUTE_V6_OUT; + vty_out(vty, + " %sdistribute-list %s %s %s\n", + v6 ? "ipv6 " : "", + dist->list[j], + output ? "out" : "in", + dist->ifname ? dist->ifname + : ""); + write++; + } + + for (j = 0; j < DISTRIBUTE_MAX; j++) + if (dist->prefix[j]) { + output = j == DISTRIBUTE_V4_OUT + || j == DISTRIBUTE_V6_OUT; + v6 = j == DISTRIBUTE_V6_IN + || j == DISTRIBUTE_V6_OUT; + vty_out(vty, + " %sdistribute-list prefix %s %s %s\n", + v6 ? "ipv6 " : "", + dist->prefix[j], + output ? "out" : "in", + dist->ifname ? dist->ifname + : ""); + write++; + } + } + return write; +} + +/* ---------- */ +/* Northbound */ +/* ---------- */ + +int group_distribute_list_create_helper( + struct nb_cb_create_args *args, struct distribute_ctx *ctx) +{ + nb_running_set_entry(args->dnode, ctx); + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/distribute-lists/distribute-list/{in,out}/{access,prefix}-list + */ + +static int distribute_list_leaf_update(const struct lyd_node *dnode, + int ip_version, bool no); + +int group_distribute_list_destroy(struct nb_cb_destroy_args *args) +{ + struct lyd_node *dnode; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* + * We don't keep the IP version of distribute-list anywhere, so we're + * trying to remove both. If one doesn't exist, it's simply skipped by + * the remove function. + */ + + dnode = yang_dnode_get(args->dnode, "in/access-list"); + if (dnode) { + distribute_list_leaf_update(dnode, 4, true); + distribute_list_leaf_update(dnode, 6, true); + } + dnode = yang_dnode_get(args->dnode, "in/prefix-list"); + if (dnode) { + distribute_list_leaf_update(dnode, 4, true); + distribute_list_leaf_update(dnode, 6, true); + } + dnode = yang_dnode_get(args->dnode, "out/access-list"); + if (dnode) { + distribute_list_leaf_update(dnode, 4, true); + distribute_list_leaf_update(dnode, 6, true); + } + dnode = yang_dnode_get(args->dnode, "out/prefix-list"); + if (dnode) { + distribute_list_leaf_update(dnode, 4, true); + distribute_list_leaf_update(dnode, 6, true); + } + + nb_running_unset_entry(args->dnode); + return NB_OK; +} + +static int distribute_list_leaf_update(const struct lyd_node *dnode, + int ip_version, bool no) +{ + struct distribute_ctx *ctx; + struct lyd_node *dir_node = lyd_parent(dnode); + struct lyd_node_inner *list_node = dir_node->parent; + struct lyd_node *intf_key = list_node->child; + bool ipv4 = ip_version == 4 ? true : false; + bool prefix; + + ctx = nb_running_get_entry_non_rec(&list_node->node, NULL, false); + + prefix = dnode->schema->name[0] == 'p' ? true : false; + if (no) + distribute_list_no_parser(ctx, NULL, prefix, ipv4, + dir_node->schema->name, + lyd_get_value(dnode), + lyd_get_value(intf_key)); + else + distribute_list_parser(ctx, prefix, ipv4, + dir_node->schema->name, + lyd_get_value(dnode), + lyd_get_value(intf_key)); + return NB_OK; +} + +static int distribute_list_leaf_modify(struct nb_cb_modify_args *args, + int ip_version) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + return distribute_list_leaf_update(args->dnode, ip_version, false); +} + +static int distribute_list_leaf_destroy(struct nb_cb_destroy_args *args, + int ip_version) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + return distribute_list_leaf_update(args->dnode, ip_version, true); +} + +int group_distribute_list_ipv4_modify(struct nb_cb_modify_args *args) +{ + return distribute_list_leaf_modify(args, 4); +} +int group_distribute_list_ipv4_destroy(struct nb_cb_destroy_args *args) +{ + return distribute_list_leaf_destroy(args, 4); +} +int group_distribute_list_ipv6_modify(struct nb_cb_modify_args *args) +{ + return distribute_list_leaf_modify(args, 6); +} +int group_distribute_list_ipv6_destroy(struct nb_cb_destroy_args *args) +{ + return distribute_list_leaf_destroy(args, 6); +} + +static int distribute_list_leaf_cli_show(struct vty *vty, + const struct lyd_node *dnode, + int ip_version) +{ + struct lyd_node *dir_node = lyd_parent(dnode); + struct lyd_node_inner *list_node = dir_node->parent; + struct lyd_node *intf_key = list_node->child; + bool ipv6 = ip_version == 6 ? true : false; + bool prefix; + + prefix = dnode->schema->name[0] == 'p' ? true : false; + vty_out(vty, + " %sdistribute-list %s%s %s %s\n", + ipv6 ? "ipv6 " : "", + prefix ? "prefix " : "", + lyd_get_value(dnode), + dir_node->schema->name, + lyd_get_value(intf_key)); + + return NB_OK; +} + +void group_distribute_list_ipv4_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + distribute_list_leaf_cli_show(vty, dnode, 4); +} +void group_distribute_list_ipv6_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + distribute_list_leaf_cli_show(vty, dnode, 6); +} + +/* ------------- */ +/* Setup/Cleanup */ +/* ------------- */ + +void distribute_list_delete(struct distribute_ctx **ctx) +{ + hash_clean_and_free(&(*ctx)->disthash, + (void (*)(void *))distribute_free); + + XFREE(MTYPE_DISTRIBUTE_CTX, (*ctx)); +} + +/* Initialize distribute list container */ +struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf) +{ + struct distribute_ctx *ctx; + + ctx = XCALLOC(MTYPE_DISTRIBUTE_CTX, sizeof(struct distribute_ctx)); + ctx->vrf = vrf; + ctx->disthash = + hash_create(distribute_hash_make, + (bool (*)(const void *, const void *))distribute_cmp, + NULL); + return ctx; +} diff --git a/lib/distribute.h b/lib/distribute.h new file mode 100644 index 0000000..a0bc348 --- /dev/null +++ b/lib/distribute.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Distribute list functions header + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_DISTRIBUTE_H +#define _ZEBRA_DISTRIBUTE_H + +#include +#include "if.h" +#include "filter.h" +#include "northbound.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Distribute list types. */ +enum distribute_type { + DISTRIBUTE_V4_IN, + DISTRIBUTE_V6_IN, + DISTRIBUTE_V4_OUT, + DISTRIBUTE_V6_OUT, + DISTRIBUTE_MAX +}; + +struct distribute { + /* Name of the interface. */ + char *ifname; + + /* Filter name of `in' and `out' */ + char *list[DISTRIBUTE_MAX]; + + /* prefix-list name of `in' and `out' */ + char *prefix[DISTRIBUTE_MAX]; +}; + +struct distribute_ctx { + /* Hash of distribute list. */ + struct hash *disthash; + + /* Hook functions. */ + void (*distribute_add_hook)(struct distribute_ctx *ctx, + struct distribute *dist); + void (*distribute_delete_hook)(struct distribute_ctx *ctx, + struct distribute *dist); + + /* vrf information */ + struct vrf *vrf; +}; + +/* Prototypes for distribute-list. */ +extern struct distribute_ctx *distribute_list_ctx_create(struct vrf *vrf); +extern void distribute_list_delete(struct distribute_ctx **ctx); +extern void distribute_list_add_hook(struct distribute_ctx *ctx, + void (*)(struct distribute_ctx *ctx, + struct distribute *)); +extern void distribute_list_delete_hook(struct distribute_ctx *ctx, + void (*)(struct distribute_ctx *ctx, + struct distribute *)); +extern struct distribute *distribute_lookup(struct distribute_ctx *ctx, + const char *ifname); +extern int config_write_distribute(struct vty *vty, + struct distribute_ctx *ctx); +extern int config_show_distribute(struct vty *vty, + struct distribute_ctx *ctx); + +extern enum filter_type distribute_apply_in(struct interface *, + struct prefix *); +extern enum filter_type distribute_apply_out(struct interface *, + struct prefix *); + +extern int distribute_list_parser(struct distribute_ctx *ctx, bool prefix, + bool v4, const char *dir, const char *list, + const char *ifname); +extern int distribute_list_no_parser(struct distribute_ctx *ctx, + struct vty *vty, bool prefix, bool v4, + const char *dir, const char *list, + const char *ifname); + +/* + * Northbound + */ + +/* + * Define your own create callback and then call thes helper with your + * distribute list context when a list entry is created. Additionally, plug the + * destroy callback into the frr_module_yang_info struct, or call it if you have + * your own callback destroy function. + */ +extern int group_distribute_list_create_helper(struct nb_cb_create_args *args, + struct distribute_ctx *ctx); +extern int group_distribute_list_destroy(struct nb_cb_destroy_args *args); + +/* + * Plug 3 of these handlers in for your distribute-list for all the northbound + * distribute_list leaf callbacks. If you need multi-protocol then use the + * grouping twice under 2 different containers. + */ +extern int group_distribute_list_ipv4_modify(struct nb_cb_modify_args *args); +extern int group_distribute_list_ipv4_destroy(struct nb_cb_destroy_args *args); +extern void group_distribute_list_ipv4_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern int group_distribute_list_ipv6_modify(struct nb_cb_modify_args *args); +extern int group_distribute_list_ipv6_destroy(struct nb_cb_destroy_args *args); +extern void group_distribute_list_ipv6_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_DISTRIBUTE_H */ diff --git a/lib/elf_py.c b/lib/elf_py.c new file mode 100644 index 0000000..2b4fea3 --- /dev/null +++ b/lib/elf_py.c @@ -0,0 +1,1384 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * fast ELF file accessor + * Copyright (C) 2018-2020 David Lamparter for NetDEF, Inc. + */ + +/* Note: this wrapper is intended to be used as build-time helper. While + * it should be generally correct and proper, there may be the occasional + * memory leak or SEGV for things that haven't been well-tested. + * _ + * / \ This code is NOT SUITABLE FOR UNTRUSTED ELF FILES. It's used + * / ! \ in FRR to read files created by its own build. Don't take it out + * /_____\ of FRR and use it to parse random ELF files you found somewhere. + * + * If you're working with this code (or even reading it), you really need to + * read a bunch of the ELF specs. There's no way around it, things in here + * just represent pieces of ELF pretty much 1:1. Also, readelf & objdump are + * your friends. + * + * Required reading: + * https://refspecs.linuxfoundation.org/elf/elf.pdf + * https://refspecs.linuxfoundation.org/elf/x86_64-SysV-psABI.pdf + * Recommended reading: + * https://github.com/ARM-software/abi-aa/releases/download/2020Q4/aaelf64.pdf + * + * The core ELF spec is *not* enough, you should read at least one of the + * processor specific (psABI) docs. They define what & how relocations work. + * Luckily we don't need to care about the processor specifics since this only + * does data relocations, but without looking at the psABI, some things aren't + * quite clear. + */ + +/* the API of this module roughly follows a very small subset of the one + * provided by the python elfutils package, which unfortunately is painfully + * slow. + */ + +#define PY_SSIZE_T_CLEAN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include "structmember.h" +#include +#include +#include +#include +#include +#include +#include + +#if defined(__sun__) && (__SIZEOF_POINTER__ == 4) +/* Solaris libelf bails otherwise ... */ +#undef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 32 +#endif + +#include +#include +#include + +#include "typesafe.h" +#include "jhash.h" +#include "clippy.h" + +static bool debug; + +#define debugf(...) \ + do { \ + if (debug) \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) + +/* Exceptions */ +static PyObject *ELFFormatError; +static PyObject *ELFAccessError; + +/* most objects can only be created as return values from one of the methods */ +static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_ValueError, + "cannot create instances of this type"); + return NULL; +} + +struct elfreloc; +struct elfsect; + +PREDECL_HASH(elfrelocs); + +/* ELFFile and ELFSection intentionally share some behaviour, particularly + * subscript[123:456] access to file data. This is because relocatables + * (.o files) do things section-based, but linked executables/libraries do + * things file-based. Having the two behave similar allows simplifying the + * Python code. + */ + +/* class ELFFile: + * + * overall entry point, instantiated by reading in an ELF file + */ +struct elffile { + PyObject_HEAD + + char *filename; + char *mmap, *mmend; + size_t len; + Elf *elf; + + /* note from here on there are several instances of + * + * GElf_Something *x, _x; + * + * this is a pattern used by libelf's generic ELF routines; the _x + * field is used to create a copy of the ELF structure from the file + * with 32/64bit and endianness adjusted. + */ + + GElf_Ehdr *ehdr, _ehdr; + Elf_Scn *symtab; + size_t nsym, symstridx; + Elf_Data *symdata; + + PyObject **sects; + size_t n_sect; + + struct elfrelocs_head dynrelocs; + + int elfclass; + bool bigendian; + bool has_symbols; +}; + +/* class ELFSection: + * + * note that executables and shared libraries can have their section headers + * removed, though in practice this is only used as an obfuscation technique. + */ +struct elfsect { + PyObject_HEAD + + const char *name; + struct elffile *ef; + + GElf_Shdr _shdr, *shdr; + Elf_Scn *scn; + unsigned long idx, len; + + struct elfrelocs_head relocs; +}; + +/* class ELFReloc: + * + * note: relocations in object files (.o) are section-based while relocations + * in executables and shared libraries are file-based. + * + * Whenever accessing something that is a pointer in the ELF file, the Python + * code needs to check for a relocation; if the pointer is pointing to some + * unresolved symbol the file will generally contain 0 bytes. The relocation + * will tell what the pointer is actually pointing to. + * + * This represents both static (.o file) and dynamic (.so/exec) relocations. + */ +struct elfreloc { + PyObject_HEAD + + struct elfrelocs_item elfrelocs_item; + + struct elfsect *es; + struct elffile *ef; + + /* there's also old-fashioned GElf_Rel; we're converting that to + * GElf_Rela in elfsect_add_relocations() + */ + GElf_Rela _rela, *rela; + GElf_Sym _sym, *sym; + size_t symidx; + const char *symname; + + /* documented below in python docstrings */ + bool symvalid, unresolved, relative; + unsigned long long st_value; +}; + +static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b); +static uint32_t elfreloc_hash(const struct elfreloc *reloc); + +DECLARE_HASH(elfrelocs, struct elfreloc, elfrelocs_item, + elfreloc_cmp, elfreloc_hash); + +static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx); +static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx); +static PyObject *elfreloc_getsection(PyObject *self, PyObject *args); +static PyObject *elfreloc_getaddend(PyObject *obj, void *closure); + +/* --- end of declarations -------------------------------------------------- */ + +/* + * class ELFReloc: + */ + +static const char elfreloc_doc[] = + "Represents an ELF relocation record\n" + "\n" + "(struct elfreloc * in elf_py.c)"; + +#define member(name, type, doc) \ + { \ + (char *)#name, type, offsetof(struct elfreloc, name), READONLY,\ + (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \ + } +static PyMemberDef members_elfreloc[] = { + member(symname, T_STRING, + "Name of symbol this relocation refers to.\n" + "\n" + "Will frequently be `None` in executables and shared libraries." + ), + member(symvalid, T_BOOL, + "Target symbol has a valid type, i.e. not STT_NOTYPE"), + member(unresolved, T_BOOL, + "Target symbol refers to an existing section"), + member(relative, T_BOOL, + "Relocation is a REL (not RELA) record and thus relative."), + member(st_value, T_ULONGLONG, + "Target symbol's value, if known\n\n" + "Will be zero for unresolved/external symbols."), + {} +}; +#undef member + +static PyGetSetDef getset_elfreloc[] = { + { .name = (char *)"r_addend", .get = elfreloc_getaddend, .doc = + (char *)"Relocation addend value"}, + {} +}; + +static PyMethodDef methods_elfreloc[] = { + {"getsection", elfreloc_getsection, METH_VARARGS, + "Find relocation target's ELF section\n\n" + "Args: address of relocatee (TODO: fix/remove?)\n" + "Returns: ELFSection or None\n\n" + "Not possible if section headers have been stripped."}, + {} +}; + +static int elfreloc_cmp(const struct elfreloc *a, const struct elfreloc *b) +{ + if (a->rela->r_offset < b->rela->r_offset) + return -1; + if (a->rela->r_offset > b->rela->r_offset) + return 1; + return 0; +} + +static uint32_t elfreloc_hash(const struct elfreloc *reloc) +{ + return jhash(&reloc->rela->r_offset, sizeof(reloc->rela->r_offset), + 0xc9a2b7f4); +} + +static struct elfreloc *elfrelocs_get(struct elfrelocs_head *head, + GElf_Addr offset) +{ + struct elfreloc dummy; + + dummy.rela = &dummy._rela; + dummy.rela->r_offset = offset; + return elfrelocs_find(head, &dummy); +} + +static PyObject *elfreloc_getsection(PyObject *self, PyObject *args) +{ + struct elfreloc *w = (struct elfreloc *)self; + long data; + + if (!PyArg_ParseTuple(args, "k", &data)) + return NULL; + + if (!w->es) + Py_RETURN_NONE; + + if (!w->symvalid || w->symidx == 0) { + size_t idx = 0; + Elf_Scn *scn; + + data = (w->relative ? data : 0) + w->rela->r_addend; + scn = elf_find_addr(w->es->ef, data, &idx); + if (!scn) + Py_RETURN_NONE; + return elffile_secbyidx(w->es->ef, scn, idx); + } + return elffile_secbyidx(w->es->ef, NULL, w->sym->st_shndx); +} + +static PyObject *elfreloc_getaddend(PyObject *obj, void *closure) +{ + struct elfreloc *w = (struct elfreloc *)obj; + + return Py_BuildValue("K", (unsigned long long)w->rela->r_addend); +} + +static PyObject *elfreloc_repr(PyObject *arg) +{ + struct elfreloc *w = (struct elfreloc *)arg; + + return PyUnicode_FromFormat("", + (unsigned long)w->rela->r_offset, + (w->symname && w->symname[0]) ? w->symname + : "[0]", + (unsigned long)w->rela->r_addend); +} + +static void elfreloc_free(void *arg) +{ + struct elfreloc *w = arg; + + (void)w; +} + +static PyTypeObject typeobj_elfreloc = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFReloc", + .tp_basicsize = sizeof(struct elfreloc), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = elfreloc_doc, + .tp_new = refuse_new, + .tp_free = elfreloc_free, + .tp_repr = elfreloc_repr, + .tp_members = members_elfreloc, + .tp_methods = methods_elfreloc, + .tp_getset = getset_elfreloc, +}; + +/* + * class ELFSection: + */ + +static const char elfsect_doc[] = + "Represents an ELF section\n" + "\n" + "To access section contents, use subscript notation, e.g.\n" + " section[123:456]\n" + "To read null terminated C strings, replace the end with str:\n" + " section[123:str]\n\n" + "(struct elfsect * in elf_py.c)"; + +static PyObject *elfsect_getaddr(PyObject *self, void *closure); + +#define member(name, type, doc) \ + { \ + (char *)#name, type, offsetof(struct elfsect, name), READONLY, \ + (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \ + } +static PyMemberDef members_elfsect[] = { + member(name, T_STRING, + "Section name, e.g. \".text\""), + member(idx, T_ULONG, + "Section index in file"), + member(len, T_ULONG, + "Section length in bytes"), + {}, +}; +#undef member + +static PyGetSetDef getset_elfsect[] = { + { .name = (char *)"sh_addr", .get = elfsect_getaddr, .doc = + (char *)"Section virtual address (mapped program view)"}, + {} +}; + +static PyObject *elfsect_getaddr(PyObject *self, void *closure) +{ + struct elfsect *w = (struct elfsect *)self; + + return Py_BuildValue("K", (unsigned long long)w->shdr->sh_addr); +} + + +static PyObject *elfsect_getreloc(PyObject *self, PyObject *args) +{ + struct elfsect *w = (struct elfsect *)self; + struct elfreloc *relw; + unsigned long offs; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "k", &offs)) + return NULL; + + relw = elfrelocs_get(&w->relocs, offs + w->shdr->sh_addr); + if (!relw) + Py_RETURN_NONE; + + ret = (PyObject *)relw; + Py_INCREF(ret); + return ret; +} + +static PyMethodDef methods_elfsect[] = { + {"getreloc", elfsect_getreloc, METH_VARARGS, + "Check for / get relocation at offset into section\n\n" + "Args: byte offset into section to check\n" + "Returns: ELFReloc or None"}, + {} +}; + +static PyObject *elfsect_subscript(PyObject *self, PyObject *key) +{ + Py_ssize_t start, stop, step, sllen; + struct elfsect *w = (struct elfsect *)self; + PySliceObject *slice; + unsigned long offs, len = ~0UL; + + if (!PySlice_Check(key)) { + PyErr_SetString(PyExc_IndexError, + "ELFSection subscript must be slice"); + return NULL; + } + slice = (PySliceObject *)key; + if (PyLong_Check(slice->stop)) { + if (PySlice_GetIndicesEx(key, w->shdr->sh_size, + &start, &stop, &step, &sllen)) + return NULL; + + if (step != 1) { + PyErr_SetString(PyExc_IndexError, + "ELFSection subscript slice step must be 1"); + return NULL; + } + if ((GElf_Xword)stop > w->shdr->sh_size) { + PyErr_Format(ELFAccessError, + "access (%lu) beyond end of section %lu/%s (%lu)", + stop, w->idx, w->name, w->shdr->sh_size); + return NULL; + } + + offs = start; + len = sllen; + } else { + if (slice->stop != (void *)&PyUnicode_Type + || !PyLong_Check(slice->start)) { + PyErr_SetString(PyExc_IndexError, "invalid slice"); + return NULL; + } + + offs = PyLong_AsUnsignedLongLong(slice->start); + len = ~0UL; + } + + offs += w->shdr->sh_offset; + if (offs > w->ef->len) { + PyErr_Format(ELFAccessError, + "access (%lu) beyond end of file (%lu)", + offs, w->ef->len); + return NULL; + } + if (len == ~0UL) + len = strnlen(w->ef->mmap + offs, w->ef->len - offs); + + Py_ssize_t pylen = len; + +#if PY_MAJOR_VERSION >= 3 + return Py_BuildValue("y#", w->ef->mmap + offs, pylen); +#else + return Py_BuildValue("s#", w->ef->mmap + offs, pylen); +#endif +} + +static PyMappingMethods mp_elfsect = { + .mp_subscript = elfsect_subscript, +}; + +static void elfsect_free(void *arg) +{ + struct elfsect *w = arg; + + (void)w; +} + +static PyObject *elfsect_repr(PyObject *arg) +{ + struct elfsect *w = (struct elfsect *)arg; + + return PyUnicode_FromFormat("", w->name); +} + +static PyTypeObject typeobj_elfsect = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFSection", + .tp_basicsize = sizeof(struct elfsect), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = elfsect_doc, + .tp_new = refuse_new, + .tp_free = elfsect_free, + .tp_repr = elfsect_repr, + .tp_as_mapping = &mp_elfsect, + .tp_members = members_elfsect, + .tp_methods = methods_elfsect, + .tp_getset = getset_elfsect, +}; + +static void elfsect_add_relocations(struct elfsect *w, Elf_Scn *rel, + GElf_Shdr *relhdr) +{ + size_t i, entries; + Elf_Scn *symtab = elf_getscn(w->ef->elf, relhdr->sh_link); + GElf_Shdr _symhdr, *symhdr = gelf_getshdr(symtab, &_symhdr); + Elf_Data *symdata = elf_getdata(symtab, NULL); + Elf_Data *reldata = elf_getdata(rel, NULL); + + entries = relhdr->sh_size / relhdr->sh_entsize; + for (i = 0; i < entries; i++) { + struct elfreloc *relw; + size_t symidx; + GElf_Rela *rela; + GElf_Sym *sym; + + relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc( + &typeobj_elfreloc, 0); + relw->es = w; + + if (relhdr->sh_type == SHT_REL) { + GElf_Rel _rel, *rel; + + rel = gelf_getrel(reldata, i, &_rel); + relw->rela = &relw->_rela; + relw->rela->r_offset = rel->r_offset; + relw->rela->r_info = rel->r_info; + relw->rela->r_addend = 0; + relw->relative = true; + } else + relw->rela = gelf_getrela(reldata, i, &relw->_rela); + + rela = relw->rela; + if (rela->r_offset < w->shdr->sh_addr + || rela->r_offset >= w->shdr->sh_addr + w->shdr->sh_size) + continue; + + symidx = relw->symidx = GELF_R_SYM(rela->r_info); + sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym); + if (sym) { + relw->symname = elf_strptr(w->ef->elf, symhdr->sh_link, + sym->st_name); + relw->symvalid = GELF_ST_TYPE(sym->st_info) + != STT_NOTYPE; + relw->unresolved = sym->st_shndx == SHN_UNDEF; + relw->st_value = sym->st_value; + } else { + relw->symname = NULL; + relw->symvalid = false; + relw->unresolved = false; + relw->st_value = 0; + } + + debugf("reloc @ %016llx sym %5llu %016llx %s\n", + (long long)rela->r_offset, (unsigned long long)symidx, + (long long)rela->r_addend, relw->symname); + + elfrelocs_add(&w->relocs, relw); + } +} + +/* + * bindings & loading code between ELFFile and ELFSection + */ + +static PyObject *elfsect_wrap(struct elffile *ef, Elf_Scn *scn, size_t idx, + const char *name) +{ + struct elfsect *w; + size_t i; + + w = (struct elfsect *)typeobj_elfsect.tp_alloc(&typeobj_elfsect, 0); + if (!w) + return NULL; + + w->name = name; + w->ef = ef; + w->scn = scn; + w->shdr = gelf_getshdr(scn, &w->_shdr); + w->len = w->shdr->sh_size; + w->idx = idx; + elfrelocs_init(&w->relocs); + + for (i = 0; i < ef->ehdr->e_shnum; i++) { + Elf_Scn *scn = elf_getscn(ef->elf, i); + GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr); + + if (shdr->sh_type != SHT_RELA && shdr->sh_type != SHT_REL) + continue; + if (shdr->sh_info && shdr->sh_info != idx) + continue; + elfsect_add_relocations(w, scn, shdr); + } + + return (PyObject *)w; +} + +static Elf_Scn *elf_find_section(struct elffile *ef, const char *name, + size_t *idx) +{ + size_t i; + const char *secname; + + for (i = 0; i < ef->ehdr->e_shnum; i++) { + Elf_Scn *scn = elf_getscn(ef->elf, i); + GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr); + + secname = elf_strptr(ef->elf, ef->ehdr->e_shstrndx, + shdr->sh_name); + if (strcmp(secname, name)) + continue; + if (idx) + *idx = i; + return scn; + } + return NULL; +} + +static Elf_Scn *elf_find_addr(struct elffile *ef, uint64_t addr, size_t *idx) +{ + size_t i; + + for (i = 0; i < ef->ehdr->e_shnum; i++) { + Elf_Scn *scn = elf_getscn(ef->elf, i); + GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr); + + /* virtual address is kinda meaningless for TLS sections */ + if (shdr->sh_flags & SHF_TLS) + continue; + if (addr < shdr->sh_addr || + addr >= shdr->sh_addr + shdr->sh_size) + continue; + + if (idx) + *idx = i; + return scn; + } + return NULL; +} + +/* + * class ELFFile: + */ + +static const char elffile_doc[] = + "Represents an ELF file\n" + "\n" + "Args: filename to load\n" + "\n" + "To access raw file contents, use subscript notation, e.g.\n" + " file[123:456]\n" + "To read null terminated C strings, replace the end with str:\n" + " file[123:str]\n\n" + "(struct elffile * in elf_py.c)"; + + +#define member(name, type, doc) \ + { \ + (char *)#name, type, offsetof(struct elffile, name), READONLY, \ + (char *)doc "\n\n(\"" #name "\", " #type " in elf_py.c)" \ + } +static PyMemberDef members_elffile[] = { + member(filename, T_STRING, + "Original file name as given when opening"), + member(elfclass, T_INT, + "ELF class (architecture bit size)\n\n" + "Either 32 or 64, straight integer."), + member(bigendian, T_BOOL, + "ELF file is big-endian\n\n" + "All internal ELF structures are automatically converted."), + member(has_symbols, T_BOOL, + "A symbol section is present\n\n" + "Note: only refers to .symtab/SHT_SYMTAB section, not DT_SYMTAB" + ), + {}, +}; +#undef member + +static PyObject *elffile_secbyidx(struct elffile *w, Elf_Scn *scn, size_t idx) +{ + const char *name; + PyObject *ret; + + if (!scn) + scn = elf_getscn(w->elf, idx); + if (!scn || idx >= w->n_sect) + Py_RETURN_NONE; + + if (!w->sects[idx]) { + GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr); + + name = elf_strptr(w->elf, w->ehdr->e_shstrndx, shdr->sh_name); + w->sects[idx] = elfsect_wrap(w, scn, idx, name); + } + + ret = w->sects[idx]; + Py_INCREF(ret); + return ret; +} + +static PyObject *elffile_get_section(PyObject *self, PyObject *args) +{ + const char *name; + struct elffile *w = (struct elffile *)self; + Elf_Scn *scn; + size_t idx = 0; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + scn = elf_find_section(w, name, &idx); + return elffile_secbyidx(w, scn, idx); +} + +static PyObject *elffile_get_section_addr(PyObject *self, PyObject *args) +{ + unsigned long long addr; + struct elffile *w = (struct elffile *)self; + Elf_Scn *scn; + size_t idx = 0; + + if (!PyArg_ParseTuple(args, "K", &addr)) + return NULL; + + scn = elf_find_addr(w, addr, &idx); + return elffile_secbyidx(w, scn, idx); +} + +static PyObject *elffile_get_section_idx(PyObject *self, PyObject *args) +{ + unsigned long long idx; + struct elffile *w = (struct elffile *)self; + + if (!PyArg_ParseTuple(args, "K", &idx)) + return NULL; + + return elffile_secbyidx(w, NULL, idx); +} + +static PyObject *elffile_get_symbol(PyObject *self, PyObject *args) +{ + const char *name, *symname; + struct elffile *w = (struct elffile *)self; + GElf_Sym _sym, *sym; + size_t i; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + for (i = 0; i < w->nsym; i++) { + sym = gelf_getsym(w->symdata, i, &_sym); + if (sym->st_name == 0) + continue; + symname = elf_strptr(w->elf, w->symstridx, sym->st_name); + if (strcmp(symname, name)) + continue; + + PyObject *pysect; + Elf_Scn *scn = elf_getscn(w->elf, sym->st_shndx); + + if (scn) + pysect = elffile_secbyidx(w, scn, sym->st_shndx); + else { + pysect = Py_None; + Py_INCREF(pysect); + } + return Py_BuildValue("sKN", symname, + (unsigned long long)sym->st_value, pysect); + } + Py_RETURN_NONE; +} + +static PyObject *elffile_getreloc(PyObject *self, PyObject *args) +{ + struct elffile *w = (struct elffile *)self; + struct elfreloc *relw; + unsigned long offs; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "k", &offs)) + return NULL; + + relw = elfrelocs_get(&w->dynrelocs, offs); + if (!relw) + Py_RETURN_NONE; + + ret = (PyObject *)relw; + Py_INCREF(ret); + return ret; +} + +static PyObject *elffile_find_note(PyObject *self, PyObject *args) +{ +#if defined(HAVE_GELF_GETNOTE) && defined(HAVE_ELF_GETDATA_RAWCHUNK) + const char *owner; + const uint8_t *ids; + GElf_Word id; + struct elffile *w = (struct elffile *)self; + size_t i; + + if (!PyArg_ParseTuple(args, "ss", &owner, &ids)) + return NULL; + + if (strlen((char *)ids) != 4) { + PyErr_SetString(PyExc_ValueError, + "ELF note ID must be exactly 4-byte string"); + return NULL; + } + if (w->bigendian) + id = (ids[0] << 24) | (ids[1] << 16) | (ids[2] << 8) | ids[3]; + else + id = (ids[3] << 24) | (ids[2] << 16) | (ids[1] << 8) | ids[0]; + + for (i = 0; i < w->ehdr->e_phnum; i++) { + GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr); + Elf_Data *notedata; + size_t offset; + + if (phdr->p_type != PT_NOTE) + continue; + + notedata = elf_getdata_rawchunk(w->elf, phdr->p_offset, + phdr->p_filesz, ELF_T_NHDR); + + GElf_Nhdr nhdr[1]; + size_t nameoffs, dataoffs; + + offset = 0; + while ((offset = gelf_getnote(notedata, offset, nhdr, + &nameoffs, &dataoffs))) { + if (phdr->p_offset + nameoffs >= w->len) + continue; + + const char *name = w->mmap + phdr->p_offset + nameoffs; + + if (strcmp(name, owner)) + continue; + if (id != nhdr->n_type) + continue; + + PyObject *s, *e; + + s = PyLong_FromUnsignedLongLong( + phdr->p_vaddr + dataoffs); + e = PyLong_FromUnsignedLongLong( + phdr->p_vaddr + dataoffs + nhdr->n_descsz); + return PySlice_New(s, e, NULL); + } + } +#endif + Py_RETURN_NONE; +} + +#ifdef HAVE_ELF_GETDATA_RAWCHUNK +static bool elffile_virt2file(struct elffile *w, GElf_Addr virt, + GElf_Addr *offs) +{ + *offs = 0; + + for (size_t i = 0; i < w->ehdr->e_phnum; i++) { + GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr); + + if (phdr->p_type != PT_LOAD) + continue; + + if (virt < phdr->p_vaddr + || virt >= phdr->p_vaddr + phdr->p_memsz) + continue; + + if (virt >= phdr->p_vaddr + phdr->p_filesz) + return false; + + *offs = virt - phdr->p_vaddr + phdr->p_offset; + return true; + } + + return false; +} +#endif /* HAVE_ELF_GETDATA_RAWCHUNK */ + +static PyObject *elffile_subscript(PyObject *self, PyObject *key) +{ + Py_ssize_t start, stop, step; + PySliceObject *slice; + struct elffile *w = (struct elffile *)self; + bool str = false; + + if (!PySlice_Check(key)) { + PyErr_SetString(PyExc_IndexError, + "ELFFile subscript must be slice"); + return NULL; + } + slice = (PySliceObject *)key; + stop = -1; + step = 1; + if (PyLong_Check(slice->stop)) { + start = PyLong_AsSsize_t(slice->start); + if (PyErr_Occurred()) + return NULL; + if (slice->stop != Py_None) { + stop = PyLong_AsSsize_t(slice->stop); + if (PyErr_Occurred()) + return NULL; + } + if (slice->step != Py_None) { + step = PyLong_AsSsize_t(slice->step); + if (PyErr_Occurred()) + return NULL; + } + } else { + if (slice->stop != (void *)&PyUnicode_Type + || !PyLong_Check(slice->start)) { + PyErr_SetString(PyExc_IndexError, "invalid slice"); + return NULL; + } + + str = true; + start = PyLong_AsUnsignedLongLong(slice->start); + } + if (step != 1) { + PyErr_SetString(PyExc_IndexError, + "ELFFile subscript slice step must be 1"); + return NULL; + } + + GElf_Addr xstart = start, xstop = stop; + + for (size_t i = 0; i < w->ehdr->e_phnum; i++) { + GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr); + + if (phdr->p_type != PT_LOAD) + continue; + + if (xstart < phdr->p_vaddr + || xstart >= phdr->p_vaddr + phdr->p_memsz) + continue; + if (!str && (xstop < phdr->p_vaddr + || xstop > phdr->p_vaddr + phdr->p_memsz)) { + PyErr_Format(ELFAccessError, + "access (%llu) beyond end of program header (%llu)", + (long long)xstop, + (long long)(phdr->p_vaddr + + phdr->p_memsz)); + return NULL; + } + + xstart = xstart - phdr->p_vaddr + phdr->p_offset; + + if (str) + xstop = strlen(w->mmap + xstart); + else + xstop = xstop - phdr->p_vaddr + phdr->p_offset; + + Py_ssize_t pylen = xstop - xstart; + +#if PY_MAJOR_VERSION >= 3 + return Py_BuildValue("y#", w->mmap + xstart, pylen); +#else + return Py_BuildValue("s#", w->mmap + xstart, pylen); +#endif + }; + + return PyErr_Format(ELFAccessError, + "virtual address (%llu) not found in program headers", + (long long)start); +} + +static PyMethodDef methods_elffile[] = { + {"find_note", elffile_find_note, METH_VARARGS, + "find specific note entry"}, + {"getreloc", elffile_getreloc, METH_VARARGS, + "find relocation"}, + {"get_symbol", elffile_get_symbol, METH_VARARGS, + "find symbol by name"}, + {"get_section", elffile_get_section, METH_VARARGS, + "find section by name"}, + {"get_section_addr", elffile_get_section_addr, METH_VARARGS, + "find section by address"}, + {"get_section_idx", elffile_get_section_idx, METH_VARARGS, + "find section by index"}, + {} +}; + +static PyObject *elffile_load(PyTypeObject *type, PyObject *args, + PyObject *kwds); + +static void elffile_free(void *arg) +{ + struct elffile *w = arg; + + elf_end(w->elf); + munmap(w->mmap, w->len); + free(w->filename); +} + +static PyMappingMethods mp_elffile = { + .mp_subscript = elffile_subscript, +}; + +static PyTypeObject typeobj_elffile = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.ELFFile", + .tp_basicsize = sizeof(struct elffile), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = elffile_doc, + .tp_new = elffile_load, + .tp_free = elffile_free, + .tp_as_mapping = &mp_elffile, + .tp_members = members_elffile, + .tp_methods = methods_elffile, +}; + +#ifdef HAVE_ELF_GETDATA_RAWCHUNK +static char *elfdata_strptr(Elf_Data *data, size_t offset) +{ + char *p; + + if (offset >= data->d_size) + return NULL; + + p = (char *)data->d_buf + offset; + if (strnlen(p, data->d_size - offset) >= data->d_size - offset) + return NULL; + + return p; +} + +static void elffile_add_dynreloc(struct elffile *w, Elf_Data *reldata, + size_t entries, Elf_Data *symdata, + Elf_Data *strdata, Elf_Type typ) +{ + size_t i; + + for (i = 0; i < entries; i++) { + struct elfreloc *relw; + size_t symidx; + GElf_Rela *rela; + GElf_Sym *sym; + GElf_Addr rel_offs = 0; + + relw = (struct elfreloc *)typeobj_elfreloc.tp_alloc( + &typeobj_elfreloc, 0); + relw->ef = w; + + if (typ == ELF_T_REL) { + GElf_Rel _rel, *rel; + GElf_Addr offs; + + rel = gelf_getrel(reldata, i, &_rel); + relw->rela = &relw->_rela; + relw->rela->r_offset = rel->r_offset; + relw->rela->r_info = rel->r_info; + relw->rela->r_addend = 0; + relw->relative = true; + + /* REL uses the pointer contents itself instead of the + * RELA addend field :( ... theoretically this could + * be some weird platform specific encoding, but since + * we only care about data relocations it should + * always be a pointer... + */ + if (elffile_virt2file(w, rel->r_offset, &offs)) { + Elf_Data *ptr; + + /* NB: this endian-converts! */ + ptr = elf_getdata_rawchunk(w->elf, offs, + w->elfclass / 8, + ELF_T_ADDR); + + if (ptr) { + char *dst = (char *)&rel_offs; + + /* sigh. it endian-converts. but + * doesn't size-convert. + */ + if (BYTE_ORDER == BIG_ENDIAN && + ptr->d_size < sizeof(rel_offs)) + dst += sizeof(rel_offs) - + ptr->d_size; + + memcpy(dst, ptr->d_buf, ptr->d_size); + + relw->relative = false; + relw->rela->r_addend = rel_offs; + } + } + } else + relw->rela = gelf_getrela(reldata, i, &relw->_rela); + + rela = relw->rela; + symidx = relw->symidx = GELF_R_SYM(rela->r_info); + sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym); + if (sym) { + if (strdata) + relw->symname = elfdata_strptr(strdata, + sym->st_name); + relw->symvalid = GELF_ST_TYPE(sym->st_info) + != STT_NOTYPE; + relw->unresolved = sym->st_shndx == SHN_UNDEF; + relw->st_value = sym->st_value; + } else { + relw->symname = NULL; + relw->symvalid = false; + relw->unresolved = false; + relw->st_value = 0; + } + + if (typ == ELF_T_RELA) + debugf("dynrela @ %016llx sym %5llu %016llx %s\n", + (long long)rela->r_offset, + (unsigned long long)symidx, + (long long)rela->r_addend, relw->symname); + else + debugf("dynrel @ %016llx sym %5llu (%016llx) %s\n", + (long long)rela->r_offset, + (unsigned long long)symidx, + (unsigned long long)rel_offs, relw->symname); + + elfrelocs_add(&w->dynrelocs, relw); + } + +} +#endif /* HAVE_ELF_GETDATA_RAWCHUNK */ + +/* primary (only, really) entry point to anything in this module */ +static PyObject *elffile_load(PyTypeObject *type, PyObject *args, + PyObject *kwds) +{ + const char *filename; + static const char * const kwnames[] = {"filename", NULL}; + struct elffile *w; + struct stat st; + int fd, err; + + w = (struct elffile *)typeobj_elffile.tp_alloc(&typeobj_elffile, 0); + if (!w) + return NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", (char **)kwnames, + &filename)) + return NULL; + + w->filename = strdup(filename); + fd = open(filename, O_RDONLY | O_NOCTTY); + if (fd < 0 || fstat(fd, &st)) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); + if (fd >= 0) + close(fd); + goto out; + } + w->len = st.st_size; + w->mmap = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!w->mmap) { + PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename); + close(fd); + goto out; + } + close(fd); + w->mmend = w->mmap + st.st_size; + + if (w->len < EI_NIDENT || memcmp(w->mmap, ELFMAG, SELFMAG)) { + PyErr_SetString(ELFFormatError, "invalid ELF signature"); + goto out; + } + + switch (w->mmap[EI_CLASS]) { + case ELFCLASS32: + w->elfclass = 32; + break; + case ELFCLASS64: + w->elfclass = 64; + break; + default: + PyErr_SetString(ELFFormatError, "invalid ELF class"); + goto out; + } + switch (w->mmap[EI_DATA]) { + case ELFDATA2LSB: + w->bigendian = false; + break; + case ELFDATA2MSB: + w->bigendian = true; + break; + default: + PyErr_SetString(ELFFormatError, "invalid ELF byte order"); + goto out; + } + + w->elf = elf_memory(w->mmap, w->len); + if (!w->elf) + goto out_elferr; + w->ehdr = gelf_getehdr(w->elf, &w->_ehdr); + if (!w->ehdr) + goto out_elferr; + + for (size_t i = 0; i < w->ehdr->e_shnum; i++) { + Elf_Scn *scn = elf_getscn(w->elf, i); + GElf_Shdr _shdr, *shdr = gelf_getshdr(scn, &_shdr); + + if (shdr->sh_type == SHT_SYMTAB) { + w->symtab = scn; + w->nsym = shdr->sh_size / shdr->sh_entsize; + w->symdata = elf_getdata(scn, NULL); + w->symstridx = shdr->sh_link; + break; + } + } + w->has_symbols = w->symtab && w->symstridx; + elfrelocs_init(&w->dynrelocs); + +#ifdef HAVE_ELF_GETDATA_RAWCHUNK + for (size_t i = 0; i < w->ehdr->e_phnum; i++) { + GElf_Phdr _phdr, *phdr = gelf_getphdr(w->elf, i, &_phdr); + + if (phdr->p_type != PT_DYNAMIC) + continue; + + Elf_Data *dyndata = elf_getdata_rawchunk(w->elf, + phdr->p_offset, phdr->p_filesz, ELF_T_DYN); + + GElf_Addr dynrela = 0, dynrel = 0, symtab = 0, strtab = 0; + size_t dynrelasz = 0, dynrelaent = 0; + size_t dynrelsz = 0, dynrelent = 0; + size_t strsz = 0; + GElf_Dyn _dyn, *dyn; + + for (size_t j = 0;; j++) { + dyn = gelf_getdyn(dyndata, j, &_dyn); + + if (dyn->d_tag == DT_NULL) + break; + + switch (dyn->d_tag) { + case DT_SYMTAB: + symtab = dyn->d_un.d_ptr; + break; + + case DT_STRTAB: + strtab = dyn->d_un.d_ptr; + break; + case DT_STRSZ: + strsz = dyn->d_un.d_val; + break; + + case DT_RELA: + dynrela = dyn->d_un.d_ptr; + break; + case DT_RELASZ: + dynrelasz = dyn->d_un.d_val; + break; + case DT_RELAENT: + dynrelaent = dyn->d_un.d_val; + break; + + case DT_REL: + dynrel = dyn->d_un.d_ptr; + break; + case DT_RELSZ: + dynrelsz = dyn->d_un.d_val; + break; + case DT_RELENT: + dynrelent = dyn->d_un.d_val; + break; + } + } + + GElf_Addr offset; + Elf_Data *symdata = NULL, *strdata = NULL; + + if (elffile_virt2file(w, symtab, &offset)) + symdata = elf_getdata_rawchunk(w->elf, offset, + w->len - offset, + ELF_T_SYM); + if (elffile_virt2file(w, strtab, &offset)) + strdata = elf_getdata_rawchunk(w->elf, offset, + strsz, ELF_T_BYTE); + + size_t c; + + if (dynrela && dynrelasz && dynrelaent + && elffile_virt2file(w, dynrela, &offset)) { + Elf_Data *reladata = NULL; + + debugf("dynrela @%llx/%llx+%llx\n", (long long)dynrela, + (long long)offset, (long long)dynrelasz); + + reladata = elf_getdata_rawchunk(w->elf, offset, + dynrelasz, ELF_T_RELA); + + c = dynrelasz / dynrelaent; + elffile_add_dynreloc(w, reladata, c, symdata, strdata, + ELF_T_RELA); + } + + if (dynrel && dynrelsz && dynrelent + && elffile_virt2file(w, dynrel, &offset)) { + Elf_Data *reldata = NULL; + + debugf("dynrel @%llx/%llx+%llx\n", (long long)dynrel, + (long long)offset, (long long)dynrelsz); + + reldata = elf_getdata_rawchunk(w->elf, offset, dynrelsz, + ELF_T_REL); + + c = dynrelsz / dynrelent; + elffile_add_dynreloc(w, reldata, c, symdata, strdata, + ELF_T_REL); + } + } +#endif + + w->sects = calloc(sizeof(PyObject *), w->ehdr->e_shnum); + w->n_sect = w->ehdr->e_shnum; + + return (PyObject *)w; + +out_elferr: + err = elf_errno(); + + PyErr_Format(ELFFormatError, "libelf error %d: %s", + err, elf_errmsg(err)); +out: + if (w->elf) + elf_end(w->elf); + free(w->filename); + return NULL; +} + +static PyObject *elfpy_debug(PyObject *self, PyObject *args) +{ + int arg; + + if (!PyArg_ParseTuple(args, "p", &arg)) + return NULL; + + debug = arg; + + Py_RETURN_NONE; +} + +static PyMethodDef methods_elfpy[] = { + {"elfpy_debug", elfpy_debug, METH_VARARGS, "switch debuging on/off"}, + {} +}; + +bool elf_py_init(PyObject *pymod) +{ + if (PyType_Ready(&typeobj_elffile) < 0) + return false; + if (PyType_Ready(&typeobj_elfsect) < 0) + return false; + if (PyType_Ready(&typeobj_elfreloc) < 0) + return false; + if (elf_version(EV_CURRENT) == EV_NONE) + return false; + +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 5 + PyModule_AddFunctions(pymod, methods_elfpy); +#else + (void)methods_elfpy; +#endif + +#if defined(HAVE_GELF_GETNOTE) && defined(HAVE_ELF_GETDATA_RAWCHUNK) + PyObject *elf_notes = Py_True; +#else + PyObject *elf_notes = Py_False; +#endif + Py_INCREF(elf_notes); + if (PyModule_AddObject(pymod, "elf_notes", elf_notes)) + Py_DECREF(elf_notes); + + ELFFormatError = PyErr_NewException("_clippy.ELFFormatError", + PyExc_ValueError, NULL); + PyModule_AddObject(pymod, "ELFFormatError", ELFFormatError); + ELFAccessError = PyErr_NewException("_clippy.ELFAccessError", + PyExc_IndexError, NULL); + PyModule_AddObject(pymod, "ELFAccessError", ELFAccessError); + + Py_INCREF(&typeobj_elffile); + PyModule_AddObject(pymod, "ELFFile", (PyObject *)&typeobj_elffile); + Py_INCREF(&typeobj_elfsect); + PyModule_AddObject(pymod, "ELFSection", (PyObject *)&typeobj_elfsect); + Py_INCREF(&typeobj_elfreloc); + PyModule_AddObject(pymod, "ELFReloc", (PyObject *)&typeobj_elfreloc); + return true; +} diff --git a/lib/event.c b/lib/event.c new file mode 100644 index 0000000..fc46a11 --- /dev/null +++ b/lib/event.c @@ -0,0 +1,2229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Thread management routine + * Copyright (C) 1998, 2000 Kunihiro Ishiguro + */ + +/* #define DEBUG */ + +#include + +#include +#include + +#include "frrevent.h" +#include "memory.h" +#include "frrcu.h" +#include "log.h" +#include "hash.h" +#include "command.h" +#include "sigevent.h" +#include "network.h" +#include "jhash.h" +#include "frratomic.h" +#include "frr_pthread.h" +#include "lib_errors.h" +#include "libfrr_trace.h" +#include "libfrr.h" + +DEFINE_MTYPE_STATIC(LIB, THREAD, "Thread"); +DEFINE_MTYPE_STATIC(LIB, EVENT_MASTER, "Thread master"); +DEFINE_MTYPE_STATIC(LIB, EVENT_POLL, "Thread Poll Info"); +DEFINE_MTYPE_STATIC(LIB, EVENT_STATS, "Thread stats"); + +DECLARE_LIST(event_list, struct event, eventitem); + +struct cancel_req { + int flags; + struct event *thread; + void *eventobj; + struct event **threadref; +}; + +/* Flags for task cancellation */ +#define EVENT_CANCEL_FLAG_READY 0x01 + +static int event_timer_cmp(const struct event *a, const struct event *b) +{ + if (a->u.sands.tv_sec < b->u.sands.tv_sec) + return -1; + if (a->u.sands.tv_sec > b->u.sands.tv_sec) + return 1; + if (a->u.sands.tv_usec < b->u.sands.tv_usec) + return -1; + if (a->u.sands.tv_usec > b->u.sands.tv_usec) + return 1; + return 0; +} + +DECLARE_HEAP(event_timer_list, struct event, timeritem, event_timer_cmp); + +#define AWAKEN(m) \ + do { \ + const unsigned char wakebyte = 0x01; \ + write(m->io_pipe[1], &wakebyte, 1); \ + } while (0) + +/* control variable for initializer */ +static pthread_once_t init_once = PTHREAD_ONCE_INIT; +pthread_key_t thread_current; + +static pthread_mutex_t masters_mtx = PTHREAD_MUTEX_INITIALIZER; +static struct list *masters; + +static void thread_free(struct event_loop *master, struct event *thread); + +bool cputime_enabled = true; +unsigned long cputime_threshold = CONSUMED_TIME_CHECK; +unsigned long walltime_threshold = CONSUMED_TIME_CHECK; + +/* CLI start ---------------------------------------------------------------- */ +#include "lib/event_clippy.c" + +static uint32_t cpu_record_hash_key(const struct cpu_event_history *a) +{ + int size = sizeof(a->func); + + return jhash(&a->func, size, 0); +} + +static int cpu_record_hash_cmp(const struct cpu_event_history *a, + const struct cpu_event_history *b) +{ + return numcmp((uintptr_t)a->func, (uintptr_t)b->func); +} + +DECLARE_HASH(cpu_records, struct cpu_event_history, item, cpu_record_hash_cmp, + cpu_record_hash_key); + +static struct cpu_event_history *cpu_records_get(struct event_loop *loop, + void (*func)(struct event *e), + const char *funcname) +{ + struct cpu_event_history ref = { .func = func }, *res; + + res = cpu_records_find(loop->cpu_records, &ref); + if (!res) { + res = XCALLOC(MTYPE_EVENT_STATS, sizeof(*res)); + res->func = func; + res->funcname = funcname; + cpu_records_add(loop->cpu_records, res); + } + return res; +} + +static void cpu_records_free(struct cpu_event_history **p) +{ + XFREE(MTYPE_EVENT_STATS, *p); +} + +static void vty_out_cpu_event_history(struct vty *vty, + struct cpu_event_history *a) +{ + vty_out(vty, + "%5zu %10zu.%03zu %9zu %8zu %9zu %8zu %9zu %9zu %9zu %10zu", + a->total_active, a->cpu.total / 1000, a->cpu.total % 1000, + a->total_calls, (a->cpu.total / a->total_calls), a->cpu.max, + (a->real.total / a->total_calls), a->real.max, + a->total_cpu_warn, a->total_wall_warn, a->total_starv_warn); + vty_out(vty, " %c%c%c%c%c %s\n", + a->types & (1 << EVENT_READ) ? 'R' : ' ', + a->types & (1 << EVENT_WRITE) ? 'W' : ' ', + a->types & (1 << EVENT_TIMER) ? 'T' : ' ', + a->types & (1 << EVENT_EVENT) ? 'E' : ' ', + a->types & (1 << EVENT_EXECUTE) ? 'X' : ' ', a->funcname); +} + +static void cpu_record_print_one(struct vty *vty, uint8_t filter, + struct cpu_event_history *totals, + const struct cpu_event_history *a) +{ + struct cpu_event_history copy; + + copy.total_active = + atomic_load_explicit(&a->total_active, memory_order_seq_cst); + copy.total_calls = + atomic_load_explicit(&a->total_calls, memory_order_seq_cst); + copy.total_cpu_warn = + atomic_load_explicit(&a->total_cpu_warn, memory_order_seq_cst); + copy.total_wall_warn = + atomic_load_explicit(&a->total_wall_warn, memory_order_seq_cst); + copy.total_starv_warn = atomic_load_explicit(&a->total_starv_warn, + memory_order_seq_cst); + copy.cpu.total = + atomic_load_explicit(&a->cpu.total, memory_order_seq_cst); + copy.cpu.max = atomic_load_explicit(&a->cpu.max, memory_order_seq_cst); + copy.real.total = + atomic_load_explicit(&a->real.total, memory_order_seq_cst); + copy.real.max = + atomic_load_explicit(&a->real.max, memory_order_seq_cst); + copy.types = atomic_load_explicit(&a->types, memory_order_seq_cst); + copy.funcname = a->funcname; + + if (!(copy.types & filter)) + return; + + vty_out_cpu_event_history(vty, ©); + totals->total_active += copy.total_active; + totals->total_calls += copy.total_calls; + totals->total_cpu_warn += copy.total_cpu_warn; + totals->total_wall_warn += copy.total_wall_warn; + totals->total_starv_warn += copy.total_starv_warn; + totals->real.total += copy.real.total; + if (totals->real.max < copy.real.max) + totals->real.max = copy.real.max; + totals->cpu.total += copy.cpu.total; + if (totals->cpu.max < copy.cpu.max) + totals->cpu.max = copy.cpu.max; +} + +static void cpu_record_print(struct vty *vty, uint8_t filter) +{ + struct cpu_event_history tmp; + struct event_loop *m; + struct listnode *ln; + + if (!cputime_enabled) + vty_out(vty, + "\n" + "Collecting CPU time statistics is currently disabled. Following statistics\n" + "will be zero or may display data from when collection was enabled. Use the\n" + " \"service cputime-stats\" command to start collecting data.\n" + "\nCounters and wallclock times are always maintained and should be accurate.\n"); + + memset(&tmp, 0, sizeof(tmp)); + tmp.funcname = "TOTAL"; + tmp.types = filter; + + frr_with_mutex (&masters_mtx) { + for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) { + const char *name = m->name ? m->name : "main"; + char underline[strlen(name) + 1]; + + memset(underline, '-', sizeof(underline)); + underline[sizeof(underline) - 1] = '\0'; + + vty_out(vty, "\n"); + vty_out(vty, "Showing statistics for pthread %s\n", + name); + vty_out(vty, "-------------------------------%s\n", + underline); + vty_out(vty, "%30s %18s %18s\n", "", + "CPU (user+system):", "Real (wall-clock):"); + vty_out(vty, + "Active Runtime(ms) Invoked Avg uSec Max uSecs"); + vty_out(vty, " Avg uSec Max uSecs"); + vty_out(vty, + " CPU_Warn Wall_Warn Starv_Warn Type Event\n"); + + if (cpu_records_count(m->cpu_records)) { + struct cpu_event_history *rec; + + frr_each (cpu_records, m->cpu_records, rec) + cpu_record_print_one(vty, filter, &tmp, + rec); + } else + vty_out(vty, "No data to display yet.\n"); + + vty_out(vty, "\n"); + } + } + + vty_out(vty, "\n"); + vty_out(vty, "Total Event statistics\n"); + vty_out(vty, "-------------------------\n"); + vty_out(vty, "%30s %18s %18s\n", "", + "CPU (user+system):", "Real (wall-clock):"); + vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs"); + vty_out(vty, " Avg uSec Max uSecs CPU_Warn Wall_Warn Starv_Warn"); + vty_out(vty, " Type Event\n"); + + if (tmp.total_calls > 0) + vty_out_cpu_event_history(vty, &tmp); +} + +static void cpu_record_clear(uint8_t filter) +{ + struct event_loop *m; + struct listnode *ln; + + frr_with_mutex (&masters_mtx) { + for (ALL_LIST_ELEMENTS_RO(masters, ln, m)) { + frr_with_mutex (&m->mtx) { + struct cpu_event_history *item; + struct cpu_records_head old[1]; + + cpu_records_init(old); + cpu_records_swap_all(old, m->cpu_records); + + while ((item = cpu_records_pop(old))) { + if (item->types & filter) + cpu_records_free(&item); + else + cpu_records_add(m->cpu_records, + item); + } + + cpu_records_fini(old); + } + } + } +} + +static uint8_t parse_filter(const char *filterstr) +{ + int i = 0; + int filter = 0; + + while (filterstr[i] != '\0') { + switch (filterstr[i]) { + case 'r': + case 'R': + filter |= (1 << EVENT_READ); + break; + case 'w': + case 'W': + filter |= (1 << EVENT_WRITE); + break; + case 't': + case 'T': + filter |= (1 << EVENT_TIMER); + break; + case 'e': + case 'E': + filter |= (1 << EVENT_EVENT); + break; + case 'x': + case 'X': + filter |= (1 << EVENT_EXECUTE); + break; + default: + break; + } + ++i; + } + return filter; +} + +#if CONFDATE > 20240707 + CPP_NOTICE("Remove `show thread ...` commands") +#endif +DEFUN_NOSH (show_event_cpu, + show_event_cpu_cmd, + "show event cpu [FILTER]", + SHOW_STR + "Event information\n" + "Event CPU usage\n" + "Display filter (rwtexb)\n") +{ + uint8_t filter = (uint8_t)-1U; + int idx = 0; + + if (argv_find(argv, argc, "FILTER", &idx)) { + filter = parse_filter(argv[idx]->arg); + if (!filter) { + vty_out(vty, + "Invalid filter \"%s\" specified; must contain at leastone of 'RWTEXB'\n", + argv[idx]->arg); + return CMD_WARNING; + } + } + + cpu_record_print(vty, filter); + return CMD_SUCCESS; +} + +ALIAS(show_event_cpu, + show_thread_cpu_cmd, + "show thread cpu [FILTER]", + SHOW_STR + "Thread information\n" + "Thread CPU usage\n" + "Display filter (rwtex)\n") + +DEFPY (service_cputime_stats, + service_cputime_stats_cmd, + "[no] service cputime-stats", + NO_STR + "Set up miscellaneous service\n" + "Collect CPU usage statistics\n") +{ + cputime_enabled = !no; + return CMD_SUCCESS; +} + +DEFPY (service_cputime_warning, + service_cputime_warning_cmd, + "[no] service cputime-warning ![(1-4294967295)]", + NO_STR + "Set up miscellaneous service\n" + "Warn for tasks exceeding CPU usage threshold\n" + "Warning threshold in milliseconds\n") +{ + if (no) + cputime_threshold = 0; + else + cputime_threshold = cputime_warning * 1000; + return CMD_SUCCESS; +} + +DEFPY (service_walltime_warning, + service_walltime_warning_cmd, + "[no] service walltime-warning ![(1-4294967295)]", + NO_STR + "Set up miscellaneous service\n" + "Warn for tasks exceeding total wallclock threshold\n" + "Warning threshold in milliseconds\n") +{ + if (no) + walltime_threshold = 0; + else + walltime_threshold = walltime_warning * 1000; + return CMD_SUCCESS; +} + +static void show_event_poll_helper(struct vty *vty, struct event_loop *m) +{ + const char *name = m->name ? m->name : "main"; + char underline[strlen(name) + 1]; + struct event *thread; + uint32_t i; + + memset(underline, '-', sizeof(underline)); + underline[sizeof(underline) - 1] = '\0'; + + vty_out(vty, "\nShowing poll FD's for %s\n", name); + vty_out(vty, "----------------------%s\n", underline); + vty_out(vty, "Count: %u/%d\n", (uint32_t)m->handler.pfdcount, + m->fd_limit); + for (i = 0; i < m->handler.pfdcount; i++) { + vty_out(vty, "\t%6d fd:%6d events:%2d revents:%2d\t\t", i, + m->handler.pfds[i].fd, m->handler.pfds[i].events, + m->handler.pfds[i].revents); + + if (m->handler.pfds[i].events & POLLIN) { + thread = m->read[m->handler.pfds[i].fd]; + + if (!thread) + vty_out(vty, "ERROR "); + else + vty_out(vty, "%s ", thread->xref->funcname); + } else + vty_out(vty, " "); + + if (m->handler.pfds[i].events & POLLOUT) { + thread = m->write[m->handler.pfds[i].fd]; + + if (!thread) + vty_out(vty, "ERROR\n"); + else + vty_out(vty, "%s\n", thread->xref->funcname); + } else + vty_out(vty, "\n"); + } +} + +DEFUN_NOSH (show_event_poll, + show_event_poll_cmd, + "show event poll", + SHOW_STR + "Event information\n" + "Event Poll Information\n") +{ + struct listnode *node; + struct event_loop *m; + + frr_with_mutex (&masters_mtx) { + for (ALL_LIST_ELEMENTS_RO(masters, node, m)) + show_event_poll_helper(vty, m); + } + + return CMD_SUCCESS; +} + +ALIAS(show_event_poll, + show_thread_poll_cmd, + "show thread poll", + SHOW_STR + "Thread information\n" + "Show poll FD's and information\n") + +DEFUN (clear_thread_cpu, + clear_thread_cpu_cmd, + "clear thread cpu [FILTER]", + "Clear stored data in all pthreads\n" + "Thread information\n" + "Thread CPU usage\n" + "Display filter (rwtexb)\n") +{ + uint8_t filter = (uint8_t)-1U; + int idx = 0; + + if (argv_find(argv, argc, "FILTER", &idx)) { + filter = parse_filter(argv[idx]->arg); + if (!filter) { + vty_out(vty, + "Invalid filter \"%s\" specified; must contain at leastone of 'RWTEXB'\n", + argv[idx]->arg); + return CMD_WARNING; + } + } + + cpu_record_clear(filter); + return CMD_SUCCESS; +} + +static void show_event_timers_helper(struct vty *vty, struct event_loop *m) +{ + const char *name = m->name ? m->name : "main"; + char underline[strlen(name) + 1]; + struct event *thread; + + memset(underline, '-', sizeof(underline)); + underline[sizeof(underline) - 1] = '\0'; + + vty_out(vty, "\nShowing timers for %s\n", name); + vty_out(vty, "-------------------%s\n", underline); + + frr_each (event_timer_list, &m->timer, thread) { + vty_out(vty, " %-50s%pTH\n", thread->hist->funcname, thread); + } +} + +DEFPY_NOSH (show_event_timers, + show_event_timers_cmd, + "show event timers", + SHOW_STR + "Event information\n" + "Show all timers and how long they have in the system\n") +{ + struct listnode *node; + struct event_loop *m; + + frr_with_mutex (&masters_mtx) { + for (ALL_LIST_ELEMENTS_RO(masters, node, m)) + show_event_timers_helper(vty, m); + } + + return CMD_SUCCESS; +} + +ALIAS(show_event_timers, + show_thread_timers_cmd, + "show thread timers", + SHOW_STR + "Thread information\n" + "Show all timers and how long they have in the system\n") + +void event_cmd_init(void) +{ + install_element(VIEW_NODE, &show_thread_cpu_cmd); + install_element(VIEW_NODE, &show_event_cpu_cmd); + install_element(VIEW_NODE, &show_thread_poll_cmd); + install_element(VIEW_NODE, &show_event_poll_cmd); + install_element(ENABLE_NODE, &clear_thread_cpu_cmd); + + install_element(CONFIG_NODE, &service_cputime_stats_cmd); + install_element(CONFIG_NODE, &service_cputime_warning_cmd); + install_element(CONFIG_NODE, &service_walltime_warning_cmd); + + install_element(VIEW_NODE, &show_thread_timers_cmd); + install_element(VIEW_NODE, &show_event_timers_cmd); +} +/* CLI end ------------------------------------------------------------------ */ + + +static void cancelreq_del(void *cr) +{ + XFREE(MTYPE_TMP, cr); +} + +/* initializer, only ever called once */ +static void initializer(void) +{ + pthread_key_create(&thread_current, NULL); +} + +#define STUPIDLY_LARGE_FD_SIZE 100000 +struct event_loop *event_master_create(const char *name) +{ + struct event_loop *rv; + struct rlimit limit; + + pthread_once(&init_once, &initializer); + + rv = XCALLOC(MTYPE_EVENT_MASTER, sizeof(struct event_loop)); + + /* Initialize master mutex */ + pthread_mutex_init(&rv->mtx, NULL); + pthread_cond_init(&rv->cancel_cond, NULL); + + /* Set name */ + name = name ? name : "default"; + rv->name = XSTRDUP(MTYPE_EVENT_MASTER, name); + + /* Initialize I/O task data structures */ + + /* Use configured limit if present, ulimit otherwise. */ + rv->fd_limit = frr_get_fd_limit(); + if (rv->fd_limit == 0) { + getrlimit(RLIMIT_NOFILE, &limit); + rv->fd_limit = (int)limit.rlim_cur; + } + + if (rv->fd_limit > STUPIDLY_LARGE_FD_SIZE) { + zlog_warn("FD Limit set: %u is stupidly large. Is this what you intended? Consider using --limit-fds also limiting size to %u", + rv->fd_limit, STUPIDLY_LARGE_FD_SIZE); + + rv->fd_limit = STUPIDLY_LARGE_FD_SIZE; + } + + rv->read = XCALLOC(MTYPE_EVENT_POLL, + sizeof(struct event *) * rv->fd_limit); + + rv->write = XCALLOC(MTYPE_EVENT_POLL, + sizeof(struct event *) * rv->fd_limit); + + char tmhashname[strlen(name) + 32]; + + snprintf(tmhashname, sizeof(tmhashname), "%s - threadmaster event hash", + name); + cpu_records_init(rv->cpu_records); + + event_list_init(&rv->event); + event_list_init(&rv->ready); + event_list_init(&rv->unuse); + event_timer_list_init(&rv->timer); + + /* Initialize event_fetch() settings */ + rv->spin = true; + rv->handle_signals = true; + + /* Set pthread owner, should be updated by actual owner */ + rv->owner = pthread_self(); + rv->cancel_req = list_new(); + rv->cancel_req->del = cancelreq_del; + rv->canceled = true; + + /* Initialize pipe poker */ + pipe(rv->io_pipe); + set_nonblocking(rv->io_pipe[0]); + set_nonblocking(rv->io_pipe[1]); + + /* Initialize data structures for poll() */ + rv->handler.pfdsize = rv->fd_limit; + rv->handler.pfdcount = 0; + rv->handler.pfds = XCALLOC(MTYPE_EVENT_MASTER, + sizeof(struct pollfd) * rv->handler.pfdsize); + rv->handler.copy = XCALLOC(MTYPE_EVENT_MASTER, + sizeof(struct pollfd) * rv->handler.pfdsize); + + /* add to list of threadmasters */ + frr_with_mutex (&masters_mtx) { + if (!masters) + masters = list_new(); + + listnode_add(masters, rv); + } + + return rv; +} + +void event_master_set_name(struct event_loop *master, const char *name) +{ + frr_with_mutex (&master->mtx) { + XFREE(MTYPE_EVENT_MASTER, master->name); + master->name = XSTRDUP(MTYPE_EVENT_MASTER, name); + } +} + +#define EVENT_UNUSED_DEPTH 10 + +/* Move thread to unuse list. */ +static void thread_add_unuse(struct event_loop *m, struct event *thread) +{ + pthread_mutex_t mtxc = thread->mtx; + + assert(m != NULL && thread != NULL); + + thread->hist->total_active--; + memset(thread, 0, sizeof(struct event)); + thread->type = EVENT_UNUSED; + + /* Restore the thread mutex context. */ + thread->mtx = mtxc; + + if (event_list_count(&m->unuse) < EVENT_UNUSED_DEPTH) { + event_list_add_tail(&m->unuse, thread); + return; + } + + thread_free(m, thread); +} + +/* Free all unused thread. */ +static void thread_list_free(struct event_loop *m, struct event_list_head *list) +{ + struct event *t; + + while ((t = event_list_pop(list))) + thread_free(m, t); +} + +static void thread_array_free(struct event_loop *m, struct event **thread_array) +{ + struct event *t; + int index; + + for (index = 0; index < m->fd_limit; ++index) { + t = thread_array[index]; + if (t) { + thread_array[index] = NULL; + thread_free(m, t); + } + } + XFREE(MTYPE_EVENT_POLL, thread_array); +} + +/* + * event_master_free_unused + * + * As threads are finished with they are put on the + * unuse list for later reuse. + * If we are shutting down, Free up unused threads + * So we can see if we forget to shut anything off + */ +void event_master_free_unused(struct event_loop *m) +{ + frr_with_mutex (&m->mtx) { + struct event *t; + + while ((t = event_list_pop(&m->unuse))) + thread_free(m, t); + } +} + +/* Stop thread scheduler. */ +void event_master_free(struct event_loop *m) +{ + struct cpu_event_history *record; + struct event *t; + + frr_with_mutex (&masters_mtx) { + listnode_delete(masters, m); + if (masters->count == 0) + list_delete(&masters); + } + + thread_array_free(m, m->read); + thread_array_free(m, m->write); + while ((t = event_timer_list_pop(&m->timer))) + thread_free(m, t); + thread_list_free(m, &m->event); + thread_list_free(m, &m->ready); + thread_list_free(m, &m->unuse); + pthread_mutex_destroy(&m->mtx); + pthread_cond_destroy(&m->cancel_cond); + close(m->io_pipe[0]); + close(m->io_pipe[1]); + list_delete(&m->cancel_req); + m->cancel_req = NULL; + + while ((record = cpu_records_pop(m->cpu_records))) + cpu_records_free(&record); + cpu_records_fini(m->cpu_records); + + XFREE(MTYPE_EVENT_MASTER, m->name); + XFREE(MTYPE_EVENT_MASTER, m->handler.pfds); + XFREE(MTYPE_EVENT_MASTER, m->handler.copy); + XFREE(MTYPE_EVENT_MASTER, m); +} + +/* Return remain time in milliseconds. */ +unsigned long event_timer_remain_msec(struct event *thread) +{ + int64_t remain; + + if (!event_is_scheduled(thread)) + return 0; + + frr_with_mutex (&thread->mtx) { + remain = monotime_until(&thread->u.sands, NULL) / 1000LL; + } + + return remain < 0 ? 0 : remain; +} + +/* Return remain time in seconds. */ +unsigned long event_timer_remain_second(struct event *thread) +{ + return event_timer_remain_msec(thread) / 1000LL; +} + +struct timeval event_timer_remain(struct event *thread) +{ + struct timeval remain; + + frr_with_mutex (&thread->mtx) { + monotime_until(&thread->u.sands, &remain); + } + return remain; +} + +static int time_hhmmss(char *buf, int buf_size, long sec) +{ + long hh; + long mm; + int wr; + + assert(buf_size >= 8); + + hh = sec / 3600; + sec %= 3600; + mm = sec / 60; + sec %= 60; + + wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec); + + return wr != 8; +} + +char *event_timer_to_hhmmss(char *buf, int buf_size, struct event *t_timer) +{ + if (t_timer) + time_hhmmss(buf, buf_size, event_timer_remain_second(t_timer)); + else + snprintf(buf, buf_size, "--:--:--"); + + return buf; +} + +/* Get new thread. */ +static struct event *thread_get(struct event_loop *m, uint8_t type, + void (*func)(struct event *), void *arg, + const struct xref_eventsched *xref) +{ + struct event *thread = event_list_pop(&m->unuse); + + if (!thread) { + thread = XCALLOC(MTYPE_THREAD, sizeof(struct event)); + /* mutex only needs to be initialized at struct creation. */ + pthread_mutex_init(&thread->mtx, NULL); + m->alloc++; + } + + thread->type = type; + thread->add_type = type; + thread->master = m; + thread->arg = arg; + thread->yield = EVENT_YIELD_TIME_SLOT; /* default */ + /* thread->ref is zeroed either by XCALLOC above or by memset before + * being put on the "unuse" list by thread_add_unuse(). + * Setting it here again makes coverity complain about a missing + * lock :( + */ + /* thread->ref = NULL; */ + thread->ignore_timer_late = false; + + /* + * So if the passed in funcname is not what we have + * stored that means the thread->hist needs to be + * updated. We keep the last one around in unused + * under the assumption that we are probably + * going to immediately allocate the same + * type of thread. + * This hopefully saves us some serious + * hash_get lookups. + */ + if ((thread->xref && thread->xref->funcname != xref->funcname) + || thread->func != func) + thread->hist = cpu_records_get(m, func, xref->funcname); + + thread->hist->total_active++; + thread->func = func; + thread->xref = xref; + + return thread; +} + +static void thread_free(struct event_loop *master, struct event *thread) +{ + /* Update statistics. */ + assert(master->alloc > 0); + master->alloc--; + + /* Free allocated resources. */ + pthread_mutex_destroy(&thread->mtx); + XFREE(MTYPE_THREAD, thread); +} + +static int fd_poll(struct event_loop *m, const struct timeval *timer_wait, + bool *eintr_p) +{ + sigset_t origsigs; + unsigned char trash[64]; + nfds_t count = m->handler.copycount; + + /* + * If timer_wait is null here, that means poll() should block + * indefinitely, unless the event_master has overridden it by setting + * ->selectpoll_timeout. + * + * If the value is positive, it specifies the maximum number of + * milliseconds to wait. If the timeout is -1, it specifies that + * we should never wait and always return immediately even if no + * event is detected. If the value is zero, the behavior is default. + */ + int timeout = -1; + + /* number of file descriptors with events */ + int num; + + if (timer_wait != NULL && m->selectpoll_timeout == 0) { + /* use the default value */ + timeout = (timer_wait->tv_sec * 1000) + + (timer_wait->tv_usec / 1000); + } else if (m->selectpoll_timeout > 0) { + /* use the user's timeout */ + timeout = m->selectpoll_timeout; + } else if (m->selectpoll_timeout < 0) { + /* effect a poll (return immediately) */ + timeout = 0; + } + + zlog_tls_buffer_flush(); + rcu_read_unlock(); + rcu_assert_read_unlocked(); + + /* add poll pipe poker */ + assert(count + 1 < m->handler.pfdsize); + m->handler.copy[count].fd = m->io_pipe[0]; + m->handler.copy[count].events = POLLIN; + m->handler.copy[count].revents = 0x00; + + /* We need to deal with a signal-handling race here: we + * don't want to miss a crucial signal, such as SIGTERM or SIGINT, + * that may arrive just before we enter poll(). We will block the + * key signals, then check whether any have arrived - if so, we return + * before calling poll(). If not, we'll re-enable the signals + * in the ppoll() call. + */ + + sigemptyset(&origsigs); + if (m->handle_signals) { + /* Main pthread that handles the app signals */ + if (frr_sigevent_check(&origsigs)) { + /* Signal to process - restore signal mask and return */ + pthread_sigmask(SIG_SETMASK, &origsigs, NULL); + num = -1; + *eintr_p = true; + goto done; + } + } else { + /* Don't make any changes for the non-main pthreads */ + pthread_sigmask(SIG_SETMASK, NULL, &origsigs); + } + +#if defined(HAVE_PPOLL) + struct timespec ts, *tsp; + + if (timeout >= 0) { + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + tsp = &ts; + } else + tsp = NULL; + + num = ppoll(m->handler.copy, count + 1, tsp, &origsigs); + pthread_sigmask(SIG_SETMASK, &origsigs, NULL); +#else + /* Not ideal - there is a race after we restore the signal mask */ + pthread_sigmask(SIG_SETMASK, &origsigs, NULL); + num = poll(m->handler.copy, count + 1, timeout); +#endif + +done: + + if (num < 0 && errno == EINTR) + *eintr_p = true; + + if (num > 0 && m->handler.copy[count].revents != 0 && num--) + while (read(m->io_pipe[0], &trash, sizeof(trash)) > 0) + ; + + rcu_read_lock(); + + return num; +} + +/* Add new read thread. */ +void _event_add_read_write(const struct xref_eventsched *xref, + struct event_loop *m, void (*func)(struct event *), + void *arg, int fd, struct event **t_ptr) +{ + int dir = xref->event_type; + struct event *thread = NULL; + struct event **thread_array; + + if (dir == EVENT_READ) + frrtrace(9, frr_libfrr, schedule_read, m, + xref->funcname, xref->xref.file, xref->xref.line, + t_ptr, fd, 0, arg, 0); + else + frrtrace(9, frr_libfrr, schedule_write, m, + xref->funcname, xref->xref.file, xref->xref.line, + t_ptr, fd, 0, arg, 0); + + assert(fd >= 0); + if (fd >= m->fd_limit) + assert(!"Number of FD's open is greater than FRR currently configured to handle, aborting"); + + frr_with_mutex (&m->mtx) { + /* Thread is already scheduled; don't reschedule */ + if (t_ptr && *t_ptr) + break; + + /* default to a new pollfd */ + nfds_t queuepos = m->handler.pfdcount; + + if (dir == EVENT_READ) + thread_array = m->read; + else + thread_array = m->write; + + /* + * if we already have a pollfd for our file descriptor, find and + * use it + */ + for (nfds_t i = 0; i < m->handler.pfdcount; i++) + if (m->handler.pfds[i].fd == fd) { + queuepos = i; + +#ifdef DEV_BUILD + /* + * What happens if we have a thread already + * created for this event? + */ + if (thread_array[fd]) + assert(!"Thread already scheduled for file descriptor"); +#endif + break; + } + + /* make sure we have room for this fd + pipe poker fd */ + assert(queuepos + 1 < m->handler.pfdsize); + + thread = thread_get(m, dir, func, arg, xref); + + m->handler.pfds[queuepos].fd = fd; + m->handler.pfds[queuepos].events |= + (dir == EVENT_READ ? POLLIN : POLLOUT); + + if (queuepos == m->handler.pfdcount) + m->handler.pfdcount++; + + if (thread) { + frr_with_mutex (&thread->mtx) { + thread->u.fd = fd; + thread_array[thread->u.fd] = thread; + } + + if (t_ptr) { + *t_ptr = thread; + thread->ref = t_ptr; + } + } + + AWAKEN(m); + } +} + +static void _event_add_timer_timeval(const struct xref_eventsched *xref, + struct event_loop *m, + void (*func)(struct event *), void *arg, + struct timeval *time_relative, + struct event **t_ptr) +{ + struct event *thread; + struct timeval t; + + assert(m != NULL); + + assert(time_relative); + + frrtrace(9, frr_libfrr, schedule_timer, m, + xref->funcname, xref->xref.file, xref->xref.line, + t_ptr, 0, 0, arg, (long)time_relative->tv_sec); + + /* Compute expiration/deadline time. */ + monotime(&t); + timeradd(&t, time_relative, &t); + + frr_with_mutex (&m->mtx) { + if (t_ptr && *t_ptr) + /* thread is already scheduled; don't reschedule */ + return; + + thread = thread_get(m, EVENT_TIMER, func, arg, xref); + + frr_with_mutex (&thread->mtx) { + thread->u.sands = t; + event_timer_list_add(&m->timer, thread); + if (t_ptr) { + *t_ptr = thread; + thread->ref = t_ptr; + } + } + + /* The timer list is sorted - if this new timer + * might change the time we'll wait for, give the pthread + * a chance to re-compute. + */ + if (event_timer_list_first(&m->timer) == thread) + AWAKEN(m); + } +#define ONEYEAR2SEC (60 * 60 * 24 * 365) + if (time_relative->tv_sec > ONEYEAR2SEC) + flog_err( + EC_LIB_TIMER_TOO_LONG, + "Timer: %pTHD is created with an expiration that is greater than 1 year", + thread); +} + + +/* Add timer event thread. */ +void _event_add_timer(const struct xref_eventsched *xref, struct event_loop *m, + void (*func)(struct event *), void *arg, long timer, + struct event **t_ptr) +{ + struct timeval trel; + + assert(m != NULL); + + trel.tv_sec = timer; + trel.tv_usec = 0; + + _event_add_timer_timeval(xref, m, func, arg, &trel, t_ptr); +} + +/* Add timer event thread with "millisecond" resolution */ +void _event_add_timer_msec(const struct xref_eventsched *xref, + struct event_loop *m, void (*func)(struct event *), + void *arg, long timer, struct event **t_ptr) +{ + struct timeval trel; + + assert(m != NULL); + + trel.tv_sec = timer / 1000; + trel.tv_usec = 1000 * (timer % 1000); + + _event_add_timer_timeval(xref, m, func, arg, &trel, t_ptr); +} + +/* Add timer event thread with "timeval" resolution */ +void _event_add_timer_tv(const struct xref_eventsched *xref, + struct event_loop *m, void (*func)(struct event *), + void *arg, struct timeval *tv, struct event **t_ptr) +{ + _event_add_timer_timeval(xref, m, func, arg, tv, t_ptr); +} + +/* Add simple event thread. */ +void _event_add_event(const struct xref_eventsched *xref, struct event_loop *m, + void (*func)(struct event *), void *arg, int val, + struct event **t_ptr) +{ + struct event *thread = NULL; + + frrtrace(9, frr_libfrr, schedule_event, m, + xref->funcname, xref->xref.file, xref->xref.line, + t_ptr, 0, val, arg, 0); + + assert(m != NULL); + + frr_with_mutex (&m->mtx) { + if (t_ptr && *t_ptr) + /* thread is already scheduled; don't reschedule */ + break; + + thread = thread_get(m, EVENT_EVENT, func, arg, xref); + frr_with_mutex (&thread->mtx) { + thread->u.val = val; + event_list_add_tail(&m->event, thread); + } + + if (t_ptr) { + *t_ptr = thread; + thread->ref = t_ptr; + } + + AWAKEN(m); + } +} + +/* Thread cancellation ------------------------------------------------------ */ + +/** + * NOT's out the .events field of pollfd corresponding to the given file + * descriptor. The event to be NOT'd is passed in the 'state' parameter. + * + * This needs to happen for both copies of pollfd's. See 'event_fetch' + * implementation for details. + * + * @param master + * @param fd + * @param state the event to cancel. One or more (OR'd together) of the + * following: + * - POLLIN + * - POLLOUT + */ +static void event_cancel_rw(struct event_loop *master, int fd, short state, + int idx_hint) +{ + bool found = false; + + /* find the index of corresponding pollfd */ + nfds_t i; + + /* Cancel POLLHUP too just in case some bozo set it */ + state |= POLLHUP; + + /* Some callers know the index of the pfd already */ + if (idx_hint >= 0) { + i = idx_hint; + found = true; + } else { + /* Have to look for the fd in the pfd array */ + for (i = 0; i < master->handler.pfdcount; i++) + if (master->handler.pfds[i].fd == fd) { + found = true; + break; + } + } + + if (!found) { + zlog_debug( + "[!] Received cancellation request for nonexistent rw job"); + zlog_debug("[!] threadmaster: %s | fd: %d", + master->name ? master->name : "", fd); + return; + } + + /* NOT out event. */ + master->handler.pfds[i].events &= ~(state); + + /* If all events are canceled, delete / resize the pollfd array. */ + if (master->handler.pfds[i].events == 0) { + memmove(master->handler.pfds + i, master->handler.pfds + i + 1, + (master->handler.pfdcount - i - 1) + * sizeof(struct pollfd)); + master->handler.pfdcount--; + master->handler.pfds[master->handler.pfdcount].fd = 0; + master->handler.pfds[master->handler.pfdcount].events = 0; + } + + /* + * If we have the same pollfd in the copy, perform the same operations, + * otherwise return. + */ + if (i >= master->handler.copycount) + return; + + master->handler.copy[i].events &= ~(state); + + if (master->handler.copy[i].events == 0) { + memmove(master->handler.copy + i, master->handler.copy + i + 1, + (master->handler.copycount - i - 1) + * sizeof(struct pollfd)); + master->handler.copycount--; + master->handler.copy[master->handler.copycount].fd = 0; + master->handler.copy[master->handler.copycount].events = 0; + } +} + +/* + * Process task cancellation given a task argument: iterate through the + * various lists of tasks, looking for any that match the argument. + */ +static void cancel_arg_helper(struct event_loop *master, + const struct cancel_req *cr) +{ + struct event *t; + nfds_t i; + int fd; + struct pollfd *pfd; + + /* We're only processing arg-based cancellations here. */ + if (cr->eventobj == NULL) + return; + + /* First process the ready lists. */ + frr_each_safe (event_list, &master->event, t) { + if (t->arg != cr->eventobj) + continue; + event_list_del(&master->event, t); + if (t->ref) + *t->ref = NULL; + thread_add_unuse(master, t); + } + + frr_each_safe (event_list, &master->ready, t) { + if (t->arg != cr->eventobj) + continue; + event_list_del(&master->ready, t); + if (t->ref) + *t->ref = NULL; + thread_add_unuse(master, t); + } + + /* If requested, stop here and ignore io and timers */ + if (CHECK_FLAG(cr->flags, EVENT_CANCEL_FLAG_READY)) + return; + + /* Check the io tasks */ + for (i = 0; i < master->handler.pfdcount;) { + pfd = master->handler.pfds + i; + + if (pfd->events & POLLIN) + t = master->read[pfd->fd]; + else + t = master->write[pfd->fd]; + + if (t && t->arg == cr->eventobj) { + fd = pfd->fd; + + /* Found a match to cancel: clean up fd arrays */ + event_cancel_rw(master, pfd->fd, pfd->events, i); + + /* Clean up thread arrays */ + master->read[fd] = NULL; + master->write[fd] = NULL; + + /* Clear caller's ref */ + if (t->ref) + *t->ref = NULL; + + thread_add_unuse(master, t); + + /* Don't increment 'i' since the cancellation will have + * removed the entry from the pfd array + */ + } else + i++; + } + + /* Check the timer tasks */ + t = event_timer_list_first(&master->timer); + while (t) { + struct event *t_next; + + t_next = event_timer_list_next(&master->timer, t); + + if (t->arg == cr->eventobj) { + event_timer_list_del(&master->timer, t); + if (t->ref) + *t->ref = NULL; + thread_add_unuse(master, t); + } + + t = t_next; + } +} + +/** + * Process cancellation requests. + * + * This may only be run from the pthread which owns the event_master. + * + * @param master the thread master to process + * @REQUIRE master->mtx + */ +static void do_event_cancel(struct event_loop *master) +{ + struct event_list_head *list = NULL; + struct event **thread_array = NULL; + struct event *thread; + struct cancel_req *cr; + struct listnode *ln; + + for (ALL_LIST_ELEMENTS_RO(master->cancel_req, ln, cr)) { + /* + * If this is an event object cancellation, search + * through task lists deleting any tasks which have the + * specified argument - use this handy helper function. + */ + if (cr->eventobj) { + cancel_arg_helper(master, cr); + continue; + } + + /* + * The pointer varies depending on whether the cancellation + * request was made asynchronously or not. If it was, we + * need to check whether the thread even exists anymore + * before cancelling it. + */ + thread = (cr->thread) ? cr->thread : *cr->threadref; + + if (!thread) + continue; + + list = NULL; + thread_array = NULL; + + /* Determine the appropriate queue to cancel the thread from */ + switch (thread->type) { + case EVENT_READ: + event_cancel_rw(master, thread->u.fd, POLLIN, -1); + thread_array = master->read; + break; + case EVENT_WRITE: + event_cancel_rw(master, thread->u.fd, POLLOUT, -1); + thread_array = master->write; + break; + case EVENT_TIMER: + event_timer_list_del(&master->timer, thread); + break; + case EVENT_EVENT: + list = &master->event; + break; + case EVENT_READY: + list = &master->ready; + break; + case EVENT_UNUSED: + case EVENT_EXECUTE: + continue; + break; + } + + if (list) + event_list_del(list, thread); + else if (thread_array) + thread_array[thread->u.fd] = NULL; + + if (thread->ref) + *thread->ref = NULL; + + thread_add_unuse(thread->master, thread); + } + + /* Delete and free all cancellation requests */ + if (master->cancel_req) + list_delete_all_node(master->cancel_req); + + /* Wake up any threads which may be blocked in event_cancel_async() */ + master->canceled = true; + pthread_cond_broadcast(&master->cancel_cond); +} + +/* + * Helper function used for multiple flavors of arg-based cancellation. + */ +static void cancel_event_helper(struct event_loop *m, void *arg, int flags) +{ + struct cancel_req *cr; + + assert(m->owner == pthread_self()); + + /* Only worth anything if caller supplies an arg. */ + if (arg == NULL) + return; + + cr = XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); + + cr->flags = flags; + + frr_with_mutex (&m->mtx) { + cr->eventobj = arg; + listnode_add(m->cancel_req, cr); + do_event_cancel(m); + } +} + +/** + * Cancel any events which have the specified argument. + * + * MT-Unsafe + * + * @param m the event_master to cancel from + * @param arg the argument passed when creating the event + */ +void event_cancel_event(struct event_loop *master, void *arg) +{ + cancel_event_helper(master, arg, 0); +} + +/* + * Cancel ready tasks with an arg matching 'arg' + * + * MT-Unsafe + * + * @param m the event_master to cancel from + * @param arg the argument passed when creating the event + */ +void event_cancel_event_ready(struct event_loop *m, void *arg) +{ + + /* Only cancel ready/event tasks */ + cancel_event_helper(m, arg, EVENT_CANCEL_FLAG_READY); +} + +/** + * Cancel a specific task. + * + * MT-Unsafe + * + * @param thread task to cancel + */ +void event_cancel(struct event **thread) +{ + struct event_loop *master; + + if (thread == NULL || *thread == NULL) + return; + + master = (*thread)->master; + + frrtrace(9, frr_libfrr, event_cancel, master, (*thread)->xref->funcname, + (*thread)->xref->xref.file, (*thread)->xref->xref.line, NULL, + (*thread)->u.fd, (*thread)->u.val, (*thread)->arg, + (*thread)->u.sands.tv_sec); + + assert(master->owner == pthread_self()); + + frr_with_mutex (&master->mtx) { + struct cancel_req *cr = + XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); + cr->thread = *thread; + listnode_add(master->cancel_req, cr); + do_event_cancel(master); + + *thread = NULL; + } +} + +/** + * Asynchronous cancellation. + * + * Called with either a struct event ** or void * to an event argument, + * this function posts the correct cancellation request and blocks until it is + * serviced. + * + * If the thread is currently running, execution blocks until it completes. + * + * The last two parameters are mutually exclusive, i.e. if you pass one the + * other must be NULL. + * + * When the cancellation procedure executes on the target event_master, the + * thread * provided is checked for nullity. If it is null, the thread is + * assumed to no longer exist and the cancellation request is a no-op. Thus + * users of this API must pass a back-reference when scheduling the original + * task. + * + * MT-Safe + * + * @param master the thread master with the relevant event / task + * @param thread pointer to thread to cancel + * @param eventobj the event + */ +void event_cancel_async(struct event_loop *master, struct event **thread, + void *eventobj) +{ + assert(!(thread && eventobj) && (thread || eventobj)); + + if (thread && *thread) + frrtrace(9, frr_libfrr, event_cancel_async, master, + (*thread)->xref->funcname, (*thread)->xref->xref.file, + (*thread)->xref->xref.line, NULL, (*thread)->u.fd, + (*thread)->u.val, (*thread)->arg, + (*thread)->u.sands.tv_sec); + else + frrtrace(9, frr_libfrr, event_cancel_async, master, NULL, NULL, + 0, NULL, 0, 0, eventobj, 0); + + assert(master->owner != pthread_self()); + + frr_with_mutex (&master->mtx) { + master->canceled = false; + + if (thread) { + struct cancel_req *cr = + XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); + cr->threadref = thread; + listnode_add(master->cancel_req, cr); + } else if (eventobj) { + struct cancel_req *cr = + XCALLOC(MTYPE_TMP, sizeof(struct cancel_req)); + cr->eventobj = eventobj; + listnode_add(master->cancel_req, cr); + } + AWAKEN(master); + + while (!master->canceled) + pthread_cond_wait(&master->cancel_cond, &master->mtx); + } + + if (thread) + *thread = NULL; +} +/* ------------------------------------------------------------------------- */ + +static struct timeval *thread_timer_wait(struct event_timer_list_head *timers, + struct timeval *timer_val) +{ + if (!event_timer_list_count(timers)) + return NULL; + + struct event *next_timer = event_timer_list_first(timers); + + monotime_until(&next_timer->u.sands, timer_val); + return timer_val; +} + +static struct event *thread_run(struct event_loop *m, struct event *thread, + struct event *fetch) +{ + *fetch = *thread; + thread_add_unuse(m, thread); + return fetch; +} + +static int thread_process_io_helper(struct event_loop *m, struct event *thread, + short state, short actual_state, int pos) +{ + struct event **thread_array; + + /* + * poll() clears the .events field, but the pollfd array we + * pass to poll() is a copy of the one used to schedule threads. + * We need to synchronize state between the two here by applying + * the same changes poll() made on the copy of the "real" pollfd + * array. + * + * This cleans up a possible infinite loop where we refuse + * to respond to a poll event but poll is insistent that + * we should. + */ + m->handler.pfds[pos].events &= ~(state); + + if (!thread) { + if ((actual_state & (POLLHUP|POLLIN)) != POLLHUP) + flog_err(EC_LIB_NO_THREAD, + "Attempting to process an I/O event but for fd: %d(%d) no thread to handle this!", + m->handler.pfds[pos].fd, actual_state); + return 0; + } + + if (thread->type == EVENT_READ) + thread_array = m->read; + else + thread_array = m->write; + + thread_array[thread->u.fd] = NULL; + event_list_add_tail(&m->ready, thread); + thread->type = EVENT_READY; + + return 1; +} + +static inline void thread_process_io_inner_loop(struct event_loop *m, + unsigned int num, + struct pollfd *pfds, nfds_t *i, + uint32_t *ready) +{ + /* no event for current fd? immediately continue */ + if (pfds[*i].revents == 0) + return; + + *ready = *ready + 1; + + /* + * Unless someone has called event_cancel from another + * pthread, the only thing that could have changed in + * m->handler.pfds while we were asleep is the .events + * field in a given pollfd. Barring event_cancel() that + * value should be a superset of the values we have in our + * copy, so there's no need to update it. Similarily, + * barring deletion, the fd should still be a valid index + * into the master's pfds. + * + * We are including POLLERR here to do a READ event + * this is because the read should fail and the + * read function should handle it appropriately + */ + if (pfds[*i].revents & (POLLIN | POLLHUP | POLLERR)) { + thread_process_io_helper(m, m->read[pfds[*i].fd], POLLIN, + pfds[*i].revents, *i); + } + if (pfds[*i].revents & POLLOUT) + thread_process_io_helper(m, m->write[pfds[*i].fd], POLLOUT, + pfds[*i].revents, *i); + + /* + * if one of our file descriptors is garbage, remove the same + * from both pfds + update sizes and index + */ + if (pfds[*i].revents & POLLNVAL) { + memmove(m->handler.pfds + *i, m->handler.pfds + *i + 1, + (m->handler.pfdcount - *i - 1) * sizeof(struct pollfd)); + m->handler.pfdcount--; + m->handler.pfds[m->handler.pfdcount].fd = 0; + m->handler.pfds[m->handler.pfdcount].events = 0; + + memmove(pfds + *i, pfds + *i + 1, + (m->handler.copycount - *i - 1) * sizeof(struct pollfd)); + m->handler.copycount--; + m->handler.copy[m->handler.copycount].fd = 0; + m->handler.copy[m->handler.copycount].events = 0; + + *i = *i - 1; + } +} + +/** + * Process I/O events. + * + * Walks through file descriptor array looking for those pollfds whose .revents + * field has something interesting. Deletes any invalid file descriptors. + * + * Try to impart some impartiality to handling of io. The event + * system will cycle through the fd's available for io + * giving each one a chance to go first. + * + * @param m the thread master + * @param num the number of active file descriptors (return value of poll()) + */ +static void thread_process_io(struct event_loop *m, unsigned int num) +{ + unsigned int ready = 0; + struct pollfd *pfds = m->handler.copy; + nfds_t i, last_read = m->last_read % m->handler.copycount; + + for (i = last_read; i < m->handler.copycount && ready < num; ++i) + thread_process_io_inner_loop(m, num, pfds, &i, &ready); + + for (i = 0; i < last_read && ready < num; ++i) + thread_process_io_inner_loop(m, num, pfds, &i, &ready); + + m->last_read++; +} + +/* Add all timers that have popped to the ready list. */ +static unsigned int thread_process_timers(struct event_loop *m, + struct timeval *timenow) +{ + struct timeval prev = *timenow; + bool displayed = false; + struct event *thread; + unsigned int ready = 0; + + while ((thread = event_timer_list_first(&m->timer))) { + if (timercmp(timenow, &thread->u.sands, <)) + break; + prev = thread->u.sands; + prev.tv_sec += 4; + /* + * If the timer would have popped 4 seconds in the + * past then we are in a situation where we are + * really getting behind on handling of events. + * Let's log it and do the right thing with it. + */ + if (timercmp(timenow, &prev, >)) { + atomic_fetch_add_explicit( + &thread->hist->total_starv_warn, 1, + memory_order_seq_cst); + if (!displayed && !thread->ignore_timer_late) { + flog_warn( + EC_LIB_STARVE_THREAD, + "Thread Starvation: %pTHD was scheduled to pop greater than 4s ago", + thread); + displayed = true; + } + } + + event_timer_list_pop(&m->timer); + thread->type = EVENT_READY; + event_list_add_tail(&m->ready, thread); + ready++; + } + + return ready; +} + +/* process a list en masse, e.g. for event thread lists */ +static unsigned int thread_process(struct event_list_head *list) +{ + struct event *thread; + unsigned int ready = 0; + + while ((thread = event_list_pop(list))) { + thread->type = EVENT_READY; + event_list_add_tail(&thread->master->ready, thread); + ready++; + } + return ready; +} + + +/* Fetch next ready thread. */ +struct event *event_fetch(struct event_loop *m, struct event *fetch) +{ + struct event *thread = NULL; + struct timeval now; + struct timeval zerotime = {0, 0}; + struct timeval tv; + struct timeval *tw = NULL; + bool eintr_p = false; + int num = 0; + + do { + /* Handle signals if any */ + if (m->handle_signals) + frr_sigevent_process(); + + pthread_mutex_lock(&m->mtx); + + /* Process any pending cancellation requests */ + do_event_cancel(m); + + /* + * Attempt to flush ready queue before going into poll(). + * This is performance-critical. Think twice before modifying. + */ + if ((thread = event_list_pop(&m->ready))) { + fetch = thread_run(m, thread, fetch); + if (fetch->ref) + *fetch->ref = NULL; + pthread_mutex_unlock(&m->mtx); + if (!m->ready_run_loop) + GETRUSAGE(&m->last_getrusage); + m->ready_run_loop = true; + break; + } + + m->ready_run_loop = false; + /* otherwise, tick through scheduling sequence */ + + /* + * Post events to ready queue. This must come before the + * following block since events should occur immediately + */ + thread_process(&m->event); + + /* + * If there are no tasks on the ready queue, we will poll() + * until a timer expires or we receive I/O, whichever comes + * first. The strategy for doing this is: + * + * - If there are events pending, set the poll() timeout to zero + * - If there are no events pending, but there are timers + * pending, set the timeout to the smallest remaining time on + * any timer. + * - If there are neither timers nor events pending, but there + * are file descriptors pending, block indefinitely in poll() + * - If nothing is pending, it's time for the application to die + * + * In every case except the last, we need to hit poll() at least + * once per loop to avoid starvation by events + */ + if (!event_list_count(&m->ready)) + tw = thread_timer_wait(&m->timer, &tv); + + if (event_list_count(&m->ready) || + (tw && !timercmp(tw, &zerotime, >))) + tw = &zerotime; + + if (!tw && m->handler.pfdcount == 0) { /* die */ + pthread_mutex_unlock(&m->mtx); + fetch = NULL; + break; + } + + /* + * Copy pollfd array + # active pollfds in it. Not necessary to + * copy the array size as this is fixed. + */ + m->handler.copycount = m->handler.pfdcount; + memcpy(m->handler.copy, m->handler.pfds, + m->handler.copycount * sizeof(struct pollfd)); + + pthread_mutex_unlock(&m->mtx); + { + eintr_p = false; + num = fd_poll(m, tw, &eintr_p); + } + pthread_mutex_lock(&m->mtx); + + /* Handle any errors received in poll() */ + if (num < 0) { + if (eintr_p) { + pthread_mutex_unlock(&m->mtx); + /* loop around to signal handler */ + continue; + } + + /* else die */ + flog_err(EC_LIB_SYSTEM_CALL, "poll() error: %s", + safe_strerror(errno)); + pthread_mutex_unlock(&m->mtx); + fetch = NULL; + break; + } + + /* Post timers to ready queue. */ + monotime(&now); + thread_process_timers(m, &now); + + /* Post I/O to ready queue. */ + if (num > 0) + thread_process_io(m, num); + + pthread_mutex_unlock(&m->mtx); + + } while (!thread && m->spin); + + return fetch; +} + +unsigned long event_consumed_time(RUSAGE_T *now, RUSAGE_T *start, + unsigned long *cputime) +{ +#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID + +#ifdef __FreeBSD__ + /* + * FreeBSD appears to have an issue when calling clock_gettime + * with CLOCK_THREAD_CPUTIME_ID really close to each other + * occassionally the now time will be before the start time. + * This is not good and FRR is ending up with CPU HOG's + * when the subtraction wraps to very large numbers + * + * What we are going to do here is cheat a little bit + * and notice that this is a problem and just correct + * it so that it is impossible to happen + */ + if (start->cpu.tv_sec == now->cpu.tv_sec && + start->cpu.tv_nsec > now->cpu.tv_nsec) + now->cpu.tv_nsec = start->cpu.tv_nsec + 1; + else if (start->cpu.tv_sec > now->cpu.tv_sec) { + now->cpu.tv_sec = start->cpu.tv_sec; + now->cpu.tv_nsec = start->cpu.tv_nsec + 1; + } +#endif + *cputime = (now->cpu.tv_sec - start->cpu.tv_sec) * TIMER_SECOND_MICRO + + (now->cpu.tv_nsec - start->cpu.tv_nsec) / 1000; +#else + /* This is 'user + sys' time. */ + *cputime = timeval_elapsed(now->cpu.ru_utime, start->cpu.ru_utime) + + timeval_elapsed(now->cpu.ru_stime, start->cpu.ru_stime); +#endif + return timeval_elapsed(now->real, start->real); +} + +/* + * We should aim to yield after yield milliseconds, which defaults + * to EVENT_YIELD_TIME_SLOT . + * Note: we are using real (wall clock) time for this calculation. + * It could be argued that CPU time may make more sense in certain + * contexts. The things to consider are whether the thread may have + * blocked (in which case wall time increases, but CPU time does not), + * or whether the system is heavily loaded with other processes competing + * for CPU time. On balance, wall clock time seems to make sense. + * Plus it has the added benefit that gettimeofday should be faster + * than calling getrusage. + */ +int event_should_yield(struct event *thread) +{ + int result; + + frr_with_mutex (&thread->mtx) { + result = monotime_since(&thread->real, NULL) + > (int64_t)thread->yield; + } + return result; +} + +void event_set_yield_time(struct event *thread, unsigned long yield_time) +{ + frr_with_mutex (&thread->mtx) { + thread->yield = yield_time; + } +} + +void event_getrusage(RUSAGE_T *r) +{ + monotime(&r->real); + if (!cputime_enabled) { + memset(&r->cpu, 0, sizeof(r->cpu)); + return; + } + +#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID + /* not currently implemented in Linux's vDSO, but maybe at some point + * in the future? + */ + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &r->cpu); +#else /* !HAVE_CLOCK_THREAD_CPUTIME_ID */ +#if defined RUSAGE_THREAD +#define FRR_RUSAGE RUSAGE_THREAD +#else +#define FRR_RUSAGE RUSAGE_SELF +#endif + getrusage(FRR_RUSAGE, &(r->cpu)); +#endif +} + +/* + * Call a thread. + * + * This function will atomically update the thread's usage history. At present + * this is the only spot where usage history is written. Nevertheless the code + * has been written such that the introduction of writers in the future should + * not need to update it provided the writers atomically perform only the + * operations done here, i.e. updating the total and maximum times. In + * particular, the maximum real and cpu times must be monotonically increasing + * or this code is not correct. + */ +void event_call(struct event *thread) +{ + RUSAGE_T before, after; + bool suppress_warnings = EVENT_ARG(thread); + + /* if the thread being called is the CLI, it may change cputime_enabled + * ("service cputime-stats" command), which can result in nonsensical + * and very confusing warnings + */ + bool cputime_enabled_here = cputime_enabled; + + if (thread->master->ready_run_loop) + before = thread->master->last_getrusage; + else + GETRUSAGE(&before); + + thread->real = before.real; + + frrtrace(9, frr_libfrr, event_call, thread->master, + thread->xref->funcname, thread->xref->xref.file, + thread->xref->xref.line, NULL, thread->u.fd, thread->u.val, + thread->arg, thread->u.sands.tv_sec); + + pthread_setspecific(thread_current, thread); + (*thread->func)(thread); + pthread_setspecific(thread_current, NULL); + + GETRUSAGE(&after); + thread->master->last_getrusage = after; + + unsigned long walltime, cputime; + unsigned long exp; + + walltime = event_consumed_time(&after, &before, &cputime); + + /* update walltime */ + atomic_fetch_add_explicit(&thread->hist->real.total, walltime, + memory_order_seq_cst); + exp = atomic_load_explicit(&thread->hist->real.max, + memory_order_seq_cst); + while (exp < walltime + && !atomic_compare_exchange_weak_explicit( + &thread->hist->real.max, &exp, walltime, + memory_order_seq_cst, memory_order_seq_cst)) + ; + + if (cputime_enabled_here && cputime_enabled) { + /* update cputime */ + atomic_fetch_add_explicit(&thread->hist->cpu.total, cputime, + memory_order_seq_cst); + exp = atomic_load_explicit(&thread->hist->cpu.max, + memory_order_seq_cst); + while (exp < cputime + && !atomic_compare_exchange_weak_explicit( + &thread->hist->cpu.max, &exp, cputime, + memory_order_seq_cst, memory_order_seq_cst)) + ; + } + + atomic_fetch_add_explicit(&thread->hist->total_calls, 1, + memory_order_seq_cst); + atomic_fetch_or_explicit(&thread->hist->types, 1 << thread->add_type, + memory_order_seq_cst); + + if (suppress_warnings) + return; + + if (cputime_enabled_here && cputime_enabled && cputime_threshold + && cputime > cputime_threshold) { + /* + * We have a CPU Hog on our hands. The time FRR has spent + * doing actual work (not sleeping) is greater than 5 seconds. + * Whinge about it now, so we're aware this is yet another task + * to fix. + */ + atomic_fetch_add_explicit(&thread->hist->total_cpu_warn, + 1, memory_order_seq_cst); + flog_warn( + EC_LIB_SLOW_THREAD_CPU, + "CPU HOG: task %s (%lx) ran for %lums (cpu time %lums)", + thread->xref->funcname, (unsigned long)thread->func, + walltime / 1000, cputime / 1000); + + } else if (walltime_threshold && walltime > walltime_threshold) { + /* + * The runtime for a task is greater than 5 seconds, but the + * cpu time is under 5 seconds. Let's whine about this because + * this could imply some sort of scheduling issue. + */ + atomic_fetch_add_explicit(&thread->hist->total_wall_warn, + 1, memory_order_seq_cst); + flog_warn( + EC_LIB_SLOW_THREAD_WALL, + "STARVATION: task %s (%lx) ran for %lums (cpu time %lums)", + thread->xref->funcname, (unsigned long)thread->func, + walltime / 1000, cputime / 1000); + } +} + +/* Execute thread */ +void _event_execute(const struct xref_eventsched *xref, struct event_loop *m, + void (*func)(struct event *), void *arg, int val, + struct event **eref) +{ + struct event *thread; + + /* Cancel existing scheduled task TODO -- nice to do in 1 lock cycle */ + if (eref) + event_cancel(eref); + + /* Get or allocate new thread to execute. */ + frr_with_mutex (&m->mtx) { + thread = thread_get(m, EVENT_EVENT, func, arg, xref); + + /* Set its event value. */ + frr_with_mutex (&thread->mtx) { + thread->add_type = EVENT_EXECUTE; + thread->u.val = val; + thread->ref = &thread; + } + } + + /* Execute thread doing all accounting. */ + event_call(thread); + + /* Give back or free thread. */ + thread_add_unuse(m, thread); +} + +/* Debug signal mask - if 'sigs' is NULL, use current effective mask. */ +void debug_signals(const sigset_t *sigs) +{ + int i, found; + sigset_t tmpsigs; + char buf[300]; + + /* + * We're only looking at the non-realtime signals here, so we need + * some limit value. Platform differences mean at some point we just + * need to pick a reasonable value. + */ +#if defined SIGRTMIN +# define LAST_SIGNAL SIGRTMIN +#else +# define LAST_SIGNAL 32 +#endif + + + if (sigs == NULL) { + sigemptyset(&tmpsigs); + pthread_sigmask(SIG_BLOCK, NULL, &tmpsigs); + sigs = &tmpsigs; + } + + found = 0; + buf[0] = '\0'; + + for (i = 0; i < LAST_SIGNAL; i++) { + char tmp[20]; + + if (sigismember(sigs, i) > 0) { + if (found > 0) + strlcat(buf, ",", sizeof(buf)); + snprintf(tmp, sizeof(tmp), "%d", i); + strlcat(buf, tmp, sizeof(buf)); + found++; + } + } + + if (found == 0) + snprintf(buf, sizeof(buf), ""); + + zlog_debug("%s: %s", __func__, buf); +} + +static ssize_t printfrr_thread_dbg(struct fbuf *buf, struct printfrr_eargs *ea, + const struct event *thread) +{ + static const char *const types[] = { + [EVENT_READ] = "read", [EVENT_WRITE] = "write", + [EVENT_TIMER] = "timer", [EVENT_EVENT] = "event", + [EVENT_READY] = "ready", [EVENT_UNUSED] = "unused", + [EVENT_EXECUTE] = "exec", + }; + ssize_t rv = 0; + char info[16] = ""; + + if (!thread) + return bputs(buf, "{(thread *)NULL}"); + + rv += bprintfrr(buf, "{(thread *)%p arg=%p", thread, thread->arg); + + if (thread->type < array_size(types) && types[thread->type]) + rv += bprintfrr(buf, " %-6s", types[thread->type]); + else + rv += bprintfrr(buf, " INVALID(%u)", thread->type); + + switch (thread->type) { + case EVENT_READ: + case EVENT_WRITE: + snprintfrr(info, sizeof(info), "fd=%d", thread->u.fd); + break; + + case EVENT_TIMER: + snprintfrr(info, sizeof(info), "r=%pTVMud", &thread->u.sands); + break; + case EVENT_READY: + case EVENT_EVENT: + case EVENT_UNUSED: + case EVENT_EXECUTE: + break; + } + + rv += bprintfrr(buf, " %-12s %s() %s from %s:%d}", info, + thread->xref->funcname, thread->xref->dest, + thread->xref->xref.file, thread->xref->xref.line); + return rv; +} + +printfrr_ext_autoreg_p("TH", printfrr_thread); +static ssize_t printfrr_thread(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct event *thread = ptr; + struct timespec remain = {}; + + if (ea->fmt[0] == 'D') { + ea->fmt++; + return printfrr_thread_dbg(buf, ea, thread); + } + + if (!thread) { + /* need to jump over time formatting flag characters in the + * input format string, i.e. adjust ea->fmt! + */ + printfrr_time(buf, ea, &remain, + TIMEFMT_TIMER_DEADLINE | TIMEFMT_SKIP); + return bputch(buf, '-'); + } + + TIMEVAL_TO_TIMESPEC(&thread->u.sands, &remain); + return printfrr_time(buf, ea, &remain, TIMEFMT_TIMER_DEADLINE); +} diff --git a/lib/explicit_bzero.c b/lib/explicit_bzero.c new file mode 100644 index 0000000..e838f95 --- /dev/null +++ b/lib/explicit_bzero.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * Public domain. + * Written by Matthew Dempsky. + * Adapted for frr. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifndef HAVE_EXPLICIT_BZERO +#undef explicit_bzero + + +void explicit_bzero(void *buf, size_t len); +__attribute__((__weak__)) void +__explicit_bzero_hook(void *buf, size_t len); + +__attribute__((__weak__)) void +__explicit_bzero_hook(void *buf, size_t len) +{ +} + +#if defined(__clang__) +#pragma clang optimize off +#else +#pragma GCC optimize("00") +#endif + +void +explicit_bzero(void *buf, size_t len) +{ + memset(buf, 0, len); + __explicit_bzero_hook(buf, len); +} + +#endif diff --git a/lib/ferr.c b/lib/ferr.c new file mode 100644 index 0000000..33bcb07 --- /dev/null +++ b/lib/ferr.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "ferr.h" +#include "vty.h" +#include "jhash.h" +#include "memory.h" +#include "hash.h" +#include "command.h" +#include "json.h" +#include "linklist.h" +#include "frr_pthread.h" + +DEFINE_MTYPE_STATIC(LIB, ERRINFO, "error information"); + +/* + * Thread-specific key for temporary storage of allocated ferr. + */ +static pthread_key_t errkey; + +static void ferr_free(void *arg) +{ + XFREE(MTYPE_ERRINFO, arg); +} + +static void err_key_init(void) __attribute__((_CONSTRUCTOR(500))); +static void err_key_init(void) +{ + pthread_key_create(&errkey, ferr_free); +} + +static void err_key_fini(void) __attribute__((_DESTRUCTOR(500))); +static void err_key_fini(void) +{ + pthread_key_delete(errkey); +} + +/* + * Global shared hash table holding reference text for all defined errors. + */ +static pthread_mutex_t refs_mtx = PTHREAD_MUTEX_INITIALIZER; +struct hash *refs; + +static bool ferr_hash_cmp(const void *a, const void *b) +{ + const struct log_ref *f_a = a; + const struct log_ref *f_b = b; + + return f_a->code == f_b->code; +} + +static inline unsigned int ferr_hash_key(const void *a) +{ + const struct log_ref *f = a; + + return f->code; +} + +void log_ref_add(struct log_ref *ref) +{ + uint32_t i = 0; + + frr_with_mutex (&refs_mtx) { + while (ref[i].code != END_FERR) { + (void)hash_get(refs, &ref[i], hash_alloc_intern); + i++; + } + } +} + +struct log_ref *log_ref_get(uint32_t code) +{ + struct log_ref holder; + struct log_ref *ref; + + holder.code = code; + frr_with_mutex (&refs_mtx) { + ref = hash_lookup(refs, &holder); + } + + return ref; +} + +void log_ref_display(struct vty *vty, uint32_t code, bool json) +{ + struct log_ref *ref; + struct json_object *top = NULL, *obj = NULL; + struct list *errlist; + struct listnode *ln; + + if (json) + top = json_object_new_object(); + + frr_with_mutex (&refs_mtx) { + errlist = code ? list_new() : hash_to_list(refs); + } + + if (code) { + ref = log_ref_get(code); + if (!ref) { + if (top) + json_object_free(top); + list_delete(&errlist); + return; + } + listnode_add(errlist, ref); + } + + for (ALL_LIST_ELEMENTS_RO(errlist, ln, ref)) { + if (json) { + char key[11]; + + snprintf(key, sizeof(key), "%u", ref->code); + obj = json_object_new_object(); + json_object_string_add(obj, "title", ref->title); + json_object_string_add(obj, "description", + ref->description); + json_object_string_add(obj, "suggestion", + ref->suggestion); + json_object_object_add(top, key, obj); + } else { + char pbuf[256]; + char ubuf[256]; + + snprintf(pbuf, sizeof(pbuf), "\nError %u - %s", + ref->code, ref->title); + memset(ubuf, '=', strlen(pbuf)); + ubuf[strlen(pbuf)] = '\0'; + + vty_out(vty, "%s\n%s\n", pbuf, ubuf); + vty_out(vty, "Description:\n%s\n\n", ref->description); + vty_out(vty, "Recommendation:\n%s\n", ref->suggestion); + } + } + + vty_json(vty, top); + list_delete(&errlist); +} + +DEFUN_NOSH(show_error_code, + show_error_code_cmd, + "show error <(1-4294967295)|all> [json]", + SHOW_STR + "Information on errors\n" + "Error code to get info about\n" + "Information on all errors\n" + JSON_STR) +{ + bool json = strmatch(argv[argc-1]->text, "json"); + uint32_t arg = 0; + + if (!strmatch(argv[2]->text, "all")) + arg = strtoul(argv[2]->arg, NULL, 10); + + log_ref_display(vty, arg, json); + return CMD_SUCCESS; +} + +void log_ref_init(void) +{ + frr_with_mutex (&refs_mtx) { + refs = hash_create(ferr_hash_key, ferr_hash_cmp, + "Error Reference Texts"); + } +} + +void log_ref_fini(void) +{ + frr_with_mutex (&refs_mtx) { + hash_clean_and_free(&refs, NULL); + } +} + +void log_ref_vty_init(void) +{ + install_element(VIEW_NODE, &show_error_code_cmd); +} + + +const struct ferr *ferr_get_last(ferr_r errval) +{ + struct ferr *last_error = pthread_getspecific(errkey); + if (!last_error || last_error->kind == 0) + return NULL; + return last_error; +} + +ferr_r ferr_clear(void) +{ + struct ferr *last_error = pthread_getspecific(errkey); + if (last_error) + last_error->kind = 0; + return ferr_ok(); +} + +PRINTFRR(7, 0) +static ferr_r ferr_set_va(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, + int errno_val, const char *text, va_list va) +{ + struct ferr *error = pthread_getspecific(errkey); + + if (!error) { + error = XCALLOC(MTYPE_ERRINFO, sizeof(*error)); + + pthread_setspecific(errkey, error); + } + + error->file = file; + error->line = line; + error->func = func; + error->kind = kind; + + error->unique_id = jhash(text, strlen(text), + jhash(file, strlen(file), 0xd4ed0298)); + + error->errno_val = errno_val; + if (pathname) + snprintf(error->pathname, sizeof(error->pathname), "%s", + pathname); + else + error->pathname[0] = '\0'; + + vsnprintf(error->message, sizeof(error->message), text, va); + return -1; +} + +ferr_r ferr_set_internal(const char *file, int line, const char *func, + enum ferr_kind kind, const char *text, ...) +{ + ferr_r rv; + va_list va; + va_start(va, text); + rv = ferr_set_va(file, line, func, kind, NULL, 0, text, va); + va_end(va); + return rv; +} + +ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, + int errno_val, const char *text, ...) +{ + ferr_r rv; + va_list va; + va_start(va, text); + rv = ferr_set_va(file, line, func, kind, pathname, errno_val, text, va); + va_end(va); + return rv; +} + +#define REPLACE "$ERR" +void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...) +{ + char tmpmsg[512], *replacepos; + const struct ferr *last_error = ferr_get_last(err); + + va_list va; + va_start(va, msg); + vsnprintf(tmpmsg, sizeof(tmpmsg), msg, va); + va_end(va); + + replacepos = strstr(tmpmsg, REPLACE); + if (!replacepos) + vty_out(vty, "%s\n", tmpmsg); + else { + replacepos[0] = '\0'; + replacepos += sizeof(REPLACE) - 1; + vty_out(vty, "%s%s%s\n", tmpmsg, + last_error ? last_error->message : "(no error?)", + replacepos); + } +} diff --git a/lib/ferr.h b/lib/ferr.h new file mode 100644 index 0000000..f0191df --- /dev/null +++ b/lib/ferr.h @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_FERR_H +#define _FRR_FERR_H + +/*********************************************************** + * scroll down to the end of this file for a full example! * + ***********************************************************/ + +#include +#include +#include + +#include "vty.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* return type when this error indication stuff is used. + * + * guaranteed to have boolean evaluation to "false" when OK, "true" when error + * (i.e. can be changed to pointer in the future if necessary) + * + * For checking, always use "if (value)", nothing else. + * Do _NOT_ use any integer constant (!= 0), or sign check (< 0). + */ +typedef int ferr_r; + +/* rough category of error indication */ +enum ferr_kind { + /* no error */ + FERR_OK = 0, + + /* something isn't the way it's supposed to be. + * (things that might otherwise be asserts, really) + */ + FERR_CODE_BUG, + + /* user-supplied parameters don't make sense or is inconsistent + * if you can express a rule for it (e.g. "holdtime > 2 * keepalive"), + * it's this category. + */ + FERR_CONFIG_INVALID, + + /* user-supplied parameters don't line up with reality + * (IP address or interface not available, etc.) + * NB: these are really TODOs where the code needs to be fixed to + * respond to future changes! + */ + FERR_CONFIG_REALITY, + + /* out of some system resource (probably memory) + * aka "you didn't spend enough money error" */ + FERR_RESOURCE, + + /* system error (permission denied, etc.) */ + FERR_SYSTEM, + + /* error return from some external library + * (FERR_SYSTEM and FERR_LIBRARY are not strongly distinct) */ + FERR_LIBRARY, +}; + +struct ferr { + /* code location */ + const char *file; + const char *func; + int line; + + enum ferr_kind kind; + + /* unique_id is calculated as a checksum of source filename and error + * message format (*before* calling vsnprintf). Line number and + * function name are not used; this keeps the number reasonably static + * across changes. + */ + uint32_t unique_id; + + char message[384]; + + /* valid if != 0. note "errno" might be preprocessor foobar. */ + int errno_val; + /* valid if pathname[0] != '\0' */ + char pathname[PATH_MAX]; +}; + +/* Numeric ranges assigned to daemons for use as error codes. */ +#define BABEL_FERR_START 0x01000001 +#define BABEL_FRRR_END 0x01FFFFFF +#define BGP_FERR_START 0x02000001 +#define BGP_FERR_END 0x02FFFFFF +#define EIGRP_FERR_START 0x03000001 +#define EIGRP_FERR_END 0x03FFFFFF +#define ISIS_FERR_START 0x04000001 +#define ISIS_FERR_END 0x04FFFFFF +#define LDP_FERR_START 0x05000001 +#define LDP_FERR_END 0x05FFFFFF +#define LIB_FERR_START 0x06000001 +#define LIB_FERR_END 0x06FFFFFF +#define NHRP_FERR_START 0x07000001 +#define NHRP_FERR_END 0x07FFFFFF +#define OSPF_FERR_START 0x08000001 +#define OSPF_FERR_END 0x08FFFFFF +#define OSPFV3_FERR_START 0x09000001 +#define OSPFV3_FERR_END 0x09FFFFFF +#define PBR_FERR_START 0x0A000001 +#define PBR_FERR_END 0x0AFFFFFF +#define PIM_FERR_START 0x0B000001 +#define PIM_FERR_STOP 0x0BFFFFFF +#define RIP_FERR_START 0x0C000001 +#define RIP_FERR_STOP 0x0CFFFFFF +#define RIPNG_FERR_START 0x0D000001 +#define RIPNG_FERR_STOP 0x0DFFFFFF +#define SHARP_FERR_START 0x0E000001 +#define SHARP_FERR_END 0x0EFFFFFF +#define VTYSH_FERR_START 0x0F000001 +#define VTYSH_FRR_END 0x0FFFFFFF +#define WATCHFRR_FERR_START 0x10000001 +#define WATCHFRR_FERR_END 0x10FFFFFF +#define PATH_FERR_START 0x11000001 +#define PATH_FERR_END 0x11FFFFFF +#define ZEBRA_FERR_START 0xF1000001 +#define ZEBRA_FERR_END 0xF1FFFFFF +#define END_FERR 0xFFFFFFFF + +struct log_ref { + /* Unique error code displayed to end user as a reference. -1 means + * this is an uncoded error that does not have reference material. */ + uint32_t code; + /* Ultra brief title */ + const char *title; + /* Brief description of error */ + const char *description; + /* Remedial suggestion */ + const char *suggestion; +}; + +void log_ref_add(struct log_ref *ref); +struct log_ref *log_ref_get(uint32_t code); +void log_ref_display(struct vty *vty, uint32_t code, bool json); + +/* + * This function should be called by the + * code in libfrr.c + */ +void log_ref_init(void); +void log_ref_fini(void); +void log_ref_vty_init(void); + +/* get error details. + * + * NB: errval/ferr_r does NOT carry the full error information. It's only + * passed around for future API flexibility. ferr_get_last always returns + * the last error set in the current thread. + */ +const struct ferr *ferr_get_last(ferr_r errval); + +/* + * Can optionally be called at strategic locations. + * Always returns 0. + */ +ferr_r ferr_clear(void); + +/* do NOT call these functions directly. only for macro use! */ +ferr_r ferr_set_internal(const char *file, int line, const char *func, + enum ferr_kind kind, const char *text, ...) + PRINTFRR(5, 6); +ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, + int errno_val, const char *text, ...) + PRINTFRR(7, 8); + +#define ferr_ok() 0 + +/* Report an error. + * + * If you need to do cleanup (free memory, etc.), save the return value in a + * variable of type ferr_r. + * + * Don't put a \n at the end of the error message. + */ +#define ferr_code_bug(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CODE_BUG, \ + __VA_ARGS__) +#define ferr_cfg_invalid(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_INVALID, \ + __VA_ARGS__) +#define ferr_cfg_reality(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_REALITY, \ + __VA_ARGS__) +#define ferr_cfg_resource(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_RESOURCE, \ + __VA_ARGS__) +#define ferr_system(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_SYSTEM, \ + __VA_ARGS__) +#define ferr_library(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_LIBRARY, \ + __VA_ARGS__) + +/* extended information variants */ +#define ferr_system_errno(...) \ + ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, NULL, \ + errno, __VA_ARGS__) +#define ferr_system_path_errno(path, ...) \ + ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, path, \ + errno, __VA_ARGS__) + +#include "vty.h" +/* print error message to vty; $ERR is replaced by the error's message */ +void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...) + PRINTFRR(3, 4); + +#define CMD_FERR_DO(func, action, ...) \ + do { \ + ferr_r cmd_retval = func; \ + if (cmd_retval) { \ + vty_print_error(vty, cmd_retval, __VA_ARGS__); \ + action; \ + } \ + } while (0) + +#define CMD_FERR_RETURN(func, ...) \ + CMD_FERR_DO(func, return CMD_WARNING_CONFIG_FAILED, __VA_ARGS__) +#define CMD_FERR_GOTO(func, label, ...) \ + CMD_FERR_DO(func, goto label, __VA_ARGS__) + +/* example: uses bogus #define to keep indent.py happy */ +#ifdef THIS_IS_AN_EXAMPLE +ferr_r foo_bar_set(struct object *obj, int bar) +{ + if (bar < 1 || bar >= 100) + return ferr_config_invalid("bar setting (%d) must be 0bar = bar; + if (ioctl(obj->fd, bar)) + return ferr_system_errno("couldn't set bar to %d", bar); + + return ferr_ok(); +} + +DEFUN("bla") +{ + CMD_FERR_RETURN(foo_bar_set(obj, atoi(argv[1])), + "command failed: $ERR\n"); + return CMD_SUCCESS; +} + +#endif /* THIS_IS_AN_EXAMPLE */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FERR_H */ diff --git a/lib/filter.c b/lib/filter.c new file mode 100644 index 0000000..0722bed --- /dev/null +++ b/lib/filter.c @@ -0,0 +1,909 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Route filtering function. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "filter.h" +#include "memory.h" +#include "command.h" +#include "sockunion.h" +#include "buffer.h" +#include "log.h" +#include "routemap.h" +#include "libfrr.h" +#include "northbound_cli.h" +#include "json.h" + +DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST, "Access List"); +DEFINE_MTYPE_STATIC(LIB, ACCESS_LIST_STR, "Access List Str"); +DEFINE_MTYPE_STATIC(LIB, ACCESS_FILTER, "Access Filter"); + +/* Static structure for mac access_list's master. */ +static struct access_master access_master_mac = { + {NULL, NULL}, + NULL, + NULL, +}; + +/* Static structure for IPv4 access_list's master. */ +static struct access_master access_master_ipv4 = { + {NULL, NULL}, + NULL, + NULL, +}; + +/* Static structure for IPv6 access_list's master. */ +static struct access_master access_master_ipv6 = { + {NULL, NULL}, + NULL, + NULL, +}; + +static struct access_master *access_master_get(afi_t afi) +{ + if (afi == AFI_IP) + return &access_master_ipv4; + else if (afi == AFI_IP6) + return &access_master_ipv6; + else if (afi == AFI_L2VPN) + return &access_master_mac; + return NULL; +} + +/* Allocate new filter structure. */ +struct filter *filter_new(void) +{ + return XCALLOC(MTYPE_ACCESS_FILTER, sizeof(struct filter)); +} + +static void filter_free(struct filter *filter) +{ + XFREE(MTYPE_ACCESS_FILTER, filter); +} + +/* Return string of filter_type. */ +static const char *filter_type_str(struct filter *filter) +{ + switch (filter->type) { + case FILTER_PERMIT: + return "permit"; + case FILTER_DENY: + return "deny"; + case FILTER_DYNAMIC: + return "dynamic"; + default: + return ""; + } +} + +/* If filter match to the prefix then return 1. */ +static int filter_match_cisco(struct filter *mfilter, const struct prefix *p) +{ + struct filter_cisco *filter; + struct in_addr mask; + uint32_t check_addr; + uint32_t check_mask; + + filter = &mfilter->u.cfilter; + check_addr = p->u.prefix4.s_addr & ~filter->addr_mask.s_addr; + + if (filter->extended) { + masklen2ip(p->prefixlen, &mask); + check_mask = mask.s_addr & ~filter->mask_mask.s_addr; + + if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN) + == 0 + && memcmp(&check_mask, &filter->mask.s_addr, + IPV4_MAX_BYTELEN) + == 0) + return 1; + } else if (memcmp(&check_addr, &filter->addr.s_addr, IPV4_MAX_BYTELEN) + == 0) + return 1; + + return 0; +} + +/* If filter match to the prefix then return 1. */ +static int filter_match_zebra(struct filter *mfilter, const struct prefix *p) +{ + struct filter_zebra *filter = NULL; + + filter = &mfilter->u.zfilter; + + if (filter->prefix.family == p->family) { + if (filter->exact) { + if (filter->prefix.prefixlen == p->prefixlen) + return prefix_match(&filter->prefix, p); + else + return 0; + } else + return prefix_match(&filter->prefix, p); + } else + return 0; +} + +/* Allocate new access list structure. */ +static struct access_list *access_list_new(void) +{ + return XCALLOC(MTYPE_ACCESS_LIST, sizeof(struct access_list)); +} + +/* Free allocated access_list. */ +static void access_list_free(struct access_list *access) +{ + XFREE(MTYPE_ACCESS_LIST, access); +} + +/* Delete access_list from access_master and free it. */ +void access_list_delete(struct access_list *access) +{ + struct filter *filter; + struct filter *next; + struct access_list_list *list; + struct access_master *master; + + for (filter = access->head; filter; filter = next) { + next = filter->next; + filter_free(filter); + } + + master = access->master; + + list = &master->str; + + if (access->next) + access->next->prev = access->prev; + else + list->tail = access->prev; + + if (access->prev) + access->prev->next = access->next; + else + list->head = access->next; + + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); + + if (master->delete_hook) + master->delete_hook(access); + + XFREE(MTYPE_ACCESS_LIST_STR, access->name); + + XFREE(MTYPE_TMP, access->remark); + + access_list_free(access); +} + +/* Insert new access list to list of access_list. Each access_list + is sorted by the name. */ +static struct access_list *access_list_insert(afi_t afi, const char *name) +{ + struct access_list *access; + struct access_list *point; + struct access_list_list *alist; + struct access_master *master; + + master = access_master_get(afi); + if (master == NULL) + return NULL; + + /* Allocate new access_list and copy given name. */ + access = access_list_new(); + access->name = XSTRDUP(MTYPE_ACCESS_LIST_STR, name); + access->master = master; + + /* Set access_list to string list. */ + alist = &master->str; + + /* Set point to insertion point. */ + for (point = alist->head; point; point = point->next) + if (strcmp(point->name, name) >= 0) + break; + + /* In case of this is the first element of master. */ + if (alist->head == NULL) { + alist->head = alist->tail = access; + return access; + } + + /* In case of insertion is made at the tail of access_list. */ + if (point == NULL) { + access->prev = alist->tail; + alist->tail->next = access; + alist->tail = access; + return access; + } + + /* In case of insertion is made at the head of access_list. */ + if (point == alist->head) { + access->next = alist->head; + alist->head->prev = access; + alist->head = access; + return access; + } + + /* Insertion is made at middle of the access_list. */ + access->next = point; + access->prev = point->prev; + + if (point->prev) + point->prev->next = access; + point->prev = access; + + return access; +} + +/* Lookup access_list from list of access_list by name. */ +struct access_list *access_list_lookup(afi_t afi, const char *name) +{ + struct access_list *access; + struct access_master *master; + + if (name == NULL) + return NULL; + + master = access_master_get(afi); + if (master == NULL) + return NULL; + + for (access = master->str.head; access; access = access->next) + if (strcmp(access->name, name) == 0) + return access; + + return NULL; +} + +/* Get access list from list of access_list. If there isn't matched + access_list create new one and return it. */ +struct access_list *access_list_get(afi_t afi, const char *name) +{ + struct access_list *access; + + access = access_list_lookup(afi, name); + if (access == NULL) + access = access_list_insert(afi, name); + return access; +} + +/* Apply access list to object (which should be struct prefix *). */ +enum filter_type access_list_apply(struct access_list *access, + const void *object) +{ + struct filter *filter; + const struct prefix *p = (const struct prefix *)object; + + if (access == NULL) + return FILTER_DENY; + + for (filter = access->head; filter; filter = filter->next) { + if (filter->cisco) { + if (filter_match_cisco(filter, p)) + return filter->type; + } else { + if (filter_match_zebra(filter, p)) + return filter->type; + } + } + + return FILTER_DENY; +} + +/* Add hook function. */ +void access_list_add_hook(void (*func)(struct access_list *access)) +{ + access_master_ipv4.add_hook = func; + access_master_ipv6.add_hook = func; + access_master_mac.add_hook = func; +} + +/* Delete hook function. */ +void access_list_delete_hook(void (*func)(struct access_list *access)) +{ + access_master_ipv4.delete_hook = func; + access_master_ipv6.delete_hook = func; + access_master_mac.delete_hook = func; +} + +/* Calculate new sequential number. */ +int64_t filter_new_seq_get(struct access_list *access) +{ + int64_t maxseq; + int64_t newseq; + struct filter *filter; + + maxseq = 0; + + for (filter = access->head; filter; filter = filter->next) { + if (maxseq < filter->seq) + maxseq = filter->seq; + } + + newseq = ((maxseq / 5) * 5) + 5; + + return (newseq > UINT_MAX) ? UINT_MAX : newseq; +} + +/* Return access list entry which has same seq number. */ +static struct filter *filter_seq_check(struct access_list *access, + int64_t seq) +{ + struct filter *filter; + + for (filter = access->head; filter; filter = filter->next) + if (filter->seq == seq) + return filter; + return NULL; +} + +/* Delete filter from specified access_list. If there is hook + function execute it. */ +void access_list_filter_delete(struct access_list *access, + struct filter *filter) +{ + struct access_master *master; + + master = access->master; + + if (filter->next) + filter->next->prev = filter->prev; + else + access->tail = filter->prev; + + if (filter->prev) + filter->prev->next = filter->next; + else + access->head = filter->next; + + filter_free(filter); + + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_DELETED); + /* Run hook function. */ + if (master->delete_hook) + (*master->delete_hook)(access); +} + +/* Add new filter to the end of specified access_list. */ +void access_list_filter_add(struct access_list *access, + struct filter *filter) +{ + struct filter *replace; + struct filter *point; + + /* Automatic assignment of seq no. */ + if (filter->seq == -1) + filter->seq = filter_new_seq_get(access); + + if (access->tail && filter->seq > access->tail->seq) + point = NULL; + else { + /* Is there any same seq access list filter? */ + replace = filter_seq_check(access, filter->seq); + if (replace) + access_list_filter_delete(access, replace); + + /* Check insert point. */ + for (point = access->head; point; point = point->next) + if (point->seq >= filter->seq) + break; + } + + /* In case of this is the first element of the list. */ + filter->next = point; + + if (point) { + if (point->prev) + point->prev->next = filter; + else + access->head = filter; + + filter->prev = point->prev; + point->prev = filter; + } else { + if (access->tail) + access->tail->next = filter; + else + access->head = filter; + + filter->prev = access->tail; + access->tail = filter; + } +} + +void access_list_filter_update(struct access_list *access) +{ + /* Run hook function. */ + if (access->master->add_hook) + (*access->master->add_hook)(access); + route_map_notify_dependencies(access->name, RMAP_EVENT_FILTER_ADDED); +} + +/* + deny Specify packets to reject + permit Specify packets to forward + dynamic ? +*/ + +/* + Hostname or A.B.C.D Address to match + any Any source host + host A single host address +*/ + +static void config_write_access_zebra(struct vty *, struct filter *, + json_object *); +static void config_write_access_cisco(struct vty *, struct filter *, + json_object *); + +static const char *filter_type2str(struct filter *filter) +{ + if (filter->cisco) { + if (filter->u.cfilter.extended) + return "Extended"; + else + return "Standard"; + } else + return "Zebra"; +} + +/* show access-list command. */ +static int filter_show(struct vty *vty, const char *name, afi_t afi, + bool use_json) +{ + struct access_list *access; + struct access_master *master; + struct filter *mfilter; + struct filter_cisco *filter; + bool first; + json_object *json = NULL; + + master = access_master_get(afi); + if (master == NULL) { + if (use_json) + vty_out(vty, "{}\n"); + return 0; + } + + if (use_json) + json = json_object_new_object(); + else + vty_out(vty, "%s:\n", frr_protoname); + + for (access = master->str.head; access; access = access->next) { + json_object *json_acl = NULL; + json_object *json_rules = NULL; + + if (name && strcmp(access->name, name) != 0) + continue; + + first = true; + + for (mfilter = access->head; mfilter; mfilter = mfilter->next) { + json_object *json_rule = NULL; + + filter = &mfilter->u.cfilter; + + if (first) { + const char *type = filter_type2str(mfilter); + + if (json) { + json_acl = json_object_new_object(); + json_object_object_add(json, + access->name, + json_acl); + + json_object_string_add(json_acl, "type", + type); + json_object_string_add(json_acl, + "addressFamily", + afi2str(afi)); + json_rules = json_object_new_array(); + json_object_object_add( + json_acl, "rules", json_rules); + } else { + vty_out(vty, "%s %s access list %s\n", + type, + (afi == AFI_IP) + ? ("IP") + : ((afi == AFI_IP6) + ? ("IPv6 ") + : ("MAC ")), + access->name); + } + + first = false; + } + + if (json) { + json_rule = json_object_new_object(); + json_object_array_add(json_rules, json_rule); + + json_object_int_add(json_rule, "sequenceNumber", + mfilter->seq); + json_object_string_add( + json_rule, "filterType", + filter_type_str(mfilter)); + } else { + vty_out(vty, " seq %" PRId64, mfilter->seq); + vty_out(vty, " %s%s", filter_type_str(mfilter), + mfilter->type == FILTER_DENY ? " " + : ""); + } + + if (!mfilter->cisco) + config_write_access_zebra(vty, mfilter, + json_rule); + else if (filter->extended) + config_write_access_cisco(vty, mfilter, + json_rule); + else { + if (json) { + json_object_string_addf( + json_rule, "address", "%pI4", + &filter->addr); + json_object_string_addf( + json_rule, "mask", "%pI4", + &filter->addr_mask); + } else { + if (filter->addr_mask.s_addr + == 0xffffffff) + vty_out(vty, " any\n"); + else { + vty_out(vty, " %pI4", + &filter->addr); + if (filter->addr_mask.s_addr + != INADDR_ANY) + vty_out(vty, + ", wildcard bits %pI4", + &filter->addr_mask); + vty_out(vty, "\n"); + } + } + } + } + } + + return vty_json(vty, json); +} + +/* show MAC access list - this only has MAC filters for now*/ +DEFUN (show_mac_access_list, + show_mac_access_list_cmd, + "show mac access-list", + SHOW_STR + "mac access lists\n" + "List mac access lists\n") +{ + return filter_show(vty, NULL, AFI_L2VPN, false); +} + +DEFUN (show_mac_access_list_name, + show_mac_access_list_name_cmd, + "show mac access-list ACCESSLIST_MAC_NAME", + SHOW_STR + "mac access lists\n" + "List mac access lists\n" + "mac address\n") +{ + return filter_show(vty, argv[3]->arg, AFI_L2VPN, false); +} + +DEFUN_NOSH (show_ip_access_list, + show_ip_access_list_cmd, + "show ip access-list [json]", + SHOW_STR + IP_STR + "List IP access lists\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + return filter_show(vty, NULL, AFI_IP, uj); +} + +DEFUN_NOSH (show_ip_access_list_name, + show_ip_access_list_name_cmd, + "show ip access-list ACCESSLIST4_NAME [json]", + SHOW_STR + IP_STR + "List IP access lists\n" + "IP access-list name\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx_acl = 3; + return filter_show(vty, argv[idx_acl]->arg, AFI_IP, uj); +} + +DEFUN_NOSH (show_ipv6_access_list, + show_ipv6_access_list_cmd, + "show ipv6 access-list [json]", + SHOW_STR + IPV6_STR + "List IPv6 access lists\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + return filter_show(vty, NULL, AFI_IP6, uj); +} + +DEFUN_NOSH (show_ipv6_access_list_name, + show_ipv6_access_list_name_cmd, + "show ipv6 access-list ACCESSLIST6_NAME [json]", + SHOW_STR + IPV6_STR + "List IPv6 access lists\n" + "IPv6 access-list name\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx_word = 3; + return filter_show(vty, argv[idx_word]->arg, AFI_IP6, uj); +} + +static void config_write_access_cisco(struct vty *vty, struct filter *mfilter, + json_object *json) +{ + struct filter_cisco *filter; + + filter = &mfilter->u.cfilter; + + if (json) { + json_object_boolean_add(json, "extended", !!filter->extended); + json_object_string_addf(json, "sourceAddress", "%pI4", + &filter->addr); + json_object_string_addf(json, "sourceMask", "%pI4", + &filter->addr_mask); + json_object_string_addf(json, "destinationAddress", "%pI4", + &filter->mask); + json_object_string_addf(json, "destinationMask", "%pI4", + &filter->mask_mask); + } else { + vty_out(vty, " ip"); + if (filter->addr_mask.s_addr == 0xffffffff) + vty_out(vty, " any"); + else if (filter->addr_mask.s_addr == INADDR_ANY) + vty_out(vty, " host %pI4", &filter->addr); + else { + vty_out(vty, " %pI4", &filter->addr); + vty_out(vty, " %pI4", &filter->addr_mask); + } + + if (filter->mask_mask.s_addr == 0xffffffff) + vty_out(vty, " any"); + else if (filter->mask_mask.s_addr == INADDR_ANY) + vty_out(vty, " host %pI4", &filter->mask); + else { + vty_out(vty, " %pI4", &filter->mask); + vty_out(vty, " %pI4", &filter->mask_mask); + } + vty_out(vty, "\n"); + } +} + +static void config_write_access_zebra(struct vty *vty, struct filter *mfilter, + json_object *json) +{ + struct filter_zebra *filter; + struct prefix *p; + char buf[BUFSIZ]; + + filter = &mfilter->u.zfilter; + p = &filter->prefix; + + if (json) { + json_object_string_addf(json, "prefix", "%pFX", p); + json_object_boolean_add(json, "exact-match", !!filter->exact); + } else { + if (p->prefixlen == 0 && !filter->exact) + vty_out(vty, " any"); + else if (p->family == AF_INET6 || p->family == AF_INET) + vty_out(vty, " %pFX%s", p, + filter->exact ? " exact-match" : ""); + else if (p->family == AF_ETHERNET) { + if (p->prefixlen == 0) + vty_out(vty, " any"); + else + vty_out(vty, " %s", + prefix_mac2str(&(p->u.prefix_eth), buf, + sizeof(buf))); + } + + vty_out(vty, "\n"); + } +} + +static struct cmd_node access_mac_node = { + .name = "MAC access list", + .node = ACCESS_MAC_NODE, + .prompt = "", +}; + +static void access_list_reset_mac(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_L2VPN); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +/* Install vty related command. */ +static void access_list_init_mac(void) +{ + install_node(&access_mac_node); + + install_element(ENABLE_NODE, &show_mac_access_list_cmd); + install_element(ENABLE_NODE, &show_mac_access_list_name_cmd); +} + +/* Access-list node. */ +static int config_write_access(struct vty *vty); +static struct cmd_node access_node = { + .name = "ipv4 access list", + .node = ACCESS_NODE, + .prompt = "", + .config_write = config_write_access, +}; + +static int config_write_access(struct vty *vty) +{ + struct lyd_node *dnode; + int written = 0; + + dnode = yang_dnode_get(running_config->dnode, "/frr-filter:lib"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + + return written; +} + +static void access_list_reset_ipv4(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_IP); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +/* Install vty related command. */ +static void access_list_init_ipv4(void) +{ + install_node(&access_node); + + install_element(ENABLE_NODE, &show_ip_access_list_cmd); + install_element(ENABLE_NODE, &show_ip_access_list_name_cmd); +} + +static void access_list_autocomplete_afi(afi_t afi, vector comps, + struct cmd_token *token) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(afi); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, access->name)); + } +} + +static struct cmd_node access_ipv6_node = { + .name = "ipv6 access list", + .node = ACCESS_IPV6_NODE, + .prompt = "", +}; + +static void access_list_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP, comps, token); + access_list_autocomplete_afi(AFI_IP6, comps, token); + access_list_autocomplete_afi(AFI_L2VPN, comps, token); +} + +static void access_list4_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP, comps, token); +} + +static void access_list6_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_IP6, comps, token); +} + +static void access_list_mac_autocomplete(vector comps, struct cmd_token *token) +{ + access_list_autocomplete_afi(AFI_L2VPN, comps, token); +} + +static const struct cmd_variable_handler access_list_handlers[] = { + {.tokenname = "ACCESSLIST_NAME", + .completions = access_list_autocomplete}, + {.tokenname = "ACCESSLIST4_NAME", + .completions = access_list4_autocomplete}, + {.tokenname = "ACCESSLIST6_NAME", + .completions = access_list6_autocomplete}, + {.tokenname = "ACCESSLIST_MAC_NAME", + .completions = access_list_mac_autocomplete}, + {.completions = NULL}}; + +static void access_list_reset_ipv6(void) +{ + struct access_list *access; + struct access_list *next; + struct access_master *master; + + master = access_master_get(AFI_IP6); + if (master == NULL) + return; + + for (access = master->str.head; access; access = next) { + next = access->next; + access_list_delete(access); + } + + assert(master->str.head == NULL); + assert(master->str.tail == NULL); +} + +static void access_list_init_ipv6(void) +{ + install_node(&access_ipv6_node); + + install_element(ENABLE_NODE, &show_ipv6_access_list_cmd); + install_element(ENABLE_NODE, &show_ipv6_access_list_name_cmd); +} + +void access_list_init_new(bool in_backend) +{ + cmd_variable_handler_register(access_list_handlers); + + access_list_init_ipv4(); + access_list_init_ipv6(); + access_list_init_mac(); + + if (!in_backend) { + /* we do not want to handle config commands in the backend */ + filter_cli_init(); + } +} + +void access_list_init(void) +{ + access_list_init_new(false); +} + +void access_list_reset(void) +{ + access_list_reset_ipv4(); + access_list_reset_ipv6(); + access_list_reset_mac(); +} diff --git a/lib/filter.h b/lib/filter.h new file mode 100644 index 0000000..4fa482b --- /dev/null +++ b/lib/filter.h @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Route filtering function. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_FILTER_H +#define _ZEBRA_FILTER_H + +#include "if.h" +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maximum ACL name length */ +#define ACL_NAMSIZ 128 + +/** Cisco host wildcard mask. */ +#define CISCO_HOST_WILDCARD_MASK "0.0.0.0" +/** Cisco host wildcard binary mask. */ +#define CISCO_BIN_HOST_WILDCARD_MASK INADDR_ANY + +/** Cisco any wildcard mask. */ +#define CISCO_ANY_WILDCARD_MASK "255.255.255.255" +/** Cisco binary any wildcard mask. */ +#define CISCO_BIN_ANY_WILDCARD_MASK INADDR_NONE + +/* Filter direction. */ +#define FILTER_IN 0 +#define FILTER_OUT 1 +#define FILTER_MAX 2 + +/* Filter type is made by `permit', `deny' and `dynamic'. */ +enum filter_type { FILTER_DENY, FILTER_PERMIT, FILTER_DYNAMIC }; + +struct filter_cisco { + /* Cisco access-list */ + int extended; + struct in_addr addr; + struct in_addr addr_mask; + struct in_addr mask; + struct in_addr mask_mask; +}; + +struct filter_zebra { + /* If this filter is "exact" match then this flag is set. */ + int exact; + + /* Prefix information. */ + struct prefix prefix; +}; + +/* Forward declaration of access-list struct. */ +struct access_list; + +/* Filter element of access list */ +struct filter { + /* For doubly linked list. */ + struct filter *next; + struct filter *prev; + + /* Parent access-list pointer. */ + struct access_list *acl; + + /* Filter type information. */ + enum filter_type type; + + /* Sequence number */ + int64_t seq; + + /* Cisco access-list */ + int cisco; + + union { + struct filter_cisco cfilter; + struct filter_zebra zfilter; + } u; +}; + +/* Access list */ +struct access_list { + char *name; + char *remark; + + struct access_master *master; + + struct access_list *next; + struct access_list *prev; + + struct filter *head; + struct filter *tail; +}; + +/* List of access_list. */ +struct access_list_list { + struct access_list *head; + struct access_list *tail; +}; + +/* Master structure of access_list. */ +struct access_master { + /* List of access_list which name is string. */ + struct access_list_list str; + + /* Hook function which is executed when new access_list is added. */ + void (*add_hook)(struct access_list *); + + /* Hook function which is executed when access_list is deleted. */ + void (*delete_hook)(struct access_list *); +}; + + +/* Prototypes for access-list. */ +extern void access_list_init(void); +extern void access_list_init_new(bool in_backend); +extern void access_list_reset(void); +extern void access_list_add_hook(void (*func)(struct access_list *)); +extern void access_list_delete_hook(void (*func)(struct access_list *)); +extern struct access_list *access_list_lookup(afi_t, const char *); +extern enum filter_type access_list_apply(struct access_list *access, + const void *object); + +struct access_list *access_list_get(afi_t afi, const char *name); +void access_list_delete(struct access_list *access); +struct filter *filter_new(void); +void access_list_filter_add(struct access_list *access, struct filter *filter); +void access_list_filter_delete(struct access_list *access, + struct filter *filter); +void access_list_filter_update(struct access_list *access); +int64_t filter_new_seq_get(struct access_list *access); + +extern const struct frr_yang_module_info frr_filter_info; +extern const struct frr_yang_module_info frr_filter_cli_info; + + +/* filter_nb.c */ +enum yang_access_list_type { + YALT_IPV4 = 0, + YALT_IPV6 = 1, + YALT_MAC = 2, +}; + +enum yang_prefix_list_type { + YPLT_IPV4 = 0, + YPLT_IPV6 = 1, +}; + +enum yang_prefix_list_action { + YPLA_DENY = 0, + YPLA_PERMIT = 1, +}; + +struct acl_dup_args { + /** Access list type ("ipv4", "ipv6" or "mac"). */ + const char *ada_type; + /** Access list name. */ + const char *ada_name; + + /** Entry action. */ + const char *ada_action; + +#define ADA_MAX_VALUES 4 + /** Entry XPath for value. */ + const char *ada_xpath[ADA_MAX_VALUES]; + /** Entry value to match. */ + const char *ada_value[ADA_MAX_VALUES]; + + /** Duplicated entry found in list? */ + bool ada_found; + + /** Sequence number of the found entry */ + int64_t ada_seq; + + /** (Optional) Already existing `dnode`. */ + const struct lyd_node *ada_entry_dnode; +}; + +/** + * Check for duplicated entries using the candidate configuration. + * + * \param vty so we can get the candidate config. + * \param ada the arguments to check. + */ +bool acl_is_dup(const struct lyd_node *dnode, struct acl_dup_args *ada); + +struct plist_dup_args { + /** Access list type ("ipv4" or "ipv6"). */ + const char *pda_type; + /** Access list name. */ + const char *pda_name; + + /** Entry action. */ + const char *pda_action; + + bool any; + struct prefix prefix; + int ge; + int le; + + /** Duplicated entry found in list? */ + bool pda_found; + + /** Sequence number of the found entry */ + int64_t pda_seq; + + /** (Optional) Already existing `dnode`. */ + const struct lyd_node *pda_entry_dnode; +}; + +/** + * Check for duplicated entries using the candidate configuration. + * + * \param vty so we can get the candidate config. + * \param pda the arguments to check. + */ +bool plist_is_dup(const struct lyd_node *dnode, struct plist_dup_args *pda); + +/* filter_cli.c */ +struct lyd_node; +struct vty; + +extern int access_list_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2); +extern void access_list_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void access_list_remark_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern int prefix_list_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2); +extern void prefix_list_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void prefix_list_remark_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); + +void filter_cli_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_FILTER_H */ diff --git a/lib/filter_cli.c b/lib/filter_cli.c new file mode 100644 index 0000000..c40c2a7 --- /dev/null +++ b/lib/filter_cli.c @@ -0,0 +1,1680 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR filter CLI implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include "zebra.h" +#include "northbound.h" +#include "prefix.h" + +#include "lib/command.h" +#include "lib/filter.h" +#include "lib/northbound_cli.h" +#include "lib/plist.h" +#include "lib/plist_int.h" +#include "lib/printfrr.h" + +#include "lib/filter_cli_clippy.c" + +#define ACCESS_LIST_STR "Access list entry\n" +#define ACCESS_LIST_ZEBRA_STR "Access list name\n" +#define ACCESS_LIST_SEQ_STR \ + "Sequence number of an entry\n" \ + "Sequence number\n" +#define ACCESS_LIST_ACTION_STR \ + "Specify packets to reject\n" \ + "Specify packets to forward\n" +#define ACCESS_LIST_REMARK_STR "Access list entry comment\n" +#define ACCESS_LIST_REMARK_LINE_STR "Comment up to 100 characters\n" + +#define PREFIX_LIST_NAME_STR "Prefix list entry name\n" + +/* + * Helper function to generate a sequence number for legacy commands. + */ +static int acl_get_seq_cb(const struct lyd_node *dnode, void *arg) +{ + int64_t *seq = arg; + int64_t cur_seq = yang_dnode_get_uint32(dnode, "sequence"); + + if (cur_seq > *seq) + *seq = cur_seq; + + return YANG_ITER_CONTINUE; +} + +/** + * Helper function that iterates over the XPath `xpath` on the candidate + * configuration in `vty->candidate_config`. + * + * \param[in] vty shell context with the candidate configuration. + * \param[in] xpath the XPath to look for the sequence leaf. + * \returns next unused sequence number, -1 if out of range when adding. + */ +static int64_t acl_get_seq(struct vty *vty, const char *xpath, bool is_remove) +{ + int64_t seq = 0; + + yang_dnode_iterate(acl_get_seq_cb, &seq, vty->candidate_config->dnode, + "%s/entry", xpath); + + seq += 5; + if (!is_remove && seq > UINT32_MAX) { + vty_out(vty, "%% Malformed sequence value\n"); + return -1; + } + return seq; +} + +/** + * Remove main data structure filter list if there are no more entries or + * remark. This fixes compatibility with old CLI and tests. + */ +static int filter_remove_check_empty(struct vty *vty, const char *ftype, + const char *iptype, const char *name, + uint32_t del_seq, bool del_remark) +{ + const struct lyd_node *remark_dnode = NULL; + const struct lyd_node *entry_dnode = NULL; + char xpath[XPATH_MAXLEN]; + uint32_t count; + + /* Count existing entries */ + count = yang_dnode_count(vty->candidate_config->dnode, + "/frr-filter:lib/%s-list[type='%s'][name='%s']/entry", + ftype, iptype, name); + + /* Check entry-to-delete actually exists */ + if (del_seq) { + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/%s-list[type='%s'][name='%s']/entry[sequence='%u']", + ftype, iptype, name, del_seq); + entry_dnode = yang_dnode_get(vty->candidate_config->dnode, + xpath); + + /* If exists, delete and don't count it, we need only remaining entries */ + if (entry_dnode) { + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + count--; + } + } + + /* Delete the remark, or check whether it exists if we're keeping it */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/%s-list[type='%s'][name='%s']/remark", ftype, + iptype, name); + if (del_remark) + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + else + remark_dnode = yang_dnode_get(vty->candidate_config->dnode, + xpath); + + /* If there are no entries left and no remark, delete the whole list */ + if (count == 0 && !remark_dnode) { + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/%s-list[type='%s'][name='%s']", ftype, + iptype, name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + + return nb_cli_apply_changes(vty, NULL); +} + +/* + * Cisco (legacy) access lists. + */ +DEFPY_YANG( + access_list_std, access_list_std_cmd, + "access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action <[host] A.B.C.D$host|A.B.C.D$host A.B.C.D$mask>", + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "A single host address\n" + "Address to match\n" + "Address to match\n" + "Wildcard bits\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + if (host_str && mask_str == NULL) { + ada.ada_xpath[0] = "./host"; + ada.ada_value[0] = host_str; + } else if (host_str && mask_str) { + ada.ada_xpath[0] = "./network/address"; + ada.ada_value[0] = host_str; + ada.ada_xpath[1] = "./network/mask"; + ada.ada_value[1] = mask_str; + } else { + ada.ada_xpath[0] = "./source-any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + return CMD_SUCCESS; + + /* + * Create the access-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (host_str != NULL && mask_str == NULL) { + nb_cli_enqueue_change(vty, "./host", NB_OP_MODIFY, host_str); + } else if (host_str != NULL && mask_str != NULL) { + nb_cli_enqueue_change(vty, "./network/address", NB_OP_MODIFY, + host_str); + nb_cli_enqueue_change(vty, "./network/mask", NB_OP_MODIFY, + mask_str); + } else { + nb_cli_enqueue_change(vty, "./source-any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_access_list_std, no_access_list_std_cmd, + "no access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action <[host] A.B.C.D$host|A.B.C.D$host A.B.C.D$mask>", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "A single host address\n" + "Address to match\n" + "Address to match\n" + "Wildcard bits\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq_str != NULL) + return filter_remove_check_empty(vty, "access", "ipv4", name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + if (host_str && mask_str == NULL) { + ada.ada_xpath[0] = "./host"; + ada.ada_value[0] = host_str; + } else if (host_str && mask_str) { + ada.ada_xpath[0] = "./network/address"; + ada.ada_value[0] = host_str; + ada.ada_xpath[1] = "./network/mask"; + ada.ada_value[1] = mask_str; + } else { + ada.ada_xpath[0] = "./source-any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + sseq = ada.ada_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "access", "ipv4", name, sseq, + false); +} + +DEFPY_YANG( + access_list_ext, access_list_ext_cmd, + "access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action ip ", + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "IPv4 address\n" + "Source address to match\n" + "Source address mask to apply\n" + "Single source host\n" + "Source address to match\n" + "Any source host\n" + "Destination address to match\n" + "Destination address mask to apply\n" + "Single destination host\n" + "Destination address to match\n" + "Any destination host\n") +{ + int idx = 0; + int64_t sseq; + struct acl_dup_args ada = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + if (src_str && src_mask_str == NULL) { + ada.ada_xpath[idx] = "./host"; + ada.ada_value[idx] = src_str; + idx++; + } else if (src_str && src_mask_str) { + ada.ada_xpath[idx] = "./network/address"; + ada.ada_value[idx] = src_str; + idx++; + ada.ada_xpath[idx] = "./network/mask"; + ada.ada_value[idx] = src_mask_str; + idx++; + } else { + ada.ada_xpath[idx] = "./source-any"; + ada.ada_value[idx] = ""; + idx++; + } + + if (dst_str && dst_mask_str == NULL) { + ada.ada_xpath[idx] = "./destination-host"; + ada.ada_value[idx] = dst_str; + idx++; + } else if (dst_str && dst_mask_str) { + ada.ada_xpath[idx] = "./destination-network/address"; + ada.ada_value[idx] = dst_str; + idx++; + ada.ada_xpath[idx] = "./destination-network/mask"; + ada.ada_value[idx] = dst_mask_str; + idx++; + } else { + ada.ada_xpath[idx] = "./destination-any"; + ada.ada_value[idx] = ""; + idx++; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + return CMD_SUCCESS; + + /* + * Create the access-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (src_str != NULL && src_mask_str == NULL) { + nb_cli_enqueue_change(vty, "./host", NB_OP_MODIFY, src_str); + } else if (src_str != NULL && src_mask_str != NULL) { + nb_cli_enqueue_change(vty, "./network/address", NB_OP_MODIFY, + src_str); + nb_cli_enqueue_change(vty, "./network/mask", NB_OP_MODIFY, + src_mask_str); + } else { + nb_cli_enqueue_change(vty, "./source-any", NB_OP_CREATE, NULL); + } + + if (dst_str != NULL && dst_mask_str == NULL) { + nb_cli_enqueue_change(vty, "./destination-host", NB_OP_MODIFY, + dst_str); + } else if (dst_str != NULL && dst_mask_str != NULL) { + nb_cli_enqueue_change(vty, "./destination-network/address", + NB_OP_MODIFY, dst_str); + nb_cli_enqueue_change(vty, "./destination-network/mask", + NB_OP_MODIFY, dst_mask_str); + } else { + nb_cli_enqueue_change(vty, "./destination-any", NB_OP_CREATE, + NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_access_list_ext, no_access_list_ext_cmd, + "no access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action ip ", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Any Internet Protocol\n" + "Source address to match\n" + "Source address mask to apply\n" + "Single source host\n" + "Source address to match\n" + "Any source host\n" + "Destination address to match\n" + "Destination address mask to apply\n" + "Single destination host\n" + "Destination address to match\n" + "Any destination host\n") +{ + int idx = 0; + int64_t sseq; + struct acl_dup_args ada = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq_str != NULL) + return filter_remove_check_empty(vty, "access", "ipv4", name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + if (src_str && src_mask_str == NULL) { + ada.ada_xpath[idx] = "./host"; + ada.ada_value[idx] = src_str; + idx++; + } else if (src_str && src_mask_str) { + ada.ada_xpath[idx] = "./network/address"; + ada.ada_value[idx] = src_str; + idx++; + ada.ada_xpath[idx] = "./network/mask"; + ada.ada_value[idx] = src_mask_str; + idx++; + } else { + ada.ada_xpath[idx] = "./source-any"; + ada.ada_value[idx] = ""; + idx++; + } + + if (dst_str && dst_mask_str == NULL) { + ada.ada_xpath[idx] = "./destination-host"; + ada.ada_value[idx] = dst_str; + idx++; + } else if (dst_str && dst_mask_str) { + ada.ada_xpath[idx] = "./destination-network/address"; + ada.ada_value[idx] = dst_str; + idx++; + ada.ada_xpath[idx] = "./destination-network/mask"; + ada.ada_value[idx] = dst_mask_str; + idx++; + } else { + ada.ada_xpath[idx] = "./destination-any"; + ada.ada_value[idx] = ""; + idx++; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + sseq = ada.ada_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "access", "ipv4", name, sseq, + false); +} + +/* + * Zebra access lists. + */ +DEFPY_YANG( + access_list, access_list_cmd, + "access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action ", + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Prefix to match. e.g. 10.0.0.0/8\n" + "Exact match of the prefixes\n" + "Match any IPv4\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + + if (prefix_str) { + ada.ada_xpath[0] = "./ipv4-prefix"; + ada.ada_value[0] = prefix_str; + if (exact) { + ada.ada_xpath[1] = "./ipv4-exact-match"; + ada.ada_value[1] = "true"; + } + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + return CMD_SUCCESS; + + /* + * Create the access-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (prefix_str != NULL) { + nb_cli_enqueue_change(vty, "./ipv4-prefix", NB_OP_MODIFY, + prefix_str); + nb_cli_enqueue_change(vty, "./ipv4-exact-match", NB_OP_MODIFY, + exact ? "true" : "false"); + } else { + nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_access_list, no_access_list_cmd, + "no access-list ACCESSLIST4_NAME$name [seq (1-4294967295)$seq] $action ", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Prefix to match. e.g. 10.0.0.0/8\n" + "Exact match of the prefixes\n" + "Match any IPv4\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq_str != NULL) + return filter_remove_check_empty(vty, "access", "ipv4", name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + ada.ada_type = "ipv4"; + ada.ada_name = name; + ada.ada_action = action; + + if (prefix_str) { + ada.ada_xpath[0] = "./ipv4-prefix"; + ada.ada_value[0] = prefix_str; + if (exact) { + ada.ada_xpath[1] = "./ipv4-exact-match"; + ada.ada_value[1] = "true"; + } + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + sseq = ada.ada_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "access", "ipv4", name, sseq, + false); +} + +DEFPY_YANG( + no_access_list_all, no_access_list_all_cmd, + "no access-list ACCESSLIST4_NAME$name", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + access_list_remark, access_list_remark_cmd, + "access-list ACCESSLIST4_NAME$name remark LINE...", + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) +{ + int rv; + char *remark; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv4'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + remark = argv_concat(argv, argc, 3); + nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark); + rv = nb_cli_apply_changes(vty, "%s", xpath); + XFREE(MTYPE_TMP, remark); + + return rv; +} + +DEFPY_YANG( + no_access_list_remark, no_access_list_remark_cmd, + "no access-list ACCESSLIST4_NAME$name remark", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR) +{ + return filter_remove_check_empty(vty, "access", "ipv4", name, 0, true); +} + +ALIAS( + no_access_list_remark, no_access_list_remark_line_cmd, + "no access-list ACCESSLIST4_NAME$name remark LINE...", + NO_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) + +DEFPY_YANG( + ipv6_access_list, ipv6_access_list_cmd, + "ipv6 access-list ACCESSLIST6_NAME$name [seq (1-4294967295)$seq] $action ", + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "IPv6 prefix\n" + "Exact match of the prefixes\n" + "Match any IPv6\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + ada.ada_type = "ipv6"; + ada.ada_name = name; + ada.ada_action = action; + + if (prefix_str) { + ada.ada_xpath[0] = "./ipv6-prefix"; + ada.ada_value[0] = prefix_str; + if (exact) { + ada.ada_xpath[1] = "./ipv6-exact-match"; + ada.ada_value[1] = "true"; + } + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + return CMD_SUCCESS; + + /* + * Create the access-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (prefix_str != NULL) { + nb_cli_enqueue_change(vty, "./ipv6-prefix", NB_OP_MODIFY, + prefix_str); + nb_cli_enqueue_change(vty, "./ipv6-exact-match", NB_OP_MODIFY, + exact ? "true" : "false"); + } else { + nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_ipv6_access_list, no_ipv6_access_list_cmd, + "no ipv6 access-list ACCESSLIST6_NAME$name [seq (1-4294967295)$seq] $action ", + NO_STR + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "IPv6 prefix\n" + "Exact match of the prefixes\n" + "Match any IPv6\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq_str != NULL) + return filter_remove_check_empty(vty, "access", "ipv6", name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + ada.ada_type = "ipv6"; + ada.ada_name = name; + ada.ada_action = action; + + if (prefix_str) { + ada.ada_xpath[0] = "./ipv6-prefix"; + ada.ada_value[0] = prefix_str; + if (exact) { + ada.ada_xpath[1] = "./ipv6-exact-match"; + ada.ada_value[1] = "true"; + } + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + sseq = ada.ada_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "access", "ipv6", name, sseq, + false); +} + +DEFPY_YANG( + no_ipv6_access_list_all, no_ipv6_access_list_all_cmd, + "no ipv6 access-list ACCESSLIST6_NAME$name", + NO_STR + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + ipv6_access_list_remark, ipv6_access_list_remark_cmd, + "ipv6 access-list ACCESSLIST6_NAME$name remark LINE...", + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) +{ + int rv; + char *remark; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='ipv6'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + remark = argv_concat(argv, argc, 4); + nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark); + rv = nb_cli_apply_changes(vty, "%s", xpath); + XFREE(MTYPE_TMP, remark); + + return rv; +} + +DEFPY_YANG( + no_ipv6_access_list_remark, no_ipv6_access_list_remark_cmd, + "no ipv6 access-list ACCESSLIST6_NAME$name remark", + NO_STR + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR) +{ + return filter_remove_check_empty(vty, "access", "ipv6", name, 0, true); +} + +ALIAS( + no_ipv6_access_list_remark, no_ipv6_access_list_remark_line_cmd, + "no ipv6 access-list ACCESSLIST6_NAME$name remark LINE...", + NO_STR + IPV6_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) + +DEFPY_YANG( + mac_access_list, mac_access_list_cmd, + "mac access-list ACCESSLIST_MAC_NAME$name [seq (1-4294967295)$seq] $action ", + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "MAC address\n" + "Match any MAC address\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + ada.ada_type = "mac"; + ada.ada_name = name; + ada.ada_action = action; + + if (mac_str) { + ada.ada_xpath[0] = "./mac"; + ada.ada_value[0] = mac_str; + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + return CMD_SUCCESS; + + /* + * Create the access-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='mac'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (mac_str != NULL) { + nb_cli_enqueue_change(vty, "./mac", NB_OP_MODIFY, mac_str); + } else { + nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_mac_access_list, no_mac_access_list_cmd, + "no mac access-list ACCESSLIST_MAC_NAME$name [seq (1-4294967295)$seq] $action ", + NO_STR + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "MAC address\n" + "Match any MAC address\n") +{ + int64_t sseq; + struct acl_dup_args ada = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq_str != NULL) + return filter_remove_check_empty(vty, "access", "mac", name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + ada.ada_type = "mac"; + ada.ada_name = name; + ada.ada_action = action; + + if (mac_str) { + ada.ada_xpath[0] = "./mac"; + ada.ada_value[0] = mac_str; + } else { + ada.ada_xpath[0] = "./any"; + ada.ada_value[0] = ""; + } + + if (acl_is_dup(vty->candidate_config->dnode, &ada)) + sseq = ada.ada_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "access", "mac", name, sseq, + false); +} + +DEFPY_YANG( + no_mac_access_list_all, no_mac_access_list_all_cmd, + "no mac access-list ACCESSLIST_MAC_NAME$name", + NO_STR + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='mac'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + mac_access_list_remark, mac_access_list_remark_cmd, + "mac access-list ACCESSLIST_MAC_NAME$name remark LINE...", + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) +{ + int rv; + char *remark; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/access-list[type='mac'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + remark = argv_concat(argv, argc, 4); + nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark); + rv = nb_cli_apply_changes(vty, "%s", xpath); + XFREE(MTYPE_TMP, remark); + + return rv; +} + +DEFPY_YANG( + no_mac_access_list_remark, no_mac_access_list_remark_cmd, + "no mac access-list ACCESSLIST_MAC_NAME$name remark", + NO_STR + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR) +{ + return filter_remove_check_empty(vty, "access", "mac", name, 0, true); +} + +ALIAS( + no_mac_access_list_remark, no_mac_access_list_remark_line_cmd, + "no mac access-list ACCESSLIST_MAC_NAME$name remark LINE...", + NO_STR + MAC_STR + ACCESS_LIST_STR + ACCESS_LIST_ZEBRA_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) + +int access_list_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + uint32_t seq1 = yang_dnode_get_uint32(dnode1, "sequence"); + uint32_t seq2 = yang_dnode_get_uint32(dnode2, "sequence"); + + return seq1 - seq2; +} + +void access_list_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int type = yang_dnode_get_enum(dnode, "../type"); + struct prefix p; + bool is_any; + bool is_exact = false; + bool cisco_style = false; + bool cisco_extended = false; + struct in_addr addr, mask; + char macstr[PREFIX2STR_BUFFER]; + + is_any = yang_dnode_exists(dnode, "any"); + switch (type) { + case YALT_IPV4: + if (is_any) + break; + + if (yang_dnode_exists(dnode, "host") + || yang_dnode_exists(dnode, "network/address") + || yang_dnode_exists(dnode, "source-any")) { + cisco_style = true; + if (yang_dnode_exists(dnode, "destination-host") + || yang_dnode_exists( + dnode, "./destination-network/address") + || yang_dnode_exists(dnode, "destination-any")) + cisco_extended = true; + } else { + yang_dnode_get_prefix(&p, dnode, "ipv4-prefix"); + is_exact = yang_dnode_get_bool(dnode, + "./ipv4-exact-match"); + } + break; + case YALT_IPV6: /* ipv6 */ + vty_out(vty, "ipv6 "); + if (is_any) + break; + + yang_dnode_get_prefix(&p, dnode, "ipv6-prefix"); + is_exact = yang_dnode_get_bool(dnode, "ipv6-exact-match"); + break; + case YALT_MAC: /* mac */ + vty_out(vty, "mac "); + if (is_any) + break; + + yang_dnode_get_prefix(&p, dnode, "mac"); + break; + } + + vty_out(vty, "access-list %s seq %s %s", + yang_dnode_get_string(dnode, "../name"), + yang_dnode_get_string(dnode, "sequence"), + yang_dnode_get_string(dnode, "action")); + + /* Handle Cisco style access lists. */ + if (cisco_style) { + if (cisco_extended) + vty_out(vty, " ip"); + + if (yang_dnode_exists(dnode, "network")) { + yang_dnode_get_ipv4(&addr, dnode, "network/address"); + yang_dnode_get_ipv4(&mask, dnode, "network/mask"); + vty_out(vty, " %pI4 %pI4", &addr, &mask); + } else if (yang_dnode_exists(dnode, "host")) { + if (cisco_extended) + vty_out(vty, " host"); + + vty_out(vty, " %s", + yang_dnode_get_string(dnode, "host")); + } else if (yang_dnode_exists(dnode, "source-any")) + vty_out(vty, " any"); + + /* Not extended, exit earlier. */ + if (!cisco_extended) { + vty_out(vty, "\n"); + return; + } + + /* Handle destination address. */ + if (yang_dnode_exists(dnode, "destination-network")) { + yang_dnode_get_ipv4(&addr, dnode, + "./destination-network/address"); + yang_dnode_get_ipv4(&mask, dnode, + "./destination-network/mask"); + vty_out(vty, " %pI4 %pI4", &addr, &mask); + } else if (yang_dnode_exists(dnode, "destination-host")) + vty_out(vty, " host %s", + yang_dnode_get_string(dnode, + "./destination-host")); + else if (yang_dnode_exists(dnode, "destination-any")) + vty_out(vty, " any"); + + vty_out(vty, "\n"); + return; + } + + /* Zebra style access list. */ + if (!is_any) { + /* If type is MAC don't show '/mask'. */ + if (type == 2 /* mac */) { + prefix_mac2str(&p.u.prefix_eth, macstr, sizeof(macstr)); + vty_out(vty, " %s", macstr); + } else + vty_out(vty, " %pFX", &p); + } else + vty_out(vty, " any"); + + if (is_exact) + vty_out(vty, " exact-match"); + + vty_out(vty, "\n"); +} + +void access_list_remark_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int type = yang_dnode_get_enum(dnode, "../type"); + + switch (type) { + case YALT_IPV4: + break; + case YALT_IPV6: + vty_out(vty, "ipv6 "); + break; + case YALT_MAC: + vty_out(vty, "mac "); + break; + } + + vty_out(vty, "access-list %s remark %s\n", + yang_dnode_get_string(dnode, "../name"), + yang_dnode_get_string(dnode, NULL)); +} + +/* + * Prefix lists. + */ + +static int plist_remove(struct vty *vty, const char *iptype, const char *name, + uint32_t seq, const char *action, + union prefixconstptr prefix, int ge, int le) +{ + int64_t sseq; + struct plist_dup_args pda = {}; + + /* If the user provided sequence number, then just go for it. */ + if (seq != 0) + return filter_remove_check_empty(vty, "prefix", iptype, name, + seq, false); + + /* Otherwise, to keep compatibility, we need to figure it out. */ + pda.pda_type = iptype; + pda.pda_name = name; + pda.pda_action = action; + if (prefix.p) { + prefix_copy(&pda.prefix, prefix); + apply_mask(&pda.prefix); + pda.ge = ge; + pda.le = le; + } else { + pda.any = true; + } + + if (plist_is_dup(vty->candidate_config->dnode, &pda)) + sseq = pda.pda_seq; + else + return CMD_WARNING_CONFIG_FAILED; + + return filter_remove_check_empty(vty, "prefix", iptype, name, sseq, + false); +} + +DEFPY_YANG( + ip_prefix_list, ip_prefix_list_cmd, + "ip prefix-list PREFIXLIST4_NAME$name [seq (1-4294967295)$seq] $action ", + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n" + "IP prefix /, e.g., 35.0.0.0/8\n" + "Minimum prefix length to be matched\n" + "Minimum prefix length\n" + "Maximum prefix length to be matched\n" + "Maximum prefix length\n") +{ + int64_t sseq; + struct plist_dup_args pda = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + pda.pda_type = "ipv4"; + pda.pda_name = name; + pda.pda_action = action; + if (prefix_str) { + prefix_copy(&pda.prefix, prefix); + pda.ge = ge; + pda.le = le; + } else { + pda.any = true; + } + + /* + * Create the prefix-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (prefix_str != NULL) { + nb_cli_enqueue_change(vty, "./ipv4-prefix", NB_OP_MODIFY, + prefix_str); + + if (ge_str) { + nb_cli_enqueue_change( + vty, "./ipv4-prefix-length-greater-or-equal", + NB_OP_MODIFY, ge_str); + } else { + /* + * Remove old ge if not being modified + */ + nb_cli_enqueue_change( + vty, "./ipv4-prefix-length-greater-or-equal", + NB_OP_DESTROY, NULL); + } + + if (le_str) { + nb_cli_enqueue_change( + vty, "./ipv4-prefix-length-lesser-or-equal", + NB_OP_MODIFY, le_str); + } else { + /* + * Remove old le if not being modified + */ + nb_cli_enqueue_change( + vty, "./ipv4-prefix-length-lesser-or-equal", + NB_OP_DESTROY, NULL); + } + nb_cli_enqueue_change(vty, "./any", NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_ip_prefix_list, no_ip_prefix_list_cmd, + "no ip prefix-list PREFIXLIST4_NAME$name [seq (1-4294967295)$seq] $action ", + NO_STR + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Any prefix match. Same as \"0.0.0.0/0 le 32\"\n" + "IP prefix /, e.g., 35.0.0.0/8\n" + "Minimum prefix length to be matched\n" + "Minimum prefix length\n" + "Maximum prefix length to be matched\n" + "Maximum prefix length\n") +{ + return plist_remove(vty, "ipv4", name, seq, action, + prefix_str ? prefix : NULL, ge, le); +} + +DEFPY_YANG( + no_ip_prefix_list_seq, no_ip_prefix_list_seq_cmd, + "no ip prefix-list PREFIXLIST4_NAME$name seq (1-4294967295)$seq", + NO_STR + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR) +{ + return plist_remove(vty, "ipv4", name, seq, NULL, NULL, 0, 0); +} + +DEFPY_YANG( + no_ip_prefix_list_all, no_ip_prefix_list_all_cmd, + "no ip prefix-list PREFIXLIST4_NAME$name", + NO_STR + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + ip_prefix_list_remark, ip_prefix_list_remark_cmd, + "ip prefix-list PREFIXLIST4_NAME$name description LINE...", + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) +{ + int rv; + char *remark; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + remark = argv_concat(argv, argc, 4); + nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark); + rv = nb_cli_apply_changes(vty, "%s", xpath); + XFREE(MTYPE_TMP, remark); + + return rv; +} + +DEFPY_YANG( + no_ip_prefix_list_remark, no_ip_prefix_list_remark_cmd, + "no ip prefix-list PREFIXLIST4_NAME$name description", + NO_STR + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR) +{ + return filter_remove_check_empty(vty, "prefix", "ipv4", name, 0, true); +} + +ALIAS( + no_ip_prefix_list_remark, no_ip_prefix_list_remark_line_cmd, + "no ip prefix-list PREFIXLIST4_NAME$name description LINE...", + NO_STR + IP_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) + +DEFPY_YANG( + ipv6_prefix_list, ipv6_prefix_list_cmd, + "ipv6 prefix-list PREFIXLIST6_NAME$name [seq (1-4294967295)] $action ", + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Any prefix match. Same as \"::0/0 le 128\"\n" + "IPv6 prefix /, e.g., 3ffe::/16\n" + "Maximum prefix length to be matched\n" + "Maximum prefix length\n" + "Minimum prefix length to be matched\n" + "Minimum prefix length\n") +{ + int64_t sseq; + struct plist_dup_args pda = {}; + char xpath[XPATH_MAXLEN]; + char xpath_entry[XPATH_MAXLEN + 128]; + + /* + * Backward compatibility: don't complain about duplicated values, + * just silently accept. + */ + pda.pda_type = "ipv6"; + pda.pda_name = name; + pda.pda_action = action; + if (prefix_str) { + prefix_copy(&pda.prefix, prefix); + pda.ge = ge; + pda.le = le; + } else { + pda.any = true; + } + + /* + * Create the prefix-list first, so we can generate sequence if + * none given (backward compatibility). + */ + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name); + if (seq_str == NULL) { + /* Use XPath to find the next sequence number. */ + sseq = acl_get_seq(vty, xpath, false); + if (sseq < 0) + return CMD_WARNING_CONFIG_FAILED; + + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%" PRId64 "']", xpath, sseq); + } else + snprintfrr(xpath_entry, sizeof(xpath_entry), + "%s/entry[sequence='%s']", xpath, seq_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, xpath_entry, NB_OP_CREATE, NULL); + + nb_cli_enqueue_change(vty, "./action", NB_OP_MODIFY, action); + if (prefix_str != NULL) { + nb_cli_enqueue_change(vty, "./ipv6-prefix", NB_OP_MODIFY, + prefix_str); + + if (ge_str) { + nb_cli_enqueue_change( + vty, "./ipv6-prefix-length-greater-or-equal", + NB_OP_MODIFY, ge_str); + } else { + /* + * Remove old ge if not being modified + */ + nb_cli_enqueue_change( + vty, "./ipv6-prefix-length-greater-or-equal", + NB_OP_DESTROY, NULL); + } + + if (le_str) { + nb_cli_enqueue_change( + vty, "./ipv6-prefix-length-lesser-or-equal", + NB_OP_MODIFY, le_str); + } else { + /* + * Remove old le if not being modified + */ + nb_cli_enqueue_change( + vty, "./ipv6-prefix-length-lesser-or-equal", + NB_OP_DESTROY, NULL); + } + nb_cli_enqueue_change(vty, "./any", NB_OP_DESTROY, NULL); + } else { + nb_cli_enqueue_change(vty, "./any", NB_OP_CREATE, NULL); + } + + return nb_cli_apply_changes(vty, "%s", xpath_entry); +} + +DEFPY_YANG( + no_ipv6_prefix_list, no_ipv6_prefix_list_cmd, + "no ipv6 prefix-list PREFIXLIST6_NAME$name [seq (1-4294967295)$seq] $action ", + NO_STR + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR + ACCESS_LIST_ACTION_STR + "Any prefix match. Same as \"::0/0 le 128\"\n" + "IPv6 prefix /, e.g., 3ffe::/16\n" + "Maximum prefix length to be matched\n" + "Maximum prefix length\n" + "Minimum prefix length to be matched\n" + "Minimum prefix length\n") +{ + return plist_remove(vty, "ipv6", name, seq, action, + prefix_str ? prefix : NULL, ge, le); +} + +DEFPY_YANG( + no_ipv6_prefix_list_seq, no_ipv6_prefix_list_seq_cmd, + "no ipv6 prefix-list PREFIXLIST6_NAME$name seq (1-4294967295)$seq", + NO_STR + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_SEQ_STR) +{ + return plist_remove(vty, "ipv6", name, seq, NULL, NULL, 0, 0); +} + +DEFPY_YANG( + no_ipv6_prefix_list_all, no_ipv6_prefix_list_all_cmd, + "no ipv6 prefix-list PREFIXLIST6_NAME$name", + NO_STR + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + ipv6_prefix_list_remark, ipv6_prefix_list_remark_cmd, + "ipv6 prefix-list PREFIXLIST6_NAME$name description LINE...", + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) +{ + int rv; + char *remark; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + remark = argv_concat(argv, argc, 4); + nb_cli_enqueue_change(vty, "./remark", NB_OP_CREATE, remark); + rv = nb_cli_apply_changes(vty, "%s", xpath); + XFREE(MTYPE_TMP, remark); + + return rv; +} + +DEFPY_YANG( + no_ipv6_prefix_list_remark, no_ipv6_prefix_list_remark_cmd, + "no ipv6 prefix-list PREFIXLIST6_NAME$name description", + NO_STR + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR) +{ + return filter_remove_check_empty(vty, "prefix", "ipv6", name, 0, true); +} + +ALIAS( + no_ipv6_prefix_list_remark, no_ipv6_prefix_list_remark_line_cmd, + "no ipv6 prefix-list PREFIXLIST6_NAME$name description LINE...", + NO_STR + IPV6_STR + PREFIX_LIST_STR + PREFIX_LIST_NAME_STR + ACCESS_LIST_REMARK_STR + ACCESS_LIST_REMARK_LINE_STR) + +int prefix_list_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + uint32_t seq1 = yang_dnode_get_uint32(dnode1, "sequence"); + uint32_t seq2 = yang_dnode_get_uint32(dnode2, "sequence"); + + return seq1 - seq2; +} + +void prefix_list_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int type = yang_dnode_get_enum(dnode, "../type"); + const char *ge_str = NULL, *le_str = NULL; + bool is_any; + struct prefix p; + + is_any = yang_dnode_exists(dnode, "any"); + switch (type) { + case YPLT_IPV4: + if (!is_any) + yang_dnode_get_prefix(&p, dnode, "ipv4-prefix"); + if (yang_dnode_exists(dnode, + "./ipv4-prefix-length-greater-or-equal")) + ge_str = yang_dnode_get_string( + dnode, "./ipv4-prefix-length-greater-or-equal"); + if (yang_dnode_exists(dnode, + "./ipv4-prefix-length-lesser-or-equal")) + le_str = yang_dnode_get_string( + dnode, "./ipv4-prefix-length-lesser-or-equal"); + + vty_out(vty, "ip "); + break; + case YPLT_IPV6: + if (!is_any) + yang_dnode_get_prefix(&p, dnode, "ipv6-prefix"); + if (yang_dnode_exists(dnode, + "./ipv6-prefix-length-greater-or-equal")) + ge_str = yang_dnode_get_string( + dnode, "./ipv6-prefix-length-greater-or-equal"); + if (yang_dnode_exists(dnode, + "./ipv6-prefix-length-lesser-or-equal")) + le_str = yang_dnode_get_string( + dnode, "./ipv6-prefix-length-lesser-or-equal"); + + vty_out(vty, "ipv6 "); + break; + } + + vty_out(vty, "prefix-list %s seq %s %s", + yang_dnode_get_string(dnode, "../name"), + yang_dnode_get_string(dnode, "sequence"), + yang_dnode_get_string(dnode, "action")); + + if (is_any) { + vty_out(vty, " any\n"); + return; + } + + vty_out(vty, " %pFX", &p); + if (ge_str) + vty_out(vty, " ge %s", ge_str); + if (le_str) + vty_out(vty, " le %s", le_str); + + vty_out(vty, "\n"); +} + +void prefix_list_remark_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int type = yang_dnode_get_enum(dnode, "../type"); + + switch (type) { + case YPLT_IPV4: + vty_out(vty, "ip "); + break; + case YPLT_IPV6: + vty_out(vty, "ipv6 "); + break; + } + + vty_out(vty, "prefix-list %s description %s\n", + yang_dnode_get_string(dnode, "../name"), + yang_dnode_get_string(dnode, NULL)); +} + +void filter_cli_init(void) +{ + /* access-list cisco-style (legacy). */ + install_element(CONFIG_NODE, &access_list_std_cmd); + install_element(CONFIG_NODE, &no_access_list_std_cmd); + install_element(CONFIG_NODE, &access_list_ext_cmd); + install_element(CONFIG_NODE, &no_access_list_ext_cmd); + + /* access-list zebra-style. */ + install_element(CONFIG_NODE, &access_list_cmd); + install_element(CONFIG_NODE, &no_access_list_cmd); + install_element(CONFIG_NODE, &no_access_list_all_cmd); + install_element(CONFIG_NODE, &access_list_remark_cmd); + install_element(CONFIG_NODE, &no_access_list_remark_cmd); + install_element(CONFIG_NODE, &no_access_list_remark_line_cmd); + + install_element(CONFIG_NODE, &ipv6_access_list_cmd); + install_element(CONFIG_NODE, &no_ipv6_access_list_cmd); + install_element(CONFIG_NODE, &no_ipv6_access_list_all_cmd); + install_element(CONFIG_NODE, &ipv6_access_list_remark_cmd); + install_element(CONFIG_NODE, &no_ipv6_access_list_remark_cmd); + install_element(CONFIG_NODE, &no_ipv6_access_list_remark_line_cmd); + + install_element(CONFIG_NODE, &mac_access_list_cmd); + install_element(CONFIG_NODE, &no_mac_access_list_cmd); + install_element(CONFIG_NODE, &no_mac_access_list_all_cmd); + install_element(CONFIG_NODE, &mac_access_list_remark_cmd); + install_element(CONFIG_NODE, &no_mac_access_list_remark_cmd); + install_element(CONFIG_NODE, &no_mac_access_list_remark_line_cmd); + + /* prefix lists. */ + install_element(CONFIG_NODE, &ip_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ip_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ip_prefix_list_seq_cmd); + install_element(CONFIG_NODE, &no_ip_prefix_list_all_cmd); + install_element(CONFIG_NODE, &ip_prefix_list_remark_cmd); + install_element(CONFIG_NODE, &no_ip_prefix_list_remark_cmd); + install_element(CONFIG_NODE, &no_ip_prefix_list_remark_line_cmd); + + install_element(CONFIG_NODE, &ipv6_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ipv6_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ipv6_prefix_list_seq_cmd); + install_element(CONFIG_NODE, &no_ipv6_prefix_list_all_cmd); + install_element(CONFIG_NODE, &ipv6_prefix_list_remark_cmd); + install_element(CONFIG_NODE, &no_ipv6_prefix_list_remark_cmd); + install_element(CONFIG_NODE, &no_ipv6_prefix_list_remark_line_cmd); +} diff --git a/lib/filter_nb.c b/lib/filter_nb.c new file mode 100644 index 0000000..39042d3 --- /dev/null +++ b/lib/filter_nb.c @@ -0,0 +1,1828 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR filter northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include "zebra.h" + +#include "lib/northbound.h" +#include "lib/prefix.h" +#include "lib/printfrr.h" + +#include "lib/filter.h" +#include "lib/plist.h" +#include "lib/plist_int.h" +#include "lib/routemap.h" + +static enum nb_error prefix_list_length_validate(struct nb_cb_modify_args *args) +{ + int type = yang_dnode_get_enum(args->dnode, "../../type"); + const char *xpath_le = NULL, *xpath_ge = NULL; + struct prefix p; + uint8_t le, ge; + + if (type == YPLT_IPV4) { + yang_dnode_get_prefix(&p, args->dnode, "../ipv4-prefix"); + xpath_le = "../ipv4-prefix-length-lesser-or-equal"; + xpath_ge = "../ipv4-prefix-length-greater-or-equal"; + } else { + yang_dnode_get_prefix(&p, args->dnode, "../ipv6-prefix"); + xpath_le = "../ipv6-prefix-length-lesser-or-equal"; + xpath_ge = "../ipv6-prefix-length-greater-or-equal"; + } + + /* + * Check rule: + * prefix length <= le. + */ + if (yang_dnode_exists(args->dnode, xpath_le)) { + le = yang_dnode_get_uint8(args->dnode, "%s", xpath_le); + if (p.prefixlen > le) + goto log_and_fail; + } + + /* + * Check rule: + * prefix length <= ge. + */ + if (yang_dnode_exists(args->dnode, xpath_ge)) { + ge = yang_dnode_get_uint8(args->dnode, "%s", xpath_ge); + if (p.prefixlen > ge) + goto log_and_fail; + } + + /* + * Check rule: + * ge <= le. + */ + if (yang_dnode_exists(args->dnode, xpath_le) + && yang_dnode_exists(args->dnode, xpath_ge)) { + le = yang_dnode_get_uint8(args->dnode, "%s", xpath_le); + ge = yang_dnode_get_uint8(args->dnode, "%s", xpath_ge); + if (ge > le) + goto log_and_fail; + } + + return NB_OK; + +log_and_fail: + snprintfrr( + args->errmsg, args->errmsg_len, + "Invalid prefix range for %pFX: Make sure that mask length <= ge <= le", + &p); + return NB_ERR_VALIDATION; +} + +/** + * Sets prefix list entry to blank value. + * + * \param[out] ple prefix list entry to modify. + */ +static void prefix_list_entry_set_empty(struct prefix_list_entry *ple) +{ + ple->any = false; + memset(&ple->prefix, 0, sizeof(ple->prefix)); + ple->ge = 0; + ple->le = 0; +} + +static int +prefix_list_nb_validate_v4_af_type(const struct lyd_node *plist_dnode, + char *errmsg, size_t errmsg_len) +{ + int af_type; + + af_type = yang_dnode_get_enum(plist_dnode, "type"); + if (af_type != YPLT_IPV4) { + snprintf(errmsg, errmsg_len, + "prefix-list type %u is mismatched.", af_type); + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +static int +prefix_list_nb_validate_v6_af_type(const struct lyd_node *plist_dnode, + char *errmsg, size_t errmsg_len) +{ + int af_type; + + af_type = yang_dnode_get_enum(plist_dnode, "type"); + if (af_type != YPLT_IPV6) { + snprintf(errmsg, errmsg_len, + "prefix-list type %u is mismatched.", af_type); + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_length_greater_or_equal_modify( + struct nb_cb_modify_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->ge = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_length_lesser_or_equal_modify( + struct nb_cb_modify_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->le = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_length_greater_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->ge = 0; + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->le = 0; + + return NB_OK; +} + +/** + * Unsets the cisco style rule for addresses so it becomes disabled (the + * equivalent of setting: `0.0.0.0/32`). + * + * \param addr address part. + * \param mask mask part. + */ +static void cisco_unset_addr_mask(struct in_addr *addr, struct in_addr *mask) +{ + addr->s_addr = INADDR_ANY; + mask->s_addr = CISCO_BIN_HOST_WILDCARD_MASK; +} + +static int _acl_is_dup(const struct lyd_node *dnode, void *arg) +{ + struct acl_dup_args *ada = arg; + int idx; + + /* This entry is the caller, so skip it. */ + if (ada->ada_entry_dnode + && ada->ada_entry_dnode == dnode) + return YANG_ITER_CONTINUE; + + if (strcmp(yang_dnode_get_string(dnode, "action"), ada->ada_action)) + return YANG_ITER_CONTINUE; + + /* Check if all values match. */ + for (idx = 0; idx < ADA_MAX_VALUES; idx++) { + /* No more values. */ + if (ada->ada_xpath[idx] == NULL) + break; + + /* Not same type, just skip it. */ + if (!yang_dnode_exists(dnode, ada->ada_xpath[idx])) + return YANG_ITER_CONTINUE; + + /* Check if different value. */ + if (strcmp(yang_dnode_get_string(dnode, "%s", + ada->ada_xpath[idx]), + ada->ada_value[idx])) + return YANG_ITER_CONTINUE; + } + + ada->ada_found = true; + ada->ada_seq = yang_dnode_get_uint32(dnode, "sequence"); + + return YANG_ITER_STOP; +} + +bool acl_is_dup(const struct lyd_node *dnode, struct acl_dup_args *ada) +{ + ada->ada_found = false; + + yang_dnode_iterate( + _acl_is_dup, ada, dnode, + "/frr-filter:lib/access-list[type='%s'][name='%s']/entry", + ada->ada_type, ada->ada_name); + + return ada->ada_found; +} + +static bool acl_cisco_is_dup(const struct lyd_node *dnode) +{ + const struct lyd_node *entry_dnode = + yang_dnode_get_parent(dnode, "entry"); + struct acl_dup_args ada = {}; + int idx = 0, arg_idx = 0; + static const char *cisco_entries[] = { + "./host", + "./network/address", + "./network/mask", + "./source-any", + "./destination-host", + "./destination-network/address", + "./destination-network/mask", + "./destination-any", + NULL + }; + + /* Initialize. */ + ada.ada_type = "ipv4"; + ada.ada_name = yang_dnode_get_string(entry_dnode, "../name"); + ada.ada_action = yang_dnode_get_string(entry_dnode, "action"); + ada.ada_entry_dnode = entry_dnode; + + /* Load all values/XPaths. */ + while (cisco_entries[idx] != NULL) { + if (!yang_dnode_exists(entry_dnode, cisco_entries[idx])) { + idx++; + continue; + } + + ada.ada_xpath[arg_idx] = cisco_entries[idx]; + ada.ada_value[arg_idx] = yang_dnode_get_string( + entry_dnode, "%s", cisco_entries[idx]); + arg_idx++; + idx++; + } + + return acl_is_dup(entry_dnode, &ada); +} + +static bool acl_zebra_is_dup(const struct lyd_node *dnode, + enum yang_access_list_type type) +{ + const struct lyd_node *entry_dnode = + yang_dnode_get_parent(dnode, "entry"); + struct acl_dup_args ada = {}; + int idx = 0, arg_idx = 0; + static const char *zebra_entries[] = { + "./ipv4-prefix", + "./ipv4-exact-match", + "./ipv6-prefix", + "./ipv6-exact-match", + "./mac", + "./any", + NULL + }; + + /* Initialize. */ + switch (type) { + case YALT_IPV4: + ada.ada_type = "ipv4"; + break; + case YALT_IPV6: + ada.ada_type = "ipv6"; + break; + case YALT_MAC: + ada.ada_type = "mac"; + break; + } + ada.ada_name = yang_dnode_get_string(entry_dnode, "../name"); + ada.ada_action = yang_dnode_get_string(entry_dnode, "action"); + ada.ada_entry_dnode = entry_dnode; + + /* Load all values/XPaths. */ + while (zebra_entries[idx] != NULL) { + if (!yang_dnode_exists(entry_dnode, zebra_entries[idx])) { + idx++; + continue; + } + + ada.ada_xpath[arg_idx] = zebra_entries[idx]; + ada.ada_value[arg_idx] = yang_dnode_get_string( + entry_dnode, "%s", zebra_entries[idx]); + arg_idx++; + idx++; + } + + return acl_is_dup(entry_dnode, &ada); +} + +static void plist_dnode_to_prefix(const struct lyd_node *dnode, bool *any, + struct prefix *p, int *ge, int *le) +{ + *any = false; + *ge = 0; + *le = 0; + + if (yang_dnode_exists(dnode, "any")) { + *any = true; + return; + } + + switch (yang_dnode_get_enum(dnode, "../type")) { + case YPLT_IPV4: + yang_dnode_get_prefix(p, dnode, "ipv4-prefix"); + if (yang_dnode_exists(dnode, + "./ipv4-prefix-length-greater-or-equal")) + *ge = yang_dnode_get_uint8( + dnode, "./ipv4-prefix-length-greater-or-equal"); + if (yang_dnode_exists(dnode, + "./ipv4-prefix-length-lesser-or-equal")) + *le = yang_dnode_get_uint8( + dnode, "./ipv4-prefix-length-lesser-or-equal"); + break; + case YPLT_IPV6: + yang_dnode_get_prefix(p, dnode, "ipv6-prefix"); + if (yang_dnode_exists(dnode, + "./ipv6-prefix-length-greater-or-equal")) + *ge = yang_dnode_get_uint8( + dnode, "./ipv6-prefix-length-greater-or-equal"); + if (yang_dnode_exists(dnode, + "./ipv6-prefix-length-lesser-or-equal")) + *le = yang_dnode_get_uint8( + dnode, "./ipv6-prefix-length-lesser-or-equal"); + break; + } +} + +static int _plist_is_dup(const struct lyd_node *dnode, void *arg) +{ + struct plist_dup_args *pda = arg; + struct prefix p = {}; + int ge, le; + bool any; + + /* This entry is the caller, so skip it. */ + if (pda->pda_entry_dnode + && pda->pda_entry_dnode == dnode) + return YANG_ITER_CONTINUE; + + if (strcmp(yang_dnode_get_string(dnode, "action"), pda->pda_action)) + return YANG_ITER_CONTINUE; + + plist_dnode_to_prefix(dnode, &any, &p, &ge, &le); + + if (pda->any) { + if (!any) + return YANG_ITER_CONTINUE; + } else { + if (!prefix_same(&pda->prefix, &p) || pda->ge != ge + || pda->le != le) + return YANG_ITER_CONTINUE; + } + + pda->pda_found = true; + pda->pda_seq = yang_dnode_get_uint32(dnode, "sequence"); + + return YANG_ITER_STOP; +} + +bool plist_is_dup(const struct lyd_node *dnode, struct plist_dup_args *pda) +{ + pda->pda_found = false; + + yang_dnode_iterate( + _plist_is_dup, pda, dnode, + "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry", + pda->pda_type, pda->pda_name); + + return pda->pda_found; +} + +/* + * XPath: /frr-filter:lib/access-list + */ +static int lib_access_list_create(struct nb_cb_create_args *args) +{ + struct access_list *acl = NULL; + const char *acl_name; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + type = yang_dnode_get_enum(args->dnode, "type"); + acl_name = yang_dnode_get_string(args->dnode, "name"); + + switch (type) { + case YALT_IPV4: + acl = access_list_get(AFI_IP, acl_name); + break; + case YALT_IPV6: + acl = access_list_get(AFI_IP6, acl_name); + break; + case YALT_MAC: + acl = access_list_get(AFI_L2VPN, acl_name); + break; + } + + nb_running_set_entry(args->dnode, acl); + + return NB_OK; +} + +static int lib_access_list_destroy(struct nb_cb_destroy_args *args) +{ + struct access_list *acl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + acl = nb_running_unset_entry(args->dnode); + access_list_delete(acl); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/remark + */ +static int lib_access_list_remark_modify(struct nb_cb_modify_args *args) +{ + struct access_list *acl; + const char *remark; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + acl = nb_running_get_entry(args->dnode, NULL, true); + if (acl->remark) + XFREE(MTYPE_TMP, acl->remark); + + remark = yang_dnode_get_string(args->dnode, NULL); + acl->remark = XSTRDUP(MTYPE_TMP, remark); + + return NB_OK; +} + +static int +lib_access_list_remark_destroy(struct nb_cb_destroy_args *args) +{ + struct access_list *acl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + acl = nb_running_get_entry(args->dnode, NULL, true); + if (acl->remark) + XFREE(MTYPE_TMP, acl->remark); + + return NB_OK; +} + + +/* + * XPath: /frr-filter:lib/access-list/entry + */ +static int lib_access_list_entry_create(struct nb_cb_create_args *args) +{ + struct access_list *acl; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = filter_new(); + f->seq = yang_dnode_get_uint32(args->dnode, "sequence"); + + acl = nb_running_get_entry(args->dnode, NULL, true); + f->acl = acl; + access_list_filter_add(acl, f); + nb_running_set_entry(args->dnode, f); + + return NB_OK; +} + +static int lib_access_list_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct access_list *acl; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_unset_entry(args->dnode); + acl = f->acl; + access_list_filter_delete(acl, f); + + return NB_OK; +} + +static void +lib_access_list_entry_apply_finish(struct nb_cb_apply_finish_args *args) +{ + struct filter *f; + + f = nb_running_get_entry(args->dnode, NULL, true); + access_list_filter_update(f->acl); +} + +/* + * XPath: /frr-filter:lib/access-list/entry/action + */ +static int +lib_access_list_entry_action_modify(struct nb_cb_modify_args *args) +{ + const char *filter_type; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + filter_type = yang_dnode_get_string(args->dnode, NULL); + if (strcmp(filter_type, "permit") == 0) + f->type = FILTER_PERMIT; + else + f->type = FILTER_DENY; + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/ipv4-prefix + */ +static int +lib_access_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_zebra_is_dup( + args->dnode, + yang_dnode_get_enum(args->dnode, "../../type"))) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 0; + fz = &f->u.zfilter; + yang_dnode_get_prefix(&fz->prefix, args->dnode, NULL); + + return NB_OK; +} + +static int +lib_access_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fz = &f->u.zfilter; + memset(&fz->prefix, 0, sizeof(fz->prefix)); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/ipv4-exact-match + */ +static int +lib_access_list_entry_ipv4_exact_match_modify(struct nb_cb_modify_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_zebra_is_dup( + args->dnode, + yang_dnode_get_enum(args->dnode, "../../type"))) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fz = &f->u.zfilter; + fz->exact = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +static int +lib_access_list_entry_ipv4_exact_match_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fz = &f->u.zfilter; + fz->exact = 0; + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/host + */ +static int +lib_access_list_entry_host_modify(struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 1; + fc = &f->u.cfilter; + yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL); + fc->addr_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK; + + return NB_OK; +} + +static int +lib_access_list_entry_host_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + cisco_unset_addr_mask(&fc->addr, &fc->addr_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/network + */ +static int lib_access_list_entry_network_create(struct nb_cb_create_args *args) +{ + /* Nothing to do here, everything is done in children callbacks */ + return NB_OK; +} + +static int lib_access_list_entry_network_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + cisco_unset_addr_mask(&fc->addr, &fc->addr_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/network/address + */ +static int +lib_access_list_entry_network_address_modify(struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 1; + fc = &f->u.cfilter; + yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/network/mask + */ +static int +lib_access_list_entry_network_mask_modify(struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 1; + fc = &f->u.cfilter; + yang_dnode_get_ipv4(&fc->addr_mask, args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/source-any + */ +static int +lib_access_list_entry_source_any_create(struct nb_cb_create_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 1; + fc = &f->u.cfilter; + fc->addr.s_addr = INADDR_ANY; + fc->addr_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK; + + return NB_OK; +} + +static int +lib_access_list_entry_source_any_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + cisco_unset_addr_mask(&fc->addr, &fc->addr_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/destination-host + */ +static int lib_access_list_entry_destination_host_modify( + struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 1; + yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL); + fc->mask_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK; + + return NB_OK; +} + +static int lib_access_list_entry_destination_host_destroy( + struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 0; + cisco_unset_addr_mask(&fc->mask, &fc->mask_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/destination-network + */ +static int +lib_access_list_entry_destination_network_create(struct nb_cb_create_args *args) +{ + /* Nothing to do here, everything is done in children callbacks */ + return NB_OK; +} + +static int lib_access_list_entry_destination_network_destroy( + struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 0; + cisco_unset_addr_mask(&fc->mask, &fc->mask_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/destination-network/address + */ +static int lib_access_list_entry_destination_network_address_modify( + struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 1; + yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/destination-network/mask + */ +static int lib_access_list_entry_destination_network_mask_modify( + struct nb_cb_modify_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 1; + yang_dnode_get_ipv4(&fc->mask_mask, args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/destination-any + */ +static int lib_access_list_entry_destination_any_create( + struct nb_cb_create_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_cisco_is_dup(args->dnode)) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 1; + fc->mask.s_addr = INADDR_ANY; + fc->mask_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK; + + return NB_OK; +} + +static int lib_access_list_entry_destination_any_destroy( + struct nb_cb_destroy_args *args) +{ + struct filter_cisco *fc; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fc = &f->u.cfilter; + fc->extended = 0; + cisco_unset_addr_mask(&fc->mask, &fc->mask_mask); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/access-list/entry/any + */ +static int lib_access_list_entry_any_create(struct nb_cb_create_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + int type; + + /* Don't allow duplicated values. */ + if (args->event == NB_EV_VALIDATE) { + if (acl_zebra_is_dup( + args->dnode, + yang_dnode_get_enum(args->dnode, "../../type"))) { + snprintfrr(args->errmsg, args->errmsg_len, + "duplicated access list value: %s", + yang_dnode_get_string(args->dnode, NULL)); + return NB_ERR_VALIDATION; + } + return NB_OK; + } + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + f->cisco = 0; + fz = &f->u.zfilter; + memset(&fz->prefix, 0, sizeof(fz->prefix)); + + type = yang_dnode_get_enum(args->dnode, "../../type"); + switch (type) { + case YALT_IPV4: + fz->prefix.family = AF_INET; + break; + case YALT_IPV6: + fz->prefix.family = AF_INET6; + break; + case YALT_MAC: + fz->prefix.family = AF_ETHERNET; + break; + } + + return NB_OK; +} + +static int lib_access_list_entry_any_destroy(struct nb_cb_destroy_args *args) +{ + struct filter_zebra *fz; + struct filter *f; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + f = nb_running_get_entry(args->dnode, NULL, true); + fz = &f->u.zfilter; + fz->prefix.family = AF_UNSPEC; + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/prefix-list + */ +static int lib_prefix_list_create(struct nb_cb_create_args *args) +{ + struct prefix_list *pl = NULL; + const char *name; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + type = yang_dnode_get_enum(args->dnode, "type"); + name = yang_dnode_get_string(args->dnode, "name"); + switch (type) { + case 0: /* ipv4 */ + pl = prefix_list_get(AFI_IP, 0, name); + break; + case 1: /* ipv6 */ + pl = prefix_list_get(AFI_IP6, 0, name); + break; + } + + nb_running_set_entry(args->dnode, pl); + + return NB_OK; +} + +static int lib_prefix_list_destroy(struct nb_cb_destroy_args *args) +{ + struct prefix_list *pl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pl = nb_running_unset_entry(args->dnode); + prefix_list_delete(pl); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/prefix-list/remark + */ +static int lib_prefix_list_remark_modify(struct nb_cb_modify_args *args) +{ + struct prefix_list *pl; + const char *remark; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pl = nb_running_get_entry(args->dnode, NULL, true); + if (pl->desc) + XFREE(MTYPE_TMP, pl->desc); + + remark = yang_dnode_get_string(args->dnode, NULL); + pl->desc = XSTRDUP(MTYPE_TMP, remark); + + return NB_OK; +} + +static int lib_prefix_list_remark_destroy(struct nb_cb_destroy_args *args) +{ + struct prefix_list *pl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pl = nb_running_get_entry(args->dnode, NULL, true); + if (pl->desc) + XFREE(MTYPE_TMP, pl->desc); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry + */ +static int lib_prefix_list_entry_create(struct nb_cb_create_args *args) +{ + struct prefix_list_entry *ple; + struct prefix_list *pl; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + pl = nb_running_get_entry(args->dnode, NULL, true); + ple = prefix_list_entry_new(); + ple->pl = pl; + ple->seq = yang_dnode_get_uint32(args->dnode, "sequence"); + prefix_list_entry_set_empty(ple); + nb_running_set_entry(args->dnode, ple); + + return NB_OK; +} + +static int lib_prefix_list_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_unset_entry(args->dnode); + if (ple->installed) + prefix_list_entry_delete2(ple); + else + prefix_list_entry_free(ple); + + return NB_OK; +} + +static void +lib_prefix_list_entry_apply_finish(struct nb_cb_apply_finish_args *args) +{ + struct prefix_list_entry *ple; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* + * Finish prefix entry update procedure. The procedure is started in + * children callbacks. `prefix_list_entry_update_start` can be called + * multiple times if multiple children are modified, but it is actually + * executed only once because of the protection by `ple->installed`. + */ + prefix_list_entry_update_finish(ple); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/action + */ +static int lib_prefix_list_entry_action_modify(struct nb_cb_modify_args *args) +{ + struct prefix_list_entry *ple; + int action_type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + action_type = yang_dnode_get_enum(args->dnode, NULL); + if (action_type == YPLA_PERMIT) + ple->type = PREFIX_PERMIT; + else + ple->type = PREFIX_DENY; + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_modify(struct nb_cb_modify_args *args) +{ + struct prefix_list_entry *ple; + struct prefix p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + yang_dnode_get_prefix(&ple->prefix, args->dnode, NULL); + + /* Apply mask and correct original address if necessary. */ + prefix_copy(&p, &ple->prefix); + apply_mask(&p); + if (!prefix_same(&ple->prefix, &p)) { + zlog_info("%s: bad network %pFX correcting it to %pFX", + __func__, &ple->prefix, &p); + prefix_copy(&ple->prefix, &p); + } + + return NB_OK; +} + +static int lib_prefix_list_entry_prefix_destroy(struct nb_cb_destroy_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + memset(&ple->prefix, 0, sizeof(ple->prefix)); + + return NB_OK; +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix + */ +static int +lib_prefix_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args) +{ + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v4_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_modify(args); +} + +static int +lib_prefix_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args) +{ + + if (args->event != NB_EV_APPLY) + return NB_OK; + + return lib_prefix_list_entry_prefix_destroy(args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix + */ +static int +lib_prefix_list_entry_ipv6_prefix_modify(struct nb_cb_modify_args *args) +{ + + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v6_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_modify(args); +} + +static int +lib_prefix_list_entry_ipv6_prefix_destroy(struct nb_cb_destroy_args *args) +{ + + if (args->event != NB_EV_APPLY) + return NB_OK; + + return lib_prefix_list_entry_prefix_destroy(args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal + */ +static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify( + struct nb_cb_modify_args *args) +{ + if (args->event == NB_EV_VALIDATE + && prefix_list_length_validate(args) != NB_OK) + return NB_ERR_VALIDATION; + + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v4_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_greater_or_equal_modify( + args); +} + +static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v4_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_greater_or_equal_destroy( + args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal + */ +static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify( + struct nb_cb_modify_args *args) +{ + if (args->event == NB_EV_VALIDATE + && prefix_list_length_validate(args) != NB_OK) + return NB_ERR_VALIDATION; + + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v4_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_lesser_or_equal_modify( + args); +} + +static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v4_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy( + args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix-length-greater-or-equal + */ +static int lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_modify( + struct nb_cb_modify_args *args) +{ + if (args->event == NB_EV_VALIDATE + && prefix_list_length_validate(args) != NB_OK) + return NB_ERR_VALIDATION; + + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v6_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_greater_or_equal_modify( + args); +} + +static int lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v6_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_greater_or_equal_destroy( + args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/ipv6-prefix-length-lesser-or-equal + */ +static int lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_modify( + struct nb_cb_modify_args *args) +{ + if (args->event == NB_EV_VALIDATE + && prefix_list_length_validate(args) != NB_OK) + return NB_ERR_VALIDATION; + + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v6_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_lesser_or_equal_modify( + args); +} + +static int lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event == NB_EV_VALIDATE) { + const struct lyd_node *plist_dnode = + yang_dnode_get_parent(args->dnode, "prefix-list"); + + return prefix_list_nb_validate_v6_af_type( + plist_dnode, args->errmsg, args->errmsg_len); + } + + return lib_prefix_list_entry_prefix_length_lesser_or_equal_destroy( + args); +} + +/* + * XPath: /frr-filter:lib/prefix-list/entry/any + */ +static int lib_prefix_list_entry_any_create(struct nb_cb_create_args *args) +{ + struct prefix_list_entry *ple; + int type; + + /* + * If we have gotten to this point, it's legal + */ + if (args->event == NB_EV_VALIDATE) + return NB_OK; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->any = true; + + /* Fill prefix struct from scratch. */ + memset(&ple->prefix, 0, sizeof(ple->prefix)); + + type = yang_dnode_get_enum(args->dnode, "../../type"); + switch (type) { + case YPLT_IPV4: + ple->prefix.family = AF_INET; + ple->ge = 0; + ple->le = IPV4_MAX_BITLEN; + break; + case YPLT_IPV6: + ple->prefix.family = AF_INET6; + ple->ge = 0; + ple->le = IPV6_MAX_BITLEN; + break; + } + + return NB_OK; +} + +static int lib_prefix_list_entry_any_destroy(struct nb_cb_destroy_args *args) +{ + struct prefix_list_entry *ple; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ple = nb_running_get_entry(args->dnode, NULL, true); + + /* Start prefix entry update procedure. */ + prefix_list_entry_update_start(ple); + + ple->any = false; + + return NB_OK; +} + +/* clang-format off */ +const struct frr_yang_module_info frr_filter_info = { + .name = "frr-filter", + .nodes = { + { + .xpath = "/frr-filter:lib/access-list", + .cbs = { + .create = lib_access_list_create, + .destroy = lib_access_list_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/remark", + .cbs = { + .modify = lib_access_list_remark_modify, + .destroy = lib_access_list_remark_destroy, + .cli_show = access_list_remark_show, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry", + .cbs = { + .create = lib_access_list_entry_create, + .destroy = lib_access_list_entry_destroy, + .apply_finish = lib_access_list_entry_apply_finish, + .cli_cmp = access_list_cmp, + .cli_show = access_list_show, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/action", + .cbs = { + .modify = lib_access_list_entry_action_modify, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/ipv4-prefix", + .cbs = { + .modify = lib_access_list_entry_ipv4_prefix_modify, + .destroy = lib_access_list_entry_ipv4_prefix_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/ipv4-exact-match", + .cbs = { + .modify = lib_access_list_entry_ipv4_exact_match_modify, + .destroy = lib_access_list_entry_ipv4_exact_match_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/host", + .cbs = { + .modify = lib_access_list_entry_host_modify, + .destroy = lib_access_list_entry_host_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/network", + .cbs = { + .create = lib_access_list_entry_network_create, + .destroy = lib_access_list_entry_network_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/network/address", + .cbs = { + .modify = lib_access_list_entry_network_address_modify, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/network/mask", + .cbs = { + .modify = lib_access_list_entry_network_mask_modify, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/source-any", + .cbs = { + .create = lib_access_list_entry_source_any_create, + .destroy = lib_access_list_entry_source_any_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/destination-host", + .cbs = { + .modify = lib_access_list_entry_destination_host_modify, + .destroy = lib_access_list_entry_destination_host_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/destination-network", + .cbs = { + .create = lib_access_list_entry_destination_network_create, + .destroy = lib_access_list_entry_destination_network_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/destination-network/address", + .cbs = { + .modify = lib_access_list_entry_destination_network_address_modify, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/destination-network/mask", + .cbs = { + .modify = lib_access_list_entry_destination_network_mask_modify, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/destination-any", + .cbs = { + .create = lib_access_list_entry_destination_any_create, + .destroy = lib_access_list_entry_destination_any_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/ipv6-prefix", + .cbs = { + .modify = lib_access_list_entry_ipv4_prefix_modify, + .destroy = lib_access_list_entry_ipv4_prefix_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/ipv6-exact-match", + .cbs = { + .modify = lib_access_list_entry_ipv4_exact_match_modify, + .destroy = lib_access_list_entry_ipv4_exact_match_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/mac", + .cbs = { + .modify = lib_access_list_entry_ipv4_prefix_modify, + .destroy = lib_access_list_entry_ipv4_prefix_destroy, + } + }, + { + .xpath = "/frr-filter:lib/access-list/entry/any", + .cbs = { + .create = lib_access_list_entry_any_create, + .destroy = lib_access_list_entry_any_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list", + .cbs = { + .create = lib_prefix_list_create, + .destroy = lib_prefix_list_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/remark", + .cbs = { + .modify = lib_prefix_list_remark_modify, + .destroy = lib_prefix_list_remark_destroy, + .cli_show = prefix_list_remark_show, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry", + .cbs = { + .create = lib_prefix_list_entry_create, + .destroy = lib_prefix_list_entry_destroy, + .apply_finish = lib_prefix_list_entry_apply_finish, + .cli_cmp = prefix_list_cmp, + .cli_show = prefix_list_show, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/action", + .cbs = { + .modify = lib_prefix_list_entry_action_modify, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix", + .cbs = { + .modify = lib_prefix_list_entry_ipv4_prefix_modify, + .destroy = lib_prefix_list_entry_ipv4_prefix_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal", + .cbs = { + .modify = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify, + .destroy = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal", + .cbs = { + .modify = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify, + .destroy = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix", + .cbs = { + .modify = lib_prefix_list_entry_ipv6_prefix_modify, + .destroy = lib_prefix_list_entry_ipv6_prefix_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-greater-or-equal", + .cbs = { + .modify = lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_modify, + .destroy = lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-lesser-or-equal", + .cbs = { + .modify = lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_modify, + .destroy = lib_prefix_list_entry_ipv6_prefix_length_lesser_or_equal_destroy, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry/any", + .cbs = { + .create = lib_prefix_list_entry_any_create, + .destroy = lib_prefix_list_entry_any_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_filter_cli_info = { + .name = "frr-filter", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-filter:lib/access-list/remark", + .cbs.cli_show = access_list_remark_show, + }, + { + .xpath = "/frr-filter:lib/access-list/entry", + .cbs = { + .cli_cmp = access_list_cmp, + .cli_show = access_list_show, + } + }, + { + .xpath = "/frr-filter:lib/prefix-list/remark", + .cbs.cli_show = prefix_list_remark_show, + }, + { + .xpath = "/frr-filter:lib/prefix-list/entry", + .cbs = { + .cli_cmp = prefix_list_cmp, + .cli_show = prefix_list_show, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/lib/flex_algo.c b/lib/flex_algo.c new file mode 100644 index 0000000..f48117f --- /dev/null +++ b/lib/flex_algo.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * flex_algo.c: Flexible Algorithm library + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#include "zebra.h" + +#include "flex_algo.h" + +DEFINE_MTYPE_STATIC(LIB, FLEX_ALGO_DATABASE, "Flex-Algo database"); +DEFINE_MTYPE_STATIC(LIB, FLEX_ALGO, "Flex-Algo algorithm information"); + +static void _flex_algo_delete(struct flex_algos *flex_algos, + struct flex_algo *fa); + +struct flex_algos *flex_algos_alloc(flex_algo_allocator_t allocator, + flex_algo_releaser_t releaser) +{ + struct flex_algos *flex_algos; + + flex_algos = + XCALLOC(MTYPE_FLEX_ALGO_DATABASE, sizeof(struct flex_algos)); + flex_algos->flex_algos = list_new(); + flex_algos->allocator = allocator; + flex_algos->releaser = releaser; + return flex_algos; +} + +void flex_algos_free(struct flex_algos *flex_algos) +{ + struct listnode *node, *nnode; + struct flex_algo *fa; + + for (ALL_LIST_ELEMENTS(flex_algos->flex_algos, node, nnode, fa)) + _flex_algo_delete(flex_algos, fa); + list_delete(&flex_algos->flex_algos); + XFREE(MTYPE_FLEX_ALGO_DATABASE, flex_algos); +} + +struct flex_algo *flex_algo_alloc(struct flex_algos *flex_algos, + uint8_t algorithm, void *arg) +{ + struct flex_algo *fa; + + fa = XCALLOC(MTYPE_FLEX_ALGO, sizeof(struct flex_algo)); + fa->algorithm = algorithm; + if (flex_algos->allocator) + fa->data = flex_algos->allocator(arg); + admin_group_init(&fa->admin_group_exclude_any); + admin_group_init(&fa->admin_group_include_any); + admin_group_init(&fa->admin_group_include_all); + listnode_add(flex_algos->flex_algos, fa); + return fa; +} + +static void _flex_algo_delete(struct flex_algos *flex_algos, + struct flex_algo *fa) +{ + if (flex_algos->releaser) + flex_algos->releaser(fa->data); + admin_group_term(&fa->admin_group_exclude_any); + admin_group_term(&fa->admin_group_include_any); + admin_group_term(&fa->admin_group_include_all); + listnode_delete(flex_algos->flex_algos, fa); + XFREE(MTYPE_FLEX_ALGO, fa); +} + + +void flex_algo_delete(struct flex_algos *flex_algos, uint8_t algorithm) +{ + struct listnode *node, *nnode; + struct flex_algo *fa; + + for (ALL_LIST_ELEMENTS(flex_algos->flex_algos, node, nnode, fa)) { + if (fa->algorithm != algorithm) + continue; + _flex_algo_delete(flex_algos, fa); + } +} + +/** + * @brief Look up the local flex-algo object by its algorithm number. + * @param algorithm flex-algo algorithm number + * @param area area pointer of flex-algo + * @return local flex-algo object if exist, else NULL + */ +struct flex_algo *flex_algo_lookup(struct flex_algos *flex_algos, + uint8_t algorithm) +{ + struct listnode *node; + struct flex_algo *fa; + + for (ALL_LIST_ELEMENTS_RO(flex_algos->flex_algos, node, fa)) + if (fa->algorithm == algorithm) + return fa; + return NULL; +} + +/** + * @brief Compare two Flex-Algo Definitions (FAD) + * @param Flex algo 1 + * @param Flex algo 2 + * @return true if the definition is equal, else false + */ +bool flex_algo_definition_cmp(struct flex_algo *fa1, struct flex_algo *fa2) +{ + if (fa1->algorithm != fa2->algorithm) + return false; + if (fa1->calc_type != fa2->calc_type) + return false; + if (fa1->metric_type != fa2->metric_type) + return false; + if (fa1->exclude_srlg != fa2->exclude_srlg) + return false; + if (fa1->flags != fa2->flags) + return false; + if (fa1->unsupported_subtlv != fa2->unsupported_subtlv) + return false; + + if (!admin_group_cmp(&fa1->admin_group_exclude_any, + &fa2->admin_group_exclude_any)) + return false; + if (!admin_group_cmp(&fa1->admin_group_include_all, + &fa2->admin_group_include_all)) + return false; + if (!admin_group_cmp(&fa1->admin_group_include_any, + &fa2->admin_group_include_any)) + return false; + + return true; +} + +/** + * Check SR Algorithm is Flex-Algo + * according to RFC9350 section 4 + * + * @param algorithm SR Algorithm + */ +bool flex_algo_id_valid(uint16_t algorithm) +{ + return algorithm >= SR_ALGORITHM_FLEX_MIN && + algorithm <= SR_ALGORITHM_FLEX_MAX; +} + +char *flex_algo_metric_type_print(char *type_str, size_t sz, + enum flex_algo_metric_type metric_type) +{ + switch (metric_type) { + case MT_IGP: + snprintf(type_str, sz, "igp"); + break; + case MT_MIN_UNI_LINK_DELAY: + snprintf(type_str, sz, "delay"); + break; + case MT_TE_DEFAULT: + snprintf(type_str, sz, "te"); + break; + } + return type_str; +} + +bool flex_algo_get_state(struct flex_algos *flex_algos, uint8_t algorithm) +{ + struct flex_algo *fa = flex_algo_lookup(flex_algos, algorithm); + + if (!fa) + return false; + + return fa->state; +} + +void flex_algo_set_state(struct flex_algos *flex_algos, uint8_t algorithm, + bool state) +{ + struct flex_algo *fa = flex_algo_lookup(flex_algos, algorithm); + + if (!fa) + return; + + fa->state = state; +} diff --git a/lib/flex_algo.h b/lib/flex_algo.h new file mode 100644 index 0000000..e617e7c --- /dev/null +++ b/lib/flex_algo.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * flex_algo.h: Flexible Algorithm library + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#ifndef _FRR_FLEX_ALGO_H +#define _FRR_FLEX_ALGO_H + +#include "admin_group.h" +#include "linklist.h" +#include "prefix.h" +#include "segment_routing.h" + +#define FLEX_ALGO_PRIO_DEFAULT 128 + +#define CALC_TYPE_SPF 0 + +/* flex-algo definition flags */ + +/* M-flag (aka. prefix-metric) + * Flex-Algorithm specific prefix and ASBR metric MUST be used + */ +#define FAD_FLAG_M 0x80 + +/* + * Metric Type values from RFC9350 section 5.1 + */ +enum flex_algo_metric_type { + MT_IGP = 0, + MT_MIN_UNI_LINK_DELAY = 1, + MT_TE_DEFAULT = 2, +}; + + +/* Flex-Algo data about a given algorithm. + * It includes the definition and some local data. + */ +struct flex_algo { + /* Flex-Algo definition */ + uint8_t algorithm; + enum flex_algo_metric_type metric_type; + uint8_t calc_type; + uint8_t priority; + uint8_t flags; + + /* extended admin-groups */ + struct admin_group admin_group_exclude_any; + struct admin_group admin_group_include_any; + struct admin_group admin_group_include_all; + + /* Exclude SRLG Sub-TLV is not yet supported by IS-IS + * True if a Exclude SRLG Sub-TLV has been found + */ + bool exclude_srlg; + + /* True if an unsupported sub-TLV other Exclude SRLG + * has been received. + * A router that receives an unsupported definition + * that is elected must not participate in the algorithm. + * This boolean prevents future sub-TLV from being considered + * as supported. + */ + bool unsupported_subtlv; + + /* Flex-Algo local data */ + + /* True if the local definition must be advertised */ + bool advertise_definition; + + /* which dataplane must be used for the algorithm */ +#define FLEX_ALGO_SR_MPLS 0x01 +#define FLEX_ALGO_SRV6 0x02 +#define FLEX_ALGO_IP 0x04 + uint8_t dataplanes; + + /* True if the Algorithm is locally enabled (ie. a definition has been + * found and is supported). + */ + bool state; + + /* + * This property can be freely extended among different routing + * protocols. Since Flex-Algo is an IGP protocol agnostic, both IS-IS + * and OSPF can implement Flex-Algo. The struct flex_algo thus provides + * the general data structure of Flex-Algo, and the value of extending + * it with the IGP protocol is provided by this property. + */ + void *data; +}; + +typedef void *(*flex_algo_allocator_t)(void *); +typedef void (*flex_algo_releaser_t)(void *); + +struct flex_algos { + flex_algo_allocator_t allocator; + flex_algo_releaser_t releaser; + struct list *flex_algos; +}; + +/* + * Flex-Algo Utilities + */ +struct flex_algos *flex_algos_alloc(flex_algo_allocator_t allocator, + flex_algo_releaser_t releaser); +void flex_algos_free(struct flex_algos *flex_algos); +struct flex_algo *flex_algo_alloc(struct flex_algos *flex_algos, + uint8_t algorithm, void *arg); +struct flex_algo *flex_algo_lookup(struct flex_algos *flex_algos, + uint8_t algorithm); +void flex_algos_free(struct flex_algos *flex_algos); +bool flex_algo_definition_cmp(struct flex_algo *fa1, struct flex_algo *fa2); +void flex_algo_delete(struct flex_algos *flex_algos, uint8_t algorithm); +bool flex_algo_id_valid(uint16_t algorithm); +char *flex_algo_metric_type_print(char *type_str, size_t sz, + enum flex_algo_metric_type metric_type); + +bool flex_algo_get_state(struct flex_algos *flex_algos, uint8_t algorithm); + +void flex_algo_set_state(struct flex_algos *flex_algos, uint8_t algorithm, + bool state); +#endif /* _FRR_FLEX_ALGO_H */ diff --git a/lib/freebsd-queue.h b/lib/freebsd-queue.h new file mode 100644 index 0000000..d53df9c --- /dev/null +++ b/lib/freebsd-queue.h @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: BSD-3-Clause +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + * $FreeBSD$ + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This file defines four types of data structures: singly-linked lists, + * singly-linked tail queues, lists and tail queues. + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A singly-linked tail queue is headed by a pair of pointers, one to the + * head of the list and the other to the tail of the list. The elements are + * singly linked for minimum space and pointer manipulation overhead at the + * expense of O(n) removal for arbitrary elements. New elements can be added + * to the list after an existing element, at the head of the list, or at the + * end of the list. Elements being removed from the head of the tail queue + * should use the explicit macro for this purpose for optimum efficiency. + * A singly-linked tail queue may only be traversed in the forward direction. + * Singly-linked tail queues are ideal for applications with large datasets + * and few or no removals or for implementing a FIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + * + * + * SLIST LIST STAILQ TAILQ + * _HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - - - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_SAFE + + + + + * _FOREACH_REVERSE - - - + + * _FOREACH_REVERSE_SAFE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _CONCAT - - + + + * _REMOVE_AFTER + - + - + * _REMOVE_HEAD + - + - + * _REMOVE + + + + + * _SWAP + + + + + * + */ +#ifdef QUEUE_MACRO_DEBUG +/* Store the last 2 places the queue element or head was altered */ +struct qm_trace { + char *lastfile; + int lastline; + char *prevfile; + int prevline; +}; + +#define TRACEBUF struct qm_trace trace; +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) + +#define QMD_TRACE_HEAD(head) \ + do { \ + (head)->trace.prevline = (head)->trace.lastline; \ + (head)->trace.prevfile = (head)->trace.lastfile; \ + (head)->trace.lastline = __LINE__; \ + (head)->trace.lastfile = __FILE__; \ + } while (0) + +#define QMD_TRACE_ELEM(elem) \ + do { \ + (elem)->trace.prevline = (elem)->trace.lastline; \ + (elem)->trace.prevfile = (elem)->trace.lastfile; \ + (elem)->trace.lastline = __LINE__; \ + (elem)->trace.lastfile = __FILE__; \ + } while (0) + +#else +#define QMD_TRACE_ELEM(elem) +#define QMD_TRACE_HEAD(head) +#define QMD_SAVELINK(name, link) +#define TRACEBUF +#define TRASHIT(x) +#endif /* QUEUE_MACRO_DEBUG */ + +/* + * Singly-linked List declarations. + */ +#define SLIST_HEAD(name, type) \ + struct name { \ + struct type *slh_first; /* first element */ \ + } + +#define SLIST_HEAD_INITIALIZER(head) \ + { \ + NULL \ + } + +#define SLIST_ENTRY(type) \ + struct { \ + struct type *sle_next; /* next element */ \ + } + +/* + * Singly-linked List functions. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) + +#define SLIST_FIRST(head) ((head)->slh_first) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST((head)); (var); \ + (var) = SLIST_NEXT((var), field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) && ((tvar) = SLIST_NEXT((var), field), 1); (var) = (tvar)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); ((var) = *(varp)) != NULL; \ + (varp) = &SLIST_NEXT((var), field)) + +#define SLIST_INIT(head) \ + do { \ + SLIST_FIRST((head)) = NULL; \ + } while (0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) \ + do { \ + SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ + SLIST_NEXT((slistelm), field) = (elm); \ + } while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) \ + do { \ + SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ + SLIST_FIRST((head)) = (elm); \ + } while (0) + +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_REMOVE(head, elm, type, field) \ + do { \ + QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ + if (SLIST_FIRST((head)) == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = SLIST_FIRST((head)); \ + while (SLIST_NEXT(curelm, field) != (elm)) \ + curelm = SLIST_NEXT(curelm, field); \ + SLIST_REMOVE_AFTER(curelm, field); \ + } \ + TRASHIT(*oldnext); \ + } while (0) + +#define SLIST_REMOVE_AFTER(elm, field) \ + do { \ + SLIST_NEXT(elm, field) = \ + SLIST_NEXT(SLIST_NEXT(elm, field), field); \ + } while (0) + +#define SLIST_REMOVE_HEAD(head, field) \ + do { \ + SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ + } while (0) + +#define SLIST_SWAP(head1, head2, type) \ + do { \ + struct type *swap_first = SLIST_FIRST(head1); \ + SLIST_FIRST(head1) = SLIST_FIRST(head2); \ + SLIST_FIRST(head2) = swap_first; \ + } while (0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ + struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ + } + +#define STAILQ_HEAD_INITIALIZER(head) \ + { \ + NULL, &(head).stqh_first \ + } + +#define STAILQ_ENTRY(type) \ + struct { \ + struct type *stqe_next; /* next element */ \ + } + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_CONCAT(head1, head2) \ + do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ + } while (0) + +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = STAILQ_FIRST((head)); (var); \ + (var) = STAILQ_NEXT((var), field)) + + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); (var) = (tvar)) + +#define STAILQ_INIT(head) \ + do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ + } while (0) + +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) \ + do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) \ + == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_NEXT((tqelm), field) = (elm); \ + } while (0) + +#define STAILQ_INSERT_HEAD(head, elm, field) \ + do { \ + if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) \ + == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + STAILQ_FIRST((head)) = (elm); \ + } while (0) + +#define STAILQ_INSERT_TAIL(head, elm, field) \ + do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + } while (0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) \ + ? NULL \ + : ((struct type *)(void *)((char *)((head)->stqh_last) \ + - offsetof(struct type, field)))) + +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_REMOVE(head, elm, type, field) \ + do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = STAILQ_FIRST((head)); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ + } while (0) + +#define STAILQ_REMOVE_AFTER(head, elm, field) \ + do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) \ + == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ + } while (0) + +#define STAILQ_REMOVE_HEAD(head, field) \ + do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) \ + == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ + } while (0) + +#define STAILQ_SWAP(head1, head2, type) \ + do { \ + struct type *swap_first = STAILQ_FIRST(head1); \ + struct type **swap_last = (head1)->stqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->stqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->stqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->stqh_last = &STAILQ_FIRST(head2); \ + } while (0) + + +/* + * List declarations. + */ +#define LIST_HEAD(name, type) \ + struct name { \ + struct type *lh_first; /* first element */ \ + } + +#define LIST_HEAD_INITIALIZER(head) \ + { \ + NULL \ + } + +#define LIST_ENTRY(type) \ + struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ + } + +/* + * List functions. + */ + +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_LIST_CHECK_HEAD(head, field) \ + do { \ + if (LIST_FIRST((head)) != NULL \ + && LIST_FIRST((head))->field.le_prev \ + != &LIST_FIRST((head))) \ + panic("Bad list head %p first->prev != head", (head)); \ + } while (0) + +#define QMD_LIST_CHECK_NEXT(elm, field) \ + do { \ + if (LIST_NEXT((elm), field) != NULL \ + && LIST_NEXT((elm), field)->field.le_prev \ + != &((elm)->field.le_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ + } while (0) + +#define QMD_LIST_CHECK_PREV(elm, field) \ + do { \ + if (*(elm)->field.le_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ + } while (0) +#else +#define QMD_LIST_CHECK_HEAD(head, field) +#define QMD_LIST_CHECK_NEXT(elm, field) +#define QMD_LIST_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define LIST_EMPTY(head) ((head)->lh_first == NULL) + +#define LIST_FIRST(head) ((head)->lh_first) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST((head)); (var); (var) = LIST_NEXT((var), field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); (var) = (tvar)) + +#define LIST_INIT(head) \ + do { \ + LIST_FIRST((head)) = NULL; \ + } while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) \ + do { \ + QMD_LIST_CHECK_NEXT(listelm, field); \ + if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) \ + != NULL) \ + LIST_NEXT((listelm), field)->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_NEXT((listelm), field) = (elm); \ + (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ + } while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) \ + do { \ + QMD_LIST_CHECK_PREV(listelm, field); \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + LIST_NEXT((elm), field) = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ + } while (0) + +#define LIST_INSERT_HEAD(head, elm, field) \ + do { \ + QMD_LIST_CHECK_HEAD((head), field); \ + if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ + LIST_FIRST((head))->field.le_prev = \ + &LIST_NEXT((elm), field); \ + LIST_FIRST((head)) = (elm); \ + (elm)->field.le_prev = &LIST_FIRST((head)); \ + } while (0) + +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_REMOVE(elm, field) \ + do { \ + QMD_SAVELINK(oldnext, (elm)->field.le_next); \ + QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ + QMD_LIST_CHECK_NEXT(elm, field); \ + QMD_LIST_CHECK_PREV(elm, field); \ + if (LIST_NEXT((elm), field) != NULL) \ + LIST_NEXT((elm), field)->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = LIST_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + } while (0) + +#define LIST_SWAP(head1, head2, type, field) \ + do { \ + struct type *swap_tmp = LIST_FIRST((head1)); \ + LIST_FIRST((head1)) = LIST_FIRST((head2)); \ + LIST_FIRST((head2)) = swap_tmp; \ + if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ + if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ + swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ + } while (0) + +/* + * Tail queue declarations. + */ +#define TAILQ_HEAD(name, type) \ + struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + TRACEBUF \ + } + +#define TAILQ_HEAD_INITIALIZER(head) \ + { \ + NULL, &(head).tqh_first \ + } + +#define TAILQ_ENTRY(type) \ + struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + TRACEBUF \ + } + +/* + * Tail queue functions. + */ +#if (defined(_KERNEL) && defined(INVARIANTS)) +#define QMD_TAILQ_CHECK_HEAD(head, field) \ + do { \ + if (!TAILQ_EMPTY(head) \ + && TAILQ_FIRST((head))->field.tqe_prev \ + != &TAILQ_FIRST((head))) \ + panic("Bad tailq head %p first->prev != head", \ + (head)); \ + } while (0) + +#define QMD_TAILQ_CHECK_TAIL(head, field) \ + do { \ + if (*(head)->tqh_last != NULL) \ + panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ + } while (0) + +#define QMD_TAILQ_CHECK_NEXT(elm, field) \ + do { \ + if (TAILQ_NEXT((elm), field) != NULL \ + && TAILQ_NEXT((elm), field)->field.tqe_prev \ + != &((elm)->field.tqe_next)) \ + panic("Bad link elm %p next->prev != elm", (elm)); \ + } while (0) + +#define QMD_TAILQ_CHECK_PREV(elm, field) \ + do { \ + if (*(elm)->field.tqe_prev != (elm)) \ + panic("Bad link elm %p prev->next != elm", (elm)); \ + } while (0) +#else +#define QMD_TAILQ_CHECK_HEAD(head, field) +#define QMD_TAILQ_CHECK_TAIL(head, headname) +#define QMD_TAILQ_CHECK_NEXT(elm, field) +#define QMD_TAILQ_CHECK_PREV(elm, field) +#endif /* (_KERNEL && INVARIANTS) */ + +#define TAILQ_CONCAT(head1, head2, field) \ + do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = \ + (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + QMD_TRACE_HEAD(head1); \ + QMD_TRACE_HEAD(head2); \ + } \ + } while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) + +#define TAILQ_FIRST(head) ((head)->tqh_first) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST((head)); (var); \ + (var) = TAILQ_NEXT((var), field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((tvar) = TAILQ_NEXT((var), field), 1); (var) = (tvar)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST((head), headname); (var); \ + (var) = TAILQ_PREV((var), headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ + (var) = (tvar)) + +#define TAILQ_INIT(head) \ + do { \ + TAILQ_FIRST((head)) = NULL; \ + (head)->tqh_last = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + } while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) \ + do { \ + QMD_TAILQ_CHECK_NEXT(listelm, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) \ + != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else { \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + } \ + TAILQ_NEXT((listelm), field) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&listelm->field); \ + } while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) \ + do { \ + QMD_TAILQ_CHECK_PREV(listelm, field); \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + TAILQ_NEXT((elm), field) = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_ELEM(&(elm)->field); \ + QMD_TRACE_ELEM(&listelm->field); \ + } while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) \ + do { \ + QMD_TAILQ_CHECK_HEAD(head, field); \ + if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ + TAILQ_FIRST((head))->field.tqe_prev = \ + &TAILQ_NEXT((elm), field); \ + else \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + TAILQ_FIRST((head)) = (elm); \ + (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ + } while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) \ + do { \ + QMD_TAILQ_CHECK_TAIL(head, field); \ + TAILQ_NEXT((elm), field) = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &TAILQ_NEXT((elm), field); \ + QMD_TRACE_HEAD(head); \ + QMD_TRACE_ELEM(&(elm)->field); \ + } while (0) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) + +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + +#define TAILQ_REMOVE(head, elm, field) \ + do { \ + QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ + QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ + QMD_TAILQ_CHECK_NEXT(elm, field); \ + QMD_TAILQ_CHECK_PREV(elm, field); \ + if ((TAILQ_NEXT((elm), field)) != NULL) \ + TAILQ_NEXT((elm), field)->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else { \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + QMD_TRACE_HEAD(head); \ + } \ + *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ + TRASHIT(*oldnext); \ + TRASHIT(*oldprev); \ + QMD_TRACE_ELEM(&(elm)->field); \ + } while (0) + +#define TAILQ_SWAP(head1, head2, type, field) \ + do { \ + struct type *swap_first = (head1)->tqh_first; \ + struct type **swap_last = (head1)->tqh_last; \ + (head1)->tqh_first = (head2)->tqh_first; \ + (head1)->tqh_last = (head2)->tqh_last; \ + (head2)->tqh_first = swap_first; \ + (head2)->tqh_last = swap_last; \ + if ((swap_first = (head1)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head1)->tqh_first; \ + else \ + (head1)->tqh_last = &(head1)->tqh_first; \ + if ((swap_first = (head2)->tqh_first) != NULL) \ + swap_first->field.tqe_prev = &(head2)->tqh_first; \ + else \ + (head2)->tqh_last = &(head2)->tqh_first; \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/lib/frr_pthread.c b/lib/frr_pthread.c new file mode 100644 index 0000000..3a4bc71 --- /dev/null +++ b/lib/frr_pthread.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Utilities and interfaces for managing POSIX threads within FRR. + * Copyright (C) 2017 Cumulus Networks, Inc. + */ + +#include + +#include + +#include +#ifdef HAVE_PTHREAD_NP_H +#include +#endif +#include + +#include "frr_pthread.h" +#include "memory.h" +#include "linklist.h" +#include "zlog.h" +#include "libfrr.h" +#include "libfrr_trace.h" + +DEFINE_MTYPE_STATIC(LIB, FRR_PTHREAD, "FRR POSIX Thread"); +DEFINE_MTYPE_STATIC(LIB, PTHREAD_PRIM, "POSIX sync primitives"); + +/* default frr_pthread start/stop routine prototypes */ +static void *fpt_run(void *arg); +static int fpt_halt(struct frr_pthread *fpt, void **res); + +/* misc sigs */ +static void frr_pthread_destroy_nolock(struct frr_pthread *fpt); + +/* default frr_pthread attributes */ +const struct frr_pthread_attr frr_pthread_attr_default = { + .start = fpt_run, + .stop = fpt_halt, +}; + +/* list to keep track of all frr_pthreads */ +static pthread_mutex_t frr_pthread_list_mtx = PTHREAD_MUTEX_INITIALIZER; +static struct list *frr_pthread_list; + +/* ------------------------------------------------------------------------ */ + +void frr_pthread_init(void) +{ + frr_with_mutex (&frr_pthread_list_mtx) { + frr_pthread_list = list_new(); + } +} + +void frr_pthread_finish(void) +{ + frr_pthread_stop_all(); + + frr_with_mutex (&frr_pthread_list_mtx) { + struct listnode *n, *nn; + struct frr_pthread *fpt; + + for (ALL_LIST_ELEMENTS(frr_pthread_list, n, nn, fpt)) { + listnode_delete(frr_pthread_list, fpt); + frr_pthread_destroy_nolock(fpt); + } + + list_delete(&frr_pthread_list); + } +} + +struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr, + const char *name, const char *os_name) +{ + struct frr_pthread *fpt = NULL; + + attr = attr ? attr : &frr_pthread_attr_default; + + fpt = XCALLOC(MTYPE_FRR_PTHREAD, sizeof(struct frr_pthread)); + /* initialize mutex */ + pthread_mutex_init(&fpt->mtx, NULL); + /* create new thread master */ + fpt->master = event_master_create(name); + /* set attributes */ + fpt->attr = *attr; + name = (name ? name : "Anonymous thread"); + fpt->name = XSTRDUP(MTYPE_FRR_PTHREAD, name); + if (os_name) + strlcpy(fpt->os_name, os_name, OS_THREAD_NAMELEN); + else + strlcpy(fpt->os_name, name, OS_THREAD_NAMELEN); + /* initialize startup synchronization primitives */ + fpt->running_cond_mtx = XCALLOC( + MTYPE_PTHREAD_PRIM, sizeof(pthread_mutex_t)); + fpt->running_cond = XCALLOC(MTYPE_PTHREAD_PRIM, + sizeof(pthread_cond_t)); + + pthread_mutex_init(fpt->running_cond_mtx, NULL); + pthread_cond_init(fpt->running_cond, NULL); + + pthread_mutex_init(&fpt->startup_cond_mtx, NULL); + pthread_cond_init(&fpt->startup_cond, NULL); + fpt->started = false; + + frr_with_mutex (&frr_pthread_list_mtx) { + listnode_add(frr_pthread_list, fpt); + } + + return fpt; +} + +static void frr_pthread_destroy_nolock(struct frr_pthread *fpt) +{ + event_master_free(fpt->master); + pthread_mutex_destroy(&fpt->mtx); + pthread_mutex_destroy(fpt->running_cond_mtx); + pthread_cond_destroy(fpt->running_cond); + pthread_mutex_destroy(&fpt->startup_cond_mtx); + pthread_cond_destroy(&fpt->startup_cond); + XFREE(MTYPE_FRR_PTHREAD, fpt->name); + XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond_mtx); + XFREE(MTYPE_PTHREAD_PRIM, fpt->running_cond); + XFREE(MTYPE_FRR_PTHREAD, fpt); +} + +void frr_pthread_destroy(struct frr_pthread *fpt) +{ + frr_with_mutex (&frr_pthread_list_mtx) { + listnode_delete(frr_pthread_list, fpt); + } + + frr_pthread_destroy_nolock(fpt); +} + +int frr_pthread_set_name(struct frr_pthread *fpt) +{ + int ret = 0; + +#ifdef HAVE_PTHREAD_SETNAME_NP +# ifdef GNU_LINUX + ret = pthread_setname_np(fpt->thread, fpt->os_name); +# elif defined(__NetBSD__) + ret = pthread_setname_np(fpt->thread, fpt->os_name, NULL); +# endif +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(fpt->thread, fpt->os_name); +#endif + + return ret; +} + +/* New pthread waits before running */ +static void frr_pthread_wait_startup(struct frr_pthread *fpt) +{ + frr_with_mutex (&fpt->startup_cond_mtx) { + while (!fpt->started) + pthread_cond_wait(&fpt->startup_cond, + &fpt->startup_cond_mtx); + } +} + +/* Parent pthread allows new pthread to start running */ +static void frr_pthread_notify_startup(struct frr_pthread *fpt) +{ + frr_with_mutex (&fpt->startup_cond_mtx) { + fpt->started = true; + pthread_cond_signal(&fpt->startup_cond); + } +} + +static void *frr_pthread_inner(void *arg) +{ + struct frr_pthread *fpt = arg; + + /* The new pthead waits until the parent allows it to continue. */ + frr_pthread_wait_startup(fpt); + + rcu_thread_start(fpt->rcu_thread); + + return fpt->attr.start(fpt); +} + +int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr) +{ + int ret; + sigset_t oldsigs, blocksigs; + + assert(frr_is_after_fork || !"trying to start thread before fork()"); + + /* Ensure we never handle signals on a background thread by blocking + * everything here (new thread inherits signal mask) + */ + sigfillset(&blocksigs); + pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs); + + frrtrace(1, frr_libfrr, frr_pthread_run, fpt->name); + + fpt->rcu_thread = rcu_thread_prepare(); + ret = pthread_create(&fpt->thread, attr, frr_pthread_inner, fpt); + + /* Restore caller's signals */ + pthread_sigmask(SIG_SETMASK, &oldsigs, NULL); + + /* Allow new child pthread to start */ + frr_pthread_notify_startup(fpt); + + /* + * Per pthread_create(3), the contents of fpt->thread are undefined if + * pthread_create() did not succeed. Reset this value to zero. + */ + if (ret < 0) { + rcu_thread_unprepare(fpt->rcu_thread); + memset(&fpt->thread, 0x00, sizeof(fpt->thread)); + } + + return ret; +} + +void frr_pthread_wait_running(struct frr_pthread *fpt) +{ + frr_with_mutex (fpt->running_cond_mtx) { + while (!fpt->running) + pthread_cond_wait(fpt->running_cond, + fpt->running_cond_mtx); + } +} + +void frr_pthread_notify_running(struct frr_pthread *fpt) +{ + frr_with_mutex (fpt->running_cond_mtx) { + fpt->running = true; + pthread_cond_signal(fpt->running_cond); + } +} + +int frr_pthread_stop(struct frr_pthread *fpt, void **result) +{ + frrtrace(1, frr_libfrr, frr_pthread_stop, fpt->name); + + int ret = (*fpt->attr.stop)(fpt, result); + memset(&fpt->thread, 0x00, sizeof(fpt->thread)); + return ret; +} + +void frr_pthread_stop_all(void) +{ + frr_with_mutex (&frr_pthread_list_mtx) { + struct listnode *n; + struct frr_pthread *fpt; + for (ALL_LIST_ELEMENTS_RO(frr_pthread_list, n, fpt)) { + if (atomic_load_explicit(&fpt->running, + memory_order_relaxed)) + frr_pthread_stop(fpt, NULL); + } + } +} + +static void *frr_pthread_attr_non_controlled_start(void *arg) +{ + struct frr_pthread *fpt = arg; + + fpt->running = true; + + return NULL; +} + +/* Create a FRR pthread context from a non FRR pthread initialized from an + * external library in order to allow logging */ +int frr_pthread_non_controlled_startup(pthread_t thread, const char *name, + const char *os_name) +{ + struct rcu_thread *rcu_thread = rcu_thread_new(NULL); + + rcu_thread_start(rcu_thread); + + struct frr_pthread_attr attr = { + .start = frr_pthread_attr_non_controlled_start, + .stop = frr_pthread_attr_default.stop, + }; + struct frr_pthread *fpt; + + fpt = frr_pthread_new(&attr, name, os_name); + if (!fpt) + return -1; + + fpt->thread = thread; + fpt->rcu_thread = rcu_thread; + fpt->started = true; + + frr_pthread_inner(fpt); + + return 0; +} + +/* + * ---------------------------------------------------------------------------- + * Default Event Loop + * ---------------------------------------------------------------------------- + */ + +/* dummy task for sleeper pipe */ +static void fpt_dummy(struct event *thread) +{ +} + +/* poison pill task to end event loop */ +static void fpt_finish(struct event *thread) +{ + struct frr_pthread *fpt = EVENT_ARG(thread); + + atomic_store_explicit(&fpt->running, false, memory_order_relaxed); +} + +/* stop function, called from other threads to halt this one */ +static int fpt_halt(struct frr_pthread *fpt, void **res) +{ + event_add_event(fpt->master, &fpt_finish, fpt, 0, NULL); + pthread_join(fpt->thread, res); + + return 0; +} + +/* + * Entry pthread function & main event loop. + * + * Upon thread start the following actions occur: + * + * - frr_pthread's owner field is set to pthread ID. + * - All signals are blocked (except for unblockable signals). + * - Pthread's threadmaster is set to never handle pending signals + * - Poker pipe for poll() is created and queued as I/O source + * - The frr_pthread->running_cond condition variable is signalled to indicate + * that the previous actions have completed. It is not safe to assume any of + * the above have occurred before receiving this signal. + * + * After initialization is completed, the event loop begins running. Each tick, + * the following actions are performed before running the usual event system + * tick function: + * + * - Verify that the running boolean is set + * - Verify that there are no pending cancellation requests + * - Verify that there are tasks scheduled + * + * So long as the conditions are met, the event loop tick is run and the + * returned task is executed. + * + * If any of these conditions are not met, the event loop exits, closes the + * pipes and dies without running any cleanup functions. + */ +static void *fpt_run(void *arg) +{ + struct frr_pthread *fpt = arg; + fpt->master->owner = pthread_self(); + + zlog_tls_buffer_init(); + + int sleeper[2]; + pipe(sleeper); + event_add_read(fpt->master, &fpt_dummy, NULL, sleeper[0], NULL); + + fpt->master->handle_signals = false; + + frr_pthread_set_name(fpt); + + frr_pthread_notify_running(fpt); + + struct event task; + while (atomic_load_explicit(&fpt->running, memory_order_relaxed)) { + pthread_testcancel(); + if (event_fetch(fpt->master, &task)) { + event_call(&task); + } + } + + close(sleeper[1]); + close(sleeper[0]); + + zlog_tls_buffer_fini(); + + return NULL; +} diff --git a/lib/frr_pthread.h b/lib/frr_pthread.h new file mode 100644 index 0000000..bb751b7 --- /dev/null +++ b/lib/frr_pthread.h @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Utilities and interfaces for managing POSIX threads within FRR. + * Copyright (C) 2017 Cumulus Networks, Inc. + */ + +#ifndef _FRR_PTHREAD_H +#define _FRR_PTHREAD_H + +#include +#include "frratomic.h" +#include "memory.h" +#include "frrcu.h" +#include "frrevent.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define OS_THREAD_NAMELEN 16 + +struct frr_pthread; +struct frr_pthread_attr; + +struct frr_pthread_attr { + void *(*start)(void *); + int (*stop)(struct frr_pthread *, void **); +}; + +struct frr_pthread { + + /* + * Mutex protecting this structure. Must be taken for reading some + * fields, denoted by a 'Requires: mtx'. + */ + pthread_mutex_t mtx; + + /* pthread id */ + pthread_t thread; + + struct rcu_thread *rcu_thread; + + /* thread master for this pthread's thread.c event loop */ + struct event_loop *master; + + /* caller-specified data; start & stop funcs, name, id */ + struct frr_pthread_attr attr; + + /* + * Startup serialization: newly-started pthreads wait at a point + * very early in life so that there isn't a race with the + * starting pthread. The OS 'start' apis don't make any guarantees + * about which pthread runs first - the existing pthread that has + * called the 'start' api, or the new pthread that is just starting. + */ + pthread_cond_t startup_cond; + pthread_mutex_t startup_cond_mtx; + atomic_bool started; + + /* + * Notification mechanism for allowing pthreads to notify their parents + * when they are ready to do work. This mechanism has two associated + * functions: + * + * - frr_pthread_wait_running() + * This function should be called by the spawning thread after + * frr_pthread_run(). It safely waits until the spawned thread + * indicates that is ready to do work by posting to the condition + * variable. + * + * - frr_pthread_notify_running() + * This function should be called by the spawned thread when it is + * ready to do work. It will wake up any threads waiting on the + * previously described condition. + */ + pthread_cond_t *running_cond; + pthread_mutex_t *running_cond_mtx; + atomic_bool running; + + /* + * Fake thread-specific storage. No constraints on usage. Helpful when + * creating reentrant pthread implementations. Can be used to pass + * argument to pthread entry function. + * + * Requires: mtx + */ + void *data; + + /* + * Human-readable thread name. + * + * Requires: mtx + */ + char *name; + + /* Used in pthread_set_name max 16 characters */ + char os_name[OS_THREAD_NAMELEN]; +}; + +extern const struct frr_pthread_attr frr_pthread_attr_default; + +/* + * Initializes this module. + * + * Must be called before using any of the other functions. + */ +void frr_pthread_init(void); + +/* + * Uninitializes this module. + * + * Destroys all registered frr_pthread's and internal data structures. + * + * It is safe to call frr_pthread_init() after this function to reinitialize + * the module. + */ +void frr_pthread_finish(void); + +/* + * Creates a new frr_pthread with the given attributes. + * + * The 'attr' argument should be filled out with the desired attributes, + * including ID, start and stop functions and the desired name. Alternatively, + * if attr is NULL, the default attributes will be used. The pthread will be + * set up to run a basic threadmaster loop and the name will be "Anonymous". + * Scheduling tasks onto the threadmaster in the 'master' field of the returned + * frr_pthread will cause them to run on that pthread. + * + * @param attr - the thread attributes + * @param name - Human-readable name + * @param os_name - 16 characters (including '\0') thread name to set in os, + * @return the created frr_pthread upon success, or NULL upon failure + */ +struct frr_pthread *frr_pthread_new(const struct frr_pthread_attr *attr, + const char *name, const char *os_name); + +/* + * Changes the name of the frr_pthread as reported by the operating + * system. + * + * @param fpt - the frr_pthread to operate on + * @return - on success returns 0 otherwise nonzero error number. + */ +int frr_pthread_set_name(struct frr_pthread *fpt); + +/* + * Destroys an frr_pthread. + * + * Assumes that the associated pthread, if any, has already terminated. + * + * @param fpt - the frr_pthread to destroy + */ +void frr_pthread_destroy(struct frr_pthread *fpt); + +/* + * Creates a new pthread and binds it to a frr_pthread. + * + * This function is a wrapper for pthread_create. The first parameter is the + * frr_pthread to bind the created pthread to. All subsequent arguments are + * passed unmodified to pthread_create(). The frr_pthread * provided will be + * used as the argument to the pthread entry function. If it is necessary to + * pass additional data, the 'data' field in the frr_pthread may be used. + * + * This function returns the same code as pthread_create(). If the value is + * zero, the provided frr_pthread is bound to a running POSIX thread. If the + * value is less than zero, the provided frr_pthread is guaranteed to be a + * clean instance that may be susbsequently passed to frr_pthread_run(). + * + * @param fpt - frr_pthread * to run + * @param attr - see pthread_create(3) + * + * @return see pthread_create(3) + */ +int frr_pthread_run(struct frr_pthread *fpt, const pthread_attr_t *attr); + +/* + * Waits until the specified pthread has finished setting up and is ready to + * begin work. + * + * If the pthread's code makes use of the startup synchronization mechanism, + * this function should be called before attempting to use the functionality + * exposed by the pthread. It waits until the 'running' condition is satisfied + * (see struct definition of frr_pthread). + * + * @param fpt - the frr_pthread * to wait on + */ +void frr_pthread_wait_running(struct frr_pthread *fpt); + +/* + * Notifies other pthreads that the calling thread has finished setting up and + * is ready to begin work. + * + * This will allow any other pthreads waiting in 'frr_pthread_wait_running' to + * proceed. + * + * @param fpt - the frr_pthread * that has finished setting up + */ +void frr_pthread_notify_running(struct frr_pthread *fpt); + +/* + * Stops a frr_pthread with a result. + * + * @param fpt - frr_pthread * to stop + * @param result - where to store the thread's result, if any. May be NULL if a + * result is not needed. + */ +int frr_pthread_stop(struct frr_pthread *fpt, void **result); + +/* Stops all frr_pthread's. */ +void frr_pthread_stop_all(void); + +#ifndef HAVE_PTHREAD_CONDATTR_SETCLOCK +#define pthread_condattr_setclock(A, B) +#endif + +int frr_pthread_non_controlled_startup(pthread_t thread, const char *name, + const char *os_name); + +/* mutex auto-lock/unlock */ + +/* variant 1: + * (for short blocks, multiple mutexes supported) + * break & return can be used for aborting the block + * + * frr_with_mutex(&mtx, &mtx2) { + * if (error) + * break; + * ... + * } + */ +#define _frr_with_mutex(mutex) \ + *NAMECTR(_mtx_) __attribute__(( \ + unused, cleanup(_frr_mtx_unlock))) = _frr_mtx_lock(mutex), \ + /* end */ + +#define frr_with_mutex(...) \ + for (pthread_mutex_t MACRO_REPEAT(_frr_with_mutex, ##__VA_ARGS__) \ + *_once = NULL; _once == NULL; _once = (void *)1) \ + /* end */ + +/* variant 2: + * (more suitable for long blocks, no extra indentation) + * + * frr_mutex_lock_autounlock(&mtx); + * ... + */ +#define frr_mutex_lock_autounlock(mutex) \ + pthread_mutex_t *NAMECTR(_mtx_) \ + __attribute__((unused, cleanup(_frr_mtx_unlock))) = \ + _frr_mtx_lock(mutex) \ + /* end */ + +static inline pthread_mutex_t *_frr_mtx_lock(pthread_mutex_t *mutex) +{ + pthread_mutex_lock(mutex); + return mutex; +} + +static inline void _frr_mtx_unlock(pthread_mutex_t **mutex) +{ + if (!*mutex) + return; + pthread_mutex_unlock(*mutex); + *mutex = NULL; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_PTHREAD_H */ diff --git a/lib/frr_zmq.c b/lib/frr_zmq.c new file mode 100644 index 0000000..5273d36 --- /dev/null +++ b/lib/frr_zmq.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libzebra ZeroMQ bindings + * Copyright (C) 2015 David Lamparter + */ + +/* + * IF YOU MODIFY THIS FILE PLEASE RUN `make check` and ensure that + * the test_zmq.c unit test is still working. There are dependencies + * between the two that are extremely fragile. My understanding + * is that there is specialized ownership of the cb pointer based + * upon what is happening. Those assumptions are supposed to be + * tested in the test_zmq.c + */ +#include +#include + +#include "frrevent.h" +#include "memory.h" +#include "frr_zmq.h" +#include "log.h" +#include "lib_errors.h" + +XREF_SETUP(); + +DEFINE_MTYPE_STATIC(LIB, ZEROMQ_CB, "ZeroMQ callback"); + +/* libzmq's context */ +void *frrzmq_context = NULL; +static unsigned frrzmq_initcount = 0; + +void frrzmq_init(void) +{ + if (frrzmq_initcount++ == 0) { + frrzmq_context = zmq_ctx_new(); + zmq_ctx_set(frrzmq_context, ZMQ_IPV6, 1); + } +} + +void frrzmq_finish(void) +{ + if (--frrzmq_initcount == 0) { + zmq_ctx_term(frrzmq_context); + frrzmq_context = NULL; + } +} + +static void frrzmq_read_msg(struct event *t) +{ + struct frrzmq_cb **cbp = EVENT_ARG(t); + struct frrzmq_cb *cb; + zmq_msg_t msg; + unsigned partno; + unsigned char read = 0; + int ret, more; + size_t moresz; + + if (!cbp) + return; + cb = (*cbp); + if (!cb || !cb->zmqsock) + return; + + while (1) { + zmq_pollitem_t polli = {.socket = cb->zmqsock, + .events = ZMQ_POLLIN}; + ret = zmq_poll(&polli, 1, 0); + + if (ret < 0) + goto out_err; + + if (!(polli.revents & ZMQ_POLLIN)) + break; + + if (cb->read.cb_msg) { + cb->in_cb = true; + cb->read.cb_msg(cb->read.arg, cb->zmqsock); + cb->in_cb = false; + + read = 1; + + if (cb->read.cancelled) { + frrzmq_check_events(cbp, &cb->write, + ZMQ_POLLOUT); + cb->read.thread = NULL; + if (cb->write.cancelled && !cb->write.thread) + XFREE(MTYPE_ZEROMQ_CB, *cbp); + + return; + } + continue; + } + + partno = 0; + if (zmq_msg_init(&msg)) + goto out_err; + do { + ret = zmq_msg_recv(&msg, cb->zmqsock, ZMQ_NOBLOCK); + if (ret < 0) { + if (errno == EAGAIN) + break; + + zmq_msg_close(&msg); + goto out_err; + } + read = 1; + + cb->in_cb = true; + cb->read.cb_part(cb->read.arg, cb->zmqsock, &msg, + partno); + cb->in_cb = false; + + if (cb->read.cancelled) { + zmq_msg_close(&msg); + frrzmq_check_events(cbp, &cb->write, + ZMQ_POLLOUT); + cb->read.thread = NULL; + if (cb->write.cancelled && !cb->write.thread) + XFREE(MTYPE_ZEROMQ_CB, *cbp); + + return; + } + + /* cb_part may have read additional parts of the + * message; don't use zmq_msg_more here */ + moresz = sizeof(more); + more = 0; + ret = zmq_getsockopt(cb->zmqsock, ZMQ_RCVMORE, &more, + &moresz); + if (ret < 0) { + zmq_msg_close(&msg); + goto out_err; + } + + partno++; + } while (more); + zmq_msg_close(&msg); + } + + if (read) + frrzmq_check_events(cbp, &cb->write, ZMQ_POLLOUT); + + event_add_read(t->master, frrzmq_read_msg, cbp, cb->fd, + &cb->read.thread); + return; + +out_err: + flog_err(EC_LIB_ZMQ, "ZeroMQ read error: %s(%d)", strerror(errno), + errno); + if (cb->read.cb_error) + cb->read.cb_error(cb->read.arg, cb->zmqsock); +} + +int _frrzmq_event_add_read(const struct xref_eventsched *xref, + struct event_loop *master, + void (*msgfunc)(void *arg, void *zmqsock), + void (*partfunc)(void *arg, void *zmqsock, + zmq_msg_t *msg, unsigned partnum), + void (*errfunc)(void *arg, void *zmqsock), void *arg, + void *zmqsock, struct frrzmq_cb **cbp) +{ + int fd, events; + size_t len; + struct frrzmq_cb *cb; + + if (!cbp) + return -1; + if (!(msgfunc || partfunc) || (msgfunc && partfunc)) + return -1; + len = sizeof(fd); + if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len)) + return -1; + len = sizeof(events); + if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len)) + return -1; + + if (*cbp) + cb = *cbp; + else { + cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb)); + cb->write.cancelled = true; + *cbp = cb; + } + + cb->zmqsock = zmqsock; + cb->fd = fd; + cb->read.arg = arg; + cb->read.cb_msg = msgfunc; + cb->read.cb_part = partfunc; + cb->read.cb_error = errfunc; + cb->read.cancelled = false; + cb->in_cb = false; + + if (events & ZMQ_POLLIN) { + event_cancel(&cb->read.thread); + + event_add_event(master, frrzmq_read_msg, cbp, fd, + &cb->read.thread); + } else + event_add_read(master, frrzmq_read_msg, cbp, fd, + &cb->read.thread); + return 0; +} + +static void frrzmq_write_msg(struct event *t) +{ + struct frrzmq_cb **cbp = EVENT_ARG(t); + struct frrzmq_cb *cb; + unsigned char written = 0; + int ret; + + if (!cbp) + return; + cb = (*cbp); + if (!cb || !cb->zmqsock) + return; + + while (1) { + zmq_pollitem_t polli = {.socket = cb->zmqsock, + .events = ZMQ_POLLOUT}; + ret = zmq_poll(&polli, 1, 0); + + if (ret < 0) + goto out_err; + + if (!(polli.revents & ZMQ_POLLOUT)) + break; + + if (cb->write.cb_msg) { + cb->in_cb = true; + cb->write.cb_msg(cb->write.arg, cb->zmqsock); + cb->in_cb = false; + + written = 1; + + if (cb->write.cancelled) { + frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN); + cb->write.thread = NULL; + if (cb->read.cancelled && !cb->read.thread) + XFREE(MTYPE_ZEROMQ_CB, *cbp); + + return; + } + continue; + } + } + + if (written) + frrzmq_check_events(cbp, &cb->read, ZMQ_POLLIN); + + event_add_write(t->master, frrzmq_write_msg, cbp, cb->fd, + &cb->write.thread); + return; + +out_err: + flog_err(EC_LIB_ZMQ, "ZeroMQ write error: %s(%d)", strerror(errno), + errno); + if (cb->write.cb_error) + cb->write.cb_error(cb->write.arg, cb->zmqsock); +} + +int _frrzmq_event_add_write(const struct xref_eventsched *xref, + struct event_loop *master, + void (*msgfunc)(void *arg, void *zmqsock), + void (*errfunc)(void *arg, void *zmqsock), + void *arg, void *zmqsock, struct frrzmq_cb **cbp) +{ + int fd, events; + size_t len; + struct frrzmq_cb *cb; + + if (!cbp) + return -1; + if (!msgfunc) + return -1; + len = sizeof(fd); + if (zmq_getsockopt(zmqsock, ZMQ_FD, &fd, &len)) + return -1; + len = sizeof(events); + if (zmq_getsockopt(zmqsock, ZMQ_EVENTS, &events, &len)) + return -1; + + if (*cbp) + cb = *cbp; + else { + cb = XCALLOC(MTYPE_ZEROMQ_CB, sizeof(struct frrzmq_cb)); + cb->read.cancelled = true; + *cbp = cb; + } + + cb->zmqsock = zmqsock; + cb->fd = fd; + cb->write.arg = arg; + cb->write.cb_msg = msgfunc; + cb->write.cb_part = NULL; + cb->write.cb_error = errfunc; + cb->write.cancelled = false; + cb->in_cb = false; + + if (events & ZMQ_POLLOUT) { + event_cancel(&cb->write.thread); + + _event_add_event(xref, master, frrzmq_write_msg, cbp, fd, + &cb->write.thread); + } else + event_add_write(master, frrzmq_write_msg, cbp, fd, + &cb->write.thread); + return 0; +} + +void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core) +{ + if (!cb || !*cb) + return; + core->cancelled = true; + event_cancel(&core->thread); + + /* If cancelled from within a callback, don't try to free memory + * in this path. + */ + if ((*cb)->in_cb) + return; + + /* Ok to free the callback context if no more ... context. */ + if ((*cb)->read.cancelled && !(*cb)->read.thread + && (*cb)->write.cancelled && ((*cb)->write.thread == NULL)) + XFREE(MTYPE_ZEROMQ_CB, *cb); +} + +void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core, + int event) +{ + struct frrzmq_cb *cb; + int events; + size_t len; + + if (!cbp) + return; + cb = (*cbp); + if (!cb || !cb->zmqsock) + return; + + len = sizeof(events); + if (zmq_getsockopt(cb->zmqsock, ZMQ_EVENTS, &events, &len)) + return; + if ((events & event) && core->thread && !core->cancelled) { + struct event_loop *tm = core->thread->master; + + event_cancel(&core->thread); + + if (event == ZMQ_POLLIN) + event_add_event(tm, frrzmq_read_msg, cbp, cb->fd, + &core->thread); + else + event_add_event(tm, frrzmq_write_msg, cbp, cb->fd, + &core->thread); + } +} diff --git a/lib/frr_zmq.h b/lib/frr_zmq.h new file mode 100644 index 0000000..73da377 --- /dev/null +++ b/lib/frr_zmq.h @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libzebra ZeroMQ bindings + * Copyright (C) 2015 David Lamparter + */ + +#ifndef _FRRZMQ_H +#define _FRRZMQ_H + +#include "frrevent.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* linking/packaging note: this is a separate library that needs to be + * linked into any daemon/library/module that wishes to use its + * functionality. The purpose of this is to encapsulate the libzmq + * dependency and not make libfrr/FRR itself depend on libzmq. + * + * libfrrzmq should be put in LDFLAGS/LIBADD *before* either libfrr or + * libzmq, and both of these should always be listed, e.g. + * foo_LDFLAGS = libfrrzmq.la libfrr.la $(ZEROMQ_LIBS) + */ + +/* callback integration */ +struct cb_core { + struct event *thread; + void *arg; + + bool cancelled; + + void (*cb_msg)(void *arg, void *zmqsock); + void (*cb_part)(void *arg, void *zmqsock, zmq_msg_t *msg, + unsigned partnum); + void (*cb_error)(void *arg, void *zmqsock); +}; + +struct frrzmq_cb { + void *zmqsock; + int fd; + + bool in_cb; /* This context is in a read or write callback. */ + + struct cb_core read; + struct cb_core write; +}; + +/* libzmq's context + * + * this is mostly here as a convenience, it has IPv6 enabled but nothing + * else is tied to it; you can use a separate context without problems + */ +extern void *frrzmq_context; + +extern void frrzmq_init(void); +extern void frrzmq_finish(void); + +#define _xref_zmq_a(type, f, d, call) \ + ({ \ + static const struct xref_eventsched _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_EVENTSCHED, NULL, __func__), \ + .funcname = #f, \ + .dest = #d, \ + .event_type = EVENT_##type, \ + }; \ + XREF_LINK(_xref.xref); \ + call; \ + }) /* end */ + +/* core event registration, one of these 2 macros should be used */ +#define frrzmq_event_add_read_msg(m, f, e, a, z, d) \ + _xref_zmq_a(READ, f, d, \ + _frrzmq_event_add_read(&_xref, m, f, NULL, e, a, z, d)) + +#define frrzmq_event_add_read_part(m, f, e, a, z, d) \ + _xref_zmq_a(READ, f, d, \ + _frrzmq_event_add_read(&_xref, m, NULL, f, e, a, z, d)) + +#define frrzmq_event_add_write_msg(m, f, e, a, z, d) \ + _xref_zmq_a(WRITE, f, d, \ + _frrzmq_event_add_write(&_xref, m, f, e, a, z, d)) + +struct cb_core; +struct frrzmq_cb; + +/* Set up a POLLIN or POLLOUT notification to be called from the libfrr main + * loop. This has the following properties: + * + * - since ZeroMQ works with edge triggered notifications, it will loop and + * dispatch as many events as ZeroMQ has pending at the time libfrr calls + * into this code + * - due to this looping (which means it non-single-issue), the callback is + * also persistent. Do _NOT_ re-register the event inside of your + * callback function. + * - either msgfunc or partfunc will be called (only one can be specified) + * - msgfunc is called once for each incoming message + * - if partfunc is specified, the message is read and partfunc is called + * for each ZeroMQ multi-part subpart. Note that you can't send replies + * before all parts have been read because that violates the ZeroMQ FSM. + * - write version doesn't allow for partial callback, you must handle the + * whole message (all parts) in msgfunc callback + * - you can safely cancel the callback from within itself + * - installing a callback will check for pending events (ZMQ_EVENTS) and + * may schedule the event to run as soon as libfrr is back in its main + * loop. + */ +extern int +_frrzmq_event_add_read(const struct xref_eventsched *xref, + struct event_loop *master, + void (*msgfunc)(void *arg, void *zmqsock), + void (*partfunc)(void *arg, void *zmqsock, + zmq_msg_t *msg, unsigned partnum), + void (*errfunc)(void *arg, void *zmqsock), void *arg, + void *zmqsock, struct frrzmq_cb **cb); +extern int _frrzmq_event_add_write(const struct xref_eventsched *xref, + struct event_loop *master, + void (*msgfunc)(void *arg, void *zmqsock), + void (*errfunc)(void *arg, void *zmqsock), + void *arg, void *zmqsock, + struct frrzmq_cb **cb); + +extern void frrzmq_thread_cancel(struct frrzmq_cb **cb, struct cb_core *core); + +/* + * http://api.zeromq.org/4-2:zmq-getsockopt#toc10 + * + * As the descriptor is edge triggered, applications must update the state of + * ZMQ_EVENTS after each invocation of zmq_send or zmq_recv.To be more explicit: + * after calling zmq_send the socket may become readable (and vice versa) + * without triggering a read event on the file descriptor. + */ +extern void frrzmq_check_events(struct frrzmq_cb **cbp, struct cb_core *core, + int event); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRRZMQ_H */ diff --git a/lib/frratomic.h b/lib/frratomic.h new file mode 100644 index 0000000..3a89052 --- /dev/null +++ b/lib/frratomic.h @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRRATOMIC_H +#define _FRRATOMIC_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef FRR_AUTOCONF_ATOMIC +#error autoconf checks for atomic functions were not properly run +#endif + +/* ISO C11 */ +#ifdef __cplusplus +#include +#include +using std::atomic_int; +using std::memory_order; +using std::memory_order_relaxed; +using std::memory_order_acquire; +using std::memory_order_release; +using std::memory_order_acq_rel; +using std::memory_order_consume; +using std::memory_order_seq_cst; + +typedef std::atomic atomic_bool; +typedef std::atomic atomic_size_t; +typedef std::atomic atomic_uint_fast32_t; +typedef std::atomic atomic_uintptr_t; + +#elif defined(HAVE_STDATOMIC_H) +#include + +/* These are available in gcc, but not in stdatomic */ +#define atomic_add_fetch_explicit __atomic_add_fetch +#define atomic_sub_fetch_explicit __atomic_sub_fetch +#define atomic_and_fetch_explicit __atomic_and_fetch +#define atomic_or_fetch_explicit __atomic_or_fetch + +/* gcc 4.7 and newer */ +#elif defined(HAVE___ATOMIC) + +#define _Atomic volatile +#define _ATOMIC_WANT_TYPEDEFS + +#define memory_order_relaxed __ATOMIC_RELAXED +#define memory_order_consume __ATOMIC_CONSUME +#define memory_order_acquire __ATOMIC_ACQUIRE +#define memory_order_release __ATOMIC_RELEASE +#define memory_order_acq_rel __ATOMIC_ACQ_REL +#define memory_order_seq_cst __ATOMIC_SEQ_CST + +#define atomic_load_explicit __atomic_load_n +#define atomic_store_explicit __atomic_store_n +#define atomic_exchange_explicit __atomic_exchange_n +#define atomic_fetch_add_explicit __atomic_fetch_add +#define atomic_fetch_sub_explicit __atomic_fetch_sub +#define atomic_fetch_and_explicit __atomic_fetch_and +#define atomic_fetch_or_explicit __atomic_fetch_or + +#define atomic_add_fetch_explicit __atomic_add_fetch +#define atomic_sub_fetch_explicit __atomic_sub_fetch +#define atomic_and_fetch_explicit __atomic_and_fetch +#define atomic_or_fetch_explicit __atomic_or_fetch + +#define atomic_compare_exchange_weak_explicit(atom, expect, desire, mem1, \ + mem2) \ + __atomic_compare_exchange_n(atom, expect, desire, 1, mem1, mem2) +#define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \ + mem2) \ + __atomic_compare_exchange_n(atom, expect, desire, 0, mem1, mem2) + +/* gcc 4.1 and newer, + * clang 3.3 (possibly older) + * + * __sync_swap isn't in gcc's documentation, but clang has it + * + * note __sync_synchronize() + */ +#elif defined(HAVE___SYNC) + +#define _Atomic volatile +#define _ATOMIC_WANT_TYPEDEFS + +#define memory_order_relaxed 0 +#define memory_order_consume 0 +#define memory_order_acquire 0 +#define memory_order_release 0 +#define memory_order_acq_rel 0 +#define memory_order_seq_cst 0 + +#define atomic_load_explicit(ptr, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_fetch_and_add((ptr), 0); \ + __sync_synchronize(); \ + rval; \ + }) +#define atomic_store_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + *(ptr) = (val); \ + __sync_synchronize(); \ + (void)0; \ + }) +#ifdef HAVE___SYNC_SWAP +#define atomic_exchange_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_swap((ptr, val), 0); \ + __sync_synchronize(); \ + rval; \ + }) +#else /* !HAVE___SYNC_SWAP */ +#define atomic_exchange_explicit(ptr, val, mem) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + typeof(val) _val = (val); \ + __sync_synchronize(); \ + typeof(*ptr) old1, old2 = __sync_fetch_and_add(_ptr, 0); \ + do { \ + old1 = old2; \ + old2 = __sync_val_compare_and_swap(_ptr, old1, _val); \ + } while (old1 != old2); \ + __sync_synchronize(); \ + old2; \ + }) +#endif /* !HAVE___SYNC_SWAP */ +#define atomic_fetch_add_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_fetch_and_add((ptr), (val)); \ + __sync_synchronize(); \ + rval; \ + }) +#define atomic_fetch_sub_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_fetch_and_sub((ptr), (val)); \ + __sync_synchronize(); \ + rval; \ + }) + +#define atomic_compare_exchange_strong_explicit(atom, expect, desire, mem1, \ + mem2) \ + ({ \ + typeof(atom) _atom = (atom); \ + typeof(expect) _expect = (expect); \ + typeof(desire) _desire = (desire); \ + __sync_synchronize(); \ + typeof(*atom) rval = \ + __sync_val_compare_and_swap(_atom, *_expect, _desire); \ + __sync_synchronize(); \ + bool ret = (rval == *_expect); \ + *_expect = rval; \ + ret; \ + }) +#define atomic_compare_exchange_weak_explicit \ + atomic_compare_exchange_strong_explicit + +#define atomic_fetch_and_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_fetch_and_and(ptr, val); \ + __sync_synchronize(); \ + rval; \ + }) +#define atomic_fetch_or_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_fetch_and_or(ptr, val); \ + __sync_synchronize(); \ + rval; \ + }) + +#define atomic_add_fetch_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_add_and_fetch((ptr), (val)); \ + __sync_synchronize(); \ + rval; \ + }) +#define atomic_sub_fetch_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_sub_and_fetch((ptr), (val)); \ + __sync_synchronize(); \ + rval; \ + }) + +#define atomic_and_fetch_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_and_and_fetch(ptr, val); \ + __sync_synchronize(); \ + rval; \ + }) +#define atomic_or_fetch_explicit(ptr, val, mem) \ + ({ \ + __sync_synchronize(); \ + typeof(*ptr) rval = __sync_or_and_fetch(ptr, val); \ + __sync_synchronize(); \ + rval; \ + }) + +#else /* !HAVE___ATOMIC && !HAVE_STDATOMIC_H */ +#error no atomic functions... +#endif + +#ifdef _ATOMIC_WANT_TYPEDEFS +#undef _ATOMIC_WANT_TYPEDEFS + +#include +#include + +typedef _Atomic bool atomic_bool; +typedef _Atomic size_t atomic_size_t; +typedef _Atomic uint_fast32_t atomic_uint_fast32_t; +typedef _Atomic uintptr_t atomic_uintptr_t; +#endif + +#endif /* _FRRATOMIC_H */ diff --git a/lib/frrcu.c b/lib/frrcu.c new file mode 100644 index 0000000..b85c525 --- /dev/null +++ b/lib/frrcu.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc. + */ + +/* implementation notes: this is an epoch-based RCU implementation. rcu_seq + * (global variable) counts the current epoch. Threads hold a specific epoch + * in rcu_read_lock(). This is the oldest epoch a thread might be accessing + * data from. + * + * The rcu_seq global is only pushed forward on rcu_read_lock() and + * rcu_read_unlock() calls. This makes things a tad more efficient since + * those are the only places it matters: + * - on rcu_read_lock, we don't want to hold an old epoch pointlessly + * - on rcu_read_unlock, we want to make sure we're not stuck on an old epoch + * when heading into a long idle period where no thread holds RCU + * + * rcu_thread structures themselves are RCU-free'd. + * + * rcu_head structures are the most iffy; normally for an ATOMLIST we would + * need to make sure we use rcu_free or pthread_rwlock to deallocate old items + * to prevent ABA or use-after-free problems. However, our ATOMLIST code + * guarantees that if the list remains non-empty in all cases, we only need + * the "last" pointer to do an "add_tail()", i.e. we can't run into ABA/UAF + * issues - but we do need to keep at least 1 item on the list. + * + * (Search the atomlist code for all uses of "last") + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_PTHREAD_NP_H +#include +#endif +#include +#include +#include + +#include "frrcu.h" +#include "seqlock.h" +#include "atomlist.h" + +DEFINE_MTYPE_STATIC(LIB, RCU_THREAD, "RCU thread"); +DEFINE_MTYPE_STATIC(LIB, RCU_NEXT, "RCU sequence barrier"); + +DECLARE_ATOMLIST(rcu_heads, struct rcu_head, head); + +PREDECL_ATOMLIST(rcu_threads); +struct rcu_thread { + struct rcu_threads_item head; + + struct rcu_head rcu_head; + + struct seqlock rcu; + + /* only accessed by thread itself, not atomic */ + unsigned depth; +}; +DECLARE_ATOMLIST(rcu_threads, struct rcu_thread, head); + +static const struct rcu_action rcua_next = { .type = RCUA_NEXT }; +static const struct rcu_action rcua_end = { .type = RCUA_END }; +static const struct rcu_action rcua_close = { .type = RCUA_CLOSE }; + +struct rcu_next { + struct rcu_head head_free; + struct rcu_head head_next; +}; + +#define rcu_free_internal(mtype, ptr, field) \ + do { \ + typeof(ptr) _ptr = (ptr); \ + struct rcu_head *_rcu_head = &_ptr->field; \ + static const struct rcu_action _rcu_action = { \ + .type = RCUA_FREE, \ + .u.free = { \ + .mt = mtype, \ + .offset = offsetof(typeof(*_ptr), field), \ + }, \ + }; \ + _rcu_head->action = &_rcu_action; \ + rcu_heads_add_tail(&rcu_heads, _rcu_head); \ + } while (0) + +/* primary global RCU position */ +static struct seqlock rcu_seq; +/* this is set to rcu_seq whenever something is added on the RCU queue. + * rcu_read_lock() and rcu_read_unlock() will then bump rcu_seq up one step. + */ +static _Atomic seqlock_val_t rcu_dirty; + +static struct rcu_threads_head rcu_threads; +static struct rcu_heads_head rcu_heads; + +/* main thread & RCU sweeper have pre-setup rcu_thread structures. The + * reasons are different: + * + * - rcu_thread_main is there because the main thread isn't started like + * other threads, it's implicitly created when the program is started. So + * rcu_thread_main matches up implicitly. + * + * - rcu_thread_rcu isn't actually put on the rcu_threads list (makes no + * sense really), it only exists so we can call RCU-using functions from + * the RCU thread without special handling in rcu_read_lock/unlock. + */ +static struct rcu_thread rcu_thread_main; +static struct rcu_thread rcu_thread_rcu; + +static pthread_t rcu_pthread; +static pthread_key_t rcu_thread_key; +static bool rcu_active; + +static void rcu_start(void); +static void rcu_bump(void); + +/* + * preinitialization for main thread + */ +static void rcu_thread_end(void *rcu_thread); + +static void rcu_preinit(void) __attribute__((constructor)); +static void rcu_preinit(void) +{ + struct rcu_thread *rt; + + rt = &rcu_thread_main; + rt->depth = 1; + seqlock_init(&rt->rcu); + seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL); + + pthread_key_create(&rcu_thread_key, rcu_thread_end); + pthread_setspecific(rcu_thread_key, rt); + + rcu_threads_add_tail(&rcu_threads, rt); + + /* RCU sweeper's rcu_thread is a dummy, NOT added to rcu_threads */ + rt = &rcu_thread_rcu; + rt->depth = 1; + + seqlock_init(&rcu_seq); + seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL); +} + +static struct rcu_thread *rcu_self(void) +{ + return (struct rcu_thread *)pthread_getspecific(rcu_thread_key); +} + +struct rcu_thread *rcu_thread_new(void *arg) +{ + struct rcu_thread *rt, *cur = arg; + + /* new thread always starts with rcu_read_lock held at depth 1, and + * holding the same epoch as the parent (this makes it possible to + * use RCU for things passed into the thread through its arg) + */ + rt = XCALLOC(MTYPE_RCU_THREAD, sizeof(*rt)); + rt->depth = 1; + + seqlock_init(&rt->rcu); + if (cur) + seqlock_acquire(&rt->rcu, &cur->rcu); + + rcu_threads_add_tail(&rcu_threads, rt); + + return rt; +} + +/* + * thread management (for the non-main thread) + */ +struct rcu_thread *rcu_thread_prepare(void) +{ + struct rcu_thread *cur; + + rcu_assert_read_locked(); + + if (!rcu_active) + rcu_start(); + + cur = rcu_self(); + assert(cur->depth); + + return rcu_thread_new(cur); +} + +void rcu_thread_start(struct rcu_thread *rt) +{ + pthread_setspecific(rcu_thread_key, rt); +} + +void rcu_thread_unprepare(struct rcu_thread *rt) +{ + if (rt == &rcu_thread_rcu) + return; + + rt->depth = 1; + seqlock_acquire(&rt->rcu, &rcu_seq); + + rcu_bump(); + if (rt != &rcu_thread_main) + /* this free() happens after seqlock_release() below */ + rcu_free_internal(MTYPE_RCU_THREAD, rt, rcu_head); + + rcu_threads_del(&rcu_threads, rt); + seqlock_release(&rt->rcu); +} + +static void rcu_thread_end(void *rtvoid) +{ + struct rcu_thread *rt = rtvoid; + rcu_thread_unprepare(rt); +} + +/* + * main RCU control aspects + */ + +static void rcu_bump(void) +{ + struct rcu_next *rn; + + rn = XMALLOC(MTYPE_RCU_NEXT, sizeof(*rn)); + + /* note: each RCUA_NEXT item corresponds to exactly one seqno bump. + * This means we don't need to communicate which seqno is which + * RCUA_NEXT, since we really don't care. + */ + + /* + * Important race condition: while rcu_heads_add_tail is executing, + * there is an intermediate point where the rcu_heads "last" pointer + * already points to rn->head_next, but rn->head_next isn't added to + * the list yet. That means any other "add_tail" calls append to this + * item, which isn't fully on the list yet. Freeze this thread at + * that point and look at another thread doing a rcu_bump. It adds + * these two items and then does a seqlock_bump. But the rcu_heads + * list is still "interrupted" and there's no RCUA_NEXT on the list + * yet (from either the frozen thread or the second thread). So + * rcu_main() might actually hit the end of the list at the + * "interrupt". + * + * This situation is prevented by requiring that rcu_read_lock is held + * for any calls to rcu_bump, since if we're holding the current RCU + * epoch, that means rcu_main can't be chewing on rcu_heads and hit + * that interruption point. Only by the time the thread has continued + * to rcu_read_unlock() - and therefore completed the add_tail - the + * RCU sweeper gobbles up the epoch and can be sure to find at least + * the RCUA_NEXT and RCUA_FREE items on rcu_heads. + */ + rn->head_next.action = &rcua_next; + rcu_heads_add_tail(&rcu_heads, &rn->head_next); + + /* free rn that we allocated above. + * + * This is INTENTIONALLY not built into the RCUA_NEXT action. This + * ensures that after the action above is popped off the queue, there + * is still at least 1 item on the RCU queue. This means we never + * delete the last item, which is extremely important since it keeps + * the atomlist ->last pointer alive and well. + * + * If we were to "run dry" on the RCU queue, add_tail may run into the + * "last item is being deleted - start over" case, and then we may end + * up accessing old RCU queue items that are already free'd. + */ + rcu_free_internal(MTYPE_RCU_NEXT, rn, head_free); + + /* Only allow the RCU sweeper to run after these 2 items are queued. + * + * If another thread enqueues some RCU action in the intermediate + * window here, nothing bad happens - the queued action is associated + * with a larger seq# than strictly necessary. Thus, it might get + * executed a bit later, but that's not a problem. + * + * If another thread acquires the read lock in this window, it holds + * the previous epoch, but its RCU queue actions will be in the next + * epoch. This isn't a problem either, just a tad inefficient. + */ + seqlock_bump(&rcu_seq); +} + +static void rcu_bump_maybe(void) +{ + seqlock_val_t dirty; + + dirty = atomic_load_explicit(&rcu_dirty, memory_order_relaxed); + /* no problem if we race here and multiple threads bump rcu_seq; + * bumping too much causes no issues while not bumping enough will + * result in delayed cleanup + */ + if (dirty == seqlock_cur(&rcu_seq)) + rcu_bump(); +} + +void rcu_read_lock(void) +{ + struct rcu_thread *rt = rcu_self(); + + assert(rt); + if (rt->depth++ > 0) + return; + + seqlock_acquire(&rt->rcu, &rcu_seq); + /* need to hold RCU for bump ... */ + rcu_bump_maybe(); + /* ... but no point in holding the old epoch if we just bumped */ + seqlock_acquire(&rt->rcu, &rcu_seq); +} + +void rcu_read_unlock(void) +{ + struct rcu_thread *rt = rcu_self(); + + assert(rt && rt->depth); + if (--rt->depth > 0) + return; + rcu_bump_maybe(); + seqlock_release(&rt->rcu); +} + +void rcu_assert_read_locked(void) +{ + struct rcu_thread *rt = rcu_self(); + assert(rt && rt->depth && seqlock_held(&rt->rcu)); +} + +void rcu_assert_read_unlocked(void) +{ + struct rcu_thread *rt = rcu_self(); + assert(rt && !rt->depth && !seqlock_held(&rt->rcu)); +} + +/* + * RCU resource-release thread + */ + +static void *rcu_main(void *arg); + +static void rcu_start(void) +{ + /* ensure we never handle signals on the RCU thread by blocking + * everything here (new thread inherits signal mask) + */ + sigset_t oldsigs, blocksigs; + + sigfillset(&blocksigs); + pthread_sigmask(SIG_BLOCK, &blocksigs, &oldsigs); + + rcu_active = true; + + assert(!pthread_create(&rcu_pthread, NULL, rcu_main, NULL)); + + pthread_sigmask(SIG_SETMASK, &oldsigs, NULL); + +#ifdef HAVE_PTHREAD_SETNAME_NP +# ifdef GNU_LINUX + pthread_setname_np(rcu_pthread, "RCU sweeper"); +# elif defined(__NetBSD__) + pthread_setname_np(rcu_pthread, "RCU sweeper", NULL); +# endif +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(rcu_pthread, "RCU sweeper"); +#endif +} + +static void rcu_do(struct rcu_head *rh) +{ + struct rcu_head_close *rhc; + void *p; + + switch (rh->action->type) { + case RCUA_FREE: + p = (char *)rh - rh->action->u.free.offset; + if (rh->action->u.free.mt) + qfree(rh->action->u.free.mt, p); + else + free(p); + break; + case RCUA_CLOSE: + rhc = container_of(rh, struct rcu_head_close, + rcu_head); + close(rhc->fd); + break; + case RCUA_CALL: + p = (char *)rh - rh->action->u.call.offset; + rh->action->u.call.fptr(p); + break; + + case RCUA_INVALID: + case RCUA_NEXT: + case RCUA_END: + default: + assert(0); + } +} + +static void rcu_watchdog(struct rcu_thread *rt) +{ +#if 0 + /* future work: print a backtrace for the thread that's holding up + * RCU. The only (good) way of doing that is to send a signal to the + * other thread, save away the backtrace in the signal handler, and + * block here until the signal is done processing. + * + * Just haven't implemented that yet. + */ + fprintf(stderr, "RCU watchdog %p\n", rt); +#endif +} + +static void *rcu_main(void *arg) +{ + struct rcu_thread *rt; + struct rcu_head *rh = NULL; + bool end = false; + struct timespec maxwait; + + seqlock_val_t rcuval = SEQLOCK_STARTVAL; + + pthread_setspecific(rcu_thread_key, &rcu_thread_rcu); + + while (!end) { + seqlock_wait(&rcu_seq, rcuval); + + /* RCU watchdog timeout, TODO: configurable value */ + clock_gettime(CLOCK_MONOTONIC, &maxwait); + maxwait.tv_nsec += 100 * 1000 * 1000; + if (maxwait.tv_nsec >= 1000000000) { + maxwait.tv_sec++; + maxwait.tv_nsec -= 1000000000; + } + + frr_each (rcu_threads, &rcu_threads, rt) + if (!seqlock_timedwait(&rt->rcu, rcuval, &maxwait)) { + rcu_watchdog(rt); + seqlock_wait(&rt->rcu, rcuval); + } + + while ((rh = rcu_heads_pop(&rcu_heads))) { + if (rh->action->type == RCUA_NEXT) + break; + else if (rh->action->type == RCUA_END) + end = true; + else + rcu_do(rh); + } + + rcuval += SEQLOCK_INCR; + } + + /* rcu_shutdown can only be called singlethreaded, and it does a + * pthread_join, so it should be impossible that anything ended up + * on the queue after RCUA_END + */ +#if 1 + assert(!rcu_heads_first(&rcu_heads)); +#else + while ((rh = rcu_heads_pop(&rcu_heads))) + if (rh->action->type >= RCUA_FREE) + rcu_do(rh); +#endif + return NULL; +} + +void rcu_shutdown(void) +{ + static struct rcu_head rcu_head_end; + struct rcu_thread *rt = rcu_self(); + void *retval; + + if (!rcu_active) + return; + + rcu_assert_read_locked(); + assert(rcu_threads_count(&rcu_threads) == 1); + + rcu_enqueue(&rcu_head_end, &rcua_end); + + rt->depth = 0; + seqlock_release(&rt->rcu); + seqlock_release(&rcu_seq); + rcu_active = false; + + /* clearing rcu_active is before pthread_join in case we hang in + * pthread_join & get a SIGTERM or something - in that case, just + * ignore the maybe-still-running RCU thread + */ + if (pthread_join(rcu_pthread, &retval) == 0) { + seqlock_acquire_val(&rcu_seq, SEQLOCK_STARTVAL); + seqlock_acquire_val(&rt->rcu, SEQLOCK_STARTVAL); + rt->depth = 1; + } +} + +/* + * RCU'd free functions + */ + +void rcu_enqueue(struct rcu_head *rh, const struct rcu_action *action) +{ + /* refer to rcu_bump() for why we need to hold RCU when adding items + * to rcu_heads + */ + rcu_assert_read_locked(); + + rh->action = action; + + if (!rcu_active) { + rcu_do(rh); + return; + } + rcu_heads_add_tail(&rcu_heads, rh); + atomic_store_explicit(&rcu_dirty, seqlock_cur(&rcu_seq), + memory_order_relaxed); +} + +void rcu_close(struct rcu_head_close *rhc, int fd) +{ + rhc->fd = fd; + rcu_enqueue(&rhc->rcu_head, &rcua_close); +} diff --git a/lib/frrcu.h b/lib/frrcu.h new file mode 100644 index 0000000..9f07a69 --- /dev/null +++ b/lib/frrcu.h @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2017-19 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRRCU_H +#define _FRRCU_H + +#include + +#include "memory.h" +#include "atomlist.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* quick RCU primer: + * There's a global sequence counter. Whenever a thread does a + * rcu_read_lock(), it is marked as holding the current sequence counter. + * When something is cleaned with RCU, the global sequence counter is + * increased and the item is queued for cleanup - *after* all threads are + * at a more recent sequence counter (or no sequence counter / unheld). + * + * So, by delaying resource cleanup, RCU ensures that things don't go away + * while another thread may hold a (stale) reference. + * + * Note that even if a thread is in rcu_read_lock(), it is invalid for that + * thread to access bits after rcu_free() & co on them. This is a design + * choice to allow no-op'ing out the entire RCU mechanism if we're running + * singlethreaded. (Also allows some optimization on the counter bumping.) + * + * differences from Linux Kernel RCU: + * - there's no rcu_synchronize(), if you really need to defer something + * use rcu_call() (and double check it's really necessary) + * - rcu_dereference() and rcu_assign_pointer() don't exist, use atomic_* + * instead (ATOM* list structures do the right thing) + */ + +/* opaque */ +struct rcu_thread; + +/* sets up rcu thread info + * + * return value must be passed into the thread's call to rcu_thread_start() + */ +extern struct rcu_thread *rcu_thread_new(void *arg); + +/* called before new thread creation, sets up rcu thread info for new thread + * before it actually exits. This ensures possible RCU references are held + * for thread startup. + * + * return value must be passed into the new thread's call to rcu_thread_start() + */ +extern struct rcu_thread *rcu_thread_prepare(void); + +/* cleanup in case pthread_create() fails */ +extern void rcu_thread_unprepare(struct rcu_thread *rcu_thread); + +/* called early in the new thread, with the return value from the above. + * NB: new thread is initially in RCU-held state! (at depth 1) + * + * TBD: maybe inherit RCU state from rcu_thread_prepare()? + */ +extern void rcu_thread_start(struct rcu_thread *rcu_thread); + +/* thread exit is handled through pthread_key_create's destructor function */ + +/* global RCU shutdown - must be called with only 1 active thread left. waits + * until remaining RCU actions are done & RCU thread has exited. + * + * This is mostly here to get a clean exit without memleaks. + */ +extern void rcu_shutdown(void); + +/* enter / exit RCU-held state. counter-based, so can be called nested. */ +extern void rcu_read_lock(void); +extern void rcu_read_unlock(void); + +/* for debugging / safety checks */ +extern void rcu_assert_read_locked(void); +extern void rcu_assert_read_unlocked(void); + +enum rcu_action_type { + RCUA_INVALID = 0, + /* used internally by the RCU code, shouldn't ever show up outside */ + RCUA_NEXT, + RCUA_END, + /* normal RCU actions, for outside use */ + RCUA_FREE, + RCUA_CLOSE, + RCUA_CALL, +}; + +/* since rcu_head is intended to be embedded into structs which may exist + * with lots of copies, rcu_head is shrunk down to its absolute minimum - + * the atomlist pointer + a pointer to this action struct. + */ +struct rcu_action { + enum rcu_action_type type; + + union { + struct { + struct memtype *mt; + ptrdiff_t offset; + } free; + + struct { + void (*fptr)(void *arg); + ptrdiff_t offset; + } call; + } u; +}; + +/* RCU cleanup function queue item */ +PREDECL_ATOMLIST(rcu_heads); +struct rcu_head { + struct rcu_heads_item head; + const struct rcu_action *action; +}; + +/* special RCU head for delayed fd-close */ +struct rcu_head_close { + struct rcu_head rcu_head; + int fd; +}; + +/* enqueue RCU action - use the macros below to get the rcu_action set up */ +extern void rcu_enqueue(struct rcu_head *head, const struct rcu_action *action); + +/* RCU free() and file close() operations. + * + * freed memory / closed fds become _immediately_ unavailable to the calling + * thread, but will remain available for other threads until they have passed + * into RCU-released state. + */ + +/* may be called with NULL mt to do non-MTYPE free() */ +#define rcu_free(mtype, ptr, field) \ + do { \ + typeof(ptr) _ptr = (ptr); \ + if (!_ptr) \ + break; \ + struct rcu_head *_rcu_head = &_ptr->field; \ + static const struct rcu_action _rcu_action = { \ + .type = RCUA_FREE, \ + .u.free = { \ + .mt = mtype, \ + .offset = offsetof(typeof(*_ptr), field), \ + }, \ + }; \ + rcu_enqueue(_rcu_head, &_rcu_action); \ + } while (0) + +/* use this sparingly, it runs on (and blocks) the RCU thread */ +#define rcu_call(func, ptr, field) \ + do { \ + typeof(ptr) _ptr = (ptr); \ + void (*fptype)(typeof(ptr)); \ + struct rcu_head *_rcu_head = &_ptr->field; \ + static const struct rcu_action _rcu_action = { \ + .type = RCUA_CALL, \ + .u.call = { \ + .fptr = (void *)func, \ + .offset = offsetof(typeof(*_ptr), field), \ + }, \ + }; \ + (void)(_fptype = func); \ + rcu_enqueue(_rcu_head, &_rcu_action); \ + } while (0) + +extern void rcu_close(struct rcu_head_close *head, int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRRCU_H */ diff --git a/lib/frrdistance.h b/lib/frrdistance.h new file mode 100644 index 0000000..d2fa76e --- /dev/null +++ b/lib/frrdistance.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra distance header + * Copyright (C) 2023 NVIDIA Corporation + * Donald Sharp + * + * Distance related defines. FRR needs a common set + * of values for distance. + */ +#ifndef __FRRDISTANCE_H__ +#define __FRRDISTANCE_H__ + +/* Default Administrative Distance of each protocol. */ +#define ZEBRA_KERNEL_DISTANCE_DEFAULT 0 +#define ZEBRA_CONNECT_DISTANCE_DEFAULT 0 +#define ZEBRA_STATIC_DISTANCE_DEFAULT 1 +#define ZEBRA_RIP_DISTANCE_DEFAULT 120 +#define ZEBRA_RIPNG_DISTANCE_DEFAULT 120 +#define ZEBRA_OSPF_DISTANCE_DEFAULT 110 +#define ZEBRA_OSPF6_DISTANCE_DEFAULT 110 +#define ZEBRA_ISIS_DISTANCE_DEFAULT 115 +#define ZEBRA_IBGP_DISTANCE_DEFAULT 200 +#define ZEBRA_EBGP_DISTANCE_DEFAULT 20 +#define ZEBRA_TABLE_DISTANCE_DEFAULT 15 +#define ZEBRA_TABLEDIRECT_DISTANCE_DEFAULT 14 +#define ZEBRA_EIGRP_DISTANCE_DEFAULT 90 +#define ZEBRA_NHRP_DISTANCE_DEFAULT 10 +#define ZEBRA_LDP_DISTANCE_DEFAULT 150 +#define ZEBRA_BABEL_DISTANCE_DEFAULT 100 +#define ZEBRA_SHARP_DISTANCE_DEFAULT 150 +#define ZEBRA_PBR_DISTANCE_DEFAULT 200 +#define ZEBRA_OPENFABRIC_DISTANCE_DEFAULT 115 +#define ZEBRA_MAX_DISTANCE_DEFAULT 255 + +#endif diff --git a/lib/frrevent.h b/lib/frrevent.h new file mode 100644 index 0000000..94640a7 --- /dev/null +++ b/lib/frrevent.h @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Event management routine header. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_THREAD_H +#define _ZEBRA_THREAD_H + +#include +#include +#include +#include +#include "monotime.h" +#include "frratomic.h" +#include "typesafe.h" +#include "xref.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CONSUMED_TIME_CHECK 5000000 + +extern bool cputime_enabled; +extern unsigned long cputime_threshold; +/* capturing wallclock time is always enabled since it is fast (reading + * hardware TSC w/o syscalls) + */ +extern unsigned long walltime_threshold; + +struct rusage_t { +#ifdef HAVE_CLOCK_THREAD_CPUTIME_ID + struct timespec cpu; +#else + struct rusage cpu; +#endif + struct timeval real; +}; +#define RUSAGE_T struct rusage_t + +#define GETRUSAGE(X) event_getrusage(X) + +PREDECL_LIST(event_list); +PREDECL_HEAP(event_timer_list); + +struct fd_handler { + /* number of pfd that fit in the allocated space of pfds. This is a + * constant and is the same for both pfds and copy. + */ + nfds_t pfdsize; + + /* file descriptors to monitor for i/o */ + struct pollfd *pfds; + /* number of pollfds stored in pfds */ + nfds_t pfdcount; + + /* chunk used for temp copy of pollfds */ + struct pollfd *copy; + /* number of pollfds stored in copy */ + nfds_t copycount; +}; + +struct xref_eventsched { + struct xref xref; + + const char *funcname; + const char *dest; + uint32_t event_type; +}; + +PREDECL_HASH(cpu_records); + +/* Master of the theads. */ +struct event_loop { + char *name; + + struct event **read; + struct event **write; + struct event_timer_list_head timer; + struct event_list_head event, ready, unuse; + struct list *cancel_req; + bool canceled; + pthread_cond_t cancel_cond; + struct cpu_records_head cpu_records[1]; + int io_pipe[2]; + int fd_limit; + struct fd_handler handler; + unsigned long alloc; + long selectpoll_timeout; + bool spin; + bool handle_signals; + pthread_mutex_t mtx; + pthread_t owner; + + nfds_t last_read; + + bool ready_run_loop; + RUSAGE_T last_getrusage; +}; + +/* Event types. */ +enum event_types { + EVENT_READ, + EVENT_WRITE, + EVENT_TIMER, + EVENT_EVENT, + EVENT_READY, + EVENT_UNUSED, + EVENT_EXECUTE, +}; + +/* Event itself. */ +struct event { + enum event_types type; /* event type */ + enum event_types add_type; /* event type */ + struct event_list_item eventitem; + struct event_timer_list_item timeritem; + struct event **ref; /* external reference (if given) */ + struct event_loop *master; /* pointer to the struct event_loop */ + void (*func)(struct event *e); /* event function */ + void *arg; /* event argument */ + union { + int val; /* second argument of the event. */ + int fd; /* file descriptor in case of r/w */ + struct timeval sands; /* rest of time sands value. */ + } u; + struct timeval real; + struct cpu_event_history *hist; /* cache pointer to cpu_history */ + unsigned long yield; /* yield time in microseconds */ + const struct xref_eventsched *xref; /* origin location */ + pthread_mutex_t mtx; /* mutex for thread.c functions */ + bool ignore_timer_late; +}; + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pTH"(struct event *) +#endif + +struct cpu_event_history { + struct cpu_records_item item; + + void (*func)(struct event *e); + atomic_size_t total_cpu_warn; + atomic_size_t total_wall_warn; + atomic_size_t total_starv_warn; + atomic_size_t total_calls; + atomic_size_t total_active; + struct time_stats { + atomic_size_t total, max; + } real; + struct time_stats cpu; + atomic_uint_fast32_t types; + const char *funcname; +}; + +/* Struct timeval's tv_usec one second value. */ +#define TIMER_SECOND_MICRO 1000000L + +static inline unsigned long timeval_elapsed(struct timeval a, struct timeval b) +{ + return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO) + + (a.tv_usec - b.tv_usec)); +} + +/* Event yield time. */ +#define EVENT_YIELD_TIME_SLOT 10 * 1000L /* 10ms */ + +#define EVENT_TIMER_STRLEN 12 + +/* Macros. */ +#define EVENT_ARG(X) ((X)->arg) +#define EVENT_FD(X) ((X)->u.fd) +#define EVENT_VAL(X) ((X)->u.val) + +/* + * Please consider this macro deprecated, and do not use it in new code. + */ +#define EVENT_OFF(thread) \ + do { \ + if ((thread)) \ + event_cancel(&(thread)); \ + } while (0) + +/* + * Macro wrappers to generate xrefs for all thread add calls. Includes + * file/line/function info for debugging/tracing. + */ +#include "lib/xref.h" + +#define _xref_t_a(addfn, type, m, f, a, v, t) \ + ({ \ + static const struct xref_eventsched _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_EVENTSCHED, NULL, __func__), \ + .funcname = #f, \ + .dest = #t, \ + .event_type = EVENT_##type, \ + }; \ + XREF_LINK(_xref.xref); \ + _event_add_##addfn(&_xref, m, f, a, v, t); \ + }) /* end */ + +#define event_add_read(m, f, a, v, t) _xref_t_a(read_write, READ, m, f, a, v, t) +#define event_add_write(m, f, a, v, t) \ + _xref_t_a(read_write, WRITE, m, f, a, v, t) +#define event_add_timer(m, f, a, v, t) _xref_t_a(timer, TIMER, m, f, a, v, t) +#define event_add_timer_msec(m, f, a, v, t) \ + _xref_t_a(timer_msec, TIMER, m, f, a, v, t) +#define event_add_timer_tv(m, f, a, v, t) \ + _xref_t_a(timer_tv, TIMER, m, f, a, v, t) +#define event_add_event(m, f, a, v, t) _xref_t_a(event, EVENT, m, f, a, v, t) + +#define event_execute(m, f, a, v, p) \ + ({ \ + static const struct xref_eventsched _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_EVENTSCHED, NULL, __func__), \ + .funcname = #f, \ + .dest = NULL, \ + .event_type = EVENT_EXECUTE, \ + }; \ + XREF_LINK(_xref.xref); \ + _event_execute(&_xref, m, f, a, v, p); \ + }) /* end */ + +/* Prototypes. */ +extern struct event_loop *event_master_create(const char *name); +void event_master_set_name(struct event_loop *master, const char *name); +extern void event_master_free(struct event_loop *m); +extern void event_master_free_unused(struct event_loop *m); + +extern void _event_add_read_write(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, int fd, + struct event **tref); + +extern void _event_add_timer(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, long t, + struct event **tref); + +extern void _event_add_timer_msec(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, long t, + struct event **tref); + +extern void _event_add_timer_tv(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, + struct timeval *tv, struct event **tref); + +extern void _event_add_event(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, int val, + struct event **tref); + +extern void _event_execute(const struct xref_eventsched *xref, + struct event_loop *master, + void (*fn)(struct event *), void *arg, int val, + struct event **eref); + +extern void event_cancel(struct event **event); +extern void event_cancel_async(struct event_loop *m, struct event **eptr, + void *data); +/* Cancel ready tasks with an arg matching 'arg' */ +extern void event_cancel_event_ready(struct event_loop *m, void *arg); +/* Cancel all tasks with an arg matching 'arg', including timers and io */ +extern void event_cancel_event(struct event_loop *m, void *arg); +extern struct event *event_fetch(struct event_loop *m, struct event *event); +extern void event_call(struct event *event); +extern unsigned long event_timer_remain_second(struct event *event); +extern struct timeval event_timer_remain(struct event *event); +extern unsigned long event_timer_remain_msec(struct event *event); +extern int event_should_yield(struct event *event); +/* set yield time for thread */ +extern void event_set_yield_time(struct event *event, unsigned long ytime); + +/* Internal libfrr exports */ +extern void event_getrusage(RUSAGE_T *r); +extern void event_cmd_init(void); + +/* Returns elapsed real (wall clock) time. */ +extern unsigned long event_consumed_time(RUSAGE_T *after, RUSAGE_T *before, + unsigned long *cpu_time_elapsed); + +/* only for use in logging functions! */ +extern pthread_key_t thread_current; +extern char *event_timer_to_hhmmss(char *buf, int buf_size, + struct event *t_timer); + +static inline bool event_is_scheduled(struct event *thread) +{ + if (thread) + return true; + + return false; +} + +/* Debug signal mask */ +void debug_signals(const sigset_t *sigs); + +static inline void event_ignore_late_timer(struct event *event) +{ + event->ignore_timer_late = true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_THREAD_H */ diff --git a/lib/frrlua.c b/lib/frrlua.c new file mode 100644 index 0000000..2cab1a5 --- /dev/null +++ b/lib/frrlua.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file defines the lua interface into + * FRRouting. + * + * Copyright (C) 2016-2019 Cumulus Networks, Inc. + * Donald Sharp, Quentin Young + */ + +#include + +#ifdef HAVE_SCRIPTING + +#include "prefix.h" +#include "frrlua.h" +#include "log.h" +#include "buffer.h" + +DEFINE_MTYPE(LIB, SCRIPT_RES, "Scripting results"); + +/* Lua stuff */ + +/* + * FRR convenience functions. + * + * This section has convenience functions used to make interacting with the Lua + * stack easier. + */ + +int frrlua_table_get_integer(lua_State *L, const char *key) +{ + int result; + + lua_pushstring(L, key); + lua_gettable(L, -2); + + result = lua_tointeger(L, -1); + lua_pop(L, 1); + + return result; +} + +/* + * This section has functions that convert internal FRR datatypes into Lua + * datatypes: one encoder function and two decoder functions for each type. + * + */ + +void lua_pushprefix(lua_State *L, const struct prefix *prefix) +{ + char buffer[PREFIX_STRLEN]; + + lua_newtable(L); + lua_pushstring(L, prefix2str(prefix, buffer, PREFIX_STRLEN)); + lua_setfield(L, -2, "network"); + lua_pushinteger(L, prefix->prefixlen); + lua_setfield(L, -2, "length"); + lua_pushinteger(L, prefix->family); + lua_setfield(L, -2, "family"); +} + +void lua_decode_prefix(lua_State *L, int idx, struct prefix *prefix) +{ + lua_getfield(L, idx, "network"); + (void)str2prefix(lua_tostring(L, -1), prefix); + lua_pop(L, 1); + /* pop the table */ + lua_pop(L, 1); +} + +void *lua_toprefix(lua_State *L, int idx) +{ + struct prefix *p = XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct prefix)); + lua_decode_prefix(L, idx, p); + return p; +} + +void lua_pushinterface(lua_State *L, const struct interface *ifp) +{ + lua_newtable(L); + lua_pushstring(L, ifp->name); + lua_setfield(L, -2, "name"); + lua_pushinteger(L, ifp->ifindex); + lua_setfield(L, -2, "ifindex"); + lua_pushinteger(L, ifp->status); + lua_setfield(L, -2, "status"); + lua_pushinteger(L, ifp->flags); + lua_setfield(L, -2, "flags"); + lua_pushinteger(L, ifp->metric); + lua_setfield(L, -2, "metric"); + lua_pushinteger(L, ifp->speed); + lua_setfield(L, -2, "speed"); + lua_pushinteger(L, ifp->mtu); + lua_setfield(L, -2, "mtu"); + lua_pushinteger(L, ifp->mtu6); + lua_setfield(L, -2, "mtu6"); + lua_pushinteger(L, ifp->bandwidth); + lua_setfield(L, -2, "bandwidth"); + lua_pushinteger(L, ifp->link_ifindex); + lua_setfield(L, -2, "link_ifindex"); + lua_pushinteger(L, ifp->ll_type); + lua_setfield(L, -2, "linklayer_type"); +} + +void lua_decode_interface(lua_State *L, int idx, struct interface *ifp) +{ + lua_getfield(L, idx, "name"); + strlcpy(ifp->name, lua_tostring(L, -1), sizeof(ifp->name)); + lua_pop(L, 1); + lua_getfield(L, idx, "ifindex"); + ifp->ifindex = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "status"); + ifp->status = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "flags"); + ifp->flags = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "metric"); + ifp->metric = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "speed"); + ifp->speed = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "mtu"); + ifp->mtu = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "mtu6"); + ifp->mtu6 = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "bandwidth"); + ifp->bandwidth = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "link_ifindex"); + ifp->link_ifindex = lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "linklayer_type"); + ifp->ll_type = lua_tointeger(L, -1); + lua_pop(L, 1); + /* pop the table */ + lua_pop(L, 1); +} +void *lua_tointerface(lua_State *L, int idx) +{ + struct interface *ifp = + XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct interface)); + + lua_decode_interface(L, idx, ifp); + return ifp; +} + +void lua_pushinaddr(lua_State *L, const struct in_addr *addr) +{ + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, addr, buf, sizeof(buf)); + + lua_newtable(L); + lua_pushinteger(L, addr->s_addr); + lua_setfield(L, -2, "value"); + lua_pushstring(L, buf); + lua_setfield(L, -2, "string"); +} + +void lua_decode_inaddr(lua_State *L, int idx, struct in_addr *inaddr) +{ + lua_getfield(L, idx, "value"); + inaddr->s_addr = lua_tointeger(L, -1); + lua_pop(L, 1); + /* pop the table */ + lua_pop(L, 1); +} + +void *lua_toinaddr(lua_State *L, int idx) +{ + struct in_addr *inaddr = + XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct in_addr)); + lua_decode_inaddr(L, idx, inaddr); + return inaddr; +} + + +void lua_pushin6addr(lua_State *L, const struct in6_addr *addr) +{ + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, addr, buf, sizeof(buf)); + + lua_newtable(L); + lua_pushlstring(L, (const char *)addr->s6_addr, 16); + lua_setfield(L, -2, "value"); + lua_pushstring(L, buf); + lua_setfield(L, -2, "string"); +} + +void lua_decode_in6addr(lua_State *L, int idx, struct in6_addr *in6addr) +{ + lua_getfield(L, idx, "string"); + inet_pton(AF_INET6, lua_tostring(L, -1), in6addr); + lua_pop(L, 1); + /* pop the table */ + lua_pop(L, 1); +} + +void *lua_toin6addr(lua_State *L, int idx) +{ + struct in6_addr *in6addr = + XCALLOC(MTYPE_SCRIPT_RES, sizeof(struct in6_addr)); + lua_decode_in6addr(L, idx, in6addr); + return in6addr; +} + +void lua_pushipaddr(lua_State *L, const struct ipaddr *addr) +{ + if (IS_IPADDR_V4(addr)) + lua_pushinaddr(L, &addr->ipaddr_v4); + else + lua_pushin6addr(L, &addr->ipaddr_v6); +} + +void lua_pushethaddr(lua_State *L, const struct ethaddr *addr) +{ + lua_newtable(L); + lua_pushinteger(L, *(addr->octet)); + lua_setfield(L, -2, "octet"); +} + +void lua_pushsockunion(lua_State *L, const union sockunion *su) +{ + char buf[SU_ADDRSTRLEN]; + + sockunion2str(su, buf, sizeof(buf)); + + lua_newtable(L); + lua_pushlstring(L, (const char *)sockunion_get_addr(su), + sockunion_get_addrlen(su)); + lua_setfield(L, -2, "value"); + lua_pushstring(L, buf); + lua_setfield(L, -2, "string"); +} + +void lua_decode_sockunion(lua_State *L, int idx, union sockunion *su) +{ + lua_getfield(L, idx, "string"); + if (str2sockunion(lua_tostring(L, -1), su) < 0) + zlog_err("Lua hook call: Failed to decode sockunion"); + + lua_pop(L, 1); + /* pop the table */ + lua_pop(L, 1); +} + +void *lua_tosockunion(lua_State *L, int idx) +{ + union sockunion *su = + XCALLOC(MTYPE_SCRIPT_RES, sizeof(union sockunion)); + + lua_decode_sockunion(L, idx, su); + return su; +} + +void lua_pushintegerp(lua_State *L, const int *num) +{ + lua_pushinteger(L, *num); +} + +void lua_decode_integerp(lua_State *L, int idx, int *num) +{ + int isnum; + *num = lua_tonumberx(L, idx, &isnum); + lua_pop(L, 1); + assert(isnum); +} + +void *lua_tointegerp(lua_State *L, int idx) +{ + int *num = XCALLOC(MTYPE_SCRIPT_RES, sizeof(int)); + + lua_decode_integerp(L, idx, num); + return num; +} + +void lua_pushnexthop(lua_State *L, const struct nexthop *nexthop) +{ + lua_newtable(L); + lua_pushinteger(L, nexthop->vrf_id); + lua_setfield(L, -2, "vrf_id"); + lua_pushinteger(L, nexthop->ifindex); + lua_setfield(L, -2, "ifindex"); + lua_pushinteger(L, nexthop->type); + lua_setfield(L, -2, "type"); + lua_pushinteger(L, nexthop->flags); + lua_setfield(L, -2, "flags"); + if (nexthop->type == NEXTHOP_TYPE_BLACKHOLE) { + lua_pushinteger(L, nexthop->bh_type); + lua_setfield(L, -2, "bh_type"); + } else if (nexthop->type == NEXTHOP_TYPE_IPV4) { + lua_pushinaddr(L, &nexthop->gate.ipv4); + lua_setfield(L, -2, "gate"); + } else if (nexthop->type == NEXTHOP_TYPE_IPV6) { + lua_pushin6addr(L, &nexthop->gate.ipv6); + lua_setfield(L, -2, "gate"); + } + lua_pushinteger(L, nexthop->nh_label_type); + lua_setfield(L, -2, "nh_label_type"); + lua_pushinteger(L, nexthop->weight); + lua_setfield(L, -2, "weight"); + lua_pushinteger(L, nexthop->backup_num); + lua_setfield(L, -2, "backup_num"); + lua_pushinteger(L, *(nexthop->backup_idx)); + lua_setfield(L, -2, "backup_idx"); + if (nexthop->nh_encap_type == NET_VXLAN) { + lua_pushinteger(L, nexthop->nh_encap.vni); + lua_setfield(L, -2, "vni"); + } + lua_pushinteger(L, nexthop->nh_encap_type); + lua_setfield(L, -2, "nh_encap_type"); + lua_pushinteger(L, nexthop->srte_color); + lua_setfield(L, -2, "srte_color"); +} + +void lua_pushnexthop_group(lua_State *L, const struct nexthop_group *ng) +{ + lua_newtable(L); + struct nexthop *nexthop; + int i = 0; + + for (ALL_NEXTHOPS_PTR(ng, nexthop)) { + lua_pushnexthop(L, nexthop); + lua_seti(L, -2, i); + i++; + } +} + +void lua_pushlonglongp(lua_State *L, const long long *num) +{ + /* lua library function; this can take a long long */ + lua_pushinteger(L, *num); +} + +void lua_decode_longlongp(lua_State *L, int idx, long long *num) +{ + int isnum; + *num = lua_tonumberx(L, idx, &isnum); + lua_pop(L, 1); + assert(isnum); +} + +void *lua_tolonglongp(lua_State *L, int idx) +{ + long long *num = XCALLOC(MTYPE_SCRIPT_RES, sizeof(long long)); + + lua_decode_longlongp(L, idx, num); + return num; +} + +void lua_decode_stringp(lua_State *L, int idx, char *str) +{ + strlcpy(str, lua_tostring(L, idx), strlen(str) + 1); + lua_pop(L, 1); +} + +void *lua_tostringp(lua_State *L, int idx) +{ + char *string = XSTRDUP(MTYPE_SCRIPT_RES, lua_tostring(L, idx)); + + return string; +} + +/* + * Logging. + * + * Lua-compatible wrappers for FRR logging functions. + */ +static const char *frrlua_log_thunk(lua_State *L) +{ + int nargs; + + nargs = lua_gettop(L); + assert(nargs == 1); + + return lua_tostring(L, 1); +} + +static int frrlua_log_debug(lua_State *L) +{ + zlog_debug("%s", frrlua_log_thunk(L)); + return 0; +} + +static int frrlua_log_info(lua_State *L) +{ + zlog_info("%s", frrlua_log_thunk(L)); + return 0; +} + +static int frrlua_log_notice(lua_State *L) +{ + zlog_notice("%s", frrlua_log_thunk(L)); + return 0; +} + +static int frrlua_log_warn(lua_State *L) +{ + zlog_warn("%s", frrlua_log_thunk(L)); + return 0; +} + +static int frrlua_log_error(lua_State *L) +{ + zlog_err("%s", frrlua_log_thunk(L)); + return 0; +} + +static const luaL_Reg log_funcs[] = { + {"debug", frrlua_log_debug}, + {"info", frrlua_log_info}, + {"notice", frrlua_log_notice}, + {"warn", frrlua_log_warn}, + {"error", frrlua_log_error}, + {}, +}; + +void frrlua_export_logging(lua_State *L) +{ + lua_newtable(L); + luaL_setfuncs(L, log_funcs, 0); + lua_setglobal(L, "log"); +} + +/* + * Debugging. + */ + +char *frrlua_stackdump(lua_State *L) +{ + int top = lua_gettop(L); + + char tmpbuf[64]; + struct buffer *buf = buffer_new(4098); + + for (int i = 1; i <= top; i++) { + int t = lua_type(L, i); + + switch (t) { + case LUA_TSTRING: /* strings */ + snprintf(tmpbuf, sizeof(tmpbuf), "\"%s\"\n", + lua_tostring(L, i)); + buffer_putstr(buf, tmpbuf); + break; + case LUA_TBOOLEAN: /* booleans */ + snprintf(tmpbuf, sizeof(tmpbuf), "%s\n", + lua_toboolean(L, i) ? "true" : "false"); + buffer_putstr(buf, tmpbuf); + break; + case LUA_TNUMBER: /* numbers */ + snprintf(tmpbuf, sizeof(tmpbuf), "%g\n", + lua_tonumber(L, i)); + buffer_putstr(buf, tmpbuf); + break; + default: /* other values */ + snprintf(tmpbuf, sizeof(tmpbuf), "%s\n", + lua_typename(L, t)); + buffer_putstr(buf, tmpbuf); + break; + } + } + + char *result = XSTRDUP(MTYPE_TMP, buffer_getstr(buf)); + + buffer_free(buf); + + return result; +} + +#endif /* HAVE_SCRIPTING */ diff --git a/lib/frrlua.h b/lib/frrlua.h new file mode 100644 index 0000000..dc0f4d9 --- /dev/null +++ b/lib/frrlua.h @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2016-2019 Cumulus Networks, Inc. + * Donald Sharp, Quentin Young + */ +#ifndef __FRRLUA_H__ +#define __FRRLUA_H__ + +#include + +#ifdef HAVE_SCRIPTING + +#include +#include +#include + +#include "prefix.h" +#include "frrscript.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(SCRIPT_RES); + +/* + * gcc-10 is complaining about the wrapper function + * not being compatible with lua_pushstring returning + * a char *. Let's wrapper it here to make our life + * easier + */ +static inline void lua_pushstring_wrapper(lua_State *L, const char *str) +{ + (void)lua_pushstring(L, str); +} + +/* + * Converts a prefix to a Lua value and pushes it on the stack. + */ +void lua_pushprefix(lua_State *L, const struct prefix *prefix); + +void lua_decode_prefix(lua_State *L, int idx, struct prefix *prefix); + +/* + * Converts the Lua value at idx to a prefix. + * + * Returns: + * struct prefix allocated with MTYPE_TMP + */ +void *lua_toprefix(lua_State *L, int idx); + +/* + * Converts an interface to a Lua value and pushes it on the stack. + */ +void lua_pushinterface(lua_State *L, const struct interface *ifp); + +void lua_decode_interface(lua_State *L, int idx, struct interface *ifp); + +/* + * Converts the Lua value at idx to an interface. + * + * Returns: + * struct interface allocated with MTYPE_TMP. This interface is not hooked + * to anything, nor is it inserted in the global interface tree. + */ +void *lua_tointerface(lua_State *L, int idx); + +/* + * Converts an in_addr to a Lua value and pushes it on the stack. + */ +void lua_pushinaddr(lua_State *L, const struct in_addr *addr); + +void lua_decode_inaddr(lua_State *L, int idx, struct in_addr *addr); + +/* + * Converts the Lua value at idx to an in_addr. + * + * Returns: + * struct in_addr allocated with MTYPE_TMP. + */ +void *lua_toinaddr(lua_State *L, int idx); + +/* + * Converts an in6_addr to a Lua value and pushes it on the stack. + */ +void lua_pushin6addr(lua_State *L, const struct in6_addr *addr); + +void lua_decode_in6addr(lua_State *L, int idx, struct in6_addr *addr); + +void lua_pushipaddr(lua_State *L, const struct ipaddr *addr); + +void lua_pushethaddr(lua_State *L, const struct ethaddr *addr); + +/* + * Converts the Lua value at idx to an in6_addr. + * + * Returns: + * struct in6_addr allocated with MTYPE_TMP. + */ +void *lua_toin6addr(lua_State *L, int idx); + +/* + * Converts a sockunion to a Lua value and pushes it on the stack. + */ +void lua_pushsockunion(lua_State *L, const union sockunion *su); + +void lua_decode_sockunion(lua_State *L, int idx, union sockunion *su); + +/* + * Converts the Lua value at idx to a sockunion. + * + * Returns: + * sockunion allocated with MTYPE_TMP. + */ +void *lua_tosockunion(lua_State *L, int idx); + +void lua_pushnexthop_group(lua_State *L, const struct nexthop_group *ng); + +void lua_pushnexthop(lua_State *L, const struct nexthop *nexthop); + +/* + * Converts an int to a Lua value and pushes it on the stack. + */ +void lua_pushintegerp(lua_State *L, const int *num); + +void lua_decode_integerp(lua_State *L, int idx, int *num); + +/* + * Converts the Lua value at idx to an int. + * + * Returns: + * int allocated with MTYPE_TMP. + */ +void *lua_tointegerp(lua_State *L, int idx); + +/* + * Converts a long long to a Lua value and pushes it on the stack. + */ +void lua_pushlonglongp(lua_State *L, const long long *num); + +void lua_decode_longlongp(lua_State *L, int idx, long long *num); + +/* + * Converts the Lua value at idx to a long long. + * + * Returns: + * long long allocated with MTYPE_TMP. + */ +void *lua_tolonglongp(lua_State *L, int idx); + +void lua_decode_stringp(lua_State *L, int idx, char *str); + +/* + * Pop string. + * + * Sets *string to a copy of the string at the top of the stack. The copy is + * allocated with MTYPE_TMP and the caller is responsible for freeing it. + */ +void *lua_tostringp(lua_State *L, int idx); + +/* + * Retrieve an integer from table on the top of the stack. + * + * key + * Key of string value in table + */ +int frrlua_table_get_integer(lua_State *L, const char *key); + +/* + * Exports a new table containing bindings to FRR zlog functions into the + * global namespace. + * + * From Lua, these functions may be accessed as: + * + * - log.debug() + * - log.info() + * - log.warn() + * - log.error() + * + * They take a single string argument. + */ +void frrlua_export_logging(lua_State *L); + +/* + * Dump Lua stack to a string. + * + * Return value must be freed with XFREE(MTYPE_TMP, ...); + */ +char *frrlua_stackdump(lua_State *L); + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_SCRIPTING */ + +#endif /* __FRRLUA_H__ */ diff --git a/lib/frrscript.c b/lib/frrscript.c new file mode 100644 index 0000000..acdd1df --- /dev/null +++ b/lib/frrscript.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Scripting foo + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ +#include + +#ifdef HAVE_SCRIPTING + +#include +#include + +#include "frrscript.h" +#include "frrlua.h" +#include "memory.h" +#include "hash.h" +#include "log.h" + + +DEFINE_MTYPE_STATIC(LIB, SCRIPT, "Scripting"); + +/* + * Script name hash utilities + */ + +struct frrscript_names_head frrscript_names_hash; + +void _lua_decode_noop(lua_State *L, ...) {} + +/* + * Wrapper for frrscript_names_add + * Use this to register hook calls when a daemon starts up + */ +int frrscript_names_add_function_name(const char *function_name) +{ + struct frrscript_names_entry *insert = + XCALLOC(MTYPE_SCRIPT, sizeof(*insert)); + strlcpy(insert->function_name, function_name, + sizeof(insert->function_name)); + + if (frrscript_names_add(&frrscript_names_hash, insert)) { + zlog_warn( + "Failed to add hook call function name to script_names"); + return 1; + } + return 0; +} + +void frrscript_names_destroy(void) +{ + struct frrscript_names_entry *ne; + + while ((ne = frrscript_names_pop(&frrscript_names_hash))) + XFREE(MTYPE_SCRIPT, ne); +} + +/* + * Given a function_name, set its script_name. function_names and script_names + * are one-to-one. Each set will wipe the previous script_name. + * Return 0 if set was successful, else 1. + * + * script_name is the base name of the file, without .lua. + */ +int frrscript_names_set_script_name(const char *function_name, + const char *script_name) +{ + struct frrscript_names_entry lookup; + + strlcpy(lookup.function_name, function_name, + sizeof(lookup.function_name)); + struct frrscript_names_entry *snhe = + frrscript_names_find(&frrscript_names_hash, &lookup); + if (!snhe) + return 1; + strlcpy(snhe->script_name, script_name, sizeof(snhe->script_name)); + return 0; +} + +/* + * Given a function_name, get its script_name. + * Return NULL if function_name not found. + * + * script_name is the base name of the file, without .lua. + */ +char *frrscript_names_get_script_name(const char *function_name) +{ + struct frrscript_names_entry lookup; + + strlcpy(lookup.function_name, function_name, + sizeof(lookup.function_name)); + struct frrscript_names_entry *snhe = + frrscript_names_find(&frrscript_names_hash, &lookup); + if (!snhe) + return NULL; + + if (snhe->script_name[0] == '\0') + return NULL; + + return snhe->script_name; +} + +uint32_t frrscript_names_hash_key(const struct frrscript_names_entry *snhe) +{ + return string_hash_make(snhe->function_name); +} + +int frrscript_names_hash_cmp(const struct frrscript_names_entry *snhe1, + const struct frrscript_names_entry *snhe2) +{ + return strncmp(snhe1->function_name, snhe2->function_name, + sizeof(snhe1->function_name)); +} + +/* Codecs */ + +struct frrscript_codec frrscript_codecs_lib[] = { + {.typename = "integer", + .encoder = (encoder_func)lua_pushintegerp, + .decoder = lua_tointegerp}, + {.typename = "string", + .encoder = (encoder_func)lua_pushstring_wrapper, + .decoder = lua_tostringp}, + {.typename = "prefix", + .encoder = (encoder_func)lua_pushprefix, + .decoder = lua_toprefix}, + {.typename = "interface", + .encoder = (encoder_func)lua_pushinterface, + .decoder = lua_tointerface}, + {.typename = "in_addr", + .encoder = (encoder_func)lua_pushinaddr, + .decoder = lua_toinaddr}, + {.typename = "in6_addr", + .encoder = (encoder_func)lua_pushin6addr, + .decoder = lua_toin6addr}, + {.typename = "sockunion", + .encoder = (encoder_func)lua_pushsockunion, + .decoder = lua_tosockunion}, + {}}; + +/* Type codecs */ + +struct hash *codec_hash; +char scriptdir[MAXPATHLEN]; + +static unsigned int codec_hash_key(const void *data) +{ + const struct frrscript_codec *c = data; + + return string_hash_make(c->typename); +} + +static bool codec_hash_cmp(const void *d1, const void *d2) +{ + const struct frrscript_codec *e1 = d1; + const struct frrscript_codec *e2 = d2; + + return strmatch(e1->typename, e2->typename); +} + +static void *codec_alloc(void *arg) +{ + struct frrscript_codec *tmp = arg; + + struct frrscript_codec *e = + XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript_codec)); + e->typename = XSTRDUP(MTYPE_SCRIPT, tmp->typename); + e->encoder = tmp->encoder; + e->decoder = tmp->decoder; + + return e; +} + +static void codec_free(void *data) +{ + struct frrscript_codec *c = data; + char *constworkaroundandihateit = (char *)c->typename; + + XFREE(MTYPE_SCRIPT, constworkaroundandihateit); + XFREE(MTYPE_SCRIPT, c); +} + +/* Lua function hash utils */ + +unsigned int lua_function_hash_key(const void *data) +{ + const struct lua_function_state *lfs = data; + + return string_hash_make(lfs->name); +} + +bool lua_function_hash_cmp(const void *d1, const void *d2) +{ + const struct lua_function_state *lfs1 = d1; + const struct lua_function_state *lfs2 = d2; + + return strmatch(lfs1->name, lfs2->name); +} + +void *lua_function_alloc(void *arg) +{ + struct lua_function_state *tmp = arg; + struct lua_function_state *lfs = + XCALLOC(MTYPE_SCRIPT, sizeof(struct lua_function_state)); + + lfs->name = tmp->name; + lfs->L = tmp->L; + return lfs; +} + +static void lua_function_free(void *data) +{ + struct lua_function_state *lfs = data; + + lua_close(lfs->L); + XFREE(MTYPE_SCRIPT, lfs); +} + +/* internal frrscript APIs */ + +int _frrscript_call_lua(struct lua_function_state *lfs, int nargs) +{ + + int ret; + ret = lua_pcall(lfs->L, nargs, 1, 0); + + switch (ret) { + case LUA_OK: + break; + case LUA_ERRRUN: + zlog_err("Lua hook call '%s' : runtime error: %s", lfs->name, + lua_tostring(lfs->L, -1)); + break; + case LUA_ERRMEM: + zlog_err("Lua hook call '%s' : memory error: %s", lfs->name, + lua_tostring(lfs->L, -1)); + break; + case LUA_ERRERR: + zlog_err("Lua hook call '%s' : error handler error: %s", + lfs->name, lua_tostring(lfs->L, -1)); + break; + case LUA_ERRGCMM: + zlog_err("Lua hook call '%s' : garbage collector error: %s", + lfs->name, lua_tostring(lfs->L, -1)); + break; + default: + zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name, + lua_tostring(lfs->L, -1)); + break; + } + + if (ret != LUA_OK) { + lua_pop(lfs->L, 1); + goto done; + } + + if (lua_gettop(lfs->L) != 1) { + zlog_err( + "Lua hook call '%s': Lua function should return only 1 result", + lfs->name); + ret = 1; + goto done; + } + + if (lua_istable(lfs->L, 1) != 1) { + zlog_err( + "Lua hook call '%s': Lua function should return a Lua table", + lfs->name); + ret = 1; + } + +done: + /* LUA_OK is 0, so we can just return lua_pcall's result directly */ + return ret; +} + +void *frrscript_get_result(struct frrscript *fs, const char *function_name, + const char *name, + void *(*lua_to)(lua_State *L, int idx)) +{ + void *p; + struct lua_function_state *lfs; + struct lua_function_state lookup = {.name = function_name}; + + lfs = hash_lookup(fs->lua_function_hash, &lookup); + + if (lfs == NULL) + return NULL; + + /* At this point, the Lua state should have only the returned table. + * We will then search the table for the key/value we're interested in. + * Then if the value is present (i.e. non-nil), call the lua_to* + * decoder. + */ + assert(lua_gettop(lfs->L) == 1); + assert(lua_istable(lfs->L, -1) == 1); + lua_getfield(lfs->L, -1, name); + if (lua_isnil(lfs->L, -1)) { + lua_pop(lfs->L, 1); + zlog_warn( + "frrscript: '%s.lua': '%s': tried to decode '%s' as result but failed", + fs->name, function_name, name); + return NULL; + } + p = lua_to(lfs->L, 2); + + /* At the end, the Lua state should be same as it was at the start + * i.e. containing solely the returned table. + */ + assert(lua_gettop(lfs->L) == 1); + assert(lua_istable(lfs->L, -1) == 1); + + return p; +} + +void frrscript_register_type_codec(struct frrscript_codec *codec) +{ + struct frrscript_codec c = *codec; + + if (hash_lookup(codec_hash, &c)) { + zlog_backtrace(LOG_ERR); + assert(!"Type codec double-registered."); + } + + (void)hash_get(codec_hash, &c, codec_alloc); +} + +void frrscript_register_type_codecs(struct frrscript_codec *codecs) +{ + for (int i = 0; codecs[i].typename != NULL; i++) + frrscript_register_type_codec(&codecs[i]); +} + +struct frrscript *frrscript_new(const char *name) +{ + struct frrscript *fs = XCALLOC(MTYPE_SCRIPT, sizeof(struct frrscript)); + + fs->name = XSTRDUP(MTYPE_SCRIPT, name); + fs->lua_function_hash = + hash_create(lua_function_hash_key, lua_function_hash_cmp, + "Lua function state hash"); + return fs; +} + +int frrscript_load(struct frrscript *fs, const char *function_name, + int (*load_cb)(struct frrscript *)) +{ + + /* Set up the Lua script */ + lua_State *L = luaL_newstate(); + + frrlua_export_logging(L); + + char script_name[MAXPATHLEN]; + + if (snprintf(script_name, sizeof(script_name), "%s/%s.lua", scriptdir, + fs->name) + >= (int)sizeof(script_name)) { + zlog_err("frrscript: path to script %s/%s.lua is too long", + scriptdir, fs->name); + goto fail; + } + + if (luaL_dofile(L, script_name) != 0) { + zlog_err("frrscript: failed loading script '%s': error: %s", + script_name, lua_tostring(L, -1)); + goto fail; + } + + /* To check the Lua function, we get it from the global table */ + lua_getglobal(L, function_name); + if (lua_isfunction(L, lua_gettop(L)) == 0) { + zlog_err("frrscript: loaded script '%s' but %s not found", + script_name, function_name); + goto fail; + } + /* Then pop the function (frrscript_call will push it when it needs it) + */ + lua_pop(L, 1); + + if (load_cb && (*load_cb)(fs) != 0) { + zlog_err( + "frrscript: '%s': %s: loaded but callback returned non-zero exit code", + script_name, function_name); + goto fail; + } + + /* Add the Lua function state to frrscript */ + struct lua_function_state key = {.name = function_name, .L = L}; + + (void)hash_get(fs->lua_function_hash, &key, lua_function_alloc); + + return 0; +fail: + lua_close(L); + return 1; +} + +void frrscript_delete(struct frrscript *fs) +{ + hash_clean_and_free(&fs->lua_function_hash, lua_function_free); + XFREE(MTYPE_SCRIPT, fs->name); + XFREE(MTYPE_SCRIPT, fs); +} + +void frrscript_init(const char *sd) +{ + codec_hash = hash_create(codec_hash_key, codec_hash_cmp, + "Lua type encoders"); + + strlcpy(scriptdir, sd, sizeof(scriptdir)); + + /* Register core library types */ + frrscript_register_type_codecs(frrscript_codecs_lib); +} + +void frrscript_fini(void) +{ + hash_clean_and_free(&codec_hash, codec_free); + + frrscript_names_destroy(); +} +#endif /* HAVE_SCRIPTING */ diff --git a/lib/frrscript.h b/lib/frrscript.h new file mode 100644 index 0000000..ce313a1 --- /dev/null +++ b/lib/frrscript.h @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Scripting foo + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ +#ifndef __FRRSCRIPT_H__ +#define __FRRSCRIPT_H__ + +#include + +#ifdef HAVE_SCRIPTING + +#include +#include +#include +#include "frrlua.h" +#include "bgpd/bgp_script.h" // for peer and attr encoders/decoders + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declarations */ +struct zebra_dplane_ctx; +extern void lua_pushzebra_dplane_ctx(lua_State *L, + const struct zebra_dplane_ctx *ctx); +extern void lua_decode_zebra_dplane_ctx(lua_State *L, int idx, + struct zebra_dplane_ctx *ctx); + +/* + * Script name hash + */ +PREDECL_HASH(frrscript_names); + +struct frrscript_names_entry { + /* Name of a Lua hook call */ + char function_name[MAXPATHLEN]; + + /* Lua script in which to look for it */ + char script_name[MAXPATHLEN]; + + struct frrscript_names_item item; +}; + +extern struct frrscript_names_head frrscript_names_hash; + +int frrscript_names_hash_cmp(const struct frrscript_names_entry *snhe1, + const struct frrscript_names_entry *snhe2); +uint32_t frrscript_names_hash_key(const struct frrscript_names_entry *snhe); + +DECLARE_HASH(frrscript_names, struct frrscript_names_entry, item, + frrscript_names_hash_cmp, frrscript_names_hash_key); + +int frrscript_names_add_function_name(const char *function_name); +void frrscript_names_destroy(void); +int frrscript_names_set_script_name(const char *function_name, + const char *script_name); +char *frrscript_names_get_script_name(const char *function_name); + +typedef void (*encoder_func)(lua_State *, const void *); +typedef void *(*decoder_func)(lua_State *, int); + +struct frrscript_codec { + const char *typename; + encoder_func encoder; + decoder_func decoder; +}; + +struct lua_function_state { + const char *name; + lua_State *L; +}; + +struct frrscript { + /* Script name */ + char *name; + + /* Hash of Lua function name to Lua function state */ + struct hash *lua_function_hash; +}; + + +/* + * Hash related functions for lua_function_hash + */ + +void *lua_function_alloc(void *arg); + +unsigned int lua_function_hash_key(const void *data); + +bool lua_function_hash_cmp(const void *d1, const void *d2); + +struct frrscript_env { + /* Value type */ + const char *typename; + + /* Binding name */ + const char *name; + + /* Value */ + const void *val; +}; + +/* + * Create new struct frrscript for a Lua script. + * This will hold the states for the Lua functions in this script. + * + * scriptname + * Name of the Lua script file, without the .lua + */ +struct frrscript *frrscript_new(const char *scriptname); + +/* + * Load a function into frrscript, run callback if any + */ +int frrscript_load(struct frrscript *fs, const char *function_name, + int (*load_cb)(struct frrscript *)); + +/* + * Delete Lua function states and frrscript + */ +void frrscript_delete(struct frrscript *fs); + +/* + * Register a Lua codec for a type. + * + * tname + * Name of type; e.g., "peer", "ospf_interface", etc. Chosen at will. + * + * codec(s) + * Function pointer to codec struct. Encoder function should push a Lua + * table representing the passed argument - which will have the C type + * associated with the chosen 'tname' to the provided stack. The decoder + * function should pop a value from the top of the stack and return a heap + * chunk containing that value. Allocations should be made with MTYPE_TMP. + * + * If using the plural function variant, pass a NULL-terminated array. + * + */ +void frrscript_register_type_codec(struct frrscript_codec *codec); +void frrscript_register_type_codecs(struct frrscript_codec *codecs); + +/* + * Initialize scripting subsystem. Call this before anything else. + * + * scriptdir + * Directory in which to look for scripts + */ +void frrscript_init(const char *scriptdir); + +/* + * On shutdown clean up memory associated with the scripting subsystem + */ +void frrscript_fini(void); + +/* + * This macro is mapped to every (name, value) in frrscript_call, + * so this in turn maps them onto their encoders + */ +#define ENCODE_ARGS(name, value) ENCODE_ARGS_WITH_STATE(lfs->L, (value)) + +/* + * This macro is also mapped to every (name, value) in frrscript_call, but + * not every value can be mapped to its decoder - only those that appear + * in the returned table will. To find out if they appear in the returned + * table, first pop the value and check if its nil. Only call the decoder + * if non-nil. + * + * At the end, the only thing left on the stack should be the + * returned table. + */ +#define DECODE_ARGS(name, value) \ + do { \ + lua_getfield(lfs->L, 1, (name)); \ + if (lua_isnil(lfs->L, 2)) { \ + lua_pop(lfs->L, 1); \ + } else { \ + DECODE_ARGS_WITH_STATE(lfs->L, (value)); \ + } \ + assert(lua_gettop(lfs->L) == 1); \ + } while (0) + +/* + * Noop function. Used below where we need a noop decoder for any type. + */ +void _lua_decode_noop(lua_State *, ...); + +/* + * Maps the type of value to its encoder/decoder. + * Add new mappings here. + * + * L + * Lua state + * scriptdir + * Directory in which to look for scripts + */ +#define ENCODE_ARGS_WITH_STATE(L, value) \ + _Generic((value), \ +int : lua_pushinteger, \ +int * : lua_pushintegerp, \ +long long : lua_pushinteger, \ +long long * : lua_pushlonglongp, \ +struct prefix * : lua_pushprefix, \ +struct interface * : lua_pushinterface, \ +struct in_addr * : lua_pushinaddr, \ +struct in6_addr * : lua_pushin6addr, \ +union sockunion * : lua_pushsockunion, \ +char * : lua_pushstring_wrapper, \ +struct attr * : lua_pushattr, \ +struct peer * : lua_pushpeer, \ +const struct prefix * : lua_pushprefix, \ +const struct ipaddr * : lua_pushipaddr, \ +const struct ethaddr * : lua_pushethaddr, \ +const struct nexthop_group * : lua_pushnexthop_group, \ +const struct nexthop * : lua_pushnexthop, \ +struct zebra_dplane_ctx * : lua_pushzebra_dplane_ctx \ +)((L), (value)) + +#define DECODE_ARGS_WITH_STATE(L, value) \ + _Generic((value), \ +int * : lua_decode_integerp, \ +long long * : lua_decode_longlongp, \ +struct prefix * : lua_decode_prefix, \ +struct interface * : lua_decode_interface, \ +struct in_addr * : lua_decode_inaddr, \ +struct in6_addr * : lua_decode_in6addr, \ +union sockunion * : lua_decode_sockunion, \ +char * : lua_decode_stringp, \ +struct attr * : lua_decode_attr, \ +default : _lua_decode_noop \ +)((L), -1, (value)) + +/* + * Call Lua function state (abstraction for a single Lua function) + * + * lfs + * The Lua function to call; this should have been loaded in by + * frrscript_load(). nargs Number of arguments the function accepts + * + * Returns: + * 0 if the script ran successfully, nonzero otherwise. + */ +int _frrscript_call_lua(struct lua_function_state *lfs, int nargs); + +/* + * Wrapper for calling Lua function state. + * + * The Lua function name (f) to run should have already been checked by + * frrscript_load. So this wrapper will: + * 1) Find the Lua function state, which contains the Lua state + * 2) Clear the Lua state (there may be leftovers items from previous call) + * 3) Push the Lua function (f) + * 4) Map frrscript_call arguments onto their encoder and decoders, push those + * 5) Call _frrscript_call_lua (Lua execution takes place) + * 6) Write back to frrscript_call arguments using their decoders + * + * This wrapper can be called multiple times (after one frrscript_load). + * + * fs + * The struct frrscript in which the Lua fuction was loaded into + * f + * Name of the Lua function. + * + * Returns: + * 0 if the script ran successfully, nonzero otherwise. + */ +#define frrscript_call(fs, f, ...) \ + ({ \ + struct lua_function_state lookup = {.name = (f)}; \ + struct lua_function_state *lfs; \ + lfs = hash_lookup((fs)->lua_function_hash, &lookup); \ + lfs == NULL ? ({ \ + zlog_err( \ + "frrscript: '%s.lua': '%s': tried to call this function but it was not loaded", \ + (fs)->name, (f)); \ + 1; \ + }) \ + : ({ \ + lua_settop(lfs->L, 0); \ + lua_getglobal(lfs->L, f); \ + MAP_LISTS(ENCODE_ARGS, ##__VA_ARGS__); \ + _frrscript_call_lua( \ + lfs, PP_NARG(__VA_ARGS__)); \ + }) != 0 \ + ? ({ \ + zlog_err( \ + "frrscript: '%s.lua': '%s': this function called but returned non-zero exit code. No variables modified.", \ + (fs)->name, (f)); \ + 1; \ + }) \ + : ({ \ + MAP_LISTS(DECODE_ARGS, \ + ##__VA_ARGS__); \ + 0; \ + }); \ + }) + +/* + * Get result from finished function + * + * fs + * The script. This script must have been run already. + * function_name + * Name of the Lua function. + * name + * Name of the result. + * This will be used as a string key to retrieve from the table that the + * Lua function returns. + * The name here should *not* appear in frrscript_call. + * lua_to + * Function pointer to a lua_to decoder function. + * This function should allocate and decode a value from the Lua state. + * + * Returns: + * A pointer to the decoded value from the Lua state, or NULL if no such + * value. + */ +void *frrscript_get_result(struct frrscript *fs, const char *function_name, + const char *name, + void *(*lua_to)(lua_State *L, int idx)); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* HAVE_SCRIPTING */ + +#endif /* __FRRSCRIPT_H__ */ diff --git a/lib/frrsendmmsg.h b/lib/frrsendmmsg.h new file mode 100644 index 0000000..ea63d13 --- /dev/null +++ b/lib/frrsendmmsg.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR sendmmsg wrapper + * Copyright (C) 2024 by Nvidia, Inc. + * Donald Sharp + */ +#ifndef __FRRSENDMMSG_H__ +#define __FRRSENDMMSG_H__ + +#if !defined(HAVE_STRUCT_MMSGHDR_MSG_HDR) || !defined(HAVE_SENDMMSG) +/* avoid conflicts in case we have partial support */ +#define mmsghdr frr_mmsghdr +#define sendmmsg frr_sendmmsg + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +/* just go 1 at a time here, the loop this is used in will handle the rest */ +static inline int sendmmsg(int fd, struct mmsghdr *mmh, unsigned int len, + int flags) +{ + int rv = sendmsg(fd, &mmh->msg_hdr, 0); + + return rv > 0 ? 1 : rv; +} +#endif + +#endif diff --git a/lib/frrstr.c b/lib/frrstr.c new file mode 100644 index 0000000..1e743d4 --- /dev/null +++ b/lib/frrstr.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR string processing utilities. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include "zebra.h" + +#include +#include +#include +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ + +#include "frrstr.h" +#include "memory.h" +#include "vector.h" + +void frrstr_split(const char *string, const char *delimiter, char ***result, + int *argc) +{ + if (!string) + return; + + unsigned int sz = 4, idx = 0; + char *copy, *copystart; + *result = XCALLOC(MTYPE_TMP, sizeof(char *) * sz); + copystart = copy = XSTRDUP(MTYPE_TMP, string); + *argc = 0; + + const char *tok = NULL; + + while (copy) { + tok = strsep(©, delimiter); + (*result)[idx] = XSTRDUP(MTYPE_TMP, tok); + if (++idx == sz) + *result = XREALLOC(MTYPE_TMP, *result, + (sz *= 2) * sizeof(char *)); + (*argc)++; + } + + XFREE(MTYPE_TMP, copystart); +} + +vector frrstr_split_vec(const char *string, const char *delimiter) +{ + char **result; + int argc; + + if (!string) + return NULL; + + frrstr_split(string, delimiter, &result, &argc); + + vector v = array_to_vector((void **)result, argc); + + XFREE(MTYPE_TMP, result); + + return v; +} + +char *frrstr_join(const char **parts, int argc, const char *join) +{ + int i; + char *str; + char *p; + size_t len = 0; + size_t joinlen = join ? strlen(join) : 0; + + if (!argc) + return NULL; + + for (i = 0; i < argc; i++) + len += strlen(parts[i]); + len += argc * joinlen + 1; + + if (!len) + return NULL; + + p = str = XMALLOC(MTYPE_TMP, len); + + for (i = 0; i < argc; i++) { + size_t arglen = strlen(parts[i]); + + memcpy(p, parts[i], arglen); + p += arglen; + if (i + 1 != argc && join) { + memcpy(p, join, joinlen); + p += joinlen; + } + } + + *p = '\0'; + + return str; +} + +char *frrstr_join_vec(vector v, const char *join) +{ + char **argv; + int argc; + + vector_to_array(v, (void ***)&argv, &argc); + + char *ret = frrstr_join((const char **)argv, argc, join); + + XFREE(MTYPE_TMP, argv); + + return ret; +} + +void frrstr_filter_vec(vector v, regex_t *filter) +{ + regmatch_t ignored[1]; + + for (unsigned int i = 0; i < vector_active(v); i++) { + if (regexec(filter, vector_slot(v, i), 0, ignored, 0)) { + XFREE(MTYPE_TMP, vector_slot(v, i)); + vector_unset(v, i); + } + } +} + +void frrstr_strvec_free(vector v) +{ + unsigned int i; + char *cp; + + if (!v) + return; + + for (i = 0; i < vector_active(v); i++) { + cp = vector_slot(v, i); + XFREE(MTYPE_TMP, cp); + } + + vector_free(v); +} + +char *frrstr_replace(const char *str, const char *find, const char *replace) +{ + char *ch; + char *nustr = XSTRDUP(MTYPE_TMP, str); + + size_t findlen = strlen(find); + size_t repllen = strlen(replace); + + while ((ch = strstr(nustr, find))) { + if (repllen > findlen) { + size_t nusz = strlen(nustr) + repllen - findlen + 1; + nustr = XREALLOC(MTYPE_TMP, nustr, nusz); + ch = strstr(nustr, find); + } + + size_t nustrlen = strlen(nustr); + size_t taillen = (nustr + nustrlen) - (ch + findlen); + + memmove(ch + findlen + (repllen - findlen), ch + findlen, + taillen + 1); + memcpy(ch, replace, repllen); + } + + return nustr; +} + +bool frrstr_startswith(const char *str, const char *prefix) +{ + if (!str || !prefix) + return false; + + size_t lenstr = strlen(str); + size_t lenprefix = strlen(prefix); + + if (lenprefix > lenstr) + return false; + + return strncmp(str, prefix, lenprefix) == 0; +} + +bool frrstr_endswith(const char *str, const char *suffix) +{ + if (!str || !suffix) + return false; + + size_t lenstr = strlen(str); + size_t lensuffix = strlen(suffix); + + if (lensuffix > lenstr) + return false; + + return strncmp(&str[lenstr - lensuffix], suffix, lensuffix) == 0; +} + +int all_digit(const char *str) +{ + for (; *str != '\0'; str++) + if (!isdigit((unsigned char)*str)) + return 0; + return 1; +} + + +char *frrstr_hex(char *buff, size_t bufsiz, const uint8_t *str, size_t num) +{ + if (bufsiz == 0) + return buff; + + char tmp[3]; + + buff[0] = '\0'; + + for (size_t i = 0; i < num; i++) { + snprintf(tmp, sizeof(tmp), "%02x", (unsigned char)str[i]); + strlcat(buff, tmp, bufsiz); + } + + return buff; +} + +const char *frrstr_skip_over_char(const char *s, int skipc) +{ + int c, quote = 0; + + while ((c = *s++)) { + if (c == '\\') { + if (!*s++) + return NULL; + continue; + } + if (quote) { + if (c == quote) + quote = 0; + continue; + } + if (c == skipc) + return s; + if (c == '"' || c == '\'') + quote = c; + } + return NULL; +} + +/* + * Advance backward in string until reaching the char `toc` + * if beginning of string is reached w/o finding char return NULL + * + * /foo/bar'baz/booz'/foo + */ +const char *frrstr_back_to_char(const char *s, int toc) +{ + const char *next = s; + const char *prev = NULL; + + if (s[0] == 0) + return NULL; + if (!strpbrk(s, "'\"\\")) + return strrchr(s, toc); + while ((next = frrstr_skip_over_char(next, toc))) + prev = next - 1; + return prev; +} + diff --git a/lib/frrstr.h b/lib/frrstr.h new file mode 100644 index 0000000..33a4992 --- /dev/null +++ b/lib/frrstr.h @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR string processing utilities. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#ifndef _FRRSTR_H_ +#define _FRRSTR_H_ + +#include +#include +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ +#include + +#include "vector.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Tokenizes a string, storing tokens in a vector. Whitespace is ignored. + * Delimiter characters are not included. + * + * string + * The string to split + * + * delimiter + * Delimiter string, as used in strsep() + * + * Returns: + * The split string. Each token is allocated with MTYPE_TMP. + */ +void frrstr_split(const char *string, const char *delimiter, char ***result, + int *argc); +vector frrstr_split_vec(const char *string, const char *delimiter); + +/* + * Concatenate string array into a single string. + * + * argv + * array of string pointers to concatenate + * + * argc + * array length + * + * join + * string to insert between each part, or NULL for nothing + * + * Returns: + * the joined string, allocated with MTYPE_TMP + */ +char *frrstr_join(const char **parts, int argc, const char *join); +char *frrstr_join_vec(vector v, const char *join); + +/* + * Filter string vector. + * Removes lines that do not contain a match for the provided regex. + * + * v + * The vector to filter. + * + * filter + * Regex to filter with. + */ +void frrstr_filter_vec(vector v, regex_t *filter); + +/* + * Free allocated string vector. + * Assumes each item is allocated with MTYPE_TMP. + * + * v + * the vector to free + */ +void frrstr_strvec_free(vector v); + +/* + * Given a string, replaces all occurrences of a substring with a different + * string. The result is a new string. The original string is not modified. + * + * If 'replace' is longer than 'find', this function performs N+1 allocations, + * where N is the number of times 'find' occurs in 'str'. If 'replace' is equal + * in length or shorter than 'find', only 1 allocation is performed. + * + * str + * String to perform replacement on. + * + * find + * Substring to replace. + * + * replace + * String to replace 'find' with. + * + * Returns: + * A new string, allocated with MTYPE_TMP, that is the result of performing + * the replacement on 'str'. This must be freed by the caller. + */ +char *frrstr_replace(const char *str, const char *find, const char *replace); + +/* + * Prefix match for string. + * + * str + * string to check for prefix match + * + * prefix + * prefix to look for + * + * Returns: + * true if str starts with prefix, false otherwise + */ +bool frrstr_startswith(const char *str, const char *prefix); + +/* + * Suffix match for string. + * + * str + * string to check for suffix match + * + * suffix + * suffix to look for + * + * Returns: + * true if str ends with suffix, false otherwise + */ +bool frrstr_endswith(const char *str, const char *suffix); + +/* + * Check the string only contains digit characters. + * + * str + * string to check for digits + * + * Returns: + * 1 str only contains digit characters, 0 otherwise + */ +int all_digit(const char *str); + +/* + * Copy the hexadecimal representation of the string to a buffer. + * + * buff + * Buffer to copy result into with size of at least (2 * num) + 1. + * + * bufsiz + * Size of destination buffer. + * + * str + * String to represent as hexadecimal. + * + * num + * Number of characters to copy. + * + * Returns: + * Pointer to buffer containing resulting hexadecimal representation. + */ +char *frrstr_hex(char *buff, size_t bufsiz, const uint8_t *str, size_t num); + +/* + * Advance past a given char `skipc` in a string, while honoring quoting and + * backslash escapes (i.e., ignore `skipc` which occur in quoted sections). + */ +const char *frrstr_skip_over_char(const char *s, int skipc); + +/* + * Advance back from end to a given char `toc` in a string, while honoring + * quoting and backslash escapes. `toc` chars inside quote or escaped are + * ignored. + */ +const char *frrstr_back_to_char(const char *s, int toc); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRRSTR_H_ */ diff --git a/lib/gitversion.pl b/lib/gitversion.pl new file mode 100644 index 0000000..dd25c89 --- /dev/null +++ b/lib/gitversion.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl -w +use strict; + +my $dir = shift; +chdir $dir || die "$dir: $!\n"; + +my $gitdesc = `git describe --always --first-parent --tags --dirty --match 'frr-*' || echo -- \"0-gUNKNOWN\"`; +chomp $gitdesc; +my $gitsuffix = ($gitdesc =~ /-g([0-9a-fA-F]+(-dirty)?)$/) ? "-g$1" : "-gUNKNOWN"; + +printf STDERR "git suffix: %s\n", $gitsuffix; +printf "#define GIT_SUFFIX \"%s\"\n", $gitsuffix; + +my $gitcommit = `git log -1 --format=\"%H\" || echo DEADBEEF`; +chomp $gitcommit; +open(BRANCHES, "git branch -a -v --abbrev=40|") || die "git branch: $!\n"; +my @names = (); +while () { + chomp $_; + if (/\s+(.*?)\s+$gitcommit/) { + my $branch = $1; + if ($branch =~ /^remotes\/(.*?)(\/.*)$/) { + my $path = $2; + my $url = `git config --get "remote.$1.url"`; + chomp $url; + $url =~ s/^(git:|https?:|git@)\/\/github\.com/github/i; + $url =~ s/^(ssh|git):\/\/git\.sv\.gnu\.org\/srv\/git\//savannah:/i; + $url =~ s/^(ssh|git):\/\/git\.savannah\.nongnu\.org\//savannah:/i; + + push @names, $url.$path; + } else { + push @names, 'local:'.$branch; + } + } +} + +printf STDERR "git branches: %s\n", join(", ", @names); + +my $cr = "\\r\\n\\"; +printf <string = command; + cmd->doc = + "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n"; + cmd->func = NULL; + + // parse the command and install it into the command graph + struct graph *graph = graph_new(); + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); + + cmd_graph_parse(graph, cmd); + cmd_graph_merge(nodegraph, graph, +1); + + return CMD_SUCCESS; +} + +DEFUN (grammar_test_complete, + grammar_test_complete_cmd, + "grammar complete COMMAND...", + GRAMMAR_STR + "attempt to complete input on DFA\n" + "command to complete\n") +{ + check_nodegraph(); + + int idx_command = 2; + char *cmdstr = argv_concat(argv, argc, idx_command); + if (!cmdstr) + return CMD_SUCCESS; + + vector command = cmd_make_strvec(cmdstr); + if (!command) { + XFREE(MTYPE_TMP, cmdstr); + return CMD_SUCCESS; + } + + // generate completions of user input + struct list *completions; + enum matcher_rv result = + command_complete(nodegraph, command, &completions); + + // print completions or relevant error message + if (!MATCHER_ERROR(result)) { + vector comps = completions_to_vec(completions); + struct cmd_token *tkn; + + // calculate length of longest tkn->text in completions + unsigned int width = 0, i = 0; + for (i = 0; i < vector_active(comps); i++) { + tkn = vector_slot(comps, i); + unsigned int len = strlen(tkn->text); + width = len > width ? len : width; + } + + // print completions + for (i = 0; i < vector_active(comps); i++) { + tkn = vector_slot(comps, i); + vty_out(vty, " %-*s %s\n", width, tkn->text, + tkn->desc); + } + + for (i = 0; i < vector_active(comps); i++) + cmd_token_del( + (struct cmd_token *)vector_slot(comps, i)); + vector_free(comps); + } else + vty_out(vty, "%% No match\n"); + + // free resources + list_delete(&completions); + cmd_free_strvec(command); + XFREE(MTYPE_TMP, cmdstr); + + return CMD_SUCCESS; +} + +DEFUN (grammar_test_match, + grammar_test_match_cmd, + "grammar match COMMAND...", + GRAMMAR_STR + "attempt to match input on DFA\n" + "command to match\n") +{ + check_nodegraph(); + + int idx_command = 2; + if (argv[2]->arg[0] == '#') + return CMD_SUCCESS; + + char *cmdstr = argv_concat(argv, argc, idx_command); + if (!cmdstr) + return CMD_SUCCESS; + vector command = cmd_make_strvec(cmdstr); + if (!command) { + XFREE(MTYPE_TMP, cmdstr); + return CMD_SUCCESS; + } + + struct list *argvv = NULL; + const struct cmd_element *element = NULL; + enum matcher_rv result = + command_match(nodegraph, command, &argvv, &element); + + // print completions or relevant error message + if (element) { + vty_out(vty, "Matched: %s\n", element->string); + struct listnode *ln; + struct cmd_token *token; + for (ALL_LIST_ELEMENTS_RO(argvv, ln, token)) + vty_out(vty, "%s -- %s\n", token->text, token->arg); + + vty_out(vty, "func: %p\n", element->func); + + list_delete(&argvv); + } else { + assert(MATCHER_ERROR(result)); + switch (result) { + case MATCHER_NO_MATCH: + vty_out(vty, "%% Unknown command\n"); + break; + case MATCHER_INCOMPLETE: + vty_out(vty, "%% Incomplete command\n"); + break; + case MATCHER_AMBIGUOUS: + vty_out(vty, "%% Ambiguous command\n"); + break; + case MATCHER_OK: + vty_out(vty, "%% Unknown error\n"); + break; + } + } + + // free resources + cmd_free_strvec(command); + XFREE(MTYPE_TMP, cmdstr); + + return CMD_SUCCESS; +} + +/** + * Testing shim to test docstrings + */ +DEFUN (grammar_test_doc, + grammar_test_doc_cmd, + "grammar test docstring", + GRAMMAR_STR + "Test function for docstring\n" + "Command end\n") +{ + check_nodegraph(); + + // create cmd_element with docstring + struct cmd_element *cmd = + XCALLOC(MTYPE_CMD_DESCRIPTIONS, sizeof(struct cmd_element)); + cmd->string = XSTRDUP( + MTYPE_CMD_DESCRIPTIONS, + "test docstring (1-255) end VARIABLE [OPTION|set lol] . VARARG"); + cmd->doc = XSTRDUP(MTYPE_CMD_DESCRIPTIONS, + "Test stuff\n" + "docstring thing\n" + "first example\n" + "second example\n" + "follow\n" + "random range\n" + "end thingy\n" + "variable\n" + "optional variable\n" + "optional set\n" + "optional lol\n" + "vararg!\n"); + cmd->func = NULL; + + // parse element + cmd_graph_parse(nodegraph, cmd); + + return CMD_SUCCESS; +} + +/** + * Debugging command to print command graph + */ +DEFUN (grammar_test_show, + grammar_test_show_cmd, + "grammar show [doc]", + GRAMMAR_STR + "print current accumulated DFA\n" + "include docstrings\n") +{ + check_nodegraph(); + + struct graph_node *stack[CMD_ARGC_MAX]; + pretty_print_graph(vty, vector_slot(nodegraph->nodes, 0), 0, argc >= 3, + stack, 0); + return CMD_SUCCESS; +} + +DEFUN (grammar_test_dot, + grammar_test_dot_cmd, + "grammar dotfile OUTNAME", + GRAMMAR_STR + "print current graph for dot\n" + ".dot filename\n") +{ + check_nodegraph(); + FILE *ofd = fopen(argv[2]->arg, "w"); + + if (!ofd) { + vty_out(vty, "%s: %s\r\n", argv[2]->arg, strerror(errno)); + return CMD_SUCCESS; + } + + char *dot = cmd_graph_dump_dot(nodegraph); + + fprintf(ofd, "%s", dot); + fclose(ofd); + XFREE(MTYPE_TMP, dot); + + return CMD_SUCCESS; +} + +struct cmd_permute_item { + char *cmd; + struct cmd_element *el; +}; + +static void cmd_permute_free(void *arg) +{ + struct cmd_permute_item *i = arg; + XFREE(MTYPE_TMP, i->cmd); + XFREE(MTYPE_TMP, i); +} + +static int cmd_permute_cmp(void *a, void *b) +{ + struct cmd_permute_item *aa = a, *bb = b; + return strcmp(aa->cmd, bb->cmd); +} + +static void cmd_graph_permute(struct list *out, struct graph_node **stack, + size_t stackpos, char *cmd) +{ + struct graph_node *gn = stack[stackpos]; + struct cmd_token *tok = gn->data; + char *appendp = cmd + strlen(cmd); + size_t j; + + if (tok->type < SPECIAL_TKN) { + sprintf(appendp, "%s ", tok->text); + appendp += strlen(appendp); + } else if (tok->type == END_TKN) { + struct cmd_permute_item *i = XMALLOC(MTYPE_TMP, sizeof(*i)); + i->el = ((struct graph_node *)vector_slot(gn->to, 0))->data; + i->cmd = XSTRDUP(MTYPE_TMP, cmd); + i->cmd[strlen(cmd) - 1] = '\0'; + listnode_add_sort(out, i); + return; + } + + if (++stackpos == CMD_ARGC_MAX) + return; + + for (size_t i = 0; i < vector_active(gn->to); i++) { + struct graph_node *gnext = vector_slot(gn->to, i); + for (j = 0; j < stackpos; j++) + if (stack[j] == gnext) + break; + if (j != stackpos) + continue; + + stack[stackpos] = gnext; + *appendp = '\0'; + cmd_graph_permute(out, stack, stackpos, cmd); + } +} + +static struct list *cmd_graph_permutations(struct graph *graph) +{ + char accumulate[2048] = ""; + struct graph_node *stack[CMD_ARGC_MAX]; + + struct list *rv = list_new(); + rv->cmp = cmd_permute_cmp; + rv->del = cmd_permute_free; + stack[0] = vector_slot(graph->nodes, 0); + cmd_graph_permute(rv, stack, 0, accumulate); + return rv; +} + +extern vector cmdvec; + +DEFUN (grammar_findambig, + grammar_findambig_cmd, + "grammar find-ambiguous [{printall|nodescan}]", + GRAMMAR_STR + "Find ambiguous commands\n" + "Print all permutations\n" + "Scan all nodes\n") +{ + struct list *commands; + struct cmd_permute_item *prev = NULL, *cur = NULL; + struct listnode *ln; + int i, printall, scan, scannode = 0; + int ambig = 0; + + i = 0; + printall = argv_find(argv, argc, "printall", &i); + i = 0; + scan = argv_find(argv, argc, "nodescan", &i); + + if (scan && nodegraph_free) { + graph_delete_graph(nodegraph_free); + nodegraph_free = NULL; + } + + if (!scan && !nodegraph) { + vty_out(vty, "nodegraph uninitialized\r\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + do { + if (scan) { + struct cmd_node *cnode = + vector_slot(cmdvec, scannode++); + if (!cnode) + continue; + cmd_finalize_node(cnode); + nodegraph = cnode->cmdgraph; + if (!nodegraph) + continue; + vty_out(vty, "scanning node %d (%s)\n", scannode - 1, + cnode->name); + } + + commands = cmd_graph_permutations(nodegraph); + prev = NULL; + for (ALL_LIST_ELEMENTS_RO(commands, ln, cur)) { + int same = prev && !strcmp(prev->cmd, cur->cmd); + if (printall && !same) + vty_out(vty, "'%s' [%x]\n", cur->cmd, + cur->el->daemon); + if (same) { + vty_out(vty, "'%s' AMBIGUOUS:\n", cur->cmd); + vty_out(vty, " %s\n '%s'\n", prev->el->name, + prev->el->string); + vty_out(vty, " %s\n '%s'\n", cur->el->name, + cur->el->string); + vty_out(vty, "\n"); + ambig++; + } + prev = cur; + } + list_delete(&commands); + + vty_out(vty, "\n"); + } while (scan && scannode < LINK_PARAMS_NODE); + + vty_out(vty, "%d ambiguous commands found.\n", ambig); + + if (scan) + nodegraph = NULL; + return ambig == 0 ? CMD_SUCCESS : CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (grammar_init_graph, + grammar_init_graph_cmd, + "grammar init", + GRAMMAR_STR + "(re)initialize graph\n") +{ + if (nodegraph_free) + graph_delete_graph(nodegraph_free); + nodegraph_free = NULL; + + init_cmdgraph(vty, &nodegraph); + return CMD_SUCCESS; +} + +DEFUN (grammar_access, + grammar_access_cmd, + "grammar access (0-65535)", + GRAMMAR_STR + "access node graph\n" + "node number\n") +{ + if (nodegraph_free) + graph_delete_graph(nodegraph_free); + nodegraph_free = NULL; + + struct cmd_node *cnode; + + cnode = vector_slot(cmdvec, atoi(argv[2]->arg)); + if (!cnode) { + vty_out(vty, "%% no such node\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vty_out(vty, "node %d\n", (int)cnode->node); + cmd_finalize_node(cnode); + nodegraph = cnode->cmdgraph; + return CMD_SUCCESS; +} + +/* this is called in vtysh.c to set up the testing shim */ +void grammar_sandbox_init(void) +{ + // install all enable elements + install_element(ENABLE_NODE, &grammar_test_cmd); + install_element(ENABLE_NODE, &grammar_test_show_cmd); + install_element(ENABLE_NODE, &grammar_test_dot_cmd); + install_element(ENABLE_NODE, &grammar_test_match_cmd); + install_element(ENABLE_NODE, &grammar_test_complete_cmd); + install_element(ENABLE_NODE, &grammar_test_doc_cmd); + install_element(ENABLE_NODE, &grammar_findambig_cmd); + install_element(ENABLE_NODE, &grammar_init_graph_cmd); + install_element(ENABLE_NODE, &grammar_access_cmd); +} + +/** + * Pretty-prints a graph, assuming it is a tree. + * + * @param start the node to take as the root + * @param level indent level for recursive calls, always pass 0 + */ +static void pretty_print_graph(struct vty *vty, struct graph_node *start, + int level, int desc, struct graph_node **stack, + size_t stackpos) +{ + // print this node + char tokennum[32]; + struct cmd_token *tok = start->data; + + snprintf(tokennum, sizeof(tokennum), "%d?", tok->type); + vty_out(vty, "%s", lookup_msg(tokennames, tok->type, NULL)); + if (tok->text) + vty_out(vty, ":\"%s\"", tok->text); + if (tok->varname) + vty_out(vty, " => %s", tok->varname); + if (desc) + vty_out(vty, " ?'%s'", tok->desc); + vty_out(vty, " "); + + if (stackpos == CMD_ARGC_MAX) { + vty_out(vty, " -aborting! (depth limit)\n"); + return; + } + stack[stackpos++] = start; + + int numto = desc ? 2 : vector_active(start->to); + if (numto) { + if (numto > 1) + vty_out(vty, "\n"); + for (unsigned int i = 0; i < vector_active(start->to); i++) { + struct graph_node *adj = vector_slot(start->to, i); + // if we're listing multiple children, indent! + if (numto > 1) + for (int j = 0; j < level + 1; j++) + vty_out(vty, " "); + // if this node is a vararg, just print * + if (adj == start) + vty_out(vty, "*"); + else if (((struct cmd_token *)adj->data)->type + == END_TKN) + vty_out(vty, "--END\n"); + else { + size_t k; + for (k = 0; k < stackpos; k++) + if (stack[k] == adj) { + vty_out(vty, "< 1 ? level + 1 : level, + desc, stack, stackpos); + } + } + } else + vty_out(vty, "\n"); +} + +/** stuff that should go in command.c + command.h */ +static void init_cmdgraph(struct vty *vty, struct graph **graph) +{ + // initialize graph, add start noe + *graph = graph_new(); + nodegraph_free = *graph; + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(*graph, token, (void (*)(void *)) & cmd_token_del); + if (vty) + vty_out(vty, "initialized graph\n"); +} diff --git a/lib/grammar_sandbox_main.c b/lib/grammar_sandbox_main.c new file mode 100644 index 0000000..abd42f3 --- /dev/null +++ b/lib/grammar_sandbox_main.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Testing shim and API examples for the new CLI backend. + * + * Minimal main() to run grammar_sandbox standalone. + * [split off grammar_sandbox.c 2017-01-23] + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + * Copyright (C) 2017 David Lamparter for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "command.h" +#include "lib_vty.h" + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + if (!isexit) + exit(0); +} + +struct event_loop *master; + +int main(int argc, char **argv) +{ + struct event event; + + master = event_master_create(NULL); + + zlog_aux_init("NONE: ", LOG_DEBUG); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + cmd_domainname_set("testdomainname"); + + vty_init(master, true); + lib_cmd_init(); + nb_init(master, NULL, 0, false); + + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &event)) + event_call(&event); + + /* Not reached. */ + exit(0); +} diff --git a/lib/graph.c b/lib/graph.c new file mode 100644 index 0000000..e6c2386 --- /dev/null +++ b/lib/graph.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Graph data structure. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +#include +#include "graph.h" +#include "memory.h" +#include "buffer.h" + +DEFINE_MTYPE_STATIC(LIB, GRAPH, "Graph"); +DEFINE_MTYPE_STATIC(LIB, GRAPH_NODE, "Graph Node"); +struct graph *graph_new(void) +{ + struct graph *graph = XCALLOC(MTYPE_GRAPH, sizeof(struct graph)); + graph->nodes = vector_init(VECTOR_MIN_SIZE); + + return graph; +} + +void graph_delete_graph(struct graph *graph) +{ + for (unsigned int i = vector_active(graph->nodes); i--; /**/) + graph_delete_node(graph, vector_slot(graph->nodes, i)); + + vector_free(graph->nodes); + XFREE(MTYPE_GRAPH, graph); +} + +struct graph_node *graph_new_node(struct graph *graph, void *data, + void (*del)(void *)) +{ + struct graph_node *node = + XCALLOC(MTYPE_GRAPH_NODE, sizeof(struct graph_node)); + + node->from = vector_init(VECTOR_MIN_SIZE); + node->to = vector_init(VECTOR_MIN_SIZE); + node->data = data; + node->del = del; + + vector_set(graph->nodes, node); + + return node; +} + +static void graph_vector_remove(vector v, unsigned int ix) +{ + if (ix >= v->active) + return; + + /* v->active is guaranteed >= 1 because ix can't be lower than 0 + * and v->active is > ix. */ + v->active--; + /* if ix == v->active--, we set the item to itself, then to NULL... + * still correct, no check necessary. */ + v->index[ix] = v->index[v->active]; + v->index[v->active] = NULL; +} + +void graph_delete_node(struct graph *graph, struct graph_node *node) +{ + if (!node) + return; + + // an adjacent node + struct graph_node *adj; + + // remove all edges from other nodes to us + for (unsigned int i = vector_active(node->from); i--; /**/) { + adj = vector_slot(node->from, i); + graph_remove_edge(adj, node); + } + + // remove all edges from us to other nodes + for (unsigned int i = vector_active(node->to); i--; /**/) { + adj = vector_slot(node->to, i); + graph_remove_edge(node, adj); + } + + // if there is a deletion callback, call it + if (node->del && node->data) + (*node->del)(node->data); + + // free adjacency lists + vector_free(node->to); + vector_free(node->from); + + // remove node from graph->nodes + for (unsigned int i = vector_active(graph->nodes); i--; /**/) + if (vector_slot(graph->nodes, i) == node) { + graph_vector_remove(graph->nodes, i); + break; + } + + // free the node itself + XFREE(MTYPE_GRAPH_NODE, node); +} + +struct graph_node *graph_add_edge(struct graph_node *from, + struct graph_node *to) +{ + vector_set(from->to, to); + vector_set(to->from, from); + return to; +} + +void graph_remove_edge(struct graph_node *from, struct graph_node *to) +{ + // remove from from to->from + for (unsigned int i = vector_active(to->from); i--; /**/) + if (vector_slot(to->from, i) == from) { + graph_vector_remove(to->from, i); + break; + } + // remove to from from->to + for (unsigned int i = vector_active(from->to); i--; /**/) + if (vector_slot(from->to, i) == to) { + graph_vector_remove(from->to, i); + break; + } +} + +struct graph_node *graph_find_node(struct graph *graph, void *data) +{ + struct graph_node *g; + + for (unsigned int i = vector_active(graph->nodes); i--; /**/) { + g = vector_slot(graph->nodes, i); + if (g->data == data) + return g; + } + + return NULL; +} + +bool graph_has_edge(struct graph_node *from, struct graph_node *to) +{ + for (unsigned int i = vector_active(from->to); i--; /**/) + if (vector_slot(from->to, i) == to) + return true; + + return false; +} + +static void _graph_dfs(struct graph *graph, struct graph_node *start, + vector visited, + void (*dfs_cb)(struct graph_node *, void *), void *arg) +{ + /* check that we have not visited this node */ + for (unsigned int i = 0; i < vector_active(visited); i++) { + if (start == vector_slot(visited, i)) + return; + } + + /* put this node in visited stack */ + vector_ensure(visited, vector_active(visited)); + vector_set_index(visited, vector_active(visited), start); + + /* callback */ + dfs_cb(start, arg); + + /* recurse into children */ + for (unsigned int i = vector_active(start->to); i--; /**/) { + struct graph_node *c = vector_slot(start->to, i); + + _graph_dfs(graph, c, visited, dfs_cb, arg); + } +} + +void graph_dfs(struct graph *graph, struct graph_node *start, + void (*dfs_cb)(struct graph_node *, void *), void *arg) +{ + vector visited = vector_init(VECTOR_MIN_SIZE); + + _graph_dfs(graph, start, visited, dfs_cb, arg); + vector_free(visited); +} + +#ifndef BUILDING_CLIPPY + +void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf) +{ + char nbuf[64]; + + for (unsigned int i = 0; i < vector_active(gn->to); i++) { + struct graph_node *adj = vector_slot(gn->to, i); + + snprintf(nbuf, sizeof(nbuf), " n%p -> n%p;\n", gn, adj); + buffer_putstr(buf, nbuf); + } +} + +char *graph_dump_dot(struct graph *graph, struct graph_node *start, + void (*pcb)(struct graph_node *, struct buffer *)) +{ + struct buffer *buf = buffer_new(0); + char *ret; + + pcb = (pcb) ? pcb : graph_dump_dot_default_print_cb; + buffer_putstr(buf, "digraph {\n"); + + graph_dfs(graph, start, (void (*)(struct graph_node *, void *))pcb, + buf); + + buffer_putstr(buf, "}\n"); + + ret = buffer_getstr(buf); + buffer_free(buf); + + return ret; +} + +#endif /* BUILDING_CLIPPY */ diff --git a/lib/graph.h b/lib/graph.h new file mode 100644 index 0000000..bfdfe14 --- /dev/null +++ b/lib/graph.h @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Graph data structure. + * + * -- + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +#ifndef _ZEBRA_COMMAND_GRAPH_H +#define _ZEBRA_COMMAND_GRAPH_H + +#include +#include "vector.h" +#include "buffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct graph { + vector nodes; +}; + +struct graph_node { + vector from; // nodes which have edges to this node + vector to; // nodes which this node has edges to + + void *data; // node data + void (*del)(void *data); // deletion callback +}; + +struct graph *graph_new(void); + +/** + * Creates a new node. + * + * @struct graph the graph this node exists in + * @param[in] data this node's data + * @param[in] del data deletion callback + * @return the new node + */ +struct graph_node *graph_new_node(struct graph *graph, void *data, + void (*del)(void *)); + +/** + * Deletes a node. + * + * Before deletion, this function removes all edges to and from this node from + * any neighbor nodes. + * + * If *data and *del are non-null, the following call is made: + * (*node->del) (node->data); + * + * @param[in] graph the graph this node belongs to + * @param[out] node pointer to node to delete + */ +void graph_delete_node(struct graph *graph, struct graph_node *node); + +/** + * Makes a directed edge between two nodes. + * + * @param[in] from + * @param[in] to + * @return to + */ +struct graph_node *graph_add_edge(struct graph_node *from, + struct graph_node *to); + +/** + * Removes a directed edge between two nodes. + * + * @param[in] from + * @param[in] to + */ +void graph_remove_edge(struct graph_node *from, struct graph_node *to); + +/** + * Deletes a graph. + * Calls graph_delete_node on each node before freeing the graph struct itself. + * + * @param graph the graph to delete + */ +void graph_delete_graph(struct graph *graph); + +/* + * Finds a node in the graph. + * + * @param[in] graph the graph to search in + * @param[in] data the data to key off + * @return the first graph node whose data pointer matches `data` + */ +struct graph_node *graph_find_node(struct graph *graph, void *data); + + +/* + * Determines whether two nodes have a directed edge between them. + * + * @param from + * @param to + * @return whether there is a directed edge from `from` to `to`. + */ +bool graph_has_edge(struct graph_node *from, struct graph_node *to); + +/* + * Depth-first search. + * + * Performs a depth-first traversal of the given graph, visiting each node + * exactly once and calling the user-provided callback for each visit. + * + * @param graph the graph to operate on + * @param start the node to take as the root + * @param dfs_cb callback called for each node visited in the traversal + * @param arg argument to provide to dfs_cb + */ +void graph_dfs(struct graph *graph, struct graph_node *start, + void (*dfs_cb)(struct graph_node *, void *), void *arg); + +#ifndef BUILDING_CLIPPY +/* + * Clippy relies on a small subset of sources in lib/, but it cannot link + * libfrr since clippy itself is required to build libfrr. Instead it directly + * includes the sources it needs. One of these is the command graph + * implementation, which wraps this graph implementation. Since we need to use + * the buffer.[ch] sources here, which indirectly rely on most of libfrr, we + * have to ignore them when compiling clippy to avoid build dependency issues. + * + * TODO: Fix clippy build. + */ + +/* + * Default node printer for use with graph_dump_dot. + * + * @param gn the node to print + * @param buf the buffer to print into + */ +void graph_dump_dot_default_print_cb(struct graph_node *gn, struct buffer *buf); + +/* + * Prints a graph in the DOT language. + * + * The generated output is produced from a depth-first traversal of the graph. + * + * @param graph the graph to print + * @param start the node to take as the root + * @param pcb callback called for each node in the traversal that should + * print the node in the DOT language. Passing NULL for this argument + * will use the default printer. See graph_dump_dot_default_print_cb for + * an example. + * @return representation of graph in DOT language, allocated with MTYPE_TMP. + * Caller is responsible for freeing this string. + */ +char *graph_dump_dot(struct graph *graph, struct graph_node *start, + void (*pcb)(struct graph_node *, struct buffer *buf)); + +#endif /* BUILDING_CLIPPY */ + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_COMMAND_GRAPH_H */ diff --git a/lib/hash.c b/lib/hash.c new file mode 100644 index 0000000..df56243 --- /dev/null +++ b/lib/hash.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hash routine. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#include +#include + +#include "hash.h" +#include "memory.h" +#include "linklist.h" +#include "termtable.h" +#include "vty.h" +#include "command.h" +#include "libfrr.h" +#include "frr_pthread.h" +#include "libfrr_trace.h" + +DEFINE_MTYPE_STATIC(LIB, HASH, "Hash"); +DEFINE_MTYPE_STATIC(LIB, HASH_BUCKET, "Hash Bucket"); +DEFINE_MTYPE_STATIC(LIB, HASH_INDEX, "Hash Index"); + +static pthread_mutex_t _hashes_mtx = PTHREAD_MUTEX_INITIALIZER; +static struct list *_hashes; + +struct hash *hash_create_size(unsigned int size, + unsigned int (*hash_key)(const void *), + bool (*hash_cmp)(const void *, const void *), + const char *name) +{ + struct hash *hash; + + assert((size & (size - 1)) == 0); + hash = XCALLOC(MTYPE_HASH, sizeof(struct hash)); + hash->index = + XCALLOC(MTYPE_HASH_INDEX, sizeof(struct hash_bucket *) * size); + hash->size = size; + hash->hash_key = hash_key; + hash->hash_cmp = hash_cmp; + hash->count = 0; + hash->name = name ? XSTRDUP(MTYPE_HASH, name) : NULL; + hash->stats.empty = hash->size; + + frr_with_mutex (&_hashes_mtx) { + if (!_hashes) + _hashes = list_new(); + + listnode_add(_hashes, hash); + } + + return hash; +} + +struct hash *hash_create(unsigned int (*hash_key)(const void *), + bool (*hash_cmp)(const void *, const void *), + const char *name) +{ + return hash_create_size(HASH_INITIAL_SIZE, hash_key, hash_cmp, name); +} + +void *hash_alloc_intern(void *arg) +{ + return arg; +} + +/* + * ssq = ssq + (new^2 - old^2) + * = ssq + ((new + old) * (new - old)) + */ +#define hash_update_ssq(hz, old, new) \ + do { \ + int _adjust = (new + old) * (new - old); \ + if (_adjust < 0) \ + atomic_fetch_sub_explicit(&hz->stats.ssq, -_adjust, \ + memory_order_relaxed); \ + else \ + atomic_fetch_add_explicit(&hz->stats.ssq, _adjust, \ + memory_order_relaxed); \ + } while (0) + +/* Expand hash if the chain length exceeds the threshold. */ +static void hash_expand(struct hash *hash) +{ + unsigned int i, new_size; + struct hash_bucket *hb, *hbnext, **new_index; + + new_size = hash->size * 2; + + if (hash->max_size && new_size > hash->max_size) + return; + + new_index = XCALLOC(MTYPE_HASH_INDEX, + sizeof(struct hash_bucket *) * new_size); + + hash->stats.empty = new_size; + + for (i = 0; i < hash->size; i++) + for (hb = hash->index[i]; hb; hb = hbnext) { + unsigned int h = hb->key & (new_size - 1); + + hbnext = hb->next; + hb->next = new_index[h]; + + int oldlen = hb->next ? hb->next->len : 0; + int newlen = oldlen + 1; + + if (newlen == 1) + hash->stats.empty--; + else + hb->next->len = 0; + + hb->len = newlen; + + hash_update_ssq(hash, oldlen, newlen); + + new_index[h] = hb; + } + + /* Switch to new table */ + XFREE(MTYPE_HASH_INDEX, hash->index); + hash->size = new_size; + hash->index = new_index; +} + +void *hash_get(struct hash *hash, void *data, void *(*alloc_func)(void *)) +{ + frrtrace(2, frr_libfrr, hash_get, hash, data); + + unsigned int key; + unsigned int index; + void *newdata; + struct hash_bucket *bucket; + + if (!alloc_func && !hash->count) + return NULL; + + key = (*hash->hash_key)(data); + index = key & (hash->size - 1); + + for (bucket = hash->index[index]; bucket != NULL; + bucket = bucket->next) { + if (bucket->key == key && (*hash->hash_cmp)(bucket->data, data)) + return bucket->data; + } + + if (alloc_func) { + newdata = (*alloc_func)(data); + if (newdata == NULL) + return NULL; + + if (HASH_THRESHOLD(hash->count + 1, hash->size)) { + hash_expand(hash); + index = key & (hash->size - 1); + } + + bucket = XCALLOC(MTYPE_HASH_BUCKET, sizeof(struct hash_bucket)); + bucket->data = newdata; + bucket->key = key; + bucket->next = hash->index[index]; + hash->index[index] = bucket; + hash->count++; + + frrtrace(3, frr_libfrr, hash_insert, hash, data, key); + + int oldlen = bucket->next ? bucket->next->len : 0; + int newlen = oldlen + 1; + + if (newlen == 1) + hash->stats.empty--; + else + bucket->next->len = 0; + + bucket->len = newlen; + + hash_update_ssq(hash, oldlen, newlen); + + return bucket->data; + } + return NULL; +} + +void *hash_lookup(struct hash *hash, void *data) +{ + return hash_get(hash, data, NULL); +} + +unsigned int string_hash_make(const char *str) +{ + unsigned int hash = 0; + + while (*str) + hash = (hash * 33) ^ (unsigned int)*str++; + + return hash; +} + +void *hash_release(struct hash *hash, void *data) +{ + void *ret = NULL; + unsigned int key; + unsigned int index; + struct hash_bucket *bucket; + struct hash_bucket *pp; + + key = (*hash->hash_key)(data); + index = key & (hash->size - 1); + + for (bucket = pp = hash->index[index]; bucket; bucket = bucket->next) { + if (bucket->key == key + && (*hash->hash_cmp)(bucket->data, data)) { + int oldlen = hash->index[index]->len; + int newlen = oldlen - 1; + + if (bucket == pp) + hash->index[index] = bucket->next; + else + pp->next = bucket->next; + + if (hash->index[index]) + hash->index[index]->len = newlen; + else + hash->stats.empty++; + + hash_update_ssq(hash, oldlen, newlen); + + ret = bucket->data; + XFREE(MTYPE_HASH_BUCKET, bucket); + hash->count--; + break; + } + pp = bucket; + } + + frrtrace(3, frr_libfrr, hash_release, hash, data, ret); + + return ret; +} + +void hash_iterate(struct hash *hash, void (*func)(struct hash_bucket *, void *), + void *arg) +{ + unsigned int i; + struct hash_bucket *hb; + struct hash_bucket *hbnext; + + for (i = 0; i < hash->size; i++) + for (hb = hash->index[i]; hb; hb = hbnext) { + /* get pointer to next hash bucket here, in case (*func) + * decides to delete hb by calling hash_release + */ + hbnext = hb->next; + (*func)(hb, arg); + } +} + +void hash_walk(struct hash *hash, int (*func)(struct hash_bucket *, void *), + void *arg) +{ + unsigned int i; + struct hash_bucket *hb; + struct hash_bucket *hbnext; + int ret = HASHWALK_CONTINUE; + + for (i = 0; i < hash->size; i++) { + for (hb = hash->index[i]; hb; hb = hbnext) { + /* get pointer to next hash bucket here, in case (*func) + * decides to delete hb by calling hash_release + */ + hbnext = hb->next; + ret = (*func)(hb, arg); + if (ret == HASHWALK_ABORT) + return; + } + } +} + +void hash_clean(struct hash *hash, void (*free_func)(void *)) +{ + unsigned int i; + struct hash_bucket *hb; + struct hash_bucket *next; + + for (i = 0; i < hash->size; i++) { + for (hb = hash->index[i]; hb; hb = next) { + next = hb->next; + + if (free_func) + (*free_func)(hb->data); + + XFREE(MTYPE_HASH_BUCKET, hb); + hash->count--; + } + hash->index[i] = NULL; + } + + hash->stats.ssq = 0; + hash->stats.empty = hash->size; +} + +void hash_clean_and_free(struct hash **hash, void (*free_func)(void *)) +{ + if (!*hash) + return; + + hash_clean(*hash, free_func); + hash_free(*hash); + *hash = NULL; +} + +static void hash_to_list_iter(struct hash_bucket *hb, void *arg) +{ + struct list *list = arg; + + listnode_add(list, hb->data); +} + +struct list *hash_to_list(struct hash *hash) +{ + struct list *list = list_new(); + + hash_iterate(hash, hash_to_list_iter, list); + return list; +} + +void hash_free(struct hash *hash) +{ + frr_with_mutex (&_hashes_mtx) { + if (_hashes) { + listnode_delete(_hashes, hash); + if (_hashes->count == 0) { + list_delete(&_hashes); + } + } + } + + XFREE(MTYPE_HASH, hash->name); + + XFREE(MTYPE_HASH_INDEX, hash->index); + XFREE(MTYPE_HASH, hash); +} + + +/* CLI commands ------------------------------------------------------------ */ + +DEFUN_NOSH(show_hash_stats, + show_hash_stats_cmd, + "show debugging hashtable [statistics]", + SHOW_STR + DEBUG_STR + "Statistics about hash tables\n" + "Statistics about hash tables\n") +{ + struct hash *h; + struct listnode *ln; + struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + + ttable_add_row(tt, "Hash table|Buckets|Entries|Empty|LF|SD|FLF|SD"); + tt->style.cell.lpad = 2; + tt->style.cell.rpad = 1; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + /* Summary statistics calculated are: + * + * - Load factor: This is the number of elements in the table divided + * by the number of buckets. Since this hash table implementation + * uses chaining, this value can be greater than 1. + * This number provides information on how 'full' the table is, but + * does not provide information on how evenly distributed the + * elements are. + * Notably, a load factor >= 1 does not imply that every bucket has + * an element; with a pathological hash function, all elements could + * be in a single bucket. + * + * - Full load factor: this is the number of elements in the table + * divided by the number of buckets that have some elements in them. + * + * - Std. Dev.: This is the standard deviation calculated from the + * relevant load factor. If the load factor is the mean of number of + * elements per bucket, the standard deviation measures how much any + * particular bucket is likely to deviate from the mean. + * As a rule of thumb this number should be less than 2, and ideally + * <= 1 for optimal performance. A number larger than 3 generally + * indicates a poor hash function. + */ + + double lf; // load factor + double flf; // full load factor + double var; // overall variance + double fvar; // full variance + double stdv; // overall stddev + double fstdv; // full stddev + + long double x2; // h->count ^ 2 + long double ldc; // (long double) h->count + long double full; // h->size - h->stats.empty + long double ssq; // ssq casted to long double + + pthread_mutex_lock(&_hashes_mtx); + if (!_hashes) { + pthread_mutex_unlock(&_hashes_mtx); + ttable_del(tt); + vty_out(vty, "No hash tables in use.\n"); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(_hashes, ln, h)) { + if (!h->name) + continue; + + ssq = (long double)h->stats.ssq; + x2 = h->count * h->count; + ldc = (long double)h->count; + full = h->size - h->stats.empty; + lf = h->count / (double)h->size; + flf = full ? h->count / (double)(full) : 0; + var = ldc ? (1.0 / ldc) * (ssq - x2 / ldc) : 0; + fvar = full ? (1.0 / full) * (ssq - x2 / full) : 0; + var = (var < .0001) ? 0 : var; + fvar = (fvar < .0001) ? 0 : fvar; + stdv = sqrt(var); + fstdv = sqrt(fvar); + + ttable_add_row(tt, "%s|%d|%ld|%.0f%%|%.2lf|%.2lf|%.2lf|%.2lf", + h->name, h->size, h->count, + (h->stats.empty / (double)h->size) * 100, lf, + stdv, flf, fstdv); + } + pthread_mutex_unlock(&_hashes_mtx); + + /* display header */ + char header[] = "Showing hash table statistics for "; + char underln[sizeof(header) + strlen(frr_protonameinst)]; + memset(underln, '-', sizeof(underln)); + underln[sizeof(underln) - 1] = '\0'; + vty_out(vty, "%s%s\n", header, frr_protonameinst); + vty_out(vty, "%s\n", underln); + + vty_out(vty, "# allocated: %d\n", _hashes->count); + vty_out(vty, "# named: %d\n\n", tt->nrows - 1); + + if (tt->nrows > 1) { + ttable_colseps(tt, 0, RIGHT, true, '|'); + char *table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No named hash tables to display.\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +void hash_cmd_init(void) +{ + install_element(ENABLE_NODE, &show_hash_stats_cmd); +} diff --git a/lib/hash.h b/lib/hash.h new file mode 100644 index 0000000..2d00a33 --- /dev/null +++ b/lib/hash.h @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Hash routine. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_HASH_H +#define _ZEBRA_HASH_H + +#include "memory.h" +#include "frratomic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Default hash table size. */ +#define HASH_INITIAL_SIZE 256 +/* Expansion threshold */ +#define HASH_THRESHOLD(used, size) ((used) > (size)) + +#define HASHWALK_CONTINUE 0 +#define HASHWALK_ABORT -1 + +struct hash_bucket { + /* + * if this bucket is the head of the linked listed, len denotes the + * number of elements in the list + */ + int len; + + /* Hash key. */ + unsigned int key; + + /* Linked list. */ + struct hash_bucket *next; + + /* Data. */ + void *data; +}; + +struct hashstats { + /* number of empty hash buckets */ + atomic_uint_fast32_t empty; + /* sum of squares of bucket length */ + atomic_uint_fast32_t ssq; +}; + +struct hash { + /* Hash bucket. */ + struct hash_bucket **index; + + /* Hash table size. Must be power of 2 */ + unsigned int size; + + /* If max_size is 0 there is no limit */ + unsigned int max_size; + + /* Key make function. */ + unsigned int (*hash_key)(const void *); + + /* Data compare function. */ + bool (*hash_cmp)(const void *, const void *); + + /* Bucket alloc. */ + unsigned long count; + + struct hashstats stats; + + /* hash name */ + char *name; +}; + +#define hashcount(X) ((X)->count) + +/* + * Create a hash table. + * + * The created hash table uses chaining and a user-provided comparator function + * to resolve collisions. For best performance use a perfect hash function. + * Worst case lookup time is O(N) when using a constant hash function. Best + * case lookup time is O(1) when using a perfect hash function. + * + * The initial size of the created hash table is HASH_INITIAL_SIZE. + * + * hash_key + * hash function to use; should return a unique unsigned integer when called + * with a data item. Collisions are acceptable. + * + * hash_cmp + * comparison function used for resolving collisions; when called with two + * data items, should return true if the two items are equal and false + * otherwise + * + * name + * optional name for the hashtable; this is used when displaying global + * hashtable statistics. If this parameter is NULL the hash's name will be + * set to NULL and the default name will be displayed when showing + * statistics. + * + * Returns: + * a new hash table + */ +extern struct hash *hash_create(unsigned int (*hash_key)(const void *), + bool (*hash_cmp)(const void *, const void *), + const char *name); + +/* + * Create a hash table. + * + * The created hash table uses chaining and a user-provided comparator function + * to resolve collisions. For best performance use a perfect hash function. + * Worst case lookup time is O(N) when using a constant hash function. Best + * case lookup time is O(1) when using a perfect hash function. + * + * size + * initial number of hash buckets to allocate; must be a power of 2 or the + * program will assert + * + * hash_key + * hash function to use; should return a unique unsigned integer when called + * with a data item. Collisions are acceptable. + * + * hash_cmp + * comparison function used for resolving collisions; when called with two + * data items, should return true if the two items are equal and false + * otherwise + * + * name + * optional name for the hashtable; this is used when displaying global + * hashtable statistics. If this parameter is NULL the hash's name will be + * set to NULL and the default name will be displayed when showing + * statistics. + * + * Returns: + * a new hash table + */ +extern struct hash * +hash_create_size(unsigned int size, unsigned int (*hash_key)(const void *), + bool (*hash_cmp)(const void *, const void *), + const char *name); + +/* + * Retrieve or insert data from / into a hash table. + * + * This function is somewhat counterintuitive in its usage. In order to look up + * an element from its key, you must provide the data item itself, with the + * portions used in the hash function set to the same values as the data item + * to retrieve. To insert a data element, either provide the key as just + * described and provide alloc_func as described below to allocate the full + * data element, or provide the full data element and pass 'hash_alloc_intern' + * to alloc_func. + * + * hash + * hash table to operate on + * + * data + * data to insert or retrieve - A hash bucket will not be created if + * the alloc_func returns a NULL pointer and nothing will be added to + * the hash. As such bucket->data will always be non-NULL. + * + * alloc_func + * function to call if the item is not found in the hash table. This + * function is called with the value of 'data' and should create the data + * item to insert and return a pointer to it. If the data has already been + * completely created and provided in the 'data' parameter, passing + * 'hash_alloc_intern' to this parameter will cause 'data' to be inserted. + * If this parameter is NULL, then this call to hash_get is equivalent to + * hash_lookup. + * + * Returns: + * the data item found or inserted, or NULL if alloc_func is NULL and the + * data is not found + */ +extern void *hash_get(struct hash *hash, void *data, + void *(*alloc_func)(void *)); + +/* + * Dummy element allocation function. + * + * See hash_get for details. + * + * data + * data to insert into the hash table + * + * Returns: + * data + */ +extern void *hash_alloc_intern(void *data); + +/* + * Retrieve an item from a hash table. + * + * This function is equivalent to calling hash_get with alloc_func set to NULL. + * + * hash + * hash table to operate on + * + * data + * data element with values used for key computation set + * + * Returns: + * the data element if found, or NULL if not found + */ +extern void *hash_lookup(struct hash *hash, void *data); + +/* + * Remove an element from a hash table. + * + * hash + * hash table to operate on + * + * data + * data element to remove with values used for key computation set + * + * Returns: + * the removed element if found, or NULL if not found + */ +extern void *hash_release(struct hash *hash, void *data); + +/* + * Iterate over the elements in a hash table. + * + * The passed in arg to the handler function is the only safe + * item to delete from the hash. + * + * Please note that adding entries to the hash + * during the walk will cause undefined behavior in that some new entries + * will be walked and some will not. So do not do this. + * + * The bucket passed to func will have a non-NULL data pointer. + * + * hash + * hash table to operate on + * + * func + * function to call with each data item + * + * arg + * arbitrary argument passed as the second parameter in each call to 'func' + */ +extern void hash_iterate(struct hash *hash, + void (*func)(struct hash_bucket *, void *), void *arg); + +/* + * Iterate over the elements in a hash table, stopping on condition. + * + * The passed in arg to the handler function is the only safe item + * to delete from the hash. + * + * Please note that adding entries to the hash + * during the walk will cause undefined behavior in that some new entries + * will be walked and some will not. So do not do this. + * + * The bucket passed to func will have a non-NULL data pointer. + * + * hash + * hash table to operate on + * + * func + * function to call with each data item. If this function returns + * HASHWALK_ABORT then the iteration stops. + * + * arg + * arbitrary argument passed as the second parameter in each call to 'func' + */ +extern void hash_walk(struct hash *hash, + int (*func)(struct hash_bucket *, void *), void *arg); + +/* + * Remove all elements from a hash table. + * + * hash + * hash table to operate on + * + * free_func + * function to call with each removed item; intended to free the data + */ +extern void hash_clean(struct hash *hash, void (*free_func)(void *)); + +/* + * Remove all elements from a hash table and free the table, + * setting the pointer to NULL. + * + * hash + * hash table to operate on + * free_func + * function to call with each removed item, intended to free the data + */ +extern void hash_clean_and_free(struct hash **hash, void (*free_func)(void *)); + +/* + * Delete a hash table. + * + * This function assumes the table is empty. Call hash_clean to delete the + * hashtable contents if necessary. + * + * hash + * hash table to delete + */ +extern void hash_free(struct hash *hash); + +/* + * Converts a hash table to an unsorted linked list. + * Does not modify the hash table in any way. + * + * hash + * hash table to convert + */ +extern struct list *hash_to_list(struct hash *hash); + +/* + * Hash a string using the modified Bernstein hash. + * + * This is not a perfect hash function. + * + * str + * string to hash + * + * Returns: + * modified Bernstein hash of the string + */ +extern unsigned int string_hash_make(const char *); + +/* + * Install CLI commands for viewing global hash table statistics. + */ +extern void hash_cmd_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_HASH_H */ diff --git a/lib/hook.c b/lib/hook.c new file mode 100644 index 0000000..1457df8 --- /dev/null +++ b/lib/hook.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "memory.h" +#include "hook.h" + +DEFINE_MTYPE_STATIC(LIB, HOOK_ENTRY, "Hook entry"); + +void _hook_register(struct hook *hook, struct hookent *stackent, void *funcptr, + void *arg, bool has_arg, struct frrmod_runtime *module, + const char *funcname, int priority) +{ + struct hookent *he, **pos; + + if (!stackent->ent_used) + he = stackent; + else { + he = XCALLOC(MTYPE_HOOK_ENTRY, sizeof(*he)); + he->ent_on_heap = true; + } + he->ent_used = true; + he->hookfn = funcptr; + he->hookarg = arg; + he->has_arg = has_arg; + he->module = module; + he->fnname = funcname; + he->priority = priority; + + for (pos = &hook->entries; *pos; pos = &(*pos)->next) + if (hook->reverse ? (*pos)->priority < priority + : (*pos)->priority >= priority) + break; + + he->next = *pos; + *pos = he; +} + +void _hook_unregister(struct hook *hook, void *funcptr, void *arg, bool has_arg) +{ + struct hookent *he, **prev; + + for (prev = &hook->entries; (he = *prev) != NULL; prev = &he->next) + if (he->hookfn == funcptr && he->hookarg == arg + && he->has_arg == has_arg) { + *prev = he->next; + if (he->ent_on_heap) + XFREE(MTYPE_HOOK_ENTRY, he); + else + memset(he, 0, sizeof(*he)); + break; + } +} diff --git a/lib/hook.h b/lib/hook.h new file mode 100644 index 0000000..58aa200 --- /dev/null +++ b/lib/hook.h @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_HOOK_H +#define _FRR_HOOK_H + +#include + +#include "module.h" +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* type-safe subscribable hook points + * + * where "type-safe" applies to the function pointers used for subscriptions + * + * overall usage: + * - to create a hook: + * + * mydaemon.h: + * #include "hook.h" + * DECLARE_HOOK (some_update_event, (struct eventinfo *info), (info)); + * + * mydaemon.c: + * DEFINE_HOOK (some_update_event, (struct eventinfo *info), (info)); + * ... + * hook_call (some_update_event, info) + * + * Note: the second and third macro args must be the hook function's + * parameter list, with the same names for each parameter. The second + * macro arg is with types (used for defining things), the third arg is + * just the names (used for passing along parameters). + * + * Do not use parameter names starting with "hook", these can collide with + * names used by the hook code itself. + * + * The return value is always "int" for now; hook_call will sum up the + * return values from each registered user. Default is 0. + * + * There are no pre-defined semantics for the value, in most cases it is + * ignored. For success/failure indication, 0 should be success, and + * handlers should make sure to only return 0 or 1 (not -1 or other values). + * + * + * - to use a hook / create a handler: + * + * #include "mydaemon.h" + * int event_handler (struct eventinfo *info) { ... } + * hook_register (some_update_event, event_handler); + * + * or, if you need an argument to be passed along (addonptr will be added + * as first argument when calling the handler): + * + * #include "mydaemon.h" + * int event_handler (void *addonptr, struct eventinfo *info) { ... } + * hook_register_arg (some_update_event, event_handler, addonptr); + * + * (addonptr isn't typesafe, but that should be manageable.) + * + * Hooks also support a "priority" value for ordering registered calls + * relative to each other. The priority is a signed integer where lower + * values are called earlier. There is also "Koohs", which is hooks with + * reverse priority ordering (for cleanup/deinit hooks, so you can use the + * same priority value). + * + * Recommended priority value ranges are: + * + * -999 ... 0 ... 999 - main executable / daemon, or library + * -1999 ... -1000 - modules registering calls that should run before + * the daemon's bits + * 1000 ... 1999 - modules calls that should run after daemon's + * + * Note: the default value is 1000, based on the following 2 expectations: + * - most hook_register() usage will be in loadable modules + * - usage of hook_register() in the daemon itself may need relative ordering + * to itself, making an explicit value the expected case + * + * The priority value is passed as extra argument on hook_register_prio() / + * hook_register_arg_prio(). Whether a hook runs in reverse is determined + * solely by the code defining / calling the hook. (DECLARE_KOOH is actually + * the same thing as DECLARE_HOOK, it's just there to make it obvious.) + */ + +/* TODO: + * - hook_unregister_all_module() + * - introspection / CLI / debug + * - testcases ;) + * + * For loadable modules, the idea is that hooks could be automatically + * unregistered when a module is unloaded. + * + * It's also possible to add a constructor (MTYPE style) to DEFINE_HOOK, + * which would make it possible for the CLI to show all hooks and all + * registered handlers. + */ + +struct hookent { + struct hookent *next; + void *hookfn; /* actually a function pointer */ + void *hookarg; + bool has_arg : 1; + bool ent_on_heap : 1; + bool ent_used : 1; + int priority; + struct frrmod_runtime *module; + const char *fnname; +}; + +struct hook { + const char *name; + struct hookent *entries; + bool reverse; +}; + +#define HOOK_DEFAULT_PRIORITY 1000 + +/* subscribe/add callback function to a hook + * + * always use hook_register(), which uses the static inline helper from + * DECLARE_HOOK in order to get type safety + */ +extern void _hook_register(struct hook *hook, struct hookent *stackent, + void *funcptr, void *arg, bool has_arg, + struct frrmod_runtime *module, + const char *funcname, int priority); + +/* most hook_register calls are not in a loop or similar and can use a + * statically allocated "struct hookent" from the data segment + */ +#define _hook_reg_svar(hook, funcptr, arg, has_arg, module, funcname, prio) \ + do { \ + static struct hookent stack_hookent = {}; \ + _hook_register(hook, &stack_hookent, funcptr, arg, has_arg, \ + module, funcname, prio); \ + } while (0) + +#define hook_register(hookname, func) \ + _hook_reg_svar(&_hook_##hookname, _hook_typecheck_##hookname(func), \ + NULL, false, THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY) +#define hook_register_arg(hookname, func, arg) \ + _hook_reg_svar(&_hook_##hookname, \ + _hook_typecheck_arg_##hookname(func), arg, true, \ + THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY) +#define hook_register_prio(hookname, prio, func) \ + _hook_reg_svar(&_hook_##hookname, _hook_typecheck_##hookname(func), \ + NULL, false, THIS_MODULE, #func, prio) +#define hook_register_arg_prio(hookname, prio, func, arg) \ + _hook_reg_svar(&_hook_##hookname, \ + _hook_typecheck_arg_##hookname(func), arg, true, \ + THIS_MODULE, #func, prio) + +extern void _hook_unregister(struct hook *hook, void *funcptr, void *arg, + bool has_arg); +#define hook_unregister(hookname, func) \ + _hook_unregister(&_hook_##hookname, _hook_typecheck_##hookname(func), \ + NULL, false) +#define hook_unregister_arg(hookname, func, arg) \ + _hook_unregister(&_hook_##hookname, \ + _hook_typecheck_arg_##hookname(func), arg, true) + +#define hook_have_hooks(hookname) (_hook_##hookname.entries != NULL) + +/* invoke hooks + * this is private (static) to the file that has the DEFINE_HOOK statement + */ +#define hook_call(hookname, ...) hook_call_##hookname(__VA_ARGS__) + +/* helpers to add the void * arg */ +#define HOOK_ADDDEF(...) (void *hookarg , ## __VA_ARGS__) +#define HOOK_ADDARG(...) (hookarg , ## __VA_ARGS__) + +/* and another helper to convert () into (void) to get a proper prototype */ +#define _SKIP_10(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, ret, ...) ret +#define _MAKE_VOID(...) _SKIP_10(, ##__VA_ARGS__, , , , , , , , , , void) + +#define HOOK_VOIDIFY(...) (_MAKE_VOID(__VA_ARGS__) __VA_ARGS__) + +/* use in header file - declares the hook and its arguments + * usage: DECLARE_HOOK(my_hook, (int arg1, struct foo *arg2), (arg1, arg2)); + * as above, "passlist" must use the same order and same names as "arglist" + * + * theoretically passlist is not necessary, but let's keep things simple and + * use exact same args on DECLARE and DEFINE. + */ +#define DECLARE_HOOK(hookname, arglist, passlist) \ + extern struct hook _hook_##hookname; \ + __attribute__((unused)) static inline void * \ + _hook_typecheck_##hookname(int(*funcptr) HOOK_VOIDIFY arglist) \ + { \ + return (void *)funcptr; \ + } \ + __attribute__((unused)) static inline void \ + *_hook_typecheck_arg_##hookname(int(*funcptr) \ + HOOK_ADDDEF arglist) \ + { \ + return (void *)funcptr; \ + } \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DECLARE_KOOH(hookname, arglist, passlist) \ + DECLARE_HOOK(hookname, arglist, passlist) + +/* use in source file - contains hook-related definitions. + */ +#define DEFINE_HOOK_INT(hookname, arglist, passlist, rev) \ + struct hook _hook_##hookname = { \ + .name = #hookname, .entries = NULL, .reverse = rev, \ + }; \ + static int hook_call_##hookname HOOK_VOIDIFY arglist \ + { \ + int hooksum = 0; \ + struct hookent *he = _hook_##hookname.entries; \ + void *hookarg; \ + union { \ + void *voidptr; \ + int(*fptr) HOOK_VOIDIFY arglist; \ + int(*farg) HOOK_ADDDEF arglist; \ + } hookp; \ + for (; he; he = he->next) { \ + hookarg = he->hookarg; \ + hookp.voidptr = he->hookfn; \ + if (!he->has_arg) \ + hooksum += hookp.fptr passlist; \ + else \ + hooksum += hookp.farg HOOK_ADDARG passlist; \ + } \ + return hooksum; \ + } \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DEFINE_HOOK(hookname, arglist, passlist) \ + DEFINE_HOOK_INT(hookname, arglist, passlist, false) +#define DEFINE_KOOH(hookname, arglist, passlist) \ + DEFINE_HOOK_INT(hookname, arglist, passlist, true) + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_HOOK_H */ diff --git a/lib/iana_afi.h b/lib/iana_afi.h new file mode 100644 index 0000000..b9c19cc --- /dev/null +++ b/lib/iana_afi.h @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * iana_afi and safi definitions. + * Copyright (C) 2018-2019 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __IANA_AFI_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The above AFI and SAFI definitions are for internal use. The protocol + * definitions (IANA values) as for example used in BGP protocol packets + * are defined below and these will get mapped to/from the internal values + * in the appropriate places. + * The rationale is that the protocol (IANA) values may be sparse and are + * not optimal for use in data-structure sizing. + * Note: Only useful (i.e., supported) values are defined below. + */ +typedef enum { + IANA_AFI_RESERVED = 0, + IANA_AFI_IPV4 = 1, + IANA_AFI_IPV6 = 2, + IANA_AFI_L2VPN = 25, +} iana_afi_t; + +typedef enum { + IANA_SAFI_RESERVED = 0, + IANA_SAFI_UNICAST = 1, + IANA_SAFI_MULTICAST = 2, + IANA_SAFI_LABELED_UNICAST = 4, + IANA_SAFI_ENCAP = 7, + IANA_SAFI_EVPN = 70, + IANA_SAFI_MPLS_VPN = 128, + IANA_SAFI_FLOWSPEC = 133 +} iana_safi_t; + +static inline afi_t afi_iana2int(iana_afi_t afi) +{ + switch (afi) { + case IANA_AFI_IPV4: + return AFI_IP; + case IANA_AFI_IPV6: + return AFI_IP6; + case IANA_AFI_L2VPN: + return AFI_L2VPN; + case IANA_AFI_RESERVED: + return AFI_MAX; + } + + return AFI_MAX; +} + +static inline iana_afi_t afi_int2iana(afi_t afi) +{ + switch (afi) { + case AFI_IP: + return IANA_AFI_IPV4; + case AFI_IP6: + return IANA_AFI_IPV6; + case AFI_L2VPN: + return IANA_AFI_L2VPN; + case AFI_UNSPEC: + case AFI_MAX: + return IANA_AFI_RESERVED; + } + + return IANA_AFI_RESERVED; +} + +static inline const char *iana_afi2str(iana_afi_t afi) +{ + return afi2str(afi_iana2int(afi)); +} + +static inline safi_t safi_iana2int(iana_safi_t safi) +{ + switch (safi) { + case IANA_SAFI_UNICAST: + return SAFI_UNICAST; + case IANA_SAFI_MULTICAST: + return SAFI_MULTICAST; + case IANA_SAFI_MPLS_VPN: + return SAFI_MPLS_VPN; + case IANA_SAFI_ENCAP: + return SAFI_ENCAP; + case IANA_SAFI_EVPN: + return SAFI_EVPN; + case IANA_SAFI_LABELED_UNICAST: + return SAFI_LABELED_UNICAST; + case IANA_SAFI_FLOWSPEC: + return SAFI_FLOWSPEC; + case IANA_SAFI_RESERVED: + return SAFI_MAX; + } + + return SAFI_MAX; +} + +static inline iana_safi_t safi_int2iana(safi_t safi) +{ + switch (safi) { + case SAFI_UNICAST: + return IANA_SAFI_UNICAST; + case SAFI_MULTICAST: + return IANA_SAFI_MULTICAST; + case SAFI_MPLS_VPN: + return IANA_SAFI_MPLS_VPN; + case SAFI_ENCAP: + return IANA_SAFI_ENCAP; + case SAFI_EVPN: + return IANA_SAFI_EVPN; + case SAFI_LABELED_UNICAST: + return IANA_SAFI_LABELED_UNICAST; + case SAFI_FLOWSPEC: + return IANA_SAFI_FLOWSPEC; + case SAFI_UNSPEC: + case SAFI_MAX: + return IANA_SAFI_RESERVED; + } + + return IANA_SAFI_RESERVED; +} + +static inline const char *iana_safi2str(iana_safi_t safi) +{ + return safi2str(safi_iana2int(safi)); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/id_alloc.c b/lib/id_alloc.c new file mode 100644 index 0000000..477f6e3 --- /dev/null +++ b/lib/id_alloc.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR ID Number Allocator + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "id_alloc.h" + +#include "log.h" +#include "lib_errors.h" +#include "memory.h" + +#include + +DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR, "ID Number Allocator"); +DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR_NAME, "ID Number Allocator Name"); +DEFINE_MTYPE_STATIC(LIB, IDALLOC_DIRECTORY, "ID Number Allocator Directory"); +DEFINE_MTYPE_STATIC(LIB, IDALLOC_SUBDIRECTORY, + "ID Number Allocator Subdirectory"); +DEFINE_MTYPE_STATIC(LIB, IDALLOC_PAGE, "ID Number Allocator Page"); +DEFINE_MTYPE_STATIC(LIB, IDALLOC_POOL, + "ID Number temporary holding pool entry"); + +#if UINT_MAX >= UINT32_MAX +#define FFS32(x) ffs(x) +#else +/* ints less than 32 bits? Yikes. */ +#define FFS32(x) ffsl(x) +#endif + +#define DIR_MASK ((1<> DIR_SHIFT) & DIR_MASK) +#define ID_SUBDIR(id) ((id >> SUBDIR_SHIFT) & SUBDIR_MASK) +#define ID_PAGE(id) ((id >> FRR_ID_PAGE_SHIFT) & FRR_ID_PAGE_MASK) +#define ID_WORD(id) ((id >> WORD_SHIFT) & WORD_MASK) +#define ID_OFFSET(id) ((id >> OFFSET_SHIFT) & OFFSET_MASK) + +/* + * Find the page that an ID number belongs to in an allocator. + * Optionally create the page if it doesn't exist. + */ +static struct id_alloc_page *find_or_create_page(struct id_alloc *alloc, + uint32_t id, int create) +{ + struct id_alloc_dir *dir = NULL; + struct id_alloc_subdir *subdir = NULL; + struct id_alloc_page *page = NULL; + + dir = alloc->sublevels[ID_DIR(id)]; + if (dir == NULL) { + if (create) { + dir = XCALLOC(MTYPE_IDALLOC_DIRECTORY, sizeof(*dir)); + alloc->sublevels[ID_DIR(id)] = dir; + } else { + return NULL; + } + } + + subdir = dir->sublevels[ID_SUBDIR(id)]; + if (subdir == NULL) { + if (create) { + subdir = XCALLOC(MTYPE_IDALLOC_SUBDIRECTORY, + sizeof(*subdir)); + dir->sublevels[ID_SUBDIR(id)] = subdir; + } else { + return NULL; + } + } + + page = subdir->sublevels[ID_PAGE(id)]; + if (page == NULL && create) { + page = XCALLOC(MTYPE_IDALLOC_PAGE, sizeof(*page)); + page->base_value = id; + subdir->sublevels[ID_PAGE(id)] = page; + + alloc->capacity += 1 << FRR_ID_PAGE_SHIFT; + page->next_has_free = alloc->has_free; + alloc->has_free = page; + } else if (page != NULL && create) { + flog_err( + EC_LIB_ID_CONSISTENCY, + "ID Allocator %s attempt to re-create page at %u", + alloc->name, id); + } + + return page; +} + +/* + * Return an ID number back to the allocator. + * While this ID can be re-assigned through idalloc_allocate, the underlying + * memory will not be freed. If this is the first free ID in the page, the page + * will be added to the allocator's list of pages with free IDs. + */ +void idalloc_free(struct id_alloc *alloc, uint32_t id) +{ + struct id_alloc_page *page = NULL; + + int word, offset; + uint32_t old_word, old_word_mask; + + page = find_or_create_page(alloc, id, 0); + if (!page) { + flog_err(EC_LIB_ID_CONSISTENCY, + "ID Allocator %s cannot free #%u. ID Block does not exist.", + alloc->name, id); + return; + } + + word = ID_WORD(id); + offset = ID_OFFSET(id); + + if ((page->allocated_mask[word] & (1 << offset)) == 0) { + flog_err(EC_LIB_ID_CONSISTENCY, + "ID Allocator %s cannot free #%u. ID was not allocated at the time of free.", + alloc->name, id); + return; + } + + old_word = page->allocated_mask[word]; + page->allocated_mask[word] &= ~(((uint32_t)1) << offset); + alloc->allocated -= 1; + + if (old_word == UINT32_MAX) { + /* first bit in this block of 32 to be freed.*/ + + old_word_mask = page->full_word_mask; + page->full_word_mask &= ~(((uint32_t)1) << word); + + if (old_word_mask == UINT32_MAX) { + /* first bit in page freed, add this to the allocator's + * list of pages with free space + */ + page->next_has_free = alloc->has_free; + alloc->has_free = page; + } + } +} + +/* + * Add a allocation page to the end of the allocator's current range. + * Returns null if the allocator has had all possible pages allocated already. + */ +static struct id_alloc_page *create_next_page(struct id_alloc *alloc) +{ + if (alloc->capacity == 0 && alloc->sublevels[0]) + return NULL; /* All IDs allocated and the capacity looped. */ + + return find_or_create_page(alloc, alloc->capacity, 1); +} + +/* + * Marks an ID within an allocator page as in use. + * If the ID was the last free ID in the page, the page is removed from the + * allocator's list of free IDs. In the typical allocation case, this page is + * the first page in the list, and removing the page is fast. If instead an ID + * is being reserved by number, this may end up scanning the whole single linked + * list of pages in order to remove it. + */ +static void reserve_bit(struct id_alloc *alloc, struct id_alloc_page *page, + int word, int offset) +{ + struct id_alloc_page *itr; + + page->allocated_mask[word] |= ((uint32_t)1) << offset; + alloc->allocated += 1; + + if (page->allocated_mask[word] == UINT32_MAX) { + page->full_word_mask |= ((uint32_t)1) << word; + if (page->full_word_mask == UINT32_MAX) { + if (alloc->has_free == page) { + /* allocate always pulls from alloc->has_free */ + alloc->has_free = page->next_has_free; + } else { + /* reserve could pull from any page with free + * bits + */ + itr = alloc->has_free; + while (itr) { + if (itr->next_has_free == page) { + itr->next_has_free = + page->next_has_free; + return; + } + + itr = itr->next_has_free; + } + } + } + } +} + +/* + * Reserve an ID number from the allocator. Returns IDALLOC_INVALID (0) if the + * allocator has no more IDs available. + */ +uint32_t idalloc_allocate(struct id_alloc *alloc) +{ + struct id_alloc_page *page; + int word, offset; + uint32_t return_value; + + if (alloc->has_free == NULL) + create_next_page(alloc); + + if (alloc->has_free == NULL) { + flog_err(EC_LIB_ID_EXHAUST, + "ID Allocator %s has run out of IDs.", alloc->name); + return IDALLOC_INVALID; + } + + page = alloc->has_free; + word = FFS32(~(page->full_word_mask)) - 1; + + if (word < 0 || word >= 32) { + flog_err(EC_LIB_ID_CONSISTENCY, + "ID Allocator %s internal error. Page starting at %d is inconsistent.", + alloc->name, page->base_value); + return IDALLOC_INVALID; + } + + offset = FFS32(~(page->allocated_mask[word])) - 1; + if (offset < 0 || offset >= 32) { + flog_err(EC_LIB_ID_CONSISTENCY, + "ID Allocator %s internal error. Page starting at %d is inconsistent on word %d", + alloc->name, page->base_value, word); + return IDALLOC_INVALID; + } + return_value = page->base_value + word * 32 + offset; + + reserve_bit(alloc, page, word, offset); + + return return_value; +} + +/* + * Tries to allocate a specific ID from the allocator. Returns IDALLOC_INVALID + * when the ID being "reserved" has allready been assigned/reserved. This should + * only be done with low numbered IDs, as the allocator needs to reserve bit-map + * pages in order + */ +uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id) +{ + struct id_alloc_page *page; + int word, offset; + + while (alloc->capacity <= id) + create_next_page(alloc); + + word = ID_WORD(id); + offset = ID_OFFSET(id); + page = find_or_create_page(alloc, id, 0); + /* page can't be null because the loop above ensured it was created. */ + + if (page->allocated_mask[word] & (((uint32_t)1) << offset)) { + flog_err(EC_LIB_ID_CONSISTENCY, + "ID Allocator %s could not reserve %u because it is already allocated.", + alloc->name, id); + return IDALLOC_INVALID; + } + + reserve_bit(alloc, page, word, offset); + return id; +} + +/* + * Set up an empty ID allocator, with IDALLOC_INVALID pre-reserved. + */ +struct id_alloc *idalloc_new(const char *name) +{ + struct id_alloc *ret; + + ret = XCALLOC(MTYPE_IDALLOC_ALLOCATOR, sizeof(*ret)); + ret->name = XSTRDUP(MTYPE_IDALLOC_ALLOCATOR_NAME, name); + + idalloc_reserve(ret, IDALLOC_INVALID); + + return ret; +} + +/* + * Free a subdir, and all pages below it. + */ +static void idalloc_destroy_subdir(struct id_alloc_subdir *subdir) +{ + int i; + + for (i = 0; i < IDALLOC_PAGE_COUNT; i++) { + if (subdir->sublevels[i]) + XFREE(MTYPE_IDALLOC_PAGE, subdir->sublevels[i]); + else + break; + } + XFREE(MTYPE_IDALLOC_SUBDIRECTORY, subdir); +} + +/* + * Free a dir, and all subdirs/pages below it. + */ +static void idalloc_destroy_dir(struct id_alloc_dir *dir) +{ + int i; + + for (i = 0; i < IDALLOC_SUBDIR_COUNT; i++) { + if (dir->sublevels[i]) + idalloc_destroy_subdir(dir->sublevels[i]); + else + break; + } + XFREE(MTYPE_IDALLOC_DIRECTORY, dir); +} + +/* + * Free all memory associated with an ID allocator. + */ +void idalloc_destroy(struct id_alloc *alloc) +{ + int i; + + for (i = 0; i < IDALLOC_DIR_COUNT; i++) { + if (alloc->sublevels[i]) + idalloc_destroy_dir(alloc->sublevels[i]); + else + break; + } + + XFREE(MTYPE_IDALLOC_ALLOCATOR_NAME, alloc->name); + XFREE(MTYPE_IDALLOC_ALLOCATOR, alloc); +} + +/* + * Give an ID number to temporary holding pool. + */ +void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id) +{ + struct id_alloc_pool *new_pool; + + new_pool = XMALLOC(MTYPE_IDALLOC_POOL, sizeof(*new_pool)); + new_pool->id = id; + new_pool->next = *pool_ptr; + *pool_ptr = new_pool; +} + +/* + * Free all ID numbers held in a holding pool back to the main allocator. + */ +void idalloc_drain_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr) +{ + struct id_alloc_pool *current, *next; + + while (*pool_ptr) { + current = *pool_ptr; + next = current->next; + idalloc_free(alloc, current->id); + XFREE(MTYPE_IDALLOC_POOL, current); + *pool_ptr = next; + } +} + +/* + * Allocate an ID from either a holding pool, or the main allocator. IDs will + * only be pulled form the main allocator when the pool is empty. + */ +uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc, + struct id_alloc_pool **pool_ptr) +{ + uint32_t ret; + struct id_alloc_pool *pool_head = *pool_ptr; + + if (pool_head) { + ret = pool_head->id; + *pool_ptr = pool_head->next; + XFREE(MTYPE_IDALLOC_POOL, pool_head); + return ret; + } else { + return idalloc_allocate(alloc); + } +} diff --git a/lib/id_alloc.h b/lib/id_alloc.h new file mode 100644 index 0000000..8a1255f --- /dev/null +++ b/lib/id_alloc.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR ID Number Allocator + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates + */ + +#ifndef _ZEBRA_ID_ALLOC_H +#define _ZEBRA_ID_ALLOC_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define IDALLOC_INVALID 0 + +#define IDALLOC_DIR_BITS 8 +#define IDALLOC_SUBDIR_BITS 7 +#define IDALLOC_PAGE_BITS 7 +#define IDALLOC_WORD_BITS 5 +#define IDALLOC_OFFSET_BITS 5 + +#define IDALLOC_DIR_COUNT (1 << IDALLOC_DIR_BITS) +#define IDALLOC_SUBDIR_COUNT (1 << IDALLOC_SUBDIR_BITS) +#define IDALLOC_PAGE_COUNT (1 << IDALLOC_PAGE_BITS) +#define IDALLOC_WORD_COUNT (1 << IDALLOC_WORD_BITS) + +struct id_alloc_page { + /* Bitmask of allocations. 1s indicates the ID is already allocated. */ + uint32_t allocated_mask[IDALLOC_WORD_COUNT]; + + /* Bitmask for free space in allocated_mask. 1s indicate whole 32 bit + * section is full. + */ + uint32_t full_word_mask; + + /* The ID that bit 0 in allocated_mask corresponds to. */ + uint32_t base_value; + + struct id_alloc_page + *next_has_free; /* Next page with at least one bit open */ +}; + +struct id_alloc_subdir { + struct id_alloc_page *sublevels[IDALLOC_PAGE_COUNT]; +}; + +struct id_alloc_dir { + struct id_alloc_subdir *sublevels[IDALLOC_SUBDIR_COUNT]; +}; + +struct id_alloc { + struct id_alloc_dir *sublevels[IDALLOC_DIR_COUNT]; + + struct id_alloc_page *has_free; + + char *name; + + uint32_t allocated, capacity; +}; + +struct id_alloc_pool { + struct id_alloc_pool *next; + uint32_t id; +}; + +void idalloc_free(struct id_alloc *alloc, uint32_t id); +void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id); +void idalloc_drain_pool(struct id_alloc *alloc, + struct id_alloc_pool **pool_ptr); +uint32_t idalloc_allocate(struct id_alloc *alloc); +uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc, + struct id_alloc_pool **pool_ptr); +uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id); +struct id_alloc *idalloc_new(const char *name); +void idalloc_destroy(struct id_alloc *alloc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/if.c b/lib/if.c new file mode 100644 index 0000000..8f15230 --- /dev/null +++ b/lib/if.c @@ -0,0 +1,1867 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface functions. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#include + +#include + +#ifdef GNU_LINUX +#include +#endif /* GNU_LINUX */ + +#include "linklist.h" +#include "vector.h" +#include "lib_errors.h" +#include "vty.h" +#include "command.h" +#include "vrf.h" +#include "if.h" +#include "sockunion.h" +#include "prefix.h" +#include "memory.h" +#include "table.h" +#include "buffer.h" +#include "log.h" +#include "northbound_cli.h" +#include "admin_group.h" +#include "lib/if_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, IF, "Interface"); +DEFINE_MTYPE_STATIC(LIB, IFDESC, "Intf Desc"); +DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected"); +DEFINE_MTYPE_STATIC(LIB, NBR_CONNECTED, "Neighbor Connected"); +DEFINE_MTYPE(LIB, CONNECTED_LABEL, "Connected interface label"); +DEFINE_MTYPE_STATIC(LIB, IF_LINK_PARAMS, "Informational Link Parameters"); + +static void if_set_name(struct interface *ifp, const char *name); +static struct interface *if_lookup_by_ifindex(ifindex_t ifindex, + vrf_id_t vrf_id); +static struct interface *if_lookup_by_index_all_vrf(ifindex_t ifindex); +static int if_cmp_func(const struct interface *, const struct interface *); +static int if_cmp_index_func(const struct interface *ifp1, + const struct interface *ifp2); +RB_GENERATE(if_name_head, interface, name_entry, if_cmp_func); +RB_GENERATE(if_index_head, interface, index_entry, if_cmp_index_func); + +DEFINE_QOBJ_TYPE(interface); + +DEFINE_HOOK(if_add, (struct interface *ifp), (ifp)); +DEFINE_KOOH(if_del, (struct interface *ifp), (ifp)); + +DEFINE_HOOK(if_real, (struct interface *ifp), (ifp)); +DEFINE_KOOH(if_unreal, (struct interface *ifp), (ifp)); + +DEFINE_HOOK(if_up, (struct interface *ifp), (ifp)); +DEFINE_KOOH(if_down, (struct interface *ifp), (ifp)); + +/* Compare interface names, returning an integer greater than, equal to, or + * less than 0, (following the strcmp convention), according to the + * relationship between ifp1 and ifp2. Interface names consist of an + * alphabetic prefix and a numeric suffix. The primary sort key is + * lexicographic by name, and then numeric by number. No number sorts + * before all numbers. Examples: de0 < de1, de100 < fxp0 < xl0, devpty < + * devpty0, de0 < del0 + */ +int if_cmp_name_func(const char *p1, const char *p2) +{ + unsigned int l1, l2; + long int x1, x2; + int res; + + while (*p1 && *p2) { + char *tmp1, *tmp2; + + /* look up to any number */ + l1 = strcspn(p1, "0123456789"); + l2 = strcspn(p2, "0123456789"); + + /* name lengths are different -> compare names */ + if (l1 != l2) + return (strcmp(p1, p2)); + + /* Note that this relies on all numbers being less than all + * letters, so + * that de0 < del0. + */ + res = strncmp(p1, p2, l1); + + /* names are different -> compare them */ + if (res) + return res; + + /* with identical name part, go to numeric part */ + p1 += l1; + p2 += l1; + + if (!*p1 && !*p2) + return 0; + if (!*p1) + return -1; + if (!*p2) + return 1; + + x1 = strtol(p1, &tmp1, 10); + x2 = strtol(p2, &tmp2, 10); + + /* let's compare numbers now */ + if (x1 < x2) + return -1; + if (x1 > x2) + return 1; + + /* Compare string if numbers are equal (distinguish foo-1 from foo-001) */ + l1 = strspn(p1, "0123456789"); + l2 = strspn(p2, "0123456789"); + if (l1 != l2) + return (strcmp(p1, p2)); + + /* Continue to parse the rest of the string */ + p1 = (const char *)tmp1; + p2 = (const char *)tmp2; + + /* numbers were equal, lets do it again.. + (it happens with name like "eth123.456:789") */ + } + if (*p1) + return 1; + if (*p2) + return -1; + return 0; +} + +static int if_cmp_func(const struct interface *ifp1, + const struct interface *ifp2) +{ + return if_cmp_name_func(ifp1->name, ifp2->name); +} + +static int if_cmp_index_func(const struct interface *ifp1, + const struct interface *ifp2) +{ + if (ifp1->ifindex == ifp2->ifindex) + return 0; + else if (ifp1->ifindex > ifp2->ifindex) + return 1; + else + return -1; +} + +static void ifp_connected_free(void *arg) +{ + struct connected *c = arg; + + connected_free(&c); +} + +/* Create new interface structure. */ +static struct interface *if_new(struct vrf *vrf) +{ + struct interface *ifp; + + assert(vrf); + + ifp = XCALLOC(MTYPE_IF, sizeof(struct interface)); + + ifp->ifindex = IFINDEX_INTERNAL; + ifp->name[0] = '\0'; + + ifp->vrf = vrf; + + if_connected_init(ifp->connected); + + ifp->nbr_connected = list_new(); + ifp->nbr_connected->del = (void (*)(void *))nbr_connected_free; + + /* Enable Link-detection by default */ + SET_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION); + + QOBJ_REG(ifp, interface); + return ifp; +} + +void if_new_via_zapi(struct interface *ifp) +{ + hook_call(if_real, ifp); +} + +void if_destroy_via_zapi(struct interface *ifp) +{ + hook_call(if_unreal, ifp); + + ifp->oldifindex = ifp->ifindex; + if_set_index(ifp, IFINDEX_INTERNAL); + + if (!ifp->configured) + if_delete(&ifp); +} + +void if_up_via_zapi(struct interface *ifp) +{ + hook_call(if_up, ifp); +} + +void if_down_via_zapi(struct interface *ifp) +{ + hook_call(if_down, ifp); +} + +static struct interface *if_create_name(const char *name, struct vrf *vrf) +{ + struct interface *ifp; + + ifp = if_new(vrf); + + if_set_name(ifp, name); + + hook_call(if_add, ifp); + return ifp; +} + +/* Create new interface structure. */ +void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id) +{ + struct vrf *old_vrf, *vrf; + + /* remove interface from old master vrf list */ + old_vrf = ifp->vrf; + + if (ifp->name[0] != '\0') + IFNAME_RB_REMOVE(old_vrf, ifp); + + if (ifp->ifindex != IFINDEX_INTERNAL) + IFINDEX_RB_REMOVE(old_vrf, ifp); + + vrf = vrf_get(vrf_id, NULL); + ifp->vrf = vrf; + + if (ifp->name[0] != '\0') + IFNAME_RB_INSERT(vrf, ifp); + + if (ifp->ifindex != IFINDEX_INTERNAL) + IFINDEX_RB_INSERT(vrf, ifp); +} + + +/* Delete interface structure. */ +void if_delete_retain(struct interface *ifp) +{ + struct connected *ifc; + + hook_call(if_del, ifp); + QOBJ_UNREG(ifp); + + /* Free connected address list */ + while ((ifc = if_connected_pop(ifp->connected))) + ifp_connected_free(ifc); + + /* Free connected nbr address list */ + list_delete_all_node(ifp->nbr_connected); +} + +/* Delete and free interface structure. */ +void if_delete(struct interface **ifp) +{ + struct interface *ptr = *ifp; + struct vrf *vrf = ptr->vrf; + + IFNAME_RB_REMOVE(vrf, ptr); + if (ptr->ifindex != IFINDEX_INTERNAL) + IFINDEX_RB_REMOVE(vrf, ptr); + + if_delete_retain(ptr); + + if_connected_fini(ptr->connected); + list_delete(&ptr->nbr_connected); + + if_link_params_free(ptr); + + XFREE(MTYPE_IFDESC, ptr->desc); + + XFREE(MTYPE_IF, ptr); + *ifp = NULL; +} + +/* Used only internally to check within VRF only */ +static struct interface *if_lookup_by_ifindex(ifindex_t ifindex, + vrf_id_t vrf_id) +{ + struct vrf *vrf; + struct interface if_tmp; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + if_tmp.ifindex = ifindex; + return RB_FIND(if_index_head, &vrf->ifaces_by_index, &if_tmp); +} + +/* Interface existence check by index. */ +struct interface *if_lookup_by_index(ifindex_t ifindex, vrf_id_t vrf_id) +{ + switch (vrf_get_backend()) { + case VRF_BACKEND_UNKNOWN: + case VRF_BACKEND_NETNS: + return(if_lookup_by_ifindex(ifindex, vrf_id)); + case VRF_BACKEND_VRF_LITE: + return(if_lookup_by_index_all_vrf(ifindex)); + } + return NULL; +} + +/* Interface existence check by index. */ +struct interface *if_vrf_lookup_by_index_next(ifindex_t ifindex, + vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface *tmp_ifp; + bool found = false; + + if (!vrf) + return NULL; + + if (ifindex == 0) { + tmp_ifp = RB_MIN(if_index_head, &vrf->ifaces_by_index); + /* skip the vrf interface */ + if (tmp_ifp && if_is_vrf(tmp_ifp)) + ifindex = tmp_ifp->ifindex; + else + return tmp_ifp; + } + + RB_FOREACH (tmp_ifp, if_index_head, &vrf->ifaces_by_index) { + if (found) { + /* skip the vrf interface */ + if (tmp_ifp && if_is_vrf(tmp_ifp)) + continue; + else + return tmp_ifp; + } + if (tmp_ifp->ifindex == ifindex) + found = true; + } + return NULL; +} + +const char *ifindex2ifname(ifindex_t ifindex, vrf_id_t vrf_id) +{ + struct interface *ifp; + + return ((ifp = if_lookup_by_index(ifindex, vrf_id)) != NULL) + ? ifp->name + : "unknown"; +} + +ifindex_t ifname2ifindex(const char *name, vrf_id_t vrf_id) +{ + struct interface *ifp; + + return ((ifp = if_lookup_by_name(name, vrf_id)) != NULL) + ? ifp->ifindex + : IFINDEX_INTERNAL; +} + +/* Interface existence check by interface name. */ +struct interface *if_lookup_by_name(const char *name, vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface if_tmp; + + if (!vrf || !name || strnlen(name, IFNAMSIZ) == IFNAMSIZ) + return NULL; + + strlcpy(if_tmp.name, name, sizeof(if_tmp.name)); + return RB_FIND(if_name_head, &vrf->ifaces_by_name, &if_tmp); +} + +struct interface *if_lookup_by_name_vrf(const char *name, struct vrf *vrf) +{ + struct interface if_tmp; + + if (!name || strnlen(name, IFNAMSIZ) == IFNAMSIZ) + return NULL; + + strlcpy(if_tmp.name, name, sizeof(if_tmp.name)); + return RB_FIND(if_name_head, &vrf->ifaces_by_name, &if_tmp); +} + +static struct interface *if_lookup_by_name_all_vrf(const char *name) +{ + struct vrf *vrf; + struct interface *ifp; + + if (!name || strnlen(name, IFNAMSIZ) == IFNAMSIZ) + return NULL; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + ifp = if_lookup_by_name_vrf(name, vrf); + if (ifp) + return ifp; + } + + return NULL; +} + +static struct interface *if_lookup_by_index_all_vrf(ifindex_t ifindex) +{ + struct vrf *vrf; + struct interface *ifp; + + if (ifindex == IFINDEX_INTERNAL) + return NULL; + + RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) { + ifp = if_lookup_by_ifindex(ifindex, vrf->vrf_id); + if (ifp) + return ifp; + } + + return NULL; +} + +/* Lookup interface by IP address. + * + * supersedes if_lookup_exact_address(), which didn't care about up/down + * state. but all users we have either only care if the address is local + * (=> use if_address_is_local() please), or care about UP interfaces before + * anything else + * + * to accept only UP interfaces, check if_is_up() on the returned ifp. + */ +struct interface *if_lookup_address_local(const void *src, int family, + vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface *ifp, *best_down = NULL; + struct prefix *p; + struct connected *c; + + if (family != AF_INET && family != AF_INET6) + return NULL; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, c) { + p = c->address; + + if (!p || p->family != family) + continue; + + if (family == AF_INET) { + if (!IPV4_ADDR_SAME(&p->u.prefix4, + (struct in_addr *)src)) + continue; + } else if (family == AF_INET6) { + if (!IPV6_ADDR_SAME(&p->u.prefix6, + (struct in6_addr *)src)) + continue; + } + + if (if_is_up(ifp)) + return ifp; + if (!best_down) + best_down = ifp; + } + } + return best_down; +} + +/* Lookup interface by IP address. */ +struct connected *if_lookup_address(const void *matchaddr, int family, + vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct prefix addr; + int bestlen = 0; + struct interface *ifp; + struct connected *c; + struct connected *match; + + if (family == AF_INET) { + addr.family = AF_INET; + addr.u.prefix4 = *((struct in_addr *)matchaddr); + addr.prefixlen = IPV4_MAX_BITLEN; + } else if (family == AF_INET6) { + addr.family = AF_INET6; + addr.u.prefix6 = *((struct in6_addr *)matchaddr); + addr.prefixlen = IPV6_MAX_BITLEN; + } else + assert(!"Attempted lookup of family not supported"); + + match = NULL; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, c) { + if (c->address && (c->address->family == AF_INET) + && prefix_match(CONNECTED_PREFIX(c), &addr) + && (c->address->prefixlen > bestlen)) { + bestlen = c->address->prefixlen; + match = c; + } + } + } + return match; +} + +/* Lookup interface by prefix */ +struct interface *if_lookup_prefix(const struct prefix *prefix, vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface *ifp; + struct connected *c; + + FOR_ALL_INTERFACES (vrf, ifp) { + frr_each (if_connected, ifp->connected, c) { + if (prefix_cmp(c->address, prefix) == 0) { + return ifp; + } + } + } + return NULL; +} + +size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz, + struct interface ***result, vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + struct list *rs = list_new(); + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->hw_addr_len == (int)addrsz + && !memcmp(hw_addr, ifp->hw_addr, addrsz)) + listnode_add(rs, ifp); + } + + if (rs->count) { + *result = XCALLOC(MTYPE_TMP, + sizeof(struct interface *) * rs->count); + list_to_array(rs, (void **)*result, rs->count); + } + + int count = rs->count; + + list_delete(&rs); + + return count; +} + +/* Get the VRF loopback interface, i.e. the loopback on the default VRF + * or the VRF interface. + */ +struct interface *if_get_vrf_loopback(vrf_id_t vrf_id) +{ + struct interface *ifp = NULL; + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + FOR_ALL_INTERFACES (vrf, ifp) + if (if_is_loopback(ifp)) + return ifp; + + return NULL; +} + +/* Get interface by name if given name interface doesn't exist create + one. */ +struct interface *if_get_by_name(const char *name, vrf_id_t vrf_id, + const char *vrf_name) +{ + struct interface *ifp = NULL; + struct vrf *vrf; + + switch (vrf_get_backend()) { + case VRF_BACKEND_UNKNOWN: + case VRF_BACKEND_NETNS: + vrf = vrf_get(vrf_id, vrf_name); + assert(vrf); + + ifp = if_lookup_by_name_vrf(name, vrf); + if (ifp) { + /* If it came from the kernel or by way of zclient, + * believe it and update the ifp accordingly. + */ + if (ifp->vrf->vrf_id != vrf_id && vrf_id != VRF_UNKNOWN) + if_update_to_new_vrf(ifp, vrf_id); + + return ifp; + } + + break; + case VRF_BACKEND_VRF_LITE: + ifp = if_lookup_by_name_all_vrf(name); + if (ifp) { + /* If it came from the kernel or by way of zclient, + * believe it and update the ifp accordingly. + */ + if (ifp->vrf->vrf_id != vrf_id && vrf_id != VRF_UNKNOWN) + if_update_to_new_vrf(ifp, vrf_id); + + return ifp; + } + + vrf = vrf_get(vrf_id, vrf_name); + assert(vrf); + + break; + default: + return NULL; + } + + return if_create_name(name, vrf); +} + +int if_set_index(struct interface *ifp, ifindex_t ifindex) +{ + if (ifp->ifindex == ifindex) + return 0; + + /* + * If there is already an interface with this ifindex, we will collide + * on insertion, so don't even try. + */ + if (if_lookup_by_ifindex(ifindex, ifp->vrf->vrf_id)) + return -1; + + if (ifp->ifindex != IFINDEX_INTERNAL) + IFINDEX_RB_REMOVE(ifp->vrf, ifp); + + ifp->ifindex = ifindex; + + if (ifp->ifindex != IFINDEX_INTERNAL) { + /* + * This should never happen, since we checked if there was + * already an interface with the desired ifindex at the top of + * the function. Nevertheless. + */ + if (IFINDEX_RB_INSERT(ifp->vrf, ifp)) + return -1; + } + + return 0; +} + +static void if_set_name(struct interface *ifp, const char *name) +{ + if (if_cmp_name_func(ifp->name, name) == 0) + return; + + if (ifp->name[0] != '\0') + IFNAME_RB_REMOVE(ifp->vrf, ifp); + + strlcpy(ifp->name, name, sizeof(ifp->name)); + + if (ifp->name[0] != '\0') + IFNAME_RB_INSERT(ifp->vrf, ifp); +} + +/* Does interface up ? */ +int if_is_up(const struct interface *ifp) +{ + return ifp->flags & IFF_UP; +} + +/* Is interface running? */ +int if_is_running(const struct interface *ifp) +{ + return ifp->flags & IFF_RUNNING; +} + +/* Is the interface operative, eg. either UP & RUNNING + or UP & !ZEBRA_INTERFACE_LINK_DETECTION and + if ptm checking is enabled, then ptm check has passed */ +int if_is_operative(const struct interface *ifp) +{ + return ((ifp->flags & IFF_UP) && + (((ifp->flags & IFF_RUNNING) +#ifdef IFF_LOWER_UP + && (ifp->flags & IFF_LOWER_UP) +#endif /* IFF_LOWER_UP */ + && (ifp->ptm_status || !ifp->ptm_enable)) || + !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION))); +} + +/* Is the interface operative, eg. either UP & RUNNING + or UP & !ZEBRA_INTERFACE_LINK_DETECTION, without PTM check */ +int if_is_no_ptm_operative(const struct interface *ifp) +{ + return ((ifp->flags & IFF_UP) && + (((ifp->flags & IFF_RUNNING) +#ifdef IFF_LOWER_UP + && (ifp->flags & IFF_LOWER_UP) +#endif /* IFF_LOWER_UP */ + ) || + !CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION))); +} + +/* Is this loopback interface ? */ +int if_is_loopback_exact(const struct interface *ifp) +{ + /* XXX: Do this better, eg what if IFF_WHATEVER means X on platform M + * but Y on platform N? + */ + return (ifp->flags & (IFF_LOOPBACK | IFF_NOXMIT | IFF_VIRTUAL)); +} + +/* Check interface is VRF */ +int if_is_vrf(const struct interface *ifp) +{ + return CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_VRF_LOOPBACK); +} + +/* Should this interface be treated as a loopback? */ +bool if_is_loopback(const struct interface *ifp) +{ + if (if_is_loopback_exact(ifp) || if_is_vrf(ifp)) + return true; + + return false; +} + +/* Does this interface support broadcast ? */ +int if_is_broadcast(const struct interface *ifp) +{ + return ifp->flags & IFF_BROADCAST; +} + +/* Does this interface support pointopoint ? */ +int if_is_pointopoint(const struct interface *ifp) +{ + return ifp->flags & IFF_POINTOPOINT; +} + +/* Does this interface support multicast ? */ +int if_is_multicast(const struct interface *ifp) +{ + return ifp->flags & IFF_MULTICAST; +} + +/* Printout flag information into log */ +const char *if_flag_dump(unsigned long flag) +{ + int separator = 0; + static char logbuf[BUFSIZ]; + +#define IFF_OUT_LOG(X, STR) \ + if (flag & (X)) { \ + if (separator) \ + strlcat(logbuf, ",", sizeof(logbuf)); \ + else \ + separator = 1; \ + strlcat(logbuf, STR, sizeof(logbuf)); \ + } + + strlcpy(logbuf, "<", BUFSIZ); + IFF_OUT_LOG(IFF_UP, "UP"); +#ifdef IFF_LOWER_UP + IFF_OUT_LOG(IFF_LOWER_UP, "LOWER_UP"); +#endif /* IFF_LOWER_UP */ + IFF_OUT_LOG(IFF_BROADCAST, "BROADCAST"); + IFF_OUT_LOG(IFF_DEBUG, "DEBUG"); + IFF_OUT_LOG(IFF_LOOPBACK, "LOOPBACK"); + IFF_OUT_LOG(IFF_POINTOPOINT, "POINTOPOINT"); + IFF_OUT_LOG(IFF_NOTRAILERS, "NOTRAILERS"); + IFF_OUT_LOG(IFF_RUNNING, "RUNNING"); + IFF_OUT_LOG(IFF_NOARP, "NOARP"); + IFF_OUT_LOG(IFF_PROMISC, "PROMISC"); + IFF_OUT_LOG(IFF_ALLMULTI, "ALLMULTI"); + IFF_OUT_LOG(IFF_OACTIVE, "OACTIVE"); + IFF_OUT_LOG(IFF_SIMPLEX, "SIMPLEX"); + IFF_OUT_LOG(IFF_LINK0, "LINK0"); + IFF_OUT_LOG(IFF_LINK1, "LINK1"); + IFF_OUT_LOG(IFF_LINK2, "LINK2"); + IFF_OUT_LOG(IFF_MULTICAST, "MULTICAST"); + IFF_OUT_LOG(IFF_NOXMIT, "NOXMIT"); + IFF_OUT_LOG(IFF_NORTEXCH, "NORTEXCH"); + IFF_OUT_LOG(IFF_VIRTUAL, "VIRTUAL"); + IFF_OUT_LOG(IFF_IPV4, "IPv4"); + IFF_OUT_LOG(IFF_IPV6, "IPv6"); + + strlcat(logbuf, ">", sizeof(logbuf)); + + return logbuf; +#undef IFF_OUT_LOG +} + +/* For debugging */ +static void if_dump(const struct interface *ifp) +{ + const struct connected *c; + + frr_each (if_connected_const, ifp->connected, c) + zlog_info( + "Interface %s vrf %s(%u) index %d metric %d mtu %d mtu6 %d %s", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, ifp->metric, ifp->mtu, ifp->mtu6, + if_flag_dump(ifp->flags)); +} + +/* Interface printing for all interface. */ +void if_dump_all(void) +{ + struct vrf *vrf; + void *ifp; + + RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) + FOR_ALL_INTERFACES (vrf, ifp) + if_dump(ifp); +} + +/* Allocate connected structure. */ +struct connected *connected_new(void) +{ + return XCALLOC(MTYPE_CONNECTED, sizeof(struct connected)); +} + +/* Allocate nbr connected structure. */ +struct nbr_connected *nbr_connected_new(void) +{ + return XCALLOC(MTYPE_NBR_CONNECTED, sizeof(struct nbr_connected)); +} + +/* Free connected structure. */ +void connected_free(struct connected **connected) +{ + struct connected *ptr = *connected; + + prefix_free(&ptr->address); + prefix_free(&ptr->destination); + + XFREE(MTYPE_CONNECTED_LABEL, ptr->label); + + XFREE(MTYPE_CONNECTED, ptr); + *connected = NULL; +} + +/* Free nbr connected structure. */ +void nbr_connected_free(struct nbr_connected *connected) +{ + if (connected->address) + prefix_free(&connected->address); + + XFREE(MTYPE_NBR_CONNECTED, connected); +} + +/* If same interface nbr address already exists... */ +struct nbr_connected *nbr_connected_check(struct interface *ifp, + struct prefix *p) +{ + struct nbr_connected *ifc; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, ifc)) + if (prefix_same(ifc->address, p)) + return ifc; + + return NULL; +} + +/* Print if_addr structure. */ +static void __attribute__((unused)) +connected_log(struct connected *connected, char *str) +{ + struct prefix *p; + struct interface *ifp; + char logbuf[BUFSIZ]; + char buf[BUFSIZ]; + + ifp = connected->ifp; + p = connected->address; + + snprintf(logbuf, sizeof(logbuf), "%s interface %s vrf %s(%u) %s %pFX ", + str, ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + prefix_family_str(p), p); + + p = connected->destination; + if (p) { + strlcat(logbuf, inet_ntop(p->family, &p->u.prefix, buf, BUFSIZ), + BUFSIZ); + } + zlog_info("%s", logbuf); +} + +/* Print if_addr structure. */ +static void __attribute__((unused)) +nbr_connected_log(struct nbr_connected *connected, char *str) +{ + struct prefix *p; + struct interface *ifp; + char logbuf[BUFSIZ]; + + ifp = connected->ifp; + p = connected->address; + + snprintf(logbuf, sizeof(logbuf), "%s interface %s %s %pFX ", str, + ifp->name, prefix_family_str(p), p); + + zlog_info("%s", logbuf); +} + +/* count the number of connected addresses that are in the given family */ +unsigned int connected_count_by_family(struct interface *ifp, int family) +{ + struct connected *connected; + unsigned int cnt = 0; + + frr_each (if_connected, ifp->connected, connected) + if (connected->address->family == family) + cnt++; + + return cnt; +} + +struct connected *connected_lookup_prefix_exact(struct interface *ifp, + const struct prefix *p) +{ + struct connected *ifc; + + frr_each (if_connected, ifp->connected, ifc) { + if (prefix_same(ifc->address, p)) + return ifc; + } + return NULL; +} + +struct connected *connected_delete_by_prefix(struct interface *ifp, + struct prefix *p) +{ + struct connected *ifc; + + /* In case of same prefix come, replace it with new one. */ + frr_each_safe (if_connected, ifp->connected, ifc) { + if (prefix_same(ifc->address, p)) { + if_connected_del(ifp->connected, ifc); + return ifc; + } + } + return NULL; +} + +/* Find the address on our side that will be used when packets + are sent to dst. */ +struct connected *connected_lookup_prefix(struct interface *ifp, + const struct prefix *addr) +{ + struct connected *c; + struct connected *match; + + match = NULL; + + frr_each (if_connected, ifp->connected, c) { + if (c->address && (c->address->family == addr->family) + && prefix_match(CONNECTED_PREFIX(c), addr) + && (!match + || (c->address->prefixlen > match->address->prefixlen))) + match = c; + } + return match; +} + +struct connected *connected_add_by_prefix(struct interface *ifp, + struct prefix *p, + struct prefix *destination) +{ + struct connected *ifc; + + /* Allocate new connected address. */ + ifc = connected_new(); + ifc->ifp = ifp; + + /* Fetch interface address */ + ifc->address = prefix_new(); + memcpy(ifc->address, p, sizeof(struct prefix)); + + /* Fetch dest address */ + if (destination) { + ifc->destination = prefix_new(); + memcpy(ifc->destination, destination, sizeof(struct prefix)); + } + + /* Add connected address to the interface. */ + if_connected_add_tail(ifp->connected, ifc); + return ifc; +} + +struct connected *connected_get_linklocal(struct interface *ifp) +{ + struct connected *c = NULL; + + frr_each (if_connected, ifp->connected, c) { + if (c->address->family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) + break; + } + return c; +} + +void if_terminate(struct vrf *vrf) +{ + struct interface *ifp; + + while (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name)) { + ifp = RB_ROOT(if_name_head, &vrf->ifaces_by_name); + + if (ifp->node) { + ifp->node->info = NULL; + route_unlock_node(ifp->node); + ifp->node = NULL; + } + if_delete(&ifp); + } +} + +const char *if_link_type_str(enum zebra_link_type llt) +{ + switch (llt) { +#define llts(T,S) case (T): return (S) + llts(ZEBRA_LLT_UNKNOWN, "Unknown"); + llts(ZEBRA_LLT_ETHER, "Ethernet"); + llts(ZEBRA_LLT_EETHER, "Experimental Ethernet"); + llts(ZEBRA_LLT_AX25, "AX.25 Level 2"); + llts(ZEBRA_LLT_PRONET, "PROnet token ring"); + llts(ZEBRA_LLT_IEEE802, "IEEE 802.2 Ethernet/TR/TB"); + llts(ZEBRA_LLT_ARCNET, "ARCnet"); + llts(ZEBRA_LLT_APPLETLK, "AppleTalk"); + llts(ZEBRA_LLT_DLCI, "Frame Relay DLCI"); + llts(ZEBRA_LLT_ATM, "ATM"); + llts(ZEBRA_LLT_METRICOM, "Metricom STRIP"); + llts(ZEBRA_LLT_IEEE1394, "IEEE 1394 IPv4"); + llts(ZEBRA_LLT_EUI64, "EUI-64"); + llts(ZEBRA_LLT_INFINIBAND, "InfiniBand"); + llts(ZEBRA_LLT_SLIP, "SLIP"); + llts(ZEBRA_LLT_CSLIP, "Compressed SLIP"); + llts(ZEBRA_LLT_SLIP6, "SLIPv6"); + llts(ZEBRA_LLT_CSLIP6, "Compressed SLIPv6"); + llts(ZEBRA_LLT_RSRVD, "Reserved"); + llts(ZEBRA_LLT_ADAPT, "Adapt"); + llts(ZEBRA_LLT_ROSE, "ROSE packet radio"); + llts(ZEBRA_LLT_X25, "CCITT X.25"); + llts(ZEBRA_LLT_PPP, "PPP"); + llts(ZEBRA_LLT_CHDLC, "Cisco HDLC"); + llts(ZEBRA_LLT_RAWHDLC, "Raw HDLC"); + llts(ZEBRA_LLT_LAPB, "LAPB"); + llts(ZEBRA_LLT_IPIP, "IPIP Tunnel"); + llts(ZEBRA_LLT_IPIP6, "IPIP6 Tunnel"); + llts(ZEBRA_LLT_FRAD, "FRAD"); + llts(ZEBRA_LLT_SKIP, "SKIP vif"); + llts(ZEBRA_LLT_LOOPBACK, "Loopback"); + llts(ZEBRA_LLT_LOCALTLK, "Localtalk"); + llts(ZEBRA_LLT_FDDI, "FDDI"); + llts(ZEBRA_LLT_SIT, "IPv6-in-IPv4 SIT"); + llts(ZEBRA_LLT_IPDDP, "IP-in-DDP tunnel"); + llts(ZEBRA_LLT_IPGRE, "GRE over IP"); + llts(ZEBRA_LLT_IP6GRE, "GRE over IPv6"); + llts(ZEBRA_LLT_PIMREG, "PIMSM registration"); + llts(ZEBRA_LLT_HIPPI, "HiPPI"); + llts(ZEBRA_LLT_ECONET, "Acorn Econet"); + llts(ZEBRA_LLT_IRDA, "IrDA"); + llts(ZEBRA_LLT_FCPP, "Fibre-Channel PtP"); + llts(ZEBRA_LLT_FCAL, "Fibre-Channel Arbitrated Loop"); + llts(ZEBRA_LLT_FCPL, "Fibre-Channel Public Loop"); + llts(ZEBRA_LLT_FCFABRIC, "Fibre-Channel Fabric"); + llts(ZEBRA_LLT_IEEE802_TR, "IEEE 802.2 Token Ring"); + llts(ZEBRA_LLT_IEEE80211, "IEEE 802.11"); + llts(ZEBRA_LLT_IEEE80211_RADIOTAP, "IEEE 802.11 Radiotap"); + llts(ZEBRA_LLT_IEEE802154, "IEEE 802.15.4"); + llts(ZEBRA_LLT_IEEE802154_PHY, "IEEE 802.15.4 Phy"); +#undef llts + } + return NULL; +} + +bool if_link_params_cmp(struct if_link_params *iflp1, + struct if_link_params *iflp2) +{ + struct if_link_params iflp1_copy, iflp2_copy; + + /* Extended admin-groups in if_link_params contain pointers. + * They cannot be compared with memcpy. + * Make copies of if_link_params without ext. admin-groups + * and compare separately the ext. admin-groups. + */ + memcpy(&iflp1_copy, iflp1, sizeof(struct if_link_params)); + memset(&iflp1_copy.ext_admin_grp, 0, sizeof(struct admin_group)); + + memcpy(&iflp2_copy, iflp2, sizeof(struct if_link_params)); + memset(&iflp2_copy.ext_admin_grp, 0, sizeof(struct admin_group)); + + if (memcmp(&iflp1_copy, &iflp2_copy, sizeof(struct if_link_params))) + return false; + + if (!admin_group_cmp(&iflp1->ext_admin_grp, &iflp2->ext_admin_grp)) + return false; + + return true; +} + +void if_link_params_copy(struct if_link_params *dst, struct if_link_params *src) +{ + struct admin_group dst_ag; + + /* backup the admin_group structure that contains a pointer */ + memcpy(&dst_ag, &dst->ext_admin_grp, sizeof(struct admin_group)); + /* copy the if_link_params structure */ + memcpy(dst, src, sizeof(struct if_link_params)); + /* restore the admin_group structure */ + memcpy(&dst->ext_admin_grp, &dst_ag, sizeof(struct admin_group)); + /* copy src->ext_admin_grp data to dst->ext_admin_grp data memory */ + admin_group_copy(&dst->ext_admin_grp, &src->ext_admin_grp); +} + +struct if_link_params *if_link_params_get(struct interface *ifp) +{ + return ifp->link_params; +} + +struct if_link_params *if_link_params_enable(struct interface *ifp) +{ + struct if_link_params *iflp; + int i; + + iflp = if_link_params_init(ifp); + + /* Compute default bandwidth based on interface */ + iflp->default_bw = + ((ifp->bandwidth ? ifp->bandwidth : DEFAULT_BANDWIDTH) + * TE_MEGA_BIT / TE_BYTE); + + /* Set Max, Reservable and Unreserved Bandwidth */ + iflp->max_bw = iflp->default_bw; + iflp->max_rsv_bw = iflp->default_bw; + for (i = 0; i < MAX_CLASS_TYPE; i++) + iflp->unrsv_bw[i] = iflp->default_bw; + + /* Update Link parameters status */ + iflp->lp_status = LP_MAX_BW | LP_MAX_RSV_BW | LP_UNRSV_BW; + + /* Set TE metric equal to standard metric only if it is set */ + if (ifp->metric != 0) { + iflp->te_metric = ifp->metric; + iflp->lp_status |= LP_TE_METRIC; + } + + /* Finally attach newly created Link Parameters */ + ifp->link_params = iflp; + + return iflp; +} + +struct if_link_params *if_link_params_init(struct interface *ifp) +{ + struct if_link_params *iflp = if_link_params_get(ifp); + + if (iflp) + return iflp; + + iflp = XCALLOC(MTYPE_IF_LINK_PARAMS, sizeof(struct if_link_params)); + + admin_group_init(&iflp->ext_admin_grp); + + ifp->link_params = iflp; + + return iflp; +} + +void if_link_params_free(struct interface *ifp) +{ + if (!ifp->link_params) + return; + + admin_group_term(&ifp->link_params->ext_admin_grp); + XFREE(MTYPE_IF_LINK_PARAMS, ifp->link_params); +} + +/* ----------- CLI commands ----------- */ + +/* Guess the VRF of an interface. */ +static int vrfname_by_ifname(const char *ifname, const char **vrfname) +{ + struct vrf *vrf; + struct interface *ifp; + int count = 0; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + if (strmatch(ifp->name, ifname)) { + *vrfname = vrf->name; + count++; + } + } + } + + return count; +} + +/* + * XPath: /frr-interface:lib/interface + */ +DEFPY_YANG_NOSH (interface, + interface_cmd, + "interface IFNAME [vrf NAME$vrf_name]", + "Select an interface to configure\n" + "Interface's name\n" + VRF_CMD_HELP_STR) +{ + char xpath_list[XPATH_MAXLEN]; + struct interface *ifp; + struct vrf *vrf; + int ret, count; + + if (vrf_is_backend_netns()) { + /* + * For backward compatibility, if the VRF name is not specified + * and there is exactly one interface with this name in the + * system, use its VRF. Otherwise fallback to the default VRF. + */ + if (!vrf_name) { + count = vrfname_by_ifname(ifname, &vrf_name); + if (count != 1) + vrf_name = VRF_DEFAULT_NAME; + } + + snprintf(xpath_list, XPATH_MAXLEN, + "/frr-interface:lib/interface[name='%s:%s']", vrf_name, + ifname); + } else { + snprintf(xpath_list, XPATH_MAXLEN, + "/frr-interface:lib/interface[name='%s']", ifname); + } + + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + ret = nb_cli_apply_changes_clear_pending(vty, "%s", xpath_list); + if (ret == CMD_SUCCESS) { + VTY_PUSH_XPATH(INTERFACE_NODE, xpath_list); + + /* + * For backward compatibility with old commands we still need + * to use the qobj infrastructure. This can be removed once + * all interface-level commands are converted to the new + * northbound model. + */ + if (vrf_is_backend_netns()) { + vrf = vrf_lookup_by_name(vrf_name); + if (vrf) + ifp = if_lookup_by_name_vrf(ifname, vrf); + else + ifp = NULL; + } else { + ifp = if_lookup_by_name_all_vrf(ifname); + } + if (ifp) + VTY_PUSH_CONTEXT(INTERFACE_NODE, ifp); + } + + return ret; +} + +DEFPY_YANG (no_interface, + no_interface_cmd, + "no interface IFNAME [vrf NAME$vrf_name]", + NO_STR + "Delete a pseudo interface's configuration\n" + "Interface's name\n" + VRF_CMD_HELP_STR) +{ + char xpath_list[XPATH_MAXLEN]; + int count; + + if (vrf_is_backend_netns()) { + /* + * For backward compatibility, if the VRF name is not specified + * and there is exactly one interface with this name in the + * system, use its VRF. Otherwise fallback to the default VRF. + */ + if (!vrf_name) { + count = vrfname_by_ifname(ifname, &vrf_name); + if (count != 1) + vrf_name = VRF_DEFAULT_NAME; + } + + snprintf(xpath_list, XPATH_MAXLEN, + "/frr-interface:lib/interface[name='%s:%s']", vrf_name, + ifname); + } else { + snprintf(xpath_list, XPATH_MAXLEN, + "/frr-interface:lib/interface[name='%s']", ifname); + } + + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "%s", xpath_list); +} + +static void netns_ifname_split(const char *xpath, char *ifname, char *vrfname) +{ + char *delim; + int len; + + assert(vrf_is_backend_netns()); + + delim = strchr(xpath, ':'); + assert(delim); + + len = delim - xpath; + memcpy(vrfname, xpath, len); + vrfname[len] = 0; + + strlcpy(ifname, delim + 1, XPATH_MAXLEN); +} + +static void cli_show_interface(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, "!\n"); + + if (vrf_is_backend_netns()) { + char ifname[XPATH_MAXLEN]; + char vrfname[XPATH_MAXLEN]; + + netns_ifname_split(yang_dnode_get_string(dnode, "name"), + ifname, vrfname); + + vty_out(vty, "interface %s", ifname); + if (!strmatch(vrfname, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", vrfname); + } else { + const char *ifname = yang_dnode_get_string(dnode, "name"); + + vty_out(vty, "interface %s", ifname); + } + + vty_out(vty, "\n"); +} + +static void cli_show_interface_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + +static int cli_cmp_interface(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + const char *ifname1 = yang_dnode_get_string(dnode1, "name"); + const char *ifname2 = yang_dnode_get_string(dnode2, "name"); + + return if_cmp_name_func(ifname1, ifname2); +} + +void if_vty_config_start(struct vty *vty, struct interface *ifp) +{ + vty_frame(vty, "!\n"); + vty_frame(vty, "interface %s", ifp->name); + + if (vrf_is_backend_netns() && strcmp(ifp->vrf->name, VRF_DEFAULT_NAME)) + vty_frame(vty, " vrf %s", ifp->vrf->name); + + vty_frame(vty, "\n"); +} + +void if_vty_config_end(struct vty *vty) +{ + vty_endframe(vty, "exit\n!\n"); +} + +/* + * XPath: /frr-interface:lib/interface/description + */ +DEFPY_YANG (interface_desc, + interface_desc_cmd, + "description LINE...", + "Interface specific description\n" + "Characters describing this interface\n") +{ + char *desc; + int ret; + + desc = argv_concat(argv, argc, 1); + nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc); + ret = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, desc); + + return ret; +} + +DEFPY_YANG (no_interface_desc, + no_interface_desc_cmd, + "no description", + NO_STR + "Interface specific description\n") +{ + nb_cli_enqueue_change(vty, "./description", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +static void cli_show_interface_desc(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " description %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* Interface autocomplete. */ +static void if_autocomplete(vector comps, struct cmd_token *token) +{ + struct interface *ifp; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, ifp->name)); + } + } +} + +static const struct cmd_variable_handler if_var_handlers[] = { + {/* "interface NAME" */ + .varname = "interface", + .completions = if_autocomplete}, + {.tokenname = "IFNAME", .completions = if_autocomplete}, + {.tokenname = "INTERFACE", .completions = if_autocomplete}, + {.completions = NULL}}; + +static struct cmd_node interface_node = { + .name = "interface", + .node = INTERFACE_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-if)# ", +}; + +static int if_config_write_single(const struct lyd_node *dnode, void *arg) +{ + nb_cli_show_dnode_cmds(arg, dnode, false); + + return YANG_ITER_CONTINUE; +} + +static int if_nb_config_write(struct vty *vty) +{ + yang_dnode_iterate(if_config_write_single, vty, running_config->dnode, + "/frr-interface:lib/interface"); + return 1; +} + +void if_cmd_init(int (*config_write)(struct vty *)) +{ + cmd_variable_handler_register(if_var_handlers); + + interface_node.config_write = config_write; + install_node(&interface_node); + + install_element(CONFIG_NODE, &interface_cmd); + install_element(CONFIG_NODE, &no_interface_cmd); + + install_default(INTERFACE_NODE); + install_element(INTERFACE_NODE, &interface_desc_cmd); + install_element(INTERFACE_NODE, &no_interface_desc_cmd); +} + +void if_cmd_init_default(void) +{ + if_cmd_init(if_nb_config_write); +} + +/* ------- Northbound callbacks ------- */ + +/* + * XPath: /frr-interface:lib/interface + */ +static int lib_interface_create(struct nb_cb_create_args *args) +{ + const char *ifname; + struct interface *ifp; + + ifname = yang_dnode_get_string(args->dnode, "name"); + + switch (args->event) { + case NB_EV_VALIDATE: + if (vrf_is_backend_netns()) { + char ifname_ns[XPATH_MAXLEN]; + char vrfname_ns[XPATH_MAXLEN]; + + netns_ifname_split(ifname, ifname_ns, vrfname_ns); + + if (strlen(ifname_ns) > 16) { + snprintf( + args->errmsg, args->errmsg_len, + "Maximum interface name length is 16 characters"); + return NB_ERR_VALIDATION; + } + if (strlen(vrfname_ns) > 36) { + snprintf( + args->errmsg, args->errmsg_len, + "Maximum VRF name length is 36 characters"); + return NB_ERR_VALIDATION; + } + } else { + if (strlen(ifname) > 16) { + snprintf( + args->errmsg, args->errmsg_len, + "Maximum interface name length is 16 characters"); + return NB_ERR_VALIDATION; + } + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (vrf_is_backend_netns()) { + char ifname_ns[XPATH_MAXLEN]; + char vrfname_ns[XPATH_MAXLEN]; + + netns_ifname_split(ifname, ifname_ns, vrfname_ns); + + ifp = if_get_by_name(ifname_ns, VRF_UNKNOWN, + vrfname_ns); + } else { + ifp = if_get_by_name(ifname, VRF_UNKNOWN, + VRF_DEFAULT_NAME); + } + + ifp->configured = true; + nb_running_set_entry(args->dnode, ifp); + break; + } + + return NB_OK; +} + +static int lib_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, true); + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { + snprintf(args->errmsg, args->errmsg_len, + "only inactive interfaces can be deleted"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_unset_entry(args->dnode); + vrf = ifp->vrf; + + ifp->configured = false; + if_delete(&ifp); + + if (!vrf_is_enabled(vrf)) + vrf_delete(vrf); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface + */ +static const void *lib_interface_get_next(struct nb_cb_get_next_args *args) +{ + struct vrf *vrf; + struct interface *pif = (struct interface *)args->list_entry; + + if (args->list_entry == NULL) { + vrf = RB_MIN(vrf_name_head, &vrfs_by_name); + assert(vrf); + pif = RB_MIN(if_name_head, &vrf->ifaces_by_name); + } else { + vrf = pif->vrf; + pif = RB_NEXT(if_name_head, pif); + /* if no more interfaces, switch to next vrf */ + while (pif == NULL) { + vrf = RB_NEXT(vrf_name_head, vrf); + if (!vrf) + return NULL; + pif = RB_MIN(if_name_head, &vrf->ifaces_by_name); + } + } + + return pif; +} + +static int lib_interface_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct interface *ifp = args->list_entry; + + args->keys->num = 1; + + if (vrf_is_backend_netns()) + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), + "%s:%s", ifp->vrf->name, ifp->name); + else + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%s", + ifp->name); + + return NB_OK; +} + +static const void * +lib_interface_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + if (vrf_is_backend_netns()) { + char ifname[XPATH_MAXLEN]; + char vrfname[XPATH_MAXLEN]; + struct vrf *vrf; + + netns_ifname_split(args->keys->key[0], ifname, vrfname); + + vrf = vrf_lookup_by_name(vrfname); + + return vrf ? if_lookup_by_name(ifname, vrf->vrf_id) : NULL; + } else { + return if_lookup_by_name_all_vrf(args->keys->key[0]); + } +} + +/* + * XPath: /frr-interface:lib/interface/description + */ +static int lib_interface_description_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + const char *description; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_IFDESC, ifp->desc); + description = yang_dnode_get_string(args->dnode, NULL); + ifp->desc = XSTRDUP(MTYPE_IFDESC, description); + + return NB_OK; +} + +static int lib_interface_description_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_IFDESC, ifp->desc); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/vrf + */ +static struct yang_data * +lib_interface_vrf_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_string(args->xpath, ifp->vrf->name); +} + +/* + * XPath: /frr-interface:lib/interface/state/if-index + */ +static struct yang_data * +lib_interface_state_if_index_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_int32(args->xpath, ifp->ifindex); +} + +/* + * XPath: /frr-interface:lib/interface/state/mtu + */ +static struct yang_data * +lib_interface_state_mtu_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_uint32(args->xpath, ifp->mtu); +} + +/* + * XPath: /frr-interface:lib/interface/state/mtu6 + */ +static struct yang_data * +lib_interface_state_mtu6_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_uint32(args->xpath, ifp->mtu6); +} + +/* + * XPath: /frr-interface:lib/interface/state/speed + */ +static struct yang_data * +lib_interface_state_speed_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_uint32(args->xpath, ifp->speed); +} + +/* + * XPath: /frr-interface:lib/interface/state/metric + */ +static struct yang_data * +lib_interface_state_metric_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + + return yang_data_new_uint32(args->xpath, ifp->metric); +} + +/* + * XPath: /frr-interface:lib/interface/state/flags + */ +static struct yang_data * +lib_interface_state_flags_get_elem(struct nb_cb_get_elem_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + +/* + * XPath: /frr-interface:lib/interface/state/type + */ +static struct yang_data * +lib_interface_state_type_get_elem(struct nb_cb_get_elem_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + +/* + * XPath: /frr-interface:lib/interface/state/phy-address + */ +static struct yang_data * +lib_interface_state_phy_address_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct interface *ifp = args->list_entry; + struct ethaddr macaddr; + + memcpy(&macaddr.octet, ifp->hw_addr, ETH_ALEN); + + return yang_data_new_mac(args->xpath, &macaddr); +} + +/* clang-format off */ + +/* cli_show callbacks are kept here for daemons not yet converted to mgmtd */ +const struct frr_yang_module_info frr_interface_info = { + .name = "frr-interface", + .nodes = { + { + .xpath = "/frr-interface:lib/interface", + .cbs = { + .create = lib_interface_create, + .destroy = lib_interface_destroy, + .cli_show = cli_show_interface, + .cli_show_end = cli_show_interface_end, + .cli_cmp = cli_cmp_interface, + .get_next = lib_interface_get_next, + .get_keys = lib_interface_get_keys, + .lookup_entry = lib_interface_lookup_entry, + }, + }, + { + .xpath = "/frr-interface:lib/interface/description", + .cbs = { + .modify = lib_interface_description_modify, + .destroy = lib_interface_description_destroy, + .cli_show = cli_show_interface_desc, + }, + }, + { + .xpath = "/frr-interface:lib/interface/vrf", + .cbs = { + .get_elem = lib_interface_vrf_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/if-index", + .cbs = { + .get_elem = lib_interface_state_if_index_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/mtu", + .cbs = { + .get_elem = lib_interface_state_mtu_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/mtu6", + .cbs = { + .get_elem = lib_interface_state_mtu6_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/speed", + .cbs = { + .get_elem = lib_interface_state_speed_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/metric", + .cbs = { + .get_elem = lib_interface_state_metric_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/flags", + .cbs = { + .get_elem = lib_interface_state_flags_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/type", + .cbs = { + .get_elem = lib_interface_state_type_get_elem, + } + }, + { + .xpath = "/frr-interface:lib/interface/state/phy-address", + .cbs = { + .get_elem = lib_interface_state_phy_address_get_elem, + } + }, + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_interface_cli_info = { + .name = "frr-interface", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-interface:lib/interface", + .cbs = { + .cli_show = cli_show_interface, + .cli_show_end = cli_show_interface_end, + .cli_cmp = cli_cmp_interface, + }, + }, + { + .xpath = "/frr-interface:lib/interface/description", + .cbs = { + .cli_show = cli_show_interface_desc, + }, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/lib/if.h b/lib/if.h new file mode 100644 index 0000000..0dc56bd --- /dev/null +++ b/lib/if.h @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Interface related header. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_IF_H +#define _ZEBRA_IF_H + +#include "zebra.h" +#include "linklist.h" +#include "memory.h" +#include "qobj.h" +#include "hook.h" +#include "admin_group.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(CONNECTED_LABEL); + +/* Interface link-layer type, if known. Derived from: + * + * net/if_arp.h on various platforms - Linux especially. + * http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + * + * Some of the more obviously defunct technologies left out. + */ +enum zebra_link_type { + ZEBRA_LLT_UNKNOWN = 0, + ZEBRA_LLT_ETHER, + ZEBRA_LLT_EETHER, + ZEBRA_LLT_AX25, + ZEBRA_LLT_PRONET, + ZEBRA_LLT_IEEE802, + ZEBRA_LLT_ARCNET, + ZEBRA_LLT_APPLETLK, + ZEBRA_LLT_DLCI, + ZEBRA_LLT_ATM, + ZEBRA_LLT_METRICOM, + ZEBRA_LLT_IEEE1394, + ZEBRA_LLT_EUI64, + ZEBRA_LLT_INFINIBAND, + ZEBRA_LLT_SLIP, + ZEBRA_LLT_CSLIP, + ZEBRA_LLT_SLIP6, + ZEBRA_LLT_CSLIP6, + ZEBRA_LLT_RSRVD, + ZEBRA_LLT_ADAPT, + ZEBRA_LLT_ROSE, + ZEBRA_LLT_X25, + ZEBRA_LLT_PPP, + ZEBRA_LLT_CHDLC, + ZEBRA_LLT_LAPB, + ZEBRA_LLT_RAWHDLC, + ZEBRA_LLT_IPIP, + ZEBRA_LLT_IPIP6, + ZEBRA_LLT_FRAD, + ZEBRA_LLT_SKIP, + ZEBRA_LLT_LOOPBACK, + ZEBRA_LLT_LOCALTLK, + ZEBRA_LLT_FDDI, + ZEBRA_LLT_SIT, + ZEBRA_LLT_IPDDP, + ZEBRA_LLT_IPGRE, + ZEBRA_LLT_IP6GRE, + ZEBRA_LLT_PIMREG, + ZEBRA_LLT_HIPPI, + ZEBRA_LLT_ECONET, + ZEBRA_LLT_IRDA, + ZEBRA_LLT_FCPP, + ZEBRA_LLT_FCAL, + ZEBRA_LLT_FCPL, + ZEBRA_LLT_FCFABRIC, + ZEBRA_LLT_IEEE802_TR, + ZEBRA_LLT_IEEE80211, + ZEBRA_LLT_IEEE80211_RADIOTAP, + ZEBRA_LLT_IEEE802154, + ZEBRA_LLT_IEEE802154_PHY, +}; + +/* + Interface name length. + + Linux define value in /usr/include/linux/if.h. + #define IFNAMSIZ 16 + + FreeBSD define value in /usr/include/net/if.h. + #define IFNAMSIZ 16 +*/ +#define INTERFACE_HWADDR_MAX 20 + +typedef signed int ifindex_t; + +#ifdef HAVE_PROC_NET_DEV +struct if_stats { + unsigned long rx_packets; /* total packets received */ + unsigned long tx_packets; /* total packets transmitted */ + unsigned long rx_bytes; /* total bytes received */ + unsigned long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* no space in linux buffers */ + unsigned long tx_dropped; /* no space available in linux */ + unsigned long rx_multicast; /* multicast packets received */ + unsigned long rx_compressed; + unsigned long tx_compressed; + unsigned long collisions; + + /* detailed rx_errors: */ + unsigned long rx_length_errors; + unsigned long rx_over_errors; /* receiver ring buff overflow */ + unsigned long rx_crc_errors; /* recved pkt with crc error */ + unsigned long rx_frame_errors; /* recv'd frame alignment error */ + unsigned long rx_fifo_errors; /* recv'r fifo overrun */ + unsigned long rx_missed_errors; /* receiver missed packet */ + /* detailed tx_errors */ + unsigned long tx_aborted_errors; + unsigned long tx_carrier_errors; + unsigned long tx_fifo_errors; + unsigned long tx_heartbeat_errors; + unsigned long tx_window_errors; +}; +#endif /* HAVE_PROC_NET_DEV */ + +/* Here are "non-official" architectural constants. */ +#define TE_EXT_MASK 0x00FFFFFF +#define TE_EXT_ANORMAL 0x80000000 +#define LOSS_PRECISION 0.000003 +/* TE_MEGA_BIT and TE_BYTE are utilized to convert TE bandwidth */ +#define TE_MEGA_BIT 1000000 +#define TE_BYTE 8 +/* Default TE bandwidth when no value in config. + * The value is in Mbps (will be multiplied by TE_BYTE) + */ +#define DEFAULT_BANDWIDTH 10 +#define MAX_CLASS_TYPE 8 +#define MAX_PKT_LOSS 50.331642 + +enum affinity_mode { + /* RFC7308 Extended Administrative group */ + AFFINITY_MODE_EXTENDED = 0, + /* RFC3630/RFC5305/RFC5329 Administrative group */ + AFFINITY_MODE_STANDARD = 1, + /* Standard and Extended Administrative group */ + AFFINITY_MODE_BOTH = 2, +}; + +/* + * Link Parameters Status: + * equal to 0: unset + * different from 0: set + */ +#define LP_UNSET 0x0000 +#define LP_TE_METRIC 0x0001 +#define LP_MAX_BW 0x0002 +#define LP_MAX_RSV_BW 0x0004 +#define LP_UNRSV_BW 0x0008 +#define LP_ADM_GRP 0x0010 +#define LP_RMT_AS 0x0020 +#define LP_DELAY 0x0040 +#define LP_MM_DELAY 0x0080 +#define LP_DELAY_VAR 0x0100 +#define LP_PKT_LOSS 0x0200 +#define LP_RES_BW 0x0400 +#define LP_AVA_BW 0x0800 +#define LP_USE_BW 0x1000 +#define LP_EXTEND_ADM_GRP 0x2000 + +#define IS_PARAM_UNSET(lp, st) !(lp->lp_status & st) +#define IS_PARAM_SET(lp, st) (lp->lp_status & st) +#define IS_LINK_PARAMS_SET(lp) (lp->lp_status != LP_UNSET) + +#define SET_PARAM(lp, st) (lp->lp_status) |= (st) +#define UNSET_PARAM(lp, st) (lp->lp_status) &= ~(st) +#define RESET_LINK_PARAM(lp) (lp->lp_status = LP_UNSET) + +/* Link Parameters for Traffic Engineering + * Do not forget to update if_link_params_copy() + * and if_link_params_cmp() when updating the structure + */ +struct if_link_params { + uint32_t lp_status; /* Status of Link Parameters: */ + uint32_t te_metric; /* Traffic Engineering metric */ + float default_bw; + float max_bw; /* Maximum Bandwidth */ + float max_rsv_bw; /* Maximum Reservable Bandwidth */ + float unrsv_bw[MAX_CLASS_TYPE]; /* Unreserved Bandwidth per Class Type + (8) */ + uint32_t admin_grp; /* RFC5305/RFC5329 Administrative group */ + struct admin_group ext_admin_grp; /* RFC7308 Extended Admin group */ + uint32_t rmt_as; /* Remote AS number */ + struct in_addr rmt_ip; /* Remote IP address */ + uint32_t av_delay; /* Link Average Delay */ + uint32_t min_delay; /* Link Min Delay */ + uint32_t max_delay; /* Link Max Delay */ + uint32_t delay_var; /* Link Delay Variation */ + uint32_t pkt_loss; /* Link Packet Loss */ + float res_bw; /* Residual Bandwidth */ + float ava_bw; /* Available Bandwidth */ + float use_bw; /* Utilized Bandwidth */ +}; + +#define INTERFACE_LINK_PARAMS_SIZE sizeof(struct if_link_params) +#define HAS_LINK_PARAMS(ifp) ((ifp)->link_params != NULL) + +PREDECL_DLIST(if_connected); + +/* Interface structure */ +struct interface { + RB_ENTRY(interface) name_entry, index_entry; + + /* Interface name. This should probably never be changed after the + interface is created, because the configuration info for this + interface + is associated with this structure. For that reason, the interface + should also never be deleted (to avoid losing configuration info). + To delete, just set ifindex to IFINDEX_INTERNAL to indicate that the + interface does not exist in the kernel. + */ + char name[IFNAMSIZ]; + + /* Interface index (should be IFINDEX_INTERNAL for non-kernel or + deleted interfaces). + WARNING: the ifindex needs to be changed using the if_set_index() + function. Failure to respect this will cause corruption in the data + structure used to store the interfaces and if_lookup_by_index() will + not work as expected. + */ + ifindex_t ifindex; + ifindex_t oldifindex; + + /* + * ifindex of parent interface, if any + */ + ifindex_t link_ifindex; +#define IFINDEX_INTERNAL 0 + + /* Zebra internal interface status */ + uint8_t status; +#define ZEBRA_INTERFACE_ACTIVE (1 << 0) +#define ZEBRA_INTERFACE_SUB (1 << 1) +#define ZEBRA_INTERFACE_LINKDETECTION (1 << 2) +#define ZEBRA_INTERFACE_VRF_LOOPBACK (1 << 3) + + /* Interface flags. */ + uint64_t flags; + + /* Interface metric */ + uint32_t metric; + + /* Interface Speed in Mb/s */ + uint32_t speed; + + /* TX queue len */ + uint32_t txqlen; + + /* Interface MTU. */ + unsigned int mtu; /* IPv4 MTU */ + unsigned int + mtu6; /* IPv6 MTU - probably, but not necessarily same as mtu + */ + + /* Link-layer information and hardware address */ + enum zebra_link_type ll_type; + uint8_t hw_addr[INTERFACE_HWADDR_MAX]; + int hw_addr_len; + + /* interface bandwidth, kbits */ + unsigned int bandwidth; + + /* Link parameters for Traffic Engineering */ + struct if_link_params *link_params; + + /* description of the interface. */ + char *desc; + + /* Connected address list. */ + struct if_connected_head connected[1]; + + /* Neighbor connected address list. */ + struct list *nbr_connected; + + /* Daemon specific interface data pointer. */ + void *info; + + char ptm_enable; /* Should we look at ptm_status ? */ + char ptm_status; + +/* Statistics fileds. */ +#ifdef HAVE_PROC_NET_DEV + struct if_stats stats; +#endif /* HAVE_PROC_NET_DEV */ +#ifdef HAVE_NET_RT_IFLIST + struct if_data stats; +#endif /* HAVE_NET_RT_IFLIST */ + + struct route_node *node; + + struct vrf *vrf; + + /* + * Has the end users entered `interface XXXX` from the cli in some + * fashion? + */ + bool configured; + + QOBJ_FIELDS; +}; + +RB_HEAD(if_name_head, interface); +RB_PROTOTYPE(if_name_head, interface, name_entry, if_cmp_func) +RB_HEAD(if_index_head, interface); +RB_PROTOTYPE(if_index_head, interface, index_entry, if_cmp_index_func) +DECLARE_QOBJ_TYPE(interface); + +#define IFNAME_RB_INSERT(v, ifp) \ + ({ \ + struct interface *_iz = \ + RB_INSERT(if_name_head, &v->ifaces_by_name, (ifp)); \ + if (_iz) \ + flog_err( \ + EC_LIB_INTERFACE, \ + "%s(%s): corruption detected -- interface with this " \ + "name exists already in VRF %s!", \ + __func__, (ifp)->name, (ifp)->vrf->name); \ + _iz; \ + }) + +#define IFNAME_RB_REMOVE(v, ifp) \ + ({ \ + struct interface *_iz = \ + RB_REMOVE(if_name_head, &v->ifaces_by_name, (ifp)); \ + if (_iz == NULL) \ + flog_err( \ + EC_LIB_INTERFACE, \ + "%s(%s): corruption detected -- interface with this " \ + "name doesn't exist in VRF %s!", \ + __func__, (ifp)->name, (ifp)->vrf->name); \ + _iz; \ + }) + + +#define IFINDEX_RB_INSERT(v, ifp) \ + ({ \ + struct interface *_iz = \ + RB_INSERT(if_index_head, &v->ifaces_by_index, (ifp)); \ + if (_iz) \ + flog_err( \ + EC_LIB_INTERFACE, \ + "%s(%u): corruption detected -- interface with this " \ + "ifindex exists already in VRF %s!", \ + __func__, (ifp)->ifindex, (ifp)->vrf->name); \ + _iz; \ + }) + +#define IFINDEX_RB_REMOVE(v, ifp) \ + ({ \ + struct interface *_iz = \ + RB_REMOVE(if_index_head, &v->ifaces_by_index, (ifp)); \ + if (_iz == NULL) \ + flog_err( \ + EC_LIB_INTERFACE, \ + "%s(%u): corruption detected -- interface with this " \ + "ifindex doesn't exist in VRF %s!", \ + __func__, (ifp)->ifindex, (ifp)->vrf->name); \ + _iz; \ + }) + +#define FOR_ALL_INTERFACES(vrf, ifp) \ + if (vrf) \ + RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) + +/* called from the library code whenever interfaces are created/deleted + * note: interfaces may not be fully realized at that point; also they + * may not exist in the system (ifindex = IFINDEX_INTERNAL) + * + * priority values are important here, daemons should be at 0 while modules + * can use 1000+ so they run after the daemon has initialised daemon-specific + * interface data + */ +DECLARE_HOOK(if_add, (struct interface *ifp), (ifp)); +DECLARE_KOOH(if_del, (struct interface *ifp), (ifp)); + +/* called (in daemons) when ZAPI tells us the interface actually exists + * (ifindex != IFINDEX_INTERNAL) + * + * WARNING: these 2 hooks NEVER CALLED inside zebra! + */ +DECLARE_HOOK(if_real, (struct interface *ifp), (ifp)); +DECLARE_KOOH(if_unreal, (struct interface *ifp), (ifp)); + +/* called (in daemons) on state changes on interfaces. Whether this is admin + * state (= pure config) or carrier state (= hardware link plugged) depends on + * zebra's "link-detect" configuration. By default, it's carrier state, so + * this won't happen until the interface actually has a link. + * + * WARNING: these 2 hooks NEVER CALLED inside zebra! + */ +DECLARE_HOOK(if_up, (struct interface *ifp), (ifp)); +DECLARE_KOOH(if_down, (struct interface *ifp), (ifp)); + + +#define METRIC_MAX (~0) + +/* Connected address structure. */ +struct connected { + struct if_connected_item item; + + /* Attached interface. */ + struct interface *ifp; + + /* Flags for configuration. */ + uint8_t conf; +#define ZEBRA_IFC_REAL (1 << 0) +#define ZEBRA_IFC_CONFIGURED (1 << 1) +#define ZEBRA_IFC_QUEUED (1 << 2) +#define ZEBRA_IFC_DOWN (1 << 3) + /* + The ZEBRA_IFC_REAL flag should be set if and only if this address + exists in the kernel and is actually usable. (A case where it exists + but is not yet usable would be IPv6 with DAD) + The ZEBRA_IFC_CONFIGURED flag should be set if and only if this + address was configured by the user from inside frr. + The ZEBRA_IFC_QUEUED flag should be set if and only if the address + exists in the kernel. It may and should be set although the + address might not be usable yet. (compare with ZEBRA_IFC_REAL) + The ZEBRA_IFC_DOWN flag is used to record that an address is + present, but down/unavailable. + */ + + /* Flags for connected address. */ + uint8_t flags; +#define ZEBRA_IFA_SECONDARY (1 << 0) +#define ZEBRA_IFA_PEER (1 << 1) +#define ZEBRA_IFA_UNNUMBERED (1 << 2) +#define ZEBRA_IFA_NOPREFIXROUTE (1 << 3) + + /* N.B. the ZEBRA_IFA_PEER flag should be set if and only if + a peer address has been configured. If this flag is set, + the destination field must contain the peer address. + */ + + /* Address of connected network. */ + struct prefix *address; + + /* Peer address, if ZEBRA_IFA_PEER is set, otherwise NULL */ + struct prefix *destination; + + /* Label for Linux 2.2.X and upper. */ + char *label; + + /* + * Used for setting the connected route's cost. If the metric + * here is set to METRIC_MAX the connected route falls back to + * "struct interface" + */ + uint32_t metric; +}; + +DECLARE_DLIST(if_connected, struct connected, item); + +/* Nbr Connected address structure. */ +struct nbr_connected { + /* Attached interface. */ + struct interface *ifp; + + /* Address of connected network. */ + struct prefix *address; +}; + +/* Does the destination field contain a peer address? */ +#define CONNECTED_PEER(C) CHECK_FLAG((C)->flags, ZEBRA_IFA_PEER) + +/* Prefix to insert into the RIB */ +#define CONNECTED_PREFIX(C) \ + (CONNECTED_PEER(C) ? (C)->destination : (C)->address) + +/* Identifying address. We guess that if there's a peer address, but the + local address is in the same prefix, then the local address may be unique. */ +#define CONNECTED_ID(C) \ + ((CONNECTED_PEER(C) && !prefix_match((C)->destination, (C)->address)) \ + ? (C)->destination \ + : (C)->address) + +/* There are some interface flags which are only supported by some + operating system. */ + +#ifndef IFF_NOTRAILERS +#define IFF_NOTRAILERS 0x0 +#endif /* IFF_NOTRAILERS */ +#ifndef IFF_OACTIVE +#define IFF_OACTIVE 0x0 +#endif /* IFF_OACTIVE */ +#ifndef IFF_SIMPLEX +#define IFF_SIMPLEX 0x0 +#endif /* IFF_SIMPLEX */ +#ifndef IFF_LINK0 +#define IFF_LINK0 0x0 +#endif /* IFF_LINK0 */ +#ifndef IFF_LINK1 +#define IFF_LINK1 0x0 +#endif /* IFF_LINK1 */ +#ifndef IFF_LINK2 +#define IFF_LINK2 0x0 +#endif /* IFF_LINK2 */ +#ifndef IFF_NOXMIT +#define IFF_NOXMIT 0x0 +#endif /* IFF_NOXMIT */ +#ifndef IFF_NORTEXCH +#define IFF_NORTEXCH 0x0 +#endif /* IFF_NORTEXCH */ +#ifndef IFF_IPV4 +#define IFF_IPV4 0x0 +#endif /* IFF_IPV4 */ +#ifndef IFF_IPV6 +#define IFF_IPV6 0x0 +#endif /* IFF_IPV6 */ +#ifndef IFF_VIRTUAL +#define IFF_VIRTUAL 0x0 +#endif /* IFF_VIRTUAL */ + +/* Prototypes. */ +extern int if_cmp_name_func(const char *p1, const char *p2); + +/* + * Passing in VRF_UNKNOWN is a valid thing to do, unless we + * are creating a new interface. + * + * This is useful for vrf route-leaking. So more than anything + * else think before you use VRF_UNKNOWN + */ +extern void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id); + +extern struct interface *if_lookup_by_index(ifindex_t ifindex, vrf_id_t vrf_id); +extern struct interface *if_vrf_lookup_by_index_next(ifindex_t ifindex, + vrf_id_t vrf_id); +extern struct interface *if_lookup_address_local(const void *matchaddr, + int family, vrf_id_t vrf_id); +extern struct connected *if_lookup_address(const void *matchaddr, int family, + vrf_id_t vrf_id); +extern struct interface *if_lookup_prefix(const struct prefix *prefix, + vrf_id_t vrf_id); +size_t if_lookup_by_hwaddr(const uint8_t *hw_addr, size_t addrsz, + struct interface ***result, vrf_id_t vrf_id); + +static inline bool if_address_is_local(const void *matchaddr, int family, + vrf_id_t vrf_id) +{ + return if_lookup_address_local(matchaddr, family, vrf_id) != NULL; +} + +struct vrf; +extern struct interface *if_lookup_by_name_vrf(const char *name, struct vrf *vrf); +extern struct interface *if_lookup_by_name(const char *ifname, vrf_id_t vrf_id); +extern struct interface *if_get_vrf_loopback(vrf_id_t vrf_id); +extern struct interface *if_get_by_name(const char *ifname, vrf_id_t vrf_id, + const char *vrf_name); + +/* Sets the index and adds to index list */ +extern int if_set_index(struct interface *ifp, ifindex_t ifindex); + +/* Delete the interface, but do not free the structure, and leave it in the + interface list. It is often advisable to leave the pseudo interface + structure because there may be configuration information attached. */ +extern void if_delete_retain(struct interface *ifp); + +/* Delete and free the interface structure: calls if_delete_retain and then + deletes it from the interface list and frees the structure. */ +extern void if_delete(struct interface **ifp); + +extern int if_is_up(const struct interface *ifp); +extern int if_is_running(const struct interface *ifp); +extern int if_is_operative(const struct interface *ifp); +extern int if_is_no_ptm_operative(const struct interface *ifp); +extern int if_is_loopback_exact(const struct interface *ifp); +extern int if_is_vrf(const struct interface *ifp); +extern bool if_is_loopback(const struct interface *ifp); +extern int if_is_broadcast(const struct interface *ifp); +extern int if_is_pointopoint(const struct interface *ifp); +extern int if_is_multicast(const struct interface *ifp); +extern void if_terminate(struct vrf *vrf); +extern void if_dump_all(void); +extern const char *if_flag_dump(unsigned long flags); +extern const char *if_link_type_str(enum zebra_link_type zlt); + +/* Please use ifindex2ifname instead of if_indextoname where possible; + ifindex2ifname uses internal interface info, whereas if_indextoname must + make a system call. */ +extern const char *ifindex2ifname(ifindex_t ifindex, vrf_id_t vrf_id); + +/* Please use ifname2ifindex instead of if_nametoindex where possible; + ifname2ifindex uses internal interface info, whereas if_nametoindex must + make a system call. */ +extern ifindex_t ifname2ifindex(const char *ifname, vrf_id_t vrf_id); + +/* Connected address functions. */ +extern struct connected *connected_new(void); +extern void connected_free(struct connected **connected); +extern struct connected *connected_add_by_prefix(struct interface *ifp, + struct prefix *p, + struct prefix *dest); +extern struct connected *connected_delete_by_prefix(struct interface *ifp, + struct prefix *p); +extern struct connected *connected_lookup_prefix(struct interface *ifp, + const struct prefix *p); +extern struct connected *connected_lookup_prefix_exact(struct interface *ifp, + const struct prefix *p); +extern unsigned int connected_count_by_family(struct interface *ifp, int family); +extern struct nbr_connected *nbr_connected_new(void); +extern void nbr_connected_free(struct nbr_connected *connected); +struct nbr_connected *nbr_connected_check(struct interface *ifp, + struct prefix *p); +struct connected *connected_get_linklocal(struct interface *ifp); + +/* link parameters */ +bool if_link_params_cmp(struct if_link_params *iflp1, + struct if_link_params *iflp2); +void if_link_params_copy(struct if_link_params *dst, + struct if_link_params *src); +struct if_link_params *if_link_params_get(struct interface *ifp); +struct if_link_params *if_link_params_enable(struct interface *ifp); +struct if_link_params *if_link_params_init(struct interface *ifp); +void if_link_params_free(struct interface *ifp); + +/* Northbound. */ +struct vty; +extern void if_vty_config_start(struct vty *vty, struct interface *ifp); +extern void if_vty_config_end(struct vty *vty); +extern void if_cmd_init(int (*config_write)(struct vty *)); +extern void if_cmd_init_default(void); + +extern void if_new_via_zapi(struct interface *ifp); +extern void if_up_via_zapi(struct interface *ifp); +extern void if_down_via_zapi(struct interface *ifp); +extern void if_destroy_via_zapi(struct interface *ifp); + +extern const struct frr_yang_module_info frr_interface_info; +extern const struct frr_yang_module_info frr_interface_cli_info; + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_IF_H */ diff --git a/lib/if_rmap.c b/lib/if_rmap.c new file mode 100644 index 0000000..5fe5061 --- /dev/null +++ b/lib/if_rmap.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* route-map for interface. + * Copyright (C) 1999 Kunihiro Ishiguro + * Copyright (C) 2023 LabN Consulting, L.L.C. + */ + +#include + +#include "hash.h" +#include "command.h" +#include "memory.h" +#include "if.h" +#include "if_rmap.h" +#include "northbound_cli.h" + +#include "lib/if_rmap_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX, "Interface route map container"); +DEFINE_MTYPE_STATIC(LIB, IF_RMAP_CTX_NAME, + "Interface route map container name"); +DEFINE_MTYPE_STATIC(LIB, IF_RMAP, "Interface route map"); +DEFINE_MTYPE_STATIC(LIB, IF_RMAP_NAME, "I.f. route map name"); + +static struct if_rmap *if_rmap_new(void) +{ + struct if_rmap *new; + + new = XCALLOC(MTYPE_IF_RMAP, sizeof(struct if_rmap)); + + return new; +} + +static void if_rmap_free(struct if_rmap *if_rmap) +{ + char *no_const_ifname = (char *)if_rmap->ifname; + + XFREE(MTYPE_IF_RMAP_NAME, no_const_ifname); + + XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_IN]); + XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[IF_RMAP_OUT]); + + XFREE(MTYPE_IF_RMAP, if_rmap); +} + +struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx, const char *ifname) +{ + struct if_rmap key = {.ifname = ifname}; + struct if_rmap *if_rmap; + + if_rmap = hash_lookup(ctx->ifrmaphash, &key); + + return if_rmap; +} + +void if_rmap_hook_add(struct if_rmap_ctx *ctx, + void (*func)(struct if_rmap_ctx *ctx, struct if_rmap *)) +{ + ctx->if_rmap_add_hook = func; +} + +void if_rmap_hook_delete(struct if_rmap_ctx *ctx, + void (*func)(struct if_rmap_ctx *ctx, + struct if_rmap *)) +{ + ctx->if_rmap_delete_hook = func; +} + +static void *if_rmap_hash_alloc(void *arg) +{ + struct if_rmap *ifarg = (struct if_rmap *)arg; + struct if_rmap *if_rmap; + + if_rmap = if_rmap_new(); + if_rmap->ifname = XSTRDUP(MTYPE_IF_RMAP_NAME, ifarg->ifname); + + return if_rmap; +} + +static struct if_rmap *if_rmap_get(struct if_rmap_ctx *ctx, const char *ifname) +{ + struct if_rmap key = {.ifname = ifname}; + struct if_rmap *ret; + + ret = hash_get(ctx->ifrmaphash, &key, if_rmap_hash_alloc); + + return ret; +} + +static unsigned int if_rmap_hash_make(const void *data) +{ + const struct if_rmap *if_rmap = data; + + return string_hash_make(if_rmap->ifname); +} + +static bool if_rmap_hash_cmp(const void *arg1, const void *arg2) +{ + const struct if_rmap *if_rmap1 = arg1; + const struct if_rmap *if_rmap2 = arg2; + + return strcmp(if_rmap1->ifname, if_rmap2->ifname) == 0; +} + +static void if_rmap_set(struct if_rmap_ctx *ctx, const char *ifname, + enum if_rmap_type type, const char *routemap_name) +{ + struct if_rmap *if_rmap = if_rmap_get(ctx, ifname); + + assert(type == IF_RMAP_IN || type == IF_RMAP_OUT); + XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[type]); + if_rmap->routemap[type] = XSTRDUP(MTYPE_IF_RMAP_NAME, routemap_name); + + if (ctx->if_rmap_add_hook) + (ctx->if_rmap_add_hook)(ctx, if_rmap); +} + +static void if_rmap_unset(struct if_rmap_ctx *ctx, const char *ifname, + enum if_rmap_type type) +{ + struct if_rmap *if_rmap = if_rmap_lookup(ctx, ifname); + + if (!if_rmap) + return; + + assert(type == IF_RMAP_IN || type == IF_RMAP_OUT); + if (!if_rmap->routemap[type]) + return; + + XFREE(MTYPE_IF_RMAP_NAME, if_rmap->routemap[type]); + + if (ctx->if_rmap_delete_hook) + ctx->if_rmap_delete_hook(ctx, if_rmap); + + if (if_rmap->routemap[IF_RMAP_IN] == NULL && + if_rmap->routemap[IF_RMAP_OUT] == NULL) { + hash_release(ctx->ifrmaphash, if_rmap); + if_rmap_free(if_rmap); + } +} + +static int if_route_map_handler(struct vty *vty, bool no, const char *dir, + const char *other_dir, const char *ifname, + const char *route_map) +{ + enum nb_operation op = no ? NB_OP_DESTROY : NB_OP_MODIFY; + const struct lyd_node *dnode; + char xpath[XPATH_MAXLEN]; + + if (!no) { + snprintf( + xpath, sizeof(xpath), + "./if-route-maps/if-route-map[interface='%s']/%s-route-map", + ifname, dir); + } else { + /* + * If we are deleting the last policy for this interface, + * (i.e., no `in` or `out` policy). delete the interface list + * node instead. + */ + dnode = yang_dnode_get(vty->candidate_config->dnode, + VTY_CURR_XPATH); + if (yang_dnode_existsf( + dnode, + "./if-route-maps/if-route-map[interface='%s']/%s-route-map", + ifname, other_dir)) { + snprintf( + xpath, sizeof(xpath), + "./if-route-maps/if-route-map[interface='%s']/%s-route-map", + ifname, dir); + } else { + /* both dir will be empty so delete the list node */ + snprintf(xpath, sizeof(xpath), + "./if-route-maps/if-route-map[interface='%s']", + ifname); + } + } + nb_cli_enqueue_change(vty, xpath, op, route_map); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(if_ipv4_route_map, if_ipv4_route_map_cmd, + "route-map ROUTE-MAP IFNAME", + "Route map set\n" + "Route map name\n" + "Route map set for input filtering\n" + "Route map set for output filtering\n" INTERFACE_STR) +{ + const char *dir = in ? "in" : "out"; + const char *other_dir = in ? "out" : "in"; + + return if_route_map_handler(vty, false, dir, other_dir, ifname, + route_map); +} + +DEFPY_YANG(no_if_ipv4_route_map, no_if_ipv4_route_map_cmd, + "no route-map [ROUTE-MAP] IFNAME", + NO_STR + "Route map set\n" + "Route map name\n" + "Route map set for input filtering\n" + "Route map set for output filtering\n" INTERFACE_STR) +{ + const char *dir = in ? "in" : "out"; + const char *other_dir = in ? "out" : "in"; + + return if_route_map_handler(vty, true, dir, other_dir, ifname, + route_map); +} + +/* + * CLI infra requires new handlers for ripngd + */ +DEFPY_YANG(if_ipv6_route_map, if_ipv6_route_map_cmd, + "route-map ROUTE-MAP IFNAME", + "Route map set\n" + "Route map name\n" + "Route map set for input filtering\n" + "Route map set for output filtering\n" INTERFACE_STR) +{ + const char *dir = in ? "in" : "out"; + const char *other_dir = in ? "out" : "in"; + + return if_route_map_handler(vty, false, dir, other_dir, ifname, + route_map); +} + +DEFPY_YANG(no_if_ipv6_route_map, no_if_ipv6_route_map_cmd, + "no route-map [ROUTE-MAP] IFNAME", + NO_STR + "Route map set\n" + "Route map name\n" + "Route map set for input filtering\n" + "Route map set for output filtering\n" INTERFACE_STR) +{ + const char *dir = in ? "in" : "out"; + const char *other_dir = in ? "out" : "in"; + + return if_route_map_handler(vty, true, dir, other_dir, ifname, + route_map); +} + +void cli_show_if_route_map(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_exists(dnode, "in-route-map")) + vty_out(vty, " route-map %s in %s\n", + yang_dnode_get_string(dnode, "in-route-map"), + yang_dnode_get_string(dnode, "interface")); + if (yang_dnode_exists(dnode, "out-route-map")) + vty_out(vty, " route-map %s out %s\n", + yang_dnode_get_string(dnode, "out-route-map"), + yang_dnode_get_string(dnode, "interface")); +} + +void if_rmap_yang_modify_cb(struct if_rmap_ctx *ctx, + const struct lyd_node *dnode, + enum if_rmap_type type, bool del) +{ + + const char *mapname = yang_dnode_get_string(dnode, NULL); + const char *ifname = yang_dnode_get_string(dnode, "../interface"); + + if (del) + if_rmap_unset(ctx, ifname, type); + else + if_rmap_set(ctx, ifname, type, mapname); +} + +void if_rmap_yang_destroy_cb(struct if_rmap_ctx *ctx, + const struct lyd_node *dnode) +{ + const char *ifname = yang_dnode_get_string(dnode, "interface"); + if_rmap_unset(ctx, ifname, IF_RMAP_IN); + if_rmap_unset(ctx, ifname, IF_RMAP_OUT); +} + +void if_rmap_ctx_delete(struct if_rmap_ctx *ctx) +{ + hash_clean_and_free(&ctx->ifrmaphash, (void (*)(void *))if_rmap_free); + XFREE(MTYPE_IF_RMAP_CTX_NAME, ctx->name); + XFREE(MTYPE_IF_RMAP_CTX, ctx); +} + +/* name is optional: either vrf name, or other */ +struct if_rmap_ctx *if_rmap_ctx_create(const char *name) +{ + struct if_rmap_ctx *ctx; + + ctx = XCALLOC(MTYPE_IF_RMAP_CTX, sizeof(struct if_rmap_ctx)); + + ctx->name = XSTRDUP(MTYPE_IF_RMAP_CTX_NAME, name); + ctx->ifrmaphash = + hash_create_size(4, if_rmap_hash_make, if_rmap_hash_cmp, + "Interface Route-Map Hash"); + return ctx; +} + +void if_rmap_init(int node) +{ + if (node == RIP_NODE) { + install_element(RIP_NODE, &if_ipv4_route_map_cmd); + install_element(RIP_NODE, &no_if_ipv4_route_map_cmd); + } else if (node == RIPNG_NODE) { + install_element(RIPNG_NODE, &if_ipv6_route_map_cmd); + install_element(RIPNG_NODE, &no_if_ipv6_route_map_cmd); + } +} + +void if_rmap_terminate(void) +{ +} diff --git a/lib/if_rmap.h b/lib/if_rmap.h new file mode 100644 index 0000000..a9f811e --- /dev/null +++ b/lib/if_rmap.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* route-map for interface. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_IF_RMAP_H +#define _ZEBRA_IF_RMAP_H + +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct lyd_node; +struct vty; + +enum if_rmap_type { IF_RMAP_IN, IF_RMAP_OUT, IF_RMAP_MAX }; + +struct if_rmap { + /* Name of the interface. */ + const char *ifname; + + char *routemap[IF_RMAP_MAX]; +}; + +struct if_rmap_ctx { + /* if_rmap */ + struct hash *ifrmaphash; + + /* Hook functions. */ + void (*if_rmap_add_hook)(struct if_rmap_ctx *ctx, + struct if_rmap *ifrmap); + void (*if_rmap_delete_hook)(struct if_rmap_ctx *ctx, + struct if_rmap *ifrmap); + + /* naming information */ + char *name; +}; + +extern struct if_rmap_ctx *if_rmap_ctx_create(const char *name); +extern void if_rmap_ctx_delete(struct if_rmap_ctx *ctx); +extern void if_rmap_init(int node); +extern void if_rmap_terminate(void); +void if_rmap_hook_add(struct if_rmap_ctx *ctx, + void (*func)(struct if_rmap_ctx *ctx, + struct if_rmap *)); +void if_rmap_hook_delete(struct if_rmap_ctx *ctx, + void (*func)(struct if_rmap_ctx *ctx, + struct if_rmap *)); +extern struct if_rmap *if_rmap_lookup(struct if_rmap_ctx *ctx, + const char *ifname); +extern void if_rmap_yang_modify_cb(struct if_rmap_ctx *ctx, + const struct lyd_node *dnode, + enum if_rmap_type type, bool del); +extern void if_rmap_yang_destroy_cb(struct if_rmap_ctx *ctx, + const struct lyd_node *dnode); +extern int config_write_if_rmap(struct vty *, struct if_rmap_ctx *ctx); +void cli_show_if_route_map(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_IF_RMAP_H */ diff --git a/lib/imsg-buffer.c b/lib/imsg-buffer.c new file mode 100644 index 0000000..556e0cf --- /dev/null +++ b/lib/imsg-buffer.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include +#include + +#include "queue.h" +#include "imsg.h" + +static int ibuf_realloc(struct ibuf *, size_t); +static void ibuf_enqueue(struct msgbuf *, struct ibuf *); +static void ibuf_dequeue(struct msgbuf *, struct ibuf *); + +struct ibuf *ibuf_open(size_t len) +{ + struct ibuf *buf; + + if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) + return NULL; + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return NULL; + } + buf->size = buf->max = len; + buf->fd = -1; + + return (buf); +} + +struct ibuf *ibuf_dynamic(size_t len, size_t max) +{ + struct ibuf *buf; + + if (max < len) + return NULL; + + if ((buf = ibuf_open(len)) == NULL) + return NULL; + + if (max > 0) + buf->max = max; + + return (buf); +} + +static int ibuf_realloc(struct ibuf *buf, size_t len) +{ + uint8_t *b; + + /* on static buffers max is eq size and so the following fails */ + if (buf->wpos + len > buf->max) { + errno = ERANGE; + return -1; + } + + b = realloc(buf->buf, buf->wpos + len); + if (b == NULL) + return -1; + buf->buf = b; + buf->size = buf->wpos + len; + + return 0; +} + +int ibuf_add(struct ibuf *buf, const void *data, size_t len) +{ + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return -1; + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return 0; +} + +void *ibuf_reserve(struct ibuf *buf, size_t len) +{ + void *b; + + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return NULL; + + b = buf->buf + buf->wpos; + buf->wpos += len; + return (b); +} + +void *ibuf_seek(struct ibuf *buf, size_t pos, size_t len) +{ + /* only allowed to seek in already written parts */ + if (pos + len > buf->wpos) + return NULL; + + return (buf->buf + pos); +} + +size_t ibuf_size(struct ibuf *buf) +{ + return (buf->wpos); +} + +size_t ibuf_left(struct ibuf *buf) +{ + return (buf->max - buf->wpos); +} + +void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) +{ + ibuf_enqueue(msgbuf, buf); +} + +int ibuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + + memset(&iov, 0, sizeof(iov)); + TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + } + +again: + if ((n = writev(msgbuf->fd, iov, i)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return -1; + } + + if (n == 0) { /* connection closed */ + errno = 0; + return 0; + } + + msgbuf_drain(msgbuf, n); + + return 1; +} + +void ibuf_free(struct ibuf *buf) +{ + if (buf == NULL) + return; + free(buf->buf); + free(buf); +} + +void msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void msgbuf_drain(struct msgbuf *msgbuf, size_t n) +{ + struct ibuf *buf, *next; + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->wpos) { + n -= buf->wpos - buf->rpos; + + TAILQ_REMOVE(&msgbuf->bufs, buf, entry); + ibuf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } +} + +void msgbuf_clear(struct msgbuf *msgbuf) +{ + struct ibuf *buf; + + while ((buf = TAILQ_POP_FIRST(&msgbuf->bufs, entry)) != NULL) + ibuf_dequeue(msgbuf, buf); +} + +int msgbuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + + memset(&iov, 0, sizeof(iov)); + memset(&msg, 0, sizeof(msg)); + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + TAILQ_FOREACH (buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + if (buf->fd != -1) + break; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmsg), &buf->fd, sizeof(int)); + } + +again: + if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return -1; + } + + if (n == 0) { /* connection closed */ + errno = 0; + return 0; + } + + /* + * assumption: fd got sent if sendmsg sent anything + * this works because fds are passed one at a time + */ + if (buf != NULL && buf->fd != -1) { + close(buf->fd); + buf->fd = -1; + } + + msgbuf_drain(msgbuf, n); + + return 1; +} + +static void ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); + msgbuf->queued++; +} + +static void ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + /* TAILQ_REMOVE done by caller */ + if (buf->fd != -1) + close(buf->fd); + + msgbuf->queued--; + ibuf_free(buf); +} diff --git a/lib/imsg.c b/lib/imsg.c new file mode 100644 index 0000000..057e4b3 --- /dev/null +++ b/lib/imsg.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#include + +#include "memory.h" +#include "queue.h" +#include "imsg.h" + +static int imsg_fd_overhead = 0; + +static int imsg_get_fd(struct imsgbuf *); + +#ifndef __OpenBSD__ +/* + * The original code calls getdtablecount() which is OpenBSD specific. Use + * available_fds() from OpenSMTPD instead. + */ +static int available_fds(unsigned int n) +{ + unsigned int i; + int ret, fds[256]; + + if (n > (unsigned int)array_size(fds)) + return 1; + + ret = 0; + for (i = 0; i < n; i++) { + fds[i] = -1; + if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) + fds[i] = socket(AF_INET6, SOCK_DGRAM, 0); + if (fds[i] < 0) { + ret = 1; + break; + } + } + } + + for (i = 0; i < n && fds[i] >= 0; i++) + close(fds[i]); + + return (ret); +} +#endif + +void imsg_init(struct imsgbuf *ibuf, int fd) +{ + msgbuf_init(&ibuf->w); + memset(&ibuf->r, 0, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); + TAILQ_INIT(&ibuf->fds); +} + +ssize_t imsg_read(struct imsgbuf *ibuf) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int) * 1)]; + } cmsgbuf; + struct iovec iov; + ssize_t n; + int fd; + struct imsg_fd *ifd; + + memset(&msg, 0, sizeof(msg)); + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + + iov.iov_base = ibuf->r.buf + ibuf->r.wpos; + iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) + return -1; + +again: +#ifdef __OpenBSD__ + if (getdtablecount() + imsg_fd_overhead + + (int)((CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0)) + / sizeof(int)) + >= getdtablesize()) { +#else + if (available_fds(imsg_fd_overhead + + (CMSG_SPACE(sizeof(int)) - CMSG_SPACE(0)) + / sizeof(int))) { +#endif + errno = EAGAIN; + free(ifd); + return -1; + } + + n = recvmsg(ibuf->fd, &msg, 0); + if (n == -1) { + if (errno == EINTR) + goto again; + goto fail; + } + + ibuf->r.wpos += n; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + int i; + int j; + + /* + * We only accept one file descriptor. Due to C + * padding rules, our control buffer might contain + * more than one fd, and we must close them. + */ + j = ((char *)cmsg + cmsg->cmsg_len + - (char *)CMSG_DATA(cmsg)) + / sizeof(int); + for (i = 0; i < j; i++) { + fd = ((int *)CMSG_DATA(cmsg))[i]; + if (ifd != NULL) { + ifd->fd = fd; + TAILQ_INSERT_TAIL(&ibuf->fds, ifd, + entry); + ifd = NULL; + } else + close(fd); + } + } + /* we do not handle other ctl data level */ + } + +fail: + free(ifd); + return (n); +} + +ssize_t imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +{ + size_t av, left, datalen; + + av = ibuf->r.wpos; + + if (IMSG_HEADER_SIZE > av) + return 0; + + memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); + if (imsg->hdr.len < IMSG_HEADER_SIZE || imsg->hdr.len > MAX_IMSGSIZE) { + errno = ERANGE; + return -1; + } + if (imsg->hdr.len > av) + return 0; + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; + if (datalen == 0) + imsg->data = NULL; + else if ((imsg->data = malloc(datalen)) == NULL) + return -1; + + if (imsg->hdr.flags & IMSGF_HASFD) + imsg->fd = imsg_get_fd(ibuf); + else + imsg->fd = -1; + + if (imsg->data) + memcpy(imsg->data, ibuf->r.rptr, datalen); + + if (imsg->hdr.len < av) { + left = av - imsg->hdr.len; + memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); + ibuf->r.wpos = left; + } else + ibuf->r.wpos = 0; + + return (datalen + IMSG_HEADER_SIZE); +} + +int imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, int fd, const void *data, uint16_t datalen) +{ + struct ibuf *wbuf; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return -1; + + if (imsg_add(wbuf, data, datalen) == -1) + return -1; + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return 1; +} + +int imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + struct ibuf *wbuf; + int i, datalen = 0; + + for (i = 0; i < iovcnt; i++) + datalen += iov[i].iov_len; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return -1; + + for (i = 0; i < iovcnt; i++) + if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) + return -1; + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return 1; +} + +/* ARGSUSED */ +struct ibuf *imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, uint16_t datalen) +{ + struct ibuf *wbuf; + struct imsg_hdr hdr; + + memset(&hdr, 0x00, IMSG_HEADER_SIZE); + + datalen += IMSG_HEADER_SIZE; + if (datalen > MAX_IMSGSIZE) { + errno = ERANGE; + return NULL; + } + + hdr.type = type; + hdr.flags = 0; + hdr.peerid = peerid; + if ((hdr.pid = pid) == 0) + hdr.pid = ibuf->pid; + if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { + return NULL; + } + if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) + return NULL; + + return (wbuf); +} + +int imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) +{ + if (datalen) + if (ibuf_add(msg, data, datalen) == -1) { + ibuf_free(msg); + return -1; + } + return (datalen); +} + +void imsg_close(struct imsgbuf *ibuf, struct ibuf *msg) +{ + struct imsg_hdr *hdr; + + hdr = (struct imsg_hdr *)msg->buf; + + hdr->flags &= ~IMSGF_HASFD; + if (msg->fd != -1) + hdr->flags |= IMSGF_HASFD; + + hdr->len = (uint16_t)msg->wpos; + + ibuf_close(&ibuf->w, msg); +} + +void imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} + +int imsg_get_fd(struct imsgbuf *ibuf) +{ + int fd; + struct imsg_fd *ifd; + + if ((ifd = TAILQ_POP_FIRST(&ibuf->fds, entry)) == NULL) + return -1; + + fd = ifd->fd; + free(ifd); + + return (fd); +} + +int imsg_flush(struct imsgbuf *ibuf) +{ + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0) + return -1; + return 0; +} + +void imsg_clear(struct imsgbuf *ibuf) +{ + int fd; + + msgbuf_clear(&ibuf->w); + while ((fd = imsg_get_fd(ibuf)) != -1) + close(fd); +} diff --git a/lib/imsg.h b/lib/imsg.h new file mode 100644 index 0000000..4651eee --- /dev/null +++ b/lib/imsg.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: ISC +/* $OpenBSD$ */ + +/* + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard + * Copyright (c) 2006, 2007, 2008 Reyk Floeter + * Copyright (c) 2003, 2004 Henning Brauer + */ + +#ifndef _IMSG_H_ +#define _IMSG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define IBUF_READ_SIZE 65535 +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 16384 + +struct ibuf { + TAILQ_ENTRY(ibuf) entry; + uint8_t *buf; + size_t size; + size_t max; + size_t wpos; + size_t rpos; + int fd; +}; + +struct msgbuf { + TAILQ_HEAD(, ibuf) bufs; + uint32_t queued; + int fd; +}; + +struct ibuf_read { + uint8_t buf[IBUF_READ_SIZE]; + uint8_t *rptr; + size_t wpos; +}; + +struct imsg_fd { + TAILQ_ENTRY(imsg_fd) entry; + int fd; +}; + +struct imsgbuf { + TAILQ_HEAD(, imsg_fd) fds; + struct ibuf_read r; + struct msgbuf w; + int fd; + pid_t pid; +}; + +#define IMSGF_HASFD 1 + +struct imsg_hdr { + uint32_t type; + uint16_t len; + uint16_t flags; + uint32_t peerid; + uint32_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + int fd; + void *data; +}; + + +/* buffer.c */ +struct ibuf *ibuf_open(size_t); +struct ibuf *ibuf_dynamic(size_t, size_t); +int ibuf_add(struct ibuf *, const void *, size_t); +void *ibuf_reserve(struct ibuf *, size_t); +void *ibuf_seek(struct ibuf *, size_t, size_t); +size_t ibuf_size(struct ibuf *); +size_t ibuf_left(struct ibuf *); +void ibuf_close(struct msgbuf *, struct ibuf *); +int ibuf_write(struct msgbuf *); +void ibuf_free(struct ibuf *); +void msgbuf_init(struct msgbuf *); +void msgbuf_clear(struct msgbuf *); +int msgbuf_write(struct msgbuf *); +void msgbuf_drain(struct msgbuf *, size_t); + +/* imsg.c */ +void imsg_init(struct imsgbuf *, int); +ssize_t imsg_read(struct imsgbuf *); +ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const void *, + uint16_t); +int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, + const struct iovec *, int); +struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, uint16_t); +int imsg_add(struct ibuf *, const void *, uint16_t); +void imsg_close(struct imsgbuf *, struct ibuf *); +void imsg_free(struct imsg *); +int imsg_flush(struct imsgbuf *); +void imsg_clear(struct imsgbuf *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/ipaddr.h b/lib/ipaddr.h new file mode 100644 index 0000000..888955f --- /dev/null +++ b/lib/ipaddr.h @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP address structure (for generic IPv4 or IPv6 address) + * Copyright (C) 2016, 2017 Cumulus Networks, Inc. + */ + +#ifndef __IPADDR_H__ +#define __IPADDR_H__ + +#include + +#include "lib/log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Generic IP address - union of IPv4 and IPv6 address. + */ +enum ipaddr_type_t { + IPADDR_NONE = AF_UNSPEC, + IPADDR_V4 = AF_INET, + IPADDR_V6 = AF_INET6, +}; + +struct ipaddr { + enum ipaddr_type_t ipa_type; + union { + uint8_t addr; + uint8_t addrbytes[16]; + struct in_addr _v4_addr; + struct in6_addr _v6_addr; + } ip; +#define ipaddr_v4 ip._v4_addr +#define ipaddr_v6 ip._v6_addr +}; + +#define IS_IPADDR_NONE(p) ((p)->ipa_type == IPADDR_NONE) +#define IS_IPADDR_V4(p) ((p)->ipa_type == IPADDR_V4) +#define IS_IPADDR_V6(p) ((p)->ipa_type == IPADDR_V6) + +#define SET_IPADDR_NONE(p) ((p)->ipa_type = IPADDR_NONE) +#define SET_IPADDR_V4(p) ((p)->ipa_type = IPADDR_V4) +#define SET_IPADDR_V6(p) ((p)->ipa_type = IPADDR_V6) + +#define IPADDRSZ(p) \ + (IS_IPADDR_V4((p)) ? sizeof(struct in_addr) : sizeof(struct in6_addr)) + +#define IPADDR_STRING_SIZE 46 + +static inline int ipaddr_family(const struct ipaddr *ip) +{ + switch (ip->ipa_type) { + case IPADDR_V4: + return AF_INET; + case IPADDR_V6: + return AF_INET6; + case IPADDR_NONE: + return AF_UNSPEC; + } + + assert(!"Reached end of function where we should never hit"); +} + +static inline int str2ipaddr(const char *str, struct ipaddr *ip) +{ + int ret; + + memset(ip, 0, sizeof(struct ipaddr)); + + ret = inet_pton(AF_INET, str, &ip->ipaddr_v4); + if (ret > 0) /* Valid IPv4 address. */ + { + ip->ipa_type = IPADDR_V4; + return 0; + } + ret = inet_pton(AF_INET6, str, &ip->ipaddr_v6); + if (ret > 0) /* Valid IPv6 address. */ + { + ip->ipa_type = IPADDR_V6; + return 0; + } + + return -1; +} + +static inline char *ipaddr2str(const struct ipaddr *ip, char *buf, int size) +{ + buf[0] = '\0'; + if (ip) + inet_ntop(ip->ipa_type, &ip->ip.addr, buf, size); + return buf; +} + +#define IS_MAPPED_IPV6(A) \ + ((A)->s6_addr32[0] == 0x00000000 \ + ? ((A)->s6_addr32[1] == 0x00000000 \ + ? (ntohl((A)->s6_addr32[2]) == 0xFFFF ? 1 : 0) \ + : 0) \ + : 0) + +/* + * Convert IPv4 address to IPv4-mapped IPv6 address which is of the + * form ::FFFF: (RFC 4291). This IPv6 address can then + * be used to represent the IPv4 address, wherever only an IPv6 address + * is required. + */ +static inline void ipv4_to_ipv4_mapped_ipv6(struct in6_addr *in6, + struct in_addr in) +{ + uint32_t addr_type = htonl(0xFFFF); + + memset(in6, 0, sizeof(struct in6_addr)); + memcpy((char *)in6 + 8, &addr_type, sizeof(addr_type)); + memcpy((char *)in6 + 12, &in, sizeof(struct in_addr)); +} + +/* + * convert an ipv4 mapped ipv6 address back to ipv4 address + */ +static inline void ipv4_mapped_ipv6_to_ipv4(const struct in6_addr *in6, + struct in_addr *in) +{ + memset(in, 0, sizeof(struct in_addr)); + memcpy(in, (char *)in6 + 12, sizeof(struct in_addr)); +} + +/* + * generic ordering comparison between IP addresses + */ +static inline int ipaddr_cmp(const struct ipaddr *a, const struct ipaddr *b) +{ + uint32_t va, vb; + va = a->ipa_type; + vb = b->ipa_type; + if (va != vb) + return (va < vb) ? -1 : 1; + switch (a->ipa_type) { + case IPADDR_V4: + va = ntohl(a->ipaddr_v4.s_addr); + vb = ntohl(b->ipaddr_v4.s_addr); + if (va != vb) + return (va < vb) ? -1 : 1; + return 0; + case IPADDR_V6: + return memcmp((void *)&a->ipaddr_v6, (void *)&b->ipaddr_v6, + sizeof(a->ipaddr_v6)); + case IPADDR_NONE: + return 0; + } + + assert(!"Reached end of function we should never hit"); +} + +static inline bool ipaddr_is_zero(const struct ipaddr *ip) +{ + switch (ip->ipa_type) { + case IPADDR_NONE: + return true; + case IPADDR_V4: + return ip->ipaddr_v4.s_addr == INADDR_ANY; + case IPADDR_V6: + return IN6_IS_ADDR_UNSPECIFIED(&ip->ipaddr_v6); + } + return true; +} + +static inline bool ipaddr_is_same(const struct ipaddr *ip1, + const struct ipaddr *ip2) +{ + return ipaddr_cmp(ip1, ip2) == 0; +} + +/* clang-format off */ +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pIA" (struct ipaddr *) +#endif +/* clang-format on */ + +#ifdef __cplusplus +} +#endif + +#endif /* __IPADDR_H__ */ diff --git a/lib/iso.c b/lib/iso.c new file mode 100644 index 0000000..fe97776 --- /dev/null +++ b/lib/iso.c @@ -0,0 +1,144 @@ +/* + * ISO Network functions - iso_net.c + * + * Author: Olivier Dugeon + * + * Copyright (C) 2023 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "compiler.h" + +#include +#include +#include + +#include "printfrr.h" +#include "iso.h" + +/** + * Print ISO System ID as 0000.0000.0000 + * + * @param Print buffer + * @param Print argument + * @param Pointer to the System ID to be printed + * + * @return Number of printed characters + */ +printfrr_ext_autoreg_p("SY", printfrr_iso_sysid); +static ssize_t printfrr_iso_sysid(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const uint8_t *id = vptr; + + if (!id) + return bputs(buf, "(null)"); + + return bprintfrr(buf, "%02x%02x.%02x%02x.%02x%02x", + id[0], id[1], id[2], id[3], id[4], id[5]); +} + +/** + * Print ISO Pseudo Node system ID as 0000.0000.0000.00 + * + * @param Print buffer + * @param Print argument + * @param Pointer to the System ID to be printed + * + * @return Number of printed characters + */ +printfrr_ext_autoreg_p("PN", printfrr_iso_pseudo); +static ssize_t printfrr_iso_pseudo(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const uint8_t *id = vptr; + + if (!id) + return bputs(buf, "(null)"); + + return bprintfrr(buf, "%02x%02x.%02x%02x.%02x%02x.%02x", + id[0], id[1], id[2], id[3], id[4], id[5], id[6]); +} + +/** + * Print ISO LSP Fragment System ID as 0000.0000.0000.00-00 + * + * @param Print buffer + * @param Print argument + * @param Pointer to the System ID to be printed + * + * @return Number of printed characters + */ +printfrr_ext_autoreg_p("LS", printfrr_iso_frag_id); +static ssize_t printfrr_iso_frag_id(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const uint8_t *id = vptr; + + if (!id) + return bputs(buf, "(null)"); + + return bprintfrr(buf, "%02x%02x.%02x%02x.%02x%02x.%02x-%02x", + id[0], id[1], id[2], id[3], id[4], id[5], id[6], + id[7]); +} + +/** + * Print ISO Network address as 00.0000.0000.0000 ... with the System ID + * as 0000.0000.0000.00 when long 'l' option is added to '%pIS' + * + * @param Print buffer + * @param Print argument + * @param Pointer to the ISO Network address + * + * @return Number of printed characters + */ +printfrr_ext_autoreg_p("IS", printfrr_iso_addr); +static ssize_t printfrr_iso_addr(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const struct iso_address *ia = vptr; + uint8_t len = 0; + int i = 0; + ssize_t ret = 0; + + if (ea->fmt[0] == 'l') { + len = 7; /* ISO SYSTEM ID + 1 */ + ea->fmt++; + } + + if (!ia) + return bputs(buf, "(null)"); + + len += ia->addr_len; + while (i < len) { + /* No dot for odd index and at the end of address */ + if ((i & 1) || (i == (len - 1))) + ret += bprintfrr(buf, "%02x", ia->area_addr[i]); + else + ret += bprintfrr(buf, "%02x.", ia->area_addr[i]); + i++; + } + + return ret; +} + diff --git a/lib/iso.h b/lib/iso.h new file mode 100644 index 0000000..975d3c1 --- /dev/null +++ b/lib/iso.h @@ -0,0 +1,49 @@ +/* + * ISO Network definition - iso_net.h + * + * Author: Olivier Dugeon + * + * Copyright (C) 2023 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIB_ISO_H_ +#define LIB_ISO_H_ + +#include "compiler.h" + +/* len of "xx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xxxx.xx" + '\0' */ +#define ISO_ADDR_STRLEN 51 +#define ISO_ADDR_MIN 8 +#define ISO_ADDR_SIZE 20 +struct iso_address { + uint8_t addr_len; + uint8_t area_addr[ISO_ADDR_SIZE]; +}; + +/* len of "xxxx.xxxx.xxxx.xx-xx" + '\0' */ +#define ISO_SYSID_STRLEN 21 + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pSY" (uint8_t *) +#pragma FRR printfrr_ext "%pPN" (uint8_t *) +#pragma FRR printfrr_ext "%pLS" (uint8_t *) +#pragma FRR printfrr_ext "%pIS" (struct iso_address *) +#endif + +#endif /* LIB_ISO_H_ */ diff --git a/lib/jhash.c b/lib/jhash.c new file mode 100644 index 0000000..4e02112 --- /dev/null +++ b/lib/jhash.c @@ -0,0 +1,187 @@ +/* jhash.h: Jenkins hash support. + * + * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * http://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup2.c, by Bob Jenkins, December 1996, Public Domain. + * hash(), hash2(), hash3, and mix() are externally useful functions. + * Routines to test the hash are included if SELF_TEST is defined. + * You can use this free for any purpose. It has no warranty. + * + * Copyright (C) 2003 David S. Miller (davem@redhat.com) + * + * I've modified Bob's hash to be useful in the Linux kernel, and + * any bugs present are surely my fault. -DaveM + */ + +#include "zebra.h" +#include "jhash.h" + +/* The golden ration: an arbitrary value */ +#define JHASH_GOLDEN_RATIO 0x9e3779b9 + +/* NOTE: Arguments are modified. */ +#define __jhash_mix(a, b, c) \ + { \ + a -= b; \ + a -= c; \ + a ^= (c >> 13); \ + b -= c; \ + b -= a; \ + b ^= (a << 8); \ + c -= a; \ + c -= b; \ + c ^= (b >> 13); \ + a -= b; \ + a -= c; \ + a ^= (c >> 12); \ + b -= c; \ + b -= a; \ + b ^= (a << 16); \ + c -= a; \ + c -= b; \ + c ^= (b >> 5); \ + a -= b; \ + a -= c; \ + a ^= (c >> 3); \ + b -= c; \ + b -= a; \ + b ^= (a << 10); \ + c -= a; \ + c -= b; \ + c ^= (b >> 15); \ + } + +/* The most generic version, hashes an arbitrary sequence + * of bytes. No alignment or length assumptions are made about + * the input key. + */ +uint32_t jhash(const void *key, uint32_t length, uint32_t initval) +{ + uint32_t a, b, c, len; + const uint8_t *k = key; + + len = length; + a = b = JHASH_GOLDEN_RATIO; + c = initval; + + while (len >= 12) { + a += (k[0] + ((uint32_t)k[1] << 8) + ((uint32_t)k[2] << 16) + + ((uint32_t)k[3] << 24)); + b += (k[4] + ((uint32_t)k[5] << 8) + ((uint32_t)k[6] << 16) + + ((uint32_t)k[7] << 24)); + c += (k[8] + ((uint32_t)k[9] << 8) + ((uint32_t)k[10] << 16) + + ((uint32_t)k[11] << 24)); + + __jhash_mix(a, b, c); + + k += 12; + len -= 12; + } + + c += length; + switch (len) { + case 11: + c += ((uint32_t)k[10] << 24); + fallthrough; + case 10: + c += ((uint32_t)k[9] << 16); + fallthrough; + case 9: + c += ((uint32_t)k[8] << 8); + fallthrough; + case 8: + b += ((uint32_t)k[7] << 24); + fallthrough; + case 7: + b += ((uint32_t)k[6] << 16); + fallthrough; + case 6: + b += ((uint32_t)k[5] << 8); + fallthrough; + case 5: + b += k[4]; + fallthrough; + case 4: + a += ((uint32_t)k[3] << 24); + fallthrough; + case 3: + a += ((uint32_t)k[2] << 16); + fallthrough; + case 2: + a += ((uint32_t)k[1] << 8); + fallthrough; + case 1: + a += k[0]; + } + + __jhash_mix(a, b, c); + + return c; +} + +/* A special optimized version that handles 1 or more of uint32_ts. + * The length parameter here is the number of uint32_ts in the key. + */ +uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval) +{ + uint32_t a, b, c, len; + + a = b = JHASH_GOLDEN_RATIO; + c = initval; + len = length; + + while (len >= 3) { + a += k[0]; + b += k[1]; + c += k[2]; + __jhash_mix(a, b, c); + k += 3; + len -= 3; + } + + c += length * 4; + + switch (len) { + case 2: + b += k[1]; + fallthrough; + case 1: + a += k[0]; + } + + __jhash_mix(a, b, c); + + return c; +} + + +/* A special ultra-optimized versions that knows they are hashing exactly + * 3, 2 or 1 word(s). + * + * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally + * done at the end is not done here. + */ +uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c, uint32_t initval) +{ + a += JHASH_GOLDEN_RATIO; + b += JHASH_GOLDEN_RATIO; + c += initval; + + __jhash_mix(a, b, c); + + return c; +} + +uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval) +{ + return jhash_3words(a, b, 0, initval); +} + +uint32_t jhash_1word(uint32_t a, uint32_t initval) +{ + return jhash_3words(a, 0, 0, initval); +} diff --git a/lib/jhash.h b/lib/jhash.h new file mode 100644 index 0000000..9774214 --- /dev/null +++ b/lib/jhash.h @@ -0,0 +1,53 @@ +/* jhash.h: Jenkins hash support. + * + * Copyright (C) 1996 Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * http://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup2.c, by Bob Jenkins, December 1996, Public Domain. + * hash(), hash2(), hash3, and mix() are externally useful functions. + * Routines to test the hash are included if SELF_TEST is defined. + * You can use this free for any purpose. It has no warranty. + * + * Copyright (C) 2003 David S. Miller (davem@redhat.com) + * + * I've modified Bob's hash to be useful in the Linux kernel, and + * any bugs present are surely my fault. -DaveM + */ + +#ifndef _QUAGGA_JHASH_H +#define _QUAGGA_JHASH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* The most generic version, hashes an arbitrary sequence + * of bytes. No alignment or length assumptions are made about + * the input key. + */ +extern uint32_t jhash(const void *key, uint32_t length, uint32_t initval); + +/* A special optimized version that handles 1 or more of uint32_ts. + * The length parameter here is the number of uint32_ts in the key. + */ +extern uint32_t jhash2(const uint32_t *k, uint32_t length, uint32_t initval); + +/* A special ultra-optimized versions that knows they are hashing exactly + * 3, 2 or 1 word(s). + * + * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally + * done at the end is not done here. + */ +extern uint32_t jhash_3words(uint32_t a, uint32_t b, uint32_t c, + uint32_t initval); +extern uint32_t jhash_2words(uint32_t a, uint32_t b, uint32_t initval); +extern uint32_t jhash_1word(uint32_t a, uint32_t initval); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_JHASH_H */ diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 0000000..66312db --- /dev/null +++ b/lib/json.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* json-c wrapper + * Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "lib/json.h" + +/* + * This function assumes that the json keyword + * is the *last* keyword on the line no matter + * what. + */ +bool use_json(const int argc, struct cmd_token *argv[]) +{ + if (argc == 0) + return false; + + if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "json")) + return true; + + return false; +} + +struct json_object *json_object_new_stringv(const char *fmt, va_list args) +{ + struct json_object *ret; + char *text, buf[256]; + + text = vasnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt, args); + ret = json_object_new_string(text); + + if (text != buf) + XFREE(MTYPE_TMP, text); + return ret; +} + +void json_array_string_add(json_object *json, const char *str) +{ + json_object_array_add(json, json_object_new_string(str)); +} + +void json_array_string_addv(json_object *json, const char *fmt, va_list args) +{ + json_object_array_add(json, json_object_new_stringv(fmt, args)); +} + +void json_object_string_add(struct json_object *obj, const char *key, + const char *s) +{ + json_object_object_add(obj, key, json_object_new_string(s)); +} + +void json_object_string_addv(struct json_object *obj, const char *key, + const char *fmt, va_list args) +{ + json_object_object_add(obj, key, json_object_new_stringv(fmt, args)); +} + +void json_object_object_addv(struct json_object *parent, + struct json_object *child, const char *keyfmt, + va_list args) +{ + char *text, buf[256]; + + text = vasnprintfrr(MTYPE_TMP, buf, sizeof(buf), keyfmt, args); + json_object_object_add(parent, text, child); + + if (text != buf) + XFREE(MTYPE_TMP, text); +} + +void json_object_int_add(struct json_object *obj, const char *key, int64_t i) +{ + json_object_object_add(obj, key, json_object_new_int64(i)); +} + +void json_object_double_add(struct json_object *obj, const char *key, double i) +{ + json_object_object_add(obj, key, json_object_new_double(i)); +} + +void json_object_boolean_false_add(struct json_object *obj, const char *key) +{ + json_object_object_add(obj, key, json_object_new_boolean(0)); +} + +void json_object_boolean_true_add(struct json_object *obj, const char *key) +{ + json_object_object_add(obj, key, json_object_new_boolean(1)); +} + +void json_object_boolean_add(struct json_object *obj, const char *key, bool val) +{ + json_object_object_add(obj, key, json_object_new_boolean(val)); +} + +struct json_object *json_object_lock(struct json_object *obj) +{ + return json_object_get(obj); +} + +void json_object_free(struct json_object *obj) +{ + json_object_put(obj); +} diff --git a/lib/json.h b/lib/json.h new file mode 100644 index 0000000..4763803 --- /dev/null +++ b/lib/json.h @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* json-c wrapper + * Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#ifndef _QUAGGA_JSON_H +#define _QUAGGA_JSON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "command.h" +#include "printfrr.h" +#include + +/* + * FRR style JSON iteration. + * Usage: JSON_FOREACH(...) { ... } + */ +#define JSON_FOREACH(jo, joi, join) \ + /* struct json_object *jo; */ \ + /* struct json_object_iterator joi; */ \ + /* struct json_object_iterator join; */ \ + for ((joi) = json_object_iter_begin((jo)), \ + (join) = json_object_iter_end((jo)); \ + json_object_iter_equal(&(joi), &(join)) == 0; \ + json_object_iter_next(&(joi))) + +#define JSON_OBJECT_NEW_ARRAY(json_func, fields, n) \ + ({ \ + struct json_object *_json_array = json_object_new_array(); \ + for (int _i = 0; _i < (n); _i++) \ + json_object_array_add(_json_array, \ + (json_func)((fields)[_i])); \ + (_json_array); \ + }) + +extern bool use_json(const int argc, struct cmd_token *argv[]); +extern void json_object_string_add(struct json_object *obj, const char *key, + const char *s); +extern void json_object_int_add(struct json_object *obj, const char *key, + int64_t i); +void json_object_boolean_add(struct json_object *obj, const char *key, + bool val); + +extern void json_object_double_add(struct json_object *obj, const char *key, + double i); +extern void json_object_boolean_false_add(struct json_object *obj, + const char *key); +extern void json_object_boolean_true_add(struct json_object *obj, + const char *key); +extern struct json_object *json_object_lock(struct json_object *obj); +extern void json_object_free(struct json_object *obj); +extern void json_array_string_add(json_object *json, const char *str); + +/* printfrr => json helpers */ + +PRINTFRR(3, 0) +extern void json_object_string_addv(struct json_object *obj, const char *key, + const char *fmt, va_list args); +PRINTFRR(3, 4) +static inline void json_object_string_addf(struct json_object *obj, + const char *key, const char *fmt, + ...) +{ + va_list args; + + va_start(args, fmt); + json_object_string_addv(obj, key, fmt, args); + va_end(args); +} + +PRINTFRR(2, 0) +extern void json_array_string_addv(json_object *json, const char *fmt, + va_list args); +PRINTFRR(2, 3) +static inline void json_array_string_addf(struct json_object *obj, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + json_array_string_addv(obj, fmt, args); + va_end(args); +} + +PRINTFRR(1, 0) +extern struct json_object *json_object_new_stringv(const char *fmt, + va_list args); +PRINTFRR(1, 2) +static inline struct json_object *json_object_new_stringf(const char *fmt, ...) +{ + struct json_object *ret; + va_list args; + + va_start(args, fmt); + ret = json_object_new_stringv(fmt, args); + va_end(args); + + return ret; +} + +/* NOTE: argument order differs! (due to varargs) + * json_object_object_add(parent, key, child) + * json_object_object_addv(parent, child, key, va) + * json_object_object_addf(parent, child, key, ...) + * (would be weird to have the child inbetween the format string and args) + */ +PRINTFRR(3, 0) +extern void json_object_object_addv(struct json_object *parent, + struct json_object *child, + const char *keyfmt, va_list args); +PRINTFRR(3, 4) +static inline void json_object_object_addf(struct json_object *parent, + struct json_object *child, + const char *keyfmt, ...) +{ + va_list args; + + va_start(args, keyfmt); + json_object_object_addv(parent, child, keyfmt, args); + va_end(args); +} + +#define JSON_STR "JavaScript Object Notation\n" + +/* NOTE: json-c lib has following commit 316da85 which + * handles escape of forward slash. + * This allows prefix "20.0.14.0\/24":{ + * to "20.0.14.0/24":{ some platforms do not have + * latest copy of json-c where defining below macro. + */ + +#ifndef JSON_C_TO_STRING_NOSLASHESCAPE + +/** + * Don't escape forward slashes. + */ +#define JSON_C_TO_STRING_NOSLASHESCAPE (1<<4) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_JSON_H */ diff --git a/lib/keychain.c b/lib/keychain.c new file mode 100644 index 0000000..1982220 --- /dev/null +++ b/lib/keychain.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* key-chain for authentication. + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include "config.h" +#include + +#include "keychain.h" +#include "linklist.h" +#include "memory.h" + +DEFINE_MTYPE(LIB, KEY, "Key"); +DEFINE_MTYPE(LIB, KEYCHAIN, "Key chain"); +DEFINE_MTYPE(LIB, KEYCHAIN_DESC, "Key chain description"); + +DEFINE_QOBJ_TYPE(keychain); +DEFINE_QOBJ_TYPE(key); + +/* Master list of key chain. */ +struct list *keychain_list; + +static struct keychain *keychain_new(void) +{ + struct keychain *keychain; + keychain = XCALLOC(MTYPE_KEYCHAIN, sizeof(struct keychain)); + QOBJ_REG(keychain, keychain); + return keychain; +} + +static void keychain_free(struct keychain *keychain) +{ + QOBJ_UNREG(keychain); + XFREE(MTYPE_KEYCHAIN, keychain); +} + +static struct key *key_new(void) +{ + struct key *key = XCALLOC(MTYPE_KEY, sizeof(struct key)); + + QOBJ_REG(key, key); + return key; +} + +static void key_free(struct key *key) +{ + QOBJ_UNREG(key); + XFREE(MTYPE_KEY, key); +} + +struct keychain *keychain_lookup(const char *name) +{ + struct listnode *node; + struct keychain *keychain; + + if (name == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) { + if (strcmp(keychain->name, name) == 0) + return keychain; + } + return NULL; +} + +static int key_cmp_func(void *arg1, void *arg2) +{ + const struct key *k1 = arg1; + const struct key *k2 = arg2; + + if (k1->index > k2->index) + return 1; + if (k1->index < k2->index) + return -1; + return 0; +} + +static void key_delete_func(struct key *key) +{ + if (key->string) + XFREE(MTYPE_KEY, key->string); + key_free(key); +} + +struct keychain *keychain_get(const char *name) +{ + struct keychain *keychain; + + keychain = keychain_lookup(name); + + if (keychain) + return keychain; + + keychain = keychain_new(); + keychain->name = XSTRDUP(MTYPE_KEYCHAIN, name); + keychain->key = list_new(); + keychain->key->cmp = (int (*)(void *, void *))key_cmp_func; + keychain->key->del = (void (*)(void *))key_delete_func; + listnode_add(keychain_list, keychain); + + return keychain; +} + +void keychain_delete(struct keychain *keychain) +{ + XFREE(MTYPE_KEYCHAIN, keychain->name); + + list_delete(&keychain->key); + listnode_delete(keychain_list, keychain); + keychain_free(keychain); +} + +struct key *key_lookup(const struct keychain *keychain, uint32_t index) +{ + struct listnode *node; + struct key *key; + + for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { + if (key->index == index) + return key; + } + return NULL; +} + +struct key *key_lookup_for_accept(const struct keychain *keychain, + uint32_t index) +{ + struct listnode *node; + struct key *key; + time_t now; + + now = time(NULL); + + for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { + if (key->index >= index) { + if (key->accept.start == 0) + return key; + + if (key->accept.start <= now) + if (key->accept.end >= now + || key->accept.end == -1) + return key; + } + } + return NULL; +} + +struct key *key_match_for_accept(const struct keychain *keychain, + const char *auth_str) +{ + struct listnode *node; + struct key *key; + time_t now; + + now = time(NULL); + + for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { + if (key->accept.start == 0 + || (key->accept.start <= now + && (key->accept.end >= now || key->accept.end == -1))) + if (key->string && (strncmp(key->string, auth_str, 16) == 0)) + return key; + } + return NULL; +} + +struct key *key_lookup_for_send(const struct keychain *keychain) +{ + struct listnode *node; + struct key *key; + time_t now; + + now = time(NULL); + + for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) { + if (key->send.start == 0) + return key; + + if (key->send.start <= now) + if (key->send.end >= now || key->send.end == -1) + return key; + } + return NULL; +} + +struct key *key_get(const struct keychain *keychain, uint32_t index) +{ + struct key *key; + + key = key_lookup(keychain, index); + + if (key) + return key; + + key = key_new(); + key->index = index; + key->hash_algo = KEYCHAIN_ALGO_NULL; + listnode_add_sort(keychain->key, key); + + return key; +} + +void key_delete(struct keychain *keychain, struct key *key) +{ + listnode_delete(keychain->key, key); + + XFREE(MTYPE_KEY, key->string); + key_free(key); +} + +const struct keychain_algo_info algo_info[] = { + {KEYCHAIN_ALGO_NULL, "null", 0, 0, "NULL"}, + {KEYCHAIN_ALGO_MD5, "md5", KEYCHAIN_MD5_HASH_SIZE, + KEYCHAIN_ALGO_MD5_INTERNAL_BLK_SIZE, "MD5"}, + {KEYCHAIN_ALGO_HMAC_SHA1, "hmac-sha-1", KEYCHAIN_HMAC_SHA1_HASH_SIZE, + KEYCHAIN_ALGO_SHA1_INTERNAL_BLK_SIZE, "HMAC-SHA-1"}, + {KEYCHAIN_ALGO_HMAC_SHA256, "hmac-sha-256", + KEYCHAIN_HMAC_SHA256_HASH_SIZE, KEYCHAIN_ALGO_SHA256_INTERNAL_BLK_SIZE, + "HMAC-SHA-256"}, + {KEYCHAIN_ALGO_HMAC_SHA384, "hmac-sha-384", + KEYCHAIN_HMAC_SHA384_HASH_SIZE, KEYCHAIN_ALGO_SHA384_INTERNAL_BLK_SIZE, + "HMAC-SHA-384"}, + {KEYCHAIN_ALGO_HMAC_SHA512, "hmac-sha-512", + KEYCHAIN_HMAC_SHA512_HASH_SIZE, KEYCHAIN_ALGO_SHA512_INTERNAL_BLK_SIZE, + "HMAC-SHA-512"}, + {KEYCHAIN_ALGO_MAX, "max", KEYCHAIN_MAX_HASH_SIZE, + KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE, "Not defined"} +}; + +uint16_t keychain_get_block_size(enum keychain_hash_algo key) +{ + return algo_info[key].block; +} + +uint16_t keychain_get_hash_len(enum keychain_hash_algo key) +{ + return algo_info[key].length; +} + +const char *keychain_get_description(enum keychain_hash_algo key) +{ + return algo_info[key].desc; +} + +struct keychain_algo_info +keychain_get_hash_algo_info(enum keychain_hash_algo key) +{ + return algo_info[key]; +} + +enum keychain_hash_algo keychain_get_algo_id_by_name(const char *name) +{ +#ifdef CRYPTO_INTERNAL + if (!strncmp(name, "hmac-sha-2", 10)) + return KEYCHAIN_ALGO_HMAC_SHA256; + else if (!strncmp(name, "m", 1)) + return KEYCHAIN_ALGO_MD5; + else + return KEYCHAIN_ALGO_NULL; +#else + if (!strncmp(name, "m", 1)) + return KEYCHAIN_ALGO_MD5; + else if (!strncmp(name, "hmac-sha-1", 10)) + return KEYCHAIN_ALGO_HMAC_SHA1; + else if (!strncmp(name, "hmac-sha-2", 10)) + return KEYCHAIN_ALGO_HMAC_SHA256; + else if (!strncmp(name, "hmac-sha-3", 10)) + return KEYCHAIN_ALGO_HMAC_SHA384; + else if (!strncmp(name, "hmac-sha-5", 10)) + return KEYCHAIN_ALGO_HMAC_SHA512; + else + return KEYCHAIN_ALGO_NULL; +#endif +} + +const char *keychain_get_algo_name_by_id(enum keychain_hash_algo key) +{ + return algo_info[key].name; +} + +void keychain_terminate(void) +{ + struct keychain *keychain; + + while (listcount(keychain_list)) { + keychain = listgetdata(listhead(keychain_list)); + + listnode_delete(keychain_list, keychain); + keychain_delete(keychain); + } + + list_delete(&keychain_list); +} + +void keychain_init_new(bool in_backend) +{ + keychain_list = list_new(); + + if (!in_backend) + keychain_cli_init(); +} + +void keychain_init(void) +{ + keychain_init_new(false); +} + +const struct frr_yang_module_info ietf_key_chain_deviation_info = { + .name = "frr-deviations-ietf-key-chain", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = NULL, + }, + }, +}; diff --git a/lib/keychain.h b/lib/keychain.h new file mode 100644 index 0000000..dc35c2e --- /dev/null +++ b/lib/keychain.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* key-chain for authentication. + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_KEYCHAIN_H +#define _ZEBRA_KEYCHAIN_H + +#include "memory.h" +#include "northbound.h" +#include "qobj.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum keychain_hash_algo { + KEYCHAIN_ALGO_NULL, + KEYCHAIN_ALGO_MD5, + KEYCHAIN_ALGO_HMAC_SHA1, + KEYCHAIN_ALGO_HMAC_SHA256, + KEYCHAIN_ALGO_HMAC_SHA384, + KEYCHAIN_ALGO_HMAC_SHA512, + KEYCHAIN_ALGO_MAX +}; + +#define KEYCHAIN_MD5_HASH_SIZE 16 +#define KEYCHAIN_HMAC_SHA1_HASH_SIZE 20 +#define KEYCHAIN_HMAC_SHA256_HASH_SIZE 32 +#define KEYCHAIN_HMAC_SHA384_HASH_SIZE 48 +#define KEYCHAIN_HMAC_SHA512_HASH_SIZE 64 +#define KEYCHAIN_MAX_HASH_SIZE 64 + +#define KEYCHAIN_ALGO_MD5_INTERNAL_BLK_SIZE 16 +#define KEYCHAIN_ALGO_SHA1_INTERNAL_BLK_SIZE 64 +#define KEYCHAIN_ALGO_SHA256_INTERNAL_BLK_SIZE 64 +#define KEYCHAIN_ALGO_SHA384_INTERNAL_BLK_SIZE 128 +#define KEYCHAIN_ALGO_SHA512_INTERNAL_BLK_SIZE 128 +#define KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE 128 + +struct keychain_algo_info { + enum keychain_hash_algo key; + const char *name; + uint16_t length; + uint16_t block; + const char *desc; +}; + +extern const struct frr_yang_module_info ietf_key_chain_info; +extern const struct frr_yang_module_info ietf_key_chain_cli_info; +extern const struct frr_yang_module_info ietf_key_chain_deviation_info; + +extern const struct keychain_algo_info algo_info[]; +uint16_t keychain_get_block_size(enum keychain_hash_algo key); +uint16_t keychain_get_hash_len(enum keychain_hash_algo key); +const char *keychain_get_description(enum keychain_hash_algo key); +struct keychain_algo_info +keychain_get_hash_algo_info(enum keychain_hash_algo key); +enum keychain_hash_algo keychain_get_algo_id_by_name(const char *name); +const char *keychain_get_algo_name_by_id(enum keychain_hash_algo key); + +struct keychain { + char *name; + char *desc; + time_t last_touch; + + struct list *key; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(keychain); + +struct key_range { + time_t start; + time_t end; + + uint8_t duration; +}; + +struct key { + uint32_t index; + + char *string; + enum keychain_hash_algo hash_algo; + struct key_range send; + struct key_range accept; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(key); + +DECLARE_MTYPE(KEY); +DECLARE_MTYPE(KEYCHAIN); +DECLARE_MTYPE(KEYCHAIN_DESC); + +/* keychain implementation */ +extern struct list *keychain_list; +struct keychain *keychain_lookup(const char *name); +struct keychain *keychain_get(const char *name); +void keychain_delete(struct keychain *keychain); +struct key *key_lookup(const struct keychain *keychain, uint32_t index); +struct key *key_get(const struct keychain *keychain, uint32_t index); +void key_delete(struct keychain *keychain, struct key *key); + +void keychain_cli_init(void); +extern void key_chains_key_chain_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_cli_write_end(struct vty *vty, const struct lyd_node *dnode); +extern void key_chains_key_chain_description_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +void key_chains_key_chain_key_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_key_cli_write_end(struct vty *vty, const struct lyd_node *dnode); +extern void key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_key_crypto_algorithm_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); +extern void key_chains_key_chain_key_key_string_keystring_cli_write(struct vty *vty, const struct lyd_node *dnode, bool show_defaults); + +/* keychain users */ +extern void keychain_init(void); +extern void keychain_init_new(bool in_backend); +extern void keychain_terminate(void); +extern struct keychain *keychain_lookup(const char *); +extern struct key *key_lookup_for_accept(const struct keychain *, uint32_t); +extern struct key *key_match_for_accept(const struct keychain *, const char *); +extern struct key *key_lookup_for_send(const struct keychain *); +const char *keychain_algo_str(enum keychain_hash_algo hash_algo); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_KEYCHAIN_H */ diff --git a/lib/keychain_cli.c b/lib/keychain_cli.c new file mode 100644 index 0000000..26f56f1 --- /dev/null +++ b/lib/keychain_cli.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * February 22 2024, Christian Hopps + * + * Copyright (C) 2024 LabN Consulting, L.L.C. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include "command.h" +#include "keychain.h" +#include "northbound.h" +#include "northbound_cli.h" +#include "vty.h" + +#include "lib/keychain_cli_clippy.c" + +DEFPY_YANG_NOSH( + key_chain, + key_chain_cmd, + "key chain WORD", + "Authentication key management\n" + "Key-chain management\n" + "Key-chain name\n") +{ + char *xpath; + int ret; + + xpath = asprintfrr(MTYPE_TMP, + "/ietf-key-chain:key-chains/key-chain[name='%s']", + chain); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(KEYCHAIN_NODE, xpath); + XFREE(MTYPE_TMP, xpath); + return ret; +} + +DEFPY_YANG( + no_key_chain, + no_key_chain_cmd, + "no key chain WORD", + NO_STR + "Authentication key management\n" + "Key-chain management\n" + "Key-chain name\n") +{ + char *xpath; + + xpath = asprintfrr(MTYPE_TMP, + "/ietf-key-chain:key-chains/key-chain[name='%s']", + chain); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + XFREE(MTYPE_TMP, xpath); + return nb_cli_apply_changes_clear_pending(vty, NULL); +} + +DEFPY_YANG_NOSH( + key, + key_cmd, + "key (0-2147483647)", + "Configure a key\n" + "Key identifier number\n") +{ + char *xpath; + int ret; + + xpath = asprintfrr(MTYPE_TMP, "%s/key[key-id='%s']", VTY_CURR_XPATH, + key_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(KEYCHAIN_KEY_NODE, xpath); + XFREE(MTYPE_TMP, xpath); + return ret; +} + +DEFPY_YANG( + no_key, + no_key_cmd, + "no key (0-2147483647)", + NO_STR + "Delete a key\n" + "Key identifier number\n") +{ + char *xpath; + + xpath = asprintfrr(MTYPE_TMP, "%s/key[key-id='%s']", VTY_CURR_XPATH, + key_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + XFREE(MTYPE_TMP, xpath); + return nb_cli_apply_changes_clear_pending(vty, NULL); +} + +DEFPY_YANG( + key_string, + key_string_cmd, + "key-string LINE", + "Set key string\n" + "The key\n") +{ + nb_cli_enqueue_change(vty, "./key-string/keystring", NB_OP_CREATE, line); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_key_string, + no_key_string_cmd, + "no key-string [LINE]", + NO_STR + "Unset key string\n" + "The key\n") +{ + nb_cli_enqueue_change(vty, "./key-string/keystring", NB_OP_DESTROY, line); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + cryptographic_algorithm, + cryptographic_algorithm_cmd, + "cryptographic-algorithm " + "$algo", + "Cryptographic-algorithm\n" + "Use MD5 algorithm\n" + "Use HMAC-SHA-1 algorithm\n" + "Use HMAC-SHA-256 algorithm\n" + "Use HMAC-SHA-384 algorithm\n" + "Use HMAC-SHA-512 algorithm\n") +{ + nb_cli_enqueue_change(vty, "./crypto-algorithm", NB_OP_CREATE, algo); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_cryptographic_algorithm, + no_cryptographic_algorithm_cmd, + "no cryptographic-algorithm " + "[$algo]", + NO_STR + "Cryptographic-algorithm\n" + "Use MD5 algorithm\n" + "Use HMAC-SHA-1 algorithm\n" + "Use HMAC-SHA-256 algorithm\n" + "Use HMAC-SHA-384 algorithm\n" + "Use HMAC-SHA-512 algorithm\n") +{ + nb_cli_enqueue_change(vty, "./crypto-algorithm", NB_OP_DESTROY, algo); + return nb_cli_apply_changes(vty, NULL); +} + +const char *month_name[] = { + "january", "february", "march", "april", "may", "june", "july", + "august", "september", "october", "november", "december", NULL +}; + +static int __get_month(const char *month_str) +{ + int i, len; + + len = strlen(month_str); + if (len < 3) + return -1; + for (i = 1; month_name[i-1]; i++) + if (strncasecmp(month_str, month_name[i-1], len) == 0) + return i; + return -1; +} + + +static long __timezone_offset(void) +{ + time_t now; + struct tm *tm_now; + + time(&now); + tm_now = localtime(&now); + return tm_now->tm_gmtoff; +} + +static int __lifetime_set(struct vty *vty, char timebuf[32], + const char *time_node, const char *leaf_node, + const char *time_str, const char *day_str, + const char *month_str, const char *year_str) +{ + char xpath[128]; + int month = __get_month(month_str); + int hoff, moff; + long offset; + + if (month < 1) { + vty_out(vty, "Bad month value: %s\n", month_str); + return -1; + } + + offset = __timezone_offset(); + hoff = offset / 3600; + if (offset < 0) + offset = -offset; + moff = (offset % 3600) / 60; + + snprintf(timebuf, 32, "%s-%02d-%02dT%s%+03d:%02d", year_str, month, + atoi(day_str), time_str, hoff, moff); + snprintf(xpath, sizeof(xpath), "./lifetime/%s/%s", time_node, leaf_node); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, timebuf); + return 0; +} + + +static int key_lifetime_set(struct vty *vty, const char *time_node, + const char *stime_str, const char *sday_str, + const char *smonth_str, const char *syear_str, + const char *etime_str, const char *eday_str, + const char *emonth_str, const char *eyear_str) +{ + char time1[32]; + char time2[32]; + + if (__lifetime_set(vty, time1, time_node, "start-date-time", stime_str, + sday_str, smonth_str, syear_str)) + return CMD_WARNING_CONFIG_FAILED; + + if (__lifetime_set(vty, time2, time_node, "end-date-time", etime_str, + eday_str, emonth_str, eyear_str)) + return CMD_WARNING_CONFIG_FAILED; + + return nb_cli_apply_changes(vty, NULL); +} + +static int key_lifetime_duration_set(struct vty *vty, const char *time_node, + const char *stime_str, const char *sday_str, + const char *smonth_str, + const char *syear_str, + const char *duration_str) +{ + char xpath[128]; + char time[32]; + + if (__lifetime_set(vty, time, time_node, "start-date-time", stime_str, + sday_str, smonth_str, syear_str)) + return CMD_WARNING_CONFIG_FAILED; + + /* End time. */ + snprintf(xpath, sizeof(xpath), "./lifetime/%s/duration", time_node); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, duration_str); + + return nb_cli_apply_changes(vty, NULL); +} + +static int key_lifetime_infinite_set(struct vty *vty, const char *time_node, + const char *stime_str, const char *sday_str, + const char *smonth_str, + const char *syear_str) +{ + char xpath[128]; + char time[32]; + + if (__lifetime_set(vty, time, time_node, "start-date-time", stime_str, + sday_str, smonth_str, syear_str)) + return CMD_WARNING_CONFIG_FAILED; + + /* End time. */ + snprintf(xpath, sizeof(xpath), "./lifetime/%s/no-end-time", time_node); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + accept_lifetime_day_month_day_month, + accept_lifetime_day_month_day_month_cmd, + "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Time to expire\n" + "Day of th month to expire\n" + "Month of the year to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_number_3 = 6; + int idx_month_2 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "accept-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(accept_lifetime_day_month_month_day, + accept_lifetime_day_month_month_day_cmd, + "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Time to expire\n" + "Month of the year to expire\n" + "Day of th month to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_month_2 = 6; + int idx_number_3 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "accept-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(accept_lifetime_month_day_day_month, + accept_lifetime_month_day_day_month_cmd, + "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Time to expire\n" + "Day of th month to expire\n" + "Month of the year to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_number_3 = 6; + int idx_month_2 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "accept-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(accept_lifetime_month_day_month_day, + accept_lifetime_month_day_month_day_cmd, + "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Time to expire\n" + "Month of the year to expire\n" + "Day of th month to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_month_2 = 6; + int idx_number_3 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "accept-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(accept_lifetime_infinite_day_month, + accept_lifetime_infinite_day_month_cmd, + "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite", + "Set accept lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Never expires\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + + return key_lifetime_infinite_set(vty, "accept-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg); +} + +DEFPY_YANG(accept_lifetime_infinite_month_day, + accept_lifetime_infinite_month_day_cmd, + "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite", + "Set accept lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Never expires\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + + return key_lifetime_infinite_set(vty, "accept-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg); +} + +DEFPY_YANG(accept_lifetime_duration_day_month, + accept_lifetime_duration_day_month_cmd, + "accept-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Duration of the key\n" + "Duration seconds\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_number_3 = 6; + + return key_lifetime_duration_set(vty, "accept-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg, + argv[idx_number_3]->arg); +} + +DEFPY_YANG(accept_lifetime_duration_month_day, + accept_lifetime_duration_month_day_cmd, + "accept-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)", + "Set accept lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Duration of the key\n" + "Duration seconds\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 6; + + return key_lifetime_duration_set(vty, "accept-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg, + argv[idx_number_3]->arg); +} + +DEFPY_YANG(no_accept_lifetime, + no_accept_lifetime_cmd, + "no accept-lifetime", + NO_STR + "Unset accept-lifetime\n") +{ + nb_cli_enqueue_change(vty, "accept-lifetime", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + send_lifetime_day_month_day_month, send_lifetime_day_month_day_month_cmd, + "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", + "Set send lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Time to expire\n" + "Day of th month to expire\n" + "Month of the year to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_number_3 = 6; + int idx_month_2 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "send-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(send_lifetime_day_month_month_day, + send_lifetime_day_month_month_day_cmd, + "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", + "Set send lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Time to expire\n" + "Month of the year to expire\n" + "Day of th month to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_month_2 = 6; + int idx_number_3 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "send-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(send_lifetime_month_day_day_month, + send_lifetime_month_day_day_month_cmd, + "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS (1-31) MONTH (1993-2035)", + "Set send lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Time to expire\n" + "Day of th month to expire\n" + "Month of the year to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_number_3 = 6; + int idx_month_2 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "send-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(send_lifetime_month_day_month_day, + send_lifetime_month_day_month_day_cmd, + "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) HH:MM:SS MONTH (1-31) (1993-2035)", + "Set send lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Time to expire\n" + "Month of the year to expire\n" + "Day of th month to expire\n" + "Year to expire\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_hhmmss_2 = 5; + int idx_month_2 = 6; + int idx_number_3 = 7; + int idx_number_4 = 8; + + return key_lifetime_set(vty, "send-lifetime", argv[idx_hhmmss]->arg, + argv[idx_number]->arg, argv[idx_month]->arg, + argv[idx_number_2]->arg, argv[idx_hhmmss_2]->arg, + argv[idx_number_3]->arg, argv[idx_month_2]->arg, + argv[idx_number_4]->arg); +} + +DEFPY_YANG(send_lifetime_infinite_day_month, + send_lifetime_infinite_day_month_cmd, + "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) infinite", + "Set send lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Never expires\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + + return key_lifetime_infinite_set(vty, "send-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg); +} + +DEFPY_YANG(send_lifetime_infinite_month_day, + send_lifetime_infinite_month_day_cmd, + "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) infinite", + "Set send lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Never expires\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + + return key_lifetime_infinite_set(vty, "send-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg); +} + +DEFPY_YANG(send_lifetime_duration_day_month, + send_lifetime_duration_day_month_cmd, + "send-lifetime HH:MM:SS (1-31) MONTH (1993-2035) duration (1-2147483646)", + "Set send lifetime of the key\n" + "Time to start\n" + "Day of th month to start\n" + "Month of the year to start\n" + "Year to start\n" + "Duration of the key\n" + "Duration seconds\n") +{ + int idx_hhmmss = 1; + int idx_number = 2; + int idx_month = 3; + int idx_number_2 = 4; + int idx_number_3 = 6; + + return key_lifetime_duration_set(vty, "send-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg, + argv[idx_number_3]->arg); +} + +DEFPY_YANG(send_lifetime_duration_month_day, + send_lifetime_duration_month_day_cmd, + "send-lifetime HH:MM:SS MONTH (1-31) (1993-2035) duration (1-2147483646)", + "Set send lifetime of the key\n" + "Time to start\n" + "Month of the year to start\n" + "Day of th month to start\n" + "Year to start\n" + "Duration of the key\n" + "Duration seconds\n") +{ + int idx_hhmmss = 1; + int idx_month = 2; + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 6; + + return key_lifetime_duration_set(vty, "send-lifetime", + argv[idx_hhmmss]->arg, + argv[idx_number]->arg, + argv[idx_month]->arg, + argv[idx_number_2]->arg, + argv[idx_number_3]->arg); +} + +DEFUN (no_send_lifetime, + no_send_lifetime_cmd, + "no send-lifetime", + NO_STR + "Unset send-lifetime\n") +{ + nb_cli_enqueue_change(vty, "send-lifetime", NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain + */ +void key_chains_key_chain_cli_write(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, "key chain %s\n", yang_dnode_get_string(dnode, "name")); +} + +void key_chains_key_chain_cli_write_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/description + */ +void key_chains_key_chain_description_cli_write(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + /* Implement CLI */ + /* vty_out(vty, " description %s\n", yang_dnode_get_string(dnode)); */ +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key + */ +void key_chains_key_chain_key_cli_write(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " key %s\n", yang_dnode_get_string(dnode, "key-id")); +} + +void key_chains_key_chain_key_cli_write_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +static const char *__dnode_to_key_strftime(char *buf, size_t bufsize, + const struct lyd_node *lt_start_dnode) +{ + const char *timestr; + struct lyd_node *end_node; + struct tm tm; + uint32_t duration; + time_t time; + int len, sz; + char *s; + + s = buf; + sz = bufsize; + + timestr = yang_dnode_get_string(lt_start_dnode, NULL); + (void)ly_time_str2time(timestr, &time, NULL); + localtime_r(&time, &tm); + len = strftime(s, sz, "%T %b %e %Y", &tm); + s += len; + sz -= len; + + if (yang_dnode_exists(lt_start_dnode, "../no-end-time")) { + strlcat(s, " infinite", sz); + return buf; + } + + end_node = yang_dnode_get(lt_start_dnode, "../duration"); + if (end_node) { + duration = yang_dnode_get_uint32(end_node, NULL); + snprintf(s, sz, " duration %u", (uint)duration); + return buf; + } + + timestr = yang_dnode_get_string(lt_start_dnode, "../end-date-time"); + (void)ly_time_str2time(timestr, &time, NULL); + localtime_r(&time, &tm); + strftime(s, sz, " %T %b %e %Y", &tm); + return buf; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/start-date-time + */ +void key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_cli_write( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + char s[256]; + + vty_out(vty, " send-lifetime %s\n", + __dnode_to_key_strftime(s, sizeof(s), dnode)); + vty_out(vty, " accept-lifetime %s\n", s); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/start-date-time + */ +void key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_cli_write( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + char s[256]; + + vty_out(vty, " send-lifetime %s\n", + __dnode_to_key_strftime(s, sizeof(s), dnode)); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/start-date-time + */ +void key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_cli_write( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + char s[256]; + + vty_out(vty, " accept-lifetime %s\n", + __dnode_to_key_strftime(s, sizeof(s), dnode)); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/crypto-algorithm + */ +void key_chains_key_chain_key_crypto_algorithm_cli_write( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + static const char prefix[] = "ietf-key-chain:"; + static const int prefix_len = sizeof(prefix) - 1; + const char *name = yang_dnode_get_string(dnode, NULL); + + if (!strncmp(name, prefix, prefix_len)) + name += prefix_len; + vty_out(vty, " cryptographic-algorithm %s\n", name); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/key-string/keystring + */ +void key_chains_key_chain_key_key_string_keystring_cli_write( + struct vty *vty, const struct lyd_node *dnode, bool show_defaults) +{ + vty_out(vty, " key-string %s\n", yang_dnode_get_string(dnode, NULL)); +} + +static const char * const keychain_features[] = { + "independent-send-accept-lifetime", + NULL, +}; + +/* clang-format off */ +const struct frr_yang_module_info ietf_key_chain_cli_info = { + .name = "ietf-key-chain", + .features = (const char **)keychain_features, + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/ietf-key-chain:key-chains/key-chain", + .cbs = { + .cli_show = key_chains_key_chain_cli_write, + .cli_show_end = key_chains_key_chain_cli_write_end, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/description", + .cbs = { + .cli_show = key_chains_key_chain_description_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key", + .cbs = { + .cli_show = key_chains_key_chain_key_cli_write, + .cli_show_end = key_chains_key_chain_key_cli_write_end, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/start-date-time", + .cbs = { + .cli_show = key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/start-date-time", + .cbs = { + .cli_show = key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/start-date-time", + .cbs = { + .cli_show = key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/crypto-algorithm", + .cbs = { + .cli_show = key_chains_key_chain_key_crypto_algorithm_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/key-string/keystring", + .cbs = { + .cli_show = key_chains_key_chain_key_key_string_keystring_cli_write, + } + }, + { + .xpath = NULL, + }, + } +}; + +static int keychain_config_write(struct vty *vty) +{ + const struct lyd_node *dnode; + int written = 0; + + dnode = yang_dnode_get(running_config->dnode, + "/ietf-key-chain:key-chains"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + return written; +} + +static struct cmd_node keychain_node = { + .name = "keychain", + .node = KEYCHAIN_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-keychain)# ", + .config_write = keychain_config_write, +}; + +static struct cmd_node keychain_key_node = { + .name = "keychain key", + .node = KEYCHAIN_KEY_NODE, + .parent_node = KEYCHAIN_NODE, + .prompt = "%s(config-keychain-key)# ", +}; + +static const struct cmd_variable_handler keychain_var_handlers[] = { + {.varname = "key_chain", .xpath = "/ietf-key-chain:key-chains/key-chain/name" }, + {.tokenname = "KEYCHAIN_NAME", .xpath = "/ietf-key-chain:key-chains/key-chain/name" }, + {.completions = NULL} +}; + +void keychain_cli_init(void) +{ + /* Register handler for keychain auto config support */ + cmd_variable_handler_register(keychain_var_handlers); + install_node(&keychain_node); + install_node(&keychain_key_node); + + install_default(KEYCHAIN_NODE); + install_default(KEYCHAIN_KEY_NODE); + + install_element(CONFIG_NODE, &key_chain_cmd); + install_element(CONFIG_NODE, &no_key_chain_cmd); + install_element(KEYCHAIN_NODE, &key_cmd); + install_element(KEYCHAIN_NODE, &no_key_cmd); + + install_element(KEYCHAIN_NODE, &key_chain_cmd); + install_element(KEYCHAIN_NODE, &no_key_chain_cmd); + + install_element(KEYCHAIN_KEY_NODE, &key_string_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_key_string_cmd); + + install_element(KEYCHAIN_KEY_NODE, &key_chain_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_key_chain_cmd); + + install_element(KEYCHAIN_KEY_NODE, &key_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_key_cmd); + + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_day_month_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_day_month_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_month_day_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_month_day_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_infinite_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_infinite_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_duration_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &accept_lifetime_duration_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_accept_lifetime_cmd); + + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_day_month_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_day_month_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_month_day_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_month_day_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_infinite_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_infinite_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_duration_day_month_cmd); + install_element(KEYCHAIN_KEY_NODE, + &send_lifetime_duration_month_day_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_send_lifetime_cmd); + install_element(KEYCHAIN_KEY_NODE, &cryptographic_algorithm_cmd); + install_element(KEYCHAIN_KEY_NODE, &no_cryptographic_algorithm_cmd); +} diff --git a/lib/keychain_nb.c b/lib/keychain_nb.c new file mode 100644 index 0000000..57967b3 --- /dev/null +++ b/lib/keychain_nb.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * February 22 2024, Christian Hopps + * + * Copyright (C) 2024 LabN Consulting, L.L.C. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include "lib_errors.h" +#include "northbound.h" +#include "keychain.h" + +static void keychain_touch(struct keychain *keychain) +{ + keychain->last_touch = time(NULL); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain + */ +static int key_chains_key_chain_create(struct nb_cb_create_args *args) +{ + const char *name; + struct keychain *keychain; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "name"); + keychain = keychain_get(name); + keychain_touch(keychain); + return NB_OK; +} + +static int key_chains_key_chain_destroy(struct nb_cb_destroy_args *args) +{ + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "name"); + keychain_delete(keychain_lookup(name)); + return NB_OK; +} + +static const void *key_chains_key_chain_get_next(struct nb_cb_get_next_args *args) +{ + const struct listnode *prev = args->list_entry; + + return prev ? prev->next : keychain_list->head; +} + +static int key_chains_key_chain_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct listnode *node = args->list_entry; + const struct keychain *keychain = node->data; + + args->keys->num = 1; + strlcpy(args->keys->key[0], keychain->name, sizeof(args->keys->key[0])); + return NB_OK; +} + +static const void *key_chains_key_chain_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *name = args->keys->key[0]; + struct keychain *keychain; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(keychain_list, node, keychain)) { + if (strcmp(keychain->name, name) == 0) + return node; + } + return NULL; +} + + +static int __destroy_nop(struct nb_cb_destroy_args *args) +{ + /* modified by sibling or cleaned up by container destroy */ + return NB_OK; +} + +static struct key *__dnode_get_key2(const struct lyd_node *dnode, bool touch) +{ + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + + name = yang_dnode_get_string(dnode, "../../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(dnode, "../../key-id"); + key = key_lookup(keychain, index); + if (touch) + keychain_touch(keychain); + return key; +} + +static struct key *__dnode_get_key3(const struct lyd_node *dnode, bool touch) +{ + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + + name = yang_dnode_get_string(dnode, "../../../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(dnode, "../../../key-id"); + key = key_lookup(keychain, index); + if (touch) + keychain_touch(keychain); + return key; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/description + */ +static int key_chains_key_chain_description_modify(struct nb_cb_modify_args *args) +{ + struct keychain *keychain; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../name"); + keychain = keychain_lookup(name); + XFREE(MTYPE_KEYCHAIN_DESC, keychain->desc); + keychain->desc = XSTRDUP(MTYPE_KEYCHAIN_DESC, + yang_dnode_get_string(args->dnode, NULL)); + + keychain_touch(keychain); + return NB_OK; +} + +static int key_chains_key_chain_description_destroy(struct nb_cb_destroy_args *args) +{ + struct keychain *keychain; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../name"); + keychain = keychain_lookup(name); + XFREE(MTYPE_KEYCHAIN_DESC, keychain->desc); + + keychain_touch(keychain); + return NB_OK; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/last-modified-timestamp + */ +static struct yang_data *key_chains_key_chain_last_modified_timestamp_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct listnode *kcnode = args->list_entry; + const struct keychain *keychain = kcnode->data; + + return yang_data_new_date_and_time(args->xpath, keychain->last_touch, + false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key + */ +static int key_chains_key_chain_key_create(struct nb_cb_create_args *args) +{ + struct keychain *keychain; + struct key *key; + const char *name; + uint64_t keyid; + + if (args->event != NB_EV_VALIDATE && args->event != NB_EV_APPLY) + return NB_OK; + + keyid = yang_dnode_get_uint64(args->dnode, "key-id"); + if (args->event == NB_EV_VALIDATE) { + if (keyid > UINT32_MAX) { + /* Warn most protocols can't use this value */ + flog_err(EC_LIB_NB_CB_CONFIG_VALIDATE, + "Protocols do not accept > 32-bit key-id values"); + return NB_EV_VALIDATE; + } + return NB_OK; + } + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../name"); + keychain = keychain_lookup(name); + assert(keyid <= UINT32_MAX); + key = key_get(keychain, (uint32_t)keyid); + assert(key); + + keychain_touch(keychain); + return NB_OK; +} + +static int key_chains_key_chain_key_destroy(struct nb_cb_destroy_args *args) +{ + struct keychain *keychain; + struct key *key; + const char *name; + uint64_t keyid; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + keyid = yang_dnode_get_uint64(args->dnode, "key-id"); + if (keyid > UINT32_MAX) + return NB_ERR_NOT_FOUND; + name = yang_dnode_get_string(args->dnode, "../name"); + keychain = keychain_lookup(name); + key = key_lookup(keychain, (uint32_t)keyid); + key_delete(keychain, key); + + keychain_touch(keychain); + return NB_OK; +} + +static const void *key_chains_key_chain_key_get_next(struct nb_cb_get_next_args *args) +{ + const struct listnode *kcnode = args->parent_list_entry; + const struct keychain *keychain = kcnode->data; + const struct listnode *prev = args->list_entry; + + return prev ? prev->next : keychain->key->head; +} + +static int key_chains_key_chain_key_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct listnode *node = args->list_entry; + const struct key *key = node->data; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%" PRIu32, + key->index); + + return NB_OK; +} + +static const void *key_chains_key_chain_key_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const struct listnode *kcnode = args->parent_list_entry; + const struct keychain *keychain = kcnode->data; + struct listnode *node; + struct key *key; + uint32_t index; + + index = strtoul(args->keys->key[0], NULL, 0); + for (ALL_LIST_ELEMENTS_RO(keychain->key, node, key)) + if (key->index == index) + return node; + return NULL; +} + +static int __lifetime_create(struct nb_cb_create_args *args, bool send, + bool accept, bool always) +{ + struct key *key; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + if (always) + key = __dnode_get_key3(args->dnode, true); + else + key = __dnode_get_key2(args->dnode, true); + if (send) { + key->send.start = 0; + key->send.end = -1; + key->send.duration = 0; + } + if (accept) { + key->accept.start = 0; + key->accept.end = -1; + key->accept.duration = 0; + } + return NB_OK; +} + +static int __lifetime_start_date_time_modify(struct nb_cb_modify_args *args, + bool send, bool accept) +{ + struct key *key; + time_t time; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + key = __dnode_get_key3(args->dnode, true); + time = yang_dnode_get_date_and_time(args->dnode, NULL); + + if (send) + key->send.start = time; + if (accept) + key->accept.start = time; + + return NB_OK; +} + +static int __lifetime_no_end_time_create(struct nb_cb_create_args *args, + bool send, bool accept) +{ + struct key *key; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + key = __dnode_get_key3(args->dnode, true); + if (send) + key->send.end = -1; + if (accept) + key->accept.end = -1; + return NB_OK; +} + +static int __lifetime_duration_modify(struct nb_cb_modify_args *args, bool send, + bool accept) +{ + struct key *key; + uint32_t duration; + time_t time; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + key = __dnode_get_key3(args->dnode, true); + time = yang_dnode_get_date_and_time(args->dnode, "../start-date-time"); + duration = yang_dnode_get_uint32(args->dnode, NULL); + + if (send) + key->send.end = time + duration; + if (accept) + key->accept.end = time + duration; + return NB_OK; +} + +static int __lifetime_end_date_time_modify(struct nb_cb_modify_args *args, + bool send, bool accept) +{ + struct key *key; + time_t time; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + key = __dnode_get_key3(args->dnode, true); + time = yang_dnode_get_date_and_time(args->dnode, NULL); + + if (send) + key->send.end = time; + if (accept) + key->accept.end = time; + return NB_OK; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime + */ +static int key_chains_key_chain_key_lifetime_send_accept_lifetime_create( + struct nb_cb_create_args *args) +{ + + return __lifetime_create(args, true, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/always + */ +static int key_chains_key_chain_key_lifetime_send_accept_lifetime_always_create( + struct nb_cb_create_args *args) +{ + return __lifetime_create(args, true, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/start-date-time + */ +static int +key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_modify( + struct nb_cb_modify_args *args) +{ + return __lifetime_start_date_time_modify(args, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/no-end-time + */ +static int +key_chains_key_chain_key_lifetime_send_accept_lifetime_no_end_time_create( + struct nb_cb_create_args *args) +{ + return __lifetime_no_end_time_create(args, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/duration + */ +static int key_chains_key_chain_key_lifetime_send_accept_lifetime_duration_modify( + struct nb_cb_modify_args *args) +{ + return __lifetime_duration_modify(args, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/end-date-time + */ +static int +key_chains_key_chain_key_lifetime_send_accept_lifetime_end_date_time_modify( + struct nb_cb_modify_args *args) +{ + return __lifetime_end_date_time_modify(args, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_create( + struct nb_cb_create_args *args) +{ + + return __lifetime_create(args, true, false, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/always + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_always_create( + struct nb_cb_create_args *args) +{ + return __lifetime_create(args, true, false, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/start-date-time + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_start_date_time_modify(args, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/no-end-time + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_no_end_time_create(struct nb_cb_create_args *args) +{ + return __lifetime_no_end_time_create(args, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/duration + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_duration_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_duration_modify(args, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/end-date-time + */ +static int key_chains_key_chain_key_lifetime_send_lifetime_end_date_time_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_end_date_time_modify(args, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_create( + struct nb_cb_create_args *args) +{ + + return __lifetime_create(args, false, true, false); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/always + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_always_create(struct nb_cb_create_args *args) +{ + return __lifetime_create(args, false, true, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/start-date-time + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_start_date_time_modify(args, false, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/no-end-time + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_no_end_time_create(struct nb_cb_create_args *args) +{ + return __lifetime_no_end_time_create(args, false, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/duration + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_duration_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_duration_modify(args, false, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/end-date-time + */ +static int key_chains_key_chain_key_lifetime_accept_lifetime_end_date_time_modify(struct nb_cb_modify_args *args) +{ + return __lifetime_end_date_time_modify(args, false, true); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/crypto-algorithm + */ +static int key_chains_key_chain_key_crypto_algorithm_modify(struct nb_cb_modify_args *args) +{ + static const char prefix[] = "ietf-key-chain:"; + static const int prefix_len = sizeof(prefix) - 1; + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + uint8_t hash_algo; + + if (args->event != NB_EV_VALIDATE && args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, NULL); + if (!strncmp(name, prefix, prefix_len)) + name += prefix_len; + hash_algo = keychain_get_algo_id_by_name(name); + + if (args->event == NB_EV_VALIDATE) { + if (!hash_algo) { + zlog_err("\"%s\" hash algo not supported", name); + return NB_ERR_VALIDATION; + } +#ifndef CRYPTO_OPENSSL + if (hash_algo == KEYCHAIN_ALGO_NULL) { + zlog_err("\"%s\" algo not supported, compile with --with-crypto=openssl", + name); + return NB_ERR_VALIDATION; + } +#endif /* CRYPTO_OPENSSL */ + return NB_OK; + } + + assert(args->event == NB_EV_APPLY); + name = yang_dnode_get_string(args->dnode, "../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../key-id"); + key = key_lookup(keychain, index); + key->hash_algo = hash_algo; + + keychain_touch(keychain); + return NB_OK; +} + +static int key_chains_key_chain_key_crypto_algorithm_destroy( + struct nb_cb_destroy_args *args) +{ + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../../key-id"); + key = key_lookup(keychain, index); + key->hash_algo = KEYCHAIN_ALGO_NULL; + keychain_touch(keychain); + + return NB_OK; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/key-string/keystring + */ +static int key_chains_key_chain_key_key_string_keystring_modify(struct nb_cb_modify_args *args) +{ + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../../key-id"); + key = key_lookup(keychain, index); + assert(key); + + + if (key->string) + XFREE(MTYPE_KEY, key->string); + key->string = XSTRDUP(MTYPE_KEY, + yang_dnode_get_string(args->dnode, NULL)); + + keychain_touch(keychain); + return NB_OK; +} + +static int key_chains_key_chain_key_key_string_keystring_destroy(struct nb_cb_destroy_args *args) +{ + struct keychain *keychain; + const char *name; + struct key *key; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "../../../name"); + keychain = keychain_lookup(name); + index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../../key-id"); + key = key_lookup(keychain, index); + assert(key); + + XFREE(MTYPE_KEY, key->string); + keychain_touch(keychain); + + return NB_OK; +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/send-lifetime-active + */ +static struct yang_data *key_chains_key_chain_key_send_lifetime_active_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct key *key = node->data; + time_t now = time(NULL); + bool active = false; + + if (key->send.start == 0) + active = true; + else if (key->send.start <= now) + if (key->send.end >= now || key->send.end == -1) + active = true; + + return yang_data_new_bool(args->xpath, active); +} + +/* + * XPath: /ietf-key-chain:key-chains/key-chain/key/accept-lifetime-active + */ +static struct yang_data *key_chains_key_chain_key_accept_lifetime_active_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct key *key = node->data; + time_t now = time(NULL); + bool active = false; + + if (key->accept.start == 0) + active = true; + else if (key->accept.start <= now) + if (key->accept.end >= now || key->accept.end == -1) + active = true; + + return yang_data_new_bool(args->xpath, active); +} + +static const char * const keychain_features[] = { + "independent-send-accept-lifetime", + NULL, +}; + +/* clang-format off */ +const struct frr_yang_module_info ietf_key_chain_info = { + .name = "ietf-key-chain", + .features = (const char **)keychain_features, + .nodes = { + { + .xpath = "/ietf-key-chain:key-chains/key-chain", + .cbs = { + .create = key_chains_key_chain_create, + .destroy = key_chains_key_chain_destroy, + .get_next = key_chains_key_chain_get_next, + .get_keys = key_chains_key_chain_get_keys, + .lookup_entry = key_chains_key_chain_lookup_entry, + .cli_show = key_chains_key_chain_cli_write, + .cli_show_end = key_chains_key_chain_cli_write_end, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/description", + .cbs = { + .modify = key_chains_key_chain_description_modify, + .destroy = key_chains_key_chain_description_destroy, + .cli_show = key_chains_key_chain_description_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/last-modified-timestamp", + .cbs = { + .get_elem = key_chains_key_chain_last_modified_timestamp_get_elem, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key", + .cbs = { + .create = key_chains_key_chain_key_create, + .destroy = key_chains_key_chain_key_destroy, + .get_next = key_chains_key_chain_key_get_next, + .get_keys = key_chains_key_chain_key_get_keys, + .lookup_entry = key_chains_key_chain_key_lookup_entry, + .cli_show = key_chains_key_chain_key_cli_write, + .cli_show_end = key_chains_key_chain_key_cli_write_end, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_accept_lifetime_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/always", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_accept_lifetime_always_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/start-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_modify, + .destroy = __destroy_nop, + .cli_show = key_chains_key_chain_key_lifetime_send_accept_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/no-end-time", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_accept_lifetime_no_end_time_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/duration", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_accept_lifetime_duration_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-accept-lifetime/end-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_accept_lifetime_end_date_time_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_lifetime_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/always", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_lifetime_always_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/start-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_modify, + .destroy = __destroy_nop, + .cli_show = key_chains_key_chain_key_lifetime_send_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/no-end-time", + .cbs = { + .create = key_chains_key_chain_key_lifetime_send_lifetime_no_end_time_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/duration", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_lifetime_duration_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/send-lifetime/end-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_send_lifetime_end_date_time_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime", + .cbs = { + .create = key_chains_key_chain_key_lifetime_accept_lifetime_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/always", + .cbs = { + .create = key_chains_key_chain_key_lifetime_accept_lifetime_always_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/start-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_modify, + .destroy = __destroy_nop, + .cli_show = key_chains_key_chain_key_lifetime_accept_lifetime_start_date_time_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/no-end-time", + .cbs = { + .create = key_chains_key_chain_key_lifetime_accept_lifetime_no_end_time_create, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/duration", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_accept_lifetime_duration_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/lifetime/accept-lifetime/end-date-time", + .cbs = { + .modify = key_chains_key_chain_key_lifetime_accept_lifetime_end_date_time_modify, + .destroy = __destroy_nop, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/crypto-algorithm", + .cbs = { + .modify = key_chains_key_chain_key_crypto_algorithm_modify, + .destroy = key_chains_key_chain_key_crypto_algorithm_destroy, + .cli_show = key_chains_key_chain_key_crypto_algorithm_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/key-string/keystring", + .cbs = { + .modify = key_chains_key_chain_key_key_string_keystring_modify, + .destroy = key_chains_key_chain_key_key_string_keystring_destroy, + .cli_show = key_chains_key_chain_key_key_string_keystring_cli_write, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/send-lifetime-active", + .cbs = { + .get_elem = key_chains_key_chain_key_send_lifetime_active_get_elem, + } + }, + { + .xpath = "/ietf-key-chain:key-chains/key-chain/key/accept-lifetime-active", + .cbs = { + .get_elem = key_chains_key_chain_key_accept_lifetime_active_get_elem, + } + }, + { + .xpath = NULL, + }, + }, +}; diff --git a/lib/ldp_sync.c b/lib/ldp_sync.c new file mode 100644 index 0000000..d55819d --- /dev/null +++ b/lib/ldp_sync.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ldp_sync.c: LDP-SYNC handling routines + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#include + +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "log.h" +#include "frrevent.h" +#include "stream.h" +#include "zclient.h" +#include "table.h" +#include "vty.h" +#include "ldp_sync.h" + +/* Library code */ +DEFINE_MTYPE_STATIC(LIB, LDP_SYNC_INFO, "LDP SYNC info"); + +/* + * ldp_sync_info_create - Allocate the LDP_SYNC information + */ +struct ldp_sync_info *ldp_sync_info_create(void) +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = XCALLOC(MTYPE_LDP_SYNC_INFO, + sizeof(struct ldp_sync_info)); + assert(ldp_sync_info); + + ldp_sync_info->flags = 0; + ldp_sync_info->enabled = LDP_IGP_SYNC_DEFAULT; + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; + ldp_sync_info->t_holddown = NULL; + return ldp_sync_info; +} + +/* + * ldp_sync_info_free - Free the LDP_SYNC information. + */ +void ldp_sync_info_free(struct ldp_sync_info **ldp_sync_info) +{ + if (*ldp_sync_info) + XFREE(MTYPE_LDP_SYNC_INFO, *ldp_sync_info); +} + +bool ldp_sync_if_is_enabled(struct ldp_sync_info *ldp_sync_info) +{ + /* return true if LDP-SYNC is configured on this interface */ + if (ldp_sync_info && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED && + ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP) + return true; + + return false; +} + +bool ldp_sync_if_down(struct ldp_sync_info *ldp_sync_info) +{ + /* Stop LDP-SYNC on this interface: + * if holddown timer is running stop it + * update state + */ + if (ldp_sync_info && ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) { + EVENT_OFF(ldp_sync_info->t_holddown); + + if (ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_UP) + ldp_sync_info->state = + LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + return true; + } + + return false; +} diff --git a/lib/ldp_sync.h b/lib/ldp_sync.h new file mode 100644 index 0000000..3a6ae5b --- /dev/null +++ b/lib/ldp_sync.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Defines and structures common to LDP-Sync for OSPFv2 and OSPFv3 and ISIS + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#ifndef _LIBLDPSYNC_H +#define _LIBLDPSYNC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* LDP-IGP Sync values */ +#define LDP_SYNC_FLAG_ENABLE (1 << 0) /* LDP-SYNC enabled */ +#define LDP_SYNC_FLAG_HOLDDOWN (1 << 1) /* Holddown timer enabled */ +#define LDP_SYNC_FLAG_IF_CONFIG (1 << 2) /* LDP-SYNC enabled on interface */ +#define LDP_SYNC_FLAG_SET_METRIC (1 << 3) /* Metric has been set on ISIS intf */ + +#define LDP_IGP_SYNC_DEFAULT 0 +#define LDP_IGP_SYNC_ENABLED 1 + +#define LDP_IGP_SYNC_STATE_NOT_REQUIRED 0 +#define LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP 1 +#define LDP_IGP_SYNC_STATE_REQUIRED_UP 2 + +#define LDP_IGP_SYNC_HOLDDOWN_DEFAULT 0 + +/* LDP-IGP Sync structures */ +struct ldp_sync_info_cmd { + uint16_t flags; + uint16_t holddown; /* timer value */ +}; + +struct ldp_sync_info { + uint16_t flags; /* indicate if set on interface or globally */ + uint8_t enabled; /* enabled */ + uint8_t state; /* running state */ + uint16_t holddown; /* timer value */ + struct event *t_holddown; /* holddown timer*/ + uint32_t metric[2]; /* isis interface metric */ +}; + +/* Prototypes. */ +extern struct ldp_sync_info *ldp_sync_info_create(void); +extern bool ldp_sync_if_is_enabled(struct ldp_sync_info *ldp_sync_info); +extern bool ldp_sync_if_down(struct ldp_sync_info *ldp_sync_info); +extern void ldp_sync_info_free(struct ldp_sync_info **ldp_sync_info); + +struct ldp_igp_sync_announce { + int proto; +}; + +struct ldp_igp_sync_if_state { + ifindex_t ifindex; + bool sync_start; +}; + +struct ldp_igp_sync_if_state_req { + int proto; + ifindex_t ifindex; + char name[IFNAMSIZ]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBLDPSYNC_H */ diff --git a/lib/lib_errors.c b/lib/lib_errors.c new file mode 100644 index 0000000..9d6c043 --- /dev/null +++ b/lib/lib_errors.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Library-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "lib_errors.h" + +/* clang-format off */ +static struct log_ref ferr_lib_warn[] = { + { + .code = EC_LIB_SNMP, + .title = "SNMP has discovered a warning", + .description = "The SNMP AgentX library has returned a warning that we should report to the end user", + .suggestion = "Gather Log data and open an Issue.", + }, + { + .code = EC_LIB_STREAM, + .title = "The stream subsystem has encountered an error", + .description = "During sanity checking stream.c has detected an error in the data associated with a particular stream", + .suggestion = "Gather log data and open an Issue, restart FRR", + }, + { + .code = EC_LIB_LINUX_NS, + .title = "The Linux namespace subsystem has encountered a parsing error", + .description = "During system startup an invalid parameter for the namespace was give to FRR", + .suggestion = "Gather log data and open an Issue. restart FRR", + }, + { + .code = EC_LIB_SLOW_THREAD_CPU, + .title = "The Event subsystem has detected a slow cpu time process", + .description = "The Event subsystem has detected a slow process, this typically indicates that FRR is having trouble completing work in a timely manner. This can be either a misconfiguration, bug, or some combination thereof. In this case total CPU time was over 5 seconds. Which indicates that FRR is very busy doing some work and should be addressed", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_SLOW_THREAD_WALL, + .title = "The Event subsystem has detected a slow wall time process", + .description = "The Event subsystem has detected a slow process, this typically indicates that FRR is having trouble completing work in a timely manner. This can be either a misconfiguration, bug or some combination thereof. In this case total WALL time was over 5 seconds. Which indicates that FRR might be having trouble being scheduled or some system call is delaying", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_STARVE_THREAD, + .title = "The Event subsystem has detected a thread starvation issue", + .description = "The event subsystem has detected a thread starvation issue. This typically indicates that the system FRR is running on is heavily loaded and this load might be impacting FRR's ability to handle events in a timely fashion", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_NO_THREAD, + .title = "The Event subsystem has detected an internal FD problem", + .description = "The Event subsystem has detected a file descriptor read/write event without an associated handling function. This is a bug, please collect log data and open an issue.", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_TIMER_TOO_LONG, + .title = "The Event subsystem has detected an internal timer that is scheduled to pop in greater than one year", + .description = "The Event subsystem has detected a timer being started that will pop in a timer that is greater than one year. This is a bug, please collect log data and open an issue.", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_RMAP_RECURSION_LIMIT, + .title = "Reached the Route-Map Recursion Limit", + .description = "The Route-Map subsystem has detected a route-map depth of RMAP_RECURSION_LIMIT and has stopped processing", + .suggestion = "Re-work the Route-Map in question to not have so many route-map statements, or recompile FRR with a higher limit", + }, + { + .code = EC_LIB_BACKUP_CONFIG, + .title = "Unable to open configuration file", + .description = "The config subsystem attempted to read in it's configuration file which failed, so we are falling back to the backup config file to see if it is available", + .suggestion = "Create configuration file", + }, + { + .code = EC_LIB_VRF_LENGTH, + .title = "The VRF subsystem has encountered a parsing error", + .description = "The VRF subsystem, during initialization, has found a parsing error with input it has received", + .suggestion = "Check the length of the vrf name and adjust accordingly", + }, + { + .code = EC_LIB_YANG_DATA_TRUNCATED, + .title = "YANG data truncation", + .description = "The northbound subsystem has detected that YANG data has been truncated as the given buffer wasn't big enough", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_YANG_UNKNOWN_DATA_PATH, + .title = "Unknown YANG data path", + .description = "The northbound subsystem has detected an unknown YANG data path", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_YANG_TRANSLATOR_LOAD, + .title = "Unable to load YANG module translator", + .description = "The northbound subsystem has detected an error while loading a YANG module translator", + .suggestion = "Ensure the YANG module translator file is valid. See documentation for further information.", + }, + { + .code = EC_LIB_YANG_TRANSLATION_ERROR, + .title = "YANG translation error", + .description = "The northbound subsystem has detected an error while performing a YANG XPath translation", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_NB_DATABASE, + .title = "The northbound database wasn't initialized correctly", + .description = "An error occurred while initializing the northbound database. As a result, the configuration rollback feature won't work as expected.", + .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct." + }, + { + .code = EC_LIB_NB_CB_UNNEEDED, + .title = "Unneeded northbound callback", + .description = "The northbound subsystem, during initialization, has detected a callback that doesn't need to be implemented", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_CB_CONFIG_VALIDATE, + .title = "A northbound configuration callback has failed in the VALIDATE phase", + .description = "A callback used to process a configuration change has returned a validation error", + .suggestion = "The provided configuration is invalid. Fix any inconsistency and try again.", + }, + { + .code = EC_LIB_NB_CB_CONFIG_PREPARE, + .title = "A northbound configuration callback has failed in the PREPARE phase", + .description = "A callback used to process a configuration change has returned a resource allocation error", + .suggestion = "The system might be running out of resources. Check the log for more details.", + }, + { + .code = EC_LIB_NB_CB_STATE, + .title = "A northbound callback for operational data has failed", + .description = "The northbound subsystem has detected that a callback used to fetch operational data has returned an error", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_LIB_NB_CB_RPC, + .title = "A northbound RPC callback has failed", + .description = "The northbound subsystem has detected that a callback used to process YANG RPCs/actions has returned an error", + .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.", + }, + { + .code = EC_LIB_NB_CANDIDATE_INVALID, + .title = "Invalid candidate configuration", + .description = "The northbound subsystem failed to validate a candidate configuration", + .suggestion = "Check the log messages to see the validation errors and edit the candidate configuration to fix them", + }, + { + .code = EC_LIB_NB_CANDIDATE_EDIT_ERROR, + .title = "Failure to edit a candidate configuration", + .description = "The northbound subsystem failed to edit a candidate configuration", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_OPERATIONAL_DATA, + .title = "Failure to obtain operational data", + .description = "The northbound subsystem failed to obtain YANG-modeled operational data", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_TRANSACTION_CREATION_FAILED, + .title = "Failure to create a configuration transaction", + .description = "The northbound subsystem failed to create a configuration transaction", + .suggestion = "The log message should contain further details on the specific error that occurred; investigate the reported error.", + }, + { + .code = EC_LIB_NB_TRANSACTION_RECORD_FAILED, + .title = "Failure to record a configuration transaction", + .description = "The northbound subsystem failed to record a configuration transaction in the northbound database", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = END_FERR, + }, +}; + +static struct log_ref ferr_lib_err[] = { + { + .code = EC_LIB_PRIVILEGES, + .title = "Failure to raise or lower privileges", + .description = "FRR attempted to raise or lower its privileges and was unable to do so", + .suggestion = "Ensure that you are running FRR as the frr user and that the user has sufficient privileges to properly access root privileges" + }, + { + .code = EC_LIB_VRF_START, + .title = "VRF Failure on Start", + .description = "Upon startup FRR failed to properly initialize and startup the VRF subsystem", + .suggestion = "Ensure that there is sufficient memory to start processes and restart FRR", + }, + { + .code = EC_LIB_SOCKET, + .title = "Socket Error", + .description = "When attempting to access a socket a system error has occurred and we were unable to properly complete the request", + .suggestion = "Ensure that there are sufficient system resources available and ensure that the frr user has sufficient permissions to work. If necessary open an Issue", + }, + { + .code = EC_LIB_ZAPI_MISSMATCH, + .title = "ZAPI Error", + .description = "A version miss-match has been detected between zebra and client protocol", + .suggestion = "Two different versions of FRR have been installed and the install is not properly setup. Completely stop FRR, remove it from the system and reinstall. Typically only developers should see this issue." + }, + { + .code = EC_LIB_ZAPI_ENCODE, + .title = "ZAPI Error", + .description = "The ZAPI subsystem has detected an encoding issue, between zebra and a client protocol", + .suggestion = "Gather data and open an Issue, also Restart FRR" + }, + { + .code = EC_LIB_ZAPI_SOCKET, + .title = "ZAPI Error", + .description = "The ZAPI subsystem has detected a socket error between zebra and a client", + .suggestion = "Restart FRR" + }, + { + .code = EC_LIB_SYSTEM_CALL, + .title = "System Call Error", + .description = "FRR has detected a error from using a vital system call and has probably already exited", + .suggestion = "Ensure permissions are correct for FRR files, users and groups are correct. Additionally check that sufficient system resources are available." + }, + { + .code = EC_LIB_VTY, + .title = "VTY Subsystem Error", + .description = "FRR has detected a problem with the specified configuration file", + .suggestion = "Ensure configuration file exists and has correct permissions for operations Additionally ensure that all config lines are correct as well", + }, + { + .code = EC_LIB_INTERFACE, + .title = "Interface Subsystem Error", + .description = "FRR has detected a problem with interface data from the kernel as it deviates from what we would expect to happen via normal netlink messaging", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_NS, + .title = "NameSpace Subsystem Error", + .description = "FRR has detected a problem with NameSpace data from the kernel as it deviates from what we would expect to happen via normal kernel messaging", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_DEVELOPMENT, + .title = "Developmental Escape Error", + .description = "FRR has detected an issue where new development has not properly updated all code paths.", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_LIB_ZMQ, + .title = "ZMQ Subsystem Error", + .description = "FRR has detected an issue with the Zero MQ subsystem and ZeroMQ is not working properly now", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_UNAVAILABLE, + .title = "Feature or system unavailable", + .description = "FRR was not compiled with support for a particular feature, or it is not available on the current platform", + .suggestion = "Recompile FRR with the feature enabled, or find out what platforms support the feature" + }, + { + .code = EC_LIB_YANG_MODULE_LOAD, + .title = "Unable to load YANG module from the file system", + .description = "The northbound subsystem has detected an error while loading a YANG module from the file system", + .suggestion = "Ensure all FRR YANG modules were installed correctly in the system.", + }, + { + .code = EC_LIB_YANG_MODULE_LOADED_ALREADY, + .title = "Attempt to load a YANG module that is already loaded", + .description = "The northbound subsystem has detected an attempt to load a YANG module that is already loaded", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_YANG_DATA_CONVERT, + .title = "YANG data conversion error", + .description = "An error has occurred while converting a YANG data value from string to binary representation or vice-versa", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_YANG_DNODE_NOT_FOUND, + .title = "YANG data node not found", + .description = "The northbound subsystem failed to find a YANG data node that was supposed to exist", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_CB_MISSING, + .title = "Missing northbound callback", + .description = "The northbound subsystem, during initialization, has detected a missing callback for one node of the loaded YANG modules", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_CB_INVALID_PRIO, + .title = "Northbound callback has an invalid priority", + .description = "The northbound subsystem, during initialization, has detected a callback whose priority is invalid", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_NB_CBS_VALIDATION, + .title = "Failure to validate the northbound callbacks", + .description = "The northbound subsystem, during initialization, has detected one or more errors while loading the northbound callbacks", + .suggestion = "This is a bug; please report it" + }, + { + .code = EC_LIB_LIBYANG, + .title = "The libyang library returned an error", + .description = "The northbound subsystem has detected that the libyang library returned an error", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_LIBYANG_PLUGIN_LOAD, + .title = "Failure to load a libyang plugin", + .description = "The northbound subsystem, during initialization, has detected that a libyang plugin failed to be loaded", + .suggestion = "Check if the FRR libyang plugins were installed correctly in the system", + }, + { + .code = EC_LIB_SYSREPO_INIT, + .title = "Sysrepo initialization error", + .description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin", + .suggestion = "Check if Sysrepo is installed correctly in the system", + }, + { + .code = EC_LIB_SYSREPO_DATA_CONVERT, + .title = "Sysrepo data conversion error", + .description = "An error has occurred while converting a YANG data value to the Sysrepo format", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_LIBSYSREPO, + .title = "libsysrepo error", + .description = "The northbound subsystem has detected that the libsysrepo library returned an error", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_LIB_GRPC_INIT, + .title = "gRPC initialization error", + .description = "Upon startup FRR failed to properly initialize and startup the gRPC northbound plugin", + .suggestion = "Check if the gRPC libraries are installed correctly in the system.", + }, + { + .code = EC_LIB_NB_CB_CONFIG_ABORT, + .title = "A northbound configuration callback has failed in the ABORT phase", + .description = "A callback used to process a configuration change has returned an error while trying to abort a change", + .suggestion = "Gather log data and open an Issue.", + }, + { + .code = EC_LIB_NB_CB_CONFIG_APPLY, + .title = "A northbound configuration callback has failed in the APPLY phase", + .description = "A callback used to process a configuration change has returned an error while applying the changes", + .suggestion = "Gather log data and open an Issue.", + }, + { + .code = EC_LIB_RESOLVER, + .title = "DNS Resolution", + .description = "An error was detected while attempting to resolve a hostname", + .suggestion = "Ensure that DNS is working properly and the hostname is configured in dns. If you are still seeing this error, open an issue" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void lib_error_init(void) +{ + log_ref_add(ferr_lib_warn); + log_ref_add(ferr_lib_err); +} diff --git a/lib/lib_errors.h b/lib/lib_errors.h new file mode 100644 index 0000000..9e0d539 --- /dev/null +++ b/lib/lib_errors.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Library-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __LIB_ERRORS_H__ +#define __LIB_ERRORS_H__ + +#include "lib/ferr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum lib_log_refs { + EC_LIB_PRIVILEGES = LIB_FERR_START, + EC_LIB_VRF_START, + EC_LIB_SOCKET, + EC_LIB_ZAPI_MISSMATCH, + EC_LIB_ZAPI_ENCODE, + EC_LIB_ZAPI_SOCKET, + EC_LIB_SYSTEM_CALL, + EC_LIB_VTY, + EC_LIB_INTERFACE, + EC_LIB_NS, + EC_LIB_DEVELOPMENT, + EC_LIB_ZMQ, + EC_LIB_UNAVAILABLE, + EC_LIB_SNMP, + EC_LIB_STREAM, + EC_LIB_LINUX_NS, + EC_LIB_SLOW_THREAD_CPU, + EC_LIB_SLOW_THREAD_WALL, + EC_LIB_STARVE_THREAD, + EC_LIB_NO_THREAD, + EC_LIB_TIMER_TOO_LONG, + EC_LIB_RMAP_RECURSION_LIMIT, + EC_LIB_BACKUP_CONFIG, + EC_LIB_VRF_LENGTH, + EC_LIB_YANG_MODULE_LOAD, + EC_LIB_YANG_MODULE_LOADED_ALREADY, + EC_LIB_YANG_DATA_CONVERT, + EC_LIB_YANG_DATA_TRUNCATED, + EC_LIB_YANG_UNKNOWN_DATA_PATH, + EC_LIB_YANG_DNODE_NOT_FOUND, + EC_LIB_YANG_TRANSLATOR_LOAD, + EC_LIB_YANG_TRANSLATION_ERROR, + EC_LIB_NB_DATABASE, + EC_LIB_NB_CB_UNNEEDED, + EC_LIB_NB_CB_MISSING, + EC_LIB_NB_CB_INVALID_PRIO, + EC_LIB_NB_CBS_VALIDATION, + EC_LIB_NB_CB_CONFIG_VALIDATE, + EC_LIB_NB_CB_CONFIG_PREPARE, + EC_LIB_NB_CB_CONFIG_ABORT, + EC_LIB_NB_CB_CONFIG_APPLY, + EC_LIB_NB_CB_STATE, + EC_LIB_NB_CB_RPC, + EC_LIB_NB_CANDIDATE_INVALID, + EC_LIB_NB_CANDIDATE_EDIT_ERROR, + EC_LIB_NB_OPERATIONAL_DATA, + EC_LIB_NB_TRANSACTION_CREATION_FAILED, + EC_LIB_NB_TRANSACTION_RECORD_FAILED, + EC_LIB_LIBYANG, + EC_LIB_LIBYANG_PLUGIN_LOAD, + EC_LIB_SYSREPO_INIT, + EC_LIB_SYSREPO_DATA_CONVERT, + EC_LIB_LIBSYSREPO, + EC_LIB_GRPC_INIT, + EC_LIB_ID_CONSISTENCY, + EC_LIB_ID_EXHAUST, + EC_LIB_RESOLVER, +}; + +extern void lib_error_init(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/lib_vty.c b/lib/lib_vty.c new file mode 100644 index 0000000..c13d88a --- /dev/null +++ b/lib/lib_vty.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Assorted library VTY commands + * + * Copyright (C) 1998 Kunihiro Ishiguro + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +#include +/* malloc.h is generally obsolete, however GNU Libc mallinfo wants it. */ +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif +#include +#ifdef HAVE_LINK_H +#include +#endif + +#include "log.h" +#include "memory.h" +#include "module.h" +#include "defaults.h" +#include "lib_vty.h" +#include "northbound_cli.h" + +/* Looking up memory status from vty interface. */ +#include "vector.h" +#include "vty.h" +#include "command.h" + +#if defined(HAVE_MALLINFO2) || defined(HAVE_MALLINFO) +static int show_memory_mallinfo(struct vty *vty) +{ +#if defined(HAVE_MALLINFO2) + struct mallinfo2 minfo = mallinfo2(); +#elif defined(HAVE_MALLINFO) + struct mallinfo minfo = mallinfo(); +#endif + char buf[MTYPE_MEMSTR_LEN]; + + vty_out(vty, "System allocator statistics:\n"); + vty_out(vty, " Total heap allocated: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.arena)); + vty_out(vty, " Holding block headers: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.hblkhd)); + vty_out(vty, " Used small blocks: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.usmblks)); + vty_out(vty, " Used ordinary blocks: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.uordblks)); + vty_out(vty, " Free small blocks: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fsmblks)); + vty_out(vty, " Free ordinary blocks: %s\n", + mtype_memstr(buf, MTYPE_MEMSTR_LEN, minfo.fordblks)); + vty_out(vty, " Ordinary blocks: %ld\n", + (unsigned long)minfo.ordblks); + vty_out(vty, " Small blocks: %ld\n", + (unsigned long)minfo.smblks); + vty_out(vty, " Holding blocks: %ld\n", + (unsigned long)minfo.hblks); + vty_out(vty, "(see system documentation for 'mallinfo' for meaning)\n"); + return 1; +} +#endif /* HAVE_MALLINFO */ + +static int qmem_walker(void *arg, struct memgroup *mg, struct memtype *mt) +{ + struct vty *vty = arg; + if (!mt) { + vty_out(vty, "--- qmem %s ---\n", mg->name); + vty_out(vty, "%-30s: %8s %-8s%s %8s %9s\n", + "Type", "Current#", " Size", +#ifdef HAVE_MALLOC_USABLE_SIZE + " Total", +#else + "", +#endif + "Max#", +#ifdef HAVE_MALLOC_USABLE_SIZE + "MaxBytes" +#else + "" +#endif + ); + } else { + if (mt->n_max != 0) { + char size[32]; + snprintf(size, sizeof(size), "%6zu", mt->size); +#ifdef HAVE_MALLOC_USABLE_SIZE +#define TSTR " %9zu" +#define TARG , mt->total +#define TARG2 , mt->max_size +#else +#define TSTR "" +#define TARG +#define TARG2 +#endif + vty_out(vty, "%-30s: %8zu %-8s"TSTR" %8zu"TSTR"\n", + mt->name, + mt->n_alloc, + mt->size == 0 ? "" + : mt->size == SIZE_VAR + ? "variable" + : size + TARG, + mt->n_max + TARG2); + } + } + return 0; +} + + +DEFUN_NOSH (show_memory, + show_memory_cmd, + "show memory", + "Show running system information\n" + "Memory statistics\n") +{ +#ifdef HAVE_MALLINFO + show_memory_mallinfo(vty); +#endif /* HAVE_MALLINFO */ + + qmem_walk(qmem_walker, vty); + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_modules, + show_modules_cmd, + "show modules", + "Show running system information\n" + "Loaded modules\n") +{ + struct frrmod_runtime *plug = frrmod_list; + + vty_out(vty, "%-12s %-25s %s\n\n", "Module Name", "Version", + "Description"); + while (plug) { + const struct frrmod_info *i = plug->info; + + vty_out(vty, "%-12s %-25s %s\n", i->name, i->version, + i->description); + if (plug->dl_handle) { +#ifdef HAVE_DLINFO_ORIGIN + char origin[MAXPATHLEN] = ""; + dlinfo(plug->dl_handle, RTLD_DI_ORIGIN, &origin); +#ifdef HAVE_DLINFO_LINKMAP + const char *name; + struct link_map *lm = NULL; + dlinfo(plug->dl_handle, RTLD_DI_LINKMAP, &lm); + if (lm) { + name = strrchr(lm->l_name, '/'); + name = name ? name + 1 : lm->l_name; + vty_out(vty, "\tfrom: %s/%s\n", origin, name); + } +#else + vty_out(vty, "\tfrom: %s \n", origin, plug->load_name); +#endif +#else + vty_out(vty, "\tfrom: %s\n", plug->load_name); +#endif + } + plug = plug->next; + } + + vty_out(vty, "pid: %u\n", (uint32_t)(getpid())); + + return CMD_SUCCESS; +} + +DEFUN (frr_defaults, + frr_defaults_cmd, + "frr defaults PROFILE...", + "FRRouting global parameters\n" + "set of configuration defaults used\n" + "profile string\n") +{ + char *profile = argv_concat(argv, argc, 2); + int rv = CMD_SUCCESS; + + if (!frr_defaults_profile_valid(profile)) { + vty_out(vty, "%% WARNING: profile %s is not known in this version\n", + profile); + rv = CMD_WARNING; + } + frr_defaults_profile_set(profile); + XFREE(MTYPE_TMP, profile); + return rv; +} + +DEFUN (frr_version, + frr_version_cmd, + "frr version VERSION...", + "FRRouting global parameters\n" + "version configuration was written by\n" + "version string\n") +{ + char *version = argv_concat(argv, argc, 2); + + frr_defaults_version_set(version); + XFREE(MTYPE_TMP, version); + return CMD_SUCCESS; +} + +static struct call_back { + time_t readin_time; + + void (*start_config)(void); + void (*end_config)(void); +} callback; + + +DEFUN_NOSH(start_config, start_config_cmd, "XFRR_start_configuration", + "The Beginning of Configuration\n") +{ + callback.readin_time = monotime(NULL); + + vty->pending_allowed = 1; + + if (callback.start_config) + (*callback.start_config)(); + + return CMD_SUCCESS; +} + +DEFUN_NOSH(end_config, end_config_cmd, "XFRR_end_configuration", + "The End of Configuration\n") +{ + time_t readin_time; + char readin_time_str[MONOTIME_STRLEN]; + int ret; + + readin_time = monotime(NULL); + readin_time -= callback.readin_time; + + frrtime_to_interval(readin_time, readin_time_str, + sizeof(readin_time_str)); + + vty->pending_allowed = 0; + ret = nb_cli_pending_commit_check(vty); + + zlog_info("Configuration Read in Took: %s", readin_time_str); + zlog_debug("%s: VTY:%p, pending SET-CFG: %u", __func__, vty, + (uint32_t)vty->mgmt_num_pending_setcfg); + + /* + * If (and only if) we have sent any CLI config commands to MGMTd + * FE interface using vty_mgmt_send_config_data() without implicit + * commit before, should we need to send an explicit COMMIT-REQ now + * to apply all those commands at once. + */ + if (vty->mgmt_num_pending_setcfg && vty_mgmt_fe_enabled()) + vty_mgmt_send_commit_config(vty, false, false); + + if (callback.end_config) + (*callback.end_config)(); + + return ret; +} + +void cmd_init_config_callbacks(void (*start_config_cb)(void), + void (*end_config_cb)(void)) +{ + callback.start_config = start_config_cb; + callback.end_config = end_config_cb; +} + + +static void defaults_autocomplete(vector comps, struct cmd_token *token) +{ + const char **p; + + for (p = frr_defaults_profiles; *p; p++) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, *p)); +} + +static const struct cmd_variable_handler default_var_handlers[] = { + {.tokenname = "PROFILE", .completions = defaults_autocomplete}, + {.completions = NULL}, +}; + +void lib_cmd_init(void) +{ + cmd_variable_handler_register(default_var_handlers); + + install_element(CONFIG_NODE, &frr_defaults_cmd); + install_element(CONFIG_NODE, &frr_version_cmd); + + install_element(VIEW_NODE, &show_memory_cmd); + install_element(VIEW_NODE, &show_modules_cmd); + + install_element(CONFIG_NODE, &start_config_cmd); + install_element(CONFIG_NODE, &end_config_cmd); +} + +/* Stats querying from users */ +/* Return a pointer to a human friendly string describing + * the byte count passed in. E.g: + * "0 bytes", "2048 bytes", "110kB", "500MiB", "11GiB", etc. + * Up to 4 significant figures will be given. + * The pointer returned may be NULL (indicating an error) + * or point to the given buffer, or point to static storage. + */ +const char *mtype_memstr(char *buf, size_t len, unsigned long bytes) +{ + unsigned int m, k; + + /* easy cases */ + if (!bytes) + return "0 bytes"; + if (bytes == 1) + return "1 byte"; + + /* + * When we pass the 2gb barrier mallinfo() can no longer report + * correct data so it just does something odd... + * Reporting like Terrabytes of data. Which makes users... + * edgy.. yes edgy that's the term for it. + * So let's just give up gracefully + */ + if (bytes > 0x7fffffff) + return "> 2GB"; + + m = bytes >> 20; + k = bytes >> 10; + + if (m > 10) { + if (bytes & (1 << 19)) + m++; + snprintf(buf, len, "%d MiB", m); + } else if (k > 10) { + if (bytes & (1 << 9)) + k++; + snprintf(buf, len, "%d KiB", k); + } else + snprintf(buf, len, "%ld bytes", bytes); + + return buf; +} diff --git a/lib/lib_vty.h b/lib/lib_vty.h new file mode 100644 index 0000000..4767332 --- /dev/null +++ b/lib/lib_vty.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Memory management routine + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_LIB_VTY_H +#define _ZEBRA_LIB_VTY_H + +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void lib_cmd_init(void); + +/* Human friendly string for given byte count */ +#define MTYPE_MEMSTR_LEN 20 +extern const char *mtype_memstr(char *, size_t, unsigned long); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_LIB_VTY_H */ diff --git a/lib/libagentx.c b/lib/libagentx.c new file mode 100644 index 0000000..2382657 --- /dev/null +++ b/lib/libagentx.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* SNMP cli support + * Copyright (C) 2024 Donald Sharp NVIDIA Corporation + */ +#include + +#include "lib/hook.h" +#include "lib/libagentx.h" +#include "command.h" + +DEFINE_HOOK(agentx_cli_enabled, (), ()); +DEFINE_HOOK(agentx_cli_disabled, (), ()); + +bool agentx_enabled; + +/* AgentX node. */ +static int config_write_agentx(struct vty *vty) +{ + if (agentx_enabled) + vty_out(vty, "agentx\n"); + return 1; +} + +static struct cmd_node agentx_node = { + .name = "smux", + .node = SMUX_NODE, + .prompt = "", + .config_write = config_write_agentx, +}; + +DEFUN(agentx_enable, agentx_enable_cmd, "agentx", + "SNMP AgentX protocol settings\n") +{ + if (!hook_have_hooks(agentx_cli_enabled)) { + zlog_info( + "agentx specified but the agentx Module is not loaded, is this intentional?"); + + return CMD_SUCCESS; + } + + hook_call(agentx_cli_enabled); + + return CMD_SUCCESS; +} + +DEFUN(no_agentx, no_agentx_cmd, "no agentx", + NO_STR "SNMP AgentX protocol settings\n") +{ + vty_out(vty, "SNMP AgentX support cannot be disabled once enabled\n"); + if (!hook_call(agentx_cli_disabled)) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +void libagentx_init(void) +{ + agentx_enabled = false; + + install_node(&agentx_node); + install_element(CONFIG_NODE, &agentx_enable_cmd); + install_element(CONFIG_NODE, &no_agentx_cmd); +} diff --git a/lib/libagentx.h b/lib/libagentx.h new file mode 100644 index 0000000..c3246d9 --- /dev/null +++ b/lib/libagentx.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* SNMP cli support + * Copyright (C) 2024 Donald Sharp NVIDIA Corporation + */ +#ifndef __LIBAGENTX_H__ +#define __LIBAGENTX_H__ + +extern void libagentx_init(void); +extern bool agentx_enabled; + +DECLARE_HOOK(agentx_cli_enabled, (), ()); +DECLARE_HOOK(agentx_cli_disabled, (), ()); + +#endif diff --git a/lib/libfrr.c b/lib/libfrr.c new file mode 100644 index 0000000..cc60cfb --- /dev/null +++ b/lib/libfrr.c @@ -0,0 +1,1465 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libfrr overall management functions + * + * Copyright (C) 2016 David Lamparter for NetDEF, Inc. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "libfrr.h" +#include "getopt.h" +#include "privs.h" +#include "vty.h" +#include "command.h" +#include "lib/version.h" +#include "lib_vty.h" +#include "log_vty.h" +#include "zclient.h" +#include "module.h" +#include "network.h" +#include "lib_errors.h" +#include "db.h" +#include "northbound_cli.h" +#include "northbound_db.h" +#include "debug.h" +#include "frrcu.h" +#include "frr_pthread.h" +#include "defaults.h" +#include "frrscript.h" +#include "systemd.h" + +#include "lib/config_paths.h" + +DEFINE_HOOK(frr_early_init, (struct event_loop * tm), (tm)); +DEFINE_HOOK(frr_late_init, (struct event_loop * tm), (tm)); +DEFINE_HOOK(frr_config_pre, (struct event_loop * tm), (tm)); +DEFINE_HOOK(frr_config_post, (struct event_loop * tm), (tm)); +DEFINE_KOOH(frr_early_fini, (), ()); +DEFINE_KOOH(frr_fini, (), ()); + +const char frr_sysconfdir[] = SYSCONFDIR; +char frr_runstatedir[256] = FRR_RUNSTATE_PATH; +char frr_libstatedir[256] = FRR_LIBSTATE_PATH; +const char frr_moduledir[] = MODULE_PATH; +const char frr_scriptdir[] = SCRIPT_PATH; + +char frr_protoname[256] = "NONE"; +char frr_protonameinst[256] = "NONE"; + +char config_default[512]; +char frr_zclientpath[512]; +static char pidfile_default[1024]; +#ifdef HAVE_SQLITE3 +static char dbfile_default[1024]; +#endif +static char vtypath_default[512]; + +/* cleared in frr_preinit(), then re-set after daemonizing */ +bool frr_is_after_fork = true; +bool debug_memstats_at_exit = false; +static bool nodetach_term, nodetach_daemon; +static uint64_t startup_fds; + +static char comb_optstr[256]; +static struct option comb_lo[64]; +static struct option *comb_next_lo = &comb_lo[0]; +static char comb_helpstr[4096]; + +struct optspec { + const char *optstr; + const char *helpstr; + const struct option *longopts; +}; + +static void opt_extend(const struct optspec *os) +{ + const struct option *lo; + + strlcat(comb_optstr, os->optstr, sizeof(comb_optstr)); + strlcat(comb_helpstr, os->helpstr, sizeof(comb_helpstr)); + for (lo = os->longopts; lo->name; lo++) + memcpy(comb_next_lo++, lo, sizeof(*lo)); +} + + +#define OPTION_VTYSOCK 1000 +#define OPTION_MODULEDIR 1002 +#define OPTION_LOG 1003 +#define OPTION_LOGLEVEL 1004 +#define OPTION_TCLI 1005 +#define OPTION_DB_FILE 1006 +#define OPTION_LOGGING 1007 +#define OPTION_LIMIT_FDS 1008 +#define OPTION_SCRIPTDIR 1009 + +static const struct option lo_always[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {"daemon", no_argument, NULL, 'd'}, + {"module", no_argument, NULL, 'M'}, + {"profile", required_argument, NULL, 'F'}, + {"pathspace", required_argument, NULL, 'N'}, + {"vrfdefaultname", required_argument, NULL, 'o'}, + {"vty_socket", required_argument, NULL, OPTION_VTYSOCK}, + {"moduledir", required_argument, NULL, OPTION_MODULEDIR}, + {"scriptdir", required_argument, NULL, OPTION_SCRIPTDIR}, + {"log", required_argument, NULL, OPTION_LOG}, + {"log-level", required_argument, NULL, OPTION_LOGLEVEL}, + {"command-log-always", no_argument, NULL, OPTION_LOGGING}, + {"limit-fds", required_argument, NULL, OPTION_LIMIT_FDS}, + {NULL}}; +static const struct optspec os_always = { + "hvdM:F:N:o:", + " -h, --help Display this help and exit\n" + " -v, --version Print program version\n" + " -d, --daemon Runs in daemon mode\n" + " -M, --module Load specified module\n" + " -F, --profile Use specified configuration profile\n" + " -N, --pathspace Insert prefix into config & socket paths\n" + " -o, --vrfdefaultname Set default VRF name.\n" + " --vty_socket Override vty socket path\n" + " --moduledir Override modules directory\n" + " --scriptdir Override scripts directory\n" + " --log Set Logging to stdout, syslog, or file:\n" + " --log-level Set Logging Level to use, debug, info, warn, etc\n" + " --limit-fds Limit number of fds supported\n", + lo_always}; + +static bool logging_to_stdout = false; /* set when --log stdout specified */ + +static const struct option lo_cfg[] = { + {"config_file", required_argument, NULL, 'f'}, + {"dryrun", no_argument, NULL, 'C'}, + {NULL}}; +static const struct optspec os_cfg = { + "f:C", + " -f, --config_file Set configuration file name\n" + " -C, --dryrun Check configuration for validity and exit\n", + lo_cfg}; + + +static const struct option lo_fullcli[] = { + {"terminal", no_argument, NULL, 't'}, + {"tcli", no_argument, NULL, OPTION_TCLI}, +#ifdef HAVE_SQLITE3 + {"db_file", required_argument, NULL, OPTION_DB_FILE}, +#endif + {NULL}}; +static const struct optspec os_fullcli = { + "t", + " --tcli Use transaction-based CLI\n" + " -t, --terminal Open terminal session on stdio\n" + " -d -t Daemonize after terminal session ends\n", + lo_fullcli}; + + +static const struct option lo_pid[] = { + {"pid_file", required_argument, NULL, 'i'}, + {NULL}}; +static const struct optspec os_pid = { + "i:", + " -i, --pid_file Set process identifier file name\n", + lo_pid}; + + +static const struct option lo_zclient[] = { + {"socket", required_argument, NULL, 'z'}, + {NULL}}; +static const struct optspec os_zclient = { + "z:", " -z, --socket Set path of zebra socket\n", lo_zclient}; + + +static const struct option lo_vty[] = { + {"vty_addr", required_argument, NULL, 'A'}, + {"vty_port", required_argument, NULL, 'P'}, + {NULL}}; +static const struct optspec os_vty = { + "A:P:", + " -A, --vty_addr Set vty's bind address\n" + " -P, --vty_port Set vty's port number\n", + lo_vty}; + + +static const struct option lo_user[] = {{"user", required_argument, NULL, 'u'}, + {"group", required_argument, NULL, 'g'}, + {NULL}}; +static const struct optspec os_user = {"u:g:", + " -u, --user User to run as\n" + " -g, --group Group to run as\n", + lo_user}; + +bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len, + const char *path) +{ + memset(sa, 0, sizeof(*sa)); + + if (!path) + path = frr_zclientpath; + + if (!strncmp(path, ZAPI_TCP_PATHNAME, strlen(ZAPI_TCP_PATHNAME))) { + /* note: this functionality is disabled at bottom */ + int af; + int port = ZEBRA_TCP_PORT; + char *err = NULL; + struct sockaddr_in *sin = NULL; + struct sockaddr_in6 *sin6 = NULL; + + path += strlen(ZAPI_TCP_PATHNAME); + + switch (path[0]) { + case '4': + path++; + af = AF_INET; + break; + case '6': + path++; + af = AF_INET6; + break; + default: + af = AF_INET6; + break; + } + + switch (path[0]) { + case '\0': + break; + case ':': + path++; + port = strtoul(path, &err, 10); + if (*err || !*path) + return false; + break; + default: + return false; + } + + sa->ss_family = af; + switch (af) { + case AF_INET: + sin = (struct sockaddr_in *)sa; + sin->sin_port = htons(port); + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + *sa_len = sizeof(struct sockaddr_in); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = *sa_len; +#endif + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)sa; + sin6->sin6_port = htons(port); + inet_pton(AF_INET6, "::1", &sin6->sin6_addr); + *sa_len = sizeof(struct sockaddr_in6); +#ifdef SIN6_LEN + sin6->sin6_len = *sa_len; +#endif + break; + } + +#if 1 + /* force-disable this path, because tcp-zebra is a + * SECURITY ISSUE. there are no checks at all against + * untrusted users on the local system connecting on TCP + * and injecting bogus routing data into the entire routing + * domain. + * + * The functionality is only left here because it may be + * useful during development, in order to be able to get + * tcpdump or wireshark watching ZAPI as TCP. If you want + * to do that, flip the #if 1 above to #if 0. */ + memset(sa, 0, sizeof(*sa)); + return false; +#endif + } else { + /* "sun" is a #define on solaris */ + struct sockaddr_un *suna = (struct sockaddr_un *)sa; + + suna->sun_family = AF_UNIX; + strlcpy(suna->sun_path, path, sizeof(suna->sun_path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + *sa_len = suna->sun_len = SUN_LEN(suna); +#else + *sa_len = sizeof(suna->sun_family) + strlen(suna->sun_path); +#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ +#if 0 + /* this is left here for future reference; Linux abstract + * socket namespace support can be enabled by replacing + * above #if 0 with #ifdef GNU_LINUX. + * + * THIS IS A SECURITY ISSUE, the abstract socket namespace + * does not have user/group permission control on sockets. + * we'd need to implement SCM_CREDENTIALS support first to + * check that only proper users can connect to abstract + * sockets. (same problem as tcp-zebra, except there is a + * fix with SCM_CREDENTIALS. tcp-zebra has no such fix.) + */ + if (suna->sun_path[0] == '@') + suna->sun_path[0] = '\0'; +#endif + } + return true; +} + +static struct frr_daemon_info *di = NULL; + +void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv) +{ + di = daemon; + frr_is_after_fork = false; + + /* basename(), opencoded. */ + char *p = strrchr(argv[0], '/'); + di->progname = p ? p + 1 : argv[0]; + + if (!getenv("GCOV_PREFIX")) + umask(0027); + else { + /* If we are profiling use a more generous umask */ + umask(0002); + } + + log_args_init(daemon->early_logging); + + opt_extend(&os_always); + if (!(di->flags & FRR_NO_SPLIT_CONFIG)) + opt_extend(&os_cfg); + if (!(di->flags & FRR_LIMITED_CLI)) + opt_extend(&os_fullcli); + if (!(di->flags & FRR_NO_PID)) + opt_extend(&os_pid); + if (!(di->flags & FRR_NO_PRIVSEP)) + opt_extend(&os_user); + if (!(di->flags & FRR_NO_ZCLIENT)) + opt_extend(&os_zclient); + if (!(di->flags & FRR_NO_TCPVTY)) + opt_extend(&os_vty); + if (di->flags & FRR_DETACH_LATER) + nodetach_daemon = true; + + snprintf(config_default, sizeof(config_default), "%s/%s.conf", + frr_sysconfdir, di->name); + snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid", + frr_runstatedir, di->name); + snprintf(frr_zclientpath, sizeof(frr_zclientpath), ZAPI_SOCK_NAME); +#ifdef HAVE_SQLITE3 + snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s.db", + frr_libstatedir, di->name); +#endif + + strlcpy(frr_protoname, di->logname, sizeof(frr_protoname)); + strlcpy(frr_protonameinst, di->logname, sizeof(frr_protonameinst)); + + di->cli_mode = FRR_CLI_CLASSIC; + + /* we may be starting with extra FDs open for whatever purpose, + * e.g. logging, some module, etc. Recording them here allows later + * checking whether an fd is valid for such extension purposes, + * without this we could end up e.g. logging to a BGP session fd. + */ + startup_fds = 0; + for (int i = 0; i < 64; i++) { + struct stat st; + + if (fstat(i, &st)) + continue; + if (S_ISDIR(st.st_mode) || S_ISBLK(st.st_mode)) + continue; + + startup_fds |= UINT64_C(0x1) << (uint64_t)i; + } + + /* note this doesn't do anything, it just grabs state, so doing it + * early in _preinit is perfect. + */ + systemd_init_env(); +} + +bool frr_is_startup_fd(int fd) +{ + return !!(startup_fds & (UINT64_C(0x1) << (uint64_t)fd)); +} + +void frr_opt_add(const char *optstr, const struct option *longopts, + const char *helpstr) +{ + const struct optspec main_opts = {optstr, helpstr, longopts}; + opt_extend(&main_opts); +} + +void frr_help_exit(int status) +{ + FILE *target = status ? stderr : stdout; + + if (status != 0) + fprintf(stderr, "Invalid options.\n\n"); + + if (di->printhelp) + di->printhelp(target); + else + fprintf(target, "Usage: %s [OPTION...]\n\n%s%s%s\n\n%s", + di->progname, di->proghelp, di->copyright ? "\n\n" : "", + di->copyright ? di->copyright : "", comb_helpstr); + fprintf(target, "\nReport bugs to %s\n", FRR_BUG_ADDRESS); + exit(status); +} + +struct option_chain { + struct option_chain *next; + const char *arg; +}; + +static struct option_chain *modules = NULL, **modnext = &modules; +static int errors = 0; + +static int frr_opt(int opt) +{ + static int vty_port_set = 0; + static int vty_addr_set = 0; + struct option_chain *oc; + struct log_arg *log_arg; + size_t arg_len; + char *err; + + switch (opt) { + case 'h': + frr_help_exit(0); + case 'v': + print_version(di->progname); + exit(0); + break; + case 'd': + di->daemon_mode = true; + break; + case 'M': + oc = XMALLOC(MTYPE_TMP, sizeof(*oc)); + oc->arg = optarg; + oc->next = NULL; + *modnext = oc; + modnext = &oc->next; + break; + case 'F': + if (!frr_defaults_profile_valid(optarg)) { + const char **p; + FILE *ofd = stderr; + + if (!strcmp(optarg, "help")) + ofd = stdout; + else + fprintf(stderr, + "The \"%s\" configuration profile is not valid for this FRR version.\n", + optarg); + + fprintf(ofd, "Available profiles are:\n"); + for (p = frr_defaults_profiles; *p; p++) + fprintf(ofd, "%s%s\n", + strcmp(*p, DFLT_NAME) ? " " : " * ", + *p); + + if (ofd == stdout) + exit(0); + fprintf(ofd, "\n"); + errors++; + break; + } + frr_defaults_profile_set(optarg); + break; + case 'i': + if (di->flags & FRR_NO_PID) + return 1; + di->pid_file = optarg; + break; + case 'f': + if (di->flags & FRR_NO_SPLIT_CONFIG) + return 1; + di->config_file = optarg; + break; + case 'N': + if (di->pathspace) { + fprintf(stderr, + "-N/--pathspace option specified more than once!\n"); + errors++; + break; + } + if (di->zpathspace) + fprintf(stderr, + "-N option overridden by -z for zebra named socket path\n"); + + if (strchr(optarg, '/') || strchr(optarg, '.')) { + fprintf(stderr, + "slashes or dots are not permitted in the --pathspace option.\n"); + errors++; + break; + } + di->pathspace = optarg; + + snprintf(frr_runstatedir, sizeof(frr_runstatedir), + FRR_RUNSTATE_PATH "/%s", di->pathspace); + snprintf(frr_libstatedir, sizeof(frr_libstatedir), + FRR_LIBSTATE_PATH "/%s", di->pathspace); + snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s.pid", + frr_runstatedir, di->name); + if (!di->zpathspace) + snprintf(frr_zclientpath, sizeof(frr_zclientpath), + ZAPI_SOCK_NAME); + break; + case 'o': + vrf_set_default_name(optarg); + break; +#ifdef HAVE_SQLITE3 + case OPTION_DB_FILE: + if (di->flags & FRR_NO_PID) + return 1; + di->db_file = optarg; + break; +#endif + case 'C': + if (di->flags & FRR_NO_SPLIT_CONFIG) + return 1; + di->dryrun = true; + break; + case 't': + if (di->flags & FRR_LIMITED_CLI) + return 1; + di->terminal = true; + break; + case 'z': + di->zpathspace = true; + if (di->pathspace) + fprintf(stderr, + "-z option overrides -N option for zebra named socket path\n"); + if (di->flags & FRR_NO_ZCLIENT) + return 1; + strlcpy(frr_zclientpath, optarg, sizeof(frr_zclientpath)); + break; + case 'A': + if (di->flags & FRR_NO_TCPVTY) + return 1; + if (vty_addr_set) { + fprintf(stderr, + "-A option specified more than once!\n"); + errors++; + break; + } + vty_addr_set = 1; + di->vty_addr = optarg; + break; + case 'P': + if (di->flags & FRR_NO_TCPVTY) + return 1; + if (vty_port_set) { + fprintf(stderr, + "-P option specified more than once!\n"); + errors++; + break; + } + vty_port_set = 1; + di->vty_port = strtoul(optarg, &err, 0); + if (*err || !*optarg) { + fprintf(stderr, + "invalid port number \"%s\" for -P option\n", + optarg); + errors++; + break; + } + break; + case OPTION_VTYSOCK: + if (di->vty_sock_path) { + fprintf(stderr, + "--vty_socket option specified more than once!\n"); + errors++; + break; + } + di->vty_sock_path = optarg; + break; + case OPTION_MODULEDIR: + if (di->module_path) { + fprintf(stderr, + "----moduledir option specified more than once!\n"); + errors++; + break; + } + di->module_path = optarg; + break; + case OPTION_SCRIPTDIR: + if (di->script_path) { + fprintf(stderr, "--scriptdir option specified more than once!\n"); + errors++; + break; + } + di->script_path = optarg; + break; + case OPTION_TCLI: + di->cli_mode = FRR_CLI_TRANSACTIONAL; + break; + case 'u': + if (di->flags & FRR_NO_PRIVSEP) + return 1; + di->privs->user = optarg; + break; + case 'g': + if (di->flags & FRR_NO_PRIVSEP) + return 1; + di->privs->group = optarg; + break; + case OPTION_LOG: + arg_len = strlen(optarg) + 1; + log_arg = XCALLOC(MTYPE_TMP, sizeof(*log_arg) + arg_len); + memcpy(log_arg->target, optarg, arg_len); + log_args_add_tail(di->early_logging, log_arg); + break; + case OPTION_LOGLEVEL: + di->early_loglevel = optarg; + break; + case OPTION_LOGGING: + di->log_always = true; + break; + case OPTION_LIMIT_FDS: + di->limit_fds = strtoul(optarg, &err, 0); + break; + default: + return 1; + } + return 0; +} + +int frr_getopt(int argc, char *const argv[], int *longindex) +{ + int opt; + int lidx; + + comb_next_lo->name = NULL; + + do { + opt = getopt_long(argc, argv, comb_optstr, comb_lo, &lidx); + if (frr_opt(opt)) + break; + } while (opt != -1); + + if (opt == -1 && errors) + frr_help_exit(1); + if (longindex) + *longindex = lidx; + return opt; +} + +static void frr_mkdir(const char *path, bool strip) +{ + char buf[256]; + mode_t prev; + int ret; + struct zprivs_ids_t ids; + + if (strip) { + char *slash = strrchr(path, '/'); + size_t plen; + if (!slash) + return; + plen = slash - path; + if (plen > sizeof(buf) - 1) + return; + memcpy(buf, path, plen); + buf[plen] = '\0'; + path = buf; + } + + /* o+rx (..5) is needed for the frrvty group to work properly; + * without it, users in the frrvty group can't access the vty sockets. + */ + prev = umask(0022); + ret = mkdir(path, 0755); + umask(prev); + + if (ret != 0) { + /* if EEXIST, return without touching the permissions, + * so user-set custom permissions are left in place + */ + if (errno == EEXIST) + return; + + flog_err(EC_LIB_SYSTEM_CALL, "failed to mkdir \"%s\": %s", path, + strerror(errno)); + return; + } + + zprivs_get_ids(&ids); + if (chown(path, ids.uid_normal, ids.gid_normal)) + flog_err(EC_LIB_SYSTEM_CALL, "failed to chown \"%s\": %s", path, + strerror(errno)); +} + +static void _err_print(const void *cookie, const char *errstr) +{ + const char *prefix = (const char *)cookie; + + fprintf(stderr, "%s: %s\n", prefix, errstr); +} + +static struct event_loop *master; +struct event_loop *frr_init(void) +{ + struct option_chain *oc; + struct log_arg *log_arg; + struct frrmod_runtime *module; + struct zprivs_ids_t ids; + char p_instance[16] = "", p_pathspace[256] = ""; + const char *dir; + + dir = di->module_path ? di->module_path : frr_moduledir; + + srandom(time(NULL)); + frr_defaults_apply(); + + if (di->instance) { + snprintf(frr_protonameinst, sizeof(frr_protonameinst), "%s[%u]", + di->logname, di->instance); + snprintf(p_instance, sizeof(p_instance), "-%d", di->instance); + } + if (di->pathspace) + snprintf(p_pathspace, sizeof(p_pathspace), "%s/", + di->pathspace); + + snprintf(config_default, sizeof(config_default), "%s%s%s%s.conf", + frr_sysconfdir, p_pathspace, di->name, p_instance); + snprintf(pidfile_default, sizeof(pidfile_default), "%s/%s%s.pid", + frr_runstatedir, di->name, p_instance); +#ifdef HAVE_SQLITE3 + snprintf(dbfile_default, sizeof(dbfile_default), "%s/%s%s%s.db", + frr_libstatedir, p_pathspace, di->name, p_instance); +#endif + + zprivs_preinit(di->privs); + zprivs_get_ids(&ids); + + zlog_init(di->progname, di->logname, di->instance, + ids.uid_normal, ids.gid_normal); + + while ((log_arg = log_args_pop(di->early_logging))) { + command_setup_early_logging(log_arg->target, + di->early_loglevel); + /* this is a bit of a hack, + but need to notice when + the target is stdout */ + if (strcmp(log_arg->target, "stdout") == 0) + logging_to_stdout = true; + XFREE(MTYPE_TMP, log_arg); + } + + if (!frr_zclient_addr(&zclient_addr, &zclient_addr_len, + frr_zclientpath)) { + fprintf(stderr, "Invalid zserv socket path: %s\n", + frr_zclientpath); + exit(1); + } + + /* don't mkdir these as root... */ + if (!(di->flags & FRR_NO_PRIVSEP)) { + frr_mkdir(frr_libstatedir, false); + if (!di->pid_file || !di->vty_path) + frr_mkdir(frr_runstatedir, false); + if (di->pid_file) + frr_mkdir(di->pid_file, true); + if (di->vty_path) + frr_mkdir(di->vty_path, true); + } + + frrmod_init(di->module); + while (modules) { + modules = (oc = modules)->next; + module = frrmod_load(oc->arg, dir, _err_print, __func__); + if (!module) + exit(1); + XFREE(MTYPE_TMP, oc); + } + + zprivs_init(di->privs); + + master = event_master_create(NULL); + signal_init(master, di->n_signals, di->signals); + hook_call(frr_early_init, master); + +#ifdef HAVE_SQLITE3 + if (!di->db_file) + di->db_file = dbfile_default; + db_init("%s", di->db_file); +#endif + + if (di->flags & FRR_LIMITED_CLI) + cmd_init(-1); + else + cmd_init(1); + + vty_init(master, di->log_always); + lib_cmd_init(); + + frr_pthread_init(); +#ifdef HAVE_SCRIPTING + frrscript_init(di->script_path ? di->script_path : frr_scriptdir); +#endif + + log_ref_init(); + log_ref_vty_init(); + lib_error_init(); + + nb_init(master, di->yang_modules, di->n_yang_modules, true); + if (nb_db_init() != NB_OK) + flog_warn(EC_LIB_NB_DATABASE, + "%s: failed to initialize northbound database", + __func__); + + debug_init_cli(); + + return master; +} + +const char *frr_get_progname(void) +{ + return di ? di->progname : NULL; +} + +enum frr_cli_mode frr_get_cli_mode(void) +{ + return di ? di->cli_mode : FRR_CLI_CLASSIC; +} + +uint32_t frr_get_fd_limit(void) +{ + return di ? di->limit_fds : 0; +} + +static int rcvd_signal = 0; + +static void rcv_signal(int signum) +{ + rcvd_signal = signum; + /* poll() is interrupted by the signal; handled below */ +} + +static void frr_daemon_wait(int fd) +{ + struct pollfd pfd[1]; + int ret; + pid_t exitpid; + int exitstat; + sigset_t sigs, prevsigs; + + sigemptyset(&sigs); + sigaddset(&sigs, SIGTSTP); + sigaddset(&sigs, SIGQUIT); + sigaddset(&sigs, SIGINT); + sigprocmask(SIG_BLOCK, &sigs, &prevsigs); + + struct sigaction sa = { + .sa_handler = rcv_signal, .sa_flags = SA_RESETHAND, + }; + sigemptyset(&sa.sa_mask); + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + do { + char buf[1]; + ssize_t nrecv; + + pfd[0].fd = fd; + pfd[0].events = POLLIN; + + rcvd_signal = 0; + +#if defined(HAVE_PPOLL) + ret = ppoll(pfd, 1, NULL, &prevsigs); +#elif defined(HAVE_POLLTS) + ret = pollts(pfd, 1, NULL, &prevsigs); +#else + /* racy -- only used on FreeBSD 9 */ + sigset_t tmpsigs; + sigprocmask(SIG_SETMASK, &prevsigs, &tmpsigs); + ret = poll(pfd, 1, -1); + sigprocmask(SIG_SETMASK, &tmpsigs, NULL); +#endif + if (ret < 0 && errno != EINTR && errno != EAGAIN) { + perror("poll()"); + exit(1); + } + switch (rcvd_signal) { + case SIGTSTP: + send(fd, "S", 1, 0); + do { + nrecv = recv(fd, buf, sizeof(buf), 0); + } while (nrecv == -1 + && (errno == EINTR || errno == EAGAIN)); + + raise(SIGTSTP); + sigaction(SIGTSTP, &sa, NULL); + send(fd, "R", 1, 0); + break; + case SIGINT: + send(fd, "I", 1, 0); + break; + case SIGQUIT: + send(fd, "Q", 1, 0); + break; + } + } while (ret <= 0); + + exitpid = waitpid(-1, &exitstat, WNOHANG); + if (exitpid == 0) + /* child successfully went to main loop & closed socket */ + exit(0); + + /* child failed one way or another ... */ + if (WIFEXITED(exitstat) && WEXITSTATUS(exitstat) == 0) + /* can happen in --terminal case if exit is fast enough */ + (void)0; + else if (WIFEXITED(exitstat)) + fprintf(stderr, "%s failed to start, exited %d\n", di->name, + WEXITSTATUS(exitstat)); + else if (WIFSIGNALED(exitstat)) + fprintf(stderr, "%s crashed in startup, signal %d\n", di->name, + WTERMSIG(exitstat)); + else + fprintf(stderr, "%s failed to start, unknown problem\n", + di->name); + exit(1); +} + +static int daemon_ctl_sock = -1; + +static void frr_daemonize(void) +{ + int fds[2]; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds)) { + perror("socketpair() for daemon control"); + exit(1); + } + set_cloexec(fds[0]); + set_cloexec(fds[1]); + + pid = fork(); + if (pid < 0) { + perror("fork()"); + exit(1); + } + if (pid == 0) { + /* child */ + close(fds[0]); + if (setsid() < 0) { + perror("setsid()"); + exit(1); + } + + daemon_ctl_sock = fds[1]; + return; + } + + close(fds[1]); + frr_daemon_wait(fds[0]); +} + +/* + * Why is this a thread? + * + * The read in of config for integrated config happens *after* + * thread execution starts( because it is passed in via a vtysh -b -n ) + * While if you are not using integrated config we want the ability + * to read the config in after thread execution starts, so that + * we can match this behavior. + */ +static void frr_config_read_in(struct event *t) +{ + hook_call(frr_config_pre, master); + + if (!vty_read_config(vty_shared_candidate_config, di->config_file, + config_default) + && di->backup_config_file) { + char *orig = XSTRDUP(MTYPE_TMP, host_config_get()); + + zlog_info("Attempting to read backup config file: %s specified", + di->backup_config_file); + vty_read_config(vty_shared_candidate_config, + di->backup_config_file, config_default); + + host_config_set(orig); + XFREE(MTYPE_TMP, orig); + } + + /* + * Automatically commit the candidate configuration after + * reading the configuration file. + */ + if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) { + struct nb_context context = {}; + char errmsg[BUFSIZ] = {0}; + int ret; + + context.client = NB_CLIENT_CLI; + ret = nb_candidate_commit(context, vty_shared_candidate_config, + true, "Read configuration file", NULL, + errmsg, sizeof(errmsg)); + if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) + zlog_err( + "%s: failed to read configuration file: %s (%s)", + __func__, nb_err_name(ret), errmsg); + } + + hook_call(frr_config_post, master); +} + +void frr_config_fork(void) +{ + hook_call(frr_late_init, master); + + if (!(di->flags & FRR_NO_SPLIT_CONFIG)) { + /* Don't start execution if we are in dry-run mode */ + if (di->dryrun) { + frr_config_read_in(NULL); + exit(0); + } + + event_add_event(master, frr_config_read_in, NULL, 0, + &di->read_in); + } + + if (di->daemon_mode || di->terminal) + frr_daemonize(); + + frr_is_after_fork = true; + + if (!di->pid_file) + di->pid_file = pidfile_default; + pid_output(di->pid_file); + zlog_tls_buffer_init(); +} + +static void frr_check_detach(void) +{ + if (nodetach_term || nodetach_daemon) + return; + + if (daemon_ctl_sock != -1) + close(daemon_ctl_sock); + daemon_ctl_sock = -1; +} + +void frr_vty_serv_start(bool check_detach) +{ + /* allow explicit override of vty_path in the future + * (not currently set anywhere) */ + if (!di->vty_path) { + const char *dir; + char defvtydir[256]; + + snprintf(defvtydir, sizeof(defvtydir), "%s", frr_runstatedir); + + dir = di->vty_sock_path ? di->vty_sock_path : defvtydir; + + if (di->instance) + snprintf(vtypath_default, sizeof(vtypath_default), + "%s/%s-%d.vty", dir, di->name, di->instance); + else + snprintf(vtypath_default, sizeof(vtypath_default), + "%s/%s.vty", dir, di->name); + + di->vty_path = vtypath_default; + } + + vty_serv_start(di->vty_addr, di->vty_port, di->vty_path); + + if (check_detach) + frr_check_detach(); +} + +void frr_vty_serv_stop(void) +{ + vty_serv_stop(); + + if (di->vty_path) + unlink(di->vty_path); +} + +static void frr_terminal_close(int isexit) +{ + int nullfd; + + nodetach_term = false; + frr_check_detach(); + + if (!di->daemon_mode || isexit) { + printf("\n%s exiting\n", di->name); + if (!isexit) + raise(SIGINT); + return; + } else { + printf("\n%s daemonizing\n", di->name); + fflush(stdout); + } + + nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); + if (nullfd == -1) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: failed to open /dev/null: %s", __func__, + safe_strerror(errno)); + } else { + int fd; + /* + * only redirect stdin, stdout, stderr to null when a tty also + * don't redirect when stdout is set with --log stdout + */ + for (fd = 2; fd >= 0; fd--) + if (isatty(fd) && + (fd != STDOUT_FILENO || !logging_to_stdout)) + dup2(nullfd, fd); + close(nullfd); + } +} + +static struct event *daemon_ctl_thread = NULL; + +static void frr_daemon_ctl(struct event *t) +{ + char buf[1]; + ssize_t nr; + + nr = recv(daemon_ctl_sock, buf, sizeof(buf), 0); + if (nr < 0 && (errno == EINTR || errno == EAGAIN)) + goto out; + if (nr <= 0) + return; + + switch (buf[0]) { + case 'S': /* SIGTSTP */ + vty_stdio_suspend(); + if (send(daemon_ctl_sock, "s", 1, 0) < 0) + zlog_err("%s send(\"s\") error (SIGTSTP propagation)", + (di && di->name ? di->name : "")); + break; + case 'R': /* SIGTCNT [implicit] */ + vty_stdio_resume(); + break; + case 'I': /* SIGINT */ + di->daemon_mode = false; + raise(SIGINT); + break; + case 'Q': /* SIGQUIT */ + di->daemon_mode = true; + vty_stdio_close(); + break; + } + +out: + event_add_read(master, frr_daemon_ctl, NULL, daemon_ctl_sock, + &daemon_ctl_thread); +} + +void frr_detach(void) +{ + nodetach_daemon = false; + frr_check_detach(); +} + +void frr_run(struct event_loop *master) +{ + char instanceinfo[64] = ""; + + if (!(di->flags & FRR_MANUAL_VTY_START)) + frr_vty_serv_start(false); + + if (di->instance) + snprintf(instanceinfo, sizeof(instanceinfo), "instance %u ", + di->instance); + + zlog_notice("%s %s starting: %svty@%d%s", di->name, FRR_VERSION, + instanceinfo, di->vty_port, di->startinfo); + + if (di->terminal) { + nodetach_term = true; + + vty_stdio(frr_terminal_close); + if (daemon_ctl_sock != -1) { + set_nonblocking(daemon_ctl_sock); + event_add_read(master, frr_daemon_ctl, NULL, + daemon_ctl_sock, &daemon_ctl_thread); + } + } else if (di->daemon_mode) { + int nullfd = open("/dev/null", O_RDONLY | O_NOCTTY); + if (nullfd == -1) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: failed to open /dev/null: %s", + __func__, safe_strerror(errno)); + } else { + int fd; + /* + * only redirect stdin, stdout, stderr to null when a + * tty also don't redirect when stdout is set with --log + * stdout + */ + for (fd = 2; fd >= 0; fd--) + if (isatty(fd) && + (fd != STDOUT_FILENO || !logging_to_stdout)) + dup2(nullfd, fd); + close(nullfd); + } + + if (!(di->flags & FRR_MANUAL_VTY_START)) + frr_check_detach(); + } + + /* end fixed stderr startup logging */ + zlog_startup_end(); + + struct event thread; + while (event_fetch(master, &thread)) + event_call(&thread); +} + +void frr_early_fini(void) +{ + hook_call(frr_early_fini); +} + +void frr_fini(void) +{ + FILE *fp; + char filename[128]; + int have_leftovers = 0; + + hook_call(frr_fini); + + vty_terminate(); + cmd_terminate(); + nb_terminate(); + yang_terminate(); +#ifdef HAVE_SQLITE3 + db_close(); +#endif + log_ref_fini(); + +#ifdef HAVE_SCRIPTING + frrscript_fini(); +#endif + frr_pthread_finish(); + zprivs_terminate(di->privs); + /* signal_init -> nothing needed */ + event_master_free(master); + master = NULL; + zlog_tls_buffer_fini(); + zlog_fini(); + /* frrmod_init -> nothing needed / hooks */ + rcu_shutdown(); + + /* also log memstats to stderr when stderr goes to a file*/ + if (debug_memstats_at_exit || !isatty(STDERR_FILENO)) + have_leftovers = log_memstats(stderr, di->name); + + /* in case we decide at runtime that we want exit-memstats for + * a daemon + * (only do this if we actually have something to print though) + */ + if (!debug_memstats_at_exit || !have_leftovers) + return; + + snprintf(filename, sizeof(filename), "/tmp/frr-memstats-%s-%llu-%llu", + di->name, (unsigned long long)getpid(), + (unsigned long long)time(NULL)); + + fp = fopen(filename, "w"); + if (fp) { + log_memstats(fp, di->name); + fclose(fp); + } +} + +struct json_object *frr_daemon_state_load(void) +{ + struct json_object *state; + char **state_path; + + assertf(di->state_paths, + "CODE BUG: daemon trying to load state, but no state path in frr_daemon_info"); + + for (state_path = di->state_paths; *state_path; state_path++) { + state = json_object_from_file(*state_path); + if (state) + return state; + } + + return json_object_new_object(); +} + +/* cross-reference file_write_config() in command.c + * the code there is similar but not identical (configs use a unique temporary + * name for writing and keep a backup of the previous config.) + */ +void frr_daemon_state_save(struct json_object **statep) +{ + struct json_object *state = *statep; + char *state_path, *slash, *temp_name, **other; + size_t name_len, json_len; + const char *json_str; + int dirfd, fd; + + assertf(di->state_paths, + "CODE BUG: daemon trying to save state, but no state path in frr_daemon_info"); + + state_path = di->state_paths[0]; + json_str = json_object_to_json_string_ext(state, + JSON_C_TO_STRING_PRETTY); + json_len = strlen(json_str); + + /* To correctly fsync() and ensure we have either consistent old state + * or consistent new state but no fs-damage garbage inbetween, we need + * to work with a directory fd. If we need that anyway we might as + * well use the dirfd with openat() & co in fd-relative operations. + */ + + slash = strrchr(state_path, '/'); + if (slash) { + char *state_dir; + + state_dir = XSTRDUP(MTYPE_TMP, state_path); + state_dir[slash - state_path] = '\0'; + dirfd = open(state_dir, O_DIRECTORY | O_RDONLY); + XFREE(MTYPE_TMP, state_dir); + + if (dirfd < 0) { + zlog_err("failed to open directory %pSQq for saving daemon state: %m", + state_dir); + return; + } + + /* skip to file name */ + slash++; + } else { + dirfd = open(".", O_DIRECTORY | O_RDONLY); + if (dirfd < 0) { + zlog_err( + "failed to open current directory for saving daemon state: %m"); + return; + } + + /* file name = path */ + slash = state_path; + } + + /* unlike saving configs, a temporary unique filename is unhelpful + * here as it might litter files on limited write-heavy storage + * (think switch with small NOR flash for frequently written data.) + * + * => always use filename with .sav suffix, worst case it litters one + * file. + */ + name_len = strlen(slash); + temp_name = XMALLOC(MTYPE_TMP, name_len + 5); + memcpy(temp_name, slash, name_len); + memcpy(temp_name + name_len, ".sav", 5); + + /* state file is always 0600, it's by and for FRR itself only */ + fd = openat(dirfd, temp_name, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + zlog_err("failed to open temporary daemon state save file for %pSQq: %m", + state_path); + goto out_closedir_free; + } + + while (json_len) { + ssize_t nwr = write(fd, json_str, json_len); + + if (nwr <= 0) { + zlog_err("failed to write temporary daemon state to %pSQq: %m", + state_path); + + close(fd); + unlinkat(dirfd, temp_name, 0); + goto out_closedir_free; + } + + json_str += nwr; + json_len -= nwr; + } + + /* fsync is theoretically implicit in close(), but... */ + if (fsync(fd) < 0) + zlog_warn("fsync for daemon state %pSQq failed: %m", state_path); + close(fd); + + /* this is the *actual* fsync that ensures we're consistent. The + * file fsync only syncs the inode, but not the directory entry + * referring to it. + */ + if (fsync(dirfd) < 0) + zlog_warn("directory fsync for daemon state %pSQq failed: %m", + state_path); + + /* atomic, hopefully. */ + if (renameat(dirfd, temp_name, dirfd, slash) < 0) { + zlog_err("renaming daemon state %pSQq to %pSQq failed: %m", + temp_name, state_path); + /* no unlink here, give the user a chance to investigate */ + goto out_closedir_free; + } + + /* and the rename needs to be synced too */ + if (fsync(dirfd) < 0) + zlog_warn("directory fsync for daemon state %pSQq failed after rename: %m", + state_path); + + /* daemon may specify other deprecated paths to load from; since we + * just saved successfully we should delete those. + */ + for (other = di->state_paths + 1; *other; other++) { + if (unlink(*other) == 0) + continue; + if (errno == ENOENT || errno == ENOTDIR) + continue; + + zlog_warn("failed to remove deprecated daemon state file %pSQq: %m", + *other); + } + +out_closedir_free: + XFREE(MTYPE_TMP, temp_name); + close(dirfd); + + json_object_free(state); + *statep = NULL; +} + +#ifdef INTERP +static const char interp[] + __attribute__((section(".interp"), used)) = INTERP; +#endif +/* + * executable entry point for libfrr.so + * + * note that libc initialization is skipped for this so the set of functions + * that can be called is rather limited + */ +extern void _libfrr_version(void) + __attribute__((visibility("hidden"), noreturn)); +void _libfrr_version(void) +{ + const char banner[] = + FRR_FULL_NAME " " FRR_VERSION ".\n" + FRR_COPYRIGHT GIT_INFO "\n" + "configured with:\n " FRR_CONFIG_ARGS "\n"; + write(1, banner, sizeof(banner) - 1); + _exit(0); +} + +/* Render simple version tuple to string */ +const char *frr_vers2str(uint32_t version, char *buf, int buflen) +{ + snprintf(buf, buflen, "%d.%d.%d", MAJOR_FRRVERSION(version), + MINOR_FRRVERSION(version), SUB_FRRVERSION(version)); + + return buf; +} diff --git a/lib/libfrr.h b/lib/libfrr.h new file mode 100644 index 0000000..d52ee9a --- /dev/null +++ b/lib/libfrr.h @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libfrr overall management functions + * + * Copyright (C) 2016 David Lamparter for NetDEF, Inc. + */ + +#ifndef _ZEBRA_FRR_H +#define _ZEBRA_FRR_H + +#include "typesafe.h" +#include "sigevent.h" +#include "privs.h" +#include "frrevent.h" +#include "log.h" +#include "getopt.h" +#include "module.h" +#include "hook.h" +#include "northbound.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZAPI_SOCK_NAME "%s/zserv.api", frr_runstatedir + +/* The following options disable specific command line options that + * are not applicable for a particular daemon. + */ +#define FRR_NO_PRIVSEP (1 << 0) +#define FRR_NO_TCPVTY (1 << 1) +#define FRR_LIMITED_CLI (1 << 2) +#define FRR_NO_SPLIT_CONFIG (1 << 3) +#define FRR_NO_PID (1 << 4) +#define FRR_NO_CFG_PID_DRY (FRR_NO_PID | FRR_NO_SPLIT_CONFIG) +#define FRR_NO_ZCLIENT (1 << 5) +/* If FRR_DETACH_LATER is used, the daemon will keep its parent running + * until frr_detach() is called. Normally "somedaemon -d" returns once the + * main event loop is reached in the daemon; use this for extra startup bits. + * + * Does nothing if -d isn't used. + */ +#define FRR_DETACH_LATER (1 << 6) +/* If FRR_MANUAL_VTY_START is used, frr_run() will not automatically start + * listening on for vty connection (either TCP or Unix socket based). The daemon + * is responsible for calling frr_vty_serv() itself. + */ +#define FRR_MANUAL_VTY_START (1 << 7) + +PREDECL_DLIST(log_args); +struct log_arg { + struct log_args_item itm; + + char target[0]; +}; +DECLARE_DLIST(log_args, struct log_arg, itm); + +/* Registry of daemons' port defaults. Many of these are VTY ports; some + * daemons have default ports for other features such as ospfapi, or zebra's + * FPM. + */ + +/* default zebra TCP port for zapi; this is currently disabled for security + * reasons. + */ +#define ZEBRA_TCP_PORT 2600 + +#define ZEBRA_VTY_PORT 2601 +#define RIP_VTY_PORT 2602 +#define RIPNG_VTY_PORT 2603 +#define OSPF_VTY_PORT 2604 +#define BGP_VTY_PORT 2605 +#define OSPF6_VTY_PORT 2606 + +/* Default API server port to accept connection request from client-side. + * This value could be overridden by "ospfapi" entry in "/etc/services". + */ +#define OSPF_API_SYNC_PORT 2607 + +#define ISISD_VTY_PORT 2608 +#define BABEL_VTY_PORT 2609 +#define NHRP_VTY_PORT 2610 +#define PIMD_VTY_PORT 2611 +#define LDP_VTY_PORT 2612 +#define EIGRP_VTY_PORT 2613 +#define SHARP_VTY_PORT 2614 +#define PBR_VTY_PORT 2615 +#define STATIC_VTY_PORT 2616 +#define BFDD_VTY_PORT 2617 +#define FABRICD_VTY_PORT 2618 +#define VRRP_VTY_PORT 2619 + +/* default port for FPM connections */ +#define FPM_DEFAULT_PORT 2620 + +#define PATH_VTY_PORT 2621 +#define PIM6D_VTY_PORT 2622 +#define MGMTD_VTY_PORT 2623 +/* Registry of daemons' port defaults */ + +enum frr_cli_mode { + FRR_CLI_CLASSIC = 0, + FRR_CLI_TRANSACTIONAL, +}; + +struct frr_daemon_info { + unsigned flags; + + const char *progname; + const char *name; + const char *logname; + unsigned short instance; + struct frrmod_runtime *module; + + char *vty_addr; + int vty_port; + char *vty_sock_path; + bool dryrun; + bool daemon_mode; + bool terminal; + enum frr_cli_mode cli_mode; + + struct event *read_in; + const char *config_file; + const char *backup_config_file; + const char *pid_file; +#ifdef HAVE_SQLITE3 + const char *db_file; +#endif + const char *vty_path; + const char *module_path; + const char *script_path; + char **state_paths; + + const char *pathspace; + bool zpathspace; + + struct log_args_head early_logging[1]; + const char *early_loglevel; + + const char *proghelp; + void (*printhelp)(FILE *target); + const char *copyright; + char startinfo[128]; + + struct frr_signal_t *signals; + size_t n_signals; + + struct zebra_privs_t *privs; + + const struct frr_yang_module_info *const *yang_modules; + size_t n_yang_modules; + + bool log_always; + + /* Optional upper limit on the number of fds used in select/poll */ + uint32_t limit_fds; +}; + +/* execname is the daemon's executable (and pidfile and configfile) name, + * i.e. "zebra" or "bgpd" + * constname is the daemons source-level name, primarily for the logging ID, + * i.e. "ZEBRA" or "BGP" + * + * note that this macro is also a latch-on point for other changes (e.g. + * upcoming module support) that need to place some per-daemon things. Each + * daemon should have one of these. + */ +#define FRR_DAEMON_INFO(execname, constname, ...) \ + static struct frr_daemon_info execname##_di = {.name = #execname, \ + .logname = #constname, \ + .module = THIS_MODULE, \ + __VA_ARGS__}; \ + FRR_COREMOD_SETUP(.name = #execname, \ + .description = #execname " daemon", \ + .version = FRR_VERSION, ); \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +extern void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv); +extern void frr_opt_add(const char *optstr, const struct option *longopts, + const char *helpstr); +extern int frr_getopt(int argc, char *const argv[], int *longindex); + +extern __attribute__((__noreturn__)) void frr_help_exit(int status); + +extern struct event_loop *frr_init(void); +extern const char *frr_get_progname(void); +extern enum frr_cli_mode frr_get_cli_mode(void); +extern uint32_t frr_get_fd_limit(void); +extern bool frr_is_startup_fd(int fd); + +/* call order of these hooks is as ordered here */ +DECLARE_HOOK(frr_early_init, (struct event_loop * tm), (tm)); +DECLARE_HOOK(frr_late_init, (struct event_loop * tm), (tm)); +/* fork() happens between late_init and config_pre */ +DECLARE_HOOK(frr_config_pre, (struct event_loop * tm), (tm)); +DECLARE_HOOK(frr_config_post, (struct event_loop * tm), (tm)); + +extern void frr_config_fork(void); + +extern void frr_run(struct event_loop *master); +extern void frr_detach(void); +extern void frr_vty_serv_start(bool check_detach); +extern void frr_vty_serv_stop(void); + +extern bool frr_zclient_addr(struct sockaddr_storage *sa, socklen_t *sa_len, + const char *path); + +struct json_object; +extern struct json_object *frr_daemon_state_load(void); +extern void frr_daemon_state_save(struct json_object **statep); + +/* these two are before the protocol daemon does its own shutdown + * it's named this way being the counterpart to frr_late_init */ +DECLARE_KOOH(frr_early_fini, (), ()); +extern void frr_early_fini(void); +/* and these two are after the daemon did its own cleanup */ +DECLARE_KOOH(frr_fini, (), ()); +extern void frr_fini(void); + +extern char config_default[512]; +extern char frr_zclientpath[512]; +extern const char frr_sysconfdir[]; +extern char frr_runstatedir[256]; +extern char frr_libstatedir[256]; +extern const char frr_moduledir[]; +extern const char frr_scriptdir[]; + +extern char frr_protoname[]; +extern char frr_protonameinst[]; +/* always set in the spot where we *would* fork even if we don't do so */ +extern bool frr_is_after_fork; + +extern bool debug_memstats_at_exit; + +/* + * Version numbering: MAJOR (8) | MINOR (16) | SUB (8) + */ +#define MAKE_FRRVERSION(maj, min, sub) \ + ((((maj) & 0xff) << 24) | (((min) & 0xffff) << 8) | ((sub) & 0xff)) +#define MAJOR_FRRVERSION(v) (((v) >> 24) & 0xff) +#define MINOR_FRRVERSION(v) (((v) >> 8) & 0xffff) +#define SUB_FRRVERSION(v) ((v) & 0xff) + +const char *frr_vers2str(uint32_t version, char *buf, int buflen); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_FRR_H */ diff --git a/lib/libfrr_trace.c b/lib/libfrr_trace.c new file mode 100644 index 0000000..14f4a3c --- /dev/null +++ b/lib/libfrr_trace.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#define TRACEPOINT_CREATE_PROBES +#define TRACEPOINT_DEFINE + +#include + +#include "libfrr_trace.h" diff --git a/lib/libfrr_trace.h b/lib/libfrr_trace.h new file mode 100644 index 0000000..05c958f --- /dev/null +++ b/lib/libfrr_trace.h @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Tracing + * + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ + +#if !defined(_LIBFRR_TRACE_H_) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _LIBFRR_TRACE_H_ + +#include "trace.h" + +#ifdef HAVE_LTTNG + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER frr_libfrr + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "./libfrr_trace.h" + +#include + +#include "hash.h" +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "table.h" + +/* clang-format off */ + +TRACEPOINT_EVENT( + frr_libfrr, + hash_get, + TP_ARGS(struct hash *, hash, void *, data), + TP_FIELDS( + ctf_string(name, hash->name ? hash->name : "(unnamed)") + ctf_integer(unsigned int, index_size, hash->size) + ctf_integer(unsigned long, item_count, hash->count) + ctf_integer_hex(intptr_t, data_ptr, data) + ) +) + +TRACEPOINT_LOGLEVEL(frr_libfrr, hash_get, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_libfrr, + hash_insert, + TP_ARGS(struct hash *, hash, void *, data, unsigned int, key), + TP_FIELDS( + ctf_string(name, hash->name ? hash->name : "(unnamed)") + ctf_integer(unsigned int, key, hash->size) + ctf_integer(unsigned int, index_size, hash->size) + ctf_integer(unsigned long, item_count, hash->count) + ctf_integer_hex(intptr_t, data_ptr, data) + ) +) + +TRACEPOINT_LOGLEVEL(frr_libfrr, hash_insert, TRACE_INFO) + +TRACEPOINT_EVENT( + frr_libfrr, + hash_release, + TP_ARGS(struct hash *, hash, void *, data, void *, released_item), + TP_FIELDS( + ctf_string(name, hash->name ? hash->name : "(unnamed)") + ctf_integer(unsigned int, index_size, hash->size) + ctf_integer(unsigned long, item_count, hash->count) + ctf_integer_hex(intptr_t, data_ptr, data) + ctf_integer_hex(intptr_t, released_item, data) + ) +) + +TRACEPOINT_LOGLEVEL(frr_libfrr, hash_release, TRACE_INFO) + +#define THREAD_SCHEDULE_ARGS \ + TP_ARGS(struct event_loop *, master, const char *, funcname, \ + const char *, schedfrom, int, fromln, struct event **, \ + thread_ptr, int, fd, int, val, void *, arg, long, time) + +TRACEPOINT_EVENT_CLASS( + frr_libfrr, + thread_operation, + THREAD_SCHEDULE_ARGS, + TP_FIELDS( + ctf_string(threadmaster_name, master->name) + ctf_string(function_name, funcname ? funcname : "(unknown function)") + ctf_string(scheduled_from, schedfrom ? schedfrom : "(unknown file)") + ctf_integer(int, scheduled_on_line, fromln) + ctf_integer_hex(intptr_t, thread_addr, thread_ptr ? *thread_ptr : NULL) + ctf_integer(int, file_descriptor, fd) + ctf_integer(int, event_value, val) + ctf_integer_hex(intptr_t, argument_ptr, arg) + ctf_integer(long, timer, time) + ) +) + +#define THREAD_OPERATION_TRACEPOINT_INSTANCE(name) \ + TRACEPOINT_EVENT_INSTANCE(frr_libfrr, thread_operation, name, \ + THREAD_SCHEDULE_ARGS) \ + TRACEPOINT_LOGLEVEL(frr_libfrr, name, TRACE_INFO) + +THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_timer) +THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_event) +THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_read) +THREAD_OPERATION_TRACEPOINT_INSTANCE(schedule_write) +THREAD_OPERATION_TRACEPOINT_INSTANCE(event_cancel) +THREAD_OPERATION_TRACEPOINT_INSTANCE(event_cancel_async) +THREAD_OPERATION_TRACEPOINT_INSTANCE(event_call) + +TRACEPOINT_EVENT( + frr_libfrr, + frr_pthread_run, + TP_ARGS( + char *, name + ), + TP_FIELDS( + ctf_string(frr_pthread_name, name) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + frr_pthread_stop, + TP_ARGS( + char *, name + ), + TP_FIELDS( + ctf_string(frr_pthread_name, name) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + memalloc, + TP_ARGS( + struct memtype *, mt, void *, ptr, size_t, size + ), + TP_FIELDS( + ctf_string(memtype, mt->name) + ctf_integer(size_t, size, size) + ctf_integer_hex(intptr_t, ptr, ptr) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + memfree, + TP_ARGS( + struct memtype *, mt, void *, ptr + ), + TP_FIELDS( + ctf_string(memtype, mt->name) + ctf_integer_hex(intptr_t, ptr, ptr) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + list_add, + TP_ARGS( + struct list *, list, const void *, ptr + ), + TP_FIELDS( + ctf_integer_hex(intptr_t, list, list) + ctf_integer(unsigned int, count, list->count) + ctf_integer_hex(intptr_t, ptr, ptr) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + list_remove, + TP_ARGS( + struct list *, list, const void *, ptr + ), + TP_FIELDS( + ctf_integer_hex(intptr_t, list, list) + ctf_integer(unsigned int, count, list->count) + ctf_integer_hex(intptr_t, ptr, ptr) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + list_delete_node, + TP_ARGS( + struct list *, list, const void *, node + ), + TP_FIELDS( + ctf_integer_hex(intptr_t, list, list) + ctf_integer(unsigned int, count, list->count) + ctf_integer_hex(intptr_t, node, node) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + list_sort, + TP_ARGS( + struct list *, list + ), + TP_FIELDS( + ctf_integer_hex(intptr_t, list, list) + ctf_integer(unsigned int, count, list->count) + ) +) + +TRACEPOINT_EVENT( + frr_libfrr, + route_node_get, + TP_ARGS( + struct route_table *, table, char *, prefix + ), + TP_FIELDS( + ctf_integer_hex(intptr_t, table, table) + ctf_string(prefix, prefix) + ) +) + +/* clang-format on */ + +#include +#include + +#endif /* HAVE_LTTNG */ + +#endif /* _LIBFRR_TRACE_H_ */ diff --git a/lib/libospf.h b/lib/libospf.h new file mode 100644 index 0000000..0ac490a --- /dev/null +++ b/lib/libospf.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Defines and structures common to OSPFv2 and OSPFv3 + * Copyright (C) 1998, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#ifndef _LIBOSPFD_H +#define _LIBOSPFD_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* IP precedence. */ +#ifndef IPTOS_PREC_INTERNETCONTROL +#define IPTOS_PREC_INTERNETCONTROL 0xC0 +#endif /* IPTOS_PREC_INTERNETCONTROL */ + +/* Default protocol, port number. */ +#ifndef IPPROTO_OSPFIGP +#define IPPROTO_OSPFIGP 89 +#endif /* IPPROTO_OSPFIGP */ + +/* Architectural Constants */ +#ifdef DEBUG +#define OSPF_LS_REFRESH_TIME 120 +#else +#define OSPF_LS_REFRESH_TIME 1800 +#endif +#define OSPF_MIN_LS_INTERVAL 5000 /* msec */ +#define OSPF_MIN_LS_ARRIVAL 1000 /* in milliseconds */ +#define OSPF_LSA_INITIAL_AGE 0 /* useful for debug */ +#define OSPF_LSA_MAXAGE 3600 +#define OSPF_CHECK_AGE 300 +#define OSPF_LSA_MAXAGE_DIFF 900 +#define OSPF_LS_INFINITY 0xffffff +#define OSPF_DEFAULT_DESTINATION 0x00000000 /* 0.0.0.0 */ +#define OSPF_INITIAL_SEQUENCE_NUMBER 0x80000001U +#define OSPF_MAX_SEQUENCE_NUMBER 0x7fffffffU +#define OSPF_INVALID_SEQUENCE_NUMBER 0x80000000U + +/* OSPF Interface Types */ +#define OSPF_IFTYPE_NONE 0 +#define OSPF_IFTYPE_POINTOPOINT 1 +#define OSPF_IFTYPE_BROADCAST 2 +#define OSPF_IFTYPE_NBMA 3 +#define OSPF_IFTYPE_POINTOMULTIPOINT 4 +#define OSPF_IFTYPE_VIRTUALLINK 5 +#define OSPF_IFTYPE_LOOPBACK 6 +#define OSPF_IFTYPE_MAX 7 + +/* OSPF interface default values. */ +#define OSPF_OUTPUT_COST_DEFAULT 10 +#define OSPF_OUTPUT_COST_INFINITE UINT16_MAX +#define OSPF_ROUTER_DEAD_INTERVAL_DEFAULT 40 +#define OSPF_ROUTER_DEAD_INTERVAL_MINIMAL 1 +#define OSPF_HELLO_INTERVAL_DEFAULT 10 +#define OSPF_HELLO_DELAY_DEFAULT 10 +#define OSPF_ROUTER_PRIORITY_DEFAULT 1 +#define OSPF_RETRANSMIT_INTERVAL_DEFAULT 5 +#define OSPF_TRANSMIT_DELAY_DEFAULT 1 +#define OSPF_DEFAULT_BANDWIDTH 10000 /* Mbps */ + +#define OSPF_DEFAULT_REF_BANDWIDTH 100000 /* Kbps */ + +#define OSPF_POLL_INTERVAL_DEFAULT 60 +#define OSPF_NEIGHBOR_PRIORITY_DEFAULT 0 + +#define OSPF_MTU_IGNORE_DEFAULT 0 +#define OSPF_FAST_HELLO_DEFAULT 0 +#define OSPF_P2MP_DELAY_REFLOOD_DEFAULT false +#define OSPF_P2MP_NON_BROADCAST_DEFAULT false +#define OSPF_OPAQUE_CAPABLE_DEFAULT true +#define OSPF_PREFIX_SUPPRESSION_DEFAULT false +#define OSPF_AREA_BACKBONE 0x00000000 /* 0.0.0.0 */ +#define OSPF_AREA_RANGE_COST_UNSPEC -1U + +#define OSPF_AREA_DEFAULT 0 +#define OSPF_AREA_STUB 1 +#define OSPF_AREA_NSSA 2 +#define OSPF_AREA_TYPE_MAX 3 + +/* SPF Throttling timer values. */ +#define OSPF_SPF_DELAY_DEFAULT 0 +#define OSPF_SPF_HOLDTIME_DEFAULT 50 +#define OSPF_SPF_MAX_HOLDTIME_DEFAULT 5000 + +#define OSPF_LSA_MAXAGE_CHECK_INTERVAL 30 +#define OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT 60 + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBOSPFD_H */ diff --git a/lib/link_state.c b/lib/link_state.c new file mode 100644 index 0000000..c758b7f --- /dev/null +++ b/lib/link_state.c @@ -0,0 +1,2914 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Link State Database - link_state.c + * + * Author: Olivier Dugeon + * + * Copyright (C) 2020 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + */ + +#include + +#include "if.h" +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "termtable.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "zclient.h" +#include "stream.h" +#include "sbuf.h" +#include "printfrr.h" +#include +#include "link_state.h" +#include "iso.h" + +/* Link State Memory allocation */ +DEFINE_MTYPE_STATIC(LIB, LS_DB, "Link State Database"); + +/** + * Link State Node management functions + */ +int ls_node_id_same(struct ls_node_id i1, struct ls_node_id i2) +{ + if (i1.origin != i2.origin) + return 0; + + if (i1.origin == UNKNOWN) + return 1; + + if (i1.origin == ISIS_L1 || i1.origin == ISIS_L2) { + if (memcmp(i1.id.iso.sys_id, i2.id.iso.sys_id, ISO_SYS_ID_LEN) + != 0 + || (i1.id.iso.level != i2.id.iso.level)) + return 0; + } else { + if (!IPV4_ADDR_SAME(&i1.id.ip.addr, &i2.id.ip.addr) + || !IPV4_ADDR_SAME(&i1.id.ip.area_id, &i2.id.ip.area_id)) + return 1; + } + + return 1; +} + +struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid, + struct in6_addr rid6) +{ + struct ls_node *new; + + if (adv.origin == UNKNOWN) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node)); + new->adv = adv; + if (!IPV4_NET0(rid.s_addr)) { + new->router_id = rid; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID); + } else { + if (adv.origin == OSPFv2 || adv.origin == STATIC + || adv.origin == DIRECT) { + new->router_id = adv.id.ip.addr; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID); + } + } + if (!IN6_IS_ADDR_UNSPECIFIED(&rid6)) { + new->router_id6 = rid6; + SET_FLAG(new->flags, LS_NODE_ROUTER_ID6); + } + return new; +} + +void ls_node_del(struct ls_node *node) +{ + if (!node) + return; + + XFREE(MTYPE_LS_DB, node); +} + +int ls_node_same(struct ls_node *n1, struct ls_node *n2) +{ + /* First, check pointer */ + if ((n1 && !n2) || (!n1 && n2)) + return 0; + + if (n1 == n2) + return 1; + + /* Then, verify Flags and Origin */ + if (n1->flags != n2->flags) + return 0; + + if (!ls_node_id_same(n1->adv, n2->adv)) + return 0; + + /* Finally, check each individual parameters that are valid */ + if (CHECK_FLAG(n1->flags, LS_NODE_NAME) + && (strncmp(n1->name, n2->name, MAX_NAME_LENGTH) != 0)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_ROUTER_ID) + && !IPV4_ADDR_SAME(&n1->router_id, &n2->router_id)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_ROUTER_ID6) + && !IPV6_ADDR_SAME(&n1->router_id6, &n2->router_id6)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_FLAG) + && (n1->node_flag != n2->node_flag)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_TYPE) && (n1->type != n2->type)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_AS_NUMBER) + && (n1->as_number != n2->as_number)) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_SR)) { + if (n1->srgb.flag != n2->srgb.flag + || n1->srgb.lower_bound != n2->srgb.lower_bound + || n1->srgb.range_size != n2->srgb.range_size) + return 0; + if ((n1->algo[0] != n2->algo[0]) + || (n1->algo[1] != n2->algo[1])) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_SRLB) + && ((n1->srlb.lower_bound != n2->srlb.lower_bound + || n1->srlb.range_size != n2->srlb.range_size))) + return 0; + if (CHECK_FLAG(n1->flags, LS_NODE_MSD) && (n1->msd != n2->msd)) + return 0; + } + if (CHECK_FLAG(n1->flags, LS_NODE_SRV6)) { + if (n1->srv6_cap_flags != n2->srv6_cap_flags) + return 0; + if (memcmp(&n1->srv6_msd, &n2->srv6_msd, sizeof(n1->srv6_msd))) + return 0; + } + + /* OK, n1 & n2 are equal */ + return 1; +} + +/** + * Link State Attributes management functions + */ +struct ls_attributes *ls_attributes_new(struct ls_node_id adv, + struct in_addr local, + struct in6_addr local6, + uint32_t local_id) +{ + struct ls_attributes *new; + + if (adv.origin == UNKNOWN) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes)); + new->adv = adv; + if (!IPV4_NET0(local.s_addr)) { + new->standard.local = local; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR); + } + if (!IN6_IS_ADDR_UNSPECIFIED(&local6)) { + new->standard.local6 = local6; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ADDR6); + } + if (local_id != 0) { + new->standard.local_id = local_id; + SET_FLAG(new->flags, LS_ATTR_LOCAL_ID); + } + + /* Check that almost one identifier is set */ + if (!CHECK_FLAG(new->flags, LS_ATTR_LOCAL_ADDR | LS_ATTR_LOCAL_ADDR6 + | LS_ATTR_LOCAL_ID)) { + XFREE(MTYPE_LS_DB, new); + return NULL; + } + + admin_group_init(&new->ext_admin_group); + + return new; +} + +void ls_attributes_srlg_del(struct ls_attributes *attr) +{ + if (!attr) + return; + + if (attr->srlgs) + XFREE(MTYPE_LS_DB, attr->srlgs); + + attr->srlgs = NULL; + attr->srlg_len = 0; + UNSET_FLAG(attr->flags, LS_ATTR_SRLG); +} + +void ls_attributes_del(struct ls_attributes *attr) +{ + if (!attr) + return; + + ls_attributes_srlg_del(attr); + + admin_group_term(&attr->ext_admin_group); + + XFREE(MTYPE_LS_DB, attr); +} + +int ls_attributes_same(struct ls_attributes *l1, struct ls_attributes *l2) +{ + /* First, check pointer */ + if ((l1 && !l2) || (!l1 && l2)) + return 0; + + if (l1 == l2) + return 1; + + /* Then, verify Flags and Origin */ + if (l1->flags != l2->flags) + return 0; + + if (!ls_node_id_same(l1->adv, l2->adv)) + return 0; + + /* Finally, check each individual parameters that are valid */ + if (CHECK_FLAG(l1->flags, LS_ATTR_NAME) + && strncmp(l1->name, l2->name, MAX_NAME_LENGTH) != 0) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_METRIC) && (l1->metric != l2->metric)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_TE_METRIC) + && (l1->standard.te_metric != l2->standard.te_metric)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_ADM_GRP) + && (l1->standard.admin_group != l2->standard.admin_group)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_EXT_ADM_GRP) && + !admin_group_cmp(&l1->ext_admin_group, &l2->ext_admin_group)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ADDR) + && !IPV4_ADDR_SAME(&l1->standard.local, &l2->standard.local)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ADDR) + && !IPV4_ADDR_SAME(&l1->standard.remote, &l2->standard.remote)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ADDR6) + && !IPV6_ADDR_SAME(&l1->standard.local6, &l2->standard.local6)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ADDR6) + && !IPV6_ADDR_SAME(&l1->standard.remote6, &l2->standard.remote6)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_LOCAL_ID) + && (l1->standard.local_id != l2->standard.local_id)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_NEIGH_ID) + && (l1->standard.remote_id != l2->standard.remote_id)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_MAX_BW) + && (l1->standard.max_bw != l2->standard.max_bw)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_MAX_RSV_BW) + && (l1->standard.max_rsv_bw != l2->standard.max_rsv_bw)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_UNRSV_BW) + && memcmp(&l1->standard.unrsv_bw, &l2->standard.unrsv_bw, 32) != 0) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_AS) + && (l1->standard.remote_as != l2->standard.remote_as)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_ADDR) + && !IPV4_ADDR_SAME(&l1->standard.remote_addr, + &l2->standard.remote_addr)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_REMOTE_ADDR6) + && !IPV6_ADDR_SAME(&l1->standard.remote_addr6, + &l2->standard.remote_addr6)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_DELAY) + && (l1->extended.delay != l2->extended.delay)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_MIN_MAX_DELAY) + && ((l1->extended.min_delay != l2->extended.min_delay) + || (l1->extended.max_delay != l2->extended.max_delay))) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_JITTER) + && (l1->extended.jitter != l2->extended.jitter)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_PACKET_LOSS) + && (l1->extended.pkt_loss != l2->extended.pkt_loss)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_AVA_BW) + && (l1->extended.ava_bw != l2->extended.ava_bw)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_RSV_BW) + && (l1->extended.rsv_bw != l2->extended.rsv_bw)) + return 0; + if (CHECK_FLAG(l1->flags, LS_ATTR_USE_BW) + && (l1->extended.used_bw != l2->extended.used_bw)) + return 0; + for (int i = 0; i < LS_ADJ_MAX; i++) { + if (!CHECK_FLAG(l1->flags, (LS_ATTR_ADJ_SID << i))) + continue; + if ((l1->adj_sid[i].sid != l2->adj_sid[i].sid) + || (l1->adj_sid[i].flags != l2->adj_sid[i].flags) + || (l1->adj_sid[i].weight != l2->adj_sid[i].weight)) + return 0; + if (((l1->adv.origin == ISIS_L1) || (l1->adv.origin == ISIS_L2)) + && (memcmp(&l1->adj_sid[i].neighbor.sysid, + &l2->adj_sid[i].neighbor.sysid, ISO_SYS_ID_LEN) + != 0)) + return 0; + if (((l1->adv.origin == OSPFv2) || (l1->adv.origin == STATIC) + || (l1->adv.origin == DIRECT)) + && (i < ADJ_PRI_IPV6) + && (!IPV4_ADDR_SAME(&l1->adj_sid[i].neighbor.addr, + &l2->adj_sid[i].neighbor.addr))) + return 0; + } + for (int i = 0; i < ADJ_SRV6_MAX; i++) { + if (!CHECK_FLAG(l1->flags, (LS_ATTR_ADJ_SRV6SID << i))) + continue; + if (memcmp(&l1->adj_srv6_sid[i].sid, &l2->adj_srv6_sid[i].sid, + sizeof(struct in6_addr)) || + (l1->adj_srv6_sid[i].flags != l2->adj_srv6_sid[i].flags) || + (l1->adj_srv6_sid[i].weight != l2->adj_srv6_sid[i].weight) || + (l1->adj_srv6_sid[i].endpoint_behavior != + l2->adj_srv6_sid[i].endpoint_behavior)) + return 0; + if (((l1->adv.origin == ISIS_L1) || + (l1->adv.origin == ISIS_L2)) && + (memcmp(&l1->adj_srv6_sid[i].neighbor.sysid, + &l2->adj_srv6_sid[i].neighbor.sysid, + ISO_SYS_ID_LEN) != 0)) + return 0; + } + if (CHECK_FLAG(l1->flags, LS_ATTR_SRLG) + && ((l1->srlg_len != l2->srlg_len) + || memcmp(l1->srlgs, l2->srlgs, + l1->srlg_len * sizeof(uint32_t)) + != 0)) + return 0; + + /* OK, l1 & l2 are equal */ + return 1; +} + +/** + * Link State prefix management functions + */ +struct ls_prefix *ls_prefix_new(struct ls_node_id adv, struct prefix *p) +{ + struct ls_prefix *new; + + if (adv.origin == UNKNOWN) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_prefix)); + new->adv = adv; + new->pref = *p; + + return new; +} + +void ls_prefix_del(struct ls_prefix *pref) +{ + if (!pref) + return; + + XFREE(MTYPE_LS_DB, pref); +} + +int ls_prefix_same(struct ls_prefix *p1, struct ls_prefix *p2) +{ + /* First, check pointer */ + if ((p1 && !p2) || (!p1 && p2)) + return 0; + + if (p1 == p2) + return 1; + + /* Then, verify Flags and Origin */ + if (p1->flags != p2->flags) + return 0; + + if (!ls_node_id_same(p1->adv, p2->adv)) + return 0; + + /* Finally, check each individual parameters that are valid */ + if (prefix_same(&p1->pref, &p2->pref) == 0) + return 0; + if (CHECK_FLAG(p1->flags, LS_PREF_IGP_FLAG) + && (p1->igp_flag != p2->igp_flag)) + return 0; + if (CHECK_FLAG(p1->flags, LS_PREF_ROUTE_TAG) + && (p1->route_tag != p2->route_tag)) + return 0; + if (CHECK_FLAG(p1->flags, LS_PREF_EXTENDED_TAG) + && (p1->extended_tag != p2->extended_tag)) + return 0; + if (CHECK_FLAG(p1->flags, LS_PREF_METRIC) && (p1->metric != p2->metric)) + return 0; + if (CHECK_FLAG(p1->flags, LS_PREF_SR)) { + if ((p1->sr.algo != p2->sr.algo) || (p1->sr.sid != p2->sr.sid) + || (p1->sr.sid_flag != p2->sr.sid_flag)) + return 0; + } + + /* OK, p1 & p2 are equal */ + return 1; +} + +/** + * Link State Vertices management functions + */ +uint64_t sysid_to_key(const uint8_t sysid[ISO_SYS_ID_LEN]) +{ + uint64_t key = 0; + +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t *byte = (uint8_t *)&key; + + for (int i = 0; i < ISO_SYS_ID_LEN; i++) + byte[i] = sysid[ISO_SYS_ID_LEN - i - 1]; + + byte[6] = 0; + byte[7] = 0; +#else + memcpy(&key, sysid, ISO_SYS_ID_LEN); +#endif + + return key; +} + +struct ls_vertex *ls_vertex_add(struct ls_ted *ted, struct ls_node *node) +{ + struct ls_vertex *new; + uint64_t key = 0; + + if ((ted == NULL) || (node == NULL)) + return NULL; + + /* set Key as the IPv4/Ipv6 Router ID or ISO System ID */ + switch (node->adv.origin) { + case OSPFv2: + case STATIC: + case DIRECT: + key = ((uint64_t)ntohl(node->adv.id.ip.addr.s_addr)) + & 0xffffffff; + break; + case ISIS_L1: + case ISIS_L2: + key = sysid_to_key(node->adv.id.iso.sys_id); + break; + case UNKNOWN: + key = 0; + break; + } + + /* Check that key is valid */ + if (key == 0) + return NULL; + + /* Create Vertex and add it to the TED */ + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_vertex)); + if (!new) + return NULL; + + new->key = key; + new->node = node; + new->status = NEW; + new->type = VERTEX; + new->incoming_edges = list_new(); + new->incoming_edges->cmp = (int (*)(void *, void *))edge_cmp; + new->outgoing_edges = list_new(); + new->outgoing_edges->cmp = (int (*)(void *, void *))edge_cmp; + new->prefixes = list_new(); + new->prefixes->cmp = (int (*)(void *, void *))subnet_cmp; + vertices_add(&ted->vertices, new); + + return new; +} + +void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex) +{ + struct listnode *node, *nnode; + struct ls_edge *edge; + struct ls_subnet *subnet; + + if (!ted || !vertex) + return; + + /* Remove outgoing Edges and list */ + for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) + ls_edge_del_all(ted, edge); + list_delete(&vertex->outgoing_edges); + + /* Disconnect incoming Edges and remove list */ + for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) { + ls_disconnect(vertex, edge, false); + if (edge->source == NULL) + ls_edge_del_all(ted, edge); + } + list_delete(&vertex->incoming_edges); + + /* Remove subnet and list */ + for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) + ls_subnet_del_all(ted, subnet); + list_delete(&vertex->prefixes); + + /* Then remove Vertex from Link State Data Base and free memory */ + vertices_del(&ted->vertices, vertex); + XFREE(MTYPE_LS_DB, vertex); +} + +void ls_vertex_del_all(struct ls_ted *ted, struct ls_vertex *vertex) +{ + if (!ted || !vertex) + return; + + /* First remove associated Link State Node */ + ls_node_del(vertex->node); + + /* Then, Vertex itself */ + ls_vertex_del(ted, vertex); +} + +struct ls_vertex *ls_vertex_update(struct ls_ted *ted, struct ls_node *node) +{ + struct ls_vertex *old; + + if (node == NULL) + return NULL; + + old = ls_find_vertex_by_id(ted, node->adv); + if (old) { + if (!ls_node_same(old->node, node)) { + ls_node_del(old->node); + old->node = node; + } else + ls_node_del(node); + + old->status = UPDATE; + return old; + } + + return ls_vertex_add(ted, node); +} + +struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, const uint64_t key) +{ + struct ls_vertex vertex = {}; + + if (key == 0) + return NULL; + + vertex.key = key; + return vertices_find(&ted->vertices, &vertex); +} + +struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, + struct ls_node_id nid) +{ + struct ls_vertex vertex = {}; + + vertex.key = 0; + switch (nid.origin) { + case OSPFv2: + case STATIC: + case DIRECT: + vertex.key = + ((uint64_t)ntohl(nid.id.ip.addr.s_addr)) & 0xffffffff; + break; + case ISIS_L1: + case ISIS_L2: + vertex.key = sysid_to_key(nid.id.iso.sys_id); + break; + case UNKNOWN: + return NULL; + } + + return vertices_find(&ted->vertices, &vertex); +} + +int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2) +{ + if ((v1 && !v2) || (!v1 && v2)) + return 0; + + if (!v1 && !v2) + return 1; + + if (v1->key != v2->key) + return 0; + + if (v1->node == v2->node) + return 1; + + return ls_node_same(v1->node, v2->node); +} + +void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex, + struct zclient *zclient) +{ + struct listnode *node, *nnode; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct ls_message msg; + + /* Remove Orphan Edge ... */ + for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) { + if (edge->status == ORPHAN) { + if (zclient) { + edge->status = DELETE; + ls_edge2msg(&msg, edge); + ls_send_msg(zclient, &msg, NULL); + } + ls_edge_del_all(ted, edge); + } + } + for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) { + if (edge->status == ORPHAN) { + if (zclient) { + edge->status = DELETE; + ls_edge2msg(&msg, edge); + ls_send_msg(zclient, &msg, NULL); + } + ls_edge_del_all(ted, edge); + } + } + + /* ... and Subnet from the Vertex */ + for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) { + if (subnet->status == ORPHAN) { + if (zclient) { + subnet->status = DELETE; + ls_subnet2msg(&msg, subnet); + ls_send_msg(zclient, &msg, NULL); + } + ls_subnet_del_all(ted, subnet); + } + } +} + +/** + * Link State Edges management functions + */ + +/** + * This function allows to connect the Edge to the vertices present in the TED. + * A temporary vertex that corresponds to the source of this Edge i.e. the + * advertised router, is created if not found in the Data Base. If a Edge that + * corresponds to the reverse path is found, the Edge is attached to the + * destination vertex as destination and reverse Edge is attached to the source + * vertex as source. + * + * @param ted Link State Data Base + * @param edge Link State Edge to be attached + */ +static void ls_edge_connect_to(struct ls_ted *ted, struct ls_edge *edge) +{ + struct ls_vertex *vertex = NULL; + struct ls_node *node; + struct ls_edge *dst; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + /* First, search if there is a Vertex that correspond to the Node ID */ + vertex = ls_find_vertex_by_id(ted, edge->attributes->adv); + if (vertex == NULL) { + /* Create a new temporary Node & Vertex if not found */ + node = ls_node_new(edge->attributes->adv, inaddr_any, + in6addr_any); + vertex = ls_vertex_add(ted, node); + } + /* and attach the edge as source to the vertex */ + listnode_add_sort_nodup(vertex->outgoing_edges, edge); + edge->source = vertex; + + /* Then search if there is a reverse Edge */ + dst = ls_find_edge_by_destination(ted, edge->attributes); + /* attach the destination edge to the vertex */ + if (dst) { + listnode_add_sort_nodup(vertex->incoming_edges, dst); + dst->destination = vertex; + /* and destination vertex to this edge */ + vertex = dst->source; + listnode_add_sort_nodup(vertex->incoming_edges, edge); + edge->destination = vertex; + } +} + +static struct ls_edge_key get_edge_key(struct ls_attributes *attr, bool dst) +{ + struct ls_edge_key key = {.family = AF_UNSPEC}; + struct ls_standard *std; + + if (!attr) + return key; + + std = &attr->standard; + + if (dst) { + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) { + /* Key is the IPv4 remote address */ + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &std->remote); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) { + /* or the IPv6 remote address */ + key.family = AF_INET6; + IPV6_ADDR_COPY(&key.k.addr6, &std->remote6); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) { + /* or Remote identifier if IP addr. are not defined */ + key.family = AF_LOCAL; + key.k.link_id = + (((uint64_t)std->remote_id) & 0xffffffff) | + ((uint64_t)std->local_id << 32); + } + } else { + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) { + /* Key is the IPv4 local address */ + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &std->local); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) { + /* or the 64 bits LSB of IPv6 local address */ + key.family = AF_INET6; + IPV6_ADDR_COPY(&key.k.addr6, &std->local6); + } else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) { + /* or Remote identifier if IP addr. are not defined */ + key.family = AF_LOCAL; + key.k.link_id = + (((uint64_t)std->local_id) & 0xffffffff) | + ((uint64_t)std->remote_id << 32); + } + } + + return key; +} + +struct ls_edge *ls_edge_add(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge *new; + struct ls_edge_key key; + + if (attributes == NULL) + return NULL; + + key = get_edge_key(attributes, false); + if (key.family == AF_UNSPEC) + return NULL; + + /* Create Edge and add it to the TED */ + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_edge)); + + new->attributes = attributes; + new->key = key; + new->status = NEW; + new->type = EDGE; + edges_add(&ted->edges, new); + + /* Finally, connect Edge to Vertices */ + ls_edge_connect_to(ted, new); + + return new; +} + +struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, + const struct ls_edge_key key) +{ + struct ls_edge edge = {}; + + if (key.family == AF_UNSPEC) + return NULL; + + edge.key = key; + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_find_edge_by_source(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge edge = {}; + + if (attributes == NULL) + return NULL; + + edge.key = get_edge_key(attributes, false); + if (edge.key.family == AF_UNSPEC) + return NULL; + + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_find_edge_by_destination(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge edge = {}; + + if (attributes == NULL) + return NULL; + + edge.key = get_edge_key(attributes, true); + if (edge.key.family == AF_UNSPEC) + return NULL; + + return edges_find(&ted->edges, &edge); +} + +struct ls_edge *ls_edge_update(struct ls_ted *ted, + struct ls_attributes *attributes) +{ + struct ls_edge *old; + + if (attributes == NULL) + return NULL; + + /* First, search for an existing Edge */ + old = ls_find_edge_by_source(ted, attributes); + if (old) { + /* Check if attributes are similar */ + if (!ls_attributes_same(old->attributes, attributes)) { + ls_attributes_del(old->attributes); + old->attributes = attributes; + } else + ls_attributes_del(attributes); + + old->status = UPDATE; + return old; + } + + /* If not found, add new Edge from the attributes */ + return ls_edge_add(ted, attributes); +} + +int ls_edge_same(struct ls_edge *e1, struct ls_edge *e2) +{ + if ((e1 && !e2) || (!e1 && e2)) + return 0; + + if (!e1 && !e2) + return 1; + + if (edge_cmp(e1, e2) != 0) + return 0; + + if (e1->attributes == e2->attributes) + return 1; + + return ls_attributes_same(e1->attributes, e2->attributes); +} + +void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge) +{ + if (!ted || !edge) + return; + + /* Fist disconnect Edge from Vertices */ + ls_disconnect_edge(edge); + /* Then remove it from the Data Base */ + edges_del(&ted->edges, edge); + XFREE(MTYPE_LS_DB, edge); +} + +void ls_edge_del_all(struct ls_ted *ted, struct ls_edge *edge) +{ + if (!ted || !edge) + return; + + /* Remove associated Link State Attributes */ + ls_attributes_del(edge->attributes); + /* Then Edge itself */ + ls_edge_del(ted, edge); +} + +/** + * Link State Subnet Management functions. + */ +struct ls_subnet *ls_subnet_add(struct ls_ted *ted, + struct ls_prefix *ls_pref) +{ + struct ls_subnet *new; + struct ls_vertex *vertex; + struct ls_node *node; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + if (ls_pref == NULL) + return NULL; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_subnet)); + new->ls_pref = ls_pref; + new->key = ls_pref->pref; + new->status = NEW; + new->type = SUBNET; + + /* Find Vertex */ + vertex = ls_find_vertex_by_id(ted, ls_pref->adv); + if (vertex == NULL) { + /* Create a new temporary Node & Vertex if not found */ + node = ls_node_new(ls_pref->adv, inaddr_any, in6addr_any); + vertex = ls_vertex_add(ted, node); + } + /* And attach the subnet to the corresponding Vertex */ + new->vertex = vertex; + listnode_add_sort_nodup(vertex->prefixes, new); + + subnets_add(&ted->subnets, new); + + return new; +} + +struct ls_subnet *ls_subnet_update(struct ls_ted *ted, struct ls_prefix *pref) +{ + struct ls_subnet *old; + + if (pref == NULL) + return NULL; + + old = ls_find_subnet(ted, &pref->pref); + if (old) { + if (!ls_prefix_same(old->ls_pref, pref)) { + ls_prefix_del(old->ls_pref); + old->ls_pref = pref; + } else + ls_prefix_del(pref); + + old->status = UPDATE; + return old; + } + + return ls_subnet_add(ted, pref); +} + +int ls_subnet_same(struct ls_subnet *s1, struct ls_subnet *s2) +{ + if ((s1 && !s2) || (!s1 && s2)) + return 0; + + if (!s1 && !s2) + return 1; + + if (!prefix_same(&s1->key, &s2->key)) + return 0; + + if (s1->ls_pref == s2->ls_pref) + return 1; + + return ls_prefix_same(s1->ls_pref, s2->ls_pref); +} + +void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet) +{ + if (!ted || !subnet) + return; + + /* First, disconnect Subnet from associated Vertex */ + listnode_delete(subnet->vertex->prefixes, subnet); + /* Then delete Subnet */ + subnets_del(&ted->subnets, subnet); + XFREE(MTYPE_LS_DB, subnet); +} + +void ls_subnet_del_all(struct ls_ted *ted, struct ls_subnet *subnet) +{ + if (!ted || !subnet) + return; + + /* First, remove associated Link State Subnet */ + ls_prefix_del(subnet->ls_pref); + /* Then, delete Subnet itself */ + ls_subnet_del(ted, subnet); +} + +struct ls_subnet *ls_find_subnet(struct ls_ted *ted, + const struct prefix *prefix) +{ + struct ls_subnet subnet = {}; + + if (!prefix) + return NULL; + + prefix_copy(&subnet.key, prefix); + return subnets_find(&ted->subnets, &subnet); +} + +/** + * Link State TED management functions + */ +struct ls_ted *ls_ted_new(const uint32_t key, const char *name, + uint32_t as_number) +{ + struct ls_ted *new; + + new = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_ted)); + + /* Set basic information for this ted */ + new->key = key; + new->as_number = as_number; + strlcpy(new->name, name, MAX_NAME_LENGTH); + + /* Initialize the various RB tree */ + vertices_init(&new->vertices); + edges_init(&new->edges); + subnets_init(&new->subnets); + + return new; +} + +void ls_ted_del(struct ls_ted *ted) +{ + if (ted == NULL) + return; + + /* Check that TED is empty */ + if (vertices_count(&ted->vertices) || edges_count(&ted->edges) + || subnets_count(&ted->subnets)) + return; + + /* Release RB Tree */ + vertices_fini(&ted->vertices); + edges_fini(&ted->edges); + subnets_fini(&ted->subnets); + + XFREE(MTYPE_LS_DB, ted); +} + +void ls_ted_del_all(struct ls_ted **ted) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + + if (*ted == NULL) + return; + + /* First remove Vertices, Edges and Subnets and associated Link State */ + frr_each_safe (vertices, &(*ted)->vertices, vertex) + ls_vertex_del_all(*ted, vertex); + frr_each_safe (edges, &(*ted)->edges, edge) + ls_edge_del_all(*ted, edge); + frr_each_safe (subnets, &(*ted)->subnets, subnet) + ls_subnet_del_all(*ted, subnet); + + /* then remove TED itself */ + ls_ted_del(*ted); + *ted = NULL; +} + +void ls_ted_clean(struct ls_ted *ted) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + + if (ted == NULL) + return; + + /* First, start with Vertices */ + frr_each_safe (vertices, &ted->vertices, vertex) + if (vertex->status == ORPHAN) + ls_vertex_del_all(ted, vertex); + + /* Then Edges */ + frr_each_safe (edges, &ted->edges, edge) + if (edge->status == ORPHAN) + ls_edge_del_all(ted, edge); + + /* and Subnets */ + frr_each_safe (subnets, &ted->subnets, subnet) + if (subnet->status == ORPHAN) + ls_subnet_del_all(ted, subnet); + +} + +void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +{ + if (vertex == NULL || edge == NULL) + return; + + if (source) { + listnode_add_sort_nodup(vertex->outgoing_edges, edge); + edge->source = vertex; + } else { + listnode_add_sort_nodup(vertex->incoming_edges, edge); + edge->destination = vertex; + } +} + +void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, bool source) +{ + + if (vertex == NULL || edge == NULL) + return; + + if (source) { + listnode_delete(vertex->outgoing_edges, edge); + edge->source = NULL; + } else { + listnode_delete(vertex->incoming_edges, edge); + edge->destination = NULL; + } +} + +void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, + struct ls_edge *edge) +{ + if (edge == NULL) + return; + + edge->source = src; + edge->destination = dst; + + if (src != NULL) + listnode_add_sort_nodup(src->outgoing_edges, edge); + + if (dst != NULL) + listnode_add_sort_nodup(dst->incoming_edges, edge); +} + +void ls_disconnect_edge(struct ls_edge *edge) +{ + if (edge == NULL) + return; + + ls_disconnect(edge->source, edge, true); + ls_disconnect(edge->destination, edge, false); + + /* Mark this Edge as ORPHAN for future cleanup */ + edge->status = ORPHAN; +} + +/** + * Link State Message management functions + */ + +int ls_register(struct zclient *zclient, bool server) +{ + int rc; + + if (server) + rc = zclient_register_opaque(zclient, LINK_STATE_SYNC); + else + rc = zclient_register_opaque(zclient, LINK_STATE_UPDATE); + + return rc; +} + +int ls_unregister(struct zclient *zclient, bool server) +{ + int rc; + + if (server) + rc = zclient_unregister_opaque(zclient, LINK_STATE_SYNC); + else + rc = zclient_unregister_opaque(zclient, LINK_STATE_UPDATE); + + return rc; +} + +int ls_request_sync(struct zclient *zclient) +{ + /* Check buffer size */ + if (STREAM_SIZE(zclient->obuf) + < (ZEBRA_HEADER_SIZE + 3 * sizeof(uint32_t))) + return -1; + + /* No data with this message */ + return zclient_send_opaque(zclient, LINK_STATE_SYNC, NULL, 0); +} + +static struct ls_node *ls_parse_node(struct stream *s) +{ + struct ls_node *node; + size_t len; + + node = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_node)); + + STREAM_GET(&node->adv, s, sizeof(struct ls_node_id)); + STREAM_GETW(s, node->flags); + if (CHECK_FLAG(node->flags, LS_NODE_NAME)) { + STREAM_GETC(s, len); + STREAM_GET(node->name, s, len); + } + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID)) + node->router_id.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6)) + STREAM_GET(&node->router_id6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(node->flags, LS_NODE_FLAG)) + STREAM_GETC(s, node->node_flag); + if (CHECK_FLAG(node->flags, LS_NODE_TYPE)) + STREAM_GETC(s, node->type); + if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER)) + STREAM_GETL(s, node->as_number); + if (CHECK_FLAG(node->flags, LS_NODE_SR)) { + STREAM_GETL(s, node->srgb.lower_bound); + STREAM_GETL(s, node->srgb.range_size); + STREAM_GETC(s, node->srgb.flag); + STREAM_GET(node->algo, s, 2); + } + if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) { + STREAM_GETL(s, node->srlb.lower_bound); + STREAM_GETL(s, node->srlb.range_size); + } + if (CHECK_FLAG(node->flags, LS_NODE_MSD)) + STREAM_GETC(s, node->msd); + + return node; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Node. Abort!", __func__); + XFREE(MTYPE_LS_DB, node); + return NULL; +} + +static struct ls_attributes *ls_parse_attributes(struct stream *s) +{ + struct ls_attributes *attr; + uint8_t nb_ext_adm_grp; + uint32_t bitmap_data; + size_t len; + + attr = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_attributes)); + admin_group_init(&attr->ext_admin_group); + attr->srlgs = NULL; + + STREAM_GET(&attr->adv, s, sizeof(struct ls_node_id)); + STREAM_GETL(s, attr->flags); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) { + STREAM_GETC(s, len); + STREAM_GET(attr->name, s, len); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC)) + STREAM_GETL(s, attr->metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + STREAM_GETL(s, attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + STREAM_GETL(s, attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP)) { + /* Extended Administrative Group */ + STREAM_GETC(s, nb_ext_adm_grp); + for (size_t i = 0; i < nb_ext_adm_grp; i++) { + STREAM_GETL(s, bitmap_data); + admin_group_bulk_set(&attr->ext_admin_group, + bitmap_data, i); + } + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + attr->standard.local.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) + attr->standard.remote.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + STREAM_GET(&attr->standard.local6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) + STREAM_GET(&attr->standard.remote6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + STREAM_GETL(s, attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + STREAM_GETL(s, attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + STREAM_GETF(s, attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + STREAM_GETF(s, attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) + for (len = 0; len < MAX_CLASS_TYPE; len++) + STREAM_GETF(s, attr->standard.unrsv_bw[len]); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + STREAM_GETL(s, attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) + attr->standard.remote_addr.s_addr = stream_get_ipv4(s); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) + STREAM_GET(&attr->standard.remote_addr6, s, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + STREAM_GETL(s, attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + STREAM_GETL(s, attr->extended.min_delay); + STREAM_GETL(s, attr->extended.max_delay); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + STREAM_GETL(s, attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + STREAM_GETL(s, attr->extended.pkt_loss); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + STREAM_GETF(s, attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + STREAM_GETF(s, attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + STREAM_GETF(s, attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + STREAM_GETL(s, attr->adj_sid[ADJ_PRI_IPV4].sid); + STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV4].flags); + STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV4].weight); + attr->adj_sid[ADJ_PRI_IPV4].neighbor.addr.s_addr = + stream_get_ipv4(s); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + STREAM_GETL(s, attr->adj_sid[ADJ_BCK_IPV4].sid); + STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV4].flags); + STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV4].weight); + attr->adj_sid[ADJ_BCK_IPV4].neighbor.addr.s_addr = + stream_get_ipv4(s); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) { + STREAM_GETL(s, attr->adj_sid[ADJ_PRI_IPV6].sid); + STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV6].flags); + STREAM_GETC(s, attr->adj_sid[ADJ_PRI_IPV6].weight); + STREAM_GET(attr->adj_sid[ADJ_PRI_IPV6].neighbor.sysid, s, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) { + STREAM_GETL(s, attr->adj_sid[ADJ_BCK_IPV6].sid); + STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV6].flags); + STREAM_GETC(s, attr->adj_sid[ADJ_BCK_IPV6].weight); + STREAM_GET(attr->adj_sid[ADJ_BCK_IPV6].neighbor.sysid, s, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID)) { + STREAM_GET(&attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].sid, s, + sizeof(struct in6_addr)); + STREAM_GETC(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].flags); + STREAM_GETC(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].weight); + STREAM_GETW(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6] + .endpoint_behavior); + STREAM_GET(attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].neighbor.sysid, + s, ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID)) { + STREAM_GET(&attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].sid, s, + sizeof(struct in6_addr)); + STREAM_GETC(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].flags); + STREAM_GETC(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].weight); + STREAM_GETW(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6] + .endpoint_behavior); + STREAM_GET(attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].neighbor.sysid, + s, ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + STREAM_GETC(s, len); + attr->srlgs = XCALLOC(MTYPE_LS_DB, len*sizeof(uint32_t)); + attr->srlg_len = len; + for (len = 0; len < attr->srlg_len; len++) + STREAM_GETL(s, attr->srlgs[len]); + } + + return attr; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Attributes. Abort!", + __func__); + /* Clean memory allocation */ + if (attr->srlgs != NULL) + XFREE(MTYPE_LS_DB, attr->srlgs); + XFREE(MTYPE_LS_DB, attr); + return NULL; + +} + +static struct ls_prefix *ls_parse_prefix(struct stream *s) +{ + struct ls_prefix *ls_pref; + size_t len; + + ls_pref = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_prefix)); + + STREAM_GET(&ls_pref->adv, s, sizeof(struct ls_node_id)); + STREAM_GETW(s, ls_pref->flags); + STREAM_GETC(s, ls_pref->pref.family); + STREAM_GETW(s, ls_pref->pref.prefixlen); + len = prefix_blen(&ls_pref->pref); + STREAM_GET(&ls_pref->pref.u.prefix, s, len); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG)) + STREAM_GETC(s, ls_pref->igp_flag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG)) + STREAM_GETL(s, ls_pref->route_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG)) + STREAM_GETQ(s, ls_pref->extended_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)) + STREAM_GETL(s, ls_pref->metric); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) { + STREAM_GETL(s, ls_pref->sr.sid); + STREAM_GETC(s, ls_pref->sr.sid_flag); + STREAM_GETC(s, ls_pref->sr.algo); + } + + return ls_pref; + +stream_failure: + zlog_err("LS(%s): Could not parse Link State Prefix. Abort!", __func__); + XFREE(MTYPE_LS_DB, ls_pref); + return NULL; +} + +struct ls_message *ls_parse_msg(struct stream *s) +{ + struct ls_message *msg; + + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + + /* Read LS Message header */ + STREAM_GETC(s, msg->event); + STREAM_GETC(s, msg->type); + + /* Read Message Payload */ + switch (msg->type) { + case LS_MSG_TYPE_NODE: + msg->data.node = ls_parse_node(s); + break; + case LS_MSG_TYPE_ATTRIBUTES: + STREAM_GET(&msg->remote_id, s, sizeof(struct ls_node_id)); + msg->data.attr = ls_parse_attributes(s); + break; + case LS_MSG_TYPE_PREFIX: + msg->data.prefix = ls_parse_prefix(s); + break; + default: + zlog_err("Unsupported Payload"); + goto stream_failure; + } + + if (msg->data.node == NULL || msg->data.attr == NULL + || msg->data.prefix == NULL) + goto stream_failure; + + return msg; + +stream_failure: + zlog_err("LS(%s): Could not parse LS message. Abort!", __func__); + XFREE(MTYPE_LS_DB, msg); + return NULL; +} + +static int ls_format_node(struct stream *s, struct ls_node *node) +{ + size_t len; + + /* Push Advertise node information first */ + stream_put(s, &node->adv, sizeof(struct ls_node_id)); + + /* Push Flags & Origin then Node information if there are present */ + stream_putw(s, node->flags); + if (CHECK_FLAG(node->flags, LS_NODE_NAME)) { + len = strlen(node->name); + stream_putc(s, len + 1); + stream_put(s, node->name, len); + stream_putc(s, '\0'); + } + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID)) + stream_put_ipv4(s, node->router_id.s_addr); + if (CHECK_FLAG(node->flags, LS_NODE_ROUTER_ID6)) + stream_put(s, &node->router_id6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(node->flags, LS_NODE_FLAG)) + stream_putc(s, node->node_flag); + if (CHECK_FLAG(node->flags, LS_NODE_TYPE)) + stream_putc(s, node->type); + if (CHECK_FLAG(node->flags, LS_NODE_AS_NUMBER)) + stream_putl(s, node->as_number); + if (CHECK_FLAG(node->flags, LS_NODE_SR)) { + stream_putl(s, node->srgb.lower_bound); + stream_putl(s, node->srgb.range_size); + stream_putc(s, node->srgb.flag); + stream_put(s, node->algo, 2); + } + if (CHECK_FLAG(node->flags, LS_NODE_SRLB)) { + stream_putl(s, node->srlb.lower_bound); + stream_putl(s, node->srlb.range_size); + } + if (CHECK_FLAG(node->flags, LS_NODE_MSD)) + stream_putc(s, node->msd); + + return 0; +} + +static int ls_format_attributes(struct stream *s, struct ls_attributes *attr) +{ + size_t len, nb_ext_adm_grp; + + /* Push Advertise node information first */ + stream_put(s, &attr->adv, sizeof(struct ls_node_id)); + + /* Push Flags & Origin then LS attributes if there are present */ + stream_putl(s, attr->flags); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) { + len = strlen(attr->name); + stream_putc(s, len + 1); + stream_put(s, attr->name, len); + stream_putc(s, '\0'); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_METRIC)) + stream_putl(s, attr->metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + stream_putl(s, attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + stream_putl(s, attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP)) { + /* Extended Administrative Group */ + nb_ext_adm_grp = admin_group_nb_words(&attr->ext_admin_group); + stream_putc(s, nb_ext_adm_grp); + for (size_t i = 0; i < nb_ext_adm_grp; i++) + stream_putl(s, admin_group_get_offset( + &attr->ext_admin_group, i)); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + stream_put_ipv4(s, attr->standard.local.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) + stream_put_ipv4(s, attr->standard.remote.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + stream_put(s, &attr->standard.local6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) + stream_put(s, &attr->standard.remote6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + stream_putl(s, attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + stream_putl(s, attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + stream_putf(s, attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + stream_putf(s, attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) + for (len = 0; len < MAX_CLASS_TYPE; len++) + stream_putf(s, attr->standard.unrsv_bw[len]); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + stream_putl(s, attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) + stream_put_ipv4(s, attr->standard.remote_addr.s_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) + stream_put(s, &attr->standard.remote_addr6, IPV6_MAX_BYTELEN); + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + stream_putl(s, attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + stream_putl(s, attr->extended.min_delay); + stream_putl(s, attr->extended.max_delay); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + stream_putl(s, attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + stream_putl(s, attr->extended.pkt_loss); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + stream_putf(s, attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + stream_putf(s, attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + stream_putf(s, attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + stream_putl(s, attr->adj_sid[ADJ_PRI_IPV4].sid); + stream_putc(s, attr->adj_sid[ADJ_PRI_IPV4].flags); + stream_putc(s, attr->adj_sid[ADJ_PRI_IPV4].weight); + stream_put_ipv4( + s, attr->adj_sid[ADJ_PRI_IPV4].neighbor.addr.s_addr); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + stream_putl(s, attr->adj_sid[ADJ_BCK_IPV4].sid); + stream_putc(s, attr->adj_sid[ADJ_BCK_IPV4].flags); + stream_putc(s, attr->adj_sid[ADJ_BCK_IPV4].weight); + stream_put_ipv4( + s, attr->adj_sid[ADJ_BCK_IPV4].neighbor.addr.s_addr); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) { + stream_putl(s, attr->adj_sid[ADJ_PRI_IPV6].sid); + stream_putc(s, attr->adj_sid[ADJ_PRI_IPV6].flags); + stream_putc(s, attr->adj_sid[ADJ_PRI_IPV6].weight); + stream_put(s, attr->adj_sid[ADJ_PRI_IPV6].neighbor.sysid, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) { + stream_putl(s, attr->adj_sid[ADJ_BCK_IPV6].sid); + stream_putc(s, attr->adj_sid[ADJ_BCK_IPV6].flags); + stream_putc(s, attr->adj_sid[ADJ_BCK_IPV6].weight); + stream_put(s, attr->adj_sid[ADJ_BCK_IPV6].neighbor.sysid, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID)) { + stream_put(s, &attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].sid, + sizeof(struct in6_addr)); + stream_putc(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].flags); + stream_putc(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].weight); + stream_putw(s, attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6] + .endpoint_behavior); + stream_put(s, + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].neighbor.sysid, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID)) { + stream_put(s, &attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].sid, + sizeof(struct in6_addr)); + stream_putc(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].flags); + stream_putc(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].weight); + stream_putw(s, attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6] + .endpoint_behavior); + stream_put(s, + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].neighbor.sysid, + ISO_SYS_ID_LEN); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + stream_putc(s, attr->srlg_len); + for (len = 0; len < attr->srlg_len; len++) + stream_putl(s, attr->srlgs[len]); + } + + return 0; +} + +static int ls_format_prefix(struct stream *s, struct ls_prefix *ls_pref) +{ + size_t len; + + /* Push Advertise node information first */ + stream_put(s, &ls_pref->adv, sizeof(struct ls_node_id)); + + /* Push Flags, Origin & Prefix then information if there are present */ + stream_putw(s, ls_pref->flags); + stream_putc(s, ls_pref->pref.family); + stream_putw(s, ls_pref->pref.prefixlen); + len = prefix_blen(&ls_pref->pref); + stream_put(s, &ls_pref->pref.u.prefix, len); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_IGP_FLAG)) + stream_putc(s, ls_pref->igp_flag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_ROUTE_TAG)) + stream_putl(s, ls_pref->route_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_EXTENDED_TAG)) + stream_putq(s, ls_pref->extended_tag); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)) + stream_putl(s, ls_pref->metric); + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) { + stream_putl(s, ls_pref->sr.sid); + stream_putc(s, ls_pref->sr.sid_flag); + stream_putc(s, ls_pref->sr.algo); + } + + return 0; +} + +static int ls_format_msg(struct stream *s, struct ls_message *msg) +{ + + /* Prepare Link State header */ + stream_putc(s, msg->event); + stream_putc(s, msg->type); + + /* Add Message Payload */ + switch (msg->type) { + case LS_MSG_TYPE_NODE: + return ls_format_node(s, msg->data.node); + case LS_MSG_TYPE_ATTRIBUTES: + /* Add remote node first */ + stream_put(s, &msg->remote_id, sizeof(struct ls_node_id)); + return ls_format_attributes(s, msg->data.attr); + case LS_MSG_TYPE_PREFIX: + return ls_format_prefix(s, msg->data.prefix); + default: + zlog_warn("Unsupported Payload"); + break; + } + + return -1; +} + +int ls_send_msg(struct zclient *zclient, struct ls_message *msg, + struct zapi_opaque_reg_info *dst) +{ + struct stream *s; + uint16_t flags = 0; + + /* Check if we have a valid message */ + if (msg->event == LS_MSG_EVENT_UNDEF) + return -1; + + /* Check buffer size */ + if (STREAM_SIZE(zclient->obuf) < + (ZEBRA_HEADER_SIZE + sizeof(uint32_t) + sizeof(msg))) + return -1; + + /* Init the message, then encode the data inline. */ + if (dst == NULL) + zapi_opaque_init(zclient, LINK_STATE_UPDATE, flags); + else + zapi_opaque_unicast_init(zclient, LINK_STATE_UPDATE, flags, + dst->proto, dst->instance, + dst->session_id); + + s = zclient->obuf; + + /* Format Link State message */ + if (ls_format_msg(s, msg) < 0) { + stream_reset(s); + return -1; + } + + /* Put length into the header at the start of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} +struct ls_message *ls_vertex2msg(struct ls_message *msg, + struct ls_vertex *vertex) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_NODE; + switch (vertex->status) { + case NEW: + msg->event = LS_MSG_EVENT_ADD; + break; + case UPDATE: + msg->event = LS_MSG_EVENT_UPDATE; + break; + case DELETE: + msg->event = LS_MSG_EVENT_DELETE; + break; + case SYNC: + msg->event = LS_MSG_EVENT_SYNC; + break; + case UNSET: + case ORPHAN: + msg->event = LS_MSG_EVENT_UNDEF; + break; + } + msg->data.node = vertex->node; + msg->remote_id.origin = UNKNOWN; + + return msg; +} + +struct ls_message *ls_edge2msg(struct ls_message *msg, struct ls_edge *edge) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_ATTRIBUTES; + switch (edge->status) { + case NEW: + msg->event = LS_MSG_EVENT_ADD; + break; + case UPDATE: + msg->event = LS_MSG_EVENT_UPDATE; + break; + case DELETE: + msg->event = LS_MSG_EVENT_DELETE; + break; + case SYNC: + msg->event = LS_MSG_EVENT_SYNC; + break; + case UNSET: + case ORPHAN: + msg->event = LS_MSG_EVENT_UNDEF; + break; + } + msg->data.attr = edge->attributes; + if (edge->destination != NULL) + msg->remote_id = edge->destination->node->adv; + else + msg->remote_id.origin = UNKNOWN; + + return msg; +} + +struct ls_message *ls_subnet2msg(struct ls_message *msg, + struct ls_subnet *subnet) +{ + /* Allocate space if needed */ + if (msg == NULL) + msg = XCALLOC(MTYPE_LS_DB, sizeof(struct ls_message)); + else + memset(msg, 0, sizeof(*msg)); + + msg->type = LS_MSG_TYPE_PREFIX; + switch (subnet->status) { + case NEW: + msg->event = LS_MSG_EVENT_ADD; + break; + case UPDATE: + msg->event = LS_MSG_EVENT_UPDATE; + break; + case DELETE: + msg->event = LS_MSG_EVENT_DELETE; + break; + case SYNC: + msg->event = LS_MSG_EVENT_SYNC; + break; + case UNSET: + case ORPHAN: + msg->event = LS_MSG_EVENT_UNDEF; + break; + } + msg->data.prefix = subnet->ls_pref; + msg->remote_id.origin = UNKNOWN; + + return msg; +} + +struct ls_vertex *ls_msg2vertex(struct ls_ted *ted, struct ls_message *msg, + bool delete) +{ + struct ls_node *node = msg->data.node; + struct ls_vertex *vertex = NULL; + + switch (msg->event) { + case LS_MSG_EVENT_SYNC: + vertex = ls_vertex_add(ted, node); + if (vertex) + vertex->status = SYNC; + break; + case LS_MSG_EVENT_ADD: + vertex = ls_vertex_add(ted, node); + if (vertex) + vertex->status = NEW; + break; + case LS_MSG_EVENT_UPDATE: + vertex = ls_vertex_update(ted, node); + if (vertex) + vertex->status = UPDATE; + break; + case LS_MSG_EVENT_DELETE: + vertex = ls_find_vertex_by_id(ted, node->adv); + if (vertex) { + if (delete) { + ls_vertex_del_all(ted, vertex); + vertex = NULL; + } else + vertex->status = DELETE; + } + break; + default: + vertex = NULL; + break; + } + + return vertex; +} + +struct ls_edge *ls_msg2edge(struct ls_ted *ted, struct ls_message *msg, + bool delete) +{ + struct ls_attributes *attr = msg->data.attr; + struct ls_edge *edge = NULL; + + switch (msg->event) { + case LS_MSG_EVENT_SYNC: + edge = ls_edge_add(ted, attr); + if (edge) + edge->status = SYNC; + break; + case LS_MSG_EVENT_ADD: + edge = ls_edge_add(ted, attr); + if (edge) + edge->status = NEW; + break; + case LS_MSG_EVENT_UPDATE: + edge = ls_edge_update(ted, attr); + if (edge) + edge->status = UPDATE; + break; + case LS_MSG_EVENT_DELETE: + edge = ls_find_edge_by_source(ted, attr); + if (edge) { + if (delete) { + ls_edge_del_all(ted, edge); + edge = NULL; + } else + edge->status = DELETE; + } + break; + default: + edge = NULL; + break; + } + + return edge; +} + +struct ls_subnet *ls_msg2subnet(struct ls_ted *ted, struct ls_message *msg, + bool delete) +{ + struct ls_prefix *pref = msg->data.prefix; + struct ls_subnet *subnet = NULL; + + switch (msg->event) { + case LS_MSG_EVENT_SYNC: + subnet = ls_subnet_add(ted, pref); + if (subnet) + subnet->status = SYNC; + break; + case LS_MSG_EVENT_ADD: + subnet = ls_subnet_add(ted, pref); + if (subnet) + subnet->status = NEW; + break; + case LS_MSG_EVENT_UPDATE: + subnet = ls_subnet_update(ted, pref); + if (subnet) + subnet->status = UPDATE; + break; + case LS_MSG_EVENT_DELETE: + subnet = ls_find_subnet(ted, &pref->pref); + if (subnet) { + if (delete) { + ls_subnet_del_all(ted, subnet); + subnet = NULL; + } else + subnet->status = DELETE; + } + break; + default: + subnet = NULL; + break; + } + + return subnet; +} + +struct ls_element *ls_msg2ted(struct ls_ted *ted, struct ls_message *msg, + bool delete) +{ + struct ls_element *lse = NULL; + + switch (msg->type) { + case LS_MSG_TYPE_NODE: + lse = (struct ls_element *)ls_msg2vertex(ted, msg, delete); + break; + case LS_MSG_TYPE_ATTRIBUTES: + lse = (struct ls_element *)ls_msg2edge(ted, msg, delete); + break; + case LS_MSG_TYPE_PREFIX: + lse = (struct ls_element *)ls_msg2subnet(ted, msg, delete); + break; + default: + lse = NULL; + break; + } + + return lse; +} + +struct ls_element *ls_stream2ted(struct ls_ted *ted, struct stream *s, + bool delete) +{ + struct ls_message *msg; + struct ls_element *lse = NULL; + + msg = ls_parse_msg(s); + if (msg) { + lse = ls_msg2ted(ted, msg, delete); + ls_delete_msg(msg); + } + + return lse; +} + +void ls_delete_msg(struct ls_message *msg) +{ + if (msg == NULL) + return; + + if (msg->event == LS_MSG_EVENT_DELETE) { + switch (msg->type) { + case LS_MSG_TYPE_NODE: + ls_node_del(msg->data.node); + break; + case LS_MSG_TYPE_ATTRIBUTES: + ls_attributes_del(msg->data.attr); + break; + case LS_MSG_TYPE_PREFIX: + ls_prefix_del(msg->data.prefix); + break; + } + } + + XFREE(MTYPE_LS_DB, msg); +} + +int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, + struct zapi_opaque_reg_info *dst) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + struct ls_message msg; + + /* Loop TED, start sending Node, then Attributes and finally Prefix */ + frr_each(vertices, &ted->vertices, vertex) { + ls_vertex2msg(&msg, vertex); + ls_send_msg(zclient, &msg, dst); + } + frr_each(edges, &ted->edges, edge) { + ls_edge2msg(&msg, edge); + ls_send_msg(zclient, &msg, dst); + } + frr_each(subnets, &ted->subnets, subnet) { + ls_subnet2msg(&msg, subnet); + ls_send_msg(zclient, &msg, dst); + } + return 0; +} + +/** + * Link State Show functions + */ +static const char *const origin2txt[] = { + "Unknown", + "ISIS_L1", + "ISIS_L2", + "OSPFv2", + "Direct", + "Static" +}; + +static const char *const type2txt[] = { + "Unknown", + "Standard", + "ABR", + "ASBR", + "Remote ASBR", + "Pseudo" +}; + +static const char *const status2txt[] = { + "Unknown", + "New", + "Update", + "Delete", + "Sync", + "Orphan" +}; + +static const char *ls_node_id_to_text(struct ls_node_id lnid, char *str, + size_t size) +{ + if (lnid.origin == ISIS_L1 || lnid.origin == ISIS_L2) + snprintfrr(str, size, "%pSY", lnid.id.iso.sys_id); + else + snprintfrr(str, size, "%pI4", &lnid.id.ip.addr); + + return str; +} + +static void ls_show_vertex_vty(struct ls_vertex *vertex, struct vty *vty, + bool verbose) +{ + struct listnode *node; + struct ls_node *lsn; + struct ls_edge *edge; + struct ls_attributes *attr; + struct ls_subnet *subnet; + struct sbuf sbuf; + uint32_t upper; + + /* Sanity Check */ + if (!vertex) + return; + + lsn = vertex->node; + + sbuf_init(&sbuf, NULL, 0); + + sbuf_push(&sbuf, 2, "Vertex (%" PRIu64 "): %s", vertex->key, lsn->name); + sbuf_push(&sbuf, 0, "\tRouter Id: %pI4", &lsn->router_id); + sbuf_push(&sbuf, 0, "\tOrigin: %s", origin2txt[lsn->adv.origin]); + sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[vertex->status]); + if (!verbose) { + sbuf_push( + &sbuf, 0, + "\t%d Outgoing Edges, %d Incoming Edges, %d Subnets\n", + listcount(vertex->outgoing_edges), + listcount(vertex->incoming_edges), + listcount(vertex->prefixes)); + goto end; + } + + if (CHECK_FLAG(lsn->flags, LS_NODE_TYPE)) + sbuf_push(&sbuf, 4, "Type: %s\n", type2txt[lsn->type]); + if (CHECK_FLAG(lsn->flags, LS_NODE_AS_NUMBER)) + sbuf_push(&sbuf, 4, "AS number: %u\n", lsn->as_number); + if (CHECK_FLAG(lsn->flags, LS_NODE_SR)) { + sbuf_push(&sbuf, 4, "Segment Routing Capabilities:\n"); + upper = lsn->srgb.lower_bound + lsn->srgb.range_size - 1; + sbuf_push(&sbuf, 8, "SRGB: [%d/%d]", lsn->srgb.lower_bound, + upper); + if (CHECK_FLAG(lsn->flags, LS_NODE_SRLB)) { + upper = lsn->srlb.lower_bound + lsn->srlb.range_size + - 1; + sbuf_push(&sbuf, 0, "\tSRLB: [%d/%d]", + lsn->srlb.lower_bound, upper); + } + sbuf_push(&sbuf, 0, "\tAlgo: "); + for (int i = 0; i < 2; i++) { + if (lsn->algo[i] == 255) + continue; + + sbuf_push(&sbuf, 0, + lsn->algo[i] == 0 ? "SPF " : "S-SPF "); + } + if (CHECK_FLAG(lsn->flags, LS_NODE_MSD)) + sbuf_push(&sbuf, 0, "\tMSD: %d", lsn->msd); + sbuf_push(&sbuf, 0, "\n"); + } + + sbuf_push(&sbuf, 4, "Outgoing Edges: %d\n", + listcount(vertex->outgoing_edges)); + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) { + if (edge->destination) { + lsn = edge->destination->node; + sbuf_push(&sbuf, 6, "To:\t%s(%pI4)", lsn->name, + &lsn->router_id); + } else { + sbuf_push(&sbuf, 6, "To:\t- (0.0.0.0)"); + } + attr = edge->attributes; + if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))) + sbuf_push(&sbuf, 0, "\tLocal: %pI4\tRemote: %pI4\n", + &attr->standard.local, + &attr->standard.remote); + else if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))) + sbuf_push(&sbuf, 0, "\tLocal: %pI6\tRemote: %pI6\n", + &attr->standard.local6, + &attr->standard.remote6); + } + + sbuf_push(&sbuf, 4, "Incoming Edges: %d\n", + listcount(vertex->incoming_edges)); + for (ALL_LIST_ELEMENTS_RO(vertex->incoming_edges, node, edge)) { + if (edge->source) { + lsn = edge->source->node; + sbuf_push(&sbuf, 6, "From:\t%s(%pI4)", lsn->name, + &lsn->router_id); + } else { + sbuf_push(&sbuf, 6, "From:\t- (0.0.0.0)"); + } + attr = edge->attributes; + if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))) + sbuf_push(&sbuf, 0, "\tLocal: %pI4\tRemote: %pI4\n", + &attr->standard.local, + &attr->standard.remote); + else if ((CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))) + sbuf_push(&sbuf, 0, "\tLocal: %pI6\tRemote: %pI6\n", + &attr->standard.local6, + &attr->standard.remote6); + } + + sbuf_push(&sbuf, 4, "Subnets: %d\n", listcount(vertex->prefixes)); + for (ALL_LIST_ELEMENTS_RO(vertex->prefixes, node, subnet)) + sbuf_push(&sbuf, 6, "Prefix:\t%pFX\n", &subnet->key); + +end: + vty_out(vty, "%s\n", sbuf_buf(&sbuf)); + sbuf_free(&sbuf); +} + +static void ls_show_vertex_json(struct ls_vertex *vertex, + struct json_object *json) +{ + struct ls_node *lsn; + json_object *jsr, *jalgo, *jobj; + char buf[INET6_BUFSIZ]; + + /* Sanity Check */ + if (!vertex) + return; + + lsn = vertex->node; + + json_object_int_add(json, "vertex-id", vertex->key); + json_object_string_add(json, "status", status2txt[vertex->status]); + json_object_string_add(json, "origin", origin2txt[lsn->adv.origin]); + if (CHECK_FLAG(lsn->flags, LS_NODE_NAME)) + json_object_string_add(json, "name", lsn->name); + if (CHECK_FLAG(lsn->flags, LS_NODE_ROUTER_ID)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI4", &lsn->router_id); + json_object_string_add(json, "router-id", buf); + } + if (CHECK_FLAG(lsn->flags, LS_NODE_ROUTER_ID6)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI6", &lsn->router_id6); + json_object_string_add(json, "router-id-v6", buf); + } + if (CHECK_FLAG(lsn->flags, LS_NODE_TYPE)) + json_object_string_add(json, "vertex-type", + type2txt[lsn->type]); + if (CHECK_FLAG(lsn->flags, LS_NODE_AS_NUMBER)) + json_object_int_add(json, "asn", lsn->as_number); + if (CHECK_FLAG(lsn->flags, LS_NODE_SR)) { + jsr = json_object_new_object(); + json_object_object_add(json, "segment-routing", jsr); + json_object_int_add(jsr, "srgb-size", lsn->srgb.range_size); + json_object_int_add(jsr, "srgb-lower", lsn->srgb.lower_bound); + jalgo = json_object_new_array(); + json_object_object_add(jsr, "algorithms", jalgo); + for (int i = 0; i < 2; i++) { + if (lsn->algo[i] == 255) + continue; + jobj = json_object_new_object(); + + snprintfrr(buf, 2, "%u", i); + json_object_string_add( + jobj, buf, lsn->algo[i] == 0 ? "SPF" : "S-SPF"); + json_object_array_add(jalgo, jobj); + } + if (CHECK_FLAG(lsn->flags, LS_NODE_SRLB)) { + json_object_int_add(jsr, "srlb-size", + lsn->srlb.range_size); + json_object_int_add(jsr, "srlb-lower", + lsn->srlb.lower_bound); + } + if (CHECK_FLAG(lsn->flags, LS_NODE_MSD)) + json_object_int_add(jsr, "msd", lsn->msd); + } +} + +void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty, + struct json_object *json, bool verbose) +{ + if (json) + ls_show_vertex_json(vertex, json); + else if (vty) + ls_show_vertex_vty(vertex, vty, verbose); +} + +void ls_show_vertices(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose) +{ + struct ls_vertex *vertex; + json_object *jnodes, *jnode; + + if (json) { + jnodes = json_object_new_array(); + json_object_object_add(json, "vertices", jnodes); + frr_each (vertices, &ted->vertices, vertex) { + jnode = json_object_new_object(); + ls_show_vertex(vertex, NULL, jnode, verbose); + json_object_array_add(jnodes, jnode); + } + } else if (vty) { + frr_each (vertices, &ted->vertices, vertex) + ls_show_vertex(vertex, vty, NULL, verbose); + } +} + +static const char *edge_key_to_text(struct ls_edge_key key) +{ +#define FORMAT_BUF_COUNT 4 + static char buf_ring[FORMAT_BUF_COUNT][INET6_BUFSIZ]; + static size_t cur_buf = 0; + char *rv; + + rv = buf_ring[cur_buf]; + cur_buf = (cur_buf + 1) % FORMAT_BUF_COUNT; + + switch (key.family) { + case AF_INET: + snprintfrr(rv, INET6_BUFSIZ, "%pI4", &key.k.addr); + break; + case AF_INET6: + snprintfrr(rv, INET6_BUFSIZ, "%pI6", &key.k.addr6); + break; + case AF_LOCAL: + snprintfrr(rv, INET6_BUFSIZ, "%" PRIu64, key.k.link_id); + break; + default: + snprintfrr(rv, INET6_BUFSIZ, "(Unknown)"); + break; + } + + return rv; +} + +static void ls_show_edge_vty(struct ls_edge *edge, struct vty *vty, + bool verbose) +{ + char admin_group_buf[ADMIN_GROUP_PRINT_MAX_SIZE]; + struct ls_attributes *attr; + struct sbuf sbuf; + char buf[INET6_BUFSIZ]; + int indent; + + attr = edge->attributes; + sbuf_init(&sbuf, NULL, 0); + + sbuf_push(&sbuf, 2, "Edge (%s): ", edge_key_to_text(edge->key)); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + sbuf_push(&sbuf, 0, "%pI4", &attr->standard.local); + else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + sbuf_push(&sbuf, 0, "%pI6", &attr->standard.local6); + else + sbuf_push(&sbuf, 0, "%u/%u", attr->standard.local_id, + attr->standard.remote_id); + ls_node_id_to_text(attr->adv, buf, INET6_BUFSIZ); + sbuf_push(&sbuf, 0, "\tAdv. Vertex: %s", buf); + sbuf_push(&sbuf, 0, "\tMetric: %u", attr->metric); + sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[edge->status]); + + if (!verbose) + goto end; + + sbuf_push(&sbuf, 4, "Origin: %s\n", origin2txt[attr->adv.origin]); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) + sbuf_push(&sbuf, 4, "Name: %s\n", attr->name); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + sbuf_push(&sbuf, 4, "TE Metric: %u\n", + attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + sbuf_push(&sbuf, 4, "Admin Group: 0x%x\n", + attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP) && + admin_group_nb_words(&attr->ext_admin_group) != 0) { + indent = 4; + sbuf_push(&sbuf, indent, "Ext Admin Group: %s\n", + admin_group_string( + admin_group_buf, ADMIN_GROUP_PRINT_MAX_SIZE, + indent + strlen("Ext Admin Group: "), + &attr->ext_admin_group)); + if (admin_group_buf[0] != '\0' && + (sbuf.pos + strlen(admin_group_buf) + + SBUF_DEFAULT_SIZE / 2) < sbuf.size) + sbuf_push(&sbuf, indent + 2, "Bit positions: %s\n", + admin_group_buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) + sbuf_push(&sbuf, 4, "Local IPv4 address: %pI4\n", + &attr->standard.local); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) + sbuf_push(&sbuf, 4, "Remote IPv4 address: %pI4\n", + &attr->standard.remote); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) + sbuf_push(&sbuf, 4, "Local IPv6 address: %pI6\n", + &attr->standard.local6); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) + sbuf_push(&sbuf, 4, "Remote IPv6 address: %pI6\n", + &attr->standard.remote6); + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + sbuf_push(&sbuf, 4, "Local Identifier: %u\n", + attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + sbuf_push(&sbuf, 4, "Remote Identifier: %u\n", + attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + sbuf_push(&sbuf, 4, "Maximum Bandwidth: %g (Bytes/s)\n", + attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + sbuf_push(&sbuf, 4, + "Maximum Reservable Bandwidth: %g (Bytes/s)\n", + attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) { + sbuf_push(&sbuf, 4, "Unreserved Bandwidth per Class Type\n"); + for (int i = 0; i < MAX_CLASS_TYPE; i += 2) + sbuf_push(&sbuf, 8, + "[%d]: %g (Bytes/sec)\t[%d]: %g (Bytes/s)\n", + i, attr->standard.unrsv_bw[i], i + 1, + attr->standard.unrsv_bw[i + 1]); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + sbuf_push(&sbuf, 4, "Remote AS: %u\n", + attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) + sbuf_push(&sbuf, 4, "Remote ASBR IPv4 address: %pI4\n", + &attr->standard.remote_addr); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) + sbuf_push(&sbuf, 4, "Remote ASBR IPv6 address: %pI6\n", + &attr->standard.remote_addr6); + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + sbuf_push(&sbuf, 4, "Average Link Delay: %d (micro-sec)\n", + attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) + sbuf_push(&sbuf, 4, "Min/Max Link Delay: %d/%d (micro-sec)\n", + attr->extended.min_delay, attr->extended.max_delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + sbuf_push(&sbuf, 4, "Delay Variation: %d (micro-sec)\n", + attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + sbuf_push(&sbuf, 4, "Link Loss: %g (%%)\n", + (float)(attr->extended.pkt_loss * LOSS_PRECISION)); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + sbuf_push(&sbuf, 4, "Available Bandwidth: %g (Bytes/s)\n", + attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + sbuf_push(&sbuf, 4, "Residual Bandwidth: %g (Bytes/s)\n", + attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + sbuf_push(&sbuf, 4, "Utilized Bandwidth: %g (Bytes/s)\n", + attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + sbuf_push(&sbuf, 4, "IPv4 Adjacency-SID: %u", + attr->adj_sid[ADJ_PRI_IPV4].sid); + sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n", + attr->adj_sid[ADJ_PRI_IPV4].flags, + attr->adj_sid[ADJ_PRI_IPV4].weight); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + sbuf_push(&sbuf, 4, "IPv4 Bck. Adjacency-SID: %u", + attr->adj_sid[ADJ_BCK_IPV4].sid); + sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n", + attr->adj_sid[ADJ_BCK_IPV4].flags, + attr->adj_sid[ADJ_BCK_IPV4].weight); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) { + sbuf_push(&sbuf, 4, "IPv6 Adjacency-SID: %u", + attr->adj_sid[ADJ_PRI_IPV6].sid); + sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n", + attr->adj_sid[ADJ_PRI_IPV6].flags, + attr->adj_sid[ADJ_PRI_IPV6].weight); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) { + sbuf_push(&sbuf, 4, "IPv6 Bck. Adjacency-SID: %u", + attr->adj_sid[ADJ_BCK_IPV6].sid); + sbuf_push(&sbuf, 0, "\tFlags: 0x%x\tWeight: 0x%x\n", + attr->adj_sid[ADJ_BCK_IPV6].flags, + attr->adj_sid[ADJ_BCK_IPV6].weight); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID)) { + sbuf_push(&sbuf, 4, "IPv6 Adjacency-SRV6-SID: %pI6", + &attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].sid); + sbuf_push(&sbuf, 0, + "\tFlags: 0x%x\tWeight: 0x%x\tbehavior: 0x%x\n", + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].flags, + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].weight, + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].endpoint_behavior); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID)) { + sbuf_push(&sbuf, 4, "IPv6 Bck. Adjacency-SRV6-SID: %pI6", + &attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].sid); + sbuf_push(&sbuf, 0, + "\tFlags: 0x%x\tWeight: 0x%x\tbehavior: 0x%x\n", + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].flags, + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].weight, + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].endpoint_behavior); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + sbuf_push(&sbuf, 4, "SRLGs: %d", attr->srlg_len); + for (int i = 1; i < attr->srlg_len; i++) { + if (i % 8) + sbuf_push(&sbuf, 8, "\n%u", attr->srlgs[i]); + else + sbuf_push(&sbuf, 8, ", %u", attr->srlgs[i]); + } + sbuf_push(&sbuf, 0, "\n"); + } + +end: + vty_out(vty, "%s\n", sbuf_buf(&sbuf)); + sbuf_free(&sbuf); +} + +static void ls_show_edge_json(struct ls_edge *edge, struct json_object *json) +{ + struct ls_attributes *attr; + struct json_object *jte, *jbw, *jobj, *jsr = NULL, *jsrlg, *js_ext_ag, + *js_ext_ag_arr_word, + *js_ext_ag_arr_bit, *jsrv6 = NULL; + char buf[INET6_BUFSIZ]; + char buf_ag[strlen("0xffffffff") + 1]; + uint32_t bitmap; + size_t i; + + attr = edge->attributes; + + json_object_string_add(json, "edge-id", edge_key_to_text(edge->key)); + json_object_string_add(json, "status", status2txt[edge->status]); + json_object_string_add(json, "origin", origin2txt[attr->adv.origin]); + ls_node_id_to_text(attr->adv, buf, INET6_BUFSIZ); + json_object_string_add(json, "advertised-router", buf); + if (edge->source) + json_object_int_add(json, "local-vertex-id", edge->source->key); + if (edge->destination) + json_object_int_add(json, "remote-vertex-id", + edge->destination->key); + json_object_int_add(json, "metric", attr->metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_NAME)) + json_object_string_add(json, "name", attr->name); + jte = json_object_new_object(); + json_object_object_add(json, "edge-attributes", jte); + if (CHECK_FLAG(attr->flags, LS_ATTR_TE_METRIC)) + json_object_int_add(jte, "te-metric", attr->standard.te_metric); + if (CHECK_FLAG(attr->flags, LS_ATTR_ADM_GRP)) + json_object_int_add(jte, "admin-group", + attr->standard.admin_group); + if (CHECK_FLAG(attr->flags, LS_ATTR_EXT_ADM_GRP)) { + js_ext_ag = json_object_new_object(); + json_object_object_add(jte, "extAdminGroup", js_ext_ag); + js_ext_ag_arr_word = json_object_new_array(); + json_object_object_add(js_ext_ag, "words", js_ext_ag_arr_word); + js_ext_ag_arr_bit = json_object_new_array(); + json_object_object_add(js_ext_ag, "bitPositions", + js_ext_ag_arr_bit); + for (i = 0; i < admin_group_nb_words(&attr->ext_admin_group); + i++) { + bitmap = admin_group_get_offset(&attr->ext_admin_group, + i); + snprintf(buf_ag, sizeof(buf_ag), "0x%08x", bitmap); + json_object_array_add(js_ext_ag_arr_word, + json_object_new_string(buf_ag)); + } + for (i = 0; + i < (admin_group_size(&attr->ext_admin_group) * WORD_SIZE); + i++) { + if (admin_group_get(&attr->ext_admin_group, i)) + json_object_array_add(js_ext_ag_arr_bit, + json_object_new_int(i)); + } + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI4", &attr->standard.local); + json_object_string_add(jte, "local-address", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI4", &attr->standard.remote); + json_object_string_add(jte, "remote-address", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI6", &attr->standard.local6); + json_object_string_add(jte, "local-address-v6", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI6", &attr->standard.remote6); + json_object_string_add(jte, "remote-address-v6", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID)) + json_object_int_add(jte, "local-identifier", + attr->standard.local_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_NEIGH_ID)) + json_object_int_add(jte, "remote-identifier", + attr->standard.remote_id); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_BW)) + json_object_double_add(jte, "max-link-bandwidth", + attr->standard.max_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW)) + json_object_double_add(jte, "max-resv-link-bandwidth", + attr->standard.max_rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_UNRSV_BW)) { + jbw = json_object_new_array(); + json_object_object_add(jte, "unreserved-bandwidth", jbw); + for (int i = 0; i < MAX_CLASS_TYPE; i++) { + jobj = json_object_new_object(); + snprintfrr(buf, 13, "class-type-%u", i); + json_object_double_add(jobj, buf, + attr->standard.unrsv_bw[i]); + json_object_array_add(jbw, jobj); + } + } + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_AS)) + json_object_int_add(jte, "remote-asn", + attr->standard.remote_as); + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI4", + &attr->standard.remote_addr); + json_object_string_add(jte, "remote-as-address", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR6)) { + snprintfrr(buf, INET6_BUFSIZ, "%pI6", + &attr->standard.remote_addr6); + json_object_string_add(jte, "remote-as-address-v6", buf); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_DELAY)) + json_object_int_add(jte, "delay", attr->extended.delay); + if (CHECK_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY)) { + json_object_int_add(jte, "min-delay", attr->extended.min_delay); + json_object_int_add(jte, "max-delay", attr->extended.max_delay); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_JITTER)) + json_object_int_add(jte, "jitter", attr->extended.jitter); + if (CHECK_FLAG(attr->flags, LS_ATTR_PACKET_LOSS)) + json_object_double_add( + jte, "loss", attr->extended.pkt_loss * LOSS_PRECISION); + if (CHECK_FLAG(attr->flags, LS_ATTR_AVA_BW)) + json_object_double_add(jte, "available-bandwidth", + attr->extended.ava_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_RSV_BW)) + json_object_double_add(jte, "residual-bandwidth", + attr->extended.rsv_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_USE_BW)) + json_object_double_add(jte, "utilized-bandwidth", + attr->extended.used_bw); + if (CHECK_FLAG(attr->flags, LS_ATTR_SRLG)) { + jsrlg = json_object_new_array(); + json_object_object_add(jte, "srlgs", jsrlg); + for (int i = 1; i < attr->srlg_len; i++) { + jobj = json_object_new_object(); + json_object_int_add(jobj, "srlg", attr->srlgs[i]); + json_object_array_add(jsrlg, jobj); + } + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID)) { + jsr = json_object_new_array(); + json_object_object_add(json, "segment-routing", jsr); + jobj = json_object_new_object(); + json_object_int_add(jobj, "adj-sid", + attr->adj_sid[ADJ_PRI_IPV4].sid); + snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_PRI_IPV4].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_sid[ADJ_PRI_IPV4].weight); + json_object_array_add(jsr, jobj); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + if (!jsr) { + jsr = json_object_new_array(); + json_object_object_add(json, "segment-routing", jsr); + } + jobj = json_object_new_object(); + json_object_int_add(jobj, "adj-sid", + attr->adj_sid[ADJ_BCK_IPV4].sid); + snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_BCK_IPV4].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_sid[ADJ_BCK_IPV4].weight); + json_object_array_add(jsr, jobj); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID6)) { + jsr = json_object_new_array(); + json_object_object_add(json, "segment-routing", jsr); + jobj = json_object_new_object(); + json_object_int_add(jobj, "adj-sid", + attr->adj_sid[ADJ_PRI_IPV6].sid); + snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_PRI_IPV6].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_sid[ADJ_PRI_IPV6].weight); + json_object_array_add(jsr, jobj); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6)) { + if (!jsr) { + jsr = json_object_new_array(); + json_object_object_add(json, "segment-routing", jsr); + } + jobj = json_object_new_object(); + json_object_int_add(jobj, "adj-sid", + attr->adj_sid[ADJ_BCK_IPV6].sid); + snprintfrr(buf, 6, "0x%x", attr->adj_sid[ADJ_BCK_IPV6].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_sid[ADJ_BCK_IPV6].weight); + json_object_array_add(jsr, jobj); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SRV6SID)) { + jsrv6 = json_object_new_array(); + json_object_object_add(json, "segment-routing-ipv6", jsrv6); + jobj = json_object_new_object(); + snprintfrr(buf, INET6_BUFSIZ, "%pI6", + &attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].sid); + json_object_string_add(jobj, "adj-sid", buf); + snprintfrr(buf, 6, "0x%x", + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6].weight); + snprintfrr(buf, 6, "0x%x", + attr->adj_srv6_sid[ADJ_SRV6_PRI_IPV6] + .endpoint_behavior); + json_object_string_add(jobj, "endpoint-behavior", buf); + json_object_array_add(jsr, jobj); + } + if (CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SRV6SID)) { + if (!jsrv6) { + jsrv6 = json_object_new_array(); + json_object_object_add(json, "segment-routing-ipv6", + jsrv6); + } + jobj = json_object_new_object(); + snprintfrr(buf, INET6_BUFSIZ, "%pI6", + &attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].sid); + json_object_string_add(jobj, "adj-sid", buf); + snprintfrr(buf, 6, "0x%x", + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].flags); + json_object_string_add(jobj, "flags", buf); + json_object_int_add(jobj, "weight", + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6].weight); + snprintfrr(buf, 6, "0x%x", + attr->adj_srv6_sid[ADJ_SRV6_BCK_IPV6] + .endpoint_behavior); + json_object_string_add(jobj, "endpoint-behavior", buf); + json_object_array_add(jsr, jobj); + } +} + +void ls_show_edge(struct ls_edge *edge, struct vty *vty, + struct json_object *json, bool verbose) +{ + /* Sanity Check */ + if (!edge) + return; + + if (json) + ls_show_edge_json(edge, json); + else if (vty) + ls_show_edge_vty(edge, vty, verbose); +} + +void ls_show_edges(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose) +{ + struct ls_edge *edge; + json_object *jedges, *jedge; + + if (json) { + jedges = json_object_new_array(); + json_object_object_add(json, "edges", jedges); + frr_each (edges, &ted->edges, edge) { + jedge = json_object_new_object(); + ls_show_edge(edge, NULL, jedge, verbose); + json_object_array_add(jedges, jedge); + } + } else if (vty) { + frr_each (edges, &ted->edges, edge) + ls_show_edge(edge, vty, NULL, verbose); + } +} + +static void ls_show_subnet_vty(struct ls_subnet *subnet, struct vty *vty, + bool verbose) +{ + struct ls_prefix *pref; + struct sbuf sbuf; + char buf[INET6_BUFSIZ]; + + pref = subnet->ls_pref; + sbuf_init(&sbuf, NULL, 0); + + sbuf_push(&sbuf, 2, "Subnet: %pFX", &subnet->key); + ls_node_id_to_text(pref->adv, buf, INET6_BUFSIZ); + sbuf_push(&sbuf, 0, "\tAdv. Vertex: %s", buf); + sbuf_push(&sbuf, 0, "\tMetric: %d", pref->metric); + sbuf_push(&sbuf, 0, "\tStatus: %s\n", status2txt[subnet->status]); + + if (!verbose) + goto end; + + sbuf_push(&sbuf, 4, "Origin: %s\n", origin2txt[pref->adv.origin]); + if (CHECK_FLAG(pref->flags, LS_PREF_IGP_FLAG)) + sbuf_push(&sbuf, 4, "Flags: %d\n", pref->igp_flag); + + if (CHECK_FLAG(pref->flags, LS_PREF_ROUTE_TAG)) + sbuf_push(&sbuf, 4, "Tag: %d\n", pref->route_tag); + + if (CHECK_FLAG(pref->flags, LS_PREF_EXTENDED_TAG)) + sbuf_push(&sbuf, 4, "Extended Tag: %" PRIu64 "\n", + pref->extended_tag); + + if (CHECK_FLAG(pref->flags, LS_PREF_SR)) + sbuf_push(&sbuf, 4, "SID: %d\tAlgorithm: %d\tFlags: 0x%x\n", + pref->sr.sid, pref->sr.algo, pref->sr.sid_flag); + +end: + vty_out(vty, "%s\n", sbuf_buf(&sbuf)); + sbuf_free(&sbuf); +} + +static void ls_show_subnet_json(struct ls_subnet *subnet, + struct json_object *json) +{ + struct ls_prefix *pref; + json_object *jsr; + char buf[INET6_BUFSIZ]; + + pref = subnet->ls_pref; + + snprintfrr(buf, INET6_BUFSIZ, "%pFX", &subnet->key); + json_object_string_add(json, "subnet-id", buf); + json_object_string_add(json, "status", status2txt[subnet->status]); + json_object_string_add(json, "origin", origin2txt[pref->adv.origin]); + ls_node_id_to_text(pref->adv, buf, INET6_BUFSIZ); + json_object_string_add(json, "advertised-router", buf); + if (subnet->vertex) + json_object_int_add(json, "vertex-id", subnet->vertex->key); + json_object_int_add(json, "metric", pref->metric); + if (CHECK_FLAG(pref->flags, LS_PREF_IGP_FLAG)) { + snprintfrr(buf, INET6_BUFSIZ, "0x%x", pref->igp_flag); + json_object_string_add(json, "flags", buf); + } + if (CHECK_FLAG(pref->flags, LS_PREF_ROUTE_TAG)) + json_object_int_add(json, "tag", pref->route_tag); + if (CHECK_FLAG(pref->flags, LS_PREF_EXTENDED_TAG)) + json_object_int_add(json, "extended-tag", pref->extended_tag); + if (CHECK_FLAG(pref->flags, LS_PREF_SR)) { + jsr = json_object_new_object(); + json_object_object_add(json, "segment-routing", jsr); + json_object_int_add(jsr, "pref-sid", pref->sr.sid); + json_object_int_add(jsr, "algo", pref->sr.algo); + snprintfrr(buf, INET6_BUFSIZ, "0x%x", pref->sr.sid_flag); + json_object_string_add(jsr, "flags", buf); + } +} + +void ls_show_subnet(struct ls_subnet *subnet, struct vty *vty, + struct json_object *json, bool verbose) +{ + /* Sanity Check */ + if (!subnet) + return; + + if (json) + ls_show_subnet_json(subnet, json); + else if (vty) + ls_show_subnet_vty(subnet, vty, verbose); +} + +void ls_show_subnets(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose) +{ + struct ls_subnet *subnet; + json_object *jsubs, *jsub; + + if (json) { + jsubs = json_object_new_array(); + json_object_object_add(json, "subnets", jsubs); + frr_each (subnets, &ted->subnets, subnet) { + jsub = json_object_new_object(); + ls_show_subnet(subnet, NULL, jsub, verbose); + json_object_array_add(jsubs, jsub); + } + } else if (vty) { + frr_each (subnets, &ted->subnets, subnet) + ls_show_subnet(subnet, vty, NULL, verbose); + } +} + +void ls_show_ted(struct ls_ted *ted, struct vty *vty, struct json_object *json, + bool verbose) +{ + json_object *jted; + + if (json) { + jted = json_object_new_object(); + json_object_object_add(json, "ted", jted); + json_object_string_add(jted, "name", ted->name); + json_object_int_add(jted, "key", ted->key); + json_object_int_add(jted, "verticesCount", + vertices_count(&ted->vertices)); + json_object_int_add(jted, "edgesCount", + edges_count(&ted->edges)); + json_object_int_add(jted, "subnetsCount", + subnets_count(&ted->subnets)); + ls_show_vertices(ted, NULL, jted, verbose); + ls_show_edges(ted, NULL, jted, verbose); + ls_show_subnets(ted, NULL, jted, verbose); + return; + } + + if (vty) { + vty_out(vty, + "\n\tTraffic Engineering Database: %s (key: %d)\n\n", + ted->name, ted->key); + ls_show_vertices(ted, vty, NULL, verbose); + ls_show_edges(ted, vty, NULL, verbose); + ls_show_subnets(ted, vty, NULL, verbose); + vty_out(vty, + "\n\tTotal: %zu Vertices, %zu Edges, %zu Subnets\n\n", + vertices_count(&ted->vertices), + edges_count(&ted->edges), subnets_count(&ted->subnets)); + } +} + +void ls_dump_ted(struct ls_ted *ted) +{ + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + zlog_debug("(%s) Ted init", __func__); + + /* Loop TED, start printing Node, then Attributes and finally Prefix */ + frr_each (vertices, &ted->vertices, vertex) { + zlog_debug(" Ted node (%s %pI4 %s)", + vertex->node->name[0] ? vertex->node->name + : "no name node", + &vertex->node->router_id, + origin2txt[vertex->node->adv.origin]); + struct listnode *lst_node; + struct ls_edge *vertex_edge; + + for (ALL_LIST_ELEMENTS_RO(vertex->incoming_edges, lst_node, + vertex_edge)) { + zlog_debug( + " inc edge key:%s attr key:%pI4 loc:(%pI4) rmt:(%pI4)", + edge_key_to_text(vertex_edge->key), + &vertex_edge->attributes->adv.id.ip.addr, + &vertex_edge->attributes->standard.local, + &vertex_edge->attributes->standard.remote); + } + for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, lst_node, + vertex_edge)) { + zlog_debug( + " out edge key:%s attr key:%pI4 loc:(%pI4) rmt:(%pI4)", + edge_key_to_text(vertex_edge->key), + &vertex_edge->attributes->adv.id.ip.addr, + &vertex_edge->attributes->standard.local, + &vertex_edge->attributes->standard.remote); + } + } + frr_each (edges, &ted->edges, edge) { + zlog_debug(" Ted edge key:%s src:%pI4 dst:%pI4", + edge_key_to_text(edge->key), + edge->source ? &edge->source->node->router_id + : &inaddr_any, + edge->destination + ? &edge->destination->node->router_id + : &inaddr_any); + } + frr_each (subnets, &ted->subnets, subnet) { + zlog_debug(" Ted subnet key:%pFX vertex:%pI4", + &subnet->ls_pref->pref, + &subnet->vertex->node->adv.id.ip.addr); + } + zlog_debug("(%s) Ted end", __func__); +} diff --git a/lib/link_state.h b/lib/link_state.h new file mode 100644 index 0000000..d819c20 --- /dev/null +++ b/lib/link_state.h @@ -0,0 +1,1169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Link State Database definition - ted.h + * + * Author: Olivier Dugeon + * + * Copyright (C) 2020 Orange http://www.orange.com + * + * This file is part of Free Range Routing (FRR). + */ + +#ifndef _FRR_LINK_STATE_H_ +#define _FRR_LINK_STATE_H_ + +#include "admin_group.h" +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This file defines the model used to implement a Link State Database + * suitable to be used by various protocol like RSVP-TE, BGP-LS, PCEP ... + * This database is normally fulfill by the link state routing protocol, + * commonly OSPF or ISIS, carrying Traffic Engineering information within + * Link State Attributes. See, RFC3630.(OSPF-TE) and RFC5305 (ISIS-TE). + * + * At least, 3 types of Link State structure are defined: + * - Link State Node that groups all information related to a node + * - Link State Attributes that groups all information related to a link + * - Link State Prefix that groups all information related to a prefix + * + * These 3 types of structures are those handled by BGP-LS (see RFC7752). + * + * Each structure, in addition to the specific parameters, embed the node + * identifier which advertises the Link State and a bit mask as flags to + * indicates which parameters are valid i.e. for which the value corresponds + * to a Link State information convey by the routing protocol. + * Node identifier is composed of the route id as IPv4 address plus the area + * id for OSPF and the ISO System id plus the IS-IS level for IS-IS. + */ + +/* external reference */ +struct zapi_opaque_reg_info; +struct zclient; + +/* Link State Common definitions */ +#define MAX_NAME_LENGTH 256 +#define ISO_SYS_ID_LEN 6 + +/* Type of Node */ +enum ls_node_type { + NONE = 0, /* Unknown */ + STANDARD, /* a P or PE node */ + ABR, /* an Array Border Node */ + ASBR, /* an Autonomous System Border Node */ + RMT_ASBR, /* Remote ASBR */ + PSEUDO /* a Pseudo Node */ +}; + +/* Origin of the Link State information */ +enum ls_origin { UNKNOWN = 0, ISIS_L1, ISIS_L2, OSPFv2, DIRECT, STATIC }; + +/** + * Link State Node Identifier as: + * - IPv4 address + Area ID for OSPF + * - ISO System ID + ISIS Level for ISIS + */ +struct ls_node_id { + enum ls_origin origin; /* Origin of the LS information */ + union { + struct { + struct in_addr addr; /* OSPF Router IS */ + struct in_addr area_id; /* OSPF Area ID */ + } ip; + struct { + uint8_t sys_id[ISO_SYS_ID_LEN]; /* ISIS System ID */ + uint8_t level; /* ISIS Level */ + uint8_t padding; + } iso; + } id; +}; + +/** + * Check if two Link State Node IDs are equal. Note that this routine has the + * same return value sense as '==' (which is different from a comparison). + * + * @param i1 First Link State Node Identifier + * @param i2 Second Link State Node Identifier + * @return 1 if equal, 0 otherwise + */ +extern int ls_node_id_same(struct ls_node_id i1, struct ls_node_id i2); + +/* Supported number of algorithm by the link-state library */ +#define LIB_LS_SR_ALGO_COUNT 2 + +/* Link State flags to indicate which Node parameters are valid */ +#define LS_NODE_UNSET 0x0000 +#define LS_NODE_NAME 0x0001 +#define LS_NODE_ROUTER_ID 0x0002 +#define LS_NODE_ROUTER_ID6 0x0004 +#define LS_NODE_FLAG 0x0008 +#define LS_NODE_TYPE 0x0010 +#define LS_NODE_AS_NUMBER 0x0020 +#define LS_NODE_SR 0x0040 +#define LS_NODE_SRLB 0x0080 +#define LS_NODE_MSD 0x0100 +#define LS_NODE_SRV6 0x0200 + +/* Link State Node structure */ +struct ls_node { + uint16_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + char name[MAX_NAME_LENGTH]; /* Name of the Node (IS-IS only) */ + struct in_addr router_id; /* IPv4 Router ID */ + struct in6_addr router_id6; /* IPv6 Router ID */ + uint8_t node_flag; /* IS-IS or OSPF Node flag */ + enum ls_node_type type; /* Type of Node */ + uint32_t as_number; /* Local or neighbor AS number */ + struct ls_srgb { /* Segment Routing Global Block */ + uint32_t lower_bound; /* MPLS label lower bound */ + uint32_t range_size; /* MPLS label range size */ + uint8_t flag; /* IS-IS SRGB flags */ + } srgb; + struct ls_srlb { /* Segment Routing Local Block */ + uint32_t lower_bound; /* MPLS label lower bound */ + uint32_t range_size; /* MPLS label range size */ + } srlb; + uint8_t algo[LIB_LS_SR_ALGO_COUNT]; /* Segment Routing Algorithms */ + uint8_t msd; /* Maximum Stack Depth */ + + uint16_t srv6_cap_flags; /* draft-ietf-idr-bgpls-srv6-ext, 3.1., flags field */ + struct ls_srv6_msd { /* draft-ietf-idr-bgpls-srv6-ext, 3.2. */ + uint8_t max_seg_left_msd; + uint8_t max_end_pop_msd; + uint8_t max_h_encaps_msd; + uint8_t max_end_d_msd; + } srv6_msd; +}; + +/* Link State flags to indicate which Attribute parameters are valid */ +#define LS_ATTR_UNSET 0x00000000 +#define LS_ATTR_NAME 0x00000001 +#define LS_ATTR_METRIC 0x00000002 +#define LS_ATTR_TE_METRIC 0x00000004 +#define LS_ATTR_ADM_GRP 0x00000008 +#define LS_ATTR_LOCAL_ADDR 0x00000010 +#define LS_ATTR_NEIGH_ADDR 0x00000020 +#define LS_ATTR_LOCAL_ADDR6 0x00000040 +#define LS_ATTR_NEIGH_ADDR6 0x00000080 +#define LS_ATTR_LOCAL_ID 0x00000100 +#define LS_ATTR_NEIGH_ID 0x00000200 +#define LS_ATTR_MAX_BW 0x00000400 +#define LS_ATTR_MAX_RSV_BW 0x00000800 +#define LS_ATTR_UNRSV_BW 0x00001000 +#define LS_ATTR_REMOTE_AS 0x00002000 +#define LS_ATTR_REMOTE_ADDR 0x00004000 +#define LS_ATTR_REMOTE_ADDR6 0x00008000 +#define LS_ATTR_DELAY 0x00010000 +#define LS_ATTR_MIN_MAX_DELAY 0x00020000 +#define LS_ATTR_JITTER 0x00040000 +#define LS_ATTR_PACKET_LOSS 0x00080000 +#define LS_ATTR_AVA_BW 0x00100000 +#define LS_ATTR_RSV_BW 0x00200000 +#define LS_ATTR_USE_BW 0x00400000 +#define LS_ATTR_ADJ_SID 0x01000000 +#define LS_ATTR_BCK_ADJ_SID 0x02000000 +#define LS_ATTR_ADJ_SID6 0x04000000 +#define LS_ATTR_BCK_ADJ_SID6 0x08000000 +#define LS_ATTR_SRLG 0x10000000 +#define LS_ATTR_EXT_ADM_GRP 0x20000000 +#define LS_ATTR_ADJ_SRV6SID 0x40000000 +#define LS_ATTR_BCK_ADJ_SRV6SID 0x80000000 + +/* Link State Attributes */ +struct ls_attributes { + uint32_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + char name[MAX_NAME_LENGTH]; /* Name of the Edge. Could be null */ + uint32_t metric; /* IGP standard metric */ + struct ls_standard { /* Standard TE metrics */ + uint32_t te_metric; /* Traffic Engineering metric */ + uint32_t admin_group; /* Administrative Group */ + struct in_addr local; /* Local IPv4 address */ + struct in_addr remote; /* Remote IPv4 address */ + struct in6_addr local6; /* Local IPv6 address */ + struct in6_addr remote6; /* Remote IPv6 address */ + uint32_t local_id; /* Local Identifier */ + uint32_t remote_id; /* Remote Identifier */ + float max_bw; /* Maximum Link Bandwidth */ + float max_rsv_bw; /* Maximum Reservable BW */ + float unrsv_bw[8]; /* Unreserved BW per CT (8) */ + uint32_t remote_as; /* Remote AS number */ + struct in_addr remote_addr; /* Remote IPv4 address */ + struct in6_addr remote_addr6; /* Remote IPv6 address */ + } standard; + struct ls_extended { /* Extended TE Metrics */ + uint32_t delay; /* Unidirectional average delay */ + uint32_t min_delay; /* Unidirectional minimum delay */ + uint32_t max_delay; /* Unidirectional maximum delay */ + uint32_t jitter; /* Unidirectional delay variation */ + uint32_t pkt_loss; /* Unidirectional packet loss */ + float ava_bw; /* Available Bandwidth */ + float rsv_bw; /* Reserved Bandwidth */ + float used_bw; /* Utilized Bandwidth */ + } extended; + struct admin_group ext_admin_group; /* Extended Admin. Group */ +#define ADJ_PRI_IPV4 0 +#define ADJ_BCK_IPV4 1 +#define ADJ_PRI_IPV6 2 +#define ADJ_BCK_IPV6 3 +#define LS_ADJ_MAX 4 + struct ls_adjacency { /* (LAN)-Adjacency SID for OSPF */ + uint32_t sid; /* SID as MPLS label or index */ + uint8_t flags; /* Flags */ + uint8_t weight; /* Administrative weight */ + union { + struct in_addr addr; /* Neighbor @IP for OSPF */ + uint8_t sysid[ISO_SYS_ID_LEN]; /* or Sys-ID for ISIS */ + } neighbor; + } adj_sid[4]; /* IPv4/IPv6 & Primary/Backup (LAN)-Adj. SID */ +#define ADJ_SRV6_PRI_IPV6 0 +#define ADJ_SRV6_BCK_IPV6 1 +#define ADJ_SRV6_MAX 2 + struct ls_srv6_adjacency { /* Adjacency SID for IS-IS */ + struct in6_addr sid; /* SID as IPv6 address */ + uint8_t flags; /* Flags */ + uint8_t weight; /* Administrative weight */ + uint16_t endpoint_behavior; /* Endpoint Behavior */ + union { + uint8_t sysid[ISO_SYS_ID_LEN]; /* Sys-ID for ISIS */ + } neighbor; + } adj_srv6_sid[2]; + uint32_t *srlgs; /* List of Shared Risk Link Group */ + uint8_t srlg_len; /* number of SRLG in the list */ +}; + +/* Link State flags to indicate which Prefix parameters are valid */ +#define LS_PREF_UNSET 0x00 +#define LS_PREF_IGP_FLAG 0x01 +#define LS_PREF_ROUTE_TAG 0x02 +#define LS_PREF_EXTENDED_TAG 0x04 +#define LS_PREF_METRIC 0x08 +#define LS_PREF_SR 0x10 + +/* Link State Prefix */ +struct ls_prefix { + uint8_t flags; /* Flag for parameters validity */ + struct ls_node_id adv; /* Adv. Router of this Link State */ + struct prefix pref; /* IPv4 or IPv6 prefix */ + uint8_t igp_flag; /* IGP Flags associated to the prefix */ + uint32_t route_tag; /* IGP Route Tag */ + uint64_t extended_tag; /* IGP Extended Route Tag */ + uint32_t metric; /* Route metric for this prefix */ + struct ls_sid { + uint32_t sid; /* Segment Routing ID */ + uint8_t sid_flag; /* Segment Routing Flags */ + uint8_t algo; /* Algorithm for Segment Routing */ + } sr; +}; + +/** + * Create a new Link State Node. Structure is dynamically allocated. + * + * @param adv Mandatory Link State Node ID i.e. advertise router information + * @param rid Router ID as IPv4 address + * @param rid6 Router ID as IPv6 address + * + * @return New Link State Node + */ +extern struct ls_node *ls_node_new(struct ls_node_id adv, struct in_addr rid, + struct in6_addr rid6); + +/** + * Remove Link State Node. Data structure is freed. + * + * @param node Pointer to a valid Link State Node structure + */ +extern void ls_node_del(struct ls_node *node); + +/** + * Check if two Link State Nodes are equal. Note that this routine has the same + * return value sense as '==' (which is different from a comparison). + * + * @param n1 First Link State Node to be compare + * @param n2 Second Link State Node to be compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_node_same(struct ls_node *n1, struct ls_node *n2); + +/** + * Create a new Link State Attributes. Structure is dynamically allocated. + * At least one of parameters MUST be valid and not equal to 0. + * + * @param adv Mandatory Link State Node ID i.e. advertise router ID + * @param local Local IPv4 address + * @param local6 Local Ipv6 address + * @param local_id Local Identifier + * + * @return New Link State Attributes + */ +extern struct ls_attributes *ls_attributes_new(struct ls_node_id adv, + struct in_addr local, + struct in6_addr local6, + uint32_t local_id); + +/** + * Remove SRLGs from Link State Attributes if defined. + * + * @param attr Pointer to a valid Link State Attribute structure + */ +extern void ls_attributes_srlg_del(struct ls_attributes *attr); + +/** + * Remove Link State Attributes. Data structure is freed. + * + * @param attr Pointer to a valid Link State Attribute structure + */ +extern void ls_attributes_del(struct ls_attributes *attr); + +/** + * Check if two Link State Attributes are equal. Note that this routine has the + * same return value sense as '==' (which is different from a comparison). + * + * @param a1 First Link State Attributes to be compare + * @param a2 Second Link State Attributes to be compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_attributes_same(struct ls_attributes *a1, + struct ls_attributes *a2); + +/** + * Create a new Link State Prefix. Structure is dynamically allocated. + * + * @param adv Mandatory Link State Node ID i.e. advertise router ID + * @param p Mandatory Prefix + * + * @return New Link State Prefix + */ +extern struct ls_prefix *ls_prefix_new(struct ls_node_id adv, struct prefix *p); + +/** + * Remove Link State Prefix. Data Structure is freed. + * + * @param pref Pointer to a valid Link State Attribute Prefix. + */ +extern void ls_prefix_del(struct ls_prefix *pref); + +/** + * Check if two Link State Prefix are equal. Note that this routine has the + * same return value sense as '==' (which is different from a comparison). + * + * @param p1 First Link State Prefix to be compare + * @param p2 Second Link State Prefix to be compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_prefix_same(struct ls_prefix *p1, struct ls_prefix *p2); + +/** + * In addition a Graph model is defined as an overlay on top of link state + * database in order to ease Path Computation algorithm implementation. + * Denoted G(V, E), a graph is composed by a list of Vertices (V) which + * represents the network Node and a list of Edges (E) which represents node + * Link. An additional list of prefixes (P) is also added. + * A prefix (P) is also attached to the Vertex (V) which advertise it. + * + * Vertex (V) contains the list of outgoing Edges (E) that connect this Vertex + * with its direct neighbors and the list of incoming Edges (E) that connect + * the direct neighbors to this Vertex. Indeed, the Edge (E) is unidirectional, + * thus, it is necessary to add 2 Edges to model a bidirectional relation + * between 2 Vertices. + * + * Edge (E) contains the source and destination Vertex that this Edge + * is connecting. + * + * A unique Key is used to identify both Vertices and Edges within the Graph. + * An easy way to build this key is to used the IP address: i.e. loopback + * address for Vertices and link IP address for Edges. + * + * -------------- --------------------------- -------------- + * | Connected |---->| Connected Edge Va to Vb |--->| Connected | + * --->| Vertex | --------------------------- | Vertex |----> + * | | | | + * | - Key (Va) | | - Key (Vb) | + * <---| - Vertex | --------------------------- | - Vertex |<---- + * | |<----| Connected Edge Vb to Va |<---| | + * -------------- --------------------------- -------------- + * + */ + +enum ls_status { UNSET = 0, NEW, UPDATE, DELETE, SYNC, ORPHAN }; +enum ls_type { GENERIC = 0, VERTEX, EDGE, SUBNET }; + +/* Link State Vertex structure */ +PREDECL_RBTREE_UNIQ(vertices); +struct ls_vertex { + enum ls_type type; /* Link State Type */ + enum ls_status status; /* Status of the Vertex in the TED */ + struct vertices_item entry; /* Entry in RB Tree */ + uint64_t key; /* Unique Key identifier */ + struct ls_node *node; /* Link State Node */ + struct list *incoming_edges; /* List of incoming Link State links */ + struct list *outgoing_edges; /* List of outgoing Link State links */ + struct list *prefixes; /* List of advertised prefix */ +}; + +/* Link State Edge Key structure */ +struct ls_edge_key { + uint8_t family; + union { + struct in_addr addr; + struct in6_addr addr6; + uint64_t link_id; + } k; +}; + +/* Link State Edge structure */ +PREDECL_RBTREE_UNIQ(edges); +struct ls_edge { + enum ls_type type; /* Link State Type */ + enum ls_status status; /* Status of the Edge in the TED */ + struct edges_item entry; /* Entry in RB tree */ + struct ls_edge_key key; /* Unique Key identifier */ + struct ls_attributes *attributes; /* Link State attributes */ + struct ls_vertex *source; /* Pointer to the source Vertex */ + struct ls_vertex *destination; /* Pointer to the destination Vertex */ +}; + +/* Link State Subnet structure */ +PREDECL_RBTREE_UNIQ(subnets); +struct ls_subnet { + enum ls_type type; /* Link State Type */ + enum ls_status status; /* Status of the Subnet in the TED */ + struct subnets_item entry; /* Entry in RB tree */ + struct prefix key; /* Unique Key identifier */ + struct ls_prefix *ls_pref; /* Link State Prefix */ + struct ls_vertex *vertex; /* Back pointer to the Vertex owner */ +}; + +/* Declaration of Vertices, Edges and Prefixes RB Trees */ +macro_inline int vertex_cmp(const struct ls_vertex *node1, + const struct ls_vertex *node2) +{ + return numcmp(node1->key, node2->key); +} +DECLARE_RBTREE_UNIQ(vertices, struct ls_vertex, entry, vertex_cmp); + +macro_inline int edge_cmp(const struct ls_edge *edge1, + const struct ls_edge *edge2) +{ + if (edge1->key.family != edge2->key.family) + return numcmp(edge1->key.family, edge2->key.family); + + switch (edge1->key.family) { + case AF_INET: + return memcmp(&edge1->key.k.addr, &edge2->key.k.addr, 4); + case AF_INET6: + return memcmp(&edge1->key.k.addr6, &edge2->key.k.addr6, 16); + case AF_LOCAL: + return numcmp(edge1->key.k.link_id, edge2->key.k.link_id); + default: + return 0; + } +} +DECLARE_RBTREE_UNIQ(edges, struct ls_edge, entry, edge_cmp); + +/* + * Prefix comparison are done to the host part so, 10.0.0.1/24 + * and 10.0.0.2/24 are considered different + */ +macro_inline int subnet_cmp(const struct ls_subnet *a, + const struct ls_subnet *b) +{ + if (a->key.family != b->key.family) + return numcmp(a->key.family, b->key.family); + + if (a->key.prefixlen != b->key.prefixlen) + return numcmp(a->key.prefixlen, b->key.prefixlen); + + if (a->key.family == AF_INET) + return memcmp(&a->key.u.val, &b->key.u.val, 4); + + return memcmp(&a->key.u.val, &b->key.u.val, 16); +} +DECLARE_RBTREE_UNIQ(subnets, struct ls_subnet, entry, subnet_cmp); + +/* Link State TED Structure */ +struct ls_ted { + uint32_t key; /* Unique identifier */ + char name[MAX_NAME_LENGTH]; /* Name of this graph. Could be null */ + uint32_t as_number; /* AS number of the modeled network */ + struct ls_vertex *self; /* Vertex of the FRR instance */ + struct vertices_head vertices; /* List of Vertices */ + struct edges_head edges; /* List of Edges */ + struct subnets_head subnets; /* List of Subnets */ +}; + +/* Generic Link State Element */ +struct ls_element { + enum ls_type type; /* Link State Element Type */ + enum ls_status status; /* Link State Status in the TED */ + void *data; /* Link State payload */ +}; + +/** + * Add new vertex to the Link State DB. Vertex is created from the Link State + * Node. Vertex data structure is dynamically allocated. + * + * @param ted Traffic Engineering Database structure + * @param node Link State Node + * + * @return New Vertex or NULL in case of error + */ +extern struct ls_vertex *ls_vertex_add(struct ls_ted *ted, + struct ls_node *node); + +/** + * Delete Link State Vertex. This function clean internal Vertex lists (incoming + * and outgoing Link State Edge and Link State Subnet). Vertex Data structure + * is freed but not the Link State Node. Link State DB is not modified if Vertex + * is NULL or not found in the Data Base. Note that referenced to Link State + * Edges & SubNets are not removed as they could be connected to other Vertices. + * + * @param ted Traffic Engineering Database structure + * @param vertex Link State Vertex to be removed + */ +extern void ls_vertex_del(struct ls_ted *ted, struct ls_vertex *vertex); + +/** + * Delete Link State Vertex as ls_vertex_del() but also removed associated + * Link State Node. + * + * @param ted Traffic Engineering Database structure + * @param vertex Link State Vertex to be removed + */ +extern void ls_vertex_del_all(struct ls_ted *ted, struct ls_vertex *vertex); + +/** + * Update Vertex with the Link State Node. A new vertex is created if no one + * corresponds to the Link State Node. + * + * @param ted Link State Data Base + * @param node Link State Node to be updated + * + * @return Updated Link State Vertex or Null in case of error + */ +extern struct ls_vertex *ls_vertex_update(struct ls_ted *ted, + struct ls_node *node); + +/** + * Clean Vertex structure by removing all Edges and Subnets marked as ORPHAN + * from this vertex. Link State Update message is sent if zclient is not NULL. + * + * @param ted Link State Data Base + * @param vertex Link State Vertex to be cleaned + * @param zclient Reference to Zebra Client + */ +extern void ls_vertex_clean(struct ls_ted *ted, struct ls_vertex *vertex, + struct zclient *zclient); + +/** + * This function convert the ISIS ISO system ID into a 64 bits unsigned integer + * following the architecture dependent byte order. + * + * @param sysid The ISO system ID + * @return Key as 64 bits unsigned integer + */ +extern uint64_t sysid_to_key(const uint8_t sysid[ISO_SYS_ID_LEN]); + +/** + * Find Vertex in the Link State DB by its unique key. + * + * @param ted Link State Data Base + * @param key Vertex Key different from 0 + * + * @return Vertex if found, NULL otherwise + */ +extern struct ls_vertex *ls_find_vertex_by_key(struct ls_ted *ted, + const uint64_t key); + +/** + * Find Vertex in the Link State DB by its Link State Node. + * + * @param ted Link State Data Base + * @param nid Link State Node ID + * + * @return Vertex if found, NULL otherwise + */ +extern struct ls_vertex *ls_find_vertex_by_id(struct ls_ted *ted, + struct ls_node_id nid); + +/** + * Check if two Vertices are equal. Note that this routine has the same return + * value sense as '==' (which is different from a comparison). + * + * @param v1 First vertex to compare + * @param v2 Second vertex to compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_vertex_same(struct ls_vertex *v1, struct ls_vertex *v2); + +/** + * Add new Edge to the Link State DB. Edge is created from the Link State + * Attributes. Edge data structure is dynamically allocated. + * + * @param ted Link State Data Base + * @param attributes Link State attributes + * + * @return New Edge or NULL in case of error + */ +extern struct ls_edge *ls_edge_add(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Update the Link State Attributes information of an existing Edge. If there is + * no corresponding Edge in the Link State Data Base, a new Edge is created. + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Updated Link State Edge, or NULL in case of error + */ +extern struct ls_edge *ls_edge_update(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Check if two Edges are equal. Note that this routine has the same return + * value sense as '==' (which is different from a comparison). + * + * @param e1 First edge to compare + * @param e2 Second edge to compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_edge_same(struct ls_edge *e1, struct ls_edge *e2); + +/** + * Remove Edge from the Link State DB. Edge data structure is freed but not the + * Link State Attributes data structure. Link State DB is not modified if Edge + * is NULL or not found in the Data Base. + * + * @param ted Link State Data Base + * @param edge Edge to be removed + */ +extern void ls_edge_del(struct ls_ted *ted, struct ls_edge *edge); + +/** + * Remove Edge and associated Link State Attributes from the Link State DB. + * Link State DB is not modified if Edge is NULL or not found. + * + * @param ted Link State Data Base + * @param edge Edge to be removed + */ +extern void ls_edge_del_all(struct ls_ted *ted, struct ls_edge *edge); + +/** + * Find Edge in the Link State Data Base by Edge key. + * + * @param ted Link State Data Base + * @param key Edge key + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge *ls_find_edge_by_key(struct ls_ted *ted, + const struct ls_edge_key key); + +/** + * Find Edge in the Link State Data Base by the source (local IPv4 or IPv6 + * address or local ID) informations of the Link State Attributes + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge * +ls_find_edge_by_source(struct ls_ted *ted, struct ls_attributes *attributes); + +/** + * Find Edge in the Link State Data Base by the destination (remote IPv4 or IPv6 + * address of remote ID) information of the Link State Attributes + * + * @param ted Link State Data Base + * @param attributes Link State Attributes + * + * @return Edge if found, NULL otherwise + */ +extern struct ls_edge * +ls_find_edge_by_destination(struct ls_ted *ted, + struct ls_attributes *attributes); + +/** + * Add new Subnet to the Link State DB. Subnet is created from the Link State + * prefix. Subnet data structure is dynamically allocated. + * + * @param ted Link State Data Base + * @param pref Link State Prefix + * + * @return New Subnet + */ +extern struct ls_subnet *ls_subnet_add(struct ls_ted *ted, + struct ls_prefix *pref); + +/** + * Update the Link State Prefix information of an existing Subnet. If there is + * no corresponding Subnet in the Link State Data Base, a new Subnet is created. + * + * @param ted Link State Data Base + * @param pref Link State Prefix + * + * @return Updated Link State Subnet, or NULL in case of error + */ +extern struct ls_subnet *ls_subnet_update(struct ls_ted *ted, + struct ls_prefix *pref); + +/** + * Check if two Subnets are equal. Note that this routine has the same return + * value sense as '==' (which is different from a comparison). + * + * @param s1 First subnet to compare + * @param s2 Second subnet to compare + * + * @return 1 if equal, 0 otherwise + */ +extern int ls_subnet_same(struct ls_subnet *s1, struct ls_subnet *s2); + +/** + * Remove Subnet from the Link State DB. Subnet data structure is freed but + * not the Link State prefix data structure. Link State DB is not modified + * if Subnet is NULL or not found in the Data Base. + * + * @param ted Link State Data Base + * @param subnet Subnet to be removed + */ +extern void ls_subnet_del(struct ls_ted *ted, struct ls_subnet *subnet); + +/** + * Remove Subnet and the associated Link State Prefix from the Link State DB. + * Link State DB is not modified if Subnet is NULL or not found. + * + * @param ted Link State Data Base + * @param subnet Subnet to be removed + */ +extern void ls_subnet_del_all(struct ls_ted *ted, struct ls_subnet *subnet); + +/** + * Find Subnet in the Link State Data Base by prefix. + * + * @param ted Link State Data Base + * @param prefix Link State Prefix + * + * @return Subnet if found, NULL otherwise + */ +extern struct ls_subnet *ls_find_subnet(struct ls_ted *ted, + const struct prefix *prefix); + +/** + * Create a new Link State Data Base. + * + * @param key Unique key of the data base. Must be different from 0 + * @param name Name of the data base (may be NULL) + * @param asn AS Number for this data base. 0 if unknown + * + * @return New Link State Database or NULL in case of error + */ +extern struct ls_ted *ls_ted_new(const uint32_t key, const char *name, + uint32_t asn); + +/** + * Delete existing Link State Data Base. Vertices, Edges, and Subnets are not + * removed. + * + * @param ted Link State Data Base + */ +extern void ls_ted_del(struct ls_ted *ted); + +/** + * Delete all Link State Vertices, Edges and SubNets and the Link State DB. + * + * @param ted Link State Data Base + */ +extern void ls_ted_del_all(struct ls_ted **ted); + +/** + * Clean Link State Data Base by removing all Vertices, Edges and SubNets marked + * as ORPHAN. + * + * @param ted Link State Data Base + */ +extern void ls_ted_clean(struct ls_ted *ted); + +/** + * Connect Source and Destination Vertices by given Edge. Only non NULL source + * and destination vertices are connected. + * + * @param src Link State Source Vertex + * @param dst Link State Destination Vertex + * @param edge Link State Edge. Must not be NULL + */ +extern void ls_connect_vertices(struct ls_vertex *src, struct ls_vertex *dst, + struct ls_edge *edge); + +/** + * Connect Link State Edge to the Link State Vertex which could be a Source or + * a Destination Vertex. + * + * @param vertex Link State Vertex to be connected. Must not be NULL + * @param edge Link State Edge connection. Must not be NULL + * @param source True for a Source, false for a Destination Vertex + */ +extern void ls_connect(struct ls_vertex *vertex, struct ls_edge *edge, + bool source); + +/** + * Disconnect Link State Edge from the Link State Vertex which could be a + * Source or a Destination Vertex. + * + * @param vertex Link State Vertex to be connected. Must not be NULL + * @param edge Link State Edge connection. Must not be NULL + * @param source True for a Source, false for a Destination Vertex + */ +extern void ls_disconnect(struct ls_vertex *vertex, struct ls_edge *edge, + bool source); + +/** + * Disconnect Link State Edge from both Source and Destination Vertex. + * + * @param edge Link State Edge to be disconnected + */ +extern void ls_disconnect_edge(struct ls_edge *edge); + + +/** + * The Link State Message is defined to convey Link State parameters from + * the routing protocol (OSPF or IS-IS) to other daemons e.g. BGP. + * + * The structure is composed of: + * - Event of the message: + * - Sync: Send the whole LS DB following a request + * - Add: Send the a new Link State element + * - Update: Send an update of an existing Link State element + * - Delete: Indicate that the given Link State element is removed + * - Type of Link State element: Node, Attribute or Prefix + * - Remote node id when known + * - Data: Node, Attributes or Prefix + * + * A Link State Message can carry only one Link State Element (Node, Attributes + * of Prefix) at once, and only one Link State Message is sent through ZAPI + * Opaque Link State type at once. + */ + +/* ZAPI Opaque Link State Message Event */ +#define LS_MSG_EVENT_UNDEF 0 +#define LS_MSG_EVENT_SYNC 1 +#define LS_MSG_EVENT_ADD 2 +#define LS_MSG_EVENT_UPDATE 3 +#define LS_MSG_EVENT_DELETE 4 + +/* ZAPI Opaque Link State Message sub-Type */ +#define LS_MSG_TYPE_NODE 1 +#define LS_MSG_TYPE_ATTRIBUTES 2 +#define LS_MSG_TYPE_PREFIX 3 + +/* Link State Message */ +struct ls_message { + uint8_t event; /* Message Event: Sync, Add, Update, Delete */ + uint8_t type; /* Message Data Type: Node, Attribute, Prefix */ + struct ls_node_id remote_id; /* Remote Link State Node ID */ + union { + struct ls_node *node; /* Link State Node */ + struct ls_attributes *attr; /* Link State Attributes */ + struct ls_prefix *prefix; /* Link State Prefix */ + } data; +}; + +/** + * Register Link State daemon as a server or client for Zebra OPAQUE API. + * + * @param zclient Zebra client structure + * @param server Register daemon as a server (true) or as a client (false) + * + * @return 0 if success, -1 otherwise + */ +extern int ls_register(struct zclient *zclient, bool server); + +/** + * Unregister Link State daemon as a server or client for Zebra OPAQUE API. + * + * @param zclient Zebra client structure + * @param server Unregister daemon as a server (true) or as a client (false) + * + * @return 0 if success, -1 otherwise + */ +extern int ls_unregister(struct zclient *zclient, bool server); + +/** + * Send Link State SYNC message to request the complete Link State Database. + * + * @param zclient Zebra client + * + * @return 0 if success, -1 otherwise + */ +extern int ls_request_sync(struct zclient *zclient); + +/** + * Parse Link State Message from stream. Used this function once receiving a + * new ZAPI Opaque message of type Link State. + * + * @param s Stream buffer. Must not be NULL. + * + * @return New Link State Message or NULL in case of error + */ +extern struct ls_message *ls_parse_msg(struct stream *s); + +/** + * Delete existing message. Data structure is freed. + * + * @param msg Link state message to be deleted + */ +extern void ls_delete_msg(struct ls_message *msg); + +/** + * Send Link State Message as new ZAPI Opaque message of type Link State. + * If destination is not NULL, message is sent as Unicast otherwise it is + * broadcast to all registered daemon. + * + * @param zclient Zebra Client + * @param msg Link State Message to be sent + * @param dst Destination daemon for unicast message, + * NULL for broadcast message + * + * @return 0 on success, -1 otherwise + */ +extern int ls_send_msg(struct zclient *zclient, struct ls_message *msg, + struct zapi_opaque_reg_info *dst); + +/** + * Create a new Link State Message from a Link State Vertex. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param vertex Link State Vertex. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_vertex2msg(struct ls_message *msg, + struct ls_vertex *vertex); + +/** + * Create a new Link State Message from a Link State Edge. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param edge Link State Edge. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_edge2msg(struct ls_message *msg, + struct ls_edge *edge); + +/** + * Create a new Link State Message from a Link State Subnet. If Link State + * Message is NULL, a new data structure is dynamically allocated. + * + * @param msg Link State Message to be filled or NULL + * @param subnet Link State Subnet. Must not be NULL + * + * @return New Link State Message msg parameter is NULL or pointer + * to the provided Link State Message + */ +extern struct ls_message *ls_subnet2msg(struct ls_message *msg, + struct ls_subnet *subnet); + +/** + * Convert Link State Message into Vertex and update TED accordingly to + * the message event: SYNC, ADD, UPDATE or DELETE. + * + * @param ted Link State Database + * @param msg Link State Message + * @param delete True to delete the Link State Vertex from the Database, + * False otherwise. If true, return value is NULL in case + * of deletion. + * + * @return Vertex if success, NULL otherwise or if Vertex is removed + */ +extern struct ls_vertex *ls_msg2vertex(struct ls_ted *ted, + struct ls_message *msg, bool delete); + +/** + * Convert Link State Message into Edge and update TED accordingly to + * the message event: SYNC, ADD, UPDATE or DELETE. + * + * @param ted Link State Database + * @param msg Link State Message + * @param delete True to delete the Link State Edge from the Database, + * False otherwise. If true, return value is NULL in case + * of deletion. + * + * @return Edge if success, NULL otherwise or if Edge is removed + */ +extern struct ls_edge *ls_msg2edge(struct ls_ted *ted, struct ls_message *msg, + bool delete); + +/** + * Convert Link State Message into Subnet and update TED accordingly to + * the message event: SYNC, ADD, UPDATE or DELETE. + * + * @param ted Link State Database + * @param msg Link State Message + * @param delete True to delete the Link State Subnet from the Database, + * False otherwise. If true, return value is NULL in case + * of deletion. + * + * @return Subnet if success, NULL otherwise or if Subnet is removed + */ +extern struct ls_subnet *ls_msg2subnet(struct ls_ted *ted, + struct ls_message *msg, bool delete); + +/** + * Convert Link State Message into Link State element (Vertex, Edge or Subnet) + * and update TED accordingly to the message event: SYNC, ADD, UPDATE or DELETE. + * + * @param ted Link State Database + * @param msg Link State Message + * @param delete True to delete the Link State Element from the Database, + * False otherwise. If true, return value is NULL in case + * of deletion. + * + * @return Element if success, NULL otherwise or if Element is removed + */ +extern struct ls_element *ls_msg2ted(struct ls_ted *ted, struct ls_message *msg, + bool delete); + +/** + * Convert stream buffer into Link State element (Vertex, Edge or Subnet) and + * update TED accordingly to the message event: SYNC, ADD, UPDATE or DELETE. + * + * @param ted Link State Database + * @param s Stream buffer + * @param delete True to delete the Link State Element from the Database, + * False otherwise. If true, return value is NULL in case + * of deletion. + * + * @return Element if success, NULL otherwise or if Element is removed + */ +extern struct ls_element *ls_stream2ted(struct ls_ted *ted, struct stream *s, + bool delete); + +/** + * Send all the content of the Link State Data Base to the given destination. + * Link State content is sent is this order: Vertices, Edges, Subnet. + * This function must be used when a daemon request a Link State Data Base + * Synchronization. + * + * @param ted Link State Data Base. Must not be NULL + * @param zclient Zebra Client. Must not be NULL + * @param dst Destination FRR daemon. Must not be NULL + * + * @return 0 on success, -1 otherwise + */ +extern int ls_sync_ted(struct ls_ted *ted, struct zclient *zclient, + struct zapi_opaque_reg_info *dst); + +struct json_object; +struct vty; +/** + * Show Link State Vertex information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param vertex Link State Vertex to show. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_vertex(struct ls_vertex *vertex, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show all Link State Vertices information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param ted Link State Data Base. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_vertices(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show Link State Edge information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param edge Link State Edge to show. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_edge(struct ls_edge *edge, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show all Link State Edges information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param ted Link State Data Base. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_edges(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show Link State Subnets information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param subnet Link State Subnet to show. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_subnet(struct ls_subnet *subnet, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show all Link State Subnet information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param ted Link State Data Base. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_subnets(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Show Link State Data Base information. If both vty and json are specified, + * Json format output supersedes standard vty output. + * + * @param ted Link State Data Base to show. Must not be NULL + * @param vty Pointer to vty output, could be NULL + * @param json Pointer to json output, could be NULL + * @param verbose Set to true for more detail + */ +extern void ls_show_ted(struct ls_ted *ted, struct vty *vty, + struct json_object *json, bool verbose); + +/** + * Dump all Link State Data Base elements for debugging purposes + * + * @param ted Link State Data Base. Must not be NULL + * + */ +extern void ls_dump_ted(struct ls_ted *ted); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_LINK_STATE_H_ */ diff --git a/lib/linklist.c b/lib/linklist.c new file mode 100644 index 0000000..28dd777 --- /dev/null +++ b/lib/linklist.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Generic linked list routine. + * Copyright (C) 1997, 2000 Kunihiro Ishiguro + */ + +#include +#include + +#include "linklist.h" +#include "memory.h" +#include "libfrr_trace.h" + +DEFINE_MTYPE_STATIC(LIB, LINK_LIST, "Link List"); +DEFINE_MTYPE_STATIC(LIB, LINK_NODE, "Link Node"); + +/* these *do not* cleanup list nodes and referenced data, as the functions + * do - these macros simply {de,at}tach a listnode from/to a list. + */ + +/* List node attach macro. */ +#define LISTNODE_ATTACH(L, N) \ + do { \ + (N)->prev = (L)->tail; \ + (N)->next = NULL; \ + if ((L)->head == NULL) \ + (L)->head = (N); \ + else \ + (L)->tail->next = (N); \ + (L)->tail = (N); \ + (L)->count++; \ + } while (0) + +/* List node detach macro. */ +#define LISTNODE_DETACH(L, N) \ + do { \ + if ((N)->prev) \ + (N)->prev->next = (N)->next; \ + else \ + (L)->head = (N)->next; \ + if ((N)->next) \ + (N)->next->prev = (N)->prev; \ + else \ + (L)->tail = (N)->prev; \ + (L)->count--; \ + } while (0) + +struct list *list_new(void) +{ + return XCALLOC(MTYPE_LINK_LIST, sizeof(struct list)); +} + +/* Free list. */ +static void list_free_internal(struct list *l) +{ + XFREE(MTYPE_LINK_LIST, l); +} + + +/* Allocate new listnode. Internal use only. */ +static struct listnode *listnode_new(struct list *list, void *val) +{ + struct listnode *node; + + /* if listnode memory is managed by the app then the val + * passed in is the listnode + */ + if (list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP) { + node = val; + node->prev = node->next = NULL; + } else { + node = XCALLOC(MTYPE_LINK_NODE, sizeof(struct listnode)); + node->data = val; + } + return node; +} + +/* Free listnode. */ +static void listnode_free(struct list *list, struct listnode *node) +{ + if (!(list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP)) + XFREE(MTYPE_LINK_NODE, node); +} + +struct listnode *listnode_add(struct list *list, void *val) +{ + frrtrace(2, frr_libfrr, list_add, list, val); + + struct listnode *node; + + assert(val != NULL); + + node = listnode_new(list, val); + + node->prev = list->tail; + + if (list->head == NULL) + list->head = node; + else + list->tail->next = node; + list->tail = node; + + list->count++; + + return node; +} + +void listnode_add_head(struct list *list, void *val) +{ + struct listnode *node; + + assert(val != NULL); + + node = listnode_new(list, val); + + node->next = list->head; + + if (list->head == NULL) { + list->head = node; + list->tail = node; + } else + list->head->prev = node; + list->head = node; + + list->count++; +} + +bool listnode_add_sort_nodup(struct list *list, void *val) +{ + struct listnode *n; + struct listnode *new; + int ret; + void *data; + + assert(val != NULL); + + if (list->flags & LINKLIST_FLAG_NODE_MEM_BY_APP) { + n = val; + data = n->data; + } else { + data = val; + } + + if (list->cmp) { + for (n = list->head; n; n = n->next) { + ret = (*list->cmp)(data, n->data); + if (ret < 0) { + new = listnode_new(list, val); + + new->next = n; + new->prev = n->prev; + + if (n->prev) + n->prev->next = new; + else + list->head = new; + n->prev = new; + list->count++; + return true; + } + /* found duplicate return false */ + if (ret == 0) + return false; + } + } + + new = listnode_new(list, val); + + LISTNODE_ATTACH(list, new); + + return true; +} + +struct list *list_dup(struct list *list) +{ + struct list *dup; + struct listnode *node; + void *data; + + assert(list); + + dup = list_new(); + dup->cmp = list->cmp; + dup->del = list->del; + for (ALL_LIST_ELEMENTS_RO(list, node, data)) + listnode_add(dup, data); + + return dup; +} + +void listnode_add_sort(struct list *list, void *val) +{ + struct listnode *n; + struct listnode *new; + + assert(val != NULL); + + new = listnode_new(list, val); + val = new->data; + + if (list->cmp) { + for (n = list->head; n; n = n->next) { + if ((*list->cmp)(val, n->data) < 0) { + new->next = n; + new->prev = n->prev; + + if (n->prev) + n->prev->next = new; + else + list->head = new; + n->prev = new; + list->count++; + return; + } + } + } + + new->prev = list->tail; + + if (list->tail) + list->tail->next = new; + else + list->head = new; + + list->tail = new; + list->count++; +} + +struct listnode *listnode_add_after(struct list *list, struct listnode *pp, + void *val) +{ + struct listnode *nn; + + assert(val != NULL); + + nn = listnode_new(list, val); + + if (pp == NULL) { + if (list->head) + list->head->prev = nn; + else + list->tail = nn; + + nn->next = list->head; + nn->prev = pp; + + list->head = nn; + } else { + if (pp->next) + pp->next->prev = nn; + else + list->tail = nn; + + nn->next = pp->next; + nn->prev = pp; + + pp->next = nn; + } + list->count++; + return nn; +} + +struct listnode *listnode_add_before(struct list *list, struct listnode *pp, + void *val) +{ + struct listnode *nn; + + assert(val != NULL); + + nn = listnode_new(list, val); + + if (pp == NULL) { + if (list->tail) + list->tail->next = nn; + else + list->head = nn; + + nn->prev = list->tail; + nn->next = pp; + + list->tail = nn; + } else { + if (pp->prev) + pp->prev->next = nn; + else + list->head = nn; + + nn->prev = pp->prev; + nn->next = pp; + + pp->prev = nn; + } + list->count++; + return nn; +} + +void listnode_move_to_tail(struct list *l, struct listnode *n) +{ + LISTNODE_DETACH(l, n); + LISTNODE_ATTACH(l, n); +} + +void listnode_delete(struct list *list, const void *val) +{ + frrtrace(2, frr_libfrr, list_remove, list, val); + + struct listnode *node = listnode_lookup(list, val); + + if (node) + list_delete_node(list, node); +} + +void *listnode_head(struct list *list) +{ + struct listnode *node; + + assert(list); + node = list->head; + + if (node) + return node->data; + return NULL; +} + +void list_delete_all_node(struct list *list) +{ + struct listnode *node; + struct listnode *next; + + assert(list); + for (node = list->head; node; node = next) { + next = node->next; + if (*list->del) + (*list->del)(node->data); + listnode_free(list, node); + } + list->head = list->tail = NULL; + list->count = 0; +} + +void list_delete(struct list **list) +{ + assert(*list); + list_delete_all_node(*list); + list_free_internal(*list); + *list = NULL; +} + +struct listnode *listnode_lookup(struct list *list, const void *data) +{ + struct listnode *node; + + assert(list); + for (node = listhead(list); node; node = listnextnode(node)) + if (data == listgetdata(node)) + return node; + return NULL; +} + +struct listnode *listnode_lookup_nocheck(struct list *list, void *data) +{ + if (!list) + return NULL; + return listnode_lookup(list, data); +} + +void list_delete_node(struct list *list, struct listnode *node) +{ + frrtrace(2, frr_libfrr, list_delete_node, list, node); + + if (node->prev) + node->prev->next = node->next; + else + list->head = node->next; + if (node->next) + node->next->prev = node->prev; + else + list->tail = node->prev; + list->count--; + listnode_free(list, node); +} + +void list_sort(struct list *list, int (*cmp)(const void **, const void **)) +{ + frrtrace(1, frr_libfrr, list_sort, list); + + struct listnode *ln, *nn; + int i = -1; + void *data; + size_t n = list->count; + void **items; + int (*realcmp)(const void *, const void *) = + (int (*)(const void *, const void *))cmp; + + if (!n) + return; + + items = XCALLOC(MTYPE_TMP, (sizeof(void *)) * n); + + for (ALL_LIST_ELEMENTS(list, ln, nn, data)) { + items[++i] = data; + list_delete_node(list, ln); + } + + qsort(items, n, sizeof(void *), realcmp); + + for (unsigned int j = 0; j < n; ++j) + listnode_add(list, items[j]); + + XFREE(MTYPE_TMP, items); +} + +struct listnode *listnode_add_force(struct list **list, void *val) +{ + if (*list == NULL) + *list = list_new(); + return listnode_add(*list, val); +} + +void **list_to_array(struct list *list, void **arr, size_t arrlen) +{ + struct listnode *ln; + void *vp; + size_t idx = 0; + + for (ALL_LIST_ELEMENTS_RO(list, ln, vp)) { + arr[idx++] = vp; + if (idx == arrlen) + break; + } + + return arr; +} diff --git a/lib/linklist.h b/lib/linklist.h new file mode 100644 index 0000000..fd953d0 --- /dev/null +++ b/lib/linklist.h @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Generic linked list + * Copyright (C) 1997, 2000 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_LINKLIST_H +#define _ZEBRA_LINKLIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* listnodes must always contain data to be valid. Adding an empty node + * to a list is invalid + */ +struct listnode { + struct listnode *next; + struct listnode *prev; + + /* private member, use getdata() to retrieve, do not access directly */ + void *data; +}; + +struct list { + struct listnode *head; + struct listnode *tail; + + /* invariant: count is the number of listnodes in the list */ + unsigned int count; + + uint8_t flags; +/* Indicates that listnode memory is managed by the application and + * doesn't need to be freed by this library via listnode_delete etc. + */ +#define LINKLIST_FLAG_NODE_MEM_BY_APP (1 << 0) + + /* + * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2. + * Used as definition of sorted for listnode_add_sort + */ + int (*cmp)(void *val1, void *val2); + + /* callback to free user-owned data when listnode is deleted. supplying + * this callback is very much encouraged! + */ + void (*del)(void *val); +}; + +#define listnextnode(X) ((X) ? ((X)->next) : NULL) +#define listnextnode_unchecked(X) ((X)->next) +#define listhead(X) ((X) ? ((X)->head) : NULL) +#define listhead_unchecked(X) ((X)->head) +#define listtail(X) ((X) ? ((X)->tail) : NULL) +#define listtail_unchecked(X) ((X)->tail) +#define listcount(X) ((X)->count) +#define list_isempty(X) ((X)->head == NULL && (X)->tail == NULL) +/* return X->data only if X and X->data are not NULL */ +#define listgetdata(X) (assert(X), assert((X)->data != NULL), (X)->data) +/* App is going to manage listnode memory */ +#define listset_app_node_mem(X) ((X)->flags |= LINKLIST_FLAG_NODE_MEM_BY_APP) +#define listnode_init(X, val) ((X)->data = (val)) + +/* + * Create a new linked list. + * + * Returns: + * the created linked list + */ +extern struct list *list_new(void); + +/* + * Add a new element to the tail of a list. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * data + * element to add + */ +extern struct listnode *listnode_add(struct list *list, void *data); + +/* + * Add a new element to the beginning of a list. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * data + * If MEM_BY_APP is set this is listnode. Otherwise it is element to add. + */ +extern void listnode_add_head(struct list *list, void *data); + +/* + * Insert a new element into a list with insertion sort. + * + * If list->cmp is set, this function is used to determine the position to + * insert the new element. If it is not set, this function is equivalent to + * listnode_add. + * + * Runtime is O(N). + * + * list + * list to operate on + * + * val + * If MEM_BY_APP is set this is listnode. Otherwise it is element to add. + */ +extern void listnode_add_sort(struct list *list, void *val); + +/* + * Insert a new element into a list after another element. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * pp + * listnode to insert after + * + * data + * If MEM_BY_APP is set this is listnode. Otherwise it is element to add. + * + * Returns: + * pointer to newly created listnode that contains the inserted data + */ +extern struct listnode *listnode_add_after(struct list *list, + struct listnode *pp, void *data); + +/* + * Insert a new element into a list before another element. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * pp + * listnode to insert before + * + * data + * If MEM_BY_APP is set this is listnode. Otherwise it is element to add. + * + * Returns: + * pointer to newly created listnode that contains the inserted data + */ +extern struct listnode *listnode_add_before(struct list *list, + struct listnode *pp, void *data); + +/* + * Move a node to the tail of a list. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * node + * node to move to tail + */ +extern void listnode_move_to_tail(struct list *list, struct listnode *node); + +/* + * Delete an element from a list. + * + * Runtime is O(N). + * + * list + * list to operate on + * + * data + * data to insert into list + */ +extern void listnode_delete(struct list *list, const void *data); + +/* + * Find the listnode corresponding to an element in a list. + * + * list + * list to operate on + * + * data + * data to search for + * + * Returns: + * pointer to listnode storing the given data if found, NULL otherwise + */ +extern struct listnode *listnode_lookup(struct list *list, const void *data); + +/* + * Retrieve the element at the head of a list. + * + * list + * list to operate on + * + * Returns: + * data at head of list, or NULL if list is empty + */ +extern void *listnode_head(struct list *list); + +/* + * Sort a list in place. + * + * The sorting algorithm used is quicksort. Runtimes are equivalent to those of + * quicksort plus N. The sort is not stable. + * + * For portability reasons, the comparison function takes a pointer to pointer + * to void. This pointer should be dereferenced to get the actual data pointer. + * It is always safe to do this. + * + * list + * list to sort + * + * cmp + * comparison function for quicksort. Should return less than, equal to or + * greater than zero if the first argument is less than, equal to or greater + * than the second argument. + */ +extern void list_sort(struct list *list, + int (*cmp)(const void **, const void **)); + +/* + * Convert a list to an array of void pointers. + * + * Starts from the list head and ends either on the last node of the list or + * when the provided array cannot store any more elements. + * + * list + * list to convert + * + * arr + * Pre-allocated array of void * + * + * arrlen + * Number of elements in arr + * + * Returns: + * arr + */ +void **list_to_array(struct list *list, void **arr, size_t arrlen); + +/* + * Delete a list and NULL its pointer. + * + * If non-null, list->del is called with each data element. + * + * plist + * pointer to list pointer; this will be set to NULL after the list has been + * deleted + */ +extern void list_delete(struct list **plist); + +/* + * Delete all nodes from a list without deleting the list itself. + * + * If non-null, list->del is called with each data element. + * + * list + * list to operate on + */ +extern void list_delete_all_node(struct list *list); + +/* + * Delete a node from a list. + * + * list->del is not called with the data associated with the node. + * + * Runtime is O(1). + * + * list + * list to operate on + * + * node + * the node to delete + */ +extern void list_delete_node(struct list *list, struct listnode *node); + +/* + * Insert a new element into a list with insertion sort if there is no + * duplicate element present in the list. This assumes the input list is + * sorted. If unsorted, it will check for duplicate until it finds out + * the position to do insertion sort with the unsorted list. + * + * If list->cmp is set, this function is used to determine the position to + * insert the new element. If it is not set, this function is equivalent to + * listnode_add. duplicate element is determined by cmp function returning 0. + * + * Runtime is O(N). + * + * list + * list to operate on + * + * val + * If MEM_BY_APP is set this is listnode. Otherwise it is element to add. + */ + +extern bool listnode_add_sort_nodup(struct list *list, void *val); + +/* + * Duplicate the specified list, creating a shallow copy of each of its + * elements. + * + * list + * list to duplicate + * + * Returns: + * the duplicated list + */ +extern struct list *list_dup(struct list *list); + +/* List iteration macro. + * Usage: for (ALL_LIST_ELEMENTS (...) { ... } + * It is safe to delete the listnode using this macro. + */ +#define ALL_LIST_ELEMENTS(list, node, nextnode, data) \ + (node) = listhead(list), ((data) = NULL); \ + (node) != NULL \ + && ((data) = static_cast(data, listgetdata(node)), \ + (nextnode) = node->next, 1); \ + (node) = (nextnode), ((data) = NULL) + +/* read-only list iteration macro. + * Usage: as per ALL_LIST_ELEMENTS, but not safe to delete the listnode Only + * use this macro when it is *immediately obvious* the listnode is not + * deleted in the body of the loop. Does not have forward-reference overhead + * of previous macro. + */ +#define ALL_LIST_ELEMENTS_RO(list, node, data) \ + (node) = listhead(list), ((data) = NULL); \ + (node) != NULL && ((data) = static_cast(data, listgetdata(node)), 1); \ + (node) = listnextnode(node), ((data) = NULL) + +extern struct listnode *listnode_lookup_nocheck(struct list *list, void *data); + +/* + * Add a node to *list, if non-NULL. Otherwise, allocate a new list, mail + * it back in *list, and add a new node. + * + * Return: the new node. + */ +extern struct listnode *listnode_add_force(struct list **list, void *val); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_LINKLIST_H */ diff --git a/lib/log.c b/lib/log.c new file mode 100644 index 0000000..969ca79 --- /dev/null +++ b/lib/log.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Logging of zebra + * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro + */ + +#define FRR_DEFINE_DESC_TABLE + +#include + +#ifdef HAVE_GLIBC_BACKTRACE +#include +#endif /* HAVE_GLIBC_BACKTRACE */ + +#include "zclient.h" +#include "log.h" +#include "memory.h" +#include "command.h" +#include "lib_errors.h" +#include "lib/hook.h" +#include "printfrr.h" +#include "frr_pthread.h" + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include +#include +#endif + +/** + * Looks up a message in a message list by key. + * + * If the message is not found, returns the provided error message. + * + * Terminates when it hits a struct message that's all zeros. + * + * @param mz the message list + * @param kz the message key + * @param nf the message to return if not found + * @return the message + */ +const char *lookup_msg(const struct message *mz, int kz, const char *nf) +{ + static struct message nt = {0}; + const char *rz = nf ? nf : "(no message found)"; + const struct message *pnt; + for (pnt = mz; memcmp(pnt, &nt, sizeof(struct message)); pnt++) + if (pnt->key == kz) { + rz = pnt->str ? pnt->str : rz; + break; + } + return rz; +} + +/* For time string format. */ +size_t frr_timestamp(int timestamp_precision, char *buf, size_t buflen) +{ + static struct { + time_t last; + size_t len; + char buf[28]; + } cache; + struct timeval clock; + + gettimeofday(&clock, NULL); + + /* first, we update the cache if the time has changed */ + if (cache.last != clock.tv_sec) { + struct tm tm; + cache.last = clock.tv_sec; + localtime_r(&cache.last, &tm); + cache.len = strftime(cache.buf, sizeof(cache.buf), + "%Y/%m/%d %H:%M:%S", &tm); + } + /* note: it's not worth caching the subsecond part, because + chances are that back-to-back calls are not sufficiently close + together + for the clock not to have ticked forward */ + + if (buflen > cache.len) { + memcpy(buf, cache.buf, cache.len); + if ((timestamp_precision > 0) + && (buflen > cache.len + 1 + timestamp_precision)) { + /* should we worry about locale issues? */ + static const int divisor[] = {0, 100000, 10000, 1000, + 100, 10, 1}; + int prec; + char *p = buf + cache.len + 1 + + (prec = timestamp_precision); + *p-- = '\0'; + while (prec > 6) + /* this is unlikely to happen, but protect anyway */ + { + *p-- = '0'; + prec--; + } + clock.tv_usec /= divisor[prec]; + do { + *p-- = '0' + (clock.tv_usec % 10); + clock.tv_usec /= 10; + } while (--prec > 0); + *p = '.'; + return cache.len + 1 + timestamp_precision; + } + buf[cache.len] = '\0'; + return cache.len; + } + if (buflen > 0) + buf[0] = '\0'; + return 0; +} + +/* + * crash handling + * + * NB: only AS-Safe (async-signal) functions can be used here! + */ + +/* Note: the goal here is to use only async-signal-safe functions. */ +void zlog_signal(int signo, const char *action, void *siginfo_v, + void *program_counter) +{ + siginfo_t *siginfo = siginfo_v; + time_t now; + char buf[sizeof("DEFAULT: Received signal S at T (si_addr 0xP, PC 0xP); aborting...") + + 100]; + struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) }; + + time(&now); + + bprintfrr(&fb, "Received signal %d at %lld", signo, (long long)now); + if (program_counter) + bprintfrr(&fb, " (si_addr 0x%tx, PC 0x%tx)", + (ptrdiff_t)siginfo->si_addr, + (ptrdiff_t)program_counter); + else + bprintfrr(&fb, " (si_addr 0x%tx)", + (ptrdiff_t)siginfo->si_addr); + bprintfrr(&fb, "; %s\n", action); + + zlog_sigsafe(fb.buf, fb.pos - fb.buf); + + zlog_backtrace_sigsafe(LOG_CRIT, program_counter); + + fb.pos = buf; + + struct event *tc; + tc = pthread_getspecific(thread_current); + + if (!tc) + bprintfrr(&fb, "no thread information available\n"); + else + bprintfrr(&fb, "in thread %s scheduled from %s:%d %s()\n", + tc->xref->funcname, tc->xref->xref.file, + tc->xref->xref.line, tc->xref->xref.func); + + zlog_sigsafe(fb.buf, fb.pos - fb.buf); +} + +/* Log a backtrace using only async-signal-safe functions. + Needs to be enhanced to support syslog logging. */ +void zlog_backtrace_sigsafe(int priority, void *program_counter) +{ +#ifdef HAVE_LIBUNWIND + char buf[256]; + struct fbuf fb = { .buf = buf, .len = sizeof(buf) }; + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t ip, off, sp; + Dl_info dlinfo; + + memset(&uc, 0, sizeof(uc)); + memset(&cursor, 0, sizeof(cursor)); + + unw_getcontext(&uc); + unw_init_local(&cursor, &uc); + while (unw_step(&cursor) > 0) { + char name[128] = "?"; + + unw_get_reg(&cursor, UNW_REG_IP, &ip); + unw_get_reg(&cursor, UNW_REG_SP, &sp); + + if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) + snprintfrr(name, sizeof(name), "%s+%#lx", + buf, (long)off); + + fb.pos = buf; + if (unw_is_signal_frame(&cursor)) + bprintfrr(&fb, " ---- signal ----\n"); + bprintfrr(&fb, "%-30s %16lx %16lx", name, (long)ip, (long)sp); + if (dladdr((void *)ip, &dlinfo)) + bprintfrr(&fb, " %s (mapped at %p)", + dlinfo.dli_fname, dlinfo.dli_fbase); + bprintfrr(&fb, "\n"); + zlog_sigsafe(fb.buf, fb.pos - fb.buf); + } +#elif defined(HAVE_GLIBC_BACKTRACE) + void *array[64]; + int size, i; + char buf[128]; + struct fbuf fb = { .buf = buf, .pos = buf, .len = sizeof(buf) }; + char **bt = NULL; + + size = backtrace(array, array_size(array)); + if (size <= 0 || (size_t)size > array_size(array)) + return; + + bprintfrr(&fb, "Backtrace for %d stack frames:", size); + zlog_sigsafe(fb.pos, fb.buf - fb.pos); + + bt = backtrace_symbols(array, size); + + for (i = 0; i < size; i++) { + fb.pos = buf; + if (bt) + bprintfrr(&fb, "%s", bt[i]); + else + bprintfrr(&fb, "[bt %d] 0x%tx", i, + (ptrdiff_t)(array[i])); + zlog_sigsafe(fb.buf, fb.pos - fb.buf); + } + if (bt) + free(bt); +#endif /* HAVE_STRACK_TRACE */ +} + +void zlog_backtrace(int priority) +{ +#ifdef HAVE_LIBUNWIND + char buf[100]; + unw_cursor_t cursor = {}; + unw_context_t uc; + unw_word_t ip, off, sp; + Dl_info dlinfo; + + unw_getcontext(&uc); + unw_init_local(&cursor, &uc); + zlog(priority, "Backtrace:"); + while (unw_step(&cursor) > 0) { + char name[128] = "?"; + + unw_get_reg(&cursor, UNW_REG_IP, &ip); + unw_get_reg(&cursor, UNW_REG_SP, &sp); + + if (unw_is_signal_frame(&cursor)) + zlog(priority, " ---- signal ----"); + + if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) + snprintf(name, sizeof(name), "%s+%#lx", + buf, (long)off); + + if (dladdr((void *)ip, &dlinfo)) + zlog(priority, "%-30s %16lx %16lx %s (mapped at %p)", + name, (long)ip, (long)sp, + dlinfo.dli_fname, dlinfo.dli_fbase); + else + zlog(priority, "%-30s %16lx %16lx", + name, (long)ip, (long)sp); + } +#elif defined(HAVE_GLIBC_BACKTRACE) + void *array[20]; + int size, i; + char **strings; + + size = backtrace(array, array_size(array)); + if (size <= 0 || (size_t)size > array_size(array)) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "Cannot get backtrace, returned invalid # of frames %d (valid range is between 1 and %lu)", + size, (unsigned long)(array_size(array))); + return; + } + zlog(priority, "Backtrace for %d stack frames:", size); + if (!(strings = backtrace_symbols(array, size))) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Cannot get backtrace symbols (out of memory?)"); + for (i = 0; i < size; i++) + zlog(priority, "[bt %d] %p", i, array[i]); + } else { + for (i = 0; i < size; i++) + zlog(priority, "[bt %d] %s", i, strings[i]); + free(strings); + } +#else /* !HAVE_GLIBC_BACKTRACE && !HAVE_LIBUNWIND */ + zlog(priority, "No backtrace available on this platform."); +#endif +} + +void zlog_thread_info(int log_level) +{ + struct event *tc; + tc = pthread_getspecific(thread_current); + + if (tc) + zlog(log_level, + "Current thread function %s, scheduled from file %s, line %u in %s()", + tc->xref->funcname, tc->xref->xref.file, + tc->xref->xref.line, tc->xref->xref.func); + else + zlog(log_level, "Current thread not known/applicable"); +} + +void memory_oom(size_t size, const char *name) +{ + zlog(LOG_CRIT, + "out of memory: failed to allocate %zu bytes for %s object", + size, name); + zlog_backtrace(LOG_CRIT); + log_memstats(stderr, "log"); + abort(); +} + +/* Wrapper around strerror to handle case where it returns NULL. */ +const char *safe_strerror(int errnum) +{ + const char *s = strerror(errnum); + return (s != NULL) ? s : "Unknown error"; +} + +#define DESC_ENTRY(T) [(T)] = { (T), (#T), '\0' } +static const struct zebra_desc_table command_types[] = { + DESC_ENTRY(ZEBRA_INTERFACE_ADD), + DESC_ENTRY(ZEBRA_INTERFACE_DELETE), + DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_ADD), + DESC_ENTRY(ZEBRA_INTERFACE_ADDRESS_DELETE), + DESC_ENTRY(ZEBRA_INTERFACE_UP), + DESC_ENTRY(ZEBRA_INTERFACE_DOWN), + DESC_ENTRY(ZEBRA_INTERFACE_SET_MASTER), + DESC_ENTRY(ZEBRA_INTERFACE_SET_PROTODOWN), + DESC_ENTRY(ZEBRA_ROUTE_ADD), + DESC_ENTRY(ZEBRA_ROUTE_DELETE), + DESC_ENTRY(ZEBRA_ROUTE_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_ADD), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_DELETE), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_ADD), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE), + DESC_ENTRY(ZEBRA_ROUTER_ID_ADD), + DESC_ENTRY(ZEBRA_ROUTER_ID_DELETE), + DESC_ENTRY(ZEBRA_ROUTER_ID_UPDATE), + DESC_ENTRY(ZEBRA_HELLO), + DESC_ENTRY(ZEBRA_CAPABILITIES), + DESC_ENTRY(ZEBRA_NEXTHOP_REGISTER), + DESC_ENTRY(ZEBRA_NEXTHOP_UNREGISTER), + DESC_ENTRY(ZEBRA_NEXTHOP_UPDATE), + DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_ADD), + DESC_ENTRY(ZEBRA_INTERFACE_NBR_ADDRESS_DELETE), + DESC_ENTRY(ZEBRA_INTERFACE_BFD_DEST_UPDATE), + DESC_ENTRY(ZEBRA_BFD_DEST_REGISTER), + DESC_ENTRY(ZEBRA_BFD_DEST_DEREGISTER), + DESC_ENTRY(ZEBRA_BFD_DEST_UPDATE), + DESC_ENTRY(ZEBRA_BFD_DEST_REPLAY), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_ADD), + DESC_ENTRY(ZEBRA_REDISTRIBUTE_ROUTE_DEL), + DESC_ENTRY(ZEBRA_VRF_UNREGISTER), + DESC_ENTRY(ZEBRA_VRF_ADD), + DESC_ENTRY(ZEBRA_VRF_DELETE), + DESC_ENTRY(ZEBRA_VRF_LABEL), + DESC_ENTRY(ZEBRA_BFD_CLIENT_REGISTER), + DESC_ENTRY(ZEBRA_BFD_CLIENT_DEREGISTER), + DESC_ENTRY(ZEBRA_INTERFACE_ENABLE_RADV), + DESC_ENTRY(ZEBRA_INTERFACE_DISABLE_RADV), + DESC_ENTRY(ZEBRA_NEXTHOP_LOOKUP_MRIB), + DESC_ENTRY(ZEBRA_INTERFACE_LINK_PARAMS), + DESC_ENTRY(ZEBRA_MPLS_LABELS_ADD), + DESC_ENTRY(ZEBRA_MPLS_LABELS_DELETE), + DESC_ENTRY(ZEBRA_MPLS_LABELS_REPLACE), + DESC_ENTRY(ZEBRA_SR_POLICY_SET), + DESC_ENTRY(ZEBRA_SR_POLICY_DELETE), + DESC_ENTRY(ZEBRA_SR_POLICY_NOTIFY_STATUS), + DESC_ENTRY(ZEBRA_IPMR_ROUTE_STATS), + DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT), + DESC_ENTRY(ZEBRA_LABEL_MANAGER_CONNECT_ASYNC), + DESC_ENTRY(ZEBRA_GET_LABEL_CHUNK), + DESC_ENTRY(ZEBRA_RELEASE_LABEL_CHUNK), + DESC_ENTRY(ZEBRA_FEC_REGISTER), + DESC_ENTRY(ZEBRA_FEC_UNREGISTER), + DESC_ENTRY(ZEBRA_FEC_UPDATE), + DESC_ENTRY(ZEBRA_ADVERTISE_DEFAULT_GW), + DESC_ENTRY(ZEBRA_ADVERTISE_SVI_MACIP), + DESC_ENTRY(ZEBRA_ADVERTISE_SUBNET), + DESC_ENTRY(ZEBRA_ADVERTISE_ALL_VNI), + DESC_ENTRY(ZEBRA_LOCAL_ES_ADD), + DESC_ENTRY(ZEBRA_LOCAL_ES_DEL), + DESC_ENTRY(ZEBRA_REMOTE_ES_VTEP_ADD), + DESC_ENTRY(ZEBRA_REMOTE_ES_VTEP_DEL), + DESC_ENTRY(ZEBRA_LOCAL_ES_EVI_ADD), + DESC_ENTRY(ZEBRA_LOCAL_ES_EVI_DEL), + DESC_ENTRY(ZEBRA_VNI_ADD), + DESC_ENTRY(ZEBRA_VNI_DEL), + DESC_ENTRY(ZEBRA_L3VNI_ADD), + DESC_ENTRY(ZEBRA_L3VNI_DEL), + DESC_ENTRY(ZEBRA_REMOTE_VTEP_ADD), + DESC_ENTRY(ZEBRA_REMOTE_VTEP_DEL), + DESC_ENTRY(ZEBRA_MACIP_ADD), + DESC_ENTRY(ZEBRA_MACIP_DEL), + DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_ADD), + DESC_ENTRY(ZEBRA_IP_PREFIX_ROUTE_DEL), + DESC_ENTRY(ZEBRA_REMOTE_MACIP_ADD), + DESC_ENTRY(ZEBRA_REMOTE_MACIP_DEL), + DESC_ENTRY(ZEBRA_DUPLICATE_ADDR_DETECTION), + DESC_ENTRY(ZEBRA_PW_ADD), + DESC_ENTRY(ZEBRA_PW_DELETE), + DESC_ENTRY(ZEBRA_PW_SET), + DESC_ENTRY(ZEBRA_PW_UNSET), + DESC_ENTRY(ZEBRA_PW_STATUS_UPDATE), + DESC_ENTRY(ZEBRA_RULE_ADD), + DESC_ENTRY(ZEBRA_RULE_DELETE), + DESC_ENTRY(ZEBRA_RULE_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_TABLE_MANAGER_CONNECT), + DESC_ENTRY(ZEBRA_GET_TABLE_CHUNK), + DESC_ENTRY(ZEBRA_RELEASE_TABLE_CHUNK), + DESC_ENTRY(ZEBRA_IPSET_CREATE), + DESC_ENTRY(ZEBRA_IPSET_DESTROY), + DESC_ENTRY(ZEBRA_IPSET_ENTRY_ADD), + DESC_ENTRY(ZEBRA_IPSET_ENTRY_DELETE), + DESC_ENTRY(ZEBRA_IPSET_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_IPSET_ENTRY_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_IPTABLE_ADD), + DESC_ENTRY(ZEBRA_IPTABLE_DELETE), + DESC_ENTRY(ZEBRA_IPTABLE_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_VXLAN_FLOOD_CONTROL), + DESC_ENTRY(ZEBRA_VXLAN_SG_ADD), + DESC_ENTRY(ZEBRA_VXLAN_SG_DEL), + DESC_ENTRY(ZEBRA_VXLAN_SG_REPLAY), + DESC_ENTRY(ZEBRA_MLAG_PROCESS_UP), + DESC_ENTRY(ZEBRA_MLAG_PROCESS_DOWN), + DESC_ENTRY(ZEBRA_MLAG_CLIENT_REGISTER), + DESC_ENTRY(ZEBRA_MLAG_CLIENT_UNREGISTER), + DESC_ENTRY(ZEBRA_MLAG_FORWARD_MSG), + DESC_ENTRY(ZEBRA_NHG_ADD), + DESC_ENTRY(ZEBRA_NHG_DEL), + DESC_ENTRY(ZEBRA_NHG_NOTIFY_OWNER), + DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_ADD), + DESC_ENTRY(ZEBRA_EVPN_REMOTE_NH_DEL), + DESC_ENTRY(ZEBRA_SRV6_LOCATOR_ADD), + DESC_ENTRY(ZEBRA_SRV6_LOCATOR_DELETE), + DESC_ENTRY(ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK), + DESC_ENTRY(ZEBRA_SRV6_MANAGER_RELEASE_LOCATOR_CHUNK), + DESC_ENTRY(ZEBRA_ERROR), + DESC_ENTRY(ZEBRA_CLIENT_CAPABILITIES), + DESC_ENTRY(ZEBRA_OPAQUE_MESSAGE), + DESC_ENTRY(ZEBRA_OPAQUE_REGISTER), + DESC_ENTRY(ZEBRA_OPAQUE_UNREGISTER), + DESC_ENTRY(ZEBRA_NEIGH_DISCOVER), + DESC_ENTRY(ZEBRA_ROUTE_NOTIFY_REQUEST), + DESC_ENTRY(ZEBRA_CLIENT_CLOSE_NOTIFY), + DESC_ENTRY(ZEBRA_NEIGH_ADDED), + DESC_ENTRY(ZEBRA_NEIGH_REMOVED), + DESC_ENTRY(ZEBRA_NEIGH_GET), + DESC_ENTRY(ZEBRA_NEIGH_REGISTER), + DESC_ENTRY(ZEBRA_NEIGH_UNREGISTER), + DESC_ENTRY(ZEBRA_NEIGH_IP_ADD), + DESC_ENTRY(ZEBRA_NEIGH_IP_DEL), + DESC_ENTRY(ZEBRA_CONFIGURE_ARP), + DESC_ENTRY(ZEBRA_GRE_GET), + DESC_ENTRY(ZEBRA_GRE_UPDATE), + DESC_ENTRY(ZEBRA_GRE_SOURCE_SET), + DESC_ENTRY(ZEBRA_TC_QDISC_INSTALL), + DESC_ENTRY(ZEBRA_TC_QDISC_UNINSTALL), + DESC_ENTRY(ZEBRA_TC_CLASS_ADD), + DESC_ENTRY(ZEBRA_TC_CLASS_DELETE), + DESC_ENTRY(ZEBRA_TC_FILTER_ADD), + DESC_ENTRY(ZEBRA_TC_FILTER_DELETE), + DESC_ENTRY(ZEBRA_OPAQUE_NOTIFY) +}; +#undef DESC_ENTRY + +static const struct zebra_desc_table unknown = {0, "unknown", '?'}; + +static const struct zebra_desc_table *zroute_lookup(unsigned int zroute) +{ + unsigned int i; + + if (zroute >= array_size(route_types)) { + flog_err(EC_LIB_DEVELOPMENT, "unknown zebra route type: %u", + zroute); + return &unknown; + } + if (zroute == route_types[zroute].type) + return &route_types[zroute]; + for (i = 0; i < array_size(route_types); i++) { + if (zroute == route_types[i].type) { + zlog_warn( + "internal error: route type table out of order while searching for %u, please notify developers", + zroute); + return &route_types[i]; + } + } + flog_err(EC_LIB_DEVELOPMENT, + "internal error: cannot find route type %u in table!", zroute); + return &unknown; +} + +const char *zebra_route_string(unsigned int zroute) +{ + return zroute_lookup(zroute)->string; +} + +char zebra_route_char(unsigned int zroute) +{ + return zroute_lookup(zroute)->chr; +} + +const char *zserv_command_string(unsigned int command) +{ + if (command >= array_size(command_types)) { + flog_err(EC_LIB_DEVELOPMENT, "unknown zserv command type: %u", + command); + return unknown.string; + } + return command_types[command].string; +} + +#define DESC_ENTRY(T) [(T)] = {(T), (#T), '\0'} +static const struct zebra_desc_table gr_client_cap_types[] = { + DESC_ENTRY(ZEBRA_CLIENT_GR_CAPABILITIES), + DESC_ENTRY(ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE), + DESC_ENTRY(ZEBRA_CLIENT_ROUTE_UPDATE_PENDING), + DESC_ENTRY(ZEBRA_CLIENT_GR_DISABLE), + DESC_ENTRY(ZEBRA_CLIENT_RIB_STALE_TIME), +}; +#undef DESC_ENTRY + +const char *zserv_gr_client_cap_string(uint32_t zcc) +{ + if (zcc >= array_size(gr_client_cap_types)) { + flog_err(EC_LIB_DEVELOPMENT, "unknown zserv command type: %u", + zcc); + return unknown.string; + } + return gr_client_cap_types[zcc].string; +} + +int proto_name2num(const char *s) +{ + unsigned i; + + for (i = 0; i < array_size(route_types); ++i) + if (strcasecmp(s, route_types[i].string) == 0) + return route_types[i].type; + return -1; +} + +int proto_redistnum(int afi, const char *s) +{ + if (!s) + return -1; + + if (afi == AFI_IP) { + if (strmatch(s, "kernel")) + return ZEBRA_ROUTE_KERNEL; + else if (strmatch(s, "connected")) + return ZEBRA_ROUTE_CONNECT; + else if (strmatch(s, "local")) + return ZEBRA_ROUTE_LOCAL; + else if (strmatch(s, "static")) + return ZEBRA_ROUTE_STATIC; + else if (strmatch(s, "rip")) + return ZEBRA_ROUTE_RIP; + else if (strmatch(s, "eigrp")) + return ZEBRA_ROUTE_EIGRP; + else if (strmatch(s, "ospf")) + return ZEBRA_ROUTE_OSPF; + else if (strmatch(s, "isis")) + return ZEBRA_ROUTE_ISIS; + else if (strmatch(s, "bgp")) + return ZEBRA_ROUTE_BGP; + else if (strmatch(s, "table")) + return ZEBRA_ROUTE_TABLE; + else if (strmatch(s, "vnc")) + return ZEBRA_ROUTE_VNC; + else if (strmatch(s, "vnc-direct")) + return ZEBRA_ROUTE_VNC_DIRECT; + else if (strmatch(s, "nhrp")) + return ZEBRA_ROUTE_NHRP; + else if (strmatch(s, "babel")) + return ZEBRA_ROUTE_BABEL; + else if (strmatch(s, "sharp")) + return ZEBRA_ROUTE_SHARP; + else if (strmatch(s, "openfabric")) + return ZEBRA_ROUTE_OPENFABRIC; + else if (strmatch(s, "table-direct")) + return ZEBRA_ROUTE_TABLE_DIRECT; + } + if (afi == AFI_IP6) { + if (strmatch(s, "kernel")) + return ZEBRA_ROUTE_KERNEL; + else if (strmatch(s, "connected")) + return ZEBRA_ROUTE_CONNECT; + else if (strmatch(s, "local")) + return ZEBRA_ROUTE_LOCAL; + else if (strmatch(s, "static")) + return ZEBRA_ROUTE_STATIC; + else if (strmatch(s, "ripng")) + return ZEBRA_ROUTE_RIPNG; + else if (strmatch(s, "ospf6")) + return ZEBRA_ROUTE_OSPF6; + else if (strmatch(s, "isis")) + return ZEBRA_ROUTE_ISIS; + else if (strmatch(s, "bgp")) + return ZEBRA_ROUTE_BGP; + else if (strmatch(s, "table")) + return ZEBRA_ROUTE_TABLE; + else if (strmatch(s, "vnc")) + return ZEBRA_ROUTE_VNC; + else if (strmatch(s, "vnc-direct")) + return ZEBRA_ROUTE_VNC_DIRECT; + else if (strmatch(s, "nhrp")) + return ZEBRA_ROUTE_NHRP; + else if (strmatch(s, "babel")) + return ZEBRA_ROUTE_BABEL; + else if (strmatch(s, "sharp")) + return ZEBRA_ROUTE_SHARP; + else if (strmatch(s, "openfabric")) + return ZEBRA_ROUTE_OPENFABRIC; + else if (strmatch(s, "table-direct")) + return ZEBRA_ROUTE_TABLE_DIRECT; + } + return -1; +} + +void zlog_hexdump(const void *mem, size_t len) +{ + char line[64]; + const uint8_t *src = mem; + const uint8_t *end = src + len; + + if (len == 0) { + zlog_debug("%016lx: (zero length / no data)", (long)src); + return; + } + + while (src < end) { + struct fbuf fb = { + .buf = line, + .pos = line, + .len = sizeof(line), + }; + const uint8_t *lineend = src + 8; + unsigned line_bytes = 0; + + bprintfrr(&fb, "%016lx: ", (long)src); + + while (src < lineend && src < end) { + bprintfrr(&fb, "%02x ", *src++); + line_bytes++; + } + if (line_bytes < 8) + bprintfrr(&fb, "%*s", (8 - line_bytes) * 3, ""); + + src -= line_bytes; + while (src < lineend && src < end && fb.pos < fb.buf + fb.len) { + uint8_t byte = *src++; + + if (isprint(byte)) + *fb.pos++ = byte; + else + *fb.pos++ = '.'; + } + + zlog_debug("%.*s", (int)(fb.pos - fb.buf), fb.buf); + } +} + +const char *zlog_sanitize(char *buf, size_t bufsz, const void *in, size_t inlen) +{ + const char *inbuf = in; + char *pos = buf, *end = buf + bufsz; + const char *iend = inbuf + inlen; + + memset(buf, 0, bufsz); + for (; inbuf < iend; inbuf++) { + /* don't write partial escape sequence */ + if (end - pos < 5) + break; + + if (*inbuf == '\n') + snprintf(pos, end - pos, "\\n"); + else if (*inbuf == '\r') + snprintf(pos, end - pos, "\\r"); + else if (*inbuf == '\t') + snprintf(pos, end - pos, "\\t"); + else if (*inbuf < ' ' || *inbuf == '"' || *inbuf >= 127) + snprintf(pos, end - pos, "\\x%02hhx", *inbuf); + else + *pos = *inbuf; + + pos += strlen(pos); + } + return buf; +} diff --git a/lib/log.h b/lib/log.h new file mode 100644 index 0000000..8a95b7a --- /dev/null +++ b/lib/log.h @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra logging funcions. + * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_LOG_H +#define _ZEBRA_LOG_H + +#include +#include +#include +#include +#include + +#include "lib/hook.h" +#include "lib/zlog.h" +#include "lib/zlog_targets.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Here is some guidance on logging levels to use: + * + * LOG_DEBUG - For all messages that are enabled by optional debugging + * features, typically preceded by "if (IS...DEBUG...)" + * LOG_INFO - Information that may be of interest, but everything seems + * to be working properly. + * LOG_NOTICE - Only for message pertaining to daemon startup or shutdown. + * LOG_WARNING - Warning conditions: unexpected events, but the daemon believes + * it can continue to operate correctly. + * LOG_ERR - Error situations indicating malfunctions. Probably require + * attention. + * + * Note: LOG_CRIT, LOG_ALERT, and LOG_EMERG are currently not used anywhere, + * please use LOG_ERR instead. + */ + +extern void zlog_rotate(void); + +/* Message structure. */ +struct message { + int key; + const char *str; +}; + +extern void zlog_thread_info(int log_level); + +#define ZLOG_FILTERS_MAX 100 /* Max # of filters at once */ +#define ZLOG_FILTER_LENGTH_MAX 80 /* 80 character filter limit */ + +struct zlog_cfg_filterfile { + struct zlog_cfg_file parent; +}; + +extern void zlog_filterfile_init(struct zlog_cfg_filterfile *zcf); +extern void zlog_filterfile_fini(struct zlog_cfg_filterfile *zcf); + +/* Add/Del/Dump log filters */ +extern void zlog_filter_clear(void); +extern int zlog_filter_add(const char *filter); +extern int zlog_filter_del(const char *filter); +extern int zlog_filter_dump(char *buf, size_t max_size); + +const char *lookup_msg(const struct message *mz, int kz, const char *nf); + +/* Safe version of strerror -- never returns NULL. */ +extern const char *safe_strerror(int errnum); + +/* To be called when a fatal signal is caught. */ +extern void zlog_signal(int signo, const char *action, void *siginfo, + void *program_counter); + +/* Log a backtrace. */ +extern void zlog_backtrace(int priority); + +/* Log a backtrace, but in an async-signal-safe way. Should not be + called unless the program is about to exit or abort, since it messes + up the state of zlog file pointers. If program_counter is non-NULL, + that is logged in addition to the current backtrace. */ +extern void zlog_backtrace_sigsafe(int priority, void *program_counter); + +/* Puts a current timestamp in buf and returns the number of characters + written (not including the terminating NUL). The purpose of + this function is to avoid calls to localtime appearing all over the code. + It caches the most recent localtime result and can therefore + avoid multiple calls within the same second. If buflen is too small, + *buf will be set to '\0', and 0 will be returned. */ +#define FRR_TIMESTAMP_LEN 40 +extern size_t frr_timestamp(int timestamp_precision /* # subsecond digits */, + char *buf, size_t buflen); + +extern void zlog_hexdump(const void *mem, size_t len); +extern const char *zlog_sanitize(char *buf, size_t bufsz, const void *in, + size_t inlen); + +/* Note: whenever a new route-type or zserv-command is added the + * corresponding {command,route}_types[] table in lib/log.c MUST be + * updated! */ + +/* Map a route type to a string. For example, ZEBRA_ROUTE_RIPNG -> "ripng". */ +extern const char *zebra_route_string(unsigned int route_type); +/* Map a route type to a char. For example, ZEBRA_ROUTE_RIPNG -> 'R'. */ +extern char zebra_route_char(unsigned int route_type); +/* Map a zserv command type to the same string, + * e.g. ZEBRA_INTERFACE_ADD -> "ZEBRA_INTERFACE_ADD" */ +/* Map a protocol name to its number. e.g. ZEBRA_ROUTE_BGP->9*/ +extern int proto_name2num(const char *s); +/* Map redistribute X argument to protocol number. + * unlike proto_name2num, this accepts shorthands and takes + * an AFI value to restrict input */ +extern int proto_redistnum(int afi, const char *s); + +extern const char *zserv_command_string(unsigned int command); +extern const char *zserv_gr_client_cap_string(unsigned int zcc); + +#define OSPF_LOG(level, cond, fmt, ...) \ + do { \ + if (cond) \ + zlog_##level(fmt, ##__VA_ARGS__); \ + } while (0) + +#define OSPF_LOG_ERR(fmt, ...) OSPF_LOG(err, true, fmt, ##__VA_ARGS__) + +#define OSPF_LOG_WARN(fmt, ...) OSPF_LOG(warn, true, fmt, ##__VA_ARGS__) + +#define OSPF_LOG_INFO(fmt, ...) OSPF_LOG(info, true, fmt, ##__VA_ARGS__) + +#define OSPF_LOG_DEBUG(cond, fmt, ...) OSPF_LOG(debug, cond, fmt, ##__VA_ARGS__) + +#define OSPF_LOG_NOTICE(fmt, ...) OSPF_LOG(notice, true, fmt, ##__VA_ARGS__) + +/* structure useful for avoiding repeated rendering of the same timestamp */ +struct timestamp_control { + size_t len; /* length of rendered timestamp */ + int precision; /* configuration parameter */ + int already_rendered; /* should be initialized to 0 */ + char buf[FRR_TIMESTAMP_LEN]; /* will contain the rendered timestamp + */ +}; + +/* Defines for use in command construction: */ + +#define LOG_LEVEL_DESC \ + "System is unusable\n" \ + "Immediate action needed\n" \ + "Critical conditions\n" \ + "Error conditions\n" \ + "Warning conditions\n" \ + "Normal but significant conditions\n" \ + "Informational messages\n" \ + "Debugging messages\n" + +#define LOG_FACILITY_DESC \ + "Kernel\n" \ + "User process\n" \ + "Mail system\n" \ + "System daemons\n" \ + "Authorization system\n" \ + "Syslog itself\n" \ + "Line printer system\n" \ + "USENET news\n" \ + "Unix-to-Unix copy system\n" \ + "Cron/at facility\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" \ + "Local use\n" + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_LOG_H */ diff --git a/lib/log_filter.c b/lib/log_filter.c new file mode 100644 index 0000000..e8d99d7 --- /dev/null +++ b/lib/log_filter.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Logging - Filtered file log target + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ + +#include + +#include "frr_pthread.h" +#include "log.h" + +static pthread_mutex_t logfilterlock = PTHREAD_MUTEX_INITIALIZER; +static char zlog_filters[ZLOG_FILTERS_MAX][ZLOG_FILTER_LENGTH_MAX + 1]; +static uint8_t zlog_filter_count; + +/* + * look for a match on the filter in the current filters, + * logfilterlock must be held + */ +static int zlog_filter_lookup(const char *lookup) +{ + for (int i = 0; i < zlog_filter_count; i++) { + if (strncmp(lookup, zlog_filters[i], sizeof(zlog_filters[0])) + == 0) + return i; + } + return -1; +} + +void zlog_filter_clear(void) +{ + frr_with_mutex (&logfilterlock) { + zlog_filter_count = 0; + } +} + +int zlog_filter_add(const char *filter) +{ + frr_with_mutex (&logfilterlock) { + if (zlog_filter_count >= ZLOG_FILTERS_MAX) + return 1; + + if (zlog_filter_lookup(filter) != -1) + /* Filter already present */ + return -1; + + strlcpy(zlog_filters[zlog_filter_count], filter, + sizeof(zlog_filters[0])); + + if (zlog_filters[zlog_filter_count][0] == '\0') + /* Filter was either empty or didn't get copied + * correctly + */ + return -1; + + zlog_filter_count++; + } + return 0; +} + +int zlog_filter_del(const char *filter) +{ + frr_with_mutex (&logfilterlock) { + int found_idx = zlog_filter_lookup(filter); + int last_idx = zlog_filter_count - 1; + + if (found_idx == -1) + /* Didn't find the filter to delete */ + return -1; + + /* Adjust the filter array */ + memmove(zlog_filters[found_idx], zlog_filters[found_idx + 1], + (last_idx - found_idx) * sizeof(zlog_filters[0])); + + zlog_filter_count--; + } + return 0; +} + +/* Dump all filters to buffer, delimited by new line */ +int zlog_filter_dump(char *buf, size_t max_size) +{ + int len = 0; + + frr_with_mutex (&logfilterlock) { + for (int i = 0; i < zlog_filter_count; i++) { + int ret; + + ret = snprintf(buf + len, max_size - len, " %s\n", + zlog_filters[i]); + len += ret; + if ((ret < 0) || ((size_t)len >= max_size)) + return -1; + } + } + + return len; +} + +static int search_buf(const char *buf, size_t len) +{ + char *found = NULL; + + frr_with_mutex (&logfilterlock) { + for (int i = 0; i < zlog_filter_count; i++) { + found = memmem(buf, len, zlog_filters[i], + strlen(zlog_filters[i])); + if (found != NULL) + return 0; + } + } + + return -1; +} + +static void zlog_filterfile_fd(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + struct zlog_msg *msgfilt[nmsgs]; + size_t i, o = 0; + const char *text; + size_t text_len; + + for (i = 0; i < nmsgs; i++) { + if (zlog_msg_prio(msgs[i]) >= LOG_DEBUG) { + text = zlog_msg_text(msgs[i], &text_len); + if (search_buf(text, text_len) < 0) + continue; + } + msgfilt[o++] = msgs[i]; + } + + if (o) + zlog_fd(zt, msgfilt, o); +} + +void zlog_filterfile_init(struct zlog_cfg_filterfile *zcf) +{ + zlog_file_init(&zcf->parent); + zcf->parent.zlog_wrap = zlog_filterfile_fd; +} + +void zlog_filterfile_fini(struct zlog_cfg_filterfile *zcf) +{ + zlog_file_fini(&zcf->parent); +} diff --git a/lib/log_vty.c b/lib/log_vty.c new file mode 100644 index 0000000..323b1b1 --- /dev/null +++ b/lib/log_vty.c @@ -0,0 +1,999 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Logging - VTY code + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ + +#include + +#include "lib/log_vty.h" +#include "command.h" +#include "lib/log.h" +#include "lib/zlog_targets.h" +#include "lib/zlog_5424.h" +#include "lib/lib_errors.h" +#include "lib/printfrr.h" +#include "lib/systemd.h" +#include "lib/vtysh_daemons.h" + +#include "lib/log_vty_clippy.c" + +#define ZLOG_MAXLVL(a, b) MAX(a, b) + +DEFINE_HOOK(zlog_rotate, (), ()); +DEFINE_HOOK(zlog_cli_show, (struct vty * vty), (vty)); + +static unsigned logmsgs_with_persist_bt; + +static const int log_default_lvl = LOG_DEBUG; + +static int log_config_stdout_lvl = ZLOG_DISABLED; +static int log_config_syslog_lvl = ZLOG_DISABLED; +static int log_cmdline_stdout_lvl = ZLOG_DISABLED; +static int log_cmdline_syslog_lvl = ZLOG_DISABLED; + +static struct zlog_cfg_file zt_file_cmdline = { + .prio_min = ZLOG_DISABLED, + .ts_subsec = LOG_TIMESTAMP_PRECISION, +}; +static struct zlog_cfg_file zt_file = { + .prio_min = ZLOG_DISABLED, + .ts_subsec = LOG_TIMESTAMP_PRECISION, +}; +static struct zlog_cfg_filterfile zt_filterfile = { + .parent = + { + .prio_min = ZLOG_DISABLED, + .ts_subsec = LOG_TIMESTAMP_PRECISION, + }, +}; + +static struct zlog_cfg_file zt_stdout_file = { + .prio_min = ZLOG_DISABLED, + .ts_subsec = LOG_TIMESTAMP_PRECISION, +}; +static struct zlog_cfg_5424 zt_stdout_journald = { + .prio_min = ZLOG_DISABLED, + + .fmt = ZLOG_FMT_JOURNALD, + .dst = ZLOG_5424_DST_UNIX, + .filename = "/run/systemd/journal/socket", + + /* this can't be changed through config since this target substitutes + * in for the "plain" stdout target + */ + .facility = LOG_DAEMON, + .kw_version = false, + .kw_location = true, + .kw_uid = true, + .kw_ec = true, + .kw_args = true, +}; +static bool stdout_journald_in_use; + +const char *zlog_progname; +static const char *zlog_protoname; + +static const struct facility_map { + int facility; + const char *name; + size_t match; +} syslog_facilities[] = { + {LOG_KERN, "kern", 1}, + {LOG_USER, "user", 2}, + {LOG_MAIL, "mail", 1}, + {LOG_DAEMON, "daemon", 1}, + {LOG_AUTH, "auth", 1}, + {LOG_SYSLOG, "syslog", 1}, + {LOG_LPR, "lpr", 2}, + {LOG_NEWS, "news", 1}, + {LOG_UUCP, "uucp", 2}, + {LOG_CRON, "cron", 1}, +#ifdef LOG_FTP + {LOG_FTP, "ftp", 1}, +#endif + {LOG_LOCAL0, "local0", 6}, + {LOG_LOCAL1, "local1", 6}, + {LOG_LOCAL2, "local2", 6}, + {LOG_LOCAL3, "local3", 6}, + {LOG_LOCAL4, "local4", 6}, + {LOG_LOCAL5, "local5", 6}, + {LOG_LOCAL6, "local6", 6}, + {LOG_LOCAL7, "local7", 6}, + {0, NULL, 0}, +}; + +static const char * const zlog_priority[] = { + "emergencies", "alerts", "critical", "errors", "warnings", + "notifications", "informational", "debugging", NULL, +}; + +const char *zlog_priority_str(int priority) +{ + if (priority > LOG_DEBUG) + return "???"; + return zlog_priority[priority]; +} + +const char *facility_name(int facility) +{ + const struct facility_map *fm; + + for (fm = syslog_facilities; fm->name; fm++) + if (fm->facility == facility) + return fm->name; + return ""; +} + +int facility_match(const char *str) +{ + const struct facility_map *fm; + + for (fm = syslog_facilities; fm->name; fm++) + if (!strncmp(str, fm->name, fm->match)) + return fm->facility; + return -1; +} + +int log_level_match(const char *s) +{ + int level; + + for (level = 0; zlog_priority[level] != NULL; level++) + if (!strncmp(s, zlog_priority[level], 2)) + return level; + return ZLOG_DISABLED; +} + +void zlog_rotate(void) +{ + zlog_file_rotate(&zt_file); + zlog_file_rotate(&zt_filterfile.parent); + zlog_file_rotate(&zt_file_cmdline); + hook_call(zlog_rotate); +} + + +void log_show_syslog(struct vty *vty) +{ + int level = zlog_syslog_get_prio_min(); + + vty_out(vty, "Syslog logging: "); + if (level == ZLOG_DISABLED) + vty_out(vty, "disabled\n"); + else + vty_out(vty, "level %s, facility %s, ident %s\n", + zlog_priority[level], + facility_name(zlog_syslog_get_facility()), + zlog_progname); +} + +DEFUN_NOSH (show_logging, + show_logging_cmd, + "show logging", + SHOW_STR + "Show current logging configuration\n") +{ + int stdout_prio; + + log_show_syslog(vty); + + stdout_prio = stdout_journald_in_use ? zt_stdout_journald.prio_min + : zt_stdout_file.prio_min; + + vty_out(vty, "Stdout logging: "); + if (stdout_prio == ZLOG_DISABLED) + vty_out(vty, "disabled"); + else + vty_out(vty, "level %s", zlog_priority[stdout_prio]); + vty_out(vty, "\n"); + + vty_out(vty, "File logging: "); + if (zt_file.prio_min == ZLOG_DISABLED || !zt_file.filename) + vty_out(vty, "disabled"); + else + vty_out(vty, "level %s, filename %s", + zlog_priority[zt_file.prio_min], zt_file.filename); + vty_out(vty, "\n"); + + if (zt_filterfile.parent.prio_min != ZLOG_DISABLED + && zt_filterfile.parent.filename) + vty_out(vty, "Filtered-file logging: level %s, filename %s\n", + zlog_priority[zt_filterfile.parent.prio_min], + zt_filterfile.parent.filename); + + if (log_cmdline_syslog_lvl != ZLOG_DISABLED) + vty_out(vty, + "From command line: \"--log syslog --log-level %s\"\n", + zlog_priority[log_cmdline_syslog_lvl]); + if (log_cmdline_stdout_lvl != ZLOG_DISABLED) + vty_out(vty, + "From command line: \"--log stdout --log-level %s\"\n", + zlog_priority[log_cmdline_stdout_lvl]); + if (zt_file_cmdline.prio_min != ZLOG_DISABLED) + vty_out(vty, + "From command line: \"--log file:%s --log-level %s\"\n", + zt_file_cmdline.filename, + zlog_priority[zt_file_cmdline.prio_min]); + + vty_out(vty, "Protocol name: %s\n", zlog_protoname); + vty_out(vty, "Record priority: %s\n", + (zt_file.record_priority ? "enabled" : "disabled")); + vty_out(vty, "Timestamp precision: %d\n", zt_file.ts_subsec); + + hook_call(zlog_cli_show, vty); + return CMD_SUCCESS; +} + +static void log_stdout_apply_level(void) +{ + int maxlvl; + + maxlvl = ZLOG_MAXLVL(log_config_stdout_lvl, log_cmdline_stdout_lvl); + + if (stdout_journald_in_use) { + zt_stdout_journald.prio_min = maxlvl; + zlog_5424_apply_meta(&zt_stdout_journald); + } else { + zt_stdout_file.prio_min = maxlvl; + zlog_file_set_other(&zt_stdout_file); + } +} + +DEFPY (config_log_stdout, + config_log_stdout_cmd, + "log stdout [$levelarg]", + "Logging control\n" + "Set stdout logging level\n" + LOG_LEVEL_DESC) +{ + int level; + + if (levelarg) { + level = log_level_match(levelarg); + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + } else + level = log_default_lvl; + + log_config_stdout_lvl = level; + log_stdout_apply_level(); + return CMD_SUCCESS; +} + +DEFUN (no_config_log_stdout, + no_config_log_stdout_cmd, + "no log stdout []", + NO_STR + "Logging control\n" + "Cancel logging to stdout\n" + LOG_LEVEL_DESC) +{ + log_config_stdout_lvl = ZLOG_DISABLED; + log_stdout_apply_level(); + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (config_log_monitor, + config_log_monitor_cmd, + "log monitor []", + "Logging control\n" + "Set terminal line (monitor) logging level\n" + LOG_LEVEL_DESC) +{ + vty_out(vty, "%% \"log monitor\" is deprecated and does nothing.\n"); + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_config_log_monitor, + no_config_log_monitor_cmd, + "no log monitor []", + NO_STR + "Logging control\n" + "Disable terminal line (monitor) logging\n" + LOG_LEVEL_DESC) +{ + return CMD_SUCCESS; +} + +DEFPY_NOSH (debug_uid_backtrace, + debug_uid_backtrace_cmd, + "[no] debug unique-id UID backtrace", + NO_STR + DEBUG_STR + "Options per individual log message, by unique ID\n" + "Log message unique ID (XXXXX-XXXXX)\n" + "Add backtrace to log when message is printed\n") +{ + struct xrefdata search, *xrd; + struct xrefdata_logmsg *xrdl; + uint8_t flag; + + strlcpy(search.uid, uid, sizeof(search.uid)); + xrd = xrefdata_uid_find(&xrefdata_uid, &search); + + if (!xrd) + return CMD_ERR_NOTHING_TODO; + + if (xrd->xref->type != XREFT_LOGMSG) { + vty_out(vty, "%% ID \"%s\" is not a log message\n", uid); + return CMD_WARNING; + } + xrdl = container_of(xrd, struct xrefdata_logmsg, xrefdata); + + flag = (vty->node == CONFIG_NODE) ? LOGMSG_FLAG_PERSISTENT + : LOGMSG_FLAG_EPHEMERAL; + + if ((xrdl->fl_print_bt & flag) == (no ? 0 : flag)) + return CMD_SUCCESS; + if (flag == LOGMSG_FLAG_PERSISTENT) + logmsgs_with_persist_bt += no ? -1 : 1; + + xrdl->fl_print_bt ^= flag; + return CMD_SUCCESS; +} + +static int set_log_file(struct zlog_cfg_file *target, struct vty *vty, + const char *fname, int loglevel) +{ + char path[MAXPATHLEN + 1]; + const char *fullpath; + bool ok; + + + /* Path detection. */ + if (!IS_DIRECTORY_SEP(*fname)) { + char cwd[MAXPATHLEN + 1]; + + cwd[MAXPATHLEN] = '\0'; + + if (getcwd(cwd, MAXPATHLEN) == NULL) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "config_log_file: Unable to alloc mem!"); + return CMD_WARNING_CONFIG_FAILED; + } + + int pr = snprintf(path, sizeof(path), "%s/%s", cwd, fname); + if (pr < 0 || (unsigned int)pr >= sizeof(path)) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "%s: Path too long ('%s/%s'); system maximum is %u", + __func__, cwd, fname, MAXPATHLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + fullpath = path; + } else + fullpath = fname; + + target->prio_min = loglevel; + ok = zlog_file_set_filename(target, fullpath); + + if (!ok) { + if (vty) + vty_out(vty, "can't open logfile %s\n", fname); + return CMD_WARNING_CONFIG_FAILED; + } + return CMD_SUCCESS; +} + +void command_setup_early_logging(const char *dest, const char *level) +{ + int nlevel; + char *sep; + int len; + char type[8]; + + if (level) { + nlevel = log_level_match(level); + + if (nlevel == ZLOG_DISABLED) { + fprintf(stderr, "invalid log level \"%s\"\n", level); + exit(1); + } + } else + nlevel = log_default_lvl; + + if (!dest) + return; + + sep = strchr(dest, ':'); + len = sep ? (int)(sep - dest) : (int)strlen(dest); + + snprintfrr(type, sizeof(type), "%.*s", len, dest); + + if (strcmp(type, "stdout") == 0) { + log_cmdline_stdout_lvl = nlevel; + log_stdout_apply_level(); + return; + } + if (strcmp(type, "syslog") == 0) { + log_cmdline_syslog_lvl = nlevel; + zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl, + log_cmdline_syslog_lvl)); + return; + } + if (strcmp(type, "file") == 0 && sep) { + sep++; + set_log_file(&zt_file_cmdline, NULL, sep, nlevel); + return; + } + if (strcmp(type, "monitor") == 0 && sep) { + struct zlog_live_cfg cfg = {}; + unsigned long fd; + char *endp; + + sep++; + fd = strtoul(sep, &endp, 10); + if (!*sep || *endp) { + fprintf(stderr, "invalid monitor fd \"%s\"\n", sep); + exit(1); + } + + zlog_live_open_fd(&cfg, nlevel, fd); + zlog_live_disown(&cfg); + return; + } + + fprintf(stderr, "invalid log target \"%s\" (\"%s\")\n", type, dest); + exit(1); +} + +DEFUN (clear_log_cmdline, + clear_log_cmdline_cmd, + "clear log cmdline-targets", + CLEAR_STR + "Logging control\n" + "Disable log targets specified at startup by --log option\n") +{ + zt_file_cmdline.prio_min = ZLOG_DISABLED; + zlog_file_set_other(&zt_file_cmdline); + + log_cmdline_syslog_lvl = ZLOG_DISABLED; + zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl, + log_cmdline_syslog_lvl)); + + log_cmdline_stdout_lvl = ZLOG_DISABLED; + log_stdout_apply_level(); + + return CMD_SUCCESS; +} + +/* Per-daemon log file config */ +DEFUN (config_log_dmn_file, + config_log_dmn_file_cmd, + "log daemon " DAEMONS_LIST " file FILENAME [$levelarg]", + "Logging control\n" + "Specific daemon\n" + DAEMONS_STR + "Logging to file\n" + "Logging filename\n" + LOG_LEVEL_DESC) +{ + int level = log_default_lvl; + int idx = 0; + const char *d_str; + const char *filename; + const char *levelarg = NULL; + + d_str = argv[2]->text; + + /* Ignore if not for this daemon */ + if (!strmatch(d_str, frr_get_progname())) + return CMD_SUCCESS; + + if (argv_find(argv, argc, "file", &idx)) + filename = argv[idx + 1]->arg; + else + return CMD_SUCCESS; + + if (argc > 5) + levelarg = argv[5]->text; + + if (levelarg) { + level = log_level_match(levelarg); + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + } + return set_log_file(&zt_file, vty, filename, level); +} + +/* Per-daemon no log file */ +DEFUN (no_config_log_dmn_file, + no_config_log_dmn_file_cmd, + "no log daemon " DAEMONS_LIST " file [FILENAME [LEVEL]]", + NO_STR + "Logging control\n" + "Specific daemon\n" + DAEMONS_STR + "Cancel logging to file\n" + "Logging file name\n" + "Logging level\n") +{ + const char *d_str; + + d_str = argv[3]->text; + + /* Ignore if not for this daemon */ + if (!strmatch(d_str, frr_get_progname())) + return CMD_SUCCESS; + + zt_file.prio_min = ZLOG_DISABLED; + zlog_file_set_other(&zt_file); + return CMD_SUCCESS; +} + +DEFPY (config_log_file, + config_log_file_cmd, + "log file FILENAME [$levelarg]", + "Logging control\n" + "Logging to file\n" + "Logging filename\n" + LOG_LEVEL_DESC) +{ + int level = log_default_lvl; + + if (levelarg) { + level = log_level_match(levelarg); + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + } + return set_log_file(&zt_file, vty, filename, level); +} + +DEFUN (no_config_log_file, + no_config_log_file_cmd, + "no log file [FILENAME [LEVEL]]", + NO_STR + "Logging control\n" + "Cancel logging to file\n" + "Logging file name\n" + "Logging level\n") +{ + zt_file.prio_min = ZLOG_DISABLED; + zlog_file_set_other(&zt_file); + return CMD_SUCCESS; +} + +DEFPY (config_log_syslog, + config_log_syslog_cmd, + "log syslog [$levelarg]", + "Logging control\n" + "Set syslog logging level\n" + LOG_LEVEL_DESC) +{ + int level; + + if (levelarg) { + level = log_level_match(levelarg); + + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + } else + level = log_default_lvl; + + log_config_syslog_lvl = level; + zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl, + log_cmdline_syslog_lvl)); + return CMD_SUCCESS; +} + +DEFUN (no_config_log_syslog, + no_config_log_syslog_cmd, + "no log syslog [] []", + NO_STR + "Logging control\n" + "Cancel logging to syslog\n" + LOG_FACILITY_DESC + LOG_LEVEL_DESC) +{ + log_config_syslog_lvl = ZLOG_DISABLED; + zlog_syslog_set_prio_min(ZLOG_MAXLVL(log_config_syslog_lvl, + log_cmdline_syslog_lvl)); + return CMD_SUCCESS; +} + +DEFPY (config_log_facility, + config_log_facility_cmd, + "log facility $facilityarg", + "Logging control\n" + "Facility parameter for syslog messages\n" + LOG_FACILITY_DESC) +{ + int facility = facility_match(facilityarg); + + zlog_syslog_set_facility(facility); + return CMD_SUCCESS; +} + +DEFUN (no_config_log_facility, + no_config_log_facility_cmd, + "no log facility []", + NO_STR + "Logging control\n" + "Reset syslog facility to default (daemon)\n" + LOG_FACILITY_DESC) +{ + zlog_syslog_set_facility(LOG_DAEMON); + return CMD_SUCCESS; +} + +DEFUN (config_log_record_priority, + config_log_record_priority_cmd, + "log record-priority", + "Logging control\n" + "Log the priority of the message within the message\n") +{ + zt_file.record_priority = true; + zlog_file_set_other(&zt_file); + if (!stdout_journald_in_use) { + zt_stdout_file.record_priority = true; + zlog_file_set_other(&zt_stdout_file); + } + zt_filterfile.parent.record_priority = true; + zlog_file_set_other(&zt_filterfile.parent); + return CMD_SUCCESS; +} + +DEFUN (no_config_log_record_priority, + no_config_log_record_priority_cmd, + "no log record-priority", + NO_STR + "Logging control\n" + "Do not log the priority of the message within the message\n") +{ + zt_file.record_priority = false; + zlog_file_set_other(&zt_file); + if (!stdout_journald_in_use) { + zt_stdout_file.record_priority = false; + zlog_file_set_other(&zt_stdout_file); + } + zt_filterfile.parent.record_priority = false; + zlog_file_set_other(&zt_filterfile.parent); + return CMD_SUCCESS; +} + +DEFPY (config_log_timestamp_precision, + config_log_timestamp_precision_cmd, + "log timestamp precision (0-6)", + "Logging control\n" + "Timestamp configuration\n" + "Set the timestamp precision\n" + "Number of subsecond digits\n") +{ + zt_file.ts_subsec = precision; + zlog_file_set_other(&zt_file); + if (!stdout_journald_in_use) { + zt_stdout_file.ts_subsec = precision; + zlog_file_set_other(&zt_stdout_file); + } + zt_filterfile.parent.ts_subsec = precision; + zlog_file_set_other(&zt_filterfile.parent); + return CMD_SUCCESS; +} + +DEFUN (no_config_log_timestamp_precision, + no_config_log_timestamp_precision_cmd, + "no log timestamp precision [(0-6)]", + NO_STR + "Logging control\n" + "Timestamp configuration\n" + "Reset the timestamp precision to the default value of 0\n" + "Number of subsecond digits\n") +{ + zt_file.ts_subsec = 0; + zlog_file_set_other(&zt_file); + if (!stdout_journald_in_use) { + zt_stdout_file.ts_subsec = 0; + zlog_file_set_other(&zt_stdout_file); + } + zt_filterfile.parent.ts_subsec = 0; + zlog_file_set_other(&zt_filterfile.parent); + return CMD_SUCCESS; +} + +DEFPY (config_log_ec, + config_log_ec_cmd, + "[no] log error-category", + NO_STR + "Logging control\n" + "Prefix log message text with [EC 9999] code\n") +{ + zlog_set_prefix_ec(!no); + return CMD_SUCCESS; +} + +DEFPY (config_log_xid, + config_log_xid_cmd, + "[no] log unique-id", + NO_STR + "Logging control\n" + "Prefix log message text with [XXXXX-XXXXX] identifier\n") +{ + zlog_set_prefix_xid(!no); + return CMD_SUCCESS; +} + +DEFPY (config_log_filterfile, + config_log_filterfile_cmd, + "log filtered-file FILENAME [$levelarg]", + "Logging control\n" + "Logging to file with string filter\n" + "Logging filename\n" + LOG_LEVEL_DESC) +{ + int level = log_default_lvl; + + if (levelarg) { + level = log_level_match(levelarg); + if (level == ZLOG_DISABLED) + return CMD_ERR_NO_MATCH; + } + return set_log_file(&zt_filterfile.parent, vty, filename, level); +} + +DEFUN (no_config_log_filterfile, + no_config_log_filterfile_cmd, + "no log filtered-file [FILENAME [LEVEL]]", + NO_STR + "Logging control\n" + "Cancel logging to file with string filter\n" + "Logging file name\n" + "Logging level\n") +{ + zt_filterfile.parent.prio_min = ZLOG_DISABLED; + zlog_file_set_other(&zt_filterfile.parent); + return CMD_SUCCESS; +} + +DEFPY (log_filter, + log_filter_cmd, + "[no] log filter-text WORD$filter", + NO_STR + "Logging control\n" + FILTER_LOG_STR + "String to filter by\n") +{ + int ret = 0; + + if (no) + ret = zlog_filter_del(filter); + else + ret = zlog_filter_add(filter); + + if (ret == 1) { + vty_out(vty, "%% filter table full\n"); + return CMD_WARNING; + } else if (ret != 0) { + vty_out(vty, "%% failed to %s log filter\n", + (no ? "remove" : "apply")); + return CMD_WARNING; + } + + vty_out(vty, " %s\n", filter); + return CMD_SUCCESS; +} + +/* Clear all log filters */ +DEFPY (log_filter_clear, + log_filter_clear_cmd, + "clear log filter-text", + CLEAR_STR + "Logging control\n" + FILTER_LOG_STR) +{ + zlog_filter_clear(); + return CMD_SUCCESS; +} + +/* Show log filter */ +DEFPY (show_log_filter, + show_log_filter_cmd, + "show logging filter-text", + SHOW_STR + "Show current logging configuration\n" + FILTER_LOG_STR) +{ + char log_filters[ZLOG_FILTERS_MAX * (ZLOG_FILTER_LENGTH_MAX + 3)] = ""; + int len = 0; + + len = zlog_filter_dump(log_filters, sizeof(log_filters)); + + if (len == -1) { + vty_out(vty, "%% failed to get filters\n"); + return CMD_WARNING; + } + + if (len != 0) + vty_out(vty, "%s", log_filters); + + return CMD_SUCCESS; +} + +/* Enable/disable 'immediate' mode, with no output buffering */ +DEFPY (log_immediate_mode, + log_immediate_mode_cmd, + "[no] log immediate-mode", + NO_STR + "Logging control\n" + "Output immediately, without buffering\n") +{ + zlog_set_immediate(!no); + return CMD_SUCCESS; +} + +void log_config_write(struct vty *vty) +{ + bool show_cmdline_hint = false; + + if (zt_file.prio_min != ZLOG_DISABLED && zt_file.filename) { + vty_out(vty, "log file %s", zt_file.filename); + + if (zt_file.prio_min != log_default_lvl) + vty_out(vty, " %s", zlog_priority[zt_file.prio_min]); + vty_out(vty, "\n"); + } + + if (zt_filterfile.parent.prio_min != ZLOG_DISABLED + && zt_filterfile.parent.filename) { + vty_out(vty, "log filtered-file %s", + zt_filterfile.parent.filename); + + if (zt_filterfile.parent.prio_min != log_default_lvl) + vty_out(vty, " %s", + zlog_priority[zt_filterfile.parent.prio_min]); + vty_out(vty, "\n"); + } + + if (log_config_stdout_lvl != ZLOG_DISABLED) { + vty_out(vty, "log stdout"); + + if (log_config_stdout_lvl != log_default_lvl) + vty_out(vty, " %s", + zlog_priority[log_config_stdout_lvl]); + vty_out(vty, "\n"); + } + + if (log_config_syslog_lvl != ZLOG_DISABLED) { + vty_out(vty, "log syslog"); + + if (log_config_syslog_lvl != log_default_lvl) + vty_out(vty, " %s", + zlog_priority[log_config_syslog_lvl]); + vty_out(vty, "\n"); + } + + if (log_cmdline_syslog_lvl != ZLOG_DISABLED) { + vty_out(vty, + "! \"log syslog %s\" enabled by \"--log\" startup option\n", + zlog_priority[log_cmdline_syslog_lvl]); + show_cmdline_hint = true; + } + if (log_cmdline_stdout_lvl != ZLOG_DISABLED) { + vty_out(vty, + "! \"log stdout %s\" enabled by \"--log\" startup option\n", + zlog_priority[log_cmdline_stdout_lvl]); + show_cmdline_hint = true; + } + if (zt_file_cmdline.prio_min != ZLOG_DISABLED) { + vty_out(vty, + "! \"log file %s %s\" enabled by \"--log\" startup option\n", + zt_file_cmdline.filename, + zlog_priority[zt_file_cmdline.prio_min]); + show_cmdline_hint = true; + } + if (show_cmdline_hint) + vty_out(vty, + "! use \"clear log cmdline-targets\" to remove this target\n"); + + if (zlog_syslog_get_facility() != LOG_DAEMON) + vty_out(vty, "log facility %s\n", + facility_name(zlog_syslog_get_facility())); + + if (zt_file.record_priority == 1) + vty_out(vty, "log record-priority\n"); + + if (zt_file.ts_subsec > 0) + vty_out(vty, "log timestamp precision %d\n", + zt_file.ts_subsec); + + if (!zlog_get_prefix_ec()) + vty_out(vty, "no log error-category\n"); + if (!zlog_get_prefix_xid()) + vty_out(vty, "no log unique-id\n"); + if (zlog_get_immediate_mode()) + vty_out(vty, "log immediate-mode\n"); + + if (logmsgs_with_persist_bt) { + struct xrefdata *xrd; + struct xrefdata_logmsg *xrdl; + + vty_out(vty, "!\n"); + + frr_each (xrefdata_uid, &xrefdata_uid, xrd) { + if (xrd->xref->type != XREFT_LOGMSG) + continue; + + xrdl = container_of(xrd, struct xrefdata_logmsg, + xrefdata); + if (xrdl->fl_print_bt & LOGMSG_FLAG_PERSISTENT) + vty_out(vty, "debug unique-id %s backtrace\n", + xrd->uid); + } + } +} + +static int log_vty_fini(void) +{ + if (zt_file_cmdline.filename) + zlog_file_fini(&zt_file_cmdline); + if (zt_file.filename) + zlog_file_fini(&zt_file); + return 0; +} + + +static int log_vty_init(const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid) +{ + zlog_progname = progname; + zlog_protoname = protoname; + + hook_register(zlog_fini, log_vty_fini); + + zlog_set_prefix_ec(true); + zlog_set_prefix_xid(true); + + zlog_filterfile_init(&zt_filterfile); + + if (sd_stdout_is_journal) { + stdout_journald_in_use = true; + zlog_5424_init(&zt_stdout_journald); + zlog_5424_apply_dst(&zt_stdout_journald); + } else + zlog_file_set_fd(&zt_stdout_file, STDOUT_FILENO); + return 0; +} + +__attribute__((_CONSTRUCTOR(475))) static void log_vty_preinit(void) +{ + hook_register(zlog_init, log_vty_init); +} + +void log_cmd_init(void) +{ + install_element(VIEW_NODE, &show_logging_cmd); + install_element(ENABLE_NODE, &clear_log_cmdline_cmd); + + install_element(CONFIG_NODE, &config_log_stdout_cmd); + install_element(CONFIG_NODE, &no_config_log_stdout_cmd); + install_element(CONFIG_NODE, &config_log_monitor_cmd); + install_element(CONFIG_NODE, &no_config_log_monitor_cmd); + install_element(CONFIG_NODE, &config_log_file_cmd); + install_element(CONFIG_NODE, &config_log_dmn_file_cmd); + install_element(CONFIG_NODE, &no_config_log_dmn_file_cmd); + install_element(CONFIG_NODE, &no_config_log_file_cmd); + install_element(CONFIG_NODE, &config_log_syslog_cmd); + install_element(CONFIG_NODE, &no_config_log_syslog_cmd); + install_element(CONFIG_NODE, &config_log_facility_cmd); + install_element(CONFIG_NODE, &no_config_log_facility_cmd); + install_element(CONFIG_NODE, &config_log_record_priority_cmd); + install_element(CONFIG_NODE, &no_config_log_record_priority_cmd); + install_element(CONFIG_NODE, &config_log_timestamp_precision_cmd); + install_element(CONFIG_NODE, &no_config_log_timestamp_precision_cmd); + install_element(CONFIG_NODE, &config_log_ec_cmd); + install_element(CONFIG_NODE, &config_log_xid_cmd); + + install_element(VIEW_NODE, &show_log_filter_cmd); + install_element(CONFIG_NODE, &log_filter_cmd); + install_element(CONFIG_NODE, &log_filter_clear_cmd); + install_element(CONFIG_NODE, &config_log_filterfile_cmd); + install_element(CONFIG_NODE, &no_config_log_filterfile_cmd); + install_element(CONFIG_NODE, &log_immediate_mode_cmd); + + install_element(ENABLE_NODE, &debug_uid_backtrace_cmd); + install_element(CONFIG_NODE, &debug_uid_backtrace_cmd); + + log_5424_cmd_init(); +} diff --git a/lib/log_vty.h b/lib/log_vty.h new file mode 100644 index 0000000..8450cd0 --- /dev/null +++ b/lib/log_vty.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Logging - VTY library + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ + +#ifndef __LOG_VTY_H__ +#define __LOG_VTY_H__ + +#include "lib/hook.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct vty; + +extern void log_cmd_init(void); +extern void log_config_write(struct vty *vty); +extern int log_level_match(const char *s); +extern void log_show_syslog(struct vty *vty); + +extern int facility_match(const char *str); +extern const char *facility_name(int facility); + +DECLARE_HOOK(zlog_rotate, (), ()); +extern void zlog_rotate(void); + +DECLARE_HOOK(zlog_cli_show, (struct vty * vty), (vty)); + +#ifdef __cplusplus +} +#endif + +#endif /* __LOG_VTY_H__ */ diff --git a/lib/md5.c b/lib/md5.c new file mode 100644 index 0000000..a62d937 --- /dev/null +++ b/lib/md5.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (C) 2004 6WIND + * + * All rights reserved. + * + * This MD5 code is Big endian and Little Endian compatible. + */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + */ + +#include +#include "md5.h" + +#define SHIFT(X, s) (((X) << (s)) | ((X) >> (32 - (s)))) + +#define F(X, Y, Z) (((X) & (Y)) | ((~X) & (Z))) +#define G(X, Y, Z) (((X) & (Z)) | ((Y) & (~Z))) +#define H(X, Y, Z) ((X) ^ (Y) ^ (Z)) +#define I(X, Y, Z) ((Y) ^ ((X) | (~Z))) + +#define ROUND1(a, b, c, d, k, s, i) \ + { \ + (a) = (a) + F((b), (c), (d)) + X[(k)] + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (b) + (a); \ + } + +#define ROUND2(a, b, c, d, k, s, i) \ + { \ + (a) = (a) + G((b), (c), (d)) + X[(k)] + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (b) + (a); \ + } + +#define ROUND3(a, b, c, d, k, s, i) \ + { \ + (a) = (a) + H((b), (c), (d)) + X[(k)] + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (b) + (a); \ + } + +#define ROUND4(a, b, c, d, k, s, i) \ + { \ + (a) = (a) + I((b), (c), (d)) + X[(k)] + T[(i)]; \ + (a) = SHIFT((a), (s)); \ + (a) = (b) + (a); \ + } + +#define Sa 7 +#define Sb 12 +#define Sc 17 +#define Sd 22 + +#define Se 5 +#define Sf 9 +#define Sg 14 +#define Sh 20 + +#define Si 4 +#define Sj 11 +#define Sk 16 +#define Sl 23 + +#define Sm 6 +#define Sn 10 +#define So 15 +#define Sp 21 + +#define MD5_A0 0x67452301 +#define MD5_B0 0xefcdab89 +#define MD5_C0 0x98badcfe +#define MD5_D0 0x10325476 + +/* Integer part of 4294967296 times abs(sin(i)), where i is in radians. */ +static const uint32_t T[65] = { + 0, 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, + 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, + 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x2441453, + 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, + 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, + 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, +}; + +static const uint8_t md5_paddat[MD5_BUFLEN] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void md5_calc(const uint8_t *, md5_ctxt *); + +void md5_init(md5_ctxt *ctxt) +{ + ctxt->md5_n = 0; + ctxt->md5_i = 0; + ctxt->md5_sta = MD5_A0; + ctxt->md5_stb = MD5_B0; + ctxt->md5_stc = MD5_C0; + ctxt->md5_std = MD5_D0; + memset(ctxt->md5_buf, 0, sizeof(ctxt->md5_buf)); +} + +void md5_loop(md5_ctxt *ctxt, const void *vinput, uint len) +{ + uint gap, i; + const uint8_t *input = vinput; + + ctxt->md5_n += len * 8; /* byte to bit */ + gap = MD5_BUFLEN - ctxt->md5_i; + + if (len >= gap) { + memcpy(ctxt->md5_buf + ctxt->md5_i, input, gap); + md5_calc(ctxt->md5_buf, ctxt); + + for (i = gap; i + MD5_BUFLEN <= len; i += MD5_BUFLEN) { + md5_calc((input + i), ctxt); + } + + ctxt->md5_i = len - i; + memcpy(ctxt->md5_buf, (input + i), ctxt->md5_i); + } else { + memcpy(ctxt->md5_buf + ctxt->md5_i, input, len); + ctxt->md5_i += len; + } +} + +void md5_pad(md5_ctxt *ctxt) +{ + uint gap; + + /* Don't count up padding. Keep md5_n. */ + gap = MD5_BUFLEN - ctxt->md5_i; + if (gap > 8) { + memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat, + gap - sizeof(ctxt->md5_n)); + } else { + /* including gap == 8 */ + memcpy(ctxt->md5_buf + ctxt->md5_i, md5_paddat, gap); + md5_calc(ctxt->md5_buf, ctxt); + memcpy(ctxt->md5_buf, md5_paddat + gap, + MD5_BUFLEN - sizeof(ctxt->md5_n)); + } + + /* 8 byte word */ + if (BYTE_ORDER == LITTLE_ENDIAN) + memcpy(&ctxt->md5_buf[56], &ctxt->md5_n8[0], 8); + else { + ctxt->md5_buf[56] = ctxt->md5_n8[7]; + ctxt->md5_buf[57] = ctxt->md5_n8[6]; + ctxt->md5_buf[58] = ctxt->md5_n8[5]; + ctxt->md5_buf[59] = ctxt->md5_n8[4]; + ctxt->md5_buf[60] = ctxt->md5_n8[3]; + ctxt->md5_buf[61] = ctxt->md5_n8[2]; + ctxt->md5_buf[62] = ctxt->md5_n8[1]; + ctxt->md5_buf[63] = ctxt->md5_n8[0]; + } + md5_calc(ctxt->md5_buf, ctxt); +} + +void md5_result(uint8_t *digest, md5_ctxt *ctxt) +{ + /* 4 byte words */ + if (BYTE_ORDER == LITTLE_ENDIAN) + memcpy(digest, &ctxt->md5_st8[0], 16); + else if (BYTE_ORDER == BIG_ENDIAN) { + digest[0] = ctxt->md5_st8[3]; + digest[1] = ctxt->md5_st8[2]; + digest[2] = ctxt->md5_st8[1]; + digest[3] = ctxt->md5_st8[0]; + digest[4] = ctxt->md5_st8[7]; + digest[5] = ctxt->md5_st8[6]; + digest[6] = ctxt->md5_st8[5]; + digest[7] = ctxt->md5_st8[4]; + digest[8] = ctxt->md5_st8[11]; + digest[9] = ctxt->md5_st8[10]; + digest[10] = ctxt->md5_st8[9]; + digest[11] = ctxt->md5_st8[8]; + digest[12] = ctxt->md5_st8[15]; + digest[13] = ctxt->md5_st8[14]; + digest[14] = ctxt->md5_st8[13]; + digest[15] = ctxt->md5_st8[12]; + } +} + +static void md5_calc(const uint8_t *b64, md5_ctxt *ctxt) +{ + uint32_t A = ctxt->md5_sta; + uint32_t B = ctxt->md5_stb; + uint32_t C = ctxt->md5_stc; + uint32_t D = ctxt->md5_std; +#if (BYTE_ORDER == LITTLE_ENDIAN) + const uint32_t *X = (const uint32_t *)b64; +#elif (BYTE_ORDER == BIG_ENDIAN) + uint32_t X[16]; + + if (BYTE_ORDER == BIG_ENDIAN) { + /* 4 byte words */ + /* what a brute force but fast! */ + uint8_t *y = (uint8_t *)X; + y[0] = b64[3]; + y[1] = b64[2]; + y[2] = b64[1]; + y[3] = b64[0]; + y[4] = b64[7]; + y[5] = b64[6]; + y[6] = b64[5]; + y[7] = b64[4]; + y[8] = b64[11]; + y[9] = b64[10]; + y[10] = b64[9]; + y[11] = b64[8]; + y[12] = b64[15]; + y[13] = b64[14]; + y[14] = b64[13]; + y[15] = b64[12]; + y[16] = b64[19]; + y[17] = b64[18]; + y[18] = b64[17]; + y[19] = b64[16]; + y[20] = b64[23]; + y[21] = b64[22]; + y[22] = b64[21]; + y[23] = b64[20]; + y[24] = b64[27]; + y[25] = b64[26]; + y[26] = b64[25]; + y[27] = b64[24]; + y[28] = b64[31]; + y[29] = b64[30]; + y[30] = b64[29]; + y[31] = b64[28]; + y[32] = b64[35]; + y[33] = b64[34]; + y[34] = b64[33]; + y[35] = b64[32]; + y[36] = b64[39]; + y[37] = b64[38]; + y[38] = b64[37]; + y[39] = b64[36]; + y[40] = b64[43]; + y[41] = b64[42]; + y[42] = b64[41]; + y[43] = b64[40]; + y[44] = b64[47]; + y[45] = b64[46]; + y[46] = b64[45]; + y[47] = b64[44]; + y[48] = b64[51]; + y[49] = b64[50]; + y[50] = b64[49]; + y[51] = b64[48]; + y[52] = b64[55]; + y[53] = b64[54]; + y[54] = b64[53]; + y[55] = b64[52]; + y[56] = b64[59]; + y[57] = b64[58]; + y[58] = b64[57]; + y[59] = b64[56]; + y[60] = b64[63]; + y[61] = b64[62]; + y[62] = b64[61]; + y[63] = b64[60]; + } +#endif + + ROUND1(A, B, C, D, 0, Sa, 1); + ROUND1(D, A, B, C, 1, Sb, 2); + ROUND1(C, D, A, B, 2, Sc, 3); + ROUND1(B, C, D, A, 3, Sd, 4); + ROUND1(A, B, C, D, 4, Sa, 5); + ROUND1(D, A, B, C, 5, Sb, 6); + ROUND1(C, D, A, B, 6, Sc, 7); + ROUND1(B, C, D, A, 7, Sd, 8); + ROUND1(A, B, C, D, 8, Sa, 9); + ROUND1(D, A, B, C, 9, Sb, 10); + ROUND1(C, D, A, B, 10, Sc, 11); + ROUND1(B, C, D, A, 11, Sd, 12); + ROUND1(A, B, C, D, 12, Sa, 13); + ROUND1(D, A, B, C, 13, Sb, 14); + ROUND1(C, D, A, B, 14, Sc, 15); + ROUND1(B, C, D, A, 15, Sd, 16); + + ROUND2(A, B, C, D, 1, Se, 17); + ROUND2(D, A, B, C, 6, Sf, 18); + ROUND2(C, D, A, B, 11, Sg, 19); + ROUND2(B, C, D, A, 0, Sh, 20); + ROUND2(A, B, C, D, 5, Se, 21); + ROUND2(D, A, B, C, 10, Sf, 22); + ROUND2(C, D, A, B, 15, Sg, 23); + ROUND2(B, C, D, A, 4, Sh, 24); + ROUND2(A, B, C, D, 9, Se, 25); + ROUND2(D, A, B, C, 14, Sf, 26); + ROUND2(C, D, A, B, 3, Sg, 27); + ROUND2(B, C, D, A, 8, Sh, 28); + ROUND2(A, B, C, D, 13, Se, 29); + ROUND2(D, A, B, C, 2, Sf, 30); + ROUND2(C, D, A, B, 7, Sg, 31); + ROUND2(B, C, D, A, 12, Sh, 32); + + ROUND3(A, B, C, D, 5, Si, 33); + ROUND3(D, A, B, C, 8, Sj, 34); + ROUND3(C, D, A, B, 11, Sk, 35); + ROUND3(B, C, D, A, 14, Sl, 36); + ROUND3(A, B, C, D, 1, Si, 37); + ROUND3(D, A, B, C, 4, Sj, 38); + ROUND3(C, D, A, B, 7, Sk, 39); + ROUND3(B, C, D, A, 10, Sl, 40); + ROUND3(A, B, C, D, 13, Si, 41); + ROUND3(D, A, B, C, 0, Sj, 42); + ROUND3(C, D, A, B, 3, Sk, 43); + ROUND3(B, C, D, A, 6, Sl, 44); + ROUND3(A, B, C, D, 9, Si, 45); + ROUND3(D, A, B, C, 12, Sj, 46); + ROUND3(C, D, A, B, 15, Sk, 47); + ROUND3(B, C, D, A, 2, Sl, 48); + + ROUND4(A, B, C, D, 0, Sm, 49); + ROUND4(D, A, B, C, 7, Sn, 50); + ROUND4(C, D, A, B, 14, So, 51); + ROUND4(B, C, D, A, 5, Sp, 52); + ROUND4(A, B, C, D, 12, Sm, 53); + ROUND4(D, A, B, C, 3, Sn, 54); + ROUND4(C, D, A, B, 10, So, 55); + ROUND4(B, C, D, A, 1, Sp, 56); + ROUND4(A, B, C, D, 8, Sm, 57); + ROUND4(D, A, B, C, 15, Sn, 58); + ROUND4(C, D, A, B, 6, So, 59); + ROUND4(B, C, D, A, 13, Sp, 60); + ROUND4(A, B, C, D, 4, Sm, 61); + ROUND4(D, A, B, C, 11, Sn, 62); + ROUND4(C, D, A, B, 2, So, 63); + ROUND4(B, C, D, A, 9, Sp, 64); + + ctxt->md5_sta += A; + ctxt->md5_stb += B; + ctxt->md5_stc += C; + ctxt->md5_std += D; +} + +/* From RFC 2104 */ +void hmac_md5(unsigned char *text, int text_len, unsigned char *key, + int key_len, uint8_t *digest) +{ + MD5_CTX context; + unsigned char k_ipad[65]; /* inner padding - + * key XORd with ipad + */ + unsigned char k_opad[65]; /* outer padding - + * key XORd with opad + */ + unsigned char tk[16]; + int i; + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) { + + MD5_CTX tctx; + + MD5Init(&tctx); + MD5Update(&tctx, key, key_len); + MD5Final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero(k_ipad, sizeof(k_ipad)); + bzero(k_opad, sizeof(k_opad)); + bcopy(key, k_ipad, key_len); + bcopy(key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ + MD5Init(&context); /* init context for 1st + * pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, text, text_len); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ + /* + * perform outer MD5 + */ + MD5Init(&context); /* init context for 2nd + * pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st + * hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ + explicit_bzero(&context, sizeof(context)); +} diff --git a/lib/md5.h b/lib/md5.h new file mode 100644 index 0000000..b22da47 --- /dev/null +++ b/lib/md5.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (C) 2004 6WIND + * + * All rights reserved. + * + * This MD5 code is Big endian and Little Endian compatible. + */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + */ + +#ifndef _LIBZEBRA_MD5_H_ +#define _LIBZEBRA_MD5_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define MD5_BUFLEN 64 + +typedef struct { + union { + uint32_t md5_state32[4]; + uint8_t md5_state8[16]; + } md5_st; + +#define md5_sta md5_st.md5_state32[0] +#define md5_stb md5_st.md5_state32[1] +#define md5_stc md5_st.md5_state32[2] +#define md5_std md5_st.md5_state32[3] +#define md5_st8 md5_st.md5_state8 + + union { + uint64_t md5_count64; + uint8_t md5_count8[8]; + } md5_count; +#define md5_n md5_count.md5_count64 +#define md5_n8 md5_count.md5_count8 + + uint md5_i; + uint8_t md5_buf[MD5_BUFLEN]; +} md5_ctxt; + +extern void md5_init(md5_ctxt *); +extern void md5_loop(md5_ctxt *, const void *, unsigned int); +extern void md5_pad(md5_ctxt *); +extern void md5_result(uint8_t *, md5_ctxt *); + +/* compatibility */ +#define MD5_CTX md5_ctxt +#define MD5Init(x) md5_init((x)) +#define MD5Update(x, y, z) md5_loop((x), (y), (z)) +#define MD5Final(x, y) \ + do { \ + md5_pad((y)); \ + md5_result((x), (y)); \ + } while (0) + +/* From RFC 2104 */ +void hmac_md5(unsigned char *text, int text_len, unsigned char *key, + int key_len, uint8_t *digest); + +#ifdef __cplusplus +} +#endif + +#endif /* ! _LIBZEBRA_MD5_H_*/ diff --git a/lib/memory.c b/lib/memory.c new file mode 100644 index 0000000..8fbe5c4 --- /dev/null +++ b/lib/memory.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#include + +#include +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_MALLOC_NP_H +#include +#endif +#ifdef HAVE_MALLOC_MALLOC_H +#include +#endif + +#include "memory.h" +#include "log.h" +#include "libfrr_trace.h" + +static struct memgroup *mg_first = NULL; +struct memgroup **mg_insert = &mg_first; + +DEFINE_MGROUP(LIB, "libfrr"); +DEFINE_MTYPE(LIB, TMP, "Temporary memory"); +DEFINE_MTYPE(LIB, BITFIELD, "Bitfield memory"); + +static inline void mt_count_alloc(struct memtype *mt, size_t size, void *ptr) +{ + size_t current; + size_t oldsize; + + current = 1 + atomic_fetch_add_explicit(&mt->n_alloc, 1, + memory_order_relaxed); + + oldsize = atomic_load_explicit(&mt->n_max, memory_order_relaxed); + if (current > oldsize) + /* note that this may fail, but approximation is sufficient */ + atomic_compare_exchange_weak_explicit(&mt->n_max, &oldsize, + current, + memory_order_relaxed, + memory_order_relaxed); + + oldsize = atomic_load_explicit(&mt->size, memory_order_relaxed); + if (oldsize == 0) + oldsize = atomic_exchange_explicit(&mt->size, size, + memory_order_relaxed); + if (oldsize != 0 && oldsize != size && oldsize != SIZE_VAR) + atomic_store_explicit(&mt->size, SIZE_VAR, + memory_order_relaxed); + +#ifdef HAVE_MALLOC_USABLE_SIZE + size_t mallocsz = malloc_usable_size(ptr); + + current = mallocsz + atomic_fetch_add_explicit(&mt->total, mallocsz, + memory_order_relaxed); + oldsize = atomic_load_explicit(&mt->max_size, memory_order_relaxed); + if (current > oldsize) + /* note that this may fail, but approximation is sufficient */ + atomic_compare_exchange_weak_explicit(&mt->max_size, &oldsize, + current, + memory_order_relaxed, + memory_order_relaxed); +#endif +} + +static inline void mt_count_free(struct memtype *mt, void *ptr) +{ + frrtrace(2, frr_libfrr, memfree, mt, ptr); + + assert(mt->n_alloc); + atomic_fetch_sub_explicit(&mt->n_alloc, 1, memory_order_relaxed); + +#ifdef HAVE_MALLOC_USABLE_SIZE + size_t mallocsz = malloc_usable_size(ptr); + + atomic_fetch_sub_explicit(&mt->total, mallocsz, memory_order_relaxed); +#endif +} + +static inline void *mt_checkalloc(struct memtype *mt, void *ptr, size_t size) +{ + frrtrace(3, frr_libfrr, memalloc, mt, ptr, size); + + if (__builtin_expect(ptr == NULL, 0)) { + if (size) { + /* malloc(0) is allowed to return NULL */ + memory_oom(size, mt->name); + } + return NULL; + } + mt_count_alloc(mt, size, ptr); + return ptr; +} + +void *qmalloc(struct memtype *mt, size_t size) +{ + return mt_checkalloc(mt, malloc(size), size); +} + +void *qcalloc(struct memtype *mt, size_t size) +{ + return mt_checkalloc(mt, calloc(size, 1), size); +} + +void *qrealloc(struct memtype *mt, void *ptr, size_t size) +{ + if (ptr) + mt_count_free(mt, ptr); + return mt_checkalloc(mt, ptr ? realloc(ptr, size) : malloc(size), size); +} + +void *qstrdup(struct memtype *mt, const char *str) +{ + return str ? mt_checkalloc(mt, strdup(str), strlen(str) + 1) : NULL; +} + +void qcountfree(struct memtype *mt, void *ptr) +{ + if (ptr) + mt_count_free(mt, ptr); +} + +void qfree(struct memtype *mt, void *ptr) +{ + if (ptr) + mt_count_free(mt, ptr); + free(ptr); +} + +int qmem_walk(qmem_walk_fn *func, void *arg) +{ + struct memgroup *mg; + struct memtype *mt; + int rv; + + for (mg = mg_first; mg; mg = mg->next) { + if ((rv = func(arg, mg, NULL))) + return rv; + for (mt = mg->types; mt; mt = mt->next) + if ((rv = func(arg, mg, mt))) + return rv; + } + return 0; +} + +struct exit_dump_args { + FILE *fp; + const char *prefix; + int error; +}; + +static int qmem_exit_walker(void *arg, struct memgroup *mg, struct memtype *mt) +{ + struct exit_dump_args *eda = arg; + + if (!mt) { + fprintf(eda->fp, + "%s: showing active allocations in memory group %s\n", + eda->prefix, mg->name); + + } else if (mt->n_alloc) { + char size[32]; + if (!mg->active_at_exit) + eda->error++; + snprintf(size, sizeof(size), "%10zu", mt->size); + fprintf(eda->fp, "%s: memstats: %-30s: %6zu * %s\n", + eda->prefix, mt->name, mt->n_alloc, + mt->size == SIZE_VAR ? "(variably sized)" : size); + } + return 0; +} + +int log_memstats(FILE *fp, const char *prefix) +{ + struct exit_dump_args eda = {.fp = fp, .prefix = prefix, .error = 0}; + qmem_walk(qmem_exit_walker, &eda); + return eda.error; +} diff --git a/lib/memory.h b/lib/memory.h new file mode 100644 index 0000000..65b99a5 --- /dev/null +++ b/lib/memory.h @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _QUAGGA_MEMORY_H +#define _QUAGGA_MEMORY_H + +#include +#include +#include +#include +#include "compiler.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(HAVE_MALLOC_SIZE) && !defined(HAVE_MALLOC_USABLE_SIZE) +#define malloc_usable_size(x) malloc_size(x) +#define HAVE_MALLOC_USABLE_SIZE +#endif + +#define SIZE_VAR ~0UL +struct memtype { + struct memtype *next, **ref; + const char *name; + atomic_size_t n_alloc; + atomic_size_t n_max; + atomic_size_t size; +#ifdef HAVE_MALLOC_USABLE_SIZE + atomic_size_t total; + atomic_size_t max_size; +#endif +}; + +struct memgroup { + struct memgroup *next, **ref; + struct memtype *types, **insert; + const char *name; + /* ignore group on dumping memleaks at exit */ + bool active_at_exit; +}; + +/* macro usage: + * + * mydaemon.h + * DECLARE_MGROUP(MYDAEMON); + * DECLARE_MTYPE(MYDAEMON_COMMON); + * + * mydaemon.c + * DEFINE_MGROUP(MYDAEMON, "my daemon memory"); + * DEFINE_MTYPE(MYDAEMON, MYDAEMON_COMMON, + * "this mtype is used in multiple files in mydaemon"); + * foo = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*foo)) + * + * mydaemon_io.c + * bar = qmalloc(MTYPE_MYDAEMON_COMMON, sizeof(*bar)) + * + * DEFINE_MTYPE_STATIC(MYDAEMON, MYDAEMON_IO, + * "this mtype is used only in this file"); + * baz = qmalloc(MTYPE_MYDAEMON_IO, sizeof(*baz)) + * + * Note: Naming conventions (MGROUP_ and MTYPE_ prefixes are enforced + * by not having these as part of the macro arguments) + * Note: MTYPE_* are symbols to the compiler (of type struct memtype *), + * but MGROUP_* aren't. + */ + +#define DECLARE_MGROUP(name) extern struct memgroup _mg_##name +#define _DEFINE_MGROUP(mname, desc, ...) \ + struct memgroup _mg_##mname _DATA_SECTION("mgroups") = { \ + .name = desc, \ + .types = NULL, \ + .next = NULL, \ + .insert = NULL, \ + .ref = NULL, \ + }; \ + static void _mginit_##mname(void) __attribute__((_CONSTRUCTOR(1000))); \ + static void _mginit_##mname(void) \ + { \ + extern struct memgroup **mg_insert; \ + _mg_##mname.ref = mg_insert; \ + *mg_insert = &_mg_##mname; \ + mg_insert = &_mg_##mname.next; \ + } \ + static void _mgfini_##mname(void) __attribute__((_DESTRUCTOR(1000))); \ + static void _mgfini_##mname(void) \ + { \ + if (_mg_##mname.next) \ + _mg_##mname.next->ref = _mg_##mname.ref; \ + *_mg_##mname.ref = _mg_##mname.next; \ + } \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DEFINE_MGROUP(mname, desc) \ + _DEFINE_MGROUP(mname, desc, ) +#define DEFINE_MGROUP_ACTIVEATEXIT(mname, desc) \ + _DEFINE_MGROUP(mname, desc, .active_at_exit = true) + +#define DECLARE_MTYPE(name) \ + extern struct memtype MTYPE_##name[1] \ + /* end */ + +#define DEFINE_MTYPE_ATTR(group, mname, attr, desc) \ + attr struct memtype MTYPE_##mname[1] _DATA_SECTION("mtypes") = { { \ + .name = desc, \ + .next = NULL, \ + .n_alloc = 0, \ + .size = 0, \ + .ref = NULL, \ + } }; \ + static void _mtinit_##mname(void) __attribute__((_CONSTRUCTOR(1001))); \ + static void _mtinit_##mname(void) \ + { \ + if (_mg_##group.insert == NULL) \ + _mg_##group.insert = &_mg_##group.types; \ + MTYPE_##mname->ref = _mg_##group.insert; \ + *_mg_##group.insert = MTYPE_##mname; \ + _mg_##group.insert = &MTYPE_##mname->next; \ + } \ + static void _mtfini_##mname(void) __attribute__((_DESTRUCTOR(1001))); \ + static void _mtfini_##mname(void) \ + { \ + if (MTYPE_##mname->next) \ + MTYPE_##mname->next->ref = MTYPE_##mname->ref; \ + *MTYPE_##mname->ref = MTYPE_##mname->next; \ + } \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DEFINE_MTYPE(group, name, desc) \ + DEFINE_MTYPE_ATTR(group, name, , desc) \ + /* end */ + +#define DEFINE_MTYPE_STATIC(group, name, desc) \ + DEFINE_MTYPE_ATTR(group, name, static, desc) \ + /* end */ + +DECLARE_MGROUP(LIB); +DECLARE_MTYPE(TMP); + + +extern void *qmalloc(struct memtype *mt, size_t size) + __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL)); +extern void *qcalloc(struct memtype *mt, size_t size) + __attribute__((malloc, _ALLOC_SIZE(2), nonnull(1) _RET_NONNULL)); +extern void *qrealloc(struct memtype *mt, void *ptr, size_t size) + __attribute__((_ALLOC_SIZE(3), nonnull(1) _RET_NONNULL)); +extern void *qstrdup(struct memtype *mt, const char *str) + __attribute__((malloc, nonnull(1) _RET_NONNULL)); +extern void qcountfree(struct memtype *mt, void *ptr) + __attribute__((nonnull(1))); +extern void qfree(struct memtype *mt, void *ptr) __attribute__((nonnull(1))); + +#define XMALLOC(mtype, size) qmalloc(mtype, size) +#define XCALLOC(mtype, size) qcalloc(mtype, size) +#define XREALLOC(mtype, ptr, size) qrealloc(mtype, ptr, size) +#define XSTRDUP(mtype, str) qstrdup(mtype, str) +#define XCOUNTFREE(mtype, ptr) qcountfree(mtype, ptr) +#define XFREE(mtype, ptr) \ + do { \ + qfree(mtype, ptr); \ + ptr = NULL; \ + } while (0) + +static inline size_t mtype_stats_alloc(struct memtype *mt) +{ + return mt->n_alloc; +} + +/* NB: calls are ordered by memgroup; and there is a call with mt == NULL for + * each memgroup (so that a header can be printed, and empty memgroups show) + * + * return value: 0: continue, !0: abort walk. qmem_walk will return the + * last value from qmem_walk_fn. */ +typedef int qmem_walk_fn(void *arg, struct memgroup *mg, struct memtype *mt); +extern int qmem_walk(qmem_walk_fn *func, void *arg); +extern int log_memstats(FILE *fp, const char *); +#define log_memstats_stderr(prefix) log_memstats(stderr, prefix) + +extern __attribute__((__noreturn__)) void memory_oom(size_t size, + const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_MEMORY_H */ diff --git a/lib/mgmt.proto b/lib/mgmt.proto new file mode 100644 index 0000000..c953011 --- /dev/null +++ b/lib/mgmt.proto @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: ISC +// +// mgmt.proto +// +// @copyright Copyright (C) 2021 Vmware, Inc. +// +// @author Pushpasis Sarkar +// + +syntax = "proto2"; + +// +// Protobuf definitions pertaining to the MGMTD component. +// + +package mgmtd; + +// +// Common Sub-Messages +// + +message YangDataXPath { + required string xpath = 1; +} + +message YangDataValue { + oneof value { + // + // NOTE: For now let's use stringized value ONLY. + // We will enhance it later to pass native-format + // if needed. + // + // bool bool_val = 2; + // double double_val = 3; + // float float_val = 4; + // string string_val = 5; + // bytes bytes_val = 6; + // int32 int32_val = 7; + // int64 int64_val = 8; + // uint32 uint32_val = 9; + // uint64 uint64_val = 10; + // int32 int8_val = 11; + // uint32 uint8_val = 12; + // int32 int16_val = 13; + // uint32 uint16_val = 14; + string encoded_str_val = 100; + } +} + +message YangData { + required string xpath = 1; + optional YangDataValue value = 2; +} + +enum CfgDataReqType { + REQ_TYPE_NONE = 0; + SET_DATA = 1; + REMOVE_DATA = 2; + CREATE_DATA = 3; + DELETE_DATA = 4; + REPLACE_DATA = 5; +} + +message YangCfgDataReq { + required YangData data = 1; + required CfgDataReqType req_type = 2; +} + +message YangGetDataReq { + required YangData data = 1; + required int64 next_indx = 2; +} + +// +// Backend Interface Messages +// +message BeSubscribeReq { + required string client_name = 1; + repeated string config_xpaths = 2; + repeated string oper_xpaths = 3; + repeated string notif_xpaths = 4; + repeated string rpc_xpaths = 5; +} + +message BeSubscribeReply { + required bool success = 1; +} + +message BeTxnReq { + required uint64 txn_id = 1; + required bool create = 2; +} + +message BeTxnReply { + required uint64 txn_id = 1; + required bool create = 2; + required bool success = 3; +} + +message BeCfgDataCreateReq { + required uint64 txn_id = 1; + repeated YangCfgDataReq data_req = 2; + required bool end_of_data = 3; +} + +message BeCfgDataCreateReply { + required uint64 txn_id = 1; + required bool success = 2; + optional string error_if_any = 3; +} + +message BeCfgDataApplyReq { + required uint64 txn_id = 1; +} + +message BeCfgDataApplyReply { + required uint64 txn_id = 1; + required bool success = 2; + optional string error_if_any = 3; +} + +message YangDataReply { + repeated YangData data = 1; + required int64 next_indx = 2; +} + +// +// Any message on the MGMTD Backend Interface. +// +message BeMessage { + oneof message { + BeSubscribeReq subscr_req = 2; + BeSubscribeReply subscr_reply = 3; + BeTxnReq txn_req = 4; + BeTxnReply txn_reply = 5; + BeCfgDataCreateReq cfg_data_req = 6; + BeCfgDataCreateReply cfg_data_reply = 7; + BeCfgDataApplyReq cfg_apply_req = 8; + BeCfgDataApplyReply cfg_apply_reply = 9; + } +} + + +// +// Frontend Interface Messages +// + +message FeRegisterReq { + required string client_name = 1; +} + +message FeSessionReq { + required bool create = 1; + oneof id { + uint64 client_conn_id = 2; // Applicable for create request only + uint64 session_id = 3; // Applicable for delete request only + } +} + +message FeSessionReply { + required bool create = 1; + required bool success = 2; + optional uint64 client_conn_id = 3; // Applicable for create request only + required uint64 session_id = 4; +} + +enum DatastoreId { + DS_NONE = 0; + RUNNING_DS = 1; + CANDIDATE_DS = 2; + OPERATIONAL_DS = 3; + STARTUP_DS = 4; +} + +message FeLockDsReq { + required uint64 session_id = 1; + required uint64 req_id = 2; + required DatastoreId ds_id = 3; + required bool lock = 4; +} + +message FeLockDsReply { + required uint64 session_id = 1; + required uint64 req_id = 2; + required DatastoreId ds_id = 3; + required bool lock = 4; + required bool success = 5; + optional string error_if_any = 6; +} + +message FeSetConfigReq { + required uint64 session_id = 1; + required DatastoreId ds_id = 2; + required uint64 req_id = 3; + repeated YangCfgDataReq data = 4; + required bool implicit_commit = 5; + required DatastoreId commit_ds_id = 6; +} + +message FeSetConfigReply { + required uint64 session_id = 1; + required DatastoreId ds_id = 2; + required uint64 req_id = 3; + required bool success = 4; + required bool implicit_commit = 5; + optional string error_if_any = 6; +} + +message FeCommitConfigReq { + required uint64 session_id = 1; + required DatastoreId src_ds_id = 2; + required DatastoreId dst_ds_id = 3; + required uint64 req_id = 4; + required bool validate_only = 5; + required bool abort = 6; +} + +message FeCommitConfigReply { + required uint64 session_id = 1; + required DatastoreId src_ds_id = 2; + required DatastoreId dst_ds_id = 3; + required uint64 req_id = 4; + required bool validate_only = 5; + required bool success = 6; + required bool abort = 7; + optional string error_if_any = 8; +} + +message FeGetReq { + required uint64 session_id = 1; + required bool config = 2; + required DatastoreId ds_id = 3; + required uint64 req_id = 4; + repeated YangGetDataReq data = 5; +} + +message FeGetReply { + required uint64 session_id = 1; + required bool config = 2; + required DatastoreId ds_id = 3; + required uint64 req_id = 4; + required bool success = 5; + optional string error_if_any = 6; + optional YangDataReply data = 7; +} + +message FeNotifyDataReq { + repeated YangData data = 1; +} + +message FeRegisterNotifyReq { + required uint64 session_id = 1; + required DatastoreId ds_id = 2; + required bool register_req = 3; + required uint64 req_id = 4; + repeated YangDataXPath data_xpath = 5; +} + +message FeMessage { + oneof message { + FeRegisterReq register_req = 2; + FeSessionReq session_req = 3; + FeSessionReply session_reply = 4; + FeLockDsReq lockds_req = 5; + FeLockDsReply lockds_reply = 6; + FeSetConfigReq setcfg_req = 7; + FeSetConfigReply setcfg_reply = 8; + FeCommitConfigReq commcfg_req = 9; + FeCommitConfigReply commcfg_reply = 10; + FeGetReq get_req = 11; + FeGetReply get_reply = 12; + FeNotifyDataReq notify_data_req = 15; + FeRegisterNotifyReq regnotify_req = 16; + } +} diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c new file mode 100644 index 0000000..6e2fb05 --- /dev/null +++ b/lib/mgmt_be_client.c @@ -0,0 +1,1352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Backend Client Library api interfaces + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "debug.h" +#include "compiler.h" +#include "darr.h" +#include "libfrr.h" +#include "lib_errors.h" +#include "mgmt_be_client.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" +#include "mgmt_pb.h" +#include "network.h" +#include "northbound.h" +#include "stream.h" +#include "sockopt.h" +#include "northbound_cli.h" + +#include "lib/mgmt_be_client_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT, "backend client"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_CLIENT_NAME, "backend client name"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_BATCH, "backend transaction batch data"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_TXN, "backend transaction data"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_BE_GT_CB_ARGS, "backend get-tree cb args"); + +enum mgmt_be_txn_event { + MGMTD_BE_TXN_PROC_SETCFG = 1, + MGMTD_BE_TXN_PROC_GETCFG, +}; + +struct mgmt_be_set_cfg_req { + struct nb_cfg_change cfg_changes[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + uint16_t num_cfg_changes; +}; + +struct mgmt_be_txn_req { + enum mgmt_be_txn_event event; + union { + struct mgmt_be_set_cfg_req set_cfg; + } req; +}; + +struct be_oper_iter_arg { + struct lyd_node *root; /* the tree we are building */ + struct lyd_node *hint; /* last node added */ +}; + +PREDECL_LIST(mgmt_be_batches); +struct mgmt_be_batch_ctx { + struct mgmt_be_txn_req txn_req; + + uint32_t flags; + + struct mgmt_be_batches_item list_linkage; +}; +#define MGMTD_BE_BATCH_FLAGS_CFG_PREPARED (1U << 0) +#define MGMTD_BE_TXN_FLAGS_CFG_APPLIED (1U << 1) +DECLARE_LIST(mgmt_be_batches, struct mgmt_be_batch_ctx, list_linkage); + +PREDECL_LIST(mgmt_be_txns); +struct mgmt_be_txn_ctx { + /* Txn-Id as assigned by MGMTD */ + uint64_t txn_id; + uint32_t flags; + + struct mgmt_be_client_txn_ctx client_data; + struct mgmt_be_client *client; + + /* List of batches belonging to this transaction */ + struct mgmt_be_batches_head cfg_batches; + struct mgmt_be_batches_head apply_cfgs; + + struct mgmt_be_txns_item list_linkage; + + struct nb_transaction *nb_txn; + uint32_t nb_txn_id; +}; +#define MGMTD_BE_TXN_FLAGS_CFGPREP_FAILED (1U << 1) + +DECLARE_LIST(mgmt_be_txns, struct mgmt_be_txn_ctx, list_linkage); + +#define FOREACH_BE_TXN_BATCH_IN_LIST(txn, batch) \ + frr_each_safe (mgmt_be_batches, &(txn)->cfg_batches, (batch)) + +#define FOREACH_BE_APPLY_BATCH_IN_LIST(txn, batch) \ + frr_each_safe (mgmt_be_batches, &(txn)->apply_cfgs, (batch)) + +struct mgmt_be_client { + struct msg_client client; + + char *name; + + struct nb_config *candidate_config; + struct nb_config *running_config; + + unsigned long num_edit_nb_cfg; + unsigned long avg_edit_nb_cfg_tm; + unsigned long num_prep_nb_cfg; + unsigned long avg_prep_nb_cfg_tm; + unsigned long num_apply_nb_cfg; + unsigned long avg_apply_nb_cfg_tm; + + struct mgmt_be_txns_head txn_head; + + struct mgmt_be_client_cbs cbs; + uintptr_t user_data; +}; + +#define FOREACH_BE_TXN_IN_LIST(client_ctx, txn) \ + frr_each_safe (mgmt_be_txns, &(client_ctx)->txn_head, (txn)) + +struct debug mgmt_dbg_be_client = { + .desc = "Management backend client operations" +}; + +/* NOTE: only one client per proc for now. */ +static struct mgmt_be_client *__be_client; + +static int be_client_send_native_msg(struct mgmt_be_client *client_ctx, + void *msg, size_t len, + bool short_circuit_ok) +{ + return msg_conn_send_msg(&client_ctx->client.conn, + MGMT_MSG_VERSION_NATIVE, msg, len, NULL, + short_circuit_ok); +} + +static int mgmt_be_client_send_msg(struct mgmt_be_client *client_ctx, + Mgmtd__BeMessage *be_msg) +{ + return msg_conn_send_msg( + &client_ctx->client.conn, MGMT_MSG_VERSION_PROTOBUF, be_msg, + mgmtd__be_message__get_packed_size(be_msg), + (size_t(*)(void *, void *))mgmtd__be_message__pack, false); +} + +static struct mgmt_be_batch_ctx * +mgmt_be_batch_create(struct mgmt_be_txn_ctx *txn) +{ + struct mgmt_be_batch_ctx *batch = NULL; + + batch = XCALLOC(MTYPE_MGMTD_BE_BATCH, sizeof(struct mgmt_be_batch_ctx)); + + mgmt_be_batches_add_tail(&txn->cfg_batches, batch); + + debug_be_client("Added new batch to transaction"); + + return batch; +} + +static void mgmt_be_batch_delete(struct mgmt_be_txn_ctx *txn, + struct mgmt_be_batch_ctx **batch) +{ + uint16_t indx; + + if (!batch) + return; + + mgmt_be_batches_del(&txn->cfg_batches, *batch); + if ((*batch)->txn_req.event == MGMTD_BE_TXN_PROC_SETCFG) { + for (indx = 0; indx < MGMTD_MAX_CFG_CHANGES_IN_BATCH; indx++) { + if ((*batch)->txn_req.req.set_cfg.cfg_changes[indx] + .value) { + free((char *)(*batch) + ->txn_req.req.set_cfg + .cfg_changes[indx] + .value); + } + } + } + + XFREE(MTYPE_MGMTD_BE_BATCH, *batch); + *batch = NULL; +} + +static void mgmt_be_cleanup_all_batches(struct mgmt_be_txn_ctx *txn) +{ + struct mgmt_be_batch_ctx *batch = NULL; + + FOREACH_BE_TXN_BATCH_IN_LIST (txn, batch) { + mgmt_be_batch_delete(txn, &batch); + } + + FOREACH_BE_APPLY_BATCH_IN_LIST (txn, batch) { + mgmt_be_batch_delete(txn, &batch); + } +} + +static struct mgmt_be_txn_ctx * +mgmt_be_find_txn_by_id(struct mgmt_be_client *client_ctx, uint64_t txn_id, + bool warn) +{ + struct mgmt_be_txn_ctx *txn = NULL; + + FOREACH_BE_TXN_IN_LIST (client_ctx, txn) + if (txn->txn_id == txn_id) + return txn; + if (warn) + log_err_be_client("client %s unkonwn txn-id: %" PRIu64, + client_ctx->name, txn_id); + + return NULL; +} + +static struct mgmt_be_txn_ctx * +mgmt_be_txn_create(struct mgmt_be_client *client_ctx, uint64_t txn_id) +{ + struct mgmt_be_txn_ctx *txn = NULL; + + txn = mgmt_be_find_txn_by_id(client_ctx, txn_id, false); + if (txn) { + log_err_be_client("Can't create existing txn-id: %" PRIu64, + txn_id); + return NULL; + } + + txn = XCALLOC(MTYPE_MGMTD_BE_TXN, sizeof(struct mgmt_be_txn_ctx)); + txn->txn_id = txn_id; + txn->client = client_ctx; + mgmt_be_batches_init(&txn->cfg_batches); + mgmt_be_batches_init(&txn->apply_cfgs); + mgmt_be_txns_add_tail(&client_ctx->txn_head, txn); + + debug_be_client("Created new txn-id: %" PRIu64, txn_id); + + return txn; +} + +static void mgmt_be_txn_delete(struct mgmt_be_client *client_ctx, + struct mgmt_be_txn_ctx **txn) +{ + char err_msg[] = "MGMT Transaction Delete"; + + if (!txn) + return; + + /* + * Remove the transaction from the list of transactions + * so that future lookups with the same transaction id + * does not return this one. + */ + mgmt_be_txns_del(&client_ctx->txn_head, *txn); + + /* + * Time to delete the transaction which should also + * take care of cleaning up all batches created via + * CFGDATA_CREATE_REQs. But first notify the client + * about the transaction delete. + */ + if (client_ctx->cbs.txn_notify) + (void)(*client_ctx->cbs.txn_notify)(client_ctx, + client_ctx->user_data, + &(*txn)->client_data, true); + + mgmt_be_cleanup_all_batches(*txn); + if ((*txn)->nb_txn) + nb_candidate_commit_abort((*txn)->nb_txn, err_msg, + sizeof(err_msg)); + XFREE(MTYPE_MGMTD_BE_TXN, *txn); + + *txn = NULL; +} + +static void mgmt_be_cleanup_all_txns(struct mgmt_be_client *client_ctx) +{ + struct mgmt_be_txn_ctx *txn = NULL; + + FOREACH_BE_TXN_IN_LIST (client_ctx, txn) { + mgmt_be_txn_delete(client_ctx, &txn); + } +} + + +/** + * Send an error back to MGMTD using native messaging. + * + * Args: + * client: the BE client. + * txn_id: the txn_id this error pertains to. + * short_circuit_ok: True if OK to short-circuit the call. + * error: An integer error value. + * errfmt: An error format string (i.e., printfrr) + * ...: args for use by the `errfmt` format string. + * + * Return: + * the return value from the underlying send message function. + */ +static int be_client_send_error(struct mgmt_be_client *client, uint64_t txn_id, + uint64_t req_id, bool short_circuit_ok, + int16_t error, const char *errfmt, ...) + PRINTFRR(6, 7); + +static int be_client_send_error(struct mgmt_be_client *client, uint64_t txn_id, + uint64_t req_id, bool short_circuit_ok, + int16_t error, const char *errfmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, errfmt); + ret = vmgmt_msg_native_send_error(&client->client.conn, txn_id, req_id, + short_circuit_ok, error, errfmt, ap); + va_end(ap); + + return ret; +} + +static int mgmt_be_send_notification(void *__be_client, const char *xpath, + const struct lyd_node *tree) +{ + struct mgmt_be_client *client = __be_client; + struct mgmt_msg_notify_data *msg = NULL; + LYD_FORMAT format = LYD_JSON; + uint8_t **darrp; + LY_ERR err; + int ret = 0; + + assert(tree); + + debug_be_client("%s: sending YANG notification: %s", __func__, + tree->schema->name); + /* + * Allocate a message and append the data to it using `format` + */ + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_data, 0, + MTYPE_MSG_NATIVE_NOTIFY); + msg->code = MGMT_MSG_CODE_NOTIFY; + msg->result_type = format; + + mgmt_msg_native_xpath_encode(msg, xpath); + + darrp = mgmt_msg_native_get_darrp(msg); + err = yang_print_tree_append(darrp, tree, format, + (LYD_PRINT_SHRINK | LYD_PRINT_WD_EXPLICIT | + LYD_PRINT_WITHSIBLINGS)); + if (err) { + flog_err(EC_LIB_LIBYANG, + "%s: error creating notification data: %s", __func__, + ly_strerrcode(err)); + ret = 1; + goto done; + } + + (void)be_client_send_native_msg(client, msg, + mgmt_msg_native_get_msg_len(msg), false); +done: + mgmt_msg_native_free_msg(msg); + return ret; +} + +static int mgmt_be_send_txn_reply(struct mgmt_be_client *client_ctx, + uint64_t txn_id, bool create) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeTxnReply txn_reply; + + mgmtd__be_txn_reply__init(&txn_reply); + txn_reply.create = create; + txn_reply.txn_id = txn_id; + txn_reply.success = true; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_TXN_REPLY; + be_msg.txn_reply = &txn_reply; + + debug_be_client("Sending TXN_REPLY txn-id %" PRIu64, txn_id); + + return mgmt_be_client_send_msg(client_ctx, &be_msg); +} + +static int mgmt_be_process_txn_req(struct mgmt_be_client *client_ctx, + uint64_t txn_id, bool create) +{ + struct mgmt_be_txn_ctx *txn; + + if (create) { + debug_be_client("Creating new txn-id %" PRIu64, txn_id); + + txn = mgmt_be_txn_create(client_ctx, txn_id); + if (!txn) + goto failed; + + if (client_ctx->cbs.txn_notify) + (*client_ctx->cbs.txn_notify)(client_ctx, + client_ctx->user_data, + &txn->client_data, false); + } else { + debug_be_client("Deleting txn-id: %" PRIu64, txn_id); + txn = mgmt_be_find_txn_by_id(client_ctx, txn_id, false); + if (txn) + mgmt_be_txn_delete(client_ctx, &txn); + } + + return mgmt_be_send_txn_reply(client_ctx, txn_id, create); + +failed: + msg_conn_disconnect(&client_ctx->client.conn, true); + return -1; +} + +static int mgmt_be_send_cfgdata_create_reply(struct mgmt_be_client *client_ctx, + uint64_t txn_id, bool success, + const char *error_if_any) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeCfgDataCreateReply cfgdata_reply; + + mgmtd__be_cfg_data_create_reply__init(&cfgdata_reply); + cfgdata_reply.txn_id = (uint64_t)txn_id; + cfgdata_reply.success = success; + if (error_if_any) + cfgdata_reply.error_if_any = (char *)error_if_any; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REPLY; + be_msg.cfg_data_reply = &cfgdata_reply; + + debug_be_client("Sending CFGDATA_CREATE_REPLY txn-id: %" PRIu64, txn_id); + + return mgmt_be_client_send_msg(client_ctx, &be_msg); +} + +static void mgmt_be_txn_cfg_abort(struct mgmt_be_txn_ctx *txn) +{ + char errmsg[BUFSIZ] = {0}; + + assert(txn && txn->client); + if (txn->nb_txn) { + log_err_be_client("Aborting configs after prep for txn-id: %" PRIu64, + txn->txn_id); + nb_candidate_commit_abort(txn->nb_txn, errmsg, sizeof(errmsg)); + txn->nb_txn = 0; + } + + /* + * revert candidate back to running + * + * This is one txn ctx but the candidate_config is per client ctx, how + * does that work? + */ + debug_be_client("Reset candidate configurations after abort of txn-id: %" PRIu64, + txn->txn_id); + nb_config_replace(txn->client->candidate_config, + txn->client->running_config, true); +} + +static int mgmt_be_txn_cfg_prepare(struct mgmt_be_txn_ctx *txn) +{ + struct mgmt_be_client *client_ctx; + struct mgmt_be_txn_req *txn_req = NULL; + struct nb_context nb_ctx = {0}; + struct timeval edit_nb_cfg_start; + struct timeval edit_nb_cfg_end; + unsigned long edit_nb_cfg_tm; + struct timeval prep_nb_cfg_start; + struct timeval prep_nb_cfg_end; + unsigned long prep_nb_cfg_tm; + struct mgmt_be_batch_ctx *batch; + bool error; + char err_buf[BUFSIZ]; + size_t num_processed; + int err; + + assert(txn && txn->client); + client_ctx = txn->client; + + num_processed = 0; + FOREACH_BE_TXN_BATCH_IN_LIST (txn, batch) { + txn_req = &batch->txn_req; + error = false; + nb_ctx.client = NB_CLIENT_CLI; + nb_ctx.user = (void *)client_ctx->user_data; + + if (!txn->nb_txn) { + /* + * This happens when the current backend client is only + * interested in consuming the config items but is not + * interested in validating it. + */ + error = false; + + gettimeofday(&edit_nb_cfg_start, NULL); + nb_candidate_edit_config_changes( + client_ctx->candidate_config, + txn_req->req.set_cfg.cfg_changes, + (size_t)txn_req->req.set_cfg.num_cfg_changes, + NULL, true, err_buf, sizeof(err_buf), &error); + if (error) { + err_buf[sizeof(err_buf) - 1] = 0; + log_err_be_client("Failed to update configs for txn-id: %" PRIu64 + " to candidate, err: '%s'", + txn->txn_id, err_buf); + return -1; + } + gettimeofday(&edit_nb_cfg_end, NULL); + edit_nb_cfg_tm = timeval_elapsed(edit_nb_cfg_end, + edit_nb_cfg_start); + client_ctx->avg_edit_nb_cfg_tm = + ((client_ctx->avg_edit_nb_cfg_tm * + client_ctx->num_edit_nb_cfg) + + edit_nb_cfg_tm) / + (client_ctx->num_edit_nb_cfg + 1); + client_ctx->num_edit_nb_cfg++; + } + + num_processed++; + } + + if (!num_processed) + return 0; + + /* + * Now prepare all the batches we have applied in one go. + */ + nb_ctx.client = NB_CLIENT_CLI; + nb_ctx.user = (void *)client_ctx->user_data; + + gettimeofday(&prep_nb_cfg_start, NULL); + err = nb_candidate_commit_prepare(nb_ctx, client_ctx->candidate_config, + "MGMTD Backend Txn", &txn->nb_txn, +#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED + true, true, +#else + false, true, +#endif + err_buf, sizeof(err_buf) - 1); + if (err != NB_OK) { + err_buf[sizeof(err_buf) - 1] = 0; + if (err == NB_ERR_VALIDATION) + log_err_be_client("Failed to validate configs txn-id: %" PRIu64 + " %zu batches, err: '%s'", + txn->txn_id, num_processed, err_buf); + else + log_err_be_client("Failed to prepare configs for txn-id: %" PRIu64 + " %zu batches, err: '%s'", + txn->txn_id, num_processed, err_buf); + error = true; + SET_FLAG(txn->flags, MGMTD_BE_TXN_FLAGS_CFGPREP_FAILED); + } else + debug_be_client("Prepared configs for txn-id: %" PRIu64 + " %zu batches", + txn->txn_id, num_processed); + + gettimeofday(&prep_nb_cfg_end, NULL); + prep_nb_cfg_tm = timeval_elapsed(prep_nb_cfg_end, prep_nb_cfg_start); + client_ctx->avg_prep_nb_cfg_tm = ((client_ctx->avg_prep_nb_cfg_tm * + client_ctx->num_prep_nb_cfg) + + prep_nb_cfg_tm) / + (client_ctx->num_prep_nb_cfg + 1); + client_ctx->num_prep_nb_cfg++; + + FOREACH_BE_TXN_BATCH_IN_LIST (txn, batch) { + if (!error) { + SET_FLAG(batch->flags, + MGMTD_BE_BATCH_FLAGS_CFG_PREPARED); + mgmt_be_batches_del(&txn->cfg_batches, batch); + mgmt_be_batches_add_tail(&txn->apply_cfgs, batch); + } + } + + mgmt_be_send_cfgdata_create_reply(client_ctx, txn->txn_id, + error ? false : true, error ? err_buf : NULL); + + debug_be_client("Avg-nb-edit-duration %lu uSec, nb-prep-duration %lu (avg: %lu) uSec, batch size %u", + client_ctx->avg_edit_nb_cfg_tm, prep_nb_cfg_tm, + client_ctx->avg_prep_nb_cfg_tm, (uint32_t)num_processed); + + if (error) + mgmt_be_txn_cfg_abort(txn); + + return 0; +} + +/* + * Process all CFG_DATA_REQs received so far and prepare them all in one go. + */ +static int mgmt_be_update_setcfg_in_batch(struct mgmt_be_client *client_ctx, + struct mgmt_be_txn_ctx *txn, + Mgmtd__YangCfgDataReq *cfg_req[], + int num_req) +{ + struct mgmt_be_batch_ctx *batch = NULL; + struct mgmt_be_txn_req *txn_req = NULL; + int index; + struct nb_cfg_change *cfg_chg; + + batch = mgmt_be_batch_create(txn); + assert(batch); + + txn_req = &batch->txn_req; + txn_req->event = MGMTD_BE_TXN_PROC_SETCFG; + debug_be_client("Created SETCFG request for txn-id: %" PRIu64 + " cfg-items:%d", + txn->txn_id, num_req); + + txn_req->req.set_cfg.num_cfg_changes = num_req; + for (index = 0; index < num_req; index++) { + cfg_chg = &txn_req->req.set_cfg.cfg_changes[index]; + + /* + * Treat all operations as destroy or modify, because we don't + * need additional existence checks on the backend. Everything + * is already checked by mgmtd. + */ + switch (cfg_req[index]->req_type) { + case MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA: + case MGMTD__CFG_DATA_REQ_TYPE__REMOVE_DATA: + cfg_chg->operation = NB_OP_DESTROY; + break; + case MGMTD__CFG_DATA_REQ_TYPE__SET_DATA: + case MGMTD__CFG_DATA_REQ_TYPE__CREATE_DATA: + case MGMTD__CFG_DATA_REQ_TYPE__REPLACE_DATA: + cfg_chg->operation = NB_OP_MODIFY; + break; + case MGMTD__CFG_DATA_REQ_TYPE__REQ_TYPE_NONE: + case _MGMTD__CFG_DATA_REQ_TYPE_IS_INT_SIZE: + default: + continue; + } + + strlcpy(cfg_chg->xpath, cfg_req[index]->data->xpath, + sizeof(cfg_chg->xpath)); + cfg_chg->value = (cfg_req[index]->data->value + && cfg_req[index] + ->data->value + ->encoded_str_val + ? strdup(cfg_req[index] + ->data->value + ->encoded_str_val) + : NULL); + if (cfg_chg->value + && !strncmp(cfg_chg->value, MGMTD_BE_CONTAINER_NODE_VAL, + strlen(MGMTD_BE_CONTAINER_NODE_VAL))) { + free((char *)cfg_chg->value); + cfg_chg->value = NULL; + } + } + + return 0; +} + +static int mgmt_be_process_cfgdata_req(struct mgmt_be_client *client_ctx, + uint64_t txn_id, + Mgmtd__YangCfgDataReq *cfg_req[], + int num_req, bool end_of_data) +{ + struct mgmt_be_txn_ctx *txn; + + txn = mgmt_be_find_txn_by_id(client_ctx, txn_id, true); + if (!txn) + goto failed; + + mgmt_be_update_setcfg_in_batch(client_ctx, txn, cfg_req, num_req); + + if (txn && end_of_data) { + debug_be_client("End of data; CFG_PREPARE_REQ processing"); + if (mgmt_be_txn_cfg_prepare(txn)) + goto failed; + } + + return 0; +failed: + msg_conn_disconnect(&client_ctx->client.conn, true); + return -1; +} + +static int mgmt_be_send_apply_reply(struct mgmt_be_client *client_ctx, + uint64_t txn_id, bool success, + const char *error_if_any) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeCfgDataApplyReply apply_reply; + + mgmtd__be_cfg_data_apply_reply__init(&apply_reply); + apply_reply.success = success; + apply_reply.txn_id = txn_id; + + if (error_if_any) + apply_reply.error_if_any = (char *)error_if_any; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REPLY; + be_msg.cfg_apply_reply = &apply_reply; + + debug_be_client("Sending CFG_APPLY_REPLY txn-id %" PRIu64, txn_id); + + return mgmt_be_client_send_msg(client_ctx, &be_msg); +} + +static int mgmt_be_txn_proc_cfgapply(struct mgmt_be_txn_ctx *txn) +{ + struct mgmt_be_client *client_ctx; + struct timeval apply_nb_cfg_start; + struct timeval apply_nb_cfg_end; + unsigned long apply_nb_cfg_tm; + struct mgmt_be_batch_ctx *batch; + char err_buf[BUFSIZ]; + + assert(txn && txn->client); + client_ctx = txn->client; + + assert(txn->nb_txn); + + /* + * Now apply all the batches we have applied in one go. + */ + gettimeofday(&apply_nb_cfg_start, NULL); + (void)nb_candidate_commit_apply(txn->nb_txn, true, &txn->nb_txn_id, + err_buf, sizeof(err_buf) - 1); + gettimeofday(&apply_nb_cfg_end, NULL); + + apply_nb_cfg_tm = timeval_elapsed(apply_nb_cfg_end, apply_nb_cfg_start); + client_ctx->avg_apply_nb_cfg_tm = ((client_ctx->avg_apply_nb_cfg_tm * + client_ctx->num_apply_nb_cfg) + + apply_nb_cfg_tm) / + (client_ctx->num_apply_nb_cfg + 1); + client_ctx->num_apply_nb_cfg++; + txn->nb_txn = NULL; + + FOREACH_BE_APPLY_BATCH_IN_LIST (txn, batch) { + /* + * No need to delete the batch yet. Will be deleted during + * transaction cleanup on receiving TXN_DELETE_REQ. + */ + SET_FLAG(batch->flags, MGMTD_BE_TXN_FLAGS_CFG_APPLIED); + mgmt_be_batches_del(&txn->apply_cfgs, batch); + mgmt_be_batches_add_tail(&txn->cfg_batches, batch); + } + + mgmt_be_send_apply_reply(client_ctx, txn->txn_id, true, NULL); + + debug_be_client("Nb-apply-duration %lu (avg: %lu) uSec", + apply_nb_cfg_tm, client_ctx->avg_apply_nb_cfg_tm); + + return 0; +} + +static int mgmt_be_process_cfg_apply(struct mgmt_be_client *client_ctx, + uint64_t txn_id) +{ + struct mgmt_be_txn_ctx *txn; + + txn = mgmt_be_find_txn_by_id(client_ctx, txn_id, true); + if (!txn) + goto failed; + + debug_be_client("Trigger CFG_APPLY_REQ processing"); + if (mgmt_be_txn_proc_cfgapply(txn)) + goto failed; + + return 0; +failed: + msg_conn_disconnect(&client_ctx->client.conn, true); + return -1; +} + + +static int mgmt_be_client_handle_msg(struct mgmt_be_client *client_ctx, + Mgmtd__BeMessage *be_msg) +{ + /* + * On error we may have closed the connection so don't do anything with + * the client_ctx on return. + * + * protobuf-c adds a max size enum with an internal, and changing by + * version, name; cast to an int to avoid unhandled enum warnings + */ + switch ((int)be_msg->message_case) { + case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REPLY: + debug_be_client("Got SUBSCR_REPLY success %u", + be_msg->subscr_reply->success); + + if (client_ctx->cbs.subscr_done) + (*client_ctx->cbs.subscr_done)(client_ctx, + client_ctx->user_data, + be_msg->subscr_reply + ->success); + break; + case MGMTD__BE_MESSAGE__MESSAGE_TXN_REQ: + debug_be_client("Got TXN_REQ %s txn-id: %" PRIu64, + be_msg->txn_req->create ? "Create" : "Delete", + be_msg->txn_req->txn_id); + mgmt_be_process_txn_req(client_ctx, + be_msg->txn_req->txn_id, + be_msg->txn_req->create); + break; + case MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REQ: + debug_be_client("Got CFG_DATA_REQ txn-id: %" PRIu64 + " end-of-data %u", + be_msg->cfg_data_req->txn_id, + be_msg->cfg_data_req->end_of_data); + mgmt_be_process_cfgdata_req( + client_ctx, be_msg->cfg_data_req->txn_id, + be_msg->cfg_data_req->data_req, + be_msg->cfg_data_req->n_data_req, + be_msg->cfg_data_req->end_of_data); + break; + case MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REQ: + debug_be_client("Got CFG_APPLY_REQ txn-id: %" PRIu64, + be_msg->cfg_data_req->txn_id); + mgmt_be_process_cfg_apply( + client_ctx, (uint64_t)be_msg->cfg_apply_req->txn_id); + break; + /* + * NOTE: The following messages are always sent from Backend + * clients to MGMTd only and/or need not be handled here. + */ + case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ: + case MGMTD__BE_MESSAGE__MESSAGE_TXN_REPLY: + case MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REPLY: + case MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REPLY: + case MGMTD__BE_MESSAGE__MESSAGE__NOT_SET: + default: + /* + * A 'default' case is being added contrary to the + * FRR code guidelines to take care of build + * failures on certain build systems (courtesy of + * the proto-c package). + */ + break; + } + + return 0; +} + +struct be_client_tree_data_batch_args { + struct mgmt_be_client *client; + uint64_t txn_id; + uint64_t req_id; + LYD_FORMAT result_type; +}; + +/* + * Process the get-tree request on our local oper state + */ +static enum nb_error be_client_send_tree_data_batch(const struct lyd_node *tree, + void *arg, enum nb_error ret) +{ + struct be_client_tree_data_batch_args *args = arg; + struct mgmt_be_client *client = args->client; + struct mgmt_msg_tree_data *tree_msg = NULL; + bool more = false; + uint8_t **darrp; + LY_ERR err; + + if (ret == NB_YIELD) { + more = true; + ret = NB_OK; + } + if (ret != NB_OK) + goto done; + + tree_msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, + MTYPE_MSG_NATIVE_TREE_DATA); + tree_msg->refer_id = args->txn_id; + tree_msg->req_id = args->req_id; + tree_msg->code = MGMT_MSG_CODE_TREE_DATA; + tree_msg->result_type = args->result_type; + tree_msg->more = more; + + darrp = mgmt_msg_native_get_darrp(tree_msg); + err = yang_print_tree_append(darrp, tree, args->result_type, + (LYD_PRINT_SHRINK | LYD_PRINT_WD_EXPLICIT | + LYD_PRINT_WITHSIBLINGS)); + if (err) { + ret = NB_ERR; + goto done; + } + (void)be_client_send_native_msg(client, tree_msg, + mgmt_msg_native_get_msg_len(tree_msg), + false); +done: + mgmt_msg_native_free_msg(tree_msg); + if (ret) + be_client_send_error(client, args->txn_id, args->req_id, false, + -EINVAL, + "BE client %s txn-id %" PRIu64 + " error fetching oper state %d", + client->name, args->txn_id, ret); + if (ret != NB_OK || !more) + XFREE(MTYPE_MGMTD_BE_GT_CB_ARGS, args); + return ret; +} + +/* + * Process the get-tree request on our local oper state + */ +static void be_client_handle_get_tree(struct mgmt_be_client *client, + uint64_t txn_id, void *msgbuf, + size_t msg_len) +{ + struct mgmt_msg_get_tree *get_tree_msg = msgbuf; + struct be_client_tree_data_batch_args *args; + + debug_be_client("Received get-tree request for client %s txn-id %" PRIu64 + " req-id %" PRIu64, + client->name, txn_id, get_tree_msg->req_id); + + /* NOTE: removed the translator, if put back merge with northbound_cli + * code + */ + + args = XMALLOC(MTYPE_MGMTD_BE_GT_CB_ARGS, sizeof(*args)); + args->client = client; + args->txn_id = get_tree_msg->refer_id; + args->req_id = get_tree_msg->req_id; + args->result_type = get_tree_msg->result_type; + nb_oper_walk(get_tree_msg->xpath, NULL, 0, true, NULL, NULL, + be_client_send_tree_data_batch, args); +} + +static void be_client_send_rpc_reply(struct mgmt_be_client *client, + uint64_t txn_id, uint64_t req_id, + uint8_t result_type, + struct lyd_node *output) +{ + struct mgmt_msg_rpc_reply *rpc_reply_msg; + uint8_t **darrp; + LY_ERR err; + int ret = NB_OK; + + rpc_reply_msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, + MTYPE_MSG_NATIVE_RPC_REPLY); + rpc_reply_msg->refer_id = txn_id; + rpc_reply_msg->req_id = req_id; + rpc_reply_msg->code = MGMT_MSG_CODE_RPC_REPLY; + rpc_reply_msg->result_type = result_type; + + if (output) { + darrp = mgmt_msg_native_get_darrp(rpc_reply_msg); + err = yang_print_tree_append(darrp, output, result_type, + LYD_PRINT_SHRINK); + lyd_free_all(output); + if (err) { + ret = NB_ERR; + goto done; + } + } + + (void)be_client_send_native_msg(client, rpc_reply_msg, + mgmt_msg_native_get_msg_len( + rpc_reply_msg), + false); +done: + mgmt_msg_native_free_msg(rpc_reply_msg); + if (ret != NB_OK) + be_client_send_error(client, txn_id, req_id, false, -EINVAL, + "Can't format RPC reply"); +} + +/* + * Process the RPC request. + */ +static void be_client_handle_rpc(struct mgmt_be_client *client, uint64_t txn_id, + void *msgbuf, size_t msg_len) +{ + struct mgmt_msg_rpc *rpc_msg = msgbuf; + struct nb_node *nb_node; + struct lyd_node *input, *output; + const char *xpath; + const char *data; + char errmsg[BUFSIZ] = { 0 }; + LY_ERR err; + int ret; + + debug_be_client("Received RPC request for client %s txn-id %" PRIu64 + " req-id %" PRIu64, + client->name, txn_id, rpc_msg->req_id); + + xpath = mgmt_msg_native_xpath_data_decode(rpc_msg, msg_len, data); + if (!xpath) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "Corrupt RPC message"); + return; + } + + nb_node = nb_node_find(xpath); + if (!nb_node) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "No schema found for RPC: %s", + xpath); + return; + } + + if (!nb_node->cbs.rpc) { + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "No RPC callback for: %s", xpath); + return; + } + + if (data) { + err = yang_parse_rpc(xpath, rpc_msg->request_type, data, false, + &input); + if (err) { + be_client_send_error(client, txn_id, rpc_msg->req_id, + false, -EINVAL, + "Can't parse RPC data for: %s", + xpath); + return; + } + } else { + /* + * If there's no input data, create an empty input container. + * It is especially needed for actions, because their parents + * may hold necessary information. + */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, + NULL, &input); + if (err) { + be_client_send_error(client, txn_id, rpc_msg->req_id, + false, -EINVAL, + "Can't create input node for RPC: %s", + xpath); + return; + } + } + + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &output); + if (err) { + lyd_free_all(input); + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, + "Can't create output node for RPC: %s", + xpath); + return; + } + + ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg, + sizeof(errmsg)); + if (ret != NB_OK) { + lyd_free_all(input); + lyd_free_all(output); + be_client_send_error(client, txn_id, rpc_msg->req_id, false, + -EINVAL, "%s", errmsg); + return; + } + + lyd_free_all(input); + if (!lyd_child(output)) { + lyd_free_all(output); + output = NULL; + } + + be_client_send_rpc_reply(client, txn_id, rpc_msg->req_id, + rpc_msg->request_type, output); +} + +/* + * Process the notification. + */ +static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf, + size_t msg_len) +{ + struct mgmt_msg_notify_data *notif_msg = msgbuf; + struct nb_node *nb_node; + struct lyd_node *dnode; + const char *data; + const char *notif; + LY_ERR err; + + debug_be_client("Received notification for client %s", client->name); + + notif = mgmt_msg_native_xpath_data_decode(notif_msg, msg_len, data); + if (!notif || !data) { + log_err_be_client("Corrupt notify msg"); + return; + } + + nb_node = nb_node_find(notif); + if (!nb_node) { + log_err_be_client("No schema found for notification: %s", notif); + return; + } + + if (!nb_node->cbs.notify) { + debug_be_client("No notification callback for: %s", notif); + return; + } + + err = yang_parse_notification(notif, notif_msg->result_type, data, + &dnode); + if (err) { + log_err_be_client("Can't parse notification data for: %s", + notif); + return; + } + + nb_callback_notify(nb_node, notif, dnode); + + lyd_free_all(dnode); +} + +/* + * Handle a native encoded message + * + * We don't create transactions with native messaging. + */ +static void be_client_handle_native_msg(struct mgmt_be_client *client, + struct mgmt_msg_header *msg, + size_t msg_len) +{ + uint64_t txn_id = msg->refer_id; + + switch (msg->code) { + case MGMT_MSG_CODE_GET_TREE: + be_client_handle_get_tree(client, txn_id, msg, msg_len); + break; + case MGMT_MSG_CODE_RPC: + be_client_handle_rpc(client, txn_id, msg, msg_len); + break; + case MGMT_MSG_CODE_NOTIFY: + be_client_handle_notify(client, msg, msg_len); + break; + default: + log_err_be_client("unknown native message txn-id %" PRIu64 + " req-id %" PRIu64 " code %u to client %s", + txn_id, msg->req_id, msg->code, client->name); + be_client_send_error(client, msg->refer_id, msg->req_id, false, + -1, + "BE client %s recv msg unknown txn-id %" PRIu64, + client->name, txn_id); + break; + } +} + +static void mgmt_be_client_process_msg(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn) +{ + struct mgmt_be_client *client_ctx; + struct msg_client *client; + Mgmtd__BeMessage *be_msg; + + client = container_of(conn, struct msg_client, conn); + client_ctx = container_of(client, struct mgmt_be_client, client); + + if (version == MGMT_MSG_VERSION_NATIVE) { + struct mgmt_msg_header *msg = (typeof(msg))data; + + if (len >= sizeof(*msg)) + be_client_handle_native_msg(client_ctx, msg, len); + else + log_err_be_client("native message to client %s too short %zu", + client_ctx->name, len); + return; + } + + be_msg = mgmtd__be_message__unpack(NULL, len, data); + if (!be_msg) { + debug_be_client("Failed to decode %zu bytes from server", len); + return; + } + debug_be_client("Decoded %zu bytes of message(msg: %u/%u) from server", + len, be_msg->message_case, be_msg->message_case); + (void)mgmt_be_client_handle_msg(client_ctx, be_msg); + mgmtd__be_message__free_unpacked(be_msg, NULL); +} + +int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, + int n_config_xpaths, char **config_xpaths, + int n_oper_xpaths, char **oper_xpaths) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeSubscribeReq subscr_req; + + mgmtd__be_subscribe_req__init(&subscr_req); + subscr_req.client_name = client_ctx->name; + subscr_req.n_config_xpaths = n_config_xpaths; + subscr_req.config_xpaths = config_xpaths; + subscr_req.n_oper_xpaths = n_oper_xpaths; + subscr_req.oper_xpaths = oper_xpaths; + + /* See if we should register for notifications */ + subscr_req.n_notif_xpaths = client_ctx->cbs.nnotif_xpaths; + subscr_req.notif_xpaths = (char **)client_ctx->cbs.notif_xpaths; + + subscr_req.n_rpc_xpaths = client_ctx->cbs.nrpc_xpaths; + subscr_req.rpc_xpaths = (char **)client_ctx->cbs.rpc_xpaths; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ; + be_msg.subscr_req = &subscr_req; + + debug_be_client("Sending SUBSCR_REQ name: %s xpaths: config %zu oper: %zu notif: %zu", + subscr_req.client_name, subscr_req.n_config_xpaths, + subscr_req.n_oper_xpaths, subscr_req.n_notif_xpaths); + + return mgmt_be_client_send_msg(client_ctx, &be_msg); +} + +static int _notify_conenct_disconnect(struct msg_client *msg_client, + bool connected) +{ + struct mgmt_be_client *client = + container_of(msg_client, struct mgmt_be_client, client); + int ret; + + if (connected) { + assert(msg_client->conn.fd != -1); + ret = mgmt_be_send_subscr_req(client, 0, NULL, 0, NULL); + if (ret) + return ret; + } + + /* Notify BE client through registered callback (if any) */ + if (client->cbs.client_connect_notify) + (void)(*client->cbs.client_connect_notify)(client, + client->user_data, + connected); + + /* Cleanup any in-progress TXN on disconnect */ + if (!connected) + mgmt_be_cleanup_all_txns(client); + + return 0; +} + +static int mgmt_be_client_notify_conenct(struct msg_client *client) +{ + return _notify_conenct_disconnect(client, true); +} + +static int mgmt_be_client_notify_disconenct(struct msg_conn *conn) +{ + struct msg_client *client = container_of(conn, struct msg_client, conn); + + return _notify_conenct_disconnect(client, false); +} + +/* + * Debug Flags + */ + +static void mgmt_debug_client_be_set(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&mgmt_dbg_be_client, flags, set); + + if (!__be_client) + return; + + __be_client->client.conn.debug = DEBUG_MODE_CHECK(&mgmt_dbg_be_client, + DEBUG_MODE_ALL); +} + +DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd, + "[no] debug mgmt client backend", + NO_STR DEBUG_STR MGMTD_STR "client\n" + "backend\n") +{ + mgmt_debug_client_be_set(DEBUG_NODE2MODE(vty->node), !no); + + return CMD_SUCCESS; +} + +static int mgmt_debug_be_client_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&mgmt_dbg_be_client, DEBUG_MODE_CONF)) + vty_out(vty, "debug mgmt client backend\n"); + + return 1; +} + +void mgmt_debug_be_client_show_debug(struct vty *vty) +{ + if (debug_check_be_client()) + vty_out(vty, "debug mgmt client backend\n"); +} + +static struct debug_callbacks mgmt_dbg_be_client_cbs = { + .debug_set_all = mgmt_debug_client_be_set +}; + +static struct cmd_node mgmt_dbg_node = { + .name = "debug mgmt client backend", + .node = MGMT_BE_DEBUG_NODE, + .prompt = "", + .config_write = mgmt_debug_be_client_config_write, +}; + +struct mgmt_be_client *mgmt_be_client_create(const char *client_name, + struct mgmt_be_client_cbs *cbs, + uintptr_t user_data, + struct event_loop *event_loop) +{ + struct mgmt_be_client *client; + char server_path[MAXPATHLEN]; + + if (__be_client) + return NULL; + + client = XCALLOC(MTYPE_MGMTD_BE_CLIENT, sizeof(*client)); + __be_client = client; + + /* Only call after frr_init() */ + assert(running_config); + + client->name = XSTRDUP(MTYPE_MGMTD_BE_CLIENT_NAME, client_name); + client->running_config = running_config; + client->candidate_config = vty_shared_candidate_config; + if (cbs) + client->cbs = *cbs; + mgmt_be_txns_init(&client->txn_head); + + snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); + + msg_client_init(&client->client, event_loop, server_path, + mgmt_be_client_notify_conenct, + mgmt_be_client_notify_disconenct, + mgmt_be_client_process_msg, MGMTD_BE_MAX_NUM_MSG_PROC, + MGMTD_BE_MAX_NUM_MSG_WRITE, MGMTD_BE_MAX_MSG_LEN, false, + "BE-client", debug_check_be_client()); + + /* Hook to receive notifications */ + hook_register_arg(nb_notification_tree_send, mgmt_be_send_notification, + client); + + debug_be_client("Initialized client '%s'", client_name); + + return client; +} + + +void mgmt_be_client_lib_vty_init(void) +{ + debug_init(&mgmt_dbg_be_client_cbs); + install_node(&mgmt_dbg_node); + install_element(ENABLE_NODE, &debug_mgmt_client_be_cmd); + install_element(CONFIG_NODE, &debug_mgmt_client_be_cmd); +} + +void mgmt_be_client_destroy(struct mgmt_be_client *client) +{ + assert(client == __be_client); + + debug_be_client("Destroying MGMTD Backend Client '%s'", client->name); + + nb_oper_cancel_all_walks(); + msg_client_cleanup(&client->client); + mgmt_be_cleanup_all_txns(client); + mgmt_be_txns_fini(&client->txn_head); + + XFREE(MTYPE_MGMTD_BE_CLIENT_NAME, client->name); + XFREE(MTYPE_MGMTD_BE_CLIENT, client); + + __be_client = NULL; +} diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h new file mode 100644 index 0000000..7ad0589 --- /dev/null +++ b/lib/mgmt_be_client.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Backend Client Library api interfaces + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_BE_CLIENT_H_ +#define _FRR_MGMTD_BE_CLIENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "northbound.h" +#include "mgmt_pb.h" +#include "mgmt_defines.h" + +/*************************************************************** + * Constants + ***************************************************************/ + +#define MGMTD_BE_MAX_NUM_MSG_PROC 500 +#define MGMTD_BE_MAX_NUM_MSG_WRITE 1000 +#define MGMTD_BE_MAX_MSG_LEN (64 * 1024) + +#define MGMTD_MAX_CFG_CHANGES_IN_BATCH \ + ((10 * MGMTD_BE_MAX_MSG_LEN) / \ + (MGMTD_MAX_XPATH_LEN + MGMTD_MAX_YANG_VALUE_LEN)) + +/* + * MGMTD_BE_MSG_MAX_LEN must be used 80% + * since there is overhead of google protobuf + * that gets added to sent message + */ +#define MGMTD_BE_CFGDATA_PACKING_EFFICIENCY 0.8 +#define MGMTD_BE_CFGDATA_MAX_MSG_LEN \ + (MGMTD_BE_MAX_MSG_LEN * MGMTD_BE_CFGDATA_PACKING_EFFICIENCY) + +#define MGMTD_BE_MAX_BATCH_IDS_IN_REQ \ + (MGMTD_BE_MAX_MSG_LEN - 128) / sizeof(uint64_t) + +#define MGMTD_BE_CONTAINER_NODE_VAL "<>" + +/*************************************************************** + * Data-structures + ***************************************************************/ + +#define MGMTD_BE_MAX_CLIENTS_PER_XPATH_REG 32 + +struct mgmt_be_client; + +struct mgmt_be_client_txn_ctx { + uintptr_t *user_ctx; +}; + +/** + * Backend client callbacks. + * + * Callbacks: + * client_connect_notify: called when connection is made/lost to mgmtd. + * txn_notify: called when a txn has been created + * notify_cbs: callbacks for notifications. + * nnotify_cbs: number of notification callbacks. + * + */ +struct mgmt_be_client_cbs { + void (*client_connect_notify)(struct mgmt_be_client *client, + uintptr_t usr_data, bool connected); + void (*subscr_done)(struct mgmt_be_client *client, uintptr_t usr_data, + bool success); + void (*txn_notify)(struct mgmt_be_client *client, uintptr_t usr_data, + struct mgmt_be_client_txn_ctx *txn_ctx, + bool destroyed); + + const char **notif_xpaths; + uint nnotif_xpaths; + const char **rpc_xpaths; + uint nrpc_xpaths; +}; + +/*************************************************************** + * Global data exported + ***************************************************************/ + +extern struct debug mgmt_dbg_be_client; + +/*************************************************************** + * API prototypes + ***************************************************************/ + +#define debug_be_client(fmt, ...) \ + DEBUGD(&mgmt_dbg_be_client, "BE-CLIENT: %s: " fmt, __func__, \ + ##__VA_ARGS__) +#define log_err_be_client(fmt, ...) \ + zlog_err("BE-CLIENT: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) +#define debug_check_be_client() \ + DEBUG_MODE_CHECK(&mgmt_dbg_be_client, DEBUG_MODE_ALL) + +/** + * Create backend client and connect to MGMTD. + * + * Args: + * client_name: the name of the client + * cbs: callbacks for various events. + * event_loop: the main event loop. + * + * Returns: + * Backend client object. + */ +extern struct mgmt_be_client * +mgmt_be_client_create(const char *name, struct mgmt_be_client_cbs *cbs, + uintptr_t user_data, struct event_loop *event_loop); + +/* + * Initialize library vty (adds debug support). + * + * This call should be added to your component when enabling other vty code to + * enable mgmtd client debugs. When adding, one needs to also add a their + * component in `xref2vtysh.py` as well. + */ +extern void mgmt_be_client_lib_vty_init(void); + +/* + * Print enabled debugging commands. + */ +extern void mgmt_debug_be_client_show_debug(struct vty *vty); + +/* + * [Un]-subscribe with MGMTD for one or more YANG subtree(s). + * + * client + * The client object. + * + * reg_yang_xpaths + * Yang xpath(s) that needs to be subscribed to + * + * num_xpaths + * Number of xpaths + * + * Returns: + * MGMTD_SUCCESS on success, MGMTD_* otherwise. + */ +extern int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, + int n_config_xpaths, char **config_xpaths, + int n_oper_xpaths, char **oper_xpaths); + +/* + * Destroy backend client and cleanup everything. + */ +extern void mgmt_be_client_destroy(struct mgmt_be_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_MGMTD_BE_CLIENT_H_ */ diff --git a/lib/mgmt_defines.h b/lib/mgmt_defines.h new file mode 100644 index 0000000..b02341e --- /dev/null +++ b/lib/mgmt_defines.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD public defines. + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_DEFINES_H +#define _FRR_MGMTD_DEFINES_H + +#include "yang.h" + +#define MGMTD_FE_SOCK_NAME "%s/mgmtd_fe.sock", frr_runstatedir +#define MGMTD_BE_SOCK_NAME "%s/mgmtd_be.sock", frr_runstatedir + +#define MGMTD_CLIENT_NAME_MAX_LEN 32 + +#define MGMTD_MAX_XPATH_LEN XPATH_MAXLEN + +#define MGMTD_MAX_YANG_VALUE_LEN YANG_VALUE_MAXLEN + +#define MGMTD_MAX_NUM_XPATH_REG 128 + +#define MGMTD_MAX_NUM_DATA_REQ_IN_BATCH 32 +#define MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH 8 + +enum mgmt_result { + MGMTD_SUCCESS = 0, + MGMTD_INVALID_PARAM, + MGMTD_INTERNAL_ERROR, + MGMTD_NO_CFG_CHANGES, + MGMTD_DS_LOCK_FAILED, + MGMTD_DS_UNLOCK_FAILED, + MGMTD_UNKNOWN_FAILURE +}; + +#endif /* _FRR_MGMTD_DEFINES_H */ diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c new file mode 100644 index 0000000..8cfb025 --- /dev/null +++ b/lib/mgmt_fe_client.c @@ -0,0 +1,964 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Frontend Client Library api interfaces + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "compiler.h" +#include "debug.h" +#include "memory.h" +#include "libfrr.h" +#include "mgmt_fe_client.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" +#include "mgmt_pb.h" +#include "network.h" +#include "stream.h" +#include "sockopt.h" + +#include "lib/mgmt_fe_client_clippy.c" + +PREDECL_LIST(mgmt_sessions); + +struct mgmt_fe_client_session { + uint64_t client_id; /* FE client identifies itself with this ID */ + uint64_t session_id; /* FE adapter identified session with this ID */ + struct mgmt_fe_client *client; + uintptr_t user_ctx; + + struct mgmt_sessions_item list_linkage; +}; + +DECLARE_LIST(mgmt_sessions, struct mgmt_fe_client_session, list_linkage); + +DEFINE_MTYPE_STATIC(LIB, MGMTD_FE_CLIENT, "frontend client"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_FE_CLIENT_NAME, "frontend client name"); +DEFINE_MTYPE_STATIC(LIB, MGMTD_FE_SESSION, "frontend session"); + +struct mgmt_fe_client { + struct msg_client client; + char *name; + struct mgmt_fe_client_cbs cbs; + uintptr_t user_data; + struct mgmt_sessions_head sessions; +}; + +#define FOREACH_SESSION_IN_LIST(client, session) \ + frr_each_safe (mgmt_sessions, &(client)->sessions, (session)) + +struct debug mgmt_dbg_fe_client = { + .desc = "Management frontend client operations" +}; + +/* NOTE: only one client per proc for now. */ +static struct mgmt_fe_client *__fe_client; + +static inline const char *dsid2name(Mgmtd__DatastoreId id) +{ + switch ((int)id) { + case MGMTD_DS_NONE: + return "none"; + case MGMTD_DS_RUNNING: + return "running"; + case MGMTD_DS_CANDIDATE: + return "candidate"; + case MGMTD_DS_OPERATIONAL: + return "operational"; + default: + return "unknown-datastore-id"; + } +} + +static struct mgmt_fe_client_session * +mgmt_fe_find_session_by_client_id(struct mgmt_fe_client *client, + uint64_t client_id) +{ + struct mgmt_fe_client_session *session; + + FOREACH_SESSION_IN_LIST (client, session) { + if (session->client_id == client_id) { + debug_fe_client("Found session-id %" PRIu64 + " using client-id %" PRIu64, + session->session_id, client_id); + return session; + } + } + debug_fe_client("Session not found using client-id %" PRIu64, client_id); + return NULL; +} + +static struct mgmt_fe_client_session * +mgmt_fe_find_session_by_session_id(struct mgmt_fe_client *client, + uint64_t session_id) +{ + struct mgmt_fe_client_session *session; + + FOREACH_SESSION_IN_LIST (client, session) { + if (session->session_id == session_id) { + debug_fe_client("Found session of client-id %" PRIu64 + " using session-id %" PRIu64, + session->client_id, session_id); + return session; + } + } + debug_fe_client("Session not found using session-id %" PRIu64, + session_id); + return NULL; +} + +static int mgmt_fe_client_send_msg(struct mgmt_fe_client *client, + Mgmtd__FeMessage *fe_msg, + bool short_circuit_ok) +{ + return msg_conn_send_msg( + &client->client.conn, MGMT_MSG_VERSION_PROTOBUF, fe_msg, + mgmtd__fe_message__get_packed_size(fe_msg), + (size_t(*)(void *, void *))mgmtd__fe_message__pack, + short_circuit_ok); +} + +static int mgmt_fe_send_register_req(struct mgmt_fe_client *client) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeRegisterReq rgstr_req; + + mgmtd__fe_register_req__init(&rgstr_req); + rgstr_req.client_name = client->name; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_REGISTER_REQ; + fe_msg.register_req = &rgstr_req; + + debug_fe_client("Sending REGISTER_REQ message to MGMTD Frontend server"); + + return mgmt_fe_client_send_msg(client, &fe_msg, true); +} + +static int mgmt_fe_send_session_req(struct mgmt_fe_client *client, + struct mgmt_fe_client_session *session, + bool create) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeSessionReq sess_req; + + mgmtd__fe_session_req__init(&sess_req); + sess_req.create = create; + if (create) { + sess_req.id_case = MGMTD__FE_SESSION_REQ__ID_CLIENT_CONN_ID; + sess_req.client_conn_id = session->client_id; + } else { + sess_req.id_case = MGMTD__FE_SESSION_REQ__ID_SESSION_ID; + sess_req.session_id = session->session_id; + } + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_SESSION_REQ; + fe_msg.session_req = &sess_req; + + debug_fe_client("Sending SESSION_REQ %s message for client-id %" PRIu64, + create ? "create" : "destroy", session->client_id); + + return mgmt_fe_client_send_msg(client, &fe_msg, true); +} + +int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, Mgmtd__DatastoreId ds_id, + bool lock, bool scok) +{ + (void)req_id; + Mgmtd__FeMessage fe_msg; + Mgmtd__FeLockDsReq lockds_req; + + mgmtd__fe_lock_ds_req__init(&lockds_req); + lockds_req.session_id = session_id; + lockds_req.req_id = req_id; + lockds_req.ds_id = ds_id; + lockds_req.lock = lock; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REQ; + fe_msg.lockds_req = &lockds_req; + + debug_fe_client("Sending LOCKDS_REQ (%sLOCK) message for DS:%s session-id %" PRIu64, + lock ? "" : "UN", dsid2name(ds_id), session_id); + + + return mgmt_fe_client_send_msg(client, &fe_msg, scok); +} + +int mgmt_fe_send_setcfg_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, Mgmtd__DatastoreId ds_id, + Mgmtd__YangCfgDataReq **data_req, int num_data_reqs, + bool implicit_commit, Mgmtd__DatastoreId dst_ds_id) +{ + (void)req_id; + Mgmtd__FeMessage fe_msg; + Mgmtd__FeSetConfigReq setcfg_req; + + mgmtd__fe_set_config_req__init(&setcfg_req); + setcfg_req.session_id = session_id; + setcfg_req.ds_id = ds_id; + setcfg_req.req_id = req_id; + setcfg_req.data = data_req; + setcfg_req.n_data = (size_t)num_data_reqs; + setcfg_req.implicit_commit = implicit_commit; + setcfg_req.commit_ds_id = dst_ds_id; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REQ; + fe_msg.setcfg_req = &setcfg_req; + + debug_fe_client("Sending SET_CONFIG_REQ message for DS:%s session-id %" PRIu64 + " (#xpaths:%d)", + dsid2name(ds_id), session_id, num_data_reqs); + + return mgmt_fe_client_send_msg(client, &fe_msg, false); +} + +int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dest_ds_id, + bool validate_only, bool abort) +{ + (void)req_id; + Mgmtd__FeMessage fe_msg; + Mgmtd__FeCommitConfigReq commitcfg_req; + + mgmtd__fe_commit_config_req__init(&commitcfg_req); + commitcfg_req.session_id = session_id; + commitcfg_req.src_ds_id = src_ds_id; + commitcfg_req.dst_ds_id = dest_ds_id; + commitcfg_req.req_id = req_id; + commitcfg_req.validate_only = validate_only; + commitcfg_req.abort = abort; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REQ; + fe_msg.commcfg_req = &commitcfg_req; + + debug_fe_client("Sending COMMIT_CONFIG_REQ message for Src-DS:%s, Dst-DS:%s session-id %" PRIu64, + dsid2name(src_ds_id), dsid2name(dest_ds_id), session_id); + + return mgmt_fe_client_send_msg(client, &fe_msg, false); +} + +int mgmt_fe_send_get_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, bool is_config, + Mgmtd__DatastoreId ds_id, + Mgmtd__YangGetDataReq *data_req[], int num_data_reqs) +{ + (void)req_id; + Mgmtd__FeMessage fe_msg; + Mgmtd__FeGetReq getcfg_req; + + mgmtd__fe_get_req__init(&getcfg_req); + getcfg_req.session_id = session_id; + getcfg_req.config = is_config; + getcfg_req.ds_id = ds_id; + getcfg_req.req_id = req_id; + getcfg_req.data = data_req; + getcfg_req.n_data = (size_t)num_data_reqs; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_GET_REQ; + fe_msg.get_req = &getcfg_req; + + debug_fe_client("Sending GET_REQ (iscfg %d) message for DS:%s session-id %" PRIu64 + " (#xpaths:%d)", + is_config, dsid2name(ds_id), session_id, num_data_reqs); + + return mgmt_fe_client_send_msg(client, &fe_msg, false); +} + +int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, bool register_req, + Mgmtd__YangDataXPath *data_req[], + int num_data_reqs) +{ + (void)req_id; + Mgmtd__FeMessage fe_msg; + Mgmtd__FeRegisterNotifyReq regntfy_req; + + mgmtd__fe_register_notify_req__init(®ntfy_req); + regntfy_req.session_id = session_id; + regntfy_req.ds_id = ds_id; + regntfy_req.register_req = register_req; + regntfy_req.data_xpath = data_req; + regntfy_req.n_data_xpath = (size_t)num_data_reqs; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_REGNOTIFY_REQ; + fe_msg.regnotify_req = ®ntfy_req; + + return mgmt_fe_client_send_msg(client, &fe_msg, false); +} + +/* + * Send get-data request. + */ +int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + uint8_t datastore, LYD_FORMAT result_type, + uint8_t flags, uint8_t defaults, const char *xpath) +{ + struct mgmt_msg_get_data *msg; + size_t xplen = strlen(xpath); + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_data, xplen + 1, + MTYPE_MSG_NATIVE_GET_DATA); + msg->refer_id = session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_GET_DATA; + msg->result_type = result_type; + msg->flags = flags; + msg->defaults = defaults; + msg->datastore = datastore; + strlcpy(msg->xpath, xpath, xplen + 1); + + debug_fe_client("Sending GET_DATA_REQ session-id %" PRIu64 + " req-id %" PRIu64 " xpath: %s", + session_id, req_id, xpath); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + mgmt_msg_native_free_msg(msg); + return ret; +} + +int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, const char *data) +{ + struct mgmt_msg_edit *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit, 0, + MTYPE_MSG_NATIVE_EDIT); + msg->refer_id = session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_EDIT; + msg->request_type = request_type; + msg->flags = flags; + msg->datastore = datastore; + msg->operation = operation; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, strlen(data) + 1); + + debug_fe_client("Sending EDIT_REQ session-id %" PRIu64 + " req-id %" PRIu64 " xpath: %s", + session_id, req_id, xpath); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + mgmt_msg_native_free_msg(msg); + return ret; +} + +int mgmt_fe_send_rpc_req(struct mgmt_fe_client *client, uint64_t session_id, + uint64_t req_id, LYD_FORMAT request_type, + const char *xpath, const char *data) +{ + struct mgmt_msg_rpc *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc, 0, + MTYPE_MSG_NATIVE_RPC); + msg->refer_id = session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC; + msg->request_type = request_type; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, strlen(data) + 1); + + debug_fe_client("Sending RPC_REQ session-id %" PRIu64 " req-id %" PRIu64 + " xpath: %s", + session_id, req_id, xpath); + + ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false); + mgmt_msg_native_free_msg(msg); + return ret; +} + +static int mgmt_fe_client_handle_msg(struct mgmt_fe_client *client, + Mgmtd__FeMessage *fe_msg) +{ + struct mgmt_fe_client_session *session = NULL; + + /* + * protobuf-c adds a max size enum with an internal, and changing by + * version, name; cast to an int to avoid unhandled enum warnings + */ + switch ((int)fe_msg->message_case) { + case MGMTD__FE_MESSAGE__MESSAGE_SESSION_REPLY: + if (fe_msg->session_reply->create && + fe_msg->session_reply->has_client_conn_id) { + debug_fe_client("Got SESSION_REPLY (create) for client-id %" PRIu64 + " with session-id: %" PRIu64, + fe_msg->session_reply->client_conn_id, + fe_msg->session_reply->session_id); + + session = mgmt_fe_find_session_by_client_id( + client, fe_msg->session_reply->client_conn_id); + + if (session && fe_msg->session_reply->success) { + debug_fe_client("Session Created for client-id %" PRIu64, + fe_msg->session_reply + ->client_conn_id); + session->session_id = + fe_msg->session_reply->session_id; + } else { + log_err_fe_client( + "Session Create failed for client-id %" PRIu64, + fe_msg->session_reply->client_conn_id); + } + } else if (!fe_msg->session_reply->create) { + debug_fe_client("Got SESSION_REPLY (destroy) for session-id %" PRIu64, + fe_msg->session_reply->session_id); + + session = mgmt_fe_find_session_by_session_id( + client, fe_msg->session_req->session_id); + } + + /* The session state may be deleted by the callback */ + if (session && session->client && + session->client->cbs.client_session_notify) + (*session->client->cbs.client_session_notify)( + client, client->user_data, session->client_id, + fe_msg->session_reply->create, + fe_msg->session_reply->success, + fe_msg->session_reply->session_id, + session->user_ctx); + break; + case MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REPLY: + debug_fe_client("Got LOCKDS_REPLY for session-id %" PRIu64, + fe_msg->lockds_reply->session_id); + session = mgmt_fe_find_session_by_session_id( + client, fe_msg->lockds_reply->session_id); + + if (session && session->client && + session->client->cbs.lock_ds_notify) + (*session->client->cbs.lock_ds_notify)( + client, client->user_data, session->client_id, + fe_msg->lockds_reply->session_id, + session->user_ctx, fe_msg->lockds_reply->req_id, + fe_msg->lockds_reply->lock, + fe_msg->lockds_reply->success, + fe_msg->lockds_reply->ds_id, + fe_msg->lockds_reply->error_if_any); + break; + case MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REPLY: + debug_fe_client("Got SETCFG_REPLY for session-id %" PRIu64, + fe_msg->setcfg_reply->session_id); + + session = mgmt_fe_find_session_by_session_id( + client, fe_msg->setcfg_reply->session_id); + + if (session && session->client && + session->client->cbs.set_config_notify) + (*session->client->cbs.set_config_notify)( + client, client->user_data, session->client_id, + fe_msg->setcfg_reply->session_id, + session->user_ctx, fe_msg->setcfg_reply->req_id, + fe_msg->setcfg_reply->success, + fe_msg->setcfg_reply->ds_id, + fe_msg->setcfg_reply->implicit_commit, + fe_msg->setcfg_reply->error_if_any); + break; + case MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REPLY: + debug_fe_client("Got COMMCFG_REPLY for session-id %" PRIu64, + fe_msg->commcfg_reply->session_id); + + session = mgmt_fe_find_session_by_session_id( + client, fe_msg->commcfg_reply->session_id); + + if (session && session->client && + session->client->cbs.commit_config_notify) + (*session->client->cbs.commit_config_notify)( + client, client->user_data, session->client_id, + fe_msg->commcfg_reply->session_id, + session->user_ctx, + fe_msg->commcfg_reply->req_id, + fe_msg->commcfg_reply->success, + fe_msg->commcfg_reply->src_ds_id, + fe_msg->commcfg_reply->dst_ds_id, + fe_msg->commcfg_reply->validate_only, + fe_msg->commcfg_reply->error_if_any); + break; + case MGMTD__FE_MESSAGE__MESSAGE_GET_REPLY: + debug_fe_client("Got GET_REPLY for session-id %" PRIu64, + fe_msg->get_reply->session_id); + + session = + mgmt_fe_find_session_by_session_id(client, + fe_msg->get_reply + ->session_id); + + if (session && session->client && + session->client->cbs.get_data_notify) + (*session->client->cbs.get_data_notify)( + client, client->user_data, session->client_id, + fe_msg->get_reply->session_id, + session->user_ctx, fe_msg->get_reply->req_id, + fe_msg->get_reply->success, + fe_msg->get_reply->ds_id, + fe_msg->get_reply->data + ? fe_msg->get_reply->data->data + : NULL, + fe_msg->get_reply->data + ? fe_msg->get_reply->data->n_data + : 0, + fe_msg->get_reply->data + ? fe_msg->get_reply->data->next_indx + : 0, + fe_msg->get_reply->error_if_any); + break; + case MGMTD__FE_MESSAGE__MESSAGE_NOTIFY_DATA_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_REGNOTIFY_REQ: + /* + * TODO: Add handling code in future. + */ + break; + /* + * NOTE: The following messages are always sent from Frontend + * clients to MGMTd only and/or need not be handled here. + */ + case MGMTD__FE_MESSAGE__MESSAGE_REGISTER_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_SESSION_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_GET_REQ: + case MGMTD__FE_MESSAGE__MESSAGE__NOT_SET: + default: + /* + * A 'default' case is being added contrary to the + * FRR code guidelines to take care of build + * failures on certain build systems (courtesy of + * the proto-c package). + */ + break; + } + + return 0; +} + +/* + * Handle a native encoded message + */ +static void fe_client_handle_native_msg(struct mgmt_fe_client *client, + struct mgmt_msg_header *msg, + size_t msg_len) +{ + struct mgmt_fe_client_session *session = NULL; + struct mgmt_msg_notify_data *notify_msg; + struct mgmt_msg_tree_data *tree_msg; + struct mgmt_msg_edit_reply *edit_msg; + struct mgmt_msg_rpc_reply *rpc_msg; + struct mgmt_msg_error *err_msg; + const char *xpath = NULL; + const char *data = NULL; + size_t dlen; + + debug_fe_client("Got native message for session-id %" PRIu64, + msg->refer_id); + + session = mgmt_fe_find_session_by_session_id(client, msg->refer_id); + if (!session || !session->client) { + log_err_fe_client("No session for received native msg session-id %" PRIu64, + msg->refer_id); + return; + } + + switch (msg->code) { + case MGMT_MSG_CODE_ERROR: + if (!session->client->cbs.error_notify) + return; + + err_msg = (typeof(err_msg))msg; + if (!MGMT_MSG_VALIDATE_NUL_TERM(err_msg, msg_len)) { + log_err_fe_client("Corrupt error msg recv"); + return; + } + session->client->cbs.error_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, + msg->req_id, err_msg->error, + err_msg->errstr); + break; + case MGMT_MSG_CODE_TREE_DATA: + if (!session->client->cbs.get_tree_notify) + return; + + tree_msg = (typeof(tree_msg))msg; + if (msg_len < sizeof(*tree_msg)) { + log_err_fe_client("Corrupt tree-data msg recv"); + return; + } + session->client->cbs.get_tree_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, + msg->req_id, + MGMTD_DS_OPERATIONAL, + tree_msg->result_type, + tree_msg->result, + msg_len - sizeof(*tree_msg), + tree_msg->partial_error); + break; + case MGMT_MSG_CODE_EDIT_REPLY: + if (!session->client->cbs.edit_notify) + return; + + edit_msg = (typeof(edit_msg))msg; + if (msg_len < sizeof(*edit_msg)) { + log_err_fe_client("Corrupt edit-reply msg recv"); + return; + } + + xpath = mgmt_msg_native_xpath_decode(edit_msg, msg_len); + if (!xpath) { + log_err_fe_client("Corrupt edit-reply msg recv"); + return; + } + + session->client->cbs.edit_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, msg->req_id, + xpath); + break; + case MGMT_MSG_CODE_RPC_REPLY: + if (!session->client->cbs.rpc_notify) + return; + + rpc_msg = (typeof(rpc_msg))msg; + if (msg_len < sizeof(*rpc_msg)) { + log_err_fe_client("Corrupt rpc-reply msg recv"); + return; + } + dlen = msg_len - sizeof(*rpc_msg); + + session->client->cbs.rpc_notify(client, client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, msg->req_id, + dlen ? rpc_msg->data : NULL); + break; + case MGMT_MSG_CODE_NOTIFY: + if (!session->client->cbs.async_notification) + return; + + notify_msg = (typeof(notify_msg))msg; + if (msg_len < sizeof(*notify_msg)) { + log_err_fe_client("Corrupt notify-data msg recv"); + return; + } + + data = mgmt_msg_native_data_decode(notify_msg, msg_len); + if (!data) { + log_err_fe_client("Corrupt error msg recv"); + return; + } + dlen = mgmt_msg_native_data_len_decode(notify_msg, msg_len); + if (notify_msg->result_type != LYD_JSON) + data = yang_convert_lyd_format(data, dlen, + notify_msg->result_type, + LYD_JSON, true); + if (!data) { + log_err_fe_client("Can't convert format %d to JSON", + notify_msg->result_type); + return; + } + + session->client->cbs.async_notification(client, + client->user_data, + session->client_id, + msg->refer_id, + session->user_ctx, data); + + if (notify_msg->result_type != LYD_JSON) + darr_free(data); + break; + default: + log_err_fe_client("unknown native message session-id %" PRIu64 + " req-id %" PRIu64 " code %u", + msg->refer_id, msg->req_id, msg->code); + break; + } +} + +static void mgmt_fe_client_process_msg(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn) +{ + struct mgmt_fe_client *client; + struct msg_client *msg_client; + Mgmtd__FeMessage *fe_msg; + + msg_client = container_of(conn, struct msg_client, conn); + client = container_of(msg_client, struct mgmt_fe_client, client); + + if (version == MGMT_MSG_VERSION_NATIVE) { + struct mgmt_msg_header *msg = (typeof(msg))data; + + if (len >= sizeof(*msg)) + fe_client_handle_native_msg(client, msg, len); + else + log_err_fe_client("native message to FE client %s too short %zu", + client->name, len); + return; + } + + fe_msg = mgmtd__fe_message__unpack(NULL, len, data); + if (!fe_msg) { + debug_fe_client("Failed to decode %zu bytes from server.", len); + return; + } + debug_fe_client("Decoded %zu bytes of message(msg: %u/%u) from server", + len, fe_msg->message_case, fe_msg->message_case); + (void)mgmt_fe_client_handle_msg(client, fe_msg); + mgmtd__fe_message__free_unpacked(fe_msg, NULL); +} + +static int _notify_connect_disconnect(struct msg_client *msg_client, + bool connected) +{ + struct mgmt_fe_client *client = + container_of(msg_client, struct mgmt_fe_client, client); + struct mgmt_fe_client_session *session; + int ret; + + /* Send REGISTER_REQ message */ + if (connected) { + if ((ret = mgmt_fe_send_register_req(client)) != 0) + return ret; + } + + /* Walk list of sessions for this FE client deleting them */ + if (!connected && mgmt_sessions_count(&client->sessions)) { + debug_fe_client("Cleaning up existing sessions"); + + FOREACH_SESSION_IN_LIST (client, session) { + assert(session->client); + + /* unlink from list first this avoids double free */ + mgmt_sessions_del(&client->sessions, session); + + /* notify FE client the session is being deleted */ + if (session->client->cbs.client_session_notify) { + (*session->client->cbs.client_session_notify)( + client, client->user_data, + session->client_id, false, true, + session->session_id, session->user_ctx); + } + + XFREE(MTYPE_MGMTD_FE_SESSION, session); + } + } + + /* Notify FE client through registered callback (if any). */ + if (client->cbs.client_connect_notify) + (void)(*client->cbs.client_connect_notify)( + client, client->user_data, connected); + return 0; +} + +static int mgmt_fe_client_notify_connect(struct msg_client *client) +{ + return _notify_connect_disconnect(client, true); +} + +static int mgmt_fe_client_notify_disconnect(struct msg_conn *conn) +{ + struct msg_client *client = container_of(conn, struct msg_client, conn); + + return _notify_connect_disconnect(client, false); +} + +static void mgmt_debug_client_fe_set(uint32_t mode, bool set) +{ + DEBUG_FLAGS_SET(&mgmt_dbg_fe_client, mode, set); + + if (!__fe_client) + return; + + __fe_client->client.conn.debug = DEBUG_MODE_CHECK(&mgmt_dbg_fe_client, + DEBUG_MODE_ALL); +} + +DEFPY(debug_mgmt_client_fe, debug_mgmt_client_fe_cmd, + "[no] debug mgmt client frontend", + NO_STR DEBUG_STR MGMTD_STR + "client\n" + "frontend\n") +{ + mgmt_debug_client_fe_set(DEBUG_NODE2MODE(vty->node), !no); + + return CMD_SUCCESS; +} + +static int mgmt_debug_fe_client_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&mgmt_dbg_fe_client, DEBUG_MODE_CONF)) + vty_out(vty, "debug mgmt client frontend\n"); + + return CMD_SUCCESS; +} + +void mgmt_debug_fe_client_show_debug(struct vty *vty) +{ + if (debug_check_fe_client()) + vty_out(vty, "debug mgmt client frontend\n"); +} + +static struct debug_callbacks mgmt_dbg_fe_client_cbs = { + .debug_set_all = mgmt_debug_client_fe_set +}; + +static struct cmd_node mgmt_dbg_node = { + .name = "debug mgmt client frontend", + .node = MGMT_FE_DEBUG_NODE, + .prompt = "", + .config_write = mgmt_debug_fe_client_config_write, +}; + +/* + * Initialize library and try connecting with MGMTD. + */ +struct mgmt_fe_client *mgmt_fe_client_create(const char *client_name, + struct mgmt_fe_client_cbs *cbs, + uintptr_t user_data, + struct event_loop *event_loop) +{ + struct mgmt_fe_client *client; + char server_path[MAXPATHLEN]; + + if (__fe_client) + return NULL; + + client = XCALLOC(MTYPE_MGMTD_FE_CLIENT, sizeof(*client)); + __fe_client = client; + + client->name = XSTRDUP(MTYPE_MGMTD_FE_CLIENT_NAME, client_name); + client->user_data = user_data; + if (cbs) + client->cbs = *cbs; + + mgmt_sessions_init(&client->sessions); + + snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); + + msg_client_init(&client->client, event_loop, server_path, + mgmt_fe_client_notify_connect, + mgmt_fe_client_notify_disconnect, + mgmt_fe_client_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, + MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, true, + "FE-client", debug_check_fe_client()); + + debug_fe_client("Initialized client '%s'", client_name); + + return client; +} + +void mgmt_fe_client_lib_vty_init(void) +{ + debug_init(&mgmt_dbg_fe_client_cbs); + install_node(&mgmt_dbg_node); + install_element(ENABLE_NODE, &debug_mgmt_client_fe_cmd); + install_element(CONFIG_NODE, &debug_mgmt_client_fe_cmd); +} + +uint mgmt_fe_client_session_count(struct mgmt_fe_client *client) +{ + return mgmt_sessions_count(&client->sessions); +} + +bool mgmt_fe_client_current_msg_short_circuit(struct mgmt_fe_client *client) +{ + return client->client.conn.is_short_circuit; +} + +const char *mgmt_fe_client_name(struct mgmt_fe_client *client) +{ + return client->name; +} + +/* + * Create a new Session for a Frontend Client connection. + */ +enum mgmt_result mgmt_fe_create_client_session(struct mgmt_fe_client *client, + uint64_t client_id, + uintptr_t user_ctx) +{ + struct mgmt_fe_client_session *session; + + session = XCALLOC(MTYPE_MGMTD_FE_SESSION, + sizeof(struct mgmt_fe_client_session)); + assert(session); + session->user_ctx = user_ctx; + session->client_id = client_id; + session->client = client; + session->session_id = 0; + + mgmt_sessions_add_tail(&client->sessions, session); + + if (mgmt_fe_send_session_req(client, session, true) != 0) { + XFREE(MTYPE_MGMTD_FE_SESSION, session); + return MGMTD_INTERNAL_ERROR; + } + + return MGMTD_SUCCESS; +} + +/* + * Delete an existing Session for a Frontend Client connection. + */ +enum mgmt_result mgmt_fe_destroy_client_session(struct mgmt_fe_client *client, + uint64_t client_id) +{ + struct mgmt_fe_client_session *session; + + session = mgmt_fe_find_session_by_client_id(client, client_id); + if (!session || session->client != client) + return MGMTD_INVALID_PARAM; + + if (session->session_id && + mgmt_fe_send_session_req(client, session, false) != 0) + log_err_fe_client("Failed to send session destroy request for the session-id %" PRIu64, + session->session_id); + + mgmt_sessions_del(&client->sessions, session); + XFREE(MTYPE_MGMTD_FE_SESSION, session); + + return MGMTD_SUCCESS; +} + +/* + * Destroy library and cleanup everything. + */ +void mgmt_fe_client_destroy(struct mgmt_fe_client *client) +{ + struct mgmt_fe_client_session *session; + + assert(client == __fe_client); + + debug_fe_client("Destroying MGMTD Frontend Client '%s'", client->name); + + FOREACH_SESSION_IN_LIST (client, session) + mgmt_fe_destroy_client_session(client, session->client_id); + + msg_client_cleanup(&client->client); + + XFREE(MTYPE_MGMTD_FE_CLIENT_NAME, client->name); + XFREE(MTYPE_MGMTD_FE_CLIENT, client); + + __fe_client = NULL; +} diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h new file mode 100644 index 0000000..20c8704 --- /dev/null +++ b/lib/mgmt_fe_client.h @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Frontend Client Library api interfaces + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_FE_CLIENT_H_ +#define _FRR_MGMTD_FE_CLIENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "mgmt_pb.h" +#include "frrevent.h" +#include "mgmt_defines.h" +#include "mgmt_msg_native.h" + +/*************************************************************** + * Macros + ***************************************************************/ + +/* + * The server port MGMTD daemon is listening for Backend Client + * connections. + */ + +#define MGMTD_FE_MSG_PROC_DELAY_USEC 10 + +#define MGMTD_FE_MAX_NUM_MSG_PROC 500 +#define MGMTD_FE_MAX_NUM_MSG_WRITE 100 +#define MGMTD_FE_MAX_MSG_LEN (64 * 1024) + +/*************************************************************** + * Data-structures + ***************************************************************/ + +#define MGMTD_SESSION_ID_NONE 0 + +#define MGMTD_CLIENT_ID_NONE 0 + +#define MGMTD_DS_NONE MGMTD__DATASTORE_ID__DS_NONE +#define MGMTD_DS_RUNNING MGMTD__DATASTORE_ID__RUNNING_DS +#define MGMTD_DS_CANDIDATE MGMTD__DATASTORE_ID__CANDIDATE_DS +#define MGMTD_DS_OPERATIONAL MGMTD__DATASTORE_ID__OPERATIONAL_DS +#define MGMTD_DS_MAX_ID MGMTD_DS_OPERATIONAL + 1 + +struct mgmt_fe_client; + + +/* + * All the client specific information this library needs to + * initialize itself, setup connection with MGMTD FrontEnd interface + * and carry on all required procedures appropriately. + * + * FrontEnd clients need to initialise a instance of this structure + * with appropriate data and pass it while calling the API + * to initialize the library (See mgmt_fe_client_lib_init for + * more details). + */ +struct mgmt_fe_client_cbs { + void (*client_connect_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, bool connected); + + void (*client_session_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + bool create, bool success, + uintptr_t session_id, + uintptr_t user_session_client); + + void (*lock_ds_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uintptr_t session_id, + uintptr_t user_session_client, uint64_t req_id, + bool lock_ds, bool success, + Mgmtd__DatastoreId ds_id, char *errmsg_if_any); + + void (*set_config_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uintptr_t session_id, + uintptr_t user_session_client, + uint64_t req_id, bool success, + Mgmtd__DatastoreId ds_id, bool implcit_commit, + char *errmsg_if_any); + + void (*commit_config_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uintptr_t session_id, + uintptr_t user_session_client, + uint64_t req_id, bool success, + Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, + bool validate_only, char *errmsg_if_any); + + int (*get_data_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uintptr_t session_id, + uintptr_t user_session_client, uint64_t req_id, + bool success, Mgmtd__DatastoreId ds_id, + Mgmtd__YangData **yang_data, size_t num_data, + int next_key, char *errmsg_if_any); + + int (*data_notify)(uint64_t client_id, uint64_t session_id, + uintptr_t user_data, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + Mgmtd__YangData **yang_data, size_t num_data); + + /* Called when get-tree result is returned */ + int (*get_tree_notify)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uint64_t session_id, uintptr_t session_ctx, + uint64_t req_id, Mgmtd__DatastoreId ds_id, + LYD_FORMAT result_type, void *result, size_t len, + int partial_error); + + /* Called when edit result is returned */ + int (*edit_notify)(struct mgmt_fe_client *client, uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *xpath); + + /* Called when RPC result is returned */ + int (*rpc_notify)(struct mgmt_fe_client *client, uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *result); + + /* Called with asynchronous notifications from backends */ + int (*async_notification)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uint64_t session_id, uintptr_t session_ctx, + const char *result); + + /* Called when new native error is returned */ + int (*error_notify)(struct mgmt_fe_client *client, uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, int error, + const char *errstr); +}; + +extern struct debug mgmt_dbg_fe_client; + +/*************************************************************** + * API prototypes + ***************************************************************/ + +#define debug_fe_client(fmt, ...) \ + DEBUGD(&mgmt_dbg_fe_client, "FE-CLIENT: %s: " fmt, __func__, \ + ##__VA_ARGS__) +#define log_err_fe_client(fmt, ...) \ + zlog_err("FE-CLIENT: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) +#define debug_check_fe_client() \ + DEBUG_MODE_CHECK(&mgmt_dbg_fe_client, DEBUG_MODE_ALL) + +/* + * Initialize library and try connecting with MGMTD FrontEnd interface. + * + * params + * Frontend client parameters. + * + * master_thread + * Thread master. + * + * Returns: + * Frontend client lib handler (nothing but address of mgmt_fe_client) + */ +extern struct mgmt_fe_client * +mgmt_fe_client_create(const char *client_name, struct mgmt_fe_client_cbs *cbs, + uintptr_t user_data, struct event_loop *event_loop); + +/* + * Initialize library vty (adds debug support). + * + * This call should be added to your component when enabling other vty + * code to enable mgmtd client debugs. When adding, one needs to also + * add a their component in `xref2vtysh.py` as well. + */ +extern void mgmt_fe_client_lib_vty_init(void); + +/* + * Print enabled debugging commands. + */ +extern void mgmt_debug_fe_client_show_debug(struct vty *vty); + +/* + * Create a new Session for a Frontend Client connection. + * + * lib_hndl + * Client library handler. + * + * client_id + * Unique identifier of client. + * + * user_client + * Client context. + * + * Returns: + * MGMTD_SUCCESS on success, MGMTD_* otherwise. + */ +extern enum mgmt_result +mgmt_fe_create_client_session(struct mgmt_fe_client *client, uint64_t client_id, + uintptr_t user_client); + +/* + * Delete an existing Session for a Frontend Client connection. + * + * lib_hndl + * Client library handler. + * + * client_id + * Unique identifier of client. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern enum mgmt_result +mgmt_fe_destroy_client_session(struct mgmt_fe_client *client, + uint64_t client_id); + +/* + * Send UN/LOCK_DS_REQ to MGMTD for a specific Datastore DS. + * + * lib_hndl + * Client library handler. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * ds_id + * Datastore ID (Running/Candidate/Oper/Startup) + * + * lock_ds + * TRUE for lock request, FALSE for unlock request. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_lockds_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, bool lock_ds, + bool scok); + +/* + * Send SET_CONFIG_REQ to MGMTD for one or more config data(s). + * + * lib_hndl + * Client library handler. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * ds_id + * Datastore ID (Running/Candidate/Oper/Startup) + * + * conf_req + * Details regarding the SET_CONFIG_REQ. + * + * num_req + * Number of config requests. + * + * implcit commit + * TRUE for implicit commit, FALSE otherwise. + * + * dst_ds_id + * Destination Datastore ID where data needs to be set. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ + +extern int mgmt_fe_send_setcfg_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + Mgmtd__YangCfgDataReq **config_req, + int num_req, bool implicit_commit, + Mgmtd__DatastoreId dst_ds_id); + +/* + * Send SET_COMMMIT_REQ to MGMTD for one or more config data(s). + * + * lib_hndl + * Client library handler. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * src_ds_id + * Source datastore ID from where data needs to be committed from. + * + * dst_ds_id + * Destination datastore ID where data needs to be committed to. + * + * validate_only + * TRUE if data needs to be validated only, FALSE otherwise. + * + * abort + * TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_commitcfg_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, + bool validate_only, bool abort); + +/* + * Send GET_REQ to MGMTD for one or more config data item(s). + * + * If is_config is true gets config from the MGMTD datastore, otherwise + * operational state is queried from the backend clients. + * + * lib_hndl + * Client library handler. + * + * session_id + * Client session ID. + * + * is_config + * True if get-config else get-data. + * + * req_id + * Client request ID. + * + * ds_id + * Datastore ID (Running/Candidate) + * + * data_req + * Get xpaths requested. + * + * num_req + * Number of get xpath requests. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_get_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + bool is_config, Mgmtd__DatastoreId ds_id, + Mgmtd__YangGetDataReq **data_req, int num_reqs); + + +/* + * Send NOTIFY_REGISTER_REQ to MGMTD daemon. + * + * lib_hndl + * Client library handler. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * ds_id + * Datastore ID. + * + * register_req + * TRUE if registering, FALSE otherwise. + * + * data_req + * Details of the YANG notification data. + * + * num_reqs + * Number of data requests. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + bool register_req, + Mgmtd__YangDataXPath **data_req, + int num_reqs); + +/* + * Send GET-DATA to MGMTD daemon. + * + * client + * Client object. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * datastore + * Datastore for getting data. + * + * result_type + * The LYD_FORMAT of the result. + * + * flags + * Flags to control the behavior of the request. + * + * defaults + * Options to control the reporting of default values. + * + * xpath + * the xpath to get. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + uint8_t datastore, LYD_FORMAT result_type, + uint8_t flags, uint8_t defaults, + const char *xpath); + +/* + * Send EDIT to MGMTD daemon. + * + * client + * Client object. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * datastore + * Datastore for editing. + * + * request_type + * The LYD_FORMAT of the request. + * + * flags + * Flags to control the behavior of the request. + * + * operation + * NB_OP_* operation to perform. + * + * xpath + * the xpath to edit. + * + * data + * the data tree. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + uint8_t datastore, LYD_FORMAT request_type, + uint8_t flags, uint8_t operation, + const char *xpath, const char *data); + +/* + * Send RPC request to MGMTD daemon. + * + * client + * Client object. + * + * session_id + * Client session ID. + * + * req_id + * Client request ID. + * + * result_type + * The LYD_FORMAT of the result. + * + * xpath + * the xpath of the RPC. + * + * data + * the data tree. + * + * Returns: + * 0 on success, otherwise msg_conn_send_msg() return values. + */ +extern int mgmt_fe_send_rpc_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + LYD_FORMAT request_type, const char *xpath, + const char *data); + +/* + * Destroy library and cleanup everything. + */ +extern void mgmt_fe_client_destroy(struct mgmt_fe_client *client); + +/* + * Get count of open sessions. + */ +extern uint mgmt_fe_client_session_count(struct mgmt_fe_client *client); + +/* + * True if the current handled message is being short-circuited + */ +extern bool +mgmt_fe_client_current_msg_short_circuit(struct mgmt_fe_client *client); + +/** + * Get the name of the client + * + * Args: + * The client object. + * + * Return: + * The name of the client. + */ +extern const char *mgmt_fe_client_name(struct mgmt_fe_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_MGMTD_FE_CLIENT_H_ */ diff --git a/lib/mgmt_msg.c b/lib/mgmt_msg.c new file mode 100644 index 0000000..aff9af7 --- /dev/null +++ b/lib/mgmt_msg.c @@ -0,0 +1,977 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * March 6 2023, Christian Hopps + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ +#include +#include + +#include "debug.h" +#include "network.h" +#include "sockopt.h" +#include "stream.h" +#include "frrevent.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" + + +#define MGMT_MSG_DBG(dbgtag, fmt, ...) \ + do { \ + if (dbgtag) \ + zlog_debug("%s: %s: " fmt, dbgtag, __func__, \ + ##__VA_ARGS__); \ + } while (0) + +#define MGMT_MSG_ERR(ms, fmt, ...) \ + zlog_err("%s: %s: " fmt, (ms)->idtag, __func__, ##__VA_ARGS__) + +DEFINE_MTYPE(LIB, MSG_CONN, "msg connection state"); + +/** + * Read data from a socket into streams containing 1 or more full msgs headed by + * mgmt_msg_hdr which contain API messages (currently protobuf). + * + * Args: + * ms: mgmt_msg_state for this process. + * fd: socket/file to read data from. + * debug: true to enable debug logging. + * + * Returns: + * MPP_DISCONNECT - socket should be closed and connect retried. + * MSV_SCHED_STREAM - this call should be rescheduled to run. + * MPP_SCHED_BOTH - this call and the procmsg buf should be scheduled to + *run. + */ +enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, + bool debug) +{ + const char *dbgtag = debug ? ms->idtag : NULL; + size_t avail = STREAM_WRITEABLE(ms->ins); + struct mgmt_msg_hdr *mhdr = NULL; + size_t total = 0; + size_t mcount = 0; + ssize_t n, left; + + assert(ms && fd != -1); + + /* + * Read as much as we can into the stream. + */ + while (avail > sizeof(struct mgmt_msg_hdr)) { + n = stream_read_try(ms->ins, fd, avail); + + /* -2 is normal nothing read, and to retry */ + if (n == -2) { + MGMT_MSG_DBG(dbgtag, "nothing more to read"); + break; + } + if (n <= 0) { + if (n == 0) + MGMT_MSG_ERR(ms, "got EOF/disconnect"); + else + MGMT_MSG_ERR(ms, + "got error while reading: '%s'", + safe_strerror(errno)); + return MSR_DISCONNECT; + } + MGMT_MSG_DBG(dbgtag, "read %zd bytes", n); + ms->nrxb += n; + avail -= n; + } + + /* + * Check if we have read a complete messages or not. + */ + assert(stream_get_getp(ms->ins) == 0); + left = stream_get_endp(ms->ins); + while (left > (ssize_t)sizeof(struct mgmt_msg_hdr)) { + mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(ms->ins) + total); + if (!MGMT_MSG_IS_MARKER(mhdr->marker)) { + MGMT_MSG_DBG(dbgtag, "recv corrupt buffer, disconnect"); + return MSR_DISCONNECT; + } + if ((ssize_t)mhdr->len > left) + break; + + MGMT_MSG_DBG(dbgtag, "read full message len %u", mhdr->len); + total += mhdr->len; + left -= mhdr->len; + mcount++; + } + + if (!mcount) { + /* Didn't manage to read a full message */ + if (mhdr && avail == 0) { + struct stream *news; + /* + * Message was longer than what was left and we have no + * available space to read more in. B/c mcount == 0 the + * message starts at the beginning of the stream so + * therefor the stream is too small to fit the message.. + * Resize the stream to fit. + */ + if (mhdr->len > MGMT_MSG_MAX_MSG_ALLOC_LEN) { + MGMT_MSG_ERR(ms, "corrupt msg len rcvd %u", + mhdr->len); + return MSR_DISCONNECT; + } + news = stream_new(mhdr->len); + stream_put(news, mhdr, left); + stream_set_endp(news, left); + stream_free(ms->ins); + ms->ins = news; + } + return MSR_SCHED_STREAM; + } + + /* + * We have read at least one message into the stream, queue it up. + */ + mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(ms->ins) + total); + stream_set_endp(ms->ins, total); + stream_fifo_push(&ms->inq, ms->ins); + if (left < (ssize_t)sizeof(struct mgmt_msg_hdr)) + ms->ins = stream_new(ms->max_msg_sz); + else + /* handle case where message is greater than max */ + ms->ins = stream_new(MAX(ms->max_msg_sz, mhdr->len)); + if (left) { + stream_put(ms->ins, mhdr, left); + stream_set_endp(ms->ins, left); + } + + return MSR_SCHED_BOTH; +} + +/** + * Process streams containing whole messages that have been pushed onto the + * FIFO. This should be called from an event/timer handler and should be + * reschedulable. + * + * Args: + * ms: mgmt_msg_state for this process. + * handle_mgs: function to call for each received message. + * user: opaque value passed through to handle_msg. + * debug: true to enable debug logging. + * + * Returns: + * true if more to process (so reschedule) else false + */ +bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, + void (*handle_msg)(uint8_t version, uint8_t *msg, + size_t msglen, void *user), + void *user, bool debug) +{ + const char *dbgtag = debug ? ms->idtag : NULL; + struct mgmt_msg_hdr *mhdr; + struct stream *work; + uint8_t *data; + size_t left, nproc; + + MGMT_MSG_DBG(dbgtag, "Have %zu streams to process", ms->inq.count); + + nproc = 0; + while (nproc < ms->max_read_buf) { + work = stream_fifo_pop(&ms->inq); + if (!work) + break; + + data = STREAM_DATA(work); + left = stream_get_endp(work); + MGMT_MSG_DBG(dbgtag, "Processing stream of len %zu", left); + + for (; left > sizeof(struct mgmt_msg_hdr); + left -= mhdr->len, data += mhdr->len) { + mhdr = (struct mgmt_msg_hdr *)data; + + assert(MGMT_MSG_IS_MARKER(mhdr->marker)); + assert(left >= mhdr->len); + + handle_msg(MGMT_MSG_MARKER_VERSION(mhdr->marker), + (uint8_t *)(mhdr + 1), + mhdr->len - sizeof(struct mgmt_msg_hdr), + user); + ms->nrxm++; + nproc++; + } + + if (work != ms->ins) + stream_free(work); /* Free it up */ + else + stream_reset(work); /* Reset stream for next read */ + } + + /* return true if should reschedule b/c more to process. */ + return stream_fifo_head(&ms->inq) != NULL; +} + +/** + * Write data onto the socket, using streams that have been queued for + * sending by mgmt_msg_send_msg. This function should be reschedulable. + * + * Args: + * ms: mgmt_msg_state for this process. + * fd: socket/file to read data from. + * debug: true to enable debug logging. + * + * Returns: + * MSW_SCHED_NONE - do not reschedule anything. + * MSW_SCHED_STREAM - this call should be rescheduled to run again. + * MSW_SCHED_WRITES_OFF - writes should be disabled with a timer to + * re-enable them a short time later + * MSW_DISCONNECT - socket should be closed and reconnect retried. + *run. + */ +enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, + bool debug) +{ + const char *dbgtag = debug ? ms->idtag : NULL; + struct stream *s; + size_t nproc = 0; + ssize_t left; + ssize_t n; + + if (ms->outs) { + MGMT_MSG_DBG(dbgtag, + "found unqueued stream with %zu bytes, queueing", + stream_get_endp(ms->outs)); + stream_fifo_push(&ms->outq, ms->outs); + ms->outs = NULL; + } + + for (s = stream_fifo_head(&ms->outq); s && nproc < ms->max_write_buf; + s = stream_fifo_head(&ms->outq)) { + left = STREAM_READABLE(s); + assert(left); + + n = stream_flush(s, fd); + if (n <= 0) { + if (n == 0) + MGMT_MSG_ERR(ms, + "connection closed while writing"); + else if (ERRNO_IO_RETRY(errno)) { + MGMT_MSG_DBG( + dbgtag, + "retry error while writing %zd bytes: %s (%d)", + left, safe_strerror(errno), errno); + return MSW_SCHED_STREAM; + } else + MGMT_MSG_ERR( + ms, + "error while writing %zd bytes: %s (%d)", + left, safe_strerror(errno), errno); + + n = mgmt_msg_reset_writes(ms); + MGMT_MSG_DBG(dbgtag, "drop and freed %zd streams", n); + + return MSW_DISCONNECT; + } + + ms->ntxb += n; + if (n != left) { + MGMT_MSG_DBG(dbgtag, "short stream write %zd of %zd", n, + left); + stream_forward_getp(s, n); + return MSW_SCHED_STREAM; + } + + stream_free(stream_fifo_pop(&ms->outq)); + MGMT_MSG_DBG(dbgtag, "wrote stream of %zd bytes", n); + nproc++; + } + if (s) { + MGMT_MSG_DBG( + dbgtag, + "reached %zu buffer writes, pausing with %zu streams left", + ms->max_write_buf, ms->outq.count); + return MSW_SCHED_STREAM; + } + MGMT_MSG_DBG(dbgtag, "flushed all streams from output q"); + return MSW_SCHED_NONE; +} + + +/** + * Send a message by enqueueing it to be written over the socket by + * mgmt_msg_write. + * + * Args: + * ms: mgmt_msg_state for this process. + * version: version of this message, will be given to receiving side. + * msg: the message to be sent. + * len: the length of the message. + * packf: a function to pack the message. + * debug: true to enable debug logging. + * + * Returns: + * 0 on success, otherwise -1 on failure. The only failure mode is if a + * the message exceeds the maximum message size configured on init. + */ +int mgmt_msg_send_msg(struct mgmt_msg_state *ms, uint8_t version, void *msg, + size_t len, size_t (*packf)(void *msg, void *buf), + bool debug) +{ + const char *dbgtag = debug ? ms->idtag : NULL; + struct mgmt_msg_hdr *mhdr; + struct stream *s; + uint8_t *dstbuf; + size_t endp, n; + size_t mlen = len + sizeof(*mhdr); + + if (mlen > ms->max_msg_sz) + MGMT_MSG_DBG(dbgtag, "Sending large msg size %zu > max size %zu", + mlen, ms->max_msg_sz); + + if (!ms->outs) { + MGMT_MSG_DBG(dbgtag, "creating new stream for msg len %zu", mlen); + ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); + } else if (mlen > ms->max_msg_sz && ms->outs->endp == 0) { + /* msg is larger than stream max size get a fit-to-size stream */ + MGMT_MSG_DBG(dbgtag, + "replacing old stream with fit-to-size for msg len %zu", + mlen); + stream_free(ms->outs); + ms->outs = stream_new(mlen); + } else if (STREAM_WRITEABLE(ms->outs) < mlen) { + MGMT_MSG_DBG(dbgtag, + "enq existing stream len %zu and creating new stream for msg len %zu", + STREAM_WRITEABLE(ms->outs), mlen); + stream_fifo_push(&ms->outq, ms->outs); + ms->outs = stream_new(MAX(ms->max_msg_sz, mlen)); + } else { + MGMT_MSG_DBG( + dbgtag, + "using existing stream with avail %zu for msg len %zu", + STREAM_WRITEABLE(ms->outs), mlen); + } + s = ms->outs; + + if (dbgtag && version == MGMT_MSG_VERSION_NATIVE) { + struct mgmt_msg_header *native_msg = msg; + + MGMT_MSG_DBG( + dbgtag, + "Sending native msg sess/txn-id %"PRIu64" req-id %"PRIu64" code %u", + native_msg->refer_id, native_msg->req_id, native_msg->code); + + } + + /* We have a stream with space, pack the message into it. */ + mhdr = (struct mgmt_msg_hdr *)(STREAM_DATA(s) + s->endp); + mhdr->marker = MGMT_MSG_MARKER(version); + mhdr->len = mlen; + stream_forward_endp(s, sizeof(*mhdr)); + endp = stream_get_endp(s); + dstbuf = STREAM_DATA(s) + endp; + if (packf) + n = packf(msg, dstbuf); + else { + memcpy(dstbuf, msg, len); + n = len; + } + stream_set_endp(s, endp + n); + ms->ntxm++; + + return 0; +} + +/** + * Create and open a unix domain stream socket on the given path + * setting non-blocking and send and receive buffer sizes. + * + * Args: + * path: path of unix domain socket to connect to. + * sendbuf: size of socket send buffer. + * recvbuf: size of socket receive buffer. + * dbgtag: if non-NULL enable log debug, and use this tag. + * + * Returns: + * socket fd or -1 on error. + */ +int mgmt_msg_connect(const char *path, size_t sendbuf, size_t recvbuf, + const char *dbgtag) +{ + int ret, sock, len; + struct sockaddr_un addr; + + MGMT_MSG_DBG(dbgtag, "connecting to server on %s", path); + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + MGMT_MSG_DBG(dbgtag, "socket failed: %s", safe_strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + len = addr.sun_len = SUN_LEN(&addr); +#else + len = sizeof(addr.sun_family) + strlen(addr.sun_path); +#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ + ret = connect(sock, (struct sockaddr *)&addr, len); + if (ret < 0) { + MGMT_MSG_DBG(dbgtag, "failed to connect on %s: %s", path, + safe_strerror(errno)); + close(sock); + return -1; + } + + MGMT_MSG_DBG(dbgtag, "connected to server on %s", path); + set_nonblocking(sock); + setsockopt_so_sendbuf(sock, sendbuf); + setsockopt_so_recvbuf(sock, recvbuf); + return sock; +} + +/** + * Reset the sending queue, by dequeueing all streams and freeing them. Return + * the number of streams freed. + * + * Args: + * ms: mgmt_msg_state for this process. + * + * Returns: + * Number of streams that were freed. + * + */ +size_t mgmt_msg_reset_writes(struct mgmt_msg_state *ms) +{ + struct stream *s; + size_t nproc = 0; + + for (s = stream_fifo_pop(&ms->outq); s; + s = stream_fifo_pop(&ms->outq), nproc++) + stream_free(s); + + return nproc; +} + + +void mgmt_msg_init(struct mgmt_msg_state *ms, size_t max_read_buf, + size_t max_write_buf, size_t max_msg_sz, const char *idtag) +{ + memset(ms, 0, sizeof(*ms)); + ms->ins = stream_new(max_msg_sz); + stream_fifo_init(&ms->inq); + stream_fifo_init(&ms->outq); + ms->max_read_buf = max_write_buf; + ms->max_write_buf = max_read_buf; + ms->max_msg_sz = max_msg_sz; + ms->idtag = strdup(idtag); +} + +void mgmt_msg_destroy(struct mgmt_msg_state *ms) +{ + mgmt_msg_reset_writes(ms); + if (ms->ins) + stream_free(ms->ins); + if (ms->outs) + stream_free(ms->outs); + free(ms->idtag); +} + +/* + * Connections + */ + +#define MSG_CONN_DEFAULT_CONN_RETRY_MSEC 250 +#define MSG_CONN_SEND_BUF_SIZE (1u << 16) +#define MSG_CONN_RECV_BUF_SIZE (1u << 16) + +static void msg_client_sched_connect(struct msg_client *client, + unsigned long msec); + +static void msg_conn_sched_proc_msgs(struct msg_conn *conn); +static void msg_conn_sched_read(struct msg_conn *conn); +static void msg_conn_sched_write(struct msg_conn *conn); + +static void msg_conn_write(struct event *thread) +{ + struct msg_conn *conn = EVENT_ARG(thread); + enum mgmt_msg_wsched rv; + + rv = mgmt_msg_write(&conn->mstate, conn->fd, conn->debug); + if (rv == MSW_SCHED_STREAM) + msg_conn_sched_write(conn); + else if (rv == MSW_DISCONNECT) + msg_conn_disconnect(conn, conn->is_client); + else + assert(rv == MSW_SCHED_NONE); +} + +static void msg_conn_read(struct event *thread) +{ + struct msg_conn *conn = EVENT_ARG(thread); + enum mgmt_msg_rsched rv; + + rv = mgmt_msg_read(&conn->mstate, conn->fd, conn->debug); + if (rv == MSR_DISCONNECT) { + msg_conn_disconnect(conn, conn->is_client); + return; + } + if (rv == MSR_SCHED_BOTH) + msg_conn_sched_proc_msgs(conn); + msg_conn_sched_read(conn); +} + +/* collapse this into mgmt_msg_procbufs */ +static void msg_conn_proc_msgs(struct event *thread) +{ + struct msg_conn *conn = EVENT_ARG(thread); + + if (mgmt_msg_procbufs(&conn->mstate, + (void (*)(uint8_t, uint8_t *, size_t, + void *))conn->handle_msg, + conn, conn->debug)) + /* there's more, schedule handling more */ + msg_conn_sched_proc_msgs(conn); +} + +static void msg_conn_sched_read(struct msg_conn *conn) +{ + event_add_read(conn->loop, msg_conn_read, conn, conn->fd, + &conn->read_ev); +} + +static void msg_conn_sched_write(struct msg_conn *conn) +{ + event_add_write(conn->loop, msg_conn_write, conn, conn->fd, + &conn->write_ev); +} + +static void msg_conn_sched_proc_msgs(struct msg_conn *conn) +{ + event_add_event(conn->loop, msg_conn_proc_msgs, conn, 0, + &conn->proc_msg_ev); +} + + +void msg_conn_disconnect(struct msg_conn *conn, bool reconnect) +{ + + /* disconnect short-circuit if present */ + if (conn->remote_conn) { + conn->remote_conn->remote_conn = NULL; + conn->remote_conn = NULL; + } + + if (conn->fd != -1) { + close(conn->fd); + conn->fd = -1; + + /* Notify client through registered callback (if any) */ + if (conn->notify_disconnect) + (void)(*conn->notify_disconnect)(conn); + } + + if (reconnect) { + assert(conn->is_client); + msg_client_sched_connect( + container_of(conn, struct msg_client, conn), + MSG_CONN_DEFAULT_CONN_RETRY_MSEC); + } +} + +int msg_conn_send_msg(struct msg_conn *conn, uint8_t version, void *msg, + size_t mlen, size_t (*packf)(void *, void *), + bool short_circuit_ok) +{ + const char *dbgtag = conn->debug ? conn->mstate.idtag : NULL; + + if (conn->fd == -1) { + MGMT_MSG_ERR(&conn->mstate, + "can't send message on closed connection"); + return -1; + } + + /* immediately handle the message if short-circuit is present */ + if (conn->remote_conn && short_circuit_ok) { + uint8_t *buf = msg; + size_t n = mlen; + bool old; + + if (packf) { + buf = XMALLOC(MTYPE_TMP, mlen); + n = packf(msg, buf); + } + + ++conn->short_circuit_depth; + MGMT_MSG_DBG(dbgtag, "SC send: depth %u msg: %p", + conn->short_circuit_depth, msg); + + old = conn->remote_conn->is_short_circuit; + conn->remote_conn->is_short_circuit = true; + conn->remote_conn->handle_msg(version, buf, n, + conn->remote_conn); + conn->remote_conn->is_short_circuit = old; + + --conn->short_circuit_depth; + MGMT_MSG_DBG(dbgtag, "SC return from depth: %u msg: %p", + conn->short_circuit_depth, msg); + + if (packf) + XFREE(MTYPE_TMP, buf); + return 0; + } + + int rv = mgmt_msg_send_msg(&conn->mstate, version, msg, mlen, packf, + conn->debug); + + msg_conn_sched_write(conn); + + return rv; +} + +void msg_conn_cleanup(struct msg_conn *conn) +{ + struct mgmt_msg_state *ms = &conn->mstate; + + /* disconnect short-circuit if present */ + if (conn->remote_conn) { + conn->remote_conn->remote_conn = NULL; + conn->remote_conn = NULL; + } + + if (conn->fd != -1) { + close(conn->fd); + conn->fd = -1; + } + + EVENT_OFF(conn->read_ev); + EVENT_OFF(conn->write_ev); + EVENT_OFF(conn->proc_msg_ev); + + mgmt_msg_destroy(ms); +} + +/* + * Client Connections + */ + +DECLARE_LIST(msg_server_list, struct msg_server, link); + +static struct msg_server_list_head msg_servers; + +static void msg_client_connect(struct msg_client *conn); + +static void msg_client_connect_timer(struct event *thread) +{ + msg_client_connect(EVENT_ARG(thread)); +} + +static void msg_client_sched_connect(struct msg_client *client, + unsigned long msec) +{ + struct msg_conn *conn = &client->conn; + const char *dbgtag = conn->debug ? conn->mstate.idtag : NULL; + + MGMT_MSG_DBG(dbgtag, "connection retry in %lu msec", msec); + if (msec) + event_add_timer_msec(conn->loop, msg_client_connect_timer, + client, msec, &client->conn_retry_tmr); + else + event_add_event(conn->loop, msg_client_connect_timer, client, 0, + &client->conn_retry_tmr); +} + +static int msg_client_connect_short_circuit(struct msg_client *client) +{ + struct msg_conn *server_conn; + struct msg_server *server; + const char *dbgtag = + client->conn.debug ? client->conn.mstate.idtag : NULL; + union sockunion su = {}; + int sockets[2]; + + frr_each (msg_server_list, &msg_servers, server) + if (!strcmp(server->sopath, client->sopath)) + break; + if (!server) { + MGMT_MSG_DBG(dbgtag, + "no short-circuit server available yet for %s", + client->sopath); + return -1; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets)) { + MGMT_MSG_ERR( + &client->conn.mstate, + "socketpair failed trying to short-circuit connection on %s: %s", + client->sopath, safe_strerror(errno)); + return -1; + } + + /* client side */ + client->conn.fd = sockets[0]; + set_nonblocking(sockets[0]); + setsockopt_so_sendbuf(sockets[0], client->conn.mstate.max_write_buf); + setsockopt_so_recvbuf(sockets[0], client->conn.mstate.max_read_buf); + + /* server side */ + memset(&su, 0, sizeof(union sockunion)); + server_conn = server->create(sockets[1], &su); + server_conn->debug = DEBUG_MODE_CHECK(server->debug, DEBUG_MODE_ALL) + ? true + : false; + + client->conn.remote_conn = server_conn; + server_conn->remote_conn = &client->conn; + + MGMT_MSG_DBG( + dbgtag, + "short-circuit connection on %s server %s:%d to client %s:%d", + client->sopath, server_conn->mstate.idtag, server_conn->fd, + client->conn.mstate.idtag, client->conn.fd); + + MGMT_MSG_DBG( + server_conn->debug ? server_conn->mstate.idtag : NULL, + "short-circuit connection on %s client %s:%d to server %s:%d", + client->sopath, client->conn.mstate.idtag, client->conn.fd, + server_conn->mstate.idtag, server_conn->fd); + + return 0; +} + + +/* Connect and start reading from the socket */ +static void msg_client_connect(struct msg_client *client) +{ + struct msg_conn *conn = &client->conn; + const char *dbgtag = conn->debug ? conn->mstate.idtag : NULL; + + if (!client->short_circuit_ok) + conn->fd = + mgmt_msg_connect(client->sopath, MSG_CONN_SEND_BUF_SIZE, + MSG_CONN_RECV_BUF_SIZE, dbgtag); + else if (msg_client_connect_short_circuit(client)) + conn->fd = -1; + + if (conn->fd == -1) + /* retry the connection */ + msg_client_sched_connect(client, + MSG_CONN_DEFAULT_CONN_RETRY_MSEC); + else if (client->notify_connect && client->notify_connect(client)) + /* client connect notify failed */ + msg_conn_disconnect(conn, true); + else + /* start reading */ + msg_conn_sched_read(conn); +} + +void msg_client_init(struct msg_client *client, struct event_loop *tm, + const char *sopath, + int (*notify_connect)(struct msg_client *client), + int (*notify_disconnect)(struct msg_conn *client), + void (*handle_msg)(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *client), + size_t max_read_buf, size_t max_write_buf, + size_t max_msg_sz, bool short_circuit_ok, + const char *idtag, bool debug) +{ + struct msg_conn *conn = &client->conn; + memset(client, 0, sizeof(*client)); + + conn->loop = tm; + conn->fd = -1; + conn->handle_msg = handle_msg; + conn->notify_disconnect = notify_disconnect; + conn->is_client = true; + conn->debug = debug; + client->short_circuit_ok = short_circuit_ok; + client->sopath = strdup(sopath); + client->notify_connect = notify_connect; + + mgmt_msg_init(&conn->mstate, max_read_buf, max_write_buf, max_msg_sz, + idtag); + + /* Start trying to connect to server */ + msg_client_sched_connect(client, 0); +} + +void msg_client_cleanup(struct msg_client *client) +{ + assert(client->conn.is_client); + + EVENT_OFF(client->conn_retry_tmr); + free(client->sopath); + + msg_conn_cleanup(&client->conn); +} + + +/* + * Server-side connections + */ + +static void msg_server_accept(struct event *event) +{ + struct msg_server *server = EVENT_ARG(event); + struct msg_conn *conn; + union sockunion su; + int fd; + + if (server->fd < 0) + return; + + /* We continue hearing server listen socket. */ + event_add_read(server->loop, msg_server_accept, server, server->fd, + &server->listen_ev); + + memset(&su, 0, sizeof(union sockunion)); + + /* We can handle IPv4 or IPv6 socket. */ + fd = sockunion_accept(server->fd, &su); + if (fd < 0) { + zlog_err("Failed to accept %s client connection: %s", + server->idtag, safe_strerror(errno)); + return; + } + set_nonblocking(fd); + set_cloexec(fd); + + DEBUGD(server->debug, "Accepted new %s connection", server->idtag); + + conn = server->create(fd, &su); + if (conn) + conn->debug = DEBUG_MODE_CHECK(server->debug, DEBUG_MODE_ALL) + ? true + : false; +} + +int msg_server_init(struct msg_server *server, const char *sopath, + struct event_loop *loop, + struct msg_conn *(*create)(int fd, union sockunion *su), + const char *idtag, struct debug *debug) +{ + int ret; + int sock; + struct sockaddr_un addr; + mode_t old_mask; + + memset(server, 0, sizeof(*server)); + server->fd = -1; + + sock = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); + if (sock < 0) { + zlog_err("Failed to create %s server socket: %s", server->idtag, + safe_strerror(errno)); + goto fail; + } + + addr.sun_family = AF_UNIX, + strlcpy(addr.sun_path, sopath, sizeof(addr.sun_path)); + unlink(addr.sun_path); + old_mask = umask(0077); + ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + zlog_err("Failed to bind %s server socket to '%s': %s", + server->idtag, addr.sun_path, safe_strerror(errno)); + umask(old_mask); + goto fail; + } + umask(old_mask); + + ret = listen(sock, MGMTD_MAX_CONN); + if (ret < 0) { + zlog_err("Failed to listen on %s server socket: %s", + server->idtag, safe_strerror(errno)); + goto fail; + } + + server->fd = sock; + server->loop = loop; + server->sopath = strdup(sopath); + server->idtag = strdup(idtag); + server->create = create; + server->debug = debug; + + msg_server_list_add_head(&msg_servers, server); + + event_add_read(server->loop, msg_server_accept, server, server->fd, + &server->listen_ev); + + + DEBUGD(debug, "Started %s server, listening on %s", idtag, sopath); + return 0; + +fail: + if (sock >= 0) + close(sock); + server->fd = -1; + return -1; +} + +void msg_server_cleanup(struct msg_server *server) +{ + DEBUGD(server->debug, "Closing %s server", server->idtag); + + if (server->listen_ev) + EVENT_OFF(server->listen_ev); + + msg_server_list_del(&msg_servers, server); + + if (server->fd >= 0) + close(server->fd); + free((char *)server->sopath); + free((char *)server->idtag); + + memset(server, 0, sizeof(*server)); + server->fd = -1; +} + +/* + * Initialize and start reading from the accepted socket + * + * notify_connect - only called for disconnect i.e., connected == false + */ +void msg_conn_accept_init(struct msg_conn *conn, struct event_loop *tm, int fd, + int (*notify_disconnect)(struct msg_conn *conn), + void (*handle_msg)(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn), + size_t max_read, size_t max_write, size_t max_size, + const char *idtag) +{ + conn->loop = tm; + conn->fd = fd; + conn->notify_disconnect = notify_disconnect; + conn->handle_msg = handle_msg; + conn->is_client = false; + + mgmt_msg_init(&conn->mstate, max_read, max_write, max_size, idtag); + + /* start reading */ + msg_conn_sched_read(conn); + + /* Make socket non-blocking. */ + set_nonblocking(conn->fd); + setsockopt_so_sendbuf(conn->fd, MSG_CONN_SEND_BUF_SIZE); + setsockopt_so_recvbuf(conn->fd, MSG_CONN_RECV_BUF_SIZE); +} + +struct msg_conn * +msg_server_conn_create(struct event_loop *tm, int fd, + int (*notify_disconnect)(struct msg_conn *conn), + void (*handle_msg)(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn), + size_t max_read, size_t max_write, size_t max_size, + void *user, const char *idtag) +{ + struct msg_conn *conn = XMALLOC(MTYPE_MSG_CONN, sizeof(*conn)); + memset(conn, 0, sizeof(*conn)); + msg_conn_accept_init(conn, tm, fd, notify_disconnect, handle_msg, + max_read, max_write, max_size, idtag); + conn->user = user; + return conn; +} + +void msg_server_conn_delete(struct msg_conn *conn) +{ + if (!conn) + return; + msg_conn_cleanup(conn); + XFREE(MTYPE_MSG_CONN, conn); +} diff --git a/lib/mgmt_msg.h b/lib/mgmt_msg.h new file mode 100644 index 0000000..6bdc9a6 --- /dev/null +++ b/lib/mgmt_msg.h @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * March 6 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ +#ifndef _MGMT_MSG_H +#define _MGMT_MSG_H + +#include "memory.h" +#include "stream.h" +#include "frrevent.h" + +DECLARE_MTYPE(MSG_CONN); + +/* + * Messages on the stream start with a marker that encodes a version octet. + */ +#define MGMT_MSG_MARKER_PFX (0x23232300u) /* ASCII - "###\ooo"*/ +#define MGMT_MSG_IS_MARKER(x) (((x)&0xFFFFFF00u) == MGMT_MSG_MARKER_PFX) +#define MGMT_MSG_MARKER(version) (MGMT_MSG_MARKER_PFX | (version)) +#define MGMT_MSG_MARKER_VERSION(x) (0xFF & (x)) + +#define MGMT_MSG_VERSION_PROTOBUF 0 +#define MGMT_MSG_VERSION_NATIVE 1 + +/* The absolute maximum message size (16MB) */ +#define MGMT_MSG_MAX_MSG_ALLOC_LEN (16 * 1024 * 1024) + +struct mgmt_msg_state { + struct stream *ins; + struct stream *outs; + struct stream_fifo inq; + struct stream_fifo outq; + uint64_t nrxm; /* number of received messages */ + uint64_t nrxb; /* number of received bytes */ + uint64_t ntxm; /* number of sent messages */ + uint64_t ntxb; /* number of sent bytes */ + size_t max_read_buf; /* should replace with max time value */ + size_t max_write_buf; /* should replace with max time value */ + size_t max_msg_sz; + char *idtag; /* identifying tag for messages */ +}; + +struct mgmt_msg_hdr { + uint32_t marker; + uint32_t len; +}; + +enum mgmt_msg_rsched { + MSR_SCHED_BOTH, /* schedule both queue and read */ + MSR_SCHED_STREAM, /* schedule read */ + MSR_DISCONNECT, /* disconnect and start reconnecting */ +}; + +enum mgmt_msg_wsched { + MSW_SCHED_NONE, /* no scheduling required */ + MSW_SCHED_STREAM, /* schedule writing */ + MSW_DISCONNECT, /* disconnect and start reconnecting */ +}; + +struct msg_conn; + + +extern int mgmt_msg_connect(const char *path, size_t sendbuf, size_t recvbuf, + const char *dbgtag); +extern bool mgmt_msg_procbufs(struct mgmt_msg_state *ms, + void (*handle_msg)(uint8_t version, uint8_t *msg, + size_t msglen, void *user), + void *user, bool debug); +extern enum mgmt_msg_rsched mgmt_msg_read(struct mgmt_msg_state *ms, int fd, + bool debug); +extern size_t mgmt_msg_reset_writes(struct mgmt_msg_state *ms); +extern int mgmt_msg_send_msg(struct mgmt_msg_state *ms, uint8_t version, + void *msg, size_t len, + size_t (*packf)(void *msg, void *buf), bool debug); +extern enum mgmt_msg_wsched mgmt_msg_write(struct mgmt_msg_state *ms, int fd, + bool debug); + +extern void mgmt_msg_destroy(struct mgmt_msg_state *state); + +extern void mgmt_msg_init(struct mgmt_msg_state *ms, size_t max_read_buf, + size_t max_write_buf, size_t max_msg_sz, + const char *idtag); + +/* + * Connections + */ + +struct msg_conn { + int fd; + struct mgmt_msg_state mstate; + struct event_loop *loop; + struct event *read_ev; + struct event *write_ev; + struct event *proc_msg_ev; + struct msg_conn *remote_conn; + int (*notify_disconnect)(struct msg_conn *conn); + void (*handle_msg)(uint8_t version, uint8_t *data, size_t len, + struct msg_conn *conn); + void *user; + uint short_circuit_depth; + bool is_short_circuit; /* true when the message being handled is SC */ + bool is_client; + bool debug; +}; + + +/* + * `notify_disconnect` is not called when `msg_conn_cleanup` is called for a + * msg_conn which is currently connected. The socket is closed but there is no + * notification. + */ +extern void msg_conn_cleanup(struct msg_conn *conn); +extern void msg_conn_disconnect(struct msg_conn *conn, bool reconnect); +extern int msg_conn_send_msg(struct msg_conn *client, uint8_t version, + void *msg, size_t mlen, + size_t (*packf)(void *, void *), + bool short_circuit_ok); + +/* + * Client-side Connections + */ + +struct msg_client { + struct msg_conn conn; + struct event *conn_retry_tmr; + char *sopath; + int (*notify_connect)(struct msg_client *client); + bool short_circuit_ok; +}; + +/* + * `notify_disconnect` is not called when `msg_client_cleanup` is called for a + * msg_client which is currently connected. The socket is closed but there is no + * notification. + */ +extern void msg_client_cleanup(struct msg_client *client); + +/* + * If `short_circuit_ok` is true, then the client-server connection will use a + * socketpair() rather than a unix-domain socket. This must be passed true if + * you wish to send messages short-circuit later. + * + * `notify_disconnect` is not called when the user `msg_client_cleanup` is + * called for a client which is currently connected. The socket is closed + * but there is no notification. + */ +extern void +msg_client_init(struct msg_client *client, struct event_loop *tm, + const char *sopath, + int (*notify_connect)(struct msg_client *client), + int (*notify_disconnect)(struct msg_conn *client), + void (*handle_msg)(uint8_t version, uint8_t *data, size_t len, + struct msg_conn *client), + size_t max_read_buf, size_t max_write_buf, size_t max_msg_sz, + bool short_circuit_ok, const char *idtag, bool debug); + +/* + * Server-side Connections + */ +#define MGMTD_MAX_CONN 32 + +PREDECL_LIST(msg_server_list); + +struct msg_server { + int fd; + struct msg_server_list_item link; + struct event_loop *loop; + struct event *listen_ev; + const char *sopath; + const char *idtag; + struct msg_conn *(*create)(int fd, union sockunion *su); + struct debug *debug; +}; + +extern int msg_server_init(struct msg_server *server, const char *sopath, + struct event_loop *loop, + struct msg_conn *(*create)(int fd, + union sockunion *su), + const char *idtag, struct debug *debug); +extern void msg_server_cleanup(struct msg_server *server); + +/* + * `notify_disconnect` is not called when the user `msg_conn_cleanup` is + * called for a client which is currently connected. The socket is closed + * but there is no notification. + */ +struct msg_conn * +msg_server_conn_create(struct event_loop *tm, int fd, + int (*notify_disconnect)(struct msg_conn *conn), + void (*handle_msg)(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn), + size_t max_read, size_t max_write, size_t max_size, + void *user, const char *idtag); + +extern void msg_server_conn_delete(struct msg_conn *conn); + +extern void +msg_conn_accept_init(struct msg_conn *conn, struct event_loop *tm, int fd, + int (*notify_disconnect)(struct msg_conn *conn), + void (*handle_msg)(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn), + size_t max_read, size_t max_write, size_t max_size, + const char *idtag); + +#endif /* _MGMT_MSG_H */ diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c new file mode 100644 index 0000000..39ce9ab --- /dev/null +++ b/lib/mgmt_msg_native.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * June 29 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ +#include +#include "mgmt_msg_native.h" + +DEFINE_MGROUP(MSG_NATIVE, "Native message allocations"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_ERROR, "native error msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC, "native RPC msg"); +DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC_REPLY, "native RPC reply msg"); + +int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, + uint64_t req_id, bool short_circuit_ok, + int16_t error, const char *errfmt, va_list ap) +{ + struct mgmt_msg_error *msg; + char *errstr; + ssize_t slen; + int ret; + + errstr = darr_vsprintf(errfmt, ap); + slen = strlen(errstr); + + msg = mgmt_msg_native_alloc_msg(typeof(*msg), slen + 1, + MTYPE_MSG_NATIVE_ERROR); + msg->refer_id = sess_or_txn_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_ERROR; + msg->error = error; + strlcpy(msg->errstr, errstr, slen + 1); + darr_free(errstr); + + if (conn->debug) + zlog_debug("Sending error %d session-id %" PRIu64 + " req-id %" PRIu64 " scok %d errstr: %s", + error, sess_or_txn_id, req_id, short_circuit_ok, + msg->errstr); + + ret = mgmt_msg_native_send_msg(conn, msg, short_circuit_ok); + mgmt_msg_native_free_msg(msg); + return ret; +} diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h new file mode 100644 index 0000000..21f702c --- /dev/null +++ b/lib/mgmt_msg_native.h @@ -0,0 +1,696 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * June 29 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + * Public APIs: + * + * The message type codes and corresponding message data definitions for + * front-end client messages represent a public API, as such any changes should + * only be made according to backward compatible principles (basically never, + * just use a new message type). Back-end clients being always compiled with FRR + * can be updated (although one should take care in modifying BE messages as it + * could impact private back-end client implementations which will then need to + * be updated by their owners). + */ + +#ifndef _FRR_MGMT_MSG_NATIVE_H_ +#define _FRR_MGMT_MSG_NATIVE_H_ + +#ifdef __cplusplus +extern "C" { +#elif 0 +} +#endif + +#include +#include "compiler.h" +#include "darr.h" +#include "memory.h" +#include "mgmt_msg.h" +#include "mgmt_defines.h" +#include "northbound.h" + +#include + +/* + * ================== + * Native Message API + * ================== + * + * ----------------------- + * Defining A New Message: + * ----------------------- + * + * 1) Start with `struct mgmt_msg_header` as the first (unnamed) field. + * + * 2) Add fixed-width fields. Add on natural aligned boundaries (*) + * + * 3) [Optional] Add a zero-length variable field. Add aligned on a 64-bit + * boundary, this is done so that: `value = (HDR + 1)` works. + * + * 4) Define a new MTYPE for the new message type (see DECLARE_MTYPE below + * as well as the paired DEFINE_MTYPE in mgmt_msg_native.c) + * + * These rules are so the messages may be read from and written directly to + * "the wire", easily, using common programming languages (e.g., C, rust, go, + * python, ...) + * + * (*) Natrual aligned boundaries, i.e., uint16_t on 2-byte boundary, uint64_t + * on 8-byte boundaries, ...) + * + * ------------------------------ + * Allocating New Native Messages + * ------------------------------ + * + * For fixed-length and variable length messages one should allocate new + * messages with the mgmt_msg_native_alloc_msg() passing in the newly defined + * MTYPE. Likewise, to free the message one should use + * mgmt_msg_native_free_msg(). + * + * Unknown Variable Length Messages: + * --------------------------------- + * + * If using a zero-length variable length field and the length is not known at + * message creation time, you can use the `native` API function + * mgmt_msg_native_append() to add data to the end of the message, or if a more + * full set of operations are required, the darr_xxxx() API is also available as + * in the Advanced section below. + * + * Notable API Functions: + * --------------------------------- + * + * mgmt_msg_native_alloc_msg() - Allocate a native msg. + * mgmt_msg_native_free_msg() - Free a native msg. + * mgmt_msg_native_append() - Append data to the end of the msg. + * mgmt_msg_native_get_msg_len() - Get the total length of the msg. + * mgmt_msg_native_send_msg() - Send the message. + * + * mgmt_msg_native_xpath_encode() - Encode xpath in xpath, data format message. + * mgmt_msg_native_xpath_data_decode() - Decode xpath, data format message. + * mgmt_msg_native_xpath_decode() - Get the xpath, from xpath, data format message. + * mgmt_msg_native_data_decode() - Get the secondary data from xpath, data message. + * mgmt_msg_native_data_len_decode() - Get length of secondary data. + * + * ------------------------------------- + * [Advanced Use] Dynamic Array Messages + * ------------------------------------- + * + * NOTE: Most users can simply use mgmt_msg_native_append() and skip this + * section. + * + * This section is only important to understand if you wish to utilize the fact + * that native messages allocated with mgmt_msg_native_alloc_msg are + * actually allocated as uint8_t dynamic arrays (`darr`). + * + * You can utilize all the darr_xxxx() API to manipulate the variable length + * message data in a native message. To do so you simply need to understand that + * the native message is actually a `uint8_t *` darr. So, for example, to append + * data to the end of a message one could do the following: + * + * void append_metric_path(struct mgmt_msg_my_msg *msg) + * { + * msg = (struct mggm_msg_my_msg *) + * darr_strcat((uint8_t *)msg, "/metric"); + * + * // ... + * } + * + * NOTE: If reallocs happen the original passed in pointer will be updated; + * however, any other pointers into the message will become invalid, and so they + * should always be discarded or reinitialized after using any reallocating + * darr_xxx() API functions. + * + * void append_metric_path(struct mgmt_msg_my_msg *msg) + * { + * char *xpath = msg->xpath; // pointer into message + * + * darr_in_strcat((uint8_t *)msg, "/metric"); + * // msg may have been updated to point at new memory + * + * xpath = NULL; // now invalid + * xpath = msg->xpath; // reinitialize + * // ... + * } + * + * Rather than worry about this, it's typical when using dynamic arrays to always + * work from the main pointer to the dynamic array, rather than caching multiple + * pointers into the data. Modern compilers will optimize the code so that it + * adds no extra execution cost. + * + * void append_metric_path(struct mgmt_msg_my_msg *msg) + * { + * darr_in_strcat((uint8_t *)msg, "/metric"); + * + * // Use `msg->xpath` directly rather creating and using an + * // `xpath = msg->xpath` local variable. + * + * if (strcmp(msg->xpath, "foobar/metric")) { + * // ... + * } + * } + * + */ + +DECLARE_MTYPE(MSG_NATIVE_MSG); +DECLARE_MTYPE(MSG_NATIVE_ERROR); +DECLARE_MTYPE(MSG_NATIVE_GET_TREE); +DECLARE_MTYPE(MSG_NATIVE_TREE_DATA); +DECLARE_MTYPE(MSG_NATIVE_GET_DATA); +DECLARE_MTYPE(MSG_NATIVE_NOTIFY); +DECLARE_MTYPE(MSG_NATIVE_EDIT); +DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY); +DECLARE_MTYPE(MSG_NATIVE_RPC); +DECLARE_MTYPE(MSG_NATIVE_RPC_REPLY); + +/* + * Native message codes + */ +#define MGMT_MSG_CODE_ERROR 0 /* Public API */ +#define MGMT_MSG_CODE_GET_TREE 1 /* BE only, non-public API */ +#define MGMT_MSG_CODE_TREE_DATA 2 /* Public API */ +#define MGMT_MSG_CODE_GET_DATA 3 /* Public API */ +#define MGMT_MSG_CODE_NOTIFY 4 /* Public API */ +#define MGMT_MSG_CODE_EDIT 5 /* Public API */ +#define MGMT_MSG_CODE_EDIT_REPLY 6 /* Public API */ +#define MGMT_MSG_CODE_RPC 7 /* Public API */ +#define MGMT_MSG_CODE_RPC_REPLY 8 /* Public API */ + +/* + * Datastores + */ +#define MGMT_MSG_DATASTORE_STARTUP 0 +#define MGMT_MSG_DATASTORE_CANDIDATE 1 +#define MGMT_MSG_DATASTORE_RUNNING 2 +#define MGMT_MSG_DATASTORE_OPERATIONAL 3 + +/* + * Formats + */ +#define MGMT_MSG_FORMAT_XML 1 +#define MGMT_MSG_FORMAT_JSON 2 +#define MGMT_MSG_FORMAT_BINARY 3 /* non-standard libyang internal format */ + +/* + * Now we're using LYD_FORMAT directly to avoid mapping code, but having our + * own definitions allows us to create such a mapping in the future if libyang + * makes a backwards incompatible change. + */ +_Static_assert(MGMT_MSG_FORMAT_XML == LYD_XML, "Format mismatch"); +_Static_assert(MGMT_MSG_FORMAT_JSON == LYD_JSON, "Format mismatch"); +_Static_assert(MGMT_MSG_FORMAT_BINARY == LYD_LYB, "Format mismatch"); + +/** + * struct mgmt_msg_header - Header common to all native messages. + * + * @code: the actual type of the message. + * @resv: Set to zero, ignore on receive. + * @vsplit: If a variable section is split in 2, the length of first part. + * @refer_id: the session, txn, conn, etc, this message is associated with. + * @req_id: the request this message is for. + */ +struct mgmt_msg_header { + uint16_t code; + uint16_t resv; + uint32_t vsplit; + uint64_t refer_id; + uint64_t req_id; +}; +_Static_assert(sizeof(struct mgmt_msg_header) == 3 * 8, "Bad padding"); +_Static_assert(sizeof(struct mgmt_msg_header) == + offsetof(struct mgmt_msg_header, req_id) + + sizeof(((struct mgmt_msg_header *)0)->req_id), + "Size mismatch"); + +/** + * struct mgmt_msg_error - Common error message. + * + * @error: An error value. + * @errst: Description of error can be 0 length. + * + * This common error message can be used for replies for many msg requests + * (req_id). + */ +struct mgmt_msg_error { + struct mgmt_msg_header; + int16_t error; + uint8_t resv2[6]; + + alignas(8) char errstr[]; +}; +_Static_assert(sizeof(struct mgmt_msg_error) == + offsetof(struct mgmt_msg_error, errstr), + "Size mismatch"); + +/** + * struct mgmt_msg_get_tree - backend oper data request. + * + * @result_type: ``LYD_FORMAT`` for the returned result. + * @xpath: the query for the data to return. + */ +struct mgmt_msg_get_tree { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) char xpath[]; +}; +_Static_assert(sizeof(struct mgmt_msg_get_tree) == + offsetof(struct mgmt_msg_get_tree, xpath), + "Size mismatch"); + +/** + * struct mgmt_msg_tree_data - Message carrying tree data. + * + * @partial_error: If the full result could not be returned do to this error. + * @result_type: ``LYD_FORMAT`` for format of the @result value. + * @more: if this is a partial return and there will be more coming. + * @result: The tree data in @result_type format. + * + */ +struct mgmt_msg_tree_data { + struct mgmt_msg_header; + int8_t partial_error; + uint8_t result_type; + uint8_t more; + uint8_t resv2[5]; + + alignas(8) uint8_t result[]; +}; +_Static_assert(sizeof(struct mgmt_msg_tree_data) == + offsetof(struct mgmt_msg_tree_data, result), + "Size mismatch"); + +/* Flags for get-data request */ +#define GET_DATA_FLAG_STATE 0x01 /* include "config false" data */ +#define GET_DATA_FLAG_CONFIG 0x02 /* include "config true" data */ +#define GET_DATA_FLAG_EXACT 0x04 /* get exact data node instead of the full tree */ + +/* + * Modes of reporting default values. Non-default values are always reported. + * These options reflect "with-defaults" modes as defined in RFC 6243. + */ +#define GET_DATA_DEFAULTS_EXPLICIT 0 /* "explicit" */ +#define GET_DATA_DEFAULTS_TRIM 1 /* "trim" */ +#define GET_DATA_DEFAULTS_ALL 2 /* "report-all" */ +#define GET_DATA_DEFAULTS_ALL_ADD_TAG 3 /* "report-all-tagged" */ + +/** + * struct mgmt_msg_get_data - frontend get-data request. + * + * @result_type: ``LYD_FORMAT`` for the returned result. + * @flags: combination of ``GET_DATA_FLAG_*`` flags. + * @defaults: one of ``GET_DATA_DEFAULTS_*`` values. + * @xpath: the query for the data to return. + */ +struct mgmt_msg_get_data { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t flags; + uint8_t defaults; + uint8_t datastore; + uint8_t resv2[4]; + + alignas(8) char xpath[]; +}; +_Static_assert(sizeof(struct mgmt_msg_get_data) == + offsetof(struct mgmt_msg_get_data, xpath), + "Size mismatch"); + +/** + * struct mgmt_msg_notify_data - Message carrying notification data. + * + * @result_type: ``LYD_FORMAT`` for format of the @result value. + * @data: The xpath string of the notification followed by the tree data in + * @result_type format. + */ +struct mgmt_msg_notify_data { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_notify_data) == + offsetof(struct mgmt_msg_notify_data, data), + "Size mismatch"); + +#define EDIT_FLAG_IMPLICIT_LOCK 0x01 +#define EDIT_FLAG_IMPLICIT_COMMIT 0x02 + +#define EDIT_OP_CREATE 0 +#define EDIT_OP_DELETE 4 +#define EDIT_OP_MERGE 2 +#define EDIT_OP_REPLACE 5 +#define EDIT_OP_REMOVE 3 + +_Static_assert(EDIT_OP_CREATE == NB_OP_CREATE_EXCL, "Operation mismatch"); +_Static_assert(EDIT_OP_DELETE == NB_OP_DELETE, "Operation mismatch"); +_Static_assert(EDIT_OP_MERGE == NB_OP_MODIFY, "Operation mismatch"); +_Static_assert(EDIT_OP_REPLACE == NB_OP_REPLACE, "Operation mismatch"); +_Static_assert(EDIT_OP_REMOVE == NB_OP_DESTROY, "Operation mismatch"); + +/** + * struct mgmt_msg_edit - frontend edit request. + * + * @request_type: ``LYD_FORMAT`` for the @data. + * @flags: combination of ``EDIT_FLAG_*`` flags. + * @datastore: the datastore to edit. + * @operation: one of ``EDIT_OP_*`` operations. + * @data: the xpath followed by the tree data for the operation. + * for CREATE, xpath points to the parent node. + */ +struct mgmt_msg_edit { + struct mgmt_msg_header; + uint8_t request_type; + uint8_t flags; + uint8_t datastore; + uint8_t operation; + uint8_t resv2[4]; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_edit) == + offsetof(struct mgmt_msg_edit, data), + "Size mismatch"); + +/** + * struct mgmt_msg_edit_reply - frontend edit reply. + * + * @data: the xpath of the data node that was created. + */ +struct mgmt_msg_edit_reply { + struct mgmt_msg_header; + uint8_t resv2[8]; + + alignas(8) char data[]; +}; +_Static_assert(sizeof(struct mgmt_msg_edit_reply) == + offsetof(struct mgmt_msg_edit_reply, data), + "Size mismatch"); + +/** + * struct mgmt_msg_rpc - RPC/action request. + * + * @request_type: ``LYD_FORMAT`` for the @data. + * @data: the xpath followed by the tree data for the operation. + */ +struct mgmt_msg_rpc { + struct mgmt_msg_header; + uint8_t request_type; + uint8_t resv2[7]; + + alignas(8) char data[]; +}; + +_Static_assert(sizeof(struct mgmt_msg_rpc) == + offsetof(struct mgmt_msg_rpc, data), + "Size mismatch"); + +/** + * struct mgmt_msg_rpc_reply - RPC/action reply. + * + * @result_type: ``LYD_FORMAT`` for the @data. + * @data: the tree data for the reply. + */ +struct mgmt_msg_rpc_reply { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) char data[]; +}; + +_Static_assert(sizeof(struct mgmt_msg_rpc_reply) == + offsetof(struct mgmt_msg_rpc_reply, data), + "Size mismatch"); + +/* + * Validate that the message ends in a NUL terminating byte + */ +#define MGMT_MSG_VALIDATE_NUL_TERM(msgp, len) \ + ((len) >= sizeof(*msgp) + 1 && ((char *)msgp)[(len)-1] == 0) + + +/** + * Send a native message error to the other end of the connection. + * + * This function is normally used by the server-side to indicate a failure to + * process a client request. For this server side handling of client messages + * which expect a reply, either that reply or this error should be returned, as + * closing the connection is not allowed during message handling. + * + * Args: + * conn: the connection. + * sess_or_txn_id: Session ID (to FE client) or Txn ID (from BE client) + * req_id: which req_id this error is associated with. + * short_circuit_ok: if short circuit sending is OK. + * error: the error value + * errfmt: vprintfrr style format string + * ap: the variable args for errfmt. + * + * Return: + * The return value of ``msg_conn_send_msg``. + */ +extern int vmgmt_msg_native_send_error(struct msg_conn *conn, + uint64_t sess_or_txn_id, uint64_t req_id, + bool short_circuit_ok, int16_t error, + const char *errfmt, va_list ap) + PRINTFRR(6, 0); + +/** + * mgmt_msg_native_alloc_msg() - Create a native appendable msg. + * @msg_type: The message structure type. + * @var_len: The initial additional length to add to the message. + * @mem_type: The initial additional length to add to the message. + * + * This function takes a C type (e.g., `struct mgmt_msg_get_tree`) as an + * argument and returns a new native message. The newly allocated message + * can be used with the other `native` functions. + * + * Importantly the mgmt_msg_native_append() function can be used to add data + * to the end of the message, and mgmt_msg_get_native_msg_len() can be used + * to obtain the total length of the message (i.e., the fixed sized header plus + * the variable length data that has been appended). + * + * Additionally, a dynamic array (darr) pointer can be obtained using + * mgmt_msg_get_native_darr() which allows adding and manipulating the + * variable data that follows the fixed sized header. + * + * Return: A `msg_type` object created using a dynamic_array. + */ +#define mgmt_msg_native_alloc_msg(msg_type, var_len, mem_type) \ + ({ \ + uint8_t *buf = NULL; \ + (msg_type *)darr_append_nz_mt(buf, \ + sizeof(msg_type) + (var_len), \ + mem_type); \ + }) + +/** + * mgmt_msg_free_native_msg() - Free a native msg. + * @msg - pointer to message allocated by mgmt_msg_create_native_msg(). + */ +#define mgmt_msg_native_free_msg(msg) darr_free(msg) + +/** + * mgmt_msg_native_get_msg_len() - Get the total length of the msg. + * @msg: the native message. + * + * Return: the total length of the message, fixed + variable length. + */ +#define mgmt_msg_native_get_msg_len(msg) (darr_len((uint8_t *)(msg))) + +/** + * mgmt_msg_native_append() - Append data to the end of the msg. + * @msg: (IN/OUT) Pointer to the native message, variable may be updated. + * @data: data to append. + * @len: length of data to append. + * + * Append @data of length @len to the native message @msg. + * + * NOTE: Be aware @msg pointer may change as a result of reallocating the + * message to fit the new data. Any other pointers into the old message should + * be discarded. + * + * Return: a pointer to the newly appended data. + */ +#define mgmt_msg_native_append(msg, data, len) \ + ({ \ + uint8_t **darrp = mgmt_msg_native_get_darrp(msg); \ + uint8_t *p = darr_append_n(*darrp, len); \ + memcpy(p, data, len); \ + p; \ + }) + +/** + * mgmt_msg_native_send_msg(msg, short_circuit_ok) - Send a native msg. + * @conn: the mgmt_msg connection. + * @msg: the native message. + * @short_circuit_ok: True if short-circuit sending is required. + * + * Return: The error return value of msg_conn_send_msg(). + */ +#define mgmt_msg_native_send_msg(conn, msg, short_circuit_ok) \ + msg_conn_send_msg(conn, MGMT_MSG_VERSION_NATIVE, msg, \ + mgmt_msg_native_get_msg_len(msg), NULL, \ + short_circuit_ok) + +/** + * mgmt_msg_native_get_darrp() - Return a ptr to the dynamic array ptr. + * @msg: Pointer to the native message. + * + * NOTE: Most users can simply use mgmt_msg_native_append() instead of this. + * + * This function obtains a pointer to the dynamic byte array for this message, + * this array actually includes the message header if one is going to look at + * the length value. With that in mind any of the `darr_*()` functions/API may + * be used to manipulate the variable data at the end of the message. + * + * NOTE: The pointer returned is actually a pointer to the message pointer + * passed in to this function. This pointer to pointer is required so that + * realloc can be done inside the darr API. + * + * NOTE: If reallocs happen the original passed in pointer will be updated; + * however, any other pointers into the message will become invalid and so they + * should always be discarded after using the returned value. + * + * Example: + * + * void append_metric_path(struct mgmt_msg_my_msg *msg) + * { + * char *xpath = msg->xpath; // pointer into message + * uint8_t **darp; + * + * darrp = mgmt_msg_native_get_darrp(msg); + * darr_in_strcat(*darrp, "/metric"); + * + * xpath = NULL; // now invalid + * xpath = msg->xpath; + * } + * + * + * Return: A pointer to the first argument -- which is a pointer to a pointer to + * a dynamic array. + */ +#define mgmt_msg_native_get_darrp(msg) ((uint8_t **)&(msg)) + +/* ------------------------- */ +/* Encode and Decode Helpers */ +/* ------------------------- */ + +/** + * mgmt_msg_native_xpath_encode() - encode an xpath in a xpath, data message. + * @msg: Pointer to the native message. + * @xpath: The xpath string to encode. + * + * This function starts the encoding of a message that can be decoded with + * `mgmt_msg_native_xpath_data_decode()`. The variable length data is comprised + * of a NUL terminated string followed by some data of any format. This starts + * the first half of the encoding, after which one can simply append the + * secondary data to the message. + */ +#define mgmt_msg_native_xpath_encode(msg, xpath) \ + do { \ + size_t __slen = strlen(xpath) + 1; \ + mgmt_msg_native_append(msg, xpath, __slen); \ + (msg)->vsplit = __slen; \ + } while (0) + +/** + * mgmt_msg_native_xpath_data_decode() - decode an xpath, data format message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * @data: [OUT] Pointer to the data section of the variable data + * + * This function decodes a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The xpath string or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_xpath_data_decode(msg, msglen, __data) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__s = NULL; \ + if (msg->vsplit && msg->vsplit <= __len && \ + msg->data[msg->vsplit - 1] == 0) { \ + if (msg->vsplit < __len) \ + (__data) = msg->data + msg->vsplit; \ + else \ + (__data) = NULL; \ + __s = msg->data; \ + } \ + __s; \ + }) + +/** + * mgmt_msg_native_xpath_decode() - return the xpath from xpath, data message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function decodes the xpath from a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The xpath string or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_xpath_decode(msg, msglen) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__s = msg->data; \ + if (!msg->vsplit || msg->vsplit > __len || \ + __s[msg->vsplit - 1] != 0) \ + __s = NULL; \ + __s; \ + }) + +/** + * mgmt_msg_native_data_decode() - return the data from xpath, data message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function decodes the secondary data from a message that was encoded with + * `mgmt_msg_native_xpath_encode()`. The variable length data is comprised of a + * NUL terminated string followed by some data of any format. + * + * Return: + * The secondary data or NULL if there was an error decoding (i.e., the + * message is corrupt). + */ +#define mgmt_msg_native_data_decode(msg, msglen) \ + ({ \ + size_t __len = (msglen) - sizeof(*msg); \ + const char *__data = msg->data + msg->vsplit; \ + if (!msg->vsplit || msg->vsplit > __len || __data[-1] != 0) \ + __data = NULL; \ + __data; \ + }) + +/** + * mgmt_msg_native_data_len_decode() - len of data in xpath, data format message. + * @msg: Pointer to the native message. + * @msglen: Length of the message. + * + * This function returns the length of the secondary variable data from a + * message that was encoded with `mgmt_msg_native_xpath_encode()`. The variable + * length data is comprised of a NUL terminated string followed by some data of + * any format. + * + * Return: + * The length of the secondary variable data. The message is assumed to be + * validated as not corrupt already. + */ +#define mgmt_msg_native_data_len_decode(msg, msglen) \ + ((msglen) - sizeof(*msg) - msg->vsplit) + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_MGMT_MSG_NATIVE_H_ */ diff --git a/lib/mgmt_pb.h b/lib/mgmt_pb.h new file mode 100644 index 0000000..08bb748 --- /dev/null +++ b/lib/mgmt_pb.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD protobuf main header file + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_PB_H_ +#define _FRR_MGMTD_PB_H_ + +#include "lib/mgmt.pb-c.h" + +#define mgmt_yang_data_xpath_init(ptr) mgmtd__yang_data_xpath__init(ptr) + +#define mgmt_yang_data_value_init(ptr) mgmtd__yang_data_value__init(ptr) + +#define mgmt_yang_data_init(ptr) mgmtd__yang_data__init(ptr) + +#define mgmt_yang_data_reply_init(ptr) mgmtd__yang_data_reply__init(ptr) + +#define mgmt_yang_cfg_data_req_init(ptr) mgmtd__yang_cfg_data_req__init(ptr) + +#define mgmt_yang_get_data_req_init(ptr) mgmtd__yang_get_data_req__init(ptr) + +#endif /* _FRR_MGMTD_PB_H_ */ diff --git a/lib/mlag.c b/lib/mlag.c new file mode 100644 index 0000000..62d00ff --- /dev/null +++ b/lib/mlag.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* mlag generic code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include + +char *mlag_role2str(enum mlag_role role, char *buf, size_t size) +{ + switch (role) { + case MLAG_ROLE_NONE: + snprintf(buf, size, "NONE"); + break; + case MLAG_ROLE_PRIMARY: + snprintf(buf, size, "PRIMARY"); + break; + case MLAG_ROLE_SECONDARY: + snprintf(buf, size, "SECONDARY"); + break; + } + + return buf; +} + +char *mlag_lib_msgid_to_str(enum mlag_msg_type msg_type, char *buf, size_t size) +{ + switch (msg_type) { + case MLAG_REGISTER: + snprintf(buf, size, "Register"); + break; + case MLAG_DEREGISTER: + snprintf(buf, size, "De-Register"); + break; + case MLAG_MROUTE_ADD: + snprintf(buf, size, "Mroute add"); + break; + case MLAG_MROUTE_DEL: + snprintf(buf, size, "Mroute del"); + break; + case MLAG_DUMP: + snprintf(buf, size, "Mlag Replay"); + break; + case MLAG_MROUTE_ADD_BULK: + snprintf(buf, size, "Mroute Add Batch"); + break; + case MLAG_MROUTE_DEL_BULK: + snprintf(buf, size, "Mroute Del Batch"); + break; + case MLAG_STATUS_UPDATE: + snprintf(buf, size, "Mlag Status"); + break; + case MLAG_VXLAN_UPDATE: + snprintf(buf, size, "Mlag vxlan update"); + break; + case MLAG_PEER_FRR_STATUS: + snprintf(buf, size, "Mlag Peer FRR Status"); + break; + case MLAG_PIM_CFG_DUMP: + snprintf(buf, size, "Mlag Pim Configuration Dump"); + break; + case MLAG_MSG_NONE: + snprintf(buf, size, "Unknown %d", msg_type); + break; + } + return buf; +} + + +int mlag_lib_decode_mlag_hdr(struct stream *s, struct mlag_msg *msg, + size_t *length) +{ +#define LIB_MLAG_HDR_LENGTH 8 + if (s == NULL || msg == NULL) + return -1; + + *length = stream_get_endp(s); + + if (*length < LIB_MLAG_HDR_LENGTH) + return -1; + + *length -= LIB_MLAG_HDR_LENGTH; + + STREAM_GETL(s, msg->msg_type); + STREAM_GETW(s, msg->data_len); + STREAM_GETW(s, msg->msg_cnt); + + return 0; +stream_failure: + return -1; +} + +#define MLAG_MROUTE_ADD_LENGTH \ + (VRF_NAMSIZ + IFNAMSIZ + 4 + 4 + 4 + 4 + 1 + 1 + 4) + +int mlag_lib_decode_mroute_add(struct stream *s, struct mlag_mroute_add *msg, + size_t *length) +{ + if (s == NULL || msg == NULL || *length < MLAG_MROUTE_ADD_LENGTH) + return -1; + + STREAM_GET(msg->vrf_name, s, VRF_NAMSIZ); + STREAM_GETL(s, msg->source_ip); + STREAM_GETL(s, msg->group_ip); + STREAM_GETL(s, msg->cost_to_rp); + STREAM_GETL(s, msg->owner_id); + STREAM_GETC(s, msg->am_i_dr); + STREAM_GETC(s, msg->am_i_dual_active); + STREAM_GETL(s, msg->vrf_id); + STREAM_GET(msg->intf_name, s, IFNAMSIZ); + + return 0; +stream_failure: + return -1; +} + +#define MLAG_MROUTE_DEL_LENGTH (VRF_NAMSIZ + IFNAMSIZ + 4 + 4 + 4 + 4) + +int mlag_lib_decode_mroute_del(struct stream *s, struct mlag_mroute_del *msg, + size_t *length) +{ + if (s == NULL || msg == NULL || *length < MLAG_MROUTE_DEL_LENGTH) + return -1; + + STREAM_GET(msg->vrf_name, s, VRF_NAMSIZ); + STREAM_GETL(s, msg->source_ip); + STREAM_GETL(s, msg->group_ip); + STREAM_GETL(s, msg->owner_id); + STREAM_GETL(s, msg->vrf_id); + STREAM_GET(msg->intf_name, s, IFNAMSIZ); + + return 0; +stream_failure: + return -1; +} + +int mlag_lib_decode_mlag_status(struct stream *s, struct mlag_status *msg) +{ + if (s == NULL || msg == NULL) + return -1; + + STREAM_GET(msg->peerlink_rif, s, IFNAMSIZ); + STREAM_GETL(s, msg->my_role); + STREAM_GETL(s, msg->peer_state); + return 0; +stream_failure: + return -1; +} + +int mlag_lib_decode_vxlan_update(struct stream *s, struct mlag_vxlan *msg) +{ + if (s == NULL || msg == NULL) + return -1; + + STREAM_GETL(s, msg->anycast_ip); + STREAM_GETL(s, msg->local_ip); + return 0; + +stream_failure: + return -1; +} + +int mlag_lib_decode_frr_status(struct stream *s, struct mlag_frr_status *msg) +{ + if (s == NULL || msg == NULL) + return -1; + + STREAM_GETL(s, msg->frr_state); + return 0; +stream_failure: + return -1; +} diff --git a/lib/mlag.h b/lib/mlag.h new file mode 100644 index 0000000..91c550b --- /dev/null +++ b/lib/mlag.h @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* mlag header. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __MLAG_H__ +#define __MLAG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lib/if.h" +#include "lib/vrf.h" +#include "lib/stream.h" + +#define MLAG_MSG_NULL_PAYLOAD 0 +#define MLAG_MSG_NO_BATCH 1 +#define MLAG_BUF_LIMIT 2048 + +enum mlag_role { + MLAG_ROLE_NONE, + MLAG_ROLE_PRIMARY, + MLAG_ROLE_SECONDARY +}; + +enum mlag_state { + MLAG_STATE_DOWN, + MLAG_STATE_RUNNING, +}; + +enum mlag_frr_state { + MLAG_FRR_STATE_NONE, + MLAG_FRR_STATE_DOWN, + MLAG_FRR_STATE_UP, +}; + +enum mlag_owner { + MLAG_OWNER_NONE, + MLAG_OWNER_INTERFACE, + MLAG_OWNER_VXLAN, +}; + +/* + * This message definition should match mlag.proto + * Because message registration is based on this + */ +enum mlag_msg_type { + MLAG_MSG_NONE = 0, + MLAG_REGISTER = 1, + MLAG_DEREGISTER = 2, + MLAG_STATUS_UPDATE = 3, + MLAG_MROUTE_ADD = 4, + MLAG_MROUTE_DEL = 5, + MLAG_DUMP = 6, + MLAG_MROUTE_ADD_BULK = 7, + MLAG_MROUTE_DEL_BULK = 8, + MLAG_PIM_CFG_DUMP = 10, + MLAG_VXLAN_UPDATE = 11, + MLAG_PEER_FRR_STATUS = 12, +}; + +struct mlag_frr_status { + enum mlag_frr_state frr_state; +}; + +struct mlag_status { + char peerlink_rif[IFNAMSIZ]; + enum mlag_role my_role; + enum mlag_state peer_state; +}; + +#define MLAG_ROLE_STRSIZE 16 + +struct mlag_vxlan { + uint32_t anycast_ip; + uint32_t local_ip; +}; + +struct mlag_mroute_add { + char vrf_name[VRF_NAMSIZ]; + uint32_t source_ip; + uint32_t group_ip; + uint32_t cost_to_rp; + enum mlag_owner owner_id; + bool am_i_dr; + bool am_i_dual_active; + vrf_id_t vrf_id; + char intf_name[IFNAMSIZ]; +}; + +struct mlag_mroute_del { + char vrf_name[VRF_NAMSIZ]; + uint32_t source_ip; + uint32_t group_ip; + enum mlag_owner owner_id; + vrf_id_t vrf_id; + char intf_name[IFNAMSIZ]; +}; + +struct mlag_msg { + enum mlag_msg_type msg_type; + uint16_t data_len; + uint16_t msg_cnt; + uint8_t data[0]; +} __attribute__((packed)); + + +extern char *mlag_role2str(enum mlag_role role, char *buf, size_t size); +extern char *mlag_lib_msgid_to_str(enum mlag_msg_type msg_type, char *buf, + size_t size); +extern int mlag_lib_decode_mlag_hdr(struct stream *s, struct mlag_msg *msg, + size_t *length); +extern int mlag_lib_decode_mroute_add(struct stream *s, + struct mlag_mroute_add *msg, + size_t *length); +extern int mlag_lib_decode_mroute_del(struct stream *s, + struct mlag_mroute_del *msg, + size_t *length); +extern int mlag_lib_decode_mlag_status(struct stream *s, + struct mlag_status *msg); +extern int mlag_lib_decode_vxlan_update(struct stream *s, + struct mlag_vxlan *msg); +extern int mlag_lib_decode_frr_status(struct stream *s, + struct mlag_frr_status *msg); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/module.c b/lib/module.c new file mode 100644 index 0000000..af7d20c --- /dev/null +++ b/lib/module.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "module.h" +#include "memory.h" +#include "lib/version.h" +#include "printfrr.h" + +DEFINE_MTYPE_STATIC(LIB, MODULE_LOADNAME, "Module loading name"); +DEFINE_MTYPE_STATIC(LIB, MODULE_LOADARGS, "Module loading arguments"); +DEFINE_MTYPE_STATIC(LIB, MODULE_LOAD_ERR, "Module loading error"); + +static struct frrmod_info frrmod_default_info = { + .name = "libfrr", + .version = FRR_VERSION, + .description = "libfrr core module", +}; +union _frrmod_runtime_u frrmod_default = { + .r = + { + .info = &frrmod_default_info, + .finished_loading = 1, + }, +}; + +XREF_SETUP(); + +// if defined(HAVE_SYS_WEAK_ALIAS_ATTRIBUTE) +// union _frrmod_runtime_u _frrmod_this_module +// __attribute__((weak, alias("frrmod_default"))); +// elif defined(HAVE_SYS_WEAK_ALIAS_PRAGMA) +#pragma weak _frrmod_this_module = frrmod_default +// else +// error need weak symbol support +// endif + +struct frrmod_runtime *frrmod_list = &frrmod_default.r; +static struct frrmod_runtime **frrmod_last = &frrmod_default.r.next; +static const char *execname = NULL; + +void frrmod_init(struct frrmod_runtime *modinfo) +{ + modinfo->finished_loading = true; + *frrmod_last = modinfo; + frrmod_last = &modinfo->next; + + execname = modinfo->info->name; +} + +/* + * If caller wants error strings, it should define non-NULL pFerrlog + * which will be called with 0-terminated error messages. These + * messages will NOT contain newlines, and the (*pFerrlog)() function + * could be called multiple times for a single call to frrmod_load(). + * + * The (*pFerrlog)() function may copy these strings if needed, but + * should expect them to be freed by frrmod_load() before frrmod_load() + * returns. + * + * frrmod_load() is coded such that (*pFerrlog)() will be called only + * in the case where frrmod_load() returns an error. + */ +struct frrmod_runtime *frrmod_load(const char *spec, const char *dir, + void (*pFerrlog)(const void *, const char *), + const void *pErrlogCookie) +{ + void *handle = NULL; + char name[PATH_MAX], fullpath[PATH_MAX * 2], *args; + struct frrmod_runtime *rtinfo, **rtinfop; + const struct frrmod_info *info; + +#define FRRMOD_LOAD_N_ERRSTR 10 + char *aErr[FRRMOD_LOAD_N_ERRSTR]; + unsigned int iErr = 0; + + memset(aErr, 0, sizeof(aErr)); + +#define ERR_RECORD(...) \ + do { \ + if (pFerrlog && (iErr < FRRMOD_LOAD_N_ERRSTR)) { \ + aErr[iErr++] = asprintfrr(MTYPE_MODULE_LOAD_ERR, \ + __VA_ARGS__); \ + } \ + } while (0) + +#define ERR_REPORT \ + do { \ + if (pFerrlog) { \ + unsigned int i; \ + \ + for (i = 0; i < iErr; ++i) { \ + (*pFerrlog)(pErrlogCookie, aErr[i]); \ + } \ + } \ + } while (0) + +#define ERR_FREE \ + do { \ + unsigned int i; \ + \ + for (i = 0; i < iErr; ++i) { \ + XFREE(MTYPE_MODULE_LOAD_ERR, aErr[i]); \ + aErr[i] = 0; \ + } \ + iErr = 0; \ + } while (0) + + snprintf(name, sizeof(name), "%s", spec); + args = strchr(name, ':'); + if (args) + *args++ = '\0'; + + if (!strchr(name, '/')) { + if (execname) { + snprintf(fullpath, sizeof(fullpath), "%s/%s_%s.so", dir, + execname, name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + if (!handle) + ERR_RECORD("loader error: dlopen(%s): %s", + fullpath, dlerror()); + } + if (!handle) { + snprintf(fullpath, sizeof(fullpath), "%s/%s.so", dir, + name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + if (!handle) + ERR_RECORD("loader error: dlopen(%s): %s", + fullpath, dlerror()); + } + } + if (!handle) { + snprintf(fullpath, sizeof(fullpath), "%s", name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + if (!handle) + ERR_RECORD("loader error: dlopen(%s): %s", fullpath, + dlerror()); + } + if (!handle) { + ERR_REPORT; + ERR_FREE; + return NULL; + } + + /* previous dlopen() errors are no longer relevant */ + ERR_FREE; + + rtinfop = dlsym(handle, "frr_module"); + if (!rtinfop) { + dlclose(handle); + ERR_RECORD("\"%s\" is not an FRR module: %s", name, dlerror()); + ERR_REPORT; + ERR_FREE; + return NULL; + } + rtinfo = *rtinfop; + rtinfo->load_name = XSTRDUP(MTYPE_MODULE_LOADNAME, name); + rtinfo->dl_handle = handle; + if (args) + rtinfo->load_args = XSTRDUP(MTYPE_MODULE_LOADARGS, args); + info = rtinfo->info; + + if (rtinfo->finished_loading) { + dlclose(handle); + ERR_RECORD("module \"%s\" already loaded", name); + goto out_fail; + } + + if (info->init && info->init()) { + dlclose(handle); + ERR_RECORD("module \"%s\" initialisation failed", name); + goto out_fail; + } + + rtinfo->finished_loading = true; + + *frrmod_last = rtinfo; + frrmod_last = &rtinfo->next; + ERR_FREE; + return rtinfo; + +out_fail: + XFREE(MTYPE_MODULE_LOADARGS, rtinfo->load_args); + XFREE(MTYPE_MODULE_LOADNAME, rtinfo->load_name); + ERR_REPORT; + ERR_FREE; + return NULL; +} + +#if 0 +void frrmod_unload(struct frrmod_runtime *module) +{ +} +#endif diff --git a/lib/module.h b/lib/module.h new file mode 100644 index 0000000..cd2be47 --- /dev/null +++ b/lib/module.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_MODULE_H +#define _FRR_MODULE_H + +#include +#include + +#include "compiler.h" +#include "xref.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct frrmod_runtime; + +struct frrmod_info { + /* single-line few-word title */ + const char *name; + /* human-readable version number, should not contain spaces */ + const char *version; + /* one-paragraph description */ + const char *description; + + int (*init)(void); +}; + +/* primary entry point structure to be present in loadable module under + * "_frrmod_this_module" dlsym() name + * + * note space for future extensions is reserved below, so other modules + * (e.g. memory management, hooks) can add fields + * + * const members/info are in frrmod_info. + */ +struct frrmod_runtime { + struct frrmod_runtime *next; + + const struct frrmod_info *info; + void *dl_handle; + bool finished_loading; + + char *load_name; + char *load_args; +}; + +/* space-reserving foo */ +struct _frrmod_runtime_size { + struct frrmod_runtime r; + /* this will barf if frrmod_runtime exceeds 1024 bytes ... */ + uint8_t space[1024 - sizeof(struct frrmod_runtime)]; +}; +union _frrmod_runtime_u { + struct frrmod_runtime r; + struct _frrmod_runtime_size s; +}; + +extern union _frrmod_runtime_u _frrmod_this_module; +#define THIS_MODULE (&_frrmod_this_module.r) + +#define FRR_COREMOD_SETUP(...) \ + static const struct frrmod_info _frrmod_info = {__VA_ARGS__}; \ + DSO_LOCAL union _frrmod_runtime_u _frrmod_this_module = {{ \ + NULL, \ + &_frrmod_info, \ + }}; \ + XREF_SETUP(); \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#define FRR_MODULE_SETUP(...) \ + FRR_COREMOD_SETUP(__VA_ARGS__); \ + DSO_SELF struct frrmod_runtime *frr_module = &_frrmod_this_module.r; \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +extern struct frrmod_runtime *frrmod_list; + +extern void frrmod_init(struct frrmod_runtime *modinfo); +extern struct frrmod_runtime *frrmod_load(const char *spec, const char *dir, + void (*pFerrlog)(const void *, + const char *), + const void *pErrlogCookie); +#if 0 +/* not implemented yet */ +extern void frrmod_unload(struct frrmod_runtime *module); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_MODULE_H */ diff --git a/lib/monotime.h b/lib/monotime.h new file mode 100644 index 0000000..f7ae1bb --- /dev/null +++ b/lib/monotime.h @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2017 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_MONOTIME_H +#define _FRR_MONOTIME_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct fbuf; +struct printfrr_eargs; + +#ifndef TIMESPEC_TO_TIMEVAL +/* should be in sys/time.h on BSD & Linux libcs */ +#define TIMESPEC_TO_TIMEVAL(tv, ts) \ + do { \ + (tv)->tv_sec = (ts)->tv_sec; \ + (tv)->tv_usec = (ts)->tv_nsec / 1000; \ + } while (0) +#endif +#ifndef TIMEVAL_TO_TIMESPEC +/* should be in sys/time.h on BSD & Linux libcs */ +#define TIMEVAL_TO_TIMESPEC(tv, ts) \ + do { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ + } while (0) +#endif + +/* Linux/glibc is sadly missing these timespec helpers */ +#ifndef timespecadd +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) +#endif + +#ifndef timespecsub +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + +static inline time_t monotime(struct timeval *tvo) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + if (tvo) { + TIMESPEC_TO_TIMEVAL(tvo, &ts); + } + return ts.tv_sec; +} + +#define ONE_DAY_SECOND (60 * 60 * 24) +#define ONE_WEEK_SECOND (ONE_DAY_SECOND * 7) +#define ONE_YEAR_SECOND (ONE_DAY_SECOND * 365) + +/* the following two return microseconds, not time_t! + * + * also, they're negative forms of each other, but having both makes the + * code more readable + */ +static inline int64_t monotime_since(const struct timeval *ref, + struct timeval *out) +{ + struct timeval tv; + monotime(&tv); + timersub(&tv, ref, &tv); + if (out) + *out = tv; + return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec; +} + +static inline int64_t monotime_until(const struct timeval *ref, + struct timeval *out) +{ + struct timeval tv; + monotime(&tv); + timersub(ref, &tv, &tv); + if (out) + *out = tv; + return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec; +} + +static inline time_t monotime_to_realtime(const struct timeval *mono, + struct timeval *realout) +{ + struct timeval delta, real; + + monotime_since(mono, &delta); + gettimeofday(&real, NULL); + + timersub(&real, &delta, &real); + if (realout) + *realout = real; + return real.tv_sec; +} + +/* Char buffer size for time-to-string api */ +#define MONOTIME_STRLEN 32 + +static inline char *time_to_string(time_t ts, char *buf) +{ + struct timeval tv; + time_t tbuf; + + monotime(&tv); + tbuf = time(NULL) - (tv.tv_sec - ts); + + return ctime_r(&tbuf, buf); +} + +/* Convert interval to human-friendly string, used in cli output e.g. */ +static inline const char *frrtime_to_interval(time_t t, char *buf, + size_t buflen) +{ + struct tm tm; + + gmtime_r(&t, &tm); + + if (t < ONE_DAY_SECOND) + snprintf(buf, buflen, "%02d:%02d:%02d", tm.tm_hour, tm.tm_min, + tm.tm_sec); + else if (t < ONE_WEEK_SECOND) + snprintf(buf, buflen, "%dd%02dh%02dm", tm.tm_yday, tm.tm_hour, + tm.tm_min); + else + snprintf(buf, buflen, "%02dw%dd%02dh", tm.tm_yday / 7, + tm.tm_yday - ((tm.tm_yday / 7) * 7), tm.tm_hour); + return buf; +} + +enum { + /* n/a - input was seconds precision, don't print any fractional */ + TIMEFMT_SECONDS = (1 << 0), + /* caller is directly invoking printfrr_time and has pre-specified + * I/Iu/Is/M/Mu/Ms/R/Ru/Rs (for printing timers) + */ + TIMEFMT_PRESELECT = (1 << 1), + /* don't print any output - this is needed for invoking printfrr_time + * from another printfrr extensions to skip over flag characters + */ + TIMEFMT_SKIP = (1 << 2), + /* use spaces in appropriate places */ + TIMEFMT_SPACE = (1 << 3), + + /* input interpretations: */ + TIMEFMT_REALTIME = (1 << 8), + TIMEFMT_MONOTONIC = (1 << 9), + TIMEFMT_SINCE = (1 << 10), + TIMEFMT_UNTIL = (1 << 11), + + TIMEFMT_ABSOLUTE = TIMEFMT_REALTIME | TIMEFMT_MONOTONIC, + TIMEFMT_ANCHORS = TIMEFMT_SINCE | TIMEFMT_UNTIL, + + /* calendaric formats: */ + TIMEFMT_ISO8601 = (1 << 16), + + /* interval formats: */ + /* 't' - use [t]raditional 3-block format */ + TIMEFMT_BASIC = (1 << 24), + /* 'm' - select mm:ss */ + TIMEFMT_MMSS = (1 << 25), + /* 'h' - select hh:mm:ss */ + TIMEFMT_HHMMSS = (1 << 26), + /* 'd' - print as decimal number of seconds */ + TIMEFMT_DECIMAL = (1 << 27), + /* 'mx'/'hx' - replace zero value with "--:--" or "--:--:--" */ + TIMEFMT_DASHES = (1 << 31), + + /* helpers for reference */ + TIMEFMT_TIMER_DEADLINE = + TIMEFMT_PRESELECT | TIMEFMT_MONOTONIC | TIMEFMT_UNTIL, + TIMEFMT_TIMER_INTERVAL = TIMEFMT_PRESELECT, +}; + +extern ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_MONOTIME_H */ diff --git a/lib/mpls.c b/lib/mpls.c new file mode 100644 index 0000000..8c656f7 --- /dev/null +++ b/lib/mpls.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mpls functions + * + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include +#include +#include + +/* + * String to label conversion, labels separated by '/'. + * + * @param label_str labels separated by / + * @param num_labels number of labels; zero if conversion was unsuccessful + * @param labels preallocated mpls_label_t array of size MPLS_MAX_LABELS; only + * modified if the conversion succeeded + * @return 0 on success + * -1 if the string could not be parsed as integers + * -2 if a label was inside the reserved range (0-15) + * -3 if the number of labels given exceeds MPLS_MAX_LABELS + */ +int mpls_str2label(const char *label_str, uint8_t *num_labels, + mpls_label_t *labels) +{ + char *ostr; // copy of label string (start) + char *lstr; // copy of label string + char *nump; // pointer to next segment + char *endp; // end pointer + int i; // for iterating label_str + int rc; // return code + mpls_label_t pl[MPLS_MAX_LABELS]; // parsed labels + + /* labels to zero until we have a successful parse */ + ostr = lstr = XSTRDUP(MTYPE_TMP, label_str); + *num_labels = 0; + rc = 0; + + for (i = 0; i < MPLS_MAX_LABELS && lstr && !rc; i++) { + nump = strsep(&lstr, "/"); + pl[i] = strtoul(nump, &endp, 10); + + /* format check */ + if (*endp != '\0') + rc = -1; + /* validity check */ + else if (!IS_MPLS_UNRESERVED_LABEL(pl[i])) + rc = -2; + } + + /* excess labels */ + if (!rc && i == MPLS_MAX_LABELS && lstr) + rc = -3; + + if (!rc) { + *num_labels = i; + memcpy(labels, pl, *num_labels * sizeof(mpls_label_t)); + } + + XFREE(MTYPE_TMP, ostr); + + return rc; +} + +/* + * Label to string conversion, labels in string separated by '/'. + */ +char *mpls_label2str(uint8_t num_labels, const mpls_label_t *labels, char *buf, + int len, enum lsp_types_t type, int pretty) +{ + char label_buf[BUFSIZ]; + int i; + + buf[0] = '\0'; + for (i = 0; i < num_labels; i++) { + if (i != 0) + strlcat(buf, "/", len); + if (pretty) + label2str(labels[i], type, label_buf, + sizeof(label_buf)); + else + snprintf(label_buf, sizeof(label_buf), "%u", + ((type == ZEBRA_LSP_EVPN) + ? label2vni(&labels[i]) + : labels[i])); + + strlcat(buf, label_buf, len); + } + + return buf; +} diff --git a/lib/mpls.h b/lib/mpls.h new file mode 100644 index 0000000..dc7f912 --- /dev/null +++ b/lib/mpls.h @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MPLS definitions + * Copyright 2015 Cumulus Networks, Inc. + */ + +#ifndef _QUAGGA_MPLS_H +#define _QUAGGA_MPLS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef MPLS_LABEL_MAX +#undef MPLS_LABEL_MAX +#endif + +#define MPLS_LABEL_HELPSTR \ + "Specify label(s) for this route\nOne or more " \ + "labels in the range (16-1048575) separated by '/'\n" + +/* Well-known MPLS label values (RFC 3032 etc). */ +#define MPLS_LABEL_IPV4_EXPLICIT_NULL 0 /* [RFC3032] */ +#define MPLS_LABEL_ROUTER_ALERT 1 /* [RFC3032] */ +#define MPLS_LABEL_IPV6_EXPLICIT_NULL 2 /* [RFC3032] */ +#define MPLS_LABEL_IMPLICIT_NULL 3 /* [RFC3032] */ +#define MPLS_LABEL_ELI 7 /* [RFC6790] */ +#define MPLS_LABEL_GAL 13 /* [RFC5586] */ +#define MPLS_LABEL_OAM_ALERT 14 /* [RFC3429] */ +#define MPLS_LABEL_EXTENSION 15 /* [RFC7274] */ +#define MPLS_LABEL_MAX 1048575 +#define MPLS_LABEL_VALUE_MASK 0x000FFFFF +#define MPLS_LABEL_NONE 0xFFFFFFFF /* for internal use only */ + +/* Minimum and maximum label values */ +#define MPLS_LABEL_RESERVED_MIN 0 +#define MPLS_LABEL_RESERVED_MAX 15 +#define MPLS_LABEL_UNRESERVED_MIN 16 +#define MPLS_LABEL_UNRESERVED_MAX 1048575 +#define MPLS_LABEL_BASE_ANY 0 + +/* Default min and max SRGB label range */ +/* Even if the SRGB allows to manage different Label space between routers, + * if an operator want to use the same SRGB for all its router, we must fix + * a common range. However, Cisco start its SRGB at 16000 and Juniper ends + * its SRGB at 16384 for OSPF. Thus, by fixing the minimum SRGB label to + * 8000 we could deal with both Cisco and Juniper. + */ +#define MPLS_DEFAULT_MIN_SRGB_LABEL 8000 +#define MPLS_DEFAULT_MAX_SRGB_LABEL 50000 +#define MPLS_DEFAULT_MIN_SRGB_SIZE 5000 +#define MPLS_DEFAULT_MAX_SRGB_SIZE 20000 + +/* Maximum # labels that can be pushed. */ +#define MPLS_MAX_LABELS 16 + +#define IS_MPLS_RESERVED_LABEL(label) (label <= MPLS_LABEL_RESERVED_MAX) + +#define IS_MPLS_UNRESERVED_LABEL(label) \ + (label >= MPLS_LABEL_UNRESERVED_MIN \ + && label <= MPLS_LABEL_UNRESERVED_MAX) + +/* Definitions for a MPLS label stack entry (RFC 3032). This encodes the + * label, EXP, BOS and TTL fields. + */ +typedef unsigned int mpls_lse_t; + +#define MPLS_LS_LABEL_MASK 0xFFFFF000 +#define MPLS_LS_LABEL_SHIFT 12 +#define MPLS_LS_EXP_MASK 0x00000E00 +#define MPLS_LS_EXP_SHIFT 9 +#define MPLS_LS_S_MASK 0x00000100 +#define MPLS_LS_S_SHIFT 8 +#define MPLS_LS_TTL_MASK 0x000000FF +#define MPLS_LS_TTL_SHIFT 0 + +#define MPLS_LABEL_VALUE(lse) \ + ((lse & MPLS_LS_LABEL_MASK) >> MPLS_LS_LABEL_SHIFT) +#define MPLS_LABEL_EXP(lse) ((lse & MPLS_LS_EXP_MASK) >> MPLS_LS_EXP_SHIFT) +#define MPLS_LABEL_BOS(lse) ((lse & MPLS_LS_S_MASK) >> MPLS_LS_S_SHIFT) +#define MPLS_LABEL_TTL(lse) ((lse & MPLS_LS_TTL_MASK) >> MPLS_LS_TTL_SHIFT) + +#define IS_MPLS_LABEL_BOS(ls) (MPLS_LABEL_BOS(ls) == 1) + +#define MPLS_LABEL_LEN_BITS 20 + +/* MPLS label value as a 32-bit (mostly we only care about the label value). */ +typedef unsigned int mpls_label_t; + +struct mpls_label_stack { + uint8_t num_labels; + uint8_t reserved[3]; + mpls_label_t label[0]; /* 1 or more labels */ +}; + +/* The MPLS explicit-null label is 0 which means when you memset a mpls_label_t + * to zero you have set that variable to explicit-null which was probably not + * your intent. The work-around is to use one bit to indicate if the + * mpls_label_t has been set by the user. MPLS_INVALID_LABEL has this bit clear + * so that we can use MPLS_INVALID_LABEL to initialize mpls_label_t variables. + */ +#define MPLS_INVALID_LABEL 0xFFFDFFFF + +/* LSP types. */ +enum lsp_types_t { + ZEBRA_LSP_NONE = 0, /* No LSP. */ + ZEBRA_LSP_STATIC = 1, /* Static LSP. */ + ZEBRA_LSP_LDP = 2, /* LDP LSP. */ + ZEBRA_LSP_BGP = 3, /* BGP LSP. */ + ZEBRA_LSP_OSPF_SR = 4,/* OSPF Segment Routing LSP. */ + ZEBRA_LSP_ISIS_SR = 5,/* IS-IS Segment Routing LSP. */ + ZEBRA_LSP_SHARP = 6, /* Identifier for test protocol */ + ZEBRA_LSP_SRTE = 7, /* SR-TE LSP */ + ZEBRA_LSP_EVPN = 8, /* EVPN VNI Label */ +}; + +/* Functions for basic label operations. */ + +static inline void vni2label(vni_t vni, mpls_label_t *label) +{ + uint8_t *tag = (uint8_t *)label; + + assert(tag); + + tag[0] = (vni >> 16) & 0xFF; + tag[1] = (vni >> 8) & 0xFF; + tag[2] = vni & 0xFF; +} + +static inline vni_t label2vni(const mpls_label_t *label) +{ + uint8_t *tag = (uint8_t *)label; + vni_t vni; + + assert(tag); + + vni = ((uint32_t)*tag++ << 16); + vni |= (uint32_t)*tag++ << 8; + vni |= (uint32_t)(*tag & 0xFF); + + return vni; +} + +/* Encode a label stack entry from fields; convert to network byte-order as + * the Netlink interface expects MPLS labels to be in this format. + */ +static inline mpls_lse_t mpls_lse_encode(mpls_label_t label, uint32_t ttl, + uint32_t exp, uint32_t bos) +{ + mpls_lse_t lse; + lse = htonl((label << MPLS_LS_LABEL_SHIFT) | (exp << MPLS_LS_EXP_SHIFT) + | (bos ? (1 << MPLS_LS_S_SHIFT) : 0) + | (ttl << MPLS_LS_TTL_SHIFT)); + return lse; +} + +/* Extract the fields from a label stack entry after converting to host-byte + * order. This is expected to be called only for messages received over the + * Netlink interface. + */ +static inline void mpls_lse_decode(mpls_lse_t lse, mpls_label_t *label, + uint32_t *ttl, uint32_t *exp, uint32_t *bos) +{ + mpls_lse_t local_lse; + + local_lse = ntohl(lse); + *label = MPLS_LABEL_VALUE(local_lse); + *exp = MPLS_LABEL_EXP(local_lse); + *bos = MPLS_LABEL_BOS(local_lse); + *ttl = MPLS_LABEL_TTL(local_lse); +} + +/* Invalid label index value (when used with BGP Prefix-SID). Should + * match the BGP definition. + */ +#define MPLS_INVALID_LABEL_INDEX 0xFFFFFFFF + +/* Printable string for labels (with consideration for reserved values). */ +static inline char *label2str(mpls_label_t label, enum lsp_types_t type, + char *buf, size_t len) +{ + if (type == ZEBRA_LSP_EVPN) { + snprintf(buf, len, "%u", label2vni(&label)); + return (buf); + } + + switch (label) { + case MPLS_LABEL_IPV4_EXPLICIT_NULL: + strlcpy(buf, "IPv4 Explicit Null", len); + return (buf); + case MPLS_LABEL_ROUTER_ALERT: + strlcpy(buf, "Router Alert", len); + return (buf); + case MPLS_LABEL_IPV6_EXPLICIT_NULL: + strlcpy(buf, "IPv6 Explicit Null", len); + return (buf); + case MPLS_LABEL_IMPLICIT_NULL: + strlcpy(buf, "implicit-null", len); + return (buf); + case MPLS_LABEL_ELI: + strlcpy(buf, "Entropy Label Indicator", len); + return (buf); + case MPLS_LABEL_GAL: + strlcpy(buf, "Generic Associated Channel", len); + return (buf); + case MPLS_LABEL_OAM_ALERT: + strlcpy(buf, "OAM Alert", len); + return (buf); + case MPLS_LABEL_EXTENSION: + strlcpy(buf, "Extension", len); + return (buf); + default: + if (label < 16) + snprintf(buf, len, "Reserved (%u)", label); + else + snprintf(buf, len, "%u", label); + return buf; + } +} + +/* + * String to label conversion, labels separated by '/'. + */ +int mpls_str2label(const char *label_str, uint8_t *num_labels, + mpls_label_t *labels); + +/* Generic string buffer for label-stack-to-str */ +#define MPLS_LABEL_STRLEN 1024 + +/* + * Label to string conversion, labels in string separated by '/'. + */ +char *mpls_label2str(uint8_t num_labels, const mpls_label_t *labels, char *buf, + int len, enum lsp_types_t type, int pretty); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/netns_linux.c b/lib/netns_linux.c new file mode 100644 index 0000000..8fa4bc6 --- /dev/null +++ b/lib/netns_linux.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NS functions. + * Copyright (C) 2014 6WIND S.A. + */ + +#include +#include + +#ifdef HAVE_NETNS +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#include +#endif + +/* for basename */ +#include + +#include "if.h" +#include "ns.h" +#include "log.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "vrf.h" +#include "lib_errors.h" + +DEFINE_MTYPE_STATIC(LIB, NS, "NetNS Context"); +DEFINE_MTYPE_STATIC(LIB, NS_NAME, "NetNS Name"); + +static inline int ns_compare(const struct ns *ns, const struct ns *ns2); +static struct ns *ns_lookup_name_internal(const char *name); + +RB_GENERATE(ns_head, ns, entry, ns_compare) + +static struct ns_head ns_tree = RB_INITIALIZER(&ns_tree); + +static struct ns *default_ns; +static int ns_current_ns_fd; +static int ns_default_ns_fd; + +static int ns_debug; + +struct ns_map_nsid { + RB_ENTRY(ns_map_nsid) id_entry; + ns_id_t ns_id_external; + ns_id_t ns_id; +}; + +static inline int ns_map_compare(const struct ns_map_nsid *a, + const struct ns_map_nsid *b) +{ + return (a->ns_id - b->ns_id); +} + +RB_HEAD(ns_map_nsid_head, ns_map_nsid); +RB_PROTOTYPE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare); +RB_GENERATE(ns_map_nsid_head, ns_map_nsid, id_entry, ns_map_compare); +static struct ns_map_nsid_head ns_map_nsid_list = + RB_INITIALIZER(&ns_map_nsid_list); + +static ns_id_t ns_id_external_numbering; + + +#ifndef CLONE_NEWNET +#define CLONE_NEWNET 0x40000000 +/* New network namespace (lo, device, names sockets, etc) */ +#endif + +#ifndef HAVE_SETNS +static inline int setns(int fd, int nstype) +{ +#ifdef __NR_setns + return syscall(__NR_setns, fd, nstype); +#else + errno = EINVAL; + return -1; +#endif +} +#endif /* !HAVE_SETNS */ + +#ifdef HAVE_NETNS +static int have_netns_enabled = -1; +#endif /* HAVE_NETNS */ + +static int have_netns(void) +{ +#ifdef HAVE_NETNS + if (have_netns_enabled < 0) { + int fd = open(NS_DEFAULT_NAME, O_RDONLY); + + if (fd < 0) + have_netns_enabled = 0; + else { + have_netns_enabled = 1; + close(fd); + } + } + return have_netns_enabled; +#else + return 0; +#endif +} + +/* Holding NS hooks */ +static struct ns_master { + int (*ns_new_hook)(struct ns *ns); + int (*ns_delete_hook)(struct ns *ns); + int (*ns_enable_hook)(struct ns *ns); + int (*ns_disable_hook)(struct ns *ns); +} ns_master = { + 0, +}; + +static int ns_is_enabled(struct ns *ns); + +static inline int ns_compare(const struct ns *a, const struct ns *b) +{ + return (a->ns_id - b->ns_id); +} + +/* Look up a NS by identifier. */ +static struct ns *ns_lookup_internal(ns_id_t ns_id) +{ + struct ns ns; + + ns.ns_id = ns_id; + return RB_FIND(ns_head, &ns_tree, &ns); +} + +/* Look up a NS by name */ +static struct ns *ns_lookup_name_internal(const char *name) +{ + struct ns *ns = NULL; + + RB_FOREACH (ns, ns_head, &ns_tree) { + if (ns->name != NULL) { + if (strcmp(name, ns->name) == 0) + return ns; + } + } + return NULL; +} + +static struct ns *ns_get_created_internal(struct ns *ns, char *name, + ns_id_t ns_id) +{ + int created = 0; + /* + * Initialize interfaces. + */ + if (!ns && !name && ns_id != NS_UNKNOWN) + ns = ns_lookup_internal(ns_id); + if (!ns && name) + ns = ns_lookup_name_internal(name); + if (!ns) { + ns = XCALLOC(MTYPE_NS, sizeof(struct ns)); + ns->ns_id = ns_id; + if (name) + ns->name = XSTRDUP(MTYPE_NS_NAME, name); + ns->fd = -1; + RB_INSERT(ns_head, &ns_tree, ns); + created = 1; + } + if (ns_id != ns->ns_id) { + RB_REMOVE(ns_head, &ns_tree, ns); + ns->ns_id = ns_id; + RB_INSERT(ns_head, &ns_tree, ns); + } + if (!created) + return ns; + if (ns_debug) { + if (ns->ns_id != NS_UNKNOWN) + zlog_info("NS %u is created.", ns->ns_id); + else + zlog_info("NS %s is created.", ns->name); + } + if (ns_master.ns_new_hook) + (*ns_master.ns_new_hook)(ns); + return ns; +} + +/* + * Enable a NS - that is, let the NS be ready to use. + * The NS_ENABLE_HOOK callback will be called to inform + * that they can allocate resources in this NS. + * + * RETURN: 1 - enabled successfully; otherwise, 0. + */ +static int ns_enable_internal(struct ns *ns, void (*func)(ns_id_t, void *)) +{ + if (!ns_is_enabled(ns)) { + if (have_netns()) { + ns->fd = open(ns->name, O_RDONLY); + } else { + ns->fd = -2; + /* Remember ns_enable_hook has been called */ + errno = -ENOTSUP; + } + + if (!ns_is_enabled(ns)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Can not enable NS %u: %s!", ns->ns_id, + safe_strerror(errno)); + return 0; + } + + /* Non default NS. leave */ + if (ns->ns_id == NS_UNKNOWN) { + flog_err(EC_LIB_NS, + "Can not enable NS %s %u: Invalid NSID", + ns->name, ns->ns_id); + return 0; + } + if (func) + func(ns->ns_id, (void *)ns->vrf_ctxt); + if (ns_debug) { + if (have_netns()) + zlog_info("NS %u is associated with NETNS %s.", + ns->ns_id, ns->name); + zlog_info("NS %u is enabled.", ns->ns_id); + } + /* zebra first receives NS enable event, + * then VRF enable event + */ + if (ns_master.ns_enable_hook) + (*ns_master.ns_enable_hook)(ns); + } + + return 1; +} + +/* + * Check whether the NS is enabled - that is, whether the NS + * is ready to allocate resources. Currently there's only one + * type of resource: socket. + */ +static int ns_is_enabled(struct ns *ns) +{ + if (have_netns()) + return ns && ns->fd >= 0; + else + return ns && ns->fd == -2 && ns->ns_id == NS_DEFAULT; +} + +/* + * Disable a NS - that is, let the NS be unusable. + * The NS_DELETE_HOOK callback will be called to inform + * that they must release the resources in the NS. + */ +static void ns_disable_internal(struct ns *ns) +{ + if (ns_is_enabled(ns)) { + if (ns_debug) + zlog_info("NS %u is to be disabled.", ns->ns_id); + + if (ns_master.ns_disable_hook) + (*ns_master.ns_disable_hook)(ns); + + if (have_netns()) + close(ns->fd); + + ns->fd = -1; + } +} + +/* VRF list existence check by name. */ +static struct ns_map_nsid *ns_map_nsid_lookup_by_nsid(ns_id_t ns_id) +{ + struct ns_map_nsid ns_map; + + ns_map.ns_id = ns_id; + return RB_FIND(ns_map_nsid_head, &ns_map_nsid_list, &ns_map); +} + +ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map) +{ + struct ns_map_nsid *ns_map; + vrf_id_t ns_id_external; + + ns_map = ns_map_nsid_lookup_by_nsid(ns_id); + if (ns_map && !map) { + ns_id_external = ns_map->ns_id_external; + RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map); + return ns_id_external; + } + if (ns_map) + return ns_map->ns_id_external; + ns_map = XCALLOC(MTYPE_NS, sizeof(struct ns_map_nsid)); + /* increase vrf_id + * default vrf is the first one : 0 + */ + ns_map->ns_id_external = ns_id_external_numbering++; + ns_map->ns_id = ns_id; + RB_INSERT(ns_map_nsid_head, &ns_map_nsid_list, ns_map); + return ns_map->ns_id_external; +} + +struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id) +{ + return ns_get_created_internal(ns, name, ns_id); +} + +int ns_have_netns(void) +{ + return have_netns(); +} + +/* Delete a NS. This is called in ns_terminate(). */ +void ns_delete(struct ns *ns) +{ + if (ns_debug) + zlog_info("NS %u is to be deleted.", ns->ns_id); + + ns_disable(ns); + + if (ns_master.ns_delete_hook) + (*ns_master.ns_delete_hook)(ns); + + /* + * I'm not entirely sure if the vrf->iflist + * needs to be moved into here or not. + */ + // if_terminate (&ns->iflist); + + RB_REMOVE(ns_head, &ns_tree, ns); + XFREE(MTYPE_NS_NAME, ns->name); + + XFREE(MTYPE_NS, ns); +} + +/* Look up the data pointer of the specified VRF. */ +void *ns_info_lookup(ns_id_t ns_id) +{ + struct ns *ns = ns_lookup_internal(ns_id); + + return ns ? ns->info : NULL; +} + +/* Look up a NS by name */ +struct ns *ns_lookup_name(const char *name) +{ + return ns_lookup_name_internal(name); +} + +int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *)) +{ + return ns_enable_internal(ns, func); +} + +void ns_disable(struct ns *ns) +{ + ns_disable_internal(ns); +} + +struct ns *ns_lookup(ns_id_t ns_id) +{ + return ns_lookup_internal(ns_id); +} + +void ns_walk_func(int (*func)(struct ns *, + void *param_in, + void **param_out), + void *param_in, + void **param_out) +{ + struct ns *ns = NULL; + int ret; + + RB_FOREACH (ns, ns_head, &ns_tree) { + ret = func(ns, param_in, param_out); + if (ret == NS_WALK_STOP) + return; + } +} + +const char *ns_get_name(struct ns *ns) +{ + if (!ns) + return NULL; + return ns->name; +} + +/* Add a NS hook. Please add hooks before calling ns_init(). */ +void ns_add_hook(int type, int (*func)(struct ns *)) +{ + switch (type) { + case NS_NEW_HOOK: + ns_master.ns_new_hook = func; + break; + case NS_DELETE_HOOK: + ns_master.ns_delete_hook = func; + break; + case NS_ENABLE_HOOK: + ns_master.ns_enable_hook = func; + break; + case NS_DISABLE_HOOK: + ns_master.ns_disable_hook = func; + break; + default: + break; + } +} + +/* + * NS realization with NETNS + */ + +char *ns_netns_pathname(struct vty *vty, const char *name) +{ + static char pathname[PATH_MAX]; + char *result; + char *check_base; + + if (name[0] == '/') /* absolute pathname */ + result = realpath(name, pathname); + else { + /* relevant pathname */ + char tmp_name[PATH_MAX]; + + snprintf(tmp_name, sizeof(tmp_name), "%s/%s", NS_RUN_DIR, name); + result = realpath(tmp_name, pathname); + } + + if (!result) { + if (vty) + vty_out(vty, "Invalid pathname for %s: %s\n", + pathname, + safe_strerror(errno)); + else + flog_warn(EC_LIB_LINUX_NS, + "Invalid pathname for %s: %s", pathname, + safe_strerror(errno)); + return NULL; + } + check_base = basename(pathname); + if (check_base != NULL && strlen(check_base) + 1 > NS_NAMSIZ) { + if (vty) + vty_out(vty, "NS name (%s) invalid: too long (>%d)\n", + check_base, NS_NAMSIZ - 1); + else + flog_warn(EC_LIB_LINUX_NS, + "NS name (%s) invalid: too long (>%d)", + check_base, NS_NAMSIZ - 1); + return NULL; + } + return pathname; +} + +void ns_init(void) +{ + static int ns_initialised; + + ns_debug = 0; + /* silently return as initialisation done */ + if (ns_initialised == 1) + return; + errno = 0; + if (have_netns()) + ns_default_ns_fd = open(NS_DEFAULT_NAME, O_RDONLY); + else { + ns_default_ns_fd = -1; + default_ns = NULL; + } + ns_current_ns_fd = -1; + ns_initialised = 1; +} + +/* Initialize NS module. */ +void ns_init_management(ns_id_t default_ns_id, ns_id_t internal_ns) +{ + int fd; + + ns_init(); + default_ns = ns_get_created_internal(NULL, NULL, default_ns_id); + if (!default_ns) { + flog_err(EC_LIB_NS, "%s: failed to create the default NS!", + __func__); + exit(1); + } + if (have_netns()) { + fd = open(NS_DEFAULT_NAME, O_RDONLY); + default_ns->fd = fd; + } + default_ns->internal_ns_id = internal_ns; + + /* Set the default NS name. */ + default_ns->name = XSTRDUP(MTYPE_NS_NAME, NS_DEFAULT_NAME); + if (ns_debug) + zlog_info("%s: default NSID is %u", __func__, + default_ns->ns_id); + + /* Enable the default NS. */ + if (!ns_enable(default_ns, NULL)) { + flog_err(EC_LIB_NS, "%s: failed to enable the default NS!", + __func__); + exit(1); + } +} + +/* Terminate NS module. */ +void ns_terminate(void) +{ + struct ns *ns; + struct ns_map_nsid *ns_map; + + while (!RB_EMPTY(ns_head, &ns_tree)) { + ns = RB_ROOT(ns_head, &ns_tree); + + ns_delete(ns); + } + + while (!RB_EMPTY(ns_map_nsid_head, &ns_map_nsid_list)) { + ns_map = RB_ROOT(ns_map_nsid_head, &ns_map_nsid_list); + RB_REMOVE(ns_map_nsid_head, &ns_map_nsid_list, ns_map); + XFREE(MTYPE_NS, ns_map); + } +} + +int ns_switch_to_netns(const char *name) +{ + int ret; + int fd; + + if (name == NULL) + return -1; + if (ns_default_ns_fd == -1) + return -1; + fd = open(name, O_RDONLY); + if (fd == -1) { + errno = EINVAL; + return -1; + } + ret = setns(fd, CLONE_NEWNET); + ns_current_ns_fd = fd; + close(fd); + return ret; +} + +/* returns 1 if switch() was not called before + * return status of setns() otherwise + */ +int ns_switchback_to_initial(void) +{ + if (ns_current_ns_fd != -1 && ns_default_ns_fd != -1) { + int ret; + + ret = setns(ns_default_ns_fd, CLONE_NEWNET); + ns_current_ns_fd = -1; + return ret; + } + /* silently ignore if setns() is not called */ + return 1; +} + +/* Create a socket for the NS. */ +int ns_socket(int domain, int type, int protocol, ns_id_t ns_id) +{ + struct ns *ns = ns_lookup(ns_id); + int ret; + + if (!ns || !ns_is_enabled(ns)) { + errno = EINVAL; + return -1; + } + if (have_netns()) { + ret = (ns_id != NS_DEFAULT) ? setns(ns->fd, CLONE_NEWNET) : 0; + if (ret >= 0) { + ret = socket(domain, type, protocol); + if (ns_id != NS_DEFAULT) { + setns(ns_lookup(NS_DEFAULT)->fd, CLONE_NEWNET); + ns_current_ns_fd = ns_id; + } + } + } else + ret = socket(domain, type, protocol); + + return ret; +} + +/* if relative link_nsid matches default netns, + * then return default absolute netns value + * otherwise, return NS_UNKNOWN + */ +ns_id_t ns_id_get_absolute(ns_id_t ns_id_reference, ns_id_t link_nsid) +{ + struct ns *ns; + + ns = ns_lookup(ns_id_reference); + if (!ns) + return NS_UNKNOWN; + + if (ns->relative_default_ns != link_nsid) + return NS_UNKNOWN; + + ns = ns_get_default(); + assert(ns); + return ns->ns_id; +} + +struct ns *ns_get_default(void) +{ + return default_ns; +} diff --git a/lib/netns_other.c b/lib/netns_other.c new file mode 100644 index 0000000..545a962 --- /dev/null +++ b/lib/netns_other.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NetNS backend for non Linux systems + * Copyright (C) 2018 6WIND S.A. + */ + + +#if !defined(GNU_LINUX) && defined(OPEN_BSD) +/* OPEN_BSD */ + +#include +#include "ns.h" +#include "log.h" +#include "memory.h" + +static inline int ns_compare(const struct ns *ns, const struct ns *ns2); + +RB_GENERATE(ns_head, ns, entry, ns_compare) + +static struct ns_head ns_tree = RB_INITIALIZER(&ns_tree); + +static inline int ns_compare(const struct ns *a, const struct ns *b) +{ + return (a->ns_id - b->ns_id); +} + +void ns_terminate(void) +{ +} + +/* API to initialize NETNS managerment + * parameter is the default ns_id + */ +void ns_init_management(ns_id_t ns_id) +{ +} + +/* + * NS utilities + */ + +/* Create a socket serving for the given NS + */ +int ns_socket(int domain, int type, int protocol, ns_id_t ns_id) +{ + return -1; +} + +/* return the path of the NETNS */ +char *ns_netns_pathname(struct vty *vty, const char *name) +{ + return NULL; +} + +/* Parse and execute a function on all the NETNS */ +void ns_walk_func(int (*func)(struct ns *)) +{ +} + +/* API to get the NETNS name, from the ns pointer */ +const char *ns_get_name(struct ns *ns) +{ + return NULL; +} + +/* only called from vrf ( when removing netns from vrf) + * or at VRF termination + */ +void ns_delete(struct ns *ns) +{ +} + +/* return > 0 if netns is available + * called by VRF to check netns backend is available for VRF + */ +int ns_have_netns(void) +{ + return 0; +} + +/* API to get context information of a NS */ +void *ns_info_lookup(ns_id_t ns_id) +{ + return NULL; +} + +/* + * NS init routine + * should be called from backendx + */ +void ns_init(void) +{ +} + +/* API that can be used to change from NS */ +int ns_switchback_to_initial(void) +{ + return 0; +} +int ns_switch_to_netns(const char *netns_name) +{ + return 0; +} + +/* + * NS handling routines. + * called by modules that use NS backend + */ + +/* API to search for already present NETNS */ +struct ns *ns_lookup(ns_id_t ns_id) +{ + return NULL; +} + +struct ns *ns_lookup_name(const char *name) +{ + return NULL; +} + +/* API to handle NS : creation, enable, disable + * for enable, a callback function is passed as parameter + * the callback belongs to the module that uses NS as backend + * upon enabling the NETNS, the upper layer is informed + */ +int ns_enable(struct ns *ns, int (*func)(ns_id_t, void *)) +{ + return 0; +} + +ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool maporunmap) +{ + return NS_UNKNOWN; +} + +struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id) +{ + return NULL; +} + +void ns_disable(struct ns *ns) +{ +} + +#endif /* !GNU_LINUX */ diff --git a/lib/network.c b/lib/network.c new file mode 100644 index 0000000..b768693 --- /dev/null +++ b/lib/network.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Network library. + * Copyright (C) 1997 Kunihiro Ishiguro + */ + +#include +#include +#include "log.h" +#include "network.h" +#include "lib_errors.h" + +/* Read nbytes from fd and store into ptr. */ +int readn(int fd, uint8_t *ptr, int nbytes) +{ + int nleft; + int nread; + + nleft = nbytes; + + while (nleft > 0) { + nread = read(fd, ptr, nleft); + + if (nread < 0) + return (nread); + else if (nread == 0) + break; + + nleft -= nread; + ptr += nread; + } + + return nbytes - nleft; +} + +/* Write nbytes from ptr to fd. */ +int writen(int fd, const uint8_t *ptr, int nbytes) +{ + int nleft; + int nwritten; + + nleft = nbytes; + + while (nleft > 0) { + nwritten = write(fd, ptr, nleft); + + if (nwritten < 0) { + if (!ERRNO_IO_RETRY(errno)) + return nwritten; + } + if (nwritten == 0) + return (nwritten); + + nleft -= nwritten; + ptr += nwritten; + } + return nbytes - nleft; +} + +int set_nonblocking(int fd) +{ + int flags; + + /* According to the Single UNIX Spec, the return value for F_GETFL + should + never be negative. */ + flags = fcntl(fd, F_GETFL); + if (flags < 0) { + flog_err(EC_LIB_SYSTEM_CALL, + "fcntl(F_GETFL) failed for fd %d: %s", fd, + safe_strerror(errno)); + return -1; + } + if (fcntl(fd, F_SETFL, (flags | O_NONBLOCK)) < 0) { + flog_err(EC_LIB_SYSTEM_CALL, + "fcntl failed setting fd %d non-blocking: %s", fd, + safe_strerror(errno)); + return -1; + } + return 0; +} + +int set_cloexec(int fd) +{ + int flags; + flags = fcntl(fd, F_GETFD, 0); + if (flags == -1) + return -1; + + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) + return -1; + return 0; +} + +float htonf(float host) +{ + uint32_t lu1, lu2; + float convert; + + memcpy(&lu1, &host, sizeof(uint32_t)); + lu2 = htonl(lu1); + memcpy(&convert, &lu2, sizeof(uint32_t)); + return convert; +} + +float ntohf(float net) +{ + return htonf(net); +} + +uint64_t frr_sequence_next(void) +{ + static uint64_t last_sequence; + struct timespec ts; + + (void)clock_gettime(CLOCK_MONOTONIC, &ts); + if (last_sequence == (uint64_t)ts.tv_sec) { + last_sequence++; + return last_sequence; + } + + last_sequence = ts.tv_sec; + return last_sequence; +} + +uint32_t frr_sequence32_next(void) +{ + /* coverity[Y2K38_SAFETY] */ + return (uint32_t)frr_sequence_next(); +} diff --git a/lib/network.h b/lib/network.h new file mode 100644 index 0000000..3a0e871 --- /dev/null +++ b/lib/network.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Network library header. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_NETWORK_H +#define _ZEBRA_NETWORK_H + +#ifdef HAVE_SYS_ENDIAN_H +#include +#endif +#ifdef HAVE_ENDIAN_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Both readn and writen are deprecated and will be removed. They are not + suitable for use with non-blocking file descriptors. + */ +extern int readn(int, uint8_t *, int); +extern int writen(int, const uint8_t *, int); + +/* Set the file descriptor to use non-blocking I/O. Returns 0 for success, + -1 on error. */ +extern int set_nonblocking(int fd); + +extern int set_cloexec(int fd); + +/* Does the I/O error indicate that the operation should be retried later? */ +#define ERRNO_IO_RETRY(EN) \ + (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +extern float htonf(float); +extern float ntohf(float); + +/* force type for be64toh/htobe64 to be uint64_t, *without* a direct cast + * + * this is a workaround for false-positive printfrr warnings from FRR's + * frr-format GCC plugin that would be triggered from + * { printfrr("%"PRIu64, (uint64_t)be64toh(...)); } + * + * the key element here is that "(uint64_t)expr" causes the warning, while + * "({ uint64_t x = expr; x; })" does not. (The cast is the trigger, a + * variable of the same type works correctly.) + */ + +/* zap system definitions... */ +#ifdef be64toh +#undef be64toh +#endif +#ifdef htobe64 +#undef htobe64 +#endif + +#if BYTE_ORDER == LITTLE_ENDIAN +#define be64toh(x) ({ uint64_t r = __builtin_bswap64(x); r; }) +#define htobe64(x) ({ uint64_t r = __builtin_bswap64(x); r; }) +#elif BYTE_ORDER == BIG_ENDIAN +#define be64toh(x) ({ uint64_t r = (x); r; }) +#define htobe64(x) ({ uint64_t r = (x); r; }) +#else +#error nobody expects the endianish inquisition. check OS endian.h headers. +#endif + +/** + * Generate a sequence number using monotonic clock with a same second call + * protection to help guarantee a unique incremental sequence number that never + * goes back (except when wrapping/overflow). + * + * **NOTE** this function is not thread safe since it uses `static` variable. + * + * This function and `frr_sequence32_next` should be used to initialize + * sequence numbers without directly calling other `time_t` returning + * functions because of `time_t` truncation warnings. + * + * \returns `uint64_t` number based on the monotonic clock. + */ +extern uint64_t frr_sequence_next(void); + +/** Same as `frr_sequence_next` but returns truncated number. */ +extern uint32_t frr_sequence32_next(void); + +/** + * Helper function that returns a random long value. The main purpose of + * this function is to hide a `random()` call that gets flagged by coverity + * scan and put it into one place. + * + * The main usage of this function should be for generating jitter or weak + * random values for simple purposes. + * + * See 'man 3 random' for more information. + * + * \returns random long integer. + */ +static inline long frr_weak_random(void) +{ + /* coverity[dont_call] */ + return random(); +} + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_NETWORK_H */ diff --git a/lib/nexthop.c b/lib/nexthop.c new file mode 100644 index 0000000..26c3382 --- /dev/null +++ b/lib/nexthop.c @@ -0,0 +1,1499 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* A generic nexthop structure + * Copyright (C) 2013 Cumulus Networks, Inc. + */ +#include + +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "log.h" +#include "sockunion.h" +#include "linklist.h" +#include "prefix.h" +#include "nexthop.h" +#include "mpls.h" +#include "jhash.h" +#include "printfrr.h" +#include "vrf.h" +#include "nexthop_group.h" + +DEFINE_MTYPE_STATIC(LIB, NEXTHOP, "Nexthop"); +DEFINE_MTYPE_STATIC(LIB, NH_LABEL, "Nexthop label"); +DEFINE_MTYPE_STATIC(LIB, NH_SRV6, "Nexthop srv6"); + +static int _nexthop_labels_cmp(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + const struct mpls_label_stack *nhl1 = NULL; + const struct mpls_label_stack *nhl2 = NULL; + + nhl1 = nh1->nh_label; + nhl2 = nh2->nh_label; + + /* No labels is a match */ + if (!nhl1 && !nhl2) + return 0; + + if (nhl1 && !nhl2) + return 1; + + if (nhl2 && !nhl1) + return -1; + + if (nhl1->num_labels > nhl2->num_labels) + return 1; + + if (nhl1->num_labels < nhl2->num_labels) + return -1; + + return memcmp(nhl1->label, nhl2->label, + (nhl1->num_labels * sizeof(mpls_label_t))); +} + +static int _nexthop_srv6_cmp(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + int ret = 0; + int i = 0; + + if (!nh1->nh_srv6 && !nh2->nh_srv6) + return 0; + + if (nh1->nh_srv6 && !nh2->nh_srv6) + return 1; + + if (!nh1->nh_srv6 && nh2->nh_srv6) + return -1; + + if (nh1->nh_srv6->seg6local_action > nh2->nh_srv6->seg6local_action) + return 1; + + if (nh2->nh_srv6->seg6local_action < nh1->nh_srv6->seg6local_action) + return -1; + + ret = memcmp(&nh1->nh_srv6->seg6local_ctx, + &nh2->nh_srv6->seg6local_ctx, + sizeof(struct seg6local_context)); + if (ret != 0) + return ret; + + if (!nh1->nh_srv6->seg6_segs && !nh2->nh_srv6->seg6_segs) + return 0; + + if (!nh1->nh_srv6->seg6_segs && nh2->nh_srv6->seg6_segs) + return -1; + + if (nh1->nh_srv6->seg6_segs && !nh2->nh_srv6->seg6_segs) + return 1; + + if (nh1->nh_srv6->seg6_segs->num_segs != + nh2->nh_srv6->seg6_segs->num_segs) + return -1; + + for (i = 0; i < nh1->nh_srv6->seg6_segs->num_segs; i++) { + ret = memcmp(&nh1->nh_srv6->seg6_segs->seg[i], + &nh2->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); + if (ret != 0) + break; + } + + return ret; +} + +int nexthop_g_addr_cmp(enum nexthop_types_t type, const union g_addr *addr1, + const union g_addr *addr2) +{ + int ret = 0; + + switch (type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + ret = IPV4_ADDR_CMP(&addr1->ipv4, &addr2->ipv4); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret = IPV6_ADDR_CMP(&addr1->ipv6, &addr2->ipv6); + break; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + /* No addr here */ + break; + } + + return ret; +} + +static int _nexthop_gateway_cmp(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + return nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate); +} + +static int _nexthop_source_cmp(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + return nexthop_g_addr_cmp(nh1->type, &nh1->src, &nh2->src); +} + +static int _nexthop_cmp_no_labels(const struct nexthop *next1, + const struct nexthop *next2) +{ + int ret = 0; + + if (next1->vrf_id < next2->vrf_id) + return -1; + + if (next1->vrf_id > next2->vrf_id) + return 1; + + if (next1->type < next2->type) + return -1; + + if (next1->type > next2->type) + return 1; + + if (next1->weight < next2->weight) + return -1; + + if (next1->weight > next2->weight) + return 1; + + switch (next1->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + ret = _nexthop_gateway_cmp(next1, next2); + if (ret != 0) + return ret; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret = _nexthop_gateway_cmp(next1, next2); + if (ret != 0) + return ret; + fallthrough; + case NEXTHOP_TYPE_IFINDEX: + if (next1->ifindex < next2->ifindex) + return -1; + + if (next1->ifindex > next2->ifindex) + return 1; + break; + case NEXTHOP_TYPE_BLACKHOLE: + if (next1->bh_type < next2->bh_type) + return -1; + + if (next1->bh_type > next2->bh_type) + return 1; + break; + } + + if (next1->srte_color < next2->srte_color) + return -1; + if (next1->srte_color > next2->srte_color) + return 1; + + ret = _nexthop_source_cmp(next1, next2); + if (ret != 0) + goto done; + + if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) && + !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) + return 0; + + if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) && + CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) + return -1; + + if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) && + !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) + return 1; + + if (next1->backup_num == 0 && next2->backup_num == 0) + goto done; + + if (next1->backup_num < next2->backup_num) + return -1; + + if (next1->backup_num > next2->backup_num) + return 1; + + ret = memcmp(next1->backup_idx, + next2->backup_idx, next1->backup_num); + +done: + return ret; +} + +int nexthop_cmp(const struct nexthop *next1, const struct nexthop *next2) +{ + int ret = 0; + + ret = _nexthop_cmp_no_labels(next1, next2); + if (ret != 0) + return ret; + + ret = _nexthop_labels_cmp(next1, next2); + if (ret != 0) + return ret; + + ret = _nexthop_srv6_cmp(next1, next2); + + return ret; +} + +/* + * More-limited comparison function used to detect duplicate + * nexthops. This is used in places where we don't need the full + * comparison of 'nexthop_cmp()'. + */ +int nexthop_cmp_basic(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + int ret = 0; + const struct mpls_label_stack *nhl1 = NULL; + const struct mpls_label_stack *nhl2 = NULL; + + if (nh1 == NULL && nh2 == NULL) + return 0; + + if (nh1 && !nh2) + return 1; + + if (!nh1 && nh2) + return -1; + + if (nh1->vrf_id < nh2->vrf_id) + return -1; + + if (nh1->vrf_id > nh2->vrf_id) + return 1; + + if (nh1->type < nh2->type) + return -1; + + if (nh1->type > nh2->type) + return 1; + + if (nh1->weight < nh2->weight) + return -1; + + if (nh1->weight > nh2->weight) + return 1; + + switch (nh1->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + ret = nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate); + if (ret != 0) + return ret; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret = nexthop_g_addr_cmp(nh1->type, &nh1->gate, &nh2->gate); + if (ret != 0) + return ret; + fallthrough; + case NEXTHOP_TYPE_IFINDEX: + if (nh1->ifindex < nh2->ifindex) + return -1; + + if (nh1->ifindex > nh2->ifindex) + return 1; + break; + case NEXTHOP_TYPE_BLACKHOLE: + if (nh1->bh_type < nh2->bh_type) + return -1; + + if (nh1->bh_type > nh2->bh_type) + return 1; + break; + } + + /* Compare source addr */ + ret = nexthop_g_addr_cmp(nh1->type, &nh1->src, &nh2->src); + if (ret != 0) + goto done; + + nhl1 = nh1->nh_label; + nhl2 = nh2->nh_label; + + /* No labels is a match */ + if (!nhl1 && !nhl2) + return 0; + + if (nhl1 && !nhl2) + return 1; + + if (nhl2 && !nhl1) + return -1; + + if (nhl1->num_labels > nhl2->num_labels) + return 1; + + if (nhl1->num_labels < nhl2->num_labels) + return -1; + + ret = memcmp(nhl1->label, nhl2->label, + (nhl1->num_labels * sizeof(mpls_label_t))); + +done: + return ret; +} + +/* + * nexthop_type_to_str + */ +const char *nexthop_type_to_str(enum nexthop_types_t nh_type) +{ + static const char *const desc[] = { + "none", "Directly connected", + "IPv4 nexthop", "IPv4 nexthop with ifindex", + "IPv6 nexthop", "IPv6 nexthop with ifindex", + "Null0 nexthop", + }; + + return desc[nh_type]; +} + +/* + * Check if the labels match for the 2 nexthops specified. + */ +bool nexthop_labels_match(const struct nexthop *nh1, const struct nexthop *nh2) +{ + if (_nexthop_labels_cmp(nh1, nh2) != 0) + return false; + + return true; +} + +struct nexthop *nexthop_new(void) +{ + struct nexthop *nh; + + nh = XCALLOC(MTYPE_NEXTHOP, sizeof(struct nexthop)); + + /* + * Default the weight to 1 here for all nexthops. + * The linux kernel does some weird stuff with adding +1 to + * all nexthop weights it gets over netlink. + * To handle this, just default everything to 1 right from + * the beginning so we don't have to special case + * default weights in the linux netlink code. + * + * 1 should be a valid on all platforms anyway. + */ + nh->weight = 1; + + return nh; +} + +/* Free nexthop. */ +void nexthop_free(struct nexthop *nexthop) +{ + nexthop_del_labels(nexthop); + nexthop_del_srv6_seg6local(nexthop); + nexthop_del_srv6_seg6(nexthop); + if (nexthop->resolved) + nexthops_free(nexthop->resolved); + XFREE(MTYPE_NEXTHOP, nexthop); +} + +/* Frees a list of nexthops */ +void nexthops_free(struct nexthop *nexthop) +{ + struct nexthop *nh, *next; + + for (nh = nexthop; nh; nh = next) { + next = nh->next; + nexthop_free(nh); + } +} + +bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2) +{ + if (nh1 && !nh2) + return false; + + if (!nh1 && nh2) + return false; + + if (nh1 == nh2) + return true; + + if (nexthop_cmp(nh1, nh2) != 0) + return false; + + return true; +} + +bool nexthop_same_no_labels(const struct nexthop *nh1, + const struct nexthop *nh2) +{ + if (nh1 && !nh2) + return false; + + if (!nh1 && nh2) + return false; + + if (nh1 == nh2) + return true; + + if (_nexthop_cmp_no_labels(nh1, nh2) != 0) + return false; + + return true; +} + +/* + * Allocate a new nexthop object and initialize it from various args. + */ +struct nexthop *nexthop_from_ifindex(ifindex_t ifindex, vrf_id_t vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->type = NEXTHOP_TYPE_IFINDEX; + nexthop->ifindex = ifindex; + nexthop->vrf_id = vrf_id; + + return nexthop; +} + +struct nexthop *nexthop_from_ipv4(const struct in_addr *ipv4, + const struct in_addr *src, + vrf_id_t vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->type = NEXTHOP_TYPE_IPV4; + nexthop->vrf_id = vrf_id; + nexthop->gate.ipv4 = *ipv4; + if (src) + nexthop->src.ipv4 = *src; + + return nexthop; +} + +struct nexthop *nexthop_from_ipv4_ifindex(const struct in_addr *ipv4, + const struct in_addr *src, + ifindex_t ifindex, vrf_id_t vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + nexthop->vrf_id = vrf_id; + nexthop->gate.ipv4 = *ipv4; + if (src) + nexthop->src.ipv4 = *src; + nexthop->ifindex = ifindex; + + return nexthop; +} + +struct nexthop *nexthop_from_ipv6(const struct in6_addr *ipv6, + vrf_id_t vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->vrf_id = vrf_id; + nexthop->type = NEXTHOP_TYPE_IPV6; + nexthop->gate.ipv6 = *ipv6; + + return nexthop; +} + +struct nexthop *nexthop_from_ipv6_ifindex(const struct in6_addr *ipv6, + ifindex_t ifindex, vrf_id_t vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->vrf_id = vrf_id; + nexthop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + nexthop->gate.ipv6 = *ipv6; + nexthop->ifindex = ifindex; + + return nexthop; +} + +struct nexthop *nexthop_from_blackhole(enum blackhole_type bh_type, + vrf_id_t nh_vrf_id) +{ + struct nexthop *nexthop; + + nexthop = nexthop_new(); + nexthop->vrf_id = nh_vrf_id; + nexthop->type = NEXTHOP_TYPE_BLACKHOLE; + nexthop->bh_type = bh_type; + + return nexthop; +} + +/* Update nexthop with label information. */ +void nexthop_add_labels(struct nexthop *nexthop, enum lsp_types_t ltype, + uint8_t num_labels, const mpls_label_t *labels) +{ + struct mpls_label_stack *nh_label; + int i; + + if (num_labels == 0) + return; + + /* Enforce limit on label stack size */ + if (num_labels > MPLS_MAX_LABELS) + num_labels = MPLS_MAX_LABELS; + + nexthop->nh_label_type = ltype; + + nh_label = XCALLOC(MTYPE_NH_LABEL, + sizeof(struct mpls_label_stack) + + num_labels * sizeof(mpls_label_t)); + nh_label->num_labels = num_labels; + for (i = 0; i < num_labels; i++) + nh_label->label[i] = *(labels + i); + nexthop->nh_label = nh_label; +} + +/* Free label information of nexthop, if present. */ +void nexthop_del_labels(struct nexthop *nexthop) +{ + XFREE(MTYPE_NH_LABEL, nexthop->nh_label); + nexthop->nh_label_type = ZEBRA_LSP_NONE; +} + +void nexthop_add_srv6_seg6local(struct nexthop *nexthop, uint32_t action, + const struct seg6local_context *ctx) +{ + if (action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + return; + + if (!nexthop->nh_srv6) + nexthop->nh_srv6 = XCALLOC(MTYPE_NH_SRV6, + sizeof(struct nexthop_srv6)); + + nexthop->nh_srv6->seg6local_action = action; + nexthop->nh_srv6->seg6local_ctx = *ctx; +} + +void nexthop_del_srv6_seg6local(struct nexthop *nexthop) +{ + if (!nexthop->nh_srv6) + return; + + if (nexthop->nh_srv6->seg6local_action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + return; + + nexthop->nh_srv6->seg6local_action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + + if (nexthop->nh_srv6->seg6_segs && + (nexthop->nh_srv6->seg6_segs->num_segs == 0 || + sid_zero(nexthop->nh_srv6->seg6_segs))) + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6->seg6_segs); + + if (nexthop->nh_srv6->seg6_segs == NULL) + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6); +} + +void nexthop_add_srv6_seg6(struct nexthop *nexthop, const struct in6_addr *segs, + int num_segs) +{ + int i; + + if (!segs) + return; + + if (!nexthop->nh_srv6) + nexthop->nh_srv6 = XCALLOC(MTYPE_NH_SRV6, + sizeof(struct nexthop_srv6)); + + /* Enforce limit on srv6 seg stack size */ + if (num_segs > SRV6_MAX_SIDS) + num_segs = SRV6_MAX_SIDS; + + if (!nexthop->nh_srv6->seg6_segs) { + nexthop->nh_srv6->seg6_segs = + XCALLOC(MTYPE_NH_SRV6, + sizeof(struct seg6_seg_stack) + + num_segs * sizeof(struct in6_addr)); + } + + nexthop->nh_srv6->seg6_segs->num_segs = num_segs; + + for (i = 0; i < num_segs; i++) + memcpy(&nexthop->nh_srv6->seg6_segs->seg[i], &segs[i], + sizeof(struct in6_addr)); +} + +void nexthop_del_srv6_seg6(struct nexthop *nexthop) +{ + if (!nexthop->nh_srv6) + return; + + if (nexthop->nh_srv6->seg6local_action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC && + nexthop->nh_srv6->seg6_segs) { + memset(nexthop->nh_srv6->seg6_segs->seg, 0, + sizeof(struct in6_addr) * + nexthop->nh_srv6->seg6_segs->num_segs); + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6->seg6_segs); + } + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6); +} + +const char *nexthop2str(const struct nexthop *nexthop, char *str, int size) +{ + switch (nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + snprintf(str, size, "if %u", nexthop->ifindex); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + snprintfrr(str, size, "%pI4 if %u", &nexthop->gate.ipv4, + nexthop->ifindex); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + snprintfrr(str, size, "%pI6 if %u", &nexthop->gate.ipv6, + nexthop->ifindex); + break; + case NEXTHOP_TYPE_BLACKHOLE: + snprintf(str, size, "blackhole"); + break; + } + + return str; +} + +/* + * Iteration step for ALL_NEXTHOPS macro: + * This is the tricky part. Check if `nexthop' has + * NEXTHOP_FLAG_RECURSIVE set. If yes, this implies that `nexthop' has + * at least one nexthop attached to `nexthop->resolved', which will be + * the next one. + * + * If NEXTHOP_FLAG_RECURSIVE is not set, `nexthop' will progress in its + * current chain. In case its current chain end is reached, it will move + * upwards in the recursion levels and progress there. Whenever a step + * forward in a chain is done, recursion will be checked again. + * In a nustshell, it's equivalent to a pre-traversal order assuming that + * left branch is 'resolved' and right branch is 'next': + * https://en.wikipedia.org/wiki/Tree_traversal#/media/File:Sorted_binary_tree_preorder.svg + */ +struct nexthop *nexthop_next(const struct nexthop *nexthop) +{ + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + return nexthop->resolved; + + if (nexthop->next) + return nexthop->next; + + for (struct nexthop *par = nexthop->rparent; par; par = par->rparent) + if (par->next) + return par->next; + + return NULL; +} + +/* Return the next nexthop in the tree that is resolved and active */ +struct nexthop *nexthop_next_active_resolved(const struct nexthop *nexthop) +{ + struct nexthop *next = nexthop_next(nexthop); + + while (next + && (CHECK_FLAG(next->flags, NEXTHOP_FLAG_RECURSIVE) + || !CHECK_FLAG(next->flags, NEXTHOP_FLAG_ACTIVE))) + next = nexthop_next(next); + + return next; +} + +unsigned int nexthop_level(const struct nexthop *nexthop) +{ + unsigned int rv = 0; + + for (const struct nexthop *par = nexthop->rparent; + par; par = par->rparent) + rv++; + + return rv; +} + +/* Only hash word-sized things, let cmp do the rest. */ +uint32_t nexthop_hash_quick(const struct nexthop *nexthop) +{ + uint32_t key = 0x45afe398; + int i; + + key = jhash_3words(nexthop->type, nexthop->vrf_id, + nexthop->nh_label_type, key); + + if (nexthop->nh_label) { + int labels = nexthop->nh_label->num_labels; + + i = 0; + + while (labels >= 3) { + key = jhash_3words(nexthop->nh_label->label[i], + nexthop->nh_label->label[i + 1], + nexthop->nh_label->label[i + 2], + key); + labels -= 3; + i += 3; + } + + if (labels >= 2) { + key = jhash_2words(nexthop->nh_label->label[i], + nexthop->nh_label->label[i + 1], + key); + labels -= 2; + i += 2; + } + + if (labels >= 1) + key = jhash_1word(nexthop->nh_label->label[i], key); + } + + key = jhash_2words(nexthop->ifindex, + CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK), + key); + + /* Include backup nexthops, if present */ + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + int backups = nexthop->backup_num; + + i = 0; + + while (backups >= 3) { + key = jhash_3words(nexthop->backup_idx[i], + nexthop->backup_idx[i + 1], + nexthop->backup_idx[i + 2], key); + backups -= 3; + i += 3; + } + + while (backups >= 2) { + key = jhash_2words(nexthop->backup_idx[i], + nexthop->backup_idx[i + 1], key); + backups -= 2; + i += 2; + } + + if (backups >= 1) + key = jhash_1word(nexthop->backup_idx[i], key); + } + + if (nexthop->nh_srv6) { + int segs_num = 0; + int i = 0; + + if (nexthop->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) { + key = jhash_1word(nexthop->nh_srv6->seg6local_action, + key); + key = jhash(&nexthop->nh_srv6->seg6local_ctx, + sizeof(nexthop->nh_srv6->seg6local_ctx), + key); + if (nexthop->nh_srv6->seg6_segs) + key = jhash(&nexthop->nh_srv6->seg6_segs->seg[0], + sizeof(struct in6_addr), key); + } else { + if (nexthop->nh_srv6->seg6_segs) { + segs_num = nexthop->nh_srv6->seg6_segs->num_segs; + while (segs_num >= 1) { + key = jhash(&nexthop->nh_srv6->seg6_segs + ->seg[i], + sizeof(struct in6_addr), + key); + segs_num -= 1; + i += 1; + } + } + } + } + + return key; +} + + +#define GATE_SIZE 4 /* Number of uint32_t words in struct g_addr */ + +/* For a more granular hash */ +uint32_t nexthop_hash(const struct nexthop *nexthop) +{ + uint32_t gate_src_rmap_raw[GATE_SIZE * 3] = {}; + /* Get all the quick stuff */ + uint32_t key = nexthop_hash_quick(nexthop); + + assert(((sizeof(nexthop->gate) + sizeof(nexthop->src) + + sizeof(nexthop->rmap_src)) + / 3) + == (GATE_SIZE * sizeof(uint32_t))); + + memcpy(gate_src_rmap_raw, &nexthop->gate, GATE_SIZE); + memcpy(gate_src_rmap_raw + GATE_SIZE, &nexthop->src, GATE_SIZE); + memcpy(gate_src_rmap_raw + (2 * GATE_SIZE), &nexthop->rmap_src, + GATE_SIZE); + + key = jhash2(gate_src_rmap_raw, (GATE_SIZE * 3), key); + + return key; +} + +void nexthop_copy_no_recurse(struct nexthop *copy, + const struct nexthop *nexthop, + struct nexthop *rparent) +{ + copy->vrf_id = nexthop->vrf_id; + copy->ifindex = nexthop->ifindex; + copy->type = nexthop->type; + copy->flags = nexthop->flags; + copy->weight = nexthop->weight; + + assert(nexthop->backup_num < NEXTHOP_MAX_BACKUPS); + copy->backup_num = nexthop->backup_num; + if (copy->backup_num > 0) + memcpy(copy->backup_idx, nexthop->backup_idx, copy->backup_num); + + copy->srte_color = nexthop->srte_color; + memcpy(©->gate, &nexthop->gate, sizeof(nexthop->gate)); + memcpy(©->src, &nexthop->src, sizeof(nexthop->src)); + memcpy(©->rmap_src, &nexthop->rmap_src, sizeof(nexthop->rmap_src)); + memcpy(©->rmac, &nexthop->rmac, sizeof(nexthop->rmac)); + copy->rparent = rparent; + if (nexthop->nh_label) + nexthop_add_labels(copy, nexthop->nh_label_type, + nexthop->nh_label->num_labels, + &nexthop->nh_label->label[0]); + + if (nexthop->nh_srv6) { + if (nexthop->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + nexthop_add_srv6_seg6local(copy, + nexthop->nh_srv6->seg6local_action, + &nexthop->nh_srv6->seg6local_ctx); + if (nexthop->nh_srv6->seg6_segs && + nexthop->nh_srv6->seg6_segs->num_segs && + !sid_zero(nexthop->nh_srv6->seg6_segs)) + nexthop_add_srv6_seg6(copy, + &nexthop->nh_srv6->seg6_segs->seg[0], + nexthop->nh_srv6->seg6_segs + ->num_segs); + } +} + +void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop, + struct nexthop *rparent) +{ + nexthop_copy_no_recurse(copy, nexthop, rparent); + + /* Bit of a special case here, we need to handle the case + * of a nexthop resolving to a group. Hence, we need to + * use a nexthop_group API. + */ + if (CHECK_FLAG(copy->flags, NEXTHOP_FLAG_RECURSIVE)) + copy_nexthops(©->resolved, nexthop->resolved, copy); +} + +struct nexthop *nexthop_dup_no_recurse(const struct nexthop *nexthop, + struct nexthop *rparent) +{ + struct nexthop *new = nexthop_new(); + + nexthop_copy_no_recurse(new, nexthop, rparent); + return new; +} + +struct nexthop *nexthop_dup(const struct nexthop *nexthop, + struct nexthop *rparent) +{ + struct nexthop *new = nexthop_new(); + + nexthop_copy(new, nexthop, rparent); + return new; +} + +/* + * Parse one or more backup index values, as comma-separated numbers, + * into caller's array of uint8_ts. The array must be NEXTHOP_MAX_BACKUPS + * in size. Mails back the number of values converted, and returns 0 on + * success, <0 if an error in parsing. + */ +int nexthop_str2backups(const char *str, int *num_backups, + uint8_t *backups) +{ + char *ostr; /* copy of string (start) */ + char *lstr; /* working copy of string */ + char *nump; /* pointer to next segment */ + char *endp; /* end pointer */ + int i, ret; + uint8_t tmp[NEXTHOP_MAX_BACKUPS]; + uint32_t lval; + + /* Copy incoming string; the parse is destructive */ + lstr = ostr = XSTRDUP(MTYPE_TMP, str); + *num_backups = 0; + ret = 0; + + for (i = 0; i < NEXTHOP_MAX_BACKUPS && lstr; i++) { + nump = strsep(&lstr, ","); + lval = strtoul(nump, &endp, 10); + + /* Format check */ + if (*endp != '\0') { + ret = -1; + break; + } + + /* Empty value */ + if (endp == nump) { + ret = -1; + break; + } + + /* Limit to one octet */ + if (lval > 255) { + ret = -1; + break; + } + + tmp[i] = lval; + } + + /* Excess values */ + if (ret == 0 && i == NEXTHOP_MAX_BACKUPS && lstr) + ret = -1; + + if (ret == 0) { + *num_backups = i; + memcpy(backups, tmp, i); + } + + XFREE(MTYPE_TMP, ostr); + + return ret; +} + +ssize_t printfrr_nhs(struct fbuf *buf, const struct nexthop *nexthop) +{ + ssize_t ret = 0; + + if (!nexthop) + return bputs(buf, "(null)"); + + switch (nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + ret += bprintfrr(buf, "if %u", nexthop->ifindex); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + ret += bprintfrr(buf, "%pI4 if %u", &nexthop->gate.ipv4, + nexthop->ifindex); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret += bprintfrr(buf, "%pI6 if %u", &nexthop->gate.ipv6, + nexthop->ifindex); + break; + case NEXTHOP_TYPE_BLACKHOLE: + ret += bputs(buf, "blackhole"); + break; + } + + ret += bprintfrr(buf, " vrfid %u", nexthop->vrf_id); + return ret; +} + +/* + * nexthop printing variants: + * %pNHvv + * via 1.2.3.4 + * via 1.2.3.4, eth0 + * is directly connected, eth0 + * unreachable (blackhole) + * %pNHv + * 1.2.3.4 + * 1.2.3.4, via eth0 + * directly connected, eth0 + * unreachable (blackhole) + * %pNHs + * nexthop2str() + * %pNHcg + * 1.2.3.4 + * (0-length if no IP address present) + * %pNHci + * eth0 + * (0-length if no interface present) + */ +printfrr_ext_autoreg_p("NH", printfrr_nh); +static ssize_t printfrr_nh(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct nexthop *nexthop = ptr; + bool do_ifi = false; + const char *v_is = "", *v_via = "", *v_viaif = "via "; + ssize_t ret = 0; + + switch (*ea->fmt) { + case 'v': + ea->fmt++; + if (*ea->fmt == 'v') { + v_is = "is "; + v_via = "via "; + v_viaif = ""; + ea->fmt++; + } + + if (!nexthop) + return bputs(buf, "(null)"); + + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + ret += bprintfrr(buf, "%s%pI4", v_via, + &nexthop->gate.ipv4); + do_ifi = true; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret += bprintfrr(buf, "%s%pI6", v_via, + &nexthop->gate.ipv6); + do_ifi = true; + break; + case NEXTHOP_TYPE_IFINDEX: + ret += bprintfrr(buf, "%sdirectly connected, %s", v_is, + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + ret += bputs(buf, "unreachable"); + + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + ret += bputs(buf, " (ICMP unreachable)"); + break; + case BLACKHOLE_ADMINPROHIB: + ret += bputs(buf, " (ICMP admin-prohibited)"); + break; + case BLACKHOLE_NULL: + ret += bputs(buf, " (blackhole)"); + break; + case BLACKHOLE_UNSPEC: + break; + } + break; + } + if (do_ifi && nexthop->ifindex) + ret += bprintfrr(buf, ", %s%s", v_viaif, + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + + return ret; + case 's': + ea->fmt++; + + ret += printfrr_nhs(buf, nexthop); + return ret; + case 'c': + ea->fmt++; + if (*ea->fmt == 'g') { + ea->fmt++; + if (!nexthop) + return bputs(buf, "(null)"); + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + ret += bprintfrr(buf, "%pI4", + &nexthop->gate.ipv4); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret += bprintfrr(buf, "%pI6", + &nexthop->gate.ipv6); + break; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + } else if (*ea->fmt == 'i') { + ea->fmt++; + if (!nexthop) + return bputs(buf, "(null)"); + switch (nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + ret += bprintfrr( + buf, "%s", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (nexthop->ifindex) + ret += bprintfrr( + buf, "%s", + ifindex2ifname( + nexthop->ifindex, + nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + } + return ret; + } + return -1; +} + +bool nexthop_is_blackhole(const struct nexthop *nh) +{ + return nh->type == NEXTHOP_TYPE_BLACKHOLE; +} + +/* + * Render a nexthop into a json object; the caller allocates and owns + * the json object memory. + */ +void nexthop_json_helper(json_object *json_nexthop, + const struct nexthop *nexthop, bool display_vrfid, + uint8_t rn_family) +{ + json_object *json_labels = NULL; + json_object *json_backups = NULL; + json_object *json_seg6local = NULL; + json_object *json_seg6 = NULL; + json_object *json_segs = NULL; + int i; + + json_object_int_add(json_nexthop, "flags", nexthop->flags); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE)) + json_object_boolean_true_add(json_nexthop, "duplicate"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + json_object_boolean_true_add(json_nexthop, "fib"); + + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + json_object_string_addf(json_nexthop, "ip", "%pI4", + &nexthop->gate.ipv4); + json_object_string_add(json_nexthop, "afi", "ipv4"); + + if (nexthop->ifindex) { + json_object_int_add(json_nexthop, "interfaceIndex", + nexthop->ifindex); + json_object_string_add(json_nexthop, "interfaceName", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + } + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + json_object_string_addf(json_nexthop, "ip", "%pI6", + &nexthop->gate.ipv6); + json_object_string_add(json_nexthop, "afi", "ipv6"); + + if (nexthop->ifindex) { + json_object_int_add(json_nexthop, "interfaceIndex", + nexthop->ifindex); + json_object_string_add(json_nexthop, "interfaceName", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + } + break; + + case NEXTHOP_TYPE_IFINDEX: + json_object_boolean_true_add(json_nexthop, "directlyConnected"); + json_object_int_add(json_nexthop, "interfaceIndex", + nexthop->ifindex); + json_object_string_add(json_nexthop, "interfaceName", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + json_object_boolean_true_add(json_nexthop, "unreachable"); + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + json_object_boolean_true_add(json_nexthop, "reject"); + break; + case BLACKHOLE_ADMINPROHIB: + json_object_boolean_true_add(json_nexthop, + "adminProhibited"); + break; + case BLACKHOLE_NULL: + json_object_boolean_true_add(json_nexthop, "blackhole"); + break; + case BLACKHOLE_UNSPEC: + break; + } + break; + } + + /* This nexthop is a resolver for the parent nexthop. + * Set resolver flag for better clarity and delimiter + * in flat list of nexthops in json. + */ + if (nexthop->rparent) + json_object_boolean_true_add(json_nexthop, "resolver"); + + if (display_vrfid) + json_object_string_add(json_nexthop, "vrf", + vrf_id_to_name(nexthop->vrf_id)); + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE)) + json_object_boolean_true_add(json_nexthop, "duplicate"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) + json_object_boolean_true_add(json_nexthop, "active"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) + json_object_boolean_true_add(json_nexthop, "onLink"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_LINKDOWN)) + json_object_boolean_true_add(json_nexthop, "linkDown"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + json_object_boolean_true_add(json_nexthop, "recursive"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + json_backups = json_object_new_array(); + for (i = 0; i < nexthop->backup_num; i++) { + json_object_array_add(json_backups, + json_object_new_int( + nexthop->backup_idx[i])); + } + + json_object_object_add(json_nexthop, "backupIndex", + json_backups); + } + + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + if (nexthop->rmap_src.ipv4.s_addr) + json_object_string_addf(json_nexthop, "rmapSource", + "%pI4", &nexthop->rmap_src.ipv4); + else if (nexthop->src.ipv4.s_addr) + json_object_string_addf(json_nexthop, "source", "%pI4", + &nexthop->src.ipv4); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + /* Allow for 5549 ipv4 prefix with ipv6 nexthop */ + if (rn_family == AF_INET && nexthop->rmap_src.ipv4.s_addr) + json_object_string_addf(json_nexthop, "rmapSource", + "%pI4", &nexthop->rmap_src.ipv4); + else if (!IPV6_ADDR_SAME(&nexthop->rmap_src.ipv6, &in6addr_any)) + json_object_string_addf(json_nexthop, "rmapSource", + "%pI6", &nexthop->rmap_src.ipv6); + else if (!IPV6_ADDR_SAME(&nexthop->src.ipv6, &in6addr_any)) + json_object_string_addf(json_nexthop, "source", "%pI6", + &nexthop->src.ipv6); + break; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + + if (nexthop->nh_label && nexthop->nh_label->num_labels) { + json_labels = json_object_new_array(); + + for (int label_index = 0; + label_index < nexthop->nh_label->num_labels; label_index++) + json_object_array_add( + json_labels, + json_object_new_int(( + (nexthop->nh_label_type == ZEBRA_LSP_EVPN) + ? label2vni( + &nexthop->nh_label->label + [label_index]) + : nexthop->nh_label + ->label[label_index]))); + + json_object_object_add(json_nexthop, "labels", json_labels); + } + + if (nexthop->weight) + json_object_int_add(json_nexthop, "weight", nexthop->weight); + + if (nexthop->srte_color) + json_object_int_add(json_nexthop, "srteColor", + nexthop->srte_color); + + if (nexthop->nh_srv6) { + json_seg6local = json_object_new_object(); + json_object_string_add(json_seg6local, "action", + seg6local_action2str( + nexthop->nh_srv6 + ->seg6local_action)); + json_object_object_add(json_nexthop, "seg6local", + json_seg6local); + if (nexthop->nh_srv6->seg6_segs && + nexthop->nh_srv6->seg6_segs->num_segs == 1) { + json_seg6 = json_object_new_object(); + json_object_string_addf(json_seg6, "segs", "%pI6", + &nexthop->nh_srv6->seg6_segs + ->seg[0]); + json_object_object_add(json_nexthop, "seg6", json_seg6); + } else { + if (nexthop->nh_srv6->seg6_segs) { + json_segs = json_object_new_array(); + for (int seg_idx = 0; + seg_idx < + nexthop->nh_srv6->seg6_segs->num_segs; + seg_idx++) + json_object_array_add( + json_segs, + json_object_new_stringf( + "%pI6", + &nexthop->nh_srv6 + ->seg6_segs + ->seg[seg_idx])); + json_object_object_add(json_nexthop, "seg6", + json_segs); + } + } + } +} + +/* + * Helper for nexthop output + */ +void nexthop_vty_helper(struct vty *vty, const struct nexthop *nexthop, + bool display_vrfid, uint8_t rn_family) +{ + char buf[MPLS_LABEL_STRLEN]; + char seg_buf[SRV6_SEG_STRLEN]; + struct seg6_segs segs; + uint8_t i; + + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + vty_out(vty, " via %pI4", &nexthop->gate.ipv4); + if (nexthop->ifindex) + vty_out(vty, ", %s", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + vty_out(vty, " via %s", + inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf, + sizeof(buf))); + if (nexthop->ifindex) + vty_out(vty, ", %s", + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + break; + + case NEXTHOP_TYPE_IFINDEX: + vty_out(vty, " is directly connected, %s", + ifindex2ifname(nexthop->ifindex, nexthop->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + vty_out(vty, " unreachable"); + switch (nexthop->bh_type) { + case BLACKHOLE_REJECT: + vty_out(vty, " (ICMP unreachable)"); + break; + case BLACKHOLE_ADMINPROHIB: + vty_out(vty, " (ICMP admin-prohibited)"); + break; + case BLACKHOLE_NULL: + vty_out(vty, " (blackhole)"); + break; + case BLACKHOLE_UNSPEC: + break; + } + break; + } + + if (display_vrfid) + vty_out(vty, " (vrf %s)", vrf_id_to_name(nexthop->vrf_id)); + + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) + vty_out(vty, " inactive"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) + vty_out(vty, " onlink"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_LINKDOWN)) + vty_out(vty, " linkdown"); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + vty_out(vty, " (recursive)"); + + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + if (nexthop->rmap_src.ipv4.s_addr) + vty_out(vty, ", rmapsrc %pI4", &nexthop->rmap_src.ipv4); + else if (nexthop->src.ipv4.s_addr) + vty_out(vty, ", src %pI4", &nexthop->src.ipv4); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + /* Allow for 5549 ipv4 prefix with ipv6 nexthop */ + if (rn_family == AF_INET && nexthop->rmap_src.ipv4.s_addr) + vty_out(vty, ", rmapsrc %pI4", &nexthop->rmap_src.ipv4); + else if (!IPV6_ADDR_SAME(&nexthop->rmap_src.ipv6, &in6addr_any)) + vty_out(vty, ", rmapsrc %pI6", &nexthop->rmap_src.ipv6); + else if (!IPV6_ADDR_SAME(&nexthop->src.ipv6, &in6addr_any)) + vty_out(vty, ", src %pI6", &nexthop->src.ipv6); + break; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + + /* SR-TE information */ + if (nexthop->srte_color) + vty_out(vty, ", SR-TE color %u", nexthop->srte_color); + + /* Label information */ + if (nexthop->nh_label && nexthop->nh_label->num_labels) { + vty_out(vty, ", label %s", + mpls_label2str(nexthop->nh_label->num_labels, + nexthop->nh_label->label, buf, + sizeof(buf), nexthop->nh_label_type, 1)); + } + + if (nexthop->nh_srv6) { + seg6local_context2str(buf, sizeof(buf), + &nexthop->nh_srv6->seg6local_ctx, + nexthop->nh_srv6->seg6local_action); + if (nexthop->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + vty_out(vty, ", seg6local %s %s", + seg6local_action2str( + nexthop->nh_srv6->seg6local_action), + buf); + if (nexthop->nh_srv6->seg6_segs && + IPV6_ADDR_CMP(&nexthop->nh_srv6->seg6_segs->seg[0], + &in6addr_any)) { + segs.num_segs = nexthop->nh_srv6->seg6_segs->num_segs; + for (i = 0; i < segs.num_segs; i++) + memcpy(&segs.segs[i], + &nexthop->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); + snprintf_seg6_segs(seg_buf, SRV6_SEG_STRLEN, &segs); + vty_out(vty, ", seg6 %s", seg_buf); + } + } + + if (nexthop->weight) + vty_out(vty, ", weight %u", nexthop->weight); + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + vty_out(vty, ", backup %d", nexthop->backup_idx[0]); + + for (i = 1; i < nexthop->backup_num; i++) + vty_out(vty, ",%d", nexthop->backup_idx[i]); + } +} diff --git a/lib/nexthop.h b/lib/nexthop.h new file mode 100644 index 0000000..27073b9 --- /dev/null +++ b/lib/nexthop.h @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nexthop structure definition. + * Copyright (C) 1997, 98, 99, 2001 Kunihiro Ishiguro + * Copyright (C) 2013 Cumulus Networks, Inc. + */ + +#ifndef _LIB_NEXTHOP_H +#define _LIB_NEXTHOP_H + +#include "prefix.h" +#include "mpls.h" +#include "vxlan.h" +#include "srv6.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maximum next hop string length - gateway + ifindex */ +#define NEXTHOP_STRLEN (INET6_ADDRSTRLEN + 30) + +union g_addr { + struct in_addr ipv4; + struct in6_addr ipv6; +}; + +enum nexthop_types_t { + NEXTHOP_TYPE_IFINDEX = 1, /* Directly connected. */ + NEXTHOP_TYPE_IPV4, /* IPv4 nexthop. */ + NEXTHOP_TYPE_IPV4_IFINDEX, /* IPv4 nexthop with ifindex. */ + NEXTHOP_TYPE_IPV6, /* IPv6 nexthop. */ + NEXTHOP_TYPE_IPV6_IFINDEX, /* IPv6 nexthop with ifindex. */ + NEXTHOP_TYPE_BLACKHOLE, /* Null0 nexthop. */ +}; + +enum blackhole_type { + BLACKHOLE_UNSPEC = 0, + BLACKHOLE_NULL, + BLACKHOLE_REJECT, + BLACKHOLE_ADMINPROHIB, +}; + +enum nh_encap_type { + NET_VXLAN = 100, /* value copied from FPM_NH_ENCAP_VXLAN. */ +}; + +/* Fixed limit on the number of backup nexthops per primary nexthop */ +#define NEXTHOP_MAX_BACKUPS 8 + +/* Backup index value is limited */ +#define NEXTHOP_BACKUP_IDX_MAX 255 + +/* Nexthop structure. */ +struct nexthop { + struct nexthop *next; + struct nexthop *prev; + + /* + * What vrf is this nexthop associated with? + */ + vrf_id_t vrf_id; + + /* Interface index. */ + ifindex_t ifindex; + + enum nexthop_types_t type; + + uint16_t flags; +#define NEXTHOP_FLAG_ACTIVE (1 << 0) /* This nexthop is alive. */ +#define NEXTHOP_FLAG_FIB (1 << 1) /* FIB nexthop. */ +#define NEXTHOP_FLAG_RECURSIVE (1 << 2) /* Recursive nexthop. */ +#define NEXTHOP_FLAG_ONLINK (1 << 3) /* Nexthop should be installed + * onlink. + */ +#define NEXTHOP_FLAG_DUPLICATE (1 << 4) /* nexthop duplicates another + * active one + */ +#define NEXTHOP_FLAG_RNH_FILTERED (1 << 5) /* rmap filtered, used by rnh */ +#define NEXTHOP_FLAG_HAS_BACKUP (1 << 6) /* Backup nexthop index is set */ +#define NEXTHOP_FLAG_SRTE (1 << 7) /* SR-TE color used for BGP traffic */ +#define NEXTHOP_FLAG_EVPN (1 << 8) /* nexthop is EVPN */ +#define NEXTHOP_FLAG_LINKDOWN (1 << 9) /* is not removed on link down */ + +#define NEXTHOP_IS_ACTIVE(flags) \ + (CHECK_FLAG(flags, NEXTHOP_FLAG_ACTIVE) \ + && !CHECK_FLAG(flags, NEXTHOP_FLAG_DUPLICATE)) + + /* Nexthop address */ + union { + union g_addr gate; + enum blackhole_type bh_type; + }; + union g_addr src; + union g_addr rmap_src; /* Src is set via routemap */ + + /* Nexthops obtained by recursive resolution. + * + * If the nexthop struct needs to be resolved recursively, + * NEXTHOP_FLAG_RECURSIVE will be set in flags and the nexthops + * obtained by recursive resolution will be added to `resolved'. + */ + struct nexthop *resolved; + /* Recursive parent */ + struct nexthop *rparent; + + /* Type of label(s), if any */ + enum lsp_types_t nh_label_type; + + /* Label(s) associated with this nexthop. */ + struct mpls_label_stack *nh_label; + + /* Weight of the nexthop ( for unequal cost ECMP ) */ + uint8_t weight; + + /* Count and index of corresponding backup nexthop(s) in a backup list; + * only meaningful if the HAS_BACKUP flag is set. + */ + uint8_t backup_num; + uint8_t backup_idx[NEXTHOP_MAX_BACKUPS]; + + /* Encapsulation information. */ + enum nh_encap_type nh_encap_type; + union { + vni_t vni; + } nh_encap; + + /* EVPN router's MAC. + * Don't support multiple RMAC from the same VTEP yet, so it's not + * included in hash key. + */ + struct ethaddr rmac; + + /* SR-TE color used for matching SR-TE policies */ + uint32_t srte_color; + + /* SRv6 information */ + struct nexthop_srv6 *nh_srv6; +}; + +/* Utility to append one nexthop to another. */ +#define NEXTHOP_APPEND(to, new) \ + do { \ + (to)->next = (new); \ + (new)->prev = (to); \ + (new)->next = NULL; \ + } while (0) + +struct nexthop *nexthop_new(void); + +void nexthop_free(struct nexthop *nexthop); +void nexthops_free(struct nexthop *nexthop); + +void nexthop_add_labels(struct nexthop *nexthop, enum lsp_types_t ltype, + uint8_t num_labels, const mpls_label_t *labels); +void nexthop_del_labels(struct nexthop *); +void nexthop_add_srv6_seg6local(struct nexthop *nexthop, uint32_t action, + const struct seg6local_context *ctx); +void nexthop_del_srv6_seg6local(struct nexthop *nexthop); +void nexthop_add_srv6_seg6(struct nexthop *nexthop, const struct in6_addr *seg, + int num_segs); +void nexthop_del_srv6_seg6(struct nexthop *nexthop); + +/* + * Allocate a new nexthop object and initialize it from various args. + */ +struct nexthop *nexthop_from_ifindex(ifindex_t ifindex, vrf_id_t vrf_id); +struct nexthop *nexthop_from_ipv4(const struct in_addr *ipv4, + const struct in_addr *src, + vrf_id_t vrf_id); +struct nexthop *nexthop_from_ipv4_ifindex(const struct in_addr *ipv4, + const struct in_addr *src, + ifindex_t ifindex, vrf_id_t vrf_id); +struct nexthop *nexthop_from_ipv6(const struct in6_addr *ipv6, + vrf_id_t vrf_id); +struct nexthop *nexthop_from_ipv6_ifindex(const struct in6_addr *ipv6, + ifindex_t ifindex, vrf_id_t vrf_id); +struct nexthop *nexthop_from_blackhole(enum blackhole_type bh_type, + vrf_id_t nh_vrf_id); + +/* + * Hash a nexthop. Suitable for use with hash tables. + * + * This function uses the following values when computing the hash: + * - vrf_id + * - ifindex + * - type + * - gate + * + * nexthop + * The nexthop to hash + * + * Returns: + * 32-bit hash of nexthop + */ +uint32_t nexthop_hash(const struct nexthop *nexthop); +/* + * Hash a nexthop only on word-sized attributes: + * - vrf_id + * - ifindex + * - type + * - (some) flags + */ +uint32_t nexthop_hash_quick(const struct nexthop *nexthop); + +extern bool nexthop_same(const struct nexthop *nh1, const struct nexthop *nh2); +extern bool nexthop_same_no_labels(const struct nexthop *nh1, + const struct nexthop *nh2); +extern int nexthop_cmp(const struct nexthop *nh1, const struct nexthop *nh2); +extern int nexthop_g_addr_cmp(enum nexthop_types_t type, + const union g_addr *addr1, + const union g_addr *addr2); + +/* More-limited comparison function used to detect duplicate nexthops. + * Returns -1, 0, 1 + */ +int nexthop_cmp_basic(const struct nexthop *nh1, const struct nexthop *nh2); + +extern const char *nexthop_type_to_str(enum nexthop_types_t nh_type); +extern bool nexthop_labels_match(const struct nexthop *nh1, + const struct nexthop *nh2); + +extern const char *nexthop2str(const struct nexthop *nexthop, + char *str, int size); +extern struct nexthop *nexthop_next(const struct nexthop *nexthop); +extern struct nexthop * +nexthop_next_active_resolved(const struct nexthop *nexthop); +extern unsigned int nexthop_level(const struct nexthop *nexthop); +/* Copies to an already allocated nexthop struct */ +extern void nexthop_copy(struct nexthop *copy, const struct nexthop *nexthop, + struct nexthop *rparent); +/* Copies to an already allocated nexthop struct, not including recurse info */ +extern void nexthop_copy_no_recurse(struct nexthop *copy, + const struct nexthop *nexthop, + struct nexthop *rparent); +/* Duplicates a nexthop and returns the newly allocated nexthop */ +extern struct nexthop *nexthop_dup(const struct nexthop *nexthop, + struct nexthop *rparent); +/* Duplicates a nexthop and returns the newly allocated nexthop */ +extern struct nexthop *nexthop_dup_no_recurse(const struct nexthop *nexthop, + struct nexthop *rparent); + +/* Is this nexthop a blackhole? */ +extern bool nexthop_is_blackhole(const struct nexthop *nh); + +/* + * Parse one or more backup index values, as comma-separated numbers, + * into caller's array of uint8_ts. The array must be NEXTHOP_MAX_BACKUPS + * in size. Mails back the number of values converted, and returns 0 on + * success, <0 if an error in parsing. + */ +int nexthop_str2backups(const char *str, int *num_backups, + uint8_t *backups); + +void nexthop_json_helper(json_object *json_nexthop, + const struct nexthop *nexthop, bool display_vrfid, + uint8_t rn_family); +void nexthop_vty_helper(struct vty *vty, const struct nexthop *nexthop, + bool display_vrfid, uint8_t rn_family); + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pNH" (struct nexthop *) +#endif + +ssize_t printfrr_nhs(struct fbuf *buf, const struct nexthop *nh); +#ifdef __cplusplus +} +#endif + +#endif /*_LIB_NEXTHOP_H */ diff --git a/lib/nexthop_group.c b/lib/nexthop_group.c new file mode 100644 index 0000000..3f408e0 --- /dev/null +++ b/lib/nexthop_group.c @@ -0,0 +1,1391 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nexthop Group structure definition. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/nexthop_group_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, NEXTHOP_GROUP, "Nexthop Group"); + +/* + * Internal struct used to hold nhg config strings + */ +struct nexthop_hold { + char *nhvrf_name; + union sockunion *addr; + char *intf; + bool onlink; + char *labels; + vni_t vni; + uint32_t weight; + char *backup_str; +}; + +struct nexthop_group_hooks { + void (*new)(const char *name); + void (*modify)(const struct nexthop_group_cmd *nhgc); + void (*add_nexthop)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); + void (*del_nexthop)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); + void (*delete)(const char *name); +}; + +static struct nexthop_group_hooks nhg_hooks; + +static inline int +nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, + const struct nexthop_group_cmd *nhgc2); +RB_GENERATE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, + nexthop_group_cmd_compare) + +static struct nhgc_entry_head nhgc_entries; + +static inline int +nexthop_group_cmd_compare(const struct nexthop_group_cmd *nhgc1, + const struct nexthop_group_cmd *nhgc2) +{ + return strcmp(nhgc1->name, nhgc2->name); +} + +static struct nexthop *nexthop_group_tail(const struct nexthop_group *nhg) +{ + struct nexthop *nexthop = nhg->nexthop; + + while (nexthop && nexthop->next) + nexthop = nexthop->next; + + return nexthop; +} + +uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg) +{ + struct nexthop *nhop; + uint8_t num = 0; + + for (ALL_NEXTHOPS_PTR(nhg, nhop)) + num++; + + return num; +} + +static uint8_t +nexthop_group_nexthop_num_no_recurse(const struct nexthop_group *nhg) +{ + struct nexthop *nhop; + uint8_t num = 0; + + for (nhop = nhg->nexthop; nhop; nhop = nhop->next) + num++; + + return num; +} + +uint8_t nexthop_group_active_nexthop_num(const struct nexthop_group *nhg) +{ + struct nexthop *nhop; + uint8_t num = 0; + + for (ALL_NEXTHOPS_PTR(nhg, nhop)) { + if (CHECK_FLAG(nhop->flags, NEXTHOP_FLAG_ACTIVE)) + num++; + } + + return num; +} + +bool nexthop_group_has_label(const struct nexthop_group *nhg) +{ + struct nexthop *nhop; + + for (ALL_NEXTHOPS_PTR(nhg, nhop)) { + if (nhop->nh_label) + return true; + } + + return false; +} + +struct nexthop *nexthop_exists(const struct nexthop_group *nhg, + const struct nexthop *nh) +{ + struct nexthop *nexthop; + + for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { + if (nexthop_same(nh, nexthop)) + return nexthop; + } + + return NULL; +} + +/* + * Helper that locates a nexthop in an nhg config list. Note that + * this uses a specific matching / equality rule that's different from + * the complete match performed by 'nexthop_same()'. + */ +static struct nexthop *nhg_nh_find(const struct nexthop_group *nhg, + const struct nexthop *nh) +{ + struct nexthop *nexthop; + int ret; + + /* We compare: vrf, gateway, and interface */ + + for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { + + /* Compare vrf and type */ + if (nexthop->vrf_id != nh->vrf_id) + continue; + if (nexthop->type != nh->type) + continue; + + /* Compare gateway */ + switch (nexthop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + ret = nexthop_g_addr_cmp(nexthop->type, + &nexthop->gate, &nh->gate); + if (ret != 0) + continue; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret = nexthop_g_addr_cmp(nexthop->type, + &nexthop->gate, &nh->gate); + if (ret != 0) + continue; + fallthrough; + case NEXTHOP_TYPE_IFINDEX: + if (nexthop->ifindex != nh->ifindex) + continue; + break; + case NEXTHOP_TYPE_BLACKHOLE: + if (nexthop->bh_type != nh->bh_type) + continue; + break; + } + + return nexthop; + } + + return NULL; +} + +static bool +nexthop_group_equal_common(const struct nexthop_group *nhg1, + const struct nexthop_group *nhg2, + uint8_t (*nexthop_group_nexthop_num_func)( + const struct nexthop_group *nhg)) +{ + if (nhg1 && !nhg2) + return false; + + if (!nhg1 && nhg2) + return false; + + if (nhg1 == nhg2) + return true; + + if (nexthop_group_nexthop_num_func(nhg1) + != nexthop_group_nexthop_num_func(nhg2)) + return false; + + return true; +} + +/* This assumes ordered */ +bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1, + const struct nexthop_group *nhg2) +{ + struct nexthop *nh1 = NULL; + struct nexthop *nh2 = NULL; + + if (!nexthop_group_equal_common(nhg1, nhg2, + &nexthop_group_nexthop_num_no_recurse)) + return false; + + for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2; + nh1 = nh1->next, nh2 = nh2->next) { + if (nh1 && !nh2) + return false; + if (!nh1 && nh2) + return false; + if (!nexthop_same(nh1, nh2)) + return false; + } + + return true; +} + +/* This assumes ordered */ +bool nexthop_group_equal(const struct nexthop_group *nhg1, + const struct nexthop_group *nhg2) +{ + struct nexthop *nh1 = NULL; + struct nexthop *nh2 = NULL; + + if (!nexthop_group_equal_common(nhg1, nhg2, &nexthop_group_nexthop_num)) + return false; + + for (nh1 = nhg1->nexthop, nh2 = nhg2->nexthop; nh1 || nh2; + nh1 = nexthop_next(nh1), nh2 = nexthop_next(nh2)) { + if (nh1 && !nh2) + return false; + if (!nh1 && nh2) + return false; + if (!nexthop_same(nh1, nh2)) + return false; + } + + return true; +} +struct nexthop_group *nexthop_group_new(void) +{ + return XCALLOC(MTYPE_NEXTHOP_GROUP, sizeof(struct nexthop_group)); +} + +void nexthop_group_copy(struct nexthop_group *to, + const struct nexthop_group *from) +{ + to->nhgr = from->nhgr; + /* Copy everything, including recursive info */ + copy_nexthops(&to->nexthop, from->nexthop, NULL); +} + +void nexthop_group_delete(struct nexthop_group **nhg) +{ + /* OK to call with NULL group */ + if ((*nhg) == NULL) + return; + + if ((*nhg)->nexthop) + nexthops_free((*nhg)->nexthop); + + XFREE(MTYPE_NEXTHOP_GROUP, *nhg); +} + +/* Add nexthop to the end of a nexthop list. */ +void _nexthop_add(struct nexthop **target, struct nexthop *nexthop) +{ + struct nexthop *last; + + for (last = *target; last && last->next; last = last->next) + ; + if (last) + last->next = nexthop; + else + *target = nexthop; + nexthop->prev = last; +} + +/* Add nexthop to sorted list of nexthops */ +static void _nexthop_add_sorted(struct nexthop **head, + struct nexthop *nexthop) +{ + struct nexthop *position, *prev; + + assert(!nexthop->next); + + for (position = *head, prev = NULL; position; + prev = position, position = position->next) { + if (nexthop_cmp(position, nexthop) > 0) { + nexthop->next = position; + nexthop->prev = prev; + + if (nexthop->prev) + nexthop->prev->next = nexthop; + else + *head = nexthop; + + position->prev = nexthop; + return; + } + } + + nexthop->prev = prev; + if (prev) + prev->next = nexthop; + else + *head = nexthop; +} + +void nexthop_group_add_sorted(struct nexthop_group *nhg, + struct nexthop *nexthop) +{ + struct nexthop *tail; + + assert(!nexthop->next); + + /* Try to just append to the end first; + * trust the list is already sorted + */ + tail = nexthop_group_tail(nhg); + + if (tail && (nexthop_cmp(tail, nexthop) < 0)) { + tail->next = nexthop; + nexthop->prev = tail; + + return; + } + + _nexthop_add_sorted(&nhg->nexthop, nexthop); +} + +/* Delete nexthop from a nexthop list. */ +void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nh) +{ + struct nexthop *nexthop; + + for (nexthop = nhg->nexthop; nexthop; nexthop = nexthop->next) { + if (nexthop_same(nh, nexthop)) + break; + } + + assert(nexthop); + + if (nexthop->prev) + nexthop->prev->next = nexthop->next; + else + nhg->nexthop = nexthop->next; + + if (nexthop->next) + nexthop->next->prev = nexthop->prev; + + nh->prev = NULL; + nh->next = NULL; +} + +/* Unlink a nexthop from the list it's on, unconditionally */ +static void nexthop_unlink(struct nexthop_group *nhg, struct nexthop *nexthop) +{ + + if (nexthop->prev) + nexthop->prev->next = nexthop->next; + else { + assert(nhg->nexthop == nexthop); + assert(nexthop->prev == NULL); + nhg->nexthop = nexthop->next; + } + + if (nexthop->next) + nexthop->next->prev = nexthop->prev; + + nexthop->prev = NULL; + nexthop->next = NULL; +} + +/* + * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order + */ +void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg, + const struct nexthop *nh) +{ + struct nexthop *nexthop, *tail; + const struct nexthop *nh1; + + /* We'll try to append to the end of the new list; + * if the original list in nh is already sorted, this eliminates + * lots of comparison operations. + */ + tail = nexthop_group_tail(nhg); + + for (nh1 = nh; nh1; nh1 = nh1->next) { + nexthop = nexthop_dup(nh1, NULL); + + if (tail && (nexthop_cmp(tail, nexthop) < 0)) { + tail->next = nexthop; + nexthop->prev = tail; + + tail = nexthop; + continue; + } + + _nexthop_add_sorted(&nhg->nexthop, nexthop); + + if (tail == NULL) + tail = nexthop; + } +} + +/* Copy a list of nexthops, no effort made to sort or order them. */ +void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh, + struct nexthop *rparent) +{ + struct nexthop *nexthop; + const struct nexthop *nh1; + + for (nh1 = nh; nh1; nh1 = nh1->next) { + nexthop = nexthop_dup(nh1, rparent); + _nexthop_add(tnh, nexthop); + } +} + +uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg) +{ + struct nexthop *nh; + uint32_t key = 0; + + /* + * We are not interested in hashing over any recursively + * resolved nexthops + */ + for (nh = nhg->nexthop; nh; nh = nh->next) + key = jhash_1word(nexthop_hash(nh), key); + + return key; +} + +uint32_t nexthop_group_hash(const struct nexthop_group *nhg) +{ + struct nexthop *nh; + uint32_t key = 0; + + for (ALL_NEXTHOPS_PTR(nhg, nh)) + key = jhash_1word(nexthop_hash(nh), key); + + return key; +} + +void nexthop_group_mark_duplicates(struct nexthop_group *nhg) +{ + struct nexthop *nexthop, *prev; + + for (ALL_NEXTHOPS_PTR(nhg, nexthop)) { + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_DUPLICATE); + for (ALL_NEXTHOPS_PTR(nhg, prev)) { + if (prev == nexthop) + break; + if (nexthop_same(nexthop, prev)) { + SET_FLAG(nexthop->flags, + NEXTHOP_FLAG_DUPLICATE); + break; + } + } + } +} + +static void nhgc_delete_nexthops(struct nexthop_group_cmd *nhgc) +{ + struct nexthop *nexthop; + + nexthop = nhgc->nhg.nexthop; + while (nexthop) { + struct nexthop *next = nexthop_next(nexthop); + + _nexthop_del(&nhgc->nhg, nexthop); + if (nhg_hooks.del_nexthop) + nhg_hooks.del_nexthop(nhgc, nexthop); + + nexthop_free(nexthop); + + nexthop = next; + } +} + +struct nexthop_group_cmd *nhgc_find(const char *name) +{ + struct nexthop_group_cmd find; + + strlcpy(find.name, name, sizeof(find.name)); + + return RB_FIND(nhgc_entry_head, &nhgc_entries, &find); +} + +static int nhgc_cmp_helper(const char *a, const char *b) +{ + if (!a && !b) + return 0; + + if (a && !b) + return -1; + + if (!a && b) + return 1; + + return strcmp(a, b); +} + +static int nhgc_addr_cmp_helper(const union sockunion *a, const union sockunion *b) +{ + if (!a && !b) + return 0; + + if (a && !b) + return -1; + + if (!a && b) + return 1; + + return sockunion_cmp(a, b); +} + +static int nhgl_cmp(struct nexthop_hold *nh1, struct nexthop_hold *nh2) +{ + int ret; + + ret = nhgc_addr_cmp_helper(nh1->addr, nh2->addr); + if (ret) + return ret; + + ret = nhgc_cmp_helper(nh1->intf, nh2->intf); + if (ret) + return ret; + + ret = nhgc_cmp_helper(nh1->nhvrf_name, nh2->nhvrf_name); + if (ret) + return ret; + + ret = ((int)nh2->onlink) - ((int)nh1->onlink); + if (ret) + return ret; + + return nhgc_cmp_helper(nh1->labels, nh2->labels); +} + +static void nhgl_delete(struct nexthop_hold *nh) +{ + XFREE(MTYPE_TMP, nh->intf); + + XFREE(MTYPE_TMP, nh->nhvrf_name); + + if (nh->addr) + sockunion_free(nh->addr); + + XFREE(MTYPE_TMP, nh->labels); + + XFREE(MTYPE_TMP, nh); +} + +static struct nexthop_group_cmd *nhgc_get(const char *name) +{ + struct nexthop_group_cmd *nhgc; + + nhgc = nhgc_find(name); + if (!nhgc) { + nhgc = XCALLOC(MTYPE_TMP, sizeof(*nhgc)); + strlcpy(nhgc->name, name, sizeof(nhgc->name)); + + QOBJ_REG(nhgc, nexthop_group_cmd); + RB_INSERT(nhgc_entry_head, &nhgc_entries, nhgc); + + nhgc->nhg_list = list_new(); + nhgc->nhg_list->cmp = (int (*)(void *, void *))nhgl_cmp; + nhgc->nhg_list->del = (void (*)(void *))nhgl_delete; + + if (nhg_hooks.new) + nhg_hooks.new(name); + } + + return nhgc; +} + +static void nhgc_delete(struct nexthop_group_cmd *nhgc) +{ + nhgc_delete_nexthops(nhgc); + + if (nhg_hooks.delete) + nhg_hooks.delete(nhgc->name); + + RB_REMOVE(nhgc_entry_head, &nhgc_entries, nhgc); + + list_delete(&nhgc->nhg_list); + + QOBJ_UNREG(nhgc); + XFREE(MTYPE_TMP, nhgc); +} + +DEFINE_QOBJ_TYPE(nexthop_group_cmd); + +DEFUN_NOSH(nexthop_group, nexthop_group_cmd, "nexthop-group NHGNAME", + "Enter into the nexthop-group submode\n" + "Specify the NAME of the nexthop-group\n") +{ + const char *nhg_name = argv[1]->arg; + struct nexthop_group_cmd *nhgc = NULL; + + nhgc = nhgc_get(nhg_name); + VTY_PUSH_CONTEXT(NH_GROUP_NODE, nhgc); + + return CMD_SUCCESS; +} + +DEFUN_NOSH(no_nexthop_group, no_nexthop_group_cmd, "no nexthop-group NHGNAME", + NO_STR + "Delete the nexthop-group\n" + "Specify the NAME of the nexthop-group\n") +{ + const char *nhg_name = argv[2]->arg; + struct nexthop_group_cmd *nhgc = NULL; + + nhgc = nhgc_find(nhg_name); + if (nhgc) + nhgc_delete(nhgc); + + return CMD_SUCCESS; +} + +DEFPY(nexthop_group_backup, nexthop_group_backup_cmd, + "backup-group WORD$name", + "Specify a group name containing backup nexthops\n" + "The name of the backup group\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + strlcpy(nhgc->backup_list_name, name, sizeof(nhgc->backup_list_name)); + + return CMD_SUCCESS; +} + +DEFPY(no_nexthop_group_backup, no_nexthop_group_backup_cmd, + "no backup-group [WORD$name]", + NO_STR + "Clear group name containing backup nexthops\n" + "The name of the backup group\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + nhgc->backup_list_name[0] = 0; + + return CMD_SUCCESS; +} + +DEFPY(nexthop_group_resilience, + nexthop_group_resilience_cmd, + "resilient buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)", + "A resilient Nexthop Group\n" + "Buckets in the Hash for this Group\n" + "Number of buckets\n" + "The Idle timer for this Resilient Nexthop Group in seconds\n" + "Number of seconds of Idle time\n" + "The length of time that the Nexthop Group can be unbalanced\n" + "Number of seconds of Unbalanced time\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + nhgc->nhg.nhgr.buckets = buckets; + nhgc->nhg.nhgr.idle_timer = idle_timer; + nhgc->nhg.nhgr.unbalanced_timer = unbalanced_timer; + + if (nhg_hooks.modify) + nhg_hooks.modify(nhgc); + + return CMD_SUCCESS; +} + +DEFPY(no_nexthop_group_resilience, + no_nexthop_group_resilience_cmd, + "no resilient [buckets (1-256) idle-timer (1-4294967295) unbalanced-timer (1-4294967295)]", + NO_STR + "A resilient Nexthop Group\n" + "Buckets in the Hash for this Group\n" + "Number of buckets\n" + "The Idle timer for this Resilient Nexthop Group in seconds\n" + "Number of seconds of Idle time\n" + "The length of time that the Nexthop Group can be unbalanced\n" + "Number of seconds of Unbalanced time\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + + nhgc->nhg.nhgr.buckets = 0; + nhgc->nhg.nhgr.idle_timer = 0; + nhgc->nhg.nhgr.unbalanced_timer = 0; + + return CMD_SUCCESS; +} + +static void nexthop_group_save_nhop(struct nexthop_group_cmd *nhgc, + const char *nhvrf_name, + const union sockunion *addr, + const char *intf, bool onlink, + const char *labels, const uint32_t weight, + const char *backup_str) +{ + struct nexthop_hold *nh; + + nh = XCALLOC(MTYPE_TMP, sizeof(*nh)); + + if (nhvrf_name) + nh->nhvrf_name = XSTRDUP(MTYPE_TMP, nhvrf_name); + if (intf) + nh->intf = XSTRDUP(MTYPE_TMP, intf); + if (addr) + nh->addr = sockunion_dup(addr); + if (labels) + nh->labels = XSTRDUP(MTYPE_TMP, labels); + + nh->onlink = onlink; + + nh->weight = weight; + + if (backup_str) + nh->backup_str = XSTRDUP(MTYPE_TMP, backup_str); + + listnode_add_sort(nhgc->nhg_list, nh); +} + +/* + * Remove config info about a nexthop from group 'nhgc'. Note that we + * use only a subset of the available attributes here to determine + * a 'match'. + * Note that this doesn't change the list of nexthops, only the config + * information. + */ +static void nexthop_group_unsave_nhop(struct nexthop_group_cmd *nhgc, + const char *nhvrf_name, + const union sockunion *addr, + const char *intf) +{ + struct nexthop_hold *nh; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { + if (nhgc_cmp_helper(nhvrf_name, nh->nhvrf_name) == 0 + && nhgc_addr_cmp_helper(addr, nh->addr) == 0 + && nhgc_cmp_helper(intf, nh->intf) == 0) + break; + } + + /* + * Something has gone seriously wrong, fail gracefully + */ + if (!nh) + return; + + list_delete_node(nhgc->nhg_list, node); + nhgl_delete(nh); +} + +/* + * Parse the config strings we support for a single nexthop. This gets used + * in a couple of different ways, and we distinguish between transient + * failures - such as a still-unprocessed interface - and fatal errors + * from label-string parsing. + */ +static bool nexthop_group_parse_nexthop(struct nexthop *nhop, + const union sockunion *addr, + const char *intf, bool onlink, + const char *name, const char *labels, + vni_t vni, int *lbl_ret, + uint32_t weight, const char *backup_str) +{ + int ret = 0; + struct vrf *vrf; + int num; + uint8_t labelnum = 0; + + memset(nhop, 0, sizeof(*nhop)); + + if (name) + vrf = vrf_lookup_by_name(name); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) + return false; + + nhop->vrf_id = vrf->vrf_id; + + if (intf) { + nhop->ifindex = ifname2ifindex(intf, vrf->vrf_id); + if (nhop->ifindex == IFINDEX_INTERNAL) + return false; + } + + if (onlink) + SET_FLAG(nhop->flags, NEXTHOP_FLAG_ONLINK); + + if (addr) { + if (addr->sa.sa_family == AF_INET) { + nhop->gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; + if (intf) + nhop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + nhop->type = NEXTHOP_TYPE_IPV4; + } else { + nhop->gate.ipv6 = addr->sin6.sin6_addr; + if (intf) + nhop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + else + nhop->type = NEXTHOP_TYPE_IPV6; + } + } else + nhop->type = NEXTHOP_TYPE_IFINDEX; + + if (labels) { + mpls_label_t larray[MPLS_MAX_LABELS]; + + ret = mpls_str2label(labels, &labelnum, larray); + + /* Return label parse result */ + if (lbl_ret) + *lbl_ret = ret; + + if (ret < 0) + return false; + else if (labelnum > 0) + nexthop_add_labels(nhop, ZEBRA_LSP_NONE, labelnum, + larray); + } else if (vni) { + mpls_label_t label = MPLS_INVALID_LABEL; + + vni2label(vni, &label); + nexthop_add_labels(nhop, ZEBRA_LSP_EVPN, 1, &label); + } + + nhop->weight = weight; + + if (backup_str) { + /* Parse backup indexes */ + ret = nexthop_str2backups(backup_str, + &num, nhop->backup_idx); + if (ret == 0) { + SET_FLAG(nhop->flags, NEXTHOP_FLAG_HAS_BACKUP); + nhop->backup_num = num; + } else + return false; + } + + return true; +} + +/* + * Wrapper to parse the strings in a 'nexthop_hold' + */ +static bool nexthop_group_parse_nhh(struct nexthop *nhop, + const struct nexthop_hold *nhh) +{ + return (nexthop_group_parse_nexthop( + nhop, nhh->addr, nhh->intf, nhh->onlink, nhh->nhvrf_name, + nhh->labels, nhh->vni, NULL, nhh->weight, nhh->backup_str)); +} + +DEFPY(ecmp_nexthops, ecmp_nexthops_cmd, + "[no] nexthop\ + <\ + $addr [INTERFACE$intf [onlink$onlink]]\ + |INTERFACE$intf\ + >\ + [{ \ + nexthop-vrf NAME$vrf_name \ + |label WORD \ + |vni (1-16777215) \ + |weight (1-255) \ + |backup-idx WORD \ + }]", + NO_STR + "Specify one of the nexthops in this ECMP group\n" + "v4 Address\n" + "v6 Address\n" + "Interface to use\n" + "Treat nexthop as directly attached to the interface\n" + "Interface to use\n" + "If the nexthop is in a different vrf tell us\n" + "The nexthop-vrf Name\n" + "Specify label(s) for this nexthop\n" + "One or more labels in the range (16-1048575) separated by '/'\n" + "Specify VNI(s) for this nexthop\n" + "VNI in the range (1-16777215)\n" + "Weight to be used by the nexthop for purposes of ECMP\n" + "Weight value to be used\n" + "Specify backup nexthop indexes in another group\n" + "One or more indexes in the range (0-254) separated by ','\n") +{ + VTY_DECLVAR_CONTEXT(nexthop_group_cmd, nhgc); + struct nexthop nhop; + struct nexthop *nh; + int lbl_ret = 0; + bool legal; + int num; + uint8_t backups[NEXTHOP_MAX_BACKUPS]; + bool yes = !no; + + /* Pre-parse backup string to validate */ + if (backup_idx) { + lbl_ret = nexthop_str2backups(backup_idx, &num, backups); + if (lbl_ret < 0) { + vty_out(vty, "%% Invalid backups\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + legal = nexthop_group_parse_nexthop(&nhop, addr, intf, !!onlink, + vrf_name, label, vni, &lbl_ret, + weight, backup_idx); + + if (nhop.type == NEXTHOP_TYPE_IPV6 + && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { + vty_out(vty, + "Specified a v6 LL with no interface, rejecting\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Handle label-string errors */ + if (!legal && lbl_ret < 0) { + switch (lbl_ret) { + case -1: + vty_out(vty, "%% Malformed label(s)\n"); + break; + case -2: + vty_out(vty, + "%% Cannot use reserved label(s) (%d-%d)\n", + MPLS_LABEL_RESERVED_MIN, + MPLS_LABEL_RESERVED_MAX); + break; + case -3: + vty_out(vty, + "%% Too many labels. Enter %d or fewer\n", + MPLS_MAX_LABELS); + break; + } + return CMD_WARNING_CONFIG_FAILED; + } + + /* Look for an existing nexthop in the config. Note that the test + * here tests only some attributes - it's not a complete comparison. + * Note that we've got two kinds of objects to manage: 'nexthop_hold' + * that represent config that may or may not be valid (yet), and + * actual nexthops that have been validated and parsed. + */ + nh = nhg_nh_find(&nhgc->nhg, &nhop); + + /* Always attempt to remove old config info. */ + nexthop_group_unsave_nhop(nhgc, vrf_name, addr, intf); + + /* Remove any existing nexthop, for delete and replace cases. */ + if (nh) { + nexthop_unlink(&nhgc->nhg, nh); + + if (nhg_hooks.del_nexthop) + nhg_hooks.del_nexthop(nhgc, nh); + + nexthop_free(nh); + } + if (yes) { + /* Add/replace case: capture nexthop if valid, and capture + * config info always. + */ + if (legal) { + nh = nexthop_new(); + + memcpy(nh, &nhop, sizeof(nhop)); + _nexthop_add(&nhgc->nhg.nexthop, nh); + } + + /* Save config always */ + nexthop_group_save_nhop(nhgc, vrf_name, addr, intf, !!onlink, + label, weight, backup_idx); + + if (legal && nhg_hooks.add_nexthop) + nhg_hooks.add_nexthop(nhgc, nh); + } + + return CMD_SUCCESS; +} + +static int nexthop_group_write(struct vty *vty); +static struct cmd_node nexthop_group_node = { + .name = "nexthop-group", + .node = NH_GROUP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-nh-group)# ", + .config_write = nexthop_group_write, +}; + +void nexthop_group_write_nexthop_simple(struct vty *vty, + const struct nexthop *nh, + char *altifname) +{ + char *ifname; + + vty_out(vty, "nexthop "); + + if (altifname) + ifname = altifname; + else + ifname = (char *)ifindex2ifname(nh->ifindex, nh->vrf_id); + + switch (nh->type) { + case NEXTHOP_TYPE_IFINDEX: + vty_out(vty, "%s", ifname); + break; + case NEXTHOP_TYPE_IPV4: + vty_out(vty, "%pI4", &nh->gate.ipv4); + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + vty_out(vty, "%pI4 %s", &nh->gate.ipv4, ifname); + break; + case NEXTHOP_TYPE_IPV6: + vty_out(vty, "%pI6", &nh->gate.ipv6); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + vty_out(vty, "%pI6 %s", &nh->gate.ipv6, ifname); + break; + case NEXTHOP_TYPE_BLACKHOLE: + vty_out(vty, "%s", "drop"); + break; + } +} + +void nexthop_group_write_nexthop(struct vty *vty, const struct nexthop *nh) +{ + struct vrf *vrf; + int i; + + nexthop_group_write_nexthop_simple(vty, nh, NULL); + + if (nh->vrf_id != VRF_DEFAULT) { + vrf = vrf_lookup_by_id(nh->vrf_id); + vty_out(vty, " nexthop-vrf %s", VRF_LOGNAME(vrf)); + } + + if (nh->nh_label && nh->nh_label->num_labels > 0) { + char buf[200]; + + mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label, + buf, sizeof(buf), nh->nh_label_type, 0); + vty_out(vty, " label %s", buf); + } + + if (nh->weight) + vty_out(vty, " weight %u", nh->weight); + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + vty_out(vty, " backup-idx %d", nh->backup_idx[0]); + + for (i = 1; i < nh->backup_num; i++) + vty_out(vty, ",%d", nh->backup_idx[i]); + } + + vty_out(vty, "\n"); +} + +void nexthop_group_json_nexthop(json_object *j, const struct nexthop *nh) +{ + struct vrf *vrf; + json_object *json_backups = NULL; + int i; + + switch (nh->type) { + case NEXTHOP_TYPE_IFINDEX: + json_object_string_add(j, "nexthop", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_IPV4: + json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4); + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + json_object_string_addf(j, "nexthop", "%pI4", &nh->gate.ipv4); + json_object_string_add(j, "vrfId", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_IPV6: + json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + json_object_string_addf(j, "nexthop", "%pI6", &nh->gate.ipv6); + json_object_string_add(j, "vrfId", + ifindex2ifname(nh->ifindex, nh->vrf_id)); + break; + case NEXTHOP_TYPE_BLACKHOLE: + break; + } + + if (nh->vrf_id != VRF_DEFAULT) { + vrf = vrf_lookup_by_id(nh->vrf_id); + json_object_string_add(j, "targetVrf", vrf->name); + } + + if (nh->nh_label && nh->nh_label->num_labels > 0) { + char buf[200]; + + mpls_label2str(nh->nh_label->num_labels, nh->nh_label->label, + buf, sizeof(buf), nh->nh_label_type, 0); + json_object_string_add(j, "label", buf); + } + + if (nh->weight) + json_object_int_add(j, "weight", nh->weight); + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + json_backups = json_object_new_array(); + for (i = 0; i < nh->backup_num; i++) + json_object_array_add( + json_backups, + json_object_new_int(nh->backup_idx[i])); + + json_object_object_add(j, "backupIdx", json_backups); + } +} + +static void nexthop_group_write_nexthop_internal(struct vty *vty, + const struct nexthop_hold *nh) +{ + vty_out(vty, "nexthop"); + + if (nh->addr) + vty_out(vty, " %pSU", nh->addr); + + if (nh->intf) + vty_out(vty, " %s", nh->intf); + + if (nh->onlink) + vty_out(vty, " onlink"); + + if (nh->nhvrf_name) + vty_out(vty, " nexthop-vrf %s", nh->nhvrf_name); + + if (nh->labels) + vty_out(vty, " label %s", nh->labels); + + if (nh->vni) + vty_out(vty, " vni %u", nh->vni); + + if (nh->weight) + vty_out(vty, " weight %u", nh->weight); + + if (nh->backup_str) + vty_out(vty, " backup-idx %s", nh->backup_str); + + vty_out(vty, "\n"); +} + +static int nexthop_group_write(struct vty *vty) +{ + struct nexthop_group_cmd *nhgc; + struct nexthop_hold *nh; + + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + struct listnode *node; + + vty_out(vty, "nexthop-group %s\n", nhgc->name); + + if (nhgc->nhg.nhgr.buckets) + vty_out(vty, + " resilient buckets %u idle-timer %u unbalanced-timer %u\n", + nhgc->nhg.nhgr.buckets, + nhgc->nhg.nhgr.idle_timer, + nhgc->nhg.nhgr.unbalanced_timer); + + if (nhgc->backup_list_name[0]) + vty_out(vty, " backup-group %s\n", + nhgc->backup_list_name); + + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nh)) { + vty_out(vty, " "); + nexthop_group_write_nexthop_internal(vty, nh); + } + + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); + } + + return 1; +} + +void nexthop_group_enable_vrf(struct vrf *vrf) +{ + struct nexthop_group_cmd *nhgc; + struct nexthop_hold *nhh; + + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { + struct nexthop nhop; + struct nexthop *nh; + + if (!nexthop_group_parse_nhh(&nhop, nhh)) + continue; + + nh = nexthop_exists(&nhgc->nhg, &nhop); + + if (nh) + continue; + + if (nhop.vrf_id != vrf->vrf_id) + continue; + + nh = nexthop_new(); + + memcpy(nh, &nhop, sizeof(nhop)); + _nexthop_add(&nhgc->nhg.nexthop, nh); + + if (nhg_hooks.add_nexthop) + nhg_hooks.add_nexthop(nhgc, nh); + } + } +} + +void nexthop_group_disable_vrf(struct vrf *vrf) +{ + struct nexthop_group_cmd *nhgc; + struct nexthop_hold *nhh; + + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(nhgc->nhg_list, node, nnode, nhh)) { + struct nexthop nhop; + struct nexthop *nh; + + if (!nexthop_group_parse_nhh(&nhop, nhh)) + continue; + + nh = nexthop_exists(&nhgc->nhg, &nhop); + + if (!nh) + continue; + + if (nh->vrf_id != vrf->vrf_id) + continue; + + _nexthop_del(&nhgc->nhg, nh); + + if (nhg_hooks.del_nexthop) + nhg_hooks.del_nexthop(nhgc, nh); + + nexthop_free(nh); + + list_delete_node(nhgc->nhg_list, node); + + nhgl_delete(nhh); + } + } +} + +void nexthop_group_interface_state_change(struct interface *ifp, + ifindex_t oldifindex) +{ + struct nexthop_group_cmd *nhgc; + struct nexthop_hold *nhh; + + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + struct listnode *node; + struct nexthop *nh; + + if (if_is_up(ifp)) { + for (ALL_LIST_ELEMENTS_RO(nhgc->nhg_list, node, nhh)) { + struct nexthop nhop; + + if (!nexthop_group_parse_nhh(&nhop, nhh)) + continue; + + switch (nhop.type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_BLACKHOLE: + continue; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + break; + } + nh = nexthop_exists(&nhgc->nhg, &nhop); + + if (nh) + continue; + + if (ifp->ifindex != nhop.ifindex) + continue; + + nh = nexthop_new(); + + memcpy(nh, &nhop, sizeof(nhop)); + _nexthop_add(&nhgc->nhg.nexthop, nh); + + if (nhg_hooks.add_nexthop) + nhg_hooks.add_nexthop(nhgc, nh); + } + } else { + struct nexthop *next_nh; + + for (nh = nhgc->nhg.nexthop; nh; nh = next_nh) { + next_nh = nh->next; + switch (nh->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_BLACKHOLE: + continue; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + break; + } + + if (oldifindex != nh->ifindex) + continue; + + _nexthop_del(&nhgc->nhg, nh); + + if (nhg_hooks.del_nexthop) + nhg_hooks.del_nexthop(nhgc, nh); + + nexthop_free(nh); + } + } + } +} + +static void nhg_name_autocomplete(vector comps, struct cmd_token *token) +{ + struct nexthop_group_cmd *nhgc; + + RB_FOREACH (nhgc, nhgc_entry_head, &nhgc_entries) { + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, nhgc->name)); + } +} + +static const struct cmd_variable_handler nhg_name_handlers[] = { + {.tokenname = "NHGNAME", .completions = nhg_name_autocomplete}, + {.completions = NULL}}; + +void nexthop_group_init(void (*new)(const char *name), + void (*modify)(const struct nexthop_group_cmd *nhgc), + void (*add_nexthop)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop), + void (*del_nexthop)(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop), + void (*delete)(const char *name)) +{ + RB_INIT(nhgc_entry_head, &nhgc_entries); + + cmd_variable_handler_register(nhg_name_handlers); + + install_node(&nexthop_group_node); + install_element(CONFIG_NODE, &nexthop_group_cmd); + install_element(CONFIG_NODE, &no_nexthop_group_cmd); + + install_default(NH_GROUP_NODE); + install_element(NH_GROUP_NODE, &nexthop_group_backup_cmd); + install_element(NH_GROUP_NODE, &no_nexthop_group_backup_cmd); + install_element(NH_GROUP_NODE, &ecmp_nexthops_cmd); + + install_element(NH_GROUP_NODE, &nexthop_group_resilience_cmd); + install_element(NH_GROUP_NODE, &no_nexthop_group_resilience_cmd); + + memset(&nhg_hooks, 0, sizeof(nhg_hooks)); + + if (new) + nhg_hooks.new = new; + if (modify) + nhg_hooks.modify = modify; + if (add_nexthop) + nhg_hooks.add_nexthop = add_nexthop; + if (del_nexthop) + nhg_hooks.del_nexthop = del_nexthop; + if (delete) + nhg_hooks.delete = delete; +} diff --git a/lib/nexthop_group.h b/lib/nexthop_group.h new file mode 100644 index 0000000..822a354 --- /dev/null +++ b/lib/nexthop_group.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nexthop Group structure definition. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __NEXTHOP_GROUP__ +#define __NEXTHOP_GROUP__ + +#include +#include "json.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct nhg_resilience { + uint16_t buckets; + uint32_t idle_timer; + uint32_t unbalanced_timer; + uint64_t unbalanced_time; +}; + +/* + * What is a nexthop group? + * + * A nexthop group is a collection of nexthops that make up + * the ECMP path for the route. + * + * This module provides a proper abstraction to this idea. + */ +struct nexthop_group { + struct nexthop *nexthop; + + struct nhg_resilience nhgr; +}; + +struct nexthop_group *nexthop_group_new(void); +void nexthop_group_delete(struct nexthop_group **nhg); + +void nexthop_group_copy(struct nexthop_group *to, + const struct nexthop_group *from); + +/* + * Copy a list of nexthops in 'nh' to an nhg, enforcing canonical sort order + */ +void nexthop_group_copy_nh_sorted(struct nexthop_group *nhg, + const struct nexthop *nh); + +void copy_nexthops(struct nexthop **tnh, const struct nexthop *nh, + struct nexthop *rparent); + +uint32_t nexthop_group_hash_no_recurse(const struct nexthop_group *nhg); +uint32_t nexthop_group_hash(const struct nexthop_group *nhg); +void nexthop_group_mark_duplicates(struct nexthop_group *nhg); + +/* Add a nexthop to a list, enforcing the canonical sort order. */ +void nexthop_group_add_sorted(struct nexthop_group *nhg, + struct nexthop *nexthop); + +/* The following for loop allows to iterate over the nexthop + * structure of routes. + * + * head: The pointer to the first nexthop in the chain. + * + * nexthop: The pointer to the current nexthop, either in the + * top-level chain or in a resolved chain. + */ +#define ALL_NEXTHOPS(head, nhop) \ + (nhop) = (head.nexthop); \ + (nhop); \ + (nhop) = nexthop_next(nhop) + +#define ALL_NEXTHOPS_PTR(head, nhop) \ + (nhop) = ((head)->nexthop); \ + (nhop); \ + (nhop) = nexthop_next(nhop) + + +#define NHGC_NAME_SIZE 80 + +struct nexthop_group_cmd { + + RB_ENTRY(nexthop_group_cmd) nhgc_entry; + + char name[NHGC_NAME_SIZE]; + + /* Name of group containing backup nexthops (if set) */ + char backup_list_name[NHGC_NAME_SIZE]; + + struct nexthop_group nhg; + + struct list *nhg_list; + + QOBJ_FIELDS; +}; +RB_HEAD(nhgc_entry_head, nexthp_group_cmd); +RB_PROTOTYPE(nhgc_entry_head, nexthop_group_cmd, nhgc_entry, + nexthop_group_cmd_compare) +DECLARE_QOBJ_TYPE(nexthop_group_cmd); + +/* + * Initialize nexthop_groups. If you are interested in when + * a nexthop_group is added/deleted/modified, then set the + * appropriate callback functions to handle it in your + * code + * + * create - The creation of the nexthop group + * modify - Modification of the nexthop group when not changing a nexthop + * ( resilience as an example ) + * add_nexthop - A nexthop is added to the NHG + * del_nexthop - A nexthop is deleted from the NHG + * destroy - The NHG is deleted + */ +void nexthop_group_init( + void (*create)(const char *name), + void (*modify)(const struct nexthop_group_cmd *nhgc), + void (*add_nexthop)(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop), + void (*del_nexthop)(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop), + void (*destroy)(const char *name)); + +void nexthop_group_enable_vrf(struct vrf *vrf); +void nexthop_group_disable_vrf(struct vrf *vrf); +void nexthop_group_interface_state_change(struct interface *ifp, + ifindex_t oldifindex); + +extern struct nexthop *nexthop_exists(const struct nexthop_group *nhg, + const struct nexthop *nh); +/* This assumes ordered */ +extern bool nexthop_group_equal_no_recurse(const struct nexthop_group *nhg1, + const struct nexthop_group *nhg2); + +/* This assumes ordered */ +extern bool nexthop_group_equal(const struct nexthop_group *nhg1, + const struct nexthop_group *nhg2); + +extern struct nexthop_group_cmd *nhgc_find(const char *name); + +extern void nexthop_group_write_nexthop_simple(struct vty *vty, + const struct nexthop *nh, + char *altifname); +extern void nexthop_group_write_nexthop(struct vty *vty, + const struct nexthop *nh); + +extern void nexthop_group_json_nexthop(json_object *j, + const struct nexthop *nh); + +/* Return the number of nexthops in this nhg */ +extern uint8_t nexthop_group_nexthop_num(const struct nexthop_group *nhg); +extern uint8_t +nexthop_group_active_nexthop_num(const struct nexthop_group *nhg); + +extern bool nexthop_group_has_label(const struct nexthop_group *nhg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/nexthop_group_private.h b/lib/nexthop_group_private.h new file mode 100644 index 0000000..b057186 --- /dev/null +++ b/lib/nexthop_group_private.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nexthop Group Private Functions. + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ + +/** + * These functions should only be used internally for nexthop groups + * and in certain special cases. Please use `lib/nexthop_group.h` for + * any general nexthop_group api needs. + */ + +#ifndef __NEXTHOP_GROUP_PRIVATE__ +#define __NEXTHOP_GROUP_PRIVATE__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void _nexthop_add(struct nexthop **target, struct nexthop *nexthop); +void _nexthop_del(struct nexthop_group *nhg, struct nexthop *nexthop); + +#ifdef __cplusplus +} +#endif + +#endif /* __NEXTHOP_GROUP_PRIVATE__ */ diff --git a/lib/northbound.c b/lib/northbound.c new file mode 100644 index 0000000..0bc79d0 --- /dev/null +++ b/lib/northbound.c @@ -0,0 +1,2757 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "darr.h" +#include "libfrr.h" +#include "log.h" +#include "lib_errors.h" +#include "hash.h" +#include "command.h" +#include "debug.h" +#include "db.h" +#include "frr_pthread.h" +#include "northbound.h" +#include "northbound_cli.h" +#include "northbound_db.h" +#include "frrstr.h" + +DEFINE_MTYPE_STATIC(LIB, NB_NODE, "Northbound Node"); +DEFINE_MTYPE_STATIC(LIB, NB_CONFIG, "Northbound Configuration"); +DEFINE_MTYPE_STATIC(LIB, NB_CONFIG_ENTRY, "Northbound Configuration Entry"); + +/* Running configuration - shouldn't be modified directly. */ +struct nb_config *running_config; + +/* Hash table of user pointers associated with configuration entries. */ +static struct hash *running_config_entries; + +/* Management lock for the running configuration. */ +static struct { + /* Mutex protecting this structure. */ + pthread_mutex_t mtx; + + /* Actual lock. */ + bool locked; + + /* Northbound client who owns this lock. */ + enum nb_client owner_client; + + /* Northbound user who owns this lock. */ + const void *owner_user; +} running_config_mgmt_lock; + +/* Knob to record config transaction */ +static bool nb_db_enabled; +/* + * Global lock used to prevent multiple configuration transactions from + * happening concurrently. + */ +static bool transaction_in_progress; + +static int nb_callback_pre_validate(struct nb_context *context, + const struct nb_node *nb_node, + const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len); +static int nb_callback_configuration(struct nb_context *context, + const enum nb_event event, + struct nb_config_change *change, + char *errmsg, size_t errmsg_len); +static struct nb_transaction * +nb_transaction_new(struct nb_context context, struct nb_config *config, + struct nb_config_cbs *changes, const char *comment, + char *errmsg, size_t errmsg_len); +static void nb_transaction_free(struct nb_transaction *transaction); +static int nb_transaction_process(enum nb_event event, + struct nb_transaction *transaction, + char *errmsg, size_t errmsg_len); +static void nb_transaction_apply_finish(struct nb_transaction *transaction, + char *errmsg, size_t errmsg_len); + +static int nb_node_check_config_only(const struct lysc_node *snode, void *arg) +{ + bool *config_only = arg; + + if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) { + *config_only = false; + return YANG_ITER_STOP; + } + + return YANG_ITER_CONTINUE; +} + +static int nb_node_new_cb(const struct lysc_node *snode, void *arg) +{ + struct nb_node *nb_node; + struct lysc_node *sparent, *sparent_list; + struct frr_yang_module_info *module; + + module = (struct frr_yang_module_info *)arg; + nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node)); + yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath, + sizeof(nb_node->xpath)); + nb_node->priority = NB_DFLT_PRIORITY; + sparent = yang_snode_real_parent(snode); + if (sparent) + nb_node->parent = sparent->priv; + sparent_list = yang_snode_parent_list(snode); + if (sparent_list) + nb_node->parent_list = sparent_list->priv; + + /* Set flags. */ + if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) { + bool config_only = true; + + (void)yang_snodes_iterate_subtree(snode, NULL, + nb_node_check_config_only, 0, + &config_only); + if (config_only) + SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY); + } + if (CHECK_FLAG(snode->nodetype, LYS_LIST)) { + if (yang_snode_num_keys(snode) == 0) + SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST); + } + + /* + * Link the northbound node and the libyang schema node with one + * another. + */ + nb_node->snode = snode; + assert(snode->priv == NULL); + ((struct lysc_node *)snode)->priv = nb_node; + + if (module && module->ignore_cfg_cbs) + SET_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS); + + return YANG_ITER_CONTINUE; +} + +static int nb_node_del_cb(const struct lysc_node *snode, void *arg) +{ + struct nb_node *nb_node; + + nb_node = snode->priv; + if (nb_node) { + ((struct lysc_node *)snode)->priv = NULL; + XFREE(MTYPE_NB_NODE, nb_node); + } + + return YANG_ITER_CONTINUE; +} + +void nb_nodes_create(void) +{ + yang_snodes_iterate(NULL, nb_node_new_cb, 0, NULL); +} + +void nb_nodes_delete(void) +{ + yang_snodes_iterate(NULL, nb_node_del_cb, 0, NULL); +} + +struct nb_node *nb_node_find(const char *path) +{ + const struct lysc_node *snode; + uint32_t llopts = 0; + + /* + * Use libyang to find the schema node associated to the path and get + * the northbound node from there (snode private pointer). We need to + * disable logging temporarily to avoid libyang from logging an error + * message when the node is not found. + */ + ly_temp_log_options(&llopts); + + snode = yang_find_snode(ly_native_ctx, path, 0); + + ly_temp_log_options(NULL); + if (!snode) + return NULL; + + return snode->priv; +} + +struct nb_node **nb_nodes_find(const char *xpath) +{ + struct lysc_node **snodes = NULL; + struct nb_node **nb_nodes = NULL; + bool simple; + LY_ERR err; + uint i; + + err = yang_resolve_snode_xpath(ly_native_ctx, xpath, &snodes, &simple); + if (err) + return NULL; + + darr_ensure_i(nb_nodes, darr_lasti(snodes)); + darr_foreach_i (snodes, i) + nb_nodes[i] = snodes[i]->priv; + darr_free(snodes); + return nb_nodes; +} + + +void nb_node_set_dependency_cbs(const char *dependency_xpath, + const char *dependant_xpath, + struct nb_dependency_callbacks *cbs) +{ + struct nb_node *dependency = nb_node_find(dependency_xpath); + struct nb_node *dependant = nb_node_find(dependant_xpath); + + if (!dependency || !dependant) + return; + + dependency->dep_cbs.get_dependant_xpath = cbs->get_dependant_xpath; + dependant->dep_cbs.get_dependency_xpath = cbs->get_dependency_xpath; +} + +bool nb_node_has_dependency(struct nb_node *node) +{ + return node->dep_cbs.get_dependency_xpath != NULL; +} + +static int nb_node_validate_cb(const struct nb_node *nb_node, + enum nb_cb_operation operation, + int callback_implemented, bool optional) +{ + bool valid; + + valid = nb_cb_operation_is_valid(operation, nb_node->snode); + + /* + * Add an exception for operational data callbacks. A rw list usually + * doesn't need any associated operational data callbacks. But if this + * rw list is augmented by another module which adds state nodes under + * it, then this list will need to have the 'get_next()', 'get_keys()' + * and 'lookup_entry()' callbacks. As such, never log a warning when + * these callbacks are implemented when they are not needed, since this + * depends on context (e.g. some daemons might augment "frr-interface" + * while others don't). + */ + if (!valid && callback_implemented && operation != NB_CB_GET_NEXT + && operation != NB_CB_GET_KEYS && operation != NB_CB_LOOKUP_ENTRY) + flog_warn(EC_LIB_NB_CB_UNNEEDED, + "unneeded '%s' callback for '%s'", + nb_cb_operation_name(operation), nb_node->xpath); + + if (!optional && valid && !callback_implemented) { + flog_err(EC_LIB_NB_CB_MISSING, "missing '%s' callback for '%s'", + nb_cb_operation_name(operation), nb_node->xpath); + return 1; + } + + return 0; +} + +/* + * Check if the required callbacks were implemented for the given northbound + * node. + */ +static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node) + +{ + unsigned int error = 0; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return error; + + error += nb_node_validate_cb(nb_node, NB_CB_CREATE, + !!nb_node->cbs.create, false); + error += nb_node_validate_cb(nb_node, NB_CB_MODIFY, + !!nb_node->cbs.modify, false); + error += nb_node_validate_cb(nb_node, NB_CB_DESTROY, + !!nb_node->cbs.destroy, false); + error += nb_node_validate_cb(nb_node, NB_CB_MOVE, !!nb_node->cbs.move, + false); + error += nb_node_validate_cb(nb_node, NB_CB_PRE_VALIDATE, + !!nb_node->cbs.pre_validate, true); + error += nb_node_validate_cb(nb_node, NB_CB_APPLY_FINISH, + !!nb_node->cbs.apply_finish, true); + error += nb_node_validate_cb(nb_node, NB_CB_GET_ELEM, + !!nb_node->cbs.get_elem, false); + error += nb_node_validate_cb(nb_node, NB_CB_GET_NEXT, + !!nb_node->cbs.get_next, false); + error += nb_node_validate_cb(nb_node, NB_CB_GET_KEYS, + !!nb_node->cbs.get_keys, false); + error += nb_node_validate_cb(nb_node, NB_CB_LOOKUP_ENTRY, + !!nb_node->cbs.lookup_entry, false); + error += nb_node_validate_cb(nb_node, NB_CB_RPC, !!nb_node->cbs.rpc, + false); + error += nb_node_validate_cb(nb_node, NB_CB_NOTIFY, + !!nb_node->cbs.notify, true); + + return error; +} + +static unsigned int nb_node_validate_priority(const struct nb_node *nb_node) +{ + /* Top-level nodes can have any priority. */ + if (!nb_node->parent) + return 0; + + if (nb_node->priority < nb_node->parent->priority) { + flog_err(EC_LIB_NB_CB_INVALID_PRIO, + "node has higher priority than its parent [xpath %s]", + nb_node->xpath); + return 1; + } + + return 0; +} + +static int nb_node_validate(const struct lysc_node *snode, void *arg) +{ + struct nb_node *nb_node = snode->priv; + unsigned int *errors = arg; + + /* Validate callbacks and priority. */ + if (nb_node) { + *errors += nb_node_validate_cbs(nb_node); + *errors += nb_node_validate_priority(nb_node); + } + + return YANG_ITER_CONTINUE; +} + +struct nb_config *nb_config_new(struct lyd_node *dnode) +{ + struct nb_config *config; + + config = XCALLOC(MTYPE_NB_CONFIG, sizeof(*config)); + if (dnode) + config->dnode = dnode; + else + config->dnode = yang_dnode_new(ly_native_ctx, true); + config->version = 0; + + return config; +} + +void nb_config_free(struct nb_config *config) +{ + if (config->dnode) + yang_dnode_free(config->dnode); + + XFREE(MTYPE_NB_CONFIG, config); +} + +struct nb_config *nb_config_dup(const struct nb_config *config) +{ + struct nb_config *dup; + + dup = XCALLOC(MTYPE_NB_CONFIG, sizeof(*dup)); + dup->dnode = yang_dnode_dup(config->dnode); + dup->version = config->version; + + return dup; +} + +int nb_config_merge(struct nb_config *config_dst, struct nb_config *config_src, + bool preserve_source) +{ + int ret; + + ret = lyd_merge_siblings(&config_dst->dnode, config_src->dnode, 0); + if (ret != 0) + flog_warn(EC_LIB_LIBYANG, "%s: lyd_merge() failed", __func__); + + if (!preserve_source) + nb_config_free(config_src); + + return (ret == 0) ? NB_OK : NB_ERR; +} + +void nb_config_replace(struct nb_config *config_dst, + struct nb_config *config_src, bool preserve_source) +{ + /* Update version. */ + if (config_src->version != 0) + config_dst->version = config_src->version; + + /* Update dnode. */ + if (config_dst->dnode) + yang_dnode_free(config_dst->dnode); + if (preserve_source) { + config_dst->dnode = yang_dnode_dup(config_src->dnode); + } else { + config_dst->dnode = config_src->dnode; + config_src->dnode = NULL; + nb_config_free(config_src); + } +} + +/* Generate the nb_config_cbs tree. */ +static inline int nb_config_cb_compare(const struct nb_config_cb *a, + const struct nb_config_cb *b) +{ + bool a_destroy = a->operation == NB_CB_DESTROY; + bool b_destroy = b->operation == NB_CB_DESTROY; + + /* + * Sort by operation first. All "destroys" must come first, to correctly + * process the change of a "case" inside a "choice". The old "case" must + * be deleted before the new "case" is created. + */ + if (a_destroy && !b_destroy) + return -1; + if (!a_destroy && b_destroy) + return 1; + + /* + * Then sort by priority. If the operation is "destroy", reverse the + * order, so that the dependants are deleted before the dependencies. + */ + if (a->nb_node->priority < b->nb_node->priority) + return !a_destroy ? -1 : 1; + if (a->nb_node->priority > b->nb_node->priority) + return !a_destroy ? 1 : -1; + + /* + * Preserve the order of the configuration changes as told by libyang. + */ + if (a->seq < b->seq) + return -1; + if (a->seq > b->seq) + return 1; + + /* + * All 'apply_finish' callbacks have their sequence number set to zero. + * In this case, compare them using their dnode pointers (the order + * doesn't matter for callbacks that have the same priority). + */ + if (a->dnode < b->dnode) + return -1; + if (a->dnode > b->dnode) + return 1; + + return 0; +} +RB_GENERATE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare); + +void nb_config_diff_add_change(struct nb_config_cbs *changes, + enum nb_cb_operation operation, uint32_t *seq, + const struct lyd_node *dnode) +{ + struct nb_config_change *change; + + /* Ignore unimplemented nodes. */ + if (!dnode->schema->priv) + return; + + change = XCALLOC(MTYPE_TMP, sizeof(*change)); + change->cb.operation = operation; + change->cb.seq = *seq; + *seq = *seq + 1; + change->cb.nb_node = dnode->schema->priv; + change->cb.dnode = dnode; + + RB_INSERT(nb_config_cbs, changes, &change->cb); +} + +void nb_config_diff_del_changes(struct nb_config_cbs *changes) +{ + while (!RB_EMPTY(nb_config_cbs, changes)) { + struct nb_config_change *change; + + change = (struct nb_config_change *)RB_ROOT(nb_config_cbs, + changes); + RB_REMOVE(nb_config_cbs, changes, &change->cb); + XFREE(MTYPE_TMP, change); + } +} + +/* + * Helper function used when calculating the delta between two different + * configurations. Given a new subtree, calculate all new YANG data nodes, + * excluding default leafs and leaf-lists. This is a recursive function. + */ +void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq, + struct nb_config_cbs *changes) +{ + enum nb_cb_operation operation; + struct lyd_node *child; + + /* Ignore unimplemented nodes. */ + if (!dnode->schema->priv) + return; + + switch (dnode->schema->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + if (lyd_is_default(dnode)) + break; + + if (nb_cb_operation_is_valid(NB_CB_CREATE, dnode->schema)) + operation = NB_CB_CREATE; + else if (nb_cb_operation_is_valid(NB_CB_MODIFY, dnode->schema)) + operation = NB_CB_MODIFY; + else + return; + + nb_config_diff_add_change(changes, operation, seq, dnode); + break; + case LYS_CONTAINER: + case LYS_LIST: + if (nb_cb_operation_is_valid(NB_CB_CREATE, dnode->schema)) + nb_config_diff_add_change(changes, NB_CB_CREATE, seq, + dnode); + + /* Process child nodes recursively. */ + LY_LIST_FOR (lyd_child(dnode), child) { + nb_config_diff_created(child, seq, changes); + } + break; + default: + break; + } +} + +static void nb_config_diff_deleted(const struct lyd_node *dnode, uint32_t *seq, + struct nb_config_cbs *changes) +{ + /* Ignore unimplemented nodes. */ + if (!dnode->schema->priv) + return; + + if (nb_cb_operation_is_valid(NB_CB_DESTROY, dnode->schema)) + nb_config_diff_add_change(changes, NB_CB_DESTROY, seq, dnode); + else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) { + struct lyd_node *child; + + /* + * Non-presence containers need special handling since they + * don't have "destroy" callbacks. In this case, what we need to + * do is to call the "destroy" callbacks of their child nodes + * when applicable (i.e. optional nodes). + */ + LY_LIST_FOR (lyd_child(dnode), child) { + nb_config_diff_deleted(child, seq, changes); + } + } +} + +static int nb_lyd_diff_get_op(const struct lyd_node *dnode) +{ + const struct lyd_meta *meta; + LY_LIST_FOR (dnode->meta, meta) { + if (strcmp(meta->name, "operation") + || strcmp(meta->annotation->module->name, "yang")) + continue; + return lyd_get_meta_value(meta)[0]; + } + return 'n'; +} + +#if 0 /* Used below in nb_config_diff inside normally disabled code */ +static inline void nb_config_diff_dnode_log_path(const char *context, + const char *path, + const struct lyd_node *dnode) +{ + if (dnode->schema->nodetype & LYD_NODE_TERM) + zlog_debug("nb_config_diff: %s: %s: %s", context, path, + lyd_get_value(dnode)); + else + zlog_debug("nb_config_diff: %s: %s", context, path); +} + +static inline void nb_config_diff_dnode_log(const char *context, + const struct lyd_node *dnode) +{ + if (!dnode) { + zlog_debug("nb_config_diff: %s: NULL", context); + return; + } + + char *path = lyd_path(dnode, LYD_PATH_STD, NULL, 0); + nb_config_diff_dnode_log_path(context, path, dnode); + free(path); +} +#endif + +/* + * Calculate the delta between two different configurations. + * + * NOTE: 'config1' is the reference DB, while 'config2' is + * the DB being compared against 'config1'. Typically 'config1' + * should be the Running DB and 'config2' is the Candidate DB. + */ +void nb_config_diff(const struct nb_config *config1, + const struct nb_config *config2, + struct nb_config_cbs *changes) +{ + struct lyd_node *diff = NULL; + const struct lyd_node *root, *dnode; + struct lyd_node *target; + int op; + LY_ERR err; + char *path; + +#if 0 /* Useful (noisy) when debugging diff code, and for improving later */ + if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) { + LY_LIST_FOR(config1->dnode, root) { + LYD_TREE_DFS_BEGIN(root, dnode) { + nb_config_diff_dnode_log("from", dnode); + LYD_TREE_DFS_END(root, dnode); + } + } + LY_LIST_FOR(config2->dnode, root) { + LYD_TREE_DFS_BEGIN(root, dnode) { + nb_config_diff_dnode_log("to", dnode); + LYD_TREE_DFS_END(root, dnode); + } + } + } +#endif + + err = lyd_diff_siblings(config1->dnode, config2->dnode, + LYD_DIFF_DEFAULTS, &diff); + assert(!err); + + if (diff && DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) { + char *s; + + if (!lyd_print_mem(&s, diff, LYD_JSON, + LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)) { + zlog_debug("%s: %s", __func__, s); + free(s); + } + } + + uint32_t seq = 0; + + LY_LIST_FOR (diff, root) { + LYD_TREE_DFS_BEGIN (root, dnode) { + op = nb_lyd_diff_get_op(dnode); + + path = lyd_path(dnode, LYD_PATH_STD, NULL, 0); + +#if 0 /* Useful (noisy) when debugging diff code, and for improving later */ + if (DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) { + char context[80]; + snprintf(context, sizeof(context), + "iterating diff: oper: %c seq: %u", op, seq); + nb_config_diff_dnode_log_path(context, path, dnode); + } +#endif + switch (op) { + case 'c': /* create */ + /* + * This is rather inefficient, but when we use + * dnode from the diff instead of the + * candidate config node we get failures when + * looking up default values, etc, based on + * the diff tree. + */ + target = yang_dnode_get(config2->dnode, path); + assert(target); + nb_config_diff_created(target, &seq, changes); + + /* Skip rest of sub-tree, move to next sibling + */ + LYD_TREE_DFS_continue = 1; + break; + case 'd': /* delete */ + target = yang_dnode_get(config1->dnode, path); + assert(target); + nb_config_diff_deleted(target, &seq, changes); + + /* Skip rest of sub-tree, move to next sibling + */ + LYD_TREE_DFS_continue = 1; + break; + case 'r': /* replace */ + /* either moving an entry or changing a value */ + target = yang_dnode_get(config2->dnode, path); + assert(target); + nb_config_diff_add_change(changes, NB_CB_MODIFY, + &seq, target); + break; + case 'n': /* none */ + default: + break; + } + free(path); + LYD_TREE_DFS_END(root, dnode); + } + } + + lyd_free_all(diff); +} + +static int dnode_create(struct nb_config *candidate, const char *xpath, + const char *value, uint32_t options, + struct lyd_node **new_dnode) +{ + struct lyd_node *dnode; + LY_ERR err; + + err = lyd_new_path(candidate->dnode, ly_native_ctx, xpath, value, + options, &dnode); + if (err) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %d", + __func__, xpath, err); + return NB_ERR; + } else if (dnode) { + err = lyd_new_implicit_tree(dnode, LYD_IMPLICIT_NO_STATE, NULL); + if (err) { + flog_warn(EC_LIB_LIBYANG, + "%s: lyd_new_implicit_all failed: %d", + __func__, err); + } + } + if (new_dnode) + *new_dnode = dnode; + return NB_OK; +} + +int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node, + enum nb_operation operation, const char *xpath, + const struct yang_data *previous, + const struct yang_data *data) +{ + struct lyd_node *dnode, *dep_dnode, *old_dnode; + char dep_xpath[XPATH_MAXLEN]; + struct lyd_node *parent = NULL; + uint32_t options = 0; + LY_ERR err; + + switch (operation) { + case NB_OP_CREATE: + case NB_OP_MODIFY: + options = LYD_NEW_PATH_UPDATE; + fallthrough; + case NB_OP_CREATE_EXCL: + err = dnode_create(candidate, xpath, data->value, options, + &dnode); + if (err) { + return err; + } else if (dnode) { + /* + * create dependency + * + * dnode returned by the lyd_new_path may be from a + * different schema, so we need to update the nb_node + */ + nb_node = dnode->schema->priv; + if (nb_node->dep_cbs.get_dependency_xpath) { + nb_node->dep_cbs.get_dependency_xpath( + dnode, dep_xpath); + + err = dnode_create(candidate, dep_xpath, NULL, + LYD_NEW_PATH_UPDATE, NULL); + if (err) { + lyd_free_tree(dnode); + return err; + } + } + } + break; + case NB_OP_DESTROY: + case NB_OP_DELETE: + dnode = yang_dnode_get(candidate->dnode, xpath); + if (!dnode) { + if (operation == NB_OP_DELETE) + return NB_ERR; + else + return NB_OK; + } + /* destroy dependant */ + if (nb_node->dep_cbs.get_dependant_xpath) { + nb_node->dep_cbs.get_dependant_xpath(dnode, dep_xpath); + + dep_dnode = yang_dnode_get(candidate->dnode, dep_xpath); + if (dep_dnode) + lyd_free_tree(dep_dnode); + } + lyd_free_tree(dnode); + break; + case NB_OP_REPLACE: + old_dnode = yang_dnode_get(candidate->dnode, xpath); + if (old_dnode) { + parent = lyd_parent(old_dnode); + lyd_unlink_tree(old_dnode); + } + err = dnode_create(candidate, xpath, data->value, options, + &dnode); + if (!err && dnode && !old_dnode) { + /* create dependency if the node didn't exist */ + nb_node = dnode->schema->priv; + if (nb_node->dep_cbs.get_dependency_xpath) { + nb_node->dep_cbs.get_dependency_xpath( + dnode, dep_xpath); + + err = dnode_create(candidate, dep_xpath, NULL, + LYD_NEW_PATH_UPDATE, NULL); + if (err) + lyd_free_tree(dnode); + } + } + if (old_dnode) { + /* restore original node on error, free it otherwise */ + if (err) { + if (parent) + lyd_insert_child(parent, old_dnode); + else + lyd_insert_sibling(candidate->dnode, + old_dnode, NULL); + return err; + } + + lyd_free_tree(old_dnode); + } + break; + case NB_OP_MOVE: + /* TODO: update configuration. */ + break; + } + + return NB_OK; +} + +static int nb_candidate_edit_tree_add(struct nb_config *candidate, + enum nb_operation operation, + LYD_FORMAT format, const char *xpath, + const char *data, char *xpath_created, + char *errmsg, size_t errmsg_len) +{ + struct lyd_node *tree = NULL; + struct lyd_node *parent = NULL; + struct lyd_node *dnode = NULL; + struct lyd_node *existing = NULL; + struct lyd_node *ex_parent = NULL; + char *parent_xpath = NULL; + struct ly_in *in; + LY_ERR err; + bool root; + int ret; + + ly_in_new_memory(data, &in); + + root = xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0); + + /* get parent xpath if xpath is not root */ + if (!root) { + /* NB_OP_CREATE_EXCT already expects parent xpath */ + parent_xpath = XSTRDUP(MTYPE_TMP, xpath); + + /* for other operations - pop one level */ + if (operation != NB_OP_CREATE_EXCL) { + ret = yang_xpath_pop_node(parent_xpath); + if (ret) { + snprintf(errmsg, errmsg_len, "Invalid xpath"); + goto done; + } + + /* root is not actually a parent */ + if (parent_xpath[0] == 0) + XFREE(MTYPE_TMP, parent_xpath); + } + } + + /* + * Create parent if it's not root. We're creating a new tree here to be + * merged later with candidate. + */ + if (parent_xpath) { + err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0, + 0, 0, &tree, &parent); + if (err) { + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } + assert(parent); + } + + /* parse data */ + err = yang_lyd_parse_data(ly_native_ctx, parent, in, format, + LYD_PARSE_ONLY | LYD_PARSE_STRICT | + LYD_PARSE_NO_STATE, + 0, &dnode); + if (err) { + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } + + /* set the tree if we created a top-level node */ + if (!parent) + tree = dnode; + + /* save xpath of the created node */ + lyd_path(dnode, LYD_PATH_STD, xpath_created, XPATH_MAXLEN); + + /* verify that list keys are the same in the xpath and the data tree */ + if (!root && (operation == NB_OP_REPLACE || operation == NB_OP_MODIFY)) { + if (lyd_find_path(tree, xpath, 0, NULL)) { + snprintf(errmsg, errmsg_len, + "List keys in xpath and data tree are different"); + ret = NB_ERR; + goto done; + } + } + + /* check if the node already exists in candidate */ + if (operation == NB_OP_CREATE_EXCL || operation == NB_OP_REPLACE) { + existing = yang_dnode_get(candidate->dnode, xpath_created); + + /* if the existing node is implicit default, ignore */ + if (existing && (existing->flags & LYD_DEFAULT)) + existing = NULL; + + if (existing) { + if (operation == NB_OP_CREATE_EXCL) { + snprintf(errmsg, errmsg_len, + "Data already exists"); + ret = NB_ERR; + goto done; + } + + if (root) { + candidate->dnode = NULL; + } else { + /* if it's the first top-level node, update candidate */ + if (candidate->dnode == existing) + candidate->dnode = + candidate->dnode->next; + + ex_parent = lyd_parent(existing); + lyd_unlink_tree(existing); + } + } + } + + err = lyd_merge_siblings(&candidate->dnode, tree, + LYD_MERGE_DESTRUCT | LYD_MERGE_WITH_FLAGS); + if (err) { + /* if replace failed, restore the original node */ + if (existing) { + if (root) { + /* Restoring the whole config. */ + candidate->dnode = existing; + } else if (ex_parent) { + /* + * Restoring a nested node. Insert it as a + * child. + */ + lyd_insert_child(ex_parent, existing); + } else { + /* + * Restoring a top-level node. Insert it as a + * sibling to candidate->dnode to make sure + * the linkage is correct. + */ + lyd_insert_sibling(candidate->dnode, existing, + &candidate->dnode); + } + } + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + ret = NB_ERR; + goto done; + } else { + /* + * Free existing node after replace. + * We're using `lyd_free_siblings` here to free the whole + * tree if we replaced the root node. It won't affect other + * siblings if it wasn't root, because the existing node + * was unlinked from the tree. + */ + if (existing) + lyd_free_siblings(existing); + + tree = NULL; /* LYD_MERGE_DESTRUCT deleted the tree */ + } + + ret = NB_OK; +done: + if (tree) + lyd_free_all(tree); + XFREE(MTYPE_TMP, parent_xpath); + ly_in_free(in, 0); + + return ret; +} + +static int nb_candidate_edit_tree_del(struct nb_config *candidate, + enum nb_operation operation, + const char *xpath, char *errmsg, + size_t errmsg_len) +{ + struct lyd_node *dnode; + + /* deleting root - remove the whole config */ + if (xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0)) { + lyd_free_all(candidate->dnode); + candidate->dnode = NULL; + return NB_OK; + } + + dnode = yang_dnode_get(candidate->dnode, xpath); + if (!dnode || (dnode->flags & LYD_DEFAULT)) { + if (operation == NB_OP_DELETE) { + snprintf(errmsg, errmsg_len, "Data missing"); + return NB_ERR; + } else + return NB_OK; + } + + /* if it's the first top-level node, update candidate */ + if (candidate->dnode == dnode) + candidate->dnode = candidate->dnode->next; + + lyd_free_tree(dnode); + + return NB_OK; +} + +int nb_candidate_edit_tree(struct nb_config *candidate, + enum nb_operation operation, LYD_FORMAT format, + const char *xpath, const char *data, + char *xpath_created, char *errmsg, size_t errmsg_len) +{ + int ret = NB_ERR; + + switch (operation) { + case NB_OP_CREATE_EXCL: + case NB_OP_CREATE: + case NB_OP_MODIFY: + case NB_OP_REPLACE: + ret = nb_candidate_edit_tree_add(candidate, operation, format, + xpath, data, xpath_created, + errmsg, errmsg_len); + break; + case NB_OP_DESTROY: + case NB_OP_DELETE: + ret = nb_candidate_edit_tree_del(candidate, operation, xpath, + errmsg, errmsg_len); + break; + case NB_OP_MOVE: + /* not supported yet */ + break; + } + + return ret; +} + +const char *nb_operation_name(enum nb_operation operation) +{ + switch (operation) { + case NB_OP_CREATE_EXCL: + return "create exclusive"; + case NB_OP_CREATE: + return "create"; + case NB_OP_MODIFY: + return "modify"; + case NB_OP_DESTROY: + return "destroy"; + case NB_OP_DELETE: + return "delete"; + case NB_OP_REPLACE: + return "replace"; + case NB_OP_MOVE: + return "move"; + } + + assert(!"Reached end of function we should never hit"); +} + +bool nb_is_operation_allowed(struct nb_node *nb_node, enum nb_operation oper) +{ + if (lysc_is_key(nb_node->snode)) { + if (oper == NB_OP_MODIFY || oper == NB_OP_DESTROY + || oper == NB_OP_DELETE || oper == NB_OP_REPLACE) + return false; + } + return true; +} + +void nb_candidate_edit_config_changes(struct nb_config *candidate_config, + struct nb_cfg_change cfg_changes[], + size_t num_cfg_changes, + const char *xpath_base, bool in_backend, + char *err_buf, int err_bufsize, + bool *error) +{ + if (error) + *error = false; + + if (xpath_base == NULL) + xpath_base = ""; + + /* Edit candidate configuration. */ + for (size_t i = 0; i < num_cfg_changes; i++) { + struct nb_cfg_change *change = &cfg_changes[i]; + struct nb_node *nb_node; + char *change_xpath = change->xpath; + char xpath[XPATH_MAXLEN]; + const char *value; + struct yang_data *data; + int ret; + + memset(xpath, 0, sizeof(xpath)); + /* If change xpath is relative, prepend base xpath. */ + if (change_xpath[0] == '.') { + strlcpy(xpath, xpath_base, sizeof(xpath)); + change_xpath++; /* skip '.' */ + } + strlcat(xpath, change_xpath, sizeof(xpath)); + + /* Find the northbound node associated to the data path. */ + nb_node = nb_node_find(xpath); + if (!nb_node) { + if (in_backend) + DEBUGD(&nb_dbg_cbs_config, + "%s: ignoring non-handled path: %s", + __func__, xpath); + else { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, + xpath); + if (error) + *error = true; + } + continue; + } + /* Find if the node to be edited is not a key node */ + if (!nb_is_operation_allowed(nb_node, change->operation)) { + zlog_err(" Xpath %s points to key node", xpath); + if (error) + *error = true; + break; + } + + /* If the value is not set, get the default if it exists. */ + value = change->value; + if (value == NULL) + value = yang_snode_get_default(nb_node->snode); + data = yang_data_new(xpath, value); + + /* + * Ignore "not found" errors when editing the candidate + * configuration. + */ + ret = nb_candidate_edit(candidate_config, nb_node, + change->operation, xpath, NULL, data); + yang_data_free(data); + if (ret != NB_OK) { + flog_warn( + EC_LIB_NB_CANDIDATE_EDIT_ERROR, + "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", + __func__, nb_operation_name(change->operation), + xpath); + if (error) + *error = true; + continue; + } + } + + if (error && *error) { + char buf[BUFSIZ]; + + snprintf(err_buf, err_bufsize, + "%% Failed to edit configuration.\n\n%s", + yang_print_errors(ly_native_ctx, buf, sizeof(buf))); + } +} + +bool nb_candidate_needs_update(const struct nb_config *candidate) +{ + if (candidate->version < running_config->version) + return true; + + return false; +} + +int nb_candidate_update(struct nb_config *candidate) +{ + struct nb_config *updated_config; + + updated_config = nb_config_dup(running_config); + if (nb_config_merge(updated_config, candidate, true) != NB_OK) + return NB_ERR; + + nb_config_replace(candidate, updated_config, false); + + return NB_OK; +} + +/* + * Perform YANG syntactic and semantic validation. + * + * WARNING: lyd_validate() can change the configuration as part of the + * validation process. + */ +int nb_candidate_validate_yang(struct nb_config *candidate, bool no_state, + char *errmsg, size_t errmsg_len) +{ + uint32_t options = 0; + +#ifdef LYD_VALIDATE_MULTI_ERROR + /* libyang 2.1.36+ */ + options |= LYD_VALIDATE_MULTI_ERROR; +#endif + + if (no_state) + SET_FLAG(options, LYD_VALIDATE_NO_STATE); + else + SET_FLAG(options, LYD_VALIDATE_PRESENT); + + if (lyd_validate_all(&candidate->dnode, ly_native_ctx, options, + NULL) != 0) { + yang_print_errors(ly_native_ctx, errmsg, errmsg_len); + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +/* Perform code-level validation using the northbound callbacks. */ +int nb_candidate_validate_code(struct nb_context *context, + struct nb_config *candidate, + struct nb_config_cbs *changes, char *errmsg, + size_t errmsg_len) +{ + struct nb_config_cb *cb; + struct lyd_node *root, *child; + int ret; + + /* First validate the candidate as a whole. */ + LY_LIST_FOR (candidate->dnode, root) { + LYD_TREE_DFS_BEGIN (root, child) { + struct nb_node *nb_node; + + nb_node = child->schema->priv; + if (!nb_node || !nb_node->cbs.pre_validate) + goto next; + + ret = nb_callback_pre_validate(context, nb_node, child, + errmsg, errmsg_len); + if (ret != NB_OK) + return NB_ERR_VALIDATION; + + next: + LYD_TREE_DFS_END(root, child); + } + } + + /* Now validate the configuration changes. */ + RB_FOREACH (cb, nb_config_cbs, changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + + ret = nb_callback_configuration(context, NB_EV_VALIDATE, change, + errmsg, errmsg_len); + if (ret != NB_OK) + return NB_ERR_VALIDATION; + } + + return NB_OK; +} + +int nb_candidate_diff_and_validate_yang(struct nb_context *context, + struct nb_config *candidate, + struct nb_config_cbs *changes, + char *errmsg, size_t errmsg_len) +{ + if (nb_candidate_validate_yang(candidate, true, errmsg, + sizeof(errmsg_len)) != NB_OK) + return NB_ERR_VALIDATION; + + RB_INIT(nb_config_cbs, changes); + nb_config_diff(running_config, candidate, changes); + + return NB_OK; +} + +int nb_candidate_validate(struct nb_context *context, + struct nb_config *candidate, char *errmsg, + size_t errmsg_len) +{ + struct nb_config_cbs changes; + int ret; + + ret = nb_candidate_diff_and_validate_yang(context, candidate, &changes, + errmsg, errmsg_len); + if (ret != NB_OK) + return ret; + + ret = nb_candidate_validate_code(context, candidate, &changes, errmsg, + errmsg_len); + nb_config_diff_del_changes(&changes); + + return ret; +} + +int nb_candidate_commit_prepare(struct nb_context context, + struct nb_config *candidate, + const char *comment, + struct nb_transaction **transaction, + bool skip_validate, bool ignore_zero_change, + char *errmsg, size_t errmsg_len) +{ + struct nb_config_cbs changes; + + if (!skip_validate && + nb_candidate_validate_yang(candidate, true, errmsg, errmsg_len) != + NB_OK) { + flog_warn(EC_LIB_NB_CANDIDATE_INVALID, + "%s: failed to validate candidate configuration", + __func__); + return NB_ERR_VALIDATION; + } + + RB_INIT(nb_config_cbs, &changes); + nb_config_diff(running_config, candidate, &changes); + if (!ignore_zero_change && RB_EMPTY(nb_config_cbs, &changes)) { + snprintf( + errmsg, errmsg_len, + "No changes to apply were found during preparation phase"); + return NB_ERR_NO_CHANGES; + } + + if (!skip_validate && + nb_candidate_validate_code(&context, candidate, &changes, errmsg, + errmsg_len) != NB_OK) { + flog_warn(EC_LIB_NB_CANDIDATE_INVALID, + "%s: failed to validate candidate configuration", + __func__); + nb_config_diff_del_changes(&changes); + return NB_ERR_VALIDATION; + } + + /* + * Re-use an existing transaction if provided. Else allocate a new one. + */ + if (!*transaction) + *transaction = nb_transaction_new(context, candidate, &changes, + comment, errmsg, errmsg_len); + if (*transaction == NULL) { + flog_warn(EC_LIB_NB_TRANSACTION_CREATION_FAILED, + "%s: failed to create transaction: %s", __func__, + errmsg); + nb_config_diff_del_changes(&changes); + return NB_ERR_LOCKED; + } + + return nb_transaction_process(NB_EV_PREPARE, *transaction, errmsg, + errmsg_len); +} + +void nb_candidate_commit_abort(struct nb_transaction *transaction, char *errmsg, + size_t errmsg_len) +{ + (void)nb_transaction_process(NB_EV_ABORT, transaction, errmsg, + errmsg_len); + nb_transaction_free(transaction); +} + +void nb_candidate_commit_apply(struct nb_transaction *transaction, + bool save_transaction, uint32_t *transaction_id, + char *errmsg, size_t errmsg_len) +{ + (void)nb_transaction_process(NB_EV_APPLY, transaction, errmsg, + errmsg_len); + nb_transaction_apply_finish(transaction, errmsg, errmsg_len); + + /* Replace running by candidate. */ + transaction->config->version++; + nb_config_replace(running_config, transaction->config, true); + + /* Record transaction. */ + if (save_transaction && nb_db_enabled + && nb_db_transaction_save(transaction, transaction_id) != NB_OK) + flog_warn(EC_LIB_NB_TRANSACTION_RECORD_FAILED, + "%s: failed to record transaction", __func__); + + nb_transaction_free(transaction); +} + +int nb_candidate_commit(struct nb_context context, struct nb_config *candidate, + bool save_transaction, const char *comment, + uint32_t *transaction_id, char *errmsg, + size_t errmsg_len) +{ + struct nb_transaction *transaction = NULL; + int ret; + + ret = nb_candidate_commit_prepare(context, candidate, comment, + &transaction, false, false, errmsg, + errmsg_len); + /* + * Apply the changes if the preparation phase succeeded. Otherwise abort + * the transaction. + */ + if (ret == NB_OK) + nb_candidate_commit_apply(transaction, save_transaction, + transaction_id, errmsg, errmsg_len); + else if (transaction != NULL) + nb_candidate_commit_abort(transaction, errmsg, errmsg_len); + + return ret; +} + +int nb_running_lock(enum nb_client client, const void *user) +{ + int ret = -1; + + frr_with_mutex (&running_config_mgmt_lock.mtx) { + if (!running_config_mgmt_lock.locked) { + running_config_mgmt_lock.locked = true; + running_config_mgmt_lock.owner_client = client; + running_config_mgmt_lock.owner_user = user; + ret = 0; + } + } + + return ret; +} + +int nb_running_unlock(enum nb_client client, const void *user) +{ + int ret = -1; + + frr_with_mutex (&running_config_mgmt_lock.mtx) { + if (running_config_mgmt_lock.locked + && running_config_mgmt_lock.owner_client == client + && running_config_mgmt_lock.owner_user == user) { + running_config_mgmt_lock.locked = false; + running_config_mgmt_lock.owner_client = NB_CLIENT_NONE; + running_config_mgmt_lock.owner_user = NULL; + ret = 0; + } + } + + return ret; +} + +int nb_running_lock_check(enum nb_client client, const void *user) +{ + int ret = -1; + + frr_with_mutex (&running_config_mgmt_lock.mtx) { + if (!running_config_mgmt_lock.locked + || (running_config_mgmt_lock.owner_client == client + && running_config_mgmt_lock.owner_user == user)) + ret = 0; + } + + return ret; +} + +static void nb_log_config_callback(const enum nb_event event, + enum nb_cb_operation operation, + const struct lyd_node *dnode) +{ + const char *value; + char xpath[XPATH_MAXLEN]; + + if (!DEBUG_MODE_CHECK(&nb_dbg_cbs_config, DEBUG_MODE_ALL)) + return; + + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + if (yang_snode_is_typeless_data(dnode->schema)) + value = "(none)"; + else + value = yang_dnode_get_string(dnode, NULL); + + zlog_debug( + "northbound callback: event [%s] op [%s] xpath [%s] value [%s]", + nb_event_name(event), nb_cb_operation_name(operation), xpath, + value); +} + +static int nb_callback_create(struct nb_context *context, + const struct nb_node *nb_node, + enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_create_args args = {}; + bool unexpected_error = false; + int ret; + + assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)); + + nb_log_config_callback(event, NB_CB_CREATE, dnode); + + args.context = context; + args.event = event; + args.dnode = dnode; + args.resource = resource; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + ret = nb_node->cbs.create(&args); + + /* Detect and log unexpected errors. */ + switch (ret) { + case NB_OK: + case NB_ERR: + break; + case NB_ERR_VALIDATION: + if (event != NB_EV_VALIDATE) + unexpected_error = true; + break; + case NB_ERR_RESOURCE: + if (event != NB_EV_PREPARE) + unexpected_error = true; + break; + case NB_ERR_INCONSISTENCY: + if (event == NB_EV_VALIDATE) + unexpected_error = true; + break; + default: + unexpected_error = true; + break; + } + if (unexpected_error) + DEBUGD(&nb_dbg_cbs_config, + "northbound callback: unexpected return value: %s", + nb_err_name(ret)); + + return ret; +} + +static int nb_callback_modify(struct nb_context *context, + const struct nb_node *nb_node, + enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_modify_args args = {}; + bool unexpected_error = false; + int ret; + + assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)); + + nb_log_config_callback(event, NB_CB_MODIFY, dnode); + + args.context = context; + args.event = event; + args.dnode = dnode; + args.resource = resource; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + ret = nb_node->cbs.modify(&args); + + /* Detect and log unexpected errors. */ + switch (ret) { + case NB_OK: + case NB_ERR: + break; + case NB_ERR_VALIDATION: + if (event != NB_EV_VALIDATE) + unexpected_error = true; + break; + case NB_ERR_RESOURCE: + if (event != NB_EV_PREPARE) + unexpected_error = true; + break; + case NB_ERR_INCONSISTENCY: + if (event == NB_EV_VALIDATE) + unexpected_error = true; + break; + default: + unexpected_error = true; + break; + } + if (unexpected_error) + DEBUGD(&nb_dbg_cbs_config, + "northbound callback: unexpected return value: %s", + nb_err_name(ret)); + + return ret; +} + +static int nb_callback_destroy(struct nb_context *context, + const struct nb_node *nb_node, + enum nb_event event, + const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_destroy_args args = {}; + bool unexpected_error = false; + int ret; + + assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)); + + nb_log_config_callback(event, NB_CB_DESTROY, dnode); + + args.context = context; + args.event = event; + args.dnode = dnode; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + ret = nb_node->cbs.destroy(&args); + + /* Detect and log unexpected errors. */ + switch (ret) { + case NB_OK: + case NB_ERR: + break; + case NB_ERR_VALIDATION: + if (event != NB_EV_VALIDATE) + unexpected_error = true; + break; + case NB_ERR_INCONSISTENCY: + if (event == NB_EV_VALIDATE) + unexpected_error = true; + break; + default: + unexpected_error = true; + break; + } + if (unexpected_error) + DEBUGD(&nb_dbg_cbs_config, + "northbound callback: unexpected return value: %s", + nb_err_name(ret)); + + return ret; +} + +static int nb_callback_move(struct nb_context *context, + const struct nb_node *nb_node, enum nb_event event, + const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_move_args args = {}; + bool unexpected_error = false; + int ret; + + assert(!CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)); + + nb_log_config_callback(event, NB_CB_MOVE, dnode); + + args.context = context; + args.event = event; + args.dnode = dnode; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + ret = nb_node->cbs.move(&args); + + /* Detect and log unexpected errors. */ + switch (ret) { + case NB_OK: + case NB_ERR: + break; + case NB_ERR_VALIDATION: + if (event != NB_EV_VALIDATE) + unexpected_error = true; + break; + case NB_ERR_INCONSISTENCY: + if (event == NB_EV_VALIDATE) + unexpected_error = true; + break; + default: + unexpected_error = true; + break; + } + if (unexpected_error) + DEBUGD(&nb_dbg_cbs_config, + "northbound callback: unexpected return value: %s", + nb_err_name(ret)); + + return ret; +} + +static int nb_callback_pre_validate(struct nb_context *context, + const struct nb_node *nb_node, + const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_pre_validate_args args = {}; + bool unexpected_error = false; + int ret; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return 0; + + nb_log_config_callback(NB_EV_VALIDATE, NB_CB_PRE_VALIDATE, dnode); + + args.dnode = dnode; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + ret = nb_node->cbs.pre_validate(&args); + + /* Detect and log unexpected errors. */ + switch (ret) { + case NB_OK: + case NB_ERR_VALIDATION: + break; + default: + unexpected_error = true; + break; + } + if (unexpected_error) + DEBUGD(&nb_dbg_cbs_config, + "northbound callback: unexpected return value: %s", + nb_err_name(ret)); + + return ret; +} + +static void nb_callback_apply_finish(struct nb_context *context, + const struct nb_node *nb_node, + const struct lyd_node *dnode, char *errmsg, + size_t errmsg_len) +{ + struct nb_cb_apply_finish_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return; + + nb_log_config_callback(NB_EV_APPLY, NB_CB_APPLY_FINISH, dnode); + + args.context = context; + args.dnode = dnode; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + nb_node->cbs.apply_finish(&args); +} + +struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node, + const char *xpath, + const void *list_entry) +{ + struct nb_cb_get_elem_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NULL; + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (get_elem): xpath [%s] list_entry [%p]", + xpath, list_entry); + + args.xpath = xpath; + args.list_entry = list_entry; + return nb_node->cbs.get_elem(&args); +} + +const void *nb_callback_get_next(const struct nb_node *nb_node, + const void *parent_list_entry, + const void *list_entry) +{ + struct nb_cb_get_next_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NULL; + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (get_next): node [%s] parent_list_entry [%p] list_entry [%p]", + nb_node->xpath, parent_list_entry, list_entry); + + args.parent_list_entry = parent_list_entry; + args.list_entry = list_entry; + return nb_node->cbs.get_next(&args); +} + +int nb_callback_get_keys(const struct nb_node *nb_node, const void *list_entry, + struct yang_list_keys *keys) +{ + struct nb_cb_get_keys_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return 0; + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (get_keys): node [%s] list_entry [%p]", + nb_node->xpath, list_entry); + + args.list_entry = list_entry; + args.keys = keys; + return nb_node->cbs.get_keys(&args); +} + +const void *nb_callback_lookup_entry(const struct nb_node *nb_node, + const void *parent_list_entry, + const struct yang_list_keys *keys) +{ + struct nb_cb_lookup_entry_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NULL; + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]", + nb_node->xpath, parent_list_entry); + + args.parent_list_entry = parent_list_entry; + args.keys = keys; + return nb_node->cbs.lookup_entry(&args); +} + +const void *nb_callback_lookup_node_entry(struct lyd_node *node, + const void *parent_list_entry) +{ + struct yang_list_keys keys; + struct nb_cb_lookup_entry_args args = {}; + const struct nb_node *nb_node = node->schema->priv; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NULL; + + if (yang_get_node_keys(node, &keys)) { + flog_warn(EC_LIB_LIBYANG, + "%s: can't get keys for lookup from existing data node %s", + __func__, node->schema->name); + return NULL; + } + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (lookup_node_entry): node [%s] parent_list_entry [%p]", + nb_node->xpath, parent_list_entry); + + args.parent_list_entry = parent_list_entry; + args.keys = &keys; + return nb_node->cbs.lookup_entry(&args); +} + +const void *nb_callback_lookup_next(const struct nb_node *nb_node, + const void *parent_list_entry, + const struct yang_list_keys *keys) +{ + struct nb_cb_lookup_entry_args args = {}; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NULL; + + DEBUGD(&nb_dbg_cbs_state, + "northbound callback (lookup_entry): node [%s] parent_list_entry [%p]", + nb_node->xpath, parent_list_entry); + + args.parent_list_entry = parent_list_entry; + args.keys = keys; + return nb_node->cbs.lookup_next(&args); +} + +int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, + const struct lyd_node *input, struct lyd_node *output, + char *errmsg, size_t errmsg_len) +{ + struct nb_cb_rpc_args args = {}; + + DEBUGD(&nb_dbg_cbs_rpc, "northbound RPC: %s", xpath); + + args.xpath = xpath; + args.input = input; + args.output = output; + args.errmsg = errmsg; + args.errmsg_len = errmsg_len; + return nb_node->cbs.rpc(&args); +} + +void nb_callback_notify(const struct nb_node *nb_node, const char *xpath, + struct lyd_node *dnode) +{ + struct nb_cb_notify_args args = {}; + + DEBUGD(&nb_dbg_cbs_notify, "northbound notify: %s", xpath); + + args.xpath = xpath; + args.dnode = dnode; + nb_node->cbs.notify(&args); +} + +/* + * Call the northbound configuration callback associated to a given + * configuration change. + */ +static int nb_callback_configuration(struct nb_context *context, + const enum nb_event event, + struct nb_config_change *change, + char *errmsg, size_t errmsg_len) +{ + enum nb_cb_operation operation = change->cb.operation; + char xpath[XPATH_MAXLEN]; + const struct nb_node *nb_node = change->cb.nb_node; + const struct lyd_node *dnode = change->cb.dnode; + union nb_resource *resource; + int ret = NB_ERR; + + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) + return NB_OK; + + if (event == NB_EV_VALIDATE) + resource = NULL; + else + resource = &change->resource; + + switch (operation) { + case NB_CB_CREATE: + ret = nb_callback_create(context, nb_node, event, dnode, + resource, errmsg, errmsg_len); + break; + case NB_CB_MODIFY: + ret = nb_callback_modify(context, nb_node, event, dnode, + resource, errmsg, errmsg_len); + break; + case NB_CB_DESTROY: + ret = nb_callback_destroy(context, nb_node, event, dnode, + errmsg, errmsg_len); + break; + case NB_CB_MOVE: + ret = nb_callback_move(context, nb_node, event, dnode, errmsg, + errmsg_len); + break; + case NB_CB_PRE_VALIDATE: + case NB_CB_APPLY_FINISH: + case NB_CB_GET_ELEM: + case NB_CB_GET_NEXT: + case NB_CB_GET_KEYS: + case NB_CB_LOOKUP_ENTRY: + case NB_CB_RPC: + case NB_CB_NOTIFY: + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown operation (%u) [xpath %s]", __func__, + operation, xpath); + exit(1); + } + + if (ret != NB_OK) { + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + + switch (event) { + case NB_EV_VALIDATE: + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s", + nb_err_name(ret), nb_event_name(event), + nb_cb_operation_name(operation), xpath, + errmsg[0] ? " message: " : "", errmsg); + break; + case NB_EV_PREPARE: + flog_warn(EC_LIB_NB_CB_CONFIG_PREPARE, + "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s", + nb_err_name(ret), nb_event_name(event), + nb_cb_operation_name(operation), xpath, + errmsg[0] ? " message: " : "", errmsg); + break; + case NB_EV_ABORT: + flog_warn(EC_LIB_NB_CB_CONFIG_ABORT, + "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s", + nb_err_name(ret), nb_event_name(event), + nb_cb_operation_name(operation), xpath, + errmsg[0] ? " message: " : "", errmsg); + break; + case NB_EV_APPLY: + flog_err(EC_LIB_NB_CB_CONFIG_APPLY, + "error processing configuration change: error [%s] event [%s] operation [%s] xpath [%s]%s%s", + nb_err_name(ret), nb_event_name(event), + nb_cb_operation_name(operation), xpath, + errmsg[0] ? " message: " : "", errmsg); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown event (%u) [xpath %s]", __func__, + event, xpath); + exit(1); + } + } + + return ret; +} + +static struct nb_transaction * +nb_transaction_new(struct nb_context context, struct nb_config *config, + struct nb_config_cbs *changes, const char *comment, + char *errmsg, size_t errmsg_len) +{ + struct nb_transaction *transaction; + + if (nb_running_lock_check(context.client, context.user)) { + strlcpy(errmsg, + "running configuration is locked by another client", + errmsg_len); + return NULL; + } + + if (transaction_in_progress) { + strlcpy(errmsg, + "there's already another transaction in progress", + errmsg_len); + return NULL; + } + transaction_in_progress = true; + + transaction = XCALLOC(MTYPE_TMP, sizeof(*transaction)); + transaction->context = context; + if (comment) + strlcpy(transaction->comment, comment, + sizeof(transaction->comment)); + transaction->config = config; + transaction->changes = *changes; + + return transaction; +} + +static void nb_transaction_free(struct nb_transaction *transaction) +{ + nb_config_diff_del_changes(&transaction->changes); + XFREE(MTYPE_TMP, transaction); + transaction_in_progress = false; +} + +/* Process all configuration changes associated to a transaction. */ +static int nb_transaction_process(enum nb_event event, + struct nb_transaction *transaction, + char *errmsg, size_t errmsg_len) +{ + struct nb_config_cb *cb; + + RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + int ret; + + /* + * Only try to release resources that were allocated + * successfully. + */ + if (event == NB_EV_ABORT && !change->prepare_ok) + break; + + /* Call the appropriate callback. */ + ret = nb_callback_configuration(&transaction->context, event, + change, errmsg, errmsg_len); + switch (event) { + case NB_EV_PREPARE: + if (ret != NB_OK) + return ret; + change->prepare_ok = true; + break; + case NB_EV_ABORT: + case NB_EV_APPLY: + /* + * At this point it's not possible to reject the + * transaction anymore, so any failure here can lead to + * inconsistencies and should be treated as a bug. + * Operations prone to errors, like validations and + * resource allocations, should be performed during the + * 'prepare' phase. + */ + break; + case NB_EV_VALIDATE: + break; + } + } + + return NB_OK; +} + +static struct nb_config_cb * +nb_apply_finish_cb_new(struct nb_config_cbs *cbs, const struct nb_node *nb_node, + const struct lyd_node *dnode) +{ + struct nb_config_cb *cb; + + cb = XCALLOC(MTYPE_TMP, sizeof(*cb)); + cb->operation = NB_CB_APPLY_FINISH; + cb->nb_node = nb_node; + cb->dnode = dnode; + RB_INSERT(nb_config_cbs, cbs, cb); + + return cb; +} + +static struct nb_config_cb * +nb_apply_finish_cb_find(struct nb_config_cbs *cbs, + const struct nb_node *nb_node, + const struct lyd_node *dnode) +{ + struct nb_config_cb s; + + s.operation = NB_CB_APPLY_FINISH; + s.seq = 0; + s.nb_node = nb_node; + s.dnode = dnode; + return RB_FIND(nb_config_cbs, cbs, &s); +} + +/* Call the 'apply_finish' callbacks. */ +static void nb_transaction_apply_finish(struct nb_transaction *transaction, + char *errmsg, size_t errmsg_len) +{ + struct nb_config_cbs cbs; + struct nb_config_cb *cb; + + /* Initialize tree of 'apply_finish' callbacks. */ + RB_INIT(nb_config_cbs, &cbs); + + /* Identify the 'apply_finish' callbacks that need to be called. */ + RB_FOREACH (cb, nb_config_cbs, &transaction->changes) { + struct nb_config_change *change = (struct nb_config_change *)cb; + const struct lyd_node *dnode = change->cb.dnode; + + /* + * Iterate up to the root of the data tree. When a node is being + * deleted, skip its 'apply_finish' callback if one is defined + * (the 'apply_finish' callbacks from the node ancestors should + * be called though). + */ + if (change->cb.operation == NB_CB_DESTROY) { + char xpath[XPATH_MAXLEN]; + + dnode = lyd_parent(dnode); + if (!dnode) + continue; + + /* + * The dnode from 'delete' callbacks point to elements + * from the running configuration. Use yang_dnode_get() + * to get the corresponding dnode from the candidate + * configuration that is being committed. + */ + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + dnode = yang_dnode_get(transaction->config->dnode, + xpath); + } + while (dnode) { + struct nb_node *nb_node; + + nb_node = dnode->schema->priv; + if (!nb_node || !nb_node->cbs.apply_finish) + goto next; + + /* + * Don't call the callback more than once for the same + * data node. + */ + if (nb_apply_finish_cb_find(&cbs, nb_node, dnode)) + goto next; + + nb_apply_finish_cb_new(&cbs, nb_node, dnode); + + next: + dnode = lyd_parent(dnode); + } + } + + /* Call the 'apply_finish' callbacks, sorted by their priorities. */ + RB_FOREACH (cb, nb_config_cbs, &cbs) + nb_callback_apply_finish(&transaction->context, cb->nb_node, + cb->dnode, errmsg, errmsg_len); + + /* Release memory. */ + while (!RB_EMPTY(nb_config_cbs, &cbs)) { + cb = RB_ROOT(nb_config_cbs, &cbs); + RB_REMOVE(nb_config_cbs, &cbs, cb); + XFREE(MTYPE_TMP, cb); + } +} + +bool nb_cb_operation_is_valid(enum nb_cb_operation operation, + const struct lysc_node *snode) +{ + struct nb_node *nb_node = snode->priv; + struct lysc_node_container *scontainer; + struct lysc_node_leaf *sleaf; + + switch (operation) { + case NB_CB_CREATE: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + if (sleaf->type->basetype != LY_TYPE_EMPTY) + return false; + break; + case LYS_CONTAINER: + if (snode->parent && snode->parent->nodetype == LYS_CASE) + return true; + scontainer = (struct lysc_node_container *)snode; + if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE)) + return false; + break; + case LYS_LIST: + case LYS_LEAFLIST: + break; + default: + return false; + } + return true; + case NB_CB_MODIFY: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + if (sleaf->type->basetype == LY_TYPE_EMPTY) + return false; + + /* List keys can't be modified. */ + if (lysc_is_key(sleaf)) + return false; + break; + default: + return false; + } + return true; + case NB_CB_DESTROY: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + + /* List keys can't be deleted. */ + if (lysc_is_key(sleaf)) + return false; + + /* + * Only optional leafs can be deleted, or leafs whose + * parent is a case statement. + */ + if (snode->parent->nodetype == LYS_CASE) + return true; + if (sleaf->when) + return true; + if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE) + || sleaf->dflt) + return false; + break; + case LYS_CONTAINER: + if (snode->parent && snode->parent->nodetype == LYS_CASE) + return true; + scontainer = (struct lysc_node_container *)snode; + if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE)) + return false; + break; + case LYS_LIST: + case LYS_LEAFLIST: + break; + default: + return false; + } + return true; + case NB_CB_MOVE: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + + switch (snode->nodetype) { + case LYS_LIST: + case LYS_LEAFLIST: + if (!CHECK_FLAG(snode->flags, LYS_ORDBY_USER)) + return false; + break; + default: + return false; + } + return true; + case NB_CB_PRE_VALIDATE: + case NB_CB_APPLY_FINISH: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + return true; + case NB_CB_GET_ELEM: + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) + return false; + + switch (snode->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + break; + case LYS_CONTAINER: + scontainer = (struct lysc_node_container *)snode; + if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE)) + return false; + break; + default: + return false; + } + return true; + case NB_CB_GET_NEXT: + switch (snode->nodetype) { + case LYS_LIST: + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) + return false; + break; + case LYS_LEAFLIST: + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return false; + break; + default: + return false; + } + return true; + case NB_CB_GET_KEYS: + case NB_CB_LOOKUP_ENTRY: + switch (snode->nodetype) { + case LYS_LIST: + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY)) + return false; + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) + return false; + break; + default: + return false; + } + return true; + case NB_CB_RPC: + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W | LYS_CONFIG_R)) + return false; + + switch (snode->nodetype) { + case LYS_RPC: + case LYS_ACTION: + break; + default: + return false; + } + return true; + case NB_CB_NOTIFY: + if (snode->nodetype != LYS_NOTIF) + return false; + return true; + default: + return false; + } +} + +DEFINE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), + (xpath, arguments)); + +int nb_notification_send(const char *xpath, struct list *arguments) +{ + struct lyd_node *root = NULL; + struct lyd_node *dnode; + struct yang_data *data; + struct listnode *ln; + LY_ERR err; + int ret; + + DEBUGD(&nb_dbg_notif, "northbound notification: %s", xpath); + + /* + * Call old hook functions + */ + ret = hook_call(nb_notification_send, xpath, arguments); + + if (!hook_have_hooks(nb_notification_tree_send)) + goto done; + /* + * Convert yang data arguments list to a libyang data tree for new hook + * functions. + */ + for (ALL_LIST_ELEMENTS_RO(arguments, ln, data)) { + err = lyd_new_path(root, ly_native_ctx, data->xpath, + data->value, LYD_NEW_PATH_UPDATE, &dnode); + if (err != LY_SUCCESS) + goto lyerr; + if (!root) { + root = dnode; + while (root->parent) + root = lyd_parent(root); + } + } + + if (!root) { + err = lyd_new_path(NULL, ly_native_ctx, xpath, "", 0, &root); + if (err) { +lyerr: + flog_err(EC_LIB_LIBYANG, + "%s: error creating notification data: %s", + __func__, ly_strerrcode(err)); + ret += 1; + goto done; + } + } + + /* + * Call new hook functions + */ + ret += nb_notification_tree_send(xpath, root); + +done: + if (root) + lyd_free_all(root); + if (arguments) + list_delete(&arguments); + + return ret; +} + +DEFINE_HOOK(nb_notification_tree_send, + (const char *xpath, const struct lyd_node *tree), (xpath, tree)); + +int nb_notification_tree_send(const char *xpath, const struct lyd_node *tree) +{ + int ret; + + assert(tree); + + DEBUGD(&nb_dbg_notif, "northbound tree notification: %s", + tree->schema->name); + + ret = hook_call(nb_notification_tree_send, xpath, tree); + + return ret; +} + +/* Running configuration user pointers management. */ +struct nb_config_entry { + char xpath[XPATH_MAXLEN]; + void *entry; +}; + +static bool running_config_entry_cmp(const void *value1, const void *value2) +{ + const struct nb_config_entry *c1 = value1; + const struct nb_config_entry *c2 = value2; + + return strmatch(c1->xpath, c2->xpath); +} + +static unsigned int running_config_entry_key_make(const void *value) +{ + return string_hash_make(value); +} + +static void *running_config_entry_alloc(void *p) +{ + struct nb_config_entry *new, *key = p; + + new = XCALLOC(MTYPE_NB_CONFIG_ENTRY, sizeof(*new)); + strlcpy(new->xpath, key->xpath, sizeof(new->xpath)); + + return new; +} + +static void running_config_entry_free(void *arg) +{ + XFREE(MTYPE_NB_CONFIG_ENTRY, arg); +} + +void nb_running_set_entry(const struct lyd_node *dnode, void *entry) +{ + struct nb_config_entry *config, s; + + yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); + config = hash_get(running_config_entries, &s, + running_config_entry_alloc); + config->entry = entry; +} + +void nb_running_move_tree(const char *xpath_from, const char *xpath_to) +{ + struct nb_config_entry *entry; + struct list *entries = hash_to_list(running_config_entries); + struct listnode *ln; + + for (ALL_LIST_ELEMENTS_RO(entries, ln, entry)) { + if (!frrstr_startswith(entry->xpath, xpath_from)) + continue; + + hash_release(running_config_entries, entry); + + char *newpath = + frrstr_replace(entry->xpath, xpath_from, xpath_to); + strlcpy(entry->xpath, newpath, sizeof(entry->xpath)); + XFREE(MTYPE_TMP, newpath); + + (void)hash_get(running_config_entries, entry, + hash_alloc_intern); + } + + list_delete(&entries); +} + +static void *nb_running_unset_entry_helper(const struct lyd_node *dnode) +{ + struct nb_config_entry *config, s; + struct lyd_node *child; + void *entry = NULL; + + yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); + config = hash_release(running_config_entries, &s); + if (config) { + entry = config->entry; + running_config_entry_free(config); + } + + /* Unset user pointers from the child nodes. */ + if (CHECK_FLAG(dnode->schema->nodetype, LYS_LIST | LYS_CONTAINER)) { + LY_LIST_FOR (lyd_child(dnode), child) { + (void)nb_running_unset_entry_helper(child); + } + } + + return entry; +} + +void *nb_running_unset_entry(const struct lyd_node *dnode) +{ + void *entry; + + entry = nb_running_unset_entry_helper(dnode); + assert(entry); + + return entry; +} + +static void *nb_running_get_entry_worker(const struct lyd_node *dnode, + const char *xpath, + bool abort_if_not_found, + bool rec_search) +{ + const struct lyd_node *orig_dnode = dnode; + char xpath_buf[XPATH_MAXLEN]; + bool rec_flag = true; + + assert(dnode || xpath); + + if (!dnode) + dnode = yang_dnode_get(running_config->dnode, xpath); + + while (rec_flag && dnode) { + struct nb_config_entry *config, s; + + yang_dnode_get_path(dnode, s.xpath, sizeof(s.xpath)); + config = hash_lookup(running_config_entries, &s); + if (config) + return config->entry; + + rec_flag = rec_search; + + dnode = lyd_parent(dnode); + } + + if (!abort_if_not_found) + return NULL; + + yang_dnode_get_path(orig_dnode, xpath_buf, sizeof(xpath_buf)); + flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, + "%s: failed to find entry [xpath %s]", __func__, xpath_buf); + zlog_backtrace(LOG_ERR); + abort(); +} + +void *nb_running_get_entry(const struct lyd_node *dnode, const char *xpath, + bool abort_if_not_found) +{ + return nb_running_get_entry_worker(dnode, xpath, abort_if_not_found, + true); +} + +void *nb_running_get_entry_non_rec(const struct lyd_node *dnode, + const char *xpath, bool abort_if_not_found) +{ + return nb_running_get_entry_worker(dnode, xpath, abort_if_not_found, + false); +} + +/* Logging functions. */ +const char *nb_event_name(enum nb_event event) +{ + switch (event) { + case NB_EV_VALIDATE: + return "validate"; + case NB_EV_PREPARE: + return "prepare"; + case NB_EV_ABORT: + return "abort"; + case NB_EV_APPLY: + return "apply"; + } + + assert(!"Reached end of function we should never hit"); +} + +const char *nb_cb_operation_name(enum nb_cb_operation operation) +{ + switch (operation) { + case NB_CB_CREATE: + return "create"; + case NB_CB_MODIFY: + return "modify"; + case NB_CB_DESTROY: + return "destroy"; + case NB_CB_MOVE: + return "move"; + case NB_CB_PRE_VALIDATE: + return "pre_validate"; + case NB_CB_APPLY_FINISH: + return "apply_finish"; + case NB_CB_GET_ELEM: + return "get_elem"; + case NB_CB_GET_NEXT: + return "get_next"; + case NB_CB_GET_KEYS: + return "get_keys"; + case NB_CB_LOOKUP_ENTRY: + return "lookup_entry"; + case NB_CB_RPC: + return "rpc"; + case NB_CB_NOTIFY: + return "notify"; + } + + assert(!"Reached end of function we should never hit"); +} + +const char *nb_err_name(enum nb_error error) +{ + switch (error) { + case NB_OK: + return "ok"; + case NB_ERR: + return "generic error"; + case NB_ERR_NO_CHANGES: + return "no changes"; + case NB_ERR_NOT_FOUND: + return "element not found"; + case NB_ERR_LOCKED: + return "resource is locked"; + case NB_ERR_VALIDATION: + return "validation"; + case NB_ERR_RESOURCE: + return "failed to allocate resource"; + case NB_ERR_INCONSISTENCY: + return "internal inconsistency"; + case NB_YIELD: + return "should yield"; + } + + assert(!"Reached end of function we should never hit"); +} + +const char *nb_client_name(enum nb_client client) +{ + switch (client) { + case NB_CLIENT_CLI: + return "CLI"; + case NB_CLIENT_SYSREPO: + return "Sysrepo"; + case NB_CLIENT_GRPC: + return "gRPC"; + case NB_CLIENT_PCEP: + return "Pcep"; + case NB_CLIENT_MGMTD_SERVER: + return "MGMTD Server"; + case NB_CLIENT_MGMTD_BE: + return "MGMT Backend"; + case NB_CLIENT_NONE: + return "None"; + } + + assert(!"Reached end of function we should never hit"); +} + +static void nb_load_callbacks(const struct frr_yang_module_info *module) +{ + for (size_t i = 0; module->nodes[i].xpath; i++) { + struct nb_node *nb_node; + uint32_t priority; + + if (i > YANG_MODULE_MAX_NODES) { + zlog_err( + "%s: %s.yang has more than %u nodes. Please increase YANG_MODULE_MAX_NODES to fix this problem.", + __func__, module->name, YANG_MODULE_MAX_NODES); + exit(1); + } + + nb_node = nb_node_find(module->nodes[i].xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, + module->nodes[i].xpath); + continue; + } + + nb_node->cbs = module->nodes[i].cbs; + priority = module->nodes[i].priority; + if (priority != 0) + nb_node->priority = priority; + } +} + +void nb_validate_callbacks(void) +{ + unsigned int errors = 0; + + yang_snodes_iterate(NULL, nb_node_validate, 0, &errors); + if (errors > 0) { + flog_err( + EC_LIB_NB_CBS_VALIDATION, + "%s: failed to validate northbound callbacks: %u error(s)", + __func__, errors); + exit(1); + } +} + + +void nb_init(struct event_loop *tm, + const struct frr_yang_module_info *const modules[], + size_t nmodules, bool db_enabled) +{ + struct yang_module *loaded[nmodules], **loadedp = loaded; + + /* + * Currently using this explicit compile feature in libyang2 leads to + * incorrect behavior in FRR. The functionality suppresses the compiling + * of modules until they have all been loaded into the context. This + * avoids multiple recompiles of the same modules as they are + * imported/augmented etc. + * (Done as a #define to make coverity happy) + */ +#define explicit_compile false + + nb_db_enabled = db_enabled; + + yang_init(true, explicit_compile); + + /* Load YANG modules and their corresponding northbound callbacks. */ + for (size_t i = 0; i < nmodules; i++) { + DEBUGD(&nb_dbg_events, "northbound: loading %s.yang", + modules[i]->name); + *loadedp++ = yang_module_load(modules[i]->name, + modules[i]->features); + } + + if (explicit_compile) + yang_init_loading_complete(); + + /* Initialize the compiled nodes with northbound data */ + for (size_t i = 0; i < nmodules; i++) { + yang_snodes_iterate(loaded[i]->info, nb_node_new_cb, 0, + (void *)modules[i]); + nb_load_callbacks(modules[i]); + } + + /* Validate northbound callbacks. */ + nb_validate_callbacks(); + + /* Create an empty running configuration. */ + running_config = nb_config_new(NULL); + running_config_entries = hash_create(running_config_entry_key_make, + running_config_entry_cmp, + "Running Configuration Entries"); + pthread_mutex_init(&running_config_mgmt_lock.mtx, NULL); + + /* Initialize the northbound CLI. */ + nb_cli_init(tm); + + /* Initialize oper-state */ + nb_oper_init(tm); +} + +void nb_terminate(void) +{ + nb_oper_terminate(); + + /* Terminate the northbound CLI. */ + nb_cli_terminate(); + + /* Delete all nb_node's from all YANG modules. */ + nb_nodes_delete(); + + /* Delete the running configuration. */ + hash_clean_and_free(&running_config_entries, running_config_entry_free); + nb_config_free(running_config); + pthread_mutex_destroy(&running_config_mgmt_lock.mtx); +} diff --git a/lib/northbound.h b/lib/northbound.h new file mode 100644 index 0000000..34d17a5 --- /dev/null +++ b/lib/northbound.h @@ -0,0 +1,1722 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_NORTHBOUND_H_ +#define _FRR_NORTHBOUND_H_ + +#include "frrevent.h" +#include "hook.h" +#include "linklist.h" +#include "openbsd-tree.h" +#include "yang.h" +#include "yang_translator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration(s). */ +struct vty; +struct debug; + +struct nb_yang_xpath_tag { + uint32_t ns; + uint32_t id; +}; + +struct nb_yang_value { + struct lyd_value value; + LY_DATA_TYPE value_type; + uint8_t value_flags; +}; + +struct nb_yang_xpath_elem { + struct nb_yang_xpath_tag tag; + struct nb_yang_value val; +}; + +#define NB_MAX_NUM_KEYS UINT8_MAX +#define NB_MAX_NUM_XPATH_TAGS UINT8_MAX + +struct nb_yang_xpath { + uint8_t length; + struct { + uint8_t num_keys; + struct nb_yang_xpath_elem keys[NB_MAX_NUM_KEYS]; + } tags[NB_MAX_NUM_XPATH_TAGS]; +}; + +#define NB_YANG_XPATH_KEY(__xpath, __indx1, __indx2) \ + ((__xpath->num_tags > __indx1) && \ + (__xpath->tags[__indx1].num_keys > __indx2) \ + ? &__xpath->tags[__indx1].keys[__indx2] \ + : NULL) + +/* Northbound events. */ +enum nb_event { + /* + * The configuration callback is supposed to verify that the changes are + * valid and can be applied. + */ + NB_EV_VALIDATE, + + /* + * The configuration callback is supposed to prepare all resources + * required to apply the changes. + */ + NB_EV_PREPARE, + + /* + * Transaction has failed, the configuration callback needs to release + * all resources previously allocated. + */ + NB_EV_ABORT, + + /* + * The configuration changes need to be applied. The changes can't be + * rejected at this point (errors are logged and ignored). + */ + NB_EV_APPLY, +}; + +/* + * Northbound callback operations. + * + * Refer to the documentation comments of nb_callbacks for more details. + */ +enum nb_cb_operation { + NB_CB_CREATE, + NB_CB_MODIFY, + NB_CB_DESTROY, + NB_CB_MOVE, + NB_CB_PRE_VALIDATE, + NB_CB_APPLY_FINISH, + NB_CB_GET_ELEM, + NB_CB_GET_NEXT, + NB_CB_GET_KEYS, + NB_CB_LOOKUP_ENTRY, + NB_CB_RPC, + NB_CB_NOTIFY, +}; + +union nb_resource { + int fd; + void *ptr; +}; + +/* + * Northbound callbacks parameters. + */ + +struct nb_cb_create_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + */ + enum nb_event event; + + /* libyang data node that is being created. */ + const struct lyd_node *dnode; + + /* + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + */ + union nb_resource *resource; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_modify_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + */ + enum nb_event event; + + /* libyang data node that is being modified. */ + const struct lyd_node *dnode; + + /* + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + */ + union nb_resource *resource; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_destroy_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + */ + enum nb_event event; + + /* libyang data node that is being deleted. */ + const struct lyd_node *dnode; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_move_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + */ + enum nb_event event; + + /* libyang data node that is being moved. */ + const struct lyd_node *dnode; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_pre_validate_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* libyang data node associated with the 'pre_validate' callback. */ + const struct lyd_node *dnode; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_apply_finish_args { + /* Context of the configuration transaction. */ + struct nb_context *context; + + /* libyang data node associated with the 'apply_finish' callback. */ + const struct lyd_node *dnode; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_get_elem_args { + /* YANG data path of the data we want to get. */ + const char *xpath; + + /* Pointer to list entry (might be NULL). */ + const void *list_entry; +}; + +struct nb_cb_get_next_args { + /* Pointer to parent list entry. */ + const void *parent_list_entry; + + /* Pointer to (leaf-)list entry. */ + const void *list_entry; +}; + +struct nb_cb_get_keys_args { + /* Pointer to list entry. */ + const void *list_entry; + + /* + * Structure to be filled based on the attributes of the provided list + * entry. + */ + struct yang_list_keys *keys; +}; + +struct nb_cb_lookup_entry_args { + /* Pointer to parent list entry. */ + const void *parent_list_entry; + + /* Structure containing the keys of the list entry. */ + const struct yang_list_keys *keys; +}; + +struct nb_cb_rpc_args { + /* XPath of the YANG RPC or action. */ + const char *xpath; + + /* Read-only "input" tree of the RPC/action. */ + const struct lyd_node *input; + + /* The "output" tree of the RPC/action to be populated by the callback. */ + struct lyd_node *output; + + /* Buffer to store human-readable error message in case of error. */ + char *errmsg; + + /* Size of errmsg. */ + size_t errmsg_len; +}; + +struct nb_cb_notify_args { + /* XPath of the notification. */ + const char *xpath; + + /* + * libyang data node representing the notification. If the notification + * is not top-level, it still points to the notification node, but it's + * part of the full data tree with all its parents. + */ + struct lyd_node *dnode; +}; + +/* + * Set of configuration callbacks that can be associated to a northbound node. + */ +struct nb_callbacks { + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or leaf of type + * empty has been created. + * + * For presence-containers and list entries, the callback is supposed to + * initialize the default values of its children (if any) from the YANG + * models. + * + * args + * Refer to the documentation comments of nb_cb_create_args for + * details. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*create)(struct nb_cb_create_args *args); + + /* + * Configuration callback. + * + * The value of a leaf has been modified. + * + * List keys don't need to implement this callback. When a list key is + * modified, the northbound treats this as if the list was deleted and a + * new one created with the updated key value. + * + * args + * Refer to the documentation comments of nb_cb_modify_args for + * details. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*modify)(struct nb_cb_modify_args *args); + + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or optional leaf + * has been deleted. + * + * The callback is supposed to delete the entire configuration object, + * including its children when they exist. + * + * args + * Refer to the documentation comments of nb_cb_destroy_args for + * details. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*destroy)(struct nb_cb_destroy_args *args); + + /* + * Configuration callback. + * + * A list entry or leaf-list entry has been moved. Only applicable when + * the "ordered-by user" statement is present. + * + * args + * Refer to the documentation comments of nb_cb_move_args for + * details. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*move)(struct nb_cb_move_args *args); + + /* + * Optional configuration callback. + * + * This callback can be used to validate subsections of the + * configuration being committed before validating the configuration + * changes themselves. It's useful to perform more complex validations + * that depend on the relationship between multiple nodes. + * + * args + * Refer to the documentation comments of nb_cb_pre_validate_args for + * details. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + */ + int (*pre_validate)(struct nb_cb_pre_validate_args *args); + + /* + * Optional configuration callback. + * + * The 'apply_finish' callbacks are called after all other callbacks + * during the apply phase (NB_EV_APPLY). These callbacks are called only + * under one of the following two cases: + * - The data node has been created or modified (but not deleted); + * - Any change was made within the descendants of the data node (e.g. a + * child leaf was modified, created or deleted). + * + * In the second case above, the 'apply_finish' callback is called only + * once even if multiple changes occurred within the descendants of the + * data node. + * + * args + * Refer to the documentation comments of nb_cb_apply_finish_args for + * details. + */ + void (*apply_finish)(struct nb_cb_apply_finish_args *args); + + /* + * Operational data callback. + * + * The callback function should return the value of a specific leaf, + * leaf-list entry or inform if a typeless value (presence containers or + * leafs of type empty) exists or not. + * + * args + * Refer to the documentation comments of nb_cb_get_elem_args for + * details. + * + * Returns: + * Pointer to newly created yang_data structure, or NULL to indicate + * the absence of data. + */ + struct yang_data *(*get_elem)(struct nb_cb_get_elem_args *args); + + /* + * Operational data callback for YANG lists and leaf-lists. + * + * The callback function should return the next entry in the list or + * leaf-list. The 'list_entry' parameter will be NULL on the first + * invocation. + * + * args + * Refer to the documentation comments of nb_cb_get_next_args for + * details. + * + * Returns: + * Pointer to the next entry in the (leaf-)list, or NULL to signal + * that the end of the (leaf-)list was reached. + */ + const void *(*get_next)(struct nb_cb_get_next_args *args); + + /* + * Operational data callback for YANG lists. + * + * The callback function should fill the 'keys' parameter based on the + * given list_entry. Keyless lists don't need to implement this + * callback. + * + * args + * Refer to the documentation comments of nb_cb_get_keys_args for + * details. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ + int (*get_keys)(struct nb_cb_get_keys_args *args); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return a list entry based on the list + * keys given as a parameter. Keyless lists don't need to implement this + * callback. + * + * args + * Refer to the documentation comments of nb_cb_lookup_entry_args for + * details. + * + * Returns: + * Pointer to the list entry if found, or NULL if not found. + */ + const void *(*lookup_entry)(struct nb_cb_lookup_entry_args *args); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return the next list entry that would + * follow a list entry with the keys given as a parameter. Keyless + * lists don't need to implement this callback. + * + * args + * Refer to the documentation comments of nb_cb_lookup_entry_args for + * details. + * + * Returns: + * Pointer to the list entry if found, or NULL if not found. + */ + const void *(*lookup_next)(struct nb_cb_lookup_entry_args *args); + + /* + * RPC and action callback. + * + * Both 'input' and 'output' are lists of 'yang_data' structures. The + * callback should fetch all the input parameters from the 'input' list, + * and add output parameters to the 'output' list if necessary. + * + * args + * Refer to the documentation comments of nb_cb_rpc_args for details. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ + int (*rpc)(struct nb_cb_rpc_args *args); + + /* + * Notification callback. + * + * The callback is called when a YANG notification is received. + * + * args + * Refer to the documentation comments of nb_cb_notify_args for + * details. + */ + void (*notify)(struct nb_cb_notify_args *args); + + /* + * Optional callback to compare the data nodes when printing + * the CLI commands associated with them. + * + * dnode1 + * The first data node to compare. + * + * dnode2 + * The second data node to compare. + * + * Returns: + * <0 when the CLI command for the dnode1 should be printed first + * >0 when the CLI command for the dnode2 should be printed first + * 0 when there is no difference + */ + int (*cli_cmp)(const struct lyd_node *dnode1, + const struct lyd_node *dnode2); + + /* + * Optional callback to show the CLI command associated to the given + * YANG data node. + * + * vty + * The vty terminal to dump the configuration to. + * + * dnode + * libyang data node that should be shown in the form of a CLI + * command. + * + * show_defaults + * Specify whether to display default configuration values or not. + * This parameter can be ignored most of the time since the + * northbound doesn't call this callback for default leaves or + * non-presence containers that contain only default child nodes. + * The exception are commands associated to multiple configuration + * nodes, in which case it might be desirable to hide one or more + * parts of the command when this parameter is set to false. + */ + void (*cli_show)(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + + /* + * Optional callback to show the CLI node end for lists or containers. + * + * vty + * The vty terminal to dump the configuration to. + * + * dnode + * libyang data node that should be shown in the form of a CLI + * command. + */ + void (*cli_show_end)(struct vty *vty, const struct lyd_node *dnode); +}; + +struct nb_dependency_callbacks { + void (*get_dependant_xpath)(const struct lyd_node *dnode, char *xpath); + void (*get_dependency_xpath)(const struct lyd_node *dnode, char *xpath); +}; + +/* + * Northbound-specific data that is allocated for each schema node of the native + * YANG modules. + */ +struct nb_node { + /* Back pointer to the libyang schema node. */ + const struct lysc_node *snode; + + /* Data path of this YANG node. */ + char xpath[XPATH_MAXLEN]; + + /* Priority - lower priorities are processed first. */ + uint32_t priority; + + struct nb_dependency_callbacks dep_cbs; + + /* Callbacks implemented for this node. */ + struct nb_callbacks cbs; + + /* + * Pointer to the parent node (disconsidering non-presence containers). + */ + struct nb_node *parent; + + /* Pointer to the nearest parent list, if any. */ + struct nb_node *parent_list; + + /* Flags. */ + uint8_t flags; +}; +/* The YANG container or list contains only config data. */ +#define F_NB_NODE_CONFIG_ONLY 0x01 +/* The YANG list doesn't contain key leafs. */ +#define F_NB_NODE_KEYLESS_LIST 0x02 +/* Ignore config callbacks for this node */ +#define F_NB_NODE_IGNORE_CFG_CBS 0x04 + +/* + * HACK: old gcc versions (< 5.x) have a bug that prevents C99 flexible arrays + * from working properly on shared libraries. For those compilers, use a fixed + * size array to work around the problem. + */ +#define YANG_MODULE_MAX_NODES 2000 + +struct frr_yang_module_info { + /* YANG module name. */ + const char *name; + + /* + * Ignore configuration callbacks for this module. Set this to true to + * load module with only CLI-related callbacks. This is useful for + * modules loaded in mgmtd. + */ + bool ignore_cfg_cbs; + + /* + * The NULL-terminated list of supported features. + * Features are defined with "feature" statements in the YANG model. + * Use ["*", NULL] to enable all features. + * Use NULL to disable all features. + */ + const char **features; + + /* Northbound callbacks. */ + const struct { + /* Data path of this YANG node. */ + const char *xpath; + + /* Callbacks implemented for this node. */ + struct nb_callbacks cbs; + + /* Priority - lower priorities are processed first. */ + uint32_t priority; +#if defined(__GNUC__) && ((__GNUC__ - 0) < 5) && !defined(__clang__) + } nodes[YANG_MODULE_MAX_NODES + 1]; +#else + } nodes[]; +#endif +}; + +/* Northbound error codes. */ +enum nb_error { + NB_OK = 0, + NB_ERR, + NB_ERR_NO_CHANGES, + NB_ERR_NOT_FOUND, + NB_ERR_LOCKED, + NB_ERR_VALIDATION, + NB_ERR_RESOURCE, + NB_ERR_INCONSISTENCY, + NB_YIELD, +}; + +/* Default priority. */ +#define NB_DFLT_PRIORITY (UINT32_MAX / 2) + +/* Default maximum of configuration rollbacks to store. */ +#define NB_DLFT_MAX_CONFIG_ROLLBACKS 20 + +/* Northbound clients. */ +enum nb_client { + NB_CLIENT_NONE = 0, + NB_CLIENT_CLI, + NB_CLIENT_SYSREPO, + NB_CLIENT_GRPC, + NB_CLIENT_PCEP, + NB_CLIENT_MGMTD_SERVER, + NB_CLIENT_MGMTD_BE, +}; + +/* Northbound context. */ +struct nb_context { + /* Northbound client. */ + enum nb_client client; + + /* Northbound user (can be NULL). */ + const void *user; +}; + +/* Northbound configuration callback. */ +struct nb_config_cb { + RB_ENTRY(nb_config_cb) entry; + enum nb_cb_operation operation; + uint32_t seq; + const struct nb_node *nb_node; + const struct lyd_node *dnode; +}; +RB_HEAD(nb_config_cbs, nb_config_cb); +RB_PROTOTYPE(nb_config_cbs, nb_config_cb, entry, nb_config_cb_compare); + +/* Northbound configuration change. */ +struct nb_config_change { + struct nb_config_cb cb; + union nb_resource resource; + bool prepare_ok; +}; + +/* Northbound configuration transaction. */ +struct nb_transaction { + struct nb_context context; + char comment[80]; + struct nb_config *config; + struct nb_config_cbs changes; +}; + +/* Northbound configuration. */ +struct nb_config { + struct lyd_node *dnode; + uint32_t version; +}; + +/* + * Northbound operations. The semantics of operations is explained in RFC 8072, + * section 2.5: https://datatracker.ietf.org/doc/html/rfc8072#section-2.5. + */ +enum nb_operation { + NB_OP_CREATE_EXCL, /* "create" */ + NB_OP_CREATE, /* "merge" - kept for backward compatibility */ + NB_OP_MODIFY, /* "merge" */ + NB_OP_DESTROY, /* "remove" */ + NB_OP_DELETE, /* "delete" */ + NB_OP_REPLACE, /* "replace" */ + NB_OP_MOVE, /* "move" */ +}; + +struct nb_cfg_change { + char xpath[XPATH_MAXLEN]; + enum nb_operation operation; + const char *value; +}; + +/* Callback function used by nb_oper_data_iterate(). */ +typedef int (*nb_oper_data_cb)(const struct lysc_node *snode, + struct yang_translator *translator, + struct yang_data *data, void *arg); + +/** + * nb_oper_data_finish_cb() - finish a portion or all of a oper data walk. + * @tree - r/o copy of the tree created during this portion of the walk. + * @arg - finish arg passed to nb_op_iterate_yielding. + * @ret - NB_OK if done with walk, NB_YIELD if done with portion, otherwise an + * error. + * + * If nb_op_iterate_yielding() was passed with @should_batch set then this + * callback will be invoked during each portion (batch) of the walk. + * + * The @tree is read-only and should not be modified or freed. + * + * If this function returns anything but NB_OK then the walk will be terminated. + * and this function will not be called again regardless of if @ret was + * `NB_YIELD` or not. + * + * Return: NB_OK to continue or complete the walk normally, otherwise an error + * to immediately terminate the walk. + */ +/* Callback function used by nb_oper_data_iter_yielding(). */ +typedef enum nb_error (*nb_oper_data_finish_cb)(const struct lyd_node *tree, + void *arg, enum nb_error ret); + +/* Iterate over direct child nodes only. */ +#define NB_OPER_DATA_ITER_NORECURSE 0x0001 + +/* Hooks. */ +DECLARE_HOOK(nb_notification_send, (const char *xpath, struct list *arguments), + (xpath, arguments)); +DECLARE_HOOK(nb_notification_tree_send, + (const char *xpath, const struct lyd_node *tree), (xpath, tree)); +DECLARE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty)); +DECLARE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set)); + +/* Northbound debugging records */ +extern struct debug nb_dbg_cbs_config; +extern struct debug nb_dbg_cbs_state; +extern struct debug nb_dbg_cbs_rpc; +extern struct debug nb_dbg_cbs_notify; +extern struct debug nb_dbg_notif; +extern struct debug nb_dbg_events; +extern struct debug nb_dbg_libyang; + +/* Global running configuration. */ +extern struct nb_config *running_config; + +/* Wrappers for the northbound callbacks. */ +extern struct yang_data *nb_callback_get_elem(const struct nb_node *nb_node, + const char *xpath, + const void *list_entry); +extern const void *nb_callback_get_next(const struct nb_node *nb_node, + const void *parent_list_entry, + const void *list_entry); +extern int nb_callback_get_keys(const struct nb_node *nb_node, + const void *list_entry, + struct yang_list_keys *keys); +extern const void *nb_callback_lookup_entry(const struct nb_node *nb_node, + const void *parent_list_entry, + const struct yang_list_keys *keys); +extern const void *nb_callback_lookup_node_entry(struct lyd_node *node, + const void *parent_list_entry); +extern const void *nb_callback_lookup_next(const struct nb_node *nb_node, + const void *parent_list_entry, + const struct yang_list_keys *keys); +extern int nb_callback_rpc(const struct nb_node *nb_node, const char *xpath, + const struct lyd_node *input, struct lyd_node *output, + char *errmsg, size_t errmsg_len); +extern void nb_callback_notify(const struct nb_node *nb_node, const char *xpath, + struct lyd_node *dnode); + +/* + * Create a northbound node for all YANG schema nodes. + */ +void nb_nodes_create(void); + +/* + * Delete all northbound nodes from all YANG schema nodes. + */ +void nb_nodes_delete(void); + +/* + * Find the northbound node corresponding to a YANG data path. + * + * xpath + * XPath to search for (with or without predicates). + * + * Returns: + * Pointer to northbound node if found, NULL otherwise. + */ +extern struct nb_node *nb_node_find(const char *xpath); + +/** + * nb_nodes_find() - find the NB nodes corresponding to complex xpath. + * @xpath: XPath to search for (with or without predicates). + * + * Return: a dynamic array (darr) of `struct nb_node *`s. + */ +extern struct nb_node **nb_nodes_find(const char *xpath); + +extern void nb_node_set_dependency_cbs(const char *dependency_xpath, + const char *dependant_xpath, + struct nb_dependency_callbacks *cbs); + +bool nb_node_has_dependency(struct nb_node *node); + +/* + * Create a new northbound configuration. + * + * dnode + * Pointer to a libyang data node containing the configuration data. If NULL + * is given, an empty configuration will be created. + * + * Returns: + * Pointer to newly created northbound configuration. + */ +extern struct nb_config *nb_config_new(struct lyd_node *dnode); + +/* + * Delete a northbound configuration. + * + * config + * Pointer to the config that is going to be deleted. + */ +extern void nb_config_free(struct nb_config *config); + +/* + * Duplicate a northbound configuration. + * + * config + * Northbound configuration to duplicate. + * + * Returns: + * Pointer to duplicated configuration. + */ +extern struct nb_config *nb_config_dup(const struct nb_config *config); + +/* + * Merge one configuration into another. + * + * config_dst + * Configuration to merge to. + * + * config_src + * Configuration to merge config_dst with. + * + * preserve_source + * Specify whether config_src should be deleted or not after the merge + * operation. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_config_merge(struct nb_config *config_dst, + struct nb_config *config_src, bool preserve_source); + +/* + * Replace one configuration by another. + * + * config_dst + * Configuration to be replaced. + * + * config_src + * Configuration to replace config_dst. + * + * preserve_source + * Specify whether config_src should be deleted or not after the replace + * operation. + */ +extern void nb_config_replace(struct nb_config *config_dst, + struct nb_config *config_src, + bool preserve_source); + +/* + * Return a human-readable string representing a northbound operation. + * + * operation + * Northbound operation. + * + * Returns: + * String representation of the given northbound operation. + */ +extern const char *nb_operation_name(enum nb_operation operation); + +/* + * Validate if the northbound operation is allowed for the given node. + * + * nb_node + * Northbound node. + * + * operation + * Operation we want to check. + * + * Returns: + * true if the operation is allowed, false otherwise. + */ +extern bool nb_is_operation_allowed(struct nb_node *nb_node, + enum nb_operation oper); + +/* + * Edit a candidate configuration. + * + * candidate + * Candidate configuration to edit. + * + * nb_node + * Northbound node associated to the configuration being edited. + * + * operation + * Operation to apply. + * + * xpath + * XPath of the configuration node being edited. + * + * previous + * Previous value of the configuration node. Should be used only when the + * operation is NB_OP_MOVE, otherwise this parameter is ignored. + * + * data + * New value of the configuration node. + * + * Returns: + * - NB_OK on success. + * - NB_ERR for other errors. + */ +extern int nb_candidate_edit(struct nb_config *candidate, + const struct nb_node *nb_node, + enum nb_operation operation, const char *xpath, + const struct yang_data *previous, + const struct yang_data *data); + +/* + * Edit a candidate configuration. Value is given as JSON/XML. + * + * candidate + * Candidate configuration to edit. + * + * operation + * Operation to apply. + * + * format + * LYD_FORMAT of the value. + * + * xpath + * XPath of the configuration node being edited. + * For create, it must be the parent. + * + * data + * New data tree for the node. + * + * xpath_created + * XPath of the created node if operation is "create". + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * - NB_OK on success. + * - NB_ERR for other errors. + */ +extern int nb_candidate_edit_tree(struct nb_config *candidate, + enum nb_operation operation, + LYD_FORMAT format, const char *xpath, + const char *data, char *xpath_created, + char *errmsg, size_t errmsg_len); + +/* + * Create diff for configuration. + * + * dnode + * Pointer to a libyang data node containing the configuration data. If NULL + * is given, an empty configuration will be created. + * + * seq + * Returns sequence number assigned to the specific change. + * + * changes + * Northbound config callback head. + */ +extern void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq, + struct nb_config_cbs *changes); + +/* + * Check if a candidate configuration is outdated and needs to be updated. + * + * candidate + * Candidate configuration to check. + * + * Returns: + * true if the candidate is outdated, false otherwise. + */ +extern bool nb_candidate_needs_update(const struct nb_config *candidate); + +/* + * Edit candidate configuration changes. + * + * candidate_config + * Candidate configuration to edit. + * + * cfg_changes + * Northbound config changes. + * + * num_cfg_changes + * Number of config changes. + * + * xpath_base + * Base xpath for config. + * + * in_backend + * Specify whether the changes are being applied in the backend or not. + * + * err_buf + * Buffer to store human-readable error message in case of error. + * + * err_bufsize + * Size of err_buf. + * + * error + * TRUE on error, FALSE on success + */ +extern void nb_candidate_edit_config_changes(struct nb_config *candidate_config, + struct nb_cfg_change cfg_changes[], + size_t num_cfg_changes, + const char *xpath_base, + bool in_backend, char *err_buf, + int err_bufsize, bool *error); + + +extern void nb_config_diff_add_change(struct nb_config_cbs *changes, + enum nb_cb_operation operation, + uint32_t *seq, + const struct lyd_node *dnode); +/* + * Delete candidate configuration changes. + * + * changes + * Northbound config changes. + */ +extern void nb_config_diff_del_changes(struct nb_config_cbs *changes); + +/* + * Create candidate diff and validate on yang tree + * + * context + * Context of the northbound transaction. + * + * candidate + * Candidate DB configuration. + * + * changes + * Northbound config changes. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * NB_OK on success, NB_ERR_VALIDATION otherwise + */ +extern int nb_candidate_diff_and_validate_yang(struct nb_context *context, + struct nb_config *candidate, + struct nb_config_cbs *changes, + char *errmsg, size_t errmsg_len); + +/* + * Calculate the delta between two different configurations. + * + * reference + * Running DB config changes to be compared against. + * + * incremental + * Candidate DB config changes that will be compared against reference. + * + * changes + * Will hold the final diff generated. + * + */ +extern void nb_config_diff(const struct nb_config *reference, + const struct nb_config *incremental, + struct nb_config_cbs *changes); + +/* + * Perform YANG syntactic and semantic validation. + * + * WARNING: lyd_validate() can change the configuration as part of the + * validation process. + * + * candidate + * Candidate DB configuration. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * NB_OK on success, NB_ERR_VALIDATION otherwise + */ +extern int nb_candidate_validate_yang(struct nb_config *candidate, + bool no_state, char *errmsg, + size_t errmsg_len); + +/* + * Perform code-level validation using the northbound callbacks. + * + * context + * Context of the northbound transaction. + * + * candidate + * Candidate DB configuration. + * + * changes + * Northbound config changes. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * NB_OK on success, NB_ERR_VALIDATION otherwise + */ +extern int nb_candidate_validate_code(struct nb_context *context, + struct nb_config *candidate, + struct nb_config_cbs *changes, + char *errmsg, size_t errmsg_len); + +/* + * Update a candidate configuration by rebasing the changes on top of the latest + * running configuration. Resolve conflicts automatically by giving preference + * to the changes done in the candidate configuration. + * + * candidate + * Candidate configuration to update. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_candidate_update(struct nb_config *candidate); + +/* + * Validate a candidate configuration. Perform both YANG syntactic/semantic + * validation and code-level validation using the northbound callbacks. + * + * WARNING: the candidate can be modified as part of the validation process + * (e.g. add default nodes). + * + * context + * Context of the northbound transaction. + * + * candidate + * Candidate configuration to validate. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * NB_OK on success, NB_ERR_VALIDATION otherwise. + */ +extern int nb_candidate_validate(struct nb_context *context, + struct nb_config *candidate, char *errmsg, + size_t errmsg_len); + +/* + * Create a new configuration transaction but do not commit it yet. Only + * validate the candidate and prepare all resources required to apply the + * configuration changes. + * + * context + * Context of the northbound transaction. + * + * candidate + * Candidate configuration to commit. + * + * comment + * Optional comment describing the commit. + * + * transaction + * Output parameter providing the created transaction when one is created + * successfully. In this case, it must be either aborted using + * nb_candidate_commit_abort() or committed using + * nb_candidate_commit_apply(). + * + * skip_validate + * TRUE to skip commit validation, FALSE otherwise. + * + * ignore_zero_change + * TRUE to ignore if zero changes, FALSE otherwise. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_NO_CHANGES when the candidate is identical to the running + * configuration. + * - NB_ERR_LOCKED when there's already another transaction in progress. + * - NB_ERR_VALIDATION when the candidate fails the validation checks. + * - NB_ERR_RESOURCE when the system fails to allocate resources to apply + * the candidate configuration. + * - NB_ERR for other errors. + */ +extern int nb_candidate_commit_prepare(struct nb_context context, + struct nb_config *candidate, + const char *comment, + struct nb_transaction **transaction, + bool skip_validate, + bool ignore_zero_change, char *errmsg, + size_t errmsg_len); + +/* + * Abort a previously created configuration transaction, releasing all resources + * allocated during the preparation phase. + * + * transaction + * Candidate configuration to abort. It's consumed by this function. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + */ +extern void nb_candidate_commit_abort(struct nb_transaction *transaction, + char *errmsg, size_t errmsg_len); + +/* + * Commit a previously created configuration transaction. + * + * transaction + * Configuration transaction to commit. It's consumed by this function. + * + * save_transaction + * Specify whether the transaction should be recorded in the transactions log + * or not. + * + * transaction_id + * Optional output parameter providing the ID of the committed transaction. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + */ +extern void nb_candidate_commit_apply(struct nb_transaction *transaction, + bool save_transaction, + uint32_t *transaction_id, char *errmsg, + size_t errmsg_len); + +/* + * Create a new transaction to commit a candidate configuration. This is a + * convenience function that performs the two-phase commit protocol + * transparently to the user. The cost is reduced flexibility, since + * network-wide and multi-daemon transactions require the network manager to + * take into account the results of the preparation phase of multiple managed + * entities. + * + * context + * Context of the northbound transaction. + * + * candidate + * Candidate configuration to commit. It's preserved regardless if the commit + * operation fails or not. + * + * save_transaction + * Specify whether the transaction should be recorded in the transactions log + * or not. + * + * comment + * Optional comment describing the commit. + * + * transaction_id + * Optional output parameter providing the ID of the committed transaction. + * + * errmsg + * Buffer to store human-readable error message in case of error. + * + * errmsg_len + * Size of errmsg. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_NO_CHANGES when the candidate is identical to the running + * configuration. + * - NB_ERR_LOCKED when there's already another transaction in progress. + * - NB_ERR_VALIDATION when the candidate fails the validation checks. + * - NB_ERR_RESOURCE when the system fails to allocate resources to apply + * the candidate configuration. + * - NB_ERR for other errors. + */ +extern int nb_candidate_commit(struct nb_context context, + struct nb_config *candidate, + bool save_transaction, const char *comment, + uint32_t *transaction_id, char *errmsg, + size_t errmsg_len); + +/* + * Lock the running configuration. + * + * client + * Northbound client. + * + * user + * Northbound user (can be NULL). + * + * Returns: + * 0 on success, -1 when the running configuration is already locked. + */ +extern int nb_running_lock(enum nb_client client, const void *user); + +/* + * Unlock the running configuration. + * + * client + * Northbound client. + * + * user + * Northbound user (can be NULL). + * + * Returns: + * 0 on success, -1 when the running configuration is already unlocked or + * locked by another client/user. + */ +extern int nb_running_unlock(enum nb_client client, const void *user); + +/* + * Check if the running configuration is locked or not for the given + * client/user. + * + * client + * Northbound client. + * + * user + * Northbound user (can be NULL). + * + * Returns: + * 0 if the running configuration is unlocked or if the client/user owns the + * lock, -1 otherwise. + */ +extern int nb_running_lock_check(enum nb_client client, const void *user); + +/* + * Iterate over operational data -- deprecated. + * + * xpath + * Data path of the YANG data we want to iterate over. + * + * translator + * YANG module translator (might be NULL). + * + * flags + * NB_OPER_DATA_ITER_ flags to control how the iteration is performed. + * + * should_batch + * Should call finish cb with partial results (i.e., creating batches) + * + * cb + * Function to call with each data node. + * + * arg + * Arbitrary argument passed as the fourth parameter in each call to 'cb'. + * + * tree + * If non-NULL will contain the data tree built from the walk. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern enum nb_error nb_oper_iterate_legacy(const char *xpath, + struct yang_translator *translator, + uint32_t flags, nb_oper_data_cb cb, + void *arg, struct lyd_node **tree); + +/** + * nb_oper_walk() - walk the schema building operational state. + * @xpath - + * @translator - + * @flags - + * @should_batch - should allow yielding and processing portions of the tree. + * @cb - callback invoked for each non-list, non-container node. + * @arg - arg to pass to @cb. + * @finish - function to call when done with portion or all of walk. + * @finish_arg - arg to pass to @finish. + * + * Return: walk - a cookie that can be used to cancel the walk. + */ +extern void *nb_oper_walk(const char *xpath, struct yang_translator *translator, + uint32_t flags, bool should_batch, nb_oper_data_cb cb, + void *arg, nb_oper_data_finish_cb finish, + void *finish_arg); + +/** + * nb_oper_cancel_walk() - cancel the in progress walk. + * @walk - value returned from nb_op_iterate_yielding() + * + * Should only be called on an in-progress walk. It is invalid to cancel and + * already finished walk. The walks `finish` callback will not be called. + */ +extern void nb_oper_cancel_walk(void *walk); + +/** + * nb_op_cancel_all_walks() - cancel all in progress walks. + */ +extern void nb_oper_cancel_all_walks(void); + +/* + * Validate if the northbound callback operation is valid for the given node. + * + * operation + * Operation we want to check. + * + * snode + * libyang schema node we want to check. + * + * Returns: + * true if the operation is valid, false otherwise. + */ +extern bool nb_cb_operation_is_valid(enum nb_cb_operation operation, + const struct lysc_node *snode); + +/* + * DEPRECATED: This call and infra should no longer be used. Instead, + * the mgmtd supported tree based call `nb_notification_tree_send` should be + * used instead + * + * Send a YANG notification. This is a no-op unless the 'nb_notification_send' + * hook was registered by a northbound plugin. + * + * xpath + * XPath of the YANG notification. + * + * arguments + * Linked list containing the arguments that should be sent. This list is + * deleted after being used. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_notification_send(const char *xpath, struct list *arguments); + +/* + * Send a YANG notification from a backend . This is a no-op unless th + * 'nb_notification_tree_send' hook was registered by a northbound plugin. + * + * xpath + * XPath of the YANG notification. + * + * tree + * The libyang tree for the notification. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_notification_tree_send(const char *xpath, + const struct lyd_node *tree); + +/* + * Associate a user pointer to a configuration node. + * + * This should be called by northbound 'create' callbacks in the NB_EV_APPLY + * phase only. + * + * dnode + * libyang data node - only its XPath is used. + * + * entry + * Arbitrary user-specified pointer. + */ +extern void nb_running_set_entry(const struct lyd_node *dnode, void *entry); + +/* + * Move an entire tree of user pointer nodes. + * + * Suppose we have xpath A/B/C/D, with user pointers associated to C and D. We + * need to move B to be under Z, so the new xpath is Z/B/C/D. Because user + * pointers are indexed with their absolute path, We need to move all user + * pointers at and below B to their new absolute paths; this function does + * that. + * + * xpath_from + * base xpath of tree to move (A/B) + * + * xpath_to + * base xpath of new location of tree (Z/B) + */ +extern void nb_running_move_tree(const char *xpath_from, const char *xpath_to); + +/* + * Unset the user pointer associated to a configuration node. + * + * This should be called by northbound 'destroy' callbacks in the NB_EV_APPLY + * phase only. + * + * dnode + * libyang data node - only its XPath is used. + * + * Returns: + * The user pointer that was unset. + */ +extern void *nb_running_unset_entry(const struct lyd_node *dnode); + +/* + * Find the user pointer (if any) associated to a configuration node. + * + * The XPath associated to the configuration node can be provided directly or + * indirectly through a libyang data node. + * + * If an user point is not found, this function follows the parent nodes in the + * running configuration until an user pointer is found or until the root node + * is reached. + * + * dnode + * libyang data node - only its XPath is used (can be NULL if 'xpath' is + * provided). + * + * xpath + * XPath of the configuration node (can be NULL if 'dnode' is provided). + * + * abort_if_not_found + * When set to true, abort the program if no user pointer is found. + * + * As a rule of thumb, this parameter should be set to true in the following + * scenarios: + * - Calling this function from any northbound configuration callback during + * the NB_EV_APPLY phase. + * - Calling this function from a 'delete' northbound configuration callback + * during any phase. + * + * In both the above cases, the given configuration node should contain an + * user pointer except when there's a bug in the code, in which case it's + * better to abort the program right away and eliminate the need for + * unnecessary NULL checks. + * + * In all other cases, this parameter should be set to false and the caller + * should check if the function returned NULL or not. + * + * Returns: + * User pointer if found, NULL otherwise. + */ +extern void *nb_running_get_entry(const struct lyd_node *dnode, + const char *xpath, bool abort_if_not_found); + +/* + * Same as 'nb_running_get_entry', but doesn't search within parent nodes + * recursively if an user point is not found. + */ +extern void *nb_running_get_entry_non_rec(const struct lyd_node *dnode, + const char *xpath, + bool abort_if_not_found); + +/* + * Return a human-readable string representing a northbound event. + * + * event + * Northbound event. + * + * Returns: + * String representation of the given northbound event. + */ +extern const char *nb_event_name(enum nb_event event); + +/* + * Return a human-readable string representing a northbound callback operation. + * + * operation + * Northbound callback operation. + * + * Returns: + * String representation of the given northbound callback operation. + */ +extern const char *nb_cb_operation_name(enum nb_cb_operation operation); + +/* + * Return a human-readable string representing a northbound error. + * + * error + * Northbound error. + * + * Returns: + * String representation of the given northbound error. + */ +extern const char *nb_err_name(enum nb_error error); + +/* + * Return a human-readable string representing a northbound client. + * + * client + * Northbound client. + * + * Returns: + * String representation of the given northbound client. + */ +extern const char *nb_client_name(enum nb_client client); + +/* + * Validate all northbound callbacks. + * + * Some errors, like missing callbacks or invalid priorities, are fatal and + * can't be recovered from. Other errors, like unneeded callbacks, are logged + * but otherwise ignored. + * + * Whenever a YANG module is loaded after startup, *all* northbound callbacks + * need to be validated and not only the callbacks from the newly loaded module. + * This is because augmentations can change the properties of the augmented + * module, making mandatory the implementation of additional callbacks. + */ +void nb_validate_callbacks(void); + +/* + * Initialize the northbound layer. Should be called only once during the + * daemon initialization process. + * + * modules + * Array of YANG modules to parse and initialize. + * + * nmodules + * Size of the modules array. + * + * db_enabled + * Set this to record the transactions in the transaction log. + */ +extern void nb_init(struct event_loop *tm, + const struct frr_yang_module_info *const modules[], + size_t nmodules, bool db_enabled); + +/* + * Finish the northbound layer gracefully. Should be called only when the daemon + * is exiting. + */ +extern void nb_terminate(void); + +extern void nb_oper_init(struct event_loop *loop); +extern void nb_oper_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_NORTHBOUND_H_ */ diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c new file mode 100644 index 0000000..4f962cd --- /dev/null +++ b/lib/northbound_cli.c @@ -0,0 +1,2045 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include +#include + +#include "libfrr.h" +#include "lib/version.h" +#include "defaults.h" +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "termtable.h" +#include "db.h" +#include "debug.h" +#include "yang_translator.h" +#include "northbound.h" +#include "northbound_cli.h" +#include "northbound_db.h" +#include "lib/northbound_cli_clippy.c" + +struct debug nb_dbg_cbs_config = {0, "Northbound callbacks: configuration"}; +struct debug nb_dbg_cbs_state = {0, "Northbound callbacks: state"}; +struct debug nb_dbg_cbs_rpc = {0, "Northbound callbacks: RPCs"}; +struct debug nb_dbg_cbs_notify = {0, "Northbound callbacks: notifications"}; +struct debug nb_dbg_notif = {0, "Northbound notifications"}; +struct debug nb_dbg_events = {0, "Northbound events"}; +struct debug nb_dbg_libyang = {0, "libyang debugging"}; + +struct nb_config *vty_shared_candidate_config; +static struct event_loop *master; + +static void vty_show_nb_errors(struct vty *vty, int error, const char *errmsg) +{ + vty_out(vty, "Error type: %s\n", nb_err_name(error)); + if (strlen(errmsg) > 0) + vty_out(vty, "Error description: %s\n", errmsg); +} + +static int nb_cli_classic_commit(struct vty *vty) +{ + struct nb_context context = {}; + char errmsg[BUFSIZ] = {0}; + int ret; + + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_commit(context, vty->candidate_config, true, NULL, + NULL, errmsg, sizeof(errmsg)); + switch (ret) { + case NB_OK: + /* Successful commit. Print warnings (if any). */ + if (strlen(errmsg) > 0) + vty_out(vty, "%s\n", errmsg); + break; + case NB_ERR_NO_CHANGES: + break; + default: + vty_out(vty, "%% Configuration failed.\n\n"); + vty_show_nb_errors(vty, ret, errmsg); + if (vty->pending_commit) + vty_out(vty, + "The following commands were dynamically grouped into the same transaction and rejected:\n%s", + vty->pending_cmds_buf); + + /* Regenerate candidate for consistency. */ + nb_config_replace(vty->candidate_config, running_config, true); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +static void nb_cli_pending_commit_clear(struct vty *vty) +{ + vty->pending_commit = 0; + XFREE(MTYPE_TMP, vty->pending_cmds_buf); + vty->pending_cmds_buflen = 0; + vty->pending_cmds_bufpos = 0; +} + +int nb_cli_pending_commit_check(struct vty *vty) +{ + int ret = CMD_SUCCESS; + + if (vty->pending_commit) { + ret = nb_cli_classic_commit(vty); + nb_cli_pending_commit_clear(vty); + } + + return ret; +} + +static int nb_cli_schedule_command(struct vty *vty) +{ + /* Append command to dynamically sized buffer of scheduled commands. */ + if (!vty->pending_cmds_buf) { + vty->pending_cmds_buflen = 4096; + vty->pending_cmds_buf = + XCALLOC(MTYPE_TMP, vty->pending_cmds_buflen); + } + if ((strlen(vty->buf) + 3) + > (vty->pending_cmds_buflen - vty->pending_cmds_bufpos)) { + vty->pending_cmds_buflen *= 2; + vty->pending_cmds_buf = + XREALLOC(MTYPE_TMP, vty->pending_cmds_buf, + vty->pending_cmds_buflen); + } + strlcat(vty->pending_cmds_buf, "- ", vty->pending_cmds_buflen); + vty->pending_cmds_bufpos = strlcat(vty->pending_cmds_buf, vty->buf, + vty->pending_cmds_buflen); + + /* Schedule the commit operation. */ + vty->pending_commit = 1; + + return CMD_SUCCESS; +} + +void nb_cli_enqueue_change(struct vty *vty, const char *xpath, + enum nb_operation operation, const char *value) +{ + struct nb_cfg_change *change; + + if (vty->num_cfg_changes == VTY_MAXCFGCHANGES) { + /* Not expected to happen. */ + vty_out(vty, + "%% Exceeded the maximum number of changes (%u) for a single command\n\n", + VTY_MAXCFGCHANGES); + return; + } + + change = &vty->cfg_changes[vty->num_cfg_changes++]; + strlcpy(change->xpath, xpath, sizeof(change->xpath)); + change->operation = operation; + change->value = value; +} + +static int nb_cli_apply_changes_internal(struct vty *vty, + const char *xpath_base, + bool clear_pending) +{ + bool error = false; + char buf[BUFSIZ]; + + VTY_CHECK_XPATH; + + nb_candidate_edit_config_changes(vty->candidate_config, vty->cfg_changes, + vty->num_cfg_changes, xpath_base, + false, buf, sizeof(buf), &error); + if (error) { + /* + * Failure to edit the candidate configuration should never + * happen in practice, unless there's a bug in the code. When + * that happens, log the error but otherwise ignore it. + */ + vty_out(vty, "%s", buf); + } + + /* + * Maybe do an implicit commit when using the classic CLI mode. + * + * NOTE: the implicit commit might be scheduled to run later when + * too many commands are being sent at the same time. This is a + * protection mechanism where multiple commands are grouped into the + * same configuration transaction, allowing them to be processed much + * faster. + */ + if (frr_get_cli_mode() == FRR_CLI_CLASSIC) { + if (clear_pending) { + if (vty->pending_commit) + return nb_cli_pending_commit_check(vty); + } else if (vty->pending_allowed) + return nb_cli_schedule_command(vty); + assert(!vty->pending_commit); + return nb_cli_classic_commit(vty); + } + + return CMD_SUCCESS; +} + +static void create_xpath_base_abs(struct vty *vty, char *xpath_base_abs, + size_t xpath_base_abs_size, + const char *xpath_base) +{ + memset(xpath_base_abs, 0, xpath_base_abs_size); + + if (xpath_base[0] == 0) + xpath_base = "."; + + /* If base xpath is relative, prepend current vty xpath. */ + if (vty->xpath_index > 0 && xpath_base[0] == '.') { + strlcpy(xpath_base_abs, VTY_CURR_XPATH, xpath_base_abs_size); + xpath_base++; /* skip '.' */ + } + strlcat(xpath_base_abs, xpath_base, xpath_base_abs_size); +} + +int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, ...) +{ + char xpath_base_abs[XPATH_MAXLEN] = {}; + char xpath_base[XPATH_MAXLEN] = {}; + bool implicit_commit; + int ret; + + /* Parse the base XPath format string. */ + if (xpath_base_fmt) { + va_list ap; + + va_start(ap, xpath_base_fmt); + vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap); + va_end(ap); + } + + create_xpath_base_abs(vty, xpath_base_abs, sizeof(xpath_base_abs), + xpath_base); + + if (vty_mgmt_should_process_cli_apply_changes(vty)) { + VTY_CHECK_XPATH; + + if (vty->type == VTY_FILE) + return CMD_SUCCESS; + + implicit_commit = vty_needs_implicit_commit(vty); + ret = vty_mgmt_send_config_data(vty, xpath_base_abs, + implicit_commit); + if (ret >= 0 && !implicit_commit) + vty->mgmt_num_pending_setcfg++; + return ret; + } + + return nb_cli_apply_changes_internal(vty, xpath_base_abs, false); +} + +int nb_cli_apply_changes_clear_pending(struct vty *vty, + const char *xpath_base_fmt, ...) +{ + char xpath_base_abs[XPATH_MAXLEN] = {}; + char xpath_base[XPATH_MAXLEN] = {}; + bool implicit_commit; + int ret; + + /* Parse the base XPath format string. */ + if (xpath_base_fmt) { + va_list ap; + + va_start(ap, xpath_base_fmt); + vsnprintf(xpath_base, sizeof(xpath_base), xpath_base_fmt, ap); + va_end(ap); + } + + create_xpath_base_abs(vty, xpath_base_abs, sizeof(xpath_base_abs), + xpath_base); + + if (vty_mgmt_should_process_cli_apply_changes(vty)) { + VTY_CHECK_XPATH; + /* + * The legacy user wanted to clear pending (i.e., perform a + * commit immediately) due to some non-yang compatible + * functionality. This new mgmtd code however, continues to send + * changes putting off the commit until XFRR_end is received + * (i.e., end-of-config-file). This should be fine b/c all + * conversions to mgmtd require full proper implementations. + */ + implicit_commit = vty_needs_implicit_commit(vty); + ret = vty_mgmt_send_config_data(vty, xpath_base_abs, + implicit_commit); + if (ret >= 0 && !implicit_commit) + vty->mgmt_num_pending_setcfg++; + return ret; + } + + return nb_cli_apply_changes_internal(vty, xpath_base_abs, true); +} + +int nb_cli_rpc_enqueue(struct vty *vty, const char *xpath, const char *value) +{ + struct nb_cfg_change *param; + + if (vty->num_rpc_params == VTY_MAXCFGCHANGES) { + /* Not expected to happen. */ + vty_out(vty, + "%% Exceeded the maximum number of params (%u) for a single command\n\n", + VTY_MAXCFGCHANGES); + return CMD_WARNING; + } + + param = &vty->rpc_params[vty->num_rpc_params++]; + strlcpy(param->xpath, xpath, sizeof(param->xpath)); + param->value = value; + + return CMD_SUCCESS; +} + +int nb_cli_rpc(struct vty *vty, const char *xpath, struct lyd_node **output_p) +{ + struct nb_node *nb_node; + struct lyd_node *input = NULL; + struct lyd_node *output = NULL; + LY_ERR err; + int ret; + char errmsg[BUFSIZ] = {0}; + + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + return CMD_WARNING; + } + + /* create input tree */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &input); + assert(err == LY_SUCCESS); + + for (size_t i = 0; i < vty->num_rpc_params; i++) { + err = lyd_new_path(input, ly_native_ctx, + vty->rpc_params[i].xpath, + vty->rpc_params[i].value, 0, NULL); + assert(err == LY_SUCCESS); + } + + if (vty_mgmt_fe_enabled()) { + char *data = NULL; + + err = lyd_print_mem(&data, input, LYD_JSON, LYD_PRINT_SHRINK); + assert(err == LY_SUCCESS); + + ret = vty_mgmt_send_rpc_req(vty, LYD_JSON, xpath, data); + + free(data); + lyd_free_all(input); + + if (ret < 0) + return CMD_WARNING; + return CMD_SUCCESS; + } + + /* validate input tree to create implicit defaults */ + err = lyd_validate_op(input, NULL, LYD_TYPE_RPC_YANG, NULL); + assert(err == LY_SUCCESS); + + /* create output tree root for population in the callback */ + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, 0, NULL, + &output); + assert(err == LY_SUCCESS); + + ret = nb_callback_rpc(nb_node, xpath, input, output, errmsg, + sizeof(errmsg)); + + /* validate output tree to create implicit defaults */ + err = lyd_validate_op(output, NULL, LYD_TYPE_REPLY_YANG, NULL); + assert(err == LY_SUCCESS); + + lyd_free_all(input); + vty->num_rpc_params = 0; + + switch (ret) { + case NB_OK: + if (output_p) + *output_p = output; + else + lyd_free_all(output); + return CMD_SUCCESS; + default: + lyd_free_all(output); + if (strlen(errmsg)) + vty_show_nb_errors(vty, ret, errmsg); + return CMD_WARNING; + } +} + +void nb_cli_confirmed_commit_clean(struct vty *vty) +{ + event_cancel(&vty->t_confirmed_commit_timeout); + nb_config_free(vty->confirmed_commit_rollback); + vty->confirmed_commit_rollback = NULL; +} + +int nb_cli_confirmed_commit_rollback(struct vty *vty) +{ + struct nb_context context = {}; + uint32_t transaction_id; + char errmsg[BUFSIZ] = {0}; + int ret; + + /* Perform the rollback. */ + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_commit( + context, vty->confirmed_commit_rollback, true, + "Rollback to previous configuration - confirmed commit has timed out", + &transaction_id, errmsg, sizeof(errmsg)); + if (ret == NB_OK) { + vty_out(vty, + "Rollback performed successfully (Transaction ID #%u).\n", + transaction_id); + /* Print warnings (if any). */ + if (strlen(errmsg) > 0) + vty_out(vty, "%s\n", errmsg); + } else { + vty_out(vty, + "Failed to rollback to previous configuration.\n\n"); + vty_show_nb_errors(vty, ret, errmsg); + } + + return ret; +} + +static void nb_cli_confirmed_commit_timeout(struct event *thread) +{ + struct vty *vty = EVENT_ARG(thread); + + /* XXX: broadcast this message to all logged-in users? */ + vty_out(vty, + "\nConfirmed commit has timed out, rolling back to previous configuration.\n\n"); + + nb_cli_confirmed_commit_rollback(vty); + nb_cli_confirmed_commit_clean(vty); +} + +static int nb_cli_commit(struct vty *vty, bool force, + unsigned int confirmed_timeout, char *comment) +{ + struct nb_context context = {}; + uint32_t transaction_id = 0; + char errmsg[BUFSIZ] = {0}; + int ret; + + /* Check if there's a pending confirmed commit. */ + if (vty->t_confirmed_commit_timeout) { + if (confirmed_timeout) { + /* Reset timeout if "commit confirmed" is used again. */ + vty_out(vty, + "%% Resetting confirmed-commit timeout to %u minute(s)\n\n", + confirmed_timeout); + + event_cancel(&vty->t_confirmed_commit_timeout); + event_add_timer(master, nb_cli_confirmed_commit_timeout, + vty, confirmed_timeout * 60, + &vty->t_confirmed_commit_timeout); + } else { + /* Accept commit confirmation. */ + vty_out(vty, "%% Commit complete.\n\n"); + nb_cli_confirmed_commit_clean(vty); + } + return CMD_SUCCESS; + } + + /* "force" parameter. */ + if (!force && nb_candidate_needs_update(vty->candidate_config)) { + vty_out(vty, + "%% Candidate configuration needs to be updated before commit.\n\n"); + vty_out(vty, + "Use the \"update\" command or \"commit force\".\n"); + return CMD_WARNING; + } + + /* "confirm" parameter. */ + if (confirmed_timeout) { + vty->confirmed_commit_rollback = nb_config_dup(running_config); + + vty->t_confirmed_commit_timeout = NULL; + event_add_timer(master, nb_cli_confirmed_commit_timeout, vty, + confirmed_timeout * 60, + &vty->t_confirmed_commit_timeout); + } + + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_commit(context, vty->candidate_config, true, comment, + &transaction_id, errmsg, sizeof(errmsg)); + + /* Map northbound return code to CLI return code. */ + switch (ret) { + case NB_OK: + nb_config_replace(vty->candidate_config_base, running_config, + true); + vty_out(vty, + "%% Configuration committed successfully (Transaction ID #%u).\n\n", + transaction_id); + /* Print warnings (if any). */ + if (strlen(errmsg) > 0) + vty_out(vty, "%s\n", errmsg); + return CMD_SUCCESS; + case NB_ERR_NO_CHANGES: + vty_out(vty, "%% No configuration changes to commit.\n\n"); + return CMD_SUCCESS; + default: + vty_out(vty, + "%% Failed to commit candidate configuration.\n\n"); + vty_show_nb_errors(vty, ret, errmsg); + return CMD_WARNING; + } +} + +static int nb_cli_candidate_load_file(struct vty *vty, + enum nb_cfg_format format, + struct yang_translator *translator, + const char *path, bool replace) +{ + struct nb_config *loaded_config = NULL; + struct lyd_node *dnode; + struct ly_ctx *ly_ctx; + int ly_format; + char buf[BUFSIZ]; + LY_ERR err; + + switch (format) { + case NB_CFG_FMT_CMDS: + loaded_config = nb_config_new(NULL); + if (!vty_read_config(loaded_config, path, config_default)) { + vty_out(vty, "%% Failed to load configuration.\n\n"); + vty_out(vty, + "Please check the logs for more details.\n"); + nb_config_free(loaded_config); + return CMD_WARNING; + } + break; + case NB_CFG_FMT_JSON: + case NB_CFG_FMT_XML: + ly_format = (format == NB_CFG_FMT_JSON) ? LYD_JSON : LYD_XML; + + ly_ctx = translator ? translator->ly_ctx : ly_native_ctx; + err = lyd_parse_data_path(ly_ctx, path, ly_format, + LYD_PARSE_ONLY | LYD_PARSE_NO_STATE, + 0, &dnode); + if (err || !dnode) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_path() failed", + __func__); + vty_out(vty, "%% Failed to load configuration:\n\n"); + vty_out(vty, "%s", + yang_print_errors(ly_native_ctx, buf, + sizeof(buf))); + return CMD_WARNING; + } + if (translator + && yang_translate_dnode(translator, + YANG_TRANSLATE_TO_NATIVE, &dnode) + != YANG_TRANSLATE_SUCCESS) { + vty_out(vty, "%% Failed to translate configuration\n"); + yang_dnode_free(dnode); + return CMD_WARNING; + } + loaded_config = nb_config_new(dnode); + break; + } + + if (replace) + nb_config_replace(vty->candidate_config, loaded_config, false); + else if (nb_config_merge(vty->candidate_config, loaded_config, false) + != NB_OK) { + vty_out(vty, + "%% Failed to merge the loaded configuration:\n\n"); + vty_out(vty, "%s", + yang_print_errors(ly_native_ctx, buf, sizeof(buf))); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static int nb_cli_candidate_load_transaction(struct vty *vty, + uint32_t transaction_id, + bool replace) +{ + struct nb_config *loaded_config; + char buf[BUFSIZ]; + + loaded_config = nb_db_transaction_load(transaction_id); + if (!loaded_config) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + transaction_id); + return CMD_WARNING; + } + + if (replace) + nb_config_replace(vty->candidate_config, loaded_config, false); + else if (nb_config_merge(vty->candidate_config, loaded_config, false) + != NB_OK) { + vty_out(vty, + "%% Failed to merge the loaded configuration:\n\n"); + vty_out(vty, "%s", + yang_print_errors(ly_native_ctx, buf, sizeof(buf))); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* Prepare the configuration for display. */ +void nb_cli_show_config_prepare(struct nb_config *config, bool with_defaults) +{ + /* Nothing to do for daemons that don't implement any YANG module. */ + if (config->dnode == NULL) + return; + + /* + * Call lyd_validate() only to create default child nodes, ignoring + * any possible validation error. This doesn't need to be done when + * displaying the running configuration since it's always fully + * validated. + */ + if (config != running_config) + (void)lyd_validate_all(&config->dnode, ly_native_ctx, + LYD_VALIDATE_NO_STATE, NULL); +} + +static int lyd_node_cmp(const struct lyd_node **dnode1, + const struct lyd_node **dnode2) +{ + struct nb_node *nb_node = (*dnode1)->schema->priv; + + return nb_node->cbs.cli_cmp(*dnode1, *dnode2); +} + +static void show_dnode_children_cmds(struct vty *vty, + const struct lyd_node *root, + bool with_defaults) +{ + struct nb_node *nb_node, *sort_node = NULL; + struct listnode *listnode; + struct lyd_node *child; + struct list *sort_list; + void *data; + + LY_LIST_FOR (lyd_child(root), child) { + nb_node = child->schema->priv; + + /* + * We finished processing current list, + * it's time to print the config. + */ + if (sort_node && nb_node != sort_node) { + list_sort(sort_list, + (int (*)(const void **, + const void **))lyd_node_cmp); + + for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data)) + nb_cli_show_dnode_cmds(vty, data, + with_defaults); + + list_delete(&sort_list); + sort_node = NULL; + } + + /* + * If the config needs to be sorted, + * then add the dnode to the sorting + * list for later processing. + */ + if (nb_node && nb_node->cbs.cli_cmp) { + if (!sort_node) { + sort_node = nb_node; + sort_list = list_new(); + } + + listnode_add(sort_list, child); + continue; + } + + nb_cli_show_dnode_cmds(vty, child, with_defaults); + } + + if (sort_node) { + list_sort(sort_list, + (int (*)(const void **, const void **))lyd_node_cmp); + + for (ALL_LIST_ELEMENTS_RO(sort_list, listnode, data)) + nb_cli_show_dnode_cmds(vty, data, with_defaults); + + list_delete(&sort_list); + sort_node = NULL; + } +} + +void nb_cli_show_dnode_cmds(struct vty *vty, const struct lyd_node *root, + bool with_defaults) +{ + struct nb_node *nb_node; + + if (!with_defaults && yang_dnode_is_default_recursive(root)) + return; + + nb_node = root->schema->priv; + + if (nb_node && nb_node->cbs.cli_show) + (*nb_node->cbs.cli_show)(vty, root, with_defaults); + + if (!(root->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) + show_dnode_children_cmds(vty, root, with_defaults); + + if (nb_node && nb_node->cbs.cli_show_end) + (*nb_node->cbs.cli_show_end)(vty, root); +} + +static void nb_cli_show_config_cmds(struct vty *vty, struct nb_config *config, + bool with_defaults) +{ + struct lyd_node *root; + + vty_out(vty, "Configuration:\n"); + vty_out(vty, "!\n"); + vty_out(vty, "frr version %s\n", FRR_VER_SHORT); + vty_out(vty, "frr defaults %s\n", frr_defaults_profile()); + + LY_LIST_FOR (config->dnode, root) { + nb_cli_show_dnode_cmds(vty, root, with_defaults); + } + + vty_out(vty, "!\n"); + vty_out(vty, "end\n"); +} + +static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format, + struct nb_config *config, + struct yang_translator *translator, + bool with_defaults) +{ + struct lyd_node *dnode; + char *strp; + int options = 0; + + dnode = yang_dnode_dup(config->dnode); + if (translator + && yang_translate_dnode(translator, YANG_TRANSLATE_FROM_NATIVE, + &dnode) + != YANG_TRANSLATE_SUCCESS) { + vty_out(vty, "%% Failed to translate configuration\n"); + yang_dnode_free(dnode); + return CMD_WARNING; + } + + SET_FLAG(options, LYD_PRINT_WITHSIBLINGS); + if (with_defaults) + SET_FLAG(options, LYD_PRINT_WD_ALL); + else + SET_FLAG(options, LYD_PRINT_WD_TRIM); + + if (lyd_print_mem(&strp, dnode, format, options) == 0 && strp) { + vty_out(vty, "%s", strp); + free(strp); + } + + yang_dnode_free(dnode); + + return CMD_SUCCESS; +} + +static int nb_cli_show_config(struct vty *vty, struct nb_config *config, + enum nb_cfg_format format, + struct yang_translator *translator, + bool with_defaults) +{ + nb_cli_show_config_prepare(config, with_defaults); + + switch (format) { + case NB_CFG_FMT_CMDS: + nb_cli_show_config_cmds(vty, config, with_defaults); + break; + case NB_CFG_FMT_JSON: + return nb_cli_show_config_libyang(vty, LYD_JSON, config, + translator, with_defaults); + case NB_CFG_FMT_XML: + return nb_cli_show_config_libyang(vty, LYD_XML, config, + translator, with_defaults); + } + + return CMD_SUCCESS; +} + +static int nb_write_config(struct nb_config *config, enum nb_cfg_format format, + struct yang_translator *translator, char *path, + size_t pathlen) +{ + int fd; + struct vty *file_vty; + int ret = 0; + + snprintf(path, pathlen, "/tmp/frr.tmp.XXXXXXXX"); + fd = mkstemp(path); + if (fd < 0) { + flog_warn(EC_LIB_SYSTEM_CALL, "%s: mkstemp() failed: %s", + __func__, safe_strerror(errno)); + return -1; + } + if (fchmod(fd, CONFIGFILE_MASK) != 0) { + flog_warn(EC_LIB_SYSTEM_CALL, + "%s: fchmod() failed: %s(%d):", __func__, + safe_strerror(errno), errno); + return -1; + } + + /* Make vty for configuration file. */ + file_vty = vty_new(); + file_vty->wfd = fd; + file_vty->type = VTY_FILE; + if (config) + ret = nb_cli_show_config(file_vty, config, format, translator, + false); + vty_close(file_vty); + + return ret; +} + +static int nb_cli_show_config_compare(struct vty *vty, + struct nb_config *config1, + struct nb_config *config2, + enum nb_cfg_format format, + struct yang_translator *translator) +{ + char config1_path[256]; + char config2_path[256]; + char command[BUFSIZ]; + FILE *fp; + char line[1024]; + int lineno = 0; + + if (nb_write_config(config1, format, translator, config1_path, + sizeof(config1_path)) + != 0) { + vty_out(vty, "%% Failed to process configurations.\n\n"); + return CMD_WARNING; + } + if (nb_write_config(config2, format, translator, config2_path, + sizeof(config2_path)) + != 0) { + vty_out(vty, "%% Failed to process configurations.\n\n"); + unlink(config1_path); + return CMD_WARNING; + } + + snprintf(command, sizeof(command), "diff -u %s %s", config1_path, + config2_path); + fp = popen(command, "r"); + if (!fp) { + vty_out(vty, "%% Failed to generate configuration diff.\n\n"); + unlink(config1_path); + unlink(config2_path); + return CMD_WARNING; + } + /* Print diff line by line. */ + while (fgets(line, sizeof(line), fp) != NULL) { + if (lineno++ < 2) + continue; + vty_out(vty, "%s", line); + } + pclose(fp); + + unlink(config1_path); + unlink(config2_path); + + return CMD_SUCCESS; +} + +/* Configure exclusively from this terminal. */ +DEFUN (config_exclusive, + config_exclusive_cmd, + "configure exclusive", + "Configuration from vty interface\n" + "Configure exclusively from this terminal\n") +{ + return vty_config_enter(vty, true, true, false); +} + +/* Configure using a private candidate configuration. */ +DEFUN (config_private, + config_private_cmd, + "configure private", + "Configuration from vty interface\n" + "Configure using a private candidate configuration\n") +{ + return vty_config_enter(vty, true, false, false); +} + +DEFPY (config_commit, + config_commit_cmd, + "commit [{force$force|confirmed (1-60)}]", + "Commit changes into the running configuration\n" + "Force commit even if the candidate is outdated\n" + "Rollback this commit unless there is a confirming commit\n" + "Timeout in minutes for the commit to be confirmed\n") +{ + return nb_cli_commit(vty, !!force, confirmed, NULL); +} + +DEFPY (config_commit_comment, + config_commit_comment_cmd, + "commit [{force$force|confirmed (1-60)}] comment LINE...", + "Commit changes into the running configuration\n" + "Force commit even if the candidate is outdated\n" + "Rollback this commit unless there is a confirming commit\n" + "Timeout in minutes for the commit to be confirmed\n" + "Assign a comment to this commit\n" + "Comment for this commit (Max 80 characters)\n") +{ + char *comment; + int idx = 0; + int ret; + + argv_find(argv, argc, "LINE", &idx); + comment = argv_concat(argv, argc, idx); + ret = nb_cli_commit(vty, !!force, confirmed, comment); + XFREE(MTYPE_TMP, comment); + + return ret; +} + +DEFPY (config_commit_check, + config_commit_check_cmd, + "commit check", + "Commit changes into the running configuration\n" + "Check if the configuration changes are valid\n") +{ + struct nb_context context = {}; + char errmsg[BUFSIZ] = {0}; + int ret; + + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_validate(&context, vty->candidate_config, errmsg, + sizeof(errmsg)); + if (ret != NB_OK) { + vty_out(vty, + "%% Failed to validate candidate configuration.\n\n"); + vty_show_nb_errors(vty, ret, errmsg); + return CMD_WARNING; + } + + vty_out(vty, "%% Candidate configuration validated successfully.\n\n"); + + return CMD_SUCCESS; +} + +DEFPY (config_update, + config_update_cmd, + "update", + "Update candidate configuration\n") +{ + if (!nb_candidate_needs_update(vty->candidate_config)) { + vty_out(vty, "%% Update is not necessary.\n\n"); + return CMD_SUCCESS; + } + + if (nb_candidate_update(vty->candidate_config) != NB_OK) { + vty_out(vty, + "%% Failed to update the candidate configuration.\n\n"); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } + + nb_config_replace(vty->candidate_config_base, running_config, true); + + vty_out(vty, "%% Candidate configuration updated successfully.\n\n"); + + return CMD_SUCCESS; +} + +DEFPY (config_discard, + config_discard_cmd, + "discard", + "Discard changes in the candidate configuration\n") +{ + nb_config_replace(vty->candidate_config, vty->candidate_config_base, + true); + + return CMD_SUCCESS; +} + +DEFPY (config_load, + config_load_cmd, + "configuration load\ + <\ + file [ [translate WORD$translator_family]] FILENAME$filename\ + |transaction (1-4294967295)$tid\ + >\ + [replace$replace]", + "Configuration related settings\n" + "Load configuration into candidate\n" + "Load configuration file into candidate\n" + "Load configuration file in JSON format\n" + "Load configuration file in XML format\n" + "Translate configuration file\n" + "YANG module translator\n" + "Configuration file name (full path)\n" + "Load configuration from transaction into candidate\n" + "Transaction ID\n" + "Replace instead of merge\n") +{ + if (filename) { + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, + "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + return nb_cli_candidate_load_file(vty, format, translator, + filename, !!replace); + } + + return nb_cli_candidate_load_transaction(vty, tid, !!replace); +} + +DEFPY (show_config_running, + show_config_running_cmd, + "show configuration running\ + [ [translate WORD$translator_family]]\ + [with-defaults$with_defaults]", + SHOW_STR + "Configuration information\n" + "Running configuration\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n") + +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + nb_cli_show_config(vty, running_config, format, translator, + !!with_defaults); + + return CMD_SUCCESS; +} + +DEFPY (show_config_candidate, + show_config_candidate_cmd, + "show configuration candidate\ + [ [translate WORD$translator_family]]\ + [<\ + with-defaults$with_defaults\ + |changes$changes\ + >]", + SHOW_STR + "Configuration information\n" + "Candidate configuration\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n" + "Show changes applied in the candidate configuration\n") + +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + if (changes) + return nb_cli_show_config_compare( + vty, vty->candidate_config_base, vty->candidate_config, + format, translator); + + nb_cli_show_config(vty, vty->candidate_config, format, translator, + !!with_defaults); + + return CMD_SUCCESS; +} + +DEFPY (show_config_candidate_section, + show_config_candidate_section_cmd, + "show", + SHOW_STR) +{ + struct lyd_node *dnode; + + /* Top-level configuration node, display everything. */ + if (vty->xpath_index == 0) + return nb_cli_show_config(vty, vty->candidate_config, + NB_CFG_FMT_CMDS, NULL, false); + + /* Display only the current section of the candidate configuration. */ + dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH); + if (!dnode) + /* Shouldn't happen. */ + return CMD_WARNING; + + nb_cli_show_dnode_cmds(vty, dnode, 0); + vty_out(vty, "!\n"); + + return CMD_SUCCESS; +} + +DEFPY (show_config_compare, + show_config_compare_cmd, + "show configuration compare\ + <\ + candidate$c1_candidate\ + |running$c1_running\ + |transaction (1-4294967295)$c1_tid\ + >\ + <\ + candidate$c2_candidate\ + |running$c2_running\ + |transaction (1-4294967295)$c2_tid\ + >\ + [ [translate WORD$translator_family]]", + SHOW_STR + "Configuration information\n" + "Compare two different configurations\n" + "Candidate configuration\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Candidate configuration\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n") +{ + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + struct nb_config *config1, *config_transaction1 = NULL; + struct nb_config *config2, *config_transaction2 = NULL; + int ret = CMD_WARNING; + + if (c1_candidate) + config1 = vty->candidate_config; + else if (c1_running) + config1 = running_config; + else { + config_transaction1 = nb_db_transaction_load(c1_tid); + if (!config_transaction1) { + vty_out(vty, "%% Transaction %u does not exist\n\n", + (unsigned int)c1_tid); + goto exit; + } + config1 = config_transaction1; + } + + if (c2_candidate) + config2 = vty->candidate_config; + else if (c2_running) + config2 = running_config; + else { + config_transaction2 = nb_db_transaction_load(c2_tid); + if (!config_transaction2) { + vty_out(vty, "%% Transaction %u does not exist\n\n", + (unsigned int)c2_tid); + goto exit; + } + config2 = config_transaction2; + } + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + goto exit; + } + } + + ret = nb_cli_show_config_compare(vty, config1, config2, format, + translator); +exit: + if (config_transaction1) + nb_config_free(config_transaction1); + if (config_transaction2) + nb_config_free(config_transaction2); + + return ret; +} + +/* + * Stripped down version of the "show configuration compare" command. + * The "candidate" option is not present so the command can be installed in + * the enable node. + */ +ALIAS (show_config_compare, + show_config_compare_without_candidate_cmd, + "show configuration compare\ + <\ + running$c1_running\ + |transaction (1-4294967295)$c1_tid\ + >\ + <\ + running$c2_running\ + |transaction (1-4294967295)$c2_tid\ + >\ + [ [translate WORD$translator_family]]", + SHOW_STR + "Configuration information\n" + "Compare two different configurations\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Running configuration\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n") + +DEFPY (clear_config_transactions, + clear_config_transactions_cmd, + "clear configuration transactions oldest (1-100)$n", + CLEAR_STR + "Configuration activity\n" + "Delete transactions from the transactions log\n" + "Delete oldest transactions\n" + "Number of transactions to delete\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (nb_db_clear_transactions(n) != NB_OK) { + vty_out(vty, "%% Failed to delete transactions.\n\n"); + return CMD_WARNING; + } +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return CMD_SUCCESS; +} + +DEFPY (config_database_max_transactions, + config_database_max_transactions_cmd, + "configuration database max-transactions (1-100)$max", + "Configuration related settings\n" + "Configuration database\n" + "Set the maximum number of transactions to store\n" + "Number of transactions\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (nb_db_set_max_transactions(max) != NB_OK) { + vty_out(vty, + "%% Failed to update the maximum number of transactions.\n\n"); + return CMD_WARNING; + } + vty_out(vty, + "%% Maximum number of transactions updated successfully.\n\n"); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return CMD_SUCCESS; +} + +DEFPY (yang_module_translator_load, + yang_module_translator_load_cmd, + "yang module-translator load FILENAME$filename", + "YANG related settings\n" + "YANG module translator\n" + "Load YANG module translator\n" + "File name (full path)\n") +{ + struct yang_translator *translator; + + translator = yang_translator_load(filename); + if (!translator) { + vty_out(vty, "%% Failed to load \"%s\"\n\n", filename); + vty_out(vty, "Please check the logs for more details.\n"); + return CMD_WARNING; + } + + vty_out(vty, "%% Module translator \"%s\" loaded successfully.\n\n", + translator->family); + + return CMD_SUCCESS; +} + +DEFPY (yang_module_translator_unload_family, + yang_module_translator_unload_cmd, + "yang module-translator unload WORD$translator_family", + "YANG related settings\n" + "YANG module translator\n" + "Unload YANG module translator\n" + "Name of the module translator\n") +{ + struct yang_translator *translator; + + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + + yang_translator_unload(translator); + + return CMD_SUCCESS; +} + +#ifdef HAVE_CONFIG_ROLLBACKS +static void nb_cli_show_transactions_cb(void *arg, int transaction_id, + const char *client_name, + const char *date, const char *comment) +{ + struct ttable *tt = arg; + + ttable_add_row(tt, "%d|%s|%s|%s", transaction_id, client_name, date, + comment); +} + +static int nb_cli_show_transactions(struct vty *vty) +{ + struct ttable *tt; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Transaction ID|Client|Date|Comment"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + /* Fetch transactions from the northbound database. */ + if (nb_db_transactions_iterate(nb_cli_show_transactions_cb, tt) + != NB_OK) { + vty_out(vty, + "%% Failed to fetch configuration transactions.\n"); + return CMD_WARNING; + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No configuration transactions to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} +#endif /* HAVE_CONFIG_ROLLBACKS */ + +DEFPY (show_config_transaction, + show_config_transaction_cmd, + "show configuration transaction\ + [\ + (1-4294967295)$transaction_id\ + [ [translate WORD$translator_family]]\ + [<\ + with-defaults$with_defaults\ + |changes$changes\ + >]\ + ]", + SHOW_STR + "Configuration information\n" + "Configuration transaction\n" + "Transaction ID\n" + "Change output format to JSON\n" + "Change output format to XML\n" + "Translate output\n" + "YANG module translator\n" + "Show default values\n" + "Show changes compared to the previous transaction\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + if (transaction_id) { + struct nb_config *config; + enum nb_cfg_format format; + struct yang_translator *translator = NULL; + + if (json) + format = NB_CFG_FMT_JSON; + else if (xml) + format = NB_CFG_FMT_XML; + else + format = NB_CFG_FMT_CMDS; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, + "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + } + + config = nb_db_transaction_load(transaction_id); + if (!config) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + (unsigned int)transaction_id); + return CMD_WARNING; + } + + if (changes) { + struct nb_config *prev_config; + int ret; + + /* NOTE: this can be NULL. */ + prev_config = + nb_db_transaction_load(transaction_id - 1); + + ret = nb_cli_show_config_compare( + vty, prev_config, config, format, translator); + if (prev_config) + nb_config_free(prev_config); + nb_config_free(config); + + return ret; + } + + nb_cli_show_config(vty, config, format, translator, + !!with_defaults); + nb_config_free(config); + + return CMD_SUCCESS; + } + + return nb_cli_show_transactions(vty); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); + return CMD_WARNING; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +static int nb_cli_oper_data_cb(const struct lysc_node *snode, + struct yang_translator *translator, + struct yang_data *data, void *arg) +{ + struct lyd_node *dnode = arg; + struct ly_ctx *ly_ctx; + + if (translator) { + int ret; + + ret = yang_translate_xpath(translator, + YANG_TRANSLATE_FROM_NATIVE, + data->xpath, sizeof(data->xpath)); + switch (ret) { + case YANG_TRANSLATE_SUCCESS: + break; + case YANG_TRANSLATE_NOTFOUND: + goto exit; + case YANG_TRANSLATE_FAILURE: + goto error; + } + + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + LY_ERR err = + lyd_new_path(dnode, ly_ctx, data->xpath, (void *)data->value, + LYD_NEW_PATH_UPDATE, &dnode); + if (err) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path(%s) failed: %s", + __func__, data->xpath, ly_errmsg(ly_native_ctx)); + goto error; + } + +exit: + return NB_OK; + +error: + return NB_ERR; +} + +DEFPY (show_yang_operational_data, + show_yang_operational_data_cmd, + "show yang operational-data XPATH$xpath\ + [{\ + format \ + |translate WORD$translator_family\ + |with-config$with_config\ + }]", + SHOW_STR + "YANG information\n" + "Show YANG operational data\n" + "XPath expression specifying the YANG data path\n" + "Set the output format\n" + "JavaScript Object Notation\n" + "Extensible Markup Language\n" + "Translate operational data\n" + "YANG module translator\n" + "Merge configuration data\n") +{ + LYD_FORMAT format; + struct yang_translator *translator = NULL; + struct ly_ctx *ly_ctx; + struct lyd_node *dnode; + char *strp; + uint32_t print_options = LYD_PRINT_WITHSIBLINGS; + int ret; + + if (xml) + format = LYD_XML; + else + format = LYD_JSON; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + /* Obtain data. */ + if (translator) { + dnode = yang_dnode_new(ly_ctx, false); + ret = nb_oper_iterate_legacy(xpath, translator, 0, + nb_cli_oper_data_cb, dnode, NULL); + } else { + dnode = NULL; + ret = nb_oper_iterate_legacy(xpath, NULL, 0, NULL, NULL, &dnode); + } + if (ret != NB_OK) { + if (format == LYD_JSON) + vty_out(vty, "{}\n"); + else { + /* embed ly_last_errmsg() when we get newer libyang */ + vty_out(vty, "\n"); + } + if (dnode) + yang_dnode_free(dnode); + return CMD_WARNING; + } + + if (with_config && yang_dnode_exists(running_config->dnode, xpath)) { + struct lyd_node *config_dnode = + yang_dnode_get(running_config->dnode, xpath); + if (config_dnode != NULL) { + lyd_merge_tree(&dnode, yang_dnode_dup(config_dnode), + LYD_MERGE_DESTRUCT); + print_options |= LYD_PRINT_WD_ALL; + } + } + + (void)lyd_validate_all(&dnode, ly_ctx, 0, NULL); + + /* Display the data. */ + if (lyd_print_mem(&strp, dnode, format, print_options) != 0 || !strp) { + vty_out(vty, "%% Failed to display operational data.\n"); + yang_dnode_free(dnode); + return CMD_WARNING; + } + vty_out(vty, "%s", strp); + free(strp); + yang_dnode_free(dnode); + + return CMD_SUCCESS; +} + +DEFPY (show_yang_module, + show_yang_module_cmd, + "show yang module [module-translator WORD$translator_family]", + SHOW_STR + "YANG information\n" + "Show loaded modules\n" + "YANG module translator\n" + "YANG module translator\n") +{ + struct ly_ctx *ly_ctx; + struct yang_translator *translator = NULL; + const struct lys_module *module; + struct ttable *tt; + uint32_t idx = 0; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Module|Version|Revision|Flags|Namespace"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + while ((module = ly_ctx_get_module_iter(ly_ctx, &idx))) { + char flags[8]; + + snprintf(flags, sizeof(flags), "%c%c", + module->implemented ? 'I' : ' ', + LY_ARRAY_COUNT(module->deviated_by) ? 'D' : ' '); + + ttable_add_row(tt, "%s|%s|%s|%s|%s", module->name, + (module->parsed->version == 2) ? "1.1" : "1.0", + module->revision ? module->revision : "-", flags, + module->ns); + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + vty_out(vty, " Flags: I - Implemented, D - Deviated\n\n"); + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No YANG modules to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +DEFPY(show_yang_module_detail, show_yang_module_detail_cmd, + "show yang module\ + [module-translator WORD$translator_family]\ + WORD$module_name ", + SHOW_STR + "YANG information\n" + "Show loaded modules\n" + "YANG module translator\n" + "YANG module translator\n" + "Module name\n" + "Display compiled module in YANG format\n" + "Display summary information about the module\n" + "Display module in the tree (RFC 8340) format\n" + "Display module in the YANG format\n" + "Display module in the YIN format\n") +{ + struct ly_ctx *ly_ctx; + struct yang_translator *translator = NULL; + const struct lys_module *module; + LYS_OUTFORMAT format; + char *strp; + + if (translator_family) { + translator = yang_translator_find(translator_family); + if (!translator) { + vty_out(vty, "%% Module translator \"%s\" not found\n", + translator_family); + return CMD_WARNING; + } + ly_ctx = translator->ly_ctx; + } else + ly_ctx = ly_native_ctx; + + module = ly_ctx_get_module_latest(ly_ctx, module_name); + if (!module) { + vty_out(vty, "%% Module \"%s\" not found\n", module_name); + return CMD_WARNING; + } + + if (yang) + format = LYS_OUT_YANG; + else if (yin) + format = LYS_OUT_YIN; + else if (compiled) + format = LYS_OUT_YANG_COMPILED; + else if (tree) + format = LYS_OUT_TREE; + else { + vty_out(vty, + "%% libyang v2 does not currently support summary\n"); + return CMD_WARNING; + } + + if (lys_print_mem(&strp, module, format, 0) == 0) { + vty_out(vty, "%s\n", strp); + free(strp); + } else { + /* Unexpected. */ + vty_out(vty, "%% Error generating module information\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFPY (show_yang_module_translator, + show_yang_module_translator_cmd, + "show yang module-translator", + SHOW_STR + "YANG information\n" + "Show loaded YANG module translators\n") +{ + struct yang_translator *translator; + struct ttable *tt; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Family|Module|Deviations|Coverage (%%)"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + RB_FOREACH (translator, yang_translators, &yang_translators) { + struct yang_tmodule *tmodule; + struct listnode *ln; + + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + ttable_add_row(tt, "%s|%s|%s|%.2f", translator->family, + tmodule->module->name, + tmodule->deviations->name, + tmodule->coverage); + } + } + + /* Dump the generated table. */ + if (tt->nrows > 1) { + char *table; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } else + vty_out(vty, "No YANG module translators to display.\n\n"); + + ttable_del(tt); + + return CMD_SUCCESS; +} + +#ifdef HAVE_CONFIG_ROLLBACKS +static int nb_cli_rollback_configuration(struct vty *vty, + uint32_t transaction_id) +{ + struct nb_context context = {}; + struct nb_config *candidate; + char comment[80]; + char errmsg[BUFSIZ] = {0}; + int ret; + + candidate = nb_db_transaction_load(transaction_id); + if (!candidate) { + vty_out(vty, "%% Transaction %u does not exist.\n\n", + transaction_id); + return CMD_WARNING; + } + + snprintf(comment, sizeof(comment), "Rollback to transaction %u", + transaction_id); + + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_commit(context, candidate, true, comment, NULL, + errmsg, sizeof(errmsg)); + nb_config_free(candidate); + switch (ret) { + case NB_OK: + vty_out(vty, + "%% Configuration was successfully rolled back.\n\n"); + /* Print warnings (if any). */ + if (strlen(errmsg) > 0) + vty_out(vty, "%s\n", errmsg); + return CMD_SUCCESS; + case NB_ERR_NO_CHANGES: + vty_out(vty, + "%% Aborting - no configuration changes detected.\n\n"); + return CMD_WARNING; + default: + vty_out(vty, "%% Rollback failed.\n\n"); + vty_show_nb_errors(vty, ret, errmsg); + return CMD_WARNING; + } +} +#endif /* HAVE_CONFIG_ROLLBACKS */ + +DEFPY (rollback_config, + rollback_config_cmd, + "rollback configuration (1-4294967295)$transaction_id", + "Rollback to a previous state\n" + "Running configuration\n" + "Transaction ID\n") +{ +#ifdef HAVE_CONFIG_ROLLBACKS + return nb_cli_rollback_configuration(vty, transaction_id); +#else + vty_out(vty, + "%% FRR was compiled without --enable-config-rollbacks.\n\n"); + return CMD_SUCCESS; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +/* Debug CLI commands. */ +static struct debug *nb_debugs[] = { + &nb_dbg_cbs_config, &nb_dbg_cbs_state, &nb_dbg_cbs_rpc, + &nb_dbg_cbs_notify, &nb_dbg_notif, &nb_dbg_events, + &nb_dbg_libyang, +}; + +static const char *const nb_debugs_conflines[] = { + "debug northbound callbacks configuration", + "debug northbound callbacks state", + "debug northbound callbacks rpc", + "debug northbound callbacks notify", + "debug northbound notifications", + "debug northbound events", + "debug northbound libyang", +}; + +DEFINE_HOOK(nb_client_debug_set_all, (uint32_t flags, bool set), (flags, set)); + +static void nb_debug_set_all(uint32_t flags, bool set) +{ + for (unsigned int i = 0; i < array_size(nb_debugs); i++) { + DEBUG_FLAGS_SET(nb_debugs[i], flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_ALL)) + DEBUG_CLEAR(nb_debugs[i]); + } + + hook_call(nb_client_debug_set_all, flags, set); +} + +DEFPY (debug_nb, + debug_nb_cmd, + "[no] debug northbound\ + [<\ + callbacks$cbs [{configuration$cbs_cfg|state$cbs_state|rpc$cbs_rpc|notify$cbs_notify}]\ + |notifications$notifications\ + |events$events\ + |libyang$libyang\ + >]", + NO_STR + DEBUG_STR + "Northbound debugging\n" + "Callbacks\n" + "Configuration\n" + "State\n" + "RPC\n" + "Notifications\n" + "Notifications\n" + "Events\n" + "libyang debugging\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + if (cbs) { + bool none = (!cbs_cfg && !cbs_state && !cbs_rpc && !cbs_notify); + + if (none || cbs_cfg) + DEBUG_MODE_SET(&nb_dbg_cbs_config, mode, !no); + if (none || cbs_state) + DEBUG_MODE_SET(&nb_dbg_cbs_state, mode, !no); + if (none || cbs_rpc) + DEBUG_MODE_SET(&nb_dbg_cbs_rpc, mode, !no); + if (none || cbs_notify) + DEBUG_MODE_SET(&nb_dbg_cbs_notify, mode, !no); + } + if (notifications) + DEBUG_MODE_SET(&nb_dbg_notif, mode, !no); + if (events) + DEBUG_MODE_SET(&nb_dbg_events, mode, !no); + if (libyang) { + DEBUG_MODE_SET(&nb_dbg_libyang, mode, !no); + yang_debugging_set(!no); + } + + /* no specific debug --> act on all of them */ + if (strmatch(argv[argc - 1]->text, "northbound")) { + nb_debug_set_all(mode, !no); + yang_debugging_set(!no); + } + + return CMD_SUCCESS; +} + +DEFINE_HOOK(nb_client_debug_config_write, (struct vty *vty), (vty)); + +static int nb_debug_config_write(struct vty *vty) +{ + for (unsigned int i = 0; i < array_size(nb_debugs); i++) + if (DEBUG_MODE_CHECK(nb_debugs[i], DEBUG_MODE_CONF)) + vty_out(vty, "%s\n", nb_debugs_conflines[i]); + + hook_call(nb_client_debug_config_write, vty); + + return 1; +} + +static struct debug_callbacks nb_dbg_cbs = {.debug_set_all = nb_debug_set_all}; +static struct cmd_node nb_debug_node = { + .name = "northbound debug", + .node = NORTHBOUND_DEBUG_NODE, + .prompt = "", + .config_write = nb_debug_config_write, +}; + +void nb_cli_install_default(int node) +{ + _install_element(node, &show_config_candidate_section_cmd); + + if (frr_get_cli_mode() != FRR_CLI_TRANSACTIONAL) + return; + + _install_element(node, &config_commit_cmd); + _install_element(node, &config_commit_comment_cmd); + _install_element(node, &config_commit_check_cmd); + _install_element(node, &config_update_cmd); + _install_element(node, &config_discard_cmd); + _install_element(node, &show_config_running_cmd); + _install_element(node, &show_config_candidate_cmd); + _install_element(node, &show_config_compare_cmd); + _install_element(node, &show_config_transaction_cmd); +} + +/* YANG module autocomplete. */ +static void yang_module_autocomplete(vector comps, struct cmd_token *token) +{ + const struct lys_module *module; + struct yang_translator *module_tr; + uint32_t idx; + + idx = 0; + while ((module = ly_ctx_get_module_iter(ly_native_ctx, &idx))) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module->name)); + + RB_FOREACH (module_tr, yang_translators, &yang_translators) { + idx = 0; + while ((module = ly_ctx_get_module_iter(module_tr->ly_ctx, + &idx))) + vector_set(comps, + XSTRDUP(MTYPE_COMPLETION, module->name)); + } +} + +/* YANG module translator autocomplete. */ +static void yang_translator_autocomplete(vector comps, struct cmd_token *token) +{ + struct yang_translator *module_tr; + + RB_FOREACH (module_tr, yang_translators, &yang_translators) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, module_tr->family)); +} + +static const struct cmd_variable_handler yang_var_handlers[] = { + {.varname = "module_name", .completions = yang_module_autocomplete}, + {.varname = "translator_family", + .completions = yang_translator_autocomplete}, + {.completions = NULL}}; + +void nb_cli_init(struct event_loop *tm) +{ + master = tm; + + /* Initialize the shared candidate configuration. */ + vty_shared_candidate_config = nb_config_new(NULL); + + debug_init(&nb_dbg_cbs); + + install_node(&nb_debug_node); + install_element(ENABLE_NODE, &debug_nb_cmd); + install_element(CONFIG_NODE, &debug_nb_cmd); + + /* Install commands specific to the transaction-base mode. */ + if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) { + install_element(ENABLE_NODE, &config_exclusive_cmd); + install_element(ENABLE_NODE, &config_private_cmd); + install_element(ENABLE_NODE, + &show_config_compare_without_candidate_cmd); + install_element(ENABLE_NODE, &show_config_transaction_cmd); + install_element(ENABLE_NODE, &rollback_config_cmd); + install_element(ENABLE_NODE, &clear_config_transactions_cmd); + + install_element(CONFIG_NODE, &config_load_cmd); + install_element(CONFIG_NODE, + &config_database_max_transactions_cmd); + } + + /* Other commands. */ + install_element(ENABLE_NODE, &show_config_running_cmd); + install_element(CONFIG_NODE, &yang_module_translator_load_cmd); + install_element(CONFIG_NODE, &yang_module_translator_unload_cmd); + install_element(ENABLE_NODE, &show_yang_operational_data_cmd); + install_element(ENABLE_NODE, &show_yang_module_cmd); + install_element(ENABLE_NODE, &show_yang_module_detail_cmd); + install_element(ENABLE_NODE, &show_yang_module_translator_cmd); + cmd_variable_handler_register(yang_var_handlers); +} + +void nb_cli_terminate(void) +{ + nb_config_free(vty_shared_candidate_config); +} diff --git a/lib/northbound_cli.h b/lib/northbound_cli.h new file mode 100644 index 0000000..4c8dc50 --- /dev/null +++ b/lib/northbound_cli.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_NORTHBOUND_CLI_H_ +#define _FRR_NORTHBOUND_CLI_H_ + +#include "northbound.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Possible formats in which a configuration can be displayed. */ +enum nb_cfg_format { + NB_CFG_FMT_CMDS = 0, + NB_CFG_FMT_JSON, + NB_CFG_FMT_XML, +}; + +extern struct nb_config *vty_shared_candidate_config; + +/* + * Enqueue change to be applied in the candidate configuration. + * + * vty + * The vty context. + * + * xpath + * XPath (absolute or relative) of the configuration option being edited. + * + * operation + * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or NB_OP_DESTROY). + * + * value + * New value of the configuration option. Should be NULL for typeless YANG + * data (e.g. presence-containers). For convenience, NULL can also be used + * to restore a leaf to its default value. + */ +extern void nb_cli_enqueue_change(struct vty *vty, const char *xpath, + enum nb_operation operation, + const char *value); + +/* + * Apply enqueued changes to the candidate configuration, do not batch, + * and apply any pending commits along with the currently enqueued. + * + * vty + * The vty context. + * + * xpath_base_fmt + * Prepend the given XPath (absolute or relative) to all enqueued + * configuration changes. This is an optional parameter. + * + * Returns: + * CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise. + */ +extern int nb_cli_apply_changes_clear_pending(struct vty *vty, + const char *xpath_base_fmt, ...) + PRINTFRR(2, 3); + +/* + * Apply enqueued changes to the candidate configuration, this function + * may not immediately apply the changes, instead adding them to a pending + * queue. + * + * vty + * The vty context. + * + * xpath_base_fmt + * Prepend the given XPath (absolute or relative) to all enqueued + * configuration changes. This is an optional parameter. + * + * Returns: + * CMD_SUCCESS on success, CMD_WARNING_CONFIG_FAILED otherwise. + */ +extern int nb_cli_apply_changes(struct vty *vty, const char *xpath_base_fmt, + ...) PRINTFRR(2, 3); + +/* + * Add an input child node for an RPC or an action. + * + * vty + * The vty context. + * + * xpath + * XPath of the child being added, relative to the input container. + * + * value + * Value of the child being added. Can be NULL for containers and leafs of + * type 'empty'. + */ +extern int nb_cli_rpc_enqueue(struct vty *vty, const char *xpath, + const char *value); + +/* + * Execute a YANG RPC or Action using the enqueued input parameters. + * + * vty + * The vty terminal to dump any error. + * + * xpath + * XPath of the YANG RPC or Action node. + * + * output_p + * A pointer to the libyang data node that will hold the output data tree. + * It can be set to NULL if the caller is not interested in processing the + * output. The caller is responsible for freeing the output data tree. + * + * Returns: + * CMD_SUCCESS on success, CMD_WARNING otherwise. + */ +extern int nb_cli_rpc(struct vty *vty, const char *xpath, + struct lyd_node **output_p); + +/* + * Show CLI commands associated to the given YANG data node. + * + * vty + * The vty terminal to dump the configuration to. + * + * dnode + * libyang data node that should be shown in the form of CLI commands. + * + * show_defaults + * Specify whether to display default configuration values or not. + */ +extern void nb_cli_show_dnode_cmds(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); + +/* + * Perform pending commit, if any. + * + * vty + * The vty context. + * + * Returns + * CMD_SUCCESS on success (or no pending), CMD_WARNING_CONFIG_FAILED + * otherwise. + */ +extern int nb_cli_pending_commit_check(struct vty *vty); + +/* Prototypes of internal functions. */ +extern void nb_cli_show_config_prepare(struct nb_config *config, + bool with_defaults); +extern void nb_cli_confirmed_commit_clean(struct vty *vty); +extern int nb_cli_confirmed_commit_rollback(struct vty *vty); +extern void nb_cli_install_default(int node); +extern void nb_cli_init(struct event_loop *tm); +extern void nb_cli_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_NORTHBOUND_CLI_H_ */ diff --git a/lib/northbound_db.c b/lib/northbound_db.c new file mode 100644 index 0000000..74abcde --- /dev/null +++ b/lib/northbound_db.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "libfrr.h" +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "db.h" +#include "northbound.h" +#include "northbound_db.h" + +int nb_db_init(void) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* + * NOTE: the delete_tail SQL trigger is used to implement a ring buffer + * where only the last N transactions are recorded in the configuration + * log. + */ + if (db_execute( + "BEGIN TRANSACTION;\n" + " CREATE TABLE IF NOT EXISTS transactions(\n" + " client CHAR(32) NOT NULL,\n" + " date DATETIME DEFAULT CURRENT_TIMESTAMP,\n" + " comment CHAR(80) ,\n" + " configuration TEXT NOT NULL\n" + " );\n" + " CREATE TRIGGER IF NOT EXISTS delete_tail\n" + " AFTER INSERT ON transactions\n" + " FOR EACH ROW\n" + " BEGIN\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" + " END;\n" + "COMMIT;", + NB_DLFT_MAX_CONFIG_ROLLBACKS, NB_DLFT_MAX_CONFIG_ROLLBACKS) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_transaction_save(const struct nb_transaction *transaction, + uint32_t *transaction_id) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + struct sqlite3_stmt *ss; + const char *client_name; + char *config_str = NULL; + int ret = NB_ERR; + + /* + * Use a transaction to ensure consistency between the INSERT and SELECT + * queries. + */ + if (db_execute("BEGIN TRANSACTION;") != 0) + return NB_ERR; + + ss = db_prepare( + "INSERT INTO transactions\n" + " (client, comment, configuration)\n" + "VALUES\n" + " (?, ?, ?);"); + if (!ss) + goto exit; + + client_name = nb_client_name(transaction->context.client); + /* + * Always record configurations in the XML format, save the default + * values too, as this covers the case where defaults may change. + */ + if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML, + LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL) + != 0) + goto exit; + + if (db_bindf(ss, "%s%s%s", client_name, strlen(client_name), + transaction->comment, strlen(transaction->comment), + config_str ? config_str : "", + config_str ? strlen(config_str) : 0) + != 0) + goto exit; + + if (db_run(ss) != SQLITE_OK) + goto exit; + + db_finalize(&ss); + + /* + * transaction_id is an optional output parameter that provides the ID + * of the recorded transaction. + */ + if (transaction_id) { + ss = db_prepare("SELECT last_insert_rowid();"); + if (!ss) + goto exit; + + if (db_run(ss) != SQLITE_ROW) + goto exit; + + if (db_loadf(ss, "%i", transaction_id) != 0) + goto exit; + + db_finalize(&ss); + } + + if (db_execute("COMMIT;") != 0) + goto exit; + + ret = NB_OK; + +exit: + if (config_str) + free(config_str); + if (ss) + db_finalize(&ss); + if (ret != NB_OK) + (void)db_execute("ROLLBACK TRANSACTION;"); + + return ret; +#else /* HAVE_CONFIG_ROLLBACKS */ + return NB_OK; +#endif /* HAVE_CONFIG_ROLLBACKS */ +} + +struct nb_config *nb_db_transaction_load(uint32_t transaction_id) +{ + struct nb_config *config = NULL; +#ifdef HAVE_CONFIG_ROLLBACKS + struct lyd_node *dnode; + const char *config_str; + struct sqlite3_stmt *ss; + LY_ERR err; + + ss = db_prepare( + "SELECT\n" + " configuration\n" + "FROM\n" + " transactions\n" + "WHERE\n" + " rowid=?;"); + if (!ss) + return NULL; + + if (db_bindf(ss, "%d", transaction_id) != 0) + goto exit; + + if (db_run(ss) != SQLITE_ROW) + goto exit; + + if (db_loadf(ss, "%s", &config_str) != 0) + goto exit; + + err = lyd_parse_data_mem(ly_native_ctx, config_str, LYD_XML, + LYD_PARSE_STRICT | LYD_PARSE_NO_STATE, + LYD_VALIDATE_NO_STATE, &dnode); + if (err || !dnode) + flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_data_mem() failed", + __func__); + else + config = nb_config_new(dnode); + +exit: + db_finalize(&ss); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return config; +} + +int nb_db_clear_transactions(unsigned int n_oldest) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* Delete oldest N entries. */ + if (db_execute("DELETE\n" + "FROM\n" + " transactions\n" + "WHERE\n" + " ROWID IN (\n" + " SELECT\n" + " ROWID\n" + " FROM\n" + " transactions\n" + " ORDER BY ROWID ASC LIMIT %u\n" + " );", + n_oldest) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_set_max_transactions(unsigned int max) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + /* + * Delete old entries if necessary and update the SQL trigger that + * auto-deletes old entries. + */ + if (db_execute("BEGIN TRANSACTION;\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " ROWID IN (\n" + " SELECT\n" + " ROWID\n" + " FROM\n" + " transactions\n" + " ORDER BY ROWID DESC LIMIT -1 OFFSET %u\n" + " );\n" + " DROP TRIGGER delete_tail;\n" + " CREATE TRIGGER delete_tail\n" + " AFTER INSERT ON transactions\n" + " FOR EACH ROW\n" + " BEGIN\n" + " DELETE\n" + " FROM\n" + " transactions\n" + " WHERE\n" + " rowid%%%u=NEW.rowid%%%u AND rowid!=NEW.rowid;\n" + " END;\n" + "COMMIT;", + max, max, max) + != 0) + return NB_ERR; +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} + +int nb_db_transactions_iterate(void (*func)(void *arg, int transaction_id, + const char *client_name, + const char *date, + const char *comment), + void *arg) +{ +#ifdef HAVE_CONFIG_ROLLBACKS + struct sqlite3_stmt *ss; + + /* Send SQL query and parse the result. */ + ss = db_prepare( + "SELECT\n" + " rowid, client, date, comment\n" + "FROM\n" + " transactions\n" + "ORDER BY\n" + " rowid DESC;"); + if (!ss) + return NB_ERR; + + while (db_run(ss) == SQLITE_ROW) { + int transaction_id; + const char *client_name; + const char *date; + const char *comment; + int ret; + + ret = db_loadf(ss, "%i%s%s%s", &transaction_id, &client_name, + &date, &comment); + if (ret != 0) + continue; + + (*func)(arg, transaction_id, client_name, date, comment); + } + + db_finalize(&ss); +#endif /* HAVE_CONFIG_ROLLBACKS */ + + return NB_OK; +} diff --git a/lib/northbound_db.h b/lib/northbound_db.h new file mode 100644 index 0000000..07a2783 --- /dev/null +++ b/lib/northbound_db.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_NORTHBOUND_DB_H_ +#define _FRR_NORTHBOUND_DB_H_ + +#include "northbound.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Initialize the northbound database. + * + * Currently the database is used only for storing and retrieving configuration + * transactions. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +int nb_db_init(void); + +/* + * Save a configuration transaction in the northbound database. + * + * transaction + * Configuration transaction to be saved. + * + * transaction_id + * Output parameter providing the ID of the saved transaction. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +int nb_db_transaction_save(const struct nb_transaction *transaction, + uint32_t *transaction_id); + +/* + * Load a configuration transaction from the transactions log. + * + * transaction_id + * ID of the transaction to be loaded. + * + * Returns: + * Pointer to newly created configuration or NULL in the case of an error. + */ +extern struct nb_config *nb_db_transaction_load(uint32_t transaction_id); + +/* + * Delete the specified number of transactions from the transactions log. + * + * n_oldest + * Number of transactions to delete. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_db_clear_transactions(unsigned int n_oldest); + +/* + * Specify the maximum number of transactions we want to record in the + * transactions log. Note that older transactions can be removed during this + * operation. + * + * max + * New upper limit of maximum transactions to log. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_db_set_max_transactions(unsigned int max); + +/* + * Iterate over all configuration transactions stored in the northbound + * database, sorted in descending order. + * + * func + * Function to call with each configuration transaction. + * + * arg + * Arbitrary argument passed as the first parameter in each call to 'func'. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_db_transactions_iterate( + void (*func)(void *arg, int transaction_id, const char *client_name, + const char *date, const char *comment), + void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_NORTHBOUND_DB_H_ */ diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp new file mode 100644 index 0000000..612ec39 --- /dev/null +++ b/lib/northbound_grpc.cpp @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// Copyright (c) 2021-2022, LabN Consulting, L.L.C +// Copyright (C) 2019 NetDEF, Inc. +// Renato Westphal +// + +#include +#include +#include "grpc/frr-northbound.grpc.pb.h" + +#include "log.h" +#include "libfrr.h" +#include "lib/version.h" +#include "frrevent.h" +#include "command.h" +#include "lib_errors.h" +#include "northbound.h" +#include "northbound_db.h" +#include "frr_pthread.h" + +#include +#include +#include +#include + +#define GRPC_DEFAULT_PORT 50051 + + +// ------------------------------------------------------ +// File Local Variables +// ------------------------------------------------------ + +/* + * NOTE: we can't use the FRR debugging infrastructure here since it uses + * atomics and C++ has a different atomics API. Enable gRPC debugging + * unconditionally until we figure out a way to solve this problem. + */ +static bool nb_dbg_client_grpc = 0; + +static struct event_loop *main_master; + +static struct frr_pthread *fpt; + +static bool grpc_running; + +#define grpc_debug(...) \ + do { \ + if (nb_dbg_client_grpc) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +// ------------------------------------------------------ +// New Types +// ------------------------------------------------------ + +enum CallState { CREATE, PROCESS, MORE, FINISH, DELETED }; +const char *call_states[] = {"CREATE", "PROCESS", "MORE", "FINISH", "DELETED"}; + +struct candidate { + uint64_t id; + struct nb_config *config; + struct nb_transaction *transaction; +}; + +class Candidates +{ + public: + ~Candidates(void) + { + // Delete candidates. + for (auto it = _cdb.begin(); it != _cdb.end(); it++) + delete_candidate(it->first); + } + + struct candidate *create_candidate(void) + { + uint64_t id = ++_next_id; + assert(id); // TODO: implement an algorithm for unique reusable + // IDs. + struct candidate *c = &_cdb[id]; + c->id = id; + c->config = nb_config_dup(running_config); + c->transaction = NULL; + + return c; + } + + bool contains(uint64_t candidate_id) + { + return _cdb.count(candidate_id) > 0; + } + + void delete_candidate(uint64_t candidate_id) + { + struct candidate *c = &_cdb[candidate_id]; + char errmsg[BUFSIZ] = {0}; + + nb_config_free(c->config); + if (c->transaction) + nb_candidate_commit_abort(c->transaction, errmsg, + sizeof(errmsg)); + _cdb.erase(c->id); + } + + struct candidate *get_candidate(uint64_t id) + { + return _cdb.count(id) == 0 ? NULL : &_cdb[id]; + } + + private: + uint64_t _next_id = 0; + std::map _cdb; +}; + +/* + * RpcStateBase is the common base class used to track a gRPC RPC. + */ +class RpcStateBase +{ + public: + virtual void do_request(::frr::Northbound::AsyncService *service, + ::grpc::ServerCompletionQueue *cq, + bool no_copy) = 0; + + RpcStateBase(const char *name) : name(name){}; + + virtual ~RpcStateBase() = default; + + CallState get_state() const + { + return state; + } + + bool is_initial_process() const + { + /* Will always be true for Unary */ + return entered_state == CREATE; + } + + // Returns "more" status, if false caller can delete + bool run(frr::Northbound::AsyncService *service, + grpc::ServerCompletionQueue *cq) + { + /* + * We enter in either CREATE or MORE state, and transition to + * PROCESS state. + */ + this->entered_state = this->state; + this->state = PROCESS; + grpc_debug("%s RPC: %s -> %s on grpc-io-thread", name, + call_states[this->entered_state], + call_states[this->state]); + /* + * We schedule the callback on the main pthread, and wait for + * the state to transition out of the PROCESS state. The new + * state will either be MORE or FINISH. It will always be FINISH + * for Unary RPCs. + */ + event_add_event(main_master, c_callback, (void *)this, 0, NULL); + + pthread_mutex_lock(&this->cmux); + while (this->state == PROCESS) + pthread_cond_wait(&this->cond, &this->cmux); + pthread_mutex_unlock(&this->cmux); + + grpc_debug("%s RPC in %s on grpc-io-thread", name, + call_states[this->state]); + + if (this->state == FINISH) { + /* + * Server is done (FINISH) so prep to receive a new + * request of this type. We could do this earlier but + * that would mean we could be handling multiple same + * type requests in parallel without limit. + */ + this->do_request(service, cq, false); + } + return true; + } + + protected: + virtual CallState run_mainthread(struct event *thread) = 0; + + static void c_callback(struct event *thread) + { + auto _tag = static_cast(EVENT_ARG(thread)); + /* + * We hold the lock until the callback finishes and has updated + * _tag->state, then we signal done and release. + */ + pthread_mutex_lock(&_tag->cmux); + + CallState enter_state = _tag->state; + grpc_debug("%s RPC: running %s on main thread", _tag->name, + call_states[enter_state]); + + _tag->state = _tag->run_mainthread(thread); + + grpc_debug("%s RPC: %s -> %s [main thread]", _tag->name, + call_states[enter_state], call_states[_tag->state]); + + pthread_cond_signal(&_tag->cond); + pthread_mutex_unlock(&_tag->cmux); + return; + } + + grpc::ServerContext ctx; + pthread_mutex_t cmux = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + CallState state = CREATE; + CallState entered_state = CREATE; + + public: + const char *name; +}; + +/* + * The UnaryRpcState class is used to track the execution of a Unary RPC. + * + * Template Args: + * Q - the request type for a given unary RPC + * S - the response type for a given unary RPC + */ +template class UnaryRpcState : public RpcStateBase +{ + public: + typedef void (frr::Northbound::AsyncService::*reqfunc_t)( + ::grpc::ServerContext *, Q *, + ::grpc::ServerAsyncResponseWriter *, + ::grpc::CompletionQueue *, ::grpc::ServerCompletionQueue *, + void *); + + UnaryRpcState(Candidates *cdb, reqfunc_t rfunc, + grpc::Status (*cb)(UnaryRpcState *), + const char *name) + : RpcStateBase(name), cdb(cdb), requestf(rfunc), callback(cb), + responder(&ctx){}; + + void do_request(::frr::Northbound::AsyncService *service, + ::grpc::ServerCompletionQueue *cq, + bool no_copy) override + { + grpc_debug("%s, posting a request for: %s", __func__, name); + auto copy = no_copy ? this + : new UnaryRpcState(cdb, requestf, callback, + name); + (service->*requestf)(©->ctx, ©->request, + ©->responder, cq, cq, copy); + } + + CallState run_mainthread(struct event *thread) override + { + // Unary RPC are always finished, see "Unary" :) + grpc::Status status = this->callback(this); + responder.Finish(response, status, this); + return FINISH; + } + + Candidates *cdb; + + Q request; + S response; + grpc::ServerAsyncResponseWriter responder; + + grpc::Status (*callback)(UnaryRpcState *); + reqfunc_t requestf = NULL; +}; + +/* + * The StreamRpcState class is used to track the execution of a Streaming RPC. + * + * Template Args: + * Q - the request type for a given streaming RPC + * S - the response type for a given streaming RPC + * X - the type used to track the streaming state + */ +template +class StreamRpcState : public RpcStateBase +{ + public: + typedef void (frr::Northbound::AsyncService::*reqsfunc_t)( + ::grpc::ServerContext *, Q *, ::grpc::ServerAsyncWriter *, + ::grpc::CompletionQueue *, ::grpc::ServerCompletionQueue *, + void *); + + StreamRpcState(reqsfunc_t rfunc, bool (*cb)(StreamRpcState *), + const char *name) + : RpcStateBase(name), requestsf(rfunc), callback(cb), + async_responder(&ctx){}; + + void do_request(::frr::Northbound::AsyncService *service, + ::grpc::ServerCompletionQueue *cq, + bool no_copy) override + { + grpc_debug("%s, posting a request for: %s", __func__, name); + auto copy = + no_copy ? this + : new StreamRpcState(requestsf, callback, name); + (service->*requestsf)(©->ctx, ©->request, + ©->async_responder, cq, cq, copy); + } + + CallState run_mainthread(struct event *thread) override + { + if (this->callback(this)) + return MORE; + else + return FINISH; + } + + Q request; + S response; + grpc::ServerAsyncWriter async_responder; + + bool (*callback)(StreamRpcState *); + reqsfunc_t requestsf = NULL; + + X context; +}; + +// ------------------------------------------------------ +// Utility Functions +// ------------------------------------------------------ + +static LYD_FORMAT encoding2lyd_format(enum frr::Encoding encoding) +{ + switch (encoding) { + case frr::JSON: + return LYD_JSON; + case frr::XML: + return LYD_XML; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown data encoding format (%u)", __func__, + encoding); + exit(1); + } +} + +static int yang_dnode_edit(struct lyd_node *dnode, const std::string &path, + const char *value) +{ + LY_ERR err = lyd_new_path(dnode, ly_native_ctx, path.c_str(), value, + LYD_NEW_PATH_UPDATE, &dnode); + if (err != LY_SUCCESS) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed: %s", + __func__, ly_errmsg(ly_native_ctx)); + return -1; + } + + return 0; +} + +static int yang_dnode_delete(struct lyd_node *dnode, const std::string &path) +{ + dnode = yang_dnode_get(dnode, path.c_str()); + if (!dnode) + return -1; + + lyd_free_tree(dnode); + + return 0; +} + +static LY_ERR data_tree_from_dnode(frr::DataTree *dt, + const struct lyd_node *dnode, + LYD_FORMAT lyd_format, bool with_defaults) +{ + char *strp; + int options = 0; + + SET_FLAG(options, LYD_PRINT_WITHSIBLINGS); + if (with_defaults) + SET_FLAG(options, LYD_PRINT_WD_ALL); + else + SET_FLAG(options, LYD_PRINT_WD_TRIM); + + LY_ERR err = lyd_print_mem(&strp, dnode, lyd_format, options); + if (err == LY_SUCCESS) { + if (strp) { + dt->set_data(strp); + free(strp); + } + } + return err; +} + +static struct lyd_node *dnode_from_data_tree(const frr::DataTree *dt, + bool config_only) +{ + struct lyd_node *dnode; + int options, opt2; + LY_ERR err; + + if (config_only) { + options = LYD_PARSE_NO_STATE; + opt2 = LYD_VALIDATE_NO_STATE; + } else { + options = LYD_PARSE_STRICT; + opt2 = 0; + } + + err = lyd_parse_data_mem(ly_native_ctx, dt->data().c_str(), + encoding2lyd_format(dt->encoding()), options, + opt2, &dnode); + if (err != LY_SUCCESS) { + flog_warn(EC_LIB_LIBYANG, "%s: lyd_parse_mem() failed: %s", + __func__, ly_errmsg(ly_native_ctx)); + } + return dnode; +} + +static struct lyd_node *get_dnode_config(const std::string &path) +{ + struct lyd_node *dnode; + + if (!yang_dnode_exists(running_config->dnode, + path.empty() ? NULL : path.c_str())) + return NULL; + + dnode = yang_dnode_get(running_config->dnode, + path.empty() ? NULL : path.c_str()); + if (dnode) + dnode = yang_dnode_dup(dnode); + + return dnode; +} + +static struct lyd_node *get_dnode_state(const std::string &path) +{ + struct lyd_node *dnode = NULL; + + (void)nb_oper_iterate_legacy(path.c_str(), NULL, 0, NULL, NULL, &dnode); + + return dnode; +} + +static grpc::Status get_path(frr::DataTree *dt, const std::string &path, + int type, LYD_FORMAT lyd_format, + bool with_defaults) +{ + struct lyd_node *dnode_config = NULL; + struct lyd_node *dnode_state = NULL; + struct lyd_node *dnode_final; + + // Configuration data. + if (type == frr::GetRequest_DataType_ALL + || type == frr::GetRequest_DataType_CONFIG) { + dnode_config = get_dnode_config(path); + if (!dnode_config) + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Data path not found"); + } + + // Operational data. + if (type == frr::GetRequest_DataType_ALL + || type == frr::GetRequest_DataType_STATE) { + dnode_state = get_dnode_state(path); + if (!dnode_state) { + if (dnode_config) + yang_dnode_free(dnode_config); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Failed to fetch operational data"); + } + } + + switch (type) { + case frr::GetRequest_DataType_ALL: + // + // Combine configuration and state data into a single + // dnode. + // + if (lyd_merge_siblings(&dnode_state, dnode_config, + LYD_MERGE_DESTRUCT) + != LY_SUCCESS) { + yang_dnode_free(dnode_state); + yang_dnode_free(dnode_config); + return grpc::Status( + grpc::StatusCode::INTERNAL, + "Failed to merge configuration and state data", + ly_errmsg(ly_native_ctx)); + } + + dnode_final = dnode_state; + break; + case frr::GetRequest_DataType_CONFIG: + dnode_final = dnode_config; + break; + case frr::GetRequest_DataType_STATE: + dnode_final = dnode_state; + break; + } + + // Validate data to create implicit default nodes if necessary. + int validate_opts = 0; + if (type == frr::GetRequest_DataType_CONFIG) + validate_opts = LYD_VALIDATE_NO_STATE; + else + validate_opts = 0; + + LY_ERR err = lyd_validate_all(&dnode_final, ly_native_ctx, + validate_opts, NULL); + + if (err) + flog_warn(EC_LIB_LIBYANG, "%s: lyd_validate_all() failed: %s", + __func__, ly_errmsg(ly_native_ctx)); + // Dump data using the requested format. + if (!err) + err = data_tree_from_dnode(dt, dnode_final, lyd_format, + with_defaults); + yang_dnode_free(dnode_final); + if (err) + return grpc::Status(grpc::StatusCode::INTERNAL, + "Failed to dump data"); + return grpc::Status::OK; +} + + +// ------------------------------------------------------ +// RPC Callback Functions: run on main thread +// ------------------------------------------------------ + +grpc::Status HandleUnaryGetCapabilities( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + // Response: string frr_version = 1; + tag->response.set_frr_version(FRR_VERSION); + + // Response: bool rollback_support = 2; +#ifdef HAVE_CONFIG_ROLLBACKS + tag->response.set_rollback_support(true); +#else + tag->response.set_rollback_support(false); +#endif + // Response: repeated ModuleData supported_modules = 3; + struct yang_module *module; + RB_FOREACH (module, yang_modules, &yang_modules) { + auto m = tag->response.add_supported_modules(); + + m->set_name(module->name); + if (module->info->revision) + m->set_revision(module->info->revision); + m->set_organization(module->info->org); + } + + // Response: repeated Encoding supported_encodings = 4; + tag->response.add_supported_encodings(frr::JSON); + tag->response.add_supported_encodings(frr::XML); + + return grpc::Status::OK; +} + +// Define the context variable type for this streaming handler +typedef std::list GetContextType; + +bool HandleStreamingGet( + StreamRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + auto mypathps = &tag->context; + if (tag->is_initial_process()) { + // Fill our context container first time through + grpc_debug("%s: initialize streaming state", __func__); + auto paths = tag->request.path(); + for (const std::string &path : paths) { + mypathps->push_back(std::string(path)); + } + } + + // Request: DataType type = 1; + int type = tag->request.type(); + // Request: Encoding encoding = 2; + frr::Encoding encoding = tag->request.encoding(); + // Request: bool with_defaults = 3; + bool with_defaults = tag->request.with_defaults(); + + if (mypathps->empty()) { + tag->async_responder.Finish(grpc::Status::OK, tag); + return false; + } + + frr::GetResponse response; + grpc::Status status; + + // Response: int64 timestamp = 1; + response.set_timestamp(time(NULL)); + + // Response: DataTree data = 2; + auto *data = response.mutable_data(); + data->set_encoding(tag->request.encoding()); + status = get_path(data, mypathps->back().c_str(), type, + encoding2lyd_format(encoding), with_defaults); + + if (!status.ok()) { + tag->async_responder.WriteAndFinish( + response, grpc::WriteOptions(), status, tag); + return false; + } + + mypathps->pop_back(); + if (mypathps->empty()) { + tag->async_responder.WriteAndFinish( + response, grpc::WriteOptions(), grpc::Status::OK, tag); + return false; + } else { + tag->async_responder.Write(response, tag); + return true; + } +} + +grpc::Status HandleUnaryCreateCandidate( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + struct candidate *candidate = tag->cdb->create_candidate(); + if (!candidate) + return grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED, + "Can't create candidate configuration"); + tag->response.set_candidate_id(candidate->id); + return grpc::Status::OK; +} + +grpc::Status HandleUnaryDeleteCandidate( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + uint32_t candidate_id = tag->request.candidate_id(); + + grpc_debug("%s(candidate_id: %u)", __func__, candidate_id); + + if (!tag->cdb->contains(candidate_id)) + return grpc::Status(grpc::StatusCode::NOT_FOUND, + "candidate configuration not found"); + tag->cdb->delete_candidate(candidate_id); + return grpc::Status::OK; +} + +grpc::Status HandleUnaryUpdateCandidate( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + uint32_t candidate_id = tag->request.candidate_id(); + + grpc_debug("%s(candidate_id: %u)", __func__, candidate_id); + + struct candidate *candidate = tag->cdb->get_candidate(candidate_id); + + if (!candidate) + return grpc::Status(grpc::StatusCode::NOT_FOUND, + "candidate configuration not found"); + if (candidate->transaction) + return grpc::Status( + grpc::StatusCode::FAILED_PRECONDITION, + "candidate is in the middle of a transaction"); + if (nb_candidate_update(candidate->config) != NB_OK) + return grpc::Status(grpc::StatusCode::INTERNAL, + "failed to update candidate configuration"); + + return grpc::Status::OK; +} + +grpc::Status HandleUnaryEditCandidate( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + uint32_t candidate_id = tag->request.candidate_id(); + + grpc_debug("%s(candidate_id: %u)", __func__, candidate_id); + + struct candidate *candidate = tag->cdb->get_candidate(candidate_id); + if (!candidate) + return grpc::Status(grpc::StatusCode::NOT_FOUND, + "candidate configuration not found"); + + struct nb_config *candidate_tmp = nb_config_dup(candidate->config); + + auto pvs = tag->request.update(); + for (const frr::PathValue &pv : pvs) { + if (yang_dnode_edit(candidate_tmp->dnode, pv.path(), + pv.value().c_str()) != 0) { + nb_config_free(candidate_tmp); + + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Failed to update \"" + pv.path() + + "\""); + } + } + + pvs = tag->request.delete_(); + for (const frr::PathValue &pv : pvs) { + if (yang_dnode_delete(candidate_tmp->dnode, pv.path()) != 0) { + nb_config_free(candidate_tmp); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Failed to remove \"" + pv.path() + + "\""); + } + } + + // No errors, accept all changes. + nb_config_replace(candidate->config, candidate_tmp, false); + return grpc::Status::OK; +} + +grpc::Status HandleUnaryLoadToCandidate( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + uint32_t candidate_id = tag->request.candidate_id(); + + grpc_debug("%s(candidate_id: %u)", __func__, candidate_id); + + // Request: LoadType type = 2; + int load_type = tag->request.type(); + // Request: DataTree config = 3; + auto config = tag->request.config(); + + struct candidate *candidate = tag->cdb->get_candidate(candidate_id); + if (!candidate) + return grpc::Status(grpc::StatusCode::NOT_FOUND, + "candidate configuration not found"); + + struct lyd_node *dnode = dnode_from_data_tree(&config, true); + if (!dnode) + return grpc::Status(grpc::StatusCode::INTERNAL, + "Failed to parse the configuration"); + + struct nb_config *loaded_config = nb_config_new(dnode); + if (load_type == frr::LoadToCandidateRequest::REPLACE) + nb_config_replace(candidate->config, loaded_config, false); + else if (nb_config_merge(candidate->config, loaded_config, false) != + NB_OK) + return grpc::Status(grpc::StatusCode::INTERNAL, + "Failed to merge the loaded configuration"); + + return grpc::Status::OK; +} + +grpc::Status +HandleUnaryCommit(UnaryRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + // Request: uint32 candidate_id = 1; + uint32_t candidate_id = tag->request.candidate_id(); + + grpc_debug("%s(candidate_id: %u)", __func__, candidate_id); + + // Request: Phase phase = 2; + int phase = tag->request.phase(); + // Request: string comment = 3; + const std::string comment = tag->request.comment(); + + // Find candidate configuration. + struct candidate *candidate = tag->cdb->get_candidate(candidate_id); + if (!candidate) + return grpc::Status(grpc::StatusCode::NOT_FOUND, + "candidate configuration not found"); + + int ret = NB_OK; + uint32_t transaction_id = 0; + + // Check for misuse of the two-phase commit protocol. + switch (phase) { + case frr::CommitRequest::PREPARE: + case frr::CommitRequest::ALL: + if (candidate->transaction) + return grpc::Status( + grpc::StatusCode::FAILED_PRECONDITION, + "candidate is in the middle of a transaction"); + break; + case frr::CommitRequest::ABORT: + case frr::CommitRequest::APPLY: + if (!candidate->transaction) + return grpc::Status( + grpc::StatusCode::FAILED_PRECONDITION, + "no transaction in progress"); + break; + default: + break; + } + + + // Execute the user request. + struct nb_context context = {}; + context.client = NB_CLIENT_GRPC; + char errmsg[BUFSIZ] = {0}; + + switch (phase) { + case frr::CommitRequest::VALIDATE: + grpc_debug("`-> Performing VALIDATE"); + ret = nb_candidate_validate(&context, candidate->config, errmsg, + sizeof(errmsg)); + break; + case frr::CommitRequest::PREPARE: + grpc_debug("`-> Performing PREPARE"); + ret = nb_candidate_commit_prepare( + context, candidate->config, comment.c_str(), + &candidate->transaction, false, false, errmsg, + sizeof(errmsg)); + break; + case frr::CommitRequest::ABORT: + grpc_debug("`-> Performing ABORT"); + nb_candidate_commit_abort(candidate->transaction, errmsg, + sizeof(errmsg)); + break; + case frr::CommitRequest::APPLY: + grpc_debug("`-> Performing APPLY"); + nb_candidate_commit_apply(candidate->transaction, true, + &transaction_id, errmsg, + sizeof(errmsg)); + break; + case frr::CommitRequest::ALL: + grpc_debug("`-> Performing ALL"); + ret = nb_candidate_commit(context, candidate->config, true, + comment.c_str(), &transaction_id, + errmsg, sizeof(errmsg)); + break; + } + + // Map northbound error codes to gRPC status codes. + grpc::Status status; + switch (ret) { + case NB_OK: + status = grpc::Status::OK; + break; + case NB_ERR_NO_CHANGES: + status = grpc::Status(grpc::StatusCode::ABORTED, errmsg); + break; + case NB_ERR_LOCKED: + status = grpc::Status(grpc::StatusCode::UNAVAILABLE, errmsg); + break; + case NB_ERR_VALIDATION: + status = grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + errmsg); + break; + case NB_ERR_RESOURCE: + status = grpc::Status(grpc::StatusCode::RESOURCE_EXHAUSTED, + errmsg); + break; + case NB_ERR: + default: + status = grpc::Status(grpc::StatusCode::INTERNAL, errmsg); + break; + } + + grpc_debug("`-> Result: %s (message: '%s')", + nb_err_name((enum nb_error)ret), errmsg); + + if (ret == NB_OK) { + // Response: uint32 transaction_id = 1; + if (transaction_id) + tag->response.set_transaction_id(transaction_id); + } + if (strlen(errmsg) > 0) + tag->response.set_error_message(errmsg); + + return status; +} + +grpc::Status HandleUnaryLockConfig( + UnaryRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + if (nb_running_lock(NB_CLIENT_GRPC, NULL)) + return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, + "running configuration is locked already"); + return grpc::Status::OK; +} + +grpc::Status HandleUnaryUnlockConfig( + UnaryRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + if (nb_running_unlock(NB_CLIENT_GRPC, NULL)) + return grpc::Status( + grpc::StatusCode::FAILED_PRECONDITION, + "failed to unlock the running configuration"); + return grpc::Status::OK; +} + +static void list_transactions_cb(void *arg, int transaction_id, + const char *client_name, const char *date, + const char *comment) +{ + auto list = static_cast> *>(arg); + list->push_back( + std::make_tuple(transaction_id, std::string(client_name), + std::string(date), std::string(comment))); +} + +// Define the context variable type for this streaming handler +typedef std::list> + ListTransactionsContextType; + +bool HandleStreamingListTransactions( + StreamRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + auto list = &tag->context; + if (tag->is_initial_process()) { + grpc_debug("%s: initialize streaming state", __func__); + // Fill our context container first time through + nb_db_transactions_iterate(list_transactions_cb, list); + list->push_back(std::make_tuple( + 0xFFFF, std::string("fake client"), + std::string("fake date"), std::string("fake comment"))); + list->push_back(std::make_tuple(0xFFFE, + std::string("fake client2"), + std::string("fake date"), + std::string("fake comment2"))); + } + + if (list->empty()) { + tag->async_responder.Finish(grpc::Status::OK, tag); + return false; + } + + auto item = list->back(); + + frr::ListTransactionsResponse response; + + // Response: uint32 id = 1; + response.set_id(std::get<0>(item)); + + // Response: string client = 2; + response.set_client(std::get<1>(item).c_str()); + + // Response: string date = 3; + response.set_date(std::get<2>(item).c_str()); + + // Response: string comment = 4; + response.set_comment(std::get<3>(item).c_str()); + + list->pop_back(); + if (list->empty()) { + tag->async_responder.WriteAndFinish( + response, grpc::WriteOptions(), grpc::Status::OK, tag); + return false; + } else { + tag->async_responder.Write(response, tag); + return true; + } +} + +grpc::Status HandleUnaryGetTransaction( + UnaryRpcState + *tag) +{ + grpc_debug("%s: entered", __func__); + + // Request: uint32 transaction_id = 1; + uint32_t transaction_id = tag->request.transaction_id(); + // Request: Encoding encoding = 2; + frr::Encoding encoding = tag->request.encoding(); + // Request: bool with_defaults = 3; + bool with_defaults = tag->request.with_defaults(); + + grpc_debug("%s(transaction_id: %u, encoding: %u)", __func__, + transaction_id, encoding); + + struct nb_config *nb_config; + + // Load configuration from the transactions database. + nb_config = nb_db_transaction_load(transaction_id); + if (!nb_config) + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Transaction not found"); + + // Response: DataTree config = 1; + auto config = tag->response.mutable_config(); + config->set_encoding(encoding); + + // Dump data using the requested format. + if (data_tree_from_dnode(config, nb_config->dnode, + encoding2lyd_format(encoding), with_defaults) + != 0) { + nb_config_free(nb_config); + return grpc::Status(grpc::StatusCode::INTERNAL, + "Failed to dump data"); + } + + nb_config_free(nb_config); + + return grpc::Status::OK; +} + +grpc::Status HandleUnaryExecute( + UnaryRpcState *tag) +{ + grpc_debug("%s: entered", __func__); + + struct nb_node *nb_node; + struct lyd_node *input_tree, *output_tree, *child; + const char *xpath; + char errmsg[BUFSIZ] = {0}; + char path[XPATH_MAXLEN]; + LY_ERR err; + + // Request: string path = 1; + xpath = tag->request.path().c_str(); + + grpc_debug("%s(path: \"%s\")", __func__, xpath); + + if (tag->request.path().empty()) + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Data path is empty"); + + nb_node = nb_node_find(xpath); + if (!nb_node) + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Unknown data path"); + + // Create input data tree. + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, + (LYD_ANYDATA_VALUETYPE)0, 0, NULL, &input_tree); + if (err != LY_SUCCESS) { + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid data path"); + } + + // Read input parameters. + auto input = tag->request.input(); + for (const frr::PathValue &pv : input) { + // Request: repeated PathValue input = 2; + err = lyd_new_path(input_tree, ly_native_ctx, pv.path().c_str(), + pv.value().c_str(), 0, NULL); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid input data"); + } + } + + // Validate input data. + err = lyd_validate_op(input_tree, NULL, LYD_TYPE_RPC_YANG, NULL); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid input data"); + } + + // Create output data tree. + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, + (LYD_ANYDATA_VALUETYPE)0, 0, NULL, &output_tree); + if (err != LY_SUCCESS) { + lyd_free_tree(input_tree); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, + "Invalid data path"); + } + + // Execute callback registered for this XPath. + if (nb_callback_rpc(nb_node, xpath, input_tree, output_tree, errmsg, + sizeof(errmsg)) != NB_OK) { + flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", + __func__, xpath); + lyd_free_tree(input_tree); + lyd_free_tree(output_tree); + + return grpc::Status(grpc::StatusCode::INTERNAL, "RPC failed"); + } + + // Process output parameters. + LY_LIST_FOR (lyd_child(output_tree), child) { + // Response: repeated PathValue output = 1; + frr::PathValue *pv = tag->response.add_output(); + pv->set_path(lyd_path(child, LYD_PATH_STD, path, sizeof(path))); + pv->set_value(yang_dnode_get_string(child, NULL)); + } + + // Release memory. + lyd_free_tree(input_tree); + lyd_free_tree(output_tree); + + return grpc::Status::OK; +} + +// ------------------------------------------------------ +// Thread Initialization and Run Functions +// ------------------------------------------------------ + + +#define REQUEST_NEWRPC(NAME, cdb) \ + do { \ + auto _rpcState = new UnaryRpcState( \ + (cdb), &frr::Northbound::AsyncService::Request##NAME, \ + &HandleUnary##NAME, #NAME); \ + _rpcState->do_request(&service, cq.get(), true); \ + } while (0) + +#define REQUEST_NEWRPC_STREAMING(NAME) \ + do { \ + auto _rpcState = new StreamRpcState( \ + &frr::Northbound::AsyncService::Request##NAME, \ + &HandleStreaming##NAME, #NAME); \ + _rpcState->do_request(&service, cq.get(), true); \ + } while (0) + +struct grpc_pthread_attr { + struct frr_pthread_attr attr; + unsigned long port; +}; + +// Capture these objects so we can try to shut down cleanly +static pthread_mutex_t s_server_lock = PTHREAD_MUTEX_INITIALIZER; +static grpc::Server *s_server; + +static void *grpc_pthread_start(void *arg) +{ + struct frr_pthread *fpt = static_cast(arg); + uint port = (uint) reinterpret_cast(fpt->data); + + Candidates candidates; + grpc::ServerBuilder builder; + std::stringstream server_address; + frr::Northbound::AsyncService service; + + frr_pthread_set_name(fpt); + + server_address << "0.0.0.0:" << port; + builder.AddListeningPort(server_address.str(), + grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + builder.AddChannelArgument( + GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS, 5000); + std::unique_ptr cq = + builder.AddCompletionQueue(); + std::unique_ptr server = builder.BuildAndStart(); + s_server = server.get(); + + pthread_mutex_lock(&s_server_lock); // Make coverity happy + grpc_running = true; + pthread_mutex_unlock(&s_server_lock); // Make coverity happy + + /* Schedule unary RPC handlers */ + REQUEST_NEWRPC(GetCapabilities, NULL); + REQUEST_NEWRPC(CreateCandidate, &candidates); + REQUEST_NEWRPC(DeleteCandidate, &candidates); + REQUEST_NEWRPC(UpdateCandidate, &candidates); + REQUEST_NEWRPC(EditCandidate, &candidates); + REQUEST_NEWRPC(LoadToCandidate, &candidates); + REQUEST_NEWRPC(Commit, &candidates); + REQUEST_NEWRPC(GetTransaction, NULL); + REQUEST_NEWRPC(LockConfig, NULL); + REQUEST_NEWRPC(UnlockConfig, NULL); + REQUEST_NEWRPC(Execute, NULL); + + /* Schedule streaming RPC handlers */ + REQUEST_NEWRPC_STREAMING(Get); + REQUEST_NEWRPC_STREAMING(ListTransactions); + + zlog_notice("gRPC server listening on %s", + server_address.str().c_str()); + + /* Process inbound RPCs */ + bool ok; + void *tag; + while (true) { + if (!cq->Next(&tag, &ok)) { + grpc_debug("%s: CQ empty exiting", __func__); + break; + } + + grpc_debug("%s: got next from CQ tag: %p ok: %d", __func__, tag, + ok); + + if (!ok) { + delete static_cast(tag); + break; + } + + RpcStateBase *rpc = static_cast(tag); + if (rpc->get_state() != FINISH) + rpc->run(&service, cq.get()); + else { + grpc_debug("%s RPC FINISH -> [delete]", rpc->name); + delete rpc; + } + } + + /* This was probably done for us to get here, but let's be safe */ + pthread_mutex_lock(&s_server_lock); + grpc_running = false; + if (s_server) { + grpc_debug("%s: shutdown server and CQ", __func__); + server->Shutdown(); + s_server = NULL; + } + pthread_mutex_unlock(&s_server_lock); + + grpc_debug("%s: shutting down CQ", __func__); + cq->Shutdown(); + + grpc_debug("%s: draining the CQ", __func__); + while (cq->Next(&tag, &ok)) { + grpc_debug("%s: drain tag %p", __func__, tag); + delete static_cast(tag); + } + + zlog_info("%s: exiting from grpc pthread", __func__); + return NULL; +} + + +static int frr_grpc_init(uint port) +{ + struct frr_pthread_attr attr = { + .start = grpc_pthread_start, + .stop = NULL, + }; + + grpc_debug("%s: entered", __func__); + + fpt = frr_pthread_new(&attr, "frr-grpc", "frr-grpc"); + fpt->data = reinterpret_cast((intptr_t)port); + + /* Create a pthread for gRPC since it runs its own event loop. */ + if (frr_pthread_run(fpt, NULL) < 0) { + flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s", + __func__, safe_strerror(errno)); + return -1; + } + + return 0; +} + +static int frr_grpc_finish(void) +{ + grpc_debug("%s: entered", __func__); + + if (!fpt) + return 0; + + /* + * Shut the server down here in main thread. This will cause the wait on + * the completion queue (cq.Next()) to exit and cleanup everything else. + */ + pthread_mutex_lock(&s_server_lock); + grpc_running = false; + if (s_server) { + grpc_debug("%s: shutdown server", __func__); + s_server->Shutdown(); + s_server = NULL; + } + pthread_mutex_unlock(&s_server_lock); + + grpc_debug("%s: joining and destroy grpc thread", __func__); + pthread_join(fpt->thread, NULL); + frr_pthread_destroy(fpt); + + // Fix protobuf 'memory leaks' during shutdown. + // https://groups.google.com/g/protobuf/c/4y_EmQiCGgs + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} + +/* + * This is done this way because module_init and module_late_init are both + * called during daemon pre-fork initialization. Because the GRPC library + * spawns threads internally, we need to delay initializing it until after + * fork. This is done by scheduling this init function as an event task, since + * the event loop doesn't run until after fork. + */ +static void frr_grpc_module_very_late_init(struct event *thread) +{ + const char *args = THIS_MODULE->load_args; + uint port = GRPC_DEFAULT_PORT; + + if (args) { + port = std::stoul(args); + if (port < 1024 || port > UINT16_MAX) { + flog_err(EC_LIB_GRPC_INIT, + "%s: port number must be between 1025 and %d", + __func__, UINT16_MAX); + goto error; + } + } + + if (frr_grpc_init(port) < 0) + goto error; + + return; + +error: + flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module"); +} + +static int frr_grpc_module_late_init(struct event_loop *tm) +{ + main_master = tm; + hook_register(frr_fini, frr_grpc_finish); + event_add_event(tm, frr_grpc_module_very_late_init, NULL, 0, NULL); + return 0; +} + +static int frr_grpc_module_init(void) +{ + hook_register(frr_late_init, frr_grpc_module_late_init); + + return 0; +} + +FRR_MODULE_SETUP(.name = "frr_grpc", .version = FRR_VERSION, + .description = "FRR gRPC northbound module", + .init = frr_grpc_module_init, ); diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c new file mode 100644 index 0000000..5f38c97 --- /dev/null +++ b/lib/northbound_oper.c @@ -0,0 +1,1826 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * October 14 2023, Christian Hopps + * + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ + +#include +#include "darr.h" +#include "debug.h" +#include "frrevent.h" +#include "frrstr.h" +#include "lib_errors.h" +#include "monotime.h" +#include "northbound.h" + +/* + * YANG model yielding design restrictions: + * + * In order to be able to yield and guarantee we have a valid data tree at the + * point of yielding we must know that each parent has all it's siblings + * collected to represent a complete element. + * + * Basically, there should be a only single branch in the schema tree that + * supports yielding. In practice this means: + * + * list node schema with lookup next: + * - must not have any lookup-next list node sibling schema + * - must not have any list or container node siblings with lookup-next descendants. + * - any parent list nodes must also be lookup-next list nodes + * + * We must also process containers with lookup-next descendants last. + */ + +DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State"); +DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos"); + +/* Amount of time allowed to spend constructing oper-state prior to yielding */ +#define NB_OP_WALK_INTERVAL_MS 50 +#define NB_OP_WALK_INTERVAL_US (NB_OP_WALK_INTERVAL_MS * 1000) + +/* ---------- */ +/* Data Types */ +/* ---------- */ +PREDECL_LIST(nb_op_walks); + +/* + * This is our information about a node on the branch we are looking at + */ +struct nb_op_node_info { + struct lyd_node *inner; + const struct lysc_node *schema; /* inner schema in case we rm inner */ + struct yang_list_keys keys; /* if list, keys to locate element */ + const void *list_entry; /* opaque entry from user or NULL */ + uint xpath_len; /* length of the xpath string for this node */ + uint niters; /* # list elems create this iteration */ + uint nents; /* # list elems create so far */ + bool query_specific_entry : 1; /* this info is specific specified */ + bool has_lookup_next : 1; /* if this node support lookup next */ + bool lookup_next_ok : 1; /* if this and all previous support */ +}; + +/** + * struct nb_op_yield_state - tracking required state for yielding. + * + * @xpath: current xpath representing the node_info stack. + * @xpath_orig: the original query string from the user + * @node_infos: the container stack for the walk from root to current + * @schema_path: the schema nodes along the path indicated by the query string. + * this will include the choice and case nodes which are not + * present in the query string. + * @query_tokstr: the query string tokenized with NUL bytes. + * @query_tokens: the string pointers to each query token (node). + * @non_specific_predicate: tracks if a query_token is non-specific predicate. + * @walk_root_level: The topmost specific node, +1 is where we start walking. + * @walk_start_level: @walk_root_level + 1. + * @query_base_level: the level the query string stops at and full walks + * commence below that. + */ +struct nb_op_yield_state { + /* Walking state */ + char *xpath; + char *xpath_orig; + struct nb_op_node_info *node_infos; + const struct lysc_node **schema_path; + char *query_tokstr; + char **query_tokens; + uint8_t *non_specific_predicate; + int walk_root_level; + int walk_start_level; + int query_base_level; + bool query_list_entry; /* XXX query was for a specific list entry */ + + /* Yielding state */ + bool query_did_entry; /* currently processing the entry */ + bool should_batch; + struct timeval start_time; + struct yang_translator *translator; + uint32_t flags; + nb_oper_data_cb cb; + void *cb_arg; + nb_oper_data_finish_cb finish; + void *finish_arg; + struct event *walk_ev; + struct nb_op_walks_item link; +}; + +DECLARE_LIST(nb_op_walks, struct nb_op_yield_state, link); + +/* ---------------- */ +/* Global Variables */ +/* ---------------- */ + +static struct event_loop *event_loop; +static struct nb_op_walks_head nb_op_walks; + +/* --------------------- */ +/* Function Declarations */ +/* --------------------- */ + +static enum nb_error nb_op_yield(struct nb_op_yield_state *ys); +static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys); + +/* -------------------- */ +/* Function Definitions */ +/* -------------------- */ + +static inline struct nb_op_yield_state * +nb_op_create_yield_state(const char *xpath, struct yang_translator *translator, + uint32_t flags, bool should_batch, nb_oper_data_cb cb, + void *cb_arg, nb_oper_data_finish_cb finish, + void *finish_arg) +{ + struct nb_op_yield_state *ys; + + ys = XCALLOC(MTYPE_NB_YIELD_STATE, sizeof(*ys)); + ys->xpath = darr_strdup_cap(xpath, (size_t)XPATH_MAXLEN); + ys->xpath_orig = darr_strdup(xpath); + ys->translator = translator; + ys->flags = flags; + ys->should_batch = should_batch; + ys->cb = cb; + ys->cb_arg = cb_arg; + ys->finish = finish; + ys->finish_arg = finish_arg; + + nb_op_walks_add_tail(&nb_op_walks, ys); + + return ys; +} + +static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys, + bool nofree_tree) +{ + if (ys) { + EVENT_OFF(ys->walk_ev); + nb_op_walks_del(&nb_op_walks, ys); + /* if we have a branch then free up it's libyang tree */ + if (!nofree_tree && ys_root_node(ys)) + lyd_free_all(ys_root_node(ys)); + darr_free(ys->query_tokens); + darr_free(ys->non_specific_predicate); + darr_free(ys->query_tokstr); + darr_free(ys->schema_path); + darr_free(ys->node_infos); + darr_free(ys->xpath_orig); + darr_free(ys->xpath); + XFREE(MTYPE_NB_YIELD_STATE, ys); + } +} + +static const struct lysc_node *ys_get_walk_stem_tip(struct nb_op_yield_state *ys) +{ + if (ys->walk_start_level <= 0) + return NULL; + return ys->node_infos[ys->walk_start_level - 1].schema; +} + +static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys) +{ + if (!darr_len(ys->node_infos)) + return NULL; + return ys->node_infos[0].inner; +} + +static void ys_trim_xpath(struct nb_op_yield_state *ys) +{ + uint len = darr_len(ys->node_infos); + + if (len == 0) + darr_setlen(ys->xpath, 1); + else + darr_setlen(ys->xpath, darr_last(ys->node_infos)->xpath_len + 1); + ys->xpath[darr_len(ys->xpath) - 1] = 0; +} + +static void ys_pop_inner(struct nb_op_yield_state *ys) +{ + uint len = darr_len(ys->node_infos); + + assert(len); + darr_setlen(ys->node_infos, len - 1); + ys_trim_xpath(ys); +} + +static void ys_free_inner(struct nb_op_yield_state *ys, + struct nb_op_node_info *ni) +{ + if (!CHECK_FLAG(ni->schema->nodetype, LYS_CASE | LYS_CHOICE)) + lyd_free_tree(ni->inner); + ni->inner = NULL; +} + +static void nb_op_get_keys(struct lyd_node_inner *list_node, + struct yang_list_keys *keys) +{ + struct lyd_node *child; + uint n = 0; + + keys->num = 0; + LY_LIST_FOR (list_node->child, child) { + if (!lysc_is_key(child->schema)) + break; + strlcpy(keys->key[n], yang_dnode_get_string(child, NULL), + sizeof(keys->key[n])); + n++; + } + + keys->num = n; +} + +/** + * __move_back_to_next() - move back to the next lookup-next schema + */ +static bool __move_back_to_next(struct nb_op_yield_state *ys, int i) +{ + struct nb_op_node_info *ni; + int j; + + /* + * We will free the subtree we are trimming back to, or we will be done + * with the walk and will free the root on cleanup. + */ + + /* pop any node_info we dropped below on entry */ + for (j = darr_ilen(ys->node_infos) - 1; j > i; j--) + ys_pop_inner(ys); + + for (; i >= ys->walk_root_level; i--) { + if (ys->node_infos[i].has_lookup_next) + break; + ys_pop_inner(ys); + } + + if (i < ys->walk_root_level) + return false; + + ni = &ys->node_infos[i]; + + /* + * The i'th node has been lost after a yield so trim it from the tree + * now. + */ + ys_free_inner(ys, ni); + ni->list_entry = NULL; + + /* + * Leave the empty-of-data node_info on top, __walk will deal with + * this, by doing a lookup-next with the keys which we still have. + */ + + return true; +} + +static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + struct nb_node *nn; + const void *parent_entry; + const void *list_entry; + uint i; + + /* + * IMPORTANT: On yielding: we always yield during list iteration and + * after the initial list element has been created and handled, so the + * top of the yield stack will always point at a list node. + * + * Additionally, that list node has been processed and was in the + * process of being "get_next"d when we yielded. We process the + * lookup-next list node last so all the rest of the data (to the left) + * has been gotten. NOTE: To keep this simple we will require only a + * single lookup-next sibling in any parents list of children. + * + * Walk the rightmost branch (the node info stack) from base to tip + * verifying all list nodes are still present. If not we backup to the + * node which has a lookup next, and we prune the branch to this node. + * If the list node that went away is the topmost we will be using + * lookup_next, but if it's a parent then the list_entry will have been + * restored. + */ + darr_foreach_i (ys->node_infos, i) { + ni = &ys->node_infos[i]; + nn = ni->schema->priv; + + if (!CHECK_FLAG(ni->schema->nodetype, LYS_LIST)) + continue; + + assert(ni->list_entry != NULL || + ni == darr_last(ys->node_infos)); + + /* Verify the entry is still present */ + parent_entry = (i == 0 ? NULL : ni[-1].list_entry); + list_entry = nb_callback_lookup_entry(nn, parent_entry, + &ni->keys); + if (!list_entry || list_entry != ni->list_entry) { + /* May be NULL or a different pointer + * move back to first of + * container with last lookup_next list node + * (which may be this one) and get next. + */ + if (!__move_back_to_next(ys, i)) + DEBUGD(&nb_dbg_events, + "%s: Nothing to resume after delete during walk (yield)", + __func__); + return; + } + } +} + +/* + * Can only yield if all list nodes to root have lookup_next() callbacks + * + * In order to support lookup_next() the list_node get_next() callback + * needs to return ordered (i.e., sorted) results. + */ + +/* ======================= */ +/* Start of walk init code */ +/* ======================= */ + +/** + * nb_op_xpath_to_trunk() - generate a lyd_node tree (trunk) using an xpath. + * @xpath_in: xpath query string to build trunk from. + * @dnode: resulting tree (trunk) + * + * Use the longest prefix of @xpath_in as possible to resolve to a tree (trunk). + * This is logically as if we walked along the xpath string resolving each + * nodename reference (in particular list nodes) until we could not. + * + * Return: error if any, if no error then @dnode contains the tree (trunk). + */ +static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in, + struct lyd_node **trunk) +{ + char *xpath = NULL; + enum nb_error ret = NB_OK; + LY_ERR err; + + darr_in_strdup(xpath, xpath_in); + for (;;) { + err = lyd_new_path2(NULL, ly_native_ctx, xpath, NULL, 0, 0, + LYD_NEW_PATH_UPDATE, NULL, trunk); + if (err == LY_SUCCESS) + break; + + ret = yang_xpath_pop_node(xpath); + if (ret != NB_OK) + break; + } + darr_free(xpath); + return ret; +} + +/* + * Finish initializing the node info based on the xpath string, and previous + * node_infos on the stack. If this node is a list node, obtain the specific + * list-entry object. + */ +static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, + uint index) +{ + struct nb_op_node_info *ni = &ys->node_infos[index]; + struct lyd_node *inner = ni->inner; + struct nb_node *nn = ni->schema->priv; + bool yield_ok = ys->finish != NULL; + + ni->has_lookup_next = nn->cbs.lookup_next != NULL; + + /* track the last list_entry until updated by new list node */ + ni->list_entry = index == 0 ? NULL : ni[-1].list_entry; + + /* Assert that we are walking the rightmost branch */ + assert(!inner->parent || inner == inner->parent->child->prev); + + if (CHECK_FLAG(inner->schema->nodetype, + LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) { + /* containers have only zero or one child on a branch of a tree */ + inner = ((struct lyd_node_inner *)inner)->child; + assert(!inner || inner->prev == inner); + ni->lookup_next_ok = yield_ok && + (index == 0 || ni[-1].lookup_next_ok); + return NB_OK; + } + + assert(CHECK_FLAG(inner->schema->nodetype, LYS_LIST)); + + ni->lookup_next_ok = yield_ok && ni->has_lookup_next && + (index == 0 || ni[-1].lookup_next_ok); + + nb_op_get_keys((struct lyd_node_inner *)inner, &ni->keys); + + /* A list entry cannot be present in a tree w/o it's keys */ + assert(ni->keys.num == yang_snode_num_keys(inner->schema)); + + /* + * Get this nodes opaque list_entry object + */ + + if (!nn->cbs.lookup_entry) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: data path doesn't support iteration over operational data: %s", + __func__, ys->xpath); + return NB_ERR_NOT_FOUND; + } + + /* ni->list_entry starts as the parent entry of this node */ + ni->list_entry = nb_callback_lookup_entry(nn, ni->list_entry, &ni->keys); + if (ni->list_entry == NULL) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: list entry lookup failed", __func__); + return NB_ERR_NOT_FOUND; + } + + /* + * By definition any list element we can get a specific list_entry for + * is specific. + */ + ni->query_specific_entry = true; + + return NB_OK; +} + +/** + * nb_op_ys_init_node_infos() - initialize the node info stack from the query. + * @ys: the yield state for this tree walk. + * + * On starting a walk we initialize the node_info stack as deeply as possible + * based on specific node references in the query string. We will stop at the + * point in the query string that is not specific (e.g., a list element without + * it's keys predicate) + * + * Return: northbound return value (enum nb_error) + */ +static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + struct lyd_node *inner; + struct lyd_node *node = NULL; + enum nb_error ret; + uint i, len; + char *tmp; + + /* + * Obtain the trunk of the data node tree of the query. + * + * These are the nodes from the root that could be specifically + * identified with the query string. The trunk ends when a no specific + * node could be identified (e.g., a list-node name with no keys). + */ + + ret = nb_op_xpath_to_trunk(ys->xpath, &node); + if (ret || !node) { + flog_warn(EC_LIB_LIBYANG, + "%s: can't instantiate concrete path using xpath: %s", + __func__, ys->xpath); + if (!ret) + ret = NB_ERR_NOT_FOUND; + return ret; + } + + /* Move up to the container if on a leaf currently. */ + if (node && + !CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)) { + struct lyd_node *leaf = node; + + node = &node->parent->node; + + /* + * If the leaf is not a key, delete it, because it has a wrong + * empty value. + */ + if (!lysc_is_key(leaf->schema)) + lyd_free_tree(leaf); + } + assert(!node || + CHECK_FLAG(node->schema->nodetype, LYS_CONTAINER | LYS_LIST)); + if (!node) + return NB_ERR_NOT_FOUND; + + inner = node; + for (len = 1; inner->parent; len++) + inner = &inner->parent->node; + + darr_append_nz_mt(ys->node_infos, len, MTYPE_NB_NODE_INFOS); + + /* + * For each node find the prefix of the xpath query that identified it + * -- save the prefix length. + */ + inner = node; + for (i = len; i > 0; i--, inner = &inner->parent->node) { + ni = &ys->node_infos[i - 1]; + ni->inner = inner; + ni->schema = inner->schema; + /* + * NOTE: we could build this by hand with a litte more effort, + * but this simple implementation works and won't be expensive + * since the number of nodes is small and only done once per + * query. + */ + tmp = yang_dnode_get_path(inner, NULL, 0); + ni->xpath_len = strlen(tmp); + + /* Replace users supplied xpath with the libyang returned value */ + if (i == len) + darr_in_strdup(ys->xpath, tmp); + + /* The prefix must match the prefix of the stored xpath */ + assert(!strncmp(tmp, ys->xpath, ni->xpath_len)); + free(tmp); + } + + /* + * Obtain the specific list-entry objects for each list node on the + * trunk and finish initializing the node_info structs. + */ + + darr_foreach_i (ys->node_infos, i) { + ret = nb_op_ys_finalize_node_info(ys, i); + if (ret != NB_OK) { + if (ys->node_infos[0].inner) + lyd_free_all(ys->node_infos[0].inner); + darr_free(ys->node_infos); + return ret; + } + } + + ys->walk_start_level = darr_len(ys->node_infos); + + ys->walk_root_level = (int)ys->walk_start_level - 1; + + return NB_OK; +} + +/* ================ */ +/* End of init code */ +/* ================ */ + +/** + * nb_op_add_leaf() - Add leaf data to the get tree results + * @ys - the yield state for this tree walk. + * @nb_node - the northbound node representing this leaf. + * @xpath - the xpath (with key predicates) to this leaf value. + * + * Return: northbound return value (enum nb_error) + */ +static enum nb_error nb_op_iter_leaf(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, + const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + struct nb_op_node_info *ni = darr_last(ys->node_infos); + struct yang_data *data; + enum nb_error ret = NB_OK; + LY_ERR err; + + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return NB_OK; + + /* Ignore list keys. */ + if (lysc_is_key(snode)) + return NB_OK; + + data = nb_callback_get_elem(nb_node, xpath, ni->list_entry); + if (data == NULL) + return NB_OK; + + /* Add a dnode to our tree */ + err = lyd_new_term(ni->inner, snode->module, snode->name, data->value, + false, NULL); + if (err) { + yang_data_free(data); + return NB_ERR_RESOURCE; + } + + if (ys->cb) + ret = (*ys->cb)(nb_node->snode, ys->translator, data, + ys->cb_arg); + yang_data_free(data); + + return ret; +} + +static enum nb_error nb_op_iter_leaflist(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, + const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + struct nb_op_node_info *ni = darr_last(ys->node_infos); + const void *list_entry = NULL; + enum nb_error ret = NB_OK; + LY_ERR err; + + if (CHECK_FLAG(snode->flags, LYS_CONFIG_W)) + return NB_OK; + + do { + struct yang_data *data; + + list_entry = nb_callback_get_next(nb_node, ni->list_entry, + list_entry); + if (!list_entry) + /* End of the list. */ + break; + + data = nb_callback_get_elem(nb_node, xpath, list_entry); + if (data == NULL) + continue; + + /* Add a dnode to our tree */ + err = lyd_new_term(ni->inner, snode->module, snode->name, + data->value, false, NULL); + if (err) { + yang_data_free(data); + return NB_ERR_RESOURCE; + } + + if (ys->cb) + ret = (*ys->cb)(nb_node->snode, ys->translator, data, + ys->cb_arg); + yang_data_free(data); + } while (ret == NB_OK && list_entry); + + return ret; +} + + +static bool nb_op_schema_path_has_predicate(struct nb_op_yield_state *ys, + int level) +{ + if (level > darr_lasti(ys->query_tokens)) + return false; + return strchr(ys->query_tokens[level], '[') != NULL; +} + +/** + * nb_op_empty_container_ok() - determine if should keep empty container node. + * + * Return: true if the empty container should be kept. + */ +static bool nb_op_empty_container_ok(const struct lysc_node *snode, + const char *xpath, const void *list_entry) +{ + struct nb_node *nn = snode->priv; + struct yang_data *data; + + if (!CHECK_FLAG(snode->flags, LYS_PRESENCE)) + return false; + + if (!nn->cbs.get_elem) + return false; + + data = nb_callback_get_elem(nn, xpath, list_entry); + if (data) { + yang_data_free(data); + return true; + } + return false; +} + +/** + * nb_op_get_child_path() - add child node name to the xpath. + * @xpath_parent - a darr string for the parent node. + * @schild - the child schema node. + * @xpath_child - a previous return value from this function to reuse. + */ +static char *nb_op_get_child_path(const char *xpath_parent, + const struct lysc_node *schild, + char *xpath_child) +{ + /* "/childname" */ + uint space, extra = strlen(schild->name) + 1; + bool new_mod = (!schild->parent || + schild->parent->module != schild->module); + int n; + + if (new_mod) + /* "modulename:" */ + extra += strlen(schild->module->name) + 1; + space = darr_len(xpath_parent) + extra; + + if (xpath_parent == xpath_child) + darr_ensure_cap(xpath_child, space); + else + darr_in_strdup_cap(xpath_child, xpath_parent, space); + if (new_mod) + n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s:%s", + schild->module->name, schild->name); + else + n = snprintf(darr_strnul(xpath_child), extra + 1, "/%s", + schild->name); + assert(n == (int)extra); + _darr_len(xpath_child) += extra; + return xpath_child; +} + +static bool __is_yielding_node(const struct lysc_node *snode) +{ + struct nb_node *nn = snode->priv; + + return nn->cbs.lookup_next != NULL; +} + +static const struct lysc_node *__sib_next(bool yn, const struct lysc_node *sib) +{ + for (; sib; sib = sib->next) { + /* Always skip keys. */ + if (lysc_is_key(sib)) + continue; + if (yn == __is_yielding_node(sib)) + return sib; + } + return NULL; +} + +/** + * nb_op_sib_next() - Return the next sibling to walk to + * @ys: the yield state for this tree walk. + * @sib: the currently being visited sibling + * + * Return: the next sibling to walk to, walking non-yielding before yielding. + */ +static const struct lysc_node *nb_op_sib_next(struct nb_op_yield_state *ys, + const struct lysc_node *sib) +{ + struct lysc_node *parent = sib->parent; + bool yn = __is_yielding_node(sib); + + /* + * If the node info stack is shorter than the schema path then we are + * doign specific query still on the node from the schema path (should + * match) so just return NULL (i.e., don't process siblings) + */ + if (darr_len(ys->schema_path) > darr_len(ys->node_infos)) + return NULL; + /* + * If sib is on top of the node info stack then + * 1) it's a container node -or- + * 2) it's a list node that we were walking and we've reach the last entry + * 3) if sib is a list and the list was empty we never would have + * pushed sib on the stack so the top of the stack is the parent + * + * If the query string included this node then we do not process any + * siblings as we are not walking all the parent's children just this + * specified one give by the query string. + */ + if (sib == darr_last(ys->node_infos)->schema && + darr_len(ys->schema_path) >= darr_len(ys->node_infos)) + return NULL; + /* case (3) */ + else if (sib->nodetype == LYS_LIST && + parent == darr_last(ys->node_infos)->schema && + darr_len(ys->schema_path) > darr_len(ys->node_infos)) + return NULL; + + sib = __sib_next(yn, sib->next); + if (sib) + return sib; + if (yn) + return NULL; + return __sib_next(true, lysc_node_child(parent)); +} +/* + * sib_walk((struct lyd_node *)ni->inner->node.parent->parent->parent->parent->parent->parent->parent) + */ + +/** + * nb_op_sib_first() - obtain the first child to walk to + * @ys: the yield state for this tree walk. + * @parent: the parent whose child we seek + * @skip_keys: if should skip over keys + * + * Return: the first child to continue the walk to, starting with non-yielding + * siblings then yielding ones. There should be no more than 1 yielding sibling. + */ +static const struct lysc_node *nb_op_sib_first(struct nb_op_yield_state *ys, + const struct lysc_node *parent) +{ + const struct lysc_node *sib = lysc_node_child(parent); + const struct lysc_node *first_sib; + + /* + * NOTE: when we want to handle root level walks we will need to use + * lys_getnext() to walk root level of each module and + * ly_ctx_get_module_iter() to walk the modules. + */ + assert(darr_len(ys->node_infos) > 0); + + /* + * The top of the node stack points at @parent. + * + * If the schema path (original query) is longer than our current node + * info stack (current xpath location), we are building back up to the + * base of the user query, return the next schema node from the query + * string (schema_path). + */ + if (darr_last(ys->node_infos) != NULL && + !CHECK_FLAG(darr_last(ys->node_infos)->schema->nodetype, + LYS_CASE | LYS_CHOICE)) + assert(darr_last(ys->node_infos)->schema == parent); + if (darr_lasti(ys->node_infos) < ys->query_base_level) + return ys->schema_path[darr_lasti(ys->node_infos) + 1]; + + /* We always skip keys. */ + while (sib && lysc_is_key(sib)) + sib = sib->next; + if (!sib) + return NULL; + + /* Return non-yielding node's first */ + first_sib = sib; + if (__is_yielding_node(sib)) { + sib = __sib_next(false, sib); + if (sib) + return sib; + } + return first_sib; +} + +/* + * "3-dimensional" walk from base of the tree to the tip in-order. + * + * The actual tree is only 2-dimensional as list nodes are organized as adjacent + * siblings under a common parent perhaps with other siblings to each side; + * however, using 3d view here is easier to diagram. + * + * - A list node is yielding if it has a lookup_next callback. + * - All other node types are not yielding. + * - There's only one yielding node in a list of children (i.e., siblings). + * + * We visit all non-yielding children prior to the yielding child. + * That way we have the fullest tree possible even when something is deleted + * during a yield. + * --- child/parent descendant poinilnters + * ... next/prev sibling pointers + * o.o list entries pointers + * ~~~ diagram extension connector + * 1 + * / \ + * / \ o~~~~12 + * / \ . / \ + * 2.......5 o~~~9 13...14 + * / \ | . / \ + * 3...4 6 10...11 Cont Nodes: 1,2,5 + * / \ List Nodes: 6,9,12 + * 7...8 Leaf Nodes: 3,4,7,8,10,11,13,14 + * Schema Leaf A: 3 + * Schema Leaf B: 4 + * Schema Leaf C: 7,10,13 + * Schema Leaf D: 8,11,14 + */ +static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) +{ + const struct lysc_node *walk_stem_tip = ys_get_walk_stem_tip(ys); + const struct lysc_node *sib; + const void *parent_list_entry = NULL; + const void *list_entry = NULL; + struct nb_op_node_info *ni, *pni; + struct lyd_node *node; + struct nb_node *nn; + char *xpath_child = NULL; + // bool at_query_base; + bool at_root_level, list_start, is_specific_node; + enum nb_error ret = NB_OK; + LY_ERR err; + int at_clevel; + uint len; + + + monotime(&ys->start_time); + + /* Don't currently support walking all root nodes */ + if (!walk_stem_tip) + return NB_ERR_NOT_FOUND; + + if (ys->schema_path[0]->nodetype == LYS_CHOICE) { + flog_err(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unable to walk root level choice node from module: %s", + __func__, ys->schema_path[0]->module->name); + return NB_ERR; + } + + /* + * If we are resuming then start with the list container on top. + * Otherwise get the first child of the container we are walking, + * starting with non-yielding children. + */ + if (is_resume) + sib = darr_last(ys->node_infos)->schema; + else { + /* + * Start with non-yielding children first. + * + * When adding root level walks, the sibling list are the root + * level nodes of all modules + */ + sib = nb_op_sib_first(ys, walk_stem_tip); + if (!sib) + return NB_ERR_NOT_FOUND; + } + + + while (true) { + /* Grab the top container/list node info on the stack */ + at_clevel = darr_lasti(ys->node_infos); + ni = &ys->node_infos[at_clevel]; + + /* + * This is the level of the last specific node at init + * time. +1 would be the first non-specific list or + * non-container if present in the container node. + */ + at_root_level = at_clevel == ys->walk_root_level; + + if (!sib) { + /* + * We've reached the end of the siblings inside a + * containing node; either a container, case, choice, or + * a specific list node entry. + * + * We handle case/choice/container node inline; however, + * for lists we are only done with a specific entry and + * need to move to the next element on the list so we + * drop down into the switch for that case. + */ + + /* Grab the containing node. */ + sib = ni->schema; + + if (CHECK_FLAG(sib->nodetype, + LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) { + /* If we added an empty container node (no + * children) and it's not a presence container + * or it's not backed by the get_elem callback, + * remove the node from the tree. + */ + if (sib->nodetype == LYS_CONTAINER && + !lyd_child(ni->inner) && + !nb_op_empty_container_ok(sib, ys->xpath, + ni->list_entry)) + ys_free_inner(ys, ni); + + /* If we have returned to our original walk base, + * then we are done with the walk. + */ + if (at_root_level) { + ret = NB_OK; + goto done; + } + /* + * Grab the sibling of the container we are + * about to pop, so we will be mid-walk on the + * parent containers children. + */ + sib = nb_op_sib_next(ys, sib); + + /* Pop container node to the parent container */ + ys_pop_inner(ys); + + /* + * If are were working on a user narrowed path + * then we are done with these siblings. + */ + if (darr_len(ys->schema_path) > + darr_len(ys->node_infos)) + sib = NULL; + + /* Start over */ + continue; + } + /* + * If we are here we have reached the end of the + * children of a list entry node. sib points + * at the list node info. + */ + } + + if (CHECK_FLAG(sib->nodetype, + LYS_LEAF | LYS_LEAFLIST | LYS_CONTAINER)) + xpath_child = nb_op_get_child_path(ys->xpath, sib, + xpath_child); + else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE)) + darr_in_strdup(xpath_child, ys->xpath); + + nn = sib->priv; + + switch (sib->nodetype) { + case LYS_LEAF: + /* + * If we have a non-specific walk to a specific leaf + * (e.g., "..../route-entry/metric") and the leaf value + * is not present, then we are left with the data nodes + * of the stem of the branch to the missing leaf data. + * For containers this will get cleaned up by the + * container code above that looks for no children; + * however, this doesn't work for lists. + * + * (FN:A) We need a similar check for empty list + * elements. Empty list elements below the + * query_base_level (i.e., the schema path length) + * should be cleaned up as they don't support anything + * the user is querying for, if they are above the + * query_base_level then they are part of the walk and + * should be kept. + */ + ret = nb_op_iter_leaf(ys, nn, xpath_child); + if (ret != NB_OK) + goto done; + sib = nb_op_sib_next(ys, sib); + continue; + case LYS_LEAFLIST: + ret = nb_op_iter_leaflist(ys, nn, xpath_child); + if (ret != NB_OK) + goto done; + sib = nb_op_sib_next(ys, sib); + continue; + case LYS_CASE: + case LYS_CHOICE: + case LYS_CONTAINER: + if (CHECK_FLAG(nn->flags, F_NB_NODE_CONFIG_ONLY)) { + sib = nb_op_sib_next(ys, sib); + continue; + } + + if (sib->nodetype != LYS_CONTAINER) { + /* Case/choice use parent inner. */ + /* TODO: thus we don't support root level choice */ + node = ni->inner; + } else { + err = lyd_new_inner(ni->inner, sib->module, + sib->name, false, &node); + if (err) { + ret = NB_ERR_RESOURCE; + goto done; + } + } + + /* push this choice/container node on top of the stack */ + ni = darr_appendz(ys->node_infos); + ni->inner = node; + ni->schema = sib; + ni->lookup_next_ok = ni[-1].lookup_next_ok; + ni->list_entry = ni[-1].list_entry; + + darr_in_strdup(ys->xpath, xpath_child); + ni->xpath_len = darr_strlen(ys->xpath); + + sib = nb_op_sib_first(ys, sib); + continue; + case LYS_LIST: + + /* + * Notes: + * + * NOTE: ni->inner may be NULL here if we resumed and it + * was gone. ni->schema and ni->keys will still be + * valid. + * + * NOTE: At this point sib is never NULL; however, if it + * was NULL at the top of the loop, then we were done + * working on a list element's children and will be + * attempting to get the next list element here so sib + * == ni->schema (i.e., !list_start). + * + * (FN:A): Before doing this let's remove empty list + * elements that are "inside" the query string as they + * represent a stem which didn't lead to actual data + * being requested by the user -- for example, + * ".../route-entry/metric" if metric is not present we + * don't want to return an empty route-entry to the + * user. + */ + + node = NULL; + list_start = ni->schema != sib; + if (list_start) { + /* + * List iteration: First Element + * ----------------------------- + * + * Our node info wasn't on top (wasn't an entry + * for sib) so this is a new list iteration, we + * will push our node info below. The top is our + * parent. + */ + if (CHECK_FLAG(nn->flags, + F_NB_NODE_CONFIG_ONLY)) { + sib = nb_op_sib_next(ys, sib); + continue; + } + /* we are now at one level higher */ + at_clevel += 1; + pni = ni; + ni = NULL; + } else { + /* + * List iteration: Next Element + * ---------------------------- + * + * This is the case where `sib == NULL` at the + * top of the loop, so, we just completed the + * walking the children of a list entry, i.e., + * we are done with that list entry. + * + * `sib` was reset to point at the our list node + * at the top of node_infos. + * + * Within this node_info, `ys->xpath`, `inner`, + * `list_entry`, and `xpath_len` are for the + * previous list entry, and need to be updated. + */ + pni = darr_len(ys->node_infos) > 1 ? &ni[-1] + : NULL; + } + + parent_list_entry = pni ? pni->list_entry : NULL; + list_entry = ni ? ni->list_entry : NULL; + + /* + * Before yielding we check to see if we are doing a + * specific list entry instead of a full list iteration. + * We do not want to yield during specific list entry + * processing. + */ + + /* + * If we are at a list start check to see if the node + * has a predicate. If so we will try and fetch the data + * node now that we've built part of the tree, if the + * predicates are keys or only depend on the tree already + * built, it should create the element for us. + */ + is_specific_node = false; + if (list_start && + at_clevel <= darr_lasti(ys->query_tokens) && + !ys->non_specific_predicate[at_clevel] && + nb_op_schema_path_has_predicate(ys, at_clevel)) { + err = lyd_new_path(pni->inner, NULL, + ys->query_tokens[at_clevel], + NULL, 0, &node); + if (!err) + is_specific_node = true; + else if (err == LY_EVALID) + ys->non_specific_predicate[at_clevel] = true; + else { + flog_err(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unable to create node for specific query string: %s: %s", + __func__, + ys->query_tokens[at_clevel], + yang_ly_strerrcode(err)); + ret = NB_ERR; + goto done; + } + } + + if (list_entry && ni->query_specific_entry) { + /* + * Ending specific list entry processing. + */ + assert(!list_start); + is_specific_node = true; + list_entry = NULL; + } + + /* + * Should we yield? + * + * Don't yield if we have a specific entry. + */ + if (!is_specific_node && ni && ni->lookup_next_ok && + // make sure we advance, if the interval is + // fast and we are very slow. + ((monotime_since(&ys->start_time, NULL) > + NB_OP_WALK_INTERVAL_US && + ni->niters) || + (ni->niters + 1) % 10000 == 0)) { + /* This is a yield supporting list node and + * we've been running at least our yield + * interval, so yield. + * + * NOTE: we never yield on list_start, and we + * are always about to be doing a get_next. + */ + DEBUGD(&nb_dbg_events, + "%s: yielding after %u iterations", + __func__, ni->niters); + + ni->niters = 0; + ret = NB_YIELD; + goto done; + } + + /* + * Now get the backend list_entry opaque object for + * this list entry from the backend. + */ + + if (is_specific_node) { + /* + * Specific List Entry: + * -------------------- + */ + if (list_start) { + list_entry = + nb_callback_lookup_node_entry( + node, parent_list_entry); + /* + * If the node we created from a + * specific predicate entry is not + * actually there we need to delete the + * node from our data tree + */ + if (!list_entry) { + lyd_free_tree(node); + node = NULL; + } + } + } else if (!list_start && !list_entry && + ni->has_lookup_next) { + /* + * After Yield: + * ------------ + * After a yield the list_entry may have become + * invalid, so use lookup_next callback with + * parent and keys instead to find next element. + */ + list_entry = + nb_callback_lookup_next(nn, + parent_list_entry, + &ni->keys); + } else { + /* + * Normal List Iteration: + * ---------------------- + * Start (list_entry == NULL) or continue + * (list_entry != NULL) the list iteration. + */ + /* Obtain [next] list entry. */ + list_entry = + nb_callback_get_next(nn, + parent_list_entry, + list_entry); + } + + /* + * (FN:A) Reap empty list element? Check to see if we + * should reap an empty list element. We do this if the + * empty list element exists at or below the query base + * (i.e., it's not part of the walk, but a failed find + * on a more specific query e.g., for below the + * `route-entry` element for a query + * `.../route-entry/metric` where the list element had + * no metric value. + * + * However, if the user query is for a key of a list + * element, then when we reach that list element it will + * have no non-key children, check for this condition + * and do not reap if true. + */ + if (!list_start && ni->inner && + !lyd_child_no_keys(ni->inner) && + /* not the top element with a key match */ + !((darr_ilen(ys->node_infos) == + darr_ilen(ys->schema_path) - 1) && + lysc_is_key((*darr_last(ys->schema_path)))) && + /* is this at or below the base? */ + darr_ilen(ys->node_infos) <= ys->query_base_level) + ys_free_inner(ys, ni); + + + if (!list_entry) { + /* + * List Iteration Done + * ------------------- + */ + + /* + * Grab next sibling of the list node + */ + if (is_specific_node) + sib = NULL; + else + sib = nb_op_sib_next(ys, sib); + + /* + * If we are at the walk root (base) level then + * that specifies a list and we are done iterating + * the list, so we are done with the walk entirely. + */ + if (!sib && at_clevel == ys->walk_root_level) { + ret = NB_OK; + goto done; + } + + /* + * Pop the our list node info back to our + * parent. + * + * We only do this if we've already pushed a + * node for the current list schema. For + * `list_start` this hasn't happened yet, as + * would have happened below. So when list_start + * is true but list_entry if NULL we + * are processing an empty list. + */ + if (!list_start) + ys_pop_inner(ys); + + /* + * We should never be below the walk root + */ + assert(darr_lasti(ys->node_infos) >= + ys->walk_root_level); + + /* Move on to the sibling of the list node */ + continue; + } + + /* + * From here on, we have selected a new top node_info + * list entry (either newly pushed or replacing the + * previous entry in the walk), and we are filling in + * the details. + */ + + if (list_start) { + /* + * Starting iteration of a list type or + * processing a specific entry, push the list + * node_info on stack. + */ + ni = darr_appendz(ys->node_infos); + pni = &ni[-1]; /* memory may have moved */ + ni->has_lookup_next = nn->cbs.lookup_next != + NULL; + ni->lookup_next_ok = ((!pni && ys->finish) || + pni->lookup_next_ok) && + ni->has_lookup_next; + ni->query_specific_entry = is_specific_node; + ni->niters = 0; + ni->nents = 0; + + /* this will be our predicate-less xpath */ + ys->xpath = nb_op_get_child_path(ys->xpath, sib, + ys->xpath); + } else { + /* + * Reset our xpath to the list node (i.e., + * remove the entry predicates) + */ + if (ni->query_specific_entry) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unexpected state", + __func__); + } + assert(!ni->query_specific_entry); + len = strlen(sib->name) + 1; /* "/sibname" */ + if (pni) + len += pni->xpath_len; + darr_setlen(ys->xpath, len + 1); + ys->xpath[len] = 0; + ni->xpath_len = len; + } + + /* Need to get keys. */ + + if (!CHECK_FLAG(nn->flags, F_NB_NODE_KEYLESS_LIST)) { + ret = nb_callback_get_keys(nn, list_entry, + &ni->keys); + if (ret) { + darr_pop(ys->node_infos); + ret = NB_ERR_RESOURCE; + goto done; + } + } + /* + * Append predicates to xpath. + */ + len = darr_strlen(ys->xpath); + if (ni->keys.num) { + yang_get_key_preds(ys->xpath + len, sib, + &ni->keys, + darr_cap(ys->xpath) - len); + } else { + /* add a position predicate (1s based?) */ + darr_ensure_avail(ys->xpath, 10); + snprintf(ys->xpath + len, + darr_cap(ys->xpath) - len + 1, "[%u]", + ni->nents + 1); + } + darr_setlen(ys->xpath, + strlen(ys->xpath + len) + len + 1); + ni->xpath_len = darr_strlen(ys->xpath); + + /* + * Create the new list entry node. + */ + + if (!node) { + err = yang_lyd_new_list((struct lyd_node_inner *) + ni[-1] + .inner, + sib, &ni->keys, &node); + if (err) { + darr_pop(ys->node_infos); + ret = NB_ERR_RESOURCE; + goto done; + } + } + + /* + * Save the new list entry with the list node info + */ + ni->inner = node; + ni->schema = node->schema; + ni->list_entry = list_entry; + ni->niters += 1; + ni->nents += 1; + + /* Skip over the key children, they've been created. */ + sib = nb_op_sib_first(ys, sib); + continue; + + default: + /*FALLTHROUGH*/ + case LYS_ANYXML: + case LYS_ANYDATA: + /* These schema types are not currently handled */ + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: unsupported schema node type: %s", + __func__, lys_nodetype2str(sib->nodetype)); + sib = nb_op_sib_next(ys, sib); + continue; + } + } + +done: + darr_free(xpath_child); + return ret; +} + +static void nb_op_walk_continue(struct event *thread) +{ + struct nb_op_yield_state *ys = EVENT_ARG(thread); + enum nb_error ret = NB_OK; + + DEBUGD(&nb_dbg_cbs_state, "northbound oper-state: resuming %s", + ys->xpath); + + nb_op_resume_data_tree(ys); + + /* if we've popped past the walk start level we're done */ + if (darr_lasti(ys->node_infos) < ys->walk_root_level) + goto finish; + + /* otherwise we are at a resumable node */ + assert(darr_last(ys->node_infos)->has_lookup_next); + + ret = __walk(ys, true); + if (ret == NB_YIELD) { + if (nb_op_yield(ys) != NB_OK) { + if (ys->should_batch) + goto stopped; + else + goto finish; + } + return; + } +finish: + (*ys->finish)(ys_root_node(ys), ys->finish_arg, ret); +stopped: + nb_op_free_yield_state(ys, false); +} + +static void __free_siblings(struct lyd_node *this) +{ + struct lyd_node *next, *sib; + uint count = 0; + + LY_LIST_FOR_SAFE(lyd_first_sibling(this), next, sib) + { + if (lysc_is_key(sib->schema)) + continue; + if (sib == this) + continue; + lyd_free_tree(sib); + count++; + } + DEBUGD(&nb_dbg_events, "NB oper-state: deleted %u siblings", count); +} + +/* + * Trim Algorithm: + * + * Delete final lookup-next list node and subtree, leave stack slot with keys. + * + * Then walking up the stack, delete all siblings except: + * 1. right-most container or list node (must be lookup-next by design) + * 2. keys supporting existing parent list node. + * + * NOTE the topmost node on the stack will be the final lookup-nexxt list node, + * as we only yield on lookup-next list nodes. + * + */ +static void nb_op_trim_yield_state(struct nb_op_yield_state *ys) +{ + struct nb_op_node_info *ni; + int i = darr_lasti(ys->node_infos); + + assert(i >= 0); + + DEBUGD(&nb_dbg_events, "NB oper-state: start trimming: top: %d", i); + + ni = &ys->node_infos[i]; + assert(ni->has_lookup_next); + + DEBUGD(&nb_dbg_events, "NB oper-state: deleting tree at level %d", i); + __free_siblings(ni->inner); + ys_free_inner(ys, ni); + + while (--i > 0) { + DEBUGD(&nb_dbg_events, + "NB oper-state: deleting siblings at level: %d", i); + __free_siblings(ys->node_infos[i].inner); + } + DEBUGD(&nb_dbg_events, "NB oper-state: stop trimming: new top: %d", + (int)darr_lasti(ys->node_infos)); +} + +static enum nb_error nb_op_yield(struct nb_op_yield_state *ys) +{ + enum nb_error ret; + unsigned long min_us = MAX(1, NB_OP_WALK_INTERVAL_US / 50000); + struct timeval tv = { .tv_sec = 0, .tv_usec = min_us }; + + DEBUGD(&nb_dbg_events, + "NB oper-state: yielding %s for %lldus (should_batch %d)", + ys->xpath, (long long)tv.tv_usec, ys->should_batch); + + if (ys->should_batch) { + /* + * TODO: add ability of finish to influence the timer. + * This will allow, for example, flow control based on how long + * it takes finish to process the batch. + */ + ret = (*ys->finish)(ys_root_node(ys), ys->finish_arg, NB_YIELD); + if (ret != NB_OK) + return ret; + /* now trim out that data we just "finished" */ + nb_op_trim_yield_state(ys); + + } + + event_add_timer_tv(event_loop, nb_op_walk_continue, ys, &tv, + &ys->walk_ev); + return NB_OK; +} + +static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, + struct nb_node **last) +{ + struct nb_node **nb_nodes = NULL; + const struct lysc_node *sn; + struct nb_node *nblast; + char *s, *s2; + int count; + uint i; + + /* + * Get the schema node stack for the entire query string + * + * The user might pass in something like "//metric" which may resolve to + * more than one schema node ("trunks"). nb_node_find() returns a single + * node though. We should expand the functionality to get the set of + * nodes that matches the xpath (not path) query and save that set in + * the yield state. Then we should do a walk using the users query + * string over each schema trunk in the set. + */ + nblast = nb_node_find(ys->xpath); + if (!nblast) { + nb_nodes = nb_nodes_find(ys->xpath); + nblast = darr_len(nb_nodes) ? nb_nodes[0] : NULL; + darr_free(nb_nodes); + } + if (!nblast) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, ys->xpath); + return NB_ERR; + } + *last = nblast; + + /* + * Create a stack of schema nodes one element per node in the query + * path, only the top (last) element may be a non-container type. + * + * NOTE: appears to be a bug in nb_node linkage where parent can be NULL, + * or I'm misunderstanding the code, in any case we use the libyang + * linkage to walk which works fine. + * + * XXX: we don't actually support choice/case yet, they are container + * types in the libyang schema, but won't be in data so our length + * checking gets messed up. + */ + for (sn = nblast->snode, count = 0; sn; count++, sn = sn->parent) + if (sn != nblast->snode) + assert(CHECK_FLAG(sn->nodetype, + LYS_CONTAINER | LYS_LIST | + LYS_CHOICE | LYS_CASE)); + /* create our arrays */ + darr_append_n(ys->schema_path, count); + darr_append_n(ys->query_tokens, count); + darr_append_nz(ys->non_specific_predicate, count); + for (sn = nblast->snode; sn; sn = sn->parent) + ys->schema_path[--count] = sn; + + /* + * Now tokenize the query string and get pointers to each token + */ + + /* Get copy of query string start after initial '/'s */ + s = ys->xpath; + while (*s && *s == '/') + s++; + ys->query_tokstr = darr_strdup(s); + s = ys->query_tokstr; + + darr_foreach_i (ys->schema_path, i) { + const char *modname = ys->schema_path[i]->module->name; + const char *name = ys->schema_path[i]->name; + int nlen = strlen(name); + int mnlen = 0; + + /* + * Technically the query_token for choice/case should probably be pointing at + * the child (leaf) rather than the parent (container), however, + * we only use these for processing list nodes so KISS. + */ + if (CHECK_FLAG(ys->schema_path[i]->nodetype, + LYS_CASE | LYS_CHOICE)) { + ys->query_tokens[i] = ys->query_tokens[i - 1]; + continue; + } + + while (true) { + s2 = strstr(s, name); + if (!s2) + goto error; + + if (s2[-1] == ':') { + mnlen = strlen(modname) + 1; + if (ys->query_tokstr > s2 - mnlen || + strncmp(s2 - mnlen, modname, mnlen - 1)) + goto error; + s2 -= mnlen; + nlen += mnlen; + } + + s = s2; + if ((i == 0 || s[-1] == '/') && + (s[nlen] == 0 || s[nlen] == '[' || s[nlen] == '/')) + break; + /* + * Advance past the incorrect match, must have been + * part of previous predicate. + */ + s += nlen; + } + + /* NUL terminate previous token and save this one */ + if (i > 0) + s[-1] = 0; + ys->query_tokens[i] = s; + s += nlen; + } + + /* NOTE: need to subtract choice/case nodes when these are supported */ + ys->query_base_level = darr_lasti(ys->schema_path); + + return NB_OK; + +error: + darr_free(ys->query_tokstr); + darr_free(ys->schema_path); + darr_free(ys->query_tokens); + darr_free(ys->non_specific_predicate); + return NB_ERR; +} + + +/** + * nb_op_walk_start() - Start walking oper-state directed by query string. + * @ys: partially initialized yield state for this walk. + * + */ +static enum nb_error nb_op_walk_start(struct nb_op_yield_state *ys) +{ + struct nb_node *nblast; + enum nb_error ret; + + /* + * Get nb_node path (stack) corresponding to the xpath query + */ + ret = nb_op_ys_init_schema_path(ys, &nblast); + if (ret != NB_OK) + return ret; + + + /* + * Get the node_info path (stack) corresponding to the uniquely + * resolvable data nodes from the beginning of the xpath query. + */ + ret = nb_op_ys_init_node_infos(ys); + if (ret != NB_OK) + return ret; + + return __walk(ys, false); +} + + +void *nb_oper_walk(const char *xpath, struct yang_translator *translator, + uint32_t flags, bool should_batch, nb_oper_data_cb cb, + void *cb_arg, nb_oper_data_finish_cb finish, void *finish_arg) +{ + struct nb_op_yield_state *ys; + enum nb_error ret; + + ys = nb_op_create_yield_state(xpath, translator, flags, should_batch, + cb, cb_arg, finish, finish_arg); + + ret = nb_op_walk_start(ys); + if (ret == NB_YIELD) { + if (nb_op_yield(ys) != NB_OK) { + if (ys->should_batch) + goto stopped; + else + goto finish; + } + return ys; + } +finish: + (void)(*ys->finish)(ys_root_node(ys), ys->finish_arg, ret); +stopped: + nb_op_free_yield_state(ys, false); + return NULL; +} + + +void nb_oper_cancel_walk(void *walk) +{ + if (walk) + nb_op_free_yield_state(walk, false); +} + + +void nb_oper_cancel_all_walks(void) +{ + struct nb_op_yield_state *ys; + + frr_each_safe (nb_op_walks, &nb_op_walks, ys) + nb_oper_cancel_walk(ys); +} + + +/* + * The old API -- remove when we've update the users to yielding. + */ +enum nb_error nb_oper_iterate_legacy(const char *xpath, + struct yang_translator *translator, + uint32_t flags, nb_oper_data_cb cb, + void *cb_arg, struct lyd_node **tree) +{ + struct nb_op_yield_state *ys; + enum nb_error ret; + + ys = nb_op_create_yield_state(xpath, translator, flags, false, cb, + cb_arg, NULL, NULL); + + ret = nb_op_walk_start(ys); + assert(ret != NB_YIELD); + + if (tree && ret == NB_OK) + *tree = ys_root_node(ys); + else { + if (ys_root_node(ys)) + yang_dnode_free(ys_root_node(ys)); + if (tree) + *tree = NULL; + } + + nb_op_free_yield_state(ys, true); + return ret; +} + +void nb_oper_init(struct event_loop *loop) +{ + event_loop = loop; + nb_op_walks_init(&nb_op_walks); +} + +void nb_oper_terminate(void) +{ + nb_oper_cancel_all_walks(); +} diff --git a/lib/northbound_sysrepo.c b/lib/northbound_sysrepo.c new file mode 100644 index 0000000..0ec7610 --- /dev/null +++ b/lib/northbound_sysrepo.c @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "log.h" +#include "lib_errors.h" +#include "command.h" +#include "debug.h" +#include "memory.h" +#include "libfrr.h" +#include "lib/version.h" +#include "northbound.h" + +#include +#include +#include + +static struct debug nb_dbg_client_sysrepo = {0, "Northbound client: Sysrepo"}; + +static struct event_loop *master; +static sr_session_ctx_t *session; +static sr_conn_ctx_t *connection; +static struct nb_transaction *transaction; + +static void frr_sr_read_cb(struct event *thread); +static int frr_sr_finish(void); + +/* Convert FRR YANG data value to sysrepo YANG data value. */ +static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data) +{ + struct nb_node *nb_node; + const struct lysc_node *snode; + struct lysc_node_container *scontainer; + struct lysc_node_leaf *sleaf; + struct lysc_node_leaflist *sleaflist; + LY_DATA_TYPE type; + + sr_val_set_xpath(sr_data, frr_data->xpath); + + nb_node = nb_node_find(frr_data->xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, + frr_data->xpath); + return -1; + } + + snode = nb_node->snode; + switch (snode->nodetype) { + case LYS_CONTAINER: + scontainer = (struct lysc_node_container *)snode; + if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE)) + return -1; + sr_data->type = SR_CONTAINER_PRESENCE_T; + return 0; + case LYS_LIST: + sr_data->type = SR_LIST_T; + return 0; + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + type = sleaf->type->basetype; + break; + case LYS_LEAFLIST: + sleaflist = (struct lysc_node_leaflist *)snode; + type = sleaflist->type->basetype; + break; + default: + return -1; + } + + switch (type) { + case LY_TYPE_BINARY: + sr_val_set_str_data(sr_data, SR_BINARY_T, frr_data->value); + break; + case LY_TYPE_BITS: + sr_val_set_str_data(sr_data, SR_BITS_T, frr_data->value); + break; + case LY_TYPE_BOOL: + sr_data->type = SR_BOOL_T; + sr_data->data.bool_val = yang_str2bool(frr_data->value); + break; + case LY_TYPE_DEC64: + sr_data->type = SR_DECIMAL64_T; + sr_data->data.decimal64_val = + yang_str2dec64(frr_data->xpath, frr_data->value); + break; + case LY_TYPE_EMPTY: + sr_data->type = SR_LEAF_EMPTY_T; + break; + case LY_TYPE_ENUM: + sr_val_set_str_data(sr_data, SR_ENUM_T, frr_data->value); + break; + case LY_TYPE_IDENT: + sr_val_set_str_data(sr_data, SR_IDENTITYREF_T, frr_data->value); + break; + case LY_TYPE_INST: + sr_val_set_str_data(sr_data, SR_INSTANCEID_T, frr_data->value); + break; + case LY_TYPE_INT8: + sr_data->type = SR_INT8_T; + sr_data->data.int8_val = yang_str2int8(frr_data->value); + break; + case LY_TYPE_INT16: + sr_data->type = SR_INT16_T; + sr_data->data.int16_val = yang_str2int16(frr_data->value); + break; + case LY_TYPE_INT32: + sr_data->type = SR_INT32_T; + sr_data->data.int32_val = yang_str2int32(frr_data->value); + break; + case LY_TYPE_INT64: + sr_data->type = SR_INT64_T; + sr_data->data.int64_val = yang_str2int64(frr_data->value); + break; + case LY_TYPE_LEAFREF: + sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); + break; + case LY_TYPE_STRING: + sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); + break; + case LY_TYPE_UINT8: + sr_data->type = SR_UINT8_T; + sr_data->data.uint8_val = yang_str2uint8(frr_data->value); + break; + case LY_TYPE_UINT16: + sr_data->type = SR_UINT16_T; + sr_data->data.uint16_val = yang_str2uint16(frr_data->value); + break; + case LY_TYPE_UINT32: + sr_data->type = SR_UINT32_T; + sr_data->data.uint32_val = yang_str2uint32(frr_data->value); + break; + case LY_TYPE_UINT64: + sr_data->type = SR_UINT64_T; + sr_data->data.uint64_val = yang_str2uint64(frr_data->value); + break; + case LY_TYPE_UNION: + /* No way to deal with this using un-typed yang_data object */ + sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); + break; + case LY_TYPE_UNKNOWN: + default: + return -1; + } + + return 0; +} + +static int frr_sr_process_change(struct nb_config *candidate, + sr_change_oper_t sr_op, sr_val_t *sr_old_val, + sr_val_t *sr_new_val) +{ + struct nb_node *nb_node; + enum nb_operation nb_op; + sr_val_t *sr_data; + const char *xpath; + char value_str[YANG_VALUE_MAXLEN]; + struct yang_data *data; + int ret; + + sr_data = sr_new_val ? sr_new_val : sr_old_val; + assert(sr_data); + + xpath = sr_data->xpath; + + DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: processing change [xpath %s]", + xpath); + + /* Non-presence container - nothing to do. */ + if (sr_data->type == SR_CONTAINER_T) + return NB_OK; + + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + return NB_ERR; + } + + /* Map operation values. */ + switch (sr_op) { + case SR_OP_CREATED: + nb_op = NB_OP_CREATE; + break; + case SR_OP_MODIFIED: + if (nb_is_operation_allowed(nb_node, NB_OP_MODIFY)) + nb_op = NB_OP_MODIFY; + else + /* Ignore list keys modifications. */ + return NB_OK; + break; + case SR_OP_DELETED: + /* + * When a list is deleted or one of its keys is changed, we are + * notified about the removal of all of its leafs, even the ones + * that are non-optional. We need to ignore these notifications. + */ + if (!nb_is_operation_allowed(nb_node, NB_OP_DESTROY)) + return NB_OK; + + nb_op = NB_OP_DESTROY; + break; + case SR_OP_MOVED: + nb_op = NB_OP_MOVE; + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unexpected operation %u [xpath %s]", __func__, + sr_op, xpath); + return NB_ERR; + } + + sr_val_to_buff(sr_data, value_str, sizeof(value_str)); + data = yang_data_new(xpath, value_str); + + ret = nb_candidate_edit(candidate, nb_node, nb_op, xpath, NULL, data); + yang_data_free(data); + if (ret != NB_OK) { + flog_warn( + EC_LIB_NB_CANDIDATE_EDIT_ERROR, + "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", + __func__, nb_operation_name(nb_op), xpath); + return NB_ERR; + } + + return NB_OK; +} + +static int frr_sr_config_change_cb_prepare(sr_session_ctx_t *session, + const char *module_name) +{ + sr_change_iter_t *it; + int ret; + sr_change_oper_t sr_op; + sr_val_t *sr_old_val, *sr_new_val; + struct nb_context context = {}; + struct nb_config *candidate; + char errmsg[BUFSIZ] = {0}; + + ret = sr_get_changes_iter(session, "//*", &it); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_LIBSYSREPO, + "%s: sr_get_changes_iter() failed for \"%s\"", + __func__, module_name); + return ret; + } + + candidate = nb_config_dup(running_config); + + while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val, + &sr_new_val)) + == SR_ERR_OK) { + ret = frr_sr_process_change(candidate, sr_op, sr_old_val, + sr_new_val); + sr_free_val(sr_old_val); + sr_free_val(sr_new_val); + if (ret != NB_OK) + break; + } + + sr_free_change_iter(it); + if (ret != NB_OK && ret != SR_ERR_NOT_FOUND) { + nb_config_free(candidate); + return SR_ERR_INTERNAL; + } + + transaction = NULL; + context.client = NB_CLIENT_SYSREPO; + /* + * Validate the configuration changes and allocate all resources + * required to apply them. + */ + ret = nb_candidate_commit_prepare(context, candidate, NULL, + &transaction, false, false, errmsg, + sizeof(errmsg)); + if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { + flog_warn(EC_LIB_LIBSYSREPO, + "%s: failed to prepare configuration transaction: %s (%s)", + __func__, nb_err_name(ret), errmsg); + sr_session_set_error_message(session, errmsg); + } + + if (!transaction) + nb_config_free(candidate); + + /* Map northbound return code to sysrepo return code. */ + switch (ret) { + case NB_OK: + return SR_ERR_OK; + case NB_ERR_NO_CHANGES: + return SR_ERR_OK; + case NB_ERR_LOCKED: + return SR_ERR_LOCKED; + case NB_ERR_RESOURCE: + return SR_ERR_NO_MEMORY; + default: + return SR_ERR_VALIDATION_FAILED; + } +} + +static int frr_sr_config_change_cb_apply(sr_session_ctx_t *session, + const char *module_name) +{ + /* Apply the transaction. */ + if (transaction) { + struct nb_config *candidate = transaction->config; + char errmsg[BUFSIZ] = {0}; + + nb_candidate_commit_apply(transaction, true, NULL, errmsg, + sizeof(errmsg)); + nb_config_free(candidate); + } + + return SR_ERR_OK; +} + +static int frr_sr_config_change_cb_abort(sr_session_ctx_t *session, + const char *module_name) +{ + /* Abort the transaction. */ + if (transaction) { + struct nb_config *candidate = transaction->config; + char errmsg[BUFSIZ] = {0}; + + nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg)); + nb_config_free(candidate); + } + + return SR_ERR_OK; +} + +/* Callback for changes in the running configuration. */ +static int frr_sr_config_change_cb(sr_session_ctx_t *session, uint32_t sub_id, + const char *module_name, const char *xpath, + sr_event_t sr_ev, uint32_t request_id, + void *private_data) +{ + switch (sr_ev) { + case SR_EV_ENABLED: + case SR_EV_CHANGE: + return frr_sr_config_change_cb_prepare(session, module_name); + case SR_EV_DONE: + return frr_sr_config_change_cb_apply(session, module_name); + case SR_EV_ABORT: + return frr_sr_config_change_cb_abort(session, module_name); + case SR_EV_RPC: + case SR_EV_UPDATE: + default: + flog_err(EC_LIB_LIBSYSREPO, "%s: unexpected sysrepo event: %u", + __func__, sr_ev); + return SR_ERR_INTERNAL; + } +} + +/* Callback for state retrieval. */ +static int frr_sr_state_cb(sr_session_ctx_t *session, uint32_t sub_id, + const char *module_name, const char *xpath, + const char *request_xpath, uint32_t request_id, + struct lyd_node **parent, void *private_ctx) +{ + struct lyd_node *dnode = NULL; + + dnode = *parent; + if (nb_oper_iterate_legacy(request_xpath, NULL, 0, NULL, NULL, &dnode)) { + flog_warn(EC_LIB_NB_OPERATIONAL_DATA, + "%s: failed to obtain operational data [xpath %s]", + __func__, xpath); + return SR_ERR_INTERNAL; + } + + *parent = dnode; + + return SR_ERR_OK; +} +static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id, + const char *xpath, const struct lyd_node *input, + sr_event_t sr_ev, uint32_t request_id, + struct lyd_node *output, void *private_ctx) +{ + struct nb_node *nb_node; + int ret = SR_ERR_OK; + char errmsg[BUFSIZ] = {0}; + + nb_node = nb_node_find(xpath); + if (!nb_node) { + flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + return SR_ERR_INTERNAL; + } + + /* Execute callback registered for this XPath. */ + if (nb_callback_rpc(nb_node, xpath, input, output, errmsg, + sizeof(errmsg)) + != NB_OK) { + flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", + __func__, xpath); + ret = SR_ERR_OPERATION_FAILED; + } + + return ret; +} + +static int frr_sr_notification_send(const char *xpath, struct list *arguments) +{ + sr_val_t *values = NULL; + size_t values_cnt = 0; + int ret; + + if (arguments && listcount(arguments) > 0) { + struct yang_data *data; + struct listnode *node; + int i = 0; + + values_cnt = listcount(arguments); + ret = sr_new_values(values_cnt, &values); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", + __func__, sr_strerror(ret)); + return NB_ERR; + } + + for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { + if (yang_data_frr2sr(data, &values[i++]) != 0) { + flog_err( + EC_LIB_SYSREPO_DATA_CONVERT, + "%s: failed to convert data to sysrepo format", + __func__); + sr_free_values(values, values_cnt); + return NB_ERR; + } + } + } + + ret = sr_notif_send(session, xpath, values, values_cnt, 0, 0); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_LIBSYSREPO, + "%s: sr_event_notif_send() failed for xpath %s", + __func__, xpath); + return NB_ERR; + } + + return NB_OK; +} + +static void frr_sr_read_cb(struct event *thread) +{ + struct yang_module *module = EVENT_ARG(thread); + int fd = EVENT_FD(thread); + int ret; + + ret = sr_subscription_process_events(module->sr_subscription, session, + NULL); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", + __func__, sr_strerror(ret)); + return; + } + + event_add_read(master, frr_sr_read_cb, module, fd, &module->sr_thread); +} + +static void frr_sr_subscribe_config(struct yang_module *module) +{ + int ret; + + DEBUGD(&nb_dbg_client_sysrepo, + "sysrepo: subscribing for configuration changes made in the '%s' module", + module->name); + + ret = sr_module_change_subscribe( + session, module->name, NULL, frr_sr_config_change_cb, NULL, 0, + SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED | SR_SUBSCR_NO_THREAD, + &module->sr_subscription); + if (ret != SR_ERR_OK) + flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s", + sr_strerror(ret)); +} + +static int frr_sr_subscribe_state(const struct lysc_node *snode, void *arg) +{ + struct yang_module *module = arg; + struct nb_node *nb_node; + int ret; + + if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) + return YANG_ITER_CONTINUE; + /* We only need to subscribe to the root of the state subtrees. */ + if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) + return YANG_ITER_CONTINUE; + + nb_node = snode->priv; + if (!nb_node) + return YANG_ITER_CONTINUE; + + DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing data to '%s'", + nb_node->xpath); + + ret = sr_oper_get_subscribe(session, snode->module->name, + nb_node->xpath, frr_sr_state_cb, NULL, 0, + &module->sr_subscription); + if (ret != SR_ERR_OK) + flog_err(EC_LIB_LIBSYSREPO, "sr_oper_get_items_subscribe(): %s", + sr_strerror(ret)); + + return YANG_ITER_CONTINUE; +} + +static int frr_sr_subscribe_rpc(const struct lysc_node *snode, void *arg) +{ + struct yang_module *module = arg; + struct nb_node *nb_node; + int ret; + + if (snode->nodetype != LYS_RPC) + return YANG_ITER_CONTINUE; + + nb_node = snode->priv; + if (!nb_node) + return YANG_ITER_CONTINUE; + + DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing RPC to '%s'", + nb_node->xpath); + + ret = sr_rpc_subscribe_tree(session, nb_node->xpath, + frr_sr_config_rpc_cb, NULL, 0, 0, + &module->sr_subscription); + if (ret != SR_ERR_OK) + flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s", + sr_strerror(ret)); + + return YANG_ITER_CONTINUE; +} + +/* CLI commands. */ +DEFUN (debug_nb_sr, + debug_nb_sr_cmd, + "[no] debug northbound client sysrepo", + NO_STR + DEBUG_STR + "Northbound debugging\n" + "Northbound client\n" + "Sysrepo\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + bool no = strmatch(argv[0]->text, "no"); + + DEBUG_MODE_SET(&nb_dbg_client_sysrepo, mode, !no); + + return CMD_SUCCESS; +} + +static int frr_sr_debug_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_CONF)) + vty_out(vty, "debug northbound client sysrepo\n"); + + return 0; +} + +static int frr_sr_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&nb_dbg_client_sysrepo); + + return 0; +} + +static void frr_sr_cli_init(void) +{ + hook_register(nb_client_debug_config_write, frr_sr_debug_config_write); + hook_register(nb_client_debug_set_all, frr_sr_debug_set_all); + + install_element(ENABLE_NODE, &debug_nb_sr_cmd); + install_element(CONFIG_NODE, &debug_nb_sr_cmd); +} + +/* FRR's Sysrepo initialization. */ +static int frr_sr_init(void) +{ + struct yang_module *module; + int ret; + + /* Connect to Sysrepo. */ + ret = sr_connect(SR_CONN_DEFAULT, &connection); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_connect(): %s", __func__, + sr_strerror(ret)); + goto cleanup; + } + + /* Start session. */ + ret = sr_session_start(connection, SR_DS_RUNNING, &session); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_session_start(): %s", + __func__, sr_strerror(ret)); + goto cleanup; + } + + /* Perform subscriptions. */ + RB_FOREACH (module, yang_modules, &yang_modules) { + int event_pipe; + + frr_sr_subscribe_config(module); + yang_snodes_iterate(module->info, frr_sr_subscribe_state, 0, + module); + yang_snodes_iterate(module->info, frr_sr_subscribe_rpc, 0, + module); + + /* Watch subscriptions. */ + ret = sr_get_event_pipe(module->sr_subscription, &event_pipe); + if (ret != SR_ERR_OK) { + flog_err(EC_LIB_SYSREPO_INIT, + "%s: sr_get_event_pipe(): %s", __func__, + sr_strerror(ret)); + goto cleanup; + } + event_add_read(master, frr_sr_read_cb, module, event_pipe, + &module->sr_thread); + } + + hook_register(nb_notification_send, frr_sr_notification_send); + + return 0; + +cleanup: + frr_sr_finish(); + + return -1; +} + +static int frr_sr_finish(void) +{ + struct yang_module *module; + + RB_FOREACH (module, yang_modules, &yang_modules) { + if (!module->sr_subscription) + continue; + sr_unsubscribe(module->sr_subscription); + EVENT_OFF(module->sr_thread); + } + + if (session) + sr_session_stop(session); + if (connection) + sr_disconnect(connection); + + return 0; +} + +static int frr_sr_module_config_loaded(struct event_loop *tm) +{ + master = tm; + + if (frr_sr_init() < 0) { + flog_err(EC_LIB_SYSREPO_INIT, + "failed to initialize the Sysrepo module"); + return -1; + } + + hook_register(frr_fini, frr_sr_finish); + + return 0; +} + +static int frr_sr_module_late_init(struct event_loop *tm) +{ + frr_sr_cli_init(); + + return 0; +} + +static int frr_sr_module_init(void) +{ + hook_register(frr_late_init, frr_sr_module_late_init); + hook_register(frr_config_post, frr_sr_module_config_loaded); + + return 0; +} + +FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION, + .description = "FRR sysrepo integration module", + .init = frr_sr_module_init, +); diff --git a/lib/ns.h b/lib/ns.h new file mode 100644 index 0000000..46c0dd7 --- /dev/null +++ b/lib/ns.h @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NS related header. + * Copyright (C) 2014 6WIND S.A. + */ + +#ifndef _ZEBRA_NS_H +#define _ZEBRA_NS_H + +#include "openbsd-tree.h" +#include "linklist.h" +#include "vty.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint32_t ns_id_t; + +/* the default NS ID */ +#define NS_UNKNOWN UINT32_MAX + +/* Default netns directory (Linux) */ +#define NS_RUN_DIR "/var/run/netns" + +#ifdef HAVE_NETNS +#define NS_DEFAULT_NAME "/proc/self/ns/net" +#else /* !HAVE_NETNS */ +#define NS_DEFAULT_NAME "default-netns" +#endif /* HAVE_NETNS */ + +struct ns { + RB_ENTRY(ns) entry; + + /* Identifier, same as the vector index */ + ns_id_t ns_id; + + /* Identifier, mapped on the NSID value */ + ns_id_t internal_ns_id; + + /* Identifier, value of NSID of default netns, + * relative value in that local netns + */ + ns_id_t relative_default_ns; + + /* Name */ + char *name; + + /* File descriptor */ + int fd; + + /* Master list of interfaces belonging to this NS */ + struct list *iflist; + + /* Back Pointer to VRF */ + void *vrf_ctxt; + + /* User data */ + void *info; +}; +RB_HEAD(ns_head, ns); +RB_PROTOTYPE(ns_head, ns, entry, ns_compare) + +/* + * API for managing NETNS. eg from zebra daemon + * one want to manage the list of NETNS, etc... + */ + +/* + * NS hooks + */ + +#define NS_NEW_HOOK 0 /* a new netns is just created */ +#define NS_DELETE_HOOK 1 /* a netns is to be deleted */ +#define NS_ENABLE_HOOK 2 /* a netns is ready to use */ +#define NS_DISABLE_HOOK 3 /* a netns is to be unusable */ + +/* + * Add a specific hook ns module. + * @param1: hook type + * @param2: the callback function + * - param 1: the NS ID + * - param 2: the address of the user data pointer (the user data + * can be stored in or freed from there) + */ +extern void ns_add_hook(int type, int (*)(struct ns *)); + + +/* + * NS initializer/destructor + */ + +extern void ns_terminate(void); + +/* API to initialize NETNS managerment + * parameter is the default ns_id + */ +extern void ns_init_management(ns_id_t ns_id, ns_id_t internal_ns_idx); + + +/* + * NS utilities + */ + +/* Create a socket serving for the given NS + */ +int ns_socket(int domain, int type, int protocol, ns_id_t ns_id); + +/* return the path of the NETNS */ +extern char *ns_netns_pathname(struct vty *vty, const char *name); + +/* Parse and execute a function on all the NETNS */ +#define NS_WALK_CONTINUE 0 +#define NS_WALK_STOP 1 + +extern void ns_walk_func(int (*func)(struct ns *, + void *, + void **), + void *param_in, + void **param_out); + +/* API to get the NETNS name, from the ns pointer */ +extern const char *ns_get_name(struct ns *ns); + +/* only called from vrf ( when removing netns from vrf) + * or at VRF termination + */ +extern void ns_delete(struct ns *ns); + +/* return > 0 if netns is available + * called by VRF to check netns backend is available for VRF + */ +extern int ns_have_netns(void); + +/* API to get context information of a NS */ +extern void *ns_info_lookup(ns_id_t ns_id); + +/* API to map internal ns id value with + * user friendly ns id external value + */ +extern ns_id_t ns_map_nsid_with_external(ns_id_t ns_id, bool map); + +/* + * NS init routine + * should be called from backendx + */ +extern void ns_init(void); + +#define NS_DEFAULT 0 + +/* API that can be used to change from NS */ +extern int ns_switchback_to_initial(void); +extern int ns_switch_to_netns(const char *netns_name); + +/* + * NS handling routines. + * called by modules that use NS backend + */ + +/* API to search for already present NETNS */ +extern struct ns *ns_lookup(ns_id_t ns_id); +extern struct ns *ns_lookup_name(const char *name); + +/* API to handle NS : creation, enable, disable + * for enable, a callback function is passed as parameter + * the callback belongs to the module that uses NS as backend + * upon enabling the NETNS, the upper layer is informed + */ +extern int ns_enable(struct ns *ns, void (*func)(ns_id_t, void *)); +extern struct ns *ns_get_created(struct ns *ns, char *name, ns_id_t ns_id); +extern ns_id_t ns_id_get_absolute(ns_id_t ns_id_reference, ns_id_t link_nsid); +extern void ns_disable(struct ns *ns); +extern struct ns *ns_get_default(void); + +#ifdef __cplusplus +} +#endif + +#endif /*_ZEBRA_NS_H*/ diff --git a/lib/ntop.c b/lib/ntop.c new file mode 100644 index 0000000..89b4d5e --- /dev/null +++ b/lib/ntop.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: ISC +/* + * optimized ntop, about 10x faster than libc versions [as of 2019] + * + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "compiler.h" + +#define pos (*posx) + +static inline void putbyte(uint8_t bytex, char **posx) + __attribute__((always_inline)) OPTIMIZE; + +static inline void putbyte(uint8_t bytex, char **posx) +{ + bool zero = false; + int byte = bytex, tmp, a, b; + + tmp = byte - 200; + if (tmp >= 0) { + *pos++ = '2'; + zero = true; + byte = tmp; + } else { + tmp = byte - 100; + if (tmp >= 0) { + *pos++ = '1'; + zero = true; + byte = tmp; + } + } + + /* make sure the compiler knows the value range of "byte" */ + assume(byte < 100 && byte >= 0); + + b = byte % 10; + a = byte / 10; + if (a || zero) { + *pos++ = '0' + a; + *pos++ = '0' + b; + } else + *pos++ = '0' + b; +} + +static inline void puthex(uint16_t word, char **posx) + __attribute__((always_inline)) OPTIMIZE; + +static inline void puthex(uint16_t word, char **posx) +{ + const char *digits = "0123456789abcdef"; + if (word >= 0x1000) + *pos++ = digits[(word >> 12) & 0xf]; + if (word >= 0x100) + *pos++ = digits[(word >> 8) & 0xf]; + if (word >= 0x10) + *pos++ = digits[(word >> 4) & 0xf]; + *pos++ = digits[word & 0xf]; +} + +#undef pos + +const char *frr_inet_ntop(int af, const void * restrict src, + char * restrict dst, socklen_t size) + __attribute__((flatten)) OPTIMIZE; + +const char *frr_inet_ntop(int af, const void * restrict src, + char * restrict dst, socklen_t size) +{ + const uint8_t *b = src; + /* 8 * "abcd:" for IPv6 + * note: the IPv4-embedded IPv6 syntax is only used for ::A.B.C.D, + * which isn't longer than 40 chars either. even with ::ffff:A.B.C.D + * it's shorter. + */ + char buf[8 * 5], *o = buf; + size_t best = 0, bestlen = 0, curlen = 0, i; + + switch (af) { + case AF_INET: +inet4: + putbyte(b[0], &o); + *o++ = '.'; + putbyte(b[1], &o); + *o++ = '.'; + putbyte(b[2], &o); + *o++ = '.'; + putbyte(b[3], &o); + *o++ = '\0'; + break; + case AF_INET6: + for (i = 0; i < 8; i++) { + if (b[i * 2] || b[i * 2 + 1]) { + if (curlen && curlen > bestlen) { + best = i - curlen; + bestlen = curlen; + } + curlen = 0; + continue; + } + curlen++; + } + if (curlen && curlen > bestlen) { + best = i - curlen; + bestlen = curlen; + } + /* do we want ::ffff:A.B.C.D? */ + if (best == 0 && bestlen == 6) { + *o++ = ':'; + *o++ = ':'; + b += 12; + goto inet4; + } + if (bestlen == 1) + bestlen = 0; + + for (i = 0; i < 8; i++) { + if (bestlen && i == best) { + if (i == 0) + *o++ = ':'; + *o++ = ':'; + continue; + } + if (i > best && i < best + bestlen) { + continue; + } + puthex((b[i * 2] << 8) | b[i * 2 + 1], &o); + + if (i < 7) + *o++ = ':'; + } + *o++ = '\0'; + break; + default: + return NULL; + } + + i = o - buf; + if (i > size) + return NULL; + /* compiler might inline memcpy if it knows the length is short, + * although neither gcc nor clang actually do this currently [2019] + */ + assume(i <= 8 * 5); + memcpy(dst, buf, i); + return dst; +} + +#if !defined(INET_NTOP_NO_OVERRIDE) +/* we want to override libc inet_ntop, but make sure it shows up in backtraces + * as frr_inet_ntop (to avoid confusion while debugging) + */ +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) + __attribute__((alias ("frr_inet_ntop"))); +#endif diff --git a/lib/openbsd-queue.h b/lib/openbsd-queue.h new file mode 100644 index 0000000..df3bbd7 --- /dev/null +++ b/lib/openbsd-queue.h @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues and XOR simple queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * An XOR simple queue is used in the same way as a regular simple queue. + * The difference is that the head structure also includes a "cookie" that + * is XOR'd with the queue pointer (first, last or next) to generate the + * real pointer value. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ + struct name { \ + struct type *slh_first; /* first element */ \ + } + +#define SLIST_HEAD_INITIALIZER(head) \ + { \ + NULL \ + } + +#define SLIST_ENTRY(type) \ + struct { \ + struct type *sle_next; /* next element */ \ + } + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for ((var) = SLIST_FIRST(head); (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) \ + { \ + SLIST_FIRST(head) = SLIST_END(head); \ + } + +#define SLIST_INSERT_AFTER(slistelm, elm, field) \ + do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ + } while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) \ + do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ + } while (0) + +#define SLIST_REMOVE_AFTER(elm, field) \ + do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ + } while (0) + +#define SLIST_REMOVE_HEAD(head, field) \ + do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ + } while (0) + +#define SLIST_REMOVE(head, elm, type, field) \ + do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ + struct name { \ + struct type *lh_first; /* first element */ \ + } + +#define LIST_HEAD_INITIALIZER(head) \ + { \ + NULL \ + } + +#define LIST_ENTRY(type) \ + struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ + } + +/* + * List access methods. + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = LIST_FIRST(head); (var) != LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) \ + do { \ + LIST_FIRST(head) = LIST_END(head); \ + } while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) \ + do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ + } while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) \ + do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ + } while (0) + +#define LIST_INSERT_HEAD(head, elm, field) \ + do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = \ + &(elm)->field.le_next; \ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ + } while (0) + +#define LIST_REMOVE(elm, field) \ + do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ + } while (0) + +#define LIST_REPLACE(elm, elm2, field) \ + do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ + } while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ + struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ + } + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { \ + NULL, &(head).sqh_first \ + } + +#define SIMPLEQ_ENTRY(type) \ + struct { \ + struct type *sqe_next; /* next element */ \ + } + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = SIMPLEQ_FIRST(head); (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) \ + do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ + } while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) \ + do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ + } while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) \ + do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + } while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \ + do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ + } while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) \ + do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ + } while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) \ + do { \ + if (((elm)->field.sqe_next = \ + (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + } while (0) + +#define SIMPLEQ_CONCAT(head1, head2) \ + do { \ + if (!SIMPLEQ_EMPTY((head2))) { \ + *(head1)->sqh_last = (head2)->sqh_first; \ + (head1)->sqh_last = (head2)->sqh_last; \ + SIMPLEQ_INIT((head2)); \ + } \ + } while (0) + +/* + * XOR Simple queue definitions. + */ +#define XSIMPLEQ_HEAD(name, type) \ + struct name { \ + struct type *sqx_first; /* first element */ \ + struct type **sqx_last; /* addr of last next element */ \ + unsigned long sqx_cookie; \ + } + +#define XSIMPLEQ_ENTRY(type) \ + struct { \ + struct type *sqx_next; /* next element */ \ + } + +/* + * XOR Simple queue access methods. + */ +#define XSIMPLEQ_XOR(head, ptr) \ + ((__typeof(ptr))((head)->sqx_cookie ^ (unsigned long)(ptr))) +#define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) +#define XSIMPLEQ_END(head) NULL +#define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) +#define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) + +#define XSIMPLEQ_FOREACH(var, head, field) \ + for ((var) = XSIMPLEQ_FIRST(head); (var) != XSIMPLEQ_END(head); \ + (var) = XSIMPLEQ_NEXT(head, var, field)) + +#define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = XSIMPLEQ_FIRST(head); \ + (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ + (var) = (tvar)) + +/* + * XOR Simple queue functions. + */ +#define XSIMPLEQ_INIT(head) \ + do { \ + arc4random_buf(&(head)->sqx_cookie, \ + sizeof((head)->sqx_cookie)); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ + } while (0) + +#define XSIMPLEQ_INSERT_HEAD(head, elm, field) \ + do { \ + if (((elm)->field.sqx_next = (head)->sqx_first) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ + } while (0) + +#define XSIMPLEQ_INSERT_TAIL(head, elm, field) \ + do { \ + (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ + *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = \ + XSIMPLEQ_XOR(head, (elm)); \ + (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + } while (0) + +#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) \ + do { \ + if (((elm)->field.sqx_next = (listelm)->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ + } while (0) + +#define XSIMPLEQ_REMOVE_HEAD(head, field) \ + do { \ + if (((head)->sqx_first = XSIMPLEQ_XOR(head, (head)->sqx_first) \ + ->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(head)->sqx_first); \ + } while (0) + +#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) \ + do { \ + if (((elm)->field.sqx_next = \ + XSIMPLEQ_XOR(head, (elm)->field.sqx_next) \ + ->field.sqx_next) \ + == XSIMPLEQ_XOR(head, NULL)) \ + (head)->sqx_last = \ + XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ + } while (0) + + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ + struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ + } + +#define TAILQ_HEAD_INITIALIZER(head) \ + { \ + NULL, &(head).tqh_first \ + } + +#define TAILQ_ENTRY(type) \ + struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ + } + +/* + * Tail queue access methods. + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = TAILQ_FIRST(head); (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = TAILQ_LAST(head, headname); (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) \ + && ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) \ + do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ + } while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) \ + do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ + } while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) \ + do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + } while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) \ + do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) \ + != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ + } while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) \ + do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ + } while (0) + +#define TAILQ_REMOVE(head, elm, field) \ + do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ + } while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) \ + do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ + } while (0) + +#define TAILQ_CONCAT(head1, head2, field) \ + do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = \ + (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/lib/openbsd-tree.c b/lib/openbsd-tree.c new file mode 100644 index 0000000..c09ac2f --- /dev/null +++ b/lib/openbsd-tree.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: ISC AND BSD-2-Clause +/* $OpenBSD: subr_tree.c,v 1.9 2017/06/08 03:30:52 dlg Exp $ */ + +/* + * Copyright 2002 Niels Provos + */ + +/* + * Copyright (c) 2016 David Gwynne + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +static inline struct rb_entry *rb_n2e(const struct rb_type *t, void *node) +{ + unsigned long addr = (unsigned long)node; + + return ((struct rb_entry *)(addr + t->t_offset)); +} + +static inline void *rb_e2n(const struct rb_type *t, struct rb_entry *rbe) +{ + unsigned long addr = (unsigned long)rbe; + + return ((void *)(addr - t->t_offset)); +} + +#define RBE_LEFT(_rbe) (_rbe)->rbt_left +#define RBE_RIGHT(_rbe) (_rbe)->rbt_right +#define RBE_PARENT(_rbe) (_rbe)->rbt_parent +#define RBE_COLOR(_rbe) (_rbe)->rbt_color + +#define RBH_ROOT(_rbt) (_rbt)->rbt_root + +static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent) +{ + RBE_PARENT(rbe) = parent; + RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL; + RBE_COLOR(rbe) = RB_RED; +} + +static inline void rbe_set_blackred(struct rb_entry *black, + struct rb_entry *red) +{ + RBE_COLOR(black) = RB_BLACK; + RBE_COLOR(red) = RB_RED; +} + +static inline void rbe_augment(const struct rb_type *t, struct rb_entry *rbe) +{ + (*t->t_augment)(rb_e2n(t, rbe)); +} + +static inline void rbe_if_augment(const struct rb_type *t, struct rb_entry *rbe) +{ + if (t->t_augment != NULL) + rbe_augment(t, rbe); +} + +static inline void rbe_rotate_left(const struct rb_type *t, + struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent; + struct rb_entry *tmp; + + tmp = RBE_RIGHT(rbe); + RBE_RIGHT(rbe) = RBE_LEFT(tmp); + if (RBE_RIGHT(rbe) != NULL) + RBE_PARENT(RBE_LEFT(tmp)) = rbe; + + parent = RBE_PARENT(rbe); + RBE_PARENT(tmp) = parent; + if (parent != NULL) { + if (rbe == RBE_LEFT(parent)) + RBE_LEFT(parent) = tmp; + else + RBE_RIGHT(parent) = tmp; + } else + RBH_ROOT(rbt) = tmp; + + RBE_LEFT(tmp) = rbe; + RBE_PARENT(rbe) = tmp; + + if (t->t_augment != NULL) { + rbe_augment(t, rbe); + rbe_augment(t, tmp); + parent = RBE_PARENT(tmp); + if (parent != NULL) + rbe_augment(t, parent); + } +} + +static inline void rbe_rotate_right(const struct rb_type *t, + struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent; + struct rb_entry *tmp; + + tmp = RBE_LEFT(rbe); + RBE_LEFT(rbe) = RBE_RIGHT(tmp); + if (RBE_LEFT(rbe) != NULL) + RBE_PARENT(RBE_RIGHT(tmp)) = rbe; + + parent = RBE_PARENT(rbe); + RBE_PARENT(tmp) = parent; + if (parent != NULL) { + if (rbe == RBE_LEFT(parent)) + RBE_LEFT(parent) = tmp; + else + RBE_RIGHT(parent) = tmp; + } else + RBH_ROOT(rbt) = tmp; + + RBE_RIGHT(tmp) = rbe; + RBE_PARENT(rbe) = tmp; + + if (t->t_augment != NULL) { + rbe_augment(t, rbe); + rbe_augment(t, tmp); + parent = RBE_PARENT(tmp); + if (parent != NULL) + rbe_augment(t, parent); + } +} + +static inline void rbe_insert_color(const struct rb_type *t, + struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent, *gparent, *tmp; + + while ((parent = RBE_PARENT(rbe)) != NULL + && RBE_COLOR(parent) == RB_RED) { + gparent = RBE_PARENT(parent); + + if (parent == RBE_LEFT(gparent)) { + tmp = RBE_RIGHT(gparent); + if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { + RBE_COLOR(tmp) = RB_BLACK; + rbe_set_blackred(parent, gparent); + rbe = gparent; + continue; + } + + if (RBE_RIGHT(parent) == rbe) { + rbe_rotate_left(t, rbt, parent); + tmp = parent; + parent = rbe; + rbe = tmp; + } + + rbe_set_blackred(parent, gparent); + rbe_rotate_right(t, rbt, gparent); + } else { + tmp = RBE_LEFT(gparent); + if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { + RBE_COLOR(tmp) = RB_BLACK; + rbe_set_blackred(parent, gparent); + rbe = gparent; + continue; + } + + if (RBE_LEFT(parent) == rbe) { + rbe_rotate_right(t, rbt, parent); + tmp = parent; + parent = rbe; + rbe = tmp; + } + + rbe_set_blackred(parent, gparent); + rbe_rotate_left(t, rbt, gparent); + } + } + + RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK; +} + +static inline void rbe_remove_color(const struct rb_type *t, + struct rbt_tree *rbt, + struct rb_entry *parent, + struct rb_entry *rbe) +{ + struct rb_entry *tmp; + + while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK) + && rbe != RBH_ROOT(rbt) && parent) { + if (RBE_LEFT(parent) == rbe) { + tmp = RBE_RIGHT(parent); + if (RBE_COLOR(tmp) == RB_RED) { + rbe_set_blackred(tmp, parent); + rbe_rotate_left(t, rbt, parent); + tmp = RBE_RIGHT(parent); + } + if ((RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) + && (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { + RBE_COLOR(tmp) = RB_RED; + rbe = parent; + parent = RBE_PARENT(rbe); + } else { + if (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) { + struct rb_entry *oleft; + + oleft = RBE_LEFT(tmp); + if (oleft != NULL) + RBE_COLOR(oleft) = RB_BLACK; + + RBE_COLOR(tmp) = RB_RED; + rbe_rotate_right(t, rbt, tmp); + tmp = RBE_RIGHT(parent); + } + + RBE_COLOR(tmp) = RBE_COLOR(parent); + RBE_COLOR(parent) = RB_BLACK; + if (RBE_RIGHT(tmp)) + RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK; + + rbe_rotate_left(t, rbt, parent); + rbe = RBH_ROOT(rbt); + break; + } + } else { + tmp = RBE_LEFT(parent); + if (RBE_COLOR(tmp) == RB_RED) { + rbe_set_blackred(tmp, parent); + rbe_rotate_right(t, rbt, parent); + tmp = RBE_LEFT(parent); + } + + if ((RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) + && (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { + RBE_COLOR(tmp) = RB_RED; + rbe = parent; + parent = RBE_PARENT(rbe); + } else { + if (RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) { + struct rb_entry *oright; + + oright = RBE_RIGHT(tmp); + if (oright != NULL) + RBE_COLOR(oright) = RB_BLACK; + + RBE_COLOR(tmp) = RB_RED; + rbe_rotate_left(t, rbt, tmp); + tmp = RBE_LEFT(parent); + } + + RBE_COLOR(tmp) = RBE_COLOR(parent); + RBE_COLOR(parent) = RB_BLACK; + if (RBE_LEFT(tmp) != NULL) + RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK; + + rbe_rotate_right(t, rbt, parent); + rbe = RBH_ROOT(rbt); + break; + } + } + } + + if (rbe != NULL) + RBE_COLOR(rbe) = RB_BLACK; +} + +static inline struct rb_entry * +rbe_remove(const struct rb_type *t, struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *child, *parent, *old = rbe; + unsigned int color; + + if (RBE_LEFT(rbe) == NULL) + child = RBE_RIGHT(rbe); + else if (RBE_RIGHT(rbe) == NULL) + child = RBE_LEFT(rbe); + else { + struct rb_entry *tmp; + + rbe = RBE_RIGHT(rbe); + while ((tmp = RBE_LEFT(rbe)) != NULL) + rbe = tmp; + + child = RBE_RIGHT(rbe); + parent = RBE_PARENT(rbe); + color = RBE_COLOR(rbe); + if (child != NULL) + RBE_PARENT(child) = parent; + if (parent != NULL) { + if (RBE_LEFT(parent) == rbe) + RBE_LEFT(parent) = child; + else + RBE_RIGHT(parent) = child; + + rbe_if_augment(t, parent); + } else + RBH_ROOT(rbt) = child; + if (RBE_PARENT(rbe) == old) + parent = rbe; + *rbe = *old; + + tmp = RBE_PARENT(old); + if (tmp != NULL) { + if (RBE_LEFT(tmp) == old) + RBE_LEFT(tmp) = rbe; + else + RBE_RIGHT(tmp) = rbe; + + rbe_if_augment(t, tmp); + } else + RBH_ROOT(rbt) = rbe; + + RBE_PARENT(RBE_LEFT(old)) = rbe; + if (RBE_RIGHT(old)) + RBE_PARENT(RBE_RIGHT(old)) = rbe; + + if (t->t_augment != NULL && parent != NULL) { + tmp = parent; + do { + rbe_augment(t, tmp); + tmp = RBE_PARENT(tmp); + } while (tmp != NULL); + } + + goto color; + } + + parent = RBE_PARENT(rbe); + color = RBE_COLOR(rbe); + + if (child != NULL) + RBE_PARENT(child) = parent; + if (parent != NULL) { + if (RBE_LEFT(parent) == rbe) + RBE_LEFT(parent) = child; + else + RBE_RIGHT(parent) = child; + + rbe_if_augment(t, parent); + } else + RBH_ROOT(rbt) = child; +color: + if (color == RB_BLACK) + rbe_remove_color(t, rbt, parent, child); + + return (old); +} + +void *_rb_remove(const struct rb_type *t, struct rbt_tree *rbt, void *elm) +{ + struct rb_entry *rbe = rb_n2e(t, elm); + struct rb_entry *old; + + old = rbe_remove(t, rbt, rbe); + + return (old == NULL ? NULL : rb_e2n(t, old)); +} + +void *_rb_insert(const struct rb_type *t, struct rbt_tree *rbt, void *elm) +{ + struct rb_entry *rbe = rb_n2e(t, elm); + struct rb_entry *tmp; + struct rb_entry *parent = NULL; + void *node; + int comp = 0; + + tmp = RBH_ROOT(rbt); + while (tmp != NULL) { + parent = tmp; + + node = rb_e2n(t, tmp); + comp = (*t->t_compare)(elm, node); + if (comp < 0) + tmp = RBE_LEFT(tmp); + else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return (node); + } + + rbe_set(rbe, parent); + + if (parent != NULL) { + if (comp < 0) + RBE_LEFT(parent) = rbe; + else + RBE_RIGHT(parent) = rbe; + + rbe_if_augment(t, parent); + } else + RBH_ROOT(rbt) = rbe; + + rbe_insert_color(t, rbt, rbe); + + return NULL; +} + +/* Finds the node with the same key as elm */ +void *_rb_find(const struct rb_type *t, const struct rbt_tree *rbt, + const void *key) +{ + struct rb_entry *tmp = RBH_ROOT(rbt); + void *node; + int comp; + + while (tmp != NULL) { + node = rb_e2n(t, tmp); + comp = (*t->t_compare)(key, node); + if (comp < 0) + tmp = RBE_LEFT(tmp); + else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return (node); + } + + return NULL; +} + +/* Finds the first node greater than or equal to the search key */ +void *_rb_nfind(const struct rb_type *t, const struct rbt_tree *rbt, + const void *key) +{ + struct rb_entry *tmp = RBH_ROOT(rbt); + void *node; + void *res = NULL; + int comp; + + while (tmp != NULL) { + node = rb_e2n(t, tmp); + comp = (*t->t_compare)(key, node); + if (comp < 0) { + res = node; + tmp = RBE_LEFT(tmp); + } else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return (node); + } + + return (res); +} + +void *_rb_next(const struct rb_type *t, void *elm) +{ + struct rb_entry *rbe = rb_n2e(t, elm); + + if (RBE_RIGHT(rbe) != NULL) { + rbe = RBE_RIGHT(rbe); + while (RBE_LEFT(rbe) != NULL) + rbe = RBE_LEFT(rbe); + } else { + if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + else { + while (RBE_PARENT(rbe) + && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + rbe = RBE_PARENT(rbe); + } + } + + return (rbe == NULL ? NULL : rb_e2n(t, rbe)); +} + +void *_rb_prev(const struct rb_type *t, void *elm) +{ + struct rb_entry *rbe = rb_n2e(t, elm); + + if (RBE_LEFT(rbe)) { + rbe = RBE_LEFT(rbe); + while (RBE_RIGHT(rbe)) + rbe = RBE_RIGHT(rbe); + } else { + if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + else { + while (RBE_PARENT(rbe) + && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + rbe = RBE_PARENT(rbe); + } + } + + return (rbe == NULL ? NULL : rb_e2n(t, rbe)); +} + +void *_rb_root(const struct rb_type *t, const struct rbt_tree *rbt) +{ + struct rb_entry *rbe = RBH_ROOT(rbt); + + return (rbe == NULL ? rbe : rb_e2n(t, rbe)); +} + +void *_rb_min(const struct rb_type *t, const struct rbt_tree *rbt) +{ + struct rb_entry *rbe = RBH_ROOT(rbt); + struct rb_entry *parent = NULL; + + while (rbe != NULL) { + parent = rbe; + rbe = RBE_LEFT(rbe); + } + + return (parent == NULL ? NULL : rb_e2n(t, parent)); +} + +void *_rb_max(const struct rb_type *t, const struct rbt_tree *rbt) +{ + struct rb_entry *rbe = RBH_ROOT(rbt); + struct rb_entry *parent = NULL; + + while (rbe != NULL) { + parent = rbe; + rbe = RBE_RIGHT(rbe); + } + + return (parent == NULL ? NULL : rb_e2n(t, parent)); +} + +void *_rb_left(const struct rb_type *t, void *node) +{ + struct rb_entry *rbe = rb_n2e(t, node); + rbe = RBE_LEFT(rbe); + return (rbe == NULL ? NULL : rb_e2n(t, rbe)); +} + +void *_rb_right(const struct rb_type *t, void *node) +{ + struct rb_entry *rbe = rb_n2e(t, node); + rbe = RBE_RIGHT(rbe); + return (rbe == NULL ? NULL : rb_e2n(t, rbe)); +} + +void *_rb_parent(const struct rb_type *t, void *node) +{ + struct rb_entry *rbe = rb_n2e(t, node); + rbe = RBE_PARENT(rbe); + return (rbe == NULL ? NULL : rb_e2n(t, rbe)); +} + +void _rb_set_left(const struct rb_type *t, void *node, void *left) +{ + struct rb_entry *rbe = rb_n2e(t, node); + struct rb_entry *rbl = (left == NULL) ? NULL : rb_n2e(t, left); + + RBE_LEFT(rbe) = rbl; +} + +void _rb_set_right(const struct rb_type *t, void *node, void *right) +{ + struct rb_entry *rbe = rb_n2e(t, node); + struct rb_entry *rbr = (right == NULL) ? NULL : rb_n2e(t, right); + + RBE_RIGHT(rbe) = rbr; +} + +void _rb_set_parent(const struct rb_type *t, void *node, void *parent) +{ + struct rb_entry *rbe = rb_n2e(t, node); + struct rb_entry *rbp = (parent == NULL) ? NULL : rb_n2e(t, parent); + + RBE_PARENT(rbe) = rbp; +} + +void _rb_poison(const struct rb_type *t, void *node, unsigned long poison) +{ + struct rb_entry *rbe = rb_n2e(t, node); + + RBE_PARENT(rbe) = RBE_LEFT(rbe) = RBE_RIGHT(rbe) = + (struct rb_entry *)poison; +} + +int _rb_check(const struct rb_type *t, void *node, unsigned long poison) +{ + struct rb_entry *rbe = rb_n2e(t, node); + + return ((unsigned long)RBE_PARENT(rbe) == poison + && (unsigned long)RBE_LEFT(rbe) == poison + && (unsigned long)RBE_RIGHT(rbe) == poison); +} diff --git a/lib/openbsd-tree.h b/lib/openbsd-tree.h new file mode 100644 index 0000000..4f3985b --- /dev/null +++ b/lib/openbsd-tree.h @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: ISC AND BSD-2-Clause +/* $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $ */ +/* + * Copyright 2002 Niels Provos + */ + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ + struct name { \ + struct type *sph_root; /* root of the tree */ \ + } + +#define SPLAY_INITIALIZER(root) \ + { \ + NULL \ + } + +#define SPLAY_INIT(root) \ + do { \ + (root)->sph_root = NULL; \ + } while (0) + +#define SPLAY_ENTRY(type) \ + struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ + } + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) \ + do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ + } while (0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) \ + do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ + } while (0) + +#define SPLAY_LINKLEFT(head, tmp, field) \ + do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ + } while (0) + +#define SPLAY_LINKRIGHT(head, tmp, field) \ + do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ + } while (0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) \ + do { \ + SPLAY_RIGHT(left, field) = \ + SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = \ + SPLAY_RIGHT((head)->sph_root, field); \ + SPLAY_LEFT((head)->sph_root, field) = \ + SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = \ + SPLAY_LEFT(node, field); \ + } while (0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ + void name##_SPLAY(struct name *, struct type *); \ + void name##_SPLAY_MINMAX(struct name *, int); \ + struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ + struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ + /* Finds the node with the same key as elm */ \ + static __inline struct type *name##_SPLAY_FIND(struct name *head, \ + struct type *elm) \ + { \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ + } \ + \ + static __inline struct type *name##_SPLAY_NEXT(struct name *head, \ + struct type *elm) \ + { \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ + } \ + \ + static __inline struct type *name##_SPLAY_MIN_MAX(struct name *head, \ + int val) \ + { \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ + } + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ + struct type *name##_SPLAY_INSERT(struct name *head, struct type *elm) \ + { \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = \ + NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if (__comp < 0) { \ + SPLAY_LEFT(elm, field) = \ + SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = \ + SPLAY_RIGHT((head)->sph_root, field); \ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ + } \ + \ + struct type *name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ + { \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = \ + SPLAY_RIGHT((head)->sph_root, field); \ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = \ + SPLAY_LEFT((head)->sph_root, field); \ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ + } \ + \ + void name##_SPLAY(struct name *head, struct type *elm) \ + { \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ + \ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \ + NULL; \ + __left = __right = &__node; \ + \ + while ((__comp = (cmp)(elm, (head)->sph_root))) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0) { \ + SPLAY_ROTATE_RIGHT(head, __tmp, \ + field); \ + if (SPLAY_LEFT((head)->sph_root, \ + field) \ + == NULL) \ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, \ + field) \ + == NULL) \ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ + } \ + \ + /* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ + void name##_SPLAY_MINMAX(struct name *head, int __comp) \ + { \ + struct type __node, *__left, *__right, *__tmp; \ + \ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = \ + NULL; \ + __left = __right = &__node; \ + \ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0) { \ + SPLAY_ROTATE_RIGHT(head, __tmp, \ + field); \ + if (SPLAY_LEFT((head)->sph_root, \ + field) \ + == NULL) \ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, \ + field) \ + == NULL) \ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ + } + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) \ + (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) \ + (SPLAY_EMPTY(x) ? NULL : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* + * Copyright (c) 2016 David Gwynne + */ + +#define RB_BLACK 0 +#define RB_RED 1 + +struct rb_type { + int (*t_compare)(const void *, const void *); + void (*t_augment)(void *); + unsigned int t_offset; /* offset of rb_entry in type */ +}; + +struct rbt_tree { + struct rb_entry *rbt_root; +}; + +struct rb_entry { + struct rb_entry *rbt_parent; + struct rb_entry *rbt_left; + struct rb_entry *rbt_right; + unsigned int rbt_color; +}; + +#define RB_HEAD(_name, _type) \ + struct _name { \ + struct rbt_tree rbh_root; \ + } + +#define RB_ENTRY(_type) struct rb_entry + +static inline void _rb_init(struct rbt_tree *rbt) +{ + rbt->rbt_root = NULL; +} + +static inline int _rb_empty(const struct rbt_tree *rbt) +{ + return (rbt->rbt_root == NULL); +} + +void *_rb_insert(const struct rb_type *, struct rbt_tree *, void *); +void *_rb_remove(const struct rb_type *, struct rbt_tree *, void *); +void *_rb_find(const struct rb_type *, const struct rbt_tree *, const void *); +void *_rb_nfind(const struct rb_type *, const struct rbt_tree *, const void *); +void *_rb_root(const struct rb_type *, const struct rbt_tree *); +void *_rb_min(const struct rb_type *, const struct rbt_tree *); +void *_rb_max(const struct rb_type *, const struct rbt_tree *); +void *_rb_next(const struct rb_type *, void *); +void *_rb_prev(const struct rb_type *, void *); +void *_rb_left(const struct rb_type *, void *); +void *_rb_right(const struct rb_type *, void *); +void *_rb_parent(const struct rb_type *, void *); +void _rb_set_left(const struct rb_type *, void *, void *); +void _rb_set_right(const struct rb_type *, void *, void *); +void _rb_set_parent(const struct rb_type *, void *, void *); +void _rb_poison(const struct rb_type *, void *, unsigned long); +int _rb_check(const struct rb_type *, void *, unsigned long); + +#define RB_INITIALIZER(_head) { { NULL } } + +#define RB_PROTOTYPE(_name, _type, _field, _cmp) \ + extern const struct rb_type *const _name##_RB_TYPE; \ + \ + __attribute__((__unused__)) static inline void _name##_RB_INIT( \ + struct _name *head) \ + { \ + _rb_init(&head->rbh_root); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_INSERT(struct _name *head, struct _type *elm) \ + { \ + return (struct _type *)_rb_insert(_name##_RB_TYPE, \ + &head->rbh_root, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_REMOVE(struct _name *head, struct _type *elm) \ + { \ + return (struct _type *)_rb_remove(_name##_RB_TYPE, \ + &head->rbh_root, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_FIND(const struct _name *head, \ + const struct _type *key) \ + { \ + return (struct _type *)_rb_find(_name##_RB_TYPE, \ + &head->rbh_root, key); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_NFIND(const struct _name *head, \ + const struct _type *key) \ + { \ + return (struct _type *)_rb_nfind(_name##_RB_TYPE, \ + &head->rbh_root, key); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_ROOT(const struct _name *head) \ + { \ + return (struct _type *)_rb_root(_name##_RB_TYPE, \ + &head->rbh_root); \ + } \ + \ + __attribute__((__unused__)) static inline int _name##_RB_EMPTY( \ + const struct _name *head) \ + { \ + return _rb_empty(&head->rbh_root); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_MIN(const struct _name *head) \ + { \ + return (struct _type *)_rb_min(_name##_RB_TYPE, \ + &head->rbh_root); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_MAX(const struct _name *head) \ + { \ + return (struct _type *)_rb_max(_name##_RB_TYPE, \ + &head->rbh_root); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_NEXT(struct _type *elm) \ + { \ + return (struct _type *)_rb_next(_name##_RB_TYPE, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_PREV(struct _type *elm) \ + { \ + return (struct _type *)_rb_prev(_name##_RB_TYPE, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_LEFT(struct _type *elm) \ + { \ + return (struct _type *)_rb_left(_name##_RB_TYPE, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_RIGHT(struct _type *elm) \ + { \ + return (struct _type *)_rb_right(_name##_RB_TYPE, elm); \ + } \ + \ + __attribute__((__unused__)) static inline struct _type \ + *_name##_RB_PARENT(struct _type *elm) \ + { \ + return (struct _type *)_rb_parent(_name##_RB_TYPE, elm); \ + } \ + \ + __attribute__((__unused__)) static inline void _name##_RB_SET_LEFT( \ + struct _type *elm, struct _type *left) \ + { \ + _rb_set_left(_name##_RB_TYPE, elm, left); \ + } \ + \ + __attribute__((__unused__)) static inline void _name##_RB_SET_RIGHT( \ + struct _type *elm, struct _type *right) \ + { \ + _rb_set_right(_name##_RB_TYPE, elm, right); \ + } \ + \ + __attribute__((__unused__)) static inline void _name##_RB_SET_PARENT( \ + struct _type *elm, struct _type *parent) \ + { \ + _rb_set_parent(_name##_RB_TYPE, elm, parent); \ + } \ + \ + __attribute__((__unused__)) static inline void _name##_RB_POISON( \ + struct _type *elm, unsigned long poison) \ + { \ + _rb_poison(_name##_RB_TYPE, elm, poison); \ + } \ + \ + __attribute__((__unused__)) static inline int _name##_RB_CHECK( \ + struct _type *elm, unsigned long poison) \ + { \ + return _rb_check(_name##_RB_TYPE, elm, poison); \ + } + +#define RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug) \ + static int _name##_RB_COMPARE(const void *lptr, const void *rptr) \ + { \ + const struct _type *l = lptr, *r = rptr; \ + return _cmp(l, r); \ + } \ + static const struct rb_type _name##_RB_INFO = { \ + _name##_RB_COMPARE, _aug, offsetof(struct _type, _field), \ + }; \ + const struct rb_type *const _name##_RB_TYPE = &_name##_RB_INFO; + +#define RB_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug) \ + static void _name##_RB_AUGMENT(void *ptr) \ + { \ + struct _type *p = ptr; \ + return _aug(p); \ + } \ + RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RB_AUGMENT) + +#define RB_GENERATE(_name, _type, _field, _cmp) \ + RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL) + +#define RB_INIT(_name, _head) _name##_RB_INIT(_head) +#define RB_INSERT(_name, _head, _elm) _name##_RB_INSERT(_head, _elm) +#define RB_REMOVE(_name, _head, _elm) _name##_RB_REMOVE(_head, _elm) +#define RB_FIND(_name, _head, _key) _name##_RB_FIND(_head, _key) +#define RB_NFIND(_name, _head, _key) _name##_RB_NFIND(_head, _key) +#define RB_ROOT(_name, _head) _name##_RB_ROOT(_head) +#define RB_EMPTY(_name, _head) _name##_RB_EMPTY(_head) +#define RB_MIN(_name, _head) _name##_RB_MIN(_head) +#define RB_MAX(_name, _head) _name##_RB_MAX(_head) +#define RB_NEXT(_name, _elm) _name##_RB_NEXT(_elm) +#define RB_PREV(_name, _elm) _name##_RB_PREV(_elm) +#define RB_LEFT(_name, _elm) _name##_RB_LEFT(_elm) +#define RB_RIGHT(_name, _elm) _name##_RB_RIGHT(_elm) +#define RB_PARENT(_name, _elm) _name##_RB_PARENT(_elm) +#define RB_SET_LEFT(_name, _elm, _l) _name##_RB_SET_LEFT(_elm, _l) +#define RB_SET_RIGHT(_name, _elm, _r) _name##_RB_SET_RIGHT(_elm, _r) +#define RB_SET_PARENT(_name, _elm, _p) _name##_RB_SET_PARENT(_elm, _p) +#define RB_POISON(_name, _elm, _p) _name##_RB_POISON(_elm, _p) +#define RB_CHECK(_name, _elm, _p) _name##_RB_CHECK(_elm, _p) + +#define RB_FOREACH(_e, _name, _head) \ + for ((_e) = RB_MIN(_name, (_head)); (_e) != NULL; \ + (_e) = RB_NEXT(_name, (_e))) + +#define RB_FOREACH_SAFE(_e, _name, _head, _n) \ + for ((_e) = RB_MIN(_name, (_head)); \ + (_e) != NULL && ((_n) = RB_NEXT(_name, (_e)), 1); (_e) = (_n)) + +#define RB_FOREACH_REVERSE(_e, _name, _head) \ + for ((_e) = RB_MAX(_name, (_head)); (_e) != NULL; \ + (_e) = RB_PREV(_name, (_e))) + +#define RB_FOREACH_REVERSE_SAFE(_e, _name, _head, _n) \ + for ((_e) = RB_MAX(_name, (_head)); \ + (_e) != NULL && ((_n) = RB_PREV(_name, (_e)), 1); (_e) = (_n)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TREE_H_ */ diff --git a/lib/pbr.h b/lib/pbr.h new file mode 100644 index 0000000..fe2d32a --- /dev/null +++ b/lib/pbr.h @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Policy Based Routing (PBR) main header + * Copyright (C) 2018 6WIND + * Portions: + * Copyright (c) 2021 The MITRE Corporation. + * Copyright (c) 2023 LabN Consulting, L.L.C. + */ + +#ifndef _PBR_H +#define _PBR_H + +#include +#include "stream.h" +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PBR_STR "Policy Based Routing\n" + +/* + * A PBR filter + * + * The filter or match criteria in a PBR rule. + * For simplicity, all supported filters are grouped into a structure rather + * than delineating further. A bitmask denotes which filters are actually + * specified. + */ +struct pbr_filter { + uint32_t filter_bm; +#define PBR_FILTER_SRC_IP (1 << 0) +#define PBR_FILTER_DST_IP (1 << 1) +#define PBR_FILTER_SRC_PORT (1 << 2) +#define PBR_FILTER_DST_PORT (1 << 3) +#define PBR_FILTER_FWMARK (1 << 4) +#define PBR_FILTER_IP_PROTOCOL (1 << 5) +#define PBR_FILTER_SRC_PORT_RANGE (1 << 6) +#define PBR_FILTER_DST_PORT_RANGE (1 << 7) +#define PBR_FILTER_DSCP (1 << 8) +#define PBR_FILTER_ECN (1 << 9) +#define PBR_FILTER_PCP (1 << 10) +#define PBR_FILTER_VLAN_FLAGS (1 << 11) +#define PBR_FILTER_VLAN_ID (1 << 12) + +#define PBR_DSFIELD_DSCP (0xfc) /* Upper 6 bits of DS field: DSCP */ +#define PBR_DSFIELD_ECN (0x03) /* Lower 2 bits of DS field: BCN */ + +#define PBR_PCP (0x07) /* 3-bit value 0..7 for prioritization*/ + +#define PBR_VLAN_FLAGS_NO_WILD 0 +#define PBR_VLAN_FLAGS_TAGGED (1 << 0) +#define PBR_VLAN_FLAGS_UNTAGGED (1 << 1) +#define PBR_VLAN_FLAGS_UNTAGGED_0 (1 << 2) + + /* Source and Destination IP address with masks */ + struct prefix src_ip; + struct prefix dst_ip; + + /* Source and Destination layer 4 (TCP/UDP/etc.) port numbers */ + uint16_t src_port; + uint16_t dst_port; + + /* Filter by VLAN and prioritization */ + uint8_t pcp; + uint16_t vlan_id; + uint16_t vlan_flags; + + /* Filter by Differentiated Services field */ + uint8_t dsfield; /* DSCP (6 bits) & ECN (2 bits) */ + + /* Filter with fwmark */ + uint32_t fwmark; + + /* Filter with the ip protocol */ + uint8_t ip_proto; +}; + +/* + * A PBR action + * + * The action corresponding to a PBR rule. + * While the user specifies the action in a particular way, the forwarding + * plane implementation (Linux only) requires that to be encoded into a + * route table and the rule then point to that route table; in some cases, + * the user criteria may directly point to a table too. + */ +struct pbr_action { + uint32_t flags; + +#define PBR_ACTION_TABLE (1 << 0) +#define PBR_ACTION_QUEUE_ID (1 << 1) +#define PBR_ACTION_PCP (1 << 2) +#define PBR_ACTION_VLAN_ID (1 << 3) +#define PBR_ACTION_VLAN_STRIP_INNER_ANY (1 << 4) +#define PBR_ACTION_SRC_IP (1 << 5) +#define PBR_ACTION_DST_IP (1 << 6) +#define PBR_ACTION_SRC_PORT (1 << 7) +#define PBR_ACTION_DST_PORT (1 << 8) +#define PBR_ACTION_DSCP (1 << 9) +#define PBR_ACTION_ECN (1 << 10) +#define PBR_ACTION_DROP (1 << 11) /* nexthop == blackhole */ + + uint32_t table; + uint32_t queue_id; + + /* VLAN */ + uint8_t pcp; + uint16_t vlan_id; + + /* Source and Destination IP addresses */ + union sockunion src_ip; + union sockunion dst_ip; + + /* Source and Destination layer 4 (TCP/UDP/etc.) port numbers */ + uint32_t src_port; + uint32_t dst_port; + + /* Differentiated Services field */ + uint8_t dscp; /* stored here already shifted to upper 6 bits */ + uint8_t ecn; /* stored here as lower 2 bits */ +}; + +/* + * A PBR rule + * + * This is a combination of the filter criteria and corresponding action. + * Rules also have a user-defined sequence number which defines the relative + * order amongst rules. + */ +struct pbr_rule { + vrf_id_t vrf_id; + uint8_t family; /* netlink: select which rule database */ + + uint32_t seq; + uint32_t priority; + uint32_t unique; + struct pbr_filter filter; + struct pbr_action action; + + char ifname[IFNAMSIZ + 1]; +}; + +/* TCP flags value shared + * those are values of byte 13 of TCP header + * as mentioned in rfc793 + */ +#define TCP_HEADER_FIN (0x01) +#define TCP_HEADER_SYN (0x02) +#define TCP_HEADER_RST (0x04) +#define TCP_HEADER_PSH (0x08) +#define TCP_HEADER_ACK (0x10) +#define TCP_HEADER_URG (0x20) +#define TCP_HEADER_ALL_FLAGS (TCP_HEADER_FIN | TCP_HEADER_SYN \ + | TCP_HEADER_RST | TCP_HEADER_PSH \ + | TCP_HEADER_ACK | TCP_HEADER_URG) + +/* Pbr IPTable defines + * those are common flags shared between BGP and Zebra + */ +#define MATCH_IP_SRC_SET (1 << 0) +#define MATCH_IP_DST_SET (1 << 1) +#define MATCH_PORT_SRC_SET (1 << 2) +#define MATCH_PORT_DST_SET (1 << 3) +#define MATCH_PORT_SRC_RANGE_SET (1 << 4) +#define MATCH_PORT_DST_RANGE_SET (1 << 5) +#define MATCH_DSCP_SET (1 << 6) +#define MATCH_DSCP_INVERSE_SET (1 << 7) +#define MATCH_PKT_LEN_INVERSE_SET (1 << 8) +#define MATCH_FRAGMENT_INVERSE_SET (1 << 9) +#define MATCH_ICMP_SET (1 << 10) +#define MATCH_PROTOCOL_SET (1 << 11) +#define MATCH_FLOW_LABEL_SET (1 << 12) +#define MATCH_FLOW_LABEL_INVERSE_SET (1 << 13) + +extern int zapi_pbr_rule_encode(struct stream *s, struct pbr_rule *r); +extern bool zapi_pbr_rule_decode(struct stream *s, struct pbr_rule *r); + +#ifdef __cplusplus +} +#endif + +#endif /* _PBR_H */ diff --git a/lib/pid_output.c b/lib/pid_output.c new file mode 100644 index 0000000..ce1b7d1 --- /dev/null +++ b/lib/pid_output.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Process id output. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include +#include +#include +#include +#include "lib/version.h" +#include "network.h" +#include "lib_errors.h" + +#define PIDFILE_MASK 0644 + +pid_t pid_output(const char *path) +{ + int tmp; + int fd; + pid_t pid; + char buf[16]; + struct flock lock; + mode_t oldumask; + + pid = getpid(); + + oldumask = umask(0777 & ~PIDFILE_MASK); + fd = open(path, O_RDWR | O_CREAT, PIDFILE_MASK); + if (fd < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Can't create pid lock file %s (%s), exiting", + path, safe_strerror(errno)); + umask(oldumask); + exit(1); + } else { + size_t pidsize; + + umask(oldumask); + memset(&lock, 0, sizeof(lock)); + + set_cloexec(fd); + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + + if (fcntl(fd, F_SETLK, &lock) < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Could not lock pid_file %s (%s), exiting. Please ensure that the daemon is not already running", + path, safe_strerror(errno)); + exit(1); + } + + snprintf(buf, sizeof(buf), "%d\n", (int)pid); + pidsize = strlen(buf); + if ((tmp = write(fd, buf, pidsize)) != (int)pidsize) + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "Could not write pid %d to pid_file %s, rc was %d: %s", + (int)pid, path, tmp, safe_strerror(errno)); + else if (ftruncate(fd, pidsize) < 0) + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "Could not truncate pid_file %s to %u bytes: %s", + path, (unsigned int)pidsize, + safe_strerror(errno)); + } + return pid; +} diff --git a/lib/plist.c b/lib/plist.c new file mode 100644 index 0000000..2cfaa7d --- /dev/null +++ b/lib/plist.c @@ -0,0 +1,1709 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Prefix list functions. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "command.h" +#include "memory.h" +#include "plist.h" +#include "sockunion.h" +#include "buffer.h" +#include "log.h" +#include "routemap.h" +#include "lib/json.h" +#include "libfrr.h" + +#include +#include "plist_int.h" + +DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST, "Prefix List"); +DEFINE_MTYPE_STATIC(LIB, MPREFIX_LIST_STR, "Prefix List Str"); +DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_ENTRY, "Prefix List Entry"); +DEFINE_MTYPE_STATIC(LIB, PREFIX_LIST_TRIE, "Prefix List Trie Table"); + +/* not currently changeable, code assumes bytes further down */ +#define PLC_BITS 8 +#define PLC_LEN (1 << PLC_BITS) +#define PLC_MAXLEVELV4 2 /* /24 for IPv4 */ +#define PLC_MAXLEVELV6 4 /* /48 for IPv6 */ +#define PLC_MAXLEVEL 4 /* max(v4,v6) */ + +struct pltrie_entry { + union { + struct pltrie_table *next_table; + struct prefix_list_entry *final_chain; + }; + + struct prefix_list_entry *up_chain; +}; + +struct pltrie_table { + struct pltrie_entry entries[PLC_LEN]; +}; + +/* Master structure of prefix_list. */ +struct prefix_master { + /* The latest update. */ + struct prefix_list *recent; + + /* Hook function which is executed when new prefix_list is added. */ + void (*add_hook)(struct prefix_list *); + + /* Hook function which is executed when prefix_list is deleted. */ + void (*delete_hook)(struct prefix_list *); + + /* number of bytes that have a trie level */ + size_t trie_depth; + + struct plist_head str; +}; +static int prefix_list_compare_func(const struct prefix_list *a, + const struct prefix_list *b); +DECLARE_RBTREE_UNIQ(plist, struct prefix_list, plist_item, + prefix_list_compare_func); + +/* Static structure of IPv4 prefix_list's master. */ +static struct prefix_master prefix_master_ipv4 = { + NULL, NULL, NULL, PLC_MAXLEVELV4, +}; + +/* Static structure of IPv6 prefix-list's master. */ +static struct prefix_master prefix_master_ipv6 = { + NULL, NULL, NULL, PLC_MAXLEVELV6, +}; + +/* Static structure of BGP ORF prefix_list's master. */ +static struct prefix_master prefix_master_orf_v4 = { + NULL, NULL, NULL, PLC_MAXLEVELV4, +}; + +/* Static structure of BGP ORF prefix_list's master. */ +static struct prefix_master prefix_master_orf_v6 = { + NULL, NULL, NULL, PLC_MAXLEVELV6, +}; + +static struct prefix_master *prefix_master_get(afi_t afi, int orf) +{ + if (afi == AFI_IP) + return orf ? &prefix_master_orf_v4 : &prefix_master_ipv4; + if (afi == AFI_IP6) + return orf ? &prefix_master_orf_v6 : &prefix_master_ipv6; + return NULL; +} + +const char *prefix_list_name(struct prefix_list *plist) +{ + return plist->name; +} + +afi_t prefix_list_afi(struct prefix_list *plist) +{ + if (plist->master == &prefix_master_ipv4 + || plist->master == &prefix_master_orf_v4) + return AFI_IP; + return AFI_IP6; +} + +static int prefix_list_compare_func(const struct prefix_list *a, + const struct prefix_list *b) +{ + return strcmp(a->name, b->name); +} + +/* Lookup prefix_list from list of prefix_list by name. */ +static struct prefix_list *prefix_list_lookup_do(afi_t afi, int orf, + const char *name) +{ + struct prefix_list *plist, lookup; + struct prefix_master *master; + + if (name == NULL) + return NULL; + + master = prefix_master_get(afi, orf); + if (master == NULL) + return NULL; + + lookup.name = XSTRDUP(MTYPE_TMP, name); + plist = plist_find(&master->str, &lookup); + XFREE(MTYPE_TMP, lookup.name); + return plist; +} + +struct prefix_list *prefix_list_lookup(afi_t afi, const char *name) +{ + return prefix_list_lookup_do(afi, 0, name); +} + +struct prefix_list *prefix_bgp_orf_lookup(afi_t afi, const char *name) +{ + return prefix_list_lookup_do(afi, 1, name); +} + +static struct prefix_list *prefix_list_new(void) +{ + struct prefix_list *new; + + new = XCALLOC(MTYPE_PREFIX_LIST, sizeof(struct prefix_list)); + return new; +} + +static void prefix_list_free(struct prefix_list *plist) +{ + XFREE(MTYPE_PREFIX_LIST, plist); +} + +struct prefix_list_entry *prefix_list_entry_new(void) +{ + struct prefix_list_entry *new; + + new = XCALLOC(MTYPE_PREFIX_LIST_ENTRY, + sizeof(struct prefix_list_entry)); + return new; +} + +void prefix_list_entry_free(struct prefix_list_entry *pentry) +{ + XFREE(MTYPE_PREFIX_LIST_ENTRY, pentry); +} + +/* Insert new prefix list to list of prefix_list. Each prefix_list + is sorted by the name. */ +static struct prefix_list *prefix_list_insert(afi_t afi, int orf, + const char *name) +{ + struct prefix_list *plist; + struct prefix_master *master; + + master = prefix_master_get(afi, orf); + if (master == NULL) + return NULL; + + /* Allocate new prefix_list and copy given name. */ + plist = prefix_list_new(); + plist->name = XSTRDUP(MTYPE_MPREFIX_LIST_STR, name); + plist->master = master; + plist->trie = + XCALLOC(MTYPE_PREFIX_LIST_TRIE, sizeof(struct pltrie_table)); + + plist_add(&master->str, plist); + + return plist; +} + +struct prefix_list *prefix_list_get(afi_t afi, int orf, const char *name) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup_do(afi, orf, name); + + if (plist == NULL) + plist = prefix_list_insert(afi, orf, name); + return plist; +} + +static void prefix_list_trie_del(struct prefix_list *plist, + struct prefix_list_entry *pentry); + +/* Delete prefix-list from prefix_list_master and free it. */ +void prefix_list_delete(struct prefix_list *plist) +{ + struct prefix_master *master; + struct prefix_list_entry *pentry; + struct prefix_list_entry *next; + + /* If prefix-list contain prefix_list_entry free all of it. */ + for (pentry = plist->head; pentry; pentry = next) { + route_map_notify_pentry_dependencies(plist->name, pentry, + RMAP_EVENT_PLIST_DELETED); + next = pentry->next; + prefix_list_trie_del(plist, pentry); + prefix_list_entry_free(pentry); + plist->count--; + } + + master = plist->master; + + plist_del(&master->str, plist); + + XFREE(MTYPE_TMP, plist->desc); + + /* Make sure master's recent changed prefix-list information is + cleared. */ + master->recent = NULL; + + route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_DELETED); + + if (master->delete_hook) + (*master->delete_hook)(plist); + + XFREE(MTYPE_MPREFIX_LIST_STR, plist->name); + + XFREE(MTYPE_PREFIX_LIST_TRIE, plist->trie); + + prefix_list_free(plist); +} + +static struct prefix_list_entry * +prefix_list_entry_make(struct prefix *prefix, enum prefix_list_type type, + int64_t seq, int le, int ge, bool any) +{ + struct prefix_list_entry *pentry; + + pentry = prefix_list_entry_new(); + + if (any) + pentry->any = true; + + prefix_copy(&pentry->prefix, prefix); + pentry->type = type; + pentry->seq = seq; + pentry->le = le; + pentry->ge = ge; + + return pentry; +} + +/* Add hook function. */ +void prefix_list_add_hook(void (*func)(struct prefix_list *plist)) +{ + prefix_master_ipv4.add_hook = func; + prefix_master_ipv6.add_hook = func; +} + +/* Delete hook function. */ +void prefix_list_delete_hook(void (*func)(struct prefix_list *plist)) +{ + prefix_master_ipv4.delete_hook = func; + prefix_master_ipv6.delete_hook = func; +} + +/* Calculate new sequential number. */ +int64_t prefix_new_seq_get(struct prefix_list *plist) +{ + int64_t maxseq; + int64_t newseq; + struct prefix_list_entry *pentry; + + maxseq = 0; + + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (maxseq < pentry->seq) + maxseq = pentry->seq; + } + + newseq = ((maxseq / 5) * 5) + 5; + + return (newseq > UINT_MAX) ? UINT_MAX : newseq; +} + +/* Return prefix list entry which has same seq number. */ +static struct prefix_list_entry *prefix_seq_check(struct prefix_list *plist, + int64_t seq) +{ + struct prefix_list_entry *pentry; + + for (pentry = plist->head; pentry; pentry = pentry->next) + if (pentry->seq == seq) + return pentry; + return NULL; +} + +struct prefix_list_entry * +prefix_list_entry_lookup(struct prefix_list *plist, struct prefix *prefix, + enum prefix_list_type type, int64_t seq, + int le, int ge) +{ + struct prefix_list_entry *pentry; + + for (pentry = plist->head; pentry; pentry = pentry->next) + if (prefix_same(&pentry->prefix, prefix) + && pentry->type == type) { + if (seq >= 0 && pentry->seq != seq) + continue; + + if (pentry->le != le) + continue; + if (pentry->ge != ge) + continue; + + return pentry; + } + + return NULL; +} + +static void trie_walk_affected(size_t validbits, struct pltrie_table *table, + uint8_t byte, struct prefix_list_entry *object, + void (*fn)(struct prefix_list_entry *object, + struct prefix_list_entry **updptr)) +{ + uint8_t mask; + uint16_t bwalk; + + if (validbits > PLC_BITS) { + fn(object, &table->entries[byte].final_chain); + return; + } + + mask = (1 << (8 - validbits)) - 1; + for (bwalk = byte & ~mask; bwalk <= byte + mask; bwalk++) { + fn(object, &table->entries[bwalk].up_chain); + } +} + +static void trie_uninstall_fn(struct prefix_list_entry *object, + struct prefix_list_entry **updptr) +{ + for (; *updptr; updptr = &(*updptr)->next_best) + if (*updptr == object) { + *updptr = object->next_best; + break; + } +} + +static int trie_table_empty(struct pltrie_table *table) +{ + size_t i; + for (i = 0; i < PLC_LEN; i++) + if (table->entries[i].next_table || table->entries[i].up_chain) + return 0; + return 1; +} + +static void prefix_list_trie_del(struct prefix_list *plist, + struct prefix_list_entry *pentry) +{ + size_t depth, maxdepth = plist->master->trie_depth; + uint8_t *bytes = pentry->prefix.u.val; + size_t validbits = pentry->prefix.prefixlen; + struct pltrie_table *table, **tables[PLC_MAXLEVEL]; + + table = plist->trie; + for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) { + uint8_t byte = bytes[depth]; + assert(table->entries[byte].next_table); + + tables[depth + 1] = &table->entries[byte].next_table; + table = table->entries[byte].next_table; + + validbits -= PLC_BITS; + } + + trie_walk_affected(validbits, table, bytes[depth], pentry, + trie_uninstall_fn); + + for (; depth > 0; depth--) + if (trie_table_empty(*tables[depth])) { + XFREE(MTYPE_PREFIX_LIST_TRIE, *tables[depth]); + } +} + +/** + * Find duplicated prefix entry (same prefix but different entry) in prefix + * list. + */ +static bool prefix_list_entry_is_duplicated(struct prefix_list *list, + struct prefix_list_entry *entry) +{ + size_t depth, maxdepth = list->master->trie_depth; + uint8_t byte, *bytes = entry->prefix.u.val; + size_t validbits = entry->prefix.prefixlen; + struct pltrie_table *table = list->trie; + struct prefix_list_entry *pentry; + + for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) { + byte = bytes[depth]; + if (!table->entries[byte].next_table) + return NULL; + + table = table->entries[byte].next_table; + validbits -= PLC_BITS; + } + + byte = bytes[depth]; + if (validbits > PLC_BITS) + pentry = table->entries[byte].final_chain; + else + pentry = table->entries[byte].up_chain; + + for (; pentry; pentry = pentry->next_best) { + if (pentry == entry) + continue; + if (prefix_same(&pentry->prefix, &entry->prefix)) + return true; + } + + return false; +} + +void prefix_list_entry_delete(struct prefix_list *plist, + struct prefix_list_entry *pentry, + int update_list) +{ + bool duplicate; + + if (plist == NULL || pentry == NULL) + return; + + duplicate = prefix_list_entry_is_duplicated(plist, pentry); + + prefix_list_trie_del(plist, pentry); + + if (pentry->prev) + pentry->prev->next = pentry->next; + else + plist->head = pentry->next; + if (pentry->next) + pentry->next->prev = pentry->prev; + else + plist->tail = pentry->prev; + + if (!duplicate) + route_map_notify_pentry_dependencies(plist->name, pentry, + RMAP_EVENT_PLIST_DELETED); + + prefix_list_entry_free(pentry); + + plist->count--; + + if (update_list) { + route_map_notify_dependencies(plist->name, + RMAP_EVENT_PLIST_DELETED); + if (plist->master->delete_hook) + (*plist->master->delete_hook)(plist); + + if (plist->head == NULL && plist->tail == NULL + && plist->desc == NULL) + prefix_list_delete(plist); + else + plist->master->recent = plist; + } +} + +static void trie_install_fn(struct prefix_list_entry *object, + struct prefix_list_entry **updptr) +{ + while (*updptr) { + if (*updptr == object) + return; + if ((*updptr)->prefix.prefixlen < object->prefix.prefixlen) + break; + if ((*updptr)->prefix.prefixlen == object->prefix.prefixlen + && (*updptr)->seq > object->seq) + break; + updptr = &(*updptr)->next_best; + } + + if (!object->next_best) + object->next_best = *updptr; + else + assert(object->next_best == *updptr || !*updptr); + + *updptr = object; +} + +static void prefix_list_trie_add(struct prefix_list *plist, + struct prefix_list_entry *pentry) +{ + size_t depth = plist->master->trie_depth; + uint8_t *bytes = pentry->prefix.u.val; + size_t validbits = pentry->prefix.prefixlen; + struct pltrie_table *table; + + table = plist->trie; + while (validbits > PLC_BITS && depth > 1) { + if (!table->entries[*bytes].next_table) + table->entries[*bytes].next_table = + XCALLOC(MTYPE_PREFIX_LIST_TRIE, + sizeof(struct pltrie_table)); + table = table->entries[*bytes].next_table; + bytes++; + depth--; + validbits -= PLC_BITS; + } + + trie_walk_affected(validbits, table, *bytes, pentry, trie_install_fn); +} + +static void prefix_list_entry_add(struct prefix_list *plist, + struct prefix_list_entry *pentry) +{ + struct prefix_list_entry *replace; + struct prefix_list_entry *point; + + /* Automatic asignment of seq no. */ + if (pentry->seq == -1) + pentry->seq = prefix_new_seq_get(plist); + + if (plist->tail && pentry->seq > plist->tail->seq) + point = NULL; + else { + /* Is there any same seq prefix list entry? */ + replace = prefix_seq_check(plist, pentry->seq); + if (replace) + prefix_list_entry_delete(plist, replace, 0); + + /* Check insert point. */ + for (point = plist->head; point; point = point->next) + if (point->seq >= pentry->seq) + break; + } + + /* In case of this is the first element of the list. */ + pentry->next = point; + + if (point) { + if (point->prev) + point->prev->next = pentry; + else + plist->head = pentry; + + pentry->prev = point->prev; + point->prev = pentry; + } else { + if (plist->tail) + plist->tail->next = pentry; + else + plist->head = pentry; + + pentry->prev = plist->tail; + plist->tail = pentry; + } + + prefix_list_trie_add(plist, pentry); + + /* Increment count. */ + plist->count++; + + route_map_notify_pentry_dependencies(plist->name, pentry, + RMAP_EVENT_PLIST_ADDED); + + /* Run hook function. */ + if (plist->master->add_hook) + (*plist->master->add_hook)(plist); + + route_map_notify_dependencies(plist->name, RMAP_EVENT_PLIST_ADDED); + plist->master->recent = plist; +} + +/** + * Prefix list entry update start procedure: + * Remove entry from previosly installed master list, tries and notify + * observers. + * + * \param[in] ple prefix list entry. + */ +void prefix_list_entry_update_start(struct prefix_list_entry *ple) +{ + struct prefix_list *pl = ple->pl; + bool duplicate; + + /* Not installed, nothing to do. */ + if (!ple->installed) + return; + + duplicate = prefix_list_entry_is_duplicated(pl, ple); + + prefix_list_trie_del(pl, ple); + + /* List manipulation: shameless copy from `prefix_list_entry_delete`. */ + if (ple->prev) + ple->prev->next = ple->next; + else + pl->head = ple->next; + if (ple->next) + ple->next->prev = ple->prev; + else + pl->tail = ple->prev; + + if (!duplicate) + route_map_notify_pentry_dependencies(pl->name, ple, + RMAP_EVENT_PLIST_DELETED); + pl->count--; + + route_map_notify_dependencies(pl->name, RMAP_EVENT_PLIST_DELETED); + if (pl->master->delete_hook) + (*pl->master->delete_hook)(pl); + + if (pl->head || pl->tail || pl->desc) + pl->master->recent = pl; + + ple->next_best = NULL; + ple->installed = false; +} + +/** + * Prefix list entry update finish procedure: + * Add entry back master list, to the trie, notify observers and call master + * hook. + * + * \param[in] ple prefix list entry. + */ +void prefix_list_entry_update_finish(struct prefix_list_entry *ple) +{ + struct prefix_list *pl = ple->pl; + struct prefix_list_entry *point; + + /* Already installed, nothing to do. */ + if (ple->installed) + return; + + /* + * Check if the entry is installable: + * We can only install entry if at least the prefix is provided (IPv4 + * or IPv6). + */ + if (ple->prefix.family != AF_INET && ple->prefix.family != AF_INET6) + return; + + /* List manipulation: shameless copy from `prefix_list_entry_add`. */ + if (pl->tail && ple->seq > pl->tail->seq) + point = NULL; + else { + /* Check insert point. */ + for (point = pl->head; point; point = point->next) + if (point->seq >= ple->seq) + break; + } + + /* In case of this is the first element of the list. */ + ple->next = point; + + if (point) { + if (point->prev) + point->prev->next = ple; + else + pl->head = ple; + + ple->prev = point->prev; + point->prev = ple; + } else { + if (pl->tail) + pl->tail->next = ple; + else + pl->head = ple; + + ple->prev = pl->tail; + pl->tail = ple; + } + + prefix_list_trie_add(pl, ple); + pl->count++; + + route_map_notify_pentry_dependencies(pl->name, ple, + RMAP_EVENT_PLIST_ADDED); + + /* Run hook function. */ + if (pl->master->add_hook) + (*pl->master->add_hook)(pl); + + route_map_notify_dependencies(pl->name, RMAP_EVENT_PLIST_ADDED); + pl->master->recent = pl; + + ple->installed = true; +} + +/** + * Same as `prefix_list_entry_delete` but without `free()`ing the list if its + * empty. + * + * \param[in] ple prefix list entry. + */ +void prefix_list_entry_delete2(struct prefix_list_entry *ple) +{ + /* Does the boiler plate list removal and entry removal notification. */ + prefix_list_entry_update_start(ple); + + /* Effective `free()` memory. */ + prefix_list_entry_free(ple); +} + +/* Return string of prefix_list_type. */ +static const char *prefix_list_type_str(struct prefix_list_entry *pentry) +{ + switch (pentry->type) { + case PREFIX_PERMIT: + return "permit"; + case PREFIX_DENY: + return "deny"; + default: + return ""; + } +} + +static int prefix_list_entry_match(struct prefix_list_entry *pentry, + const struct prefix *p, bool address_mode) +{ + int ret; + + if (pentry->prefix.family != p->family) + return 0; + + ret = prefix_match(&pentry->prefix, p); + if (!ret) + return 0; + + if (address_mode) + return 1; + + /* In case of le nor ge is specified, exact match is performed. */ + if (!pentry->le && !pentry->ge) { + if (pentry->prefix.prefixlen != p->prefixlen) + return 0; + } else { + if (pentry->le) + if (p->prefixlen > pentry->le) + return 0; + + if (pentry->ge) + if (p->prefixlen < pentry->ge) + return 0; + } + return 1; +} + +enum prefix_list_type prefix_list_apply_ext( + struct prefix_list *plist, + const struct prefix_list_entry **which, + union prefixconstptr object, + bool address_mode) +{ + struct prefix_list_entry *pentry, *pbest = NULL; + + const struct prefix *p = object.p; + const uint8_t *byte = p->u.val; + size_t depth; + size_t validbits = p->prefixlen; + struct pltrie_table *table; + + if (plist == NULL) { + if (which) + *which = NULL; + return PREFIX_DENY; + } + + if (plist->count == 0) { + if (which) + *which = NULL; + return PREFIX_PERMIT; + } + + depth = plist->master->trie_depth; + table = plist->trie; + while (1) { + for (pentry = table->entries[*byte].up_chain; pentry; + pentry = pentry->next_best) { + if (pbest && pbest->seq < pentry->seq) + continue; + if (prefix_list_entry_match(pentry, p, address_mode)) + pbest = pentry; + } + + if (validbits <= PLC_BITS) + break; + validbits -= PLC_BITS; + + if (--depth) { + if (!table->entries[*byte].next_table) + break; + + table = table->entries[*byte].next_table; + byte++; + continue; + } + + for (pentry = table->entries[*byte].final_chain; pentry; + pentry = pentry->next_best) { + if (pbest && pbest->seq < pentry->seq) + continue; + if (prefix_list_entry_match(pentry, p, address_mode)) + pbest = pentry; + } + break; + } + + if (which) { + if (pbest) + *which = pbest; + else + *which = NULL; + } + + if (pbest == NULL) + return PREFIX_DENY; + + pbest->hitcnt++; + return pbest->type; +} + +static void __attribute__((unused)) prefix_list_print(struct prefix_list *plist) +{ + struct prefix_list_entry *pentry; + + if (plist == NULL) + return; + + printf("ip prefix-list %s: %d entries\n", plist->name, plist->count); + + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (pentry->any) + printf("any %s\n", prefix_list_type_str(pentry)); + else { + struct prefix *p; + + p = &pentry->prefix; + + printf(" seq %lld %s %pFX", (long long)pentry->seq, + prefix_list_type_str(pentry), p); + if (pentry->ge) + printf(" ge %d", pentry->ge); + if (pentry->le) + printf(" le %d", pentry->le); + printf("\n"); + } + } +} + +/* Return 1 when plist already include pentry policy. */ +static struct prefix_list_entry * +prefix_entry_dup_check(struct prefix_list *plist, struct prefix_list_entry *new) +{ + size_t depth, maxdepth = plist->master->trie_depth; + uint8_t byte, *bytes = new->prefix.u.val; + size_t validbits = new->prefix.prefixlen; + struct pltrie_table *table; + struct prefix_list_entry *pentry; + int64_t seq = 0; + + if (new->seq == -1) + seq = prefix_new_seq_get(plist); + else + seq = new->seq; + + table = plist->trie; + for (depth = 0; validbits > PLC_BITS && depth < maxdepth - 1; depth++) { + byte = bytes[depth]; + if (!table->entries[byte].next_table) + return NULL; + + table = table->entries[byte].next_table; + validbits -= PLC_BITS; + } + + byte = bytes[depth]; + if (validbits > PLC_BITS) + pentry = table->entries[byte].final_chain; + else + pentry = table->entries[byte].up_chain; + + for (; pentry; pentry = pentry->next_best) { + if (prefix_same(&pentry->prefix, &new->prefix) + && pentry->type == new->type && pentry->le == new->le + && pentry->ge == new->ge && pentry->seq != seq) + return pentry; + } + return NULL; +} + +enum display_type { + normal_display, + summary_display, + detail_display, + sequential_display, + longer_display, + first_match_display +}; + +static void vty_show_prefix_entry(struct vty *vty, json_object *json, afi_t afi, + struct prefix_list *plist, + struct prefix_master *master, + enum display_type dtype, int seqnum) +{ + struct prefix_list_entry *pentry; + json_object *json_pl = NULL; + + /* Print the name of the protocol */ + if (json) { + json_pl = json_object_new_object(); + json_object_object_add(json, plist->name, json_pl); + } else + vty_out(vty, "%s: ", frr_protoname); + + if (dtype == normal_display) { + if (json) { + json_object_string_add(json_pl, "addressFamily", + afi2str(afi)); + json_object_int_add(json_pl, "entries", plist->count); + if (plist->desc) + json_object_string_add(json_pl, "description", + plist->desc); + } else { + vty_out(vty, "ip%s prefix-list %s: %d entries\n", + afi == AFI_IP ? "" : "v6", plist->name, + plist->count); + if (plist->desc) + vty_out(vty, " Description: %s\n", + plist->desc); + } + } else if (dtype == summary_display || dtype == detail_display) { + if (json) { + json_object_string_add(json_pl, "addressFamily", + afi2str(afi)); + if (plist->desc) + json_object_string_add(json_pl, "description", + plist->desc); + json_object_int_add(json_pl, "count", plist->count); + json_object_int_add(json_pl, "rangeEntries", + plist->rangecount); + json_object_int_add(json_pl, "sequenceStart", + plist->head ? plist->head->seq : 0); + json_object_int_add(json_pl, "sequenceEnd", + plist->tail ? plist->tail->seq : 0); + } else { + vty_out(vty, "ip%s prefix-list %s:\n", + afi == AFI_IP ? "" : "v6", plist->name); + + if (plist->desc) + vty_out(vty, " Description: %s\n", + plist->desc); + + vty_out(vty, + " count: %d, range entries: %d, sequences: %" PRId64 + " - %" PRId64 "\n", + plist->count, plist->rangecount, + plist->head ? plist->head->seq : 0, + plist->tail ? plist->tail->seq : 0); + } + } + + if (dtype != summary_display) { + json_object *json_entries = NULL; + + if (json) { + json_entries = json_object_new_array(); + json_object_object_add(json_pl, "entries", + json_entries); + } + + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (dtype == sequential_display + && pentry->seq != seqnum) + continue; + + if (json) { + json_object *json_entry; + + json_entry = json_object_new_object(); + json_object_array_add(json_entries, json_entry); + + json_object_int_add(json_entry, + "sequenceNumber", + pentry->seq); + json_object_string_add( + json_entry, "type", + prefix_list_type_str(pentry)); + json_object_string_addf(json_entry, "prefix", + "%pFX", + &pentry->prefix); + + if (pentry->ge) + json_object_int_add( + json_entry, + "minimumPrefixLength", + pentry->ge); + if (pentry->le) + json_object_int_add( + json_entry, + "maximumPrefixLength", + pentry->le); + + if (dtype == detail_display + || dtype == sequential_display) { + json_object_int_add(json_entry, + "hitCount", + pentry->hitcnt); + json_object_int_add(json_entry, + "referenceCount", + pentry->refcnt); + } + } else { + vty_out(vty, " "); + + vty_out(vty, "seq %" PRId64 " ", pentry->seq); + + vty_out(vty, "%s ", + prefix_list_type_str(pentry)); + + if (pentry->any) + vty_out(vty, "any"); + else { + struct prefix *p = &pentry->prefix; + + vty_out(vty, "%pFX", p); + + if (pentry->ge) + vty_out(vty, " ge %d", + pentry->ge); + if (pentry->le) + vty_out(vty, " le %d", + pentry->le); + } + + if (dtype == detail_display + || dtype == sequential_display) + vty_out(vty, + " (hit count: %ld, refcount: %ld)", + pentry->hitcnt, pentry->refcnt); + + vty_out(vty, "\n"); + } + } + } +} + +static int vty_show_prefix_list(struct vty *vty, afi_t afi, const char *name, + const char *seq, enum display_type dtype, + bool uj) +{ + struct prefix_list *plist; + struct prefix_master *master; + int64_t seqnum = 0; + json_object *json = NULL; + + master = prefix_master_get(afi, 0); + if (master == NULL) + return CMD_WARNING; + + if (uj) + json = json_object_new_object(); + + if (seq) + seqnum = (int64_t)atol(seq); + + if (name) { + plist = prefix_list_lookup(afi, name); + if (!plist) { + if (!uj) + vty_out(vty, + "%% Can't find specified prefix-list\n"); + return CMD_WARNING; + } + vty_show_prefix_entry(vty, json, afi, plist, master, dtype, + seqnum); + } else { + if (dtype == detail_display || dtype == summary_display) { + if (master->recent && !uj) + vty_out(vty, + "Prefix-list with the last deletion/insertion: %s\n", + master->recent->name); + } + + frr_each (plist, &master->str, plist) + vty_show_prefix_entry(vty, json, afi, plist, master, + dtype, seqnum); + } + + return vty_json(vty, json); +} + +static int vty_show_prefix_list_prefix(struct vty *vty, afi_t afi, + const char *name, const char *prefix, + enum display_type type) +{ + struct prefix_list *plist; + struct prefix_list_entry *pentry; + struct prefix p; + int ret; + int match; + + plist = prefix_list_lookup(afi, name); + if (!plist) { + vty_out(vty, "%% Can't find specified prefix-list\n"); + return CMD_WARNING; + } + + ret = str2prefix(prefix, &p); + if (ret <= 0) { + vty_out(vty, "%% prefix is malformed\n"); + return CMD_WARNING; + } + + for (pentry = plist->head; pentry; pentry = pentry->next) { + match = 0; + + if (type == normal_display || type == first_match_display) + if (prefix_same(&p, &pentry->prefix)) + match = 1; + + if (type == longer_display) { + if ((p.family == pentry->prefix.family) + && (prefix_match(&p, &pentry->prefix))) + match = 1; + } + + if (match) { + vty_out(vty, " seq %" PRId64 " %s ", pentry->seq, + prefix_list_type_str(pentry)); + + if (pentry->any) + vty_out(vty, "any"); + else { + struct prefix *pf = &pentry->prefix; + + vty_out(vty, "%pFX", pf); + + if (pentry->ge) + vty_out(vty, " ge %d", pentry->ge); + if (pentry->le) + vty_out(vty, " le %d", pentry->le); + } + + if (type == normal_display + || type == first_match_display) + vty_out(vty, " (hit count: %ld, refcount: %ld)", + pentry->hitcnt, pentry->refcnt); + + vty_out(vty, "\n"); + + if (type == first_match_display) + return CMD_SUCCESS; + } + } + return CMD_SUCCESS; +} + +static int vty_clear_prefix_list(struct vty *vty, afi_t afi, const char *name, + const char *prefix) +{ + struct prefix_master *master; + struct prefix_list *plist; + struct prefix_list_entry *pentry; + int ret; + struct prefix p; + + master = prefix_master_get(afi, 0); + if (master == NULL) + return CMD_WARNING; + + if (name == NULL && prefix == NULL) { + frr_each (plist, &master->str, plist) + for (pentry = plist->head; pentry; + pentry = pentry->next) + pentry->hitcnt = 0; + } else { + plist = prefix_list_lookup(afi, name); + if (!plist) { + vty_out(vty, "%% Can't find specified prefix-list\n"); + return CMD_WARNING; + } + + if (prefix) { + ret = str2prefix(prefix, &p); + if (ret <= 0) { + vty_out(vty, "%% prefix is malformed\n"); + return CMD_WARNING; + } + } + + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (prefix) { + if (pentry->prefix.family == p.family + && prefix_match(&pentry->prefix, &p)) + pentry->hitcnt = 0; + } else + pentry->hitcnt = 0; + } + } + return CMD_SUCCESS; +} + +#include "lib/plist_clippy.c" + +DEFPY_NOSH (show_ip_prefix_list, + show_ip_prefix_list_cmd, + "show ip prefix-list [PREFIXLIST4_NAME$name [seq$dseq (1-4294967295)$arg]] [json$uj]", + SHOW_STR + IP_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "sequence number of an entry\n" + "Sequence number\n" + JSON_STR) +{ + enum display_type dtype = normal_display; + + if (dseq) + dtype = sequential_display; + + return vty_show_prefix_list(vty, AFI_IP, name, arg_str, dtype, + !!uj); +} + +DEFPY (show_ip_prefix_list_prefix, + show_ip_prefix_list_prefix_cmd, + "show ip prefix-list PREFIXLIST4_NAME$name A.B.C.D/M$prefix [longer$dl|first-match$dfm]", + SHOW_STR + IP_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "IP prefix /, e.g., 35.0.0.0/8\n" + "Lookup longer prefix\n" + "First matched prefix\n") +{ + enum display_type dtype = normal_display; + + if (dl) + dtype = longer_display; + else if (dfm) + dtype = first_match_display; + + return vty_show_prefix_list_prefix(vty, AFI_IP, name, prefix_str, + dtype); +} + +DEFPY_NOSH (show_ip_prefix_list_summary, + show_ip_prefix_list_summary_cmd, + "show ip prefix-list summary [PREFIXLIST4_NAME$name] [json$uj]", + SHOW_STR + IP_STR + PREFIX_LIST_STR + "Summary of prefix lists\n" + "Name of a prefix list\n" + JSON_STR) +{ + return vty_show_prefix_list(vty, AFI_IP, name, NULL, + summary_display, !!uj); +} + +DEFPY_NOSH (show_ip_prefix_list_detail, + show_ip_prefix_list_detail_cmd, + "show ip prefix-list detail [PREFIXLIST4_NAME$name] [json$uj]", + SHOW_STR + IP_STR + PREFIX_LIST_STR + "Detail of prefix lists\n" + "Name of a prefix list\n" + JSON_STR) +{ + return vty_show_prefix_list(vty, AFI_IP, name, NULL, + detail_display, !!uj); +} + +DEFPY (clear_ip_prefix_list, + clear_ip_prefix_list_cmd, + "clear ip prefix-list [PREFIXLIST4_NAME$name [A.B.C.D/M$prefix]]", + CLEAR_STR + IP_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "IP prefix /, e.g., 35.0.0.0/8\n") +{ + return vty_clear_prefix_list(vty, AFI_IP, name, prefix_str); +} + +DEFPY_NOSH(show_ipv6_prefix_list, + show_ipv6_prefix_list_cmd, + "show ipv6 prefix-list [PREFIXLIST6_NAME$name [seq$dseq (1-4294967295)$arg]] [json$uj]", + SHOW_STR + IPV6_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "sequence number of an entry\n" + "Sequence number\n" + JSON_STR) +{ + enum display_type dtype = normal_display; + + if (dseq) + dtype = sequential_display; + + return vty_show_prefix_list(vty, AFI_IP6, name, arg_str, dtype, + !!uj); +} + +DEFPY (show_ipv6_prefix_list_prefix, + show_ipv6_prefix_list_prefix_cmd, + "show ipv6 prefix-list PREFIXLIST6_NAME$name X:X::X:X/M$prefix [longer$dl|first-match$dfm]", + SHOW_STR + IPV6_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "IPv6 prefix /, e.g., 3ffe::/16\n" + "Lookup longer prefix\n" + "First matched prefix\n") +{ + enum display_type dtype = normal_display; + + if (dl) + dtype = longer_display; + else if (dfm) + dtype = first_match_display; + + return vty_show_prefix_list_prefix(vty, AFI_IP6, name, + prefix_str, dtype); +} + +DEFPY_NOSH (show_ipv6_prefix_list_summary, + show_ipv6_prefix_list_summary_cmd, + "show ipv6 prefix-list summary [PREFIXLIST6_NAME$name] [json$uj]", + SHOW_STR + IPV6_STR + PREFIX_LIST_STR + "Summary of prefix lists\n" + "Name of a prefix list\n" + JSON_STR) +{ + return vty_show_prefix_list(vty, AFI_IP6, name, NULL, + summary_display, !!uj); +} + +DEFPY_NOSH (show_ipv6_prefix_list_detail, + show_ipv6_prefix_list_detail_cmd, + "show ipv6 prefix-list detail [PREFIXLIST6_NAME$name] [json$uj]", + SHOW_STR + IPV6_STR + PREFIX_LIST_STR + "Detail of prefix lists\n" + "Name of a prefix list\n" + JSON_STR) +{ + return vty_show_prefix_list(vty, AFI_IP6, name, NULL, + detail_display, !!uj); +} + +DEFPY (clear_ipv6_prefix_list, + clear_ipv6_prefix_list_cmd, + "clear ipv6 prefix-list [PREFIXLIST6_NAME$name [X:X::X:X/M$prefix]]", + CLEAR_STR + IPV6_STR + PREFIX_LIST_STR + "Name of a prefix list\n" + "IPv6 prefix /, e.g., 3ffe::/16\n") +{ + return vty_clear_prefix_list(vty, AFI_IP6, name, prefix_str); +} + +DEFPY (debug_prefix_list_match, + debug_prefix_list_match_cmd, + "debug prefix-list WORD$prefix-list match " + " [address-mode$addr_mode]", + DEBUG_STR + "Prefix-list test access\n" + "Name of a prefix list\n" + "Test prefix for prefix list result\n" + "Prefix to test in ip prefix-list\n" + "Prefix to test in ipv6 prefix-list\n" + "Use address matching mode (PIM RP)\n") +{ + struct prefix_list *plist; + const struct prefix_list_entry *entry = NULL; + enum prefix_list_type ret; + + plist = prefix_list_lookup(family2afi(match->family), prefix_list); + if (!plist) { + vty_out(vty, "%% no prefix list named %s for AFI %s\n", + prefix_list, afi2str(family2afi(match->family))); + return CMD_WARNING; + } + + ret = prefix_list_apply_ext(plist, &entry, match, !!addr_mode); + + vty_out(vty, "%s prefix list %s yields %s for %pFX, ", + afi2str(family2afi(match->family)), prefix_list, + ret == PREFIX_DENY ? "DENY" : "PERMIT", match); + + if (!entry) + vty_out(vty, "no match found\n"); + else { + vty_out(vty, "matching entry #%"PRId64": %pFX", entry->seq, + &entry->prefix); + if (entry->ge) + vty_out(vty, " ge %d", entry->ge); + if (entry->le) + vty_out(vty, " le %d", entry->le); + vty_out(vty, "\n"); + } + + /* allow using this in scripts for quick prefix-list member tests */ + return (ret == PREFIX_PERMIT) ? CMD_SUCCESS : CMD_WARNING; +} + +struct stream *prefix_bgp_orf_entry(struct stream *s, struct prefix_list *plist, + uint8_t init_flag, uint8_t permit_flag, + uint8_t deny_flag) +{ + struct prefix_list_entry *pentry; + + if (!plist) + return s; + + for (pentry = plist->head; pentry; pentry = pentry->next) { + uint8_t flag = init_flag; + struct prefix *p = &pentry->prefix; + + flag |= (pentry->type == PREFIX_PERMIT ? permit_flag + : deny_flag); + stream_putc(s, flag); + stream_putl(s, (uint32_t)pentry->seq); + stream_putc(s, (uint8_t)pentry->ge); + stream_putc(s, (uint8_t)pentry->le); + stream_put_prefix(s, p); + } + + return s; +} + +int prefix_bgp_orf_set(char *name, afi_t afi, struct orf_prefix *orfp, + int permit, int set) +{ + struct prefix_list *plist; + struct prefix_list_entry *pentry; + + /* ge and le value check */ + if (orfp->ge && orfp->ge < orfp->p.prefixlen) + return CMD_WARNING_CONFIG_FAILED; + if (orfp->le && orfp->le < orfp->p.prefixlen) + return CMD_WARNING_CONFIG_FAILED; + if (orfp->le && orfp->ge > orfp->le) + return CMD_WARNING_CONFIG_FAILED; + + if (orfp->ge && orfp->le == (afi == AFI_IP ? 32 : 128)) + orfp->le = 0; + + plist = prefix_list_get(afi, 1, name); + if (!plist) + return CMD_WARNING_CONFIG_FAILED; + + apply_mask(&orfp->p); + + if (set) { + pentry = prefix_list_entry_make( + &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY), + orfp->seq, orfp->le, orfp->ge, false); + + if (prefix_entry_dup_check(plist, pentry)) { + prefix_list_entry_free(pentry); + return CMD_WARNING_CONFIG_FAILED; + } + + prefix_list_entry_add(plist, pentry); + } else { + pentry = prefix_list_entry_lookup( + plist, &orfp->p, (permit ? PREFIX_PERMIT : PREFIX_DENY), + orfp->seq, orfp->le, orfp->ge); + + if (!pentry) + return CMD_WARNING_CONFIG_FAILED; + + prefix_list_entry_delete(plist, pentry, 1); + } + + return CMD_SUCCESS; +} + +void prefix_bgp_orf_remove_all(afi_t afi, char *name) +{ + struct prefix_list *plist; + + plist = prefix_bgp_orf_lookup(afi, name); + if (plist) + prefix_list_delete(plist); +} + +/* return prefix count */ +int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name, + bool use_json) +{ + struct prefix_list *plist; + struct prefix_list_entry *pentry; + json_object *json = NULL; + json_object *json_prefix = NULL; + json_object *json_list = NULL; + + plist = prefix_bgp_orf_lookup(afi, name); + if (!plist) + return 0; + + if (!vty) + return plist->count; + + if (use_json) { + json = json_object_new_object(); + json_prefix = json_object_new_object(); + json_list = json_object_new_object(); + + json_object_int_add(json_prefix, "prefixListCounter", + plist->count); + json_object_string_add(json_prefix, "prefixListName", + plist->name); + + for (pentry = plist->head; pentry; pentry = pentry->next) { + struct prefix *p = &pentry->prefix; + char buf_a[BUFSIZ]; + + snprintf(buf_a, sizeof(buf_a), "%pFX", p); + + json_object_int_add(json_list, "seq", pentry->seq); + json_object_string_add(json_list, "seqPrefixListType", + prefix_list_type_str(pentry)); + + if (pentry->ge) + json_object_int_add(json_list, "ge", + pentry->ge); + if (pentry->le) + json_object_int_add(json_list, "le", + pentry->le); + + json_object_object_add(json_prefix, buf_a, json_list); + } + if (afi == AFI_IP) + json_object_object_add(json, "ipPrefixList", + json_prefix); + else + json_object_object_add(json, "ipv6PrefixList", + json_prefix); + + vty_json(vty, json); + } else { + vty_out(vty, "ip%s prefix-list %s: %d entries\n", + afi == AFI_IP ? "" : "v6", plist->name, plist->count); + + for (pentry = plist->head; pentry; pentry = pentry->next) { + struct prefix *p = &pentry->prefix; + + vty_out(vty, " seq %" PRId64 " %s %pFX", pentry->seq, + prefix_list_type_str(pentry), p); + + if (pentry->ge) + vty_out(vty, " ge %d", pentry->ge); + if (pentry->le) + vty_out(vty, " le %d", pentry->le); + + vty_out(vty, "\n"); + } + } + return plist->count; +} + +static void prefix_list_reset_afi(afi_t afi, int orf) +{ + struct prefix_list *plist; + struct prefix_master *master; + + master = prefix_master_get(afi, orf); + if (master == NULL) + return; + + while ((plist = plist_first(&master->str))) { + prefix_list_delete(plist); + } + + master->recent = NULL; +} + +/* Prefix-list node. */ +static struct cmd_node prefix_node = { + .name = "ipv4 prefix list", + .node = PREFIX_NODE, + .prompt = "", +}; + +static void plist_autocomplete_afi(afi_t afi, vector comps, + struct cmd_token *token) +{ + struct prefix_list *plist; + struct prefix_master *master; + + master = prefix_master_get(afi, 0); + if (master == NULL) + return; + + frr_each (plist, &master->str, plist) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, plist->name)); +} + +static void plist_autocomplete(vector comps, struct cmd_token *token) +{ + plist_autocomplete_afi(AFI_IP, comps, token); + plist_autocomplete_afi(AFI_IP6, comps, token); +} + +static void plist4_autocomplete(vector comps, struct cmd_token *token) +{ + plist_autocomplete_afi(AFI_IP, comps, token); +} + +static void plist6_autocomplete(vector comps, struct cmd_token *token) +{ + plist_autocomplete_afi(AFI_IP6, comps, token); +} + +static const struct cmd_variable_handler plist_var_handlers[] = { + {/* "prefix-list WORD" */ + .varname = "prefix_list", + .completions = plist_autocomplete}, + {.tokenname = "PREFIXLIST_NAME", + .completions = plist_autocomplete}, + {.tokenname = "PREFIXLIST4_NAME", + .completions = plist4_autocomplete}, + {.tokenname = "PREFIXLIST6_NAME", + .completions = plist6_autocomplete}, + {.completions = NULL}}; + + +static void prefix_list_init_ipv4(void) +{ + install_node(&prefix_node); + + install_element(VIEW_NODE, &show_ip_prefix_list_cmd); + install_element(VIEW_NODE, &show_ip_prefix_list_prefix_cmd); + install_element(VIEW_NODE, &show_ip_prefix_list_summary_cmd); + install_element(VIEW_NODE, &show_ip_prefix_list_detail_cmd); + + install_element(ENABLE_NODE, &clear_ip_prefix_list_cmd); +} + +/* Prefix-list node. */ +static struct cmd_node prefix_ipv6_node = { + .name = "ipv6 prefix list", + .node = PREFIX_IPV6_NODE, + .prompt = "", +}; + +static void prefix_list_init_ipv6(void) +{ + install_node(&prefix_ipv6_node); + + install_element(VIEW_NODE, &show_ipv6_prefix_list_cmd); + install_element(VIEW_NODE, &show_ipv6_prefix_list_prefix_cmd); + install_element(VIEW_NODE, &show_ipv6_prefix_list_summary_cmd); + install_element(VIEW_NODE, &show_ipv6_prefix_list_detail_cmd); + install_element(VIEW_NODE, &debug_prefix_list_match_cmd); + + install_element(ENABLE_NODE, &clear_ipv6_prefix_list_cmd); +} + +void prefix_list_init(void) +{ + plist_init(&prefix_master_ipv4.str); + plist_init(&prefix_master_orf_v4.str); + plist_init(&prefix_master_ipv6.str); + plist_init(&prefix_master_orf_v6.str); + + cmd_variable_handler_register(plist_var_handlers); + + prefix_list_init_ipv4(); + prefix_list_init_ipv6(); +} + +void prefix_list_reset(void) +{ + prefix_list_reset_afi(AFI_IP, 0); + prefix_list_reset_afi(AFI_IP6, 0); + prefix_list_reset_afi(AFI_IP, 1); + prefix_list_reset_afi(AFI_IP6, 1); +} diff --git a/lib/plist.h b/lib/plist.h new file mode 100644 index 0000000..f31d8e8 --- /dev/null +++ b/lib/plist.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Prefix list functions. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_PLIST_H +#define _QUAGGA_PLIST_H + +#include + +#include "stream.h" +#include "vty.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum prefix_list_type { + PREFIX_DENY, + PREFIX_PERMIT, +}; + +struct prefix_list; +struct prefix_list_entry; + +struct orf_prefix { + uint32_t seq; + uint8_t ge; + uint8_t le; + struct prefix p; +}; + +/* Prototypes. */ +extern void prefix_list_init(void); +extern void prefix_list_reset(void); +extern void prefix_list_add_hook(void (*func)(struct prefix_list *)); +extern void prefix_list_delete_hook(void (*func)(struct prefix_list *)); + +extern const char *prefix_list_name(struct prefix_list *); +extern afi_t prefix_list_afi(struct prefix_list *); +extern struct prefix_list *prefix_list_lookup(afi_t, const char *); + +/* + * prefix_list_apply_which_prefix + * + * Allow calling function to learn which prefix + * caused the DENY or PERMIT. + * + * If no pointer is sent in, do not return anything. + * If it is a empty plist return a NULL pointer. + * + * address_mode = the "prefix" being passed in is really an address, match + * regardless of prefix length (i.e. ge/le are ignored.) prefix->prefixlen + * must be /32. + */ +extern enum prefix_list_type +prefix_list_apply_ext(struct prefix_list *plist, + const struct prefix_list_entry **matches, + union prefixconstptr prefix, + bool address_mode); +#define prefix_list_apply(A, B) \ + prefix_list_apply_ext((A), NULL, (B), false) + +extern struct prefix_list *prefix_bgp_orf_lookup(afi_t, const char *); +extern struct stream *prefix_bgp_orf_entry(struct stream *, + struct prefix_list *, uint8_t, + uint8_t, uint8_t); +extern int prefix_bgp_orf_set(char *, afi_t, struct orf_prefix *, int, int); +extern void prefix_bgp_orf_remove_all(afi_t, char *); +extern int prefix_bgp_show_prefix_list(struct vty *vty, afi_t afi, char *name, + bool use_json); + +extern struct prefix_list *prefix_list_get(afi_t afi, int orf, + const char *name); +extern void prefix_list_delete(struct prefix_list *plist); +extern int64_t prefix_new_seq_get(struct prefix_list *plist); + +extern struct prefix_list_entry *prefix_list_entry_new(void); +extern void prefix_list_entry_delete(struct prefix_list *plist, + struct prefix_list_entry *pentry, + int update_list); +extern struct prefix_list_entry * +prefix_list_entry_lookup(struct prefix_list *plist, struct prefix *prefix, + enum prefix_list_type type, int64_t seq, int le, + int ge); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_PLIST_H */ diff --git a/lib/plist_int.h b/lib/plist_int.h new file mode 100644 index 0000000..d33ac0f --- /dev/null +++ b/lib/plist_int.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Prefix list internal definitions. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_PLIST_INT_H +#define _QUAGGA_PLIST_INT_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct pltrie_table; + +PREDECL_RBTREE_UNIQ(plist); + +struct prefix_list { + char *name; + char *desc; + + struct prefix_master *master; + + int count; + int rangecount; + + struct plist_item plist_item; + + struct prefix_list_entry *head; + struct prefix_list_entry *tail; + + struct pltrie_table *trie; +}; + +/* Each prefix-list's entry. */ +struct prefix_list_entry { + int64_t seq; + + int le; + int ge; + + enum prefix_list_type type; + + bool any; + struct prefix prefix; + + unsigned long refcnt; + unsigned long hitcnt; + + struct prefix_list *pl; + + struct prefix_list_entry *next; + struct prefix_list_entry *prev; + + /* up the chain for best match search */ + struct prefix_list_entry *next_best; + + /* Flag to track trie/list installation status. */ + bool installed; +}; + +extern void prefix_list_entry_free(struct prefix_list_entry *pentry); +extern void prefix_list_entry_delete2(struct prefix_list_entry *ple); +extern void prefix_list_entry_update_start(struct prefix_list_entry *ple); +extern void prefix_list_entry_update_finish(struct prefix_list_entry *ple); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_PLIST_INT_H */ diff --git a/lib/prefix.c b/lib/prefix.c new file mode 100644 index 0000000..2485c3e --- /dev/null +++ b/lib/prefix.c @@ -0,0 +1,1660 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Prefix related functions. + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "ipaddr.h" +#include "vty.h" +#include "sockunion.h" +#include "memory.h" +#include "log.h" +#include "jhash.h" +#include "lib_errors.h" +#include "printfrr.h" +#include "vxlan.h" + +DEFINE_MTYPE_STATIC(LIB, PREFIX, "Prefix"); +DEFINE_MTYPE_STATIC(LIB, PREFIX_FLOWSPEC, "Prefix Flowspec"); + +/* Maskbit. */ +static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff}; + +/* Number of bits in prefix type. */ +#ifndef PNBBY +#define PNBBY 8 +#endif /* PNBBY */ + +#define MASKBIT(offset) ((0xff << (PNBBY - (offset))) & 0xff) + +int is_zero_mac(const struct ethaddr *mac) +{ + int i = 0; + + for (i = 0; i < ETH_ALEN; i++) { + if (mac->octet[i]) + return 0; + } + + return 1; +} + +bool is_bcast_mac(const struct ethaddr *mac) +{ + int i = 0; + + for (i = 0; i < ETH_ALEN; i++) + if (mac->octet[i] != 0xFF) + return false; + + return true; +} + +bool is_mcast_mac(const struct ethaddr *mac) +{ + if ((mac->octet[0] & 0x01) == 0x01) + return true; + + return false; +} + +unsigned int prefix_bit(const uint8_t *prefix, const uint16_t bit_index) +{ + unsigned int offset = bit_index / 8; + unsigned int shift = 7 - (bit_index % 8); + + return (prefix[offset] >> shift) & 1; +} + +int str2family(const char *string) +{ + if (!strcmp("ipv4", string)) + return AF_INET; + else if (!strcmp("ipv6", string)) + return AF_INET6; + else if (!strcmp("ethernet", string)) + return AF_ETHERNET; + else if (!strcmp("evpn", string)) + return AF_EVPN; + return -1; +} + +const char *family2str(int family) +{ + switch (family) { + case AF_INET: + return "IPv4"; + case AF_INET6: + return "IPv6"; + case AF_ETHERNET: + return "Ethernet"; + case AF_EVPN: + return "Evpn"; + } + return "?"; +} + +/* Address Family Identifier to Address Family converter. */ +int afi2family(afi_t afi) +{ + if (afi == AFI_IP) + return AF_INET; + else if (afi == AFI_IP6) + return AF_INET6; + else if (afi == AFI_L2VPN) + return AF_ETHERNET; + /* NOTE: EVPN code should NOT use this interface. */ + return 0; +} + +afi_t family2afi(int family) +{ + if (family == AF_INET) + return AFI_IP; + else if (family == AF_INET6) + return AFI_IP6; + else if (family == AF_ETHERNET || family == AF_EVPN) + return AFI_L2VPN; + return 0; +} + +const char *afi2str_lower(afi_t afi) +{ + switch (afi) { + case AFI_IP: + return "ipv4"; + case AFI_IP6: + return "ipv6"; + case AFI_L2VPN: + return "l2vpn"; + case AFI_MAX: + case AFI_UNSPEC: + return "bad-value"; + } + + assert(!"Reached end of function we should never reach"); +} + +const char *afi2str(afi_t afi) +{ + switch (afi) { + case AFI_IP: + return "IPv4"; + case AFI_IP6: + return "IPv6"; + case AFI_L2VPN: + return "l2vpn"; + case AFI_MAX: + case AFI_UNSPEC: + return "bad-value"; + } + + assert(!"Reached end of function we should never reach"); +} + +const char *safi2str(safi_t safi) +{ + switch (safi) { + case SAFI_UNICAST: + return "unicast"; + case SAFI_MULTICAST: + return "multicast"; + case SAFI_MPLS_VPN: + return "vpn"; + case SAFI_ENCAP: + return "encap"; + case SAFI_EVPN: + return "evpn"; + case SAFI_LABELED_UNICAST: + return "labeled-unicast"; + case SAFI_FLOWSPEC: + return "flowspec"; + case SAFI_UNSPEC: + case SAFI_MAX: + return "unknown"; + } + + assert(!"Reached end of function we should never reach"); +} + +/* If n includes p prefix then return 1 else return 0. */ +int prefix_match(union prefixconstptr unet, union prefixconstptr upfx) +{ + const struct prefix *n = unet.p; + const struct prefix *p = upfx.p; + int offset; + int shift; + const uint8_t *np, *pp; + + /* If n's prefix is longer than p's one return 0. */ + if (n->prefixlen > p->prefixlen) + return 0; + + if (n->family == AF_FLOWSPEC) { + /* prefixlen is unused. look at fs prefix len */ + if (n->u.prefix_flowspec.family != + p->u.prefix_flowspec.family) + return 0; + + if (n->u.prefix_flowspec.prefixlen > + p->u.prefix_flowspec.prefixlen) + return 0; + + /* Set both prefix's head pointer. */ + np = (const uint8_t *)&n->u.prefix_flowspec.ptr; + pp = (const uint8_t *)&p->u.prefix_flowspec.ptr; + + offset = n->u.prefix_flowspec.prefixlen; + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + return 1; + } + + /* Set both prefix's head pointer. */ + np = n->u.val; + pp = p->u.val; + + offset = n->prefixlen / PNBBY; + shift = n->prefixlen % PNBBY; + + if (shift) + if (maskbit[shift] & (np[offset] ^ pp[offset])) + return 0; + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + return 1; + +} + +/* + * n is a type5 evpn prefix. This function tries to see if there is an + * ip-prefix within n which matches prefix p + * If n includes p prefix then return 1 else return 0. + */ +int evpn_type5_prefix_match(const struct prefix *n, const struct prefix *p) +{ + int offset; + int shift; + int prefixlen; + const uint8_t *np, *pp; + struct prefix_evpn *evp; + + if (n->family != AF_EVPN) + return 0; + + evp = (struct prefix_evpn *)n; + pp = p->u.val; + + if ((evp->prefix.route_type != 5) || + (p->family == AF_INET6 && !is_evpn_prefix_ipaddr_v6(evp)) || + (p->family == AF_INET && !is_evpn_prefix_ipaddr_v4(evp)) || + (is_evpn_prefix_ipaddr_none(evp))) + return 0; + + prefixlen = evp->prefix.prefix_addr.ip_prefix_length; + np = evp->prefix.prefix_addr.ip.ip.addrbytes; + + /* If n's prefix is longer than p's one return 0. */ + if (prefixlen > p->prefixlen) + return 0; + + offset = prefixlen / PNBBY; + shift = prefixlen % PNBBY; + + if (shift) + if (maskbit[shift] & (np[offset] ^ pp[offset])) + return 0; + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + return 1; + +} + +/* If n includes p then return 1 else return 0. Prefix mask is not considered */ +int prefix_match_network_statement(union prefixconstptr unet, + union prefixconstptr upfx) +{ + const struct prefix *n = unet.p; + const struct prefix *p = upfx.p; + int offset; + int shift; + const uint8_t *np, *pp; + + /* Set both prefix's head pointer. */ + np = n->u.val; + pp = p->u.val; + + offset = n->prefixlen / PNBBY; + shift = n->prefixlen % PNBBY; + + if (shift) + if (maskbit[shift] & (np[offset] ^ pp[offset])) + return 0; + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + return 1; +} + +#ifdef __clang_analyzer__ +#undef prefix_copy /* cf. prefix.h */ +#endif + +void prefix_copy(union prefixptr udest, union prefixconstptr usrc) +{ + struct prefix *dest = udest.p; + const struct prefix *src = usrc.p; + + dest->family = src->family; + dest->prefixlen = src->prefixlen; + + if (src->family == AF_INET) + dest->u.prefix4 = src->u.prefix4; + else if (src->family == AF_INET6) + dest->u.prefix6 = src->u.prefix6; + else if (src->family == AF_ETHERNET) { + memcpy(&dest->u.prefix_eth, &src->u.prefix_eth, + sizeof(struct ethaddr)); + } else if (src->family == AF_EVPN) { + memcpy(&dest->u.prefix_evpn, &src->u.prefix_evpn, + sizeof(struct evpn_addr)); + } else if (src->family == AF_UNSPEC) { + dest->u.lp.id = src->u.lp.id; + dest->u.lp.adv_router = src->u.lp.adv_router; + } else if (src->family == AF_FLOWSPEC) { + void *temp; + int len; + + len = src->u.prefix_flowspec.prefixlen; + dest->u.prefix_flowspec.prefixlen = + src->u.prefix_flowspec.prefixlen; + dest->u.prefix_flowspec.family = + src->u.prefix_flowspec.family; + dest->family = src->family; + temp = XCALLOC(MTYPE_PREFIX_FLOWSPEC, len); + dest->u.prefix_flowspec.ptr = (uintptr_t)temp; + memcpy((void *)dest->u.prefix_flowspec.ptr, + (void *)src->u.prefix_flowspec.ptr, len); + } else { + flog_err(EC_LIB_DEVELOPMENT, + "prefix_copy(): Unknown address family %d", + src->family); + assert(0); + } +} + +bool evpn_addr_same(const struct evpn_addr *e1, const struct evpn_addr *e2) +{ + if (e1->route_type != e2->route_type) + return false; + if (e1->route_type == BGP_EVPN_AD_ROUTE) + return (!memcmp(&e1->ead_addr.esi.val, + &e2->ead_addr.esi.val, ESI_BYTES) && + e1->ead_addr.eth_tag == e2->ead_addr.eth_tag && + !ipaddr_cmp(&e1->ead_addr.ip, &e2->ead_addr.ip)); + if (e1->route_type == BGP_EVPN_MAC_IP_ROUTE) + return (e1->macip_addr.eth_tag == e2->macip_addr.eth_tag && + e1->macip_addr.ip_prefix_length + == e2->macip_addr.ip_prefix_length && + !memcmp(&e1->macip_addr.mac, + &e2->macip_addr.mac, ETH_ALEN) && + !ipaddr_cmp(&e1->macip_addr.ip, &e2->macip_addr.ip)); + if (e1->route_type == BGP_EVPN_IMET_ROUTE) + return (e1->imet_addr.eth_tag == e2->imet_addr.eth_tag && + e1->imet_addr.ip_prefix_length + == e2->imet_addr.ip_prefix_length && + !ipaddr_cmp(&e1->imet_addr.ip, &e2->imet_addr.ip)); + if (e1->route_type == BGP_EVPN_ES_ROUTE) + return (!memcmp(&e1->es_addr.esi.val, + &e2->es_addr.esi.val, ESI_BYTES) && + e1->es_addr.ip_prefix_length + == e2->es_addr.ip_prefix_length && + !ipaddr_cmp(&e1->es_addr.ip, &e2->es_addr.ip)); + if (e1->route_type == BGP_EVPN_IP_PREFIX_ROUTE) + return (e1->prefix_addr.eth_tag == e2->prefix_addr.eth_tag && + e1->prefix_addr.ip_prefix_length + == e2->prefix_addr.ip_prefix_length && + !ipaddr_cmp(&e1->prefix_addr.ip, &e2->prefix_addr.ip)); + return true; +} + +/* + * Return 1 if the address/netmask contained in the prefix structure + * is the same, and else return 0. For this routine, 'same' requires + * that not only the prefix length and the network part be the same, + * but also the host part. Thus, 10.0.0.1/8 and 10.0.0.2/8 are not + * the same. Note that this routine has the same return value sense + * as '==' (which is different from prefix_cmp). + */ +int prefix_same(union prefixconstptr up1, union prefixconstptr up2) +{ + const struct prefix *p1 = up1.p; + const struct prefix *p2 = up2.p; + + if ((p1 && !p2) || (!p1 && p2)) + return 0; + + if (!p1 && !p2) + return 1; + + if (p1->family == p2->family && p1->prefixlen == p2->prefixlen) { + if (p1->family == AF_INET) + if (IPV4_ADDR_SAME(&p1->u.prefix4, &p2->u.prefix4)) + return 1; + if (p1->family == AF_INET6) + if (IPV6_ADDR_SAME(&p1->u.prefix6.s6_addr, + &p2->u.prefix6.s6_addr)) + return 1; + if (p1->family == AF_ETHERNET) + if (!memcmp(&p1->u.prefix_eth, &p2->u.prefix_eth, + sizeof(struct ethaddr))) + return 1; + if (p1->family == AF_EVPN) + if (evpn_addr_same(&p1->u.prefix_evpn, &p2->u.prefix_evpn)) + return 1; + if (p1->family == AF_FLOWSPEC) { + if (p1->u.prefix_flowspec.family != + p2->u.prefix_flowspec.family) + return 0; + if (p1->u.prefix_flowspec.prefixlen != + p2->u.prefix_flowspec.prefixlen) + return 0; + if (!memcmp(&p1->u.prefix_flowspec.ptr, + &p2->u.prefix_flowspec.ptr, + p2->u.prefix_flowspec.prefixlen)) + return 1; + } + } + return 0; +} + +/* + * Return -1/0/1 comparing the prefixes in a way that gives a full/linear + * order. + * + * Network prefixes are considered the same if the prefix lengths are equal + * and the network parts are the same. Host bits (which are considered masked + * by the prefix length) are not significant. Thus, 10.0.0.1/8 and + * 10.0.0.2/8 are considered equivalent by this routine. Note that + * this routine has the same return sense as strcmp (which is different + * from prefix_same). + */ +int prefix_cmp(union prefixconstptr up1, union prefixconstptr up2) +{ + const struct prefix *p1 = up1.p; + const struct prefix *p2 = up2.p; + int offset; + int shift; + int i; + + /* Set both prefix's head pointer. */ + const uint8_t *pp1; + const uint8_t *pp2; + + if (p1->family != p2->family) + return numcmp(p1->family, p2->family); + if (p1->family == AF_FLOWSPEC) { + pp1 = (const uint8_t *)p1->u.prefix_flowspec.ptr; + pp2 = (const uint8_t *)p2->u.prefix_flowspec.ptr; + + if (p1->u.prefix_flowspec.family != + p2->u.prefix_flowspec.family) + return 1; + + if (p1->u.prefix_flowspec.prefixlen != + p2->u.prefix_flowspec.prefixlen) + return numcmp(p1->u.prefix_flowspec.prefixlen, + p2->u.prefix_flowspec.prefixlen); + + offset = p1->u.prefix_flowspec.prefixlen; + while (offset--) + if (pp1[offset] != pp2[offset]) + return numcmp(pp1[offset], pp2[offset]); + return 0; + } + pp1 = p1->u.val; + pp2 = p2->u.val; + + if (p1->prefixlen != p2->prefixlen) + return numcmp(p1->prefixlen, p2->prefixlen); + offset = p1->prefixlen / PNBBY; + shift = p1->prefixlen % PNBBY; + + i = memcmp(pp1, pp2, offset); + if (i) + return i; + + /* + * At this point offset was the same, if we have shift + * that means we still have data to compare, if shift is + * 0 then we are at the end of the data structure + * and should just return, as that we will be accessing + * memory beyond the end of the party zone + */ + if (shift) + return numcmp(pp1[offset] & maskbit[shift], + pp2[offset] & maskbit[shift]); + + return 0; +} + +/* + * Count the number of common bits in 2 prefixes. The prefix length is + * ignored for this function; the whole prefix is compared. If the prefix + * address families don't match, return -1; otherwise the return value is + * in range 0 ... maximum prefix length for the address family. + */ +int prefix_common_bits(union prefixconstptr ua, union prefixconstptr ub) +{ + const struct prefix *p1 = ua.p; + const struct prefix *p2 = ub.p; + int pos, bit; + int length = 0; + uint8_t xor ; + + /* Set both prefix's head pointer. */ + const uint8_t *pp1 = p1->u.val; + const uint8_t *pp2 = p2->u.val; + + if (p1->family == AF_INET) + length = IPV4_MAX_BYTELEN; + if (p1->family == AF_INET6) + length = IPV6_MAX_BYTELEN; + if (p1->family == AF_ETHERNET) + length = ETH_ALEN; + if (p1->family == AF_EVPN) + length = 8 * sizeof(struct evpn_addr); + + if (p1->family != p2->family || !length) + return -1; + + for (pos = 0; pos < length; pos++) + if (pp1[pos] != pp2[pos]) + break; + if (pos == length) + return pos * 8; + + xor = pp1[pos] ^ pp2[pos]; + for (bit = 0; bit < 8; bit++) + if (xor&(1 << (7 - bit))) + break; + + return pos * 8 + bit; +} + +/* Return prefix family type string. */ +const char *prefix_family_str(union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + + if (p->family == AF_INET) + return "inet"; + if (p->family == AF_INET6) + return "inet6"; + if (p->family == AF_ETHERNET) + return "ether"; + if (p->family == AF_EVPN) + return "evpn"; + return "unspec"; +} + +/* Allocate new prefix_ipv4 structure. */ +struct prefix_ipv4 *prefix_ipv4_new(void) +{ + struct prefix_ipv4 *p; + + /* Call prefix_new to allocate a full-size struct prefix to avoid + problems + where the struct prefix_ipv4 is cast to struct prefix and unallocated + bytes were being referenced (e.g. in structure assignments). */ + p = (struct prefix_ipv4 *)prefix_new(); + p->family = AF_INET; + return p; +} + +/* Free prefix_ipv4 structure. */ +void prefix_ipv4_free(struct prefix_ipv4 **p) +{ + prefix_free((struct prefix **)p); +} + +/* If given string is valid return 1 else return 0 */ +int str2prefix_ipv4(const char *str, struct prefix_ipv4 *p) +{ + int ret; + int plen; + char *pnt; + char *cp; + + /* Find slash inside string. */ + pnt = strchr(str, '/'); + + /* String doesn't contail slash. */ + if (pnt == NULL) { + /* Convert string to prefix. */ + ret = inet_pton(AF_INET, str, &p->prefix); + if (ret == 0) + return 0; + + /* If address doesn't contain slash we assume it host address. + */ + p->family = AF_INET; + p->prefixlen = IPV4_MAX_BITLEN; + + return ret; + } else { + cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); + memcpy(cp, str, pnt - str); + *(cp + (pnt - str)) = '\0'; + ret = inet_pton(AF_INET, cp, &p->prefix); + XFREE(MTYPE_TMP, cp); + if (ret == 0) + return 0; + + /* Get prefix length. */ + plen = (uint8_t)atoi(++pnt); + if (plen > IPV4_MAX_BITLEN) + return 0; + + p->family = AF_INET; + p->prefixlen = plen; + } + + return ret; +} + +/* When string format is invalid return 0. */ +int str2prefix_eth(const char *str, struct prefix_eth *p) +{ + int ret = 0; + int plen = 48; + char *pnt; + char *cp = NULL; + const char *str_addr = str; + unsigned int a[6]; + int i; + bool slash = false; + + if (!strcmp(str, "any")) { + memset(p, 0, sizeof(*p)); + p->family = AF_ETHERNET; + return 1; + } + + /* Find slash inside string. */ + pnt = strchr(str, '/'); + + if (pnt) { + /* Get prefix length. */ + plen = (uint8_t)atoi(++pnt); + if (plen > 48) { + ret = 0; + goto done; + } + + cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); + memcpy(cp, str, pnt - str); + *(cp + (pnt - str)) = '\0'; + + str_addr = cp; + slash = true; + } + + /* Convert string to prefix. */ + if (sscanf(str_addr, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, + a + 3, a + 4, a + 5) + != 6) { + ret = 0; + goto done; + } + for (i = 0; i < 6; ++i) { + p->eth_addr.octet[i] = a[i] & 0xff; + } + p->prefixlen = plen; + p->family = AF_ETHERNET; + + /* + * special case to allow old configurations to work + * Since all zero's is implicitly meant to allow + * a comparison to zero, let's assume + */ + if (!slash && is_zero_mac(&(p->eth_addr))) + p->prefixlen = 0; + + ret = 1; + +done: + XFREE(MTYPE_TMP, cp); + + return ret; +} + +/* Convert masklen into IP address's netmask (network byte order). */ +void masklen2ip(const int masklen, struct in_addr *netmask) +{ + assert(masklen >= 0 && masklen <= IPV4_MAX_BITLEN); + + /* left shift is only defined for less than the size of the type. + * we unconditionally use long long in case the target platform + * has defined behaviour for << 32 (or has a 64-bit left shift) */ + + if (sizeof(unsigned long long) > 4) + netmask->s_addr = htonl(0xffffffffULL << (32 - masklen)); + else + netmask->s_addr = + htonl(masklen ? 0xffffffffU << (32 - masklen) : 0); +} + +/* Convert IP address's netmask into integer. We assume netmask is + * sequential one. Argument netmask should be network byte order. */ +uint8_t ip_masklen(struct in_addr netmask) +{ + uint32_t tmp = ~ntohl(netmask.s_addr); + + /* + * clz: count leading zeroes. sadly, the behaviour of this builtin is + * undefined for a 0 argument, even though most CPUs give 32 + */ + return tmp ? __builtin_clz(tmp) : 32; +} + +/* Apply mask to IPv4 prefix (network byte order). */ +void apply_mask_ipv4(struct prefix_ipv4 *p) +{ + struct in_addr mask; + masklen2ip(p->prefixlen, &mask); + p->prefix.s_addr &= mask.s_addr; +} + +/* If prefix is 0.0.0.0/0 then return 1 else return 0. */ +int prefix_ipv4_any(const struct prefix_ipv4 *p) +{ + return (p->prefix.s_addr == INADDR_ANY && p->prefixlen == 0); +} + +/* Allocate a new ip version 6 route */ +struct prefix_ipv6 *prefix_ipv6_new(void) +{ + struct prefix_ipv6 *p; + + /* Allocate a full-size struct prefix to avoid problems with structure + size mismatches. */ + p = (struct prefix_ipv6 *)prefix_new(); + p->family = AF_INET6; + return p; +} + +/* Free prefix for IPv6. */ +void prefix_ipv6_free(struct prefix_ipv6 **p) +{ + prefix_free((struct prefix **)p); +} + +/* If given string is valid return 1 else return 0 */ +int str2prefix_ipv6(const char *str, struct prefix_ipv6 *p) +{ + char *pnt; + char *cp; + int ret; + + pnt = strchr(str, '/'); + + /* If string doesn't contain `/' treat it as host route. */ + if (pnt == NULL) { + ret = inet_pton(AF_INET6, str, &p->prefix); + if (ret == 0) + return 0; + p->prefixlen = IPV6_MAX_BITLEN; + } else { + int plen; + + cp = XMALLOC(MTYPE_TMP, (pnt - str) + 1); + memcpy(cp, str, pnt - str); + *(cp + (pnt - str)) = '\0'; + ret = inet_pton(AF_INET6, cp, &p->prefix); + XFREE(MTYPE_TMP, cp); + if (ret == 0) + return 0; + plen = (uint8_t)atoi(++pnt); + if (plen > IPV6_MAX_BITLEN) + return 0; + p->prefixlen = plen; + } + p->family = AF_INET6; + + return ret; +} + +/* Convert struct in6_addr netmask into integer. + * FIXME return uint8_t as ip_maskleni() does. */ +int ip6_masklen(struct in6_addr netmask) +{ + if (netmask.s6_addr32[0] != 0xffffffffU) + return __builtin_clz(~ntohl(netmask.s6_addr32[0])); + if (netmask.s6_addr32[1] != 0xffffffffU) + return __builtin_clz(~ntohl(netmask.s6_addr32[1])) + 32; + if (netmask.s6_addr32[2] != 0xffffffffU) + return __builtin_clz(~ntohl(netmask.s6_addr32[2])) + 64; + if (netmask.s6_addr32[3] != 0xffffffffU) + return __builtin_clz(~ntohl(netmask.s6_addr32[3])) + 96; + /* note __builtin_clz(0) is undefined */ + return 128; +} + +void masklen2ip6(const int masklen, struct in6_addr *netmask) +{ + assert(masklen >= 0 && masklen <= IPV6_MAX_BITLEN); + + if (masklen == 0) { + /* note << 32 is undefined */ + memset(netmask, 0, sizeof(*netmask)); + } else if (masklen <= 32) { + netmask->s6_addr32[0] = htonl(0xffffffffU << (32 - masklen)); + netmask->s6_addr32[1] = 0; + netmask->s6_addr32[2] = 0; + netmask->s6_addr32[3] = 0; + } else if (masklen <= 64) { + netmask->s6_addr32[0] = 0xffffffffU; + netmask->s6_addr32[1] = htonl(0xffffffffU << (64 - masklen)); + netmask->s6_addr32[2] = 0; + netmask->s6_addr32[3] = 0; + } else if (masklen <= 96) { + netmask->s6_addr32[0] = 0xffffffffU; + netmask->s6_addr32[1] = 0xffffffffU; + netmask->s6_addr32[2] = htonl(0xffffffffU << (96 - masklen)); + netmask->s6_addr32[3] = 0; + } else { + netmask->s6_addr32[0] = 0xffffffffU; + netmask->s6_addr32[1] = 0xffffffffU; + netmask->s6_addr32[2] = 0xffffffffU; + netmask->s6_addr32[3] = htonl(0xffffffffU << (128 - masklen)); + } +} + +void apply_mask_ipv6(struct prefix_ipv6 *p) +{ + uint8_t *pnt; + int index; + int offset; + + index = p->prefixlen / 8; + + if (index < 16) { + pnt = (uint8_t *)&p->prefix; + offset = p->prefixlen % 8; + + pnt[index] &= maskbit[offset]; + index++; + + while (index < 16) + pnt[index++] = 0; + } +} + +void apply_mask(union prefixptr pu) +{ + struct prefix *p = pu.p; + + switch (p->family) { + case AF_INET: + apply_mask_ipv4(pu.p4); + break; + case AF_INET6: + apply_mask_ipv6(pu.p6); + break; + default: + break; + } + return; +} + +/* Utility function of convert between struct prefix <=> union sockunion. */ +struct prefix *sockunion2hostprefix(const union sockunion *su, + struct prefix *prefix) +{ + if (su->sa.sa_family == AF_INET) { + struct prefix_ipv4 *p; + + p = prefix ? (struct prefix_ipv4 *)prefix : prefix_ipv4_new(); + p->family = AF_INET; + p->prefix = su->sin.sin_addr; + p->prefixlen = IPV4_MAX_BITLEN; + return (struct prefix *)p; + } + if (su->sa.sa_family == AF_INET6) { + struct prefix_ipv6 *p; + + p = prefix ? (struct prefix_ipv6 *)prefix : prefix_ipv6_new(); + p->family = AF_INET6; + p->prefixlen = IPV6_MAX_BITLEN; + memcpy(&p->prefix, &su->sin6.sin6_addr, + sizeof(struct in6_addr)); + return (struct prefix *)p; + } + return NULL; +} + +void prefix2sockunion(const struct prefix *p, union sockunion *su) +{ + memset(su, 0, sizeof(*su)); + + su->sa.sa_family = p->family; + if (p->family == AF_INET) + su->sin.sin_addr = p->u.prefix4; + if (p->family == AF_INET6) + memcpy(&su->sin6.sin6_addr, &p->u.prefix6, + sizeof(struct in6_addr)); +} + +int prefix_blen(union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + + switch (p->family) { + case AF_INET: + return IPV4_MAX_BYTELEN; + case AF_INET6: + return IPV6_MAX_BYTELEN; + case AF_ETHERNET: + return ETH_ALEN; + } + return 0; +} + +/* Generic function for conversion string to struct prefix. */ +int str2prefix(const char *str, struct prefix *p) +{ + int ret; + + if (!str || !p) + return 0; + + /* First we try to convert string to struct prefix_ipv4. */ + ret = str2prefix_ipv4(str, (struct prefix_ipv4 *)p); + if (ret) + return ret; + + /* Next we try to convert string to struct prefix_ipv6. */ + ret = str2prefix_ipv6(str, (struct prefix_ipv6 *)p); + if (ret) + return ret; + + /* Next we try to convert string to struct prefix_eth. */ + ret = str2prefix_eth(str, (struct prefix_eth *)p); + if (ret) + return ret; + + return 0; +} + +static const char *prefixevpn_ead2str(const struct prefix_evpn *p, char *str, + int size) +{ + uint8_t family; + char buf[ESI_STR_LEN]; + char buf1[INET6_ADDRSTRLEN]; + + family = IS_IPADDR_V4(&p->prefix.ead_addr.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%u]:[%s]:[%d]:[%s]:[%u]", + p->prefix.route_type, p->prefix.ead_addr.eth_tag, + esi_to_str(&p->prefix.ead_addr.esi, buf, sizeof(buf)), + (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, + inet_ntop(family, &p->prefix.ead_addr.ip.ipaddr_v4, buf1, + sizeof(buf1)), + p->prefix.ead_addr.frag_id); + return str; +} + +static const char *prefixevpn_macip2str(const struct prefix_evpn *p, char *str, + int size) +{ + uint8_t family; + char buf1[ETHER_ADDR_STRLEN]; + char buf2[PREFIX2STR_BUFFER]; + + if (is_evpn_prefix_ipaddr_none(p)) + snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, + p->prefix.macip_addr.eth_tag, 8 * ETH_ALEN, + prefix_mac2str(&p->prefix.macip_addr.mac, buf1, + sizeof(buf1))); + else { + family = is_evpn_prefix_ipaddr_v4(p) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%s]:[%d]:[%s]", + p->prefix.route_type, p->prefix.macip_addr.eth_tag, + 8 * ETH_ALEN, + prefix_mac2str(&p->prefix.macip_addr.mac, buf1, + sizeof(buf1)), + family == AF_INET ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, + inet_ntop(family, &p->prefix.macip_addr.ip.ip.addr, + buf2, PREFIX2STR_BUFFER)); + } + return str; +} + +static const char *prefixevpn_imet2str(const struct prefix_evpn *p, char *str, + int size) +{ + uint8_t family; + char buf[INET6_ADDRSTRLEN]; + + family = IS_IPADDR_V4(&p->prefix.imet_addr.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, + p->prefix.imet_addr.eth_tag, + (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, + inet_ntop(family, &p->prefix.imet_addr.ip.ipaddr_v4, buf, + sizeof(buf))); + + return str; +} + +static const char *prefixevpn_es2str(const struct prefix_evpn *p, char *str, + int size) +{ + uint8_t family; + char buf[ESI_STR_LEN]; + char buf1[INET6_ADDRSTRLEN]; + + family = IS_IPADDR_V4(&p->prefix.es_addr.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%s]:[%d]:[%s]", p->prefix.route_type, + esi_to_str(&p->prefix.es_addr.esi, buf, sizeof(buf)), + (family == AF_INET) ? IPV4_MAX_BITLEN : IPV6_MAX_BITLEN, + inet_ntop(family, &p->prefix.es_addr.ip.ipaddr_v4, buf1, + sizeof(buf1))); + + return str; +} + +static const char *prefixevpn_prefix2str(const struct prefix_evpn *p, char *str, + int size) +{ + uint8_t family; + char buf[INET6_ADDRSTRLEN]; + + family = IS_IPADDR_V4(&p->prefix.prefix_addr.ip) ? AF_INET : AF_INET6; + snprintf(str, size, "[%d]:[%d]:[%d]:[%s]", p->prefix.route_type, + p->prefix.prefix_addr.eth_tag, + p->prefix.prefix_addr.ip_prefix_length, + inet_ntop(family, &p->prefix.prefix_addr.ip.ipaddr_v4, buf, + sizeof(buf))); + return str; +} + +static const char *prefixevpn2str(const struct prefix_evpn *p, char *str, + int size) +{ + switch (p->prefix.route_type) { + case BGP_EVPN_AD_ROUTE: + return prefixevpn_ead2str(p, str, size); + case BGP_EVPN_MAC_IP_ROUTE: + return prefixevpn_macip2str(p, str, size); + case BGP_EVPN_IMET_ROUTE: + return prefixevpn_imet2str(p, str, size); + case BGP_EVPN_ES_ROUTE: + return prefixevpn_es2str(p, str, size); + case BGP_EVPN_IP_PREFIX_ROUTE: + return prefixevpn_prefix2str(p, str, size); + default: + snprintf(str, size, "Unsupported EVPN prefix"); + break; + } + return str; +} + +const char *prefix2str(union prefixconstptr pu, char *str, int size) +{ + const struct prefix *p = pu.p; + char buf[PREFIX2STR_BUFFER]; + int byte, tmp, a, b; + bool z = false; + size_t l; + + switch (p->family) { + case AF_INET: + case AF_INET6: + inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf)); + l = strlen(buf); + buf[l++] = '/'; + byte = p->prefixlen; + tmp = p->prefixlen - 100; + if (tmp >= 0) { + buf[l++] = '1'; + z = true; + byte = tmp; + } + b = byte % 10; + a = byte / 10; + if (a || z) + buf[l++] = '0' + a; + buf[l++] = '0' + b; + buf[l] = '\0'; + strlcpy(str, buf, size); + break; + + case AF_ETHERNET: + snprintf(str, size, "%s/%d", + prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)), + p->prefixlen); + break; + + case AF_EVPN: + prefixevpn2str((const struct prefix_evpn *)p, str, size); + break; + + case AF_FLOWSPEC: + strlcpy(str, "FS prefix", size); + break; + + default: + strlcpy(str, "UNK prefix", size); + break; + } + + return str; +} + +void prefix_mcast_ip_dump(const char *onfail, const struct ipaddr *addr, + char *buf, int buf_size) +{ + if (ipaddr_is_zero(addr)) + strlcpy(buf, "*", buf_size); + else + (void)snprintfrr(buf, buf_size, "%pIA", addr); +} + +static ssize_t prefixhost2str(struct fbuf *fbuf, union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + char buf[PREFIX2STR_BUFFER]; + + switch (p->family) { + case AF_INET: + case AF_INET6: + inet_ntop(p->family, &p->u.prefix, buf, sizeof(buf)); + return bputs(fbuf, buf); + + case AF_ETHERNET: + prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)); + return bputs(fbuf, buf); + + default: + return bprintfrr(fbuf, "{prefix.af=%dPF}", p->family); + } +} + +void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr, + char *buf, int buf_size) +{ + int save_errno = errno; + + if (addr.s_addr == INADDR_ANY) + strlcpy(buf, "*", buf_size); + else { + if (!inet_ntop(AF_INET, &addr, buf, buf_size)) { + if (onfail) + snprintf(buf, buf_size, "%s", onfail); + } + } + + errno = save_errno; +} + +const char *prefix_sg2str(const struct prefix_sg *sg, char *sg_str) +{ + char src_str[INET_ADDRSTRLEN]; + char grp_str[INET_ADDRSTRLEN]; + + prefix_mcast_ip_dump("", &sg->src, src_str, sizeof(src_str)); + prefix_mcast_inet4_dump("", sg->grp, grp_str, sizeof(grp_str)); + snprintf(sg_str, PREFIX_SG_STR_LEN, "(%s,%s)", src_str, grp_str); + + return sg_str; +} + +struct prefix *prefix_new(void) +{ + struct prefix *p; + + p = XCALLOC(MTYPE_PREFIX, sizeof(*p)); + return p; +} + +void prefix_free_lists(void *arg) +{ + struct prefix *p = arg; + + prefix_free(&p); +} + +/* Free prefix structure. */ +void prefix_free(struct prefix **p) +{ + XFREE(MTYPE_PREFIX, *p); +} + +/* Utility function to convert ipv4 prefixes to Classful prefixes */ +void apply_classful_mask_ipv4(struct prefix_ipv4 *p) +{ + + uint32_t destination; + + destination = ntohl(p->prefix.s_addr); + + if (p->prefixlen == IPV4_MAX_BITLEN) + ; + /* do nothing for host routes */ + else if (IN_CLASSC(destination)) { + p->prefixlen = 24; + apply_mask_ipv4(p); + } else if (IN_CLASSB(destination)) { + p->prefixlen = 16; + apply_mask_ipv4(p); + } else { + p->prefixlen = 8; + apply_mask_ipv4(p); + } +} + +in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen) +{ + struct in_addr mask; + + masklen2ip(masklen, &mask); + return (masklen != IPV4_MAX_BITLEN - 1) + ? + /* normal case */ + (hostaddr | ~mask.s_addr) + : + /* For prefix 31 return 255.255.255.255 (RFC3021) */ + htonl(0xFFFFFFFF); +} + +/* Utility function to convert ipv4 netmask to prefixes + ex.) "1.1.0.0" "255.255.0.0" => "1.1.0.0/16" + ex.) "1.0.0.0" NULL => "1.0.0.0/8" */ +int netmask_str2prefix_str(const char *net_str, const char *mask_str, + char *prefix_str, size_t prefix_str_len) +{ + struct in_addr network; + struct in_addr mask; + uint8_t prefixlen; + uint32_t destination; + int ret; + + ret = inet_aton(net_str, &network); + if (!ret) + return 0; + + if (mask_str) { + ret = inet_aton(mask_str, &mask); + if (!ret) + return 0; + + prefixlen = ip_masklen(mask); + } else { + destination = ntohl(network.s_addr); + + if (network.s_addr == INADDR_ANY) + prefixlen = 0; + else if (IN_CLASSC(destination)) + prefixlen = 24; + else if (IN_CLASSB(destination)) + prefixlen = 16; + else if (IN_CLASSA(destination)) + prefixlen = 8; + else + return 0; + } + + snprintf(prefix_str, prefix_str_len, "%s/%d", net_str, prefixlen); + + return 1; +} + +/* converts to internal representation of mac address + * returns 1 on success, 0 otherwise + * format accepted: AA:BB:CC:DD:EE:FF + * if mac parameter is null, then check only + */ +int prefix_str2mac(const char *str, struct ethaddr *mac) +{ + unsigned int a[6]; + int i; + + if (!str) + return 0; + + if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x", a + 0, a + 1, a + 2, a + 3, + a + 4, a + 5) + != 6) { + /* error in incoming str length */ + return 0; + } + /* valid mac address */ + if (!mac) + return 1; + for (i = 0; i < 6; ++i) + mac->octet[i] = a[i] & 0xff; + return 1; +} + +char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size) +{ + char *ptr; + + if (!mac) + return NULL; + if (!buf) + ptr = XMALLOC(MTYPE_TMP, ETHER_ADDR_STRLEN * sizeof(char)); + else { + assert(size >= ETHER_ADDR_STRLEN); + ptr = buf; + } + snprintf(ptr, (ETHER_ADDR_STRLEN), "%02x:%02x:%02x:%02x:%02x:%02x", + (uint8_t)mac->octet[0], (uint8_t)mac->octet[1], + (uint8_t)mac->octet[2], (uint8_t)mac->octet[3], + (uint8_t)mac->octet[4], (uint8_t)mac->octet[5]); + return ptr; +} + +unsigned prefix_hash_key(const void *pp) +{ + struct prefix copy; + + if (((struct prefix *)pp)->family == AF_FLOWSPEC) { + uint32_t len; + void *temp; + + /* make sure *all* unused bits are zero, + * particularly including alignment / + * padding and unused prefix bytes. + */ + memset(©, 0, sizeof(copy)); + prefix_copy(©, (struct prefix *)pp); + len = jhash((void *)copy.u.prefix_flowspec.ptr, + copy.u.prefix_flowspec.prefixlen, + 0x55aa5a5a); + temp = (void *)copy.u.prefix_flowspec.ptr; + XFREE(MTYPE_PREFIX_FLOWSPEC, temp); + copy.u.prefix_flowspec.ptr = (uintptr_t)NULL; + return len; + } + /* make sure *all* unused bits are zero, particularly including + * alignment / + * padding and unused prefix bytes. */ + memset(©, 0, sizeof(copy)); + prefix_copy(©, (struct prefix *)pp); + return jhash(©, + offsetof(struct prefix, u.prefix) + PSIZE(copy.prefixlen), + 0x55aa5a5a); +} + +/* converts to internal representation of esi + * returns 1 on success, 0 otherwise + * format accepted: aa:aa:aa:aa:aa:aa:aa:aa:aa:aa + * if esi parameter is null, then check only + */ +int str_to_esi(const char *str, esi_t *esi) +{ + int i; + unsigned int a[ESI_BYTES]; + + if (!str) + return 0; + + if (sscanf(str, "%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x:%2x", + a + 0, a + 1, a + 2, a + 3, + a + 4, a + 5, a + 6, a + 7, + a + 8, a + 9) + != ESI_BYTES) { + /* error in incoming str length */ + return 0; + } + + /* valid ESI */ + if (!esi) + return 1; + for (i = 0; i < ESI_BYTES; ++i) + esi->val[i] = a[i] & 0xff; + return 1; +} + +char *esi_to_str(const esi_t *esi, char *buf, int size) +{ + char *ptr; + + if (!esi) + return NULL; + if (!buf) + ptr = XMALLOC(MTYPE_TMP, ESI_STR_LEN * sizeof(char)); + else { + assert(size >= ESI_STR_LEN); + ptr = buf; + } + + snprintf(ptr, ESI_STR_LEN, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", + esi->val[0], esi->val[1], esi->val[2], + esi->val[3], esi->val[4], esi->val[5], + esi->val[6], esi->val[7], esi->val[8], + esi->val[9]); + return ptr; +} + +char *evpn_es_df_alg2str(uint8_t df_alg, char *buf, int buf_len) +{ + switch (df_alg) { + case EVPN_MH_DF_ALG_SERVICE_CARVING: + snprintf(buf, buf_len, "service-carving"); + break; + + case EVPN_MH_DF_ALG_HRW: + snprintf(buf, buf_len, "HRW"); + break; + + case EVPN_MH_DF_ALG_PREF: + snprintf(buf, buf_len, "preference"); + break; + + default: + snprintf(buf, buf_len, "unknown %u", df_alg); + break; + } + + return buf; +} + +bool ipv4_unicast_valid(const struct in_addr *addr) +{ + in_addr_t ip = ntohl(addr->s_addr); + + if (IPV4_CLASS_D(ip)) + return false; + + if (IPV4_NET0(ip) || IPV4_NET127(ip) || IPV4_CLASS_E(ip)) { + if (cmd_allow_reserved_ranges_get()) + return true; + else + return false; + } + + return true; +} + +static int ipaddr2prefix(const struct ipaddr *ip, uint16_t prefixlen, + struct prefix *p) +{ + switch (ip->ipa_type) { + case (IPADDR_V4): + p->family = AF_INET; + p->u.prefix4 = ip->ipaddr_v4; + p->prefixlen = prefixlen; + break; + case (IPADDR_V6): + p->family = AF_INET6; + p->u.prefix6 = ip->ipaddr_v6; + p->prefixlen = prefixlen; + break; + case (IPADDR_NONE): + p->family = AF_UNSPEC; + break; + } + + return 0; +} + +/* + * Convert type-2 and type-5 evpn route prefixes into the more + * general ipv4/ipv6 prefix types so we can match prefix lists + * and such. + */ +int evpn_prefix2prefix(const struct prefix *evpn, struct prefix *to) +{ + const struct evpn_addr *addr; + + if (evpn->family != AF_EVPN) + return -1; + + addr = &evpn->u.prefix_evpn; + + switch (addr->route_type) { + case BGP_EVPN_MAC_IP_ROUTE: + if (IS_IPADDR_V4(&addr->macip_addr.ip)) + ipaddr2prefix(&addr->macip_addr.ip, IPV4_MAX_BITLEN, + to); + else if (IS_IPADDR_V6(&addr->macip_addr.ip)) + ipaddr2prefix(&addr->macip_addr.ip, IPV6_MAX_BITLEN, + to); + else + return -1; /* mac only? */ + + break; + case BGP_EVPN_IP_PREFIX_ROUTE: + ipaddr2prefix(&addr->prefix_addr.ip, + addr->prefix_addr.ip_prefix_length, to); + break; + default: + return -1; + } + + return 0; +} + +printfrr_ext_autoreg_p("EA", printfrr_ea); +static ssize_t printfrr_ea(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct ethaddr *mac = ptr; + char cbuf[ETHER_ADDR_STRLEN]; + + if (!mac) + return bputs(buf, "(null)"); + + /* need real length even if buffer is too short */ + prefix_mac2str(mac, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +} + +printfrr_ext_autoreg_p("IA", printfrr_ia); +static ssize_t printfrr_ia(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct ipaddr *ipa = ptr; + char cbuf[INET6_ADDRSTRLEN]; + bool use_star = false; + + if (ea->fmt[0] == 's') { + use_star = true; + ea->fmt++; + } + + if (!ipa || !ipa->ipa_type) + return bputs(buf, "(null)"); + + if (use_star) { + struct in_addr zero4 = {}; + struct in6_addr zero6 = {}; + + switch (ipa->ipa_type) { + case IPADDR_V4: + if (!memcmp(&ipa->ip.addr, &zero4, sizeof(zero4))) + return bputch(buf, '*'); + break; + + case IPADDR_V6: + if (!memcmp(&ipa->ip.addr, &zero6, sizeof(zero6))) + return bputch(buf, '*'); + break; + + case IPADDR_NONE: + break; + } + } + + ipaddr2str(ipa, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +} + +printfrr_ext_autoreg_p("I4", printfrr_i4); +static ssize_t printfrr_i4(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + char cbuf[INET_ADDRSTRLEN]; + bool use_star = false; + struct in_addr zero = {}; + + if (ea->fmt[0] == 's') { + use_star = true; + ea->fmt++; + } + + if (!ptr) + return bputs(buf, "(null)"); + + if (use_star && !memcmp(ptr, &zero, sizeof(zero))) + return bputch(buf, '*'); + + inet_ntop(AF_INET, ptr, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +} + +printfrr_ext_autoreg_p("I6", printfrr_i6); +static ssize_t printfrr_i6(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + char cbuf[INET6_ADDRSTRLEN]; + bool use_star = false; + struct in6_addr zero = {}; + + if (ea->fmt[0] == 's') { + use_star = true; + ea->fmt++; + } + + if (!ptr) + return bputs(buf, "(null)"); + + if (use_star && !memcmp(ptr, &zero, sizeof(zero))) + return bputch(buf, '*'); + + inet_ntop(AF_INET6, ptr, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +} + +printfrr_ext_autoreg_p("FX", printfrr_pfx); +static ssize_t printfrr_pfx(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + bool host_only = false; + + if (ea->fmt[0] == 'h') { + ea->fmt++; + host_only = true; + } + + if (!ptr) + return bputs(buf, "(null)"); + + if (host_only) + return prefixhost2str(buf, (struct prefix *)ptr); + else { + char cbuf[PREFIX_STRLEN]; + + prefix2str(ptr, cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); + } +} + +printfrr_ext_autoreg_p("PSG4", printfrr_psg); +static ssize_t printfrr_psg(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct prefix_sg *sg = ptr; + ssize_t ret = 0; + + if (!sg) + return bputs(buf, "(null)"); + + if (ipaddr_is_zero(&sg->src)) + ret += bputs(buf, "(*,"); + else + ret += bprintfrr(buf, "(%pIA,", &sg->src); + + if (sg->grp.s_addr == INADDR_ANY) + ret += bputs(buf, "*)"); + else + ret += bprintfrr(buf, "%pI4)", &sg->grp); + + return ret; +} diff --git a/lib/prefix.h b/lib/prefix.h new file mode 100644 index 0000000..2d679d0 --- /dev/null +++ b/lib/prefix.h @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Prefix structure. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_PREFIX_H +#define _ZEBRA_PREFIX_H + +#ifdef GNU_LINUX +#include +#else +#include +#endif +#include "sockunion.h" +#include "ipaddr.h" +#include "compiler.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ETH_ALEN +#define ETH_ALEN 6 +#endif + +/* EVPN route types. */ +typedef enum { + BGP_EVPN_AD_ROUTE = 1, /* Ethernet Auto-Discovery (A-D) route */ + BGP_EVPN_MAC_IP_ROUTE, /* MAC/IP Advertisement route */ + BGP_EVPN_IMET_ROUTE, /* Inclusive Multicast Ethernet Tag route */ + BGP_EVPN_ES_ROUTE, /* Ethernet Segment route */ + BGP_EVPN_IP_PREFIX_ROUTE, /* IP Prefix route */ +} bgp_evpn_route_type; + +/* value of first byte of ESI */ +#define ESI_TYPE_ARBITRARY 0 /* */ +#define ESI_TYPE_LACP 1 /* <> */ +#define ESI_TYPE_BRIDGE 2 /* ::00 */ +#define ESI_TYPE_MAC 3 /* : */ +#define ESI_TYPE_ROUTER 4 /* : */ +#define ESI_TYPE_AS 5 /* : */ + +#define MAX_ESI {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} + + +#define EVPN_ETH_TAG_BYTES 4 +#define ESI_BYTES 10 +#define ESI_STR_LEN (3 * ESI_BYTES) +#define EVPN_DF_ALG_STR_LEN 24 + +/* Maximum number of VTEPs per-ES - + * XXX - temporary limit for allocating strings etc. + */ +#define ES_VTEP_MAX_CNT 10 +#define ES_VTEP_LIST_STR_SZ (ES_VTEP_MAX_CNT * IPADDR_STRING_SIZE) + +#define ETHER_ADDR_STRLEN (3 * ETH_ALEN) +/* + * there isn't a portable ethernet address type. We define our + * own to simplify internal handling + */ +struct ethaddr { + uint8_t octet[ETH_ALEN]; +} __attribute__((packed)); + + +/* length is the number of valuable bits of prefix structure +* 18 bytes is current length in structure, if address is ipv4 +* 30 bytes is in case of ipv6 +*/ +#define PREFIX_LEN_ROUTE_TYPE_5_IPV4 (18*8) +#define PREFIX_LEN_ROUTE_TYPE_5_IPV6 (30*8) + +typedef struct esi_t_ { + uint8_t val[ESI_BYTES]; +} esi_t; + +struct evpn_ead_addr { + esi_t esi; + uint32_t eth_tag; + struct ipaddr ip; + uint16_t frag_id; +}; + +struct evpn_macip_addr { + uint32_t eth_tag; + uint8_t ip_prefix_length; + struct ethaddr mac; + struct ipaddr ip; +}; + +struct evpn_imet_addr { + uint32_t eth_tag; + uint8_t ip_prefix_length; + struct ipaddr ip; +}; + +struct evpn_es_addr { + esi_t esi; + uint8_t ip_prefix_length; + struct ipaddr ip; +}; + +struct evpn_prefix_addr { + uint32_t eth_tag; + uint8_t ip_prefix_length; + struct ipaddr ip; +}; + +/* EVPN address (RFC 7432) */ +struct evpn_addr { + uint8_t route_type; + union { + struct evpn_ead_addr _ead_addr; + struct evpn_macip_addr _macip_addr; + struct evpn_imet_addr _imet_addr; + struct evpn_es_addr _es_addr; + struct evpn_prefix_addr _prefix_addr; + } u; +#define ead_addr u._ead_addr +#define macip_addr u._macip_addr +#define imet_addr u._imet_addr +#define es_addr u._es_addr +#define prefix_addr u._prefix_addr +}; + +/* + * A struct prefix contains an address family, a prefix length, and an + * address. This can represent either a 'network prefix' as defined + * by CIDR, where the 'host bits' of the prefix are 0 + * (e.g. AF_INET:10.0.0.0/8), or an address and netmask + * (e.g. AF_INET:10.0.0.9/8), such as might be configured on an + * interface. + */ + +/* different OSes use different names */ +#if defined(AF_PACKET) +#define AF_ETHERNET AF_PACKET +#else +#if defined(AF_LINK) +#define AF_ETHERNET AF_LINK +#endif +#endif + +/* The 'family' in the prefix structure is internal to FRR and need not + * map to standard OS AF_ definitions except where needed for interacting + * with the kernel. However, AF_ definitions are currently in use and + * prevalent across the code. Define a new FRR-specific AF for EVPN to + * distinguish between 'ethernet' (MAC-only) and 'evpn' prefixes and + * ensure it does not conflict with any OS AF_ definition. + */ +#if !defined(AF_EVPN) +#define AF_EVPN (AF_MAX + 1) +#endif + +#if !defined(AF_FLOWSPEC) +#define AF_FLOWSPEC (AF_MAX + 2) +#endif + +struct flowspec_prefix { + uint8_t family; + uint16_t prefixlen; /* length in bytes */ + uintptr_t ptr; +}; + +/* FRR generic prefix structure. */ +struct prefix { + uint8_t family; + uint16_t prefixlen; + union { + uint8_t prefix; + struct in_addr prefix4; + struct in6_addr prefix6; + struct { + struct in_addr id; + struct in_addr adv_router; + } lp; + struct ethaddr prefix_eth; /* AF_ETHERNET */ + uint8_t val[16]; + uint32_t val32[4]; + uintptr_t ptr; + struct evpn_addr prefix_evpn; /* AF_EVPN */ + struct flowspec_prefix prefix_flowspec; /* AF_FLOWSPEC */ + } u __attribute__((aligned(8))); +}; + +/* IPv4 prefix structure. */ +struct prefix_ipv4 { + uint8_t family; + uint16_t prefixlen; + struct in_addr prefix __attribute__((aligned(8))); +}; + +/* IPv6 prefix structure. */ +struct prefix_ipv6 { + uint8_t family; + uint16_t prefixlen; + struct in6_addr prefix __attribute__((aligned(8))); +}; + +struct prefix_ls { + uint8_t family; + uint16_t prefixlen; + struct in_addr id __attribute__((aligned(8))); + struct in_addr adv_router; +}; + +/* Prefix for routing distinguisher. */ +struct prefix_rd { + uint8_t family; + uint16_t prefixlen; + uint8_t val[8] __attribute__((aligned(8))); +}; + +/* Prefix for ethernet. */ +struct prefix_eth { + uint8_t family; + uint16_t prefixlen; + struct ethaddr eth_addr __attribute__((aligned(8))); /* AF_ETHERNET */ +}; + +/* EVPN prefix structure. */ +struct prefix_evpn { + uint8_t family; + uint16_t prefixlen; + struct evpn_addr prefix __attribute__((aligned(8))); +}; + +static inline int is_evpn_prefix_ipaddr_none(const struct prefix_evpn *evp) +{ + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) + return IS_IPADDR_NONE(&(evp)->prefix.ead_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + return IS_IPADDR_NONE(&(evp)->prefix.macip_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE) + return IS_IPADDR_NONE(&(evp)->prefix.imet_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE) + return IS_IPADDR_NONE(&(evp)->prefix.es_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) + return IS_IPADDR_NONE(&(evp)->prefix.prefix_addr.ip); + return 0; +} + +static inline int is_evpn_prefix_ipaddr_v4(const struct prefix_evpn *evp) +{ + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) + return IS_IPADDR_V4(&(evp)->prefix.ead_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + return IS_IPADDR_V4(&(evp)->prefix.macip_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE) + return IS_IPADDR_V4(&(evp)->prefix.imet_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE) + return IS_IPADDR_V4(&(evp)->prefix.es_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) + return IS_IPADDR_V4(&(evp)->prefix.prefix_addr.ip); + return 0; +} + +static inline int is_evpn_prefix_ipaddr_v6(const struct prefix_evpn *evp) +{ + if (evp->prefix.route_type == BGP_EVPN_AD_ROUTE) + return IS_IPADDR_V6(&(evp)->prefix.ead_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_MAC_IP_ROUTE) + return IS_IPADDR_V6(&(evp)->prefix.macip_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IMET_ROUTE) + return IS_IPADDR_V6(&(evp)->prefix.imet_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_ES_ROUTE) + return IS_IPADDR_V6(&(evp)->prefix.es_addr.ip); + if (evp->prefix.route_type == BGP_EVPN_IP_PREFIX_ROUTE) + return IS_IPADDR_V6(&(evp)->prefix.prefix_addr.ip); + return 0; +} + +/* Prefix for a Flowspec entry */ +struct prefix_fs { + uint8_t family; + uint16_t prefixlen; /* unused */ + struct flowspec_prefix prefix __attribute__((aligned(8))); +}; + +struct prefix_sg { + uint8_t family; + uint16_t prefixlen; + struct ipaddr src __attribute__((aligned(8))); + struct in_addr grp; +}; + +/* clang-format off */ +union prefixptr { + uniontype(prefixptr, struct prefix, p) + uniontype(prefixptr, struct prefix_ipv4, p4) + uniontype(prefixptr, struct prefix_ipv6, p6) + uniontype(prefixptr, struct prefix_evpn, evp) + uniontype(prefixptr, struct prefix_fs, fs) + uniontype(prefixptr, struct prefix_rd, rd) +} TRANSPARENT_UNION; + +union prefixconstptr { + uniontype(prefixconstptr, const struct prefix, p) + uniontype(prefixconstptr, const struct prefix_ipv4, p4) + uniontype(prefixconstptr, const struct prefix_ipv6, p6) + uniontype(prefixconstptr, const struct prefix_evpn, evp) + uniontype(prefixconstptr, const struct prefix_fs, fs) + uniontype(prefixconstptr, const struct prefix_rd, rd) +} TRANSPARENT_UNION; +/* clang-format on */ + +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN 16 +#endif /* INET_ADDRSTRLEN */ + +#ifndef INET6_ADDRSTRLEN +/* dead:beef:dead:beef:dead:beef:dead:beef + \0 */ +#define INET6_ADDRSTRLEN 46 +#endif /* INET6_ADDRSTRLEN */ + +#ifndef INET6_BUFSIZ +#define INET6_BUFSIZ 53 +#endif /* INET6_BUFSIZ */ + +/* Maximum string length of the result of prefix2str */ +#define PREFIX_STRLEN 80 + +/* + * Longest possible length of a (S,G) string is 34 bytes + * 123.123.123.123 = 15 * 2 + * (,) = 3 + * NULL Character at end = 1 + * (123.123.123.123,123.123.123.123) + */ +#define PREFIX_SG_STR_LEN 34 + +/* Max bit/byte length of IPv4 address. */ +#define IPV4_MAX_BYTELEN 4 +#define IPV4_MAX_BITLEN 32 +#define IPV4_ADDR_CMP(D,S) memcmp ((D), (S), IPV4_MAX_BYTELEN) + +static inline bool ipv4_addr_same(const struct in_addr *a, + const struct in_addr *b) +{ + return (a->s_addr == b->s_addr); +} +#define IPV4_ADDR_SAME(A,B) ipv4_addr_same((A), (B)) + +static inline void ipv4_addr_copy(struct in_addr *dst, + const struct in_addr *src) +{ + dst->s_addr = src->s_addr; +} +#define IPV4_ADDR_COPY(D,S) ipv4_addr_copy((D), (S)) + +#define IPV4_NET0(a) ((((uint32_t)(a)) & 0xff000000) == 0x00000000) +#define IPV4_NET127(a) ((((uint32_t)(a)) & 0xff000000) == 0x7f000000) +#define IPV4_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffff0000) == 0xa9fe0000) +#define IPV4_CLASS_D(a) ((((uint32_t)(a)) & 0xf0000000) == 0xe0000000) +#define IPV4_CLASS_E(a) ((((uint32_t)(a)) & 0xf0000000) == 0xf0000000) +#define IPV4_CLASS_DE(a) ((((uint32_t)(a)) & 0xe0000000) == 0xe0000000) +#define IPV4_MC_LINKLOCAL(a) ((((uint32_t)(a)) & 0xffffff00) == 0xe0000000) + +/* Max bit/byte length of IPv6 address. */ +#define IPV6_MAX_BYTELEN 16 +#define IPV6_MAX_BITLEN 128 +#define IPV6_ADDR_CMP(D,S) memcmp ((D), (S), IPV6_MAX_BYTELEN) +#define IPV6_ADDR_SAME(D,S) (memcmp ((D), (S), IPV6_MAX_BYTELEN) == 0) +#define IPV6_ADDR_COPY(D,S) memcpy ((D), (S), IPV6_MAX_BYTELEN) + +/* Count prefix size from mask length */ +#define PSIZE(a) (((a) + 7) / (8)) + +#define BSIZE(a) ((a) * (8)) + +/* Prefix's family member. */ +#define PREFIX_FAMILY(p) ((p)->family) + +/* glibc defines s6_addr32 to __in6_u.__u6_addr32 if __USE_{MISC || GNU} */ +#ifndef s6_addr32 +#define s6_addr32 __u6_addr.__u6_addr32 +#endif /*s6_addr32*/ + +/* Prototypes. */ +extern int str2family(const char *string); +extern int afi2family(afi_t afi); +extern afi_t family2afi(int family); +extern const char *family2str(int family); +extern const char *safi2str(safi_t safi); +extern const char *afi2str(afi_t afi); +extern const char *afi2str_lower(afi_t afi); + +static inline afi_t prefix_afi(union prefixconstptr pu) +{ + return family2afi(pu.p->family); +} + +/* + * Check bit of the prefix. + * + * prefix + * byte buffer + * + * bit_index + * which bit to fetch from byte buffer, 0 indexed. + */ +extern unsigned int prefix_bit(const uint8_t *prefix, const uint16_t bit_index); + +extern struct prefix *prefix_new(void); +extern void prefix_free(struct prefix **p); +/* + * Function to handle prefix_free being used as a del function. + */ +extern void prefix_free_lists(void *arg); +extern const char *prefix_family_str(union prefixconstptr pu); +extern int prefix_blen(union prefixconstptr pu); +extern int str2prefix(const char *string, struct prefix *prefix); + +#define PREFIX2STR_BUFFER PREFIX_STRLEN + +extern void prefix_mcast_ip_dump(const char *onfail, const struct ipaddr *addr, + char *buf, int buf_size); +extern void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr, + char *buf, int buf_size); +extern const char *prefix_sg2str(const struct prefix_sg *sg, char *str); +extern const char *prefix2str(union prefixconstptr upfx, char *buffer, + int size); +extern int evpn_type5_prefix_match(const struct prefix *evpn_pfx, + const struct prefix *match_pfx); +extern int prefix_match(union prefixconstptr unet, union prefixconstptr upfx); +extern int prefix_match_network_statement(union prefixconstptr unet, + union prefixconstptr upfx); +extern int prefix_same(union prefixconstptr ua, union prefixconstptr ub); +extern int prefix_cmp(union prefixconstptr ua, union prefixconstptr ub); +extern int prefix_common_bits(union prefixconstptr ua, union prefixconstptr ub); +extern void prefix_copy(union prefixptr udst, union prefixconstptr usrc); +extern void apply_mask(union prefixptr pu); +extern bool evpn_addr_same(const struct evpn_addr *e1, const struct evpn_addr *e2); + +#ifdef __clang_analyzer__ +/* clang-SA doesn't understand transparent unions, making it think that the + * target of prefix_copy is uninitialized. So just memset the target. + * cf. https://bugs.llvm.org/show_bug.cgi?id=42811 + */ +#define prefix_copy(a, b) ({ memset(a, 0, sizeof(*a)); prefix_copy(a, b); }) +#endif + +extern struct prefix *sockunion2hostprefix(const union sockunion *su, + struct prefix *p); +extern void prefix2sockunion(const struct prefix *p, union sockunion *su); + +extern int str2prefix_eth(const char *string, struct prefix_eth *p); + +extern struct prefix_ipv4 *prefix_ipv4_new(void); +extern void prefix_ipv4_free(struct prefix_ipv4 **p); +extern int str2prefix_ipv4(const char *string, struct prefix_ipv4 *p); +extern void apply_mask_ipv4(struct prefix_ipv4 *p); + +extern int prefix_ipv4_any(const struct prefix_ipv4 *p); +extern void apply_classful_mask_ipv4(struct prefix_ipv4 *p); + +extern uint8_t ip_masklen(struct in_addr addr); +extern void masklen2ip(const int length, struct in_addr *addr); +/* given the address of a host on a network and the network mask length, + * calculate the broadcast address for that network; + * special treatment for /31 according to RFC3021 section 3.3 */ +extern in_addr_t ipv4_broadcast_addr(in_addr_t hostaddr, int masklen); + +extern int netmask_str2prefix_str(const char *net_str, const char *mask_str, + char *prefix_str, size_t prefix_str_len); + +extern struct prefix_ipv6 *prefix_ipv6_new(void); +extern void prefix_ipv6_free(struct prefix_ipv6 **p); +extern int str2prefix_ipv6(const char *str, struct prefix_ipv6 *p); +extern void apply_mask_ipv6(struct prefix_ipv6 *p); + +extern int ip6_masklen(struct in6_addr netmask); +extern void masklen2ip6(const int masklen, struct in6_addr *netmask); + +extern int is_zero_mac(const struct ethaddr *mac); +extern bool is_mcast_mac(const struct ethaddr *mac); +extern bool is_bcast_mac(const struct ethaddr *mac); +extern int prefix_str2mac(const char *str, struct ethaddr *mac); +extern char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size); + +extern unsigned prefix_hash_key(const void *pp); + +extern int str_to_esi(const char *str, esi_t *esi); +extern char *esi_to_str(const esi_t *esi, char *buf, int size); +extern char *evpn_es_df_alg2str(uint8_t df_alg, char *buf, int buf_len); +extern void prefix_evpn_hexdump(const struct prefix_evpn *p); +extern bool ipv4_unicast_valid(const struct in_addr *addr); +extern int evpn_prefix2prefix(const struct prefix *evpn, struct prefix *to); + +static inline int ipv6_martian(const struct in6_addr *addr) +{ + struct in6_addr localhost_addr; + + inet_pton(AF_INET6, "::1", &localhost_addr); + + if (IPV6_ADDR_SAME(&localhost_addr, addr)) + return 1; + + return 0; +} + +extern int macstr2prefix_evpn(const char *str, struct prefix_evpn *p); + +/* NOTE: This routine expects the address argument in network byte order. */ +static inline bool ipv4_martian(const struct in_addr *addr) +{ + if (!ipv4_unicast_valid(addr)) + return true; + return false; +} + +static inline bool is_default_prefix4(const struct prefix_ipv4 *p) +{ + return p && p->family == AF_INET && p->prefixlen == 0 + && p->prefix.s_addr == INADDR_ANY; +} + +static inline bool is_default_prefix6(const struct prefix_ipv6 *p) +{ + return p && p->family == AF_INET6 && p->prefixlen == 0 + && memcmp(&p->prefix, &in6addr_any, sizeof(struct in6_addr)) + == 0; +} + +static inline bool is_default_prefix(const struct prefix *p) +{ + if (p == NULL) + return false; + + switch (p->family) { + case AF_INET: + return is_default_prefix4((const struct prefix_ipv4 *)p); + case AF_INET6: + return is_default_prefix6((const struct prefix_ipv6 *)p); + } + + return false; +} + +static inline int is_host_route(const struct prefix *p) +{ + if (p->family == AF_INET) + return (p->prefixlen == IPV4_MAX_BITLEN); + else if (p->family == AF_INET6) + return (p->prefixlen == IPV6_MAX_BITLEN); + return 0; +} + +static inline int is_default_host_route(const struct prefix *p) +{ + if (p->family == AF_INET) { + return (p->u.prefix4.s_addr == INADDR_ANY && + p->prefixlen == IPV4_MAX_BITLEN); + } else if (p->family == AF_INET6) { + return ((!memcmp(&p->u.prefix6, &in6addr_any, + sizeof(struct in6_addr))) && + p->prefixlen == IPV6_MAX_BITLEN); + } + return 0; +} + +static inline bool is_ipv6_global_unicast(const struct in6_addr *p) +{ + if (IN6_IS_ADDR_UNSPECIFIED(p) || IN6_IS_ADDR_LOOPBACK(p) || + IN6_IS_ADDR_LINKLOCAL(p) || IN6_IS_ADDR_MULTICAST(p)) + return false; + + return true; +} + +/* IPv6 scope values, usable for IPv4 too (cf. below) */ +/* clang-format off */ +enum { + /* 0: reserved */ + MCAST_SCOPE_IFACE = 0x1, + MCAST_SCOPE_LINK = 0x2, + MCAST_SCOPE_REALM = 0x3, + MCAST_SCOPE_ADMIN = 0x4, + MCAST_SCOPE_SITE = 0x5, + /* 6-7: unassigned */ + MCAST_SCOPE_ORG = 0x8, + /* 9-d: unassigned */ + MCAST_SCOPE_GLOBAL = 0xe, + /* f: reserved */ +}; +/* clang-format on */ + +static inline uint8_t ipv6_mcast_scope(const struct in6_addr *addr) +{ + return addr->s6_addr[1] & 0xf; +} + +static inline bool ipv6_mcast_nofwd(const struct in6_addr *addr) +{ + return (addr->s6_addr[1] & 0xf) <= MCAST_SCOPE_LINK; +} + +static inline bool ipv6_mcast_ssm(const struct in6_addr *addr) +{ + uint32_t bits = ntohl(addr->s6_addr32[0]); + + /* ff3x:0000::/32 */ + return (bits & 0xfff0ffff) == 0xff300000; +} + +static inline bool ipv6_mcast_reserved(const struct in6_addr *addr) +{ + uint32_t bits = ntohl(addr->s6_addr32[0]); + + /* ffx2::/16 */ + return (bits & 0xff0fffff) == 0xff020000; +} + +static inline uint8_t ipv4_mcast_scope(const struct in_addr *addr) +{ + uint32_t bits = ntohl(addr->s_addr); + + /* 224.0.0.0/24 - link scope */ + if ((bits & 0xffffff00) == 0xe0000000) + return MCAST_SCOPE_LINK; + /* 239.0.0.0/8 - org scope */ + if ((bits & 0xff000000) == 0xef000000) + return MCAST_SCOPE_ORG; + + return MCAST_SCOPE_GLOBAL; +} + +static inline bool ipv4_mcast_nofwd(const struct in_addr *addr) +{ + uint32_t bits = ntohl(addr->s_addr); + + /* 224.0.0.0/24 */ + return (bits & 0xffffff00) == 0xe0000000; +} + +static inline bool ipv4_mcast_ssm(const struct in_addr *addr) +{ + uint32_t bits = ntohl(addr->s_addr); + + /* 232.0.0.0/8 */ + return (bits & 0xff000000) == 0xe8000000; +} + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pEA" (struct ethaddr *) + +#pragma FRR printfrr_ext "%pI4" (struct in_addr *) +#pragma FRR printfrr_ext "%pI4" (in_addr_t *) + +#pragma FRR printfrr_ext "%pI6" (struct in6_addr *) + +#pragma FRR printfrr_ext "%pFX" (struct prefix *) +#pragma FRR printfrr_ext "%pFX" (struct prefix_ipv4 *) +#pragma FRR printfrr_ext "%pFX" (struct prefix_ipv6 *) +#pragma FRR printfrr_ext "%pFX" (struct prefix_eth *) +#pragma FRR printfrr_ext "%pFX" (struct prefix_evpn *) +#pragma FRR printfrr_ext "%pFX" (struct prefix_fs *) +#pragma FRR printfrr_ext "%pRDP" (struct prefix_rd *) +/* RD with AS4B with dot and dot+ format */ +#pragma FRR printfrr_ext "%pRDD" (struct prefix_rd *) +#pragma FRR printfrr_ext "%pRDE" (struct prefix_rd *) + +#pragma FRR printfrr_ext "%pPSG4" (struct prefix_sg *) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_PREFIX_H */ diff --git a/lib/printf/README b/lib/printf/README new file mode 100644 index 0000000..3c3ef74 --- /dev/null +++ b/lib/printf/README @@ -0,0 +1,10 @@ +This is the printf implementation from FreeBSD. The history of this code is: +- imported on 2019-05-12, from SVN revision 347514 +- resynced on 2023-09-03, to pick up `%b` implementation +- resynced on 2024-03-10, to pick up `%w[f](8|16|32|64)d` implementation + +Please don't reindent or otherwise mangle the files to make importing fixes +easy (not that there are significant changes likely to happen...) + +The changes to this code are published under FreeBSD's license as listed in the +file headers. If you change license, please make that as obvious as possible. diff --git a/lib/printf/glue.c b/lib/printf/glue.c new file mode 100644 index 0000000..f799378 --- /dev/null +++ b/lib/printf/glue.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "printfrr.h" +#include "printflocal.h" + +ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) +{ + ssize_t ret; + va_list ap; + + va_start(ap, fmt); + ret = vbprintfrr(out, fmt, ap); + va_end(ap); + return ret; +} + +ssize_t vsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap) +{ + struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; + struct fbuf *fb = (out && outsz) ? &fbb : NULL; + ssize_t ret; + + ret = vbprintfrr(fb, fmt, ap); + if (fb) + fb->pos[0] = '\0'; + return ret; +} + +ssize_t snprintfrr(char *out, size_t outsz, const char *fmt, ...) +{ + struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; + struct fbuf *fb = (out && outsz) ? &fbb : NULL; + ssize_t ret; + va_list ap; + + va_start(ap, fmt); + ret = vbprintfrr(fb, fmt, ap); + va_end(ap); + if (fb) + fb->pos[0] = '\0'; + return ret; +} + +ssize_t vcsnprintfrr(char *out, size_t outsz, const char *fmt, va_list ap) +{ + if (!out || !outsz) + return vbprintfrr(NULL, fmt, ap); + + struct fbuf fbb = { .buf = out, .pos = out, .len = outsz - 1, }; + ssize_t ret; + size_t pos; + + pos = strnlen(out, outsz); + fbb.pos += pos; + + ret = vbprintfrr(&fbb, fmt, ap); + fbb.pos[0] = '\0'; + return ret >= 0 ? ret + (ssize_t)pos : ret; +} + +ssize_t csnprintfrr(char *out, size_t outsz, const char *fmt, ...) +{ + ssize_t ret; + va_list ap; + + va_start(ap, fmt); + ret = vcsnprintfrr(out, outsz, fmt, ap); + va_end(ap); + return ret; +} + +char *vasnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt, + va_list ap) +{ + struct fbuf fb = { .buf = out, .pos = out, .len = outsz - 1, }; + ssize_t len; + va_list ap2; + char *ret = out; + + va_copy(ap2, ap); + len = vbprintfrr(&fb, fmt, ap); + if (len < 0) { + va_end(ap2); + /* error = malformed format string => try something useful */ + return qstrdup(mt, fmt); + } + + if ((size_t)len >= outsz - 1) { + ret = qmalloc(mt, len + 1); + fb.buf = fb.pos = ret; + fb.len = len; + + vbprintfrr(&fb, fmt, ap2); + } + + va_end(ap2); + ret[len] = '\0'; + return ret; +} + +char *asnprintfrr(struct memtype *mt, char *out, size_t outsz, const char *fmt, + ...) +{ + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vasnprintfrr(mt, out, outsz, fmt, ap); + va_end(ap); + return ret; +} + +char *vasprintfrr(struct memtype *mt, const char *fmt, va_list ap) +{ + char buf[256]; + char *ret; + + ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap); + + if (ret == buf) + ret = qstrdup(mt, ret); + return ret; +} + +char *asprintfrr(struct memtype *mt, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vasnprintfrr(mt, buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (ret == buf) + ret = qstrdup(mt, ret); + return ret; +} + +/* Q: WTF? + * A: since printf should be reasonably fast (think debugging logs), the idea + * here is to keep things close by each other in a cacheline. That's why + * ext_quick just has the first 2 characters of an extension, and we do a + * nice linear continuous sweep. Only if we find something, we go do more + * expensive things. + * + * Q: doesn't this need a mutex/lock? + * A: theoretically, yes, but that's quite expensive and I rather elide that + * necessity by putting down some usage rules. Just call this at startup + * while singlethreaded and all is fine. Ideally, just use constructors + * (and make sure dlopen() doesn't mess things up...) + */ +#define MAXEXT 64 + +struct ext_quick { + char fmt[2]; +}; + +static uint8_t ext_offsets[26] __attribute__((aligned(32))); +static struct ext_quick entries[MAXEXT] __attribute__((aligned(64))); +static const struct printfrr_ext *exts[MAXEXT] __attribute__((aligned(64))); + +void printfrr_ext_reg(const struct printfrr_ext *ext) +{ + uint8_t o; + ptrdiff_t i; + + if (!printfrr_ext_char(ext->match[0])) + return; + + o = ext->match[0] - 'A'; + for (i = ext_offsets[o]; + i < MAXEXT && entries[i].fmt[0] && + memcmp(entries[i].fmt, ext->match, 2) < 0; + i++) + ; + if (i == MAXEXT) + return; + for (o++; o <= 'Z' - 'A'; o++) + ext_offsets[o]++; + + memmove(entries + i + 1, entries + i, + (MAXEXT - i - 1) * sizeof(entries[0])); + memmove(exts + i + 1, exts + i, + (MAXEXT - i - 1) * sizeof(exts[0])); + + memcpy(entries[i].fmt, ext->match, 2); + exts[i] = ext; +} + +ssize_t printfrr_extp(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const char *fmt = ea->fmt; + const struct printfrr_ext *ext; + size_t i; + + for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) { + if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0]) + return -1; + if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1]) + continue; + ext = exts[i]; + if (!ext->print_ptr) + continue; + if (strncmp(ext->match, fmt, strlen(ext->match))) + continue; + ea->fmt += strlen(ext->match); + return ext->print_ptr(buf, ea, ptr); + } + return -1; +} + +ssize_t printfrr_exti(struct fbuf *buf, struct printfrr_eargs *ea, + uintmax_t num) +{ + const char *fmt = ea->fmt; + const struct printfrr_ext *ext; + size_t i; + + for (i = ext_offsets[fmt[0] - 'A']; i < MAXEXT; i++) { + if (!entries[i].fmt[0] || entries[i].fmt[0] > fmt[0]) + return -1; + if (entries[i].fmt[1] && entries[i].fmt[1] != fmt[1]) + continue; + ext = exts[i]; + if (!ext->print_int) + continue; + if (strncmp(ext->match, fmt, strlen(ext->match))) + continue; + ea->fmt += strlen(ext->match); + return ext->print_int(buf, ea, num); + } + return -1; +} + +printfrr_ext_autoreg_p("FB", printfrr_fb); +static ssize_t printfrr_fb(struct fbuf *out, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct fbuf *in = ptr; + ptrdiff_t copy_len; + + if (!in) + return bputs(out, "NULL"); + + if (out) { + copy_len = MIN(in->pos - in->buf, + out->buf + out->len - out->pos); + if (copy_len > 0) { + memcpy(out->pos, in->buf, copy_len); + out->pos += copy_len; + } + } + + return in->pos - in->buf; +} + +printfrr_ext_autoreg_p("VA", printfrr_va); +static ssize_t printfrr_va(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct va_format *vaf = ptr; + va_list ap; + ssize_t s; + + if (!vaf || !vaf->fmt || !vaf->va) + return bputs(buf, "NULL"); + + /* make sure we don't alter the data passed in - especially since + * bprintfrr (and thus this) might be called on the same format twice, + * when allocating a larger buffer in asnprintfrr() + */ + va_copy(ap, *vaf->va); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* can't format check this */ + s = vbprintfrr(buf, vaf->fmt, ap); +#pragma GCC diagnostic pop + va_end(ap); + + return s; +} diff --git a/lib/printf/printf-pos.c b/lib/printf/printf-pos.c new file mode 100644 index 0000000..b2ba1a7 --- /dev/null +++ b/lib/printf/printf-pos.c @@ -0,0 +1,791 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_CDEFS_H +#include +#endif + +/* + * This is the code responsible for handling positional arguments + * (%m$ and %m$.n$) for vfprintf() and vfwprintf(). + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "printflocal.h" + +#ifdef NL_ARGMAX +#define MAX_POSARG NL_ARGMAX +#else +#define MAX_POSARG 65536 +#endif + +/* + * Type ids for argument type table. + */ +enum typeid { + T_UNUSED, TP_SHORT, T_INT, T_U_INT, TP_INT, + T_LONG, T_U_LONG, TP_LONG, T_LLONG, T_U_LLONG, TP_LLONG, + T_PTRDIFFT, TP_PTRDIFFT, T_SSIZET, T_SIZET, TP_SSIZET, + T_INT64T, T_UINT64T, T_INTMAXT, T_UINTMAXT, TP_INTMAXT, TP_VOID, + TP_CHAR, TP_SCHAR, T_DOUBLE, T_LONG_DOUBLE, T_WINT, TP_WCHAR +}; + +/* An expandable array of types. */ +struct typetable { + enum typeid *table; /* table of types */ + enum typeid stattable[STATIC_ARG_TBL_SIZE]; + u_int tablesize; /* current size of type table */ + u_int tablemax; /* largest used index in table */ + u_int nextarg; /* 1-based argument index */ +}; + +static int __grow_type_table(struct typetable *); +static void build_arg_table (struct typetable *, va_list, union arg **); + +/* + * Initialize a struct typetable. + */ +static inline void +inittypes(struct typetable *types) +{ + u_int n; + + types->table = types->stattable; + types->tablesize = STATIC_ARG_TBL_SIZE; + types->tablemax = 0; + types->nextarg = 1; + for (n = 0; n < STATIC_ARG_TBL_SIZE; n++) + types->table[n] = T_UNUSED; +} + +/* + * struct typetable destructor. + */ +static inline void +freetypes(struct typetable *types) +{ + + if (types->table != types->stattable) + free (types->table); +} + +/* + * Ensure that there is space to add a new argument type to the type table. + * Expand the table if necessary. Returns 0 on success. + */ +static inline int +_ensurespace(struct typetable *types) +{ + + if (types->nextarg >= types->tablesize) { + if (__grow_type_table(types)) + return -1; + } + if (types->nextarg > types->tablemax) + types->tablemax = types->nextarg; + return 0; +} + +/* + * Add an argument type to the table, expanding if necessary. + * Returns 0 on success. + */ +static inline int +addtype(struct typetable *types, enum typeid type) +{ + + if (_ensurespace(types)) + return -1; + types->table[types->nextarg++] = type; + return 0; +} + +static inline int +addsarg(struct typetable *types, int flags) +{ + + if (_ensurespace(types)) + return -1; + if (flags & LONGDBL) + types->table[types->nextarg++] = T_INT64T; + else if (flags & INTMAXT) + types->table[types->nextarg++] = T_INTMAXT; + else if (flags & SIZET) + types->table[types->nextarg++] = T_SSIZET; + else if (flags & PTRDIFFT) + types->table[types->nextarg++] = T_PTRDIFFT; + else if (flags & LLONGINT) + types->table[types->nextarg++] = T_LLONG; + else if (flags & LONGINT) + types->table[types->nextarg++] = T_LONG; + else + types->table[types->nextarg++] = T_INT; + return 0; +} + +static inline int +adduarg(struct typetable *types, int flags) +{ + + if (_ensurespace(types)) + return -1; + if (flags & LONGDBL) + types->table[types->nextarg++] = T_UINT64T; + else if (flags & INTMAXT) + types->table[types->nextarg++] = T_UINTMAXT; + else if (flags & SIZET) + types->table[types->nextarg++] = T_SIZET; + else if (flags & PTRDIFFT) + types->table[types->nextarg++] = T_SIZET; + else if (flags & LLONGINT) + types->table[types->nextarg++] = T_U_LLONG; + else if (flags & LONGINT) + types->table[types->nextarg++] = T_U_LONG; + else + types->table[types->nextarg++] = T_U_INT; + return 0; +} + +/* + * Add * arguments to the type array. + */ +static inline int +addaster(struct typetable *types, char **fmtp) +{ + char *cp; + u_int n2; + + n2 = 0; + cp = *fmtp; + while (is_digit(*cp)) { + n2 = 10 * n2 + to_digit(*cp); + cp++; + } + if (*cp == '$') { + u_int hold = types->nextarg; + types->nextarg = n2; + if (addtype(types, T_INT)) + return -1; + types->nextarg = hold; + *fmtp = ++cp; + } else { + if (addtype(types, T_INT)) + return -1; + } + return 0; +} + +#ifdef WCHAR_SUPPORT +static inline int +addwaster(struct typetable *types, wchar_t **fmtp) +{ + wchar_t *cp; + u_int n2; + + n2 = 0; + cp = *fmtp; + while (is_digit(*cp)) { + n2 = 10 * n2 + to_digit(*cp); + cp++; + } + if (*cp == '$') { + u_int hold = types->nextarg; + types->nextarg = n2; + if (addtype(types, T_INT)) + return -1; + types->nextarg = hold; + *fmtp = ++cp; + } else { + if (addtype(types, T_INT)) + return -1; + } + return 0; +} +#endif /* WCHAR_SUPPORT */ + +/* + * Find all arguments when a positional parameter is encountered. Returns a + * table, indexed by argument number, of pointers to each arguments. The + * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries. + * It will be replaces with a malloc-ed one if it overflows. + * Returns 0 on success. On failure, returns nonzero and sets errno. + */ +int +_frr_find_arguments (const char *fmt0, va_list ap, union arg **argtable) +{ + char *fmt; /* format string */ + int ch; /* character from fmt */ + u_int n; /* handy integer (short term usage) */ + int error; + int flags; /* flags as above */ + struct typetable types; /* table of types */ + + fmt = (char *)fmt0; + inittypes(&types); + error = 0; + + /* + * Scan the format for conversions (`%' character). + */ + for (;;) { + while ((ch = *fmt) != '\0' && ch != '%') + fmt++; + if (ch == '\0') + goto done; + fmt++; /* skip over '%' */ + + flags = 0; + +rflag: ch = *fmt++; +reswitch: switch (ch) { + case ' ': + case '#': + goto rflag; + case '*': + if ((error = addaster(&types, &fmt))) + goto error; + goto rflag; + case '-': + case '+': + case '\'': + goto rflag; + case '.': + if ((ch = *fmt++) == '*') { + if ((error = addaster(&types, &fmt))) + goto error; + goto rflag; + } + while (is_digit(ch)) { + ch = *fmt++; + } + goto reswitch; + case '0': + goto rflag; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = 0; + do { + n = 10 * n + to_digit(ch); + /* Detect overflow */ + if (n > MAX_POSARG) { + error = -1; + goto error; + } + ch = *fmt++; + } while (is_digit(ch)); + if (ch == '$') { + types.nextarg = n; + goto rflag; + } + goto reswitch; + case 'L': + flags |= LONGDBL; + goto rflag; + case 'h': + if (flags & SHORTINT) { + flags &= ~SHORTINT; + flags |= CHARINT; + } else + flags |= SHORTINT; + goto rflag; + case 'j': + flags |= INTMAXT; + goto rflag; + case 'l': + if (flags & LONGINT) { + flags &= ~LONGINT; + flags |= LLONGINT; + } else + flags |= LONGINT; + goto rflag; + case 'q': + flags |= LLONGINT; /* not necessarily */ + goto rflag; + case 't': + flags |= PTRDIFFT; + goto rflag; + case 'z': + flags |= SIZET; + goto rflag; + case 'C': + flags |= LONGINT; + fallthrough; + case 'c': + error = addtype(&types, + (flags & LONGINT) ? T_WINT : T_INT); + if (error) + goto error; + break; + case 'D': + flags |= LONGINT; + fallthrough; + case 'd': + case 'i': + if ((error = addsarg(&types, flags))) + goto error; + break; +#ifndef NO_FLOATING_POINT + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + error = addtype(&types, + (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE); + if (error) + goto error; + break; +#endif /* !NO_FLOATING_POINT */ +#ifdef DANGEROUS_PERCENT_N + case 'n': + if (flags & INTMAXT) + error = addtype(&types, TP_INTMAXT); + else if (flags & PTRDIFFT) + error = addtype(&types, TP_PTRDIFFT); + else if (flags & SIZET) + error = addtype(&types, TP_SSIZET); + else if (flags & LLONGINT) + error = addtype(&types, TP_LLONG); + else if (flags & LONGINT) + error = addtype(&types, TP_LONG); + else if (flags & SHORTINT) + error = addtype(&types, TP_SHORT); + else if (flags & CHARINT) + error = addtype(&types, TP_SCHAR); + else + error = addtype(&types, TP_INT); + if (error) + goto error; + continue; /* no output */ +#endif + case 'O': + flags |= LONGINT; + fallthrough; + case 'o': + if ((error = adduarg(&types, flags))) + goto error; + break; + case 'p': + if ((error = addtype(&types, TP_VOID))) + goto error; + break; + case 'S': + flags |= LONGINT; + fallthrough; + case 's': + error = addtype(&types, + (flags & LONGINT) ? TP_WCHAR : TP_CHAR); + if (error) + goto error; + break; + case 'U': + flags |= LONGINT; + fallthrough; + case 'u': + case 'X': + case 'x': + if ((error = adduarg(&types, flags))) + goto error; + break; + default: /* "%?" prints ?, unless ? is NUL */ + if (ch == '\0') + goto done; + break; + } + } +done: + build_arg_table(&types, ap, argtable); +error: + freetypes(&types); + return (error || *argtable == NULL); +} + +#ifdef WCHAR_SUPPORT +/* wchar version of __find_arguments. */ +int +_frr_find_warguments (const wchar_t *fmt0, va_list ap, union arg **argtable) +{ + wchar_t *fmt; /* format string */ + wchar_t ch; /* character from fmt */ + u_int n; /* handy integer (short term usage) */ + int error; + int flags; /* flags as above */ + struct typetable types; /* table of types */ + + fmt = (wchar_t *)fmt0; + inittypes(&types); + error = 0; + + /* + * Scan the format for conversions (`%' character). + */ + for (;;) { + while ((ch = *fmt) != '\0' && ch != '%') + fmt++; + if (ch == '\0') + goto done; + fmt++; /* skip over '%' */ + + flags = 0; + +rflag: ch = *fmt++; +reswitch: switch (ch) { + case ' ': + case '#': + goto rflag; + case '*': + if ((error = addwaster(&types, &fmt))) + goto error; + goto rflag; + case '-': + case '+': + case '\'': + goto rflag; + case '.': + if ((ch = *fmt++) == '*') { + if ((error = addwaster(&types, &fmt))) + goto error; + goto rflag; + } + while (is_digit(ch)) { + ch = *fmt++; + } + goto reswitch; + case '0': + goto rflag; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = 0; + do { + n = 10 * n + to_digit(ch); + /* Detect overflow */ + if (n > MAX_POSARG) { + error = -1; + goto error; + } + ch = *fmt++; + } while (is_digit(ch)); + if (ch == '$') { + types.nextarg = n; + goto rflag; + } + goto reswitch; + case 'L': + flags |= LONGDBL; + goto rflag; + case 'h': + if (flags & SHORTINT) { + flags &= ~SHORTINT; + flags |= CHARINT; + } else + flags |= SHORTINT; + goto rflag; + case 'j': + flags |= INTMAXT; + goto rflag; + case 'l': + if (flags & LONGINT) { + flags &= ~LONGINT; + flags |= LLONGINT; + } else + flags |= LONGINT; + goto rflag; + case 'q': + flags |= LLONGINT; /* not necessarily */ + goto rflag; + case 't': + flags |= PTRDIFFT; + goto rflag; + case 'z': + flags |= SIZET; + goto rflag; + case 'C': + flags |= LONGINT; + fallthrough; + case 'c': + error = addtype(&types, + (flags & LONGINT) ? T_WINT : T_INT); + if (error) + goto error; + break; + case 'D': + flags |= LONGINT; + fallthrough; + case 'd': + case 'i': + if ((error = addsarg(&types, flags))) + goto error; + break; +#ifndef NO_FLOATING_POINT + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + error = addtype(&types, + (flags & LONGDBL) ? T_LONG_DOUBLE : T_DOUBLE); + if (error) + goto error; + break; +#endif /* !NO_FLOATING_POINT */ +#ifdef DANGEROUS_PERCENT_N + case 'n': + if (flags & INTMAXT) + error = addtype(&types, TP_INTMAXT); + else if (flags & PTRDIFFT) + error = addtype(&types, TP_PTRDIFFT); + else if (flags & SIZET) + error = addtype(&types, TP_SSIZET); + else if (flags & LLONGINT) + error = addtype(&types, TP_LLONG); + else if (flags & LONGINT) + error = addtype(&types, TP_LONG); + else if (flags & SHORTINT) + error = addtype(&types, TP_SHORT); + else if (flags & CHARINT) + error = addtype(&types, TP_SCHAR); + else + error = addtype(&types, TP_INT); + if (error) + goto error; + continue; /* no output */ +#endif + case 'O': + flags |= LONGINT; + fallthrough; + case 'o': + if ((error = adduarg(&types, flags))) + goto error; + break; + case 'p': + if ((error = addtype(&types, TP_VOID))) + goto error; + break; + case 'S': + flags |= LONGINT; + fallthrough; + case 's': + error = addtype(&types, + (flags & LONGINT) ? TP_WCHAR : TP_CHAR); + if (error) + goto error; + break; + case 'U': + flags |= LONGINT; + fallthrough; + case 'u': + case 'X': + case 'x': + if ((error = adduarg(&types, flags))) + goto error; + break; + default: /* "%?" prints ?, unless ? is NUL */ + if (ch == '\0') + goto done; + break; + } + } +done: + build_arg_table(&types, ap, argtable); +error: + freetypes(&types); + return (error || *argtable == NULL); +} +#endif /* WCHAR_SUPPORT */ + +/* + * Increase the size of the type table. Returns 0 on success. + */ +static int +__grow_type_table(struct typetable *types) +{ + enum typeid *const oldtable = types->table; + const int oldsize = types->tablesize; + enum typeid *newtable; + u_int n, newsize; + + /* Detect overflow */ + if (types->nextarg > MAX_POSARG) + return -1; + + newsize = oldsize * 2; + if (newsize < types->nextarg + 1) + newsize = types->nextarg + 1; + if (oldsize == STATIC_ARG_TBL_SIZE) { + if ((newtable = malloc(newsize * sizeof(enum typeid))) == NULL) + return -1; + bcopy(oldtable, newtable, oldsize * sizeof(enum typeid)); + } else { + newtable = realloc(oldtable, newsize * sizeof(enum typeid)); + if (newtable == NULL) + return -1; + } + for (n = oldsize; n < newsize; n++) + newtable[n] = T_UNUSED; + + types->table = newtable; + types->tablesize = newsize; + + return 0; +} + +/* + * Build the argument table from the completed type table. + * On malloc failure, *argtable is set to NULL. + */ +static void +build_arg_table(struct typetable *types, va_list ap, union arg **argtable) +{ + u_int n; + + if (types->tablemax >= STATIC_ARG_TBL_SIZE) { + *argtable = (union arg *) + malloc (sizeof(union arg) * (types->tablemax + 1)); + if (*argtable == NULL) + return; + } + + (*argtable) [0].intarg = 0; + for (n = 1; n <= types->tablemax; n++) { + switch (types->table[n]) { + case T_UNUSED: /* whoops! */ + (*argtable) [n].intarg = va_arg (ap, int); + break; + case TP_SCHAR: + (*argtable) [n].pschararg = va_arg (ap, signed char *); + break; + case TP_SHORT: + (*argtable) [n].pshortarg = va_arg (ap, short *); + break; + case T_INT: + (*argtable) [n].intarg = va_arg (ap, int); + break; + case T_U_INT: + (*argtable) [n].uintarg = va_arg (ap, unsigned int); + break; + case TP_INT: + (*argtable) [n].pintarg = va_arg (ap, int *); + break; + case T_LONG: + (*argtable) [n].longarg = va_arg (ap, long); + break; + case T_U_LONG: + (*argtable) [n].ulongarg = va_arg (ap, unsigned long); + break; + case TP_LONG: + (*argtable) [n].plongarg = va_arg (ap, long *); + break; + case T_LLONG: + (*argtable) [n].longlongarg = va_arg (ap, long long); + break; + case T_U_LLONG: + (*argtable) [n].ulonglongarg = va_arg (ap, unsigned long long); + break; + case TP_LLONG: + (*argtable) [n].plonglongarg = va_arg (ap, long long *); + break; + case T_PTRDIFFT: + (*argtable) [n].ptrdiffarg = va_arg (ap, ptrdiff_t); + break; + case TP_PTRDIFFT: + (*argtable) [n].pptrdiffarg = va_arg (ap, ptrdiff_t *); + break; + case T_SIZET: + (*argtable) [n].sizearg = va_arg (ap, size_t); + break; + case T_SSIZET: + (*argtable) [n].sizearg = va_arg (ap, ssize_t); + break; + case TP_SSIZET: + (*argtable) [n].pssizearg = va_arg (ap, ssize_t *); + break; + case T_INTMAXT: + (*argtable) [n].intmaxarg = va_arg (ap, intmax_t); + break; + case T_UINTMAXT: + (*argtable) [n].uintmaxarg = va_arg (ap, uintmax_t); + break; + case TP_INTMAXT: + (*argtable) [n].pintmaxarg = va_arg (ap, intmax_t *); + break; + case T_INT64T: + (*argtable) [n].intmaxarg = va_arg (ap, int64_t); + break; + case T_UINT64T: + (*argtable) [n].uintmaxarg = va_arg (ap, uint64_t); + break; + case T_DOUBLE: +#ifndef NO_FLOATING_POINT + (*argtable) [n].doublearg = va_arg (ap, double); +#endif + break; + case T_LONG_DOUBLE: +#ifndef NO_FLOATING_POINT + (*argtable) [n].longdoublearg = va_arg (ap, long double); +#endif + break; + case TP_CHAR: + (*argtable) [n].pchararg = va_arg (ap, char *); + break; + case TP_VOID: + (*argtable) [n].pvoidarg = va_arg (ap, void *); + break; + case T_WINT: + (*argtable) [n].wintarg = va_arg (ap, wint_t); + break; + case TP_WCHAR: + (*argtable) [n].pwchararg = va_arg (ap, wchar_t *); + break; + } + } +} diff --git a/lib/printf/printfcommon.h b/lib/printf/printfcommon.h new file mode 100644 index 0000000..f777be8 --- /dev/null +++ b/lib/printf/printfcommon.h @@ -0,0 +1,256 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Copyright (c) 2011 The FreeBSD Foundation + * + * Portions of this software were developed by David Chisnall + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file defines common routines used by both printf and wprintf. + * You must define CHAR to either char or wchar_t prior to including this. + */ + + +static CHAR *__ujtoa(uintmax_t, CHAR *, int, int, const char *); +static CHAR *__ultoa(u_long, CHAR *, int, int, const char *); + +#define NIOV 8 +struct io_state { + struct fbuf *cb; + size_t avail; +}; + +static inline void +io_init(struct io_state *iop, struct fbuf *cb) +{ + iop->cb = cb; + iop->avail = cb ? cb->len - (cb->pos - cb->buf) : 0; +} + +/* + * WARNING: The buffer passed to io_print() is not copied immediately; it must + * remain valid until io_flush() is called. + */ +static inline int +io_print(struct io_state *iop, const CHAR * __restrict ptr, size_t len) +{ + size_t copylen = len; + + if (!iop->cb || !len) + return 0; + if (iop->avail < copylen) + copylen = iop->avail; + + memcpy(iop->cb->pos, ptr, copylen); + iop->avail -= copylen; + iop->cb->pos += copylen; + return 0; +} + +/* + * Choose PADSIZE to trade efficiency vs. size. If larger printf + * fields occur frequently, increase PADSIZE and make the initialisers + * below longer. + */ +#define PADSIZE 16 /* pad chunk size */ +static const CHAR blanks[PADSIZE] = +{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; +static const CHAR zeroes[PADSIZE] = +{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; + +/* + * Pad with blanks or zeroes. 'with' should point to either the blanks array + * or the zeroes array. + */ +static inline int +io_pad(struct io_state *iop, int howmany, const CHAR * __restrict with) +{ + int n; + + while (howmany > 0) { + n = (howmany >= PADSIZE) ? PADSIZE : howmany; + if (io_print(iop, with, n)) + return (-1); + howmany -= n; + } + return (0); +} + +/* + * Print exactly len characters of the string spanning p to ep, truncating + * or padding with 'with' as necessary. + */ +static inline int +io_printandpad(struct io_state *iop, const CHAR *p, const CHAR *ep, + int len, const CHAR * __restrict with) +{ + int p_len; + + p_len = ep - p; + if (p_len > len) + p_len = len; + if (p_len > 0) { + if (io_print(iop, p, p_len)) + return (-1); + } else { + p_len = 0; + } + return (io_pad(iop, len - p_len, with)); +} + +/* + * Convert an unsigned long to ASCII for printf purposes, returning + * a pointer to the first character of the string representation. + * Octal numbers can be forced to have a leading zero; hex numbers + * use the given digits. + */ +static CHAR * +__ultoa(u_long val, CHAR *endp, int base, int octzero, const char *xdigs) +{ + CHAR *cp = endp; + long sval; + + /* + * Handle the three cases separately, in the hope of getting + * better/faster code. + */ + switch (base) { + case 10: + if (val < 10) { /* many numbers are 1 digit */ + *--cp = to_char(val); + return (cp); + } + /* + * On many machines, unsigned arithmetic is harder than + * signed arithmetic, so we do at most one unsigned mod and + * divide; this is sufficient to reduce the range of + * the incoming value to where signed arithmetic works. + */ + if (val > LONG_MAX) { + *--cp = to_char(val % 10); + sval = val / 10; + } else + sval = val; + do { + *--cp = to_char(sval % 10); + sval /= 10; + } while (sval != 0); + break; + + case 2: + do { + *--cp = to_char(val & 1); + val >>= 1; + } while (val); + break; + + case 8: + do { + *--cp = to_char(val & 7); + val >>= 3; + } while (val); + if (octzero && *cp != '0') + *--cp = '0'; + break; + + case 16: + do { + *--cp = xdigs[val & 15]; + val >>= 4; + } while (val); + break; + + default: /* oops */ + abort(); + } + return (cp); +} + +/* Identical to __ultoa, but for intmax_t. */ +static CHAR * +__ujtoa(uintmax_t val, CHAR *endp, int base, int octzero, const char *xdigs) +{ + CHAR *cp = endp; + intmax_t sval; + + /* quick test for small values; __ultoa is typically much faster */ + /* (perhaps instead we should run until small, then call __ultoa?) */ + if (val <= ULONG_MAX) + return (__ultoa((u_long)val, endp, base, octzero, xdigs)); + switch (base) { + case 10: + if (val < 10) { + *--cp = to_char(val % 10); + return (cp); + } + if (val > INTMAX_MAX) { + *--cp = to_char(val % 10); + sval = val / 10; + } else + sval = val; + do { + *--cp = to_char(sval % 10); + sval /= 10; + } while (sval != 0); + break; + + case 2: + do { + *--cp = to_char(val & 1); + val >>= 1; + } while (val); + break; + + case 8: + do { + *--cp = to_char(val & 7); + val >>= 3; + } while (val); + if (octzero && *cp != '0') + *--cp = '0'; + break; + + case 16: + do { + *--cp = xdigs[val & 15]; + val >>= 4; + } while (val); + break; + + default: + abort(); + } + return (cp); +} diff --git a/lib/printf/printflocal.h b/lib/printf/printflocal.h new file mode 100644 index 0000000..93318c8 --- /dev/null +++ b/lib/printf/printflocal.h @@ -0,0 +1,106 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "compiler.h" +#include "printfrr.h" + +/* + * Flags used during conversion. + */ +#define ALT 0x001 /* alternate form */ +#define LADJUST 0x004 /* left adjustment */ +#define LONGDBL 0x008 /* long double */ +#define LONGINT 0x010 /* long integer */ +#define LLONGINT 0x020 /* long long integer */ +#define SHORTINT 0x040 /* short integer */ +#define ZEROPAD 0x080 /* zero (as opposed to blank) pad */ +#define FPT 0x100 /* Floating point number */ +#define GROUPING 0x200 /* use grouping ("'" flag) */ + /* C99 additional size modifiers: */ +#define SIZET 0x400 /* size_t */ +#define PTRDIFFT 0x800 /* ptrdiff_t */ +#define INTMAXT 0x1000 /* intmax_t */ +#define CHARINT 0x2000 /* print char using int format */ +#define FASTINT 0x4000 /* int_fastN_t */ + +/* + * Macros for converting digits to letters and vice versa + */ +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +/* Size of the static argument table. */ +#define STATIC_ARG_TBL_SIZE 8 + +union arg { + int intarg; + u_int uintarg; + long longarg; + u_long ulongarg; + long long longlongarg; + unsigned long long ulonglongarg; + ptrdiff_t ptrdiffarg; + size_t sizearg; + intmax_t intmaxarg; + uintmax_t uintmaxarg; + void *pvoidarg; + char *pchararg; + signed char *pschararg; + short *pshortarg; + int *pintarg; + long *plongarg; + long long *plonglongarg; + ptrdiff_t *pptrdiffarg; + ssize_t *pssizearg; + intmax_t *pintmaxarg; +#ifndef NO_FLOATING_POINT + double doublearg; + long double longdoublearg; +#endif + wint_t wintarg; + wchar_t *pwchararg; +}; + +/* Handle positional parameters. */ +int _frr_find_arguments(const char *, va_list, union arg **) DSO_LOCAL; +#ifdef WCHAR_SUPPORT +int _frr_find_warguments(const wchar_t *, va_list, union arg **) DSO_LOCAL; +#endif + +/* returns number of bytes needed for full output, or -1 */ +ssize_t printfrr_extp(struct fbuf *, struct printfrr_eargs *ea, const void *) + DSO_LOCAL; +ssize_t printfrr_exti(struct fbuf *, struct printfrr_eargs *ea, uintmax_t) + DSO_LOCAL; diff --git a/lib/printf/vfprintf.c b/lib/printf/vfprintf.c new file mode 100644 index 0000000..3f6700c --- /dev/null +++ b/lib/printf/vfprintf.c @@ -0,0 +1,884 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Copyright (c) 2011 The FreeBSD Foundation + * + * Portions of this software were developed by David Chisnall + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_SYS_CDEFS_H +#include +#endif + +/* + * Actual printf innards. + * + * This code is large and complicated... + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "printflocal.h" + +#define CHAR char +#include "printfcommon.h" + +#ifdef WCHAR_SUPPORT +/* + * Convert a wide character string argument for the %ls format to a multibyte + * string representation. If not -1, prec specifies the maximum number of + * bytes to output, and also means that we can't assume that the wide char. + * string ends is null-terminated. + */ +static char * +__wcsconv(wchar_t *wcsarg, int prec) +{ + static const mbstate_t initial; + mbstate_t mbs; + char buf[MB_LEN_MAX]; + wchar_t *p; + char *convbuf; + size_t clen, nbytes; + + /* Allocate space for the maximum number of bytes we could output. */ + if (prec < 0) { + p = wcsarg; + mbs = initial; + nbytes = wcsrtombs(NULL, (const wchar_t **)&p, 0, &mbs); + if (nbytes == (size_t)-1) + return NULL; + } else { + /* + * Optimisation: if the output precision is small enough, + * just allocate enough memory for the maximum instead of + * scanning the string. + */ + if (prec < 128) + nbytes = prec; + else { + nbytes = 0; + p = wcsarg; + mbs = initial; + for (;;) { + clen = wcrtomb(buf, *p++, &mbs); + if (clen == 0 || clen == (size_t)-1 || + nbytes + clen > (size_t)prec) + break; + nbytes += clen; + } + } + } + if ((convbuf = malloc(nbytes + 1)) == NULL) + return NULL; + + /* Fill the output buffer. */ + p = wcsarg; + mbs = initial; + if ((nbytes = wcsrtombs(convbuf, (const wchar_t **)&p, + nbytes, &mbs)) == (size_t)-1) { + free(convbuf); + return NULL; + } + convbuf[nbytes] = '\0'; + return (convbuf); +} +#endif /* WCHAR_SUPPORT */ + +/* + * The size of the buffer we use as scratch space for integer + * conversions, among other things. We need enough space to + * write a uintmax_t in octal (plus one byte). + */ +#if UINTMAX_MAX <= UINT64_MAX +#define BUF 80 +#else +#error "BUF must be large enough to format a uintmax_t" +#endif + +/* + * Non-MT-safe version + */ +ssize_t +vbprintfrr(struct fbuf *cb_in, const char *fmt0, va_list ap) +{ + const char *fmt; /* format string */ + int ch; /* character from fmt */ + int n, n2; /* handy integer (short term usage) */ + const char *cp; /* handy char pointer (short term usage) */ + int flags; /* flags as above */ + int ret; /* return value accumulator */ + int width; /* width from format (%8d), or 0 */ + int prec; /* precision from format; <0 for N/A */ + int saved_errno; + char sign; /* sign prefix (' ', '+', '-', or \0) */ + + u_long ulval = 0; /* integer arguments %[diouxX] */ + uintmax_t ujval = 0; /* %j, %ll, %q, %t, %z integers */ + void *ptrval; /* %p */ + int base; /* base for [diouxX] conversion */ + int dprec; /* a copy of prec if [diouxX], 0 otherwise */ + int realsz; /* field size expanded by dprec, sign, etc */ + int size; /* size of converted field or string */ + int prsize; /* max size of printed field */ + const char *xdigs; /* digits for %[xX] conversion */ + struct io_state io; /* I/O buffering state */ + char buf[BUF]; /* buffer with space for digits of uintmax_t */ + char ox[2]; /* space for 0x; ox[1] is either x, X, or \0 */ + union arg *argtable; /* args, built due to positional arg */ + union arg statargtable [STATIC_ARG_TBL_SIZE]; + int nextarg; /* 1-based argument index */ + va_list orgap; /* original argument pointer */ + char *convbuf; /* wide to multibyte conversion result */ + char *extstart = NULL; /* where printfrr_ext* started printing */ + struct fbuf cb_copy, *cb; + struct fmt_outpos *opos; + + static const char xdigs_lower[16] = "0123456789abcdef"; + static const char xdigs_upper[16] = "0123456789ABCDEF"; + + /* BEWARE, these `goto error' on error. */ +#define PRINT(ptr, len) { \ + if (io_print(&io, (ptr), (len))) \ + goto error; \ +} +#define PAD(howmany, with) { \ + if (io_pad(&io, (howmany), (with))) \ + goto error; \ +} +#define PRINTANDPAD(p, ep, len, with) { \ + if (io_printandpad(&io, (p), (ep), (len), (with))) \ + goto error; \ +} +#define FLUSH() do { } while (0) + + /* + * Get the argument indexed by nextarg. If the argument table is + * built, use it to get the argument. If its not, get the next + * argument (and arguments must be gotten sequentially). + */ +#define GETARG(type) \ + ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : \ + (nextarg++, va_arg(ap, type))) + + /* + * To extend shorts properly, we need both signed and unsigned + * argument extraction methods. + */ +#define SARG() \ + (flags&LONGINT ? GETARG(long) : \ + flags&SHORTINT ? (long)(short)GETARG(int) : \ + flags&CHARINT ? (long)(signed char)GETARG(int) : \ + (long)GETARG(int)) +#define UARG() \ + (flags&LONGINT ? GETARG(u_long) : \ + flags&SHORTINT ? (u_long)(u_short)GETARG(int) : \ + flags&CHARINT ? (u_long)(u_char)GETARG(int) : \ + (u_long)GETARG(u_int)) +#define INTMAX_SIZE (INTMAXT|SIZET|PTRDIFFT|LLONGINT|LONGDBL) +#define SJARG() \ + (flags&LONGDBL ? GETARG(int64_t) : \ + flags&INTMAXT ? GETARG(intmax_t) : \ + flags&SIZET ? (intmax_t)GETARG(ssize_t) : \ + flags&PTRDIFFT ? (intmax_t)GETARG(ptrdiff_t) : \ + (intmax_t)GETARG(long long)) +#define UJARG() \ + (flags&LONGDBL ? GETARG(uint64_t) : \ + flags&INTMAXT ? GETARG(uintmax_t) : \ + flags&SIZET ? (uintmax_t)GETARG(size_t) : \ + flags&PTRDIFFT ? (uintmax_t)GETARG(ptrdiff_t) : \ + (uintmax_t)GETARG(unsigned long long)) + + /* + * Get * arguments, including the form *nn$. Preserve the nextarg + * that the argument can be gotten once the type is determined. + */ +#define GETASTER(val) \ + n2 = 0; \ + cp = fmt; \ + while (is_digit(*cp)) { \ + n2 = 10 * n2 + to_digit(*cp); \ + cp++; \ + } \ + if (*cp == '$') { \ + int hold = nextarg; \ + if (argtable == NULL) { \ + argtable = statargtable; \ + if (_frr_find_arguments (fmt0, orgap, &argtable)) { \ + ret = EOF; \ + goto error; \ + } \ + } \ + nextarg = n2; \ + val = GETARG (int); \ + nextarg = hold; \ + fmt = ++cp; \ + } else { \ + val = GETARG (int); \ + } + + xdigs = xdigs_lower; + saved_errno = errno; + convbuf = NULL; + fmt = (char *)fmt0; + argtable = NULL; + nextarg = 1; + va_copy(orgap, ap); + + if (cb_in) { + /* prevent printfrr exts from polluting cb->outpos */ + cb_copy = *cb_in; + cb_copy.outpos = NULL; + cb_copy.outpos_n = cb_copy.outpos_i = 0; + cb = &cb_copy; + } else + cb = NULL; + + io_init(&io, cb); + ret = 0; + + /* + * Scan the format for conversions (`%' character). + */ + for (;;) { + for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) + /* void */; + if ((n = fmt - cp) != 0) { + if ((unsigned)ret + n > INT_MAX) { + ret = EOF; + errno = EOVERFLOW; + goto error; + } + PRINT(cp, n); + ret += n; + } + if (ch == '\0') + goto done; + fmt++; /* skip over '%' */ + + flags = 0; + dprec = 0; + width = -1; + prec = -1; + sign = '\0'; + ox[1] = '\0'; + + if (cb_in && cb_in->outpos_i < cb_in->outpos_n) + opos = &cb_in->outpos[cb_in->outpos_i]; + else + opos = NULL; + +rflag: ch = *fmt++; +reswitch: switch (ch) { + case ' ': + /*- + * ``If the space and + flags both appear, the space + * flag will be ignored.'' + * -- ANSI X3J11 + */ + if (!sign) + sign = ' '; + goto rflag; + case '#': + flags |= ALT; + goto rflag; + case '*': + /*- + * ``A negative field width argument is taken as a + * - flag followed by a positive field width.'' + * -- ANSI X3J11 + * They don't exclude field widths read from args. + */ + GETASTER (width); + if (width >= 0) + goto rflag; + width = -width; + fallthrough; + case '-': + flags |= LADJUST; + goto rflag; + case '+': + sign = '+'; + goto rflag; + case '\'': + flags |= GROUPING; + goto rflag; + case '.': + if ((ch = *fmt++) == '*') { + GETASTER (prec); + goto rflag; + } + prec = 0; + while (is_digit(ch)) { + prec = 10 * prec + to_digit(ch); + ch = *fmt++; + } + goto reswitch; + case '0': + /*- + * ``Note that 0 is taken as a flag, not as the + * beginning of a field width.'' + * -- ANSI X3J11 + */ + flags |= ZEROPAD; + goto rflag; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = 0; + do { + n = 10 * n + to_digit(ch); + ch = *fmt++; + } while (is_digit(ch)); + if (ch == '$') { + nextarg = n; + if (argtable == NULL) { + argtable = statargtable; + if (_frr_find_arguments (fmt0, orgap, + &argtable)) { + ret = EOF; + goto error; + } + } + goto rflag; + } + width = n; + goto reswitch; + case 'L': + flags |= LONGDBL; + goto rflag; + case 'h': + if (flags & SHORTINT) { + flags &= ~SHORTINT; + flags |= CHARINT; + } else + flags |= SHORTINT; + goto rflag; + case 'j': + flags |= INTMAXT; + goto rflag; + case 'l': + if (flags & LONGINT) { + flags &= ~LONGINT; + flags |= LLONGINT; + } else + flags |= LONGINT; + goto rflag; + case 'q': + flags |= LLONGINT; /* not necessarily */ + goto rflag; + case 't': + flags |= PTRDIFFT; + goto rflag; + case 'w': + /* + * Fixed-width integer types. On all platforms we + * support, int8_t is equivalent to char, int16_t + * is equivalent to short, int32_t is equivalent + * to int, int64_t is equivalent to long long int. + * Furthermore, int_fast8_t, int_fast16_t and + * int_fast32_t are equivalent to int, and + * int_fast64_t is equivalent to long long int. + */ + flags &= ~(CHARINT|SHORTINT|LONGINT|LLONGINT|INTMAXT); + if (fmt[0] == 'f') { + flags |= FASTINT; + fmt++; + } else { + flags &= ~FASTINT; + } + if (fmt[0] == '8') { + if (!(flags & FASTINT)) + flags |= CHARINT; + else + (void) 0; /* no flag set = 32 */ + fmt += 1; + } else if (fmt[0] == '1' && fmt[1] == '6') { + if (!(flags & FASTINT)) + flags |= SHORTINT; + else + (void) 0; /* no flag set = 32 */ + fmt += 2; + } else if (fmt[0] == '3' && fmt[1] == '2') { + /* no flag set = 32 */ + fmt += 2; + } else if (fmt[0] == '6' && fmt[1] == '4') { + flags |= LLONGINT; + fmt += 2; + } else { + if (flags & FASTINT) { + flags &= ~FASTINT; + fmt--; + } + goto invalid; + } + goto rflag; + case 'z': + flags |= SIZET; + goto rflag; + case 'B': + case 'b': + if (flags & INTMAX_SIZE) + ujval = UJARG(); + else + ulval = UARG(); + base = 2; + /* leading 0b/B only if non-zero */ + if (flags & ALT && + (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0)) + ox[1] = ch; + goto nosign; + break; + case 'C': + flags |= LONGINT; + fallthrough; + case 'c': +#ifdef WCHAR_SUPPORT + if (flags & LONGINT) { + static const mbstate_t initial; + mbstate_t mbs; + size_t mbseqlen; + + mbs = initial; + mbseqlen = wcrtomb(cp = buf, + (wchar_t)GETARG(wint_t), &mbs); + if (mbseqlen == (size_t)-1) { + goto error; + } + size = (int)mbseqlen; + } else +#endif /* WCHAR_SUPPORT */ + { + buf[0] = GETARG(int); + cp = buf; + size = 1; + } + sign = '\0'; + break; + case 'D': + flags |= LONGINT; + fallthrough; + case 'd': + case 'i': + if (flags & INTMAX_SIZE) + ujval = SJARG(); + else + ulval = SARG(); + + if (printfrr_ext_char(fmt[0])) { + struct printfrr_eargs ea = { + .fmt = fmt, + .precision = prec, + .width = width, + .alt_repr = !!(flags & ALT), + .leftadj = !!(flags & LADJUST), + }; + + if (cb) + extstart = cb->pos; + + size = printfrr_exti(cb, &ea, + (flags & INTMAX_SIZE) ? ujval + : (uintmax_t)ulval); + if (size >= 0) { + fmt = ea.fmt; + width = ea.width; + goto ext_printed; + } + } + if (flags & INTMAX_SIZE) { + if ((intmax_t)ujval < 0) { + ujval = -ujval; + sign = '-'; + } + } else { + if ((long)ulval < 0) { + ulval = -ulval; + sign = '-'; + } + } + base = 10; + goto number; +#ifndef NO_FLOATING_POINT + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + if (flags & LONGDBL) { + long double arg = GETARG(long double); + char fmt[6] = "%.*L"; + fmt[4] = ch; + fmt[5] = '\0'; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(buf, sizeof(buf), fmt, prec, arg); +#pragma GCC diagnostic pop + } else { + double arg = GETARG(double); + char fmt[5] = "%.*"; + fmt[3] = ch; + fmt[4] = '\0'; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + snprintf(buf, sizeof(buf), fmt, prec, arg); +#pragma GCC diagnostic pop + } + cp = buf; + /* for proper padding */ + if (*cp == '-') { + cp++; + sign = '-'; + } + /* "inf" */ + if (!is_digit(*cp) && *cp != '.') + flags &= ~ZEROPAD; + size = strlen(buf); + break; +#endif + case 'm': + cp = strerror(saved_errno); + size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp); + sign = '\0'; + break; + case 'O': + flags |= LONGINT; + fallthrough; + case 'o': + if (flags & INTMAX_SIZE) + ujval = UJARG(); + else + ulval = UARG(); + base = 8; + goto nosign; + case 'p': + /*- + * ``The argument shall be a pointer to void. The + * value of the pointer is converted to a sequence + * of printable characters, in an implementation- + * defined manner.'' + * -- ANSI X3J11 + */ + ptrval = GETARG(void *); + if (printfrr_ext_char(fmt[0])) { + struct printfrr_eargs ea = { + .fmt = fmt, + .precision = prec, + .width = width, + .alt_repr = !!(flags & ALT), + .leftadj = !!(flags & LADJUST), + }; + + if (cb) + extstart = cb->pos; + + size = printfrr_extp(cb, &ea, ptrval); + if (size >= 0) { + fmt = ea.fmt; + width = ea.width; + goto ext_printed; + } + } + ujval = (uintmax_t)(uintptr_t)ptrval; + base = 16; + xdigs = xdigs_lower; + flags = flags | INTMAXT; + ox[1] = 'x'; + goto nosign; + case 'S': + flags |= LONGINT; + fallthrough; + case 's': +#ifdef WCHAR_SUPPORT + if (flags & LONGINT) { + wchar_t *wcp; + + if (convbuf != NULL) + free(convbuf); + if ((wcp = GETARG(wchar_t *)) == NULL) + cp = "(null)"; + else { + convbuf = __wcsconv(wcp, prec); + if (convbuf == NULL) { + goto error; + } + cp = convbuf; + } + } else +#endif + if ((cp = GETARG(char *)) == NULL) + cp = "(null)"; + size = (prec >= 0) ? strnlen(cp, prec) : strlen(cp); + sign = '\0'; + break; + case 'U': + flags |= LONGINT; + fallthrough; + case 'u': + if (flags & INTMAX_SIZE) + ujval = UJARG(); + else + ulval = UARG(); + base = 10; + goto nosign; + case 'X': + xdigs = xdigs_upper; + goto hex; + case 'x': + xdigs = xdigs_lower; +hex: + if (flags & INTMAX_SIZE) + ujval = UJARG(); + else + ulval = UARG(); + base = 16; + /* leading 0x/X only if non-zero */ + if (flags & ALT && + (flags & INTMAX_SIZE ? ujval != 0 : ulval != 0)) + ox[1] = ch; + + flags &= ~GROUPING; + /* unsigned conversions */ +nosign: sign = '\0'; + /*- + * ``... diouXx conversions ... if a precision is + * specified, the 0 flag will be ignored.'' + * -- ANSI X3J11 + */ +number: if ((dprec = prec) >= 0) + flags &= ~ZEROPAD; + + /*- + * ``The result of converting a zero value with an + * explicit precision of zero is no characters.'' + * -- ANSI X3J11 + * + * ``The C Standard is clear enough as is. The call + * printf("%#.0o", 0) should print 0.'' + * -- Defect Report #151 + */ + cp = buf + BUF; + if (flags & INTMAX_SIZE) { + if (ujval != 0 || prec != 0 || + (flags & ALT && base == 8)) + cp = __ujtoa(ujval, buf + BUF, base, + flags & ALT, xdigs); + } else { + if (ulval != 0 || prec != 0 || + (flags & ALT && base == 8)) + cp = __ultoa(ulval, buf + BUF, base, + flags & ALT, xdigs); + } + size = buf + BUF - cp; + if (size > BUF) /* should never happen */ + abort(); + break; + default: /* "%?" prints ?, unless ? is NUL */ + if (ch == '\0') + goto done; +invalid: + /* pretend it was %c with argument ch */ + buf[0] = ch; + cp = buf; + size = 1; + sign = '\0'; + opos = NULL; + break; + } + + /* + * All reasonable formats wind up here. At this point, `cp' + * points to a string which (if not flags&LADJUST) should be + * padded out to `width' places. If flags&ZEROPAD, it should + * first be prefixed by any sign or other prefix; otherwise, + * it should be blank padded before the prefix is emitted. + * After any left-hand padding and prefixing, emit zeroes + * required by a decimal [diouxX] precision, then print the + * string proper, then emit zeroes required by any leftover + * floating precision; finally, if LADJUST, pad with blanks. + * + * Compute actual size, so we know how much to pad. + * size excludes decimal prec; realsz includes it. + */ + if (width < 0) + width = 0; + + realsz = dprec > size ? dprec : size; + if (sign) + realsz++; + if (ox[1]) + realsz += 2; + + prsize = width > realsz ? width : realsz; + if ((unsigned int)ret + prsize > INT_MAX) { + ret = EOF; + errno = EOVERFLOW; + goto error; + } + + /* right-adjusting blank padding */ + if ((flags & (LADJUST|ZEROPAD)) == 0) + PAD(width - realsz, blanks); + + if (opos) + opos->off_start = cb->pos - cb->buf; + + /* prefix */ + if (sign) + PRINT(&sign, 1); + + if (ox[1]) { /* ox[1] is either x, X, or \0 */ + ox[0] = '0'; + PRINT(ox, 2); + } + + /* right-adjusting zero padding */ + if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) + PAD(width - realsz, zeroes); + + /* the string or number proper */ + /* leading zeroes from decimal precision */ + PAD(dprec - size, zeroes); + PRINT(cp, size); + + if (opos) { + opos->off_end = cb->pos - cb->buf; + cb_in->outpos_i++; + } + + /* left-adjusting padding (always blank) */ + if (flags & LADJUST) + PAD(width - realsz, blanks); + + /* finally, adjust ret */ + ret += prsize; + + FLUSH(); /* copy out the I/O vectors */ + continue; + +ext_printed: + /* when we arrive here, a printfrr extension has written to cb + * (if non-NULL), but we still need to handle padding. The + * original cb->pos is in extstart; the return value from the + * ext is in size. + * + * Keep analogous to code above please. + */ + + if (width < 0) + width = 0; + + realsz = size; + prsize = width > realsz ? width : realsz; + if ((unsigned int)ret + prsize > INT_MAX) { + ret = EOF; + errno = EOVERFLOW; + goto error; + } + + /* right-adjusting blank padding - need to move the chars + * that the extension has already written. Should be very + * rare. + */ + if (cb && width > size && (flags & (LADJUST|ZEROPAD)) == 0) { + size_t nwritten = cb->pos - extstart; + size_t navail = cb->buf + cb->len - extstart; + size_t npad = width - realsz; + size_t nmove; + + if (navail < npad) + navail = 0; + else + navail -= npad; + nmove = MIN(nwritten, navail); + + memmove(extstart + npad, extstart, nmove); + + cb->pos = extstart; + PAD(npad, blanks); + cb->pos += nmove; + extstart += npad; + } + + io.avail = cb ? cb->len - (cb->pos - cb->buf) : 0; + + if (opos && extstart <= cb->pos) { + opos->off_start = extstart - cb->buf; + opos->off_end = cb->pos - cb->buf; + cb_in->outpos_i++; + } + + /* left-adjusting padding (always blank) */ + if (flags & LADJUST) + PAD(width - realsz, blanks); + + /* finally, adjust ret */ + ret += prsize; + + FLUSH(); /* copy out the I/O vectors */ + } +done: + FLUSH(); +error: + va_end(orgap); + if (convbuf != NULL) + free(convbuf); + if ((argtable != NULL) && (argtable != statargtable)) + free (argtable); + if (cb_in) + cb_in->pos = cb->pos; + return (ret); + /* NOTREACHED */ +} + diff --git a/lib/printfrr.h b/lib/printfrr.h new file mode 100644 index 0000000..b8bef56 --- /dev/null +++ b/lib/printfrr.h @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_PRINTFRR_H +#define _FRR_PRINTFRR_H + +#include +#include +#include + +#include "compiler.h" +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct fmt_outpos { + unsigned int off_start, off_end; +}; + +struct fbuf { + char *buf; + char *pos; + size_t len; + + struct fmt_outpos *outpos; + size_t outpos_n, outpos_i; +}; + +#define at(a, b) PRINTFRR(a, b) +#define atn(a, b) \ + at(a, b) __attribute__((nonnull(1) _RET_NONNULL)) +#define atm(a, b) \ + atn(a, b) __attribute__((malloc)) + +/* return value is length needed for the full string (excluding \0) in all + * cases. The functions write as much as they can, but continue regardless, + * so the return value is independent of buffer length. Both bprintfrr and + * snprintf also accept NULL as output buffer. + */ + +/* bprintfrr does NOT null terminate! use sparingly (only provided since it's + * the most direct interface) - useful for incrementally building long text + * (call bprintfrr repeatedly with the same buffer) + */ +ssize_t vbprintfrr(struct fbuf *out, const char *fmt, va_list) at(2, 0); +ssize_t bprintfrr(struct fbuf *out, const char *fmt, ...) at(2, 3); + +/* these do null terminate like their snprintf cousins */ +ssize_t vsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0); +ssize_t snprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4); + +/* c = continue / concatenate (append at the end of the string) + * return value is would-be string length (regardless of buffer length), + * i.e. includes already written chars */ +ssize_t vcsnprintfrr(char *out, size_t sz, const char *fmt, va_list) at(3, 0); +ssize_t csnprintfrr(char *out, size_t sz, const char *fmt, ...) at(3, 4); + +/* memory allocations don't fail in FRR, so you always get something here. + * (in case of error, returns a strdup of the format string) */ +char *vasprintfrr(struct memtype *mt, const char *fmt, va_list) atm(2, 0); +char *asprintfrr(struct memtype *mt, const char *fmt, ...) atm(2, 3); + +/* try to use provided buffer (presumably from stack), allocate if it's too + * short. Must call XFREE(mt, return value) if return value != out. + */ +char *vasnprintfrr(struct memtype *mt, char *out, size_t sz, + const char *fmt, va_list) atn(4, 0); +char *asnprintfrr(struct memtype *mt, char *out, size_t sz, + const char *fmt, ...) atn(4, 5); + +#define printfrr(fmt, ...) \ + do { \ + char buf[256], *out; \ + out = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), fmt, \ + ##__VA_ARGS__); \ + fputs(out, stdout); \ + if (out != buf) \ + XFREE(MTYPE_TMP, out); \ + } while (0) + +#undef at +#undef atm +#undef atn + +/* extension specs must start with a capital letter (this is a restriction + * for both performance's and human understanding's sake.) + * + * Note that the entire thing mostly works because a letter directly following + * a %p print specifier is extremely unlikely to occur (why would you want to + * print "0x12345678HELLO"?) Normally, you'd expect spacing or punctuation + * after a placeholder. That also means that neither of those works well for + * extension purposes, e.g. "%p{foo}" is reasonable to see actually used. + * + * TODO: would be nice to support a "%pF%dF" specifier that consumes 2 + * arguments, e.g. to pass an integer + a list of known values... can be + * done, but a bit tricky. + */ +#define printfrr_ext_char(ch) ((ch) >= 'A' && (ch) <= 'Z') + +struct printfrr_eargs; + +struct printfrr_ext { + /* embedded string to minimize cache line pollution */ + char match[8]; + + /* both can be given, if not the code continues searching + * (you can do %pX and %dX in 2 different entries) + * + * return value: number of bytes that would be printed if the buffer + * was large enough. be careful about not under-reporting this; + * otherwise asnprintf() & co. will get broken. Returning -1 means + * something went wrong & default %p/%d handling should be executed. + * + * to consume extra input flags after %pXY, increment *fmt. It points + * at the first character after %pXY at entry. Convention is to make + * those flags lowercase letters or numbers. + */ + ssize_t (*print_ptr)(struct fbuf *buf, struct printfrr_eargs *info, + const void *); + ssize_t (*print_int)(struct fbuf *buf, struct printfrr_eargs *info, + uintmax_t); +}; + +/* additional information passed to extended formatters */ + +struct printfrr_eargs { + /* position in the format string. Points to directly after the + * extension specifier. Increment when consuming extra "flag + * characters". + */ + const char *fmt; + + /* %.1234x / %.*x + * not POSIX compatible when used with %p, will cause warnings from + * GCC & clang. Usable with %d. Not used by the printfrr() itself + * for extension specifiers, so essentially available as a "free" + * parameter. -1 if not specified. Value in the format string + * cannot be negative, but negative values can be passed with %.*x + */ + int precision; + + /* %1234x / %*x + * regular width specification. Internally handled by printfrr(), set + * to 0 if consumed by the extension in order to suppress standard + * width/padding behavior. 0 if not specified. + * + * NB: always positive, even if a negative value is passed in with + * %*x. (The sign is used for the - flag.) + */ + int width; + + /* %#x + * "alternate representation" flag, not POSIX compatible when used + * with %p or %d, will cause warnings from GCC & clang. Not used by + * printfrr() itself for extension specifiers. + */ + bool alt_repr; + + /* %-x + * left-pad flag. Internally handled by printfrr() if width is + * nonzero. Only use if the extension sets width to 0. + */ + bool leftadj; +}; + +/* for any extension that needs a buffer length */ + +static inline ssize_t printfrr_ext_len(struct printfrr_eargs *ea) +{ + ssize_t rv; + + if (ea->precision >= 0) + rv = ea->precision; + else if (ea->width >= 0) { + rv = ea->width; + ea->width = -1; + } else + rv = -1; + + return rv; +} + +/* no locking - must be called when single threaded (e.g. at startup.) + * this restriction hopefully won't be a huge bother considering normal usage + * scenarios... + */ +void printfrr_ext_reg(const struct printfrr_ext *); + +#define printfrr_ext_autoreg_p(matchs, print_fn) \ + static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \ + const void *); \ + static const struct printfrr_ext _printext_##print_fn = { \ + .match = matchs, \ + .print_ptr = print_fn, \ + }; \ + static void _printreg_##print_fn(void) __attribute__((constructor)); \ + static void _printreg_##print_fn(void) \ + { \ + printfrr_ext_reg(&_printext_##print_fn); \ + } \ + MACRO_REQUIRE_SEMICOLON() + +#define printfrr_ext_autoreg_i(matchs, print_fn) \ + static ssize_t print_fn(struct fbuf *, struct printfrr_eargs *, \ + uintmax_t); \ + static const struct printfrr_ext _printext_##print_fn = { \ + .match = matchs, \ + .print_int = print_fn, \ + }; \ + static void _printreg_##print_fn(void) __attribute__((constructor)); \ + static void _printreg_##print_fn(void) \ + { \ + printfrr_ext_reg(&_printext_##print_fn); \ + } \ + MACRO_REQUIRE_SEMICOLON() + +/* fbuf helper functions - note all 3 of these return the length that would + * be written regardless of how much space was available in the buffer, as + * needed for implementing printfrr extensions. (They also accept NULL buf + * for that.) + */ + +static inline ssize_t bputs(struct fbuf *buf, const char *str) +{ + size_t len = strlen(str); + size_t ncopy; + + if (!buf) + return len; + + ncopy = MIN(len, (size_t)(buf->buf + buf->len - buf->pos)); + memcpy(buf->pos, str, ncopy); + buf->pos += ncopy; + + return len; +} + +static inline ssize_t bputch(struct fbuf *buf, char ch) +{ + if (buf && buf->pos < buf->buf + buf->len) + *buf->pos++ = ch; + return 1; +} + +static inline ssize_t bputhex(struct fbuf *buf, uint8_t val) +{ + static const char hexch[] = "0123456789abcdef"; + + if (buf && buf->pos < buf->buf + buf->len) + *buf->pos++ = hexch[(val >> 4) & 0xf]; + if (buf && buf->pos < buf->buf + buf->len) + *buf->pos++ = hexch[val & 0xf]; + return 2; +} + +/* %pVA extension, equivalent to Linux kernel %pV */ + +struct va_format { + const char *fmt; + va_list *va; +}; + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pFB" (struct fbuf *) +#pragma FRR printfrr_ext "%pVA" (struct va_format *) + +#pragma FRR printfrr_ext "%pHX" (signed char *) +#pragma FRR printfrr_ext "%pHX" (unsigned char *) +#pragma FRR printfrr_ext "%pHX" (void *) +#pragma FRR printfrr_ext "%pHS" (signed char *) +#pragma FRR printfrr_ext "%pHS" (unsigned char *) +#pragma FRR printfrr_ext "%pHS" (void *) + +#pragma FRR printfrr_ext "%pSE" (char *) +#pragma FRR printfrr_ext "%pSQ" (char *) + +#pragma FRR printfrr_ext "%pTS" (struct timespec *) +#pragma FRR printfrr_ext "%pTV" (struct timeval *) +#pragma FRR printfrr_ext "%pTT" (time_t *) +#endif + +/* when using non-ISO-C compatible extension specifiers... */ + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#define FMT_NSTD_BEGIN +#define FMT_NSTD_END +#else /* !_FRR_ATTRIBUTE_PRINTFRR */ +#define FMT_NSTD_BEGIN \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wformat\"") \ + /* end */ +#define FMT_NSTD_END \ + _Pragma("GCC diagnostic pop") \ + /* end */ +#endif + +#define FMT_NSTD(expr) \ + ({ \ + FMT_NSTD_BEGIN \ + typeof(expr) _v; \ + _v = expr; \ + FMT_NSTD_END \ + _v; \ + }) \ + /* end */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/privs.c b/lib/privs.c new file mode 100644 index 0000000..717a2e4 --- /dev/null +++ b/lib/privs.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra privileges. + * + * Copyright (C) 2003 Paul Jakma. + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + */ +#include + +#include +#include + +#ifdef HAVE_LCAPS +#include +#include +#endif /* HAVE_LCAPS */ + +#include "log.h" +#include "privs.h" +#include "memory.h" +#include "frr_pthread.h" +#include "lib_errors.h" +#include "lib/queue.h" + +DEFINE_MTYPE_STATIC(LIB, PRIVS, "Privilege information"); + +/* + * Different capabilities/privileges apis have different characteristics: some + * are process-wide, and some are per-thread. + */ +#ifdef HAVE_CAPABILITIES +#ifdef HAVE_LCAPS +static const bool privs_per_process; /* = false */ +#else +static const bool privs_per_process = true; +#endif /* HAVE_LCAPS */ +#else /* HAVE_CAPABILITIES */ +static const bool privs_per_process = true; +#endif + +#ifdef HAVE_CAPABILITIES + +/* sort out some generic internal types for: + * + * privilege values (cap_value_t, priv_t) -> pvalue_t + * privilege set (..., priv_set_t) -> pset_t + * privilege working storage (cap_t, ...) -> pstorage_t + * + * values we think of as numeric (they're ints really, but we dont know) + * sets are mostly opaque, to hold a set of privileges, related in some way. + * storage binds together a set of sets we're interested in. + * (in reality: cap_value_t and priv_t are ints) + */ +#ifdef HAVE_LCAPS +/* Linux doesn't have a 'set' type: a set of related privileges */ +struct _pset { + int num; + cap_value_t *caps; +}; +typedef cap_value_t pvalue_t; +typedef struct _pset pset_t; +typedef cap_t pstorage_t; + +#else /* no LCAPS */ +#error "HAVE_CAPABILITIES defined, but neither LCAPS nor Solaris Capabilties!" +#endif /* HAVE_LCAPS */ +#endif /* HAVE_CAPABILITIES */ + +/* the default NULL state we report is RAISED, but could be LOWERED if + * zprivs_terminate is called and the NULL handler is installed. + */ +static zebra_privs_current_t zprivs_null_state = ZPRIVS_RAISED; + +/* internal privileges state */ +static struct _zprivs_t { +#ifdef HAVE_CAPABILITIES + pstorage_t caps; /* working storage */ + pset_t *syscaps_p; /* system-type requested permitted caps */ + pset_t *syscaps_i; /* system-type requested inheritable caps */ +#endif /* HAVE_CAPABILITIES */ + uid_t zuid, /* uid to run as */ + zsuid; /* saved uid */ + gid_t zgid; /* gid to run as */ + gid_t vtygrp; /* gid for vty sockets */ +} zprivs_state; + +/* externally exported but not directly accessed functions */ +#ifdef HAVE_CAPABILITIES +int zprivs_change_caps(zebra_privs_ops_t); +zebra_privs_current_t zprivs_state_caps(void); +#endif /* HAVE_CAPABILITIES */ +int zprivs_change_uid(zebra_privs_ops_t); +zebra_privs_current_t zprivs_state_uid(void); +int zprivs_change_null(zebra_privs_ops_t); +zebra_privs_current_t zprivs_state_null(void); + +#ifdef HAVE_CAPABILITIES +/* internal capability API */ +static pset_t *zcaps2sys(zebra_capabilities_t *, int); +static void zprivs_caps_init(struct zebra_privs_t *); +static void zprivs_caps_terminate(void); + +/* Map of Quagga abstract capabilities to system capabilities */ +static struct { + int num; + pvalue_t *system_caps; +} cap_map[ZCAP_MAX] = { +#ifdef HAVE_LCAPS /* Quagga -> Linux capabilities mappings */ + [ZCAP_SETID] = + { + 2, (pvalue_t[]){CAP_SETGID, CAP_SETUID}, + }, + [ZCAP_BIND] = + { + 1, (pvalue_t[]){CAP_NET_BIND_SERVICE}, + }, + [ZCAP_NET_ADMIN] = + { + 1, (pvalue_t[]){CAP_NET_ADMIN}, + }, + [ZCAP_NET_RAW] = + { + 1, (pvalue_t[]){CAP_NET_RAW}, + }, + [ZCAP_CHROOT] = + { + 1, + (pvalue_t[]){ + CAP_SYS_CHROOT, + }, + }, + [ZCAP_NICE] = + { + 1, (pvalue_t[]){CAP_SYS_NICE}, + }, + [ZCAP_PTRACE] = + { + 1, (pvalue_t[]){CAP_SYS_PTRACE}, + }, + [ZCAP_DAC_OVERRIDE] = + { + 1, (pvalue_t[]){CAP_DAC_OVERRIDE}, + }, + [ZCAP_READ_SEARCH] = + { + 1, (pvalue_t[]){CAP_DAC_READ_SEARCH}, + }, + [ZCAP_SYS_ADMIN] = + { + 1, (pvalue_t[]){CAP_SYS_ADMIN}, + }, + [ZCAP_FOWNER] = + { + 1, (pvalue_t[]){CAP_FOWNER}, + }, + [ZCAP_IPC_LOCK] = + { + 1, (pvalue_t[]){CAP_IPC_LOCK}, + }, + [ZCAP_SYS_RAWIO] = + { + 1, (pvalue_t[]){CAP_SYS_RAWIO}, + }, +#endif /* HAVE_LCAPS */ +}; + +#ifdef HAVE_LCAPS +/* Linux forms of capabilities methods */ +/* convert zebras privileges to system capabilities */ +static pset_t *zcaps2sys(zebra_capabilities_t *zcaps, int num) +{ + pset_t *syscaps; + int i, j = 0, count = 0; + + if (!num) + return NULL; + + /* first count up how many system caps we have */ + for (i = 0; i < num; i++) + count += cap_map[zcaps[i]].num; + + if ((syscaps = XCALLOC(MTYPE_PRIVS, (sizeof(pset_t) * num))) == NULL) { + fprintf(stderr, "%s: could not allocate syscaps!", __func__); + return NULL; + } + + syscaps->caps = XCALLOC(MTYPE_PRIVS, (sizeof(pvalue_t) * count)); + + if (!syscaps->caps) { + fprintf(stderr, "%s: could not XCALLOC caps!", __func__); + return NULL; + } + + /* copy the capabilities over */ + count = 0; + for (i = 0; i < num; i++) + for (j = 0; j < cap_map[zcaps[i]].num; j++) + syscaps->caps[count++] = + cap_map[zcaps[i]].system_caps[j]; + + /* iterations above should be exact same as previous count, obviously.. + */ + syscaps->num = count; + + return syscaps; +} + +/* set or clear the effective capabilities to/from permitted */ +int zprivs_change_caps(zebra_privs_ops_t op) +{ + cap_flag_value_t cflag; + + /* should be no possibility of being called without valid caps */ + assert(zprivs_state.syscaps_p && zprivs_state.caps); + if (!(zprivs_state.syscaps_p && zprivs_state.caps)) + exit(1); + + if (op == ZPRIVS_RAISE) + cflag = CAP_SET; + else if (op == ZPRIVS_LOWER) + cflag = CAP_CLEAR; + else + return -1; + + if (!cap_set_flag(zprivs_state.caps, CAP_EFFECTIVE, + zprivs_state.syscaps_p->num, + zprivs_state.syscaps_p->caps, cflag)) + return cap_set_proc(zprivs_state.caps); + return -1; +} + +zebra_privs_current_t zprivs_state_caps(void) +{ + int i; + cap_flag_value_t val; + + /* should be no possibility of being called without valid caps */ + assert(zprivs_state.syscaps_p && zprivs_state.caps); + if (!(zprivs_state.syscaps_p && zprivs_state.caps)) + exit(1); + + for (i = 0; i < zprivs_state.syscaps_p->num; i++) { + if (cap_get_flag(zprivs_state.caps, + zprivs_state.syscaps_p->caps[i], CAP_EFFECTIVE, + &val)) { + flog_err( + EC_LIB_SYSTEM_CALL, + "zprivs_state_caps: could not cap_get_flag, %s", + safe_strerror(errno)); + return ZPRIVS_UNKNOWN; + } + if (val == CAP_SET) + return ZPRIVS_RAISED; + } + return ZPRIVS_LOWERED; +} + +/** Release private cap state if allocated. */ +static void zprivs_state_free_caps(void) +{ + if (zprivs_state.syscaps_p) { + if (zprivs_state.syscaps_p->num) + XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p->caps); + + XFREE(MTYPE_PRIVS, zprivs_state.syscaps_p); + } + + if (zprivs_state.syscaps_i) { + if (zprivs_state.syscaps_i->num) + XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i->caps); + + XFREE(MTYPE_PRIVS, zprivs_state.syscaps_i); + } + + if (zprivs_state.caps) { + cap_free(zprivs_state.caps); + zprivs_state.caps = NULL; + } +} + +static void zprivs_caps_init(struct zebra_privs_t *zprivs) +{ + /* Release allocated zcaps if this function was called before. */ + zprivs_state_free_caps(); + + zprivs_state.syscaps_p = zcaps2sys(zprivs->caps_p, zprivs->cap_num_p); + zprivs_state.syscaps_i = zcaps2sys(zprivs->caps_i, zprivs->cap_num_i); + + /* Tell kernel we want caps maintained across uid changes */ + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + fprintf(stderr, + "privs_init: could not set PR_SET_KEEPCAPS, %s\n", + safe_strerror(errno)); + exit(1); + } + + /* we have caps, we have no need to ever change back the original user + */ + /* only change uid if we don't have the correct one */ + if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { + if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) { + fprintf(stderr, + "zprivs_init (cap): could not setreuid, %s\n", + safe_strerror(errno)); + exit(1); + } + } + + if (!(zprivs_state.caps = cap_init())) { + fprintf(stderr, "privs_init: failed to cap_init, %s\n", + safe_strerror(errno)); + exit(1); + } + + if (cap_clear(zprivs_state.caps)) { + fprintf(stderr, "privs_init: failed to cap_clear, %s\n", + safe_strerror(errno)); + exit(1); + } + + /* set permitted caps, if any */ + if (zprivs_state.syscaps_p && zprivs_state.syscaps_p->num) { + cap_set_flag(zprivs_state.caps, CAP_PERMITTED, + zprivs_state.syscaps_p->num, + zprivs_state.syscaps_p->caps, CAP_SET); + } + + /* set inheritable caps, if any */ + if (zprivs_state.syscaps_i && zprivs_state.syscaps_i->num) { + cap_set_flag(zprivs_state.caps, CAP_INHERITABLE, + zprivs_state.syscaps_i->num, + zprivs_state.syscaps_i->caps, CAP_SET); + } + + /* apply caps. CAP_EFFECTIVE is cleared. we'll raise the caps as + * and when, and only when, they are needed. + */ + if (cap_set_proc(zprivs_state.caps)) { + cap_t current_caps; + char *current_caps_text = NULL; + char *wanted_caps_text = NULL; + + fprintf(stderr, "privs_init: initial cap_set_proc failed: %s\n", + safe_strerror(errno)); + + current_caps = cap_get_proc(); + if (current_caps) { + current_caps_text = cap_to_text(current_caps, NULL); + cap_free(current_caps); + } + + wanted_caps_text = cap_to_text(zprivs_state.caps, NULL); + fprintf(stderr, "Wanted caps: %s\n", + wanted_caps_text ? wanted_caps_text : "???"); + fprintf(stderr, "Have caps: %s\n", + current_caps_text ? current_caps_text : "???"); + if (current_caps_text) + cap_free(current_caps_text); + if (wanted_caps_text) + cap_free(wanted_caps_text); + + exit(1); + } + + /* set methods for the caller to use */ + zprivs->change = zprivs_change_caps; + zprivs->current_state = zprivs_state_caps; +} + +static void zprivs_caps_terminate(void) +{ + /* Clear all capabilities, if we have any. */ + if (zprivs_state.caps) + cap_clear(zprivs_state.caps); + else + return; + + /* and boom, capabilities are gone forever */ + if (cap_set_proc(zprivs_state.caps)) { + fprintf(stderr, "privs_terminate: cap_set_proc failed, %s", + safe_strerror(errno)); + exit(1); + } + + zprivs_state_free_caps(); +} +#else /* !HAVE_LCAPS */ +#error "no Linux capabilities, dazed and confused..." +#endif /* HAVE_LCAPS */ +#endif /* HAVE_CAPABILITIES */ + +int zprivs_change_uid(zebra_privs_ops_t op) +{ + if (zprivs_state.zsuid == zprivs_state.zuid) + return 0; + if (op == ZPRIVS_RAISE) + return seteuid(zprivs_state.zsuid); + else if (op == ZPRIVS_LOWER) + return seteuid(zprivs_state.zuid); + else + return -1; +} + +zebra_privs_current_t zprivs_state_uid(void) +{ + return ((zprivs_state.zuid == geteuid()) ? ZPRIVS_LOWERED + : ZPRIVS_RAISED); +} + +int zprivs_change_null(zebra_privs_ops_t op) +{ + return 0; +} + +zebra_privs_current_t zprivs_state_null(void) +{ + return zprivs_null_state; +} + +#ifndef HAVE_GETGROUPLIST +/* Solaris 11 has no getgrouplist() */ +static int getgrouplist(const char *user, gid_t group, gid_t *groups, + int *ngroups) +{ + struct group *grp; + size_t usridx; + int pos = 0, ret; + + if (pos < *ngroups) + groups[pos] = group; + pos++; + + setgrent(); + while ((grp = getgrent())) { + if (grp->gr_gid == group) + continue; + for (usridx = 0; grp->gr_mem[usridx] != NULL; usridx++) + if (!strcmp(grp->gr_mem[usridx], user)) { + if (pos < *ngroups) + groups[pos] = grp->gr_gid; + pos++; + break; + } + } + endgrent(); + + ret = (pos <= *ngroups) ? pos : -1; + *ngroups = pos; + return ret; +} +#endif /* HAVE_GETGROUPLIST */ + +/* + * Helper function that locates a refcounting object to use: a process-wide + * object or a per-pthread object. + */ +static struct zebra_privs_refs_t *get_privs_refs(struct zebra_privs_t *privs) +{ + struct zebra_privs_refs_t *temp, *refs = NULL; + pthread_t tid; + + if (privs_per_process) + refs = &(privs->process_refs); + else { + /* Locate - or create - the object for the current pthread. */ + tid = pthread_self(); + + STAILQ_FOREACH(temp, &(privs->thread_refs), entry) { + if (pthread_equal(temp->tid, tid)) { + refs = temp; + break; + } + } + + /* Need to create a new refcounting object. */ + if (refs == NULL) { + refs = XCALLOC(MTYPE_PRIVS, + sizeof(struct zebra_privs_refs_t)); + refs->tid = tid; + STAILQ_INSERT_TAIL(&(privs->thread_refs), refs, entry); + } + } + + return refs; +} + +struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, + const char *funcname) +{ + int save_errno = errno; + struct zebra_privs_refs_t *refs; + + if (!privs) + return NULL; + + /* + * Serialize 'raise' operations; particularly important for + * OSes where privs are process-wide. + */ + frr_with_mutex (&(privs->mutex)) { + /* Locate ref-counting object to use */ + refs = get_privs_refs(privs); + + if (++(refs->refcount) == 1) { + errno = 0; + if (privs->change(ZPRIVS_RAISE)) { + zlog_err("%s: Failed to raise privileges (%s)", + funcname, safe_strerror(errno)); + } + errno = save_errno; + refs->raised_in_funcname = funcname; + } + } + + return privs; +} + +void _zprivs_lower(struct zebra_privs_t **privs) +{ + int save_errno = errno; + struct zebra_privs_refs_t *refs; + + if (!*privs) + return; + + /* Serialize 'lower privs' operation - particularly important + * when OS privs are process-wide. + */ + frr_with_mutex (&(*privs)->mutex) { + refs = get_privs_refs(*privs); + + if (--(refs->refcount) == 0) { + errno = 0; + if ((*privs)->change(ZPRIVS_LOWER)) { + zlog_err("%s: Failed to lower privileges (%s)", + refs->raised_in_funcname, + safe_strerror(errno)); + } + errno = save_errno; + refs->raised_in_funcname = NULL; + } + } + + *privs = NULL; +} + +void zprivs_preinit(struct zebra_privs_t *zprivs) +{ + struct passwd *pwentry = NULL; + struct group *grentry = NULL; + + if (!zprivs) { + fprintf(stderr, "zprivs_init: called with NULL arg!\n"); + exit(1); + } + + pthread_mutex_init(&(zprivs->mutex), NULL); + zprivs->process_refs.refcount = 0; + zprivs->process_refs.raised_in_funcname = NULL; + STAILQ_INIT(&zprivs->thread_refs); + + if (zprivs->vty_group) { + /* in a "NULL" setup, this is allowed to fail too, but still + * try. */ + if ((grentry = getgrnam(zprivs->vty_group))) + zprivs_state.vtygrp = grentry->gr_gid; + else + zprivs_state.vtygrp = (gid_t)-1; + } + + /* NULL privs */ + if (!(zprivs->user || zprivs->group || zprivs->cap_num_p + || zprivs->cap_num_i)) { + zprivs->change = zprivs_change_null; + zprivs->current_state = zprivs_state_null; + return; + } + + if (zprivs->user) { + if ((pwentry = getpwnam(zprivs->user)) == NULL) { + /* cant use log.h here as it depends on vty */ + fprintf(stderr, + "privs_init: could not lookup user %s\n", + zprivs->user); + exit(1); + } + + zprivs_state.zuid = pwentry->pw_uid; + zprivs_state.zgid = pwentry->pw_gid; + } + + grentry = NULL; + + if (zprivs->group) { + if ((grentry = getgrnam(zprivs->group)) == NULL) { + fprintf(stderr, + "privs_init: could not lookup group %s\n", + zprivs->group); + exit(1); + } + + zprivs_state.zgid = grentry->gr_gid; + } +} + +struct zebra_privs_t *lib_privs; + +void zprivs_init(struct zebra_privs_t *zprivs) +{ + gid_t groups[NGROUPS_MAX] = {}; + int i, ngroups = 0; + int found = 0; + + /* NULL privs */ + if (!(zprivs->user || zprivs->group || zprivs->cap_num_p + || zprivs->cap_num_i)) + return; + + lib_privs = zprivs; + + if (zprivs->user) { + ngroups = array_size(groups); + if (getgrouplist(zprivs->user, zprivs_state.zgid, groups, + &ngroups) + < 0) { + /* cant use log.h here as it depends on vty */ + fprintf(stderr, + "privs_init: could not getgrouplist for user %s\n", + zprivs->user); + exit(1); + } + } + + if (zprivs->vty_group) + /* Add the vty_group to the supplementary groups so it can be chowned to + */ + { + if (zprivs_state.vtygrp == (gid_t)-1) { + fprintf(stderr, + "privs_init: could not lookup vty group %s\n", + zprivs->vty_group); + exit(1); + } + + for (i = 0; i < ngroups; i++) + if (groups[i] == zprivs_state.vtygrp) { + found++; + break; + } + + if (!found) { + fprintf(stderr, + "privs_init: user(%s) is not part of vty group specified(%s)\n", + zprivs->user, zprivs->vty_group); + exit(1); + } + if (i >= ngroups && ngroups < (int)array_size(groups)) { + groups[i] = zprivs_state.vtygrp; + } + } + + zprivs_state.zsuid = geteuid(); /* initial uid */ + /* add groups only if we changed uid - otherwise skip */ + if ((ngroups) && (zprivs_state.zsuid != zprivs_state.zuid)) { + if (setgroups(ngroups, groups)) { + fprintf(stderr, "privs_init: could not setgroups, %s\n", + safe_strerror(errno)); + exit(1); + } + } + + /* change gid only if we changed uid - otherwise skip */ + if ((zprivs_state.zgid) && (zprivs_state.zsuid != zprivs_state.zuid)) { + /* change group now, forever. uid we do later */ + if (setregid(zprivs_state.zgid, zprivs_state.zgid)) { + fprintf(stderr, "zprivs_init: could not setregid, %s\n", + safe_strerror(errno)); + exit(1); + } + } + +#ifdef HAVE_CAPABILITIES + zprivs_caps_init(zprivs); + + /* + * If we have initialized the system with no requested + * capabilities, change will not have been set + * to anything by zprivs_caps_init, As such + * we should make sure that when we attempt + * to raize privileges that we actually have + * a do nothing function to call instead of a + * crash :). + */ + if (!zprivs->change) + zprivs->change = zprivs_change_null; + +#else /* !HAVE_CAPABILITIES */ + /* we dont have caps. we'll need to maintain rid and saved uid + * and change euid back to saved uid (who we presume has all necessary + * privileges) whenever we are asked to raise our privileges. + * + * This is not worth that much security wise, but all we can do. + */ + zprivs_state.zsuid = geteuid(); + /* only change uid if we don't have the correct one */ + if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { + if (setreuid(-1, zprivs_state.zuid)) { + fprintf(stderr, + "privs_init (uid): could not setreuid, %s\n", + safe_strerror(errno)); + exit(1); + } + } + + zprivs->change = zprivs_change_uid; + zprivs->current_state = zprivs_state_uid; +#endif /* HAVE_CAPABILITIES */ +} + +void zprivs_terminate(struct zebra_privs_t *zprivs) +{ + struct zebra_privs_refs_t *refs; + + lib_privs = NULL; + + if (!zprivs) { + fprintf(stderr, "%s: no privs struct given, terminating", + __func__); + exit(0); + } + +#ifdef HAVE_CAPABILITIES + if (zprivs->user || zprivs->group || zprivs->cap_num_p + || zprivs->cap_num_i) + zprivs_caps_terminate(); +#else /* !HAVE_CAPABILITIES */ + /* only change uid if we don't have the correct one */ + if ((zprivs_state.zuid) && (zprivs_state.zsuid != zprivs_state.zuid)) { + if (setreuid(zprivs_state.zuid, zprivs_state.zuid)) { + fprintf(stderr, + "privs_terminate: could not setreuid, %s", + safe_strerror(errno)); + exit(1); + } + } +#endif /* HAVE_LCAPS */ + + while ((refs = STAILQ_FIRST(&(zprivs->thread_refs))) != NULL) { + STAILQ_REMOVE_HEAD(&(zprivs->thread_refs), entry); + XFREE(MTYPE_PRIVS, refs); + } + + zprivs->change = zprivs_change_null; + zprivs->current_state = zprivs_state_null; + zprivs_null_state = ZPRIVS_LOWERED; + return; +} + +void zprivs_get_ids(struct zprivs_ids_t *ids) +{ + + ids->uid_priv = getuid(); + (zprivs_state.zuid) ? (ids->uid_normal = zprivs_state.zuid) + : (ids->uid_normal = (uid_t)-1); + (zprivs_state.zgid) ? (ids->gid_normal = zprivs_state.zgid) + : (ids->gid_normal = (uid_t)-1); + (zprivs_state.vtygrp) ? (ids->gid_vty = zprivs_state.vtygrp) + : (ids->gid_vty = (uid_t)-1); + + return; +} diff --git a/lib/privs.h b/lib/privs.h new file mode 100644 index 0000000..d4a9609 --- /dev/null +++ b/lib/privs.h @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra privileges header. + * + * Copyright (C) 2003 Paul Jakma. + */ + +#ifndef _ZEBRA_PRIVS_H +#define _ZEBRA_PRIVS_H + +#include +#include +#include "lib/queue.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* list of zebra capabilities */ +typedef enum { + ZCAP_SETID, + ZCAP_BIND, + ZCAP_NET_ADMIN, + ZCAP_SYS_ADMIN, + ZCAP_NET_RAW, + ZCAP_CHROOT, + ZCAP_NICE, + ZCAP_PTRACE, + ZCAP_DAC_OVERRIDE, + ZCAP_READ_SEARCH, + ZCAP_FOWNER, + ZCAP_IPC_LOCK, + ZCAP_SYS_RAWIO, + ZCAP_MAX +} zebra_capabilities_t; + +typedef enum { + ZPRIVS_LOWERED, + ZPRIVS_RAISED, + ZPRIVS_UNKNOWN, +} zebra_privs_current_t; + +typedef enum { + ZPRIVS_RAISE, + ZPRIVS_LOWER, +} zebra_privs_ops_t; + +struct zebra_privs_refs_t { + STAILQ_ENTRY(zebra_privs_refs_t) entry; + pthread_t tid; + uint32_t refcount; + const char *raised_in_funcname; +}; + +struct zebra_privs_t { + zebra_capabilities_t *caps_p; /* caps required for operation */ + zebra_capabilities_t *caps_i; /* caps to allow inheritance of */ + int cap_num_p; /* number of caps in arrays */ + int cap_num_i; + + /* Mutex and counter used to avoid race conditions in multi-threaded + * processes. If privs status is process-wide, we need to + * control changes to the privilege status among threads. + * If privs changes are per-thread, we need to be able to + * manage that too. + */ + pthread_mutex_t mutex; + struct zebra_privs_refs_t process_refs; + + STAILQ_HEAD(thread_refs_q, zebra_privs_refs_t) thread_refs; + + const char *user; /* user and group to run as */ + const char *group; + const char *vty_group; /* group to chown vty socket to */ + /* methods */ + int (*change)(zebra_privs_ops_t); /* change privileges, 0 on success */ + zebra_privs_current_t (*current_state)( + void); /* current privilege state */ +}; + +struct zprivs_ids_t { + /* -1 is undefined */ + uid_t uid_priv; /* privileged uid */ + uid_t uid_normal; /* normal uid */ + gid_t gid_priv; /* privileged uid */ + gid_t gid_normal; /* normal uid */ + gid_t gid_vty; /* vty gid */ +}; + +extern struct zebra_privs_t *lib_privs; + +/* initialise zebra privileges */ +extern void zprivs_preinit(struct zebra_privs_t *zprivs); +extern void zprivs_init(struct zebra_privs_t *zprivs); +/* drop all and terminate privileges */ +extern void zprivs_terminate(struct zebra_privs_t *); +/* query for runtime uid's and gid's, eg vty needs this */ +extern void zprivs_get_ids(struct zprivs_ids_t *); + +/* + * Wrapper around zprivs, to be used as: + * frr_with_privs(&privs) { + * ... code ... + * if (error) + * break; -- break can be used to get out of the block + * ... code ... + * } + * + * The argument to frr_with_privs() can be NULL to leave privileges as-is + * (mostly useful for conditional privilege-raising, i.e.:) + * frr_with_privs(cond ? &privs : NULL) {} + * + * NB: The code block is always executed, regardless of whether privileges + * could be raised or not, or whether NULL was given or not. This is fully + * intentional; the user may have configured some RBAC or similar that we + * are not aware of, but that allows our code to proceed without privileges. + * + * The point of this wrapper is to prevent accidental bugs where privileges + * are elevated but then not dropped. This can happen when, for example, a + * "return", "goto" or "break" in the middle of the elevated-privilege code + * skips past the privilege dropping call. + * + * The macro below uses variable cleanup to drop privileges as soon as the + * code block is left in any way (and thus the _privs variable goes out of + * scope.) _once is just a trick to run the loop exactly once. + */ +extern struct zebra_privs_t *_zprivs_raise(struct zebra_privs_t *privs, + const char *funcname); +extern void _zprivs_lower(struct zebra_privs_t **privs); + +#define frr_with_privs(privs) \ + for (struct zebra_privs_t *_once = NULL, \ + *_privs __attribute__( \ + (unused, cleanup(_zprivs_lower))) = \ + _zprivs_raise(privs, __func__); \ + _once == NULL; _once = (void *)1) + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_PRIVS_H */ diff --git a/lib/ptm_lib.c b/lib/ptm_lib.c new file mode 100644 index 0000000..ac800be --- /dev/null +++ b/lib/ptm_lib.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PTM Library + * Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "csv.h" +#include "ptm_lib.h" + +#define DEBUG_E 0 +#define DEBUG_V 0 + +#define ERRLOG(fmt, ...) \ + do { \ + if (DEBUG_E) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); \ + } while (0) + +#define DLOG(fmt, ...) \ + do { \ + if (DEBUG_V) \ + fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); \ + } while (0) + +typedef struct ptm_lib_msg_ctxt_s { + int cmd_id; + csv_t *csv; + ptmlib_msg_type type; +} ptm_lib_msg_ctxt_t; + +static csv_record_t *_ptm_lib_encode_header(csv_t *csv, csv_record_t *rec, + int msglen, int version, int type, + int cmd_id, char *client_name) +{ + char msglen_buf[16], vers_buf[16], type_buf[16], cmdid_buf[16]; + char client_buf[32]; + csv_record_t *rec1; + + snprintf(msglen_buf, sizeof(msglen_buf), "%4d", msglen); + snprintf(vers_buf, sizeof(vers_buf), "%4d", version); + snprintf(type_buf, sizeof(type_buf), "%4d", type); + snprintf(cmdid_buf, sizeof(cmdid_buf), "%4d", cmd_id); + snprintf(client_buf, 17, "%16.16s", client_name); + if (rec) { + rec1 = csv_encode_record(csv, rec, 5, msglen_buf, vers_buf, + type_buf, cmdid_buf, client_buf); + } else { + rec1 = csv_encode(csv, 5, msglen_buf, vers_buf, type_buf, + cmdid_buf, client_buf); + } + return (rec1); +} + +static int _ptm_lib_decode_header(csv_t *csv, int *msglen, int *version, + int *type, int *cmd_id, char *client_name) +{ + char *hdr; + csv_record_t *rec; + csv_field_t *fld; + int i, j; + + csv_decode(csv, NULL); + rec = csv_record_iter(csv); + if (rec == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + hdr = csv_field_iter(rec, &fld); + if (hdr == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + *msglen = atoi(hdr); + hdr = csv_field_iter_next(&fld); + if (hdr == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + *version = atoi(hdr); + hdr = csv_field_iter_next(&fld); + if (hdr == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + *type = atoi(hdr); + hdr = csv_field_iter_next(&fld); + if (hdr == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + *cmd_id = atoi(hdr); + hdr = csv_field_iter_next(&fld); + if (hdr == NULL) { + DLOG("malformed CSV\n"); + return -1; + } + /* remove leading spaces */ + for (i = j = 0; i < csv_field_len(fld); i++) { + if (!isspace((unsigned char)hdr[i])) { + client_name[j] = hdr[i]; + j++; + } + } + client_name[j] = '\0'; + + return 0; +} + +int ptm_lib_append_msg(ptm_lib_handle_t *hdl, void *ctxt, const char *key, + const char *val) +{ + ptm_lib_msg_ctxt_t *p_ctxt = ctxt; + csv_t *csv; + csv_record_t *mh_rec, *rec; + + if (!p_ctxt) { + ERRLOG("%s: no context \n", __func__); + return -1; + } + + csv = p_ctxt->csv; + mh_rec = csv_record_iter(csv); + rec = csv_record_iter_next(mh_rec); + + /* append to the hdr record */ + rec = csv_append_record(csv, rec, 1, key); + if (!rec) { + ERRLOG("%s: Could not append key \n", __func__); + return -1; + } + + rec = csv_record_iter_next(rec); + /* append to the data record */ + rec = csv_append_record(csv, rec, 1, val); + if (!rec) { + ERRLOG("%s: Could not append val \n", __func__); + return -1; + } + + /* update the msg hdr */ + _ptm_lib_encode_header(csv, mh_rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN), + PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id, + hdl->client_name); + + return 0; +} + +int ptm_lib_init_msg(ptm_lib_handle_t *hdl, int cmd_id, int type, void *in_ctxt, + void **out_ctxt) +{ + ptm_lib_msg_ctxt_t *p_ctxt; + ptm_lib_msg_ctxt_t *p_in_ctxt = in_ctxt; + csv_t *csv; + csv_record_t *rec, *d_rec; + + /* Initialize csv for using discrete record buffers */ + csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); + + if (!csv) { + ERRLOG("%s: Could not allocate csv \n", __func__); + return -1; + } + + rec = _ptm_lib_encode_header(csv, NULL, 0, PTMLIB_MSG_VERSION, type, + cmd_id, hdl->client_name); + + if (!rec) { + ERRLOG("%s: Could not allocate record \n", __func__); + csv_clean(csv); + csv_free(csv); + return -1; + } + + p_ctxt = calloc(1, sizeof(*p_ctxt)); + if (!p_ctxt) { + ERRLOG("%s: Could not allocate context \n", __func__); + csv_clean(csv); + csv_free(csv); + return -1; + } + + p_ctxt->csv = csv; + p_ctxt->cmd_id = cmd_id; + p_ctxt->type = type; + + *(ptm_lib_msg_ctxt_t **)out_ctxt = p_ctxt; + + /* caller supplied a context to initialize with? */ + if (p_in_ctxt) { + /* insert the hdr rec */ + rec = csv_record_iter(p_in_ctxt->csv); + csv_clone_record(p_in_ctxt->csv, rec, &d_rec); + csv_insert_record(csv, d_rec); + /* insert the data rec */ + rec = csv_record_iter_next(rec); + csv_clone_record(p_in_ctxt->csv, rec, &d_rec); + csv_insert_record(csv, d_rec); + } + return 0; +} + +int ptm_lib_cleanup_msg(ptm_lib_handle_t *hdl, void *ctxt) +{ + ptm_lib_msg_ctxt_t *p_ctxt = ctxt; + csv_t *csv; + + if (!p_ctxt) { + ERRLOG("%s: no context \n", __func__); + return -1; + } + + csv = p_ctxt->csv; + + csv_clean(csv); + csv_free(csv); + free(p_ctxt); + + return 0; +} + +int ptm_lib_complete_msg(ptm_lib_handle_t *hdl, void *ctxt, char *buf, int *len) +{ + ptm_lib_msg_ctxt_t *p_ctxt = ctxt; + csv_t *csv; + csv_record_t *rec; + + if (!p_ctxt) { + ERRLOG("%s: no context \n", __func__); + return -1; + } + + csv = p_ctxt->csv; + rec = csv_record_iter(csv); + + _ptm_lib_encode_header(csv, rec, (csvlen(csv) - PTMLIB_MSG_HDR_LEN), + PTMLIB_MSG_VERSION, p_ctxt->type, p_ctxt->cmd_id, + hdl->client_name); + + /* parse csv contents into string */ + if (buf && len) { + if (csv_serialize(csv, buf, *len)) { + ERRLOG("%s: cannot serialize\n", __func__); + return -1; + } + *len = csvlen(csv); + } + + csv_clean(csv); + csv_free(csv); + free(p_ctxt); + + return 0; +} + +int ptm_lib_find_key_in_msg(void *ctxt, const char *key, char *val) +{ + ptm_lib_msg_ctxt_t *p_ctxt = ctxt; + csv_t *csv = p_ctxt->csv; + csv_record_t *hrec, *drec; + csv_field_t *hfld, *dfld; + char *hstr, *dstr; + + /** + * skip over ptm hdr if present + * The next hdr is the keys (column name) + * The next hdr is the data + */ + if (csv_num_records(csv) > 2) { + hrec = csv_record_iter(csv); + hrec = csv_record_iter_next(hrec); + } else { + hrec = csv_record_iter(csv); + } + drec = csv_record_iter_next(hrec); + val[0] = '\0'; + for (hstr = csv_field_iter(hrec, &hfld), + dstr = csv_field_iter(drec, &dfld); + (hstr && dstr); hstr = csv_field_iter_next(&hfld), + dstr = csv_field_iter_next(&dfld)) { + if (!strncmp(hstr, key, csv_field_len(hfld))) { + snprintf(val, csv_field_len(dfld) + 1, "%s", dstr); + return 0; + } + } + + return -1; +} + +static int _ptm_lib_read_ptm_socket(int fd, char *buf, int len) +{ + int retries = 0, rc; + int bytes_read = 0; + + while (bytes_read != len) { + rc = recv(fd, (void *)(buf + bytes_read), (len - bytes_read), + MSG_DONTWAIT); + if (rc <= 0) { + if (errno && (errno != EAGAIN) + && (errno != EWOULDBLOCK)) { + ERRLOG("fatal recv error(%s), closing connection, rc %d\n", + strerror(errno), rc); + return (rc); + } else { + if (retries++ < 2) { + usleep(10000); + continue; + } + DLOG("max retries - recv error(%d - %s) bytes read %d (%d)\n", + errno, strerror(errno), bytes_read, len); + return (bytes_read); + } + break; + } else { + bytes_read += rc; + } + } + + return bytes_read; +} + +int ptm_lib_process_msg(ptm_lib_handle_t *hdl, int fd, char *inbuf, int inlen, + void *arg) +{ + int rc, len; + char client_name[32]; + int cmd_id = 0, type = 0, ver = 0, msglen = 0; + csv_t *csv; + ptm_lib_msg_ctxt_t *p_ctxt = NULL; + + len = _ptm_lib_read_ptm_socket(fd, inbuf, PTMLIB_MSG_HDR_LEN); + if (len <= 0) + return (len); + + csv = csv_init(NULL, inbuf, PTMLIB_MSG_HDR_LEN); + + if (!csv) { + DLOG("Cannot allocate csv for hdr\n"); + return -1; + } + + rc = _ptm_lib_decode_header(csv, &msglen, &ver, &type, &cmd_id, + client_name); + + csv_clean(csv); + csv_free(csv); + + if (rc < 0) { + /* could not decode the CSV - maybe its legacy cmd? + * get the entire cmd from the socket and see if we can process + * it + */ + if (len == PTMLIB_MSG_HDR_LEN) { + len += _ptm_lib_read_ptm_socket( + fd, (inbuf + PTMLIB_MSG_HDR_LEN), + inlen - PTMLIB_MSG_HDR_LEN); + if (len <= 0) + return (len); + } + + inbuf[len] = '\0'; + /* we only support the get-status cmd */ + if (strcmp(inbuf, PTMLIB_CMD_GET_STATUS)) { + DLOG("unsupported legacy cmd %s\n", inbuf); + return -1; + } + /* internally create a csv-style cmd */ + ptm_lib_init_msg(hdl, 0, PTMLIB_MSG_TYPE_CMD, NULL, + (void *)&p_ctxt); + if (!p_ctxt) { + DLOG("couldnt allocate context\n"); + return -1; + } + ptm_lib_append_msg(hdl, p_ctxt, "cmd", PTMLIB_CMD_GET_STATUS); + + } else { + + if (msglen > inlen) { + DLOG("msglen [%d] > inlen [%d]\n", msglen, inlen); + return -1; + } + + /* read the rest of the msg */ + len = _ptm_lib_read_ptm_socket(fd, inbuf, msglen); + if (len <= 0) { + return (len); + } + + inbuf[len] = '\0'; + + csv = csv_init(NULL, NULL, PTMLIB_MSG_SZ); + if (!csv) { + ERRLOG("Cannot allocate csv for msg\n"); + return -1; + } + + csv_decode(csv, inbuf); + p_ctxt = calloc(1, sizeof(*p_ctxt)); + if (!p_ctxt) { + ERRLOG("%s: Could not allocate context \n", __func__); + csv_clean(csv); + csv_free(csv); + return -1; + } + + p_ctxt->csv = csv; + p_ctxt->cmd_id = cmd_id; + p_ctxt->type = type; + } + + switch (p_ctxt->type) { + case PTMLIB_MSG_TYPE_NOTIFICATION: + if (hdl->notify_cb) + hdl->notify_cb(arg, p_ctxt); + break; + case PTMLIB_MSG_TYPE_CMD: + if (hdl->cmd_cb) + hdl->cmd_cb(arg, p_ctxt); + break; + case PTMLIB_MSG_TYPE_RESPONSE: + if (hdl->response_cb) + hdl->response_cb(arg, p_ctxt); + break; + default: + return -1; + } + + csv_clean(p_ctxt->csv); + csv_free(p_ctxt->csv); + free(p_ctxt); + + return len; +} + +ptm_lib_handle_t *ptm_lib_register(char *client_name, ptm_cmd_cb cmd_cb, + ptm_notify_cb notify_cb, + ptm_response_cb response_cb) +{ + ptm_lib_handle_t *hdl; + + hdl = calloc(1, sizeof(*hdl)); + + if (hdl) { + strncpy(hdl->client_name, client_name, PTMLIB_MAXNAMELEN - 1); + hdl->cmd_cb = cmd_cb; + hdl->notify_cb = notify_cb; + hdl->response_cb = response_cb; + } + + return hdl; +} + +void ptm_lib_deregister(ptm_lib_handle_t *hdl) +{ + if (hdl) { + memset(hdl, 0x00, sizeof(*hdl)); + free(hdl); + } +} diff --git a/lib/ptm_lib.h b/lib/ptm_lib.h new file mode 100644 index 0000000..1f17f76 --- /dev/null +++ b/lib/ptm_lib.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PTM Library + * Copyright (C) 2015 Cumulus Networks, Inc. + */ +#ifndef __PTM_LIB_H__ +#define __PTM_LIB_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PTMLIB_MSG_SZ 1024 +#define PTMLIB_MSG_HDR_LEN 37 +#define PTMLIB_MSG_VERSION 2 +#define PTMLIB_MAXNAMELEN 32 + +#define PTMLIB_CMD_GET_STATUS "get-status" +#define PTMLIB_CMD_GET_BFD_CLIENT "get-bfd-client" +#define PTMLIB_CMD_START_BFD_SESS "start-bfd-sess" +#define PTMLIB_CMD_STOP_BFD_SESS "stop-bfd-sess" + +typedef enum { + PTMLIB_MSG_TYPE_NOTIFICATION = 1, + PTMLIB_MSG_TYPE_CMD, + PTMLIB_MSG_TYPE_RESPONSE, +} ptmlib_msg_type; + +typedef enum { + MODULE_BFD = 0, + MODULE_LLDP, + MODULE_MAX, +} ptmlib_mod_type; + +typedef int (*ptm_cmd_cb)(void *data, void *arg); +typedef int (*ptm_notify_cb)(void *data, void *arg); +typedef int (*ptm_response_cb)(void *data, void *arg); +typedef int (*ptm_log_cb)(void *data, void *arg, ...); + +typedef struct ptm_lib_handle_s { + char client_name[PTMLIB_MAXNAMELEN]; + ptm_cmd_cb cmd_cb; + ptm_notify_cb notify_cb; + ptm_response_cb response_cb; +} ptm_lib_handle_t; + +/* Prototypes */ +int ptm_lib_process_msg(ptm_lib_handle_t *, int, char *, int, void *); +ptm_lib_handle_t *ptm_lib_register(char *, ptm_cmd_cb, ptm_notify_cb, + ptm_response_cb); +void ptm_lib_deregister(ptm_lib_handle_t *); +int ptm_lib_find_key_in_msg(void *, const char *, char *); +int ptm_lib_init_msg(ptm_lib_handle_t *, int, int, void *, void **); +int ptm_lib_append_msg(ptm_lib_handle_t *, void *, const char *, const char *); +int ptm_lib_complete_msg(ptm_lib_handle_t *, void *, char *, int *); +int ptm_lib_cleanup_msg(ptm_lib_handle_t *, void *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/pullwr.c b/lib/pullwr.c new file mode 100644 index 0000000..919a663 --- /dev/null +++ b/lib/pullwr.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pull-driven write event handler + * Copyright (C) 2019 David Lamparter + */ + +#include "zebra.h" + +#include + +#include "pullwr.h" +#include "memory.h" +#include "monotime.h" + +/* defaults */ +#define PULLWR_THRESH 16384 /* size at which we start to call write() */ +#define PULLWR_MAXSPIN 2500 /* max µs to spend grabbing more data */ + +struct pullwr { + int fd; + struct event_loop *tm; + /* writer == NULL <=> we're idle */ + struct event *writer; + + void *arg; + void (*fill)(void *, struct pullwr *); + void (*err)(void *, struct pullwr *, bool); + + /* ring buffer (although it's "un-ringed" on resizing, it WILL wrap + * around if data is trickling in while keeping it at a constant size) + */ + size_t bufsz, valid, pos; + uint64_t total_written; + char *buffer; + + size_t thresh; /* PULLWR_THRESH */ + int64_t maxspin; /* PULLWR_MAXSPIN */ +}; + +DEFINE_MTYPE_STATIC(LIB, PULLWR_HEAD, "pull-driven write controller"); +DEFINE_MTYPE_STATIC(LIB, PULLWR_BUF, "pull-driven write buffer"); + +static void pullwr_run(struct event *t); + +struct pullwr *_pullwr_new(struct event_loop *tm, int fd, void *arg, + void (*fill)(void *, struct pullwr *), + void (*err)(void *, struct pullwr *, bool)) +{ + struct pullwr *pullwr; + + pullwr = XCALLOC(MTYPE_PULLWR_HEAD, sizeof(*pullwr)); + pullwr->fd = fd; + pullwr->tm = tm; + pullwr->arg = arg; + pullwr->fill = fill; + pullwr->err = err; + + pullwr->thresh = PULLWR_THRESH; + pullwr->maxspin = PULLWR_MAXSPIN; + + return pullwr; +} + +void pullwr_del(struct pullwr *pullwr) +{ + EVENT_OFF(pullwr->writer); + + XFREE(MTYPE_PULLWR_BUF, pullwr->buffer); + XFREE(MTYPE_PULLWR_HEAD, pullwr); +} + +void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec, + size_t write_threshold) +{ + pullwr->maxspin = max_spin_usec ?: PULLWR_MAXSPIN; + pullwr->thresh = write_threshold ?: PULLWR_THRESH; +} + +void pullwr_bump(struct pullwr *pullwr) +{ + if (pullwr->writer) + return; + + event_add_timer(pullwr->tm, pullwr_run, pullwr, 0, &pullwr->writer); +} + +static size_t pullwr_iov(struct pullwr *pullwr, struct iovec *iov) +{ + size_t len1; + + if (pullwr->valid == 0) + return 0; + + if (pullwr->pos + pullwr->valid <= pullwr->bufsz) { + iov[0].iov_base = pullwr->buffer + pullwr->pos; + iov[0].iov_len = pullwr->valid; + return 1; + } + + len1 = pullwr->bufsz - pullwr->pos; + + iov[0].iov_base = pullwr->buffer + pullwr->pos; + iov[0].iov_len = len1; + iov[1].iov_base = pullwr->buffer; + iov[1].iov_len = pullwr->valid - len1; + return 2; +} + +static void pullwr_resize(struct pullwr *pullwr, size_t need) +{ + struct iovec iov[2]; + size_t niov, newsize; + char *newbuf; + + /* the buffer is maintained at pullwr->thresh * 2 since we'll be + * trying to fill it as long as it's anywhere below pullwr->thresh. + * That means we frequently end up a little short of it and then write + * something that goes over the threshold. So, just use double. + */ + if (need) { + /* resize up */ + if (pullwr->bufsz - pullwr->valid >= need) + return; + + newsize = MAX((pullwr->valid + need) * 2, pullwr->thresh * 2); + newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize); + } else if (!pullwr->valid) { + /* resize down, buffer empty */ + newsize = 0; + newbuf = NULL; + } else { + /* resize down */ + if (pullwr->bufsz - pullwr->valid < pullwr->thresh) + return; + newsize = MAX(pullwr->valid, pullwr->thresh * 2); + newbuf = XMALLOC(MTYPE_PULLWR_BUF, newsize); + } + + niov = pullwr_iov(pullwr, iov); + if (niov >= 1) { + memcpy(newbuf, iov[0].iov_base, iov[0].iov_len); + if (niov >= 2) + memcpy(newbuf + iov[0].iov_len, + iov[1].iov_base, iov[1].iov_len); + } + + XFREE(MTYPE_PULLWR_BUF, pullwr->buffer); + pullwr->buffer = newbuf; + pullwr->bufsz = newsize; + pullwr->pos = 0; +} + +void pullwr_write(struct pullwr *pullwr, const void *data, size_t len) +{ + pullwr_resize(pullwr, len); + + if (pullwr->pos + pullwr->valid > pullwr->bufsz) { + size_t pos; + + pos = (pullwr->pos + pullwr->valid) % pullwr->bufsz; + memcpy(pullwr->buffer + pos, data, len); + } else { + size_t max1, len1; + max1 = pullwr->bufsz - (pullwr->pos + pullwr->valid); + max1 = MIN(max1, len); + + memcpy(pullwr->buffer + pullwr->pos + pullwr->valid, + data, max1); + len1 = len - max1; + + if (len1) + memcpy(pullwr->buffer, (char *)data + max1, len1); + + } + pullwr->valid += len; + + pullwr_bump(pullwr); +} + +static void pullwr_run(struct event *t) +{ + struct pullwr *pullwr = EVENT_ARG(t); + struct iovec iov[2]; + size_t niov, lastvalid; + ssize_t nwr; + struct timeval t0; + bool maxspun = false; + + monotime(&t0); + + do { + lastvalid = pullwr->valid - 1; + while (pullwr->valid < pullwr->thresh + && pullwr->valid != lastvalid + && !maxspun) { + lastvalid = pullwr->valid; + pullwr->fill(pullwr->arg, pullwr); + + /* check after doing at least one fill() call so we + * don't spin without making progress on slow boxes + */ + if (!maxspun && monotime_since(&t0, NULL) + >= pullwr->maxspin) + maxspun = true; + } + + if (pullwr->valid == 0) { + /* we made a fill() call above that didn't feed any + * data in, and we have nothing more queued, so we go + * into idle, i.e. no calling event_add_write() + */ + pullwr_resize(pullwr, 0); + return; + } + + niov = pullwr_iov(pullwr, iov); + assert(niov); + + nwr = writev(pullwr->fd, iov, niov); + if (nwr < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + pullwr->err(pullwr->arg, pullwr, false); + return; + } + + if (nwr == 0) { + pullwr->err(pullwr->arg, pullwr, true); + return; + } + + pullwr->total_written += nwr; + pullwr->valid -= nwr; + pullwr->pos += nwr; + pullwr->pos %= pullwr->bufsz; + } while (pullwr->valid == 0 && !maxspun); + /* pullwr->valid != 0 implies we did an incomplete write, i.e. socket + * is full and we go wait until it's available for writing again. + */ + + event_add_write(pullwr->tm, pullwr_run, pullwr, pullwr->fd, + &pullwr->writer); + + /* if we hit the time limit, just keep the buffer, we'll probably need + * it anyway & another run is already coming up. + */ + if (!maxspun) + pullwr_resize(pullwr, 0); +} + +void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written, + size_t *pending, size_t *kernel_pending) +{ + int tmp; + + *total_written = pullwr->total_written; + *pending = pullwr->valid; + + if (ioctl(pullwr->fd, TIOCOUTQ, &tmp) != 0) + tmp = 0; + *kernel_pending = tmp; +} diff --git a/lib/pullwr.h b/lib/pullwr.h new file mode 100644 index 0000000..ef2e01c --- /dev/null +++ b/lib/pullwr.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pull-driven write event handler + * Copyright (C) 2019 David Lamparter + */ + +#ifndef _WRITEPOLL_H +#define _WRITEPOLL_H + +#include +#include + +#include "frrevent.h" +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct pullwr; + +/* This is a "pull-driven" write event handler. Instead of having some buffer + * or being driven by the availability of data, it triggers on the space being + * available on the socket for data to be written on and then calls fill() to + * get data to be sent. + * + * pullwr_* maintains an "idle" vs. "active" state, going into idle when a + * fill() call completes without feeing more data into it. The overall + * semantics are: + * - to put data out, call pullwr_write(). This is possible from both inside + * fill() callbacks or anywhere else. Doing so puts the pullwr into + * active state. + * - in active state, the fill() callback will be called and should feed more + * data in. It should NOT loop to push out more than one "unit" of data; + * the pullwr code handles this by calling fill() until it has enough data. + * - if there's nothing more to be sent, fill() returns without doing anything + * and pullwr goes into idle state after flushing all buffered data out. + * - when new data becomes available, pullwr_bump() should be called to put + * the pullwr back into active mode so it will collect data from fill(), + * or you can directly call pullwr_write(). + * - only calling pullwr_write() from within fill() is the cleanest way of + * doing things. + * + * When the err() callback is called, the pullwr should be considered unusable + * and released with pullwr_del(). This can be done from inside the callback, + * the pullwr code holds no more references on it when calling err(). + */ +extern struct pullwr *_pullwr_new(struct event_loop *tm, int fd, void *arg, + void (*fill)(void *, struct pullwr *), + void (*err)(void *, struct pullwr *, + bool eof)); +extern void pullwr_del(struct pullwr *pullwr); + +/* type-checking wrapper. makes sure fill() and err() take a first argument + * whose type is identical to the type of arg. + * => use "void fill(struct mystruct *arg, ...)" - no "void *arg" + */ +#define pullwr_new(tm, fd, arg, fill, err) ({ \ + void (*fill_typechk)(typeof(arg), struct pullwr *) = fill; \ + void (*err_typechk)(typeof(arg), struct pullwr *, bool) = err; \ + _pullwr_new(tm, fd, arg, (void *)fill_typechk, (void *)err_typechk); \ +}) + +/* max_spin_usec is the time after which the pullwr event handler will stop + * trying to get more data from fill() and yield control back to the + * thread_master. It does reschedule itself to continue later; this is + * only to make sure we don't freeze the entire process if we're piping a + * lot of data to a local endpoint that reads quickly (i.e. no backpressure) + * + * default: 2500 (2.5 ms) + * + * write_threshold is the amount of data buffered from fill() calls at which + * the pullwr code starts calling write(). But this is not a "limit". + * pullwr will keep poking fill() for more data until + * (a) max_spin_usec is reached; fill() will be called again later after + * returning to the thread_master to give other events a chance to run + * (b) fill() returns without pushing any data onto the pullwr with + * pullwr_write(), so fill() will NOT be called again until a call to + * pullwr_bump() or pullwr_write() comes in. + * + * default: 16384 (16 kB) + * + * passing 0 for either value (or not calling it at all) uses the default. + */ +extern void pullwr_cfg(struct pullwr *pullwr, int64_t max_spin_usec, + size_t write_threshold); + +extern void pullwr_bump(struct pullwr *pullwr); +extern void pullwr_write(struct pullwr *pullwr, + const void *data, size_t len); + +static inline void pullwr_write_stream(struct pullwr *pullwr, + struct stream *s) +{ + pullwr_write(pullwr, s->data, stream_get_endp(s)); +} + +extern void pullwr_stats(struct pullwr *pullwr, uint64_t *total_written, + size_t *pending, size_t *kernel_pending); + +#ifdef __cplusplus +} +#endif + +#endif /* _WRITEPOLL_H */ diff --git a/lib/pw.h b/lib/pw.h new file mode 100644 index 0000000..17f7bda --- /dev/null +++ b/lib/pw.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Pseudowire definitions + * Copyright (C) 2016 Volta Networks, Inc. + */ + +#ifndef _FRR_PW_H +#define _FRR_PW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* L2VPN name length. */ +#define L2VPN_NAME_LEN 32 + +/* Pseudowire type - LDP and BGP use the same values. */ +#define PW_TYPE_ETHERNET_TAGGED 0x0004 /* RFC 4446 */ +#define PW_TYPE_ETHERNET 0x0005 /* RFC 4446 */ +#define PW_TYPE_WILDCARD 0x7FFF /* RFC 4863, RFC 6668 */ + +/* Pseudowire flags. */ +#define F_PSEUDOWIRE_CWORD 0x01 + +/* Pseudowire status TLV */ +#define PW_FORWARDING 0 +#define PW_NOT_FORWARDING (1 << 0) +#define PW_LOCAL_RX_FAULT (1 << 1) +#define PW_LOCAL_TX_FAULT (1 << 2) +#define PW_PSN_RX_FAULT (1 << 3) +#define PW_PSN_TX_FAULT (1 << 4) + +/* + * Protocol-specific information about the pseudowire. + */ +union pw_protocol_fields { + struct { + struct in_addr lsr_id; + uint32_t pwid; + char vpn_name[L2VPN_NAME_LEN]; + } ldp; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_PW_H */ diff --git a/lib/qobj.c b/lib/qobj.c new file mode 100644 index 0000000..b9630e7 --- /dev/null +++ b/lib/qobj.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + * + * This file is part of Quagga + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "hash.h" +#include "log.h" +#include "qobj.h" +#include "jhash.h" +#include "network.h" + +static uint32_t qobj_hash(const struct qobj_node *node) +{ + return (uint32_t)node->nid; +} + +static int qobj_cmp(const struct qobj_node *na, const struct qobj_node *nb) +{ + if (na->nid < nb->nid) + return -1; + if (na->nid > nb->nid) + return 1; + return 0; +} + +DECLARE_HASH(qobj_nodes, struct qobj_node, nodehash, + qobj_cmp, qobj_hash); + +static pthread_rwlock_t nodes_lock; +static struct qobj_nodes_head nodes = { }; + + +void qobj_reg(struct qobj_node *node, const struct qobj_nodetype *type) +{ + node->type = type; + pthread_rwlock_wrlock(&nodes_lock); + do { + node->nid = (uint64_t)frr_weak_random(); + node->nid ^= (uint64_t)frr_weak_random() << 32; + } while (!node->nid || qobj_nodes_find(&nodes, node)); + qobj_nodes_add(&nodes, node); + pthread_rwlock_unlock(&nodes_lock); +} + +void qobj_unreg(struct qobj_node *node) +{ + pthread_rwlock_wrlock(&nodes_lock); + qobj_nodes_del(&nodes, node); + pthread_rwlock_unlock(&nodes_lock); +} + +struct qobj_node *qobj_get(uint64_t id) +{ + struct qobj_node dummy = {.nid = id}, *rv; + pthread_rwlock_rdlock(&nodes_lock); + rv = qobj_nodes_find(&nodes, &dummy); + pthread_rwlock_unlock(&nodes_lock); + return rv; +} + +void *qobj_get_typed(uint64_t id, const struct qobj_nodetype *type) +{ + struct qobj_node dummy = {.nid = id}; + struct qobj_node *node; + void *rv; + + pthread_rwlock_rdlock(&nodes_lock); + node = qobj_nodes_find(&nodes, &dummy); + + /* note: we explicitly hold the lock until after we have checked the + * type. + * if the caller holds a lock that for example prevents the deletion of + * route-maps, we can still race against a delete of something that + * isn't + * a route-map. */ + if (!node || node->type != type) + rv = NULL; + else + rv = (char *)node - node->type->node_member_offset; + + pthread_rwlock_unlock(&nodes_lock); + return rv; +} + +void qobj_init(void) +{ + pthread_rwlock_init(&nodes_lock, NULL); + qobj_nodes_init(&nodes); +} + +void qobj_finish(void) +{ + struct qobj_node *node; + while ((node = qobj_nodes_pop(&nodes))) + qobj_nodes_del(&nodes, node); + pthread_rwlock_destroy(&nodes_lock); +} diff --git a/lib/qobj.h b/lib/qobj.h new file mode 100644 index 0000000..a3883fd --- /dev/null +++ b/lib/qobj.h @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _QOBJ_H +#define _QOBJ_H + +#include +#include +#include + +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* reserve a specific amount of bytes for a struct, which can grow up to + * that size (or be dummy'd out if not needed) + * + * note the padding's array size will be an error if it gets negative or zero; + * this is intentional to prevent the struct from growing beyond the allocated + * space. + */ +#ifndef __cplusplus +#define RESERVED_SPACE_STRUCT(name, fieldname, size) \ + struct { \ + struct name fieldname; \ + char padding##fieldname[size - sizeof(struct name)]; \ + }; +#else +#define RESERVED_SPACE_STRUCT(name, fieldname, size) \ + struct name fieldname; \ + char padding##fieldname[size - sizeof(struct name)]; +#endif + +/* don't need struct definitions for these here. code actually using + * these needs to define the struct *before* including this header. + * HAVE_QOBJ_xxx should be defined to +1 in that case, like this: + * + * #if defined(HAVE_QOBJ_NODETYPE_CLI) && HAVE_QOBJ_NODETYPE_CLI < 0 + * #error include files are in wrong order + * #else + * #define HAVE_QOBJ_NODETYPE_CLI 1 + * struct qobj_nodetype_cli { ... } + * #endif + */ +#ifndef HAVE_QOBJ_NODETYPE_CLI +#define HAVE_QOBJ_NODETYPE_CLI -1 +struct qobj_nodetype_cli { + int dummy; +}; +#endif + +#ifndef HAVE_QOBJ_NODETYPE_CAPNP +#define HAVE_QOBJ_NODETYPE_CAPNP -1 +struct qobj_nodetype_capnp { + int dummy; +}; +#endif + +#include "typesafe.h" + +/* each different kind of object will have a global variable of this type, + * which can be used by various other pieces to store type-related bits. + * type equality can be tested as pointer equality. (cf. QOBJ_GET_TYPESAFE) + */ +struct qobj_nodetype { + ptrdiff_t node_member_offset; + RESERVED_SPACE_STRUCT(qobj_nodetype_cli, cli, 256) + RESERVED_SPACE_STRUCT(qobj_nodetype_capnp, capnp, 256) +}; + +PREDECL_HASH(qobj_nodes); + +/* anchor to be embedded somewhere in the object's struct */ +struct qobj_node { + uint64_t nid; + struct qobj_nodes_item nodehash; + const struct qobj_nodetype *type; +}; + +#define QOBJ_FIELDS struct qobj_node qobj_node + +/* call these at the end of any _create function (QOBJ_REG) + * and beginning of any _destroy function (QOBJ_UNREG) */ +#define QOBJ_REG(n, structname) qobj_reg(&n->qobj_node, &qobj_t_##structname) +#define QOBJ_UNREG(n) qobj_unreg(&n->qobj_node) + +/* internals - should not be directly used without a good reason + * + * note: qobj_get is essentially never safe to use in MT context because + * the object could be deleted by another thread -- and worse, it could be + * of the "wrong" type and deleted. + * + * with qobj_get_typed, the type check is done under lock, which means that + * it can be used as long as another lock prevents the deletion of objects + * of the expected type. + * + * in the long this may need another touch, e.g. built-in per-object locking. + */ +void qobj_reg(struct qobj_node *node, const struct qobj_nodetype *type); +void qobj_unreg(struct qobj_node *node); +struct qobj_node *qobj_get(uint64_t id); +void *qobj_get_typed(uint64_t id, const struct qobj_nodetype *type); + +/* type declarations */ +#define DECLARE_QOBJ_TYPE(structname) \ + extern const struct qobj_nodetype qobj_t_##structname \ + /* end */ +#define DEFINE_QOBJ_TYPE(structname) \ + const struct qobj_nodetype qobj_t_##structname = { \ + .node_member_offset = \ + (ptrdiff_t)offsetof(struct structname, qobj_node)} \ + /* end */ +#define DEFINE_QOBJ_TYPE_INIT(structname, ...) \ + const struct qobj_nodetype qobj_t_##structname = { \ + .node_member_offset = \ + (ptrdiff_t)offsetof(struct structname, qobj_node), \ + __VA_ARGS__} \ + /* end */ + +/* ID dereference with typecheck. + * will return NULL if id not found or wrong type. */ +#define QOBJ_GET_TYPESAFE(id, structname) \ + ((struct structname *)qobj_get_typed((id), &qobj_t_##structname)) + +#define QOBJ_ID(ptr) ((ptr)->qobj_node.nid) +#define QOBJ_ID_0SAFE(ptr) \ + ({ \ + typeof(ptr) _ptr = (ptr); \ + _ptr ? _ptr->qobj_node.nid : 0ULL; \ + }) + +void qobj_init(void); +void qobj_finish(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QOBJ_H */ diff --git a/lib/queue.h b/lib/queue.h new file mode 100644 index 0000000..26a5d4d --- /dev/null +++ b/lib/queue.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * lists and queues implementations + */ + +#ifndef _FRR_QUEUE_H +#define _FRR_QUEUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__OpenBSD__) && !defined(STAILQ_HEAD) +#include "openbsd-queue.h" + +/* Try to map FreeBSD implementation to OpenBSD one. */ +#define STAILQ_HEAD(name, type) SIMPLEQ_HEAD(name, type) +#define STAILQ_HEAD_INITIALIZER(head) SIMPLEQ_HEAD_INITIALIZER(head) +#define STAILQ_ENTRY(entry) SIMPLEQ_ENTRY(entry) + +#define STAILQ_CONCAT(head1, head2) SIMPLEQ_CONCAT(head1, head2) +#define STAILQ_EMPTY(head) SIMPLEQ_EMPTY(head) +#define STAILQ_FIRST(head) SIMPLEQ_FIRST(head) +#define STAILQ_FOREACH(var, head, field) SIMPLEQ_FOREACH(var, head, field) +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) +#define STAILQ_INIT(head) SIMPLEQ_INIT(head) +#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) SIMPLEQ_INSERT_AFTER(head, tqelm, elm, field) +#define STAILQ_INSERT_HEAD(head, elm, field) SIMPLEQ_INSERT_HEAD(head, elm, field) +#define STAILQ_INSERT_TAIL(head, elm, field) SIMPLEQ_INSERT_TAIL(head, elm, field) +#define STAILQ_LAST(head, type, field) \ + (SIMPLEQ_EMPTY((head)) \ + ? NULL \ + : ((struct type *)(void *)((char *)((head)->sqh_last) \ + - offsetof(struct type, field)))) +#define STAILQ_NEXT(elm, field) SIMPLEQ_NEXT(elm, field) +#define STAILQ_REMOVE(head, elm, type, field) \ + do { \ + if (SIMPLEQ_FIRST((head)) == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = SIMPLEQ_FIRST((head)); \ + while (SIMPLEQ_NEXT(curelm, field) != (elm)) \ + curelm = SIMPLEQ_NEXT(curelm, field); \ + SIMPLEQ_REMOVE_AFTER(head, curelm, field); \ + } \ + } while (0) +#define STAILQ_REMOVE_AFTER(head, elm, field) SIMPLEQ_REMOVE_AFTER(head, elm, field) +#define STAILQ_REMOVE_HEAD(head, field) SIMPLEQ_REMOVE_HEAD(head, field) +#define STAILQ_SWAP(head1, head2, type) \ + do { \ + struct type *swap_first = STAILQ_FIRST(head1); \ + struct type **swap_last = (head1)->sqh_last; \ + STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ + (head1)->sqh_last = (head2)->sqh_last; \ + STAILQ_FIRST(head2) = swap_first; \ + (head2)->sqh_last = swap_last; \ + if (STAILQ_EMPTY(head1)) \ + (head1)->sqh_last = &STAILQ_FIRST(head1); \ + if (STAILQ_EMPTY(head2)) \ + (head2)->sqh_last = &STAILQ_FIRST(head2); \ + } while (0) +#else +#include "freebsd-queue.h" +#endif /* defined(__OpenBSD__) && !defined(STAILQ_HEAD) */ + +#ifndef TAILQ_POP_FIRST +#define TAILQ_POP_FIRST(head, field) \ + ({ typeof((head)->tqh_first) _elm = TAILQ_FIRST(head); \ + if (_elm) { \ + if ((TAILQ_NEXT((_elm), field)) != NULL) \ + TAILQ_NEXT((_elm), field)->field.tqe_prev = \ + &TAILQ_FIRST(head); \ + else \ + (head)->tqh_last = &TAILQ_FIRST(head); \ + TAILQ_FIRST(head) = TAILQ_NEXT((_elm), field); \ + }; _elm; }) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_QUEUE_H */ diff --git a/lib/resolver.c b/lib/resolver.c new file mode 100644 index 0000000..901ccf8 --- /dev/null +++ b/lib/resolver.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* C-Ares integration to Quagga mainloop + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "typesafe.h" +#include "jhash.h" +#include "frrevent.h" +#include "lib_errors.h" +#include "resolver.h" +#include "command.h" +#include "xref.h" +#include "vrf.h" + +XREF_SETUP(); + +struct resolver_state { + ares_channel channel; + struct event_loop *master; + struct event *timeout; +}; + +static struct resolver_state state; +static bool resolver_debug; + +/* a FD doesn't necessarily map 1:1 to a request; we could be talking to + * multiple caches simultaneously, to see which responds fastest. + * Theoretically we could also be using the same fd for multiple lookups, + * but the c-ares API guarantees an n:1 mapping for fd => channel. + * + * Either way c-ares makes that decision and we just need to deal with + * whatever FDs it gives us. + */ + +DEFINE_MTYPE_STATIC(LIB, ARES_FD, "c-ares (DNS) file descriptor information"); +PREDECL_HASH(resolver_fds); + +struct resolver_fd { + struct resolver_fds_item itm; + + int fd; + struct resolver_state *state; + struct event *t_read, *t_write; +}; + +static int resolver_fd_cmp(const struct resolver_fd *a, + const struct resolver_fd *b) +{ + return numcmp(a->fd, b->fd); +} + +static uint32_t resolver_fd_hash(const struct resolver_fd *item) +{ + return jhash_1word(item->fd, 0xacd04c9e); +} + +DECLARE_HASH(resolver_fds, struct resolver_fd, itm, resolver_fd_cmp, + resolver_fd_hash); + +static struct resolver_fds_head resfds[1] = {INIT_HASH(resfds[0])}; + +static struct resolver_fd *resolver_fd_get(int fd, + struct resolver_state *newstate) +{ + struct resolver_fd ref = {.fd = fd}, *res; + + res = resolver_fds_find(resfds, &ref); + if (!res && newstate) { + res = XCALLOC(MTYPE_ARES_FD, sizeof(*res)); + res->fd = fd; + res->state = newstate; + resolver_fds_add(resfds, res); + + if (resolver_debug) + zlog_debug("c-ares registered FD %d", fd); + } + return res; +} + +static void resolver_fd_drop_maybe(struct resolver_fd *resfd) +{ + if (resfd->t_read || resfd->t_write) + return; + + if (resolver_debug) + zlog_debug("c-ares unregistered FD %d", resfd->fd); + + resolver_fds_del(resfds, resfd); + XFREE(MTYPE_ARES_FD, resfd); +} + +/* end of FD housekeeping */ + +static void resolver_update_timeouts(struct resolver_state *r); + +static void resolver_cb_timeout(struct event *t) +{ + struct resolver_state *r = EVENT_ARG(t); + + ares_process_fd(r->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD); + resolver_update_timeouts(r); +} + +static void resolver_cb_socket_readable(struct event *t) +{ + struct resolver_fd *resfd = EVENT_ARG(t); + struct resolver_state *r = resfd->state; + + event_add_read(r->master, resolver_cb_socket_readable, resfd, resfd->fd, + &resfd->t_read); + /* ^ ordering important: + * ares_process_fd may transitively call EVENT_OFF(resfd->t_read) + * combined with resolver_fd_drop_maybe, so resfd may be free'd after! + */ + ares_process_fd(r->channel, resfd->fd, ARES_SOCKET_BAD); + resolver_update_timeouts(r); +} + +static void resolver_cb_socket_writable(struct event *t) +{ + struct resolver_fd *resfd = EVENT_ARG(t); + struct resolver_state *r = resfd->state; + + event_add_write(r->master, resolver_cb_socket_writable, resfd, + resfd->fd, &resfd->t_write); + /* ^ ordering important: + * ares_process_fd may transitively call EVENT_OFF(resfd->t_write) + * combined with resolver_fd_drop_maybe, so resfd may be free'd after! + */ + ares_process_fd(r->channel, ARES_SOCKET_BAD, resfd->fd); + resolver_update_timeouts(r); +} + +static void resolver_update_timeouts(struct resolver_state *r) +{ + struct timeval *tv, tvbuf; + + EVENT_OFF(r->timeout); + tv = ares_timeout(r->channel, NULL, &tvbuf); + if (tv) { + unsigned int timeoutms = tv->tv_sec * 1000 + tv->tv_usec / 1000; + + event_add_timer_msec(r->master, resolver_cb_timeout, r, + timeoutms, &r->timeout); + } +} + +static void ares_socket_cb(void *data, ares_socket_t fd, int readable, + int writable) +{ + struct resolver_state *r = (struct resolver_state *)data; + struct resolver_fd *resfd; + + resfd = resolver_fd_get(fd, (readable || writable) ? r : NULL); + if (!resfd) + return; + + assert(resfd->state == r); + + if (!readable) + EVENT_OFF(resfd->t_read); + else if (!resfd->t_read) + event_add_read(r->master, resolver_cb_socket_readable, resfd, + fd, &resfd->t_read); + + if (!writable) + EVENT_OFF(resfd->t_write); + else if (!resfd->t_write) + event_add_write(r->master, resolver_cb_socket_writable, resfd, + fd, &resfd->t_write); + + resolver_fd_drop_maybe(resfd); +} + +#if (ARES_VERSION >= 0x011c00) +static void ares_address_cb(void *arg, int status, int timeouts, + struct ares_addrinfo *result) +{ + struct resolver_query *query = (struct resolver_query *)arg; + union sockunion addr[16]; + void (*callback)(struct resolver_query *q, const char *err, int ret, + union sockunion *s); + size_t i; + struct ares_addrinfo_node *node; + + callback = query->callback; + query->callback = NULL; + + if (status != ARES_SUCCESS) { + if (resolver_debug) + zlog_debug("[%p] Resolving failed (%s)", + query, ares_strerror(status)); + + callback(query, ares_strerror(status), -1, NULL); + if (result) + ares_freeaddrinfo(result); + return; + } + + + node = result->nodes; + for (i = 0; i < array_size(addr) && node; i++) { + memset(&addr[i], 0, sizeof(addr[i])); + addr[i].sa.sa_family = node->ai_family; + switch (node->ai_family) { + case AF_INET: + memcpy(&addr[i].sin.sin_addr, node->ai_addr, + node->ai_addrlen); + break; + case AF_INET6: + memcpy(&addr[i].sin6.sin6_addr, node->ai_addr, + node->ai_addrlen); + break; + } + node = node->ai_next; + } + + if (resolver_debug) + zlog_debug("[%p] Resolved with %d results", query, (int)i); + + callback(query, NULL, i, &addr[0]); + ares_freeaddrinfo(result); +} +#else +static void ares_address_cb(void *arg, int status, int timeouts, + struct hostent *he) +{ + struct resolver_query *query = (struct resolver_query *)arg; + union sockunion addr[16]; + void (*callback)(struct resolver_query *, const char *, int, + union sockunion *); + size_t i; + + callback = query->callback; + query->callback = NULL; + + if (status != ARES_SUCCESS) { + if (resolver_debug) + zlog_debug("[%p] Resolving failed (%s)", + query, ares_strerror(status)); + + callback(query, ares_strerror(status), -1, NULL); + return; + } + + for (i = 0; i < array_size(addr) && he->h_addr_list[i] != NULL; i++) { + memset(&addr[i], 0, sizeof(addr[i])); + addr[i].sa.sa_family = he->h_addrtype; + switch (he->h_addrtype) { + case AF_INET: + memcpy(&addr[i].sin.sin_addr, + (uint8_t *)he->h_addr_list[i], he->h_length); + break; + case AF_INET6: + memcpy(&addr[i].sin6.sin6_addr, + (uint8_t *)he->h_addr_list[i], he->h_length); + break; + } + } + + if (resolver_debug) + zlog_debug("[%p] Resolved with %d results", query, (int)i); + + callback(query, NULL, i, &addr[0]); +} + +#endif + +static void resolver_cb_literal(struct event *t) +{ + struct resolver_query *query = EVENT_ARG(t); + void (*callback)(struct resolver_query *, const char *, int, + union sockunion *); + + callback = query->callback; + query->callback = NULL; + + callback(query, ARES_SUCCESS, 1, &query->literal_addr); +} + +void resolver_resolve(struct resolver_query *query, int af, vrf_id_t vrf_id, + const char *hostname, + void (*callback)(struct resolver_query *, const char *, + int, union sockunion *)) +{ + int ret; +#if (ARES_VERSION >= 0x011c00) + struct ares_addrinfo_hints hints = { + .ai_flags = 0, + .ai_family = af, + .ai_socktype = 0, /* any of SOCK_STREAM or SOCK_DGRAM */ + .ai_protocol = 0 /* any protocol */ + }; +#endif + + if (hostname == NULL) + return; + + if (query->callback != NULL) { + flog_err( + EC_LIB_RESOLVER, + "Trying to resolve '%s', but previous query was not finished yet", + hostname); + return; + } + + query->callback = callback; + query->literal_cb = NULL; + + ret = str2sockunion(hostname, &query->literal_addr); + if (ret == 0) { + if (resolver_debug) + zlog_debug("[%p] Resolving '%s' (IP literal)", + query, hostname); + + /* for consistency with proper name lookup, don't call the + * callback immediately; defer to thread loop + */ + event_add_timer_msec(state.master, resolver_cb_literal, query, + 0, &query->literal_cb); + return; + } + + if (resolver_debug) + zlog_debug("[%p] Resolving '%s'", query, hostname); + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + return; + } + +#if (ARES_VERSION >= 0x011c00) + ares_getaddrinfo(state.channel, hostname, NULL, &hints, ares_address_cb, + query); +#else + ares_gethostbyname(state.channel, hostname, af, ares_address_cb, query); +#endif + ret = vrf_switchback_to_initial(); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + resolver_update_timeouts(&state); +} + +DEFUN(debug_resolver, + debug_resolver_cmd, + "[no] debug resolver", + NO_STR + DEBUG_STR + "Debug DNS resolver actions\n") +{ + resolver_debug = (argc == 2); + return CMD_SUCCESS; +} + +static int resolver_config_write_debug(struct vty *vty); +static struct cmd_node resolver_debug_node = { + .name = "resolver debug", + .node = RESOLVER_DEBUG_NODE, + .prompt = "", + .config_write = resolver_config_write_debug, +}; + +static int resolver_config_write_debug(struct vty *vty) +{ + if (resolver_debug) + vty_out(vty, "debug resolver\n"); + return 1; +} + + +void resolver_init(struct event_loop *tm) +{ + struct ares_options ares_opts; + + state.master = tm; + + ares_opts = (struct ares_options){ + .sock_state_cb = &ares_socket_cb, + .sock_state_cb_data = &state, + .timeout = 2, + .tries = 3, + }; + + ares_init_options(&state.channel, &ares_opts, + ARES_OPT_SOCK_STATE_CB | ARES_OPT_TIMEOUT + | ARES_OPT_TRIES); + + install_node(&resolver_debug_node); + install_element(CONFIG_NODE, &debug_resolver_cmd); + install_element(ENABLE_NODE, &debug_resolver_cmd); +} + +void resolver_terminate(void) +{ + ares_destroy(state.channel); +} diff --git a/lib/resolver.h b/lib/resolver.h new file mode 100644 index 0000000..882f960 --- /dev/null +++ b/lib/resolver.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* C-Ares integration to Quagga mainloop + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifndef _FRR_RESOLVER_H +#define _FRR_RESOLVER_H + +#include "frrevent.h" +#include "sockunion.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct resolver_query { + void (*callback)(struct resolver_query *, const char *errstr, int n, + union sockunion *); + + /* used to immediate provide the result if IP literal is passed in */ + union sockunion literal_addr; + struct event *literal_cb; +}; + +void resolver_init(struct event_loop *tm); +void resolver_terminate(void); +void resolver_resolve(struct resolver_query *query, int af, vrf_id_t vrf_id, + const char *hostname, + void (*cb)(struct resolver_query *, const char *, int, + union sockunion *)); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_RESOLVER_H */ diff --git a/lib/ringbuf.c b/lib/ringbuf.c new file mode 100644 index 0000000..3cd7b49 --- /dev/null +++ b/lib/ringbuf.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Circular buffer implementation. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#include + +#include "ringbuf.h" +#include "memory.h" + +DEFINE_MTYPE_STATIC(LIB, RINGBUFFER, "Ring buffer"); + +struct ringbuf *ringbuf_new(size_t size) +{ + struct ringbuf *buf = XCALLOC(MTYPE_RINGBUFFER, sizeof(struct ringbuf)); + buf->data = XCALLOC(MTYPE_RINGBUFFER, size); + buf->size = size; + buf->empty = true; + return buf; +} + +void ringbuf_del(struct ringbuf *buf) +{ + XFREE(MTYPE_RINGBUFFER, buf->data); + XFREE(MTYPE_RINGBUFFER, buf); +} + +size_t ringbuf_remain(struct ringbuf *buf) +{ + ssize_t diff = buf->end - buf->start; + diff += ((diff == 0) && !buf->empty) ? buf->size : 0; + diff += (diff < 0) ? buf->size : 0; + return (size_t)diff; +} + +size_t ringbuf_space(struct ringbuf *buf) +{ + return buf->size - ringbuf_remain(buf); +} + +size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size) +{ + const uint8_t *dp = data; + size_t space = ringbuf_space(buf); + size_t copysize = MIN(size, space); + size_t tocopy = copysize; + if (tocopy >= buf->size - buf->end) { + size_t ts = buf->size - buf->end; + memcpy(buf->data + buf->end, dp, ts); + buf->end = 0; + tocopy -= ts; + dp += ts; + } + memcpy(buf->data + buf->end, dp, tocopy); + buf->end += tocopy; + buf->empty = (buf->start == buf->end) && (buf->empty && !copysize); + return copysize; +} + +size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size) +{ + uint8_t *dp = data; + size_t remain = ringbuf_remain(buf); + size_t copysize = MIN(remain, size); + size_t tocopy = copysize; + if (tocopy >= buf->size - buf->start) { + size_t ts = buf->size - buf->start; + memcpy(dp, buf->data + buf->start, ts); + buf->start = 0; + tocopy -= ts; + dp += ts; + } + memcpy(dp, buf->data + buf->start, tocopy); + buf->start = buf->start + tocopy; + buf->empty = (buf->start == buf->end) && (buf->empty || copysize); + return copysize; +} + +size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data, size_t size) +{ + uint8_t *dp = data; + size_t remain = ringbuf_remain(buf); + if (offset >= remain) + return 0; + size_t copysize = MAX(MIN(remain - offset, size), (size_t)0); + size_t tocopy = copysize; + size_t cstart = (buf->start + offset) % buf->size; + if (tocopy >= buf->size - cstart) { + size_t ts = buf->size - cstart; + memcpy(dp, buf->data + cstart, ts); + cstart = 0; + tocopy -= ts; + dp += ts; + } + memcpy(dp, buf->data + cstart, tocopy); + return copysize; +} + +size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size) +{ + size_t tocopy = MIN(ringbuf_space(to), size); + uint8_t *cbuf = XCALLOC(MTYPE_TMP, tocopy); + tocopy = ringbuf_peek(from, 0, cbuf, tocopy); + size_t put = ringbuf_put(to, cbuf, tocopy); + XFREE(MTYPE_TMP, cbuf); + return put; +} + +void ringbuf_reset(struct ringbuf *buf) +{ + buf->start = buf->end = 0; + buf->empty = true; +} + +void ringbuf_wipe(struct ringbuf *buf) +{ + memset(buf->data, 0x00, buf->size); + ringbuf_reset(buf); +} diff --git a/lib/ringbuf.h b/lib/ringbuf.h new file mode 100644 index 0000000..2bef6d3 --- /dev/null +++ b/lib/ringbuf.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Circular buffer implementation. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#ifndef _FRR_RINGBUF_H_ +#define _FRR_RINGBUF_H_ + +#include +#include + +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ringbuf { + size_t size; + ssize_t start; + ssize_t end; + bool empty; + uint8_t *data; +}; + +/* + * Creates a new ring buffer. + * + * @param size buffer size, in bytes + * @return the newly created buffer + */ +struct ringbuf *ringbuf_new(size_t size); + +/* + * Deletes a ring buffer and frees all associated resources. + * + * @param buf the ring buffer to destroy + */ +void ringbuf_del(struct ringbuf *buf); + +/* + * Get amount of data left to read from the buffer. + * + * @return number of readable bytes + */ +size_t ringbuf_remain(struct ringbuf *buf); + +/* + * Get amount of space left to write to the buffer + * + * @return number of writeable bytes + */ +size_t ringbuf_space(struct ringbuf *buf); + + +/* + * Put data into the ring buffer. + * + * @param data the data to put in the buffer + * @param size how much of data to put in + * @return number of bytes written; will be less than size if there was not + * enough space + */ +size_t ringbuf_put(struct ringbuf *buf, const void *data, size_t size); + +/* + * Get data from the ring buffer. + * + * @param data where to put the data + * @param size how much of data to get + * @return number of bytes read into data; will be less than size if there was + * not enough data to read + */ +size_t ringbuf_get(struct ringbuf *buf, void *data, size_t size); + +/* + * Peek data from the ring buffer. + * + * @param offset where to get the data from, in bytes offset from the + * start of the data + * @param data where to put the data + * @param size how much data to get + * @return number of bytes read into data; will be less than size + * if there was not enough data to read; will be -1 if the + * offset exceeds the amount of data left in the ring + * buffer + */ +size_t ringbuf_peek(struct ringbuf *buf, size_t offset, void *data, + size_t size); + +/* + * Copy data from one ringbuf to another. + * + * @param to destination ringbuf + * @param from source ringbuf + * @param size how much data to copy + * @return amount of data copied + */ +size_t ringbuf_copy(struct ringbuf *to, struct ringbuf *from, size_t size); + +/* + * Reset buffer. Does not wipe. + * + * @param buf + */ +void ringbuf_reset(struct ringbuf *buf); + +/* + * Reset buffer. Wipes. + * + * @param buf + */ +void ringbuf_wipe(struct ringbuf *buf); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_RINGBUF_H_ */ diff --git a/lib/route_opaque.h b/lib/route_opaque.h new file mode 100644 index 0000000..82ff21c --- /dev/null +++ b/lib/route_opaque.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Opaque data for Zebra from other daemons. + * + * Copyright (C) 2021 Donatas Abraitis + */ + +#ifndef FRR_ROUTE_OPAQUE_H +#define FRR_ROUTE_OPAQUE_H + +#include "assert.h" +#include "zclient.h" + +/* copied from bgpd/bgp_community.h */ +#define COMMUNITY_SIZE 4 +/* copied from bgpd/bgp_lcommunity.h */ +#define LCOMMUNITY_SIZE 12 +/* copied from bgpd/bgp_route.h */ +#define BGP_MAX_SELECTION_REASON_STR_BUF 32 + +struct bgp_zebra_opaque { + char aspath[256]; + + /* Show at least 10 communities AA:BB */ + char community[COMMUNITY_SIZE * 20]; + + /* Show at least 10 large-communities AA:BB:CC */ + char lcommunity[LCOMMUNITY_SIZE * 30]; + + /* 32 bytes seems enough because of + * bgp_path_selection_confed_as_path which is + * `Confederation based AS Path`. + */ + char selection_reason[BGP_MAX_SELECTION_REASON_STR_BUF]; +}; + +struct ospf_zebra_opaque { + char path_type[32]; + char area_id[INET_ADDRSTRLEN]; + char tag[16]; +}; + +static_assert(sizeof(struct bgp_zebra_opaque) <= ZAPI_MESSAGE_OPAQUE_LENGTH, + "BGP opaque data shouldn't be larger than zebra's buffer"); +static_assert(sizeof(struct ospf_zebra_opaque) <= ZAPI_MESSAGE_OPAQUE_LENGTH, + "OSPF opaque data shouldn't be larger than zebra's buffer"); + +#endif /* FRR_ROUTE_OPAQUE_H */ diff --git a/lib/route_types.pl b/lib/route_types.pl new file mode 100755 index 0000000..c75a866 --- /dev/null +++ b/lib/route_types.pl @@ -0,0 +1,213 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: GPL-2.0-or-later +## +## Scan a file of route-type definitions (see eg route_types.txt) and +## generate a corresponding header file with: +## +## - enum of Zserv route-types +## - redistribute strings for the various Quagga daemons +## +## See route_types.txt for the format. +## +## +## Copyright (C) 2009 David Lamparter. +## + +use strict; +use Getopt::Long; + +# input processing +# +my @protos; +my %protodetail; + +my %daemons; + +my @enabled; + +GetOptions ("enabled=s" => \@enabled); +@enabled = split(/,/,join(',',@enabled)); + +while () { + # skip comments and empty lines + next if (/^\s*(#|$)/); + + # strip whitespace + chomp; + $_ =~ s/^\s*//; + $_ =~ s/\s*$//; + + # match help strings + if (/^(ZEBRA_ROUTE_[^\s]+)\s*,\s*"(.*)"$/) { + $protodetail{$1}->{'longhelp'} = $2; + next; + } + + $_ =~ s/\s*,\s*/,/g; + + # else: 8-field line + my @f = split(/,/, $_); + unless (@f == 9 || @f == 10) { + die "invalid input on route_types line $.\n"; + } + + my $proto = $f[0]; + $f[3] = $1 if ($f[3] =~ /^'(.*)'$/); + $f[7] = $1 if ($f[7] =~ /^"(.*)"$/); + + $protodetail{$proto} = { + "number" => scalar @protos, + "type" => $f[0], + "cname" => $f[1], + "daemon" => $f[2], + "char" => $f[3], + "ipv4" => int($f[4]), + "ipv6" => int($f[5]), + "redist" => int($f[6]), + "shorthelp" => $f[7], + "enabled" => $f[8], + "restrict2" => $f[9], + }; + push @protos, $proto; + $daemons{$f[2]} = { + "ipv4" => int($f[4]), + "ipv6" => int($f[5]) + } unless ($f[2] eq "NULL"); +} + +# output +printf <{"ipv4"}); + push @protosv6, $p if ($protodetail{$p}->{"ipv6"}); +} +pop @protos; + +sub codelist { + my (@protos) = @_; + my (@lines) = (); + my $str = " \"Codes: "; + for my $p (@protos) { + next unless (grep $_ eq $protodetail{$p}->{"enabled"}, @enabled); + my $s = sprintf("%s - %s, ", + $protodetail{$p}->{"char"}, + $protodetail{$p}->{"shorthelp"}); + if (length($str . $s) > 70) { + $str =~ s/ $//; + push @lines, $str . "\\n\" \\\n"; + $str = " \" "; + } + $str .= $s; + } + $str =~ s/ $//; + push @lines, $str . "\\n\" \\\n"; + push @lines, " \" > - selected route, * - FIB route, q - queued, r - rejected, b - backup\\n\""; + push @lines, " \" t - trapped, o - offload failure\\n\\n\""; + + + return join("", @lines); +} + +print "\n"; +printf "#define SHOW_ROUTE_V4_HEADER \\\n%s\n", codelist(@protosv4); +printf "#define SHOW_ROUTE_V6_HEADER \\\n%s\n", codelist(@protosv6); +print "\n"; + +sub collect { + my ($daemon, $ipv4, $ipv6, $any) = @_; + my (@names, @help) = ((), ()); + for my $p (@protos) { + next if ($protodetail{$p}->{"daemon"} eq $daemon && $daemon ne "zebra"); + next if ($protodetail{$p}->{"restrict2"} ne "" && + $protodetail{$p}->{"restrict2"} ne $daemon); + next if ($protodetail{$p}->{"redist"} eq 0); + next unless (grep $_ eq $protodetail{$p}->{"enabled"}, @enabled); + next unless (($ipv4 && $protodetail{$p}->{"ipv4"}) + || ($ipv6 && $protodetail{$p}->{"ipv6"})); + push @names, $protodetail{$p}->{"cname"}; + push @help, " \"".$protodetail{$p}->{"longhelp"}."\\n\""; + } + if ($any == 1) { + push @names, "any"; + push @help, " \"Any of the above protocols\\n\""; + } + return ("\"<" . join("|", @names) . ">\"", join(" \\\n", @help)); +} + +for my $daemon (sort keys %daemons) { + next unless ($daemons{$daemon}->{"ipv4"} || $daemons{$daemon}->{"ipv6"}); + printf "/* %s */\n", $daemon; + if ($daemons{$daemon}->{"ipv4"} && $daemons{$daemon}->{"ipv6"}) { + my ($names, $help) = collect($daemon, 1, 1, 0); + printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + + ($names, $help) = collect($daemon, 1, 0, 0); + printf "#define FRR_IP_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_IP_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + + ($names, $help) = collect($daemon, 0, 1, 0); + printf "#define FRR_IP6_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_IP6_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + + if ($daemon eq "zebra") { + ($names, $help) = collect($daemon, 1, 0, 1); + printf "#define FRR_IP_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_IP_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + + ($names, $help) = collect($daemon, 0, 1, 1); + printf "#define FRR_IP6_PROTOCOL_MAP_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_IP6_PROTOCOL_MAP_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + } + } else { + my ($names, $help) = collect($daemon, + $daemons{$daemon}->{"ipv4"}, $daemons{$daemon}->{"ipv6"}, 0); + printf "#define FRR_REDIST_STR_%s \\\n %s\n", uc $daemon, $names; + printf "#define FRR_REDIST_HELP_STR_%s \\\n%s\n", uc $daemon, $help; + } + print "\n"; +} + +print <{"cname"}, $protodetail{$p}->{"char"}; +} + +print < vnc +ZEBRA_ROUTE_BGP_DIRECT, bgp-direct, NULL, 'b', 0, 0, 0, "BGP-Direct", bgpd-vnc +# bgp unicast -> vnc +ZEBRA_ROUTE_BGP_DIRECT_EXT, bgp-direct-to-nve-groups, NULL, 'e', 0, 0, 0, "BGP2VNC", bgpd-vnc +ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, 1, "Babel", babeld +ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP", sharpd +ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR", pbrd +ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD", bfdd +ZEBRA_ROUTE_OPENFABRIC, openfabric, fabricd, 'f', 1, 1, 1, "OpenFabric", fabricd +ZEBRA_ROUTE_VRRP, vrrp, vrrpd, '-', 0, 0, 0, "VRRP", vrrpd +ZEBRA_ROUTE_NHG, zebra, none, '-', 0, 0, 0, "Nexthop Group", none +ZEBRA_ROUTE_SRTE, srte, none, '-', 0, 0, 0, "SR-TE", none +ZEBRA_ROUTE_TABLE_DIRECT, table-direct, zebra, 't', 1, 1, 1, "Table-Direct", zebra +ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-", none + + +## help strings +ZEBRA_ROUTE_SYSTEM, "Reserved route type, for internal use only" +ZEBRA_ROUTE_KERNEL, "Kernel routes (not installed via the zebra RIB)" +ZEBRA_ROUTE_CONNECT,"Connected routes (directly attached subnet or host)" +ZEBRA_ROUTE_LOCAL, "Local routes (directly attached host route)" +ZEBRA_ROUTE_STATIC, "Statically configured routes" +ZEBRA_ROUTE_RIP, "Routing Information Protocol (RIP)" +ZEBRA_ROUTE_RIPNG, "Routing Information Protocol next-generation (IPv6) (RIPng)" +ZEBRA_ROUTE_OSPF, "Open Shortest Path First (OSPFv2)" +ZEBRA_ROUTE_OSPF6, "Open Shortest Path First (IPv6) (OSPFv3)" +ZEBRA_ROUTE_ISIS, "Intermediate System to Intermediate System (IS-IS)" +ZEBRA_ROUTE_BGP, "Border Gateway Protocol (BGP)" +ZEBRA_ROUTE_PIM, "Protocol Independent Multicast (PIM)" +ZEBRA_ROUTE_EIGRP, "Enhanced Interior Gateway Routing Protocol (EIGRP)" +ZEBRA_ROUTE_NHRP, "Next Hop Resolution Protocol (NHRP)" +ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)" +ZEBRA_ROUTE_VNC, "Virtual Network Control (VNC)" +ZEBRA_ROUTE_OLSR, "Optimised Link State Routing (OLSR)" +ZEBRA_ROUTE_TABLE, "Non-main Kernel Routing Table" +ZEBRA_ROUTE_LDP, "Label Distribution Protocol (LDP)" +ZEBRA_ROUTE_VNC_DIRECT, "VNC direct (not via zebra) routes" +ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)" +ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)" +ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)" +ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)" +ZEBRA_ROUTE_VRRP, "Virtual Router Redundancy Protocol (VRRP)" +ZEBRA_ROUTE_OPENFABRIC, "OpenFabric Routing Protocol" +ZEBRA_ROUTE_NHG, "Zebra Nexthop Groups (NHG)" +ZEBRA_ROUTE_TABLE_DIRECT, "Non-main Kernel Routing Table - Direct" diff --git a/lib/routemap.c b/lib/routemap.c new file mode 100644 index 0000000..ea917eb --- /dev/null +++ b/lib/routemap.c @@ -0,0 +1,3435 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Route map function. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "linklist.h" +#include "memory.h" +#include "command.h" +#include "vector.h" +#include "prefix.h" +#include "vty.h" +#include "routemap.h" +#include "command.h" +#include "log.h" +#include "hash.h" +#include "libfrr.h" +#include "lib_errors.h" +#include "table.h" +#include "json.h" +#include "jhash.h" + +#include "lib/routemap_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP, "Route map"); +DEFINE_MTYPE(LIB, ROUTE_MAP_NAME, "Route map name"); +DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_INDEX, "Route map index"); +DEFINE_MTYPE(LIB, ROUTE_MAP_RULE, "Route map rule"); +DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_RULE_STR, "Route map rule str"); +DEFINE_MTYPE(LIB, ROUTE_MAP_COMPILED, "Route map compiled"); +DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP, "Route map dependency"); +DEFINE_MTYPE_STATIC(LIB, ROUTE_MAP_DEP_DATA, "Route map dependency data"); + +DEFINE_QOBJ_TYPE(route_map_index); +DEFINE_QOBJ_TYPE(route_map); + +static int rmap_cmd_name_cmp(const struct route_map_rule_cmd_proxy *a, + const struct route_map_rule_cmd_proxy *b) +{ + return strcmp(a->cmd->str, b->cmd->str); +} + +static uint32_t rmap_cmd_name_hash(const struct route_map_rule_cmd_proxy *item) +{ + return jhash(item->cmd->str, strlen(item->cmd->str), 0xbfd69320); +} + +DECLARE_HASH(rmap_cmd_name, struct route_map_rule_cmd_proxy, itm, + rmap_cmd_name_cmp, rmap_cmd_name_hash); + +static struct rmap_cmd_name_head rmap_match_cmds[1] = { + INIT_HASH(rmap_match_cmds[0]), +}; +static struct rmap_cmd_name_head rmap_set_cmds[1] = { + INIT_HASH(rmap_set_cmds[0]), +}; + +#define IPv4_PREFIX_LIST "ip address prefix-list" +#define IPv6_PREFIX_LIST "ipv6 address prefix-list" + +#define IS_RULE_IPv4_PREFIX_LIST(S) \ + (strncmp(S, IPv4_PREFIX_LIST, strlen(IPv4_PREFIX_LIST)) == 0) +#define IS_RULE_IPv6_PREFIX_LIST(S) \ + (strncmp(S, IPv6_PREFIX_LIST, strlen(IPv6_PREFIX_LIST)) == 0) + +struct route_map_pentry_dep { + struct prefix_list_entry *pentry; + const char *plist_name; + route_map_event_t event; +}; + +static void route_map_pfx_tbl_update(route_map_event_t event, + struct route_map_index *index, afi_t afi, + const char *plist_name); +static void route_map_pfx_table_add_default(afi_t afi, + struct route_map_index *index); +static void route_map_pfx_table_del_default(afi_t afi, + struct route_map_index *index); +static void route_map_add_plist_entries(afi_t afi, + struct route_map_index *index, + const char *plist_name, + struct prefix_list_entry *entry); +static void route_map_del_plist_entries(afi_t afi, + struct route_map_index *index, + const char *plist_name, + struct prefix_list_entry *entry); + +static struct hash *route_map_get_dep_hash(route_map_event_t event); +static void route_map_free_map(struct route_map *map); + +struct route_map_match_set_hooks rmap_match_set_hook; + +/* match interface */ +void route_map_match_interface_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_interface = func; +} + +/* no match interface */ +void route_map_no_match_interface_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_interface = func; +} + +/* match ip address */ +void route_map_match_ip_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ip_address = func; +} + +/* no match ip address */ +void route_map_no_match_ip_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ip_address = func; +} + +/* match ip address prefix list */ +void route_map_match_ip_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ip_address_prefix_list = func; +} + +/* no match ip address prefix list */ +void route_map_no_match_ip_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ip_address_prefix_list = func; +} + +/* match ip next hop */ +void route_map_match_ip_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ip_next_hop = func; +} + +/* no match ip next hop */ +void route_map_no_match_ip_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ip_next_hop = func; +} + +/* match ipv6 next-hop */ +void route_map_match_ipv6_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ipv6_next_hop = func; +} + +/* no match ipv6 next-hop */ +void route_map_no_match_ipv6_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ipv6_next_hop = func; +} + +/* match ip next hop prefix list */ +void route_map_match_ip_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ip_next_hop_prefix_list = func; +} + +/* no match ip next hop prefix list */ +void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ip_next_hop_prefix_list = func; +} + +/* match ip next-hop type */ +void route_map_match_ip_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ip_next_hop_type = func; +} + +/* no match ip next-hop type */ +void route_map_no_match_ip_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ip_next_hop_type = func; +} + +/* match ipv6 address */ +void route_map_match_ipv6_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ipv6_address = func; +} + +/* no match ipv6 address */ +void route_map_no_match_ipv6_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ipv6_address = func; +} + + +/* match ipv6 address prefix list */ +void route_map_match_ipv6_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ipv6_address_prefix_list = func; +} + +/* no match ipv6 address prefix list */ +void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ipv6_address_prefix_list = func; +} + +/* match ipv6 next-hop type */ +void route_map_match_ipv6_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ipv6_next_hop_type = func; +} + +/* no match ipv6 next-hop type */ +void route_map_no_match_ipv6_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ipv6_next_hop_type = func; +} + +/* match ipv6 next-hop prefix-list */ +void route_map_match_ipv6_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_ipv6_next_hop_prefix_list = func; +} + +/* no match ipv6 next-hop prefix-list */ +void route_map_no_match_ipv6_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_ipv6_next_hop_prefix_list = func; +} + +/* match metric */ +void route_map_match_metric_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_metric = func; +} + +/* no match metric */ +void route_map_no_match_metric_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_metric = func; +} + +/* match tag */ +void route_map_match_tag_hook(int (*func)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.match_tag = func; +} + +/* no match tag */ +void route_map_no_match_tag_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_match_tag = func; +} + +/* set sr-te color */ +void route_map_set_srte_color_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.set_srte_color = func; +} + +/* no set sr-te color */ +void route_map_no_set_srte_color_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_srte_color = func; +} + +/* set ip nexthop */ +void route_map_set_ip_nexthop_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.set_ip_nexthop = func; +} + +/* no set ip nexthop */ +void route_map_no_set_ip_nexthop_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_ip_nexthop = func; +} + +/* set ipv6 nexthop local */ +void route_map_set_ipv6_nexthop_local_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.set_ipv6_nexthop_local = func; +} + +/* no set ipv6 nexthop local */ +void route_map_no_set_ipv6_nexthop_local_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_ipv6_nexthop_local = func; +} + +/* set metric */ +void route_map_set_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.set_metric = func; +} + +/* no set metric */ +void route_map_no_set_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_metric = func; +} +/* set min-metric */ +void route_map_set_min_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.set_min_metric = func; +} + +/* no set min-metric */ +void route_map_no_set_min_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_min_metric = func; +} +/* set max-metric */ +void route_map_set_max_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.set_max_metric = func; +} + +/* no set max-metric */ +void route_map_no_set_max_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, char *errmsg, + size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_max_metric = func; +} + +/* set tag */ +void route_map_set_tag_hook(int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.set_tag = func; +} + +/* no set tag */ +void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, size_t errmsg_len)) +{ + rmap_match_set_hook.no_set_tag = func; +} + +int generic_match_add(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len) +{ + enum rmap_compile_rets ret; + + ret = route_map_add_match(index, command, arg, type); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", + frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, + "%% [%s] Argument form is unsupported or malformed.", + frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + /* + * Nothing to do here move along + */ + break; + } + + return CMD_SUCCESS; +} + +int generic_match_delete(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len) +{ + enum rmap_compile_rets ret; + int retval = CMD_SUCCESS; + char *dep_name = NULL; + const char *tmpstr; + char *rmap_name = NULL; + + if (type != RMAP_EVENT_MATCH_DELETED) { + /* ignore the mundane, the types without any dependency */ + if (arg == NULL) { + if ((tmpstr = route_map_get_match_arg(index, command)) + != NULL) + dep_name = + XSTRDUP(MTYPE_ROUTE_MAP_RULE, tmpstr); + } else { + dep_name = XSTRDUP(MTYPE_ROUTE_MAP_RULE, arg); + } + rmap_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, index->map->name); + } + + ret = route_map_delete_match(index, command, dep_name, type); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", + frr_protonameinst); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, + "%% [%s] Argument form is unsupported or malformed.", + frr_protonameinst); + retval = CMD_WARNING_CONFIG_FAILED; + break; + case RMAP_COMPILE_SUCCESS: + /* + * Nothing to do here + */ + break; + } + + XFREE(MTYPE_ROUTE_MAP_RULE, dep_name); + XFREE(MTYPE_ROUTE_MAP_NAME, rmap_name); + + return retval; +} + +int generic_set_add(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len) +{ + enum rmap_compile_rets ret; + + ret = route_map_add_set(index, command, arg); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, + "%% [%s] Can't find rule.", frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, + "%% [%s] Argument form is unsupported or malformed.", + frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + break; + } + + return CMD_SUCCESS; +} + +int generic_set_delete(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len) +{ + enum rmap_compile_rets ret; + + ret = route_map_delete_set(index, command, arg); + switch (ret) { + case RMAP_RULE_MISSING: + snprintf(errmsg, errmsg_len, "%% [%s] Can't find rule.", + frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_ERROR: + snprintf(errmsg, errmsg_len, + "%% [%s] Argument form is unsupported or malformed.", + frr_protonameinst); + return CMD_WARNING_CONFIG_FAILED; + case RMAP_COMPILE_SUCCESS: + break; + } + + return CMD_SUCCESS; +} + + +/* Master list of route map. */ +struct route_map_list route_map_master = {NULL, NULL, NULL, NULL, NULL}; +struct hash *route_map_master_hash = NULL; + +static unsigned int route_map_hash_key_make(const void *p) +{ + const struct route_map *map = p; + return string_hash_make(map->name); +} + +static bool route_map_hash_cmp(const void *p1, const void *p2) +{ + const struct route_map *map1 = p1; + const struct route_map *map2 = p2; + + if (!strcmp(map1->name, map2->name)) + return true; + + return false; +} + +enum route_map_upd8_type { + ROUTE_MAP_ADD = 1, + ROUTE_MAP_DEL, +}; + +/* all possible route-map dependency types */ +enum route_map_dep_type { + ROUTE_MAP_DEP_RMAP = 1, + ROUTE_MAP_DEP_CLIST, + ROUTE_MAP_DEP_ECLIST, + ROUTE_MAP_DEP_LCLIST, + ROUTE_MAP_DEP_PLIST, + ROUTE_MAP_DEP_ASPATH, + ROUTE_MAP_DEP_FILTER, + ROUTE_MAP_DEP_MAX, +}; + +struct route_map_dep { + char *dep_name; + struct hash *dep_rmap_hash; + struct hash *this_hash; /* ptr to the hash structure this is part of */ +}; + +struct route_map_dep_data { + /* Route-map name. + */ + char *rname; + /* Count of number of sequences of this + * route-map that depend on the same entity. + */ + uint16_t refcnt; +}; + +/* Hashes maintaining dependency between various sublists used by route maps */ +static struct hash *route_map_dep_hash[ROUTE_MAP_DEP_MAX]; + +static unsigned int route_map_dep_hash_make_key(const void *p); +static void route_map_clear_all_references(char *rmap_name); +static void route_map_rule_delete(struct route_map_rule_list *, + struct route_map_rule *); + +uint32_t rmap_debug; + +/* New route map allocation. Please note route map's name must be + specified. */ +static struct route_map *route_map_new(const char *name) +{ + struct route_map *new; + + new = XCALLOC(MTYPE_ROUTE_MAP, sizeof(struct route_map)); + new->name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); + QOBJ_REG(new, route_map); + return new; +} + +/* Add new name to route_map. */ +static struct route_map *route_map_add(const char *name) +{ + struct route_map *map, *exist; + struct route_map_list *list; + + map = route_map_new(name); + list = &route_map_master; + + /* + * Add map to the hash + * + * If the map already exists in the hash, then we know that + * FRR is now in a sequence of delete/create. + * All FRR needs to do here is set the to_be_processed + * bit (to inherit from the old one + */ + exist = hash_release(route_map_master_hash, map); + if (exist) { + map->to_be_processed = exist->to_be_processed; + route_map_free_map(exist); + } + hash_get(route_map_master_hash, map, hash_alloc_intern); + + /* Add new entry to the head of the list to match how it is added in the + * hash table. This is to ensure that if the same route-map has been + * created more than once and then marked for deletion (which can happen + * if prior deletions haven't completed as BGP hasn't yet done the + * route-map processing), the order of the entities is the same in both + * the list and the hash table. Otherwise, since there is nothing to + * distinguish between the two entries, the wrong entry could get freed. + * TODO: This needs to be re-examined to handle it better - e.g., revive + * a deleted entry if the route-map is created again. + */ + map->prev = NULL; + map->next = list->head; + if (list->head) + list->head->prev = map; + list->head = map; + if (!list->tail) + list->tail = map; + + /* Execute hook. */ + if (route_map_master.add_hook) { + (*route_map_master.add_hook)(name); + route_map_notify_dependencies(name, RMAP_EVENT_CALL_ADDED); + } + + if (!map->ipv4_prefix_table) + map->ipv4_prefix_table = route_table_init(); + + if (!map->ipv6_prefix_table) + map->ipv6_prefix_table = route_table_init(); + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Add route-map %s", name); + return map; +} + +/* this is supposed to be called post processing by + * the delete hook function. Don't invoke delete_hook + * again in this routine. + */ +static void route_map_free_map(struct route_map *map) +{ + struct route_map_list *list; + struct route_map_index *index; + + if (map == NULL) + return; + + while ((index = map->head) != NULL) + route_map_index_delete(index, 0); + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Deleting route-map %s", map->name); + + list = &route_map_master; + + QOBJ_UNREG(map); + + if (map->next) + map->next->prev = map->prev; + else + list->tail = map->prev; + + if (map->prev) + map->prev->next = map->next; + else + list->head = map->next; + + route_table_finish(map->ipv4_prefix_table); + route_table_finish(map->ipv6_prefix_table); + + hash_release(route_map_master_hash, map); + XFREE(MTYPE_ROUTE_MAP_NAME, map->name); + XFREE(MTYPE_ROUTE_MAP, map); +} + +/* Route map delete from list. */ +void route_map_delete(struct route_map *map) +{ + struct route_map_index *index; + char *name; + + while ((index = map->head) != NULL) + route_map_index_delete(index, 0); + + name = map->name; + map->head = NULL; + + /* Clear all dependencies */ + route_map_clear_all_references(name); + map->deleted = true; + /* Execute deletion hook. */ + if (route_map_master.delete_hook) { + (*route_map_master.delete_hook)(name); + route_map_notify_dependencies(name, RMAP_EVENT_CALL_DELETED); + } + + if (!map->to_be_processed) { + route_map_free_map(map); + } +} + +/* Lookup route map by route map name string. */ +struct route_map *route_map_lookup_by_name(const char *name) +{ + struct route_map *map; + struct route_map tmp_map; + + if (!name) + return NULL; + + // map.deleted is false via memset + memset(&tmp_map, 0, sizeof(tmp_map)); + tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); + map = hash_lookup(route_map_master_hash, &tmp_map); + XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); + + if (map && map->deleted) + return NULL; + + return map; +} + +/* Simple helper to warn if route-map does not exist. */ +struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name) +{ + struct route_map *route_map = route_map_lookup_by_name(name); + + if (!route_map) + if (vty_shell_serv(vty)) + vty_out(vty, "The route-map '%s' does not exist.\n", name); + + return route_map; +} + +int route_map_mark_updated(const char *name) +{ + struct route_map *map; + int ret = -1; + struct route_map tmp_map; + + if (!name) + return (ret); + + map = route_map_lookup_by_name(name); + + /* If we did not find the routemap with deleted=false try again + * with deleted=true + */ + if (!map) { + memset(&tmp_map, 0, sizeof(tmp_map)); + tmp_map.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, name); + tmp_map.deleted = true; + map = hash_lookup(route_map_master_hash, &tmp_map); + XFREE(MTYPE_ROUTE_MAP_NAME, tmp_map.name); + } + + if (map) { + map->to_be_processed = true; + ret = 0; + } + + return (ret); +} + +static void route_map_clear_updated(struct route_map *map) +{ + if (map) { + map->to_be_processed = false; + if (map->deleted) + route_map_free_map(map); + } +} + +/* Lookup route map. If there isn't route map create one and return + it. */ +struct route_map *route_map_get(const char *name) +{ + struct route_map *map; + + map = route_map_lookup_by_name(name); + if (map == NULL) + map = route_map_add(name); + + return map; +} + +void route_map_walk_update_list(void (*route_map_update_fn)(char *name)) +{ + struct route_map *node; + struct route_map *nnode = NULL; + + for (node = route_map_master.head; node; node = nnode) { + if (node->to_be_processed) { + /* DD: Should we add any thread yield code here */ + route_map_update_fn(node->name); + nnode = node->next; + route_map_clear_updated(node); + } else + nnode = node->next; + } +} + +/* Return route map's type string. */ +static const char *route_map_type_str(enum route_map_type type) +{ + switch (type) { + case RMAP_PERMIT: + return "permit"; + case RMAP_DENY: + return "deny"; + case RMAP_ANY: + return ""; + } + + return ""; +} + +static const char *route_map_cmd_result_str(enum route_map_cmd_result_t res) +{ + switch (res) { + case RMAP_MATCH: + return "match"; + case RMAP_NOMATCH: + return "no match"; + case RMAP_NOOP: + return "noop"; + case RMAP_ERROR: + return "error"; + case RMAP_OKAY: + return "okay"; + } + + return "invalid"; +} + +static const char *route_map_result_str(route_map_result_t res) +{ + switch (res) { + case RMAP_DENYMATCH: + return "deny"; + case RMAP_PERMITMATCH: + return "permit"; + } + + return "invalid"; +} + +/* show route-map */ +static void vty_show_route_map_entry(struct vty *vty, struct route_map *map, + json_object *json) +{ + struct route_map_index *index; + struct route_map_rule *rule; + json_object *json_rmap = NULL; + json_object *json_rules = NULL; + + if (json) { + json_rmap = json_object_new_object(); + json_object_object_add(json, map->name, json_rmap); + + json_rules = json_object_new_array(); + json_object_int_add(json_rmap, "invoked", + map->applied - map->applied_clear); + json_object_boolean_add(json_rmap, "disabledOptimization", + map->optimization_disabled); + json_object_boolean_add(json_rmap, "processedChange", + map->to_be_processed); + json_object_object_add(json_rmap, "rules", json_rules); + } else { + vty_out(vty, + "route-map: %s Invoked: %" PRIu64 + " Optimization: %s Processed Change: %s\n", + map->name, map->applied - map->applied_clear, + map->optimization_disabled ? "disabled" : "enabled", + map->to_be_processed ? "true" : "false"); + } + + for (index = map->head; index; index = index->next) { + if (json) { + json_object *json_rule; + json_object *json_matches; + json_object *json_sets; + char action[BUFSIZ] = {}; + + json_rule = json_object_new_object(); + json_object_array_add(json_rules, json_rule); + + json_object_int_add(json_rule, "sequenceNumber", + index->pref); + json_object_string_add(json_rule, "type", + route_map_type_str(index->type)); + json_object_int_add(json_rule, "invoked", + index->applied + - index->applied_clear); + + /* Description */ + if (index->description) + json_object_string_add(json_rule, "description", + index->description); + + /* Match clauses */ + json_matches = json_object_new_array(); + json_object_object_add(json_rule, "matchClauses", + json_matches); + for (rule = index->match_list.head; rule; + rule = rule->next) { + char buf[BUFSIZ]; + + snprintf(buf, sizeof(buf), "%s %s", + rule->cmd->str, rule->rule_str); + json_array_string_add(json_matches, buf); + } + + /* Set clauses */ + json_sets = json_object_new_array(); + json_object_object_add(json_rule, "setClauses", + json_sets); + for (rule = index->set_list.head; rule; + rule = rule->next) { + char buf[BUFSIZ]; + + snprintf(buf, sizeof(buf), "%s %s", + rule->cmd->str, rule->rule_str); + json_array_string_add(json_sets, buf); + } + + /* Call clause */ + if (index->nextrm) + json_object_string_add(json_rule, "callClause", + index->nextrm); + + /* Exit Policy */ + if (index->exitpolicy == RMAP_GOTO) + snprintf(action, sizeof(action), "Goto %d", + index->nextpref); + else if (index->exitpolicy == RMAP_NEXT) + snprintf(action, sizeof(action), + "Continue to next entry"); + else if (index->exitpolicy == RMAP_EXIT) + snprintf(action, sizeof(action), + "Exit routemap"); + if (action[0] != '\0') + json_object_string_add(json_rule, "action", + action); + } else { + vty_out(vty, " %s, sequence %d Invoked %" PRIu64 "\n", + route_map_type_str(index->type), index->pref, + index->applied - index->applied_clear); + + /* Description */ + if (index->description) + vty_out(vty, " Description:\n %s\n", + index->description); + + /* Match clauses */ + vty_out(vty, " Match clauses:\n"); + for (rule = index->match_list.head; rule; + rule = rule->next) + vty_out(vty, " %s %s\n", rule->cmd->str, + rule->rule_str); + + /* Set clauses */ + vty_out(vty, " Set clauses:\n"); + for (rule = index->set_list.head; rule; + rule = rule->next) + vty_out(vty, " %s %s\n", rule->cmd->str, + rule->rule_str); + + /* Call clause */ + vty_out(vty, " Call clause:\n"); + if (index->nextrm) + vty_out(vty, " Call %s\n", index->nextrm); + + /* Exit Policy */ + vty_out(vty, " Action:\n"); + if (index->exitpolicy == RMAP_GOTO) + vty_out(vty, " Goto %d\n", index->nextpref); + else if (index->exitpolicy == RMAP_NEXT) + vty_out(vty, " Continue to next entry\n"); + else if (index->exitpolicy == RMAP_EXIT) + vty_out(vty, " Exit routemap\n"); + } + } +} + +static int sort_route_map(const void **map1, const void **map2) +{ + const struct route_map *m1 = *map1; + const struct route_map *m2 = *map2; + + return strcmp(m1->name, m2->name); +} + +static int vty_show_route_map(struct vty *vty, const char *name, bool use_json) +{ + struct route_map *map; + json_object *json = NULL; + + if (use_json) + json = json_object_new_object(); + else + vty_out(vty, "%s:\n", frr_protonameinst); + + if (name) { + map = route_map_lookup_by_name(name); + + if (map) { + vty_show_route_map_entry(vty, map, json); + } else if (!use_json) { + vty_out(vty, "%s: 'route-map %s' not found\n", + frr_protonameinst, name); + } + } else { + + struct list *maplist = list_new(); + struct listnode *ln; + + for (map = route_map_master.head; map; map = map->next) + listnode_add(maplist, map); + + list_sort(maplist, sort_route_map); + + for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) + vty_show_route_map_entry(vty, map, json); + + list_delete(&maplist); + } + + return vty_json(vty, json); +} + +/* Unused route map details */ +static int vty_show_unused_route_map(struct vty *vty) +{ + struct list *maplist = list_new(); + struct listnode *ln; + struct route_map *map; + + for (map = route_map_master.head; map; map = map->next) { + /* If use_count is zero, No protocol is using this routemap. + * so adding to the list. + */ + if (!map->use_count) + listnode_add(maplist, map); + } + + if (maplist->count > 0) { + vty_out(vty, "\n%s:\n", frr_protonameinst); + list_sort(maplist, sort_route_map); + + for (ALL_LIST_ELEMENTS_RO(maplist, ln, map)) + vty_show_route_map_entry(vty, map, NULL); + } else { + vty_out(vty, "\n%s: None\n", frr_protonameinst); + } + + list_delete(&maplist); + return CMD_SUCCESS; +} + +/* New route map allocation. Please note route map's name must be + specified. */ +static struct route_map_index *route_map_index_new(void) +{ + struct route_map_index *new; + + new = XCALLOC(MTYPE_ROUTE_MAP_INDEX, sizeof(struct route_map_index)); + new->exitpolicy = RMAP_EXIT; /* Default to Cisco-style */ + TAILQ_INIT(&new->rhclist); + QOBJ_REG(new, route_map_index); + return new; +} + +/* Free route map index. */ +void route_map_index_delete(struct route_map_index *index, int notify) +{ + struct routemap_hook_context *rhc; + struct route_map_rule *rule; + + QOBJ_UNREG(index); + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Deleting route-map %s sequence %d", + index->map->name, index->pref); + + /* Free route map entry description. */ + XFREE(MTYPE_TMP, index->description); + + /* Free route map northbound hook contexts. */ + while ((rhc = TAILQ_FIRST(&index->rhclist)) != NULL) + routemap_hook_context_free(rhc); + + /* Free route match. */ + while ((rule = index->match_list.head) != NULL) { + if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str)) + route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED, + index, AFI_IP, rule->rule_str); + else if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str)) + route_map_pfx_tbl_update(RMAP_EVENT_PLIST_DELETED, + index, AFI_IP6, + rule->rule_str); + + route_map_rule_delete(&index->match_list, rule); + } + + /* Free route set. */ + while ((rule = index->set_list.head) != NULL) + route_map_rule_delete(&index->set_list, rule); + + /* Remove index from route map list. */ + if (index->next) + index->next->prev = index->prev; + else + index->map->tail = index->prev; + + if (index->prev) + index->prev->next = index->next; + else + index->map->head = index->next; + + /* Free 'char *nextrm' if not NULL */ + XFREE(MTYPE_ROUTE_MAP_NAME, index->nextrm); + + route_map_pfx_tbl_update(RMAP_EVENT_INDEX_DELETED, index, 0, NULL); + + /* Execute event hook. */ + if (route_map_master.event_hook && notify) { + (*route_map_master.event_hook)(index->map->name); + route_map_notify_dependencies(index->map->name, + RMAP_EVENT_CALL_ADDED); + } + XFREE(MTYPE_ROUTE_MAP_INDEX, index); +} + +/* Lookup index from route map. */ +static struct route_map_index *route_map_index_lookup(struct route_map *map, + enum route_map_type type, + int pref) +{ + struct route_map_index *index; + + for (index = map->head; index; index = index->next) + if ((index->type == type || type == RMAP_ANY) + && index->pref == pref) + return index; + return NULL; +} + +/* Add new index to route map. */ +static struct route_map_index * +route_map_index_add(struct route_map *map, enum route_map_type type, int pref) +{ + struct route_map_index *index; + struct route_map_index *point; + + /* Allocate new route map inex. */ + index = route_map_index_new(); + index->map = map; + index->type = type; + index->pref = pref; + + /* Compare preference. */ + for (point = map->head; point; point = point->next) + if (point->pref >= pref) + break; + + if (map->head == NULL) { + map->head = map->tail = index; + } else if (point == NULL) { + index->prev = map->tail; + map->tail->next = index; + map->tail = index; + } else if (point == map->head) { + index->next = map->head; + map->head->prev = index; + map->head = index; + } else { + index->next = point; + index->prev = point->prev; + if (point->prev) + point->prev->next = index; + point->prev = index; + } + + route_map_pfx_tbl_update(RMAP_EVENT_INDEX_ADDED, index, 0, NULL); + + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(map->name); + route_map_notify_dependencies(map->name, RMAP_EVENT_CALL_ADDED); + } + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Route-map %s add sequence %d, type: %s", + map->name, pref, route_map_type_str(type)); + + return index; +} + +/* Get route map index. */ +struct route_map_index * +route_map_index_get(struct route_map *map, enum route_map_type type, int pref) +{ + struct route_map_index *index; + + index = route_map_index_lookup(map, RMAP_ANY, pref); + if (index && index->type != type) { + /* Delete index from route map. */ + route_map_index_delete(index, 1); + index = NULL; + } + if (index == NULL) + index = route_map_index_add(map, type, pref); + return index; +} + +/* New route map rule */ +static struct route_map_rule *route_map_rule_new(void) +{ + struct route_map_rule *new; + + new = XCALLOC(MTYPE_ROUTE_MAP_RULE, sizeof(struct route_map_rule)); + return new; +} + +/* Install rule command to the match list. */ +void _route_map_install_match(struct route_map_rule_cmd_proxy *proxy) +{ + rmap_cmd_name_add(rmap_match_cmds, proxy); +} + +/* Install rule command to the set list. */ +void _route_map_install_set(struct route_map_rule_cmd_proxy *proxy) +{ + rmap_cmd_name_add(rmap_set_cmds, proxy); +} + +/* Lookup rule command from match list. */ +static const struct route_map_rule_cmd *route_map_lookup_match(const char *name) +{ + struct route_map_rule_cmd refcmd = {.str = name}; + struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd}; + struct route_map_rule_cmd_proxy *res; + + res = rmap_cmd_name_find(rmap_match_cmds, &ref); + if (res) + return res->cmd; + return NULL; +} + +/* Lookup rule command from set list. */ +static const struct route_map_rule_cmd *route_map_lookup_set(const char *name) +{ + struct route_map_rule_cmd refcmd = {.str = name}; + struct route_map_rule_cmd_proxy ref = {.cmd = &refcmd}; + struct route_map_rule_cmd_proxy *res; + + res = rmap_cmd_name_find(rmap_set_cmds, &ref); + if (res) + return res->cmd; + return NULL; +} + +/* Add match and set rule to rule list. */ +static void route_map_rule_add(struct route_map_rule_list *list, + struct route_map_rule *rule) +{ + rule->next = NULL; + rule->prev = list->tail; + if (list->tail) + list->tail->next = rule; + else + list->head = rule; + list->tail = rule; +} + +/* Delete rule from rule list. */ +static void route_map_rule_delete(struct route_map_rule_list *list, + struct route_map_rule *rule) +{ + if (rule->cmd->func_free) + (*rule->cmd->func_free)(rule->value); + + XFREE(MTYPE_ROUTE_MAP_RULE_STR, rule->rule_str); + + if (rule->next) + rule->next->prev = rule->prev; + else + list->tail = rule->prev; + if (rule->prev) + rule->prev->next = rule->next; + else + list->head = rule->next; + + XFREE(MTYPE_ROUTE_MAP_RULE, rule); +} + +/* strcmp wrapper function which don't crush even argument is NULL. */ +static int rulecmp(const char *dst, const char *src) +{ + if (dst == NULL) { + if (src == NULL) + return 0; + else + return 1; + } else { + if (src == NULL) + return 1; + else + return strcmp(dst, src); + } + return 1; +} + +/* Use this to return the already specified argument for this match. This is + * useful to get the specified argument with a route map match rule when the + * rule is being deleted and the argument is not provided. + */ +const char *route_map_get_match_arg(struct route_map_index *index, + const char *match_name) +{ + struct route_map_rule *rule; + const struct route_map_rule_cmd *cmd; + + /* First lookup rule for add match statement. */ + cmd = route_map_lookup_match(match_name); + if (cmd == NULL) + return NULL; + + for (rule = index->match_list.head; rule; rule = rule->next) + if (rule->cmd == cmd && rule->rule_str != NULL) + return (rule->rule_str); + + return NULL; +} + +static route_map_event_t get_route_map_delete_event(route_map_event_t type) +{ + switch (type) { + case RMAP_EVENT_CALL_ADDED: + return RMAP_EVENT_CALL_DELETED; + case RMAP_EVENT_PLIST_ADDED: + return RMAP_EVENT_PLIST_DELETED; + case RMAP_EVENT_CLIST_ADDED: + return RMAP_EVENT_CLIST_DELETED; + case RMAP_EVENT_ECLIST_ADDED: + return RMAP_EVENT_ECLIST_DELETED; + case RMAP_EVENT_LLIST_ADDED: + return RMAP_EVENT_LLIST_DELETED; + case RMAP_EVENT_ASLIST_ADDED: + return RMAP_EVENT_ASLIST_DELETED; + case RMAP_EVENT_FILTER_ADDED: + return RMAP_EVENT_FILTER_DELETED; + case RMAP_EVENT_SET_ADDED: + case RMAP_EVENT_SET_DELETED: + case RMAP_EVENT_SET_REPLACED: + case RMAP_EVENT_MATCH_ADDED: + case RMAP_EVENT_MATCH_DELETED: + case RMAP_EVENT_MATCH_REPLACED: + case RMAP_EVENT_INDEX_ADDED: + case RMAP_EVENT_INDEX_DELETED: + case RMAP_EVENT_CALL_DELETED: + case RMAP_EVENT_PLIST_DELETED: + case RMAP_EVENT_CLIST_DELETED: + case RMAP_EVENT_ECLIST_DELETED: + case RMAP_EVENT_LLIST_DELETED: + case RMAP_EVENT_ASLIST_DELETED: + case RMAP_EVENT_FILTER_DELETED: + /* This function returns the appropriate 'deleted' event type + * for every 'added' event type passed to this function. + * This is done only for named entities used in the + * route-map match commands. + * This function is not to be invoked for any of the other event + * types. + */ + assert(0); + } + + assert(0); + /* + * Return to make c happy but if we get here something has gone + * terribly terribly wrong, so yes this return makes no sense. + */ + return RMAP_EVENT_CALL_ADDED; +} + +/* Add match statement to route map. */ +enum rmap_compile_rets route_map_add_match(struct route_map_index *index, + const char *match_name, + const char *match_arg, + route_map_event_t type) +{ + struct route_map_rule *rule; + struct route_map_rule *next; + const struct route_map_rule_cmd *cmd; + void *compile; + int8_t delete_rmap_event_type = 0; + const char *rule_key; + + /* First lookup rule for add match statement. */ + cmd = route_map_lookup_match(match_name); + if (cmd == NULL) + return RMAP_RULE_MISSING; + + /* Next call compile function for this match statement. */ + if (cmd->func_compile) { + compile = (*cmd->func_compile)(match_arg); + if (compile == NULL) + return RMAP_COMPILE_ERROR; + } else + compile = NULL; + /* use the compiled results if applicable */ + if (compile && cmd->func_get_rmap_rule_key) + rule_key = (*cmd->func_get_rmap_rule_key) + (compile); + else + rule_key = match_arg; + + /* If argument is completely same ignore it. */ + for (rule = index->match_list.head; rule; rule = next) { + next = rule->next; + if (rule->cmd == cmd) { + /* If the configured route-map match rule is exactly + * the same as the existing configuration then, + * ignore the duplicate configuration. + */ + if (rulecmp(match_arg, rule->rule_str) == 0) { + if (cmd->func_free) + (*cmd->func_free)(compile); + + return RMAP_COMPILE_SUCCESS; + } + + /* If IPv4 or IPv6 prefix-list match criteria + * has been delete to the route-map index, update + * the route-map's prefix table. + */ + if (IS_RULE_IPv4_PREFIX_LIST(match_name)) + route_map_pfx_tbl_update( + RMAP_EVENT_PLIST_DELETED, index, AFI_IP, + rule->rule_str); + else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) + route_map_pfx_tbl_update( + RMAP_EVENT_PLIST_DELETED, index, + AFI_IP6, rule->rule_str); + + /* Remove the dependency of the route-map on the rule + * that is being replaced. + */ + if (type >= RMAP_EVENT_CALL_ADDED) { + delete_rmap_event_type = + get_route_map_delete_event(type); + route_map_upd8_dependency( + delete_rmap_event_type, + rule->rule_str, + index->map->name); + } + + route_map_rule_delete(&index->match_list, rule); + } + } + + /* Add new route map match rule. */ + rule = route_map_rule_new(); + rule->cmd = cmd; + rule->value = compile; + if (match_arg) + rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, match_arg); + else + rule->rule_str = NULL; + + /* Add new route match rule to linked list. */ + route_map_rule_add(&index->match_list, rule); + + /* If IPv4 or IPv6 prefix-list match criteria + * has been added to the route-map index, update + * the route-map's prefix table. + */ + if (IS_RULE_IPv4_PREFIX_LIST(match_name)) { + route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP, + match_arg); + } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) { + route_map_pfx_tbl_update(RMAP_EVENT_PLIST_ADDED, index, AFI_IP6, + match_arg); + } + + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(index->map->name); + route_map_notify_dependencies(index->map->name, + RMAP_EVENT_CALL_ADDED); + } + if (type != RMAP_EVENT_MATCH_ADDED) + route_map_upd8_dependency(type, rule_key, index->map->name); + + return RMAP_COMPILE_SUCCESS; +} + +/* Delete specified route match rule. */ +enum rmap_compile_rets route_map_delete_match(struct route_map_index *index, + const char *match_name, + const char *match_arg, + route_map_event_t type) +{ + struct route_map_rule *rule; + const struct route_map_rule_cmd *cmd; + const char *rule_key; + + cmd = route_map_lookup_match(match_name); + if (cmd == NULL) + return RMAP_RULE_MISSING; + + for (rule = index->match_list.head; rule; rule = rule->next) + if (rule->cmd == cmd && (rulecmp(rule->rule_str, match_arg) == 0 + || match_arg == NULL)) { + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(index->map->name); + route_map_notify_dependencies( + index->map->name, + RMAP_EVENT_CALL_ADDED); + } + if (cmd->func_get_rmap_rule_key) + rule_key = (*cmd->func_get_rmap_rule_key) + (rule->value); + else + rule_key = match_arg; + + if (type != RMAP_EVENT_MATCH_DELETED && rule_key) + route_map_upd8_dependency(type, rule_key, + index->map->name); + + route_map_rule_delete(&index->match_list, rule); + + /* If IPv4 or IPv6 prefix-list match criteria + * has been delete from the route-map index, update + * the route-map's prefix table. + */ + if (IS_RULE_IPv4_PREFIX_LIST(match_name)) { + route_map_pfx_tbl_update( + RMAP_EVENT_PLIST_DELETED, index, AFI_IP, + match_arg); + } else if (IS_RULE_IPv6_PREFIX_LIST(match_name)) { + route_map_pfx_tbl_update( + RMAP_EVENT_PLIST_DELETED, index, + AFI_IP6, match_arg); + } + + return RMAP_COMPILE_SUCCESS; + } + /* Can't find matched rule. */ + return RMAP_RULE_MISSING; +} + +/* Add route-map set statement to the route map. */ +enum rmap_compile_rets route_map_add_set(struct route_map_index *index, + const char *set_name, + const char *set_arg) +{ + struct route_map_rule *rule; + struct route_map_rule *next; + const struct route_map_rule_cmd *cmd; + void *compile; + + cmd = route_map_lookup_set(set_name); + if (cmd == NULL) + return RMAP_RULE_MISSING; + + /* Next call compile function for this match statement. */ + if (cmd->func_compile) { + compile = (*cmd->func_compile)(set_arg); + if (compile == NULL) + return RMAP_COMPILE_ERROR; + } else + compile = NULL; + + /* Add by WJL. if old set command of same kind exist, delete it first + to ensure only one set command of same kind exist under a + route_map_index. */ + for (rule = index->set_list.head; rule; rule = next) { + next = rule->next; + if (rule->cmd == cmd) + route_map_rule_delete(&index->set_list, rule); + } + + /* Add new route map match rule. */ + rule = route_map_rule_new(); + rule->cmd = cmd; + rule->value = compile; + if (set_arg) + rule->rule_str = XSTRDUP(MTYPE_ROUTE_MAP_RULE_STR, set_arg); + else + rule->rule_str = NULL; + + /* Add new route match rule to linked list. */ + route_map_rule_add(&index->set_list, rule); + + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(index->map->name); + route_map_notify_dependencies(index->map->name, + RMAP_EVENT_CALL_ADDED); + } + return RMAP_COMPILE_SUCCESS; +} + +/* Delete route map set rule. */ +enum rmap_compile_rets route_map_delete_set(struct route_map_index *index, + const char *set_name, + const char *set_arg) +{ + struct route_map_rule *rule; + const struct route_map_rule_cmd *cmd; + + cmd = route_map_lookup_set(set_name); + if (cmd == NULL) + return RMAP_RULE_MISSING; + + for (rule = index->set_list.head; rule; rule = rule->next) + if ((rule->cmd == cmd) && (rulecmp(rule->rule_str, set_arg) == 0 + || set_arg == NULL)) { + route_map_rule_delete(&index->set_list, rule); + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(index->map->name); + route_map_notify_dependencies( + index->map->name, + RMAP_EVENT_CALL_ADDED); + } + return RMAP_COMPILE_SUCCESS; + } + /* Can't find matched rule. */ + return RMAP_RULE_MISSING; +} + +static enum route_map_cmd_result_t +route_map_apply_match(struct route_map_rule_list *match_list, + const struct prefix *prefix, void *object) +{ + enum route_map_cmd_result_t ret = RMAP_NOMATCH; + struct route_map_rule *match; + bool is_matched = false; + + + /* Check all match rule and if there is no match rule, go to the + set statement. */ + if (!match_list->head) + ret = RMAP_MATCH; + else { + for (match = match_list->head; match; match = match->next) { + /* + * Try each match statement. If any match does not + * return RMAP_MATCH or RMAP_NOOP, return. + * Otherwise continue on to next match statement. + * All match statements must MATCH for + * end-result to be a match. + * (Exception:If match stmts result in a mix of + * MATCH/NOOP, then also end-result is a match) + * If all result in NOOP, end-result is NOOP. + */ + ret = (*match->cmd->func_apply)(match->value, prefix, + object); + + /* + * If the consolidated result of func_apply is: + * ----------------------------------------------- + * | MATCH | NOMATCH | NOOP | Final Result | + * ------------------------------------------------ + * | yes | yes | yes | NOMATCH | + * | no | no | yes | NOOP | + * | yes | no | yes | MATCH | + * | no | yes | yes | NOMATCH | + * |----------------------------------------------- + * + * Traditionally, all rules within route-map + * should match for it to MATCH. + * If there are noops within the route-map rules, + * it follows the above matrix. + * + * Eg: route-map rm1 permit 10 + * match rule1 + * match rule2 + * match rule3 + * .... + * route-map rm1 permit 20 + * match ruleX + * match ruleY + * ... + */ + + switch (ret) { + case RMAP_MATCH: + is_matched = true; + break; + + case RMAP_NOMATCH: + return ret; + + case RMAP_NOOP: + if (is_matched) + ret = RMAP_MATCH; + break; + + case RMAP_OKAY: + case RMAP_ERROR: + break; + } + + } + } + return ret; +} + +static struct list *route_map_get_index_list(struct route_node **rn, + const struct prefix *prefix, + struct route_table *table) +{ + struct route_node *tmp_rn = NULL; + + if (!(*rn)) { + *rn = route_node_match(table, prefix); + + if (!(*rn)) + return NULL; + + if ((*rn)->info) + return (struct list *)((*rn)->info); + + /* If rn->info is NULL, get the parent. + * Store the rn in tmp_rn and unlock it later. + */ + tmp_rn = *rn; + } + + do { + *rn = (*rn)->parent; + if (tmp_rn) + route_unlock_node(tmp_rn); + + if (!(*rn)) + break; + + if ((*rn)->info) { + route_lock_node(*rn); + return (struct list *)((*rn)->info); + } + } while (!(*rn)->info); + + return NULL; +} + +/* + * This function returns the route-map index that best matches the prefix. + */ +static struct route_map_index * +route_map_get_index(struct route_map *map, const struct prefix *prefix, + void *object, enum route_map_cmd_result_t *match_ret) +{ + enum route_map_cmd_result_t ret = RMAP_NOMATCH; + struct list *candidate_rmap_list = NULL; + struct route_node *rn = NULL; + struct listnode *ln = NULL, *nn = NULL; + struct route_map_index *index = NULL, *best_index = NULL; + struct route_map_index *head_index = NULL; + struct route_table *table = NULL; + + /* Route-map optimization relies on LPM lookups of the prefix to reduce + * the amount of route-map clauses a given prefix needs to be processed + * against. These LPM trees are IPv4/IPv6-specific and prefix->family + * must be AF_INET or AF_INET6 in order for the lookup to succeed. So if + * the AF doesn't line up with the LPM trees, skip the optimization. + */ + if (map->optimization_disabled) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "Skipping route-map optimization for route-map: %s, pfx: %pFX, family: %d", + map->name, prefix, prefix->family); + return map->head; + } + + if (prefix->family == AF_INET) + table = map->ipv4_prefix_table; + else + table = map->ipv6_prefix_table; + + do { + candidate_rmap_list = + route_map_get_index_list(&rn, prefix, table); + if (!rn) + break; + + /* If the index at the head of the list is of seq higher + * than that in best_index, ignore the list and get the + * parent node's list. + */ + head_index = (struct route_map_index *)(listgetdata( + listhead(candidate_rmap_list))); + if (best_index && head_index + && (best_index->pref < head_index->pref)) { + route_unlock_node(rn); + continue; + } + + for (ALL_LIST_ELEMENTS(candidate_rmap_list, ln, nn, index)) { + /* If the index is of seq higher than that in + * best_index, ignore the list and get the parent + * node's list. + */ + if (best_index && (best_index->pref < index->pref)) + break; + + ret = route_map_apply_match(&index->match_list, prefix, + object); + + if (ret == RMAP_MATCH) { + *match_ret = ret; + best_index = index; + break; + } else if (ret == RMAP_NOOP) { + /* + * If match_ret is denymatch, even if we see + * more noops, we retain this return value and + * return this eventually if there are no + * matches. + * If a best match route-map index already + * exists, do not reset the match_ret. + */ + if (!best_index && (*match_ret != RMAP_NOMATCH)) + *match_ret = ret; + } else { + /* + * ret is RMAP_NOMATCH. + * If a best match route-map index already + * exists, do not reset the match_ret. + */ + if (!best_index) + *match_ret = ret; + } + } + + route_unlock_node(rn); + + } while (rn); + + return best_index; +} + +static int route_map_candidate_list_cmp(struct route_map_index *idx1, + struct route_map_index *idx2) +{ + return idx1->pref - idx2->pref; +} + +/* + * This function adds the route-map index into the default route's + * route-node in the route-map's IPv4/IPv6 prefix-table. + */ +static void route_map_pfx_table_add_default(afi_t afi, + struct route_map_index *index) +{ + struct route_node *rn = NULL; + struct list *rmap_candidate_list = NULL; + struct prefix p; + bool updated_rn = false; + struct route_table *table = NULL; + + memset(&p, 0, sizeof(p)); + p.family = afi2family(afi); + p.prefixlen = 0; + + if (p.family == AF_INET) + table = index->map->ipv4_prefix_table; + else + table = index->map->ipv6_prefix_table; + + /* Add default route to table */ + rn = route_node_get(table, &p); + + if (!rn) + return; + + if (!rn->info) { + rmap_candidate_list = list_new(); + rmap_candidate_list->cmp = + (int (*)(void *, void *))route_map_candidate_list_cmp; + rn->info = rmap_candidate_list; + } else { + rmap_candidate_list = (struct list *)rn->info; + updated_rn = true; + } + + listnode_add_sort_nodup(rmap_candidate_list, index); + if (updated_rn) + route_unlock_node(rn); +} + +/* + * This function removes the route-map index from the default route's + * route-node in the route-map's IPv4/IPv6 prefix-table. + */ +static void route_map_pfx_table_del_default(afi_t afi, + struct route_map_index *index) +{ + struct route_node *rn = NULL; + struct list *rmap_candidate_list = NULL; + struct prefix p; + struct route_table *table = NULL; + + memset(&p, 0, sizeof(p)); + p.family = afi2family(afi); + p.prefixlen = 0; + + if (p.family == AF_INET) + table = index->map->ipv4_prefix_table; + else + table = index->map->ipv6_prefix_table; + + /* Remove RMAP index from default route in table */ + rn = route_node_lookup(table, &p); + if (!rn || !rn->info) + return; + + rmap_candidate_list = (struct list *)rn->info; + + listnode_delete(rmap_candidate_list, index); + + if (listcount(rmap_candidate_list) == 0) { + list_delete(&rmap_candidate_list); + rn->info = NULL; + route_unlock_node(rn); + } + route_unlock_node(rn); +} + +/* + * This function adds the route-map index to the route-node for + * the prefix-entry in the route-map's IPv4/IPv6 prefix-table. + */ +static void route_map_pfx_table_add(struct route_table *table, + struct route_map_index *index, + struct prefix_list_entry *pentry) +{ + struct route_node *rn = NULL; + struct list *rmap_candidate_list = NULL; + bool updated_rn = false; + + rn = route_node_get(table, &pentry->prefix); + if (!rn) + return; + + if (!rn->info) { + rmap_candidate_list = list_new(); + rmap_candidate_list->cmp = + (int (*)(void *, void *))route_map_candidate_list_cmp; + rn->info = rmap_candidate_list; + } else { + rmap_candidate_list = (struct list *)rn->info; + updated_rn = true; + } + + listnode_add_sort_nodup(rmap_candidate_list, index); + if (updated_rn) + route_unlock_node(rn); +} + +/* + * This function removes the route-map index from the route-node for + * the prefix-entry in the route-map's IPv4/IPv6 prefix-table. + */ +static void route_map_pfx_table_del(struct route_table *table, + struct route_map_index *index, + struct prefix_list_entry *pentry) +{ + struct route_node *rn = NULL; + struct list *rmap_candidate_list = NULL; + + rn = route_node_lookup(table, &pentry->prefix); + if (!rn || !rn->info) + return; + + rmap_candidate_list = (struct list *)rn->info; + + listnode_delete(rmap_candidate_list, index); + + if (listcount(rmap_candidate_list) == 0) { + list_delete(&rmap_candidate_list); + rn->info = NULL; + route_unlock_node(rn); + } + route_unlock_node(rn); +} + +/* This function checks for the presence of an IPv4 prefix-list + * match rule in the given route-map index. + */ +static bool route_map_is_ip_pfx_list_rule_present(struct route_map_index *index) +{ + struct route_map_rule_list *match_list = NULL; + struct route_map_rule *rule = NULL; + + match_list = &index->match_list; + for (rule = match_list->head; rule; rule = rule->next) + if (IS_RULE_IPv4_PREFIX_LIST(rule->cmd->str)) + return true; + + return false; +} + +/* This function checks for the presence of an IPv6 prefix-list + * match rule in the given route-map index. + */ +static bool +route_map_is_ipv6_pfx_list_rule_present(struct route_map_index *index) +{ + struct route_map_rule_list *match_list = NULL; + struct route_map_rule *rule = NULL; + + match_list = &index->match_list; + for (rule = match_list->head; rule; rule = rule->next) + if (IS_RULE_IPv6_PREFIX_LIST(rule->cmd->str)) + return true; + + return false; +} + +/* This function does the following: + * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list + * match clause (based on the afi passed to this foo) and get the + * prefix-list name. + * 2) Look up the prefix-list using the name. + * 3) If the prefix-list is not found then, add the index to the IPv4/IPv6 + * default-route's node in the trie (based on the afi passed to this foo). + * 4) If the prefix-list is found then, remove the index from the IPv4/IPv6 + * default-route's node in the trie (based on the afi passed to this foo). + * 5) If a prefix-entry is passed then, create a route-node for this entry and + * add this index to the route-node. + * 6) If prefix-entry is not passed then, for every prefix-entry in the + * prefix-list, create a route-node for this entry and + * add this index to the route-node. + */ +static void route_map_add_plist_entries(afi_t afi, + struct route_map_index *index, + const char *plist_name, + struct prefix_list_entry *entry) +{ + struct route_map_rule_list *match_list = NULL; + struct route_map_rule *match = NULL; + struct prefix_list *plist = NULL; + struct prefix_list_entry *pentry = NULL; + bool plist_rule_is_present = false; + + if (!plist_name) { + match_list = &index->match_list; + + for (match = match_list->head; match; match = match->next) { + if (afi == AFI_IP) { + if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) { + plist_rule_is_present = true; + break; + } + } else { + if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) { + plist_rule_is_present = true; + break; + } + } + } + + if (plist_rule_is_present) + plist = prefix_list_lookup(afi, match->rule_str); + } else { + plist = prefix_list_lookup(afi, plist_name); + } + + if (!plist) { + route_map_pfx_table_add_default(afi, index); + return; + } + + /* Default entry should be deleted only if the first entry of the + * prefix-list is created. + */ + if (entry) { + if (plist->count == 1) + route_map_pfx_table_del_default(afi, index); + } else { + route_map_pfx_table_del_default(afi, index); + } + + if (entry) { + if (afi == AFI_IP) { + route_map_pfx_table_add(index->map->ipv4_prefix_table, + index, entry); + } else { + route_map_pfx_table_add(index->map->ipv6_prefix_table, + index, entry); + } + } else { + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (afi == AFI_IP) { + route_map_pfx_table_add( + index->map->ipv4_prefix_table, index, + pentry); + } else { + route_map_pfx_table_add( + index->map->ipv6_prefix_table, index, + pentry); + } + } + } +} + +/* This function does the following: + * 1) If plist_name is not present, search for a IPv4 or IPv6 prefix-list + * match clause (based on the afi passed to this foo) and get the + * prefix-list name. + * 2) Look up the prefix-list using the name. + * 3) If the prefix-list is not found then, delete the index from the IPv4/IPv6 + * default-route's node in the trie (based on the afi passed to this foo). + * 4) If a prefix-entry is passed then, remove this index from the route-node + * for the prefix in this prefix-entry. + * 5) If prefix-entry is not passed then, for every prefix-entry in the + * prefix-list, remove this index from the route-node + * for the prefix in this prefix-entry. + */ +static void route_map_del_plist_entries(afi_t afi, + struct route_map_index *index, + const char *plist_name, + struct prefix_list_entry *entry) +{ + struct route_map_rule_list *match_list = NULL; + struct route_map_rule *match = NULL; + struct prefix_list *plist = NULL; + struct prefix_list_entry *pentry = NULL; + bool plist_rule_is_present = false; + + if (!plist_name) { + match_list = &index->match_list; + + for (match = match_list->head; match; match = match->next) { + if (afi == AFI_IP) { + if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str)) { + plist_rule_is_present = true; + break; + } + } else { + if (IS_RULE_IPv6_PREFIX_LIST(match->cmd->str)) { + plist_rule_is_present = true; + break; + } + } + } + + if (plist_rule_is_present) + plist = prefix_list_lookup(afi, match->rule_str); + } else { + plist = prefix_list_lookup(afi, plist_name); + } + + if (!plist) { + route_map_pfx_table_del_default(afi, index); + return; + } + + if (entry) { + if (afi == AFI_IP) { + route_map_pfx_table_del(index->map->ipv4_prefix_table, + index, entry); + } else { + route_map_pfx_table_del(index->map->ipv6_prefix_table, + index, entry); + } + } else { + for (pentry = plist->head; pentry; pentry = pentry->next) { + if (afi == AFI_IP) { + route_map_pfx_table_del( + index->map->ipv4_prefix_table, index, + pentry); + } else { + route_map_pfx_table_del( + index->map->ipv6_prefix_table, index, + pentry); + } + } + } +} + +/* + * This function handles the cases where a prefix-list is added/removed + * as a match command from a particular route-map index. + * It updates the prefix-table of the route-map accordingly. + */ +static void route_map_trie_update(afi_t afi, route_map_event_t event, + struct route_map_index *index, + const char *plist_name) +{ + if (event == RMAP_EVENT_PLIST_ADDED) { + if (afi == AFI_IP) { + if (!route_map_is_ipv6_pfx_list_rule_present(index)) { + route_map_pfx_table_del_default(AFI_IP6, index); + route_map_add_plist_entries(afi, index, + plist_name, NULL); + } else { + route_map_del_plist_entries(AFI_IP6, index, + NULL, NULL); + } + } else { + if (!route_map_is_ip_pfx_list_rule_present(index)) { + route_map_pfx_table_del_default(AFI_IP, index); + route_map_add_plist_entries(afi, index, + plist_name, NULL); + } else { + route_map_del_plist_entries(AFI_IP, index, NULL, + NULL); + } + } + } else if (event == RMAP_EVENT_PLIST_DELETED) { + if (afi == AFI_IP) { + route_map_del_plist_entries(afi, index, plist_name, + NULL); + + /* If IPv6 prefix-list match rule is not present, + * add this index to the IPv4 default route's trie + * node. + * Also, add this index to the trie nodes created + * for each of the prefix-entries within the IPv6 + * prefix-list, if the IPv6 prefix-list match rule + * is present. Else, add this index to the IPv6 + * default route's trie node. + */ + if (!route_map_is_ipv6_pfx_list_rule_present(index)) + route_map_pfx_table_add_default(afi, index); + + route_map_add_plist_entries(AFI_IP6, index, NULL, NULL); + } else { + route_map_del_plist_entries(afi, index, plist_name, + NULL); + + /* If IPv4 prefix-list match rule is not present, + * add this index to the IPv6 default route's trie + * node. + * Also, add this index to the trie nodes created + * for each of the prefix-entries within the IPv4 + * prefix-list, if the IPv4 prefix-list match rule + * is present. Else, add this index to the IPv4 + * default route's trie node. + */ + if (!route_map_is_ip_pfx_list_rule_present(index)) + route_map_pfx_table_add_default(afi, index); + + route_map_add_plist_entries(AFI_IP, index, NULL, NULL); + } + } +} + +/* + * This function handles the cases where a route-map index and + * prefix-list is added/removed. + * It updates the prefix-table of the route-map accordingly. + */ +static void route_map_pfx_tbl_update(route_map_event_t event, + struct route_map_index *index, afi_t afi, + const char *plist_name) +{ + if (!index) + return; + + if (event == RMAP_EVENT_INDEX_ADDED) { + route_map_pfx_table_add_default(AFI_IP, index); + route_map_pfx_table_add_default(AFI_IP6, index); + return; + } + + if (event == RMAP_EVENT_INDEX_DELETED) { + route_map_pfx_table_del_default(AFI_IP, index); + route_map_pfx_table_del_default(AFI_IP6, index); + + return; + } + + /* Handle prefix-list match rule addition/deletion. + */ + route_map_trie_update(afi, event, index, plist_name); +} + +/* + * This function handles the cases where a new prefix-entry is added to + * a prefix-list or, an existing prefix-entry is removed from the prefix-list. + * It updates the prefix-table of the route-map accordingly. + */ +static void route_map_pentry_update(route_map_event_t event, + const char *plist_name, + struct route_map_index *index, + struct prefix_list_entry *pentry) +{ + struct prefix_list *plist = NULL; + afi_t afi; + unsigned char family = pentry->prefix.family; + + if (family == AF_INET) { + afi = AFI_IP; + plist = prefix_list_lookup(AFI_IP, plist_name); + } else { + afi = AFI_IP6; + plist = prefix_list_lookup(AFI_IP6, plist_name); + } + + if (event == RMAP_EVENT_PLIST_ADDED) { + if (afi == AFI_IP) { + if (!route_map_is_ipv6_pfx_list_rule_present(index)) + route_map_add_plist_entries(afi, index, + plist_name, pentry); + } else { + if (!route_map_is_ip_pfx_list_rule_present(index)) + route_map_add_plist_entries(afi, index, + plist_name, pentry); + } + } else if (event == RMAP_EVENT_PLIST_DELETED) { + route_map_del_plist_entries(afi, index, plist_name, pentry); + + if (plist->count == 1) { + if (afi == AFI_IP) { + if (!route_map_is_ipv6_pfx_list_rule_present( + index)) + route_map_pfx_table_add_default(afi, + index); + } else { + if (!route_map_is_ip_pfx_list_rule_present( + index)) + route_map_pfx_table_add_default(afi, + index); + } + } + } +} + +static void route_map_pentry_process_dependency(struct hash_bucket *bucket, + void *data) +{ + char *rmap_name = NULL; + struct route_map *rmap = NULL; + struct route_map_index *index = NULL; + struct route_map_rule_list *match_list = NULL; + struct route_map_rule *match = NULL; + struct route_map_dep_data *dep_data = NULL; + struct route_map_pentry_dep *pentry_dep = + (struct route_map_pentry_dep *)data; + unsigned char family = pentry_dep->pentry->prefix.family; + + dep_data = (struct route_map_dep_data *)bucket->data; + if (!dep_data) + return; + + rmap_name = dep_data->rname; + rmap = route_map_lookup_by_name(rmap_name); + if (!rmap || !rmap->head) + return; + + for (index = rmap->head; index; index = index->next) { + match_list = &index->match_list; + + if (!match_list) + continue; + + for (match = match_list->head; match; match = match->next) { + if (strcmp(match->rule_str, pentry_dep->plist_name) + == 0) { + if (IS_RULE_IPv4_PREFIX_LIST(match->cmd->str) + && family == AF_INET) { + route_map_pentry_update( + pentry_dep->event, + pentry_dep->plist_name, index, + pentry_dep->pentry); + } else if (IS_RULE_IPv6_PREFIX_LIST( + match->cmd->str) + && family == AF_INET6) { + route_map_pentry_update( + pentry_dep->event, + pentry_dep->plist_name, index, + pentry_dep->pentry); + } + } + } + } +} + +void route_map_notify_pentry_dependencies(const char *affected_name, + struct prefix_list_entry *pentry, + route_map_event_t event) +{ + struct route_map_dep *dep = NULL; + struct hash *upd8_hash = NULL; + struct route_map_pentry_dep pentry_dep; + + if (!affected_name || !pentry) + return; + + upd8_hash = route_map_get_dep_hash(event); + if (!upd8_hash) + return; + + dep = (struct route_map_dep *)hash_get(upd8_hash, (void *)affected_name, + NULL); + if (dep) { + if (!dep->this_hash) + dep->this_hash = upd8_hash; + + memset(&pentry_dep, 0, sizeof(pentry_dep)); + pentry_dep.pentry = pentry; + pentry_dep.plist_name = affected_name; + pentry_dep.event = event; + + hash_iterate(dep->dep_rmap_hash, + route_map_pentry_process_dependency, + (void *)&pentry_dep); + } +} + +/* Apply route map's each index to the object. + + The matrix for a route-map looks like this: + (note, this includes the description for the "NEXT" + and "GOTO" frobs now + + | Match | No Match | No op + |-----------|--------------|------- + permit | action | cont | cont. + | | default:deny | default:permit + -------------------+----------------------- + | deny | cont | cont. + deny | | default:deny | default:permit + |-----------|--------------|-------- + + action) + -Apply Set statements, accept route + -If Call statement is present jump to the specified route-map, if it + denies the route we finish. + -If NEXT is specified, goto NEXT statement + -If GOTO is specified, goto the first clause where pref > nextpref + -If nothing is specified, do as Cisco and finish + deny) + -Route is denied by route-map. + cont) + -Goto Next index + + If we get no matches after we've processed all updates, then the route + is dropped too. + + Some notes on the new "CALL", "NEXT" and "GOTO" + call WORD - If this clause is matched, then the set statements + are executed and then we jump to route-map 'WORD'. If + this route-map denies the route, we finish, in other + case we + do whatever the exit policy (EXIT, NEXT or GOTO) tells. + on-match next - If this clause is matched, then the set statements + are executed and then we drop through to the next clause + on-match goto n - If this clause is matched, then the set statements + are executed and then we goto the nth clause, or the + first clause greater than this. In order to ensure + route-maps *always* exit, you cannot jump backwards. + Sorry ;) + + We need to make sure our route-map processing matches the above +*/ +route_map_result_t route_map_apply_ext(struct route_map *map, + const struct prefix *prefix, + void *match_object, void *set_object, + int *pref) +{ + static int recursion = 0; + enum route_map_cmd_result_t match_ret = RMAP_NOMATCH; + route_map_result_t ret = RMAP_PERMITMATCH; + struct route_map_index *index = NULL; + struct route_map_rule *set = NULL; + bool skip_match_clause = false; + + if (recursion > RMAP_RECURSION_LIMIT) { + if (map) + map->applied++; + + flog_warn( + EC_LIB_RMAP_RECURSION_LIMIT, + "route-map recursion limit (%d) reached, discarding route", + RMAP_RECURSION_LIMIT); + recursion = 0; + return RMAP_DENYMATCH; + } + + if (map == NULL || map->head == NULL) { + if (map) + map->applied++; + ret = RMAP_DENYMATCH; + goto route_map_apply_end; + } + + map->applied++; + + if (prefix->family == AF_EVPN) { + index = map->head; + } else { + skip_match_clause = true; + index = route_map_get_index(map, prefix, match_object, + &match_ret); + } + + if (index) { + index->applied++; + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug( + "Best match route-map: %s, sequence: %d for pfx: %pFX, result: %s", + map->name, index->pref, prefix, + route_map_cmd_result_str(match_ret)); + } else { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug( + "No best match sequence for pfx: %pFX in route-map: %s, result: %s", + prefix, map->name, + route_map_cmd_result_str(match_ret)); + /* + * No index matches this prefix. Return deny unless, + * match_ret = RMAP_NOOP. + */ + if (match_ret == RMAP_NOOP) + ret = RMAP_PERMITMATCH; + else + ret = RMAP_DENYMATCH; + goto route_map_apply_end; + } + + for (; index; index = index->next) { + if (!skip_match_clause) { + index->applied++; + /* Apply this index. */ + match_ret = route_map_apply_match(&index->match_list, + prefix, match_object); + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) { + zlog_debug( + "Route-map: %s, sequence: %d, prefix: %pFX, result: %s", + map->name, index->pref, prefix, + route_map_cmd_result_str(match_ret)); + } + } else + skip_match_clause = false; + + + /* Now we apply the matrix from above */ + if (match_ret == RMAP_NOOP) + /* + * Do not change the return value. Retain the previous + * return value. Previous values can be: + * 1)permitmatch (if a nomatch was never + * seen before in this route-map.) + * 2)denymatch (if a nomatch was seen earlier in one + * of the previous sequences) + */ + + /* + * 'cont' from matrix - continue to next route-map + * sequence + */ + continue; + else if (match_ret == RMAP_NOMATCH) { + + /* + * The return value is now changed to denymatch. + * So from here on out, even if we see more noops, + * we retain this return value and return this + * eventually if there are no matches. + */ + ret = RMAP_DENYMATCH; + + /* + * 'cont' from matrix - continue to next route-map + * sequence + */ + continue; + } else if (match_ret == RMAP_MATCH) { + if (index->type == RMAP_PERMIT) + /* 'action' */ + { + /* Match succeeded, rmap is of type permit */ + ret = RMAP_PERMITMATCH; + + /* permit+match must execute sets */ + for (set = index->set_list.head; set; + set = set->next) + /* + * set cmds return RMAP_OKAY or + * RMAP_ERROR. We do not care if + * set succeeded or not. So, ignore + * return code. + */ + (void)(*set->cmd->func_apply)( + set->value, prefix, set_object); + + /* Call another route-map if available */ + if (index->nextrm) { + struct route_map *nextrm = + route_map_lookup_by_name( + index->nextrm); + + if (nextrm) /* Target route-map found, + jump to it */ + { + recursion++; + ret = route_map_apply_ext( + nextrm, prefix, + match_object, + set_object, NULL); + recursion--; + } + + /* If nextrm returned 'deny', finish. */ + if (ret == RMAP_DENYMATCH) + goto route_map_apply_end; + } + + switch (index->exitpolicy) { + case RMAP_EXIT: + goto route_map_apply_end; + case RMAP_NEXT: + continue; + case RMAP_GOTO: { + /* Find the next clause to jump to */ + struct route_map_index *next = + index->next; + int nextpref = index->nextpref; + + while (next && next->pref < nextpref) { + index = next; + next = next->next; + } + if (next == NULL) { + /* No clauses match! */ + goto route_map_apply_end; + } + } + } + } else if (index->type == RMAP_DENY) + /* 'deny' */ + { + ret = RMAP_DENYMATCH; + goto route_map_apply_end; + } + } + } + +route_map_apply_end: + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Route-map: %s, prefix: %pFX, result: %s", + (map ? map->name : "null"), prefix, + route_map_result_str(ret)); + + if (pref) { + if (index != NULL && ret == RMAP_PERMITMATCH) + *pref = index->pref; + else + *pref = 65536; + } + + return (ret); +} + +void route_map_add_hook(void (*func)(const char *)) +{ + route_map_master.add_hook = func; +} + +void route_map_delete_hook(void (*func)(const char *)) +{ + route_map_master.delete_hook = func; +} + +void route_map_event_hook(void (*func)(const char *name)) +{ + route_map_master.event_hook = func; +} + +/* Routines for route map dependency lists and dependency processing */ +static bool route_map_rmap_hash_cmp(const void *p1, const void *p2) +{ + return strcmp(((const struct route_map_dep_data *)p1)->rname, + ((const struct route_map_dep_data *)p2)->rname) + == 0; +} + +static bool route_map_dep_hash_cmp(const void *p1, const void *p2) +{ + + return (strcmp(((const struct route_map_dep *)p1)->dep_name, + (const char *)p2) + == 0); +} + +static void route_map_clear_reference(struct hash_bucket *bucket, void *arg) +{ + struct route_map_dep *dep = bucket->data; + struct route_map_dep_data *dep_data = NULL, tmp_dep_data; + + memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); + tmp_dep_data.rname = arg; + dep_data = hash_release(dep->dep_rmap_hash, &tmp_dep_data); + if (dep_data) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Clearing reference for %s to %s count: %d", + dep->dep_name, tmp_dep_data.rname, + dep_data->refcnt); + + XFREE(MTYPE_ROUTE_MAP_NAME, dep_data->rname); + XFREE(MTYPE_ROUTE_MAP_DEP_DATA, dep_data); + } + if (!dep->dep_rmap_hash->count) { + dep = hash_release(dep->this_hash, (void *)dep->dep_name); + hash_free(dep->dep_rmap_hash); + XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); + XFREE(MTYPE_ROUTE_MAP_DEP, dep); + } +} + +static void route_map_clear_all_references(char *rmap_name) +{ + int i; + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Clearing references for %s", rmap_name); + + for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { + hash_iterate(route_map_dep_hash[i], route_map_clear_reference, + (void *)rmap_name); + } +} + +static unsigned int route_map_dep_data_hash_make_key(const void *p) +{ + const struct route_map_dep_data *dep_data = p; + + return string_hash_make(dep_data->rname); +} + +static void *route_map_dep_hash_alloc(void *p) +{ + char *dep_name = (char *)p; + struct route_map_dep *dep_entry; + + dep_entry = XCALLOC(MTYPE_ROUTE_MAP_DEP, sizeof(struct route_map_dep)); + dep_entry->dep_name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); + dep_entry->dep_rmap_hash = + hash_create_size(8, route_map_dep_data_hash_make_key, + route_map_rmap_hash_cmp, "Route Map Dep Hash"); + dep_entry->this_hash = NULL; + + return dep_entry; +} + +static void *route_map_name_hash_alloc(void *p) +{ + struct route_map_dep_data *dep_data = NULL, *tmp_dep_data = NULL; + + dep_data = XCALLOC(MTYPE_ROUTE_MAP_DEP_DATA, + sizeof(struct route_map_dep_data)); + tmp_dep_data = p; + dep_data->rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, tmp_dep_data->rname); + return dep_data; +} + +static unsigned int route_map_dep_hash_make_key(const void *p) +{ + return (string_hash_make((char *)p)); +} + +static void route_map_print_dependency(struct hash_bucket *bucket, void *data) +{ + struct route_map_dep_data *dep_data = bucket->data; + char *rmap_name = dep_data->rname; + char *dep_name = data; + + zlog_debug("%s: Dependency for %s: %s", __func__, dep_name, rmap_name); +} + +static int route_map_dep_update(struct hash *dephash, const char *dep_name, + const char *rmap_name, route_map_event_t type) +{ + struct route_map_dep *dep = NULL; + char *dname, *rname; + int ret = 0; + struct route_map_dep_data *dep_data = NULL, *ret_dep_data = NULL; + struct route_map_dep_data tmp_dep_data; + + dname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, dep_name); + rname = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap_name); + + switch (type) { + case RMAP_EVENT_PLIST_ADDED: + case RMAP_EVENT_CLIST_ADDED: + case RMAP_EVENT_ECLIST_ADDED: + case RMAP_EVENT_ASLIST_ADDED: + case RMAP_EVENT_LLIST_ADDED: + case RMAP_EVENT_CALL_ADDED: + case RMAP_EVENT_FILTER_ADDED: + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Adding dependency for filter %s in route-map %s", + dep_name, rmap_name); + dep = (struct route_map_dep *)hash_get( + dephash, dname, route_map_dep_hash_alloc); + if (!dep) { + ret = -1; + goto out; + } + + if (!dep->this_hash) + dep->this_hash = dephash; + + memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); + tmp_dep_data.rname = rname; + dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); + if (!dep_data) + dep_data = hash_get(dep->dep_rmap_hash, &tmp_dep_data, + route_map_name_hash_alloc); + + dep_data->refcnt++; + break; + case RMAP_EVENT_PLIST_DELETED: + case RMAP_EVENT_CLIST_DELETED: + case RMAP_EVENT_ECLIST_DELETED: + case RMAP_EVENT_ASLIST_DELETED: + case RMAP_EVENT_LLIST_DELETED: + case RMAP_EVENT_CALL_DELETED: + case RMAP_EVENT_FILTER_DELETED: + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Deleting dependency for filter %s in route-map %s", + dep_name, rmap_name); + dep = (struct route_map_dep *)hash_get(dephash, dname, NULL); + if (!dep) { + goto out; + } + + memset(&tmp_dep_data, 0, sizeof(tmp_dep_data)); + tmp_dep_data.rname = rname; + dep_data = hash_lookup(dep->dep_rmap_hash, &tmp_dep_data); + /* + * If dep_data is NULL then something has gone seriously + * wrong in route-map handling. Note it and prevent + * the crash. + */ + if (!dep_data) { + zlog_warn( + "route-map dependency for route-map %s: %s is not correct", + rmap_name, dep_name); + goto out; + } + + dep_data->refcnt--; + + if (!dep_data->refcnt) { + ret_dep_data = hash_release(dep->dep_rmap_hash, + &tmp_dep_data); + if (ret_dep_data) { + XFREE(MTYPE_ROUTE_MAP_NAME, + ret_dep_data->rname); + XFREE(MTYPE_ROUTE_MAP_DEP_DATA, ret_dep_data); + } + } + + if (!dep->dep_rmap_hash->count) { + dep = hash_release(dephash, dname); + hash_free(dep->dep_rmap_hash); + XFREE(MTYPE_ROUTE_MAP_NAME, dep->dep_name); + XFREE(MTYPE_ROUTE_MAP_DEP, dep); + } + break; + case RMAP_EVENT_SET_ADDED: + case RMAP_EVENT_SET_DELETED: + case RMAP_EVENT_SET_REPLACED: + case RMAP_EVENT_MATCH_ADDED: + case RMAP_EVENT_MATCH_DELETED: + case RMAP_EVENT_MATCH_REPLACED: + case RMAP_EVENT_INDEX_ADDED: + case RMAP_EVENT_INDEX_DELETED: + break; + } + + if (dep) { + if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) + hash_iterate(dep->dep_rmap_hash, + route_map_print_dependency, dname); + } + +out: + XFREE(MTYPE_ROUTE_MAP_NAME, rname); + XFREE(MTYPE_ROUTE_MAP_NAME, dname); + return ret; +} + +static struct hash *route_map_get_dep_hash(route_map_event_t event) +{ + struct hash *upd8_hash = NULL; + + switch (event) { + case RMAP_EVENT_PLIST_ADDED: + case RMAP_EVENT_PLIST_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_PLIST]; + break; + case RMAP_EVENT_CLIST_ADDED: + case RMAP_EVENT_CLIST_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_CLIST]; + break; + case RMAP_EVENT_ECLIST_ADDED: + case RMAP_EVENT_ECLIST_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ECLIST]; + break; + case RMAP_EVENT_ASLIST_ADDED: + case RMAP_EVENT_ASLIST_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_ASPATH]; + break; + case RMAP_EVENT_LLIST_ADDED: + case RMAP_EVENT_LLIST_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_LCLIST]; + break; + case RMAP_EVENT_CALL_ADDED: + case RMAP_EVENT_CALL_DELETED: + case RMAP_EVENT_MATCH_ADDED: + case RMAP_EVENT_MATCH_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_RMAP]; + break; + case RMAP_EVENT_FILTER_ADDED: + case RMAP_EVENT_FILTER_DELETED: + upd8_hash = route_map_dep_hash[ROUTE_MAP_DEP_FILTER]; + break; + /* + * Should we actually be ignoring these? + * I am not sure but at this point in time, let + * us get them into this switch and we can peel + * them into the appropriate place in the future + */ + case RMAP_EVENT_SET_ADDED: + case RMAP_EVENT_SET_DELETED: + case RMAP_EVENT_SET_REPLACED: + case RMAP_EVENT_MATCH_REPLACED: + case RMAP_EVENT_INDEX_ADDED: + case RMAP_EVENT_INDEX_DELETED: + upd8_hash = NULL; + break; + } + return (upd8_hash); +} + +static void route_map_process_dependency(struct hash_bucket *bucket, void *data) +{ + struct route_map_dep_data *dep_data = NULL; + char *rmap_name = NULL; + + dep_data = bucket->data; + rmap_name = dep_data->rname; + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Notifying %s of dependency", rmap_name); + if (route_map_master.event_hook) + (*route_map_master.event_hook)(rmap_name); +} + +void route_map_upd8_dependency(route_map_event_t type, const char *arg, + const char *rmap_name) +{ + struct hash *upd8_hash = NULL; + + if ((upd8_hash = route_map_get_dep_hash(type))) { + route_map_dep_update(upd8_hash, arg, rmap_name, type); + + if (type == RMAP_EVENT_CALL_ADDED) { + /* Execute hook. */ + if (route_map_master.add_hook) + (*route_map_master.add_hook)(rmap_name); + } else if (type == RMAP_EVENT_CALL_DELETED) { + /* Execute hook. */ + if (route_map_master.delete_hook) + (*route_map_master.delete_hook)(rmap_name); + } + } +} + +void route_map_notify_dependencies(const char *affected_name, + route_map_event_t event) +{ + struct route_map_dep *dep; + struct hash *upd8_hash; + char *name; + + if (!affected_name) + return; + + name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, affected_name); + + if ((upd8_hash = route_map_get_dep_hash(event)) == NULL) { + XFREE(MTYPE_ROUTE_MAP_NAME, name); + return; + } + + dep = (struct route_map_dep *)hash_get(upd8_hash, name, NULL); + if (dep) { + if (!dep->this_hash) + dep->this_hash = upd8_hash; + + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP))) + zlog_debug("Filter %s updated", dep->dep_name); + hash_iterate(dep->dep_rmap_hash, route_map_process_dependency, + (void *)event); + } + + XFREE(MTYPE_ROUTE_MAP_NAME, name); +} + +/* VTY related functions. */ +static void clear_route_map_helper(struct route_map *map) +{ + struct route_map_index *index; + + map->applied_clear = map->applied; + for (index = map->head; index; index = index->next) + index->applied_clear = index->applied; +} + +DEFPY (rmap_clear_counters, + rmap_clear_counters_cmd, + "clear route-map counters [RMAP_NAME$rmapname]", + CLEAR_STR + "route-map information\n" + "counters associated with the specified route-map\n" + "route-map name\n") +{ + struct route_map *map; + + if (rmapname) { + map = route_map_lookup_by_name(rmapname); + + if (map) + clear_route_map_helper(map); + else { + vty_out(vty, "%s: 'route-map %s' not found\n", + frr_protonameinst, rmapname); + return CMD_SUCCESS; + } + } else { + for (map = route_map_master.head; map; map = map->next) + clear_route_map_helper(map); + } + + return CMD_SUCCESS; + +} + +DEFUN_NOSH (rmap_show_name, + rmap_show_name_cmd, + "show route-map [WORD] [json]", + SHOW_STR + "route-map information\n" + "route-map name\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx = 0; + const char *name = NULL; + + if (argv_find(argv, argc, "WORD", &idx)) + name = argv[idx]->arg; + + return vty_show_route_map(vty, name, uj); +} + +DEFUN (rmap_show_unused, + rmap_show_unused_cmd, + "show route-map-unused", + SHOW_STR + "unused route-map information\n") +{ + return vty_show_unused_route_map(vty); +} + +DEFPY (debug_rmap, + debug_rmap_cmd, + "debug route-map [detail]$detail", + DEBUG_STR + "Debug option set for route-maps\n" + "Detailed output\n") +{ + if (!detail) + SET_FLAG(rmap_debug, DEBUG_ROUTEMAP); + else + SET_FLAG(rmap_debug, DEBUG_ROUTEMAP | DEBUG_ROUTEMAP_DETAIL); + + return CMD_SUCCESS; +} + +DEFPY (no_debug_rmap, + no_debug_rmap_cmd, + "no debug route-map [detail]$detail", + NO_STR + DEBUG_STR + "Debug option set for route-maps\n" + "Detailed output\n") +{ + if (!detail) + UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP); + else + UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP | DEBUG_ROUTEMAP_DETAIL); + + return CMD_SUCCESS; +} + +/* Debug node. */ +static int rmap_config_write_debug(struct vty *vty); +static struct cmd_node rmap_debug_node = { + .name = "route-map debug", + .node = RMAP_DEBUG_NODE, + .prompt = "", + .config_write = rmap_config_write_debug, +}; + +void route_map_show_debug(struct vty *vty) +{ + if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) + vty_out(vty, "debug route-map\n"); +} + +/* Configuration write function. */ +static int rmap_config_write_debug(struct vty *vty) +{ + int write = 0; + + if (CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP)) { + vty_out(vty, "debug route-map\n"); + write++; + } + + return write; +} + +/* Common route map rules */ + +void *route_map_rule_tag_compile(const char *arg) +{ + unsigned long int tmp; + char *endptr; + route_tag_t *tag; + + errno = 0; + tmp = strtoul(arg, &endptr, 0); + if (arg[0] == '\0' || *endptr != '\0' || errno || tmp > ROUTE_TAG_MAX) + return NULL; + + tag = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*tag)); + *tag = tmp; + + return tag; +} + +void route_map_rule_tag_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +void route_map_finish(void) +{ + int i; + struct route_map_rule_cmd_proxy *proxy; + + /* these 2 hash tables have INIT_HASH initializers, so the "default" + * state is "initialized & empty" => fini() followed by init() to + * return to that same state + */ + while ((proxy = rmap_cmd_name_pop(rmap_match_cmds))) + (void)proxy; + rmap_cmd_name_fini(rmap_match_cmds); + rmap_cmd_name_init(rmap_match_cmds); + + while ((proxy = rmap_cmd_name_pop(rmap_set_cmds))) + (void)proxy; + rmap_cmd_name_fini(rmap_set_cmds); + rmap_cmd_name_init(rmap_set_cmds); + + /* + * All protocols are setting these to NULL + * by default on shutdown( route_map_finish ) + * Why are we making them do this work? + */ + route_map_master.add_hook = NULL; + route_map_master.delete_hook = NULL; + route_map_master.event_hook = NULL; + + /* cleanup route_map */ + while (route_map_master.head) { + struct route_map *map = route_map_master.head; + map->to_be_processed = false; + route_map_delete(map); + } + + for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) { + hash_free(route_map_dep_hash[i]); + route_map_dep_hash[i] = NULL; + } + + hash_free(route_map_master_hash); + route_map_master_hash = NULL; +} + +/* Increment the use_count counter while attaching the route map */ +void route_map_counter_increment(struct route_map *map) +{ + if (map) + map->use_count++; +} + +/* Decrement the use_count counter while detaching the route map. */ +void route_map_counter_decrement(struct route_map *map) +{ + if (map) { + if (map->use_count <= 0) + return; + map->use_count--; + } +} + +DEFUN_HIDDEN(show_route_map_pfx_tbl, show_route_map_pfx_tbl_cmd, + "show route-map RMAP_NAME prefix-table", + SHOW_STR + "route-map\n" + "route-map name\n" + "internal prefix-table\n") +{ + const char *rmap_name = argv[2]->arg; + struct route_map *rmap = NULL; + struct route_table *rm_pfx_tbl4 = NULL; + struct route_table *rm_pfx_tbl6 = NULL; + struct route_node *rn = NULL, *prn = NULL; + struct list *rmap_index_list = NULL; + struct listnode *ln = NULL, *nln = NULL; + struct route_map_index *index = NULL; + uint8_t len = 54; + + vty_out(vty, "%s:\n", frr_protonameinst); + rmap = route_map_lookup_by_name(rmap_name); + if (rmap) { + rm_pfx_tbl4 = rmap->ipv4_prefix_table; + if (rm_pfx_tbl4) { + vty_out(vty, "\n%s%43s%s\n", "IPv4 Prefix", "", + "Route-map Index List"); + vty_out(vty, "%s%39s%s\n", "_______________", "", + "____________________"); + for (rn = route_top(rm_pfx_tbl4); rn; + rn = route_next(rn)) { + vty_out(vty, " %pRN (%d)\n", rn, + route_node_get_lock_count(rn)); + + vty_out(vty, "(P) "); + prn = rn->parent; + if (prn) { + vty_out(vty, "%pRN\n", prn); + } + + vty_out(vty, "\n"); + rmap_index_list = (struct list *)rn->info; + if (!rmap_index_list + || !listcount(rmap_index_list)) + vty_out(vty, "%*s%s\n", len, "", "-"); + else + for (ALL_LIST_ELEMENTS(rmap_index_list, + ln, nln, + index)) { + vty_out(vty, "%*s%s seq %d\n", + len, "", + index->map->name, + index->pref); + } + vty_out(vty, "\n"); + } + } + + rm_pfx_tbl6 = rmap->ipv6_prefix_table; + if (rm_pfx_tbl6) { + vty_out(vty, "\n%s%43s%s\n", "IPv6 Prefix", "", + "Route-map Index List"); + vty_out(vty, "%s%39s%s\n", "_______________", "", + "____________________"); + for (rn = route_top(rm_pfx_tbl6); rn; + rn = route_next(rn)) { + vty_out(vty, " %pRN (%d)\n", rn, + route_node_get_lock_count(rn)); + + vty_out(vty, "(P) "); + prn = rn->parent; + if (prn) { + vty_out(vty, "%pRN\n", prn); + } + + vty_out(vty, "\n"); + rmap_index_list = (struct list *)rn->info; + if (!rmap_index_list + || !listcount(rmap_index_list)) + vty_out(vty, "%*s%s\n", len, "", "-"); + else + for (ALL_LIST_ELEMENTS(rmap_index_list, + ln, nln, + index)) { + vty_out(vty, "%*s%s seq %d\n", + len, "", + index->map->name, + index->pref); + } + vty_out(vty, "\n"); + } + } + } + + vty_out(vty, "\n"); + return CMD_SUCCESS; +} + +/* Initialization of route map vector. */ +void route_map_init_new(bool in_backend) +{ + int i; + + route_map_master_hash = + hash_create_size(8, route_map_hash_key_make, route_map_hash_cmp, + "Route Map Master Hash"); + + for (i = 1; i < ROUTE_MAP_DEP_MAX; i++) + route_map_dep_hash[i] = hash_create_size( + 8, route_map_dep_hash_make_key, route_map_dep_hash_cmp, + "Route Map Dep Hash"); + + UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP); + + if (!in_backend) { + /* we do not want to handle config commands in the backend */ + route_map_cli_init(); + } + + /* Install route map top node. */ + install_node(&rmap_debug_node); + + /* Install route map commands. */ + install_element(CONFIG_NODE, &debug_rmap_cmd); + install_element(CONFIG_NODE, &no_debug_rmap_cmd); + + /* Install show command */ + install_element(ENABLE_NODE, &rmap_clear_counters_cmd); + + install_element(ENABLE_NODE, &rmap_show_name_cmd); + install_element(ENABLE_NODE, &rmap_show_unused_cmd); + + install_element(ENABLE_NODE, &debug_rmap_cmd); + install_element(ENABLE_NODE, &no_debug_rmap_cmd); + + install_element(ENABLE_NODE, &show_route_map_pfx_tbl_cmd); +} + +void route_map_init(void) +{ + route_map_init_new(false); +} diff --git a/lib/routemap.h b/lib/routemap.h new file mode 100644 index 0000000..dfb84ce --- /dev/null +++ b/lib/routemap.h @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Route map function. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_ROUTEMAP_H +#define _ZEBRA_ROUTEMAP_H + +#include "typesafe.h" +#include "prefix.h" +#include "memory.h" +#include "qobj.h" +#include "vty.h" +#include "lib/plist.h" +#include "lib/plist_int.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(ROUTE_MAP_NAME); +DECLARE_MTYPE(ROUTE_MAP_RULE); +DECLARE_MTYPE(ROUTE_MAP_COMPILED); + +#define DEBUG_ROUTEMAP 0x01 +#define DEBUG_ROUTEMAP_DETAIL 0x02 +extern uint32_t rmap_debug; + +/* Route map's type. */ +enum route_map_type { RMAP_PERMIT, RMAP_DENY, RMAP_ANY }; + +typedef enum { + RMAP_DENYMATCH, + RMAP_PERMITMATCH +} route_map_result_t; + +/* + * Route-map match or set result "Eg: match evpn vni xx" + * route-map match cmd always returns match/nomatch/noop + * match--> found a match + * nomatch--> didnt find a match + * noop--> not applicable + * route-map set retuns okay/error + * okay --> set was successful + * error --> set was not successful + */ +enum route_map_cmd_result_t { + /* + * route-map match cmd results + */ + RMAP_MATCH, + RMAP_NOMATCH, + RMAP_NOOP, + /* + * route-map set cmd results + */ + RMAP_OKAY, + RMAP_ERROR +}; + +typedef enum { RMAP_EXIT, RMAP_GOTO, RMAP_NEXT } route_map_end_t; + +typedef enum { + RMAP_EVENT_SET_ADDED, + RMAP_EVENT_SET_DELETED, + RMAP_EVENT_SET_REPLACED, + RMAP_EVENT_MATCH_ADDED, + RMAP_EVENT_MATCH_DELETED, + RMAP_EVENT_MATCH_REPLACED, + RMAP_EVENT_INDEX_ADDED, + RMAP_EVENT_INDEX_DELETED, + RMAP_EVENT_CALL_ADDED, /* call to another routemap added */ + RMAP_EVENT_CALL_DELETED, + RMAP_EVENT_PLIST_ADDED, + RMAP_EVENT_PLIST_DELETED, + RMAP_EVENT_CLIST_ADDED, + RMAP_EVENT_CLIST_DELETED, + RMAP_EVENT_ECLIST_ADDED, + RMAP_EVENT_ECLIST_DELETED, + RMAP_EVENT_LLIST_ADDED, + RMAP_EVENT_LLIST_DELETED, + RMAP_EVENT_ASLIST_ADDED, + RMAP_EVENT_ASLIST_DELETED, + RMAP_EVENT_FILTER_ADDED, + RMAP_EVENT_FILTER_DELETED, +} route_map_event_t; + +/* Depth limit in RMAP recursion using RMAP_CALL. */ +#define RMAP_RECURSION_LIMIT 10 + +/* Route map rule structure for matching and setting. */ +struct route_map_rule_cmd { + /* Route map rule name (e.g. as-path, metric) */ + const char *str; + + /* Function for value set or match. */ + enum route_map_cmd_result_t (*func_apply)(void *rule, + const struct prefix *prefix, + void *object); + + /* Compile argument and return result as void *. */ + void *(*func_compile)(const char *); + + /* Free allocated value by func_compile (). */ + void (*func_free)(void *); + + /** To get the rule key after Compilation **/ + void *(*func_get_rmap_rule_key)(void *val); +}; + +/* Route map apply error. */ +enum rmap_compile_rets { + RMAP_COMPILE_SUCCESS, + + /* Route map rule is missing. */ + RMAP_RULE_MISSING, + + /* Route map rule can't compile */ + RMAP_COMPILE_ERROR, + +}; + +/* Route map rule. This rule has both `match' rule and `set' rule. */ +struct route_map_rule { + /* Rule type. */ + const struct route_map_rule_cmd *cmd; + + /* For pretty printing. */ + char *rule_str; + + /* Pre-compiled match rule. */ + void *value; + + /* Linked list. */ + struct route_map_rule *next; + struct route_map_rule *prev; +}; + +/* Route map rule list. */ +struct route_map_rule_list { + struct route_map_rule *head; + struct route_map_rule *tail; +}; + +/* Forward struct declaration: the complete can be found later this file. */ +struct routemap_hook_context; + +/* Route map index structure. */ +struct route_map_index { + struct route_map *map; + char *description; + + /* Preference of this route map rule. */ + int pref; + + /* Route map type permit or deny. */ + enum route_map_type type; + + /* Do we follow old rules, or hop forward? */ + route_map_end_t exitpolicy; + + /* If we're using "GOTO", to where do we go? */ + int nextpref; + + /* If we're using "CALL", to which route-map do ew go? */ + char *nextrm; + + /* Matching rule list. */ + struct route_map_rule_list match_list; + struct route_map_rule_list set_list; + + /* Make linked list. */ + struct route_map_index *next; + struct route_map_index *prev; + + /* Keep track how many times we've try to apply */ + uint64_t applied; + uint64_t applied_clear; + + /* List of match/sets contexts. */ + TAILQ_HEAD(, routemap_hook_context) rhclist; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(route_map_index); + +/* route map maximum length. Not strictly the maximum xpath length but cannot be + * greater + */ +#define RMAP_NAME_MAXLEN XPATH_MAXLEN + +/* Route map list structure. */ +struct route_map { + /* Name of route map. */ + char *name; + + /* Route map's rule. */ + struct route_map_index *head; + struct route_map_index *tail; + + /* Make linked list. */ + struct route_map *next; + struct route_map *prev; + + /* Maintain update info */ + bool to_be_processed; /* True if modification isn't acted on yet */ + bool deleted; /* If 1, then this node will be deleted */ + bool optimization_disabled; + + /* How many times have we applied this route-map */ + uint64_t applied; + uint64_t applied_clear; + + /* Counter to track active usage of this route-map */ + uint16_t use_count; + + /* Tables to maintain IPv4 and IPv6 prefixes from + * the prefix-list match clause. + */ + struct route_table *ipv4_prefix_table; + struct route_table *ipv6_prefix_table; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(route_map); + +/* Route-map match conditions */ +#define IS_MATCH_INTERFACE(C) \ + (strmatch(C, "frr-route-map:interface")) +#define IS_MATCH_IPv4_ADDRESS_LIST(C) \ + (strmatch(C, "frr-route-map:ipv4-address-list")) +#define IS_MATCH_IPv6_ADDRESS_LIST(C) \ + (strmatch(C, "frr-route-map:ipv6-address-list")) +#define IS_MATCH_IPv4_NEXTHOP_LIST(C) \ + (strmatch(C, "frr-route-map:ipv4-next-hop-list")) +#define IS_MATCH_IPv6_NEXTHOP_LIST(C) \ + (strmatch(C, "frr-route-map:ipv6-next-hop-list")) +#define IS_MATCH_IPv4_PREFIX_LIST(C) \ + (strmatch(C, "frr-route-map:ipv4-prefix-list")) +#define IS_MATCH_IPv6_PREFIX_LIST(C) \ + (strmatch(C, "frr-route-map:ipv6-prefix-list")) +#define IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(C) \ + (strmatch(C, "frr-route-map:ipv4-next-hop-prefix-list")) +#define IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(C) \ + (strmatch(C, "frr-route-map:ipv6-next-hop-prefix-list")) +#define IS_MATCH_IPv4_NEXTHOP_TYPE(C) \ + (strmatch(C, "frr-route-map:ipv4-next-hop-type")) +#define IS_MATCH_IPv6_NEXTHOP_TYPE(C) \ + (strmatch(C, "frr-route-map:ipv6-next-hop-type")) +#define IS_MATCH_METRIC(C) \ + (strmatch(C, "frr-route-map:match-metric")) +#define IS_MATCH_TAG(C) (strmatch(C, "frr-route-map:match-tag")) +/* Zebra route-map match conditions */ +#define IS_MATCH_IPv4_PREFIX_LEN(C) \ + (strmatch(C, "frr-zebra-route-map:ipv4-prefix-length")) +#define IS_MATCH_IPv6_PREFIX_LEN(C) \ + (strmatch(C, "frr-zebra-route-map:ipv6-prefix-length")) +#define IS_MATCH_IPv4_NH_PREFIX_LEN(C) \ + (strmatch(C, "frr-zebra-route-map:ipv4-next-hop-prefix-length")) +#define IS_MATCH_SRC_PROTO(C) \ + (strmatch(C, "frr-zebra-route-map:source-protocol")) +#define IS_MATCH_BGP_SRC_PROTO(C) \ + (strmatch(C, "frr-bgp-route-map:source-protocol")) +#define IS_MATCH_SRC_INSTANCE(C) \ + (strmatch(C, "frr-zebra-route-map:source-instance")) +/* BGP route-map match conditions */ +#define IS_MATCH_LOCAL_PREF(C) \ + (strmatch(C, "frr-bgp-route-map:match-local-preference")) +#define IS_MATCH_ALIAS(C) (strmatch(C, "frr-bgp-route-map:match-alias")) +#define IS_MATCH_SCRIPT(C) (strmatch(C, "frr-bgp-route-map:match-script")) +#define IS_MATCH_ORIGIN(C) \ + (strmatch(C, "frr-bgp-route-map:match-origin")) +#define IS_MATCH_RPKI(C) (strmatch(C, "frr-bgp-route-map:rpki")) +#define IS_MATCH_RPKI_EXTCOMMUNITY(C) \ + (strmatch(C, "frr-bgp-route-map:rpki-extcommunity")) +#define IS_MATCH_PROBABILITY(C) \ + (strmatch(C, "frr-bgp-route-map:probability")) +#define IS_MATCH_SRC_VRF(C) \ + (strmatch(C, "frr-bgp-route-map:source-vrf")) +#define IS_MATCH_PEER(C) (strmatch(C, "frr-bgp-route-map:peer")) +#define IS_MATCH_AS_LIST(C) \ + (strmatch(C, "frr-bgp-route-map:as-path-list")) +#define IS_MATCH_MAC_LIST(C) \ + (strmatch(C, "frr-bgp-route-map:mac-address-list")) +#define IS_MATCH_EVPN_ROUTE_TYPE(C) \ + (strmatch(C, "frr-bgp-route-map:evpn-route-type")) +#define IS_MATCH_EVPN_DEFAULT_ROUTE(C) \ + (strmatch(C, "frr-bgp-route-map:evpn-default-route")) +#define IS_MATCH_EVPN_VNI(C) \ + (strmatch(C, "frr-bgp-route-map:evpn-vni")) +#define IS_MATCH_EVPN_DEFAULT_ROUTE(C) \ + (strmatch(C, "frr-bgp-route-map:evpn-default-route")) +#define IS_MATCH_EVPN_RD(C) \ + (strmatch(C, "frr-bgp-route-map:evpn-rd")) +#define IS_MATCH_ROUTE_SRC(C) \ + (strmatch(C, "frr-bgp-route-map:ip-route-source")) +#define IS_MATCH_ROUTE_SRC_PL(C) \ + (strmatch(C, "frr-bgp-route-map:ip-route-source-prefix-list")) +#define IS_MATCH_COMMUNITY(C) \ + (strmatch(C, "frr-bgp-route-map:match-community")) +#define IS_MATCH_LCOMMUNITY(C) \ + (strmatch(C, "frr-bgp-route-map:match-large-community")) +#define IS_MATCH_EXTCOMMUNITY(C) \ + (strmatch(C, "frr-bgp-route-map:match-extcommunity")) +#define IS_MATCH_IPV4_NH(C) \ + (strmatch(C, "frr-bgp-route-map:ipv4-nexthop")) +#define IS_MATCH_IPV6_NH(C) \ + (strmatch(C, "frr-bgp-route-map:ipv6-nexthop")) + +/* Route-map set actions */ +#define IS_SET_IPv4_NH(A) \ + (strmatch(A, "frr-route-map:ipv4-next-hop")) +#define IS_SET_IPv6_NH(A) \ + (strmatch(A, "frr-route-map:ipv6-next-hop")) +#define IS_SET_METRIC(A) \ + (strmatch(A, "frr-route-map:set-metric")) +#define IS_SET_MIN_METRIC(A) (strmatch(A, "frr-route-map:set-min-metric")) +#define IS_SET_MAX_METRIC(A) (strmatch(A, "frr-route-map:set-max-metric")) +#define IS_SET_TAG(A) (strmatch(A, "frr-route-map:set-tag")) +#define IS_SET_SR_TE_COLOR(A) \ + (strmatch(A, "frr-route-map:set-sr-te-color")) +/* Zebra route-map set actions */ +#define IS_SET_SRC(A) \ + (strmatch(A, "frr-zebra-route-map:src-address")) +/* OSPF route-map set actions */ +#define IS_SET_METRIC_TYPE(A) \ + (strmatch(A, "frr-ospf-route-map:metric-type")) +#define IS_SET_FORWARDING_ADDR(A) \ + (strmatch(A, "frr-ospf6-route-map:forwarding-address")) +/* BGP route-map_set actions */ +#define IS_SET_WEIGHT(A) \ + (strmatch(A, "frr-bgp-route-map:weight")) +#define IS_SET_TABLE(A) (strmatch(A, "frr-bgp-route-map:table")) +#define IS_SET_LOCAL_PREF(A) \ + (strmatch(A, "frr-bgp-route-map:set-local-preference")) +#define IS_SET_LABEL_INDEX(A) \ + (strmatch(A, "frr-bgp-route-map:label-index")) +#define IS_SET_DISTANCE(A) \ + (strmatch(A, "frr-bgp-route-map:distance")) +#define IS_SET_ORIGIN(A) \ + (strmatch(A, "frr-bgp-route-map:set-origin")) +#define IS_SET_ATOMIC_AGGREGATE(A) \ + (strmatch(A, "frr-bgp-route-map:atomic-aggregate")) +#define IS_SET_AIGP_METRIC(A) (strmatch(A, "frr-bgp-route-map:aigp-metric")) +#define IS_SET_ORIGINATOR_ID(A) \ + (strmatch(A, "frr-bgp-route-map:originator-id")) +#define IS_SET_COMM_LIST_DEL(A) \ + (strmatch(A, "frr-bgp-route-map:comm-list-delete")) +#define IS_SET_LCOMM_LIST_DEL(A) \ + (strmatch(A, "frr-bgp-route-map:large-comm-list-delete")) +#define IS_SET_EXTCOMM_LIST_DEL(A) \ + (strmatch(A, "frr-bgp-route-map:extended-comm-list-delete")) +#define IS_SET_LCOMMUNITY(A) \ + (strmatch(A, "frr-bgp-route-map:set-large-community")) +#define IS_SET_COMMUNITY(A) \ + (strmatch(A, "frr-bgp-route-map:set-community")) +#define IS_SET_EXTCOMMUNITY_NONE(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-none")) +#define IS_SET_EXTCOMMUNITY_RT(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-rt")) +#define IS_SET_EXTCOMMUNITY_NT(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-nt")) +#define IS_SET_EXTCOMMUNITY_SOO(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-soo")) +#define IS_SET_EXTCOMMUNITY_LB(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-lb")) +#define IS_SET_EXTCOMMUNITY_COLOR(A) \ + (strmatch(A, "frr-bgp-route-map:set-extcommunity-color")) + +#define IS_SET_AGGREGATOR(A) \ + (strmatch(A, "frr-bgp-route-map:aggregator")) +#define IS_SET_AS_PREPEND(A) \ + (strmatch(A, "frr-bgp-route-map:as-path-prepend")) +#define IS_SET_AS_EXCLUDE(A) \ + (strmatch(A, "frr-bgp-route-map:as-path-exclude")) +#define IS_SET_AS_REPLACE(A) (strmatch(A, "frr-bgp-route-map:as-path-replace")) +#define IS_SET_IPV6_NH_GLOBAL(A) \ + (strmatch(A, "frr-bgp-route-map:ipv6-nexthop-global")) +#define IS_SET_IPV6_VPN_NH(A) \ + (strmatch(A, "frr-bgp-route-map:ipv6-vpn-address")) +#define IS_SET_IPV6_PEER_ADDR(A) \ + (strmatch(A, "frr-bgp-route-map:ipv6-peer-address")) +#define IS_SET_IPV6_PREFER_GLOBAL(A) \ + (strmatch(A, "frr-bgp-route-map:ipv6-prefer-global")) +#define IS_SET_IPV4_VPN_NH(A) \ + (strmatch(A, "frr-bgp-route-map:ipv4-vpn-address")) +#define IS_SET_BGP_IPV4_NH(A) \ + (strmatch(A, "frr-bgp-route-map:set-ipv4-nexthop")) +#define IS_SET_BGP_EVPN_GATEWAY_IP_IPV4(A) \ + (strmatch(A, "frr-bgp-route-map:set-evpn-gateway-ip-ipv4")) +#define IS_SET_BGP_EVPN_GATEWAY_IP_IPV6(A) \ + (strmatch(A, "frr-bgp-route-map:set-evpn-gateway-ip-ipv6")) +#define IS_SET_BGP_L3VPN_NEXTHOP_ENCAPSULATION(A) \ + (strmatch(A, "frr-bgp-route-map:set-l3vpn-nexthop-encapsulation")) + +enum ecommunity_lb_type { + EXPLICIT_BANDWIDTH, + CUMULATIVE_BANDWIDTH, + COMPUTED_BANDWIDTH +}; + +/* Prototypes. */ +extern void route_map_init(void); +extern void route_map_init_new(bool in_backend); + +/* + * This should only be called on shutdown + * Additionally this function sets the hooks to NULL + * before any processing is done. + */ +extern void route_map_finish(void); + +/* Add match statement to route map. */ +extern enum rmap_compile_rets route_map_add_match(struct route_map_index *index, + const char *match_name, + const char *match_arg, + route_map_event_t type); + +/* Delete specified route match rule. */ +extern enum rmap_compile_rets +route_map_delete_match(struct route_map_index *index, + const char *match_name, const char *match_arg, + route_map_event_t type); + +extern const char *route_map_get_match_arg(struct route_map_index *index, + const char *match_name); + +/* Add route-map set statement to the route map. */ +extern enum rmap_compile_rets route_map_add_set(struct route_map_index *index, + const char *set_name, + const char *set_arg); + +/* Delete route map set rule. */ +extern enum rmap_compile_rets +route_map_delete_set(struct route_map_index *index, + const char *set_name, const char *set_arg); + +/* struct route_map_rule_cmd is kept const in order to not have writable + * function pointers (which is a security benefit.) Hence, below struct is + * used as proxy for hashing these for by-name lookup. + */ + +PREDECL_HASH(rmap_cmd_name); + +struct route_map_rule_cmd_proxy { + struct rmap_cmd_name_item itm; + const struct route_map_rule_cmd *cmd; +}; + +/* ... and just automatically create a proxy struct for each call location + * to route_map_install_{match,set} to avoid unnecessarily added boilerplate + * for each route-map user + */ + +#define route_map_install_match(c) \ + do { \ + static struct route_map_rule_cmd_proxy proxy = {.cmd = c}; \ + _route_map_install_match(&proxy); \ + } while (0) + +#define route_map_install_set(c) \ + do { \ + static struct route_map_rule_cmd_proxy proxy = {.cmd = c}; \ + _route_map_install_set(&proxy); \ + } while (0) + +/* Install rule command to the match list. */ +extern void _route_map_install_match(struct route_map_rule_cmd_proxy *proxy); + +/* + * Install rule command to the set list. + * + * When installing a particular item, Allow a difference of handling + * of bad cli inputted(return NULL) -vs- this particular daemon cannot use + * this form of the command(return a pointer and handle it appropriately + * in the apply command). See 'set metric' command + * as it is handled in ripd/ripngd and ospfd. + */ +extern void _route_map_install_set(struct route_map_rule_cmd_proxy *proxy); + +/* Lookup route map by name. */ +extern struct route_map *route_map_lookup_by_name(const char *name); + +/* Simple helper to warn if route-map does not exist. */ +struct route_map *route_map_lookup_warn_noexist(struct vty *vty, const char *name); + +/* Apply route map to the object. */ +extern route_map_result_t route_map_apply_ext(struct route_map *map, + const struct prefix *prefix, + void *match_object, + void *set_object, int *pref); +#define route_map_apply(map, prefix, object) \ + route_map_apply_ext(map, prefix, object, object, NULL) + +extern void route_map_add_hook(void (*func)(const char *)); +extern void route_map_delete_hook(void (*func)(const char *)); + +/* + * This is the callback for when something has changed about a + * route-map. The interested parties can register to receive + * this data. + * + * name - Is the name of the changed route-map + */ +extern void route_map_event_hook(void (*func)(const char *name)); +extern int route_map_mark_updated(const char *name); +extern void route_map_walk_update_list(void (*update_fn)(char *name)); +extern void route_map_upd8_dependency(route_map_event_t type, const char *arg, + const char *rmap_name); +extern void route_map_notify_dependencies(const char *affected_name, + route_map_event_t event); +extern void +route_map_notify_pentry_dependencies(const char *affected_name, + struct prefix_list_entry *pentry, + route_map_event_t event); +extern int generic_match_add(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); +extern int generic_match_delete(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + +extern int generic_set_add(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); +extern int generic_set_delete(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + +/* match interface */ +extern void route_map_match_interface_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match interface */ +extern void route_map_no_match_interface_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ip address */ +extern void route_map_match_ip_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ip address */ +extern void route_map_no_match_ip_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ip address prefix list */ +extern void route_map_match_ip_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ip address prefix list */ +extern void route_map_no_match_ip_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ip next hop */ +extern void route_map_match_ip_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ip next hop */ +extern void route_map_no_match_ip_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)); +/* match ipv6 next hop */ +extern void route_map_match_ipv6_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)); +/* no match ipv6 next hop */ +extern void route_map_no_match_ipv6_next_hop_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)); +/* match ip next hop prefix list */ +extern void route_map_match_ip_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ip next hop prefix list */ +extern void route_map_no_match_ip_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ip next hop type */ +extern void route_map_match_ip_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ip next hop type */ +extern void route_map_no_match_ip_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ipv6 address */ +extern void route_map_match_ipv6_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ipv6 address */ +extern void route_map_no_match_ipv6_address_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ipv6 address prefix list */ +extern void route_map_match_ipv6_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ipv6 address prefix list */ +extern void route_map_no_match_ipv6_address_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ipv6 next-hop type */ +extern void route_map_match_ipv6_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match ipv6 next-hop type */ +extern void route_map_no_match_ipv6_next_hop_type_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match ipv6 next-hop prefix-list */ +extern void route_map_match_ipv6_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)); +/* no match ipv6 next-hop prefix-list */ +extern void route_map_no_match_ipv6_next_hop_prefix_list_hook(int (*func)( + struct route_map_index *index, const char *command, const char *arg, + route_map_event_t type, char *errmsg, size_t errmsg_len)); +/* match metric */ +extern void route_map_match_metric_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match metric */ +extern void route_map_no_match_metric_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* match tag */ +extern void route_map_match_tag_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* no match tag */ +extern void route_map_no_match_tag_hook(int (*func)( + struct route_map_index *index, const char *command, + const char *arg, route_map_event_t type, + char *errmsg, size_t errmsg_len)); +/* set sr-te color */ +extern void route_map_set_srte_color_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* no set sr-te color */ +extern void route_map_no_set_srte_color_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* set ip nexthop */ +extern void route_map_set_ip_nexthop_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* no set ip nexthop */ +extern void route_map_no_set_ip_nexthop_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* set ipv6 nexthop local */ +extern void route_map_set_ipv6_nexthop_local_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* no set ipv6 nexthop local */ +extern void route_map_no_set_ipv6_nexthop_local_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* set metric */ +extern void route_map_set_metric_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, + size_t errmsg_len)); +/* no set metric */ +extern void route_map_no_set_metric_hook( + int (*func)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len)); +/* set metric */ +extern void route_map_set_max_metric_hook( + int (*func)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len)); +/* no set metric */ +extern void route_map_no_set_max_metric_hook( + int (*func)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len)); +/* set metric */ +extern void route_map_set_min_metric_hook( + int (*func)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len)); +/* no set metric */ +extern void route_map_no_set_min_metric_hook( + int (*func)(struct route_map_index *index, const char *command, + const char *arg, char *errmsg, size_t errmsg_len)); +/* set tag */ +extern void route_map_set_tag_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, + size_t errmsg_len)); +/* no set tag */ +extern void route_map_no_set_tag_hook(int (*func)(struct route_map_index *index, + const char *command, + const char *arg, + char *errmsg, + size_t errmsg_len)); + +extern void *route_map_rule_tag_compile(const char *arg); +extern void route_map_rule_tag_free(void *rule); + +/* Increment the route-map used counter */ +extern void route_map_counter_increment(struct route_map *map); + +/* Decrement the route-map used counter */ +extern void route_map_counter_decrement(struct route_map *map); + +/* Route map hooks data structure. */ +struct route_map_match_set_hooks { + /* match interface */ + int (*match_interface)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match interface */ + int (*no_match_interface)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match ip address */ + int (*match_ip_address)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ip address */ + int (*no_match_ip_address)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match ip address prefix list */ + int (*match_ip_address_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ip address prefix list */ + int (*no_match_ip_address_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match ip next hop */ + int (*match_ip_next_hop)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ip next hop */ + int (*no_match_ip_next_hop)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match ipv6 next hop */ + int (*match_ipv6_next_hop)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, char *errmsg, + size_t errmsg_len); + + /* no match ipv6 next hop */ + int (*no_match_ipv6_next_hop)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, char *errmsg, + size_t errmsg_len); + + /* match ipv6 next hop prefix-list */ + int (*match_ipv6_next_hop_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ipv6 next-hop prefix-list */ + int (*no_match_ipv6_next_hop_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, + size_t errmsg_len); + + /* match ip next hop prefix list */ + int (*match_ip_next_hop_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ip next hop prefix list */ + int (*no_match_ip_next_hop_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, + size_t errmsg_len); + + /* match ip next-hop type */ + int (*match_ip_next_hop_type)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, + size_t errmsg_len); + + /* no match ip next-hop type */ + int (*no_match_ip_next_hop_type)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, + size_t errmsg_len); + + /* match ipv6 address */ + int (*match_ipv6_address)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ipv6 address */ + int (*no_match_ipv6_address)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + + /* match ipv6 address prefix list */ + int (*match_ipv6_address_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ipv6 address prefix list */ + int (*no_match_ipv6_address_prefix_list)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, + size_t errmsg_len); + + /* match ipv6 next-hop type */ + int (*match_ipv6_next_hop_type)(struct route_map_index *index, + const char *command, + const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match ipv6 next-hop type */ + int (*no_match_ipv6_next_hop_type)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match metric */ + int (*match_metric)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match metric */ + int (*no_match_metric)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* match tag */ + int (*match_tag)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* no match tag */ + int (*no_match_tag)(struct route_map_index *index, + const char *command, const char *arg, + route_map_event_t type, + char *errmsg, size_t errmsg_len); + + /* set sr-te color */ + int (*set_srte_color)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set sr-te color */ + int (*no_set_srte_color)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* set ip nexthop */ + int (*set_ip_nexthop)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set ip nexthop */ + int (*no_set_ip_nexthop)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* set ipv6 nexthop local */ + int (*set_ipv6_nexthop_local)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set ipv6 nexthop local */ + int (*no_set_ipv6_nexthop_local)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* set metric */ + int (*set_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set metric */ + int (*no_set_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + /* set min-metric */ + int (*set_min_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set min-metric */ + int (*no_set_min_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* set max-metric */ + int (*set_max_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set max-metric */ + int (*no_set_max_metric)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* set tag */ + int (*set_tag)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); + + /* no set tag */ + int (*no_set_tag)(struct route_map_index *index, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); +}; + +extern struct route_map_match_set_hooks rmap_match_set_hook; + +/* Making route map list. */ +struct route_map_list { + struct route_map *head; + struct route_map *tail; + + void (*add_hook)(const char *); + void (*delete_hook)(const char *); + void (*event_hook)(const char *); +}; + +extern struct route_map_list route_map_master; + +extern struct route_map *route_map_get(const char *name); +extern void route_map_delete(struct route_map *map); +extern struct route_map_index *route_map_index_get(struct route_map *map, + enum route_map_type type, + int pref); +extern void route_map_index_delete(struct route_map_index *index, int notify); + +/* routemap_northbound.c */ +typedef int (*routemap_match_hook_fun)(struct route_map_index *rmi, + const char *command, const char *arg, + route_map_event_t event, + char *errmsg, size_t errmsg_len); +typedef int (*routemap_set_hook_fun)(struct route_map_index *rmi, + const char *command, const char *arg, + char *errmsg, size_t errmsg_len); +struct routemap_hook_context { + struct route_map_index *rhc_rmi; + const char *rhc_rule; + route_map_event_t rhc_event; + routemap_set_hook_fun rhc_shook; + routemap_match_hook_fun rhc_mhook; + TAILQ_ENTRY(routemap_hook_context) rhc_entry; +}; + +int lib_route_map_entry_match_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_destroy(struct nb_cb_destroy_args *args); + +struct routemap_hook_context * +routemap_hook_context_insert(struct route_map_index *rmi); +void routemap_hook_context_free(struct routemap_hook_context *rhc); + +extern const struct frr_yang_module_info frr_route_map_info; +extern const struct frr_yang_module_info frr_route_map_cli_info; + +/* routemap_cli.c */ +extern int route_map_instance_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2); +extern void route_map_instance_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_instance_show_end(struct vty *vty, + const struct lyd_node *dnode); +extern void route_map_condition_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_action_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_exit_policy_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_call_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_description_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_optimization_disabled_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +extern void route_map_cli_init(void); + +extern void route_map_show_debug(struct vty *vty); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_ROUTEMAP_H */ diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c new file mode 100644 index 0000000..8e2e497 --- /dev/null +++ b/lib/routemap_cli.c @@ -0,0 +1,1695 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Route map northbound CLI implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/command.h" +#include "lib/northbound_cli.h" +#include "lib/routemap.h" + +#include "lib/routemap_cli_clippy.c" + +#define ROUTE_MAP_CMD_STR \ + "Create route-map or enter route-map command mode\n" \ + "Route map tag\n" +#define ROUTE_MAP_OP_CMD_STR \ + "Route map denies set operations\n" \ + "Route map permits set operations\n" +#define ROUTE_MAP_SEQUENCE_CMD_STR \ + "Sequence to insert to/delete from existing route-map entry\n" + +DEFPY_YANG_NOSH( + route_map, route_map_cmd, + "route-map RMAP_NAME$name $action (1-65535)$sequence", + ROUTE_MAP_CMD_STR + ROUTE_MAP_OP_CMD_STR + ROUTE_MAP_SEQUENCE_CMD_STR) +{ + char xpath_action[XPATH_MAXLEN + 64]; + char xpath_index[XPATH_MAXLEN + 32]; + char xpath[XPATH_MAXLEN]; + int rv; + + snprintf(xpath, sizeof(xpath), + "/frr-route-map:lib/route-map[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_index, sizeof(xpath_index), "%s/entry[sequence='%lu']", + xpath, sequence); + nb_cli_enqueue_change(vty, xpath_index, NB_OP_CREATE, NULL); + + snprintf(xpath_action, sizeof(xpath_action), "%s/action", xpath_index); + nb_cli_enqueue_change(vty, xpath_action, NB_OP_MODIFY, action); + + rv = nb_cli_apply_changes(vty, NULL); + if (rv == CMD_SUCCESS) + VTY_PUSH_XPATH(RMAP_NODE, xpath_index); + + return rv; +} + +DEFPY_YANG( + no_route_map_all, no_route_map_all_cmd, + "no route-map RMAP_NAME$name", + NO_STR + ROUTE_MAP_CMD_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-route-map:lib/route-map[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_route_map, no_route_map_cmd, + "no route-map RMAP_NAME$name $action (1-65535)$sequence", + NO_STR + ROUTE_MAP_CMD_STR + ROUTE_MAP_OP_CMD_STR + ROUTE_MAP_SEQUENCE_CMD_STR) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-route-map:lib/route-map[name='%s']/entry[sequence='%lu']", + name, sequence); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int route_map_instance_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + uint16_t seq1 = yang_dnode_get_uint16(dnode1, "sequence"); + uint16_t seq2 = yang_dnode_get_uint16(dnode2, "sequence"); + + return seq1 - seq2; +} + +void route_map_instance_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *name = yang_dnode_get_string(dnode, "../name"); + const char *action = yang_dnode_get_string(dnode, "action"); + const char *sequence = yang_dnode_get_string(dnode, "sequence"); + + vty_out(vty, "route-map %s %s %s\n", name, action, sequence); + +} + +void route_map_instance_show_end(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); +} + +DEFPY_YANG( + match_interface, match_interface_cmd, + "match interface IFNAME", + MATCH_STR + "Match first hop interface of route\n" + INTERFACE_STR) +{ + const char *xpath = + "./match-condition[condition='frr-route-map:interface']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/interface", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ifname); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_interface, no_match_interface_cmd, + "no match interface [IFNAME]", + NO_STR + MATCH_STR + "Match first hop interface of route\n" + INTERFACE_STR) +{ + const char *xpath = + "./match-condition[condition='frr-route-map:interface']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ip_address, match_ip_address_cmd, + "match ip address ACCESSLIST4_NAME$name", + MATCH_STR + IP_STR + "Match address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-address-list']"; + char xpath_value[XPATH_MAXLEN + 32]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ip_address, no_match_ip_address_cmd, + "no match ip address [ACCESSLIST4_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-address-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ip_address_prefix_list, + match_ip_address_prefix_list_cmd, + "match ip address prefix-list PREFIXLIST4_NAME$name", + MATCH_STR + IP_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-prefix-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ip_address_prefix_list, no_match_ip_address_prefix_list_cmd, + "no match ip address prefix-list [PREFIXLIST4_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-prefix-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ip_next_hop, match_ip_next_hop_cmd, + "match ip next-hop ACCESSLIST4_NAME$name", + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-list']"; + char xpath_value[XPATH_MAXLEN + 32]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ip_next_hop, no_match_ip_next_hop_cmd, + "no match ip next-hop [ACCESSLIST4_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match address of route\n" + "IP Access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ip_next_hop_prefix_list, + match_ip_next_hop_prefix_list_cmd, + "match ip next-hop prefix-list PREFIXLIST4_NAME$name", + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-prefix-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ip_next_hop_prefix_list, + no_match_ip_next_hop_prefix_list_cmd, + "no match ip next-hop prefix-list [PREFIXLIST4_NAME]", + NO_STR + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-prefix-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ip_next_hop_type, match_ip_next_hop_type_cmd, + "match ip next-hop type $type", + MATCH_STR + IP_STR + "Match next-hop address of route\n" + "Match entries by type\n" + "Blackhole\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/ipv4-next-hop-type", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, type); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ip_next_hop_type, no_match_ip_next_hop_type_cmd, + "no match ip next-hop type []", + NO_STR MATCH_STR IP_STR + "Match next-hop address of route\n" + "Match entries by type\n" + "Blackhole\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv4-next-hop-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ipv6_address, match_ipv6_address_cmd, + "match ipv6 address ACCESSLIST6_NAME$name", + MATCH_STR + IPV6_STR + "Match IPv6 address of route\n" + "IPv6 access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-address-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ipv6_address, no_match_ipv6_address_cmd, + "no match ipv6 address [ACCESSLIST6_NAME]", + NO_STR + MATCH_STR + IPV6_STR + "Match IPv6 address of route\n" + "IPv6 access-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-address-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ipv6_address_prefix_list, match_ipv6_address_prefix_list_cmd, + "match ipv6 address prefix-list PREFIXLIST6_NAME$name", + MATCH_STR + IPV6_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-prefix-list']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/list-name", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ipv6_address_prefix_list, + no_match_ipv6_address_prefix_list_cmd, + "no match ipv6 address prefix-list [PREFIXLIST6_NAME]", + NO_STR + MATCH_STR + IPV6_STR + "Match address of route\n" + "Match entries of prefix-lists\n" + "IP prefix-list name\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-prefix-list']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_ipv6_next_hop_type, match_ipv6_next_hop_type_cmd, + "match ipv6 next-hop type $type", + MATCH_STR IPV6_STR + "Match next-hop address of route\n" + "Match entries by type\n" + "Blackhole\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/ipv6-next-hop-type", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, type); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_ipv6_next_hop_type, no_match_ipv6_next_hop_type_cmd, + "no match ipv6 next-hop type []", + NO_STR MATCH_STR IPV6_STR + "Match address of route\n" + "Match entries by type\n" + "Blackhole\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:ipv6-next-hop-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_metric, match_metric_cmd, + "match metric (0-4294967295)$metric", + MATCH_STR + "Match metric of route\n" + "Metric value\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:match-metric']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/metric", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, metric_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_metric, no_match_metric_cmd, + "no match metric [(0-4294967295)]", + NO_STR + MATCH_STR + "Match metric of route\n" + "Metric value\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:match-metric']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + match_tag, match_tag_cmd, + "match tag ", + MATCH_STR + "Match tag of route\n" + "Untagged route\n" + "Tag value\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:match-tag']"; + char xpath_value[XPATH_MAXLEN]; + char value[64]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/tag", xpath); + snprintf(value, sizeof(value), "%lu", tagged ? tagged : 0); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_match_tag, no_match_tag_cmd, + "no match tag []", + NO_STR + MATCH_STR + "Match tag of route\n" + "Untagged route\n" + "Tag value\n") +{ + const char *xpath = + "./match-condition[condition='frr-route-map:match-tag']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *condition = yang_dnode_get_string(dnode, "condition"); + const struct lyd_node *ln; + const char *acl; + + if (IS_MATCH_INTERFACE(condition)) { + vty_out(vty, " match interface %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/interface")); + } else if (IS_MATCH_IPv4_ADDRESS_LIST(condition)) { + vty_out(vty, " match ip address %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv4_NEXTHOP_LIST(condition)) { + vty_out(vty, " match ip next-hop %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv6_NEXTHOP_LIST(condition)) { + vty_out(vty, " match ipv6 next-hop %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv4_PREFIX_LIST(condition)) { + vty_out(vty, " match ip address prefix-list %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(condition)) { + vty_out(vty, " match ip next-hop prefix-list %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(condition)) { + vty_out(vty, " match ipv6 next-hop prefix-list %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv6_ADDRESS_LIST(condition)) { + vty_out(vty, " match ipv6 address %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv6_PREFIX_LIST(condition)) { + vty_out(vty, " match ipv6 address prefix-list %s\n", + yang_dnode_get_string( + dnode, "./rmap-match-condition/list-name")); + } else if (IS_MATCH_IPv4_NEXTHOP_TYPE(condition)) { + vty_out(vty, " match ip next-hop type %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/ipv4-next-hop-type")); + } else if (IS_MATCH_IPv6_NEXTHOP_TYPE(condition)) { + vty_out(vty, " match ipv6 next-hop type %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/ipv6-next-hop-type")); + } else if (IS_MATCH_METRIC(condition)) { + vty_out(vty, " match metric %s\n", + yang_dnode_get_string(dnode, + "./rmap-match-condition/metric")); + } else if (IS_MATCH_TAG(condition)) { + uint32_t tag = + strtoul(yang_dnode_get_string(dnode, + "./rmap-match-condition/tag"), + NULL, 10); + + if (!tag) + vty_out(vty, " match tag untagged\n"); + else + vty_out(vty, " match tag %u\n", tag); + } else if (IS_MATCH_IPv4_PREFIX_LEN(condition)) { + vty_out(vty, " match ip address prefix-len %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length")); + } else if (IS_MATCH_IPv6_PREFIX_LEN(condition)) { + vty_out(vty, " match ipv6 address prefix-len %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-zebra-route-map:ipv6-prefix-length")); + } else if (IS_MATCH_IPv4_NH_PREFIX_LEN(condition)) { + vty_out(vty, " match ip next-hop prefix-len %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length")); + } else if (IS_MATCH_SRC_PROTO(condition) || + IS_MATCH_BGP_SRC_PROTO(condition)) { + vty_out(vty, " match source-protocol %s\n", + yang_dnode_get_string( + dnode, + IS_MATCH_SRC_PROTO(condition) + ? "./rmap-match-condition/frr-zebra-route-map:source-protocol" + : "./rmap-match-condition/frr-bgp-route-map:source-protocol")); + } else if (IS_MATCH_SRC_INSTANCE(condition)) { + vty_out(vty, " match source-instance %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-zebra-route-map:source-instance")); + } else if (IS_MATCH_LOCAL_PREF(condition)) { + vty_out(vty, " match local-preference %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:local-preference")); + } else if (IS_MATCH_ALIAS(condition)) { + vty_out(vty, " match alias %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:alias")); + } else if (IS_MATCH_SCRIPT(condition)) { + vty_out(vty, " match script %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:script")); + } else if (IS_MATCH_ORIGIN(condition)) { + vty_out(vty, " match origin %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:origin")); + } else if (IS_MATCH_RPKI(condition)) { + vty_out(vty, " match rpki %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:rpki")); + } else if (IS_MATCH_RPKI_EXTCOMMUNITY(condition)) { + vty_out(vty, " match rpki-extcommunity %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:rpki-extcommunity")); + } else if (IS_MATCH_PROBABILITY(condition)) { + vty_out(vty, " match probability %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:probability")); + } else if (IS_MATCH_SRC_VRF(condition)) { + vty_out(vty, " match source-vrf %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:source-vrf")); + } else if (IS_MATCH_PEER(condition)) { + acl = NULL; + if ((ln = yang_dnode_get( + dnode, + "./rmap-match-condition/frr-bgp-route-map:peer-ipv4-address")) + != NULL) + acl = yang_dnode_get_string(ln, NULL); + else if ( + (ln = yang_dnode_get( + dnode, + "./rmap-match-condition/frr-bgp-route-map:peer-ipv6-address")) + != NULL) + acl = yang_dnode_get_string(ln, NULL); + else if ( + (ln = yang_dnode_get( + dnode, + "./rmap-match-condition/frr-bgp-route-map:peer-interface")) + != NULL) + acl = yang_dnode_get_string(ln, NULL); + else if (yang_dnode_get( + dnode, + "./rmap-match-condition/frr-bgp-route-map:peer-local") + != NULL) + acl = "local"; + + vty_out(vty, " match peer %s\n", acl); + } else if (IS_MATCH_AS_LIST(condition)) { + vty_out(vty, " match as-path %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:list-name")); + } else if (IS_MATCH_EVPN_ROUTE_TYPE(condition)) { + vty_out(vty, " match evpn route-type %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:evpn-route-type")); + } else if (IS_MATCH_EVPN_DEFAULT_ROUTE(condition)) { + vty_out(vty, " match evpn default-route\n"); + } else if (IS_MATCH_EVPN_VNI(condition)) { + vty_out(vty, " match evpn vni %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:evpn-vni")); + } else if (IS_MATCH_EVPN_DEFAULT_ROUTE(condition)) { + vty_out(vty, " match evpn default-route %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:evpn-default-route")); + } else if (IS_MATCH_EVPN_RD(condition)) { + vty_out(vty, " match evpn rd %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:route-distinguisher")); + } else if (IS_MATCH_MAC_LIST(condition)) { + vty_out(vty, " match mac address %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:list-name")); + } else if (IS_MATCH_ROUTE_SRC(condition)) { + vty_out(vty, " match ip route-source %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:list-name")); + } else if (IS_MATCH_ROUTE_SRC_PL(condition)) { + vty_out(vty, " match ip route-source prefix-list %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:list-name")); + } else if (IS_MATCH_COMMUNITY(condition)) { + vty_out(vty, " match community %s", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name")); + if (yang_dnode_get_bool( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match")) + vty_out(vty, " exact-match"); + if (yang_dnode_get_bool( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any")) + vty_out(vty, " any"); + vty_out(vty, "\n"); + } else if (IS_MATCH_LCOMMUNITY(condition)) { + vty_out(vty, " match large-community %s", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name")); + if (yang_dnode_get_bool( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-exact-match")) + vty_out(vty, " exact-match"); + if (yang_dnode_get_bool( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name-any")) + vty_out(vty, " any"); + vty_out(vty, "\n"); + } else if (IS_MATCH_EXTCOMMUNITY(condition)) { + vty_out(vty, " match extcommunity %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:comm-list/comm-list-name")); + } else if (IS_MATCH_IPV4_NH(condition)) { + vty_out(vty, " match ip next-hop address %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:ipv4-address")); + } else if (IS_MATCH_IPV6_NH(condition)) { + vty_out(vty, " match ipv6 next-hop address %s\n", + yang_dnode_get_string( + dnode, + "./rmap-match-condition/frr-bgp-route-map:ipv6-address")); + } +} + +DEFPY_YANG( + set_ip_nexthop, set_ip_nexthop_cmd, + "set ip next-hop A.B.C.D$addr", + SET_STR + IP_STR + "Next hop address\n" + "IP address of next hop\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:ipv4-next-hop']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/ipv4-address", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, addr_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_ip_nexthop, no_set_ip_nexthop_cmd, + "no set ip next-hop [A.B.C.D]", + NO_STR + SET_STR + IP_STR + "Next hop address\n" + "IP address of next hop\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:ipv4-next-hop']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + set_ipv6_nexthop_local, set_ipv6_nexthop_local_cmd, + "set ipv6 next-hop local X:X::X:X$addr", + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "IPv6 local address\n" + "IPv6 address of next hop\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:ipv6-next-hop']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/ipv6-address", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, addr_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_ipv6_nexthop_local, no_set_ipv6_nexthop_local_cmd, + "no set ipv6 next-hop local [X:X::X:X]", + NO_STR + SET_STR + IPV6_STR + "IPv6 next-hop address\n" + "IPv6 local address\n" + "IPv6 address of next hop\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:ipv6-next-hop']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + set_metric, set_metric_cmd, + "set metric <(-4294967295-4294967295)$metric|rtt$rtt|+rtt$artt|-rtt$srtt>", + SET_STR + "Metric value for destination routing protocol\n" + "Metric value (use +/- for additions or subtractions)\n" + "Assign round trip time\n" + "Add round trip time\n" + "Subtract round trip time\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-metric']"; + char xpath_value[XPATH_MAXLEN]; + char value[64]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + if (rtt) { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/use-round-trip-time", xpath); + snprintf(value, sizeof(value), "true"); + } else if (artt) { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/add-round-trip-time", xpath); + snprintf(value, sizeof(value), "true"); + } else if (srtt) { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/subtract-round-trip-time", xpath); + snprintf(value, sizeof(value), "true"); + } else if (metric_str && metric_str[0] == '+') { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/add-metric", xpath); + snprintf(value, sizeof(value), "%s", ++metric_str); + } else if (metric_str && metric_str[0] == '-') { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/subtract-metric", xpath); + snprintf(value, sizeof(value), "%s", ++metric_str); + } else { + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/value", xpath); + snprintf(value, sizeof(value), "%s", metric_str); + } + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_metric, no_set_metric_cmd, + "no set metric [OPTVAL]", + NO_STR + SET_STR + "Metric value for destination routing protocol\n" + "Metric value\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-metric']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_min_metric, set_min_metric_cmd, + "set min-metric <(0-4294967295)$metric>", + SET_STR + "Minimum metric value for destination routing protocol\n" + "Minimum metric value\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-min-metric']"; + char xpath_value[XPATH_MAXLEN]; + char value[64]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/min-metric", xpath); + snprintf(value, sizeof(value), "%s", metric_str); + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_min_metric, no_set_min_metric_cmd, + "no set min-metric [(0-4294967295)]", + NO_STR SET_STR + "Minimum metric value for destination routing protocol\n" + "Minumum metric value\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-min-metric']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(set_max_metric, set_max_metric_cmd, + "set max-metric <(0-4294967295)$metric>", + SET_STR + "Maximum metric value for destination routing protocol\n" + "Miximum metric value\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-max-metric']"; + char xpath_value[XPATH_MAXLEN]; + char value[64]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/max-metric", xpath); + snprintf(value, sizeof(value), "%s", metric_str); + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_set_max_metric, no_set_max_metric_cmd, + "no set max-metric [(0-4294967295)]", + NO_STR SET_STR + "Maximum Metric value for destination routing protocol\n" + "Maximum metric value\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-max-metric']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + set_tag, set_tag_cmd, + "set tag ", + SET_STR + "Tag value for routing protocol\n" + "Untagged route\n" + "Tag value\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-tag']"; + char xpath_value[XPATH_MAXLEN]; + char value[64]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), "%s/rmap-set-action/tag", + xpath); + snprintf(value, sizeof(value), "%lu", tagged ? tagged : 0); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_set_tag, no_set_tag_cmd, + "no set tag []", + NO_STR + SET_STR + "Tag value for routing protocol\n" + "Untagged route\n" + "Tag value\n") +{ + const char *xpath = "./set-action[action='frr-route-map:set-tag']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (set_srte_color, + set_srte_color_cmd, + "set sr-te color (1-4294967295)", + SET_STR + SRTE_STR + SRTE_COLOR_STR + "Color of the SR-TE Policies to match with\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-sr-te-color']"; + char xpath_value[XPATH_MAXLEN]; + int idx = 0; + + char *arg = argv_find(argv, argc, "(1-4294967295)", &idx) + ? argv[idx]->arg + : NULL; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/policy", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_srte_color, + no_set_srte_color_cmd, + "no set sr-te color [(1-4294967295)]", + NO_STR + SET_STR + SRTE_STR + SRTE_COLOR_STR + "Color of the SR-TE Policies to match with\n") +{ + const char *xpath = + "./set-action[action='frr-route-map:set-sr-te-color']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + + +void route_map_action_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *action = yang_dnode_get_string(dnode, "action"); + const struct lyd_node *ln; + const char *acl; + + if (IS_SET_IPv4_NH(action)) { + vty_out(vty, " set ip next-hop %s\n", + yang_dnode_get_string( + dnode, "./rmap-set-action/ipv4-address")); + } else if (IS_SET_IPv6_NH(action)) { + vty_out(vty, " set ipv6 next-hop local %s\n", + yang_dnode_get_string( + dnode, "./rmap-set-action/ipv6-address")); + } else if (IS_SET_METRIC(action)) { + if (yang_dnode_get(dnode, + "./rmap-set-action/use-round-trip-time")) { + vty_out(vty, " set metric rtt\n"); + } else if (yang_dnode_get( + dnode, + "./rmap-set-action/add-round-trip-time")) { + vty_out(vty, " set metric +rtt\n"); + } else if ( + yang_dnode_get( + dnode, + "./rmap-set-action/subtract-round-trip-time")) { + vty_out(vty, " set metric -rtt\n"); + } else if (yang_dnode_get(dnode, + "./rmap-set-action/add-metric")) { + vty_out(vty, " set metric +%s\n", + yang_dnode_get_string( + dnode, "./rmap-set-action/add-metric")); + } else if (yang_dnode_get( + dnode, + "./rmap-set-action/subtract-metric")) { + vty_out(vty, " set metric -%s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/subtract-metric")); + } else { + vty_out(vty, " set metric %s\n", + yang_dnode_get_string( + dnode, "./rmap-set-action/value")); + } + } else if (IS_SET_MIN_METRIC(action)) { + vty_out(vty, " set min-metric %s\n", + yang_dnode_get_string(dnode, + "./rmap-set-action/min-metric")); + } else if (IS_SET_MAX_METRIC(action)) { + vty_out(vty, " set max-metric %s\n", + yang_dnode_get_string(dnode, + "./rmap-set-action/max-metric")); + } else if (IS_SET_TAG(action)) { + uint32_t tag = + strtoul(yang_dnode_get_string(dnode, + "rmap-set-action/tag"), + NULL, 10); + + if (!tag) + vty_out(vty, " set tag untagged\n"); + else + vty_out(vty, " set tag %u\n", tag); + } else if (IS_SET_SR_TE_COLOR(action)) { + vty_out(vty, " set sr-te color %s\n", + yang_dnode_get_string(dnode, + "./rmap-set-action/policy")); + } else if (IS_SET_SRC(action)) { + if (yang_dnode_exists( + dnode, + "./rmap-set-action/frr-zebra-route-map:ipv4-src-address")) + vty_out(vty, " set src %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-zebra-route-map:ipv4-src-address")); + else + vty_out(vty, " set src %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-zebra-route-map:ipv6-src-address")); + } else if (IS_SET_METRIC_TYPE(action)) { + vty_out(vty, " set metric-type %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-ospf-route-map:metric-type")); + } else if (IS_SET_FORWARDING_ADDR(action)) { + vty_out(vty, " set forwarding-address %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-ospf6-route-map:ipv6-address")); + } else if (IS_SET_WEIGHT(action)) { + vty_out(vty, " set weight %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:weight")); + } else if (IS_SET_TABLE(action)) { + vty_out(vty, " set table %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:table")); + } else if (IS_SET_LOCAL_PREF(action)) { + vty_out(vty, " set local-preference %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:local-pref")); + } else if (IS_SET_LABEL_INDEX(action)) { + vty_out(vty, " set label-index %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:label-index")); + } else if (IS_SET_DISTANCE(action)) { + vty_out(vty, " set distance %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:distance")); + } else if (IS_SET_ORIGIN(action)) { + vty_out(vty, " set origin %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:origin")); + } else if (IS_SET_ATOMIC_AGGREGATE(action)) { + vty_out(vty, " set atomic-aggregate\n"); + } else if (IS_SET_AIGP_METRIC(action)) { + vty_out(vty, " set aigp-metric %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:aigp-metric")); + } else if (IS_SET_ORIGINATOR_ID(action)) { + vty_out(vty, " set originator-id %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:originator-id")); + } else if (IS_SET_COMM_LIST_DEL(action)) { + acl = NULL; + if ((ln = yang_dnode_get( + dnode, + "./rmap-set-action/frr-bgp-route-map:comm-list-name")) + != NULL) + acl = yang_dnode_get_string(ln, NULL); + + assert(acl); + + vty_out(vty, " set comm-list %s delete\n", acl); + } else if (IS_SET_LCOMM_LIST_DEL(action)) { + acl = NULL; + if ((ln = yang_dnode_get( + dnode, + "./rmap-set-action/frr-bgp-route-map:comm-list-name")) + != NULL) + acl = yang_dnode_get_string(ln, NULL); + + assert(acl); + + vty_out(vty, " set large-comm-list %s delete\n", acl); + } else if (IS_SET_EXTCOMM_LIST_DEL(action)) { + acl = NULL; + ln = yang_dnode_get(dnode, "rmap-set-action/frr-bgp-route-map:comm-list-name"); + + if (ln) + acl = yang_dnode_get_string(ln, NULL); + + assert(acl); + + vty_out(vty, " set extended-comm-list %s delete\n", acl); + } else if (IS_SET_LCOMMUNITY(action)) { + if (yang_dnode_exists( + dnode, + "./rmap-set-action/frr-bgp-route-map:large-community-string")) + vty_out(vty, " set large-community %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:large-community-string")); + else { + if (true + == yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:large-community-none")) + vty_out(vty, " set large-community none\n"); + } + } else if (IS_SET_COMMUNITY(action)) { + if (yang_dnode_exists( + dnode, + "./rmap-set-action/frr-bgp-route-map:community-string")) + vty_out(vty, " set community %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:community-string")); + else { + if (true + == yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:community-none")) + vty_out(vty, " set community none\n"); + } + } else if (IS_SET_EXTCOMMUNITY_RT(action)) { + vty_out(vty, " set extcommunity rt %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-rt")); + } else if (IS_SET_EXTCOMMUNITY_NT(action)) { + vty_out(vty, " set extcommunity nt %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-nt")); + } else if (IS_SET_EXTCOMMUNITY_SOO(action)) { + vty_out(vty, " set extcommunity soo %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-soo")); + } else if (IS_SET_EXTCOMMUNITY_LB(action)) { + enum ecommunity_lb_type lb_type; + char str[VTY_BUFSIZ]; + uint32_t bandwidth; + + lb_type = yang_dnode_get_enum( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/lb-type"); + switch (lb_type) { + case EXPLICIT_BANDWIDTH: + bandwidth = yang_dnode_get_uint32( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/bandwidth"); + snprintf(str, sizeof(str), "%d", bandwidth); + break; + case CUMULATIVE_BANDWIDTH: + snprintf(str, sizeof(str), "%s", "cumulative"); + break; + case COMPUTED_BANDWIDTH: + snprintf(str, sizeof(str), "%s", "num-multipaths"); + } + + if (yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-lb/two-octet-as-specific")) + strlcat(str, " non-transitive", sizeof(str)); + + vty_out(vty, " set extcommunity bandwidth %s\n", str); + } else if (IS_SET_EXTCOMMUNITY_COLOR(action)) { + vty_out(vty, " set extcommunity color %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-color")); + } else if (IS_SET_EXTCOMMUNITY_NONE(action)) { + if (yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:extcommunity-none")) + vty_out(vty, " set extcommunity none\n"); + } else if (IS_SET_AGGREGATOR(action)) { + vty_out(vty, " set aggregator as %s %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:aggregator/aggregator-asn"), + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:aggregator/aggregator-address")); + } else if (IS_SET_AS_EXCLUDE(action)) { + vty_out(vty, " set as-path exclude %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:exclude-as-path")); + } else if (IS_SET_AS_REPLACE(action)) { + vty_out(vty, " set as-path replace %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:replace-as-path")); + } else if (IS_SET_AS_PREPEND(action)) { + if (yang_dnode_exists( + dnode, + "./rmap-set-action/frr-bgp-route-map:prepend-as-path")) + vty_out(vty, " set as-path prepend %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:prepend-as-path")); + else { + vty_out(vty, " set as-path prepend last-as %u\n", + yang_dnode_get_uint8( + dnode, + "./rmap-set-action/frr-bgp-route-map:last-as")); + } + } else if (IS_SET_IPV6_NH_GLOBAL(action)) { + vty_out(vty, " set ipv6 next-hop global %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:ipv6-address")); + } else if (IS_SET_IPV6_VPN_NH(action)) { + vty_out(vty, " set ipv6 vpn next-hop %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:ipv6-address")); + } else if (IS_SET_IPV6_PEER_ADDR(action)) { + if (true + == yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:preference")) + vty_out(vty, " set ipv6 next-hop peer-address\n"); + } else if (IS_SET_IPV6_PREFER_GLOBAL(action)) { + if (true + == yang_dnode_get_bool( + dnode, + "./rmap-set-action/frr-bgp-route-map:preference")) + vty_out(vty, " set ipv6 next-hop prefer-global\n"); + } else if (IS_SET_IPV4_VPN_NH(action)) { + vty_out(vty, " set ipv4 vpn next-hop %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:ipv4-address")); + } else if (IS_SET_BGP_IPV4_NH(action)) { + vty_out(vty, " set ip next-hop %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:ipv4-nexthop")); + } else if (IS_SET_BGP_EVPN_GATEWAY_IP_IPV4(action)) { + vty_out(vty, " set evpn gateway-ip ipv4 %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv4")); + } else if (IS_SET_BGP_EVPN_GATEWAY_IP_IPV6(action)) { + vty_out(vty, " set evpn gateway-ip ipv6 %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:evpn-gateway-ip-ipv6")); + } else if (IS_SET_BGP_L3VPN_NEXTHOP_ENCAPSULATION(action)) { + vty_out(vty, " set l3vpn next-hop encapsulation %s\n", + yang_dnode_get_string( + dnode, + "./rmap-set-action/frr-bgp-route-map:l3vpn-nexthop-encapsulation")); + } +} + +DEFPY_YANG( + rmap_onmatch_next, rmap_onmatch_next_cmd, + "on-match next", + "Exit policy on matches\n" + "Next clause\n") +{ + nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_MODIFY, "next"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_rmap_onmatch_next, + no_rmap_onmatch_next_cmd, + "no on-match next", + NO_STR + "Exit policy on matches\n" + "Next clause\n") +{ + nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + rmap_onmatch_goto, rmap_onmatch_goto_cmd, + "on-match goto (1-65535)$rm_num", + "Exit policy on matches\n" + "Goto Clause number\n" + "Number\n") +{ + nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_MODIFY, "goto"); + nb_cli_enqueue_change(vty, "./goto-value", NB_OP_MODIFY, rm_num_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_rmap_onmatch_goto, no_rmap_onmatch_goto_cmd, + "no on-match goto", + NO_STR + "Exit policy on matches\n" + "Goto Clause number\n") +{ + nb_cli_enqueue_change(vty, "./exit-policy", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +/* Cisco/GNU Zebra compatibility aliases */ +ALIAS_YANG( + rmap_onmatch_goto, rmap_continue_cmd, + "continue (1-65535)$rm_num", + "Continue on a different entry within the route-map\n" + "Route-map entry sequence number\n") + +ALIAS_YANG( + no_rmap_onmatch_goto, no_rmap_continue_cmd, + "no continue [(1-65535)]", + NO_STR + "Continue on a different entry within the route-map\n" + "Route-map entry sequence number\n") + +void route_map_exit_policy_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + int exit_policy = yang_dnode_get_enum(dnode, NULL); + + switch (exit_policy) { + case 0: /* permit-or-deny */ + /* NOTHING: default option. */ + break; + case 1: /* next */ + vty_out(vty, " on-match next\n"); + break; + case 2: /* goto */ + vty_out(vty, " on-match goto %s\n", + yang_dnode_get_string(dnode, "../goto-value")); + break; + } +} + +DEFPY_YANG( + rmap_call, rmap_call_cmd, + "call WORD$name", + "Jump to another Route-Map after match+set\n" + "Target route-map name\n") +{ + nb_cli_enqueue_change(vty, "./call", NB_OP_MODIFY, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + no_rmap_call, no_rmap_call_cmd, + "no call [NAME]", + NO_STR + "Jump to another Route-Map after match+set\n" + "Target route-map name\n") +{ + nb_cli_enqueue_change(vty, "./call", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void route_map_call_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " call %s\n", yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG( + rmap_description, rmap_description_cmd, + "description LINE...", + "Route-map comment\n" + "Comment describing this route-map rule\n") +{ + char *desc; + int rv; + + desc = argv_concat(argv, argc, 1); + nb_cli_enqueue_change(vty, "./description", NB_OP_MODIFY, desc); + rv = nb_cli_apply_changes(vty, NULL); + XFREE(MTYPE_TMP, desc); + + return rv; +} + +DEFUN_YANG (no_rmap_description, + no_rmap_description_cmd, + "no description", + NO_STR + "Route-map comment\n") +{ + nb_cli_enqueue_change(vty, "./description", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void route_map_description_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " description %s\n", yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG( + route_map_optimization, route_map_optimization_cmd, + "[no] route-map RMAP_NAME$name optimization", + NO_STR + ROUTE_MAP_CMD_STR + "Configure route-map optimization\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-route-map:lib/route-map[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf( + xpath, sizeof(xpath), + "/frr-route-map:lib/route-map[name='%s']/optimization-disabled", + name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, no ? "true" : "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +void route_map_optimization_disabled_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const char *name = yang_dnode_get_string(dnode, "../name"); + const bool disabled = yang_dnode_get_bool(dnode, NULL); + + vty_out(vty, "%sroute-map %s optimization\n", disabled ? "no " : "", + name); +} + +static int route_map_config_write(struct vty *vty) +{ + const struct lyd_node *dnode; + int written = 0; + + dnode = yang_dnode_get(running_config->dnode, + "/frr-route-map:lib"); + if (dnode) { + nb_cli_show_dnode_cmds(vty, dnode, false); + written = 1; + } + + return written; +} + +/* Route map node structure. */ +static int route_map_config_write(struct vty *vty); +static struct cmd_node rmap_node = { + .name = "routemap", + .node = RMAP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-route-map)# ", + .config_write = route_map_config_write, +}; + +static void rmap_autocomplete(vector comps, struct cmd_token *token) +{ + struct route_map *map; + + for (map = route_map_master.head; map; map = map->next) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, map->name)); +} + +static const struct cmd_variable_handler rmap_var_handlers[] = { + {.varname = "route_map", .completions = rmap_autocomplete}, + {.tokenname = "ROUTEMAP_NAME", .completions = rmap_autocomplete}, + {.tokenname = "RMAP_NAME", .completions = rmap_autocomplete}, + {.completions = NULL} +}; + +void route_map_cli_init(void) +{ + /* Auto complete handler. */ + cmd_variable_handler_register(rmap_var_handlers); + + /* CLI commands. */ + install_node(&rmap_node); + install_default(RMAP_NODE); + install_element(CONFIG_NODE, &route_map_cmd); + install_element(CONFIG_NODE, &no_route_map_cmd); + install_element(CONFIG_NODE, &no_route_map_all_cmd); + install_element(CONFIG_NODE, &route_map_optimization_cmd); + + /* Install the on-match stuff */ + install_element(RMAP_NODE, &rmap_onmatch_next_cmd); + install_element(RMAP_NODE, &no_rmap_onmatch_next_cmd); + install_element(RMAP_NODE, &rmap_onmatch_goto_cmd); + install_element(RMAP_NODE, &no_rmap_onmatch_goto_cmd); + install_element(RMAP_NODE, &rmap_continue_cmd); + install_element(RMAP_NODE, &no_rmap_continue_cmd); + + /* Install the call stuff. */ + install_element(RMAP_NODE, &rmap_call_cmd); + install_element(RMAP_NODE, &no_rmap_call_cmd); + + /* Install description commands. */ + install_element(RMAP_NODE, &rmap_description_cmd); + install_element(RMAP_NODE, &no_rmap_description_cmd); + + /* Install 'match' commands. */ + install_element(RMAP_NODE, &match_interface_cmd); + install_element(RMAP_NODE, &no_match_interface_cmd); + + install_element(RMAP_NODE, &match_ip_address_cmd); + install_element(RMAP_NODE, &no_match_ip_address_cmd); + + install_element(RMAP_NODE, &match_ip_address_prefix_list_cmd); + install_element(RMAP_NODE, &no_match_ip_address_prefix_list_cmd); + + install_element(RMAP_NODE, &match_ip_next_hop_cmd); + install_element(RMAP_NODE, &no_match_ip_next_hop_cmd); + + install_element(RMAP_NODE, &match_ip_next_hop_prefix_list_cmd); + install_element(RMAP_NODE, &no_match_ip_next_hop_prefix_list_cmd); + + install_element(RMAP_NODE, &match_ip_next_hop_type_cmd); + install_element(RMAP_NODE, &no_match_ip_next_hop_type_cmd); + + install_element(RMAP_NODE, &match_ipv6_address_cmd); + install_element(RMAP_NODE, &no_match_ipv6_address_cmd); + + install_element(RMAP_NODE, &match_ipv6_address_prefix_list_cmd); + install_element(RMAP_NODE, &no_match_ipv6_address_prefix_list_cmd); + + install_element(RMAP_NODE, &match_ipv6_next_hop_type_cmd); + install_element(RMAP_NODE, &no_match_ipv6_next_hop_type_cmd); + + install_element(RMAP_NODE, &match_metric_cmd); + install_element(RMAP_NODE, &no_match_metric_cmd); + + install_element(RMAP_NODE, &match_tag_cmd); + install_element(RMAP_NODE, &no_match_tag_cmd); + + /* Install 'set' commands. */ + install_element(RMAP_NODE, &set_ip_nexthop_cmd); + install_element(RMAP_NODE, &no_set_ip_nexthop_cmd); + + install_element(RMAP_NODE, &set_ipv6_nexthop_local_cmd); + install_element(RMAP_NODE, &no_set_ipv6_nexthop_local_cmd); + + install_element(RMAP_NODE, &set_metric_cmd); + install_element(RMAP_NODE, &no_set_metric_cmd); + + install_element(RMAP_NODE, &set_min_metric_cmd); + install_element(RMAP_NODE, &no_set_min_metric_cmd); + + install_element(RMAP_NODE, &set_max_metric_cmd); + install_element(RMAP_NODE, &no_set_max_metric_cmd); + + install_element(RMAP_NODE, &set_tag_cmd); + install_element(RMAP_NODE, &no_set_tag_cmd); + + install_element(RMAP_NODE, &set_srte_color_cmd); + install_element(RMAP_NODE, &no_set_srte_color_cmd); +} diff --git a/lib/routemap_northbound.c b/lib/routemap_northbound.c new file mode 100644 index 0000000..1bba4da --- /dev/null +++ b/lib/routemap_northbound.c @@ -0,0 +1,1594 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Route map northbound implementation. + * + * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/routemap.h" + +/* + * Auxiliary functions to avoid code duplication: + * + * lib_route_map_entry_set_destroy: unset `set` commands. + * lib_route_map_entry_match_destroy: unset `match` commands. + */ +int lib_route_map_entry_match_destroy(struct nb_cb_destroy_args *args) +{ + struct routemap_hook_context *rhc; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rhc = nb_running_get_entry(args->dnode, NULL, true); + if (rhc->rhc_mhook == NULL) + return NB_OK; + + rv = rhc->rhc_mhook(rhc->rhc_rmi, rhc->rhc_rule, NULL, + rhc->rhc_event, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) + return NB_ERR_INCONSISTENCY; + + return NB_OK; +} + +int lib_route_map_entry_set_destroy(struct nb_cb_destroy_args *args) +{ + struct routemap_hook_context *rhc; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rhc = nb_running_get_entry(args->dnode, NULL, true); + if (rhc->rhc_shook == NULL) + return NB_OK; + + rv = rhc->rhc_shook(rhc->rhc_rmi, rhc->rhc_rule, NULL, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) + return NB_ERR_INCONSISTENCY; + + return NB_OK; +} + +/* + * Auxiliary hook context list manipulation functions. + */ +struct routemap_hook_context * +routemap_hook_context_insert(struct route_map_index *rmi) +{ + struct routemap_hook_context *rhc; + + rhc = XCALLOC(MTYPE_TMP, sizeof(*rhc)); + rhc->rhc_rmi = rmi; + TAILQ_INSERT_TAIL(&rmi->rhclist, rhc, rhc_entry); + + return rhc; +} + +void routemap_hook_context_free(struct routemap_hook_context *rhc) +{ + struct route_map_index *rmi = rhc->rhc_rmi; + + TAILQ_REMOVE(&rmi->rhclist, rhc, rhc_entry); + XFREE(MTYPE_TMP, rhc); +} + +/* + * XPath: /frr-route-map:lib/route-map + */ +static int lib_route_map_create(struct nb_cb_create_args *args) +{ + struct route_map *rm; + const char *rm_name; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rm_name = yang_dnode_get_string(args->dnode, "name"); + rm = route_map_get(rm_name); + nb_running_set_entry(args->dnode, rm); + break; + } + + return NB_OK; +} + +static int lib_route_map_destroy(struct nb_cb_destroy_args *args) +{ + struct route_map *rm; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rm = nb_running_unset_entry(args->dnode); + route_map_delete(rm); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/optimization-disabled + */ +static int +lib_route_map_optimization_disabled_modify(struct nb_cb_modify_args *args) +{ + struct route_map *rm; + bool disabled = yang_dnode_get_bool(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rm = nb_running_get_entry(args->dnode, NULL, true); + rm->optimization_disabled = disabled; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry + */ +static int lib_route_map_entry_create(struct nb_cb_create_args *args) +{ + struct route_map_index *rmi; + struct route_map *rm; + uint16_t sequence; + int action; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + sequence = yang_dnode_get_uint16(args->dnode, "sequence"); + action = yang_dnode_get_enum(args->dnode, "action") == 0 + ? RMAP_PERMIT + : RMAP_DENY; + rm = nb_running_get_entry(args->dnode, NULL, true); + rmi = route_map_index_get(rm, action, sequence); + nb_running_set_entry(args->dnode, rmi); + break; + } + + return NB_OK; +} + +static int lib_route_map_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct route_map_index *rmi; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_unset_entry(args->dnode); + route_map_index_delete(rmi, 1); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/description + */ +static int +lib_route_map_entry_description_modify(struct nb_cb_modify_args *args) +{ + struct route_map_index *rmi; + const char *description; + + switch (args->event) { + case NB_EV_VALIDATE: + /* NOTHING */ + break; + case NB_EV_PREPARE: + description = yang_dnode_get_string(args->dnode, NULL); + args->resource->ptr = XSTRDUP(MTYPE_TMP, description); + if (args->resource->ptr == NULL) + return NB_ERR_RESOURCE; + break; + case NB_EV_ABORT: + XFREE(MTYPE_TMP, args->resource->ptr); + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_TMP, rmi->description); + rmi->description = args->resource->ptr; + break; + } + + return NB_OK; +} + +static int +lib_route_map_entry_description_destroy(struct nb_cb_destroy_args *args) +{ + struct route_map_index *rmi; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_TMP, rmi->description); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/action + */ +static int lib_route_map_entry_action_modify(struct nb_cb_modify_args *args) +{ + struct route_map_index *rmi; + struct route_map *map; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + rmi->type = yang_dnode_get_enum(args->dnode, NULL); + map = rmi->map; + + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(map->name); + route_map_notify_dependencies(map->name, + RMAP_EVENT_CALL_ADDED); + } + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/call + */ +static int lib_route_map_entry_call_modify(struct nb_cb_modify_args *args) +{ + struct route_map_index *rmi; + const char *rm_name, *rmn_name; + + switch (args->event) { + case NB_EV_VALIDATE: + rm_name = yang_dnode_get_string(args->dnode, "../../name"); + rmn_name = yang_dnode_get_string(args->dnode, NULL); + /* Don't allow to jump to the same route map instance. */ + if (strcmp(rm_name, rmn_name) == 0) + return NB_ERR_VALIDATION; + + /* TODO: detect circular route map sequences. */ + break; + case NB_EV_PREPARE: + rmn_name = yang_dnode_get_string(args->dnode, NULL); + args->resource->ptr = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmn_name); + break; + case NB_EV_ABORT: + XFREE(MTYPE_ROUTE_MAP_NAME, args->resource->ptr); + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + if (rmi->nextrm) { + route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, + rmi->nextrm, rmi->map->name); + XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm); + } + rmi->nextrm = args->resource->ptr; + route_map_upd8_dependency(RMAP_EVENT_CALL_ADDED, rmi->nextrm, + rmi->map->name); + break; + } + + return NB_OK; +} + +static int lib_route_map_entry_call_destroy(struct nb_cb_destroy_args *args) +{ + struct route_map_index *rmi; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, rmi->nextrm, + rmi->map->name); + XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm); + rmi->nextrm = NULL; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/exit-policy + */ +static int +lib_route_map_entry_exit_policy_modify(struct nb_cb_modify_args *args) +{ + struct route_map_index *rmi; + struct route_map *map; + int rm_action; + int policy; + + switch (args->event) { + case NB_EV_VALIDATE: + policy = yang_dnode_get_enum(args->dnode, NULL); + switch (policy) { + case 0: /* permit-or-deny */ + break; + case 1: /* next */ + case 2: /* goto */ + rm_action = + yang_dnode_get_enum(args->dnode, "../action"); + if (rm_action == 1 /* deny */) { + /* + * On deny it is not possible to 'goto' + * anywhere. + */ + return NB_ERR_VALIDATION; + } + break; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + map = rmi->map; + policy = yang_dnode_get_enum(args->dnode, NULL); + + switch (policy) { + case 0: /* permit-or-deny */ + rmi->exitpolicy = RMAP_EXIT; + break; + case 1: /* next */ + rmi->exitpolicy = RMAP_NEXT; + break; + case 2: /* goto */ + rmi->exitpolicy = RMAP_GOTO; + break; + } + + /* Execute event hook. */ + if (route_map_master.event_hook) { + (*route_map_master.event_hook)(map->name); + route_map_notify_dependencies(map->name, + RMAP_EVENT_CALL_ADDED); + } + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/goto-value + */ +static int lib_route_map_entry_goto_value_modify(struct nb_cb_modify_args *args) +{ + struct route_map_index *rmi; + uint16_t rmi_index; + uint16_t rmi_next; + + switch (args->event) { + case NB_EV_VALIDATE: + rmi_index = yang_dnode_get_uint16(args->dnode, "../sequence"); + rmi_next = yang_dnode_get_uint16(args->dnode, NULL); + if (rmi_next <= rmi_index) { + /* Can't jump backwards on a route map. */ + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + rmi->nextpref = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +static int +lib_route_map_entry_goto_value_destroy(struct nb_cb_destroy_args *args) +{ + struct route_map_index *rmi; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + rmi->nextpref = 0; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition + */ +static int +lib_route_map_entry_match_condition_create(struct nb_cb_create_args *args) +{ + struct routemap_hook_context *rhc; + struct route_map_index *rmi; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + rmi = nb_running_get_entry(args->dnode, NULL, true); + rhc = routemap_hook_context_insert(rmi); + nb_running_set_entry(args->dnode, rhc); + break; + } + + return NB_OK; +} + +static int +lib_route_map_entry_match_condition_destroy(struct nb_cb_destroy_args *args) +{ + struct routemap_hook_context *rhc; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rv = lib_route_map_entry_match_destroy(args); + rhc = nb_running_unset_entry(args->dnode); + routemap_hook_context_free(rhc); + + return rv; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/interface + */ +static int lib_route_map_entry_match_condition_interface_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *ifname; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.match_interface == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = rmap_match_set_hook.no_match_interface; + rhc->rhc_rule = "interface"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + rv = rmap_match_set_hook.match_interface(rhc->rhc_rmi, + "interface", ifname, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_match_condition_interface_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/list-name + */ +static int lib_route_map_entry_match_condition_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *acl; + const char *condition; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook installation, otherwise we can just stop. */ + acl = yang_dnode_get_string(args->dnode, NULL); + rhc = nb_running_get_entry(args->dnode, NULL, true); + condition = yang_dnode_get_string(args->dnode, "../../condition"); + + if (IS_MATCH_IPv4_ADDRESS_LIST(condition)) { + if (rmap_match_set_hook.match_ip_address == NULL) + return NB_OK; + rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_address; + rhc->rhc_rule = "ip address"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + rv = rmap_match_set_hook.match_ip_address( + rhc->rhc_rmi, "ip address", acl, + RMAP_EVENT_FILTER_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv4_PREFIX_LIST(condition)) { + if (rmap_match_set_hook.match_ip_address_prefix_list == NULL) + return NB_OK; + rhc->rhc_mhook = + rmap_match_set_hook.no_match_ip_address_prefix_list; + rhc->rhc_rule = "ip address prefix-list"; + rhc->rhc_event = RMAP_EVENT_PLIST_DELETED; + rv = rmap_match_set_hook.match_ip_address_prefix_list( + rhc->rhc_rmi, "ip address prefix-list", acl, + RMAP_EVENT_PLIST_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv4_NEXTHOP_LIST(condition)) { + if (rmap_match_set_hook.match_ip_next_hop == NULL) + return NB_OK; + rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop; + rhc->rhc_rule = "ip next-hop"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + rv = rmap_match_set_hook.match_ip_next_hop( + rhc->rhc_rmi, "ip next-hop", acl, + RMAP_EVENT_FILTER_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv6_NEXTHOP_LIST(condition)) { + if (rmap_match_set_hook.match_ipv6_next_hop == NULL) + return NB_OK; + rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_next_hop; + rhc->rhc_rule = "ipv6 next-hop"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + rv = rmap_match_set_hook.match_ipv6_next_hop( + rhc->rhc_rmi, "ipv6 next-hop", acl, + RMAP_EVENT_FILTER_ADDED, args->errmsg, + args->errmsg_len); + } else if (IS_MATCH_IPv4_NEXTHOP_PREFIX_LIST(condition)) { + if (rmap_match_set_hook.match_ip_next_hop_prefix_list == NULL) + return NB_OK; + rhc->rhc_mhook = + rmap_match_set_hook.no_match_ip_next_hop_prefix_list; + rhc->rhc_rule = "ip next-hop prefix-list"; + rhc->rhc_event = RMAP_EVENT_PLIST_DELETED; + rv = rmap_match_set_hook.match_ip_next_hop_prefix_list( + rhc->rhc_rmi, "ip next-hop prefix-list", acl, + RMAP_EVENT_PLIST_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv6_NEXTHOP_PREFIX_LIST(condition)) { + if (rmap_match_set_hook.match_ipv6_next_hop_prefix_list == NULL) + return NB_OK; + rhc->rhc_mhook = + rmap_match_set_hook.no_match_ipv6_next_hop_prefix_list; + rhc->rhc_rule = "ipv6 next-hop prefix-list"; + rhc->rhc_event = RMAP_EVENT_PLIST_DELETED; + rv = rmap_match_set_hook.match_ipv6_next_hop_prefix_list( + rhc->rhc_rmi, "ipv6 next-hop prefix-list", acl, + RMAP_EVENT_PLIST_ADDED, args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv6_ADDRESS_LIST(condition)) { + if (rmap_match_set_hook.match_ipv6_address == NULL) + return NB_OK; + rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_address; + rhc->rhc_rule = "ipv6 address"; + rhc->rhc_event = RMAP_EVENT_FILTER_DELETED; + rv = rmap_match_set_hook.match_ipv6_address( + rhc->rhc_rmi, "ipv6 address", acl, + RMAP_EVENT_FILTER_ADDED, + args->errmsg, args->errmsg_len); + } else if (IS_MATCH_IPv6_PREFIX_LIST(condition)) { + if (rmap_match_set_hook.match_ipv6_address_prefix_list == NULL) + return NB_OK; + rhc->rhc_mhook = + rmap_match_set_hook.no_match_ipv6_address_prefix_list; + rhc->rhc_rule = "ipv6 address prefix-list"; + rhc->rhc_event = RMAP_EVENT_PLIST_DELETED; + rv = rmap_match_set_hook.match_ipv6_address_prefix_list( + rhc->rhc_rmi, "ipv6 address prefix-list", acl, + RMAP_EVENT_PLIST_ADDED, + args->errmsg, args->errmsg_len); + } else + rv = CMD_ERR_NO_MATCH; + + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_match_condition_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv4-next-hop-type + */ +static int lib_route_map_entry_match_condition_ipv4_next_hop_type_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.match_ip_next_hop_type == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop_type; + rhc->rhc_rule = "ip next-hop type"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + rv = rmap_match_set_hook.match_ip_next_hop_type( + rhc->rhc_rmi, "ip next-hop type", type, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv6-next-hop-type + */ +static int lib_route_map_entry_match_condition_ipv6_next_hop_type_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.match_ipv6_next_hop_type == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_next_hop_type; + rhc->rhc_rule = "ipv6 next-hop type"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + rv = rmap_match_set_hook.match_ipv6_next_hop_type( + rhc->rhc_rmi, "ipv6 next-hop type", type, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/metric + */ +static int lib_route_map_entry_match_condition_metric_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.match_metric == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = rmap_match_set_hook.no_match_metric; + rhc->rhc_rule = "metric"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + rv = rmap_match_set_hook.match_metric(rhc->rhc_rmi, "metric", + type, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_match_condition_metric_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/match-condition/tag + */ +static int +lib_route_map_entry_match_condition_tag_modify(struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *tag; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.match_tag == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + tag = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = rmap_match_set_hook.no_match_tag; + rhc->rhc_rule = "tag"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + rv = rmap_match_set_hook.match_tag(rhc->rhc_rmi, "tag", tag, + RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_match_condition_tag_destroy(struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_match_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action + */ +static int lib_route_map_entry_set_action_create(struct nb_cb_create_args *args) +{ + return lib_route_map_entry_match_condition_create(args); +} + +static int +lib_route_map_entry_set_action_destroy(struct nb_cb_destroy_args *args) +{ + struct routemap_hook_context *rhc; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rv = lib_route_map_entry_set_destroy(args); + rhc = nb_running_unset_entry(args->dnode); + routemap_hook_context_free(rhc); + + return rv; +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv4-address + */ +static int lib_route_map_entry_set_action_ipv4_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *address; + struct in_addr ia; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + /* + * NOTE: validate if 'action' is 'ipv4-next-hop', + * currently it is not necessary because this is the + * only implemented action. + */ + yang_dnode_get_ipv4(&ia, args->dnode, NULL); + if (ia.s_addr == INADDR_ANY || !ipv4_unicast_valid(&ia)) + return NB_ERR_VALIDATION; + return NB_OK; + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + break; + } + + /* Check for hook function. */ + if (rmap_match_set_hook.set_ip_nexthop == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + address = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_ip_nexthop; + rhc->rhc_rule = "ip next-hop"; + + rv = rmap_match_set_hook.set_ip_nexthop(rhc->rhc_rmi, "ip next-hop", + address, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_set_action_ipv4_address_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv6-address + */ +static int lib_route_map_entry_set_action_ipv6_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *address; + struct in6_addr i6a; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + /* + * NOTE: validate if 'action' is 'ipv6-next-hop', + * currently it is not necessary because this is the + * only implemented action. Other actions might have + * different validations. + */ + yang_dnode_get_ipv6(&i6a, args->dnode, NULL); + if (!IN6_IS_ADDR_LINKLOCAL(&i6a)) + return NB_ERR_VALIDATION; + return NB_OK; + case NB_EV_PREPARE: + case NB_EV_ABORT: + return NB_OK; + case NB_EV_APPLY: + break; + } + + /* Check for hook function. */ + if (rmap_match_set_hook.set_ipv6_nexthop_local == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + address = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_ipv6_nexthop_local; + rhc->rhc_rule = "ipv6 next-hop local"; + + rv = rmap_match_set_hook.set_ipv6_nexthop_local( + rhc->rhc_rmi, "ipv6 next-hop local", address, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int lib_route_map_entry_set_action_ipv6_address_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/value + */ +static int set_action_modify(enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource, const char *value, + char *errmsg, size_t errmsg_len) +{ + struct routemap_hook_context *rhc; + int rv; + + /* + * NOTE: validate if 'action' is 'metric', currently it is not + * necessary because this is the only implemented action. Other + * actions might have different validations. + */ + if (event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_metric == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(dnode, NULL, true); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_metric; + rhc->rhc_rule = "metric"; + + rv = rmap_match_set_hook.set_metric(rhc->rhc_rmi, "metric", + value, + errmsg, errmsg_len + ); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_value_modify(struct nb_cb_modify_args *args) +{ + const char *metric = yang_dnode_get_string(args->dnode, NULL); + + return set_action_modify(args->event, args->dnode, args->resource, + metric, args->errmsg, args->errmsg_len); +} + +static int +lib_route_map_entry_set_action_value_destroy(struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/min-metric + */ +static int set_action_min_metric_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource, + const char *value, char *errmsg, + size_t errmsg_len) +{ + struct routemap_hook_context *rhc; + int rv; + + if (event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_min_metric == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(dnode, NULL, true); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_min_metric; + rhc->rhc_rule = "min-metric"; + + rv = rmap_match_set_hook.set_min_metric(rhc->rhc_rmi, "min-metric", + value, errmsg, errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_min_metric_modify(struct nb_cb_modify_args *args) +{ + const char *min_metric = yang_dnode_get_string(args->dnode, NULL); + + return set_action_min_metric_modify(args->event, args->dnode, + args->resource, min_metric, + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_min_metric_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/max-metric + */ +static int set_action_max_metric_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource, + const char *value, char *errmsg, + size_t errmsg_len) +{ + struct routemap_hook_context *rhc; + int rv; + + if (event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_max_metric == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(dnode, NULL, true); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_max_metric; + rhc->rhc_rule = "max-metric"; + + rv = rmap_match_set_hook.set_max_metric(rhc->rhc_rmi, "max-metric", + value, errmsg, errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_max_metric_modify(struct nb_cb_modify_args *args) +{ + const char *max_metric = yang_dnode_get_string(args->dnode, NULL); + + return set_action_max_metric_modify(args->event, args->dnode, + args->resource, max_metric, + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_max_metric_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/add-metric + */ +static int +lib_route_map_entry_set_action_add_metric_modify(struct nb_cb_modify_args *args) +{ + char metric_str[16]; + + if (args->event == NB_EV_VALIDATE + && yang_dnode_get_uint32(args->dnode, NULL) == 0) { + snprintf(args->errmsg, args->errmsg_len, + "Can't add zero to metric"); + return NB_ERR_VALIDATION; + } + + snprintf(metric_str, sizeof(metric_str), "+%s", + yang_dnode_get_string(args->dnode, NULL)); + return set_action_modify(args->event, args->dnode, args->resource, + metric_str, + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_add_metric_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_action_value_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-metric + */ +static int lib_route_map_entry_set_action_subtract_metric_modify( + struct nb_cb_modify_args *args) +{ + char metric_str[16]; + + if (args->event == NB_EV_VALIDATE + && yang_dnode_get_uint32(args->dnode, NULL) == 0) { + snprintf(args->errmsg, args->errmsg_len, + "Can't subtract zero from metric"); + return NB_ERR_VALIDATION; + } + + snprintf(metric_str, sizeof(metric_str), "-%s", + yang_dnode_get_string(args->dnode, NULL)); + return set_action_modify(args->event, args->dnode, args->resource, + metric_str, + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_subtract_metric_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_action_value_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/use-round-trip-time + */ +static int lib_route_map_entry_set_action_use_round_trip_time_modify( + struct nb_cb_modify_args *args) +{ + return set_action_modify(args->event, args->dnode, args->resource, + "rtt", + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_use_round_trip_time_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_action_value_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/add-round-trip-time + */ +static int lib_route_map_entry_set_action_add_round_trip_time_modify( + struct nb_cb_modify_args *args) +{ + return set_action_modify(args->event, args->dnode, args->resource, + "+rtt", + args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_add_round_trip_time_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_action_value_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-round-trip-time + */ +static int lib_route_map_entry_set_action_subtract_round_trip_time_modify( + struct nb_cb_modify_args *args) +{ + return set_action_modify(args->event, args->dnode, args->resource, + "-rtt", args->errmsg, args->errmsg_len); +} + +static int lib_route_map_entry_set_action_subtract_round_trip_time_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_action_value_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/tag + */ +static int +lib_route_map_entry_set_action_tag_modify(struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *tag; + int rv; + + /* + * NOTE: validate if 'action' is 'tag', currently it is not + * necessary because this is the only implemented action. Other + * actions might have different validations. + */ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_tag == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + tag = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_tag; + rhc->rhc_rule = "tag"; + + rv = rmap_match_set_hook.set_tag(rhc->rhc_rmi, "tag", tag, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_tag_destroy(struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* + * XPath: /frr-route-map:lib/route-map/entry/set-action/policy + */ +static int +lib_route_map_entry_set_action_policy_modify(struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *policy; + int rv; + + /* + * NOTE: validate if 'action' is 'tag', currently it is not + * necessary because this is the only implemented action. Other + * actions might have different validations. + */ + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Check for hook function. */ + if (rmap_match_set_hook.set_srte_color == NULL) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + policy = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = rmap_match_set_hook.no_set_tag; + rhc->rhc_rule = "sr-te color"; + + rv = rmap_match_set_hook.set_tag(rhc->rhc_rmi, "sr-te color", policy, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_shook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int +lib_route_map_entry_set_action_policy_destroy(struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} + +/* clang-format off */ +const struct frr_yang_module_info frr_route_map_info = { + .name = "frr-route-map", + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map", + .cbs = { + .create = lib_route_map_create, + .destroy = lib_route_map_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/optimization-disabled", + .cbs = { + .modify = lib_route_map_optimization_disabled_modify, + .cli_show = route_map_optimization_disabled_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry", + .cbs = { + .create = lib_route_map_entry_create, + .destroy = lib_route_map_entry_destroy, + .cli_cmp = route_map_instance_cmp, + .cli_show = route_map_instance_show, + .cli_show_end = route_map_instance_show_end, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/description", + .cbs = { + .modify = lib_route_map_entry_description_modify, + .destroy = lib_route_map_entry_description_destroy, + .cli_show = route_map_description_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/action", + .cbs = { + .modify = lib_route_map_entry_action_modify, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/call", + .cbs = { + .modify = lib_route_map_entry_call_modify, + .destroy = lib_route_map_entry_call_destroy, + .cli_show = route_map_call_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/exit-policy", + .cbs = { + .modify = lib_route_map_entry_exit_policy_modify, + .cli_show = route_map_exit_policy_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/goto-value", + .cbs = { + .modify = lib_route_map_entry_goto_value_modify, + .destroy = lib_route_map_entry_goto_value_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition", + .cbs = { + .create = lib_route_map_entry_match_condition_create, + .destroy = lib_route_map_entry_match_condition_destroy, + .cli_show = route_map_condition_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/interface", + .cbs = { + .modify = lib_route_map_entry_match_condition_interface_modify, + .destroy = lib_route_map_entry_match_condition_interface_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/list-name", + .cbs = { + .modify = lib_route_map_entry_match_condition_list_name_modify, + .destroy = lib_route_map_entry_match_condition_list_name_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/ipv4-next-hop-type", + .cbs = { + .modify = lib_route_map_entry_match_condition_ipv4_next_hop_type_modify, + .destroy = lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/ipv6-next-hop-type", + .cbs = { + .modify = lib_route_map_entry_match_condition_ipv6_next_hop_type_modify, + .destroy = lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/metric", + .cbs = { + .modify = lib_route_map_entry_match_condition_metric_modify, + .destroy = lib_route_map_entry_match_condition_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/tag", + .cbs = { + .modify = lib_route_map_entry_match_condition_tag_modify, + .destroy = lib_route_map_entry_match_condition_tag_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action", + .cbs = { + .create = lib_route_map_entry_set_action_create, + .destroy = lib_route_map_entry_set_action_destroy, + .cli_show = route_map_action_show, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/ipv4-address", + .cbs = { + .modify = lib_route_map_entry_set_action_ipv4_address_modify, + .destroy = lib_route_map_entry_set_action_ipv4_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/ipv6-address", + .cbs = { + .modify = lib_route_map_entry_set_action_ipv6_address_modify, + .destroy = lib_route_map_entry_set_action_ipv6_address_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/value", + .cbs = { + .modify = lib_route_map_entry_set_action_value_modify, + .destroy = lib_route_map_entry_set_action_value_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/min-metric", + .cbs = { + .modify = lib_route_map_entry_set_action_min_metric_modify, + .destroy = lib_route_map_entry_set_action_min_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/max-metric", + .cbs = { + .modify = lib_route_map_entry_set_action_max_metric_modify, + .destroy = lib_route_map_entry_set_action_max_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/add-metric", + .cbs = { + .modify = lib_route_map_entry_set_action_add_metric_modify, + .destroy = lib_route_map_entry_set_action_add_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/subtract-metric", + .cbs = { + .modify = lib_route_map_entry_set_action_subtract_metric_modify, + .destroy = lib_route_map_entry_set_action_subtract_metric_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/use-round-trip-time", + .cbs = { + .modify = lib_route_map_entry_set_action_use_round_trip_time_modify, + .destroy = lib_route_map_entry_set_action_use_round_trip_time_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/add-round-trip-time", + .cbs = { + .modify = lib_route_map_entry_set_action_add_round_trip_time_modify, + .destroy = lib_route_map_entry_set_action_add_round_trip_time_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/subtract-round-trip-time", + .cbs = { + .modify = lib_route_map_entry_set_action_subtract_round_trip_time_modify, + .destroy = lib_route_map_entry_set_action_subtract_round_trip_time_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/tag", + .cbs = { + .modify = lib_route_map_entry_set_action_tag_modify, + .destroy = lib_route_map_entry_set_action_tag_destroy, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/policy", + .cbs = { + .modify = lib_route_map_entry_set_action_policy_modify, + .destroy = lib_route_map_entry_set_action_policy_destroy, + } + }, + + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_route_map_cli_info = { + .name = "frr-route-map", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map/optimization-disabled", + .cbs.cli_show = route_map_optimization_disabled_show, + }, + { + .xpath = "/frr-route-map:lib/route-map/entry", + .cbs = { + .cli_cmp = route_map_instance_cmp, + .cli_show = route_map_instance_show, + .cli_show_end = route_map_instance_show_end, + } + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/description", + .cbs.cli_show = route_map_description_show, + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/call", + .cbs.cli_show = route_map_call_show, + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/exit-policy", + .cbs.cli_show = route_map_exit_policy_show, + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition", + .cbs.cli_show = route_map_condition_show, + }, + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action", + .cbs.cli_show = route_map_action_show, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/lib/routing_nb.c b/lib/routing_nb.c new file mode 100644 index 0000000..33372d1 --- /dev/null +++ b/lib/routing_nb.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Vmware + * Vishal Dhingra + */ +#include + +#include "northbound.h" +#include "libfrr.h" +#include "routing_nb.h" + + + +/* clang-format off */ +const struct frr_yang_module_info frr_routing_info = { + .name = "frr-routing", + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_routing_cli_info = { + .name = "frr-routing", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = NULL, + }, + } +}; diff --git a/lib/routing_nb.h b/lib/routing_nb.h new file mode 100644 index 0000000..26b4cf0 --- /dev/null +++ b/lib/routing_nb.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef _FRR_ROUTING_NB_H_ +#define _FRR_ROUTING_NB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct frr_yang_module_info frr_routing_info; +extern const struct frr_yang_module_info frr_routing_cli_info; + +/* Mandatory callbacks. */ +int routing_control_plane_protocols_control_plane_protocol_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_destroy( + struct nb_cb_destroy_args *args); + +#define FRR_ROUTING_XPATH \ + "/frr-routing:routing/control-plane-protocols/control-plane-protocol" + +#define FRR_ROUTING_KEY_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']" + +#define FRR_ROUTING_KEY_XPATH_VRF \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[vrf='%s']" + +/* + * callbacks for routing to handle configuration events + * based on the control plane protocol + */ +DECLARE_HOOK(routing_conf_event, (struct nb_cb_create_args *args), (args)); +DECLARE_HOOK(routing_create, (struct nb_cb_create_args *args), (args)); +DECLARE_KOOH(routing_destroy, (struct nb_cb_destroy_args *args), (args)); + +void routing_control_plane_protocols_register_vrf_dependency(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ROUTING_NB_H_ */ diff --git a/lib/routing_nb_config.c b/lib/routing_nb_config.c new file mode 100644 index 0000000..d532279 --- /dev/null +++ b/lib/routing_nb_config.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Vmware + * Vishal Dhingra + */ + +#include + +#include "northbound.h" +#include "libfrr.h" +#include "vrf.h" +#include "lib_errors.h" +#include "routing_nb.h" + + +DEFINE_HOOK(routing_conf_event, (struct nb_cb_create_args *args), (args)); +DEFINE_HOOK(routing_create, (struct nb_cb_create_args *args), (args)); +DEFINE_KOOH(routing_destroy, (struct nb_cb_destroy_args *args), (args)); + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol + */ + +int routing_control_plane_protocols_control_plane_protocol_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + const char *vrfname; + + switch (args->event) { + case NB_EV_VALIDATE: + if (hook_call(routing_conf_event, args)) + return NB_ERR_VALIDATION; + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* + * If the daemon relies on the VRF pointer stored in this + * dnode, then it should register the dependency between this + * module and the VRF module using + * routing_control_plane_protocols_register_vrf_dependency. + * If such dependency is not registered, then nothing is + * stored in the dnode. If the dependency is registered, + * find the vrf and store the pointer. + */ + if (nb_node_has_dependency(args->dnode->schema->priv)) { + vrfname = yang_dnode_get_string(args->dnode, "vrf"); + vrf = vrf_lookup_by_name(vrfname); + assert(vrf); + nb_running_set_entry(args->dnode, vrf); + } + hook_call(routing_create, args); + break; + }; + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + hook_call(routing_destroy, args); + + /* + * If dependency on VRF module is registered, then VRF + * pointer was stored and must be cleared. + */ + if (nb_node_has_dependency(args->dnode->schema->priv)) + nb_running_unset_entry(args->dnode); + + return NB_OK; +} + +static void vrf_to_control_plane_protocol(const struct lyd_node *dnode, + char *xpath) +{ + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "name"); + + snprintf(xpath, XPATH_MAXLEN, FRR_ROUTING_KEY_XPATH_VRF, vrf); +} + +static void control_plane_protocol_to_vrf(const struct lyd_node *dnode, + char *xpath) +{ + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "vrf"); + + snprintf(xpath, XPATH_MAXLEN, FRR_VRF_KEY_XPATH, vrf); +} + +void routing_control_plane_protocols_register_vrf_dependency(void) +{ + struct nb_dependency_callbacks cbs; + + cbs.get_dependant_xpath = vrf_to_control_plane_protocol; + cbs.get_dependency_xpath = control_plane_protocol_to_vrf; + + nb_node_set_dependency_cbs(FRR_VRF_XPATH, FRR_ROUTING_XPATH, &cbs); +} diff --git a/lib/sbuf.c b/lib/sbuf.c new file mode 100644 index 0000000..2762e44 --- /dev/null +++ b/lib/sbuf.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple string buffer + * + * Copyright (C) 2017 Christian Franke + */ +#include + +#include "printfrr.h" +#include "sbuf.h" +#include "memory.h" + +void sbuf_init(struct sbuf *dest, char *buf, size_t size) +{ + dest->fixed = (size > 0); + if (dest->fixed) { + dest->buf = buf; + dest->size = size; + } else { + dest->buf = XMALLOC(MTYPE_TMP, SBUF_DEFAULT_SIZE); + dest->size = SBUF_DEFAULT_SIZE; + } + + dest->pos = 0; + dest->buf[0] = '\0'; +} + +void sbuf_reset(struct sbuf *dest) +{ + dest->pos = 0; + dest->buf[0] = '\0'; +} + +const char *sbuf_buf(struct sbuf *buf) +{ + return buf->buf; +} + +void sbuf_free(struct sbuf *buf) +{ + if (!buf->fixed) + XFREE(MTYPE_TMP, buf->buf); +} + +void sbuf_push(struct sbuf *buf, int indent, const char *format, ...) +{ + va_list args; + int written; + + if (!buf->fixed) { + int written1, written2; + size_t new_size; + + written1 = indent; + va_start(args, format); + written2 = vsnprintfrr(NULL, 0, format, args); + va_end(args); + + new_size = buf->size; + if (written1 >= 0 && written2 >= 0) { + while (buf->pos + written1 + written2 >= new_size) + new_size *= 2; + if (new_size > buf->size) { + buf->buf = + XREALLOC(MTYPE_TMP, buf->buf, new_size); + buf->size = new_size; + } + } + } + + written = snprintf(buf->buf + buf->pos, buf->size - buf->pos, "%*s", + indent, ""); + + if (written >= 0) + buf->pos += written; + if (buf->pos > buf->size) + buf->pos = buf->size; + + va_start(args, format); + written = vsnprintfrr(buf->buf + buf->pos, buf->size - buf->pos, + format, args); + va_end(args); + + if (written >= 0) + buf->pos += written; + if (buf->pos > buf->size) + buf->pos = buf->size; + + if (buf->pos == buf->size) + assert(!"Buffer filled up!"); +} diff --git a/lib/sbuf.h b/lib/sbuf.h new file mode 100644 index 0000000..a73875a --- /dev/null +++ b/lib/sbuf.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple string buffer + * + * Copyright (C) 2017 Christian Franke + */ +#ifndef SBUF_H +#define SBUF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * sbuf provides a simple string buffer. One application where this comes + * in handy is the parsing of binary data: If there is an error in the parsing + * process due to invalid input data, printing an error message explaining what + * went wrong is definitely useful. However, just printing the actual error, + * without any information about the previous parsing steps, is usually not very + * helpful. + * Using sbuf, the parser can log the whole parsing process into a buffer using + * a printf like API. When an error occurs, all the information about previous + * parsing steps is there in the log, without any need for backtracking, and can + * be used to give a detailed and useful error description. + * When parsing completes successfully without any error, the log can just be + * discarded unless debugging is turned on, to not spam the log. + * + * For the described usecase, the code would look something like this: + * + * int sbuf_example(..., char **parser_log) + * { + * struct sbuf logbuf; + * + * sbuf_init(&logbuf, NULL, 0); + * sbuf_push(&logbuf, 0, "Starting parser\n"); + * + * int rv = do_parse(&logbuf, ...); + * + * *parser_log = sbuf_buf(&logbuf); + * + * return 1; + * } + * + * In this case, sbuf_example uses a string buffer with undefined size, which + * will + * be allocated on the heap by sbuf. The caller of sbuf_example is expected to + * free + * the string returned in parser_log. + */ + +#define SBUF_DEFAULT_SIZE 8192 + +struct sbuf { + bool fixed; + char *buf; + size_t size; + size_t pos; + int indent; +}; + +void sbuf_init(struct sbuf *dest, char *buf, size_t size); +void sbuf_reset(struct sbuf *buf); +const char *sbuf_buf(struct sbuf *buf); +void sbuf_free(struct sbuf *buf); +#include "lib/log.h" +void sbuf_push(struct sbuf *buf, int indent, const char *format, ...) + PRINTFRR(3, 4); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/segment_routing.c b/lib/segment_routing.c new file mode 100644 index 0000000..f45a64d --- /dev/null +++ b/lib/segment_routing.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * segment_routing.c: Segment-Routing Library + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#include "segment_routing.h" + +const char *sr_algorithm_string(uint8_t algo) +{ + switch (algo) { + case SR_ALGORITHM_SPF: + return "SPF"; + case SR_ALGORITHM_STRICT_SPF: + return "Strict SPF"; + case SR_ALGORITHM_UNSET: + return "Unset"; + default: + return algo >= SR_ALGORITHM_FLEX_MIN ? "Flex-Algo" : "Unknown"; + } +} diff --git a/lib/segment_routing.h b/lib/segment_routing.h new file mode 100644 index 0000000..5e71466 --- /dev/null +++ b/lib/segment_routing.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/********************************************************************* + * Copyright 2022 Hiroki Shirokura, LINE Corporation + * Copyright 2022 Masakazu Asama + * Copyright 2022 6WIND S.A. + * + * segment_routing.h: Segment-Routing Library + * + * Authors + * ------- + * Hiroki Shirokura + * Masakazu Asama + * Louis Scalbert + */ + +#ifndef _FRR_SR_H +#define _FRR_SR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * IGP Algorithm Types + * https://www.iana.org/assignments/igp-parameters/igp-parameters.xhtml + */ +#define SR_ALGORITHM_SPF 0 /* RFC8665 */ +#define SR_ALGORITHM_STRICT_SPF 1 /* RFC8665 */ +#define SR_ALGORITHM_UNSET 127 /* FRRouting defined */ +#define SR_ALGORITHM_FLEX_MIN 128 /* RFC9350 Flex-Algorithm */ +#define SR_ALGORITHM_FLEX_MAX 255 /* RFC9350 Flex-Algorithm */ +#define SR_ALGORITHM_COUNT 256 + +const char *sr_algorithm_string(uint8_t algo); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_SR_H */ diff --git a/lib/seqlock.c b/lib/seqlock.c new file mode 100644 index 0000000..62ce316 --- /dev/null +++ b/lib/seqlock.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * "Sequence" lock primitive + * + * Copyright (C) 2015 David Lamparter + */ + +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "seqlock.h" + +/**************************************** + * OS specific synchronization wrappers * + ****************************************/ + +/* + * Linux: sys_futex() + */ +#ifdef HAVE_SYNC_LINUX_FUTEX +#include +#include + +static long sys_futex(void *addr1, int op, int val1, + const struct timespec *timeout, void *addr2, int val3) +{ + return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3); +} + +#define wait_once(sqlo, val) \ + sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) +#define wait_time(sqlo, val, time, reltime) \ + sys_futex((int *)&sqlo->pos, FUTEX_WAIT_BITSET, (int)val, time, \ + NULL, ~0U) +#define wait_poke(sqlo) \ + sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) + +/* + * OpenBSD: sys_futex(), almost the same as on Linux + */ +#elif defined(HAVE_SYNC_OPENBSD_FUTEX) +#include +#include + +#define TIME_RELATIVE 1 + +#define wait_once(sqlo, val) \ + futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0) +#define wait_time(sqlo, val, time, reltime) \ + futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, reltime, NULL, 0) +#define wait_poke(sqlo) \ + futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0) + +/* + * FreeBSD: _umtx_op() + */ +#elif defined(HAVE_SYNC_UMTX_OP) +#include + +#define wait_once(sqlo, val) \ + _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL) +static int wait_time(struct seqlock *sqlo, uint32_t val, + const struct timespec *abstime, + const struct timespec *reltime) +{ + struct _umtx_time t; + t._flags = UMTX_ABSTIME; + t._clockid = CLOCK_MONOTONIC; + memcpy(&t._timeout, abstime, sizeof(t._timeout)); + return _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, + (void *)(uintptr_t) sizeof(t), &t); +} +#define wait_poke(sqlo) \ + _umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL) + +/* + * generic version. used on NetBSD, Solaris and OSX. really shitty. + */ +#else + +#define TIME_ABS_REALTIME 1 + +#define wait_init(sqlo) do { \ + pthread_mutex_init(&sqlo->lock, NULL); \ + pthread_cond_init(&sqlo->wake, NULL); \ + } while (0) +#define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock) +#define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock) +#define wait_time(sqlo, val, time, reltime) \ + pthread_cond_timedwait(&sqlo->wake, \ + &sqlo->lock, time); +#define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock) +#define wait_poke(sqlo) do { \ + pthread_mutex_lock(&sqlo->lock); \ + pthread_cond_broadcast(&sqlo->wake); \ + pthread_mutex_unlock(&sqlo->lock); \ + } while (0) + +#endif + +#ifndef wait_init +#define wait_init(sqlo) /**/ +#define wait_prep(sqlo) /**/ +#define wait_done(sqlo) /**/ +#endif /* wait_init */ + + +void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val) +{ + seqlock_val_t cur, cal; + + seqlock_assert_valid(val); + + wait_prep(sqlo); + cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); + + while (cur & SEQLOCK_HELD) { + cal = SEQLOCK_VAL(cur) - val - 1; + assert(cal < 0x40000000 || cal > 0xc0000000); + if (cal < 0x80000000) + break; + + if ((cur & SEQLOCK_WAITERS) + || atomic_compare_exchange_weak_explicit( + &sqlo->pos, &cur, cur | SEQLOCK_WAITERS, + memory_order_relaxed, memory_order_relaxed)) { + wait_once(sqlo, cur | SEQLOCK_WAITERS); + cur = atomic_load_explicit(&sqlo->pos, + memory_order_relaxed); + } + /* else: we failed to swap in cur because it just changed */ + } + wait_done(sqlo); +} + +bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val, + const struct timespec *abs_monotime_limit) +{ +/* + * ABS_REALTIME - used on NetBSD, Solaris and OSX + */ +#ifdef TIME_ABS_REALTIME +#define time_arg1 &abs_rt +#define time_arg2 NULL +#define time_prep + struct timespec curmono, abs_rt; + + clock_gettime(CLOCK_MONOTONIC, &curmono); + clock_gettime(CLOCK_REALTIME, &abs_rt); + + abs_rt.tv_nsec += abs_monotime_limit->tv_nsec - curmono.tv_nsec; + if (abs_rt.tv_nsec < 0) { + abs_rt.tv_sec--; + abs_rt.tv_nsec += 1000000000; + } else if (abs_rt.tv_nsec >= 1000000000) { + abs_rt.tv_sec++; + abs_rt.tv_nsec -= 1000000000; + } + abs_rt.tv_sec += abs_monotime_limit->tv_sec - curmono.tv_sec; + +/* + * RELATIVE - used on OpenBSD (might get a patch to get absolute monotime) + */ +#elif defined(TIME_RELATIVE) + struct timespec reltime; + +#define time_arg1 abs_monotime_limit +#define time_arg2 &reltime +#define time_prep \ + clock_gettime(CLOCK_MONOTONIC, &reltime); \ + reltime.tv_sec = abs_monotime_limit.tv_sec - reltime.tv_sec; \ + reltime.tv_nsec = abs_monotime_limit.tv_nsec - reltime.tv_nsec; \ + if (reltime.tv_nsec < 0) { \ + reltime.tv_sec--; \ + reltime.tv_nsec += 1000000000; \ + } +/* + * FreeBSD & Linux: absolute time re. CLOCK_MONOTONIC + */ +#else +#define time_arg1 abs_monotime_limit +#define time_arg2 NULL +#define time_prep +#endif + + bool ret = true; + seqlock_val_t cur, cal; + + seqlock_assert_valid(val); + + wait_prep(sqlo); + cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); + + while (cur & SEQLOCK_HELD) { + cal = SEQLOCK_VAL(cur) - val - 1; + assert(cal < 0x40000000 || cal > 0xc0000000); + if (cal < 0x80000000) + break; + + if ((cur & SEQLOCK_WAITERS) + || atomic_compare_exchange_weak_explicit( + &sqlo->pos, &cur, cur | SEQLOCK_WAITERS, + memory_order_relaxed, memory_order_relaxed)) { + int rv; + + time_prep + + rv = wait_time(sqlo, cur | SEQLOCK_WAITERS, time_arg1, + time_arg2); + if (rv) { + ret = false; + break; + } + cur = atomic_load_explicit(&sqlo->pos, + memory_order_relaxed); + } + } + wait_done(sqlo); + + return ret; +} + +bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val) +{ + seqlock_val_t cur; + + seqlock_assert_valid(val); + + cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); + if (!(cur & SEQLOCK_HELD)) + return true; + cur = SEQLOCK_VAL(cur) - val - 1; + assert(cur < 0x40000000 || cur > 0xc0000000); + return cur < 0x80000000; +} + +void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val) +{ + seqlock_val_t prev; + + seqlock_assert_valid(val); + + prev = atomic_exchange_explicit(&sqlo->pos, val, memory_order_relaxed); + if (prev & SEQLOCK_WAITERS) + wait_poke(sqlo); +} + +void seqlock_release(struct seqlock *sqlo) +{ + seqlock_val_t prev; + + prev = atomic_exchange_explicit(&sqlo->pos, 0, memory_order_relaxed); + if (prev & SEQLOCK_WAITERS) + wait_poke(sqlo); +} + +void seqlock_init(struct seqlock *sqlo) +{ + sqlo->pos = 0; + wait_init(sqlo); +} + + +seqlock_val_t seqlock_cur(struct seqlock *sqlo) +{ + return SEQLOCK_VAL(atomic_load_explicit(&sqlo->pos, + memory_order_relaxed)); +} + +seqlock_val_t seqlock_bump(struct seqlock *sqlo) +{ + seqlock_val_t val, cur; + + cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed); + seqlock_assert_valid(cur); + + do { + val = SEQLOCK_VAL(cur) + SEQLOCK_INCR; + } while (!atomic_compare_exchange_weak_explicit(&sqlo->pos, &cur, val, + memory_order_relaxed, memory_order_relaxed)); + + if (cur & SEQLOCK_WAITERS) + wait_poke(sqlo); + return val; +} diff --git a/lib/seqlock.h b/lib/seqlock.h new file mode 100644 index 0000000..0173581 --- /dev/null +++ b/lib/seqlock.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * "Sequence" lock primitive + * + * Copyright (C) 2015 David Lamparter + */ + +#ifndef _SEQLOCK_H +#define _SEQLOCK_H + +#include +#include +#include +#include "frratomic.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * this locking primitive is intended to use in a 1:N setup. + * + * - one "counter" seqlock issuing increasing numbers + * - multiple seqlock users hold references on these numbers + * + * this is intended for implementing RCU reference-holding. There is one + * global counter, with threads locking a seqlock whenever they take a + * reference. A seqlock can also be idle/unlocked. + * + * The "counter" seqlock will always stay locked; the RCU cleanup thread + * continuously counts it up, waiting for threads to release or progress to a + * sequence number further ahead. If all threads are > N, references dropped + * in N can be free'd. + * + * generally, the lock function is: + * + * Thread-A Thread-B + * + * seqlock_acquire(a) + * | running seqlock_wait(b) -- a <= b + * seqlock_release() | blocked + * OR: seqlock_acquire(a') | -- a' > b + * (resumes) + */ + +/* use sequentially increasing "ticket numbers". lowest bit will always + * be 1 to have a 'cleared' indication (i.e., counts 1,5,9,13,etc. ) + * 2nd lowest bit is used to indicate we have waiters. + */ +typedef _Atomic uint32_t seqlock_ctr_t; +typedef uint32_t seqlock_val_t; +#define seqlock_assert_valid(val) assert((val) & SEQLOCK_HELD) + +/* NB: SEQLOCK_WAITERS is only allowed if SEQLOCK_HELD is also set; can't + * have waiters on an unheld seqlock + */ +#define SEQLOCK_HELD (1U << 0) +#define SEQLOCK_WAITERS (1U << 1) +#define SEQLOCK_VAL(n) ((n) & ~SEQLOCK_WAITERS) +#define SEQLOCK_STARTVAL 1U +#define SEQLOCK_INCR 4U + +/* TODO: originally, this was using "atomic_fetch_add", which is the reason + * bit 0 is used to indicate held state. With SEQLOCK_WAITERS added, there's + * no fetch_add anymore (cmpxchg loop instead), so we don't need to use bit 0 + * for this anymore & can just special-case the value 0 for it and skip it in + * counting. + */ + +struct seqlock { +/* always used */ + seqlock_ctr_t pos; +/* used when futexes not available: (i.e. non-linux) */ + pthread_mutex_t lock; + pthread_cond_t wake; +}; + + +/* sqlo = 0 - init state: not held */ +extern void seqlock_init(struct seqlock *sqlo); + + +/* basically: "while (sqlo <= val) wait();" + * returns when sqlo > val || !seqlock_held(sqlo) + */ +extern void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val); + +/* same, but time-limited (limit is an absolute CLOCK_MONOTONIC value) */ +extern bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val, + const struct timespec *abs_monotime_limit); + +/* one-shot test, returns true if seqlock_wait would return immediately */ +extern bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val); + +static inline bool seqlock_held(struct seqlock *sqlo) +{ + return !!atomic_load_explicit(&sqlo->pos, memory_order_relaxed); +} + +/* sqlo - get seqlock position -- for the "counter" seqlock */ +extern seqlock_val_t seqlock_cur(struct seqlock *sqlo); + +/* ++sqlo (but atomic & wakes waiters) - returns value that we bumped to. + * + * guarantees: + * - each seqlock_bump call bumps the position by exactly one SEQLOCK_INCR. + * There are no skipped/missed or multiple increments. + * - each return value is only returned from one seqlock_bump() call + */ +extern seqlock_val_t seqlock_bump(struct seqlock *sqlo); + + +/* sqlo = val - can be used on held seqlock. */ +extern void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val); + +/* sqlo = ref - standard pattern: acquire relative to other seqlock */ +static inline void seqlock_acquire(struct seqlock *sqlo, struct seqlock *ref) +{ + seqlock_acquire_val(sqlo, seqlock_cur(ref)); +} + +/* sqlo = 0 - set seqlock position to 0, marking as non-held */ +extern void seqlock_release(struct seqlock *sqlo); +/* release should normally be followed by a bump on the "counter", if + * anything other than reading RCU items was done + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SEQLOCK_H */ diff --git a/lib/sha256.c b/lib/sha256.c new file mode 100644 index 0000000..08e08eb --- /dev/null +++ b/lib/sha256.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: BSD-2-Clause +/*- + * Copyright 2005,2007,2009 Colin Percival + * All rights reserved. + */ + +#include +#include "sha256.h" + +#if !HAVE_DECL_BE32DEC +static inline uint32_t be32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); +} +#endif + +#if !HAVE_DECL_BE32ENC +static inline void be32enc(void *pp, uint32_t x) +{ + uint8_t *p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} +#endif + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (unsigned char) in big-endian form. Assumes len is a multiple of 4. + */ +static void be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (unsigned char) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + t0 = h + S1(e) + Ch(e, f, g) + k; \ + t1 = S0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, k) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], S[(66 - i) % 8], \ + S[(67 - i) % 8], S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], W[i] + k) + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void SHA256_Transform(uint32_t *state, const unsigned char block[64]) +{ + uint32_t W[64]; + uint32_t S[8]; + uint32_t t0, t1; + int i; + + /* 1. Prepare message schedule W. */ + be32dec_vect(W, block, 64); + for (i = 16; i < 64; i++) + W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + RNDr(S, W, 0, 0x428a2f98); + RNDr(S, W, 1, 0x71374491); + RNDr(S, W, 2, 0xb5c0fbcf); + RNDr(S, W, 3, 0xe9b5dba5); + RNDr(S, W, 4, 0x3956c25b); + RNDr(S, W, 5, 0x59f111f1); + RNDr(S, W, 6, 0x923f82a4); + RNDr(S, W, 7, 0xab1c5ed5); + RNDr(S, W, 8, 0xd807aa98); + RNDr(S, W, 9, 0x12835b01); + RNDr(S, W, 10, 0x243185be); + RNDr(S, W, 11, 0x550c7dc3); + RNDr(S, W, 12, 0x72be5d74); + RNDr(S, W, 13, 0x80deb1fe); + RNDr(S, W, 14, 0x9bdc06a7); + RNDr(S, W, 15, 0xc19bf174); + RNDr(S, W, 16, 0xe49b69c1); + RNDr(S, W, 17, 0xefbe4786); + RNDr(S, W, 18, 0x0fc19dc6); + RNDr(S, W, 19, 0x240ca1cc); + RNDr(S, W, 20, 0x2de92c6f); + RNDr(S, W, 21, 0x4a7484aa); + RNDr(S, W, 22, 0x5cb0a9dc); + RNDr(S, W, 23, 0x76f988da); + RNDr(S, W, 24, 0x983e5152); + RNDr(S, W, 25, 0xa831c66d); + RNDr(S, W, 26, 0xb00327c8); + RNDr(S, W, 27, 0xbf597fc7); + RNDr(S, W, 28, 0xc6e00bf3); + RNDr(S, W, 29, 0xd5a79147); + RNDr(S, W, 30, 0x06ca6351); + RNDr(S, W, 31, 0x14292967); + RNDr(S, W, 32, 0x27b70a85); + RNDr(S, W, 33, 0x2e1b2138); + RNDr(S, W, 34, 0x4d2c6dfc); + RNDr(S, W, 35, 0x53380d13); + RNDr(S, W, 36, 0x650a7354); + RNDr(S, W, 37, 0x766a0abb); + RNDr(S, W, 38, 0x81c2c92e); + RNDr(S, W, 39, 0x92722c85); + RNDr(S, W, 40, 0xa2bfe8a1); + RNDr(S, W, 41, 0xa81a664b); + RNDr(S, W, 42, 0xc24b8b70); + RNDr(S, W, 43, 0xc76c51a3); + RNDr(S, W, 44, 0xd192e819); + RNDr(S, W, 45, 0xd6990624); + RNDr(S, W, 46, 0xf40e3585); + RNDr(S, W, 47, 0x106aa070); + RNDr(S, W, 48, 0x19a4c116); + RNDr(S, W, 49, 0x1e376c08); + RNDr(S, W, 50, 0x2748774c); + RNDr(S, W, 51, 0x34b0bcb5); + RNDr(S, W, 52, 0x391c0cb3); + RNDr(S, W, 53, 0x4ed8aa4a); + RNDr(S, W, 54, 0x5b9cca4f); + RNDr(S, W, 55, 0x682e6ff3); + RNDr(S, W, 56, 0x748f82ee); + RNDr(S, W, 57, 0x78a5636f); + RNDr(S, W, 58, 0x84c87814); + RNDr(S, W, 59, 0x8cc70208); + RNDr(S, W, 60, 0x90befffa); + RNDr(S, W, 61, 0xa4506ceb); + RNDr(S, W, 62, 0xbef9a3f7); + RNDr(S, W, 63, 0xc67178f2); + + /* 4. Mix local working variables into global state */ + for (i = 0; i < 8; i++) + state[i] += S[i]; + + /* Clean the stack. */ + explicit_bzero(W, 256); + explicit_bzero(S, 32); + explicit_bzero(&t0, sizeof(t0)); + explicit_bzero(&t1, sizeof(t0)); +} + +static unsigned char PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* Add padding and terminating bit-count. */ +static void SHA256_Pad(SHA256_CTX *ctx) +{ + unsigned char len[8]; + uint32_t r, plen; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be32enc_vect(len, ctx->count, 8); + + /* Add 1--64 bytes so that the resulting length is 56 mod 64 */ + r = (ctx->count[1] >> 3) & 0x3f; + plen = (r < 56) ? (56 - r) : (120 - r); + SHA256_Update(ctx, PAD, (size_t)plen); + + /* Add the terminating bit-count */ + SHA256_Update(ctx, len, 8); +} + +/* SHA-256 initialization. Begins a SHA-256 operation. */ +void SHA256_Init(SHA256_CTX *ctx) +{ + + /* Zero bits processed so far */ + ctx->count[0] = ctx->count[1] = 0; + + /* Magic initialization constants */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +/* Add bytes into the hash */ +void SHA256_Update(SHA256_CTX *ctx, const void *in, size_t len) +{ + uint32_t bitlen[2]; + uint32_t r; + const unsigned char *src = in; + + /* Number of bytes left in the buffer from previous updates */ + r = (ctx->count[1] >> 3) & 0x3f; + + /* Convert the length into a number of bits */ + bitlen[1] = ((uint32_t)len) << 3; + bitlen[0] = (uint32_t)(len >> 29); + + /* Update number of bits */ + if ((ctx->count[1] += bitlen[1]) < bitlen[1]) + ctx->count[0]++; + ctx->count[0] += bitlen[0]; + + /* Handle the case where we don't need to perform any transforms */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks */ + while (len >= 64) { + SHA256_Transform(ctx->state, src); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer */ + memcpy(ctx->buf, src, len); +} + +/* + * SHA-256 finalization. Pads the input data, exports the hash value, + * and clears the context state. + */ +void SHA256_Final(unsigned char digest[32], SHA256_CTX *ctx) +{ + + /* Add padding */ + SHA256_Pad(ctx); + + /* Write the hash */ + be32enc_vect(digest, ctx->state, 32); + + /* Clear the context state */ + explicit_bzero((void *)ctx, sizeof(*ctx)); +} + +/* Initialize an HMAC-SHA256 operation with the given key. */ +void HMAC__SHA256_Init(HMAC_SHA256_CTX *ctx, const void *_K, size_t Klen) +{ + unsigned char pad[64]; + unsigned char khash[32]; + const unsigned char *K = _K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + SHA256_Update(&ctx->ictx, K, Klen); + SHA256_Final(khash, &ctx->ictx); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->ictx, pad, 64); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->octx, pad, 64); + + /* Clean the stack. */ + explicit_bzero(khash, 32); +} + +/* Add bytes to the HMAC-SHA256 operation. */ +void HMAC__SHA256_Update(HMAC_SHA256_CTX *ctx, const void *in, size_t len) +{ + + /* Feed data to the inner SHA256 operation. */ + SHA256_Update(&ctx->ictx, in, len); +} + +/* Finish an HMAC-SHA256 operation. */ +void HMAC__SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX *ctx) +{ + unsigned char ihash[32]; + + /* Finish the inner SHA256 operation. */ + SHA256_Final(ihash, &ctx->ictx); + + /* Feed the inner hash to the outer SHA256 operation. */ + SHA256_Update(&ctx->octx, ihash, 32); + + /* Finish the outer SHA256 operation. */ + SHA256_Final(digest, &ctx->octx); + + /* Clean the stack. */ + explicit_bzero(ihash, 32); +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, + size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen) +{ + HMAC_SHA256_CTX PShctx = {}, hctx; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Compute HMAC state after processing P and S. */ + HMAC__SHA256_Init(&PShctx, passwd, passwdlen); + HMAC__SHA256_Update(&PShctx, salt, saltlen); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + HMAC__SHA256_Update(&hctx, ivec, 4); + HMAC__SHA256_Final(U, &hctx); + + /* T_i = U_1 ... */ + memcpy(T, U, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + HMAC__SHA256_Init(&hctx, passwd, passwdlen); + HMAC__SHA256_Update(&hctx, U, 32); + HMAC__SHA256_Final(U, &hctx); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean PShctx, since we never called _Final on it. */ + explicit_bzero(&PShctx, sizeof(HMAC_SHA256_CTX)); +} diff --git a/lib/sha256.h b/lib/sha256.h new file mode 100644 index 0000000..dbadf4e --- /dev/null +++ b/lib/sha256.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-2-Clause +/*- + * Copyright 2005,2007,2009 Colin Percival + * All rights reserved. + * + * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $ + */ + +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SHA256Context { + uint32_t state[8]; + uint32_t count[2]; + unsigned char buf[64]; +} SHA256_CTX; + +typedef struct HMAC_SHA256Context { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX *, const void *, size_t); +void SHA256_Final(unsigned char[32], SHA256_CTX *); +void HMAC__SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); +void HMAC__SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); +void HMAC__SHA256_Final(unsigned char[32], HMAC_SHA256_CTX *); + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, + uint8_t *, size_t); + +#ifdef __cplusplus +} +#endif + +#endif /* !_SHA256_H_ */ diff --git a/lib/sigevent.c b/lib/sigevent.c new file mode 100644 index 0000000..3e69f28 --- /dev/null +++ b/lib/sigevent.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Quagga signal handling functions. + * Copyright (C) 2004 Paul Jakma, + */ + +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_UCONTEXT_H +#ifdef GNU_LINUX +/* get REG_EIP from ucontext.h */ +#ifndef __USE_GNU +#define __USE_GNU +#endif /* __USE_GNU */ +#endif /* GNU_LINUX */ +#include +#endif /* HAVE_UCONTEXT_H */ + + +/* master signals descriptor struct */ +static struct frr_sigevent_master_t { + struct event *t; + + struct frr_signal_t *signals; + int sigc; + + volatile sig_atomic_t caught; +} sigmaster; + +/* Generic signal handler + * Schedules signal event thread + */ +static void frr_signal_handler(int signo) +{ + int i; + struct frr_signal_t *sig; + + for (i = 0; i < sigmaster.sigc; i++) { + sig = &(sigmaster.signals[i]); + + if (sig->signal == signo) + sig->caught = 1; + } + + sigmaster.caught = 1; +} + +/* + * Check whether any signals have been received and are pending. This is done + * with the application's key signals blocked. The complete set of signals + * is returned in 'setp', so the caller can restore them when appropriate. + * If there are pending signals, returns 'true', 'false' otherwise. + */ +bool frr_sigevent_check(sigset_t *setp) +{ + sigset_t blocked; + int i; + bool ret; + + sigemptyset(setp); + sigemptyset(&blocked); + + /* Set up mask of application's signals */ + for (i = 0; i < sigmaster.sigc; i++) + sigaddset(&blocked, sigmaster.signals[i].signal); + + pthread_sigmask(SIG_BLOCK, &blocked, setp); + + /* Now that the application's signals are blocked, test. */ + ret = (sigmaster.caught != 0); + + return ret; +} + +/* check if signals have been caught and run appropriate handlers */ +int frr_sigevent_process(void) +{ + struct frr_signal_t *sig; + int i; +#ifdef SIGEVENT_BLOCK_SIGNALS + /* shouldn't need to block signals, but potentially may be needed */ + sigset_t newmask, oldmask; + + /* + * Block most signals, but be careful not to defer SIGTRAP because + * doing so breaks gdb, at least on NetBSD 2.0. Avoid asking to + * block SIGKILL, just because we shouldn't be able to do so. + */ + sigfillset(&newmask); + sigdelset(&newmask, SIGTRAP); + sigdelset(&newmask, SIGKILL); + + if ((sigprocmask(SIG_BLOCK, &newmask, &oldmask)) < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "frr_signal_timer: couldnt block signals!"); + return -1; + } +#endif /* SIGEVENT_BLOCK_SIGNALS */ + + if (sigmaster.caught > 0) { + sigmaster.caught = 0; + /* must not read or set sigmaster.caught after here, + * race condition with per-sig caught flags if one does + */ + + for (i = 0; i < sigmaster.sigc; i++) { + sig = &(sigmaster.signals[i]); + + if (sig->caught > 0) { + sig->caught = 0; + if (sig->handler) + sig->handler(); + } + } + } + +#ifdef SIGEVENT_BLOCK_SIGNALS + if (sigprocmask(SIG_UNBLOCK, &oldmask, NULL) < 0) + return -1; +#endif /* SIGEVENT_BLOCK_SIGNALS */ + + return 0; +} + +#ifdef SIGEVENT_SCHEDULE_THREAD +/* timer thread to check signals. shouldn't be needed */ +void frr_signal_timer(struct event *t) +{ + struct frr_sigevent_master_t *sigm; + + sigm = EVENT_ARG(t); + sigm->t = NULL; + event_add_timer(sigm->t->master, frr_signal_timer, &sigmaster, + FRR_SIGNAL_TIMER_INTERVAL, &sigm->t); + frr_sigevent_process(); +} +#endif /* SIGEVENT_SCHEDULE_THREAD */ + +/* Initialization of signal handles. */ +/* Signal wrapper. */ +static int signal_set(int signo) +{ + int ret; + struct sigaction sig; + struct sigaction osig; + + sig.sa_handler = &frr_signal_handler; + sigfillset(&sig.sa_mask); + sig.sa_flags = 0; + if (signo == SIGALRM) { +#ifdef SA_INTERRUPT + sig.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + } else { +#ifdef SA_RESTART + sig.sa_flags |= SA_RESTART; +#endif /* SA_RESTART */ + } + + ret = sigaction(signo, &sig, &osig); + if (ret < 0) + return ret; + else + return 0; +} + +/* XXX This function should be enhanced to support more platforms + (it currently works only on Linux/x86). */ +static void *program_counter(void *context) +{ +#ifdef HAVE_UCONTEXT_H +#ifdef GNU_LINUX +/* these are from GNU libc, rather than Linux, strictly speaking */ +#if defined(REG_EIP) +# define REG_INDEX REG_EIP +#elif defined(REG_RIP) +# define REG_INDEX REG_RIP +#elif defined(__powerpc__) +# define REG_INDEX 32 +#endif +#endif /* GNU_LINUX */ + +#ifdef REG_INDEX +#ifdef HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS +# define REGS gregs[REG_INDEX] +#elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_UC_REGS) +# define REGS uc_regs->gregs[REG_INDEX] +#endif /* HAVE_UCONTEXT_T_UC_MCONTEXT_GREGS */ +#endif /* REG_INDEX */ + +#ifdef REGS + if (context) + return (void *)(((ucontext_t *)context)->uc_mcontext.REGS); +#elif defined(HAVE_UCONTEXT_T_UC_MCONTEXT_REGS__NIP) + /* older Linux / struct pt_regs ? */ + if (context) + return (void *)(((ucontext_t *)context)->uc_mcontext.regs->nip); +#endif /* REGS */ + +#endif /* HAVE_UCONTEXT_H */ + return NULL; +} + +static void __attribute__((noreturn)) +exit_handler(int signo, siginfo_t *siginfo, void *context) +{ + void *pc = program_counter(context); + + zlog_signal(signo, "exiting...", siginfo, pc); + _exit(128 + signo); +} + +static void __attribute__((noreturn)) +core_handler(int signo, siginfo_t *siginfo, void *context) +{ + void *pc = program_counter(context); + + /* make sure we don't hang in here. default for SIGALRM is terminate. + * - if we're in backtrace for more than a second, abort. */ + struct sigaction sa_default = {.sa_handler = SIG_DFL}; + + sigaction(SIGALRM, &sa_default, NULL); + sigaction(signo, &sa_default, NULL); + + sigset_t sigset; + + sigemptyset(&sigset); + sigaddset(&sigset, SIGALRM); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + + alarm(1); + + zlog_signal(signo, "aborting...", siginfo, pc); + + /* dump memory stats on core */ + log_memstats(stderr, "core_handler"); + + /* + * This is a buffer flush because FRR is going down + * hard. This is especially important if the crash + * was caused by a memory operation and if we call + * zlog_tls_buffer_fini() then it has memory + * operations as well. This will cause the + * core dump to not happen. BAD MOJO + * So this is intentional, let's try to flush + * what we can and let the crash happen. + */ + zlog_tls_buffer_flush(); + + /* give the kernel a chance to generate a coredump */ + sigaddset(&sigset, signo); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); + raise(signo); + + /* only chance to end up here is if the default action for signo is + * something other than kill or coredump the process + */ + _exit(128 + signo); +} + +static void trap_default_signals(void) +{ + static const int core_signals[] = { + SIGQUIT, SIGILL, SIGABRT, +#ifdef SIGEMT + SIGEMT, +#endif + SIGFPE, SIGBUS, SIGSEGV, +#ifdef SIGSYS + SIGSYS, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + static const int exit_signals[] = { + SIGHUP, SIGINT, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGSTKFLT + SIGSTKFLT, +#endif + }; + static const int ignore_signals[] = { + SIGPIPE, + }; + static const struct { + const int *sigs; + unsigned int nsigs; + void (*handler)(int signo, siginfo_t *info, void *context); + } sigmap[] = { + {core_signals, array_size(core_signals), core_handler}, + {exit_signals, array_size(exit_signals), exit_handler}, + {ignore_signals, array_size(ignore_signals), NULL}, + }; + unsigned int i; + + for (i = 0; i < array_size(sigmap); i++) { + unsigned int j; + + for (j = 0; j < sigmap[i].nsigs; j++) { + struct sigaction oact; + if ((sigaction(sigmap[i].sigs[j], NULL, &oact) == 0) + && (oact.sa_handler == SIG_DFL)) { + struct sigaction act; + sigfillset(&act.sa_mask); + if (sigmap[i].handler == NULL) { + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + } else { + /* Request extra arguments to signal + * handler. */ + act.sa_sigaction = sigmap[i].handler; + act.sa_flags = SA_SIGINFO; +#ifdef SA_RESETHAND + /* don't try to print backtraces + * recursively */ + if (sigmap[i].handler == core_handler) + act.sa_flags |= SA_RESETHAND; +#endif + } + if (sigaction(sigmap[i].sigs[j], &act, NULL) + < 0) + flog_err( + EC_LIB_SYSTEM_CALL, + "Unable to set signal handler for signal %d: %s", + sigmap[i].sigs[j], + safe_strerror(errno)); + } + } + } +} + +void signal_init(struct event_loop *m, int sigc, struct frr_signal_t signals[]) +{ + + int i = 0; + struct frr_signal_t *sig; + + /* First establish some default handlers that can be overridden by + the application. */ + trap_default_signals(); + + while (i < sigc) { + sig = &signals[i]; + if (signal_set(sig->signal) < 0) + exit(-1); + i++; + } + + sigmaster.sigc = sigc; + sigmaster.signals = signals; + +#ifdef SIGEVENT_SCHEDULE_THREAD + sigmaster.t = NULL; + event_add_timer(m, frr_signal_timer, &sigmaster, + FRR_SIGNAL_TIMER_INTERVAL, &sigmaster.t); +#endif /* SIGEVENT_SCHEDULE_THREAD */ +} diff --git a/lib/sigevent.h b/lib/sigevent.h new file mode 100644 index 0000000..0b07f59 --- /dev/null +++ b/lib/sigevent.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quagga Signal handling header. + * + * Copyright (C) 2004 Paul Jakma. + */ + +#ifndef _FRR_SIGNAL_H +#define _FRR_SIGNAL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define FRR_SIGNAL_TIMER_INTERVAL 2L + +struct frr_signal_t { + int signal; /* signal number */ + void (*handler)(void); /* handler to call */ + + volatile sig_atomic_t caught; /* private member */ +}; + +/* initialise sigevent system + * takes: + * - pointer to valid struct event_loop + * - number of elements in passed in signals array + * - array of frr_signal_t's describing signals to handle + * and handlers to use for each signal + */ +extern void signal_init(struct event_loop *m, int sigc, + struct frr_signal_t *signals); + + +/* + * Check whether any signals have been received and are pending. This is done + * with the application's key signals blocked. The complete set of signals + * is returned in 'setp', so the caller can restore them when appropriate. + * If there are pending signals, returns 'true', 'false' otherwise. + */ +bool frr_sigevent_check(sigset_t *setp); + +/* check whether there are signals to handle, process any found */ +extern int frr_sigevent_process(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_SIGNAL_H */ diff --git a/lib/skiplist.c b/lib/skiplist.c new file mode 100644 index 0000000..fb8cb72 --- /dev/null +++ b/lib/skiplist.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: LicenseRef-Skiplist-BSD-0-Clause +/* + * Copyright 1990 William Pugh + * + * Permission to include in quagga provide on March 31, 2016 + */ + +/* + * Skip List implementation based on code from William Pugh. + * ftp://ftp.cs.umd.edu/pub/skipLists/ + * + * Skip Lists are a probabilistic alternative to balanced trees, as + * described in the June 1990 issue of CACM and were invented by + * William Pugh in 1987. + * + * This file contains source code to implement a dictionary using + * skip lists and a test driver to test the routines. + * + * A couple of comments about this implementation: + * The routine randomLevel has been hard-coded to generate random + * levels using p=0.25. It can be easily changed. + * + * The insertion routine has been implemented so as to use the + * dirty hack described in the CACM paper: if a random level is + * generated that is more than the current maximum level, the + * current maximum level plus one is used instead. + * + * Levels start at zero and go up to MaxLevel (which is equal to + * (MaxNumberOfLevels-1). + * + * The run-time flag SKIPLIST_FLAG_ALLOW_DUPLICATES determines whether or + * not duplicates are allowed for a given list. If set, duplicates are + * allowed and act in a FIFO manner. If not set, an insertion of a value + * already in the list updates the previously existing binding. + * + * BitsInRandom is defined to be the number of bits returned by a call to + * random(). For most all machines with 32-bit integers, this is 31 bits + * as currently set. + */ + + +#include + +#include "memory.h" +#include "log.h" +#include "vty.h" +#include "skiplist.h" +#include "lib_errors.h" +#include "network.h" + +DEFINE_MTYPE_STATIC(LIB, SKIP_LIST, "Skip List"); +DEFINE_MTYPE_STATIC(LIB, SKIP_LIST_NODE, "Skip Node"); +DEFINE_MTYPE_STATIC(LIB, SKIP_LIST_STATS, "Skiplist Counters"); + +#define BitsInRandom 31 + +#define MaxNumberOfLevels 16 +#define MaxLevel (MaxNumberOfLevels-1) +#define newNodeOfLevel(l) \ + XCALLOC(MTYPE_SKIP_LIST_NODE, \ + sizeof(struct skiplistnode) \ + + (l) * sizeof(struct skiplistnode *)) + +/* XXX must match type of (struct skiplist).level_stats */ +#define newStatsOfLevel(l) \ + XCALLOC(MTYPE_SKIP_LIST_STATS, ((l) + 1) * sizeof(int)) + +static int randomsLeft; +static int randomBits; + +#ifdef SKIPLIST_DEBUG +#define CHECKLAST(sl) \ + do { \ + if ((sl)->header->forward[0] && !(sl)->last) \ + assert(0); \ + if (!(sl)->header->forward[0] && (sl)->last) \ + assert(0); \ + } while (0) +#else +#define CHECKLAST(sl) +#endif + + +static int randomLevel(void) +{ + register int level = 0; + register int b; + + do { + if (randomsLeft <= 0) { + randomBits = frr_weak_random(); + randomsLeft = BitsInRandom / 2; + } + b = randomBits & 3; + randomBits >>= 2; + --randomsLeft; + + if (!b) { + level++; + if (level >= MaxLevel) + return MaxLevel; + } + } while (!b); + + return level; +} + +static int default_cmp(const void *key1, const void *key2) +{ + if (key1 < key2) + return -1; + if (key1 > key2) + return 1; + return 0; +} + +unsigned int skiplist_count(struct skiplist *l) +{ + return l->count; +} + +struct skiplist *skiplist_new(int flags, + int (*cmp)(const void *key1, const void *key2), + void (*del)(void *val)) +{ + struct skiplist *new; + + new = XCALLOC(MTYPE_SKIP_LIST, sizeof(struct skiplist)); + assert(new); + + new->level = 0; + new->count = 0; + new->header = newNodeOfLevel(MaxNumberOfLevels); + new->level_stats = newStatsOfLevel(MaxNumberOfLevels); + + new->flags = flags; + if (cmp) + new->cmp = cmp; + else + new->cmp = default_cmp; + + if (del) + new->del = del; + + return new; +} + +void skiplist_free(struct skiplist *l) +{ + register struct skiplistnode *p, *q; + + p = l->header; + + do { + q = p->forward[0]; + if (l->del && p != l->header) + (*l->del)(p->value); + XFREE(MTYPE_SKIP_LIST_NODE, p); + p = q; + } while (p); + + XFREE(MTYPE_SKIP_LIST_STATS, l->level_stats); + XFREE(MTYPE_SKIP_LIST, l); +} + + +int skiplist_insert(register struct skiplist *l, register void *key, + register void *value) +{ + register int k; + struct skiplistnode *update[MaxNumberOfLevels]; + register struct skiplistnode *p, *q; + + CHECKLAST(l); + +#ifdef SKIPLIST_DEBUG + /* DEBUG */ + if (!key) { + flog_err(EC_LIB_DEVELOPMENT, "%s: key is 0, value is %p", + __func__, value); + } +#endif + + p = l->header; + k = l->level; + do { + while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) + p = q; + update[k] = p; + } while (--k >= 0); + + if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) && q + && ((*l->cmp)(q->key, key) == 0)) { + + return -1; + } + + k = randomLevel(); + assert(k >= 0); + if (k > l->level) { + k = ++l->level; + update[k] = l->header; + } + + q = newNodeOfLevel(k); + q->key = key; + q->value = value; +#ifdef SKIPLIST_0TIMER_DEBUG + q->flags = SKIPLIST_NODE_FLAG_INSERTED; /* debug */ +#endif + + ++(l->level_stats[k]); +#ifdef SKIPLIST_DEBUG + zlog_debug("%s: incremented level_stats @%p:%d, now %d", __func__, l, k, + l->level_stats[k]); +#endif + + do { + p = update[k]; + q->forward[k] = p->forward[k]; + p->forward[k] = q; + } while (--k >= 0); + + /* + * If this is the last item in the list, update the "last" pointer + */ + if (!q->forward[0]) { + l->last = q; + } + + ++(l->count); + + CHECKLAST(l); + + return 0; +} + +int skiplist_delete(register struct skiplist *l, register void *key, + register void *value) /* used only if duplicates allowed */ +{ + register int k, m; + struct skiplistnode *update[MaxNumberOfLevels]; + register struct skiplistnode *p, *q; + + CHECKLAST(l); + + /* to make debugging easier */ + for (k = 0; k < MaxNumberOfLevels; ++k) + update[k] = NULL; + + p = l->header; + k = m = l->level; + do { + while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) + p = q; + update[k] = p; + } while (--k >= 0); + + if (l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) { + while (q && ((*l->cmp)(q->key, key) == 0) + && (q->value != value)) { + int i; + for (i = 0; i <= l->level; ++i) { + if (update[i]->forward[i] == q) + update[i] = q; + } + q = q->forward[0]; + } + } + + if (q && (*l->cmp)(q->key, key) == 0) { + if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES) + || (q->value == value)) { + +/* + * found node to delete + */ +#ifdef SKIPLIST_0TIMER_DEBUG + q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED; +#endif + /* + * If we are deleting the last element of the list, + * update the list's "last" pointer. + */ + if (l->last == q) { + if (update[0] == l->header) + l->last = NULL; + else + l->last = update[0]; + } + + for (k = 0; k <= m && (p = update[k])->forward[k] == q; + k++) { + p->forward[k] = q->forward[k]; + } + --(l->level_stats[k - 1]); +#ifdef SKIPLIST_DEBUG + zlog_debug("%s: decremented level_stats @%p:%d, now %d", + __func__, l, k - 1, l->level_stats[k - 1]); +#endif + if (l->del) + (*l->del)(q->value); + XFREE(MTYPE_SKIP_LIST_NODE, q); + while (l->header->forward[m] == NULL && m > 0) + m--; + l->level = m; + CHECKLAST(l); + --(l->count); + return 0; + } + } + + CHECKLAST(l); + return -1; +} + +/* + * Obtain first value matching "key". Unless SKIPLIST_FLAG_ALLOW_DUPLICATES + * is set, this will also be the only value matching "key". + * + * Also set a cursor for use with skiplist_next_value. + */ +int skiplist_first_value(register struct skiplist *l, /* in */ + register const void *key, /* in */ + void **valuePointer, /* out */ + void **cursor) /* out */ +{ + register int k; + register struct skiplistnode *p, *q; + + p = l->header; + k = l->level; + + do { + while (q = p->forward[k], q && (*l->cmp)(q->key, key) < 0) + p = q; + + } while (--k >= 0); + + if (!q || (*l->cmp)(q->key, key)) + return -1; + + if (valuePointer) + *valuePointer = q->value; + + if (cursor) + *cursor = q; + + return 0; +} + +int skiplist_search(register struct skiplist *l, register void *key, + void **valuePointer) +{ + return skiplist_first_value(l, key, valuePointer, NULL); +} + + +/* + * Caller supplies key and value of an existing item in the list. + * Function returns the value of the next list item that has the + * same key (useful when SKIPLIST_FLAG_ALLOW_DUPLICATES is set). + * + * Returns 0 on success. If the caller-supplied key and value + * do not correspond to a list element, or if they specify the + * last element with the given key, -1 is returned. + */ +int skiplist_next_value(register struct skiplist *l, /* in */ + register const void *key, /* in */ + void **valuePointer, /* in/out */ + void **cursor) /* in/out */ +{ + register int k; + register struct skiplistnode *p, *q; + + CHECKLAST(l); + + if (!(l->flags & SKIPLIST_FLAG_ALLOW_DUPLICATES)) { + return -1; + } + + if (!cursor || !*cursor) { + p = l->header; + k = l->level; + + /* + * Find matching key + */ + do { + while (q = p->forward[k], + q && (*l->cmp)(q->key, key) < 0) + p = q; + } while (--k >= 0); + + /* + * Find matching value + */ + while (q && ((*l->cmp)(q->key, key) == 0) + && (q->value != *valuePointer)) { + q = q->forward[0]; + } + + if (!q || ((*l->cmp)(q->key, key) != 0) + || (q->value != *valuePointer)) { + /* + * No matching value + */ + CHECKLAST(l); + return -1; + } + } else { + q = (struct skiplistnode *)*cursor; + } + + /* + * Advance cursor + */ + q = q->forward[0]; + + /* + * If we reached end-of-list or if the key is no longer the same, + * then return error + */ + if (!q || ((*l->cmp)(q->key, key) != 0)) + return -1; + + *valuePointer = q->value; + if (cursor) + *cursor = q; + CHECKLAST(l); + return 0; +} + +int skiplist_first(register struct skiplist *l, void **keyPointer, + void **valuePointer) +{ + register struct skiplistnode *p; + + CHECKLAST(l); + p = l->header->forward[0]; + if (!p) + return -1; + + if (keyPointer) + *keyPointer = p->key; + + if (valuePointer) + *valuePointer = p->value; + + CHECKLAST(l); + + return 0; +} + +int skiplist_last(register struct skiplist *l, void **keyPointer, + void **valuePointer) +{ + CHECKLAST(l); + if (l->last) { + if (keyPointer) + *keyPointer = l->last->key; + if (valuePointer) + *valuePointer = l->last->value; + return 0; + } + return -1; +} + +/* + * true = empty + */ +int skiplist_empty(register struct skiplist *l) +{ + CHECKLAST(l); + if (l->last) + return 0; + return 1; +} + +/* + * Use this to walk the list. Caller sets *cursor to NULL to obtain + * first element. Return value of 0 indicates valid cursor/element + * returned, otherwise NULL cursor arg or EOL. + */ +int skiplist_next(register struct skiplist *l, /* in */ + void **keyPointer, /* out */ + void **valuePointer, /* out */ + void **cursor) /* in/out */ +{ + struct skiplistnode *p; + + if (!cursor) + return -1; + + CHECKLAST(l); + + if (!*cursor) { + p = l->header->forward[0]; + } else { + p = *cursor; + p = p->forward[0]; + } + *cursor = p; + + if (!p) + return -1; + + if (keyPointer) + *keyPointer = p->key; + + if (valuePointer) + *valuePointer = p->value; + + CHECKLAST(l); + + return 0; +} + +int skiplist_delete_first(register struct skiplist *l) +{ + register int k; + register struct skiplistnode *p, *q; + int nodelevel = 0; + + CHECKLAST(l); + + p = l->header; + q = l->header->forward[0]; + + if (!q) + return -1; + + for (k = l->level; k >= 0; --k) { + if (p->forward[k] == q) { + p->forward[k] = q->forward[k]; + if ((k == l->level) && (p->forward[k] == NULL) + && (l->level > 0)) + --(l->level); + if (!nodelevel) + nodelevel = k; + } + } + +#ifdef SKIPLIST_0TIMER_DEBUG + q->flags &= ~SKIPLIST_NODE_FLAG_INSERTED; +#endif + /* + * If we are deleting the last element of the list, + * update the list's "last" pointer. + */ + if (l->last == q) { + l->last = NULL; + } + + --(l->level_stats[nodelevel]); +#ifdef SKIPLIST_DEBUG + zlog_debug("%s: decremented level_stats @%p:%d, now %d", __func__, l, + nodelevel, l->level_stats[nodelevel]); +#endif + + if (l->del) + (*l->del)(q->value); + + XFREE(MTYPE_SKIP_LIST_NODE, q); + + CHECKLAST(l); + + --(l->count); + + return 0; +} + +void skiplist_debug(struct vty *vty, struct skiplist *l) +{ + int i; + + if (!l) + return; + + vty_out(vty, "Skiplist %p has max level %d\n", l, l->level); + for (i = l->level; i >= 0; --i) + vty_out(vty, " @%d: %d\n", i, l->level_stats[i]); +} + +static void *scramble(int i) +{ + uintptr_t result; + + result = (unsigned)(i & 0xff) << 24; + result |= (unsigned)i >> 8; + + return (void *)result; +} + +#define sampleSize 65536 +void skiplist_test(struct vty *vty) +{ + struct skiplist *l; + register int i, k; + void *keys[sampleSize]; + void *v = NULL; + + zlog_debug("%s: entry", __func__); + + l = skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, NULL, NULL); + + zlog_debug("%s: skiplist_new returned %p", __func__, l); + + for (i = 0; i < 4; i++) { + + for (k = 0; k < sampleSize; k++) { + if (!(k % 1000)) { + zlog_debug("%s: (%d:%d)", __func__, i, k); + } + // keys[k] = (void *)random(); + keys[k] = scramble(k); + if (skiplist_insert(l, keys[k], keys[k])) + zlog_debug("error in insert #%d,#%d", i, k); + } + + zlog_debug("%s: inserts done", __func__); + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 1000)) + zlog_debug("[%d:%d]", i, k); + if (skiplist_search(l, keys[k], &v)) + zlog_debug("error in search #%d,#%d", i, k); + + if (v != keys[k]) + zlog_debug("search returned wrong value"); + } + + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 1000)) + zlog_debug("<%d:%d>", i, k); + if (skiplist_delete(l, keys[k], keys[k])) + zlog_debug("error in delete"); + keys[k] = scramble(k ^ 0xf0f0f0f0); + if (skiplist_insert(l, keys[k], keys[k])) + zlog_debug("error in insert #%d,#%d", i, k); + } + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 1000)) + zlog_debug("{%d:%d}", i, k); + if (skiplist_delete_first(l)) + zlog_debug("error in delete_first"); + } + } + + skiplist_free(l); +} diff --git a/lib/skiplist.h b/lib/skiplist.h new file mode 100644 index 0000000..39bfa39 --- /dev/null +++ b/lib/skiplist.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LicenseRef-Skiplist-BSD-0-Clause +/* + * Copyright 1990 William Pugh + * + * Permission to include in quagga provide on March 31, 2016 + */ + +/* + * Skip List implementation based on code from William Pugh. + * ftp://ftp.cs.umd.edu/pub/skipLists/ + */ + +/* skiplist.h */ + + +#ifndef _ZEBRA_SKIPLIST_H +#define _ZEBRA_SKIPLIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SKIPLIST_0TIMER_DEBUG 1 + +/* + * skiplistnodes must always contain data to be valid. Adding an + * empty node to a list is invalid + */ +struct skiplistnode { + void *key; + void *value; +#if SKIPLIST_0TIMER_DEBUG + int flags; +#define SKIPLIST_NODE_FLAG_INSERTED 0x00000001 +#endif + + struct skiplistnode *forward[1]; /* variable sized */ +}; + +struct skiplist { + int flags; + +#define SKIPLIST_FLAG_ALLOW_DUPLICATES 0x00000001 + + int level; /* max lvl (1 + current # of levels in list) */ + unsigned int count; + struct skiplistnode *header; + int *level_stats; + struct skiplistnode + *last; /* last real list item (NULL if empty list) */ + + /* + * Returns -1 if val1 < val2, 0 if equal?, 1 if val1 > val2. + * Used as definition of sorted for listnode_add_sort + */ + int (*cmp)(const void *val1, const void *val2); + + /* callback to free user-owned data when listnode is deleted. supplying + * this callback is very much encouraged! + */ + void (*del)(void *val); +}; + + +/* Prototypes. */ +extern struct skiplist * +skiplist_new(/* encouraged: set list.del callback on new lists */ + int flags, + int (*cmp)(const void *key1, + const void *key2), /* NULL => default cmp */ + void (*del)(void *val)); /* NULL => no auto val free */ + +extern void skiplist_free(struct skiplist *); + +extern int skiplist_insert(register struct skiplist *l, register void *key, + register void *value); + +extern int skiplist_delete(register struct skiplist *l, register void *key, + register void *value); + +extern int skiplist_search(register struct skiplist *l, register void *key, + void **valuePointer); + +extern int skiplist_first_value(register struct skiplist *l, /* in */ + register const void *key, /* in */ + void **valuePointer, /* in/out */ + void **cursor); /* out */ + +extern int skiplist_next_value(register struct skiplist *l, /* in */ + register const void *key, /* in */ + void **valuePointer, /* in/out */ + void **cursor); /* in/out */ + +extern int skiplist_first(register struct skiplist *l, void **keyPointer, + void **valuePointer); + +extern int skiplist_last(register struct skiplist *l, void **keyPointer, + void **valuePointer); + +extern int skiplist_delete_first(register struct skiplist *l); + +extern int skiplist_next(register struct skiplist *l, /* in */ + void **keyPointer, /* out */ + void **valuePointer, /* out */ + void **cursor); /* in/out */ + +extern int skiplist_empty(register struct skiplist *l); /* in */ + +extern unsigned int skiplist_count(register struct skiplist *l); /* in */ + +struct vty; +extern void skiplist_debug(struct vty *vty, struct skiplist *l); + +extern void skiplist_test(struct vty *vty); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_SKIPLIST_H */ diff --git a/lib/smux.h b/lib/smux.h new file mode 100644 index 0000000..8ec847a --- /dev/null +++ b/lib/smux.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* SNMP support + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_SNMP_H +#define _ZEBRA_SNMP_H + +#include +#include + +#include "frrevent.h" +#include "hook.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structures here are mostly compatible with UCD SNMP 4.1.1 */ +#define MATCH_FAILED (-1) +#define MATCH_SUCCEEDED 0 + +/* SYNTAX TruthValue from SNMPv2-TC. */ +#define SNMP_TRUE 1 +#define SNMP_FALSE 2 + +/* SYNTAX RowStatus from SNMPv2-TC. */ +#define SNMP_VALID 1 +#define SNMP_INVALID 2 + +#define IN_ADDR_SIZE sizeof(struct in_addr) +#define IN6_ADDR_SIZE sizeof(struct in6_addr) + +/* IANAipRouteProtocol */ +#define IANAIPROUTEPROTOCOLOTHER 1 +#define IANAIPROUTEPROTOCOLLOCAL 2 +#define IANAIPROUTEPROTOCOLNETMGMT 3 +#define IANAIPROUTEPROTOCOLICMP 4 +#define IANAIPROUTEPROTOCOLEGP 5 +#define IANAIPROUTEPROTOCOLGGP 6 +#define IANAIPROUTEPROTOCOLHELLO 7 +#define IANAIPROUTEPROTOCOLRIP 8 +#define IANAIPROUTEPROTOCOLISIS 9 +#define IANAIPROUTEPROTOCOLESIS 10 +#define IANAIPROUTEPROTOCOLCISCOIGRP 11 +#define IANAIPROUTEPROTOCOLBBNSPFIGP 12 +#define IANAIPROUTEPROTOCOLOSPF 13 +#define IANAIPROUTEPROTOCOLBGP 14 +#define IANAIPROUTEPROTOCOLIDPR 15 +#define IANAIPROUTEPROTOCOLCISCOEIGRP 16 +#define IANAIPROUTEPROTOCOLDVMRP 17 + +#define INETADDRESSTYPEUNKNOWN 0 +#define INETADDRESSTYPEIPV4 1 +#define INETADDRESSTYPEIPV6 2 + +#undef REGISTER_MIB +#define REGISTER_MIB(descr, var, vartype, theoid) \ + smux_register_mib(descr, (struct variable *)var, \ + sizeof(struct vartype), \ + sizeof(var) / sizeof(struct vartype), theoid, \ + sizeof(theoid) / sizeof(oid)) + +struct trap_object { + int namelen; /* Negative if the object is not indexed */ + oid name[MAX_OID_LEN]; +}; + +struct index_oid { + int indexlen; + oid indexname[MAX_OID_LEN]; +}; +/* Declare SMUX return value. */ +#define SNMP_LOCAL_VARIABLES \ + static long snmp_int_val __attribute__((unused)); \ + static struct in_addr snmp_in_addr_val __attribute__((unused)); \ + static uint8_t snmp_octet_val __attribute__((unused)); \ + static char snmp_string_val[255] __attribute__((unused)); +#define SNMP_INTEGER(V) \ + (*var_len = sizeof(snmp_int_val), snmp_int_val = V, \ + (uint8_t *)&snmp_int_val) + +#define SNMP_OCTET(V) \ + (*var_len = sizeof(snmp_octet_val), snmp_octet_val = V, \ + (uint8_t *)&snmp_octet_val) + +#define SNMP_STRING(V) \ + (*var_len = MIN(sizeof(snmp_string_val), strlen(V) + 1), \ + strlcpy(snmp_string_val, V, *var_len), (uint8_t *)&snmp_string_val) + +#define SNMP_IPADDRESS(V) \ + (*var_len = sizeof(struct in_addr), snmp_in_addr_val = V, \ + (uint8_t *)&snmp_in_addr_val) + +#define SNMP_IP6ADDRESS(V) (*var_len = sizeof(struct in6_addr), (uint8_t *)&V) + +/* + * Check to see if snmp is enabled or not + */ +extern bool smux_enabled(void); + +extern void libagentx_init(void); +extern void smux_init(struct event_loop *tm); +extern void smux_agentx_enable(void); +extern void smux_register_mib(const char *, struct variable *, size_t, int, + oid[], size_t); +extern int smux_header_generic(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); +extern int smux_header_table(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); + +/* For traps, three OID are provided: + + 1. The enterprise OID to use (the last argument will be appended to + it to form the SNMP trap OID) + + 2. The base OID for objects to be sent in traps. + + 3. The index OID for objects to be sent in traps. This index is used + to designate a particular instance of a column. + + The provided trap object contains the bindings to be sent with the + trap. The base OID will be prefixed to the provided OID and, if the + length is positive, the requested OID is assumed to be a columnar + object and the index OID will be appended. + + The two first arguments are the MIB registry used to locate the trap + objects. + + The use of the arguments may differ depending on the implementation + used. +*/ +extern void smux_trap(struct variable *, size_t, const oid *, size_t, + const oid *, size_t, const oid *, size_t, + const struct trap_object *, size_t, uint8_t); + +extern int smux_trap_multi_index(struct variable *vp, size_t vp_len, + const oid *ename, size_t enamelen, + const oid *name, size_t namelen, + struct index_oid *iname, size_t index_len, + const struct trap_object *trapobj, + size_t trapobjlen, uint8_t sptrap); + +extern void smux_events_update(void); +extern int oid_compare(const oid *, int, const oid *, int); +extern void oid2in_addr(oid[], int, struct in_addr *); +extern void oid2in6_addr(oid oid[], struct in6_addr *addr); +extern void oid2int(oid oid[], int *dest); +extern void *oid_copy(void *, const void *, size_t); +extern void oid_copy_in_addr(oid[], const struct in_addr *); +extern void oid_copy_in6_addr(oid[], const struct in6_addr *); +extern void oid_copy_int(oid oid[], int *val); +extern void oid2string(oid oid[], int len, char *string); +extern void oid_copy_str(oid oid[], const char *string, int len); + +DECLARE_HOOK(agentx_enabled, (), ()); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_SNMP_H */ diff --git a/lib/snmp.c b/lib/snmp.c new file mode 100644 index 0000000..d28e649 --- /dev/null +++ b/lib/snmp.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* SNMP support + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include +#include + +#include "smux.h" + +int oid_compare(const oid *o1, int o1_len, const oid *o2, int o2_len) +{ + int i; + + for (i = 0; i < MIN(o1_len, o2_len); i++) { + if (o1[i] < o2[i]) + return -1; + else if (o1[i] > o2[i]) + return 1; + } + if (o1_len < o2_len) + return -1; + if (o1_len > o2_len) + return 1; + + return 0; +} + +void *oid_copy(void *dest, const void *src, size_t size) +{ + return memcpy(dest, src, size * sizeof(oid)); +} + +void oid2in_addr(oid oid[], int len, struct in_addr *addr) +{ + int i; + uint8_t *pnt; + + if (len == 0) + return; + + pnt = (uint8_t *)addr; + + for (i = 0; i < len; i++) + *pnt++ = oid[i]; +} + +void oid2in6_addr(oid oid[], struct in6_addr *addr) +{ + unsigned int i; + uint8_t *pnt; + + pnt = (uint8_t *)addr; + + for (i = 0; i < sizeof(struct in6_addr); i++) + *pnt++ = oid[i]; +} + +void oid2int(oid oid[], int *dest) +{ + uint8_t i; + uint8_t *pnt; + int network_dest; + + pnt = (uint8_t *)&network_dest; + + for (i = 0; i < sizeof(int); i++) + *pnt++ = oid[i]; + *dest = ntohl(network_dest); +} + +void oid_copy_in_addr(oid oid[], const struct in_addr *addr) +{ + int i; + const uint8_t *pnt; + int len = sizeof(struct in_addr); + + pnt = (uint8_t *)addr; + + for (i = 0; i < len; i++) + oid[i] = *pnt++; +} + + +void oid_copy_in6_addr(oid oid[], const struct in6_addr *addr) +{ + int i; + const uint8_t *pnt; + int len = sizeof(struct in6_addr); + + pnt = (uint8_t *)addr; + + for (i = 0; i < len; i++) + oid[i] = *pnt++; +} + +void oid_copy_int(oid oid[], int *val) +{ + uint8_t i; + const uint8_t *pnt; + int network_val; + + network_val = htonl(*val); + pnt = (uint8_t *)&network_val; + + for (i = 0; i < sizeof(int); i++) + oid[i] = *pnt++; +} + +void oid2string(oid oid[], int len, char *string) +{ + int i; + uint8_t *pnt; + + if (len == 0) + return; + + pnt = (uint8_t *)string; + + for (i = 0; i < len; i++) + *pnt++ = (uint8_t)oid[i]; +} + +void oid_copy_str(oid oid[], const char *string, int len) +{ + int i; + const uint8_t *pnt; + + if (len == 0) + return; + + pnt = (uint8_t *)string; + + for (i = 0; i < len; i++) + oid[i] = *pnt++; +} + +int smux_header_generic(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + oid fulloid[MAX_OID_LEN]; + int ret; + + oid_copy(fulloid, v->name, v->namelen); + fulloid[v->namelen] = 0; + /* Check against full instance. */ + ret = oid_compare(name, *length, fulloid, v->namelen + 1); + + /* Check single instance. */ + if ((exact && (ret != 0)) || (!exact && (ret >= 0))) + return MATCH_FAILED; + + /* In case of getnext, fill in full instance. */ + memcpy(name, fulloid, (v->namelen + 1) * sizeof(oid)); + *length = v->namelen + 1; + + *write_method = 0; + *var_len = sizeof(long); /* default to 'long' results */ + + return MATCH_SUCCEEDED; +} + +int smux_header_table(struct variable *v, oid *name, size_t *length, int exact, + size_t *var_len, WriteMethod **write_method) +{ + /* If the requested OID name is less than OID prefix we + handle, adjust it to our prefix. */ + if ((oid_compare(name, *length, v->name, v->namelen)) < 0) { + if (exact) + return MATCH_FAILED; + oid_copy(name, v->name, v->namelen); + *length = v->namelen; + } + + *write_method = 0; + *var_len = sizeof(long); + + return MATCH_SUCCEEDED; +} diff --git a/lib/sockopt.c b/lib/sockopt.c new file mode 100644 index 0000000..74bc034 --- /dev/null +++ b/lib/sockopt.c @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* setsockopt functions + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "log.h" +#include "sockopt.h" +#include "sockunion.h" +#include "lib_errors.h" + +#if (defined(__FreeBSD__) && \ + ((__FreeBSD_version >= 500022 && __FreeBSD_version < 700000) || \ + (__FreeBSD_version < 500000 && __FreeBSD_version >= 440000))) || \ + (defined(__NetBSD__) && defined(__NetBSD_Version__) && \ + __NetBSD_Version__ >= 106010000) || \ + defined(__OpenBSD__) || defined(__DragonFly__) || defined(__sun) +#define HAVE_BSD_STRUCT_IP_MREQ_HACK +#endif + +void setsockopt_so_recvbuf(int sock, int size) +{ + int orig_req = size; + + while (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)) == + -1) { + if (size == 0) + break; + size /= 2; + } + + if (size != orig_req) + flog_err(EC_LIB_SOCKET, + "%s: fd %d: SO_RCVBUF set to %d (requested %d)", + __func__, sock, size, orig_req); +} + +void setsockopt_so_sendbuf(const int sock, int size) +{ + int orig_req = size; + + while (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == + -1) { + if (size == 0) + break; + size /= 2; + } + + if (size != orig_req) + flog_err(EC_LIB_SOCKET, + "%s: fd %d: SO_SNDBUF set to %d (requested %d)", + __func__, sock, size, orig_req); +} + +int getsockopt_so_sendbuf(const int sock) +{ + uint32_t optval; + socklen_t optlen = sizeof(optval); + int ret = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&optval, + &optlen); + if (ret < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "fd %d: can't getsockopt SO_SNDBUF: %d (%s)", sock, + errno, safe_strerror(errno)); + return ret; + } + return optval; +} + +int getsockopt_so_recvbuf(const int sock) +{ + uint32_t optval; + socklen_t optlen = sizeof(optval); + int ret = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&optval, + &optlen); + if (ret < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "fd %d: can't getsockopt SO_RCVBUF: %d (%s)", sock, + errno, safe_strerror(errno)); + return ret; + } + return optval; +} + +static void *getsockopt_cmsg_data(struct msghdr *msgh, int level, int type) +{ + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR(msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(msgh, cmsg)) + if (cmsg->cmsg_level == level && cmsg->cmsg_type == type) + return CMSG_DATA(cmsg); + + return NULL; +} + +/* Set IPv6 packet info to the socket. */ +int setsockopt_ipv6_pktinfo(int sock, int val) +{ + int ret; + +#ifdef IPV6_RECVPKTINFO /*2292bis-01*/ + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IPV6_RECVPKTINFO : %s", + safe_strerror(errno)); +#else /*RFC2292*/ + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO, &val, sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_PKTINFO : %s", + safe_strerror(errno)); +#endif /* IANA_IPV6 */ + return ret; +} + +/* Set multicast hops val to the socket. */ +int setsockopt_ipv6_multicast_hops(int sock, int val) +{ + int ret; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_HOPS"); + return ret; +} + +/* Set multicast hops val to the socket. */ +int setsockopt_ipv6_unicast_hops(int sock, int val) +{ + int ret; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_UNICAST_HOPS"); + return ret; +} + +int setsockopt_ipv6_hoplimit(int sock, int val) +{ + int ret; + +#ifdef IPV6_RECVHOPLIMIT /*2292bis-01*/ + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_RECVHOPLIMIT"); +#else /*RFC2292*/ + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &val, sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_HOPLIMIT"); +#endif + return ret; +} + +/* Set multicast loop zero to the socket. */ +int setsockopt_ipv6_multicast_loop(int sock, int val) +{ + int ret; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IPV6_MULTICAST_LOOP"); + return ret; +} + +static int getsockopt_ipv6_ifindex(struct msghdr *msgh) +{ + struct in6_pktinfo *pktinfo; + + pktinfo = getsockopt_cmsg_data(msgh, IPPROTO_IPV6, IPV6_PKTINFO); + + return pktinfo->ipi6_ifindex; +} + +int setsockopt_ipv6_tclass(int sock, int tclass) +{ + int ret = 0; + +#ifdef IPV6_TCLASS /* RFC3542 */ + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_TCLASS, &tclass, + sizeof(tclass)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "Can't set IPV6_TCLASS option for fd %d to %#x: %s", + sock, tclass, safe_strerror(errno)); +#endif + return ret; +} + +/* + * Process multicast socket options for IPv4 in an OS-dependent manner. + * Supported options are IP_{ADD,DROP}_MEMBERSHIP. + * + * Many operating systems have a limit on the number of groups that + * can be joined per socket (where each group and local address + * counts). This impacts OSPF, which joins groups on each interface + * using a single socket. The limit is typically 20, derived from the + * original BSD multicast implementation. Some systems have + * mechanisms for increasing this limit. + * + * In many 4.4BSD-derived systems, multicast group operations are not + * allowed on interfaces that are not UP. Thus, a previous attempt to + * leave the group may have failed, leaving it still joined, and we + * drop/join quietly to recover. This may not be necessary, but aims to + * defend against unknown behavior in that we will still return an error + * if the second join fails. It is not clear how other systems + * (e.g. Linux, Solaris) behave when leaving groups on down interfaces, + * but this behavior should not be harmful if they behave the same way, + * allow leaves, or implicitly leave all groups joined to down interfaces. + */ +int setsockopt_ipv4_multicast(int sock, int optname, struct in_addr if_addr, + unsigned int mcast_addr, ifindex_t ifindex) +{ +#ifdef HAVE_RFC3678 + struct group_req gr; + struct sockaddr_in *si; + int ret; + memset(&gr, 0, sizeof(gr)); + si = (struct sockaddr_in *)&gr.gr_group; + gr.gr_interface = ifindex; + si->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + si->sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + si->sin_addr.s_addr = mcast_addr; + ret = setsockopt(sock, IPPROTO_IP, + (optname == IP_ADD_MEMBERSHIP) ? MCAST_JOIN_GROUP + : MCAST_LEAVE_GROUP, + (void *)&gr, sizeof(gr)); + if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) + && (errno == EADDRINUSE)) { + setsockopt(sock, IPPROTO_IP, MCAST_LEAVE_GROUP, (void *)&gr, + sizeof(gr)); + ret = setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, + (void *)&gr, sizeof(gr)); + } + return ret; + +#elif defined(HAVE_STRUCT_IP_MREQN_IMR_IFINDEX) && !defined(__FreeBSD__) + struct ip_mreqn mreqn; + int ret; + + assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP); + memset(&mreqn, 0, sizeof(mreqn)); + + mreqn.imr_multiaddr.s_addr = mcast_addr; + mreqn.imr_ifindex = ifindex; + + ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreqn, + sizeof(mreqn)); + if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) + && (errno == EADDRINUSE)) { + /* see above: handle possible problem when interface comes back + * up */ + zlog_info( + "setsockopt_ipv4_multicast attempting to drop and re-add (fd %d, mcast %pI4, ifindex %u)", + sock, &mreqn.imr_multiaddr, ifindex); + setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreqn, + sizeof(mreqn)); + ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (void *)&mreqn, sizeof(mreqn)); + } + return ret; + +/* Example defines for another OS, boilerplate off other code in this + function, AND handle optname as per other sections for consistency !! */ +/* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */ +/* Add your favourite OS here! */ + +#elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK) /* #if OS_TYPE */ + /* standard BSD API */ + + struct ip_mreq mreq; + int ret; + + assert(optname == IP_ADD_MEMBERSHIP || optname == IP_DROP_MEMBERSHIP); + + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = mcast_addr; +#if !defined __OpenBSD__ + mreq.imr_interface.s_addr = htonl(ifindex); +#else + mreq.imr_interface.s_addr = if_addr.s_addr; +#endif + + ret = setsockopt(sock, IPPROTO_IP, optname, (void *)&mreq, + sizeof(mreq)); + if ((ret < 0) && (optname == IP_ADD_MEMBERSHIP) + && (errno == EADDRINUSE)) { + /* see above: handle possible problem when interface comes back + * up */ + zlog_info( + "setsockopt_ipv4_multicast attempting to drop and re-add (fd %d, mcast %pI4, ifindex %u)", + sock, &mreq.imr_multiaddr, ifindex); + setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreq, + sizeof(mreq)); + ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (void *)&mreq, sizeof(mreq)); + } + return ret; + +#else +#error "Unsupported multicast API" +#endif /* #if OS_TYPE */ +} + +/* + * Set IP_MULTICAST_IF socket option in an OS-dependent manner. + */ +int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr, + ifindex_t ifindex) +{ + +#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX + struct ip_mreqn mreqn; + memset(&mreqn, 0, sizeof(mreqn)); + + mreqn.imr_ifindex = ifindex; + return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&mreqn, + sizeof(mreqn)); + +/* Example defines for another OS, boilerplate off other code in this + function */ +/* #elif defined(BOGON_NIX) && EXAMPLE_VERSION_CODE > -100000 */ +/* Add your favourite OS here! */ +#elif defined(HAVE_BSD_STRUCT_IP_MREQ_HACK) + struct in_addr m; + +#if !defined __OpenBSD__ + m.s_addr = htonl(ifindex); +#else + m.s_addr = if_addr.s_addr; +#endif + + return setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (void *)&m, + sizeof(m)); +#else +#error "Unsupported multicast API" +#endif +} + +int setsockopt_ipv4_multicast_loop(int sock, uint8_t val) +{ + int ret; + + ret = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&val, + sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, "can't setsockopt IP_MULTICAST_LOOP"); + + return ret; +} + +static int setsockopt_ipv4_ifindex(int sock, ifindex_t val) +{ + int ret; + +#if defined(IP_PKTINFO) + ret = setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "Can't set IP_PKTINFO option for fd %d to %d: %s", + sock, val, safe_strerror(errno)); +#elif defined(IP_RECVIF) + ret = setsockopt(sock, IPPROTO_IP, IP_RECVIF, &val, sizeof(val)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "Can't set IP_RECVIF option for fd %d to %d: %s", sock, + val, safe_strerror(errno)); +#else +#warning "Neither IP_PKTINFO nor IP_RECVIF is available." +#warning "Will not be able to receive link info." +#warning "Things might be seriously broken.." + /* XXX Does this ever happen? Should there be a zlog_warn message here? + */ + ret = -1; +#endif + return ret; +} + +int setsockopt_ipv4_tos(int sock, int tos) +{ + int ret; + + ret = setsockopt(sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "Can't set IP_TOS option for fd %d to %#x: %s", sock, + tos, safe_strerror(errno)); + return ret; +} + + +int setsockopt_ifindex(int af, int sock, ifindex_t val) +{ + int ret = -1; + + switch (af) { + case AF_INET: + ret = setsockopt_ipv4_ifindex(sock, val); + break; + case AF_INET6: + ret = setsockopt_ipv6_pktinfo(sock, val); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, + "setsockopt_ifindex: unknown address family %d", af); + } + return ret; +} + +/* + * Requires: msgh is not NULL and points to a valid struct msghdr, which + * may or may not have control data about the incoming interface. + * + * Returns the interface index (small integer >= 1) if it can be + * determined, or else 0. + */ +static ifindex_t getsockopt_ipv4_ifindex(struct msghdr *msgh) +{ + ifindex_t ifindex; + +#if defined(IP_PKTINFO) + /* Linux pktinfo based ifindex retrieval */ + struct in_pktinfo *pktinfo; + + pktinfo = (struct in_pktinfo *)getsockopt_cmsg_data(msgh, IPPROTO_IP, + IP_PKTINFO); + + /* getsockopt_ifindex() will forward this, being 0 "not found" */ + if (pktinfo == NULL) + return 0; + + ifindex = pktinfo->ipi_ifindex; + +#elif defined(IP_RECVIF) + +/* retrieval based on IP_RECVIF */ + + /* BSD systems use a sockaddr_dl as the control message payload. */ + struct sockaddr_dl *sdl; + + /* BSD */ + sdl = (struct sockaddr_dl *)getsockopt_cmsg_data(msgh, IPPROTO_IP, + IP_RECVIF); + if (sdl != NULL) + ifindex = sdl->sdl_index; + else + ifindex = 0; + +#else +/* + * Neither IP_PKTINFO nor IP_RECVIF defined - warn at compile time. + * XXX Decide if this is a core service, or if daemons have to cope. + * Since Solaris 8 and OpenBSD seem not to provide it, it seems that + * daemons have to cope. + */ +#warning "getsockopt_ipv4_ifindex: Neither IP_PKTINFO nor IP_RECVIF defined." +#warning "Some daemons may fail to operate correctly!" + ifindex = 0; + +#endif /* IP_PKTINFO */ + + return ifindex; +} + +/* return ifindex, 0 if none found */ +ifindex_t getsockopt_ifindex(int af, struct msghdr *msgh) +{ + switch (af) { + case AF_INET: + return (getsockopt_ipv4_ifindex(msgh)); + case AF_INET6: + return (getsockopt_ipv6_ifindex(msgh)); + default: + flog_err(EC_LIB_DEVELOPMENT, + "getsockopt_ifindex: unknown address family %d", af); + return 0; + } +} + +/* swab iph between order system uses for IP_HDRINCL and host order */ +void sockopt_iphdrincl_swab_htosys(struct ip *iph) +{ +/* BSD and derived take iph in network order, except for + * ip_len and ip_off + */ +#ifndef HAVE_IP_HDRINCL_BSD_ORDER + iph->ip_len = htons(iph->ip_len); + iph->ip_off = htons(iph->ip_off); +#endif /* HAVE_IP_HDRINCL_BSD_ORDER */ + + iph->ip_id = htons(iph->ip_id); +} + +void sockopt_iphdrincl_swab_systoh(struct ip *iph) +{ +#ifndef HAVE_IP_HDRINCL_BSD_ORDER + iph->ip_len = ntohs(iph->ip_len); + iph->ip_off = ntohs(iph->ip_off); +#endif /* HAVE_IP_HDRINCL_BSD_ORDER */ + + iph->ip_id = ntohs(iph->ip_id); +} + +int sockopt_tcp_rtt(int sock) +{ +#ifdef TCP_INFO + struct tcp_info ti; + socklen_t len = sizeof(ti); + + if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &ti, &len) != 0) + return 0; + + return ti.tcpi_rtt / 1000; +#else + return 0; +#endif +} + +int sockopt_tcp_signature_ext(int sock, union sockunion *su, uint16_t prefixlen, + const char *password) +{ +#ifndef HAVE_DECL_TCP_MD5SIG + /* + * We have been asked to enable MD5 auth for an address, but our + * platform doesn't support that + */ + return -2; +#endif + +#ifndef TCP_MD5SIG_EXT + /* + * We have been asked to enable MD5 auth for a prefix, but our platform + * doesn't support that + */ + if (prefixlen > 0) + return -2; +#endif + +#if HAVE_DECL_TCP_MD5SIG + int ret; + + int optname = TCP_MD5SIG; +#ifndef GNU_LINUX + /* + * XXX Need to do PF_KEY operation here to add/remove an SA entry, + * and add/remove an SP entry for this peer's packet flows also. + */ + int md5sig = password && *password ? 1 : 0; +#else + int keylen = password ? strlen(password) : 0; + struct tcp_md5sig md5sig; + union sockunion *su2, *susock; + + /* Figure out whether the socket and the sockunion are the same family.. + * adding AF_INET to AF_INET6 needs to be v4 mapped, you'd think.. + */ + if (!(susock = sockunion_getsockname(sock))) + return -1; + + if (susock->sa.sa_family == su->sa.sa_family) + su2 = su; + else { + /* oops.. */ + su2 = susock; + + if (su2->sa.sa_family == AF_INET) { + sockunion_free(susock); + return 0; + } + + /* If this does not work, then all users of this sockopt will + * need to + * differentiate between IPv4 and IPv6, and keep separate + * sockets for + * each. + * + * Sadly, it doesn't seem to work at present. It's unknown + * whether + * this is a bug or not. + */ + if (su2->sa.sa_family == AF_INET6 + && su->sa.sa_family == AF_INET) { + su2->sin6.sin6_family = AF_INET6; + /* V4Map the address */ + memset(&su2->sin6.sin6_addr, 0, + sizeof(struct in6_addr)); + su2->sin6.sin6_addr.s6_addr32[2] = htonl(0xffff); + memcpy(&su2->sin6.sin6_addr.s6_addr32[3], + &su->sin.sin_addr, 4); + } + } + + memset(&md5sig, 0, sizeof(md5sig)); + memcpy(&md5sig.tcpm_addr, su2, sizeof(*su2)); + + md5sig.tcpm_keylen = keylen; + if (keylen) + memcpy(md5sig.tcpm_key, password, keylen); + sockunion_free(susock); + + /* + * Handle support for MD5 signatures on prefixes, if available and + * requested. Technically the #ifdef check below is not needed because + * if prefixlen > 0 and we don't have support for this feature we would + * have already returned by now, but leaving it there to be explicit. + */ +#ifdef TCP_MD5SIG_EXT + if (prefixlen > 0) { + md5sig.tcpm_prefixlen = prefixlen; + md5sig.tcpm_flags = TCP_MD5SIG_FLAG_PREFIX; + optname = TCP_MD5SIG_EXT; + } +#endif /* TCP_MD5SIG_EXT */ + +#endif /* GNU_LINUX */ + + ret = setsockopt(sock, IPPROTO_TCP, optname, &md5sig, sizeof(md5sig)); + if (ret < 0) { + if (ENOENT == errno) + ret = 0; + else + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "sockopt_tcp_signature: setsockopt(%d): %s", + sock, safe_strerror(errno)); + } + return ret; +#endif /* HAVE_TCP_MD5SIG */ + + /* + * Making compiler happy. If we get to this point we probably + * have done something really really wrong. + */ + return -2; +} + +int sockopt_tcp_signature(int sock, union sockunion *su, const char *password) +{ + return sockopt_tcp_signature_ext(sock, su, 0, password); +} + +/* set TCP mss value to socket */ +int sockopt_tcp_mss_set(int sock, int tcp_maxseg) +{ + int ret = 0; + socklen_t tcp_maxseg_len = sizeof(tcp_maxseg); + + ret = setsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, + tcp_maxseg_len); + if (ret != 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: setsockopt(%d): %s", __func__, sock, + safe_strerror(errno)); + } + + return ret; +} + +/* get TCP mss value synced by socket */ +int sockopt_tcp_mss_get(int sock) +{ + int ret = 0; + int tcp_maxseg = 0; + socklen_t tcp_maxseg_len = sizeof(tcp_maxseg); + + if (sock < 0) + return 0; + + ret = getsockopt(sock, IPPROTO_TCP, TCP_MAXSEG, &tcp_maxseg, + &tcp_maxseg_len); + if (ret != 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: getsockopt(%d): %s", __func__, sock, + safe_strerror(errno)); + return 0; + } + + return tcp_maxseg; +} + +int setsockopt_tcp_keepalive(int sock, uint16_t keepalive_idle, + uint16_t keepalive_intvl, + uint16_t keepalive_probes) +{ + int val = 1; + + if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: setsockopt SO_KEEPALIVE (%d): %s", + __func__, sock, safe_strerror(errno)); + return -1; + } + +#if defined __OpenBSD__ + return 0; +#else + /* Send first probe after keepalive_idle seconds */ + val = keepalive_idle; + if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < + 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: setsockopt TCP_KEEPIDLE (%d): %s", + __func__, sock, safe_strerror(errno)); + return -1; + } + + /* Set interval between two probes */ + val = keepalive_intvl; + if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < + 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: setsockopt TCP_KEEPINTVL (%d): %s", + __func__, sock, safe_strerror(errno)); + return -1; + } + + /* Set maximum probes */ + val = keepalive_probes; + if (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s failed: setsockopt TCP_KEEPCNT (%d): %s", + __func__, sock, safe_strerror(errno)); + return -1; + } + + return 0; +#endif +} diff --git a/lib/sockopt.h b/lib/sockopt.h new file mode 100644 index 0000000..ce7d665 --- /dev/null +++ b/lib/sockopt.h @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Router advertisement + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_SOCKOPT_H +#define _ZEBRA_SOCKOPT_H + +#include "sockunion.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void setsockopt_so_recvbuf(int sock, int size); +extern void setsockopt_so_sendbuf(const int sock, int size); +extern int getsockopt_so_sendbuf(const int sock); +extern int getsockopt_so_recvbuf(const int sock); + +extern int setsockopt_ipv6_pktinfo(int, int); +extern int setsockopt_ipv6_multicast_hops(int, int); +extern int setsockopt_ipv6_unicast_hops(int, int); +extern int setsockopt_ipv6_hoplimit(int, int); +extern int setsockopt_ipv6_multicast_loop(int, int); +extern int setsockopt_ipv6_tclass(int, int); + +#define SOPT_SIZE_CMSG_PKTINFO_IPV6() (sizeof(struct in6_pktinfo)); + +/* + * Size defines for control messages used to get ifindex. We define + * values for each method, and define a macro that can be used by code + * that is unaware of which method is in use. + * These values are without any alignment needed (see CMSG_SPACE in RFC3542). + */ +#if defined(IP_PKTINFO) +/* Linux in_pktinfo. */ +#define SOPT_SIZE_CMSG_PKTINFO_IPV4() (CMSG_SPACE(sizeof(struct in_pktinfo))) +/* XXX This should perhaps be defined even if IP_PKTINFO is not. */ +#define SOPT_SIZE_CMSG_PKTINFO(af) \ + ((af == AF_INET) ? SOPT_SIZE_CMSG_PKTINFO_IPV4() \ + : SOPT_SIZE_CMSG_PKTINFO_IPV6() +#endif /* IP_PKTINFO */ + +#if defined(IP_RECVIF) +/* BSD/Solaris */ + +#define SOPT_SIZE_CMSG_RECVIF_IPV4() (sizeof(struct sockaddr_dl)) +#endif /* IP_RECVIF */ + +/* SOPT_SIZE_CMSG_IFINDEX_IPV4 - portable type */ +#if defined(SOPT_SIZE_CMSG_PKTINFO) +#define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_PKTINFO_IPV4() +#elif defined(SOPT_SIZE_CMSG_RECVIF_IPV4) +#define SOPT_SIZE_CMSG_IFINDEX_IPV4() SOPT_SIZE_CMSG_RECVIF_IPV4() +#else /* Nothing available */ +#define SOPT_SIZE_CMSG_IFINDEX_IPV4() (sizeof(char *)) +#endif /* SOPT_SIZE_CMSG_IFINDEX_IPV4 */ + +#define SOPT_SIZE_CMSG_IFINDEX(af) \ + (((af) == AF_INET) : SOPT_SIZE_CMSG_IFINDEX_IPV4() \ + ? SOPT_SIZE_CMSG_PKTINFO_IPV6()) + +extern int setsockopt_ipv4_multicast_if(int sock, struct in_addr if_addr, + ifindex_t ifindex); +extern int setsockopt_ipv4_multicast(int sock, int optname, + struct in_addr if_addr, + unsigned int mcast_addr, + ifindex_t ifindex); +extern int setsockopt_ipv4_multicast_loop(int sock, uint8_t val); + +extern int setsockopt_ipv4_tos(int sock, int tos); + +/* Ask for, and get, ifindex, by whatever method is supported. */ +extern int setsockopt_ifindex(int, int, ifindex_t); +extern ifindex_t getsockopt_ifindex(int, struct msghdr *); + +/* swab the fields in iph between the host order and system order expected + * for IP_HDRINCL. + */ +extern void sockopt_iphdrincl_swab_htosys(struct ip *iph); +extern void sockopt_iphdrincl_swab_systoh(struct ip *iph); + +extern int sockopt_tcp_rtt(int); + +/* + * TCP MD5 signature option. This option allows TCP MD5 to be enabled on + * addresses. + * + * sock + * Socket to enable option on. + * + * su + * Sockunion specifying address to enable option on. + * + * password + * MD5 auth password + */ +extern int sockopt_tcp_signature(int sock, union sockunion *su, + const char *password); + +/* + * Extended TCP MD5 signature option. This option allows TCP MD5 to be enabled + * on prefixes. + * + * sock + * Socket to enable option on. + * + * su + * Sockunion specifying address (or prefix) to enable option on. + * + * prefixlen + * 0 - su is an address; fall back to non-extended mode + * Else - su is a prefix; prefixlen is the mask length + * + * password + * MD5 auth password + */ +extern int sockopt_tcp_signature_ext(int sock, union sockunion *su, + uint16_t prefixlen, const char *password); + +/* + * set TCP max segment size. This option allows user to configure + * max segment size for TCP session + * + * sock + * Socket to enable option on. + * + * tcp_maxseg + * value used for TCP segment size negotiation during SYN + */ +extern int sockopt_tcp_mss_set(int sock, int tcp_maxseg); + +/* + * get TCP max segment size. This option allows user to get + * the segment size for TCP session + * + * sock + * Socket to get max segement size. + */ +extern int sockopt_tcp_mss_get(int sock); + +/* + * Configure TCP keepalive for a given socket + * + * sock + * Socket to enable keepalive option on. + * + * keepalive_idle + * number of seconds a connection needs to be idle + * before sending out keep-alive proves + * + * keepalive_intvl + * number of seconds between TCP keep-alive probes + * + * keepalive_probes + * max number of probers to send before giving up + * and killing tcp connection + */ +extern int setsockopt_tcp_keepalive(int sock, uint16_t keepalive_idle, + uint16_t keepalive_intvl, + uint16_t keepalive_probes); + +#ifdef __cplusplus +} +#endif + +#endif /*_ZEBRA_SOCKOPT_H */ diff --git a/lib/sockunion.c b/lib/sockunion.c new file mode 100644 index 0000000..c37ab1d --- /dev/null +++ b/lib/sockunion.c @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Socket union related function. + * Copyright (c) 1997, 98 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "vty.h" +#include "sockunion.h" +#include "memory.h" +#include "log.h" +#include "jhash.h" +#include "lib_errors.h" +#include "printfrr.h" + +DEFINE_MTYPE_STATIC(LIB, SOCKUNION, "Socket union"); + +const char *inet_sutop(const union sockunion *su, char *str) +{ + switch (su->sa.sa_family) { + case AF_INET: + inet_ntop(AF_INET, &su->sin.sin_addr, str, INET_ADDRSTRLEN); + break; + case AF_INET6: + inet_ntop(AF_INET6, &su->sin6.sin6_addr, str, INET6_ADDRSTRLEN); + break; + } + return str; +} + +int str2sockunion(const char *str, union sockunion *su) +{ + int ret; + + if (str == NULL) + return -1; + + memset(su, 0, sizeof(union sockunion)); + + ret = inet_pton(AF_INET, str, &su->sin.sin_addr); + if (ret > 0) /* Valid IPv4 address format. */ + { + su->sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + return 0; + } + ret = inet_pton(AF_INET6, str, &su->sin6.sin6_addr); + if (ret > 0) /* Valid IPv6 address format. */ + { + su->sin6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + su->sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + return 0; + } + return -1; +} + +const char *sockunion2str(const union sockunion *su, char *buf, size_t len) +{ + switch (sockunion_family(su)) { + case AF_UNSPEC: + snprintf(buf, len, "(unspec)"); + return buf; + case AF_INET: + return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len); + case AF_INET6: + return inet_ntop(AF_INET6, &su->sin6.sin6_addr, buf, len); + } + snprintf(buf, len, "(af %d)", sockunion_family(su)); + return buf; +} + +union sockunion *sockunion_str2su(const char *str) +{ + union sockunion *su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + + if (!str2sockunion(str, su)) + return su; + + XFREE(MTYPE_SOCKUNION, su); + return NULL; +} + +/* Convert IPv4 compatible IPv6 address to IPv4 address. */ +static void sockunion_normalise_mapped(union sockunion *su) +{ + struct sockaddr_in sin; + + if (su->sa.sa_family == AF_INET6 + && IN6_IS_ADDR_V4MAPPED(&su->sin6.sin6_addr)) { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = su->sin6.sin6_port; + memcpy(&sin.sin_addr, ((char *)&su->sin6.sin6_addr) + 12, 4); + memcpy(su, &sin, sizeof(struct sockaddr_in)); + } +} + +/* return sockunion structure : this function should be revised. */ +static const char *sockunion_log(const union sockunion *su, char *buf, + size_t len) +{ + switch (su->sa.sa_family) { + case AF_INET: + return inet_ntop(AF_INET, &su->sin.sin_addr, buf, len); + + case AF_INET6: + return inet_ntop(AF_INET6, &(su->sin6.sin6_addr), buf, len); + + default: + snprintf(buf, len, "af_unknown %d ", su->sa.sa_family); + return buf; + } +} + +/* Return socket of sockunion. */ +int sockunion_socket(const union sockunion *su) +{ + int sock; + + sock = socket(su->sa.sa_family, SOCK_STREAM, 0); + if (sock < 0) { + char buf[SU_ADDRSTRLEN]; + flog_err(EC_LIB_SOCKET, "Can't make socket for %s : %s", + sockunion_log(su, buf, SU_ADDRSTRLEN), + safe_strerror(errno)); + return -1; + } + + return sock; +} + +/* Return accepted new socket file descriptor. */ +int sockunion_accept(int sock, union sockunion *su) +{ + socklen_t len; + int client_sock; + + len = sizeof(union sockunion); + client_sock = accept(sock, (struct sockaddr *)su, &len); + + sockunion_normalise_mapped(su); + return client_sock; +} + +/* Return sizeof union sockunion. */ +int sockunion_sizeof(const union sockunion *su) +{ + int ret; + + ret = 0; + switch (su->sa.sa_family) { + case AF_INET: + ret = sizeof(struct sockaddr_in); + break; + case AF_INET6: + ret = sizeof(struct sockaddr_in6); + break; + } + return ret; +} + +/* Performs a non-blocking connect(). */ +enum connect_result sockunion_connect(int fd, const union sockunion *peersu, + unsigned short port, ifindex_t ifindex) +{ + int ret; + union sockunion su; + + memcpy(&su, peersu, sizeof(union sockunion)); + + switch (su.sa.sa_family) { + case AF_INET: + su.sin.sin_port = port; + break; + case AF_INET6: + su.sin6.sin6_port = port; +#ifdef KAME + if (IN6_IS_ADDR_LINKLOCAL(&su.sin6.sin6_addr) && ifindex) { + su.sin6.sin6_scope_id = ifindex; + SET_IN6_LINKLOCAL_IFINDEX(su.sin6.sin6_addr, ifindex); + } +#endif /* KAME */ + break; + } + + /* Call connect function. */ + ret = connect(fd, (struct sockaddr *)&su, sockunion_sizeof(&su)); + + /* Immediate success */ + if (ret == 0) + return connect_success; + + /* If connect is in progress then return 1 else it's real error. */ + if (ret < 0) { + if (errno != EINPROGRESS) { + char str[SU_ADDRSTRLEN]; + zlog_info("can't connect to %s fd %d : %s", + sockunion_log(&su, str, sizeof(str)), fd, + safe_strerror(errno)); + return connect_error; + } + } + + return connect_in_progress; +} + +/* Make socket from sockunion union. */ +int sockunion_stream_socket(union sockunion *su) +{ + int sock; + + if (su->sa.sa_family == 0) + su->sa.sa_family = AF_INET_UNION; + + sock = socket(su->sa.sa_family, SOCK_STREAM, 0); + + if (sock < 0) + flog_err(EC_LIB_SOCKET, + "can't make socket sockunion_stream_socket"); + + return sock; +} + +/* Bind socket to specified address. */ +int sockunion_bind(int sock, union sockunion *su, unsigned short port, + union sockunion *su_addr) +{ + int size = 0; + int ret; + + if (su->sa.sa_family == AF_INET) { + size = sizeof(struct sockaddr_in); + su->sin.sin_port = htons(port); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = size; +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + if (su_addr == NULL) + sockunion2ip(su) = htonl(INADDR_ANY); + } else if (su->sa.sa_family == AF_INET6) { + size = sizeof(struct sockaddr_in6); + su->sin6.sin6_port = htons(port); +#ifdef SIN6_LEN + su->sin6.sin6_len = size; +#endif /* SIN6_LEN */ + if (su_addr == NULL) { +#ifdef LINUX_IPV6 + memset(&su->sin6.sin6_addr, 0, sizeof(struct in6_addr)); +#else + su->sin6.sin6_addr = in6addr_any; +#endif /* LINUX_IPV6 */ + } + } + + ret = bind(sock, (struct sockaddr *)su, size); + if (ret < 0) { + char buf[SU_ADDRSTRLEN]; + flog_err(EC_LIB_SOCKET, "can't bind socket for %s : %s", + sockunion_log(su, buf, SU_ADDRSTRLEN), + safe_strerror(errno)); + } + + return ret; +} + +int sockopt_reuseaddr(int sock) +{ + int ret; + int on = 1; + + ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, + sizeof(on)); + if (ret < 0) { + flog_err( + EC_LIB_SOCKET, + "can't set sockopt SO_REUSEADDR to socket %d errno=%d: %s", + sock, errno, safe_strerror(errno)); + return -1; + } + return 0; +} + +#ifdef SO_REUSEPORT +int sockopt_reuseport(int sock) +{ + int ret; + int on = 1; + + ret = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&on, + sizeof(on)); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "can't set sockopt SO_REUSEPORT to socket %d", sock); + return -1; + } + return 0; +} +#else +int sockopt_reuseport(int sock) +{ + return 0; +} +#endif /* 0 */ + +int sockopt_ttl(int family, int sock, int ttl) +{ + int ret; + +#ifdef IP_TTL + if (family == AF_INET) { + ret = setsockopt(sock, IPPROTO_IP, IP_TTL, (void *)&ttl, + sizeof(int)); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "can't set sockopt IP_TTL %d to socket %d", + ttl, sock); + return -1; + } + return 0; + } +#endif /* IP_TTL */ + if (family == AF_INET6) { + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + (void *)&ttl, sizeof(int)); + if (ret < 0) { + flog_err( + EC_LIB_SOCKET, + "can't set sockopt IPV6_UNICAST_HOPS %d to socket %d", + ttl, sock); + return -1; + } + return 0; + } + return 0; +} + +int sockopt_minttl(int family, int sock, int minttl) +{ +#ifdef IP_MINTTL + if (family == AF_INET) { + int ret = setsockopt(sock, IPPROTO_IP, IP_MINTTL, &minttl, + sizeof(minttl)); + if (ret < 0) + flog_err( + EC_LIB_SOCKET, + "can't set sockopt IP_MINTTL to %d on socket %d: %s", + minttl, sock, safe_strerror(errno)); + return ret; + } +#endif /* IP_MINTTL */ +#ifdef IPV6_MINHOPCOUNT + if (family == AF_INET6) { + int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MINHOPCOUNT, + &minttl, sizeof(minttl)); + if (ret < 0) + flog_err( + EC_LIB_SOCKET, + "can't set sockopt IPV6_MINHOPCOUNT to %d on socket %d: %s", + minttl, sock, safe_strerror(errno)); + return ret; + } +#endif + + errno = EOPNOTSUPP; + return -1; +} + +int sockopt_v6only(int family, int sock) +{ + int ret, on = 1; + +#ifdef IPV6_V6ONLY + if (family == AF_INET6) { + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, + sizeof(int)); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "can't set sockopt IPV6_V6ONLY to socket %d", + sock); + return -1; + } + return 0; + } +#endif /* IPV6_V6ONLY */ + return 0; +} + +/* If same family and same prefix return 1. */ +int sockunion_same(const union sockunion *su1, const union sockunion *su2) +{ + int ret = 0; + + if (su1->sa.sa_family != su2->sa.sa_family) + return 0; + + switch (su1->sa.sa_family) { + case AF_INET: + ret = memcmp(&su1->sin.sin_addr, &su2->sin.sin_addr, + sizeof(struct in_addr)); + break; + case AF_INET6: + ret = memcmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr, + sizeof(struct in6_addr)); + if ((ret == 0) && IN6_IS_ADDR_LINKLOCAL(&su1->sin6.sin6_addr)) { + /* compare interface indices */ + if (su1->sin6.sin6_scope_id && su2->sin6.sin6_scope_id) + ret = (su1->sin6.sin6_scope_id + == su2->sin6.sin6_scope_id) + ? 0 + : 1; + } + break; + } + if (ret == 0) + return 1; + else + return 0; +} + +unsigned int sockunion_hash(const union sockunion *su) +{ + switch (sockunion_family(su)) { + case AF_INET: + return jhash_1word(su->sin.sin_addr.s_addr, 0); + case AF_INET6: + return jhash2(su->sin6.sin6_addr.s6_addr32, + array_size(su->sin6.sin6_addr.s6_addr32), 0); + } + return 0; +} + +size_t family2addrsize(int family) +{ + switch (family) { + case AF_INET: + return sizeof(struct in_addr); + case AF_INET6: + return sizeof(struct in6_addr); + } + return 0; +} + +size_t sockunion_get_addrlen(const union sockunion *su) +{ + return family2addrsize(sockunion_family(su)); +} + +const uint8_t *sockunion_get_addr(const union sockunion *su) +{ + switch (sockunion_family(su)) { + case AF_INET: + return (const uint8_t *)&su->sin.sin_addr.s_addr; + case AF_INET6: + return (const uint8_t *)&su->sin6.sin6_addr; + } + return NULL; +} + +void sockunion_set(union sockunion *su, int family, const uint8_t *addr, + size_t bytes) +{ + if (family2addrsize(family) != bytes) + return; + + sockunion_family(su) = family; + switch (family) { + case AF_INET: + memcpy(&su->sin.sin_addr.s_addr, addr, bytes); + break; + case AF_INET6: + memcpy(&su->sin6.sin6_addr, addr, bytes); + break; + } +} + +/* After TCP connection is established. Get local address and port. */ +union sockunion *sockunion_getsockname(int fd) +{ + int ret; + socklen_t len; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + char tmp_buffer[128]; + } name; + union sockunion *su; + + memset(&name, 0, sizeof(name)); + len = sizeof(name); + + ret = getsockname(fd, (struct sockaddr *)&name, &len); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "Can't get local address and port by getsockname: %s", + safe_strerror(errno)); + return NULL; + } + + if (name.sa.sa_family == AF_INET) { + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + memcpy(su, &name, sizeof(struct sockaddr_in)); + return su; + } + if (name.sa.sa_family == AF_INET6) { + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + memcpy(su, &name, sizeof(struct sockaddr_in6)); + sockunion_normalise_mapped(su); + return su; + } + + flog_err( + EC_LIB_SOCKET, + "Unexpected AFI received(%d) for sockunion_getsockname call for fd: %d", + name.sa.sa_family, fd); + return NULL; +} + +/* After TCP connection is established. Get remote address and port. */ +union sockunion *sockunion_getpeername(int fd) +{ + int ret; + socklen_t len; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + char tmp_buffer[128]; + } name; + union sockunion *su; + + memset(&name, 0, sizeof(name)); + len = sizeof(name); + ret = getpeername(fd, (struct sockaddr *)&name, &len); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, "Can't get remote address and port: %s", + safe_strerror(errno)); + return NULL; + } + + if (name.sa.sa_family == AF_INET) { + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + memcpy(su, &name, sizeof(struct sockaddr_in)); + return su; + } + if (name.sa.sa_family == AF_INET6) { + su = XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + memcpy(su, &name, sizeof(struct sockaddr_in6)); + sockunion_normalise_mapped(su); + return su; + } + + flog_err( + EC_LIB_SOCKET, + "Unexpected AFI received(%d) for sockunion_getpeername call for fd: %d", + name.sa.sa_family, fd); + return NULL; +} + +/* Print sockunion structure */ +static void __attribute__((unused)) sockunion_print(const union sockunion *su) +{ + if (su == NULL) + return; + + switch (su->sa.sa_family) { + case AF_INET: + printf("%pI4\n", &su->sin.sin_addr); + break; + case AF_INET6: + printf("%pI6\n", &su->sin6.sin6_addr); + break; +#ifdef AF_LINK + case AF_LINK: { + struct sockaddr_dl *sdl; + + sdl = (struct sockaddr_dl *)&(su->sa); + printf("link#%d\n", sdl->sdl_index); + } break; +#endif /* AF_LINK */ + default: + printf("af_unknown %d\n", su->sa.sa_family); + break; + } +} + +int in6addr_cmp(const struct in6_addr *addr1, const struct in6_addr *addr2) +{ + unsigned int i; + const uint8_t *p1, *p2; + + p1 = (const uint8_t *)addr1; + p2 = (const uint8_t *)addr2; + + for (i = 0; i < sizeof(struct in6_addr); i++) { + if (p1[i] > p2[i]) + return 1; + else if (p1[i] < p2[i]) + return -1; + } + return 0; +} + +int sockunion_cmp(const union sockunion *su1, const union sockunion *su2) +{ + if (su1->sa.sa_family > su2->sa.sa_family) + return 1; + if (su1->sa.sa_family < su2->sa.sa_family) + return -1; + + if (su1->sa.sa_family == AF_INET) { + if (ntohl(sockunion2ip(su1)) == ntohl(sockunion2ip(su2))) + return 0; + if (ntohl(sockunion2ip(su1)) > ntohl(sockunion2ip(su2))) + return 1; + else + return -1; + } + if (su1->sa.sa_family == AF_INET6) + return in6addr_cmp(&su1->sin6.sin6_addr, &su2->sin6.sin6_addr); + return 0; +} + +/* Duplicate sockunion. */ +union sockunion *sockunion_dup(const union sockunion *su) +{ + union sockunion *dup = + XCALLOC(MTYPE_SOCKUNION, sizeof(union sockunion)); + memcpy(dup, su, sizeof(union sockunion)); + return dup; +} + +void sockunion_free(union sockunion *su) +{ + XFREE(MTYPE_SOCKUNION, su); +} + +void sockunion_init(union sockunion *su) +{ + memset(su, 0, sizeof(union sockunion)); +} + +printfrr_ext_autoreg_p("SU", printfrr_psu); +static ssize_t printfrr_psu(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const union sockunion *su = ptr; + bool include_port = false, include_scope = false; + bool endflags = false; + ssize_t ret = 0; + char cbuf[INET6_ADDRSTRLEN]; + + if (!su) + return bputs(buf, "(null)"); + + while (!endflags) { + switch (*ea->fmt) { + case 'p': + ea->fmt++; + include_port = true; + break; + case 's': + ea->fmt++; + include_scope = true; + break; + default: + endflags = true; + break; + } + } + + switch (sockunion_family(su)) { + case AF_UNSPEC: + ret += bputs(buf, "(unspec)"); + break; + case AF_INET: + inet_ntop(AF_INET, &su->sin.sin_addr, cbuf, sizeof(cbuf)); + ret += bputs(buf, cbuf); + if (include_port) + ret += bprintfrr(buf, ":%d", ntohs(su->sin.sin_port)); + break; + case AF_INET6: + if (include_port) + ret += bputch(buf, '['); + inet_ntop(AF_INET6, &su->sin6.sin6_addr, cbuf, sizeof(cbuf)); + ret += bputs(buf, cbuf); + if (include_scope && su->sin6.sin6_scope_id) + ret += bprintfrr(buf, "%%%u", + (unsigned int)su->sin6.sin6_scope_id); + if (include_port) + ret += bprintfrr(buf, "]:%d", + ntohs(su->sin6.sin6_port)); + break; + case AF_UNIX: { + int len; +#ifdef __linux__ + if (su->sun.sun_path[0] == '\0' && su->sun.sun_path[1]) { + len = strnlen(su->sun.sun_path + 1, + sizeof(su->sun.sun_path) - 1); + ret += bprintfrr(buf, "@%*pSE", len, + su->sun.sun_path + 1); + break; + } +#endif + len = strnlen(su->sun.sun_path, sizeof(su->sun.sun_path)); + ret += bprintfrr(buf, "%*pSE", len, su->sun.sun_path); + break; + } + default: + ret += bprintfrr(buf, "(af %d)", sockunion_family(su)); + } + + return ret; +} + +int sockunion_is_null(const union sockunion *su) +{ + unsigned char null_s6_addr[16] = {0}; + + switch (sockunion_family(su)) { + case AF_UNSPEC: + return 1; + case AF_INET: + return (su->sin.sin_addr.s_addr == 0); + case AF_INET6: + return !memcmp(su->sin6.sin6_addr.s6_addr, null_s6_addr, + sizeof(null_s6_addr)); + default: + return 0; + } +} + +printfrr_ext_autoreg_i("PF", printfrr_pf); +static ssize_t printfrr_pf(struct fbuf *buf, struct printfrr_eargs *ea, + uintmax_t val) +{ + switch (val) { + case AF_INET: + return bputs(buf, "AF_INET"); + case AF_INET6: + return bputs(buf, "AF_INET6"); + case AF_UNIX: + return bputs(buf, "AF_UNIX"); +#ifdef AF_PACKET + case AF_PACKET: + return bputs(buf, "AF_PACKET"); +#endif +#ifdef AF_NETLINK + case AF_NETLINK: + return bputs(buf, "AF_NETLINK"); +#endif + } + return bprintfrr(buf, "AF_(%ju)", val); +} + +printfrr_ext_autoreg_i("SO", printfrr_so); +static ssize_t printfrr_so(struct fbuf *buf, struct printfrr_eargs *ea, + uintmax_t val) +{ + switch (val) { + case SOCK_STREAM: + return bputs(buf, "SOCK_STREAM"); + case SOCK_DGRAM: + return bputs(buf, "SOCK_DGRAM"); + case SOCK_SEQPACKET: + return bputs(buf, "SOCK_SEQPACKET"); +#ifdef SOCK_RAW + case SOCK_RAW: + return bputs(buf, "SOCK_RAW"); +#endif +#ifdef SOCK_PACKET + case SOCK_PACKET: + return bputs(buf, "SOCK_PACKET"); +#endif + } + return bprintfrr(buf, "SOCK_(%ju)", val); +} diff --git a/lib/sockunion.h b/lib/sockunion.h new file mode 100644 index 0000000..1466512 --- /dev/null +++ b/lib/sockunion.h @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Socket union header. + * Copyright (c) 1997 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_SOCKUNION_H +#define _ZEBRA_SOCKUNION_H + +#include "compiler.h" + +#include "privs.h" +#include "if.h" +#include +#ifdef __OpenBSD__ +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_un sun; +#ifdef __OpenBSD__ + struct sockaddr_mpls smpls; + struct sockaddr_rtlabel rtlabel; +#endif + + /* sockaddr_storage is guaranteed to be larger than the others */ + struct sockaddr_storage sa_storage; +}; + +/* clang-format off */ +/* for functions that want to accept any sockaddr pointer without casts */ +union sockaddrptr { + uniontype(sockaddrptr, union sockunion, su) + uniontype(sockaddrptr, struct sockaddr, sa) + uniontype(sockaddrptr, struct sockaddr_in, sin) + uniontype(sockaddrptr, struct sockaddr_in6, sin6) + uniontype(sockaddrptr, struct sockaddr_un, sun) +#ifdef __OpenBSD__ + uniontype(sockaddrptr, struct sockaddr_mpls, smpls) + uniontype(sockaddrptr, struct sockaddr_rtlabel, rtlabel) +#endif + uniontype(sockaddrptr, struct sockaddr_storage, sa_storage) +} TRANSPARENT_UNION; + +union sockaddrconstptr { + uniontype(sockaddrconstptr, const union sockunion, su) + uniontype(sockaddrconstptr, const struct sockaddr, sa) + uniontype(sockaddrconstptr, const struct sockaddr_in, sin) + uniontype(sockaddrconstptr, const struct sockaddr_in6, sin6) + uniontype(sockaddrconstptr, const struct sockaddr_un, sun) +#ifdef __OpenBSD__ + uniontype(sockaddrconstptr, const struct sockaddr_mpls, smpls) + uniontype(sockaddrconstptr, const struct sockaddr_rtlabel, rtlabel) +#endif + uniontype(sockaddrconstptr, const struct sockaddr_storage, sa_storage) +} TRANSPARENT_UNION; +/* clang-format on */ + +enum connect_result { connect_error, connect_success, connect_in_progress }; + +/* Default address family. */ +#define AF_INET_UNION AF_INET6 + +/* Sockunion address string length. Same as INET6_ADDRSTRLEN. */ +#define SU_ADDRSTRLEN 46 + +/* Macro to set link local index to the IPv6 address. For KAME IPv6 + stack. */ +#ifdef KAME +#define IN6_LINKLOCAL_IFINDEX(a) ((a).s6_addr[2] << 8 | (a).s6_addr[3]) +#define SET_IN6_LINKLOCAL_IFINDEX(a, i) \ + do { \ + (a).s6_addr[2] = ((i) >> 8) & 0xff; \ + (a).s6_addr[3] = (i)&0xff; \ + } while (0) +#else +#define IN6_LINKLOCAL_IFINDEX(a) +#define SET_IN6_LINKLOCAL_IFINDEX(a, i) +#endif /* KAME */ + +#define sockunion_family(X) (X)->sa.sa_family + +#define sockunion2ip(X) (X)->sin.sin_addr.s_addr + +/* Prototypes. */ +extern int str2sockunion(const char *, union sockunion *); +extern const char *sockunion2str(const union sockunion *, char *, size_t); +int in6addr_cmp(const struct in6_addr *addr1, const struct in6_addr *addr2); +extern int sockunion_cmp(const union sockunion *, const union sockunion *); +extern int sockunion_same(const union sockunion *, const union sockunion *); +extern unsigned int sockunion_hash(const union sockunion *); + +extern size_t family2addrsize(int family); +extern size_t sockunion_get_addrlen(const union sockunion *); +extern const uint8_t *sockunion_get_addr(const union sockunion *); +extern void sockunion_set(union sockunion *, int family, const uint8_t *addr, + size_t bytes); + +extern union sockunion *sockunion_str2su(const char *str); +extern int sockunion_accept(int sock, union sockunion *); +extern int sockunion_sizeof(const union sockunion *su); +extern int sockunion_stream_socket(union sockunion *); +extern int sockopt_reuseaddr(int); +extern int sockopt_reuseport(int); +extern int sockopt_v6only(int family, int sock); +extern int sockunion_bind(int sock, union sockunion *, unsigned short, + union sockunion *); +extern int sockopt_ttl(int family, int sock, int ttl); +extern int sockopt_minttl(int family, int sock, int minttl); +extern int sockunion_socket(const union sockunion *su); +extern const char *inet_sutop(const union sockunion *su, char *str); +extern enum connect_result sockunion_connect(int fd, const union sockunion *su, + unsigned short port, ifindex_t); +extern union sockunion *sockunion_getsockname(int); +extern union sockunion *sockunion_getpeername(int); +extern union sockunion *sockunion_dup(const union sockunion *); +extern void sockunion_free(union sockunion *); +extern void sockunion_init(union sockunion *); +extern int sockunion_is_null(const union sockunion *su); + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pSU" (union sockunion *) +#pragma FRR printfrr_ext "%pSU" (struct sockaddr *) +#pragma FRR printfrr_ext "%pSU" (struct sockaddr_storage *) +#pragma FRR printfrr_ext "%pSU" (struct sockaddr_in *) +#pragma FRR printfrr_ext "%pSU" (struct sockaddr_in6 *) +#pragma FRR printfrr_ext "%pSU" (struct sockaddr_un *) + +/* AF_INET/PF_INET & co., using "PF" to avoid confusion with AFI/SAFI */ +#pragma FRR printfrr_ext "%dPF" (int) +/* SOCK_STREAM & co. */ +#pragma FRR printfrr_ext "%dSO" (int) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_SOCKUNION_H */ diff --git a/lib/spf_backoff.c b/lib/spf_backoff.c new file mode 100644 index 0000000..b05c44d --- /dev/null +++ b/lib/spf_backoff.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of the IETF SPF delay algorithm + * as explained in draft-ietf-rtgwg-backoff-algo-04 + * + * Created: 25-01-2017 by S. Litkowski + * + * Copyright (C) 2017 Orange Labs http://www.orange.com/ + * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc. + * + * This file is part of FRRouting (FRR) + */ + +#include + +#include "spf_backoff.h" + +#include "command.h" +#include "memory.h" +#include "frrevent.h" +#include "vty.h" + +DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF, "SPF backoff"); +DEFINE_MTYPE_STATIC(LIB, SPF_BACKOFF_NAME, "SPF backoff name"); + +static bool debug_spf_backoff = false; +#define backoff_debug(...) \ + do { \ + if (debug_spf_backoff) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +enum spf_backoff_state { + SPF_BACKOFF_QUIET, + SPF_BACKOFF_SHORT_WAIT, + SPF_BACKOFF_LONG_WAIT +}; + +struct spf_backoff { + struct event_loop *m; + + /* Timers as per draft */ + long init_delay; + long short_delay; + long long_delay; + long holddown; + long timetolearn; + + /* State machine */ + enum spf_backoff_state state; + struct event *t_holddown; + struct event *t_timetolearn; + + /* For debugging */ + char *name; + struct timeval first_event_time; + struct timeval last_event_time; +}; + +static const char *spf_backoff_state2str(enum spf_backoff_state state) +{ + switch (state) { + case SPF_BACKOFF_QUIET: + return "QUIET"; + case SPF_BACKOFF_SHORT_WAIT: + return "SHORT_WAIT"; + case SPF_BACKOFF_LONG_WAIT: + return "LONG_WAIT"; + } + return "???"; +} + +struct spf_backoff *spf_backoff_new(struct event_loop *m, const char *name, + long init_delay, long short_delay, + long long_delay, long holddown, + long timetolearn) +{ + struct spf_backoff *rv; + + rv = XCALLOC(MTYPE_SPF_BACKOFF, sizeof(*rv)); + rv->m = m; + + rv->init_delay = init_delay; + rv->short_delay = short_delay; + rv->long_delay = long_delay; + rv->holddown = holddown; + rv->timetolearn = timetolearn; + + rv->state = SPF_BACKOFF_QUIET; + + rv->name = XSTRDUP(MTYPE_SPF_BACKOFF_NAME, name); + return rv; +} + +void spf_backoff_free(struct spf_backoff *backoff) +{ + if (!backoff) + return; + + event_cancel(&backoff->t_holddown); + event_cancel(&backoff->t_timetolearn); + XFREE(MTYPE_SPF_BACKOFF_NAME, backoff->name); + + XFREE(MTYPE_SPF_BACKOFF, backoff); +} + +static void spf_backoff_timetolearn_elapsed(struct event *thread) +{ + struct spf_backoff *backoff = EVENT_ARG(thread); + + backoff->state = SPF_BACKOFF_LONG_WAIT; + backoff_debug("SPF Back-off(%s) TIMETOLEARN elapsed, move to state %s", + backoff->name, spf_backoff_state2str(backoff->state)); +} + +static void spf_backoff_holddown_elapsed(struct event *thread) +{ + struct spf_backoff *backoff = EVENT_ARG(thread); + + EVENT_OFF(backoff->t_timetolearn); + timerclear(&backoff->first_event_time); + backoff->state = SPF_BACKOFF_QUIET; + backoff_debug("SPF Back-off(%s) HOLDDOWN elapsed, move to state %s", + backoff->name, spf_backoff_state2str(backoff->state)); +} + +long spf_backoff_schedule(struct spf_backoff *backoff) +{ + long rv = 0; + struct timeval now; + + gettimeofday(&now, NULL); + + backoff_debug("SPF Back-off(%s) schedule called in state %s", + backoff->name, spf_backoff_state2str(backoff->state)); + + backoff->last_event_time = now; + + switch (backoff->state) { + case SPF_BACKOFF_QUIET: + backoff->state = SPF_BACKOFF_SHORT_WAIT; + event_add_timer_msec( + backoff->m, spf_backoff_timetolearn_elapsed, backoff, + backoff->timetolearn, &backoff->t_timetolearn); + event_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed, + backoff, backoff->holddown, + &backoff->t_holddown); + backoff->first_event_time = now; + rv = backoff->init_delay; + break; + case SPF_BACKOFF_SHORT_WAIT: + case SPF_BACKOFF_LONG_WAIT: + event_cancel(&backoff->t_holddown); + event_add_timer_msec(backoff->m, spf_backoff_holddown_elapsed, + backoff, backoff->holddown, + &backoff->t_holddown); + if (backoff->state == SPF_BACKOFF_SHORT_WAIT) + rv = backoff->short_delay; + else + rv = backoff->long_delay; + break; + } + + backoff_debug( + "SPF Back-off(%s) changed state to %s and returned %ld delay", + backoff->name, spf_backoff_state2str(backoff->state), rv); + return rv; +} + +static const char *timeval_format(struct timeval *tv) +{ + struct tm tm_store; + struct tm *tm; + static char timebuf[256]; + + if (!tv->tv_sec && !tv->tv_usec) + return "(never)"; + + tm = localtime_r(&tv->tv_sec, &tm_store); + if (!tm + || strftime(timebuf, sizeof(timebuf), "%Z %a %Y-%m-%d %H:%M:%S", tm) + == 0) { + return "???"; + } + + size_t offset = strlen(timebuf); + snprintf(timebuf + offset, sizeof(timebuf) - offset, ".%ld", + (long int)tv->tv_usec); + + return timebuf; +} + +void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty, + const char *prefix) +{ + vty_out(vty, "%sCurrent state: %s\n", prefix, + spf_backoff_state2str(backoff->state)); + vty_out(vty, "%sInit timer: %ld msec\n", prefix, + backoff->init_delay); + vty_out(vty, "%sShort timer: %ld msec\n", prefix, + backoff->short_delay); + vty_out(vty, "%sLong timer: %ld msec\n", prefix, + backoff->long_delay); + vty_out(vty, "%sHolddown timer: %ld msec\n", prefix, + backoff->holddown); + if (backoff->t_holddown) { + struct timeval remain = event_timer_remain(backoff->t_holddown); + + vty_out(vty, "%s Still runs for %lld msec\n", + prefix, + (long long)remain.tv_sec * 1000 + + remain.tv_usec / 1000); + } else { + vty_out(vty, "%s Inactive\n", prefix); + } + + vty_out(vty, "%sTimeToLearn timer: %ld msec\n", prefix, + backoff->timetolearn); + if (backoff->t_timetolearn) { + struct timeval remain = + event_timer_remain(backoff->t_timetolearn); + vty_out(vty, "%s Still runs for %lld msec\n", + prefix, + (long long)remain.tv_sec * 1000 + + remain.tv_usec / 1000); + } else { + vty_out(vty, "%s Inactive\n", prefix); + } + + vty_out(vty, "%sFirst event: %s\n", prefix, + timeval_format(&backoff->first_event_time)); + vty_out(vty, "%sLast event: %s\n", prefix, + timeval_format(&backoff->last_event_time)); +} + +DEFUN(spf_backoff_debug, + spf_backoff_debug_cmd, + "debug spf-delay-ietf", + DEBUG_STR + "SPF Back-off Debugging\n") +{ + debug_spf_backoff = true; + return CMD_SUCCESS; +} + +DEFUN(no_spf_backoff_debug, + no_spf_backoff_debug_cmd, + "no debug spf-delay-ietf", + NO_STR + DEBUG_STR + "SPF Back-off Debugging\n") +{ + debug_spf_backoff = false; + return CMD_SUCCESS; +} + +int spf_backoff_write_config(struct vty *vty) +{ + int written = 0; + + if (debug_spf_backoff) { + vty_out(vty, "debug spf-delay-ietf\n"); + written++; + } + + return written; +} + +void spf_backoff_cmd_init(void) +{ + install_element(ENABLE_NODE, &spf_backoff_debug_cmd); + install_element(CONFIG_NODE, &spf_backoff_debug_cmd); + install_element(ENABLE_NODE, &no_spf_backoff_debug_cmd); + install_element(CONFIG_NODE, &no_spf_backoff_debug_cmd); +} + +long spf_backoff_init_delay(struct spf_backoff *backoff) +{ + return backoff->init_delay; +} + +long spf_backoff_short_delay(struct spf_backoff *backoff) +{ + return backoff->short_delay; +} + +long spf_backoff_long_delay(struct spf_backoff *backoff) +{ + return backoff->long_delay; +} + +long spf_backoff_holddown(struct spf_backoff *backoff) +{ + return backoff->holddown; +} + +long spf_backoff_timetolearn(struct spf_backoff *backoff) +{ + return backoff->timetolearn; +} diff --git a/lib/spf_backoff.h b/lib/spf_backoff.h new file mode 100644 index 0000000..83f1b76 --- /dev/null +++ b/lib/spf_backoff.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of the IETF SPF delay algorithm + * as explained in draft-ietf-rtgwg-backoff-algo-04 + * + * Created: 25-01-2017 by S. Litkowski + * + * Copyright (C) 2017 Orange Labs http://www.orange.com/ + * Copyright (C) 2017 by Christian Franke, Open Source Routing / NetDEF Inc. + * + * This file is part of FRRouting (FRR) + */ +#ifndef _ZEBRA_SPF_BACKOFF_H +#define _ZEBRA_SPF_BACKOFF_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct spf_backoff; +struct event_loop; +struct vty; + +struct spf_backoff *spf_backoff_new(struct event_loop *m, const char *name, + long init_delay, long short_delay, + long long_delay, long holddown, + long timetolearn); + +void spf_backoff_free(struct spf_backoff *backoff); + +/* Called whenever an IGP event is received, returns how many + * milliseconds routing table computation should be delayed */ +long spf_backoff_schedule(struct spf_backoff *backoff); + +/* Shows status of SPF backoff instance */ +void spf_backoff_show(struct spf_backoff *backoff, struct vty *vty, + const char *prefix); + +/* Writes out global SPF backoff debug config */ +int spf_backoff_write_config(struct vty *vty); + +/* Registers global SPF backoff debug commands */ +void spf_backoff_cmd_init(void); + +/* Accessor functions for SPF backoff parameters */ +long spf_backoff_init_delay(struct spf_backoff *backoff); +long spf_backoff_short_delay(struct spf_backoff *backoff); +long spf_backoff_long_delay(struct spf_backoff *backoff); +long spf_backoff_holddown(struct spf_backoff *backoff); +long spf_backoff_timetolearn(struct spf_backoff *backoff); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/srcdest_table.c b/lib/srcdest_table.c new file mode 100644 index 0000000..3247a03 --- /dev/null +++ b/lib/srcdest_table.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRC-DEST Routing Table + * + * Copyright (C) 2017 by David Lamparter & Christian Franke, + * Open Source Routing / NetDEF Inc. + * + * This file is part of FRRouting (FRR) + */ + +#include + +#include "srcdest_table.h" + +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "printfrr.h" + +DEFINE_MTYPE_STATIC(LIB, ROUTE_SRC_NODE, "Route source node"); + +/* ----- functions to manage rnodes _with_ srcdest table ----- */ +struct srcdest_rnode { + /* must be first in structure for casting to/from route_node */ + ROUTE_NODE_FIELDS; + + struct route_table *src_table; +}; + +static struct srcdest_rnode *srcdest_rnode_from_rnode(struct route_node *rn) +{ + assert(rnode_is_dstnode(rn)); + return (struct srcdest_rnode *)rn; +} + +static struct route_node *srcdest_rnode_to_rnode(struct srcdest_rnode *srn) +{ + return (struct route_node *)srn; +} + +static struct route_node *srcdest_rnode_create(route_table_delegate_t *delegate, + struct route_table *table) +{ + struct srcdest_rnode *srn; + srn = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct srcdest_rnode)); + return srcdest_rnode_to_rnode(srn); +} + +static void srcdest_rnode_destroy(route_table_delegate_t *delegate, + struct route_table *table, + struct route_node *rn) +{ + struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); + struct route_table *src_table; + + /* Clear route node's src_table here already, otherwise the + * deletion of the last node in the src_table will trigger + * another call to route_table_finish for the src_table. + * + * (Compare with srcdest_srcnode_destroy) + */ + src_table = srn->src_table; + srn->src_table = NULL; + route_table_finish(src_table); + XFREE(MTYPE_ROUTE_NODE, rn); +} + +route_table_delegate_t _srcdest_dstnode_delegate = { + .create_node = srcdest_rnode_create, + .destroy_node = srcdest_rnode_destroy}; + +/* ----- functions to manage rnodes _in_ srcdest table ----- */ + +/* node creation / deletion for srcdest source prefix nodes. + * the route_node isn't actually different from the normal route_node, + * but the cleanup is special to free the table (and possibly the + * destination prefix's route_node) */ + +static struct route_node * +srcdest_srcnode_create(route_table_delegate_t *delegate, + struct route_table *table) +{ + return XCALLOC(MTYPE_ROUTE_SRC_NODE, sizeof(struct route_node)); +} + +static void srcdest_srcnode_destroy(route_table_delegate_t *delegate, + struct route_table *table, + struct route_node *rn) +{ + struct srcdest_rnode *srn; + + XFREE(MTYPE_ROUTE_SRC_NODE, rn); + + srn = route_table_get_info(table); + if (srn->src_table && route_table_count(srn->src_table) == 0) { + /* deleting the route_table from inside destroy_node is ONLY + * permitted IF table->count is 0! see lib/table.c + * route_node_delete() + * for details */ + route_table_finish(srn->src_table); + srn->src_table = NULL; + + /* drop the ref we're holding in srcdest_node_get(). there + * might be + * non-srcdest routes, so the route_node may still exist. + * hence, it's + * important to clear src_table above. */ + route_unlock_node(srcdest_rnode_to_rnode(srn)); + } +} + +route_table_delegate_t _srcdest_srcnode_delegate = { + .create_node = srcdest_srcnode_create, + .destroy_node = srcdest_srcnode_destroy}; + +/* NB: read comments in code for refcounting before using! */ +static struct route_node *srcdest_srcnode_get(struct route_node *rn, + const struct prefix_ipv6 *src_p) +{ + struct srcdest_rnode *srn; + + if (!src_p || src_p->prefixlen == 0) + return rn; + + srn = srcdest_rnode_from_rnode(rn); + if (!srn->src_table) { + /* this won't use srcdest_rnode, we're already on the source + * here */ + srn->src_table = route_table_init_with_delegate( + &_srcdest_srcnode_delegate); + route_table_set_info(srn->src_table, srn); + + /* there is no route_unlock_node on the original rn here. + * The reference is kept for the src_table. */ + } else { + /* only keep 1 reference for the src_table, makes the + * refcounting + * more similar to the non-srcdest case. Either way after + * return from + * function, the only reference held is the one on the return + * value. + * + * We can safely drop our reference here because src_table is + * holding + * another reference, so this won't free rn */ + route_unlock_node(rn); + } + + return route_node_get(srn->src_table, (const struct prefix *)src_p); +} + +static struct route_node *srcdest_srcnode_lookup( + struct route_node *rn, + const struct prefix_ipv6 *src_p) +{ + struct srcdest_rnode *srn; + + if (!rn || !src_p || src_p->prefixlen == 0) + return rn; + + /* We got this rn from a lookup, so its refcnt was incremented. As we + * won't + * return return rn from any point beyond here, we should decrement its + * refcnt. + */ + route_unlock_node(rn); + + srn = srcdest_rnode_from_rnode(rn); + if (!srn->src_table) + return NULL; + + return route_node_lookup(srn->src_table, (const struct prefix *)src_p); +} + +/* ----- exported functions ----- */ + +struct route_table *srcdest_table_init(void) +{ + return route_table_init_with_delegate(&_srcdest_dstnode_delegate); +} + +struct route_node *srcdest_route_next(struct route_node *rn) +{ + struct route_node *next, *parent; + + /* For a non src-dest node, just return route_next */ + if (!(rnode_is_dstnode(rn) || rnode_is_srcnode(rn))) + return route_next(rn); + + if (rnode_is_dstnode(rn)) { + /* This means the route_node is part of the top hierarchy + * and refers to a destination prefix. */ + struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); + + if (srn->src_table) + next = route_top(srn->src_table); + else + next = NULL; + + if (next) { + /* There is a source prefix. Return the node for it */ + route_unlock_node(rn); + return next; + } else { + /* There is no source prefix, just continue as usual */ + return route_next(rn); + } + } + + /* This part handles the case of iterating source nodes. */ + parent = route_lock_node(route_table_get_info(rn->table)); + next = route_next(rn); + + if (next) { + /* There is another source node, continue in the source table */ + route_unlock_node(parent); + return next; + } else { + /* The source table is complete, continue in the parent table */ + return route_next(parent); + } +} + +struct route_node *srcdest_rnode_get(struct route_table *table, + union prefixconstptr dst_pu, + const struct prefix_ipv6 *src_p) +{ + const struct prefix_ipv6 *dst_p = dst_pu.p6; + struct route_node *rn; + + rn = route_node_get(table, (const struct prefix *)dst_p); + return srcdest_srcnode_get(rn, src_p); +} + +struct route_node *srcdest_rnode_lookup(struct route_table *table, + union prefixconstptr dst_pu, + const struct prefix_ipv6 *src_p) +{ + const struct prefix_ipv6 *dst_p = dst_pu.p6; + struct route_node *rn; + struct route_node *srn; + + rn = route_node_lookup_maynull(table, (const struct prefix *)dst_p); + srn = srcdest_srcnode_lookup(rn, src_p); + + if (rn != NULL && rn == srn && !rn->info) { + /* Match the behavior of route_node_lookup and don't return an + * empty route-node for a dest-route */ + route_unlock_node(rn); + return NULL; + } + return srn; +} + +void srcdest_rnode_prefixes(const struct route_node *rn, + const struct prefix **p, + const struct prefix **src_p) +{ + if (rnode_is_srcnode(rn)) { + struct route_node *dst_rn = route_table_get_info(rn->table); + if (p) + *p = &dst_rn->p; + if (src_p) + *src_p = &rn->p; + } else { + if (p) + *p = &rn->p; + if (src_p) + *src_p = NULL; + } +} + +const char *srcdest2str(const struct prefix *dst_p, + const struct prefix_ipv6 *src_p, + char *str, int size) +{ + char dst_buf[PREFIX_STRLEN], src_buf[PREFIX_STRLEN]; + + snprintf(str, size, "%s%s%s", + prefix2str(dst_p, dst_buf, sizeof(dst_buf)), + (src_p && src_p->prefixlen) ? " from " : "", + (src_p && src_p->prefixlen) + ? prefix2str(src_p, src_buf, sizeof(src_buf)) + : ""); + return str; +} + +const char *srcdest_rnode2str(const struct route_node *rn, char *str, int size) +{ + const struct prefix *dst_p, *src_p; + + srcdest_rnode_prefixes(rn, &dst_p, &src_p); + return srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, str, size); +} + +printfrr_ext_autoreg_p("RN", printfrr_rn); +static ssize_t printfrr_rn(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + const struct route_node *rn = ptr; + const struct prefix *dst_p, *src_p; + char cbuf[PREFIX_STRLEN * 2 + 6]; + + if (!rn) + return bputs(buf, "(null)"); + + srcdest_rnode_prefixes(rn, &dst_p, &src_p); + srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, + cbuf, sizeof(cbuf)); + return bputs(buf, cbuf); +} + +struct route_table *srcdest_srcnode_table(struct route_node *rn) +{ + if (rnode_is_dstnode(rn)) { + struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); + + return srn->src_table; + } + return NULL; +} diff --git a/lib/srcdest_table.h b/lib/srcdest_table.h new file mode 100644 index 0000000..ff97f9b --- /dev/null +++ b/lib/srcdest_table.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRC-DEST Routing Table + * + * Copyright (C) 2017 by David Lamparter & Christian Franke, + * Open Source Routing / NetDEF Inc. + * + * This file is part of FRRouting (FRR) + */ + +#ifndef _ZEBRA_SRC_DEST_TABLE_H +#define _ZEBRA_SRC_DEST_TABLE_H + +/* old/IPv4/non-srcdest: + * table -> route_node .info -> [obj] + * + * new/IPv6/srcdest: + * table -...-> srcdest_rnode [prefix = dest] .info -> [obj] + * .src_table -> + * srcdest table -...-> route_node [prefix = src] .info -> [obj] + * + * non-srcdest routes (src = ::/0) are treated just like before, their + * information being directly there in the info pointer. + * + * srcdest routes are found by looking up destination first, then looking + * up the source in the "src_table". src_table contains normal route_nodes, + * whose prefix is the _source_ prefix. + * + * NB: info can be NULL on the destination rnode, if there are only srcdest + * routes for a particular destination prefix. + */ + +#include "prefix.h" +#include "table.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SRCDEST2STR_BUFFER (2*PREFIX2STR_BUFFER + sizeof(" from ")) + +/* extended route node for IPv6 srcdest routing */ +struct srcdest_rnode; + +extern route_table_delegate_t _srcdest_dstnode_delegate; +extern route_table_delegate_t _srcdest_srcnode_delegate; + +extern struct route_table *srcdest_table_init(void); +extern struct route_node *srcdest_rnode_get(struct route_table *table, + union prefixconstptr dst_pu, + const struct prefix_ipv6 *src_p); +extern struct route_node *srcdest_rnode_lookup(struct route_table *table, + union prefixconstptr dst_pu, + const struct prefix_ipv6 *src_p); +extern void srcdest_rnode_prefixes(const struct route_node *rn, + const struct prefix **p, + const struct prefix **src_p); +extern const char *srcdest2str(const struct prefix *dst_p, + const struct prefix_ipv6 *src_p, + char *str, int size); +extern const char *srcdest_rnode2str(const struct route_node *rn, char *str, + int size); +extern struct route_node *srcdest_route_next(struct route_node *rn); + +static inline int rnode_is_dstnode(const struct route_node *rn) +{ + return rn->table->delegate == &_srcdest_dstnode_delegate; +} + +static inline int rnode_is_srcnode(const struct route_node *rn) +{ + return rn->table->delegate == &_srcdest_srcnode_delegate; +} + +static inline struct route_table *srcdest_rnode_table(struct route_node *rn) +{ + if (rnode_is_srcnode(rn)) { + struct route_node *dst_rn = + (struct route_node *)route_table_get_info(rn->table); + return dst_rn->table; + } else { + return rn->table; + } +} +static inline void *srcdest_rnode_table_info(struct route_node *rn) +{ + return route_table_get_info(srcdest_rnode_table(rn)); +} + +extern struct route_table *srcdest_srcnode_table(struct route_node *rn); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_SRC_DEST_TABLE_H */ diff --git a/lib/srte.h b/lib/srte.h new file mode 100644 index 0000000..69c3dbc --- /dev/null +++ b/lib/srte.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SR-TE definitions + * Copyright 2020 NetDef Inc. + * Sascha Kattelmann + */ + +#ifndef _FRR_SRTE_H +#define _FRR_SRTE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SRTE_POLICY_NAME_MAX_LENGTH 64 + +enum zebra_sr_policy_status { + ZEBRA_SR_POLICY_UP = 0, + ZEBRA_SR_POLICY_DOWN, +}; + +static inline int sr_policy_compare(const struct ipaddr *a_endpoint, + const struct ipaddr *b_endpoint, + uint32_t a_color, uint32_t b_color) +{ + int ret; + + ret = ipaddr_cmp(a_endpoint, b_endpoint); + if (ret < 0) + return -1; + if (ret > 0) + return 1; + + return a_color - b_color; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_SRTE_H */ diff --git a/lib/srv6.c b/lib/srv6.c new file mode 100644 index 0000000..a82103e --- /dev/null +++ b/lib/srv6.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRv6 definitions + * Copyright (C) 2020 Hiroki Shirokura, LINE Corporation + */ + +#include "zebra.h" + +#include "srv6.h" +#include "log.h" + +DEFINE_QOBJ_TYPE(srv6_locator); +DEFINE_MTYPE_STATIC(LIB, SRV6_LOCATOR, "SRV6 locator"); +DEFINE_MTYPE_STATIC(LIB, SRV6_LOCATOR_CHUNK, "SRV6 locator chunk"); + +const char *seg6local_action2str(uint32_t action) +{ + switch (action) { + case ZEBRA_SEG6_LOCAL_ACTION_END: + return "End"; + case ZEBRA_SEG6_LOCAL_ACTION_END_X: + return "End.X"; + case ZEBRA_SEG6_LOCAL_ACTION_END_T: + return "End.T"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DX2: + return "End.DX2"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DX6: + return "End.DX6"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DX4: + return "End.DX4"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT6: + return "End.DT6"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT4: + return "End.DT4"; + case ZEBRA_SEG6_LOCAL_ACTION_END_B6: + return "End.B6"; + case ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP: + return "End.B6.Encap"; + case ZEBRA_SEG6_LOCAL_ACTION_END_BM: + return "End.BM"; + case ZEBRA_SEG6_LOCAL_ACTION_END_S: + return "End.S"; + case ZEBRA_SEG6_LOCAL_ACTION_END_AS: + return "End.AS"; + case ZEBRA_SEG6_LOCAL_ACTION_END_AM: + return "End.AM"; + case ZEBRA_SEG6_LOCAL_ACTION_END_DT46: + return "End.DT46"; + case ZEBRA_SEG6_LOCAL_ACTION_UNSPEC: + return "unspec"; + default: + return "unknown"; + } +} + +int snprintf_seg6_segs(char *str, + size_t size, const struct seg6_segs *segs) +{ + str[0] = '\0'; + for (size_t i = 0; i < segs->num_segs; i++) { + char addr[INET6_ADDRSTRLEN]; + bool not_last = (i + 1) < segs->num_segs; + + inet_ntop(AF_INET6, &segs->segs[i], addr, sizeof(addr)); + strlcat(str, addr, size); + strlcat(str, not_last ? "," : "", size); + } + return strlen(str); +} + +const char *seg6local_context2str(char *str, size_t size, + const struct seg6local_context *ctx, + uint32_t action) +{ + switch (action) { + + case ZEBRA_SEG6_LOCAL_ACTION_END: + snprintf(str, size, "USP"); + return str; + + case ZEBRA_SEG6_LOCAL_ACTION_END_X: + case ZEBRA_SEG6_LOCAL_ACTION_END_DX6: + snprintfrr(str, size, "nh6 %pI6", &ctx->nh6); + return str; + + case ZEBRA_SEG6_LOCAL_ACTION_END_DX4: + snprintfrr(str, size, "nh4 %pI4", &ctx->nh4); + return str; + + case ZEBRA_SEG6_LOCAL_ACTION_END_T: + case ZEBRA_SEG6_LOCAL_ACTION_END_DT6: + case ZEBRA_SEG6_LOCAL_ACTION_END_DT4: + case ZEBRA_SEG6_LOCAL_ACTION_END_DT46: + snprintf(str, size, "table %u", ctx->table); + return str; + + case ZEBRA_SEG6_LOCAL_ACTION_END_B6: + case ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP: + snprintfrr(str, size, "nh6 %pI6", &ctx->nh6); + return str; + case ZEBRA_SEG6_LOCAL_ACTION_END_DX2: + case ZEBRA_SEG6_LOCAL_ACTION_END_BM: + case ZEBRA_SEG6_LOCAL_ACTION_END_S: + case ZEBRA_SEG6_LOCAL_ACTION_END_AS: + case ZEBRA_SEG6_LOCAL_ACTION_END_AM: + case ZEBRA_SEG6_LOCAL_ACTION_UNSPEC: + default: + snprintf(str, size, "unknown(%s)", __func__); + return str; + } +} + +void srv6_locator_chunk_list_free(void *data) +{ + struct srv6_locator_chunk *chunk = data; + + srv6_locator_chunk_free(&chunk); +} + +struct srv6_locator *srv6_locator_alloc(const char *name) +{ + struct srv6_locator *locator = NULL; + + locator = XCALLOC(MTYPE_SRV6_LOCATOR, sizeof(struct srv6_locator)); + strlcpy(locator->name, name, sizeof(locator->name)); + locator->chunks = list_new(); + locator->chunks->del = srv6_locator_chunk_list_free; + + QOBJ_REG(locator, srv6_locator); + return locator; +} + +struct srv6_locator_chunk *srv6_locator_chunk_alloc(void) +{ + struct srv6_locator_chunk *chunk = NULL; + + chunk = XCALLOC(MTYPE_SRV6_LOCATOR_CHUNK, + sizeof(struct srv6_locator_chunk)); + return chunk; +} + +void srv6_locator_free(struct srv6_locator *locator) +{ + if (locator) { + QOBJ_UNREG(locator); + list_delete(&locator->chunks); + + XFREE(MTYPE_SRV6_LOCATOR, locator); + } +} + +void srv6_locator_chunk_free(struct srv6_locator_chunk **chunk) +{ + XFREE(MTYPE_SRV6_LOCATOR_CHUNK, *chunk); +} + +json_object *srv6_locator_chunk_json(const struct srv6_locator_chunk *chunk) +{ + json_object *jo_root = NULL; + + jo_root = json_object_new_object(); + json_object_string_addf(jo_root, "prefix", "%pFX", &chunk->prefix); + json_object_string_add(jo_root, "proto", + zebra_route_string(chunk->proto)); + + return jo_root; +} + +json_object * +srv6_locator_chunk_detailed_json(const struct srv6_locator_chunk *chunk) +{ + json_object *jo_root = NULL; + + jo_root = json_object_new_object(); + + /* set prefix */ + json_object_string_addf(jo_root, "prefix", "%pFX", &chunk->prefix); + + /* set block_bits_length */ + json_object_int_add(jo_root, "blockBitsLength", + chunk->block_bits_length); + + /* set node_bits_length */ + json_object_int_add(jo_root, "nodeBitsLength", chunk->node_bits_length); + + /* set function_bits_length */ + json_object_int_add(jo_root, "functionBitsLength", + chunk->function_bits_length); + + /* set argument_bits_length */ + json_object_int_add(jo_root, "argumentBitsLength", + chunk->argument_bits_length); + + /* set keep */ + json_object_int_add(jo_root, "keep", chunk->keep); + + /* set proto */ + json_object_string_add(jo_root, "proto", + zebra_route_string(chunk->proto)); + + /* set instance */ + json_object_int_add(jo_root, "instance", chunk->instance); + + /* set session_id */ + json_object_int_add(jo_root, "sessionId", chunk->session_id); + + return jo_root; +} + +json_object *srv6_locator_json(const struct srv6_locator *loc) +{ + struct listnode *node; + struct srv6_locator_chunk *chunk; + json_object *jo_root = NULL; + json_object *jo_chunk = NULL; + json_object *jo_chunks = NULL; + + jo_root = json_object_new_object(); + + /* set name */ + json_object_string_add(jo_root, "name", loc->name); + + /* set prefix */ + json_object_string_addf(jo_root, "prefix", "%pFX", &loc->prefix); + + /* set block_bits_length */ + json_object_int_add(jo_root, "blockBitsLength", loc->block_bits_length); + + /* set node_bits_length */ + json_object_int_add(jo_root, "nodeBitsLength", loc->node_bits_length); + + /* set function_bits_length */ + json_object_int_add(jo_root, "functionBitsLength", + loc->function_bits_length); + + /* set argument_bits_length */ + json_object_int_add(jo_root, "argumentBitsLength", + loc->argument_bits_length); + + /* set true if the locator is a Micro-segment (uSID) locator */ + if (CHECK_FLAG(loc->flags, SRV6_LOCATOR_USID)) + json_object_string_add(jo_root, "behavior", "usid"); + + /* set status_up */ + json_object_boolean_add(jo_root, "statusUp", + loc->status_up); + + /* set chunks */ + jo_chunks = json_object_new_array(); + json_object_object_add(jo_root, "chunks", jo_chunks); + for (ALL_LIST_ELEMENTS_RO((struct list *)loc->chunks, node, chunk)) { + jo_chunk = srv6_locator_chunk_json(chunk); + json_object_array_add(jo_chunks, jo_chunk); + } + + return jo_root; +} + +json_object *srv6_locator_detailed_json(const struct srv6_locator *loc) +{ + struct listnode *node; + struct srv6_locator_chunk *chunk; + json_object *jo_root = NULL; + json_object *jo_chunk = NULL; + json_object *jo_chunks = NULL; + + jo_root = json_object_new_object(); + + /* set name */ + json_object_string_add(jo_root, "name", loc->name); + + /* set prefix */ + json_object_string_addf(jo_root, "prefix", "%pFX", &loc->prefix); + + /* set block_bits_length */ + json_object_int_add(jo_root, "blockBitsLength", loc->block_bits_length); + + /* set node_bits_length */ + json_object_int_add(jo_root, "nodeBitsLength", loc->node_bits_length); + + /* set function_bits_length */ + json_object_int_add(jo_root, "functionBitsLength", + loc->function_bits_length); + + /* set argument_bits_length */ + json_object_int_add(jo_root, "argumentBitsLength", + loc->argument_bits_length); + + /* set true if the locator is a Micro-segment (uSID) locator */ + if (CHECK_FLAG(loc->flags, SRV6_LOCATOR_USID)) + json_object_string_add(jo_root, "behavior", "usid"); + + /* set algonum */ + json_object_int_add(jo_root, "algoNum", loc->algonum); + + /* set status_up */ + json_object_boolean_add(jo_root, "statusUp", loc->status_up); + + /* set chunks */ + jo_chunks = json_object_new_array(); + json_object_object_add(jo_root, "chunks", jo_chunks); + for (ALL_LIST_ELEMENTS_RO((struct list *)loc->chunks, node, chunk)) { + jo_chunk = srv6_locator_chunk_detailed_json(chunk); + json_object_array_add(jo_chunks, jo_chunk); + } + + return jo_root; +} diff --git a/lib/srv6.h b/lib/srv6.h new file mode 100644 index 0000000..433c5c1 --- /dev/null +++ b/lib/srv6.h @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SRv6 definitions + * Copyright (C) 2020 Hiroki Shirokura, LINE Corporation + */ + +#ifndef _FRR_SRV6_H +#define _FRR_SRV6_H + +#include +#include "prefix.h" +#include "json.h" + +#include +#include + +#define SRV6_MAX_SIDS 16 +#define SRV6_MAX_SEGS 8 +#define SRV6_LOCNAME_SIZE 256 +#define SRH_BASE_HEADER_LENGTH 8 +#define SRH_SEGMENT_LENGTH 16 + +#ifdef __cplusplus +extern "C" { +#endif + +#define sid2str(sid, str, size) \ + inet_ntop(AF_INET6, sid, str, size) + +/* SRv6 flavors manipulation macros */ +#define CHECK_SRV6_FLV_OP(OPS,OP) ((OPS) & (1 << OP)) +#define SET_SRV6_FLV_OP(OPS,OP) (OPS) |= (1 << OP) +#define UNSET_SRV6_FLV_OP(OPS,OP) (OPS) &= ~(1 << OP) +#define RESET_SRV6_FLV_OP(OPS) (OPS) = 0 + +/* SRv6 Flavors default values */ +#define ZEBRA_DEFAULT_SEG6_LOCAL_FLV_LCBLOCK_LEN 32 +#define ZEBRA_DEFAULT_SEG6_LOCAL_FLV_LCNODE_FN_LEN 16 + +enum seg6_mode_t { + INLINE, + ENCAP, + L2ENCAP, +}; + +enum seg6local_action_t { + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC = 0, + ZEBRA_SEG6_LOCAL_ACTION_END = 1, + ZEBRA_SEG6_LOCAL_ACTION_END_X = 2, + ZEBRA_SEG6_LOCAL_ACTION_END_T = 3, + ZEBRA_SEG6_LOCAL_ACTION_END_DX2 = 4, + ZEBRA_SEG6_LOCAL_ACTION_END_DX6 = 5, + ZEBRA_SEG6_LOCAL_ACTION_END_DX4 = 6, + ZEBRA_SEG6_LOCAL_ACTION_END_DT6 = 7, + ZEBRA_SEG6_LOCAL_ACTION_END_DT4 = 8, + ZEBRA_SEG6_LOCAL_ACTION_END_B6 = 9, + ZEBRA_SEG6_LOCAL_ACTION_END_B6_ENCAP = 10, + ZEBRA_SEG6_LOCAL_ACTION_END_BM = 11, + ZEBRA_SEG6_LOCAL_ACTION_END_S = 12, + ZEBRA_SEG6_LOCAL_ACTION_END_AS = 13, + ZEBRA_SEG6_LOCAL_ACTION_END_AM = 14, + ZEBRA_SEG6_LOCAL_ACTION_END_BPF = 15, + ZEBRA_SEG6_LOCAL_ACTION_END_DT46 = 16, +}; + +/* Flavor operations for SRv6 End* Behaviors */ +enum seg6local_flavor_op { + ZEBRA_SEG6_LOCAL_FLV_OP_UNSPEC = 0, + /* PSP Flavor as per RFC 8986 section #4.16.1 */ + ZEBRA_SEG6_LOCAL_FLV_OP_PSP = 1, + /* USP Flavor as per RFC 8986 section #4.16.2 */ + ZEBRA_SEG6_LOCAL_FLV_OP_USP = 2, + /* USD Flavor as per RFC 8986 section #4.16.3 */ + ZEBRA_SEG6_LOCAL_FLV_OP_USD = 3, + /* NEXT-C-SID Flavor as per draft-ietf-spring-srv6-srh-compression-03 + section 4.1 */ + ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID = 4, +}; + +#define SRV6_SEG_STRLEN 1024 + +struct seg6_segs { + size_t num_segs; + struct in6_addr segs[256]; +}; + +struct seg6local_flavors_info { + /* Flavor operations */ + uint32_t flv_ops; + + /* Locator-Block length, expressed in bits */ + uint8_t lcblock_len; + /* Locator-Node Function length, expressed in bits */ + uint8_t lcnode_func_len; +}; + +struct seg6_seg_stack { + uint8_t num_segs; + struct in6_addr seg[0]; /* 1 or more segs */ +}; + +struct seg6local_context { + struct in_addr nh4; + struct in6_addr nh6; + uint32_t table; + struct seg6local_flavors_info flv; +}; + +struct srv6_locator { + char name[SRV6_LOCNAME_SIZE]; + struct prefix_ipv6 prefix; + + /* + * Bit length of SRv6 locator described in + * draft-ietf-bess-srv6-services-05#section-3.2.1 + */ + uint8_t block_bits_length; + uint8_t node_bits_length; + uint8_t function_bits_length; + uint8_t argument_bits_length; + + int algonum; + uint64_t current; + bool status_up; + struct list *chunks; + + uint8_t flags; +#define SRV6_LOCATOR_USID (1 << 0) /* The SRv6 Locator is a uSID Locator */ + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(srv6_locator); + +struct srv6_locator_chunk { + char locator_name[SRV6_LOCNAME_SIZE]; + struct prefix_ipv6 prefix; + + /* + * Bit length of SRv6 locator described in + * draft-ietf-bess-srv6-services-05#section-3.2.1 + */ + uint8_t block_bits_length; + uint8_t node_bits_length; + uint8_t function_bits_length; + uint8_t argument_bits_length; + + /* + * For Zclient communication values + */ + uint8_t keep; + uint8_t proto; + uint16_t instance; + uint32_t session_id; + + uint8_t flags; +}; + +/* + * SRv6 Endpoint Behavior codepoints, as defined by IANA in + * https://www.iana.org/assignments/segment-routing/segment-routing.xhtml + */ +enum srv6_endpoint_behavior_codepoint { + SRV6_ENDPOINT_BEHAVIOR_RESERVED = 0x0000, + SRV6_ENDPOINT_BEHAVIOR_END = 0x0001, + SRV6_ENDPOINT_BEHAVIOR_END_X = 0x0005, + SRV6_ENDPOINT_BEHAVIOR_END_DT6 = 0x0012, + SRV6_ENDPOINT_BEHAVIOR_END_DT4 = 0x0013, + SRV6_ENDPOINT_BEHAVIOR_END_DT46 = 0x0014, + SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID = 0x002B, + SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID = 0x002C, + SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID = 0x003E, + SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID = 0x003F, + SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID = 0x0040, + SRV6_ENDPOINT_BEHAVIOR_OPAQUE = 0xFFFF, +}; + +struct nexthop_srv6 { + /* SRv6 localsid info for Endpoint-behaviour */ + enum seg6local_action_t seg6local_action; + struct seg6local_context seg6local_ctx; + + /* SRv6 Headend-behaviour */ + struct seg6_seg_stack *seg6_segs; +}; + +static inline const char *seg6_mode2str(enum seg6_mode_t mode) +{ + switch (mode) { + case INLINE: + return "INLINE"; + case ENCAP: + return "ENCAP"; + case L2ENCAP: + return "L2ENCAP"; + default: + return "unknown"; + } +} + +static inline bool sid_same( + const struct in6_addr *a, + const struct in6_addr *b) +{ + if (!a && !b) + return true; + else if (!(a && b)) + return false; + else + return memcmp(a, b, sizeof(struct in6_addr)) == 0; +} + +static inline bool sid_diff( + const struct in6_addr *a, + const struct in6_addr *b) +{ + return !sid_same(a, b); +} + + +static inline bool sid_zero(const struct seg6_seg_stack *a) +{ + struct in6_addr zero = {}; + + assert(a); + + return sid_same(&a->seg[0], &zero); +} + +static inline bool sid_zero_ipv6(const struct in6_addr *a) +{ + struct in6_addr zero = {}; + + return sid_same(&a[0], &zero); +} + +static inline void *sid_copy(struct in6_addr *dst, + const struct in6_addr *src) +{ + return memcpy(dst, src, sizeof(struct in6_addr)); +} + +const char * +seg6local_action2str(uint32_t action); + +const char *seg6local_context2str(char *str, size_t size, + const struct seg6local_context *ctx, + uint32_t action); + +int snprintf_seg6_segs(char *str, + size_t size, const struct seg6_segs *segs); + +extern struct srv6_locator *srv6_locator_alloc(const char *name); +extern struct srv6_locator_chunk *srv6_locator_chunk_alloc(void); +extern void srv6_locator_free(struct srv6_locator *locator); +extern void srv6_locator_chunk_list_free(void *data); +extern void srv6_locator_chunk_free(struct srv6_locator_chunk **chunk); +json_object *srv6_locator_chunk_json(const struct srv6_locator_chunk *chunk); +json_object *srv6_locator_json(const struct srv6_locator *loc); +json_object *srv6_locator_detailed_json(const struct srv6_locator *loc); +json_object * +srv6_locator_chunk_detailed_json(const struct srv6_locator_chunk *chunk); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/stream.c b/lib/stream.c new file mode 100644 index 0000000..bb90f3b --- /dev/null +++ b/lib/stream.c @@ -0,0 +1,1368 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Packet interface + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include +#include +#include + +#include "stream.h" +#include "memory.h" +#include "network.h" +#include "prefix.h" +#include "log.h" +#include "frr_pthread.h" +#include "lib_errors.h" + +DEFINE_MTYPE_STATIC(LIB, STREAM, "Stream"); +DEFINE_MTYPE_STATIC(LIB, STREAM_FIFO, "Stream FIFO"); + +/* Tests whether a position is valid */ +#define GETP_VALID(S, G) ((G) <= (S)->endp) +#define PUT_AT_VALID(S,G) GETP_VALID(S,G) +#define ENDP_VALID(S, E) ((E) <= (S)->size) + +/* asserting sanity checks. Following must be true before + * stream functions are called: + * + * Following must always be true of stream elements + * before and after calls to stream functions: + * + * getp <= endp <= size + * + * Note that after a stream function is called following may be true: + * if (getp == endp) then stream is no longer readable + * if (endp == size) then stream is no longer writeable + * + * It is valid to put to anywhere within the size of the stream, but only + * using stream_put..._at() functions. + */ +#define STREAM_WARN_OFFSETS(S) \ + do { \ + flog_warn(EC_LIB_STREAM, \ + "&(struct stream): %p, size: %lu, getp: %lu, endp: %lu", \ + (void *)(S), (unsigned long)(S)->size, \ + (unsigned long)(S)->getp, (unsigned long)(S)->endp); \ + zlog_backtrace(LOG_WARNING); \ + } while (0) + +#define STREAM_VERIFY_SANE(S) \ + do { \ + if (!(GETP_VALID(S, (S)->getp) && ENDP_VALID(S, (S)->endp))) { \ + STREAM_WARN_OFFSETS(S); \ + } \ + assert(GETP_VALID(S, (S)->getp)); \ + assert(ENDP_VALID(S, (S)->endp)); \ + } while (0) + +#define STREAM_BOUND_WARN(S, WHAT) \ + do { \ + flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \ + __func__, (WHAT)); \ + STREAM_WARN_OFFSETS(S); \ + assert(0); \ + } while (0) + +#define STREAM_BOUND_WARN2(S, WHAT) \ + do { \ + flog_warn(EC_LIB_STREAM, "%s: Attempt to %s out of bounds", \ + __func__, (WHAT)); \ + STREAM_WARN_OFFSETS(S); \ + } while (0) + +/* XXX: Deprecated macro: do not use */ +#define CHECK_SIZE(S, Z) \ + do { \ + if (((S)->endp + (Z)) > (S)->size) { \ + flog_warn( \ + EC_LIB_STREAM, \ + "CHECK_SIZE: truncating requested size %lu", \ + (unsigned long)(Z)); \ + STREAM_WARN_OFFSETS(S); \ + (Z) = (S)->size - (S)->endp; \ + } \ + } while (0); + +/* Make stream buffer. */ +struct stream *stream_new(size_t size) +{ + struct stream *s; + + assert(size > 0); + + s = XMALLOC(MTYPE_STREAM, sizeof(struct stream) + size); + + s->getp = s->endp = 0; + s->next = NULL; + s->size = size; + return s; +} + +/* Free it now. */ +void stream_free(struct stream *s) +{ + if (!s) + return; + + XFREE(MTYPE_STREAM, s); +} + +struct stream *stream_copy(struct stream *dest, const struct stream *src) +{ + STREAM_VERIFY_SANE(src); + + assert(dest != NULL); + assert(STREAM_SIZE(dest) >= src->endp); + + dest->endp = src->endp; + dest->getp = src->getp; + + memcpy(dest->data, src->data, src->endp); + + return dest; +} + +struct stream *stream_dup(const struct stream *s) +{ + struct stream *snew; + + STREAM_VERIFY_SANE(s); + + snew = stream_new(s->endp); + + return (stream_copy(snew, s)); +} + +struct stream *stream_dupcat(const struct stream *s1, const struct stream *s2, + size_t offset) +{ + struct stream *new; + + STREAM_VERIFY_SANE(s1); + STREAM_VERIFY_SANE(s2); + + if ((new = stream_new(s1->endp + s2->endp)) == NULL) + return NULL; + + memcpy(new->data, s1->data, offset); + memcpy(new->data + offset, s2->data, s2->endp); + memcpy(new->data + offset + s2->endp, s1->data + offset, + (s1->endp - offset)); + new->endp = s1->endp + s2->endp; + return new; +} + +size_t stream_resize_inplace(struct stream **sptr, size_t newsize) +{ + struct stream *orig = *sptr; + + STREAM_VERIFY_SANE(orig); + + orig = XREALLOC(MTYPE_STREAM, orig, sizeof(struct stream) + newsize); + + orig->size = newsize; + + if (orig->endp > orig->size) + orig->endp = orig->size; + if (orig->getp > orig->endp) + orig->getp = orig->endp; + + STREAM_VERIFY_SANE(orig); + + *sptr = orig; + return orig->size; +} + +size_t stream_get_getp(const struct stream *s) +{ + STREAM_VERIFY_SANE(s); + return s->getp; +} + +size_t stream_get_endp(const struct stream *s) +{ + STREAM_VERIFY_SANE(s); + return s->endp; +} + +size_t stream_get_size(const struct stream *s) +{ + STREAM_VERIFY_SANE(s); + return s->size; +} + +/* Stream structre' stream pointer related functions. */ +void stream_set_getp(struct stream *s, size_t pos) +{ + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, pos)) { + STREAM_BOUND_WARN(s, "set getp"); + pos = s->endp; + } + + s->getp = pos; +} + +void stream_set_endp(struct stream *s, size_t pos) +{ + STREAM_VERIFY_SANE(s); + + if (!ENDP_VALID(s, pos)) { + STREAM_BOUND_WARN(s, "set endp"); + return; + } + + /* + * Make sure the current read pointer is not beyond the new endp. + */ + if (s->getp > pos) { + STREAM_BOUND_WARN(s, "set endp"); + return; + } + + s->endp = pos; + STREAM_VERIFY_SANE(s); +} + +/* Forward pointer. */ +void stream_forward_getp(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, s->getp + size)) { + STREAM_BOUND_WARN(s, "seek getp"); + return; + } + + s->getp += size; +} + +bool stream_forward_getp2(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, s->getp + size)) + return false; + + s->getp += size; + + return true; +} + +void stream_rewind_getp(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (size > s->getp || !GETP_VALID(s, s->getp - size)) { + STREAM_BOUND_WARN(s, "rewind getp"); + return; + } + + s->getp -= size; +} + +bool stream_rewind_getp2(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (size > s->getp || !GETP_VALID(s, s->getp - size)) + return false; + + s->getp -= size; + + return true; +} + +void stream_forward_endp(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (!ENDP_VALID(s, s->endp + size)) { + STREAM_BOUND_WARN(s, "seek endp"); + return; + } + + s->endp += size; +} + +bool stream_forward_endp2(struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (!ENDP_VALID(s, s->endp + size)) + return false; + + s->endp += size; + + return true; +} + +/* Copy from stream to destination. */ +bool stream_get2(void *dst, struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < size) { + STREAM_BOUND_WARN2(s, "get"); + return false; + } + + memcpy(dst, s->data + s->getp, size); + s->getp += size; + + return true; +} + +void stream_get(void *dst, struct stream *s, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < size) { + STREAM_BOUND_WARN(s, "get"); + return; + } + + memcpy(dst, s->data + s->getp, size); + s->getp += size; +} + +/* Get next character from the stream. */ +bool stream_getc2(struct stream *s, uint8_t *byte) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint8_t)) { + STREAM_BOUND_WARN2(s, "get char"); + return false; + } + *byte = s->data[s->getp++]; + + return true; +} + +uint8_t stream_getc(struct stream *s) +{ + uint8_t c; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint8_t)) { + STREAM_BOUND_WARN(s, "get char"); + return 0; + } + c = s->data[s->getp++]; + + return c; +} + +/* Get next character from the stream. */ +uint8_t stream_getc_from(struct stream *s, size_t from) +{ + uint8_t c; + + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + sizeof(uint8_t))) { + STREAM_BOUND_WARN(s, "get char"); + return 0; + } + + c = s->data[from]; + + return c; +} + +bool stream_getw2(struct stream *s, uint16_t *word) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint16_t)) { + STREAM_BOUND_WARN2(s, "get "); + return false; + } + + *word = s->data[s->getp++] << 8; + *word |= s->data[s->getp++]; + + return true; +} + +/* Get next word from the stream. */ +uint16_t stream_getw(struct stream *s) +{ + uint16_t w; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint16_t)) { + STREAM_BOUND_WARN(s, "get "); + return 0; + } + + w = s->data[s->getp++] << 8; + w |= s->data[s->getp++]; + + return w; +} + +/* Get next word from the stream. */ +uint16_t stream_getw_from(struct stream *s, size_t from) +{ + uint16_t w; + + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + sizeof(uint16_t))) { + STREAM_BOUND_WARN(s, "get "); + return 0; + } + + w = s->data[from++] << 8; + w |= s->data[from]; + + return w; +} + +/* Get next 3-byte from the stream. */ +uint32_t stream_get3_from(struct stream *s, size_t from) +{ + uint32_t l; + + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + 3)) { + STREAM_BOUND_WARN(s, "get 3byte"); + return 0; + } + + l = s->data[from++] << 16; + l |= s->data[from++] << 8; + l |= s->data[from]; + + return l; +} + +uint32_t stream_get3(struct stream *s) +{ + uint32_t l; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < 3) { + STREAM_BOUND_WARN(s, "get 3byte"); + return 0; + } + + l = s->data[s->getp++] << 16; + l |= s->data[s->getp++] << 8; + l |= s->data[s->getp++]; + + return l; +} + +/* Get next long word from the stream. */ +uint32_t stream_getl_from(struct stream *s, size_t from) +{ + uint32_t l; + + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + sizeof(uint32_t))) { + STREAM_BOUND_WARN(s, "get long"); + return 0; + } + + l = (unsigned)(s->data[from++]) << 24; + l |= s->data[from++] << 16; + l |= s->data[from++] << 8; + l |= s->data[from]; + + return l; +} + +/* Copy from stream at specific location to destination. */ +void stream_get_from(void *dst, struct stream *s, size_t from, size_t size) +{ + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + size)) { + STREAM_BOUND_WARN(s, "get from"); + return; + } + + memcpy(dst, s->data + from, size); +} + +bool stream_getl2(struct stream *s, uint32_t *l) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN2(s, "get long"); + return false; + } + + *l = (unsigned int)(s->data[s->getp++]) << 24; + *l |= s->data[s->getp++] << 16; + *l |= s->data[s->getp++] << 8; + *l |= s->data[s->getp++]; + + return true; +} + +uint32_t stream_getl(struct stream *s) +{ + uint32_t l; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN(s, "get long"); + return 0; + } + + l = (unsigned)(s->data[s->getp++]) << 24; + l |= s->data[s->getp++] << 16; + l |= s->data[s->getp++] << 8; + l |= s->data[s->getp++]; + + return l; +} + +/* Get next quad word from the stream. */ +uint64_t stream_getq_from(struct stream *s, size_t from) +{ + uint64_t q; + + STREAM_VERIFY_SANE(s); + + if (!GETP_VALID(s, from + sizeof(uint64_t))) { + STREAM_BOUND_WARN(s, "get quad"); + return 0; + } + + q = ((uint64_t)s->data[from++]) << 56; + q |= ((uint64_t)s->data[from++]) << 48; + q |= ((uint64_t)s->data[from++]) << 40; + q |= ((uint64_t)s->data[from++]) << 32; + q |= ((uint64_t)s->data[from++]) << 24; + q |= ((uint64_t)s->data[from++]) << 16; + q |= ((uint64_t)s->data[from++]) << 8; + q |= ((uint64_t)s->data[from++]); + + return q; +} + +uint64_t stream_getq(struct stream *s) +{ + uint64_t q; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint64_t)) { + STREAM_BOUND_WARN(s, "get quad"); + return 0; + } + + q = ((uint64_t)s->data[s->getp++]) << 56; + q |= ((uint64_t)s->data[s->getp++]) << 48; + q |= ((uint64_t)s->data[s->getp++]) << 40; + q |= ((uint64_t)s->data[s->getp++]) << 32; + q |= ((uint64_t)s->data[s->getp++]) << 24; + q |= ((uint64_t)s->data[s->getp++]) << 16; + q |= ((uint64_t)s->data[s->getp++]) << 8; + q |= ((uint64_t)s->data[s->getp++]); + + return q; +} + +bool stream_getq2(struct stream *s, uint64_t *q) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint64_t)) { + STREAM_BOUND_WARN2(s, "get uint64"); + return false; + } + + *q = ((uint64_t)s->data[s->getp++]) << 56; + *q |= ((uint64_t)s->data[s->getp++]) << 48; + *q |= ((uint64_t)s->data[s->getp++]) << 40; + *q |= ((uint64_t)s->data[s->getp++]) << 32; + *q |= ((uint64_t)s->data[s->getp++]) << 24; + *q |= ((uint64_t)s->data[s->getp++]) << 16; + *q |= ((uint64_t)s->data[s->getp++]) << 8; + *q |= ((uint64_t)s->data[s->getp++]); + + return true; +} + +/* Get next long word from the stream. */ +uint32_t stream_get_ipv4(struct stream *s) +{ + uint32_t l; + + STREAM_VERIFY_SANE(s); + + if (STREAM_READABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN(s, "get ipv4"); + return 0; + } + + memcpy(&l, s->data + s->getp, sizeof(uint32_t)); + s->getp += sizeof(uint32_t); + + return l; +} + +bool stream_get_ipaddr(struct stream *s, struct ipaddr *ip) +{ + uint16_t ipa_len = 0; + + STREAM_VERIFY_SANE(s); + + /* Get address type. */ + if (STREAM_READABLE(s) < sizeof(uint16_t)) { + STREAM_BOUND_WARN2(s, "get ipaddr"); + return false; + } + ip->ipa_type = stream_getw(s); + + /* Get address value. */ + switch (ip->ipa_type) { + case IPADDR_V4: + ipa_len = IPV4_MAX_BYTELEN; + break; + case IPADDR_V6: + ipa_len = IPV6_MAX_BYTELEN; + break; + case IPADDR_NONE: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown ip address-family: %u", __func__, + ip->ipa_type); + return false; + } + if (STREAM_READABLE(s) < ipa_len) { + STREAM_BOUND_WARN2(s, "get ipaddr"); + return false; + } + memcpy(&ip->ip, s->data + s->getp, ipa_len); + s->getp += ipa_len; + + return true; +} + +float stream_getf(struct stream *s) +{ + union { + float r; + uint32_t d; + } u; + u.d = stream_getl(s); + return u.r; +} + +double stream_getd(struct stream *s) +{ + union { + double r; + uint64_t d; + } u; + u.d = stream_getq(s); + return u.r; +} + +/* Copy from source to stream. + * + * XXX: This uses CHECK_SIZE and hence has funny semantics -> Size will wrap + * around. This should be fixed once the stream updates are working. + * + * stream_write() is saner + */ +void stream_put(struct stream *s, const void *src, size_t size) +{ + + /* XXX: CHECK_SIZE has strange semantics. It should be deprecated */ + CHECK_SIZE(s, size); + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + return; + } + + if (src) + memcpy(s->data + s->endp, src, size); + else + memset(s->data + s->endp, 0, size); + + s->endp += size; +} + +/* Put character to the stream. */ +int stream_putc(struct stream *s, uint8_t c) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint8_t)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[s->endp++] = c; + return sizeof(uint8_t); +} + +/* Put word to the stream. */ +int stream_putw(struct stream *s, uint16_t w) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint16_t)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[s->endp++] = (uint8_t)(w >> 8); + s->data[s->endp++] = (uint8_t)w; + + return 2; +} + +/* Put long word to the stream. */ +int stream_put3(struct stream *s, uint32_t l) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < 3) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[s->endp++] = (uint8_t)(l >> 16); + s->data[s->endp++] = (uint8_t)(l >> 8); + s->data[s->endp++] = (uint8_t)l; + + return 3; +} + +/* Put long word to the stream. */ +int stream_putl(struct stream *s, uint32_t l) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[s->endp++] = (uint8_t)(l >> 24); + s->data[s->endp++] = (uint8_t)(l >> 16); + s->data[s->endp++] = (uint8_t)(l >> 8); + s->data[s->endp++] = (uint8_t)l; + + return 4; +} + +/* Put quad word to the stream. */ +int stream_putq(struct stream *s, uint64_t q) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint64_t)) { + STREAM_BOUND_WARN(s, "put quad"); + return 0; + } + + s->data[s->endp++] = (uint8_t)(q >> 56); + s->data[s->endp++] = (uint8_t)(q >> 48); + s->data[s->endp++] = (uint8_t)(q >> 40); + s->data[s->endp++] = (uint8_t)(q >> 32); + s->data[s->endp++] = (uint8_t)(q >> 24); + s->data[s->endp++] = (uint8_t)(q >> 16); + s->data[s->endp++] = (uint8_t)(q >> 8); + s->data[s->endp++] = (uint8_t)q; + + return 8; +} + +int stream_putf(struct stream *s, float f) +{ + union { + float i; + uint32_t o; + } u; + u.i = f; + return stream_putl(s, u.o); +} + +int stream_putd(struct stream *s, double d) +{ + union { + double i; + uint64_t o; + } u; + u.i = d; + return stream_putq(s, u.o); +} + +int stream_putc_at(struct stream *s, size_t putp, uint8_t c) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + sizeof(uint8_t))) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[putp] = c; + + return 1; +} + +int stream_putw_at(struct stream *s, size_t putp, uint16_t w) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + sizeof(uint16_t))) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + s->data[putp] = (uint8_t)(w >> 8); + s->data[putp + 1] = (uint8_t)w; + + return 2; +} + +int stream_put3_at(struct stream *s, size_t putp, uint32_t l) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + 3)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + s->data[putp] = (uint8_t)(l >> 16); + s->data[putp + 1] = (uint8_t)(l >> 8); + s->data[putp + 2] = (uint8_t)l; + + return 3; +} + +int stream_putl_at(struct stream *s, size_t putp, uint32_t l) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + sizeof(uint32_t))) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + s->data[putp] = (uint8_t)(l >> 24); + s->data[putp + 1] = (uint8_t)(l >> 16); + s->data[putp + 2] = (uint8_t)(l >> 8); + s->data[putp + 3] = (uint8_t)l; + + return 4; +} + +int stream_putq_at(struct stream *s, size_t putp, uint64_t q) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + sizeof(uint64_t))) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + s->data[putp] = (uint8_t)(q >> 56); + s->data[putp + 1] = (uint8_t)(q >> 48); + s->data[putp + 2] = (uint8_t)(q >> 40); + s->data[putp + 3] = (uint8_t)(q >> 32); + s->data[putp + 4] = (uint8_t)(q >> 24); + s->data[putp + 5] = (uint8_t)(q >> 16); + s->data[putp + 6] = (uint8_t)(q >> 8); + s->data[putp + 7] = (uint8_t)q; + + return 8; +} + +/* Put long word to the stream. */ +int stream_put_ipv4(struct stream *s, uint32_t l) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + memcpy(s->data + s->endp, &l, sizeof(uint32_t)); + s->endp += sizeof(uint32_t); + + return sizeof(uint32_t); +} + +/* Put long word to the stream. */ +int stream_put_in_addr(struct stream *s, const struct in_addr *addr) +{ + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < sizeof(uint32_t)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + memcpy(s->data + s->endp, addr, sizeof(uint32_t)); + s->endp += sizeof(uint32_t); + + return sizeof(uint32_t); +} + +bool stream_put_ipaddr(struct stream *s, const struct ipaddr *ip) +{ + stream_putw(s, ip->ipa_type); + + switch (ip->ipa_type) { + case IPADDR_V4: + stream_put_in_addr(s, &ip->ipaddr_v4); + break; + case IPADDR_V6: + stream_write(s, (uint8_t *)&ip->ipaddr_v6, 16); + break; + case IPADDR_NONE: + flog_err(EC_LIB_DEVELOPMENT, + "%s: unknown ip address-family: %u", __func__, + ip->ipa_type); + return false; + } + + return true; +} + +/* Put in_addr at location in the stream. */ +int stream_put_in_addr_at(struct stream *s, size_t putp, + const struct in_addr *addr) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + 4)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + memcpy(&s->data[putp], addr, 4); + return 4; +} + +/* Put in6_addr at location in the stream. */ +int stream_put_in6_addr_at(struct stream *s, size_t putp, + const struct in6_addr *addr) +{ + STREAM_VERIFY_SANE(s); + + if (!PUT_AT_VALID(s, putp + 16)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + memcpy(&s->data[putp], addr, 16); + return 16; +} + +/* Put prefix by nlri type format. */ +int stream_put_prefix_addpath(struct stream *s, const struct prefix *p, + bool addpath_capable, uint32_t addpath_tx_id) +{ + size_t psize; + size_t psize_with_addpath; + + STREAM_VERIFY_SANE(s); + + psize = PSIZE(p->prefixlen); + + if (addpath_capable) + psize_with_addpath = psize + 4; + else + psize_with_addpath = psize; + + if (STREAM_WRITEABLE(s) < (psize_with_addpath + sizeof(uint8_t))) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + if (addpath_capable) { + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24); + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16); + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8); + s->data[s->endp++] = (uint8_t)addpath_tx_id; + } + + s->data[s->endp++] = p->prefixlen; + memcpy(s->data + s->endp, &p->u.prefix, psize); + s->endp += psize; + + return psize; +} + +int stream_put_prefix(struct stream *s, const struct prefix *p) +{ + return stream_put_prefix_addpath(s, p, 0, 0); +} + +/* Put NLRI with label */ +int stream_put_labeled_prefix(struct stream *s, const struct prefix *p, + mpls_label_t *label, bool addpath_capable, + uint32_t addpath_tx_id) +{ + size_t psize; + size_t psize_with_addpath; + uint8_t *label_pnt = (uint8_t *)label; + + STREAM_VERIFY_SANE(s); + + psize = PSIZE(p->prefixlen); + psize_with_addpath = psize + (addpath_capable ? 4 : 0); + + if (STREAM_WRITEABLE(s) < (psize_with_addpath + 3)) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + if (addpath_capable) { + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 24); + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 16); + s->data[s->endp++] = (uint8_t)(addpath_tx_id >> 8); + s->data[s->endp++] = (uint8_t)addpath_tx_id; + } + + stream_putc(s, (p->prefixlen + 24)); + stream_putc(s, label_pnt[0]); + stream_putc(s, label_pnt[1]); + stream_putc(s, label_pnt[2]); + memcpy(s->data + s->endp, &p->u.prefix, psize); + s->endp += psize; + + return (psize + 3); +} + +/* Read size from fd. */ +int stream_read(struct stream *s, int fd, size_t size) +{ + int nbytes; + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + nbytes = readn(fd, s->data + s->endp, size); + + if (nbytes > 0) + s->endp += nbytes; + + return nbytes; +} + +ssize_t stream_read_try(struct stream *s, int fd, size_t size) +{ + ssize_t nbytes; + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + /* Fatal (not transient) error, since retrying will not help + (stream is too small to contain the desired data). */ + return -1; + } + + nbytes = read(fd, s->data + s->endp, size); + if (nbytes >= 0) { + s->endp += nbytes; + return nbytes; + } + /* Error: was it transient (return -2) or fatal (return -1)? */ + if (ERRNO_IO_RETRY(errno)) + return -2; + flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd, + safe_strerror(errno)); + return -1; +} + +/* Read up to size bytes into the stream from the fd, using recvmsgfrom + * whose arguments match the remaining arguments to this function + */ +ssize_t stream_recvfrom(struct stream *s, int fd, size_t size, int flags, + struct sockaddr *from, socklen_t *fromlen) +{ + ssize_t nbytes; + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + /* Fatal (not transient) error, since retrying will not help + (stream is too small to contain the desired data). */ + return -1; + } + + nbytes = recvfrom(fd, s->data + s->endp, size, flags, from, fromlen); + if (nbytes >= 0) { + s->endp += nbytes; + return nbytes; + } + /* Error: was it transient (return -2) or fatal (return -1)? */ + if (ERRNO_IO_RETRY(errno)) + return -2; + flog_err(EC_LIB_SOCKET, "%s: read failed on fd %d: %s", __func__, fd, + safe_strerror(errno)); + return -1; +} + +/* Read up to smaller of size or SIZE_REMAIN() bytes to the stream, starting + * from endp. + * First iovec will be used to receive the data. + * Stream need not be empty. + */ +ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *msgh, int flags, + size_t size) +{ + int nbytes; + struct iovec *iov; + + STREAM_VERIFY_SANE(s); + assert(msgh->msg_iovlen > 0); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + /* This is a logic error in the calling code: the stream is too + small + to hold the desired data! */ + return -1; + } + + iov = &(msgh->msg_iov[0]); + iov->iov_base = (s->data + s->endp); + iov->iov_len = size; + + nbytes = recvmsg(fd, msgh, flags); + + if (nbytes > 0) + s->endp += nbytes; + + return nbytes; +} + +/* Write data to buffer. */ +size_t stream_write(struct stream *s, const void *ptr, size_t size) +{ + + CHECK_SIZE(s, size); + + STREAM_VERIFY_SANE(s); + + if (STREAM_WRITEABLE(s) < size) { + STREAM_BOUND_WARN(s, "put"); + return 0; + } + + memcpy(s->data + s->endp, ptr, size); + s->endp += size; + + return size; +} + +/* Return current read pointer. + * DEPRECATED! + * Use stream_get_pnt_to if you must, but decoding streams properly + * is preferred + */ +uint8_t *stream_pnt(struct stream *s) +{ + STREAM_VERIFY_SANE(s); + return s->data + s->getp; +} + +/* Check does this stream empty? */ +int stream_empty(struct stream *s) +{ + STREAM_VERIFY_SANE(s); + + return (s->endp == 0); +} + +/* Reset stream. */ +void stream_reset(struct stream *s) +{ + STREAM_VERIFY_SANE(s); + + s->getp = s->endp = 0; +} + +/* Write stream contens to the file discriptor. */ +int stream_flush(struct stream *s, int fd) +{ + int nbytes; + + STREAM_VERIFY_SANE(s); + + nbytes = write(fd, s->data + s->getp, s->endp - s->getp); + + return nbytes; +} + +void stream_hexdump(const struct stream *s) +{ + zlog_hexdump(s->data, s->endp); +} + +/* Stream first in first out queue. */ + +struct stream_fifo *stream_fifo_new(void) +{ + struct stream_fifo *new; + + new = XMALLOC(MTYPE_STREAM_FIFO, sizeof(struct stream_fifo)); + stream_fifo_init(new); + return new; +} + +void stream_fifo_init(struct stream_fifo *fifo) +{ + memset(fifo, 0, sizeof(struct stream_fifo)); + pthread_mutex_init(&fifo->mtx, NULL); +} + +/* Add new stream to fifo. */ +void stream_fifo_push(struct stream_fifo *fifo, struct stream *s) +{ + size_t max, curmax; + + if (fifo->tail) + fifo->tail->next = s; + else + fifo->head = s; + + fifo->tail = s; + fifo->tail->next = NULL; + max = atomic_fetch_add_explicit(&fifo->count, 1, memory_order_release); + curmax = atomic_load_explicit(&fifo->max_count, memory_order_relaxed); + if (max > curmax) + atomic_store_explicit(&fifo->max_count, max, + memory_order_relaxed); +} + +void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s) +{ + frr_with_mutex (&fifo->mtx) { + stream_fifo_push(fifo, s); + } +} + +/* Delete first stream from fifo. */ +struct stream *stream_fifo_pop(struct stream_fifo *fifo) +{ + struct stream *s; + + s = fifo->head; + + if (s) { + fifo->head = s->next; + + if (fifo->head == NULL) + fifo->tail = NULL; + + atomic_fetch_sub_explicit(&fifo->count, 1, + memory_order_release); + + /* ensure stream is scrubbed of references to this fifo */ + s->next = NULL; + } + + return s; +} + +struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo) +{ + struct stream *ret; + + frr_with_mutex (&fifo->mtx) { + ret = stream_fifo_pop(fifo); + } + + return ret; +} + +struct stream *stream_fifo_head(struct stream_fifo *fifo) +{ + return fifo->head; +} + +struct stream *stream_fifo_head_safe(struct stream_fifo *fifo) +{ + struct stream *ret; + + frr_with_mutex (&fifo->mtx) { + ret = stream_fifo_head(fifo); + } + + return ret; +} + +void stream_fifo_clean(struct stream_fifo *fifo) +{ + struct stream *s; + struct stream *next; + + for (s = fifo->head; s; s = next) { + next = s->next; + stream_free(s); + } + fifo->head = fifo->tail = NULL; + atomic_store_explicit(&fifo->count, 0, memory_order_release); +} + +void stream_fifo_clean_safe(struct stream_fifo *fifo) +{ + frr_with_mutex (&fifo->mtx) { + stream_fifo_clean(fifo); + } +} + +size_t stream_fifo_count_safe(struct stream_fifo *fifo) +{ + return atomic_load_explicit(&fifo->count, memory_order_acquire); +} + +void stream_fifo_deinit(struct stream_fifo *fifo) +{ + stream_fifo_clean(fifo); + pthread_mutex_destroy(&fifo->mtx); +} + +void stream_fifo_free(struct stream_fifo *fifo) +{ + stream_fifo_deinit(fifo); + XFREE(MTYPE_STREAM_FIFO, fifo); +} + +void stream_pulldown(struct stream *s) +{ + size_t rlen = STREAM_READABLE(s); + + /* No more data, so just move the pointers. */ + if (rlen == 0) { + stream_reset(s); + return; + } + + /* Move the available data to the beginning. */ + memmove(s->data, &s->data[s->getp], rlen); + s->getp = 0; + s->endp = rlen; +} diff --git a/lib/stream.h b/lib/stream.h new file mode 100644 index 0000000..e48cedc --- /dev/null +++ b/lib/stream.h @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Packet interface + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_STREAM_H +#define _ZEBRA_STREAM_H + +#include + +#include "frratomic.h" +#include "mpls.h" +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * A stream is an arbitrary buffer, whose contents generally are assumed to + * be in network order. + * + * A stream has the following attributes associated with it: + * + * - size: the allocated, invariant size of the buffer. + * + * - getp: the get position marker, denoting the offset in the stream where + * the next read (or 'get') will be from. This getp marker is + * automatically adjusted when data is read from the stream, the + * user may also manipulate this offset as they wish, within limits + * (see below) + * + * - endp: the end position marker, denoting the offset in the stream where + * valid data ends, and if the user attempted to write (or + * 'put') data where that data would be written (or 'put') to. + * + * These attributes are all size_t values. + * + * Constraints: + * + * 1. getp can never exceed endp + * + * - hence if getp is equal to endp, there is no more valid data that can be + * gotten from the stream (though, the user may reposition getp to earlier in + * the stream, if they wish). + * + * 2. endp can never exceed size + * + * - hence, if endp is equal to size, then the stream is full, and no more + * data can be written to the stream. + * + * In other words the following must always be true, and the stream + * abstraction is allowed internally to assert that the following property + * holds true for a stream, as and when it wishes: + * + * getp <= endp <= size + * + * It is the users responsibility to ensure this property is never violated. + * + * A stream therefore can be thought of like this: + * + * --------------------------------------------------- + * |XXXXXXXXXXXXXXXXXXXXXXXX | + * --------------------------------------------------- + * ^ ^ ^ + * getp endp size + * + * This shows a stream containing data (shown as 'X') up to the endp offset. + * The stream is empty from endp to size. Without adjusting getp, there are + * still endp-getp bytes of valid data to be read from the stream. + * + * Methods are provided to get and put to/from the stream, as well as + * retrieve the values of the 3 markers and manipulate the getp marker. + * + * Note: + * At the moment, newly allocated streams are zero filled. Hence, one can + * use stream_forward_endp() to effectively create arbitrary zero-fill + * padding. However, note that stream_reset() does *not* zero-out the + * stream. This property should **not** be relied upon. + * + * Best practice is to use stream_put (, NULL, ) to zero out + * any part of a stream which isn't otherwise written to. + */ + +/* Stream buffer. */ +struct stream { + struct stream *next; + + /* + * Remainder is ***private*** to stream + * direct access is frowned upon! + * Use the appropriate functions/macros + */ + size_t getp; /* next get position */ + size_t endp; /* last valid data position */ + size_t size; /* size of data segment */ + unsigned char data[]; /* data pointer */ +}; + +/* First in first out queue structure. */ +struct stream_fifo { + /* lock for mt-safe operations */ + pthread_mutex_t mtx; + + /* number of streams in this fifo */ + atomic_size_t count; + atomic_size_t max_count; + + struct stream *head; + struct stream *tail; +}; + +/* Utility macros. */ +#define STREAM_SIZE(S) ((S)->size) +/* number of bytes which can still be written */ +#define STREAM_WRITEABLE(S) ((S)->size - (S)->endp) +/* number of bytes still to be read */ +#define STREAM_READABLE(S) ((S)->endp - (S)->getp) + +#define STREAM_CONCAT_REMAIN(S1, S2, size) ((size) - (S1)->endp - (S2)->endp) + +/* this macro is deprecated, but not slated for removal anytime soon */ +#define STREAM_DATA(S) ((S)->data) + +/* Stream prototypes. + * For stream_{put,get}S, the S suffix mean: + * + * c: character (unsigned byte) + * w: word (two bytes) + * l: long (two words) + * q: quad (four words) + */ +extern struct stream *stream_new(size_t); +extern void stream_free(struct stream *); +/* Copy 'src' into 'dest', returns 'dest' */ +extern struct stream *stream_copy(struct stream *dest, + const struct stream *src); +extern struct stream *stream_dup(const struct stream *s); + +extern size_t stream_resize_inplace(struct stream **sptr, size_t newsize); + +extern size_t stream_get_getp(const struct stream *s); +extern size_t stream_get_endp(const struct stream *s); +extern size_t stream_get_size(const struct stream *s); + +/** + * Create a new stream structure; copy offset bytes from s1 to the new + * stream; copy s2 data to the new stream; copy rest of s1 data to the + * new stream. + */ +extern struct stream *stream_dupcat(const struct stream *s1, + const struct stream *s2, size_t offset); + +extern void stream_set_getp(struct stream *, size_t); +extern void stream_set_endp(struct stream *, size_t); +extern void stream_forward_getp(struct stream *, size_t); +extern bool stream_forward_getp2(struct stream *, size_t); +extern void stream_rewind_getp(struct stream *s, size_t size); +extern bool stream_rewind_getp2(struct stream *s, size_t size); +extern void stream_forward_endp(struct stream *, size_t); +extern bool stream_forward_endp2(struct stream *, size_t); + +/* steam_put: NULL source zeroes out size_t bytes of stream */ +extern void stream_put(struct stream *, const void *, size_t); +extern int stream_putc(struct stream *, uint8_t); +extern int stream_putc_at(struct stream *, size_t, uint8_t); +extern int stream_putw(struct stream *, uint16_t); +extern int stream_putw_at(struct stream *, size_t, uint16_t); +extern int stream_put3(struct stream *, uint32_t); +extern int stream_put3_at(struct stream *, size_t, uint32_t); +extern int stream_putl(struct stream *, uint32_t); +extern int stream_putl_at(struct stream *, size_t, uint32_t); +extern int stream_putq(struct stream *, uint64_t); +extern int stream_putq_at(struct stream *, size_t, uint64_t); +extern int stream_put_ipv4(struct stream *, uint32_t); +extern int stream_put_in_addr(struct stream *s, const struct in_addr *addr); +extern bool stream_put_ipaddr(struct stream *s, const struct ipaddr *ip); +extern int stream_put_in_addr_at(struct stream *s, size_t putp, + const struct in_addr *addr); +extern int stream_put_in6_addr_at(struct stream *s, size_t putp, + const struct in6_addr *addr); +extern int stream_put_prefix_addpath(struct stream *s, const struct prefix *p, + bool addpath_capable, + uint32_t addpath_tx_id); +extern int stream_put_prefix(struct stream *s, const struct prefix *p); +extern int stream_put_labeled_prefix(struct stream *, const struct prefix *, + mpls_label_t *, bool addpath_capable, + uint32_t addpath_tx_id); +extern void stream_get(void *, struct stream *, size_t); +extern bool stream_get2(void *data, struct stream *s, size_t size); +extern void stream_get_from(void *, struct stream *, size_t, size_t); +extern uint8_t stream_getc(struct stream *); +extern bool stream_getc2(struct stream *s, uint8_t *byte); +extern uint8_t stream_getc_from(struct stream *, size_t); +extern uint16_t stream_getw(struct stream *); +extern bool stream_getw2(struct stream *s, uint16_t *word); +extern uint16_t stream_getw_from(struct stream *, size_t); +extern uint32_t stream_get3(struct stream *); +extern uint32_t stream_get3_from(struct stream *, size_t); +extern uint32_t stream_getl(struct stream *); +extern bool stream_getl2(struct stream *s, uint32_t *l); +extern uint32_t stream_getl_from(struct stream *, size_t); +extern uint64_t stream_getq(struct stream *); +extern uint64_t stream_getq_from(struct stream *, size_t); +bool stream_getq2(struct stream *s, uint64_t *q); +extern uint32_t stream_get_ipv4(struct stream *); +extern bool stream_get_ipaddr(struct stream *s, struct ipaddr *ip); + +/* IEEE-754 floats */ +extern float stream_getf(struct stream *); +extern double stream_getd(struct stream *); +extern int stream_putf(struct stream *, float); +extern int stream_putd(struct stream *, double); + +#undef stream_read +#undef stream_write + +/* Deprecated: assumes blocking I/O. Will be removed. + Use stream_read_try instead. */ +extern int stream_read(struct stream *, int, size_t); + +/* Read up to size bytes into the stream. + Return code: + >0: number of bytes read + 0: end-of-file + -1: fatal error + -2: transient error, should retry later (i.e. EAGAIN or EINTR) + This is suitable for use with non-blocking file descriptors. + */ +extern ssize_t stream_read_try(struct stream *s, int fd, size_t size); + +extern ssize_t stream_recvmsg(struct stream *s, int fd, struct msghdr *, + int flags, size_t size); +extern ssize_t stream_recvfrom(struct stream *s, int fd, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen); +extern size_t stream_write(struct stream *, const void *, size_t); + +/* reset the stream. See Note above */ +extern void stream_reset(struct stream *); +extern int stream_flush(struct stream *, int); +extern int stream_empty(struct stream *); /* is the stream empty? */ + +/* debugging */ +extern void stream_hexdump(const struct stream *s); + +/** + * Reorganize the buffer data so it can fit more. This function is normally + * called right after stream data is consumed so we can read more data + * (the functions that consume data start with `stream_get*()` and macros + * `STREAM_GET*()`). + * + * \param s stream pointer. + */ +extern void stream_pulldown(struct stream *s); + +/* deprecated */ +extern uint8_t *stream_pnt(struct stream *); + +/* + * Operations on struct stream_fifo. + * + * Each function has a safe variant, which ensures that the operation performed + * is atomic with respect to the operations performed by all other safe + * variants. In other words, the safe variants lock the stream_fifo's mutex + * before performing their action. These are provided for convenience when + * using stream_fifo in a multithreaded context, to alleviate the need for the + * caller to implement their own synchronization around the stream_fifo. + * + * The following functions do not have safe variants. The caller must ensure + * that these operations are performed safely in a multithreaded context: + * - stream_fifo_new + * - stream_fifo_free + */ + +/* + * Create a new stream_fifo. + * + * Returns: + * newly created stream_fifo + */ +extern struct stream_fifo *stream_fifo_new(void); + +/* + * Init or re-init an on-stack fifo. This allows use of a fifo struct without + * requiring a malloc/free cycle. + * Note well that the fifo must be de-inited with the 'fifo_deinit' api. + */ +void stream_fifo_init(struct stream_fifo *fifo); + +/* + * Deinit an on-stack fifo. + */ +void stream_fifo_deinit(struct stream_fifo *fifo); + +/* + * Push a stream onto a stream_fifo. + * + * fifo + * the stream_fifo to push onto + * + * s + * the stream to push onto the stream_fifo + */ +extern void stream_fifo_push(struct stream_fifo *fifo, struct stream *s); +extern void stream_fifo_push_safe(struct stream_fifo *fifo, struct stream *s); + +/* + * Pop a stream off a stream_fifo. + * + * fifo + * the stream_fifo to pop from + * + * Returns: + * the next stream in the stream_fifo + */ +extern struct stream *stream_fifo_pop(struct stream_fifo *fifo); +extern struct stream *stream_fifo_pop_safe(struct stream_fifo *fifo); + +/* + * Retrieve the next stream from a stream_fifo without popping it. + * + * fifo + * the stream_fifo to operate on + * + * Returns: + * the next stream that would be returned from stream_fifo_pop + */ +extern struct stream *stream_fifo_head(struct stream_fifo *fifo); +extern struct stream *stream_fifo_head_safe(struct stream_fifo *fifo); + +/* + * Remove all streams from a stream_fifo. + * + * fifo + * the stream_fifo to clean + */ +extern void stream_fifo_clean(struct stream_fifo *fifo); +extern void stream_fifo_clean_safe(struct stream_fifo *fifo); + +/* + * Retrieve number of streams on a stream_fifo. + * + * fifo + * the stream_fifo to retrieve the count for + * + * Returns: + * the number of streams on the stream_fifo + */ +extern size_t stream_fifo_count_safe(struct stream_fifo *fifo); + +/* + * Free a stream_fifo. + * + * Calls stream_fifo_clean, then deinitializes the stream_fifo and frees it. + * + * fifo + * the stream_fifo to free + */ +extern void stream_fifo_free(struct stream_fifo *fifo); + +/* This is here because "<< 24" is particularly problematic in C. + * This is because the left operand of << is integer-promoted, which means + * an uint8_t gets converted into a *signed* int. Shifting into the sign + * bit of a signed int is theoretically undefined behaviour, so - the left + * operand needs to be cast to unsigned. + * + * This is not a problem for 16- or 8-bit values (they don't reach the sign + * bit), for 64-bit values (you need to cast them anyway), and neither for + * encoding (because it's downcasted.) + */ +static inline const uint8_t *ptr_get_be64(const uint8_t *ptr, uint64_t *out) +{ + uint32_t tmp1, tmp2; + + memcpy(&tmp1, ptr, sizeof(tmp1)); + memcpy(&tmp2, ptr + sizeof(tmp1), sizeof(tmp1)); + + *out = (((uint64_t)ntohl(tmp1)) << 32) | ntohl(tmp2); + + return ptr + 8; +} + +static inline const uint8_t *ptr_get_be32(const uint8_t *ptr, uint32_t *out) +{ + uint32_t tmp; + + memcpy(&tmp, ptr, sizeof(tmp)); + *out = ntohl(tmp); + return ptr + 4; +} + +static inline uint8_t *ptr_get_be16(uint8_t *ptr, uint16_t *out) +{ + uint16_t tmp; + + memcpy(&tmp, ptr, sizeof(tmp)); + *out = ntohs(tmp); + + return ptr + 2; +} + +/* + * so Normal stream_getX functions assert. Which is anathema + * to keeping a daemon up and running when something goes south + * Provide a stream_getX2 functions that do not assert. + * In addition provide these macro's that upon failure + * goto stream_failure. This is modeled upon some NL_XX + * macros in the linux kernel. + * + * This change allows for proper memory freeing + * after we've detected an error. + * + * In the future we will be removing the assert in + * the stream functions but we need a transition + * plan. + */ +#define STREAM_GETC(S, P) \ + do { \ + uint8_t _pval; \ + if (!stream_getc2((S), &_pval)) \ + goto stream_failure; \ + (P) = _pval; \ + } while (0) + +#define STREAM_GETW(S, P) \ + do { \ + uint16_t _pval; \ + if (!stream_getw2((S), &_pval)) \ + goto stream_failure; \ + (P) = _pval; \ + } while (0) + +#define STREAM_GETL(S, P) \ + do { \ + uint32_t _pval; \ + if (!stream_getl2((S), &_pval)) \ + goto stream_failure; \ + (P) = _pval; \ + } while (0) + +#define STREAM_GETF(S, P) \ + do { \ + union { \ + float r; \ + uint32_t d; \ + } _pval; \ + if (!stream_getl2((S), &_pval.d)) \ + goto stream_failure; \ + (P) = _pval.r; \ + } while (0) + +#define STREAM_GETQ(S, P) \ + do { \ + uint64_t _pval; \ + if (!stream_getq2((S), &_pval)) \ + goto stream_failure; \ + (P) = _pval; \ + } while (0) + +#define STREAM_GET_IPADDR(S, P) \ + do { \ + if (!stream_get_ipaddr((S), (P))) \ + goto stream_failure; \ + } while (0) + +#define STREAM_GET(P, STR, SIZE) \ + do { \ + if (!stream_get2((P), (STR), (SIZE))) \ + goto stream_failure; \ + } while (0) + +#define STREAM_FORWARD_GETP(STR, SIZE) \ + do { \ + if (!stream_forward_getp2((STR), (SIZE))) \ + goto stream_failure; \ + } while (0) + +#define STREAM_REWIND_GETP(STR, SIZE) \ + do { \ + if (!stream_rewind_getp2((STR), (SIZE))) \ + goto stream_failure; \ + } while (0) + +#define STREAM_FORWARD_ENDP(STR, SIZE) \ + do { \ + if (!stream_forward_endp2((STR), (SIZE))) \ + goto stream_failure; \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_STREAM_H */ diff --git a/lib/strformat.c b/lib/strformat.c new file mode 100644 index 0000000..87d1715 --- /dev/null +++ b/lib/strformat.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "compiler.h" + +#include +#include +#include + +#include "printfrr.h" +#include "monotime.h" + +printfrr_ext_autoreg_p("HX", printfrr_hexdump); +static ssize_t printfrr_hexdump(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + ssize_t ret = 0; + ssize_t input_len = printfrr_ext_len(ea); + char sep = ' '; + const uint8_t *pos, *end; + + if (ea->fmt[0] == 'c') { + ea->fmt++; + sep = ':'; + } else if (ea->fmt[0] == 'n') { + ea->fmt++; + sep = '\0'; + } + + if (input_len < 0) + return 0; + + for (pos = ptr, end = pos + input_len; pos < end; pos++) { + if (sep && pos != ptr) + ret += bputch(buf, sep); + ret += bputhex(buf, *pos); + } + + return ret; +} + +/* string analog for hexdumps / the "this." in ("74 68 69 73 0a |this.|") */ + +printfrr_ext_autoreg_p("HS", printfrr_hexdstr); +static ssize_t printfrr_hexdstr(struct fbuf *buf, struct printfrr_eargs *ea, + const void *ptr) +{ + ssize_t ret = 0; + ssize_t input_len = printfrr_ext_len(ea); + const uint8_t *pos, *end; + + if (input_len < 0) + return 0; + + for (pos = ptr, end = pos + input_len; pos < end; pos++) { + if (*pos >= 0x20 && *pos < 0x7f) + ret += bputch(buf, *pos); + else + ret += bputch(buf, '.'); + } + + return ret; +} + +enum escape_flags { + ESC_N_R_T = (1 << 0), /* use \n \r \t instead of \x0a ...*/ + ESC_SPACE = (1 << 1), /* \ */ + ESC_BACKSLASH = (1 << 2), /* \\ */ + ESC_DBLQUOTE = (1 << 3), /* \" */ + ESC_SGLQUOTE = (1 << 4), /* \' */ + ESC_BACKTICK = (1 << 5), /* \` */ + ESC_DOLLAR = (1 << 6), /* \$ */ + ESC_CLBRACKET = (1 << 7), /* \] for RFC5424 syslog */ + ESC_OTHER = (1 << 8), /* remaining non-alpha */ + + ESC_ALL = ESC_N_R_T | ESC_SPACE | ESC_BACKSLASH | ESC_DBLQUOTE + | ESC_SGLQUOTE | ESC_DOLLAR | ESC_OTHER, + ESC_QUOTSTRING = ESC_N_R_T | ESC_BACKSLASH | ESC_DBLQUOTE, + /* if needed: ESC_SHELL = ... */ +}; + +static ssize_t bquote(struct fbuf *buf, const uint8_t *pos, size_t len, + unsigned int flags) +{ + ssize_t ret = 0; + const uint8_t *end = pos + len; + + for (; pos < end; pos++) { + /* here's to hoping this might be a bit faster... */ + if (__builtin_expect(!!isalnum(*pos), 1)) { + ret += bputch(buf, *pos); + continue; + } + + switch (*pos) { + case '%': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case '@': + case '_': + ret += bputch(buf, *pos); + continue; + + case '\r': + if (!(flags & ESC_N_R_T)) + break; + ret += bputch(buf, '\\'); + ret += bputch(buf, 'r'); + continue; + case '\n': + if (!(flags & ESC_N_R_T)) + break; + ret += bputch(buf, '\\'); + ret += bputch(buf, 'n'); + continue; + case '\t': + if (!(flags & ESC_N_R_T)) + break; + ret += bputch(buf, '\\'); + ret += bputch(buf, 't'); + continue; + + case ' ': + if (flags & ESC_SPACE) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case '\\': + if (flags & ESC_BACKSLASH) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case '"': + if (flags & ESC_DBLQUOTE) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case '\'': + if (flags & ESC_SGLQUOTE) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case '`': + if (flags & ESC_BACKTICK) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case '$': + if (flags & ESC_DOLLAR) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + case ']': + if (flags & ESC_CLBRACKET) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + + /* remaining: !#&'()*;<=>?[^{|}~ */ + + default: + if (*pos >= 0x20 && *pos < 0x7f) { + if (flags & ESC_OTHER) + ret += bputch(buf, '\\'); + ret += bputch(buf, *pos); + continue; + } + } + ret += bputch(buf, '\\'); + ret += bputch(buf, 'x'); + ret += bputhex(buf, *pos); + } + + return ret; +} + +printfrr_ext_autoreg_p("SE", printfrr_escape); +static ssize_t printfrr_escape(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + ssize_t len = printfrr_ext_len(ea); + const uint8_t *ptr = vptr; + bool null_is_empty = false; + + if (ea->fmt[0] == 'n') { + null_is_empty = true; + ea->fmt++; + } + + if (!ptr) { + if (null_is_empty) + return 0; + return bputs(buf, "(null)"); + } + + if (len < 0) + len = strlen((const char *)ptr); + + return bquote(buf, ptr, len, ESC_ALL); +} + +printfrr_ext_autoreg_p("SQ", printfrr_quote); +static ssize_t printfrr_quote(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + ssize_t len = printfrr_ext_len(ea); + const uint8_t *ptr = vptr; + ssize_t ret = 0; + bool null_is_empty = false; + bool do_quotes = false; + unsigned int flags = ESC_QUOTSTRING; + + while (ea->fmt[0]) { + switch (ea->fmt[0]) { + case 'n': + null_is_empty = true; + ea->fmt++; + continue; + case 'q': + do_quotes = true; + ea->fmt++; + continue; + case 's': + flags |= ESC_CLBRACKET; + flags &= ~ESC_N_R_T; + ea->fmt++; + continue; + } + break; + } + + if (!ptr) { + if (null_is_empty) + return bputs(buf, do_quotes ? "\"\"" : ""); + return bputs(buf, "(null)"); + } + + if (len < 0) + len = strlen((const char *)ptr); + + if (do_quotes) + ret += bputch(buf, '"'); + ret += bquote(buf, ptr, len, flags); + if (do_quotes) + ret += bputch(buf, '"'); + return ret; +} + +static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags); +static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags); + +ssize_t printfrr_time(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags) +{ + bool have_abs, have_anchor; + + if (!(flags & TIMEFMT_PRESELECT)) { + switch (ea->fmt[0]) { + case 'I': + /* no bit set */ + break; + case 'M': + flags |= TIMEFMT_MONOTONIC; + break; + case 'R': + flags |= TIMEFMT_REALTIME; + break; + default: + return bputs(buf, + "{invalid time format input specifier}"); + } + ea->fmt++; + + if (ea->fmt[0] == 's') { + flags |= TIMEFMT_SINCE; + ea->fmt++; + } else if (ea->fmt[0] == 'u') { + flags |= TIMEFMT_UNTIL; + ea->fmt++; + } + } + + have_abs = !!(flags & TIMEFMT_ABSOLUTE); + have_anchor = !!(flags & TIMEFMT_ANCHORS); + + if (have_abs ^ have_anchor) + return printfrr_abstime(buf, ea, ts, flags); + else + return printfrr_reltime(buf, ea, ts, flags); +} + +static ssize_t do_subsec(struct fbuf *buf, const struct timespec *ts, + int precision, unsigned int flags) +{ + unsigned long long frac; + + if (precision <= 0 || (flags & TIMEFMT_SECONDS)) + return 0; + + frac = ts->tv_nsec; + if (precision > 9) + precision = 9; + for (int i = precision; i < 9; i++) + frac /= 10; + return bprintfrr(buf, ".%0*llu", precision, frac); +} + +static ssize_t printfrr_abstime(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags) +{ + struct timespec real_ts[1]; + struct tm tm; + char cbuf[32] = ""; /* manpage says 26 for ctime_r */ + ssize_t ret = 0; + int precision = ea->precision; + + while (ea->fmt[0]) { + char ch = *ea->fmt++; + + switch (ch) { + case 'p': + flags |= TIMEFMT_SPACE; + continue; + case 'i': + flags |= TIMEFMT_ISO8601; + continue; + } + + ea->fmt--; + break; + } + + if (flags & TIMEFMT_SKIP) + return 0; + if (!ts) + return bputch(buf, '-'); + + if (flags & TIMEFMT_REALTIME) + *real_ts = *ts; + else if (flags & TIMEFMT_MONOTONIC) { + struct timespec mono_now[1]; + + clock_gettime(CLOCK_REALTIME, real_ts); + clock_gettime(CLOCK_MONOTONIC, mono_now); + + timespecsub(real_ts, mono_now, real_ts); + timespecadd(real_ts, ts, real_ts); + } else { + clock_gettime(CLOCK_REALTIME, real_ts); + + if (flags & TIMEFMT_SINCE) + timespecsub(real_ts, ts, real_ts); + else /* flags & TIMEFMT_UNTIL */ + timespecadd(real_ts, ts, real_ts); + } + + localtime_r(&real_ts->tv_sec, &tm); + + if (flags & TIMEFMT_ISO8601) { + if (flags & TIMEFMT_SPACE) + strftime(cbuf, sizeof(cbuf), "%Y-%m-%d %H:%M:%S", &tm); + else + strftime(cbuf, sizeof(cbuf), "%Y-%m-%dT%H:%M:%S", &tm); + ret += bputs(buf, cbuf); + + if (precision == -1) + precision = 3; + ret += do_subsec(buf, real_ts, precision, flags); + } else { + size_t len; + + asctime_r(&tm, cbuf); + + len = strlen(cbuf); + if (!len) + /* WTF. */ + return 0; + if (cbuf[len - 1] == '\n') + cbuf[len - 1] = '\0'; + + ret += bputs(buf, cbuf); + } + return ret; +} + +static ssize_t printfrr_reltime(struct fbuf *buf, struct printfrr_eargs *ea, + const struct timespec *ts, unsigned int flags) +{ + struct timespec real_ts[1]; + ssize_t ret = 0; + const char *space = ""; + const char *dashes = "-"; + int precision = ea->precision; + + while (ea->fmt[0]) { + char ch = *ea->fmt++; + + switch (ch) { + case 'p': + flags |= TIMEFMT_SPACE; + space = " "; + continue; + case 't': + flags |= TIMEFMT_BASIC; + continue; + case 'd': + flags |= TIMEFMT_DECIMAL; + continue; + case 'm': + flags |= TIMEFMT_MMSS; + dashes = "--:--"; + continue; + case 'h': + flags |= TIMEFMT_HHMMSS; + dashes = "--:--:--"; + continue; + case 'x': + flags |= TIMEFMT_DASHES; + continue; + } + + ea->fmt--; + break; + } + + if (flags & TIMEFMT_SKIP) + return 0; + if (!ts) + return bputch(buf, '-'); + + if (flags & TIMEFMT_ABSOLUTE) { + struct timespec anchor[1]; + + if (flags & TIMEFMT_REALTIME) + clock_gettime(CLOCK_REALTIME, anchor); + else + clock_gettime(CLOCK_MONOTONIC, anchor); + if (flags & TIMEFMT_UNTIL) + timespecsub(ts, anchor, real_ts); + else /* flags & TIMEFMT_SINCE */ + timespecsub(anchor, ts, real_ts); + } else + *real_ts = *ts; + + if (real_ts->tv_sec == 0 && real_ts->tv_nsec == 0 && + (flags & TIMEFMT_DASHES)) + return bputs(buf, dashes); + + if (real_ts->tv_sec < 0) { + if (flags & TIMEFMT_DASHES) + return bputs(buf, dashes); + + /* -0.3s is { -1s + 700ms } */ + real_ts->tv_sec = -real_ts->tv_sec - 1; + real_ts->tv_nsec = 1000000000L - real_ts->tv_nsec; + if (real_ts->tv_nsec >= 1000000000L) { + real_ts->tv_sec++; + real_ts->tv_nsec -= 1000000000L; + } + + /* all formats have a - make sense in front */ + ret += bputch(buf, '-'); + } + + if (flags & TIMEFMT_DECIMAL) { + ret += bprintfrr(buf, "%lld", (long long)real_ts->tv_sec); + if (precision == -1) + precision = 3; + ret += do_subsec(buf, real_ts, precision, flags); + return ret; + } + + /* these divisions may be slow on embedded boxes, hence only do the + * ones we need, plus the ?: zero check to hopefully skip zeros fast + */ + lldiv_t min_sec = lldiv(real_ts->tv_sec, 60); + + if (flags & TIMEFMT_MMSS) { + ret += bprintfrr(buf, "%02lld:%02lld", min_sec.quot, + min_sec.rem); + ret += do_subsec(buf, real_ts, precision, flags); + return ret; + } + + lldiv_t hour_min = min_sec.quot ? lldiv(min_sec.quot, 60) : (lldiv_t){}; + + if (flags & TIMEFMT_HHMMSS) { + ret += bprintfrr(buf, "%02lld:%02lld:%02lld", hour_min.quot, + hour_min.rem, min_sec.rem); + ret += do_subsec(buf, real_ts, precision, flags); + return ret; + } + + lldiv_t day_hour = + hour_min.quot ? lldiv(hour_min.quot, 24) : (lldiv_t){}; + lldiv_t week_day = + day_hour.quot ? lldiv(day_hour.quot, 7) : (lldiv_t){}; + + /* if sub-second precision is not supported, return */ + if (flags & TIMEFMT_BASIC) { + /* match frrtime_to_interval (without space flag) */ + if (week_day.quot) + ret += bprintfrr(buf, "%lldw%s%lldd%s%02lldh", + week_day.quot, space, week_day.rem, + space, day_hour.rem); + else if (day_hour.quot) + ret += bprintfrr(buf, "%lldd%s%02lldh%s%02lldm", + day_hour.quot, space, day_hour.rem, + space, hour_min.rem); + else + ret += bprintfrr(buf, "%02lld:%02lld:%02lld", + hour_min.quot, hour_min.rem, + min_sec.rem); + /* no sub-seconds here */ + return ret; + } + + /* default format */ + if (week_day.quot) + ret += bprintfrr(buf, "%lldw%s", week_day.quot, space); + if (week_day.rem || week_day.quot) + ret += bprintfrr(buf, "%lldd%s", week_day.rem, space); + + ret += bprintfrr(buf, "%02lld:%02lld:%02lld", day_hour.rem, + hour_min.rem, min_sec.rem); + + if (precision == -1) + precision = 3; + ret += do_subsec(buf, real_ts, precision, flags); + return ret; +} + +printfrr_ext_autoreg_p("TS", printfrr_ts); +static ssize_t printfrr_ts(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const struct timespec *ts = vptr; + + return printfrr_time(buf, ea, ts, 0); +} + +printfrr_ext_autoreg_p("TV", printfrr_tv); +static ssize_t printfrr_tv(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const struct timeval *tv = vptr; + struct timespec ts; + + if (!tv) + return printfrr_time(buf, ea, NULL, 0); + + ts.tv_sec = tv->tv_sec; + ts.tv_nsec = tv->tv_usec * 1000; + return printfrr_time(buf, ea, &ts, 0); +} + +printfrr_ext_autoreg_p("TT", printfrr_tt); +static ssize_t printfrr_tt(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const time_t *tt = vptr; + struct timespec ts; + + if (!tt) + return printfrr_time(buf, ea, NULL, TIMEFMT_SECONDS); + + ts.tv_sec = *tt; + ts.tv_nsec = 0; + return printfrr_time(buf, ea, &ts, TIMEFMT_SECONDS); +} diff --git a/lib/strlcat.c b/lib/strlcat.c new file mode 100644 index 0000000..e3e29a7 --- /dev/null +++ b/lib/strlcat.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* Append a null-terminated string to another string, with length checking. + * Copyright (C) 2016 Free Software Foundation, Inc. + * This file is part of the GNU C Library. + */ + +/* adapted for Quagga from glibc patch submission originally from + * Florian Weimer , 2016-05-18 */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifndef HAVE_STRLCAT +#undef strlcat + +size_t strlcat(char *__restrict dest, + const char *__restrict src, size_t destsize); + +size_t strlcat(char *__restrict dest, + const char *__restrict src, size_t destsize) +{ + size_t src_length = strlen(src); + + /* Our implementation strlcat supports dest == NULL if size == 0 + (for consistency with snprintf and strlcpy), but strnlen does + not, so we have to cover this case explicitly. */ + if (destsize == 0) + return src_length; + + size_t dest_length = strnlen(dest, destsize); + if (dest_length != destsize) { + /* Copy at most the remaining number of characters in the + destination buffer. Leave for the NUL terminator. */ + size_t to_copy = destsize - dest_length - 1; + /* But not more than what is available in the source string. */ + if (to_copy > src_length) + to_copy = src_length; + + char *target = dest + dest_length; + memcpy(target, src, to_copy); + target[to_copy] = '\0'; + } + +/* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in + the two input strings (including both null terminators). If each + byte in the address space can be assigned a unique size_t value + (which the static_assert checks), then by the pigeonhole + principle, the two input strings must overlap, which is + undefined. */ + _Static_assert(sizeof(uintptr_t) == sizeof(size_t), + "theoretical maximum object size covers address space"); + return dest_length + src_length; +} +#endif /* HAVE_STRLCAT */ diff --git a/lib/strlcpy.c b/lib/strlcpy.c new file mode 100644 index 0000000..74ea9cb --- /dev/null +++ b/lib/strlcpy.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* Copy a null-terminated string to a fixed-size buffer, with length checking. + * Copyright (C) 2016 Free Software Foundation, Inc. + * This file is part of the GNU C Library. + */ + +/* adapted for Quagga from glibc patch submission originally from + * Florian Weimer , 2016-05-18 */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifndef HAVE_STRLCPY +#undef strlcpy + +size_t strlcpy(char *__restrict dest, + const char *__restrict src, size_t destsize); + +size_t strlcpy(char *__restrict dest, + const char *__restrict src, size_t destsize) +{ + size_t src_length = strlen(src); + + if (__builtin_expect(src_length >= destsize, 0)) { + if (destsize > 0) { + /* + * Copy the leading portion of the string. The last + * character is subsequently overwritten with the NUL + * terminator, but the destination destsize is usually + * a multiple of a small power of two, so writing it + * twice should be more efficient than copying an odd + * number of bytes. + */ + memcpy(dest, src, destsize); + dest[destsize - 1] = '\0'; + } + } else + /* Copy the string and its terminating NUL character. */ + memcpy(dest, src, src_length + 1); + return src_length; +} +#endif /* HAVE_STRLCPY */ diff --git a/lib/subdir.am b/lib/subdir.am new file mode 100644 index 0000000..3264f31 --- /dev/null +++ b/lib/subdir.am @@ -0,0 +1,654 @@ +# +# libfrr +# + +lib_LTLIBRARIES += lib/libfrr.la +lib_libfrr_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 -Xlinker -e_libfrr_version +lib_libfrr_la_LIBADD = $(LIBCAP) $(UNWIND_LIBS) $(LIBYANG_LIBS) $(LUA_LIB) $(UST_LIBS) $(LIBCRYPT) $(LIBDL) $(LIBM) + +lib_libfrr_la_SOURCES = \ + lib/admin_group.c \ + lib/affinitymap.c \ + lib/affinitymap_cli.c \ + lib/affinitymap_northbound.c \ + lib/agg_table.c \ + lib/atomlist.c \ + lib/asn.c \ + lib/base64.c \ + lib/bfd.c \ + lib/buffer.c \ + lib/checksum.c \ + lib/command.c \ + lib/command_graph.c \ + lib/command_lex.l \ + lib/command_match.c \ + lib/command_parse.y \ + lib/cspf.c \ + lib/csv.c \ + lib/darr.c \ + lib/debug.c \ + lib/defaults.c \ + lib/distribute.c \ + lib/explicit_bzero.c \ + lib/ferr.c \ + lib/filter.c \ + lib/filter_cli.c \ + lib/filter_nb.c \ + lib/flex_algo.c \ + lib/frrcu.c \ + lib/frrlua.c \ + lib/frrscript.c \ + lib/frr_pthread.c \ + lib/frrstr.c \ + lib/grammar_sandbox.c \ + lib/graph.c \ + lib/hash.c \ + lib/hook.c \ + lib/id_alloc.c \ + lib/if.c \ + lib/if_rmap.c \ + lib/imsg-buffer.c \ + lib/imsg.c \ + lib/iso.c \ + lib/jhash.c \ + lib/json.c \ + lib/keychain.c \ + lib/keychain_cli.c \ + lib/keychain_nb.c \ + lib/ldp_sync.c \ + lib/lib_errors.c \ + lib/lib_vty.c \ + lib/libagentx.c \ + lib/libfrr.c \ + lib/libfrr_trace.c \ + lib/linklist.c \ + lib/link_state.c \ + lib/log.c \ + lib/log_filter.c \ + lib/log_vty.c \ + lib/md5.c \ + lib/memory.c \ + lib/mgmt_be_client.c \ + lib/mgmt_fe_client.c \ + lib/mgmt_msg.c \ + lib/mgmt_msg_native.c \ + lib/mlag.c \ + lib/module.c \ + lib/mpls.c \ + lib/srv6.c \ + lib/network.c \ + lib/nexthop.c \ + lib/netns_linux.c \ + lib/netns_other.c \ + lib/nexthop_group.c \ + lib/northbound.c \ + lib/northbound_cli.c \ + lib/northbound_db.c \ + lib/northbound_oper.c \ + lib/ntop.c \ + lib/openbsd-tree.c \ + lib/pid_output.c \ + lib/plist.c \ + lib/prefix.c \ + lib/privs.c \ + lib/ptm_lib.c \ + lib/pullwr.c \ + lib/qobj.c \ + lib/ringbuf.c \ + lib/routemap.c \ + lib/routemap_cli.c \ + lib/routemap_northbound.c \ + lib/sbuf.c \ + lib/seqlock.c \ + lib/sha256.c \ + lib/sigevent.c \ + lib/skiplist.c \ + lib/sockopt.c \ + lib/sockunion.c \ + lib/spf_backoff.c \ + lib/segment_routing.c \ + lib/srcdest_table.c \ + lib/stream.c \ + lib/strformat.c \ + lib/strlcat.c \ + lib/strlcpy.c \ + lib/systemd.c \ + lib/table.c \ + lib/termtable.c \ + lib/event.c \ + lib/typerb.c \ + lib/typesafe.c \ + lib/vector.c \ + lib/vrf.c \ + lib/vty.c \ + lib/wheel.c \ + lib/workqueue.c \ + lib/xref.c \ + lib/yang.c \ + lib/yang_translator.c \ + lib/yang_wrappers.c \ + lib/zclient.c \ + lib/zlog.c \ + lib/zlog_5424.c \ + lib/zlog_5424_cli.c \ + lib/zlog_live.c \ + lib/zlog_recirculate.c \ + lib/zlog_targets.c \ + lib/printf/printf-pos.c \ + lib/printf/vfprintf.c \ + lib/printf/glue.c \ + lib/routing_nb.c \ + lib/routing_nb_config.c \ + lib/tc.c \ + # end + +nodist_lib_libfrr_la_SOURCES = \ + yang/frr-affinity-map.yang.c \ + yang/frr-filter.yang.c \ + yang/frr-if-rmap.yang.c \ + yang/frr-interface.yang.c \ + yang/frr-route-map.yang.c \ + yang/frr-route-types.yang.c \ + yang/frr-vrf.yang.c \ + yang/frr-routing.yang.c \ + yang/frr-nexthop.yang.c \ + yang/ietf/frr-deviations-ietf-key-chain.yang.c \ + yang/ietf/ietf-routing-types.yang.c \ + yang/ietf/ietf-netconf-acm.yang.c \ + yang/ietf/ietf-key-chain.yang.c \ + yang/ietf/ietf-interfaces.yang.c \ + yang/ietf/ietf-bgp-types.yang.c \ + yang/frr-module-translator.yang.c \ + # end + +# Add logic to build mgmt.proto +lib_libfrr_la_LIBADD += $(PROTOBUF_C_LIBS) + +BUILT_SOURCES += \ + lib/mgmt.pb-c.c \ + lib/mgmt.pb-c.h \ + # end + +CLEANFILES += \ + lib/mgmt.pb-c.h \ + lib/mgmt.pb-c.c \ + # end + +lib_libfrr_la_SOURCES += \ + lib/mgmt.pb-c.c \ + #end + +if SQLITE3 +lib_libfrr_la_LIBADD += $(SQLITE3_LIBS) +lib_libfrr_la_SOURCES += lib/db.c +endif + +clippy_scan += \ + lib/affinitymap_cli.c \ + lib/if.c \ + lib/filter_cli.c \ + lib/if_rmap.c \ + lib/keychain_cli.c \ + lib/log_vty.c \ + lib/mgmt_be_client.c \ + lib/mgmt_fe_client.c \ + lib/nexthop_group.c \ + lib/northbound_cli.c \ + lib/plist.c \ + lib/routemap.c \ + lib/routemap_cli.c \ + lib/event.c \ + lib/vty.c \ + lib/zlog_5424_cli.c \ + # end + +pkginclude_HEADERS += \ + lib/admin_group.h \ + lib/affinitymap.h \ + lib/agg_table.h \ + lib/asn.h \ + lib/atomlist.h \ + lib/base64.h \ + lib/bfd.h \ + lib/bitfield.h \ + lib/buffer.h \ + lib/checksum.h \ + lib/mlag.h \ + lib/command.h \ + lib/command_graph.h \ + lib/command_match.h \ + lib/compiler.h \ + lib/cspf.h \ + lib/csv.h \ + lib/darr.h \ + lib/db.h \ + lib/debug.h \ + lib/defaults.h \ + lib/distribute.h \ + lib/ferr.h \ + lib/filter.h \ + lib/flex_algo.h \ + lib/freebsd-queue.h \ + lib/frrdistance.h \ + lib/frrlua.h \ + lib/frrscript.h \ + lib/frr_pthread.h \ + lib/frratomic.h \ + lib/frrcu.h \ + lib/frrsendmmsg.h \ + lib/frrstr.h \ + lib/graph.h \ + lib/hash.h \ + lib/hook.h \ + lib/iana_afi.h \ + lib/id_alloc.h \ + lib/if.h \ + lib/if_rmap.h \ + lib/imsg.h \ + lib/ipaddr.h \ + lib/iso.h \ + lib/jhash.h \ + lib/json.h \ + lib/keychain.h \ + lib/ldp_sync.h \ + lib/lib_errors.h \ + lib/lib_vty.h \ + lib/libagentx.h \ + lib/libfrr.h \ + lib/libfrr_trace.h \ + lib/libospf.h \ + lib/linklist.h \ + lib/link_state.h \ + lib/log.h \ + lib/log_vty.h \ + lib/md5.h \ + lib/memory.h \ + lib/mgmt.pb-c.h \ + lib/mgmt_be_client.h \ + lib/mgmt_defines.h \ + lib/mgmt_fe_client.h \ + lib/mgmt_msg.h \ + lib/mgmt_msg_native.h \ + lib/mgmt_pb.h \ + lib/module.h \ + lib/monotime.h \ + lib/mpls.h \ + lib/srv6.h \ + lib/network.h \ + lib/nexthop.h \ + lib/nexthop_group.h \ + lib/nexthop_group_private.h \ + lib/northbound.h \ + lib/northbound_cli.h \ + lib/northbound_db.h \ + lib/ns.h \ + lib/openbsd-queue.h \ + lib/openbsd-tree.h \ + lib/plist.h \ + lib/prefix.h \ + lib/printfrr.h \ + lib/privs.h \ + lib/ptm_lib.h \ + lib/pullwr.h \ + lib/pw.h \ + lib/qobj.h \ + lib/queue.h \ + lib/ringbuf.h \ + lib/routemap.h \ + lib/route_opaque.h \ + lib/sbuf.h \ + lib/seqlock.h \ + lib/sha256.h \ + lib/sigevent.h \ + lib/skiplist.h \ + lib/smux.h \ + lib/sockopt.h \ + lib/sockunion.h \ + lib/spf_backoff.h \ + lib/segment_routing.h \ + lib/srcdest_table.h \ + lib/srte.h \ + lib/stream.h \ + lib/systemd.h \ + lib/table.h \ + lib/termtable.h \ + lib/frrevent.h \ + lib/trace.h \ + lib/typerb.h \ + lib/typesafe.h \ + lib/vector.h \ + lib/vlan.h \ + lib/vrf.h \ + lib/vrf_int.h \ + lib/vty.h \ + lib/vxlan.h \ + lib/wheel.h \ + lib/workqueue.h \ + lib/xref.h \ + lib/yang.h \ + lib/yang_translator.h \ + lib/yang_wrappers.h \ + lib/zclient.h \ + lib/zebra.h \ + lib/zlog.h \ + lib/zlog_5424.h \ + lib/zlog_live.h \ + lib/zlog_recirculate.h \ + lib/zlog_targets.h \ + lib/pbr.h \ + lib/tc.h \ + lib/routing_nb.h \ + \ + lib/assert/assert.h \ + # end + + +nodist_pkginclude_HEADERS += \ + lib/route_types.h \ + lib/version.h \ + # end + +noinst_HEADERS += \ + lib/clippy.h \ + lib/plist_int.h \ + lib/printf/printfcommon.h \ + lib/printf/printflocal.h \ + #end + +# General note about module and module helper library (libfrrsnmp, libfrrzmq) +# linking: If we're linking libfrr statically into daemons, we *must* remove +# libfrr from modules because modules will always link it in dynamically and +# thus 2 copies of libfrr will be loaded... hilarity ensues. +# +# Not linking libfrr into modules should generally work fine because the +# executable refers to libfrr either way and the dynamic linker should make +# libfrr available to modules. If some OS platform has a dynamic linker that +# doesn't do that, libfrr needs to be readded to modules, but _only_ _if_ +# it's not linked into daemons statically. + +# +# SNMP support +# +if SNMP +lib_LTLIBRARIES += lib/libfrrsnmp.la +endif + +lib_libfrrsnmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +lib_libfrrsnmp_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +lib_libfrrsnmp_la_LIBADD = $(SNMP_LIBS) +lib_libfrrsnmp_la_SOURCES = \ + lib/agentx.c \ + lib/snmp.c \ + # end + + +# +# c-ares support +# +if CARES +lib_LTLIBRARIES += lib/libfrrcares.la +pkginclude_HEADERS += lib/resolver.h +endif + +lib_libfrrcares_la_CFLAGS = $(AM_CFLAGS) $(CARES_CFLAGS) +lib_libfrrcares_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +lib_libfrrcares_la_LIBADD = $(CARES_LIBS) +lib_libfrrcares_la_SOURCES = \ + lib/resolver.c \ + #end + +# +# ZeroMQ support +# +if ZEROMQ +lib_LTLIBRARIES += lib/libfrrzmq.la +pkginclude_HEADERS += lib/frr_zmq.h +endif + +lib_libfrrzmq_la_CFLAGS = $(AM_CFLAGS) $(ZEROMQ_CFLAGS) +lib_libfrrzmq_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +lib_libfrrzmq_la_LIBADD = $(ZEROMQ_LIBS) +lib_libfrrzmq_la_SOURCES = \ + lib/frr_zmq.c \ + #end + +# +# Sysrepo support +# +if SYSREPO +module_LTLIBRARIES += lib/sysrepo.la +endif + +lib_sysrepo_la_CFLAGS = $(AM_CFLAGS) $(SYSREPO_CFLAGS) +lib_sysrepo_la_LDFLAGS = $(MODULE_LDFLAGS) +lib_sysrepo_la_LIBADD = lib/libfrr.la $(SYSREPO_LIBS) +lib_sysrepo_la_SOURCES = lib/northbound_sysrepo.c + +# +# gRPC northbound plugin +# +if GRPC +module_LTLIBRARIES += lib/grpc.la +endif + +lib_grpc_la_CXXFLAGS = $(WERROR) $(GRPC_CFLAGS) +lib_grpc_la_LDFLAGS = $(MODULE_LDFLAGS) +lib_grpc_la_LIBADD = lib/libfrr.la grpc/libfrrgrpc_pb.la $(GRPC_LIBS) +lib_grpc_la_SOURCES = lib/northbound_grpc.cpp + +# +# CLI utilities +# +noinst_PROGRAMS += \ + lib/grammar_sandbox \ + # end + +if BUILD_CLIPPY +noinst_PROGRAMS += lib/clippy +else +if HOSTTOOLS_CLIPPY +$(CLIPPY): + @$(MAKE) -C $(top_builddir)/hosttools lib/route_types.h lib/clippy +endif +endif + +lib_grammar_sandbox_SOURCES = \ + lib/grammar_sandbox_main.c +lib_grammar_sandbox_LDADD = \ + lib/libfrr.la + +lib_clippy_CPPFLAGS = $(CPPFLAGS_BASE) -D_GNU_SOURCE -DBUILDING_CLIPPY +lib_clippy_CFLAGS = $(AC_CFLAGS) $(PYTHON_CFLAGS) +lib_clippy_LDADD = $(PYTHON_LIBS) $(UST_LIBS) -lelf +# no $(SAN_FLAGS) here +lib_clippy_LDFLAGS = -export-dynamic $(AC_LDFLAGS) $(AC_LDFLAGS_EXEC) +lib_clippy_SOURCES = \ + lib/jhash.c \ + lib/clippy.c \ + lib/command_graph.c \ + lib/command_lex.l \ + lib/command_parse.y \ + lib/command_py.c \ + lib/defun_lex.l \ + lib/elf_py.c \ + lib/graph.c \ + lib/libfrr_trace.c \ + lib/memory.c \ + lib/typesafe.c \ + lib/vector.c \ + # end + +# (global) clippy rules for all directories + +AM_V_CLIPPY = $(am__v_CLIPPY_$(V)) +am__v_CLIPPY_ = $(am__v_CLIPPY_$(AM_DEFAULT_VERBOSITY)) +am__v_CLIPPY_0 = @echo " CLIPPY " $@; +am__v_CLIPPY_1 = + +CLIPPY_DEPS = $(CLIPPY) $(top_srcdir)/python/clidef.py + +SUFFIXES += _clippy.c +.c_clippy.c: + $(AM_V_CLIPPY) $(CLIPPY) $(top_srcdir)/python/clidef.py -o $@ $< + +# xrelfo, the ELF xref extractor + +AM_V_XRELFO = $(am__v_XRELFO_$(V)) +am__v_XRELFO_ = $(am__v_XRELFO_$(AM_DEFAULT_VERBOSITY)) +am__v_XRELFO_0 = @echo " XRELFO " $@; +am__v_XRELFO_1 = + +XRELFO_FLAGS = -Wlog-format -Wlog-args + +SUFFIXES += .xref +%.xref: % $(CLIPPY) + $(AM_V_XRELFO) $(CLIPPY) $(top_srcdir)/python/xrelfo.py $(WERROR) $(XRELFO_FLAGS) -o $@ $< + +# dependencies added in python/makefile.py +frr.xref: + $(AM_V_XRELFO) $(CLIPPY) $(top_srcdir)/python/xrelfo.py -o $@ -c vtysh/vtysh_cmd.c $^ +all-am: frr.xref + +clean-xref: + -rm -rf $(xrefs) frr.xref +clean-local: clean-xref + +CLEANFILES += vtysh/vtysh_cmd.c +vtysh/vtysh_cmd.c: frr.xref + @test -f $@ || rm -f frr.xref || true + @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) frr.xref + +## automake's "ylwrap" is a great piece of GNU software... not. +.l.c: + $(AM_V_LEX)$(am__skiplex) $(LEXCOMPILE) $< +.y.c: + $(AM_V_YACC)$(am__skipyacc) $(YACCCOMPILE) $< + +# +# generated sources & extra foo +# +EXTRA_DIST += \ + lib/command_lex.h \ + lib/command_parse.h \ + lib/gitversion.pl \ + lib/route_types.pl \ + lib/route_types.txt \ + # end + +BUILT_SOURCES += \ + lib/gitversion.h \ + lib/route_types.h \ + lib/vtysh_daemons.h \ + # end + +## force route_types.h +$(lib_clippy_OBJECTS): lib/route_types.h +$(lib_libfrr_la_OBJECTS): lib/route_types.h + +# force lib_daemons.h +$(lib_libfrr_la_OBJECTS): lib/vtysh_daemons.h + +CLEANFILES += lib/vtysh_daemons.h +lib/vtysh_daemons.h: + @$(MKDIR_P) lib + $(PERL) $(top_srcdir)/vtysh/daemons.pl $(vtysh_daemons) > lib/vtysh_daemons.h + + +AM_YFLAGS = -d -Dapi.prefix=@BISON_OPENBRACE@cmd_yy@BISON_CLOSEBRACE@ @BISON_VERBOSE@ + +lib/command_lex.h: lib/command_lex.c + @if test ! -f $@; then rm -f "lib/command_lex.c"; else :; fi + @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) "lib/command_lex.c"; else :; fi +lib/command_lex.lo: lib/command_parse.h +lib/command_parse.lo: lib/command_lex.h +lib/clippy-command_lex.$(OBJEXT): lib/command_parse.h +lib/clippy-command_parse.$(OBJEXT): lib/command_lex.h +lib/lib_clippy-command_lex.$(OBJEXT): lib/command_parse.h +lib/lib_clippy-command_parse.$(OBJEXT): lib/command_lex.h + +DISTCLEANFILES += lib/command_lex.h \ + lib/command_lex.c \ + lib/command_parse.h \ + lib/command_parse.c + +rt_enabled = + +if BABELD +rt_enabled += --enabled babeld +endif +if BFDD +rt_enabled += --enabled bfdd +endif +if BGPD +rt_enabled += --enabled bgpd +if ENABLE_BGP_VNC +rt_enabled += --enabled bgpd-vnc +endif +endif +if EIGRPD +rt_enabled += --enabled eigrpd +endif +if ISISD +rt_enabled += --enabled isisd +endif +if FABRICD +rt_enabled += --enabled fabricd +endif +if LDPD +rt_enabled += --enabled ldpd +endif +if NHRPD +rt_enabled += --enabled nhrpd +endif +if OSPFD +rt_enabled += --enabled ospfd +endif +if OSPF6D +rt_enabled += --enabled ospf6d +endif +if PBRD +rt_enabled += --enabled pbrd +endif +if PIMD +rt_enabled += --enabled pimd +endif +if RIPD +rt_enabled += --enabled ripd +endif +if RIPNGD +rt_enabled += --enabled ripngd +endif +if SHARPD +rt_enabled += --enabled sharpd +endif +if ZEBRA +rt_enabled += --enabled zebra +endif + +lib/route_types.h: $(top_srcdir)/lib/route_types.txt $(top_srcdir)/lib/route_types.pl + @$(MKDIR_P) lib + $(PERL) $(top_srcdir)/lib/route_types.pl $(rt_enabled) < $(top_srcdir)/lib/route_types.txt > $@ +DISTCLEANFILES += lib/route_types.h + +if GIT_VERSION +# bit of a trick here to always have up-to-date git stamps without triggering +# unnecessary rebuilds. .PHONY causes the .tmp file to be rebuilt always, +# but if we use that on gitversion.h it'll ripple through the .c file deps. +# (even if gitversion.h's file timestamp doesn't change, make will think it +# did, because of .PHONY...) + +PHONY_GITVERSION=lib/gitversion.h.tmp +.SILENT: lib/gitversion.h lib/gitversion.h.tmp +GITH=lib/gitversion.h +lib/gitversion.h.tmp: $(top_srcdir)/.git + @$(MKDIR_P) lib + $(PERL) $(top_srcdir)/lib/gitversion.pl $(top_srcdir) > ${GITH}.tmp +lib/gitversion.h: lib/gitversion.h.tmp + { test -f ${GITH} && diff -s -q ${GITH}.tmp ${GITH}; } || cp ${GITH}.tmp ${GITH} + +else +PHONY_GITVERSION=lib/gitversion.h +lib/gitversion.h: + true +endif +.PHONY: $(PHONY_GITVERSION) diff --git a/lib/systemd.c b/lib/systemd.c new file mode 100644 index 0000000..a82c376 --- /dev/null +++ b/lib/systemd.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* lib/systemd Code + * Copyright (C) 2016 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include +#include +#include + +#include "frrevent.h" +#include "systemd.h" +#include "lib_errors.h" + +/* these are cleared from env so they don't "leak" into things we fork(), + * particularly for watchfrr starting individual daemons + * + * watchdog_pid is currently not used since watchfrr starts forking. + * (TODO: handle that better, somehow?) + */ +static pid_t watchdog_pid = -1; +static intmax_t watchdog_msec; + +/* not used yet, but can trigger auto-switch to journald logging */ +bool sd_stdout_is_journal; +bool sd_stderr_is_journal; + +static char *notify_socket; + +/* talk to whatever entity claims to be systemd ;) + * + * refer to sd_notify docs for messages systemd accepts over this socket. + * This function should be functionally equivalent to sd_notify(). + */ +static void systemd_send_information(const char *info) +{ + int sock; + struct sockaddr_un sun; + + if (!notify_socket) + return; + + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) + return; + + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, notify_socket, sizeof(sun.sun_path)); + + /* linux abstract unix socket namespace */ + if (sun.sun_path[0] == '@') + sun.sun_path[0] = '\0'; + + /* nothing we can do if this errors out... */ + (void)sendto(sock, info, strlen(info), 0, (struct sockaddr *)&sun, + sizeof(sun)); + + close(sock); +} + +void systemd_send_stopping(void) +{ + systemd_send_information("STATUS="); + systemd_send_information("STOPPING=1"); +} + +static struct event_loop *systemd_master = NULL; + +static void systemd_send_watchdog(struct event *t) +{ + systemd_send_information("WATCHDOG=1"); + + assert(watchdog_msec > 0); + event_add_timer_msec(systemd_master, systemd_send_watchdog, NULL, + watchdog_msec, NULL); +} + +void systemd_send_started(struct event_loop *m) +{ + assert(m != NULL); + + systemd_master = m; + + systemd_send_information("READY=1"); + if (watchdog_msec > 0) + systemd_send_watchdog(NULL); +} + +void systemd_send_status(const char *status) +{ + char buffer[1024]; + + snprintf(buffer, sizeof(buffer), "STATUS=%s", status); + systemd_send_information(buffer); +} + +static intmax_t getenv_int(const char *varname, intmax_t dflt) +{ + char *val, *err; + intmax_t intval; + + val = getenv(varname); + if (!val) + return dflt; + + intval = strtoimax(val, &err, 0); + if (*err || !*val) + return dflt; + return intval; +} + +void systemd_init_env(void) +{ + char *tmp; + uintmax_t dev, ino; + int len; + struct stat st; + + notify_socket = getenv("NOTIFY_SOCKET"); + + /* no point in setting up watchdog w/o notify socket */ + if (notify_socket) { + intmax_t watchdog_usec; + + watchdog_pid = getenv_int("WATCHDOG_PID", -1); + if (watchdog_pid <= 0) + watchdog_pid = -1; + + /* note this is the deadline, hence the divide by 3 */ + watchdog_usec = getenv_int("WATCHDOG_USEC", 0); + if (watchdog_usec >= 3000) + watchdog_msec = watchdog_usec / 3000; + else { + if (watchdog_usec != 0) + flog_err( + EC_LIB_UNAVAILABLE, + "systemd expects a %jd microsecond watchdog timer, but FRR only supports millisecond resolution!", + watchdog_usec); + watchdog_msec = 0; + } + } + + tmp = getenv("JOURNAL_STREAM"); + if (tmp && sscanf(tmp, "%ju:%ju%n", &dev, &ino, &len) == 2 + && (size_t)len == strlen(tmp)) { + if (fstat(1, &st) == 0 && st.st_dev == (dev_t)dev + && st.st_ino == (ino_t)ino) + sd_stdout_is_journal = true; + if (fstat(2, &st) == 0 && st.st_dev == (dev_t)dev + && st.st_ino == (ino_t)ino) + sd_stderr_is_journal = true; + } + + /* these should *not* be passed to any other process we start */ + unsetenv("WATCHDOG_PID"); + unsetenv("WATCHDOG_USEC"); +} diff --git a/lib/systemd.h b/lib/systemd.h new file mode 100644 index 0000000..ba2461b --- /dev/null +++ b/lib/systemd.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* lib/systemd Code + * Copyright (C) 2016 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* fd 1/2 connected to journald? */ +extern bool sd_stdout_is_journal; +extern bool sd_stderr_is_journal; + +/* + * Wrapper functions to systemd calls. + * + * Design point is that if systemd is not being used on this system + * then these functions becomes a no-op. + */ +void systemd_send_stopping(void); + +/* + * master - The struct event_loop * to use to schedule ourself + * the_process - Should we send watchdog if we are not the requested + * process? + */ +void systemd_send_started(struct event_loop *master); + +/* + * status - A status string to send to systemd + */ +void systemd_send_status(const char *status); + +/* + * grab startup state from env vars + */ +void systemd_init_env(void); + +#ifdef __cplusplus +} +#endif diff --git a/lib/table.c b/lib/table.c new file mode 100644 index 0000000..3bf9389 --- /dev/null +++ b/lib/table.c @@ -0,0 +1,780 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routing Table functions. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#define FRR_COMPILING_TABLE_C + +#include + +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "sockunion.h" +#include "libfrr_trace.h" + +DEFINE_MTYPE_STATIC(LIB, ROUTE_TABLE, "Route table"); +DEFINE_MTYPE(LIB, ROUTE_NODE, "Route node"); + +static void route_table_free(struct route_table *); + +static int route_table_hash_cmp(const struct route_node *a, + const struct route_node *b) +{ + return prefix_cmp(&a->p, &b->p); +} + +DECLARE_HASH(rn_hash_node, struct route_node, nodehash, route_table_hash_cmp, + prefix_hash_key); +/* + * route_table_init_with_delegate + */ +struct route_table * +route_table_init_with_delegate(route_table_delegate_t *delegate) +{ + struct route_table *rt; + + rt = XCALLOC(MTYPE_ROUTE_TABLE, sizeof(struct route_table)); + rt->delegate = delegate; + rn_hash_node_init(&rt->hash); + return rt; +} + +void route_table_finish(struct route_table *rt) +{ + route_table_free(rt); +} + +/* Allocate new route node. */ +static struct route_node *route_node_new(struct route_table *table) +{ + return table->delegate->create_node(table->delegate, table); +} + +/* Allocate new route node with prefix set. */ +static struct route_node *route_node_set(struct route_table *table, + const struct prefix *prefix) +{ + struct route_node *node; + + node = route_node_new(table); + + prefix_copy(&node->p, prefix); + node->table = table; + + rn_hash_node_add(&node->table->hash, node); + + return node; +} + +/* Free route node. */ +static void route_node_free(struct route_table *table, struct route_node *node) +{ + if (table->cleanup) + table->cleanup(table, node); + table->delegate->destroy_node(table->delegate, table, node); +} + +/* Free route table. */ +static void route_table_free(struct route_table *rt) +{ + struct route_node *tmp_node; + struct route_node *node; + + if (rt == NULL) + return; + + node = rt->top; + + /* Bulk deletion of nodes remaining in this table. This function is not + called until workers have completed their dependency on this table. + A final route_unlock_node() will not be called for these nodes. */ + while (node) { + if (node->l_left) { + node = node->l_left; + continue; + } + + if (node->l_right) { + node = node->l_right; + continue; + } + + tmp_node = node; + node = node->parent; + + tmp_node->table->count--; + tmp_node->lock = + 0; /* to cause assert if unlocked after this */ + rn_hash_node_del(&rt->hash, tmp_node); + route_node_free(rt, tmp_node); + + if (node != NULL) { + if (node->l_left == tmp_node) + node->l_left = NULL; + else + node->l_right = NULL; + } else { + break; + } + } + + assert(rt->count == 0); + + rn_hash_node_fini(&rt->hash); + XFREE(MTYPE_ROUTE_TABLE, rt); + return; +} + +/* Utility mask array. */ +static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, + 0xf8, 0xfc, 0xfe, 0xff}; + +/* Common prefix route genaration. */ +static void route_common(const struct prefix *n, const struct prefix *p, + struct prefix *new) +{ + int i; + uint8_t diff; + uint8_t mask; + const uint8_t *np; + const uint8_t *pp; + uint8_t *newp; + + if (n->family == AF_FLOWSPEC) + return prefix_copy(new, p); + np = (const uint8_t *)&n->u.prefix; + pp = (const uint8_t *)&p->u.prefix; + + newp = &new->u.prefix; + + for (i = 0; i < p->prefixlen / 8; i++) { + if (np[i] == pp[i]) + newp[i] = np[i]; + else + break; + } + + new->prefixlen = i * 8; + + if (new->prefixlen != p->prefixlen) { + diff = np[i] ^ pp[i]; + mask = 0x80; + while (new->prefixlen < p->prefixlen && !(mask & diff)) { + mask >>= 1; + new->prefixlen++; + } + newp[i] = np[i] & maskbit[new->prefixlen % 8]; + } +} + +static void set_link(struct route_node *node, struct route_node *new) +{ + unsigned int bit = prefix_bit(&new->p.u.prefix, node->p.prefixlen); + + node->link[bit] = new; + new->parent = node; +} + +/* Find matched prefix. */ +struct route_node *route_node_match(struct route_table *table, + union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + struct route_node *node; + struct route_node *matched; + + matched = NULL; + node = table->top; + + /* Walk down tree. If there is matched route then store it to + matched. */ + while (node && node->p.prefixlen <= p->prefixlen + && prefix_match(&node->p, p)) { + if (node->info) + matched = node; + + if (node->p.prefixlen == p->prefixlen) + break; + + node = node->link[prefix_bit(&p->u.prefix, node->p.prefixlen)]; + } + + /* If matched route found, return it. */ + if (matched) + return route_lock_node(matched); + + return NULL; +} + +struct route_node *route_node_match_ipv4(struct route_table *table, + const struct in_addr *addr) +{ + struct prefix_ipv4 p; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = *addr; + + return route_node_match(table, (struct prefix *)&p); +} + +struct route_node *route_node_match_ipv6(struct route_table *table, + const struct in6_addr *addr) +{ + struct prefix_ipv6 p; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_BITLEN; + p.prefix = *addr; + + return route_node_match(table, &p); +} + +/* Lookup same prefix node. Return NULL when we can't find route. */ +struct route_node *route_node_lookup(struct route_table *table, + union prefixconstptr pu) +{ + struct route_node rn, *node; + prefix_copy(&rn.p, pu.p); + apply_mask(&rn.p); + + node = rn_hash_node_find(&table->hash, &rn); + return (node && node->info) ? route_lock_node(node) : NULL; +} + +/* Lookup same prefix node. Return NULL when we can't find route. */ +struct route_node *route_node_lookup_maynull(struct route_table *table, + union prefixconstptr pu) +{ + struct route_node rn, *node; + prefix_copy(&rn.p, pu.p); + apply_mask(&rn.p); + + node = rn_hash_node_find(&table->hash, &rn); + return node ? route_lock_node(node) : NULL; +} + +/* Add node to routing table. */ +struct route_node *route_node_get(struct route_table *table, + union prefixconstptr pu) +{ + if (frrtrace_enabled(frr_libfrr, route_node_get)) { + char buf[PREFIX2STR_BUFFER]; + prefix2str(pu, buf, sizeof(buf)); + frrtrace(2, frr_libfrr, route_node_get, table, buf); + } + + struct route_node search; + struct prefix *p = &search.p; + + prefix_copy(p, pu.p); + apply_mask(p); + + struct route_node *new; + struct route_node *node; + struct route_node *match; + uint16_t prefixlen = p->prefixlen; + const uint8_t *prefix = &p->u.prefix; + + node = rn_hash_node_find(&table->hash, &search); + if (node && node->info) + return route_lock_node(node); + + match = NULL; + node = table->top; + while (node && node->p.prefixlen <= prefixlen + && prefix_match(&node->p, p)) { + if (node->p.prefixlen == prefixlen) + return route_lock_node(node); + + match = node; + node = node->link[prefix_bit(prefix, node->p.prefixlen)]; + } + + if (node == NULL) { + new = route_node_set(table, p); + if (match) + set_link(match, new); + else + table->top = new; + } else { + new = route_node_new(table); + route_common(&node->p, p, &new->p); + new->p.family = p->family; + new->table = table; + set_link(new, node); + rn_hash_node_add(&table->hash, new); + + if (match) + set_link(match, new); + else + table->top = new; + + if (new->p.prefixlen != p->prefixlen) { + match = new; + new = route_node_set(table, p); + set_link(match, new); + table->count++; + } + } + table->count++; + route_lock_node(new); + + return new; +} + +/* Delete node from the routing table. */ +void route_node_delete(struct route_node *node) +{ + struct route_node *child; + struct route_node *parent; + + assert(node->lock == 0); + + if (node->l_left && node->l_right) + return; + + if (node->l_left) + child = node->l_left; + else + child = node->l_right; + + parent = node->parent; + + if (child) + child->parent = parent; + + if (parent) { + if (parent->l_left == node) + parent->l_left = child; + else + parent->l_right = child; + } else + node->table->top = child; + + node->table->count--; + + rn_hash_node_del(&node->table->hash, node); + + /* WARNING: FRAGILE CODE! + * route_node_free may have the side effect of free'ing the entire + * table. + * this is permitted only if table->count got decremented to zero above, + * because in that case parent will also be NULL, so that we won't try + * to + * delete a now-stale parent below. + * + * cf. srcdest_srcnode_destroy() in zebra/zebra_rib.c */ + + route_node_free(node->table, node); + + /* If parent node is stub then delete it also. */ + if (parent && parent->lock == 0) + route_node_delete(parent); +} + +/* Get first node and lock it. This function is useful when one wants + to lookup all the node exist in the routing table. */ +struct route_node *route_top(struct route_table *table) +{ + /* If there is no node in the routing table return NULL. */ + if (table->top == NULL) + return NULL; + + /* Lock the top node and return it. */ + route_lock_node(table->top); + return table->top; +} + +/* Unlock current node and lock next node then return it. */ +struct route_node *route_next(struct route_node *node) +{ + struct route_node *next; + struct route_node *start; + + /* Node may be deleted from route_unlock_node so we have to preserve + next node's pointer. */ + + if (node->l_left) { + next = node->l_left; + route_lock_node(next); + route_unlock_node(node); + return next; + } + if (node->l_right) { + next = node->l_right; + route_lock_node(next); + route_unlock_node(node); + return next; + } + + start = node; + while (node->parent) { + if (node->parent->l_left == node && node->parent->l_right) { + next = node->parent->l_right; + route_lock_node(next); + route_unlock_node(start); + return next; + } + node = node->parent; + } + route_unlock_node(start); + return NULL; +} + +/* Unlock current node and lock next node until limit. */ +struct route_node *route_next_until(struct route_node *node, + const struct route_node *limit) +{ + struct route_node *next; + struct route_node *start; + + /* Node may be deleted from route_unlock_node so we have to preserve + next node's pointer. */ + + if (node->l_left) { + next = node->l_left; + route_lock_node(next); + route_unlock_node(node); + return next; + } + if (node->l_right) { + next = node->l_right; + route_lock_node(next); + route_unlock_node(node); + return next; + } + + start = node; + while (node->parent && node != limit) { + if (node->parent->l_left == node && node->parent->l_right) { + next = node->parent->l_right; + route_lock_node(next); + route_unlock_node(start); + return next; + } + node = node->parent; + } + route_unlock_node(start); + return NULL; +} + +unsigned long route_table_count(struct route_table *table) +{ + return table->count; +} + +/** + * route_node_create + * + * Default function for creating a route node. + */ +struct route_node *route_node_create(route_table_delegate_t *delegate, + struct route_table *table) +{ + struct route_node *node; + node = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct route_node)); + return node; +} + +/** + * route_node_destroy + * + * Default function for destroying a route node. + */ +void route_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, struct route_node *node) +{ + XFREE(MTYPE_ROUTE_NODE, node); +} + +/* + * Default delegate. + */ +static route_table_delegate_t default_delegate = { + .create_node = route_node_create, + .destroy_node = route_node_destroy}; + +route_table_delegate_t *route_table_get_default_delegate(void) +{ + return &default_delegate; +} + +/* + * route_table_init + */ +struct route_table *route_table_init(void) +{ + return route_table_init_with_delegate(&default_delegate); +} + +/** + * route_table_prefix_iter_cmp + * + * Compare two prefixes according to the order in which they appear in + * an iteration over a tree. + * + * @return -1 if p1 occurs before p2 (p1 < p2) + * 0 if the prefixes are identical (p1 == p2) + * +1 if p1 occurs after p2 (p1 > p2) + */ +int route_table_prefix_iter_cmp(const struct prefix *p1, + const struct prefix *p2) +{ + struct prefix common_space; + struct prefix *common = &common_space; + + if (p1->prefixlen <= p2->prefixlen) { + if (prefix_match(p1, p2)) { + + /* + * p1 contains p2, or is equal to it. + */ + return (p1->prefixlen == p2->prefixlen) ? 0 : -1; + } + } else { + + /* + * Check if p2 contains p1. + */ + if (prefix_match(p2, p1)) + return 1; + } + + route_common(p1, p2, common); + assert(common->prefixlen < p1->prefixlen); + assert(common->prefixlen < p2->prefixlen); + + /* + * Both prefixes are longer than the common prefix. + * + * We need to check the bit after the common prefixlen to determine + * which one comes later. + */ + if (prefix_bit(&p1->u.prefix, common->prefixlen)) { + + /* + * We branch to the right to get to p1 from the common prefix. + */ + assert(!prefix_bit(&p2->u.prefix, common->prefixlen)); + return 1; + } + + /* + * We branch to the right to get to p2 from the common prefix. + */ + assert(prefix_bit(&p2->u.prefix, common->prefixlen)); + return -1; +} + +/* + * route_get_subtree_next + * + * Helper function that returns the first node that follows the nodes + * in the sub-tree under 'node' in iteration order. + */ +static struct route_node *route_get_subtree_next(struct route_node *node) +{ + while (node->parent) { + if (node->parent->l_left == node && node->parent->l_right) + return node->parent->l_right; + + node = node->parent; + } + + return NULL; +} + +/** + * route_table_get_next_internal + * + * Helper function to find the node that occurs after the given prefix in + * order of iteration. + * + * @see route_table_get_next + */ +static struct route_node * +route_table_get_next_internal(struct route_table *table, + const struct prefix *p) +{ + struct route_node *node, *tmp_node; + int cmp; + + node = table->top; + + while (node) { + int match; + + if (node->p.prefixlen < p->prefixlen) + match = prefix_match(&node->p, p); + else + match = prefix_match(p, &node->p); + + if (match) { + if (node->p.prefixlen == p->prefixlen) { + + /* + * The prefix p exists in the tree, just return + * the next + * node. + */ + route_lock_node(node); + node = route_next(node); + if (node) + route_unlock_node(node); + + return (node); + } + + if (node->p.prefixlen > p->prefixlen) { + + /* + * Node is in the subtree of p, and hence + * greater than p. + */ + return node; + } + + /* + * p is in the sub-tree under node. + */ + tmp_node = node->link[prefix_bit(&p->u.prefix, + node->p.prefixlen)]; + + if (tmp_node) { + node = tmp_node; + continue; + } + + /* + * There are no nodes in the direction where p should + * be. If + * node has a right child, then it must be greater than + * p. + */ + if (node->l_right) + return node->l_right; + + /* + * No more children to follow, go upwards looking for + * the next + * node. + */ + return route_get_subtree_next(node); + } + + /* + * Neither node prefix nor 'p' contains the other. + */ + cmp = route_table_prefix_iter_cmp(&node->p, p); + if (cmp > 0) { + + /* + * Node follows p in iteration order. Return it. + */ + return node; + } + + assert(cmp < 0); + + /* + * Node and the subtree under it come before prefix p in + * iteration order. Prefix p and its sub-tree are not present in + * the tree. Go upwards and find the first node that follows the + * subtree. That node will also succeed p. + */ + return route_get_subtree_next(node); + } + + return NULL; +} + +/** + * route_table_get_next + * + * Find the node that occurs after the given prefix in order of + * iteration. + */ +struct route_node *route_table_get_next(struct route_table *table, + union prefixconstptr pu) +{ + const struct prefix *p = pu.p; + struct route_node *node; + + node = route_table_get_next_internal(table, p); + if (node) { + assert(route_table_prefix_iter_cmp(&node->p, p) > 0); + route_lock_node(node); + } + return node; +} + +/* + * route_table_iter_init + */ +void route_table_iter_init(route_table_iter_t *iter, struct route_table *table) +{ + memset(iter, 0, sizeof(*iter)); + iter->state = RT_ITER_STATE_INIT; + iter->table = table; +} + +/* + * route_table_iter_pause + * + * Pause an iteration over the table. This allows the iteration to be + * resumed point after arbitrary additions/deletions from the table. + * An iteration can be resumed by just calling route_table_iter_next() + * on the iterator. + */ +void route_table_iter_pause(route_table_iter_t *iter) +{ + switch (iter->state) { + + case RT_ITER_STATE_INIT: + case RT_ITER_STATE_PAUSED: + case RT_ITER_STATE_DONE: + return; + + case RT_ITER_STATE_ITERATING: + + /* + * Save the prefix that we are currently at. The next call to + * route_table_iter_next() will return the node after this + * prefix + * in the tree. + */ + prefix_copy(&iter->pause_prefix, &iter->current->p); + route_unlock_node(iter->current); + iter->current = NULL; + iter->state = RT_ITER_STATE_PAUSED; + return; + + default: + assert(0); + } +} + +/* + * route_table_iter_cleanup + * + * Release any resources held by the iterator. + */ +void route_table_iter_cleanup(route_table_iter_t *iter) +{ + if (iter->state == RT_ITER_STATE_ITERATING) { + route_unlock_node(iter->current); + iter->current = NULL; + } + assert(!iter->current); + + /* + * Set the state to RT_ITER_STATE_DONE to make any + * route_table_iter_next() calls on this iterator return NULL. + */ + iter->state = RT_ITER_STATE_DONE; +} diff --git a/lib/table.h b/lib/table.h new file mode 100644 index 0000000..acfc876 --- /dev/null +++ b/lib/table.h @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routing Table + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_TABLE_H +#define _ZEBRA_TABLE_H + +#include "memory.h" +#include "hash.h" +#include "prefix.h" +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(ROUTE_NODE); + +/* + * Forward declarations. + */ +struct route_node; +struct route_table; + +/* + * route_table_delegate_t + * + * Function vector that can be used by a client to customize the + * behavior of one or more route tables. + */ +typedef const struct route_table_delegate_t_ route_table_delegate_t; + +typedef struct route_node *(*route_table_create_node_func_t)( + route_table_delegate_t *, struct route_table *); + +typedef void (*route_table_destroy_node_func_t)(route_table_delegate_t *, + struct route_table *, + struct route_node *); + +struct route_table_delegate_t_ { + route_table_create_node_func_t create_node; + route_table_destroy_node_func_t destroy_node; +}; + +PREDECL_HASH(rn_hash_node); + +/* Routing table top structure. */ +struct route_table { + struct route_node *top; + struct rn_hash_node_head hash; + + /* + * Delegate that performs certain functions for this table. + */ + route_table_delegate_t *delegate; + void (*cleanup)(struct route_table *, struct route_node *); + + unsigned long count; + + /* + * User data. + */ + void *info; +}; + +/* + * node->link is really internal to the table code and should not be + * accessed by outside code. We don't have any writers (yay), though some + * readers are left to be fixed. + * + * rationale: we need to add a hash table in parallel, to speed up + * exact-match lookups. + * + * same really applies for node->parent, though that's less of an issue. + * table->link should be - and is - NEVER written by outside code + */ +#ifdef FRR_COMPILING_TABLE_C +#define table_rdonly(x) x +#define table_internal(x) x +#else +#define table_rdonly(x) const x +#define table_internal(x) \ + const x __attribute__( \ + (deprecated("this should only be accessed by lib/table.c"))) +/* table_internal is for node->link and node->lock, once we have done + * something about remaining accesses */ +#endif + +/* so... the problem with this is that "const" doesn't mean "readonly". + * It in fact may allow the compiler to optimize based on the assumption + * that the value doesn't change. Hence, since the only purpose of this + * is to aid in development, don't put the "const" in release builds. + * + * (I haven't seen this actually break, but GCC and LLVM are getting ever + * more aggressive in optimizing...) + */ +#ifndef DEV_BUILD +#undef table_rdonly +#define table_rdonly(x) x +#endif + +/* + * Macro that defines all fields in a route node. + */ +#define ROUTE_NODE_FIELDS \ + /* Actual prefix of this radix. */ \ + struct prefix p; \ + \ + /* Tree link. */ \ + struct route_table *table_rdonly(table); \ + struct route_node *table_rdonly(parent); \ + struct route_node *table_rdonly(link[2]); \ + \ + /* Lock of this radix */ \ + unsigned int table_rdonly(lock); \ + \ + struct rn_hash_node_item nodehash; \ + /* Each node of route. */ \ + void *info; \ + + +/* Each routing entry. */ +struct route_node { + ROUTE_NODE_FIELDS + +#define l_left link[0] +#define l_right link[1] +}; + +typedef struct route_table_iter_t_ route_table_iter_t; + +typedef enum { + RT_ITER_STATE_INIT, + RT_ITER_STATE_ITERATING, + RT_ITER_STATE_PAUSED, + RT_ITER_STATE_DONE +} route_table_iter_state_t; + +/* + * route_table_iter_t + * + * Structure that holds state for iterating over a route table. + */ +struct route_table_iter_t_ { + + route_table_iter_state_t state; + + /* + * Routing table that we are iterating over. The caller must ensure + * that that table outlives the iterator. + */ + struct route_table *table; + + /* + * The node that the iterator is currently on. + */ + struct route_node *current; + + /* + * The last prefix that the iterator processed before it was paused. + */ + struct prefix pause_prefix; +}; + +/* Prototypes. */ +extern struct route_table *route_table_init(void); + +extern struct route_table * +route_table_init_with_delegate(route_table_delegate_t *delegate); + +extern route_table_delegate_t *route_table_get_default_delegate(void); + +static inline void *route_table_get_info(struct route_table *table) +{ + return table->info; +} + +static inline void route_table_set_info(struct route_table *table, void *d) +{ + table->info = d; +} + +extern void route_table_finish(struct route_table *table); +extern struct route_node *route_top(struct route_table *table); +extern struct route_node *route_next(struct route_node *node); +extern struct route_node *route_next_until(struct route_node *node, + const struct route_node *limit); +extern struct route_node *route_node_get(struct route_table *table, + union prefixconstptr pu); +extern struct route_node *route_node_lookup(struct route_table *table, + union prefixconstptr pu); +extern struct route_node *route_node_lookup_maynull(struct route_table *table, + union prefixconstptr pu); +extern struct route_node *route_node_match(struct route_table *table, + union prefixconstptr pu); +extern struct route_node *route_node_match_ipv4(struct route_table *table, + const struct in_addr *addr); +extern struct route_node *route_node_match_ipv6(struct route_table *table, + const struct in6_addr *addr); + +extern unsigned long route_table_count(struct route_table *table); + +extern struct route_node *route_node_create(route_table_delegate_t *delegate, + struct route_table *table); +extern void route_node_delete(struct route_node *node); +extern void route_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, + struct route_node *node); + +extern struct route_node *route_table_get_next(struct route_table *table, + union prefixconstptr pu); +extern int route_table_prefix_iter_cmp(const struct prefix *p1, + const struct prefix *p2); + +/* + * Iterator functions. + */ +extern void route_table_iter_init(route_table_iter_t *iter, + struct route_table *table); +extern void route_table_iter_pause(route_table_iter_t *iter); +extern void route_table_iter_cleanup(route_table_iter_t *iter); + +/* + * Inline functions. + */ + +/* Lock node. */ +static inline struct route_node *route_lock_node(struct route_node *node) +{ + (*(unsigned *)&node->lock)++; + return node; +} + +/* Unlock node. */ +static inline void route_unlock_node(struct route_node *node) +{ + assert(node->lock > 0); + (*(unsigned *)&node->lock)--; + + if (node->lock == 0) + route_node_delete(node); +} + +static inline unsigned int route_node_get_lock_count(struct route_node *node) +{ + return node->lock; +} + +/* + * route_table_iter_next + * + * Get the next node in the tree. + */ +static inline struct route_node *route_table_iter_next(route_table_iter_t *iter) +{ + struct route_node *node; + + switch (iter->state) { + + case RT_ITER_STATE_INIT: + + /* + * We're just starting the iteration. + */ + node = route_top(iter->table); + break; + + case RT_ITER_STATE_ITERATING: + node = route_next(iter->current); + break; + + case RT_ITER_STATE_PAUSED: + + /* + * Start with the node following pause_prefix. + */ + node = route_table_get_next(iter->table, &iter->pause_prefix); + break; + + case RT_ITER_STATE_DONE: + return NULL; + + default: + /* Suppress uninitialized variable warning */ + node = NULL; + assert(0); + } + + iter->current = node; + if (node) + iter->state = RT_ITER_STATE_ITERATING; + else + iter->state = RT_ITER_STATE_DONE; + + return node; +} + +/* + * route_table_iter_is_done + * + * Returns true if the iteration is complete. + */ +static inline int route_table_iter_is_done(route_table_iter_t *iter) +{ + return iter->state == RT_ITER_STATE_DONE; +} + +/* + * route_table_iter_started + * + * Returns true if this iterator has started iterating over the tree. + */ +static inline int route_table_iter_started(route_table_iter_t *iter) +{ + return iter->state != RT_ITER_STATE_INIT; +} + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pRN" (struct route_node *) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_TABLE_H */ diff --git a/lib/tc.c b/lib/tc.c new file mode 100644 index 0000000..2e45add --- /dev/null +++ b/lib/tc.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Traffic Control (TC) main library + * Copyright (C) 2022 Shichu Yang + */ + +#include "tc.h" + +int tc_getrate(const char *str, uint64_t *rate) +{ + char *endp; + uint64_t raw = strtoull(str, &endp, 10); + + if (endp == str) + return -1; + + /* if the string only contains a number, it must be valid rate (bps) */ + bool valid = (*endp == '\0'); + + const char *p = endp; + bool bytes = false, binary_base = false; + int power = 0; + + while (*p) { + if (strcmp(p, "Bps") == 0) { + bytes = true; + valid = true; + break; + } else if (strcmp(p, "bit") == 0) { + valid = true; + break; + } + switch (*p) { + case 'k': + case 'K': + power = 1; + break; + case 'm': + case 'M': + power = 2; + break; + case 'g': + case 'G': + power = 3; + break; + case 't': + case 'T': + power = 4; + break; + case 'i': + case 'I': + if (power != 0) + binary_base = true; + else + return -1; + break; + default: + return -1; + } + p++; + } + + if (!valid) + return -1; + + for (int i = 0; i < power; i++) + raw *= binary_base ? 1024ULL : 1000ULL; + + if (bytes) + *rate = raw; + else + *rate = raw / 8ULL; + + return 0; +} diff --git a/lib/tc.h b/lib/tc.h new file mode 100644 index 0000000..c4d23d6 --- /dev/null +++ b/lib/tc.h @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Traffic Control (TC) main header + * Copyright (C) 2022 Shichu Yang + */ + +#ifndef _TC_H +#define _TC_H + +#include +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TC_STR "Traffic Control\n" + +/* qdisc definitions */ + +/* qdisc kind (same as class kinds) */ +enum tc_qdisc_kind { + TC_QDISC_UNSPEC, + TC_QDISC_HTB, + TC_QDISC_NOQUEUE, +}; + +struct tc_qdisc_htb { + /* currently no members */ +}; + +struct tc_qdisc { + ifindex_t ifindex; + + enum tc_qdisc_kind kind; + union { + struct tc_qdisc_htb htb; + } u; +}; + +/* class definitions */ + +/* since classes share the same kinds of qdisc, duplicates omitted */ +struct tc_class_htb { + uint64_t rate; + uint64_t ceil; +}; + +struct tc_class { + ifindex_t ifindex; + uint32_t handle; + + enum tc_qdisc_kind kind; + union { + struct tc_class_htb htb; + } u; +}; + +/* filter definitions */ + +/* filter kinds */ +enum tc_filter_kind { + TC_FILTER_UNSPEC, + TC_FILTER_BPF, + TC_FILTER_FLOW, + TC_FILTER_FLOWER, + TC_FILTER_U32, +}; + +struct tc_bpf { + /* TODO: fill in */ +}; + +struct tc_flow { + /* TODO: fill in */ +}; + +struct tc_flower { + uint32_t classid; + +#define TC_FLOWER_IP_PROTOCOL (1 << 0) +#define TC_FLOWER_SRC_IP (1 << 1) +#define TC_FLOWER_DST_IP (1 << 2) +#define TC_FLOWER_SRC_PORT (1 << 3) +#define TC_FLOWER_DST_PORT (1 << 4) +#define TC_FLOWER_DSFIELD (1 << 5) + + uint32_t filter_bm; + + uint8_t ip_proto; + + struct prefix src_ip; + struct prefix dst_ip; + + uint16_t src_port_min; + uint16_t src_port_max; + uint16_t dst_port_min; + uint16_t dst_port_max; + + uint8_t dsfield; + uint8_t dsfield_mask; +}; + +struct tc_u32 { + /* TODO: fill in */ +}; + +struct tc_filter { + ifindex_t ifindex; + uint32_t handle; + + uint32_t priority; + uint16_t protocol; + + enum tc_filter_kind kind; + + union { + struct tc_bpf bpf; + struct tc_flow flow; + struct tc_flower flower; + struct tc_u32 u32; + } u; +}; + +extern int tc_getrate(const char *str, uint64_t *rate); + +extern int zapi_tc_qdisc_encode(uint8_t cmd, struct stream *s, + struct tc_qdisc *qdisc); +extern int zapi_tc_class_encode(uint8_t cmd, struct stream *s, + struct tc_class *class); +extern int zapi_tc_filter_encode(uint8_t cmd, struct stream *s, + struct tc_filter *filter); + +#ifdef __cplusplus +} +#endif + +#endif /* _TC_H */ diff --git a/lib/termtable.c b/lib/termtable.c new file mode 100644 index 0000000..9b36d5e --- /dev/null +++ b/lib/termtable.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASCII table generator. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#include +#include + +#include "lib/json.h" +#include "printfrr.h" +#include "memory.h" +#include "termtable.h" + +DEFINE_MTYPE_STATIC(LIB, TTABLE, "ASCII table"); + +/* clang-format off */ +const struct ttable_style ttable_styles[] = { + { // default ascii + .corner = '+', + .rownums_on = false, + .indent = 1, + .border = { + .top = '-', + .bottom = '-', + .left = '|', + .right = '|', + .top_on = true, + .bottom_on = true, + .left_on = true, + .right_on = true, + }, + .cell = { + .lpad = 1, + .rpad = 1, + .align = LEFT, + .border = { + .bottom = '-', + .bottom_on = true, + .top = '-', + .top_on = false, + .right = '|', + .right_on = true, + .left = '|', + .left_on = false, + }, + }, + }, { // blank, suitable for plaintext alignment + .corner = ' ', + .rownums_on = false, + .indent = 1, + .border = { + .top = ' ', + .bottom = ' ', + .left = ' ', + .right = ' ', + .top_on = false, + .bottom_on = false, + .left_on = false, + .right_on = false, + }, + .cell = { + .lpad = 0, + .rpad = 3, + .align = LEFT, + .border = { + .bottom = ' ', + .bottom_on = false, + .top = ' ', + .top_on = false, + .right = ' ', + .right_on = false, + .left = ' ', + .left_on = false, + }, + } + } +}; +/* clang-format on */ + +void ttable_del(struct ttable *tt) +{ + for (int i = tt->nrows - 1; i >= 0; i--) + ttable_del_row(tt, i); + + XFREE(MTYPE_TTABLE, tt->table); + XFREE(MTYPE_TTABLE, tt); +} + +struct ttable *ttable_new(const struct ttable_style *style) +{ + struct ttable *tt; + + tt = XCALLOC(MTYPE_TTABLE, sizeof(struct ttable)); + tt->style = *style; + tt->nrows = 0; + tt->ncols = 0; + tt->size = 0; + tt->table = NULL; + + return tt; +} + +/** + * Inserts or appends a new row at the specified index. + * + * If the index is -1, the row is added to the end of the table. Otherwise the + * index must be a valid index into tt->table. + * + * If the table already has at least one row (and therefore a determinate + * number of columns), a format string specifying a number of columns not equal + * to tt->ncols will result in a no-op and a return value of NULL. + * + * @param tt table to insert into + * @param i insertion index; inserted row will be (i + 1)'th row + * @param format printf format string as in ttable_[add|insert]_row() + * @param ap pre-initialized variadic list of arguments for format string + * + * @return pointer to the first cell of allocated row + */ +PRINTFRR(3, 0) +static struct ttable_cell *ttable_insert_row_va(struct ttable *tt, int i, + const char *format, va_list ap) +{ + assert(i >= -1 && i < tt->nrows); + + char shortbuf[256]; + char *res, *orig, *section; + struct ttable_cell *row; + int col = 0; + int ncols = 0; + + /* count how many columns we have */ + for (int j = 0; format[j]; j++) + ncols += !!(format[j] == '|'); + ncols++; + + if (tt->ncols == 0) + tt->ncols = ncols; + else if (ncols != tt->ncols) + return NULL; + + /* reallocate chunk if necessary */ + while (tt->size < (tt->nrows + 1) * sizeof(struct ttable_cell *)) { + tt->size = MAX(2 * tt->size, 2 * sizeof(struct ttable_cell *)); + tt->table = XREALLOC(MTYPE_TTABLE, tt->table, tt->size); + } + + /* CALLOC a block of cells */ + row = XCALLOC(MTYPE_TTABLE, tt->ncols * sizeof(struct ttable_cell)); + + res = vasnprintfrr(MTYPE_TMP, shortbuf, sizeof(shortbuf), format, ap); + orig = res; + + while (res && col < tt->ncols) { + section = strsep(&res, "|"); + row[col].text = XSTRDUP(MTYPE_TTABLE, section); + row[col].style = tt->style.cell; + col++; + } + + if (orig != shortbuf) + XFREE(MTYPE_TMP, orig); + + /* insert row */ + if (i == -1 || i == tt->nrows) + tt->table[tt->nrows] = row; + else { + memmove(&tt->table[i + 1], &tt->table[i], + (tt->nrows - i) * sizeof(struct ttable_cell *)); + tt->table[i] = row; + } + + tt->nrows++; + + return row; +} + +struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int i, + const char *format, ...) +{ + struct ttable_cell *ret; + va_list ap; + + va_start(ap, format); + ret = ttable_insert_row_va(tt, i, format, ap); + va_end(ap); + + return ret; +} + +struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...) +{ + struct ttable_cell *ret; + va_list ap; + + va_start(ap, format); + ret = ttable_insert_row_va(tt, -1, format, ap); + va_end(ap); + + return ret; +} + +void ttable_del_row(struct ttable *tt, unsigned int i) +{ + assert((int)i < tt->nrows); + + for (int j = 0; j < tt->ncols; j++) + XFREE(MTYPE_TTABLE, tt->table[i][j].text); + + XFREE(MTYPE_TTABLE, tt->table[i]); + + memmove(&tt->table[i], &tt->table[i + 1], + (tt->nrows - i - 1) * sizeof(struct ttable_cell *)); + + tt->nrows--; + + if (tt->nrows == 0) + tt->ncols = 0; +} + +void ttable_align(struct ttable *tt, unsigned int row, unsigned int col, + unsigned int nrow, unsigned int ncol, enum ttable_align align) +{ + assert((int)row < tt->nrows); + assert((int)col < tt->ncols); + assert((int)row + (int)nrow <= tt->nrows); + assert((int)col + (int)ncol <= tt->ncols); + + for (unsigned int i = row; i < row + nrow; i++) + for (unsigned int j = col; j < col + ncol; j++) + tt->table[i][j].style.align = align; +} + +static void ttable_cell_pad(struct ttable_cell *cell, enum ttable_align align, + short pad) +{ + if (align == LEFT) + cell->style.lpad = pad; + else + cell->style.rpad = pad; +} + +void ttable_pad(struct ttable *tt, unsigned int row, unsigned int col, + unsigned int nrow, unsigned int ncol, enum ttable_align align, + short pad) +{ + assert((int)row < tt->nrows); + assert((int)col < tt->ncols); + assert((int)row + (int)nrow <= tt->nrows); + assert((int)col + (int)ncol <= tt->ncols); + + for (unsigned int i = row; i < row + nrow; i++) + for (unsigned int j = col; j < col + ncol; j++) + ttable_cell_pad(&tt->table[i][j], align, pad); +} + +void ttable_restyle(struct ttable *tt) +{ + for (int i = 0; i < tt->nrows; i++) + for (int j = 0; j < tt->ncols; j++) + tt->table[i][j].style = tt->style.cell; +} + +void ttable_colseps(struct ttable *tt, unsigned int col, + enum ttable_align align, bool on, char sep) +{ + for (int i = 0; i < tt->nrows; i++) { + if (align == RIGHT) { + tt->table[i][col].style.border.right_on = on; + tt->table[i][col].style.border.right = sep; + } else { + tt->table[i][col].style.border.left_on = on; + tt->table[i][col].style.border.left = sep; + } + } +} + +void ttable_rowseps(struct ttable *tt, unsigned int row, + enum ttable_align align, bool on, char sep) +{ + for (int i = 0; i < tt->ncols; i++) { + if (align == TOP) { + tt->table[row][i].style.border.top_on = on; + tt->table[row][i].style.border.top = sep; + } else { + tt->table[row][i].style.border.bottom_on = on; + tt->table[row][i].style.border.bottom = sep; + } + } +} + +char *ttable_dump(struct ttable *tt, const char *newline) +{ + /* clang-format off */ + char *buf; // print buffer + size_t pos; // position in buffer + size_t nl_len; // strlen(newline) + int cw[tt->ncols]; // calculated column widths + int nlines; // total number of newlines / table lines + size_t width; // length of one line, with newline + int abspad; // calculated whitespace for sprintf + char *left; // left part of line + size_t lsize; // size of above + char *right; // right part of line + size_t rsize; // size of above + struct ttable_cell *cell, *row; // iteration pointers + /* clang-format on */ + + nl_len = strlen(newline); + + /* calculate width of each column */ + memset(cw, 0x00, sizeof(int) * tt->ncols); + + for (int j = 0; j < tt->ncols; j++) + for (int i = 0, cellw = 0; i < tt->nrows; i++) { + cell = &tt->table[i][j]; + cellw = 0; + cellw += (int)strlen(cell->text); + cellw += cell->style.lpad; + cellw += cell->style.rpad; + if (j != 0) + cellw += cell->style.border.left_on ? 1 : 0; + if (j != tt->ncols - 1) + cellw += cell->style.border.right_on ? 1 : 0; + cw[j] = MAX(cw[j], cellw); + } + + /* calculate overall line width, including newline */ + width = 0; + width += tt->style.indent; + width += tt->style.border.left_on ? 1 : 0; + width += tt->style.border.right_on ? 1 : 0; + width += strlen(newline); + for (int i = 0; i < tt->ncols; i++) + width += cw[i]; + + /* calculate number of lines en total */ + nlines = tt->nrows; + nlines += tt->style.border.top_on ? 1 : 0; + nlines += 1; // tt->style.border.bottom_on ? 1 : 1; makes life easier + for (int i = 0; i < tt->nrows; i++) { + /* if leftmost cell has top / bottom border, whole row does */ + nlines += tt->table[i][0].style.border.top_on ? 1 : 0; + nlines += tt->table[i][0].style.border.bottom_on ? 1 : 0; + } + + /* initialize left & right */ + lsize = tt->style.indent + (tt->style.border.left_on ? 1 : 0); + left = XCALLOC(MTYPE_TTABLE, lsize); + rsize = nl_len + (tt->style.border.right_on ? 1 : 0); + right = XCALLOC(MTYPE_TTABLE, rsize); + + memset(left, ' ', lsize); + + if (tt->style.border.left_on) + left[lsize - 1] = tt->style.border.left; + + if (tt->style.border.right_on) { + right[0] = tt->style.border.right; + memcpy(&right[1], newline, nl_len); + } else + memcpy(&right[0], newline, nl_len); + + /* allocate print buffer */ + buf = XCALLOC(MTYPE_TMP, width * (nlines + 1) + 1); + pos = 0; + + if (tt->style.border.top_on) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t i = 0; i < width - lsize - rsize; i++) + buf[pos++] = tt->style.border.top; + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + for (int i = 0; i < tt->nrows; i++) { + row = tt->table[i]; + + /* if top border and not first row, print top row border */ + if (row[0].style.border.top_on && i != 0) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = row[0].style.border.top; + + pos -= width - lsize - rsize; + for (int k = 0; k < tt->ncols; k++) { + if (k != 0 && row[k].style.border.left_on) + buf[pos] = tt->style.corner; + pos += cw[k]; + if (row[k].style.border.right_on + && k != tt->ncols - 1) + buf[pos - 1] = tt->style.corner; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (int j = 0; j < tt->ncols; j++) { + /* if left border && not first col print left border */ + if (row[j].style.border.left_on && j != 0) + buf[pos++] = row[j].style.border.left; + + /* print left padding */ + for (int k = 0; k < row[j].style.lpad; k++) + buf[pos++] = ' '; + + /* calculate padding for sprintf */ + abspad = cw[j]; + abspad -= row[j].style.rpad; + abspad -= row[j].style.lpad; + if (j != 0) + abspad -= row[j].style.border.left_on ? 1 : 0; + if (j != tt->ncols - 1) + abspad -= row[j].style.border.right_on ? 1 : 0; + + /* print text */ + if (row[j].style.align == LEFT) + pos += sprintf(&buf[pos], "%-*s", abspad, + row[j].text); + else + pos += sprintf(&buf[pos], "%*s", abspad, + row[j].text); + + /* print right padding */ + for (int k = 0; k < row[j].style.rpad; k++) + buf[pos++] = ' '; + + /* if right border && not last col print right border */ + if (row[j].style.border.right_on && j != tt->ncols - 1) + buf[pos++] = row[j].style.border.right; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + + /* if bottom border and not last row, print bottom border */ + if (row[0].style.border.bottom_on && i != tt->nrows - 1) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = row[0].style.border.bottom; + + pos -= width - lsize - rsize; + for (int k = 0; k < tt->ncols; k++) { + if (k != 0 && row[k].style.border.left_on) + buf[pos] = tt->style.corner; + pos += cw[k]; + if (row[k].style.border.right_on + && k != tt->ncols - 1) + buf[pos - 1] = tt->style.corner; + } + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + assert(!buf[pos]); /* pos == & of first \0 in buf */ + } + + if (tt->style.border.bottom_on) { + memcpy(&buf[pos], left, lsize); + pos += lsize; + + for (size_t l = 0; l < width - lsize - rsize; l++) + buf[pos++] = tt->style.border.bottom; + + memcpy(&buf[pos], right, rsize); + pos += rsize; + } + + buf[pos] = '\0'; + + XFREE(MTYPE_TTABLE, left); + XFREE(MTYPE_TTABLE, right); + + return buf; +} + +/* Crude conversion from ttable to json array. + * Assume that the first row has column headings. + * + * Formats are: + * d int32 + * f double + * l int64 + * s string (default) + */ +json_object *ttable_json(struct ttable *tt, const char *const formats) +{ + struct ttable_cell *row; /* iteration pointers */ + json_object *json = NULL; + + json = json_object_new_array(); + + for (int i = 1; i < tt->nrows; i++) { + json_object *jobj; + json_object *val; + + row = tt->table[i]; + jobj = json_object_new_object(); + json_object_array_add(json, jobj); + for (int j = 0; j < tt->ncols; j++) { + switch (formats[j]) { + case 'd': + case 'l': + val = json_object_new_int64(atol(row[j].text)); + break; + case 'f': + val = json_object_new_double(atof(row[j].text)); + break; + default: + val = json_object_new_string(row[j].text); + } + json_object_object_add(jobj, tt->table[0][j].text, val); + } + } + + return json; +} diff --git a/lib/termtable.h b/lib/termtable.h new file mode 100644 index 0000000..7258682 --- /dev/null +++ b/lib/termtable.h @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASCII table generator. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#ifndef _TERMTABLE_H_ +#define _TERMTABLE_H_ + +#include +#include "lib/json.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ttable_align { + LEFT, + RIGHT, + TOP, + BOTTOM, +}; + +struct ttable_border { + char top; + char bottom; + char left; + char right; + + bool top_on; + bool bottom_on; + bool left_on; + bool right_on; +}; + +/* cell style and cell */ +struct ttable_cellstyle { + short lpad; + short rpad; + enum ttable_align align; + struct ttable_border border; +}; + +struct ttable_cell { + char *text; + struct ttable_cellstyle style; +}; + +/* table style and table */ +struct ttable_style { + char corner; /* intersection */ + int indent; /* left table indent */ + bool rownums_on; /* show row numbers; unimplemented */ + + struct ttable_border border; + struct ttable_cellstyle cell; +}; + +struct ttable { + int nrows; /* number of rows */ + int ncols; /* number of cols */ + struct ttable_cell **table; /* table, row x col */ + size_t size; /* size */ + struct ttable_style style; /* style */ +}; + +/* some predefined styles */ +#define TTSTYLE_ASCII 0 +#define TTSTYLE_BLANK 1 + +extern const struct ttable_style ttable_styles[2]; + +/** + * Creates a new table with the default style, which looks like this: + * + * +----------+----------+ + * | column 1 | column 2 | + * +----------+----------+ + * | data... | data!! | + * +----------+----------+ + * | datums | 12345 | + * +----------+----------+ + * + * @return the created table + */ +struct ttable *ttable_new(const struct ttable_style *tts); + +/** + * Deletes a table and releases all associated resources. + * + * @param tt the table to destroy + */ +void ttable_del(struct ttable *tt); + +/** + * Inserts a new row at the given index. + * + * The row contents are determined by a format string. The format string has + * the same form as a regular printf format string, except that columns are + * delimited by '|'. For example, to make the first column of the table above, + * the call is: + * + * ttable_insert_row(, , "%s|%s", "column 1", "column 2"); + * + * All features of printf format strings are permissible here. + * + * Caveats: + * - At present you cannot insert '|' into a cell's contents. + * - If there are N columns, '|' must appear n-1 times or the row will not be + * created + * + * @param tt table to insert row into + * @param row the row number (begins at 0) + * @param format column-separated format string + * @param ... arguments to format string + * + * @return pointer to the first cell in the created row, or NULL if not enough + * columns were specified + */ +struct ttable_cell *ttable_insert_row(struct ttable *tt, unsigned int row, + const char *format, ...) PRINTFRR(3, 4); +/** + * Inserts a new row at the end of the table. + * + * The row contents are determined by a format string. The format string has + * the same form as a regular printf format string, except that columns are + * delimited by '|'. For example, to make the first column of the table above, + * the call is: + * + * ttable_add_row(, "%s|%s", "column 1", "column 2"); + * + * All features of printf format strings are permissible here. + * + * Caveats: + * - At present you cannot insert '|' into a cell's contents. + * - If there are N columns, '|' must appear n-1 times or the row will not be + * created + * + * @param tt table to insert row into + * @param format column-separated format string + * @param ... arguments to format string + * + * @return pointer to the first cell in the created row, or NULL if not enough + * columns were specified + */ +struct ttable_cell *ttable_add_row(struct ttable *tt, const char *format, ...) + PRINTFRR(2, 3); + +/** + * Removes a row from the table. + * + * @param tt table to delete row from + * @param row the row number (begins at 0) + */ +void ttable_del_row(struct ttable *tt, unsigned int row); + +/** + * Sets alignment for a range of cells. + * + * Available alignments are LEFT and RIGHT. Cell contents will be aligned + * accordingly, while respecting padding (if any). Suppose a cell has: + * + * lpad = 1 + * rpad = 1 + * align = RIGHT + * text = 'datums' + * + * The cell would look like: + * + * +-------------------+ + * | datums | + * +-------------------+ + * + * On the other hand: + * + * lpad = 1 + * rpad = 10 + * align = RIGHT + * text = 'datums' + * + * +-------------------+ + * | datums | + * +-------------------+ + * + * The default alignment is LEFT. + * + * @param tt the table to set alignment on + * @param srow starting row index + * @param scol starting column index + * @param nrow # rows to align + * @param ncol # cols to align + * @param align the alignment to set + */ +void ttable_align(struct ttable *tt, unsigned int srow, unsigned int scol, + unsigned int erow, unsigned int ecol, + enum ttable_align align); + +/** + * Sets padding for a range of cells. + * + * Available padding options are LEFT and RIGHT (the alignment enum is reused). + * Both options may be set. Padding is treated as though it is stuck to the + * walls of the cell. Suppose a cell has: + * + * lpad = 4 + * rpad = 2 + * align = RIGHT + * text = 'datums' + * + * The cell padding, marked by '.', would look like: + * + * +--------------+ + * | .datums. | + * +--------------+ + * + * If the column is wider than the cell, the cell contents are aligned in an + * additional padded field according to the cell alignment. + * + * +--------------------+ + * | Data!!!11!~~~~~:-) | + * +--------------------+ + * | . datums. | + * +--------------------+ + * + * @param tt the table to set padding on + * @param srow starting row index + * @param scol starting column index + * @param nrow # rows to pad + * @param ncol # cols to pad + * @param align LEFT or RIGHT + * @param pad # spaces to pad with + */ +void ttable_pad(struct ttable *tt, unsigned int srow, unsigned int scol, + unsigned int nrow, unsigned int ncol, enum ttable_align align, + short pad); + +/** + * Restyle all cells according to table.cell.style. + * + * @param tt table to restyle + */ +void ttable_restyle(struct ttable *tt); + +/** + * Turn left/right column separators on or off for specified column. + * + * @param tt table + * @param col column index + * @param align left or right separators + * @param on true/false for on/off + * @param sep character to use + */ +void ttable_colseps(struct ttable *tt, unsigned int col, + enum ttable_align align, bool on, char sep); + +/** + * Turn bottom row separators on or off for specified row. + * + * @param tt table + * @param row row index + * @param align left or right separators + * @param on true/false for on/off + * @param sep character to use + */ +void ttable_rowseps(struct ttable *tt, unsigned int row, + enum ttable_align align, bool on, char sep); + +/** + * Dumps a table to a heap-allocated string. + * + * Caller must free this string after use with + * + * XFREE (MTYPE_TMP, str); + * + * @param tt the table to dump + * @param newline the desired newline sequence to use, null terminated. + * @return table in text form + */ +char *ttable_dump(struct ttable *tt, const char *newline); + +/** + * Convert a table to a JSON array of objects. + * + * Caller must free the returned json_object structure. + * + * @param tt the table to convert + * @param formats an array of characters indicating what JSON type should be + * used. + */ +json_object *ttable_json(struct ttable *tt, const char *const formats); + +#ifdef __cplusplus +} +#endif + +#endif /* _TERMTABLE_H */ diff --git a/lib/trace.h b/lib/trace.h new file mode 100644 index 0000000..a30f53c --- /dev/null +++ b/lib/trace.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Tracing macros + * + * Wraps tracepoint macros for different tracing systems to allow switching + * between them at compile time. + * + * This should not be included directly by source files wishing to provide + * tracepoints. Instead, write a header that defines LTTng tracepoints and + * which includes this header, and include your new header in your source. USDT + * probes do not need tracepoint definitions, but are less capable than LTTng + * tracepoints. + * + * Copyright (C) 2020 NVIDIA Corporation + * Quentin Young + */ + +#ifndef _TRACE_H_ +#define _TRACE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +/* + * Provided here: + * - frrtrace(n, provider, name, ...args...) + * - frrtrace_enabled(provider, name) + * - frrtracelog(level, msg, ...) + * + * Use frrtrace() to define tracepoints. n is the number of arguments; this is + * needed because USDT probe definitions use DTRACE_PROBEn macros, so the + * number of args must be passed in order to expand the correct macro. + * + * frrtrace_enabled() maps to tracepoint_enabled() under LTTng and is always + * true when using USDT. In the future it could be mapped to USDT semaphores + * but this is not implemented at present. + * + * frrtracelog() maps to tracelog() under LTTng and should only be used in zlog + * core code, to propagate zlog messages to LTTng. It expands to nothing + * otherwise. + */ + +#if defined(HAVE_LTTNG) + +#define frrtrace(nargs, provider, name, ...) \ + tracepoint(provider, name, ## __VA_ARGS__) +#define frrtrace_enabled(...) tracepoint_enabled(__VA_ARGS__) +#define frrtracelog(...) tracelog(__VA_ARGS__) + +#elif defined(HAVE_USDT) + +#include "sys/sdt.h" + +#define frrtrace(nargs, provider, name, ...) \ + DTRACE_PROBE##nargs(provider, name, ## __VA_ARGS__) +#define frrtrace_enabled(...) true +#define frrtracelog(...) + +#else + +#define frrtrace(nargs, provider, name, ...) (void)0 +#define frrtrace_enabled(...) false +#define frrtracelog(...) (void)0 + +#endif + +#endif /* _TRACE_H_ */ diff --git a/lib/typerb.c b/lib/typerb.c new file mode 100644 index 0000000..af04307 --- /dev/null +++ b/lib/typerb.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: ISC AND BSD-2-Clause +/* RB-tree */ + +/* + * Copyright 2002 Niels Provos + */ + +/* + * Copyright (c) 2016 David Gwynne + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "typerb.h" + +#define RB_BLACK 0 +#define RB_RED 1 + +#define rb_entry typed_rb_entry +#define rbt_tree typed_rb_root + +#define RBE_LEFT(_rbe) (_rbe)->rbt_left +#define RBE_RIGHT(_rbe) (_rbe)->rbt_right +#define RBE_PARENT(_rbe) (_rbe)->rbt_parent +#define RBE_COLOR(_rbe) (_rbe)->rbt_color + +#define RBH_ROOT(_rbt) (_rbt)->rbt_root + +static inline void rbe_set(struct rb_entry *rbe, struct rb_entry *parent) +{ + RBE_PARENT(rbe) = parent; + RBE_LEFT(rbe) = RBE_RIGHT(rbe) = NULL; + RBE_COLOR(rbe) = RB_RED; +} + +static inline void rbe_set_blackred(struct rb_entry *black, + struct rb_entry *red) +{ + RBE_COLOR(black) = RB_BLACK; + RBE_COLOR(red) = RB_RED; +} + +static inline void rbe_rotate_left(struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent; + struct rb_entry *tmp; + + tmp = RBE_RIGHT(rbe); + RBE_RIGHT(rbe) = RBE_LEFT(tmp); + if (RBE_RIGHT(rbe) != NULL) + RBE_PARENT(RBE_LEFT(tmp)) = rbe; + + parent = RBE_PARENT(rbe); + RBE_PARENT(tmp) = parent; + if (parent != NULL) { + if (rbe == RBE_LEFT(parent)) + RBE_LEFT(parent) = tmp; + else + RBE_RIGHT(parent) = tmp; + } else + RBH_ROOT(rbt) = tmp; + + RBE_LEFT(tmp) = rbe; + RBE_PARENT(rbe) = tmp; +} + +static inline void rbe_rotate_right(struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent; + struct rb_entry *tmp; + + tmp = RBE_LEFT(rbe); + RBE_LEFT(rbe) = RBE_RIGHT(tmp); + if (RBE_LEFT(rbe) != NULL) + RBE_PARENT(RBE_RIGHT(tmp)) = rbe; + + parent = RBE_PARENT(rbe); + RBE_PARENT(tmp) = parent; + if (parent != NULL) { + if (rbe == RBE_LEFT(parent)) + RBE_LEFT(parent) = tmp; + else + RBE_RIGHT(parent) = tmp; + } else + RBH_ROOT(rbt) = tmp; + + RBE_RIGHT(tmp) = rbe; + RBE_PARENT(rbe) = tmp; +} + +static inline void rbe_insert_color(struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *parent, *gparent, *tmp; + + rbt->count++; + + while ((parent = RBE_PARENT(rbe)) != NULL + && RBE_COLOR(parent) == RB_RED) { + gparent = RBE_PARENT(parent); + + if (parent == RBE_LEFT(gparent)) { + tmp = RBE_RIGHT(gparent); + if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { + RBE_COLOR(tmp) = RB_BLACK; + rbe_set_blackred(parent, gparent); + rbe = gparent; + continue; + } + + if (RBE_RIGHT(parent) == rbe) { + rbe_rotate_left(rbt, parent); + tmp = parent; + parent = rbe; + rbe = tmp; + } + + rbe_set_blackred(parent, gparent); + rbe_rotate_right(rbt, gparent); + } else { + tmp = RBE_LEFT(gparent); + if (tmp != NULL && RBE_COLOR(tmp) == RB_RED) { + RBE_COLOR(tmp) = RB_BLACK; + rbe_set_blackred(parent, gparent); + rbe = gparent; + continue; + } + + if (RBE_LEFT(parent) == rbe) { + rbe_rotate_right(rbt, parent); + tmp = parent; + parent = rbe; + rbe = tmp; + } + + rbe_set_blackred(parent, gparent); + rbe_rotate_left(rbt, gparent); + } + } + + RBE_COLOR(RBH_ROOT(rbt)) = RB_BLACK; +} + +static inline void rbe_remove_color(struct rbt_tree *rbt, + struct rb_entry *parent, + struct rb_entry *rbe) +{ + struct rb_entry *tmp; + + while ((rbe == NULL || RBE_COLOR(rbe) == RB_BLACK) + && rbe != RBH_ROOT(rbt) && parent) { + if (RBE_LEFT(parent) == rbe) { + tmp = RBE_RIGHT(parent); + if (RBE_COLOR(tmp) == RB_RED) { + rbe_set_blackred(tmp, parent); + rbe_rotate_left(rbt, parent); + tmp = RBE_RIGHT(parent); + } + if ((RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) + && (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { + RBE_COLOR(tmp) = RB_RED; + rbe = parent; + parent = RBE_PARENT(rbe); + } else { + if (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK) { + struct rb_entry *oleft; + + oleft = RBE_LEFT(tmp); + if (oleft != NULL) + RBE_COLOR(oleft) = RB_BLACK; + + RBE_COLOR(tmp) = RB_RED; + rbe_rotate_right(rbt, tmp); + tmp = RBE_RIGHT(parent); + } + + RBE_COLOR(tmp) = RBE_COLOR(parent); + RBE_COLOR(parent) = RB_BLACK; + if (RBE_RIGHT(tmp)) + RBE_COLOR(RBE_RIGHT(tmp)) = RB_BLACK; + + rbe_rotate_left(rbt, parent); + rbe = RBH_ROOT(rbt); + break; + } + } else { + tmp = RBE_LEFT(parent); + if (RBE_COLOR(tmp) == RB_RED) { + rbe_set_blackred(tmp, parent); + rbe_rotate_right(rbt, parent); + tmp = RBE_LEFT(parent); + } + + if ((RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) + && (RBE_RIGHT(tmp) == NULL + || RBE_COLOR(RBE_RIGHT(tmp)) == RB_BLACK)) { + RBE_COLOR(tmp) = RB_RED; + rbe = parent; + parent = RBE_PARENT(rbe); + } else { + if (RBE_LEFT(tmp) == NULL + || RBE_COLOR(RBE_LEFT(tmp)) == RB_BLACK) { + struct rb_entry *oright; + + oright = RBE_RIGHT(tmp); + if (oright != NULL) + RBE_COLOR(oright) = RB_BLACK; + + RBE_COLOR(tmp) = RB_RED; + rbe_rotate_left(rbt, tmp); + tmp = RBE_LEFT(parent); + } + + RBE_COLOR(tmp) = RBE_COLOR(parent); + RBE_COLOR(parent) = RB_BLACK; + if (RBE_LEFT(tmp) != NULL) + RBE_COLOR(RBE_LEFT(tmp)) = RB_BLACK; + + rbe_rotate_right(rbt, parent); + rbe = RBH_ROOT(rbt); + break; + } + } + } + + if (rbe != NULL) + RBE_COLOR(rbe) = RB_BLACK; +} + +static inline struct rb_entry * +rbe_remove(struct rbt_tree *rbt, struct rb_entry *rbe) +{ + struct rb_entry *child, *parent, *old = rbe; + unsigned int color; + + if (RBE_LEFT(rbe) == NULL) + child = RBE_RIGHT(rbe); + else if (RBE_RIGHT(rbe) == NULL) + child = RBE_LEFT(rbe); + else { + struct rb_entry *tmp; + + rbe = RBE_RIGHT(rbe); + while ((tmp = RBE_LEFT(rbe)) != NULL) + rbe = tmp; + + child = RBE_RIGHT(rbe); + parent = RBE_PARENT(rbe); + color = RBE_COLOR(rbe); + if (child != NULL) + RBE_PARENT(child) = parent; + if (parent != NULL) { + if (RBE_LEFT(parent) == rbe) + RBE_LEFT(parent) = child; + else + RBE_RIGHT(parent) = child; + } else + RBH_ROOT(rbt) = child; + if (RBE_PARENT(rbe) == old) + parent = rbe; + *rbe = *old; + + tmp = RBE_PARENT(old); + if (tmp != NULL) { + if (RBE_LEFT(tmp) == old) + RBE_LEFT(tmp) = rbe; + else + RBE_RIGHT(tmp) = rbe; + } else + RBH_ROOT(rbt) = rbe; + + RBE_PARENT(RBE_LEFT(old)) = rbe; + if (RBE_RIGHT(old)) + RBE_PARENT(RBE_RIGHT(old)) = rbe; + + goto color; + } + + parent = RBE_PARENT(rbe); + color = RBE_COLOR(rbe); + + if (child != NULL) + RBE_PARENT(child) = parent; + if (parent != NULL) { + if (RBE_LEFT(parent) == rbe) + RBE_LEFT(parent) = child; + else + RBE_RIGHT(parent) = child; + } else + RBH_ROOT(rbt) = child; +color: + if (color == RB_BLACK) + rbe_remove_color(rbt, parent, child); + + rbt->count--; + memset(old, 0, sizeof(*old)); + return (old); +} + +struct typed_rb_entry *typed_rb_remove(struct rbt_tree *rbt, + struct rb_entry *rbe) +{ + return rbe_remove(rbt, rbe); +} + +struct typed_rb_entry *typed_rb_insert(struct rbt_tree *rbt, + struct rb_entry *rbe, int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)) +{ + struct rb_entry *tmp; + struct rb_entry *parent = NULL; + int comp = 0; + + tmp = RBH_ROOT(rbt); + while (tmp != NULL) { + parent = tmp; + + comp = cmpfn(rbe, tmp); + if (comp < 0) + tmp = RBE_LEFT(tmp); + else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return tmp; + } + + rbe_set(rbe, parent); + + if (parent != NULL) { + if (comp < 0) + RBE_LEFT(parent) = rbe; + else + RBE_RIGHT(parent) = rbe; + } else + RBH_ROOT(rbt) = rbe; + + rbe_insert_color(rbt, rbe); + + return NULL; +} + +/* Finds the node with the same key as elm */ +const struct rb_entry *typed_rb_find(const struct rbt_tree *rbt, + const struct rb_entry *key, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)) +{ + const struct rb_entry *tmp = RBH_ROOT(rbt); + int comp; + + while (tmp != NULL) { + comp = cmpfn(key, tmp); + if (comp < 0) + tmp = RBE_LEFT(tmp); + else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return tmp; + } + + return NULL; +} + +const struct rb_entry *typed_rb_find_gteq(const struct rbt_tree *rbt, + const struct rb_entry *key, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)) +{ + const struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL; + int comp; + + while (tmp != NULL) { + comp = cmpfn(key, tmp); + if (comp < 0) { + best = tmp; + tmp = RBE_LEFT(tmp); + } else if (comp > 0) + tmp = RBE_RIGHT(tmp); + else + return tmp; + } + + return best; +} + +const struct rb_entry *typed_rb_find_lt(const struct rbt_tree *rbt, + const struct rb_entry *key, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)) +{ + const struct rb_entry *tmp = RBH_ROOT(rbt), *best = NULL; + int comp; + + while (tmp != NULL) { + comp = cmpfn(key, tmp); + if (comp <= 0) + tmp = RBE_LEFT(tmp); + else { + best = tmp; + tmp = RBE_RIGHT(tmp); + } + } + + return best; +} + +struct rb_entry *typed_rb_next(const struct rb_entry *rbe_const) +{ + struct rb_entry *rbe = (struct rb_entry *)rbe_const; + + if (RBE_RIGHT(rbe) != NULL) { + rbe = RBE_RIGHT(rbe); + while (RBE_LEFT(rbe) != NULL) + rbe = RBE_LEFT(rbe); + } else { + if (RBE_PARENT(rbe) && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + else { + while (RBE_PARENT(rbe) + && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + rbe = RBE_PARENT(rbe); + } + } + + return rbe; +} + +struct rb_entry *typed_rb_prev(const struct rb_entry *rbe_const) +{ + struct rb_entry *rbe = (struct rb_entry *)rbe_const; + + if (RBE_LEFT(rbe)) { + rbe = RBE_LEFT(rbe); + while (RBE_RIGHT(rbe)) + rbe = RBE_RIGHT(rbe); + } else { + if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + else { + while (RBE_PARENT(rbe) + && (rbe == RBE_LEFT(RBE_PARENT(rbe)))) + rbe = RBE_PARENT(rbe); + rbe = RBE_PARENT(rbe); + } + } + + return rbe; +} + +struct rb_entry *typed_rb_min(const struct rbt_tree *rbt) +{ + struct rb_entry *rbe = RBH_ROOT(rbt); + struct rb_entry *parent = NULL; + + while (rbe != NULL) { + parent = rbe; + rbe = RBE_LEFT(rbe); + } + + return parent; +} + +struct rb_entry *typed_rb_max(const struct rbt_tree *rbt) +{ + struct rb_entry *rbe = RBH_ROOT(rbt); + struct rb_entry *parent = NULL; + + while (rbe != NULL) { + parent = rbe; + rbe = RBE_RIGHT(rbe); + } + + return parent; +} + +bool typed_rb_member(const struct typed_rb_root *rbt, + const struct typed_rb_entry *rbe) +{ + while (rbe->rbt_parent) + rbe = rbe->rbt_parent; + return rbe == rbt->rbt_root; +} diff --git a/lib/typerb.h b/lib/typerb.h new file mode 100644 index 0000000..93370e1 --- /dev/null +++ b/lib/typerb.h @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: ISC +/* + * The following Red-Black tree implementation is based off code with + * original copyright: + * + * Copyright (c) 2016 David Gwynne + */ + +#ifndef _FRR_TYPERB_H +#define _FRR_TYPERB_H + +#ifndef _TYPESAFE_EXPAND_MACROS +#include +#include "typesafe.h" +#endif /* _TYPESAFE_EXPAND_MACROS */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct typed_rb_entry { + struct typed_rb_entry *rbt_parent; + struct typed_rb_entry *rbt_left; + struct typed_rb_entry *rbt_right; + unsigned int rbt_color; +}; + +struct typed_rb_root { + struct typed_rb_entry *rbt_root; + size_t count; +}; + +struct typed_rb_entry *typed_rb_insert(struct typed_rb_root *rbt, + struct typed_rb_entry *rbe, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)); +struct typed_rb_entry *typed_rb_remove(struct typed_rb_root *rbt, + struct typed_rb_entry *rbe); +const struct typed_rb_entry *typed_rb_find(const struct typed_rb_root *rbt, + const struct typed_rb_entry *rbe, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)); +const struct typed_rb_entry *typed_rb_find_gteq(const struct typed_rb_root *rbt, + const struct typed_rb_entry *rbe, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)); +const struct typed_rb_entry *typed_rb_find_lt(const struct typed_rb_root *rbt, + const struct typed_rb_entry *rbe, + int (*cmpfn)( + const struct typed_rb_entry *a, + const struct typed_rb_entry *b)); +struct typed_rb_entry *typed_rb_min(const struct typed_rb_root *rbt); +struct typed_rb_entry *typed_rb_max(const struct typed_rb_root *rbt); +struct typed_rb_entry *typed_rb_prev(const struct typed_rb_entry *rbe); +struct typed_rb_entry *typed_rb_next(const struct typed_rb_entry *rbe); +bool typed_rb_member(const struct typed_rb_root *rbt, + const struct typed_rb_entry *rbe); + +#define _PREDECL_RBTREE(prefix) \ +struct prefix ## _head { struct typed_rb_root rr; }; \ +struct prefix ## _item { struct typed_rb_entry re; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_RBTREE_UNIQ(var) { } +#define INIT_RBTREE_NONUNIQ(var) { } + +#define _DECLARE_RBTREE(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + struct typed_rb_entry *re; \ + re = typed_rb_insert(&h->rr, &item->field.re, cmpfn_uq); \ + return container_of_null(re, type, field.re); \ +} \ +macro_inline const type *prefix ## _const_find_gteq( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_find_gteq(&h->rr, &item->field.re, cmpfn_nuq); \ + return container_of_null(re, type, field.re); \ +} \ +macro_inline const type *prefix ## _const_find_lt( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_find_lt(&h->rr, &item->field.re, cmpfn_nuq); \ + return container_of_null(re, type, field.re); \ +} \ +TYPESAFE_FIND_CMP(prefix, type) \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct typed_rb_entry *re; \ + re = typed_rb_remove(&h->rr, &item->field.re); \ + return container_of_null(re, type, field.re); \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct typed_rb_entry *re; \ + re = typed_rb_min(&h->rr); \ + if (!re) \ + return NULL; \ + typed_rb_remove(&h->rr, re); \ + return container_of(re, type, field.re); \ +} \ +TYPESAFE_SWAP_ALL_SIMPLE(prefix) \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_min(&h->rr); \ + return container_of_null(re, type, field.re); \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_next(&item->field.re); \ + return container_of_null(re, type, field.re); \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_max(&h->rr); \ + return container_of_null(re, type, field.re); \ +} \ +macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_prev(&item->field.re); \ + return container_of_null(re, type, field.re); \ +} \ +TYPESAFE_LAST_PREV(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + struct typed_rb_entry *re; \ + re = item ? typed_rb_next(&item->field.re) : NULL; \ + return container_of_null(re, type, field.re); \ +} \ +macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \ +{ \ + struct typed_rb_entry *re; \ + re = item ? typed_rb_prev(&item->field.re) : NULL; \ + return container_of_null(re, type, field.re); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->rr.count; \ +} \ +macro_pure bool prefix ## _member(const struct prefix##_head *h, \ + const type *item) \ +{ \ + return typed_rb_member(&h->rr, &item->field.re); \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_RBTREE_UNIQ(prefix) \ + _PREDECL_RBTREE(prefix) +#define DECLARE_RBTREE_UNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \ + const struct typed_rb_entry *b) \ +{ \ + return cmpfn(container_of(a, type, field.re), \ + container_of(b, type, field.re)); \ +} \ +macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct typed_rb_entry *re; \ + re = typed_rb_find(&h->rr, &item->field.re, &prefix ## __cmp); \ + return container_of_null(re, type, field.re); \ +} \ +TYPESAFE_FIND(prefix, type) \ + \ +_DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp); \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_RBTREE_NONUNIQ(prefix) \ + _PREDECL_RBTREE(prefix) +#define DECLARE_RBTREE_NONUNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct typed_rb_entry *a, \ + const struct typed_rb_entry *b) \ +{ \ + return cmpfn(container_of(a, type, field.re), \ + container_of(b, type, field.re)); \ +} \ +macro_inline int prefix ## __cmp_uq(const struct typed_rb_entry *a, \ + const struct typed_rb_entry *b) \ +{ \ + int cmpval = cmpfn(container_of(a, type, field.re), \ + container_of(b, type, field.re)); \ + if (cmpval) \ + return cmpval; \ + if (a < b) \ + return -1; \ + if (a > b) \ + return 1; \ + return 0; \ +} \ + \ +_DECLARE_RBTREE(prefix, type, field, prefix ## __cmp, prefix ## __cmp_uq); \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_TYPERB_H */ diff --git a/lib/typesafe.c b/lib/typesafe.c new file mode 100644 index 0000000..c077447 --- /dev/null +++ b/lib/typesafe.c @@ -0,0 +1,609 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "typesafe.h" +#include "memory.h" +#include "network.h" + +DEFINE_MTYPE_STATIC(LIB, TYPEDHASH_BUCKET, "Typed-hash bucket"); +DEFINE_MTYPE_STATIC(LIB, SKIPLIST_OFLOW, "Skiplist overflow"); +DEFINE_MTYPE_STATIC(LIB, HEAP_ARRAY, "Typed-heap array"); + +struct slist_item typesafe_slist_sentinel = { NULL }; + +bool typesafe_list_member(const struct slist_head *head, + const struct slist_item *item) +{ + struct slist_item *fromhead = head->first; + struct slist_item **fromnext = (struct slist_item **)&item->next; + + while (fromhead != _SLIST_LAST) { + if (fromhead == item || fromnext == head->last_next) + return true; + + fromhead = fromhead->next; + if (!*fromnext || *fromnext == _SLIST_LAST) + break; + fromnext = &(*fromnext)->next; + } + + return false; +} + +bool typesafe_dlist_member(const struct dlist_head *head, + const struct dlist_item *item) +{ + const struct dlist_item *fromhead = head->hitem.next; + const struct dlist_item *fromitem = item->next; + + if (!item->prev || !item->next) + return false; + + while (fromhead != &head->hitem && fromitem != item) { + if (fromitem == &head->hitem || fromhead == item) + return true; + fromhead = fromhead->next; + fromitem = fromitem->next; + } + + return false; +} + +#if 0 +static void hash_consistency_check(struct thash_head *head) +{ + uint32_t i; + struct thash_item *item, *prev; + + for (i = 0; i < HASH_SIZE(*head); i++) { + item = head->entries[i]; + prev = NULL; + while (item) { + assert(HASH_KEY(*head, item->hashval) == i); + assert(!prev || item->hashval >= prev->hashval); + prev = item; + item = item->next; + } + } +} +#else +#define hash_consistency_check(x) +#endif + +void typesafe_hash_grow(struct thash_head *head) +{ + uint32_t newsize = head->count, i, j; + uint8_t newshift, delta; + + /* note hash_grow is called after head->count++, so newsize is + * guaranteed to be >= 1. So the minimum argument to builtin_ctz + * below is 2, which returns 1, and that makes newshift >= 2. + * + * Calling hash_grow with a zero head->count would result in a + * malformed hash table that has tabshift == 1. + */ + assert(head->count > 0); + + hash_consistency_check(head); + + newsize |= newsize >> 1; + newsize |= newsize >> 2; + newsize |= newsize >> 4; + newsize |= newsize >> 8; + newsize |= newsize >> 16; + newsize++; + newshift = __builtin_ctz(newsize) + 1; + + if (head->maxshift && newshift > head->maxshift) + newshift = head->maxshift; + if (newshift == head->tabshift) + return; + newsize = _HASH_SIZE(newshift); + + head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries, + sizeof(head->entries[0]) * newsize); + memset(head->entries + HASH_SIZE(*head), 0, + sizeof(head->entries[0]) * + (newsize - HASH_SIZE(*head))); + + delta = newshift - head->tabshift; + + i = HASH_SIZE(*head); + if (i == 0) + goto out; + do { + struct thash_item **apos, *item; + + i--; + apos = &head->entries[i]; + + for (j = 0; j < (1U << delta); j++) { + item = *apos; + *apos = NULL; + + head->entries[(i << delta) + j] = item; + apos = &head->entries[(i << delta) + j]; + + while ((item = *apos)) { + uint32_t midbits; + midbits = _HASH_KEY(newshift, item->hashval); + midbits &= (1 << delta) - 1; + if (midbits > j) + break; + apos = &item->next; + } + } + } while (i > 0); + +out: + head->tabshift = newshift; + hash_consistency_check(head); +} + +void typesafe_hash_shrink(struct thash_head *head) +{ + uint32_t newsize = head->count, i, j; + uint8_t newshift, delta; + + hash_consistency_check(head); + + if (!head->count) { + XFREE(MTYPE_TYPEDHASH_BUCKET, head->entries); + head->tabshift = 0; + return; + } + + newsize |= newsize >> 1; + newsize |= newsize >> 2; + newsize |= newsize >> 4; + newsize |= newsize >> 8; + newsize |= newsize >> 16; + newsize++; + newshift = __builtin_ctz(newsize) + 1; + + if (head->minshift && newshift < head->minshift) + newshift = head->minshift; + if (newshift == head->tabshift) + return; + newsize = _HASH_SIZE(newshift); + + delta = head->tabshift - newshift; + + for (i = 0; i < newsize; i++) { + struct thash_item **apos = &head->entries[i]; + + for (j = 0; j < (1U << delta); j++) { + *apos = head->entries[(i << delta) + j]; + while (*apos) + apos = &(*apos)->next; + } + } + head->entries = XREALLOC(MTYPE_TYPEDHASH_BUCKET, head->entries, + sizeof(head->entries[0]) * newsize); + head->tabshift = newshift; + + hash_consistency_check(head); +} + +/* skiplist */ + +static inline struct sskip_item *sl_level_get(const struct sskip_item *item, + size_t level) +{ + if (level < SKIPLIST_OVERFLOW) + return item->next[level]; + if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1)) + return item->next[level]; + + uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; + ptrval &= UINTPTR_MAX - 3; + struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; + return oflow->next[level - SKIPLIST_OVERFLOW]; +} + +static inline void sl_level_set(struct sskip_item *item, size_t level, + struct sskip_item *value) +{ + if (level < SKIPLIST_OVERFLOW) + item->next[level] = value; + else if (level == SKIPLIST_OVERFLOW && !((uintptr_t)item->next[level] & 1)) + item->next[level] = value; + else { + uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; + ptrval &= UINTPTR_MAX - 3; + struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; + oflow->next[level - SKIPLIST_OVERFLOW] = value; + } +} + +struct sskip_item *typesafe_skiplist_add(struct sskip_head *head, + struct sskip_item *item, + int (*cmpfn)(const struct sskip_item *a, + const struct sskip_item *b)) +{ + size_t level = SKIPLIST_MAXDEPTH, newlevel, auxlevel; + struct sskip_item *prev = &head->hitem, *next, *auxprev, *auxnext; + int cmpval; + + /* level / newlevel are 1-counted here */ + newlevel = __builtin_ctz(frr_weak_random()) + 1; + if (newlevel > SKIPLIST_MAXDEPTH) + newlevel = SKIPLIST_MAXDEPTH; + + next = NULL; + while (level >= newlevel) { + next = sl_level_get(prev, level - 1); + if (!next) { + level--; + continue; + } + cmpval = cmpfn(next, item); + if (cmpval < 0) { + prev = next; + continue; + } else if (cmpval == 0) { + return next; + } + level--; + } + + /* check for duplicate item - could be removed if code doesn't rely + * on it, but not really work the complication. */ + auxlevel = level; + auxprev = prev; + while (auxlevel) { + auxlevel--; + auxnext = sl_level_get(auxprev, auxlevel); + cmpval = 1; + while (auxnext && (cmpval = cmpfn(auxnext, item)) < 0) { + auxprev = auxnext; + auxnext = sl_level_get(auxprev, auxlevel); + } + if (cmpval == 0) + return auxnext; + }; + + head->count++; + memset(item, 0, sizeof(*item)); + if (newlevel > SKIPLIST_EMBED) { + struct sskip_overflow *oflow; + oflow = XMALLOC(MTYPE_SKIPLIST_OFLOW, sizeof(void *) + * (newlevel - SKIPLIST_OVERFLOW)); + item->next[SKIPLIST_OVERFLOW] = (struct sskip_item *) + ((uintptr_t)oflow | 1); + } + + sl_level_set(item, level, next); + sl_level_set(prev, level, item); + /* level is now 0-counted and < newlevel*/ + while (level) { + level--; + next = sl_level_get(prev, level); + while (next && cmpfn(next, item) < 0) { + prev = next; + next = sl_level_get(prev, level); + } + + sl_level_set(item, level, next); + sl_level_set(prev, level, item); + }; + return NULL; +} + +/* NOTE: level counting below is 1-based since that makes the code simpler! */ + +const struct sskip_item *typesafe_skiplist_find( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)) +{ + size_t level = SKIPLIST_MAXDEPTH; + const struct sskip_item *prev = &head->hitem, *next; + int cmpval; + + while (level) { + next = sl_level_get(prev, level - 1); + if (!next) { + level--; + continue; + } + cmpval = cmpfn(next, item); + if (cmpval < 0) { + prev = next; + continue; + } + if (cmpval == 0) + return next; + level--; + } + return NULL; +} + +const struct sskip_item *typesafe_skiplist_find_gteq( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)) +{ + size_t level = SKIPLIST_MAXDEPTH; + const struct sskip_item *prev = &head->hitem, *next; + int cmpval; + + while (level) { + next = sl_level_get(prev, level - 1); + if (!next) { + level--; + continue; + } + cmpval = cmpfn(next, item); + if (cmpval < 0) { + prev = next; + continue; + } + if (cmpval == 0) + return next; + level--; + } + return next; +} + +const struct sskip_item *typesafe_skiplist_find_lt( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)) +{ + size_t level = SKIPLIST_MAXDEPTH; + const struct sskip_item *prev = &head->hitem, *next, *best = NULL; + int cmpval; + + while (level) { + next = sl_level_get(prev, level - 1); + if (!next) { + level--; + continue; + } + cmpval = cmpfn(next, item); + if (cmpval < 0) { + best = prev = next; + continue; + } + level--; + } + return best; +} + +struct sskip_item *typesafe_skiplist_del( + struct sskip_head *head, struct sskip_item *item, + int (*cmpfn)(const struct sskip_item *a, const struct sskip_item *b)) +{ + size_t level = SKIPLIST_MAXDEPTH; + struct sskip_item *prev = &head->hitem, *next; + int cmpval; + bool found = false; + + while (level) { + next = sl_level_get(prev, level - 1); + if (!next) { + level--; + continue; + } + if (next == item) { + sl_level_set(prev, level - 1, + sl_level_get(item, level - 1)); + level--; + found = true; + continue; + } + cmpval = cmpfn(next, item); + if (cmpval < 0) { + prev = next; + continue; + } + level--; + } + + if (!found) + return NULL; + + /* TBD: assert when trying to remove non-existing item? */ + head->count--; + + if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) { + uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; + ptrval &= UINTPTR_MAX - 3; + struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; + XFREE(MTYPE_SKIPLIST_OFLOW, oflow); + } + memset(item, 0, sizeof(*item)); + + return item; +} + +struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head) +{ + size_t level = SKIPLIST_MAXDEPTH; + struct sskip_item *prev = &head->hitem, *next, *item; + + item = sl_level_get(prev, 0); + if (!item) + return NULL; + + do { + level--; + + next = sl_level_get(prev, level); + if (next != item) + continue; + + sl_level_set(prev, level, sl_level_get(item, level)); + } while (level); + + head->count--; + + if ((uintptr_t)item->next[SKIPLIST_OVERFLOW] & 1) { + uintptr_t ptrval = (uintptr_t)item->next[SKIPLIST_OVERFLOW]; + ptrval &= UINTPTR_MAX - 3; + struct sskip_overflow *oflow = (struct sskip_overflow *)ptrval; + XFREE(MTYPE_SKIPLIST_OFLOW, oflow); + } + memset(item, 0, sizeof(*item)); + + return item; +} + +/* heap */ + +#if 0 +static void heap_consistency_check(struct heap_head *head, + int (*cmpfn)(const struct heap_item *a, + const struct heap_item *b), + uint32_t pos) +{ + uint32_t rghtpos = pos + 1; + uint32_t downpos = HEAP_NARY * (pos + 1); + + if (pos + 1 > ~0U / HEAP_NARY) + downpos = ~0U; + + if ((pos & (HEAP_NARY - 1)) != HEAP_NARY - 1 && rghtpos < head->count) { + assert(cmpfn(head->array[rghtpos], head->array[pos]) >= 0); + heap_consistency_check(head, cmpfn, rghtpos); + } + if (downpos < head->count) { + assert(cmpfn(head->array[downpos], head->array[pos]) >= 0); + heap_consistency_check(head, cmpfn, downpos); + } +} +#else +#define heap_consistency_check(head, cmpfn, pos) +#endif + +void typesafe_heap_resize(struct heap_head *head, bool grow) +{ + uint32_t newsize; + + if (grow) { + newsize = head->arraysz; + if (newsize <= 36) + newsize = 72; + else if (newsize < 262144) + newsize += newsize / 2; + else if (newsize < 0xaaaa0000) + newsize += newsize / 3; + else + assert(!newsize); + } else if (head->count > 0) { + newsize = head->count; + } else { + XFREE(MTYPE_HEAP_ARRAY, head->array); + head->arraysz = 0; + return; + } + + newsize += HEAP_NARY - 1; + newsize &= ~(HEAP_NARY - 1); + if (newsize == head->arraysz) + return; + + head->array = XREALLOC(MTYPE_HEAP_ARRAY, head->array, + newsize * sizeof(struct heap_item *)); + head->arraysz = newsize; +} + +void typesafe_heap_pushdown(struct heap_head *head, uint32_t pos, + struct heap_item *item, + int (*cmpfn)(const struct heap_item *a, + const struct heap_item *b)) +{ + uint32_t rghtpos, downpos, moveto; + + while (1) { + /* rghtpos: neighbor to the "right", inside block of NARY. + * may be invalid if last in block, check nary_last() + * downpos: first neighbor in the "downwards" block further + * away from the root + */ + rghtpos = pos + 1; + + /* make sure we can use the full 4G items */ + downpos = HEAP_NARY * (pos + 1); + if (pos + 1 > ~0U / HEAP_NARY) + /* multiplication overflowed. ~0U is guaranteed + * to be an invalid index; size limit is enforced in + * resize() + */ + downpos = ~0U; + + /* only used on break */ + moveto = pos; + +#define nary_last(x) (((x) & (HEAP_NARY - 1)) == HEAP_NARY - 1) + if (downpos >= head->count + || cmpfn(head->array[downpos], item) >= 0) { + /* not moving down; either at end or down is >= item */ + if (nary_last(pos) || rghtpos >= head->count + || cmpfn(head->array[rghtpos], item) >= 0) + /* not moving right either - got our spot */ + break; + + moveto = rghtpos; + + /* else: downpos is valid and < item. choose between down + * or right (if the latter is an option) */ + } else if (nary_last(pos) || cmpfn(head->array[rghtpos], + head->array[downpos]) >= 0) + moveto = downpos; + else + moveto = rghtpos; +#undef nary_last + + head->array[pos] = head->array[moveto]; + head->array[pos]->index = pos; + pos = moveto; + } + + head->array[moveto] = item; + item->index = moveto; + + heap_consistency_check(head, cmpfn, 0); +} + +void typesafe_heap_pullup(struct heap_head *head, uint32_t pos, + struct heap_item *item, + int (*cmpfn)(const struct heap_item *a, + const struct heap_item *b)) +{ + uint32_t moveto; + + while (pos != 0) { + if ((pos & (HEAP_NARY - 1)) == 0) + moveto = pos / HEAP_NARY - 1; + else + moveto = pos - 1; + + if (cmpfn(head->array[moveto], item) <= 0) + break; + + head->array[pos] = head->array[moveto]; + head->array[pos]->index = pos; + + pos = moveto; + } + + head->array[pos] = item; + item->index = pos; + + heap_consistency_check(head, cmpfn, 0); +} diff --git a/lib/typesafe.h b/lib/typesafe.h new file mode 100644 index 0000000..fc02804 --- /dev/null +++ b/lib/typesafe.h @@ -0,0 +1,1178 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016-2019 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_TYPESAFE_H +#define _FRR_TYPESAFE_H + +#ifndef _TYPESAFE_EXPAND_MACROS +#include +#include +#include +#include "compiler.h" +#endif /* _TYPESAFE_EXPAND_MACROS */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* generic macros for all list-like types */ + +/* to iterate using the const variants of the functions, append "_const" to + * the name of the container, e.g. "frr_each (my_list, head, item)" becomes + * "frr_each (my_list_const, head, item)" + */ + +#define frr_each(prefix, head, item) \ + for (item = prefix##_first(head); item; \ + item = prefix##_next(head, item)) +#define frr_each_safe(prefix, head, item) \ + for (typeof(prefix##_next_safe(head, NULL)) prefix##_safe = \ + prefix##_next_safe(head, \ + (item = prefix##_first(head))); \ + item; \ + item = prefix##_safe, \ + prefix##_safe = prefix##_next_safe(head, prefix##_safe)) +#define frr_each_from(prefix, head, item, from) \ + for (item = from, from = prefix##_next_safe(head, item); \ + item; \ + item = from, from = prefix##_next_safe(head, from)) + +/* reverse direction, only supported by a few containers */ + +#define frr_rev_each(prefix, head, item) \ + for (item = prefix##_last(head); item; \ + item = prefix##_prev(head, item)) +#define frr_rev_each_safe(prefix, head, item) \ + for (typeof(prefix##_prev_safe(head, NULL)) prefix##_safe = \ + prefix##_prev_safe(head, \ + (item = prefix##_last(head))); \ + item; \ + item = prefix##_safe, \ + prefix##_safe = prefix##_prev_safe(head, prefix##_safe)) +#define frr_rev_each_from(prefix, head, item, from) \ + for (item = from, from = prefix##_prev_safe(head, item); \ + item; \ + item = from, from = prefix##_prev_safe(head, from)) + +/* non-const variants. these wrappers are the same for all the types, so + * bundle them together here. + */ +#define TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _first(struct prefix##_head *h) \ +{ \ + return (type *)prefix ## _const_first(h); \ +} \ +macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \ +{ \ + return (type *)prefix ## _const_next(h, item); \ +} \ +/* ... */ +#define TYPESAFE_LAST_PREV(prefix, type) \ +macro_pure type *prefix ## _last(struct prefix##_head *h) \ +{ \ + return (type *)prefix ## _const_last(h); \ +} \ +macro_pure type *prefix ## _prev(struct prefix##_head *h, type *item) \ +{ \ + return (type *)prefix ## _const_prev(h, item); \ +} \ +/* ... */ +#define TYPESAFE_FIND(prefix, type) \ +macro_inline type *prefix ## _find(struct prefix##_head *h, \ + const type *item) \ +{ \ + return (type *)prefix ## _const_find(h, item); \ +} \ +/* ... */ +#define TYPESAFE_FIND_CMP(prefix, type) \ +macro_inline type *prefix ## _find_lt(struct prefix##_head *h, \ + const type *item) \ +{ \ + return (type *)prefix ## _const_find_lt(h, item); \ +} \ +macro_inline type *prefix ## _find_gteq(struct prefix##_head *h, \ + const type *item) \ +{ \ + return (type *)prefix ## _const_find_gteq(h, item); \ +} \ +/* ... */ + +/* *_member via find - when there is no better membership check than find() */ +#define TYPESAFE_MEMBER_VIA_FIND(prefix, type) \ +macro_inline bool prefix ## _member(struct prefix##_head *h, \ + const type *item) \ +{ \ + return item == prefix ## _const_find(h, item); \ +} \ +/* ... */ + +/* *_member via find_gteq - same for non-unique containers */ +#define TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \ +macro_inline bool prefix ## _member(struct prefix##_head *h, \ + const type *item) \ +{ \ + const type *iter; \ + for (iter = prefix ## _const_find_gteq(h, item); iter; \ + iter = prefix ## _const_next(h, iter)) { \ + if (iter == item) \ + return true; \ + if (cmpfn(iter, item) > 0) \ + break; \ + } \ + return false; \ +} \ +/* ... */ + +/* SWAP_ALL_SIMPLE = for containers where the items don't point back to the + * head *AND* the head doesn't point to itself (= everything except LIST, + * DLIST and SKIPLIST), just switch out the entire head + */ +#define TYPESAFE_SWAP_ALL_SIMPLE(prefix) \ +macro_inline void prefix ## _swap_all(struct prefix##_head *a, \ + struct prefix##_head *b) \ +{ \ + struct prefix##_head tmp = *a; \ + *a = *b; \ + *b = tmp; \ +} \ +/* ... */ + +/* single-linked list, unsorted/arbitrary. + * can be used as queue with add_tail / pop + */ + +/* don't use these structs directly */ +struct slist_item { + struct slist_item *next; +}; + +struct slist_head { + struct slist_item *first, **last_next; + size_t count; +}; + +/* this replaces NULL as the value for ->next on the last item. */ +extern struct slist_item typesafe_slist_sentinel; +#define _SLIST_LAST &typesafe_slist_sentinel + +static inline void typesafe_list_add(struct slist_head *head, + struct slist_item **pos, struct slist_item *item) +{ + item->next = *pos; + *pos = item; + if (pos == head->last_next) + head->last_next = &item->next; + head->count++; +} + +extern bool typesafe_list_member(const struct slist_head *head, + const struct slist_item *item); + +/* use as: + * + * PREDECL_LIST(namelist); + * struct name { + * struct namelist_item nlitem; + * } + * DECLARE_LIST(namelist, struct name, nlitem); + */ +#define PREDECL_LIST(prefix) \ +struct prefix ## _head { struct slist_head sh; }; \ +struct prefix ## _item { struct slist_item si; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_LIST(var) { .sh = { .last_next = &var.sh.first, }, } + +#define DECLARE_LIST(prefix, type, field) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ + h->sh.first = _SLIST_LAST; \ + h->sh.last_next = &h->sh.first; \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ +{ \ + typesafe_list_add(&h->sh, &h->sh.first, &item->field.si); \ +} \ +macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ +{ \ + typesafe_list_add(&h->sh, h->sh.last_next, &item->field.si); \ +} \ +macro_inline void prefix ## _add_after(struct prefix##_head *h, \ + type *after, type *item) \ +{ \ + struct slist_item **nextp; \ + nextp = after ? &after->field.si.next : &h->sh.first; \ + typesafe_list_add(&h->sh, nextp, &item->field.si); \ +} \ +/* TODO: del_hint */ \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct slist_item **iter = &h->sh.first; \ + while (*iter != _SLIST_LAST && *iter != &item->field.si) \ + iter = &(*iter)->next; \ + if (*iter == _SLIST_LAST) \ + return NULL; \ + h->sh.count--; \ + *iter = item->field.si.next; \ + if (item->field.si.next == _SLIST_LAST) \ + h->sh.last_next = iter; \ + item->field.si.next = NULL; \ + return item; \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct slist_item *sitem = h->sh.first; \ + if (sitem == _SLIST_LAST) \ + return NULL; \ + h->sh.count--; \ + h->sh.first = sitem->next; \ + if (h->sh.first == _SLIST_LAST) \ + h->sh.last_next = &h->sh.first; \ + sitem->next = NULL; \ + return container_of(sitem, type, field.si); \ +} \ +macro_inline void prefix ## _swap_all(struct prefix##_head *a, \ + struct prefix##_head *b) \ +{ \ + struct prefix##_head tmp = *a; \ + *a = *b; \ + *b = tmp; \ + if (a->sh.last_next == &b->sh.first) \ + a->sh.last_next = &a->sh.first; \ + if (b->sh.last_next == &a->sh.first) \ + b->sh.last_next = &b->sh.first; \ +} \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + if (h->sh.first != _SLIST_LAST) \ + return container_of(h->sh.first, type, field.si); \ + return NULL; \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct slist_item *sitem = &item->field.si; \ + if (sitem->next != _SLIST_LAST) \ + return container_of(sitem->next, type, field.si); \ + return NULL; \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + struct slist_item *sitem; \ + if (!item) \ + return NULL; \ + sitem = &item->field.si; \ + if (sitem->next != _SLIST_LAST) \ + return container_of(sitem->next, type, field.si); \ + return NULL; \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->sh.count; \ +} \ +macro_pure bool prefix ## _anywhere(const type *item) \ +{ \ + return item->field.si.next != NULL; \ +} \ +macro_pure bool prefix ## _member(const struct prefix##_head *h, \ + const type *item) \ +{ \ + return typesafe_list_member(&h->sh, &item->field.si); \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +/* don't use these structs directly */ +struct dlist_item { + struct dlist_item *next; + struct dlist_item *prev; +}; + +struct dlist_head { + struct dlist_item hitem; + size_t count; +}; + +static inline void typesafe_dlist_add(struct dlist_head *head, + struct dlist_item *prev, struct dlist_item *item) +{ + /* SA on clang-11 thinks this can happen, but in reality -assuming no + * memory corruption- it can't. DLIST uses a "closed" ring, i.e. the + * termination at the end of the list is not NULL but rather a pointer + * back to the head. (This eliminates special-casing the first or last + * item.) + * + * Sadly, can't use assert() here since the libfrr assert / xref code + * uses typesafe lists itself... that said, if an assert tripped here + * we'd already be way past some memory corruption, so we might as + * well just take the SEGV. (In the presence of corruption, we'd see + * random SEGVs from places that make no sense at all anyway, an + * assert might actually be a red herring.) + * + * ("assume()" tells the compiler to produce code as if the condition + * will always hold; it doesn't have any actual effect here, it'll + * just SEGV out on "item->next->prev = item".) + */ + assume(prev->next != NULL); + + item->next = prev->next; + item->next->prev = item; + item->prev = prev; + prev->next = item; + head->count++; +} + +static inline void typesafe_dlist_swap_all(struct dlist_head *a, + struct dlist_head *b) +{ + struct dlist_head tmp = *a; + + a->count = b->count; + if (a->count) { + a->hitem.next = b->hitem.next; + a->hitem.prev = b->hitem.prev; + a->hitem.next->prev = &a->hitem; + a->hitem.prev->next = &a->hitem; + } else { + a->hitem.next = &a->hitem; + a->hitem.prev = &a->hitem; + } + + b->count = tmp.count; + if (b->count) { + b->hitem.next = tmp.hitem.next; + b->hitem.prev = tmp.hitem.prev; + b->hitem.next->prev = &b->hitem; + b->hitem.prev->next = &b->hitem; + } else { + b->hitem.next = &b->hitem; + b->hitem.prev = &b->hitem; + } +} + +extern bool typesafe_dlist_member(const struct dlist_head *head, + const struct dlist_item *item); + +/* double-linked list, for fast item deletion + */ +#define PREDECL_DLIST(prefix) \ +struct prefix ## _head { struct dlist_head dh; }; \ +struct prefix ## _item { struct dlist_item di; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_DLIST(var) { .dh = { \ + .hitem = { &var.dh.hitem, &var.dh.hitem }, }, } + +#define DECLARE_DLIST(prefix, type, field) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ + h->dh.hitem.prev = &h->dh.hitem; \ + h->dh.hitem.next = &h->dh.hitem; \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _add_head(struct prefix##_head *h, type *item) \ +{ \ + typesafe_dlist_add(&h->dh, &h->dh.hitem, &item->field.di); \ +} \ +macro_inline void prefix ## _add_tail(struct prefix##_head *h, type *item) \ +{ \ + typesafe_dlist_add(&h->dh, h->dh.hitem.prev, &item->field.di); \ +} \ +macro_inline void prefix ## _add_after(struct prefix##_head *h, \ + type *after, type *item) \ +{ \ + struct dlist_item *prev; \ + prev = after ? &after->field.di : &h->dh.hitem; \ + typesafe_dlist_add(&h->dh, prev, &item->field.di); \ +} \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct dlist_item *ditem = &item->field.di; \ + ditem->prev->next = ditem->next; \ + ditem->next->prev = ditem->prev; \ + h->dh.count--; \ + ditem->prev = ditem->next = NULL; \ + return item; \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct dlist_item *ditem = h->dh.hitem.next; \ + if (ditem == &h->dh.hitem) \ + return NULL; \ + ditem->prev->next = ditem->next; \ + ditem->next->prev = ditem->prev; \ + h->dh.count--; \ + ditem->prev = ditem->next = NULL; \ + return container_of(ditem, type, field.di); \ +} \ +macro_inline void prefix ## _swap_all(struct prefix##_head *a, \ + struct prefix##_head *b) \ +{ \ + typesafe_dlist_swap_all(&a->dh, &b->dh); \ +} \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + const struct dlist_item *ditem = h->dh.hitem.next; \ + if (ditem == &h->dh.hitem) \ + return NULL; \ + return container_of(ditem, type, field.di); \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct dlist_item *ditem = &item->field.di; \ + if (ditem->next == &h->dh.hitem) \ + return NULL; \ + return container_of(ditem->next, type, field.di); \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \ +{ \ + const struct dlist_item *ditem = h->dh.hitem.prev; \ + if (ditem == &h->dh.hitem) \ + return NULL; \ + return container_of(ditem, type, field.di); \ +} \ +macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct dlist_item *ditem = &item->field.di; \ + if (ditem->prev == &h->dh.hitem) \ + return NULL; \ + return container_of(ditem->prev, type, field.di); \ +} \ +TYPESAFE_LAST_PREV(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + if (!item) \ + return NULL; \ + return prefix ## _next(h, item); \ +} \ +macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \ +{ \ + if (!item) \ + return NULL; \ + return prefix ## _prev(h, item); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->dh.count; \ +} \ +macro_pure bool prefix ## _anywhere(const type *item) \ +{ \ + const struct dlist_item *ditem = &item->field.di; \ + return ditem->next && ditem->prev; \ +} \ +macro_pure bool prefix ## _member(const struct prefix##_head *h, \ + const type *item) \ +{ \ + return typesafe_dlist_member(&h->dh, &item->field.di); \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +/* note: heap currently caps out at 4G items */ + +#define HEAP_NARY 8U +typedef uint32_t heap_index_i; + +struct heap_item { + uint32_t index; +}; + +struct heap_head { + struct heap_item **array; + uint32_t arraysz, count; +}; + +#define HEAP_RESIZE_TRESH_UP(h) \ + (h->hh.count + 1 >= h->hh.arraysz) +#define HEAP_RESIZE_TRESH_DN(h) \ + (h->hh.count == 0 || \ + h->hh.arraysz - h->hh.count > (h->hh.count + 1024) / 2) + +#define PREDECL_HEAP(prefix) \ +struct prefix ## _head { struct heap_head hh; }; \ +struct prefix ## _item { struct heap_item hi; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_HEAP(var) { } + +#define DECLARE_HEAP(prefix, type, field, cmpfn) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + assert(h->hh.count == 0); \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline int prefix ## __cmp(const struct heap_item *a, \ + const struct heap_item *b) \ +{ \ + return cmpfn(container_of(a, type, field.hi), \ + container_of(b, type, field.hi)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + if (HEAP_RESIZE_TRESH_UP(h)) \ + typesafe_heap_resize(&h->hh, true); \ + typesafe_heap_pullup(&h->hh, h->hh.count, &item->field.hi, \ + prefix ## __cmp); \ + h->hh.count++; \ + return NULL; \ +} \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct heap_item *other; \ + uint32_t index = item->field.hi.index; \ + assert(h->hh.array[index] == &item->field.hi); \ + h->hh.count--; \ + other = h->hh.array[h->hh.count]; \ + if (cmpfn(container_of(other, type, field.hi), item) < 0) \ + typesafe_heap_pullup(&h->hh, index, other, prefix ## __cmp); \ + else \ + typesafe_heap_pushdown(&h->hh, index, other, prefix ## __cmp); \ + if (HEAP_RESIZE_TRESH_DN(h)) \ + typesafe_heap_resize(&h->hh, false); \ + return item; \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct heap_item *hitem, *other; \ + if (h->hh.count == 0) \ + return NULL; \ + hitem = h->hh.array[0]; \ + h->hh.count--; \ + other = h->hh.array[h->hh.count]; \ + typesafe_heap_pushdown(&h->hh, 0, other, prefix ## __cmp); \ + if (HEAP_RESIZE_TRESH_DN(h)) \ + typesafe_heap_resize(&h->hh, false); \ + return container_of(hitem, type, field.hi); \ +} \ +TYPESAFE_SWAP_ALL_SIMPLE(prefix) \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + if (h->hh.count == 0) \ + return NULL; \ + return container_of(h->hh.array[0], type, field.hi); \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + uint32_t idx = item->field.hi.index + 1; \ + if (idx >= h->hh.count) \ + return NULL; \ + return container_of(h->hh.array[idx], type, field.hi); \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + if (!item) \ + return NULL; \ + return prefix ## _next(h, item); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->hh.count; \ +} \ +macro_pure bool prefix ## _member(const struct prefix##_head *h, \ + const type *item) \ +{ \ + uint32_t idx = item->field.hi.index; \ + if (idx >= h->hh.count) \ + return false; \ + return h->hh.array[idx] == &item->field.hi; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +extern void typesafe_heap_resize(struct heap_head *head, bool grow); +extern void typesafe_heap_pushdown(struct heap_head *head, uint32_t index, + struct heap_item *item, + int (*cmpfn)(const struct heap_item *a, + const struct heap_item *b)); +extern void typesafe_heap_pullup(struct heap_head *head, uint32_t index, + struct heap_item *item, + int (*cmpfn)(const struct heap_item *a, + const struct heap_item *b)); + +/* single-linked list, sorted. + * can be used as priority queue with add / pop + */ + +/* don't use these structs directly */ +struct ssort_item { + struct ssort_item *next; +}; + +struct ssort_head { + struct ssort_item *first; + size_t count; +}; + +/* use as: + * + * PREDECL_SORTLIST(namelist) + * struct name { + * struct namelist_item nlitem; + * } + * DECLARE_SORTLIST(namelist, struct name, nlitem) + */ +#define _PREDECL_SORTLIST(prefix) \ +struct prefix ## _head { struct ssort_head sh; }; \ +struct prefix ## _item { struct ssort_item si; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_SORTLIST_UNIQ(var) { } +#define INIT_SORTLIST_NONUNIQ(var) { } + +#define PREDECL_SORTLIST_UNIQ(prefix) \ + _PREDECL_SORTLIST(prefix) +#define PREDECL_SORTLIST_NONUNIQ(prefix) \ + _PREDECL_SORTLIST(prefix) + +#define _DECLARE_SORTLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + struct ssort_item **np = &h->sh.first; \ + int c = 1; \ + while (*np && (c = cmpfn_uq( \ + container_of(*np, type, field.si), item)) < 0) \ + np = &(*np)->next; \ + if (c == 0) \ + return container_of(*np, type, field.si); \ + item->field.si.next = *np; \ + *np = &item->field.si; \ + h->sh.count++; \ + return NULL; \ +} \ +macro_inline const type *prefix ## _const_find_gteq( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct ssort_item *sitem = h->sh.first; \ + int cmpval = 0; \ + while (sitem && (cmpval = cmpfn_nuq( \ + container_of(sitem, type, field.si), item)) < 0) \ + sitem = sitem->next; \ + return container_of_null(sitem, type, field.si); \ +} \ +macro_inline const type *prefix ## _const_find_lt( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct ssort_item *prev = NULL, *sitem = h->sh.first; \ + int cmpval = 0; \ + while (sitem && (cmpval = cmpfn_nuq( \ + container_of(sitem, type, field.si), item)) < 0) \ + sitem = (prev = sitem)->next; \ + return container_of_null(prev, type, field.si); \ +} \ +TYPESAFE_FIND_CMP(prefix, type) \ +/* TODO: del_hint */ \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct ssort_item **iter = &h->sh.first; \ + while (*iter && *iter != &item->field.si) \ + iter = &(*iter)->next; \ + if (!*iter) \ + return NULL; \ + h->sh.count--; \ + *iter = item->field.si.next; \ + item->field.si.next = NULL; \ + return item; \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct ssort_item *sitem = h->sh.first; \ + if (!sitem) \ + return NULL; \ + h->sh.count--; \ + h->sh.first = sitem->next; \ + return container_of(sitem, type, field.si); \ +} \ +TYPESAFE_SWAP_ALL_SIMPLE(prefix) \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + return container_of_null(h->sh.first, type, field.si); \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct ssort_item *sitem = &item->field.si; \ + return container_of_null(sitem->next, type, field.si); \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + struct ssort_item *sitem; \ + if (!item) \ + return NULL; \ + sitem = &item->field.si; \ + return container_of_null(sitem->next, type, field.si); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->sh.count; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DECLARE_SORTLIST_UNIQ(prefix, type, field, cmpfn) \ + _DECLARE_SORTLIST(prefix, type, field, cmpfn, cmpfn); \ + \ +macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct ssort_item *sitem = h->sh.first; \ + int cmpval = 0; \ + while (sitem && (cmpval = cmpfn( \ + container_of(sitem, type, field.si), item)) < 0) \ + sitem = sitem->next; \ + if (!sitem || cmpval > 0) \ + return NULL; \ + return container_of(sitem, type, field.si); \ +} \ +TYPESAFE_FIND(prefix, type) \ +TYPESAFE_MEMBER_VIA_FIND(prefix, type) \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define DECLARE_SORTLIST_NONUNIQ(prefix, type, field, cmpfn) \ +macro_inline int _ ## prefix ## _cmp(const type *a, const type *b) \ +{ \ + int cmpval = cmpfn(a, b); \ + if (cmpval) \ + return cmpval; \ + if (a < b) \ + return -1; \ + if (a > b) \ + return 1; \ + return 0; \ +} \ + _DECLARE_SORTLIST(prefix, type, field, cmpfn, _ ## prefix ## _cmp); \ +TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \ +MACRO_REQUIRE_SEMICOLON() /* end */ + + +/* hash, "sorted" by hash value + */ + +/* don't use these structs directly */ +struct thash_item { + struct thash_item *next; + uint32_t hashval; +}; + +struct thash_head { + struct thash_item **entries; + uint32_t count; + + /* tabshift can be 0 if the hash table is empty and entries is NULL. + * otherwise it will always be 2 or larger because it contains + * the shift value *plus 1*. This is a trick to make HASH_SIZE return + * the correct value (with the >> 1) for tabshift == 0, without needing + * a conditional branch. + */ + uint8_t tabshift; + uint8_t minshift, maxshift; +}; + +#define _HASH_SIZE(tabshift) \ + ({ \ + assume((tabshift) <= 31); \ + (1U << (tabshift)) >> 1; \ + }) +#define HASH_SIZE(head) \ + _HASH_SIZE((head).tabshift) +#define _HASH_KEY(tabshift, val) \ + ({ \ + assume((tabshift) >= 2 && (tabshift) <= 31); \ + (val) >> (33 - (tabshift)); \ + }) +#define HASH_KEY(head, val) \ + _HASH_KEY((head).tabshift, val) +#define HASH_GROW_THRESHOLD(head) \ + ((head).count >= HASH_SIZE(head)) +#define HASH_SHRINK_THRESHOLD(head) \ + ((head).count <= (HASH_SIZE(head) - 1) / 2) + +extern void typesafe_hash_grow(struct thash_head *head); +extern void typesafe_hash_shrink(struct thash_head *head); + +/* use as: + * + * PREDECL_HASH(namelist) + * struct name { + * struct namelist_item nlitem; + * } + * DECLARE_HASH(namelist, struct name, nlitem, cmpfunc, hashfunc) + */ +#define PREDECL_HASH(prefix) \ +struct prefix ## _head { struct thash_head hh; }; \ +struct prefix ## _item { struct thash_item hi; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_HASH(var) { } + +#define DECLARE_HASH(prefix, type, field, cmpfn, hashfn) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + assert(h->hh.count == 0); \ + h->hh.minshift = 0; \ + typesafe_hash_shrink(&h->hh); \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + h->hh.count++; \ + if (!h->hh.tabshift || HASH_GROW_THRESHOLD(h->hh)) \ + typesafe_hash_grow(&h->hh); \ + \ + uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \ + item->field.hi.hashval = hval; \ + struct thash_item **np = &h->hh.entries[hbits]; \ + while (*np && (*np)->hashval < hval) \ + np = &(*np)->next; \ + while (*np && (*np)->hashval == hval) { \ + if (cmpfn(container_of(*np, type, field.hi), item) == 0) { \ + h->hh.count--; \ + return container_of(*np, type, field.hi); \ + } \ + np = &(*np)->next; \ + } \ + item->field.hi.next = *np; \ + *np = &item->field.hi; \ + return NULL; \ +} \ +macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \ + const type *item) \ +{ \ + if (!h->hh.tabshift) \ + return NULL; \ + uint32_t hval = hashfn(item), hbits = HASH_KEY(h->hh, hval); \ + const struct thash_item *hitem = h->hh.entries[hbits]; \ + while (hitem && hitem->hashval < hval) \ + hitem = hitem->next; \ + while (hitem && hitem->hashval == hval) { \ + if (!cmpfn(container_of(hitem, type, field.hi), item)) \ + return container_of(hitem, type, field.hi); \ + hitem = hitem->next; \ + } \ + return NULL; \ +} \ +TYPESAFE_FIND(prefix, type) \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + if (!h->hh.tabshift) \ + return NULL; \ + uint32_t hval = item->field.hi.hashval, hbits = HASH_KEY(h->hh, hval); \ + struct thash_item **np = &h->hh.entries[hbits]; \ + while (*np && (*np)->hashval < hval) \ + np = &(*np)->next; \ + while (*np && *np != &item->field.hi && (*np)->hashval == hval) \ + np = &(*np)->next; \ + if (*np != &item->field.hi) \ + return NULL; \ + *np = item->field.hi.next; \ + item->field.hi.next = NULL; \ + h->hh.count--; \ + if (HASH_SHRINK_THRESHOLD(h->hh)) \ + typesafe_hash_shrink(&h->hh); \ + return item; \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + uint32_t i; \ + for (i = 0; i < HASH_SIZE(h->hh); i++) \ + if (h->hh.entries[i]) { \ + struct thash_item *hitem = h->hh.entries[i]; \ + h->hh.entries[i] = hitem->next; \ + h->hh.count--; \ + hitem->next = NULL; \ + if (HASH_SHRINK_THRESHOLD(h->hh)) \ + typesafe_hash_shrink(&h->hh); \ + return container_of(hitem, type, field.hi); \ + } \ + return NULL; \ +} \ +TYPESAFE_SWAP_ALL_SIMPLE(prefix) \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + uint32_t i; \ + for (i = 0; i < HASH_SIZE(h->hh); i++) \ + if (h->hh.entries[i]) \ + return container_of(h->hh.entries[i], type, field.hi); \ + return NULL; \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct thash_item *hitem = &item->field.hi; \ + if (hitem->next) \ + return container_of(hitem->next, type, field.hi); \ + uint32_t i = HASH_KEY(h->hh, hitem->hashval) + 1; \ + for (; i < HASH_SIZE(h->hh); i++) \ + if (h->hh.entries[i]) \ + return container_of(h->hh.entries[i], type, field.hi); \ + return NULL; \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + if (!item) \ + return NULL; \ + return prefix ## _next(h, item); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->hh.count; \ +} \ +macro_pure bool prefix ## _member(const struct prefix##_head *h, \ + const type *item) \ +{ \ + if (!h->hh.tabshift) \ + return NULL; \ + uint32_t hval = item->field.hi.hashval, hbits = HASH_KEY(h->hh, hval); \ + const struct thash_item *hitem = h->hh.entries[hbits]; \ + while (hitem && hitem->hashval < hval) \ + hitem = hitem->next; \ + for (hitem = h->hh.entries[hbits]; hitem && hitem->hashval <= hval; \ + hitem = hitem->next) \ + if (hitem == &item->field.hi) \ + return true; \ + return false; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +/* skiplist, sorted. + * can be used as priority queue with add / pop + */ + +/* don't use these structs directly */ +#define SKIPLIST_MAXDEPTH 16 +#define SKIPLIST_EMBED 4 +#define SKIPLIST_OVERFLOW (SKIPLIST_EMBED - 1) + +struct sskip_item { + struct sskip_item *next[SKIPLIST_EMBED]; +}; + +struct sskip_overflow { + struct sskip_item *next[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW]; +}; + +struct sskip_head { + struct sskip_item hitem; + struct sskip_item *overflow[SKIPLIST_MAXDEPTH - SKIPLIST_OVERFLOW]; + size_t count; +}; + +/* use as: + * + * PREDECL_SKIPLIST(namelist) + * struct name { + * struct namelist_item nlitem; + * } + * DECLARE_SKIPLIST(namelist, struct name, nlitem, cmpfunc) + */ +#define _PREDECL_SKIPLIST(prefix) \ +struct prefix ## _head { struct sskip_head sh; }; \ +struct prefix ## _item { struct sskip_item si; }; \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define INIT_SKIPLIST_UNIQ(var) { } +#define INIT_SKIPLIST_NONUNIQ(var) { } + +#define _DECLARE_SKIPLIST(prefix, type, field, cmpfn_nuq, cmpfn_uq) \ + \ +macro_inline void prefix ## _init(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ + h->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \ + ((uintptr_t)h->sh.overflow | 1); \ +} \ +macro_inline void prefix ## _fini(struct prefix##_head *h) \ +{ \ + memset(h, 0, sizeof(*h)); \ +} \ +macro_inline type *prefix ## _add(struct prefix##_head *h, type *item) \ +{ \ + struct sskip_item *si; \ + si = typesafe_skiplist_add(&h->sh, &item->field.si, cmpfn_uq); \ + return container_of_null(si, type, field.si); \ +} \ +macro_inline const type *prefix ## _const_find_gteq( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct sskip_item *sitem = typesafe_skiplist_find_gteq(&h->sh, \ + &item->field.si, cmpfn_nuq); \ + return container_of_null(sitem, type, field.si); \ +} \ +macro_inline const type *prefix ## _const_find_lt( \ + const struct prefix##_head *h, const type *item) \ +{ \ + const struct sskip_item *sitem = typesafe_skiplist_find_lt(&h->sh, \ + &item->field.si, cmpfn_nuq); \ + return container_of_null(sitem, type, field.si); \ +} \ +TYPESAFE_FIND_CMP(prefix, type) \ +macro_inline type *prefix ## _del(struct prefix##_head *h, type *item) \ +{ \ + struct sskip_item *sitem = typesafe_skiplist_del(&h->sh, \ + &item->field.si, cmpfn_uq); \ + return container_of_null(sitem, type, field.si); \ +} \ +macro_inline type *prefix ## _pop(struct prefix##_head *h) \ +{ \ + struct sskip_item *sitem = typesafe_skiplist_pop(&h->sh); \ + return container_of_null(sitem, type, field.si); \ +} \ +macro_inline void prefix ## _swap_all(struct prefix##_head *a, \ + struct prefix##_head *b) \ +{ \ + struct prefix##_head tmp = *a; \ + *a = *b; \ + *b = tmp; \ + a->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \ + ((uintptr_t)a->sh.overflow | 1); \ + b->sh.hitem.next[SKIPLIST_OVERFLOW] = (struct sskip_item *) \ + ((uintptr_t)b->sh.overflow | 1); \ +} \ +macro_pure const type *prefix ## _const_first(const struct prefix##_head *h) \ +{ \ + const struct sskip_item *first = h->sh.hitem.next[0]; \ + return container_of_null(first, type, field.si); \ +} \ +macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct sskip_item *next = item->field.si.next[0]; \ + return container_of_null(next, type, field.si); \ +} \ +TYPESAFE_FIRST_NEXT(prefix, type) \ +macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ +{ \ + struct sskip_item *next; \ + next = item ? item->field.si.next[0] : NULL; \ + return container_of_null(next, type, field.si); \ +} \ +macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ +{ \ + return h->sh.count; \ +} \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_SKIPLIST_UNIQ(prefix) \ + _PREDECL_SKIPLIST(prefix) +#define DECLARE_SKIPLIST_UNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct sskip_item *a, \ + const struct sskip_item *b) \ +{ \ + return cmpfn(container_of(a, type, field.si), \ + container_of(b, type, field.si)); \ +} \ +macro_inline const type *prefix ## _const_find(const struct prefix##_head *h, \ + const type *item) \ +{ \ + const struct sskip_item *sitem = typesafe_skiplist_find(&h->sh, \ + &item->field.si, &prefix ## __cmp); \ + return container_of_null(sitem, type, field.si); \ +} \ +TYPESAFE_FIND(prefix, type) \ +TYPESAFE_MEMBER_VIA_FIND(prefix, type) \ + \ +_DECLARE_SKIPLIST(prefix, type, field, \ + prefix ## __cmp, prefix ## __cmp); \ +MACRO_REQUIRE_SEMICOLON() /* end */ + +#define PREDECL_SKIPLIST_NONUNIQ(prefix) \ + _PREDECL_SKIPLIST(prefix) +#define DECLARE_SKIPLIST_NONUNIQ(prefix, type, field, cmpfn) \ + \ +macro_inline int prefix ## __cmp(const struct sskip_item *a, \ + const struct sskip_item *b) \ +{ \ + return cmpfn(container_of(a, type, field.si), \ + container_of(b, type, field.si)); \ +} \ +macro_inline int prefix ## __cmp_uq(const struct sskip_item *a, \ + const struct sskip_item *b) \ +{ \ + int cmpval = cmpfn(container_of(a, type, field.si), \ + container_of(b, type, field.si)); \ + if (cmpval) \ + return cmpval; \ + if (a < b) \ + return -1; \ + if (a > b) \ + return 1; \ + return 0; \ +} \ + \ +_DECLARE_SKIPLIST(prefix, type, field, \ + prefix ## __cmp, prefix ## __cmp_uq); \ +TYPESAFE_MEMBER_VIA_FIND_GTEQ(prefix, type, cmpfn) \ +MACRO_REQUIRE_SEMICOLON() /* end */ + + +extern struct sskip_item *typesafe_skiplist_add(struct sskip_head *head, + struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)); +extern const struct sskip_item *typesafe_skiplist_find( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)); +extern const struct sskip_item *typesafe_skiplist_find_gteq( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)); +extern const struct sskip_item *typesafe_skiplist_find_lt( + const struct sskip_head *head, + const struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)); +extern struct sskip_item *typesafe_skiplist_del( + struct sskip_head *head, struct sskip_item *item, int (*cmpfn)( + const struct sskip_item *a, + const struct sskip_item *b)); +extern struct sskip_item *typesafe_skiplist_pop(struct sskip_head *head); + +#ifdef __cplusplus +} +#endif + +/* this needs to stay at the end because both files include each other. + * the resolved order is typesafe.h before typerb.h + */ +#include "typerb.h" + +#endif /* _FRR_TYPESAFE_H */ diff --git a/lib/vector.c b/lib/vector.c new file mode 100644 index 0000000..60d3831 --- /dev/null +++ b/lib/vector.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Generic vector interface routine + * Copyright (C) 1997 Kunihiro Ishiguro + */ + +#include +#include + +#include "vector.h" +#include "memory.h" + +DEFINE_MTYPE_STATIC(LIB, VECTOR, "Vector"); +DEFINE_MTYPE_STATIC(LIB, VECTOR_INDEX, "Vector index"); + +/* Initialize vector : allocate memory and return vector. */ +vector vector_init(unsigned int size) +{ + vector v = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector)); + + /* allocate at least one slot */ + if (size == 0) + size = 1; + + v->alloced = size; + v->active = 0; + v->count = 0; + v->index = XCALLOC(MTYPE_VECTOR_INDEX, sizeof(void *) * size); + return v; +} + +void vector_free(vector v) +{ + XFREE(MTYPE_VECTOR_INDEX, v->index); + XFREE(MTYPE_VECTOR, v); +} + +vector vector_copy(vector v) +{ + unsigned int size; + vector new = XCALLOC(MTYPE_VECTOR, sizeof(struct _vector)); + + new->active = v->active; + new->alloced = v->alloced; + new->count = v->count; + + size = sizeof(void *) * (v->alloced); + new->index = XCALLOC(MTYPE_VECTOR_INDEX, size); + memcpy(new->index, v->index, size); + + return new; +} + +/* Check assigned index, and if it runs short double index pointer */ +void vector_ensure(vector v, unsigned int num) +{ + if (v->alloced > num) + return; + + v->index = XREALLOC(MTYPE_VECTOR_INDEX, v->index, + sizeof(void *) * (v->alloced * 2)); + memset(&v->index[v->alloced], 0, sizeof(void *) * v->alloced); + v->alloced *= 2; + + if (v->alloced <= num) + vector_ensure(v, num); +} + +/* This function only returns next empty slot index. It dose not mean + the slot's index memory is assigned, please call vector_ensure() + after calling this function. */ +int vector_empty_slot(vector v) +{ + unsigned int i; + + if (v->active == v->count) + return v->active; + + if (v->active == 0) + return 0; + + for (i = 0; i < v->active; i++) + if (v->index[i] == 0) + return i; + + return i; +} + +/* Set value to the smallest empty slot. */ +int vector_set(vector v, void *val) +{ + unsigned int i; + + i = vector_empty_slot(v); + vector_ensure(v, i); + + if (v->index[i]) + v->count--; + if (val) + v->count++; + v->index[i] = val; + + if (v->active <= i) + v->active = i + 1; + + return i; +} + +/* Set value to specified index slot. */ +int vector_set_index(vector v, unsigned int i, void *val) +{ + vector_ensure(v, i); + + if (v->index[i]) + v->count--; + if (val) + v->count++; + v->index[i] = val; + + if (v->active <= i) + v->active = i + 1; + + return i; +} + +/* Look up vector. */ +void *vector_lookup(vector v, unsigned int i) +{ + if (i >= v->active) + return NULL; + return v->index[i]; +} + +/* Lookup vector, ensure it. */ +void *vector_lookup_ensure(vector v, unsigned int i) +{ + vector_ensure(v, i); + return v->index[i]; +} + +/* Unset value at specified index slot. */ +void vector_unset(vector v, unsigned int i) +{ + if (i >= v->alloced) + return; + + if (v->index[i]) + v->count--; + + v->index[i] = NULL; + + if (i + 1 == v->active) { + v->active--; + while (i && v->index[--i] == NULL && v->active--) + ; /* Is this ugly ? */ + } +} + +void vector_remove(vector v, unsigned int ix) +{ + if (ix >= v->active) + return; + + if (v->index[ix]) + v->count--; + + int n = (--v->active) - ix; + + memmove(&v->index[ix], &v->index[ix + 1], n * sizeof(void *)); + v->index[v->active] = NULL; +} + +void vector_compact(vector v) +{ + for (unsigned int i = 0; i < vector_active(v); ++i) { + if (vector_slot(v, i) == NULL) { + vector_remove(v, i); + --i; + } + } +} + +void vector_unset_value(vector v, void *val) +{ + size_t i; + + for (i = 0; i < v->active; i++) + if (v->index[i] == val) { + v->index[i] = NULL; + v->count--; + break; + } + + if (i + 1 == v->active) + do + v->active--; + while (i && v->index[--i] == NULL); +} + +void vector_to_array(vector v, void ***dest, int *argc) +{ + *dest = XCALLOC(MTYPE_TMP, sizeof(void *) * v->active); + memcpy(*dest, v->index, sizeof(void *) * v->active); + *argc = v->active; +} + +vector array_to_vector(void **src, int argc) +{ + vector v = vector_init(VECTOR_MIN_SIZE); + + for (int i = 0; i < argc; i++) + vector_set_index(v, i, src[i]); + return v; +} diff --git a/lib/vector.h b/lib/vector.h new file mode 100644 index 0000000..fcbc132 --- /dev/null +++ b/lib/vector.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic vector interface header. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_VECTOR_H +#define _ZEBRA_VECTOR_H + +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* struct for vector */ +struct _vector { + unsigned int active; /* number of active slots */ + unsigned int alloced; /* number of allocated slot */ + unsigned int count; + void **index; /* index to data */ +}; +typedef struct _vector *vector; + +#define VECTOR_MIN_SIZE 1 + +/* (Sometimes) usefull macros. This macro convert index expression to + array expression. */ +/* Reference slot at given index, caller must ensure slot is active */ +#define vector_slot(V,I) ((V)->index[(I)]) +/* Number of active slots. + * Note that this differs from vector_count() as it the count returned + * will include any empty slots + */ +#define vector_active(V) ((V)->active) + +/* Prototypes. */ +extern vector vector_init(unsigned int size); +extern void vector_ensure(vector v, unsigned int num); +extern int vector_empty_slot(vector v); +extern int vector_set(vector v, void *val); +extern int vector_set_index(vector v, unsigned int i, void *val); +extern void vector_unset(vector v, unsigned int i); +extern void vector_unset_value(vector v, void *val); +extern void vector_remove(vector v, unsigned int ix); +extern void vector_compact(vector v); + +static inline unsigned int vector_count(vector v) +{ + return v->count; +} + +extern void vector_free(vector v); +extern vector vector_copy(vector v); + +extern void *vector_lookup(vector, unsigned int); +extern void *vector_lookup_ensure(vector, unsigned int); +extern void vector_to_array(vector v, void ***dest, int *argc); +extern vector array_to_vector(void **src, int argc); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_VECTOR_H */ diff --git a/lib/version.h.in b/lib/version.h.in new file mode 100644 index 0000000..7c31379 --- /dev/null +++ b/lib/version.h.in @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @configure_input@ + * + * Quagga version + * Copyright (C) 1997, 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_VERSION_H +#define _ZEBRA_VERSION_H + +#ifdef GIT_VERSION +#include "lib/gitversion.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef GIT_SUFFIX +#define GIT_SUFFIX "" +#endif +#ifndef GIT_INFO +#define GIT_INFO "" +#endif + +#define FRR_PAM_NAME "@PACKAGE_NAME@" +#define FRR_SMUX_NAME "@PACKAGE_NAME@" +#define FRR_PTM_NAME "@PACKAGE_NAME@" + +#define FRR_FULL_NAME "FRRouting" +#define FRR_VERSION "@PACKAGE_VERSION@" GIT_SUFFIX +#define FRR_VER_SHORT "@PACKAGE_VERSION@" +#define FRR_BUG_ADDRESS "@PACKAGE_BUGREPORT@" +#define FRR_COPYRIGHT "Copyright 1996-2005 Kunihiro Ishiguro, et al." +#define FRR_CONFIG_ARGS "@CONFIG_ARGS@" + +#define FRR_DEFAULT_MOTD \ + "\n" \ + "Hello, this is " FRR_FULL_NAME " (version " FRR_VERSION ").\n" \ + FRR_COPYRIGHT "\n" \ + GIT_INFO "\n" + +pid_t pid_output (const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_VERSION_H */ diff --git a/lib/vlan.h b/lib/vlan.h new file mode 100644 index 0000000..a601046 --- /dev/null +++ b/lib/vlan.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* VLAN (802.1q) common header. + * Copyright (C) 2016, 2017 Cumulus Networks, Inc. + */ + +#ifndef __VLAN_H__ +#define __VLAN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* VLAN Identifier */ +typedef uint16_t vlanid_t; +#define VLANID_MAX 4095 + +#ifdef __cplusplus +} +#endif + +#endif /* __VLAN_H__ */ diff --git a/lib/vrf.c b/lib/vrf.c new file mode 100644 index 0000000..31632a8 --- /dev/null +++ b/lib/vrf.c @@ -0,0 +1,1096 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VRF functions. + * Copyright (C) 2014 6WIND S.A. + */ + +#include +#include + +#include "if.h" +#include "vrf.h" +#include "vrf_int.h" +#include "prefix.h" +#include "table.h" +#include "log.h" +#include "memory.h" +#include "command.h" +#include "ns.h" +#include "privs.h" +#include "nexthop_group.h" +#include "lib_errors.h" +#include "northbound.h" +#include "northbound_cli.h" + +/* default VRF name value used when VRF backend is not NETNS */ +#define VRF_DEFAULT_NAME_INTERNAL "default" + +DEFINE_MTYPE_STATIC(LIB, VRF, "VRF"); +DEFINE_MTYPE_STATIC(LIB, VRF_BITMAP, "VRF bit-map"); + +DEFINE_QOBJ_TYPE(vrf); + +static __inline int vrf_id_compare(const struct vrf *, const struct vrf *); +static __inline int vrf_name_compare(const struct vrf *, const struct vrf *); + +RB_GENERATE(vrf_id_head, vrf, id_entry, vrf_id_compare); +RB_GENERATE(vrf_name_head, vrf, name_entry, vrf_name_compare); + +struct vrf_id_head vrfs_by_id = RB_INITIALIZER(&vrfs_by_id); +struct vrf_name_head vrfs_by_name = RB_INITIALIZER(&vrfs_by_name); + +static int vrf_backend; +static int vrf_backend_configured; +static char vrf_default_name[VRF_NAMSIZ] = VRF_DEFAULT_NAME_INTERNAL; + +/* + * Turn on/off debug code + * for vrf. + */ +static int debug_vrf = 0; + +/* Holding VRF hooks */ +static struct vrf_master { + int (*vrf_new_hook)(struct vrf *); + int (*vrf_delete_hook)(struct vrf *); + int (*vrf_enable_hook)(struct vrf *); + int (*vrf_disable_hook)(struct vrf *); +} vrf_master = { + 0, +}; + +static int vrf_is_enabled(struct vrf *vrf); + +/* VRF list existance check by name. */ +struct vrf *vrf_lookup_by_name(const char *name) +{ + struct vrf vrf; + strlcpy(vrf.name, name, sizeof(vrf.name)); + return (RB_FIND(vrf_name_head, &vrfs_by_name, &vrf)); +} + +static __inline int vrf_id_compare(const struct vrf *a, const struct vrf *b) +{ + return (a->vrf_id - b->vrf_id); +} + +static int vrf_name_compare(const struct vrf *a, const struct vrf *b) +{ + return strcmp(a->name, b->name); +} + +int vrf_switch_to_netns(vrf_id_t vrf_id) +{ + char *name; + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + /* VRF is default VRF. silently ignore */ + if (!vrf || vrf->vrf_id == VRF_DEFAULT) + return 1; /* 1 = default */ + /* VRF has no NETNS backend. silently ignore */ + if (vrf->data.l.netns_name[0] == '\0') + return 2; /* 2 = no netns */ + name = ns_netns_pathname(NULL, vrf->data.l.netns_name); + if (debug_vrf) + zlog_debug("VRF_SWITCH: %s(%u)", name, vrf->vrf_id); + return ns_switch_to_netns(name); +} + +int vrf_switchback_to_initial(void) +{ + int ret = ns_switchback_to_initial(); + + if (ret == 0 && debug_vrf) + zlog_debug("VRF_SWITCHBACK"); + return ret; +} + +/* Get a VRF. If not found, create one. + * Arg: + * name - The name of the vrf. May be NULL if unknown. + * vrf_id - The vrf_id of the vrf. May be VRF_UNKNOWN if unknown + * Description: Please note that this routine can be called with just the name + * and 0 vrf-id + */ +struct vrf *vrf_get(vrf_id_t vrf_id, const char *name) +{ + struct vrf *vrf = NULL; + int new = 0; + + /* Nothing to see, move along here */ + if (!name && vrf_id == VRF_UNKNOWN) + return NULL; + + /* attempt to find already available VRF + */ + if (name) + vrf = vrf_lookup_by_name(name); + if (vrf && vrf_id != VRF_UNKNOWN + && vrf->vrf_id != VRF_UNKNOWN + && vrf->vrf_id != vrf_id) { + zlog_debug("VRF_GET: avoid %s creation(%u), same name exists (%u)", + name, vrf_id, vrf->vrf_id); + return NULL; + } + /* Try to find VRF both by ID and name */ + if (!vrf && vrf_id != VRF_UNKNOWN) + vrf = vrf_lookup_by_id(vrf_id); + + if (vrf == NULL) { + vrf = XCALLOC(MTYPE_VRF, sizeof(struct vrf)); + vrf->vrf_id = VRF_UNKNOWN; + QOBJ_REG(vrf, vrf); + new = 1; + + if (debug_vrf) + zlog_debug("VRF(%u) %s is created.", vrf_id, + (name) ? name : "(NULL)"); + } + + /* Set identifier */ + if (vrf_id != VRF_UNKNOWN && vrf->vrf_id == VRF_UNKNOWN) { + vrf->vrf_id = vrf_id; + RB_INSERT(vrf_id_head, &vrfs_by_id, vrf); + } + + /* Set name */ + if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) { + /* update the vrf name */ + RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf); + strlcpy(vrf->data.l.netns_name, + name, NS_NAMSIZ); + strlcpy(vrf->name, name, sizeof(vrf->name)); + RB_INSERT(vrf_name_head, &vrfs_by_name, vrf); + } else if (name && vrf->name[0] == '\0') { + strlcpy(vrf->name, name, sizeof(vrf->name)); + RB_INSERT(vrf_name_head, &vrfs_by_name, vrf); + } + if (new &&vrf_master.vrf_new_hook) + (*vrf_master.vrf_new_hook)(vrf); + + return vrf; +} + +/* Update a VRF. If not found, create one. + * Arg: + * name - The name of the vrf. + * vrf_id - The vrf_id of the vrf. + * Description: This function first finds the vrf using its name. If the vrf is + * found and the vrf-id of the existing vrf does not match the new vrf id, it + * will disable the existing vrf and update it with new vrf-id. If the vrf is + * not found, it will create the vrf with given name and the new vrf id. + */ +struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name) +{ + struct vrf *vrf = NULL; + + /*Treat VRF add for existing vrf as update + * Update VRF ID and also update in VRF ID table + */ + if (name) + vrf = vrf_lookup_by_name(name); + if (vrf && new_vrf_id != VRF_UNKNOWN && vrf->vrf_id != VRF_UNKNOWN + && vrf->vrf_id != new_vrf_id) { + if (debug_vrf) { + zlog_debug( + "Vrf Update event: %s old id: %u, new id: %u", + name, vrf->vrf_id, new_vrf_id); + } + + /*Disable the vrf to simulate implicit delete + * so that all stale routes are deleted + * This vrf will be enabled down the line + */ + vrf_disable(vrf); + + + RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf); + vrf->vrf_id = new_vrf_id; + RB_INSERT(vrf_id_head, &vrfs_by_id, vrf); + + } else { + + /* + * vrf_get is implied creation if it does not exist + */ + vrf = vrf_get(new_vrf_id, name); + } + return vrf; +} + +/* Delete a VRF. This is called when the underlying VRF goes away, a + * pre-configured VRF is deleted or when shutting down (vrf_terminate()). + */ +void vrf_delete(struct vrf *vrf) +{ + if (debug_vrf) + zlog_debug("VRF %s(%u) is to be deleted.", vrf->name, + vrf->vrf_id); + + if (vrf_is_enabled(vrf)) + vrf_disable(vrf); + + if (vrf->vrf_id != VRF_UNKNOWN) { + RB_REMOVE(vrf_id_head, &vrfs_by_id, vrf); + vrf->vrf_id = VRF_UNKNOWN; + } + + /* If the VRF is user configured, it'll stick around, just remove + * the ID mapping. Interfaces assigned to this VRF should've been + * removed already as part of the VRF going down. + */ + if (vrf_is_user_cfged(vrf)) + return; + + /* Do not delete the VRF if it has interfaces configured in it. */ + if (!RB_EMPTY(if_name_head, &vrf->ifaces_by_name)) + return; + + if (vrf_master.vrf_delete_hook) + (*vrf_master.vrf_delete_hook)(vrf); + + QOBJ_UNREG(vrf); + + if (vrf->name[0] != '\0') + RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf); + + XFREE(MTYPE_VRF, vrf); +} + +/* Look up a VRF by identifier. */ +struct vrf *vrf_lookup_by_id(vrf_id_t vrf_id) +{ + struct vrf vrf; + vrf.vrf_id = vrf_id; + return (RB_FIND(vrf_id_head, &vrfs_by_id, &vrf)); +} + +/* + * Enable a VRF - that is, let the VRF be ready to use. + * The VRF_ENABLE_HOOK callback will be called to inform + * that they can allocate resources in this VRF. + * + * RETURN: 1 - enabled successfully; otherwise, 0. + */ +int vrf_enable(struct vrf *vrf) +{ + if (vrf_is_enabled(vrf)) + return 1; + + if (debug_vrf) + zlog_debug("VRF %s(%u) is enabled.", vrf->name, vrf->vrf_id); + + SET_FLAG(vrf->status, VRF_ACTIVE); + + if (vrf_master.vrf_enable_hook) + (*vrf_master.vrf_enable_hook)(vrf); + + /* + * If we have any nexthop group entries that + * are awaiting vrf initialization then + * let's let people know about it + */ + nexthop_group_enable_vrf(vrf); + + return 1; +} + +/* + * Disable a VRF - that is, let the VRF be unusable. + * The VRF_DELETE_HOOK callback will be called to inform + * that they must release the resources in the VRF. + */ +void vrf_disable(struct vrf *vrf) +{ + if (!vrf_is_enabled(vrf)) + return; + + UNSET_FLAG(vrf->status, VRF_ACTIVE); + + if (debug_vrf) + zlog_debug("VRF %s(%u) is to be disabled.", vrf->name, + vrf->vrf_id); + + /* Till now, nothing to be done for the default VRF. */ + // Pending: see why this statement. + + + /* + * When the vrf is disabled let's + * handle all nexthop-groups associated + * with this vrf + */ + nexthop_group_disable_vrf(vrf); + + if (vrf_master.vrf_disable_hook) + (*vrf_master.vrf_disable_hook)(vrf); +} + +void vrf_iterate(vrf_iter_func fnc) +{ + struct vrf *vrf, *tmp; + + if (debug_vrf) + zlog_debug("%s: vrf subsystem iteration", __func__); + + RB_FOREACH_SAFE (vrf, vrf_id_head, &vrfs_by_id, tmp) { + if (vrf->vrf_id == VRF_DEFAULT) + continue; + + fnc(vrf); + } + + RB_FOREACH_SAFE (vrf, vrf_name_head, &vrfs_by_name, tmp) { + if (vrf->vrf_id == VRF_DEFAULT) + continue; + + fnc(vrf); + } + + /* Finally process default VRF */ + vrf = vrf_lookup_by_id(VRF_DEFAULT); + if (vrf) + fnc(vrf); +} + +const char *vrf_id_to_name(vrf_id_t vrf_id) +{ + struct vrf *vrf; + + if (vrf_id == VRF_DEFAULT) + return VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_id(vrf_id); + return VRF_LOGNAME(vrf); +} + +/* Look up the data pointer of the specified VRF. */ +void *vrf_info_lookup(vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + return vrf ? vrf->info : NULL; +} + +/* + * VRF hash for storing set or not. + */ +struct vrf_bit_set { + vrf_id_t vrf_id; + bool set; +}; + +static unsigned int vrf_hash_bitmap_key(const void *data) +{ + const struct vrf_bit_set *bit = data; + + return bit->vrf_id; +} + +static bool vrf_hash_bitmap_cmp(const void *a, const void *b) +{ + const struct vrf_bit_set *bit1 = a; + const struct vrf_bit_set *bit2 = b; + + return bit1->vrf_id == bit2->vrf_id; +} + +static void *vrf_hash_bitmap_alloc(void *data) +{ + struct vrf_bit_set *copy = data; + struct vrf_bit_set *bit; + + bit = XMALLOC(MTYPE_VRF_BITMAP, sizeof(*bit)); + bit->vrf_id = copy->vrf_id; + + return bit; +} + +static void vrf_hash_bitmap_free(void *data) +{ + struct vrf_bit_set *bit = data; + + XFREE(MTYPE_VRF_BITMAP, bit); +} + +void vrf_bitmap_init(vrf_bitmap_t *pbmap) +{ + *pbmap = NULL; +} + +void vrf_bitmap_free(vrf_bitmap_t *pbmap) +{ + struct hash *vrf_hash; + + if (!*pbmap) + return; + + vrf_hash = *pbmap; + + hash_clean_and_free(&vrf_hash, vrf_hash_bitmap_free); +} + +void vrf_bitmap_set(vrf_bitmap_t *pbmap, vrf_id_t vrf_id) +{ + struct vrf_bit_set lookup = { .vrf_id = vrf_id }; + struct hash *vrf_hash; + struct vrf_bit_set *bit; + + if (vrf_id == VRF_UNKNOWN) + return; + + if (!*pbmap) + *pbmap = vrf_hash = + hash_create_size(2, vrf_hash_bitmap_key, + vrf_hash_bitmap_cmp, "VRF BIT HASH"); + else + vrf_hash = *pbmap; + + bit = hash_get(vrf_hash, &lookup, vrf_hash_bitmap_alloc); + bit->set = true; +} + +void vrf_bitmap_unset(vrf_bitmap_t *pbmap, vrf_id_t vrf_id) +{ + struct vrf_bit_set lookup = { .vrf_id = vrf_id }; + struct hash *vrf_hash; + struct vrf_bit_set *bit; + + if (vrf_id == VRF_UNKNOWN) + return; + + /* + * If the hash is not created then unsetting is unnecessary + */ + if (!*pbmap) + return; + + vrf_hash = *pbmap; + + /* + * If we can't look it up, no need to unset it! + */ + bit = hash_lookup(vrf_hash, &lookup); + if (!bit) + return; + + bit->set = false; +} + +int vrf_bitmap_check(vrf_bitmap_t *pbmap, vrf_id_t vrf_id) +{ + struct vrf_bit_set lookup = { .vrf_id = vrf_id }; + struct hash *vrf_hash; + struct vrf_bit_set *bit; + + if (!*pbmap || vrf_id == VRF_UNKNOWN) + return 0; + + vrf_hash = *pbmap; + bit = hash_lookup(vrf_hash, &lookup); + if (bit) + return bit->set; + + return 0; +} + +static void vrf_autocomplete(vector comps, struct cmd_token *token) +{ + struct vrf *vrf = NULL; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, vrf->name)); +} + +static const struct cmd_variable_handler vrf_var_handlers[] = { + { + .varname = "vrf", + .completions = vrf_autocomplete, + }, + { + .varname = "vrf_name", + .completions = vrf_autocomplete, + }, + { + .varname = "nexthop_vrf", + .completions = vrf_autocomplete, + }, + {.completions = NULL}, +}; + +/* Initialize VRF module. */ +void vrf_init(int (*create)(struct vrf *), int (*enable)(struct vrf *), + int (*disable)(struct vrf *), int (*destroy)(struct vrf *)) +{ + struct vrf *default_vrf; + + /* initialise NS, in case VRF backend if NETNS */ + ns_init(); + if (debug_vrf) + zlog_debug("%s: Initializing VRF subsystem", __func__); + + vrf_master.vrf_new_hook = create; + vrf_master.vrf_enable_hook = enable; + vrf_master.vrf_disable_hook = disable; + vrf_master.vrf_delete_hook = destroy; + + /* The default VRF always exists. */ + default_vrf = vrf_get(VRF_DEFAULT, VRF_DEFAULT_NAME); + if (!default_vrf) { + flog_err(EC_LIB_VRF_START, + "vrf_init: failed to create the default VRF!"); + exit(1); + } + if (vrf_is_backend_netns()) { + struct ns *ns; + + strlcpy(default_vrf->data.l.netns_name, + VRF_DEFAULT_NAME, NS_NAMSIZ); + ns = ns_lookup(NS_DEFAULT); + ns->vrf_ctxt = default_vrf; + default_vrf->ns_ctxt = ns; + } + + /* Enable the default VRF. */ + if (!vrf_enable(default_vrf)) { + flog_err(EC_LIB_VRF_START, + "vrf_init: failed to enable the default VRF!"); + exit(1); + } + + cmd_variable_handler_register(vrf_var_handlers); +} + +static void vrf_terminate_single(struct vrf *vrf) +{ + /* Clear configured flag and invoke delete. */ + vrf_disable(vrf); + UNSET_FLAG(vrf->status, VRF_CONFIGURED); + if_terminate(vrf); + vrf_delete(vrf); +} + +void vrf_terminate(void) +{ + if (debug_vrf) + zlog_debug("%s: Shutting down vrf subsystem", __func__); + + vrf_iterate(vrf_terminate_single); +} + +int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id, + const char *interfacename) +{ + int ret, save_errno, ret2; + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + + ret = socket(domain, type, protocol); + save_errno = errno; + ret2 = vrf_switchback_to_initial(); + if (ret2 < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + errno = save_errno; + if (ret <= 0) + return ret; + ret2 = vrf_bind(vrf_id, ret, interfacename); + if (ret2 < 0) { + close(ret); + ret = ret2; + } + return ret; +} + +int vrf_is_backend_netns(void) +{ + return (vrf_backend == VRF_BACKEND_NETNS); +} + +int vrf_get_backend(void) +{ + if (!vrf_backend_configured) + return VRF_BACKEND_UNKNOWN; + return vrf_backend; +} + +int vrf_configure_backend(enum vrf_backend_type backend) +{ + /* Work around issue in old gcc */ + switch (backend) { + case VRF_BACKEND_UNKNOWN: + case VRF_BACKEND_NETNS: + case VRF_BACKEND_VRF_LITE: + break; + case VRF_BACKEND_MAX: + return -1; + } + + vrf_backend = backend; + vrf_backend_configured = 1; + + return 0; +} + +/* vrf CLI commands */ +DEFUN_YANG_NOSH (vrf_exit, + vrf_exit_cmd, + "exit-vrf", + "Exit current mode and down to previous mode\n") +{ + cmd_exit(vty); + return CMD_SUCCESS; +} + +DEFUN_YANG_NOSH (vrf, + vrf_cmd, + "vrf NAME", + "Select a VRF to configure\n" + "VRF's name\n") +{ + int idx_name = 1; + const char *vrfname = argv[idx_name]->arg; + char xpath_list[XPATH_MAXLEN]; + struct vrf *vrf; + int ret; + + if (strlen(vrfname) > VRF_NAMSIZ) { + vty_out(vty, + "%% VRF name %s invalid: length exceeds %d bytes\n", + vrfname, VRF_NAMSIZ); + return CMD_WARNING_CONFIG_FAILED; + } + + snprintf(xpath_list, sizeof(xpath_list), FRR_VRF_KEY_XPATH, vrfname); + + nb_cli_enqueue_change(vty, xpath_list, NB_OP_CREATE, NULL); + ret = nb_cli_apply_changes_clear_pending(vty, "%s", xpath_list); + if (ret == CMD_SUCCESS) { + VTY_PUSH_XPATH(VRF_NODE, xpath_list); + vrf = vrf_lookup_by_name(vrfname); + if (vrf) + VTY_PUSH_CONTEXT(VRF_NODE, vrf); + } + + return ret; +} + +DEFUN_YANG (no_vrf, + no_vrf_cmd, + "no vrf NAME", + NO_STR + "Delete a pseudo VRF's configuration\n" + "VRF's name\n") +{ + const char *vrfname = argv[2]->arg; + char xpath_list[XPATH_MAXLEN]; + + if (vrf_get_backend() == VRF_BACKEND_VRF_LITE) { + /* + * Remove the VRF interface config when removing the VRF. + */ + snprintf(xpath_list, sizeof(xpath_list), + "/frr-interface:lib/interface[name='%s']", vrfname); + nb_cli_enqueue_change(vty, xpath_list, NB_OP_DESTROY, NULL); + } + + snprintf(xpath_list, sizeof(xpath_list), FRR_VRF_KEY_XPATH, vrfname); + + nb_cli_enqueue_change(vty, xpath_list, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + + +static struct cmd_node vrf_node = { + .name = "vrf", + .node = VRF_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-vrf)# ", +}; + +/* + * Debug CLI for vrf's + */ +DEFUN (vrf_debug, + vrf_debug_cmd, + "debug vrf", + DEBUG_STR + "VRF Debugging\n") +{ + debug_vrf = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_vrf_debug, + no_vrf_debug_cmd, + "no debug vrf", + NO_STR + DEBUG_STR + "VRF Debugging\n") +{ + debug_vrf = 0; + + return CMD_SUCCESS; +} + +static int vrf_write_host(struct vty *vty) +{ + if (debug_vrf) + vty_out(vty, "debug vrf\n"); + + return 1; +} + +static int vrf_write_host(struct vty *vty); +static struct cmd_node vrf_debug_node = { + .name = "vrf debug", + .node = VRF_DEBUG_NODE, + .prompt = "", + .config_write = vrf_write_host, +}; + +void vrf_install_commands(void) +{ + install_node(&vrf_debug_node); + + install_element(CONFIG_NODE, &vrf_debug_cmd); + install_element(ENABLE_NODE, &vrf_debug_cmd); + install_element(CONFIG_NODE, &no_vrf_debug_cmd); + install_element(ENABLE_NODE, &no_vrf_debug_cmd); +} + +void vrf_cmd_init(int (*writefunc)(struct vty *vty)) +{ + install_element(CONFIG_NODE, &vrf_cmd); + install_element(CONFIG_NODE, &no_vrf_cmd); + vrf_node.config_write = writefunc; + install_node(&vrf_node); + install_default(VRF_NODE); + install_element(VRF_NODE, &vrf_exit_cmd); +} + +void vrf_set_default_name(const char *default_name) +{ + snprintf(vrf_default_name, VRF_NAMSIZ, "%s", default_name); +} + +const char *vrf_get_default_name(void) +{ + return vrf_default_name; +} + +int vrf_bind(vrf_id_t vrf_id, int fd, const char *ifname) +{ + int ret = 0; + struct interface *ifp; + struct vrf *vrf; + + if (fd < 0) + return -1; + + if (vrf_id == VRF_UNKNOWN) + return -1; + + /* can't bind to a VRF that doesn't exist */ + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf_is_enabled(vrf)) + return -1; + + if (ifname && strcmp(ifname, vrf->name)) { + /* binding to a regular interface */ + + /* can't bind to an interface that doesn't exist */ + ifp = if_lookup_by_name(ifname, vrf_id); + if (!ifp) + return -1; + } else { + /* binding to a VRF device */ + + /* nothing to do for netns */ + if (vrf_is_backend_netns()) + return 0; + + /* nothing to do for default vrf */ + if (vrf_id == VRF_DEFAULT) + return 0; + + ifname = vrf->name; + } + +#ifdef SO_BINDTODEVICE + ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, + strlen(ifname) + 1); + if (ret < 0) + zlog_err("bind to interface %s failed, errno=%d", ifname, + errno); +#endif /* SO_BINDTODEVICE */ + return ret; +} +int vrf_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res, + vrf_id_t vrf_id) +{ + int ret, ret2, save_errno; + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + ret = getaddrinfo(node, service, hints, res); + save_errno = errno; + ret2 = vrf_switchback_to_initial(); + if (ret2 < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + errno = save_errno; + return ret; +} + +int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *params) +{ + int ret, saved_errno, rc; + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + return 0; + } + rc = ioctl(d, request, params); + saved_errno = errno; + ret = vrf_switchback_to_initial(); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + errno = saved_errno; + return rc; +} + +int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id, + const char *interfacename) +{ + int ret, save_errno, ret2; + + ret = vrf_switch_to_netns(vrf_id); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, "%s: Can't switch to VRF %u (%s)", + __func__, vrf_id, safe_strerror(errno)); + ret = sockunion_socket(su); + save_errno = errno; + ret2 = vrf_switchback_to_initial(); + if (ret2 < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s: Can't switchback from VRF %u (%s)", __func__, + vrf_id, safe_strerror(errno)); + errno = save_errno; + + if (ret <= 0) + return ret; + ret2 = vrf_bind(vrf_id, ret, interfacename); + if (ret2 < 0) { + close(ret); + ret = ret2; + } + return ret; +} + +/* ------- Northbound callbacks ------- */ + +/* + * XPath: /frr-vrf:lib/vrf + */ +static int lib_vrf_create(struct nb_cb_create_args *args) +{ + const char *vrfname; + struct vrf *vrfp; + + vrfname = yang_dnode_get_string(args->dnode, "name"); + + if (args->event != NB_EV_APPLY) + return NB_OK; + + vrfp = vrf_get(VRF_UNKNOWN, vrfname); + + SET_FLAG(vrfp->status, VRF_CONFIGURED); + nb_running_set_entry(args->dnode, vrfp); + + return NB_OK; +} + +static int lib_vrf_destroy(struct nb_cb_destroy_args *args) +{ + struct vrf *vrfp; + + switch (args->event) { + case NB_EV_VALIDATE: + vrfp = nb_running_get_entry(args->dnode, NULL, true); + if (CHECK_FLAG(vrfp->status, VRF_ACTIVE)) { + snprintf(args->errmsg, args->errmsg_len, + "Only inactive VRFs can be deleted"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrfp = nb_running_unset_entry(args->dnode); + + /* Clear configured flag and invoke delete. */ + UNSET_FLAG(vrfp->status, VRF_CONFIGURED); + vrf_delete(vrfp); + break; + } + + return NB_OK; +} + +static void lib_vrf_cli_write(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *name = yang_dnode_get_string(dnode, "name"); + + if (strcmp(name, VRF_DEFAULT_NAME)) { + vty_out(vty, "!\n"); + vty_out(vty, "vrf %s\n", name); + } +} + +static void lib_vrf_cli_write_end(struct vty *vty, const struct lyd_node *dnode) +{ + const char *name = yang_dnode_get_string(dnode, "name"); + + if (strcmp(name, VRF_DEFAULT_NAME)) + vty_out(vty, "exit-vrf\n"); +} + +static const void *lib_vrf_get_next(struct nb_cb_get_next_args *args) +{ + struct vrf *vrfp = (struct vrf *)args->list_entry; + + if (args->list_entry == NULL) { + vrfp = RB_MIN(vrf_name_head, &vrfs_by_name); + } else { + vrfp = RB_NEXT(vrf_name_head, vrfp); + } + + return vrfp; +} + +static int lib_vrf_get_keys(struct nb_cb_get_keys_args *args) +{ + struct vrf *vrfp = (struct vrf *)args->list_entry; + + args->keys->num = 1; + strlcpy(args->keys->key[0], vrfp->name, sizeof(args->keys->key[0])); + + return NB_OK; +} + +static const void *lib_vrf_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *vrfname = args->keys->key[0]; + + struct vrf *vrf = vrf_lookup_by_name(vrfname); + + return vrf; +} + +static const void *lib_vrf_lookup_next(struct nb_cb_lookup_entry_args *args) +{ + const char *vrfname = args->keys->key[0]; + struct vrf vrfkey, *vrf; + + strlcpy(vrfkey.name, vrfname, sizeof(vrfkey.name)); + vrf = RB_FIND(vrf_name_head, &vrfs_by_name, &vrfkey); + if (!strcmp(vrf->name, vrfname)) + vrf = RB_NEXT(vrf_name_head, vrf); + + return vrf; +} + +/* + * XPath: /frr-vrf:lib/vrf/id + */ +static struct yang_data * +lib_vrf_state_id_get_elem(struct nb_cb_get_elem_args *args) +{ + struct vrf *vrfp = (struct vrf *)args->list_entry; + + return yang_data_new_uint32(args->xpath, vrfp->vrf_id); +} + +/* + * XPath: /frr-vrf:lib/vrf/active + */ +static struct yang_data * +lib_vrf_state_active_get_elem(struct nb_cb_get_elem_args *args) +{ + struct vrf *vrfp = (struct vrf *)args->list_entry; + + if (vrfp->status == VRF_ACTIVE) + return yang_data_new_bool(args->xpath, true); + + return NULL; +} + +/* clang-format off */ + +/* cli_show callbacks are kept here for daemons not yet converted to mgmtd */ +const struct frr_yang_module_info frr_vrf_info = { + .name = "frr-vrf", + .nodes = { + { + .xpath = "/frr-vrf:lib/vrf", + .cbs = { + .create = lib_vrf_create, + .destroy = lib_vrf_destroy, + .cli_show = lib_vrf_cli_write, + .cli_show_end = lib_vrf_cli_write_end, + .get_next = lib_vrf_get_next, + .get_keys = lib_vrf_get_keys, + .lookup_entry = lib_vrf_lookup_entry, + .lookup_next = lib_vrf_lookup_next, + }, + .priority = NB_DFLT_PRIORITY - 2, + }, + { + .xpath = "/frr-vrf:lib/vrf/state/id", + .cbs = { + .get_elem = lib_vrf_state_id_get_elem, + } + }, + { + .xpath = "/frr-vrf:lib/vrf/state/active", + .cbs = { + .get_elem = lib_vrf_state_active_get_elem, + } + }, + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_vrf_cli_info = { + .name = "frr-vrf", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-vrf:lib/vrf", + .cbs = { + .cli_show = lib_vrf_cli_write, + .cli_show_end = lib_vrf_cli_write_end, + }, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/lib/vrf.h b/lib/vrf.h new file mode 100644 index 0000000..3ebb6dd --- /dev/null +++ b/lib/vrf.h @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VRF related header. + * Copyright (C) 2014 6WIND S.A. + */ + +#ifndef _ZEBRA_VRF_H +#define _ZEBRA_VRF_H + +#include "openbsd-tree.h" +#include "linklist.h" +#include "qobj.h" +#include "vty.h" +#include "ns.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The default VRF ID */ +#define VRF_UNKNOWN UINT32_MAX + +/* Pending: May need to refine this. */ +#ifndef IFLA_VRF_MAX +enum { IFLA_VRF_UNSPEC, IFLA_VRF_TABLE, __IFLA_VRF_MAX }; + +#define IFLA_VRF_MAX (__IFLA_VRF_MAX - 1) +#endif + +#define VRF_NAMSIZ 36 +#define NS_NAMSIZ 36 + +/* + * The command strings + */ +#define VRF_CMD_HELP_STR "Specify the VRF\nThe VRF name\n" +#define VRF_ALL_CMD_HELP_STR "Specify the VRF\nAll VRFs\n" +#define VRF_FULL_CMD_HELP_STR "Specify the VRF\nThe VRF name\nAll VRFs\n" + +#define FRR_VRF_XPATH "/frr-vrf:lib/vrf" +#define FRR_VRF_KEY_XPATH "/frr-vrf:lib/vrf[name='%s']" + +/* + * Pass some OS specific data up through + * to the daemons + */ +struct vrf_data { + union { + struct { + uint32_t table_id; + char netns_name[NS_NAMSIZ]; + } l; + }; +}; + +struct vrf { + RB_ENTRY(vrf) id_entry, name_entry; + + /* Identifier, same as the vector index */ + vrf_id_t vrf_id; + + /* Name */ + char name[VRF_NAMSIZ + 1]; + + /* Zebra internal VRF status */ + uint8_t status; +#define VRF_ACTIVE (1 << 0) /* VRF is up in kernel */ +#define VRF_CONFIGURED (1 << 1) /* VRF has some FRR configuration */ + + /* Interfaces belonging to this VRF */ + struct if_name_head ifaces_by_name; + struct if_index_head ifaces_by_index; + + /* User data */ + void *info; + + /* The table_id from the kernel */ + struct vrf_data data; + + /* Back pointer to namespace context */ + void *ns_ctxt; + + QOBJ_FIELDS; +}; +RB_HEAD(vrf_id_head, vrf); +RB_PROTOTYPE(vrf_id_head, vrf, id_entry, vrf_id_compare) +RB_HEAD(vrf_name_head, vrf); +RB_PROTOTYPE(vrf_name_head, vrf, name_entry, vrf_name_compare) +DECLARE_QOBJ_TYPE(vrf); + +/* Allow VRF with netns as backend */ +enum vrf_backend_type { + VRF_BACKEND_VRF_LITE, + VRF_BACKEND_NETNS, + VRF_BACKEND_UNKNOWN, + VRF_BACKEND_MAX, +}; + +extern struct vrf_id_head vrfs_by_id; +extern struct vrf_name_head vrfs_by_name; + +extern struct vrf *vrf_lookup_by_id(vrf_id_t); +extern struct vrf *vrf_lookup_by_name(const char *); +extern struct vrf *vrf_get(vrf_id_t, const char *); +extern struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name); +extern const char *vrf_id_to_name(vrf_id_t vrf_id); + +#define VRF_LOGNAME(V) V ? V->name : "Unknown" + +#define VRF_GET_ID(V, NAME, USE_JSON) \ + do { \ + struct vrf *_vrf; \ + if (!(_vrf = vrf_lookup_by_name(NAME))) { \ + if (USE_JSON) { \ + vty_out(vty, "{}\n"); \ + } else { \ + vty_out(vty, "%% VRF %s not found\n", NAME); \ + } \ + return CMD_WARNING; \ + } \ + if (_vrf->vrf_id == VRF_UNKNOWN) { \ + if (USE_JSON) { \ + vty_out(vty, "{}\n"); \ + } else { \ + vty_out(vty, "%% VRF %s not active\n", NAME); \ + } \ + return CMD_WARNING; \ + } \ + (V) = _vrf->vrf_id; \ + } while (0) + +/* + * Check whether the VRF is enabled. + */ +static inline int vrf_is_enabled(struct vrf *vrf) +{ + return vrf && CHECK_FLAG(vrf->status, VRF_ACTIVE); +} + +/* check if the vrf is user configured */ +static inline int vrf_is_user_cfged(struct vrf *vrf) +{ + return vrf && CHECK_FLAG(vrf->status, VRF_CONFIGURED); +} + +static inline uint32_t vrf_interface_count(struct vrf *vrf) +{ + uint32_t count = 0; + struct interface *ifp; + + RB_FOREACH (ifp, if_name_head, &vrf->ifaces_by_name) { + /* skip the l3mdev */ + if (strncmp(ifp->name, vrf->name, VRF_NAMSIZ) == 0) + continue; + count++; + } + return count; +} + +/* + * Utilities to obtain the user data + */ + +/* Look up the data pointer of the specified VRF. */ +extern void *vrf_info_lookup(vrf_id_t); + +/* + * VRF bit-map: maintaining flags, one bit per VRF ID + */ +typedef void *vrf_bitmap_t; +#define VRF_BITMAP_NULL NULL + +extern void vrf_bitmap_init(vrf_bitmap_t *pbmap); +extern void vrf_bitmap_free(vrf_bitmap_t *pbmap); +extern void vrf_bitmap_set(vrf_bitmap_t *pbmap, vrf_id_t vrf_id); +extern void vrf_bitmap_unset(vrf_bitmap_t *pbmap, vrf_id_t vrf_id); +extern int vrf_bitmap_check(vrf_bitmap_t *pbmap, vrf_id_t vrf_id); + +/* + * VRF initializer/destructor + * + * create -> Called back when a new VRF is created. This + * can be either through these 3 options: + * 1) CLI mentions a vrf before OS knows about it + * 2) OS calls zebra and we create the vrf from OS + * callback + * 3) zebra calls individual protocols to notify + * about the new vrf + * + * enable -> Called back when a VRF is actually usable from + * an OS perspective ( 2 and 3 above ) + * + * disable -> Called back when a VRF is being deleted from + * the system ( 2 and 3 ) above + * + * delete -> Called back when a vrf is being deleted from + * the system ( 2 and 3 ) above. + */ +extern void vrf_init(int (*create)(struct vrf *vrf), + int (*enable)(struct vrf *vrf), + int (*disable)(struct vrf *vrf), + int (*destroy)(struct vrf *vrf)); + +/* + * Iterate over custom VRFs and round up by processing the default VRF. + */ +typedef void (*vrf_iter_func)(struct vrf *vrf); +extern void vrf_iterate(vrf_iter_func fnc); + +/* + * Call vrf_terminate when the protocol is being shutdown + */ +extern void vrf_terminate(void); + +/* + * Utilities to create networks objects, + * or call network operations + */ + +/* + * Create a new socket associated with a VRF. + * + * This is a wrapper that ensures correct behavior when using namespace VRFs. + * In the namespace case, the socket is created within the namespace. In the + * non-namespace case, this is equivalent to socket(). + * + * If name is provided, this is provided to vrf_bind() to bind the socket to + * the VRF. This is only relevant when using VRF-lite. + * + * Summary: + * - Namespace: pass vrf_id but not name + * - VRF-lite: pass vrf_id and name of VRF device to bind to + * - VRF-lite, no binding: pass vrf_id but not name, or just use socket() + */ +extern int vrf_socket(int domain, int type, int protocol, vrf_id_t vrf_id, + const char *name); + +extern int vrf_sockunion_socket(const union sockunion *su, vrf_id_t vrf_id, + const char *name); + +/* + * Binds a socket to an interface (ifname) in a VRF (vrf_id). + * + * If ifname is NULL or is equal to the VRF name then bind to a VRF device. + * Otherwise, bind to the specified interface in the specified VRF. + * + * Returns 0 on success and -1 on failure. + */ +extern int vrf_bind(vrf_id_t vrf_id, int fd, const char *ifname); + +/* VRF ioctl operations */ +extern int vrf_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res, + vrf_id_t vrf_id); + +extern int vrf_ioctl(vrf_id_t vrf_id, int d, unsigned long request, char *args); + +/* The default VRF ID */ +#define VRF_DEFAULT 0 + +/* Must be called only during startup, before config is read */ +extern void vrf_set_default_name(const char *default_name); + +extern const char *vrf_get_default_name(void); +#define VRF_DEFAULT_NAME vrf_get_default_name() + +/* VRF switch from NETNS */ +extern int vrf_switch_to_netns(vrf_id_t vrf_id); +extern int vrf_switchback_to_initial(void); + +/* + * VRF backend routines + * should be called from zebra only + */ + +/* VRF vty command initialisation + */ +extern void vrf_cmd_init(int (*writefunc)(struct vty *vty)); + +/* VRF vty debugging + */ +extern void vrf_install_commands(void); + +/* + * VRF utilities + */ + +/* + * API for configuring VRF backend + */ +extern int vrf_configure_backend(enum vrf_backend_type backend); +extern int vrf_get_backend(void); +extern int vrf_is_backend_netns(void); + +/* used internally to enable or disable VRF. + * Notify a change in the VRF ID of the VRF + */ +extern void vrf_disable(struct vrf *vrf); +extern int vrf_enable(struct vrf *vrf); +extern void vrf_delete(struct vrf *vrf); + +extern const struct frr_yang_module_info frr_vrf_info; +extern const struct frr_yang_module_info frr_vrf_cli_info; + +#ifdef __cplusplus +} +#endif + +#endif /*_ZEBRA_VRF_H*/ diff --git a/lib/vrf_int.h b/lib/vrf_int.h new file mode 100644 index 0000000..bd40433 --- /dev/null +++ b/lib/vrf_int.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VRF Internal Header + * Copyright (C) 2017 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __LIB_VRF_PRIVATE_H__ +#define __LIB_VRF_PRIVATE_H__ + +#include "vrf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * These functions should only be called by: + * zebra/if_netlink.c -> The interface from OS into Zebra + * lib/zclient.c -> The interface from Zebra to each daemon + * + * Why you ask? Well because these are the turn on/off + * functions and the only place we can really turn a + * vrf on properly is in the call up from the os -> zebra + * and the pass through of this informatoin from zebra -> protocols + */ + +/* + * vrf_enable + * + * Given a newly running vrf enable it to be used + * by interested routing protocols + */ +extern int vrf_enable(struct vrf *); + +/* + * vrf_delete + * + * Given a vrf that is being deleted, delete it + * from interested parties + */ +extern void vrf_delete(struct vrf *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/vty.c b/lib/vty.c new file mode 100644 index 0000000..d0bbf0e --- /dev/null +++ b/lib/vty.c @@ -0,0 +1,4306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual terminal [aka TeletYpe] interface routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#include + +#include +#include +#include +#include +#include +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ +#include + +#include "debug.h" +#include "linklist.h" +#include "frrevent.h" +#include "buffer.h" +#include "command.h" +#include "sockunion.h" +#include "memory.h" +#include "log.h" +#include "prefix.h" +#include "filter.h" +#include "vty.h" +#include "privs.h" +#include "network.h" +#include "libfrr.h" +#include "frrstr.h" +#include "lib_errors.h" +#include +#include "northbound_cli.h" +#include "printfrr.h" +#include "json.h" + +#include +#include + +#include "lib/config_paths.h" + +#include "lib/vty_clippy.c" + +DEFINE_MTYPE_STATIC(LIB, VTY, "VTY"); +DEFINE_MTYPE_STATIC(LIB, VTY_SERV, "VTY server"); +DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer"); +DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history"); + +DECLARE_DLIST(vtys, struct vty, itm); + +/* Vty events */ +enum vty_event { + VTY_SERV, + VTY_READ, + VTY_WRITE, + VTY_TIMEOUT_RESET, +#ifdef VTYSH + VTYSH_SERV, + VTYSH_READ, + VTYSH_WRITE +#endif /* VTYSH */ +}; + +struct nb_config *vty_mgmt_candidate_config; + +static struct mgmt_fe_client *mgmt_fe_client; +static bool mgmt_fe_connected; +static uint64_t mgmt_client_id_next; +static uint64_t mgmt_last_req_id = UINT64_MAX; + +PREDECL_DLIST(vtyservs); + +struct vty_serv { + struct vtyservs_item itm; + + int sock; + bool vtysh; + + struct event *t_accept; +}; + +DECLARE_DLIST(vtyservs, struct vty_serv, itm); + +static void vty_event_serv(enum vty_event event, struct vty_serv *); +static void vty_event(enum vty_event, struct vty *); +static int vtysh_flush(struct vty *vty); + +/* Extern host structure from command.c */ +extern struct host host; + +/* active listeners */ +static struct vtyservs_head vty_servs[1] = {INIT_DLIST(vty_servs[0])}; + +/* active connections */ +static struct vtys_head vty_sessions[1] = {INIT_DLIST(vty_sessions[0])}; +static struct vtys_head vtysh_sessions[1] = {INIT_DLIST(vtysh_sessions[0])}; + +/* Vty timeout value. */ +static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; + +/* Vty access-class command */ +static char *vty_accesslist_name = NULL; + +/* Vty access-calss for IPv6. */ +static char *vty_ipv6_accesslist_name = NULL; + +/* Current directory. */ +static char vty_cwd[MAXPATHLEN]; + +/* Login password check. */ +static int no_password_check = 0; + +/* Integrated configuration file path */ +static char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; + +bool vty_log_commands; +static bool vty_log_commands_perm; + +char const *const mgmt_daemons[] = { + "zebra", +#ifdef HAVE_RIPD + "ripd", +#endif +#ifdef HAVE_RIPNGD + "ripngd", +#endif +#ifdef HAVE_STATICD + "staticd", +#endif +}; +uint mgmt_daemons_count = array_size(mgmt_daemons); + + +static int vty_mgmt_lock_candidate_inline(struct vty *vty) +{ + assert(!vty->mgmt_locked_candidate_ds); + (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); + return vty->mgmt_locked_candidate_ds ? 0 : -1; +} + +static int vty_mgmt_unlock_candidate_inline(struct vty *vty) +{ + assert(vty->mgmt_locked_candidate_ds); + (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); + return vty->mgmt_locked_candidate_ds ? -1 : 0; +} + +static int vty_mgmt_lock_running_inline(struct vty *vty) +{ + assert(!vty->mgmt_locked_running_ds); + (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); + return vty->mgmt_locked_running_ds ? 0 : -1; +} + +static int vty_mgmt_unlock_running_inline(struct vty *vty) +{ + assert(vty->mgmt_locked_running_ds); + (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); + return vty->mgmt_locked_running_ds ? -1 : 0; +} + +void vty_mgmt_resume_response(struct vty *vty, int ret) +{ + uint8_t header[4] = {0, 0, 0, 0}; + + if (!vty->mgmt_req_pending_cmd) { + zlog_err( + "vty resume response called without mgmt_req_pending_cmd"); + return; + } + + debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64 + " with '%s'", + vty->mgmt_req_pending_cmd, vty->mgmt_session_id, + ret == CMD_SUCCESS ? "success" : "failed"); + + vty->mgmt_req_pending_cmd = NULL; + + if (vty->type != VTY_FILE) { + header[3] = ret; + buffer_put(vty->obuf, header, 4); + if (!vty->t_write && (vtysh_flush(vty) < 0)) { + zlog_err("failed to vtysh_flush"); + /* Try to flush results; exit if a write error occurs */ + return; + } + } + + if (vty->status == VTY_CLOSE) + vty_close(vty); + else if (vty->type != VTY_FILE) + vty_event(VTYSH_READ, vty); + else + /* should we assert here? */ + zlog_err("mgmtd: unexpected resume while reading config file"); +} + +void vty_frame(struct vty *vty, const char *format, ...) +{ + va_list args; + + va_start(args, format); + vsnprintfrr(vty->frame + vty->frame_pos, + sizeof(vty->frame) - vty->frame_pos, format, args); + vty->frame_pos = strlen(vty->frame); + va_end(args); +} + +void vty_endframe(struct vty *vty, const char *endtext) +{ + if (vty->frame_pos == 0 && endtext) + vty_out(vty, "%s", endtext); + vty->frame_pos = 0; +} + +bool vty_set_include(struct vty *vty, const char *regexp) +{ + int errcode; + bool ret = true; + char errbuf[256]; + + if (!regexp) { + if (vty->filter) { + regfree(&vty->include); + vty->filter = false; + } + return true; + } + + errcode = regcomp(&vty->include, regexp, + REG_EXTENDED | REG_NEWLINE | REG_NOSUB); + if (errcode) { + ret = false; + regerror(errcode, &vty->include, errbuf, sizeof(errbuf)); + vty_out(vty, "%% Regex compilation error: %s\n", errbuf); + } else { + vty->filter = true; + } + + return ret; +} + +/* VTY standard output function. */ +int vty_out(struct vty *vty, const char *format, ...) +{ + va_list args; + ssize_t len; + char buf[1024]; + char *p = NULL; + char *filtered; + /* format string may contain %m, keep errno intact for printfrr */ + int saved_errno = errno; + + if (vty->frame_pos) { + vty->frame_pos = 0; + vty_out(vty, "%s", vty->frame); + } + + va_start(args, format); + errno = saved_errno; + p = vasnprintfrr(MTYPE_VTY_OUT_BUF, buf, sizeof(buf), format, args); + va_end(args); + + len = strlen(p); + + /* filter buffer */ + if (vty->filter) { + vector lines = frrstr_split_vec(p, "\n"); + + /* Place first value in the cache */ + char *firstline = vector_slot(lines, 0); + buffer_put(vty->lbuf, (uint8_t *) firstline, strlen(firstline)); + + /* If our split returned more than one entry, time to filter */ + if (vector_active(lines) > 1) { + /* + * returned string is MTYPE_TMP so it matches the MTYPE + * of everything else in the vector + */ + char *bstr = buffer_getstr(vty->lbuf); + buffer_reset(vty->lbuf); + XFREE(MTYPE_TMP, lines->index[0]); + vector_set_index(lines, 0, bstr); + frrstr_filter_vec(lines, &vty->include); + vector_compact(lines); + /* + * Consider the string "foo\n". If the regex is an empty string + * and the line ended with a newline, then the vector will look + * like: + * + * [0]: 'foo' + * [1]: '' + * + * If the regex isn't empty, the vector will look like: + * + * [0]: 'foo' + * + * In this case we'd like to preserve the newline, so we add + * the empty string [1] as in the first example. + */ + if (p[strlen(p) - 1] == '\n' && vector_active(lines) > 0 + && strlen(vector_slot(lines, vector_active(lines) - 1))) + vector_set(lines, XSTRDUP(MTYPE_TMP, "")); + + filtered = frrstr_join_vec(lines, "\n"); + } + else { + filtered = NULL; + } + + frrstr_strvec_free(lines); + + } else { + filtered = p; + } + + if (!filtered) + goto done; + + switch (vty->type) { + case VTY_TERM: + /* print with crlf replacement */ + buffer_put_crlf(vty->obuf, (uint8_t *)filtered, + strlen(filtered)); + break; + case VTY_SHELL: + if (vty->of) { + fprintf(vty->of, "%s", filtered); + fflush(vty->of); + } else if (vty->of_saved) { + fprintf(vty->of_saved, "%s", filtered); + fflush(vty->of_saved); + } + break; + case VTY_SHELL_SERV: + case VTY_FILE: + default: + /* print without crlf replacement */ + buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered)); + break; + } + +done: + + if (vty->filter && filtered) + XFREE(MTYPE_TMP, filtered); + + /* If p is not different with buf, it is allocated buffer. */ + if (p != buf) + XFREE(MTYPE_VTY_OUT_BUF, p); + + return len; +} + +static int vty_json_helper(struct vty *vty, struct json_object *json, + uint32_t options) +{ + const char *text; + + if (!json) + return CMD_SUCCESS; + + text = json_object_to_json_string_ext( + json, options); + vty_out(vty, "%s\n", text); + json_object_free(json); + + return CMD_SUCCESS; +} + +int vty_json(struct vty *vty, struct json_object *json) +{ + return vty_json_helper(vty, json, + JSON_C_TO_STRING_PRETTY | + JSON_C_TO_STRING_NOSLASHESCAPE); +} + +int vty_json_no_pretty(struct vty *vty, struct json_object *json) +{ + return vty_json_helper(vty, json, JSON_C_TO_STRING_NOSLASHESCAPE); +} + + +void vty_json_key(struct vty *vty, const char *key, bool *first_key) +{ + vty_out(vty, "%s\"%s\":", *first_key ? "{" : ",", key); + *first_key = false; +} + +void vty_json_close(struct vty *vty, bool first_key) +{ + if (first_key) + /* JSON was not opened */ + vty_out(vty, "{"); + vty_out(vty, "}\n"); +} + +void vty_json_empty(struct vty *vty, struct json_object *json) +{ + json_object *jsonobj = json; + + if (!json) + jsonobj = json_object_new_object(); + + vty_json(vty, jsonobj); +} + +/* Output current time to the vty. */ +void vty_time_print(struct vty *vty, int cr) +{ + char buf[FRR_TIMESTAMP_LEN]; + + if (frr_timestamp(0, buf, sizeof(buf)) == 0) { + zlog_info("frr_timestamp error"); + return; + } + if (cr) + vty_out(vty, "%s\n", buf); + else + vty_out(vty, "%s ", buf); + + return; +} + +/* Say hello to vty interface. */ +void vty_hello(struct vty *vty) +{ + if (host.motdfile) { + FILE *f; + char buf[4096]; + + f = fopen(host.motdfile, "r"); + if (f) { + while (fgets(buf, sizeof(buf), f)) { + char *s; + /* work backwards to ignore trailling isspace() + */ + for (s = buf + strlen(buf); + (s > buf) && isspace((unsigned char)s[-1]); + s--) + ; + *s = '\0'; + vty_out(vty, "%s\n", buf); + } + fclose(f); + } else + vty_out(vty, "MOTD file not found\n"); + } else if (host.motd) + vty_out(vty, "%s", host.motd); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +/* prompt formatting has a %s in the cmd_node prompt string. + * + * Also for some reason GCC emits the warning on the end of the function + * (optimization maybe?) rather than on the vty_out line, so this pragma + * wraps the entire function rather than just the vty_out line. + */ + +/* Put out prompt and wait input from user. */ +static void vty_prompt(struct vty *vty) +{ + if (vty->type == VTY_TERM) { + vty_out(vty, cmd_prompt(vty->node), cmd_hostname_get()); + } +} +#pragma GCC diagnostic pop + +/* Send WILL TELOPT_ECHO to remote server. */ +static void vty_will_echo(struct vty *vty) +{ + unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Make suppress Go-Ahead telnet option. */ +static void vty_will_suppress_go_ahead(struct vty *vty) +{ + unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Make don't use linemode over telnet. */ +static void vty_dont_linemode(struct vty *vty) +{ + unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Use window size. */ +static void vty_do_window_size(struct vty *vty) +{ + unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'}; + vty_out(vty, "%s", cmd); +} + +/* Authentication of vty */ +static void vty_auth(struct vty *vty, char *buf) +{ + char *passwd = NULL; + enum node_type next_node = 0; + int fail; + + switch (vty->node) { + case AUTH_NODE: + if (host.encrypt) + passwd = host.password_encrypt; + else + passwd = host.password; + if (host.advanced) + next_node = host.enable ? VIEW_NODE : ENABLE_NODE; + else + next_node = VIEW_NODE; + break; + case AUTH_ENABLE_NODE: + if (host.encrypt) + passwd = host.enable_encrypt; + else + passwd = host.enable; + next_node = ENABLE_NODE; + break; + } + + if (passwd) { + if (host.encrypt) + fail = strcmp(crypt(buf, passwd), passwd); + else + fail = strcmp(buf, passwd); + } else + fail = 1; + + if (!fail) { + vty->fail = 0; + vty->node = next_node; /* Success ! */ + } else { + vty->fail++; + if (vty->fail >= 3) { + if (vty->node == AUTH_NODE) { + vty_out(vty, + "%% Bad passwords, too many failures!\n"); + vty->status = VTY_CLOSE; + } else { + /* AUTH_ENABLE_NODE */ + vty->fail = 0; + vty_out(vty, + "%% Bad enable passwords, too many failures!\n"); + vty->status = VTY_CLOSE; + } + } + } +} + +/* Command execution over the vty interface. */ +static int vty_command(struct vty *vty, char *buf) +{ + int ret; + const char *protocolname; + char *cp = NULL; + + assert(vty); + + /* + * Log non empty command lines + */ + if (vty_log_commands && + strncmp(buf, "echo PING", strlen("echo PING")) != 0) + cp = buf; + if (cp != NULL) { + /* Skip white spaces. */ + while (isspace((unsigned char)*cp) && *cp != '\0') + cp++; + } + if (cp != NULL && *cp != '\0') { + char vty_str[VTY_BUFSIZ]; + char prompt_str[VTY_BUFSIZ]; + + /* format the base vty info */ + snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s", vty->fd, + vty->address); + + /* format the prompt */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* prompt formatting has a %s in the cmd_node prompt string */ + snprintf(prompt_str, sizeof(prompt_str), cmd_prompt(vty->node), + vty_str); +#pragma GCC diagnostic pop + + /* now log the command */ + zlog_notice("%s%s", prompt_str, buf); + } + + RUSAGE_T before; + RUSAGE_T after; + unsigned long walltime, cputime; + + /* cmd_execute() may change cputime_enabled if we're executing the + * "service cputime-stats" command, which can result in nonsensical + * and very confusing warnings + */ + bool cputime_enabled_here = cputime_enabled; + + GETRUSAGE(&before); + + ret = cmd_execute(vty, buf, NULL, 0); + + GETRUSAGE(&after); + + walltime = event_consumed_time(&after, &before, &cputime); + + if (cputime_enabled_here && cputime_enabled && cputime_threshold + && cputime > cputime_threshold) + /* Warn about CPU hog that must be fixed. */ + flog_warn(EC_LIB_SLOW_THREAD_CPU, + "CPU HOG: command took %lums (cpu time %lums): %s", + walltime / 1000, cputime / 1000, buf); + else if (walltime_threshold && walltime > walltime_threshold) + flog_warn(EC_LIB_SLOW_THREAD_WALL, + "STARVATION: command took %lums (cpu time %lums): %s", + walltime / 1000, cputime / 1000, buf); + + /* Get the name of the protocol if any */ + protocolname = frr_protoname; + + if (ret != CMD_SUCCESS) + switch (ret) { + case CMD_WARNING: + if (vty->type == VTY_FILE) + vty_out(vty, "Warning...\n"); + break; + case CMD_ERR_AMBIGUOUS: + vty_out(vty, "%% Ambiguous command.\n"); + break; + case CMD_ERR_NO_MATCH: + vty_out(vty, "%% [%s] Unknown command: %s\n", + protocolname, buf); + break; + case CMD_ERR_INCOMPLETE: + vty_out(vty, "%% Command incomplete.\n"); + break; + } + + return ret; +} + +static const char telnet_backward_char = 0x08; +static const char telnet_space_char = ' '; + +/* Basic function to write buffer to vty. */ +static void vty_write(struct vty *vty, const char *buf, size_t nbytes) +{ + if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) + return; + + /* Should we do buffering here ? And make vty_flush (vty) ? */ + buffer_put(vty->obuf, buf, nbytes); +} + +/* Basic function to insert character into vty. */ +static void vty_self_insert(struct vty *vty, char c) +{ + int i; + int length; + + if (vty->length + 1 >= VTY_BUFSIZ) + return; + + length = vty->length - vty->cp; + memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); + vty->buf[vty->cp] = c; + + vty_write(vty, &vty->buf[vty->cp], length + 1); + for (i = 0; i < length; i++) + vty_write(vty, &telnet_backward_char, 1); + + vty->cp++; + vty->length++; + + vty->buf[vty->length] = '\0'; +} + +/* Self insert character 'c' in overwrite mode. */ +static void vty_self_insert_overwrite(struct vty *vty, char c) +{ + if (vty->cp == vty->length) { + vty_self_insert(vty, c); + return; + } + + vty->buf[vty->cp++] = c; + vty_write(vty, &c, 1); +} + +/** + * Insert a string into vty->buf at the current cursor position. + * + * If the resultant string would be larger than VTY_BUFSIZ it is + * truncated to fit. + */ +static void vty_insert_word_overwrite(struct vty *vty, char *str) +{ + if (vty->cp == VTY_BUFSIZ) + return; + + size_t nwrite = MIN((int)strlen(str), VTY_BUFSIZ - vty->cp - 1); + memcpy(&vty->buf[vty->cp], str, nwrite); + vty->cp += nwrite; + vty->length = MAX(vty->cp, vty->length); + vty->buf[vty->length] = '\0'; + vty_write(vty, str, nwrite); +} + +/* Forward character. */ +static void vty_forward_char(struct vty *vty) +{ + if (vty->cp < vty->length) { + vty_write(vty, &vty->buf[vty->cp], 1); + vty->cp++; + } +} + +/* Backward character. */ +static void vty_backward_char(struct vty *vty) +{ + if (vty->cp > 0) { + vty->cp--; + vty_write(vty, &telnet_backward_char, 1); + } +} + +/* Move to the beginning of the line. */ +static void vty_beginning_of_line(struct vty *vty) +{ + while (vty->cp) + vty_backward_char(vty); +} + +/* Move to the end of the line. */ +static void vty_end_of_line(struct vty *vty) +{ + while (vty->cp < vty->length) + vty_forward_char(vty); +} + +static void vty_kill_line_from_beginning(struct vty *); +static void vty_redraw_line(struct vty *); + +/* Print command line history. This function is called from + vty_next_line and vty_previous_line. */ +static void vty_history_print(struct vty *vty) +{ + int length; + + vty_kill_line_from_beginning(vty); + + /* Get previous line from history buffer */ + length = strlen(vty->hist[vty->hp]); + memcpy(vty->buf, vty->hist[vty->hp], length); + vty->cp = vty->length = length; + vty->buf[vty->length] = '\0'; + + /* Redraw current line */ + vty_redraw_line(vty); +} + +/* Show next command line history. */ +static void vty_next_line(struct vty *vty) +{ + int try_index; + + if (vty->hp == vty->hindex) + return; + + /* Try is there history exist or not. */ + try_index = vty->hp; + if (try_index == (VTY_MAXHIST - 1)) + try_index = 0; + else + try_index++; + + /* If there is not history return. */ + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + vty_history_print(vty); +} + +/* Show previous command line history. */ +static void vty_previous_line(struct vty *vty) +{ + int try_index; + + try_index = vty->hp; + if (try_index == 0) + try_index = VTY_MAXHIST - 1; + else + try_index--; + + if (vty->hist[try_index] == NULL) + return; + else + vty->hp = try_index; + + vty_history_print(vty); +} + +/* This function redraw all of the command line character. */ +static void vty_redraw_line(struct vty *vty) +{ + vty_write(vty, vty->buf, vty->length); + vty->cp = vty->length; +} + +/* Forward word. */ +static void vty_forward_word(struct vty *vty) +{ + while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') + vty_forward_char(vty); + + while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') + vty_forward_char(vty); +} + +/* Backward word without skipping training space. */ +static void vty_backward_pure_word(struct vty *vty) +{ + while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') + vty_backward_char(vty); +} + +/* Backward word. */ +static void vty_backward_word(struct vty *vty) +{ + while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') + vty_backward_char(vty); + + while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') + vty_backward_char(vty); +} + +/* When '^D' is typed at the beginning of the line we move to the down + level. */ +static void vty_down_level(struct vty *vty) +{ + vty_out(vty, "\n"); + cmd_exit(vty); + vty_prompt(vty); + vty->cp = 0; +} + +/* When '^Z' is received from vty, move down to the enable mode. */ +static void vty_end_config(struct vty *vty) +{ + vty_out(vty, "\n"); + + if (vty->config) { + vty_config_exit(vty); + vty->node = ENABLE_NODE; + } + + vty_prompt(vty); + vty->cp = 0; +} + +/* Delete a character at the current point. */ +static void vty_delete_char(struct vty *vty) +{ + int i; + int size; + + if (vty->length == 0) { + vty_down_level(vty); + return; + } + + if (vty->cp == vty->length) + return; /* completion need here? */ + + size = vty->length - vty->cp; + + vty->length--; + memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); + vty->buf[vty->length] = '\0'; + + if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) + return; + + vty_write(vty, &vty->buf[vty->cp], size - 1); + vty_write(vty, &telnet_space_char, 1); + + for (i = 0; i < size; i++) + vty_write(vty, &telnet_backward_char, 1); +} + +/* Delete a character before the point. */ +static void vty_delete_backward_char(struct vty *vty) +{ + if (vty->cp == 0) + return; + + vty_backward_char(vty); + vty_delete_char(vty); +} + +/* Kill rest of line from current point. */ +static void vty_kill_line(struct vty *vty) +{ + int i; + int size; + + size = vty->length - vty->cp; + + if (size == 0) + return; + + for (i = 0; i < size; i++) + vty_write(vty, &telnet_space_char, 1); + for (i = 0; i < size; i++) + vty_write(vty, &telnet_backward_char, 1); + + memset(&vty->buf[vty->cp], 0, size); + vty->length = vty->cp; +} + +/* Kill line from the beginning. */ +static void vty_kill_line_from_beginning(struct vty *vty) +{ + vty_beginning_of_line(vty); + vty_kill_line(vty); +} + +/* Delete a word before the point. */ +static void vty_forward_kill_word(struct vty *vty) +{ + while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') + vty_delete_char(vty); + while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') + vty_delete_char(vty); +} + +/* Delete a word before the point. */ +static void vty_backward_kill_word(struct vty *vty) +{ + while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') + vty_delete_backward_char(vty); + while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') + vty_delete_backward_char(vty); +} + +/* Transpose chars before or at the point. */ +static void vty_transpose_chars(struct vty *vty) +{ + char c1, c2; + + /* If length is short or point is near by the beginning of line then + return. */ + if (vty->length < 2 || vty->cp < 1) + return; + + /* In case of point is located at the end of the line. */ + if (vty->cp == vty->length) { + c1 = vty->buf[vty->cp - 1]; + c2 = vty->buf[vty->cp - 2]; + + vty_backward_char(vty); + vty_backward_char(vty); + vty_self_insert_overwrite(vty, c1); + vty_self_insert_overwrite(vty, c2); + } else { + c1 = vty->buf[vty->cp]; + c2 = vty->buf[vty->cp - 1]; + + vty_backward_char(vty); + vty_self_insert_overwrite(vty, c1); + vty_self_insert_overwrite(vty, c2); + } +} + +/* Do completion at vty interface. */ +static void vty_complete_command(struct vty *vty) +{ + int i; + int ret; + char **matched = NULL; + vector vline; + + if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) + return; + + vline = cmd_make_strvec(vty->buf); + if (vline == NULL) + return; + + /* In case of 'help \t'. */ + if (isspace((unsigned char)vty->buf[vty->length - 1])) + vector_set(vline, NULL); + + matched = cmd_complete_command(vline, vty, &ret); + + cmd_free_strvec(vline); + + vty_out(vty, "\n"); + switch (ret) { + case CMD_ERR_AMBIGUOUS: + vty_out(vty, "%% Ambiguous command.\n"); + vty_prompt(vty); + vty_redraw_line(vty); + break; + case CMD_ERR_NO_MATCH: + /* vty_out (vty, "%% There is no matched command.\n"); */ + vty_prompt(vty); + vty_redraw_line(vty); + break; + case CMD_COMPLETE_FULL_MATCH: + if (!matched[0]) { + /* 2016-11-28 equinox -- need to debug, SEGV here */ + vty_out(vty, "%% CLI BUG: FULL_MATCH with NULL str\n"); + vty_prompt(vty); + vty_redraw_line(vty); + break; + } + vty_prompt(vty); + vty_redraw_line(vty); + vty_backward_pure_word(vty); + vty_insert_word_overwrite(vty, matched[0]); + vty_self_insert(vty, ' '); + XFREE(MTYPE_COMPLETION, matched[0]); + break; + case CMD_COMPLETE_MATCH: + vty_prompt(vty); + vty_redraw_line(vty); + vty_backward_pure_word(vty); + vty_insert_word_overwrite(vty, matched[0]); + XFREE(MTYPE_COMPLETION, matched[0]); + break; + case CMD_COMPLETE_LIST_MATCH: + for (i = 0; matched[i] != NULL; i++) { + if (i != 0 && ((i % 6) == 0)) + vty_out(vty, "\n"); + vty_out(vty, "%-10s ", matched[i]); + XFREE(MTYPE_COMPLETION, matched[i]); + } + vty_out(vty, "\n"); + + vty_prompt(vty); + vty_redraw_line(vty); + break; + case CMD_ERR_NOTHING_TODO: + vty_prompt(vty); + vty_redraw_line(vty); + break; + default: + break; + } + XFREE(MTYPE_TMP, matched); +} + +static void vty_describe_fold(struct vty *vty, int cmd_width, + unsigned int desc_width, struct cmd_token *token) +{ + char *buf; + const char *cmd, *p; + int pos; + + cmd = token->text; + + if (desc_width <= 0) { + vty_out(vty, " %-*s %s\n", cmd_width, cmd, token->desc); + return; + } + + buf = XCALLOC(MTYPE_TMP, strlen(token->desc) + 1); + + for (p = token->desc; strlen(p) > desc_width; p += pos + 1) { + for (pos = desc_width; pos > 0; pos--) + if (*(p + pos) == ' ') + break; + + if (pos == 0) + break; + + memcpy(buf, p, pos); + buf[pos] = '\0'; + vty_out(vty, " %-*s %s\n", cmd_width, cmd, buf); + + cmd = ""; + } + + vty_out(vty, " %-*s %s\n", cmd_width, cmd, p); + + XFREE(MTYPE_TMP, buf); +} + +/* Describe matched command function. */ +static void vty_describe_command(struct vty *vty) +{ + int ret; + vector vline; + vector describe; + unsigned int i, width, desc_width; + struct cmd_token *token, *token_cr = NULL; + + vline = cmd_make_strvec(vty->buf); + + /* In case of '> ?'. */ + if (vline == NULL) { + vline = vector_init(1); + vector_set(vline, NULL); + } else if (isspace((unsigned char)vty->buf[vty->length - 1])) + vector_set(vline, NULL); + + describe = cmd_describe_command(vline, vty, &ret); + + vty_out(vty, "\n"); + + /* Ambiguous error. */ + switch (ret) { + case CMD_ERR_AMBIGUOUS: + vty_out(vty, "%% Ambiguous command.\n"); + goto out; + break; + case CMD_ERR_NO_MATCH: + vty_out(vty, "%% There is no matched command.\n"); + goto out; + break; + } + + /* Get width of command string. */ + width = 0; + for (i = 0; i < vector_active(describe); i++) + if ((token = vector_slot(describe, i)) != NULL) { + unsigned int len; + + if (token->text[0] == '\0') + continue; + + len = strlen(token->text); + + if (width < len) + width = len; + } + + /* Get width of description string. */ + desc_width = vty->width - (width + 6); + + /* Print out description. */ + for (i = 0; i < vector_active(describe); i++) + if ((token = vector_slot(describe, i)) != NULL) { + if (token->text[0] == '\0') + continue; + + if (strcmp(token->text, CMD_CR_TEXT) == 0) { + token_cr = token; + continue; + } + + if (!token->desc) + vty_out(vty, " %-s\n", token->text); + else if (desc_width >= strlen(token->desc)) + vty_out(vty, " %-*s %s\n", width, token->text, + token->desc); + else + vty_describe_fold(vty, width, desc_width, + token); + + if (IS_VARYING_TOKEN(token->type)) { + const char *ref = vector_slot( + vline, vector_active(vline) - 1); + + vector varcomps = vector_init(VECTOR_MIN_SIZE); + cmd_variable_complete(token, ref, varcomps); + + if (vector_active(varcomps) > 0) { + char *ac = cmd_variable_comp2str( + varcomps, vty->width); + vty_out(vty, "%s\n", ac); + XFREE(MTYPE_TMP, ac); + } + + vector_free(varcomps); + } + } + + if ((token = token_cr)) { + if (!token->desc) + vty_out(vty, " %-s\n", token->text); + else if (desc_width >= strlen(token->desc)) + vty_out(vty, " %-*s %s\n", width, token->text, + token->desc); + else + vty_describe_fold(vty, width, desc_width, token); + } + +out: + cmd_free_strvec(vline); + if (describe) + vector_free(describe); + + vty_prompt(vty); + vty_redraw_line(vty); +} + +static void vty_clear_buf(struct vty *vty) +{ + memset(vty->buf, 0, vty->max); +} + +/* ^C stop current input and do not add command line to the history. */ +static void vty_stop_input(struct vty *vty) +{ + vty->cp = vty->length = 0; + vty_clear_buf(vty); + vty_out(vty, "\n"); + + if (vty->config) { + vty_config_exit(vty); + vty->node = ENABLE_NODE; + } + + vty_prompt(vty); + + /* Set history pointer to the latest one. */ + vty->hp = vty->hindex; +} + +/* Add current command line to the history buffer. */ +static void vty_hist_add(struct vty *vty) +{ + int index; + + if (vty->length == 0) + return; + + index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; + + /* Ignore the same string as previous one. */ + if (vty->hist[index]) + if (strcmp(vty->buf, vty->hist[index]) == 0) { + vty->hp = vty->hindex; + return; + } + + /* Insert history entry. */ + XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]); + vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf); + + /* History index rotation. */ + vty->hindex++; + if (vty->hindex == VTY_MAXHIST) + vty->hindex = 0; + + vty->hp = vty->hindex; +} + +/* #define TELNET_OPTION_DEBUG */ + +/* Get telnet window size. */ +static int vty_telnet_option(struct vty *vty, unsigned char *buf, int nbytes) +{ +#ifdef TELNET_OPTION_DEBUG + int i; + + for (i = 0; i < nbytes; i++) { + switch (buf[i]) { + case IAC: + vty_out(vty, "IAC "); + break; + case WILL: + vty_out(vty, "WILL "); + break; + case WONT: + vty_out(vty, "WONT "); + break; + case DO: + vty_out(vty, "DO "); + break; + case DONT: + vty_out(vty, "DONT "); + break; + case SB: + vty_out(vty, "SB "); + break; + case SE: + vty_out(vty, "SE "); + break; + case TELOPT_ECHO: + vty_out(vty, "TELOPT_ECHO \n"); + break; + case TELOPT_SGA: + vty_out(vty, "TELOPT_SGA \n"); + break; + case TELOPT_NAWS: + vty_out(vty, "TELOPT_NAWS \n"); + break; + default: + vty_out(vty, "%x ", buf[i]); + break; + } + } + vty_out(vty, "\n"); + +#endif /* TELNET_OPTION_DEBUG */ + + switch (buf[0]) { + case SB: + vty->sb_len = 0; + vty->iac_sb_in_progress = 1; + return 0; + case SE: { + if (!vty->iac_sb_in_progress) + return 0; + + if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) { + vty->iac_sb_in_progress = 0; + return 0; + } + switch (vty->sb_buf[0]) { + case TELOPT_NAWS: + if (vty->sb_len != TELNET_NAWS_SB_LEN) + flog_err( + EC_LIB_SYSTEM_CALL, + "RFC 1073 violation detected: telnet NAWS option should send %d characters, but we received %lu", + TELNET_NAWS_SB_LEN, + (unsigned long)vty->sb_len); + else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) + flog_err( + EC_LIB_DEVELOPMENT, + "Bug detected: sizeof(vty->sb_buf) %lu < %d, too small to handle the telnet NAWS option", + (unsigned long)sizeof(vty->sb_buf), + TELNET_NAWS_SB_LEN); + else { + vty->width = ((vty->sb_buf[1] << 8) + | vty->sb_buf[2]); + vty->height = ((vty->sb_buf[3] << 8) + | vty->sb_buf[4]); +#ifdef TELNET_OPTION_DEBUG + vty_out(vty, + "TELNET NAWS window size negotiation completed: width %d, height %d\n", + vty->width, vty->height); +#endif + } + break; + } + vty->iac_sb_in_progress = 0; + return 0; + } + default: + break; + } + return 1; +} + +/* Execute current command line. */ +static int vty_execute(struct vty *vty) +{ + int ret; + + ret = CMD_SUCCESS; + + switch (vty->node) { + case AUTH_NODE: + case AUTH_ENABLE_NODE: + vty_auth(vty, vty->buf); + break; + default: + ret = vty_command(vty, vty->buf); + if (vty->type == VTY_TERM) + vty_hist_add(vty); + break; + } + + /* Clear command line buffer. */ + vty->cp = vty->length = 0; + vty_clear_buf(vty); + + if (vty->status != VTY_CLOSE) + vty_prompt(vty); + + return ret; +} + +#define CONTROL(X) ((X) - '@') +#define VTY_NORMAL 0 +#define VTY_PRE_ESCAPE 1 +#define VTY_ESCAPE 2 +#define VTY_CR 3 + +/* Escape character command map. */ +static void vty_escape_map(unsigned char c, struct vty *vty) +{ + switch (c) { + case ('A'): + vty_previous_line(vty); + break; + case ('B'): + vty_next_line(vty); + break; + case ('C'): + vty_forward_char(vty); + break; + case ('D'): + vty_backward_char(vty); + break; + default: + break; + } + + /* Go back to normal mode. */ + vty->escape = VTY_NORMAL; +} + +/* Quit print out to the buffer. */ +static void vty_buffer_reset(struct vty *vty) +{ + buffer_reset(vty->obuf); + buffer_reset(vty->lbuf); + vty_prompt(vty); + vty_redraw_line(vty); +} + +/* Read data via vty socket. */ +static void vty_read(struct event *thread) +{ + int i; + int nbytes; + unsigned char buf[VTY_READ_BUFSIZ]; + + struct vty *vty = EVENT_ARG(thread); + + /* Read raw data from socket */ + if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) { + if (nbytes < 0) { + if (ERRNO_IO_RETRY(errno)) { + vty_event(VTY_READ, vty); + return; + } + flog_err( + EC_LIB_SOCKET, + "%s: read error on vty client fd %d, closing: %s", + __func__, vty->fd, safe_strerror(errno)); + buffer_reset(vty->obuf); + buffer_reset(vty->lbuf); + } + vty->status = VTY_CLOSE; + } + + for (i = 0; i < nbytes; i++) { + if (buf[i] == IAC) { + if (!vty->iac) { + vty->iac = 1; + continue; + } else { + vty->iac = 0; + } + } + + if (vty->iac_sb_in_progress && !vty->iac) { + if (vty->sb_len < sizeof(vty->sb_buf)) + vty->sb_buf[vty->sb_len] = buf[i]; + vty->sb_len++; + continue; + } + + if (vty->iac) { + /* In case of telnet command */ + int ret = 0; + ret = vty_telnet_option(vty, buf + i, nbytes - i); + vty->iac = 0; + i += ret; + continue; + } + + + if (vty->status == VTY_MORE) { + switch (buf[i]) { + case CONTROL('C'): + case 'q': + case 'Q': + vty_buffer_reset(vty); + break; + default: + break; + } + continue; + } + + /* Escape character. */ + if (vty->escape == VTY_ESCAPE) { + vty_escape_map(buf[i], vty); + continue; + } + + /* Pre-escape status. */ + if (vty->escape == VTY_PRE_ESCAPE) { + switch (buf[i]) { + case '[': + vty->escape = VTY_ESCAPE; + break; + case 'b': + vty_backward_word(vty); + vty->escape = VTY_NORMAL; + break; + case 'f': + vty_forward_word(vty); + vty->escape = VTY_NORMAL; + break; + case 'd': + vty_forward_kill_word(vty); + vty->escape = VTY_NORMAL; + break; + case CONTROL('H'): + case 0x7f: + vty_backward_kill_word(vty); + vty->escape = VTY_NORMAL; + break; + default: + vty->escape = VTY_NORMAL; + break; + } + continue; + } + + if (vty->escape == VTY_CR) { + /* if we get CR+NL, the NL results in an extra empty + * prompt line being printed without this; just drop + * the NL if it immediately follows CR. + */ + vty->escape = VTY_NORMAL; + + if (buf[i] == '\n') + continue; + } + + switch (buf[i]) { + case CONTROL('A'): + vty_beginning_of_line(vty); + break; + case CONTROL('B'): + vty_backward_char(vty); + break; + case CONTROL('C'): + vty_stop_input(vty); + break; + case CONTROL('D'): + vty_delete_char(vty); + break; + case CONTROL('E'): + vty_end_of_line(vty); + break; + case CONTROL('F'): + vty_forward_char(vty); + break; + case CONTROL('H'): + case 0x7f: + vty_delete_backward_char(vty); + break; + case CONTROL('K'): + vty_kill_line(vty); + break; + case CONTROL('N'): + vty_next_line(vty); + break; + case CONTROL('P'): + vty_previous_line(vty); + break; + case CONTROL('T'): + vty_transpose_chars(vty); + break; + case CONTROL('U'): + vty_kill_line_from_beginning(vty); + break; + case CONTROL('W'): + vty_backward_kill_word(vty); + break; + case CONTROL('Z'): + vty_end_config(vty); + break; + case '\r': + vty->escape = VTY_CR; + fallthrough; + case '\n': + vty_out(vty, "\n"); + buffer_flush_available(vty->obuf, vty->wfd); + vty_execute(vty); + + if (vty->pass_fd != -1) { + close(vty->pass_fd); + vty->pass_fd = -1; + } + break; + case '\t': + vty_complete_command(vty); + break; + case '?': + if (vty->node == AUTH_NODE + || vty->node == AUTH_ENABLE_NODE) + vty_self_insert(vty, buf[i]); + else + vty_describe_command(vty); + break; + case '\033': + if (i + 1 < nbytes && buf[i + 1] == '[') { + vty->escape = VTY_ESCAPE; + i++; + } else + vty->escape = VTY_PRE_ESCAPE; + break; + default: + if (buf[i] > 31 && buf[i] < 127) + vty_self_insert(vty, buf[i]); + break; + } + } + + /* Check status. */ + if (vty->status == VTY_CLOSE) + vty_close(vty); + else { + vty_event(VTY_WRITE, vty); + vty_event(VTY_READ, vty); + } +} + +/* Flush buffer to the vty. */ +static void vty_flush(struct event *thread) +{ + int erase; + buffer_status_t flushrc; + struct vty *vty = EVENT_ARG(thread); + + /* Tempolary disable read thread. */ + if (vty->lines == 0) + EVENT_OFF(vty->t_read); + + /* Function execution continue. */ + erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); + + /* N.B. if width is 0, that means we don't know the window size. */ + if ((vty->lines == 0) || (vty->width == 0) || (vty->height == 0)) + flushrc = buffer_flush_available(vty->obuf, vty->wfd); + else if (vty->status == VTY_MORELINE) + flushrc = buffer_flush_window(vty->obuf, vty->wfd, vty->width, + 1, erase, 0); + else + flushrc = buffer_flush_window( + vty->obuf, vty->wfd, vty->width, + vty->lines >= 0 ? vty->lines : vty->height, erase, 0); + switch (flushrc) { + case BUFFER_ERROR: + zlog_info("buffer_flush failed on vty client fd %d/%d, closing", + vty->fd, vty->wfd); + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty_close(vty); + return; + case BUFFER_EMPTY: + if (vty->status == VTY_CLOSE) + vty_close(vty); + else { + vty->status = VTY_NORMAL; + if (vty->lines == 0) + vty_event(VTY_READ, vty); + } + break; + case BUFFER_PENDING: + /* There is more data waiting to be written. */ + vty->status = VTY_MORE; + if (vty->lines == 0) + vty_event(VTY_WRITE, vty); + break; + } +} + +/* Allocate new vty struct. */ +struct vty *vty_new(void) +{ + struct vty *new = XCALLOC(MTYPE_VTY, sizeof(struct vty)); + + new->fd = new->wfd = -1; + new->of = stdout; + new->lbuf = buffer_new(0); + new->obuf = buffer_new(0); /* Use default buffer size. */ + new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ); + new->max = VTY_BUFSIZ; + new->pass_fd = -1; + + if (mgmt_fe_client) { + if (!mgmt_client_id_next) + mgmt_client_id_next++; + new->mgmt_client_id = mgmt_client_id_next++; + new->mgmt_session_id = 0; + mgmt_fe_create_client_session( + mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new); + /* we short-circuit create the session so it must be set now */ + assertf(new->mgmt_session_id != 0, + "Failed to create client session for VTY"); + } + + return new; +} + + +/* allocate and initialise vty */ +static struct vty *vty_new_init(int vty_sock) +{ + struct vty *vty; + + vty = vty_new(); + vty->fd = vty_sock; + vty->wfd = vty_sock; + vty->type = VTY_TERM; + vty->node = AUTH_NODE; + vty->fail = 0; + vty->cp = 0; + vty_clear_buf(vty); + vty->length = 0; + memset(vty->hist, 0, sizeof(vty->hist)); + vty->hp = 0; + vty->hindex = 0; + vty->xpath_index = 0; + memset(vty->xpath, 0, sizeof(vty->xpath)); + vty->private_config = false; + vty->candidate_config = vty_shared_candidate_config; + vty->status = VTY_NORMAL; + vty->lines = -1; + vty->iac = 0; + vty->iac_sb_in_progress = 0; + vty->sb_len = 0; + + vtys_add_tail(vty_sessions, vty); + + return vty; +} + +/* Create new vty structure. */ +static struct vty *vty_create(int vty_sock, union sockunion *su) +{ + char buf[SU_ADDRSTRLEN]; + struct vty *vty; + + sockunion2str(su, buf, SU_ADDRSTRLEN); + + /* Allocate new vty structure and set up default values. */ + vty = vty_new_init(vty_sock); + + /* configurable parameters not part of basic init */ + vty->v_timeout = vty_timeout_val; + strlcpy(vty->address, buf, sizeof(vty->address)); + if (no_password_check) { + if (host.advanced) + vty->node = ENABLE_NODE; + else + vty->node = VIEW_NODE; + } + if (host.lines >= 0) + vty->lines = host.lines; + + if (!no_password_check) { + /* Vty is not available if password isn't set. */ + if (host.password == NULL && host.password_encrypt == NULL) { + vty_out(vty, "Vty password is not set.\n"); + vty->status = VTY_CLOSE; + vty_close(vty); + return NULL; + } + } + + /* Say hello to the world. */ + vty_hello(vty); + if (!no_password_check) + vty_out(vty, "\nUser Access Verification\n\n"); + + /* Setting up terminal. */ + vty_will_echo(vty); + vty_will_suppress_go_ahead(vty); + + vty_dont_linemode(vty); + vty_do_window_size(vty); + /* vty_dont_lflow_ahead (vty); */ + + vty_prompt(vty); + + /* Add read/write thread. */ + vty_event(VTY_WRITE, vty); + vty_event(VTY_READ, vty); + + return vty; +} + +/* create vty for stdio */ +static struct termios stdio_orig_termios; +static struct vty *stdio_vty = NULL; +static bool stdio_termios = false; +static void (*stdio_vty_atclose)(int isexit); + +static void vty_stdio_reset(int isexit) +{ + if (stdio_vty) { + if (stdio_termios) + tcsetattr(0, TCSANOW, &stdio_orig_termios); + stdio_termios = false; + + stdio_vty = NULL; + + if (stdio_vty_atclose) + stdio_vty_atclose(isexit); + stdio_vty_atclose = NULL; + } +} + +static void vty_stdio_atexit(void) +{ + vty_stdio_reset(1); +} + +void vty_stdio_suspend(void) +{ + if (!stdio_vty) + return; + + EVENT_OFF(stdio_vty->t_write); + EVENT_OFF(stdio_vty->t_read); + EVENT_OFF(stdio_vty->t_timeout); + + if (stdio_termios) + tcsetattr(0, TCSANOW, &stdio_orig_termios); + stdio_termios = false; +} + +void vty_stdio_resume(void) +{ + if (!stdio_vty) + return; + + if (!tcgetattr(0, &stdio_orig_termios)) { + struct termios termios; + + termios = stdio_orig_termios; + termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR + | IGNCR | ICRNL | IXON); + termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); + termios.c_cflag &= ~(CSIZE | PARENB); + termios.c_cflag |= CS8; + tcsetattr(0, TCSANOW, &termios); + stdio_termios = true; + } + + vty_prompt(stdio_vty); + + /* Add read/write thread. */ + vty_event(VTY_WRITE, stdio_vty); + vty_event(VTY_READ, stdio_vty); +} + +void vty_stdio_close(void) +{ + if (!stdio_vty) + return; + vty_close(stdio_vty); +} + +struct vty *vty_stdio(void (*atclose)(int isexit)) +{ + struct vty *vty; + + /* refuse creating two vtys on stdio */ + if (stdio_vty) + return NULL; + + vty = stdio_vty = vty_new_init(0); + stdio_vty_atclose = atclose; + vty->wfd = 1; + + /* always have stdio vty in a known _unchangeable_ state, don't want + * config + * to have any effect here to make sure scripting this works as intended + */ + vty->node = ENABLE_NODE; + vty->v_timeout = 0; + strlcpy(vty->address, "console", sizeof(vty->address)); + + vty_stdio_resume(); + return vty; +} + +/* Accept connection from the network. */ +static void vty_accept(struct event *thread) +{ + struct vty_serv *vtyserv = EVENT_ARG(thread); + int vty_sock; + union sockunion su; + int ret; + unsigned int on; + int accept_sock = vtyserv->sock; + struct prefix p; + struct access_list *acl = NULL; + + /* We continue hearing vty socket. */ + vty_event_serv(VTY_SERV, vtyserv); + + memset(&su, 0, sizeof(union sockunion)); + + /* We can handle IPv4 or IPv6 socket. */ + vty_sock = sockunion_accept(accept_sock, &su); + if (vty_sock < 0) { + flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", + safe_strerror(errno)); + return; + } + set_nonblocking(vty_sock); + set_cloexec(vty_sock); + + if (!sockunion2hostprefix(&su, &p)) { + close(vty_sock); + zlog_info("Vty unable to convert prefix from sockunion %pSU", + &su); + return; + } + + /* VTY's accesslist apply. */ + if (p.family == AF_INET && vty_accesslist_name) { + if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) + && (access_list_apply(acl, &p) == FILTER_DENY)) { + zlog_info("Vty connection refused from %pSU", &su); + close(vty_sock); + return; + } + } + + /* VTY's ipv6 accesslist apply. */ + if (p.family == AF_INET6 && vty_ipv6_accesslist_name) { + if ((acl = access_list_lookup(AFI_IP6, + vty_ipv6_accesslist_name)) + && (access_list_apply(acl, &p) == FILTER_DENY)) { + zlog_info("Vty connection refused from %pSU", &su); + close(vty_sock); + return; + } + } + + on = 1; + ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, + sizeof(on)); + if (ret < 0) + zlog_info("can't set sockopt to vty_sock : %s", + safe_strerror(errno)); + + zlog_info("Vty connection from %pSU", &su); + + vty_create(vty_sock, &su); +} + +static void vty_serv_sock_addrinfo(const char *hostname, unsigned short port) +{ + int ret; + struct addrinfo req; + struct addrinfo *ainfo; + struct addrinfo *ainfo_save; + int sock; + char port_str[BUFSIZ]; + + memset(&req, 0, sizeof(req)); + req.ai_flags = AI_PASSIVE; + req.ai_family = AF_UNSPEC; + req.ai_socktype = SOCK_STREAM; + snprintf(port_str, sizeof(port_str), "%d", port); + port_str[sizeof(port_str) - 1] = '\0'; + + ret = getaddrinfo(hostname, port_str, &req, &ainfo); + + if (ret != 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s", + gai_strerror(ret)); + exit(1); + } + + ainfo_save = ainfo; + + do { + struct vty_serv *vtyserv; + + if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) + continue; + + sock = socket(ainfo->ai_family, ainfo->ai_socktype, + ainfo->ai_protocol); + if (sock < 0) + continue; + + sockopt_v6only(ainfo->ai_family, sock); + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + set_cloexec(sock); + + ret = bind(sock, ainfo->ai_addr, ainfo->ai_addrlen); + if (ret < 0) { + close(sock); /* Avoid sd leak. */ + continue; + } + + ret = listen(sock, 3); + if (ret < 0) { + close(sock); /* Avoid sd leak. */ + continue; + } + + vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv)); + vtyserv->sock = sock; + vtyservs_add_tail(vty_servs, vtyserv); + + vty_event_serv(VTY_SERV, vtyserv); + } while ((ainfo = ainfo->ai_next) != NULL); + + freeaddrinfo(ainfo_save); +} + +#ifdef VTYSH +/* For sockaddr_un. */ +#include + +/* VTY shell UNIX domain socket. */ +static void vty_serv_un(const char *path) +{ + struct vty_serv *vtyserv; + int ret; + int sock, len; + struct sockaddr_un serv; + mode_t old_mask; + struct zprivs_ids_t ids; + + /* First of all, unlink existing socket */ + unlink(path); + + /* Set umask */ + old_mask = umask(0007); + + /* Make UNIX domain socket. */ + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + flog_err_sys(EC_LIB_SOCKET, + "Cannot create unix stream socket: %s", + safe_strerror(errno)); + return; + } + + /* Make server socket. */ + memset(&serv, 0, sizeof(serv)); + serv.sun_family = AF_UNIX; + strlcpy(serv.sun_path, path, sizeof(serv.sun_path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + len = serv.sun_len = SUN_LEN(&serv); +#else + len = sizeof(serv.sun_family) + strlen(serv.sun_path); +#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ + + set_cloexec(sock); + + ret = bind(sock, (struct sockaddr *)&serv, len); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path, + safe_strerror(errno)); + close(sock); /* Avoid sd leak. */ + return; + } + + ret = listen(sock, 5); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock, + safe_strerror(errno)); + close(sock); /* Avoid sd leak. */ + return; + } + + umask(old_mask); + + zprivs_get_ids(&ids); + + /* Hack: ids.gid_vty is actually a uint, but we stored -1 in it + earlier for the case when we don't need to chown the file + type casting it here to make a compare */ + if ((int)ids.gid_vty > 0) { + /* set group of socket */ + if (chown(path, -1, ids.gid_vty)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "vty_serv_un: could chown socket, %s", + safe_strerror(errno)); + } + } + + vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv)); + vtyserv->sock = sock; + vtyserv->vtysh = true; + vtyservs_add_tail(vty_servs, vtyserv); + + vty_event_serv(VTYSH_SERV, vtyserv); +} + +/* #define VTYSH_DEBUG 1 */ + +static void vtysh_accept(struct event *thread) +{ + struct vty_serv *vtyserv = EVENT_ARG(thread); + int accept_sock = vtyserv->sock; + int sock; + int client_len; + struct sockaddr_un client; + struct vty *vty; + + vty_event_serv(VTYSH_SERV, vtyserv); + + memset(&client, 0, sizeof(client)); + client_len = sizeof(struct sockaddr_un); + + sock = accept(accept_sock, (struct sockaddr *)&client, + (socklen_t *)&client_len); + + if (sock < 0) { + flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", + safe_strerror(errno)); + return; + } + + if (set_nonblocking(sock) < 0) { + flog_err( + EC_LIB_SOCKET, + "vtysh_accept: could not set vty socket %d to non-blocking, %s, closing", + sock, safe_strerror(errno)); + close(sock); + return; + } + set_cloexec(sock); + +#ifdef VTYSH_DEBUG + printf("VTY shell accept\n"); +#endif /* VTYSH_DEBUG */ + + vty = vty_new(); + vty->fd = sock; + vty->wfd = sock; + vty->type = VTY_SHELL_SERV; + vty->node = VIEW_NODE; + vtys_add_tail(vtysh_sessions, vty); + + vty_event(VTYSH_READ, vty); +} + +static int vtysh_do_pass_fd(struct vty *vty) +{ + struct iovec iov[1] = { + { + .iov_base = vty->pass_fd_status, + .iov_len = sizeof(vty->pass_fd_status), + }, + }; + union { + uint8_t buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr align; + } u; + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = array_size(iov), + .msg_control = u.buf, + .msg_controllen = sizeof(u.buf), + }; + struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh); + ssize_t ret; + + memset(&u.buf, 0, sizeof(u.buf)); + cmh->cmsg_level = SOL_SOCKET; + cmh->cmsg_type = SCM_RIGHTS; + cmh->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmh), &vty->pass_fd, sizeof(int)); + + ret = sendmsg(vty->wfd, &mh, 0); + if (ret < 0 && ERRNO_IO_RETRY(errno)) + return BUFFER_PENDING; + + close(vty->pass_fd); + vty->pass_fd = -1; + vty->status = VTY_NORMAL; + + if (ret <= 0) + return BUFFER_ERROR; + + /* resume accepting commands (suspended in vtysh_read) */ + vty_event(VTYSH_READ, vty); + + if ((size_t)ret < sizeof(vty->pass_fd_status)) { + size_t remains = sizeof(vty->pass_fd_status) - ret; + + buffer_put(vty->obuf, vty->pass_fd_status + ret, remains); + return BUFFER_PENDING; + } + return BUFFER_EMPTY; +} + +static int vtysh_flush(struct vty *vty) +{ + int ret; + + ret = buffer_flush_available(vty->obuf, vty->wfd); + if (ret == BUFFER_EMPTY && vty->status == VTY_PASSFD) + ret = vtysh_do_pass_fd(vty); + + switch (ret) { + case BUFFER_PENDING: + vty_event(VTYSH_WRITE, vty); + break; + case BUFFER_ERROR: + flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing", + __func__, vty->fd); + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty_close(vty); + return -1; + case BUFFER_EMPTY: + break; + } + return 0; +} + +void vty_pass_fd(struct vty *vty, int fd) +{ + if (vty->pass_fd != -1) + close(vty->pass_fd); + + vty->pass_fd = fd; +} + +bool mgmt_vty_read_configs(void) +{ + char path[PATH_MAX]; + struct vty *vty; + FILE *confp; + uint line_num = 0; + uint count = 0; + uint index; + + vty = vty_new(); + vty->wfd = STDERR_FILENO; + vty->type = VTY_FILE; + vty->node = CONFIG_NODE; + vty->config = true; + vty->pending_allowed = true; + + vty->candidate_config = vty_shared_candidate_config; + + vty_mgmt_lock_candidate_inline(vty); + vty_mgmt_lock_running_inline(vty); + + for (index = 0; index < array_size(mgmt_daemons); index++) { + snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, + mgmt_daemons[index]); + + confp = vty_open_config(path, config_default); + if (!confp) + continue; + + zlog_info("mgmtd: reading config file: %s", path); + + /* Execute configuration file */ + line_num = 0; + (void)config_from_file(vty, confp, &line_num); + count++; + + fclose(confp); + } + + snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); + confp = vty_open_config(path, config_default); + if (confp) { + zlog_info("mgmtd: reading config file: %s", path); + + line_num = 0; + (void)config_from_file(vty, confp, &line_num); + count++; + + fclose(confp); + } + + /* Conditionally unlock as the config file may have "exit"d early which + * would then have unlocked things. + */ + if (vty->mgmt_locked_running_ds) + vty_mgmt_unlock_running_inline(vty); + if (vty->mgmt_locked_candidate_ds) + vty_mgmt_unlock_candidate_inline(vty); + + vty->pending_allowed = false; + + if (!count) + vty_close(vty); + else + vty_read_file_finish(vty, NULL); + + zlog_info("mgmtd: finished reading config files"); + + return true; +} + +static void vtysh_read(struct event *thread) +{ + int ret; + int sock; + int nbytes; + struct vty *vty; + unsigned char buf[VTY_READ_BUFSIZ]; + unsigned char *p; + uint8_t header[4] = {0, 0, 0, 0}; + + sock = EVENT_FD(thread); + vty = EVENT_ARG(thread); + + /* + * This code looks like it can read multiple commands from the `buf` + * value returned by read(); however, it cannot in some cases. + * + * There are multiple paths out of the "copying to vty->buf" loop, which + * lose any content not yet copied from the stack `buf`, `passfd`, + * `CMD_SUSPEND` and finally if a front-end for mgmtd (generally this + * would be mgmtd itself). So these code paths are counting on vtysh not + * sending us more than 1 command line before waiting on the reply to + * that command. + */ + assert(vty->type == VTY_SHELL_SERV); + + if ((nbytes = read(sock, buf, VTY_READ_BUFSIZ)) <= 0) { + if (nbytes < 0) { + if (ERRNO_IO_RETRY(errno)) { + vty_event(VTYSH_READ, vty); + return; + } + flog_err( + EC_LIB_SOCKET, + "%s: read failed on vtysh client fd %d, closing: %s", + __func__, sock, safe_strerror(errno)); + } + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty_close(vty); +#ifdef VTYSH_DEBUG + printf("close vtysh\n"); +#endif /* VTYSH_DEBUG */ + return; + } + +#ifdef VTYSH_DEBUG + printf("line: %.*s\n", nbytes, buf); +#endif /* VTYSH_DEBUG */ + + if (vty->length + nbytes >= VTY_BUFSIZ) { + /* Clear command line buffer. */ + vty->cp = vty->length = 0; + vty_clear_buf(vty); + vty_out(vty, "%% Command is too long.\n"); + } else { + for (p = buf; p < buf + nbytes; p++) { + vty->buf[vty->length++] = *p; + if (*p == '\0') { + /* Pass this line to parser. */ + ret = vty_execute(vty); +/* Note that vty_execute clears the command buffer and resets + vty->length to 0. */ + +/* Return result. */ +#ifdef VTYSH_DEBUG + printf("result: %d\n", ret); + printf("vtysh node: %d\n", vty->node); +#endif /* VTYSH_DEBUG */ + if (vty->pass_fd >= 0) { + memset(vty->pass_fd_status, 0, 4); + vty->pass_fd_status[3] = ret; + vty->status = VTY_PASSFD; + + if (!vty->t_write) + vty_event(VTYSH_WRITE, vty); + + /* this introduces a "sequence point" + * command output is written normally, + * read processing is suspended until + * buffer is empty + * then retcode + FD is written + * then normal processing resumes + * + * => skip vty_event(VTYSH_READ, vty)! + */ + return; + } else { + assertf(vty->status != VTY_PASSFD, + "%p address=%s passfd=%d", vty, + vty->address, vty->pass_fd); + + /* normalize other invalid values */ + vty->pass_fd = -1; + } + + /* hack for asynchronous "write integrated" + * - other commands in "buf" will be ditched + * - input during pending config-write is + * "unsupported" */ + if (ret == CMD_SUSPEND) + break; + + /* with new infra we need to stop response till + * we get response through callback. + */ + if (vty->mgmt_req_pending_cmd) { + debug_fe_client("postpone CLI response pending mgmtd %s on vty session-id %" PRIu64, + vty->mgmt_req_pending_cmd, + vty->mgmt_session_id); + return; + } + + /* warning: watchfrr hardcodes this result write + */ + header[3] = ret; + buffer_put(vty->obuf, header, 4); + + if (!vty->t_write && (vtysh_flush(vty) < 0)) + /* Try to flush results; exit if a write + * error occurs. */ + return; + } + } + } + + if (vty->status == VTY_CLOSE) + vty_close(vty); + else + vty_event(VTYSH_READ, vty); +} + +static void vtysh_write(struct event *thread) +{ + struct vty *vty = EVENT_ARG(thread); + + vtysh_flush(vty); +} + +#endif /* VTYSH */ + +/* Determine address family to bind. */ +void vty_serv_start(const char *addr, unsigned short port, const char *path) +{ + /* If port is set to 0, do not listen on TCP/IP at all! */ + if (port) + vty_serv_sock_addrinfo(addr, port); + +#ifdef VTYSH + vty_serv_un(path); +#endif /* VTYSH */ +} + +void vty_serv_stop(void) +{ + struct vty_serv *vtyserv; + + while ((vtyserv = vtyservs_pop(vty_servs))) { + EVENT_OFF(vtyserv->t_accept); + close(vtyserv->sock); + XFREE(MTYPE_VTY_SERV, vtyserv); + } + + vtyservs_fini(vty_servs); + vtyservs_init(vty_servs); +} + +static void vty_error_delete(void *arg) +{ + struct vty_error *ve = arg; + + XFREE(MTYPE_TMP, ve); +} + +/* Close vty interface. Warning: call this only from functions that + will be careful not to access the vty afterwards (since it has + now been freed). This is safest from top-level functions (called + directly by the thread dispatcher). */ +void vty_close(struct vty *vty) +{ + int i; + bool was_stdio = false; + + vty->status = VTY_CLOSE; + + /* + * If we reach here with pending config to commit we will be losing it + * so warn the user. + */ + if (vty->mgmt_num_pending_setcfg) + log_err_fe_client( + "vty closed, uncommitted config will be lost."); + + /* Drop out of configure / transaction if needed. */ + vty_config_exit(vty); + + if (mgmt_fe_client && vty->mgmt_session_id) { + debug_fe_client("closing vty session"); + mgmt_fe_destroy_client_session(mgmt_fe_client, + vty->mgmt_client_id); + vty->mgmt_session_id = 0; + } + + /* Cancel threads.*/ + EVENT_OFF(vty->t_read); + EVENT_OFF(vty->t_write); + EVENT_OFF(vty->t_timeout); + + if (vty->pass_fd != -1) { + close(vty->pass_fd); + vty->pass_fd = -1; + } + zlog_live_close(&vty->live_log); + + /* Flush buffer. */ + buffer_flush_all(vty->obuf, vty->wfd); + + /* Free input buffer. */ + buffer_free(vty->obuf); + buffer_free(vty->lbuf); + + /* Free command history. */ + for (i = 0; i < VTY_MAXHIST; i++) { + XFREE(MTYPE_VTY_HIST, vty->hist[i]); + } + + /* Unset vector. */ + if (vty->fd != -1) { + if (vty->type == VTY_SHELL_SERV) + vtys_del(vtysh_sessions, vty); + else if (vty->type == VTY_TERM) + vtys_del(vty_sessions, vty); + } + + if (vty->wfd > 0 && vty->type == VTY_FILE) + fsync(vty->wfd); + + /* Close socket. + * note check is for fd > STDERR_FILENO, not fd != -1. + * We never close stdin/stdout/stderr here, because we may be + * running in foreground mode with logging to stdout. Also, + * additionally, we'd need to replace these fds with /dev/null. */ + if (vty->wfd > STDERR_FILENO && vty->wfd != vty->fd) + close(vty->wfd); + if (vty->fd > STDERR_FILENO) + close(vty->fd); + if (vty->fd == STDIN_FILENO) + was_stdio = true; + + XFREE(MTYPE_TMP, vty->pending_cmds_buf); + XFREE(MTYPE_VTY, vty->buf); + + if (vty->error) { + vty->error->del = vty_error_delete; + list_delete(&vty->error); + } + + /* OK free vty. */ + XFREE(MTYPE_VTY, vty); + + if (was_stdio) + vty_stdio_reset(0); +} + +/* When time out occur output message then close connection. */ +static void vty_timeout(struct event *thread) +{ + struct vty *vty; + + vty = EVENT_ARG(thread); + vty->v_timeout = 0; + + /* Clear buffer*/ + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty_out(vty, "\nVty connection is timed out.\n"); + + /* Close connection. */ + vty->status = VTY_CLOSE; + vty_close(vty); +} + +/* Read up configuration file from file_name. */ +void vty_read_file(struct nb_config *config, FILE *confp) +{ + struct vty *vty; + unsigned int line_num = 0; + + vty = vty_new(); + /* vty_close won't close stderr; if some config command prints + * something it'll end up there. (not ideal; it'd be better if output + * from a file-load went to logging instead. Also note that if this + * function is called after daemonizing, stderr will be /dev/null.) + * + * vty->fd will be -1 from vty_new() + */ + vty->wfd = STDERR_FILENO; + vty->type = VTY_FILE; + vty->node = CONFIG_NODE; + vty->config = true; + if (config) + vty->candidate_config = config; + else { + vty->private_config = true; + vty->candidate_config = nb_config_new(NULL); + } + + /* Execute configuration file */ + (void)config_from_file(vty, confp, &line_num); + + vty_read_file_finish(vty, config); +} + +void vty_read_file_finish(struct vty *vty, struct nb_config *config) +{ + struct vty_error *ve; + struct listnode *node; + + /* Flush any previous errors before printing messages below */ + buffer_flush_all(vty->obuf, vty->wfd); + + for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) { + const char *message = NULL; + char *nl; + + switch (ve->cmd_ret) { + case CMD_SUCCESS: + message = "Command succeeded"; + break; + case CMD_ERR_NOTHING_TODO: + message = "Nothing to do"; + break; + case CMD_ERR_AMBIGUOUS: + message = "Ambiguous command"; + break; + case CMD_ERR_NO_MATCH: + message = "No such command"; + break; + case CMD_WARNING: + message = "Command returned Warning"; + break; + case CMD_WARNING_CONFIG_FAILED: + message = "Command returned Warning Config Failed"; + break; + case CMD_ERR_INCOMPLETE: + message = "Command returned Incomplete"; + break; + case CMD_ERR_EXEED_ARGC_MAX: + message = + "Command exceeded maximum number of Arguments"; + break; + default: + message = "Command returned unhandled error message"; + break; + } + + nl = strchr(ve->error_buf, '\n'); + if (nl) + *nl = '\0'; + flog_err(EC_LIB_VTY, "%s on config line %u: %s", message, + ve->line_num, ve->error_buf); + } + + /* + * Automatically commit the candidate configuration after + * reading the configuration file. + */ + if (config == NULL) { + struct nb_context context = {}; + char errmsg[BUFSIZ] = {0}; + int ret; + + context.client = NB_CLIENT_CLI; + context.user = vty; + ret = nb_candidate_commit(context, vty->candidate_config, true, + "Read configuration file", NULL, + errmsg, sizeof(errmsg)); + if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) + zlog_err( + "%s: failed to read configuration file: %s (%s)", + __func__, nb_err_name(ret), errmsg); + } + + vty_close(vty); +} + +static FILE *vty_use_backup_config(const char *fullpath) +{ + char *fullpath_sav, *fullpath_tmp; + FILE *ret = NULL; + int tmp, sav; + int c; + char buffer[512]; + + size_t fullpath_sav_sz = strlen(fullpath) + strlen(CONF_BACKUP_EXT) + 1; + fullpath_sav = malloc(fullpath_sav_sz); + strlcpy(fullpath_sav, fullpath, fullpath_sav_sz); + strlcat(fullpath_sav, CONF_BACKUP_EXT, fullpath_sav_sz); + + sav = open(fullpath_sav, O_RDONLY); + if (sav < 0) { + free(fullpath_sav); + return NULL; + } + + fullpath_tmp = malloc(strlen(fullpath) + 8); + snprintf(fullpath_tmp, strlen(fullpath) + 8, "%s.XXXXXX", fullpath); + + /* Open file to configuration write. */ + tmp = mkstemp(fullpath_tmp); + if (tmp < 0) + goto out_close_sav; + + if (fchmod(tmp, CONFIGFILE_MASK) != 0) + goto out_close; + + while ((c = read(sav, buffer, 512)) > 0) { + if (write(tmp, buffer, c) <= 0) + goto out_close; + } + close(sav); + close(tmp); + + if (rename(fullpath_tmp, fullpath) == 0) + ret = fopen(fullpath, "r"); + else + unlink(fullpath_tmp); + + if (0) { + out_close: + close(tmp); + unlink(fullpath_tmp); + out_close_sav: + close(sav); + } + + free(fullpath_sav); + free(fullpath_tmp); + return ret; +} + +FILE *vty_open_config(const char *config_file, char *config_default_dir) +{ + char cwd[MAXPATHLEN]; + FILE *confp = NULL; + const char *fullpath; + char *tmp = NULL; + + /* If -f flag specified. */ + if (config_file != NULL) { + if (!IS_DIRECTORY_SEP(config_file[0])) { + if (getcwd(cwd, MAXPATHLEN) == NULL) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "%s: failure to determine Current Working Directory %d!", + __func__, errno); + goto tmp_free_and_out; + } + size_t tmp_len = strlen(cwd) + strlen(config_file) + 2; + tmp = XMALLOC(MTYPE_TMP, tmp_len); + snprintf(tmp, tmp_len, "%s/%s", cwd, config_file); + fullpath = tmp; + } else + fullpath = config_file; + + confp = fopen(fullpath, "r"); + + if (confp == NULL) { + flog_warn( + EC_LIB_BACKUP_CONFIG, + "%s: failed to open configuration file %s: %s, checking backup", + __func__, fullpath, safe_strerror(errno)); + + confp = vty_use_backup_config(fullpath); + if (confp) + flog_warn(EC_LIB_BACKUP_CONFIG, + "using backup configuration file!"); + else { + flog_err( + EC_LIB_VTY, + "%s: can't open configuration file [%s]", + __func__, config_file); + goto tmp_free_and_out; + } + } + } else { + + host_config_set(config_default_dir); + +#ifdef VTYSH + int ret; + struct stat conf_stat; + + /* !!!!PLEASE LEAVE!!!! + * This is NEEDED for use with vtysh -b, or else you can get + * a real configuration food fight with a lot garbage in the + * merged configuration file it creates coming from the per + * daemon configuration files. This also allows the daemons + * to start if there default configuration file is not + * present or ignore them, as needed when using vtysh -b to + * configure the daemons at boot - MAG + */ + + /* Stat for vtysh Zebra.conf, if found startup and wait for + * boot configuration + */ + + if (strstr(config_default_dir, "vtysh") == NULL) { + ret = stat(integrate_default, &conf_stat); + if (ret >= 0) + goto tmp_free_and_out; + } +#endif /* VTYSH */ + confp = fopen(config_default_dir, "r"); + if (confp == NULL) { + flog_err( + EC_LIB_SYSTEM_CALL, + "%s: failed to open configuration file %s: %s, checking backup", + __func__, config_default_dir, + safe_strerror(errno)); + + confp = vty_use_backup_config(config_default_dir); + if (confp) { + flog_warn(EC_LIB_BACKUP_CONFIG, + "using backup configuration file!"); + fullpath = config_default_dir; + } else { + flog_err(EC_LIB_VTY, + "can't open configuration file [%s]", + config_default_dir); + goto tmp_free_and_out; + } + } else + fullpath = config_default_dir; + } + + host_config_set(fullpath); + +tmp_free_and_out: + XFREE(MTYPE_TMP, tmp); + + return confp; +} + + +bool vty_read_config(struct nb_config *config, const char *config_file, + char *config_default_dir) +{ + FILE *confp; + + confp = vty_open_config(config_file, config_default_dir); + if (!confp) + return false; + + vty_read_file(config, confp); + + fclose(confp); + + return true; +} + +int vty_config_enter(struct vty *vty, bool private_config, bool exclusive, + bool file_lock) +{ + if (exclusive && !vty_mgmt_fe_enabled() && + nb_running_lock(NB_CLIENT_CLI, vty)) { + vty_out(vty, "%% Configuration is locked by other client\n"); + return CMD_WARNING; + } + + /* + * We only need to do a lock when reading a config file as we will be + * sending a batch of setcfg changes followed by a single commit + * message. For user interactive mode we are doing implicit commits + * those will obtain the lock (or not) when they try and commit. + */ + if (file_lock && vty_mgmt_fe_enabled() && !private_config) { + if (vty_mgmt_lock_candidate_inline(vty)) { + vty_out(vty, + "%% Can't enter config; candidate datastore locked by another session\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (vty_mgmt_lock_running_inline(vty)) { + vty_out(vty, + "%% Can't enter config; running datastore locked by another session\n"); + vty_mgmt_unlock_candidate_inline(vty); + return CMD_WARNING_CONFIG_FAILED; + } + assert(vty->mgmt_locked_candidate_ds); + assert(vty->mgmt_locked_running_ds); + + /* + * As datastores are locked explicitly, we don't need implicit + * commits and should allow pending changes. + */ + vty->pending_allowed = true; + } + + vty->node = CONFIG_NODE; + vty->config = true; + vty->private_config = private_config; + vty->xpath_index = 0; + + if (private_config) { + vty->candidate_config = nb_config_dup(running_config); + vty->candidate_config_base = nb_config_dup(running_config); + vty_out(vty, + "Warning: uncommitted changes will be discarded on exit.\n\n"); + return CMD_SUCCESS; + } + + /* + * NOTE: On the MGMTD daemon we point the VTY candidate DS to + * the global MGMTD candidate DS. Else we point to the VTY + * Shared Candidate Config. + */ + vty->candidate_config = vty_mgmt_candidate_config + ? vty_mgmt_candidate_config + : vty_shared_candidate_config; + if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) + vty->candidate_config_base = nb_config_dup(running_config); + + return CMD_SUCCESS; +} + + +void vty_config_exit(struct vty *vty) +{ + enum node_type node = vty->node; + struct cmd_node *cnode; + + /* unlock and jump up to ENABLE_NODE if -and only if- we're + * somewhere below CONFIG_NODE */ + while (node && node != CONFIG_NODE) { + cnode = vector_lookup(cmdvec, node); + node = cnode->parent_node; + } + if (node != CONFIG_NODE) + /* called outside config, e.g. vty_close() in ENABLE_NODE */ + return; + + while (vty->node != ENABLE_NODE) + /* will call vty_config_node_exit() below */ + cmd_exit(vty); +} + +int vty_config_node_exit(struct vty *vty) +{ + vty->xpath_index = 0; + + /* TODO: could we check for un-commited changes here? */ + + vty->pending_allowed = false; + + if (vty->mgmt_locked_running_ds) + vty_mgmt_unlock_running_inline(vty); + + if (vty->mgmt_locked_candidate_ds) + vty_mgmt_unlock_candidate_inline(vty); + + /* Perform any pending commits. */ + (void)nb_cli_pending_commit_check(vty); + + /* Check if there's a pending confirmed commit. */ + if (vty->t_confirmed_commit_timeout) { + vty_out(vty, + "exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n"); + nb_cli_confirmed_commit_rollback(vty); + nb_cli_confirmed_commit_clean(vty); + } + + (void)nb_running_unlock(NB_CLIENT_CLI, vty); + + if (vty->candidate_config) { + if (vty->private_config) + nb_config_free(vty->candidate_config); + vty->candidate_config = NULL; + } + if (vty->candidate_config_base) { + nb_config_free(vty->candidate_config_base); + vty->candidate_config_base = NULL; + } + + vty->config = false; + + /* + * If this is a config file and we are dropping out of config end + * parsing. + */ + if (vty->type == VTY_FILE && vty->status != VTY_CLOSE) { + vty_out(vty, "exit from config node while reading config file"); + vty->status = VTY_CLOSE; + } + + return 1; +} + +/* Master of the threads. */ +static struct event_loop *vty_master; + +static void vty_event_serv(enum vty_event event, struct vty_serv *vty_serv) +{ + switch (event) { + case VTY_SERV: + event_add_read(vty_master, vty_accept, vty_serv, vty_serv->sock, + &vty_serv->t_accept); + break; +#ifdef VTYSH + case VTYSH_SERV: + event_add_read(vty_master, vtysh_accept, vty_serv, + vty_serv->sock, &vty_serv->t_accept); + break; +#endif /* VTYSH */ + case VTY_READ: + case VTY_WRITE: + case VTY_TIMEOUT_RESET: + case VTYSH_READ: + case VTYSH_WRITE: + assert(!"vty_event_serv() called incorrectly"); + } +} + +static void vty_event(enum vty_event event, struct vty *vty) +{ + switch (event) { +#ifdef VTYSH + case VTYSH_READ: + event_add_read(vty_master, vtysh_read, vty, vty->fd, + &vty->t_read); + break; + case VTYSH_WRITE: + event_add_write(vty_master, vtysh_write, vty, vty->wfd, + &vty->t_write); + break; +#endif /* VTYSH */ + case VTY_READ: + event_add_read(vty_master, vty_read, vty, vty->fd, + &vty->t_read); + + /* Time out treatment. */ + if (vty->v_timeout) { + EVENT_OFF(vty->t_timeout); + event_add_timer(vty_master, vty_timeout, vty, + vty->v_timeout, &vty->t_timeout); + } + break; + case VTY_WRITE: + event_add_write(vty_master, vty_flush, vty, vty->wfd, + &vty->t_write); + break; + case VTY_TIMEOUT_RESET: + EVENT_OFF(vty->t_timeout); + if (vty->v_timeout) + event_add_timer(vty_master, vty_timeout, vty, + vty->v_timeout, &vty->t_timeout); + break; + case VTY_SERV: + case VTYSH_SERV: + assert(!"vty_event() called incorrectly"); + } +} + +DEFUN_NOSH (config_who, + config_who_cmd, + "who", + "Display who is on vty\n") +{ + struct vty *v; + + frr_each (vtys, vty_sessions, v) + vty_out(vty, "%svty[%d] connected from %s%s.\n", + v->config ? "*" : " ", v->fd, v->address, + zlog_live_is_null(&v->live_log) ? "" : ", live log"); + return CMD_SUCCESS; +} + +/* Move to vty configuration mode. */ +DEFUN_NOSH (line_vty, + line_vty_cmd, + "line vty", + "Configure a terminal line\n" + "Virtual terminal\n") +{ + vty->node = VTY_NODE; + return CMD_SUCCESS; +} + +/* Set time out value. */ +static int exec_timeout(struct vty *vty, const char *min_str, + const char *sec_str) +{ + unsigned long timeout = 0; + + /* min_str and sec_str are already checked by parser. So it must be + all digit string. */ + if (min_str) { + timeout = strtol(min_str, NULL, 10); + timeout *= 60; + } + if (sec_str) + timeout += strtol(sec_str, NULL, 10); + + vty_timeout_val = timeout; + vty->v_timeout = timeout; + vty_event(VTY_TIMEOUT_RESET, vty); + + + return CMD_SUCCESS; +} + +DEFUN (exec_timeout_min, + exec_timeout_min_cmd, + "exec-timeout (0-35791)", + "Set timeout value\n" + "Timeout value in minutes\n") +{ + int idx_number = 1; + return exec_timeout(vty, argv[idx_number]->arg, NULL); +} + +DEFUN (exec_timeout_sec, + exec_timeout_sec_cmd, + "exec-timeout (0-35791) (0-2147483)", + "Set the EXEC timeout\n" + "Timeout in minutes\n" + "Timeout in seconds\n") +{ + int idx_number = 1; + int idx_number_2 = 2; + return exec_timeout(vty, argv[idx_number]->arg, + argv[idx_number_2]->arg); +} + +DEFUN (no_exec_timeout, + no_exec_timeout_cmd, + "no exec-timeout", + NO_STR + "Set the EXEC timeout\n") +{ + return exec_timeout(vty, NULL, NULL); +} + +/* Set vty access class. */ +DEFUN (vty_access_class, + vty_access_class_cmd, + "access-class WORD", + "Filter connections based on an IP access list\n" + "IP access list\n") +{ + int idx_word = 1; + if (vty_accesslist_name) + XFREE(MTYPE_VTY, vty_accesslist_name); + + vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); + + return CMD_SUCCESS; +} + +/* Clear vty access class. */ +DEFUN (no_vty_access_class, + no_vty_access_class_cmd, + "no access-class [WORD]", + NO_STR + "Filter connections based on an IP access list\n" + "IP access list\n") +{ + int idx_word = 2; + const char *accesslist = (argc == 3) ? argv[idx_word]->arg : NULL; + if (!vty_accesslist_name + || (argc == 3 && strcmp(vty_accesslist_name, accesslist))) { + vty_out(vty, "Access-class is not currently applied to vty\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_VTY, vty_accesslist_name); + + vty_accesslist_name = NULL; + + return CMD_SUCCESS; +} + +/* Set vty access class. */ +DEFUN (vty_ipv6_access_class, + vty_ipv6_access_class_cmd, + "ipv6 access-class WORD", + IPV6_STR + "Filter connections based on an IP access list\n" + "IPv6 access list\n") +{ + int idx_word = 2; + if (vty_ipv6_accesslist_name) + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + + vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); + + return CMD_SUCCESS; +} + +/* Clear vty access class. */ +DEFUN (no_vty_ipv6_access_class, + no_vty_ipv6_access_class_cmd, + "no ipv6 access-class [WORD]", + NO_STR + IPV6_STR + "Filter connections based on an IP access list\n" + "IPv6 access list\n") +{ + int idx_word = 3; + const char *accesslist = (argc == 4) ? argv[idx_word]->arg : NULL; + + if (!vty_ipv6_accesslist_name + || (argc == 4 && strcmp(vty_ipv6_accesslist_name, accesslist))) { + vty_out(vty, + "IPv6 access-class is not currently applied to vty\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); + + vty_ipv6_accesslist_name = NULL; + + return CMD_SUCCESS; +} + +/* vty login. */ +DEFUN (vty_login, + vty_login_cmd, + "login", + "Enable password checking\n") +{ + no_password_check = 0; + return CMD_SUCCESS; +} + +DEFUN (no_vty_login, + no_vty_login_cmd, + "no login", + NO_STR + "Enable password checking\n") +{ + no_password_check = 1; + return CMD_SUCCESS; +} + +DEFUN (service_advanced_vty, + service_advanced_vty_cmd, + "service advanced-vty", + "Set up miscellaneous service\n" + "Enable advanced mode vty interface\n") +{ + host.advanced = 1; + return CMD_SUCCESS; +} + +DEFUN (no_service_advanced_vty, + no_service_advanced_vty_cmd, + "no service advanced-vty", + NO_STR + "Set up miscellaneous service\n" + "Enable advanced mode vty interface\n") +{ + host.advanced = 0; + return CMD_SUCCESS; +} + +DEFUN_NOSH(terminal_monitor, + terminal_monitor_cmd, + "terminal monitor [detach]", + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n" + "Keep logging feed open independent of VTY session\n") +{ + int fd_ret = -1; + + if (vty->type != VTY_SHELL_SERV) { + vty_out(vty, "%% not supported\n"); + return CMD_WARNING; + } + + if (argc == 3) { + struct zlog_live_cfg detach_log = {}; + + zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret); + zlog_live_disown(&detach_log); + } else + zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret); + + if (fd_ret == -1) { + vty_out(vty, "%% error opening live log: %m\n"); + return CMD_WARNING; + } + + vty_pass_fd(vty, fd_ret); + return CMD_SUCCESS; +} + +DEFUN_NOSH(no_terminal_monitor, + no_terminal_monitor_cmd, + "no terminal monitor", + NO_STR + "Set terminal line parameters\n" + "Copy debug output to the current terminal line\n") +{ + zlog_live_close(&vty->live_log); + return CMD_SUCCESS; +} + +DEFUN_NOSH(terminal_no_monitor, + terminal_no_monitor_cmd, + "terminal no monitor", + "Set terminal line parameters\n" + NO_STR + "Copy debug output to the current terminal line\n") +{ + return no_terminal_monitor(self, vty, argc, argv); +} + + +DEFUN_NOSH (show_history, + show_history_cmd, + "show history", + SHOW_STR + "Display the session command history\n") +{ + int index; + + for (index = vty->hindex + 1; index != vty->hindex;) { + if (index == VTY_MAXHIST) { + index = 0; + continue; + } + + if (vty->hist[index] != NULL) + vty_out(vty, " %s\n", vty->hist[index]); + + index++; + } + + return CMD_SUCCESS; +} + +/* vty login. */ +DEFPY (log_commands, + log_commands_cmd, + "[no] log commands", + NO_STR + "Logging control\n" + "Log all commands\n") +{ + if (no) { + if (vty_log_commands_perm) { + vty_out(vty, + "Daemon started with permanent logging turned on for commands, ignoring\n"); + return CMD_WARNING; + } + + vty_log_commands = false; + } else + vty_log_commands = true; + + return CMD_SUCCESS; +} + +/* Display current configuration. */ +static int vty_config_write(struct vty *vty) +{ + vty_frame(vty, "line vty\n"); + + if (vty_accesslist_name) + vty_out(vty, " access-class %s\n", vty_accesslist_name); + + if (vty_ipv6_accesslist_name) + vty_out(vty, " ipv6 access-class %s\n", + vty_ipv6_accesslist_name); + + /* exec-timeout */ + if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) + vty_out(vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, + vty_timeout_val % 60); + + /* login */ + if (no_password_check) + vty_out(vty, " no login\n"); + + vty_endframe(vty, "exit\n"); + + if (vty_log_commands) + vty_out(vty, "log commands\n"); + + vty_out(vty, "!\n"); + + return CMD_SUCCESS; +} + +static int vty_config_write(struct vty *vty); +struct cmd_node vty_node = { + .name = "vty", + .node = VTY_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-line)# ", + .config_write = vty_config_write, +}; + +/* Reset all VTY status. */ +void vty_reset(void) +{ + struct vty *vty; + + frr_each_safe (vtys, vty_sessions, vty) { + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty->status = VTY_CLOSE; + vty_close(vty); + } + + vty_timeout_val = VTY_TIMEOUT_DEFAULT; + + XFREE(MTYPE_VTY, vty_accesslist_name); + XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); +} + +static void vty_save_cwd(void) +{ + char *c; + + c = getcwd(vty_cwd, sizeof(vty_cwd)); + + if (!c) { + /* + * At this point if these go wrong, more than likely + * the whole world is coming down around us + * Hence not worrying about it too much. + */ + if (chdir(SYSCONFDIR)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Failure to chdir to %s, errno: %d", + SYSCONFDIR, errno); + exit(-1); + } + if (getcwd(vty_cwd, sizeof(vty_cwd)) == NULL) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "Failure to getcwd, errno: %d", errno); + exit(-1); + } + } +} + +char *vty_get_cwd(void) +{ + return vty_cwd; +} + +int vty_shell(struct vty *vty) +{ + return vty->type == VTY_SHELL ? 1 : 0; +} + +int vty_shell_serv(struct vty *vty) +{ + return vty->type == VTY_SHELL_SERV ? 1 : 0; +} + +void vty_init_vtysh(void) +{ + /* currently nothing to do, but likely to have future use */ +} + + +/* + * These functions allow for CLI handling to be placed inside daemons; however, + * currently they are only used by mgmtd, with mgmtd having each daemons CLI + * functionality linked into it. This design choice was taken for efficiency. + */ + +static void vty_mgmt_server_connected(struct mgmt_fe_client *client, + uintptr_t usr_data, bool connected) +{ + debug_fe_client("Got %sconnected %s MGMTD Frontend Server", + !connected ? "dis: " : "", !connected ? "from" : "to"); + + /* + * We should not have any sessions for connecting or disconnecting case. + * The fe client library will delete all session on disconnect before + * calling us. + */ + assert(mgmt_fe_client_session_count(client) == 0); + + mgmt_fe_connected = connected; + + /* Start or stop listening for vty connections */ + if (connected) + frr_vty_serv_start(true); + else + frr_vty_serv_stop(); +} + +/* + * A session has successfully been created for a vty. + */ +static void vty_mgmt_session_notify(struct mgmt_fe_client *client, + uintptr_t usr_data, uint64_t client_id, + bool create, bool success, + uintptr_t session_id, uintptr_t session_ctx) +{ + struct vty *vty; + + vty = (struct vty *)session_ctx; + + if (!success) { + zlog_err("%s session for client %" PRIu64 " failed!", + create ? "Creating" : "Destroying", client_id); + return; + } + + debug_fe_client("%s session for client %" PRIu64 " successfully", + create ? "Created" : "Destroyed", client_id); + + if (create) { + assert(session_id != 0); + vty->mgmt_session_id = session_id; + } else { + vty->mgmt_session_id = 0; + /* We may come here by way of vty_close() and short-circuits */ + if (vty->status != VTY_CLOSE) + vty_close(vty); + } +} + +static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client, + uintptr_t usr_data, uint64_t client_id, + uintptr_t session_id, + uintptr_t session_ctx, uint64_t req_id, + bool lock_ds, bool success, + Mgmtd__DatastoreId ds_id, + char *errmsg_if_any) +{ + struct vty *vty; + bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); + + vty = (struct vty *)session_ctx; + + assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); + if (!success) + zlog_err("%socking for DS %u failed, Err: '%s' vty %p", + lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty); + else { + debug_fe_client("%socked DS %u successfully", + lock_ds ? "L" : "Unl", ds_id); + if (ds_id == MGMTD_DS_CANDIDATE) + vty->mgmt_locked_candidate_ds = lock_ds; + else + vty->mgmt_locked_running_ds = lock_ds; + } + + if (!is_short_circuit && vty->mgmt_req_pending_cmd) { + assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); + vty_mgmt_resume_response(vty, + success ? CMD_SUCCESS : CMD_WARNING); + } +} + +static void vty_mgmt_set_config_result_notified( + struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, + uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, + bool success, Mgmtd__DatastoreId ds_id, bool implicit_commit, + char *errmsg_if_any) +{ + struct vty *vty; + + vty = (struct vty *)session_ctx; + + if (!success) { + zlog_err("SET_CONFIG request for client 0x%" PRIx64 + " failed, Error: '%s'", + client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_out(vty, "%% Configuration failed.\n\n"); + if (errmsg_if_any) + vty_out(vty, "%s\n", errmsg_if_any); + } else { + debug_fe_client("SET_CONFIG request for client 0x%" PRIx64 + " req-id %" PRIu64 " was successfull%s%s", + client_id, req_id, errmsg_if_any ? ": " : "", + errmsg_if_any ?: ""); + } + + if (implicit_commit) { + /* In this case the changes have been applied, we are done */ + vty_mgmt_unlock_candidate_inline(vty); + vty_mgmt_unlock_running_inline(vty); + } + + vty_mgmt_resume_response(vty, success ? CMD_SUCCESS + : CMD_WARNING_CONFIG_FAILED); +} + +static void vty_mgmt_commit_config_result_notified( + struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, + uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, + bool success, Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, bool validate_only, char *errmsg_if_any) +{ + struct vty *vty; + + vty = (struct vty *)session_ctx; + + if (!success) { + zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 + " failed, Error: '%s'", + client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_out(vty, "%% Configuration failed.\n\n"); + if (errmsg_if_any) + vty_out(vty, "%s\n", errmsg_if_any); + } else { + debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64 + " req-id %" PRIu64 " was successfull%s%s", + client_id, req_id, errmsg_if_any ? ": " : "", + errmsg_if_any ?: ""); + if (errmsg_if_any) + vty_out(vty, "MGMTD: %s\n", errmsg_if_any); + } + + vty_mgmt_resume_response(vty, success ? CMD_SUCCESS + : CMD_WARNING_CONFIG_FAILED); +} + +static int vty_mgmt_get_data_result_notified( + struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, + uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, + bool success, Mgmtd__DatastoreId ds_id, Mgmtd__YangData **yang_data, + size_t num_data, int next_key, char *errmsg_if_any) +{ + struct vty *vty; + size_t indx; + + vty = (struct vty *)session_ctx; + + if (!success) { + zlog_err("GET_DATA request for client 0x%" PRIx64 + " failed, Error: '%s'", + client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_out(vty, "ERROR: GET_DATA request failed, Error: %s\n", + errmsg_if_any ? errmsg_if_any : "Unknown"); + vty_mgmt_resume_response(vty, CMD_WARNING); + return -1; + } + + debug_fe_client("GET_DATA request succeeded, client 0x%" PRIx64 + " req-id %" PRIu64 "%s%s", + client_id, req_id, errmsg_if_any ? ": " : "", + errmsg_if_any ?: ""); + + if (req_id != mgmt_last_req_id) { + mgmt_last_req_id = req_id; + vty_out(vty, "[\n"); + } + + for (indx = 0; indx < num_data; indx++) { + vty_out(vty, " \"%s\": \"%s\"\n", yang_data[indx]->xpath, + yang_data[indx]->value->encoded_str_val); + } + if (next_key < 0) { + vty_out(vty, "]\n"); + vty_mgmt_resume_response(vty, CMD_SUCCESS); + } + + return 0; +} + +static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf, + size_t count) +{ + struct vty *vty = user_data; + + vty_out(vty, "%.*s", (int)count, (const char *)buf); + return count; +} + +static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format, + const struct ly_err_item *ei) +{ +#if (LY_VERSION_MAJOR < 3) +#define data_path path +#else +#define data_path data_path +#endif + bool have_apptag = ei->apptag && ei->apptag[0] != 0; + bool have_path = ei->data_path && ei->data_path[0] != 0; + bool have_msg = ei->msg && ei->msg[0] != 0; + const char *severity = NULL; + const char *evalid = NULL; + const char *ecode = NULL; +#if (LY_VERSION_MAJOR < 3) + LY_ERR err = ei->no; +#else + LY_ERR err = ei->err; +#endif + + if (ei->level == LY_LLERR) + severity = "error"; + else if (ei->level == LY_LLWRN) + severity = "warning"; + + ecode = yang_ly_strerrcode(err); + if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS) + evalid = yang_ly_strvecode(ei->vecode); + + switch (format) { + case LYD_XML: + vty_out(vty, + ""); + vty_out(vty, "application"); + if (severity) + vty_out(vty, "%s", + severity); + if (ecode) + vty_out(vty, "%s", ecode); + if (evalid) + vty_out(vty, "%s\n", + evalid); + if (have_path) + vty_out(vty, "%s\n", + ei->data_path); + if (have_apptag) + vty_out(vty, "%s\n", + ei->apptag); + if (have_msg) + vty_out(vty, "%s\n", + ei->msg); + + vty_out(vty, ""); + break; + case LYD_JSON: + vty_out(vty, "{ \"error-type\": \"application\""); + if (severity) + vty_out(vty, ", \"error-severity\": \"%s\"", severity); + if (ecode) + vty_out(vty, ", \"error-code\": \"%s\"", ecode); + if (evalid) + vty_out(vty, ", \"error-validation\": \"%s\"", evalid); + if (have_path) + vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path); + if (have_apptag) + vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag); + if (have_msg) + vty_out(vty, ", \"error-message\": \"%s\"", ei->msg); + + vty_out(vty, "}"); + break; + case LYD_UNKNOWN: + case LYD_LYB: + default: + vty_out(vty, "%% error"); + if (severity) + vty_out(vty, " severity: %s", severity); + if (evalid) + vty_out(vty, " invalid: %s", evalid); + if (have_path) + vty_out(vty, " path: %s", ei->data_path); + if (have_apptag) + vty_out(vty, " app-tag: %s", ei->apptag); + if (have_msg) + vty_out(vty, " msg: %s", ei->msg); + break; + } +#undef data_path +} + +static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format) +{ + const struct ly_err_item *ei = ly_err_first(ly_native_ctx); + uint count; + + if (!ei) + return 0; + + if (format == LYD_JSON) + vty_out(vty, "\"ietf-restconf:errors\": [ "); + + for (count = 0; ei; count++, ei = ei->next) { + if (count) + vty_out(vty, ", "); + vty_out_yang_error(vty, format, ei); + } + + if (format == LYD_JSON) + vty_out(vty, " ]"); + + ly_err_clean(ly_native_ctx, NULL); + + return count; +} + + +static int vty_mgmt_get_tree_result_notified( + struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, + uint64_t session_id, uintptr_t session_ctx, uint64_t req_id, + Mgmtd__DatastoreId ds_id, LYD_FORMAT result_type, void *result, + size_t len, int partial_error) +{ + struct vty *vty; + struct lyd_node *dnode; + int ret = CMD_SUCCESS; + LY_ERR err; + + vty = (struct vty *)session_ctx; + + debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64 + " req-id %" PRIu64, + partial_error ? "partially " : "", client_id, req_id); + + assert(result_type == LYD_LYB || + result_type == vty->mgmt_req_pending_data); + + if (vty->mgmt_req_pending_data == LYD_XML && partial_error) + vty_out(vty, + "\n"); + + if (result_type == LYD_LYB) { + /* + * parse binary into tree and print in the specified format + */ + result_type = vty->mgmt_req_pending_data; + + err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0, + &dnode); + if (!err) + err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode, + result_type, LYD_PRINT_WITHSIBLINGS); + lyd_free_all(dnode); + + if (vty_out_yang_errors(vty, result_type) || err) + ret = CMD_WARNING; + } else { + /* + * Print the in-format result + */ + assert(result_type == LYD_XML || result_type == LYD_JSON); + vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result); + } + + vty_mgmt_resume_response(vty, ret); + + return 0; +} + +static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client, + uintptr_t user_data, + uint64_t client_id, uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *xpath) +{ + struct vty *vty = (struct vty *)session_ctx; + + debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64 + " was successful, xpath: %s", + client_id, req_id, xpath); + + vty_mgmt_resume_response(vty, CMD_SUCCESS); + + return 0; +} + +static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uint64_t session_id, + uintptr_t session_ctx, uint64_t req_id, + const char *result) +{ + struct vty *vty = (struct vty *)session_ctx; + + debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64 + " was successful", + client_id, req_id); + + if (result) + vty_out(vty, "%s\n", result); + + vty_mgmt_resume_response(vty, CMD_SUCCESS); + + return 0; +} + +static int vty_mgmt_error_notified(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uint64_t session_id, uintptr_t session_ctx, + uint64_t req_id, int error, + const char *errstr) +{ + struct vty *vty = (struct vty *)session_ctx; + const char *cname = mgmt_fe_client_name(client); + + if (!vty->mgmt_req_pending_cmd) { + debug_fe_client("Erorr with no pending command: %d returned for client %s 0x%" PRIx64 + " session-id %" PRIu64 " req-id %" PRIu64 + "error-str %s", + error, cname, client_id, session_id, req_id, + errstr); + vty_out(vty, + "%% Error %d from MGMTD for %s with no pending command: %s\n", + error, cname, errstr); + return CMD_WARNING; + } + + debug_fe_client("Erorr %d returned for client %s 0x%" PRIx64 + " session-id %" PRIu64 " req-id %" PRIu64 "error-str %s", + error, cname, client_id, session_id, req_id, errstr); + + vty_out(vty, "%% %s (for %s, client %s)\n", errstr, + vty->mgmt_req_pending_cmd, cname); + + vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS); + + return 0; +} + +static struct mgmt_fe_client_cbs mgmt_cbs = { + .client_connect_notify = vty_mgmt_server_connected, + .client_session_notify = vty_mgmt_session_notify, + .lock_ds_notify = vty_mgmt_ds_lock_notified, + .set_config_notify = vty_mgmt_set_config_result_notified, + .commit_config_notify = vty_mgmt_commit_config_result_notified, + .get_data_notify = vty_mgmt_get_data_result_notified, + .get_tree_notify = vty_mgmt_get_tree_result_notified, + .edit_notify = vty_mgmt_edit_result_notified, + .rpc_notify = vty_mgmt_rpc_result_notified, + .error_notify = vty_mgmt_error_notified, + +}; + +void vty_init_mgmt_fe(void) +{ + char name[40]; + + assert(vty_master); + assert(!mgmt_fe_client); + snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), + (long)getpid()); + mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master); + assert(mgmt_fe_client); +} + +bool vty_mgmt_fe_enabled(void) +{ + return mgmt_fe_client && mgmt_fe_connected; +} + +bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty) +{ + return vty->type != VTY_FILE && vty_mgmt_fe_enabled(); +} + +int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, + bool lock, bool scok) +{ + assert(mgmt_fe_client); + assert(vty->mgmt_session_id); + + vty->mgmt_req_id++; + if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, ds_id, lock, scok)) { + zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, + lock ? "" : "UN", vty->mgmt_req_id); + vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", + lock ? "" : "UN"); + return -1; + } + + if (!scok) + vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; + + return 0; +} + +int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, + bool implicit_commit) +{ + Mgmtd__YangDataValue value[VTY_MAXCFGCHANGES]; + Mgmtd__YangData cfg_data[VTY_MAXCFGCHANGES]; + Mgmtd__YangCfgDataReq cfg_req[VTY_MAXCFGCHANGES]; + Mgmtd__YangCfgDataReq *cfgreq[VTY_MAXCFGCHANGES] = {0}; + char xpath[VTY_MAXCFGCHANGES][XPATH_MAXLEN]; + char *change_xpath; + size_t indx; + + if (vty->type == VTY_FILE) { + /* + * if this is a config file read we will not send any of the + * changes until we are done reading the file and have modified + * the local candidate DS. + */ + /* no-one else should be sending data right now */ + assert(!vty->mgmt_num_pending_setcfg); + return 0; + } + + /* If we are FE client and we have a vty then we have a session */ + assert(mgmt_fe_client && vty->mgmt_client_id && vty->mgmt_session_id); + + if (!vty->num_cfg_changes) + return 0; + + /* grab the candidate and running lock prior to sending implicit commit + * command + */ + if (implicit_commit) { + if (vty_mgmt_lock_candidate_inline(vty)) { + vty_out(vty, + "%% command failed, could not lock candidate DS\n"); + return -1; + } else if (vty_mgmt_lock_running_inline(vty)) { + vty_out(vty, + "%% command failed, could not lock running DS\n"); + vty_mgmt_unlock_candidate_inline(vty); + return -1; + } + } + + if (xpath_base == NULL) + xpath_base = ""; + + for (indx = 0; indx < vty->num_cfg_changes; indx++) { + mgmt_yang_data_init(&cfg_data[indx]); + + if (vty->cfg_changes[indx].value) { + mgmt_yang_data_value_init(&value[indx]); + value[indx].encoded_str_val = + (char *)vty->cfg_changes[indx].value; + value[indx].value_case = + MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL; + cfg_data[indx].value = &value[indx]; + } + + change_xpath = vty->cfg_changes[indx].xpath; + + memset(xpath[indx], 0, sizeof(xpath[indx])); + /* If change xpath is relative, prepend base xpath. */ + if (change_xpath[0] == '.') { + strlcpy(xpath[indx], xpath_base, sizeof(xpath[indx])); + change_xpath++; /* skip '.' */ + } + strlcat(xpath[indx], change_xpath, sizeof(xpath[indx])); + + cfg_data[indx].xpath = xpath[indx]; + + mgmt_yang_cfg_data_req_init(&cfg_req[indx]); + cfg_req[indx].data = &cfg_data[indx]; + switch (vty->cfg_changes[indx].operation) { + case NB_OP_DELETE: + cfg_req[indx].req_type = + MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA; + break; + + case NB_OP_DESTROY: + cfg_req[indx].req_type = + MGMTD__CFG_DATA_REQ_TYPE__REMOVE_DATA; + break; + + case NB_OP_CREATE_EXCL: + cfg_req[indx].req_type = + MGMTD__CFG_DATA_REQ_TYPE__CREATE_DATA; + break; + + case NB_OP_REPLACE: + cfg_req[indx].req_type = + MGMTD__CFG_DATA_REQ_TYPE__REPLACE_DATA; + break; + + case NB_OP_CREATE: + case NB_OP_MODIFY: + case NB_OP_MOVE: + cfg_req[indx].req_type = + MGMTD__CFG_DATA_REQ_TYPE__SET_DATA; + break; + default: + assertf(false, + "Invalid operation type for send config: %d", + vty->cfg_changes[indx].operation); + /*NOTREACHED*/ + abort(); + } + + cfgreq[indx] = &cfg_req[indx]; + } + if (!indx) + return 0; + + vty->mgmt_req_id++; + if (mgmt_fe_send_setcfg_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, MGMTD_DS_CANDIDATE, + cfgreq, indx, implicit_commit, + MGMTD_DS_RUNNING)) { + zlog_err("Failed to send %zu config xpaths to mgmtd", indx); + vty_out(vty, "%% Failed to send commands to mgmtd\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_SETCFG_REQ"; + + return 0; +} + +int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort) +{ + if (mgmt_fe_client && vty->mgmt_session_id) { + vty->mgmt_req_id++; + if (mgmt_fe_send_commitcfg_req( + mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, MGMTD_DS_CANDIDATE, + MGMTD_DS_RUNNING, validate_only, abort)) { + zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, + vty->mgmt_req_id); + vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; + vty->mgmt_num_pending_setcfg = 0; + } + + return 0; +} + +int vty_mgmt_send_get_req(struct vty *vty, bool is_config, + Mgmtd__DatastoreId datastore, const char **xpath_list, + int num_req) +{ + Mgmtd__YangData yang_data[VTY_MAXCFGCHANGES]; + Mgmtd__YangGetDataReq get_req[VTY_MAXCFGCHANGES]; + Mgmtd__YangGetDataReq *getreq[VTY_MAXCFGCHANGES]; + int i; + + vty->mgmt_req_id++; + + for (i = 0; i < num_req; i++) { + mgmt_yang_get_data_req_init(&get_req[i]); + mgmt_yang_data_init(&yang_data[i]); + + yang_data->xpath = (char *)xpath_list[i]; + + get_req[i].data = &yang_data[i]; + getreq[i] = &get_req[i]; + } + if (mgmt_fe_send_get_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, is_config, datastore, getreq, + num_req)) { + zlog_err("Failed to send GET- to MGMTD for req-id %" PRIu64 ".", + vty->mgmt_req_id); + vty_out(vty, "Failed to send GET-CONFIG to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_GETCFG_REQ"; + + return 0; +} + +int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT result_type, uint8_t flags, + uint8_t defaults, const char *xpath) +{ + LYD_FORMAT intern_format = result_type; + + vty->mgmt_req_id++; + + if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, datastore, + intern_format, flags, defaults, xpath)) { + zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 + " req-id %" PRIu64 ".", + vty->mgmt_session_id, vty->mgmt_req_id); + vty_out(vty, "Failed to send GET-DATA to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ"; + vty->mgmt_req_pending_data = result_type; + + return 0; +} + +int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, + const char *data) +{ + vty->mgmt_req_id++; + + if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, datastore, request_type, + flags, operation, xpath, data)) { + zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64 + " req-id %" PRIu64 ".", + vty->mgmt_session_id, vty->mgmt_req_id); + vty_out(vty, "Failed to send EDIT to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ"; + + return 0; +} + +int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, + const char *xpath, const char *data) +{ + vty->mgmt_req_id++; + + if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id, + vty->mgmt_req_id, request_type, xpath, data)) { + zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64 + " req-id %" PRIu64 ".", + vty->mgmt_session_id, vty->mgmt_req_id); + vty_out(vty, "Failed to send RPC to MGMTD!\n"); + return -1; + } + + vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ"; + + return 0; +} + +/* Install vty's own commands like `who' command. */ +void vty_init(struct event_loop *master_thread, bool do_command_logging) +{ + /* For further configuration read, preserve current directory. */ + vty_save_cwd(); + + vty_master = master_thread; + + atexit(vty_stdio_atexit); + + /* Install bgp top node. */ + install_node(&vty_node); + + install_element(VIEW_NODE, &config_who_cmd); + install_element(VIEW_NODE, &show_history_cmd); + install_element(CONFIG_NODE, &line_vty_cmd); + install_element(CONFIG_NODE, &service_advanced_vty_cmd); + install_element(CONFIG_NODE, &no_service_advanced_vty_cmd); + install_element(CONFIG_NODE, &show_history_cmd); + install_element(CONFIG_NODE, &log_commands_cmd); + + if (do_command_logging) { + vty_log_commands = true; + vty_log_commands_perm = true; + } + + install_element(ENABLE_NODE, &terminal_monitor_cmd); + install_element(ENABLE_NODE, &terminal_no_monitor_cmd); + install_element(ENABLE_NODE, &no_terminal_monitor_cmd); + + install_default(VTY_NODE); + install_element(VTY_NODE, &exec_timeout_min_cmd); + install_element(VTY_NODE, &exec_timeout_sec_cmd); + install_element(VTY_NODE, &no_exec_timeout_cmd); + install_element(VTY_NODE, &vty_access_class_cmd); + install_element(VTY_NODE, &no_vty_access_class_cmd); + install_element(VTY_NODE, &vty_login_cmd); + install_element(VTY_NODE, &no_vty_login_cmd); + install_element(VTY_NODE, &vty_ipv6_access_class_cmd); + install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd); +} + +void vty_terminate(void) +{ + struct vty *vty; + + if (mgmt_fe_client) { + mgmt_fe_client_destroy(mgmt_fe_client); + mgmt_fe_client = 0; + } + + memset(vty_cwd, 0x00, sizeof(vty_cwd)); + + vty_reset(); + + /* default state of vty_sessions is initialized & empty. */ + vtys_fini(vty_sessions); + vtys_init(vty_sessions); + + /* vty_reset() doesn't close vtysh sessions */ + frr_each_safe (vtys, vtysh_sessions, vty) { + buffer_reset(vty->lbuf); + buffer_reset(vty->obuf); + vty->status = VTY_CLOSE; + vty_close(vty); + } + + vtys_fini(vtysh_sessions); + vtys_init(vtysh_sessions); + + vty_serv_stop(); +} diff --git a/lib/vty.h b/lib/vty.h new file mode 100644 index 0000000..c336a81 --- /dev/null +++ b/lib/vty.h @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Virtual terminal [aka TeletYpe] interface routine + * Copyright (C) 1997 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_VTY_H +#define _ZEBRA_VTY_H + +#include +#ifdef HAVE_LIBPCRE2_POSIX +#ifndef _FRR_PCRE2_POSIX +#define _FRR_PCRE2_POSIX +#include +#endif /* _FRR_PCRE2_POSIX */ +#elif defined(HAVE_LIBPCREPOSIX) +#include +#else +#include +#endif /* HAVE_LIBPCRE2_POSIX */ + +#include "frrevent.h" +#include "log.h" +#include "sockunion.h" +#include "qobj.h" +#include "compiler.h" +#include "northbound.h" +#include "zlog_live.h" +#include "libfrr.h" +#include "mgmt_fe_client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_object; + +#define VTY_BUFSIZ 4096 +#define VTY_MAXHIST 20 +#define VTY_MAXDEPTH 8 + +#define VTY_MAXCFGCHANGES 16 + +struct vty_error { + char error_buf[VTY_BUFSIZ]; + uint32_t line_num; + int cmd_ret; +}; + +struct vty_cfg_change { + char xpath[XPATH_MAXLEN]; + enum nb_operation operation; + const char *value; +}; + +PREDECL_DLIST(vtys); + +/* VTY struct. */ +struct vty { + struct vtys_item itm; + + /* File descripter of this vty. */ + int fd; + + /* output FD, to support stdin/stdout combination */ + int wfd; + + /* File output, used for VTYSH only */ + FILE *of; + FILE *of_saved; + + /* whether we are using pager or not */ + bool is_paged; + + /* Is this vty connect to file or not */ + enum { VTY_TERM, /* telnet conn or stdin/stdout UI */ + VTY_FILE, /* reading and writing config files */ + VTY_SHELL, /* vtysh client side UI */ + VTY_SHELL_SERV, /* server-side vtysh connection */ + } type; + + /* Node status of this vty */ + int node; + + /* Failure count */ + int fail; + + /* Output filer regex */ + bool filter; + regex_t include; + + /* Line buffer */ + struct buffer *lbuf; + + /* Output buffer. */ + struct buffer *obuf; + + /* Command input buffer */ + char *buf; + + /* Command input error buffer */ + struct list *error; + + /* Command cursor point */ + int cp; + + /* Command length */ + int length; + + /* Command max length. */ + int max; + + /* Histry of command */ + char *hist[VTY_MAXHIST]; + + /* History lookup current point */ + int hp; + + /* History insert end point */ + int hindex; + + /* Changes enqueued to be applied in the candidate configuration. */ + size_t num_cfg_changes; + struct nb_cfg_change cfg_changes[VTY_MAXCFGCHANGES]; + + /* Input parameters */ + size_t num_rpc_params; + struct nb_cfg_change rpc_params[VTY_MAXCFGCHANGES]; + + /* XPath of the current node */ + int xpath_index; + char xpath[VTY_MAXDEPTH][XPATH_MAXLEN]; + + /* + * Keep track of how many SET_CFG requests has been sent so far that + * has not been committed yet. + */ + size_t mgmt_num_pending_setcfg; + + /* In configure mode. */ + bool config; + + /* Private candidate configuration mode. */ + bool private_config; + + /* Candidate configuration. */ + struct nb_config *candidate_config; + + /* Base candidate configuration. */ + struct nb_config *candidate_config_base; + + /* Dynamic transaction information. */ + bool pending_allowed; + bool pending_commit; + char *pending_cmds_buf; + size_t pending_cmds_buflen; + size_t pending_cmds_bufpos; + + /* Confirmed-commit timeout and rollback configuration. */ + struct event *t_confirmed_commit_timeout; + struct nb_config *confirmed_commit_rollback; + + /* qobj object ID (replacement for "index") */ + uint64_t qobj_index; + + /* qobj second-level object ID (replacement for "index_sub") */ + uint64_t qobj_index_sub; + + /* For escape character. */ + unsigned char escape; + + /* Current vty status. */ + enum { + VTY_NORMAL, + VTY_CLOSE, + VTY_MORE, + VTY_MORELINE, + VTY_PASSFD, + } status; + + /* vtysh socket/fd passing (for terminal monitor) */ + int pass_fd; + + /* CLI command return value (likely CMD_SUCCESS) when pass_fd != -1 */ + uint8_t pass_fd_status[4]; + + /* live logging target / terminal monitor */ + struct zlog_live_cfg live_log; + + /* IAC handling: was the last character received the + IAC (interpret-as-command) escape character (and therefore the next + character will be the command code)? Refer to Telnet RFC 854. */ + unsigned char iac; + + /* IAC SB (option subnegotiation) handling */ + unsigned char iac_sb_in_progress; +/* At the moment, we care only about the NAWS (window size) negotiation, + and that requires just a 5-character buffer (RFC 1073): + <16-bit width> <16-bit height> */ +#define TELNET_NAWS_SB_LEN 5 + unsigned char sb_buf[TELNET_NAWS_SB_LEN]; + /* How many subnegotiation characters have we received? We just drop + those that do not fit in the buffer. */ + size_t sb_len; + + /* Window width/height. */ + int width; + int height; + + /* Configure lines. */ + int lines; + + /* Read and write thread. */ + struct event *t_read; + struct event *t_write; + + /* Timeout seconds and thread. */ + unsigned long v_timeout; + struct event *t_timeout; + + /* What address is this vty comming from. */ + char address[SU_ADDRSTRLEN]; + + /* "frame" output. This is buffered and will be printed if some + * actual output follows, or will be discarded if the frame ends + * without any output. */ + size_t frame_pos; + char frame[1024]; + + uint64_t mgmt_session_id; /* FE adapter identifies session w/ this */ + uint64_t mgmt_client_id; /* FE vty client identifies w/ this ID */ + uint64_t mgmt_req_id; + /* set when we have sent mgmtd a *REQ command in response to some vty + * CLI command and we are waiting on the reply so we can respond to the + * vty user. */ + const char *mgmt_req_pending_cmd; + uintptr_t mgmt_req_pending_data; + bool mgmt_locked_candidate_ds; + bool mgmt_locked_running_ds; +}; + +static inline void vty_push_context(struct vty *vty, int node, uint64_t id) +{ + vty->node = node; + vty->qobj_index = id; +} + +/* note: VTY_PUSH_CONTEXT(..., NULL) doesn't work, since it will try to + * dereference "NULL->qobj_node.nid" */ +#define VTY_PUSH_CONTEXT(nodeval, ptr) \ + vty_push_context(vty, nodeval, QOBJ_ID_0SAFE(ptr)) +#define VTY_PUSH_CONTEXT_NULL(nodeval) vty_push_context(vty, nodeval, 0ULL) +#define VTY_PUSH_CONTEXT_SUB(nodeval, ptr) \ + do { \ + vty->node = nodeval; \ + /* qobj_index stays untouched */ \ + vty->qobj_index_sub = QOBJ_ID_0SAFE(ptr); \ + } while (0) + +/* can return NULL if context is invalid! */ +#define VTY_GET_CONTEXT(structname) \ + QOBJ_GET_TYPESAFE(vty->qobj_index, structname) +#define VTY_GET_CONTEXT_SUB(structname) \ + QOBJ_GET_TYPESAFE(vty->qobj_index_sub, structname) + +/* will return if ptr is NULL. */ +#define VTY_CHECK_CONTEXT(ptr) \ + if (!ptr) { \ + vty_out(vty, \ + "Current configuration object was deleted " \ + "by another process.\n"); \ + return CMD_WARNING; \ + } + +/* struct structname *ptr = ; ptr will never be NULL. */ +#define VTY_DECLVAR_CONTEXT(structname, ptr) \ + struct structname *ptr = VTY_GET_CONTEXT(structname); \ + VTY_CHECK_CONTEXT(ptr); +#define VTY_DECLVAR_CONTEXT_SUB(structname, ptr) \ + struct structname *ptr = VTY_GET_CONTEXT_SUB(structname); \ + VTY_CHECK_CONTEXT(ptr); +#define VTY_DECLVAR_INSTANCE_CONTEXT(structname, ptr) \ + if (vty->qobj_index == 0) \ + return CMD_NOT_MY_INSTANCE; \ + struct structname *ptr = VTY_GET_CONTEXT(structname); \ + VTY_CHECK_CONTEXT(ptr); + +#define VTY_DECLVAR_CONTEXT_VRF(vrfptr) \ + struct vrf *vrfptr; \ + if (vty->node == CONFIG_NODE) \ + vrfptr = vrf_lookup_by_id(VRF_DEFAULT); \ + else \ + vrfptr = VTY_GET_CONTEXT(vrf); \ + VTY_CHECK_CONTEXT(vrfptr); \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +/* XPath macros. */ +#define VTY_PUSH_XPATH(nodeval, value) \ + do { \ + if (vty->xpath_index >= VTY_MAXDEPTH) { \ + vty_out(vty, "%% Reached maximum CLI depth (%u)\n", \ + VTY_MAXDEPTH); \ + return CMD_WARNING; \ + } \ + vty->node = nodeval; \ + strlcpy(vty->xpath[vty->xpath_index], value, \ + sizeof(vty->xpath[0])); \ + vty->xpath_index++; \ + } while (0) + +#define VTY_CURR_XPATH vty->xpath[vty->xpath_index - 1] + +#define VTY_CHECK_XPATH \ + do { \ + if (vty->type != VTY_FILE && !vty->private_config && \ + vty->xpath_index > 0 && \ + !yang_dnode_exists(vty->candidate_config->dnode, \ + VTY_CURR_XPATH)) { \ + vty_out(vty, \ + "Current configuration object was deleted " \ + "by another process.\n\n"); \ + return CMD_WARNING; \ + } \ + } while (0) + +struct vty_arg { + const char *name; + const char *value; + const char **argv; + int argc; +}; + +/* Integrated configuration file. */ +#define INTEGRATE_DEFAULT_CONFIG "frr.conf" + +/* Default time out value */ +#define VTY_TIMEOUT_DEFAULT 600 + +/* Vty read buffer size. */ +#define VTY_READ_BUFSIZ 512 + +/* Directory separator. */ +#ifndef DIRECTORY_SEP +#define DIRECTORY_SEP '/' +#endif /* DIRECTORY_SEP */ + +#ifndef IS_DIRECTORY_SEP +#define IS_DIRECTORY_SEP(c) ((c) == DIRECTORY_SEP) +#endif + +extern struct nb_config *vty_mgmt_candidate_config; +extern bool vty_log_commands; + +extern char const *const mgmt_daemons[]; +extern uint mgmt_daemons_count; + +/* Prototypes. */ +extern void vty_init(struct event_loop *m, bool do_command_logging); +extern void vty_init_vtysh(void); +extern void vty_terminate(void); +extern void vty_reset(void); +extern struct vty *vty_new(void); +extern struct vty *vty_stdio(void (*atclose)(int isexit)); + +/* - vty_frame() output goes to a buffer (for context-begin markers) + * - vty_out() will first print this buffer, and clear it + * - vty_endframe() clears the buffer without printing it, and prints an + * extra string if the buffer was empty before (for context-end markers) + */ +extern int vty_out(struct vty *, const char *, ...) PRINTFRR(2, 3); +extern void vty_frame(struct vty *, const char *, ...) PRINTFRR(2, 3); +extern void vty_endframe(struct vty *, const char *); +extern bool vty_set_include(struct vty *vty, const char *regexp); +/* returns CMD_SUCCESS so you can do a one-line "return vty_json(...)" + * NULL check and json_object_free() is included. + * + * _no_pretty means do not add a bunch of newlines and dump the output + * as densely as possible. + */ +extern int vty_json(struct vty *vty, struct json_object *json); +extern int vty_json_no_pretty(struct vty *vty, struct json_object *json); +void vty_json_key(struct vty *vty, const char *key, bool *first_key); +void vty_json_close(struct vty *vty, bool first_key); +extern void vty_json_empty(struct vty *vty, struct json_object *json); +/* post fd to be passed to the vtysh client + * fd is owned by the VTY code after this and will be closed when done + */ +extern void vty_pass_fd(struct vty *vty, int fd); + +extern FILE *vty_open_config(const char *config_file, char *config_default_dir); +extern bool vty_read_config(struct nb_config *config, const char *config_file, + char *config_default_dir); +extern void vty_read_file(struct nb_config *config, FILE *confp); +extern void vty_read_file_finish(struct vty *vty, struct nb_config *config); +extern void vty_time_print(struct vty *, int); +extern void vty_serv_start(const char *, unsigned short, const char *); +extern void vty_serv_stop(void); +extern void vty_close(struct vty *); +extern char *vty_get_cwd(void); +extern void vty_update_xpath(const char *oldpath, const char *newpath); +extern int vty_config_enter(struct vty *vty, bool private_config, + bool exclusive, bool file_lock); +extern void vty_config_exit(struct vty *); +extern int vty_config_node_exit(struct vty *); +extern int vty_shell(struct vty *); +extern int vty_shell_serv(struct vty *); +extern void vty_hello(struct vty *); + +/* ^Z / SIGTSTP handling */ +extern void vty_stdio_suspend(void); +extern void vty_stdio_resume(void); +extern void vty_stdio_close(void); + +extern void vty_init_mgmt_fe(void); +extern bool vty_mgmt_fe_enabled(void); +extern bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty); + +extern bool mgmt_vty_read_configs(void); +extern int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base, + bool implicit_commit); +extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, + bool abort); +extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config, + Mgmtd__DatastoreId datastore, + const char **xpath_list, int num_req); +extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT result_type, uint8_t flags, + uint8_t defaults, const char *xpath); +extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, + const char *data); +extern int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type, + const char *xpath, const char *data); +extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, + bool lock, bool scok); +extern void vty_mgmt_resume_response(struct vty *vty, int ret); + +static inline bool vty_needs_implicit_commit(struct vty *vty) +{ + return frr_get_cli_mode() == FRR_CLI_CLASSIC && !vty->pending_allowed; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_VTY_H */ diff --git a/lib/vxlan.h b/lib/vxlan.h new file mode 100644 index 0000000..65ff08e --- /dev/null +++ b/lib/vxlan.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* VxLAN common header. + * Copyright (C) 2016, 2017 Cumulus Networks, Inc. + */ + +#ifndef __VXLAN_H__ +#define __VXLAN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* EVPN MH DF election algorithm */ +#define EVPN_MH_DF_ALG_SERVICE_CARVING 0 +#define EVPN_MH_DF_ALG_HRW 1 +#define EVPN_MH_DF_ALG_PREF 2 + +/* preference range for DF election */ +#define EVPN_MH_DF_PREF_MIN 0 +#define EVPN_MH_DF_PREF_DEFAULT 32767 +#define EVPN_MH_DF_PREF_MAX 65535 + +/* VxLAN Network Identifier - 24-bit (RFC 7348) */ +typedef uint32_t vni_t; +#define VNI_MAX 16777215 /* (2^24 - 1) */ + +/* Flooding mechanisms for BUM packets. */ +/* Currently supported mechanisms are head-end (ingress) replication + * (which is the default) and no flooding. Future options could be + * using PIM-SM, PIM-Bidir etc. + */ +enum vxlan_flood_control { + VXLAN_FLOOD_HEAD_END_REPL = 0, + VXLAN_FLOOD_DISABLED, + VXLAN_FLOOD_PIM_SM, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __VXLAN_H__ */ diff --git a/lib/wheel.c b/lib/wheel.c new file mode 100644 index 0000000..2520e81 --- /dev/null +++ b/lib/wheel.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Timer Wheel + * Copyright (C) 2016 Cumulus Networks, Inc. + * Donald Sharp + */ +#include "zebra.h" +#include "linklist.h" +#include "frrevent.h" +#include "memory.h" +#include "wheel.h" +#include "log.h" + +DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL, "Timer Wheel"); +DEFINE_MTYPE_STATIC(LIB, TIMER_WHEEL_LIST, "Timer Wheel Slot List"); + +static int debug_timer_wheel = 0; + +static void wheel_timer_thread(struct event *t); + +static void wheel_timer_thread_helper(struct event *t) +{ + struct listnode *node, *nextnode; + unsigned long long curr_slot; + unsigned int slots_to_skip = 1; + struct timer_wheel *wheel; + void *data; + + wheel = EVENT_ARG(t); + + wheel->curr_slot += wheel->slots_to_skip; + + curr_slot = wheel->curr_slot % wheel->slots; + + if (debug_timer_wheel) + zlog_debug("%s: Wheel Slot: %lld(%lld) count: %d", __func__, + wheel->curr_slot, curr_slot, + listcount(wheel->wheel_slot_lists[curr_slot])); + + for (ALL_LIST_ELEMENTS(wheel->wheel_slot_lists[curr_slot], node, + nextnode, data)) + (*wheel->slot_run)(data); + + while (list_isempty(wheel->wheel_slot_lists[(curr_slot + slots_to_skip) + % wheel->slots]) + && (curr_slot + slots_to_skip) % wheel->slots != curr_slot) + slots_to_skip++; + + wheel->slots_to_skip = slots_to_skip; + event_add_timer_msec(wheel->master, wheel_timer_thread, wheel, + wheel->nexttime * slots_to_skip, &wheel->timer); +} + +static void wheel_timer_thread(struct event *t) +{ + struct timer_wheel *wheel; + + wheel = EVENT_ARG(t); + + event_execute(wheel->master, wheel_timer_thread_helper, wheel, 0, NULL); +} + +struct timer_wheel *wheel_init(struct event_loop *master, int period, + size_t slots, + unsigned int (*slot_key)(const void *), + void (*slot_run)(void *), const char *run_name) +{ + struct timer_wheel *wheel; + size_t i; + + wheel = XCALLOC(MTYPE_TIMER_WHEEL, sizeof(struct timer_wheel)); + + wheel->name = XSTRDUP(MTYPE_TIMER_WHEEL, run_name); + wheel->slot_key = slot_key; + wheel->slot_run = slot_run; + + wheel->period = period; + wheel->slots = slots; + wheel->curr_slot = 0; + wheel->master = master; + wheel->nexttime = period / slots; + + wheel->wheel_slot_lists = XCALLOC(MTYPE_TIMER_WHEEL_LIST, + slots * sizeof(struct list *)); + for (i = 0; i < slots; i++) + wheel->wheel_slot_lists[i] = list_new(); + + event_add_timer_msec(wheel->master, wheel_timer_thread, wheel, + wheel->nexttime, &wheel->timer); + + return wheel; +} + +void wheel_delete(struct timer_wheel *wheel) +{ + int i; + + for (i = 0; i < wheel->slots; i++) { + list_delete(&wheel->wheel_slot_lists[i]); + } + + EVENT_OFF(wheel->timer); + XFREE(MTYPE_TIMER_WHEEL_LIST, wheel->wheel_slot_lists); + XFREE(MTYPE_TIMER_WHEEL, wheel->name); + XFREE(MTYPE_TIMER_WHEEL, wheel); +} + +int wheel_add_item(struct timer_wheel *wheel, void *item) +{ + long long slot; + + slot = (*wheel->slot_key)(item); + + if (debug_timer_wheel) + zlog_debug("%s: Inserting %p: %lld %lld", __func__, item, slot, + slot % wheel->slots); + listnode_add(wheel->wheel_slot_lists[slot % wheel->slots], item); + + return 0; +} + +int wheel_remove_item(struct timer_wheel *wheel, void *item) +{ + long long slot; + + slot = (*wheel->slot_key)(item); + + if (debug_timer_wheel) + zlog_debug("%s: Removing %p: %lld %lld", __func__, item, slot, + slot % wheel->slots); + listnode_delete(wheel->wheel_slot_lists[slot % wheel->slots], item); + + return 0; +} diff --git a/lib/wheel.h b/lib/wheel.h new file mode 100644 index 0000000..0d9ac10 --- /dev/null +++ b/lib/wheel.h @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Timer Wheel + * Copyright (C) 2016 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __WHEEL_H__ +#define __WHEEL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct timer_wheel { + char *name; + struct event_loop *master; + int slots; + long long curr_slot; + unsigned int period; + unsigned int nexttime; + unsigned int slots_to_skip; + + struct list **wheel_slot_lists; + struct event *timer; + /* + * Key to determine what slot the item belongs in + */ + unsigned int (*slot_key)(const void *); + + void (*slot_run)(void *); +}; + +/* + * Creates a timer wheel + * + * master - Thread master structure for the process + * period - The Time in seconds that the timer wheel will + * take before it starts issuing commands again + * for items in each slot + * slots - The number of slots to have in this particular + * timer wheel + * slot_key - A hashing function of some sort that will allow + * the timer wheel to put items into individual slots + * slot_run - The function to run over each item in a particular slot + * + * Creates a timer wheel that will wake up 'slots' times over the entire + * wheel. Each time the timer wheel wakes up it will iterate through + * and run the slot_run function for each item stored in that particular + * slot. + * + * The timer code is 'intelligent' in that it notices if anything is + * in a particular slot and can schedule the next timer to skip + * the empty slot. + * + * The general purpose of a timer wheel is to reduce events in a system. + * A perfect example of usage for this is say hello packets that need + * to be sent out to all your neighbors. Suppose a large routing protocol + * has to send keepalive packets every Y seconds to each of it's peers. + * At scale we can have a very large number of peers, X. + * This means that we will have X timing events every Y seconds. + * If you replace these events with a timer wheel that has Z slots + * you will have at most Y/Z timer events if each slot has a work item + * in it. + * + * When X is large the number of events in a system can quickly escalate + * and cause significant amount of time handling thread events instead + * of running your code. + */ +struct timer_wheel *wheel_init(struct event_loop *master, int period, + size_t slots, + unsigned int (*slot_key)(const void *), + void (*slot_run)(void *), const char *run_name); + +/* + * Delete the specified timer wheel created + */ +void wheel_delete(struct timer_wheel *); + +/* + * wheel - The Timer wheel being modified + * item - The generic data structure that will be handed + * to the slot_run function. + * + * Add item to a slot setup by the slot_key, + * possibly change next time pop. + */ +int wheel_add_item(struct timer_wheel *wheel, void *item); + +/* + * wheel - The Timer wheel being modified. + * item - The item to remove from one of the slots in + * the timer wheel. + * + * Remove a item to a slot setup by the slot_key, + * possibly change next time pop. + */ +int wheel_remove_item(struct timer_wheel *wheel, void *item); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/workqueue.c b/lib/workqueue.c new file mode 100644 index 0000000..d630af1 --- /dev/null +++ b/lib/workqueue.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quagga Work Queue Support. + * + * Copyright (C) 2005 Sun Microsystems, Inc. + */ + +#include +#include "frrevent.h" +#include "memory.h" +#include "workqueue.h" +#include "linklist.h" +#include "command.h" +#include "log.h" + +DEFINE_MTYPE(LIB, WORK_QUEUE, "Work queue"); +DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_ITEM, "Work queue item"); +DEFINE_MTYPE_STATIC(LIB, WORK_QUEUE_NAME, "Work queue name string"); + +/* master list of work_queues */ +static struct list _work_queues; +/* pointer primarily to avoid an otherwise harmless warning on + * ALL_LIST_ELEMENTS_RO + */ +static struct list *work_queues = &_work_queues; + +#define WORK_QUEUE_MIN_GRANULARITY 1 + +static struct work_queue_item *work_queue_item_new(struct work_queue *wq) +{ + struct work_queue_item *item; + assert(wq); + + item = XCALLOC(MTYPE_WORK_QUEUE_ITEM, sizeof(struct work_queue_item)); + + return item; +} + +static void work_queue_item_free(struct work_queue_item *item) +{ + XFREE(MTYPE_WORK_QUEUE_ITEM, item); + return; +} + +static inline void work_queue_item_dequeue(struct work_queue *wq, + struct work_queue_item *item) +{ + assert(wq->item_count > 0); + + wq->item_count--; + STAILQ_REMOVE(&wq->items, item, work_queue_item, wq); +} + +static void work_queue_item_remove(struct work_queue *wq, + struct work_queue_item *item) +{ + assert(item && item->data); + + /* call private data deletion callback if needed */ + if (wq->spec.del_item_data) + wq->spec.del_item_data(wq, item->data); + + work_queue_item_dequeue(wq, item); + + work_queue_item_free(item); + + return; +} + +/* create new work queue */ +struct work_queue *work_queue_new(struct event_loop *m, const char *queue_name) +{ + struct work_queue *new; + + new = XCALLOC(MTYPE_WORK_QUEUE, sizeof(struct work_queue)); + + new->name = XSTRDUP(MTYPE_WORK_QUEUE_NAME, queue_name); + new->master = m; + SET_FLAG(new->flags, WQ_UNPLUGGED); + + STAILQ_INIT(&new->items); + + listnode_add(work_queues, new); + + new->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; + + /* Default values, can be overridden by caller */ + new->spec.hold = WORK_QUEUE_DEFAULT_HOLD; + new->spec.yield = EVENT_YIELD_TIME_SLOT; + new->spec.retry = WORK_QUEUE_DEFAULT_RETRY; + + return new; +} + +void work_queue_free_and_null(struct work_queue **wqp) +{ + struct work_queue *wq = *wqp; + + EVENT_OFF(wq->thread); + + while (!work_queue_empty(wq)) { + struct work_queue_item *item = work_queue_last_item(wq); + + work_queue_item_remove(wq, item); + } + + listnode_delete(work_queues, wq); + + XFREE(MTYPE_WORK_QUEUE_NAME, wq->name); + XFREE(MTYPE_WORK_QUEUE, wq); + + *wqp = NULL; +} + +bool work_queue_is_scheduled(struct work_queue *wq) +{ + return event_is_scheduled(wq->thread); +} + +static int work_queue_schedule(struct work_queue *wq, unsigned int delay) +{ + /* if appropriate, schedule work queue thread */ + if (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) && + !event_is_scheduled(wq->thread) && !work_queue_empty(wq)) { + /* Schedule timer if there's a delay, otherwise just schedule + * as an 'event' + */ + if (delay > 0) { + event_add_timer_msec(wq->master, work_queue_run, wq, + delay, &wq->thread); + event_ignore_late_timer(wq->thread); + } else + event_add_event(wq->master, work_queue_run, wq, 0, + &wq->thread); + + /* set thread yield time, if needed */ + if (event_is_scheduled(wq->thread) && + wq->spec.yield != EVENT_YIELD_TIME_SLOT) + event_set_yield_time(wq->thread, wq->spec.yield); + return 1; + } else + return 0; +} + +static inline void work_queue_item_enqueue(struct work_queue *wq, + struct work_queue_item *item) +{ + STAILQ_INSERT_TAIL(&wq->items, item, wq); + wq->item_count++; +} + +void work_queue_add(struct work_queue *wq, void *data) +{ + struct work_queue_item *item; + + assert(wq); + + item = work_queue_item_new(wq); + + item->data = data; + work_queue_item_enqueue(wq, item); + + work_queue_schedule(wq, wq->spec.hold); + + return; +} + +static void work_queue_item_requeue(struct work_queue *wq, + struct work_queue_item *item) +{ + work_queue_item_dequeue(wq, item); + + /* attach to end of list */ + work_queue_item_enqueue(wq, item); +} + +DEFUN (show_work_queues, + show_work_queues_cmd, + "show work-queues", + SHOW_STR + "Work Queue information\n") +{ + struct listnode *node; + struct work_queue *wq; + + vty_out(vty, "%c %8s %5s %8s %8s %21s\n", ' ', "List", "(ms) ", + "Q. Runs", "Yields", "Cycle Counts "); + vty_out(vty, "%c %8s %5s %8s %8s %7s %6s %8s %6s %s\n", 'P', "Items", + "Hold", "Total", "Total", "Best", "Gran.", "Total", "Avg.", + "Name"); + + for (ALL_LIST_ELEMENTS_RO(work_queues, node, wq)) { + vty_out(vty, "%c %8d %5d %8ld %8ld %7d %6d %8ld %6u %s\n", + (CHECK_FLAG(wq->flags, WQ_UNPLUGGED) ? ' ' : 'P'), + work_queue_item_count(wq), wq->spec.hold, wq->runs, + wq->yields, wq->cycles.best, wq->cycles.granularity, + wq->cycles.total, + (wq->runs) ? (unsigned int)(wq->cycles.total / wq->runs) + : 0, + wq->name); + } + + return CMD_SUCCESS; +} + +void workqueue_cmd_init(void) +{ + install_element(VIEW_NODE, &show_work_queues_cmd); +} + +/* 'plug' a queue: Stop it from being scheduled, + * ie: prevent the queue from draining. + */ +void work_queue_plug(struct work_queue *wq) +{ + EVENT_OFF(wq->thread); + + UNSET_FLAG(wq->flags, WQ_UNPLUGGED); +} + +/* unplug queue, schedule it again, if appropriate + * Ie: Allow the queue to be drained again + */ +void work_queue_unplug(struct work_queue *wq) +{ + SET_FLAG(wq->flags, WQ_UNPLUGGED); + + /* if thread isnt already waiting, add one */ + work_queue_schedule(wq, wq->spec.hold); +} + +/* timer thread to process a work queue + * will reschedule itself if required, + * otherwise work_queue_item_add + */ +void work_queue_run(struct event *thread) +{ + struct work_queue *wq; + struct work_queue_item *item, *titem; + wq_item_status ret = WQ_SUCCESS; + unsigned int cycles = 0; + char yielded = 0; + + wq = EVENT_ARG(thread); + + assert(wq); + + /* calculate cycle granularity: + * list iteration == 1 run + * listnode processing == 1 cycle + * granularity == # cycles between checks whether we should yield. + * + * granularity should be > 0, and can increase slowly after each run to + * provide some hysteris, but not past cycles.best or 2*cycles. + * + * Best: starts low, can only increase + * + * Granularity: starts at WORK_QUEUE_MIN_GRANULARITY, can be decreased + * if we run to end of time slot, can increase otherwise + * by a small factor. + * + * We could use just the average and save some work, however we want to + * be + * able to adjust quickly to CPU pressure. Average wont shift much if + * daemon has been running a long time. + */ + if (wq->cycles.granularity == 0) + wq->cycles.granularity = WORK_QUEUE_MIN_GRANULARITY; + + STAILQ_FOREACH_SAFE (item, &wq->items, wq, titem) { + assert(item->data); + + /* dont run items which are past their allowed retries */ + if (item->ran > wq->spec.max_retries) { + work_queue_item_remove(wq, item); + continue; + } + + /* run and take care of items that want to be retried + * immediately */ + do { + ret = wq->spec.workfunc(wq, item->data); + item->ran++; + } while (item->ran < wq->spec.max_retries); + + switch (ret) { + case WQ_QUEUE_BLOCKED: { + /* decrement item->ran again, cause this isn't an item + * specific error, and retry later + */ + item->ran--; + goto stats; + } + case WQ_REQUEUE: { + item->ran--; + work_queue_item_requeue(wq, item); + /* If a single node is being used with a meta-queue + * (e.g., zebra), + * update the next node as we don't want to exit the + * thread and + * reschedule it after every node. By definition, + * WQ_REQUEUE is + * meant to continue the processing; the yield logic + * will kick in + * to terminate the thread when time has exceeded. + */ + if (titem == NULL) + titem = item; + break; + } + case WQ_SUCCESS: + default: { + work_queue_item_remove(wq, item); + break; + } + } + + /* completed cycle */ + cycles++; + + /* test if we should yield */ + if (!(cycles % wq->cycles.granularity) && + event_should_yield(thread)) { + yielded = 1; + goto stats; + } + } + +stats: + +#define WQ_HYSTERESIS_FACTOR 4 + + /* we yielded, check whether granularity should be reduced */ + if (yielded && (cycles < wq->cycles.granularity)) { + wq->cycles.granularity = + ((cycles > 0) ? cycles : WORK_QUEUE_MIN_GRANULARITY); + } + /* otherwise, should granularity increase? */ + else if (cycles >= (wq->cycles.granularity)) { + if (cycles > wq->cycles.best) + wq->cycles.best = cycles; + + /* along with yielded check, provides hysteresis for granularity + */ + if (cycles > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR + * WQ_HYSTERESIS_FACTOR)) + wq->cycles.granularity *= + WQ_HYSTERESIS_FACTOR; /* quick ramp-up */ + else if (cycles + > (wq->cycles.granularity * WQ_HYSTERESIS_FACTOR)) + wq->cycles.granularity += WQ_HYSTERESIS_FACTOR; + } +#undef WQ_HYSTERIS_FACTOR + + wq->runs++; + wq->cycles.total += cycles; + if (yielded) + wq->yields++; + + /* Is the queue done yet? If it is, call the completion callback. */ + if (!work_queue_empty(wq)) { + if (ret == WQ_QUEUE_BLOCKED) + work_queue_schedule(wq, wq->spec.retry); + else + work_queue_schedule(wq, 0); + + } else if (wq->spec.completion_func) + wq->spec.completion_func(wq); +} diff --git a/lib/workqueue.h b/lib/workqueue.h new file mode 100644 index 0000000..a495fe8 --- /dev/null +++ b/lib/workqueue.h @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quagga Work Queues. + * + * Copyright (C) 2005 Sun Microsystems, Inc. + */ + +#ifndef _QUAGGA_WORK_QUEUE_H +#define _QUAGGA_WORK_QUEUE_H + +#include "memory.h" +#include "queue.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MTYPE(WORK_QUEUE); + +/* Hold time for the initial schedule of a queue run, in millisec */ +#define WORK_QUEUE_DEFAULT_HOLD 50 + +/* Retry for queue that is 'blocked' or 'retry later' */ +#define WORK_QUEUE_DEFAULT_RETRY 0 + +/* action value, for use by item processor and item error handlers */ +typedef enum { + WQ_SUCCESS = 0, + WQ_REQUEUE, /* requeue item, continue processing work queue */ + WQ_QUEUE_BLOCKED, /* Queue cant be processed at this time. + * Similar to WQ_RETRY_LATER, but doesn't penalise + * the particular item.. */ +} wq_item_status; + +/* A single work queue item, unsurprisingly */ +struct work_queue_item { + STAILQ_ENTRY(work_queue_item) wq; + void *data; /* opaque data */ + unsigned short ran; /* # of times item has been run */ +}; + +#define WQ_UNPLUGGED (1 << 0) /* available for draining */ + +struct work_queue { + /* Everything but the specification struct is private + * the following may be read + */ + struct event_loop *master; /* thread master */ + struct event *thread; /* thread, if one is active */ + char *name; /* work queue name */ + + /* Specification for this work queue. + * Public, must be set before use by caller. May be modified at will. + */ + struct { + /* optional opaque user data, global to the queue. */ + void *data; + + /* work function to process items with: + * First argument is the workqueue queue. + * Second argument is the item data + */ + wq_item_status (*workfunc)(struct work_queue *, void *); + + /* callback to delete user specific item data */ + void (*del_item_data)(struct work_queue *, void *); + + /* completion callback, called when queue is emptied, optional + */ + void (*completion_func)(struct work_queue *); + + /* max number of retries to make for item that errors */ + unsigned int max_retries; + + unsigned int hold; /* hold time for first run, in ms */ + + unsigned long + yield; /* yield time in us for associated thread */ + + uint32_t retry; /* Optional retry timeout if queue is blocked */ + } spec; + + /* remaining fields should be opaque to users */ + STAILQ_HEAD(work_queue_items, work_queue_item) + items; /* queue item list */ + int item_count; /* queued items */ + unsigned long runs; /* runs count */ + unsigned long yields; /* yields count */ + + struct { + unsigned int best; + unsigned int granularity; + unsigned long total; + } cycles; /* cycle counts */ + + /* private state */ + uint16_t flags; /* user set flag */ +}; + +/* User API */ + +static inline int work_queue_item_count(struct work_queue *wq) +{ + return wq->item_count; +} + +static inline bool work_queue_empty(struct work_queue *wq) +{ + return (wq->item_count == 0) ? true : false; +} + +static inline struct work_queue_item * +work_queue_last_item(struct work_queue *wq) +{ + return STAILQ_LAST(&wq->items, work_queue_item, wq); +} + +/* create a new work queue, of given name. + * user must fill in the spec of the returned work queue before adding + * anything to it + */ +extern struct work_queue *work_queue_new(struct event_loop *m, + const char *queue_name); + +/* destroy work queue */ +/* + * The usage of work_queue_free is being transitioned to pass + * in the double pointer to remove use after free's. + */ +extern void work_queue_free_and_null(struct work_queue **wqp); + +/* Add the supplied data as an item onto the workqueue */ +extern void work_queue_add(struct work_queue *wq, void *item); + +/* plug the queue, ie prevent it from being drained / processed */ +extern void work_queue_plug(struct work_queue *wq); +/* unplug the queue, allow it to be drained again */ +extern void work_queue_unplug(struct work_queue *wq); + +bool work_queue_is_scheduled(struct work_queue *wq); + +/* Helpers, exported for thread.c and command.c */ +extern void work_queue_run(struct event *thread); + +/* Function to initialize the workqueue cli */ +extern void workqueue_cmd_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAGGA_WORK_QUEUE_H */ diff --git a/lib/xref.c b/lib/xref.c new file mode 100644 index 0000000..fc0ba62 --- /dev/null +++ b/lib/xref.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2017-20 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "xref.h" +#include "vty.h" +#include "jhash.h" +#include "sha256.h" +#include "memory.h" +#include "hash.h" + +struct xref_block *xref_blocks; +static struct xref_block **xref_block_last = &xref_blocks; + +struct xrefdata_uid_head xrefdata_uid = INIT_RBTREE_UNIQ(xrefdata_uid); + +static void base32(uint8_t **inpos, int *bitpos, + char *out, size_t n_chars) +{ + static const char base32ch[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; + + char *opos = out; + uint8_t *in = *inpos; + int bp = *bitpos; + + while (opos < out + n_chars) { + uint32_t bits = in[0] | (in[1] << 8); + + if (bp == -1) + bits |= 0x10; + else + bits >>= bp; + + *opos++ = base32ch[bits & 0x1f]; + + bp += 5; + if (bp >= 8) + in++, bp -= 8; + } + *opos = '\0'; + *inpos = in; + *bitpos = bp; +} + +static void xref_add_one(const struct xref *xref) +{ + SHA256_CTX sha; + struct xrefdata *xrefdata; + + const char *filename, *p, *q; + uint8_t hash[32], *h = hash; + uint32_t be_val; + int bitpos; + + if (!xref || !xref->xrefdata) + return; + + xrefdata = xref->xrefdata; + xrefdata->xref = xref; + + if (!xrefdata->hashstr) + return; + + /* as far as the unique ID is concerned, only use the last + * directory name + filename, e.g. "bgpd/bgp_route.c". This + * gives a little leeway in moving things and avoids IDs being + * screwed up by out of tree builds or absolute pathnames. + */ + filename = xref->file; + p = strrchr(filename, '/'); + if (p) { + q = memrchr(filename, '/', p - filename); + if (q) + filename = q + 1; + } + + SHA256_Init(&sha); + SHA256_Update(&sha, filename, strlen(filename)); + SHA256_Update(&sha, xrefdata->hashstr, + strlen(xrefdata->hashstr)); + be_val = htonl(xrefdata->hashu32[0]); + SHA256_Update(&sha, &be_val, sizeof(be_val)); + be_val = htonl(xrefdata->hashu32[1]); + SHA256_Update(&sha, &be_val, sizeof(be_val)); + SHA256_Final(hash, &sha); + + bitpos = -1; + base32(&h, &bitpos, &xrefdata->uid[0], 5); + xrefdata->uid[5] = '-'; + base32(&h, &bitpos, &xrefdata->uid[6], 5); + + xrefdata_uid_add(&xrefdata_uid, xrefdata); +} + +void xref_gcc_workaround(const struct xref *xref) +{ + xref_add_one(xref); +} + +void xref_block_add(struct xref_block *block) +{ + const struct xref * const *xrefp; + + *xref_block_last = block; + xref_block_last = &block->next; + + for (xrefp = block->start; xrefp < block->stop; xrefp++) + xref_add_one(*xrefp); +} diff --git a/lib/xref.h b/lib/xref.h new file mode 100644 index 0000000..f06d65b --- /dev/null +++ b/lib/xref.h @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2017-20 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_XREF_H +#define _FRR_XREF_H + +#include +#include +#include +#include +#include "compiler.h" +#include "typesafe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum xref_type { + XREFT_NONE = 0, + + XREFT_EVENTSCHED = 0x100, + + XREFT_LOGMSG = 0x200, + XREFT_ASSERT = 0x280, + + XREFT_DEFUN = 0x300, + XREFT_INSTALL_ELEMENT = 0x301, +}; + +/* struct xref is the "const" part; struct xrefdata is the writable part. */ +struct xref; +struct xrefdata; + +struct xref { + /* this may be NULL, depending on the type of the xref. + * if it is NULL, the xref has no unique ID and cannot be accessed + * through that mechanism. + */ + struct xrefdata *xrefdata; + + /* type isn't generally needed at runtime */ + enum xref_type type; + + /* code location */ + int line; + const char *file; + const char *func; + + /* -- 32 bytes (on 64bit) -- */ + + /* type-specific bits appended by embedding this struct */ +}; + +PREDECL_RBTREE_UNIQ(xrefdata_uid); + +struct xrefdata { + /* pointer back to the const part; this will be initialized at + * program startup by xref_block_add(). (Creating structs with + * cyclic pointers to each other is not easily possible for + * function-scoped static variables.) + * + * There is no xrefdata w/o xref, but there are xref w/o xrefdata. + */ + const struct xref *xref; + + /* base32(crockford) of unique ID. not all bytes are used, but + * let's pad to 16 for simplicity + */ + char uid[16]; + + /* hash/uid input + * if hashstr is NULL, no UID is assigned/calculated. Use macro + * string concatenation if multiple values need to be fed in. + * (This is here to not make the UID calculation independent of + * xref type.) + */ + const char *hashstr; + uint32_t hashu32[2]; + + /* -- 32 bytes (on 64bit) -- */ + struct xrefdata_uid_item xui; +}; + +static inline int xrefdata_uid_cmp(const struct xrefdata *a, + const struct xrefdata *b) +{ + return strcmp(a->uid, b->uid); +} + +DECLARE_RBTREE_UNIQ(xrefdata_uid, struct xrefdata, xui, xrefdata_uid_cmp); +extern struct xrefdata_uid_head xrefdata_uid; + +/* linker "magic" is used to create an array of pointers to struct xref. + * the result is a contiguous block of pointers, each pointing to an xref + * somewhere in the code. The linker gives us start and end pointers, we + * stuff those into the struct below and hook up a constructor to run at + * program startup with the struct passed. + * + * Placing the xrefs themselves into an array doesn't work because they'd + * need to be constant size, but we're embedding struct xref into other + * container structs with extra data. Also this means that external code + * (like the python xref dumper) can safely ignore extra data at the end of + * xrefs without needing to account for size in iterating the array. + * + * If you're curious, this is also how __attribute__((constructor)) (and + * destructor) are implemented - there are 2 arrays, ".init_array" and + * ".fini_array", containing function pointers. The magic turns out to be + * quite mundane, actually ;) + * + * The slightly tricky bit is that this is a per-object (i.e. per shared + * library & daemon) thing and we need a bit of help (in XREF_SETUP) to + * initialize correctly. + */ + +struct xref_block { + struct xref_block *next; + const struct xref * const *start; + const struct xref * const *stop; +}; + +extern struct xref_block *xref_blocks; +extern void xref_block_add(struct xref_block *block); +extern void xref_gcc_workaround(const struct xref *xref); + +#ifndef HAVE_SECTION_SYMS +/* we have a build system patch to use GNU ld on Solaris; if that doesn't + * work we end up on Solaris ld which doesn't support the section start/end + * symbols. + */ +#define XREF_SETUP() \ + CPP_NOTICE("Missing linker support for section arrays. Solaris ld?") +#else +/* the actual symbols that the linker provides for us. Note these are + * _symbols_ referring to the actual section start/end, i.e. they are very + * much NOT _pointers_, rather the symbol *value* is the pointer. Declaring + * them as size-1 arrays is the "best" / "right" thing. + */ +extern const struct xref * const __start_xref_array[1] DSO_LOCAL; +extern const struct xref * const __stop_xref_array[1] DSO_LOCAL; + +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +/* no redzone around each of the xref_p please, we're building an array out + * of variables here. kinda breaks things if there's redzones between each + * array item. + */ +#define xref_array_attr used, section("xref_array"), no_sanitize("address") +#endif +#endif +#ifndef xref_array_attr +#define xref_array_attr used, section("xref_array") +#endif + +/* this macro is invoked once for each standalone DSO through + * FRR_MODULE_SETUP \ + * }-> FRR_COREMOD_SETUP -> XREF_SETUP + * FRR_DAEMON_INFO / + */ +#define XREF_SETUP() \ + static const struct xref _dummy_xref = { \ + /* .xrefdata = */ NULL, \ + /* .type = */ XREFT_NONE, \ + /* .line = */ __LINE__, \ + /* .file = */ __FILE__, \ + /* .func = */ "dummy", \ + }; \ + static const struct xref * const _dummy_xref_p \ + __attribute__((xref_array_attr)) = &_dummy_xref; \ + static void __attribute__((used, _CONSTRUCTOR(1100))) \ + _xref_init(void) { \ + static struct xref_block _xref_block = { \ + .next = NULL, \ + .start = __start_xref_array, \ + .stop = __stop_xref_array, \ + }; \ + xref_block_add(&_xref_block); \ + } \ + asm(XREF_NOTE); \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +/* the following blurb emits an ELF note indicating start and end of the xref + * array in the binary. This is technically the "correct" entry point for + * external tools reading xrefs out of an ELF shared library or executable. + * + * right now, the extraction tools use the section header for "xref_array" + * instead; however, section headers are technically not necessarily preserved + * for fully linked libraries or executables. (In practice they are only + * stripped by obfuscation tools.) + * + * conversely, for reading xrefs out of a single relocatable object file (e.g. + * bar.o), section headers are the right thing to look at since the note is + * only emitted for the final binary once. + * + * FRR itself does not need this note to operate correctly, so if you have + * some build issue with it just add -DFRR_XREF_NO_NOTE to your build flags + * to disable it. + */ +#if defined(FRR_XREF_NO_NOTE) || defined(__mips64) +#define XREF_NOTE "" + +/* mips64 note: MIPS64 (regardless of endianness, both mips64 & mips64el) + * does not have a 64-bit PC-relative relocation type. Unfortunately, a + * 64-bit PC-relative relocation is exactly what the below asm magic emits. + * Therefore, the xref ELF note is permanently disabled on MIPS64. + * + * For some context, refer to https://reviews.llvm.org/D80390 + * + * As noted above, xref extraction still works through the section header + * path, so no functionality is lost. + */ +#else + +#if __SIZEOF_POINTER__ == 4 +#define _NOTE_2PTRSIZE "8" +#define _NOTE_PTR ".long" +#elif __SIZEOF_POINTER__ == 8 +#define _NOTE_2PTRSIZE "16" +#define _NOTE_PTR ".quad" +#else +#error unsupported pointer size +#endif + +#ifdef __arm__ +# define asmspecial "%" +#else +# define asmspecial "@" +#endif + +#define XREF_NOTE \ + "" "\n"\ + " .type _frr_xref_note," asmspecial "object" "\n"\ + " .pushsection .note.FRR,\"a\"," asmspecial "note" "\n"\ + " .p2align 2" "\n"\ + "_frr_xref_note:" "\n"\ + " .long 9" "\n"\ + " .long " _NOTE_2PTRSIZE "\n"\ + " .ascii \"XREF\"" "\n"\ + " .ascii \"FRRouting\\0\\0\\0\"" "\n"\ + " " _NOTE_PTR " __start_xref_array-." "\n"\ + " " _NOTE_PTR " __stop_xref_array-." "\n"\ + " .size _frr_xref_note, .-_frr_xref_note" "\n"\ + " .popsection" "\n"\ + "" "\n"\ + /* end */ +#endif + +#endif /* HAVE_SECTION_SYMS */ + +/* emit the array entry / pointer to xref */ +#if defined(__clang__) || !defined(__cplusplus) +#define XREF_LINK(dst) \ + static const struct xref * const NAMECTR(xref_p_) \ + __attribute__((xref_array_attr)) \ + = &(dst) \ + /* end */ + +#else /* GCC && C++ */ +/* workaround for GCC bug 41091 (dated 2009), added in 2021... + * + * this breaks extraction of xrefs with xrelfo.py (because the xref_array + * entry will be missing), but provides full runtime functionality. To get + * the proper list of xrefs from C++ code, build with clang... + */ +struct _xref_p { + const struct xref * const ptr; + + _xref_p(const struct xref *_ptr) : ptr(_ptr) + { + xref_gcc_workaround(_ptr); + } +}; + +#define XREF_LINK(dst) \ + static const struct _xref_p __attribute__((used)) \ + NAMECTR(xref_p_)(&(dst)) \ + /* end */ +#endif + +/* initializer for a "struct xref" */ +#define XREF_INIT(type_, xrefdata_, func_) \ + { \ + /* .xrefdata = */ (xrefdata_), \ + /* .type = */ (type_), \ + /* .line = */ __LINE__, \ + /* .file = */ __FILE__, \ + /* .func = */ func_, \ + } \ + /* end */ + +/* use with XREF_INIT when outside of a function, i.e. no __func__ */ +#define XREF_NO_FUNC "" + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_XREF_H */ diff --git a/lib/yang.c b/lib/yang.c new file mode 100644 index 0000000..6a8e522 --- /dev/null +++ b/lib/yang.c @@ -0,0 +1,1526 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "darr.h" +#include "log.h" +#include "lib_errors.h" +#include "yang.h" +#include "yang_translator.h" +#include +#include "northbound.h" +#include "frrstr.h" + +#include "lib/config_paths.h" + +DEFINE_MTYPE_STATIC(LIB, YANG_MODULE, "YANG module"); +DEFINE_MTYPE_STATIC(LIB, YANG_DATA, "YANG data structure"); + +/* Safe to remove after libyang 2.2.8 */ +#if (LY_VERSION_MAJOR < 3) +#define yang_lyd_find_xpath3(ctx_node, tree, xpath, format, prefix_data, vars, \ + set) \ + lyd_find_xpath3(ctx_node, tree, xpath, vars, set) + +#ifndef LYD_NEW_VAL_OUTPUT +#define LYD_NEW_VAL_OUTPUT LYD_NEW_PATH_OUTPUT +#endif + +#else +#define yang_lyd_find_xpath3(ctx_node, tree, xpath, format, prefix_data, vars, \ + set) \ + lyd_find_xpath3(ctx_node, tree, xpath, LY_VALUE_JSON, NULL, vars, set) +#endif + +/* libyang container. */ +struct ly_ctx *ly_native_ctx; + +static struct yang_module_embed *embeds, **embedupd = &embeds; + +void yang_module_embed(struct yang_module_embed *embed) +{ + embed->next = NULL; + *embedupd = embed; + embedupd = &embed->next; +} + +static LY_ERR yang_module_imp_clb(const char *mod_name, const char *mod_rev, + const char *submod_name, + const char *submod_rev, void *user_data, + LYS_INFORMAT *format, + const char **module_data, + void (**free_module_data)(void *, void *)) +{ + struct yang_module_embed *e; + + if (!strcmp(mod_name, "ietf-inet-types") || + !strcmp(mod_name, "ietf-yang-types") || + !strcmp(mod_name, "ietf-yang-metadata")) + /* libyang has these built in, don't try finding them here */ + return LY_ENOTFOUND; + + for (e = embeds; e; e = e->next) { + if (e->sub_mod_name && submod_name) { + if (strcmp(e->sub_mod_name, submod_name)) + continue; + + if (submod_rev && strcmp(e->sub_mod_rev, submod_rev)) + continue; + } else { + if (strcmp(e->mod_name, mod_name)) + continue; + + if (mod_rev && strcmp(e->mod_rev, mod_rev)) + continue; + } + + *format = e->format; + *module_data = e->data; + return LY_SUCCESS; + } + + /* We get here for indirect modules like ietf-inet-types */ + zlog_debug( + "YANG model \"%s@%s\" \"%s@%s\"not embedded, trying external file", + mod_name, mod_rev ? mod_rev : "*", + submod_name ? submod_name : "*", submod_rev ? submod_rev : "*"); + + return LY_ENOTFOUND; +} + +/* clang-format off */ +static const char *const frr_native_modules[] = { + "frr-interface", + "frr-vrf", + "frr-routing", + "frr-affinity-map", + "frr-route-map", + "frr-nexthop", + "frr-ripd", + "frr-ripngd", + "frr-isisd", + "frr-vrrpd", + "frr-zebra", + "frr-pathd", +}; +/* clang-format on */ + +/* Generate the yang_modules tree. */ +static inline int yang_module_compare(const struct yang_module *a, + const struct yang_module *b) +{ + return strcmp(a->name, b->name); +} +RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare) + +struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules); + +struct yang_module *yang_module_load(const char *module_name, + const char **features) +{ + struct yang_module *module; + const struct lys_module *module_info; + + module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL, + features); + if (!module_info) { + flog_err(EC_LIB_YANG_MODULE_LOAD, + "%s: failed to load data model: %s", __func__, + module_name); + exit(1); + } + + module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module)); + module->name = module_name; + module->info = module_info; + + if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) { + flog_err(EC_LIB_YANG_MODULE_LOADED_ALREADY, + "%s: YANG module is loaded already: %s", __func__, + module_name); + exit(1); + } + + return module; +} + +void yang_module_load_all(void) +{ + static const char * const all_features[] = { "*", NULL }; + + for (size_t i = 0; i < array_size(frr_native_modules); i++) + yang_module_load(frr_native_modules[i], (const char **)all_features); +} + +struct yang_module *yang_module_find(const char *module_name) +{ + struct yang_module s; + + s.name = module_name; + return RB_FIND(yang_modules, &yang_modules, &s); +} + +int yang_snodes_iterate_subtree(const struct lysc_node *snode, + const struct lys_module *module, + yang_iterate_cb cb, uint16_t flags, void *arg) +{ + const struct lysc_node *child; + int ret = YANG_ITER_CONTINUE; + + if (module && snode->module != module) + goto next; + + switch (snode->nodetype) { + case LYS_CONTAINER: + if (CHECK_FLAG(flags, YANG_ITER_FILTER_NPCONTAINERS)) { + if (!CHECK_FLAG(snode->flags, LYS_PRESENCE)) + goto next; + } + break; + case LYS_LEAF: + if (CHECK_FLAG(flags, YANG_ITER_FILTER_LIST_KEYS)) { + /* Ignore list keys. */ + if (lysc_is_key(snode)) + goto next; + } + break; + case LYS_INPUT: + case LYS_OUTPUT: + if (CHECK_FLAG(flags, YANG_ITER_FILTER_INPUT_OUTPUT)) + goto next; + break; + default: + assert(snode->nodetype != LYS_AUGMENT + && snode->nodetype != LYS_GROUPING + && snode->nodetype != LYS_USES); + break; + } + + ret = (*cb)(snode, arg); + if (ret == YANG_ITER_STOP) + return ret; + +next: + /* + * YANG leafs and leaf-lists can't have child nodes. + */ + if (CHECK_FLAG(snode->nodetype, LYS_LEAF | LYS_LEAFLIST)) + return YANG_ITER_CONTINUE; + + LY_LIST_FOR (lysc_node_child(snode), child) { + ret = yang_snodes_iterate_subtree(child, module, cb, flags, + arg); + if (ret == YANG_ITER_STOP) + return ret; + } + LY_LIST_FOR ((const struct lysc_node *)lysc_node_notifs(snode), child) { + ret = yang_snodes_iterate_subtree(child, module, cb, flags, arg); + if (ret == YANG_ITER_STOP) + return ret; + } + LY_LIST_FOR ((const struct lysc_node *)lysc_node_actions(snode), child) { + ret = yang_snodes_iterate_subtree(child, module, cb, flags, arg); + if (ret == YANG_ITER_STOP) + return ret; + } + return ret; +} + +int yang_snodes_iterate(const struct lys_module *module, yang_iterate_cb cb, + uint16_t flags, void *arg) +{ + const struct lys_module *module_iter; + uint32_t idx = 0; + int ret = YANG_ITER_CONTINUE; + + idx = ly_ctx_internal_modules_count(ly_native_ctx); + while ((module_iter = ly_ctx_get_module_iter(ly_native_ctx, &idx))) { + struct lysc_node *snode; + + if (!module_iter->implemented) + continue; + + LY_LIST_FOR (module_iter->compiled->data, snode) { + ret = yang_snodes_iterate_subtree(snode, module, cb, + flags, arg); + if (ret == YANG_ITER_STOP) + return ret; + } + LY_LIST_FOR (&module_iter->compiled->rpcs->node, snode) { + ret = yang_snodes_iterate_subtree(snode, module, cb, + flags, arg); + if (ret == YANG_ITER_STOP) + return ret; + } + LY_LIST_FOR (&module_iter->compiled->notifs->node, snode) { + ret = yang_snodes_iterate_subtree(snode, module, cb, + flags, arg); + if (ret == YANG_ITER_STOP) + return ret; + } + } + + return ret; +} + +void yang_snode_get_path(const struct lysc_node *snode, + enum yang_path_type type, char *xpath, + size_t xpath_len) +{ + switch (type) { + case YANG_PATH_SCHEMA: + (void)lysc_path(snode, LYSC_PATH_LOG, xpath, xpath_len); + break; + case YANG_PATH_DATA: + (void)lysc_path(snode, LYSC_PATH_DATA, xpath, xpath_len); + break; + default: + flog_err(EC_LIB_DEVELOPMENT, "%s: unknown yang path type: %u", + __func__, type); + exit(1); + } +} + +LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath, + struct lysc_node ***snodes, bool *simple) +{ + struct lysc_node *snode; + struct ly_set *set; + LY_ERR err; + + /* lys_find_path will not resolve complex xpaths */ + snode = (struct lysc_node *)lys_find_path(ly_ctx, NULL, xpath, 0); + if (snode) { + *darr_append(*snodes) = snode; + *simple = true; + return LY_SUCCESS; + } + + /* Try again to catch complex query cases */ + err = lys_find_xpath(ly_native_ctx, NULL, xpath, 0, &set); + if (err) + return err; + if (!set->count) { + ly_set_free(set, NULL); + return LY_ENOTFOUND; + } + + *simple = false; + darr_ensure_i(*snodes, set->count - 1); + memcpy(*snodes, set->snodes, set->count * sizeof(set->snodes[0])); + ly_set_free(set, NULL); + return LY_SUCCESS; +} + + +struct lysc_node *yang_find_snode(struct ly_ctx *ly_ctx, const char *xpath, + uint32_t options) +{ + struct lysc_node *snode; + + snode = (struct lysc_node *)lys_find_path(ly_ctx, NULL, xpath, 0); + + return snode; +} + +struct lysc_node *yang_snode_real_parent(const struct lysc_node *snode) +{ + struct lysc_node *parent = snode->parent; + + while (parent) { + switch (parent->nodetype) { + case LYS_CONTAINER: + if (CHECK_FLAG(parent->flags, LYS_PRESENCE)) + return parent; + break; + case LYS_LIST: + return parent; + default: + break; + } + parent = parent->parent; + } + + return NULL; +} + +struct lysc_node *yang_snode_parent_list(const struct lysc_node *snode) +{ + struct lysc_node *parent = snode->parent; + + while (parent) { + switch (parent->nodetype) { + case LYS_LIST: + return parent; + default: + break; + } + parent = parent->parent; + } + + return NULL; +} + +bool yang_snode_is_typeless_data(const struct lysc_node *snode) +{ + const struct lysc_node_leaf *sleaf; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + if (sleaf->type->basetype == LY_TYPE_EMPTY) + return true; + return false; + case LYS_LEAFLIST: + return false; + default: + return true; + } +} + +const char *yang_snode_get_default(const struct lysc_node *snode) +{ + const struct lysc_node_leaf *sleaf; + + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (const struct lysc_node_leaf *)snode; + return sleaf->dflt ? lyd_value_get_canonical(sleaf->module->ctx, + sleaf->dflt) + : NULL; + case LYS_LEAFLIST: + /* TODO: check leaf-list default values */ + return NULL; + default: + return NULL; + } +} + +const struct lysc_type *yang_snode_get_type(const struct lysc_node *snode) +{ + struct lysc_node_leaf *sleaf = (struct lysc_node_leaf *)snode; + struct lysc_type *type; + + if (!CHECK_FLAG(sleaf->nodetype, LYS_LEAF | LYS_LEAFLIST)) + return NULL; + + type = sleaf->type; + while (type->basetype == LY_TYPE_LEAFREF) + type = ((struct lysc_type_leafref *)type)->realtype; + + return type; +} + +unsigned int yang_snode_num_keys(const struct lysc_node *snode) +{ + const struct lysc_node_leaf *skey; + uint count = 0; + + if (!CHECK_FLAG(snode->nodetype, LYS_LIST)) + return 0; + + /* Walk list of children */ + LY_FOR_KEYS (snode, skey) { + count++; + } + return count; +} + +char *yang_dnode_get_path(const struct lyd_node *dnode, char *xpath, + size_t xpath_len) +{ + return lyd_path(dnode, LYD_PATH_STD, xpath, xpath_len); +} + +struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, const char *xpath) +{ + struct ly_set *set = NULL; + struct lyd_node *dnode_ret = NULL; + + /* + * XXX a lot of the code uses this for style I guess. It shouldn't, as + * it adds to the xpath parsing complexity in libyang. + */ + if (xpath[0] == '.' && xpath[1] == '/') + xpath += 2; + + if (lyd_find_xpath(dnode, xpath, &set)) { + /* + * Commenting out the below assert failure as it crashes mgmtd + * when bad xpath is passed. + * + * assert(0); XXX replicates old libyang1 base code + */ + goto exit; + } + if (set->count == 0) + goto exit; + + if (set->count > 1) { + flog_warn(EC_LIB_YANG_DNODE_NOT_FOUND, + "%s: found %u elements (expected 0 or 1) [xpath %s]", + __func__, set->count, xpath); + goto exit; + } + + dnode_ret = set->dnodes[0]; + +exit: + ly_set_free(set, NULL); + + return dnode_ret; +} + +struct lyd_node *yang_dnode_getf(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + return yang_dnode_get(dnode, xpath); +} + +bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath) +{ + struct ly_set *set = NULL; + bool exists = false; + + if (xpath[0] == '.' && xpath[1] == '/') + xpath += 2; + if (lyd_find_xpath(dnode, xpath, &set)) + return false; + exists = set->count > 0; + ly_set_free(set, NULL); + return exists; +} + +bool yang_dnode_existsf(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + return yang_dnode_exists(dnode, xpath); +} + +void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg, + const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + struct ly_set *set; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + if (lyd_find_xpath(dnode, xpath, &set)) { + assert(0); /* XXX libyang2: ly1 code asserted success */ + return; + } + for (unsigned int i = 0; i < set->count; i++) { + int ret; + + ret = (*cb)(set->dnodes[i], arg); + if (ret == YANG_ITER_STOP) + break; + } + + ly_set_free(set, NULL); +} + +uint32_t yang_dnode_count(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + struct ly_set *set; + uint32_t count; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + if (lyd_find_xpath(dnode, xpath, &set)) { + assert(0); + return 0; + } + + count = set->count; + + ly_set_free(set, NULL); + + return count; +} + +bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath) +{ + const struct lysc_node *snode; + struct lysc_node_leaf *sleaf; + + if (xpath) + dnode = yang_dnode_get(dnode, xpath); + + assert(dnode); + snode = dnode->schema; + switch (snode->nodetype) { + case LYS_LEAF: + sleaf = (struct lysc_node_leaf *)snode; + if (sleaf->type->basetype == LY_TYPE_EMPTY) + return false; + return lyd_is_default(dnode); + case LYS_LEAFLIST: + /* TODO: check leaf-list default values */ + return false; + case LYS_CONTAINER: + if (CHECK_FLAG(snode->flags, LYS_PRESENCE)) + return false; + return true; + default: + return false; + } +} + +bool yang_dnode_is_defaultf(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + if (!xpath_fmt) + return yang_dnode_is_default(dnode, NULL); + else { + va_list ap; + char xpath[XPATH_MAXLEN]; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + return yang_dnode_is_default(dnode, xpath); + } +} + +bool yang_dnode_is_default_recursive(const struct lyd_node *dnode) +{ + struct lyd_node *root, *dnode_iter; + + if (!yang_dnode_is_default(dnode, NULL)) + return false; + + if (CHECK_FLAG(dnode->schema->nodetype, LYS_LEAF | LYS_LEAFLIST)) + return true; + + LY_LIST_FOR (lyd_child(dnode), root) { + LYD_TREE_DFS_BEGIN (root, dnode_iter) { + if (!yang_dnode_is_default(dnode_iter, NULL)) + return false; + + LYD_TREE_DFS_END(root, dnode_iter); + } + } + + return true; +} + +void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value) +{ + assert(dnode->schema->nodetype == LYS_LEAF); + lyd_change_term(dnode, value); +} + +struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only) +{ + struct lyd_node *dnode = NULL; + int options = config_only ? LYD_VALIDATE_NO_STATE : 0; + + if (lyd_validate_all(&dnode, ly_ctx, options, NULL) != 0) { + /* Should never happen. */ + flog_err(EC_LIB_LIBYANG, "%s: lyd_validate() failed", __func__); + exit(1); + } + + return dnode; +} + +struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode) +{ + struct lyd_node *dup = NULL; + LY_ERR err; + err = lyd_dup_siblings(dnode, NULL, + LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, &dup); + assert(!err); + return dup; +} + +void yang_dnode_free(struct lyd_node *dnode) +{ + while (dnode->parent) + dnode = lyd_parent(dnode); + lyd_free_all(dnode); +} + +void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath, + const char *value) +{ + LY_ERR err; + + err = lyd_new_path(output, ly_native_ctx, xpath, value, + LYD_NEW_VAL_OUTPUT | LYD_NEW_PATH_UPDATE, NULL); + assert(err == LY_SUCCESS); +} + +struct yang_data *yang_data_new(const char *xpath, const char *value) +{ + struct yang_data *data; + + data = XCALLOC(MTYPE_YANG_DATA, sizeof(*data)); + strlcpy(data->xpath, xpath, sizeof(data->xpath)); + if (value) + data->value = strdup(value); + + return data; +} + +void yang_data_free(struct yang_data *data) +{ + if (data->value) + free(data->value); + XFREE(MTYPE_YANG_DATA, data); +} + +struct list *yang_data_list_new(void) +{ + struct list *list; + + list = list_new(); + list->del = (void (*)(void *))yang_data_free; + + return list; +} + +struct yang_data *yang_data_list_find(const struct list *list, + const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + struct yang_data *data; + struct listnode *node; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + for (ALL_LIST_ELEMENTS_RO(list, node, data)) + if (strmatch(data->xpath, xpath)) + return data; + + return NULL; +} + +/* Make libyang log its errors using FRR logging infrastructure. */ +static void ly_zlog_cb(LY_LOG_LEVEL level, const char *msg, const char *data_path +#if !(LY_VERSION_MAJOR < 3) + , + const char *schema_path, uint64_t line +#endif +) +{ + int priority = LOG_ERR; + + switch (level) { + case LY_LLERR: + priority = LOG_ERR; + break; + case LY_LLWRN: + priority = LOG_WARNING; + break; + case LY_LLVRB: + case LY_LLDBG: + priority = LOG_DEBUG; + break; + } + + if (data_path) + zlog(priority, "libyang: %s (%s)", msg, data_path); +#if !(LY_VERSION_MAJOR < 3) + else if (schema_path) + zlog(priority, "libyang %s (%s)\n", msg, schema_path); + else if (line) + zlog(priority, "libyang %s (line %" PRIu64 ")\n", msg, line); +#endif + else + zlog(priority, "libyang: %s", msg); +} + +LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, + const char *data, struct lyd_node **notif) +{ + struct lyd_node *tree; + struct ly_set *set = NULL; + struct ly_in *in = NULL; + LY_ERR err; + + err = ly_in_new_memory(data, &in); + if (err) { + zlog_err("Failed to initialize ly_in: %s", ly_last_errmsg()); + return err; + } + + err = lyd_parse_op(ly_native_ctx, NULL, in, format, LYD_TYPE_NOTIF_YANG, + &tree, NULL); + ly_in_free(in, 0); + if (err) { + zlog_err("Failed to parse notification: %s", ly_last_errmsg()); + return err; + } + + err = yang_lyd_find_xpath3(NULL, tree, xpath, LY_VALUE_JSON, NULL, NULL, + &set); + if (err) { + zlog_err("Failed to parse notification: %s", ly_last_errmsg()); + lyd_free_all(tree); + return err; + } + if (set->count == 0) { + zlog_err("Notification not found in the parsed tree: %s", xpath); + ly_set_free(set, NULL); + lyd_free_all(tree); + return LY_ENOTFOUND; + } + *notif = set->dnodes[0]; + ly_set_free(set, NULL); + return LY_SUCCESS; +} + +LY_ERR yang_parse_rpc(const char *xpath, LYD_FORMAT format, const char *data, + bool reply, struct lyd_node **rpc) +{ + const struct lysc_node *snode; + struct lyd_node *parent = NULL; + struct ly_in *in = NULL; + LY_ERR err; + + snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); + if (!snode) { + zlog_err("Failed to find RPC/action schema node: %s", xpath); + return LY_ENOTFOUND; + } + + /* If it's an action, create its parent */ + if (snode->nodetype == LYS_ACTION) { + char *parent_xpath = XSTRDUP(MTYPE_TMP, xpath); + + if (yang_xpath_pop_node(parent_xpath) != NB_OK) { + XFREE(MTYPE_TMP, parent_xpath); + zlog_err("Invalid action xpath: %s", xpath); + return LY_EINVAL; + } + + err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0, + 0, 0, NULL, &parent); + XFREE(MTYPE_TMP, parent_xpath); + if (err) { + zlog_err("Failed to create parent node for action: %s", + ly_last_errmsg()); + return err; + } + } else if (snode->nodetype != LYS_RPC) { + zlog_err("Schema node is not an RPC/action: %s", xpath); + return LY_EINVAL; + } + + err = ly_in_new_memory(data, &in); + if (err) { + lyd_free_all(parent); + zlog_err("Failed to initialize ly_in: %s", ly_last_errmsg()); + return err; + } + + err = lyd_parse_op(ly_native_ctx, parent, in, format, + reply ? LYD_TYPE_REPLY_YANG : LYD_TYPE_RPC_YANG, + NULL, rpc); + ly_in_free(in, 0); + if (err) { + lyd_free_all(parent); + zlog_err("Failed to parse RPC/action: %s", ly_last_errmsg()); + return err; + } + + return LY_SUCCESS; +} + +static ssize_t yang_print_darr(void *arg, const void *buf, size_t count) +{ + uint8_t *dst = darr_append_n(*(uint8_t **)arg, count); + + memcpy(dst, buf, count); + return count; +} + +LY_ERR yang_print_tree_append(uint8_t **darr, const struct lyd_node *root, + LYD_FORMAT format, uint32_t options) +{ + LY_ERR err; + + err = lyd_print_clb(yang_print_darr, darr, root, format, options); + if (err) + zlog_err("Failed to save yang tree: %s", ly_last_errmsg()); + else if (format != LYD_LYB) + *darr_append(*darr) = 0; + return err; +} + +uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, + uint32_t options) +{ + uint8_t *darr = NULL; + + if (yang_print_tree_append(&darr, root, format, options)) + return NULL; + return darr; +} + +char *yang_convert_lyd_format(const char *data, size_t data_len, + LYD_FORMAT in_format, LYD_FORMAT out_format, + bool shrink) +{ + struct lyd_node *tree = NULL; + uint32_t options = LYD_PRINT_WD_EXPLICIT | LYD_PRINT_WITHSIBLINGS; + uint8_t *result = NULL; + LY_ERR err; + + assert(out_format != LYD_LYB); + + if (in_format != LYD_LYB && (!data_len || data[data_len - 1] != 0)) { + zlog_err("Corrupt input data, no NUL terminating byte"); + return NULL; + } + + if (in_format == out_format) + return darr_strdup((const char *)data); + + err = lyd_parse_data_mem(ly_native_ctx, (const char *)data, in_format, + LYD_PARSE_ONLY, 0, &tree); + + if (err) { + flog_err_sys(EC_LIB_LIBYANG, + "cannot parse input data to convert: %s", + ly_last_errmsg()); + return NULL; + } + + if (shrink) + options |= LYD_PRINT_SHRINK; + + /* Take a guess at the initial capacity based on input data size */ + darr_ensure_cap(result, data_len); + err = yang_print_tree_append(&result, tree, out_format, options); + lyd_free_all(tree); + if (err) { + darr_free(result); + return NULL; + } + return (char *)result; +} + +const char *yang_print_errors(struct ly_ctx *ly_ctx, char *buf, size_t buf_len) +{ + const struct ly_err_item *ei; + + ei = ly_err_first(ly_ctx); + if (!ei) + return ""; + + strlcpy(buf, "YANG error(s):\n", buf_len); +#if (LY_VERSION_MAJOR < 3) +#define data_path path +#else +#define data_path data_path +#endif + for (; ei; ei = ei->next) { + if (ei->data_path) { + strlcat(buf, " Path: ", buf_len); + strlcat(buf, ei->data_path, buf_len); + strlcat(buf, "\n", buf_len); + } + strlcat(buf, " Error: ", buf_len); + strlcat(buf, ei->msg, buf_len); + strlcat(buf, "\n", buf_len); + } +#undef data_path + + ly_err_clean(ly_ctx, NULL); + + return buf; +} + +void yang_debugging_set(bool enable) +{ + if (enable) { + ly_log_level(LY_LLDBG); + ly_log_dbg_groups(0xFF); + } else { + ly_log_level(LY_LLERR); + ly_log_dbg_groups(0); + } +} + +struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, bool explicit_compile) +{ + struct ly_ctx *ctx = NULL; + const char *yang_models_path = YANG_MODELS_PATH; + uint options; + LY_ERR err; + + if (access(yang_models_path, R_OK | X_OK)) { + yang_models_path = NULL; + if (errno == ENOENT) + zlog_info("yang model directory \"%s\" does not exist", + YANG_MODELS_PATH); + else + flog_err_sys(EC_LIB_LIBYANG, + "cannot access yang model directory \"%s\"", + YANG_MODELS_PATH); + } + + options = LY_CTX_NO_YANGLIBRARY | LY_CTX_DISABLE_SEARCHDIR_CWD; + if (explicit_compile) + options |= LY_CTX_EXPLICIT_COMPILE; + err = ly_ctx_new(yang_models_path, options, &ctx); + if (err) + return NULL; + + if (embedded_modules) + ly_ctx_set_module_imp_clb(ctx, yang_module_imp_clb, NULL); + + return ctx; +} + +void yang_init(bool embedded_modules, bool defer_compile) +{ + /* Initialize libyang global parameters that affect all containers. */ + ly_set_log_clb(ly_zlog_cb +#if (LY_VERSION_MAJOR < 3) + , + 1 +#endif + ); + ly_log_options(LY_LOLOG | LY_LOSTORE); + + /* Initialize libyang container for native models. */ + ly_native_ctx = yang_ctx_new_setup(embedded_modules, defer_compile); + if (!ly_native_ctx) { + flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + exit(1); + } + + yang_translator_init(); +} + +void yang_init_loading_complete(void) +{ + /* Compile everything */ + if (ly_ctx_compile(ly_native_ctx) != LY_SUCCESS) { + flog_err(EC_LIB_YANG_MODULE_LOAD, + "%s: failed to compile loaded modules: %s", __func__, + ly_errmsg(ly_native_ctx)); + exit(1); + } +} + +void yang_terminate(void) +{ + struct yang_module *module; + + yang_translator_terminate(); + + while (!RB_EMPTY(yang_modules, &yang_modules)) { + module = RB_ROOT(yang_modules, &yang_modules); + + /* + * We shouldn't call ly_ctx_remove_module() here because this + * function also removes other modules that depend on it. + * + * ly_ctx_destroy() will release all memory for us. + */ + RB_REMOVE(yang_modules, &yang_modules, module); + XFREE(MTYPE_YANG_MODULE, module); + } + + ly_ctx_destroy(ly_native_ctx); +} + +const struct lyd_node *yang_dnode_get_parent(const struct lyd_node *dnode, + const char *name) +{ + const struct lyd_node *orig_dnode = dnode; + + while (orig_dnode) { + switch (orig_dnode->schema->nodetype) { + case LYS_LIST: + case LYS_CONTAINER: + if (!strcmp(orig_dnode->schema->name, name)) + return orig_dnode; + break; + default: + break; + } + + orig_dnode = lyd_parent(orig_dnode); + } + + return NULL; +} + +bool yang_is_last_list_dnode(const struct lyd_node *dnode) +{ + return (((dnode->next == NULL) + || (dnode->next + && (strcmp(dnode->next->schema->name, dnode->schema->name) + != 0))) + && dnode->prev + && ((dnode->prev == dnode) + || (strcmp(dnode->prev->schema->name, dnode->schema->name) + != 0))); +} + +bool yang_is_last_level_dnode(const struct lyd_node *dnode) +{ + const struct lyd_node *parent; + const struct lyd_node *key_leaf; + uint8_t keys_size; + + switch (dnode->schema->nodetype) { + case LYS_LIST: + assert(dnode->parent); + parent = lyd_parent(dnode); + uint snode_num_keys = yang_snode_num_keys(parent->schema); + /* XXX libyang2: q: really don't understand this code. */ + key_leaf = dnode->prev; + for (keys_size = 1; keys_size < snode_num_keys; keys_size++) + key_leaf = key_leaf->prev; + if (key_leaf->prev == dnode) + return true; + break; + case LYS_CONTAINER: + return true; + default: + break; + } + + return false; +} + +const struct lyd_node * +yang_get_subtree_with_no_sibling(const struct lyd_node *dnode) +{ + bool parent = true; + const struct lyd_node *node; + + node = dnode; + if (node->schema->nodetype != LYS_LIST) + return node; + + while (parent) { + switch (node->schema->nodetype) { + case LYS_CONTAINER: + if (!CHECK_FLAG(node->schema->flags, LYS_PRESENCE)) { + if (node->parent + && (node->parent->schema->module + == dnode->schema->module)) + node = lyd_parent(node); + else + parent = false; + } else + parent = false; + break; + case LYS_LIST: + if (yang_is_last_list_dnode(node) + && yang_is_last_level_dnode(node)) { + if (node->parent + && (node->parent->schema->module + == dnode->schema->module)) + node = lyd_parent(node); + else + parent = false; + } else + parent = false; + break; + default: + parent = false; + break; + } + } + return node; +} + +uint32_t yang_get_list_pos(const struct lyd_node *node) +{ + return lyd_list_pos(node); +} + +uint32_t yang_get_list_elements_count(const struct lyd_node *node) +{ + unsigned int count; + const struct lysc_node *schema; + + if (!node + || ((node->schema->nodetype != LYS_LIST) + && (node->schema->nodetype != LYS_LEAFLIST))) { + return 0; + } + + schema = node->schema; + count = 0; + do { + if (node->schema == schema) + ++count; + node = node->next; + } while (node); + return count; +} + +int yang_get_key_preds(char *s, const struct lysc_node *snode, + struct yang_list_keys *keys, ssize_t space) +{ + const struct lysc_node_leaf *skey; + ssize_t len2, len = 0; + ssize_t i = 0; + + LY_FOR_KEYS (snode, skey) { + assert(i < keys->num); + len2 = snprintf(s + len, space - len, "[%s='%s']", skey->name, + keys->key[i]); + if (len2 > space - len) + len = space; + else + len += len2; + i++; + } + + assert(i == keys->num); + return i; +} + +int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys) +{ + struct lyd_node *child = lyd_child(node); + + keys->num = 0; + for (; child && lysc_is_key(child->schema); child = child->next) { + const char *value = lyd_get_value(child); + + if (!value) + return NB_ERR; + strlcpy(keys->key[keys->num], value, + sizeof(keys->key[keys->num])); + keys->num++; + } + return NB_OK; +} + +int yang_xpath_pop_node(char *xpath) +{ + int len = strlen(xpath); + bool abs = xpath[0] == '/'; + char *slash; + + /* "//" or "/" => NULL */ + if (abs && (len == 1 || (len == 2 && xpath[1] == '/'))) + return NB_ERR_NOT_FOUND; + + slash = (char *)frrstr_back_to_char(xpath, '/'); + /* "/foo/bar/" or "/foo/bar//" => "/foo " */ + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + if (slash && slash == &xpath[len - 1]) { + xpath[--len] = 0; + slash = (char *)frrstr_back_to_char(xpath, '/'); + } + } + if (!slash) + return NB_ERR_NOT_FOUND; + *slash = 0; + return NB_OK; +} + +/* + * ------------------------ + * Libyang Future Functions + * ------------------------ + * + * All these functions are implemented in libyang versions (perhaps unreleased) + * beyond what we require currently so we must supply the functionality. + */ + +/* + * Safe to remove after libyang v2.1.xxx is required (.144 has a bug so + * something > .144) https://github.com/CESNET/libyang/issues/2149 + */ +LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent, + const struct lysc_node *snode, + const struct yang_list_keys *list_keys, + struct lyd_node **node) +{ +#if defined(HAVE_LYD_NEW_LIST3) && 0 + LY_ERR err; + const char *keys[LIST_MAXKEYS]; + + assert(list_keys->num <= LIST_MAXKEYS); + for (int i = 0; i < list_keys->num; i++) + keys[i] = list_keys->key[i]; + + err = lyd_new_list3(&parent->node, snode->module, snode->name, keys, + NULL, 0, node); + return err; +#else + struct lyd_node *pnode = &parent->node; + const char(*keys)[LIST_MAXKEYLEN] = list_keys->key; + + assert(list_keys->num <= 8); + switch (list_keys->num) { + case 0: + return lyd_new_list(pnode, snode->module, snode->name, false, + node); + case 1: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0]); + case 2: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1]); + case 3: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2]); + case 4: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2], keys[3]); + case 5: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2], keys[3], + keys[4]); + case 6: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2], keys[3], + keys[4], keys[5]); + case 7: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2], keys[3], + keys[4], keys[5], keys[6]); + case 8: + return lyd_new_list(pnode, snode->module, snode->name, false, + node, keys[0], keys[1], keys[2], keys[3], + keys[4], keys[5], keys[6], keys[7]); + } + _Static_assert(LIST_MAXKEYS == 8, "max key mismatch in switch unroll"); + /*NOTREACHED*/ + return LY_EINVAL; +#endif +} + + +/* + * Safe to remove after libyang v2.1.144 is required + */ +LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath) +{ + LY_ERR err; +#ifdef HAVE_LYD_TRIM_XPATH + err = lyd_trim_xpath(root, xpath, NULL); + if (err) { + flog_err_sys(EC_LIB_LIBYANG, + "cannot obtain specific result for xpath \"%s\": %s", + xpath, yang_ly_strerrcode(err)); + return err; + } + return LY_SUCCESS; +#else + struct lyd_node *node, *sib; + struct lyd_node **remove = NULL; + struct ly_set *set = NULL; + uint32_t i; + + *root = lyd_first_sibling(*root); + + err = yang_lyd_find_xpath3(NULL, *root, xpath, LY_VALUE_JSON, NULL, + NULL, &set); + if (err) { + flog_err_sys(EC_LIB_LIBYANG, + "cannot obtain specific result for xpath \"%s\": %s", + xpath, yang_ly_strerrcode(err)); + return err; + } + /* + * Mark keepers and sweep deleting non-keepers. + * + * NOTE: We assume the data-nodes have NULL priv pointers and use that + * for our mark. + */ + + /* Mark */ + for (i = 0; i < set->count; i++) { + for (node = set->dnodes[i]; node; node = &node->parent->node) { + if (node->priv) + break; + if (node == set->dnodes[i]) + node->priv = (void *)2; + else + node->priv = (void *)1; + } + } + + darr_ensure_cap(remove, 128); + LY_LIST_FOR(*root, sib) { + LYD_TREE_DFS_BEGIN (sib, node) { + /* + * If this is a direct matching node then include its + * subtree which won't be marked and would otherwise + * be removed. + */ + if (node->priv == (void *)2) + LYD_TREE_DFS_continue = 1; + else if (!node->priv) { + *darr_append(remove) = node; + LYD_TREE_DFS_continue = 1; + } + LYD_TREE_DFS_END(sib, node); + } + } + darr_foreach_i (remove, i) { + if (remove[i] == *root) + *root = (*root)->next; + lyd_free_tree(remove[i]); + } + darr_free(remove); + + ly_set_free(set, NULL); + + return LY_SUCCESS; +#endif +} + +/* Can be replaced by `lyd_parse_data` with libyang >= 2.1.156 */ +LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, + struct ly_in *in, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, + struct lyd_node **tree) +{ + struct lyd_node *child; + LY_ERR err; + + err = lyd_parse_data(ctx, parent, in, format, parse_options, + validate_options, tree); + if (err) + return err; + + if (!parent || !(parse_options & LYD_PARSE_ONLY)) + return LY_SUCCESS; + + /* + * Versions prior to 2.1.156 don't return `tree` if `parent` is not NULL + * and validation is disabled (`LYD_PARSE_ONLY`). To work around this, + * go through the children and find the one with `LYD_NEW` flag set. + */ + *tree = NULL; + + LY_LIST_FOR (lyd_child_no_keys(parent), child) { + if (child->flags & LYD_NEW) { + *tree = child; + break; + } + } + + assert(tree); + + return LY_SUCCESS; +} + +/* + * Safe to remove after libyang v2.1.128 is required + */ +const char *yang_ly_strerrcode(LY_ERR err) +{ +#ifdef HAVE_LY_STRERRCODE + return ly_strerrcode(err); +#else + switch (err) { + case LY_SUCCESS: + return "ok"; + case LY_EMEM: + return "out of memory"; + case LY_ESYS: + return "system error"; + case LY_EINVAL: + return "invalid value given"; + case LY_EEXIST: + return "item exists"; + case LY_ENOTFOUND: + return "item not found"; + case LY_EINT: + return "operation interrupted"; + case LY_EVALID: + return "validation failed"; + case LY_EDENIED: + return "access denied"; + case LY_EINCOMPLETE: + return "incomplete"; + case LY_ERECOMPILE: + return "compile error"; + case LY_ENOT: + return "not"; + case LY_EPLUGIN: + case LY_EOTHER: + return "other"; + default: + return "unknown"; + } +#endif +} + +/* + * Safe to remove after libyang v2.1.128 is required + */ +const char *yang_ly_strvecode(LY_VECODE vecode) +{ +#ifdef HAVE_LY_STRVECODE + return ly_strvecode(vecode); +#else + switch (vecode) { + case LYVE_SUCCESS: + return ""; + case LYVE_SYNTAX: + return "syntax"; + case LYVE_SYNTAX_YANG: + return "yang-syntax"; + case LYVE_SYNTAX_YIN: + return "yin-syntax"; + case LYVE_REFERENCE: + return "reference"; + case LYVE_XPATH: + return "xpath"; + case LYVE_SEMANTICS: + return "semantics"; + case LYVE_SYNTAX_XML: + return "xml-syntax"; + case LYVE_SYNTAX_JSON: + return "json-syntax"; + case LYVE_DATA: + return "data"; + case LYVE_OTHER: + return "other"; + default: + return "unknown"; + } +#endif +} diff --git a/lib/yang.h b/lib/yang.h new file mode 100644 index 0000000..57131f4 --- /dev/null +++ b/lib/yang.h @@ -0,0 +1,852 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_YANG_H_ +#define _FRR_YANG_H_ + +#include "memory.h" + +#include +#ifdef HAVE_SYSREPO +#include +#endif + +#include "yang_wrappers.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maximum XPath length. */ +#define XPATH_MAXLEN 1024 + +/* Maximum list key length. */ +#define LIST_MAXKEYS 8 + +/* Maximum list key length. */ +#define LIST_MAXKEYLEN 128 + +/* Maximum string length of an YANG value. */ +#define YANG_VALUE_MAXLEN 1024 + +struct yang_module_embed { + struct yang_module_embed *next; + const char *mod_name, *mod_rev; + const char *sub_mod_name; + const char *sub_mod_rev; + const char *data; + LYS_INFORMAT format; +}; + +struct yang_module { + RB_ENTRY(yang_module) entry; + const char *name; + const struct lys_module *info; +#ifdef HAVE_SYSREPO + sr_subscription_ctx_t *sr_subscription; + struct event *sr_thread; +#endif +}; +RB_HEAD(yang_modules, yang_module); +RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare); + +struct yang_data { + /* XPath identifier of the data element. */ + char xpath[XPATH_MAXLEN]; + + /* Value encoded as a raw string. */ + char *value; +}; + +struct yang_list_keys { + /* Number os keys (max: LIST_MAXKEYS). */ + uint8_t num; + + /* Value encoded as a raw string. */ + char key[LIST_MAXKEYS][LIST_MAXKEYLEN]; +}; + +enum yang_path_type { + YANG_PATH_SCHEMA = 0, + YANG_PATH_DATA, +}; + +enum yang_iter_flags { + /* Filter non-presence containers. */ + YANG_ITER_FILTER_NPCONTAINERS = (1<<0), + + /* Filter list keys (leafs). */ + YANG_ITER_FILTER_LIST_KEYS = (1<<1), + + /* Filter RPC input/output nodes. */ + YANG_ITER_FILTER_INPUT_OUTPUT = (1<<2), +}; + +/* Callback used by the yang_snodes_iterate_*() family of functions. */ +typedef int (*yang_iterate_cb)(const struct lysc_node *snode, void *arg); + +/* Callback used by the yang_dnode_iterate() function. */ +typedef int (*yang_dnode_iter_cb)(const struct lyd_node *dnode, void *arg); + +/* Return values of the 'yang_iterate_cb' callback. */ +#define YANG_ITER_CONTINUE 0 +#define YANG_ITER_STOP -1 + +/* Global libyang context for native FRR models. */ +extern struct ly_ctx *ly_native_ctx; + +/* Tree of all loaded YANG modules. */ +extern struct yang_modules yang_modules; + +/* + * Create a new YANG module and load it using libyang. If the YANG module is not + * found in the YANG_MODELS_PATH directory, the program will exit with an error. + * Once loaded, a YANG module can't be unloaded anymore. + * + * module_name + * Name of the YANG module. + * + * features + * NULL-terminated array of feature names to enable. + * If NULL, all features are disabled. + * To enable all features, use ["*", NULL]. + * + * Returns: + * Pointer to newly created YANG module. + */ +extern struct yang_module *yang_module_load(const char *module_name, + const char **features); + +/* + * Load all FRR native YANG models. + */ +extern void yang_module_load_all(void); + +/* + * Find a YANG module by its name. + * + * module_name + * Name of the YANG module. + * + * Returns: + * Pointer to YANG module if found, NULL otherwise. + */ +extern struct yang_module *yang_module_find(const char *module_name); + +/* + * Register a YANG module embedded in the binary file. Should be called + * from a constructor function. + * + * embed + * YANG module embedding structure to register. (static global provided + * by caller.) + */ +extern void yang_module_embed(struct yang_module_embed *embed); + +/* + * Iterate recursively over all children of a schema node. + * + * snode + * YANG schema node to operate on. + * + * module + * When set, iterate over all nodes of the specified module only. + * + * cb + * Function to call with each schema node. + * + * flags + * YANG_ITER_* flags to control how the iteration is performed. + * + * arg + * Arbitrary argument passed as the second parameter in each call to 'cb'. + * + * Returns: + * The return value of the last called callback. + */ +extern int yang_snodes_iterate_subtree(const struct lysc_node *snode, + const struct lys_module *module, + yang_iterate_cb cb, uint16_t flags, + void *arg); + +/* + * Iterate over all libyang schema nodes from all loaded modules of the + * given YANG module. + * + * module + * When set, iterate over all nodes of the specified module only. + * + * cb + * Function to call with each schema node. + * + * flags + * YANG_ITER_* flags to control how the iteration is performed. + * + * arg + * Arbitrary argument passed as the second parameter in each call to 'cb'. + * + * Returns: + * The return value of the last called callback. + */ +extern int yang_snodes_iterate(const struct lys_module *module, + yang_iterate_cb cb, uint16_t flags, void *arg); + +/* + * Build schema path or data path of the schema node. + * + * snode + * libyang schema node to be processed. + * + * type + * Specify whether a schema path or a data path should be built. + * + * xpath + * Pointer to previously allocated buffer. + * + * xpath_len + * Size of the xpath buffer. + */ +extern void yang_snode_get_path(const struct lysc_node *snode, + enum yang_path_type type, char *xpath, + size_t xpath_len); + + +/* + * Find libyang schema node for the given xpath. Uses `lys_find_xpath`, + * returning only the first of a set of nodes -- normally there should only + * be one. + * + * ly_ctx + * libyang context to operate on. + * + * xpath + * XPath expression (absolute or relative) to find the schema node for. + * + * options + * Libyang findxpathoptions value (see lys_find_xpath). + * + * Returns: + * The libyang schema node if found, or NULL if not found. + */ +extern struct lysc_node *yang_find_snode(struct ly_ctx *ly_ctx, + const char *xpath, uint32_t options); + +/* + * Find first parent schema node which is a presence-container or a list + * (non-presence containers are ignored). + * + * snode + * libyang schema node to operate on. + * + * Returns: + * The parent libyang schema node if found, or NULL if not found. + */ +extern struct lysc_node *yang_snode_real_parent(const struct lysc_node *snode); + +/* + * Find first parent schema node which is a list. + * + * snode + * libyang schema node to operate on. + * + * Returns: + * The parent libyang schema node (list) if found, or NULL if not found. + */ +extern struct lysc_node *yang_snode_parent_list(const struct lysc_node *snode); + +/* + * Check if the libyang schema node represents typeless data (e.g. containers, + * leafs of type empty, etc). + * + * snode + * libyang schema node to operate on. + * + * Returns: + * true if the schema node represents typeless data, false otherwise. + */ +extern bool yang_snode_is_typeless_data(const struct lysc_node *snode); + +/* + * Get the default value associated to a YANG leaf or leaf-list. + * + * snode + * libyang schema node to operate on. + * + * Returns: + * The default value if it exists, NULL otherwise. + */ +extern const char *yang_snode_get_default(const struct lysc_node *snode); + +/* + * Get the type structure of a leaf of leaf-list. If the type is a leafref, the + * final (if there is a chain of leafrefs) target's type is found. + * + * snode + * libyang schema node to operate on. + * + * Returns: + * The found type if the schema node represents a leaf or a leaf-list, NULL + * otherwise. + */ +extern const struct lysc_type * +yang_snode_get_type(const struct lysc_node *snode); + +/* + * Get the number of key nodes for the given list. + * + * snode + * libyang (LYS_LIST) schema node to operate on. + * + * Returns: + * The number of key LYS_LEAFs as children of this list node. + */ +extern unsigned int yang_snode_num_keys(const struct lysc_node *snode); + +#define LY_FOR_KEYS(snode, skey) \ + for ((skey) = (const struct lysc_node_leaf *)lysc_node_child((snode)); \ + (skey); (skey) = (const struct lysc_node_leaf *)((skey)->next)) \ + if (!lysc_is_key(skey)) { \ + break; \ + } else + + +/* + * Build data path of the data node. + * + * dnode + * libyang data node to be processed. + * + * xpath + * Pointer to previously allocated buffer or NULL. + * + * xpath_len + * Size of the xpath buffer if xpath non-NULL. + * + * If xpath is NULL, the returned string (if non-NULL) needs to be free()d by + * the caller. + */ +extern char *yang_dnode_get_path(const struct lyd_node *dnode, char *xpath, + size_t xpath_len); + +/* + * Find a libyang data node by its YANG data path. + * + * dnode + * Base libyang data node to operate on. + * + * xpath + * Limited XPath (absolute or relative) string. See Path in libyang + * documentation for restrictions. + * + * Returns: + * The libyang data node if found, or NULL if not found. + */ +extern struct lyd_node *yang_dnode_get(const struct lyd_node *dnode, + const char *xpath); + +/* + * Find a libyang data node by its YANG data path. + * + * dnode + * Base libyang data node to operate on. + * + * xpath_fmt + * Limited XPath (absolute or relative) format string. See Path in libyang + * documentation for restrictions. + * + * ... + * any parameters for xpath_fmt. + * + * Returns: + * The libyang data node if found, or NULL if not found. + */ +extern struct lyd_node *yang_dnode_getf(const struct lyd_node *dnode, + const char *path_fmt, ...) + PRINTFRR(2, 3); + +/* + * Check if a libyang data node exists. + * + * dnode + * Base libyang data node to operate on. + * + * xpath + * Limited XPath (absolute or relative) string. See Path in libyang + * documentation for restrictions. + * + * Returns: + * true if a libyang data node was found, false otherwise. + */ +extern bool yang_dnode_exists(const struct lyd_node *dnode, const char *xpath); + +/* + * Check if a libyang data node exists. + * + * dnode + * Base libyang data node to operate on. + * + * xpath_fmt + * Limited XPath (absolute or relative) format string. See Path in + * libyang documentation for restrictions. + * + * ... + * any parameters for xpath_fmt. + * + * Returns: + * true if a libyang data node was found, false otherwise. + */ +extern bool yang_dnode_existsf(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); + +/* + * Iterate over all libyang data nodes that satisfy an XPath query. + * + * cb + * Function to call with each data node. + * + * arg + * Arbitrary argument passed as the second parameter in each call to 'cb'. + * + * dnode + * Base libyang data node to operate on. + * + * xpath_fmt + * XPath expression (absolute or relative). + * + * ... + * any parameters for xpath_fmt. + */ +void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg, + const struct lyd_node *dnode, const char *xpath_fmt, + ...) PRINTFRR(4, 5); + +/* + * Count the number of data nodes that satisfy an XPath query. + * + * dnode + * Base libyang data node to operate on. + * + * xpath_fmt + * XPath expression (absolute or relative). + * + * ... + * any parameters for xpath_fmt. + */ +uint32_t yang_dnode_count(const struct lyd_node *dnode, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* + * Check if the libyang data node contains a default value. Non-presence + * containers are assumed to always contain a default value. + * + * dnode + * Base libyang data node to operate on. + * + * xpath + * Optional XPath expression (absolute or relative) to specify a different + * data node to operate on in the same data tree. + * + * Returns: + * true if the data node contains the default value, false otherwise. + */ +extern bool yang_dnode_is_default(const struct lyd_node *dnode, + const char *xpath); + +/* + * Check if the libyang data node contains a default value. Non-presence + * containers are assumed to always contain a default value. + * + * dnode + * Base libyang data node to operate on. + * + * xpath + * Optional limited XPath (absolute or relative) format string. See Path in + * libyang documentation for restrictions. + * + * ... + * any parameters for xpath_fmt. + * + * Returns: + * true if the data node contains the default value, false otherwise. + */ +extern bool yang_dnode_is_defaultf(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); + +/* + * Check if the libyang data node and all of its children contain default + * values. Non-presence containers are assumed to always contain a default + * value. + * + * dnode + * libyang data node to operate on. + * + * Returns: + * true if the data node and all of its children contain default values, + * false otherwise. + */ +extern bool yang_dnode_is_default_recursive(const struct lyd_node *dnode); + +/* + * Change the value of a libyang leaf node. + * + * dnode + * libyang data node to operate on. + * + * value + * String representing the new value. + */ +extern void yang_dnode_change_leaf(struct lyd_node *dnode, const char *value); + +/* + * Create a new libyang data node. + * + * ly_ctx + * libyang context to operate on. + * + * config + * Specify whether the data node will contain only configuration data (true) + * or both configuration data and state data (false). + * + * Returns: + * Pointer to newly created libyang data node. + */ +extern struct lyd_node *yang_dnode_new(struct ly_ctx *ly_ctx, bool config_only); + +/* + * Duplicate a libyang data node. + * + * dnode + * libyang data node to duplicate. + * + * Returns: + * Pointer to duplicated libyang data node. + */ +extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode); + +/* + * Delete a libyang data node. + * + * dnode + * Pointer to the libyang data node that is going to be deleted along with + * the entire tree it belongs to. + */ +extern void yang_dnode_free(struct lyd_node *dnode); + +/* + * Add a libyang data node to an RPC/action output container. + * + * output + * RPC/action output container. + * + * xpath + * XPath of the data node to add, relative to the output container. + * + * value + * String representing the value of the data node. + */ +extern void yang_dnode_rpc_output_add(struct lyd_node *output, + const char *xpath, const char *value); + +/* + * Create a new yang_data structure. + * + * xpath + * Data path of the YANG data. + * + * value + * String representing the value of the YANG data. + * + * Returns: + * Pointer to newly created yang_data structure. + */ +extern struct yang_data *yang_data_new(const char *xpath, const char *value); + +/* + * Delete a yang_data structure. + * + * data + * yang_data to delete. + */ +extern void yang_data_free(struct yang_data *data); + +/* + * Create a new linked list of yang_data structures. The list 'del' callback is + * initialized appropriately so that the entire list can be deleted safely with + * list_delete_and_null(). + * + * Returns: + * Pointer to newly created linked list. + */ +extern struct list *yang_data_list_new(void); + +/* + * Find the yang_data structure corresponding to an XPath in a list. + * + * list + * list of yang_data structures to operate on. + * + * xpath_fmt + * XPath to search for (format string). + * + * Returns: + * Pointer to yang_data if found, NULL otherwise. + */ +extern struct yang_data *yang_data_list_find(const struct list *list, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); + +/* + * Create and set up a libyang context (for use by the translator) + * + * embedded_modules + * Specify whether libyang should attempt to look for embedded YANG modules. + * + * explicit_compile + * True if the caller will later call ly_ctx_compile to compile all loaded + * modules at once. + */ +extern struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, + bool explicit_compile); + +/* + * Enable or disable libyang verbose debugging. + * + * enable + * When set to true, enable libyang verbose debugging, otherwise disable it. + */ +extern void yang_debugging_set(bool enable); + +/* + * Parse a YANG notification. + * + * Args: + * xpath: xpath of notification. + * format: LYD_FORMAT of input data. + * data: input data. + * notif: pointer to the libyang data tree to store the parsed notification. + * If the notification is not on the top level of the yang model, + * the pointer to the notification node is still returned, but it's + * part of the full data tree with all its parents. + */ +extern LY_ERR yang_parse_notification(const char *xpath, LYD_FORMAT format, + const char *data, struct lyd_node **notif); + +/* + * Parse a YANG RPC. + * + * Args: + * xpath: xpath of an RPC/action. + * format: LYD_FORMAT of input data. + * data: input data. + * reply: true if the data represents a reply to an RPC/action. + * rpc: pointer to the libyang data tree to store the parsed RPC/action. + * If data represents an action, the pointer to the action node is + * still returned, but it's part of the full data tree with all its + * parents. + * + * Returns: + * LY_ERR from underlying calls. + */ +LY_ERR yang_parse_rpc(const char *xpath, LYD_FORMAT format, const char *data, + bool reply, struct lyd_node **rpc); + +/* + * "Print" the yang tree in `root` into dynamic sized array. + * + * Args: + * root: root of the subtree to "print" along with siblings. + * format: LYD_FORMAT of output (see lyd_print_mem) + * options: printing options (see lyd_print_mem) + * + * Return: + * A darr dynamic array with the "printed" output or NULL on failure. + */ +extern uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, + uint32_t options); + + +/** + * yang_convert_lyd_format() - convert one libyang format to darr string. + * @data: data to convert. + * @data_len: length of the data. + * @in_format: format of the data. + * @out_format: format to return. + * @shrink: true to avoid pretty printing. + * + * Return: + * A darr based string or NULL for error. + */ +extern char *yang_convert_lyd_format(const char *data, size_t msg_len, + LYD_FORMAT in_format, + LYD_FORMAT out_format, bool shrink); + +/* + * "Print" the yang tree in `root` into an existing dynamic sized array. + * + * This function does not initialize or free the dynamic array, the array can + * already existing data, the tree will be appended to this data. + * + * Args: + * darr: existing `uint8_t *`, dynamic array. + * root: root of the subtree to "print" along with siblings. + * format: LYD_FORMAT of output (see lyd_print_mem) + * options: printing options (see lyd_print_mem) + * + * Return: + * LY_ERR from underlying calls. + */ +extern LY_ERR yang_print_tree_append(uint8_t **darr, const struct lyd_node *root, + LYD_FORMAT format, uint32_t options); + +/* + * Print libyang error messages into the provided buffer. + * + * ly_ctx + * libyang context to operate on. + * + * buf + * Buffer to store the libyang error messages. + * + * buf_len + * Size of buf. + * + * Returns: + * The provided buffer. + */ +extern const char *yang_print_errors(struct ly_ctx *ly_ctx, char *buf, + size_t buf_len); + +/* + * Initialize the YANG subsystem. Should be called only once during the + * daemon initialization process. + * + * embedded_modules + * Specify whether libyang should attempt to look for embedded YANG modules. + * defer_compile + * Hold off on compiling modules until yang_init_loading_complete is called. + */ +extern void yang_init(bool embedded_modules, bool defer_compile); + +/* + * Should be called after yang_init and all yang_module_load()s have been done, + * compiles all modules loaded into the yang context. + */ +extern void yang_init_loading_complete(void); + +/* + * Finish the YANG subsystem gracefully. Should be called only when the daemon + * is exiting. + */ +extern void yang_terminate(void); + +/* + * API to return the parent dnode having a given schema-node name + * Use case: One has to access the parent dnode's private pointer + * for a given child node. + * For that there is a need to find parent dnode first. + * + * dnode The starting node to work on + * + * name The name of container/list schema-node + * + * Returns The dnode matched with the given name + */ +extern const struct lyd_node * +yang_dnode_get_parent(const struct lyd_node *dnode, const char *name); + + +/* + * In some cases there is a need to auto delete the parent nodes + * if the given node is last in the list. + * It tries to delete all the parents in a given tree in a given module. + * The use case is with static routes and route maps + * example : ip route 1.1.1.1/32 ens33 + * ip route 1.1.1.1/32 ens34 + * After this no ip route 1.1.1.1/32 ens34 came, now staticd + * has to find out upto which level it has to delete the dnodes. + * For this case it has to send delete nexthop + * After this no ip route 1.1.1.1/32 ens33 came, now staticd has to + * clear nexthop, path and route nodes. + * The same scheme is required for routemaps also + * dnode The starting node to work on + * + * Returns The final parent node selected for deletion + */ +extern const struct lyd_node * +yang_get_subtree_with_no_sibling(const struct lyd_node *dnode); + +/* To get the relative position of a node in list */ +extern uint32_t yang_get_list_pos(const struct lyd_node *node); + +/* To get the number of elements in a list + * + * dnode : The head of list + * Returns : The number of dnodes present in the list + */ +extern uint32_t yang_get_list_elements_count(const struct lyd_node *node); + +/* API to check if the given node is last node in the list */ +bool yang_is_last_list_dnode(const struct lyd_node *dnode); + +/* API to check if the given node is last node in the data tree level */ +bool yang_is_last_level_dnode(const struct lyd_node *dnode); + +/* Create a YANG predicate string based on the keys */ +extern int yang_get_key_preds(char *s, const struct lysc_node *snode, + struct yang_list_keys *keys, ssize_t space); + +/* Get YANG keys from an existing dnode */ +extern int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys); + +/** + * yang_xpath_pop_node() - remove the last node from xpath string + * @xpath: an xpath string + * + * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop. + */ +extern int yang_xpath_pop_node(char *xpath); + +/** + * yang_resolve_snodes() - Resolve an XPath to matching schema nodes. + * @ly_ctx: libyang context to operate on. + * @xpath: the path or XPath to resolve. + * @snodes: [OUT] pointer for resulting dynamic array (darr) of schema node + * pointers. + * @simple: [OUT] indicates if @xpath was resolvable simply or not. Non-simple + * means that the @xpath is not a simple path and utilizes XPath 1.0 + * functionality beyond simple key predicates. + * + * This function can be used to find the schema node (or nodes) that correspond + * to a given @xpath. If the @xpath includes non-key predicates (e.g., using + * functions) then @simple will be set to false, and @snodes may contain more + * than a single schema node. + * + * Return: a libyang error or LY_SUCCESS. + */ +extern LY_ERR yang_resolve_snode_xpath(struct ly_ctx *ly_ctx, const char *xpath, + struct lysc_node ***snodes, bool *simple); + +/* + * Libyang future functions + */ +extern const char *yang_ly_strerrcode(LY_ERR err); +extern const char *yang_ly_strvecode(LY_VECODE vecode); +extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent, + const struct lysc_node *snode, + const struct yang_list_keys *keys, + struct lyd_node **nodes); +extern LY_ERR yang_lyd_trim_xpath(struct lyd_node **rootp, const char *xpath); +extern LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx, + struct lyd_node *parent, struct ly_in *in, + LYD_FORMAT format, uint32_t parse_options, + uint32_t validate_options, + struct lyd_node **tree); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_YANG_H_ */ diff --git a/lib/yang_translator.c b/lib/yang_translator.c new file mode 100644 index 0000000..005f642 --- /dev/null +++ b/lib/yang_translator.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "log.h" +#include "lib_errors.h" +#include "hash.h" +#include "yang.h" +#include "yang_translator.h" +#include "frrstr.h" + +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator"); +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module"); +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping"); + +/* Generate the yang_translators tree. */ +static inline int yang_translator_compare(const struct yang_translator *a, + const struct yang_translator *b) +{ + return strcmp(a->family, b->family); +} +RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare) + +struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators); + +/* Separate libyang context for the translator module. */ +static struct ly_ctx *ly_translator_ctx; + +static unsigned int +yang_translator_validate(struct yang_translator *translator); +static unsigned int yang_module_nodes_count(const struct lys_module *module); + +struct yang_mapping_node { + char xpath_from_canonical[XPATH_MAXLEN]; + char xpath_from_fmt[XPATH_MAXLEN]; + char xpath_to_fmt[XPATH_MAXLEN]; +}; + +static bool yang_mapping_hash_cmp(const void *value1, const void *value2) +{ + const struct yang_mapping_node *c1 = value1; + const struct yang_mapping_node *c2 = value2; + + return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical); +} + +static unsigned int yang_mapping_hash_key(const void *value) +{ + return string_hash_make(value); +} + +static void *yang_mapping_hash_alloc(void *p) +{ + struct yang_mapping_node *new, *key = p; + + new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new)); + strlcpy(new->xpath_from_canonical, key->xpath_from_canonical, + sizeof(new->xpath_from_canonical)); + + return new; +} + +static void yang_mapping_hash_free(void *arg) +{ + XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg); +} + +static struct yang_mapping_node * +yang_mapping_lookup(const struct yang_translator *translator, int dir, + const char *xpath) +{ + struct yang_mapping_node s; + + strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical)); + return hash_lookup(translator->mappings[dir], &s); +} + +static void yang_mapping_add(struct yang_translator *translator, int dir, + const struct lysc_node *snode, + const char *xpath_from_fmt, + const char *xpath_to_fmt) +{ + struct yang_mapping_node *mapping, s; + + yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical, + sizeof(s.xpath_from_canonical)); + mapping = hash_get(translator->mappings[dir], &s, + yang_mapping_hash_alloc); + strlcpy(mapping->xpath_from_fmt, xpath_from_fmt, + sizeof(mapping->xpath_from_fmt)); + strlcpy(mapping->xpath_to_fmt, xpath_to_fmt, + sizeof(mapping->xpath_to_fmt)); + + const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"}; + char *xpfmt; + + for (unsigned int i = 0; i < array_size(keys); i++) { + xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i], + "%[^']"); + strlcpy(mapping->xpath_from_fmt, xpfmt, + sizeof(mapping->xpath_from_fmt)); + XFREE(MTYPE_TMP, xpfmt); + } + + for (unsigned int i = 0; i < array_size(keys); i++) { + xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s"); + strlcpy(mapping->xpath_to_fmt, xpfmt, + sizeof(mapping->xpath_to_fmt)); + XFREE(MTYPE_TMP, xpfmt); + } +} + +static void yang_tmodule_delete(struct yang_tmodule *tmodule) +{ + XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule); +} + +struct yang_translator *yang_translator_load(const char *path) +{ + struct yang_translator *translator; + struct yang_tmodule *tmodule = NULL; + const char *family; + struct lyd_node *dnode; + struct ly_set *set; + struct listnode *ln; + LY_ERR err; + + /* Load module translator (JSON file). */ + err = lyd_parse_data_path(ly_translator_ctx, path, LYD_JSON, + LYD_PARSE_NO_STATE, LYD_VALIDATE_NO_STATE, + &dnode); + if (err) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: lyd_parse_path() failed: %d", __func__, err); + return NULL; + } + dnode = yang_dnode_get(dnode, + "/frr-module-translator:frr-module-translator"); + /* + * libyang guarantees the "frr-module-translator" top-level container is + * always present since it contains mandatory child nodes. + */ + assert(dnode); + + family = yang_dnode_get_string(dnode, "family"); + translator = yang_translator_find(family); + if (translator != NULL) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: module translator \"%s\" is loaded already", + __func__, family); + yang_dnode_free(dnode); + return NULL; + } + + translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator)); + strlcpy(translator->family, family, sizeof(translator->family)); + translator->modules = list_new(); + for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) + translator->mappings[i] = hash_create(yang_mapping_hash_key, + yang_mapping_hash_cmp, + "YANG translation table"); + RB_INSERT(yang_translators, &yang_translators, translator); + + /* Initialize the translator libyang context. */ + translator->ly_ctx = yang_ctx_new_setup(false, false); + if (!translator->ly_ctx) { + flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + goto error; + } + + /* Load modules */ + if (lyd_find_xpath(dnode, "./module", &set) != LY_SUCCESS) + assert(0); /* XXX libyang2: old ly1 code asserted success */ + + for (size_t i = 0; i < set->count; i++) { + const char *module_name; + + tmodule = + XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule)); + + module_name = yang_dnode_get_string(set->dnodes[i], "name"); + tmodule->module = ly_ctx_load_module(translator->ly_ctx, + module_name, NULL, NULL); + if (!tmodule->module) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set, NULL); + goto error; + } + } + + /* Count nodes in modules. */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + tmodule->nodes_before_deviations = + yang_module_nodes_count(tmodule->module); + } + + /* Load the deviations and count nodes again */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + const char *module_name = tmodule->module->name; + tmodule->deviations = ly_ctx_load_module( + translator->ly_ctx, module_name, NULL, NULL); + if (!tmodule->deviations) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set, NULL); + goto error; + } + + tmodule->nodes_after_deviations = + yang_module_nodes_count(tmodule->module); + } + ly_set_free(set, NULL); + + /* Calculate the coverage. */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + tmodule->coverage = ((double)tmodule->nodes_after_deviations + / (double)tmodule->nodes_before_deviations) + * 100; + } + + /* Load mappings. */ + if (lyd_find_xpath(dnode, "./module/mappings", &set) != LY_SUCCESS) + assert(0); /* XXX libyang2: old ly1 code asserted success */ + for (size_t i = 0; i < set->count; i++) { + const char *xpath_custom, *xpath_native; + const struct lysc_node *snode_custom, *snode_native; + + xpath_custom = + yang_dnode_get_string(set->dnodes[i], "custom"); + + snode_custom = + yang_find_snode(translator->ly_ctx, xpath_custom, 0); + if (!snode_custom) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: unknown data path: %s", __func__, + xpath_custom); + ly_set_free(set, NULL); + goto error; + } + + xpath_native = + yang_dnode_get_string(set->dnodes[i], "native"); + snode_native = yang_find_snode(ly_native_ctx, xpath_native, 0); + if (!snode_native) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: unknown data path: %s", __func__, + xpath_native); + ly_set_free(set, NULL); + goto error; + } + + yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE, + snode_custom, xpath_custom, xpath_native); + yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE, + snode_native, xpath_native, xpath_custom); + } + ly_set_free(set, NULL); + + /* Validate mappings. */ + if (yang_translator_validate(translator) != 0) + goto error; + + yang_dnode_free(dnode); + + return translator; + +error: + yang_dnode_free(dnode); + yang_translator_unload(translator); + yang_tmodule_delete(tmodule); + + return NULL; +} + +void yang_translator_unload(struct yang_translator *translator) +{ + for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) + hash_clean(translator->mappings[i], yang_mapping_hash_free); + translator->modules->del = (void (*)(void *))yang_tmodule_delete; + list_delete(&translator->modules); + ly_ctx_destroy(translator->ly_ctx); + RB_REMOVE(yang_translators, &yang_translators, translator); + XFREE(MTYPE_YANG_TRANSLATOR, translator); +} + +struct yang_translator *yang_translator_find(const char *family) +{ + struct yang_translator s; + + strlcpy(s.family, family, sizeof(s.family)); + return RB_FIND(yang_translators, &yang_translators, &s); +} + +enum yang_translate_result +yang_translate_xpath(const struct yang_translator *translator, int dir, + char *xpath, size_t xpath_len) +{ + struct ly_ctx *ly_ctx; + const struct lysc_node *snode; + struct yang_mapping_node *mapping; + char xpath_canonical[XPATH_MAXLEN]; + char keys[4][LIST_MAXKEYLEN]; + int n; + + if (dir == YANG_TRANSLATE_TO_NATIVE) + ly_ctx = translator->ly_ctx; + else + ly_ctx = ly_native_ctx; + + snode = yang_find_snode(ly_ctx, xpath, 0); + if (!snode) { + flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, + "%s: unknown data path: %s", __func__, xpath); + return YANG_TRANSLATE_FAILURE; + } + + yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical, + sizeof(xpath_canonical)); + mapping = yang_mapping_lookup(translator, dir, xpath_canonical); + if (!mapping) + return YANG_TRANSLATE_NOTFOUND; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* processing format strings from mapping node... */ + n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2], + keys[3]); +#pragma GCC diagnostic pop + if (n < 0) { + flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, + "%s: sscanf() failed: %s", __func__, + safe_strerror(errno)); + return YANG_TRANSLATE_FAILURE; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* processing format strings from mapping node... */ + snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1], + keys[2], keys[3]); +#pragma GCC diagnostic pop + + return YANG_TRANSLATE_SUCCESS; +} + +int yang_translate_dnode(const struct yang_translator *translator, int dir, + struct lyd_node **dnode) +{ + struct ly_ctx *ly_ctx; + struct lyd_node *new; + struct lyd_node *root, *dnode_iter; + + /* Create new libyang data node to hold the translated data. */ + if (dir == YANG_TRANSLATE_TO_NATIVE) + ly_ctx = ly_native_ctx; + else + ly_ctx = translator->ly_ctx; + new = yang_dnode_new(ly_ctx, false); + + /* Iterate over all nodes from the data tree. */ + LY_LIST_FOR (*dnode, root) { + LYD_TREE_DFS_BEGIN (root, dnode_iter) { + char xpath[XPATH_MAXLEN]; + enum yang_translate_result ret; + + yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath)); + ret = yang_translate_xpath(translator, dir, xpath, + sizeof(xpath)); + switch (ret) { + case YANG_TRANSLATE_SUCCESS: + break; + case YANG_TRANSLATE_NOTFOUND: + goto next; + case YANG_TRANSLATE_FAILURE: + goto error; + } + + /* Create new node in the tree of translated data. */ + if (lyd_new_path(new, ly_ctx, xpath, + (void *)yang_dnode_get_string( + dnode_iter, NULL), + LYD_NEW_PATH_UPDATE, NULL)) { + flog_err(EC_LIB_LIBYANG, + "%s: lyd_new_path() failed", __func__); + goto error; + } + + next: + LYD_TREE_DFS_END(root, dnode_iter); + } + } + + /* Replace dnode by the new translated dnode. */ + yang_dnode_free(*dnode); + *dnode = new; + + return YANG_TRANSLATE_SUCCESS; + +error: + yang_dnode_free(new); + + return YANG_TRANSLATE_FAILURE; +} + +struct translator_validate_args { + struct yang_translator *translator; + unsigned int errors; +}; + +static int yang_translator_validate_cb(const struct lysc_node *snode_custom, + void *arg) +{ + struct translator_validate_args *args = arg; + struct yang_mapping_node *mapping; + const struct lysc_node *snode_native; + const struct lysc_type *stype_custom, *stype_native; + char xpath[XPATH_MAXLEN]; + + yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath)); + mapping = yang_mapping_lookup(args->translator, + YANG_TRANSLATE_TO_NATIVE, xpath); + if (!mapping) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: missing mapping for \"%s\"", __func__, xpath); + args->errors += 1; + return YANG_ITER_CONTINUE; + } + + snode_native = + lys_find_path(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0); + assert(snode_native); + + /* Check if the YANG types are compatible. */ + stype_custom = yang_snode_get_type(snode_custom); + stype_native = yang_snode_get_type(snode_native); + if (stype_custom && stype_native) { + if (stype_custom->basetype != stype_native->basetype) { + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: YANG types are incompatible (xpath: \"%s\")", + __func__, xpath); + args->errors += 1; + return YANG_ITER_CONTINUE; + } + + /* TODO: check if the value spaces are identical. */ + } + + return YANG_ITER_CONTINUE; +} + +/* + * Check if the modules from the translator have a mapping for all of their + * schema nodes (after loading the deviations). + */ +static unsigned int yang_translator_validate(struct yang_translator *translator) +{ + struct yang_tmodule *tmodule; + struct listnode *ln; + struct translator_validate_args args; + + args.translator = translator; + args.errors = 0; + + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + yang_snodes_iterate(tmodule->module, + yang_translator_validate_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + &args); + } + + if (args.errors) + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to validate \"%s\" module translator: %u error(s)", + __func__, translator->family, args.errors); + + return args.errors; +} + +static int yang_module_nodes_count_cb(const struct lysc_node *snode, void *arg) +{ + unsigned int *total = arg; + + *total += 1; + + return YANG_ITER_CONTINUE; +} + +/* Calculate the number of nodes for the given module. */ +static unsigned int yang_module_nodes_count(const struct lys_module *module) +{ + unsigned int total = 0; + + yang_snodes_iterate(module, yang_module_nodes_count_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + &total); + + return total; +} + +void yang_translator_init(void) +{ + ly_translator_ctx = yang_ctx_new_setup(true, false); + if (!ly_translator_ctx) { + flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + exit(1); + } + + if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator", + NULL, NULL)) { + flog_err( + EC_LIB_YANG_MODULE_LOAD, + "%s: failed to load the \"frr-module-translator\" module", + __func__); + exit(1); + } +} + +void yang_translator_terminate(void) +{ + while (!RB_EMPTY(yang_translators, &yang_translators)) { + struct yang_translator *translator; + + translator = RB_ROOT(yang_translators, &yang_translators); + yang_translator_unload(translator); + } + + ly_ctx_destroy(ly_translator_ctx); +} diff --git a/lib/yang_translator.h b/lib/yang_translator.h new file mode 100644 index 0000000..16d55eb --- /dev/null +++ b/lib/yang_translator.h @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_YANG_TRANSLATOR_H_ +#define _FRR_YANG_TRANSLATOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define YANG_TRANSLATE_TO_NATIVE 0 +#define YANG_TRANSLATE_FROM_NATIVE 1 +#define YANG_TRANSLATE_MAX 2 + +struct yang_tmodule { + const struct lys_module *module; + const struct lys_module *deviations; + uint32_t nodes_before_deviations; + uint32_t nodes_after_deviations; + double coverage; +}; + +struct yang_translator { + RB_ENTRY(yang_translator) entry; + char family[32]; + struct ly_ctx *ly_ctx; + struct list *modules; + struct hash *mappings[YANG_TRANSLATE_MAX]; +}; +RB_HEAD(yang_translators, yang_translator); +RB_PROTOTYPE(yang_translators, yang_translator, entry, yang_translator_compare); + +enum yang_translate_result { + YANG_TRANSLATE_SUCCESS, + YANG_TRANSLATE_NOTFOUND, + YANG_TRANSLATE_FAILURE, +}; + +/* Tree of all loaded YANG module translators. */ +extern struct yang_translators yang_translators; + +/* + * Load a YANG module translator from a JSON file. + * + * path + * Absolute path to the module translator file. + * + * Returns: + * Pointer to newly created YANG module translator, or NULL in the case of an + * error. + */ +extern struct yang_translator *yang_translator_load(const char *path); + +/* + * Unload a YANG module translator. + * + * translator + * Pointer to the YANG module translator. + */ +extern void yang_translator_unload(struct yang_translator *translator); + +/* + * Find a YANG module translator by its family name. + * + * family + * Family of the YANG module translator (e.g. ietf, openconfig). + * + * Returns: + * Pointer to the YANG module translator if found, NULL otherwise. + */ +extern struct yang_translator *yang_translator_find(const char *family); + +/* + * Translate an XPath expression. + * + * translator + * Pointer to YANG module translator. + * + * dir + * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or + * YANG_TRANSLATE_FROM_NATIVE). + * + * xpath + * Pointer to previously allocated buffer containing the xpath expression to + * be translated. + * + * xpath_len + * Size of the xpath buffer. + * + * Returns: + * - YANG_TRANSLATE_SUCCESS on success. + * - YANG_TRANSLATE_NOTFOUND when there's no available mapping to perform + * the translation. + * - YANG_TRANSLATE_FAILURE when an error occurred during the translation. + */ +extern enum yang_translate_result +yang_translate_xpath(const struct yang_translator *translator, int dir, + char *xpath, size_t xpath_len); + +/* + * Translate an entire libyang data node. + * + * translator + * Pointer to YANG module translator. + * + * dir + * Direction of the translation (either YANG_TRANSLATE_TO_NATIVE or + * YANG_TRANSLATE_FROM_NATIVE). + * + * dnode + * libyang schema node we want to translate. + * + * Returns: + * - YANG_TRANSLATE_SUCCESS on success. + * - YANG_TRANSLATE_FAILURE when an error occurred during the translation. + */ +extern int yang_translate_dnode(const struct yang_translator *translator, + int dir, struct lyd_node **dnode); + +/* + * Initialize the YANG module translator subsystem. Should be called only once + * during the daemon initialization process. + */ +extern void yang_translator_init(void); + +/* + * Finish the YANG module translator subsystem gracefully. Should be called only + * when the daemon is exiting. + */ +extern void yang_translator_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_YANG_TRANSLATOR_H_ */ diff --git a/lib/yang_wrappers.c b/lib/yang_wrappers.c new file mode 100644 index 0000000..4e49a12 --- /dev/null +++ b/lib/yang_wrappers.c @@ -0,0 +1,1181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "base64.h" +#include "log.h" +#include "lib_errors.h" +#include "northbound.h" +#include "printfrr.h" +#include "nexthop.h" +#include "printfrr.h" + + +#define YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt) \ + ({ \ + va_list __ap; \ + va_start(__ap, (xpath_fmt)); \ + const struct lyd_value *__dvalue = \ + yang_dnode_xpath_get_value(dnode, xpath_fmt, __ap); \ + va_end(__ap); \ + __dvalue; \ + }) + +#define YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt) \ + ({ \ + va_list __ap; \ + va_start(__ap, (xpath_fmt)); \ + const char *__canon = \ + yang_dnode_xpath_get_canon(dnode, xpath_fmt, __ap); \ + va_end(__ap); \ + __canon; \ + }) + +#define YANG_DNODE_GET_ASSERT(dnode, xpath) \ + do { \ + if ((dnode) == NULL) { \ + flog_err(EC_LIB_YANG_DNODE_NOT_FOUND, \ + "%s: couldn't find %s", __func__, (xpath)); \ + zlog_backtrace(LOG_ERR); \ + abort(); \ + } \ + } while (0) + +PRINTFRR(2, 0) +static inline const char * +yang_dnode_xpath_get_canon(const struct lyd_node *dnode, const char *xpath_fmt, + va_list ap) +{ + const struct lyd_node_term *__dleaf = + (const struct lyd_node_term *)dnode; + assert(__dleaf); + if (xpath_fmt) { + char __xpath[XPATH_MAXLEN]; + vsnprintf(__xpath, sizeof(__xpath), xpath_fmt, ap); + __dleaf = (const struct lyd_node_term *)yang_dnode_get(dnode, + __xpath); + YANG_DNODE_GET_ASSERT(__dleaf, __xpath); + } + return lyd_get_value(&__dleaf->node); +} + +PRINTFRR(2, 0) +static inline const struct lyd_value * +yang_dnode_xpath_get_value(const struct lyd_node *dnode, const char *xpath_fmt, + va_list ap) +{ + const struct lyd_node_term *__dleaf = + (const struct lyd_node_term *)dnode; + assert(__dleaf); + if (xpath_fmt) { + char __xpath[XPATH_MAXLEN]; + vsnprintf(__xpath, sizeof(__xpath), xpath_fmt, ap); + __dleaf = (const struct lyd_node_term *)yang_dnode_get(dnode, + __xpath); + YANG_DNODE_GET_ASSERT(__dleaf, __xpath); + } + const struct lyd_value *__dvalue = &__dleaf->value; + if (__dvalue->realtype->basetype == LY_TYPE_UNION) + __dvalue = &__dvalue->subvalue->value; + return __dvalue; +} + +static const char *yang_get_default_value(const char *xpath) +{ + const struct lysc_node *snode; + const char *value; + + snode = yang_find_snode(ly_native_ctx, xpath, 0); + if (snode == NULL) { + flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + zlog_backtrace(LOG_ERR); + abort(); + } + + value = yang_snode_get_default(snode); + assert(value); + + return value; +} + +/* + * Primitive type: bool. + */ +bool yang_str2bool(const char *value) +{ + return strmatch(value, "true"); +} + +struct yang_data *yang_data_new_bool(const char *xpath, bool value) +{ + return yang_data_new(xpath, (value) ? "true" : "false"); +} + +bool yang_dnode_get_bool(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_BOOL); + return dvalue->boolean; +} + +bool yang_get_default_bool(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2bool(value); +} + +/* + * Primitive type: dec64. + */ +double yang_str2dec64(const char *xpath, const char *value) +{ + double dbl = 0; + + if (sscanf(value, "%lf", &dbl) != 1) { + flog_err(EC_LIB_YANG_DATA_CONVERT, + "%s: couldn't convert string to decimal64 [xpath %s]", + __func__, xpath); + zlog_backtrace(LOG_ERR); + abort(); + } + + return dbl; +} + +struct yang_data *yang_data_new_dec64(const char *xpath, double value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%lf", value); + return yang_data_new(xpath, value_str); +} + +double yang_dnode_get_dec64(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + const double denom[19] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, + 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, + 1e14, 1e15, 1e16, 1e17, 1e18 }; + const struct lysc_type_dec *dectype; + const struct lyd_value *dvalue; + + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + dectype = (const struct lysc_type_dec *)dvalue->realtype; + assert(dectype->basetype == LY_TYPE_DEC64); + assert(dectype->fraction_digits < sizeof(denom) / sizeof(*denom)); + return (double)dvalue->dec64 / denom[dectype->fraction_digits]; +} + +double yang_get_default_dec64(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2dec64(xpath, value); +} + +/* + * Primitive type: enum. + */ +int yang_str2enum(const char *xpath, const char *value) +{ + const struct lysc_node *snode; + const struct lysc_node_leaf *sleaf; + const struct lysc_type_enum *type; + const struct lysc_type_bitenum_item *enums; + + snode = yang_find_snode(ly_native_ctx, xpath, 0); + if (snode == NULL) { + flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + zlog_backtrace(LOG_ERR); + abort(); + } + + assert(snode->nodetype == LYS_LEAF); + sleaf = (const struct lysc_node_leaf *)snode; + type = (const struct lysc_type_enum *)sleaf->type; + assert(type->basetype == LY_TYPE_ENUM); + enums = type->enums; + unsigned int count = LY_ARRAY_COUNT(enums); + for (unsigned int i = 0; i < count; i++) { + if (strmatch(value, enums[i].name)) { + assert(CHECK_FLAG(enums[i].flags, LYS_SET_VALUE)); + return enums[i].value; + } + } + + flog_err(EC_LIB_YANG_DATA_CONVERT, + "%s: couldn't convert string to enum [xpath %s]", __func__, + xpath); + zlog_backtrace(LOG_ERR); + abort(); +} + +struct yang_data *yang_data_new_enum(const char *xpath, int value) +{ + const struct lysc_node *snode; + const struct lysc_node_leaf *sleaf; + const struct lysc_type_enum *type; + const struct lysc_type_bitenum_item *enums; + + snode = yang_find_snode(ly_native_ctx, xpath, 0); + if (snode == NULL) { + flog_err(EC_LIB_YANG_UNKNOWN_DATA_PATH, + "%s: unknown data path: %s", __func__, xpath); + zlog_backtrace(LOG_ERR); + abort(); + } + + assert(snode->nodetype == LYS_LEAF); + sleaf = (const struct lysc_node_leaf *)snode; + type = (const struct lysc_type_enum *)sleaf->type; + assert(type->basetype == LY_TYPE_ENUM); + enums = type->enums; + unsigned int count = LY_ARRAY_COUNT(enums); + for (unsigned int i = 0; i < count; i++) { + if (CHECK_FLAG(enums[i].flags, LYS_SET_VALUE) + && value == enums[i].value) + return yang_data_new(xpath, enums[i].name); + } + + flog_err(EC_LIB_YANG_DATA_CONVERT, + "%s: couldn't convert enum to string [xpath %s]", __func__, + xpath); + zlog_backtrace(LOG_ERR); + abort(); +} + +int yang_dnode_get_enum(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + const struct lyd_value *dvalue; + + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_ENUM); + assert(dvalue->enum_item->flags & LYS_SET_VALUE); + return dvalue->enum_item->value; +} + +int yang_get_default_enum(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2enum(xpath, value); +} + +/* + * Primitive type: int8. + */ +int8_t yang_str2int8(const char *value) +{ + return strtol(value, NULL, 10); +} + +struct yang_data *yang_data_new_int8(const char *xpath, int8_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%d", value); + return yang_data_new(xpath, value_str); +} + +int8_t yang_dnode_get_int8(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_INT8); + return dvalue->int8; +} + +int8_t yang_get_default_int8(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2int8(value); +} + +/* + * Primitive type: int16. + */ +int16_t yang_str2int16(const char *value) +{ + return strtol(value, NULL, 10); +} + +struct yang_data *yang_data_new_int16(const char *xpath, int16_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%d", value); + return yang_data_new(xpath, value_str); +} + +int16_t yang_dnode_get_int16(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_INT16); + return dvalue->int16; +} + +int16_t yang_get_default_int16(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2int16(value); +} + +/* + * Primitive type: int32. + */ +int32_t yang_str2int32(const char *value) +{ + return strtol(value, NULL, 10); +} + +struct yang_data *yang_data_new_int32(const char *xpath, int32_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%d", value); + return yang_data_new(xpath, value_str); +} + +int32_t yang_dnode_get_int32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_INT32); + return dvalue->int32; +} + +int32_t yang_get_default_int32(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2int32(value); +} + +/* + * Primitive type: int64. + */ +int64_t yang_str2int64(const char *value) +{ + return strtoll(value, NULL, 10); +} + +struct yang_data *yang_data_new_int64(const char *xpath, int64_t value) +{ + char value_str[BUFSIZ]; + + snprintfrr(value_str, sizeof(value_str), "%" PRId64, value); + return yang_data_new(xpath, value_str); +} + +int64_t yang_dnode_get_int64(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_INT64); + return dvalue->int64; +} + +int64_t yang_get_default_int64(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2int64(value); +} + +/* + * Primitive type: uint8. + */ +uint8_t yang_str2uint8(const char *value) +{ + return strtoul(value, NULL, 10); +} + +struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%u", value); + return yang_data_new(xpath, value_str); +} + +uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_UINT8); + return dvalue->uint8; +} + +uint8_t yang_get_default_uint8(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2uint8(value); +} + +/* + * Primitive type: uint16. + */ +uint16_t yang_str2uint16(const char *value) +{ + return strtoul(value, NULL, 10); +} + +struct yang_data *yang_data_new_uint16(const char *xpath, uint16_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%u", value); + return yang_data_new(xpath, value_str); +} + +uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_UINT16); + return dvalue->uint16; +} + +uint16_t yang_get_default_uint16(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2uint16(value); +} + +/* + * Primitive type: uint32. + */ +uint32_t yang_str2uint32(const char *value) +{ + return strtoul(value, NULL, 10); +} + +struct yang_data *yang_data_new_uint32(const char *xpath, uint32_t value) +{ + char value_str[BUFSIZ]; + + snprintf(value_str, sizeof(value_str), "%u", value); + return yang_data_new(xpath, value_str); +} + +uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_UINT32); + return dvalue->uint32; +} + +uint32_t yang_get_default_uint32(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2uint32(value); +} + +/* + * Primitive type: uint64. + */ +uint64_t yang_str2uint64(const char *value) +{ + return strtoull(value, NULL, 10); +} + +struct yang_data *yang_data_new_uint64(const char *xpath, uint64_t value) +{ + char value_str[BUFSIZ]; + + snprintfrr(value_str, sizeof(value_str), "%" PRIu64, value); + return yang_data_new(xpath, value_str); +} + +uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const struct lyd_value *dvalue; + dvalue = YANG_DNODE_XPATH_GET_VALUE(dnode, xpath_fmt); + assert(dvalue->realtype->basetype == LY_TYPE_UINT64); + return dvalue->uint64; +} + +uint64_t yang_get_default_uint64(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + return yang_str2uint64(value); +} + +/* + * Primitive type: string. + * + * All string wrappers can be used with non-string types. + */ +struct yang_data *yang_data_new_string(const char *xpath, const char *value) +{ + return yang_data_new(xpath, value); +} + +const char *yang_dnode_get_string(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + return YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); +} + +void yang_dnode_get_string_buf(char *buf, size_t size, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + if (strlcpy(buf, canon, size) >= size) { + char xpath[XPATH_MAXLEN]; + + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + flog_warn(EC_LIB_YANG_DATA_TRUNCATED, + "%s: value was truncated [xpath %s]", __func__, + xpath); + } +} + +const char *yang_get_default_string(const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + return yang_get_default_value(xpath); +} + +void yang_get_default_string_buf(char *buf, size_t size, const char *xpath_fmt, + ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + if (strlcpy(buf, value, size) >= size) + flog_warn(EC_LIB_YANG_DATA_TRUNCATED, + "%s: value was truncated [xpath %s]", __func__, + xpath); +} + +/* + * Primitive type: binary. + */ +struct yang_data *yang_data_new_binary(const char *xpath, const char *value, + size_t len) +{ + char *value_str; + struct base64_encodestate s; + int cnt; + char *c; + struct yang_data *data; + + value_str = (char *)malloc(len * 2); + base64_init_encodestate(&s); + cnt = base64_encode_block(value, len, value_str, &s); + c = value_str + cnt; + cnt = base64_encode_blockend(c, &s); + c += cnt; + *c = 0; + data = yang_data_new(xpath, value_str); + free(value_str); + return data; +} + +size_t yang_dnode_get_binary_buf(char *buf, size_t size, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon; + size_t cannon_len; + size_t decode_len; + size_t ret_len; + size_t cnt; + char *value_str; + struct base64_decodestate s; + + canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + cannon_len = strlen(canon); + decode_len = cannon_len + 1; + value_str = (char *)malloc(decode_len); + base64_init_decodestate(&s); + cnt = base64_decode_block(canon, cannon_len, value_str, &s); + + ret_len = size > cnt ? cnt : size; + memcpy(buf, value_str, ret_len); + if (size < cnt) { + char xpath[XPATH_MAXLEN]; + + yang_dnode_get_path(dnode, xpath, sizeof(xpath)); + flog_warn(EC_LIB_YANG_DATA_TRUNCATED, + "%s: value was truncated [xpath %s]", __func__, + xpath); + } + free(value_str); + return ret_len; +} + + +/* + * Primitive type: empty. + */ +struct yang_data *yang_data_new_empty(const char *xpath) +{ + return yang_data_new(xpath, NULL); +} + +bool yang_dnode_get_empty(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + const struct lyd_node_term *dleaf; + + assert(dnode); + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + dnode = yang_dnode_get(dnode, xpath); + if (dnode) { + dleaf = (const struct lyd_node_term *)dnode; + if (dleaf->value.realtype->basetype == LY_TYPE_EMPTY) + return true; + } + + return false; +} + +/* + * Derived type: IP prefix. + */ +void yang_str2prefix(const char *value, union prefixptr prefix) +{ + (void)str2prefix(value, prefix.p); + apply_mask(prefix.p); +} + +struct yang_data *yang_data_new_prefix(const char *xpath, + union prefixconstptr prefix) +{ + char value_str[PREFIX2STR_BUFFER]; + + (void)prefix2str(prefix.p, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_prefix(struct prefix *prefix, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon; + /* + * Initialize prefix to avoid static analyzer complaints about + * uninitialized memory. + */ + memset(prefix, 0, sizeof(*prefix)); + + /* XXX ip_prefix is a native type now in ly2, leverage? */ + canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)str2prefix(canon, prefix); +} + +void yang_get_default_prefix(union prefixptr var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2prefix(value, var); +} + +/* + * Derived type: ipv4. + */ +void yang_str2ipv4(const char *value, struct in_addr *addr) +{ + (void)inet_pton(AF_INET, value, addr); +} + +struct yang_data *yang_data_new_ipv4(const char *xpath, + const struct in_addr *addr) +{ + char value_str[INET_ADDRSTRLEN]; + + (void)inet_ntop(AF_INET, addr, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_ipv4(struct in_addr *addr, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + /* XXX libyang2 IPv4 address is a native type now in ly2 */ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)inet_pton(AF_INET, canon, addr); +} + +void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2ipv4(value, var); +} + +/* + * Derived type: ipv4p. + */ +void yang_str2ipv4p(const char *value, union prefixptr prefix) +{ + struct prefix_ipv4 *prefix4 = prefix.p4; + + (void)str2prefix_ipv4(value, prefix4); + apply_mask_ipv4(prefix4); +} + +struct yang_data *yang_data_new_ipv4p(const char *xpath, + union prefixconstptr prefix) +{ + char value_str[PREFIX2STR_BUFFER]; + + (void)prefix2str(prefix.p, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_ipv4p(union prefixptr prefix, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + struct prefix_ipv4 *prefix4 = prefix.p4; + /* XXX libyang2: ipv4/6 address is a native type now in ly2 */ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)str2prefix_ipv4(canon, prefix4); +} + +void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2ipv4p(value, var); +} + +/* + * Derived type: ipv6. + */ +void yang_str2ipv6(const char *value, struct in6_addr *addr) +{ + (void)inet_pton(AF_INET6, value, addr); +} + +struct yang_data *yang_data_new_ipv6(const char *xpath, + const struct in6_addr *addr) +{ + char value_str[INET6_ADDRSTRLEN]; + + (void)inet_ntop(AF_INET6, addr, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_ipv6(struct in6_addr *addr, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + /* XXX libyang2: IPv6 address is a native type now, leverage. */ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)inet_pton(AF_INET6, canon, addr); +} + +void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2ipv6(value, var); +} + +/* + * Derived type: ipv6p. + */ +void yang_str2ipv6p(const char *value, union prefixptr prefix) +{ + struct prefix_ipv6 *prefix6 = prefix.p6; + + (void)str2prefix_ipv6(value, prefix6); + apply_mask_ipv6(prefix6); +} + +struct yang_data *yang_data_new_ipv6p(const char *xpath, + union prefixconstptr prefix) +{ + char value_str[PREFIX2STR_BUFFER]; + + (void)prefix2str(prefix.p, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_ipv6p(union prefixptr prefix, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + struct prefix_ipv6 *prefix6 = prefix.p6; + + /* XXX IPv6 address is a native type now in ly2 -- can we leverage? */ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)str2prefix_ipv6(canon, prefix6); +} + +void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2ipv6p(value, var); +} + +/* + * Derived type: ip. + */ +void yang_str2ip(const char *value, struct ipaddr *ip) +{ + (void)str2ipaddr(value, ip); +} + +struct yang_data *yang_data_new_ip(const char *xpath, const struct ipaddr *addr) +{ + size_t sz = IS_IPADDR_V4(addr) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; + char value_str[sz]; + + ipaddr2str(addr, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_dnode_get_ip(struct ipaddr *addr, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + /* XXX IPv4 address could be a plugin type now in ly2, leverage? */ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)str2ipaddr(canon, addr); +} + +void yang_get_default_ip(struct ipaddr *var, const char *xpath_fmt, ...) +{ + char xpath[XPATH_MAXLEN]; + const char *value; + va_list ap; + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + value = yang_get_default_value(xpath); + yang_str2ip(value, var); +} + +struct yang_data *yang_data_new_mac(const char *xpath, + const struct ethaddr *mac) +{ + char value_str[ETHER_ADDR_STRLEN]; + + prefix_mac2str(mac, value_str, sizeof(value_str)); + return yang_data_new(xpath, value_str); +} + +void yang_str2mac(const char *value, struct ethaddr *mac) +{ + (void)prefix_str2mac(value, mac); +} + +void yang_dnode_get_mac(struct ethaddr *mac, const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + (void)prefix_str2mac(canon, mac); +} + +struct yang_data *yang_data_new_date_and_time(const char *xpath, time_t time, bool is_monotime) +{ + struct yang_data *yd; + char *times = NULL; + + if (is_monotime) { + struct timeval _time = { time, 0 }; + struct timeval time_real; + + monotime_to_realtime(&_time, &time_real); + time = time_real.tv_sec; + } + + (void)ly_time_time2str(time, NULL, ×); + yd = yang_data_new(xpath, times); + free(times); + + return yd; +} + +struct timespec yang_dnode_get_date_and_timespec(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + struct timespec ts; + LY_ERR err; + + err = ly_time_str2ts(canon, &ts); + assert(!err); + + return ts; +} + +time_t yang_dnode_get_date_and_time(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + time_t time; + LY_ERR err; + + err = ly_time_str2time(canon, &time, NULL); + assert(!err); + + return time; +} + +float yang_dnode_get_bandwidth_ieee_float32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) +{ + const char *canon = YANG_DNODE_XPATH_GET_CANON(dnode, xpath_fmt); + float value; + + assert(sscanf(canon, "%a", &value) == 1); + + return value; +} + +const char *yang_nexthop_type2str(uint32_t ntype) +{ + switch (ntype) { + case NEXTHOP_TYPE_IFINDEX: + return "ifindex"; + break; + case NEXTHOP_TYPE_IPV4: + return "ip4"; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + return "ip4-ifindex"; + break; + case NEXTHOP_TYPE_IPV6: + return "ip6"; + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + return "ip6-ifindex"; + break; + case NEXTHOP_TYPE_BLACKHOLE: + return "blackhole"; + break; + default: + return "unknown"; + break; + } +} + + +const char *yang_afi_safi_value2identity(afi_t afi, safi_t safi) +{ + if (afi == AFI_IP && safi == SAFI_UNICAST) + return "frr-routing:ipv4-unicast"; + if (afi == AFI_IP6 && safi == SAFI_UNICAST) + return "frr-routing:ipv6-unicast"; + if (afi == AFI_IP && safi == SAFI_MULTICAST) + return "frr-routing:ipv4-multicast"; + if (afi == AFI_IP6 && safi == SAFI_MULTICAST) + return "frr-routing:ipv6-multicast"; + if (afi == AFI_IP && safi == SAFI_MPLS_VPN) + return "frr-routing:l3vpn-ipv4-unicast"; + if (afi == AFI_IP6 && safi == SAFI_MPLS_VPN) + return "frr-routing:l3vpn-ipv6-unicast"; + if (afi == AFI_L2VPN && safi == SAFI_EVPN) + return "frr-routing:l2vpn-evpn"; + if (afi == AFI_IP && safi == SAFI_LABELED_UNICAST) + return "frr-routing:ipv4-labeled-unicast"; + if (afi == AFI_IP6 && safi == SAFI_LABELED_UNICAST) + return "frr-routing:ipv6-labeled-unicast"; + if (afi == AFI_IP && safi == SAFI_FLOWSPEC) + return "frr-routing:ipv4-flowspec"; + if (afi == AFI_IP6 && safi == SAFI_FLOWSPEC) + return "frr-routing:ipv6-flowspec"; + + return NULL; +} + +void yang_afi_safi_identity2value(const char *key, afi_t *afi, safi_t *safi) +{ + if (strmatch(key, "frr-routing:ipv4-unicast")) { + *afi = AFI_IP; + *safi = SAFI_UNICAST; + } else if (strmatch(key, "frr-routing:ipv6-unicast")) { + *afi = AFI_IP6; + *safi = SAFI_UNICAST; + } else if (strmatch(key, "frr-routing:ipv4-multicast")) { + *afi = AFI_IP; + *safi = SAFI_MULTICAST; + } else if (strmatch(key, "frr-routing:ipv6-multicast")) { + *afi = AFI_IP6; + *safi = SAFI_MULTICAST; + } else if (strmatch(key, "frr-routing:l3vpn-ipv4-unicast")) { + *afi = AFI_IP; + *safi = SAFI_MPLS_VPN; + } else if (strmatch(key, "frr-routing:l3vpn-ipv6-unicast")) { + *afi = AFI_IP6; + *safi = SAFI_MPLS_VPN; + } else if (strmatch(key, "frr-routing:ipv4-labeled-unicast")) { + *afi = AFI_IP; + *safi = SAFI_LABELED_UNICAST; + } else if (strmatch(key, "frr-routing:ipv6-labeled-unicast")) { + *afi = AFI_IP6; + *safi = SAFI_LABELED_UNICAST; + } else if (strmatch(key, "frr-routing:l2vpn-evpn")) { + *afi = AFI_L2VPN; + *safi = SAFI_EVPN; + } else if (strmatch(key, "frr-routing:ipv4-flowspec")) { + *afi = AFI_IP; + *safi = SAFI_FLOWSPEC; + } else if (strmatch(key, "frr-routing:ipv6-flowspec")) { + *afi = AFI_IP6; + *safi = SAFI_FLOWSPEC; + } else { + *afi = AFI_UNSPEC; + *safi = SAFI_UNSPEC; + } +} diff --git a/lib/yang_wrappers.h b/lib/yang_wrappers.h new file mode 100644 index 0000000..d3d8419 --- /dev/null +++ b/lib/yang_wrappers.h @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_NORTHBOUND_WRAPPERS_H_ +#define _FRR_NORTHBOUND_WRAPPERS_H_ + +#include +#include "prefix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* bool */ +extern bool yang_str2bool(const char *value); +extern struct yang_data *yang_data_new_bool(const char *xpath, bool value); +extern bool yang_dnode_get_bool(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern bool yang_get_default_bool(const char *xpath_fmt, ...) PRINTFRR(1, 2); + +/* dec64 */ +extern double yang_str2dec64(const char *xpath, const char *value); +extern struct yang_data *yang_data_new_dec64(const char *xpath, double value); +extern double yang_dnode_get_dec64(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern double yang_get_default_dec64(const char *xpath_fmt, ...) PRINTFRR(1, 2); + +/* enum */ +extern int yang_str2enum(const char *xpath, const char *value); +extern struct yang_data *yang_data_new_enum(const char *xpath, int value); +extern int yang_dnode_get_enum(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern int yang_get_default_enum(const char *xpath_fmt, ...) PRINTFRR(1, 2); + +/* int8 */ +extern int8_t yang_str2int8(const char *value); +extern struct yang_data *yang_data_new_int8(const char *xpath, int8_t value); +extern int8_t yang_dnode_get_int8(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern int8_t yang_get_default_int8(const char *xpath_fmt, ...) PRINTFRR(1, 2); + +/* int16 */ +extern int16_t yang_str2int16(const char *value); +extern struct yang_data *yang_data_new_int16(const char *xpath, int16_t value); +extern int16_t yang_dnode_get_int16(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern int16_t yang_get_default_int16(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* int32 */ +extern int32_t yang_str2int32(const char *value); +extern struct yang_data *yang_data_new_int32(const char *xpath, int32_t value); +extern int32_t yang_dnode_get_int32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern int32_t yang_get_default_int32(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* int64 */ +extern int64_t yang_str2int64(const char *value); +extern struct yang_data *yang_data_new_int64(const char *xpath, int64_t value); +extern int64_t yang_dnode_get_int64(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern int64_t yang_get_default_int64(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* uint8 */ +extern uint8_t yang_str2uint8(const char *value); +extern struct yang_data *yang_data_new_uint8(const char *xpath, uint8_t value); +extern uint8_t yang_dnode_get_uint8(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); +extern uint8_t yang_get_default_uint8(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* uint16 */ +extern uint16_t yang_str2uint16(const char *value); +extern struct yang_data *yang_data_new_uint16(const char *xpath, + uint16_t value); +extern uint16_t yang_dnode_get_uint16(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); +extern uint16_t yang_get_default_uint16(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* uint32 */ +extern uint32_t yang_str2uint32(const char *value); +extern struct yang_data *yang_data_new_uint32(const char *xpath, + uint32_t value); +extern uint32_t yang_dnode_get_uint32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); +extern uint32_t yang_get_default_uint32(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* uint64 */ +extern uint64_t yang_str2uint64(const char *value); +extern struct yang_data *yang_data_new_uint64(const char *xpath, + uint64_t value); +extern uint64_t yang_dnode_get_uint64(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); +extern uint64_t yang_get_default_uint64(const char *xpath_fmt, ...) + PRINTFRR(1, 2); + +/* string */ +extern struct yang_data *yang_data_new_string(const char *xpath, + const char *value); +extern const char *yang_dnode_get_string(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); +extern void yang_dnode_get_string_buf(char *buf, size_t size, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(4, 5); +extern const char *yang_get_default_string(const char *xpath_fmt, ...) + PRINTFRR(1, 2); +extern void yang_get_default_string_buf(char *buf, size_t size, + const char *xpath_fmt, ...) + PRINTFRR(3, 4); + +/* binary */ +extern struct yang_data *yang_data_new_binary(const char *xpath, + const char *value, size_t len); +extern size_t yang_dnode_get_binary_buf(char *buf, size_t size, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(4, 5); + +/* empty */ +extern struct yang_data *yang_data_new_empty(const char *xpath); +extern bool yang_dnode_get_empty(const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(2, 3); + +/* ip prefix */ +extern void yang_str2prefix(const char *value, union prefixptr prefix); +extern struct yang_data *yang_data_new_prefix(const char *xpath, + union prefixconstptr prefix); +extern void yang_dnode_get_prefix(struct prefix *prefix, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_prefix(union prefixptr var, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* ipv4 */ +extern void yang_str2ipv4(const char *value, struct in_addr *addr); +extern struct yang_data *yang_data_new_ipv4(const char *xpath, + const struct in_addr *addr); +extern void yang_dnode_get_ipv4(struct in_addr *addr, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_ipv4(struct in_addr *var, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* ipv4p */ +extern void yang_str2ipv4p(const char *value, union prefixptr prefix); +extern struct yang_data *yang_data_new_ipv4p(const char *xpath, + union prefixconstptr prefix); +extern void yang_dnode_get_ipv4p(union prefixptr prefix, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_ipv4p(union prefixptr var, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* ipv6 */ +extern void yang_str2ipv6(const char *value, struct in6_addr *addr); +extern struct yang_data *yang_data_new_ipv6(const char *xpath, + const struct in6_addr *addr); +extern void yang_dnode_get_ipv6(struct in6_addr *addr, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_ipv6(struct in6_addr *var, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* ipv6p */ +extern void yang_str2ipv6p(const char *value, union prefixptr prefix); +extern struct yang_data *yang_data_new_ipv6p(const char *xpath, + union prefixconstptr prefix); +extern void yang_dnode_get_ipv6p(union prefixptr prefix, + const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_ipv6p(union prefixptr var, const char *xpath_fmt, + ...) PRINTFRR(2, 3); + +/* ip */ +extern void yang_str2ip(const char *value, struct ipaddr *addr); +extern struct yang_data *yang_data_new_ip(const char *xpath, + const struct ipaddr *addr); +extern void yang_dnode_get_ip(struct ipaddr *addr, const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); +extern void yang_get_default_ip(struct ipaddr *var, const char *xpath_fmt, ...) + PRINTFRR(2, 3); + +/* mac */ +extern struct yang_data *yang_data_new_mac(const char *xpath, + const struct ethaddr *mac); +extern void yang_str2mac(const char *value, struct ethaddr *mac); +extern void yang_dnode_get_mac(struct ethaddr *mac, const struct lyd_node *dnode, + const char *xpath_fmt, ...) PRINTFRR(3, 4); + +/*data-and-time */ +extern struct yang_data *yang_data_new_date_and_time(const char *xpath, + time_t time, + bool is_monotime); +struct timespec yang_dnode_get_date_and_timespec(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); +time_t yang_dnode_get_date_and_time(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); + +/* rt-types:bandwidth-ieee-float32 */ +extern float yang_dnode_get_bandwidth_ieee_float32(const struct lyd_node *dnode, + const char *xpath_fmt, ...) + PRINTFRR(2, 3); + +/* nexthop enum2str */ +extern const char *yang_nexthop_type2str(uint32_t ntype); + +const char *yang_afi_safi_value2identity(afi_t afi, safi_t safi); +void yang_afi_safi_identity2value(const char *key, afi_t *afi, safi_t *safi); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_NORTHBOUND_WRAPPERS_H_ */ diff --git a/lib/zclient.c b/lib/zclient.c new file mode 100644 index 0000000..1aab7b4 --- /dev/null +++ b/lib/zclient.c @@ -0,0 +1,4935 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Zebra's client library. + * Copyright (C) 1999 Kunihiro Ishiguro + * Copyright (C) 2005 Andrew J. Schorr + */ + +#include + +#include "prefix.h" +#include "stream.h" +#include "buffer.h" +#include "network.h" +#include "vrf.h" +#include "vrf_int.h" +#include "if.h" +#include "log.h" +#include "frrevent.h" +#include "zclient.h" +#include "memory.h" +#include "table.h" +#include "nexthop.h" +#include "mpls.h" +#include "sockopt.h" +#include "pbr.h" +#include "tc.h" +#include "nexthop_group.h" +#include "lib_errors.h" +#include "srte.h" +#include "printfrr.h" +#include "srv6.h" + +DEFINE_MTYPE_STATIC(LIB, ZCLIENT, "Zclient"); +DEFINE_MTYPE_STATIC(LIB, REDIST_INST, "Redistribution instance IDs"); + +/* Zebra client events. */ +enum zclient_event { ZCLIENT_SCHEDULE, ZCLIENT_READ, ZCLIENT_CONNECT }; + +/* Prototype for event manager. */ +static void zclient_event(enum zclient_event, struct zclient *); + +static void zebra_interface_if_set_value(struct stream *s, + struct interface *ifp); + +const struct zclient_options zclient_options_default = { + .synchronous = false, + .auxiliary = false, +}; + +const struct zclient_options zclient_options_sync = { + .synchronous = true, + .auxiliary = true, +}; + +const struct zclient_options zclient_options_auxiliary = { + .synchronous = false, + .auxiliary = true, +}; + +struct sockaddr_storage zclient_addr; +socklen_t zclient_addr_len; + +/* This file local debug flag. */ +static int zclient_debug; + +/* Allocate zclient structure. */ +struct zclient *zclient_new(struct event_loop *master, + const struct zclient_options *opt, + zclient_handler *const *handlers, size_t n_handlers) +{ + struct zclient *zclient; + size_t stream_size = + MAX(ZEBRA_MAX_PACKET_SIZ, sizeof(struct zapi_route)); + + zclient = XCALLOC(MTYPE_ZCLIENT, sizeof(struct zclient)); + + zclient->ibuf = stream_new(stream_size); + zclient->obuf = stream_new(stream_size); + zclient->wb = buffer_new(0); + zclient->master = master; + + zclient->handlers = handlers; + zclient->n_handlers = n_handlers; + + zclient->synchronous = opt->synchronous; + zclient->auxiliary = opt->auxiliary; + + return zclient; +} + +/* This function is only called when exiting, because + many parts of the code do not check for I/O errors, so they could + reference an invalid pointer if the structure was ever freed. + + Free zclient structure. */ +void zclient_free(struct zclient *zclient) +{ + if (zclient->ibuf) + stream_free(zclient->ibuf); + if (zclient->obuf) + stream_free(zclient->obuf); + if (zclient->wb) + buffer_free(zclient->wb); + + XFREE(MTYPE_ZCLIENT, zclient); +} + +unsigned short *redist_check_instance(struct redist_proto *red, + unsigned short instance) +{ + struct listnode *node; + unsigned short *id; + + if (!red->instances) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(red->instances, node, id)) + if (*id == instance) + return id; + + return NULL; +} + +void redist_add_instance(struct redist_proto *red, unsigned short instance) +{ + unsigned short *in; + + red->enabled = 1; + + if (!red->instances) + red->instances = list_new(); + + in = XMALLOC(MTYPE_REDIST_INST, sizeof(unsigned short)); + *in = instance; + listnode_add(red->instances, in); +} + +void redist_del_instance(struct redist_proto *red, unsigned short instance) +{ + unsigned short *id; + + id = redist_check_instance(red, instance); + if (!id) + return; + + listnode_delete(red->instances, id); + XFREE(MTYPE_REDIST_INST, id); + if (!red->instances->count) { + red->enabled = 0; + list_delete(&red->instances); + } +} + +void redist_del_all_instances(struct redist_proto *red) +{ + struct listnode *ln, *nn; + unsigned short *id; + + if (!red->instances) + return; + + for (ALL_LIST_ELEMENTS(red->instances, ln, nn, id)) + redist_del_instance(red, *id); +} + +/* Stop zebra client services. */ +void zclient_stop(struct zclient *zclient) +{ + afi_t afi; + int i; + + if (zclient_debug) + zlog_debug("zclient %p stopped", zclient); + + /* Stop threads. */ + EVENT_OFF(zclient->t_read); + EVENT_OFF(zclient->t_connect); + EVENT_OFF(zclient->t_write); + + /* Reset streams. */ + stream_reset(zclient->ibuf); + stream_reset(zclient->obuf); + + /* Empty the write buffer. */ + buffer_reset(zclient->wb); + + /* Close socket. */ + if (zclient->sock >= 0) { + close(zclient->sock); + zclient->sock = -1; + } + zclient->fail = 0; + + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + vrf_bitmap_free(&zclient->redist[afi][i]); + zclient->redist[afi][i] = VRF_BITMAP_NULL; + } + redist_del_instance( + &zclient->mi_redist[afi][zclient->redist_default], + zclient->instance); + + vrf_bitmap_free(&zclient->default_information[afi]); + zclient->default_information[afi] = VRF_BITMAP_NULL; + } +} + +void zclient_reset(struct zclient *zclient) +{ + afi_t afi; + + zclient_stop(zclient); + + for (afi = AFI_IP; afi < AFI_MAX; afi++) + redist_del_instance( + &zclient->mi_redist[afi][zclient->redist_default], + zclient->instance); + + zclient_init(zclient, zclient->redist_default, zclient->instance, + zclient->privs); +} + +/** + * Connect to zebra daemon. + * @param zclient a pointer to zclient structure + * @return socket fd just to make sure that connection established + * @see zclient_init + * @see zclient_new + */ +int zclient_socket_connect(struct zclient *zclient) +{ + int sock; + int ret; + + /* We should think about IPv6 connection. */ + sock = socket(zclient_addr.ss_family, SOCK_STREAM, 0); + if (sock < 0) + return -1; + + set_cloexec(sock); + setsockopt_so_sendbuf(sock, 1048576); + + /* Connect to zebra. */ + ret = connect(sock, (struct sockaddr *)&zclient_addr, zclient_addr_len); + if (ret < 0) { + if (zclient_debug) + zlog_debug("%s connect failure: %d(%s)", __func__, + errno, safe_strerror(errno)); + close(sock); + return -1; + } + + zclient->sock = sock; + return sock; +} + +static enum zclient_send_status zclient_failed(struct zclient *zclient) +{ + zclient->fail++; + zclient_stop(zclient); + zclient_event(ZCLIENT_CONNECT, zclient); + return ZCLIENT_SEND_FAILURE; +} + +static void zclient_flush_data(struct event *thread) +{ + struct zclient *zclient = EVENT_ARG(thread); + + zclient->t_write = NULL; + if (zclient->sock < 0) + return; + switch (buffer_flush_available(zclient->wb, zclient->sock)) { + case BUFFER_ERROR: + flog_err( + EC_LIB_ZAPI_SOCKET, + "%s: buffer_flush_available failed on zclient fd %d, closing", + __func__, zclient->sock); + zclient_failed(zclient); + return; + case BUFFER_PENDING: + zclient->t_write = NULL; + event_add_write(zclient->master, zclient_flush_data, zclient, + zclient->sock, &zclient->t_write); + break; + case BUFFER_EMPTY: + /* Currently only Sharpd and Bgpd has callbacks defined */ + if (zclient->zebra_buffer_write_ready) + (*zclient->zebra_buffer_write_ready)(); + break; + } +} + +/* + * Returns: + * ZCLIENT_SEND_FAILED - is a failure + * ZCLIENT_SEND_SUCCESS - means we sent data to zebra + * ZCLIENT_SEND_BUFFERED - means we are buffering + */ +enum zclient_send_status zclient_send_message(struct zclient *zclient) +{ + if (zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + switch (buffer_write(zclient->wb, zclient->sock, + STREAM_DATA(zclient->obuf), + stream_get_endp(zclient->obuf))) { + case BUFFER_ERROR: + flog_err(EC_LIB_ZAPI_SOCKET, + "%s: buffer_write failed to zclient fd %d, closing", + __func__, zclient->sock); + return zclient_failed(zclient); + case BUFFER_EMPTY: + EVENT_OFF(zclient->t_write); + return ZCLIENT_SEND_SUCCESS; + case BUFFER_PENDING: + event_add_write(zclient->master, zclient_flush_data, zclient, + zclient->sock, &zclient->t_write); + return ZCLIENT_SEND_BUFFERED; + } + + /* should not get here */ + return ZCLIENT_SEND_SUCCESS; +} + +/* + * If we add more data to this structure please ensure that + * struct zmsghdr in lib/zclient.h is updated as appropriate. + */ +void zclient_create_header(struct stream *s, uint16_t command, vrf_id_t vrf_id) +{ + /* length placeholder, caller can update */ + stream_putw(s, ZEBRA_HEADER_SIZE); + stream_putc(s, ZEBRA_HEADER_MARKER); + stream_putc(s, ZSERV_VERSION); + stream_putl(s, vrf_id); + stream_putw(s, command); +} + +int zclient_read_header(struct stream *s, int sock, uint16_t *size, + uint8_t *marker, uint8_t *version, vrf_id_t *vrf_id, + uint16_t *cmd) +{ + if (stream_read(s, sock, ZEBRA_HEADER_SIZE) != ZEBRA_HEADER_SIZE) + return -1; + + STREAM_GETW(s, *size); + *size -= ZEBRA_HEADER_SIZE; + STREAM_GETC(s, *marker); + STREAM_GETC(s, *version); + STREAM_GETL(s, *vrf_id); + STREAM_GETW(s, *cmd); + + if (*version != ZSERV_VERSION || *marker != ZEBRA_HEADER_MARKER) { + flog_err( + EC_LIB_ZAPI_MISSMATCH, + "%s: socket %d version mismatch, marker %d, version %d", + __func__, sock, *marker, *version); + return -1; + } + + if (*size && stream_read(s, sock, *size) != *size) + return -1; + + return 0; +stream_failure: + return -1; +} + +bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr) +{ + STREAM_GETW(zmsg, hdr->length); + STREAM_GETC(zmsg, hdr->marker); + STREAM_GETC(zmsg, hdr->version); + STREAM_GETL(zmsg, hdr->vrf_id); + STREAM_GETW(zmsg, hdr->command); + return true; +stream_failure: + return false; +} + +/* Send simple Zebra message. */ +static enum zclient_send_status zebra_message_send(struct zclient *zclient, + int command, vrf_id_t vrf_id) +{ + struct stream *s; + + /* Get zclient output buffer. */ + s = zclient->obuf; + stream_reset(s); + + /* Send very simple command only Zebra message. */ + zclient_create_header(s, command, vrf_id); + + return zclient_send_message(zclient); +} + +enum zclient_send_status zclient_send_hello(struct zclient *zclient) +{ + struct stream *s; + + if (zclient->redist_default || zclient->synchronous) { + s = zclient->obuf; + stream_reset(s); + + /* The VRF ID in the HELLO message is always 0. */ + zclient_create_header(s, ZEBRA_HELLO, VRF_DEFAULT); + stream_putc(s, zclient->redist_default); + stream_putw(s, zclient->instance); + stream_putl(s, zclient->session_id); + if (zclient->synchronous) + stream_putc(s, 1); + else + stream_putc(s, 0); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); + } + + return ZCLIENT_SEND_SUCCESS; +} + +enum zclient_send_status zclient_send_vrf_label(struct zclient *zclient, + vrf_id_t vrf_id, afi_t afi, + mpls_label_t label, + enum lsp_types_t ltype) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_VRF_LABEL, vrf_id); + stream_putl(s, label); + stream_putc(s, afi); + stream_putc(s, ltype); + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); +} + +enum zclient_send_status zclient_send_localsid(struct zclient *zclient, + const struct in6_addr *sid, vrf_id_t vrf_id, + enum seg6local_action_t action, + const struct seg6local_context *context) +{ + struct prefix_ipv6 p = {}; + struct zapi_route api = {}; + struct zapi_nexthop *znh; + struct interface *ifp; + + ifp = if_get_vrf_loopback(vrf_id); + if (ifp == NULL) + return ZCLIENT_SEND_FAILURE; + + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_BITLEN; + p.prefix = *sid; + + api.vrf_id = VRF_DEFAULT; + api.type = zclient->redist_default; + api.instance = 0; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, &p, sizeof(p)); + + if (action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + return zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + + znh = &api.nexthops[0]; + + memset(znh, 0, sizeof(*znh)); + + znh->type = NEXTHOP_TYPE_IFINDEX; + znh->ifindex = ifp->ifindex; + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL); + znh->seg6local_action = action; + memcpy(&znh->seg6local_ctx, context, sizeof(struct seg6local_context)); + + api.nexthop_num = 1; + + return zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); +} + +/* Send register requests to zebra daemon for the information in a VRF. */ +void zclient_send_reg_requests(struct zclient *zclient, vrf_id_t vrf_id) +{ + int i; + afi_t afi; + + /* If not connected to the zebra yet. */ + if (zclient->sock < 0) + return; + + if (zclient_debug) + zlog_debug("%s: send register messages for VRF %u", __func__, + vrf_id); + + /* We need router-id information. */ + zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_ADD, AFI_IP, + vrf_id); + + /* We need interface information. */ + zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, vrf_id); + + /* Set unwanted redistribute route. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) + vrf_bitmap_set(&zclient->redist[afi][zclient->redist_default], + vrf_id); + + /* Flush all redistribute request. */ + if (vrf_id == VRF_DEFAULT) { + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!zclient->mi_redist[afi][i].enabled) + continue; + + struct listnode *node; + unsigned short *id; + + for (ALL_LIST_ELEMENTS_RO( + zclient->mi_redist[afi][i] + .instances, + node, id)) + if (!(i == zclient->redist_default + && *id == zclient->instance)) + zebra_redistribute_send( + ZEBRA_REDISTRIBUTE_ADD, + zclient, afi, i, *id, + VRF_DEFAULT); + } + } + } + + /* Resend all redistribute request. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) + if (i != zclient->redist_default && + vrf_bitmap_check(&zclient->redist[afi][i], vrf_id)) + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, + zclient, afi, i, 0, + vrf_id); + + /* If default information is needed. */ + if (vrf_bitmap_check(&zclient->default_information[afi], + vrf_id)) + zebra_redistribute_default_send( + ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, afi, + vrf_id); + } +} + +/* Send unregister requests to zebra daemon for the information in a VRF. */ +void zclient_send_dereg_requests(struct zclient *zclient, vrf_id_t vrf_id) +{ + int i; + afi_t afi; + + /* If not connected to the zebra yet. */ + if (zclient->sock < 0) + return; + + if (zclient_debug) + zlog_debug("%s: send deregister messages for VRF %u", __func__, + vrf_id); + + /* We need router-id information. */ + zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_DELETE, AFI_IP, + vrf_id); + + zebra_message_send(zclient, ZEBRA_INTERFACE_DELETE, vrf_id); + + /* Set unwanted redistribute route. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) + vrf_bitmap_unset(&zclient->redist[afi][zclient->redist_default], + vrf_id); + + /* Flush all redistribute request. */ + if (vrf_id == VRF_DEFAULT) { + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!zclient->mi_redist[afi][i].enabled) + continue; + + struct listnode *node; + unsigned short *id; + + for (ALL_LIST_ELEMENTS_RO( + zclient->mi_redist[afi][i] + .instances, + node, id)) + if (!(i == zclient->redist_default + && *id == zclient->instance)) + zebra_redistribute_send( + ZEBRA_REDISTRIBUTE_DELETE, + zclient, afi, i, *id, + VRF_DEFAULT); + } + } + } + + /* Flush all redistribute request. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) + if (i != zclient->redist_default && + vrf_bitmap_check(&zclient->redist[afi][i], vrf_id)) + zebra_redistribute_send( + ZEBRA_REDISTRIBUTE_DELETE, zclient, afi, + i, 0, vrf_id); + + /* If default information is needed. */ + if (vrf_bitmap_check(&zclient->default_information[afi], + vrf_id)) + zebra_redistribute_default_send( + ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, afi, + vrf_id); + } +} + +enum zclient_send_status +zclient_send_router_id_update(struct zclient *zclient, + zebra_message_types_t type, afi_t afi, + vrf_id_t vrf_id) +{ + struct stream *s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, type, vrf_id); + stream_putw(s, afi); + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); +} + +/* Send request to zebra daemon to start or stop RA. */ +enum zclient_send_status +zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id, + struct interface *ifp, int enable, + uint32_t ra_interval) +{ + struct stream *s; + + /* If not connected to the zebra yet. */ + if (zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + /* Form and send message. */ + s = zclient->obuf; + stream_reset(s); + + if (enable) + zclient_create_header(s, ZEBRA_INTERFACE_ENABLE_RADV, vrf_id); + else + zclient_create_header(s, ZEBRA_INTERFACE_DISABLE_RADV, vrf_id); + + stream_putl(s, ifp->ifindex); + stream_putl(s, ra_interval); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +enum zclient_send_status +zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id, + struct interface *ifp, bool down) +{ + struct stream *s; + + if (zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_INTERFACE_SET_PROTODOWN, vrf_id); + stream_putl(s, ifp->ifindex); + stream_putc(s, !!down); + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); +} + +/* Make connection to zebra daemon. */ +int zclient_start(struct zclient *zclient) +{ + if (zclient_debug) + zlog_info("zclient_start is called"); + + /* If already connected to the zebra. */ + if (zclient->sock >= 0) + return 0; + + /* Check connect thread. */ + if (zclient->t_connect) + return 0; + + if (zclient_socket_connect(zclient) < 0) { + if (zclient_debug) + zlog_debug("zclient connection fail"); + zclient->fail++; + zclient_event(ZCLIENT_CONNECT, zclient); + return -1; + } + + if (set_nonblocking(zclient->sock) < 0) + flog_err(EC_LIB_ZAPI_SOCKET, "%s: set_nonblocking(%d) failed", + __func__, zclient->sock); + + /* Clear fail count. */ + zclient->fail = 0; + if (zclient_debug) + zlog_debug("zclient connect success with socket [%d]", + zclient->sock); + + /* Create read thread. */ + zclient_event(ZCLIENT_READ, zclient); + + zclient_send_hello(zclient); + + zebra_message_send(zclient, ZEBRA_INTERFACE_ADD, VRF_DEFAULT); + + /* Inform the successful connection. */ + if (zclient->zebra_connected) + (*zclient->zebra_connected)(zclient); + + return 0; +} + +/* Initialize zebra client. Argument redist_default is unwanted + redistribute route type. */ +void zclient_init(struct zclient *zclient, int redist_default, + unsigned short instance, struct zebra_privs_t *privs) +{ + int afi, i; + + /* Set -1 to the default socket value. */ + zclient->sock = -1; + zclient->privs = privs; + + /* Clear redistribution flags. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) + vrf_bitmap_init(&zclient->redist[afi][i]); + + /* Set unwanted redistribute route. bgpd does not need BGP route + redistribution. */ + zclient->redist_default = redist_default; + zclient->instance = instance; + /* Pending: make afi(s) an arg. */ + for (afi = AFI_IP; afi < AFI_MAX; afi++) { + redist_add_instance(&zclient->mi_redist[afi][redist_default], + instance); + + /* Set default-information redistribute to zero. */ + vrf_bitmap_init(&zclient->default_information[afi]); + } + + if (zclient_debug) + zlog_debug("scheduling zclient connection"); + + zclient_event(ZCLIENT_SCHEDULE, zclient); +} + +/* This function is a wrapper function for calling zclient_start from + timer or event thread. */ +static void zclient_connect(struct event *t) +{ + struct zclient *zclient; + + zclient = EVENT_ARG(t); + zclient->t_connect = NULL; + + if (zclient_debug) + zlog_debug("zclient_connect is called"); + + zclient_start(zclient); +} + +enum zclient_send_status zclient_send_rnh(struct zclient *zclient, int command, + const struct prefix *p, safi_t safi, + bool connected, bool resolve_via_def, + vrf_id_t vrf_id) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, command, vrf_id); + stream_putc(s, (connected) ? 1 : 0); + stream_putc(s, (resolve_via_def) ? 1 : 0); + stream_putw(s, safi); + stream_putw(s, PREFIX_FAMILY(p)); + stream_putc(s, p->prefixlen); + switch (PREFIX_FAMILY(p)) { + case AF_INET: + stream_put_in_addr(s, &p->u.prefix4); + break; + case AF_INET6: + stream_put(s, &(p->u.prefix6), 16); + break; + default: + break; + } + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * "xdr_encode"-like interface that allows daemon (client) to send + * a message to zebra server for a route that needs to be + * added/deleted to the kernel. Info about the route is specified + * by the caller in a struct zapi_route. zapi_route_encode() then writes + * the info down the zclient socket using the stream_* functions. + * + * The corresponding read ("xdr_decode") function on the server + * side is zapi_route_decode(). + * + * If ZAPI_MESSAGE_DISTANCE is set, the distance value is written as a 1 + * byte value. + * + * If ZAPI_MESSAGE_METRIC is set, the metric value is written as a 4 + * byte value. + * + * If ZAPI_MESSAGE_TAG is set, the tag value is written as a 4 byte value + * + * If ZAPI_MESSAGE_MTU is set, the mtu value is written as a 4 byte value + * + * XXX: No attention paid to alignment. + */ +enum zclient_send_status +zclient_route_send(uint8_t cmd, struct zclient *zclient, struct zapi_route *api) +{ + if (zapi_route_encode(cmd, zclient->obuf, api) < 0) + return ZCLIENT_SEND_FAILURE; + return zclient_send_message(zclient); +} + +static int zapi_nexthop_labels_cmp(const struct zapi_nexthop *next1, + const struct zapi_nexthop *next2) +{ + if (next1->label_num > next2->label_num) + return 1; + + if (next1->label_num < next2->label_num) + return -1; + + return memcmp(next1->labels, next2->labels, next1->label_num); +} + +static int zapi_nexthop_srv6_cmp(const struct zapi_nexthop *next1, + const struct zapi_nexthop *next2) +{ + int ret = 0; + + ret = memcmp(&next1->seg6_segs, &next2->seg6_segs, + sizeof(struct in6_addr)); + if (ret != 0) + return ret; + + if (next1->seg6local_action > next2->seg6local_action) + return 1; + + if (next1->seg6local_action < next2->seg6local_action) + return -1; + + return memcmp(&next1->seg6local_ctx, &next2->seg6local_ctx, + sizeof(struct seg6local_context)); +} + +static int zapi_nexthop_cmp_no_labels(const struct zapi_nexthop *next1, + const struct zapi_nexthop *next2) +{ + int ret = 0; + + if (next1->vrf_id < next2->vrf_id) + return -1; + + if (next1->vrf_id > next2->vrf_id) + return 1; + + if (next1->type < next2->type) + return -1; + + if (next1->type > next2->type) + return 1; + + if (next1->weight < next2->weight) + return -1; + + if (next1->weight > next2->weight) + return 1; + + switch (next1->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + ret = nexthop_g_addr_cmp(next1->type, &next1->gate, + &next2->gate); + if (ret != 0) + return ret; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + ret = nexthop_g_addr_cmp(next1->type, &next1->gate, + &next2->gate); + if (ret != 0) + return ret; + fallthrough; + case NEXTHOP_TYPE_IFINDEX: + if (next1->ifindex < next2->ifindex) + return -1; + + if (next1->ifindex > next2->ifindex) + return 1; + break; + case NEXTHOP_TYPE_BLACKHOLE: + if (next1->bh_type < next2->bh_type) + return -1; + + if (next1->bh_type > next2->bh_type) + return 1; + break; + } + + if (next1->srte_color < next2->srte_color) + return -1; + if (next1->srte_color > next2->srte_color) + return 1; + + if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) || + CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + + if (!CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) && + CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) + return -1; + + if (CHECK_FLAG(next1->flags, NEXTHOP_FLAG_HAS_BACKUP) && + !CHECK_FLAG(next2->flags, NEXTHOP_FLAG_HAS_BACKUP)) + return 1; + + if (next1->backup_num > 0 || next2->backup_num > 0) { + + if (next1->backup_num < next2->backup_num) + return -1; + + if (next1->backup_num > next2->backup_num) + return 1; + + ret = memcmp(next1->backup_idx, + next2->backup_idx, next1->backup_num); + if (ret != 0) + return ret; + } + } + + return 0; +} + +static int zapi_nexthop_cmp(const void *item1, const void *item2) +{ + int ret = 0; + + const struct zapi_nexthop *next1 = item1; + const struct zapi_nexthop *next2 = item2; + + ret = zapi_nexthop_cmp_no_labels(next1, next2); + if (ret != 0) + return ret; + + ret = zapi_nexthop_labels_cmp(next1, next2); + if (ret != 0) + return ret; + + ret = zapi_nexthop_srv6_cmp(next1, next2); + + return ret; +} + +static void zapi_nexthop_group_sort(struct zapi_nexthop *nh_grp, + uint16_t nexthop_num) +{ + qsort(nh_grp, nexthop_num, sizeof(struct zapi_nexthop), + &zapi_nexthop_cmp); +} + +/* + * Encode a single zapi nexthop + */ +int zapi_nexthop_encode(struct stream *s, const struct zapi_nexthop *api_nh, + uint32_t api_flags, uint32_t api_message) +{ + int i, ret = 0; + int nh_flags = api_nh->flags; + + stream_putl(s, api_nh->vrf_id); + stream_putc(s, api_nh->type); + + /* If needed, set 'labelled nexthop' flag */ + if (api_nh->label_num > 0) { + SET_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_LABEL); + + /* Validate label count */ + if (api_nh->label_num > MPLS_MAX_LABELS) { + ret = -1; + goto done; + } + } + + /* If present, set 'weight' flag before encoding flags */ + if (api_nh->weight) + SET_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_WEIGHT); + + /* Note that we're only encoding a single octet */ + stream_putc(s, nh_flags); + + switch (api_nh->type) { + case NEXTHOP_TYPE_BLACKHOLE: + stream_putc(s, api_nh->bh_type); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + stream_put_in_addr(s, &api_nh->gate.ipv4); + stream_putl(s, api_nh->ifindex); + break; + case NEXTHOP_TYPE_IFINDEX: + stream_putl(s, api_nh->ifindex); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + stream_write(s, (uint8_t *)&api_nh->gate.ipv6, + 16); + stream_putl(s, api_nh->ifindex); + break; + } + + /* We only encode labels if we have >0 - we use + * the per-nexthop flag above to signal that the count + * is present in the payload. + */ + if (api_nh->label_num > 0) { + stream_putc(s, api_nh->label_num); + stream_putc(s, api_nh->label_type); + stream_put(s, &api_nh->labels[0], + api_nh->label_num * sizeof(mpls_label_t)); + } + + if (api_nh->weight) + stream_putq(s, api_nh->weight); + + /* Router MAC for EVPN routes. */ + if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_EVPN)) + stream_put(s, &(api_nh->rmac), + sizeof(struct ethaddr)); + + /* Color for Segment Routing TE. */ + if (CHECK_FLAG(api_message, ZAPI_MESSAGE_SRTE)) + stream_putl(s, api_nh->srte_color); + + /* Index of backup nexthop */ + if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) { + /* Validate backup count */ + if (api_nh->backup_num > NEXTHOP_MAX_BACKUPS) { + ret = -1; + goto done; + } + + stream_putc(s, api_nh->backup_num); + for (i = 0; i < api_nh->backup_num; i++) + stream_putc(s, api_nh->backup_idx[i]); + } + + if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL)) { + stream_putl(s, api_nh->seg6local_action); + stream_write(s, &api_nh->seg6local_ctx, + sizeof(struct seg6local_context)); + } + + if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6)) { + stream_putc(s, api_nh->seg_num); + stream_put(s, &api_nh->seg6_segs[0], + api_nh->seg_num * sizeof(struct in6_addr)); + } +done: + return ret; +} + +int zapi_srv6_locator_chunk_encode(struct stream *s, + const struct srv6_locator_chunk *c) +{ + stream_putw(s, strlen(c->locator_name)); + stream_put(s, c->locator_name, strlen(c->locator_name)); + stream_putw(s, c->prefix.prefixlen); + stream_put(s, &c->prefix.prefix, sizeof(c->prefix.prefix)); + stream_putc(s, c->block_bits_length); + stream_putc(s, c->node_bits_length); + stream_putc(s, c->function_bits_length); + stream_putc(s, c->argument_bits_length); + stream_putc(s, c->flags); + return 0; +} + +int zapi_srv6_locator_chunk_decode(struct stream *s, + struct srv6_locator_chunk *c) +{ + uint16_t len = 0; + + c->prefix.family = AF_INET6; + + STREAM_GETW(s, len); + if (len > SRV6_LOCNAME_SIZE) + goto stream_failure; + + STREAM_GET(c->locator_name, s, len); + STREAM_GETW(s, c->prefix.prefixlen); + STREAM_GET(&c->prefix.prefix, s, sizeof(c->prefix.prefix)); + STREAM_GETC(s, c->block_bits_length); + STREAM_GETC(s, c->node_bits_length); + STREAM_GETC(s, c->function_bits_length); + STREAM_GETC(s, c->argument_bits_length); + STREAM_GETC(s, c->flags); + return 0; + +stream_failure: + return -1; +} + +int zapi_srv6_locator_encode(struct stream *s, const struct srv6_locator *l) +{ + stream_putw(s, strlen(l->name)); + stream_put(s, l->name, strlen(l->name)); + stream_putw(s, l->prefix.prefixlen); + stream_put(s, &l->prefix.prefix, sizeof(l->prefix.prefix)); + stream_putc(s, l->flags); + return 0; +} + +int zapi_srv6_locator_decode(struct stream *s, struct srv6_locator *l) +{ + uint16_t len = 0; + + STREAM_GETW(s, len); + if (len > SRV6_LOCNAME_SIZE) + goto stream_failure; + + STREAM_GET(l->name, s, len); + STREAM_GETW(s, l->prefix.prefixlen); + STREAM_GET(&l->prefix.prefix, s, sizeof(l->prefix.prefix)); + l->prefix.family = AF_INET6; + STREAM_GETC(s, l->flags); + return 0; + +stream_failure: + return -1; +} + +static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) +{ + int i; + + if (cmd != ZEBRA_NHG_DEL && cmd != ZEBRA_NHG_ADD) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified zapi NHG command (%d) doesn't exist", + __func__, cmd); + return -1; + } + + if (api_nhg->nexthop_num >= MULTIPATH_NUM || + api_nhg->backup_nexthop_num >= MULTIPATH_NUM) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: zapi NHG encode with invalid input", __func__); + return -1; + } + + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + stream_putw(s, api_nhg->proto); + stream_putl(s, api_nhg->id); + + stream_putw(s, api_nhg->resilience.buckets); + stream_putl(s, api_nhg->resilience.idle_timer); + stream_putl(s, api_nhg->resilience.unbalanced_timer); + + if (cmd == ZEBRA_NHG_ADD) { + /* Nexthops */ + zapi_nexthop_group_sort(api_nhg->nexthops, + api_nhg->nexthop_num); + + stream_putw(s, api_nhg->nexthop_num); + + for (i = 0; i < api_nhg->nexthop_num; i++) + zapi_nexthop_encode(s, &api_nhg->nexthops[i], 0, 0); + + /* Backup nexthops */ + stream_putw(s, api_nhg->backup_nexthop_num); + + for (i = 0; i < api_nhg->backup_nexthop_num; i++) + zapi_nexthop_encode(s, &api_nhg->backup_nexthops[i], 0, + 0); + } + + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +enum zclient_send_status zclient_nhg_send(struct zclient *zclient, int cmd, + struct zapi_nhg *api_nhg) +{ + api_nhg->proto = zclient->redist_default; + + if (zapi_nhg_encode(zclient->obuf, cmd, api_nhg)) + return -1; + + return zclient_send_message(zclient); +} + +int zapi_route_encode(uint8_t cmd, struct stream *s, struct zapi_route *api) +{ + struct zapi_nexthop *api_nh; + int i; + int psize; + + stream_reset(s); + zclient_create_header(s, cmd, api->vrf_id); + + if (api->type >= ZEBRA_ROUTE_MAX) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified route type (%u) is not a legal value", + __func__, api->type); + return -1; + } + stream_putc(s, api->type); + + stream_putw(s, api->instance); + stream_putl(s, api->flags); + stream_putl(s, api->message); + + if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified route SAFI (%u) is not a legal value", + __func__, api->safi); + return -1; + } + stream_putc(s, api->safi); + + /* Put prefix information. */ + stream_putc(s, api->prefix.family); + psize = PSIZE(api->prefix.prefixlen); + stream_putc(s, api->prefix.prefixlen); + stream_write(s, &api->prefix.u.prefix, psize); + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) { + psize = PSIZE(api->src_prefix.prefixlen); + stream_putc(s, api->src_prefix.prefixlen); + stream_write(s, (uint8_t *)&api->src_prefix.prefix, psize); + } + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NHG)) + stream_putl(s, api->nhgid); + + /* Nexthops. */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) { + /* limit the number of nexthops if necessary */ + if (api->nexthop_num > MULTIPATH_NUM) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: prefix %pFX: can't encode %u nexthops (maximum is %u)", + __func__, &api->prefix, api->nexthop_num, + MULTIPATH_NUM); + return -1; + } + + /* We canonicalize the nexthops by sorting them; this allows + * zebra to resolve the list of nexthops to a nexthop-group + * more efficiently. + */ + zapi_nexthop_group_sort(api->nexthops, api->nexthop_num); + + stream_putw(s, api->nexthop_num); + + for (i = 0; i < api->nexthop_num; i++) { + api_nh = &api->nexthops[i]; + + /* MPLS labels for BGP-LU or Segment Routing */ + if (api_nh->label_num > MPLS_MAX_LABELS) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: prefix %pFX: can't encode %u labels (maximum is %u)", + __func__, &api->prefix, + api_nh->label_num, MPLS_MAX_LABELS); + return -1; + } + + if (zapi_nexthop_encode(s, api_nh, api->flags, + api->message) + != 0) + return -1; + } + } + + /* Backup nexthops */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS)) { + /* limit the number of nexthops if necessary */ + if (api->backup_nexthop_num > MULTIPATH_NUM) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: prefix %pFX: can't encode %u backup nexthops (maximum is %u)", + __func__, &api->prefix, api->backup_nexthop_num, + MULTIPATH_NUM); + return -1; + } + + /* Note that we do not sort the list of backup nexthops - + * this list is treated as an array and indexed by each + * primary nexthop that is associated with a backup. + */ + + stream_putw(s, api->backup_nexthop_num); + + for (i = 0; i < api->backup_nexthop_num; i++) { + api_nh = &api->backup_nexthops[i]; + + /* MPLS labels for BGP-LU or Segment Routing */ + if (api_nh->label_num > MPLS_MAX_LABELS) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: prefix %pFX: backup: can't encode %u labels (maximum is %u)", + __func__, &api->prefix, + api_nh->label_num, MPLS_MAX_LABELS); + return -1; + } + + if (zapi_nexthop_encode(s, api_nh, api->flags, + api->message) + != 0) + return -1; + } + } + + /* Attributes. */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE)) + stream_putc(s, api->distance); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) + stream_putl(s, api->metric); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) + stream_putl(s, api->tag); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) + stream_putl(s, api->mtu); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID)) + stream_putl(s, api->tableid); + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_OPAQUE)) { + if (api->opaque.length > ZAPI_MESSAGE_OPAQUE_LENGTH) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: opaque length %u is greater than allowed value", + __func__, api->opaque.length); + return -1; + } + + stream_putw(s, api->opaque.length); + stream_write(s, api->opaque.data, api->opaque.length); + } + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +/* + * Decode a single zapi nexthop object + */ +int zapi_nexthop_decode(struct stream *s, struct zapi_nexthop *api_nh, + uint32_t api_flags, uint32_t api_message) +{ + int i, ret = -1; + + STREAM_GETL(s, api_nh->vrf_id); + STREAM_GETC(s, api_nh->type); + + /* Note that we're only using a single octet of flags */ + STREAM_GETC(s, api_nh->flags); + + switch (api_nh->type) { + case NEXTHOP_TYPE_BLACKHOLE: + STREAM_GETC(s, api_nh->bh_type); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + STREAM_GET(&api_nh->gate.ipv4.s_addr, s, + IPV4_MAX_BYTELEN); + STREAM_GETL(s, api_nh->ifindex); + break; + case NEXTHOP_TYPE_IFINDEX: + STREAM_GETL(s, api_nh->ifindex); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + STREAM_GET(&api_nh->gate.ipv6, s, 16); + STREAM_GETL(s, api_nh->ifindex); + break; + } + + /* MPLS labels for BGP-LU or Segment Routing */ + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL)) { + STREAM_GETC(s, api_nh->label_num); + STREAM_GETC(s, api_nh->label_type); + if (api_nh->label_num > MPLS_MAX_LABELS) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: invalid number of MPLS labels (%u)", + __func__, api_nh->label_num); + return -1; + } + + STREAM_GET(&api_nh->labels[0], s, + api_nh->label_num * sizeof(mpls_label_t)); + } + + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_WEIGHT)) + STREAM_GETQ(s, api_nh->weight); + + /* Router MAC for EVPN routes. */ + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) + STREAM_GET(&(api_nh->rmac), s, + sizeof(struct ethaddr)); + + /* Color for Segment Routing TE. */ + if (CHECK_FLAG(api_message, ZAPI_MESSAGE_SRTE)) + STREAM_GETL(s, api_nh->srte_color); + + /* Backup nexthop index */ + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) { + STREAM_GETC(s, api_nh->backup_num); + + if (api_nh->backup_num > NEXTHOP_MAX_BACKUPS) + return -1; + + for (i = 0; i < api_nh->backup_num; i++) + STREAM_GETC(s, api_nh->backup_idx[i]); + } + + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL)) { + STREAM_GETL(s, api_nh->seg6local_action); + STREAM_GET(&api_nh->seg6local_ctx, s, + sizeof(struct seg6local_context)); + } + + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6)) { + STREAM_GETC(s, api_nh->seg_num); + if (api_nh->seg_num > SRV6_MAX_SIDS) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: invalid number of SRv6 Segs (%u)", + __func__, api_nh->seg_num); + return -1; + } + + STREAM_GET(&api_nh->seg6_segs[0], s, + api_nh->seg_num * sizeof(struct in6_addr)); + } + + /* Success */ + ret = 0; + +stream_failure: + + return ret; +} + +int zapi_route_decode(struct stream *s, struct zapi_route *api) +{ + struct zapi_nexthop *api_nh; + int i; + + memset(api, 0, sizeof(*api)); + + /* Type, flags, message. */ + STREAM_GETC(s, api->type); + if (api->type >= ZEBRA_ROUTE_MAX) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified route type: %d is not a legal value", + __func__, api->type); + return -1; + } + + STREAM_GETW(s, api->instance); + STREAM_GETL(s, api->flags); + STREAM_GETL(s, api->message); + STREAM_GETC(s, api->safi); + if (api->safi < SAFI_UNICAST || api->safi >= SAFI_MAX) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified route SAFI (%u) is not a legal value", + __func__, api->safi); + return -1; + } + + /* Prefix. */ + STREAM_GETC(s, api->prefix.family); + STREAM_GETC(s, api->prefix.prefixlen); + switch (api->prefix.family) { + case AF_INET: + if (api->prefix.prefixlen > IPV4_MAX_BITLEN) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: V4 prefixlen is %d which should not be more than 32", + __func__, api->prefix.prefixlen); + return -1; + } + break; + case AF_INET6: + if (api->prefix.prefixlen > IPV6_MAX_BITLEN) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: v6 prefixlen is %d which should not be more than 128", + __func__, api->prefix.prefixlen); + return -1; + } + break; + default: + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified family %d is not v4 or v6", __func__, + api->prefix.family); + return -1; + } + STREAM_GET(&api->prefix.u.prefix, s, PSIZE(api->prefix.prefixlen)); + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_SRCPFX)) { + api->src_prefix.family = AF_INET6; + STREAM_GETC(s, api->src_prefix.prefixlen); + if (api->src_prefix.prefixlen > IPV6_MAX_BITLEN) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: SRC Prefix prefixlen received: %d is too large", + __func__, api->src_prefix.prefixlen); + return -1; + } + STREAM_GET(&api->src_prefix.prefix, s, + PSIZE(api->src_prefix.prefixlen)); + + if (api->prefix.family != AF_INET6 + || api->src_prefix.prefixlen == 0) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: SRC prefix specified in some manner that makes no sense", + __func__); + return -1; + } + } + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NHG)) + STREAM_GETL(s, api->nhgid); + + /* Nexthops. */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP)) { + STREAM_GETW(s, api->nexthop_num); + if (api->nexthop_num > MULTIPATH_NUM) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: invalid number of nexthops (%u)", + __func__, api->nexthop_num); + return -1; + } + + for (i = 0; i < api->nexthop_num; i++) { + api_nh = &api->nexthops[i]; + + if (zapi_nexthop_decode(s, api_nh, api->flags, + api->message) + != 0) + return -1; + } + } + + /* Backup nexthops. */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS)) { + STREAM_GETW(s, api->backup_nexthop_num); + if (api->backup_nexthop_num > MULTIPATH_NUM) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: invalid number of backup nexthops (%u)", + __func__, api->backup_nexthop_num); + return -1; + } + + for (i = 0; i < api->backup_nexthop_num; i++) { + api_nh = &api->backup_nexthops[i]; + + if (zapi_nexthop_decode(s, api_nh, api->flags, + api->message) + != 0) + return -1; + } + } + + /* Attributes. */ + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_DISTANCE)) + STREAM_GETC(s, api->distance); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_METRIC)) + STREAM_GETL(s, api->metric); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TAG)) + STREAM_GETL(s, api->tag); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_MTU)) + STREAM_GETL(s, api->mtu); + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_TABLEID)) + STREAM_GETL(s, api->tableid); + + if (CHECK_FLAG(api->message, ZAPI_MESSAGE_OPAQUE)) { + STREAM_GETW(s, api->opaque.length); + if (api->opaque.length > ZAPI_MESSAGE_OPAQUE_LENGTH) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: opaque length %u is greater than allowed value", + __func__, api->opaque.length); + return -1; + } + + STREAM_GET(api->opaque.data, s, api->opaque.length); + } + + return 0; +stream_failure: + return -1; +} + +static void zapi_encode_prefix(struct stream *s, struct prefix *p, + uint8_t family) +{ + struct prefix any; + + if (!p) { + memset(&any, 0, sizeof(any)); + any.family = family; + p = &any; + } + + stream_putc(s, p->family); + stream_putc(s, p->prefixlen); + stream_put(s, &p->u.prefix, prefix_blen(p)); +} + +static bool zapi_decode_prefix(struct stream *s, struct prefix *p) +{ + STREAM_GETC(s, p->family); + STREAM_GETC(s, p->prefixlen); + STREAM_GET(&(p->u.prefix), s, prefix_blen(p)); + return true; + +stream_failure: + return false; +} + +static void zapi_encode_sockunion(struct stream *s, const union sockunion *su) +{ + int family = sockunion_family(su); + size_t addrlen = family2addrsize(family); + + /* + * Must know length to encode + */ + assert(addrlen); + + stream_putc(s, (uint8_t)family); + + stream_write(s, sockunion_get_addr(su), addrlen); +} + +static bool zapi_decode_sockunion(struct stream *s, union sockunion *su) +{ + uint8_t family; + size_t addrlen; + uint8_t buf[sizeof(union sockunion)]; + + memset(su, 0, sizeof(*su)); + + STREAM_GETC(s, family); + sockunion_family(su) = family; + + addrlen = family2addrsize(family); + if (!addrlen) + return false; + + if (addrlen > sizeof(buf)) + return false; + + STREAM_GET(buf, s, addrlen); + sockunion_set(su, family, buf, addrlen); + return true; + +stream_failure: + return false; +} + +/* + * Encode filter subsection of pbr_rule + */ +static void zapi_pbr_rule_filter_encode(struct stream *s, struct pbr_filter *f) +{ + assert(f->src_ip.family == f->dst_ip.family); + assert((f->src_ip.family == AF_INET) || (f->src_ip.family == AF_INET6)); + + stream_putl(s, f->filter_bm); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_IP_PROTOCOL)) + stream_putc(s, f->ip_proto); + + /* addresses */ + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_SRC_IP)) + zapi_encode_prefix(s, &f->src_ip, f->src_ip.family); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DST_IP)) + zapi_encode_prefix(s, &f->dst_ip, f->dst_ip.family); + + /* port numbers */ + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_SRC_PORT)) + stream_putw(s, f->src_port); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DST_PORT)) + stream_putw(s, f->dst_port); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DSCP)) + stream_putc(s, f->dsfield & PBR_DSFIELD_DSCP); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_ECN)) + stream_putc(s, f->dsfield & PBR_DSFIELD_ECN); + + /* vlan */ + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_PCP)) + stream_putc(s, f->pcp); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_VLAN_ID)) + stream_putw(s, f->vlan_id); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_VLAN_FLAGS)) + stream_putw(s, f->vlan_flags); + + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_FWMARK)) + stream_putl(s, f->fwmark); +} + +static bool zapi_pbr_rule_filter_decode(struct stream *s, struct pbr_filter *f) +{ + uint8_t dscp = 0; + uint8_t ecn = 0; + + STREAM_GETL(s, f->filter_bm); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_IP_PROTOCOL)) + STREAM_GETC(s, f->ip_proto); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_SRC_IP)) + if (!zapi_decode_prefix(s, &(f->src_ip))) + goto stream_failure; + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DST_IP)) + if (!zapi_decode_prefix(s, &(f->dst_ip))) + goto stream_failure; + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_SRC_PORT)) + STREAM_GETW(s, f->src_port); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DST_PORT)) + STREAM_GETW(s, f->dst_port); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_DSCP)) + STREAM_GETC(s, dscp); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_ECN)) + STREAM_GETC(s, ecn); + f->dsfield = (dscp & PBR_DSFIELD_DSCP) | (ecn & PBR_DSFIELD_ECN); + + /* vlan */ + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_PCP)) + STREAM_GETC(s, f->pcp); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_VLAN_ID)) + STREAM_GETW(s, f->vlan_id); + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_VLAN_FLAGS)) + STREAM_GETW(s, f->vlan_flags); + + if (CHECK_FLAG(f->filter_bm, PBR_FILTER_FWMARK)) + STREAM_GETL(s, f->fwmark); + + return true; + +stream_failure: + return false; +} + +static void zapi_pbr_rule_action_encode(struct stream *s, struct pbr_action *a) +{ + stream_putl(s, a->flags); + + if (CHECK_FLAG(a->flags, PBR_ACTION_TABLE)) + stream_putl(s, a->table); + if (CHECK_FLAG(a->flags, PBR_ACTION_QUEUE_ID)) + stream_putl(s, a->queue_id); + + /* L3 */ + if (CHECK_FLAG(a->flags, PBR_ACTION_SRC_IP)) + zapi_encode_sockunion(s, &a->src_ip); + if (CHECK_FLAG(a->flags, PBR_ACTION_DST_IP)) + zapi_encode_sockunion(s, &a->dst_ip); + if (CHECK_FLAG(a->flags, PBR_ACTION_SRC_PORT)) + stream_putw(s, a->src_port); + if (CHECK_FLAG(a->flags, PBR_ACTION_DST_PORT)) + stream_putw(s, a->dst_port); + + if (CHECK_FLAG(a->flags, PBR_ACTION_DSCP)) + stream_putc(s, a->dscp & PBR_DSFIELD_DSCP); + if (CHECK_FLAG(a->flags, PBR_ACTION_ECN)) + stream_putc(s, a->ecn & PBR_DSFIELD_ECN); + + /* L2 */ + if (CHECK_FLAG(a->flags, PBR_ACTION_PCP)) + stream_putc(s, a->pcp); + if (CHECK_FLAG(a->flags, PBR_ACTION_VLAN_ID)) + stream_putw(s, a->vlan_id); +} + +static bool zapi_pbr_rule_action_decode(struct stream *s, struct pbr_action *a) +{ + STREAM_GETL(s, a->flags); + + if (CHECK_FLAG(a->flags, PBR_ACTION_TABLE)) + STREAM_GETL(s, a->table); + if (CHECK_FLAG(a->flags, PBR_ACTION_QUEUE_ID)) + STREAM_GETL(s, a->queue_id); + + /* L3 */ + if (CHECK_FLAG(a->flags, PBR_ACTION_SRC_IP)) { + if (!zapi_decode_sockunion(s, &(a->src_ip))) + goto stream_failure; + } + if (CHECK_FLAG(a->flags, PBR_ACTION_DST_IP)) + if (!zapi_decode_sockunion(s, &(a->dst_ip))) + goto stream_failure; + + if (CHECK_FLAG(a->flags, PBR_ACTION_SRC_PORT)) + STREAM_GETW(s, a->src_port); + if (CHECK_FLAG(a->flags, PBR_ACTION_DST_PORT)) + STREAM_GETW(s, a->dst_port); + + if (CHECK_FLAG(a->flags, PBR_ACTION_DSCP)) { + STREAM_GETC(s, a->dscp); + a->dscp &= PBR_DSFIELD_DSCP; + } + if (CHECK_FLAG(a->flags, PBR_ACTION_ECN)) { + STREAM_GETC(s, a->ecn); + a->ecn &= PBR_DSFIELD_ECN; + } + + /* L2 */ + if (CHECK_FLAG(a->flags, PBR_ACTION_PCP)) + STREAM_GETC(s, a->pcp); + if (CHECK_FLAG(a->flags, PBR_ACTION_VLAN_ID)) + STREAM_GETW(s, a->vlan_id); + + return true; + +stream_failure: + return false; +} + +int zapi_pbr_rule_encode(struct stream *s, struct pbr_rule *r) +{ + /* + * PBR record count is always 1 + */ + stream_putl(s, 1); + + stream_putc(s, r->family); + stream_putl(s, r->seq); + stream_putl(s, r->priority); + stream_putl(s, r->unique); + + zapi_pbr_rule_filter_encode(s, &(r->filter)); + zapi_pbr_rule_action_encode(s, &(r->action)); + + stream_put(s, r->ifname, IFNAMSIZ); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +bool zapi_pbr_rule_decode(struct stream *s, struct pbr_rule *r) +{ + /* NB caller has already read 4-byte rule count */ + + memset(r, 0, sizeof(*r)); + + STREAM_GETC(s, r->family); + STREAM_GETL(s, r->seq); + STREAM_GETL(s, r->priority); + STREAM_GETL(s, r->unique); + + if (!zapi_pbr_rule_filter_decode(s, &(r->filter))) + goto stream_failure; + if (!zapi_pbr_rule_action_decode(s, &(r->action))) + goto stream_failure; + + STREAM_GET(r->ifname, s, IFNAMSIZ); + return true; + +stream_failure: + return false; +} + +int zapi_tc_qdisc_encode(uint8_t cmd, struct stream *s, struct tc_qdisc *qdisc) +{ + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + + stream_putl(s, 1); + + stream_putl(s, qdisc->ifindex); + stream_putl(s, qdisc->kind); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +int zapi_tc_class_encode(uint8_t cmd, struct stream *s, struct tc_class *class) +{ + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + stream_putl(s, 1); + + stream_putl(s, class->ifindex); + stream_putl(s, class->handle); + stream_putl(s, class->kind); + + switch (class->kind) { + case TC_QDISC_HTB: + stream_putq(s, class->u.htb.rate); + stream_putq(s, class->u.htb.ceil); + break; + case TC_QDISC_UNSPEC: + case TC_QDISC_NOQUEUE: + /* not implemented */ + break; + } + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +int zapi_tc_filter_encode(uint8_t cmd, struct stream *s, + struct tc_filter *filter) +{ + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + stream_putl(s, 1); + + stream_putl(s, filter->ifindex); + stream_putl(s, filter->handle); + stream_putl(s, filter->priority); + stream_putl(s, filter->protocol); + stream_putl(s, filter->kind); + + switch (filter->kind) { + case TC_FILTER_FLOWER: + stream_putl(s, filter->u.flower.filter_bm); + if (filter->u.flower.filter_bm & TC_FLOWER_IP_PROTOCOL) + stream_putc(s, filter->u.flower.ip_proto); + if (filter->u.flower.filter_bm & TC_FLOWER_SRC_IP) + zapi_encode_prefix(s, &filter->u.flower.src_ip, + filter->u.flower.src_ip.family); + if (filter->u.flower.filter_bm & TC_FLOWER_SRC_PORT) { + stream_putw(s, filter->u.flower.src_port_min); + stream_putw(s, filter->u.flower.src_port_max); + } + if (filter->u.flower.filter_bm & TC_FLOWER_DST_IP) + zapi_encode_prefix(s, &filter->u.flower.dst_ip, + filter->u.flower.dst_ip.family); + if (filter->u.flower.filter_bm & TC_FLOWER_DST_PORT) { + stream_putw(s, filter->u.flower.dst_port_min); + stream_putw(s, filter->u.flower.dst_port_max); + } + if (filter->u.flower.filter_bm & TC_FLOWER_DSFIELD) { + stream_putc(s, filter->u.flower.dsfield); + stream_putc(s, filter->u.flower.dsfield_mask); + } + stream_putl(s, filter->u.flower.classid); + break; + case TC_FILTER_UNSPEC: + case TC_FILTER_BPF: + case TC_FILTER_FLOW: + case TC_FILTER_U32: + /* not implemented */ + break; + } + + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +bool zapi_nhg_notify_decode(struct stream *s, uint32_t *id, + enum zapi_nhg_notify_owner *note) +{ + uint32_t read_id; + + STREAM_GET(note, s, sizeof(*note)); + STREAM_GETL(s, read_id); + + *id = read_id; + + return true; + +stream_failure: + return false; +} + +bool zapi_route_notify_decode(struct stream *s, struct prefix *p, + uint32_t *tableid, + enum zapi_route_notify_owner *note, + afi_t *afi, safi_t *safi) +{ + uint32_t t; + afi_t afi_val; + safi_t safi_val; + + STREAM_GET(note, s, sizeof(*note)); + + STREAM_GETC(s, p->family); + STREAM_GETC(s, p->prefixlen); + STREAM_GET(&p->u.prefix, s, prefix_blen(p)); + STREAM_GETL(s, t); + STREAM_GETC(s, afi_val); + STREAM_GETC(s, safi_val); + + *tableid = t; + + if (afi) + *afi = afi_val; + if (safi) + *safi = safi_val; + + return true; + +stream_failure: + return false; +} + +bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno, + uint32_t *priority, uint32_t *unique, char *ifname, + enum zapi_rule_notify_owner *note) +{ + uint32_t prio, seq, uni; + + STREAM_GET(note, s, sizeof(*note)); + + STREAM_GETL(s, seq); + STREAM_GETL(s, prio); + STREAM_GETL(s, uni); + STREAM_GET(ifname, s, IFNAMSIZ); + + if (zclient_debug) + zlog_debug("%s: %u %u %u %s", __func__, seq, prio, uni, ifname); + *seqno = seq; + *priority = prio; + *unique = uni; + + return true; + +stream_failure: + return false; +} + +bool zapi_ipset_notify_decode(struct stream *s, uint32_t *unique, + enum zapi_ipset_notify_owner *note) +{ + uint32_t uni; + uint16_t notew; + + STREAM_GETW(s, notew); + + STREAM_GETL(s, uni); + + if (zclient_debug) + zlog_debug("%s: %u", __func__, uni); + *unique = uni; + *note = (enum zapi_ipset_notify_owner)notew; + return true; + +stream_failure: + return false; +} + +bool zapi_ipset_entry_notify_decode(struct stream *s, uint32_t *unique, + char *ipset_name, + enum zapi_ipset_entry_notify_owner *note) +{ + uint32_t uni; + uint16_t notew; + + STREAM_GETW(s, notew); + + STREAM_GETL(s, uni); + + STREAM_GET(ipset_name, s, ZEBRA_IPSET_NAME_SIZE); + + if (zclient_debug) + zlog_debug("%s: %u", __func__, uni); + *unique = uni; + *note = (enum zapi_ipset_entry_notify_owner)notew; + + return true; + +stream_failure: + return false; +} + +bool zapi_iptable_notify_decode(struct stream *s, + uint32_t *unique, + enum zapi_iptable_notify_owner *note) +{ + uint32_t uni; + uint16_t notew; + + STREAM_GETW(s, notew); + + STREAM_GETL(s, uni); + + if (zclient_debug) + zlog_debug("%s: %u", __func__, uni); + *unique = uni; + *note = (enum zapi_iptable_notify_owner)notew; + + return true; + +stream_failure: + return false; +} + +struct nexthop *nexthop_from_zapi_nexthop(const struct zapi_nexthop *znh) +{ + struct nexthop *n = nexthop_new(); + + n->type = znh->type; + n->vrf_id = znh->vrf_id; + n->ifindex = znh->ifindex; + n->gate = znh->gate; + n->srte_color = znh->srte_color; + + /* + * This function currently handles labels + */ + if (znh->label_num) { + nexthop_add_labels(n, ZEBRA_LSP_NONE, znh->label_num, + znh->labels); + } + + if (CHECK_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP)) { + SET_FLAG(n->flags, NEXTHOP_FLAG_HAS_BACKUP); + n->backup_num = znh->backup_num; + memcpy(n->backup_idx, znh->backup_idx, n->backup_num); + } + + if (znh->seg6local_action != ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + nexthop_add_srv6_seg6local(n, znh->seg6local_action, + &znh->seg6local_ctx); + + if (znh->seg_num && !sid_zero_ipv6(znh->seg6_segs)) + nexthop_add_srv6_seg6(n, &znh->seg6_segs[0], znh->seg_num); + + return n; +} + +/* + * Convert nexthop to zapi nexthop + */ +int zapi_nexthop_from_nexthop(struct zapi_nexthop *znh, + const struct nexthop *nh) +{ + int i; + + memset(znh, 0, sizeof(*znh)); + + znh->type = nh->type; + znh->vrf_id = nh->vrf_id; + znh->weight = nh->weight; + znh->ifindex = nh->ifindex; + znh->gate = nh->gate; + znh->srte_color = nh->srte_color; + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_ONLINK)) + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_EVPN)) + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_EVPN); + + if (nh->nh_label && (nh->nh_label->num_labels > 0)) { + + /* Validate */ + if (nh->nh_label->num_labels > MPLS_MAX_LABELS) + return -1; + + for (i = 0; i < nh->nh_label->num_labels; i++) + znh->labels[i] = nh->nh_label->label[i]; + + znh->label_num = i; + znh->label_type = nh->nh_label_type; + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_LABEL); + } + + if (CHECK_FLAG(nh->flags, NEXTHOP_FLAG_HAS_BACKUP)) { + if (nh->backup_num > NEXTHOP_MAX_BACKUPS) + return -1; + + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + znh->backup_num = nh->backup_num; + memcpy(znh->backup_idx, nh->backup_idx, znh->backup_num); + } + + if (nh->nh_srv6) { + if (nh->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) { + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6LOCAL); + znh->seg6local_action = nh->nh_srv6->seg6local_action; + memcpy(&znh->seg6local_ctx, + &nh->nh_srv6->seg6local_ctx, + sizeof(struct seg6local_context)); + } + + if (nh->nh_srv6->seg6_segs && nh->nh_srv6->seg6_segs->num_segs && + !sid_zero(nh->nh_srv6->seg6_segs)) { + SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6); + znh->seg_num = nh->nh_srv6->seg6_segs->num_segs; + for (i = 0; i < nh->nh_srv6->seg6_segs->num_segs; i++) + memcpy(&znh->seg6_segs[i], + &nh->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); + } + } + + return 0; +} + +/* + * Wrapper that converts backup nexthop + */ +int zapi_backup_nexthop_from_nexthop(struct zapi_nexthop *znh, + const struct nexthop *nh) +{ + int ret; + + /* Ensure that zapi flags are correct: backups don't have backups */ + ret = zapi_nexthop_from_nexthop(znh, nh); + if (ret == 0) + UNSET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + + return ret; +} + +/* + * Format some info about a zapi nexthop, for debug or logging. + */ +const char *zapi_nexthop2str(const struct zapi_nexthop *znh, char *buf, + int bufsize) +{ + char tmp[INET6_ADDRSTRLEN]; + + switch (znh->type) { + case NEXTHOP_TYPE_IFINDEX: + snprintf(buf, bufsize, "if %u", znh->ifindex); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + inet_ntop(AF_INET, &znh->gate.ipv4, tmp, sizeof(tmp)); + snprintf(buf, bufsize, "%s if %u", tmp, znh->ifindex); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + inet_ntop(AF_INET6, &znh->gate.ipv6, tmp, sizeof(tmp)); + snprintf(buf, bufsize, "%s if %u", tmp, znh->ifindex); + break; + case NEXTHOP_TYPE_BLACKHOLE: + snprintf(buf, bufsize, "blackhole"); + break; + default: + snprintf(buf, bufsize, "unknown"); + break; + } + + return buf; +} + +/* + * Decode the nexthop-tracking update message + */ +static bool zapi_nexthop_update_decode(struct stream *s, struct prefix *match, + struct zapi_route *nhr) +{ + uint32_t i; + + memset(nhr, 0, sizeof(*nhr)); + + STREAM_GETL(s, nhr->message); + STREAM_GETW(s, nhr->safi); + STREAM_GETW(s, match->family); + STREAM_GETC(s, match->prefixlen); + /* + * What we got told to match against + */ + switch (match->family) { + case AF_INET: + STREAM_GET(&match->u.prefix4.s_addr, s, IPV4_MAX_BYTELEN); + break; + case AF_INET6: + STREAM_GET(&match->u.prefix6, s, IPV6_MAX_BYTELEN); + break; + } + /* + * What we matched against + */ + STREAM_GETW(s, nhr->prefix.family); + STREAM_GETC(s, nhr->prefix.prefixlen); + switch (nhr->prefix.family) { + case AF_INET: + STREAM_GET(&nhr->prefix.u.prefix4.s_addr, s, IPV4_MAX_BYTELEN); + break; + case AF_INET6: + STREAM_GET(&nhr->prefix.u.prefix6, s, IPV6_MAX_BYTELEN); + break; + default: + break; + } + if (CHECK_FLAG(nhr->message, ZAPI_MESSAGE_SRTE)) + STREAM_GETL(s, nhr->srte_color); + + STREAM_GETC(s, nhr->type); + STREAM_GETW(s, nhr->instance); + STREAM_GETC(s, nhr->distance); + STREAM_GETL(s, nhr->metric); + STREAM_GETC(s, nhr->nexthop_num); + + for (i = 0; i < nhr->nexthop_num; i++) { + if (zapi_nexthop_decode(s, &(nhr->nexthops[i]), 0, 0) != 0) + return false; + } + + return true; +stream_failure: + return false; +} + +bool zapi_error_decode(struct stream *s, enum zebra_error_types *error) +{ + memset(error, 0, sizeof(*error)); + + STREAM_GET(error, s, sizeof(*error)); + + if (zclient_debug) + zlog_debug("%s: type: %s", __func__, + zebra_error_type2str(*error)); + + return true; +stream_failure: + return false; +} + +/* + * send a ZEBRA_REDISTRIBUTE_ADD or ZEBRA_REDISTRIBUTE_DELETE + * for the route type (ZEBRA_ROUTE_KERNEL etc.). The zebra server will + * then set/unset redist[type] in the client handle (a struct zserv) for the + * sending client + */ +enum zclient_send_status +zebra_redistribute_send(int command, struct zclient *zclient, afi_t afi, + int type, unsigned short instance, vrf_id_t vrf_id) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, command, vrf_id); + stream_putc(s, afi); + stream_putc(s, type); + stream_putw(s, instance); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +enum zclient_send_status +zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi, + vrf_id_t vrf_id) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, command, vrf_id); + stream_putc(s, afi); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* Send route notify request to zebra */ +int zebra_route_notify_send(int command, struct zclient *zclient, bool set) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, command, 0); + stream_putc(s, !!set); + + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* Get prefix in ZServ format; family should be filled in on prefix */ +static int zclient_stream_get_prefix(struct stream *s, struct prefix *p) +{ + size_t plen = prefix_blen(p); + uint8_t c; + p->prefixlen = 0; + + if (plen == 0) + return -1; + + STREAM_GET(&p->u.prefix, s, plen); + STREAM_GETC(s, c); + p->prefixlen = MIN(plen * 8, c); + + return 0; +stream_failure: + return -1; +} + +/* Router-id update from zebra daemon. */ +int zebra_router_id_update_read(struct stream *s, struct prefix *rid) +{ + /* Fetch interface address. */ + STREAM_GETC(s, rid->family); + + return zclient_stream_get_prefix(s, rid); + +stream_failure: + return -1; +} + +/* Interface addition from zebra daemon. */ +/* + * The format of the message sent with type ZEBRA_INTERFACE_ADD or + * ZEBRA_INTERFACE_DELETE from zebra to the client is: + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ifname | + * | | + * | | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ifindex | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | status | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | if_flags | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | metric | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | speed | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ifmtu | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | ifmtu6 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | bandwidth | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | parent ifindex | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Link Layer Type | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Harware Address Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Hardware Address if HW length different from 0 | + * | ... max INTERFACE_HWADDR_MAX | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Link_params? | Whether a link-params follows: 1 or 0. + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Link_params 0 or 1 INTERFACE_LINK_PARAMS_SIZE sized | + * | .... (struct if_link_params). | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +static int zclient_vrf_add(ZAPI_CALLBACK_ARGS) +{ + struct vrf *vrf; + char vrfname_tmp[VRF_NAMSIZ + 1] = {}; + struct vrf_data data; + + STREAM_GET(&data, zclient->ibuf, sizeof(struct vrf_data)); + /* Read interface name. */ + STREAM_GET(vrfname_tmp, zclient->ibuf, VRF_NAMSIZ); + + if (strlen(vrfname_tmp) == 0) + goto stream_failure; + + /* Lookup/create vrf by name, then vrf_id. */ + vrf = vrf_get(vrf_id, vrfname_tmp); + + /* If there's already a VRF with this name, don't create vrf */ + if (!vrf) + return 0; + + vrf->data.l.table_id = data.l.table_id; + memcpy(vrf->data.l.netns_name, data.l.netns_name, NS_NAMSIZ); + vrf_enable(vrf); + + return 0; +stream_failure: + return -1; +} + +static int zclient_vrf_delete(ZAPI_CALLBACK_ARGS) +{ + struct vrf *vrf; + + /* Lookup vrf by vrf_id. */ + vrf = vrf_lookup_by_id(vrf_id); + + /* + * If a routing protocol doesn't know about a + * vrf that is about to be deleted. There is + * no point in attempting to delete it. + */ + if (!vrf) + return 0; + + vrf_delete(vrf); + return 0; +} + +static int zclient_interface_add(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + char ifname_tmp[IFNAMSIZ + 1] = {}; + struct stream *s = zclient->ibuf; + struct vrf *vrf; + + /* Read interface name. */ + STREAM_GET(ifname_tmp, s, IFNAMSIZ); + + /* Lookup/create interface by name. */ + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) { + zlog_debug( + "Rx'd interface add from Zebra, but VRF %u does not exist", + vrf_id); + return -1; + } + + ifp = if_get_by_name(ifname_tmp, vrf_id, vrf->name); + + zebra_interface_if_set_value(s, ifp); + + if_new_via_zapi(ifp); + + return 0; +stream_failure: + return -1; +} + +/* + * Read interface up/down msg (ZEBRA_INTERFACE_UP/ZEBRA_INTERFACE_DOWN) + * from zebra server. The format of this message is the same as + * that sent for ZEBRA_INTERFACE_ADD/ZEBRA_INTERFACE_DELETE, + * except that no sockaddr_dl is sent at the tail of the message. + */ +struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t vrf_id) +{ + struct interface *ifp; + char ifname_tmp[IFNAMSIZ + 1] = {}; + + /* Read interface name. */ + STREAM_GET(ifname_tmp, s, IFNAMSIZ); + + /* Lookup this by interface index. */ + ifp = if_lookup_by_name(ifname_tmp, vrf_id); + if (ifp == NULL) { + flog_err(EC_LIB_ZAPI_ENCODE, + "INTERFACE_STATE: Cannot find IF %s in VRF %d", + ifname_tmp, vrf_id); + return NULL; + } + + zebra_interface_if_set_value(s, ifp); + + return ifp; +stream_failure: + return NULL; +} + +static int zclient_interface_delete(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + struct stream *s = zclient->ibuf; + + ifp = zebra_interface_state_read(s, vrf_id); + + if (ifp == NULL) + return 0; + + if_destroy_via_zapi(ifp); + return 0; +} + +static int zclient_interface_up(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + struct stream *s = zclient->ibuf; + + ifp = zebra_interface_state_read(s, vrf_id); + + if (!ifp) + return 0; + + if_up_via_zapi(ifp); + return 0; +} + +static int zclient_interface_down(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + struct stream *s = zclient->ibuf; + + ifp = zebra_interface_state_read(s, vrf_id); + + if (!ifp) + return 0; + + if_down_via_zapi(ifp); + return 0; +} + +static int zclient_handle_error(ZAPI_CALLBACK_ARGS) +{ + enum zebra_error_types error; + struct stream *s = zclient->ibuf; + + zapi_error_decode(s, &error); + + if (zclient->handle_error) + (*zclient->handle_error)(error); + return 0; +} + +static int link_params_set_value(struct stream *s, struct interface *ifp) +{ + uint8_t link_params_enabled, nb_ext_adm_grp; + struct if_link_params *iflp; + uint32_t bwclassnum, bitmap_data; + + iflp = if_link_params_get(ifp); + + if (iflp == NULL) + iflp = if_link_params_init(ifp); + + STREAM_GETC(s, link_params_enabled); + if (!link_params_enabled) { + if_link_params_free(ifp); + return 0; + } + + STREAM_GETL(s, iflp->lp_status); + STREAM_GETL(s, iflp->te_metric); + STREAM_GETF(s, iflp->max_bw); + STREAM_GETF(s, iflp->max_rsv_bw); + STREAM_GETL(s, bwclassnum); + { + unsigned int i; + for (i = 0; i < bwclassnum && i < MAX_CLASS_TYPE; i++) + STREAM_GETF(s, iflp->unrsv_bw[i]); + if (i < bwclassnum) + flog_err( + EC_LIB_ZAPI_MISSMATCH, + "%s: received %d > %d (MAX_CLASS_TYPE) bw entries - outdated library?", + __func__, bwclassnum, MAX_CLASS_TYPE); + } + STREAM_GETL(s, iflp->admin_grp); + + /* Extended Administrative Group */ + admin_group_clear(&iflp->ext_admin_grp); + STREAM_GETC(s, nb_ext_adm_grp); + for (size_t i = 0; i < nb_ext_adm_grp; i++) { + STREAM_GETL(s, bitmap_data); + admin_group_bulk_set(&iflp->ext_admin_grp, bitmap_data, i); + } + + STREAM_GETL(s, iflp->rmt_as); + iflp->rmt_ip.s_addr = stream_get_ipv4(s); + + STREAM_GETL(s, iflp->av_delay); + STREAM_GETL(s, iflp->min_delay); + STREAM_GETL(s, iflp->max_delay); + STREAM_GETL(s, iflp->delay_var); + + STREAM_GETF(s, iflp->pkt_loss); + STREAM_GETF(s, iflp->res_bw); + STREAM_GETF(s, iflp->ava_bw); + STREAM_GETF(s, iflp->use_bw); + + return 0; +stream_failure: + return -1; +} + +struct interface *zebra_interface_link_params_read(struct stream *s, + vrf_id_t vrf_id, + bool *changed) +{ + struct if_link_params *iflp; + struct if_link_params iflp_prev = {0}; + ifindex_t ifindex; + bool iflp_prev_set = false; + + STREAM_GETL(s, ifindex); + + struct interface *ifp = if_lookup_by_index(ifindex, vrf_id); + + if (ifp == NULL) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: unknown ifindex %u, shouldn't happen", __func__, + ifindex); + return NULL; + } + + iflp = if_link_params_get(ifp); + + if (iflp) { + iflp_prev_set = true; + admin_group_init(&iflp_prev.ext_admin_grp); + if_link_params_copy(&iflp_prev, iflp); + } + + /* read the link_params from stream + * Free ifp->link_params if the stream has no params + * to means that link-params are not enabled on links. + */ + if (link_params_set_value(s, ifp) != 0) + goto stream_failure; + + if (changed != NULL) { + iflp = if_link_params_get(ifp); + + if (iflp_prev_set && iflp) { + if (if_link_params_cmp(&iflp_prev, iflp)) + *changed = false; + else + *changed = true; + } else if (!iflp_prev_set && !iflp) + *changed = false; + else + *changed = true; + } + + if (iflp_prev_set) + admin_group_term(&iflp_prev.ext_admin_grp); + + return ifp; + +stream_failure: + if (iflp_prev_set) + admin_group_term(&iflp_prev.ext_admin_grp); + return NULL; +} + +static void zebra_interface_if_set_value(struct stream *s, + struct interface *ifp) +{ + uint8_t link_params_status = 0; + ifindex_t old_ifindex, new_ifindex; + + old_ifindex = ifp->oldifindex; + /* Read interface's index. */ + STREAM_GETL(s, new_ifindex); + if_set_index(ifp, new_ifindex); + STREAM_GETC(s, ifp->status); + + /* Read interface's value. */ + STREAM_GETQ(s, ifp->flags); + STREAM_GETC(s, ifp->ptm_enable); + STREAM_GETC(s, ifp->ptm_status); + STREAM_GETL(s, ifp->metric); + STREAM_GETL(s, ifp->speed); + STREAM_GETL(s, ifp->txqlen); + STREAM_GETL(s, ifp->mtu); + STREAM_GETL(s, ifp->mtu6); + STREAM_GETL(s, ifp->bandwidth); + STREAM_GETL(s, ifp->link_ifindex); + STREAM_GETL(s, ifp->ll_type); + STREAM_GETL(s, ifp->hw_addr_len); + if (ifp->hw_addr_len) + STREAM_GET(ifp->hw_addr, s, + MIN(ifp->hw_addr_len, INTERFACE_HWADDR_MAX)); + + /* Read Traffic Engineering status */ + link_params_status = stream_getc(s); + /* Then, Traffic Engineering parameters if any */ + if (link_params_status) + link_params_set_value(s, ifp); + + nexthop_group_interface_state_change(ifp, old_ifindex); + + return; +stream_failure: + zlog_err("Could not parse interface values; aborting"); + assert(!"Failed to parse interface values"); +} + +size_t zebra_interface_link_params_write(struct stream *s, + struct interface *ifp) +{ + size_t w, nb_ext_adm_grp; + struct if_link_params *iflp; + int i; + + + if (s == NULL || ifp == NULL) + return 0; + + iflp = ifp->link_params; + w = 0; + + /* encode if link_params is enabled */ + if (iflp) { + w += stream_putc(s, true); + } else { + w += stream_putc(s, false); + return w; + } + + w += stream_putl(s, iflp->lp_status); + + w += stream_putl(s, iflp->te_metric); + w += stream_putf(s, iflp->max_bw); + w += stream_putf(s, iflp->max_rsv_bw); + + w += stream_putl(s, MAX_CLASS_TYPE); + for (i = 0; i < MAX_CLASS_TYPE; i++) + w += stream_putf(s, iflp->unrsv_bw[i]); + + w += stream_putl(s, iflp->admin_grp); + + /* Extended Administrative Group */ + nb_ext_adm_grp = admin_group_nb_words(&iflp->ext_admin_grp); + w += stream_putc(s, nb_ext_adm_grp); + for (size_t i = 0; i < nb_ext_adm_grp; i++) + stream_putl(s, admin_group_get_offset(&iflp->ext_admin_grp, i)); + + w += stream_putl(s, iflp->rmt_as); + w += stream_put_in_addr(s, &iflp->rmt_ip); + + w += stream_putl(s, iflp->av_delay); + w += stream_putl(s, iflp->min_delay); + w += stream_putl(s, iflp->max_delay); + w += stream_putl(s, iflp->delay_var); + + w += stream_putf(s, iflp->pkt_loss); + w += stream_putf(s, iflp->res_bw); + w += stream_putf(s, iflp->ava_bw); + w += stream_putf(s, iflp->use_bw); + + return w; +} + +/* + * format of message for address addition is: + * 0 + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * | type | ZEBRA_INTERFACE_ADDRESS_ADD or + * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_ADDRES_DELETE + * | | + * + + + * | ifindex | + * + + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+ + * | ifc_flags | flags for connected address + * +-+-+-+-+-+-+-+-+ + * | addr_family | + * +-+-+-+-+-+-+-+-+ + * | addr... | + * : : + * | | + * +-+-+-+-+-+-+-+-+ + * | addr_len | len of addr. E.g., addr_len = 4 for ipv4 addrs. + * +-+-+-+-+-+-+-+-+ + * | daddr.. | + * : : + * | | + * +-+-+-+-+-+-+-+-+ + */ + +static int memconstant(const void *s, int c, size_t n) +{ + const uint8_t *p = s; + + while (n-- > 0) + if (*p++ != c) + return 0; + return 1; +} + + +struct connected *zebra_interface_address_read(int type, struct stream *s, + vrf_id_t vrf_id) +{ + ifindex_t ifindex; + struct interface *ifp; + struct connected *ifc; + struct prefix p, d, *dp; + int plen; + uint8_t ifc_flags; + + memset(&p, 0, sizeof(p)); + memset(&d, 0, sizeof(d)); + + /* Get interface index. */ + STREAM_GETL(s, ifindex); + + /* Lookup index. */ + ifp = if_lookup_by_index(ifindex, vrf_id); + if (ifp == NULL) { + flog_err(EC_LIB_ZAPI_ENCODE, + "INTERFACE_ADDRESS_%s: Cannot find IF %u in VRF %d", + (type == ZEBRA_INTERFACE_ADDRESS_ADD) ? "ADD" : "DEL", + ifindex, vrf_id); + return NULL; + } + + /* Fetch flag. */ + STREAM_GETC(s, ifc_flags); + + /* Fetch interface address. */ + STREAM_GETC(s, d.family); + p.family = d.family; + plen = prefix_blen(&d); + + if (zclient_stream_get_prefix(s, &p) != 0) + goto stream_failure; + + /* Fetch destination address. */ + STREAM_GET(&d.u.prefix, s, plen); + + /* N.B. NULL destination pointers are encoded as all zeroes */ + dp = memconstant(&d.u.prefix, 0, plen) ? NULL : &d; + + if (type == ZEBRA_INTERFACE_ADDRESS_ADD) { + ifc = connected_lookup_prefix_exact(ifp, &p); + if (!ifc) { + /* N.B. NULL destination pointers are encoded as all + * zeroes */ + ifc = connected_add_by_prefix(ifp, &p, dp); + } + if (ifc) { + ifc->flags = ifc_flags; + if (ifc->destination) + ifc->destination->prefixlen = + ifc->address->prefixlen; + else if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_PEER)) { + /* carp interfaces on OpenBSD with 0.0.0.0/0 as + * "peer" */ + flog_err( + EC_LIB_ZAPI_ENCODE, + "interface %s address %pFX with peer flag set, but no peer address!", + ifp->name, ifc->address); + UNSET_FLAG(ifc->flags, ZEBRA_IFA_PEER); + } + } + } else { + assert(type == ZEBRA_INTERFACE_ADDRESS_DELETE); + ifc = connected_delete_by_prefix(ifp, &p); + } + + return ifc; + +stream_failure: + return NULL; +} + +/* + * format of message for neighbor connected address is: + * 0 + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * | type | ZEBRA_INTERFACE_NBR_ADDRESS_ADD or + * +-+-+-+-+-+-+-+-+ ZEBRA_INTERFACE_NBR_ADDRES_DELETE + * | | + * + + + * | ifindex | + * + + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+ + * | addr_family | + * +-+-+-+-+-+-+-+-+ + * | addr... | + * : : + * | | + * +-+-+-+-+-+-+-+-+ + * | addr_len | len of addr. + * +-+-+-+-+-+-+-+-+ + */ +struct nbr_connected * +zebra_interface_nbr_address_read(int type, struct stream *s, vrf_id_t vrf_id) +{ + unsigned int ifindex; + struct interface *ifp; + struct prefix p; + struct nbr_connected *ifc; + + /* Get interface index. */ + STREAM_GETL(s, ifindex); + + /* Lookup index. */ + ifp = if_lookup_by_index(ifindex, vrf_id); + if (ifp == NULL) { + flog_err(EC_LIB_ZAPI_ENCODE, + "INTERFACE_NBR_%s: Cannot find IF %u in VRF %d", + (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) ? "ADD" + : "DELETE", + ifindex, vrf_id); + return NULL; + } + + STREAM_GETC(s, p.family); + STREAM_GET(&p.u.prefix, s, prefix_blen(&p)); + STREAM_GETC(s, p.prefixlen); + + if (type == ZEBRA_INTERFACE_NBR_ADDRESS_ADD) { + /* Currently only supporting P2P links, so any new RA source + address is + considered as the replacement of the previously learnt + Link-Local address. */ + if (!(ifc = listnode_head(ifp->nbr_connected))) { + ifc = nbr_connected_new(); + ifc->address = prefix_new(); + ifc->ifp = ifp; + listnode_add(ifp->nbr_connected, ifc); + } + + prefix_copy(ifc->address, &p); + } else { + assert(type == ZEBRA_INTERFACE_NBR_ADDRESS_DELETE); + + ifc = nbr_connected_check(ifp, &p); + if (ifc) + listnode_delete(ifp->nbr_connected, ifc); + } + + return ifc; + +stream_failure: + return NULL; +} + +/* filter unwanted messages until the expected one arrives */ +static int zclient_read_sync_response(struct zclient *zclient, + uint16_t expected_cmd) +{ + struct stream *s; + uint16_t size = -1; + uint8_t marker; + uint8_t version; + vrf_id_t vrf_id; + uint16_t cmd; + fd_set readfds; + int ret; + + ret = 0; + cmd = expected_cmd + 1; + while (ret == 0 && cmd != expected_cmd) { + s = zclient->ibuf; + stream_reset(s); + + /* wait until response arrives */ + FD_ZERO(&readfds); + FD_SET(zclient->sock, &readfds); + select(zclient->sock + 1, &readfds, NULL, NULL, NULL); + if (!FD_ISSET(zclient->sock, &readfds)) + continue; + /* read response */ + ret = zclient_read_header(s, zclient->sock, &size, &marker, + &version, &vrf_id, &cmd); + if (zclient_debug) + zlog_debug("%s: Response (%d bytes) received", __func__, + size); + } + if (ret != 0) { + flog_err(EC_LIB_ZAPI_ENCODE, "%s: Invalid Sync Message Reply", + __func__); + return -1; + } + + return 0; +} +/** + * Connect to label manager in a synchronous way + * + * It first writes the request to zclient output buffer and then + * immediately reads the answer from the input buffer. + * + * @param zclient Zclient used to connect to label manager (zebra) + * @param async Synchronous (0) or asynchronous (1) operation + * @result Result of response + */ +int lm_label_manager_connect(struct zclient *zclient, int async) +{ + int ret; + struct stream *s; + uint8_t result; + uint16_t cmd = async ? ZEBRA_LABEL_MANAGER_CONNECT_ASYNC : + ZEBRA_LABEL_MANAGER_CONNECT; + + if (zclient_debug) + zlog_debug("Connecting to Label Manager (LM)"); + + if (zclient->sock < 0) { + zlog_debug("%s: invalid zclient socket", __func__); + return -1; + } + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, cmd, VRF_DEFAULT); + + /* proto */ + stream_putc(s, zclient->redist_default); + /* instance */ + stream_putw(s, zclient->instance); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = writen(zclient->sock, s->data, stream_get_endp(s)); + if (ret < 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (ret == 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (zclient_debug) + zlog_debug("LM connect request sent (%d bytes)", ret); + + if (async) + return 0; + + /* read response */ + if (zclient_read_sync_response(zclient, cmd) + != 0) + return -1; + + s = zclient->ibuf; + + /* read instance and proto */ + uint8_t proto; + uint16_t instance; + + STREAM_GETC(s, proto); + STREAM_GETW(s, instance); + + /* sanity */ + if (proto != zclient->redist_default) + flog_err( + EC_LIB_ZAPI_ENCODE, + "Wrong proto (%u) in LM connect response. Should be %u", + proto, zclient->redist_default); + if (instance != zclient->instance) + flog_err( + EC_LIB_ZAPI_ENCODE, + "Wrong instId (%u) in LM connect response. Should be %u", + instance, zclient->instance); + + /* result code */ + STREAM_GETC(s, result); + if (zclient_debug) + zlog_debug("LM connect-response received, result %u", result); + + return (int)result; + +stream_failure: + return -1; +} + +/** + * Function to request a srv6-locator chunk in an asynchronous way + * + * @param zclient Zclient used to connect to table manager (zebra) + * @param locator_name Name of SRv6-locator + * @result 0 on success, -1 otherwise + */ +int srv6_manager_get_locator_chunk(struct zclient *zclient, + const char *locator_name) +{ + struct stream *s; + const size_t len = strlen(locator_name); + + if (zclient_debug) + zlog_debug("Getting SRv6-Locator Chunk %s", locator_name); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK, + VRF_DEFAULT); + + /* locator_name */ + stream_putw(s, len); + stream_put(s, locator_name, len); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/** + * Function to release a srv6-locator chunk + * + * @param zclient Zclient used to connect to table manager (zebra) + * @param locator_name Name of SRv6-locator + * @result 0 on success, -1 otherwise + */ +int srv6_manager_release_locator_chunk(struct zclient *zclient, + const char *locator_name) +{ + struct stream *s; + const size_t len = strlen(locator_name); + + if (zclient_debug) + zlog_debug("Releasing SRv6-Locator Chunk %s", locator_name); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_SRV6_MANAGER_RELEASE_LOCATOR_CHUNK, + VRF_DEFAULT); + + /* locator_name */ + stream_putw(s, len); + stream_put(s, locator_name, len); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Asynchronous label chunk request + * + * @param zclient Zclient used to connect to label manager (zebra) + * @param keep Avoid garbage collection + * @param chunk_size Amount of labels requested + * @param base Base for the label chunk. if MPLS_LABEL_BASE_ANY we do not care + * @result 0 on success, -1 otherwise + */ +enum zclient_send_status zclient_send_get_label_chunk(struct zclient *zclient, + uint8_t keep, + uint32_t chunk_size, + uint32_t base) +{ + struct stream *s; + + if (zclient_debug) + zlog_debug("Getting Label Chunk"); + + if (zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT); + /* proto */ + stream_putc(s, zclient->redist_default); + /* instance */ + stream_putw(s, zclient->instance); + stream_putc(s, keep); + stream_putl(s, chunk_size); + stream_putl(s, base); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/** + * Function to request a label chunk in a synchronous way + * + * It first writes the request to zclient output buffer and then + * immediately reads the answer from the input buffer. + * + * @param zclient Zclient used to connect to label manager (zebra) + * @param keep Avoid garbage collection + * @param chunk_size Amount of labels requested + * @param start To write first assigned chunk label to + * @param end To write last assigned chunk label to + * @result 0 on success, -1 otherwise + */ +int lm_get_label_chunk(struct zclient *zclient, uint8_t keep, uint32_t base, + uint32_t chunk_size, uint32_t *start, uint32_t *end) +{ + int ret; + struct stream *s; + uint8_t response_keep; + + if (zclient_debug) + zlog_debug("Getting Label Chunk"); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_GET_LABEL_CHUNK, VRF_DEFAULT); + /* proto */ + stream_putc(s, zclient->redist_default); + /* instance */ + stream_putw(s, zclient->instance); + /* keep */ + stream_putc(s, keep); + /* chunk size */ + stream_putl(s, chunk_size); + /* requested chunk base */ + stream_putl(s, base); + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = writen(zclient->sock, s->data, stream_get_endp(s)); + if (ret < 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (ret == 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock closed"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (zclient_debug) + zlog_debug("Label chunk request (%d bytes) sent", ret); + + /* read response */ + if (zclient_read_sync_response(zclient, ZEBRA_GET_LABEL_CHUNK) != 0) + return -1; + + /* parse response */ + s = zclient->ibuf; + + /* read proto and instance */ + uint8_t proto; + uint8_t instance; + + STREAM_GETC(s, proto); + STREAM_GETW(s, instance); + + /* sanities */ + if (proto != zclient->redist_default) + flog_err(EC_LIB_ZAPI_ENCODE, + "Wrong proto (%u) in get chunk response. Should be %u", + proto, zclient->redist_default); + if (instance != zclient->instance) + flog_err(EC_LIB_ZAPI_ENCODE, + "Wrong instId (%u) in get chunk response Should be %u", + instance, zclient->instance); + + /* if we requested a specific chunk and it could not be allocated, the + * response message will end here + */ + if (!STREAM_READABLE(s)) { + zlog_info("Unable to assign Label Chunk to %s instance %u", + zebra_route_string(proto), instance); + return -1; + } + + /* keep */ + STREAM_GETC(s, response_keep); + /* start and end labels */ + STREAM_GETL(s, *start); + STREAM_GETL(s, *end); + + /* not owning this response */ + if (keep != response_keep) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "Invalid Label chunk: %u - %u, keeps mismatch %u != %u", + *start, *end, keep, response_keep); + } + /* sanity */ + if (*start > *end || *start < MPLS_LABEL_UNRESERVED_MIN + || *end > MPLS_LABEL_UNRESERVED_MAX) { + flog_err(EC_LIB_ZAPI_ENCODE, "Invalid Label chunk: %u - %u", + *start, *end); + return -1; + } + + if (zclient_debug) + zlog_debug("Label Chunk assign: %u - %u (%u)", *start, *end, + response_keep); + + return 0; + +stream_failure: + return -1; +} + +/** + * Function to release a label chunk + * + * @param zclient Zclient used to connect to label manager (zebra) + * @param start First label of chunk + * @param end Last label of chunk + * @result 0 on success, -1 otherwise + */ +int lm_release_label_chunk(struct zclient *zclient, uint32_t start, + uint32_t end) +{ + int ret; + struct stream *s; + + if (zclient_debug) + zlog_debug("Releasing Label Chunk %u - %u", start, end); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_RELEASE_LABEL_CHUNK, VRF_DEFAULT); + + /* proto */ + stream_putc(s, zclient->redist_default); + /* instance */ + stream_putw(s, zclient->instance); + /* start */ + stream_putl(s, start); + /* end */ + stream_putl(s, end); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = writen(zclient->sock, s->data, stream_get_endp(s)); + if (ret < 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Can't write to zclient sock"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (ret == 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "Zclient sock connection closed"); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + + return 0; +} + +/** + * Connect to table manager in a synchronous way + * + * It first writes the request to zclient output buffer and then + * immediately reads the answer from the input buffer. + * + * @param zclient Zclient used to connect to table manager (zebra) + * @result Result of response + */ +int tm_table_manager_connect(struct zclient *zclient) +{ + int ret; + struct stream *s; + uint8_t result; + + if (zclient_debug) + zlog_debug("Connecting to Table Manager"); + + if (zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_TABLE_MANAGER_CONNECT, VRF_DEFAULT); + + /* proto */ + stream_putc(s, zclient->redist_default); + /* instance */ + stream_putw(s, zclient->instance); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = zclient_send_message(zclient); + if (ret == ZCLIENT_SEND_FAILURE) + return -1; + + if (zclient_debug) + zlog_debug("%s: Table manager connect request sent", __func__); + + /* read response */ + if (zclient_read_sync_response(zclient, ZEBRA_TABLE_MANAGER_CONNECT) + != 0) + return -1; + + /* result */ + s = zclient->ibuf; + STREAM_GETC(s, result); + if (zclient_debug) + zlog_debug( + "%s: Table Manager connect response received, result %u", + __func__, result); + + return (int)result; +stream_failure: + return -1; +} + +/** + * Function to request a table chunk in a synchronous way + * + * It first writes the request to zclient output buffer and then + * immediately reads the answer from the input buffer. + * + * @param zclient Zclient used to connect to table manager (zebra) + * @param chunk_size Amount of table requested + * @param start to write first assigned chunk table RT ID to + * @param end To write last assigned chunk table RT ID to + * @result 0 on success, -1 otherwise + */ +int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size, + uint32_t *start, uint32_t *end) +{ + int ret; + struct stream *s; + + if (zclient_debug) + zlog_debug("Getting Table Chunk"); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_GET_TABLE_CHUNK, VRF_DEFAULT); + /* chunk size */ + stream_putl(s, chunk_size); + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = writen(zclient->sock, s->data, stream_get_endp(s)); + if (ret < 0) { + flog_err(EC_LIB_ZAPI_SOCKET, "%s: can't write to zclient->sock", + __func__); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (ret == 0) { + flog_err(EC_LIB_ZAPI_SOCKET, + "%s: zclient->sock connection closed", __func__); + close(zclient->sock); + zclient->sock = -1; + return -1; + } + if (zclient_debug) + zlog_debug("%s: Table chunk request (%d bytes) sent", __func__, + ret); + + /* read response */ + if (zclient_read_sync_response(zclient, ZEBRA_GET_TABLE_CHUNK) != 0) + return -1; + + s = zclient->ibuf; + /* start and end table IDs */ + STREAM_GETL(s, *start); + STREAM_GETL(s, *end); + + if (zclient_debug) + zlog_debug("Table Chunk assign: %u - %u ", *start, *end); + + return 0; +stream_failure: + return -1; +} + +/** + * Function to release a table chunk + * + * @param zclient Zclient used to connect to table manager (zebra) + * @param start First label of table + * @param end Last label of chunk + * @result 0 on success, -1 otherwise + */ +int tm_release_table_chunk(struct zclient *zclient, uint32_t start, + uint32_t end) +{ + struct stream *s; + + if (zclient_debug) + zlog_debug("Releasing Table Chunk"); + + if (zclient->sock < 0) + return -1; + + /* send request */ + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_RELEASE_TABLE_CHUNK, VRF_DEFAULT); + + /* start */ + stream_putl(s, start); + /* end */ + stream_putl(s, end); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + if (zclient_send_message(zclient) == ZCLIENT_SEND_FAILURE) + return -1; + + return 0; +} + +enum zclient_send_status zebra_send_sr_policy(struct zclient *zclient, int cmd, + struct zapi_sr_policy *zp) +{ + if (zapi_sr_policy_encode(zclient->obuf, cmd, zp) < 0) + return ZCLIENT_SEND_FAILURE; + return zclient_send_message(zclient); +} + +int zapi_sr_policy_encode(struct stream *s, int cmd, struct zapi_sr_policy *zp) +{ + struct zapi_srte_tunnel *zt = &zp->segment_list; + + stream_reset(s); + + zclient_create_header(s, cmd, VRF_DEFAULT); + stream_putl(s, zp->color); + stream_put_ipaddr(s, &zp->endpoint); + stream_write(s, &zp->name, SRTE_POLICY_NAME_MAX_LENGTH); + + stream_putc(s, zt->type); + stream_putl(s, zt->local_label); + + if (zt->label_num > MPLS_MAX_LABELS) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: label %u: can't encode %u labels (maximum is %u)", + __func__, zt->local_label, zt->label_num, + MPLS_MAX_LABELS); + return -1; + } + stream_putw(s, zt->label_num); + + for (int i = 0; i < zt->label_num; i++) + stream_putl(s, zt->labels[i]); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +int zapi_sr_policy_decode(struct stream *s, struct zapi_sr_policy *zp) +{ + memset(zp, 0, sizeof(*zp)); + + struct zapi_srte_tunnel *zt = &zp->segment_list; + + STREAM_GETL(s, zp->color); + STREAM_GET_IPADDR(s, &zp->endpoint); + STREAM_GET(&zp->name, s, SRTE_POLICY_NAME_MAX_LENGTH); + + /* segment list of active candidate path */ + STREAM_GETC(s, zt->type); + STREAM_GETL(s, zt->local_label); + STREAM_GETW(s, zt->label_num); + if (zt->label_num > MPLS_MAX_LABELS) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: label %u: can't decode %u labels (maximum is %u)", + __func__, zt->local_label, zt->label_num, + MPLS_MAX_LABELS); + return -1; + } + for (int i = 0; i < zt->label_num; i++) + STREAM_GETL(s, zt->labels[i]); + + return 0; + +stream_failure: + return -1; +} + +int zapi_sr_policy_notify_status_decode(struct stream *s, + struct zapi_sr_policy *zp) +{ + memset(zp, 0, sizeof(*zp)); + + STREAM_GETL(s, zp->color); + STREAM_GET_IPADDR(s, &zp->endpoint); + STREAM_GET(&zp->name, s, SRTE_POLICY_NAME_MAX_LENGTH); + STREAM_GETL(s, zp->status); + + return 0; + +stream_failure: + return -1; +} + +enum zclient_send_status zebra_send_mpls_labels(struct zclient *zclient, + int cmd, struct zapi_labels *zl) +{ + if (zapi_labels_encode(zclient->obuf, cmd, zl) < 0) + return ZCLIENT_SEND_FAILURE; + return zclient_send_message(zclient); +} + +int zapi_labels_encode(struct stream *s, int cmd, struct zapi_labels *zl) +{ + struct zapi_nexthop *znh; + + stream_reset(s); + + zclient_create_header(s, cmd, VRF_DEFAULT); + stream_putc(s, zl->message); + stream_putc(s, zl->type); + stream_putl(s, zl->local_label); + + if (CHECK_FLAG(zl->message, ZAPI_LABELS_FTN)) { + stream_putw(s, zl->route.prefix.family); + stream_put_prefix(s, &zl->route.prefix); + stream_putc(s, zl->route.type); + stream_putw(s, zl->route.instance); + } + + if (zl->nexthop_num > MULTIPATH_NUM) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: label %u: can't encode %u nexthops (maximum is %u)", + __func__, zl->local_label, zl->nexthop_num, + MULTIPATH_NUM); + return -1; + } + stream_putw(s, zl->nexthop_num); + + for (int i = 0; i < zl->nexthop_num; i++) { + znh = &zl->nexthops[i]; + + if (zapi_nexthop_encode(s, znh, 0, 0) < 0) + return -1; + } + + if (CHECK_FLAG(zl->message, ZAPI_LABELS_HAS_BACKUPS)) { + + if (zl->backup_nexthop_num > MULTIPATH_NUM) { + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: label %u: can't encode %u nexthops (maximum is %u)", + __func__, zl->local_label, zl->nexthop_num, + MULTIPATH_NUM); + return -1; + } + stream_putw(s, zl->backup_nexthop_num); + + for (int i = 0; i < zl->backup_nexthop_num; i++) { + znh = &zl->backup_nexthops[i]; + + if (zapi_nexthop_encode(s, znh, 0, 0) < 0) + return -1; + } + + } + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return 0; +} + +int zapi_labels_decode(struct stream *s, struct zapi_labels *zl) +{ + struct zapi_nexthop *znh; + + memset(zl, 0, sizeof(*zl)); + + /* Get data. */ + STREAM_GETC(s, zl->message); + STREAM_GETC(s, zl->type); + STREAM_GETL(s, zl->local_label); + + if (CHECK_FLAG(zl->message, ZAPI_LABELS_FTN)) { + size_t psize; + + STREAM_GETW(s, zl->route.prefix.family); + STREAM_GETC(s, zl->route.prefix.prefixlen); + + psize = PSIZE(zl->route.prefix.prefixlen); + switch (zl->route.prefix.family) { + case AF_INET: + if (zl->route.prefix.prefixlen > IPV4_MAX_BITLEN) { + zlog_debug( + "%s: Specified prefix length %d is greater than a v4 address can support", + __func__, zl->route.prefix.prefixlen); + return -1; + } + STREAM_GET(&zl->route.prefix.u.prefix4.s_addr, s, + psize); + break; + case AF_INET6: + if (zl->route.prefix.prefixlen > IPV6_MAX_BITLEN) { + zlog_debug( + "%s: Specified prefix length %d is greater than a v6 address can support", + __func__, zl->route.prefix.prefixlen); + return -1; + } + STREAM_GET(&zl->route.prefix.u.prefix6, s, psize); + break; + default: + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Specified family %u is not v4 or v6", + __func__, zl->route.prefix.family); + return -1; + } + + STREAM_GETC(s, zl->route.type); + STREAM_GETW(s, zl->route.instance); + } + + STREAM_GETW(s, zl->nexthop_num); + + if (zl->nexthop_num > MULTIPATH_NUM) { + flog_warn( + EC_LIB_ZAPI_ENCODE, + "%s: Prefix %pFX has %d nexthops, but we can only use the first %d", + __func__, &zl->route.prefix, zl->nexthop_num, + MULTIPATH_NUM); + } + + zl->nexthop_num = MIN(MULTIPATH_NUM, zl->nexthop_num); + + for (int i = 0; i < zl->nexthop_num; i++) { + znh = &zl->nexthops[i]; + + if (zapi_nexthop_decode(s, znh, 0, 0) < 0) + return -1; + + if (znh->type == NEXTHOP_TYPE_BLACKHOLE) { + flog_warn( + EC_LIB_ZAPI_ENCODE, + "%s: Prefix %pFX has a blackhole nexthop which we cannot use for a label", + __func__, &zl->route.prefix); + return -1; + } + } + + if (CHECK_FLAG(zl->message, ZAPI_LABELS_HAS_BACKUPS)) { + STREAM_GETW(s, zl->backup_nexthop_num); + + if (zl->backup_nexthop_num > MULTIPATH_NUM) { + flog_warn( + EC_LIB_ZAPI_ENCODE, + "%s: Prefix %pFX has %d backup nexthops, but we can only use the first %d", + __func__, &zl->route.prefix, + zl->backup_nexthop_num, MULTIPATH_NUM); + } + + zl->backup_nexthop_num = MIN(MULTIPATH_NUM, + zl->backup_nexthop_num); + + for (int i = 0; i < zl->backup_nexthop_num; i++) { + znh = &zl->backup_nexthops[i]; + + if (zapi_nexthop_decode(s, znh, 0, 0) < 0) + return -1; + + if (znh->type == NEXTHOP_TYPE_BLACKHOLE) { + flog_warn( + EC_LIB_ZAPI_ENCODE, + "%s: Prefix %pFX has a backup blackhole nexthop which we cannot use for a label", + __func__, &zl->route.prefix); + return -1; + } + } + } + + return 0; +stream_failure: + return -1; +} + +enum zclient_send_status zebra_send_pw(struct zclient *zclient, int command, + struct zapi_pw *pw) +{ + struct stream *s; + + /* Reset stream. */ + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, command, VRF_DEFAULT); + stream_write(s, pw->ifname, IFNAMSIZ); + stream_putl(s, pw->ifindex); + + /* Put type */ + stream_putl(s, pw->type); + + /* Put nexthop */ + stream_putl(s, pw->af); + switch (pw->af) { + case AF_INET: + stream_put_in_addr(s, &pw->nexthop.ipv4); + break; + case AF_INET6: + stream_write(s, (uint8_t *)&pw->nexthop.ipv6, 16); + break; + default: + flog_err(EC_LIB_ZAPI_ENCODE, "%s: unknown af", __func__); + return ZCLIENT_SEND_FAILURE; + } + + /* Put labels */ + stream_putl(s, pw->local_label); + stream_putl(s, pw->remote_label); + + /* Put flags */ + stream_putc(s, pw->flags); + + /* Protocol specific fields */ + stream_write(s, &pw->data, sizeof(union pw_protocol_fields)); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Receive PW status update from Zebra and send it to LDE process. + */ +int zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS, struct zapi_pw_status *pw) +{ + struct stream *s; + + memset(pw, 0, sizeof(struct zapi_pw_status)); + s = zclient->ibuf; + + /* Get data. */ + stream_get(pw->ifname, s, IFNAMSIZ); + STREAM_GETL(s, pw->ifindex); + STREAM_GETL(s, pw->status); + + return 0; +stream_failure: + return -1; +} + +static int zclient_capability_decode(ZAPI_CALLBACK_ARGS) +{ + struct zclient_capabilities cap; + struct stream *s = zclient->ibuf; + int vrf_backend; + uint8_t mpls_enabled; + + STREAM_GETL(s, vrf_backend); + + if (vrf_backend < 0 || vrf_configure_backend(vrf_backend)) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: Garbage VRF backend type: %d", __func__, + vrf_backend); + goto stream_failure; + } + + + memset(&cap, 0, sizeof(cap)); + STREAM_GETC(s, mpls_enabled); + cap.mpls_enabled = !!mpls_enabled; + STREAM_GETL(s, cap.ecmp); + STREAM_GETC(s, cap.role); + STREAM_GETC(s, cap.v6_with_v4_nexthop); + + if (zclient->zebra_capabilities) + (*zclient->zebra_capabilities)(&cap); + +stream_failure: + return 0; +} + +enum zclient_send_status zclient_send_mlag_register(struct zclient *client, + uint32_t bit_map) +{ + struct stream *s; + + s = client->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_MLAG_CLIENT_REGISTER, VRF_DEFAULT); + stream_putl(s, bit_map); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(client); +} + +enum zclient_send_status zclient_send_mlag_deregister(struct zclient *client) +{ + return zebra_message_send(client, ZEBRA_MLAG_CLIENT_UNREGISTER, + VRF_DEFAULT); +} + +enum zclient_send_status zclient_send_mlag_data(struct zclient *client, + struct stream *client_s) +{ + struct stream *s; + + s = client->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_MLAG_FORWARD_MSG, VRF_DEFAULT); + stream_put(s, client_s->data, client_s->endp); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(client); +} + +/* + * Init/header setup for opaque zapi messages + */ +enum zclient_send_status zapi_opaque_init(struct zclient *zclient, + uint32_t type, uint16_t flags) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_OPAQUE_MESSAGE, VRF_DEFAULT); + + /* Send sub-type and flags */ + stream_putl(s, type); + stream_putw(s, flags); + + /* Source daemon identifiers */ + stream_putc(s, zclient->redist_default); + stream_putw(s, zclient->instance); + stream_putl(s, zclient->session_id); + + return ZCLIENT_SEND_SUCCESS; +} + +/* + * Init, header setup for opaque unicast messages. + */ +enum zclient_send_status +zapi_opaque_unicast_init(struct zclient *zclient, uint32_t type, uint16_t flags, + uint8_t proto, uint16_t instance, uint32_t session_id) +{ + struct stream *s; + + s = zclient->obuf; + + /* Common init */ + zapi_opaque_init(zclient, type, flags | ZAPI_OPAQUE_FLAG_UNICAST); + + /* Send destination client info */ + stream_putc(s, proto); + stream_putw(s, instance); + stream_putl(s, session_id); + + return ZCLIENT_SEND_SUCCESS; +} + +/* + * Send an OPAQUE message, contents opaque to zebra. The message header + * is a message subtype. + */ +enum zclient_send_status zclient_send_opaque(struct zclient *zclient, + uint32_t type, const uint8_t *data, + size_t datasize) +{ + struct stream *s; + uint16_t flags = 0; + + /* Check buffer size */ + if (STREAM_SIZE(zclient->obuf) < + (ZEBRA_HEADER_SIZE + sizeof(type) + datasize)) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + + zapi_opaque_init(zclient, type, flags); + + /* Send opaque data */ + if (datasize > 0) + stream_write(s, data, datasize); + + /* Put length into the header at the start of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Send an OPAQUE message to a specific zclient. The contents are opaque + * to zebra. + */ +enum zclient_send_status +zclient_send_opaque_unicast(struct zclient *zclient, uint32_t type, + uint8_t proto, uint16_t instance, + uint32_t session_id, const uint8_t *data, + size_t datasize) +{ + struct stream *s; + uint16_t flags = 0; + + /* Check buffer size */ + if (STREAM_SIZE(zclient->obuf) < + (ZEBRA_HEADER_SIZE + sizeof(struct zapi_opaque_msg) + datasize)) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + + /* Common init */ + zapi_opaque_unicast_init(zclient, type, flags, proto, instance, + session_id); + + /* Send opaque data */ + if (datasize > 0) + stream_write(s, data, datasize); + + /* Put length into the header at the start of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Decode incoming opaque message into info struct + */ +int zclient_opaque_decode(struct stream *s, struct zapi_opaque_msg *info) +{ + memset(info, 0, sizeof(*info)); + + /* Decode subtype and flags */ + STREAM_GETL(s, info->type); + STREAM_GETW(s, info->flags); + + /* Decode sending daemon info */ + STREAM_GETC(s, info->src_proto); + STREAM_GETW(s, info->src_instance); + STREAM_GETL(s, info->src_session_id); + + /* Decode unicast destination info, if present */ + if (CHECK_FLAG(info->flags, ZAPI_OPAQUE_FLAG_UNICAST)) { + STREAM_GETC(s, info->dest_proto); + STREAM_GETW(s, info->dest_instance); + STREAM_GETL(s, info->dest_session_id); + } + + info->len = STREAM_READABLE(s); + + return 0; + +stream_failure: + + return -1; +} + +/* + * Send a registration request for opaque messages with a specified subtype. + */ +enum zclient_send_status zclient_register_opaque(struct zclient *zclient, + uint32_t type) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_OPAQUE_REGISTER, VRF_DEFAULT); + + /* Send sub-type */ + stream_putl(s, type); + + /* Add zclient info */ + stream_putc(s, zclient->redist_default); + stream_putw(s, zclient->instance); + stream_putl(s, zclient->session_id); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Send an un-registration request for a specified opaque subtype. + */ +enum zclient_send_status zclient_unregister_opaque(struct zclient *zclient, + uint32_t type) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_OPAQUE_UNREGISTER, VRF_DEFAULT); + + /* Send sub-type */ + stream_putl(s, type); + + /* Add zclient info */ + stream_putc(s, zclient->redist_default); + stream_putw(s, zclient->instance); + stream_putl(s, zclient->session_id); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* Utility to decode opaque registration info */ +int zapi_opaque_reg_decode(struct stream *s, struct zapi_opaque_reg_info *info) +{ + STREAM_GETL(s, info->type); + STREAM_GETC(s, info->proto); + STREAM_GETW(s, info->instance); + STREAM_GETL(s, info->session_id); + + return 0; + +stream_failure: + + return -1; +} + +/* Utility to decode client close notify info */ +int zapi_client_close_notify_decode(struct stream *s, + struct zapi_client_close_info *info) +{ + memset(info, 0, sizeof(*info)); + + STREAM_GETC(s, info->proto); + STREAM_GETW(s, info->instance); + STREAM_GETL(s, info->session_id); + + return 0; + +stream_failure: + + return -1; +} + +static int zclient_nexthop_update(ZAPI_CALLBACK_ARGS) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct prefix match; + struct zapi_route route; + + if (!vrf) { + zlog_warn("nexthop update for unknown VRF ID %u", vrf_id); + return 0; + } + + if (!zapi_nexthop_update_decode(zclient->ibuf, &match, &route)) { + zlog_err("failed to decode nexthop update"); + return -1; + } + + if (zclient->nexthop_update) + zclient->nexthop_update(vrf, &match, &route); + + return 0; +} + +static zclient_handler *const lib_handlers[] = { + /* fundamentals */ + [ZEBRA_CAPABILITIES] = zclient_capability_decode, + [ZEBRA_ERROR] = zclient_handle_error, + + /* VRF & interface code is shared in lib */ + [ZEBRA_VRF_ADD] = zclient_vrf_add, + [ZEBRA_VRF_DELETE] = zclient_vrf_delete, + [ZEBRA_INTERFACE_ADD] = zclient_interface_add, + [ZEBRA_INTERFACE_DELETE] = zclient_interface_delete, + [ZEBRA_INTERFACE_UP] = zclient_interface_up, + [ZEBRA_INTERFACE_DOWN] = zclient_interface_down, + + /* NHT pre-decode */ + [ZEBRA_NEXTHOP_UPDATE] = zclient_nexthop_update, + + /* BFD */ + [ZEBRA_BFD_DEST_REPLAY] = zclient_bfd_session_replay, + [ZEBRA_INTERFACE_BFD_DEST_UPDATE] = zclient_bfd_session_update, +}; + +/* Zebra client message read function. */ +static void zclient_read(struct event *thread) +{ + size_t already; + uint16_t length, command; + uint8_t marker, version; + vrf_id_t vrf_id; + struct zclient *zclient; + + /* Get socket to zebra. */ + zclient = EVENT_ARG(thread); + zclient->t_read = NULL; + + /* Read zebra header (if we don't have it already). */ + already = stream_get_endp(zclient->ibuf); + if (already < ZEBRA_HEADER_SIZE) { + ssize_t nbyte; + if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock, + ZEBRA_HEADER_SIZE - already)) + == 0) + || (nbyte == -1)) { + if (zclient_debug) + zlog_debug( + "zclient connection closed socket [%d].", + zclient->sock); + zclient_failed(zclient); + return; + } + if (nbyte != (ssize_t)(ZEBRA_HEADER_SIZE - already)) { + zclient_event(ZCLIENT_READ, zclient); + return; + } + already = ZEBRA_HEADER_SIZE; + } + + /* Reset to read from the beginning of the incoming packet. */ + stream_set_getp(zclient->ibuf, 0); + + /* Fetch header values. */ + length = stream_getw(zclient->ibuf); + marker = stream_getc(zclient->ibuf); + version = stream_getc(zclient->ibuf); + vrf_id = stream_getl(zclient->ibuf); + command = stream_getw(zclient->ibuf); + + if (marker != ZEBRA_HEADER_MARKER || version != ZSERV_VERSION) { + flog_err( + EC_LIB_ZAPI_MISSMATCH, + "%s: socket %d version mismatch, marker %d, version %d", + __func__, zclient->sock, marker, version); + zclient_failed(zclient); + return; + } + + if (length < ZEBRA_HEADER_SIZE) { + flog_err(EC_LIB_ZAPI_MISSMATCH, + "%s: socket %d message length %u is less than %d ", + __func__, zclient->sock, length, ZEBRA_HEADER_SIZE); + zclient_failed(zclient); + return; + } + + /* Length check. */ + if (length > STREAM_SIZE(zclient->ibuf)) { + struct stream *ns; + flog_err( + EC_LIB_ZAPI_ENCODE, + "%s: message size %u exceeds buffer size %lu, expanding...", + __func__, length, + (unsigned long)STREAM_SIZE(zclient->ibuf)); + ns = stream_new(length); + stream_copy(ns, zclient->ibuf); + stream_free(zclient->ibuf); + zclient->ibuf = ns; + } + + /* Read rest of zebra packet. */ + if (already < length) { + ssize_t nbyte; + if (((nbyte = stream_read_try(zclient->ibuf, zclient->sock, + length - already)) + == 0) + || (nbyte == -1)) { + if (zclient_debug) + zlog_debug( + "zclient connection closed socket [%d].", + zclient->sock); + zclient_failed(zclient); + return; + } + if (nbyte != (ssize_t)(length - already)) { + /* Try again later. */ + zclient_event(ZCLIENT_READ, zclient); + return; + } + } + + length -= ZEBRA_HEADER_SIZE; + + if (zclient_debug) + zlog_debug("zclient %p command %s VRF %u", zclient, + zserv_command_string(command), vrf_id); + + if (!zclient->auxiliary && command < array_size(lib_handlers) && + lib_handlers[command]) + lib_handlers[command](command, zclient, length, vrf_id); + if (command < zclient->n_handlers && zclient->handlers[command]) + zclient->handlers[command](command, zclient, length, vrf_id); + + if (zclient->sock < 0) + /* Connection was closed during packet processing. */ + return; + + /* Register read thread. */ + stream_reset(zclient->ibuf); + zclient_event(ZCLIENT_READ, zclient); +} + +void zclient_redistribute(int command, struct zclient *zclient, afi_t afi, + int type, unsigned short instance, vrf_id_t vrf_id) +{ + + if (instance) { + if (command == ZEBRA_REDISTRIBUTE_ADD) { + if (redist_check_instance( + &zclient->mi_redist[afi][type], instance)) + return; + redist_add_instance(&zclient->mi_redist[afi][type], + instance); + } else { + if (!redist_check_instance( + &zclient->mi_redist[afi][type], instance)) + return; + redist_del_instance(&zclient->mi_redist[afi][type], + instance); + } + + } else { + if (command == ZEBRA_REDISTRIBUTE_ADD) { + if (vrf_bitmap_check(&zclient->redist[afi][type], + vrf_id)) + return; + vrf_bitmap_set(&zclient->redist[afi][type], vrf_id); + } else { + if (!vrf_bitmap_check(&zclient->redist[afi][type], + vrf_id)) + return; + vrf_bitmap_unset(&zclient->redist[afi][type], vrf_id); + } + } + + if (zclient->sock > 0) + zebra_redistribute_send(command, zclient, afi, type, instance, + vrf_id); +} + + +void zclient_redistribute_default(int command, struct zclient *zclient, + afi_t afi, vrf_id_t vrf_id) +{ + + if (command == ZEBRA_REDISTRIBUTE_DEFAULT_ADD) { + if (vrf_bitmap_check(&zclient->default_information[afi], + vrf_id)) + return; + vrf_bitmap_set(&zclient->default_information[afi], vrf_id); + } else { + if (!vrf_bitmap_check(&zclient->default_information[afi], + vrf_id)) + return; + vrf_bitmap_unset(&zclient->default_information[afi], vrf_id); + } + + if (zclient->sock > 0) + zebra_redistribute_default_send(command, zclient, afi, vrf_id); +} + +static void zclient_event(enum zclient_event event, struct zclient *zclient) +{ + switch (event) { + case ZCLIENT_SCHEDULE: + event_add_event(zclient->master, zclient_connect, zclient, 0, + &zclient->t_connect); + break; + case ZCLIENT_CONNECT: + if (zclient_debug) + zlog_debug( + "zclient connect failures: %d schedule interval is now %d", + zclient->fail, zclient->fail < 3 ? 10 : 60); + event_add_timer(zclient->master, zclient_connect, zclient, + zclient->fail < 3 ? 10 : 60, + &zclient->t_connect); + break; + case ZCLIENT_READ: + zclient->t_read = NULL; + event_add_read(zclient->master, zclient_read, zclient, + zclient->sock, &zclient->t_read); + break; + } +} + +enum zclient_send_status zclient_interface_set_arp(struct zclient *client, + struct interface *ifp, + bool arp_enable) +{ + struct stream *s; + + s = client->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_INTERFACE_SET_ARP, ifp->vrf->vrf_id); + + stream_putl(s, ifp->ifindex); + stream_putc(s, arp_enable); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(client); +} + +enum zclient_send_status zclient_interface_set_master(struct zclient *client, + struct interface *master, + struct interface *slave) +{ + struct stream *s; + + s = client->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_INTERFACE_SET_MASTER, + master->vrf->vrf_id); + + stream_putl(s, master->vrf->vrf_id); + stream_putl(s, master->ifindex); + stream_putl(s, slave->vrf->vrf_id); + stream_putl(s, slave->ifindex); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(client); +} + +/* + * Send capabilities message to zebra + */ +enum zclient_send_status zclient_capabilities_send(uint32_t cmd, + struct zclient *zclient, + struct zapi_cap *api) +{ + + struct stream *s; + + if (zclient == NULL) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, cmd, 0); + stream_putl(s, api->cap); + + switch (api->cap) { + case ZEBRA_CLIENT_GR_CAPABILITIES: + case ZEBRA_CLIENT_RIB_STALE_TIME: + stream_putl(s, api->stale_removal_time); + stream_putl(s, api->vrf_id); + break; + case ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE: + case ZEBRA_CLIENT_ROUTE_UPDATE_PENDING: + stream_putl(s, api->afi); + stream_putl(s, api->safi); + stream_putl(s, api->vrf_id); + break; + case ZEBRA_CLIENT_GR_DISABLE: + stream_putl(s, api->vrf_id); + break; + } + + /* Put length at the first point of the stream */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return zclient_send_message(zclient); +} + +/* + * Process capabilities message from zebra + */ +int32_t zapi_capabilities_decode(struct stream *s, struct zapi_cap *api) +{ + + memset(api, 0, sizeof(*api)); + + api->safi = SAFI_UNICAST; + + STREAM_GETL(s, api->cap); + switch (api->cap) { + case ZEBRA_CLIENT_GR_CAPABILITIES: + case ZEBRA_CLIENT_RIB_STALE_TIME: + STREAM_GETL(s, api->stale_removal_time); + STREAM_GETL(s, api->vrf_id); + break; + case ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE: + case ZEBRA_CLIENT_ROUTE_UPDATE_PENDING: + STREAM_GETL(s, api->afi); + STREAM_GETL(s, api->safi); + STREAM_GETL(s, api->vrf_id); + break; + case ZEBRA_CLIENT_GR_DISABLE: + STREAM_GETL(s, api->vrf_id); + break; + } +stream_failure: + return 0; +} + +enum zclient_send_status +zclient_send_neigh_discovery_req(struct zclient *zclient, + const struct interface *ifp, + const struct prefix *p) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_NEIGH_DISCOVER, ifp->vrf->vrf_id); + stream_putl(s, ifp->ifindex); + + stream_putc(s, p->family); + stream_putc(s, p->prefixlen); + stream_put(s, &p->u.prefix, prefix_blen(p)); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); +} + +/* + * Get a starting nhg point for a routing protocol + */ +uint32_t zclient_get_nhg_start(uint32_t proto) +{ + assert(proto < ZEBRA_ROUTE_MAX); + + return ZEBRA_NHG_PROTO_SPACING * proto; +} + +char *zclient_dump_route_flags(uint32_t flags, char *buf, size_t len) +{ + if (flags == 0) { + snprintfrr(buf, len, "None "); + return buf; + } + + snprintfrr(buf, len, "%s%s%s%s%s%s%s%s%s%s%s", + CHECK_FLAG(flags, ZEBRA_FLAG_ALLOW_RECURSION) ? "Recursion " + : "", + + CHECK_FLAG(flags, ZEBRA_FLAG_SELFROUTE) ? "Self " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_IBGP) ? "iBGP " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_SELECTED) ? "Selected " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_FIB_OVERRIDE) ? "Override " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_EVPN_ROUTE) ? "Evpn " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_RR_USE_DISTANCE) ? "RR Distance " + : "", + + CHECK_FLAG(flags, ZEBRA_FLAG_TRAPPED) ? "Trapped " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOADED) ? "Offloaded " : "", + CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOAD_FAILED) + ? "Offload Failed " + : "", + CHECK_FLAG(flags, ZEBRA_FLAG_OUTOFSYNC) ? "OutOfSync " : ""); + + return buf; +} + +char *zclient_evpn_dump_macip_flags(uint8_t flags, char *buf, size_t len) +{ + if (flags == 0) { + snprintfrr(buf, len, "None "); + return buf; + } + + snprintfrr( + buf, len, "%s%s%s%s%s%s%s", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_STICKY) ? "Sticky MAC " : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_GW) ? "Gateway MAC " : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_ROUTER_FLAG) ? "Router " + : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_OVERRIDE_FLAG) ? "Override " + : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SVI_IP) ? "SVI MAC " : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_PROXY_ADVERT) ? "Proxy " + : "", + CHECK_FLAG(flags, ZEBRA_MACIP_TYPE_SYNC_PATH) ? "Sync " : ""); + + return buf; +} + +static int zclient_neigh_ip_read_entry(struct stream *s, struct ipaddr *add) +{ + uint8_t family; + + STREAM_GETC(s, family); + if (family != AF_INET && family != AF_INET6) + return -1; + + STREAM_GET(&add->ip.addr, s, family2addrsize(family)); + add->ipa_type = family; + return 0; + stream_failure: + return -1; +} + +int zclient_neigh_ip_encode(struct stream *s, uint16_t cmd, union sockunion *in, + union sockunion *out, struct interface *ifp, + int ndm_state, int ip_len) +{ + int ret = 0; + + zclient_create_header(s, cmd, ifp->vrf->vrf_id); + stream_putc(s, sockunion_family(in)); + stream_write(s, sockunion_get_addr(in), sockunion_get_addrlen(in)); + if (out && sockunion_family(out) != AF_UNSPEC) { + stream_putc(s, sockunion_family(out)); + stream_write(s, sockunion_get_addr(out), + sockunion_get_addrlen(out)); + } else + stream_putc(s, AF_UNSPEC); + stream_putl(s, ip_len); + stream_putl(s, ifp->ifindex); + if (out) + stream_putl(s, ndm_state); + else + stream_putl(s, ZEBRA_NEIGH_STATE_FAILED); + return ret; +} + +int zclient_neigh_ip_decode(struct stream *s, struct zapi_neigh_ip *api) +{ + int ret; + + ret = zclient_neigh_ip_read_entry(s, &api->ip_in); + if (ret < 0) + return -1; + zclient_neigh_ip_read_entry(s, &api->ip_out); + + STREAM_GETL(s, api->ip_len); + STREAM_GETL(s, api->index); + STREAM_GETL(s, api->ndm_state); + return 0; + stream_failure: + return -1; +} + +int zclient_send_zebra_gre_request(struct zclient *client, + struct interface *ifp) +{ + struct stream *s; + + if (!client || client->sock < 0) { + zlog_err("%s : zclient not ready", __func__); + return -1; + } + s = client->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_GRE_GET, ifp->vrf->vrf_id); + stream_putl(s, ifp->ifindex); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(client); + return 0; +} + + +/* + * Opaque notification features + */ + +/* + * Common encode helper for opaque notifications, both registration + * and async notification messages. + */ +static int opaque_notif_encode_common(struct stream *s, uint32_t msg_type, + bool request, bool reg, uint8_t proto, + uint16_t instance, uint32_t session_id) +{ + int ret = 0; + uint8_t val = 0; + + stream_reset(s); + + zclient_create_header(s, ZEBRA_OPAQUE_NOTIFY, VRF_DEFAULT); + + /* Notification or request */ + if (request) + val = 1; + stream_putc(s, val); + + if (reg) + val = 1; + else + val = 0; + stream_putc(s, val); + + stream_putl(s, msg_type); + + stream_putc(s, proto); + stream_putw(s, instance); + stream_putl(s, session_id); + + /* And capture message length */ + stream_putw_at(s, 0, stream_get_endp(s)); + + return ret; +} + +/* + * Encode a zapi opaque message type notification into buffer 's' + */ +int zclient_opaque_notif_encode(struct stream *s, uint32_t msg_type, bool reg, + uint8_t proto, uint16_t instance, + uint32_t session_id) +{ + return opaque_notif_encode_common(s, msg_type, false /* !request */, + reg, proto, instance, session_id); +} + +/* + * Decode an incoming zapi opaque message type notification + */ +int zclient_opaque_notif_decode(struct stream *s, + struct zapi_opaque_notif_info *info) +{ + uint8_t val; + + memset(info, 0, sizeof(*info)); + + STREAM_GETC(s, val); /* Registration or notification */ + info->request = (val != 0); + + STREAM_GETC(s, val); + info->reg = (val != 0); + + STREAM_GETL(s, info->msg_type); + + STREAM_GETC(s, info->proto); + STREAM_GETW(s, info->instance); + STREAM_GETL(s, info->session_id); + + return 0; + +stream_failure: + return -1; +} + +/* + * Encode and send a zapi opaque message type notification request to zebra + */ +enum zclient_send_status zclient_opaque_request_notify(struct zclient *zclient, + uint32_t msgtype) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + + opaque_notif_encode_common(s, msgtype, true /* request */, + true /* register */, zclient->redist_default, + zclient->instance, zclient->session_id); + + return zclient_send_message(zclient); +} + +/* + * Encode and send a request to drop notifications for an opaque message type. + */ +enum zclient_send_status zclient_opaque_drop_notify(struct zclient *zclient, + uint32_t msgtype) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return ZCLIENT_SEND_FAILURE; + + s = zclient->obuf; + + opaque_notif_encode_common(s, msgtype, true /* req */, + false /* unreg */, zclient->redist_default, + zclient->instance, zclient->session_id); + + return zclient_send_message(zclient); +} + +void zclient_register_neigh(struct zclient *zclient, vrf_id_t vrf_id, afi_t afi, + bool reg) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + reg ? ZEBRA_NEIGH_REGISTER + : ZEBRA_NEIGH_UNREGISTER, + vrf_id); + stream_putw(s, afi); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} diff --git a/lib/zclient.h b/lib/zclient.h new file mode 100644 index 0000000..3759f94 --- /dev/null +++ b/lib/zclient.h @@ -0,0 +1,1355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Zebra's client header. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_ZCLIENT_H +#define _ZEBRA_ZCLIENT_H + +struct zclient; + +/* For struct zapi_route. */ +#include "prefix.h" +#include "ipaddr.h" + +/* For struct interface and struct connected. */ +#include "if.h" + +/* For vrf_bitmap_t. */ +#include "vrf.h" + +/* For union g_addr */ +#include "nexthop.h" +/* For resilience */ +#include "nexthop_group.h" + +/* For union pw_protocol_fields */ +#include "pw.h" + +#include "mlag.h" +#include "srte.h" +#include "srv6.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Zebra types. Used in Zserv message header. */ +typedef uint16_t zebra_size_t; + +/* Marker value used in new Zserv, in the byte location corresponding + * the command value in the old zserv header. To allow old and new + * Zserv headers to be distinguished from each other. + */ +#define ZEBRA_HEADER_MARKER 254 + +/* For input/output buffer to zebra. */ +#define ZEBRA_MAX_PACKET_SIZ 16384U +#define ZEBRA_SMALL_PACKET_SIZE 200U + +/* Zebra header size. */ +#define ZEBRA_HEADER_SIZE 10 + +/* special socket path name to use TCP + * @ is used as first character because that's abstract socket names on Linux + */ +#define ZAPI_TCP_PATHNAME "@tcp" + +/* IPset size name stands for the name of the ipset entry + * that can be created by using some zapi interfaces + */ +#define ZEBRA_IPSET_NAME_SIZE 32 + +/* IPTable action is defined by two values: either + * forward or drop + */ +#define ZEBRA_IPTABLES_FORWARD 0 +#define ZEBRA_IPTABLES_DROP 1 + +/* Zebra FEC register command flags. */ +#define ZEBRA_FEC_REGISTER_LABEL 0x1 +#define ZEBRA_FEC_REGISTER_LABEL_INDEX 0x2 + +/* Client capabilities */ +enum zserv_client_capabilities { + ZEBRA_CLIENT_GR_CAPABILITIES = 1, + ZEBRA_CLIENT_ROUTE_UPDATE_COMPLETE = 2, + ZEBRA_CLIENT_ROUTE_UPDATE_PENDING = 3, + ZEBRA_CLIENT_GR_DISABLE = 4, + ZEBRA_CLIENT_RIB_STALE_TIME +}; + +/* Macro to check if there GR enabled. */ +#define ZEBRA_CLIENT_GR_ENABLED(X) (X == ZEBRA_CLIENT_GR_CAPABILITIES) + +#define ZEBRA_SR_POLICY_NAME_MAX_LENGTH 100 + +extern struct sockaddr_storage zclient_addr; +extern socklen_t zclient_addr_len; + +/* Zebra message types. Please update the corresponding + * command_types array with any changes! + */ +typedef enum { + ZEBRA_INTERFACE_ADD, + ZEBRA_INTERFACE_DELETE, + ZEBRA_INTERFACE_ADDRESS_ADD, + ZEBRA_INTERFACE_ADDRESS_DELETE, + ZEBRA_INTERFACE_UP, + ZEBRA_INTERFACE_DOWN, + ZEBRA_INTERFACE_SET_MASTER, + ZEBRA_INTERFACE_SET_ARP, + ZEBRA_INTERFACE_SET_PROTODOWN, + ZEBRA_ROUTE_ADD, + ZEBRA_ROUTE_DELETE, + ZEBRA_ROUTE_NOTIFY_OWNER, + ZEBRA_REDISTRIBUTE_ADD, + ZEBRA_REDISTRIBUTE_DELETE, + ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, + ZEBRA_ROUTER_ID_ADD, + ZEBRA_ROUTER_ID_DELETE, + ZEBRA_ROUTER_ID_UPDATE, + ZEBRA_HELLO, + ZEBRA_CAPABILITIES, + ZEBRA_NEXTHOP_REGISTER, + ZEBRA_NEXTHOP_UNREGISTER, + ZEBRA_NEXTHOP_UPDATE, + ZEBRA_INTERFACE_NBR_ADDRESS_ADD, + ZEBRA_INTERFACE_NBR_ADDRESS_DELETE, + ZEBRA_INTERFACE_BFD_DEST_UPDATE, + ZEBRA_BFD_DEST_REGISTER, + ZEBRA_BFD_DEST_DEREGISTER, + ZEBRA_BFD_DEST_UPDATE, + ZEBRA_BFD_DEST_REPLAY, + ZEBRA_REDISTRIBUTE_ROUTE_ADD, + ZEBRA_REDISTRIBUTE_ROUTE_DEL, + ZEBRA_VRF_UNREGISTER, + ZEBRA_VRF_ADD, + ZEBRA_VRF_DELETE, + ZEBRA_VRF_LABEL, + ZEBRA_BFD_CLIENT_REGISTER, + ZEBRA_BFD_CLIENT_DEREGISTER, + ZEBRA_INTERFACE_ENABLE_RADV, + ZEBRA_INTERFACE_DISABLE_RADV, + ZEBRA_NEXTHOP_LOOKUP_MRIB, + ZEBRA_INTERFACE_LINK_PARAMS, + ZEBRA_MPLS_LABELS_ADD, + ZEBRA_MPLS_LABELS_DELETE, + ZEBRA_MPLS_LABELS_REPLACE, + ZEBRA_SR_POLICY_SET, + ZEBRA_SR_POLICY_DELETE, + ZEBRA_SR_POLICY_NOTIFY_STATUS, + ZEBRA_IPMR_ROUTE_STATS, + ZEBRA_LABEL_MANAGER_CONNECT, + ZEBRA_LABEL_MANAGER_CONNECT_ASYNC, + ZEBRA_GET_LABEL_CHUNK, + ZEBRA_RELEASE_LABEL_CHUNK, + ZEBRA_FEC_REGISTER, + ZEBRA_FEC_UNREGISTER, + ZEBRA_FEC_UPDATE, + ZEBRA_ADVERTISE_DEFAULT_GW, + ZEBRA_ADVERTISE_SVI_MACIP, + ZEBRA_ADVERTISE_SUBNET, + ZEBRA_ADVERTISE_ALL_VNI, + ZEBRA_LOCAL_ES_ADD, + ZEBRA_LOCAL_ES_DEL, + ZEBRA_REMOTE_ES_VTEP_ADD, + ZEBRA_REMOTE_ES_VTEP_DEL, + ZEBRA_LOCAL_ES_EVI_ADD, + ZEBRA_LOCAL_ES_EVI_DEL, + ZEBRA_VNI_ADD, + ZEBRA_VNI_DEL, + ZEBRA_L3VNI_ADD, + ZEBRA_L3VNI_DEL, + ZEBRA_REMOTE_VTEP_ADD, + ZEBRA_REMOTE_VTEP_DEL, + ZEBRA_MACIP_ADD, + ZEBRA_MACIP_DEL, + ZEBRA_IP_PREFIX_ROUTE_ADD, + ZEBRA_IP_PREFIX_ROUTE_DEL, + ZEBRA_REMOTE_MACIP_ADD, + ZEBRA_REMOTE_MACIP_DEL, + ZEBRA_DUPLICATE_ADDR_DETECTION, + ZEBRA_PW_ADD, + ZEBRA_PW_DELETE, + ZEBRA_PW_SET, + ZEBRA_PW_UNSET, + ZEBRA_PW_STATUS_UPDATE, + ZEBRA_RULE_ADD, + ZEBRA_RULE_DELETE, + ZEBRA_RULE_NOTIFY_OWNER, + ZEBRA_TABLE_MANAGER_CONNECT, + ZEBRA_GET_TABLE_CHUNK, + ZEBRA_RELEASE_TABLE_CHUNK, + ZEBRA_IPSET_CREATE, + ZEBRA_IPSET_DESTROY, + ZEBRA_IPSET_ENTRY_ADD, + ZEBRA_IPSET_ENTRY_DELETE, + ZEBRA_IPSET_NOTIFY_OWNER, + ZEBRA_IPSET_ENTRY_NOTIFY_OWNER, + ZEBRA_IPTABLE_ADD, + ZEBRA_IPTABLE_DELETE, + ZEBRA_IPTABLE_NOTIFY_OWNER, + ZEBRA_VXLAN_FLOOD_CONTROL, + ZEBRA_VXLAN_SG_ADD, + ZEBRA_VXLAN_SG_DEL, + ZEBRA_VXLAN_SG_REPLAY, + ZEBRA_MLAG_PROCESS_UP, + ZEBRA_MLAG_PROCESS_DOWN, + ZEBRA_MLAG_CLIENT_REGISTER, + ZEBRA_MLAG_CLIENT_UNREGISTER, + ZEBRA_MLAG_FORWARD_MSG, + ZEBRA_NHG_ADD, + ZEBRA_NHG_DEL, + ZEBRA_NHG_NOTIFY_OWNER, + ZEBRA_EVPN_REMOTE_NH_ADD, + ZEBRA_EVPN_REMOTE_NH_DEL, + ZEBRA_SRV6_LOCATOR_ADD, + ZEBRA_SRV6_LOCATOR_DELETE, + ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK, + ZEBRA_SRV6_MANAGER_RELEASE_LOCATOR_CHUNK, + ZEBRA_ERROR, + ZEBRA_CLIENT_CAPABILITIES, + ZEBRA_OPAQUE_MESSAGE, + ZEBRA_OPAQUE_REGISTER, + ZEBRA_OPAQUE_UNREGISTER, + ZEBRA_NEIGH_DISCOVER, + ZEBRA_ROUTE_NOTIFY_REQUEST, + ZEBRA_CLIENT_CLOSE_NOTIFY, + ZEBRA_NEIGH_ADDED, + ZEBRA_NEIGH_REMOVED, + ZEBRA_NEIGH_GET, + ZEBRA_NEIGH_REGISTER, + ZEBRA_NEIGH_UNREGISTER, + ZEBRA_NEIGH_IP_ADD, + ZEBRA_NEIGH_IP_DEL, + ZEBRA_CONFIGURE_ARP, + ZEBRA_GRE_GET, + ZEBRA_GRE_UPDATE, + ZEBRA_GRE_SOURCE_SET, + ZEBRA_TC_QDISC_INSTALL, + ZEBRA_TC_QDISC_UNINSTALL, + ZEBRA_TC_CLASS_ADD, + ZEBRA_TC_CLASS_DELETE, + ZEBRA_TC_FILTER_ADD, + ZEBRA_TC_FILTER_DELETE, + ZEBRA_OPAQUE_NOTIFY, +} zebra_message_types_t; +/* Zebra message types. Please update the corresponding + * command_types array with any changes! + */ + +enum zebra_error_types { + ZEBRA_UNKNOWN_ERROR, /* Error of unknown type */ + ZEBRA_NO_VRF, /* Vrf in header was not found */ + ZEBRA_INVALID_MSG_TYPE, /* No handler found for msg type */ +}; + +static inline const char *zebra_error_type2str(enum zebra_error_types type) +{ + const char *ret = "UNKNOWN"; + + switch (type) { + case ZEBRA_UNKNOWN_ERROR: + ret = "ZEBRA_UNKNOWN_ERROR"; + break; + case ZEBRA_NO_VRF: + ret = "ZEBRA_NO_VRF"; + break; + case ZEBRA_INVALID_MSG_TYPE: + ret = "ZEBRA_INVALID_MSG_TYPE"; + break; + } + + return ret; +} + +struct redist_proto { + uint8_t enabled; + struct list *instances; +}; + +struct zclient_capabilities { + uint32_t ecmp; + bool mpls_enabled; + enum mlag_role role; + bool v6_with_v4_nexthop; +}; + +/* Graceful Restart Capabilities message */ +struct zapi_cap { + enum zserv_client_capabilities cap; + uint32_t stale_removal_time; + afi_t afi; + safi_t safi; + vrf_id_t vrf_id; +}; + +/* clang-format off */ +#define ZAPI_CALLBACK_ARGS \ + int cmd, struct zclient *zclient, uint16_t length, vrf_id_t vrf_id + +/* function-type typedef (pointer not included) */ +typedef int (zclient_handler)(ZAPI_CALLBACK_ARGS); +/* clang-format on */ + +struct zapi_route; + +/* Structure for the zebra client. */ +struct zclient { + /* The thread master we schedule ourselves on */ + struct event_loop *master; + + /* Privileges to change socket values */ + struct zebra_privs_t *privs; + + /* Is this a synchronous client? */ + bool synchronous; + + /* Auxiliary clients don't execute standard library handlers + * (which otherwise would duplicate VRF/interface add/delete/etc. + */ + bool auxiliary; + + /* BFD enabled with bfd_protocol_integration_init() */ + bool bfd_integration; + + /* Session id (optional) to support clients with multiple sessions */ + uint32_t session_id; + + /* Socket to zebra daemon. */ + int sock; + + /* Connection failure count. */ + int fail; + + /* Input buffer for zebra message. */ + struct stream *ibuf; + + /* Output buffer for zebra message. */ + struct stream *obuf; + + /* Buffer of data waiting to be written to zebra. */ + struct buffer *wb; + + /* Read and connect thread. */ + struct event *t_read; + struct event *t_connect; + + /* Thread to write buffered data to zebra. */ + struct event *t_write; + + /* Redistribute information. */ + uint8_t redist_default; /* clients protocol */ + unsigned short instance; + struct redist_proto mi_redist[AFI_MAX][ZEBRA_ROUTE_MAX]; + vrf_bitmap_t redist[AFI_MAX][ZEBRA_ROUTE_MAX]; + + /* Redistribute default. */ + vrf_bitmap_t default_information[AFI_MAX]; + + /* Pointer to the callback functions. */ + void (*zebra_connected)(struct zclient *); + void (*zebra_capabilities)(struct zclient_capabilities *cap); + + /* + * match -> is the prefix that the calling daemon asked to be matched + * against. + * nhr->prefix -> is the actual prefix that was matched against in the + * rib itself. + * + * This distinction is made because a LPM can be made if there is a + * covering route. This way the upper level protocol can make a + * decision point about whether or not it wants to use the match or not. + */ + void (*nexthop_update)(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr); + + int (*handle_error)(enum zebra_error_types error); + + /* + * When the zclient attempts to write the stream data to + * it's named pipe to/from zebra, we may have a situation + * where the other daemon has not fully drained the data + * from the socket. In this case provide a mechanism + * where we will *still* buffer the data to be sent + * and also provide a callback mechanism to the appropriate + * place where we can signal that we're ready to receive + * more data. + */ + void (*zebra_buffer_write_ready)(void); + + zclient_handler *const *handlers; + size_t n_handlers; +}; + +/* lib handlers added in bfd.c */ +extern int zclient_bfd_session_replay(ZAPI_CALLBACK_ARGS); +extern int zclient_bfd_session_update(ZAPI_CALLBACK_ARGS); + +/* Zebra API message flag. */ +#define ZAPI_MESSAGE_NEXTHOP 0x01 +#define ZAPI_MESSAGE_DISTANCE 0x02 +#define ZAPI_MESSAGE_METRIC 0x04 +#define ZAPI_MESSAGE_TAG 0x08 +#define ZAPI_MESSAGE_MTU 0x10 +#define ZAPI_MESSAGE_SRCPFX 0x20 +/* Backup nexthops are present */ +#define ZAPI_MESSAGE_BACKUP_NEXTHOPS 0x40 +#define ZAPI_MESSAGE_NHG 0x80 +/* + * This should only be used by a DAEMON that needs to communicate + * the table being used is not in the VRF. You must pass the + * default vrf, else this will be ignored. + */ +#define ZAPI_MESSAGE_TABLEID 0x0100 +#define ZAPI_MESSAGE_SRTE 0x0200 +#define ZAPI_MESSAGE_OPAQUE 0x0400 + +#define ZSERV_VERSION 6 +/* Zserv protocol message header */ +struct zmsghdr { + uint16_t length; + /* Always set to 255 in new zserv */ + uint8_t marker; + uint8_t version; + vrf_id_t vrf_id; + uint16_t command; +} __attribute__((packed)); +#define ZAPI_HEADER_CMD_LOCATION offsetof(struct zmsghdr, command) + +/* + * ZAPI nexthop. Note that these are sorted when associated with ZAPI routes, + * and that sorting must be aligned with the sorting of nexthops in + * lib/nexthop.c. Any new fields must be accounted for in zapi_nexthop_cmp(). + */ +struct zapi_nexthop { + enum nexthop_types_t type; + vrf_id_t vrf_id; + ifindex_t ifindex; + uint8_t flags; + union { + union g_addr gate; + enum blackhole_type bh_type; + }; + + /* MPLS labels for BGP-LU or Segment Routing */ + uint8_t label_num; + enum lsp_types_t label_type; + mpls_label_t labels[MPLS_MAX_LABELS]; + + struct ethaddr rmac; + + uint64_t weight; + + /* Backup nexthops, for IP-FRR, TI-LFA, etc */ + uint8_t backup_num; + uint8_t backup_idx[NEXTHOP_MAX_BACKUPS]; + + /* SR-TE color. */ + uint32_t srte_color; + + /* SRv6 localsid info for Endpoint-behaviour */ + uint32_t seg6local_action; + struct seg6local_context seg6local_ctx; + + /* SRv6 Headend-behaviour */ + int seg_num; + struct in6_addr seg6_segs[SRV6_MAX_SEGS]; +}; + +/* + * ZAPI nexthop flags values - we're encoding a single octet + * initially, so ensure that the on-the-wire encoding continues + * to match the number of valid flags. + */ + +#define ZAPI_NEXTHOP_FLAG_ONLINK 0x01 +#define ZAPI_NEXTHOP_FLAG_LABEL 0x02 +#define ZAPI_NEXTHOP_FLAG_WEIGHT 0x04 +#define ZAPI_NEXTHOP_FLAG_HAS_BACKUP 0x08 /* Nexthop has a backup */ +#define ZAPI_NEXTHOP_FLAG_SEG6 0x10 +#define ZAPI_NEXTHOP_FLAG_SEG6LOCAL 0x20 +#define ZAPI_NEXTHOP_FLAG_EVPN 0x40 + +/* + * ZAPI Nexthop Group. For use with protocol creation of nexthop groups. + */ +struct zapi_nhg { + uint16_t proto; + uint32_t id; + + struct nhg_resilience resilience; + + uint16_t nexthop_num; + struct zapi_nexthop nexthops[MULTIPATH_NUM]; + + uint16_t backup_nexthop_num; + struct zapi_nexthop backup_nexthops[MULTIPATH_NUM]; +}; + +/* + * Some of these data structures do not map easily to + * a actual data structure size giving different compilers + * and systems. For those data structures we need + * to use the smallest available stream_getX/putX functions + * to encode/decode. + */ +struct zapi_route { + uint8_t type; + unsigned short instance; + + /* If you add flags, update zclient_dump_route_flags */ + uint32_t flags; +/* + * Cause Zebra to consider this routes nexthops recursively + */ +#define ZEBRA_FLAG_ALLOW_RECURSION 0x01 +/* + * This is a route that is read in on startup that was left around + * from a previous run of FRR + */ +#define ZEBRA_FLAG_SELFROUTE 0x02 +/* + * This flag is used to tell Zebra that the BGP route being passed + * down is a IBGP route + */ +#define ZEBRA_FLAG_IBGP 0x04 +/* + * This is a route that has been selected for FIB installation. + * This flag is set in zebra and can be passed up to routing daemons + */ +#define ZEBRA_FLAG_SELECTED 0x08 +/* + * This is a route that we are telling Zebra that this route *must* + * win and will be installed even over ZEBRA_FLAG_SELECTED + */ +#define ZEBRA_FLAG_FIB_OVERRIDE 0x10 +/* + * This flag tells Zebra that the route is a EVPN route and should + * be treated specially + */ +#define ZEBRA_FLAG_EVPN_ROUTE 0x20 +/* + * This flag tells Zebra that it should treat the distance passed + * down as an additional discriminator for route selection of the + * route entry. This mainly is used for backup static routes. + */ +#define ZEBRA_FLAG_RR_USE_DISTANCE 0x40 +/* + * This flag tells everyone that the route was intentionally + * not offloaded and the route will be sent to the cpu for + * forwarding. This flag makes no sense unless you are in + * an asic offload situation + */ +#define ZEBRA_FLAG_TRAPPED 0x80 +/* + * This flag tells everyone that the route has been + * successfully offloaded to an asic for forwarding. + * This flag makes no sense unless you are in an asic + * offload situation. + */ +#define ZEBRA_FLAG_OFFLOADED 0x100 +/* + * This flag tells everyone that the route has + * failed offloading. + * This flag makes no sense unless you are in an asic + * offload situation. + */ +#define ZEBRA_FLAG_OFFLOAD_FAILED 0x200 + +/* + * This flag lets us know that we think the route entry + * received has caused us to be out of sync with the + * kernel (NLM_F_APPEND at the very least ) + */ +#define ZEBRA_FLAG_OUTOFSYNC 0x400 + + /* The older XXX_MESSAGE flags live here */ + uint32_t message; + + /* + * This is an enum but we are going to treat it as a uint8_t + * for purpose of encoding/decoding + */ + safi_t safi; + + struct prefix prefix; + struct prefix_ipv6 src_prefix; + + uint16_t nexthop_num; + struct zapi_nexthop nexthops[MULTIPATH_NUM]; + + /* Support backup routes for IP FRR, TI-LFA, traffic engineering */ + uint16_t backup_nexthop_num; + struct zapi_nexthop backup_nexthops[MULTIPATH_NUM]; + + uint32_t nhgid; + + uint8_t distance; + + uint32_t metric; + + route_tag_t tag; + + uint32_t mtu; + + vrf_id_t vrf_id; + + uint32_t tableid; + + /* SR-TE color (used for nexthop updates only). */ + uint32_t srte_color; + +#define ZAPI_MESSAGE_OPAQUE_LENGTH 1024 + struct { + uint16_t length; + uint8_t data[ZAPI_MESSAGE_OPAQUE_LENGTH]; + } opaque; +}; + +extern char *zclient_dump_route_flags(uint32_t flags, char *buf, size_t len); + +struct zapi_labels { + uint8_t message; +#define ZAPI_LABELS_FTN 0x01 +#define ZAPI_LABELS_HAS_BACKUPS 0x02 + enum lsp_types_t type; + mpls_label_t local_label; + struct { + struct prefix prefix; + uint8_t type; + unsigned short instance; + } route; + + uint16_t nexthop_num; + struct zapi_nexthop nexthops[MULTIPATH_NUM]; + + /* Backup nexthops, if present */ + uint16_t backup_nexthop_num; + struct zapi_nexthop backup_nexthops[MULTIPATH_NUM]; +}; + +struct zapi_srte_tunnel { + enum lsp_types_t type; + mpls_label_t local_label; + uint8_t label_num; + mpls_label_t labels[MPLS_MAX_LABELS]; +}; + +struct zapi_sr_policy { + uint32_t color; + struct ipaddr endpoint; + char name[SRTE_POLICY_NAME_MAX_LENGTH]; + struct zapi_srte_tunnel segment_list; + int status; +}; + +struct zapi_pw { + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + int type; + int af; + union g_addr nexthop; + uint32_t local_label; + uint32_t remote_label; + uint8_t flags; + union pw_protocol_fields data; + uint8_t protocol; +}; + +struct zapi_pw_status { + char ifname[IFNAMSIZ]; + ifindex_t ifindex; + uint32_t status; +}; + +/* IGP instance data associated to a RLFA. */ +struct zapi_rlfa_igp { + vrf_id_t vrf_id; + int protocol; + union { + struct { + char area_tag[32]; + struct { + int tree_id; + int level; + unsigned int run_id; + } spf; + } isis; + }; +}; + +/* IGP -> LDP RLFA (un)registration message. */ +struct zapi_rlfa_request { + /* IGP instance data. */ + struct zapi_rlfa_igp igp; + + /* Destination prefix. */ + struct prefix destination; + + /* PQ node address. */ + struct in_addr pq_address; +}; + +/* LDP -> IGP RLFA label update. */ +struct zapi_rlfa_response { + /* IGP instance data. */ + struct zapi_rlfa_igp igp; + + /* Destination prefix. */ + struct prefix destination; + + /* Resolved LDP labels. */ + mpls_label_t pq_label; + uint16_t nexthop_num; + struct { + int family; + union g_addr gate; + mpls_label_t label; + } nexthops[MULTIPATH_NUM]; +}; + +enum zapi_route_notify_owner { + ZAPI_ROUTE_FAIL_INSTALL, + ZAPI_ROUTE_BETTER_ADMIN_WON, + ZAPI_ROUTE_INSTALLED, + ZAPI_ROUTE_REMOVED, + ZAPI_ROUTE_REMOVE_FAIL, +}; + +enum zapi_nhg_notify_owner { + ZAPI_NHG_FAIL_INSTALL, + ZAPI_NHG_INSTALLED, + ZAPI_NHG_REMOVED, + ZAPI_NHG_REMOVE_FAIL, +}; + +enum zapi_rule_notify_owner { + ZAPI_RULE_FAIL_INSTALL, + ZAPI_RULE_INSTALLED, + ZAPI_RULE_REMOVED, + ZAPI_RULE_FAIL_REMOVE, +}; + +enum ipset_type { + IPSET_NET_NET = 1, + IPSET_NET_PORT_NET, + IPSET_NET_PORT, + IPSET_NET +}; + +enum zapi_ipset_notify_owner { + ZAPI_IPSET_FAIL_INSTALL = 0, + ZAPI_IPSET_INSTALLED, + ZAPI_IPSET_REMOVED, + ZAPI_IPSET_FAIL_REMOVE, +}; + +enum zapi_ipset_entry_notify_owner { + ZAPI_IPSET_ENTRY_FAIL_INSTALL = 0, + ZAPI_IPSET_ENTRY_INSTALLED, + ZAPI_IPSET_ENTRY_REMOVED, + ZAPI_IPSET_ENTRY_FAIL_REMOVE, +}; + +enum zapi_iptable_notify_owner { + ZAPI_IPTABLE_FAIL_INSTALL = 0, + ZAPI_IPTABLE_INSTALLED, + ZAPI_IPTABLE_REMOVED, + ZAPI_IPTABLE_FAIL_REMOVE, +}; + +enum zclient_send_status { + ZCLIENT_SEND_FAILURE = -1, + ZCLIENT_SEND_SUCCESS = 0, + ZCLIENT_SEND_BUFFERED = 1 +}; + +static inline const char * +zapi_nhg_notify_owner2str(enum zapi_nhg_notify_owner note) +{ + const char *ret = "UNKNOWN"; + + switch (note) { + case ZAPI_NHG_FAIL_INSTALL: + ret = "ZAPI_NHG_FAIL_INSTALL"; + break; + case ZAPI_NHG_INSTALLED: + ret = "ZAPI_NHG_INSTALLED"; + break; + case ZAPI_NHG_REMOVE_FAIL: + ret = "ZAPI_NHG_REMOVE_FAIL"; + break; + case ZAPI_NHG_REMOVED: + ret = "ZAPI_NHG_REMOVED"; + break; + } + + return ret; +} + +static inline const char * +zapi_rule_notify_owner2str(enum zapi_rule_notify_owner note) +{ + const char *ret = "UNKNOWN"; + + switch (note) { + case ZAPI_RULE_FAIL_INSTALL: + ret = "ZAPI_RULE_FAIL_INSTALL"; + break; + case ZAPI_RULE_INSTALLED: + ret = "ZAPI_RULE_INSTALLED"; + break; + case ZAPI_RULE_FAIL_REMOVE: + ret = "ZAPI_RULE_FAIL_REMOVE"; + break; + case ZAPI_RULE_REMOVED: + ret = "ZAPI_RULE_REMOVED"; + break; + } + + return ret; +} + +/* Zebra MAC types */ +#define ZEBRA_MACIP_TYPE_STICKY 0x01 /* Sticky MAC*/ +#define ZEBRA_MACIP_TYPE_GW 0x02 /* gateway (SVI) mac*/ +#define ZEBRA_MACIP_TYPE_ROUTER_FLAG 0x04 /* Router Flag - proxy NA */ +#define ZEBRA_MACIP_TYPE_OVERRIDE_FLAG 0x08 /* Override Flag */ +#define ZEBRA_MACIP_TYPE_SVI_IP 0x10 /* SVI MAC-IP */ +#define ZEBRA_MACIP_TYPE_PROXY_ADVERT 0x20 /* Not locally active */ +#define ZEBRA_MACIP_TYPE_SYNC_PATH 0x40 /* sync path */ +/* XXX - flags is an u8; that needs to be changed to u32 if you need + * to allocate past 0x80. Additionally touch zclient_evpn_dump_macip_flags + */ +#define MACIP_BUF_SIZE 128 +extern char *zclient_evpn_dump_macip_flags(uint8_t flags, char *buf, + size_t len); + +/* Zebra ES VTEP flags (ZEBRA_REMOTE_ES_VTEP_ADD) */ +/* ESR has been rxed from the VTEP. Only VTEPs that have advertised the + * Type-4 route can participate in DF election. + */ +#define ZAPI_ES_VTEP_FLAG_ESR_RXED (1 << 0) + +enum zebra_neigh_state { ZEBRA_NEIGH_INACTIVE = 0, ZEBRA_NEIGH_ACTIVE = 1 }; + +struct zclient_options { + bool synchronous; + + /* auxiliary = don't call common lib/ handlers that manage bits. + * Those should only run once, on the "main" zclient, which this is + * not. (This is also set for synchronous clients.) + */ + bool auxiliary; +}; + +extern const struct zclient_options zclient_options_default; +extern const struct zclient_options zclient_options_sync; +extern const struct zclient_options zclient_options_auxiliary; + +/* link layer representation for GRE like interfaces + * ip_in is the underlay IP, ip_out is the tunnel dest + * index stands for the index of the interface + * ndm state stands for the NDM value in netlink + * (see linux/neighbour.h) + */ +#define ZEBRA_NEIGH_STATE_INCOMPLETE (0x01) +#define ZEBRA_NEIGH_STATE_REACHABLE (0x02) +#define ZEBRA_NEIGH_STATE_STALE (0x04) +#define ZEBRA_NEIGH_STATE_DELAY (0x08) +#define ZEBRA_NEIGH_STATE_PROBE (0x10) +#define ZEBRA_NEIGH_STATE_FAILED (0x20) +#define ZEBRA_NEIGH_STATE_NOARP (0x40) +#define ZEBRA_NEIGH_STATE_PERMANENT (0x80) +#define ZEBRA_NEIGH_STATE_NONE (0x00) + +struct zapi_neigh_ip { + int cmd; + int ip_len; + struct ipaddr ip_in; + struct ipaddr ip_out; + ifindex_t index; + uint32_t ndm_state; +}; +int zclient_neigh_ip_decode(struct stream *s, struct zapi_neigh_ip *api); +int zclient_neigh_ip_encode(struct stream *s, uint16_t cmd, union sockunion *in, + union sockunion *out, struct interface *ifp, + int ndm_state, int ip_len); + +/* + * We reserve the top 4 bits for l2-NHG, everything else + * is for zebra/proto l3-NHG. + * + * Each client is going to get it's own nexthop group space + * and we'll separate them, we'll figure out where to start based upon + * the route_types.h + */ +#define ZEBRA_NHG_PROTO_UPPER \ + ((uint32_t)250000000) /* Bottom 28 bits then rounded down */ +#define ZEBRA_NHG_PROTO_SPACING (ZEBRA_NHG_PROTO_UPPER / ZEBRA_ROUTE_MAX) +#define ZEBRA_NHG_PROTO_LOWER \ + (ZEBRA_NHG_PROTO_SPACING * (ZEBRA_ROUTE_LOCAL + 1)) + +extern uint32_t zclient_get_nhg_start(uint32_t proto); + +extern struct zclient *zclient_new(struct event_loop *m, + const struct zclient_options *opt, + zclient_handler *const *handlers, + size_t n_handlers); + +extern void zclient_init(struct zclient *, int, unsigned short, + struct zebra_privs_t *privs); +extern int zclient_start(struct zclient *); +extern void zclient_stop(struct zclient *); +extern void zclient_reset(struct zclient *); +extern void zclient_free(struct zclient *); + +extern int zclient_socket_connect(struct zclient *); + +extern unsigned short *redist_check_instance(struct redist_proto *, + unsigned short); +extern void redist_add_instance(struct redist_proto *, unsigned short); +extern void redist_del_instance(struct redist_proto *, unsigned short); +extern void redist_del_all_instances(struct redist_proto *red); + +/* + * Send to zebra that the specified vrf is using label to resolve + * itself for L3VPN's. Repeated calls of this function with + * different labels will cause an effective update of the + * label for lookup. If you pass in MPLS_LABEL_NONE + * we will cause a delete action and remove this label pop + * operation. + * + * The underlying AF_MPLS doesn't care about afi's + * but we can make the zebra_vrf keep track of what + * we have installed and play some special games + * to get them both installed. + */ +extern enum zclient_send_status +zclient_send_vrf_label(struct zclient *zclient, vrf_id_t vrf_id, afi_t afi, + mpls_label_t label, enum lsp_types_t ltype); + +extern enum zclient_send_status +zclient_send_localsid(struct zclient *zclient, const struct in6_addr *sid, + vrf_id_t vrf_id, enum seg6local_action_t action, + const struct seg6local_context *context); + +extern void zclient_send_reg_requests(struct zclient *, vrf_id_t); +extern void zclient_send_dereg_requests(struct zclient *, vrf_id_t); +extern enum zclient_send_status +zclient_send_router_id_update(struct zclient *zclient, + zebra_message_types_t type, afi_t afi, + vrf_id_t vrf_id); + +extern enum zclient_send_status +zclient_send_interface_radv_req(struct zclient *zclient, vrf_id_t vrf_id, + struct interface *ifp, int enable, + uint32_t ra_interval); +extern enum zclient_send_status +zclient_send_interface_protodown(struct zclient *zclient, vrf_id_t vrf_id, + struct interface *ifp, bool down); + +/* Send redistribute command to zebra daemon. Do not update zclient state. */ +extern enum zclient_send_status +zebra_redistribute_send(int command, struct zclient *, afi_t, int type, + unsigned short instance, vrf_id_t vrf_id); + +extern enum zclient_send_status +zebra_redistribute_default_send(int command, struct zclient *zclient, afi_t afi, + vrf_id_t vrf_id); + +/* Send route notify request to zebra */ +extern int zebra_route_notify_send(int command, struct zclient *zclient, + bool set); + +/* If state has changed, update state and call zebra_redistribute_send. */ +extern void zclient_redistribute(int command, struct zclient *, afi_t, int type, + unsigned short instance, vrf_id_t vrf_id); + +/* If state has changed, update state and send the command to zebra. */ +extern void zclient_redistribute_default(int command, struct zclient *, + afi_t, vrf_id_t vrf_id); + +/* + * Send the message in zclient->obuf to the zebra daemon (or enqueue it). + * Returns: + * -1 on a I/O error + * 0 data was successfully sent + * 1 data was buffered for future usage + */ +extern enum zclient_send_status zclient_send_message(struct zclient *); + +/* create header for command, length to be filled in by user later */ +extern void zclient_create_header(struct stream *, uint16_t, vrf_id_t); +/* + * Read sizeof(struct zmsghdr) bytes from the provided socket and parse the + * received data into the specified fields. If this is successful, read the + * rest of the packet into the provided stream. + * + * s + * The stream to read into + * + * sock + * The socket to read from + * + * size + * Parsed message size will be placed in the pointed-at integer + * + * marker + * Parsed marker will be placed in the pointed-at byte + * + * version + * Parsed version will be placed in the pointed-at byte + * + * vrf_id + * Parsed VRF ID will be placed in the pointed-at vrf_id_t + * + * cmd + * Parsed command number will be placed in the pointed-at integer + * + * Returns: + * -1 if: + * - insufficient data for header was read + * - a version mismatch was detected + * - a marker mismatch was detected + * - header size field specified more data than could be read + */ +extern int zclient_read_header(struct stream *s, int sock, uint16_t *size, + uint8_t *marker, uint8_t *version, + vrf_id_t *vrf_id, uint16_t *cmd); +/* + * Parse header from ZAPI message stream into struct zmsghdr. + * This function assumes the stream getp points at the first byte of the header. + * If the function is successful then the stream getp will point to the byte + * immediately after the last byte of the header. + * + * zmsg + * The stream containing the header + * + * hdr + * The header struct to parse into. + * + * Returns: + * true if parsing succeeded, false otherwise + */ +extern bool zapi_parse_header(struct stream *zmsg, struct zmsghdr *hdr); + +extern enum zclient_send_status zclient_interface_set_arp(struct zclient *client, + struct interface *ifp, + bool arp_enable); +extern enum zclient_send_status +zclient_interface_set_master(struct zclient *client, struct interface *master, + struct interface *slave); +extern struct interface *zebra_interface_state_read(struct stream *s, vrf_id_t); +extern struct connected *zebra_interface_address_read(int, struct stream *, + vrf_id_t); +extern struct nbr_connected * +zebra_interface_nbr_address_read(int, struct stream *, vrf_id_t); +extern int zebra_router_id_update_read(struct stream *s, struct prefix *rid); + +extern struct interface *zebra_interface_link_params_read(struct stream *s, + vrf_id_t vrf_id, + bool *changed); +extern size_t zebra_interface_link_params_write(struct stream *, + struct interface *); +extern enum zclient_send_status +zclient_send_get_label_chunk(struct zclient *zclient, uint8_t keep, + uint32_t chunk_size, uint32_t base); + +extern int lm_label_manager_connect(struct zclient *zclient, int async); +extern int lm_get_label_chunk(struct zclient *zclient, uint8_t keep, + uint32_t base, uint32_t chunk_size, + uint32_t *start, uint32_t *end); +extern int lm_release_label_chunk(struct zclient *zclient, uint32_t start, + uint32_t end); +extern int tm_table_manager_connect(struct zclient *zclient); +extern int tm_get_table_chunk(struct zclient *zclient, uint32_t chunk_size, + uint32_t *start, uint32_t *end); +extern int tm_release_table_chunk(struct zclient *zclient, uint32_t start, + uint32_t end); +extern int srv6_manager_get_locator_chunk(struct zclient *zclient, + const char *locator_name); +extern int srv6_manager_release_locator_chunk(struct zclient *zclient, + const char *locator_name); + +extern enum zclient_send_status zebra_send_sr_policy(struct zclient *zclient, + int cmd, + struct zapi_sr_policy *zp); +extern int zapi_sr_policy_encode(struct stream *s, int cmd, + struct zapi_sr_policy *zp); +extern int zapi_sr_policy_decode(struct stream *s, struct zapi_sr_policy *zp); +extern int zapi_sr_policy_notify_status_decode(struct stream *s, + struct zapi_sr_policy *zp); + +extern enum zclient_send_status zebra_send_mpls_labels(struct zclient *zclient, + int cmd, + struct zapi_labels *zl); +extern int zapi_labels_encode(struct stream *s, int cmd, + struct zapi_labels *zl); +extern int zapi_labels_decode(struct stream *s, struct zapi_labels *zl); + +extern int zapi_srv6_locator_encode(struct stream *s, + const struct srv6_locator *l); +extern int zapi_srv6_locator_decode(struct stream *s, struct srv6_locator *l); +extern int zapi_srv6_locator_chunk_encode(struct stream *s, + const struct srv6_locator_chunk *c); +extern int zapi_srv6_locator_chunk_decode(struct stream *s, + struct srv6_locator_chunk *c); + +extern enum zclient_send_status zebra_send_pw(struct zclient *zclient, + int command, struct zapi_pw *pw); +extern int zebra_read_pw_status_update(ZAPI_CALLBACK_ARGS, + struct zapi_pw_status *pw); + +extern enum zclient_send_status zclient_route_send(uint8_t, struct zclient *, + struct zapi_route *); +extern enum zclient_send_status +zclient_send_rnh(struct zclient *zclient, int command, const struct prefix *p, + safi_t safi, bool connected, bool resolve_via_default, + vrf_id_t vrf_id); +int zapi_nexthop_encode(struct stream *s, const struct zapi_nexthop *api_nh, + uint32_t api_flags, uint32_t api_message); +extern int zapi_route_encode(uint8_t, struct stream *, struct zapi_route *); +extern int zapi_route_decode(struct stream *s, struct zapi_route *api); +extern int zapi_nexthop_decode(struct stream *s, struct zapi_nexthop *api_nh, + uint32_t api_flags, uint32_t api_message); +bool zapi_nhg_notify_decode(struct stream *s, uint32_t *id, + enum zapi_nhg_notify_owner *note); +bool zapi_route_notify_decode(struct stream *s, struct prefix *p, + uint32_t *tableid, + enum zapi_route_notify_owner *note, + afi_t *afi, safi_t *safi); +bool zapi_rule_notify_decode(struct stream *s, uint32_t *seqno, + uint32_t *priority, uint32_t *unique, char *ifname, + enum zapi_rule_notify_owner *note); +bool zapi_ipset_notify_decode(struct stream *s, + uint32_t *unique, + enum zapi_ipset_notify_owner *note); + +/* Nexthop-group message apis */ +extern enum zclient_send_status +zclient_nhg_send(struct zclient *zclient, int cmd, struct zapi_nhg *api_nhg); + +#define ZEBRA_IPSET_NAME_SIZE 32 + +bool zapi_ipset_entry_notify_decode(struct stream *s, + uint32_t *unique, + char *ipset_name, + enum zapi_ipset_entry_notify_owner *note); +bool zapi_iptable_notify_decode(struct stream *s, + uint32_t *unique, + enum zapi_iptable_notify_owner *note); + +extern struct nexthop * +nexthop_from_zapi_nexthop(const struct zapi_nexthop *znh); +int zapi_nexthop_from_nexthop(struct zapi_nexthop *znh, + const struct nexthop *nh); +int zapi_backup_nexthop_from_nexthop(struct zapi_nexthop *znh, + const struct nexthop *nh); +const char *zapi_nexthop2str(const struct zapi_nexthop *znh, char *buf, + int bufsize); + +/* Decode the zebra error message */ +extern bool zapi_error_decode(struct stream *s, enum zebra_error_types *error); + +/* Encode and decode restart capabilities */ +extern enum zclient_send_status +zclient_capabilities_send(uint32_t cmd, struct zclient *zclient, + struct zapi_cap *api); +extern int32_t zapi_capabilities_decode(struct stream *s, struct zapi_cap *api); + +static inline void zapi_route_set_blackhole(struct zapi_route *api, + enum blackhole_type bh_type) +{ + api->nexthop_num = 1; + api->nexthops[0].type = NEXTHOP_TYPE_BLACKHOLE; + api->nexthops[0].vrf_id = VRF_DEFAULT; + api->nexthops[0].bh_type = bh_type; + SET_FLAG(api->message, ZAPI_MESSAGE_NEXTHOP); +}; + +static inline void zapi_route_set_nhg_id(struct zapi_route *api, + uint32_t *nhg_id) +{ + api->nexthop_num = 0; + api->nhgid = *nhg_id; + if (api->nhgid) + SET_FLAG(api->message, ZAPI_MESSAGE_NHG); +}; + +extern enum zclient_send_status +zclient_send_mlag_register(struct zclient *client, uint32_t bit_map); +extern enum zclient_send_status +zclient_send_mlag_deregister(struct zclient *client); + +extern enum zclient_send_status zclient_send_mlag_data(struct zclient *client, + struct stream *client_s); + +/* + * Send an OPAQUE message, contents opaque to zebra - but note that + * the length of the payload is restricted by the zclient's + * outgoing message buffer. + * The message header is a message subtype; please use the registry + * below to avoid sub-type collisions. Clients use the registration + * apis to manage the specific opaque subtypes they want to receive. + */ +enum zclient_send_status zclient_send_opaque(struct zclient *zclient, + uint32_t type, const uint8_t *data, + size_t datasize); + +enum zclient_send_status +zclient_send_opaque_unicast(struct zclient *zclient, uint32_t type, + uint8_t proto, uint16_t instance, + uint32_t session_id, const uint8_t *data, + size_t datasize); + +/* Init functions also provided for clients who want to encode their + * data inline into the zclient's stream buffer. Please use these instead + * of hand-encoding the header info, since that may change over time. + * Note that these will reset the zclient's outbound stream before encoding. + */ +enum zclient_send_status zapi_opaque_init(struct zclient *zclient, + uint32_t type, uint16_t flags); + +enum zclient_send_status +zapi_opaque_unicast_init(struct zclient *zclient, uint32_t type, uint16_t flags, + uint8_t proto, uint16_t instance, uint32_t session_id); + +/* Struct representing the decoded opaque header info */ +struct zapi_opaque_msg { + uint32_t type; /* Subtype */ + uint16_t len; /* len after zapi header and this info */ + uint16_t flags; + + /* Sending client info */ + uint8_t src_proto; + uint16_t src_instance; + uint32_t src_session_id; + + /* Destination client info - *if* UNICAST flag is set */ + uint8_t dest_proto; + uint16_t dest_instance; + uint32_t dest_session_id; +}; + +#define ZAPI_OPAQUE_FLAG_UNICAST 0x01 + +/* Simple struct to convey registration/unreg requests */ +struct zapi_opaque_reg_info { + /* Message subtype */ + uint32_t type; + + /* Client session tuple */ + uint8_t proto; + uint16_t instance; + uint32_t session_id; +}; + +/* Simple struct conveying information about opaque notifications. + * Daemons can request notifications about the status of registration for + * opaque message types. For example, a client daemon can request notification + * when a server registers to receive a certain message code. Or a server can + * request notification when a subscriber registers for its output. + */ +struct zapi_opaque_notif_info { + bool request; /* Request to register, or notification from zebra */ + bool reg; /* Register or unregister */ + uint32_t msg_type; /* Target message code */ + + /* For notif registration, zapi info for the client. + * For notifications, zapi info for the message's server/registrant. + * For notification that there is no server/registrant, not present. + */ + uint8_t proto; + uint16_t instance; + uint32_t session_id; +}; + +/* The same ZAPI message is used for daemon->zebra requests, and for + * zebra->daemon notifications. + * Daemons send 'request' true, and 'reg' true or false. + * Zebra sends 'request' false, 'reg' set if the notification is a + * server/receiver registration for the message type, and false if the event + * is the end of registrations. + */ + +/* Decode incoming opaque */ +int zclient_opaque_decode(struct stream *msg, struct zapi_opaque_msg *info); + +enum zclient_send_status zclient_register_opaque(struct zclient *zclient, + uint32_t type); +enum zclient_send_status zclient_unregister_opaque(struct zclient *zclient, + uint32_t type); +int zapi_opaque_reg_decode(struct stream *msg, + struct zapi_opaque_reg_info *info); + +/* Opaque notification features */ +enum zclient_send_status zclient_opaque_request_notify(struct zclient *zclient, + uint32_t msgtype); +enum zclient_send_status zclient_opaque_drop_notify(struct zclient *zclient, + uint32_t msgtype); + +/* Encode, decode an incoming zapi opaque notification */ +int zclient_opaque_notif_encode(struct stream *s, uint32_t msg_type, + bool reg /* register or unreg*/, uint8_t proto, + uint16_t instance, uint32_t session_id); +int zclient_opaque_notif_decode(struct stream *s, + struct zapi_opaque_notif_info *info); + +/* + * Registry of opaque message types. Please do not reuse an in-use + * type code; some daemons are likely relying on it. + */ +enum zapi_opaque_registry { + /* Request link-state database dump, at restart for example */ + LINK_STATE_SYNC = 1, + /* Update containing link-state db info */ + LINK_STATE_UPDATE = 2, + /* Request LDP-SYNC state from LDP */ + LDP_IGP_SYNC_IF_STATE_REQUEST = 3, + /* Update containing LDP IGP Sync State info */ + LDP_IGP_SYNC_IF_STATE_UPDATE = 4, + /* Announce that LDP is up */ + LDP_IGP_SYNC_ANNOUNCE_UPDATE = 5, + /* Register RLFA with LDP */ + LDP_RLFA_REGISTER = 7, + /* Unregister all RLFAs with LDP */ + LDP_RLFA_UNREGISTER_ALL = 8, + /* Announce LDP labels associated to a previously registered RLFA */ + LDP_RLFA_LABELS = 9, +}; + +/* Send the hello message. + * Returns 0 for success or -1 on an I/O error. + */ +extern enum zclient_send_status zclient_send_hello(struct zclient *client); + +extern void zclient_register_neigh(struct zclient *zclient, vrf_id_t vrf_id, + afi_t afi, bool reg); + +extern enum zclient_send_status +zclient_send_neigh_discovery_req(struct zclient *zclient, + const struct interface *ifp, + const struct prefix *p); + +struct zapi_client_close_info { + /* Client session tuple */ + uint8_t proto; + uint16_t instance; + uint32_t session_id; +}; + +/* Decode incoming client close notify */ +extern int zapi_client_close_notify_decode(struct stream *s, + struct zapi_client_close_info *info); + +extern int zclient_send_zebra_gre_request(struct zclient *client, + struct interface *ifp); +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_ZCLIENT_H */ diff --git a/lib/zebra.h b/lib/zebra.h new file mode 100644 index 0000000..15a54f6 --- /dev/null +++ b/lib/zebra.h @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Zebra common header. + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_H +#define _ZEBRA_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "compiler.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_STROPTS_H +#include +#endif /* HAVE_STROPTS_H */ +#include +#include +#ifdef HAVE_SYS_SYSCTL_H +#ifdef GNU_LINUX +#include +#else +#include +#endif +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_SYS_CONF_H +#include +#endif /* HAVE_SYS_CONF_H */ +#ifdef HAVE_SYS_KSYM_H +#include +#endif /* HAVE_SYS_KSYM_H */ +#include +#include +#include +#ifdef HAVE_SYS_ENDIAN_H +#include +#endif +#ifdef HAVE_ENDIAN_H +#include +#endif + +/* misc include group */ +#include + +/* network include group */ + +#include + +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif /* HAVE_SYS_SOCKIO_H */ + +#ifndef HAVE_LIBCRYPT +#ifdef HAVE_LIBCRYPTO +#include +# define crypt DES_crypt +#endif +#endif + +#include "openbsd-tree.h" + +#include +#include +#include + +#ifdef HAVE_NET_NETOPT_H +#include +#endif /* HAVE_NET_NETOPT_H */ + +#include + +#ifdef HAVE_NET_IF_DL_H +#include +#endif /* HAVE_NET_IF_DL_H */ + +#ifdef HAVE_NET_IF_VAR_H +#include +#endif /* HAVE_NET_IF_VAR_H */ + +#ifndef HAVE_NETLINK +#define RT_TABLE_MAIN 0 +#define RT_TABLE_LOCAL RT_TABLE_MAIN +#endif /* HAVE_NETLINK */ + +#include +#include + +#ifdef HAVE_INET_ND_H +#include +#endif /* HAVE_INET_ND_H */ + +#ifdef HAVE_NETINET_IN_VAR_H +#include +#endif /* HAVE_NETINET_IN_VAR_H */ + +#ifdef HAVE_NETINET6_IN6_VAR_H +#include +#endif /* HAVE_NETINET6_IN6_VAR_H */ + +#ifdef HAVE_NETINET_IN6_VAR_H +#include +#endif /* HAVE_NETINET_IN6_VAR_H */ + +#ifdef HAVE_NETINET6_IN_H +#include +#endif /* HAVE_NETINET6_IN_H */ + +#ifdef HAVE_NETINET6_IP6_H +#include +#endif /* HAVE_NETINET6_IP6_H */ + +#ifdef HAVE_NETINET6_ND6_H +#include +#endif /* HAVE_NETINET6_ND6_H */ + +/* Local includes: */ +#if !defined(__GNUC__) +#define __attribute__(x) +#endif /* !__GNUC__ */ + +#include + +/* + * Add explicit static cast only when using a C++ compiler. + */ +#ifdef __cplusplus +#define static_cast(l, r) static_cast((r)) +#else +#define static_cast(l, r) (r) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_STRLCAT +size_t strlcat(char *__restrict dest, + const char *__restrict src, size_t destsize); +#endif +#ifndef HAVE_STRLCPY +size_t strlcpy(char *__restrict dest, + const char *__restrict src, size_t destsize); +#endif + +#ifndef HAVE_EXPLICIT_BZERO +void explicit_bzero(void *buf, size_t len); +#endif + +/* + * RFC 3542 defines several macros for using struct cmsghdr. + * Here, we define those that are not present + */ + +/* + * Internal defines, for use only in this file. + * These are likely wrong on other than ILP32 machines, so warn. + */ +#ifndef _CMSG_DATA_ALIGN +#define _CMSG_DATA_ALIGN(n) (((n) + 3) & ~3) +#endif /* _CMSG_DATA_ALIGN */ + +#ifndef _CMSG_HDR_ALIGN +#define _CMSG_HDR_ALIGN(n) (((n) + 3) & ~3) +#endif /* _CMSG_HDR_ALIGN */ + +/* + * CMSG_SPACE and CMSG_LEN are required in RFC3542, but were new in that + * version. + */ +#ifndef CMSG_SPACE +#define CMSG_SPACE(l) \ + (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + _CMSG_HDR_ALIGN(l)) +#warning "assuming 4-byte alignment for CMSG_SPACE" +#endif /* CMSG_SPACE */ + + +#ifndef CMSG_LEN +#define CMSG_LEN(l) (_CMSG_DATA_ALIGN(sizeof(struct cmsghdr)) + (l)) +#warning "assuming 4-byte alignment for CMSG_LEN" +#endif /* CMSG_LEN */ + + +/* The definition of struct in_pktinfo is missing in old version of + GLIBC 2.1 (Redhat 6.1). */ +#if defined(GNU_LINUX) && !defined(HAVE_STRUCT_IN_PKTINFO) +struct in_pktinfo { + int ipi_ifindex; + struct in_addr ipi_spec_dst; + struct in_addr ipi_addr; +}; +#endif + +/* + * IP_HDRINCL / struct ip byte order + * + * Linux: network byte order + * *BSD: network, except for length and offset. (cf Stevens) + * SunOS: nominally as per BSD. but bug: network order on LE. + * OpenBSD: network byte order, apart from older versions which are as per + * *BSD + */ +#if defined(__NetBSD__) || \ + (defined(__FreeBSD__) && (__FreeBSD_version < 1100030)) || \ + (defined(__OpenBSD__) && (OpenBSD < 200311)) +#define HAVE_IP_HDRINCL_BSD_ORDER +#endif + +/* autoconf macros for this are deprecated, just find endian.h */ +#ifndef BYTE_ORDER +#error please locate an endian.h file appropriate to your platform +#endif + +/* For old definition. */ +#ifndef IN6_ARE_ADDR_EQUAL +#define IN6_ARE_ADDR_EQUAL IN6_IS_ADDR_EQUAL +#endif /* IN6_ARE_ADDR_EQUAL */ + +/* Zebra route's types are defined in route_types.h */ +#include "lib/route_types.h" + +#define strmatch(a,b) (!strcmp((a), (b))) + +#if BYTE_ORDER == LITTLE_ENDIAN +#define htonll(x) (((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)) +#define ntohll(x) (((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +#else +#define htonll(x) (x) +#define ntohll(x) (x) +#endif + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001 /* Internet address 127.0.0.1. */ +#endif + +/* Address family numbers from RFC1700. */ +typedef enum { + AFI_UNSPEC = 0, + AFI_IP = 1, + AFI_IP6 = 2, + AFI_L2VPN = 3, + AFI_MAX = 4 +} afi_t; + +#define IS_VALID_AFI(a) ((a) > AFI_UNSPEC && (a) < AFI_MAX) + +/* Subsequent Address Family Identifier. */ +typedef enum { + SAFI_UNSPEC = 0, + SAFI_UNICAST = 1, + SAFI_MULTICAST = 2, + SAFI_MPLS_VPN = 3, + SAFI_ENCAP = 4, + SAFI_EVPN = 5, + SAFI_LABELED_UNICAST = 6, + SAFI_FLOWSPEC = 7, + SAFI_MAX = 8 +} safi_t; + +#define FOREACH_AFI_SAFI(afi, safi) \ + for (afi = AFI_IP; afi < AFI_MAX; afi++) \ + for (safi = SAFI_UNICAST; safi < SAFI_MAX; safi++) + +#define FOREACH_AFI_SAFI_NSF(afi, safi) \ + for (afi = AFI_IP; afi < AFI_MAX; afi++) \ + for (safi = SAFI_UNICAST; safi <= SAFI_MPLS_VPN; safi++) + +/* Flag manipulation macros. */ +#define CHECK_FLAG(V,F) ((V) & (F)) +#define SET_FLAG(V,F) (V) |= (F) +#define UNSET_FLAG(V,F) (V) &= ~(F) +#define RESET_FLAG(V) (V) = 0 +#define COND_FLAG(V, F, C) ((C) ? (SET_FLAG(V, F)) : (UNSET_FLAG(V, F))) + +/* Atomic flag manipulation macros. */ +#define CHECK_FLAG_ATOMIC(PV, F) \ + ((atomic_load_explicit(PV, memory_order_seq_cst)) & (F)) +#define SET_FLAG_ATOMIC(PV, F) \ + ((atomic_fetch_or_explicit(PV, (F), memory_order_seq_cst))) +#define UNSET_FLAG_ATOMIC(PV, F) \ + ((atomic_fetch_and_explicit(PV, ~(F), memory_order_seq_cst))) +#define RESET_FLAG_ATOMIC(PV) \ + ((atomic_store_explicit(PV, 0, memory_order_seq_cst))) + +/* VRF ID type. */ +typedef uint32_t vrf_id_t; + +typedef uint32_t route_tag_t; +#define ROUTE_TAG_MAX UINT32_MAX +#define ROUTE_TAG_PRI PRIu32 + +#ifdef __cplusplus +} +#endif + +#endif /* _ZEBRA_H */ diff --git a/lib/zlog.c b/lib/zlog.c new file mode 100644 index 0000000..ffca572 --- /dev/null +++ b/lib/zlog.c @@ -0,0 +1,1163 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc. + */ + +#include "zebra.h" +#include +#include + +#ifdef HAVE_GLIBC_BACKTRACE +#include +#endif /* HAVE_GLIBC_BACKTRACE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* gettid() & co. */ +#ifdef HAVE_PTHREAD_NP_H +#include +#endif +#ifdef linux +#include +#endif +#ifdef __FreeBSD__ +#include +#endif +#ifdef __NetBSD__ +#include +#endif +#ifdef __DragonFly__ +#include +#endif + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include +#include +#endif + +#include "memory.h" +#include "atomlist.h" +#include "printfrr.h" +#include "frrcu.h" +#include "zlog.h" +#include "zlog_live.h" +#include "libfrr_trace.h" +#include "frrevent.h" + +DEFINE_MTYPE_STATIC(LIB, LOG_MESSAGE, "log message"); +DEFINE_MTYPE_STATIC(LIB, LOG_TLSBUF, "log thread-local buffer"); + +DEFINE_HOOK(zlog_init, (const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid), + (progname, protoname, instance, uid, gid)); +DEFINE_KOOH(zlog_fini, (), ()); +DEFINE_HOOK(zlog_aux_init, (const char *prefix, int prio_min), + (prefix, prio_min)); + +char zlog_prefix[128]; +size_t zlog_prefixsz; +int zlog_tmpdirfd = -1; +int zlog_instance = -1; + +static atomic_bool zlog_ec = true, zlog_xid = true; + +/* these are kept around because logging is initialized (and directories + * & files created) before zprivs code switches to the FRR user; therefore + * we need to chown() things so we don't get permission errors later when + * trying to delete things on shutdown + */ +static uid_t zlog_uid = -1; +static gid_t zlog_gid = -1; + +DECLARE_ATOMLIST(zlog_targets, struct zlog_target, head); +static struct zlog_targets_head zlog_targets; + +/* Global setting for buffered vs immediate output. The default is + * per-pthread buffering. + */ +static bool zlog_default_immediate; + +/* cf. zlog.h for additional comments on this struct. + * + * Note: you MUST NOT pass the format string + va_list to non-FRR format + * string functions (e.g. vsyslog, sd_journal_printv, ...) since FRR uses an + * extended prinf() with additional formats (%pI4 and the like). + * + * Also remember to use va_copy() on args. + */ + +struct zlog_msg { + struct timespec ts; + int prio; + + const char *fmt; + va_list args; + const struct xref_logmsg *xref; + + char *stackbuf; + size_t stackbufsz; + char *text; + size_t textlen; + size_t hdrlen; + + /* for relayed log messages ONLY (cf. zlog_recirculate_live_msg) */ + intmax_t pid, tid; + + /* This is always ISO8601 with sub-second precision 9 here, it's + * converted for callers as needed. ts_dot points to the "." + * separating sub-seconds. ts_zonetail is "Z" or "+00:00" for the + * local time offset. + * + * Valid if ZLOG_TS_ISO8601 is set. + * (0 if timestamp has not been formatted yet) + */ + char ts_str[32], *ts_dot, ts_zonetail[8]; + uint32_t ts_flags; + + /* "mmm dd hh:mm:ss" for 3164 legacy syslog - too dissimilar from + * the above, so just kept separately here. + */ + uint32_t ts_3164_flags; + char ts_3164_str[16]; + + /* at the time of writing, 16 args was the actual maximum of arguments + * to a single zlog call. Particularly printing flag bitmasks seems + * to drive this. That said, the overhead of dynamically sizing this + * probably outweighs the value. If anything, a printfrr extension + * for printing flag bitmasks might be a good idea. + */ + struct fmt_outpos argpos[24]; + size_t n_argpos; +}; + +/* thread-local log message buffering + * + * This is strictly optional and set up by calling zlog_tls_buffer_init() + * on a particular thread. + * + * If in use, this will create a temporary file in /var/tmp which is used as + * memory-mapped MAP_SHARED log message buffer. The idea there is that buffer + * access doesn't require any syscalls, but in case of a crash the kernel + * knows to sync the memory back to disk. This way the user can still get the + * last log messages if there were any left unwritten in the buffer. + * + * Sizing this dynamically isn't particularly useful, so here's an 8k buffer + * with a message limit of 64 messages. Message metadata (e.g. priority, + * timestamp) aren't in the mmap region, so they're lost on crash, but we can + * live with that. + */ + +#if defined(HAVE_OPENAT) && defined(HAVE_UNLINKAT) +#define CAN_DO_TLS 1 +#endif + +#define TLS_LOG_BUF_SIZE 8192 +#define TLS_LOG_MAXMSG 64 + +struct zlog_tls { + char *mmbuf; + size_t bufpos; + bool do_unlink; + + size_t nmsgs; + struct zlog_msg msgs[TLS_LOG_MAXMSG]; + struct zlog_msg *msgp[TLS_LOG_MAXMSG]; +}; + +static inline void zlog_tls_free(void *arg); + +/* proper ELF TLS is a bit faster than pthread_[gs]etspecific, so if it's + * available we'll use it here + */ + +#ifdef __OpenBSD__ +static pthread_key_t zlog_tls_key; + +static void zlog_tls_key_init(void) __attribute__((_CONSTRUCTOR(500))); +static void zlog_tls_key_init(void) +{ + pthread_key_create(&zlog_tls_key, zlog_tls_free); +} + +static void zlog_tls_key_fini(void) __attribute__((_DESTRUCTOR(500))); +static void zlog_tls_key_fini(void) +{ + pthread_key_delete(zlog_tls_key); +} + +static inline struct zlog_tls *zlog_tls_get(void) +{ + return pthread_getspecific(zlog_tls_key); +} + +static inline void zlog_tls_set(struct zlog_tls *val) +{ + pthread_setspecific(zlog_tls_key, val); +} +#else +# ifndef thread_local +# define thread_local __thread +# endif + +static thread_local struct zlog_tls *zlog_tls_var + __attribute__((tls_model("initial-exec"))); + +static inline struct zlog_tls *zlog_tls_get(void) +{ + return zlog_tls_var; +} + +static inline void zlog_tls_set(struct zlog_tls *val) +{ + zlog_tls_var = val; +} +#endif + +#ifdef CAN_DO_TLS +static intmax_t zlog_gettid(void) +{ +#ifndef __OpenBSD__ + /* accessing a TLS variable is much faster than a syscall */ + static thread_local intmax_t cached_tid = -1; + if (cached_tid != -1) + return cached_tid; +#endif + + long rv = -1; +#ifdef HAVE_PTHREAD_GETTHREADID_NP + rv = pthread_getthreadid_np(); +#elif defined(linux) + rv = syscall(__NR_gettid); +#elif defined(__NetBSD__) + rv = _lwp_self(); +#elif defined(__FreeBSD__) + thr_self(&rv); +#elif defined(__DragonFly__) + rv = lwp_gettid(); +#elif defined(__OpenBSD__) + rv = getthrid(); +#elif defined(__sun) + rv = pthread_self(); +#elif defined(__APPLE__) + rv = mach_thread_self(); + mach_port_deallocate(mach_task_self(), rv); +#endif + +#ifndef __OpenBSD__ + cached_tid = rv; +#endif + return rv; +} + +void zlog_tls_buffer_init(void) +{ + struct zlog_tls *zlog_tls; + char mmpath[MAXPATHLEN]; + int mmfd; + size_t i; + + zlog_tls = zlog_tls_get(); + + if (zlog_tls || zlog_tmpdirfd < 0) + return; + + zlog_tls = XCALLOC(MTYPE_LOG_TLSBUF, sizeof(*zlog_tls)); + for (i = 0; i < array_size(zlog_tls->msgp); i++) + zlog_tls->msgp[i] = &zlog_tls->msgs[i]; + + snprintfrr(mmpath, sizeof(mmpath), "logbuf.%jd", zlog_gettid()); + + mmfd = openat(zlog_tmpdirfd, mmpath, + O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600); + if (mmfd < 0) { + zlog_err("failed to open thread log buffer \"%s\": %s", + mmpath, strerror(errno)); + goto out_anon; + } + fchown(mmfd, zlog_uid, zlog_gid); + +#ifdef HAVE_POSIX_FALLOCATE + if (posix_fallocate(mmfd, 0, TLS_LOG_BUF_SIZE) != 0) + /* note next statement is under above if() */ +#endif + if (ftruncate(mmfd, TLS_LOG_BUF_SIZE) < 0) { + zlog_err("failed to allocate thread log buffer \"%s\": %s", + mmpath, strerror(errno)); + goto out_anon_unlink; + } + + zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, mmfd, 0); + if (zlog_tls->mmbuf == MAP_FAILED) { + zlog_err("failed to mmap thread log buffer \"%s\": %s", + mmpath, strerror(errno)); + goto out_anon_unlink; + } + zlog_tls->do_unlink = true; + + close(mmfd); + zlog_tls_set(zlog_tls); + return; + +out_anon_unlink: + unlinkat(zlog_tmpdirfd, mmpath, 0); + close(mmfd); +out_anon: + +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif + zlog_tls->mmbuf = mmap(NULL, TLS_LOG_BUF_SIZE, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + + if (!zlog_tls->mmbuf) { + zlog_err("failed to anonymous-mmap thread log buffer: %s", + strerror(errno)); + XFREE(MTYPE_LOG_TLSBUF, zlog_tls); + zlog_tls_set(NULL); + return; + } + + zlog_tls_set(zlog_tls); +} + +void zlog_tls_buffer_fini(void) +{ + char mmpath[MAXPATHLEN]; + struct zlog_tls *zlog_tls = zlog_tls_get(); + bool do_unlink = zlog_tls ? zlog_tls->do_unlink : false; + + zlog_tls_buffer_flush(); + + zlog_tls_free(zlog_tls); + zlog_tls_set(NULL); + + snprintfrr(mmpath, sizeof(mmpath), "logbuf.%jd", zlog_gettid()); + if (do_unlink && unlinkat(zlog_tmpdirfd, mmpath, 0)) + zlog_err("unlink logbuf: %s (%d)", strerror(errno), errno); +} + +#else /* !CAN_DO_TLS */ +void zlog_tls_buffer_init(void) +{ +} + +void zlog_tls_buffer_fini(void) +{ +} +#endif + +void zlog_msg_pid(struct zlog_msg *msg, intmax_t *pid, intmax_t *tid) +{ +#ifndef __OpenBSD__ + static thread_local intmax_t cached_pid = -1; +#endif + + /* recirculated messages */ + if (msg->pid) { + *pid = msg->pid; + *tid = msg->tid; + return; + } + +#ifndef __OpenBSD__ + if (cached_pid != -1) + *pid = cached_pid; + else + cached_pid = *pid = (intmax_t)getpid(); +#else + *pid = (intmax_t)getpid(); +#endif +#ifdef CAN_DO_TLS + *tid = zlog_gettid(); +#else + *tid = *pid; +#endif +} + +static inline void zlog_tls_free(void *arg) +{ + struct zlog_tls *zlog_tls = arg; + + if (!zlog_tls) + return; + + munmap(zlog_tls->mmbuf, TLS_LOG_BUF_SIZE); + XFREE(MTYPE_LOG_TLSBUF, zlog_tls); +} + +void zlog_tls_buffer_flush(void) +{ + struct zlog_target *zt; + struct zlog_tls *zlog_tls = zlog_tls_get(); + + if (!zlog_tls) + return; + if (!zlog_tls->nmsgs) + return; + + rcu_read_lock(); + frr_each_safe (zlog_targets, &zlog_targets, zt) { + if (!zt->logfn) + continue; + + zt->logfn(zt, zlog_tls->msgp, zlog_tls->nmsgs); + } + rcu_read_unlock(); + + zlog_tls->bufpos = 0; + zlog_tls->nmsgs = 0; +} + + +static void vzlog_notls(const struct xref_logmsg *xref, int prio, + const char *fmt, va_list ap) +{ + struct zlog_target *zt; + struct zlog_msg stackmsg = { + .prio = prio & LOG_PRIMASK, + .fmt = fmt, + .xref = xref, + }, *msg = &stackmsg; + char stackbuf[512]; + + clock_gettime(CLOCK_REALTIME, &msg->ts); + va_copy(msg->args, ap); + msg->stackbuf = stackbuf; + msg->stackbufsz = sizeof(stackbuf); + + rcu_read_lock(); + frr_each_safe (zlog_targets, &zlog_targets, zt) { + if (prio > zt->prio_min) + continue; + if (!zt->logfn) + continue; + + zt->logfn(zt, &msg, 1); + } + rcu_read_unlock(); + + va_end(msg->args); + if (msg->text && msg->text != stackbuf) + XFREE(MTYPE_LOG_MESSAGE, msg->text); +} + +static void vzlog_tls(struct zlog_tls *zlog_tls, const struct xref_logmsg *xref, + int prio, const char *fmt, va_list ap) +{ + struct zlog_target *zt; + struct zlog_msg *msg; + char *buf; + bool ignoremsg = true; + bool immediate = zlog_default_immediate; + + /* avoid further processing cost if no target wants this message */ + rcu_read_lock(); + frr_each (zlog_targets, &zlog_targets, zt) { + if (prio > zt->prio_min) + continue; + ignoremsg = false; + break; + } + rcu_read_unlock(); + + if (ignoremsg) + return; + + msg = &zlog_tls->msgs[zlog_tls->nmsgs]; + zlog_tls->nmsgs++; + if (zlog_tls->nmsgs == array_size(zlog_tls->msgs)) + immediate = true; + + memset(msg, 0, sizeof(*msg)); + clock_gettime(CLOCK_REALTIME, &msg->ts); + va_copy(msg->args, ap); + msg->stackbuf = buf = zlog_tls->mmbuf + zlog_tls->bufpos; + msg->stackbufsz = TLS_LOG_BUF_SIZE - zlog_tls->bufpos - 1; + msg->fmt = fmt; + msg->prio = prio & LOG_PRIMASK; + msg->xref = xref; + if (msg->prio < LOG_INFO) + immediate = true; + + if (!immediate) { + /* messages written later need to take the formatting cost + * immediately since we can't hold a reference on varargs + */ + zlog_msg_text(msg, NULL); + + if (msg->text != buf) + /* zlog_msg_text called malloc() on us :( */ + immediate = true; + else { + zlog_tls->bufpos += msg->textlen + 1; + /* write a second \0 to mark current end position + * (in case of crash this signals end of unwritten log + * messages in mmap'd logbuf file) + */ + zlog_tls->mmbuf[zlog_tls->bufpos] = '\0'; + + /* avoid malloc() for next message */ + if (TLS_LOG_BUF_SIZE - zlog_tls->bufpos < 256) + immediate = true; + } + } + + if (immediate) + zlog_tls_buffer_flush(); + + va_end(msg->args); + if (msg->text && msg->text != buf) + XFREE(MTYPE_LOG_MESSAGE, msg->text); +} + +/* reinject log message received by zlog_recirculate_recv(). As of writing, + * only used in the ldpd parent process to proxy messages from lde/ldpe + * subprocesses. + */ +void zlog_recirculate_live_msg(uint8_t *data, size_t len) +{ + struct zlog_target *zt; + struct zlog_msg stackmsg = {}, *msg = &stackmsg; + struct zlog_live_hdr *hdr; + struct xrefdata *xrefdata, ref = {}; + + if (len < sizeof(*hdr)) + return; + + hdr = (struct zlog_live_hdr *)data; + if (hdr->hdrlen < sizeof(*hdr)) + return; + data += hdr->hdrlen; + len -= sizeof(*hdr); + + msg->ts.tv_sec = hdr->ts_sec; + msg->ts.tv_nsec = hdr->ts_nsec; + msg->pid = hdr->pid; + msg->tid = hdr->tid; + msg->prio = hdr->prio; + + if (hdr->textlen > len) + return; + msg->textlen = hdr->textlen; + msg->hdrlen = hdr->texthdrlen; + msg->text = (char *)data; + + /* caller needs to make sure we have a trailing \n\0, it's not + * transmitted on zlog_live + */ + if (msg->text[msg->textlen] != '\n' || + msg->text[msg->textlen + 1] != '\0') + return; + + static_assert(sizeof(msg->argpos[0]) == sizeof(hdr->argpos[0]), + "in-memory struct doesn't match on-wire variant"); + msg->n_argpos = MIN(hdr->n_argpos, array_size(msg->argpos)); + memcpy(msg->argpos, hdr->argpos, msg->n_argpos * sizeof(msg->argpos[0])); + + /* This will only work if we're in the same daemon: we received a log + * message uid and are now doing a lookup in *our* known uids to find + * it. This works for ldpd because it's the same binary containing the + * same log messages, and ldpd is the only use case right now. + * + * When the uid is not found, the log message uid is lost but the + * message itself is still processed correctly. If this is needed, + * this can be made to work in two ways: + * (a) synthesize a temporary xref_logmsg from the received data. + * This is a bit annoying due to lifetimes with per-thread buffers. + * (b) extract and aggregate all log messages. This already happens + * with frr.xref but that would need to be fed back in. + */ + strlcpy(ref.uid, hdr->uid, sizeof(ref.uid)); + xrefdata = xrefdata_uid_find(&xrefdata_uid, &ref); + + if (xrefdata && xrefdata->xref->type == XREFT_LOGMSG) { + struct xref_logmsg *xref_logmsg; + + xref_logmsg = (struct xref_logmsg *)xrefdata->xref; + msg->xref = xref_logmsg; + msg->fmt = xref_logmsg->fmtstring; + } else { + /* fake out format string... */ + msg->fmt = msg->text + hdr->texthdrlen; + } + + rcu_read_lock(); + frr_each_safe (zlog_targets, &zlog_targets, zt) { + if (msg->prio > zt->prio_min) + continue; + if (!zt->logfn) + continue; + + zt->logfn(zt, &msg, 1); + } + rcu_read_unlock(); +} + +static void zlog_backtrace_msg(const struct xref_logmsg *xref, int prio) +{ + struct event *tc = pthread_getspecific(thread_current); + const char *uid = xref->xref.xrefdata->uid; + bool found_thread = false; + + zlog(prio, "| (%s) message in thread %jd, at %s(), %s:%d", uid, + zlog_gettid(), xref->xref.func, xref->xref.file, xref->xref.line); + +#ifdef HAVE_LIBUNWIND + const char *threadfunc = tc ? tc->xref->funcname : NULL; + bool found_caller = false; + unw_cursor_t cursor; + unw_context_t uc; + unw_word_t ip, off, sp; + Dl_info dlinfo; + + unw_getcontext(&uc); + + unw_init_local(&cursor, &uc); + while (unw_step(&cursor) > 0) { + char buf[96], name[128] = "?"; + bool is_thread = false; + + unw_get_reg(&cursor, UNW_REG_IP, &ip); + unw_get_reg(&cursor, UNW_REG_SP, &sp); + + if (unw_is_signal_frame(&cursor)) + zlog(prio, "| (%s) ---- signal ----", uid); + + if (!unw_get_proc_name(&cursor, buf, sizeof(buf), &off)) { + if (!strcmp(buf, xref->xref.func)) + found_caller = true; + if (threadfunc && !strcmp(buf, threadfunc)) + found_thread = is_thread = true; + + snprintf(name, sizeof(name), "%s+%#lx", buf, (long)off); + } + + if (!found_caller) + continue; + + if (dladdr((void *)ip, &dlinfo)) + zlog(prio, "| (%s) %-36s %16lx+%08lx %16lx %s", uid, + name, (long)dlinfo.dli_fbase, + (long)ip - (long)dlinfo.dli_fbase, (long)sp, + dlinfo.dli_fname); + else + zlog(prio, "| (%s) %-36s %16lx %16lx", uid, name, + (long)ip, (long)sp); + + if (is_thread) + zlog(prio, "| (%s) ^- scheduled from %s(), %s:%u", uid, + tc->xref->xref.func, tc->xref->xref.file, + tc->xref->xref.line); + } +#elif defined(HAVE_GLIBC_BACKTRACE) + void *frames[64]; + char **names = NULL; + int n_frames, i; + + n_frames = backtrace(frames, array_size(frames)); + if (n_frames < 0) + n_frames = 0; + if (n_frames) + names = backtrace_symbols(frames, n_frames); + + for (i = 0; i < n_frames; i++) { + void *retaddr = frames[i]; + char *loc = names[i]; + + zlog(prio, "| (%s) %16lx %-36s", uid, (long)retaddr, loc); + } + free(names); +#endif + if (!found_thread && tc) + zlog(prio, "| (%s) scheduled from %s(), %s:%u", uid, + tc->xref->xref.func, tc->xref->xref.file, + tc->xref->xref.line); +} + +void vzlogx(const struct xref_logmsg *xref, int prio, + const char *fmt, va_list ap) +{ + struct zlog_tls *zlog_tls = zlog_tls_get(); + +#ifdef HAVE_LTTNG + va_list copy; + va_copy(copy, ap); + char *msg = vasprintfrr(MTYPE_LOG_MESSAGE, fmt, copy); + + switch (prio) { + case LOG_ERR: + frrtracelog(TRACE_ERR, msg); + break; + case LOG_WARNING: + frrtracelog(TRACE_WARNING, msg); + break; + case LOG_DEBUG: + frrtracelog(TRACE_DEBUG, msg); + break; + case LOG_NOTICE: + frrtracelog(TRACE_DEBUG, msg); + break; + case LOG_INFO: + default: + frrtracelog(TRACE_INFO, msg); + break; + } + + va_end(copy); + XFREE(MTYPE_LOG_MESSAGE, msg); +#endif + + if (zlog_tls) + vzlog_tls(zlog_tls, xref, prio, fmt, ap); + else + vzlog_notls(xref, prio, fmt, ap); + + if (xref) { + struct xrefdata_logmsg *xrdl; + + xrdl = container_of(xref->xref.xrefdata, struct xrefdata_logmsg, + xrefdata); + if (xrdl->fl_print_bt) + zlog_backtrace_msg(xref, prio); + } +} + +void zlog_sigsafe(const char *text, size_t len) +{ + struct zlog_target *zt; + const char *end = text + len, *nlpos; + + while (text < end) { + nlpos = memchr(text, '\n', end - text); + if (!nlpos) + nlpos = end; + + frr_each (zlog_targets, &zlog_targets, zt) { + if (LOG_CRIT > zt->prio_min) + continue; + if (!zt->logfn_sigsafe) + continue; + + zt->logfn_sigsafe(zt, text, nlpos - text); + } + + if (nlpos == end) + break; + text = nlpos + 1; + } +} + +void _zlog_assert_failed(const struct xref_assert *xref, const char *extra, ...) +{ + va_list ap; + static bool assert_in_assert; /* "global-ish" variable, init to 0 */ + + if (assert_in_assert) + abort(); + assert_in_assert = true; + + if (extra) { + struct va_format vaf; + + va_start(ap, extra); + vaf.fmt = extra; + vaf.va = ≈ + + zlog(LOG_CRIT, + "%s:%d: %s(): assertion (%s) failed, extra info: %pVA", + xref->xref.file, xref->xref.line, xref->xref.func, + xref->expr, &vaf); + + va_end(ap); + } else + zlog(LOG_CRIT, "%s:%d: %s(): assertion (%s) failed", + xref->xref.file, xref->xref.line, xref->xref.func, + xref->expr); + + /* abort() prints backtrace & memstats in SIGABRT handler */ + abort(); +} + +int zlog_msg_prio(struct zlog_msg *msg) +{ + return msg->prio; +} + +const struct xref_logmsg *zlog_msg_xref(struct zlog_msg *msg) +{ + return msg->xref; +} + +const char *zlog_msg_text(struct zlog_msg *msg, size_t *textlen) +{ + if (!msg->text) { + va_list args; + bool do_xid, do_ec; + size_t need = 0, hdrlen; + struct fbuf fb = { + .buf = msg->stackbuf, + .pos = msg->stackbuf, + .len = msg->stackbufsz, + }; + + do_ec = atomic_load_explicit(&zlog_ec, memory_order_relaxed); + do_xid = atomic_load_explicit(&zlog_xid, memory_order_relaxed); + + if (msg->xref && do_xid && msg->xref->xref.xrefdata->uid[0]) { + need += bputch(&fb, '['); + need += bputs(&fb, msg->xref->xref.xrefdata->uid); + need += bputch(&fb, ']'); + } + if (msg->xref && do_ec && msg->xref->ec) + need += bprintfrr(&fb, "[EC %u]", msg->xref->ec); + if (need) + need += bputch(&fb, ' '); + + msg->hdrlen = hdrlen = need; + assert(hdrlen < msg->stackbufsz); + + fb.outpos = msg->argpos; + fb.outpos_n = array_size(msg->argpos); + fb.outpos_i = 0; + + va_copy(args, msg->args); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* format-string checking is done further up the chain */ + need += vbprintfrr(&fb, msg->fmt, args); +#pragma GCC diagnostic pop + va_end(args); + + msg->textlen = need; + need += bputch(&fb, '\n'); + + if (need <= msg->stackbufsz) + msg->text = msg->stackbuf; + else { + msg->text = XMALLOC(MTYPE_LOG_MESSAGE, need); + + memcpy(msg->text, msg->stackbuf, hdrlen); + + fb.buf = msg->text; + fb.len = need; + fb.pos = msg->text + hdrlen; + fb.outpos_i = 0; + + va_copy(args, msg->args); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + /* same as above */ + vbprintfrr(&fb, msg->fmt, args); +#pragma GCC diagnostic pop + va_end(args); + + bputch(&fb, '\n'); + } + + msg->n_argpos = fb.outpos_i; + } + if (textlen) + *textlen = msg->textlen; + return msg->text; +} + +void zlog_msg_args(struct zlog_msg *msg, size_t *hdrlen, size_t *n_argpos, + const struct fmt_outpos **argpos) +{ + if (!msg->text) + zlog_msg_text(msg, NULL); + + if (hdrlen) + *hdrlen = msg->hdrlen; + if (n_argpos) + *n_argpos = msg->n_argpos; + if (argpos) + *argpos = msg->argpos; +} + +#define ZLOG_TS_FORMAT (ZLOG_TS_ISO8601 | ZLOG_TS_LEGACY) +#define ZLOG_TS_FLAGS ~ZLOG_TS_PREC + +size_t zlog_msg_ts(struct zlog_msg *msg, struct fbuf *out, uint32_t flags) +{ + size_t outsz = out ? (out->buf + out->len - out->pos) : 0; + size_t len1; + + if (!(flags & ZLOG_TS_FORMAT)) + return 0; + + if (!(msg->ts_flags & ZLOG_TS_FORMAT) || + ((msg->ts_flags ^ flags) & ZLOG_TS_UTC)) { + struct tm tm; + + if (flags & ZLOG_TS_UTC) + gmtime_r(&msg->ts.tv_sec, &tm); + else + localtime_r(&msg->ts.tv_sec, &tm); + + strftime(msg->ts_str, sizeof(msg->ts_str), + "%Y-%m-%dT%H:%M:%S", &tm); + + if (flags & ZLOG_TS_UTC) { + msg->ts_zonetail[0] = 'Z'; + msg->ts_zonetail[1] = '\0'; + } else + snprintfrr(msg->ts_zonetail, sizeof(msg->ts_zonetail), + "%+03d:%02d", + (int)(tm.tm_gmtoff / 3600), + (int)(labs(tm.tm_gmtoff) / 60) % 60); + + msg->ts_dot = msg->ts_str + strlen(msg->ts_str); + snprintfrr(msg->ts_dot, + msg->ts_str + sizeof(msg->ts_str) - msg->ts_dot, + ".%09lu", (unsigned long)msg->ts.tv_nsec); + + msg->ts_flags = ZLOG_TS_ISO8601 | (flags & ZLOG_TS_UTC); + } + + len1 = flags & ZLOG_TS_PREC; + len1 = (msg->ts_dot - msg->ts_str) + (len1 ? len1 + 1 : 0); + + if (len1 > strlen(msg->ts_str)) + len1 = strlen(msg->ts_str); + + if (flags & ZLOG_TS_LEGACY) { + if (!out) + return len1; + + if (len1 > outsz) { + memset(out->pos, 0, outsz); + out->pos += outsz; + return len1; + } + + /* just swap out the formatting, faster than redoing it */ + for (char *p = msg->ts_str; p < msg->ts_str + len1; p++) { + switch (*p) { + case '-': + *out->pos++ = '/'; + break; + case 'T': + *out->pos++ = ' '; + break; + default: + *out->pos++ = *p; + } + } + return len1; + } else { + size_t len2 = strlen(msg->ts_zonetail); + + if (!out) + return len1 + len2; + + if (len1 + len2 > outsz) { + memset(out->pos, 0, outsz); + out->pos += outsz; + return len1 + len2; + } + + memcpy(out->pos, msg->ts_str, len1); + out->pos += len1; + memcpy(out->pos, msg->ts_zonetail, len2); + out->pos += len2; + return len1 + len2; + } +} + +size_t zlog_msg_ts_3164(struct zlog_msg *msg, struct fbuf *out, uint32_t flags) +{ + flags &= ZLOG_TS_UTC; + + if (!msg->ts_3164_str[0] || flags != msg->ts_3164_flags) { + /* these are "hardcoded" in RFC3164, so they're here too... */ + static const char *const months[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + struct tm tm; + + /* RFC3164 explicitly asks for local time, but common usage + * also includes UTC. + */ + if (flags & ZLOG_TS_UTC) + gmtime_r(&msg->ts.tv_sec, &tm); + else + localtime_r(&msg->ts.tv_sec, &tm); + + snprintfrr(msg->ts_3164_str, sizeof(msg->ts_3164_str), + "%3s %2d %02d:%02d:%02d", months[tm.tm_mon], + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + msg->ts_3164_flags = flags; + } + return bputs(out, msg->ts_3164_str); +} + +void zlog_msg_tsraw(struct zlog_msg *msg, struct timespec *ts) +{ + memcpy(ts, &msg->ts, sizeof(*ts)); +} + +void zlog_set_prefix_ec(bool enable) +{ + atomic_store_explicit(&zlog_ec, enable, memory_order_relaxed); +} + +bool zlog_get_prefix_ec(void) +{ + return atomic_load_explicit(&zlog_ec, memory_order_relaxed); +} + +void zlog_set_prefix_xid(bool enable) +{ + atomic_store_explicit(&zlog_xid, enable, memory_order_relaxed); +} + +bool zlog_get_prefix_xid(void) +{ + return atomic_load_explicit(&zlog_xid, memory_order_relaxed); +} + +/* setup functions */ + +struct zlog_target *zlog_target_clone(struct memtype *mt, + struct zlog_target *oldzt, size_t size) +{ + struct zlog_target *newzt; + + newzt = XCALLOC(mt, size); + if (oldzt) { + newzt->prio_min = oldzt->prio_min; + newzt->logfn = oldzt->logfn; + newzt->logfn_sigsafe = oldzt->logfn_sigsafe; + } + + return newzt; +} + +struct zlog_target *zlog_target_replace(struct zlog_target *oldzt, + struct zlog_target *newzt) +{ + if (newzt) + zlog_targets_add_tail(&zlog_targets, newzt); + if (oldzt) + zlog_targets_del(&zlog_targets, oldzt); + return oldzt; +} + +/* + * Enable or disable 'immediate' output - default is to buffer + * each pthread's messages. + */ +void zlog_set_immediate(bool set_p) +{ + zlog_default_immediate = set_p; +} + +bool zlog_get_immediate_mode(void) +{ + return zlog_default_immediate; +} + +/* common init */ + +#define TMPBASEDIR "/var/tmp/frr" + +static char zlog_tmpdir[MAXPATHLEN]; + +void zlog_aux_init(const char *prefix, int prio_min) +{ + if (prefix) + strlcpy(zlog_prefix, prefix, sizeof(zlog_prefix)); + + hook_call(zlog_aux_init, prefix, prio_min); +} + +void zlog_init(const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid) +{ + zlog_uid = uid; + zlog_gid = gid; + zlog_instance = instance; + + if (instance) { + snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir), "%s/%s-%d.%ld", + TMPBASEDIR, progname, instance, (long)getpid()); + + zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix), + "%s[%d]: ", protoname, instance); + } else { + snprintfrr(zlog_tmpdir, sizeof(zlog_tmpdir), "%s/%s.%ld", + TMPBASEDIR, progname, (long)getpid()); + + zlog_prefixsz = snprintfrr(zlog_prefix, sizeof(zlog_prefix), + "%s: ", protoname); + } + + if (mkdir(TMPBASEDIR, 0700) != 0) { + if (errno != EEXIST) { + zlog_err("failed to mkdir \"%s\": %s", + TMPBASEDIR, strerror(errno)); + goto out_warn; + } + } + chown(TMPBASEDIR, zlog_uid, zlog_gid); + + if (mkdir(zlog_tmpdir, 0700) != 0) { + zlog_err("failed to mkdir \"%s\": %s", + zlog_tmpdir, strerror(errno)); + goto out_warn; + } + +#ifdef O_PATH + zlog_tmpdirfd = open(zlog_tmpdir, + O_PATH | O_RDONLY | O_CLOEXEC); +#else + zlog_tmpdirfd = open(zlog_tmpdir, + O_DIRECTORY | O_RDONLY | O_CLOEXEC); +#endif + if (zlog_tmpdirfd < 0) { + zlog_err("failed to open \"%s\": %s", + zlog_tmpdir, strerror(errno)); + goto out_warn; + } + +#ifdef AT_EMPTY_PATH + fchownat(zlog_tmpdirfd, "", zlog_uid, zlog_gid, AT_EMPTY_PATH); +#else + chown(zlog_tmpdir, zlog_uid, zlog_gid); +#endif + + hook_call(zlog_init, progname, protoname, instance, uid, gid); + return; + +out_warn: + zlog_err("crashlog and per-thread log buffering unavailable!"); + hook_call(zlog_init, progname, protoname, instance, uid, gid); +} + +void zlog_fini(void) +{ + hook_call(zlog_fini); + + if (zlog_tmpdirfd >= 0) { + close(zlog_tmpdirfd); + zlog_tmpdirfd = -1; + + if (rmdir(zlog_tmpdir)) + zlog_err("failed to rmdir \"%s\": %s", + zlog_tmpdir, strerror(errno)); + } +} diff --git a/lib/zlog.h b/lib/zlog.h new file mode 100644 index 0000000..9d93979 --- /dev/null +++ b/lib/zlog.h @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ZLOG_H +#define _FRR_ZLOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "atomlist.h" +#include "frrcu.h" +#include "memory.h" +#include "hook.h" +#include "printfrr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MGROUP(LOG); + +extern char zlog_prefix[]; +extern size_t zlog_prefixsz; +extern int zlog_tmpdirfd; +extern int zlog_instance; +extern const char *zlog_progname; + +struct xref_logmsg { + struct xref xref; + + const char *fmtstring; + uint32_t priority; + uint32_t ec; + const char *args; +}; + +/* whether flag was added in config mode or enable mode */ +#define LOGMSG_FLAG_EPHEMERAL (1 << 0) +#define LOGMSG_FLAG_PERSISTENT (1 << 1) + +struct xrefdata_logmsg { + struct xrefdata xrefdata; + + uint8_t fl_print_bt; +}; + +/* These functions are set up to write to stdout/stderr without explicit + * initialization and/or before config load. There is no need to call e.g. + * fprintf(stderr, ...) just because it's "too early" at startup. Depending + * on context, it may still be the right thing to use fprintf though -- try to + * determine whether something is a log message or something else. + */ + +extern void vzlogx(const struct xref_logmsg *xref, int prio, const char *fmt, + va_list ap) PRINTFRR(3, 0); +#define vzlog(prio, ...) vzlogx(NULL, prio, __VA_ARGS__) + +PRINTFRR(2, 3) +static inline void zlog(int prio, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vzlog(prio, fmt, ap); + va_end(ap); +} + +PRINTFRR(2, 3) +static inline void zlog_ref(const struct xref_logmsg *xref, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vzlogx(xref, xref->priority, fmt, ap); + va_end(ap); +} + +#define _zlog_ecref(ec_, prio, msg, ...) \ + do { \ + static struct xrefdata_logmsg _xrefdata = { \ + .xrefdata = \ + { \ + .xref = NULL, \ + .uid = {}, \ + .hashstr = (msg), \ + .hashu32 = {(prio), (ec_)}, \ + }, \ + }; \ + static const struct xref_logmsg _xref __attribute__( \ + (used)) = { \ + .xref = XREF_INIT(XREFT_LOGMSG, &_xrefdata.xrefdata, \ + __func__), \ + .fmtstring = (msg), \ + .priority = (prio), \ + .ec = (ec_), \ + .args = (#__VA_ARGS__), \ + }; \ + XREF_LINK(_xref.xref); \ + zlog_ref(&_xref, (msg), ##__VA_ARGS__); \ + } while (0) + +#define zlog_err(...) _zlog_ecref(0, LOG_ERR, __VA_ARGS__) +#define zlog_warn(...) _zlog_ecref(0, LOG_WARNING, __VA_ARGS__) +#define zlog_info(...) _zlog_ecref(0, LOG_INFO, __VA_ARGS__) +#define zlog_notice(...) _zlog_ecref(0, LOG_NOTICE, __VA_ARGS__) +#define zlog_debug(...) _zlog_ecref(0, LOG_DEBUG, __VA_ARGS__) + +#define flog_err(ferr_id, format, ...) \ + _zlog_ecref(ferr_id, LOG_ERR, format, ## __VA_ARGS__) +#define flog_warn(ferr_id, format, ...) \ + _zlog_ecref(ferr_id, LOG_WARNING, format, ## __VA_ARGS__) + +#define flog_err_sys(ferr_id, format, ...) \ + _zlog_ecref(ferr_id, LOG_ERR, format, ## __VA_ARGS__) + +extern void zlog_sigsafe(const char *text, size_t len); + +/* recirculate a log message from zlog_live */ +extern void zlog_recirculate_live_msg(uint8_t *data, size_t len); + +/* extra priority value to disable a target without deleting it */ +#define ZLOG_DISABLED (LOG_EMERG-1) + +/* zlog_msg encapsulates a particular logging call from somewhere in the code. + * The same struct is passed around to all zlog_targets. + * + * This is used to defer formatting the log message until it is actually + * requested by one of the targets. If none of the targets needs the message + * formatted, the formatting call is avoided entirely. + * + * This struct is opaque / private to the core zlog code. Logging targets + * should use zlog_msg_* functions to get text / timestamps / ... for a + * message. + */ + +struct zlog_msg; + +extern int zlog_msg_prio(struct zlog_msg *msg); +extern const struct xref_logmsg *zlog_msg_xref(struct zlog_msg *msg); + +/* text is NOT \0 terminated; instead there is a \n after textlen since the + * logging targets would jump extra hoops otherwise for a single byte. (the + * \n is not included in textlen) + * + * calling this with NULL textlen is likely wrong. + * use "%.*s", (int)textlen, text when passing to printf-like functions + */ +extern const char *zlog_msg_text(struct zlog_msg *msg, size_t *textlen); + +extern void zlog_msg_args(struct zlog_msg *msg, size_t *hdrlen, + size_t *n_argpos, const struct fmt_outpos **argpos); + +/* timestamp formatting control flags */ + +/* sub-second digit count */ +#define ZLOG_TS_PREC 0xfU + +/* 8601: 0000-00-00T00:00:00Z (if used with ZLOG_TS_UTC) + * 0000-00-00T00:00:00+00:00 (otherwise) + * Legacy: 0000/00/00 00:00:00 (no TZ indicated!) + */ +#define ZLOG_TS_ISO8601 (1 << 8) +#define ZLOG_TS_LEGACY (1 << 9) + +/* default is local time zone */ +#define ZLOG_TS_UTC (1 << 10) + +struct timespec; + +extern size_t zlog_msg_ts(struct zlog_msg *msg, struct fbuf *out, + uint32_t flags); +extern void zlog_msg_tsraw(struct zlog_msg *msg, struct timespec *ts); + +/* "mmm dd hh:mm:ss" for RFC3164 syslog. Only ZLOG_TS_UTC for flags. */ +extern size_t zlog_msg_ts_3164(struct zlog_msg *msg, struct fbuf *out, + uint32_t flags); + +/* currently just returns the current PID/TID since we never write another + * thread's messages + */ +extern void zlog_msg_pid(struct zlog_msg *msg, intmax_t *pid, intmax_t *tid); + +/* This list & struct implements the actual logging targets. It is accessed + * lock-free from all threads, and thus MUST only be changed atomically, i.e. + * RCU. + * + * Since there's no atomic replace, the replacement action is an add followed + * by a delete. This means that during logging config changes, log messages + * may be duplicated in the log target that is being changed. The old entry + * being changed MUST also at the very least not crash or do other stupid + * things. + * + * This list and struct are NOT related to config. Logging config is kept + * separately, and results in creating appropriate zlog_target(s) to realize + * the config. Log targets may also be created from varying sources, e.g. + * command line options, or VTY commands ("log monitor"). + * + * struct zlog_target is intended to be embedded into a larger structure that + * contains additional field for the specific logging target, e.g. an fd or + * additional options. It MUST be the first field in that larger struct. + */ + +PREDECL_ATOMLIST(zlog_targets); +struct zlog_target { + struct zlog_targets_item head; + + int prio_min; + + void (*logfn)(struct zlog_target *zt, struct zlog_msg *msg[], + size_t nmsgs); + + /* for crash handlers, set to NULL if log target can't write crash logs + * without possibly deadlocking (AS-Safe) + * + * text is not \0 terminated & split up into lines (e.g. no \n) + */ + void (*logfn_sigsafe)(struct zlog_target *zt, const char *text, + size_t len); + + struct rcu_head rcu_head; +}; + +/* make a copy for RCUpdating. oldzt may be NULL to allocate a fresh one. */ +extern struct zlog_target *zlog_target_clone(struct memtype *mt, + struct zlog_target *oldzt, + size_t size); + +/* update the zlog_targets list; both oldzt and newzt may be NULL. You + * still need to zlog_target_free() the old target afterwards if it wasn't + * NULL. + * + * Returns oldzt so you can zlog_target_free(zlog_target_replace(old, new)); + * (Some log targets may need extra cleanup inbetween, but remember the old + * target MUST remain functional until the end of the current RCU cycle.) + */ +extern struct zlog_target *zlog_target_replace(struct zlog_target *oldzt, + struct zlog_target *newzt); + +/* Mostly for symmetry for zlog_target_clone(), just rcu_free() internally. */ +#define zlog_target_free(mt, zt) \ + rcu_free(mt, zt, rcu_head) + +extern void zlog_init(const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid); +DECLARE_HOOK(zlog_init, (const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid), + (progname, protoname, instance, uid, gid)); + +extern void zlog_fini(void); +DECLARE_KOOH(zlog_fini, (), ()); + +extern void zlog_set_prefix_ec(bool enable); +extern bool zlog_get_prefix_ec(void); +extern void zlog_set_prefix_xid(bool enable); +extern bool zlog_get_prefix_xid(void); + +/* for tools & test programs, i.e. anything not a daemon. + * (no cleanup needed at exit) + */ +extern void zlog_aux_init(const char *prefix, int prio_min); +DECLARE_HOOK(zlog_aux_init, (const char *prefix, int prio_min), + (prefix, prio_min)); + +extern void zlog_startup_end(void); + +extern void zlog_tls_buffer_init(void); +extern void zlog_tls_buffer_flush(void); +extern void zlog_tls_buffer_fini(void); + +/* Enable or disable 'immediate' output - default is to buffer messages. */ +extern void zlog_set_immediate(bool set_p); +bool zlog_get_immediate_mode(void); + +extern const char *zlog_priority_str(int priority); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ZLOG_H */ diff --git a/lib/zlog_5424.c b/lib/zlog_5424.c new file mode 100644 index 0000000..4c60d4b --- /dev/null +++ b/lib/zlog_5424.c @@ -0,0 +1,1144 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-21 David Lamparter, for NetDEF, Inc. + */ + +/* when you work on this code, please install a fuzzer (e.g. AFL) and run + * tests/lib/fuzz_zlog.c + * + * The most likely type of bug in this code is an off-by-one error in the + * buffer management pieces, and this isn't easily covered by an unit test + * or topotests. Fuzzing is the best tool here, but the CI can't do that + * since it's quite resource intensive. + */ + +#include "zebra.h" +#include + +#include "frrsendmmsg.h" + +#include "zlog_5424.h" + +#include +#include + +#include "memory.h" +#include "frrcu.h" +#include "printfrr.h" +#include "typerb.h" +#include "frr_pthread.h" +#include "command.h" +#include "monotime.h" +#include "frrevent.h" + +#include "lib/version.h" +#include "lib/lib_errors.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_5424, "extended log target"); +DEFINE_MTYPE_STATIC(LOG, LOG_5424_ROTATE, "extended log rotate helper"); + +/* the actual log target data structure + * + * remember this is RCU'd by the core zlog functions. Changing anything + * works by allocating a new struct, filling it, adding it, and removing the + * old one. + */ +struct zlt_5424 { + struct zlog_target zt; + + atomic_uint_fast32_t fd; + + enum zlog_5424_format fmt; + uint32_t ts_flags; + int facility; + + /* the various extra pieces to add... */ + bool kw_version : 1; + bool kw_location : 1; + bool kw_uid : 1; + bool kw_ec : 1; + bool kw_args : 1; + + /* some formats may or may not include the trailing \n */ + bool use_nl : 1; + + /* for DGRAM & SEQPACKET sockets, send 1 log message per packet, since + * the socket preserves packet boundaries. On Linux, this uses + * sendmmsg() for efficiency, on other systems we need a syscall each. + */ + bool packets : 1; + + /* for DGRAM, in order to not have to reconnect, we need to use + * sendto()/sendmsg() with the destination given; otherwise we'll get + * ENOTCONN. (We do a connect(), which serves to verify the type of + * socket, but if the receiver goes away, the kernel disconnects the + * socket so writev() no longer works since the destination is now + * unspecified.) + */ + struct sockaddr_storage sa; + socklen_t sa_len; + + /* these are both getting set, but current_err is cleared on success, + * so we know whether the error is current or past. + */ + int last_err, current_err; + atomic_size_t lost_msgs; + struct timeval last_err_ts; + + struct rcu_head_close head_close; +}; + +static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type); + +/* rough header length estimate + * ============================ + * + * ^ = might grow + * + * 49^ longest filename (pceplib/test/pcep_utils_double_linked_list_test.h) + * 5^ highest line number (48530, bgpd/bgp_nb_config.c) + * 65^ longest function name + * (lib_prefix_list_entry_ipv6_prefix_length_greater_or_equal_destroy) + * 11 unique id ("XXXXX-XXXXX") + * 10 EC ("4294967295" or "0xffffffff") + * 35 ISO8601 TS at full length ("YYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ") + * --- + * 175 + * + * rarely used (hopefully...): + * 26^ FRR_VERSION ("10.10.10-dev-gffffffffffff") + * --- + * 201 + * + * x16 highest number of format parameters currently + * 40 estimate for hostname + 2*daemon + pid + * + * specific format overhead: + * + * RFC3164 - shorter than the others + * RFC5424 - 175 + "<999>1 "=7 + 52 (location@50145) + 40 (host/...) + * rarely: + 65 + 26 (for [origin]) + * args: 16 * (8 + per-arg (20?)) = ~448 + * + * so without "args@", origin or (future) keywords, around 256 seems OK + * with args@ and/or origin and/or keywords, 512 seems more reasonable + * + * but - note the code allocates this amount multiplied by the number of + * messages in the incoming batch (minimum 3), this means short messages and + * long messages smooth each other out. + * + * Since the code handles space-exceeded by grabbing a bunch of stack memory, + * a reasonable middle ground estimate is desirable here, so ... + * *drumroll* + * let's go with 128 + args?128. (remember the minimum 3 multiplier) + * + * low_space is the point where we don't try to fit another message in & just + * submit what we have to the kernel. + * + * The zlog code only buffers debug & informational messages, so in production + * usage most of the calls will be writing out only 1 message. This makes + * the min *3 multiplier quite useful. + */ + +static inline size_t zlog_5424_bufsz(struct zlt_5424 *zte, size_t nmsgs, + size_t *low_space) +{ + size_t ret = 128; + + if (zte->kw_args) + ret += 128; + *low_space = ret; + return ret * MAX(nmsgs, 3); +} + +struct state { + struct fbuf *fbuf; + struct iovec *iov; +}; + +/* stack-based keyword support is likely to bump this to 3 or 4 */ +#define IOV_PER_MSG 2 +_Static_assert(IOV_MAX >= IOV_PER_MSG, + "this code won't work with IOV_MAX < IOV_PER_MSG"); + +/* the following functions are quite similar, but trying to merge them just + * makes a big mess. check the others when touching one. + * + * timestamp keywords hostname + * RFC5424 ISO8601 yes yes + * RFC3164 RFC3164 no yes + * local RFC3164 no no + * journald ISO8601(unused) yes (unused) + */ + +static size_t zlog_5424_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, "<%d>1 ", prio | zte->facility); + need += zlog_msg_ts(msg, fbuf, zte->ts_flags); + need += bprintfrr(fbuf, " %s %s %jd %.*s ", cmd_hostname_get() ?: "-", + zlog_progname, pid, (int)(zlog_prefixsz - 2), + zlog_prefix); + + if (zte->kw_version) + need += bprintfrr( + fbuf, + "[origin enterpriseId=\"50145\" software=\"FRRouting\" swVersion=\"%s\"]", + FRR_VERSION); + + const struct xref_logmsg *xref; + struct xrefdata *xrefdata; + + need += bprintfrr(fbuf, "[location@50145 tid=\"%jd\"", tid); + if (zlog_instance > 0) + need += bprintfrr(fbuf, " instance=\"%d\"", zlog_instance); + + xref = zlog_msg_xref(msg); + xrefdata = xref ? xref->xref.xrefdata : NULL; + if (xrefdata) { + if (zte->kw_uid) + need += bprintfrr(fbuf, " id=\"%s\"", xrefdata->uid); + if (zte->kw_ec && prio <= LOG_WARNING) + need += bprintfrr(fbuf, " ec=\"%u\"", xref->ec); + if (zte->kw_location) + need += bprintfrr( + fbuf, " file=\"%s\" line=\"%d\" func=\"%s\"", + xref->xref.file, xref->xref.line, + xref->xref.func); + } + need += bputch(fbuf, ']'); + + size_t hdrlen, n_argpos; + const struct fmt_outpos *argpos; + const char *text; + + text = zlog_msg_text(msg, &textlen); + zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos); + + if (zte->kw_args && n_argpos) { + need += bputs(fbuf, "[args@50145"); + + for (size_t i = 0; i < n_argpos; i++) { + int len = argpos[i].off_end - argpos[i].off_start; + + need += bprintfrr(fbuf, " arg%zu=%*pSQsq", i + 1, len, + text + argpos[i].off_start); + } + + need += bputch(fbuf, ']'); + } + + need += bputch(fbuf, ' '); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. the loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)text + hdrlen; + state->iov->iov_len = textlen - hdrlen + zte->use_nl; + state->iov++; + return 0; +} + +static size_t zlog_3164_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, "<%d>", prio | zte->facility); + need += zlog_msg_ts_3164(msg, fbuf, zte->ts_flags); + if (zte->fmt != ZLOG_FMT_LOCAL) { + need += bputch(fbuf, ' '); + need += bputs(fbuf, cmd_hostname_get() ?: "-"); + } + need += bprintfrr(fbuf, " %s[%jd]: ", zlog_progname, pid); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)zlog_msg_text(msg, &textlen); + state->iov->iov_len = textlen + zte->use_nl; + state->iov++; + return 0; +} + +static size_t zlog_journald_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + size_t textlen; + struct fbuf *fbuf = state->fbuf; + char *orig_pos = fbuf->pos; + size_t need = 0; + int prio = zlog_msg_prio(msg); + intmax_t pid, tid; + + zlog_msg_pid(msg, &pid, &tid); + + need += bprintfrr(fbuf, + "PRIORITY=%d\n" + "SYSLOG_FACILITY=%d\n" + "TID=%jd\n" + "FRR_DAEMON=%s\n" + "SYSLOG_TIMESTAMP=", + prio, zte->facility, tid, zlog_progname); + need += zlog_msg_ts(msg, fbuf, zte->ts_flags); + need += bputch(fbuf, '\n'); + if (zlog_instance > 0) + need += bprintfrr(fbuf, "FRR_INSTANCE=%d\n", zlog_instance); + + const struct xref_logmsg *xref; + struct xrefdata *xrefdata; + + xref = zlog_msg_xref(msg); + xrefdata = xref ? xref->xref.xrefdata : NULL; + if (xrefdata) { + if (zte->kw_uid && xrefdata->uid[0]) + need += bprintfrr(fbuf, "FRR_ID=%s\n", xrefdata->uid); + if (zte->kw_ec && prio <= LOG_WARNING) + need += bprintfrr(fbuf, "FRR_EC=%d\n", xref->ec); + if (zte->kw_location) + need += bprintfrr(fbuf, + "CODE_FILE=%s\n" + "CODE_LINE=%d\n" + "CODE_FUNC=%s\n", + xref->xref.file, xref->xref.line, + xref->xref.func); + } + + size_t hdrlen, n_argpos; + const struct fmt_outpos *argpos; + const char *text; + + text = zlog_msg_text(msg, &textlen); + zlog_msg_args(msg, &hdrlen, &n_argpos, &argpos); + + if (zte->kw_args && n_argpos) { + for (size_t i = 0; i < n_argpos; i++) { + int len = argpos[i].off_end - argpos[i].off_start; + + /* rather than escape the value, we could use + * journald's binary encoding, but that seems a bit + * excessive/unnecessary. 99% of things we print here + * will just output 1:1 with %pSE. + */ + need += bprintfrr(fbuf, "FRR_ARG%zu=%*pSE\n", i + 1, + len, text + argpos[i].off_start); + } + } + + need += bputs(fbuf, "MESSAGE="); + + if (orig_pos + need > fbuf->buf + fbuf->len) { + /* not enough space in the buffer for headers. loop in + * zlog_5424() will flush other messages that are already in + * the buffer, grab a bigger buffer if needed, and try again. + */ + fbuf->pos = orig_pos; + return need; + } + + /* NB: zlog_5424 below assumes we use max. IOV_PER_MSG iovs here */ + state->iov->iov_base = orig_pos; + state->iov->iov_len = fbuf->pos - orig_pos; + state->iov++; + + state->iov->iov_base = (char *)text + hdrlen; + state->iov->iov_len = textlen - hdrlen + 1; + state->iov++; + return 0; +} + +static size_t zlog_one(struct zlt_5424 *zte, struct zlog_msg *msg, + struct state *state) +{ + switch (zte->fmt) { + case ZLOG_FMT_5424: + return zlog_5424_one(zte, msg, state); + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + return zlog_3164_one(zte, msg, state); + case ZLOG_FMT_JOURNALD: + return zlog_journald_one(zte, msg, state); + } + return 0; +} + +static void zlog_5424_err(struct zlt_5424 *zte, size_t count) +{ + if (!count) { + zte->current_err = 0; + return; + } + + /* only the counter is atomic because otherwise it'd be meaningless */ + atomic_fetch_add_explicit(&zte->lost_msgs, count, memory_order_relaxed); + + /* these are non-atomic and can provide wrong results when read, but + * since they're only for debugging / display, that's OK. + */ + zte->current_err = zte->last_err = errno; + monotime(&zte->last_err_ts); +} + +static void zlog_5424(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + size_t i; + struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt); + int fd, ret; + size_t niov = MIN(IOV_PER_MSG * nmsgs, IOV_MAX); + struct iovec iov[niov], *iov_last = iov + niov; + struct mmsghdr mmsg[zte->packets ? nmsgs : 1], *mpos = mmsg; + size_t count = 0; + + /* refer to size estimate at top of file */ + size_t low_space; + char hdr_buf[zlog_5424_bufsz(zte, nmsgs, &low_space)]; + struct fbuf hdr_pos = { + .buf = hdr_buf, + .pos = hdr_buf, + .len = sizeof(hdr_buf), + }; + struct state state = { + .fbuf = &hdr_pos, + .iov = iov, + }; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + memset(mmsg, 0, sizeof(mmsg)); + if (zte->sa_len) { + for (i = 0; i < array_size(mmsg); i++) { + mmsg[i].msg_hdr.msg_name = (struct sockaddr *)&zte->sa; + mmsg[i].msg_hdr.msg_namelen = zte->sa_len; + } + } + mmsg[0].msg_hdr.msg_iov = iov; + + for (i = 0; i < nmsgs; i++) { + int prio = zlog_msg_prio(msgs[i]); + size_t need = 0; + + if (prio <= zte->zt.prio_min) { + if (zte->packets) + mpos->msg_hdr.msg_iov = state.iov; + + need = zlog_one(zte, msgs[i], &state); + + if (zte->packets) { + mpos->msg_hdr.msg_iovlen = + state.iov - mpos->msg_hdr.msg_iov; + mpos++; + } + count++; + } + + /* clang-format off */ + if ((need + || (size_t)(hdr_pos.buf + hdr_pos.len - hdr_pos.pos) + < low_space + || i + 1 == nmsgs + || state.iov + IOV_PER_MSG > iov_last) + && state.iov > iov) { + /* clang-format on */ + + if (zte->packets) { + struct mmsghdr *sendpos; + + for (sendpos = mmsg; sendpos < mpos;) { + ret = sendmmsg(fd, sendpos, + mpos - sendpos, 0); + if (ret <= 0) + break; + sendpos += ret; + } + zlog_5424_err(zte, mpos - sendpos); + mpos = mmsg; + } else { + if (!zte->sa_len) + ret = writev(fd, iov, state.iov - iov); + else { + mpos->msg_hdr.msg_iovlen = + state.iov - iov; + ret = sendmsg(fd, &mpos->msg_hdr, 0); + } + + if (ret < 0) + zlog_5424_err(zte, count); + else + zlog_5424_err(zte, 0); + } + + count = 0; + hdr_pos.pos = hdr_buf; + state.iov = iov; + } + + /* if need == 0, we just put a message (or nothing) in the + * buffer and are continuing for more to batch in a single + * writev() + */ + if (need == 0) + continue; + + if (need && need <= sizeof(hdr_buf)) { + /* don't need to allocate, just try this msg + * again without other msgs already using up + * buffer space + */ + i--; + continue; + } + + /* need > sizeof(hdr_buf), need to grab some memory. Taking + * it off the stack is fine here. + */ + char buf2[need]; + struct fbuf fbuf2 = { + .buf = buf2, + .pos = buf2, + .len = sizeof(buf2), + }; + + state.fbuf = &fbuf2; + need = zlog_one(zte, msgs[i], &state); + assert(need == 0); + + if (!zte->sa_len) + ret = writev(fd, iov, state.iov - iov); + else { + mpos->msg_hdr.msg_iovlen = state.iov - iov; + ret = sendmsg(fd, &mpos->msg_hdr, 0); + } + + if (ret < 0) + zlog_5424_err(zte, 1); + else + zlog_5424_err(zte, 0); + + count = 0; + state.fbuf = &hdr_pos; + state.iov = iov; + mpos = mmsg; + } + + assert(state.iov == iov); +} + +/* strftime(), gmtime_r() and localtime_r() aren't AS-Safe (they access locale + * data), but we need an AS-Safe timestamp below :( + */ +static void gmtime_assafe(time_t ts, struct tm *res) +{ + res->tm_sec = ts % 60; + ts /= 60; + res->tm_min = ts % 60; + ts /= 60; + res->tm_hour = ts % 24; + ts /= 24; + + ts -= 11017; /* start on 2020-03-01, 11017 days since 1970-01-01 */ + + /* 1461 days = 3 regular years + 1 leap year + * this works until 2100, which isn't a leap year + * + * struct tm.tm_year starts at 1900. + */ + res->tm_year = 2000 - 1900 + 4 * (ts / 1461); + ts = ts % 1461; + + if (ts == 1460) { + res->tm_year += 4; + res->tm_mon = 1; + res->tm_mday = 29; + return; + } + res->tm_year += ts / 365; + ts %= 365; + + /* note we're starting in march like the romans did... */ + if (ts >= 306) /* Jan 1 of next year */ + res->tm_year++; + + static time_t months[13] = { + 0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337, 365, + }; + const size_t month_max = array_size(months) - 1; + + for (size_t i = 0; i < month_max; i++) { + if (ts < months[i + 1]) { + res->tm_mon = ((i + 2) % 12); + res->tm_mday = 1 + ts - months[i]; + break; + } + } +} + +/* one of the greatest advantages of this logging target: unlike syslog(), + * which is not AS-Safe, we can send crashlogs to syslog here. + */ +static void zlog_5424_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + static const char *const months_3164[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + + struct zlt_5424 *zte = container_of(zt, struct zlt_5424, zt); + struct iovec iov[3], *iovp = iov; + char buf[256]; + struct fbuf fbuf = { + .buf = buf, + .pos = buf, + .len = sizeof(buf), + }; + int fd; + intmax_t pid = (intmax_t)getpid(); + struct tm tm; + + switch (zte->fmt) { + case ZLOG_FMT_5424: + gmtime_assafe(time(NULL), &tm); + bprintfrr( + &fbuf, + "<%d>1 %04u-%02u-%02uT%02u:%02u:%02uZ - %s %jd %.*s ", + zte->facility | LOG_CRIT, tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, zlog_progname, pid, (int)(zlog_prefixsz - 2), + zlog_prefix); + break; + + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + /* this will unfortuantely be wrong by the timezone offset + * if the user selected non-UTC. But not much we can do + * about that... + */ + gmtime_assafe(time(NULL), &tm); + bprintfrr(&fbuf, "<%d>%3s %2u %02u:%02u:%02u %s%s[%jd]: ", + zte->facility | LOG_CRIT, months_3164[tm.tm_mon], + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + (zte->fmt == ZLOG_FMT_LOCAL) ? "" : "- ", + zlog_progname, pid); + break; + + case ZLOG_FMT_JOURNALD: + bprintfrr(&fbuf, + "PRIORITY=%d\n" + "SYSLOG_FACILITY=%d\n" + "FRR_DAEMON=%s\n" + "MESSAGE=", + LOG_CRIT, zte->facility, zlog_progname); + break; + } + + iovp->iov_base = fbuf.buf; + iovp->iov_len = fbuf.pos - fbuf.buf; + iovp++; + + iovp->iov_base = (char *)text; + iovp->iov_len = len; + iovp++; + + if (zte->use_nl) { + iovp->iov_base = (char *)"\n"; + iovp->iov_len = 1; + iovp++; + } + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + if (!zte->sa_len) + writev(fd, iov, iovp - iov); + else { + struct msghdr mh = {}; + + mh.msg_name = (struct sockaddr *)&zte->sa; + mh.msg_namelen = zte->sa_len; + mh.msg_iov = iov; + mh.msg_iovlen = iovp - iov; + sendmsg(fd, &mh, 0); + } +} + +/* housekeeping & configuration */ + +void zlog_5424_init(struct zlog_cfg_5424 *zcf) +{ + pthread_mutex_init(&zcf->cfg_mtx, NULL); +} + +static void zlog_5424_target_free(struct zlt_5424 *zlt) +{ + if (!zlt) + return; + + rcu_close(&zlt->head_close, zlt->fd); + rcu_free(MTYPE_LOG_5424, zlt, zt.rcu_head); +} + +void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen) +{ + if (keepopen) + zcf->active = NULL; + + if (zcf->active) { + struct zlt_5424 *ztf; + struct zlog_target *zt; + + zt = zlog_target_replace(&zcf->active->zt, NULL); + ztf = container_of(zt, struct zlt_5424, zt); + zlog_5424_target_free(ztf); + } + pthread_mutex_destroy(&zcf->cfg_mtx); +} + +static void zlog_5424_cycle(struct zlog_cfg_5424 *zcf, int fd) +{ + struct zlog_target *old; + struct zlt_5424 *zlt = NULL, *oldt; + + if (fd >= 0) { + struct zlog_target *zt; + + /* all of this is swapped in by zlog_target_replace() below, + * the old target is RCU-freed afterwards. + */ + zt = zlog_target_clone(MTYPE_LOG_5424, &zcf->active->zt, + sizeof(*zlt)); + zlt = container_of(zt, struct zlt_5424, zt); + + zlt->fd = fd; + zlt->kw_version = zcf->kw_version; + zlt->kw_location = zcf->kw_location; + zlt->kw_uid = zcf->kw_uid; + zlt->kw_ec = zcf->kw_ec; + zlt->kw_args = zcf->kw_args; + zlt->use_nl = true; + zlt->facility = zcf->facility; + + /* DGRAM & SEQPACKET = 1 log message per packet */ + zlt->packets = (zcf->sock_type == SOCK_DGRAM) || + (zcf->sock_type == SOCK_SEQPACKET); + zlt->sa = zcf->sa; + zlt->sa_len = zcf->sa_len; + zlt->fmt = zcf->fmt; + zlt->zt.prio_min = zcf->prio_min; + zlt->zt.logfn = zlog_5424; + zlt->zt.logfn_sigsafe = zlog_5424_sigsafe; + + switch (zcf->fmt) { + case ZLOG_FMT_5424: + case ZLOG_FMT_JOURNALD: + zlt->ts_flags = zcf->ts_flags; + zlt->ts_flags &= ZLOG_TS_PREC | ZLOG_TS_UTC; + zlt->ts_flags |= ZLOG_TS_ISO8601; + break; + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + zlt->ts_flags = zcf->ts_flags & ZLOG_TS_UTC; + if (zlt->packets) + zlt->use_nl = false; + break; + } + } + + old = zcf->active ? &zcf->active->zt : NULL; + old = zlog_target_replace(old, &zlt->zt); + zcf->active = zlt; + + /* oldt->fd == fd happens for zlog_5424_apply_meta() */ + oldt = container_of(old, struct zlt_5424, zt); + if (oldt && oldt->fd != (unsigned int)fd) + rcu_close(&oldt->head_close, oldt->fd); + rcu_free(MTYPE_LOG_5424, oldt, zt.rcu_head); +} + +static void zlog_5424_reconnect(struct event *t) +{ + struct zlog_cfg_5424 *zcf = EVENT_ARG(t); + int fd = EVENT_FD(t); + char dummy[256]; + ssize_t ret; + + if (zcf->active) { + ret = read(fd, dummy, sizeof(dummy)); + if (ret > 0) { + /* logger is sending us something?!?! */ + event_add_read(t->master, zlog_5424_reconnect, zcf, fd, + &zcf->t_reconnect); + return; + } + + if (ret == 0) + zlog_warn("logging socket %pSE closed by peer", + zcf->filename); + else + zlog_warn("logging socket %pSE error: %m", + zcf->filename); + } + + /* do NOT close() anything here; other threads may still be writing + * and their messages need to be lost rather than end up on a random + * other fd that got reassigned the same number, like a BGP session! + */ + fd = zlog_5424_open(zcf, -1); + + frr_with_mutex (&zcf->cfg_mtx) { + zlog_5424_cycle(zcf, fd); + } +} + +static int zlog_5424_unix(struct sockaddr_un *suna, int sock_type) +{ + int fd; + int size = 1 * 1024 * 1024, prev_size; + socklen_t opt_size; + int save_errno; + + fd = socket(AF_UNIX, sock_type, 0); + if (fd < 0) + return -1; + + if (connect(fd, (struct sockaddr *)suna, sizeof(*suna))) { + /* zlog_5424_open() will print the error for connect() */ + save_errno = errno; + close(fd); + errno = save_errno; + return -1; + } + + opt_size = sizeof(prev_size); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &prev_size, &opt_size)) + return fd; + + /* setsockopt_so_sendbuf() logs on error; we don't really care that + * much here. Also, never shrink the buffer below the initial size. + */ + while (size > prev_size && + setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) == -1) + size /= 2; + + return fd; +} + +static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) +{ + int fd = -1; + int flags = 0; + int err; + socklen_t optlen; + bool do_chown = false; + bool need_reconnect = false; + static const int unix_types[] = { + SOCK_STREAM, + SOCK_SEQPACKET, + SOCK_DGRAM, + }; + struct sockaddr_un sa; + + zcf->sock_type = -1; + zcf->sa_len = 0; + + switch (zcf->dst) { + case ZLOG_5424_DST_NONE: + return -1; + + case ZLOG_5424_DST_FD: + fd = dup(zcf->fd); + if (fd < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "failed to dup() log file descriptor: %m (FD limit too low?)"); + return -1; + } + + optlen = sizeof(sock_type); + if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) { + zcf->sock_type = sock_type; + need_reconnect = (zcf->sock_type != SOCK_DGRAM); + } + break; + + case ZLOG_5424_DST_FIFO: + if (!zcf->filename) + return -1; + + if (!zcf->file_nocreate) { + frr_with_privs (lib_privs) { + mode_t prevmask; + + prevmask = umask(0777 ^ zcf->file_mode); + err = mkfifo(zcf->filename, 0666); + umask(prevmask); + } + if (err == 0) + do_chown = true; + else if (errno != EEXIST) + return -1; + } + + flags = O_NONBLOCK; + fallthrough; + + case ZLOG_5424_DST_FILE: + if (!zcf->filename) + return -1; + + frr_with_privs (lib_privs) { + fd = open(zcf->filename, flags | O_WRONLY | O_APPEND | + O_CLOEXEC | O_NOCTTY); + } + if (fd >= 0) + break; + if (zcf->file_nocreate || flags) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "could not open log file %pSE: %m", + zcf->filename); + return -1; + } + + frr_with_privs (lib_privs) { + mode_t prevmask; + + prevmask = umask(0777 ^ zcf->file_mode); + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY | + O_CREAT | O_EXCL, + zcf->file_mode); + umask(prevmask); + } + if (fd >= 0) { + do_chown = true; + break; + } + + frr_with_privs (lib_privs) { + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CLOEXEC | O_NOCTTY); + } + if (fd >= 0) + break; + + flog_err_sys(EC_LIB_SYSTEM_CALL, + "could not open or create log file %pSE: %m", + zcf->filename); + return -1; + + case ZLOG_5424_DST_UNIX: + if (!zcf->filename) + return -1; + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strlcpy(sa.sun_path, zcf->filename, sizeof(sa.sun_path)); + + /* check if ZLOG_5424_DST_FD needs a touch when changing + * something here. the user can pass in a pre-opened unix + * socket through a fd at startup. + */ + frr_with_privs (lib_privs) { + if (sock_type != -1) + fd = zlog_5424_unix(&sa, sock_type); + else { + for (size_t i = 0; i < array_size(unix_types); + i++) { + fd = zlog_5424_unix(&sa, unix_types[i]); + if (fd != -1) { + zcf->sock_type = unix_types[i]; + break; + } + } + } + } + if (fd == -1) { + zcf->sock_type = -1; + + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "could not connect to log unix path %pSE: %m", + zcf->filename); + need_reconnect = true; + /* no return -1 here, trigger retry code below */ + } else { + /* datagram sockets are connectionless, restarting + * the receiver may lose some packets but will resume + * working afterwards without any action from us. + */ + need_reconnect = (zcf->sock_type != SOCK_DGRAM); + } + break; + } + + /* viable on both DST_FD and DST_UNIX path */ + if (zcf->sock_type == SOCK_DGRAM) { + zcf->sa_len = sizeof(zcf->sa); + if (getpeername(fd, (struct sockaddr *)&zcf->sa, + &zcf->sa_len)) { + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "could not get remote address for log socket. logging may break if log receiver restarts."); + zcf->sa_len = 0; + } + } + + if (do_chown) { + uid_t uid = zcf->file_uid; + gid_t gid = zcf->file_gid; + + if (uid != (uid_t)-1 || gid != (gid_t)-1) { + frr_with_privs (lib_privs) { + err = fchown(fd, uid, gid); + } + if (err) + flog_err_sys( + EC_LIB_SYSTEM_CALL, + "failed to chown() log file %pSE: %m", + zcf->filename); + } + } + + if (need_reconnect) { + assert(zcf->master); + + if (fd != -1) { + event_add_read(zcf->master, zlog_5424_reconnect, zcf, + fd, &zcf->t_reconnect); + zcf->reconn_backoff_cur = zcf->reconn_backoff; + + } else { + event_add_timer_msec(zcf->master, zlog_5424_reconnect, + zcf, zcf->reconn_backoff_cur, + &zcf->t_reconnect); + + zcf->reconn_backoff_cur += zcf->reconn_backoff_cur / 2; + if (zcf->reconn_backoff_cur > zcf->reconn_backoff_max) + zcf->reconn_backoff_cur = + zcf->reconn_backoff_max; + } + } + + return fd; +} + +bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf) +{ + int fd = -1; + + event_cancel(&zcf->t_reconnect); + + if (zcf->prio_min != ZLOG_DISABLED) + fd = zlog_5424_open(zcf, -1); + + frr_with_mutex (&zcf->cfg_mtx) { + zlog_5424_cycle(zcf, fd); + } + return fd != -1; +} + + +bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf) +{ + frr_with_mutex (&zcf->cfg_mtx) { + if (zcf->active) + zlog_5424_cycle(zcf, zcf->active->fd); + } + + return true; +} + +void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs, + int *last_errno, bool *stale_errno, struct timeval *err_ts) +{ + if (lost_msgs) + *lost_msgs = + zcf->active + ? atomic_load_explicit(&zcf->active->lost_msgs, + memory_order_relaxed) + : 0; + if (last_errno) + *last_errno = zcf->active ? zcf->active->last_err : 0; + if (stale_errno) + *stale_errno = zcf->active ? !zcf->active->current_err : 0; + if (err_ts && zcf->active) + *err_ts = zcf->active->last_err_ts; +} + +struct rcu_close_rotate { + struct rcu_head_close head_close; + struct rcu_head head_self; +}; + +bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf) +{ + struct rcu_close_rotate *rcr; + int fd; + + frr_with_mutex (&zcf->cfg_mtx) { + if (!zcf->active) + return true; + + event_cancel(&zcf->t_reconnect); + + /* need to retain the socket type because it also influences + * other fields (packets) and we can't atomically swap these + * out. But we really want the atomic swap so we neither lose + * nor duplicate log messages, particularly for file targets. + * + * (zlog_5424_apply_dst / zlog_target_replace will cause + * duplicate log messages if another thread logs something + * while we're right in the middle of swapping out the log + * target) + */ + fd = zlog_5424_open(zcf, zcf->sock_type); + if (fd < 0) + return false; + + fd = atomic_exchange_explicit(&zcf->active->fd, + (uint_fast32_t)fd, + memory_order_relaxed); + } + + rcr = XCALLOC(MTYPE_LOG_5424_ROTATE, sizeof(*rcr)); + rcu_close(&rcr->head_close, fd); + rcu_free(MTYPE_LOG_5424_ROTATE, rcr, head_self); + + return true; +} diff --git a/lib/zlog_5424.h b/lib/zlog_5424.h new file mode 100644 index 0000000..06525c0 --- /dev/null +++ b/lib/zlog_5424.h @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2021 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ZLOG_5424_H +#define _FRR_ZLOG_5424_H + +#include + +#include "typerb.h" +#include "zlog.h" +#include "zlog_targets.h" +#include "qobj.h" + +struct event; +struct event_loop; + +enum zlog_5424_dst { + /* can be used to disable a target temporarily */ + ZLOG_5424_DST_NONE = 0, + + ZLOG_5424_DST_FD, + ZLOG_5424_DST_FILE, + ZLOG_5424_DST_FIFO, + ZLOG_5424_DST_UNIX, + +#define ZLOG_5424_DST_LAST ZLOG_5424_DST_UNIX +}; + +enum zlog_5424_format { + ZLOG_FMT_5424 = 0, + ZLOG_FMT_3164, + ZLOG_FMT_LOCAL, + ZLOG_FMT_JOURNALD, + +#define ZLOG_FMT_LAST ZLOG_FMT_JOURNALD +}; + +/* actual RCU'd logging backend */ +struct zlt_5424; + +struct zlog_cfg_5424 { + struct zlt_5424 *active; + + pthread_mutex_t cfg_mtx; + + /* general settings for all dsts */ + int facility; + int prio_min; + bool kw_version; + bool kw_location; + bool kw_uid; + bool kw_ec; + bool kw_args; + + uint32_t ts_flags; + + enum zlog_5424_format fmt; + + /* destination specifics */ + enum zlog_5424_dst dst; + + /* pre-opened FD. not the actual fd we log to */ + int fd; + + /* file, fifo, unix */ + bool file_nocreate; + + const char *filename; + mode_t file_mode; + /* -1 = no change */ + uid_t file_uid; + gid_t file_gid; + + /* remaining fields are internally used & updated by the 5424 + * code - *not* config. don't set these. + */ + + /* sockets only - read handler to reconnect on errors */ + struct event_loop *master; + struct event *t_reconnect; + unsigned int reconn_backoff, reconn_backoff_cur, reconn_backoff_max; + int sock_type; + struct sockaddr_storage sa; + socklen_t sa_len; +}; + +/* these don't do malloc/free to allow using a static global */ +extern void zlog_5424_init(struct zlog_cfg_5424 *zcf); + +/* keepopen = true => for shutdown, just zap the config, keep logging */ +extern void zlog_5424_fini(struct zlog_cfg_5424 *zcf, bool keepopen); + +/* apply metadata/config changes */ +extern bool zlog_5424_apply_meta(struct zlog_cfg_5424 *zcf); + +/* apply changes requiring (re-)opening the destination + * + * also does log cycling/rotate & applies _meta at the same time + */ +extern bool zlog_5424_apply_dst(struct zlog_cfg_5424 *zcf); + +/* SIGHUP log rotation */ +extern bool zlog_5424_rotate(struct zlog_cfg_5424 *zcf); + +extern void zlog_5424_state(struct zlog_cfg_5424 *zcf, size_t *lost_msgs, + int *last_errno, bool *stale_errno, + struct timeval *err_ts); + +/* this is the dynamically allocated "variant" */ +PREDECL_RBTREE_UNIQ(targets); + +struct zlog_cfg_5424_user { + struct targets_item targets_item; + char *name; + + struct zlog_cfg_5424 cfg; + + char *envvar; + + /* non-const, always same as cfg.filename */ + char *filename; + + /* uid/gid strings to write back out in show config */ + char *file_user; + char *file_group; + + bool reconf_dst; + bool reconf_meta; + + int unix_special; + + QOBJ_FIELDS; +}; + +DECLARE_QOBJ_TYPE(zlog_cfg_5424_user); + +extern void log_5424_cmd_init(void); + +#endif /* _FRR_ZLOG_5424_H */ diff --git a/lib/zlog_5424_cli.c b/lib/zlog_5424_cli.c new file mode 100644 index 0000000..3003df5 --- /dev/null +++ b/lib/zlog_5424_cli.c @@ -0,0 +1,992 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + */ + +#include "zebra.h" +#include "zlog_5424.h" + +#include +#include +#include + +#include "lib/command.h" +#include "lib/libfrr.h" +#include "lib/log_vty.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config"); +DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items"); + +static int target_cmp(const struct zlog_cfg_5424_user *a, + const struct zlog_cfg_5424_user *b) +{ + return strcmp(a->name, b->name); +} + +DECLARE_RBTREE_UNIQ(targets, struct zlog_cfg_5424_user, targets_item, + target_cmp); +DEFINE_QOBJ_TYPE(zlog_cfg_5424_user); + +static struct targets_head targets = INIT_RBTREE_UNIQ(targets); +static struct event_loop *log_5424_master; + +static void clear_dst(struct zlog_cfg_5424_user *cfg); + +struct log_option { + const char *name; + ptrdiff_t offs; + bool dflt; +}; + +/* clang-format off */ +static struct log_option log_opts[] = { + { "code-location", offsetof(struct zlog_cfg_5424, kw_location) }, + { "version", offsetof(struct zlog_cfg_5424, kw_version) }, + { "unique-id", offsetof(struct zlog_cfg_5424, kw_uid), true }, + { "error-category", offsetof(struct zlog_cfg_5424, kw_ec), true }, + { "format-args", offsetof(struct zlog_cfg_5424, kw_args) }, + {}, +}; + +#define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC) +#define DFLT_FACILITY LOG_DAEMON +#define DFLT_PRIO_MIN LOG_DEBUG +/* clang-format on */ + +enum unix_special { + SPECIAL_NONE = 0, + SPECIAL_SYSLOG, + SPECIAL_JOURNALD, +}; + +static struct zlog_cfg_5424_user *log_5424_alloc(const char *name) +{ + struct zlog_cfg_5424_user *cfg; + + cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg)); + cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name); + + cfg->cfg.master = log_5424_master; + cfg->cfg.kw_location = true; + cfg->cfg.kw_version = false; + cfg->cfg.facility = DFLT_FACILITY; + cfg->cfg.prio_min = DFLT_PRIO_MIN; + cfg->cfg.ts_flags = DFLT_TS_FLAGS; + clear_dst(cfg); + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + *ptr = opt->dflt; + } + + zlog_5424_init(&cfg->cfg); + + QOBJ_REG(cfg, zlog_cfg_5424_user); + targets_add(&targets, cfg); + return cfg; +} + +static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen) +{ + targets_del(&targets, cfg); + QOBJ_UNREG(cfg); + + zlog_5424_fini(&cfg->cfg, keepopen); + clear_dst(cfg); + + XFREE(MTYPE_LOG_5424_DATA, cfg->filename); + XFREE(MTYPE_LOG_5424_DATA, cfg->name); + XFREE(MTYPE_LOG_5424_CONFIG, cfg); +} + +static void clear_dst(struct zlog_cfg_5424_user *cfg) +{ + XFREE(MTYPE_LOG_5424_DATA, cfg->filename); + cfg->cfg.filename = cfg->filename; + + XFREE(MTYPE_LOG_5424_DATA, cfg->file_user); + XFREE(MTYPE_LOG_5424_DATA, cfg->file_group); + XFREE(MTYPE_LOG_5424_DATA, cfg->envvar); + + cfg->cfg.fd = -1; + cfg->cfg.file_uid = -1; + cfg->cfg.file_gid = -1; + cfg->cfg.file_mode = LOGFILE_MASK & 0666; + cfg->cfg.file_nocreate = false; + cfg->cfg.dst = ZLOG_5424_DST_NONE; +} + +static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) + vty_out(vty, + "%% Changes will be applied when exiting this config block\n"); + + cfg->reconf_dst = true; + return CMD_SUCCESS; +} + +static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) + vty_out(vty, + "%% Changes will be applied when exiting this config block\n"); + + cfg->reconf_meta = true; + return CMD_SUCCESS; +} + +static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) +{ + if (cfg->cfg.dst == ZLOG_5424_DST_NONE) + return CMD_SUCCESS; + + clear_dst(cfg); + return reconf_dst(cfg, vty); +} + +#include "lib/zlog_5424_cli_clippy.c" + +DEFPY_NOSH(log_5424_target, + log_5424_target_cmd, + "log extended-syslog EXTLOGNAME", + "Logging control\n" + "Extended RFC5424 syslog (including file targets)\n" + "Name identifying this syslog target\n") +{ + struct zlog_cfg_5424_user *cfg, ref; + + ref.name = (char *)extlogname; + cfg = targets_find(&targets, &ref); + + if (!cfg) + cfg = log_5424_alloc(extlogname); + + VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg); + return CMD_SUCCESS; +} + +DEFPY(no_log_5424_target, + no_log_5424_target_cmd, + "no log extended-syslog EXTLOGNAME", + NO_STR + "Logging control\n" + "Extended RFC5424 syslog (including file targets)\n" + "Name identifying this syslog target\n") +{ + struct zlog_cfg_5424_user *cfg, ref; + + ref.name = (char *)extlogname; + cfg = targets_find(&targets, &ref); + + if (!cfg) { + vty_out(vty, "%% No extended syslog target named \"%s\"\n", + extlogname); + return CMD_WARNING; + } + + log_5424_free(cfg, false); + return CMD_SUCCESS; +} + +/* "format $fmt" */ +#define FORMAT_HELP \ + "Select log message formatting\n" \ + "RFC3164 (legacy) syslog\n" \ + "RFC5424 (modern) syslog, supports structured data (default)\n" \ + "modified RFC3164 without hostname for local syslogd (/dev/log)\n" \ + "journald (systemd log) native format\n" \ + /* end */ + +static enum zlog_5424_format log_5424_fmt(const char *fmt, + enum zlog_5424_format dflt) +{ + if (!fmt) + return dflt; + else if (!strcmp(fmt, "rfc5424")) + return ZLOG_FMT_5424; + else if (!strcmp(fmt, "rfc3164")) + return ZLOG_FMT_3164; + else if (!strcmp(fmt, "local-syslogd")) + return ZLOG_FMT_LOCAL; + else if (!strcmp(fmt, "journald")) + return ZLOG_FMT_JOURNALD; + + return dflt; +} + +DEFPY(log_5424_destination_file, + log_5424_destination_file_cmd, + "[no] destination file$type PATH " + "[create$create [{user WORD|group WORD|mode PERMS}]" + "|no-create$nocreate] " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to file\n" + "Path to destination\n" + "Create file if it does not exist\n" + "Set file owner\n" + "User name\n" + "Set file group\n" + "Group name\n" + "Set permissions\n" + "File permissions (octal)\n" + "Do not create file if it does not exist\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + enum zlog_5424_dst dst; + bool reconf = true, warn_perm = false; + char *prev_user, *prev_group; + mode_t perm_val = LOGFILE_MASK & 0666; + enum zlog_5424_format fmtv; + + if (no) + return reconf_clear_dst(cfg, vty); + + fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + if (perms) { + char *errp = (char *)perms; + + perm_val = strtoul(perms, &errp, 8); + if (*errp || errp == perms || perm_val == 0 || + (perm_val & ~0666)) { + vty_out(vty, "%% Invalid permissions value \"%s\"\n", + perms); + return CMD_WARNING; + } + } + + dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO + : ZLOG_5424_DST_FILE; + + if (cfg->filename && !strcmp(path, cfg->filename) && + dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv) + reconf = false; + + /* keep for compare below */ + prev_user = cfg->file_user; + prev_group = cfg->file_group; + cfg->file_user = NULL; + cfg->file_group = NULL; + + clear_dst(cfg); + + cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); + cfg->cfg.dst = dst; + cfg->cfg.filename = cfg->filename; + cfg->cfg.fmt = fmtv; + + if (nocreate) + cfg->cfg.file_nocreate = true; + else { + if (user) { + struct passwd *pwent; + + warn_perm |= (prev_user && strcmp(user, prev_user)); + cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user); + + errno = 0; + pwent = getpwnam(user); + if (!pwent) + vty_out(vty, + "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n", + user, + errno ? safe_strerror(errno) + : "No entry by this user name"); + else + cfg->cfg.file_uid = pwent->pw_uid; + } + if (group) { + struct group *grent; + + warn_perm |= (prev_group && strcmp(group, prev_group)); + cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group); + + errno = 0; + grent = getgrnam(group); + if (!grent) + vty_out(vty, + "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n", + group, + errno ? safe_strerror(errno) + : "No entry by this group name"); + else + cfg->cfg.file_gid = grent->gr_gid; + } + } + XFREE(MTYPE_LOG_5424_DATA, prev_user); + XFREE(MTYPE_LOG_5424_DATA, prev_group); + + if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) { + struct stat st; + + if (stat(cfg->filename, &st) == 0) { + warn_perm |= (st.st_uid != cfg->cfg.file_uid); + warn_perm |= (st.st_gid != cfg->cfg.file_gid); + } + } + if (warn_perm) + vty_out(vty, + "%% Warning: ownership and permission bits are only applied when creating\n" + "%% log files. Use system tools to change existing files.\n" + "%% FRR may also be missing necessary privileges to set these.\n"); + + if (reconf) + return reconf_dst(cfg, vty); + + return CMD_SUCCESS; +} + +/* FIFOs are for legacy /dev/log implementations; using this is very much not + * recommended since it can unexpectedly block in logging calls. Also the fd + * would need to be reopened when the process at the other end restarts. None + * of this is handled - use at your own caution. It's _HIDDEN for a purpose. + */ +ALIAS_HIDDEN(log_5424_destination_file, + log_5424_destination_fifo_cmd, + "[no] destination fifo$type PATH " + "[create$create [{owner WORD|group WORD|permissions PERMS}]" + "|no-create$nocreate] " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to filesystem FIFO\n" + "Path to destination\n" + "Create file if it does not exist\n" + "Set file owner\n" + "User name\n" + "Set file group\n" + "Group name\n" + "Set permissions\n" + "File permissions (octal)\n" + "Do not create file if it does not exist\n" + FORMAT_HELP) + +static int dst_unix(struct vty *vty, const char *no, const char *path, + enum zlog_5424_format fmt, enum unix_special special) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + if (no) + return reconf_clear_dst(cfg, vty); + + cfg->unix_special = special; + + if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename && + !strcmp(path, cfg->filename) && cfg->cfg.active && + cfg->cfg.fmt == fmt) + return CMD_SUCCESS; + + clear_dst(cfg); + + cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); + cfg->cfg.dst = ZLOG_5424_DST_UNIX; + cfg->cfg.filename = cfg->filename; + cfg->cfg.fmt = fmt; + + cfg->cfg.reconn_backoff = 25; + cfg->cfg.reconn_backoff_cur = 25; + cfg->cfg.reconn_backoff_max = 10000; + return reconf_dst(cfg, vty); +} + +DEFPY(log_5424_destination_unix, + log_5424_destination_unix_cmd, + "[no] destination unix PATH " + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to unix socket\n" + "Unix socket path\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + return dst_unix(vty, no, path, fmtv, SPECIAL_NONE); +} + +DEFPY(log_5424_destination_journald, + log_5424_destination_journald_cmd, + "[no] destination journald", + NO_STR + "Log destination setup\n" + "Log directly to systemd's journald\n") +{ + return dst_unix(vty, no, "/run/systemd/journal/socket", + ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD); +} + +#if defined(__FreeBSD_version) && (__FreeBSD_version >= 1200061) +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 +#elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 500000000) +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 +#else +#define ZLOG_FMT_DEV_LOG ZLOG_FMT_LOCAL +#endif + +DEFPY(log_5424_destination_syslog, + log_5424_destination_syslog_cmd, + "[no] destination syslog [supports-rfc5424]$supp5424", + NO_STR + "Log destination setup\n" + "Log directly to syslog\n" + "Use RFC5424 format (please refer to documentation)\n") +{ + int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG; + + /* unfortunately, there is no way to detect 5424 support */ + return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG); +} + +/* could add something like + * "destination $proto (1-65535)$port" + * here, but there are 2 reasons not to do that: + * + * - each FRR daemon would open its own connection, there's no system level + * aggregation. That's the system's syslogd's job. It likely also + * supports directing & filtering log messages with configurable rules. + * - we're likely not going to support DTLS or TLS for more secure logging; + * adding this would require a considerable amount of additional config + * and an entire TLS library to begin with. A proper syslogd implements + * all of this, why reinvent the wheel? + */ + +DEFPY(log_5424_destination_fd, + log_5424_destination_fd_cmd, + "[no] destination |stdout$fd1|stderr$fd2>" + "[format $fmt]", + NO_STR + "Log destination setup\n" + "Log to pre-opened file descriptor\n" + "File descriptor number (must be open at startup)\n" + "Read file descriptor number from environment variable\n" + "Environment variable name\n" + "Log to standard output\n" + "Log to standard error output\n" + FORMAT_HELP) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + bool envvar_problem = false; + enum zlog_5424_format fmtv; + + if (no) + return reconf_clear_dst(cfg, vty); + + fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); + + if (envvar) { + char *envval; + + envval = getenv(envvar); + if (!envval) + envvar_problem = true; + else { + char *errp = envval; + + fd = strtoul(envval, &errp, 0); + if (errp == envval || *errp) + envvar_problem = true; + } + + if (envvar_problem) + fd = -1; + } else if (fd1) + fd = 1; + else if (fd2) + fd = 2; + + if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd && + cfg->cfg.active && cfg->cfg.fmt == fmtv) + return CMD_SUCCESS; + + clear_dst(cfg); + + cfg->cfg.dst = ZLOG_5424_DST_FD; + cfg->cfg.fd = fd; + cfg->cfg.fmt = fmtv; + if (envvar) + cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar); + + if (envvar_problem) + vty_out(vty, + "%% environment variable \"%s\" not present or invalid.\n", + envvar); + if (!frr_is_startup_fd(fd)) + vty_out(vty, + "%% file descriptor %d was not open when this process was started\n", + (int)fd); + if (envvar_problem || !frr_is_startup_fd(fd)) + vty_out(vty, + "%% configuration will be saved but has no effect currently\n"); + + return reconf_dst(cfg, vty); +} + +DEFPY(log_5424_destination_none, + log_5424_destination_none_cmd, + "[no] destination [none]", + NO_STR + "Log destination setup\n" + "Deconfigure destination\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + return reconf_clear_dst(cfg, vty); +} + +/* end of destinations */ + +DEFPY(log_5424_prio, + log_5424_prio_cmd, + "[no] priority $levelarg", + NO_STR + "Set minimum message priority to include for this target\n" + LOG_LEVEL_DESC) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + int prio_min = log_level_match(levelarg); + + if (prio_min == cfg->cfg.prio_min) + return CMD_SUCCESS; + + cfg->cfg.prio_min = prio_min; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_facility, + log_5424_facility_cmd, + "[no] facility $facilityarg", + NO_STR + "Set syslog facility to use\n" + LOG_FACILITY_DESC) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + int facility = facility_match(facilityarg); + + if (cfg->cfg.facility == facility) + return CMD_SUCCESS; + + cfg->cfg.facility = facility; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_meta, + log_5424_meta_cmd, + "[no] structured-data $option", + NO_STR + "Select structured data (key/value pairs) to include in each message\n" + "FRR source code location\n" + "FRR version\n" + "Unique message identifier (XXXXX-XXXXX)\n" + "Error category (EC numeric)\n" + "Individual formatted log message arguments\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + bool val = !no, *ptr; + struct log_option *opt = log_opts; + + while (opt->name && strcmp(opt->name, option)) + opt++; + if (!opt->name) + return CMD_WARNING; + + ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + if (*ptr == val) + return CMD_SUCCESS; + + *ptr = val; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_ts_prec, + log_5424_ts_prec_cmd, + "[no] timestamp precision (0-9)", + NO_STR + "Timestamp options\n" + "Number of sub-second digits to include\n" + "Number of sub-second digits to include\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + uint32_t ts_flags = cfg->cfg.ts_flags; + + ts_flags &= ~ZLOG_TS_PREC; + if (no) + ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC; + else + ts_flags |= precision; + + if (ts_flags == cfg->cfg.ts_flags) + return CMD_SUCCESS; + + cfg->cfg.ts_flags = ts_flags; + return reconf_meta(cfg, vty); +} + +DEFPY(log_5424_ts_local, + log_5424_ts_local_cmd, + "[no] timestamp local-time", + NO_STR + "Timestamp options\n" + "Use local system time zone rather than UTC\n") +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + uint32_t ts_flags = cfg->cfg.ts_flags; + + ts_flags &= ~ZLOG_TS_UTC; + if (no) + ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC; + else + ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC; + + if (ts_flags == cfg->cfg.ts_flags) + return CMD_SUCCESS; + + cfg->cfg.ts_flags = ts_flags; + return reconf_meta(cfg, vty); +} + +static int log_5424_node_exit(struct vty *vty) +{ + VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); + + if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE) + vty_out(vty, "%% applying changes.\n"); + + if (cfg->reconf_dst) + zlog_5424_apply_dst(&cfg->cfg); + else if (cfg->reconf_meta) + zlog_5424_apply_meta(&cfg->cfg); + + cfg->reconf_dst = cfg->reconf_meta = false; + return 1; +} + +static int log_5424_config_write(struct vty *vty) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) { + const char *fmt_str = ""; + + vty_out(vty, "log extended %s\n", cfg->name); + + switch (cfg->cfg.fmt) { + case ZLOG_FMT_5424: + fmt_str = " format rfc5424"; + break; + case ZLOG_FMT_3164: + fmt_str = " format rfc3164"; + break; + case ZLOG_FMT_LOCAL: + fmt_str = " format local-syslogd"; + break; + case ZLOG_FMT_JOURNALD: + fmt_str = " format journald"; + break; + } + + switch (cfg->cfg.dst) { + case ZLOG_5424_DST_NONE: + vty_out(vty, " ! no destination configured\n"); + break; + + case ZLOG_5424_DST_FD: + if (cfg->cfg.fmt == ZLOG_FMT_5424) + fmt_str = ""; + + if (cfg->envvar) + vty_out(vty, " destination fd envvar %s%s\n", + cfg->envvar, fmt_str); + else if (cfg->cfg.fd == 1) + vty_out(vty, " destination stdout%s\n", + fmt_str); + else if (cfg->cfg.fd == 2) + vty_out(vty, " destination stderr%s\n", + fmt_str); + else + vty_out(vty, " destination fd %d%s\n", + cfg->cfg.fd, fmt_str); + break; + + case ZLOG_5424_DST_FILE: + case ZLOG_5424_DST_FIFO: + if (cfg->cfg.fmt == ZLOG_FMT_5424) + fmt_str = ""; + + vty_out(vty, " destination %s %s", + (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" + : "file", + cfg->filename); + + if (cfg->cfg.file_nocreate) + vty_out(vty, " no-create"); + else if (cfg->file_user || cfg->file_group || + cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) { + vty_out(vty, " create"); + + if (cfg->file_user) + vty_out(vty, " user %s", + cfg->file_user); + if (cfg->file_group) + vty_out(vty, " group %s", + cfg->file_group); + if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) + vty_out(vty, " mode %04o", + cfg->cfg.file_mode); + } + vty_out(vty, "%s\n", fmt_str); + break; + + case ZLOG_5424_DST_UNIX: + switch (cfg->unix_special) { + case SPECIAL_NONE: + vty_out(vty, " destination unix %s%s\n", + cfg->filename, fmt_str); + break; + case SPECIAL_SYSLOG: + if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG) + vty_out(vty, " destination syslog\n"); + else + vty_out(vty, + " destination syslog supports-rfc5424\n"); + break; + case SPECIAL_JOURNALD: + vty_out(vty, " destination journald\n"); + break; + } + break; + } + + if (cfg->cfg.prio_min != LOG_DEBUG) + vty_out(vty, " priority %s\n", + zlog_priority_str(cfg->cfg.prio_min)); + if (cfg->cfg.facility != DFLT_FACILITY) + vty_out(vty, " facility %s\n", + facility_name(cfg->cfg.facility)); + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + + if (*ptr != opt->dflt) + vty_out(vty, " %sstructured-data %s\n", + *ptr ? "" : "no ", opt->name); + } + + if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC) + vty_out(vty, " timestamp precision %u\n", + cfg->cfg.ts_flags & ZLOG_TS_PREC); + + if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) { + if (cfg->cfg.ts_flags & ZLOG_TS_UTC) + vty_out(vty, " no timestamp local-time\n"); + else + vty_out(vty, " timestamp local-time\n"); + } + + vty_out(vty, "!\n"); + } + return 0; +} + +static int log_5424_show(struct vty *vty) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) { + vty_out(vty, "\nExtended log target %pSQq\n", cfg->name); + + switch (cfg->cfg.dst) { + case ZLOG_5424_DST_NONE: + vty_out(vty, + " Inactive (no destination configured)\n"); + break; + + case ZLOG_5424_DST_FD: + if (cfg->envvar) + vty_out(vty, + " logging to fd %d from environment variable %pSE\n", + cfg->cfg.fd, cfg->envvar); + else if (cfg->cfg.fd == 1) + vty_out(vty, " logging to stdout\n"); + else if (cfg->cfg.fd == 2) + vty_out(vty, " logging to stderr\n"); + else + vty_out(vty, " logging to fd %d\n", + cfg->cfg.fd); + break; + + case ZLOG_5424_DST_FILE: + case ZLOG_5424_DST_FIFO: + case ZLOG_5424_DST_UNIX: + vty_out(vty, " logging to %s: %pSE\n", + (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" + : (cfg->cfg.dst == ZLOG_5424_DST_UNIX) + ? "unix socket" + : "file", + cfg->filename); + break; + } + + vty_out(vty, " log level: %s, facility: %s\n", + zlog_priority_str(cfg->cfg.prio_min), + facility_name(cfg->cfg.facility)); + + bool any_meta = false, first = true; + + for (struct log_option *opt = log_opts; opt->name; opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); + + any_meta |= *ptr; + } + + if (!any_meta) + continue; + + switch (cfg->cfg.fmt) { + case ZLOG_FMT_5424: + case ZLOG_FMT_JOURNALD: + vty_out(vty, " structured data: "); + + for (struct log_option *opt = log_opts; opt->name; + opt++) { + bool *ptr = (bool *)(((char *)&cfg->cfg) + + opt->offs); + + if (*ptr) { + vty_out(vty, "%s%s", first ? "" : ", ", + opt->name); + first = false; + } + } + break; + + case ZLOG_FMT_3164: + case ZLOG_FMT_LOCAL: + vty_out(vty, + " structured data is not supported by the selected format\n"); + break; + } + + vty_out(vty, "\n"); + + size_t lost_msgs; + int last_errno; + bool stale_errno; + struct timeval err_ts; + int64_t since; + + zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno, + &stale_errno, &err_ts); + vty_out(vty, " number of lost messages: %zu\n", lost_msgs); + + if (last_errno == 0) + since = 0; + else + since = monotime_since(&err_ts, NULL); + vty_out(vty, + " last error: %s (%lld.%06llds ago, currently %s)\n", + last_errno ? safe_strerror(last_errno) : "none", + since / 1000000LL, since % 1000000LL, + stale_errno ? "OK" : "erroring"); + } + return 0; +} + +static struct cmd_node extlog_node = { + .name = "extended", + .node = EXTLOG_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-ext-log)# ", + + .config_write = log_5424_config_write, + .node_exit = log_5424_node_exit, +}; + +static void log_5424_autocomplete(vector comps, struct cmd_token *token) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name)); +} + +static const struct cmd_variable_handler log_5424_var_handlers[] = { + {.tokenname = "EXTLOGNAME", .completions = log_5424_autocomplete}, + {.completions = NULL}, +}; + +void log_5424_cmd_init(void) +{ + hook_register(zlog_cli_show, log_5424_show); + + cmd_variable_handler_register(log_5424_var_handlers); + + /* CLI commands. */ + install_node(&extlog_node); + install_default(EXTLOG_NODE); + + install_element(CONFIG_NODE, &log_5424_target_cmd); + install_element(CONFIG_NODE, &no_log_5424_target_cmd); + + install_element(EXTLOG_NODE, &log_5424_destination_file_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd); + install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd); + + install_element(EXTLOG_NODE, &log_5424_meta_cmd); + install_element(EXTLOG_NODE, &log_5424_prio_cmd); + install_element(EXTLOG_NODE, &log_5424_facility_cmd); + install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd); + install_element(EXTLOG_NODE, &log_5424_ts_local_cmd); +} + +/* hooks */ + +static int log_5424_early_init(struct event_loop *master); +static int log_5424_rotate(void); +static int log_5424_fini(void); + +__attribute__((_CONSTRUCTOR(475))) static void zlog_5424_startup_init(void) +{ + hook_register(frr_early_init, log_5424_early_init); + hook_register(zlog_rotate, log_5424_rotate); + hook_register(frr_fini, log_5424_fini); +} + +static int log_5424_early_init(struct event_loop *master) +{ + log_5424_master = master; + + return 0; +} + +static int log_5424_rotate(void) +{ + struct zlog_cfg_5424_user *cfg; + + frr_each (targets, &targets, cfg) + if (!zlog_5424_rotate(&cfg->cfg)) + zlog_err( + "log rotation on extended log target %s failed", + cfg->name); + + return 0; +} + +static int log_5424_fini(void) +{ + struct zlog_cfg_5424_user *cfg; + + while ((cfg = targets_pop(&targets))) + log_5424_free(cfg, true); + + log_5424_master = NULL; + + return 0; +} diff --git a/lib/zlog_live.c b/lib/zlog_live.c new file mode 100644 index 0000000..1b7696c --- /dev/null +++ b/lib/zlog_live.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc. + */ + +#include "zebra.h" + +#include "frrsendmmsg.h" + +#include "zlog_live.h" + +#include "memory.h" +#include "frrcu.h" +#include "zlog.h" +#include "printfrr.h" +#include "network.h" + +DEFINE_MTYPE_STATIC(LOG, LOG_LIVE, "log vtysh live target"); + +enum { + STATE_NORMAL = 0, + STATE_FD_DEAD, + STATE_DISOWNED, +}; + +struct zlt_live { + struct zlog_target zt; + + atomic_uint_fast32_t fd; + struct rcu_head_close head_close; + struct rcu_head head_self; + + atomic_uint_fast32_t state; + atomic_uint_fast32_t lost_msgs; +}; + +static void zlog_live(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + struct zlt_live *zte = container_of(zt, struct zlt_live, zt); + struct zlog_live_hdr hdrs[nmsgs], *hdr = hdrs; + struct mmsghdr mmhs[nmsgs], *mmh = mmhs; + struct iovec iovs[nmsgs * 3], *iov = iovs; + struct timespec ts; + size_t i, textlen; + int fd; + uint_fast32_t state; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + if (fd < 0) + return; + + memset(mmhs, 0, sizeof(mmhs)); + memset(hdrs, 0, sizeof(hdrs)); + + for (i = 0; i < nmsgs; i++) { + const struct fmt_outpos *argpos; + size_t n_argpos, texthdrlen; + struct zlog_msg *msg = msgs[i]; + int prio = zlog_msg_prio(msg); + const struct xref_logmsg *xref; + intmax_t pid, tid; + + if (prio > zt->prio_min) + continue; + + zlog_msg_args(msg, &texthdrlen, &n_argpos, &argpos); + + mmh->msg_hdr.msg_iov = iov; + + iov->iov_base = hdr; + iov->iov_len = sizeof(*hdr); + iov++; + + if (n_argpos) { + iov->iov_base = (char *)argpos; + iov->iov_len = sizeof(*argpos) * n_argpos; + iov++; + } + + iov->iov_base = (char *)zlog_msg_text(msg, &textlen); + iov->iov_len = textlen; + iov++; + + zlog_msg_tsraw(msg, &ts); + zlog_msg_pid(msg, &pid, &tid); + xref = zlog_msg_xref(msg); + + hdr->ts_sec = ts.tv_sec; + hdr->ts_nsec = ts.tv_nsec; + hdr->pid = pid; + hdr->tid = tid; + hdr->lost_msgs = atomic_load_explicit(&zte->lost_msgs, + memory_order_relaxed); + hdr->prio = prio; + hdr->flags = 0; + hdr->textlen = textlen; + hdr->texthdrlen = texthdrlen; + hdr->n_argpos = n_argpos; + if (xref) { + memcpy(hdr->uid, xref->xref.xrefdata->uid, + sizeof(hdr->uid)); + hdr->ec = xref->ec; + } else { + memset(hdr->uid, 0, sizeof(hdr->uid)); + hdr->ec = 0; + } + hdr->hdrlen = sizeof(*hdr) + sizeof(*argpos) * n_argpos; + + mmh->msg_hdr.msg_iovlen = iov - mmh->msg_hdr.msg_iov; + mmh++; + hdr++; + } + + size_t msgtotal = mmh - mmhs; + ssize_t sent; + + for (size_t msgpos = 0; msgpos < msgtotal; msgpos += sent) { + sent = sendmmsg(fd, mmhs + msgpos, msgtotal - msgpos, 0); + + if (sent <= 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + atomic_fetch_add_explicit(&zte->lost_msgs, + msgtotal - msgpos, + memory_order_relaxed); + break; + } + if (sent <= 0) + goto out_err; + } + return; + +out_err: + fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed); + if (fd < 0) + return; + + rcu_close(&zte->head_close, fd); + zlog_target_replace(zt, NULL); + + state = STATE_NORMAL; + atomic_compare_exchange_strong_explicit( + &zte->state, &state, STATE_FD_DEAD, memory_order_relaxed, + memory_order_relaxed); + if (state == STATE_DISOWNED) + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} + +static void zlog_live_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + struct zlt_live *zte = container_of(zt, struct zlt_live, zt); + struct zlog_live_hdr hdr[1] = {}; + struct iovec iovs[2], *iov = iovs; + struct timespec ts; + int fd; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + if (fd < 0) + return; + + clock_gettime(CLOCK_REALTIME, &ts); + + hdr->ts_sec = ts.tv_sec; + hdr->ts_nsec = ts.tv_nsec; + hdr->prio = LOG_CRIT; + hdr->textlen = len; + + iov->iov_base = (char *)hdr; + iov->iov_len = sizeof(hdr); + iov++; + + iov->iov_base = (char *)text; + iov->iov_len = len; + iov++; + + writev(fd, iovs, iov - iovs); +} + +void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, int *other_fd) +{ + int sockets[2]; + + if (cfg->target) + zlog_live_close(cfg); + + *other_fd = -1; + if (prio_min == ZLOG_DISABLED) + return; + + /* the only reason for SEQPACKET here is getting close notifications. + * otherwise if you open a bunch of vtysh connections with live logs + * and close them all, the fds will stick around until we get an error + * when trying to log something to them at some later point -- which + * eats up fds and might be *much* later for some daemons. + */ + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) < 0) { + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) { + zlog_warn("%% could not open socket pair: %m"); + return; + } + } else + /* SEQPACKET only: try to zap read direction */ + shutdown(sockets[0], SHUT_RD); + + *other_fd = sockets[1]; + zlog_live_open_fd(cfg, prio_min, sockets[0]); +} + +void zlog_live_open_fd(struct zlog_live_cfg *cfg, int prio_min, int fd) +{ + struct zlt_live *zte; + struct zlog_target *zt; + + if (cfg->target) + zlog_live_close(cfg); + + zt = zlog_target_clone(MTYPE_LOG_LIVE, NULL, sizeof(*zte)); + zte = container_of(zt, struct zlt_live, zt); + cfg->target = zte; + + set_nonblocking(fd); + zte->fd = fd; + zte->zt.prio_min = prio_min; + zte->zt.logfn = zlog_live; + zte->zt.logfn_sigsafe = zlog_live_sigsafe; + + zlog_target_replace(NULL, zt); +} + +void zlog_live_close(struct zlog_live_cfg *cfg) +{ + struct zlt_live *zte; + int fd; + + if (!cfg->target) + return; + + zte = cfg->target; + cfg->target = NULL; + + fd = atomic_exchange_explicit(&zte->fd, -1, memory_order_relaxed); + + if (fd >= 0) { + rcu_close(&zte->head_close, fd); + zlog_target_replace(&zte->zt, NULL); + } + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} + +void zlog_live_disown(struct zlog_live_cfg *cfg) +{ + struct zlt_live *zte; + uint_fast32_t state; + + if (!cfg->target) + return; + + zte = cfg->target; + cfg->target = NULL; + + state = STATE_NORMAL; + atomic_compare_exchange_strong_explicit( + &zte->state, &state, STATE_DISOWNED, memory_order_relaxed, + memory_order_relaxed); + if (state == STATE_FD_DEAD) + rcu_free(MTYPE_LOG_LIVE, zte, head_self); +} diff --git a/lib/zlog_live.h b/lib/zlog_live.h new file mode 100644 index 0000000..17245fe --- /dev/null +++ b/lib/zlog_live.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019-22 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ZLOG_LIVE_H +#define _FRR_ZLOG_LIVE_H + +#include "printfrr.h" + +struct zlog_live_hdr { + /* timestamp (CLOCK_REALTIME) */ + uint64_t ts_sec; + uint32_t ts_nsec; + + /* length of zlog_live_hdr, including variable length bits and + * possible future extensions - aka start of text + */ + uint32_t hdrlen; + + /* process & thread ID, meaning depends on OS */ + int64_t pid; + int64_t tid; + + /* number of lost messages due to best-effort non-blocking mode */ + uint32_t lost_msgs; + /* syslog priority value */ + uint32_t prio; + /* flags: currently unused */ + uint32_t flags; + /* length of message text - extra data (e.g. future key/value metadata) + * may follow after it + */ + uint32_t textlen; + /* length of "[XXXXX-XXXXX][EC 0] " header; consumer may want to skip + * over it if using the raw values below. Note that this text may be + * absent depending on "log error-category" and "log unique-id" + * settings + */ + uint32_t texthdrlen; + + /* xref unique identifier, "XXXXX-XXXXX\0" = 12 bytes */ + char uid[12]; + /* EC value */ + uint32_t ec; + + /* recorded printf formatting argument positions (variable length) */ + uint32_t n_argpos; + struct fmt_outpos argpos[0]; +}; + +struct zlt_live; + +struct zlog_live_cfg { + struct zlt_live *target; + + /* nothing else here */ +}; + +extern void zlog_live_open(struct zlog_live_cfg *cfg, int prio_min, + int *other_fd); +extern void zlog_live_open_fd(struct zlog_live_cfg *cfg, int prio_min, int fd); + +static inline bool zlog_live_is_null(struct zlog_live_cfg *cfg) +{ + return cfg->target == NULL; +} + +extern void zlog_live_close(struct zlog_live_cfg *cfg); +extern void zlog_live_disown(struct zlog_live_cfg *cfg); + +#endif /* _FRR_ZLOG_5424_H */ diff --git a/lib/zlog_recirculate.c b/lib/zlog_recirculate.c new file mode 100644 index 0000000..abc73ee --- /dev/null +++ b/lib/zlog_recirculate.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2024 David Lamparter, for NetDEF, Inc. + */ + +#include "zebra.h" + +#include "log.h" +#include "frrevent.h" + +#include "zlog_recirculate.h" + +/* This is only the event loop part; it's split off from + * zlog_recirculate_live_msg since there's an integration boundary; this + * half deals with events, the other half with zlog interna. + * + * As of writing, this runs in ldpd in the *parent* process and receives log + * messages from the lde/ldpe subprocesses. It is not used anywhere else + * (yet?) + */ +static void zlog_recirculate_recv(struct event *ev) +{ + uint8_t rxbuf[4096]; + ssize_t n_rd; + int fd = EVENT_FD(ev); + + /* see below for -2, "\n\0" are added */ + n_rd = read(fd, rxbuf, sizeof(rxbuf) - 2); + if (n_rd == 0) { + /* EOF */ + close(fd); + /* event_add_read not called yet, nothing to cancel */ + return; + } + if (n_rd < 0 && (errno != EAGAIN) && (errno != EWOULDBLOCK)) { + /* error */ + zlog_warn("error on log relay socket %d: %m", fd); + close(fd); + /* event_add_read not called yet, nothing to cancel */ + return; + } + + event_add_read(ev->master, zlog_recirculate_recv, NULL, fd, NULL); + if (n_rd < 0) + return; + + /* log infrastructure has an implicit \n\0 at the end */ + rxbuf[n_rd] = '\n'; + rxbuf[n_rd + 1] = '\0'; + zlog_recirculate_live_msg(rxbuf, n_rd); +} + +void zlog_recirculate_subscribe(struct event_loop *el, int fd) +{ + event_add_read(el, zlog_recirculate_recv, NULL, fd, NULL); +} diff --git a/lib/zlog_recirculate.h b/lib/zlog_recirculate.h new file mode 100644 index 0000000..a2ddb4e --- /dev/null +++ b/lib/zlog_recirculate.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2024 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ZLOG_RECIRCULATE_H +#define _FRR_ZLOG_RECIRCULATE_H + +/* fd should be one end of a socketpair() */ +extern void zlog_recirculate_subscribe(struct event_loop *tm, int fd); + +#endif diff --git a/lib/zlog_targets.c b/lib/zlog_targets.c new file mode 100644 index 0000000..bbd228f --- /dev/null +++ b/lib/zlog_targets.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc. + */ + +#include "zebra.h" + +#include +#include +#include + +#include "memory.h" +#include "frrcu.h" +#include "frr_pthread.h" +#include "printfrr.h" +#include "zlog.h" +#include "zlog_targets.h" + +/* these allocations are intentionally left active even when doing full exit + * cleanup, in order to keep the logging subsystem fully functional until the + * absolute end. + */ + +DEFINE_MGROUP_ACTIVEATEXIT(LOG, "logging subsystem"); + +DEFINE_MTYPE_STATIC(LOG, LOG_FD, "log file target"); +DEFINE_MTYPE_STATIC(LOG, LOG_FD_NAME, "log file name"); +DEFINE_MTYPE_STATIC(LOG, LOG_FD_ROTATE, "log file rotate helper"); +DEFINE_MTYPE_STATIC(LOG, LOG_SYSL, "syslog target"); + +struct zlt_fd { + struct zlog_target zt; + + atomic_uint_fast32_t fd; + + char ts_subsec; + bool record_priority; + + struct rcu_head_close head_close; +}; + +static const char * const prionames[] = { + [LOG_EMERG] = "emergencies: ", + [LOG_ALERT] = "alerts: ", + [LOG_CRIT] = "critical: ", + [LOG_ERR] = "errors: ", + [LOG_WARNING] = "warnings: ", + [LOG_NOTICE] = "notifications: ", + [LOG_INFO] = "informational: ", + [LOG_DEBUG] = "debugging: ", +}; + +void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[], size_t nmsgs) +{ + struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt); + int fd; + size_t i, textlen, iovpos = 0; + size_t niov = MIN(4 * nmsgs + 1, IOV_MAX); + struct iovec iov[niov]; + /* "\nYYYY-MM-DD HH:MM:SS.NNNNNNNNN+ZZ:ZZ " = 37 chars */ +#define TS_LEN 40 + char ts_buf[TS_LEN * nmsgs], *ts_pos = ts_buf; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + for (i = 0; i < nmsgs; i++) { + struct zlog_msg *msg = msgs[i]; + int prio = zlog_msg_prio(msg); + + if (prio <= zt->prio_min) { + struct fbuf fbuf = { + .buf = ts_buf, + .pos = ts_pos, + .len = sizeof(ts_buf), + }; + + iov[iovpos].iov_base = ts_pos; + zlog_msg_ts(msg, &fbuf, + ZLOG_TS_LEGACY | zte->ts_subsec); + ts_pos = fbuf.pos; + + *ts_pos++ = ' '; + iov[iovpos].iov_len = + ts_pos - (char *)iov[iovpos].iov_base; + + iovpos++; + + if (zte->record_priority) { + iov[iovpos].iov_base = (char *)prionames[prio]; + iov[iovpos].iov_len = + strlen(iov[iovpos].iov_base); + + iovpos++; + } + + iov[iovpos].iov_base = zlog_prefix; + iov[iovpos].iov_len = zlog_prefixsz; + + iovpos++; + + iov[iovpos].iov_base = + (char *)zlog_msg_text(msg, &textlen); + iov[iovpos].iov_len = textlen + 1; + + iovpos++; + } + + /* conditions that trigger writing: + * - out of space for more timestamps/headers + * - this being the last message in the batch + * - not enough remaining iov entries + */ + if (iovpos > 0 && (ts_buf + sizeof(ts_buf) - ts_pos < TS_LEN + || i + 1 == nmsgs + || array_size(iov) - iovpos < 5)) { + writev(fd, iov, iovpos); + + iovpos = 0; + ts_pos = ts_buf; + } + } + + assert(iovpos == 0); +} + +static void zlog_fd_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + struct zlt_fd *zte = container_of(zt, struct zlt_fd, zt); + struct iovec iov[4]; + int fd; + + iov[0].iov_base = (char *)prionames[LOG_CRIT]; + iov[0].iov_len = zte->record_priority ? strlen(iov[0].iov_base) : 0; + + iov[1].iov_base = zlog_prefix; + iov[1].iov_len = zlog_prefixsz; + + iov[2].iov_base = (char *)text; + iov[2].iov_len = len; + + iov[3].iov_base = (char *)"\n"; + iov[3].iov_len = 1; + + fd = atomic_load_explicit(&zte->fd, memory_order_relaxed); + + writev(fd, iov, array_size(iov)); +} + +/* + * (re-)configuration + */ + +void zlog_file_init(struct zlog_cfg_file *zcf) +{ + memset(zcf, 0, sizeof(*zcf)); + zcf->prio_min = ZLOG_DISABLED; + zcf->fd = -1; + pthread_mutex_init(&zcf->cfg_mtx, NULL); +} + +static void zlog_file_target_free(struct zlt_fd *zlt) +{ + if (!zlt) + return; + + rcu_close(&zlt->head_close, zlt->fd); + rcu_free(MTYPE_LOG_FD, zlt, zt.rcu_head); +} + +void zlog_file_fini(struct zlog_cfg_file *zcf) +{ + if (zcf->active) { + struct zlt_fd *ztf; + struct zlog_target *zt; + + zt = zlog_target_replace(&zcf->active->zt, NULL); + ztf = container_of(zt, struct zlt_fd, zt); + zlog_file_target_free(ztf); + } + XFREE(MTYPE_LOG_FD_NAME, zcf->filename); + pthread_mutex_destroy(&zcf->cfg_mtx); +} + +static bool zlog_file_cycle(struct zlog_cfg_file *zcf) +{ + struct zlog_target *zt, *old; + struct zlt_fd *zlt = NULL; + int fd; + bool rv = true; + + do { + if (zcf->prio_min == ZLOG_DISABLED) + break; + + if (zcf->fd != -1) + fd = dup(zcf->fd); + else if (zcf->filename) + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC + | O_NOCTTY, + LOGFILE_MASK); + else + fd = -1; + + if (fd < 0) { + rv = false; + break; + } + + zt = zlog_target_clone(MTYPE_LOG_FD, &zcf->active->zt, + sizeof(*zlt)); + zlt = container_of(zt, struct zlt_fd, zt); + + zlt->fd = fd; + zlt->record_priority = zcf->record_priority; + zlt->ts_subsec = zcf->ts_subsec; + + zlt->zt.prio_min = zcf->prio_min; + zlt->zt.logfn = zcf->zlog_wrap ? zcf->zlog_wrap : zlog_fd; + zlt->zt.logfn_sigsafe = zlog_fd_sigsafe; + } while (0); + + old = zlog_target_replace(zcf->active ? &zcf->active->zt : NULL, + zlt ? &zlt->zt : NULL); + zcf->active = zlt; + + zlog_file_target_free(container_of_null(old, struct zlt_fd, zt)); + + return rv; +} + +void zlog_file_set_other(struct zlog_cfg_file *zcf) +{ + frr_with_mutex (&zcf->cfg_mtx) { + zlog_file_cycle(zcf); + } +} + +bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *filename) +{ + frr_with_mutex (&zcf->cfg_mtx) { + XFREE(MTYPE_LOG_FD_NAME, zcf->filename); + zcf->filename = XSTRDUP(MTYPE_LOG_FD_NAME, filename); + zcf->fd = -1; + + return zlog_file_cycle(zcf); + } + assert(0); + return false; +} + +bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd) +{ + frr_with_mutex (&zcf->cfg_mtx) { + if (zcf->fd == fd) + return true; + + XFREE(MTYPE_LOG_FD_NAME, zcf->filename); + zcf->fd = fd; + + return zlog_file_cycle(zcf); + } + assert(0); + return false; +} + +struct rcu_close_rotate { + struct rcu_head_close head_close; + struct rcu_head head_self; +}; + +bool zlog_file_rotate(struct zlog_cfg_file *zcf) +{ + struct rcu_close_rotate *rcr; + int fd; + + frr_with_mutex (&zcf->cfg_mtx) { + if (!zcf->active || !zcf->filename) + return true; + + fd = open(zcf->filename, + O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC | O_NOCTTY, + LOGFILE_MASK); + if (fd < 0) + return false; + + fd = atomic_exchange_explicit(&zcf->active->fd, + (uint_fast32_t)fd, + memory_order_relaxed); + } + + rcr = XCALLOC(MTYPE_LOG_FD_ROTATE, sizeof(*rcr)); + rcu_close(&rcr->head_close, fd); + rcu_free(MTYPE_LOG_FD_ROTATE, rcr, head_self); + + return true; +} + +/* fixed crash logging */ + +static struct zlt_fd zlog_crashlog; + +static void zlog_crashlog_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + static int crashlog_fd = -1; + + if (crashlog_fd == -1) { +#ifdef HAVE_OPENAT + crashlog_fd = openat(zlog_tmpdirfd, "crashlog", + O_WRONLY | O_APPEND | O_CREAT, + LOGFILE_MASK); +#endif + if (crashlog_fd < 0) + crashlog_fd = -2; + } + + if (crashlog_fd == -2) + return; + + zlog_crashlog.fd = crashlog_fd; + zlog_fd_sigsafe(&zlog_crashlog.zt, text, len); +} + +/* this is used for assert failures (they don't need AS-Safe logging) */ +static void zlog_crashlog_plain(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + size_t i, len; + const char *text; + + for (i = 0; i < nmsgs; i++) { + if (zlog_msg_prio(msgs[i]) > zt->prio_min) + continue; + + text = zlog_msg_text(msgs[i], &len); + zlog_crashlog_sigsafe(zt, text, len); + } +} + +static void zlog_crashlog_init(void) +{ + zlog_crashlog.zt.prio_min = LOG_CRIT; + zlog_crashlog.zt.logfn = zlog_crashlog_plain; + zlog_crashlog.zt.logfn_sigsafe = zlog_crashlog_sigsafe; + zlog_crashlog.fd = -1; + + zlog_target_replace(NULL, &zlog_crashlog.zt); +} + +/* fixed logging for test/auxiliary programs */ + +static struct zlt_fd zlog_aux_stdout; +static bool zlog_is_aux; + +static int zlt_aux_init(const char *prefix, int prio_min) +{ + zlog_is_aux = true; + + zlog_aux_stdout.zt.prio_min = prio_min; + zlog_aux_stdout.zt.logfn = zlog_fd; + zlog_aux_stdout.zt.logfn_sigsafe = zlog_fd_sigsafe; + zlog_aux_stdout.fd = STDOUT_FILENO; + + zlog_target_replace(NULL, &zlog_aux_stdout.zt); + zlog_startup_end(); + return 0; +} + +static int zlt_init(const char *progname, const char *protoname, + unsigned short instance, uid_t uid, gid_t gid) +{ + openlog(progname, LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); + return 0; +} + +static int zlt_fini(void) +{ + closelog(); + return 0; +} + +/* fixed startup logging to stderr */ + +static struct zlt_fd zlog_startup_stderr; + +__attribute__((_CONSTRUCTOR(450))) static void zlog_startup_init(void) +{ + zlog_startup_stderr.zt.prio_min = LOG_WARNING; + zlog_startup_stderr.zt.logfn = zlog_fd; + zlog_startup_stderr.zt.logfn_sigsafe = zlog_fd_sigsafe; + zlog_startup_stderr.fd = STDERR_FILENO; + + zlog_target_replace(NULL, &zlog_startup_stderr.zt); + + hook_register(zlog_aux_init, zlt_aux_init); + hook_register(zlog_init, zlt_init); + hook_register(zlog_fini, zlt_fini); +} + +void zlog_startup_end(void) +{ + static bool startup_ended = false; + + if (startup_ended) + return; + startup_ended = true; + + zlog_target_replace(&zlog_startup_stderr.zt, NULL); + + if (zlog_is_aux) + return; + + /* until here, crashlogs go to stderr */ + zlog_crashlog_init(); +} + +/* syslog */ + +struct zlt_syslog { + struct zlog_target zt; + + int syslog_facility; +}; + +static void zlog_syslog(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs) +{ + size_t i; + struct zlt_syslog *zte = container_of(zt, struct zlt_syslog, zt); + const char *text; + size_t text_len; + + for (i = 0; i < nmsgs; i++) { + if (zlog_msg_prio(msgs[i]) > zt->prio_min) + continue; + + text = zlog_msg_text(msgs[i], &text_len); + syslog(zlog_msg_prio(msgs[i]) | zte->syslog_facility, "%.*s", + (int)text_len, text); + } +} + +#ifndef _PATH_LOG +#define _PATH_LOG "/dev/log" +#endif + +static void zlog_syslog_sigsafe(struct zlog_target *zt, const char *text, + size_t len) +{ + static int syslog_fd = -1; + + char hdr[192]; + size_t hdrlen; + struct iovec iov[2]; + + if (syslog_fd == -1) { + syslog_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (syslog_fd >= 0) { + struct sockaddr_un sa; + socklen_t salen = sizeof(sa); + + sa.sun_family = AF_UNIX; + strlcpy(sa.sun_path, _PATH_LOG, sizeof(sa.sun_path)); +#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN + salen = sa.sun_len = SUN_LEN(&sa); +#endif + if (connect(syslog_fd, (struct sockaddr *)&sa, salen)) { + close(syslog_fd); + syslog_fd = -1; + } + } + + /* /dev/log could be a fifo instead of a socket */ + if (syslog_fd == -1) { + syslog_fd = open(_PATH_LOG, O_WRONLY | O_NOCTTY); + if (syslog_fd < 0) + /* give up ... */ + syslog_fd = -2; + } + } + + if (syslog_fd == -2) + return; + + /* note zlog_prefix includes trailing ": ", need to cut off 2 chars */ + hdrlen = snprintfrr(hdr, sizeof(hdr), "<%d>%.*s[%ld]: ", LOG_CRIT, + zlog_prefixsz > 2 ? (int)(zlog_prefixsz - 2) : 0, + zlog_prefix, (long)getpid()); + + iov[0].iov_base = hdr; + iov[0].iov_len = hdrlen; + + iov[1].iov_base = (char *)text; + iov[1].iov_len = len; + + writev(syslog_fd, iov, array_size(iov)); +} + + +static pthread_mutex_t syslog_cfg_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct zlt_syslog *zlt_syslog; +static int syslog_facility = LOG_DAEMON; +static int syslog_prio_min = ZLOG_DISABLED; + +void zlog_syslog_set_facility(int facility) +{ + struct zlog_target *newztc; + struct zlt_syslog *newzt; + + frr_with_mutex (&syslog_cfg_mutex) { + if (facility == syslog_facility) + return; + syslog_facility = facility; + + if (syslog_prio_min == ZLOG_DISABLED) + return; + + newztc = zlog_target_clone(MTYPE_LOG_SYSL, &zlt_syslog->zt, + sizeof(*newzt)); + newzt = container_of(newztc, struct zlt_syslog, zt); + newzt->syslog_facility = syslog_facility; + + zlog_target_free(MTYPE_LOG_SYSL, + zlog_target_replace(&zlt_syslog->zt, + &newzt->zt)); + + zlt_syslog = newzt; + } +} + +int zlog_syslog_get_facility(void) +{ + frr_with_mutex (&syslog_cfg_mutex) { + return syslog_facility; + } + assert(0); + return 0; +} + +void zlog_syslog_set_prio_min(int prio_min) +{ + struct zlog_target *newztc; + struct zlt_syslog *newzt = NULL; + + frr_with_mutex (&syslog_cfg_mutex) { + if (prio_min == syslog_prio_min) + return; + syslog_prio_min = prio_min; + + if (syslog_prio_min != ZLOG_DISABLED) { + newztc = zlog_target_clone(MTYPE_LOG_SYSL, + &zlt_syslog->zt, + sizeof(*newzt)); + newzt = container_of(newztc, struct zlt_syslog, zt); + newzt->zt.prio_min = prio_min; + newzt->zt.logfn = zlog_syslog; + newzt->zt.logfn_sigsafe = zlog_syslog_sigsafe; + newzt->syslog_facility = syslog_facility; + } + + zlog_target_free(MTYPE_LOG_SYSL, + zlog_target_replace(&zlt_syslog->zt, + &newzt->zt)); + + zlt_syslog = newzt; + } +} + +int zlog_syslog_get_prio_min(void) +{ + frr_with_mutex (&syslog_cfg_mutex) { + return syslog_prio_min; + } + assert(0); + return 0; +} diff --git a/lib/zlog_targets.h b/lib/zlog_targets.h new file mode 100644 index 0000000..d70834e --- /dev/null +++ b/lib/zlog_targets.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2015-19 David Lamparter, for NetDEF, Inc. + */ + +#ifndef _FRR_ZLOG_TARGETS_H +#define _FRR_ZLOG_TARGETS_H + +#include + +#include "zlog.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* multiple file log targets can be active */ + +struct zlt_fd; + +struct zlog_cfg_file { + struct zlt_fd *active; + + pthread_mutex_t cfg_mtx; + + /* call zlog_file_set_other() to apply these */ + int prio_min; + char ts_subsec; + bool record_priority; + + /* call zlog_file_set_filename/fd() to change this */ + char *filename; + int fd; + + void (*zlog_wrap)(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs); +}; + +extern void zlog_file_init(struct zlog_cfg_file *zcf); +extern void zlog_file_fini(struct zlog_cfg_file *zcf); + +extern void zlog_file_set_other(struct zlog_cfg_file *zcf); +extern bool zlog_file_set_filename(struct zlog_cfg_file *zcf, const char *name); +extern bool zlog_file_set_fd(struct zlog_cfg_file *zcf, int fd); +extern bool zlog_file_rotate(struct zlog_cfg_file *zcf); + +extern void zlog_fd(struct zlog_target *zt, struct zlog_msg *msgs[], + size_t nmsgs); + +/* syslog is always limited to one target */ + +extern void zlog_syslog_set_facility(int facility); +extern int zlog_syslog_get_facility(void); + +/* use ZLOG_DISABLED to disable */ +extern void zlog_syslog_set_prio_min(int prio_min); +extern int zlog_syslog_get_prio_min(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRR_ZLOG_TARGETS_H */ diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..37888dd --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,15 @@ +*.m4 +!*.patch + +!ax_compare_version.m4 +!ax_cxx_compile_stdcxx.m4 +!ax_lua.m4 +!ax_prog_perl_modules.m4 +!ax_pthread.m4 +!ax_python.m4 +!ax_recursive_eval.m4 +!ax_sys_weak_alias.m4 +!ax_sys_weak_alias.m4 +!pkg.m4 + +/ac diff --git a/m4/README.txt b/m4/README.txt new file mode 100644 index 0000000..ce06853 --- /dev/null +++ b/m4/README.txt @@ -0,0 +1,18 @@ +This directory contains local additions to/overrides for the Quagga +autoconf build system. + +At this time additions are: + +- m4 files taken from libtool CVS circa august 2004. These have been +imported into Quagga as they are more robust with respect to configuring +libtool support for languages which Quagga does not use. As and when libtool +releases become commonly available with that capability, these can be +removed. The files are: + + argz.m4 + libtool.m4 + ltdl.m4 + ltoptions.m4 + ltsugar.m4 + ltversion.m4 + diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4 new file mode 100644 index 0000000..74dc0fd --- /dev/null +++ b/m4/ax_compare_version.m4 @@ -0,0 +1,177 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_compare_version.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# This macro compares two version strings. Due to the various number of +# minor-version numbers that can exist, and the fact that string +# comparisons are not compatible with numeric comparisons, this is not +# necessarily trivial to do in a autoconf script. This macro makes doing +# these comparisons easy. +# +# The six basic comparisons are available, as well as checking equality +# limited to a certain number of minor-version levels. +# +# The operator OP determines what type of comparison to do, and can be one +# of: +# +# eq - equal (test A == B) +# ne - not equal (test A != B) +# le - less than or equal (test A <= B) +# ge - greater than or equal (test A >= B) +# lt - less than (test A < B) +# gt - greater than (test A > B) +# +# Additionally, the eq and ne operator can have a number after it to limit +# the test to that number of minor versions. +# +# eq0 - equal up to the length of the shorter version +# ne0 - not equal up to the length of the shorter version +# eqN - equal up to N sub-version levels +# neN - not equal up to N sub-version levels +# +# When the condition is true, shell commands ACTION-IF-TRUE are run, +# otherwise shell commands ACTION-IF-FALSE are run. The environment +# variable 'ax_compare_version' is always set to either 'true' or 'false' +# as well. +# +# Examples: +# +# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +# +# would both be true. +# +# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +# +# would both be false. +# +# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +# +# would be true because it is only comparing two minor versions. +# +# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +# +# would be true because it is only comparing the lesser number of minor +# versions of the two values. +# +# Note: The characters that separate the version numbers do not matter. An +# empty string is the same as version 0. OP is evaluated by autoconf, not +# configure, so must be a string, not a variable. +# +# The author would like to acknowledge Guido Draheim whose advice about +# the m4_case and m4_ifvaln functions make this macro only include the +# portions necessary to perform the specific comparison specified by the +# OP argument in the final configure script. +# +# LICENSE +# +# Copyright (c) 2008 Tim Toolan +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + AC_REQUIRE([AC_PROG_AWK]) + + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 0000000..9413da6 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,962 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) +# or '14' (for the C++14 standard). +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201103L + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L + +]]) diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4 new file mode 100644 index 0000000..f4236cf --- /dev/null +++ b/m4/ax_lua.m4 @@ -0,0 +1,641 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_lua.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# +# DESCRIPTION +# +# Detect a Lua interpreter, optionally specifying a minimum and maximum +# version number. Set up important Lua paths, such as the directories in +# which to install scripts and modules (shared libraries). +# +# Also detect Lua headers and libraries. The Lua version contained in the +# header is checked to match the Lua interpreter version exactly. When +# searching for Lua libraries, the version number is used as a suffix. +# This is done with the goal of supporting multiple Lua installs (5.1, +# 5.2, and 5.3 side-by-side). +# +# A note on compatibility with previous versions: This file has been +# mostly rewritten for serial 18. Most developers should be able to use +# these macros without needing to modify configure.ac. Care has been taken +# to preserve each macro's behavior, but there are some differences: +# +# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as +# AX_PROG_LUA with no arguments. +# +# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h +# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore +# unnecessary, so it is deprecated and does not expand to anything. +# +# 3) The configure flag --with-lua-suffix no longer exists; the user +# should instead specify the LUA precious variable on the command line. +# See the AX_PROG_LUA description for details. +# +# Please read the macro descriptions below for more information. +# +# This file was inspired by Andrew Dalke's and James Henstridge's +# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4 +# (serial 17). Basically, this file is a mash-up of those two files. I +# like to think it combines the best of the two! +# +# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua +# paths. Adds precious variable LUA, which may contain the path of the Lua +# interpreter. If LUA is blank, the user's path is searched for an +# suitable interpreter. +# +# If MINIMUM-VERSION is supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION will be accepted. If +# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION and less than +# TOO-BIG-VERSION will be accepted. +# +# The Lua version number, LUA_VERSION, is found from the interpreter, and +# substituted. LUA_PLATFORM is also found, but not currently supported (no +# standard representation). +# +# Finally, the macro finds four paths: +# +# luadir Directory to install Lua scripts. +# pkgluadir $luadir/$PACKAGE +# luaexecdir Directory to install Lua modules. +# pkgluaexecdir $luaexecdir/$PACKAGE +# +# These paths are found based on $prefix, $exec_prefix, Lua's +# package.path, and package.cpath. The first path of package.path +# beginning with $prefix is selected as luadir. The first path of +# package.cpath beginning with $exec_prefix is used as luaexecdir. This +# should work on all reasonable Lua installations. If a path cannot be +# determined, a default path is used. Of course, the user can override +# these later when invoking make. +# +# luadir Default: $prefix/share/lua/$LUA_VERSION +# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION +# +# These directories can be used by Automake as install destinations. The +# variable name minus 'dir' needs to be used as a prefix to the +# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES. +# +# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT- +# FOUND is blank, then it will default to printing an error. To prevent +# the default behavior, give ':' as an action. +# +# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_INCLUDE, which +# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If +# LUA_INCLUDE is blank, then this macro will attempt to find suitable +# flags. +# +# LUA_INCLUDE can be used by Automake to compile Lua modules or +# executables with embedded interpreters. The *_CPPFLAGS variables should +# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE). +# +# This macro searches for the header lua.h (and others). The search is +# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE. +# If the search is unsuccessful, then some common directories are tried. +# If the headers are then found, then LUA_INCLUDE is set accordingly. +# +# The paths automatically searched are: +# +# * /usr/include/luaX.Y +# * /usr/include/lua/X.Y +# * /usr/include/luaXY +# * /usr/local/include/luaX.Y +# * /usr/local/include/lua-X.Y +# * /usr/local/include/lua/X.Y +# * /usr/local/include/luaXY +# +# (Where X.Y is the Lua version number, e.g. 5.1.) +# +# The Lua version number found in the headers is always checked to match +# the Lua interpreter's version number. Lua headers with mismatched +# version numbers are not accepted. +# +# If headers are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_LIB, which may +# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank, +# then this macro will attempt to find suitable flags. +# +# LUA_LIB can be used by Automake to link Lua modules or executables with +# embedded interpreters. The *_LIBADD and *_LDADD variables should be used +# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB). +# +# This macro searches for the Lua library. More technically, it searches +# for a library containing the function lua_load. The search is performed +# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB. +# +# If the search determines that some linker flags are missing, then those +# flags will be added to LUA_LIB. +# +# If libraries are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_READLINE: Search for readline headers and libraries. Requires the +# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the +# Autoconf Archive. +# +# If a readline compatible library is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is performed. +# +# LICENSE +# +# Copyright (c) 2015 Reuben Thomas +# Copyright (c) 2014 Tim Perkins +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 39 + +dnl ========================================================================= +dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION], +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_PROG_LUA], +[ + dnl Check for required tools. + AC_REQUIRE([AC_PROG_GREP]) + AC_REQUIRE([AC_PROG_SED]) + + dnl Make LUA a precious variable. + AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1]) + + dnl Find a Lua interpreter. + m4_define_default([_AX_LUA_INTERPRETER_LIST], + [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50]) + + m4_if([$1], [], + [ dnl No version check is needed. Find any Lua interpreter. + AS_IF([test "x$LUA" = 'x'], + [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])]) + ax_display_LUA='lua' + + AS_IF([test "x$LUA" != 'x:'], + [ dnl At least check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + ]) + ], + [ dnl A version check is needed. + AS_IF([test "x$LUA" != 'x'], + [ dnl Check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + dnl Check the version. + m4_if([$2], [], + [_ax_check_text="whether $LUA version >= $1"], + [_ax_check_text="whether $LUA version >= $1, < $2"]) + AC_MSG_CHECKING([$_ax_check_text]) + _AX_LUA_CHK_VER([$LUA], [$1], [$2], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([version is out of range for specified LUA])]) + ax_display_LUA=$LUA + ], + [ dnl Try each interpreter until we find one that satisfies VERSION. + m4_if([$2], [], + [_ax_check_text="for a Lua interpreter with version >= $1"], + [_ax_check_text="for a Lua interpreter with version >= $1, < $2"]) + AC_CACHE_CHECK([$_ax_check_text], + [ax_cv_pathless_LUA], + [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do + test "x$ax_cv_pathless_LUA" = 'xnone' && break + _AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue]) + _AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break]) + done + ]) + dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA. + AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'], + [LUA=':'], + [AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])]) + ax_display_LUA=$ax_cv_pathless_LUA + ]) + ]) + + AS_IF([test "x$LUA" = 'x:'], + [ dnl Run any user-specified action, or abort. + m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])]) + ], + [ dnl Query Lua for its version number. + AC_CACHE_CHECK([for $ax_display_LUA version], + [ax_cv_lua_version], + [ dnl Get the interpreter version in X.Y format. This should work for + dnl interpreters version 5.0 and beyond. + ax_cv_lua_version=[`$LUA -e ' + -- return a version number in X.Y format + local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") + print(ver)'`] + ]) + AS_IF([test "x$ax_cv_lua_version" = 'x'], + [AC_MSG_ERROR([invalid Lua version number])]) + AC_SUBST([LUA_VERSION], [$ax_cv_lua_version]) + AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | $SED 's|\.||'`]) + + dnl The following check is not supported: + dnl At times (like when building shared libraries) you may want to know + dnl which OS platform Lua thinks this is. + AC_CACHE_CHECK([for $ax_display_LUA platform], + [ax_cv_lua_platform], + [ax_cv_lua_platform=[`$LUA -e 'print("unknown")'`]]) + AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct + dnl variables so they can be overridden if need be. However, the general + dnl consensus is that you shouldn't need this ability. + AC_SUBST([LUA_PREFIX], ['${prefix}']) + AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}']) + + dnl Lua provides no way to query the script directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $prefix, then we can store scripts there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA script directory], + [ax_cv_lua_luadir], + [ AS_IF([test "x$prefix" = 'xNONE'], + [ax_lua_prefix=$ac_default_prefix], + [ax_lua_prefix=$prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [script]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` + ]) + ]) + AC_SUBST([luadir], [$ax_cv_lua_luadir]) + AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE]) + + dnl Lua provides no way to query the module directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $exec_prefix, then we can store modules there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA module directory], + [ax_cv_lua_luaexecdir], + [ AS_IF([test "x$exec_prefix" = 'xNONE'], + [ax_lua_exec_prefix=$ax_lua_prefix], + [ax_lua_exec_prefix=$exec_prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], + [$ax_lua_exec_prefix], [module]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` + ]) + ]) + AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir]) + AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE]) + + dnl Run any user specified action. + $3 + ]) +]) + +dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA. +AC_DEFUN([AX_WITH_LUA], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA instead]]) + AX_PROG_LUA +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_IS_INTRP], +[ + dnl A minimal Lua factorial to prove this is an interpreter. This should work + dnl for Lua interpreters version 5.0 and beyond. + _ax_lua_factorial=[`$1 2>/dev/null -e ' + -- a simple factorial + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + print("fact(5) is " .. fact(5))'`] + AS_IF([test "$_ax_lua_factorial" = 'fact(5) is 120'], + [$2], [$3]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION], +dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_VER], +[ + dnl Check that the Lua version is within the bounds. Only the major and minor + dnl version numbers are considered. This should work for Lua interpreters + dnl version 5.0 and beyond. + _ax_lua_good_version=[`$1 -e ' + -- a script to compare versions + function verstr2num(verstr) + local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") + if majorver and minorver then + return tonumber(majorver) * 100 + tonumber(minorver) + end + end + local minver = verstr2num("$2") + local _, _, trimver = string.find(_VERSION, "^Lua (.*)") + local ver = verstr2num(trimver) + local maxver = verstr2num("$3") or 1e9 + if minver <= ver and ver < maxver then + print("yes") + else + print("no") + end'`] + AS_IF([test "x$_ax_lua_good_version" = "xyes"], + [$4], [$5]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, SCRIPT-OR-MODULE-DIR) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_FND_PRFX_PTH], +[ + dnl Get the script or module directory by querying the Lua interpreter, + dnl filtering on the given prefix, and selecting the shallowest path. If no + dnl path is found matching the prefix, the result will be an empty string. + dnl The third argument determines the type of search, it can be 'script' or + dnl 'module'. Supplying 'script' will perform the search with package.path + dnl and LUA_PATH, and supplying 'module' will search with package.cpath and + dnl LUA_CPATH. This is done for compatibility with Lua 5.0. + + ax_lua_prefixed_path=[`$1 -e ' + -- get the path based on search type + local searchtype = "$3" + local paths = "" + if searchtype == "script" then + paths = (package and package.path) or LUA_PATH + elseif searchtype == "module" then + paths = (package and package.cpath) or LUA_CPATH + end + -- search for the prefix + local prefix = "'$2'" + local minpath = "" + local mindepth = 1e9 + string.gsub(paths, "(@<:@^;@:>@+)", + function (path) + path = string.gsub(path, "%?.*$", "") + path = string.gsub(path, "/@<:@^/@:>@*$", "") + if string.find(path, prefix) then + local depth = string.len(string.gsub(path, "@<:@^/@:>@", "")) + if depth < mindepth then + minpath = path + mindepth = depth + end + end + end) + print(minpath)'`] +]) + + +dnl ========================================================================= +dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_HEADERS], +[ + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION]) + ]) + + dnl Make LUA_INCLUDE a precious variable. + AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1]) + + dnl Some default directories to search. + LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` + m4_define_default([_AX_LUA_INCLUDE_LIST], + [ /usr/include/lua$LUA_VERSION \ + /usr/include/lua-$LUA_VERSION \ + /usr/include/lua/$LUA_VERSION \ + /usr/include/lua$LUA_SHORT_VERSION \ + /usr/local/include/lua$LUA_VERSION \ + /usr/local/include/lua-$LUA_VERSION \ + /usr/local/include/lua/$LUA_VERSION \ + /usr/local/include/lua$LUA_SHORT_VERSION \ + ]) + + dnl Try to find the headers. + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + dnl Try some other directories if LUA_INCLUDE was not set. + AS_IF([test "x$LUA_INCLUDE" = 'x' && + test "x$ac_cv_header_lua_h" != 'xyes'], + [ dnl Try some common include paths. + for _ax_include_path in _AX_LUA_INCLUDE_LIST; do + test ! -d "$_ax_include_path" && continue + + AC_MSG_CHECKING([for Lua headers in]) + AC_MSG_RESULT([$_ax_include_path]) + + AS_UNSET([ac_cv_header_lua_h]) + AS_UNSET([ac_cv_header_lualib_h]) + AS_UNSET([ac_cv_header_lauxlib_h]) + AS_UNSET([ac_cv_header_luaconf_h]) + + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I$_ax_include_path" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ LUA_INCLUDE="-I$_ax_include_path" + break + ]) + done + ]) + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ AC_CACHE_CHECK([for Lua header version], + [ax_cv_lua_header_version], + [ + ax_cv_lua_header_version=`echo LUA_VERSION | \ + $CC -P -E $LUA_INCLUDE -imacros lua.h - | \ + $SED -e 's%"@<:@@<:@:space:@:>@@:>@*"%%g' -e 's%^@<:@@<:@:space:@:>@@:>@*%%' | \ + tr -d '"\n' | \ + $SED -n "s|^Lua \(@<:@0-9@:>@\{1,\}\.@<:@0-9@:>@\{1,\}\).\{0,\}|\1|p"` + ]) + + dnl Compare this to the previously found LUA_VERSION. + AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) + AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], + [ AC_MSG_RESULT([yes]) + ax_header_version_match='yes' + ], + [ AC_MSG_RESULT([no]) + ax_header_version_match='no' + ]) + ]) + + dnl Was LUA_INCLUDE specified? + AS_IF([test "x$ax_header_version_match" != 'xyes' && + test "x$LUA_INCLUDE" != 'x'], + [AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])]) + + dnl Test the final result and run user code. + AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])]) +]) + +dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS. +AC_DEFUN([AX_LUA_HEADERS_VERSION], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS instead]]) +]) + + +dnl ========================================================================= +dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_LIBS], +[ + dnl TODO Should this macro also check various -L flags? + + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION]) + ]) + + dnl Make LUA_LIB a precious variable. + AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1]) + + AS_IF([test "x$LUA_LIB" != 'x'], + [ dnl Check that LUA_LIBS works. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], [], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no']) + LIBS=$_ax_lua_saved_libs + + dnl Check the result. + AS_IF([test "x$_ax_found_lua_libs" != 'xyes'], + [AC_MSG_ERROR([cannot find libs for specified LUA_LIB])]) + ], + [ dnl First search for extra libs. + _ax_lua_extra_libs='' + + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([exp], [m]) + AC_SEARCH_LIBS([dlopen], [dl]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_exp" != 'xno' && + test "x$ac_cv_search_exp" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"]) + + AS_IF([test "x$ac_cv_search_dlopen" != 'xno' && + test "x$ac_cv_search_dlopen" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"]) + + dnl Try to find the Lua libs. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], + [ lua$LUA_VERSION \ + lua$LUA_SHORT_VERSION \ + lua-$LUA_VERSION \ + lua-$LUA_SHORT_VERSION \ + lua \ + ], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no'], + [$_ax_lua_extra_libs]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_lua_load" != 'xno' && + test "x$ac_cv_search_lua_load" != 'xnone required'], + [LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"]) + ]) + + dnl Test the result and run user code. + AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])]) +]) + + +dnl ========================================================================= +dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_READLINE], +[ + AX_LIB_READLINE + AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' && + test "x$ac_cv_header_readline_history_h" != 'x'], + [ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS" + $1 + ], + [$2]) +]) diff --git a/m4/ax_prog_perl_modules.m4 b/m4/ax_prog_perl_modules.m4 new file mode 100644 index 0000000..11a326c --- /dev/null +++ b/m4/ax_prog_perl_modules.m4 @@ -0,0 +1,77 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_prog_perl_modules.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_PERL_MODULES([MODULES], [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# Checks to see if the given perl modules are available. If true the shell +# commands in ACTION-IF-TRUE are executed. If not the shell commands in +# ACTION-IF-FALSE are run. Note if $PERL is not set (for example by +# calling AC_CHECK_PROG, or AC_PATH_PROG), AC_CHECK_PROG(PERL, perl, perl) +# will be run. +# +# MODULES is a space separated list of module names. To check for a +# minimum version of a module, append the version number to the module +# name, separated by an equals sign. +# +# Example: +# +# AX_PROG_PERL_MODULES( Text::Wrap Net::LDAP=1.0.3, , +# AC_MSG_WARN(Need some Perl modules) +# +# LICENSE +# +# Copyright (c) 2009 Dean Povey +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +AU_ALIAS([AC_PROG_PERL_MODULES], [AX_PROG_PERL_MODULES]) +AC_DEFUN([AX_PROG_PERL_MODULES],[dnl + +m4_define([ax_perl_modules]) +m4_foreach([ax_perl_module], m4_split(m4_normalize([$1])), + [ + m4_append([ax_perl_modules], + [']m4_bpatsubst(ax_perl_module,=,[ ])[' ]) + ]) + +# Make sure we have perl +if test -z "$PERL"; then +AC_CHECK_PROG(PERL,perl,perl) +fi + +if test "x$PERL" != x; then + ax_perl_modules_failed=0 + for ax_perl_module in ax_perl_modules; do + AC_MSG_CHECKING(for perl module $ax_perl_module) + + # Would be nice to log result here, but can't rely on autoconf internals + $PERL -e "use $ax_perl_module; exit" > /dev/null 2>&1 + if test $? -ne 0; then + AC_MSG_RESULT(no); + ax_perl_modules_failed=1 + else + AC_MSG_RESULT(ok); + fi + done + + # Run optional shell commands + if test "$ax_perl_modules_failed" = 0; then + : + $2 + else + : + $3 + fi +else + AC_MSG_WARN(could not find perl) +fi])dnl diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000..b7872d9 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,332 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. e.g. you should link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threads programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name +# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2011 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 21 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test x"$ax_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case ${host_os} in + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; + + darwin*) + ax_pthread_flags="-pthread $ax_pthread_flags" + ;; +esac + +# Clang doesn't consider unrecognized options an error unless we specify +# -Werror. We throw in some extra Clang-specific options to ensure that +# this doesn't happen for GCC, which also accepts -Werror. + +AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) +save_CFLAGS="$CFLAGS" +ax_pthread_extra_flags="-Werror" +CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], + [AC_MSG_RESULT([yes])], + [ax_pthread_extra_flags= + AC_MSG_RESULT([no])]) +CFLAGS="$save_CFLAGS" + +if test x"$ax_pthread_ok" = xno; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + if test x"$ax_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include + static void routine(void *a) { if (a) a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], + [int attr = $attr; return attr /* ; */])], + [attr_name=$attr; break], + []) + done + AC_MSG_RESULT([$attr_name]) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case ${host_os} in + aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; + osf* | hpux*) flag="-D_REENTRANT";; + solaris*) + if test "$GCC" = "yes"; then + flag="-D_REENTRANT" + else + # TODO: What about Clang on Solaris? + flag="-mt -D_REENTRANT" + fi + ;; + esac + AC_MSG_RESULT([$flag]) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != xyes; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$ax_pthread_ok" = xyes; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/ax_python.m4 b/m4/ax_python.m4 new file mode 100644 index 0000000..f5e603b --- /dev/null +++ b/m4/ax_python.m4 @@ -0,0 +1,289 @@ +dnl FRR Python autoconf magic +dnl 2019 David Lamparter for NetDEF, Inc. +dnl SPDX-License-Identifier: GPL-2.0-or-later + +dnl the _ at the beginning will be cut off (to support the empty version string) +m4_define_default([_FRR_PY_VERS], [_3 _3.10 _3.9 _3.8 _3.7 _3.6 _3.5 _3.4 _3.3 _3.2 _ _2 _2.7]) + +dnl check basic interpreter properties (py2/py3) +dnl doubles as simple check whether the interpreter actually works +dnl also swaps in the full path to the interpreter +dnl arg1: if-true, arg2: if-false +AC_DEFUN([_FRR_PYTHON_INTERP], [dnl +AC_ARG_VAR([PYTHON], [Python interpreter to use])dnl + AC_MSG_CHECKING([python interpreter $PYTHON]) + AC_RUN_LOG(["$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not (sys.version_info.major == 2 and sys.version_info.minor >= 7))']) + py2=$ac_status + _py2_full="`cat conftest.pyver 2>/dev/null`" + rm -f "conftest.pyver" >/dev/null 2>/dev/null + + AC_RUN_LOG(["$PYTHON" -c 'import sys; open("conftest.pyver", "w").write(sys.executable or ""); sys.exit(not ((sys.version_info.major == 3 and sys.version_info.minor >= 2) or sys.version_info.major > 3))']) + py3=$ac_status + _py3_full="`cat conftest.pyver 2>/dev/null`" + rm -f "conftest.pyver" >/dev/null 2>/dev/null + + case "p${py2}p${py3}" in + p0p1) frr_cv_python=python2 + _python_full="$_py2_full" ;; + p1p0) frr_cv_python=python3 + _python_full="$_py3_full" ;; + *) frr_cv_python=none ;; + esac + + if test "$frr_cv_python" = none; then + AC_MSG_RESULT([not working]) + $2 + else + test -n "$_python_full" -a -x "$_python_full" && PYTHON="$_python_full" + AC_MSG_RESULT([$PYTHON ($frr_cv_python)]) + $1 + fi + + dnl return value + test "$frr_cv_python" != none +]) + +dnl check whether $PYTHON has modules available +dnl arg1: list of modules (space separated) +dnl arg2: if all true, arg3: if any missing +dnl also sets frr_py_mod_ to "true" or "false" +AC_DEFUN([FRR_PYTHON_MODULES], [ + result=true + for pymod in $1; do + AC_MSG_CHECKING([whether $PYTHON module $pymod is available]) + AC_RUN_LOG(["$PYTHON" -c "import $pymod"]) + sane="`echo \"$pymod\" | tr -c '[a-zA-Z0-9\n]' '_'`" + if test "$ac_status" -eq 0; then + AC_MSG_RESULT([yes]) + eval frr_py_mod_$sane=true + else + AC_MSG_RESULT([no]) + eval frr_py_mod_$sane=false + result=false + fi + done + if $result; then + m4_default([$2], [:]) + else + m4_default([$3], [:]) + fi + $result +]) + +dnl check whether $PYTHON has modules available +dnl arg1: list of modules (space separated) +dnl arg2: command line parameters for executing +dnl arg3: if all true, arg4: if any missing +dnl also sets frr_py_modexec_ to "true" or "false" +AC_DEFUN([FRR_PYTHON_MOD_EXEC], [ + result=true + for pymod in $1; do + AC_MSG_CHECKING([whether $PYTHON module $pymod is executable]) + AC_RUN_LOG(["$PYTHON" -m "$pymod" $2 > /dev/null]) + sane="`echo \"$pymod\" | tr -c '[a-zA-Z0-9\n]' '_'`" + if test "$ac_status" -eq 0; then + AC_MSG_RESULT([yes]) + eval frr_py_modexec_$sane=true + else + AC_MSG_RESULT([no]) + eval frr_py_modexec_$sane=false + result=false + fi + done + if $result; then + m4_default([$3], [:]) + else + m4_default([$4], [:]) + fi + $result +]) + +dnl check whether we can build & link python bits +dnl input: PYTHON_CFLAGS and PYTHON_LIBS +AC_DEFUN([_FRR_PYTHON_DEVENV], [ + result=true + AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ +#include +#if PY_VERSION_HEX < 0x02070000 +#error python too old +#endif +int main(void); +], +[ +{ + Py_Initialize(); + return 0; +} +])], [ + # some python installs are missing the zlib dependency... + PYTHON_LIBS="${PYTHON_LIBS} -lz" + AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ +#include +#if PY_VERSION_HEX < 0x02070000 +#error python too old +#endif +int main(void); +], +[ +{ + Py_Initialize(); + return 0; +} +])], [ + result=false + AC_MSG_RESULT([no]) + ], [:]) + ], [:]) + + if $result; then + AC_LINK_IFELSE_FLAGS([$PYTHON_CFLAGS], [$PYTHON_LIBS], [AC_LANG_PROGRAM([ +#include +#if PY_VERSION_HEX != $1 +#error python version mismatch +#endif +int main(void); +], +[ +{ + Py_Initialize(); + return 0; +} +])], [ + result=false + AC_MSG_RESULT([version mismatch]) + ], [ + AC_MSG_RESULT([yes]) + ]) + fi + + if $result; then + m4_default([$2], [:]) + else + m4_default([$3], [ + unset PYTHON_LIBS + unset PYTHON_CFLAGS + ]) + fi +]) + +AC_DEFUN([_FRR_PYTHON_GETDEV], [dnl +AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl + + py_abi="` \"$1\" -c \"import sys; print(getattr(sys, 'abiflags', ''))\"`" + py_hex="` \"$1\" -c \"import sys; print(hex(sys.hexversion))\"`" + py_ldver="` \"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('LDVERSION') or '')\"`" + py_ver="` \"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('VERSION') or '')\"`" + py_bindir="`\"$1\" -c \"import sysconfig; print(sysconfig.get_config_var('BINDIR') or '')\"`" + test -z "$py_bindir" || py_bindir="$py_bindir/" + echo "py_abi=${py_abi} py_ldver=${py_ldver} py_ver=${py_ver} py_bindir=${py_bindir}" >&AS_MESSAGE_LOG_FD + + py_found=false + + for tryver in "${py_ldver}" "${py_ver}"; do + pycfg="${py_bindir}python${tryver}-config" + AC_MSG_CHECKING([whether ${pycfg} is available]) + if "$pycfg" --configdir >/dev/null 2>/dev/null; then + AC_MSG_RESULT([yes]) + + PYTHON_CFLAGS="`\"$pycfg\" --includes`" + minor_ver=${py_ver#*\.} + if test $((minor_ver)) -gt 7; then + PYTHON_LIBS="`\"$pycfg\" --ldflags --embed`" + else + PYTHON_LIBS="`\"$pycfg\" --ldflags`" + fi + + AC_MSG_CHECKING([whether ${pycfg} provides a working build environment]) + _FRR_PYTHON_DEVENV([$py_hex], [ + py_found=true + break + ]) + else + AC_MSG_RESULT([no]) + fi + + pkg_failed=no + AC_MSG_CHECKING([whether pkg-config python-${tryver} is available]) + unset PYTHON_CFLAGS + unset PYTHON_LIBS + pkg="python-${tryver}-embed" + pkg="${pkg%-}" + _PKG_CONFIG([PYTHON_CFLAGS], [cflags], [${pkg}]) + _PKG_CONFIG([PYTHON_LIBS], [libs], [${pkg}]) + if test $pkg_failed = no; then + AC_MSG_RESULT([yes]) + + PYTHON_CFLAGS=$pkg_cv_PYTHON_CFLAGS + PYTHON_LIBS=$pkg_cv_PYTHON_LIBS + + AC_MSG_CHECKING([whether pkg-config python-${tryver} provides a working build environment]) + _FRR_PYTHON_DEVENV([$py_hex], [ + py_found=true + break + ]) + else + AC_MSG_RESULT([no]) + fi + done + + if $py_found; then + m4_default([$2], [:]) + else + unset PYTHON_CFLAGS + unset PYTHON_LIBS + m4_default([$3], [:]) + fi +]) + +dnl just find python without checking headers/libs +AC_DEFUN([FRR_PYTHON], [ + dnl user override + if test "x$PYTHON" != "x"; then + _FRR_PYTHON_INTERP([], [ + AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but not working]) + ]) + else + for frr_pyver in _FRR_PY_VERS; do + PYTHON="python${frr_pyver#_}" + _FRR_PYTHON_INTERP([break]) + PYTHON=":" + done + if test "$PYTHON" = ":"; then + AC_MSG_ERROR([no working python version found]) + fi + fi + AC_SUBST([PYTHON]) +]) + +dnl find python with checking headers/libs +AC_DEFUN([FRR_PYTHON_DEV], [dnl +AC_ARG_VAR([PYTHON_CFLAGS], [C compiler flags for Python])dnl +AC_ARG_VAR([PYTHON_LIBS], [linker flags for Python])dnl + + dnl user override + if test "x$PYTHON" != "x"; then + _FRR_PYTHON_INTERP([], [ + AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but not working]) + ]) + _FRR_PYTHON_GETDEV([$PYTHON], [], [ + AC_MSG_ERROR([PYTHON ($PYTHON) explicitly specified but development environment not working]) + ]) + else + for frr_pyver in _FRR_PY_VERS; do + PYTHON="python${frr_pyver#_}" + _FRR_PYTHON_INTERP([ + _FRR_PYTHON_GETDEV([$PYTHON], [ + break + ]) + ]) + PYTHON=":" + done + if test "$PYTHON" = ":"; then + AC_MSG_ERROR([no working python version found]) + fi + fi + + AC_SUBST([PYTHON_CFLAGS]) + AC_SUBST([PYTHON_LIBS]) + AC_SUBST([PYTHON]) +]) diff --git a/m4/ax_recursive_eval.m4 b/m4/ax_recursive_eval.m4 new file mode 100644 index 0000000..0283602 --- /dev/null +++ b/m4/ax_recursive_eval.m4 @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-or-later WITH Autoconf-exception-2.0 +# +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_recursive_eval.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_RECURSIVE_EVAL(VALUE, RESULT) +# +# DESCRIPTION +# +# Interpolate the VALUE in loop until it doesn't change, and set the +# result to $RESULT. This version has a recursion limit (10). +# +# LICENSE +# +# Copyright (c) 2008 Alexandre Duret-Lutz +# Copyright (c) 2024 David Lamparter + +AC_DEFUN([AX_RECURSIVE_EVAL], +[_lcl_receval="$1" +$2=`(test "x$prefix" = xNONE && prefix="$ac_default_prefix" + test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" + _lcl_receval_old='' + for _rec_limit in 1 2 3 4 5 6 7 8 9 10; do + test "[$]_lcl_receval_old" = "[$]_lcl_receval" && break + _lcl_receval_old="[$]_lcl_receval" + eval _lcl_receval="\"[$]_lcl_receval\"" + done + echo "[$]_lcl_receval")`]) diff --git a/m4/ax_sys_weak_alias.m4 b/m4/ax_sys_weak_alias.m4 new file mode 100644 index 0000000..37cbe91 --- /dev/null +++ b/m4/ax_sys_weak_alias.m4 @@ -0,0 +1,333 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_sys_weak_alias.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_SYS_WEAK_ALIAS +# +# DESCRIPTION +# +# Determines whether weak aliases are supported on the system, and if so, +# what scheme is used to declare them. Also checks to see if aliases can +# cross object file boundaries, as some systems don't permit them to. +# +# Most systems permit something called a "weak alias" or "weak symbol." +# These aliases permit a library to provide a stub form of a routine +# defined in another library, thus allowing the first library to operate +# even if the other library is not linked. This macro will check for +# support of weak aliases, figure out what schemes are available, and +# determine some characteristics of the weak alias support -- primarily, +# whether a weak alias declared in one object file may be referenced from +# another object file. +# +# There are four known schemes of declaring weak symbols; each scheme is +# checked in turn, and the first one found is preferred. Note that only one +# of the mentioned preprocessor macros will be defined! +# +# 1. Function attributes +# +# This scheme was first introduced by the GNU C compiler, and attaches +# attributes to particular functions. It is among the easiest to use, and +# so is the first one checked. If this scheme is detected, the +# preprocessor macro HAVE_SYS_WEAK_ALIAS_ATTRIBUTE will be defined to 1. +# This scheme is used as in the following code fragment: +# +# void __weakf(int c) +# { +# /* Function definition... */ +# } +# +# void weakf(int c) __attribute__((weak, alias("__weakf"))); +# +# 2. #pragma weak +# +# This scheme is in use by many compilers other than the GNU C compiler. +# It is also particularly easy to use, and fairly portable -- well, as +# portable as these things get. If this scheme is detected first, the +# preprocessor macro HAVE_SYS_WEAK_ALIAS_PRAGMA will be defined to 1. This +# scheme is used as in the following code fragment: +# +# extern void weakf(int c); +# #pragma weak weakf = __weakf +# void __weakf(int c) +# { +# /* Function definition... */ +# } +# +# 3. #pragma _HP_SECONDARY_DEF +# +# This scheme appears to be in use by the HP compiler. As it is rather +# specialized, this is one of the last schemes checked. If it is the first +# one detected, the preprocessor macro HAVE_SYS_WEAK_ALIAS_HPSECONDARY +# will be defined to 1. This scheme is used as in the following code +# fragment: +# +# extern void weakf(int c); +# #pragma _HP_SECONDARY_DEF __weakf weakf +# void __weakf(int c) +# { +# /* Function definition... */ +# } +# +# 4. #pragma _CRI duplicate +# +# This scheme appears to be in use by the Cray compiler. As it is rather +# specialized, it too is one of the last schemes checked. If it is the +# first one detected, the preprocessor macro +# HAVE_SYS_WEAK_ALIAS_CRIDUPLICATE will be defined to 1. This scheme is +# used as in the following code fragment: +# +# extern void weakf(int c); +# #pragma _CRI duplicate weakf as __weakf +# void __weakf(int c) +# { +# /* Function definition... */ +# } +# +# In addition to the preprocessor macros listed above, if any scheme is +# found, the preprocessor macro HAVE_SYS_WEAK_ALIAS will also be defined +# to 1. +# +# Once a weak aliasing scheme has been found, a check will be performed to +# see if weak aliases are honored across object file boundaries. If they +# are, the HAVE_SYS_WEAK_ALIAS_CROSSFILE preprocessor macro is defined to +# 1. +# +# This Autoconf macro also makes two substitutions. The first, WEAK_ALIAS, +# contains the name of the scheme found (one of "attribute", "pragma", +# "hpsecondary", or "criduplicate"), or "no" if no weak aliasing scheme +# was found. The second, WEAK_ALIAS_CROSSFILE, is set to "yes" or "no" +# depending on whether or not weak aliases may cross object file +# boundaries. +# +# LICENSE +# +# Copyright (c) 2008 Kevin L. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([KLM_SYS_WEAK_ALIAS], [AX_SYS_WEAK_ALIAS]) +AC_DEFUN([AX_SYS_WEAK_ALIAS], [ + # starting point: no aliasing scheme yet... + ax_sys_weak_alias=no + + # Figure out what kind of aliasing may be supported... + _AX_SYS_WEAK_ALIAS_ATTRIBUTE + _AX_SYS_WEAK_ALIAS_PRAGMA + _AX_SYS_WEAK_ALIAS_HPSECONDARY + _AX_SYS_WEAK_ALIAS_CRIDUPLICATE + + # Do we actually support aliasing? + AC_CACHE_CHECK([how to create weak aliases with $CC], + [ax_cv_sys_weak_alias], + [ax_cv_sys_weak_alias=$ax_sys_weak_alias]) + + # OK, set a #define + AS_IF([test $ax_cv_sys_weak_alias != no], [ + AC_DEFINE([HAVE_SYS_WEAK_ALIAS], 1, + [Define this if your system can create weak aliases]) + ]) + + # Can aliases cross object file boundaries? + _AX_SYS_WEAK_ALIAS_CROSSFILE + + # OK, remember the results + AC_SUBST([WEAK_ALIAS], [$ax_cv_sys_weak_alias]) + AC_SUBST([WEAK_ALIAS_CROSSFILE], [$ax_cv_sys_weak_alias_crossfile]) +]) + +AC_DEFUN([_AX_SYS_WEAK_ALIAS_ATTRIBUTE], +[ # Test whether compiler accepts __attribute__ form of weak aliasing + AC_CACHE_CHECK([whether $CC accepts function __attribute__((weak,alias()))], + [ax_cv_sys_weak_alias_attribute], [ + # We add -Werror if it's gcc to force an error exit if the weak attribute + # isn't understood + AS_IF([test $GCC = yes], [ + save_CFLAGS=$CFLAGS + CFLAGS=-Werror]) + + # Try linking with a weak alias... + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ +void __weakf(int c) {} +void weakf(int c) __attribute__((weak, alias("__weakf")));], + [weakf(0)])], + [ax_cv_sys_weak_alias_attribute=yes], + [ax_cv_sys_weak_alias_attribute=no]) + + # Restore original CFLAGS + AS_IF([test $GCC = yes], [ + CFLAGS=$save_CFLAGS]) + ]) + + # What was the result of the test? + AS_IF([test $ax_cv_sys_weak_alias_attribute = yes], [ + test $ax_sys_weak_alias = no && ax_sys_weak_alias=attribute + AC_DEFINE([HAVE_SYS_WEAK_ALIAS_ATTRIBUTE], 1, + [Define this if weak aliases may be created with __attribute__]) + ]) +]) + +AC_DEFUN([_AX_SYS_WEAK_ALIAS_PRAGMA], +[ # Test whether compiler accepts #pragma form of weak aliasing + AC_CACHE_CHECK([whether $CC supports @%:@pragma weak], + [ax_cv_sys_weak_alias_pragma], [ + + # Try linking with a weak alias... + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ +extern void weakf(int c); +@%:@pragma weak weakf = __weakf +void __weakf(int c) {}], + [weakf(0)])], + [ax_cv_sys_weak_alias_pragma=yes], + [ax_cv_sys_weak_alias_pragma=no]) + ]) + + # What was the result of the test? + AS_IF([test $ax_cv_sys_weak_alias_pragma = yes], [ + test $ax_sys_weak_alias = no && ax_sys_weak_alias=pragma + AC_DEFINE([HAVE_SYS_WEAK_ALIAS_PRAGMA], 1, + [Define this if weak aliases may be created with @%:@pragma weak]) + ]) +]) + +AC_DEFUN([_AX_SYS_WEAK_ALIAS_HPSECONDARY], +[ # Test whether compiler accepts _HP_SECONDARY_DEF pragma from HP... + AC_CACHE_CHECK([whether $CC supports @%:@pragma _HP_SECONDARY_DEF], + [ax_cv_sys_weak_alias_hpsecondary], [ + + # Try linking with a weak alias... + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ +extern void weakf(int c); +@%:@pragma _HP_SECONDARY_DEF __weakf weakf +void __weakf(int c) {}], + [weakf(0)])], + [ax_cv_sys_weak_alias_hpsecondary=yes], + [ax_cv_sys_weak_alias_hpsecondary=no]) + ]) + + # What was the result of the test? + AS_IF([test $ax_cv_sys_weak_alias_hpsecondary = yes], [ + test $ax_sys_weak_alias = no && ax_sys_weak_alias=hpsecondary + AC_DEFINE([HAVE_SYS_WEAK_ALIAS_HPSECONDARY], 1, + [Define this if weak aliases may be created with @%:@pragma _HP_SECONDARY_DEF]) + ]) +]) + +AC_DEFUN([_AX_SYS_WEAK_ALIAS_CRIDUPLICATE], +[ # Test whether compiler accepts "_CRI duplicate" pragma from Cray + AC_CACHE_CHECK([whether $CC supports @%:@pragma _CRI duplicate], + [ax_cv_sys_weak_alias_criduplicate], [ + + # Try linking with a weak alias... + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([ +extern void weakf(int c); +@%:@pragma _CRI duplicate weakf as __weakf +void __weakf(int c) {}], + [weakf(0)])], + [ax_cv_sys_weak_alias_criduplicate=yes], + [ax_cv_sys_weak_alias_criduplicate=no]) + ]) + + # What was the result of the test? + AS_IF([test $ax_cv_sys_weak_alias_criduplicate = yes], [ + test $ax_sys_weak_alias = no && ax_sys_weak_alias=criduplicate + AC_DEFINE([HAVE_SYS_WEAK_ALIAS_CRIDUPLICATE], 1, + [Define this if weak aliases may be created with @%:@pragma _CRI duplicate]) + ]) +]) + +dnl Note: This macro is modeled closely on AC_LINK_IFELSE, and in fact +dnl depends on some implementation details of that macro, particularly +dnl its use of _AC_MSG_LOG_CONFTEST to log the failed test program and +dnl its use of ac_link for running the linker. +AC_DEFUN([_AX_SYS_WEAK_ALIAS_CROSSFILE], +[ # Check to see if weak aliases can cross object file boundaries + AC_CACHE_CHECK([whether $CC supports weak aliases across object file boundaries], + [ax_cv_sys_weak_alias_crossfile], [ + AS_IF([test $ax_cv_sys_weak_alias = no], + [ax_cv_sys_weak_alias_crossfile=no], [ +dnl Must build our own test files... + # conftest1 contains our weak alias definition... + cat >conftest1.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF + cat confdefs.h >>conftest1.$ac_ext + cat >>conftest1.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +@%:@ifndef HAVE_SYS_WEAK_ALIAS_ATTRIBUTE +extern void weakf(int c); +@%:@if defined(HAVE_SYS_WEAK_ALIAS_PRAGMA) +@%:@pragma weak weakf = __weakf +@%:@elif defined(HAVE_SYS_WEAK_ALIAS_HPSECONDARY) +@%:@pragma _HP_SECONDARY_DEF __weakf weakf +@%:@elif defined(HAVE_SYS_WEAK_ALIAS_CRIDUPLICATE) +@%:@pragma _CRI duplicate weakf as __weakf +@%:@endif +@%:@endif +void __weakf(int c) {} +@%:@ifdef HAVE_SYS_WEAK_ALIAS_ATTRIBUTE +void weakf(int c) __attribute((weak, alias("__weakf"))); +@%:@endif +_ACEOF + # And conftest2 contains our main routine that calls it + cat >conftest2.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF + cat confdefs.h >> conftest2.$ac_ext + cat >>conftest2.$ac_ext <<_ACEOF +/* end confdefs.h. */ + +extern void weakf(int c); +int +main () +{ + weakf(0); + return 0; +} +_ACEOF + # We must remove the object files (if any) ourselves... + rm -f conftest2.$ac_objext conftest$ac_exeext + + # Change ac_link to compile *2* files together + save_aclink=$ac_link + ac_link=`echo "$ac_link" | \ + sed -e 's/conftest\(\.\$ac_ext\)/conftest1\1 conftest2\1/'` +dnl Substitute our own routine for logging the conftest +m4_pushdef([_AC_MSG_LOG_CONFTEST], +[echo "$as_me: failed program was:" >&AS_MESSAGE_LOG_FD +echo ">>> conftest1.$ac_ext" >&AS_MESSAGE_LOG_FD +sed "s/^/| /" conftest1.$ac_ext >&AS_MESSAGE_LOG_FD +echo ">>> conftest2.$ac_ext" >&AS_MESSAGE_LOG_FD +sed "s/^/| /" conftest2.$ac_ext >&AS_MESSAGE_LOG_FD +])dnl + # Since we created the files ourselves, don't use SOURCE argument + AC_LINK_IFELSE(, [ax_cv_sys_weak_alias_crossfile=yes], + [ax_cv_sys_weak_alias_crossfile=no]) +dnl Restore _AC_MSG_LOG_CONFTEST +m4_popdef([_AC_MSG_LOG_CONFTEST])dnl + # Restore ac_link + ac_link=$save_aclink + + # We must remove the object files (if any) and C files ourselves... + rm -f conftest1.$ac_ext conftest2.$ac_ext \ + conftest1.$ac_objext conftest2.$ac_objext + ]) + ]) + + # What were the results of the test? + AS_IF([test $ax_cv_sys_weak_alias_crossfile = yes], [ + AC_DEFINE([HAVE_SYS_WEAK_ALIAS_CROSSFILE], 1, + [Define this if weak aliases in other files are honored]) + ]) +]) diff --git a/m4/libtool-whole-archive.patch b/m4/libtool-whole-archive.patch new file mode 100644 index 0000000..7e2749c --- /dev/null +++ b/m4/libtool-whole-archive.patch @@ -0,0 +1,18 @@ +--- /usr/share/libtool/build-aux/ltmain.sh 2017-08-01 07:13:09.611041402 +0200 ++++ ltmain.sh 2018-08-31 17:32:15.381903718 +0200 +@@ -8439,8 +8439,13 @@ + # shared platforms. + if test unsupported != "$hardcode_direct"; then + test -n "$old_library" && linklib=$old_library +- compile_deplibs="$dir/$linklib $compile_deplibs" +- finalize_deplibs="$dir/$linklib $finalize_deplibs" ++ if test yes,yes = "$export_dynamic,$with_gnu_ld"; then ++ compile_deplibs="-Wl,--no-whole-archive $dir/$linklib -Wl,--whole-archive $compile_deplibs" ++ finalize_deplibs="-Wl,--no-whole-archive $dir/$linklib -Wl,--whole-archive $finalize_deplibs" ++ else ++ compile_deplibs="$dir/$linklib $compile_deplibs" ++ finalize_deplibs="$dir/$linklib $finalize_deplibs" ++ fi + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" diff --git a/m4/pkg.m4 b/m4/pkg.m4 new file mode 100644 index 0000000..a8dcd17 --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,214 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 1 (pkg-config-0.24) +# +# Copyright © 2004 Scott James Remnant . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +# only at the first occurence in configure.ac, so if the first place +# it's called might be skipped (such as if it is within an "if", you +# have to call PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1 ($2)]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])# PKG_CHECK_MODULES + + +# PKG_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable pkgconfigdir as the location where a module +# should install pkg-config .pc files. By default the directory is +# $libdir/pkgconfig, but the default can be changed by passing +# DIRECTORY. The user can override through the --with-pkgconfigdir +# parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_INSTALLDIR + + +# PKG_NOARCH_INSTALLDIR(DIRECTORY) +# ------------------------- +# Substitutes the variable noarch_pkgconfigdir as the location where a +# module should install arch-independent pkg-config .pc files. By +# default the directory is $datadir/pkgconfig, but the default can be +# changed by passing DIRECTORY. The user can override through the +# --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +]) dnl PKG_NOARCH_INSTALLDIR + + +# PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +# [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# ------------------------------------------- +# Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])# PKG_CHECK_VAR diff --git a/mgmtd/.gitignore b/mgmtd/.gitignore new file mode 100644 index 0000000..7ce107e --- /dev/null +++ b/mgmtd/.gitignore @@ -0,0 +1 @@ +mgmtd diff --git a/mgmtd/Makefile b/mgmtd/Makefile new file mode 100644 index 0000000..d69ec5f --- /dev/null +++ b/mgmtd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. mgmtd/mgmtd +%: ALWAYS + @$(MAKE) -s -C .. mgmtd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/mgmtd/mgmt.c b/mgmtd/mgmt.c new file mode 100644 index 0000000..8d41643 --- /dev/null +++ b/mgmtd/mgmt.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR Management Daemon (MGMTD) program + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "debug.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_be_adapter.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_fe_adapter.h" +#include "mgmtd/mgmt_history.h" +#include "mgmtd/mgmt_memory.h" + +struct debug mgmt_debug_be = {.desc = "Management backend adapater"}; +struct debug mgmt_debug_ds = {.desc = "Management datastore"}; +struct debug mgmt_debug_fe = {.desc = "Management frontend adapater"}; +struct debug mgmt_debug_txn = {.desc = "Management transaction"}; + +/* MGMTD process wide configuration. */ +static struct mgmt_master mgmt_master; + +/* MGMTD process wide configuration pointer to export. */ +struct mgmt_master *mm; + +void mgmt_master_init(struct event_loop *master, const int buffer_size) +{ + memset(&mgmt_master, 0, sizeof(struct mgmt_master)); + + mm = &mgmt_master; + mm->master = master; + mm->terminating = false; + mm->socket_buffer = buffer_size; + mm->perf_stats_en = true; +} + +void mgmt_init(void) +{ + + /* Initialize datastores */ + mgmt_ds_init(mm); + + /* Initialize history */ + mgmt_history_init(); + + /* Initialize MGMTD Transaction module */ + mgmt_txn_init(mm, mm->master); + + /* Initialize the MGMTD Frontend Adapter Module */ + mgmt_fe_adapter_init(mm->master); + + /* + * Initialize the CLI frontend client -- this queues an event for the + * client to short-circuit connect to the server (ourselves). + */ + vty_init_mgmt_fe(); + + /* + * MGMTD VTY commands installation -- the frr lib code will queue an + * event to read the config files which needs to happen after the + * connect from above is made. + */ + mgmt_vty_init(); + + /* + * Initialize the MGMTD Backend Adapter Module + * + * We do this after the FE stuff so that we have read our config file + * prior to any BE connection. Setting up the server will queue a + * "socket read" event to accept BE connections. So the code is counting + * on the above 2 events to run prior to any `accept` event from here. + */ + mgmt_be_adapter_init(mm->master); +} + +void mgmt_terminate(void) +{ + mgmt_fe_adapter_destroy(); + mgmt_be_adapter_destroy(); + mgmt_txn_destroy(); + mgmt_history_destroy(); + mgmt_ds_destroy(); +} diff --git a/mgmtd/mgmt.h b/mgmtd/mgmt.h new file mode 100644 index 0000000..665e8d8 --- /dev/null +++ b/mgmtd/mgmt.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD message definition header. + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_H +#define _FRR_MGMTD_H + +#include "debug.h" +#include "vrf.h" +#include "defaults.h" +#include "stream.h" +#include "mgmt_defines.h" + +#include "mgmtd/mgmt_memory.h" +#include "mgmtd/mgmt_history.h" +#include "mgmtd/mgmt_txn.h" +#include "mgmtd/mgmt_ds.h" + +#define MGMTD_SOCKET_BUF_SIZE 65535 +#define MGMTD_MAX_COMMIT_LIST 10 + +extern struct debug mgmt_debug_be; +extern struct debug mgmt_debug_ds; +extern struct debug mgmt_debug_fe; +extern struct debug mgmt_debug_txn; + +#define MGMT_DEBUG_BE_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL) +#define MGMT_DEBUG_DS_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_ds, DEBUG_MODE_ALL) +#define MGMT_DEBUG_FE_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL) +#define MGMT_DEBUG_TXN_CHECK() DEBUG_MODE_CHECK(&mgmt_debug_tx, DEBUG_MODE_ALL) + +struct mgmt_txn_ctx; + +/* + * MGMTD master for system wide configurations and variables. + */ +struct mgmt_master { + struct event_loop *master; + + /* How big should we set the socket buffer size */ + uint32_t socket_buffer; + + /* The single instance of config transaction allowed at any time */ + struct mgmt_txns_head txn_list; + + /* Map of Transactions and its ID */ + struct hash *txn_hash; + uint64_t next_txn_id; + + /* The single instance of config transaction allowed at any time */ + struct mgmt_txn_ctx *cfg_txn; + + /* Datastores */ + struct mgmt_ds_ctx *running_ds; + struct mgmt_ds_ctx *candidate_ds; + struct mgmt_ds_ctx *oper_ds; + + bool terminating; /* global flag that sigint terminate seen */ + bool perf_stats_en; /* to enable performance stats measurement */ + + /* List of commit infos */ + struct mgmt_cmt_infos_head cmts; /* List of last 10 commits executed. */ +}; + +extern struct mgmt_master *mm; + +/* Inline functions */ + +/* + * Remove trailing separator from a string. + * + * str + * A null terminated string. + * + * sep + * Trailing character that needs to be removed. + */ +static inline void mgmt_remove_trailing_separator(char *str, char sep) +{ + size_t len; + + len = strlen(str); + if (len && str[len - 1] == sep) + str[len - 1] = '\0'; +} + +/* Prototypes. */ +extern void mgmt_terminate(void); +extern void mgmt_reset(void); +extern time_t mgmt_clock(void); + +extern int mgmt_config_write(struct vty *vty); +extern struct vty *mgmt_vty_read_config(const char *config_file, + char *config_default_dir); +extern void mgmt_master_init(struct event_loop *master, const int buffer_size); + +extern void mgmt_init(void); +extern void mgmt_vty_init(void); + +#endif /* _FRR_MGMTD_H */ diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c new file mode 100644 index 0000000..c7f4fb9 --- /dev/null +++ b/mgmtd/mgmt_be_adapter.c @@ -0,0 +1,1125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Backend Client Connection Adapter + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include +#include "darr.h" +#include "frrevent.h" +#include "frrstr.h" +#include "sockopt.h" +#include "network.h" +#include "libfrr.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" +#include "mgmt_pb.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_memory.h" +#include "mgmt_be_client.h" +#include "mgmtd/mgmt_be_adapter.h" + +#define __dbg(fmt, ...) \ + DEBUGD(&mgmt_debug_be, "BE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) +#define __log_err(fmt, ...) \ + zlog_err("BE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +#define FOREACH_ADAPTER_IN_LIST(adapter) \ + frr_each_safe (mgmt_be_adapters, &mgmt_be_adapters, (adapter)) + +/* ---------- */ +/* Client IDs */ +/* ---------- */ + +const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { + [MGMTD_BE_CLIENT_ID_TESTC] = "mgmtd-testc", /* always first */ + [MGMTD_BE_CLIENT_ID_ZEBRA] = "zebra", +#ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = "ripd", +#endif +#ifdef HAVE_RIPNGD + [MGMTD_BE_CLIENT_ID_RIPNGD] = "ripngd", +#endif +#ifdef HAVE_STATICD + [MGMTD_BE_CLIENT_ID_STATICD] = "staticd", +#endif + [MGMTD_BE_CLIENT_ID_MAX] = "Unknown/Invalid", +}; + +/* ------------- */ +/* XPATH MAPPING */ +/* ------------- */ + +/* + * Mapping of YANG XPath prefixes to their corresponding backend clients. + */ +struct mgmt_be_xpath_map { + char *xpath_prefix; + uint64_t clients; +}; + +/* + * Each client gets their own map, but also union all the strings into the + * above map as well. + */ + +static const char *const zebra_config_xpaths[] = { + "/frr-affinity-map:lib", + "/frr-filter:lib", + "/frr-route-map:lib", + "/frr-zebra:zebra", + "/frr-interface:lib", + "/frr-vrf:lib", + NULL, +}; + +static const char *const zebra_oper_xpaths[] = { + "/frr-interface:lib/interface", + "/frr-vrf:lib/vrf/frr-zebra:zebra", + "/frr-zebra:zebra", + NULL, +}; + +#if HAVE_RIPD +static const char *const ripd_config_xpaths[] = { + "/frr-filter:lib", + "/frr-interface:lib/interface", + "/frr-ripd:ripd", + "/frr-route-map:lib", + "/frr-vrf:lib", + "/ietf-key-chain:key-chains", + NULL, +}; +static const char *const ripd_oper_xpaths[] = { + "/frr-ripd:ripd", + "/ietf-key-chain:key-chains", + NULL, +}; +static const char *const ripd_rpc_xpaths[] = { + "/frr-ripd", + NULL, +}; +#endif + +#if HAVE_RIPNGD +static const char *const ripngd_config_xpaths[] = { + "/frr-filter:lib", + "/frr-interface:lib/interface", + "/frr-ripngd:ripngd", + "/frr-route-map:lib", + "/frr-vrf:lib", + NULL, +}; +static const char *const ripngd_oper_xpaths[] = { + "/frr-ripngd:ripngd", + NULL, +}; +static const char *const ripngd_rpc_xpaths[] = { + "/frr-ripngd", + NULL, +}; +#endif + +#if HAVE_STATICD +static const char *const staticd_config_xpaths[] = { + "/frr-vrf:lib", + "/frr-interface:lib", + "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd", + NULL, +}; +#endif + +static const char *const *be_client_config_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { + [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_config_xpaths, +#ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = ripd_config_xpaths, +#endif +#ifdef HAVE_RIPNGD + [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_config_xpaths, +#endif +#ifdef HAVE_STATICD + [MGMTD_BE_CLIENT_ID_STATICD] = staticd_config_xpaths, +#endif +}; + +static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { +#ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = ripd_oper_xpaths, +#endif +#ifdef HAVE_RIPNGD + [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_oper_xpaths, +#endif + [MGMTD_BE_CLIENT_ID_ZEBRA] = zebra_oper_xpaths, +}; + +static const char *const *be_client_notif_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { +}; + +static const char *const *be_client_rpc_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { +#ifdef HAVE_RIPD + [MGMTD_BE_CLIENT_ID_RIPD] = ripd_rpc_xpaths, +#endif +#ifdef HAVE_RIPNGD + [MGMTD_BE_CLIENT_ID_RIPNGD] = ripngd_rpc_xpaths, +#endif +}; + +/* + * We would like to have a better ADT than one with O(n) comparisons + * + * Perhaps it's possible to sort this array in a way that allows binary search + * to find the start, then walk until no possible match can follow? Intuition + * says this probably involves exact match/no-match on a stem in the map array + * or something like that. + */ + +static struct mgmt_be_xpath_map *be_cfg_xpath_map; +static struct mgmt_be_xpath_map *be_oper_xpath_map; +static struct mgmt_be_xpath_map *be_notif_xpath_map; +static struct mgmt_be_xpath_map *be_rpc_xpath_map; + +static struct event_loop *mgmt_loop; +static struct msg_server mgmt_be_server = {.fd = -1}; + +static struct mgmt_be_adapters_head mgmt_be_adapters; + +static struct mgmt_be_client_adapter + *mgmt_be_adapters_by_id[MGMTD_BE_CLIENT_ID_MAX]; + + +/* Forward declarations */ +static void +mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter); + +static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, + enum mgmt_be_xpath_subscr_type type); + +const char *mgmt_be_client_id2name(enum mgmt_be_client_id id) +{ + if (id > MGMTD_BE_CLIENT_ID_MAX) + return "invalid client id"; + return mgmt_be_client_names[id]; +} + +static enum mgmt_be_client_id mgmt_be_client_name2id(const char *name) +{ + enum mgmt_be_client_id id; + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (!strncmp(mgmt_be_client_names[id], name, + MGMTD_CLIENT_NAME_MAX_LEN)) + return id; + } + + return MGMTD_BE_CLIENT_ID_MAX; +} + +static struct mgmt_be_client_adapter * +mgmt_be_find_adapter_by_fd(int conn_fd) +{ + struct mgmt_be_client_adapter *adapter; + + FOREACH_ADAPTER_IN_LIST (adapter) { + if (adapter->conn->fd == conn_fd) + return adapter; + } + + return NULL; +} + +static struct mgmt_be_client_adapter * +mgmt_be_find_adapter_by_name(const char *name) +{ + struct mgmt_be_client_adapter *adapter; + + FOREACH_ADAPTER_IN_LIST (adapter) { + if (!strncmp(adapter->name, name, sizeof(adapter->name))) + return adapter; + } + + return NULL; +} + +static void mgmt_register_client_xpath(enum mgmt_be_client_id id, + const char *xpath, + enum mgmt_be_xpath_subscr_type type) +{ + struct mgmt_be_xpath_map **maps, *map; + + switch (type) { + case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: + maps = &be_cfg_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: + maps = &be_oper_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: + maps = &be_notif_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: + maps = &be_rpc_xpath_map; + break; + } + + darr_foreach_p (*maps, map) { + if (!strcmp(xpath, map->xpath_prefix)) { + map->clients |= (1u << id); + return; + } + } + /* we didn't find a matching entry */ + map = darr_append(*maps); + map->xpath_prefix = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + map->clients = (1ul << id); +} + +/* + * initial the combined maps from per client maps + */ +static void mgmt_be_xpath_map_init(void) +{ + enum mgmt_be_client_id id; + const char *const *init; + + __dbg("Init XPath Maps"); + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + /* Initialize the common config init map */ + for (init = be_client_config_xpaths[id]; init && *init; init++) { + __dbg(" - CFG XPATH: '%s'", *init); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + } + + /* Initialize the common oper init map */ + for (init = be_client_oper_xpaths[id]; init && *init; init++) { + __dbg(" - OPER XPATH: '%s'", *init); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } + + /* Initialize the common NOTIF init map */ + for (init = be_client_notif_xpaths[id]; init && *init; init++) { + __dbg(" - NOTIF XPATH: '%s'", *init); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + } + + /* Initialize the common RPC init map */ + for (init = be_client_rpc_xpaths[id]; init && *init; init++) { + __dbg(" - RPC XPATH: '%s'", *init); + mgmt_register_client_xpath(id, *init, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + } + } + + __dbg("Total Cfg XPath Maps: %u", darr_len(be_cfg_xpath_map)); + __dbg("Total Oper XPath Maps: %u", darr_len(be_oper_xpath_map)); + __dbg("Total Noitf XPath Maps: %u", darr_len(be_notif_xpath_map)); + __dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map)); +} + +static void mgmt_be_xpath_map_cleanup(void) +{ + struct mgmt_be_xpath_map *map; + + darr_foreach_p (be_cfg_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_cfg_xpath_map); + + darr_foreach_p (be_oper_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_oper_xpath_map); + + darr_foreach_p (be_notif_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_notif_xpath_map); + + darr_foreach_p (be_rpc_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_rpc_xpath_map); +} + + +/* + * Check if either path or xpath is a prefix of the other. Before checking the + * xpath is converted to a regular path string (e..g, removing key value + * specifiers). + */ +static bool mgmt_be_xpath_prefix(const char *path, const char *xpath) +{ + int xc, pc; + + while ((xc = *xpath++)) { + if (xc == '[') { + xpath = frrstr_skip_over_char(xpath, ']'); + if (!xpath) + return false; + continue; + } + pc = *path++; + if (!pc) + return true; + if (pc != xc) + return false; + } + return true; +} + +static void mgmt_be_adapter_delete(struct mgmt_be_client_adapter *adapter) +{ + __dbg("deleting client adapter '%s'", adapter->name); + + /* + * Notify about disconnect for appropriate cleanup + */ + mgmt_txn_notify_be_adapter_conn(adapter, false); + if (adapter->id < MGMTD_BE_CLIENT_ID_MAX) { + mgmt_be_adapters_by_id[adapter->id] = NULL; + adapter->id = MGMTD_BE_CLIENT_ID_MAX; + } + + assert(adapter->refcount == 1); + mgmt_be_adapter_unlock(&adapter); +} + +static int mgmt_be_adapter_notify_disconnect(struct msg_conn *conn) +{ + struct mgmt_be_client_adapter *adapter = conn->user; + + __dbg("notify disconnect for client adapter '%s'", adapter->name); + + mgmt_be_adapter_delete(adapter); + + return 0; +} + +static void +mgmt_be_adapter_cleanup_old_conn(struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_be_client_adapter *old; + + FOREACH_ADAPTER_IN_LIST (old) { + if (old != adapter && + !strncmp(adapter->name, old->name, sizeof(adapter->name))) { + /* + * We have a Zombie lingering around + */ + __dbg("Client '%s' (FD:%d) seems to have reconnected. Removing old connection (FD:%d)!", + adapter->name, adapter->conn->fd, old->conn->fd); + /* this will/should delete old */ + msg_conn_disconnect(old->conn, false); + } + } +} + +static int mgmt_be_adapter_send_msg(struct mgmt_be_client_adapter *adapter, + Mgmtd__BeMessage *be_msg) +{ + return msg_conn_send_msg( + adapter->conn, MGMT_MSG_VERSION_PROTOBUF, be_msg, + mgmtd__be_message__get_packed_size(be_msg), + (size_t(*)(void *, void *))mgmtd__be_message__pack, false); +} + +static int mgmt_be_send_subscr_reply(struct mgmt_be_client_adapter *adapter, + bool success) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeSubscribeReply reply; + + mgmtd__be_subscribe_reply__init(&reply); + reply.success = success; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REPLY; + be_msg.subscr_reply = &reply; + + __dbg("Sending SUBSCR_REPLY client: %s sucess: %u", adapter->name, + success); + + return mgmt_be_adapter_send_msg(adapter, &be_msg); +} + +static int +mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, + Mgmtd__BeMessage *be_msg) +{ + const char *xpath; + uint i, num; + + /* + * protobuf-c adds a max size enum with an internal, and changing by + * version, name; cast to an int to avoid unhandled enum warnings + */ + switch ((int)be_msg->message_case) { + case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ: + __dbg("Got SUBSCR_REQ from '%s' to register xpaths config: %zu oper: %zu notif: %zu rpc: %zu", + be_msg->subscr_req->client_name, + be_msg->subscr_req->n_config_xpaths, + be_msg->subscr_req->n_oper_xpaths, + be_msg->subscr_req->n_notif_xpaths, + be_msg->subscr_req->n_rpc_xpaths); + + if (strlen(be_msg->subscr_req->client_name)) { + strlcpy(adapter->name, be_msg->subscr_req->client_name, + sizeof(adapter->name)); + adapter->id = mgmt_be_client_name2id(adapter->name); + if (adapter->id >= MGMTD_BE_CLIENT_ID_MAX) { + __log_err("Unable to resolve adapter '%s' to a valid ID. Disconnecting!", + adapter->name); + /* this will/should delete old */ + msg_conn_disconnect(adapter->conn, false); + break; + } + mgmt_be_adapters_by_id[adapter->id] = adapter; + mgmt_be_adapter_cleanup_old_conn(adapter); + + /* schedule INIT sequence now that it is registered */ + mgmt_be_adapter_sched_init_event(adapter); + } + + num = be_msg->subscr_req->n_config_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->config_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + } + + num = be_msg->subscr_req->n_oper_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->oper_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + } + + num = be_msg->subscr_req->n_notif_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->notif_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + } + + num = be_msg->subscr_req->n_rpc_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->rpc_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + } + + mgmt_be_send_subscr_reply(adapter, true); + break; + case MGMTD__BE_MESSAGE__MESSAGE_TXN_REPLY: + __dbg("Got %s TXN_REPLY from '%s' txn-id %" PRIx64 " with '%s'", + be_msg->txn_reply->create ? "Create" : "Delete", + adapter->name, be_msg->txn_reply->txn_id, + be_msg->txn_reply->success ? "success" : "failure"); + /* + * Forward the TXN_REPLY to txn module. + */ + mgmt_txn_notify_be_txn_reply( + be_msg->txn_reply->txn_id, + be_msg->txn_reply->create, + be_msg->txn_reply->success, adapter); + break; + case MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REPLY: + __dbg("Got CFGDATA_REPLY from '%s' txn-id %" PRIx64 " err:'%s'", + adapter->name, be_msg->cfg_data_reply->txn_id, + be_msg->cfg_data_reply->error_if_any + ? be_msg->cfg_data_reply->error_if_any + : "None"); + /* + * Forward the CGFData-create reply to txn module. + */ + mgmt_txn_notify_be_cfgdata_reply( + be_msg->cfg_data_reply->txn_id, + be_msg->cfg_data_reply->success, + be_msg->cfg_data_reply->error_if_any, adapter); + break; + case MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REPLY: + __dbg("Got %s CFG_APPLY_REPLY from '%s' txn-id %" PRIx64 + " err:'%s'", + be_msg->cfg_apply_reply->success ? "successful" : "failed", + adapter->name, be_msg->cfg_apply_reply->txn_id, + be_msg->cfg_apply_reply->error_if_any + ? be_msg->cfg_apply_reply->error_if_any + : "None"); + /* + * Forward the CGFData-apply reply to txn module. + */ + mgmt_txn_notify_be_cfg_apply_reply( + be_msg->cfg_apply_reply->txn_id, + be_msg->cfg_apply_reply->success, + be_msg->cfg_apply_reply->error_if_any, adapter); + break; + /* + * NOTE: The following messages are always sent from MGMTD to + * Backend clients only and/or need not be handled on MGMTd. + */ + case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REPLY: + case MGMTD__BE_MESSAGE__MESSAGE_TXN_REQ: + case MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REQ: + case MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REQ: + case MGMTD__BE_MESSAGE__MESSAGE__NOT_SET: + default: + /* + * A 'default' case is being added contrary to the + * FRR code guidelines to take care of build + * failures on certain build systems (courtesy of + * the proto-c package). + */ + break; + } + + return 0; +} + +int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, bool create) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeTxnReq txn_req; + + mgmtd__be_txn_req__init(&txn_req); + txn_req.create = create; + txn_req.txn_id = txn_id; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_TXN_REQ; + be_msg.txn_req = &txn_req; + + __dbg("Sending TXN_REQ to '%s' txn-id: %" PRIu64, adapter->name, txn_id); + + return mgmt_be_adapter_send_msg(adapter, &be_msg); +} + +int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, + Mgmtd__YangCfgDataReq **cfgdata_reqs, + size_t num_reqs, bool end_of_data) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeCfgDataCreateReq cfgdata_req; + + mgmtd__be_cfg_data_create_req__init(&cfgdata_req); + cfgdata_req.txn_id = txn_id; + cfgdata_req.data_req = cfgdata_reqs; + cfgdata_req.n_data_req = num_reqs; + cfgdata_req.end_of_data = end_of_data; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_CFG_DATA_REQ; + be_msg.cfg_data_req = &cfgdata_req; + + __dbg("Sending CFGDATA_CREATE_REQ to '%s' txn-id: %" PRIu64 " last: %s", + adapter->name, txn_id, end_of_data ? "yes" : "no"); + + return mgmt_be_adapter_send_msg(adapter, &be_msg); +} + +int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id) +{ + Mgmtd__BeMessage be_msg; + Mgmtd__BeCfgDataApplyReq apply_req; + + mgmtd__be_cfg_data_apply_req__init(&apply_req); + apply_req.txn_id = txn_id; + + mgmtd__be_message__init(&be_msg); + be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_CFG_APPLY_REQ; + be_msg.cfg_apply_req = &apply_req; + + __dbg("Sending CFG_APPLY_REQ to '%s' txn-id: %" PRIu64, adapter->name, + txn_id); + + return mgmt_be_adapter_send_msg(adapter, &be_msg); +} + +int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg) +{ + struct mgmt_be_client_adapter *adapter = mgmt_be_get_adapter_by_id(id); + + if (!adapter) + return -1; + + return mgmt_msg_native_send_msg(adapter->conn, msg, false); +} + +static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg, + size_t msglen) +{ + struct mgmt_be_client_adapter *adapter; + struct mgmt_be_xpath_map *map; + struct nb_node *nb_node; + const char *notif; + uint id, len; + + if (!darr_len(be_notif_xpath_map)) + return; + + notif = mgmt_msg_native_xpath_decode(msg, msglen); + if (!notif) { + __log_err("Corrupt notify msg"); + return; + } + + nb_node = nb_node_find(notif); + if (!nb_node) { + __log_err("No schema found for notification: %s", notif); + return; + } + + darr_foreach_p (be_notif_xpath_map, map) { + len = strlen(map->xpath_prefix); + if (strncmp(map->xpath_prefix, nb_node->xpath, len) && + strncmp(map->xpath_prefix, notif, len)) + continue; + + FOREACH_BE_CLIENT_BITS (id, map->clients) { + adapter = mgmt_be_get_adapter_by_id(id); + if (!adapter) + continue; + msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, + msg, msglen, NULL, false); + } + } +} + +/* + * Handle a native encoded message + */ +static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_header *msg, + size_t msg_len) +{ + struct mgmt_msg_notify_data *notify_msg; + struct mgmt_msg_tree_data *tree_msg; + struct mgmt_msg_rpc_reply *rpc_msg; + struct mgmt_msg_error *error_msg; + + /* get the transaction */ + + switch (msg->code) { + case MGMT_MSG_CODE_ERROR: + error_msg = (typeof(error_msg))msg; + __dbg("Got ERROR from '%s' txn-id %" PRIx64, adapter->name, + msg->refer_id); + + /* Forward the reply to the txn module */ + mgmt_txn_notify_error(adapter, msg->refer_id, msg->req_id, + error_msg->error, error_msg->errstr); + + break; + case MGMT_MSG_CODE_TREE_DATA: + /* tree data from a backend client */ + tree_msg = (typeof(tree_msg))msg; + __dbg("Got TREE_DATA from '%s' txn-id %" PRIx64, adapter->name, + msg->refer_id); + + /* Forward the reply to the txn module */ + mgmt_txn_notify_tree_data_reply(adapter, tree_msg, msg_len); + break; + case MGMT_MSG_CODE_RPC_REPLY: + /* RPC reply from a backend client */ + rpc_msg = (typeof(rpc_msg))msg; + __dbg("Got RPC_REPLY from '%s' txn-id %" PRIx64, adapter->name, + msg->refer_id); + + /* Forward the reply to the txn module */ + mgmt_txn_notify_rpc_reply(adapter, rpc_msg, msg_len); + break; + case MGMT_MSG_CODE_NOTIFY: + notify_msg = (typeof(notify_msg))msg; + __dbg("Got NOTIFY from '%s'", adapter->name); + mgmt_be_adapter_send_notify(notify_msg, msg_len); + mgmt_fe_adapter_send_notify(notify_msg, msg_len); + break; + default: + __log_err("unknown native message txn-id %" PRIu64 + " req-id %" PRIu64 + " code %u from BE client for adapter %s", + msg->refer_id, msg->req_id, msg->code, adapter->name); + break; + } +} + + +static void mgmt_be_adapter_process_msg(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn) +{ + struct mgmt_be_client_adapter *adapter = conn->user; + Mgmtd__BeMessage *be_msg; + + if (version == MGMT_MSG_VERSION_NATIVE) { + struct mgmt_msg_header *msg = (typeof(msg))data; + + if (len >= sizeof(*msg)) + be_adapter_handle_native_msg(adapter, msg, len); + else + __log_err("native message to adapter %s too short %zu", + adapter->name, len); + return; + } + + be_msg = mgmtd__be_message__unpack(NULL, len, data); + if (!be_msg) { + __dbg("Failed to decode %zu bytes for adapter: %s", len, + adapter->name); + return; + } + __dbg("Decoded %zu bytes of message: %u for adapter: %s", len, + be_msg->message_case, adapter->name); + (void)mgmt_be_adapter_handle_msg(adapter, be_msg); + mgmtd__be_message__free_unpacked(be_msg, NULL); +} + +/* + * Args for callback + */ +struct mgmt_be_get_adapter_config_params { + struct mgmt_be_client_adapter *adapter; + struct nb_config_cbs *cfg_chgs; + uint32_t seq; +}; + +/* + * Initialize a BE client over a new connection + */ +static void mgmt_be_adapter_conn_init(struct event *thread) +{ + struct mgmt_be_client_adapter *adapter; + + adapter = (struct mgmt_be_client_adapter *)EVENT_ARG(thread); + assert(adapter && adapter->conn->fd >= 0); + + /* + * Notify TXN module to create a CONFIG transaction and + * download the CONFIGs identified for this new client. + * If the TXN module fails to initiate the CONFIG transaction + * retry a bit later. It only fails if there's an existing config + * transaction in progress. + */ + if (mgmt_txn_notify_be_adapter_conn(adapter, true) != 0) { + zlog_err("XXX txn in progress, retry init"); + mgmt_be_adapter_sched_init_event(adapter); + return; + } +} + +/* + * Schedule the initialization of the BE client connection. + */ +static void +mgmt_be_adapter_sched_init_event(struct mgmt_be_client_adapter *adapter) +{ + event_add_timer_msec(mgmt_loop, mgmt_be_adapter_conn_init, adapter, + MGMTD_BE_CONN_INIT_DELAY_MSEC, + &adapter->conn_init_ev); +} + +void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter) +{ + adapter->refcount++; +} + +extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter) +{ + struct mgmt_be_client_adapter *a = *adapter; + assert(a && a->refcount); + + if (!--a->refcount) { + mgmt_be_adapters_del(&mgmt_be_adapters, a); + EVENT_OFF(a->conn_init_ev); + msg_server_conn_delete(a->conn); + XFREE(MTYPE_MGMTD_BE_ADPATER, a); + } + + *adapter = NULL; +} + +/* + * Initialize the BE adapter module + */ +void mgmt_be_adapter_init(struct event_loop *tm) +{ + char server_path[MAXPATHLEN]; + + assert(!mgmt_loop); + mgmt_loop = tm; + + mgmt_be_adapters_init(&mgmt_be_adapters); + mgmt_be_xpath_map_init(); + + snprintf(server_path, sizeof(server_path), MGMTD_BE_SOCK_NAME); + + if (msg_server_init(&mgmt_be_server, server_path, tm, + mgmt_be_create_adapter, "backend", &mgmt_debug_be)) { + zlog_err("cannot initialize backend server"); + exit(1); + } +} + +/* + * Destroy the BE adapter module + */ +void mgmt_be_adapter_destroy(void) +{ + struct mgmt_be_client_adapter *adapter; + + msg_server_cleanup(&mgmt_be_server); + FOREACH_ADAPTER_IN_LIST (adapter) { + mgmt_be_adapter_delete(adapter); + } + mgmt_be_xpath_map_cleanup(); +} + +/* + * The server accepted a new connection + */ +struct msg_conn *mgmt_be_create_adapter(int conn_fd, union sockunion *from) +{ + struct mgmt_be_client_adapter *adapter = NULL; + + assert(!mgmt_be_find_adapter_by_fd(conn_fd)); + + adapter = XCALLOC(MTYPE_MGMTD_BE_ADPATER, + sizeof(struct mgmt_be_client_adapter)); + adapter->id = MGMTD_BE_CLIENT_ID_MAX; + snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", + conn_fd); + + mgmt_be_adapter_lock(adapter); + mgmt_be_adapters_add_tail(&mgmt_be_adapters, adapter); + RB_INIT(nb_config_cbs, &adapter->cfg_chgs); + + adapter->conn = msg_server_conn_create(mgmt_loop, conn_fd, + mgmt_be_adapter_notify_disconnect, + mgmt_be_adapter_process_msg, + MGMTD_BE_MAX_NUM_MSG_PROC, + MGMTD_BE_MAX_NUM_MSG_WRITE, + MGMTD_BE_MAX_MSG_LEN, adapter, + "BE-adapter"); + + adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL); + + __dbg("Added new MGMTD Backend adapter '%s'", adapter->name); + + return adapter->conn; +} + +struct mgmt_be_client_adapter * +mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id) +{ + return (id < MGMTD_BE_CLIENT_ID_MAX ? mgmt_be_adapters_by_id[id] : NULL); +} + +struct mgmt_be_client_adapter * +mgmt_be_get_adapter_by_name(const char *name) +{ + return mgmt_be_find_adapter_by_name(name); +} + +void mgmt_be_adapter_toggle_client_debug(bool set) +{ + struct mgmt_be_client_adapter *adapter; + + FOREACH_ADAPTER_IN_LIST (adapter) + adapter->conn->debug = set; +} + +/* + * Get a full set of changes for all the config that an adapter is subscribed to + * receive. + */ +void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, + struct nb_config_cbs **changes) +{ + const struct lyd_node *root, *dnode; + uint32_t seq = 0; + char *xpath; + + /* We can't be in the middle of sending other chgs when here. */ + assert(RB_EMPTY(nb_config_cbs, &adapter->cfg_chgs)); + + *changes = &adapter->cfg_chgs; + LY_LIST_FOR (running_config->dnode, root) { + LYD_TREE_DFS_BEGIN (root, dnode) { + if (lysc_is_key(dnode->schema)) + goto walk_cont; + + xpath = lyd_path(dnode, LYD_PATH_STD, NULL, 0); + if (be_is_client_interested(xpath, adapter->id, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG)) + nb_config_diff_add_change(*changes, NB_CB_CREATE, &seq, dnode); + else + LYD_TREE_DFS_continue = 1; /* skip any subtree */ + free(xpath); + walk_cont: + LYD_TREE_DFS_END(root, dnode); + } + } +} + +uint64_t mgmt_be_interested_clients(const char *xpath, + enum mgmt_be_xpath_subscr_type type) +{ + struct mgmt_be_xpath_map *maps = NULL, *map; + enum mgmt_be_client_id id; + uint64_t clients; + + switch (type) { + case MGMT_BE_XPATH_SUBSCR_TYPE_CFG: + maps = be_cfg_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_OPER: + maps = be_oper_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF: + maps = be_notif_xpath_map; + break; + case MGMT_BE_XPATH_SUBSCR_TYPE_RPC: + maps = be_rpc_xpath_map; + break; + } + + clients = 0; + + __dbg("XPATH: '%s'", xpath); + darr_foreach_p (maps, map) + if (mgmt_be_xpath_prefix(map->xpath_prefix, xpath)) + clients |= map->clients; + + if (DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)) { + FOREACH_BE_CLIENT_BITS (id, clients) + __dbg("Cient: %s: subscribed", + mgmt_be_client_id2name(id)); + } + return clients; +} + +/** + * Return true if `client_id` is interested in `xpath` for `config` + * or oper (!`config`). + * + * Args: + * xpath - the xpath to check for interest. + * client_id - the BE client being checked for. + * bool - check for config (vs oper) subscription. + * + * Returns: + * Interested or not. + */ +static bool be_is_client_interested(const char *xpath, enum mgmt_be_client_id id, + enum mgmt_be_xpath_subscr_type type) +{ + uint64_t clients; + + assert(id < MGMTD_BE_CLIENT_ID_MAX); + + __dbg("Checking client: %s for xpath: '%s'", mgmt_be_client_id2name(id), + xpath); + + clients = mgmt_be_interested_clients(xpath, type); + if (IS_IDBIT_SET(clients, id)) { + __dbg("client: %s: interested", mgmt_be_client_id2name(id)); + return true; + } + + __dbg("client: %s: not interested", mgmt_be_client_id2name(id)); + return false; +} + +void mgmt_be_adapter_status_write(struct vty *vty) +{ + struct mgmt_be_client_adapter *adapter; + + vty_out(vty, "MGMTD Backend Adapters\n"); + + FOREACH_ADAPTER_IN_LIST (adapter) { + vty_out(vty, " Client: \t\t\t%s\n", adapter->name); + vty_out(vty, " Conn-FD: \t\t\t%d\n", adapter->conn->fd); + vty_out(vty, " Client-Id: \t\t\t%d\n", adapter->id); + vty_out(vty, " Ref-Count: \t\t\t%u\n", adapter->refcount); + vty_out(vty, " Msg-Recvd: \t\t\t%" PRIu64 "\n", + adapter->conn->mstate.nrxm); + vty_out(vty, " Bytes-Recvd: \t\t%" PRIu64 "\n", + adapter->conn->mstate.nrxb); + vty_out(vty, " Msg-Sent: \t\t\t%" PRIu64 "\n", + adapter->conn->mstate.ntxm); + vty_out(vty, " Bytes-Sent: \t\t%" PRIu64 "\n", + adapter->conn->mstate.ntxb); + } + vty_out(vty, " Total: %d\n", + (int)mgmt_be_adapters_count(&mgmt_be_adapters)); +} + +static void be_show_xpath_register(struct vty *vty, + struct mgmt_be_xpath_map *map) +{ + enum mgmt_be_client_id id; + const char *astr; + + vty_out(vty, " - xpath: '%s'\n", map->xpath_prefix); + FOREACH_BE_CLIENT_BITS (id, map->clients) { + astr = mgmt_be_get_adapter_by_id(id) ? "active" : "inactive"; + vty_out(vty, " -- %s-client: '%s'\n", astr, + mgmt_be_client_id2name(id)); + } +} +void mgmt_be_xpath_register_write(struct vty *vty) +{ + struct mgmt_be_xpath_map *map; + + vty_out(vty, "MGMTD Backend CFG XPath Registry: Count: %u\n", + darr_len(be_oper_xpath_map)); + darr_foreach_p (be_cfg_xpath_map, map) + be_show_xpath_register(vty, map); + + vty_out(vty, "\nMGMTD Backend OPER XPath Registry: Count: %u\n", + darr_len(be_oper_xpath_map)); + darr_foreach_p (be_oper_xpath_map, map) + be_show_xpath_register(vty, map); + + vty_out(vty, "\nMGMTD Backend NOTIFY XPath Registry: Count: %u\n", + darr_len(be_notif_xpath_map)); + darr_foreach_p (be_notif_xpath_map, map) + be_show_xpath_register(vty, map); + + vty_out(vty, "\nMGMTD Backend RPC XPath Registry: Count: %u\n", + darr_len(be_rpc_xpath_map)); + darr_foreach_p (be_rpc_xpath_map, map) + be_show_xpath_register(vty, map); +} + +void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath) +{ + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + uint64_t cclients, nclients, oclients, rclients, combined; + + cclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + oclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + nclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF); + rclients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + combined = cclients | nclients | oclients | rclients; + + vty_out(vty, "XPath: '%s'\n", xpath); + FOREACH_BE_CLIENT_BITS (id, combined) { + vty_out(vty, + " -- Client: '%s'\tconfig:%d notify:%d oper:%d rpc:%d\n", + mgmt_be_client_id2name(id), IS_IDBIT_SET(cclients, id), + IS_IDBIT_SET(nclients, id), IS_IDBIT_SET(oclients, id), + IS_IDBIT_SET(rclients, id)); + adapter = mgmt_be_get_adapter_by_id(id); + if (adapter) + vty_out(vty, " -- Adapter: %p\n", adapter); + } +} diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h new file mode 100644 index 0000000..c9f2ab1 --- /dev/null +++ b/mgmtd/mgmt_be_adapter.h @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Backend Client Connection Adapter + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#ifndef _FRR_MGMTD_BE_ADAPTER_H_ +#define _FRR_MGMTD_BE_ADAPTER_H_ + +#include "mgmt_be_client.h" +#include "mgmt_msg.h" +#include "mgmt_defines.h" +#include "mgmtd/mgmt_ds.h" + +#define MGMTD_BE_CONN_INIT_DELAY_MSEC 50 + +#define MGMTD_FIND_ADAPTER_BY_INDEX(adapter_index) \ + mgmt_adaptr_ref[adapter_index] + +/** + * CLIENT-ID + * + * Add enum value for each supported component, wrap with + * #ifdef HAVE_COMPONENT + */ +enum mgmt_be_client_id { + MGMTD_BE_CLIENT_ID_TESTC, /* always first */ + MGMTD_BE_CLIENT_ID_ZEBRA, +#ifdef HAVE_RIPD + MGMTD_BE_CLIENT_ID_RIPD, +#endif +#ifdef HAVE_RIPNGD + MGMTD_BE_CLIENT_ID_RIPNGD, +#endif +#ifdef HAVE_STATICD + MGMTD_BE_CLIENT_ID_STATICD, +#endif + MGMTD_BE_CLIENT_ID_MAX +}; +#define MGMTD_BE_CLIENT_ID_MIN 0 + + +enum mgmt_be_req_type { + MGMTD_BE_REQ_NONE = 0, + MGMTD_BE_REQ_CFG_VALIDATE, + MGMTD_BE_REQ_CFG_APPLY, + MGMTD_BE_REQ_DATA_GET_ELEM, + MGMTD_BE_REQ_DATA_GET_NEXT +}; + +struct mgmt_be_cfgreq { + Mgmtd__YangCfgDataReq **cfgdata_reqs; + size_t num_reqs; +}; + +struct mgmt_be_datareq { + Mgmtd__YangGetDataReq **getdata_reqs; + size_t num_reqs; +}; + +PREDECL_LIST(mgmt_be_adapters); +PREDECL_LIST(mgmt_txn_badapters); + +struct mgmt_be_client_adapter { + struct msg_conn *conn; + + struct event *conn_init_ev; + + enum mgmt_be_client_id id; + uint32_t flags; + char name[MGMTD_CLIENT_NAME_MAX_LEN]; + + int refcount; + + /* + * List of config items that should be sent to the + * backend during re/connect. This is temporarily + * created and then freed-up as soon as the initial + * config items has been applied onto the backend. + */ + struct nb_config_cbs cfg_chgs; + + struct mgmt_be_adapters_item list_linkage; +}; + +#define MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED (1U << 0) + +DECLARE_LIST(mgmt_be_adapters, struct mgmt_be_client_adapter, list_linkage); + +/* + * MGMT_SUBSCR_xxx - flags for subscription types for xpaths registrations + * + * MGMT_SUBSCR_VALIDATE_CFG :: the client should be asked to validate config + * MGMT_SUBSCR_NOTIFY_CFG :: the client should be notified of config changes + * MGMT_SUBSCR_OPER_OWN :: the client owns the given oeprational state + */ +#define MGMT_SUBSCR_VALIDATE_CFG 0x1 +#define MGMT_SUBSCR_NOTIFY_CFG 0x2 +#define MGMT_SUBSCR_OPER_OWN 0x4 +#define MGMT_SUBSCR_ALL 0x7 + +/* --------- */ +/* CLIENT-ID */ +/* --------- */ + +#define FOREACH_MGMTD_BE_CLIENT_ID(id) \ + for ((id) = MGMTD_BE_CLIENT_ID_MIN; (id) < MGMTD_BE_CLIENT_ID_MAX; \ + (id)++) + +#define IS_IDBIT_SET(v, id) (!IS_IDBIT_UNSET(v, id)) +#define IS_IDBIT_UNSET(v, id) (!((v) & (1ull << (id)))) + +#define __GET_NEXT_SET(id, bits) \ + ({ \ + enum mgmt_be_client_id __id = (id); \ + \ + for (; __id < MGMTD_BE_CLIENT_ID_MAX && \ + IS_IDBIT_UNSET(bits, __id); \ + __id++) \ + ; \ + __id; \ + }) + +#define FOREACH_BE_CLIENT_BITS(id, bits) \ + for ((id) = __GET_NEXT_SET(MGMTD_BE_CLIENT_ID_MIN, bits); \ + (id) < MGMTD_BE_CLIENT_ID_MAX; \ + (id) = __GET_NEXT_SET((id) + 1, bits)) + +/* ---------- */ +/* Prototypes */ +/* ---------- */ + +/* Initialise backend adapter module. */ +extern void mgmt_be_adapter_init(struct event_loop *tm); + +/* Destroy the backend adapter module. */ +extern void mgmt_be_adapter_destroy(void); + +/* Acquire lock for backend adapter. */ +extern void mgmt_be_adapter_lock(struct mgmt_be_client_adapter *adapter); + +/* Remove lock from backend adapter. */ +extern void mgmt_be_adapter_unlock(struct mgmt_be_client_adapter **adapter); + +/* Create backend adapter. */ +extern struct msg_conn *mgmt_be_create_adapter(int conn_fd, + union sockunion *su); + +/* Fetch backend adapter given an adapter name. */ +extern struct mgmt_be_client_adapter * +mgmt_be_get_adapter_by_name(const char *name); + +/* Fetch backend adapter given an client ID. */ +extern struct mgmt_be_client_adapter * +mgmt_be_get_adapter_by_id(enum mgmt_be_client_id id); + +/* Get the client name given a client ID */ +extern const char *mgmt_be_client_id2name(enum mgmt_be_client_id id); + +/* Toggle debug on or off for connected clients. */ +extern void mgmt_be_adapter_toggle_client_debug(bool set); + +/* Fetch backend adapter config. */ +extern void mgmt_be_get_adapter_config(struct mgmt_be_client_adapter *adapter, + struct nb_config_cbs **changes); + +/* Create/destroy a transaction. */ +extern int mgmt_be_send_txn_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, bool create); + +/* + * Send config data create request to backend client. + * + * adaptr + * Backend adapter information. + * + * txn_id + * Unique transaction identifier. + * + * cfgdata_reqs + * An array of pointer to Mgmtd__YangCfgDataReq. + * + * num_reqs + * Length of the cfgdata_reqs array. + * + * end_of_data + * TRUE if the data from last batch, FALSE otherwise. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_be_send_cfgdata_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, + Mgmtd__YangCfgDataReq **cfgdata_reqs, + size_t num_reqs, bool end_of_data); + +/* + * Send config apply request to backend client. + * + * adapter + * Backend adapter information. + * + * txn_id + * Unique transaction identifier. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_be_send_cfgapply_req(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id); + +/* + * Dump backend adapter status to vty. + */ +extern void mgmt_be_adapter_status_write(struct vty *vty); + +/* + * Dump xpath registry for each backend client to vty. + */ +extern void mgmt_be_xpath_register_write(struct vty *vty); + + +/** + * Send a native message to a backend client + * + * Args: + * adapter: the client to send the message to. + * msg: a native message from mgmt_msg_native_alloc_msg() + * + * Return: + * Any return value from msg_conn_send_msg(). + */ +extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg); + +enum mgmt_be_xpath_subscr_type { + MGMT_BE_XPATH_SUBSCR_TYPE_CFG, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER, + MGMT_BE_XPATH_SUBSCR_TYPE_NOTIF, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC, +}; + +/** + * Lookup the clients which are subscribed to a given `xpath` + * and the way they are subscribed. + * + * Args: + * xpath - the xpath to check for subscription information. + * type - type of subscription to check for. + */ +extern uint64_t mgmt_be_interested_clients(const char *xpath, + enum mgmt_be_xpath_subscr_type type); + +/** + * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. + * @msg: the notify message from the backend client. + * @msglen: the length of the notify message. + */ +extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, + size_t msglen); +/* + * Dump backend client information for a given xpath to vty. + */ +extern void mgmt_be_show_xpath_registries(struct vty *vty, const char *xpath); + +#endif /* _FRR_MGMTD_BE_ADAPTER_H_ */ diff --git a/mgmtd/mgmt_be_nb.c b/mgmtd/mgmt_be_nb.c new file mode 100644 index 0000000..613272d --- /dev/null +++ b/mgmtd/mgmt_be_nb.c @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "config.h" +#include "xref.h" + +XREF_SETUP(); diff --git a/mgmtd/mgmt_ds.c b/mgmtd/mgmt_ds.c new file mode 100644 index 0000000..dabae4a --- /dev/null +++ b/mgmtd/mgmt_ds.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Datastores + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "md5.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_memory.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_history.h" +#include "mgmtd/mgmt_txn.h" +#include "libyang/libyang.h" + +#define __dbg(fmt, ...) \ + DEBUGD(&mgmt_debug_ds, "DS: %s: " fmt, __func__, ##__VA_ARGS__) +#define __log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +struct mgmt_ds_ctx { + Mgmtd__DatastoreId ds_id; + + bool locked; + uint64_t vty_session_id; /* Owner of the lock or 0 */ + + bool config_ds; + + union { + struct nb_config *cfg_root; + struct lyd_node *dnode_root; + } root; +}; + +const char *mgmt_ds_names[MGMTD_DS_MAX_ID + 1] = { + MGMTD_DS_NAME_NONE, /* MGMTD_DS_NONE */ + MGMTD_DS_NAME_RUNNING, /* MGMTD_DS_RUNNING */ + MGMTD_DS_NAME_CANDIDATE, /* MGMTD_DS_CANDIDATE */ + MGMTD_DS_NAME_OPERATIONAL, /* MGMTD_DS_OPERATIONAL */ + "Unknown/Invalid", /* MGMTD_DS_ID_MAX */ +}; + +static struct mgmt_master *mgmt_ds_mm; +static struct mgmt_ds_ctx running, candidate, oper; + +/* Dump the data tree of the specified format in the file pointed by the path */ +static int mgmt_ds_dump_in_memory(struct mgmt_ds_ctx *ds_ctx, + const char *base_xpath, LYD_FORMAT format, + struct ly_out *out) +{ + struct lyd_node *root; + uint32_t options = 0; + + if (base_xpath[0] == '\0') + root = ds_ctx->config_ds ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root; + else + root = yang_dnode_get(ds_ctx->config_ds + ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root, + base_xpath); + if (!root) + return -1; + + options = ds_ctx->config_ds ? LYD_PRINT_WD_TRIM : + LYD_PRINT_WD_EXPLICIT; + + if (base_xpath[0] == '\0') + lyd_print_all(out, root, format, options); + else + lyd_print_tree(out, root, format, options); + + return 0; +} + +static int mgmt_ds_replace_dst_with_src_ds(struct mgmt_ds_ctx *src, + struct mgmt_ds_ctx *dst) +{ + if (!src || !dst) + return -1; + + __dbg("Replacing %s with %s", mgmt_ds_id2name(dst->ds_id), + mgmt_ds_id2name(src->ds_id)); + + if (src->config_ds && dst->config_ds) + nb_config_replace(dst->root.cfg_root, src->root.cfg_root, true); + else { + assert(!src->config_ds && !dst->config_ds); + if (dst->root.dnode_root) + yang_dnode_free(dst->root.dnode_root); + dst->root.dnode_root = yang_dnode_dup(src->root.dnode_root); + } + + return 0; +} + +static int mgmt_ds_merge_src_with_dst_ds(struct mgmt_ds_ctx *src, + struct mgmt_ds_ctx *dst) +{ + int ret; + + if (!src || !dst) + return -1; + + __dbg("Merging DS %d with %d", dst->ds_id, src->ds_id); + if (src->config_ds && dst->config_ds) + ret = nb_config_merge(dst->root.cfg_root, src->root.cfg_root, + true); + else { + assert(!src->config_ds && !dst->config_ds); + ret = lyd_merge_siblings(&dst->root.dnode_root, + src->root.dnode_root, 0); + } + if (ret != 0) { + __log_err("merge failed with err: %d", ret); + return ret; + } + + return 0; +} + +static int mgmt_ds_load_cfg_from_file(const char *filepath, + struct lyd_node **dnode) +{ + LY_ERR ret; + + *dnode = NULL; + ret = lyd_parse_data_path(ly_native_ctx, filepath, LYD_JSON, + LYD_PARSE_NO_STATE | LYD_PARSE_STRICT, + LYD_VALIDATE_NO_STATE, dnode); + + if (ret != LY_SUCCESS) { + if (*dnode) + yang_dnode_free(*dnode); + return -1; + } + + return 0; +} + +void mgmt_ds_reset_candidate(void) +{ + struct lyd_node *dnode = mm->candidate_ds->root.cfg_root->dnode; + + if (dnode) + yang_dnode_free(dnode); + + dnode = yang_dnode_new(ly_native_ctx, true); + mm->candidate_ds->root.cfg_root->dnode = dnode; +} + + +int mgmt_ds_init(struct mgmt_master *mm) +{ + if (mgmt_ds_mm || mm->running_ds || mm->candidate_ds || mm->oper_ds) + assert(!"MGMTD: Call ds_init only once!"); + + /* Use Running DS from NB module??? */ + if (!running_config) + assert(!"MGMTD: Call ds_init after frr_init only!"); + + running.root.cfg_root = running_config; + running.config_ds = true; + running.ds_id = MGMTD_DS_RUNNING; + + candidate.root.cfg_root = nb_config_dup(running.root.cfg_root); + candidate.config_ds = true; + candidate.ds_id = MGMTD_DS_CANDIDATE; + + /* + * Redirect lib/vty candidate-config datastore to the global candidate + * config Ds on the MGMTD process. + */ + vty_mgmt_candidate_config = candidate.root.cfg_root; + + oper.root.dnode_root = yang_dnode_new(ly_native_ctx, true); + oper.config_ds = false; + oper.ds_id = MGMTD_DS_OPERATIONAL; + + mm->running_ds = &running; + mm->candidate_ds = &candidate; + mm->oper_ds = &oper; + mgmt_ds_mm = mm; + + return 0; +} + +void mgmt_ds_destroy(void) +{ + nb_config_free(candidate.root.cfg_root); + candidate.root.cfg_root = NULL; + + yang_dnode_free(oper.root.dnode_root); + oper.root.dnode_root = NULL; +} + +struct mgmt_ds_ctx *mgmt_ds_get_ctx_by_id(struct mgmt_master *mm, + Mgmtd__DatastoreId ds_id) +{ + switch (ds_id) { + case MGMTD_DS_CANDIDATE: + return (mm->candidate_ds); + case MGMTD_DS_RUNNING: + return (mm->running_ds); + case MGMTD_DS_OPERATIONAL: + return (mm->oper_ds); + case MGMTD_DS_NONE: + case MGMTD__DATASTORE_ID__STARTUP_DS: + case _MGMTD__DATASTORE_ID_IS_INT_SIZE: + return 0; + } + + return 0; +} + +bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx) +{ + if (!ds_ctx) + return false; + + return ds_ctx->config_ds; +} + +bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) +{ + assert(ds_ctx); + return (ds_ctx->locked && ds_ctx->vty_session_id == session_id); +} + +int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id) +{ + assert(ds_ctx); + + if (ds_ctx->locked) + return EBUSY; + + ds_ctx->locked = true; + ds_ctx->vty_session_id = session_id; + return 0; +} + +void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx) +{ + assert(ds_ctx); + if (!ds_ctx->locked) + zlog_warn( + "%s: WARNING: unlock on unlocked in DS:%s last session-id %" PRIu64, + __func__, mgmt_ds_id2name(ds_ctx->ds_id), + ds_ctx->vty_session_id); + ds_ctx->locked = 0; +} + +int mgmt_ds_copy_dss(struct mgmt_ds_ctx *src_ds_ctx, + struct mgmt_ds_ctx *dst_ds_ctx, bool updt_cmt_rec) +{ + if (mgmt_ds_replace_dst_with_src_ds(src_ds_ctx, dst_ds_ctx) != 0) + return -1; + + if (updt_cmt_rec && dst_ds_ctx->ds_id == MGMTD_DS_RUNNING) + mgmt_history_new_record(dst_ds_ctx); + + return 0; +} + +int mgmt_ds_dump_ds_to_file(char *file_name, struct mgmt_ds_ctx *ds_ctx) +{ + struct ly_out *out; + int ret = 0; + + if (ly_out_new_filepath(file_name, &out) == LY_SUCCESS) { + ret = mgmt_ds_dump_in_memory(ds_ctx, "", LYD_JSON, out); + ly_out_free(out, NULL, 0); + } + + return ret; +} + +struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx) +{ + if (!ds_ctx) + return NULL; + + return ds_ctx->config_ds ? ds_ctx->root.cfg_root : NULL; +} + +static int mgmt_walk_ds_nodes( + struct nb_config *root, const char *base_xpath, + struct lyd_node *base_dnode, + void (*mgmt_ds_node_iter_fn)(const char *xpath, struct lyd_node *node, + struct nb_node *nb_node, void *ctx), + void *ctx) +{ + /* this is 1k per recursion... */ + char xpath[MGMTD_MAX_XPATH_LEN]; + struct lyd_node *dnode; + struct nb_node *nbnode; + int ret = 0; + + assert(mgmt_ds_node_iter_fn); + + __dbg(" -- START: base xpath: '%s'", base_xpath); + + if (!base_dnode) + /* + * This function only returns the first node of a possible set + * of matches issuing a warning if more than 1 matches + */ + base_dnode = yang_dnode_get(root->dnode, base_xpath); + if (!base_dnode) + return -1; + + __dbg(" search base schema: '%s'", + lysc_path(base_dnode->schema, LYSC_PATH_LOG, xpath, + sizeof(xpath))); + + nbnode = (struct nb_node *)base_dnode->schema->priv; + (*mgmt_ds_node_iter_fn)(base_xpath, base_dnode, nbnode, ctx); + + /* + * If the base_xpath points to a leaf node we can skip the tree walk. + */ + if (base_dnode->schema->nodetype & LYD_NODE_TERM) + return 0; + + /* + * at this point the xpath matched this container node (or some parent + * and we're wildcard descending now) so by walking it's children we + * continue to change the meaning of an xpath regex to rather be a + * prefix matching path + */ + + LY_LIST_FOR (lyd_child(base_dnode), dnode) { + assert(dnode->schema && dnode->schema->priv); + + (void)lyd_path(dnode, LYD_PATH_STD, xpath, sizeof(xpath)); + + __dbg(" -- Child xpath: %s", xpath); + + ret = mgmt_walk_ds_nodes(root, xpath, dnode, + mgmt_ds_node_iter_fn, ctx); + if (ret != 0) + break; + } + + __dbg(" -- END: base xpath: '%s'", base_xpath); + + return ret; +} + +struct lyd_node *mgmt_ds_find_data_node_by_xpath(struct mgmt_ds_ctx *ds_ctx, + const char *xpath) +{ + if (!ds_ctx) + return NULL; + + return yang_dnode_get(ds_ctx->config_ds ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root, + xpath); +} + +int mgmt_ds_delete_data_nodes(struct mgmt_ds_ctx *ds_ctx, const char *xpath) +{ + struct nb_node *nb_node; + struct lyd_node *dnode, *dep_dnode; + char dep_xpath[XPATH_MAXLEN]; + + if (!ds_ctx) + return -1; + + nb_node = nb_node_find(xpath); + + dnode = yang_dnode_get(ds_ctx->config_ds + ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root, + xpath); + + if (!dnode) + /* + * Return a special error code so the caller can choose + * whether to ignore it or not. + */ + return NB_ERR_NOT_FOUND; + /* destroy dependant */ + if (nb_node && nb_node->dep_cbs.get_dependant_xpath) { + nb_node->dep_cbs.get_dependant_xpath(dnode, dep_xpath); + + dep_dnode = yang_dnode_get( + ds_ctx->config_ds ? ds_ctx->root.cfg_root->dnode + : ds_ctx->root.dnode_root, + dep_xpath); + if (dep_dnode) + lyd_free_tree(dep_dnode); + } + lyd_free_tree(dnode); + + return 0; +} + +int mgmt_ds_load_config_from_file(struct mgmt_ds_ctx *dst, + const char *file_path, bool merge) +{ + struct lyd_node *iter; + struct mgmt_ds_ctx parsed; + + if (!dst) + return -1; + + if (mgmt_ds_load_cfg_from_file(file_path, &iter) != 0) { + __log_err("Failed to load config from the file %s", file_path); + return -1; + } + + parsed.root.cfg_root = nb_config_new(iter); + parsed.config_ds = true; + parsed.ds_id = dst->ds_id; + + if (merge) + mgmt_ds_merge_src_with_dst_ds(&parsed, dst); + else + mgmt_ds_replace_dst_with_src_ds(&parsed, dst); + + nb_config_free(parsed.root.cfg_root); + + return 0; +} + +int mgmt_ds_iter_data(Mgmtd__DatastoreId ds_id, struct nb_config *root, + const char *base_xpath, + void (*mgmt_ds_node_iter_fn)(const char *xpath, + struct lyd_node *node, + struct nb_node *nb_node, + void *ctx), + void *ctx) +{ + int ret = 0; + char xpath[MGMTD_MAX_XPATH_LEN]; + struct lyd_node *base_dnode = NULL; + struct lyd_node *node; + + if (!root) + return -1; + + strlcpy(xpath, base_xpath, sizeof(xpath)); + mgmt_remove_trailing_separator(xpath, '/'); + + /* + * mgmt_ds_iter_data is the only user of mgmt_walk_ds_nodes other than + * mgmt_walk_ds_nodes itself, so we can modify the API if we would like. + * Oper-state should be kept in mind though for the prefix walk + */ + + __dbg(" -- START DS walk for DSid: %d", ds_id); + + /* If the base_xpath is empty then crawl the sibblings */ + if (xpath[0] == 0) { + base_dnode = root->dnode; + + /* get first top-level sibling */ + while (base_dnode->parent) + base_dnode = lyd_parent(base_dnode); + + while (base_dnode->prev->next) + base_dnode = base_dnode->prev; + + LY_LIST_FOR (base_dnode, node) { + ret = mgmt_walk_ds_nodes(root, xpath, node, + mgmt_ds_node_iter_fn, ctx); + } + } else + ret = mgmt_walk_ds_nodes(root, xpath, base_dnode, + mgmt_ds_node_iter_fn, ctx); + + return ret; +} + +void mgmt_ds_dump_tree(struct vty *vty, struct mgmt_ds_ctx *ds_ctx, + const char *xpath, FILE *f, LYD_FORMAT format) +{ + struct ly_out *out; + char *str; + char base_xpath[MGMTD_MAX_XPATH_LEN] = {0}; + + if (!ds_ctx) { + vty_out(vty, " >>>>> Datastore Not Initialized!\n"); + return; + } + + if (xpath) { + strlcpy(base_xpath, xpath, MGMTD_MAX_XPATH_LEN); + mgmt_remove_trailing_separator(base_xpath, '/'); + } + + if (f) + ly_out_new_file(f, &out); + else + ly_out_new_memory(&str, 0, &out); + + mgmt_ds_dump_in_memory(ds_ctx, base_xpath, format, out); + + if (!f) + vty_out(vty, "%s\n", str); + + ly_out_free(out, NULL, 0); +} + +void mgmt_ds_status_write_one(struct vty *vty, struct mgmt_ds_ctx *ds_ctx) +{ + if (!ds_ctx) { + vty_out(vty, " >>>>> Datastore Not Initialized!\n"); + return; + } + + vty_out(vty, " DS: %s\n", mgmt_ds_id2name(ds_ctx->ds_id)); + vty_out(vty, " DS-Hndl: \t\t\t%p\n", ds_ctx); + vty_out(vty, " Config: \t\t\t%s\n", + ds_ctx->config_ds ? "True" : "False"); +} + +void mgmt_ds_status_write(struct vty *vty) +{ + vty_out(vty, "MGMTD Datastores\n"); + + mgmt_ds_status_write_one(vty, mgmt_ds_mm->running_ds); + + mgmt_ds_status_write_one(vty, mgmt_ds_mm->candidate_ds); + + mgmt_ds_status_write_one(vty, mgmt_ds_mm->oper_ds); +} diff --git a/mgmtd/mgmt_ds.h b/mgmtd/mgmt_ds.h new file mode 100644 index 0000000..b8e77e3 --- /dev/null +++ b/mgmtd/mgmt_ds.h @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Datastores + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_DS_H_ +#define _FRR_MGMTD_DS_H_ + +#include "mgmt_fe_client.h" +#include "northbound.h" +#include "mgmt_defines.h" + +#include "mgmtd/mgmt_be_adapter.h" +#include "mgmtd/mgmt_fe_adapter.h" + +#define MGMTD_MAX_NUM_DSNODES_PER_BATCH 128 + +#define MGMTD_DS_NAME_MAX_LEN 32 +#define MGMTD_DS_NAME_NONE "none" +#define MGMTD_DS_NAME_RUNNING "running" +#define MGMTD_DS_NAME_CANDIDATE "candidate" +#define MGMTD_DS_NAME_OPERATIONAL "operational" + +#define FOREACH_MGMTD_DS_ID(id) \ + for ((id) = MGMTD_DS_NONE; (id) < MGMTD_DS_MAX_ID; (id)++) + +#define MGMTD_MAX_COMMIT_LIST 10 + +#define MGMTD_COMMIT_FILE_PATH(id) "%s/commit-%s.json", frr_libstatedir, id +#define MGMTD_COMMIT_INDEX_FILE_PATH "%s/commit-index.dat", frr_libstatedir + +extern struct nb_config *running_config; + +struct mgmt_ds_ctx; + +/*************************************************************** + * Global data exported + ***************************************************************/ + +extern const char *mgmt_ds_names[MGMTD_DS_MAX_ID + 1]; + +/* + * Convert datastore ID to datastore name. + * + * id + * Datastore ID. + * + * Returns: + * Datastore name. + */ +static inline const char *mgmt_ds_id2name(Mgmtd__DatastoreId id) +{ + if (id > MGMTD_DS_MAX_ID) + id = MGMTD_DS_MAX_ID; + return mgmt_ds_names[id]; +} + +/* + * Convert datastore name to datastore ID. + * + * id + * Datastore name. + * + * Returns: + * Datastore ID. + */ +static inline Mgmtd__DatastoreId mgmt_ds_name2id(const char *name) +{ + Mgmtd__DatastoreId id; + + FOREACH_MGMTD_DS_ID (id) { + if (!strncmp(mgmt_ds_names[id], name, MGMTD_DS_NAME_MAX_LEN)) + return id; + } + + return MGMTD_DS_NONE; +} + +/* + * Convert datastore ID to datastore name. + * + * similar to above funtion. + */ +static inline Mgmtd__DatastoreId mgmt_get_ds_id_by_name(const char *ds_name) +{ + if (!strncmp(ds_name, "candidate", sizeof("candidate"))) + return MGMTD_DS_CANDIDATE; + else if (!strncmp(ds_name, "running", sizeof("running"))) + return MGMTD_DS_RUNNING; + else if (!strncmp(ds_name, "operational", sizeof("operational"))) + return MGMTD_DS_OPERATIONAL; + return MGMTD_DS_NONE; +} + +/* + * Appends trail wildcard '/' '*' to a given xpath. + * + * xpath + * YANG xpath. + * + * path_len + * xpath length. + */ +static inline void mgmt_xpath_append_trail_wildcard(char *xpath, + size_t *xpath_len) +{ + if (!xpath || !xpath_len) + return; + + if (!*xpath_len) + *xpath_len = strlen(xpath); + + if (*xpath_len > 2 && *xpath_len < MGMTD_MAX_XPATH_LEN - 2) { + if (xpath[*xpath_len - 1] == '/') { + xpath[*xpath_len] = '*'; + xpath[*xpath_len + 1] = 0; + (*xpath_len)++; + } else if (xpath[*xpath_len - 1] != '*') { + xpath[*xpath_len] = '/'; + xpath[*xpath_len + 1] = '*'; + xpath[*xpath_len + 2] = 0; + (*xpath_len) += 2; + } + } +} + +/* + * Removes trail wildcard '/' '*' from a given xpath. + * + * xpath + * YANG xpath. + * + * path_len + * xpath length. + */ +static inline void mgmt_xpath_remove_trail_wildcard(char *xpath, + size_t *xpath_len) +{ + if (!xpath || !xpath_len) + return; + + if (!*xpath_len) + *xpath_len = strlen(xpath); + + if (*xpath_len > 2 && xpath[*xpath_len - 2] == '/' + && xpath[*xpath_len - 1] == '*') { + xpath[*xpath_len - 2] = 0; + (*xpath_len) -= 2; + } +} + +/* Initialise datastore */ +extern int mgmt_ds_init(struct mgmt_master *cm); + +/* Destroy datastore */ +extern void mgmt_ds_destroy(void); + +/* + * Get datastore handler by ID + * + * mm + * Management master structure. + * + * ds_id + * Datastore ID. + * + * Returns: + * Datastore context (Holds info about ID, lock, root node etc). + */ +extern struct mgmt_ds_ctx *mgmt_ds_get_ctx_by_id(struct mgmt_master *mm, + Mgmtd__DatastoreId ds_id); + +/* + * Check if a given datastore is config ds + */ +extern bool mgmt_ds_is_config(struct mgmt_ds_ctx *ds_ctx); + +/* + * Check if a given datastore is locked by a given session + */ +extern bool mgmt_ds_is_locked(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); + +/* + * Acquire write lock to a ds given a ds_handle + */ +extern int mgmt_ds_lock(struct mgmt_ds_ctx *ds_ctx, uint64_t session_id); + +/* + * Remove a lock from ds given a ds_handle + */ +extern void mgmt_ds_unlock(struct mgmt_ds_ctx *ds_ctx); + +/* + * Copy from source to destination datastore. + * + * src_ds + * Source datastore handle (ds to be copied from). + * + * dst_ds + * Destination datastore handle (ds to be copied to). + * + * update_cmd_rec + * TRUE if need to update commit record, FALSE otherwise. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_ds_copy_dss(struct mgmt_ds_ctx *src_ds_ctx, + struct mgmt_ds_ctx *dst_ds_ctx, + bool update_cmt_rec); + +/* + * Fetch northbound configuration for a given datastore context. + */ +extern struct nb_config *mgmt_ds_get_nb_config(struct mgmt_ds_ctx *ds_ctx); + +/* + * Find YANG data node given a datastore handle YANG xpath. + */ +extern struct lyd_node * +mgmt_ds_find_data_node_by_xpath(struct mgmt_ds_ctx *ds_ctx, + const char *xpath); + +/* + * Delete YANG data node given a datastore handle and YANG xpath. + */ +extern int mgmt_ds_delete_data_nodes(struct mgmt_ds_ctx *ds_ctx, + const char *xpath); + +/* + * Iterate over datastore data. + * + * ds_id + * Datastore ID.. + * + * root + * The root of the tree to iterate over. + * + * base_xpath + * Base YANG xpath from where needs to be iterated. + * + * iter_fn + * function that will be called during each iteration. + * + * ctx + * User defined opaque value normally used to pass + * reference to some user private context that will + * be passed to the iterator function provided in + * 'iter_fn'. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_ds_iter_data( + Mgmtd__DatastoreId ds_id, struct nb_config *root, + const char *base_xpath, + void (*mgmt_ds_node_iter_fn)(const char *xpath, struct lyd_node *node, + struct nb_node *nb_node, void *ctx), + void *ctx); + +/* + * Load config to datastore from a file. + * + * ds_ctx + * Datastore context. + * + * file_path + * File path of the configuration file. + * + * merge + * TRUE if you want to merge with existing config, + * FALSE if you want to replace with existing config + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_ds_load_config_from_file(struct mgmt_ds_ctx *ds_ctx, + const char *file_path, bool merge); + +/* + * Dump the data tree to a file with JSON/XML format. + * + * vty + * VTY context. + * + * ds_ctx + * Datastore context. + * + * xpath + * Base YANG xpath from where data needs to be dumped. + * + * f + * File pointer to where data to be dumped. + * + * format + * JSON/XML + */ +extern void mgmt_ds_dump_tree(struct vty *vty, struct mgmt_ds_ctx *ds_ctx, + const char *xpath, FILE *f, LYD_FORMAT format); + +/* + * Dump the complete data tree to a file with JSON format. + * + * file_name + * File path to where data to be dumped. + * + * ds + * Datastore context. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_ds_dump_ds_to_file(char *file_name, + struct mgmt_ds_ctx *ds_ctx); + +/* + * Dump information about specific datastore. + */ +extern void mgmt_ds_status_write_one(struct vty *vty, + struct mgmt_ds_ctx *ds_ctx); + +/* + * Dump information about all the datastores. + */ +extern void mgmt_ds_status_write(struct vty *vty); + + +/* + * Reset the candidate DS to empty state + */ +void mgmt_ds_reset_candidate(void); + +#endif /* _FRR_MGMTD_DS_H_ */ diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c new file mode 100644 index 0000000..fc1bde0 --- /dev/null +++ b/mgmtd/mgmt_fe_adapter.c @@ -0,0 +1,2036 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Frontend Client Connection Adapter + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include +#include "darr.h" +#include "sockopt.h" +#include "network.h" +#include "libfrr.h" +#include "mgmt_fe_client.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" +#include "mgmt_pb.h" +#include "hash.h" +#include "jhash.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_memory.h" +#include "mgmtd/mgmt_fe_adapter.h" + +#define __dbg(fmt, ...) \ + DEBUGD(&mgmt_debug_fe, "FE-ADAPTER: %s: " fmt, __func__, ##__VA_ARGS__) +#define __log_err(fmt, ...) \ + zlog_err("FE-ADAPTER: %s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +#define FOREACH_ADAPTER_IN_LIST(adapter) \ + frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter)) + +enum mgmt_session_event { + MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1, + MGMTD_FE_SESSION_SHOW_TXN_CLNUP, +}; + +struct mgmt_fe_session_ctx { + struct mgmt_fe_client_adapter *adapter; + uint64_t session_id; + uint64_t client_id; + uint64_t txn_id; + uint64_t cfg_txn_id; + uint8_t ds_locked[MGMTD_DS_MAX_ID]; + struct event *proc_cfg_txn_clnp; + struct event *proc_show_txn_clnp; + + struct mgmt_fe_sessions_item list_linkage; +}; + +DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage); + +#define FOREACH_SESSION_IN_LIST(adapter, session) \ + frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session)) + +static struct event_loop *mgmt_loop; +static struct msg_server mgmt_fe_server = {.fd = -1}; + +static struct mgmt_fe_adapters_head mgmt_fe_adapters; + +static struct hash *mgmt_fe_sessions; +static uint64_t mgmt_fe_next_session_id; + +/* Forward declarations */ +static void +mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, + enum mgmt_session_event event); + +static int +mgmt_fe_session_write_lock_ds(Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, + struct mgmt_fe_session_ctx *session) +{ + if (session->ds_locked[ds_id]) + zlog_warn("multiple lock taken by session-id: %" PRIu64 + " on DS:%s", + session->session_id, mgmt_ds_id2name(ds_id)); + else { + if (mgmt_ds_lock(ds_ctx, session->session_id)) { + __dbg("Failed to lock the DS:%s for session-id: %" PRIu64 + " from %s!", + mgmt_ds_id2name(ds_id), session->session_id, + session->adapter->name); + return -1; + } + + session->ds_locked[ds_id] = true; + __dbg("Write-Locked the DS:%s for session-id: %" PRIu64 + " from %s", + mgmt_ds_id2name(ds_id), session->session_id, + session->adapter->name); + } + + return 0; +} + +static void mgmt_fe_session_unlock_ds(Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, + struct mgmt_fe_session_ctx *session) +{ + if (!session->ds_locked[ds_id]) + zlog_warn("unlock unlocked by session-id: %" PRIu64 " on DS:%s", + session->session_id, mgmt_ds_id2name(ds_id)); + + session->ds_locked[ds_id] = false; + mgmt_ds_unlock(ds_ctx); + __dbg("Unlocked DS:%s write-locked earlier by session-id: %" PRIu64 + " from %s", + mgmt_ds_id2name(ds_id), session->session_id, + session->adapter->name); +} + +static void +mgmt_fe_session_cfg_txn_cleanup(struct mgmt_fe_session_ctx *session) +{ + /* + * Ensure any uncommitted changes in Candidate DS + * is discarded. + */ + mgmt_ds_copy_dss(mm->running_ds, mm->candidate_ds, false); + + /* + * Destroy the actual transaction created earlier. + */ + if (session->cfg_txn_id != MGMTD_TXN_ID_NONE) + mgmt_destroy_txn(&session->cfg_txn_id); +} + +static void +mgmt_fe_session_show_txn_cleanup(struct mgmt_fe_session_ctx *session) +{ + /* + * Destroy the transaction created recently. + */ + if (session->txn_id != MGMTD_TXN_ID_NONE) + mgmt_destroy_txn(&session->txn_id); +} + +static void +mgmt_fe_adapter_compute_set_cfg_timers(struct mgmt_setcfg_stats *setcfg_stats) +{ + setcfg_stats->last_exec_tm = timeval_elapsed(setcfg_stats->last_end, + setcfg_stats->last_start); + if (setcfg_stats->last_exec_tm > setcfg_stats->max_tm) + setcfg_stats->max_tm = setcfg_stats->last_exec_tm; + + if (setcfg_stats->last_exec_tm < setcfg_stats->min_tm) + setcfg_stats->min_tm = setcfg_stats->last_exec_tm; + + setcfg_stats->avg_tm = + (((setcfg_stats->avg_tm * (setcfg_stats->set_cfg_count - 1)) + + setcfg_stats->last_exec_tm) + / setcfg_stats->set_cfg_count); +} + +static void +mgmt_fe_session_compute_commit_timers(struct mgmt_commit_stats *cmt_stats) +{ + cmt_stats->last_exec_tm = + timeval_elapsed(cmt_stats->last_end, cmt_stats->last_start); + if (cmt_stats->last_exec_tm > cmt_stats->max_tm) { + cmt_stats->max_tm = cmt_stats->last_exec_tm; + cmt_stats->max_batch_cnt = cmt_stats->last_batch_cnt; + } + + if (cmt_stats->last_exec_tm < cmt_stats->min_tm) { + cmt_stats->min_tm = cmt_stats->last_exec_tm; + cmt_stats->min_batch_cnt = cmt_stats->last_batch_cnt; + } +} + +static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp) +{ + Mgmtd__DatastoreId ds_id; + struct mgmt_ds_ctx *ds_ctx; + struct mgmt_fe_session_ctx *session = *sessionp; + + if (session->adapter) { + mgmt_fe_session_cfg_txn_cleanup(session); + mgmt_fe_session_show_txn_cleanup(session); + for (ds_id = 0; ds_id < MGMTD_DS_MAX_ID; ds_id++) { + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + if (ds_ctx && session->ds_locked[ds_id]) + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, + session); + } + mgmt_fe_sessions_del(&session->adapter->fe_sessions, session); + assert(session->adapter->refcount > 1); + mgmt_fe_adapter_unlock(&session->adapter); + } + + hash_release(mgmt_fe_sessions, session); + XFREE(MTYPE_MGMTD_FE_SESSION, session); + *sessionp = NULL; +} + +static struct mgmt_fe_session_ctx * +mgmt_fe_find_session_by_client_id(struct mgmt_fe_client_adapter *adapter, + uint64_t client_id) +{ + struct mgmt_fe_session_ctx *session; + + FOREACH_SESSION_IN_LIST (adapter, session) { + if (session->client_id == client_id) { + __dbg("Found session-id %" PRIu64 + " using client-id %" PRIu64, + session->session_id, client_id); + return session; + } + } + __dbg("Session not found using client-id %" PRIu64, client_id); + return NULL; +} + +static unsigned int mgmt_fe_session_hash_key(const void *data) +{ + const struct mgmt_fe_session_ctx *session = data; + + return jhash2((uint32_t *) &session->session_id, + sizeof(session->session_id) / sizeof(uint32_t), 0); +} + +static bool mgmt_fe_session_hash_cmp(const void *d1, const void *d2) +{ + const struct mgmt_fe_session_ctx *session1 = d1; + const struct mgmt_fe_session_ctx *session2 = d2; + + return (session1->session_id == session2->session_id); +} + +static inline struct mgmt_fe_session_ctx * +mgmt_session_id2ctx(uint64_t session_id) +{ + struct mgmt_fe_session_ctx key = {0}; + struct mgmt_fe_session_ctx *session; + + if (!mgmt_fe_sessions) + return NULL; + + key.session_id = session_id; + session = hash_lookup(mgmt_fe_sessions, &key); + + return session; +} + +void mgmt_fe_adapter_toggle_client_debug(bool set) +{ + struct mgmt_fe_client_adapter *adapter; + + FOREACH_ADAPTER_IN_LIST (adapter) + adapter->conn->debug = set; +} + +static struct mgmt_fe_session_ctx *fe_adapter_session_by_txn_id(uint64_t txn_id) +{ + uint64_t session_id = mgmt_txn_get_session_id(txn_id); + + if (session_id == MGMTD_SESSION_ID_NONE) + return NULL; + return mgmt_session_id2ctx(session_id); +} + +static struct mgmt_fe_session_ctx * +mgmt_fe_create_session(struct mgmt_fe_client_adapter *adapter, + uint64_t client_id) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_fe_find_session_by_client_id(adapter, client_id); + if (session) + mgmt_fe_cleanup_session(&session); + + session = XCALLOC(MTYPE_MGMTD_FE_SESSION, + sizeof(struct mgmt_fe_session_ctx)); + assert(session); + session->client_id = client_id; + session->adapter = adapter; + session->txn_id = MGMTD_TXN_ID_NONE; + session->cfg_txn_id = MGMTD_TXN_ID_NONE; + mgmt_fe_adapter_lock(adapter); + mgmt_fe_sessions_add_tail(&adapter->fe_sessions, session); + if (!mgmt_fe_next_session_id) + mgmt_fe_next_session_id++; + session->session_id = mgmt_fe_next_session_id++; + hash_get(mgmt_fe_sessions, session, hash_alloc_intern); + + return session; +} + +static int fe_adapter_send_native_msg(struct mgmt_fe_client_adapter *adapter, + void *msg, size_t len, + bool short_circuit_ok) +{ + return msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE, msg, + len, NULL, short_circuit_ok); +} + +static int fe_adapter_send_msg(struct mgmt_fe_client_adapter *adapter, + Mgmtd__FeMessage *fe_msg, bool short_circuit_ok) +{ + return msg_conn_send_msg( + adapter->conn, MGMT_MSG_VERSION_PROTOBUF, fe_msg, + mgmtd__fe_message__get_packed_size(fe_msg), + (size_t(*)(void *, void *))mgmtd__fe_message__pack, + short_circuit_ok); +} + +static int fe_adapter_send_session_reply(struct mgmt_fe_client_adapter *adapter, + struct mgmt_fe_session_ctx *session, + bool create, bool success) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeSessionReply session_reply; + + mgmtd__fe_session_reply__init(&session_reply); + session_reply.create = create; + if (create) { + session_reply.has_client_conn_id = 1; + session_reply.client_conn_id = session->client_id; + } + session_reply.session_id = session->session_id; + session_reply.success = success; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_SESSION_REPLY; + fe_msg.session_reply = &session_reply; + + __dbg("Sending SESSION_REPLY message to MGMTD Frontend client '%s'", + adapter->name); + + return fe_adapter_send_msg(adapter, &fe_msg, true); +} + +static int fe_adapter_send_lockds_reply(struct mgmt_fe_session_ctx *session, + Mgmtd__DatastoreId ds_id, + uint64_t req_id, bool lock_ds, + bool success, const char *error_if_any) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeLockDsReply lockds_reply; + bool scok = session->adapter->conn->is_short_circuit; + + assert(session->adapter); + + mgmtd__fe_lock_ds_reply__init(&lockds_reply); + lockds_reply.session_id = session->session_id; + lockds_reply.ds_id = ds_id; + lockds_reply.req_id = req_id; + lockds_reply.lock = lock_ds; + lockds_reply.success = success; + if (error_if_any) + lockds_reply.error_if_any = (char *)error_if_any; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REPLY; + fe_msg.lockds_reply = &lockds_reply; + + __dbg("Sending LOCK_DS_REPLY message to MGMTD Frontend client '%s' scok: %d", + session->adapter->name, scok); + + return fe_adapter_send_msg(session->adapter, &fe_msg, scok); +} + +static int fe_adapter_send_set_cfg_reply(struct mgmt_fe_session_ctx *session, + Mgmtd__DatastoreId ds_id, + uint64_t req_id, bool success, + const char *error_if_any, + bool implicit_commit) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeSetConfigReply setcfg_reply; + + assert(session->adapter); + + if (implicit_commit && session->cfg_txn_id) + mgmt_fe_session_register_event( + session, MGMTD_FE_SESSION_CFG_TXN_CLNUP); + + mgmtd__fe_set_config_reply__init(&setcfg_reply); + setcfg_reply.session_id = session->session_id; + setcfg_reply.ds_id = ds_id; + setcfg_reply.req_id = req_id; + setcfg_reply.success = success; + setcfg_reply.implicit_commit = implicit_commit; + if (error_if_any) + setcfg_reply.error_if_any = (char *)error_if_any; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REPLY; + fe_msg.setcfg_reply = &setcfg_reply; + + __dbg("Sending SETCFG_REPLY message to MGMTD Frontend client '%s'", + session->adapter->name); + + if (implicit_commit) { + if (mm->perf_stats_en) + gettimeofday(&session->adapter->cmt_stats.last_end, + NULL); + mgmt_fe_session_compute_commit_timers( + &session->adapter->cmt_stats); + } + + if (mm->perf_stats_en) + gettimeofday(&session->adapter->setcfg_stats.last_end, NULL); + mgmt_fe_adapter_compute_set_cfg_timers(&session->adapter->setcfg_stats); + + return fe_adapter_send_msg(session->adapter, &fe_msg, false); +} + +static int fe_adapter_send_commit_cfg_reply( + struct mgmt_fe_session_ctx *session, Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, uint64_t req_id, enum mgmt_result result, + bool validate_only, const char *error_if_any) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeCommitConfigReply commcfg_reply; + + assert(session->adapter); + + mgmtd__fe_commit_config_reply__init(&commcfg_reply); + commcfg_reply.session_id = session->session_id; + commcfg_reply.src_ds_id = src_ds_id; + commcfg_reply.dst_ds_id = dst_ds_id; + commcfg_reply.req_id = req_id; + commcfg_reply.success = + (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES) + ? true + : false; + commcfg_reply.validate_only = validate_only; + if (error_if_any) + commcfg_reply.error_if_any = (char *)error_if_any; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REPLY; + fe_msg.commcfg_reply = &commcfg_reply; + + __dbg("Sending COMMIT_CONFIG_REPLY message to MGMTD Frontend client '%s'", + session->adapter->name); + + /* + * Cleanup the CONFIG transaction associated with this session. + */ + if (session->cfg_txn_id + && ((result == MGMTD_SUCCESS && !validate_only) + || (result == MGMTD_NO_CFG_CHANGES))) + mgmt_fe_session_register_event( + session, MGMTD_FE_SESSION_CFG_TXN_CLNUP); + + if (mm->perf_stats_en) + gettimeofday(&session->adapter->cmt_stats.last_end, NULL); + mgmt_fe_session_compute_commit_timers(&session->adapter->cmt_stats); + return fe_adapter_send_msg(session->adapter, &fe_msg, false); +} + +static int fe_adapter_send_get_reply(struct mgmt_fe_session_ctx *session, + Mgmtd__DatastoreId ds_id, uint64_t req_id, + bool success, Mgmtd__YangDataReply *data, + const char *error_if_any) +{ + Mgmtd__FeMessage fe_msg; + Mgmtd__FeGetReply get_reply; + + assert(session->adapter); + + mgmtd__fe_get_reply__init(&get_reply); + get_reply.session_id = session->session_id; + get_reply.ds_id = ds_id; + get_reply.req_id = req_id; + get_reply.success = success; + get_reply.data = data; + if (error_if_any) + get_reply.error_if_any = (char *)error_if_any; + + mgmtd__fe_message__init(&fe_msg); + fe_msg.message_case = MGMTD__FE_MESSAGE__MESSAGE_GET_REPLY; + fe_msg.get_reply = &get_reply; + + __dbg("Sending GET_REPLY message to MGMTD Frontend client '%s'", + session->adapter->name); + + /* + * Cleanup the SHOW transaction associated with this session. + */ + if (session->txn_id && (!success || (data && data->next_indx < 0))) + mgmt_fe_session_register_event(session, + MGMTD_FE_SESSION_SHOW_TXN_CLNUP); + + return fe_adapter_send_msg(session->adapter, &fe_msg, false); +} + +static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, + uint64_t req_id, bool short_circuit_ok, + int16_t error, const char *errfmt, ...) + PRINTFRR(5, 6); + +static int fe_adapter_send_error(struct mgmt_fe_session_ctx *session, + uint64_t req_id, bool short_circuit_ok, + int16_t error, const char *errfmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, errfmt); + ret = vmgmt_msg_native_send_error(session->adapter->conn, + session->session_id, req_id, + short_circuit_ok, error, errfmt, ap); + va_end(ap); + + return ret; +} + + +static void mgmt_fe_session_cfg_txn_clnup(struct event *thread) +{ + struct mgmt_fe_session_ctx *session; + + session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); + + mgmt_fe_session_cfg_txn_cleanup(session); +} + +static void mgmt_fe_session_show_txn_clnup(struct event *thread) +{ + struct mgmt_fe_session_ctx *session; + + session = (struct mgmt_fe_session_ctx *)EVENT_ARG(thread); + + mgmt_fe_session_show_txn_cleanup(session); +} + +static void +mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session, + enum mgmt_session_event event) +{ + struct timeval tv = {.tv_sec = 0, + .tv_usec = MGMTD_FE_MSG_PROC_DELAY_USEC}; + + switch (event) { + case MGMTD_FE_SESSION_CFG_TXN_CLNUP: + event_add_timer_tv(mgmt_loop, mgmt_fe_session_cfg_txn_clnup, + session, &tv, &session->proc_cfg_txn_clnp); + break; + case MGMTD_FE_SESSION_SHOW_TXN_CLNUP: + event_add_timer_tv(mgmt_loop, mgmt_fe_session_show_txn_clnup, + session, &tv, &session->proc_show_txn_clnp); + break; + } +} + +static struct mgmt_fe_client_adapter * +mgmt_fe_find_adapter_by_fd(int conn_fd) +{ + struct mgmt_fe_client_adapter *adapter; + + FOREACH_ADAPTER_IN_LIST (adapter) { + if (adapter->conn->fd == conn_fd) + return adapter; + } + + return NULL; +} + +static void mgmt_fe_adapter_delete(struct mgmt_fe_client_adapter *adapter) +{ + struct mgmt_fe_session_ctx *session; + __dbg("deleting client adapter '%s'", adapter->name); + + /* TODO: notify about client disconnect for appropriate cleanup */ + FOREACH_SESSION_IN_LIST (adapter, session) + mgmt_fe_cleanup_session(&session); + mgmt_fe_sessions_fini(&adapter->fe_sessions); + + assert(adapter->refcount == 1); + mgmt_fe_adapter_unlock(&adapter); +} + +static int mgmt_fe_adapter_notify_disconnect(struct msg_conn *conn) +{ + struct mgmt_fe_client_adapter *adapter = conn->user; + + __dbg("notify disconnect for client adapter '%s'", adapter->name); + + mgmt_fe_adapter_delete(adapter); + + return 0; +} + +/* + * Purge any old connections that share the same client name with `adapter` + */ +static void +mgmt_fe_adapter_cleanup_old_conn(struct mgmt_fe_client_adapter *adapter) +{ + struct mgmt_fe_client_adapter *old; + + FOREACH_ADAPTER_IN_LIST (old) { + if (old == adapter) + continue; + if (strncmp(adapter->name, old->name, sizeof(adapter->name))) + continue; + + __dbg("Client '%s' (FD:%d) seems to have reconnected. Removing old connection (FD:%d)", + adapter->name, adapter->conn->fd, old->conn->fd); + msg_conn_disconnect(old->conn, false); + } +} + +static int +mgmt_fe_session_handle_lockds_req_msg(struct mgmt_fe_session_ctx *session, + Mgmtd__FeLockDsReq *lockds_req) +{ + struct mgmt_ds_ctx *ds_ctx; + + if (lockds_req->ds_id != MGMTD_DS_CANDIDATE && + lockds_req->ds_id != MGMTD_DS_RUNNING) { + fe_adapter_send_lockds_reply( + session, lockds_req->ds_id, lockds_req->req_id, + lockds_req->lock, false, + "Lock/Unlock on DS other than candidate or running DS not supported"); + return -1; + } + + ds_ctx = mgmt_ds_get_ctx_by_id(mm, lockds_req->ds_id); + if (!ds_ctx) { + fe_adapter_send_lockds_reply(session, lockds_req->ds_id, + lockds_req->req_id, + lockds_req->lock, false, + "Failed to retrieve handle for DS!"); + return -1; + } + + if (lockds_req->lock) { + if (mgmt_fe_session_write_lock_ds(lockds_req->ds_id, ds_ctx, + session)) { + fe_adapter_send_lockds_reply( + session, lockds_req->ds_id, lockds_req->req_id, + lockds_req->lock, false, + "Lock already taken on DS by another session!"); + return -1; + } + } else { + if (!session->ds_locked[lockds_req->ds_id]) { + fe_adapter_send_lockds_reply( + session, lockds_req->ds_id, lockds_req->req_id, + lockds_req->lock, false, + "Lock on DS was not taken by this session!"); + return 0; + } + + mgmt_fe_session_unlock_ds(lockds_req->ds_id, ds_ctx, session); + } + + if (fe_adapter_send_lockds_reply(session, lockds_req->ds_id, + lockds_req->req_id, lockds_req->lock, + true, NULL) != 0) { + __dbg("Failed to send LOCK_DS_REPLY for DS %u session-id: %" PRIu64 + " from %s", + lockds_req->ds_id, session->session_id, + session->adapter->name); + } + + return 0; +} + +/* + * TODO: this function has too many conditionals relating to complex error + * conditions. It needs to be simplified and these complex error conditions + * probably need to just disconnect the client with a suitably loud log message. + */ +static int +mgmt_fe_session_handle_setcfg_req_msg(struct mgmt_fe_session_ctx *session, + Mgmtd__FeSetConfigReq *setcfg_req) +{ + struct mgmt_ds_ctx *ds_ctx, *dst_ds_ctx = NULL; + bool txn_created = false; + + if (mm->perf_stats_en) + gettimeofday(&session->adapter->setcfg_stats.last_start, NULL); + + /* MGMTD currently only supports editing the candidate DS. */ + if (setcfg_req->ds_id != MGMTD_DS_CANDIDATE) { + fe_adapter_send_set_cfg_reply( + session, setcfg_req->ds_id, setcfg_req->req_id, false, + "Set-Config on datastores other than Candidate DS not supported", + setcfg_req->implicit_commit); + return 0; + } + ds_ctx = mgmt_ds_get_ctx_by_id(mm, setcfg_req->ds_id); + assert(ds_ctx); + + /* MGMTD currently only supports targetting the running DS. */ + if (setcfg_req->implicit_commit && + setcfg_req->commit_ds_id != MGMTD_DS_RUNNING) { + fe_adapter_send_set_cfg_reply( + session, setcfg_req->ds_id, setcfg_req->req_id, false, + "Implicit commit on datastores other than running DS not supported", + setcfg_req->implicit_commit); + return 0; + } + dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, setcfg_req->commit_ds_id); + assert(dst_ds_ctx); + + /* User should have write lock to change the DS */ + if (!session->ds_locked[setcfg_req->ds_id]) { + fe_adapter_send_set_cfg_reply(session, setcfg_req->ds_id, + setcfg_req->req_id, false, + "Candidate DS is not locked", + setcfg_req->implicit_commit); + return 0; + } + + if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) { + /* Start a CONFIG Transaction (if not started already) */ + session->cfg_txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_CONFIG); + if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_set_cfg_reply( + session, setcfg_req->ds_id, setcfg_req->req_id, + false, + "Failed to create a Configuration session!", + setcfg_req->implicit_commit); + return 0; + } + txn_created = true; + + __dbg("Created new Config txn-id: %" PRIu64 + " for session-id %" PRIu64, + session->cfg_txn_id, session->session_id); + } else { + __dbg("Config txn-id: %" PRIu64 " for session-id: %" PRIu64 + " already created", + session->cfg_txn_id, session->session_id); + + if (setcfg_req->implicit_commit) { + /* + * In this scenario need to skip cleanup of the txn, + * so setting implicit commit to false. + */ + fe_adapter_send_set_cfg_reply( + session, setcfg_req->ds_id, setcfg_req->req_id, + false, + "A Configuration transaction is already in progress!", + false); + return 0; + } + } + + /* Create the SETConfig request under the transaction. */ + if (mgmt_txn_send_set_config_req(session->cfg_txn_id, setcfg_req->req_id, + setcfg_req->ds_id, ds_ctx, + setcfg_req->data, setcfg_req->n_data, + setcfg_req->implicit_commit, + setcfg_req->commit_ds_id, + dst_ds_ctx) != 0) { + fe_adapter_send_set_cfg_reply(session, setcfg_req->ds_id, + setcfg_req->req_id, false, + "Request processing for SET-CONFIG failed!", + setcfg_req->implicit_commit); + + /* delete transaction if we just created it */ + if (txn_created) + mgmt_destroy_txn(&session->cfg_txn_id); + } + + return 0; +} + +static int mgmt_fe_session_handle_get_req_msg(struct mgmt_fe_session_ctx *session, + Mgmtd__FeGetReq *get_req) +{ + struct mgmt_ds_ctx *ds_ctx; + struct nb_config *cfg_root = NULL; + Mgmtd__DatastoreId ds_id = get_req->ds_id; + uint64_t req_id = get_req->req_id; + + if (ds_id != MGMTD_DS_CANDIDATE && ds_id != MGMTD_DS_RUNNING) { + fe_adapter_send_get_reply(session, ds_id, req_id, false, NULL, + "get-req on unsupported datastore"); + return 0; + } + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + assert(ds_ctx); + + if (session->txn_id == MGMTD_TXN_ID_NONE) { + /* + * Start a SHOW Transaction (if not started already) + */ + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_SHOW); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_get_reply(session, ds_id, req_id, false, + NULL, + "Failed to create a Show transaction!"); + return -1; + } + + __dbg("Created new show txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); + } else { + fe_adapter_send_get_reply(session, ds_id, req_id, false, NULL, + "Request processing for GET failed!"); + __dbg("Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); + return -1; + } + + /* + * Get a copy of the datastore config root, avoids locking. + */ + cfg_root = nb_config_dup(mgmt_ds_get_nb_config(ds_ctx)); + + /* + * Create a GET request under the transaction. + */ + if (mgmt_txn_send_get_req(session->txn_id, req_id, ds_id, cfg_root, + get_req->data, get_req->n_data)) { + fe_adapter_send_get_reply(session, ds_id, req_id, false, NULL, + "Request processing for GET failed!"); + + goto failed; + } + + return 0; +failed: + if (cfg_root) + nb_config_free(cfg_root); + /* + * Destroy the transaction created recently. + */ + if (session->txn_id != MGMTD_TXN_ID_NONE) + mgmt_destroy_txn(&session->txn_id); + + return -1; +} + + +static int mgmt_fe_session_handle_commit_config_req_msg( + struct mgmt_fe_session_ctx *session, + Mgmtd__FeCommitConfigReq *commcfg_req) +{ + struct mgmt_ds_ctx *src_ds_ctx, *dst_ds_ctx; + + if (mm->perf_stats_en) + gettimeofday(&session->adapter->cmt_stats.last_start, NULL); + session->adapter->cmt_stats.commit_cnt++; + + /* Validate source and dest DS */ + if (commcfg_req->src_ds_id != MGMTD_DS_CANDIDATE || + commcfg_req->dst_ds_id != MGMTD_DS_RUNNING) { + fe_adapter_send_commit_cfg_reply( + session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id, + commcfg_req->req_id, MGMTD_INTERNAL_ERROR, + commcfg_req->validate_only, + "Source/Dest for commit must be candidate/running DS"); + return 0; + } + src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, commcfg_req->src_ds_id); + assert(src_ds_ctx); + dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, commcfg_req->dst_ds_id); + assert(dst_ds_ctx); + + /* User should have lock on both source and dest DS */ + if (!session->ds_locked[commcfg_req->dst_ds_id] || + !session->ds_locked[commcfg_req->src_ds_id]) { + fe_adapter_send_commit_cfg_reply( + session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id, + commcfg_req->req_id, MGMTD_DS_LOCK_FAILED, + commcfg_req->validate_only, + "Commit requires lock on candidate and/or running DS"); + return 0; + } + + if (session->cfg_txn_id == MGMTD_TXN_ID_NONE) { + /* as we have the lock no-one else should have a config txn */ + assert(!mgmt_config_txn_in_progress()); + + /* + * Start a CONFIG Transaction (if not started already) + */ + session->cfg_txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_CONFIG); + if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_commit_cfg_reply( + session, commcfg_req->src_ds_id, + commcfg_req->dst_ds_id, commcfg_req->req_id, + MGMTD_INTERNAL_ERROR, commcfg_req->validate_only, + "Failed to create a Configuration session!"); + return 0; + } + __dbg("Created txn-id: %" PRIu64 " for session-id %" PRIu64 + " for COMMIT-CFG-REQ", + session->cfg_txn_id, session->session_id); + } + + /* + * Create COMMITConfig request under the transaction + */ + if (mgmt_txn_send_commit_config_req(session->cfg_txn_id, + commcfg_req->req_id, + commcfg_req->src_ds_id, src_ds_ctx, + commcfg_req->dst_ds_id, dst_ds_ctx, + commcfg_req->validate_only, + commcfg_req->abort, false, + NULL) != 0) { + fe_adapter_send_commit_cfg_reply( + session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id, + commcfg_req->req_id, MGMTD_INTERNAL_ERROR, + commcfg_req->validate_only, + "Request processing for COMMIT-CONFIG failed!"); + return 0; + } + + return 0; +} + +static int +mgmt_fe_adapter_handle_msg(struct mgmt_fe_client_adapter *adapter, + Mgmtd__FeMessage *fe_msg) +{ + struct mgmt_fe_session_ctx *session; + + /* + * protobuf-c adds a max size enum with an internal, and changing by + * version, name; cast to an int to avoid unhandled enum warnings + */ + switch ((int)fe_msg->message_case) { + case MGMTD__FE_MESSAGE__MESSAGE_REGISTER_REQ: + __dbg("Got REGISTER_REQ from '%s'", + fe_msg->register_req->client_name); + + if (strlen(fe_msg->register_req->client_name)) { + strlcpy(adapter->name, + fe_msg->register_req->client_name, + sizeof(adapter->name)); + mgmt_fe_adapter_cleanup_old_conn(adapter); + } + break; + case MGMTD__FE_MESSAGE__MESSAGE_SESSION_REQ: + if (fe_msg->session_req->create + && fe_msg->session_req->id_case + == MGMTD__FE_SESSION_REQ__ID_CLIENT_CONN_ID) { + __dbg("Got SESSION_REQ (create) for client-id %" PRIu64 + " from '%s'", + fe_msg->session_req->client_conn_id, + adapter->name); + + session = mgmt_fe_create_session( + adapter, fe_msg->session_req->client_conn_id); + fe_adapter_send_session_reply(adapter, session, true, + session ? true : false); + } else if ( + !fe_msg->session_req->create + && fe_msg->session_req->id_case + == MGMTD__FE_SESSION_REQ__ID_SESSION_ID) { + __dbg("Got SESSION_REQ (destroy) for session-id %" PRIu64 + "from '%s'", + fe_msg->session_req->session_id, adapter->name); + + session = mgmt_session_id2ctx( + fe_msg->session_req->session_id); + fe_adapter_send_session_reply(adapter, session, false, + true); + mgmt_fe_cleanup_session(&session); + } + break; + case MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REQ: + session = mgmt_session_id2ctx( + fe_msg->lockds_req->session_id); + __dbg("Got LOCKDS_REQ (%sLOCK) for DS:%s for session-id %" PRIu64 + " from '%s'", + fe_msg->lockds_req->lock ? "" : "UN", + mgmt_ds_id2name(fe_msg->lockds_req->ds_id), + fe_msg->lockds_req->session_id, adapter->name); + mgmt_fe_session_handle_lockds_req_msg( + session, fe_msg->lockds_req); + break; + case MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REQ: + session = mgmt_session_id2ctx( + fe_msg->setcfg_req->session_id); + session->adapter->setcfg_stats.set_cfg_count++; + __dbg("Got SETCFG_REQ (%d Xpaths, Implicit:%c) on DS:%s for session-id %" PRIu64 + " from '%s'", + (int)fe_msg->setcfg_req->n_data, + fe_msg->setcfg_req->implicit_commit ? 'T' : 'F', + mgmt_ds_id2name(fe_msg->setcfg_req->ds_id), + fe_msg->setcfg_req->session_id, adapter->name); + + mgmt_fe_session_handle_setcfg_req_msg( + session, fe_msg->setcfg_req); + break; + case MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REQ: + session = mgmt_session_id2ctx( + fe_msg->commcfg_req->session_id); + __dbg("Got COMMCFG_REQ for src-DS:%s dst-DS:%s (Abort:%c) on session-id %" PRIu64 + " from '%s'", + mgmt_ds_id2name(fe_msg->commcfg_req->src_ds_id), + mgmt_ds_id2name(fe_msg->commcfg_req->dst_ds_id), + fe_msg->commcfg_req->abort ? 'T' : 'F', + fe_msg->commcfg_req->session_id, adapter->name); + mgmt_fe_session_handle_commit_config_req_msg( + session, fe_msg->commcfg_req); + break; + case MGMTD__FE_MESSAGE__MESSAGE_GET_REQ: + session = mgmt_session_id2ctx(fe_msg->get_req->session_id); + __dbg("Got GET_REQ for DS:%s (xpaths: %d) on session-id %" PRIu64 + " from '%s'", + mgmt_ds_id2name(fe_msg->get_req->ds_id), + (int)fe_msg->get_req->n_data, fe_msg->get_req->session_id, + adapter->name); + mgmt_fe_session_handle_get_req_msg(session, fe_msg->get_req); + break; + case MGMTD__FE_MESSAGE__MESSAGE_NOTIFY_DATA_REQ: + case MGMTD__FE_MESSAGE__MESSAGE_REGNOTIFY_REQ: + __log_err("Got unhandled message of type %u from '%s'", + fe_msg->message_case, adapter->name); + /* + * TODO: Add handling code in future. + */ + break; + /* + * NOTE: The following messages are always sent from MGMTD to + * Frontend clients only and/or need not be handled on MGMTd. + */ + case MGMTD__FE_MESSAGE__MESSAGE_SESSION_REPLY: + case MGMTD__FE_MESSAGE__MESSAGE_LOCKDS_REPLY: + case MGMTD__FE_MESSAGE__MESSAGE_SETCFG_REPLY: + case MGMTD__FE_MESSAGE__MESSAGE_COMMCFG_REPLY: + case MGMTD__FE_MESSAGE__MESSAGE_GET_REPLY: + case MGMTD__FE_MESSAGE__MESSAGE__NOT_SET: + default: + /* + * A 'default' case is being added contrary to the + * FRR code guidelines to take care of build + * failures on certain build systems (courtesy of + * the proto-c package). + */ + break; + } + + return 0; +} + +/** + * Send result of get-tree request back to the FE client. + * + * Args: + * session: the session. + * req_id: the request ID. + * short_circuit_ok: if allowed to short circuit the message. + * result_format: LYD_FORMAT for the sent output. + * tree: the tree to send, can be NULL which will send an empty tree. + * partial_error: if an error occurred during gathering results. + * + * Return: + * Any error that occurs -- the message is likely not sent if non-zero. + */ +static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, + uint64_t req_id, bool short_circuit_ok, + uint8_t result_type, uint32_t wd_options, + const struct lyd_node *tree, + int partial_error) + +{ + struct mgmt_msg_tree_data *msg; + uint8_t **darrp = NULL; + int ret = 0; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, + MTYPE_MSG_NATIVE_TREE_DATA); + msg->refer_id = session->session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_TREE_DATA; + msg->partial_error = partial_error; + msg->result_type = result_type; + + darrp = mgmt_msg_native_get_darrp(msg); + ret = yang_print_tree_append(darrp, tree, result_type, + (wd_options | LYD_PRINT_WITHSIBLINGS)); + if (ret != LY_SUCCESS) { + __log_err("Error building get-tree result for client %s session-id %" PRIu64 + " req-id %" PRIu64 " scok %d result type %u", + session->adapter->name, session->session_id, req_id, + short_circuit_ok, result_type); + goto done; + } + + __dbg("Sending get-tree result from adapter %s to session-id %" PRIu64 + " req-id %" PRIu64 " scok %d result type %u len %u", + session->adapter->name, session->session_id, req_id, + short_circuit_ok, result_type, mgmt_msg_native_get_msg_len(msg)); + + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), + short_circuit_ok); +done: + mgmt_msg_native_free_msg(msg); + + return ret; +} + +static int fe_adapter_send_rpc_reply(struct mgmt_fe_session_ctx *session, + uint64_t req_id, uint8_t result_type, + const struct lyd_node *result) +{ + struct mgmt_msg_rpc_reply *msg; + uint8_t **darrp = NULL; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc_reply, 0, + MTYPE_MSG_NATIVE_RPC_REPLY); + msg->refer_id = session->session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC_REPLY; + msg->result_type = result_type; + + if (result) { + darrp = mgmt_msg_native_get_darrp(msg); + ret = yang_print_tree_append(darrp, result, result_type, 0); + if (ret != LY_SUCCESS) { + __log_err("Error building rpc-reply result for client %s session-id %" PRIu64 + " req-id %" PRIu64 " result type %u", + session->adapter->name, session->session_id, + req_id, result_type); + goto done; + } + } + + __dbg("Sending rpc-reply from adapter %s to session-id %" PRIu64 + " req-id %" PRIu64 " len %u", + session->adapter->name, session->session_id, req_id, + mgmt_msg_native_get_msg_len(msg)); + + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), + false); +done: + mgmt_msg_native_free_msg(msg); + + return ret; +} + +static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session, + uint64_t req_id, const char *xpath) +{ + struct mgmt_msg_edit_reply *msg; + int ret; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0, + MTYPE_MSG_NATIVE_EDIT_REPLY); + msg->refer_id = session->session_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_EDIT_REPLY; + + mgmt_msg_native_xpath_encode(msg, xpath); + + __dbg("Sending edit-reply from adapter %s to session-id %" PRIu64 + " req-id %" PRIu64 " len %u", + session->adapter->name, session->session_id, req_id, + mgmt_msg_native_get_msg_len(msg)); + + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), + false); + mgmt_msg_native_free_msg(msg); + + return ret; +} + +/** + * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client. + * @session: the client session. + * @msg_raw: the message data. + * @msg_len: the length of the message data. + */ +static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, + void *__msg, size_t msg_len) +{ + struct mgmt_msg_get_data *msg = __msg; + struct lysc_node **snodes = NULL; + char *xpath_resolved = NULL; + uint64_t req_id = msg->req_id; + Mgmtd__DatastoreId ds_id; + uint64_t clients; + uint32_t wd_options; + bool simple_xpath; + LY_ERR err; + int ret; + + __dbg("Received get-data request from client %s for session-id %" PRIu64 + " req-id %" PRIu64, + session->adapter->name, session->session_id, msg->req_id); + + if (!MGMT_MSG_VALIDATE_NUL_TERM(msg, msg_len)) { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid message rcvd from session-id: %" PRIu64, + session->session_id); + goto done; + } + + if (session->txn_id != MGMTD_TXN_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); + goto done; + } + + switch (msg->defaults) { + case GET_DATA_DEFAULTS_EXPLICIT: + wd_options = LYD_PRINT_WD_EXPLICIT; + break; + case GET_DATA_DEFAULTS_TRIM: + wd_options = LYD_PRINT_WD_TRIM; + break; + case GET_DATA_DEFAULTS_ALL: + wd_options = LYD_PRINT_WD_ALL; + break; + case GET_DATA_DEFAULTS_ALL_ADD_TAG: + wd_options = LYD_PRINT_WD_IMPL_TAG; + break; + default: + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid defaults value %u for session-id: %" PRIu64, + msg->defaults, session->session_id); + goto done; + } + + switch (msg->datastore) { + case MGMT_MSG_DATASTORE_CANDIDATE: + ds_id = MGMTD_DS_CANDIDATE; + break; + case MGMT_MSG_DATASTORE_RUNNING: + ds_id = MGMTD_DS_RUNNING; + break; + case MGMT_MSG_DATASTORE_OPERATIONAL: + ds_id = MGMTD_DS_OPERATIONAL; + break; + default: + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Unsupported datastore %" PRIu8 + " requested from session-id: %" PRIu64, + msg->datastore, session->session_id); + goto done; + } + + err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes, + &simple_xpath); + if (err) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "XPath doesn't resolve for session-id: %" PRIu64, + session->session_id); + goto done; + } + darr_free(snodes); + + clients = mgmt_be_interested_clients(msg->xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_OPER); + if (!clients && !CHECK_FLAG(msg->flags, GET_DATA_FLAG_CONFIG)) { + __dbg("No backends provide xpath: %s for txn-id: %" PRIu64 + " session-id: %" PRIu64, + msg->xpath, session->txn_id, session->session_id); + + fe_adapter_send_tree_data(session, req_id, false, + msg->result_type, wd_options, NULL, 0); + goto done; + } + + /* Start a SHOW Transaction */ + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_SHOW); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "failed to create a 'show' txn"); + goto done; + } + + __dbg("Created new show txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->txn_id, session->session_id); + + /* Create a GET-TREE request under the transaction */ + ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients, + ds_id, msg->result_type, msg->flags, + wd_options, simple_xpath, msg->xpath); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->txn_id); + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "failed to create a 'show' txn"); + } +done: + darr_free(snodes); + darr_free(xpath_resolved); +} + +static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session, + void *__msg, size_t msg_len) +{ + struct mgmt_msg_edit *msg = __msg; + Mgmtd__DatastoreId ds_id, rds_id; + struct mgmt_ds_ctx *ds_ctx, *rds_ctx; + const char *xpath, *data; + bool lock, commit; + int ret; + + if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) { + fe_adapter_send_error(session, msg->req_id, false, -EINVAL, + "Unsupported datastore"); + return; + } + + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath) { + fe_adapter_send_error(session, msg->req_id, false, -EINVAL, + "Invalid message"); + return; + } + + ds_id = MGMTD_DS_CANDIDATE; + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + assert(ds_ctx); + + rds_id = MGMTD_DS_RUNNING; + rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); + assert(rds_ctx); + + lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK); + commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT); + + if (lock) { + if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) { + fe_adapter_send_error(session, msg->req_id, false, + -EBUSY, + "Candidate DS is locked by another session"); + return; + } + + if (commit) { + if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx, + session)) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, + session); + fe_adapter_send_error( + session, msg->req_id, false, -EBUSY, + "Running DS is locked by another session"); + return; + } + } + } else { + if (!session->ds_locked[ds_id]) { + fe_adapter_send_error(session, msg->req_id, false, + -EBUSY, + "Candidate DS is not locked"); + return; + } + + if (commit) { + if (!session->ds_locked[rds_id]) { + fe_adapter_send_error(session, msg->req_id, + false, -EBUSY, + "Running DS is not locked"); + return; + } + } + } + + session->cfg_txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_CONFIG); + if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) { + if (lock) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + if (commit) + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, + session); + } + fe_adapter_send_error(session, msg->req_id, false, -EBUSY, + "Failed to create a configuration transaction"); + return; + } + + __dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->cfg_txn_id, session->session_id); + + ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id, + ds_ctx, rds_id, rds_ctx, lock, commit, + msg->request_type, msg->flags, msg->operation, + xpath, data); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->cfg_txn_id); + if (lock) { + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + if (commit) + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, + session); + } + fe_adapter_send_error(session, msg->req_id, false, -EBUSY, + "Failed to create a configuration transaction"); + } +} + +/** + * fe_adapter_handle_rpc() - Handle an RPC message from an FE client. + * @session: the client session. + * @msg_raw: the message data. + * @msg_len: the length of the message data. + */ +static void fe_adapter_handle_rpc(struct mgmt_fe_session_ctx *session, + void *__msg, size_t msg_len) +{ + struct mgmt_msg_rpc *msg = __msg; + const struct lysc_node *snode; + const char *xpath, *data; + uint64_t req_id = msg->req_id; + uint64_t clients; + int ret; + + __dbg("Received RPC request from client %s for session-id %" PRIu64 + " req-id %" PRIu64, + session->adapter->name, session->session_id, msg->req_id); + + xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data); + if (!xpath) { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid message"); + return; + } + + if (session->txn_id != MGMTD_TXN_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Transaction in progress txn-id: %" PRIu64 + " for session-id: %" PRIu64, + session->txn_id, session->session_id); + return; + } + + snode = lys_find_path(ly_native_ctx, NULL, xpath, 0); + if (!snode) { + fe_adapter_send_error(session, req_id, false, -ENOENT, + "No such path: %s", xpath); + return; + } + + if (snode->nodetype != LYS_RPC && snode->nodetype != LYS_ACTION) { + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Not an RPC or action path: %s", xpath); + return; + } + + clients = mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_RPC); + if (!clients) { + __dbg("No backends implement xpath: %s for txn-id: %" PRIu64 + " session-id: %" PRIu64, + xpath, session->txn_id, session->session_id); + + fe_adapter_send_error(session, req_id, false, -ENOENT, + "No backends implement xpath: %s", xpath); + return; + } + + /* Start a RPC Transaction */ + session->txn_id = mgmt_create_txn(session->session_id, + MGMTD_TXN_TYPE_RPC); + if (session->txn_id == MGMTD_SESSION_ID_NONE) { + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Failed to create an RPC transaction"); + return; + } + + __dbg("Created new rpc txn-id: %" PRIu64 " for session-id: %" PRIu64, + session->txn_id, session->session_id); + + /* Create an RPC request under the transaction */ + ret = mgmt_txn_send_rpc(session->txn_id, req_id, clients, + msg->request_type, xpath, data, + mgmt_msg_native_data_len_decode(msg, msg_len)); + if (ret) { + /* destroy the just created txn */ + mgmt_destroy_txn(&session->txn_id); + fe_adapter_send_error(session, req_id, false, -EINPROGRESS, + "Failed to create an RPC transaction"); + } +} + +/** + * Handle a native encoded message from the FE client. + */ +static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter, + struct mgmt_msg_header *msg, + size_t msg_len) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(msg->refer_id); + if (!session) { + __log_err("adapter %s: recv msg unknown session-id %" PRIu64, + adapter->name, msg->refer_id); + return; + } + assert(session->adapter == adapter); + + switch (msg->code) { + case MGMT_MSG_CODE_GET_DATA: + fe_adapter_handle_get_data(session, msg, msg_len); + break; + case MGMT_MSG_CODE_EDIT: + fe_adapter_handle_edit(session, msg, msg_len); + break; + case MGMT_MSG_CODE_RPC: + fe_adapter_handle_rpc(session, msg, msg_len); + break; + default: + __log_err("unknown native message session-id %" PRIu64 + " req-id %" PRIu64 " code %u to FE adapter %s", + msg->refer_id, msg->req_id, msg->code, adapter->name); + break; + } +} + + +static void mgmt_fe_adapter_process_msg(uint8_t version, uint8_t *data, + size_t len, struct msg_conn *conn) +{ + struct mgmt_fe_client_adapter *adapter = conn->user; + Mgmtd__FeMessage *fe_msg; + + if (version == MGMT_MSG_VERSION_NATIVE) { + struct mgmt_msg_header *msg = (typeof(msg))data; + + if (len >= sizeof(*msg)) + fe_adapter_handle_native_msg(adapter, msg, len); + else + __log_err("native message to adapter %s too short %zu", + adapter->name, len); + return; + } + + fe_msg = mgmtd__fe_message__unpack(NULL, len, data); + if (!fe_msg) { + __dbg("Failed to decode %zu bytes for adapter: %s", len, + adapter->name); + return; + } + __dbg("Decoded %zu bytes of message: %u from adapter: %s", len, + fe_msg->message_case, adapter->name); + (void)mgmt_fe_adapter_handle_msg(adapter, fe_msg); + mgmtd__fe_message__free_unpacked(fe_msg, NULL); +} + +void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen) +{ + struct mgmt_fe_client_adapter *adapter; + struct mgmt_fe_session_ctx *session; + + assert(msg->refer_id == 0); + + FOREACH_ADAPTER_IN_LIST (adapter) { + FOREACH_SESSION_IN_LIST (adapter, session) { + msg->refer_id = session->session_id; + (void)fe_adapter_send_native_msg(adapter, msg, msglen, + false); + } + } + msg->refer_id = 0; +} + +void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter) +{ + adapter->refcount++; +} + +extern void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter) +{ + struct mgmt_fe_client_adapter *a = *adapter; + assert(a && a->refcount); + + if (!--a->refcount) { + mgmt_fe_adapters_del(&mgmt_fe_adapters, a); + msg_server_conn_delete(a->conn); + XFREE(MTYPE_MGMTD_FE_ADPATER, a); + } + *adapter = NULL; +} + +/* + * Initialize the FE adapter module + */ +void mgmt_fe_adapter_init(struct event_loop *tm) +{ + char server_path[MAXPATHLEN]; + + assert(!mgmt_loop); + mgmt_loop = tm; + + mgmt_fe_adapters_init(&mgmt_fe_adapters); + + assert(!mgmt_fe_sessions); + mgmt_fe_sessions = + hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp, + "MGMT Frontend Sessions"); + + snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME); + + if (msg_server_init(&mgmt_fe_server, server_path, tm, + mgmt_fe_create_adapter, "frontend", &mgmt_debug_fe)) { + zlog_err("cannot initialize frontend server"); + exit(1); + } +} + +static void mgmt_fe_abort_if_session(void *data) +{ + struct mgmt_fe_session_ctx *session = data; + + __log_err("found orphaned session id %" PRIu64 " client id %" PRIu64 + " adapter %s", + session->session_id, session->client_id, + session->adapter ? session->adapter->name : "NULL"); + abort(); +} + +/* + * Destroy the FE adapter module + */ +void mgmt_fe_adapter_destroy(void) +{ + struct mgmt_fe_client_adapter *adapter; + + msg_server_cleanup(&mgmt_fe_server); + + /* Deleting the adapters will delete all the sessions */ + FOREACH_ADAPTER_IN_LIST (adapter) + mgmt_fe_adapter_delete(adapter); + + hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session); +} + +/* + * The server accepted a new connection + */ +struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from) +{ + struct mgmt_fe_client_adapter *adapter = NULL; + + adapter = mgmt_fe_find_adapter_by_fd(conn_fd); + if (!adapter) { + adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, + sizeof(struct mgmt_fe_client_adapter)); + snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d", + conn_fd); + + mgmt_fe_sessions_init(&adapter->fe_sessions); + mgmt_fe_adapter_lock(adapter); + mgmt_fe_adapters_add_tail(&mgmt_fe_adapters, adapter); + + adapter->conn = msg_server_conn_create( + mgmt_loop, conn_fd, mgmt_fe_adapter_notify_disconnect, + mgmt_fe_adapter_process_msg, MGMTD_FE_MAX_NUM_MSG_PROC, + MGMTD_FE_MAX_NUM_MSG_WRITE, MGMTD_FE_MAX_MSG_LEN, + adapter, "FE-adapter"); + + adapter->conn->debug = DEBUG_MODE_CHECK(&mgmt_debug_fe, + DEBUG_MODE_ALL); + + adapter->setcfg_stats.min_tm = ULONG_MAX; + adapter->cmt_stats.min_tm = ULONG_MAX; + __dbg("Added new MGMTD Frontend adapter '%s'", adapter->name); + } + return adapter->conn; +} + +int mgmt_fe_send_set_cfg_reply(uint64_t session_id, uint64_t txn_id, + Mgmtd__DatastoreId ds_id, uint64_t req_id, + enum mgmt_result result, + const char *error_if_any, + bool implicit_commit) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->cfg_txn_id != txn_id) { + if (session) + __log_err("txn-id doesn't match, session txn-id is %" PRIu64 + " current txnid: %" PRIu64, + session->cfg_txn_id, txn_id); + return -1; + } + + return fe_adapter_send_set_cfg_reply(session, ds_id, req_id, + result == MGMTD_SUCCESS, + error_if_any, implicit_commit); +} + +int mgmt_fe_send_commit_cfg_reply(uint64_t session_id, uint64_t txn_id, + Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, + uint64_t req_id, bool validate_only, + enum mgmt_result result, + const char *error_if_any) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->cfg_txn_id != txn_id) + return -1; + + return fe_adapter_send_commit_cfg_reply(session, src_ds_id, dst_ds_id, + req_id, result, validate_only, + error_if_any); +} + +int mgmt_fe_send_get_reply(uint64_t session_id, uint64_t txn_id, + Mgmtd__DatastoreId ds_id, uint64_t req_id, + enum mgmt_result result, + Mgmtd__YangDataReply *data_resp, + const char *error_if_any) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->txn_id != txn_id) + return -1; + + return fe_adapter_send_get_reply(session, ds_id, req_id, + result == MGMTD_SUCCESS, data_resp, + error_if_any); +} + +int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, LYD_FORMAT result_type, + uint32_t wd_options, + const struct lyd_node *tree, + int partial_error, bool short_circuit_ok) +{ + struct mgmt_fe_session_ctx *session; + int ret; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->txn_id != txn_id) + return -1; + + ret = fe_adapter_send_tree_data(session, req_id, short_circuit_ok, + result_type, wd_options, tree, + partial_error); + + mgmt_destroy_txn(&session->txn_id); + + return ret; +} + +int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, LYD_FORMAT result_type, + const struct lyd_node *result) +{ + struct mgmt_fe_session_ctx *session; + int ret; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->txn_id != txn_id) + return -1; + + ret = fe_adapter_send_rpc_reply(session, req_id, result_type, result); + + mgmt_destroy_txn(&session->txn_id); + + return ret; +} + +int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, bool unlock, bool commit, + const char *xpath, int16_t error, + const char *errstr) +{ + struct mgmt_fe_session_ctx *session; + Mgmtd__DatastoreId ds_id, rds_id; + struct mgmt_ds_ctx *ds_ctx, *rds_ctx; + int ret; + + session = mgmt_session_id2ctx(session_id); + if (!session || session->cfg_txn_id != txn_id) + return -1; + + if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit) + mgmt_fe_session_register_event(session, + MGMTD_FE_SESSION_CFG_TXN_CLNUP); + + if (unlock) { + ds_id = MGMTD_DS_CANDIDATE; + ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id); + assert(ds_ctx); + + mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session); + + if (commit) { + rds_id = MGMTD_DS_RUNNING; + rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id); + assert(rds_ctx); + + mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session); + } + } + + if (error) + ret = fe_adapter_send_error(session, req_id, false, error, "%s", + errstr); + else + ret = fe_adapter_send_edit_reply(session, req_id, xpath); + + if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit) + mgmt_destroy_txn(&session->cfg_txn_id); + + return ret; +} + +/** + * Send an error back to the FE client and cleanup any in-progress txn. + */ +int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, + bool short_circuit_ok, int16_t error, + const char *errstr) +{ + struct mgmt_fe_session_ctx *session; + int ret; + + session = fe_adapter_session_by_txn_id(txn_id); + if (!session) { + __log_err("failed sending error for txn-id %" PRIu64 + " session not found", + txn_id); + return -ENOENT; + } + + + ret = fe_adapter_send_error(session, req_id, false, error, "%s", errstr); + + mgmt_destroy_txn(&session->txn_id); + + return ret; +} + + +struct mgmt_setcfg_stats *mgmt_fe_get_session_setcfg_stats(uint64_t session_id) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(session_id); + if (!session || !session->adapter) + return NULL; + + return &session->adapter->setcfg_stats; +} + +struct mgmt_commit_stats * +mgmt_fe_get_session_commit_stats(uint64_t session_id) +{ + struct mgmt_fe_session_ctx *session; + + session = mgmt_session_id2ctx(session_id); + if (!session || !session->adapter) + return NULL; + + return &session->adapter->cmt_stats; +} + +static void +mgmt_fe_adapter_cmt_stats_write(struct vty *vty, + struct mgmt_fe_client_adapter *adapter) +{ + char buf[MGMT_LONG_TIME_MAX_LEN]; + + if (!mm->perf_stats_en) + return; + + vty_out(vty, " Num-Commits: \t\t\t%lu\n", + adapter->cmt_stats.commit_cnt); + if (adapter->cmt_stats.commit_cnt > 0) { + if (mm->perf_stats_en) + vty_out(vty, " Max-Commit-Duration: \t\t%lu uSecs\n", + adapter->cmt_stats.max_tm); + vty_out(vty, " Max-Commit-Batch-Size: \t\t%lu\n", + adapter->cmt_stats.max_batch_cnt); + if (mm->perf_stats_en) + vty_out(vty, " Min-Commit-Duration: \t\t%lu uSecs\n", + adapter->cmt_stats.min_tm); + vty_out(vty, " Min-Commit-Batch-Size: \t\t%lu\n", + adapter->cmt_stats.min_batch_cnt); + if (mm->perf_stats_en) + vty_out(vty, + " Last-Commit-Duration: \t\t%lu uSecs\n", + adapter->cmt_stats.last_exec_tm); + vty_out(vty, " Last-Commit-Batch-Size: \t\t%lu\n", + adapter->cmt_stats.last_batch_cnt); + vty_out(vty, " Last-Commit-CfgData-Reqs: \t\t%lu\n", + adapter->cmt_stats.last_num_cfgdata_reqs); + vty_out(vty, " Last-Commit-CfgApply-Reqs: \t\t%lu\n", + adapter->cmt_stats.last_num_apply_reqs); + if (mm->perf_stats_en) { + vty_out(vty, " Last-Commit-Details:\n"); + vty_out(vty, " Commit Start: \t\t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.last_start, buf, + sizeof(buf))); +#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED + vty_out(vty, " Config-Validate Start: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.validate_start, buf, + sizeof(buf))); +#endif + vty_out(vty, " Prep-Config Start: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.prep_cfg_start, buf, + sizeof(buf))); + vty_out(vty, " Txn-Create Start: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.txn_create_start, + buf, sizeof(buf))); + vty_out(vty, " Apply-Config Start: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.apply_cfg_start, + buf, sizeof(buf))); + vty_out(vty, " Apply-Config End: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.apply_cfg_end, buf, + sizeof(buf))); + vty_out(vty, " Txn-Delete Start: \t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.txn_del_start, buf, + sizeof(buf))); + vty_out(vty, " Commit End: \t\t\t%s\n", + mgmt_realtime_to_string( + &adapter->cmt_stats.last_end, buf, + sizeof(buf))); + } + } +} + +static void +mgmt_fe_adapter_setcfg_stats_write(struct vty *vty, + struct mgmt_fe_client_adapter *adapter) +{ + char buf[MGMT_LONG_TIME_MAX_LEN]; + + if (!mm->perf_stats_en) + return; + + vty_out(vty, " Num-Set-Cfg: \t\t\t%lu\n", + adapter->setcfg_stats.set_cfg_count); + if (mm->perf_stats_en && adapter->setcfg_stats.set_cfg_count > 0) { + vty_out(vty, " Max-Set-Cfg-Duration: \t\t%lu uSec\n", + adapter->setcfg_stats.max_tm); + vty_out(vty, " Min-Set-Cfg-Duration: \t\t%lu uSec\n", + adapter->setcfg_stats.min_tm); + vty_out(vty, " Avg-Set-Cfg-Duration: \t\t%lu uSec\n", + adapter->setcfg_stats.avg_tm); + vty_out(vty, " Last-Set-Cfg-Details:\n"); + vty_out(vty, " Set-Cfg Start: \t\t\t%s\n", + mgmt_realtime_to_string( + &adapter->setcfg_stats.last_start, buf, + sizeof(buf))); + vty_out(vty, " Set-Cfg End: \t\t\t%s\n", + mgmt_realtime_to_string(&adapter->setcfg_stats.last_end, + buf, sizeof(buf))); + } +} + +void mgmt_fe_adapter_status_write(struct vty *vty, bool detail) +{ + struct mgmt_fe_client_adapter *adapter; + struct mgmt_fe_session_ctx *session; + Mgmtd__DatastoreId ds_id; + bool locked = false; + + vty_out(vty, "MGMTD Frontend Adpaters\n"); + + FOREACH_ADAPTER_IN_LIST (adapter) { + vty_out(vty, " Client: \t\t\t\t%s\n", adapter->name); + vty_out(vty, " Conn-FD: \t\t\t\t%d\n", adapter->conn->fd); + if (detail) { + mgmt_fe_adapter_setcfg_stats_write(vty, adapter); + mgmt_fe_adapter_cmt_stats_write(vty, adapter); + } + vty_out(vty, " Sessions\n"); + FOREACH_SESSION_IN_LIST (adapter, session) { + vty_out(vty, " Session: \t\t\t\t%p\n", session); + vty_out(vty, " Client-Id: \t\t\t%" PRIu64 "\n", + session->client_id); + vty_out(vty, " Session-Id: \t\t\t%" PRIu64 "\n", + session->session_id); + vty_out(vty, " DS-Locks:\n"); + FOREACH_MGMTD_DS_ID (ds_id) { + if (session->ds_locked[ds_id]) { + locked = true; + vty_out(vty, " %s\n", + mgmt_ds_id2name(ds_id)); + } + } + if (!locked) + vty_out(vty, " None\n"); + } + vty_out(vty, " Total-Sessions: \t\t\t%d\n", + (int)mgmt_fe_sessions_count(&adapter->fe_sessions)); + vty_out(vty, " Msg-Recvd: \t\t\t\t%" PRIu64 "\n", + adapter->conn->mstate.nrxm); + vty_out(vty, " Bytes-Recvd: \t\t\t%" PRIu64 "\n", + adapter->conn->mstate.nrxb); + vty_out(vty, " Msg-Sent: \t\t\t\t%" PRIu64 "\n", + adapter->conn->mstate.ntxm); + vty_out(vty, " Bytes-Sent: \t\t\t%" PRIu64 "\n", + adapter->conn->mstate.ntxb); + } + vty_out(vty, " Total: %d\n", + (int)mgmt_fe_adapters_count(&mgmt_fe_adapters)); +} + +void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config) +{ + mm->perf_stats_en = config; +} + +void mgmt_fe_adapter_reset_perf_stats(struct vty *vty) +{ + struct mgmt_fe_client_adapter *adapter; + struct mgmt_fe_session_ctx *session; + + FOREACH_ADAPTER_IN_LIST (adapter) { + memset(&adapter->setcfg_stats, 0, + sizeof(adapter->setcfg_stats)); + FOREACH_SESSION_IN_LIST (adapter, session) { + memset(&adapter->cmt_stats, 0, + sizeof(adapter->cmt_stats)); + } + } +} diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h new file mode 100644 index 0000000..61d6cfa --- /dev/null +++ b/mgmtd/mgmt_fe_adapter.h @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Frontend Client Connection Adapter + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#ifndef _FRR_MGMTD_FE_ADAPTER_H_ +#define _FRR_MGMTD_FE_ADAPTER_H_ + +#include "mgmt_fe_client.h" +#include "mgmt_msg.h" +#include "mgmt_defines.h" + +struct mgmt_fe_client_adapter; +struct mgmt_master; + +struct mgmt_commit_stats { + struct timeval last_start; +#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED + struct timeval validate_start; +#endif + struct timeval prep_cfg_start; + struct timeval txn_create_start; + struct timeval apply_cfg_start; + struct timeval apply_cfg_end; + struct timeval txn_del_start; + struct timeval last_end; + unsigned long last_exec_tm; + unsigned long max_tm; + unsigned long min_tm; + unsigned long last_batch_cnt; + unsigned long last_num_cfgdata_reqs; + unsigned long last_num_apply_reqs; + unsigned long max_batch_cnt; + unsigned long min_batch_cnt; + unsigned long commit_cnt; +}; + +struct mgmt_setcfg_stats { + struct timeval last_start; + struct timeval last_end; + unsigned long last_exec_tm; + unsigned long max_tm; + unsigned long min_tm; + unsigned long avg_tm; + unsigned long set_cfg_count; +}; + +PREDECL_LIST(mgmt_fe_sessions); + +PREDECL_LIST(mgmt_fe_adapters); + +struct mgmt_fe_client_adapter { + struct msg_conn *conn; + char name[MGMTD_CLIENT_NAME_MAX_LEN]; + + /* List of sessions created and being maintained for this client. */ + struct mgmt_fe_sessions_head fe_sessions; + + int refcount; + struct mgmt_commit_stats cmt_stats; + struct mgmt_setcfg_stats setcfg_stats; + + struct mgmt_fe_adapters_item list_linkage; +}; + +DECLARE_LIST(mgmt_fe_adapters, struct mgmt_fe_client_adapter, list_linkage); + +/* Initialise frontend adapter module */ +extern void mgmt_fe_adapter_init(struct event_loop *tm); + +/* Destroy frontend adapter module */ +extern void mgmt_fe_adapter_destroy(void); + +/* Acquire lock for frontend adapter */ +extern void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter); + +/* Remove lock from frontend adapter */ +extern void +mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter); + +/* Create frontend adapter */ +extern struct msg_conn *mgmt_fe_create_adapter(int conn_fd, + union sockunion *su); + +/* + * Send set-config reply to the frontend client. + * + * session + * Unique session identifier. + * + * txn_id + * Unique transaction identifier. + * + * ds_id + * Datastore ID. + * + * req_id + * Config request ID. + * + * result + * Config request result (MGMT_*). + * + * error_if_any + * Buffer to store human-readable error message in case of error. + * + * implicit_commit + * TRUE if the commit is implicit, FALSE otherwise. + * + * Returns: + * 0 on success, -1 on failures. + */ +extern int mgmt_fe_send_set_cfg_reply(uint64_t session_id, uint64_t txn_id, + Mgmtd__DatastoreId ds_id, + uint64_t req_id, + enum mgmt_result result, + const char *error_if_any, + bool implcit_commit); + +/* + * Send commit-config reply to the frontend client. + */ +extern int mgmt_fe_send_commit_cfg_reply( + uint64_t session_id, uint64_t txn_id, Mgmtd__DatastoreId src_ds_id, + Mgmtd__DatastoreId dst_ds_id, uint64_t req_id, bool validate_only, + enum mgmt_result result, const char *error_if_any); + +/* + * Send get-config/get-data reply to the frontend client. + */ +extern int mgmt_fe_send_get_reply(uint64_t session_id, uint64_t txn_id, + Mgmtd__DatastoreId ds_id, uint64_t req_id, + enum mgmt_result result, + Mgmtd__YangDataReply *data_resp, + const char *error_if_any); + +/** + * Send get-tree data reply back to client. + * + * This also cleans up and frees the transaction. + * + * Args: + * session_id: the session. + * txn_id: the txn_id this data pertains to + * req_id: the req id for the get_tree message + * result_type: the format of the result data. + * wd_options: with-defaults options. + * tree: the results. + * partial_error: if there were errors while gather results. + * short_circuit_ok: True if OK to short-circuit the call. + * + * Return: + * the return value from the underlying send function. + * + */ +extern int +mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, LYD_FORMAT result_type, + uint32_t wd_options, const struct lyd_node *tree, + int partial_error, bool short_circuit_ok); + +/** + * Send RPC reply back to client. + * + * This also cleans up and frees the transaction. + * + * Args: + * session_id: the session. + * txn_id: the txn_id this data pertains to + * req_id: the req id for the rpc message + * result_type: the format of the result data. + * result: the results. + * + * Return: + * the return value from the underlying send function. + */ +extern int mgmt_fe_adapter_send_rpc_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, + LYD_FORMAT result_type, + const struct lyd_node *result); + +/** + * Send edit reply back to client. If error is not 0, a native error is sent. + * + * This also cleans up and frees the transaction. + * + * Args: + * session_id: the session. + * txn_id: the txn_id this data pertains to + * req_id: the req id for the edit message + * unlock: implicit-lock flag was set in the request + * commit: implicit-commit flag was set in the request + * xpath: the xpath of the data node that was created + * error: the error code, zero for successful request + * errstr: the error string, if error is non-zero + */ +extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, bool unlock, + bool commit, const char *xpath, + int16_t error, const char *errstr); + +/** + * Send an error back to the FE client using native messaging. + * + * This also cleans up and frees the transaction. + * + * Args: + * txn_id: the txn_id this error pertains to. + * short_circuit_ok: True if OK to short-circuit the call. + * error: An integer error value. + * errfmt: An error format string (i.e., printfrr) + * ...: args for use by the `errfmt` format string. + * + * Return: + * the return value from the underlying send function. + * + */ +extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id, + bool short_circuit_ok, int16_t error, + const char *errstr); + + +/* Fetch frontend client session set-config stats */ +extern struct mgmt_setcfg_stats * +mgmt_fe_get_session_setcfg_stats(uint64_t session_id); + +/* Fetch frontend client session commit stats */ +extern struct mgmt_commit_stats * +mgmt_fe_get_session_commit_stats(uint64_t session_id); + +extern void mgmt_fe_adapter_status_write(struct vty *vty, bool detail); +extern void mgmt_fe_adapter_perf_measurement(struct vty *vty, bool config); +extern void mgmt_fe_adapter_reset_perf_stats(struct vty *vty); + +/* Toggle debug on or off for connected clients. */ +extern void mgmt_fe_adapter_toggle_client_debug(bool set); + +#endif /* _FRR_MGMTD_FE_ADAPTER_H_ */ diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c new file mode 100644 index 0000000..c97cb7f --- /dev/null +++ b/mgmtd/mgmt_history.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + */ + +#include +#include "md5.h" +#include "frrevent.h" +#include "xref.h" + +#include "mgmt_fe_client.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_history.h" + +struct mgmt_cmt_info_t { + struct mgmt_cmt_infos_item cmts; + + char cmtid_str[MGMT_SHORT_TIME_MAX_LEN]; + char time_str[MGMT_LONG_TIME_MAX_LEN]; + char cmt_json_file[PATH_MAX]; +}; + + +DECLARE_DLIST(mgmt_cmt_infos, struct mgmt_cmt_info_t, cmts); + +#define FOREACH_CMT_REC(mm, cmt_info) \ + frr_each_safe (mgmt_cmt_infos, &mm->cmts, cmt_info) + +/* + * The only instance of VTY session that has triggered an ongoing + * config rollback operation. + */ +static struct vty *rollback_vty; + +static bool file_exists(const char *path) +{ + return !access(path, F_OK); +} + +static void remove_file(const char *path) +{ + if (!file_exists(path)) + return; + if (unlink(path)) + zlog_err("Failed to remove commit history file %s: %s", path, + safe_strerror(errno)); +} + +static struct mgmt_cmt_info_t *mgmt_history_new_cmt_info(void) +{ + struct mgmt_cmt_info_t *new; + struct timespec tv; + struct tm tm; + + new = XCALLOC(MTYPE_MGMTD_CMT_INFO, sizeof(struct mgmt_cmt_info_t)); + + clock_gettime(CLOCK_REALTIME, &tv); + localtime_r(&tv.tv_sec, &tm); + + mgmt_time_to_string(&tv, true, new->time_str, sizeof(new->time_str)); + mgmt_time_to_string(&tv, false, new->cmtid_str, sizeof(new->cmtid_str)); + snprintf(new->cmt_json_file, sizeof(new->cmt_json_file), + MGMTD_COMMIT_FILE_PATH(new->cmtid_str)); + + return new; +} + +static struct mgmt_cmt_info_t *mgmt_history_create_cmt_rec(void) +{ + struct mgmt_cmt_info_t *new = mgmt_history_new_cmt_info(); + struct mgmt_cmt_info_t *cmt_info; + struct mgmt_cmt_info_t *last_cmt_info = NULL; + + if (mgmt_cmt_infos_count(&mm->cmts) == MGMTD_MAX_COMMIT_LIST) { + FOREACH_CMT_REC (mm, cmt_info) + last_cmt_info = cmt_info; + + if (last_cmt_info) { + remove_file(last_cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, last_cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, last_cmt_info); + } + } + + mgmt_cmt_infos_add_head(&mm->cmts, new); + return new; +} + +static struct mgmt_cmt_info_t * +mgmt_history_find_cmt_record(const char *cmtid_str) +{ + struct mgmt_cmt_info_t *cmt_info; + + FOREACH_CMT_REC (mm, cmt_info) { + if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) + return cmt_info; + } + + return NULL; +} + +static bool mgmt_history_read_cmt_record_index(void) +{ + char index_path[MAXPATHLEN]; + FILE *fp; + struct mgmt_cmt_info_t cmt_info; + struct mgmt_cmt_info_t *new; + int cnt = 0; + + snprintf(index_path, sizeof(index_path), MGMTD_COMMIT_INDEX_FILE_PATH); + + fp = fopen(index_path, "rb"); + if (!fp) { + if (errno == ENOENT || errno == ENOTDIR) + return false; + + zlog_err("Failed to open commit history %pSQq for reading: %m", + index_path); + return false; + } + + while ((fread(&cmt_info, sizeof(cmt_info), 1, fp)) > 0) { + if (cnt < MGMTD_MAX_COMMIT_LIST) { + if (!file_exists(cmt_info.cmt_json_file)) { + zlog_err("Commit in index, but file %s missing", + cmt_info.cmt_json_file); + continue; + } + + new = XCALLOC(MTYPE_MGMTD_CMT_INFO, + sizeof(struct mgmt_cmt_info_t)); + memcpy(new, &cmt_info, sizeof(struct mgmt_cmt_info_t)); + mgmt_cmt_infos_add_tail(&mm->cmts, new); + } else { + zlog_warn("More records found in commit history file %pSQq than expected", + index_path); + fclose(fp); + return false; + } + + cnt++; + } + + fclose(fp); + return true; +} + +static bool mgmt_history_dump_cmt_record_index(void) +{ + char index_path[MAXPATHLEN]; + FILE *fp; + int ret = 0; + struct mgmt_cmt_info_t *cmt_info; + struct mgmt_cmt_info_t cmt_info_set[10]; + int cnt = 0; + + snprintf(index_path, sizeof(index_path), MGMTD_COMMIT_INDEX_FILE_PATH); + + fp = fopen(index_path, "wb"); + if (!fp) { + zlog_err("Failed to open commit history %pSQq for writing: %m", + index_path); + return false; + } + + FOREACH_CMT_REC (mm, cmt_info) { + memcpy(&cmt_info_set[cnt], cmt_info, + sizeof(struct mgmt_cmt_info_t)); + cnt++; + } + + if (!cnt) { + fclose(fp); + return false; + } + + ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp); + fclose(fp); + if (ret != cnt) { + zlog_err("Failed to write full commit history, removing file"); + remove_file(index_path); + return false; + } + return true; +} + +static int mgmt_history_rollback_to_cmt(struct vty *vty, + struct mgmt_cmt_info_t *cmt_info, + bool skip_file_load) +{ + struct mgmt_ds_ctx *src_ds_ctx; + struct mgmt_ds_ctx *dst_ds_ctx; + int ret = 0; + + if (rollback_vty) { + vty_out(vty, "ERROR: Rollback already in progress!\n"); + return -1; + } + + src_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE); + dst_ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); + assert(src_ds_ctx); + assert(dst_ds_ctx); + + ret = mgmt_ds_lock(src_ds_ctx, vty->mgmt_session_id); + if (ret != 0) { + vty_out(vty, + "Failed to lock the DS %u for rollback Reason: %s!\n", + MGMTD_DS_RUNNING, strerror(ret)); + return -1; + } + + ret = mgmt_ds_lock(dst_ds_ctx, vty->mgmt_session_id); + if (ret != 0) { + mgmt_ds_unlock(src_ds_ctx); + vty_out(vty, + "Failed to lock the DS %u for rollback Reason: %s!\n", + MGMTD_DS_RUNNING, strerror(ret)); + return -1; + } + + if (!skip_file_load) { + ret = mgmt_ds_load_config_from_file( + src_ds_ctx, cmt_info->cmt_json_file, false); + if (ret != 0) { + vty_out(vty, + "Error with parsing the file with error code %d\n", + ret); + goto failed_unlock; + } + } + + /* Internally trigger a commit-request. */ + ret = mgmt_txn_rollback_trigger_cfg_apply(src_ds_ctx, dst_ds_ctx); + if (ret != 0) { + vty_out(vty, + "Error with creating commit apply txn with error code %d\n", + ret); + goto failed_unlock; + } + + mgmt_history_dump_cmt_record_index(); + + /* + * TODO: Cleanup: the generic TXN code currently checks for rollback + * and does the unlock when it completes. + */ + + /* + * Block the rollback command from returning till the rollback + * is completed. On rollback completion mgmt_history_rollback_complete() + * shall be called to resume the rollback command return to VTYSH. + */ + vty->mgmt_req_pending_cmd = "ROLLBACK"; + rollback_vty = vty; + return 0; + +failed_unlock: + mgmt_ds_unlock(src_ds_ctx); + mgmt_ds_unlock(dst_ds_ctx); + return ret; +} + +void mgmt_history_rollback_complete(bool success) +{ + vty_mgmt_resume_response(rollback_vty, + success ? CMD_SUCCESS + : CMD_WARNING_CONFIG_FAILED); + rollback_vty = NULL; +} + +int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str) +{ + int ret = 0; + struct mgmt_cmt_info_t *cmt_info; + + if (!mgmt_cmt_infos_count(&mm->cmts) || + !mgmt_history_find_cmt_record(cmtid_str)) { + vty_out(vty, "Invalid commit Id\n"); + return -1; + } + + FOREACH_CMT_REC (mm, cmt_info) { + if (strcmp(cmt_info->cmtid_str, cmtid_str) == 0) { + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, + false); + return ret; + } + + remove_file(cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + return 0; +} + +int mgmt_history_rollback_n(struct vty *vty, int num_cmts) +{ + int ret = 0; + int cnt = 0; + struct mgmt_cmt_info_t *cmt_info; + size_t cmts; + + if (!num_cmts) + num_cmts = 1; + + cmts = mgmt_cmt_infos_count(&mm->cmts); + if ((int)cmts < num_cmts) { + vty_out(vty, + "Number of commits found (%d) less than required to rollback\n", + (int)cmts); + return -1; + } + + if ((int)cmts == 1 || (int)cmts == num_cmts) { + vty_out(vty, + "Number of commits found (%d), Rollback of last commit is not supported\n", + (int)cmts); + return -1; + } + + FOREACH_CMT_REC (mm, cmt_info) { + if (cnt == num_cmts) { + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, + false); + return ret; + } + + cnt++; + remove_file(cmt_info->cmt_json_file); + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + if (!mgmt_cmt_infos_count(&mm->cmts)) { + mgmt_ds_reset_candidate(); + ret = mgmt_history_rollback_to_cmt(vty, cmt_info, true); + } + + return ret; +} + +void show_mgmt_cmt_history(struct vty *vty) +{ + struct mgmt_cmt_info_t *cmt_info; + int slno = 0; + + vty_out(vty, "Last 10 commit history:\n"); + vty_out(vty, "Slot Commit-ID Commit-Record-Time\n"); + FOREACH_CMT_REC (mm, cmt_info) { + vty_out(vty, "%4d %23s %s\n", slno, cmt_info->cmtid_str, + cmt_info->time_str); + slno++; + } +} + +void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx) +{ + struct mgmt_cmt_info_t *cmt_info = mgmt_history_create_cmt_rec(); + + mgmt_ds_dump_ds_to_file(cmt_info->cmt_json_file, ds_ctx); + mgmt_history_dump_cmt_record_index(); +} + +void mgmt_history_init(void) +{ + /* Create commit record for previously stored commit-apply */ + mgmt_cmt_infos_init(&mm->cmts); + mgmt_history_read_cmt_record_index(); +} + +void mgmt_history_destroy(void) +{ + struct mgmt_cmt_info_t *cmt_info; + + FOREACH_CMT_REC(mm, cmt_info) { + mgmt_cmt_infos_del(&mm->cmts, cmt_info); + XFREE(MTYPE_MGMTD_CMT_INFO, cmt_info); + } + + mgmt_cmt_infos_fini(&mm->cmts); +} diff --git a/mgmtd/mgmt_history.h b/mgmtd/mgmt_history.h new file mode 100644 index 0000000..5d9b662 --- /dev/null +++ b/mgmtd/mgmt_history.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ +#ifndef _FRR_MGMTD_HISTORY_H_ +#define _FRR_MGMTD_HISTORY_H_ + +#include "vrf.h" + +PREDECL_DLIST(mgmt_cmt_infos); + +struct mgmt_ds_ctx; + +/* + * Rollback specific commit from commit history. + * + * vty + * VTY context. + * + * cmtid_str + * Specific commit id from commit history. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_history_rollback_by_id(struct vty *vty, const char *cmtid_str); + +/* + * Rollback n commits from commit history. + * + * vty + * VTY context. + * + * num_cmts + * Number of commits to be rolled back. + * + * Returns: + * 0 on success, -1 on failure. + */ +extern int mgmt_history_rollback_n(struct vty *vty, int num_cmts); + +extern void mgmt_history_rollback_complete(bool success); + +/* + * Show mgmt commit history. + */ +extern void show_mgmt_cmt_history(struct vty *vty); + +extern void mgmt_history_new_record(struct mgmt_ds_ctx *ds_ctx); + +extern void mgmt_history_destroy(void); +extern void mgmt_history_init(void); + +/* + * 012345678901234567890123456789 + * 2023-12-31T12:12:12,012345678 + * 20231231121212012345678 + */ +#define MGMT_LONG_TIME_FMT "%Y-%m-%dT%H:%M:%S" +#define MGMT_LONG_TIME_MAX_LEN 30 +#define MGMT_SHORT_TIME_FMT "%Y%m%d%H%M%S" +#define MGMT_SHORT_TIME_MAX_LEN 24 + +static inline const char * +mgmt_time_to_string(struct timespec *tv, bool long_fmt, char *buffer, size_t sz) +{ + struct tm tm; + size_t n; + + localtime_r(&tv->tv_sec, &tm); + + if (long_fmt) { + n = strftime(buffer, sz, MGMT_LONG_TIME_FMT, &tm); + assert(n < sz); + snprintf(&buffer[n], sz - n, ",%09lu", tv->tv_nsec); + } else { + n = strftime(buffer, sz, MGMT_SHORT_TIME_FMT, &tm); + assert(n < sz); + snprintf(&buffer[n], sz - n, "%09lu", tv->tv_nsec); + } + + return buffer; +} + +static inline const char *mgmt_realtime_to_string(struct timeval *tv, char *buf, + size_t sz) +{ + struct timespec ts = {.tv_sec = tv->tv_sec, + .tv_nsec = tv->tv_usec * 1000}; + + return mgmt_time_to_string(&ts, true, buf, sz); +} + +#endif /* _FRR_MGMTD_HISTORY_H_ */ diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c new file mode 100644 index 0000000..e181d0d --- /dev/null +++ b/mgmtd/mgmt_main.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Main routine of mgmt. + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "lib/version.h" +#include "routemap.h" +#include "filter.h" +#include "keychain.h" +#include "libfrr.h" +#include "frr_pthread.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_ds.h" +#include "ripd/rip_nb.h" +#include "ripngd/ripng_nb.h" +#include "routing_nb.h" +#include "affinitymap.h" +#include "zebra/zebra_cli.h" + +/* mgmt options, we use GNU getopt library. */ +static const struct option longopts[] = { + {"skip_runas", no_argument, NULL, 'S'}, + {"no_zebra", no_argument, NULL, 'Z'}, + {"socket_size", required_argument, NULL, 's'}, + {"vrfwnetns", no_argument, NULL, 'n'}, + {0}}; + +static void mgmt_exit(int); + +/* privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_NET_RAW, + ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN}; + +struct zebra_privs_t mgmt_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +static struct frr_daemon_info mgmtd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received, ignoring"); + + return; + + /* + * This is turned off for the moment. There is all + * sorts of config turned off by mgmt_terminate + * that is not setup properly again in mgmt_reset. + * I see no easy way to do this nor do I see that + * this is a desirable way to reload config + * given the yang work. + */ + /* Terminate all thread. */ + mgmt_terminate(); + + /* + * mgmt_reset(); + */ + zlog_info("MGMTD restarting!"); + + /* + * Reload config file. + * vty_read_config(NULL, mgmtd_di.config_file, config_default); + */ + /* Try to return to normal operation. */ +} + +/* SIGINT handler. */ +static __attribute__((__noreturn__)) void sigint(void) +{ + zlog_notice("Terminating on signal"); + assert(mm->terminating == false); + mm->terminating = true; /* global flag that shutting down */ + + mgmt_terminate(); + + mgmt_exit(0); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +/* + * Try to free up allocations we know about so that diagnostic tools such as + * valgrind are able to better illuminate leaks. + * + * Zebra route removal and protocol teardown are not meant to be done here. + * For example, "retain_mode" may be set. + */ +static __attribute__((__noreturn__)) void mgmt_exit(int status) +{ + /* it only makes sense for this to be called on a clean exit */ + assert(status == 0); + + frr_early_fini(); + + /* stop pthreads (if any) */ + frr_pthread_stop_all(); + + frr_fini(); + exit(status); +} + +static struct frr_signal_t mgmt_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +#ifdef HAVE_STATICD +extern const struct frr_yang_module_info frr_staticd_cli_info; +#endif + +/* + * These are modules that are only needed by mgmtd and hence not included into + * the lib and backend daemons. + */ +const struct frr_yang_module_info ietf_netconf_with_defaults_info = { + .name = "ietf-netconf-with-defaults", + .ignore_cfg_cbs = true, + .nodes = { { .xpath = NULL } }, +}; + +/* + * These are stub info structs that are used to load the modules used by backend + * clients into mgmtd. The modules are used by libyang in order to support + * parsing binary data returns from the backend. + */ +const struct frr_yang_module_info zebra_route_map_info = { + .name = "frr-zebra-route-map", + .ignore_cfg_cbs = true, + .nodes = { { .xpath = NULL } }, +}; + +/* + * List of YANG modules to be loaded in the process context of + * MGMTd. + */ +static const struct frr_yang_module_info *const mgmt_yang_modules[] = { + &frr_filter_cli_info, + &frr_interface_cli_info, + &frr_route_map_cli_info, + &frr_routing_cli_info, + &frr_vrf_cli_info, + &frr_affinity_map_cli_info, + + /* mgmtd-only modules */ + &ietf_netconf_with_defaults_info, + + /* + * YANG module info used by backend clients get added here. + */ + + &frr_zebra_cli_info, + &zebra_route_map_info, + &ietf_key_chain_cli_info, + &ietf_key_chain_deviation_info, + +#ifdef HAVE_RIPD + &frr_ripd_cli_info, +#endif +#ifdef HAVE_RIPNGD + &frr_ripngd_cli_info, +#endif +#ifdef HAVE_STATICD + &frr_staticd_cli_info, +#endif +}; + +/* clang-format off */ +FRR_DAEMON_INFO(mgmtd, MGMTD, + .vty_port = MGMTD_VTY_PORT, + .proghelp = "FRR Management Daemon.", + + .signals = mgmt_signals, + .n_signals = array_size(mgmt_signals), + + .privs = &mgmt_privs, + + .yang_modules = mgmt_yang_modules, + .n_yang_modules = array_size(mgmt_yang_modules), + + /* avoid libfrr trying to read our config file for us */ + .flags = FRR_MANUAL_VTY_START | FRR_NO_SPLIT_CONFIG, + ); +/* clang-format on */ + +#define DEPRECATED_OPTIONS "" + +struct frr_daemon_info *mgmt_daemon_info = &mgmtd_di; + +/* Main routine of mgmt. Treatment of argument and start mgmt finite + * state machine is handled at here. + */ +int main(int argc, char **argv) +{ + int opt; + int buffer_size = MGMTD_SOCKET_BUF_SIZE; + + frr_preinit(&mgmtd_di, argc, argv); + frr_opt_add( + "s:n" DEPRECATED_OPTIONS, longopts, + " -s, --socket_size Set MGMTD peer socket send buffer size\n" + " -n, --vrfwnetns Use NetNS as VRF backend\n"); + + /* Command line argument treatment. */ + while (1) { + opt = frr_getopt(argc, argv, 0); + + if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { + fprintf(stderr, + "The -%c option no longer exists.\nPlease refer to the manual.\n", + opt); + continue; + } + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 's': + buffer_size = atoi(optarg); + break; + case 'n': + vrf_configure_backend(VRF_BACKEND_NETNS); + break; + default: + frr_help_exit(1); + break; + } + } + + /* MGMTD master init. */ + mgmt_master_init(frr_init(), buffer_size); + + /* VRF commands initialization. */ + vrf_cmd_init(NULL); + + /* Interface commands initialization. */ + if_cmd_init(NULL); + + /* MGMTD related initialization. */ + mgmt_init(); + + frr_config_fork(); + + frr_run(mm->master); + + /* Not reached. */ + return 0; +} diff --git a/mgmtd/mgmt_memory.c b/mgmtd/mgmt_memory.c new file mode 100644 index 0000000..72ccca0 --- /dev/null +++ b/mgmtd/mgmt_memory.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mgmt memory type definitions + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mgmt_memory.h" + +/* this file is temporary in nature; definitions should be moved to the + * files they're used in + */ + +DEFINE_MGROUP(MGMTD, "mgmt"); +DEFINE_MTYPE(MGMTD, MGMTD, "instance"); +DEFINE_MTYPE(MGMTD, MGMTD_XPATH, "xpath regex"); +DEFINE_MTYPE(MGMTD, MGMTD_ERR, "error"); +DEFINE_MTYPE(MGMTD, MGMTD_BE_ADPATER, "backend adapter"); +DEFINE_MTYPE(MGMTD, MGMTD_FE_ADPATER, "frontend adapter"); +DEFINE_MTYPE(MGMTD, MGMTD_FE_SESSION, "frontend session"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN, "txn"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_REQ, "txn request"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_SETCFG_REQ, "txn set-config requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_COMMCFG_REQ, "txn commit-config requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REQ, "txn get-data requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETDATA_REPLY, "txn get-data replies"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_GETTREE_REQ, "txn get-tree requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_RPC_REQ, "txn rpc requests"); +DEFINE_MTYPE(MGMTD, MGMTD_TXN_CFG_BATCH, "txn config batches"); +DEFINE_MTYPE(MGMTD, MGMTD_CMT_INFO, "commit info"); diff --git a/mgmtd/mgmt_memory.h b/mgmtd/mgmt_memory.h new file mode 100644 index 0000000..e28586e --- /dev/null +++ b/mgmtd/mgmt_memory.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * mgmt memory type declarations + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_MEMORY_H +#define _FRR_MGMTD_MEMORY_H + +#include "memory.h" + +DECLARE_MGROUP(MGMTD); +DECLARE_MTYPE(MGMTD); +DECLARE_MTYPE(MGMTD_XPATH); +DECLARE_MTYPE(MGMTD_ERR); +DECLARE_MTYPE(MGMTD_BE_ADPATER); +DECLARE_MTYPE(MGMTD_FE_ADPATER); +DECLARE_MTYPE(MGMTD_FE_SESSION); +DECLARE_MTYPE(MGMTD_TXN); +DECLARE_MTYPE(MGMTD_TXN_REQ); +DECLARE_MTYPE(MGMTD_TXN_SETCFG_REQ); +DECLARE_MTYPE(MGMTD_TXN_COMMCFG_REQ); +DECLARE_MTYPE(MGMTD_TXN_GETDATA_REQ); +DECLARE_MTYPE(MGMTD_TXN_GETDATA_REPLY); +DECLARE_MTYPE(MGMTD_TXN_GETTREE_REQ); +DECLARE_MTYPE(MGMTD_TXN_RPC_REQ); +DECLARE_MTYPE(MGMTD_TXN_CFG_BATCH); +DECLARE_MTYPE(MGMTD_BE_ADAPTER_MSG_BUF); +DECLARE_MTYPE(MGMTD_CMT_INFO); +#endif /* _FRR_MGMTD_MEMORY_H */ diff --git a/mgmtd/mgmt_testc.c b/mgmtd/mgmt_testc.c new file mode 100644 index 0000000..8bb07ed --- /dev/null +++ b/mgmtd/mgmt_testc.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * January 29 2024, Christian Hopps + * + * Copyright (c) 2024, LabN Consulting, L.L.C. + * + */ + +#include +#include +#include "darr.h" +#include "libfrr.h" +#include "mgmt_be_client.h" +#include "northbound.h" + +/* ---------------- */ +/* Local Prototypes */ +/* ---------------- */ + +static void async_notification(struct nb_cb_notify_args *args); +static int rpc_callback(struct nb_cb_rpc_args *args); + +static void sigusr1(void); +static void sigint(void); + +/* ----------- */ +/* Global Data */ +/* ----------- */ + +/* privileges */ +static zebra_capabilities_t _caps_p[] = {}; + +struct zebra_privs_t __privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +#define OPTION_LISTEN 2000 +#define OPTION_NOTIF_COUNT 2001 +#define OPTION_TIMEOUT 2002 +const struct option longopts[] = { + { "listen", no_argument, NULL, OPTION_LISTEN }, + { "notif-count", required_argument, NULL, OPTION_NOTIF_COUNT }, + { "timeout", required_argument, NULL, OPTION_TIMEOUT }, + { 0 } +}; + + +/* Master of threads. */ +struct event_loop *master; + +struct mgmt_be_client *mgmt_be_client; + +static struct frr_daemon_info mgmtd_testc_di; + +struct frr_signal_t __signals[] = { + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +#define MGMTD_TESTC_VTY_PORT 2624 + +/* clang-format off */ +static const struct frr_yang_module_info frr_ripd_info = { + .name = "frr-ripd", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-ripd:authentication-failure", + .cbs.notify = async_notification, + }, + { + .xpath = "/frr-ripd:clear-rip-route", + .cbs.rpc = rpc_callback, + }, + { + .xpath = NULL, + } + } +}; + +static const struct frr_yang_module_info *const mgmt_yang_modules[] = { + &frr_ripd_info, +}; + +FRR_DAEMON_INFO(mgmtd_testc, MGMTD_TESTC, + .proghelp = "FRR Management Daemon Test Client.", + + .signals = __signals, + .n_signals = array_size(__signals), + + .privs = &__privs, + + .yang_modules = mgmt_yang_modules, + .n_yang_modules = array_size(mgmt_yang_modules), + + /* avoid libfrr trying to read our config file for us */ + .flags = FRR_MANUAL_VTY_START, + ); +/* clang-format on */ + +const char **__notif_xpaths; +const char **__rpc_xpaths; + +struct mgmt_be_client_cbs __client_cbs = {}; +struct event *event_timeout; + +int o_notif_count = 1; +int o_timeout; + +/* --------- */ +/* Functions */ +/* --------- */ + + +static void sigusr1(void) +{ + zlog_rotate(); +} + +static void quit(int exit_code) +{ + EVENT_OFF(event_timeout); + darr_free(__client_cbs.notif_xpaths); + darr_free(__client_cbs.rpc_xpaths); + + frr_fini(); + + exit(exit_code); +} + +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + quit(0); +} + +static void timeout(struct event *event) +{ + zlog_notice("Timeout, exiting"); + quit(1); +} + +static void success(struct event *event) +{ + zlog_notice("Success, exiting"); + quit(0); +} + +static void async_notification(struct nb_cb_notify_args *args) +{ + zlog_notice("Received YANG notification"); + + printf("{\"frr-ripd:authentication-failure\": {\"interface-name\": \"%s\"}}\n", + yang_dnode_get_string(args->dnode, "interface-name")); + + if (o_notif_count && !--o_notif_count) + quit(0); +} + +static int rpc_callback(struct nb_cb_rpc_args *args) +{ + const char *vrf = NULL; + + zlog_notice("Received YANG RPC"); + + if (yang_dnode_exists(args->input, "vrf")) + vrf = yang_dnode_get_string(args->input, "vrf"); + + printf("{\"frr-ripd:clear-rip-route\": {\"vrf\": \"%s\"}}\n", vrf); + + event_cancel(&event_timeout); + event_add_timer(master, success, NULL, 1, NULL); + + return 0; +} + +int main(int argc, char **argv) +{ + int f_listen = 0; + int i; + + frr_preinit(&mgmtd_testc_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case OPTION_LISTEN: + f_listen = 1; + break; + case OPTION_NOTIF_COUNT: + o_notif_count = atoi(optarg); + break; + case OPTION_TIMEOUT: + o_timeout = atoi(optarg); + break; + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + /* + * Setup notification listen + */ + argv += optind; + argc -= optind; + if (!argc && f_listen) { + fprintf(stderr, + "Must specify at least one notification xpath to listen to\n"); + exit(1); + } + if (argc && f_listen) { + for (i = 0; i < argc; i++) { + zlog_notice("Listen on xpath: %s", argv[i]); + darr_push(__notif_xpaths, argv[i]); + } + __client_cbs.notif_xpaths = __notif_xpaths; + __client_cbs.nnotif_xpaths = darr_len(__notif_xpaths); + } + + darr_push(__rpc_xpaths, "/frr-ripd:clear-rip-route"); + __client_cbs.rpc_xpaths = __rpc_xpaths; + __client_cbs.nrpc_xpaths = darr_len(__rpc_xpaths); + + mgmt_be_client = mgmt_be_client_create("mgmtd-testc", &__client_cbs, 0, + master); + + frr_config_fork(); + + if (o_timeout) + event_add_timer(master, timeout, NULL, o_timeout, &event_timeout); + + frr_run(master); + + /* Reached. */ + return 0; +} diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c new file mode 100644 index 0000000..0f0cccb --- /dev/null +++ b/mgmtd/mgmt_txn.c @@ -0,0 +1,2946 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Transactions + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include +#include "darr.h" +#include "hash.h" +#include "jhash.h" +#include "libfrr.h" +#include "mgmt_msg.h" +#include "mgmt_msg_native.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_memory.h" +#include "mgmtd/mgmt_txn.h" + +#define __dbg(fmt, ...) \ + DEBUGD(&mgmt_debug_txn, "TXN: %s: " fmt, __func__, ##__VA_ARGS__) +#define __log_err(fmt, ...) zlog_err("%s: ERROR: " fmt, __func__, ##__VA_ARGS__) + +#define MGMTD_TXN_LOCK(txn) mgmt_txn_lock(txn, __FILE__, __LINE__) +#define MGMTD_TXN_UNLOCK(txn) mgmt_txn_unlock(txn, __FILE__, __LINE__) + +enum mgmt_txn_event { + MGMTD_TXN_PROC_SETCFG = 1, + MGMTD_TXN_PROC_COMMITCFG, + MGMTD_TXN_PROC_GETCFG, + MGMTD_TXN_PROC_GETTREE, + MGMTD_TXN_PROC_RPC, + MGMTD_TXN_COMMITCFG_TIMEOUT, +}; + +PREDECL_LIST(mgmt_txn_reqs); + +struct mgmt_set_cfg_req { + Mgmtd__DatastoreId ds_id; + struct mgmt_ds_ctx *ds_ctx; + struct nb_cfg_change cfg_changes[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + uint16_t num_cfg_changes; + bool implicit_commit; + Mgmtd__DatastoreId dst_ds_id; + struct mgmt_ds_ctx *dst_ds_ctx; + struct mgmt_setcfg_stats *setcfg_stats; +}; + +enum mgmt_commit_phase { + MGMTD_COMMIT_PHASE_PREPARE_CFG = 0, + MGMTD_COMMIT_PHASE_TXN_CREATE, + MGMTD_COMMIT_PHASE_APPLY_CFG, + MGMTD_COMMIT_PHASE_TXN_DELETE, + MGMTD_COMMIT_PHASE_MAX +}; + +static inline const char *mgmt_commit_phase2str(enum mgmt_commit_phase cmt_phase) +{ + switch (cmt_phase) { + case MGMTD_COMMIT_PHASE_PREPARE_CFG: + return "PREP-CFG"; + case MGMTD_COMMIT_PHASE_TXN_CREATE: + return "CREATE-TXN"; + case MGMTD_COMMIT_PHASE_APPLY_CFG: + return "APPLY-CFG"; + case MGMTD_COMMIT_PHASE_TXN_DELETE: + return "DELETE-TXN"; + case MGMTD_COMMIT_PHASE_MAX: + return "Invalid/Unknown"; + } + + return "Invalid/Unknown"; +} + +PREDECL_LIST(mgmt_txn_batches); + +struct mgmt_txn_be_cfg_batch { + struct mgmt_txn_ctx *txn; + enum mgmt_be_client_id be_id; + struct mgmt_be_client_adapter *be_adapter; + Mgmtd__YangCfgDataReq cfg_data[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + Mgmtd__YangCfgDataReq *cfg_datap[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + Mgmtd__YangData data[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + Mgmtd__YangDataValue value[MGMTD_MAX_CFG_CHANGES_IN_BATCH]; + size_t num_cfg_data; + int buf_space_left; + struct mgmt_txn_batches_item list_linkage; +}; + +DECLARE_LIST(mgmt_txn_batches, struct mgmt_txn_be_cfg_batch, list_linkage); + +#define FOREACH_TXN_CFG_BATCH_IN_LIST(list, batch) \ + frr_each_safe (mgmt_txn_batches, list, batch) + +struct mgmt_edit_req { + char xpath_created[XPATH_MAXLEN]; + bool unlock; +}; + +struct mgmt_commit_cfg_req { + Mgmtd__DatastoreId src_ds_id; + struct mgmt_ds_ctx *src_ds_ctx; + Mgmtd__DatastoreId dst_ds_id; + struct mgmt_ds_ctx *dst_ds_ctx; + uint32_t nb_txn_id; + uint8_t validate_only : 1; + uint8_t abort : 1; + uint8_t implicit : 1; + uint8_t rollback : 1; + uint8_t init : 1; + + /* Track commit phases */ + enum mgmt_commit_phase phase; + + enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX]; + + /* + * Additional information when the commit is triggered by native edit + * request. + */ + struct mgmt_edit_req *edit; + + /* + * Set of config changes to commit. This is used only + * when changes are NOT to be determined by comparing + * candidate and running DSs. This is typically used + * for downloading all relevant configs for a new backend + * client that has recently come up and connected with + * MGMTD. + */ + struct nb_config_cbs *cfg_chgs; + + /* + * Details on all the Backend Clients associated with + * this commit. + */ + uint64_t clients; + + /* + * List of backend batches for this commit to be validated + * and applied at the backend. + */ + struct mgmt_txn_batches_head batches[MGMTD_BE_CLIENT_ID_MAX]; + /* + * The last batch added for any backend client. + */ + struct mgmt_txn_be_cfg_batch *last_be_cfg_batch[MGMTD_BE_CLIENT_ID_MAX]; + + struct mgmt_commit_stats *cmt_stats; +}; + +struct mgmt_get_data_reply { + /* Buffer space for preparing data reply */ + int num_reply; + int last_batch; + Mgmtd__YangDataReply data_reply; + Mgmtd__YangData reply_data[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + Mgmtd__YangData *reply_datap[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + Mgmtd__YangDataValue reply_value[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; + char *reply_xpathp[MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH]; +}; + +struct mgmt_get_data_req { + Mgmtd__DatastoreId ds_id; + struct nb_config *cfg_root; + char *xpaths[MGMTD_MAX_NUM_DATA_REQ_IN_BATCH]; + int num_xpaths; + + /* + * Buffer space for preparing reply. + * NOTE: Should only be malloc-ed on demand to reduce + * memory footprint. Freed up via mgmt_trx_req_free() + */ + struct mgmt_get_data_reply *reply; + + int total_reply; +}; + + +struct txn_req_get_tree { + char *xpath; /* xpath of tree to get */ + uint64_t sent_clients; /* Bitmask of clients sent req to */ + uint64_t recv_clients; /* Bitmask of clients recv reply from */ + int32_t partial_error; /* an error while gather results */ + uint8_t result_type; /* LYD_FORMAT for results */ + uint8_t wd_options; /* LYD_PRINT_WD_* flags for results */ + uint8_t exact; /* if exact node is requested */ + uint8_t simple_xpath; /* if xpath is simple */ + struct lyd_node *client_results; /* result tree from clients */ +}; + +struct txn_req_rpc { + char *xpath; /* xpath of rpc/action to invoke */ + uint64_t sent_clients; /* Bitmask of clients sent req to */ + uint64_t recv_clients; /* Bitmask of clients recv reply from */ + uint8_t result_type; /* LYD_FORMAT for results */ + char *errstr; /* error string */ + struct lyd_node *client_results; /* result tree from clients */ +}; + +struct mgmt_txn_req { + struct mgmt_txn_ctx *txn; + enum mgmt_txn_event req_event; + uint64_t req_id; + union { + struct mgmt_set_cfg_req *set_cfg; + struct mgmt_get_data_req *get_data; + struct txn_req_get_tree *get_tree; + struct txn_req_rpc *rpc; + struct mgmt_commit_cfg_req commit_cfg; + } req; + + struct mgmt_txn_reqs_item list_linkage; +}; + +DECLARE_LIST(mgmt_txn_reqs, struct mgmt_txn_req, list_linkage); + +#define FOREACH_TXN_REQ_IN_LIST(list, req) \ + frr_each_safe (mgmt_txn_reqs, list, req) + +struct mgmt_txn_ctx { + uint64_t session_id; /* One transaction per client session */ + uint64_t txn_id; + enum mgmt_txn_type type; + + /* struct mgmt_master *mm; */ + + struct event *proc_set_cfg; + struct event *proc_comm_cfg; + struct event *proc_get_cfg; + struct event *proc_get_data; + struct event *proc_get_tree; + struct event *comm_cfg_timeout; + struct event *get_tree_timeout; + struct event *rpc_timeout; + struct event *clnup; + + /* List of backend adapters involved in this transaction */ + struct mgmt_txn_badapters_head be_adapters; + + int refcount; + + struct mgmt_txns_item list_linkage; + + /* TODO: why do we need unique lists for each type of transaction since + * a transaction is of only 1 type? + */ + + /* + * List of pending set-config requests for a given + * transaction/session. Just one list for requests + * not processed at all. There's no backend interaction + * involved. + */ + struct mgmt_txn_reqs_head set_cfg_reqs; + /* + * List of pending get-config requests for a given + * transaction/session. Just one list for requests + * not processed at all. There's no backend interaction + * involved. + */ + struct mgmt_txn_reqs_head get_cfg_reqs; + /* + * List of pending get-tree requests. + */ + struct mgmt_txn_reqs_head get_tree_reqs; + /* + * List of pending rpc requests. + */ + struct mgmt_txn_reqs_head rpc_reqs; + /* + * There will always be one commit-config allowed for a given + * transaction/session. No need to maintain lists for it. + */ + struct mgmt_txn_req *commit_cfg_req; +}; + +DECLARE_LIST(mgmt_txns, struct mgmt_txn_ctx, list_linkage); + +#define FOREACH_TXN_IN_LIST(mm, txn) \ + frr_each_safe (mgmt_txns, &(mm)->txn_list, (txn)) + +static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, + enum mgmt_result result, + const char *error_if_any); + +static inline const char *mgmt_txn_commit_phase_str(struct mgmt_txn_ctx *txn) +{ + if (!txn->commit_cfg_req) + return "None"; + + return mgmt_commit_phase2str(txn->commit_cfg_req->req.commit_cfg.phase); +} + +static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line); +static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file, + int line); +static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, + struct mgmt_be_client_adapter *adapter); + +static struct event_loop *mgmt_txn_tm; +static struct mgmt_master *mgmt_txn_mm; + +static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, + enum mgmt_txn_event event); + +static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn); + +static struct mgmt_txn_be_cfg_batch * +mgmt_txn_cfg_batch_alloc(struct mgmt_txn_ctx *txn, enum mgmt_be_client_id id, + struct mgmt_be_client_adapter *be_adapter) +{ + struct mgmt_txn_be_cfg_batch *batch; + + batch = XCALLOC(MTYPE_MGMTD_TXN_CFG_BATCH, + sizeof(struct mgmt_txn_be_cfg_batch)); + assert(batch); + batch->be_id = id; + + batch->txn = txn; + MGMTD_TXN_LOCK(txn); + assert(txn->commit_cfg_req); + mgmt_txn_batches_add_tail(&txn->commit_cfg_req->req.commit_cfg + .batches[id], + batch); + batch->be_adapter = be_adapter; + batch->buf_space_left = MGMTD_BE_CFGDATA_MAX_MSG_LEN; + if (be_adapter) + mgmt_be_adapter_lock(be_adapter); + + txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] = batch; + + return batch; +} + +static void mgmt_txn_cfg_batch_free(struct mgmt_txn_be_cfg_batch **batch) +{ + size_t indx; + struct mgmt_commit_cfg_req *cmtcfg_req; + + __dbg(" freeing batch txn-id %" PRIu64, (*batch)->txn->txn_id); + + assert((*batch)->txn && (*batch)->txn->type == MGMTD_TXN_TYPE_CONFIG); + + cmtcfg_req = &(*batch)->txn->commit_cfg_req->req.commit_cfg; + mgmt_txn_batches_del(&cmtcfg_req->batches[(*batch)->be_id], *batch); + + if ((*batch)->be_adapter) + mgmt_be_adapter_unlock(&(*batch)->be_adapter); + + for (indx = 0; indx < (*batch)->num_cfg_data; indx++) { + if ((*batch)->data[indx].xpath) { + free((*batch)->data[indx].xpath); + (*batch)->data[indx].xpath = NULL; + } + } + + MGMTD_TXN_UNLOCK(&(*batch)->txn); + + XFREE(MTYPE_MGMTD_TXN_CFG_BATCH, *batch); + *batch = NULL; +} + +static void mgmt_txn_cleanup_be_cfg_batches(struct mgmt_txn_ctx *txn, + enum mgmt_be_client_id id) +{ + struct mgmt_txn_be_cfg_batch *batch; + struct mgmt_txn_batches_head *list; + + list = &txn->commit_cfg_req->req.commit_cfg.batches[id]; + FOREACH_TXN_CFG_BATCH_IN_LIST (list, batch) + mgmt_txn_cfg_batch_free(&batch); + + mgmt_txn_batches_fini(list); + + txn->commit_cfg_req->req.commit_cfg.last_be_cfg_batch[id] = NULL; +} + +static struct mgmt_txn_req *mgmt_txn_req_alloc(struct mgmt_txn_ctx *txn, + uint64_t req_id, + enum mgmt_txn_event req_event) +{ + struct mgmt_txn_req *txn_req; + enum mgmt_be_client_id id; + + txn_req = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_txn_req)); + assert(txn_req); + txn_req->txn = txn; + txn_req->req_id = req_id; + txn_req->req_event = req_event; + + switch (txn_req->req_event) { + case MGMTD_TXN_PROC_SETCFG: + txn_req->req.set_cfg = XCALLOC(MTYPE_MGMTD_TXN_SETCFG_REQ, + sizeof(struct mgmt_set_cfg_req)); + assert(txn_req->req.set_cfg); + mgmt_txn_reqs_add_tail(&txn->set_cfg_reqs, txn_req); + __dbg("Added a new SETCFG req-id: %" PRIu64 " txn-id: %" PRIu64 + ", session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + break; + case MGMTD_TXN_PROC_COMMITCFG: + txn->commit_cfg_req = txn_req; + __dbg("Added a new COMMITCFG req-id: %" PRIu64 + " txn-id: %" PRIu64 " session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + txn_req->req.commit_cfg.be_phase[id] = + MGMTD_COMMIT_PHASE_PREPARE_CFG; + mgmt_txn_batches_init( + &txn_req->req.commit_cfg.batches[id]); + } + + txn_req->req.commit_cfg.phase = MGMTD_COMMIT_PHASE_PREPARE_CFG; + break; + case MGMTD_TXN_PROC_GETCFG: + txn_req->req.get_data = + XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REQ, + sizeof(struct mgmt_get_data_req)); + assert(txn_req->req.get_data); + mgmt_txn_reqs_add_tail(&txn->get_cfg_reqs, txn_req); + __dbg("Added a new GETCFG req-id: %" PRIu64 " txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + break; + case MGMTD_TXN_PROC_GETTREE: + txn_req->req.get_tree = XCALLOC(MTYPE_MGMTD_TXN_GETTREE_REQ, + sizeof(struct txn_req_get_tree)); + mgmt_txn_reqs_add_tail(&txn->get_tree_reqs, txn_req); + __dbg("Added a new GETTREE req-id: %" PRIu64 " txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + break; + case MGMTD_TXN_PROC_RPC: + txn_req->req.rpc = XCALLOC(MTYPE_MGMTD_TXN_RPC_REQ, + sizeof(struct txn_req_rpc)); + assert(txn_req->req.rpc); + mgmt_txn_reqs_add_tail(&txn->rpc_reqs, txn_req); + __dbg("Added a new RPC req-id: %" PRIu64 " txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn_req->req_id, txn->txn_id, txn->session_id); + break; + case MGMTD_TXN_COMMITCFG_TIMEOUT: + break; + } + + MGMTD_TXN_LOCK(txn); + + return txn_req; +} + +static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req) +{ + int indx; + struct mgmt_txn_reqs_head *req_list = NULL; + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + struct mgmt_commit_cfg_req *ccreq; + struct mgmt_set_cfg_req *set_cfg; + bool cleanup; + + switch ((*txn_req)->req_event) { + case MGMTD_TXN_PROC_SETCFG: + set_cfg = (*txn_req)->req.set_cfg; + for (indx = 0; indx < set_cfg->num_cfg_changes; indx++) { + if (set_cfg->cfg_changes[indx].value) + free((void *)set_cfg->cfg_changes[indx].value); + } + req_list = &(*txn_req)->txn->set_cfg_reqs; + __dbg("Deleting SETCFG req-id: %" PRIu64 " txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + XFREE(MTYPE_MGMTD_TXN_SETCFG_REQ, (*txn_req)->req.set_cfg); + break; + case MGMTD_TXN_PROC_COMMITCFG: + __dbg("Deleting COMMITCFG req-id: %" PRIu64 " txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + + ccreq = &(*txn_req)->req.commit_cfg; + cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE && + ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE); + + XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit); + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + /* + * Send TXN_DELETE to cleanup state for this + * transaction on backend + */ + + /* + * Get rid of the batches first so we don't end up doing + * anything more with them + */ + mgmt_txn_cleanup_be_cfg_batches((*txn_req)->txn, id); + + /* + * If we were in the middle of the state machine then + * send a txn delete message + */ + adapter = mgmt_be_get_adapter_by_id(id); + if (adapter && cleanup && IS_IDBIT_SET(ccreq->clients, id)) + mgmt_txn_send_be_txn_delete((*txn_req)->txn, + adapter); + } + break; + case MGMTD_TXN_PROC_GETCFG: + for (indx = 0; indx < (*txn_req)->req.get_data->num_xpaths; + indx++) { + if ((*txn_req)->req.get_data->xpaths[indx]) + free((void *)(*txn_req) + ->req.get_data->xpaths[indx]); + } + req_list = &(*txn_req)->txn->get_cfg_reqs; + __dbg("Deleting GETCFG req-id: %" PRIu64 " txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + if ((*txn_req)->req.get_data->reply) + XFREE(MTYPE_MGMTD_TXN_GETDATA_REPLY, + (*txn_req)->req.get_data->reply); + + if ((*txn_req)->req.get_data->cfg_root) + nb_config_free((*txn_req)->req.get_data->cfg_root); + + XFREE(MTYPE_MGMTD_TXN_GETDATA_REQ, (*txn_req)->req.get_data); + break; + case MGMTD_TXN_PROC_GETTREE: + __dbg("Deleting GETTREE req-id: %" PRIu64 " of txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + req_list = &(*txn_req)->txn->get_tree_reqs; + lyd_free_all((*txn_req)->req.get_tree->client_results); + XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.get_tree->xpath); + XFREE(MTYPE_MGMTD_TXN_GETTREE_REQ, (*txn_req)->req.get_tree); + break; + case MGMTD_TXN_PROC_RPC: + __dbg("Deleting RPC req-id: %" PRIu64 " txn-id: %" PRIu64, + (*txn_req)->req_id, (*txn_req)->txn->txn_id); + req_list = &(*txn_req)->txn->rpc_reqs; + lyd_free_all((*txn_req)->req.rpc->client_results); + XFREE(MTYPE_MGMTD_ERR, (*txn_req)->req.rpc->errstr); + XFREE(MTYPE_MGMTD_XPATH, (*txn_req)->req.rpc->xpath); + XFREE(MTYPE_MGMTD_TXN_RPC_REQ, (*txn_req)->req.rpc); + break; + case MGMTD_TXN_COMMITCFG_TIMEOUT: + break; + } + + if (req_list) { + mgmt_txn_reqs_del(req_list, *txn_req); + __dbg("Removed req-id: %" PRIu64 " from request-list (left:%zu)", + (*txn_req)->req_id, mgmt_txn_reqs_count(req_list)); + } + + MGMTD_TXN_UNLOCK(&(*txn_req)->txn); + XFREE(MTYPE_MGMTD_TXN_REQ, (*txn_req)); + *txn_req = NULL; +} + +static void mgmt_txn_process_set_cfg(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct mgmt_ds_ctx *ds_ctx; + struct nb_config *nb_config; + char err_buf[1024]; + bool error; + int num_processed = 0; + size_t left; + struct mgmt_commit_stats *cmt_stats; + int ret = 0; + + txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); + assert(txn); + cmt_stats = mgmt_fe_get_session_commit_stats(txn->session_id); + + __dbg("Processing %zu SET_CONFIG requests txn-id:%" PRIu64 + " session-id: %" PRIu64, + mgmt_txn_reqs_count(&txn->set_cfg_reqs), txn->txn_id, + txn->session_id); + + FOREACH_TXN_REQ_IN_LIST (&txn->set_cfg_reqs, txn_req) { + assert(txn_req->req_event == MGMTD_TXN_PROC_SETCFG); + ds_ctx = txn_req->req.set_cfg->ds_ctx; + if (!ds_ctx) { + mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id, + txn_req->req.set_cfg->ds_id, + txn_req->req_id, + MGMTD_INTERNAL_ERROR, + "No such datastore!", + txn_req->req.set_cfg + ->implicit_commit); + goto mgmt_txn_process_set_cfg_done; + } + + nb_config = mgmt_ds_get_nb_config(ds_ctx); + if (!nb_config) { + mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id, + txn_req->req.set_cfg->ds_id, + txn_req->req_id, + MGMTD_INTERNAL_ERROR, + "Unable to retrieve DS Config Tree!", + txn_req->req.set_cfg + ->implicit_commit); + goto mgmt_txn_process_set_cfg_done; + } + + error = false; + nb_candidate_edit_config_changes(nb_config, + txn_req->req.set_cfg->cfg_changes, + (size_t)txn_req->req.set_cfg + ->num_cfg_changes, + NULL, false, err_buf, + sizeof(err_buf), &error); + if (error) { + mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id, + txn_req->req.set_cfg->ds_id, + txn_req->req_id, + MGMTD_INTERNAL_ERROR, err_buf, + txn_req->req.set_cfg + ->implicit_commit); + goto mgmt_txn_process_set_cfg_done; + } + + if (txn_req->req.set_cfg->implicit_commit) { + assert(mgmt_txn_reqs_count(&txn->set_cfg_reqs) == 1); + assert(txn_req->req.set_cfg->dst_ds_ctx); + + /* We expect the user to have locked the DST DS */ + if (!mgmt_ds_is_locked(txn_req->req.set_cfg->dst_ds_ctx, + txn->session_id)) { + __log_err("DS %u not locked for implicit commit txn-id: %" PRIu64 + " session-id: %" PRIu64 " err: %s", + txn_req->req.set_cfg->dst_ds_id, + txn->txn_id, txn->session_id, + strerror(ret)); + mgmt_fe_send_set_cfg_reply( + txn->session_id, txn->txn_id, + txn_req->req.set_cfg->ds_id, + txn_req->req_id, MGMTD_DS_LOCK_FAILED, + "running DS not locked for implicit commit", + txn_req->req.set_cfg->implicit_commit); + goto mgmt_txn_process_set_cfg_done; + } + + mgmt_txn_send_commit_config_req(txn->txn_id, + txn_req->req_id, + txn_req->req.set_cfg + ->ds_id, + txn_req->req.set_cfg + ->ds_ctx, + txn_req->req.set_cfg + ->dst_ds_id, + txn_req->req.set_cfg + ->dst_ds_ctx, + false, false, true, + NULL); + + if (mm->perf_stats_en) + gettimeofday(&cmt_stats->last_start, NULL); + cmt_stats->commit_cnt++; + } else if (mgmt_fe_send_set_cfg_reply(txn->session_id, + txn->txn_id, + txn_req->req.set_cfg->ds_id, + txn_req->req_id, + MGMTD_SUCCESS, NULL, + false) != 0) { + __log_err("Failed to send SET_CONFIG_REPLY txn-id %" PRIu64 + " session-id: %" PRIu64, + txn->txn_id, txn->session_id); + } + +mgmt_txn_process_set_cfg_done: + + /* + * Note: The following will remove it from the list as well. + */ + mgmt_txn_req_free(&txn_req); + + num_processed++; + if (num_processed == MGMTD_TXN_MAX_NUM_SETCFG_PROC) + break; + } + + left = mgmt_txn_reqs_count(&txn->set_cfg_reqs); + if (left) { + __dbg("Processed maximum number of Set-Config requests (%d/%d/%d). Rescheduling for rest.", + num_processed, MGMTD_TXN_MAX_NUM_SETCFG_PROC, (int)left); + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_SETCFG); + } +} + +static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn, + enum mgmt_result result, + const char *error_if_any) +{ + bool success, create_cmt_info_rec; + + if (!txn->commit_cfg_req) + return -1; + + success = (result == MGMTD_SUCCESS || result == MGMTD_NO_CFG_CHANGES); + + /* TODO: these replies should not be send if it's a rollback + * b/c right now that is special cased.. that special casing should be + * removed; however... + */ + if (!txn->commit_cfg_req->req.commit_cfg.edit && + !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && + !txn->commit_cfg_req->req.commit_cfg.rollback && + mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id, + txn->commit_cfg_req->req.commit_cfg + .src_ds_id, + txn->commit_cfg_req->req.commit_cfg + .dst_ds_id, + txn->commit_cfg_req->req_id, + txn->commit_cfg_req->req.commit_cfg + .validate_only, + result, error_if_any) != 0) { + __log_err("Failed to send COMMIT-CONFIG-REPLY txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn->txn_id, txn->session_id); + } + + if (!txn->commit_cfg_req->req.commit_cfg.edit && + txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id && + !txn->commit_cfg_req->req.commit_cfg.rollback && + mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id, + txn->commit_cfg_req->req.commit_cfg + .src_ds_id, + txn->commit_cfg_req->req_id, + success ? MGMTD_SUCCESS + : MGMTD_INTERNAL_ERROR, + error_if_any, true) != 0) { + __log_err("Failed to send SET-CONFIG-REPLY txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn->txn_id, txn->session_id); + } + + if (txn->commit_cfg_req->req.commit_cfg.edit && + mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, + txn->commit_cfg_req->req_id, + txn->commit_cfg_req->req.commit_cfg + .edit->unlock, + true, + txn->commit_cfg_req->req.commit_cfg + .edit->xpath_created, + success ? 0 : -1, + error_if_any) != 0) { + __log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64 + " session-id: %" PRIu64, + txn->txn_id, txn->session_id); + } + + if (success) { + /* Stop the commit-timeout timer */ + /* XXX why only on success? */ + EVENT_OFF(txn->comm_cfg_timeout); + + create_cmt_info_rec = + (result != MGMTD_NO_CFG_CHANGES && + !txn->commit_cfg_req->req.commit_cfg.rollback); + + /* + * Successful commit: Merge Src DS into Dst DS if and only if + * this was not a validate-only or abort request. + */ + if ((txn->session_id && + !txn->commit_cfg_req->req.commit_cfg.validate_only && + !txn->commit_cfg_req->req.commit_cfg.abort) || + txn->commit_cfg_req->req.commit_cfg.rollback) { + mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg + .src_ds_ctx, + txn->commit_cfg_req->req.commit_cfg + .dst_ds_ctx, + create_cmt_info_rec); + } + + /* + * Restore Src DS back to Dest DS only through a commit abort + * request. + */ + if (txn->session_id && txn->commit_cfg_req->req.commit_cfg.abort) + mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg + .dst_ds_ctx, + txn->commit_cfg_req->req.commit_cfg + .src_ds_ctx, + false); + } else { + /* + * The commit has failied. For implicit commit requests restore + * back the contents of the candidate DS. + */ + if (txn->commit_cfg_req->req.commit_cfg.implicit) + mgmt_ds_copy_dss(txn->commit_cfg_req->req.commit_cfg + .dst_ds_ctx, + txn->commit_cfg_req->req.commit_cfg + .src_ds_ctx, + false); + } + + if (txn->commit_cfg_req->req.commit_cfg.rollback) { + mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); + mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); + /* + * Resume processing the rollback command. + * + * TODO: there's no good reason to special case rollback, the + * rollback boolean should be passed back to the FE client and it + * can do the right thing. + */ + mgmt_history_rollback_complete(success); + } + + if (txn->commit_cfg_req->req.commit_cfg.init) { + /* + * This is the backend init request. + * We need to unlock the running datastore. + */ + mgmt_ds_unlock(txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx); + } + + txn->commit_cfg_req->req.commit_cfg.cmt_stats = NULL; + mgmt_txn_req_free(&txn->commit_cfg_req); + + /* + * The CONFIG Transaction should be destroyed from Frontend-adapter. + * But in case the transaction is not triggered from a front-end session + * we need to cleanup by itself. + */ + if (!txn->session_id) + mgmt_txn_cleanup_txn(&txn); + + return 0; +} + +static int +mgmt_try_move_commit_to_next_phase(struct mgmt_txn_ctx *txn, + struct mgmt_commit_cfg_req *cmtcfg_req) +{ + enum mgmt_be_client_id id; + + __dbg("txn-id: %" PRIu64 ", Phase '%s'", txn->txn_id, + mgmt_txn_commit_phase_str(txn)); + + /* + * Check if all clients has moved to next phase or not. + */ + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (IS_IDBIT_SET(cmtcfg_req->clients, id) && + cmtcfg_req->be_phase[id] == cmtcfg_req->phase) { + /* + * There's atleast once client who hasn't moved to + * next phase. + * + * TODO: Need to re-think this design for the case + * set of validators for a given YANG data item is + * different from the set of notifiers for the same. + */ + return -1; + } + } + + /* + * If we are here, it means all the clients has moved to next phase. + * So we can move the whole commit to next phase. + */ + cmtcfg_req->phase++; + + __dbg("Move entire txn-id: %" PRIu64 " to phase '%s'", txn->txn_id, + mgmt_txn_commit_phase_str(txn)); + + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG); + + return 0; +} + +/* + * This is the real workhorse + */ +static int mgmt_txn_create_config_batches(struct mgmt_txn_req *txn_req, + struct nb_config_cbs *changes) +{ + struct nb_config_cb *cb, *nxt; + struct nb_config_change *chg; + struct mgmt_txn_be_cfg_batch *batch; + char *xpath = NULL, *value = NULL; + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + struct mgmt_commit_cfg_req *cmtcfg_req; + int num_chgs = 0; + int xpath_len, value_len; + uint64_t clients, chg_clients; + + cmtcfg_req = &txn_req->req.commit_cfg; + + RB_FOREACH_SAFE (cb, nb_config_cbs, changes, nxt) { + chg = (struct nb_config_change *)cb; + + /* + * Could have directly pointed to xpath in nb_node. + * But dont want to mess with it now. + * xpath = chg->cb.nb_node->xpath; + */ + xpath = lyd_path(chg->cb.dnode, LYD_PATH_STD, NULL, 0); + if (!xpath) { + (void)mgmt_txn_send_commit_cfg_reply( + txn_req->txn, MGMTD_INTERNAL_ERROR, + "Internal error! Could not get Xpath from Ds node!"); + return -1; + } + + value = (char *)lyd_get_value(chg->cb.dnode); + if (!value) + value = (char *)MGMTD_BE_CONTAINER_NODE_VAL; + + __dbg("XPATH: %s, Value: '%s'", xpath, value ? value : "NIL"); + + clients = + mgmt_be_interested_clients(xpath, + MGMT_BE_XPATH_SUBSCR_TYPE_CFG); + + chg_clients = 0; + + xpath_len = strlen(xpath) + 1; + value_len = strlen(value) + 1; + FOREACH_BE_CLIENT_BITS (id, clients) { + adapter = mgmt_be_get_adapter_by_id(id); + if (!adapter) + continue; + + chg_clients |= (1ull << id); + + batch = cmtcfg_req->last_be_cfg_batch[id]; + if (!batch || + (batch->num_cfg_data == + MGMTD_MAX_CFG_CHANGES_IN_BATCH) || + (batch->buf_space_left < (xpath_len + value_len))) { + /* Allocate a new config batch */ + batch = mgmt_txn_cfg_batch_alloc(txn_req->txn, + id, adapter); + } + + batch->buf_space_left -= (xpath_len + value_len); + + mgmt_yang_cfg_data_req_init( + &batch->cfg_data[batch->num_cfg_data]); + batch->cfg_datap[batch->num_cfg_data] = + &batch->cfg_data[batch->num_cfg_data]; + + /* + * On the backend, we don't really care if it's CREATE + * or MODIFY, because the existence was already checked + * on the frontend. Therefore we use SET for both. + */ + if (chg->cb.operation == NB_CB_DESTROY) + batch->cfg_data[batch->num_cfg_data].req_type = + MGMTD__CFG_DATA_REQ_TYPE__REMOVE_DATA; + else + batch->cfg_data[batch->num_cfg_data].req_type = + MGMTD__CFG_DATA_REQ_TYPE__SET_DATA; + + mgmt_yang_data_init(&batch->data[batch->num_cfg_data]); + batch->cfg_data[batch->num_cfg_data].data = + &batch->data[batch->num_cfg_data]; + batch->data[batch->num_cfg_data].xpath = strdup(xpath); + + mgmt_yang_data_value_init( + &batch->value[batch->num_cfg_data]); + batch->data[batch->num_cfg_data].value = + &batch->value[batch->num_cfg_data]; + batch->value[batch->num_cfg_data].value_case = + MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL; + batch->value[batch->num_cfg_data].encoded_str_val = + value; + + __dbg(" -- %s, batch item:%d", adapter->name, + (int)batch->num_cfg_data); + + batch->num_cfg_data++; + num_chgs++; + } + + if (!chg_clients) + __dbg("Daemons interested in XPATH are not currently connected: %s", + xpath); + + cmtcfg_req->clients |= chg_clients; + + free(xpath); + } + + cmtcfg_req->cmt_stats->last_batch_cnt = num_chgs; + if (!num_chgs) { + (void)mgmt_txn_send_commit_cfg_reply(txn_req->txn, + MGMTD_NO_CFG_CHANGES, + "No connected daemons interested in changes"); + return -1; + } + + /* Move all BE clients to create phase */ + FOREACH_MGMTD_BE_CLIENT_ID(id) { + if (IS_IDBIT_SET(cmtcfg_req->clients, id)) + cmtcfg_req->be_phase[id] = + MGMTD_COMMIT_PHASE_TXN_CREATE; + } + + return 0; +} + +static int mgmt_txn_prepare_config(struct mgmt_txn_ctx *txn) +{ + struct nb_context nb_ctx; + struct nb_config *nb_config; + struct nb_config_cbs changes; + struct nb_config_cbs *cfg_chgs = NULL; + int ret; + bool del_cfg_chgs = false; + + ret = 0; + memset(&nb_ctx, 0, sizeof(nb_ctx)); + memset(&changes, 0, sizeof(changes)); + if (txn->commit_cfg_req->req.commit_cfg.cfg_chgs) { + cfg_chgs = txn->commit_cfg_req->req.commit_cfg.cfg_chgs; + del_cfg_chgs = true; + goto mgmt_txn_prep_config_validation_done; + } + + if (txn->commit_cfg_req->req.commit_cfg.src_ds_id != MGMTD_DS_CANDIDATE) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INVALID_PARAM, + "Source DS cannot be any other than CANDIDATE!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + if (txn->commit_cfg_req->req.commit_cfg.dst_ds_id != MGMTD_DS_RUNNING) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INVALID_PARAM, + "Destination DS cannot be any other than RUNNING!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + if (!txn->commit_cfg_req->req.commit_cfg.src_ds_ctx) { + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, + "No such source datastore!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + if (!txn->commit_cfg_req->req.commit_cfg.dst_ds_ctx) { + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, + "No such destination datastore!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + if (txn->commit_cfg_req->req.commit_cfg.abort) { + /* + * This is a commit abort request. Return back success. + * That should trigger a restore of Candidate datastore to + * Running. + */ + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); + goto mgmt_txn_prepare_config_done; + } + + nb_config = mgmt_ds_get_nb_config( + txn->commit_cfg_req->req.commit_cfg.src_ds_ctx); + if (!nb_config) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Unable to retrieve Commit DS Config Tree!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + /* + * Validate YANG contents of the source DS and get the diff + * between source and destination DS contents. + */ + char err_buf[BUFSIZ] = { 0 }; + + ret = nb_candidate_validate_yang(nb_config, true, err_buf, + sizeof(err_buf) - 1); + if (ret != NB_OK) { + if (strncmp(err_buf, " ", strlen(err_buf)) == 0) + strlcpy(err_buf, "Validation failed", sizeof(err_buf)); + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, + err_buf); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + nb_config_diff(mgmt_ds_get_nb_config(txn->commit_cfg_req->req.commit_cfg + .dst_ds_ctx), + nb_config, &changes); + cfg_chgs = &changes; + del_cfg_chgs = true; + + if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { + /* + * This means there's no changes to commit whatsoever + * is the source of the changes in config. + */ + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_NO_CFG_CHANGES, + "No changes found to be committed!"); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + +#ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED + if (mm->perf_stats_en) + gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats + ->validate_start, + NULL); + /* + * Perform application level validations locally on the MGMTD + * process by calling application specific validation routines + * loaded onto MGMTD process using libraries. + */ + nb_ctx.client = NB_CLIENT_MGMTD_SERVER; + nb_ctx.user = (void *)txn; + ret = nb_candidate_validate_code(&nb_ctx, nb_config, &changes, err_buf, + sizeof(err_buf) - 1); + if (ret != NB_OK) { + if (strncmp(err_buf, " ", strlen(err_buf)) == 0) + strlcpy(err_buf, "Validation failed", sizeof(err_buf)); + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_INVALID_PARAM, + err_buf); + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + if (txn->commit_cfg_req->req.commit_cfg.validate_only) { + /* + * This was a validate-only COMMIT request return success. + */ + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); + goto mgmt_txn_prepare_config_done; + } +#endif /* ifdef MGMTD_LOCAL_VALIDATIONS_ENABLED */ + +mgmt_txn_prep_config_validation_done: + + if (mm->perf_stats_en) + gettimeofday(&txn->commit_cfg_req->req.commit_cfg.cmt_stats + ->prep_cfg_start, + NULL); + + /* + * Iterate over the diffs and create ordered batches of config + * commands to be validated. + */ + ret = mgmt_txn_create_config_batches(txn->commit_cfg_req, cfg_chgs); + if (ret != 0) { + ret = -1; + goto mgmt_txn_prepare_config_done; + } + + /* Move to the Transaction Create Phase */ + txn->commit_cfg_req->req.commit_cfg.phase = + MGMTD_COMMIT_PHASE_TXN_CREATE; + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG); + + /* + * Start the COMMIT Timeout Timer to abort Txn if things get stuck at + * backend. + */ + mgmt_txn_register_event(txn, MGMTD_TXN_COMMITCFG_TIMEOUT); +mgmt_txn_prepare_config_done: + + if (cfg_chgs && del_cfg_chgs) + nb_config_diff_del_changes(cfg_chgs); + + return ret; +} + +static int mgmt_txn_send_be_txn_create(struct mgmt_txn_ctx *txn) +{ + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + struct mgmt_commit_cfg_req *cmtcfg_req; + + assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); + + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { + adapter = mgmt_be_get_adapter_by_id(id); + if (mgmt_be_send_txn_req(adapter, txn->txn_id, true)) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Could not send TXN_CREATE to backend adapter"); + return -1; + } + } + } + + /* + * Dont move the commit to next phase yet. Wait for the TXN_REPLY to + * come back. + */ + + __dbg("txn-id: %" PRIu64 " session-id: %" PRIu64 " Phase '%s'", + txn->txn_id, txn->session_id, mgmt_txn_commit_phase_str(txn)); + + return 0; +} + +static int mgmt_txn_send_be_cfg_data(struct mgmt_txn_ctx *txn, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_commit_cfg_req *cmtcfg_req; + struct mgmt_txn_be_cfg_batch *batch; + struct mgmt_be_cfgreq cfg_req = { 0 }; + size_t num_batches, indx; + + assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); + + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + assert(IS_IDBIT_SET(cmtcfg_req->clients, adapter->id)); + + indx = 0; + num_batches = mgmt_txn_batches_count(&cmtcfg_req->batches[adapter->id]); + FOREACH_TXN_CFG_BATCH_IN_LIST (&cmtcfg_req->batches[adapter->id], + batch) { + + cfg_req.cfgdata_reqs = batch->cfg_datap; + cfg_req.num_reqs = batch->num_cfg_data; + indx++; + if (mgmt_be_send_cfgdata_req(adapter, txn->txn_id, + cfg_req.cfgdata_reqs, + cfg_req.num_reqs, + indx == num_batches)) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Internal Error! Could not send config data to backend!"); + __log_err("Could not send CFGDATA_CREATE txn-id: %" PRIu64 + " to client '%s", + txn->txn_id, adapter->name); + return -1; + } + + cmtcfg_req->cmt_stats->last_num_cfgdata_reqs++; + } + + /* + * We don't advance the phase here, instead that is driven by the + * cfg_reply. + */ + + return 0; +} + +static int mgmt_txn_send_be_txn_delete(struct mgmt_txn_ctx *txn, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_commit_cfg_req *cmtcfg_req = + &txn->commit_cfg_req->req.commit_cfg; + + assert(txn->type == MGMTD_TXN_TYPE_CONFIG); + + if (IS_IDBIT_UNSET(cmtcfg_req->clients, adapter->id)) + return 0; + + return mgmt_be_send_txn_req(adapter, txn->txn_id, false); +} + +static void mgmt_txn_cfg_commit_timedout(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + + txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); + assert(txn); + + assert(txn->type == MGMTD_TXN_TYPE_CONFIG); + + if (!txn->commit_cfg_req) + return; + + __log_err("Backend timeout txn-id: %" PRIu64 " aborting commit", + txn->txn_id); + + /* + * Send a COMMIT_CONFIG_REPLY with failure. + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Operation on the backend timed-out. Aborting commit!"); +} + + +static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, + struct mgmt_txn_req *txn_req) +{ + struct txn_req_get_tree *get_tree = txn_req->req.get_tree; + uint64_t req_id = txn_req->req_id; + struct lyd_node *result; + int ret = NB_OK; + + /* cancel timer and send reply onward */ + EVENT_OFF(txn->get_tree_timeout); + + if (!get_tree->simple_xpath && get_tree->client_results) { + /* + * We have a complex query so Filter results by the xpath query. + */ + if (yang_lyd_trim_xpath(&get_tree->client_results, + txn_req->req.get_tree->xpath)) + ret = NB_ERR; + } + + result = get_tree->client_results; + + if (ret == NB_OK && result && get_tree->exact) + result = yang_dnode_get(result, get_tree->xpath); + + if (ret == NB_OK) + ret = mgmt_fe_adapter_send_tree_data(txn->session_id, + txn->txn_id, + txn_req->req_id, + get_tree->result_type, + get_tree->wd_options, + result, + get_tree->partial_error, + false); + + /* we're done with the request */ + mgmt_txn_req_free(&txn_req); + + if (ret) { + __log_err("Error sending the results of GETTREE for txn-id %" PRIu64 + " req_id %" PRIu64 " to requested type %u", + txn->txn_id, req_id, get_tree->result_type); + + (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, ret, + "Error converting results of GETTREE"); + } + + return ret; +} + +static int txn_rpc_done(struct mgmt_txn_ctx *txn, struct mgmt_txn_req *txn_req) +{ + struct txn_req_rpc *rpc = txn_req->req.rpc; + uint64_t req_id = txn_req->req_id; + + /* cancel timer and send reply onward */ + EVENT_OFF(txn->rpc_timeout); + + if (rpc->errstr) + mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -1, + rpc->errstr); + else if (mgmt_fe_adapter_send_rpc_reply(txn->session_id, txn->txn_id, + req_id, rpc->result_type, + rpc->client_results)) { + __log_err("Error sending the results of RPC for txn-id %" PRIu64 + " req_id %" PRIu64 " to requested type %u", + txn->txn_id, req_id, rpc->result_type); + + (void)mgmt_fe_adapter_txn_error(txn->txn_id, req_id, false, -1, + "Error converting results of RPC"); + } + + /* we're done with the request */ + mgmt_txn_req_free(&txn_req); + + return 0; +} + +static void txn_get_tree_timeout(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + + txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); + txn = txn_req->txn; + + assert(txn); + assert(txn->type == MGMTD_TXN_TYPE_SHOW); + + + __log_err("Backend timeout txn-id: %" PRIu64 " ending get-tree", + txn->txn_id); + + /* + * Send a get-tree data reply. + * + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + + txn_req->req.get_tree->partial_error = -ETIMEDOUT; + txn_get_tree_data_done(txn, txn_req); +} + +static void txn_rpc_timeout(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + + txn_req = (struct mgmt_txn_req *)EVENT_ARG(thread); + txn = txn_req->txn; + + assert(txn); + assert(txn->type == MGMTD_TXN_TYPE_RPC); + + __log_err("Backend timeout txn-id: %" PRIu64 " ending rpc", txn->txn_id); + + /* + * Send a get-tree data reply. + * + * NOTE: The transaction cleanup will be triggered from Front-end + * adapter. + */ + + txn_req->req.rpc->errstr = + XSTRDUP(MTYPE_MGMTD_ERR, "Operation on the backend timed-out"); + txn_rpc_done(txn, txn_req); +} + +/* + * Send CFG_APPLY_REQs to all the backend client. + * + * NOTE: This is always dispatched when all CFGDATA_CREATE_REQs + * for all backend clients has been generated. Please see + * mgmt_txn_register_event() and mgmt_txn_process_commit_cfg() + * for details. + */ +static int mgmt_txn_send_be_cfg_apply(struct mgmt_txn_ctx *txn) +{ + enum mgmt_be_client_id id; + struct mgmt_be_client_adapter *adapter; + struct mgmt_commit_cfg_req *cmtcfg_req; + + assert(txn->type == MGMTD_TXN_TYPE_CONFIG && txn->commit_cfg_req); + + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + if (cmtcfg_req->validate_only) { + /* + * If this was a validate-only COMMIT request return success. + */ + (void)mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); + return 0; + } + + FOREACH_MGMTD_BE_CLIENT_ID (id) { + if (IS_IDBIT_SET(cmtcfg_req->clients, id)) { + adapter = mgmt_be_get_adapter_by_id(id); + if (!adapter) + return -1; + + if (mgmt_be_send_cfgapply_req(adapter, txn->txn_id)) { + (void)mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Could not send CFG_APPLY_REQ to backend adapter"); + return -1; + } + cmtcfg_req->cmt_stats->last_num_apply_reqs++; + + UNSET_FLAG(adapter->flags, + MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); + } + } + + /* + * Dont move the commit to next phase yet. Wait for all VALIDATE_REPLIES + * to come back. + */ + + return 0; +} + +static void mgmt_txn_process_commit_cfg(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_commit_cfg_req *cmtcfg_req; + + txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); + assert(txn); + + __dbg("Processing COMMIT_CONFIG for txn-id: %" PRIu64 + " session-id: %" PRIu64 " Phase '%s'", + txn->txn_id, txn->session_id, mgmt_txn_commit_phase_str(txn)); + + assert(txn->commit_cfg_req); + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + switch (cmtcfg_req->phase) { + case MGMTD_COMMIT_PHASE_PREPARE_CFG: + mgmt_txn_prepare_config(txn); + break; + case MGMTD_COMMIT_PHASE_TXN_CREATE: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->txn_create_start, + NULL); + /* + * Send TXN_CREATE_REQ to all Backend now. + */ + mgmt_txn_send_be_txn_create(txn); + break; + case MGMTD_COMMIT_PHASE_APPLY_CFG: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_start, + NULL); + /* + * We should have received successful CFG_VALIDATE_REPLY from + * all concerned Backend Clients by now. Send out the + * CFG_APPLY_REQs now. + */ + mgmt_txn_send_be_cfg_apply(txn); + break; + case MGMTD_COMMIT_PHASE_TXN_DELETE: + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->txn_del_start, + NULL); + /* + * We would have sent TXN_DELETE_REQ to all backend by now. + * Send a successful CONFIG_COMMIT_REPLY back to front-end. + * NOTE: This should also trigger DS merge/unlock and Txn + * cleanup. Please see mgmt_fe_send_commit_cfg_reply() for + * more details. + */ + EVENT_OFF(txn->comm_cfg_timeout); + mgmt_txn_send_commit_cfg_reply(txn, MGMTD_SUCCESS, NULL); + break; + case MGMTD_COMMIT_PHASE_MAX: + break; + } +} + +static void mgmt_init_get_data_reply(struct mgmt_get_data_reply *get_reply) +{ + size_t indx; + + for (indx = 0; indx < array_size(get_reply->reply_data); indx++) + get_reply->reply_datap[indx] = &get_reply->reply_data[indx]; +} + +static void mgmt_reset_get_data_reply(struct mgmt_get_data_reply *get_reply) +{ + int indx; + + for (indx = 0; indx < get_reply->num_reply; indx++) { + if (get_reply->reply_xpathp[indx]) { + free(get_reply->reply_xpathp[indx]); + get_reply->reply_xpathp[indx] = 0; + } + if (get_reply->reply_data[indx].xpath) { + free(get_reply->reply_data[indx].xpath); + get_reply->reply_data[indx].xpath = 0; + } + } + + get_reply->num_reply = 0; + memset(&get_reply->data_reply, 0, sizeof(get_reply->data_reply)); + memset(&get_reply->reply_data, 0, sizeof(get_reply->reply_data)); + memset(&get_reply->reply_datap, 0, sizeof(get_reply->reply_datap)); + + memset(&get_reply->reply_value, 0, sizeof(get_reply->reply_value)); + + mgmt_init_get_data_reply(get_reply); +} + +static void mgmt_reset_get_data_reply_buf(struct mgmt_get_data_req *get_data) +{ + if (get_data->reply) + mgmt_reset_get_data_reply(get_data->reply); +} + +static void mgmt_txn_send_getcfg_reply_data(struct mgmt_txn_req *txn_req, + struct mgmt_get_data_req *get_req) +{ + struct mgmt_get_data_reply *get_reply; + Mgmtd__YangDataReply *data_reply; + + get_reply = get_req->reply; + if (!get_reply) + return; + + data_reply = &get_reply->data_reply; + mgmt_yang_data_reply_init(data_reply); + data_reply->n_data = get_reply->num_reply; + data_reply->data = get_reply->reply_datap; + data_reply->next_indx = (!get_reply->last_batch ? get_req->total_reply + : -1); + + __dbg("Sending %zu Get-Config/Data replies next-index:%" PRId64, + data_reply->n_data, data_reply->next_indx); + + switch (txn_req->req_event) { + case MGMTD_TXN_PROC_GETCFG: + if (mgmt_fe_send_get_reply(txn_req->txn->session_id, + txn_req->txn->txn_id, get_req->ds_id, + txn_req->req_id, MGMTD_SUCCESS, + data_reply, NULL) != 0) { + __log_err("Failed to send GET-CONFIG-REPLY txn-id: %" PRIu64 + " session-id: %" PRIu64 " req-id: %" PRIu64, + txn_req->txn->txn_id, + txn_req->txn->session_id, txn_req->req_id); + } + break; + case MGMTD_TXN_PROC_SETCFG: + case MGMTD_TXN_PROC_COMMITCFG: + case MGMTD_TXN_PROC_GETTREE: + case MGMTD_TXN_PROC_RPC: + case MGMTD_TXN_COMMITCFG_TIMEOUT: + __log_err("Invalid Txn-Req-Event %u", txn_req->req_event); + break; + } + + /* + * Reset reply buffer for next reply. + */ + mgmt_reset_get_data_reply_buf(get_req); +} + +static void txn_iter_get_config_data_cb(const char *xpath, struct lyd_node *node, + struct nb_node *nb_node, void *ctx) +{ + struct mgmt_txn_req *txn_req; + struct mgmt_get_data_req *get_req; + struct mgmt_get_data_reply *get_reply; + Mgmtd__YangData *data; + Mgmtd__YangDataValue *data_value; + + txn_req = (struct mgmt_txn_req *)ctx; + if (!txn_req) + return; + + if (!(node->schema->nodetype & LYD_NODE_TERM)) + return; + + assert(txn_req->req_event == MGMTD_TXN_PROC_GETCFG); + + get_req = txn_req->req.get_data; + assert(get_req); + get_reply = get_req->reply; + data = &get_reply->reply_data[get_reply->num_reply]; + data_value = &get_reply->reply_value[get_reply->num_reply]; + + mgmt_yang_data_init(data); + data->xpath = strdup(xpath); + mgmt_yang_data_value_init(data_value); + data_value->value_case = MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL; + data_value->encoded_str_val = (char *)lyd_get_value(node); + data->value = data_value; + + get_reply->num_reply++; + get_req->total_reply++; + __dbg(" [%d] XPATH: '%s', Value: '%s'", get_req->total_reply, + data->xpath, data_value->encoded_str_val); + + if (get_reply->num_reply == MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH) + mgmt_txn_send_getcfg_reply_data(txn_req, get_req); +} + +static int mgmt_txn_get_config(struct mgmt_txn_ctx *txn, + struct mgmt_txn_req *txn_req, + struct nb_config *root) +{ + int indx; + struct mgmt_get_data_req *get_data; + struct mgmt_get_data_reply *get_reply; + + get_data = txn_req->req.get_data; + + if (!get_data->reply) { + get_data->reply = XCALLOC(MTYPE_MGMTD_TXN_GETDATA_REPLY, + sizeof(struct mgmt_get_data_reply)); + if (!get_data->reply) { + mgmt_fe_send_get_reply( + txn->session_id, txn->txn_id, get_data->ds_id, + txn_req->req_id, MGMTD_INTERNAL_ERROR, NULL, + "Internal error: Unable to allocate reply buffers!"); + goto mgmt_txn_get_config_failed; + } + } + + /* + * Read data contents from the DS and respond back directly. + * No need to go to backend for getting data. + */ + get_reply = get_data->reply; + for (indx = 0; indx < get_data->num_xpaths; indx++) { + __dbg("Trying to get all data under '%s'", + get_data->xpaths[indx]); + mgmt_init_get_data_reply(get_reply); + /* + * mgmt_ds_iter_data works on path prefixes, but the user may + * want to also use an xpath regexp we need to add this + * functionality. + */ + if (mgmt_ds_iter_data(get_data->ds_id, root, + get_data->xpaths[indx], + txn_iter_get_config_data_cb, + (void *)txn_req) == -1) { + __dbg("Invalid Xpath '%s", get_data->xpaths[indx]); + mgmt_fe_send_get_reply(txn->session_id, txn->txn_id, + get_data->ds_id, txn_req->req_id, + MGMTD_INTERNAL_ERROR, NULL, + "Invalid xpath"); + goto mgmt_txn_get_config_failed; + } + __dbg("Got %d remaining data-replies for xpath '%s'", + get_reply->num_reply, get_data->xpaths[indx]); + get_reply->last_batch = true; + mgmt_txn_send_getcfg_reply_data(txn_req, get_data); + } + +mgmt_txn_get_config_failed: + + /* + * Delete the txn request. It will also remove it from request + * list. + */ + mgmt_txn_req_free(&txn_req); + + return 0; +} + +static void mgmt_txn_process_get_cfg(struct event *thread) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct nb_config *cfg_root; + int num_processed = 0; + bool error; + + txn = (struct mgmt_txn_ctx *)EVENT_ARG(thread); + assert(txn); + + __dbg("Processing %zu GET_CONFIG requests txn-id: %" PRIu64 + " session-id: %" PRIu64, + mgmt_txn_reqs_count(&txn->get_cfg_reqs), txn->txn_id, + txn->session_id); + + FOREACH_TXN_REQ_IN_LIST (&txn->get_cfg_reqs, txn_req) { + error = false; + assert(txn_req->req_event == MGMTD_TXN_PROC_GETCFG); + cfg_root = txn_req->req.get_data->cfg_root; + assert(cfg_root); + + if (mgmt_txn_get_config(txn, txn_req, cfg_root) != 0) { + __log_err("Unable to retrieve config from DS %d txn-id: %" PRIu64 + " session-id: %" PRIu64 " req-id: %" PRIu64, + txn_req->req.get_data->ds_id, txn->txn_id, + txn->session_id, txn_req->req_id); + error = true; + } + + if (error) { + /* + * Delete the txn request. + * Note: The following will remove it from the list + * as well. + */ + mgmt_txn_req_free(&txn_req); + } + + /* + * Else the transaction would have been already deleted or + * moved to corresponding pending list. No need to delete it. + */ + num_processed++; + if (num_processed == MGMTD_TXN_MAX_NUM_GETCFG_PROC) + break; + } + + if (mgmt_txn_reqs_count(&txn->get_cfg_reqs)) { + __dbg("Processed maximum number of Get-Config requests (%d/%d). Rescheduling for rest.", + num_processed, MGMTD_TXN_MAX_NUM_GETCFG_PROC); + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_GETCFG); + } +} + +static struct mgmt_txn_ctx * +mgmt_fe_find_txn_by_session_id(struct mgmt_master *cm, uint64_t session_id, + enum mgmt_txn_type type) +{ + struct mgmt_txn_ctx *txn; + + FOREACH_TXN_IN_LIST (cm, txn) { + if (txn->session_id == session_id && txn->type == type) + return txn; + } + + return NULL; +} + +static struct mgmt_txn_ctx *mgmt_txn_create_new(uint64_t session_id, + enum mgmt_txn_type type) +{ + struct mgmt_txn_ctx *txn = NULL; + + /* Do not allow multiple config transactions */ + if (type == MGMTD_TXN_TYPE_CONFIG && mgmt_config_txn_in_progress()) + return NULL; + + txn = mgmt_fe_find_txn_by_session_id(mgmt_txn_mm, session_id, type); + if (!txn) { + txn = XCALLOC(MTYPE_MGMTD_TXN, sizeof(struct mgmt_txn_ctx)); + assert(txn); + + txn->session_id = session_id; + txn->type = type; + mgmt_txns_add_tail(&mgmt_txn_mm->txn_list, txn); + /* TODO: why do we need N lists for one transaction */ + mgmt_txn_reqs_init(&txn->set_cfg_reqs); + mgmt_txn_reqs_init(&txn->get_cfg_reqs); + mgmt_txn_reqs_init(&txn->get_tree_reqs); + mgmt_txn_reqs_init(&txn->rpc_reqs); + txn->commit_cfg_req = NULL; + txn->refcount = 0; + if (!mgmt_txn_mm->next_txn_id) + mgmt_txn_mm->next_txn_id++; + txn->txn_id = mgmt_txn_mm->next_txn_id++; + hash_get(mgmt_txn_mm->txn_hash, txn, hash_alloc_intern); + + __dbg("Added new '%s' txn-id: %" PRIu64, + mgmt_txn_type2str(type), txn->txn_id); + + if (type == MGMTD_TXN_TYPE_CONFIG) + mgmt_txn_mm->cfg_txn = txn; + + MGMTD_TXN_LOCK(txn); + } + + return txn; +} + +static void mgmt_txn_delete(struct mgmt_txn_ctx **txn) +{ + MGMTD_TXN_UNLOCK(txn); +} + +static unsigned int mgmt_txn_hash_key(const void *data) +{ + const struct mgmt_txn_ctx *txn = data; + + return jhash2((uint32_t *)&txn->txn_id, + sizeof(txn->txn_id) / sizeof(uint32_t), 0); +} + +static bool mgmt_txn_hash_cmp(const void *d1, const void *d2) +{ + const struct mgmt_txn_ctx *txn1 = d1; + const struct mgmt_txn_ctx *txn2 = d2; + + return (txn1->txn_id == txn2->txn_id); +} + +static void mgmt_txn_hash_free(void *data) +{ + struct mgmt_txn_ctx *txn = data; + + mgmt_txn_delete(&txn); +} + +static void mgmt_txn_hash_init(void) +{ + if (!mgmt_txn_mm || mgmt_txn_mm->txn_hash) + return; + + mgmt_txn_mm->txn_hash = hash_create(mgmt_txn_hash_key, mgmt_txn_hash_cmp, + "MGMT Transactions"); +} + +static void mgmt_txn_hash_destroy(void) +{ + if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) + return; + + hash_clean(mgmt_txn_mm->txn_hash, mgmt_txn_hash_free); + hash_free(mgmt_txn_mm->txn_hash); + mgmt_txn_mm->txn_hash = NULL; +} + +static inline struct mgmt_txn_ctx *mgmt_txn_id2ctx(uint64_t txn_id) +{ + struct mgmt_txn_ctx key = { 0 }; + struct mgmt_txn_ctx *txn; + + if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) + return NULL; + + key.txn_id = txn_id; + txn = hash_lookup(mgmt_txn_mm->txn_hash, &key); + + return txn; +} + +uint64_t mgmt_txn_get_session_id(uint64_t txn_id) +{ + struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); + + return txn ? txn->session_id : MGMTD_SESSION_ID_NONE; +} + +static void mgmt_txn_lock(struct mgmt_txn_ctx *txn, const char *file, int line) +{ + txn->refcount++; + __dbg("%s:%d --> Lock %s txn-id: %" PRIu64 " refcnt: %d", file, line, + mgmt_txn_type2str(txn->type), txn->txn_id, txn->refcount); +} + +static void mgmt_txn_unlock(struct mgmt_txn_ctx **txn, const char *file, + int line) +{ + assert(*txn && (*txn)->refcount); + + (*txn)->refcount--; + __dbg("%s:%d --> Unlock %s txn-id: %" PRIu64 " refcnt: %d", file, line, + mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, (*txn)->refcount); + if (!(*txn)->refcount) { + if ((*txn)->type == MGMTD_TXN_TYPE_CONFIG) + if (mgmt_txn_mm->cfg_txn == *txn) + mgmt_txn_mm->cfg_txn = NULL; + EVENT_OFF((*txn)->proc_get_cfg); + EVENT_OFF((*txn)->proc_get_data); + EVENT_OFF((*txn)->proc_comm_cfg); + EVENT_OFF((*txn)->comm_cfg_timeout); + EVENT_OFF((*txn)->get_tree_timeout); + hash_release(mgmt_txn_mm->txn_hash, *txn); + mgmt_txns_del(&mgmt_txn_mm->txn_list, *txn); + + __dbg("Deleted %s txn-id: %" PRIu64 " session-id: %" PRIu64, + mgmt_txn_type2str((*txn)->type), (*txn)->txn_id, + (*txn)->session_id); + + XFREE(MTYPE_MGMTD_TXN, *txn); + } + + *txn = NULL; +} + +static void mgmt_txn_cleanup_txn(struct mgmt_txn_ctx **txn) +{ + /* TODO: Any other cleanup applicable */ + + mgmt_txn_delete(txn); +} + +static void mgmt_txn_cleanup_all_txns(void) +{ + struct mgmt_txn_ctx *txn; + + if (!mgmt_txn_mm || !mgmt_txn_mm->txn_hash) + return; + + FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) + mgmt_txn_cleanup_txn(&txn); +} + +static void mgmt_txn_register_event(struct mgmt_txn_ctx *txn, + enum mgmt_txn_event event) +{ + struct timeval tv = { .tv_sec = 0, + .tv_usec = MGMTD_TXN_PROC_DELAY_USEC }; + + assert(mgmt_txn_mm && mgmt_txn_tm); + + switch (event) { + case MGMTD_TXN_PROC_SETCFG: + event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_set_cfg, txn, + &tv, &txn->proc_set_cfg); + break; + case MGMTD_TXN_PROC_COMMITCFG: + event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_commit_cfg, + txn, &tv, &txn->proc_comm_cfg); + break; + case MGMTD_TXN_PROC_GETCFG: + event_add_timer_tv(mgmt_txn_tm, mgmt_txn_process_get_cfg, txn, + &tv, &txn->proc_get_cfg); + break; + case MGMTD_TXN_COMMITCFG_TIMEOUT: + event_add_timer(mgmt_txn_tm, mgmt_txn_cfg_commit_timedout, txn, + MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC, + &txn->comm_cfg_timeout); + break; + case MGMTD_TXN_PROC_GETTREE: + case MGMTD_TXN_PROC_RPC: + assert(!"code bug do not register this event"); + break; + } +} + +int mgmt_txn_init(struct mgmt_master *mm, struct event_loop *tm) +{ + if (mgmt_txn_mm || mgmt_txn_tm) + assert(!"MGMTD TXN: Call txn_init() only once"); + + mgmt_txn_mm = mm; + mgmt_txn_tm = tm; + mgmt_txns_init(&mm->txn_list); + mgmt_txn_hash_init(); + assert(!mm->cfg_txn); + mm->cfg_txn = NULL; + + return 0; +} + +void mgmt_txn_destroy(void) +{ + mgmt_txn_cleanup_all_txns(); + mgmt_txn_hash_destroy(); +} + +bool mgmt_config_txn_in_progress(void) +{ + if (mgmt_txn_mm && mgmt_txn_mm->cfg_txn) + return true; + + return false; +} + +uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type) +{ + struct mgmt_txn_ctx *txn; + + txn = mgmt_txn_create_new(session_id, type); + return txn ? txn->txn_id : MGMTD_TXN_ID_NONE; +} + +void mgmt_destroy_txn(uint64_t *txn_id) +{ + struct mgmt_txn_ctx *txn; + + txn = mgmt_txn_id2ctx(*txn_id); + if (!txn) + return; + + mgmt_txn_delete(&txn); + *txn_id = MGMTD_TXN_ID_NONE; +} + +int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, + Mgmtd__YangCfgDataReq **cfg_req, + size_t num_req, bool implicit_commit, + Mgmtd__DatastoreId dst_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + size_t indx; + uint16_t *num_chgs; + struct nb_cfg_change *cfg_chg; + struct nb_node *node; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + if (implicit_commit && mgmt_txn_reqs_count(&txn->set_cfg_reqs)) { + __log_err( + "For implicit commit config only one SETCFG-REQ can be allowed!"); + return -1; + } + + txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_SETCFG); + txn_req->req.set_cfg->ds_id = ds_id; + txn_req->req.set_cfg->ds_ctx = ds_ctx; + num_chgs = &txn_req->req.set_cfg->num_cfg_changes; + for (indx = 0; indx < num_req; indx++) { + cfg_chg = &txn_req->req.set_cfg->cfg_changes[*num_chgs]; + + switch (cfg_req[indx]->req_type) { + case MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA: + cfg_chg->operation = NB_OP_DELETE; + break; + case MGMTD__CFG_DATA_REQ_TYPE__REMOVE_DATA: + cfg_chg->operation = NB_OP_DESTROY; + break; + case MGMTD__CFG_DATA_REQ_TYPE__SET_DATA: + /* + * For backward compatibility, we need to allow creating + * *new* list keys with SET_DATA operation. NB_OP_MODIFY + * is not allowed for keys, so use NB_OP_CREATE_EXCL. + */ + node = nb_node_find(cfg_req[indx]->data->xpath); + if (node && lysc_is_key(node->snode)) + cfg_chg->operation = NB_OP_CREATE_EXCL; + else + cfg_chg->operation = NB_OP_MODIFY; + break; + case MGMTD__CFG_DATA_REQ_TYPE__CREATE_DATA: + cfg_chg->operation = NB_OP_CREATE_EXCL; + break; + case MGMTD__CFG_DATA_REQ_TYPE__REPLACE_DATA: + cfg_chg->operation = NB_OP_REPLACE; + break; + case MGMTD__CFG_DATA_REQ_TYPE__REQ_TYPE_NONE: + case _MGMTD__CFG_DATA_REQ_TYPE_IS_INT_SIZE: + default: + continue; + } + + __dbg("XPath: '%s', Value: '%s'", cfg_req[indx]->data->xpath, + (cfg_req[indx]->data->value && + cfg_req[indx]->data->value->encoded_str_val + ? cfg_req[indx]->data->value->encoded_str_val + : "NULL")); + strlcpy(cfg_chg->xpath, cfg_req[indx]->data->xpath, + sizeof(cfg_chg->xpath)); + cfg_chg->value = + (cfg_req[indx]->data->value && + cfg_req[indx]->data->value->encoded_str_val + ? strdup(cfg_req[indx] + ->data->value->encoded_str_val) + : NULL); + if (cfg_chg->value) + __dbg("Allocated value at %p ==> '%s'", cfg_chg->value, + cfg_chg->value); + + (*num_chgs)++; + } + txn_req->req.set_cfg->implicit_commit = implicit_commit; + txn_req->req.set_cfg->dst_ds_id = dst_ds_id; + txn_req->req.set_cfg->dst_ds_ctx = dst_ds_ctx; + txn_req->req.set_cfg->setcfg_stats = + mgmt_fe_get_session_setcfg_stats(txn->session_id); + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_SETCFG); + + return 0; +} + +int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId src_ds_id, + struct mgmt_ds_ctx *src_ds_ctx, + Mgmtd__DatastoreId dst_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx, + bool validate_only, bool abort, + bool implicit, struct mgmt_edit_req *edit) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + if (txn->commit_cfg_req) { + __log_err("Commit already in-progress txn-id: %" PRIu64 + " session-id: %" PRIu64 ". Cannot start another", + txn->txn_id, txn->session_id); + return -1; + } + + txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_COMMITCFG); + txn_req->req.commit_cfg.src_ds_id = src_ds_id; + txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; + txn_req->req.commit_cfg.dst_ds_id = dst_ds_id; + txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; + txn_req->req.commit_cfg.validate_only = validate_only; + txn_req->req.commit_cfg.abort = abort; + txn_req->req.commit_cfg.implicit = implicit; + txn_req->req.commit_cfg.edit = edit; + txn_req->req.commit_cfg.cmt_stats = + mgmt_fe_get_session_commit_stats(txn->session_id); + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG); + return 0; +} + +int mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, + bool connect) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct mgmt_commit_cfg_req *cmtcfg_req; + static struct mgmt_commit_stats dummy_stats; + struct nb_config_cbs *adapter_cfgs = NULL; + struct mgmt_ds_ctx *ds_ctx; + + memset(&dummy_stats, 0, sizeof(dummy_stats)); + if (connect) { + ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); + assert(ds_ctx); + + /* + * Lock the running datastore to prevent any changes while we + * are initializing the backend. + */ + if (mgmt_ds_lock(ds_ctx, 0) != 0) + return -1; + + /* Get config for this single backend client */ + mgmt_be_get_adapter_config(adapter, &adapter_cfgs); + if (!adapter_cfgs || RB_EMPTY(nb_config_cbs, adapter_cfgs)) { + SET_FLAG(adapter->flags, + MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); + mgmt_ds_unlock(ds_ctx); + return 0; + } + + /* + * Create a CONFIG transaction to push the config changes + * provided to the backend client. + */ + txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); + if (!txn) { + __log_err("Failed to create CONFIG Transaction for downloading CONFIGs for client '%s'", + adapter->name); + mgmt_ds_unlock(ds_ctx); + nb_config_diff_del_changes(adapter_cfgs); + return -1; + } + + __dbg("Created initial txn-id: %" PRIu64 " for BE client '%s'", + txn->txn_id, adapter->name); + /* + * Set the changeset for transaction to commit and trigger the + * commit request. + */ + txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); + txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_NONE; + txn_req->req.commit_cfg.src_ds_ctx = 0; + txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; + txn_req->req.commit_cfg.dst_ds_ctx = ds_ctx; + txn_req->req.commit_cfg.validate_only = false; + txn_req->req.commit_cfg.abort = false; + txn_req->req.commit_cfg.init = true; + txn_req->req.commit_cfg.cmt_stats = &dummy_stats; + txn_req->req.commit_cfg.cfg_chgs = adapter_cfgs; + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG); + + } else { + /* + * Check if any transaction is currently on-going that + * involves this backend client. If so, report the transaction + * has failed. + */ + FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { + /* TODO: update with operational state when that is + * completed */ + if (txn->type == MGMTD_TXN_TYPE_CONFIG) { + cmtcfg_req = txn->commit_cfg_req + ? &txn->commit_cfg_req->req + .commit_cfg + : NULL; + if (cmtcfg_req && IS_IDBIT_SET(cmtcfg_req->clients, + adapter->id)) { + mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Backend daemon disconnected while processing commit!"); + } + } + } + } + + return 0; +} + +int mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG) + return -1; + + if (!create && !txn->commit_cfg_req) + return 0; + + assert(txn->commit_cfg_req); + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + if (create) { + if (success) { + /* + * Done with TXN_CREATE. Move the backend client to + * next phase. + */ + assert(cmtcfg_req->phase == + MGMTD_COMMIT_PHASE_TXN_CREATE); + + /* + * Send CFGDATA_CREATE-REQs to the backend immediately. + */ + mgmt_txn_send_be_cfg_data(txn, adapter); + } else { + mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + "Internal error! Failed to initiate transaction at backend!"); + } + } + + return 0; +} + +int mgmt_txn_notify_be_cfgdata_reply(uint64_t txn_id, bool success, + char *error_if_any, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_commit_cfg_req *cmtcfg_req; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG) + return -1; + + if (!txn->commit_cfg_req) + return -1; + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + + if (!success) { + __log_err("CFGDATA_CREATE_REQ sent to '%s' failed txn-id: %" PRIu64 + " err: %s", + adapter->name, txn->txn_id, + error_if_any ? error_if_any : "None"); + mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + error_if_any + ? error_if_any + : "Internal error! Failed to download config data to backend!"); + return 0; + } + + __dbg("CFGDATA_CREATE_REQ sent to '%s' was successful txn-id: %" PRIu64 + " err: %s", + adapter->name, txn->txn_id, error_if_any ? error_if_any : "None"); + + cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_APPLY_CFG; + + mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); + + return 0; +} + +int mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, + char *error_if_any, + struct mgmt_be_client_adapter *adapter) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_commit_cfg_req *cmtcfg_req = NULL; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn || txn->type != MGMTD_TXN_TYPE_CONFIG || !txn->commit_cfg_req) + return -1; + + cmtcfg_req = &txn->commit_cfg_req->req.commit_cfg; + + if (!success) { + __log_err("CFGDATA_APPLY_REQ sent to '%s' failed txn-id: %" PRIu64 + " err: %s", + adapter->name, txn->txn_id, + error_if_any ? error_if_any : "None"); + mgmt_txn_send_commit_cfg_reply( + txn, MGMTD_INTERNAL_ERROR, + error_if_any + ? error_if_any + : "Internal error! Failed to apply config data on backend!"); + return 0; + } + + cmtcfg_req->be_phase[adapter->id] = MGMTD_COMMIT_PHASE_TXN_DELETE; + + /* + * All configuration for the specific backend has been applied. + * Send TXN-DELETE to wrap up the transaction for this backend. + */ + SET_FLAG(adapter->flags, MGMTD_BE_ADAPTER_FLAGS_CFG_SYNCED); + mgmt_txn_send_be_txn_delete(txn, adapter); + + mgmt_try_move_commit_to_next_phase(txn, cmtcfg_req); + if (mm->perf_stats_en) + gettimeofday(&cmtcfg_req->cmt_stats->apply_cfg_end, NULL); + + return 0; +} + +int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, struct nb_config *cfg_root, + Mgmtd__YangGetDataReq **data_req, size_t num_reqs) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + enum mgmt_txn_event req_event; + size_t indx; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + req_event = MGMTD_TXN_PROC_GETCFG; + txn_req = mgmt_txn_req_alloc(txn, req_id, req_event); + txn_req->req.get_data->ds_id = ds_id; + txn_req->req.get_data->cfg_root = cfg_root; + for (indx = 0; + indx < num_reqs && indx < MGMTD_MAX_NUM_DATA_REPLY_IN_BATCH; + indx++) { + __dbg("XPath: '%s'", data_req[indx]->data->xpath); + txn_req->req.get_data->xpaths[indx] = + strdup(data_req[indx]->data->xpath); + txn_req->req.get_data->num_xpaths++; + } + + mgmt_txn_register_event(txn, req_event); + + return 0; +} + + +/** + * Send get-tree requests to each client indicated in `clients` bitmask, which + * has registered operational state that matches the given `xpath` + */ +int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, + uint64_t clients, Mgmtd__DatastoreId ds_id, + LYD_FORMAT result_type, uint8_t flags, + uint32_t wd_options, bool simple_xpath, + const char *xpath) +{ + struct mgmt_msg_get_tree *msg; + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct txn_req_get_tree *get_tree; + enum mgmt_be_client_id id; + ssize_t slen = strlen(xpath); + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + /* If error in this function below here, be sure to free the req */ + txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); + get_tree = txn_req->req.get_tree; + get_tree->result_type = result_type; + get_tree->wd_options = wd_options; + get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT); + get_tree->simple_xpath = simple_xpath; + get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + + if (CHECK_FLAG(flags, GET_DATA_FLAG_CONFIG)) { + /* + * If the requested datastore is operational, get the config + * from running. + */ + struct mgmt_ds_ctx *ds = + mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL + ? MGMTD_DS_RUNNING + : ds_id); + struct nb_config *config = mgmt_ds_get_nb_config(ds); + + if (config) { + struct ly_set *set = NULL; + LY_ERR err; + + err = lyd_find_xpath(config->dnode, xpath, &set); + if (err) { + get_tree->partial_error = err; + goto state; + } + + /* + * If there's a single result, duplicate the returned + * node. If there are multiple results, duplicate the + * whole config and mark simple_xpath as false so the + * result is trimmed later in txn_get_tree_data_done. + */ + if (set->count == 1) { + err = lyd_dup_single(set->dnodes[0], NULL, + LYD_DUP_WITH_PARENTS | + LYD_DUP_WITH_FLAGS | + LYD_DUP_RECURSIVE, + &get_tree->client_results); + if (!err) + while (get_tree->client_results->parent) + get_tree->client_results = lyd_parent( + get_tree->client_results); + } else if (set->count > 1) { + err = lyd_dup_siblings(config->dnode, NULL, + LYD_DUP_RECURSIVE | + LYD_DUP_WITH_FLAGS, + &get_tree->client_results); + if (!err) + get_tree->simple_xpath = false; + } + + if (err) + get_tree->partial_error = err; + + ly_set_free(set, NULL); + } + } +state: + /* If we are only getting config, we are done */ + if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || + ds_id != MGMTD_DS_OPERATIONAL || !clients) + return txn_get_tree_data_done(txn, txn_req); + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, + MTYPE_MSG_NATIVE_GET_TREE); + msg->refer_id = txn_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_GET_TREE; + /* Always operate with the binary format in the backend */ + msg->result_type = LYD_LYB; + strlcpy(msg->xpath, xpath, slen + 1); + + assert(clients); + FOREACH_BE_CLIENT_BITS (id, clients) { + ret = mgmt_be_send_native(id, msg); + if (ret) { + __log_err("Could not send get-tree message to backend client %s", + mgmt_be_client_id2name(id)); + continue; + } + + __dbg("Sent get-tree req to backend client %s", + mgmt_be_client_id2name(id)); + + /* record that we sent the request to the client */ + get_tree->sent_clients |= (1u << id); + } + + mgmt_msg_native_free_msg(msg); + + /* Return if we didn't send any messages to backends */ + if (!get_tree->sent_clients) + return txn_get_tree_data_done(txn, txn_req); + + /* Start timeout timer - pulled out of register event code so we can + * pass a different arg + */ + event_add_timer(mgmt_txn_tm, txn_get_tree_timeout, txn_req, + MGMTD_TXN_GET_TREE_MAX_DELAY_SEC, + &txn->get_tree_timeout); + return 0; +} + +int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, struct mgmt_ds_ctx *ds_ctx, + Mgmtd__DatastoreId commit_ds_id, + struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, + bool commit, LYD_FORMAT request_type, uint8_t flags, + uint8_t operation, const char *xpath, const char *data) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_edit_req *edit; + struct nb_config *nb_config; + char errstr[BUFSIZ]; + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req)); + + nb_config = mgmt_ds_get_nb_config(ds_ctx); + assert(nb_config); + + ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath, + data, edit->xpath_created, errstr, + sizeof(errstr)); + if (ret) + goto reply; + + if (commit) { + edit->unlock = unlock; + + mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx, + commit_ds_id, commit_ds_ctx, + false, false, true, edit); + return 0; + } +reply: + mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id, + unlock, commit, edit->xpath_created, + ret ? -1 : 0, errstr); + + XFREE(MTYPE_MGMTD_TXN_REQ, edit); + + return 0; +} + +int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + LYD_FORMAT result_type, const char *xpath, + const char *data, size_t data_len) +{ + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + struct mgmt_msg_rpc *msg; + struct txn_req_rpc *rpc; + uint64_t id; + int ret; + + txn = mgmt_txn_id2ctx(txn_id); + if (!txn) + return -1; + + txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_RPC); + rpc = txn_req->req.rpc; + rpc->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); + rpc->result_type = result_type; + + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_rpc, 0, + MTYPE_MSG_NATIVE_RPC); + msg->refer_id = txn_id; + msg->req_id = req_id; + msg->code = MGMT_MSG_CODE_RPC; + msg->request_type = result_type; + + mgmt_msg_native_xpath_encode(msg, xpath); + if (data) + mgmt_msg_native_append(msg, data, data_len); + + assert(clients); + FOREACH_BE_CLIENT_BITS (id, clients) { + ret = mgmt_be_send_native(id, msg); + if (ret) { + __log_err("Could not send rpc message to backend client %s", + mgmt_be_client_id2name(id)); + continue; + } + + __dbg("Sent rpc req to backend client %s", + mgmt_be_client_id2name(id)); + + /* record that we sent the request to the client */ + rpc->sent_clients |= (1u << id); + } + + mgmt_msg_native_free_msg(msg); + + if (!rpc->sent_clients) + return txn_rpc_done(txn, txn_req); + + event_add_timer(mgmt_txn_tm, txn_rpc_timeout, txn_req, + MGMTD_TXN_RPC_MAX_DELAY_SEC, &txn->rpc_timeout); + + return 0; +} + +/* + * Error reply from the backend client. + */ +int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, uint64_t req_id, int error, + const char *errstr) +{ + enum mgmt_be_client_id id = adapter->id; + struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); + struct txn_req_get_tree *get_tree; + struct txn_req_rpc *rpc; + struct mgmt_txn_req *txn_req; + + if (!txn) { + __log_err("Error reply from %s cannot find txn-id %" PRIu64, + adapter->name, txn_id); + return -1; + } + + /* Find the request. */ + FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) + if (txn_req->req_id == req_id) + break; + if (!txn_req) + FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) + if (txn_req->req_id == req_id) + break; + if (!txn_req) { + __log_err("Error reply from %s for txn-id %" PRIu64 + " cannot find req_id %" PRIu64, + adapter->name, txn_id, req_id); + return -1; + } + + __log_err("Error reply from %s for txn-id %" PRIu64 " req_id %" PRIu64, + adapter->name, txn_id, req_id); + + switch (txn_req->req_event) { + case MGMTD_TXN_PROC_GETTREE: + get_tree = txn_req->req.get_tree; + get_tree->recv_clients |= (1u << id); + get_tree->partial_error = error; + + /* check if done yet */ + if (get_tree->recv_clients != get_tree->sent_clients) + return 0; + return txn_get_tree_data_done(txn, txn_req); + case MGMTD_TXN_PROC_RPC: + rpc = txn_req->req.rpc; + rpc->recv_clients |= (1u << id); + if (errstr) { + XFREE(MTYPE_MGMTD_ERR, rpc->errstr); + rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, errstr); + } + /* check if done yet */ + if (rpc->recv_clients != rpc->sent_clients) + return 0; + return txn_rpc_done(txn, txn_req); + + /* non-native message events */ + case MGMTD_TXN_PROC_SETCFG: + case MGMTD_TXN_PROC_COMMITCFG: + case MGMTD_TXN_PROC_GETCFG: + case MGMTD_TXN_COMMITCFG_TIMEOUT: + default: + assert(!"non-native req event in native erorr path"); + return -1; + } +} + +/* + * Get-tree data from the backend client. + */ +int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_tree_data *data_msg, + size_t msg_len) +{ + uint64_t txn_id = data_msg->refer_id; + uint64_t req_id = data_msg->req_id; + + enum mgmt_be_client_id id = adapter->id; + struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); + struct mgmt_txn_req *txn_req; + struct txn_req_get_tree *get_tree; + struct lyd_node *tree = NULL; + LY_ERR err; + + if (!txn) { + __log_err("GETTREE reply from %s for a missing txn-id %" PRIu64, + adapter->name, txn_id); + return -1; + } + + /* Find the request. */ + FOREACH_TXN_REQ_IN_LIST (&txn->get_tree_reqs, txn_req) + if (txn_req->req_id == req_id) + break; + if (!txn_req) { + __log_err("GETTREE reply from %s for txn-id %" PRIu64 + " missing req_id %" PRIu64, + adapter->name, txn_id, req_id); + return -1; + } + + get_tree = txn_req->req.get_tree; + + /* store the result */ + err = lyd_parse_data_mem(ly_native_ctx, (const char *)data_msg->result, + data_msg->result_type, + LYD_PARSE_STRICT | LYD_PARSE_ONLY, + 0 /*LYD_VALIDATE_OPERATIONAL*/, &tree); + if (err) { + __log_err("GETTREE reply from %s for txn-id %" PRIu64 + " req_id %" PRIu64 " error parsing result of type %u", + adapter->name, txn_id, req_id, data_msg->result_type); + } + if (!err) { + /* TODO: we could merge ly_errs here if it's not binary */ + + if (!get_tree->client_results) + get_tree->client_results = tree; + else + err = lyd_merge_siblings(&get_tree->client_results, + tree, LYD_MERGE_DESTRUCT); + if (err) { + __log_err("GETTREE reply from %s for txn-id %" PRIu64 + " req_id %" PRIu64 " error merging result", + adapter->name, txn_id, req_id); + } + } + if (!get_tree->partial_error) + get_tree->partial_error = (data_msg->partial_error + ? data_msg->partial_error + : (int)err); + + if (!data_msg->more) + get_tree->recv_clients |= (1u << id); + + /* check if done yet */ + if (get_tree->recv_clients != get_tree->sent_clients) + return 0; + + return txn_get_tree_data_done(txn, txn_req); +} + +int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_rpc_reply *reply_msg, + size_t msg_len) +{ + uint64_t txn_id = reply_msg->refer_id; + uint64_t req_id = reply_msg->req_id; + enum mgmt_be_client_id id = adapter->id; + struct mgmt_txn_ctx *txn = mgmt_txn_id2ctx(txn_id); + struct mgmt_txn_req *txn_req; + struct txn_req_rpc *rpc; + struct lyd_node *tree; + size_t data_len = msg_len - sizeof(*reply_msg); + LY_ERR err = LY_SUCCESS; + + if (!txn) { + __log_err("RPC reply from %s for a missing txn-id %" PRIu64, + adapter->name, txn_id); + return -1; + } + + /* Find the request. */ + FOREACH_TXN_REQ_IN_LIST (&txn->rpc_reqs, txn_req) + if (txn_req->req_id == req_id) + break; + if (!txn_req) { + __log_err("RPC reply from %s for txn-id %" PRIu64 + " missing req_id %" PRIu64, + adapter->name, txn_id, req_id); + return -1; + } + + rpc = txn_req->req.rpc; + + tree = NULL; + if (data_len) + err = yang_parse_rpc(rpc->xpath, reply_msg->result_type, + reply_msg->data, true, &tree); + if (err) { + __log_err("RPC reply from %s for txn-id %" PRIu64 + " req_id %" PRIu64 " error parsing result of type %u: %s", + adapter->name, txn_id, req_id, reply_msg->result_type, + ly_strerrcode(err)); + } + if (!err && tree) { + if (!rpc->client_results) + rpc->client_results = tree; + else + err = lyd_merge_siblings(&rpc->client_results, tree, + LYD_MERGE_DESTRUCT); + if (err) { + __log_err("RPC reply from %s for txn-id %" PRIu64 + " req_id %" PRIu64 " error merging result: %s", + adapter->name, txn_id, req_id, + ly_strerrcode(err)); + } + } + if (err) { + XFREE(MTYPE_MGMTD_ERR, rpc->errstr); + rpc->errstr = XSTRDUP(MTYPE_MGMTD_ERR, + "Cannot parse result from the backend"); + } + + rpc->recv_clients |= (1u << id); + + /* check if done yet */ + if (rpc->recv_clients != rpc->sent_clients) + return 0; + + return txn_rpc_done(txn, txn_req); +} + +void mgmt_txn_status_write(struct vty *vty) +{ + struct mgmt_txn_ctx *txn; + + vty_out(vty, "MGMTD Transactions\n"); + + FOREACH_TXN_IN_LIST (mgmt_txn_mm, txn) { + vty_out(vty, " Txn: \t\t\t0x%p\n", txn); + vty_out(vty, " Txn-Id: \t\t\t%" PRIu64 "\n", txn->txn_id); + vty_out(vty, " Session-Id: \t\t%" PRIu64 "\n", + txn->session_id); + vty_out(vty, " Type: \t\t\t%s\n", + mgmt_txn_type2str(txn->type)); + vty_out(vty, " Ref-Count: \t\t\t%d\n", txn->refcount); + } + vty_out(vty, " Total: %d\n", + (int)mgmt_txns_count(&mgmt_txn_mm->txn_list)); +} + +int mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, + struct mgmt_ds_ctx *dst_ds_ctx) +{ + static struct nb_config_cbs changes; + static struct mgmt_commit_stats dummy_stats; + + struct nb_config_cbs *cfg_chgs = NULL; + struct mgmt_txn_ctx *txn; + struct mgmt_txn_req *txn_req; + + memset(&changes, 0, sizeof(changes)); + memset(&dummy_stats, 0, sizeof(dummy_stats)); + /* + * This could be the case when the config is directly + * loaded onto the candidate DS from a file. Get the + * diff from a full comparison of the candidate and + * running DSs. + */ + nb_config_diff(mgmt_ds_get_nb_config(dst_ds_ctx), + mgmt_ds_get_nb_config(src_ds_ctx), &changes); + cfg_chgs = &changes; + + if (RB_EMPTY(nb_config_cbs, cfg_chgs)) { + /* + * This means there's no changes to commit whatsoever + * is the source of the changes in config. + */ + return -1; + } + + /* + * Create a CONFIG transaction to push the config changes + * provided to the backend client. + */ + txn = mgmt_txn_create_new(0, MGMTD_TXN_TYPE_CONFIG); + if (!txn) { + __log_err( + "Failed to create CONFIG Transaction for downloading CONFIGs"); + return -1; + } + + __dbg("Created rollback txn-id: %" PRIu64, txn->txn_id); + + /* + * Set the changeset for transaction to commit and trigger the commit + * request. + */ + txn_req = mgmt_txn_req_alloc(txn, 0, MGMTD_TXN_PROC_COMMITCFG); + txn_req->req.commit_cfg.src_ds_id = MGMTD_DS_CANDIDATE; + txn_req->req.commit_cfg.src_ds_ctx = src_ds_ctx; + txn_req->req.commit_cfg.dst_ds_id = MGMTD_DS_RUNNING; + txn_req->req.commit_cfg.dst_ds_ctx = dst_ds_ctx; + txn_req->req.commit_cfg.validate_only = false; + txn_req->req.commit_cfg.abort = false; + txn_req->req.commit_cfg.rollback = true; + txn_req->req.commit_cfg.cmt_stats = &dummy_stats; + txn_req->req.commit_cfg.cfg_chgs = cfg_chgs; + + /* + * Trigger a COMMIT-CONFIG process. + */ + mgmt_txn_register_event(txn, MGMTD_TXN_PROC_COMMITCFG); + return 0; +} diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h new file mode 100644 index 0000000..b6ca288 --- /dev/null +++ b/mgmtd/mgmt_txn.h @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD Transactions + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#ifndef _FRR_MGMTD_TXN_H_ +#define _FRR_MGMTD_TXN_H_ + +#include "lib/mgmt_msg_native.h" +#include "mgmtd/mgmt_be_adapter.h" +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_ds.h" + +#define MGMTD_TXN_PROC_DELAY_USEC 10 +#define MGMTD_TXN_MAX_NUM_SETCFG_PROC 128 +#define MGMTD_TXN_MAX_NUM_GETCFG_PROC 128 +#define MGMTD_TXN_MAX_NUM_GETDATA_PROC 128 + +#define MGMTD_TXN_CFG_COMMIT_MAX_DELAY_SEC 600 +#define MGMTD_TXN_GET_TREE_MAX_DELAY_SEC 600 +#define MGMTD_TXN_RPC_MAX_DELAY_SEC 60 + +#define MGMTD_TXN_CLEANUP_DELAY_USEC 10 + +#define MGMTD_TXN_ID_NONE 0 + +/* + * The following definition enables local validation of config + * on the MGMTD process by loading client-defined NB callbacks + * and calling them locally before sening CNFG_APPLY_REQ to + * backend for actual apply of configuration on internal state + * of the backend application. + * + * #define MGMTD_LOCAL_VALIDATIONS_ENABLED + * + * Note: Enabled by default in configure.ac, if this needs to be + * disabled then pass --enable-mgmtd-local-validations=no to + * the list of arguments passed to ./configure + */ + +PREDECL_LIST(mgmt_txns); + +struct mgmt_master; +struct mgmt_edit_req; + +enum mgmt_txn_type { + MGMTD_TXN_TYPE_NONE = 0, + MGMTD_TXN_TYPE_CONFIG, + MGMTD_TXN_TYPE_SHOW, + MGMTD_TXN_TYPE_RPC, +}; + +static inline const char *mgmt_txn_type2str(enum mgmt_txn_type type) +{ + switch (type) { + case MGMTD_TXN_TYPE_NONE: + return "None"; + case MGMTD_TXN_TYPE_CONFIG: + return "CONFIG"; + case MGMTD_TXN_TYPE_SHOW: + return "SHOW"; + case MGMTD_TXN_TYPE_RPC: + return "RPC"; + } + + return "Unknown"; +} + +/* Initialise transaction module. */ +extern int mgmt_txn_init(struct mgmt_master *cm, struct event_loop *tm); + +/* Destroy the transaction module. */ +extern void mgmt_txn_destroy(void); + +/* + * Check if configuration transaction is in progress. + * + * Returns: + * true if in-progress, false otherwise. + */ +extern bool mgmt_config_txn_in_progress(void); + +/** + * Get the session ID associated with the given ``txn-id``. + * + */ +extern uint64_t mgmt_txn_get_session_id(uint64_t txn_id); + +/* + * Create transaction. + * + * session_id + * Session ID. + * + * type + * Transaction type (CONFIG/SHOW/NONE) + * + * Returns: + * transaction ID. + */ +extern uint64_t mgmt_create_txn(uint64_t session_id, enum mgmt_txn_type type); + +/* + * Destroy transaction. + * + * txn_id + * Unique transaction identifier. + */ +extern void mgmt_destroy_txn(uint64_t *txn_id); + +/* + * Send set-config request to be processed later in transaction. + * + * txn_id + * Unique transaction identifier. + * + * req_id + * Unique transaction request identifier. + * + * ds_id + * Datastore ID. + * + * ds_hndl + * Datastore handle. + * + * cfg_req + * Config requests. + * + * num_req + * Number of config requests. + * + * implicit_commit + * TRUE if the commit is implicit, FALSE otherwise. + * + * dst_ds_id + * Destination datastore ID. + * + * dst_ds_handle + * Destination datastore handle. + * + * Returns: + * 0 on success, -1 on failures. + */ +extern int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, + Mgmtd__YangCfgDataReq **cfg_req, + size_t num_req, bool implicit_commit, + Mgmtd__DatastoreId dst_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx); + +/* + * Send commit-config request to be processed later in transaction. + * + * txn_id + * Unique transaction identifier. + * + * req_id + * Unique transaction request identifier. + * + * src_ds_id + * Source datastore ID. + * + * src_ds_hndl + * Source Datastore handle. + * + * validate_only + * TRUE if commit request needs to be validated only, FALSE otherwise. + * + * abort + * TRUE if need to restore Src DS back to Dest DS, FALSE otherwise. + * + * implicit + * TRUE if the commit is implicit, FALSE otherwise. + * + * edit + * Additional info when triggered from native edit request. + * + * Returns: + * 0 on success, -1 on failures. + */ +extern int mgmt_txn_send_commit_config_req( + uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId src_ds_id, + struct mgmt_ds_ctx *dst_ds_ctx, Mgmtd__DatastoreId dst_ds_id, + struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort, + bool implicit, struct mgmt_edit_req *edit); + +/* + * Send get-{cfg,data} request to be processed later in transaction. + * + * Is get-config if cfg_root is provided and the config is gathered locally, + * otherwise it's get-data and data is fetched from backedn clients. + */ +extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, + Mgmtd__DatastoreId ds_id, + struct nb_config *cfg_root, + Mgmtd__YangGetDataReq **data_req, + size_t num_reqs); + + +/** + * Send get-tree to the backend `clients`. + * + * Args: + * txn_id: Transaction identifier. + * req_id: FE client request identifier. + * clients: Bitmask of clients to send get-tree to. + * ds_id: datastore ID. + * result_type: LYD_FORMAT result format. + * flags: option flags for the request. + * wd_options: LYD_PRINT_WD_* flags for the result. + * simple_xpath: true if xpath is simple (only key predicates). + * xpath: The xpath to get the tree from. + * + * Return: + * 0 on success. + */ +extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, + uint64_t clients, + Mgmtd__DatastoreId ds_id, + LYD_FORMAT result_type, uint8_t flags, + uint32_t wd_options, bool simple_xpath, + const char *xpath); + +/** + * Send edit request. + * + * Args: + * txn_id: Transaction identifier. + * req_id: FE client request identifier. + * ds_id: Datastore ID. + * ds_ctx: Datastore context. + * commit_ds_id: Commit datastore ID. + * commit_ds_ctx: Commit datastore context. + * unlock: Unlock datastores after the edit. + * commit: Commit the candidate datastore after the edit. + * request_type: LYD_FORMAT request type. + * flags: option flags for the request. + * operation: The operation to perform. + * xpath: The xpath of data node to edit. + * data: The data tree. + */ +extern int +mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId ds_id, + struct mgmt_ds_ctx *ds_ctx, Mgmtd__DatastoreId commit_ds_id, + struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit, + LYD_FORMAT request_type, uint8_t flags, uint8_t operation, + const char *xpath, const char *data); + +/** + * Send RPC request. + * + * Args: + * txn_id: Transaction identifier. + * req_id: FE client request identifier. + * clients: Bitmask of clients to send RPC to. + * result_type: LYD_FORMAT result format. + * xpath: The xpath of the RPC. + * data: The input parameters data tree. + * data_len: The length of the input parameters data. + * + * Return: + * 0 on success. + */ +extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients, + LYD_FORMAT result_type, const char *xpath, + const char *data, size_t data_len); + +/* + * Notifiy backend adapter on connection. + */ +extern int +mgmt_txn_notify_be_adapter_conn(struct mgmt_be_client_adapter *adapter, + bool connect); + +/* + * Reply to backend adapter about transaction create/delete. + */ +extern int +mgmt_txn_notify_be_txn_reply(uint64_t txn_id, bool create, bool success, + struct mgmt_be_client_adapter *adapter); + +/* + * Reply to backend adapater with config data create request. + */ +extern int +mgmt_txn_notify_be_cfgdata_reply(uint64_t txn_id, bool success, + char *error_if_any, + struct mgmt_be_client_adapter *adapter); + +/* + * Reply to backend adapater with config data validate request. + */ +extern int mgmt_txn_notify_be_cfg_validate_reply( + uint64_t txn_id, bool success, uint64_t batch_ids[], + size_t num_batch_ids, char *error_if_any, + struct mgmt_be_client_adapter *adapter); + +/* + * Reply to backend adapater with config data apply request. + */ +extern int +mgmt_txn_notify_be_cfg_apply_reply(uint64_t txn_id, bool success, + char *error_if_any, + struct mgmt_be_client_adapter *adapter); + + +/** + * Process a reply from a backend client to our get-tree request + * + * Args: + * adapter: The adapter that received the result. + * txn_id: The transaction for this get-tree request. + * req_id: The request ID for this transaction. + * error: the integer error value (negative) + * errstr: the string description of the error. + */ +int mgmt_txn_notify_error(struct mgmt_be_client_adapter *adapter, + uint64_t txn_id, uint64_t req_id, int error, + const char *errstr); + +/** + * Process a reply from a backend client to our get-tree request + * + * Args: + * adapter: The adapter that received the result. + * data_msg: The message from the backend. + * msg_len: Total length of the message. + */ + +extern int mgmt_txn_notify_tree_data_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_tree_data *data_msg, + size_t msg_len); + +/** + * Process a reply from a backend client to our RPC request + * + * Args: + * adapter: The adapter that received the result. + * reply_msg: The message from the backend. + * msg_len: Total length of the message. + */ +extern int mgmt_txn_notify_rpc_reply(struct mgmt_be_client_adapter *adapter, + struct mgmt_msg_rpc_reply *reply_msg, + size_t msg_len); + +/* + * Dump transaction status to vty. + */ +extern void mgmt_txn_status_write(struct vty *vty); + +/* + * Trigger rollback config apply. + * + * Creates a new transaction and commit request for rollback. + */ +extern int +mgmt_txn_rollback_trigger_cfg_apply(struct mgmt_ds_ctx *src_ds_ctx, + struct mgmt_ds_ctx *dst_ds_ctx); +#endif /* _FRR_MGMTD_TXN_H_ */ diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c new file mode 100644 index 0000000..8ccb463 --- /dev/null +++ b/mgmtd/mgmt_vty.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MGMTD VTY Interface + * + * Copyright (C) 2021 Vmware, Inc. + * Pushpasis Sarkar + */ + +#include + +#include "affinitymap.h" +#include "command.h" +#include "filter.h" +#include "json.h" +#include "keychain.h" +#include "network.h" +#include "northbound_cli.h" +#include "routemap.h" + +#include "mgmtd/mgmt.h" +#include "mgmtd/mgmt_be_adapter.h" +#include "mgmtd/mgmt_fe_adapter.h" +#include "mgmtd/mgmt_ds.h" +#include "mgmtd/mgmt_history.h" + +#include "mgmtd/mgmt_vty_clippy.c" +#include "ripd/rip_nb.h" +#include "ripngd/ripng_nb.h" +#include "staticd/static_vty.h" +#include "zebra/zebra_cli.h" + +extern struct frr_daemon_info *mgmt_daemon_info; + +DEFPY(show_mgmt_be_adapter, + show_mgmt_be_adapter_cmd, + "show mgmt backend-adapter all", + SHOW_STR + MGMTD_STR + MGMTD_BE_ADAPTER_STR + "Display all Backend Adapters\n") +{ + mgmt_be_adapter_status_write(vty); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_be_xpath_reg, + show_mgmt_be_xpath_reg_cmd, + "show mgmt backend-yang-xpath-registry", + SHOW_STR + MGMTD_STR + "Backend Adapter YANG Xpath Registry\n") +{ + mgmt_be_xpath_register_write(vty); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_fe_adapter, show_mgmt_fe_adapter_cmd, + "show mgmt frontend-adapter all [detail$detail]", + SHOW_STR + MGMTD_STR + MGMTD_FE_ADAPTER_STR + "Display all Frontend Adapters\n" + "Display more details\n") +{ + mgmt_fe_adapter_status_write(vty, !!detail); + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN(mgmt_performance_measurement, + mgmt_performance_measurement_cmd, + "[no] mgmt performance-measurement", + NO_STR + MGMTD_STR + "Enable performance measurement\n") +{ + if (no) + mgmt_fe_adapter_perf_measurement(vty, false); + else + mgmt_fe_adapter_perf_measurement(vty, true); + + return CMD_SUCCESS; +} + +DEFPY(mgmt_reset_performance_stats, + mgmt_reset_performance_stats_cmd, + "mgmt reset-statistics", + MGMTD_STR + "Reset the Performance measurement statistics\n") +{ + mgmt_fe_adapter_reset_perf_stats(vty); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_txn, + show_mgmt_txn_cmd, + "show mgmt transaction all", + SHOW_STR + MGMTD_STR + MGMTD_TXN_STR + "Display all Transactions\n") +{ + mgmt_txn_status_write(vty); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_ds, + show_mgmt_ds_cmd, + "show mgmt datastore [all|candidate|operational|running]$dsname", + SHOW_STR + MGMTD_STR + MGMTD_DS_STR + "All datastores (default)\n" + "Candidate datastore\n" + "Operational datastore\n" + "Running datastore\n") +{ + struct mgmt_ds_ctx *ds_ctx; + + if (!dsname || dsname[0] == 'a') { + mgmt_ds_status_write(vty); + return CMD_SUCCESS; + } + ds_ctx = mgmt_ds_get_ctx_by_id(mm, mgmt_ds_name2id(dsname)); + if (!ds_ctx) { + vty_out(vty, "ERROR: Could not access %s datastore!\n", dsname); + return CMD_ERR_NO_MATCH; + } + mgmt_ds_status_write_one(vty, ds_ctx); + + return CMD_SUCCESS; +} + +DEFPY(mgmt_commit, + mgmt_commit_cmd, + "mgmt commit $type", + MGMTD_STR + "Commit action\n" + "Validate the set of config commands\n" + "Validate and apply the set of config commands\n" + "Abort and drop the set of config commands recently added\n") +{ + bool validate_only = type[0] == 'c'; + bool abort = type[1] == 'b'; + + if (vty_mgmt_send_commit_config(vty, validate_only, abort) != 0) + return CMD_WARNING_CONFIG_FAILED; + return CMD_SUCCESS; +} + +DEFPY(mgmt_create_config_data, mgmt_create_config_data_cmd, + "mgmt create-config WORD$path VALUE", + MGMTD_STR + "Create configuration data\n" + "XPath expression specifying the YANG data path\n" + "Value of the data to create\n") +{ + strlcpy(vty->cfg_changes[0].xpath, path, + sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = value; + vty->cfg_changes[0].operation = NB_OP_CREATE_EXCL; + vty->num_cfg_changes = 1; + + vty_mgmt_send_config_data(vty, NULL, false); + return CMD_SUCCESS; +} + +DEFPY(mgmt_set_config_data, mgmt_set_config_data_cmd, + "mgmt set-config WORD$path VALUE", + MGMTD_STR + "Set configuration data\n" + "XPath expression specifying the YANG data path\n" + "Value of the data to set\n") +{ + strlcpy(vty->cfg_changes[0].xpath, path, + sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = value; + vty->cfg_changes[0].operation = NB_OP_MODIFY; + vty->num_cfg_changes = 1; + + vty_mgmt_send_config_data(vty, NULL, false); + return CMD_SUCCESS; +} + +DEFPY(mgmt_delete_config_data, mgmt_delete_config_data_cmd, + "mgmt delete-config WORD$path", + MGMTD_STR + "Delete configuration data\n" + "XPath expression specifying the YANG data path\n") +{ + + strlcpy(vty->cfg_changes[0].xpath, path, + sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = NULL; + vty->cfg_changes[0].operation = NB_OP_DELETE; + vty->num_cfg_changes = 1; + + vty_mgmt_send_config_data(vty, NULL, false); + return CMD_SUCCESS; +} + +DEFPY(mgmt_remove_config_data, mgmt_remove_config_data_cmd, + "mgmt remove-config WORD$path", + MGMTD_STR + "Remove configuration data\n" + "XPath expression specifying the YANG data path\n") +{ + + strlcpy(vty->cfg_changes[0].xpath, path, + sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = NULL; + vty->cfg_changes[0].operation = NB_OP_DESTROY; + vty->num_cfg_changes = 1; + + vty_mgmt_send_config_data(vty, NULL, false); + return CMD_SUCCESS; +} + +DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd, + "mgmt replace-config WORD$path VALUE", + MGMTD_STR + "Replace configuration data\n" + "XPath expression specifying the YANG data path\n" + "Value of the data to set\n") +{ + + strlcpy(vty->cfg_changes[0].xpath, path, + sizeof(vty->cfg_changes[0].xpath)); + vty->cfg_changes[0].value = value; + vty->cfg_changes[0].operation = NB_OP_REPLACE; + vty->num_cfg_changes = 1; + + vty_mgmt_send_config_data(vty, NULL, false); + return CMD_SUCCESS; +} + +DEFPY(mgmt_edit, mgmt_edit_cmd, + "mgmt edit {create|delete|merge|replace|remove}$op XPATH [json|xml]$fmt [lock$lock] [commit$commit] [DATA]", + MGMTD_STR + "Edit configuration data\n" + "Create data\n" + "Delete data\n" + "Merge data\n" + "Replace data\n" + "Remove data\n" + "XPath expression specifying the YANG data path\n" + "JSON input format (default)\n" + "XML input format\n" + "Lock the datastores automatically\n" + "Commit the changes automatically\n" + "Data tree\n") +{ + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + uint8_t operation; + uint8_t flags = 0; + + switch (op[2]) { + case 'e': + operation = NB_OP_CREATE_EXCL; + break; + case 'l': + operation = NB_OP_DELETE; + break; + case 'r': + operation = NB_OP_MODIFY; + break; + case 'p': + operation = NB_OP_REPLACE; + break; + case 'm': + operation = NB_OP_DESTROY; + break; + default: + vty_out(vty, "Invalid operation!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!data && (operation == NB_OP_CREATE_EXCL || + operation == NB_OP_MODIFY || operation == NB_OP_REPLACE)) { + vty_out(vty, "Data tree is missing!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (lock) + flags |= EDIT_FLAG_IMPLICIT_LOCK; + + if (commit) + flags |= EDIT_FLAG_IMPLICIT_COMMIT; + + vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags, + operation, xpath, data); + return CMD_SUCCESS; +} + +DEFPY(mgmt_rpc, mgmt_rpc_cmd, + "mgmt rpc XPATH [json|xml]$fmt [DATA]", + MGMTD_STR + "Invoke RPC\n" + "XPath expression specifying the YANG data path\n" + "JSON input format (default)\n" + "XML input format\n" + "Input data tree\n") +{ + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + + vty_mgmt_send_rpc_req(vty, format, xpath, data); + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, + "show mgmt get-config [candidate|operational|running]$dsname WORD$path", + SHOW_STR MGMTD_STR + "Get configuration data from a specific configuration datastore\n" + "Candidate datastore (default)\n" + "Operational datastore\n" + "Running datastore\n" + "XPath expression specifying the YANG data path\n") +{ + const char *xpath_list[VTY_MAXCFGCHANGES] = {0}; + Mgmtd__DatastoreId datastore = MGMTD_DS_CANDIDATE; + + if (dsname) + datastore = mgmt_ds_name2id(dsname); + + xpath_list[0] = path; + vty_mgmt_send_get_req(vty, true, datastore, xpath_list, 1); + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, + "show mgmt get-data WORD$path [datastore $ds] [with-config|only-config]$content [exact]$exact [with-defaults $wd] [json|xml]$fmt", + SHOW_STR + MGMTD_STR + "Get a data from the operational datastore\n" + "XPath expression specifying the YANG data root\n" + "Specify datastore to get data from (operational by default)\n" + "Candidate datastore\n" + "Running datastore\n" + "Operational datastore\n" + "Include \"config true\" data\n" + "Get only \"config true\" data\n" + "Get exact node instead of the whole data tree\n" + "Configure 'with-defaults' mode per RFC 6243 (\"explicit\" mode by default)\n" + "Use \"trim\" mode\n" + "Use \"report-all-tagged\" mode\n" + "Use \"report-all\" mode\n" + "JSON output format\n" + "XML output format\n") +{ + LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON; + int plen = strlen(path); + char *xpath = NULL; + uint8_t flags = content ? GET_DATA_FLAG_CONFIG : GET_DATA_FLAG_STATE; + uint8_t defaults = GET_DATA_DEFAULTS_EXPLICIT; + uint8_t datastore = MGMT_MSG_DATASTORE_OPERATIONAL; + + if (content && content[0] == 'w') + flags |= GET_DATA_FLAG_STATE; + + if (exact) + flags |= GET_DATA_FLAG_EXACT; + + if (wd) { + if (wd[0] == 't') + defaults = GET_DATA_DEFAULTS_TRIM; + else if (wd[3] == '-') + defaults = GET_DATA_DEFAULTS_ALL_ADD_TAG; + else + defaults = GET_DATA_DEFAULTS_ALL; + } + + if (ds) { + if (ds[0] == 'c') + datastore = MGMT_MSG_DATASTORE_CANDIDATE; + else if (ds[0] == 'r') + datastore = MGMT_MSG_DATASTORE_RUNNING; + } + + /* get rid of extraneous trailing slash-* or single '/' unless root */ + if (plen > 2 && ((path[plen - 2] == '/' && path[plen - 1] == '*') || + (path[plen - 2] != '/' && path[plen - 1] == '/'))) { + plen = path[plen - 1] == '/' ? plen - 1 : plen - 2; + xpath = XSTRDUP(MTYPE_TMP, path); + xpath[plen] = 0; + path = xpath; + } + + vty_mgmt_send_get_data_req(vty, datastore, format, flags, defaults, + path); + + if (xpath) + XFREE(MTYPE_TMP, xpath); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_dump_data, + show_mgmt_dump_data_cmd, + "show mgmt datastore-contents [candidate|operational|running]$dsname [xpath WORD$path] [file WORD$filepath] $fmt", + SHOW_STR + MGMTD_STR + "Get Datastore contents from a specific datastore\n" + "Candidate datastore (default)\n" + "Operational datastore\n" + "Running datastore\n" + "XPath expression specifying the YANG data path\n" + "XPath string\n" + "Dump the contents to a file\n" + "Full path of the file\n" + "json output\n" + "xml output\n") +{ + struct mgmt_ds_ctx *ds_ctx; + Mgmtd__DatastoreId datastore = MGMTD_DS_CANDIDATE; + LYD_FORMAT format = fmt[0] == 'j' ? LYD_JSON : LYD_XML; + FILE *f = NULL; + + if (dsname) + datastore = mgmt_ds_name2id(dsname); + + ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); + if (!ds_ctx) { + vty_out(vty, "ERROR: Could not access datastore!\n"); + return CMD_ERR_NO_MATCH; + } + + if (filepath) { + f = fopen(filepath, "w"); + if (!f) { + vty_out(vty, + "Could not open file pointed by filepath %s\n", + filepath); + return CMD_SUCCESS; + } + } + + mgmt_ds_dump_tree(vty, ds_ctx, path, f, format); + + if (f) + fclose(f); + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_map_xpath, + show_mgmt_map_xpath_cmd, + "show mgmt yang-xpath-subscription WORD$path", + SHOW_STR + MGMTD_STR + "Get YANG Backend Subscription\n" + "XPath expression specifying the YANG data path\n") +{ + mgmt_be_show_xpath_registries(vty, path); + return CMD_SUCCESS; +} + +DEFPY(mgmt_load_config, + mgmt_load_config_cmd, + "mgmt load-config WORD$filepath $type", + MGMTD_STR + "Load configuration onto Candidate Datastore\n" + "Full path of the file\n" + "Merge configuration with contents of Candidate Datastore\n" + "Replace the existing contents of Candidate datastore\n") +{ + bool merge = type[0] == 'm' ? true : false; + struct mgmt_ds_ctx *ds_ctx; + int ret; + + if (access(filepath, F_OK) == -1) { + vty_out(vty, "ERROR: File %s : %s\n", filepath, + strerror(errno)); + return CMD_ERR_NO_FILE; + } + + ds_ctx = mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_CANDIDATE); + if (!ds_ctx) { + vty_out(vty, "ERROR: Could not access Candidate datastore!\n"); + return CMD_ERR_NO_MATCH; + } + + ret = mgmt_ds_load_config_from_file(ds_ctx, filepath, merge); + if (ret != 0) + vty_out(vty, "Error with parsing the file with error code %d\n", + ret); + return CMD_SUCCESS; +} + +DEFPY(mgmt_save_config, + mgmt_save_config_cmd, + "mgmt save-config $dsname WORD$filepath", + MGMTD_STR + "Save configuration from datastore\n" + "Candidate datastore\n" + "Running datastore\n" + "Full path of the file\n") +{ + Mgmtd__DatastoreId datastore = mgmt_ds_name2id(dsname); + struct mgmt_ds_ctx *ds_ctx; + FILE *f; + + ds_ctx = mgmt_ds_get_ctx_by_id(mm, datastore); + if (!ds_ctx) { + vty_out(vty, "ERROR: Could not access the '%s' datastore!\n", + dsname); + return CMD_ERR_NO_MATCH; + } + + if (!filepath) { + vty_out(vty, "ERROR: No file path mentioned!\n"); + return CMD_ERR_NO_MATCH; + } + + f = fopen(filepath, "w"); + if (!f) { + vty_out(vty, "Could not open file pointed by filepath %s\n", + filepath); + return CMD_SUCCESS; + } + + mgmt_ds_dump_tree(vty, ds_ctx, "/", f, LYD_JSON); + + fclose(f); + + return CMD_SUCCESS; +} + +DEFPY(show_mgmt_cmt_hist, + show_mgmt_cmt_hist_cmd, + "show mgmt commit-history", + SHOW_STR + MGMTD_STR + "Show commit history\n") +{ + show_mgmt_cmt_history(vty); + return CMD_SUCCESS; +} + +DEFPY(mgmt_rollback, + mgmt_rollback_cmd, + "mgmt rollback ", + MGMTD_STR + "Rollback commits\n" + "Rollback to commit ID\n" + "Commit-ID\n" + "Rollbak n commits\n" + "Number of commits\n") +{ + if (commit) + mgmt_history_rollback_by_id(vty, commit); + else + mgmt_history_rollback_n(vty, last); + + return CMD_SUCCESS; +} + +int config_write_mgmt_debug(struct vty *vty); +static struct cmd_node debug_node = { + .name = "mgmt debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_mgmt_debug, +}; + +static int write_mgmt_debug_helper(struct vty *vty, bool config) +{ + uint32_t mode = config ? DEBUG_MODE_CONF : DEBUG_MODE_ALL; + bool be = DEBUG_MODE_CHECK(&mgmt_debug_be, mode); + bool ds = DEBUG_MODE_CHECK(&mgmt_debug_ds, mode); + bool fe = DEBUG_MODE_CHECK(&mgmt_debug_fe, mode); + bool txn = DEBUG_MODE_CHECK(&mgmt_debug_txn, mode); + + if (!(be || ds || fe || txn)) + return 0; + + vty_out(vty, "debug mgmt"); + if (be) + vty_out(vty, " backend"); + if (ds) + vty_out(vty, " datastore"); + if (fe) + vty_out(vty, " frontend"); + if (txn) + vty_out(vty, " transaction"); + + vty_out(vty, "\n"); + + return 0; +} + +int config_write_mgmt_debug(struct vty *vty) +{ + return write_mgmt_debug_helper(vty, true); +} + +DEFPY_NOSH(show_debugging_mgmt, show_debugging_mgmt_cmd, + "show debugging [mgmt]", SHOW_STR DEBUG_STR "MGMT Information\n") +{ + vty_out(vty, "MGMT debugging status:\n"); + + write_mgmt_debug_helper(vty, false); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFPY(debug_mgmt, debug_mgmt_cmd, + "[no$no] debug mgmt {backend$be|datastore$ds|frontend$fe|transaction$txn}", + NO_STR DEBUG_STR MGMTD_STR + "Backend debug\n" + "Datastore debug\n" + "Frontend debug\n" + "Transaction debug\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + if (be) { + DEBUG_MODE_SET(&mgmt_debug_be, mode, !no); + mgmt_be_adapter_toggle_client_debug( + DEBUG_MODE_CHECK(&mgmt_debug_be, DEBUG_MODE_ALL)); + } + if (ds) + DEBUG_MODE_SET(&mgmt_debug_ds, mode, !no); + if (fe) { + DEBUG_MODE_SET(&mgmt_debug_fe, mode, !no); + mgmt_fe_adapter_toggle_client_debug( + DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)); + } + if (txn) + DEBUG_MODE_SET(&mgmt_debug_txn, mode, !no); + + return CMD_SUCCESS; +} + +static void mgmt_config_read_in(struct event *event) +{ + if (vty_mgmt_fe_enabled()) + mgmt_vty_read_configs(); + else { + zlog_warn("%s: no connection to front-end server, retry in 1s", + __func__); + event_add_timer(mm->master, mgmt_config_read_in, NULL, 1, + &mgmt_daemon_info->read_in); + } +} + +static int mgmtd_config_write(struct vty *vty) +{ + struct lyd_node *root; + + LY_LIST_FOR (running_config->dnode, root) { + nb_cli_show_dnode_cmds(vty, root, false); + } + + return 1; +} + +static struct cmd_node mgmtd_node = { + .name = "mgmtd", + .node = MGMTD_NODE, + .prompt = "", + .config_write = mgmtd_config_write, +}; + +void mgmt_vty_init(void) +{ + /* + * Library based CLI handlers + */ + filter_cli_init(); + route_map_cli_init(); + affinity_map_init(); + keychain_cli_init(); + + /* + * Initialize command handling from VTYSH connection. + * Call command initialization routines defined by + * backend components that are moved to new MGMTD infra + * here one by one. + */ + zebra_cli_init(); +#ifdef HAVE_RIPD + rip_cli_init(); +#endif +#ifdef HAVE_RIPNGD + ripng_cli_init(); +#endif +#ifdef HAVE_STATICD + static_vty_init(); +#endif + + event_add_event(mm->master, mgmt_config_read_in, NULL, 0, + &mgmt_daemon_info->read_in); + + install_node(&debug_node); + install_node(&mgmtd_node); + + install_element(VIEW_NODE, &show_mgmt_be_adapter_cmd); + install_element(VIEW_NODE, &show_mgmt_be_xpath_reg_cmd); + install_element(VIEW_NODE, &show_mgmt_fe_adapter_cmd); + install_element(VIEW_NODE, &show_mgmt_txn_cmd); + install_element(VIEW_NODE, &show_mgmt_ds_cmd); + install_element(VIEW_NODE, &show_mgmt_get_config_cmd); + install_element(VIEW_NODE, &show_mgmt_get_data_cmd); + install_element(VIEW_NODE, &show_mgmt_dump_data_cmd); + install_element(VIEW_NODE, &show_mgmt_map_xpath_cmd); + install_element(VIEW_NODE, &show_mgmt_cmt_hist_cmd); + + install_element(CONFIG_NODE, &mgmt_commit_cmd); + install_element(CONFIG_NODE, &mgmt_create_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_set_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_delete_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_remove_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_replace_config_data_cmd); + install_element(CONFIG_NODE, &mgmt_edit_cmd); + install_element(CONFIG_NODE, &mgmt_rpc_cmd); + install_element(CONFIG_NODE, &mgmt_load_config_cmd); + install_element(CONFIG_NODE, &mgmt_save_config_cmd); + install_element(CONFIG_NODE, &mgmt_rollback_cmd); + + install_element(VIEW_NODE, &debug_mgmt_cmd); + install_element(CONFIG_NODE, &debug_mgmt_cmd); + + /* Enable view */ + install_element(ENABLE_NODE, &mgmt_performance_measurement_cmd); + install_element(ENABLE_NODE, &mgmt_reset_performance_stats_cmd); + + install_element(ENABLE_NODE, &show_debugging_mgmt_cmd); + + mgmt_fe_client_lib_vty_init(); + /* + * TODO: Register and handlers for auto-completion here. + */ +} diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am new file mode 100644 index 0000000..14544c4 --- /dev/null +++ b/mgmtd/subdir.am @@ -0,0 +1,107 @@ +# +# mgmtd -- Mangagement Daemon +# + +# dist_examples_DATA += \ + # end + +vtysh_daemons += mgmtd + +# man8 += $(MANBUILD)/frr-mgmtd.8 +# endif + +clippy_scan += \ + mgmtd/mgmt_vty.c \ + # end + +lib_LTLIBRARIES += mgmtd/libmgmt_be_nb.la +mgmtd_libmgmt_be_nb_la_SOURCES = \ + mgmtd/mgmt_be_nb.c \ + zebra/zebra_cli.c \ + # end +nodist_mgmtd_libmgmt_be_nb_la_SOURCES = \ + # end +mgmtd_libmgmt_be_nb_la_CFLAGS = $(AM_CFLAGS) -DINCLUDE_MGMTD_CMDDEFS_ONLY +mgmtd_libmgmt_be_nb_la_CPPFLAGS = $(AM_CPPFLAGS) -DINCLUDE_MGMTD_CMDDEFS_ONLY +mgmtd_libmgmt_be_nb_la_LDFLAGS = -version-info 0:0:0 + +noinst_LIBRARIES += mgmtd/libmgmtd.a +mgmtd_libmgmtd_a_SOURCES = \ + mgmtd/mgmt.c \ + mgmtd/mgmt_ds.c \ + mgmtd/mgmt_be_adapter.c \ + mgmtd/mgmt_fe_adapter.c \ + mgmtd/mgmt_history.c \ + mgmtd/mgmt_memory.c \ + mgmtd/mgmt_txn.c \ + mgmtd/mgmt_vty.c \ + # end + +noinst_HEADERS += \ + mgmtd/mgmt.h \ + mgmtd/mgmt_be_adapter.h \ + mgmtd/mgmt_ds.h \ + mgmtd/mgmt_fe_adapter.h \ + mgmtd/mgmt_history.h \ + mgmtd/mgmt_memory.h \ + mgmtd/mgmt_txn.h \ + zebra/zebra_cli.h \ + # end + +sbin_PROGRAMS += mgmtd/mgmtd + +if MGMTD_TESTC +sbin_PROGRAMS += mgmtd/mgmtd_testc +mgmtd_mgmtd_testc_SOURCES = mgmtd/mgmt_testc.c +mgmtd_mgmtd_testc_LDADD = lib/libfrr.la +endif + +mgmtd_mgmtd_SOURCES = \ + mgmtd/mgmt_main.c \ + # end +nodist_mgmtd_mgmtd_SOURCES = \ + yang/frr-zebra.yang.c \ + yang/frr-zebra-route-map.yang.c \ + yang/ietf/ietf-netconf.yang.c \ + yang/ietf/ietf-netconf-with-defaults.yang.c \ + # nothing +mgmtd_mgmtd_CFLAGS = $(AM_CFLAGS) -I ./ +mgmtd_mgmtd_LDADD = mgmtd/libmgmtd.a lib/libfrr.la $(LIBCAP) $(LIBM) $(LIBYANG_LIBS) $(UST_LIBS) +mgmtd_mgmtd_LDADD += mgmtd/libmgmt_be_nb.la + + +if STATICD +nodist_mgmtd_mgmtd_SOURCES += yang/frr-bfdd.yang.c +else +if RIPD +nodist_mgmtd_mgmtd_SOURCES += yang/frr-bfdd.yang.c +endif +endif + +if RIPD +nodist_mgmtd_mgmtd_SOURCES += \ + yang/frr-ripd.yang.c \ + # end +mgmtd_libmgmt_be_nb_la_SOURCES += \ + ripd/rip_cli.c \ + # end +endif + +if RIPNGD +nodist_mgmtd_mgmtd_SOURCES += \ + yang/frr-ripngd.yang.c \ + # end +mgmtd_libmgmt_be_nb_la_SOURCES += \ + ripngd/ripng_cli.c \ + # end +endif + +if STATICD +nodist_mgmtd_mgmtd_SOURCES += \ + yang/frr-staticd.yang.c \ + # end +nodist_mgmtd_libmgmt_be_nb_la_SOURCES += \ + staticd/static_vty.c \ + # end +endif + diff --git a/mlag/mlag.proto b/mlag/mlag.proto new file mode 100644 index 0000000..1e30215 --- /dev/null +++ b/mlag/mlag.proto @@ -0,0 +1,186 @@ +// See README.txt for information and build instructions. +// +// Note: START and END tags are used in comments to define sections used in +// tutorials. They are not part of the syntax for Protocol Buffers. +// +// To get an in-depth walkthrough of this file and the related examples, see: +// https://developers.google.com/protocol-buffers/docs/tutorials + +// [START declaration] +syntax = "proto3"; +//package tutorial; + +/* + * This Contains the Message structures used for PIM MLAG Active-Active support. + * Mainly there were two types of messages + * + * 1. Messages sent from PIM (Node-1) to PIM (Node-2) + * 2. Messages sent from CLAG to PIM (status Messages) + * + * ProtoBuf supports maximum 32 fields, so to make it more generic message + * encoding is like below. + * __________________________________________ + * | | | + * | Header | bytes | + * ___________________________________________ + * + * + * Header carries Information about + * 1) what Message it is carrying + * 2) Bytes carries the actual payload encoded with protobuf + * + * + * Limitations + *============= + * Since message-type is 32-bit, there were no real limitations on number of + * messages Infra can support, but each message can carry only 32 fields. + * + */ + + +// [START messages] +message ZebraMlag_Header { + enum MessageType { + ZEBRA_MLAG_NONE = 0; //Invalid message-type + ZEBRA_MLAG_REGISTER = 1; + ZEBRA_MLAG_DEREGISTER = 2; + ZEBRA_MLAG_STATUS_UPDATE = 3; + ZEBRA_MLAG_MROUTE_ADD = 4; + ZEBRA_MLAG_MROUTE_DEL = 5; + ZEBRA_MLAG_DUMP = 6; + ZEBRA_MLAG_MROUTE_ADD_BULK = 7; + ZEBRA_MLAG_MROUTE_DEL_BULK = 8; + ZEBRA_MLAG_PIM_CFG_DUMP = 10; + ZEBRA_MLAG_VXLAN_UPDATE = 11; + ZEBRA_MLAG_ZEBRA_STATUS_UPDATE = 12; + } + + /* + * tells what type of message this payload carries + */ + MessageType type = 1; + + /* + * Length of payload + */ + uint32 len = 2; + + /* + * Actual Encoded payload + */ + bytes data = 3; +} + + +/* + * ZEBRA_MLAG_REGISTER & ZEBRA_MLAG_DEREGISTER + * + * After the MLAGD is up, First Zebra has to register to send any data, + * otherwise MLAGD will not accept any data from the client. + * De-register will be used for the Data cleanup at MLAGD + * These are NULL payload message currently + */ + +/* + * ZEBRA_MLAG_STATUS_UPDATE + * + * This message will be posted by CLAGD(an external control plane manager + * which monitors CLAG failures) to inform peerlink/CLAG Failure + * to zebra, after the failure Notification Node with primary role will + * forward the Traffic and Node with standby will drop the traffic + */ + +message ZebraMlagStatusUpdate { + enum ClagState { + CLAG_STATE_DOWN = 0; + CLAG_STATE_RUNNING = 1; + } + + enum ClagRole { + CLAG_ROLE_NONE = 0; + CLAG_ROLE_PRIMAY = 1; + CLAG_ROLE_SECONDARY = 2; + } + + string peerlink = 1; + ClagRole my_role = 2; + ClagState peer_state = 3; +} + +/* + * ZEBRA_MLAG_VXLAN_UPDATE + * + * This message will be posted by CLAGD(an external control plane Manager + * which is responsible for MCLAG) to inform zebra obout anycast/local + * ip updates. + */ +message ZebraMlagVxlanUpdate { + uint32 anycast_ip = 1; + uint32 local_ip = 2; +} + +/* + * ZebraMlagZebraStatusUpdate + * + * This message will be posted by CLAGD to advertise FRR state + * Change Information to peer + */ + +message ZebraMlagZebraStatusUpdate{ + enum FrrState { + FRR_STATE_NONE = 0; + FRR_STATE_DOWN = 1; + FRR_STATE_UP = 2; + } + + FrrState peer_frrstate = 1; +} + +/* + * ZEBRA_MLAG_MROUTE_ADD & ZEBRA_MLAG_MROUTE_DEL + * + * These messages will be sent from PIM (Node-1) to PIM (Node-2) to perform + * DF Election for each Mcast flow. Elected DF will forward the traffic + * towards the host and loser will keep the OIL as empty, so that only single + * copy will be sent to host + * This message will be posted with any change in the params. + * + * ZEBRA_MLAG_MROUTE_DEL is mainly to delete the record at MLAGD when the + * mcast flow is deleted. + * key for the MLAGD lookup is (vrf_id, source_ip & group_ip) + */ + +message ZebraMlagMrouteAdd { + string vrf_name = 1; + uint32 source_ip = 2; + uint32 group_ip = 3; + /* + * This is the IGP Cost to reach Configured RP in case of (*,G) or + * Cost to the source in case of (S,G) entry + */ + uint32 cost_to_rp = 4; + uint32 owner_id = 5; + bool am_i_DR = 6; + bool am_i_Dual_active = 7; + uint32 vrf_id = 8; + string intf_name = 9; +} + +message ZebraMlagMrouteDel { + string vrf_name = 1; + uint32 source_ip = 2; + uint32 group_ip = 3; + uint32 owner_id = 4; + uint32 vrf_id = 5; + string intf_name = 6; +} + +message ZebraMlagMrouteAddBulk { + repeated ZebraMlagMrouteAdd mroute_add = 1; +} + +message ZebraMlagMrouteDelBulk { + repeated ZebraMlagMrouteDel mroute_del = 1; +} + +// [END messages] diff --git a/mlag/mlag_pb.c b/mlag/mlag_pb.c new file mode 100644 index 0000000..01c4f0b --- /dev/null +++ b/mlag/mlag_pb.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: ISC +/* + * libmlag_pb library stub + */ + +#include "config.h" +#include "xref.h" + +XREF_SETUP(); diff --git a/mlag/subdir.am b/mlag/subdir.am new file mode 100644 index 0000000..aa30d33 --- /dev/null +++ b/mlag/subdir.am @@ -0,0 +1,20 @@ +if HAVE_PROTOBUF3 +lib_LTLIBRARIES += mlag/libmlag_pb.la +endif + +mlag_libmlag_pb_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +mlag_libmlag_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) +mlag_libmlag_pb_la_SOURCES = \ + mlag/mlag_pb.c \ + # end + +nodist_mlag_libmlag_pb_la_SOURCES = \ + mlag/mlag.pb-c.c \ + # end + +CLEANFILES += \ + mlag/mlag.pb-c.c \ + mlag/mlag.pb-c.h \ + # end + +EXTRA_DIST += mlag/mlag.proto diff --git a/nhrpd/.gitignore b/nhrpd/.gitignore new file mode 100644 index 0000000..3d4d56d --- /dev/null +++ b/nhrpd/.gitignore @@ -0,0 +1 @@ +nhrpd diff --git a/nhrpd/Makefile b/nhrpd/Makefile new file mode 100644 index 0000000..62c9546 --- /dev/null +++ b/nhrpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. nhrpd/nhrpd +%: ALWAYS + @$(MAKE) -s -C .. nhrpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/nhrpd/README.kernel b/nhrpd/README.kernel new file mode 100644 index 0000000..067ff98 --- /dev/null +++ b/nhrpd/README.kernel @@ -0,0 +1,146 @@ +KERNEL REQUIREMENTS +=================== + +The linux kernel has had various major regressions, performance +issues and subtle bugs (especially in pmtu). Here is a short list +of some -stable kernels and the first point release that is supposedly +working well with opennhrp/dmvpn: + 3.12.8 or later + 3.14.54 or later + 3.18.22 or later[1] + +[1] But you need to apply the following two backported commits: + 3cdaa5be9e ipv4: Don't increase PMTU with Datagram Too Big message + cb6ccf09d6 route: Use ipv4_mtu instead of raw rt_pmtu + +See below for list of known issues in various kernel versions. + +Kernels earlier than 3.12 need CONFIG_ARPD enabled in the configuration. +Many distributions do not enable it by default, and you may need to +compile your own kernel. + +KERNEL BUGS +=========== + +DMVPN and mGRE support in the kernel has been brittle. There are various +regressions in multiple kernel versions. + +This list tries to collect them to one source of information: + +- forward pmtu is disabled intentionally (but tunnel devices rely on it) + Broken since 3.14-rc1: + commit "ipv4: introduce ip_dst_mtu_maybe_forward and protect forwarding path against pmtu spoofing" + Workaround: + Set sysctl net.ipv4.ip_forward_use_pmtu=1 + See: https://marc.info/?t=143636239500003&r=1&w=2 for details + (Should fix kernel to have this by default on for tunnel devices) + +- subtle path mtu mishandling issues + Broken since (uncertain) + Fixed in 4.1-rc2: + commit "ipv4: Don't increase PMTU with Datagram Too Big message." + commit "route: Use ipv4_mtu instead of raw rt_pmtu" + +- fragmentation of large packets inside tunnel not working + Broken since 3.11-rc1 + commit "ip_tunnels: Use skb-len to PMTU check." + Fixed in 3.14.54, 3.18.22, 4.1.9, 4.2-rc3 + commit "ip_tunnel: fix ipv4 pmtu check to honor inner ip header df" + +- ipsec will crash during xfrm gc + Broke since 3.15-rc1 + commit "flowcache: Make flow cache name space aware" + Fixed in 3.18.10, 4.0 + commit "flowcache: Fix kernel panic in flow_cache_flush_task" + +- TSO on GRE tunnels failed, and resulted in very slow performance + Broke since 3.14.24, 3.18-rc3 + commit "gre: Use inner mac length when computing tunnel length" + Fixed in 3.14.30, 3.18.4 + commit "gre: fix the inner mac header in nbma tunnel xmit path" + commit "gre: Set inner mac header in gro complete" + +- NAPI GRO handling was broken; causing immediate crash (32-bit only?) + Broken since 3.13-rc1 + commit "net: gro: allow to build full sized skb" + Fixed 3.14.5, 3.15-rc7 + commit "net: gro: make sure skb->cb[] initial content has not to be zero" + +- ip_gre dst caching broke NBMA GRE tunnels + Broken since 3.14-rc1 + Fixed in 3.14.5, 3.15-rc6 + commit "ipv4: ip_tunnels: disable cache for nbma gre tunnels" + +- Few packets can be lost when neighbor entry is in NUD_PROBE state, + and there is continuous traffic to it. + Broken since dawn of time + Fixed in 3.15-rc1 + commit "neigh: probe application via netlink in NUD_PROBE" + +- GRO was implemented for GRE, but the hw capabilities were not updated + correctly. In practice forwarding from non-GRE (physical) interface + to GRE interface with gro/gso/tx offloads enabled (also on the target + interface) does not work properly. + Broken around 3.9 to 3.11, need to check details. + +- recvfrom() returned incorrect NBMA address, breaking NAT detection + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.10.27, 3.12.8, 3.13-rc7 + commit "ip_gre: fix msg_name parsing for recvfrom/recvmsg" + +- sendto() was broken causing opennhrp not work at all + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.10.12, 3.11-rc6 + commit "ip_gre: fix ipgre_header to return correct offset" + +- PMTU was broken due to GRE driver rewrite + Broken since 3.10-rc1 + commit "GRE: Refactor GRE tunneling code." + Fixed in 3.11-rc1 + commit "ip_tunnels: Use skb-len to PMTU check." + +- PMTU was broken due to routing cache removal + Broken since 3.6-rc1 + commit "ipv4: Cache input routes in fib_info nexthops" + Fixed in 3.11-rc1 + commit "ipv4: use next hop exceptions also for input routes" + + 3 other commits + Patches exist for 3.10, but they were not approved to 3.10-stable. + +- Race condition during bootup: changing ARP flag did not flush + existing neighbor entries, causing problems if traffic was routed + to gre interface before opennhrp was running. + Broken since dawn of time + Fixed in 3.11-rc1 + commit "arp: flush arp cache on IFF_NOARP change" + +- Crash in IPsec + Broken since 3.9-rc1 + commit "xfrm: removes a superfluous check and add a statistic" + Fixed in 3.10-rc3 + commit "xfrm: properly handle invalid states as an error" + +- An incorrect ip_gre change broke NHRP traffic over GRE + Broken since 3.8-rc2 + commit "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally" + Fixed in 3.8.5, 3.9-rc4 + commit "Revert "ip_gre: make ipgre_tunnel_xmit() not parse network header as IP unconditionally"" + +- Multicast traffic over mGRE was broken. + Broken since 2.6.34-rc2 + commit "gre: fix hard header destination address checking" + Fixed in 2.6.39-rc2 + commit "net: gre: provide multicast mappings for ipv4 and ipv6" + +- Serious performance issues causing small throughput on medium to large DMVPN networks + Broken since dawn of time + Fixed in 2.6.35 + multiple commits rewriting ipsec caching + +- Even though around 2.6.24 is the first version where opennhrp started + to work, there has been various PMTU, performance, and functionality + bugs before 2.6.34. That's one of the first version I consider stable + wrt. to opennhrp functionality. + diff --git a/nhrpd/README.nhrpd b/nhrpd/README.nhrpd new file mode 100644 index 0000000..8bb5f69 --- /dev/null +++ b/nhrpd/README.nhrpd @@ -0,0 +1,138 @@ +Quagga / NHRP Design and Configuration Notes +============================================ + +Quagga/NHRP is an NHRP (RFC2332) implementation for Linux. The primary +use case is to implement DMVPN. The aim is thus to be compatible with +Cisco DMVPN (and potentially with FlexVPN in the future). + + +Current Status +-------------- + +- IPsec integration with strongSwan (requires patched strongSwan) +- IPv4 over IPv4 NBMA GRE +- IPv6 over IPv4 NBMA GRE -- majority of code exist; but is not tested +- Spoke (NHC) functionality complete +- Hub (NHS) functionality complete +- Multicast support is not done yet + (so OSPF will not work, use BGP for now) + +The code is not (yet) compatible with Cisco FlexVPN style DMVPN. It +would require relaying IKEv2 routing messages from strongSwan to nhrpd +and parsing that. It is doable, but not implemented for the time being. + + +Routing Design +-------------- + +In contrast to opennhrp routing design, Quagga/NHRP routes each NHRP +domain address individually (similar to Cisco FlexVPN). + +To create NBMA GRE tunnel you might use following: + ip tunnel add gre1 mode gre key 42 ttl 64 dev eth0 + ip addr add 10.255.255.2/32 dev gre1 + ip link set gre1 up + +This has two important differences compared to opennhrp setup: + 1. The 'tunnel add' now specifies physical device binding. Quagga/NHRP + wants to know stable protocol address to NBMA address mapping. Thus, + add 'dev ' binding, or specify 'local '. If + neither of this is specified, NHRP will not be enabled on the interface. + Alternatively you can skip 'dev' binding on tunnel if you allow + nhrpd to manage it using 'tunnel source' command (see below). + + 2. The 'addr add' now has host prefix. In opennhrp you would have used + the GRE subnet prefix length here instead, e.g. /24. + +Quagga/NHRP will automatically create additional host routes pointing to +gre1 when a connection with these hosts is established. The gre1 subnet +should be announced by routing protocol. This allows routing protocol +to decide which is the closest hub and get the gre addresses' traffic. + +The second benefit is that hubs can then easily exchange host prefixes +of directly connected gre addresses. And thus routing of gre addresses +inside hubs is based on routing protocol's shortest path choice -- not +on random choice from next hop server list. + + +Configuring nhrpd +----------------- + +The configuration is done using vtysh, and most commands do what they +do in Cisco. As minimal configuration example one can do: + configure terminal + interface gre1 + tunnel protection vici profile dmvpn + tunnel source eth0 + ip nhrp network-id 1 + ip nhrp shortcut + ip nhrp registration no-unique + ip nhrp nhs dynamic nbma hubs.example.com + +There's important notes about the "ip nhrp nhs" command: + + 1. The 'dynamic' works only against Cisco (or nhrpd), but is not + compatible with opennhrp. To use dynamic detection of opennhrp hub's + protocol address use the GRE broadcast address there. For the above + example of 10.255.255.0/24 the configuration should read instead: + ip nhrp nhs 10.255.255.255 nbma hubs.example.com + + 2. nbma works like opennhrp dynamic-map. That is, all of the + A-records are configured as NBMA addresses of different hubs, and + each hub protocol address will be dynamically detected. + + +Hub functionality +----------------- + +Sending Traffic Indication (redirect) notifications is now accomplished +using NFLOG. + +Use: +iptables -A FORWARD -i gre1 -o gre1 \ + -m hashlimit --hashlimit-upto 4/minute --hashlimit-burst 1 \ + --hashlimit-mode srcip,dstip --hashlimit-srcmask 16 --hashlimit-dstmask 16 \ + --hashlimit-name loglimit-0 -j NFLOG --nflog-group 1 --nflog-range 128 + +or similar to get rate-limited samples of the packets that match traffic +flow needing redirection. This kernel NFLOG target's nflog-group is configured +in global nhrp config with: + nhrp nflog-group 1 + +To start sending these traffic notices out from hubs, use the nhrp per-interface +directive: + ip nhrp redirect + +opennhrp used PF_PACKET and tried to create packet filter to get only +the packets of interest. Though, this was bad if shortcut fails to +establish (remote policy, or both are behind NAT or restrictive +firewalls), all of the relayaed traffic would match always. + + +Getting information via vtysh +----------------------------- + +Some commands of interest: + - show dmvpn + - show ip nhrp cache + - show ip nhrp shortcut + - show ip route nhrp + - clear ip nhrp cache + - clear ip nhrp shortcut + + +Integration with strongSwan +--------------------------- + +Contrary to opennhrp, Quagga/NHRP has tight integration with IKE daemon. +Currently strongSwan is supported using the VICI protocol. strongSwan +is connected using UNIX socket (default /var/run/charon.vici use configure +argument --with-vici-socket= to change). +Thus nhrpd needs to be run as user that can open that file. + +Currently, you will need patched strongSwan. The working tree is at: + http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras + +And the branch with patches against latest release are: + http://git.alpinelinux.org/cgit/user/tteras/strongswan/log/?h=tteras-release + diff --git a/nhrpd/debug.h b/nhrpd/debug.h new file mode 100644 index 0000000..d5c00ad --- /dev/null +++ b/nhrpd/debug.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "log.h" + +#define NHRP_DEBUG_COMMON (1 << 0) +#define NHRP_DEBUG_KERNEL (1 << 1) +#define NHRP_DEBUG_IF (1 << 2) +#define NHRP_DEBUG_ROUTE (1 << 3) +#define NHRP_DEBUG_VICI (1 << 4) +#define NHRP_DEBUG_EVENT (1 << 5) +#define NHRP_DEBUG_ALL (0xFFFF) + +extern unsigned int debug_flags; + +#define debugf(level, ...) \ + do { \ + if (unlikely(debug_flags & level)) \ + zlog_debug(__VA_ARGS__); \ + } while (0) diff --git a/nhrpd/linux.c b/nhrpd/linux.c new file mode 100644 index 0000000..e4df0dd --- /dev/null +++ b/nhrpd/linux.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon Linux specific glue + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" + +#include +#include +#include + +#include "nhrp_protocol.h" +#include "os.h" + +#ifndef HAVE_STRLCPY +size_t strlcpy(char *__restrict dest, + const char *__restrict src, size_t destsize); +#endif + +static int nhrp_socket_fd = -1; + +int os_socket(void) +{ + if (nhrp_socket_fd < 0) + nhrp_socket_fd = + socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_NHRP)); + return nhrp_socket_fd; +} + +int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, + size_t addrlen, uint16_t protocol) +{ + struct sockaddr_ll lladdr; + struct iovec iov = { + .iov_base = (void *)buf, .iov_len = len, + }; + struct msghdr msg = { + .msg_name = &lladdr, + .msg_namelen = sizeof(lladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int status, fd; + + if (addrlen > sizeof(lladdr.sll_addr)) + return -1; + + memset(&lladdr, 0, sizeof(lladdr)); + lladdr.sll_family = AF_PACKET; + lladdr.sll_protocol = htons(protocol); + lladdr.sll_ifindex = ifindex; + lladdr.sll_halen = addrlen; + memcpy(lladdr.sll_addr, addr, addrlen); + + fd = os_socket(); + if (fd < 0) + return -1; + + status = sendmsg(fd, &msg, 0); + if (status < 0) + return -errno; + + return status; +} + +int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, + size_t *addrlen) +{ + struct sockaddr_ll lladdr; + struct iovec iov = { + .iov_base = buf, .iov_len = *len, + }; + struct msghdr msg = { + .msg_name = &lladdr, + .msg_namelen = sizeof(lladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int r; + + r = recvmsg(nhrp_socket_fd, &msg, MSG_DONTWAIT); + if (r < 0) + return r; + + *len = r; + *ifindex = lladdr.sll_ifindex; + + if (*addrlen <= (size_t)lladdr.sll_addr) { + if (memcmp(lladdr.sll_addr, "\x00\x00\x00\x00", 4) != 0) { + memcpy(addr, lladdr.sll_addr, lladdr.sll_halen); + *addrlen = lladdr.sll_halen; + } else { + *addrlen = 0; + } + } + + return 0; +} + +static int linux_icmp_redirect_off(const char *iface) +{ + char fname[PATH_MAX]; + int fd, ret = -1; + + snprintf(fname, sizeof(fname), + "/proc/sys/net/ipv4/conf/%s/send_redirects", iface); + fd = open(fname, O_WRONLY); + if (fd < 0) + return -1; + if (write(fd, "0\n", 2) == 2) + ret = 0; + close(fd); + + return ret; +} + +int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af) +{ + int ret = 0; + + switch (af) { + case AF_INET: + ret |= linux_icmp_redirect_off("all"); + ret |= linux_icmp_redirect_off(ifname); + break; + } + + return ret; +} diff --git a/nhrpd/netlink.h b/nhrpd/netlink.h new file mode 100644 index 0000000..33af1a0 --- /dev/null +++ b/nhrpd/netlink.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP netlink/neighbor table API + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include +#include +#include + + +extern int netlink_nflog_group; +extern int netlink_mcast_nflog_group; + +void netlink_update_binding(struct interface *ifp, union sockunion *proto, + union sockunion *nbma); +void netlink_set_nflog_group(int nlgroup); + diff --git a/nhrpd/netlink_arp.c b/nhrpd/netlink_arp.c new file mode 100644 index 0000000..be909c8 --- /dev/null +++ b/nhrpd/netlink_arp.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP netlink/neighbor table arpd code + * Copyright (c) 2014-2016 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef GNU_LINUX +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "frrevent.h" +#include "stream.h" +#include "prefix.h" +#include "nhrpd.h" +#include "netlink.h" +#include "znl.h" + +int netlink_nflog_group; +static int netlink_log_fd = -1; +static struct event *netlink_log_thread; + +void netlink_update_binding(struct interface *ifp, union sockunion *proto, + union sockunion *nbma) +{ + nhrp_send_zebra_nbr(proto, nbma, ifp); +} + +static void netlink_log_register(int fd, int group) +{ + struct nlmsghdr *n; + struct nfgenmsg *nf; + struct nfulnl_msg_config_cmd cmd; + struct zbuf *zb = zbuf_alloc(512); + + n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG, + NLM_F_REQUEST | NLM_F_ACK); + nf = znl_push(zb, sizeof(*nf)); + *nf = (struct nfgenmsg){ + .nfgen_family = AF_UNSPEC, + .version = NFNETLINK_V0, + .res_id = htons(group), + }; + cmd.command = NFULNL_CFG_CMD_BIND; + znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); + znl_nlmsg_complete(zb, n); + + zbuf_send(zb, fd); + zbuf_free(zb); +} + +static void netlink_log_indication(struct nlmsghdr *msg, struct zbuf *zb) +{ + struct nfgenmsg *nf; + struct rtattr *rta; + struct zbuf rtapl, pktpl; + struct interface *ifp; + struct nfulnl_msg_packet_hdr *pkthdr = NULL; + uint32_t *in_ndx = NULL; + + nf = znl_pull(zb, sizeof(*nf)); + if (!nf) + return; + + memset(&pktpl, 0, sizeof(pktpl)); + while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { + switch (rta->rta_type) { + case NFULA_PACKET_HDR: + pkthdr = znl_pull(&rtapl, sizeof(*pkthdr)); + break; + case NFULA_IFINDEX_INDEV: + in_ndx = znl_pull(&rtapl, sizeof(*in_ndx)); + break; + case NFULA_PAYLOAD: + pktpl = rtapl; + break; + /* NFULA_HWHDR exists and is supposed to contain source + * hardware address. However, for ip_gre it seems to be + * the nexthop destination address if the packet matches + * route. */ + } + } + + if (!pkthdr || !in_ndx || !zbuf_used(&pktpl)) + return; + + ifp = if_lookup_by_index(htonl(*in_ndx), VRF_DEFAULT); + if (!ifp) + return; + + nhrp_peer_send_indication(ifp, htons(pkthdr->hw_protocol), &pktpl); +} + +static void netlink_log_recv(struct event *t) +{ + uint8_t buf[ZNL_BUFFER_SIZE]; + int fd = EVENT_FD(t); + struct zbuf payload, zb; + struct nlmsghdr *n; + + + zbuf_init(&zb, buf, sizeof(buf), 0); + while (zbuf_recv(&zb, fd) > 0) { + while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { + debugf(NHRP_DEBUG_KERNEL, + "Netlink-log: Received msg_type %u, msg_flags %u", + n->nlmsg_type, n->nlmsg_flags); + switch (n->nlmsg_type) { + case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET: + netlink_log_indication(n, &payload); + break; + } + } + } + + event_add_read(master, netlink_log_recv, 0, netlink_log_fd, + &netlink_log_thread); +} + +void netlink_set_nflog_group(int nlgroup) +{ + if (netlink_log_fd >= 0) { + event_cancel(&netlink_log_thread); + close(netlink_log_fd); + netlink_log_fd = -1; + } + netlink_nflog_group = nlgroup; + if (nlgroup) { + netlink_log_fd = znl_open(NETLINK_NETFILTER, 0); + if (netlink_log_fd < 0) + return; + + netlink_log_register(netlink_log_fd, nlgroup); + event_add_read(master, netlink_log_recv, 0, netlink_log_fd, + &netlink_log_thread); + } +} + +int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS) +{ + union sockunion addr = {}, lladdr = {}; + struct interface *ifp; + int state, ndm_state; + struct nhrp_cache *c; + struct zapi_neigh_ip api = {}; + + zclient_neigh_ip_decode(zclient->ibuf, &api); + + if (api.ip_len != IPV4_MAX_BYTELEN && api.ip_len != 0) + return 0; + + if (api.ip_in.ipa_type == AF_UNSPEC) + return 0; + sockunion_family(&addr) = api.ip_in.ipa_type; + memcpy((uint8_t *)sockunion_get_addr(&addr), &api.ip_in.ip.addr, + family2addrsize(api.ip_in.ipa_type)); + + sockunion_family(&lladdr) = api.ip_out.ipa_type; + if (api.ip_out.ipa_type != AF_UNSPEC) + memcpy((uint8_t *)sockunion_get_addr(&lladdr), + &api.ip_out.ip.addr, + family2addrsize(api.ip_out.ipa_type)); + + ifp = if_lookup_by_index(api.index, vrf_id); + ndm_state = api.ndm_state; + + if (!ifp) + return 0; + c = nhrp_cache_get(ifp, &addr, 0); + if (!c) + return 0; + debugf(NHRP_DEBUG_KERNEL, + "Netlink: %s %pSU dev %s lladdr %pSU nud 0x%x cache used %u type %u", + (cmd == ZEBRA_NEIGH_GET) ? "who-has" + : (cmd == ZEBRA_NEIGH_ADDED) ? "new-neigh" + : "del-neigh", + &addr, ifp->name, &lladdr, ndm_state, c->used, c->cur.type); + if (cmd == ZEBRA_NEIGH_GET) { + if (c->cur.type >= NHRP_CACHE_CACHED) { + nhrp_cache_set_used(c, 1); + debugf(NHRP_DEBUG_KERNEL, + "Netlink: update binding for %pSU dev %s from c %pSU peer.vc.nbma %pSU to lladdr %pSU", + &addr, ifp->name, &c->cur.remote_nbma_natoa, + &c->cur.peer->vc->remote.nbma, &lladdr); + + if (lladdr.sa.sa_family == AF_UNSPEC) + /* nothing from zebra, so use nhrp peer */ + lladdr = c->cur.peer->vc->remote.nbma; + + /* In case of shortcuts, nbma is given by lladdr, not + * vc->remote.nbma. + */ + netlink_update_binding(ifp, &addr, &lladdr); + } + } else { + state = (cmd == ZEBRA_NEIGH_ADDED) ? ndm_state + : ZEBRA_NEIGH_STATE_FAILED; + nhrp_cache_set_used(c, state == ZEBRA_NEIGH_STATE_REACHABLE); + } + return 0; +} diff --git a/nhrpd/nhrp_cache.c b/nhrpd/nhrp_cache.c new file mode 100644 index 0000000..e0b8f7b --- /dev/null +++ b/nhrpd/nhrp_cache.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP cache + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "memory.h" +#include "frrevent.h" +#include "hash.h" +#include "nhrpd.h" + +#include "netlink.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE, "NHRP cache entry"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_CACHE_CONFIG, "NHRP cache config entry"); + +unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; + +const char *const nhrp_cache_type_str[] = { + [NHRP_CACHE_INVALID] = "invalid", + [NHRP_CACHE_INCOMPLETE] = "incomplete", + [NHRP_CACHE_NEGATIVE] = "negative", + [NHRP_CACHE_CACHED] = "cached", + [NHRP_CACHE_DYNAMIC] = "dynamic", + [NHRP_CACHE_NHS] = "nhs", + [NHRP_CACHE_STATIC] = "static", + [NHRP_CACHE_LOCAL] = "local", +}; + +static unsigned int nhrp_cache_protocol_key(const void *peer_data) +{ + const struct nhrp_cache *p = peer_data; + return sockunion_hash(&p->remote_addr); +} + +static bool nhrp_cache_protocol_cmp(const void *cache_data, + const void *key_data) +{ + const struct nhrp_cache *a = cache_data; + const struct nhrp_cache *b = key_data; + + return sockunion_same(&a->remote_addr, &b->remote_addr); +} + +static void *nhrp_cache_alloc(void *data) +{ + struct nhrp_cache *p, *key = data; + + p = XMALLOC(MTYPE_NHRP_CACHE, sizeof(struct nhrp_cache)); + + *p = (struct nhrp_cache){ + .cur.type = NHRP_CACHE_INVALID, + .new.type = NHRP_CACHE_INVALID, + .remote_addr = key->remote_addr, + .ifp = key->ifp, + .notifier_list = + NOTIFIER_LIST_INITIALIZER(&p->notifier_list), + }; + nhrp_cache_counts[p->cur.type]++; + + return p; +} + +static void nhrp_cache_free(struct nhrp_cache *c) +{ + struct nhrp_interface *nifp = c->ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Deleting cache entry"); + nhrp_cache_counts[c->cur.type]--; + notifier_call(&c->notifier_list, NOTIFY_CACHE_DELETE); + assert(!notifier_active(&c->notifier_list)); + hash_release(nifp->cache_hash, c); + if (c->cur.peer) + nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier); + nhrp_peer_unref(c->cur.peer); + nhrp_peer_unref(c->new.peer); + EVENT_OFF(c->t_timeout); + EVENT_OFF(c->t_auth); + XFREE(MTYPE_NHRP_CACHE, c); +} + +static unsigned int nhrp_cache_config_protocol_key(const void *peer_data) +{ + const struct nhrp_cache_config *p = peer_data; + return sockunion_hash(&p->remote_addr); +} + +static bool nhrp_cache_config_protocol_cmp(const void *cache_data, + const void *key_data) +{ + const struct nhrp_cache_config *a = cache_data; + const struct nhrp_cache_config *b = key_data; + + if (!sockunion_same(&a->remote_addr, &b->remote_addr)) + return false; + if (a->ifp != b->ifp) + return false; + return true; +} + +static void *nhrp_cache_config_alloc(void *data) +{ + struct nhrp_cache_config *p, *key = data; + + p = XCALLOC(MTYPE_NHRP_CACHE_CONFIG, sizeof(struct nhrp_cache_config)); + + *p = (struct nhrp_cache_config){ + .remote_addr = key->remote_addr, + .ifp = key->ifp, + }; + return p; +} + +void nhrp_cache_config_free(struct nhrp_cache_config *c) +{ + struct nhrp_interface *nifp = c->ifp->info; + + hash_release(nifp->cache_config_hash, c); + XFREE(MTYPE_NHRP_CACHE_CONFIG, c); +} + +struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp, + union sockunion *remote_addr, + int create) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_config key; + + if (!nifp->cache_config_hash) { + nifp->cache_config_hash = + hash_create(nhrp_cache_config_protocol_key, + nhrp_cache_config_protocol_cmp, + "NHRP Config Cache"); + if (!nifp->cache_config_hash) + return NULL; + } + key.remote_addr = *remote_addr; + key.ifp = ifp; + + return hash_get(nifp->cache_config_hash, &key, + create ? nhrp_cache_config_alloc : NULL); +} + +static void do_nhrp_cache_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct nhrp_cache *c = hb->data; + + nhrp_cache_free(c); +} + +static void do_nhrp_cache_config_free(struct hash_bucket *hb, + void *arg __attribute__((__unused__))) +{ + struct nhrp_cache_config *cc = hb->data; + + nhrp_cache_config_free(cc); +} + +void nhrp_cache_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted cache entries (%lu)", + nifp->cache_hash ? nifp->cache_hash->count : 0); + + if (nifp->cache_hash) { + hash_iterate(nifp->cache_hash, do_nhrp_cache_free, NULL); + hash_free(nifp->cache_hash); + } + + if (nifp->cache_config_hash) { + hash_iterate(nifp->cache_config_hash, do_nhrp_cache_config_free, + NULL); + hash_free(nifp->cache_config_hash); + } +} + +struct nhrp_cache *nhrp_cache_get(struct interface *ifp, + union sockunion *remote_addr, int create) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache key; + + if (!nifp->cache_hash) { + nifp->cache_hash = + hash_create(nhrp_cache_protocol_key, + nhrp_cache_protocol_cmp, "NHRP Cache"); + if (!nifp->cache_hash) + return NULL; + } + + key.remote_addr = *remote_addr; + key.ifp = ifp; + + return hash_get(nifp->cache_hash, &key, + create ? nhrp_cache_alloc : NULL); +} + +static void nhrp_cache_do_free(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + + c->t_timeout = NULL; + nhrp_cache_free(c); +} + +static void nhrp_cache_do_timeout(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + + c->t_timeout = NULL; + if (c->cur.type != NHRP_CACHE_INVALID) + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); +} + +static void nhrp_cache_update_route(struct nhrp_cache *c) +{ + struct prefix pfx; + struct nhrp_peer *p = c->cur.peer; + struct nhrp_interface *nifp; + + if (!sockunion2hostprefix(&c->remote_addr, &pfx)) + return; + + if (p && nhrp_peer_check(p, 1)) { + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) { + /* remote_nbma_natoa is already set. Therefore, binding + * should be updated to this value and not vc's remote + * nbma. + */ + debugf(NHRP_DEBUG_COMMON, + "cache (remote_nbma_natoa set): Update binding for %pSU dev %s from (deleted) peer.vc.nbma %pSU to %pSU", + &c->remote_addr, p->ifp->name, + &p->vc->remote.nbma, &c->cur.remote_nbma_natoa); + + netlink_update_binding(p->ifp, &c->remote_addr, + &c->cur.remote_nbma_natoa); + } else { + /* update binding to peer->vc->remote->nbma */ + debugf(NHRP_DEBUG_COMMON, + "cache (remote_nbma_natoa unspec): Update binding for %pSU dev %s from (deleted) to peer.vc.nbma %pSU", + &c->remote_addr, p->ifp->name, + &p->vc->remote.nbma); + + netlink_update_binding(p->ifp, &c->remote_addr, + &p->vc->remote.nbma); + } + + nhrp_route_announce(1, c->cur.type, &pfx, c->ifp, NULL, + c->cur.mtu); + if (c->cur.type >= NHRP_CACHE_DYNAMIC) { + nhrp_route_update_nhrp(&pfx, c->ifp); + c->nhrp_route_installed = 1; + } else if (c->nhrp_route_installed) { + nhrp_route_update_nhrp(&pfx, NULL); + c->nhrp_route_installed = 0; + } + if (!c->route_installed) { + notifier_call(&c->notifier_list, NOTIFY_CACHE_UP); + c->route_installed = 1; + } + } else { + /* debug the reason for peer check fail */ + if (p) { + nifp = p->ifp->info; + debugf(NHRP_DEBUG_COMMON, + "cache (peer check failed: online?%d requested?%d ipsec?%d)", + p->online, p->requested, + nifp->ipsec_profile ? 1 : 0); + } else + debugf(NHRP_DEBUG_COMMON, + "cache (peer check failed: no p)"); + + if (c->nhrp_route_installed) { + nhrp_route_update_nhrp(&pfx, NULL); + c->nhrp_route_installed = 0; + } + if (c->route_installed) { + assert(sockunion2hostprefix(&c->remote_addr, &pfx)); + notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); + nhrp_route_announce(0, c->cur.type, &pfx, NULL, NULL, + 0); + c->route_installed = 0; + } + } +} + +static void nhrp_cache_peer_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_cache *c = + container_of(n, struct nhrp_cache, peer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + nhrp_cache_update_route(c); + break; + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + notifier_call(&c->notifier_list, NOTIFY_CACHE_DOWN); + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); + break; + case NOTIFY_PEER_NBMA_CHANGING: + if (c->cur.type == NHRP_CACHE_DYNAMIC) + c->cur.peer->vc->abort_migration = 1; + break; + } +} + +static void nhrp_cache_reset_new(struct nhrp_cache *c) +{ + EVENT_OFF(c->t_auth); + if (notifier_list_anywhere(&c->newpeer_notifier)) + nhrp_peer_notify_del(c->new.peer, &c->newpeer_notifier); + nhrp_peer_unref(c->new.peer); + memset(&c->new, 0, sizeof(c->new)); + c->new.type = NHRP_CACHE_INVALID; +} + +static void nhrp_cache_update_timers(struct nhrp_cache *c) +{ + EVENT_OFF(c->t_timeout); + + switch (c->cur.type) { + case NHRP_CACHE_INVALID: + if (!c->t_auth) + event_add_timer_msec(master, nhrp_cache_do_free, c, 10, + &c->t_timeout); + break; + case NHRP_CACHE_INCOMPLETE: + case NHRP_CACHE_NEGATIVE: + case NHRP_CACHE_CACHED: + case NHRP_CACHE_DYNAMIC: + case NHRP_CACHE_NHS: + case NHRP_CACHE_STATIC: + case NHRP_CACHE_LOCAL: + case NHRP_CACHE_NUM_TYPES: + if (c->cur.expires) + event_add_timer(master, nhrp_cache_do_timeout, c, + c->cur.expires - monotime(NULL), + &c->t_timeout); + break; + } +} + +static void nhrp_cache_authorize_binding(struct nhrp_reqid *r, void *arg) +{ + struct nhrp_cache *c = container_of(r, struct nhrp_cache, eventid); + char buf[3][SU_ADDRSTRLEN]; + + debugf(NHRP_DEBUG_COMMON, "cache: %s %pSU: %s", c->ifp->name, + &c->remote_addr, (const char *)arg); + + nhrp_reqid_free(&nhrp_event_reqid, r); + + if (arg && strcmp(arg, "accept") == 0) { + if (c->cur.peer) { + netlink_update_binding(c->cur.peer->ifp, + &c->remote_addr, NULL); + nhrp_peer_notify_del(c->cur.peer, &c->peer_notifier); + nhrp_peer_unref(c->cur.peer); + } + nhrp_cache_counts[c->cur.type]--; + nhrp_cache_counts[c->new.type]++; + c->cur = c->new; + c->cur.peer = nhrp_peer_ref(c->cur.peer); + nhrp_cache_reset_new(c); + if (c->cur.peer) + nhrp_peer_notify_add(c->cur.peer, &c->peer_notifier, + nhrp_cache_peer_notifier); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) { + debugf(NHRP_DEBUG_COMMON, + "cache: update binding for %pSU dev %s from (deleted) peer.vc.nbma %s to %pSU", + &c->remote_addr, c->ifp->name, + (c->cur.peer ? sockunion2str( + &c->cur.peer->vc->remote.nbma, buf[1], + sizeof(buf[1])) + : "(no peer)"), + &c->cur.remote_nbma_natoa); + + if (c->cur.peer) + netlink_update_binding( + c->cur.peer->ifp, &c->remote_addr, + &c->cur.remote_nbma_natoa); + } + + nhrp_cache_update_route(c); + notifier_call(&c->notifier_list, NOTIFY_CACHE_BINDING_CHANGE); + } else { + nhrp_cache_reset_new(c); + } + + nhrp_cache_update_timers(c); +} + +static void nhrp_cache_do_auth_timeout(struct event *t) +{ + struct nhrp_cache *c = EVENT_ARG(t); + c->t_auth = NULL; + nhrp_cache_authorize_binding(&c->eventid, (void *)"timeout"); +} + +static void nhrp_cache_newpeer_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_cache *c = + container_of(n, struct nhrp_cache, newpeer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + if (nhrp_peer_check(c->new.peer, 1)) { + evmgr_notify("authorize-binding", c, + nhrp_cache_authorize_binding); + event_add_timer(master, nhrp_cache_do_auth_timeout, c, + 10, &c->t_auth); + } + break; + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + nhrp_cache_reset_new(c); + break; + } +} + +int nhrp_cache_update_binding(struct nhrp_cache *c, enum nhrp_cache_type type, + int holding_time, struct nhrp_peer *p, + uint32_t mtu, union sockunion *nbma_oa, + union sockunion *nbma_claimed) +{ + char buf[2][SU_ADDRSTRLEN]; + + if (c->cur.type > type || c->new.type > type) { + nhrp_peer_unref(p); + return 0; + } + + /* Sanitize MTU */ + switch (sockunion_family(&c->remote_addr)) { + case AF_INET: + if (mtu < 576 || mtu >= 1500) + mtu = 0; + /* Opennhrp announces nbma mtu, but we use protocol mtu. + * This heuristic tries to fix up it. */ + if (mtu > 1420) + mtu = (mtu & -16) - 80; + break; + default: + mtu = 0; + break; + } + + sockunion2str(&c->cur.remote_nbma_natoa, buf[0], sizeof(buf[0])); + if (nbma_oa) + sockunion2str(nbma_oa, buf[1], sizeof(buf[1])); + + nhrp_cache_reset_new(c); + if (c->cur.type == type && c->cur.peer == p && c->cur.mtu == mtu) { + debugf(NHRP_DEBUG_COMMON, + "cache: same type %u, updating expiry and changing nbma addr from %s to %s", + type, buf[0], nbma_oa ? buf[1] : "(NULL)"); + if (holding_time > 0) + c->cur.expires = monotime(NULL) + holding_time; + + if (nbma_oa) + c->cur.remote_nbma_natoa = *nbma_oa; + else + memset(&c->cur.remote_nbma_natoa, 0, + sizeof(c->cur.remote_nbma_natoa)); + + if (nbma_claimed) + c->cur.remote_nbma_claimed = *nbma_claimed; + else + memset(&c->cur.remote_nbma_claimed, 0, + sizeof(c->cur.remote_nbma_claimed)); + + nhrp_peer_unref(p); + } else { + debugf(NHRP_DEBUG_COMMON, + "cache: new type %u/%u, or peer %s, or mtu %u/%u, nbma %s --> %s (map %d)", + c->cur.type, type, (c->cur.peer == p) ? "same" : "diff", + c->cur.mtu, mtu, buf[0], nbma_oa ? buf[1] : "(NULL)", + c->map); + c->new.type = type; + c->new.peer = p; + c->new.mtu = mtu; + c->new.holding_time = holding_time; + if (nbma_oa) + c->new.remote_nbma_natoa = *nbma_oa; + + if (nbma_claimed) + c->new.remote_nbma_claimed = *nbma_claimed; + + if (holding_time > 0) + c->new.expires = monotime(NULL) + holding_time; + else if (holding_time < 0) + nhrp_cache_reset_new(c); + + if (c->new.type == NHRP_CACHE_INVALID + || c->new.type >= NHRP_CACHE_STATIC || c->map) { + nhrp_cache_authorize_binding(&c->eventid, + (void *)"accept"); + } else { + nhrp_peer_notify_add(c->new.peer, &c->newpeer_notifier, + nhrp_cache_newpeer_notifier); + nhrp_cache_newpeer_notifier(&c->newpeer_notifier, + NOTIFY_PEER_UP); + event_add_timer(master, nhrp_cache_do_auth_timeout, c, + 60, &c->t_auth); + } + } + nhrp_cache_update_timers(c); + + return 1; +} + +void nhrp_cache_set_used(struct nhrp_cache *c, int used) +{ + c->used = used; + if (c->used) + notifier_call(&c->notifier_list, NOTIFY_CACHE_USED); +} + +struct nhrp_cache_iterator_ctx { + void (*cb)(struct nhrp_cache *, void *); + void *ctx; +}; + +struct nhrp_cache_config_iterator_ctx { + void (*cb)(struct nhrp_cache_config *, void *); + void *ctx; +}; + +static void nhrp_cache_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_cache_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +static void nhrp_cache_config_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_cache_config_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +void nhrp_cache_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache *, void *), void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + + if (nifp->cache_hash) + hash_iterate(nifp->cache_hash, nhrp_cache_iterator, &ic); +} + +void nhrp_cache_config_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache_config *, void *), void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_cache_config_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + + if (nifp->cache_config_hash) + hash_iterate(nifp->cache_config_hash, nhrp_cache_config_iterator, &ic); +} + +void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *n, + notifier_fn_t fn) +{ + notifier_add(n, &c->notifier_list, fn); +} + +void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *n) +{ + notifier_del(n, &c->notifier_list); +} diff --git a/nhrpd/nhrp_errors.c b/nhrpd/nhrp_errors.c new file mode 100644 index 0000000..12ad7c6 --- /dev/null +++ b/nhrpd/nhrp_errors.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NHRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "nhrp_errors.h" + +/* clang-format off */ +static struct log_ref ferr_nhrp_err[] = { + { + .code = EC_NHRP_SWAN, + .title = "NHRP Strong Swan Error", + .description = "NHRP has detected a error with the Strongswan code", + .suggestion = "Ensure that StrongSwan is configured correctly. Restart StrongSwan and FRR" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void nhrp_error_init(void) +{ + log_ref_add(ferr_nhrp_err); +} diff --git a/nhrpd/nhrp_errors.h b/nhrpd/nhrp_errors.h new file mode 100644 index 0000000..d7867a4 --- /dev/null +++ b/nhrpd/nhrp_errors.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NHRP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __NHRP_ERRORS_H__ +#define __NHRP_ERRORS_H__ + +#include "lib/ferr.h" + +enum nhrp_log_refs { + EC_NHRP_SWAN = NHRP_FERR_START, +}; + +extern void nhrp_error_init(void); + +#endif diff --git a/nhrpd/nhrp_event.c b/nhrpd/nhrp_event.c new file mode 100644 index 0000000..ba31858 --- /dev/null +++ b/nhrpd/nhrp_event.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP event manager + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "frrevent.h" +#include "zbuf.h" +#include "log.h" +#include "nhrpd.h" + +const char *nhrp_event_socket_path; +struct nhrp_reqid_pool nhrp_event_reqid; + +struct event_manager { + struct event *t_reconnect, *t_read, *t_write; + struct zbuf ibuf; + struct zbuf_queue obuf; + int fd; + uint8_t ibuf_data[4 * 1024]; +}; + +static void evmgr_reconnect(struct event *t); + +static void evmgr_connection_error(struct event_manager *evmgr) +{ + EVENT_OFF(evmgr->t_read); + EVENT_OFF(evmgr->t_write); + zbuf_reset(&evmgr->ibuf); + zbufq_reset(&evmgr->obuf); + + if (evmgr->fd >= 0) + close(evmgr->fd); + evmgr->fd = -1; + if (nhrp_event_socket_path) + event_add_timer_msec(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); +} + +static void evmgr_recv_message(struct event_manager *evmgr, struct zbuf *zb) +{ + struct zbuf zl; + uint32_t eventid = 0; + size_t len; + char buf[256], result[64] = ""; + + while (zbuf_may_pull_until(zb, "\n", &zl)) { + len = zbuf_used(&zl) - 1; + if (len >= sizeof(buf) - 1) + continue; + memcpy(buf, zbuf_pulln(&zl, len), len); + buf[len] = 0; + + debugf(NHRP_DEBUG_EVENT, "evmgr: msg: %s", buf); + if (sscanf(buf, "eventid=%" SCNu32, &eventid) == 1) + continue; + if (sscanf(buf, "result=%63s", result) == 1) + continue; + } + debugf(NHRP_DEBUG_EVENT, "evmgr: received: eventid=%d result=%s", + eventid, result); + if (eventid && result[0]) { + struct nhrp_reqid *r = + nhrp_reqid_lookup(&nhrp_event_reqid, eventid); + if (r) + r->cb(r, result); + } +} + +static void evmgr_read(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + struct zbuf *ibuf = &evmgr->ibuf; + struct zbuf msg; + + if (zbuf_read(ibuf, evmgr->fd, (size_t)-1) < 0) { + evmgr_connection_error(evmgr); + return; + } + + /* Process all messages in buffer */ + while (zbuf_may_pull_until(ibuf, "\n\n", &msg)) + evmgr_recv_message(evmgr, &msg); + + event_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); +} + +static void evmgr_write(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + int r; + + r = zbufq_write(&evmgr->obuf, evmgr->fd); + if (r > 0) { + event_add_write(master, evmgr_write, evmgr, evmgr->fd, + &evmgr->t_write); + } else if (r < 0) { + evmgr_connection_error(evmgr); + } +} + +static void evmgr_hexdump(struct zbuf *zb, const uint8_t *val, size_t vallen) +{ + static const char xd[] = "0123456789abcdef"; + size_t i; + char *ptr; + + ptr = zbuf_pushn(zb, 2 * vallen); + if (!ptr) + return; + + for (i = 0; i < vallen; i++) { + uint8_t b = val[i]; + *(ptr++) = xd[b >> 4]; + *(ptr++) = xd[b & 0xf]; + } +} + +static void evmgr_put(struct zbuf *zb, const char *fmt, ...) +{ + const char *pos, *nxt, *str; + const uint8_t *bin; + const union sockunion *su; + int len; + va_list va; + + va_start(va, fmt); + for (pos = fmt; (nxt = strchr(pos, '%')) != NULL; pos = nxt + 2) { + zbuf_put(zb, pos, nxt - pos); + switch (nxt[1]) { + case '%': + zbuf_put8(zb, '%'); + break; + case 'u': + zb->tail += + snprintf((char *)zb->tail, zbuf_tailroom(zb), + "%u", va_arg(va, uint32_t)); + break; + case 's': + str = va_arg(va, const char *); + zbuf_put(zb, str, strlen(str)); + break; + case 'U': + su = va_arg(va, const union sockunion *); + if (sockunion2str(su, (char *)zb->tail, + zbuf_tailroom(zb))) + zb->tail += strlen((char *)zb->tail); + else + zbuf_set_werror(zb); + break; + case 'H': + bin = va_arg(va, const uint8_t *); + len = va_arg(va, int); + evmgr_hexdump(zb, bin, len); + break; + } + } + va_end(va); + zbuf_put(zb, pos, strlen(pos)); +} + +static void evmgr_submit(struct event_manager *evmgr, struct zbuf *obuf) +{ + if (obuf->error) { + zbuf_free(obuf); + return; + } + zbuf_put(obuf, "\n", 1); + zbufq_queue(&evmgr->obuf, obuf); + if (evmgr->fd >= 0) + event_add_write(master, evmgr_write, evmgr, evmgr->fd, + &evmgr->t_write); +} + +static void evmgr_reconnect(struct event *t) +{ + struct event_manager *evmgr = EVENT_ARG(t); + int fd; + + if (evmgr->fd >= 0 || !nhrp_event_socket_path) + return; + + fd = sock_open_unix(nhrp_event_socket_path); + if (fd < 0) { + zlog_warn("%s: failure connecting nhrp-event socket: %s", + __func__, strerror(errno)); + zbufq_reset(&evmgr->obuf); + event_add_timer(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); + return; + } + + zlog_info("Connected to Event Manager"); + evmgr->fd = fd; + event_add_read(master, evmgr_read, evmgr, evmgr->fd, &evmgr->t_read); +} + +static struct event_manager evmgr_connection; + +void evmgr_init(void) +{ + struct event_manager *evmgr = &evmgr_connection; + + evmgr->fd = -1; + zbuf_init(&evmgr->ibuf, evmgr->ibuf_data, sizeof(evmgr->ibuf_data), 0); + zbufq_init(&evmgr->obuf); + event_add_timer_msec(master, evmgr_reconnect, evmgr, 10, + &evmgr->t_reconnect); +} + +void evmgr_set_socket(const char *socket) +{ + if (nhrp_event_socket_path) { + free((char *)nhrp_event_socket_path); + nhrp_event_socket_path = NULL; + } + if (socket) + nhrp_event_socket_path = strdup(socket); + evmgr_connection_error(&evmgr_connection); +} + +void evmgr_terminate(void) +{ +} + +void evmgr_notify(const char *name, struct nhrp_cache *c, + void (*cb)(struct nhrp_reqid *, void *)) +{ + struct event_manager *evmgr = &evmgr_connection; + struct nhrp_vc *vc; + struct nhrp_interface *nifp = c->ifp->info; + struct zbuf *zb; + afi_t afi = family2afi(sockunion_family(&c->remote_addr)); + + if (!nhrp_event_socket_path) { + cb(&c->eventid, (void *)"accept"); + return; + } + + debugf(NHRP_DEBUG_EVENT, "evmgr: sending event %s", name); + + vc = c->new.peer ? c->new.peer->vc : NULL; + zb = zbuf_alloc( + 1024 + (vc ? (vc->local.certlen + vc->remote.certlen) * 2 : 0)); + + if (cb) { + nhrp_reqid_free(&nhrp_event_reqid, &c->eventid); + evmgr_put(zb, "eventid=%u\n", + nhrp_reqid_alloc(&nhrp_event_reqid, &c->eventid, cb)); + } + + evmgr_put(zb, + "event=%s\n" + "type=%s\n" + "old_type=%s\n" + "num_nhs=%u\n" + "interface=%s\n" + "local_addr=%U\n", + name, nhrp_cache_type_str[c->new.type], + nhrp_cache_type_str[c->cur.type], + (unsigned int)nhrp_cache_counts[NHRP_CACHE_NHS], c->ifp->name, + &nifp->afi[afi].addr); + + if (vc) { + evmgr_put(zb, + "vc_initiated=%s\n" + "local_nbma=%U\n" + "local_cert=%H\n" + "remote_addr=%U\n" + "remote_nbma=%U\n" + "remote_cert=%H\n", + c->new.peer->requested ? "yes" : "no", + &vc->local.nbma, vc->local.cert, vc->local.certlen, + &c->remote_addr, &vc->remote.nbma, vc->remote.cert, + vc->remote.certlen); + } + + evmgr_submit(evmgr, zb); +} diff --git a/nhrpd/nhrp_interface.c b/nhrpd/nhrp_interface.c new file mode 100644 index 0000000..7d0ab97 --- /dev/null +++ b/nhrpd/nhrp_interface.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP interface + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "zebra.h" +#include "linklist.h" +#include "memory.h" +#include "frrevent.h" + +#include "nhrpd.h" +#include "os.h" +#include "hash.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF, "NHRP interface"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_IF_GRE, "NHRP GRE interface"); + +struct hash *nhrp_gre_list; + +static void nhrp_interface_update_cache_config(struct interface *ifp, + bool available, + uint8_t family); + +static unsigned int nhrp_gre_info_key(const void *data) +{ + const struct nhrp_gre_info *r = data; + + return r->ifindex; +} + +static bool nhrp_gre_info_cmp(const void *data, const void *key) +{ + const struct nhrp_gre_info *a = data, *b = key; + + if (a->ifindex == b->ifindex) + return true; + return false; +} + +static void *nhrp_interface_gre_alloc(void *data) +{ + struct nhrp_gre_info *a; + struct nhrp_gre_info *b = data; + + a = XMALLOC(MTYPE_NHRP_IF_GRE, sizeof(struct nhrp_gre_info)); + memcpy(a, b, sizeof(struct nhrp_gre_info)); + return a; +} + +struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p) +{ + struct nhrp_gre_info *a; + + a = (struct nhrp_gre_info *)hash_get(nhrp_gre_list, p, + nhrp_interface_gre_alloc); + return a; +} + +static int nhrp_if_new_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp; + afi_t afi; + + nifp = XCALLOC(MTYPE_NHRP_IF, sizeof(struct nhrp_interface)); + + ifp->info = nifp; + nifp->ifp = ifp; + + notifier_init(&nifp->notifier_list); + for (afi = 0; afi < AFI_MAX; afi++) { + struct nhrp_afi_data *ad = &nifp->afi[afi]; + ad->holdtime = NHRPD_DEFAULT_HOLDTIME; + nhrp_nhslist_init(&ad->nhslist_head); + nhrp_mcastlist_init(&ad->mcastlist_head); + } + + return 0; +} + +static int nhrp_if_delete_hook(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_IF, "Deleted interface (%s)", ifp->name); + + nhrp_cache_interface_del(ifp); + nhrp_nhs_interface_del(ifp); + nhrp_multicast_interface_del(ifp); + nhrp_peer_interface_del(ifp); + + if (nifp->ipsec_profile) + free(nifp->ipsec_profile); + if (nifp->ipsec_fallback_profile) + free(nifp->ipsec_fallback_profile); + if (nifp->source) + free(nifp->source); + + XFREE(MTYPE_NHRP_IF, ifp->info); + return 0; +} + +void nhrp_interface_init(void) +{ + hook_register_prio(if_add, 0, nhrp_if_new_hook); + hook_register_prio(if_del, 0, nhrp_if_delete_hook); + + nhrp_gre_list = hash_create(nhrp_gre_info_key, nhrp_gre_info_cmp, + "NHRP GRE list Hash"); +} + +void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + unsigned short new_mtu; + + if (if_ad->configured_mtu < 0) + new_mtu = nifp->nbmaifp ? nifp->nbmaifp->mtu : 0; + else + new_mtu = if_ad->configured_mtu; + if (new_mtu >= 1500) + new_mtu = 0; + + if (new_mtu != if_ad->mtu) { + debugf(NHRP_DEBUG_IF, "%s: MTU changed to %d", ifp->name, + new_mtu); + if_ad->mtu = new_mtu; + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_MTU_CHANGED); + } +} + +static void nhrp_interface_update_source(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + if (!nifp->source || !nifp->nbmaifp + || ((ifindex_t)nifp->link_idx == nifp->nbmaifp->ifindex + && (nifp->link_vrf_id == nifp->nbmaifp->vrf->vrf_id))) + return; + + nifp->link_idx = nifp->nbmaifp->ifindex; + nifp->link_vrf_id = nifp->nbmaifp->vrf->vrf_id; + debugf(NHRP_DEBUG_IF, "%s: bound device index changed to %d, vr %u", + ifp->name, nifp->link_idx, nifp->link_vrf_id); + nhrp_send_zebra_gre_source_set(ifp, nifp->link_idx, nifp->link_vrf_id); +} + +static void nhrp_interface_interface_notifier(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_interface *nifp = + container_of(n, struct nhrp_interface, nbmanifp_notifier); + struct interface *nbmaifp = nifp->nbmaifp; + struct nhrp_interface *nbmanifp = nbmaifp->info; + + switch (cmd) { + case NOTIFY_INTERFACE_CHANGED: + nhrp_interface_update_nbma(nifp->ifp, NULL); + break; + case NOTIFY_INTERFACE_ADDRESS_CHANGED: + nifp->nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update(nifp->ifp); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + debugf(NHRP_DEBUG_IF, "%s: NBMA change: address %pSU", + nifp->ifp->name, &nifp->nbma); + break; + } +} + +void nhrp_interface_update_nbma(struct interface *ifp, + struct nhrp_gre_info *gre_info) +{ + struct nhrp_interface *nifp = ifp->info, *nbmanifp = NULL; + struct interface *nbmaifp = NULL; + union sockunion nbma; + struct in_addr saddr = {0}; + + sockunion_family(&nbma) = AF_UNSPEC; + + if (nifp->source) + nbmaifp = if_lookup_by_name(nifp->source, nifp->link_vrf_id); + + if (ifp->ll_type != ZEBRA_LLT_IPGRE) + debugf(NHRP_DEBUG_IF, "%s: Ignoring non GRE interface type %u", + __func__, ifp->ll_type); + else { + if (!gre_info) { + nhrp_send_zebra_gre_request(ifp); + return; + } + nifp->i_grekey = gre_info->ikey; + nifp->o_grekey = gre_info->okey; + nifp->link_idx = gre_info->ifindex_link; + nifp->link_vrf_id = gre_info->vrfid_link; + saddr.s_addr = gre_info->vtep_ip.s_addr; + + debugf(NHRP_DEBUG_IF, "%s: GRE: %x %x %x", ifp->name, + nifp->i_grekey, nifp->link_idx, saddr.s_addr); + if (saddr.s_addr) + sockunion_set(&nbma, AF_INET, + (uint8_t *)&saddr.s_addr, + sizeof(saddr.s_addr)); + else if (!nbmaifp && nifp->link_idx != IFINDEX_INTERNAL) + nbmaifp = + if_lookup_by_index(nifp->link_idx, + nifp->link_vrf_id); + } + + if (nbmaifp) + nbmanifp = nbmaifp->info; + + if (nbmaifp != nifp->nbmaifp) { + if (nifp->nbmaifp) { + struct nhrp_interface *prev_nifp = nifp->nbmaifp->info; + + notifier_del(&nifp->nbmanifp_notifier, + &prev_nifp->notifier_list); + } + nifp->nbmaifp = nbmaifp; + if (nbmaifp) { + notifier_add(&nifp->nbmanifp_notifier, + &nbmanifp->notifier_list, + nhrp_interface_interface_notifier); + debugf(NHRP_DEBUG_IF, "%s: bound to %s", ifp->name, + nbmaifp->name); + } + } + + if (nbmaifp) { + if (sockunion_family(&nbma) == AF_UNSPEC) + nbma = nbmanifp->afi[AFI_IP].addr; + nhrp_interface_update_mtu(ifp, AFI_IP); + nhrp_interface_update_source(ifp); + } + + if (!sockunion_same(&nbma, &nifp->nbma)) { + nifp->nbma = nbma; + nhrp_interface_update(nifp->ifp); + debugf(NHRP_DEBUG_IF, "%s: NBMA address changed", ifp->name); + notifier_call(&nifp->notifier_list, + NOTIFY_INTERFACE_NBMA_CHANGED); + } + + nhrp_interface_update(ifp); +} + +static void nhrp_interface_update_address(struct interface *ifp, afi_t afi, + int force) +{ + const int family = afi2family(afi); + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[afi]; + struct nhrp_cache *nc; + struct connected *c, *best; + union sockunion addr; + char buf[PREFIX_STRLEN]; + + /* Select new best match preferring primary address */ + best = NULL; + frr_each (if_connected, ifp->connected, c) { + if (PREFIX_FAMILY(c->address) != family) + continue; + if (best == NULL) { + best = c; + continue; + } + if ((best->flags & ZEBRA_IFA_SECONDARY) + && !(c->flags & ZEBRA_IFA_SECONDARY)) { + best = c; + continue; + } + if (!(best->flags & ZEBRA_IFA_SECONDARY) + && (c->flags & ZEBRA_IFA_SECONDARY)) + continue; + if (best->address->prefixlen > c->address->prefixlen) { + best = c; + continue; + } + if (best->address->prefixlen < c->address->prefixlen) + continue; + } + + /* On NHRP interfaces a host prefix is required */ + if (best && if_ad->configured + && best->address->prefixlen != 8 * prefix_blen(best->address)) { + zlog_notice("%s: %pFX is not a host prefix", ifp->name, + best->address); + best = NULL; + } + + /* Update address if it changed */ + if (best) + prefix2sockunion(best->address, &addr); + else + memset(&addr, 0, sizeof(addr)); + + if (!force && sockunion_same(&if_ad->addr, &addr)) + return; + + if (sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &if_ad->addr, 0); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, -1, + NULL, 0, NULL, NULL); + } + + debugf(NHRP_DEBUG_KERNEL, "%s: IPv%d address changed to %s", ifp->name, + afi == AFI_IP ? 4 : 6, + best ? prefix2str(best->address, buf, sizeof(buf)) : "(none)"); + if_ad->addr = addr; + + if (if_ad->configured && sockunion_family(&if_ad->addr) != AF_UNSPEC) { + nc = nhrp_cache_get(ifp, &addr, 1); + if (nc) + nhrp_cache_update_binding(nc, NHRP_CACHE_LOCAL, 0, NULL, + 0, NULL, NULL); + } + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_ADDRESS_CHANGED); +} + +void nhrp_interface_update(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad; + afi_t afi; + int enabled = 0; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_CHANGED); + + for (afi = 0; afi < AFI_MAX; afi++) { + if_ad = &nifp->afi[afi]; + + if (sockunion_family(&nifp->nbma) == AF_UNSPEC + || ifp->ifindex == IFINDEX_INTERNAL || !if_is_up(ifp) + || !if_ad->network_id) { + if (if_ad->configured) { + if_ad->configured = 0; + nhrp_interface_update_address(ifp, afi, 1); + } + continue; + } + + if (!if_ad->configured) { + os_configure_dmvpn(ifp->ifindex, ifp->name, + afi2family(afi)); + nhrp_interface_update_arp(ifp, true); + nhrp_send_zebra_configure_arp(ifp, afi2family(afi)); + if_ad->configured = 1; + nhrp_interface_update_address(ifp, afi, 1); + } + + enabled = 1; + } + + if (enabled != nifp->enabled) { + nifp->enabled = enabled; + notifier_call(&nifp->notifier_list, + enabled ? NOTIFY_INTERFACE_UP + : NOTIFY_INTERFACE_DOWN); + } +} + +int nhrp_ifp_create(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-add: %s, ifindex: %u, hw_type: %d %s", + ifp->name, ifp->ifindex, ifp->ll_type, + if_link_type_str(ifp->ll_type)); + + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_destroy(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-delete: %s", ifp->name); + + nhrp_interface_update_cache_config(ifp, false, AF_INET); + nhrp_interface_update_cache_config(ifp, false, AF_INET6); + nhrp_interface_update(ifp); + + return 0; +} + +struct map_ctx { + int family; + bool enabled; +}; + +static void interface_config_update_nhrp_map(struct nhrp_cache_config *cc, + void *data) +{ + struct map_ctx *ctx = data; + struct interface *ifp = cc->ifp; + struct nhrp_cache *c; + union sockunion nbma_addr; + + if (sockunion_family(&cc->remote_addr) != ctx->family) + return; + + /* gre layer not ready */ + if (ifp->vrf->vrf_id == VRF_UNKNOWN) + return; + + c = nhrp_cache_get(ifp, &cc->remote_addr, ctx->enabled ? 1 : 0); + if (!c && !ctx->enabled) + return; + + /* suppress */ + if (!ctx->enabled) { + if (c && c->map) { + nhrp_cache_update_binding( + c, c->cur.type, -1, + nhrp_peer_get(ifp, &nbma_addr), 0, NULL, NULL); + } + return; + } + + /* Newly created */ + assert(c != NULL); + + c->map = 1; + if (cc->type == NHRP_CACHE_LOCAL) + nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, + NULL, NULL); + else { + nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, + nhrp_peer_get(ifp, &cc->nbma), 0, + NULL, NULL); + } +} + +static void nhrp_interface_update_cache_config(struct interface *ifp, bool available, uint8_t family) +{ + struct map_ctx mapctx; + + mapctx = (struct map_ctx){ + .family = family, + .enabled = available + }; + nhrp_cache_config_foreach(ifp, interface_config_update_nhrp_map, + &mapctx); + +} + +int nhrp_ifp_up(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-up: %s", ifp->name); + nhrp_interface_update_nbma(ifp, NULL); + + return 0; +} + +int nhrp_ifp_down(struct interface *ifp) +{ + debugf(NHRP_DEBUG_IF, "if-down: %s", ifp->name); + nhrp_interface_update(ifp); + + return 0; +} + +int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-add: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + nhrp_interface_update_cache_config(ifc->ifp, true, PREFIX_FAMILY(ifc->address)); + return 0; +} + +int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + + ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (ifc == NULL) + return 0; + + debugf(NHRP_DEBUG_IF, "if-addr-del: %s: %pFX", ifc->ifp->name, + ifc->address); + + nhrp_interface_update_address( + ifc->ifp, family2afi(PREFIX_FAMILY(ifc->address)), 0); + connected_free(&ifc); + + return 0; +} + +void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, + notifier_fn_t fn) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_add(n, &nifp->notifier_list, fn); +} + +void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n) +{ + struct nhrp_interface *nifp = ifp->info; + + notifier_del(n, &nifp->notifier_list); +} + +void nhrp_interface_set_protection(struct interface *ifp, const char *profile, + const char *fallback_profile) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->ipsec_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_profile); + nhrp_vc_reset(); + free(nifp->ipsec_profile); + } + nifp->ipsec_profile = profile ? strdup(profile) : NULL; + + if (nifp->ipsec_fallback_profile) { + vici_terminate_vc_by_profile_name(nifp->ipsec_fallback_profile); + nhrp_vc_reset(); + free(nifp->ipsec_fallback_profile); + } + nifp->ipsec_fallback_profile = + fallback_profile ? strdup(fallback_profile) : NULL; + + notifier_call(&nifp->notifier_list, NOTIFY_INTERFACE_IPSEC_CHANGED); +} + +void nhrp_interface_set_source(struct interface *ifp, const char *ifname) +{ + struct nhrp_interface *nifp = ifp->info; + + if (nifp->source) + free(nifp->source); + nifp->source = ifname ? strdup(ifname) : NULL; + + nhrp_interface_update_nbma(ifp, NULL); +} diff --git a/nhrpd/nhrp_main.c b/nhrpd/nhrp_main.c new file mode 100644 index 0000000..adb8be3 --- /dev/null +++ b/nhrpd/nhrp_main.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon main functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "zebra.h" +#include "privs.h" +#include "getopt.h" +#include "frrevent.h" +#include "sigevent.h" +#include "lib/version.h" +#include "log.h" +#include "memory.h" +#include "command.h" +#include "libfrr.h" +#include "filter.h" + +#include "nhrpd.h" +#include "nhrp_errors.h" + +DEFINE_MGROUP(NHRPD, "NHRP"); + +unsigned int debug_flags = 0; + +struct event_loop *master; +struct timeval current_time; + +/* nhrpd options. */ +struct option longopts[] = {{0}}; + +/* nhrpd privileges */ +static zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_NET_ADMIN, + ZCAP_DAC_OVERRIDE, /* for now needed to write to + /proc/sys/net/ipv4//send_redirect */ +}; + +struct zebra_privs_t nhrpd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0 +}; + + +static void parse_arguments(int argc, char **argv) +{ + int opt; + + while (1) { + opt = frr_getopt(argc, argv, 0); + if (opt < 0) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } +} + +static void nhrp_sigusr1(void) +{ + zlog_rotate(); +} + +static void nhrp_request_stop(void) +{ + debugf(NHRP_DEBUG_COMMON, "Exiting..."); + frr_early_fini(); + + nhrp_shortcut_terminate(); + nhrp_nhs_terminate(); + nhrp_zebra_terminate(); + vici_terminate(); + evmgr_terminate(); + vrf_terminate(); + nhrp_vc_terminate(); + + debugf(NHRP_DEBUG_COMMON, "Done."); + + resolver_terminate(); + frr_fini(); + + exit(0); +} + +static struct frr_signal_t sighandlers[] = { + { + .signal = SIGUSR1, + .handler = &nhrp_sigusr1, + }, + { + .signal = SIGINT, + .handler = &nhrp_request_stop, + }, + { + .signal = SIGTERM, + .handler = &nhrp_request_stop, + }, +}; + +static const struct frr_yang_module_info *const nhrpd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(nhrpd, NHRP, + .vty_port = NHRP_VTY_PORT, + .proghelp = "Implementation of the NHRP routing protocol.", + + .signals = sighandlers, + .n_signals = array_size(sighandlers), + + .privs = &nhrpd_privs, + + .yang_modules = nhrpd_yang_modules, + .n_yang_modules = array_size(nhrpd_yang_modules), +); +/* clang-format on */ + +int main(int argc, char **argv) +{ + frr_preinit(&nhrpd_di, argc, argv); + frr_opt_add("", longopts, ""); + + parse_arguments(argc, argv); + + /* Library inits. */ + master = frr_init(); + nhrp_error_init(); + vrf_init(NULL, NULL, NULL, NULL); + nhrp_interface_init(); + resolver_init(master); + + /* + * Run with elevated capabilities, as for all netlink activity + * we need privileges anyway. + * The assert is for clang SA code where it does + * not see the change function being set in lib + */ + assert(nhrpd_privs.change); + nhrpd_privs.change(ZPRIVS_RAISE); + + evmgr_init(); + nhrp_vc_init(); + nhrp_packet_init(); + vici_init(); + hook_register_prio(if_real, 0, nhrp_ifp_create); + hook_register_prio(if_up, 0, nhrp_ifp_up); + hook_register_prio(if_down, 0, nhrp_ifp_down); + hook_register_prio(if_unreal, 0, nhrp_ifp_destroy); + nhrp_zebra_init(); + nhrp_shortcut_init(); + + nhrp_config_init(); + + frr_config_fork(); + frr_run(master); + return 0; +} diff --git a/nhrpd/nhrp_multicast.c b/nhrpd/nhrp_multicast.c new file mode 100644 index 0000000..aead982 --- /dev/null +++ b/nhrpd/nhrp_multicast.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP Multicast Support + * Copyright (c) 2020-2021 4RF Limited + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef GNU_LINUX +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "frrevent.h" +#include "nhrpd.h" +#include "netlink.h" +#include "znl.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_MULTICAST, "NHRP Multicast"); + +int netlink_mcast_nflog_group; +static int netlink_mcast_log_fd = -1; +static struct event *netlink_mcast_log_thread; + +struct mcast_ctx { + struct interface *ifp; + struct zbuf *pkt; +}; + +static void nhrp_multicast_send(struct nhrp_peer *p, struct zbuf *zb) +{ + size_t addrlen; + int ret; + + addrlen = sockunion_get_addrlen(&p->vc->remote.nbma); + ret = os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex, + sockunion_get_addr(&p->vc->remote.nbma), addrlen, + addrlen == 4 ? ETH_P_IP : ETH_P_IPV6); + + debugf(NHRP_DEBUG_COMMON, + "Multicast Packet: %pSU -> %pSU, ret = %d, size = %zu, addrlen = %zu", + &p->vc->local.nbma, &p->vc->remote.nbma, ret, zbuf_used(zb), + addrlen); +} + +static void nhrp_multicast_forward_nbma(union sockunion *nbma_addr, + struct interface *ifp, struct zbuf *pkt) +{ + struct nhrp_peer *p = nhrp_peer_get(ifp, nbma_addr); + + if (p && p->online) { + /* Send packet */ + nhrp_multicast_send(p, pkt); + } + nhrp_peer_unref(p); +} + +static void nhrp_multicast_forward_cache(struct nhrp_cache *c, void *pctx) +{ + struct mcast_ctx *ctx = (struct mcast_ctx *)pctx; + + if (c->cur.type == NHRP_CACHE_DYNAMIC && c->cur.peer) + nhrp_multicast_forward_nbma(&c->cur.peer->vc->remote.nbma, + ctx->ifp, ctx->pkt); +} + +static void nhrp_multicast_forward(struct nhrp_multicast *mcast, void *pctx) +{ + struct mcast_ctx *ctx = (struct mcast_ctx *)pctx; + struct nhrp_interface *nifp = ctx->ifp->info; + + if (!nifp->enabled) + return; + + /* dynamic */ + if (sockunion_family(&mcast->nbma_addr) == AF_UNSPEC) { + nhrp_cache_foreach(ctx->ifp, nhrp_multicast_forward_cache, + pctx); + return; + } + + /* Fixed IP Address */ + nhrp_multicast_forward_nbma(&mcast->nbma_addr, ctx->ifp, ctx->pkt); +} + +static void netlink_mcast_log_handler(struct nlmsghdr *msg, struct zbuf *zb) +{ + struct nfgenmsg *nf; + struct rtattr *rta; + struct zbuf rtapl; + uint32_t *out_ndx = NULL; + afi_t afi; + struct mcast_ctx ctx; + + nf = znl_pull(zb, sizeof(*nf)); + if (!nf) + return; + + ctx.pkt = NULL; + while ((rta = znl_rta_pull(zb, &rtapl)) != NULL) { + switch (rta->rta_type) { + case NFULA_IFINDEX_OUTDEV: + out_ndx = znl_pull(&rtapl, sizeof(*out_ndx)); + break; + case NFULA_PAYLOAD: + ctx.pkt = &rtapl; + break; + /* NFULA_HWHDR exists and is supposed to contain source + * hardware address. However, for ip_gre it seems to be + * the nexthop destination address if the packet matches + * route. + */ + } + } + + if (!out_ndx || !ctx.pkt) + return; + + ctx.ifp = if_lookup_by_index(htonl(*out_ndx), VRF_DEFAULT); + if (!ctx.ifp) + return; + + debugf(NHRP_DEBUG_COMMON, + "Intercepted multicast packet leaving %s len %zu", + ctx.ifp->name, zbuf_used(ctx.pkt)); + + for (afi = 0; afi < AFI_MAX; afi++) { + nhrp_multicast_foreach(ctx.ifp, afi, nhrp_multicast_forward, + (void *)&ctx); + } +} + +static void netlink_mcast_log_recv(struct event *t) +{ + uint8_t buf[65535]; /* Max OSPF Packet size */ + int fd = EVENT_FD(t); + struct zbuf payload, zb; + struct nlmsghdr *n; + + + zbuf_init(&zb, buf, sizeof(buf), 0); + while (zbuf_recv(&zb, fd) > 0) { + while ((n = znl_nlmsg_pull(&zb, &payload)) != NULL) { + debugf(NHRP_DEBUG_COMMON, + "Netlink-mcast-log: Received msg_type %u, msg_flags %u", + n->nlmsg_type, n->nlmsg_flags); + switch (n->nlmsg_type) { + case (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_PACKET: + netlink_mcast_log_handler(n, &payload); + break; + } + } + } + + event_add_read(master, netlink_mcast_log_recv, 0, netlink_mcast_log_fd, + &netlink_mcast_log_thread); +} + +static void netlink_mcast_log_register(int fd, int group) +{ + struct nlmsghdr *n; + struct nfgenmsg *nf; + struct nfulnl_msg_config_cmd cmd; + struct zbuf *zb = zbuf_alloc(512); + + n = znl_nlmsg_push(zb, (NFNL_SUBSYS_ULOG << 8) | NFULNL_MSG_CONFIG, + NLM_F_REQUEST | NLM_F_ACK); + nf = znl_push(zb, sizeof(*nf)); + *nf = (struct nfgenmsg){ + .nfgen_family = AF_UNSPEC, + .version = NFNETLINK_V0, + .res_id = htons(group), + }; + cmd.command = NFULNL_CFG_CMD_BIND; + znl_rta_push(zb, NFULA_CFG_CMD, &cmd, sizeof(cmd)); + znl_nlmsg_complete(zb, n); + + zbuf_send(zb, fd); + zbuf_free(zb); +} + +void netlink_mcast_set_nflog_group(int nlgroup) +{ + if (netlink_mcast_log_fd >= 0) { + EVENT_OFF(netlink_mcast_log_thread); + close(netlink_mcast_log_fd); + netlink_mcast_log_fd = -1; + debugf(NHRP_DEBUG_COMMON, "De-register nflog group"); + } + netlink_mcast_nflog_group = nlgroup; + if (nlgroup) { + netlink_mcast_log_fd = znl_open(NETLINK_NETFILTER, 0); + if (netlink_mcast_log_fd < 0) + return; + + netlink_mcast_log_register(netlink_mcast_log_fd, nlgroup); + event_add_read(master, netlink_mcast_log_recv, 0, + netlink_mcast_log_fd, &netlink_mcast_log_thread); + debugf(NHRP_DEBUG_COMMON, "Register nflog group: %d", + netlink_mcast_nflog_group); + } +} + +static int nhrp_multicast_free(struct interface *ifp, + struct nhrp_multicast *mcast) +{ + struct nhrp_interface *nifp = ifp->info; + + nhrp_mcastlist_del(&nifp->afi[mcast->afi].mcastlist_head, mcast); + XFREE(MTYPE_NHRP_MULTICAST, mcast); + return 0; +} + +int nhrp_multicast_add(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + if (sockunion_same(&mcast->nbma_addr, nbma_addr)) + return NHRP_ERR_ENTRY_EXISTS; + } + + mcast = XMALLOC(MTYPE_NHRP_MULTICAST, sizeof(struct nhrp_multicast)); + + *mcast = (struct nhrp_multicast){ + .afi = afi, .ifp = ifp, .nbma_addr = *nbma_addr, + }; + nhrp_mcastlist_add_tail(&nifp->afi[afi].mcastlist_head, mcast); + + debugf(NHRP_DEBUG_COMMON, "Adding multicast entry (%pSU)", nbma_addr); + + return NHRP_OK; +} + +int nhrp_multicast_del(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + if (!sockunion_same(&mcast->nbma_addr, nbma_addr)) + continue; + + debugf(NHRP_DEBUG_COMMON, "Deleting multicast entry (%pSU)", + nbma_addr); + + nhrp_multicast_free(ifp, mcast); + + return NHRP_OK; + } + + return NHRP_ERR_ENTRY_NOT_FOUND; +} + +void nhrp_multicast_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + afi_t afi; + + for (afi = 0; afi < AFI_MAX; afi++) { + debugf(NHRP_DEBUG_COMMON, "Cleaning up multicast entries (%zu)", + nhrp_mcastlist_count(&nifp->afi[afi].mcastlist_head)); + + frr_each_safe (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, + mcast) { + nhrp_multicast_free(ifp, mcast); + } + } +} + +void nhrp_multicast_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_multicast *, void *), + void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_multicast *mcast; + + frr_each (nhrp_mcastlist, &nifp->afi[afi].mcastlist_head, mcast) { + cb(mcast, ctx); + } +} diff --git a/nhrpd/nhrp_nhs.c b/nhrpd/nhrp_nhs.c new file mode 100644 index 0000000..f779f93 --- /dev/null +++ b/nhrpd/nhrp_nhs.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP NHC nexthop server functions (registration) + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "zbuf.h" +#include "memory.h" +#include "frrevent.h" +#include "nhrpd.h" +#include "nhrp_protocol.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_NHS, "NHRP next hop server"); +DEFINE_MTYPE_STATIC(NHRPD, NHRP_REGISTRATION, "NHRP registration entries"); + +static void nhrp_nhs_resolve(struct event *t); +static void nhrp_reg_send_req(struct event *t); + +static void nhrp_reg_reply(struct nhrp_reqid *reqid, void *arg) +{ + struct nhrp_packet_parser *p = arg; + struct nhrp_registration *r = + container_of(reqid, struct nhrp_registration, reqid); + struct nhrp_nhs *nhs = r->nhs; + struct interface *ifp = nhs->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + struct nhrp_cache *c; + struct zbuf extpl; + union sockunion cie_nbma, cie_nbma_nhs, cie_proto, cie_proto_nhs, + *proto; + int ok = 0, holdtime; + unsigned short mtu = 0; + + nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); + + if (p->hdr->type != NHRP_PACKET_REGISTRATION_REPLY) { + debugf(NHRP_DEBUG_COMMON, "NHS: Registration failed"); + return; + } + + debugf(NHRP_DEBUG_COMMON, "NHS: Reg.reply received"); + + ok = 1; + while ((cie = nhrp_cie_pull(&p->payload, p->hdr, &cie_nbma, &cie_proto)) + != NULL) { + proto = sockunion_family(&cie_proto) != AF_UNSPEC + ? &cie_proto + : &p->src_proto; + debugf(NHRP_DEBUG_COMMON, "NHS: CIE registration: %pSU: %d", + proto, cie->code); + if (!((cie->code == NHRP_CODE_SUCCESS) + || (cie->code == NHRP_CODE_ADMINISTRATIVELY_PROHIBITED + && nhs->hub))) + ok = 0; + mtu = ntohs(cie->mtu); + debugf(NHRP_DEBUG_COMMON, "NHS: CIE MTU: %d", mtu); + } + + if (!ok) + return; + + /* Parse extensions */ + sockunion_family(&nifp->nat_nbma) = AF_UNSPEC; + sockunion_family(&cie_nbma_nhs) = AF_UNSPEC; + while ((ext = nhrp_ext_pull(&p->extensions, &extpl)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + /* NHS adds second CIE if NAT is detected */ + if (nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, &cie_proto) + && nhrp_cie_pull(&extpl, p->hdr, &cie_nbma, + &cie_proto)) { + nifp->nat_nbma = cie_nbma; + debugf(NHRP_DEBUG_IF, + "%s: NAT detected, real NBMA address: %pSU", + ifp->name, &nifp->nbma); + } + break; + case NHRP_EXTENSION_RESPONDER_ADDRESS: + /* NHS adds its own record as responder address */ + nhrp_cie_pull(&extpl, p->hdr, &cie_nbma_nhs, + &cie_proto_nhs); + break; + } + } + + /* Success - schedule next registration, and route NHS */ + r->timeout = 2; + holdtime = nifp->afi[nhs->afi].holdtime; + EVENT_OFF(r->t_register); + + /* RFC 2332 5.2.3 - Registration is recommend to be renewed + * every one third of holdtime */ + event_add_timer(master, nhrp_reg_send_req, r, holdtime / 3, + &r->t_register); + + r->proto_addr = p->dst_proto; + c = nhrp_cache_get(ifp, &p->dst_proto, 1); + if (c) + nhrp_cache_update_binding(c, NHRP_CACHE_NHS, holdtime, + nhrp_peer_ref(r->peer), mtu, NULL, + &cie_nbma_nhs); +} + +static void nhrp_reg_timeout(struct event *t) +{ + struct nhrp_registration *r = EVENT_ARG(t); + struct nhrp_cache *c; + + + if (r->timeout >= 16 && sockunion_family(&r->proto_addr) != AF_UNSPEC) { + nhrp_reqid_free(&nhrp_packet_reqid, &r->reqid); + c = nhrp_cache_get(r->nhs->ifp, &r->proto_addr, 0); + if (c) + nhrp_cache_update_binding(c, NHRP_CACHE_NHS, -1, NULL, + 0, NULL, NULL); + sockunion_family(&r->proto_addr) = AF_UNSPEC; + } + + r->timeout <<= 1; + if (r->timeout > 64) { + /* If registration fails repeatedly, this may be because the + * IPSec connection is not working. Close the connection so it + * can be re-established correctly + */ + if (r->peer && r->peer->vc && r->peer->vc->ike_uniqueid) { + debugf(NHRP_DEBUG_COMMON, + "Terminating IPSec Connection for %d", + r->peer->vc->ike_uniqueid); + vici_terminate_vc_by_ike_id(r->peer->vc->ike_uniqueid); + r->peer->vc->ike_uniqueid = 0; + } + r->timeout = 2; + } + event_add_timer_msec(master, nhrp_reg_send_req, r, 10, &r->t_register); +} + +static void nhrp_reg_peer_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_registration *r = + container_of(n, struct nhrp_registration, peer_notifier); + + switch (cmd) { + case NOTIFY_PEER_UP: + case NOTIFY_PEER_DOWN: + case NOTIFY_PEER_IFCONFIG_CHANGED: + case NOTIFY_PEER_MTU_CHANGED: + debugf(NHRP_DEBUG_COMMON, "NHS: Flush timer for %pSU", + &r->peer->vc->remote.nbma); + EVENT_OFF(r->t_register); + event_add_timer_msec(master, nhrp_reg_send_req, r, 10, + &r->t_register); + break; + } +} + +static void nhrp_reg_send_req(struct event *t) +{ + struct nhrp_registration *r = EVENT_ARG(t); + struct nhrp_nhs *nhs = r->nhs; + struct interface *ifp = nhs->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad = &nifp->afi[nhs->afi]; + union sockunion *dst_proto, nhs_proto; + struct zbuf *zb; + struct nhrp_packet_header *hdr; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + + if (!nhrp_peer_check(r->peer, 2)) { + int renewtime = if_ad->holdtime / 4; + /* RFC 2332 5.2.0.1 says "a retry is sent after an appropriate + * interval." Using holdtime/4, to be shorter than + * recommended renew time (holdtime/3), see RFC2332 Sec 5.2.3 + */ + debugf(NHRP_DEBUG_COMMON, + "NHS: Waiting link for %pSU, retrying in %d seconds", + &r->peer->vc->remote.nbma, renewtime); + event_add_timer(master, nhrp_reg_send_req, r, renewtime, + &r->t_register); + return; + } + + event_add_timer(master, nhrp_reg_timeout, r, r->timeout, + &r->t_register); + + /* RFC2332 5.2.3 NHC uses it's own address as dst if NHS is unknown */ + dst_proto = &nhs->proto_addr; + if (sockunion_family(dst_proto) == AF_UNSPEC) + dst_proto = &if_ad->addr; + + debugf(NHRP_DEBUG_COMMON, "NHS: Register %pSU -> %pSU (timeout %d)", + &if_ad->addr, dst_proto, r->timeout); + + /* No protocol address configured for tunnel interface */ + if (sockunion_family(&if_ad->addr) == AF_UNSPEC) + return; + + zb = zbuf_alloc(1400); + hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REQUEST, + &nifp->nbma, &if_ad->addr, dst_proto); + hdr->hop_count = 1; + if (!(if_ad->flags & NHRP_IFF_REG_NO_UNIQUE)) + hdr->flags |= htons(NHRP_FLAG_REGISTRATION_UNIQUE); + + hdr->u.request_id = htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, + &r->reqid, nhrp_reg_reply)); + + /* FIXME: push CIE for each local protocol address */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); + /* RFC2332 5.2.1 if unique is set then prefix length must be 0xff */ + cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE) + ? 8 * sockunion_get_addrlen(dst_proto) + : 0xff; + cie->holding_time = htons(if_ad->holdtime); + cie->mtu = htons(if_ad->mtu); + + nhrp_ext_request(zb, hdr, ifp); + + /* Cisco NAT detection extension */ + if (sockunion_family(&r->proto_addr) != AF_UNSPEC) { + nhs_proto = r->proto_addr; + } else if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC) { + nhs_proto = nhs->proto_addr; + } else { + /* cisco magic: If NHS is not known then use all 0s as + * client protocol address in NAT Extension header + */ + memset(&nhs_proto, 0, sizeof(nhs_proto)); + sockunion_family(&nhs_proto) = afi2family(nhs->afi); + } + + hdr->flags |= htons(NHRP_FLAG_REGISTRATION_NAT); + ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); + /* push NHS details */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &r->peer->vc->remote.nbma, + &nhs_proto); + cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr); + cie->mtu = htons(if_ad->mtu); + nhrp_ext_complete(zb, ext); + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(r->peer, zb); + zbuf_free(zb); +} + +static void nhrp_reg_delete(struct nhrp_registration *r) +{ + nhrp_peer_notify_del(r->peer, &r->peer_notifier); + nhrp_peer_unref(r->peer); + nhrp_reglist_del(&r->nhs->reglist_head, r); + EVENT_OFF(r->t_register); + XFREE(MTYPE_NHRP_REGISTRATION, r); +} + +static struct nhrp_registration * +nhrp_reg_by_nbma(struct nhrp_nhs *nhs, const union sockunion *nbma_addr) +{ + struct nhrp_registration *r; + + frr_each (nhrp_reglist, &nhs->reglist_head, r) + if (sockunion_same(&r->peer->vc->remote.nbma, nbma_addr)) + return r; + return NULL; +} + +static void nhrp_nhs_resolve_cb(struct resolver_query *q, const char *errstr, + int n, union sockunion *addrs) +{ + struct nhrp_nhs *nhs = container_of(q, struct nhrp_nhs, dns_resolve); + struct nhrp_interface *nifp = nhs->ifp->info; + struct nhrp_registration *reg; + int i; + + if (n < 0) { + /* Failed, retry in a moment */ + event_add_timer(master, nhrp_nhs_resolve, nhs, 5, + &nhs->t_resolve); + return; + } + + event_add_timer(master, nhrp_nhs_resolve, nhs, 2 * 60 * 60, + &nhs->t_resolve); + + frr_each (nhrp_reglist, &nhs->reglist_head, reg) + reg->mark = 1; + + nhs->hub = 0; + for (i = 0; i < n; i++) { + if (sockunion_same(&addrs[i], &nifp->nbma)) { + nhs->hub = 1; + continue; + } + + reg = nhrp_reg_by_nbma(nhs, &addrs[i]); + if (reg) { + reg->mark = 0; + continue; + } + + reg = XCALLOC(MTYPE_NHRP_REGISTRATION, sizeof(*reg)); + reg->peer = nhrp_peer_get(nhs->ifp, &addrs[i]); + reg->nhs = nhs; + reg->timeout = 1; + nhrp_reglist_add_tail(&nhs->reglist_head, reg); + nhrp_peer_notify_add(reg->peer, ®->peer_notifier, + nhrp_reg_peer_notify); + event_add_timer_msec(master, nhrp_reg_send_req, reg, 50, + ®->t_register); + } + + frr_each_safe (nhrp_reglist, &nhs->reglist_head, reg) + if (reg->mark) + nhrp_reg_delete(reg); +} + +static void nhrp_nhs_resolve(struct event *t) +{ + struct nhrp_nhs *nhs = EVENT_ARG(t); + + resolver_resolve(&nhs->dns_resolve, AF_INET, VRF_DEFAULT, + nhs->nbma_fqdn, nhrp_nhs_resolve_cb); +} + +int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + + if (sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != afi2family(afi)) + return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; + + frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (sockunion_family(&nhs->proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_same(&nhs->proto_addr, proto_addr)) + return NHRP_ERR_ENTRY_EXISTS; + + if (strcmp(nhs->nbma_fqdn, nbma_fqdn) == 0) + return NHRP_ERR_ENTRY_EXISTS; + } + + nhs = XMALLOC(MTYPE_NHRP_NHS, sizeof(struct nhrp_nhs)); + + *nhs = (struct nhrp_nhs){ + .afi = afi, + .ifp = ifp, + .proto_addr = *proto_addr, + .nbma_fqdn = strdup(nbma_fqdn), + .reglist_head = INIT_DLIST(nhs->reglist_head), + }; + nhrp_nhslist_add_tail(&nifp->afi[afi].nhslist_head, nhs); + event_add_timer_msec(master, nhrp_nhs_resolve, nhs, 1000, + &nhs->t_resolve); + + return NHRP_OK; +} + +int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + int ret = NHRP_ERR_ENTRY_NOT_FOUND; + + if (sockunion_family(proto_addr) != AF_UNSPEC + && sockunion_family(proto_addr) != afi2family(afi)) + return NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH; + + frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (!sockunion_same(&nhs->proto_addr, proto_addr)) + continue; + if (strcmp(nhs->nbma_fqdn, nbma_fqdn) != 0) + continue; + + nhrp_nhs_free(nifp, afi, nhs); + ret = NHRP_OK; + } + + return ret; +} + +int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs) +{ + struct nhrp_registration *r; + + frr_each_safe (nhrp_reglist, &nhs->reglist_head, r) + nhrp_reg_delete(r); + EVENT_OFF(nhs->t_resolve); + nhrp_nhslist_del(&nifp->afi[afi].nhslist_head, nhs); + free((void *)nhs->nbma_fqdn); + XFREE(MTYPE_NHRP_NHS, nhs); + return 0; +} + +void nhrp_nhs_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + afi_t afi; + + for (afi = 0; afi < AFI_MAX; afi++) { + debugf(NHRP_DEBUG_COMMON, "Cleaning up nhs entries (%zu)", + nhrp_nhslist_count(&nifp->afi[afi].nhslist_head)); + + frr_each_safe (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) + nhrp_nhs_free(nifp, afi, nhs); + } +} + +void nhrp_nhs_terminate(void) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_nhs *nhs; + afi_t afi; + + FOR_ALL_INTERFACES (vrf, ifp) { + nifp = ifp->info; + for (afi = 0; afi < AFI_MAX; afi++) { + frr_each_safe (nhrp_nhslist, + &nifp->afi[afi].nhslist_head, nhs) + nhrp_nhs_free(nifp, afi, nhs); + } + nhrp_peer_interface_del(ifp); + } +} + +void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, + void *), + void *ctx) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_nhs *nhs; + struct nhrp_registration *reg; + + frr_each (nhrp_nhslist, &nifp->afi[afi].nhslist_head, nhs) { + if (nhrp_reglist_count(&nhs->reglist_head)) { + frr_each (nhrp_reglist, &nhs->reglist_head, reg) + cb(nhs, reg, ctx); + } else + cb(nhs, 0, ctx); + } +} + +int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp) +{ + int i; + struct nhrp_nhs *nhs; + struct nhrp_registration *reg; + + for (i = 0; i < AFI_MAX; i++) { + frr_each (nhrp_nhslist, &nifp->afi[i].nhslist_head, nhs) { + if (!nhrp_reglist_count(&nhs->reglist_head)) + continue; + + frr_each (nhrp_reglist, &nhs->reglist_head, reg) { + if (!sockunion_cmp(in_ip, + ®->peer->vc->remote.nbma)) + return 1; + } + } + } + return 0; +} diff --git a/nhrpd/nhrp_packet.c b/nhrpd/nhrp_packet.c new file mode 100644 index 0000000..c6bd3bb --- /dev/null +++ b/nhrpd/nhrp_packet.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP packet handling functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "nhrpd.h" +#include "zbuf.h" +#include "frrevent.h" +#include "hash.h" + +#include "nhrp_protocol.h" +#include "os.h" + +struct nhrp_reqid_pool nhrp_packet_reqid; + +static uint16_t family2proto(int family) +{ + switch (family) { + case AF_INET: + return ETH_P_IP; + case AF_INET6: + return ETH_P_IPV6; + } + return 0; +} + +static int proto2family(uint16_t proto) +{ + switch (proto) { + case ETH_P_IP: + return AF_INET; + case ETH_P_IPV6: + return AF_INET6; + } + return AF_UNSPEC; +} + +struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, + const union sockunion *src_nbma, + const union sockunion *src_proto, + const union sockunion *dst_proto) +{ + struct nhrp_packet_header *hdr; + + hdr = zbuf_push(zb, struct nhrp_packet_header); + if (!hdr) + return NULL; + + *hdr = (struct nhrp_packet_header){ + .afnum = htons(family2afi(sockunion_family(src_nbma))), + .protocol_type = + htons(family2proto(sockunion_family(src_proto))), + .version = NHRP_VERSION_RFC2332, + .type = type, + .hop_count = 64, + .src_nbma_address_len = sockunion_get_addrlen(src_nbma), + .src_protocol_address_len = sockunion_get_addrlen(src_proto), + .dst_protocol_address_len = sockunion_get_addrlen(dst_proto), + }; + + zbuf_put(zb, sockunion_get_addr(src_nbma), hdr->src_nbma_address_len); + zbuf_put(zb, sockunion_get_addr(src_proto), + hdr->src_protocol_address_len); + zbuf_put(zb, sockunion_get_addr(dst_proto), + hdr->dst_protocol_address_len); + + return hdr; +} + +struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, + union sockunion *src_nbma, + union sockunion *src_proto, + union sockunion *dst_proto) +{ + struct nhrp_packet_header *hdr; + + hdr = zbuf_pull(zb, struct nhrp_packet_header); + if (!hdr) + return NULL; + + sockunion_set(src_nbma, afi2family(htons(hdr->afnum)), + zbuf_pulln(zb, + hdr->src_nbma_address_len + + hdr->src_nbma_subaddress_len), + hdr->src_nbma_address_len + hdr->src_nbma_subaddress_len); + sockunion_set(src_proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, hdr->src_protocol_address_len), + hdr->src_protocol_address_len); + sockunion_set(dst_proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, hdr->dst_protocol_address_len), + hdr->dst_protocol_address_len); + + return hdr; +} + +uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len) +{ + const uint16_t *pdu16 = (const uint16_t *)pdu; + uint32_t csum = 0; + int i; + + for (i = 0; i < len / 2; i++) + csum += pdu16[i]; + if (len & 1) + csum += htons(pdu[len - 1]); + + while (csum & 0xffff0000) + csum = (csum & 0xffff) + (csum >> 16); + + return (~csum) & 0xffff; +} + +void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr) +{ + unsigned short size; + + if (hdr->extension_offset) + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_END + | NHRP_EXTENSION_FLAG_COMPULSORY); + + size = zb->tail - (uint8_t *)hdr; + hdr->packet_size = htons(size); + hdr->checksum = 0; + hdr->checksum = nhrp_packet_calculate_checksum((uint8_t *)hdr, size); +} + +struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, + const union sockunion *nbma, + const union sockunion *proto) +{ + struct nhrp_cie_header *cie; + + cie = zbuf_push(zb, struct nhrp_cie_header); + *cie = (struct nhrp_cie_header){ + .code = code, + }; + if (nbma) { + cie->nbma_address_len = sockunion_get_addrlen(nbma); + zbuf_put(zb, sockunion_get_addr(nbma), cie->nbma_address_len); + } + if (proto) { + cie->protocol_address_len = sockunion_get_addrlen(proto); + zbuf_put(zb, sockunion_get_addr(proto), + cie->protocol_address_len); + } + + return cie; +} + +struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, + struct nhrp_packet_header *hdr, + union sockunion *nbma, + union sockunion *proto) +{ + struct nhrp_cie_header *cie; + + cie = zbuf_pull(zb, struct nhrp_cie_header); + if (!cie) + return NULL; + + if (cie->nbma_address_len + cie->nbma_subaddress_len > 0) { + sockunion_set(nbma, afi2family(htons(hdr->afnum)), + zbuf_pulln(zb, + cie->nbma_address_len + + cie->nbma_subaddress_len), + cie->nbma_address_len + cie->nbma_subaddress_len); + } else { + sockunion_family(nbma) = AF_UNSPEC; + } + + if (cie->protocol_address_len) { + sockunion_set(proto, proto2family(htons(hdr->protocol_type)), + zbuf_pulln(zb, cie->protocol_address_len), + cie->protocol_address_len); + } else { + sockunion_family(proto) = AF_UNSPEC; + } + + return cie; +} + +struct nhrp_extension_header * +nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type) +{ + struct nhrp_extension_header *ext; + ext = zbuf_push(zb, struct nhrp_extension_header); + if (!ext) + return NULL; + + if (!hdr->extension_offset) + hdr->extension_offset = + htons(zb->tail - (uint8_t *)hdr + - sizeof(struct nhrp_extension_header)); + + *ext = (struct nhrp_extension_header){ + .type = htons(type), .length = 0, + }; + return ext; +} + +void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext) +{ + ext->length = htons(zb->tail - (uint8_t *)ext + - sizeof(struct nhrp_extension_header)); +} + +struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, + struct zbuf *payload) +{ + struct nhrp_extension_header *ext; + uint16_t plen; + + ext = zbuf_pull(zb, struct nhrp_extension_header); + if (!ext) + return NULL; + + plen = htons(ext->length); + zbuf_init(payload, zbuf_pulln(zb, plen), plen, plen); + return ext; +} + +void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp) +{ + /* Place holders for standard extensions */ + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS + | NHRP_EXTENSION_FLAG_COMPULSORY); + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_REVERSE_TRANSIT_NHS + | NHRP_EXTENSION_FLAG_COMPULSORY); + nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_RESPONDER_ADDRESS + | NHRP_EXTENSION_FLAG_COMPULSORY); +} + +int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp, struct nhrp_extension_header *ext, + struct zbuf *extpayload) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *ad = &nifp->afi[htons(hdr->afnum)]; + struct nhrp_extension_header *dst; + struct nhrp_cie_header *cie; + uint16_t type; + + type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; + if (type == NHRP_EXTENSION_END) + return 0; + + dst = nhrp_ext_push(zb, hdr, htons(ext->type)); + if (!dst) + goto err; + + switch (type) { + case NHRP_EXTENSION_RESPONDER_ADDRESS: + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, + &ad->addr); + if (!cie) + goto err; + cie->mtu = htons(ad->mtu); + cie->holding_time = htons(ad->holdtime); + break; + default: + if (type & NHRP_EXTENSION_FLAG_COMPULSORY) + goto err; + fallthrough; + case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: + case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: + /* Supported compulsory extensions, and any + * non-compulsory that is not explicitly handled, + * should be just copied. */ + zbuf_copy(zb, extpayload, zbuf_used(extpayload)); + break; + } + nhrp_ext_complete(zb, dst); + return 0; +err: + zbuf_set_werror(zb); + return -1; +} + +static void nhrp_packet_recvraw(struct event *t) +{ + int fd = EVENT_FD(t), ifindex; + struct zbuf *zb; + struct interface *ifp; + struct nhrp_peer *p; + union sockunion remote_nbma; + uint8_t addr[64]; + size_t len, addrlen; + + event_add_read(master, nhrp_packet_recvraw, 0, fd, NULL); + + zb = zbuf_alloc(1500); + if (!zb) + return; + + len = zbuf_size(zb); + addrlen = sizeof(addr); + if (os_recvmsg(zb->buf, &len, &ifindex, addr, &addrlen) < 0) + goto err; + + zb->head = zb->buf; + zb->tail = zb->buf + len; + + switch (addrlen) { + case 4: + sockunion_set(&remote_nbma, AF_INET, addr, addrlen); + break; + default: + goto err; + } + + ifp = if_lookup_by_index(ifindex, VRF_DEFAULT); + if (!ifp) + goto err; + + p = nhrp_peer_get(ifp, &remote_nbma); + if (!p) + goto err; + + nhrp_peer_recv(p, zb); + nhrp_peer_unref(p); + return; + +err: + zbuf_free(zb); +} + +int nhrp_packet_init(void) +{ + event_add_read(master, nhrp_packet_recvraw, 0, os_socket(), NULL); + return 0; +} diff --git a/nhrpd/nhrp_peer.c b/nhrpd/nhrp_peer.c new file mode 100644 index 0000000..6e7857c --- /dev/null +++ b/nhrpd/nhrp_peer.c @@ -0,0 +1,1245 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP peer functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "zebra.h" +#include "memory.h" +#include "frrevent.h" +#include "hash.h" +#include "network.h" + +#include "nhrpd.h" +#include "nhrp_protocol.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_PEER, "NHRP peer entry"); + +struct ipv6hdr { + uint8_t priority_version; + uint8_t flow_lbl[3]; + uint16_t payload_len; + uint8_t nexthdr; + uint8_t hop_limit; + struct in6_addr saddr; + struct in6_addr daddr; +}; + +static void nhrp_packet_debug(struct zbuf *zb, const char *dir); + +static void nhrp_peer_check_delete(struct nhrp_peer *p) +{ + struct nhrp_interface *nifp = p->ifp->info; + + if (p->ref || notifier_active(&p->notifier_list)) + return; + + debugf(NHRP_DEBUG_COMMON, "Deleting peer ref:%d remote:%pSU local:%pSU", + p->ref, &p->vc->remote.nbma, &p->vc->local.nbma); + + EVENT_OFF(p->t_fallback); + EVENT_OFF(p->t_timer); + if (nifp->peer_hash) + hash_release(nifp->peer_hash, p); + nhrp_interface_notify_del(p->ifp, &p->ifp_notifier); + nhrp_vc_notify_del(p->vc, &p->vc_notifier); + XFREE(MTYPE_NHRP_PEER, p); +} + +static void nhrp_peer_notify_up(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + p->t_fallback = NULL; + if (nifp->enabled && (!nifp->ipsec_profile || vc->ipsec)) { + p->online = 1; + nhrp_peer_ref(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_UP); + nhrp_peer_unref(p); + } +} + +static void __nhrp_peer_check(struct nhrp_peer *p) +{ + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + unsigned online; + + online = nifp->enabled && (!nifp->ipsec_profile || vc->ipsec); + if (p->online != online) { + EVENT_OFF(p->t_fallback); + if (online && notifier_active(&p->notifier_list)) { + /* If we requested the IPsec connection, delay + * the up notification a bit to allow things + * settle down. This allows IKE to install + * SPDs and SAs. */ + event_add_timer_msec(master, nhrp_peer_notify_up, p, 50, + &p->t_fallback); + } else { + nhrp_peer_ref(p); + p->online = online; + if (online) { + notifier_call(&p->notifier_list, + NOTIFY_PEER_UP); + } else { + p->requested = p->fallback_requested = 0; + notifier_call(&p->notifier_list, + NOTIFY_PEER_DOWN); + } + nhrp_peer_unref(p); + } + } +} + +static void nhrp_peer_vc_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_peer *p = container_of(n, struct nhrp_peer, vc_notifier); + + switch (cmd) { + case NOTIFY_VC_IPSEC_CHANGED: + __nhrp_peer_check(p); + break; + case NOTIFY_VC_IPSEC_UPDATE_NBMA: + nhrp_peer_ref(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_NBMA_CHANGING); + nhrp_peer_unref(p); + break; + } +} + +static void nhrp_peer_ifp_notify(struct notifier_block *n, unsigned long cmd) +{ + struct nhrp_peer *p = container_of(n, struct nhrp_peer, ifp_notifier); + struct nhrp_interface *nifp; + struct nhrp_vc *vc; + + nhrp_peer_ref(p); + switch (cmd) { + case NOTIFY_INTERFACE_UP: + case NOTIFY_INTERFACE_DOWN: + __nhrp_peer_check(p); + break; + case NOTIFY_INTERFACE_NBMA_CHANGED: + /* Source NBMA changed, rebind to new VC */ + nifp = p->ifp->info; + vc = nhrp_vc_get(&nifp->nbma, &p->vc->remote.nbma, 1); + if (vc && p->vc != vc) { + nhrp_vc_notify_del(p->vc, &p->vc_notifier); + p->vc = vc; + nhrp_vc_notify_add(p->vc, &p->vc_notifier, + nhrp_peer_vc_notify); + __nhrp_peer_check(p); + } + fallthrough; /* to post config update */ + case NOTIFY_INTERFACE_ADDRESS_CHANGED: + notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED); + break; + case NOTIFY_INTERFACE_IPSEC_CHANGED: + __nhrp_peer_check(p); + notifier_call(&p->notifier_list, NOTIFY_PEER_IFCONFIG_CHANGED); + break; + case NOTIFY_INTERFACE_MTU_CHANGED: + notifier_call(&p->notifier_list, NOTIFY_PEER_MTU_CHANGED); + break; + } + nhrp_peer_unref(p); +} + +static unsigned int nhrp_peer_key(const void *peer_data) +{ + const struct nhrp_peer *p = peer_data; + return sockunion_hash(&p->vc->remote.nbma); +} + +static bool nhrp_peer_cmp(const void *cache_data, const void *key_data) +{ + const struct nhrp_peer *a = cache_data; + const struct nhrp_peer *b = key_data; + + return a->ifp == b->ifp && a->vc == b->vc; +} + +static void *nhrp_peer_create(void *data) +{ + struct nhrp_peer *p, *key = data; + + p = XMALLOC(MTYPE_NHRP_PEER, sizeof(*p)); + + *p = (struct nhrp_peer){ + .ref = 0, + .ifp = key->ifp, + .vc = key->vc, + .notifier_list = NOTIFIER_LIST_INITIALIZER(&p->notifier_list), + }; + nhrp_vc_notify_add(p->vc, &p->vc_notifier, nhrp_peer_vc_notify); + nhrp_interface_notify_add(p->ifp, &p->ifp_notifier, + nhrp_peer_ifp_notify); + + return p; +} + +static void do_peer_hash_free(void *hb_data) +{ + struct nhrp_peer *p = (struct nhrp_peer *)hb_data; + + nhrp_peer_check_delete(p); +} + +void nhrp_peer_interface_del(struct interface *ifp) +{ + struct nhrp_interface *nifp = ifp->info; + + debugf(NHRP_DEBUG_COMMON, "Cleaning up undeleted peer entries (%lu)", + nifp->peer_hash ? nifp->peer_hash->count : 0); + + hash_clean_and_free(&nifp->peer_hash, do_peer_hash_free); +} + +struct nhrp_peer *nhrp_peer_get(struct interface *ifp, + const union sockunion *remote_nbma) +{ + struct nhrp_interface *nifp = ifp->info; + struct nhrp_peer key, *p; + struct nhrp_vc *vc; + + if (!nifp->peer_hash) { + nifp->peer_hash = hash_create(nhrp_peer_key, nhrp_peer_cmp, + "NHRP Peer Hash"); + if (!nifp->peer_hash) + return NULL; + } + + vc = nhrp_vc_get(&nifp->nbma, remote_nbma, 1); + if (!vc) + return NULL; + + key.ifp = ifp; + key.vc = vc; + + p = hash_get(nifp->peer_hash, &key, nhrp_peer_create); + nhrp_peer_ref(p); + if (p->ref == 1) + __nhrp_peer_check(p); + + return p; +} + +struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p) +{ + if (p) + p->ref++; + return p; +} + +void nhrp_peer_unref(struct nhrp_peer *p) +{ + if (p) { + p->ref--; + nhrp_peer_check_delete(p); + } +} + +static void nhrp_peer_request_timeout(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + + if (p->online) + return; + + if (nifp->ipsec_fallback_profile && !p->prio + && !p->fallback_requested) { + p->fallback_requested = 1; + vici_request_vc(nifp->ipsec_fallback_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, 30, + &p->t_fallback); + } else { + p->requested = p->fallback_requested = 0; + } +} + +static void nhrp_peer_defer_vici_request(struct event *t) +{ + struct nhrp_peer *p = EVENT_ARG(t); + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + EVENT_OFF(p->t_timer); + + if (p->online) { + debugf(NHRP_DEBUG_COMMON, + "IPsec connection to %pSU already established", + &vc->remote.nbma); + } else { + vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, + (nifp->ipsec_fallback_profile && !p->prio) ? 15 + : 30, + &p->t_fallback); + } +} + +int nhrp_peer_check(struct nhrp_peer *p, int establish) +{ + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + + if (p->online) + return 1; + if (!establish) + return 0; + if (p->requested) + return 0; + if (!nifp->ipsec_profile) + return 0; + if (sockunion_family(&vc->local.nbma) == AF_UNSPEC) + return 0; + if (vc->ipsec) + return 1; + + p->prio = establish > 1; + p->requested = 1; + + /* All NHRP registration requests are prioritized */ + if (p->prio) { + vici_request_vc(nifp->ipsec_profile, &vc->local.nbma, + &vc->remote.nbma, p->prio); + event_add_timer(master, nhrp_peer_request_timeout, p, + (nifp->ipsec_fallback_profile && !p->prio) ? 15 + : 30, + &p->t_fallback); + } else { + /* Maximum timeout is 1 second */ + int r_time_ms = frr_weak_random() % 1000; + + debugf(NHRP_DEBUG_COMMON, + "Initiating IPsec connection request to %pSU after %d ms:", + &vc->remote.nbma, r_time_ms); + event_add_timer_msec(master, nhrp_peer_defer_vici_request, p, + r_time_ms, &p->t_timer); + } + + return 0; +} + +void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *n, + notifier_fn_t fn) +{ + notifier_add(n, &p->notifier_list, fn); +} + +void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *n) +{ + notifier_del(n, &p->notifier_list); + nhrp_peer_check_delete(p); +} + +void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb) +{ + nhrp_packet_debug(zb, "Send"); + + if (!p->online) + return; + + debugf(NHRP_DEBUG_KERNEL, "PACKET: Send %pSU -> %pSU", + &p->vc->local.nbma, &p->vc->remote.nbma); + + os_sendmsg(zb->head, zbuf_used(zb), p->ifp->ifindex, + sockunion_get_addr(&p->vc->remote.nbma), + sockunion_get_addrlen(&p->vc->remote.nbma), ETH_P_NHRP); + zbuf_reset(zb); +} + +static void nhrp_process_nat_extension(struct nhrp_packet_parser *pp, + union sockunion *proto, + union sockunion *cie_nbma) +{ + union sockunion cie_proto; + struct zbuf payload; + struct nhrp_extension_header *ext; + struct zbuf *extensions; + + if (!cie_nbma) + return; + + sockunion_family(cie_nbma) = AF_UNSPEC; + + if (!proto || sockunion_family(proto) == AF_UNSPEC) + return; + + /* Handle extensions */ + extensions = zbuf_alloc(zbuf_used(&pp->extensions)); + if (extensions) { + zbuf_copy_peek(extensions, &pp->extensions, + zbuf_used(&pp->extensions)); + while ((ext = nhrp_ext_pull(extensions, &payload)) != NULL) { + switch (htons(ext->type) + & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + /* Process the NBMA and proto address in NAT + * extension and update the cache without which + * the neighbor table in the kernel contains the + * source NBMA address which is not reachable + * since it is behind a NAT device + */ + debugf(NHRP_DEBUG_COMMON, + "shortcut res_resp: Processing NAT Extension for %pSU", + proto); + while (nhrp_cie_pull(&payload, pp->hdr, + cie_nbma, &cie_proto)) { + if (sockunion_family(&cie_proto) + == AF_UNSPEC) + continue; + + if (!sockunion_cmp(proto, &cie_proto)) { + debugf(NHRP_DEBUG_COMMON, + "cie_nbma for proto %pSU is %pSU", + proto, cie_nbma); + break; + } + } + } + } + zbuf_free(extensions); + } +} + +static void nhrp_handle_resolution_req(struct nhrp_packet_parser *pp) +{ + struct interface *ifp = pp->ifp; + struct zbuf *zb, payload; + struct nhrp_packet_header *hdr; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + struct nhrp_cache *c; + union sockunion cie_nbma, cie_nbma_nat, cie_proto, *proto_addr, + *nbma_addr, *claimed_nbma_addr; + int holdtime, prefix_len, hostprefix_len; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_peer *peer; + size_t paylen; + + if (!(pp->if_ad->flags & NHRP_IFF_SHORTCUT)) { + debugf(NHRP_DEBUG_COMMON, "Shortcuts disabled"); + /* FIXME: Send error indication? */ + return; + } + + if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA + && pp->route_prefix.prefixlen < 8) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut to more generic than /8 dropped"); + return; + } + + debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Resolution Req"); + + if (nhrp_route_address(ifp, &pp->src_proto, NULL, &peer) + != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + /* Copy payload CIE */ + hostprefix_len = 8 * sockunion_get_addrlen(&pp->if_ad->addr); + paylen = zbuf_used(&pp->payload); + debugf(NHRP_DEBUG_COMMON, "shortcut res_rep: paylen %zu", paylen); + + while ((cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, + &cie_proto)) + != NULL) { + prefix_len = cie->prefix_length; + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: parsing CIE with prefixlen=%u", + prefix_len); + if (prefix_len == 0 || prefix_len >= hostprefix_len) + prefix_len = hostprefix_len; + + if (prefix_len != hostprefix_len + && !(pp->hdr->flags + & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) { + cie->code = NHRP_CODE_BINDING_NON_UNIQUE; + continue; + } + + /* We currently support only unique prefix registrations */ + if (prefix_len != hostprefix_len) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) + ? &pp->src_proto + : &cie_proto; + + /* Check for this proto_addr in NHRP_NAT_EXTENSION */ + nhrp_process_nat_extension(pp, proto_addr, &cie_nbma_nat); + + if (sockunion_family(&cie_nbma_nat) == AF_UNSPEC) { + /* It may be possible that this resolution reply is + * coming directly from NATTED Spoke and there is not + * NAT Extension present + */ + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: No NAT Extension for %pSU", + proto_addr); + + if (!sockunion_same(&pp->src_nbma, + &pp->peer->vc->remote.nbma) + && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, + nifp)) { + cie_nbma_nat = pp->peer->vc->remote.nbma; + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: NAT detected using %pSU as cie_nbma", + &cie_nbma_nat); + } + } + + if (sockunion_family(&cie_nbma_nat) != AF_UNSPEC) + nbma_addr = &cie_nbma_nat; + else if (sockunion_family(&cie_nbma) != AF_UNSPEC) + nbma_addr = &cie_nbma; + else + nbma_addr = &pp->src_nbma; + + if (sockunion_family(&cie_nbma) != AF_UNSPEC) + claimed_nbma_addr = &cie_nbma; + else + claimed_nbma_addr = &pp->src_nbma; + + holdtime = htons(cie->holding_time); + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: holdtime is %u (if 0, using %u)", + holdtime, pp->if_ad->holdtime); + if (!holdtime) + holdtime = pp->if_ad->holdtime; + + c = nhrp_cache_get(ifp, proto_addr, 1); + if (!c) { + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: no cache found"); + cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES; + continue; + } + + debugf(NHRP_DEBUG_COMMON, + "shortcut res_rep: updating binding for nmba addr %pSU", + nbma_addr); + if (!nhrp_cache_update_binding( + c, NHRP_CACHE_DYNAMIC, holdtime, + nhrp_peer_get(pp->ifp, nbma_addr), htons(cie->mtu), + nbma_addr, claimed_nbma_addr)) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + cie->code = NHRP_CODE_SUCCESS; + } + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_RESOLUTION_REPLY, &pp->src_nbma, + &pp->src_proto, &pp->dst_proto); + + /* Copied information from request */ + hdr->flags = pp->hdr->flags + & htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER + | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); + hdr->flags |= htons(NHRP_FLAG_RESOLUTION_DESTINATION_STABLE + | NHRP_FLAG_RESOLUTION_AUTHORATIVE); + hdr->u.request_id = pp->hdr->u.request_id; + + /* CIE payload for the reply packet */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nbma, + &pp->if_ad->addr); + cie->holding_time = htons(pp->if_ad->holdtime); + cie->mtu = htons(pp->if_ad->mtu); + if (pp->if_ad->network_id && pp->route_type == NHRP_ROUTE_OFF_NBMA) + cie->prefix_length = pp->route_prefix.prefixlen; + else + cie->prefix_length = + 8 * sockunion_get_addrlen(&pp->if_ad->addr); + + /* Handle extensions */ + while ((ext = nhrp_ext_pull(&pp->extensions, &payload)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + ext = nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_NAT_ADDRESS); + if (!ext) + goto err; + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &nifp->nat_nbma, + &pp->if_ad->addr); + if (!cie) + goto err; + cie->prefix_length = + 8 * sockunion_get_addrlen( + &pp->if_ad->addr); + + cie->mtu = htons(pp->if_ad->mtu); + nhrp_ext_complete(zb, ext); + } + break; + default: + if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0) + goto err; + break; + } + } + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(peer, zb); +err: + nhrp_peer_unref(peer); + zbuf_free(zb); +} + +static void nhrp_handle_registration_request(struct nhrp_packet_parser *p) +{ + struct interface *ifp = p->ifp; + struct zbuf *zb, payload; + struct nhrp_packet_header *hdr; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + struct nhrp_cache *c; + union sockunion cie_nbma, cie_proto, *proto_addr, *nbma_addr, + *nbma_natoa; + int holdtime, prefix_len, hostprefix_len, natted = 0; + size_t paylen; + void *pay; + + debugf(NHRP_DEBUG_COMMON, "Parsing and replying to Registration Req"); + hostprefix_len = 8 * sockunion_get_addrlen(&p->if_ad->addr); + + if (!sockunion_same(&p->src_nbma, &p->peer->vc->remote.nbma)) + natted = 1; + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_REGISTRATION_REPLY, &p->src_nbma, + &p->src_proto, &p->if_ad->addr); + + /* Copied information from request */ + hdr->flags = p->hdr->flags & htons(NHRP_FLAG_REGISTRATION_UNIQUE + | NHRP_FLAG_REGISTRATION_NAT); + hdr->u.request_id = p->hdr->u.request_id; + + /* Copy payload CIEs */ + paylen = zbuf_used(&p->payload); + pay = zbuf_pushn(zb, paylen); + if (!pay) + goto err; + memcpy(pay, zbuf_pulln(&p->payload, paylen), paylen); + zbuf_init(&payload, pay, paylen, paylen); + + while ((cie = nhrp_cie_pull(&payload, hdr, &cie_nbma, &cie_proto)) + != NULL) { + prefix_len = cie->prefix_length; + if (prefix_len == 0 || prefix_len >= hostprefix_len) + prefix_len = hostprefix_len; + + if (prefix_len != hostprefix_len + && !(p->hdr->flags + & htons(NHRP_FLAG_REGISTRATION_UNIQUE))) { + cie->code = NHRP_CODE_BINDING_NON_UNIQUE; + continue; + } + + /* We currently support only unique prefix registrations */ + if (prefix_len != hostprefix_len) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + proto_addr = (sockunion_family(&cie_proto) == AF_UNSPEC) + ? &p->src_proto + : &cie_proto; + nbma_addr = (sockunion_family(&cie_nbma) == AF_UNSPEC) + ? &p->src_nbma + : &cie_nbma; + nbma_natoa = NULL; + if (natted) { + nbma_natoa = + (sockunion_family(&p->peer->vc->remote.nbma) + == AF_UNSPEC) + ? nbma_addr + : &p->peer->vc->remote.nbma; + } + + holdtime = htons(cie->holding_time); + if (!holdtime) + holdtime = p->if_ad->holdtime; + + c = nhrp_cache_get(ifp, proto_addr, 1); + if (!c) { + cie->code = NHRP_CODE_INSUFFICIENT_RESOURCES; + continue; + } + + if (!nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, holdtime, + nhrp_peer_ref(p->peer), + htons(cie->mtu), nbma_natoa, + nbma_addr)) { + cie->code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + continue; + } + + cie->code = NHRP_CODE_SUCCESS; + } + + /* Handle extensions */ + while ((ext = nhrp_ext_pull(&p->extensions, &payload)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: + ext = nhrp_ext_push(zb, hdr, + NHRP_EXTENSION_NAT_ADDRESS); + if (!ext) + goto err; + zbuf_copy(zb, &payload, zbuf_used(&payload)); + if (natted) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &p->peer->vc->remote.nbma, + &p->src_proto); + cie->prefix_length = + 8 * sockunion_get_addrlen( + &p->if_ad->addr); + cie->mtu = htons(p->if_ad->mtu); + } + nhrp_ext_complete(zb, ext); + break; + default: + if (nhrp_ext_reply(zb, hdr, ifp, ext, &payload) < 0) + goto err; + break; + } + } + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p->peer, zb); +err: + zbuf_free(zb); +} + +static int parse_ether_packet(struct zbuf *zb, uint16_t protocol_type, + union sockunion *src, union sockunion *dst) +{ + switch (protocol_type) { + case ETH_P_IP: { + struct iphdr *iph = zbuf_pull(zb, struct iphdr); + if (iph) { + if (src) + sockunion_set(src, AF_INET, + (uint8_t *)&iph->saddr, + sizeof(iph->saddr)); + if (dst) + sockunion_set(dst, AF_INET, + (uint8_t *)&iph->daddr, + sizeof(iph->daddr)); + } + } break; + case ETH_P_IPV6: { + struct ipv6hdr *iph = zbuf_pull(zb, struct ipv6hdr); + if (iph) { + if (src) + sockunion_set(src, AF_INET6, + (uint8_t *)&iph->saddr, + sizeof(iph->saddr)); + if (dst) + sockunion_set(dst, AF_INET6, + (uint8_t *)&iph->daddr, + sizeof(iph->daddr)); + } + } break; + default: + return 0; + } + return 1; +} + +void nhrp_peer_send_indication(struct interface *ifp, uint16_t protocol_type, + struct zbuf *pkt) +{ + union sockunion dst; + struct zbuf *zb, payload; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_afi_data *if_ad; + struct nhrp_packet_header *hdr; + struct nhrp_peer *p; + + if (!nifp->enabled) + return; + + payload = *pkt; + if (!parse_ether_packet(&payload, protocol_type, &dst, NULL)) + return; + + if (nhrp_route_address(ifp, &dst, NULL, &p) != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + if_ad = &nifp->afi[family2afi(sockunion_family(&dst))]; + if (!(if_ad->flags & NHRP_IFF_REDIRECT)) { + debugf(NHRP_DEBUG_COMMON, + "Send Traffic Indication to %pSU about packet to %pSU ignored", + &p->vc->remote.nbma, &dst); + return; + } + + debugf(NHRP_DEBUG_COMMON, + "Send Traffic Indication to %pSU (online=%d) about packet to %pSU", + &p->vc->remote.nbma, p->online, &dst); + + /* Create reply */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push(zb, NHRP_PACKET_TRAFFIC_INDICATION, &nifp->nbma, + &if_ad->addr, &dst); + hdr->hop_count = 1; + + /* Payload is the packet causing indication */ + zbuf_copy(zb, pkt, zbuf_used(pkt)); + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p, zb); + nhrp_peer_unref(p); + zbuf_free(zb); +} + +static void nhrp_handle_error_ind(struct nhrp_packet_parser *pp) +{ + struct zbuf origmsg = pp->payload; + struct nhrp_packet_header *hdr; + struct nhrp_reqid *reqid; + union sockunion src_nbma, src_proto, dst_proto; + + hdr = nhrp_packet_pull(&origmsg, &src_nbma, &src_proto, &dst_proto); + if (!hdr) + return; + + debugf(NHRP_DEBUG_COMMON, + "Error Indication from %pSU about packet to %pSU ignored", + &pp->src_proto, &dst_proto); + + reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, htonl(hdr->u.request_id)); + if (reqid) + reqid->cb(reqid, pp); +} + +static void nhrp_handle_traffic_ind(struct nhrp_packet_parser *p) +{ + union sockunion dst; + + if (!parse_ether_packet(&p->payload, htons(p->hdr->protocol_type), NULL, + &dst)) + return; + + debugf(NHRP_DEBUG_COMMON, + "Traffic Indication from %pSU about packet to %pSU: %s", + &p->src_proto, &dst, + (p->if_ad->flags & NHRP_IFF_SHORTCUT) ? "trying shortcut" + : "ignored"); + + if (p->if_ad->flags & NHRP_IFF_SHORTCUT) + nhrp_shortcut_initiate(&dst); +} + +enum packet_type_t { + PACKET_UNKNOWN = 0, + PACKET_REQUEST, + PACKET_REPLY, + PACKET_INDICATION, +}; + +static struct { + enum packet_type_t type; + const char *name; + void (*handler)(struct nhrp_packet_parser *); +} packet_types[] = {[0] = + { + .type = PACKET_UNKNOWN, + .name = "UNKNOWN", + }, + [NHRP_PACKET_RESOLUTION_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Resolution-Request", + .handler = nhrp_handle_resolution_req, + }, + [NHRP_PACKET_RESOLUTION_REPLY] = + { + .type = PACKET_REPLY, + .name = "Resolution-Reply", + }, + [NHRP_PACKET_REGISTRATION_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Registration-Request", + .handler = nhrp_handle_registration_request, + }, + [NHRP_PACKET_REGISTRATION_REPLY] = + { + .type = PACKET_REPLY, + .name = "Registration-Reply", + }, + [NHRP_PACKET_PURGE_REQUEST] = + { + .type = PACKET_REQUEST, + .name = "Purge-Request", + }, + [NHRP_PACKET_PURGE_REPLY] = + { + .type = PACKET_REPLY, + .name = "Purge-Reply", + }, + [NHRP_PACKET_ERROR_INDICATION] = + { + .type = PACKET_INDICATION, + .name = "Error-Indication", + .handler = nhrp_handle_error_ind, + }, + [NHRP_PACKET_TRAFFIC_INDICATION] = { + .type = PACKET_INDICATION, + .name = "Traffic-Indication", + .handler = nhrp_handle_traffic_ind, + }}; + +static void nhrp_peer_forward(struct nhrp_peer *p, + struct nhrp_packet_parser *pp) +{ + struct zbuf *zb, *zb_copy, extpl; + struct nhrp_packet_header *hdr; + struct nhrp_extension_header *ext, *dst; + struct nhrp_cie_header *cie; + struct nhrp_interface *nifp = pp->ifp->info; + struct nhrp_afi_data *if_ad = pp->if_ad; + union sockunion cie_nbma, cie_protocol, cie_protocol_mandatory, *proto; + uint16_t type, len; + struct nhrp_cache *c; + + if (pp->hdr->hop_count == 0) + return; + + /* Create forward packet - copy header */ + zb = zbuf_alloc(1500); + zb_copy = zbuf_alloc(1500); + + hdr = nhrp_packet_push(zb, pp->hdr->type, &pp->src_nbma, &pp->src_proto, + &pp->dst_proto); + hdr->flags = pp->hdr->flags; + hdr->hop_count = pp->hdr->hop_count - 1; + hdr->u.request_id = pp->hdr->u.request_id; + + /* Copy payload */ + zbuf_copy_peek(zb_copy, &pp->payload, zbuf_used(&pp->payload)); + zbuf_copy(zb, &pp->payload, zbuf_used(&pp->payload)); + + /* Get CIE Extension from Mandatory part */ + sockunion_family(&cie_protocol_mandatory) = AF_UNSPEC; + nhrp_cie_pull(zb_copy, pp->hdr, &cie_nbma, &cie_protocol_mandatory); + + /* Copy extensions */ + while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { + type = htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY; + len = htons(ext->length); + + if (type == NHRP_EXTENSION_END) + break; + + dst = nhrp_ext_push(zb, hdr, htons(ext->type)); + if (!dst) + goto err; + + switch (type) { + case NHRP_EXTENSION_FORWARD_TRANSIT_NHS: + case NHRP_EXTENSION_REVERSE_TRANSIT_NHS: + zbuf_put(zb, extpl.head, len); + if ((type == NHRP_EXTENSION_REVERSE_TRANSIT_NHS) + == (packet_types[hdr->type].type == PACKET_REPLY)) { + /* Check NHS list for forwarding loop */ + while (nhrp_cie_pull(&extpl, pp->hdr, + &cie_nbma, + &cie_protocol) != NULL) { + if (sockunion_same(&p->vc->remote.nbma, + &cie_nbma)) + goto err; + } + /* Append our selves to the list */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, + &nifp->nbma, &if_ad->addr); + if (!cie) + goto err; + cie->mtu = htons(if_ad->mtu); + cie->holding_time = htons(if_ad->holdtime); + } + break; + case NHRP_EXTENSION_NAT_ADDRESS: + c = NULL; + proto = NULL; + + /* If NAT extension is empty then attempt to populate + * it with cached NBMA information + */ + if (len == 0) { + if (packet_types[hdr->type].type + == PACKET_REQUEST) { + debugf(NHRP_DEBUG_COMMON, + "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the request packet"); + proto = &pp->src_proto; + } else if (packet_types[hdr->type].type + == PACKET_REPLY) { + debugf(NHRP_DEBUG_COMMON, + "Processing NHRP_EXTENSION_NAT_ADDRESS while forwarding the reply packet"); + /* For reply packet use protocol + * specified in CIE of mandatory part + * for cache lookup + */ + if (sockunion_family( + &cie_protocol_mandatory) + != AF_UNSPEC) + proto = &cie_protocol_mandatory; + } + } + + if (proto) { + debugf(NHRP_DEBUG_COMMON, "Proto is %pSU", + proto); + c = nhrp_cache_get(nifp->ifp, proto, 0); + } + + if (c) { + debugf(NHRP_DEBUG_COMMON, + "c->cur.remote_nbma_natoa is %pSU", + &c->cur.remote_nbma_natoa); + if (sockunion_family(&c->cur.remote_nbma_natoa) + != AF_UNSPEC) { + cie = nhrp_cie_push( + zb, + NHRP_CODE_SUCCESS, + &c->cur.remote_nbma_natoa, + proto); + if (!cie) + goto err; + } + } else { + if (proto) + debugf(NHRP_DEBUG_COMMON, + "No cache entry for proto %pSU", + proto); + /* Copy existing NAT extension to new packet if + * either it was already not-empty, or we do not + * have valid cache information + */ + zbuf_put(zb, extpl.head, len); + } + break; + default: + if (htons(ext->type) & NHRP_EXTENSION_FLAG_COMPULSORY) + /* FIXME: RFC says to just copy, but not + * append our selves to the transit NHS list + */ + goto err; + fallthrough; + case NHRP_EXTENSION_RESPONDER_ADDRESS: + /* Supported compulsory extensions, and any + * non-compulsory that is not explicitly handled, + * should be just copied. + */ + zbuf_copy(zb, &extpl, len); + break; + } + nhrp_ext_complete(zb, dst); + } + + nhrp_packet_complete(zb, hdr); + nhrp_peer_send(p, zb); + zbuf_free(zb); + zbuf_free(zb_copy); + return; +err: + nhrp_packet_debug(pp->pkt, "FWD-FAIL"); + zbuf_free(zb); + zbuf_free(zb_copy); +} + +static void nhrp_packet_debug(struct zbuf *zb, const char *dir) +{ + union sockunion src_nbma, src_proto, dst_proto; + struct nhrp_packet_header *hdr; + struct zbuf zhdr; + int reply; + + if (likely(!(debug_flags & NHRP_DEBUG_COMMON))) + return; + + zbuf_init(&zhdr, zb->buf, zb->tail - zb->buf, zb->tail - zb->buf); + hdr = nhrp_packet_pull(&zhdr, &src_nbma, &src_proto, &dst_proto); + + reply = packet_types[hdr->type].type == PACKET_REPLY; + debugf(NHRP_DEBUG_COMMON, "%s %s(%d) %pSU -> %pSU", dir, + (packet_types[hdr->type].name ? packet_types[hdr->type].name + : "Unknown"), + hdr->type, reply ? &dst_proto : &src_proto, + reply ? &src_proto : &dst_proto); +} + +static int proto2afi(uint16_t proto) +{ + switch (proto) { + case ETH_P_IP: + return AFI_IP; + case ETH_P_IPV6: + return AFI_IP6; + } + return AF_UNSPEC; +} + +struct nhrp_route_info { + int local; + struct interface *ifp; + struct nhrp_vc *vc; +}; + +void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb) +{ + struct nhrp_packet_header *hdr; + struct nhrp_vc *vc = p->vc; + struct interface *ifp = p->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_packet_parser pp; + struct nhrp_peer *peer = NULL; + struct nhrp_reqid *reqid; + const char *info = NULL; + union sockunion *target_addr; + unsigned paylen, extoff, extlen, realsize; + afi_t nbma_afi, proto_afi; + + debugf(NHRP_DEBUG_KERNEL, "PACKET: Recv %pSU -> %pSU", &vc->remote.nbma, + &vc->local.nbma); + + if (!p->online) { + info = "peer not online"; + goto drop; + } + + if (nhrp_packet_calculate_checksum(zb->head, zbuf_used(zb)) != 0) { + info = "bad checksum"; + goto drop; + } + + realsize = zbuf_used(zb); + hdr = nhrp_packet_pull(zb, &pp.src_nbma, &pp.src_proto, &pp.dst_proto); + if (!hdr) { + info = "corrupt header"; + goto drop; + } + + pp.ifp = ifp; + pp.pkt = zb; + pp.hdr = hdr; + pp.peer = p; + + nbma_afi = htons(hdr->afnum); + proto_afi = proto2afi(htons(hdr->protocol_type)); + if (hdr->type > NHRP_PACKET_MAX || hdr->version != NHRP_VERSION_RFC2332 + || nbma_afi >= AFI_MAX || proto_afi == AF_UNSPEC + || packet_types[hdr->type].type == PACKET_UNKNOWN + || htons(hdr->packet_size) > realsize) { + zlog_info( + "From %pSU: error: packet type %d, version %d, AFI %d, proto %x, size %d (real size %d)", + &vc->remote.nbma, (int)hdr->type, (int)hdr->version, + (int)nbma_afi, (int)htons(hdr->protocol_type), + (int)htons(hdr->packet_size), (int)realsize); + goto drop; + } + pp.if_ad = &((struct nhrp_interface *)ifp->info)->afi[proto_afi]; + + extoff = htons(hdr->extension_offset); + if (extoff) { + assert(zb->head > zb->buf); + uint32_t header_offset = zb->head - zb->buf; + if (extoff >= realsize) { + info = "extoff larger than packet"; + goto drop; + } + if (extoff < header_offset) { + info = "extoff smaller than header offset"; + goto drop; + } + paylen = extoff - header_offset; + } else { + paylen = zbuf_used(zb); + } + zbuf_init(&pp.payload, zbuf_pulln(zb, paylen), paylen, paylen); + extlen = zbuf_used(zb); + zbuf_init(&pp.extensions, zbuf_pulln(zb, extlen), extlen, extlen); + + if (!nifp->afi[proto_afi].network_id) { + info = "nhrp not enabled"; + goto drop; + } + + nhrp_packet_debug(zb, "Recv"); + + /* FIXME: Check authentication here. This extension needs to be + * pre-handled. */ + + /* Figure out if this is local */ + target_addr = (packet_types[hdr->type].type == PACKET_REPLY) + ? &pp.src_proto + : &pp.dst_proto; + + if (sockunion_same(&pp.src_proto, &pp.dst_proto)) + pp.route_type = NHRP_ROUTE_LOCAL; + else + pp.route_type = nhrp_route_address(pp.ifp, target_addr, + &pp.route_prefix, &peer); + + switch (pp.route_type) { + case NHRP_ROUTE_LOCAL: + nhrp_packet_debug(zb, "!LOCAL"); + if (packet_types[hdr->type].type == PACKET_REPLY) { + reqid = nhrp_reqid_lookup(&nhrp_packet_reqid, + htonl(hdr->u.request_id)); + if (reqid) { + reqid->cb(reqid, &pp); + break; + } else { + nhrp_packet_debug(zb, "!UNKNOWN-REQID"); + /* FIXME: send error-indication */ + } + } + fallthrough; /* FIXME: double check, is this correct? */ + case NHRP_ROUTE_OFF_NBMA: + if (packet_types[hdr->type].handler) { + packet_types[hdr->type].handler(&pp); + break; + } + break; + case NHRP_ROUTE_NBMA_NEXTHOP: + nhrp_peer_forward(peer, &pp); + break; + case NHRP_ROUTE_BLACKHOLE: + break; + } + +drop: + if (info) { + zlog_info("From %pSU: error: %s", &vc->remote.nbma, info); + } + if (peer) + nhrp_peer_unref(peer); + zbuf_free(zb); +} diff --git a/nhrpd/nhrp_protocol.h b/nhrpd/nhrp_protocol.h new file mode 100644 index 0000000..8cf1ebb --- /dev/null +++ b/nhrpd/nhrp_protocol.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +/* nhrp_protocol.h - NHRP protocol definitions + * + * Copyright (c) 2007-2012 Timo Teräs + */ + +#ifndef NHRP_PROTOCOL_H +#define NHRP_PROTOCOL_H + +#include + +/* NHRP Ethernet protocol number */ +#define ETH_P_NHRP 0x2001 + +/* NHRP Version */ +#define NHRP_VERSION_RFC2332 1 + +/* NHRP Packet Types */ +#define NHRP_PACKET_RESOLUTION_REQUEST 1 +#define NHRP_PACKET_RESOLUTION_REPLY 2 +#define NHRP_PACKET_REGISTRATION_REQUEST 3 +#define NHRP_PACKET_REGISTRATION_REPLY 4 +#define NHRP_PACKET_PURGE_REQUEST 5 +#define NHRP_PACKET_PURGE_REPLY 6 +#define NHRP_PACKET_ERROR_INDICATION 7 +#define NHRP_PACKET_TRAFFIC_INDICATION 8 +#define NHRP_PACKET_MAX 8 + +/* NHRP Extension Types */ +#define NHRP_EXTENSION_FLAG_COMPULSORY 0x8000 +#define NHRP_EXTENSION_END 0 +#define NHRP_EXTENSION_PAYLOAD 0 +#define NHRP_EXTENSION_RESPONDER_ADDRESS 3 +#define NHRP_EXTENSION_FORWARD_TRANSIT_NHS 4 +#define NHRP_EXTENSION_REVERSE_TRANSIT_NHS 5 +#define NHRP_EXTENSION_AUTHENTICATION 7 +#define NHRP_EXTENSION_VENDOR 8 +#define NHRP_EXTENSION_NAT_ADDRESS 9 + +/* NHRP Error Indication Codes */ +#define NHRP_ERROR_UNRECOGNIZED_EXTENSION 1 +#define NHRP_ERROR_LOOP_DETECTED 2 +#define NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE 6 +#define NHRP_ERROR_PROTOCOL_ERROR 7 +#define NHRP_ERROR_SDU_SIZE_EXCEEDED 8 +#define NHRP_ERROR_INVALID_EXTENSION 9 +#define NHRP_ERROR_INVALID_RESOLUTION_REPLY 10 +#define NHRP_ERROR_AUTHENTICATION_FAILURE 11 +#define NHRP_ERROR_HOP_COUNT_EXCEEDED 15 + +/* NHRP CIE Codes */ +#define NHRP_CODE_SUCCESS 0 +#define NHRP_CODE_ADMINISTRATIVELY_PROHIBITED 4 +#define NHRP_CODE_INSUFFICIENT_RESOURCES 5 +#define NHRP_CODE_NO_BINDING_EXISTS 11 +#define NHRP_CODE_BINDING_NON_UNIQUE 13 +#define NHRP_CODE_UNIQUE_ADDRESS_REGISTERED 14 + +/* NHRP Flags for Resolution request/reply */ +#define NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER 0x8000 +#define NHRP_FLAG_RESOLUTION_AUTHORATIVE 0x4000 +#define NHRP_FLAG_RESOLUTION_DESTINATION_STABLE 0x2000 +#define NHRP_FLAG_RESOLUTION_UNIQUE 0x1000 +#define NHRP_FLAG_RESOLUTION_SOURCE_STABLE 0x0800 +#define NHRP_FLAG_RESOLUTION_NAT 0x0002 + +/* NHRP Flags for Registration request/reply */ +#define NHRP_FLAG_REGISTRATION_UNIQUE 0x8000 +#define NHRP_FLAG_REGISTRATION_NAT 0x0002 + +/* NHRP Flags for Purge request/reply */ +#define NHRP_FLAG_PURGE_NO_REPLY 0x8000 + +/* NHRP Authentication extension types (ala Cisco) */ +#define NHRP_AUTHENTICATION_PLAINTEXT 0x00000001 + +/* NHRP Packet Structures */ +struct nhrp_packet_header { + /* Fixed header */ + uint16_t afnum; + uint16_t protocol_type; + uint8_t snap[5]; + uint8_t hop_count; + uint16_t packet_size; + uint16_t checksum; + uint16_t extension_offset; + uint8_t version; + uint8_t type; + uint8_t src_nbma_address_len; + uint8_t src_nbma_subaddress_len; + + /* Mandatory header */ + uint8_t src_protocol_address_len; + uint8_t dst_protocol_address_len; + uint16_t flags; + union { + uint32_t request_id; + struct { + uint16_t code; + uint16_t offset; + } error; + } u; +} __attribute__((packed)); + +struct nhrp_cie_header { + uint8_t code; + uint8_t prefix_length; + uint16_t unused; + uint16_t mtu; + uint16_t holding_time; + uint8_t nbma_address_len; + uint8_t nbma_subaddress_len; + uint8_t protocol_address_len; + uint8_t preference; +} __attribute__((packed)); + +struct nhrp_extension_header { + uint16_t type; + uint16_t length; +} __attribute__((packed)); + +struct nhrp_cisco_authentication_extension { + uint32_t type; + uint8_t secret[8]; +} __attribute__((packed)); + +#endif diff --git a/nhrpd/nhrp_route.c b/nhrpd/nhrp_route.c new file mode 100644 index 0000000..7adc4a6 --- /dev/null +++ b/nhrpd/nhrp_route.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP routing functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nhrpd.h" +#include "table.h" +#include "memory.h" +#include "stream.h" +#include "log.h" +#include "zclient.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_ROUTE, "NHRP routing entry"); + +static struct zclient *zclient; +static struct route_table *zebra_rib[AFI_MAX]; + +struct route_info { + union sockunion via; + struct interface *ifp; + struct interface *nhrp_ifp; +}; + +static struct route_node *nhrp_route_update_get(const struct prefix *p, + int create) +{ + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(p)); + + if (!zebra_rib[afi]) + return NULL; + + if (create) { + rn = route_node_get(zebra_rib[afi], p); + if (!rn->info) { + rn->info = XCALLOC(MTYPE_NHRP_ROUTE, + sizeof(struct route_info)); + route_lock_node(rn); + } + return rn; + } else { + return route_node_lookup(zebra_rib[afi], p); + } +} + +static void nhrp_route_update_put(struct route_node *rn) +{ + struct route_info *ri = rn->info; + + if (!ri->ifp && !ri->nhrp_ifp + && sockunion_is_null(&ri->via)) { + XFREE(MTYPE_NHRP_ROUTE, rn->info); + route_unlock_node(rn); + } + route_unlock_node(rn); +} + +static void nhrp_route_update_zebra(const struct prefix *p, + union sockunion *nexthop, + struct interface *ifp) +{ + struct route_node *rn; + struct route_info *ri; + + rn = nhrp_route_update_get(p, !sockunion_is_null(nexthop) || ifp); + if (rn) { + ri = rn->info; + ri->via = *nexthop; + ri->ifp = ifp; + nhrp_route_update_put(rn); + } +} + +void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp) +{ + struct route_node *rn; + struct route_info *ri; + + rn = nhrp_route_update_get(p, ifp != NULL); + if (rn) { + ri = rn->info; + ri->nhrp_ifp = ifp; + nhrp_route_update_put(rn); + } +} + +void nhrp_route_announce(int add, enum nhrp_cache_type type, + const struct prefix *p, struct interface *ifp, + const union sockunion *nexthop_ref, uint32_t mtu) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + + if (zclient->sock < 0) + return; + + memset(&api, 0, sizeof(api)); + api.type = ZEBRA_ROUTE_NHRP; + api.safi = SAFI_UNICAST; + api.vrf_id = VRF_DEFAULT; + api.prefix = *p; + + switch (type) { + case NHRP_CACHE_NEGATIVE: + /* Fill in a blackhole nexthop */ + zapi_route_set_blackhole(&api, BLACKHOLE_REJECT); + ifp = NULL; + nexthop_ref = NULL; + break; + case NHRP_CACHE_DYNAMIC: + case NHRP_CACHE_NHS: + case NHRP_CACHE_STATIC: + /* Regular route, so these are announced + * to other routing daemons */ + break; + case NHRP_CACHE_INVALID: + case NHRP_CACHE_INCOMPLETE: + /* + * I cannot believe that we want to set a FIB_OVERRIDE + * for invalid state or incomplete. But this matches + * the original code. Someone will probably notice + * the problem eventually + */ + case NHRP_CACHE_CACHED: + case NHRP_CACHE_LOCAL: + case NHRP_CACHE_NUM_TYPES: + SET_FLAG(api.flags, ZEBRA_FLAG_FIB_OVERRIDE); + break; + } + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + api.nexthop_num = 1; + api_nh = &api.nexthops[0]; + api_nh->vrf_id = VRF_DEFAULT; + + switch (api.prefix.family) { + case AF_INET: + if (api.prefix.prefixlen == IPV4_MAX_BITLEN && + nexthop_ref && + memcmp(&nexthop_ref->sin.sin_addr, &api.prefix.u.prefix4, + sizeof(struct in_addr)) == 0) { + nexthop_ref = NULL; + } + if (nexthop_ref) { + api_nh->gate.ipv4 = nexthop_ref->sin.sin_addr; + api_nh->type = NEXTHOP_TYPE_IPV4; + } + if (ifp) { + api_nh->ifindex = ifp->ifindex; + if (api_nh->type == NEXTHOP_TYPE_IPV4) + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + break; + case AF_INET6: + if (api.prefix.prefixlen == IPV6_MAX_BITLEN && + nexthop_ref && + memcmp(&nexthop_ref->sin6.sin6_addr, &api.prefix.u.prefix6, + sizeof(struct in6_addr)) == 0) { + nexthop_ref = NULL; + } + if (nexthop_ref) { + api_nh->gate.ipv6 = nexthop_ref->sin6.sin6_addr; + api_nh->type = NEXTHOP_TYPE_IPV6; + } + if (ifp) { + api_nh->ifindex = ifp->ifindex; + if (api_nh->type == NEXTHOP_TYPE_IPV6) + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + else + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + break; + } + if (mtu) { + SET_FLAG(api.message, ZAPI_MESSAGE_MTU); + api.mtu = mtu; + } + + if (unlikely(debug_flags & NHRP_DEBUG_ROUTE)) { + char buf[PREFIX_STRLEN]; + + zlog_debug( + "Zebra send: route %s %pFX nexthop %s metric %u count %d dev %s", + add ? "add" : "del", &api.prefix, + nexthop_ref ? inet_ntop(api.prefix.family, + &api_nh->gate, + buf, sizeof(buf)) + : "", + api.metric, api.nexthop_num, ifp ? ifp->name : "none"); + } + + zclient_route_send(add ? ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, zclient, + &api); +} + +int nhrp_route_read(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct interface *ifp = NULL; + union sockunion nexthop_addr; + int added; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + /* ignore our routes */ + if (api.type == ZEBRA_ROUTE_NHRP) + return 0; + + /* ignore local routes */ + if (api.type == ZEBRA_ROUTE_LOCAL) + return 0; + + sockunion_family(&nexthop_addr) = AF_UNSPEC; + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) { + api_nh = &api.nexthops[0]; + + nexthop_addr.sa.sa_family = api.prefix.family; + switch (nexthop_addr.sa.sa_family) { + case AF_INET: + nexthop_addr.sin.sin_addr = api_nh->gate.ipv4; + break; + case AF_INET6: + nexthop_addr.sin6.sin6_addr = api_nh->gate.ipv6; + break; + } + + if (api_nh->ifindex != IFINDEX_INTERNAL) + ifp = if_lookup_by_index(api_nh->ifindex, VRF_DEFAULT); + } + + added = (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD); + debugf(NHRP_DEBUG_ROUTE, "if-route-%s: %pFX via %pSU dev %s", + added ? "add" : "del", &api.prefix, &nexthop_addr, + ifp ? ifp->name : "(none)"); + + nhrp_route_update_zebra(&api.prefix, &nexthop_addr, added ? ifp : NULL); + nhrp_shortcut_prefix_change(&api.prefix, !added); + + return 0; +} + +int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, + union sockunion *via, struct interface **ifp) +{ + struct route_node *rn; + struct route_info *ri; + struct prefix lookup; + afi_t afi = family2afi(sockunion_family(addr)); + + sockunion2hostprefix(addr, &lookup); + + rn = route_node_match(zebra_rib[afi], &lookup); + if (!rn) + return 0; + + ri = rn->info; + if (ri->nhrp_ifp) { + debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: nhrp_if=%s", &lookup, + ri->nhrp_ifp->name); + + if (via) + sockunion_family(via) = AF_UNSPEC; + if (ifp) + *ifp = ri->nhrp_ifp; + } else { + debugf(NHRP_DEBUG_ROUTE, "lookup %pFX: zebra route dev %s", + &lookup, ri->ifp ? ri->ifp->name : "(none)"); + + if (via) + *via = ri->via; + if (ifp) + *ifp = ri->ifp; + } + if (p) + *p = rn->p; + route_unlock_node(rn); + return 1; +} + +enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, + union sockunion *addr, struct prefix *p, + struct nhrp_peer **peer) +{ + struct interface *ifp = in_ifp; + struct nhrp_interface *nifp; + struct nhrp_cache *c; + union sockunion via[4]; + uint32_t network_id = 0; + afi_t afi = family2afi(sockunion_family(addr)); + int i; + + if (ifp) { + nifp = ifp->info; + network_id = nifp->afi[afi].network_id; + + c = nhrp_cache_get(ifp, addr, 0); + if (c && c->cur.type == NHRP_CACHE_LOCAL) { + if (p) + memset(p, 0, sizeof(*p)); + return NHRP_ROUTE_LOCAL; + } + } + + for (i = 0; i < 4; i++) { + if (!nhrp_route_get_nexthop(addr, p, &via[i], &ifp)) + return NHRP_ROUTE_BLACKHOLE; + if (ifp) { + /* Departing from nbma network? */ + nifp = ifp->info; + if (network_id + && network_id != nifp->afi[afi].network_id) + return NHRP_ROUTE_OFF_NBMA; + } + if (sockunion_family(&via[i]) == AF_UNSPEC) + break; + /* Resolve via node, but return the prefix of first match */ + addr = &via[i]; + p = NULL; + } + + if (ifp) { + c = nhrp_cache_get(ifp, addr, 0); + if (c && c->cur.type >= NHRP_CACHE_DYNAMIC) { + if (p) + memset(p, 0, sizeof(*p)); + if (c->cur.type == NHRP_CACHE_LOCAL) + return NHRP_ROUTE_LOCAL; + if (peer) + *peer = nhrp_peer_ref(c->cur.peer); + return NHRP_ROUTE_NBMA_NEXTHOP; + } + } + + return NHRP_ROUTE_BLACKHOLE; +} + +static void nhrp_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, + ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + zclient_register_neigh(zclient, VRF_DEFAULT, AFI_IP, true); + zclient_register_neigh(zclient, VRF_DEFAULT, AFI_IP6, true); +} + +static zclient_handler *const nhrp_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = nhrp_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = nhrp_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = nhrp_route_read, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = nhrp_route_read, + [ZEBRA_NEIGH_ADDED] = nhrp_neighbor_operation, + [ZEBRA_NEIGH_REMOVED] = nhrp_neighbor_operation, + [ZEBRA_NEIGH_GET] = nhrp_neighbor_operation, + [ZEBRA_GRE_UPDATE] = nhrp_gre_update, +}; + +void nhrp_zebra_init(void) +{ + zebra_rib[AFI_IP] = route_table_init(); + zebra_rib[AFI_IP6] = route_table_init(); + + zclient = zclient_new(master, &zclient_options_default, nhrp_handlers, + array_size(nhrp_handlers)); + zclient->zebra_connected = nhrp_zebra_connected; + zclient_init(zclient, ZEBRA_ROUTE_NHRP, 0, &nhrpd_privs); +} + +static void nhrp_table_node_cleanup(struct route_table *table, + struct route_node *node) +{ + if (!node->info) + return; + + XFREE(MTYPE_NHRP_ROUTE, node->info); +} + +void nhrp_send_zebra_configure_arp(struct interface *ifp, int family) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) { + debugf(NHRP_DEBUG_COMMON, "%s() : zclient not ready", + __func__); + return; + } + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_CONFIGURE_ARP, ifp->vrf->vrf_id); + stream_putc(s, family); + stream_putl(s, ifp->ifindex); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +void nhrp_send_zebra_gre_source_set(struct interface *ifp, + unsigned int link_idx, + vrf_id_t link_vrf_id) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) { + zlog_err("%s : zclient not ready", __func__); + return; + } + if (link_idx == IFINDEX_INTERNAL || link_vrf_id == VRF_UNKNOWN) { + /* silently ignore */ + return; + } + s = zclient->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_GRE_SOURCE_SET, ifp->vrf->vrf_id); + stream_putl(s, ifp->ifindex); + stream_putl(s, link_idx); + stream_putl(s, link_vrf_id); + stream_putl(s, 0); /* mtu provisioning */ + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +void nhrp_send_zebra_nbr(union sockunion *in, + union sockunion *out, + struct interface *ifp) +{ + struct stream *s; + + if (!zclient || zclient->sock < 0) + return; + s = zclient->obuf; + stream_reset(s); + zclient_neigh_ip_encode(s, out ? ZEBRA_NEIGH_IP_ADD : ZEBRA_NEIGH_IP_DEL, + in, out, ifp, + out ? ZEBRA_NEIGH_STATE_REACHABLE + : ZEBRA_NEIGH_STATE_FAILED, + 0); + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); +} + +int nhrp_send_zebra_gre_request(struct interface *ifp) +{ + return zclient_send_zebra_gre_request(zclient, ifp); +} + +void nhrp_interface_update_arp(struct interface *ifp, bool arp_enable) +{ + zclient_interface_set_arp(zclient, ifp, arp_enable); +} + + +void nhrp_zebra_terminate(void) +{ + zclient_register_neigh(zclient, VRF_DEFAULT, AFI_IP, false); + zclient_register_neigh(zclient, VRF_DEFAULT, AFI_IP6, false); + zclient_stop(zclient); + zclient_free(zclient); + + zebra_rib[AFI_IP]->cleanup = nhrp_table_node_cleanup; + zebra_rib[AFI_IP6]->cleanup = nhrp_table_node_cleanup; + route_table_finish(zebra_rib[AFI_IP]); + route_table_finish(zebra_rib[AFI_IP6]); +} + +int nhrp_gre_update(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct nhrp_gre_info gre_info, *val; + struct interface *ifp; + + /* result */ + s = zclient->ibuf; + if (vrf_id != VRF_DEFAULT) + return 0; + + /* read GRE information */ + STREAM_GETL(s, gre_info.ifindex); + STREAM_GETL(s, gre_info.ikey); + STREAM_GETL(s, gre_info.okey); + STREAM_GETL(s, gre_info.ifindex_link); + STREAM_GETL(s, gre_info.vrfid_link); + STREAM_GETL(s, gre_info.vtep_ip.s_addr); + STREAM_GETL(s, gre_info.vtep_ip_remote.s_addr); + if (gre_info.ifindex == IFINDEX_INTERNAL) + val = NULL; + else + val = hash_lookup(nhrp_gre_list, &gre_info); + if (val) { + if (gre_info.vtep_ip.s_addr != val->vtep_ip.s_addr || + gre_info.vrfid_link != val->vrfid_link || + gre_info.ifindex_link != val->ifindex_link || + gre_info.ikey != val->ikey || + gre_info.okey != val->okey) { + /* update */ + memcpy(val, &gre_info, sizeof(struct nhrp_gre_info)); + } + } else { + val = nhrp_gre_info_alloc(&gre_info); + } + ifp = if_lookup_by_index(gre_info.ifindex, vrf_id); + debugf(NHRP_DEBUG_EVENT, "%s: gre interface %d vr %d obtained from system", + ifp ? ifp->name : "", gre_info.ifindex, vrf_id); + if (ifp) + nhrp_interface_update_nbma(ifp, val); + return 0; + +stream_failure: + zlog_err("%s(): error reading response ..", __func__); + return -1; +} diff --git a/nhrpd/nhrp_shortcut.c b/nhrpd/nhrp_shortcut.c new file mode 100644 index 0000000..e83ce7f --- /dev/null +++ b/nhrpd/nhrp_shortcut.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP shortcut related functions + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "nhrpd.h" +#include "table.h" +#include "memory.h" +#include "frrevent.h" +#include "log.h" +#include "nhrp_protocol.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_SHORTCUT, "NHRP shortcut"); + +static struct route_table *shortcut_rib[AFI_MAX]; + +static void nhrp_shortcut_do_purge(struct event *t); +static void nhrp_shortcut_delete(struct nhrp_shortcut *s, + void *arg __attribute__((__unused__))); +static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s); + +static void nhrp_shortcut_check_use(struct nhrp_shortcut *s) +{ + if (s->expiring && s->cache && s->cache->used) { + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX used and expiring", + s->p); + nhrp_shortcut_send_resolution_req(s); + } +} + +static void nhrp_shortcut_do_expire(struct event *t) +{ + struct nhrp_shortcut *s = EVENT_ARG(t); + + event_add_timer(master, nhrp_shortcut_do_purge, s, s->holding_time / 3, + &s->t_timer); + s->expiring = 1; + nhrp_shortcut_check_use(s); +} + +static void nhrp_shortcut_cache_notify(struct notifier_block *n, + unsigned long cmd) +{ + struct nhrp_shortcut *s = + container_of(n, struct nhrp_shortcut, cache_notifier); + struct nhrp_cache *c = s->cache; + + switch (cmd) { + case NOTIFY_CACHE_UP: + if (!s->route_installed) { + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: route install %pFX nh %pSU dev %s", + s->p, &c->remote_addr, + c && c->ifp ? c->ifp->name : ""); + + nhrp_route_announce(1, s->type, s->p, c ? c->ifp : NULL, + c ? &c->remote_addr : NULL, 0); + s->route_installed = 1; + } + break; + case NOTIFY_CACHE_USED: + nhrp_shortcut_check_use(s); + break; + case NOTIFY_CACHE_DOWN: + case NOTIFY_CACHE_DELETE: + if (s->route_installed) { + nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, + NULL, 0); + s->route_installed = 0; + } + if (cmd == NOTIFY_CACHE_DELETE) + nhrp_shortcut_delete(s, NULL); + break; + } +} + +static void nhrp_shortcut_update_binding(struct nhrp_shortcut *s, + enum nhrp_cache_type type, + struct nhrp_cache *c, int holding_time) +{ + s->type = type; + if (c != s->cache) { + if (s->cache) { + nhrp_cache_notify_del(s->cache, &s->cache_notifier); + s->cache = NULL; + } + s->cache = c; + if (s->cache) { + nhrp_cache_notify_add(s->cache, &s->cache_notifier, + nhrp_shortcut_cache_notify); + if (s->cache->route_installed) { + /* Force renewal of Zebra announce on prefix + * change */ + s->route_installed = 0; + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: forcing renewal of zebra announce on prefix change peer %pSU ht %u cur nbma %pSU dev %s", + &s->cache->remote_addr, holding_time, + &s->cache->cur.remote_nbma_natoa, + s->cache->ifp->name); + nhrp_shortcut_cache_notify(&s->cache_notifier, + NOTIFY_CACHE_UP); + } + } + if (!s->cache || !s->cache->route_installed) { + debugf(NHRP_DEBUG_ROUTE, + "Shortcut: notify cache down because cache?%s or ri?%s", + s->cache ? "yes" : "no", + s->cache ? (s->cache->route_installed ? "yes" + : "no") + : "n/a"); + nhrp_shortcut_cache_notify(&s->cache_notifier, + NOTIFY_CACHE_DOWN); + } + } + if (s->type == NHRP_CACHE_NEGATIVE && !s->route_installed) { + nhrp_route_announce(1, s->type, s->p, NULL, NULL, 0); + s->route_installed = 1; + } else if (s->type == NHRP_CACHE_INVALID && s->route_installed) { + nhrp_route_announce(0, NHRP_CACHE_INVALID, s->p, NULL, NULL, 0); + s->route_installed = 0; + } + + EVENT_OFF(s->t_timer); + if (holding_time) { + s->expiring = 0; + s->holding_time = holding_time; + event_add_timer(master, nhrp_shortcut_do_expire, s, + 2 * holding_time / 3, &s->t_timer); + } +} + +static void nhrp_shortcut_delete(struct nhrp_shortcut *s, + void *arg __attribute__((__unused__))) +{ + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(s->p)); + + EVENT_OFF(s->t_timer); + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX purged", s->p); + + nhrp_shortcut_update_binding(s, NHRP_CACHE_INVALID, NULL, 0); + + /* Delete node */ + rn = route_node_lookup(shortcut_rib[afi], s->p); + if (rn) { + XFREE(MTYPE_NHRP_SHORTCUT, rn->info); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } +} + +static void nhrp_shortcut_do_purge(struct event *t) +{ + struct nhrp_shortcut *s = EVENT_ARG(t); + s->t_timer = NULL; + nhrp_shortcut_delete(s, NULL); +} + +static struct nhrp_shortcut *nhrp_shortcut_get(struct prefix *p) +{ + struct nhrp_shortcut *s; + struct route_node *rn; + afi_t afi = family2afi(PREFIX_FAMILY(p)); + + if (!shortcut_rib[afi]) + return 0; + + rn = route_node_get(shortcut_rib[afi], p); + if (!rn->info) { + s = rn->info = XCALLOC(MTYPE_NHRP_SHORTCUT, + sizeof(struct nhrp_shortcut)); + s->type = NHRP_CACHE_INVALID; + s->p = &rn->p; + + debugf(NHRP_DEBUG_ROUTE, "Shortcut %pFX created", s->p); + } else { + s = rn->info; + route_unlock_node(rn); + } + return s; +} + +static void nhrp_shortcut_recv_resolution_rep(struct nhrp_reqid *reqid, + void *arg) +{ + struct nhrp_packet_parser *pp = arg; + struct interface *ifp = pp->ifp; + struct nhrp_interface *nifp = ifp->info; + struct nhrp_shortcut *s = + container_of(reqid, struct nhrp_shortcut, reqid); + struct nhrp_shortcut *ps; + struct nhrp_extension_header *ext; + struct nhrp_cie_header *cie; + struct nhrp_cache *c = NULL; + struct nhrp_cache *c_dst = NULL; + union sockunion *proto, cie_proto, *nbma, cie_nbma, nat_nbma; + struct prefix prefix, route_prefix; + struct zbuf extpl; + int holding_time = pp->if_ad->holdtime; + + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + EVENT_OFF(s->t_timer); + event_add_timer(master, nhrp_shortcut_do_purge, s, 1, &s->t_timer); + + if (pp->hdr->type != NHRP_PACKET_RESOLUTION_REPLY) { + if (pp->hdr->type == NHRP_PACKET_ERROR_INDICATION + && pp->hdr->u.error.code + == NHRP_ERROR_PROTOCOL_ADDRESS_UNREACHABLE) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Resolution: Protocol address unreachable"); + nhrp_shortcut_update_binding(s, NHRP_CACHE_NEGATIVE, + NULL, holding_time); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Resolution failed"); + } + return; + } + + /* Minor sanity check */ + prefix2sockunion(s->p, &cie_proto); + if (!sockunion_same(&cie_proto, &pp->dst_proto)) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: Warning dst_proto altered from %pSU to %pSU", + &cie_proto, &pp->dst_proto); + ; + } + + /* One or more CIEs should be given as reply, we support only one */ + cie = nhrp_cie_pull(&pp->payload, pp->hdr, &cie_nbma, &cie_proto); + if (!cie || cie->code != NHRP_CODE_SUCCESS) { + debugf(NHRP_DEBUG_COMMON, "Shortcut: CIE code %d", + cie ? cie->code : -1); + return; + } + + proto = sockunion_family(&cie_proto) != AF_UNSPEC ? &cie_proto + : &pp->dst_proto; + if (cie->holding_time) + holding_time = htons(cie->holding_time); + + prefix = *s->p; + prefix.prefixlen = cie->prefix_length; + + /* Sanity check prefix length */ + if (prefix.prefixlen >= 8 * prefix_blen(&prefix) + || prefix.prefixlen == 0) { + prefix.prefixlen = 8 * prefix_blen(&prefix); + } else if (nhrp_route_address(NULL, &pp->dst_proto, &route_prefix, NULL) + == NHRP_ROUTE_NBMA_NEXTHOP) { + if (prefix.prefixlen < route_prefix.prefixlen) + prefix.prefixlen = route_prefix.prefixlen; + } + + /* Parse extensions */ + memset(&nat_nbma, 0, sizeof(nat_nbma)); + while ((ext = nhrp_ext_pull(&pp->extensions, &extpl)) != NULL) { + switch (htons(ext->type) & ~NHRP_EXTENSION_FLAG_COMPULSORY) { + case NHRP_EXTENSION_NAT_ADDRESS: { + struct nhrp_cie_header *cie_nat; + + do { + union sockunion cie_nat_proto, cie_nat_nbma; + + sockunion_family(&cie_nat_proto) = AF_UNSPEC; + sockunion_family(&cie_nat_nbma) = AF_UNSPEC; + cie_nat = nhrp_cie_pull(&extpl, pp->hdr, + &cie_nat_nbma, + &cie_nat_proto); + /* We are interested only in peer CIE */ + if (cie_nat + && sockunion_same(&cie_nat_proto, proto)) { + nat_nbma = cie_nat_nbma; + } + } while (cie_nat); + } break; + default: + break; + } + } + + /* Update cache entry for the protocol to nbma binding */ + if (sockunion_family(&nat_nbma) != AF_UNSPEC) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: NAT detected (NAT extension) proto %pSU NBMA %pSU claimed-NBMA %pSU", + proto, &nat_nbma, &cie_nbma); + nbma = &nat_nbma; + } + /* For NHRP resolution reply the cie_nbma in mandatory part is the + * address of the actual address of the sender + */ + else if (!sockunion_same(&cie_nbma, &pp->peer->vc->remote.nbma) + && !nhrp_nhs_match_ip(&pp->peer->vc->remote.nbma, nifp)) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: NAT detected (no NAT Extension) proto %pSU NBMA %pSU claimed-NBMA %pSU", + proto, &pp->peer->vc->remote.nbma, &cie_nbma); + nbma = &pp->peer->vc->remote.nbma; + nat_nbma = *nbma; + } else { + nbma = &cie_nbma; + } + + debugf(NHRP_DEBUG_COMMON, + "Shortcut: %pFX is at proto %pSU dst_proto %pSU NBMA %pSU cie-holdtime %d", + &prefix, proto, &pp->dst_proto, nbma, + htons(cie->holding_time)); + + if (sockunion_family(nbma)) { + c = nhrp_cache_get(pp->ifp, proto, 1); + if (c) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: cache found, update binding"); + nhrp_cache_update_binding(c, NHRP_CACHE_DYNAMIC, + holding_time, + nhrp_peer_get(pp->ifp, nbma), + htons(cie->mtu), + nbma, + &cie_nbma); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: no cache for proto %pSU", proto); + } + + /* Update cache binding for dst_proto as well */ + if (sockunion_cmp(proto, &pp->dst_proto)) { + c_dst = nhrp_cache_get(pp->ifp, &pp->dst_proto, 1); + if (c_dst) { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: cache found, update binding"); + nhrp_cache_update_binding(c_dst, + NHRP_CACHE_DYNAMIC, + holding_time, + nhrp_peer_get(pp->ifp, nbma), + htons(cie->mtu), + nbma, + &cie_nbma); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: no cache for proto %pSU", + &pp->dst_proto); + } + } + } + + /* Update shortcut entry for subnet to protocol gw binding */ + if (c) { + ps = nhrp_shortcut_get(&prefix); + if (ps) { + ps->addr = s->addr; + debugf(NHRP_DEBUG_COMMON, + "Shortcut: calling update_binding"); + nhrp_shortcut_update_binding(ps, NHRP_CACHE_DYNAMIC, c, + holding_time); + } else { + debugf(NHRP_DEBUG_COMMON, + "Shortcut: proto diff but no ps"); + } + } else { + debugf(NHRP_DEBUG_COMMON, + "NO Shortcut because c NULL?%s or same proto?%s", + c ? "no" : "yes", + proto && pp && sockunion_same(proto, &pp->dst_proto) + ? "yes" + : "no"); + } + + debugf(NHRP_DEBUG_COMMON, "Shortcut: Resolution reply handled"); +} + +static void nhrp_shortcut_send_resolution_req(struct nhrp_shortcut *s) +{ + struct zbuf *zb; + struct nhrp_packet_header *hdr; + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_afi_data *if_ad; + struct nhrp_peer *peer; + struct nhrp_cie_header *cie; + struct nhrp_extension_header *ext; + + if (nhrp_route_address(NULL, &s->addr, NULL, &peer) + != NHRP_ROUTE_NBMA_NEXTHOP) + return; + + if (s->type == NHRP_CACHE_INVALID || s->type == NHRP_CACHE_NEGATIVE) + s->type = NHRP_CACHE_INCOMPLETE; + + ifp = peer->ifp; + nifp = ifp->info; + + /* Create request */ + zb = zbuf_alloc(1500); + hdr = nhrp_packet_push( + zb, NHRP_PACKET_RESOLUTION_REQUEST, &nifp->nbma, + &nifp->afi[family2afi(sockunion_family(&s->addr))].addr, + &s->addr); + hdr->u.request_id = + htonl(nhrp_reqid_alloc(&nhrp_packet_reqid, &s->reqid, + nhrp_shortcut_recv_resolution_rep)); + hdr->flags = htons(NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER + | NHRP_FLAG_RESOLUTION_AUTHORATIVE + | NHRP_FLAG_RESOLUTION_SOURCE_STABLE); + + /* RFC2332 - One or zero CIEs, if CIE is present contains: + * - Prefix length: widest acceptable prefix we accept (if U set, 0xff) + * - MTU: MTU of the source station + * - Holding Time: Max time to cache the source information + * */ + /* FIXME: push CIE for each local protocol address */ + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, NULL, NULL); + if_ad = &nifp->afi[family2afi(sockunion_family(&s->addr))]; + cie->prefix_length = (if_ad->flags & NHRP_IFF_REG_NO_UNIQUE) + ? 8 * sockunion_get_addrlen(&s->addr) + : 0xff; + cie->holding_time = htons(if_ad->holdtime); + cie->mtu = htons(if_ad->mtu); + debugf(NHRP_DEBUG_COMMON, + "Shortcut res_req: set cie ht to %u and mtu to %u. shortcut ht is %u", + ntohs(cie->holding_time), ntohs(cie->mtu), s->holding_time); + + nhrp_ext_request(zb, hdr, ifp); + + /* Cisco NAT detection extension */ + hdr->flags |= htons(NHRP_FLAG_RESOLUTION_NAT); + ext = nhrp_ext_push(zb, hdr, NHRP_EXTENSION_NAT_ADDRESS); + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) { + cie = nhrp_cie_push(zb, NHRP_CODE_SUCCESS, &nifp->nat_nbma, + &if_ad->addr); + cie->prefix_length = 8 * sockunion_get_addrlen(&if_ad->addr); + cie->mtu = htons(if_ad->mtu); + nhrp_ext_complete(zb, ext); + } + + nhrp_packet_complete(zb, hdr); + + nhrp_peer_send(peer, zb); + nhrp_peer_unref(peer); + zbuf_free(zb); +} + +void nhrp_shortcut_initiate(union sockunion *addr) +{ + struct prefix p; + struct nhrp_shortcut *s; + + if (!sockunion2hostprefix(addr, &p)) + return; + + s = nhrp_shortcut_get(&p); + if (s && s->type != NHRP_CACHE_INCOMPLETE) { + s->addr = *addr; + EVENT_OFF(s->t_timer); + event_add_timer(master, nhrp_shortcut_do_purge, s, 30, + &s->t_timer); + nhrp_shortcut_send_resolution_req(s); + } +} + +void nhrp_shortcut_init(void) +{ + shortcut_rib[AFI_IP] = route_table_init(); + shortcut_rib[AFI_IP6] = route_table_init(); +} + +void nhrp_shortcut_terminate(void) +{ + nhrp_shortcut_foreach(AFI_IP, nhrp_shortcut_delete, NULL); + nhrp_shortcut_foreach(AFI_IP6, nhrp_shortcut_delete, NULL); + route_table_finish(shortcut_rib[AFI_IP]); + route_table_finish(shortcut_rib[AFI_IP6]); +} + +void nhrp_shortcut_foreach(afi_t afi, + void (*cb)(struct nhrp_shortcut *, void *), + void *ctx) +{ + struct route_table *rt = shortcut_rib[afi]; + struct route_node *rn; + route_table_iter_t iter; + + if (!rt) + return; + + route_table_iter_init(&iter, rt); + while ((rn = route_table_iter_next(&iter)) != NULL) { + if (rn->info) + cb(rn->info, ctx); + } + route_table_iter_cleanup(&iter); +} + +struct purge_ctx { + const struct prefix *p; + int deleted; +}; + +void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force) +{ + EVENT_OFF(s->t_timer); + nhrp_reqid_free(&nhrp_packet_reqid, &s->reqid); + + if (force) { + /* Immediate purge on route with draw or pending shortcut */ + event_add_timer_msec(master, nhrp_shortcut_do_purge, s, 5, + &s->t_timer); + } else { + /* Soft expire - force immediate renewal, but purge + * in few seconds to make sure stale route is not + * used too long. In practice most purges are caused + * by hub bgp change, but target usually stays same. + * This allows to keep nhrp route up, and to not + * cause temporary rerouting via hubs causing latency + * jitter. */ + event_add_timer_msec(master, nhrp_shortcut_do_purge, s, 3000, + &s->t_timer); + s->expiring = 1; + nhrp_shortcut_check_use(s); + } +} + +static void nhrp_shortcut_purge_prefix(struct nhrp_shortcut *s, void *ctx) +{ + struct purge_ctx *pctx = ctx; + + if (prefix_match(pctx->p, s->p)) + nhrp_shortcut_purge(s, pctx->deleted || !s->cache); +} + +void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted) +{ + struct purge_ctx pctx = { + .p = p, .deleted = deleted, + }; + nhrp_shortcut_foreach(family2afi(PREFIX_FAMILY(p)), + nhrp_shortcut_purge_prefix, &pctx); +} diff --git a/nhrpd/nhrp_vc.c b/nhrpd/nhrp_vc.c new file mode 100644 index 0000000..6f3346c --- /dev/null +++ b/nhrpd/nhrp_vc.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP virtual connection + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "memory.h" +#include "stream.h" +#include "hash.h" +#include "frrevent.h" +#include "jhash.h" + +#include "nhrpd.h" +#include "os.h" + +DEFINE_MTYPE_STATIC(NHRPD, NHRP_VC, "NHRP virtual connection"); + +PREDECL_DLIST(childlist); + +struct child_sa { + uint32_t id; + struct nhrp_vc *vc; + struct childlist_item childlist_entry; +}; + +DECLARE_DLIST(childlist, struct child_sa, childlist_entry); + +static struct hash *nhrp_vc_hash; +static struct childlist_head childlist_head[512]; + +static unsigned int nhrp_vc_key(const void *peer_data) +{ + const struct nhrp_vc *vc = peer_data; + return jhash_2words(sockunion_hash(&vc->local.nbma), + sockunion_hash(&vc->remote.nbma), 0); +} + +static bool nhrp_vc_cmp(const void *cache_data, const void *key_data) +{ + const struct nhrp_vc *a = cache_data; + const struct nhrp_vc *b = key_data; + + return sockunion_same(&a->local.nbma, &b->local.nbma) + && sockunion_same(&a->remote.nbma, &b->remote.nbma); +} + +static void *nhrp_vc_alloc(void *data) +{ + struct nhrp_vc *vc, *key = data; + + vc = XMALLOC(MTYPE_NHRP_VC, sizeof(struct nhrp_vc)); + + *vc = (struct nhrp_vc){ + .local.nbma = key->local.nbma, + .remote.nbma = key->remote.nbma, + .notifier_list = + NOTIFIER_LIST_INITIALIZER(&vc->notifier_list), + }; + + return vc; +} + +static void nhrp_vc_free(void *data) +{ + XFREE(MTYPE_NHRP_VC, data); +} + +struct nhrp_vc *nhrp_vc_get(const union sockunion *src, + const union sockunion *dst, int create) +{ + struct nhrp_vc key; + key.local.nbma = *src; + key.remote.nbma = *dst; + return hash_get(nhrp_vc_hash, &key, create ? nhrp_vc_alloc : 0); +} + +static void nhrp_vc_check_delete(struct nhrp_vc *vc) +{ + if (vc->updating || vc->ipsec || notifier_active(&vc->notifier_list)) + return; + hash_release(nhrp_vc_hash, vc); + nhrp_vc_free(vc); +} + +static void nhrp_vc_update(struct nhrp_vc *vc, long cmd) +{ + vc->updating = 1; + notifier_call(&vc->notifier_list, cmd); + vc->updating = 0; + nhrp_vc_check_delete(vc); +} + +static void nhrp_vc_ipsec_reset(struct nhrp_vc *vc) +{ + vc->local.id[0] = 0; + vc->local.certlen = 0; + vc->remote.id[0] = 0; + vc->remote.certlen = 0; +} + +int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc) +{ + struct child_sa *sa = NULL, *lsa; + uint32_t child_hash = child_id % array_size(childlist_head); + int abort_migration = 0; + + frr_each (childlist, &childlist_head[child_hash], lsa) { + if (lsa->id == child_id) { + sa = lsa; + break; + } + } + + if (!sa) { + if (!vc) + return 0; + + sa = XMALLOC(MTYPE_NHRP_VC, sizeof(struct child_sa)); + + *sa = (struct child_sa){ + .id = child_id, + .vc = NULL, + }; + childlist_add_tail(&childlist_head[child_hash], sa); + } + + if (sa->vc == vc) + return 0; + + if (vc) { + /* Attach first to new VC */ + vc->ipsec++; + nhrp_vc_update(vc, NOTIFY_VC_IPSEC_CHANGED); + } + if (sa->vc && vc) { + /* Notify old VC of migration */ + sa->vc->abort_migration = 0; + debugf(NHRP_DEBUG_COMMON, "IPsec NBMA change of %pSU to %pSU", + &sa->vc->remote.nbma, &vc->remote.nbma); + nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_UPDATE_NBMA); + abort_migration = sa->vc->abort_migration; + } + if (sa->vc) { + /* Deattach old VC */ + sa->vc->ipsec--; + if (!sa->vc->ipsec) + nhrp_vc_ipsec_reset(sa->vc); + nhrp_vc_update(sa->vc, NOTIFY_VC_IPSEC_CHANGED); + } + + /* Update */ + sa->vc = vc; + if (!vc) { + childlist_del(&childlist_head[child_hash], sa); + XFREE(MTYPE_NHRP_VC, sa); + } + + return abort_migration; +} + +void nhrp_vc_notify_add(struct nhrp_vc *vc, struct notifier_block *n, + notifier_fn_t action) +{ + notifier_add(n, &vc->notifier_list, action); +} + +void nhrp_vc_notify_del(struct nhrp_vc *vc, struct notifier_block *n) +{ + notifier_del(n, &vc->notifier_list); + nhrp_vc_check_delete(vc); +} + + +struct nhrp_vc_iterator_ctx { + void (*cb)(struct nhrp_vc *, void *); + void *ctx; +}; + +static void nhrp_vc_iterator(struct hash_bucket *b, void *ctx) +{ + struct nhrp_vc_iterator_ctx *ic = ctx; + ic->cb(b->data, ic->ctx); +} + +void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx) +{ + struct nhrp_vc_iterator_ctx ic = { + .cb = cb, .ctx = ctx, + }; + hash_iterate(nhrp_vc_hash, nhrp_vc_iterator, &ic); +} + +void nhrp_vc_init(void) +{ + size_t i; + + nhrp_vc_hash = hash_create(nhrp_vc_key, nhrp_vc_cmp, "NHRP VC hash"); + for (i = 0; i < array_size(childlist_head); i++) + childlist_init(&childlist_head[i]); +} + +void nhrp_vc_reset(void) +{ + struct child_sa *sa; + size_t i; + + for (i = 0; i < array_size(childlist_head); i++) { + frr_each_safe (childlist, &childlist_head[i], sa) + nhrp_vc_ipsec_updown(sa->id, 0); + } +} + +void nhrp_vc_terminate(void) +{ + nhrp_vc_reset(); + hash_clean(nhrp_vc_hash, nhrp_vc_free); + hash_free(nhrp_vc_hash); +} diff --git a/nhrpd/nhrp_vty.c b/nhrpd/nhrp_vty.c new file mode 100644 index 0000000..f91fcb5 --- /dev/null +++ b/nhrpd/nhrp_vty.c @@ -0,0 +1,1271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP vty handling + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zebra.h" +#include "command.h" +#include "zclient.h" +#include "stream.h" +#include "filter.h" +#include "json.h" + +#include "nhrpd.h" +#include "netlink.h" + +static int nhrp_config_write(struct vty *vty); +static struct cmd_node zebra_node = { + .name = "zebra", + .node = ZEBRA_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = nhrp_config_write, +}; + +#define NHRP_DEBUG_FLAGS_CMD "" + +#define NHRP_DEBUG_FLAGS_STR \ + "All messages\n" \ + "Common messages (default)\n" \ + "Event manager messages\n" \ + "Interface messages\n" \ + "Kernel messages\n" \ + "Route messages\n" \ + "VICI messages\n" + +static const struct message debug_flags_desc[] = { + {NHRP_DEBUG_ALL, "all"}, {NHRP_DEBUG_COMMON, "common"}, + {NHRP_DEBUG_IF, "interface"}, {NHRP_DEBUG_KERNEL, "kernel"}, + {NHRP_DEBUG_ROUTE, "route"}, {NHRP_DEBUG_VICI, "vici"}, + {NHRP_DEBUG_EVENT, "event"}, {0}}; + +static const struct message interface_flags_desc[] = { + {NHRP_IFF_SHORTCUT, "shortcut"}, + {NHRP_IFF_REDIRECT, "redirect"}, + {NHRP_IFF_REG_NO_UNIQUE, "registration no-unique"}, + {0}}; + +static int nhrp_vty_return(struct vty *vty, int ret) +{ + static const char *const errmsgs[] = { + [NHRP_ERR_FAIL] = "Command failed", + [NHRP_ERR_NO_MEMORY] = "Out of memory", + [NHRP_ERR_UNSUPPORTED_INTERFACE] = + "NHRP not supported on this interface", + [NHRP_ERR_NHRP_NOT_ENABLED] = + "NHRP not enabled (set 'nhrp network-id' first)", + [NHRP_ERR_ENTRY_EXISTS] = "Entry exists already", + [NHRP_ERR_ENTRY_NOT_FOUND] = "Entry not found", + [NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH] = + "Protocol address family does not match command (ip/ipv6 mismatch)", + }; + const char *str = NULL; + char buf[256]; + + if (ret == NHRP_OK) + return CMD_SUCCESS; + + if (ret > 0 && ret <= NHRP_ERR_MAX) + if (errmsgs[ret]) + str = errmsgs[ret]; + + if (!str) { + str = buf; + snprintf(buf, sizeof(buf), "Unknown error %d", ret); + } + + vty_out(vty, "%% %s\n", str); + + return CMD_WARNING_CONFIG_FAILED; + ; +} + +static int toggle_flag(struct vty *vty, const struct message *flag_desc, + const char *name, int on_off, unsigned *flags) +{ + int i; + + for (i = 0; flag_desc[i].str != NULL; i++) { + if (strcmp(flag_desc[i].str, name) != 0) + continue; + if (on_off) + *flags |= flag_desc[i].key; + else + *flags &= ~flag_desc[i].key; + return CMD_SUCCESS; + } + + vty_out(vty, "%% Invalid value %s\n", name); + return CMD_WARNING_CONFIG_FAILED; + ; +} + +#ifndef NO_DEBUG + +DEFUN_NOSH(show_debugging_nhrp, show_debugging_nhrp_cmd, + "show debugging [nhrp]", + SHOW_STR + "Debugging information\n" + "NHRP configuration\n") +{ + int i; + + vty_out(vty, "NHRP debugging status:\n"); + + for (i = 0; debug_flags_desc[i].str != NULL; i++) { + if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) + continue; + if (!(debug_flags_desc[i].key & debug_flags)) + continue; + + vty_out(vty, " NHRP %s debugging is on\n", + debug_flags_desc[i].str); + } + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFUN(debug_nhrp, debug_nhrp_cmd, + "debug nhrp " NHRP_DEBUG_FLAGS_CMD, + "Enable debug messages for specific or all parts.\n" + "NHRP information\n" + NHRP_DEBUG_FLAGS_STR) +{ + return toggle_flag(vty, debug_flags_desc, argv[2]->text, 1, + &debug_flags); +} + +DEFUN(no_debug_nhrp, no_debug_nhrp_cmd, + "no debug nhrp " NHRP_DEBUG_FLAGS_CMD, + NO_STR + "Disable debug messages for specific or all parts.\n" + "NHRP information\n" + NHRP_DEBUG_FLAGS_STR) +{ + return toggle_flag(vty, debug_flags_desc, argv[3]->text, 0, + &debug_flags); +} + +#endif /* NO_DEBUG */ + +static int nhrp_config_write(struct vty *vty) +{ +#ifndef NO_DEBUG + if (debug_flags == NHRP_DEBUG_ALL) { + vty_out(vty, "debug nhrp all\n"); + } else { + int i; + + for (i = 0; debug_flags_desc[i].str != NULL; i++) { + if (debug_flags_desc[i].key == NHRP_DEBUG_ALL) + continue; + if (!(debug_flags & debug_flags_desc[i].key)) + continue; + vty_out(vty, "debug nhrp %s\n", + debug_flags_desc[i].str); + } + } + vty_out(vty, "!\n"); +#endif /* NO_DEBUG */ + + if (nhrp_event_socket_path) { + vty_out(vty, "nhrp event socket %s\n", nhrp_event_socket_path); + } + if (netlink_nflog_group) { + vty_out(vty, "nhrp nflog-group %d\n", netlink_nflog_group); + } + if (netlink_mcast_nflog_group) + vty_out(vty, "nhrp multicast-nflog-group %d\n", + netlink_mcast_nflog_group); + + return 0; +} + +#define IP_STR "IP information\n" +#define IPV6_STR "IPv6 information\n" +#define AFI_CMD "" +#define AFI_STR IP_STR IPV6_STR +#define NHRP_STR "Next Hop Resolution Protocol functions\n" + +static afi_t cmd_to_afi(const struct cmd_token *tok) +{ + return strcmp(tok->text, "ipv6") == 0 ? AFI_IP6 : AFI_IP; +} + +static const char *afi_to_cmd(afi_t afi) +{ + if (afi == AFI_IP6) + return "ipv6"; + return "ip"; +} + +DEFUN(nhrp_event_socket, nhrp_event_socket_cmd, + "nhrp event socket SOCKET", + NHRP_STR + "Event Manager commands\n" + "Event Manager unix socket path\n" + "Unix path for the socket\n") +{ + evmgr_set_socket(argv[3]->arg); + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_event_socket, no_nhrp_event_socket_cmd, + "no nhrp event socket [SOCKET]", + NO_STR + NHRP_STR + "Event Manager commands\n" + "Event Manager unix socket path\n" + "Unix path for the socket\n") +{ + evmgr_set_socket(NULL); + return CMD_SUCCESS; +} + +DEFUN(nhrp_nflog_group, nhrp_nflog_group_cmd, + "nhrp nflog-group (1-65535)", + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + uint32_t nfgroup; + + nfgroup = strtoul(argv[2]->arg, NULL, 10); + netlink_set_nflog_group(nfgroup); + + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_nflog_group, no_nhrp_nflog_group_cmd, + "no nhrp nflog-group [(1-65535)]", + NO_STR + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + netlink_set_nflog_group(0); + return CMD_SUCCESS; +} + +DEFUN(nhrp_multicast_nflog_group, nhrp_multicast_nflog_group_cmd, + "nhrp multicast-nflog-group (1-65535)", + NHRP_STR + "Specify NFLOG group number for Multicast Packets\n" + "NFLOG group number\n") +{ + uint32_t nfgroup; + + nfgroup = strtoul(argv[2]->arg, NULL, 10); + netlink_mcast_set_nflog_group(nfgroup); + + return CMD_SUCCESS; +} + +DEFUN(no_nhrp_multicast_nflog_group, no_nhrp_multicast_nflog_group_cmd, + "no nhrp multicast-nflog-group [(1-65535)]", + NO_STR + NHRP_STR + "Specify NFLOG group number\n" + "NFLOG group number\n") +{ + netlink_mcast_set_nflog_group(0); + return CMD_SUCCESS; +} + +DEFUN(tunnel_protection, tunnel_protection_cmd, + "tunnel protection vici profile PROFILE [fallback-profile FALLBACK]", + "NHRP/GRE integration\n" + "IPsec protection\n" + "VICI (StrongSwan)\n" + "IPsec profile\n" + "IPsec profile name\n" + "Fallback IPsec profile\n" + "Fallback IPsec profile name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + + nhrp_interface_set_protection(ifp, argv[4]->arg, + argc > 6 ? argv[6]->arg : NULL); + return CMD_SUCCESS; +} + +DEFUN(no_tunnel_protection, no_tunnel_protection_cmd, + "no tunnel protection", + NO_STR + "NHRP/GRE integration\n" + "IPsec protection\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + + nhrp_interface_set_protection(ifp, NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN(tunnel_source, tunnel_source_cmd, + "tunnel source INTERFACE", + "NHRP/GRE integration\n" + "Tunnel device binding tracking\n" + "Interface name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + nhrp_interface_set_source(ifp, argv[2]->arg); + return CMD_SUCCESS; +} + +DEFUN(no_tunnel_source, no_tunnel_source_cmd, + "no tunnel source", + "NHRP/GRE integration\n" + "Tunnel device binding tracking\n" + "Interface name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + nhrp_interface_set_source(ifp, NULL); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_network_id, if_nhrp_network_id_cmd, + AFI_CMD " nhrp network-id (1-4294967295)", + AFI_STR + NHRP_STR + "Enable NHRP and specify network-id\n" + "System local ID to specify interface group\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + nifp->afi[afi].network_id = strtoul(argv[3]->arg, NULL, 10); + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_network_id, if_no_nhrp_network_id_cmd, + "no " AFI_CMD " nhrp network-id [(1-4294967295)]", + NO_STR + AFI_STR + NHRP_STR + "Enable NHRP and specify network-id\n" + "System local ID to specify interface group\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + nifp->afi[afi].network_id = 0; + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_flags, if_nhrp_flags_cmd, + AFI_CMD " nhrp ", + AFI_STR + NHRP_STR + "Allow shortcut establishment\n" + "Send redirect notifications\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + return toggle_flag(vty, interface_flags_desc, argv[2]->text, 1, + &nifp->afi[afi].flags); +} + +DEFUN(if_no_nhrp_flags, if_no_nhrp_flags_cmd, + "no " AFI_CMD " nhrp ", + NO_STR + AFI_STR + NHRP_STR + "Allow shortcut establishment\n" + "Send redirect notifications\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + return toggle_flag(vty, interface_flags_desc, argv[3]->text, 0, + &nifp->afi[afi].flags); +} + +DEFUN(if_nhrp_reg_flags, if_nhrp_reg_flags_cmd, + AFI_CMD " nhrp registration no-unique", + AFI_STR + NHRP_STR + "Registration configuration\n" + "Don't set unique flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + char name[256]; + snprintf(name, sizeof(name), "registration %s", argv[3]->text); + return toggle_flag(vty, interface_flags_desc, name, 1, + &nifp->afi[afi].flags); +} + +DEFUN(if_no_nhrp_reg_flags, if_no_nhrp_reg_flags_cmd, + "no " AFI_CMD " nhrp registration no-unique", + NO_STR + AFI_STR + NHRP_STR + "Registration configuration\n" + "Don't set unique flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + char name[256]; + snprintf(name, sizeof(name), "registration %s", argv[4]->text); + return toggle_flag(vty, interface_flags_desc, name, 0, + &nifp->afi[afi].flags); +} + +DEFUN(if_nhrp_holdtime, if_nhrp_holdtime_cmd, + AFI_CMD " nhrp holdtime (1-65000)", + AFI_STR + NHRP_STR + "Specify NBMA address validity time\n" + "Time in seconds that NBMA addresses are advertised valid\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[0]); + + nifp->afi[afi].holdtime = strtoul(argv[3]->arg, NULL, 10); + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_holdtime, if_no_nhrp_holdtime_cmd, + "no " AFI_CMD " nhrp holdtime [(1-65000)]", + NO_STR + AFI_STR + NHRP_STR + "Specify NBMA address validity time\n" + "Time in seconds that NBMA addresses are advertised valid\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + afi_t afi = cmd_to_afi(argv[1]); + + nifp->afi[afi].holdtime = NHRPD_DEFAULT_HOLDTIME; + nhrp_interface_update(ifp); + + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_mtu, if_nhrp_mtu_cmd, + "ip nhrp mtu <(576-1500)|opennhrp>", + IP_STR + NHRP_STR + "Configure NHRP advertised MTU\n" + "MTU value\n" + "Advertise bound interface MTU similar to OpenNHRP\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + + if (argv[3]->arg[0] == 'o') { + nifp->afi[AFI_IP].configured_mtu = -1; + } else { + nifp->afi[AFI_IP].configured_mtu = + strtoul(argv[3]->arg, NULL, 10); + } + nhrp_interface_update_mtu(ifp, AFI_IP); + + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_mtu, if_no_nhrp_mtu_cmd, + "no ip nhrp mtu [(576-1500)|opennhrp]", + NO_STR + IP_STR + NHRP_STR + "Configure NHRP advertised MTU\n" + "MTU value\n" + "Advertise bound interface MTU similar to OpenNHRP\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct nhrp_interface *nifp = ifp->info; + + nifp->afi[AFI_IP].configured_mtu = 0; + nhrp_interface_update_mtu(ifp, AFI_IP); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_map, if_nhrp_map_cmd, + AFI_CMD " nhrp map ", + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "IPv4 NBMA address\n" + "Handle protocol address locally\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion proto_addr, nbma_addr; + struct nhrp_cache_config *cc; + struct nhrp_cache *c; + enum nhrp_cache_type type; + + if (str2sockunion(argv[3]->arg, &proto_addr) < 0 + || afi2family(afi) != sockunion_family(&proto_addr)) + return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); + + if (strmatch(argv[4]->text, "local")) + type = NHRP_CACHE_LOCAL; + else { + if (str2sockunion(argv[4]->arg, &nbma_addr) < 0) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + type = NHRP_CACHE_STATIC; + } + cc = nhrp_cache_config_get(ifp, &proto_addr, 1); + if (!cc) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + cc->nbma = nbma_addr; + cc->type = type; + /* gre layer not ready */ + if (ifp->ifindex == IFINDEX_INTERNAL) + return CMD_SUCCESS; + + c = nhrp_cache_get(ifp, &proto_addr, 1); + if (!c) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + + c->map = 1; + if (type == NHRP_CACHE_LOCAL) + nhrp_cache_update_binding(c, NHRP_CACHE_LOCAL, 0, NULL, 0, + NULL, NULL); + else + nhrp_cache_update_binding(c, NHRP_CACHE_STATIC, 0, + nhrp_peer_get(ifp, &nbma_addr), 0, + NULL, NULL); + return CMD_SUCCESS; +} + +DEFUN(if_no_nhrp_map, if_no_nhrp_map_cmd, + "no " AFI_CMD " nhrp map []", + NO_STR + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "IPv4 NBMA address\n" + "Handle protocol address locally\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion proto_addr, nbma_addr; + struct nhrp_cache_config *cc; + struct nhrp_cache *c; + + if (str2sockunion(argv[4]->arg, &proto_addr) < 0 + || afi2family(afi) != sockunion_family(&proto_addr)) + return nhrp_vty_return(vty, NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH); + + cc = nhrp_cache_config_get(ifp, &proto_addr, 0); + if (!cc) + return nhrp_vty_return(vty, NHRP_ERR_FAIL); + nhrp_cache_config_free(cc); + + c = nhrp_cache_get(ifp, &proto_addr, 0); + /* silently return */ + if (!c || !c->map) + return CMD_SUCCESS; + + nhrp_cache_update_binding(c, c->cur.type, -1, + nhrp_peer_get(ifp, &nbma_addr), 0, NULL, + NULL); + return CMD_SUCCESS; +} + +DEFUN(if_nhrp_map_multicast, if_nhrp_map_multicast_cmd, + AFI_CMD " nhrp map multicast ", + AFI_STR + NHRP_STR + "Multicast NBMA Configuration\n" + "Use this NBMA mapping for multicasts\n" + "IPv4 NBMA address\n" + "IPv6 NBMA address\n" + "Dynamically learn destinations from client registrations on hub\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion nbma_addr; + int ret; + + if (str2sockunion(argv[4]->arg, &nbma_addr) < 0) + sockunion_family(&nbma_addr) = AF_UNSPEC; + + ret = nhrp_multicast_add(ifp, afi, &nbma_addr); + + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_no_nhrp_map_multicast, if_no_nhrp_map_multicast_cmd, + "no " AFI_CMD " nhrp map multicast ", + NO_STR + AFI_STR + NHRP_STR + "Multicast NBMA Configuration\n" + "Use this NBMA mapping for multicasts\n" + "IPv4 NBMA address\n" + "IPv6 NBMA address\n" + "Dynamically learn destinations from client registrations on hub\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion nbma_addr; + int ret; + + if (str2sockunion(argv[5]->arg, &nbma_addr) < 0) + sockunion_family(&nbma_addr) = AF_UNSPEC; + + ret = nhrp_multicast_del(ifp, afi, &nbma_addr); + + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_nhrp_nhs, if_nhrp_nhs_cmd, + AFI_CMD " nhrp nhs nbma ", + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "Automatic detection of protocol address\n" + "NBMA address\n" + "IPv4 NBMA address\n" + "Fully qualified domain name for NBMA address(es)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[0]); + union sockunion proto_addr; + int ret; + + if (str2sockunion(argv[3]->arg, &proto_addr) < 0) + sockunion_family(&proto_addr) = AF_UNSPEC; + + ret = nhrp_nhs_add(ifp, afi, &proto_addr, argv[5]->arg); + return nhrp_vty_return(vty, ret); +} + +DEFUN(if_no_nhrp_nhs, if_no_nhrp_nhs_cmd, + "no " AFI_CMD " nhrp nhs nbma ", + NO_STR + AFI_STR + NHRP_STR + "Nexthop Server configuration\n" + "IPv4 protocol address\n" + "IPv6 protocol address\n" + "Automatic detection of protocol address\n" + "NBMA address\n" + "IPv4 NBMA address\n" + "Fully qualified domain name for NBMA address(es)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + afi_t afi = cmd_to_afi(argv[1]); + union sockunion proto_addr; + int ret; + + if (str2sockunion(argv[4]->arg, &proto_addr) < 0) + sockunion_family(&proto_addr) = AF_UNSPEC; + + ret = nhrp_nhs_del(ifp, afi, &proto_addr, argv[6]->arg); + return nhrp_vty_return(vty, ret); +} + +struct info_ctx { + struct vty *vty; + afi_t afi; + int count; + struct json_object *json; +}; + +static void show_ip_nhrp_cache(struct nhrp_cache *c, void *pctx) +{ + struct info_ctx *ctx = pctx; + struct vty *vty = ctx->vty; + char buf[3][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) + return; + + + if (!ctx->count && !ctx->json) { + vty_out(vty, "%-8s %-8s %-24s %-24s %-24s %-6s %s\n", "Iface", + "Type", "Protocol", "NBMA", "Claimed NBMA", "Flags", + "Identity"); + } + ctx->count++; + + sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0])); + if (c->cur.type == NHRP_CACHE_LOCAL) { + struct nhrp_interface *nifp = c->ifp->info; + + if (sockunion_family(&nifp->nbma) != AF_UNSPEC) { + sockunion2str(&nifp->nbma, buf[1], sizeof(buf[1])); + sockunion2str(&nifp->nbma, buf[2], sizeof(buf[2])); + } else { + snprintf(buf[1], sizeof(buf[1]), "-"); + snprintf(buf[2], sizeof(buf[2]), "-"); + } + + /* if we are behind NAT then update NBMA field */ + if (sockunion_family(&nifp->nat_nbma) != AF_UNSPEC) + sockunion2str(&nifp->nat_nbma, buf[1], sizeof(buf[1])); + } else { + if (c->cur.peer) + sockunion2str(&c->cur.peer->vc->remote.nbma, + buf[1], sizeof(buf[1])); + else + snprintf(buf[1], sizeof(buf[1]), "-"); + + if (c->cur.peer + && sockunion_family(&c->cur.remote_nbma_claimed) + != AF_UNSPEC) + sockunion2str(&c->cur.remote_nbma_claimed, + buf[2], sizeof(buf[2])); + else + snprintf(buf[2], sizeof(buf[2]), "-"); + } + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "interface", c->ifp->name); + json_object_string_add(json, "type", + nhrp_cache_type_str[c->cur.type]); + json_object_string_add(json, "protocol", buf[0]); + json_object_string_add(json, "nbma", buf[1]); + json_object_string_add(json, "claimed_nbma", buf[2]); + + if (c->used) + json_object_boolean_true_add(json, "used"); + else + json_object_boolean_false_add(json, "used"); + + if (c->t_timeout) + json_object_boolean_true_add(json, "timeout"); + else + json_object_boolean_false_add(json, "timeout"); + + if (c->t_auth) + json_object_boolean_true_add(json, "auth"); + else + json_object_boolean_false_add(json, "auth"); + + if (c->cur.peer) + json_object_string_add(json, "identity", + c->cur.peer->vc->remote.id); + else + json_object_string_add(json, "identity", "-"); + + json_object_array_add(ctx->json, json); + return; + } + vty_out(ctx->vty, "%-8s %-8s %-24s %-24s %-24s %c%c%c %s\n", + c->ifp->name, + nhrp_cache_type_str[c->cur.type], + buf[0], buf[1], buf[2], + c->used ? 'U' : ' ', c->t_timeout ? 'T' : ' ', + c->t_auth ? 'A' : ' ', + c->cur.peer ? c->cur.peer->vc->remote.id : "-"); +} + +static void show_ip_nhrp_nhs(struct nhrp_nhs *n, struct nhrp_registration *reg, + void *pctx) +{ + struct info_ctx *ctx = pctx; + struct vty *vty = ctx->vty; + char buf[2][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (!ctx->count && !ctx->json) { + vty_out(vty, "%-8s %-24s %-16s %-16s\n", "Iface", "FQDN", + "NBMA", "Protocol"); + } + ctx->count++; + + if (reg && reg->peer) + sockunion2str(®->peer->vc->remote.nbma, buf[0], + sizeof(buf[0])); + else + snprintf(buf[0], sizeof(buf[0]), "-"); + sockunion2str(reg ? ®->proto_addr : &n->proto_addr, buf[1], + sizeof(buf[1])); + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "interface", n->ifp->name); + json_object_string_add(json, "fqdn", n->nbma_fqdn); + json_object_string_add(json, "nbma", buf[0]); + json_object_string_add(json, "protocol", buf[1]); + + json_object_array_add(ctx->json, json); + return; + } + + vty_out(vty, "%-8s %-24s %-16s %-16s\n", n->ifp->name, n->nbma_fqdn, + buf[0], buf[1]); +} + +static void show_ip_nhrp_shortcut(struct nhrp_shortcut *s, void *pctx) +{ + struct info_ctx *ctx = pctx; + struct nhrp_cache *c; + struct vty *vty = ctx->vty; + char buf1[PREFIX_STRLEN], buf2[SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + if (!ctx->count && !ctx->json) { + vty_out(vty, "%-8s %-24s %-24s %s\n", "Type", "Prefix", "Via", + "Identity"); + } + ctx->count++; + + c = s->cache; + buf2[0] = '\0'; + if (c) + sockunion2str(&c->remote_addr, buf2, sizeof(buf2)); + prefix2str(s->p, buf1, sizeof(buf1)); + + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "type", + nhrp_cache_type_str[s->type]); + json_object_string_add(json, "prefix", buf1); + + if (c) + json_object_string_add(json, "via", buf2); + + if (c && c->cur.peer) + json_object_string_add(json, "identity", + c->cur.peer->vc->remote.id); + else + json_object_string_add(json, "identity", ""); + + json_object_array_add(ctx->json, json); + return; + } + + vty_out(ctx->vty, "%-8s %-24s %-24s %s\n", + nhrp_cache_type_str[s->type], + buf1, buf2, + (c && c->cur.peer) ? c->cur.peer->vc->remote.id : ""); +} + +static void show_ip_opennhrp_cache(struct nhrp_cache *c, void *pctx) +{ + struct info_ctx *ctx = pctx; + char buf[3][SU_ADDRSTRLEN]; + struct json_object *json = NULL; + + + if (ctx->afi != family2afi(sockunion_family(&c->remote_addr))) + return; + + sockunion2str(&c->remote_addr, buf[0], sizeof(buf[0])); + if (c->cur.peer) + sockunion2str(&c->cur.peer->vc->remote.nbma, buf[1], + sizeof(buf[1])); + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + sockunion2str(&c->cur.remote_nbma_natoa, buf[2], + sizeof(buf[2])); + if (ctx->json) { + json = json_object_new_object(); + json_object_string_add(json, "type", + nhrp_cache_type_str[c->cur.type]); + + if (c->cur.peer && c->cur.peer->online) + json_object_boolean_true_add(json, "up"); + else + json_object_boolean_false_add(json, "up"); + + if (c->used) + json_object_boolean_true_add(json, "used"); + else + json_object_boolean_false_add(json, "used"); + + json_object_string_add(json, "protocolAddress", buf[0]); + json_object_int_add(json, "protocolAddressSize", + 8 * family2addrsize(sockunion_family + (&c->remote_addr))); + + if (c->cur.peer) + json_object_string_add(json, "nbmaAddress", buf[1]); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + json_object_string_add(json, "nbmaNatOaAddress", + buf[2]); + + json_object_array_add(ctx->json, json); + return; + } + vty_out(ctx->vty, + "Type: %s\n" + "Flags:%s%s\n" + "Protocol-Address: %s/%zu\n", + nhrp_cache_type_str[c->cur.type], + (c->cur.peer && c->cur.peer->online) ? " up" : "", + c->used ? " used" : "", + buf[0], + 8 * family2addrsize(sockunion_family(&c->remote_addr))); + + if (c->cur.peer) + vty_out(ctx->vty, "NBMA-Address: %s\n", buf[1]); + + if (sockunion_family(&c->cur.remote_nbma_natoa) != AF_UNSPEC) + vty_out(ctx->vty, "NBMA-NAT-OA-Address: %s\n", buf[2]); + + vty_out(ctx->vty, "\n\n"); +} + +DEFUN(show_ip_nhrp, show_ip_nhrp_cmd, + "show " AFI_CMD " nhrp [cache|nhs|shortcut|opennhrp] [json]", + SHOW_STR + AFI_STR + "NHRP information\n" + "Forwarding cache information\n" + "Next hop server information\n" + "Shortcut information\n" + "opennhrpctl style cache dump\n" + JSON_STR) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct info_ctx ctx = { + .vty = vty, .afi = cmd_to_afi(argv[1]), .json = NULL + }; + bool uj = use_json(argc, argv); + struct json_object *json_path = NULL; + struct json_object *json_vrf = NULL, *json_vrf_path = NULL; + int ret = CMD_SUCCESS; + + if (uj) { + json_vrf = json_object_new_object(); + json_vrf_path = json_object_new_object(); + json_path = json_object_new_array(); + ctx.json = json_path; + } + if (argc <= 3 || argv[3]->text[0] == 'c') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, show_ip_nhrp_cache, &ctx); + } else if (argv[3]->text[0] == 'n') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_nhs_foreach(ifp, ctx.afi, show_ip_nhrp_nhs, &ctx); + } else if (argv[3]->text[0] == 's') { + nhrp_shortcut_foreach(ctx.afi, show_ip_nhrp_shortcut, &ctx); + } else { + if (!ctx.json) + vty_out(vty, "Status: ok\n\n"); + else + json_object_string_add(json_vrf, "status", "ok"); + + ctx.count++; + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, show_ip_opennhrp_cache, &ctx); + } + + if (uj) + json_object_int_add(json_vrf, "entriesCount", ctx.count); + if (!ctx.count) { + if (!ctx.json) + vty_out(vty, "%% No entries\n"); + ret = CMD_WARNING; + } + if (uj) { + json_object_object_add(json_vrf_path, "attr", json_vrf); + json_object_object_add(json_vrf_path, "table", ctx.json); + vty_json(vty, json_vrf_path); + } + return ret; +} + +struct dmvpn_cfg { + struct vty *vty; + struct json_object *json; +}; + +static void show_dmvpn_entry(struct nhrp_vc *vc, void *ctx) +{ + struct dmvpn_cfg *ctxt = ctx; + struct vty *vty; + struct json_object *json = NULL; + + if (!ctxt || !ctxt->vty) + return; + vty = ctxt->vty; + if (ctxt->json) { + json = json_object_new_object(); + json_object_string_addf(json, "src", "%pSU", &vc->local.nbma); + json_object_string_addf(json, "dst", "%pSU", &vc->remote.nbma); + + if (notifier_active(&vc->notifier_list)) + json_object_boolean_true_add(json, "notifierActive"); + else + json_object_boolean_false_add(json, "notifierActive"); + + json_object_int_add(json, "sas", vc->ipsec); + json_object_string_add(json, "identity", vc->remote.id); + json_object_array_add(ctxt->json, json); + } else { + vty_out(vty, "%-24pSU %-24pSU %c %-4d %-24s\n", + &vc->local.nbma, &vc->remote.nbma, + notifier_active(&vc->notifier_list) ? 'n' : ' ', + vc->ipsec, vc->remote.id); + } +} + +DEFUN(show_dmvpn, show_dmvpn_cmd, + "show dmvpn [json]", + SHOW_STR + "DMVPN information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct dmvpn_cfg ctxt; + struct json_object *json_path = NULL; + + ctxt.vty = vty; + if (!uj) { + ctxt.json = NULL; + vty_out(vty, "%-24s %-24s %-6s %-4s %-24s\n", + "Src", "Dst", "Flags", "SAs", "Identity"); + } else { + json_path = json_object_new_array(); + ctxt.json = json_path; + } + nhrp_vc_foreach(show_dmvpn_entry, &ctxt); + if (uj) + vty_json(vty, json_path); + return CMD_SUCCESS; +} + +static void clear_nhrp_cache(struct nhrp_cache *c, void *data) +{ + struct info_ctx *ctx = data; + if (c->cur.type <= NHRP_CACHE_DYNAMIC) { + nhrp_cache_update_binding(c, c->cur.type, -1, NULL, 0, NULL, + NULL); + if (ctx) + ctx->count++; + } +} + +static void clear_nhrp_shortcut(struct nhrp_shortcut *s, void *data) +{ + struct info_ctx *ctx = data; + nhrp_shortcut_purge(s, 1); + ctx->count++; +} + +DEFUN(clear_nhrp, clear_nhrp_cmd, + "clear " AFI_CMD " nhrp ", + CLEAR_STR + AFI_STR + NHRP_STR + "Dynamic cache entries\n" + "Shortcut entries\n") +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + struct info_ctx ctx = { + .vty = vty, .afi = cmd_to_afi(argv[1]), .count = 0, + }; + + if (argc <= 3 || argv[3]->text[0] == 'c') { + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, clear_nhrp_cache, &ctx); + } else { + nhrp_shortcut_foreach(ctx.afi, clear_nhrp_shortcut, &ctx); + /* Clear cache also because when a shortcut is cleared then its + * cache entry should be cleared as well (otherwise traffic + * continues via the shortcut path) + */ + FOR_ALL_INTERFACES (vrf, ifp) + nhrp_cache_foreach(ifp, clear_nhrp_cache, NULL); + } + + if (!ctx.count) { + vty_out(vty, "%% No entries\n"); + return CMD_WARNING; + } + + vty_out(vty, "%% %d entries cleared\n", ctx.count); + return CMD_SUCCESS; +} + +struct write_map_ctx { + struct vty *vty; + int family; + const char *aficmd; +}; + +static void interface_config_write_nhrp_map(struct nhrp_cache_config *c, + void *data) +{ + struct write_map_ctx *ctx = data; + struct vty *vty = ctx->vty; + + if (sockunion_family(&c->remote_addr) != ctx->family) + return; + + vty_out(vty, " %s nhrp map %pSU ", ctx->aficmd, &c->remote_addr); + if (c->type == NHRP_CACHE_LOCAL) + vty_out(vty, "local\n"); + else + vty_out(vty, "%pSU\n", &c->nbma); +} + +static int interface_config_write(struct vty *vty) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct write_map_ctx mapctx; + struct interface *ifp; + struct nhrp_interface *nifp; + struct nhrp_nhs *nhs; + struct nhrp_multicast *mcast; + const char *aficmd; + afi_t afi; + int i; + + FOR_ALL_INTERFACES (vrf, ifp) { + if_vty_config_start(vty, ifp); + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + + nifp = ifp->info; + if (nifp->ipsec_profile) { + vty_out(vty, " tunnel protection vici profile %s", + nifp->ipsec_profile); + if (nifp->ipsec_fallback_profile) + vty_out(vty, " fallback-profile %s", + nifp->ipsec_fallback_profile); + vty_out(vty, "\n"); + } + if (nifp->source) + vty_out(vty, " tunnel source %s\n", nifp->source); + + for (afi = 0; afi < AFI_MAX; afi++) { + struct nhrp_afi_data *ad = &nifp->afi[afi]; + + aficmd = afi_to_cmd(afi); + + if (ad->network_id) + vty_out(vty, " %s nhrp network-id %u\n", aficmd, + ad->network_id); + + if (ad->holdtime != NHRPD_DEFAULT_HOLDTIME) + vty_out(vty, " %s nhrp holdtime %u\n", aficmd, + ad->holdtime); + + if (ad->configured_mtu < 0) + vty_out(vty, " %s nhrp mtu opennhrp\n", aficmd); + else if (ad->configured_mtu) + vty_out(vty, " %s nhrp mtu %u\n", aficmd, + ad->configured_mtu); + + for (i = 0; interface_flags_desc[i].str != NULL; i++) { + if (!(ad->flags & interface_flags_desc[i].key)) + continue; + vty_out(vty, " %s nhrp %s\n", aficmd, + interface_flags_desc[i].str); + } + + mapctx = (struct write_map_ctx){ + .vty = vty, + .family = afi2family(afi), + .aficmd = aficmd, + }; + nhrp_cache_config_foreach( + ifp, interface_config_write_nhrp_map, &mapctx); + + frr_each (nhrp_nhslist, &ad->nhslist_head, nhs) { + vty_out(vty, " %s nhrp nhs ", aficmd); + if (sockunion_family(&nhs->proto_addr) + == AF_UNSPEC) + vty_out(vty, "dynamic"); + else + vty_out(vty, "%pSU", &nhs->proto_addr); + vty_out(vty, " nbma %s\n", nhs->nbma_fqdn); + } + + frr_each (nhrp_mcastlist, &ad->mcastlist_head, mcast) { + vty_out(vty, " %s nhrp map multicast ", aficmd); + if (sockunion_family(&mcast->nbma_addr) + == AF_UNSPEC) + vty_out(vty, "dynamic\n"); + else + vty_out(vty, "%pSU\n", + &mcast->nbma_addr); + } + } + + if_vty_config_end(vty); + } + + return 0; +} + +void nhrp_config_init(void) +{ + install_node(&zebra_node); + install_default(ZEBRA_NODE); + + /* access-list commands */ + access_list_init(); + + /* global commands */ + install_element(VIEW_NODE, &show_ip_nhrp_cmd); + install_element(VIEW_NODE, &show_dmvpn_cmd); + install_element(ENABLE_NODE, &clear_nhrp_cmd); + + install_element(ENABLE_NODE, &show_debugging_nhrp_cmd); + + install_element(ENABLE_NODE, &debug_nhrp_cmd); + install_element(ENABLE_NODE, &no_debug_nhrp_cmd); + + install_element(CONFIG_NODE, &debug_nhrp_cmd); + install_element(CONFIG_NODE, &no_debug_nhrp_cmd); + + install_element(CONFIG_NODE, &nhrp_event_socket_cmd); + install_element(CONFIG_NODE, &no_nhrp_event_socket_cmd); + install_element(CONFIG_NODE, &nhrp_nflog_group_cmd); + install_element(CONFIG_NODE, &no_nhrp_nflog_group_cmd); + install_element(CONFIG_NODE, &nhrp_multicast_nflog_group_cmd); + install_element(CONFIG_NODE, &no_nhrp_multicast_nflog_group_cmd); + + vrf_cmd_init(NULL); + + /* interface specific commands */ + if_cmd_init(interface_config_write); + install_element(INTERFACE_NODE, &tunnel_protection_cmd); + install_element(INTERFACE_NODE, &no_tunnel_protection_cmd); + install_element(INTERFACE_NODE, &tunnel_source_cmd); + install_element(INTERFACE_NODE, &no_tunnel_source_cmd); + install_element(INTERFACE_NODE, &if_nhrp_network_id_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_network_id_cmd); + install_element(INTERFACE_NODE, &if_nhrp_holdtime_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_holdtime_cmd); + install_element(INTERFACE_NODE, &if_nhrp_mtu_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_mtu_cmd); + install_element(INTERFACE_NODE, &if_nhrp_flags_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_flags_cmd); + install_element(INTERFACE_NODE, &if_nhrp_reg_flags_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_reg_flags_cmd); + install_element(INTERFACE_NODE, &if_nhrp_map_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_map_cmd); + install_element(INTERFACE_NODE, &if_nhrp_map_multicast_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_map_multicast_cmd); + install_element(INTERFACE_NODE, &if_nhrp_nhs_cmd); + install_element(INTERFACE_NODE, &if_no_nhrp_nhs_cmd); +} diff --git a/nhrpd/nhrpd.h b/nhrpd/nhrpd.h new file mode 100644 index 0000000..e389b74 --- /dev/null +++ b/nhrpd/nhrpd.h @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* NHRP daemon internal structures and function prototypes + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifndef NHRPD_H +#define NHRPD_H + +#include "zbuf.h" +#include "zclient.h" +#include "debug.h" +#include "memory.h" +#include "resolver.h" + +DECLARE_MGROUP(NHRPD); + +#define NHRPD_DEFAULT_HOLDTIME 7200 + +#define NHRP_DEFAULT_CONFIG "nhrpd.conf" + +extern struct event_loop *master; + +enum { NHRP_OK = 0, + NHRP_ERR_FAIL, + NHRP_ERR_NO_MEMORY, + NHRP_ERR_UNSUPPORTED_INTERFACE, + NHRP_ERR_NHRP_NOT_ENABLED, + NHRP_ERR_ENTRY_EXISTS, + NHRP_ERR_ENTRY_NOT_FOUND, + NHRP_ERR_PROTOCOL_ADDRESS_MISMATCH, + __NHRP_ERR_MAX }; +#define NHRP_ERR_MAX (__NHRP_ERR_MAX - 1) + +struct notifier_block; + +typedef void (*notifier_fn_t)(struct notifier_block *, unsigned long); + +PREDECL_DLIST(notifier_list); + +struct notifier_block { + struct notifier_list_item notifier_entry; + notifier_fn_t action; +}; + +DECLARE_DLIST(notifier_list, struct notifier_block, notifier_entry); + +struct notifier_list { + struct notifier_list_head head; +}; + +#define NOTIFIER_LIST_INITIALIZER(l) \ + { \ + .head = INIT_DLIST((l)->head) \ + } + +static inline void notifier_init(struct notifier_list *l) +{ + notifier_list_init(&l->head); +} + +static inline void notifier_add(struct notifier_block *n, + struct notifier_list *l, notifier_fn_t action) +{ + n->action = action; + notifier_list_add_tail(&l->head, n); +} + +static inline void notifier_del(struct notifier_block *n, + struct notifier_list *l) +{ + notifier_list_del(&l->head, n); +} + +static inline void notifier_call(struct notifier_list *l, int cmd) +{ + struct notifier_block *n; + + frr_each_safe (notifier_list, &l->head, n) + n->action(n, cmd); +} + +static inline int notifier_active(struct notifier_list *l) +{ + return notifier_list_count(&l->head) > 0; +} + +extern struct hash *nhrp_gre_list; + +void nhrp_zebra_init(void); +void nhrp_zebra_terminate(void); +void nhrp_send_zebra_configure_arp(struct interface *ifp, int family); +void nhrp_send_zebra_nbr(union sockunion *in, + union sockunion *out, + struct interface *ifp); + +void nhrp_send_zebra_gre_source_set(struct interface *ifp, + unsigned int link_idx, + vrf_id_t link_vrf_id); + +extern int nhrp_send_zebra_gre_request(struct interface *ifp); +extern struct nhrp_gre_info *nhrp_gre_info_alloc(struct nhrp_gre_info *p); + +struct zbuf; +struct nhrp_vc; +struct nhrp_cache; +struct nhrp_nhs; +struct nhrp_interface; + +#define MAX_ID_LENGTH 64 +#define MAX_CERT_LENGTH 2048 + +enum nhrp_notify_type { + NOTIFY_INTERFACE_UP, + NOTIFY_INTERFACE_DOWN, + NOTIFY_INTERFACE_CHANGED, + NOTIFY_INTERFACE_ADDRESS_CHANGED, + NOTIFY_INTERFACE_NBMA_CHANGED, + NOTIFY_INTERFACE_MTU_CHANGED, + NOTIFY_INTERFACE_IPSEC_CHANGED, + + NOTIFY_VC_IPSEC_CHANGED, + NOTIFY_VC_IPSEC_UPDATE_NBMA, + + NOTIFY_PEER_UP, + NOTIFY_PEER_DOWN, + NOTIFY_PEER_IFCONFIG_CHANGED, + NOTIFY_PEER_MTU_CHANGED, + NOTIFY_PEER_NBMA_CHANGING, + + NOTIFY_CACHE_UP, + NOTIFY_CACHE_DOWN, + NOTIFY_CACHE_DELETE, + NOTIFY_CACHE_USED, + NOTIFY_CACHE_BINDING_CHANGE, +}; + +struct nhrp_vc { + struct notifier_list notifier_list; + uint32_t ipsec; + uint32_t ike_uniqueid; + uint8_t updating; + uint8_t abort_migration; + + struct nhrp_vc_peer { + union sockunion nbma; + char id[MAX_ID_LENGTH]; + uint16_t certlen; + uint8_t cert[MAX_CERT_LENGTH]; + } local, remote; +}; + +enum nhrp_route_type { + NHRP_ROUTE_BLACKHOLE, + NHRP_ROUTE_LOCAL, + NHRP_ROUTE_NBMA_NEXTHOP, + NHRP_ROUTE_OFF_NBMA, +}; + +struct nhrp_peer { + unsigned int ref; + unsigned online : 1; + unsigned requested : 1; + unsigned fallback_requested : 1; + unsigned prio : 1; + struct notifier_list notifier_list; + struct interface *ifp; + struct nhrp_vc *vc; + struct event *t_fallback; + struct notifier_block vc_notifier, ifp_notifier; + struct event *t_timer; +}; + +struct nhrp_packet_parser { + struct interface *ifp; + struct nhrp_afi_data *if_ad; + struct nhrp_peer *peer; + struct zbuf *pkt; + struct zbuf payload; + struct zbuf extensions; + struct nhrp_packet_header *hdr; + enum nhrp_route_type route_type; + struct prefix route_prefix; + union sockunion src_nbma, src_proto, dst_proto; +}; + +struct nhrp_reqid_pool { + struct hash *reqid_hash; + uint32_t next_request_id; +}; + +struct nhrp_reqid { + uint32_t request_id; + void (*cb)(struct nhrp_reqid *, void *); +}; + +extern struct nhrp_reqid_pool nhrp_packet_reqid; +extern struct nhrp_reqid_pool nhrp_event_reqid; + +enum nhrp_cache_type { + NHRP_CACHE_INVALID = 0, + NHRP_CACHE_INCOMPLETE, + NHRP_CACHE_NEGATIVE, + NHRP_CACHE_CACHED, + NHRP_CACHE_DYNAMIC, + NHRP_CACHE_NHS, + NHRP_CACHE_STATIC, + NHRP_CACHE_LOCAL, + NHRP_CACHE_NUM_TYPES +}; + +extern const char *const nhrp_cache_type_str[]; +extern unsigned long nhrp_cache_counts[NHRP_CACHE_NUM_TYPES]; + +struct nhrp_cache_config { + struct interface *ifp; + union sockunion remote_addr; + enum nhrp_cache_type type; + union sockunion nbma; +}; + +struct nhrp_cache { + struct interface *ifp; + union sockunion remote_addr; + + unsigned map : 1; + unsigned used : 1; + unsigned route_installed : 1; + unsigned nhrp_route_installed : 1; + + struct notifier_block peer_notifier; + struct notifier_block newpeer_notifier; + struct notifier_list notifier_list; + struct nhrp_reqid eventid; + struct event *t_timeout; + struct event *t_auth; + + struct { + enum nhrp_cache_type type; + union sockunion remote_nbma_natoa; + union sockunion remote_nbma_claimed; + struct nhrp_peer *peer; + time_t expires; + uint32_t mtu; + int holding_time; + } cur, new; +}; + +struct nhrp_shortcut { + struct prefix *p; + union sockunion addr; + + struct nhrp_reqid reqid; + struct event *t_timer; + + enum nhrp_cache_type type; + unsigned int holding_time; + unsigned route_installed : 1; + unsigned expiring : 1; + + struct nhrp_cache *cache; + struct notifier_block cache_notifier; +}; + +PREDECL_DLIST(nhrp_nhslist); +PREDECL_DLIST(nhrp_mcastlist); +PREDECL_DLIST(nhrp_reglist); + +struct nhrp_nhs { + struct interface *ifp; + struct nhrp_nhslist_item nhslist_entry; + + unsigned hub : 1; + afi_t afi; + union sockunion proto_addr; + const char *nbma_fqdn; /* IP-address or FQDN */ + + struct event *t_resolve; + struct resolver_query dns_resolve; + struct nhrp_reglist_head reglist_head; +}; + +DECLARE_DLIST(nhrp_nhslist, struct nhrp_nhs, nhslist_entry); + +struct nhrp_multicast { + struct interface *ifp; + struct nhrp_mcastlist_item mcastlist_entry; + afi_t afi; + union sockunion nbma_addr; /* IP-address */ +}; + +DECLARE_DLIST(nhrp_mcastlist, struct nhrp_multicast, mcastlist_entry); + +struct nhrp_registration { + struct nhrp_reglist_item reglist_entry; + struct event *t_register; + struct nhrp_nhs *nhs; + struct nhrp_reqid reqid; + unsigned int timeout; + unsigned mark : 1; + union sockunion proto_addr; + struct nhrp_peer *peer; + struct notifier_block peer_notifier; +}; + +DECLARE_DLIST(nhrp_reglist, struct nhrp_registration, reglist_entry); + +#define NHRP_IFF_SHORTCUT 0x0001 +#define NHRP_IFF_REDIRECT 0x0002 +#define NHRP_IFF_REG_NO_UNIQUE 0x0100 + +struct nhrp_interface { + struct interface *ifp; + + unsigned enabled : 1; + + char *ipsec_profile, *ipsec_fallback_profile, *source; + union sockunion nbma; + union sockunion nat_nbma; + unsigned int link_idx; + unsigned int link_vrf_id; + uint32_t i_grekey; + uint32_t o_grekey; + + struct hash *peer_hash; + struct hash *cache_config_hash; + struct hash *cache_hash; + + struct notifier_list notifier_list; + + struct interface *nbmaifp; + struct notifier_block nbmanifp_notifier; + + struct nhrp_afi_data { + unsigned flags; + unsigned short configured : 1; + union sockunion addr; + uint32_t network_id; + short configured_mtu; + unsigned short mtu; + unsigned int holdtime; + struct nhrp_nhslist_head nhslist_head; + struct nhrp_mcastlist_head mcastlist_head; + } afi[AFI_MAX]; +}; + +struct nhrp_gre_info { + ifindex_t ifindex; + struct in_addr vtep_ip; /* IFLA_GRE_LOCAL */ + struct in_addr vtep_ip_remote; /* IFLA_GRE_REMOTE */ + uint32_t ikey; + uint32_t okey; + ifindex_t ifindex_link; /* Interface index of interface + * linked with GRE + */ + vrf_id_t vrfid_link; +}; + +extern struct zebra_privs_t nhrpd_privs; + +int sock_open_unix(const char *path); + +void nhrp_interface_init(void); +void nhrp_interface_update(struct interface *ifp); +void nhrp_interface_update_arp(struct interface *ifp, bool arp_enable); +void nhrp_interface_update_mtu(struct interface *ifp, afi_t afi); +void nhrp_interface_update_nbma(struct interface *ifp, + struct nhrp_gre_info *gre_info); + +int nhrp_interface_add(ZAPI_CALLBACK_ARGS); +int nhrp_interface_delete(ZAPI_CALLBACK_ARGS); +int nhrp_interface_up(ZAPI_CALLBACK_ARGS); +int nhrp_interface_down(ZAPI_CALLBACK_ARGS); +int nhrp_interface_address_add(ZAPI_CALLBACK_ARGS); +int nhrp_interface_address_delete(ZAPI_CALLBACK_ARGS); +int nhrp_neighbor_operation(ZAPI_CALLBACK_ARGS); +int nhrp_gre_update(ZAPI_CALLBACK_ARGS); + +void nhrp_interface_notify_add(struct interface *ifp, struct notifier_block *n, + notifier_fn_t fn); +void nhrp_interface_notify_del(struct interface *ifp, struct notifier_block *n); +void nhrp_interface_set_protection(struct interface *ifp, const char *profile, + const char *fallback_profile); +void nhrp_interface_set_source(struct interface *ifp, const char *ifname); +extern int nhrp_ifp_create(struct interface *ifp); +extern int nhrp_ifp_up(struct interface *ifp); +extern int nhrp_ifp_down(struct interface *ifp); +extern int nhrp_ifp_destroy(struct interface *ifp); + +int nhrp_nhs_add(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn); +int nhrp_nhs_del(struct interface *ifp, afi_t afi, union sockunion *proto_addr, + const char *nbma_fqdn); +int nhrp_nhs_free(struct nhrp_interface *nifp, afi_t afi, struct nhrp_nhs *nhs); +void nhrp_nhs_terminate(void); +void nhrp_nhs_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_nhs *, struct nhrp_registration *, + void *), + void *ctx); +void nhrp_nhs_interface_del(struct interface *ifp); + +int nhrp_multicast_add(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr); +int nhrp_multicast_del(struct interface *ifp, afi_t afi, + union sockunion *nbma_addr); +void nhrp_multicast_interface_del(struct interface *ifp); +void nhrp_multicast_foreach(struct interface *ifp, afi_t afi, + void (*cb)(struct nhrp_multicast *, void *), + void *ctx); +void netlink_mcast_set_nflog_group(int nlgroup); + +void nhrp_route_update_nhrp(const struct prefix *p, struct interface *ifp); +void nhrp_route_announce(int add, enum nhrp_cache_type type, + const struct prefix *p, struct interface *ifp, + const union sockunion *nexthop, uint32_t mtu); +int nhrp_route_read(ZAPI_CALLBACK_ARGS); +int nhrp_route_get_nexthop(const union sockunion *addr, struct prefix *p, + union sockunion *via, struct interface **ifp); +enum nhrp_route_type nhrp_route_address(struct interface *in_ifp, + union sockunion *addr, struct prefix *p, + struct nhrp_peer **peer); + +void nhrp_config_init(void); + +void nhrp_shortcut_init(void); +void nhrp_shortcut_terminate(void); +void nhrp_shortcut_initiate(union sockunion *addr); +void nhrp_shortcut_foreach(afi_t afi, + void (*cb)(struct nhrp_shortcut *, void *), + void *ctx); +void nhrp_shortcut_purge(struct nhrp_shortcut *s, int force); +void nhrp_shortcut_prefix_change(const struct prefix *p, int deleted); + +void nhrp_cache_interface_del(struct interface *ifp); +void nhrp_cache_config_free(struct nhrp_cache_config *c); +struct nhrp_cache_config *nhrp_cache_config_get(struct interface *ifp, + union sockunion *remote_addr, + int create); +struct nhrp_cache *nhrp_cache_get(struct interface *ifp, + union sockunion *remote_addr, int create); +void nhrp_cache_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache *, void *), void *ctx); +void nhrp_cache_config_foreach(struct interface *ifp, + void (*cb)(struct nhrp_cache_config *, void *), void *ctx); +void nhrp_cache_set_used(struct nhrp_cache *, int); +int nhrp_cache_update_binding(struct nhrp_cache *, enum nhrp_cache_type type, + int holding_time, struct nhrp_peer *p, + uint32_t mtu, union sockunion *nbma_natoa, + union sockunion *claimed_nbma); +void nhrp_cache_notify_add(struct nhrp_cache *c, struct notifier_block *, + notifier_fn_t); +void nhrp_cache_notify_del(struct nhrp_cache *c, struct notifier_block *); + +void nhrp_vc_init(void); +void nhrp_vc_terminate(void); +struct nhrp_vc *nhrp_vc_get(const union sockunion *src, + const union sockunion *dst, int create); +int nhrp_vc_ipsec_updown(uint32_t child_id, struct nhrp_vc *vc); +void nhrp_vc_notify_add(struct nhrp_vc *, struct notifier_block *, + notifier_fn_t); +void nhrp_vc_notify_del(struct nhrp_vc *, struct notifier_block *); +void nhrp_vc_foreach(void (*cb)(struct nhrp_vc *, void *), void *ctx); +void nhrp_vc_reset(void); + +void vici_init(void); +void vici_terminate(void); +void vici_terminate_vc_by_profile_name(char *profile_name); +void vici_terminate_vc_by_ike_id(unsigned int ike_id); +void vici_request_vc(const char *profile, union sockunion *src, + union sockunion *dst, int prio); + +extern const char *nhrp_event_socket_path; + +void evmgr_init(void); +void evmgr_terminate(void); +void evmgr_set_socket(const char *socket); +void evmgr_notify(const char *name, struct nhrp_cache *c, + void (*cb)(struct nhrp_reqid *, void *)); + +struct nhrp_packet_header *nhrp_packet_push(struct zbuf *zb, uint8_t type, + const union sockunion *src_nbma, + const union sockunion *src_proto, + const union sockunion *dst_proto); +void nhrp_packet_complete(struct zbuf *zb, struct nhrp_packet_header *hdr); +uint16_t nhrp_packet_calculate_checksum(const uint8_t *pdu, uint16_t len); + +struct nhrp_packet_header *nhrp_packet_pull(struct zbuf *zb, + union sockunion *src_nbma, + union sockunion *src_proto, + union sockunion *dst_proto); + +struct nhrp_cie_header *nhrp_cie_push(struct zbuf *zb, uint8_t code, + const union sockunion *nbma, + const union sockunion *proto); +struct nhrp_cie_header *nhrp_cie_pull(struct zbuf *zb, + struct nhrp_packet_header *hdr, + union sockunion *nbma, + union sockunion *proto); + +struct nhrp_extension_header * +nhrp_ext_push(struct zbuf *zb, struct nhrp_packet_header *hdr, uint16_t type); +void nhrp_ext_complete(struct zbuf *zb, struct nhrp_extension_header *ext); +struct nhrp_extension_header *nhrp_ext_pull(struct zbuf *zb, + struct zbuf *payload); +void nhrp_ext_request(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *); +int nhrp_ext_reply(struct zbuf *zb, struct nhrp_packet_header *hdr, + struct interface *ifp, struct nhrp_extension_header *ext, + struct zbuf *extpayload); + +uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *, struct nhrp_reqid *r, + void (*cb)(struct nhrp_reqid *, void *)); +void nhrp_reqid_free(struct nhrp_reqid_pool *, struct nhrp_reqid *r); +struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *, uint32_t reqid); + +int nhrp_packet_init(void); + +void nhrp_peer_interface_del(struct interface *ifp); +struct nhrp_peer *nhrp_peer_get(struct interface *ifp, + const union sockunion *remote_nbma); +struct nhrp_peer *nhrp_peer_ref(struct nhrp_peer *p); +void nhrp_peer_unref(struct nhrp_peer *p); +int nhrp_peer_check(struct nhrp_peer *p, int establish); +void nhrp_peer_notify_add(struct nhrp_peer *p, struct notifier_block *, + notifier_fn_t); +void nhrp_peer_notify_del(struct nhrp_peer *p, struct notifier_block *); +void nhrp_peer_recv(struct nhrp_peer *p, struct zbuf *zb); +void nhrp_peer_send(struct nhrp_peer *p, struct zbuf *zb); +void nhrp_peer_send_indication(struct interface *ifp, uint16_t, struct zbuf *); + +int nhrp_nhs_match_ip(union sockunion *in_ip, struct nhrp_interface *nifp); + +#endif diff --git a/nhrpd/os.h b/nhrpd/os.h new file mode 100644 index 0000000..3f3b0de --- /dev/null +++ b/nhrpd/os.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +int os_socket(void); +int os_sendmsg(const uint8_t *buf, size_t len, int ifindex, const uint8_t *addr, + size_t addrlen, uint16_t protocol); +int os_recvmsg(uint8_t *buf, size_t *len, int *ifindex, uint8_t *addr, + size_t *addrlen); +int os_configure_dmvpn(unsigned int ifindex, const char *ifname, int af); diff --git a/nhrpd/reqid.c b/nhrpd/reqid.c new file mode 100644 index 0000000..c2f5d24 --- /dev/null +++ b/nhrpd/reqid.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "zebra.h" +#include "hash.h" +#include "nhrpd.h" + +static unsigned int nhrp_reqid_key(const void *data) +{ + const struct nhrp_reqid *r = data; + return r->request_id; +} + +static bool nhrp_reqid_cmp(const void *data, const void *key) +{ + const struct nhrp_reqid *a = data, *b = key; + + return a->request_id == b->request_id; +} + +uint32_t nhrp_reqid_alloc(struct nhrp_reqid_pool *p, struct nhrp_reqid *r, + void (*cb)(struct nhrp_reqid *, void *)) +{ + if (!p->reqid_hash) { + p->reqid_hash = hash_create(nhrp_reqid_key, nhrp_reqid_cmp, + "NHRP reqid Hash"); + p->next_request_id = 1; + } + + if (r->cb != cb) { + r->request_id = p->next_request_id; + if (++p->next_request_id == 0) + p->next_request_id = 1; + r->cb = cb; + (void)hash_get(p->reqid_hash, r, hash_alloc_intern); + } + return r->request_id; +} + +void nhrp_reqid_free(struct nhrp_reqid_pool *p, struct nhrp_reqid *r) +{ + if (r->cb) { + hash_release(p->reqid_hash, r); + r->cb = NULL; + } +} + +struct nhrp_reqid *nhrp_reqid_lookup(struct nhrp_reqid_pool *p, uint32_t reqid) +{ + struct nhrp_reqid key; + if (!p->reqid_hash) + return 0; + key.request_id = reqid; + return hash_lookup(p->reqid_hash, &key); +} diff --git a/nhrpd/subdir.am b/nhrpd/subdir.am new file mode 100644 index 0000000..227ff6c --- /dev/null +++ b/nhrpd/subdir.am @@ -0,0 +1,44 @@ +# +# nhrpd +# + +if NHRPD +sbin_PROGRAMS += nhrpd/nhrpd +vtysh_daemons += nhrpd +man8 += $(MANBUILD)/frr-nhrpd.8 +endif + +nhrpd_nhrpd_LDADD = lib/libfrr.la lib/libfrrcares.la $(LIBCAP) +nhrpd_nhrpd_SOURCES = \ + nhrpd/linux.c \ + nhrpd/netlink_arp.c \ + nhrpd/nhrp_cache.c \ + nhrpd/nhrp_errors.c \ + nhrpd/nhrp_event.c \ + nhrpd/nhrp_interface.c \ + nhrpd/nhrp_main.c \ + nhrpd/nhrp_nhs.c \ + nhrpd/nhrp_packet.c \ + nhrpd/nhrp_peer.c \ + nhrpd/nhrp_multicast.c \ + nhrpd/nhrp_route.c \ + nhrpd/nhrp_shortcut.c \ + nhrpd/nhrp_vc.c \ + nhrpd/nhrp_vty.c \ + nhrpd/reqid.c \ + nhrpd/vici.c \ + nhrpd/zbuf.c \ + nhrpd/znl.c \ + # end + +noinst_HEADERS += \ + nhrpd/debug.h \ + nhrpd/netlink.h \ + nhrpd/nhrp_errors.h \ + nhrpd/nhrp_protocol.h \ + nhrpd/nhrpd.h \ + nhrpd/os.h \ + nhrpd/vici.h \ + nhrpd/zbuf.h \ + nhrpd/znl.h \ + # end diff --git a/nhrpd/vici.c b/nhrpd/vici.c new file mode 100644 index 0000000..8162ac0 --- /dev/null +++ b/nhrpd/vici.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* strongSwan VICI protocol implementation for NHRP + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "frrevent.h" +#include "zbuf.h" +#include "log.h" +#include "lib_errors.h" + +#include "nhrpd.h" +#include "vici.h" +#include "nhrp_errors.h" + +#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +struct blob { + char *ptr; + int len; +}; + +static int blob_equal(const struct blob *b, const char *str) +{ + if (!b || b->len != (int)strlen(str)) + return 0; + return memcmp(b->ptr, str, b->len) == 0; +} + +static int blob2buf(const struct blob *b, char *buf, size_t n) +{ + if (!b || b->len >= (int)n) + return 0; + memcpy(buf, b->ptr, b->len); + buf[b->len] = 0; + return 1; +} + +struct vici_conn { + struct event *t_reconnect, *t_read, *t_write; + struct zbuf ibuf; + struct zbuf_queue obuf; + int fd; + uint8_t ibuf_data[VICI_MAX_MSGLEN]; +}; + +struct vici_message_ctx { + const char *sections[8]; + int nsections; +}; + +static void vici_reconnect(struct event *t); +static void vici_submit_request(struct vici_conn *vici, const char *name, ...); + +static void vici_zbuf_puts(struct zbuf *obuf, const char *str) +{ + size_t len = strlen(str); + zbuf_put8(obuf, len); + zbuf_put(obuf, str, len); +} + +static void vici_connection_error(struct vici_conn *vici) +{ + nhrp_vc_reset(); + + EVENT_OFF(vici->t_read); + EVENT_OFF(vici->t_write); + zbuf_reset(&vici->ibuf); + zbufq_reset(&vici->obuf); + + close(vici->fd); + vici->fd = -1; + event_add_timer(master, vici_reconnect, vici, 2, &vici->t_reconnect); +} + +static void vici_parse_message(struct vici_conn *vici, struct zbuf *msg, + void (*parser)(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, + const struct blob *key, + const struct blob *val), + struct vici_message_ctx *ctx) +{ + uint8_t *type; + struct blob key = {0}; + struct blob val = {0}; + + while ((type = zbuf_may_pull(msg, uint8_t)) != NULL) { + switch (*type) { + case VICI_SECTION_START: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + debugf(NHRP_DEBUG_VICI, "VICI: Section start '%.*s'", + key.len, key.ptr); + parser(ctx, *type, &key, NULL); + ctx->nsections++; + break; + case VICI_SECTION_END: + debugf(NHRP_DEBUG_VICI, "VICI: Section end"); + parser(ctx, *type, NULL, NULL); + ctx->nsections--; + break; + case VICI_KEY_VALUE: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + val.len = zbuf_get_be16(msg); + val.ptr = zbuf_pulln(msg, val.len); + debugf(NHRP_DEBUG_VICI, "VICI: Key '%.*s'='%.*s'", + key.len, key.ptr, val.len, val.ptr); + parser(ctx, *type, &key, &val); + break; + case VICI_LIST_START: + key.len = zbuf_get8(msg); + key.ptr = zbuf_pulln(msg, key.len); + debugf(NHRP_DEBUG_VICI, "VICI: List start '%.*s'", + key.len, key.ptr); + break; + case VICI_LIST_ITEM: + val.len = zbuf_get_be16(msg); + val.ptr = zbuf_pulln(msg, val.len); + debugf(NHRP_DEBUG_VICI, "VICI: List item: '%.*s'", + val.len, val.ptr); + parser(ctx, *type, &key, &val); + break; + case VICI_LIST_END: + debugf(NHRP_DEBUG_VICI, "VICI: List end"); + break; + } + } +} + +struct handle_sa_ctx { + struct vici_message_ctx msgctx; + int event; + int child_ok; + int kill_ikesa; + uint32_t child_uniqueid, ike_uniqueid; + struct { + union sockunion host; + struct blob id, cert; + } local, remote; +}; + +static void parse_sa_message(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, const struct blob *key, + const struct blob *val) +{ + struct handle_sa_ctx *sactx = + container_of(ctx, struct handle_sa_ctx, msgctx); + struct nhrp_vc *vc; + char buf[512]; + + switch (msgtype) { + case VICI_SECTION_START: + if (ctx->nsections == 3) { + /* Begin of child-sa section, reset child vars */ + sactx->child_uniqueid = 0; + sactx->child_ok = 0; + } + break; + case VICI_SECTION_END: + if (ctx->nsections == 3) { + /* End of child-sa section, update nhrp_vc */ + int up = sactx->child_ok || sactx->event == 1; + if (up) { + vc = nhrp_vc_get(&sactx->local.host, + &sactx->remote.host, up); + if (vc) { + blob2buf(&sactx->local.id, vc->local.id, + sizeof(vc->local.id)); + if (blob2buf(&sactx->local.cert, + (char *)vc->local.cert, + sizeof(vc->local.cert))) + vc->local.certlen = + sactx->local.cert.len; + blob2buf(&sactx->remote.id, + vc->remote.id, + sizeof(vc->remote.id)); + if (blob2buf(&sactx->remote.cert, + (char *)vc->remote.cert, + sizeof(vc->remote.cert))) + vc->remote.certlen = + sactx->remote.cert.len; + sactx->kill_ikesa |= + nhrp_vc_ipsec_updown( + sactx->child_uniqueid, + vc); + vc->ike_uniqueid = sactx->ike_uniqueid; + } + } else { + nhrp_vc_ipsec_updown(sactx->child_uniqueid, 0); + } + } + break; + case VICI_START: + case VICI_KEY_VALUE: + case VICI_LIST_START: + case VICI_LIST_ITEM: + case VICI_LIST_END: + case VICI_END: + if (!key || !key->ptr) + break; + + switch (key->ptr[0]) { + case 'l': + if (blob_equal(key, "local-host") + && ctx->nsections == 1) { + if (blob2buf(val, buf, sizeof(buf))) + if (str2sockunion(buf, + &sactx->local.host) + < 0) + flog_err( + EC_NHRP_SWAN, + "VICI: bad strongSwan local-host: %s", + buf); + } else if (blob_equal(key, "local-id") + && ctx->nsections == 1) { + sactx->local.id = *val; + } else if (blob_equal(key, "local-cert-data") + && ctx->nsections == 1) { + sactx->local.cert = *val; + } + break; + case 'r': + if (blob_equal(key, "remote-host") + && ctx->nsections == 1) { + if (blob2buf(val, buf, sizeof(buf))) + if (str2sockunion(buf, + &sactx->remote.host) + < 0) + flog_err( + EC_NHRP_SWAN, + "VICI: bad strongSwan remote-host: %s", + buf); + } else if (blob_equal(key, "remote-id") + && ctx->nsections == 1) { + sactx->remote.id = *val; + } else if (blob_equal(key, "remote-cert-data") + && ctx->nsections == 1) { + sactx->remote.cert = *val; + } + break; + case 'u': + if (blob_equal(key, "uniqueid") + && blob2buf(val, buf, sizeof(buf))) { + if (ctx->nsections == 3) + sactx->child_uniqueid = + strtoul(buf, NULL, 0); + else if (ctx->nsections == 1) + sactx->ike_uniqueid = + strtoul(buf, NULL, 0); + } + break; + case 's': + if (blob_equal(key, "state") && ctx->nsections == 3) { + sactx->child_ok = + (sactx->event == 0 + && (blob_equal(val, "INSTALLED") + || blob_equal(val, "REKEYED"))); + } + break; + } + break; + } +} + +static void parse_cmd_response(struct vici_message_ctx *ctx, + enum vici_type_t msgtype, const struct blob *key, + const struct blob *val) +{ + char buf[512]; + + switch (msgtype) { + case VICI_KEY_VALUE: + if (blob_equal(key, "errmsg") + && blob2buf(val, buf, sizeof(buf))) + flog_err(EC_NHRP_SWAN, "VICI: strongSwan: %s", buf); + break; + case VICI_START: + case VICI_SECTION_START: + case VICI_SECTION_END: + case VICI_LIST_START: + case VICI_LIST_ITEM: + case VICI_LIST_END: + case VICI_END: + break; + } +} + +static void vici_recv_sa(struct vici_conn *vici, struct zbuf *msg, int event) +{ + char buf[32]; + struct handle_sa_ctx ctx = { + .event = event, + .msgctx.nsections = 0 + }; + + vici_parse_message(vici, msg, parse_sa_message, &ctx.msgctx); + + if (ctx.kill_ikesa && ctx.ike_uniqueid) { + debugf(NHRP_DEBUG_COMMON, "VICI: Deleting IKE_SA %u", + ctx.ike_uniqueid); + snprintf(buf, sizeof(buf), "%u", ctx.ike_uniqueid); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id", + strlen(buf), buf, VICI_END); + } +} + +static void vici_recv_message(struct vici_conn *vici, struct zbuf *msg) +{ + uint32_t msglen; + uint8_t msgtype; + struct blob name; + struct vici_message_ctx ctx = { .nsections = 0 }; + + msglen = zbuf_get_be32(msg); + msgtype = zbuf_get8(msg); + debugf(NHRP_DEBUG_VICI, "VICI: Message %d, %d bytes", msgtype, msglen); + + switch (msgtype) { + case VICI_EVENT: + name.len = zbuf_get8(msg); + name.ptr = zbuf_pulln(msg, name.len); + + debugf(NHRP_DEBUG_VICI, "VICI: Event '%.*s'", name.len, + name.ptr); + if (blob_equal(&name, "list-sa") + || blob_equal(&name, "child-updown") + || blob_equal(&name, "child-rekey")) + vici_recv_sa(vici, msg, 0); + else if (blob_equal(&name, "child-state-installed") + || blob_equal(&name, "child-state-rekeyed")) + vici_recv_sa(vici, msg, 1); + else if (blob_equal(&name, "child-state-destroying")) + vici_recv_sa(vici, msg, 2); + break; + case VICI_CMD_RESPONSE: + vici_parse_message(vici, msg, parse_cmd_response, &ctx); + break; + case VICI_EVENT_UNKNOWN: + case VICI_CMD_UNKNOWN: + flog_err( + EC_NHRP_SWAN, + "VICI: StrongSwan does not support mandatory events (unpatched?)"); + break; + case VICI_EVENT_CONFIRM: + break; + default: + zlog_notice("VICI: Unrecognized message type %d", msgtype); + break; + } +} + +static void vici_read(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + struct zbuf *ibuf = &vici->ibuf; + struct zbuf pktbuf; + + if (zbuf_read(ibuf, vici->fd, (size_t)-1) < 0) { + vici_connection_error(vici); + return; + } + + /* Process all messages in buffer */ + do { + uint32_t *hdrlen = zbuf_may_pull(ibuf, uint32_t); + if (!hdrlen) + break; + if (!zbuf_may_pulln(ibuf, ntohl(*hdrlen))) { + zbuf_reset_head(ibuf, hdrlen); + break; + } + + /* Handle packet */ + zbuf_init(&pktbuf, hdrlen, htonl(*hdrlen) + 4, + htonl(*hdrlen) + 4); + vici_recv_message(vici, &pktbuf); + } while (1); + + event_add_read(master, vici_read, vici, vici->fd, &vici->t_read); +} + +static void vici_write(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + int r; + + r = zbufq_write(&vici->obuf, vici->fd); + if (r > 0) { + event_add_write(master, vici_write, vici, vici->fd, + &vici->t_write); + } else if (r < 0) { + vici_connection_error(vici); + } +} + +static void vici_submit(struct vici_conn *vici, struct zbuf *obuf) +{ + if (vici->fd < 0) { + zbuf_free(obuf); + return; + } + + zbufq_queue(&vici->obuf, obuf); + event_add_write(master, vici_write, vici, vici->fd, &vici->t_write); +} + +static void vici_submit_request(struct vici_conn *vici, const char *name, ...) +{ + struct zbuf *obuf; + uint32_t *hdrlen; + va_list va; + size_t len; + int type; + + obuf = zbuf_alloc(256); + if (!obuf) + return; + + hdrlen = zbuf_push(obuf, uint32_t); + zbuf_put8(obuf, VICI_CMD_REQUEST); + vici_zbuf_puts(obuf, name); + + va_start(va, name); + for (type = va_arg(va, int); type != VICI_END; type = va_arg(va, int)) { + zbuf_put8(obuf, type); + switch (type) { + case VICI_KEY_VALUE: + vici_zbuf_puts(obuf, va_arg(va, const char *)); + len = va_arg(va, size_t); + zbuf_put_be16(obuf, len); + zbuf_put(obuf, va_arg(va, void *), len); + break; + default: + break; + } + } + va_end(va); + *hdrlen = htonl(zbuf_used(obuf) - 4); + vici_submit(vici, obuf); +} + +static void vici_register_event(struct vici_conn *vici, const char *name) +{ + struct zbuf *obuf; + uint32_t *hdrlen; + uint8_t namelen; + + namelen = strlen(name); + obuf = zbuf_alloc(4 + 1 + 1 + namelen); + if (!obuf) + return; + + hdrlen = zbuf_push(obuf, uint32_t); + zbuf_put8(obuf, VICI_EVENT_REGISTER); + zbuf_put8(obuf, namelen); + zbuf_put(obuf, name, namelen); + *hdrlen = htonl(zbuf_used(obuf) - 4); + + vici_submit(vici, obuf); +} + +static bool vici_charon_filepath_done; +static bool vici_charon_not_found; + +static char *vici_get_charon_filepath(void) +{ + static char buff[1200]; + FILE *fp; + char *ptr; + char line[1024]; + + if (vici_charon_filepath_done) + return (char *)buff; + fp = popen("ipsec --piddir", "r"); + if (!fp) { + if (!vici_charon_not_found) { + flog_err(EC_NHRP_SWAN, + "VICI: Failed to retrieve charon file path"); + vici_charon_not_found = true; + } + return NULL; + } + /* last line of output is used to get vici path */ + while (fgets(line, sizeof(line), fp) != NULL) { + ptr = strchr(line, '\n'); + if (ptr) + *ptr = '\0'; + snprintf(buff, sizeof(buff), "%s/charon.vici", line); + } + pclose(fp); + vici_charon_filepath_done = true; + return buff; +} + +static void vici_reconnect(struct event *t) +{ + struct vici_conn *vici = EVENT_ARG(t); + int fd; + char *file_path; + + if (vici->fd >= 0) + return; + + fd = sock_open_unix(VICI_SOCKET); + if (fd < 0) { + file_path = vici_get_charon_filepath(); + if (file_path) + fd = sock_open_unix(file_path); + } + if (fd < 0) { + debugf(NHRP_DEBUG_VICI, + "%s: failure connecting VICI socket: %s", __func__, + strerror(errno)); + event_add_timer(master, vici_reconnect, vici, 2, + &vici->t_reconnect); + return; + } + + debugf(NHRP_DEBUG_COMMON, "VICI: Connected"); + vici->fd = fd; + event_add_read(master, vici_read, vici, vici->fd, &vici->t_read); + + /* Send event subscribtions */ + // vici_register_event(vici, "child-updown"); + // vici_register_event(vici, "child-rekey"); + vici_register_event(vici, "child-state-installed"); + vici_register_event(vici, "child-state-rekeyed"); + vici_register_event(vici, "child-state-destroying"); + vici_register_event(vici, "list-sa"); + vici_submit_request(vici, "list-sas", VICI_END); +} + +static struct vici_conn vici_connection; + +void vici_init(void) +{ + struct vici_conn *vici = &vici_connection; + + vici->fd = -1; + zbuf_init(&vici->ibuf, vici->ibuf_data, sizeof(vici->ibuf_data), 0); + zbufq_init(&vici->obuf); + event_add_timer_msec(master, vici_reconnect, vici, 10, + &vici->t_reconnect); +} + +void vici_terminate(void) +{ +} + +void vici_terminate_vc_by_profile_name(char *profile_name) +{ + struct vici_conn *vici = &vici_connection; + + debugf(NHRP_DEBUG_VICI, "Terminate profile = %s", profile_name); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike", + strlen(profile_name), profile_name, VICI_END); +} + +void vici_terminate_vc_by_ike_id(unsigned int ike_id) +{ + struct vici_conn *vici = &vici_connection; + char ike_id_str[10]; + + snprintf(ike_id_str, sizeof(ike_id_str), "%d", ike_id); + debugf(NHRP_DEBUG_VICI, "Terminate ike_id_str = %s", ike_id_str); + vici_submit_request(vici, "terminate", VICI_KEY_VALUE, "ike-id", + strlen(ike_id_str), ike_id_str, VICI_END); +} + +void vici_request_vc(const char *profile, union sockunion *src, + union sockunion *dst, int prio) +{ + struct vici_conn *vici = &vici_connection; + char buf[2][SU_ADDRSTRLEN]; + + sockunion2str(src, buf[0], sizeof(buf[0])); + sockunion2str(dst, buf[1], sizeof(buf[1])); + + vici_submit_request(vici, "initiate", VICI_KEY_VALUE, "child", + strlen(profile), profile, VICI_KEY_VALUE, "timeout", + (size_t)2, "-1", VICI_KEY_VALUE, "async", (size_t)1, + "1", VICI_KEY_VALUE, "init-limits", (size_t)1, + prio ? "0" : "1", VICI_KEY_VALUE, "my-host", + strlen(buf[0]), buf[0], VICI_KEY_VALUE, + "other-host", strlen(buf[1]), buf[1], VICI_END); +} + +int sock_open_unix(const char *path) +{ + int ret, fd; + struct sockaddr_un addr; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + + ret = connect(fd, (struct sockaddr *)&addr, + sizeof(addr.sun_family) + strlen(addr.sun_path)); + if (ret < 0) { + close(fd); + return -1; + } + + ret = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/nhrpd/vici.h b/nhrpd/vici.h new file mode 100644 index 0000000..8a8011f --- /dev/null +++ b/nhrpd/vici.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +enum vici_type_t { + VICI_START = 0, + VICI_SECTION_START = 1, + VICI_SECTION_END = 2, + VICI_KEY_VALUE = 3, + VICI_LIST_START = 4, + VICI_LIST_ITEM = 5, + VICI_LIST_END = 6, + VICI_END = 7 +}; + +enum vici_operation_t { + VICI_CMD_REQUEST = 0, + VICI_CMD_RESPONSE, + VICI_CMD_UNKNOWN, + VICI_EVENT_REGISTER, + VICI_EVENT_UNREGISTER, + VICI_EVENT_CONFIRM, + VICI_EVENT_UNKNOWN, + VICI_EVENT, +}; + +#define VICI_MAX_MSGLEN (512*1024) diff --git a/nhrpd/zbuf.c b/nhrpd/zbuf.c new file mode 100644 index 0000000..0eb8198 --- /dev/null +++ b/nhrpd/zbuf.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Stream/packet buffer API implementation + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include "zbuf.h" +#include "memory.h" +#include "nhrpd.h" + +#define ERRNO_IO_RETRY(EN) (((EN) == EAGAIN) || ((EN) == EWOULDBLOCK) || ((EN) == EINTR)) + +DEFINE_MTYPE_STATIC(NHRPD, ZBUF_DATA, "NHRPD zbuf data"); + +struct zbuf *zbuf_alloc(size_t size) +{ + struct zbuf *zb; + + zb = XCALLOC(MTYPE_ZBUF_DATA, sizeof(*zb) + size); + + zbuf_init(zb, zb + 1, size, 0); + zb->allocated = 1; + + return zb; +} + +void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen) +{ + *zb = (struct zbuf){ + .buf = buf, + .end = (uint8_t *)buf + len, + .head = buf, + .tail = (uint8_t *)buf + datalen, + }; +} + +void zbuf_free(struct zbuf *zb) +{ + if (zb->allocated) + XFREE(MTYPE_ZBUF_DATA, zb); +} + +void zbuf_reset(struct zbuf *zb) +{ + zb->head = zb->tail = zb->buf; + zb->error = 0; +} + +void zbuf_reset_head(struct zbuf *zb, void *ptr) +{ + assert((void *)zb->buf <= ptr && ptr <= (void *)zb->tail); + zb->head = ptr; +} + +static void zbuf_remove_headroom(struct zbuf *zb) +{ + ssize_t headroom = zbuf_headroom(zb); + if (!headroom) + return; + memmove(zb->buf, zb->head, zbuf_used(zb)); + zb->head -= headroom; + zb->tail -= headroom; +} + +ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen) +{ + ssize_t r; + + if (zb->error) + return -3; + + zbuf_remove_headroom(zb); + if (maxlen > zbuf_tailroom(zb)) + maxlen = zbuf_tailroom(zb); + + r = read(fd, zb->tail, maxlen); + if (r > 0) + zb->tail += r; + else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + + return r; +} + +ssize_t zbuf_write(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + r = write(fd, zb->head, zbuf_used(zb)); + if (r > 0) { + zb->head += r; + if (zb->head == zb->tail) + zbuf_reset(zb); + } else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + + return r; +} + +ssize_t zbuf_recv(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + zbuf_remove_headroom(zb); + r = recv(fd, zb->tail, zbuf_tailroom(zb), 0); + if (r > 0) + zb->tail += r; + else if (r == 0) + r = -2; + else if (ERRNO_IO_RETRY(errno)) + r = 0; + return r; +} + +ssize_t zbuf_send(struct zbuf *zb, int fd) +{ + ssize_t r; + + if (zb->error) + return -3; + + r = send(fd, zb->head, zbuf_used(zb), 0); + if (r >= 0) + zbuf_reset(zb); + + return r; +} + +void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg) +{ + size_t seplen = strlen(sep), len; + uint8_t *ptr; + + ptr = memmem(zb->head, zbuf_used(zb), sep, seplen); + if (!ptr) + return NULL; + + len = ptr - zb->head + seplen; + zbuf_init(msg, zbuf_pulln(zb, len), len, len); + return msg->head; +} + +void zbufq_init(struct zbuf_queue *zbq) +{ + *zbq = (struct zbuf_queue){ + .queue_head = INIT_DLIST(zbq->queue_head), + }; +} + +void zbufq_reset(struct zbuf_queue *zbq) +{ + struct zbuf *buf; + + frr_each_safe (zbuf_queue, &zbq->queue_head, buf) { + zbuf_queue_del(&zbq->queue_head, buf); + zbuf_free(buf); + } +} + +void zbufq_queue(struct zbuf_queue *zbq, struct zbuf *zb) +{ + zbuf_queue_add_tail(&zbq->queue_head, zb); +} + +int zbufq_write(struct zbuf_queue *zbq, int fd) +{ + struct iovec iov[16]; + struct zbuf *zb; + ssize_t r; + size_t iovcnt = 0; + + frr_each_safe (zbuf_queue, &zbq->queue_head, zb) { + iov[iovcnt++] = (struct iovec){ + .iov_base = zb->head, .iov_len = zbuf_used(zb), + }; + if (iovcnt >= array_size(iov)) + break; + } + + r = writev(fd, iov, iovcnt); + if (r < 0) + return r; + + frr_each_safe (zbuf_queue, &zbq->queue_head, zb) { + if (r < (ssize_t)zbuf_used(zb)) { + zb->head += r; + return 1; + } + + r -= zbuf_used(zb); + zbuf_queue_del(&zbq->queue_head, zb); + zbuf_free(zb); + } + + return 0; +} + +void zbuf_copy(struct zbuf *zdst, struct zbuf *zsrc, size_t len) +{ + const void *src; + void *dst; + + dst = zbuf_pushn(zdst, len); + src = zbuf_pulln(zsrc, len); + if (!dst || !src) + return; + memcpy(dst, src, len); +} + +void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len) +{ + const void *src; + void *dst; + + dst = zbuf_pushn(zdst, len); + src = zbuf_pulln(zsrc, 0); + if (!dst || !src) + return; + memcpy(dst, src, len); +} diff --git a/nhrpd/zbuf.h b/nhrpd/zbuf.h new file mode 100644 index 0000000..23492b4 --- /dev/null +++ b/nhrpd/zbuf.h @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Stream/packet buffer API + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifndef ZBUF_H +#define ZBUF_H + +#include +#include +#include +#include + +#include "typesafe.h" + +PREDECL_DLIST(zbuf_queue); + +struct zbuf { + struct zbuf_queue_item queue_entry; + unsigned allocated : 1; + unsigned error : 1; + uint8_t *buf, *end; + uint8_t *head, *tail; +}; + +DECLARE_DLIST(zbuf_queue, struct zbuf, queue_entry); + +struct zbuf_queue { + struct zbuf_queue_head queue_head; +}; + +struct zbuf *zbuf_alloc(size_t size); +void zbuf_init(struct zbuf *zb, void *buf, size_t len, size_t datalen); +void zbuf_free(struct zbuf *zb); + +static inline size_t zbuf_size(struct zbuf *zb) +{ + return zb->end - zb->buf; +} + +static inline size_t zbuf_used(struct zbuf *zb) +{ + return zb->tail - zb->head; +} + +static inline size_t zbuf_tailroom(struct zbuf *zb) +{ + return zb->end - zb->tail; +} + +static inline size_t zbuf_headroom(struct zbuf *zb) +{ + return zb->head - zb->buf; +} + +void zbuf_reset(struct zbuf *zb); +void zbuf_reset_head(struct zbuf *zb, void *ptr); +ssize_t zbuf_read(struct zbuf *zb, int fd, size_t maxlen); +ssize_t zbuf_write(struct zbuf *zb, int fd); +ssize_t zbuf_recv(struct zbuf *zb, int fd); +ssize_t zbuf_send(struct zbuf *zb, int fd); + +static inline void zbuf_set_rerror(struct zbuf *zb) +{ + zb->error = 1; + zb->head = zb->tail; +} + +static inline void zbuf_set_werror(struct zbuf *zb) +{ + zb->error = 1; + zb->tail = zb->end; +} + +static inline void *__zbuf_pull(struct zbuf *zb, size_t size, int error) +{ + void *head = zb->head; + if (size > zbuf_used(zb)) { + if (error) + zbuf_set_rerror(zb); + return NULL; + } + zb->head += size; + return head; +} + +#define zbuf_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 1)) +#define zbuf_pulln(zb, sz) (__zbuf_pull(zb, sz, 1)) +#define zbuf_may_pull(zb, type) ((type *)__zbuf_pull(zb, sizeof(type), 0)) +#define zbuf_may_pulln(zb, sz) (__zbuf_pull(zb, sz, 0)) + +void *zbuf_may_pull_until(struct zbuf *zb, const char *sep, struct zbuf *msg); + +static inline void zbuf_get(struct zbuf *zb, void *dst, size_t len) +{ + void *src = zbuf_pulln(zb, len); + if (src) + memcpy(dst, src, len); +} + +static inline uint8_t zbuf_get8(struct zbuf *zb) +{ + uint8_t *src = zbuf_pull(zb, uint8_t); + if (src) + return *src; + return 0; +} + +static inline uint32_t zbuf_get32(struct zbuf *zb) +{ + struct unaligned32 { + uint32_t value; + } __attribute__((packed)); + + struct unaligned32 *v = zbuf_pull(zb, struct unaligned32); + if (v) + return v->value; + return 0; +} + +static inline uint16_t zbuf_get_be16(struct zbuf *zb) +{ + struct unaligned16 { + uint16_t value; + } __attribute__((packed)); + + struct unaligned16 *v = zbuf_pull(zb, struct unaligned16); + if (v) + return be16toh(v->value); + return 0; +} + +static inline uint32_t zbuf_get_be32(struct zbuf *zb) +{ + return be32toh(zbuf_get32(zb)); +} + +static inline void *__zbuf_push(struct zbuf *zb, size_t size, int error) +{ + void *tail = zb->tail; + if (size > zbuf_tailroom(zb)) { + if (error) + zbuf_set_werror(zb); + return NULL; + } + zb->tail += size; + return tail; +} + +#define zbuf_push(zb, type) ((type *)__zbuf_push(zb, sizeof(type), 1)) +#define zbuf_pushn(zb, sz) (__zbuf_push(zb, sz, 1)) +#define zbuf_may_push(zb, type) ((type *)__zbuf_may_push(zb, sizeof(type), 0)) +#define zbuf_may_pushn(zb, sz) (__zbuf_push(zb, sz, 0)) + +static inline void zbuf_put(struct zbuf *zb, const void *src, size_t len) +{ + void *dst = zbuf_pushn(zb, len); + if (dst) + memcpy(dst, src, len); +} + +static inline void zbuf_put8(struct zbuf *zb, uint8_t val) +{ + uint8_t *dst = zbuf_push(zb, uint8_t); + if (dst) + *dst = val; +} + +static inline void zbuf_put_be16(struct zbuf *zb, uint16_t val) +{ + struct unaligned16 { + uint16_t value; + } __attribute__((packed)); + + struct unaligned16 *v = zbuf_push(zb, struct unaligned16); + if (v) + v->value = htobe16(val); +} + +static inline void zbuf_put_be32(struct zbuf *zb, uint32_t val) +{ + struct unaligned32 { + uint32_t value; + } __attribute__((packed)); + + struct unaligned32 *v = zbuf_push(zb, struct unaligned32); + if (v) + v->value = htobe32(val); +} + +void zbuf_copy(struct zbuf *zb, struct zbuf *src, size_t len); +void zbuf_copy_peek(struct zbuf *zdst, struct zbuf *zsrc, size_t len); + +void zbufq_init(struct zbuf_queue *); +void zbufq_reset(struct zbuf_queue *); +void zbufq_queue(struct zbuf_queue *, struct zbuf *); +int zbufq_write(struct zbuf_queue *, int); + +#endif diff --git a/nhrpd/znl.c b/nhrpd/znl.c new file mode 100644 index 0000000..a65282f --- /dev/null +++ b/nhrpd/znl.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Netlink helpers for zbuf + * Copyright (c) 2014-2015 Timo Teräs + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "znl.h" + +#define ZNL_ALIGN(len) (((len)+3) & ~3) + +void *znl_push(struct zbuf *zb, size_t n) +{ + return zbuf_pushn(zb, ZNL_ALIGN(n)); +} + +void *znl_pull(struct zbuf *zb, size_t n) +{ + return zbuf_pulln(zb, ZNL_ALIGN(n)); +} + +struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags) +{ + struct nlmsghdr *n; + + n = znl_push(zb, sizeof(*n)); + if (!n) + return NULL; + + *n = (struct nlmsghdr){ + .nlmsg_type = type, .nlmsg_flags = flags, + }; + return n; +} + +void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n) +{ + n->nlmsg_len = zb->tail - (uint8_t *)n; +} + +struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload) +{ + struct nlmsghdr *n; + size_t plen; + + n = znl_pull(zb, sizeof(*n)); + if (!n) + return NULL; + + plen = n->nlmsg_len - sizeof(*n); + zbuf_init(payload, znl_pull(zb, plen), plen, plen); + zbuf_may_pulln(zb, ZNL_ALIGN(plen) - plen); + + return n; +} + +struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, + size_t len) +{ + struct rtattr *rta; + uint8_t *dst; + + rta = znl_push(zb, ZNL_ALIGN(sizeof(*rta)) + ZNL_ALIGN(len)); + if (!rta) + return NULL; + + *rta = (struct rtattr){ + .rta_type = type, .rta_len = ZNL_ALIGN(sizeof(*rta)) + len, + }; + + dst = (uint8_t *)(rta + 1); + memcpy(dst, val, len); + memset(dst + len, 0, ZNL_ALIGN(len) - len); + + return rta; +} + +struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val) +{ + return znl_rta_push(zb, type, &val, sizeof(val)); +} + +struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type) +{ + struct rtattr *rta; + + rta = znl_push(zb, sizeof(*rta)); + if (!rta) + return NULL; + + *rta = (struct rtattr){ + .rta_type = type, + }; + return rta; +} + +void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta) +{ + size_t len = zb->tail - (uint8_t *)rta; + size_t align = ZNL_ALIGN(len) - len; + + if (align) { + void *dst = zbuf_pushn(zb, align); + if (dst) + memset(dst, 0, align); + } + rta->rta_len = len; +} + +struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload) +{ + struct rtattr *rta; + size_t plen; + + rta = znl_pull(zb, sizeof(*rta)); + if (!rta) + return NULL; + + if (rta->rta_len > sizeof(*rta)) { + plen = rta->rta_len - sizeof(*rta); + zbuf_init(payload, znl_pull(zb, plen), plen, plen); + } else { + zbuf_init(payload, NULL, 0, 0); + } + + return rta; +} + +int znl_open(int protocol, int groups) +{ + struct sockaddr_nl addr; + int fd, buf = 128 * 1024; + + fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (fd < 0) + return -1; + + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) + goto error; + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) + goto error; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buf, sizeof(buf)) < 0) + goto error; + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = groups; + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + goto error; + + return fd; +error: + close(fd); + return -1; +} diff --git a/nhrpd/znl.h b/nhrpd/znl.h new file mode 100644 index 0000000..95dfae8 --- /dev/null +++ b/nhrpd/znl.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Netlink helpers for zbuf + * Copyright (c) 2014-2015 Timo Teräs + */ + +#include "zbuf.h" + +#define ZNL_BUFFER_SIZE 8192 + +void *znl_push(struct zbuf *zb, size_t n); +void *znl_pull(struct zbuf *zb, size_t n); + +struct nlmsghdr *znl_nlmsg_push(struct zbuf *zb, uint16_t type, uint16_t flags); +void znl_nlmsg_complete(struct zbuf *zb, struct nlmsghdr *n); +struct nlmsghdr *znl_nlmsg_pull(struct zbuf *zb, struct zbuf *payload); + +struct rtattr *znl_rta_push(struct zbuf *zb, uint16_t type, const void *val, + size_t len); +struct rtattr *znl_rta_push_u32(struct zbuf *zb, uint16_t type, uint32_t val); +struct rtattr *znl_rta_nested_push(struct zbuf *zb, uint16_t type); +void znl_rta_nested_complete(struct zbuf *zb, struct rtattr *rta); + +struct rtattr *znl_rta_pull(struct zbuf *zb, struct zbuf *payload); + +int znl_open(int protocol, int groups); diff --git a/ospf6d/.gitignore b/ospf6d/.gitignore new file mode 100644 index 0000000..e398b1c --- /dev/null +++ b/ospf6d/.gitignore @@ -0,0 +1,2 @@ +ospf6d +ospf6d.conf diff --git a/ospf6d/Makefile b/ospf6d/Makefile new file mode 100644 index 0000000..97b37b8 --- /dev/null +++ b/ospf6d/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ospf6d/ospf6d +%: ALWAYS + @$(MAKE) -s -C .. ospf6d/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ospf6d/README b/ospf6d/README new file mode 100644 index 0000000..f5a0046 --- /dev/null +++ b/ospf6d/README @@ -0,0 +1,102 @@ + + Zebra OSPF daemon for IPv6 network + + 2003/08/18 + +README for newer code is not yet. General usage should remain +the same. For further usage, see command helps by typing '?' +in vty, and then imagin ! ;p) Previous README contents follows. + + Zebra OSPF daemon for IPv6 network + + 2001/12/20 + +Zebra OSPF6d is OSPF version 3 daemon which is specified by +"OSPF for IPv6" (RFC 2740). + +*** NOTE *** + Zebra ospf6d is in development yet. It may lack some functionalities, + and may have some bugs. Use the latest version from the anoncvs + repository (http://www.zebra.org/cvs.html) ! + +This file README is like memo yet, so please feel free to ask + by E-mail. Patches will be appriciated. + +ospf6d's vty port was default to 2606/tcp. +Use commands below. + +VIEW NODE: + show ipv6 ospf6 + To see Router-ID, uptime of ospf6d, some statistics. + + show ipv6 ospf6 database ... + This command shows LSA database. You can specify + LS-type/LS-ID/Advertising-Router of LSAs. '*' is recognized. + + show ipv6 ospf6 interface ... + To see the status of the OSPF interface, and the configuration + like interface costs. + + show ipv6 ospf6 neighbor ... + Shows state of neighbors and choosed (Backup) DR on the I/F. + + show ipv6 ospf6 route (X::X) + This command shows internal routing table of the ospf6d. + Routes not calculated by OSPFv3 (like connected routes) + are not shown. If Address is specified (X::X), shows the route + that the address matches. + + show ipv6 ospf6 route redistribute (X::X) + Shows the routes advertised as AS-External routes by the router + itself. If Address is specified (X::X), shows the route + that the address matches. + +CONFIG NODE: + interface NAME + To enter INTERFACE NODE + + router ospf6 ... + To enter OSPF6 NODE + +INTERFACE NODE: + ipv6 ospf6 cost COST + Sets the interface's output cost. Depends on interface bandwidth by default. + + ipv6 ospf6 hello-interval HELLOINTERVAL + Sets the interface's Hello Interval. default 10 + + ipv6 ospf6 dead-interval DEADINTERVAL + Sets the interface's Router Dead Interval. default 40 + + ipv6 ospf6 retransmit-interval RETRANSMITINTERVAL + Sets the interface's Rxmt Interval. default 5 + + ipv6 ospf6 priority PRIORITY + Sets the interface's Router Priority. default 1 + + ipv6 ospf6 transmit-delay TRANSMITDELAY + Sets the interface's Inf-Trans-Delay. default 1 + +OSPF6 NODE: + router-id A.B.C.D + Sets the router's Router-ID + + interface NAME area AREA + Binds interface to specified Area, and start + sending OSPFv3 packets. + + auto-cost reference-bandwidth COST + Sets the reference bandwidth for cost calculations, where this + bandwidth is considered equivalent to an OSPF cost of 1, specified + in Mbits/s. The default is 100Mbit/s (i.e. a link of bandwidth + 100Mbit/s or higher will have a cost of 1. Cost of lower bandwidth + links will be scaled with reference to this cost). This + configuration setting MUST be consistent across all routers within + the OSPF domain. + +Sample configuration is in ospf6d.conf.sample. + +-- +Yasuhiro Ohara +Kunihiro Ishiguro + diff --git a/ospf6d/ospf6_abr.c b/ospf6d/ospf6_abr.c new file mode 100644 index 0000000..0c5be29 --- /dev/null +++ b/ospf6d/ospf6_abr.c @@ -0,0 +1,1596 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Area Border Router function. + * Copyright (C) 2004 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "linklist.h" +#include "command.h" +#include "frrevent.h" +#include "plist.h" +#include "filter.h" + +#include "ospf6_proto.h" +#include "ospf6_route.h" +#include "ospf6_lsa.h" +#include "ospf6_route.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_zebra.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" + +#include "ospf6_flood.h" +#include "ospf6_intra.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6d.h" +#include "ospf6_nssa.h" + +unsigned char conf_debug_ospf6_abr; + +int ospf6_ls_origin_same(struct ospf6_path *o_path, struct ospf6_path *r_path) +{ + if (((o_path->origin.type == r_path->origin.type) + && (o_path->origin.id == r_path->origin.id) + && (o_path->origin.adv_router == r_path->origin.adv_router))) + return 1; + else + return 0; +} + +bool ospf6_check_and_set_router_abr(struct ospf6 *o) +{ + struct listnode *node; + struct ospf6_area *oa; + int area_count = 0; + bool is_backbone = false; + + for (ALL_LIST_ELEMENTS_RO(o->area_list, node, oa)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s, area_id %pI4", __func__, &oa->area_id); + if (IS_AREA_ENABLED(oa)) + area_count++; + + if (o->backbone == oa) + is_backbone = true; + } + + if ((area_count > 1) && (is_backbone)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : set flag OSPF6_FLAG_ABR", __func__); + SET_FLAG(o->flag, OSPF6_FLAG_ABR); + return true; + } else { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : reset flag OSPF6_FLAG_ABR", __func__); + UNSET_FLAG(o->flag, OSPF6_FLAG_ABR); + return false; + } +} + +static int ospf6_abr_nexthops_belong_to_area(struct ospf6_route *route, + struct ospf6_area *area) +{ + struct ospf6_interface *oi; + + oi = ospf6_interface_lookup_by_ifindex( + ospf6_route_get_first_nh_index(route), area->ospf6->vrf_id); + if (oi && oi->area && oi->area == area) + return 1; + else + return 0; +} + +static void ospf6_abr_delete_route(struct ospf6_route *summary, + struct ospf6_route_table *summary_table, + struct ospf6_lsa *old) +{ + if (summary) { + ospf6_route_remove(summary, summary_table); + } + + if (old && !OSPF6_LSA_IS_MAXAGE(old)) + ospf6_lsa_purge(old); +} + +void ospf6_abr_enable_area(struct ospf6_area *area) +{ + struct ospf6_area *oa; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(area->ospf6->area_list, node, nnode, oa)) + /* update B bit for each area */ + OSPF6_ROUTER_LSA_SCHEDULE(oa); +} + +void ospf6_abr_disable_area(struct ospf6_area *area) +{ + struct ospf6_area *oa; + struct ospf6_route *ro, *nro; + struct ospf6_lsa *old; + struct listnode *node, *nnode; + + /* Withdraw all summary prefixes previously originated */ + for (ro = ospf6_route_head(area->summary_prefix); ro; ro = nro) { + nro = ospf6_route_next(ro); + old = ospf6_lsdb_lookup(ro->path.origin.type, + ro->path.origin.id, + area->ospf6->router_id, area->lsdb); + if (old) + ospf6_lsa_purge(old); + ospf6_route_remove(ro, area->summary_prefix); + } + + /* Withdraw all summary router-routes previously originated */ + for (ro = ospf6_route_head(area->summary_router); ro; ro = nro) { + nro = ospf6_route_next(ro); + old = ospf6_lsdb_lookup(ro->path.origin.type, + ro->path.origin.id, + area->ospf6->router_id, area->lsdb); + if (old) + ospf6_lsa_purge(old); + ospf6_route_remove(ro, area->summary_router); + } + + /* Schedule Router-LSA for each area (ABR status may change) */ + for (ALL_LIST_ELEMENTS(area->ospf6->area_list, node, nnode, oa)) + /* update B bit for each area */ + OSPF6_ROUTER_LSA_SCHEDULE(oa); +} + +/* RFC 2328 12.4.3. Summary-LSAs */ +/* Returns 1 if a summary LSA has been generated for the area */ +/* This is used by the area/range logic to add/remove blackhole routes */ +int ospf6_abr_originate_summary_to_area(struct ospf6_route *route, + struct ospf6_area *area) +{ + struct ospf6_lsa *lsa, *old = NULL; + struct ospf6_route *summary, *range = NULL; + struct ospf6_area *route_area; + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + caddr_t p; + struct ospf6_inter_prefix_lsa *prefix_lsa; + struct ospf6_inter_router_lsa *router_lsa; + struct ospf6_route_table *summary_table = NULL; + uint16_t type; + int is_debug = 0; + + if (IS_OSPF6_DEBUG_ABR) { + char buf[BUFSIZ]; + + if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(AF_INET, + &ADV_ROUTER_IN_PREFIX(&route->prefix), buf, + sizeof(buf)); + else + prefix2str(&route->prefix, buf, sizeof(buf)); + + zlog_debug("%s : start area %s, route %s", __func__, area->name, + buf); + } + + if (route->type == OSPF6_DEST_TYPE_ROUTER) + summary_table = area->summary_router; + else + summary_table = area->summary_prefix; + + summary = ospf6_route_lookup(&route->prefix, summary_table); + if (summary) { + old = ospf6_lsdb_lookup(summary->path.origin.type, + summary->path.origin.id, + area->ospf6->router_id, area->lsdb); + /* Reset the OSPF6_LSA_UNAPPROVED flag */ + if (old) + UNSET_FLAG(old->flag, OSPF6_LSA_UNAPPROVED); + } + + /* Only destination type network, range or ASBR are considered */ + if (route->type != OSPF6_DEST_TYPE_NETWORK + && route->type != OSPF6_DEST_TYPE_RANGE + && ((route->type != OSPF6_DEST_TYPE_ROUTER) + || !CHECK_FLAG(route->path.router_bits, OSPF6_ROUTER_BIT_E))) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: Route type %d flag 0x%x is none of network, range nor ASBR, ignore", + __func__, route->type, route->path.router_bits); + return 0; + } + + /* AS External routes are never considered */ + if (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 + || route->path.type == OSPF6_PATH_TYPE_EXTERNAL2) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Path type is external, skip", + __func__); + return 0; + } + + /* do not generate if the path's area is the same as target area */ + if (route->path.area_id == area->area_id) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: The route is in the area itself, ignore", + __func__); + return 0; + } + + if (route->type == OSPF6_DEST_TYPE_NETWORK) { + bool filter = false; + + route_area = + ospf6_area_lookup(route->path.area_id, area->ospf6); + assert(route_area); + + /* Check export-list */ + if (EXPORT_LIST(route_area) + && access_list_apply(EXPORT_LIST(route_area), + &route->prefix) + == FILTER_DENY) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: prefix %pFX was denied by export-list", + __func__, &route->prefix); + filter = true; + } + + /* Check output prefix-list */ + if (PREFIX_LIST_OUT(route_area) + && prefix_list_apply(PREFIX_LIST_OUT(route_area), + &route->prefix) + != PREFIX_PERMIT) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: prefix %pFX was denied by prefix-list out", + __func__, &route->prefix); + filter = true; + } + + /* Check import-list */ + if (IMPORT_LIST(area) + && access_list_apply(IMPORT_LIST(area), &route->prefix) + == FILTER_DENY) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: prefix %pFX was denied by import-list", + __func__, &route->prefix); + filter = true; + } + + /* Check input prefix-list */ + if (PREFIX_LIST_IN(area) + && prefix_list_apply(PREFIX_LIST_IN(area), &route->prefix) + != PREFIX_PERMIT) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: prefix %pFX was denied by prefix-list in", + __func__, &route->prefix); + filter = true; + } + + if (filter) { + if (summary) { + ospf6_route_remove(summary, summary_table); + if (old) + ospf6_lsa_purge(old); + } + return 0; + } + } + + /* do not generate if the nexthops belongs to the target area */ + if (ospf6_abr_nexthops_belong_to_area(route, area)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: The route's nexthop is in the same area, ignore", + __func__); + return 0; + } + + if (route->type == OSPF6_DEST_TYPE_ROUTER) { + if (ADV_ROUTER_IN_PREFIX(&route->prefix) + == area->ospf6->router_id) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s: Skipping ASBR announcement for ABR (%pI4)", + __func__, + &ADV_ROUTER_IN_PREFIX(&route->prefix)); + return 0; + } + } + + if (route->type == OSPF6_DEST_TYPE_ROUTER) { + if (IS_OSPF6_DEBUG_ABR + || IS_OSPF6_DEBUG_ORIGINATE(INTER_ROUTER)) { + is_debug++; + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "Originating summary in area %s for ASBR %pI4", + area->name, + &ADV_ROUTER_IN_PREFIX(&route->prefix)); + } + } else { + if (IS_OSPF6_DEBUG_ABR + || IS_OSPF6_DEBUG_ORIGINATE(INTER_PREFIX)) + is_debug++; + + if (route->type == OSPF6_DEST_TYPE_NETWORK && + route->path.origin.type == + htons(OSPF6_LSTYPE_INTER_PREFIX)) { + if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { + if (is_debug) + zlog_debug( + "%s: route %pFX with cost %u is not best, ignore.", + __func__, &route->prefix, + route->path.cost); + return 0; + } + } + + if (route->path.origin.type == + htons(OSPF6_LSTYPE_INTRA_PREFIX)) { + if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { + if (is_debug) + zlog_debug( + "%s: intra-prefix route %pFX with cost %u is not best, ignore.", + __func__, &route->prefix, + route->path.cost); + return 0; + } + } + + if (is_debug) + zlog_debug( + "Originating summary in area %s for %pFX cost %u", + area->name, &route->prefix, route->path.cost); + } + + /* if this route has just removed, remove corresponding LSA */ + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) { + if (is_debug) + zlog_debug( + "The route has just removed, purge previous LSA"); + + if (route->type == OSPF6_DEST_TYPE_RANGE) { + /* Whether the route have active longer prefix */ + if (!CHECK_FLAG(route->flag, + OSPF6_ROUTE_ACTIVE_SUMMARY)) { + if (is_debug) + zlog_debug( + "The range is not active. withdraw"); + + ospf6_abr_delete_route(summary, summary_table, + old); + } + } else if (old) { + ospf6_route_remove(summary, summary_table); + ospf6_lsa_purge(old); + } + return 0; + } + + if ((route->type == OSPF6_DEST_TYPE_ROUTER) + && (IS_AREA_STUB(area) || IS_AREA_NSSA(area))) { + if (is_debug) + zlog_debug( + "Area has been stubbed, purge Inter-Router LSA"); + + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + + if (area->no_summary + && (route->path.subtype != OSPF6_PATH_SUBTYPE_DEFAULT_RT)) { + if (is_debug) + zlog_debug("Area has been stubbed, purge prefix LSA"); + + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + + /* do not generate if the route cost is greater or equal to LSInfinity + */ + if (route->path.cost >= OSPF_LS_INFINITY) { + /* When we're clearing the range route because all active + * prefixes + * under the range are gone, we set the range's cost to + * OSPF_AREA_RANGE_COST_UNSPEC, which is > OSPF_LS_INFINITY. We + * don't want to trigger the code here for that. This code is + * for + * handling routes that have gone to infinity. The range removal + * happens + * elsewhere. + */ + if ((route->type != OSPF6_DEST_TYPE_RANGE) + && (route->path.cost != OSPF_AREA_RANGE_COST_UNSPEC)) { + if (is_debug) + zlog_debug( + "The cost exceeds LSInfinity, withdraw"); + if (old) + ospf6_lsa_purge(old); + return 0; + } + } + + /* if this is a route to ASBR */ + if (route->type == OSPF6_DEST_TYPE_ROUTER) { + /* Only the preferred best path is considered */ + if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST)) { + if (is_debug) + zlog_debug( + "This is the secondary path to the ASBR, ignore"); + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + + /* Do not generate if area is NSSA */ + route_area = + ospf6_area_lookup(route->path.area_id, area->ospf6); + assert(route_area); + + if (IS_AREA_NSSA(route_area)) { + if (is_debug) + zlog_debug( + "%s: The route comes from NSSA area, skip", + __func__); + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + + /* Do not generate if the area is stub */ + /* XXX */ + } + + /* if this is an intra-area route, this may be suppressed by aggregation + */ + if (route->type == OSPF6_DEST_TYPE_NETWORK + && route->path.type == OSPF6_PATH_TYPE_INTRA) { + /* search for configured address range for the route's area */ + route_area = + ospf6_area_lookup(route->path.area_id, area->ospf6); + assert(route_area); + range = ospf6_route_lookup_bestmatch(&route->prefix, + route_area->range_table); + + /* ranges are ignored when originate backbone routes to transit + area. + Otherwise, if ranges are configured, the route is suppressed. + */ + if (range && !CHECK_FLAG(range->flag, OSPF6_ROUTE_REMOVE) + && (route->path.area_id != OSPF_AREA_BACKBONE + || !IS_AREA_TRANSIT(area))) { + if (is_debug) + zlog_debug( + "Suppressed by range %pFX of area %s", + &range->prefix, route_area->name); + /* The existing summary route could be a range, don't + * remove it in this case + */ + if (summary && summary->type != OSPF6_DEST_TYPE_RANGE) + ospf6_abr_delete_route(summary, summary_table, + old); + return 0; + } + } + + /* If this is a configured address range */ + if (route->type == OSPF6_DEST_TYPE_RANGE) { + /* If DoNotAdvertise is set */ + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { + if (is_debug) + zlog_debug( + "This is the range with DoNotAdvertise set. ignore"); + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + + /* If there are no active prefixes in this range, remove */ + if (!CHECK_FLAG(route->flag, OSPF6_ROUTE_ACTIVE_SUMMARY)) { + if (is_debug) + zlog_debug("The range is not active. withdraw"); + ospf6_abr_delete_route(summary, summary_table, old); + return 0; + } + } + + /* the route is going to be originated. store it in area's summary_table + */ + if (summary == NULL) { + summary = ospf6_route_copy(route); + summary->path.origin.adv_router = area->ospf6->router_id; + + if (route->type == OSPF6_DEST_TYPE_ROUTER) { + summary->path.origin.type = + htons(OSPF6_LSTYPE_INTER_ROUTER); + summary->path.origin.id = + ADV_ROUTER_IN_PREFIX(&route->prefix); + } else { + struct ospf6_lsa *old; + + summary->path.origin.type = + htons(OSPF6_LSTYPE_INTER_PREFIX); + + /* Try to reuse LS-ID from previous running instance. */ + old = ospf6_find_inter_prefix_lsa(area->ospf6, area, + &route->prefix); + if (old) + summary->path.origin.id = old->header->id; + else + summary->path.origin.id = ospf6_new_ls_id( + summary->path.origin.type, + summary->path.origin.adv_router, + area->lsdb); + } + summary = ospf6_route_add(summary, summary_table); + } else { + summary->type = route->type; + monotime(&summary->changed); + } + + summary->prefix_options = route->prefix_options; + summary->path.router_bits = route->path.router_bits; + summary->path.options[0] = route->path.options[0]; + summary->path.options[1] = route->path.options[1]; + summary->path.options[2] = route->path.options[2]; + summary->path.area_id = area->area_id; + summary->path.type = OSPF6_PATH_TYPE_INTER; + summary->path.subtype = route->path.subtype; + summary->path.cost = route->path.cost; + /* summary->nexthop[0] = route->nexthop[0]; */ + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + + if (route->type == OSPF6_DEST_TYPE_ROUTER) { + router_lsa = (struct ospf6_inter_router_lsa *) + ospf6_lsa_header_end(lsa_header); + p = (caddr_t)router_lsa + sizeof(struct ospf6_inter_router_lsa); + + /* Fill Inter-Area-Router-LSA */ + router_lsa->options[0] = route->path.options[0]; + router_lsa->options[1] = route->path.options[1]; + router_lsa->options[2] = route->path.options[2]; + OSPF6_ABR_SUMMARY_METRIC_SET(router_lsa, route->path.cost); + router_lsa->router_id = ADV_ROUTER_IN_PREFIX(&route->prefix); + type = htons(OSPF6_LSTYPE_INTER_ROUTER); + } else { + prefix_lsa = (struct ospf6_inter_prefix_lsa *) + ospf6_lsa_header_end(lsa_header); + p = (caddr_t)prefix_lsa + sizeof(struct ospf6_inter_prefix_lsa); + + /* Fill Inter-Area-Prefix-LSA */ + OSPF6_ABR_SUMMARY_METRIC_SET(prefix_lsa, route->path.cost); + prefix_lsa->prefix.prefix_length = route->prefix.prefixlen; + prefix_lsa->prefix.prefix_options = route->prefix_options; + + /* set Prefix */ + memcpy(p, &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); + ospf6_prefix_apply_mask(&prefix_lsa->prefix); + p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); + type = htons(OSPF6_LSTYPE_INTER_PREFIX); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = type; + lsa_header->id = summary->path.origin.id; + lsa_header->adv_router = area->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, area->lsdb); + lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Reset the unapproved flag */ + UNSET_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED); + + /* Originate */ + ospf6_lsa_originate_area(lsa, area); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : finish area %s", __func__, area->name); + + return 1; +} + +void ospf6_abr_range_reset_cost(struct ospf6 *ospf6) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct ospf6_route *range; + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) { + for (range = ospf6_route_head(oa->range_table); range; + range = ospf6_route_next(range)) + OSPF6_ABR_RANGE_CLEAR_COST(range); + for (range = ospf6_route_head(oa->nssa_range_table); range; + range = ospf6_route_next(range)) + OSPF6_ABR_RANGE_CLEAR_COST(range); + } +} + +static inline uint32_t ospf6_abr_range_compute_cost(struct ospf6_route *range, + struct ospf6 *o) +{ + struct ospf6_route *ro; + uint32_t cost = 0; + + for (ro = ospf6_route_match_head(&range->prefix, o->route_table); ro; + ro = ospf6_route_match_next(&range->prefix, ro)) { + if (CHECK_FLAG(ro->flag, OSPF6_ROUTE_REMOVE)) + continue; + if (ro->path.area_id != range->path.area_id) + continue; + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_NSSA_RANGE) + && ro->path.type != OSPF6_PATH_TYPE_EXTERNAL1 + && ro->path.type != OSPF6_PATH_TYPE_EXTERNAL2) + continue; + if (!CHECK_FLAG(range->flag, OSPF6_ROUTE_NSSA_RANGE) + && ro->path.type != OSPF6_PATH_TYPE_INTRA) + continue; + + cost = MAX(cost, ro->path.cost); + } + + return cost; +} + +static inline int +ospf6_abr_range_summary_needs_update(struct ospf6_route *range, uint32_t cost) +{ + int redo_summary = 0; + + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_REMOVE)) { + UNSET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); + redo_summary = 1; + } else if (CHECK_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { + if (range->path.cost != 0) { + range->path.cost = 0; + redo_summary = 1; + } + } else if (cost) { + if ((OSPF6_PATH_COST_IS_CONFIGURED(range->path) + && range->path.cost != range->path.u.cost_config)) { + range->path.cost = range->path.u.cost_config; + SET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); + redo_summary = 1; + } else if (!OSPF6_PATH_COST_IS_CONFIGURED(range->path) + && range->path.cost != cost) { + range->path.cost = cost; + SET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); + redo_summary = 1; + } + } else if (CHECK_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY)) { + /* Cost is zero, meaning no active range */ + UNSET_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY); + range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC; + redo_summary = 1; + } + + return (redo_summary); +} + +void ospf6_abr_range_update(struct ospf6_route *range, struct ospf6 *ospf6) +{ + uint32_t cost = 0; + struct listnode *node, *nnode; + struct ospf6_area *oa; + int summary_orig = 0; + + assert(range->type == OSPF6_DEST_TYPE_RANGE); + oa = ospf6_area_lookup(range->path.area_id, ospf6); + assert(oa); + + /* update range's cost and active flag */ + cost = ospf6_abr_range_compute_cost(range, ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s: range %pFX, cost %d", __func__, &range->prefix, + cost); + + /* Non-zero cost is a proxy for active longer prefixes in this range. + * If there are active routes covered by this range AND either the + * configured + * cost has changed or the summarized cost has changed then redo + * summaries. + * Alternately, if there are no longer active prefixes and there are + * summary announcements, withdraw those announcements. + * + * The don't advertise code relies on the path.cost being set to UNSPEC + * to + * work the first time. Subsequent times the path.cost is not 0 anyway + * if there + * were active ranges. + */ + if (!ospf6_abr_range_summary_needs_update(range, cost)) + return; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s: range %pFX update", __func__, &range->prefix); + + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_NSSA_RANGE)) { + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY) + && !CHECK_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE)) { + ospf6_nssa_lsa_originate(range, oa, true); + summary_orig = 1; + } else { + struct ospf6_lsa *lsa; + + lsa = ospf6_lsdb_lookup(range->path.origin.type, + range->path.origin.id, + ospf6->router_id, oa->lsdb); + if (lsa) + ospf6_lsa_premature_aging(lsa); + } + } else { + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) { + summary_orig += + ospf6_abr_originate_summary_to_area(range, oa); + } + } + + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_ACTIVE_SUMMARY) + && summary_orig) { + if (!CHECK_FLAG(range->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Add discard route"); + + ospf6_zebra_add_discard(range, ospf6); + } + } else { + /* Summary removed or no summary generated as no + * specifics exist */ + if (CHECK_FLAG(range->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Delete discard route"); + + ospf6_zebra_delete_discard(range, ospf6); + } + } +} + +void ospf6_abr_originate_summary(struct ospf6_route *route, struct ospf6 *ospf6) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct ospf6_route *range = NULL; + + if (IS_OSPF6_DEBUG_ABR) { + char buf[BUFSIZ]; + + if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(AF_INET, + &ADV_ROUTER_IN_PREFIX(&route->prefix), buf, + sizeof(buf)); + else + prefix2str(&route->prefix, buf, sizeof(buf)); + + zlog_debug("%s: route %s", __func__, buf); + } + + if (route->type == OSPF6_DEST_TYPE_NETWORK) { + oa = ospf6_area_lookup(route->path.area_id, ospf6); + if (!oa) { + zlog_err("OSPFv6 area lookup failed"); + return; + } + + range = ospf6_route_lookup_bestmatch(&route->prefix, + oa->range_table); + if (range) { + ospf6_abr_range_update(range, ospf6); + } + } + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) + ospf6_abr_originate_summary_to_area(route, oa); +} + +void ospf6_abr_defaults_to_stub(struct ospf6 *o) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct ospf6_route *def, *route; + int type = DEFAULT_ROUTE; + + if (!o->backbone) + return; + + def = ospf6_route_create(o); + def->type = OSPF6_DEST_TYPE_NETWORK; + def->prefix.family = AF_INET6; + def->prefix.prefixlen = 0; + memset(&def->prefix.u.prefix6, 0, sizeof(struct in6_addr)); + def->type = OSPF6_DEST_TYPE_NETWORK; + def->path.type = OSPF6_PATH_TYPE_INTER; + def->path.subtype = OSPF6_PATH_SUBTYPE_DEFAULT_RT; + def->path.area_id = o->backbone->area_id; + def->path.metric_type = metric_type(o, type, 0); + def->path.cost = metric_value(o, type, 0); + + for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) { + if (IS_AREA_STUB(oa) || (IS_AREA_NSSA(oa) && oa->no_summary)) { + /* announce defaults to stubby areas */ + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "Announcing default route into stubby area %s", + oa->name); + UNSET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); + ospf6_abr_originate_summary_to_area(def, oa); + } else { + /* withdraw defaults when an area switches from stub to + * non-stub */ + route = ospf6_route_lookup(&def->prefix, + oa->summary_prefix); + if (route + && (route->path.subtype == def->path.subtype)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "Withdrawing default route from non-stubby area %s", + oa->name); + SET_FLAG(def->flag, OSPF6_ROUTE_REMOVE); + ospf6_abr_originate_summary_to_area(def, oa); + } + } + } + ospf6_route_delete(def); +} + +void ospf6_abr_old_path_update(struct ospf6_route *old_route, + struct ospf6_route *route, + struct ospf6_route_table *table) +{ + struct ospf6_path *o_path = NULL; + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + + for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, o_path)) { + if (o_path->area_id != route->path.area_id + || !ospf6_ls_origin_same(o_path, &route->path)) + continue; + + if ((o_path->cost == route->path.cost) && + (o_path->u.cost_e2 == route->path.u.cost_e2)) + continue; + + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { + for (ALL_LIST_ELEMENTS(old_route->nh_list, rnode, + rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, nh)) + continue; + listnode_delete(old_route->nh_list, rnh); + ospf6_nexthop_delete(rnh); + } + + } + + listnode_delete(old_route->paths, o_path); + ospf6_path_free(o_path); + + for (ALL_LIST_ELEMENTS(old_route->paths, anode, + anext, o_path)) { + ospf6_merge_nexthops(old_route->nh_list, + o_path->nh_list); + } + + if (IS_OSPF6_DEBUG_ABR || IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) + zlog_debug("%s: paths %u nh %u", __func__, + old_route->paths + ? listcount(old_route->paths) + : 0, + old_route->nh_list + ? listcount(old_route->nh_list) + : 0); + + if (table->hook_add) + (*table->hook_add)(old_route); + + if (old_route->path.origin.id == route->path.origin.id && + old_route->path.origin.adv_router == + route->path.origin.adv_router) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata(listhead(old_route->paths)); + old_route->path.origin.type = h_path->origin.type; + old_route->path.origin.id = h_path->origin.id; + old_route->path.origin.adv_router = + h_path->origin.adv_router; + } + } +} + +void ospf6_abr_old_route_remove(struct ospf6_lsa *lsa, struct ospf6_route *old, + struct ospf6_route_table *table) +{ + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s: route %pFX, paths %d", __func__, &old->prefix, + listcount(old->paths)); + + if (listcount(old->paths) > 1) { + struct listnode *anode, *anext, *nnode, *rnode, *rnext; + struct ospf6_path *o_path; + struct ospf6_nexthop *nh, *rnh; + bool nh_updated = false; + + for (ALL_LIST_ELEMENTS(old->paths, anode, anext, o_path)) { + if (o_path->origin.adv_router != lsa->header->adv_router + || o_path->origin.id != lsa->header->id) + continue; + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { + for (ALL_LIST_ELEMENTS(old->nh_list, + rnode, rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, nh)) + continue; + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("deleted nexthop"); + listnode_delete(old->nh_list, rnh); + ospf6_nexthop_delete(rnh); + } + } + listnode_delete(old->paths, o_path); + ospf6_path_free(o_path); + nh_updated = true; + } + + if (nh_updated) { + if (listcount(old->paths)) { + if (IS_OSPF6_DEBUG_ABR + || IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) + zlog_debug("%s: old %pFX updated nh %u", + __func__, &old->prefix, + old->nh_list ? listcount( + old->nh_list) + : 0); + + if (table->hook_add) + (*table->hook_add)(old); + + if ((old->path.origin.id == lsa->header->id) && + (old->path.origin.adv_router + == lsa->header->adv_router)) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata( + listhead(old->paths)); + old->path.origin.type = + h_path->origin.type; + old->path.origin.id = h_path->origin.id; + old->path.origin.adv_router = + h_path->origin.adv_router; + } + } else + ospf6_route_remove(old, table); + } + } else + ospf6_route_remove(old, table); +} + +/* RFC 2328 16.2. Calculating the inter-area routes */ +void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, struct ospf6_area *oa) +{ + struct prefix prefix, abr_prefix; + struct ospf6_route_table *table = NULL; + struct ospf6_route *range, *route, *old = NULL, *old_route; + struct ospf6_route *abr_entry; + uint8_t type = 0; + char options[3] = {0, 0, 0}; + uint8_t prefix_options = 0; + uint32_t cost = 0; + uint8_t router_bits = 0; + char buf[PREFIX2STR_BUFFER]; + int is_debug = 0; + struct ospf6_inter_prefix_lsa *prefix_lsa = NULL; + struct ospf6_inter_router_lsa *router_lsa = NULL; + bool old_entry_updated = false; + struct ospf6_path *path, *o_path, *ecmp_path; + struct listnode *anode; + bool add_route = false; + + memset(&prefix, 0, sizeof(prefix)); + + if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_PREFIX)) { + if (IS_OSPF6_DEBUG_EXAMIN(INTER_PREFIX)) { + is_debug++; + zlog_debug("%s: LSA %s age %d in area %s", __func__, + lsa->name, ospf6_lsa_age_current(lsa), + oa->name); + } + + prefix_lsa = (struct ospf6_inter_prefix_lsa *) + ospf6_lsa_header_end(lsa->header); + prefix.family = AF_INET6; + prefix.prefixlen = prefix_lsa->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, prefix_lsa, + &prefix_lsa->prefix); + if (is_debug) + prefix2str(&prefix, buf, sizeof(buf)); + table = oa->ospf6->route_table; + type = OSPF6_DEST_TYPE_NETWORK; + prefix_options = prefix_lsa->prefix.prefix_options; + cost = OSPF6_ABR_SUMMARY_METRIC(prefix_lsa); + } else if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_ROUTER)) { + if (IS_OSPF6_DEBUG_EXAMIN(INTER_ROUTER)) { + is_debug++; + zlog_debug("%s: LSA %s age %d in area %s", __func__, + lsa->name, ospf6_lsa_age_current(lsa), + oa->name); + } + + router_lsa = (struct ospf6_inter_router_lsa *) + ospf6_lsa_header_end(lsa->header); + ospf6_linkstate_prefix(router_lsa->router_id, htonl(0), &prefix); + if (is_debug) + inet_ntop(AF_INET, &router_lsa->router_id, buf, + sizeof(buf)); + + table = oa->ospf6->brouter_table; + type = OSPF6_DEST_TYPE_ROUTER; + options[0] = router_lsa->options[0]; + options[1] = router_lsa->options[1]; + options[2] = router_lsa->options[2]; + cost = OSPF6_ABR_SUMMARY_METRIC(router_lsa); + SET_FLAG(router_bits, OSPF6_ROUTER_BIT_E); + } else + assert(0); + + /* Find existing route */ + route = ospf6_route_lookup(&prefix, table); + if (route) { + ospf6_route_lock(route); + if (is_debug) + zlog_debug("%s: route %pFX, paths %d", __func__, + &prefix, listcount(route->paths)); + } + while (route && ospf6_route_is_prefix(&prefix, route)) { + if (route->path.area_id == oa->area_id + && route->path.origin.type == lsa->header->type + && !CHECK_FLAG(route->flag, OSPF6_ROUTE_WAS_REMOVED)) { + /* LSA adv. router could be part of route's + * paths list. Find the existing path and set + * old as the route. + */ + if (listcount(route->paths) > 1) { + for (ALL_LIST_ELEMENTS_RO(route->paths, anode, + o_path)) { + if (o_path->origin.id == lsa->header->id + && o_path->origin.adv_router == + lsa->header->adv_router) { + old = route; + + if (is_debug) + zlog_debug( + "%s: old entry found in paths, adv_router %pI4", + __func__, + &o_path->origin.adv_router); + + break; + } + } + } else if (route->path.origin.id == lsa->header->id && + route->path.origin.adv_router == + lsa->header->adv_router) + old = route; + } + route = ospf6_route_next(route); + } + if (route) + ospf6_route_unlock(route); + + /* (1) if cost == LSInfinity or if the LSA is MaxAge */ + if (cost == OSPF_LS_INFINITY) { + if (is_debug) + zlog_debug("cost is LS_INFINITY, ignore"); + if (old) + ospf6_abr_old_route_remove(lsa, old, table); + return; + } + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + if (is_debug) + zlog_debug("%s: LSA %s is MaxAge, ignore", __func__, + lsa->name); + if (old) + ospf6_abr_old_route_remove(lsa, old, table); + return; + } + + + /* (2) if the LSA is self-originated, ignore */ + if (lsa->header->adv_router == oa->ospf6->router_id) { + if (is_debug) + zlog_debug("LSA %s is self-originated, ignore", + lsa->name); + if (old) + ospf6_route_remove(old, table); + return; + } + + /* (3) if the prefix is equal to an active configured address range */ + /* or if the NU bit is set in the prefix */ + if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_PREFIX)) { + /* must have been set in previous block */ + assert(prefix_lsa); + + range = ospf6_route_lookup(&prefix, oa->range_table); + if (range) { + if (is_debug) + zlog_debug( + "Prefix is equal to address range, ignore"); + if (old) + ospf6_route_remove(old, table); + return; + } + + if (CHECK_FLAG(prefix_lsa->prefix.prefix_options, + OSPF6_PREFIX_OPTION_NU)) { + if (is_debug) + zlog_debug("Prefix has the NU bit set, ignore"); + if (old) + ospf6_route_remove(old, table); + return; + } + } + + if (lsa->header->type == htons(OSPF6_LSTYPE_INTER_ROUTER)) { + /* To pass test suites */ + assert(router_lsa); + if (!OSPF6_OPT_ISSET(router_lsa->options, OSPF6_OPT_R) + || !OSPF6_OPT_ISSET(router_lsa->options, OSPF6_OPT_V6)) { + if (is_debug) + zlog_debug( + "Router-LSA has the V6-bit or R-bit unset, ignore"); + if (old) + ospf6_route_remove(old, table); + + return; + } + /* Avoid infinite recursion if someone has maliciously announced + an + Inter-Router LSA for an ABR + */ + if (lsa->header->adv_router == router_lsa->router_id) { + if (is_debug) + zlog_debug( + "Ignoring Inter-Router LSA for an ABR (%s)", + buf); + if (old) + ospf6_route_remove(old, table); + + return; + } + } + + /* (4) if the routing table entry for the ABR does not exist */ + ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &abr_prefix); + abr_entry = ospf6_route_lookup(&abr_prefix, oa->ospf6->brouter_table); + if (abr_entry == NULL || abr_entry->path.area_id != oa->area_id + || CHECK_FLAG(abr_entry->flag, OSPF6_ROUTE_REMOVE) + || !CHECK_FLAG(abr_entry->path.router_bits, OSPF6_ROUTER_BIT_B)) { + if (is_debug) + zlog_debug( + "%s: ABR router entry %pFX does not exist, ignore", + __func__, &abr_prefix); + if (old) { + if (old->type == OSPF6_DEST_TYPE_ROUTER && + oa->intra_brouter_calc) { + if (is_debug) + zlog_debug( + "%s: intra_brouter_calc is on, skip brouter remove: %s (%p)", + __func__, buf, (void *)old); + } else { + if (is_debug) + zlog_debug( + "%s: remove old entry: %s %p ", + __func__, buf, (void *)old); + ospf6_abr_old_route_remove(lsa, old, table); + } + } + return; + } + + /* (5),(6): the path preference is handled by the sorting + in the routing table. Always install the path by substituting + old route (if any). */ + route = ospf6_route_create(oa->ospf6); + + route->type = type; + route->prefix = prefix; + route->prefix_options = prefix_options; + route->path.origin.type = lsa->header->type; + route->path.origin.id = lsa->header->id; + route->path.origin.adv_router = lsa->header->adv_router; + route->path.router_bits = router_bits; + route->path.options[0] = options[0]; + route->path.options[1] = options[1]; + route->path.options[2] = options[2]; + route->path.area_id = oa->area_id; + route->path.type = OSPF6_PATH_TYPE_INTER; + route->path.cost = abr_entry->path.cost + cost; + + /* copy brouter rechable nexthops into the route. */ + ospf6_route_copy_nexthops(route, abr_entry); + + /* (7) If the routes are identical, copy the next hops over to existing + route. ospf6's route table implementation will otherwise string both + routes, but keep the older one as the best route since the routes + are identical. + */ + old = ospf6_route_lookup(&prefix, table); + if (old) { + if (is_debug) + zlog_debug("%s: found old route %pFX, paths %d", + __func__, &prefix, listcount(old->paths)); + } + for (old_route = old; old_route; old_route = old_route->next) { + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if ((old_route->type != route->type) + || (old_route->path.type != route->path.type)) + continue; + + if ((ospf6_route_cmp(route, old_route) != 0)) { + if (is_debug) + zlog_debug( + "%s: old %p %pFX cost %u new route cost %u are not same", + __func__, (void *)old_route, &prefix, + old_route->path.cost, route->path.cost); + + /* Check new route's adv. router is same in one of + * the paths with differed cost, if so remove the + * old path as later new route will be added. + */ + if (listcount(old_route->paths) > 1) + ospf6_abr_old_path_update(old_route, route, + table); + continue; + } + + old_entry_updated = true; + + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + if (o_path->area_id == route->path.area_id + && ospf6_ls_origin_same(o_path, &route->path)) + break; + } + + /* New adv. router for a existing path add to paths list */ + if (o_path == NULL) { + ecmp_path = ospf6_path_dup(&route->path); + + /* Add a nh_list to new ecmp path */ + ospf6_copy_nexthops(ecmp_path->nh_list, route->nh_list); + + /* Add the new path to route's path list */ + listnode_add_sort(old_route->paths, ecmp_path); + + if (is_debug) { + zlog_debug( + "%s: route %pFX cost %u another path %pI4 added with nh %u, effective paths %u nh %u", + __func__, &route->prefix, + old_route->path.cost, + &ecmp_path->origin.adv_router, + listcount(ecmp_path->nh_list), + old_route->paths + ? listcount(old_route->paths) + : 0, + listcount(old_route->nh_list)); + } + } else { + struct ospf6_route *tmp_route; + + tmp_route = ospf6_route_create(oa->ospf6); + + ospf6_copy_nexthops(tmp_route->nh_list, + o_path->nh_list); + + if (!ospf6_route_cmp_nexthops(tmp_route, route)) { + /* adv. router exists in the list, update nhs */ + list_delete_all_node(o_path->nh_list); + ospf6_copy_nexthops(o_path->nh_list, + route->nh_list); + ospf6_route_delete(tmp_route); + } else { + /* adv. router has no change in nhs */ + old_entry_updated = false; + ospf6_route_delete(tmp_route); + continue; + } + } + + /* We added a path or updated a path's nexthops above, + * recompute (old) route nexthops by merging all path nexthops + */ + list_delete_all_node(old_route->nh_list); + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, o_path)) { + ospf6_merge_nexthops(old_route->nh_list, + o_path->nh_list); + } + + if (is_debug) + zlog_debug( + "%s: Update route: %s %p old cost %u new cost %u nh %u", + __func__, buf, (void *)old_route, + old_route->path.cost, route->path.cost, + listcount(old_route->nh_list)); + + /* For Inter-Prefix route: Update RIB/FIB, + * For Inter-Router trigger summary update + */ + if (table->hook_add) + (*table->hook_add)(old_route); + + break; + } + + /* If the old entry is not updated and old entry not found or old entry + * does not match with the new entry then add the new route + */ + if (old_entry_updated == false) { + if ((old == NULL) || (old->type != route->type) || + (old->path.type != route->path.type) || + (old->path.cost != route->path.cost) || + (old->path.router_bits != route->path.router_bits)) + add_route = true; + } + + if (add_route) { + if (is_debug) { + zlog_debug( + "%s: Install new route: %s cost %u nh %u adv_router %pI4", + __func__, buf, route->path.cost, + listcount(route->nh_list), + &route->path.origin.adv_router); + } + + path = ospf6_path_dup(&route->path); + ospf6_copy_nexthops(path->nh_list, abr_entry->nh_list); + listnode_add_sort(route->paths, path); + /* ospf6_ia_add_nw_route (table, &prefix, route); */ + ospf6_route_add(route, table); + } else + /* if we did not add the route remove it */ + ospf6_route_delete(route); +} + +void ospf6_abr_examin_brouter(uint32_t router_id, struct ospf6_route *route, + struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + struct ospf6_area *oa; + uint16_t type; + + oa = ospf6_area_lookup(route->path.area_id, ospf6); + /* + * It is possible to designate a non backbone + * area first. If that is the case safely + * fall out of this function. + */ + if (oa == NULL) + return; + + type = htons(OSPF6_LSTYPE_INTER_ROUTER); + for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router_id, lsa)) + ospf6_abr_examin_summary(lsa, oa); + + type = htons(OSPF6_LSTYPE_INTER_PREFIX); + for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router_id, lsa)) + ospf6_abr_examin_summary(lsa, oa); +} + +void ospf6_abr_prefix_resummarize(struct ospf6 *o) +{ + struct ospf6_route *route; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Re-examining Inter-Prefix Summaries"); + + for (route = ospf6_route_head(o->route_table); route; + route = ospf6_route_next(route)) + ospf6_abr_originate_summary(route, o); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Finished re-examining Inter-Prefix Summaries"); +} + + +/* Display functions */ +static char *ospf6_inter_area_prefix_lsa_get_prefix_str(struct ospf6_lsa *lsa, + char *buf, int buflen, + int pos) +{ + struct ospf6_inter_prefix_lsa *prefix_lsa; + struct in6_addr in6; + char tbuf[16]; + + if (lsa != NULL) { + prefix_lsa = (struct ospf6_inter_prefix_lsa *) + ospf6_lsa_header_end(lsa->header); + + ospf6_prefix_in6_addr(&in6, prefix_lsa, &prefix_lsa->prefix); + if (buf) { + inet_ntop(AF_INET6, &in6, buf, buflen); + snprintf(tbuf, sizeof(tbuf), "/%d", + prefix_lsa->prefix.prefix_length); + strlcat(buf, tbuf, buflen); + } + } + + return (buf); +} + +static int ospf6_inter_area_prefix_lsa_show(struct vty *vty, + struct ospf6_lsa *lsa, + json_object *json_obj, + bool use_json) +{ + struct ospf6_inter_prefix_lsa *prefix_lsa; + char buf[INET6_ADDRSTRLEN]; + + prefix_lsa = (struct ospf6_inter_prefix_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (use_json) { + json_object_int_add( + json_obj, "metric", + (unsigned long)OSPF6_ABR_SUMMARY_METRIC(prefix_lsa)); + ospf6_prefix_options_printbuf(prefix_lsa->prefix.prefix_options, + buf, sizeof(buf)); + json_object_string_add(json_obj, "prefixOptions", buf); + json_object_string_add( + json_obj, "prefix", + ospf6_inter_area_prefix_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 0)); + } else { + vty_out(vty, " Metric: %lu\n", + (unsigned long)OSPF6_ABR_SUMMARY_METRIC(prefix_lsa)); + + ospf6_prefix_options_printbuf(prefix_lsa->prefix.prefix_options, + buf, sizeof(buf)); + vty_out(vty, " Prefix Options: %s\n", buf); + + vty_out(vty, " Prefix: %s\n", + ospf6_inter_area_prefix_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 0)); + } + + return 0; +} + +static char *ospf6_inter_area_router_lsa_get_prefix_str(struct ospf6_lsa *lsa, + char *buf, int buflen, + int pos) +{ + struct ospf6_inter_router_lsa *router_lsa; + + if (lsa != NULL) { + router_lsa = (struct ospf6_inter_router_lsa *) + ospf6_lsa_header_end(lsa->header); + + + if (buf) + inet_ntop(AF_INET, &router_lsa->router_id, buf, buflen); + } + + return (buf); +} + +static int ospf6_inter_area_router_lsa_show(struct vty *vty, + struct ospf6_lsa *lsa, + json_object *json_obj, + bool use_json) +{ + struct ospf6_inter_router_lsa *router_lsa; + char buf[64]; + + router_lsa = (struct ospf6_inter_router_lsa *)ospf6_lsa_header_end( + lsa->header); + + ospf6_options_printbuf(router_lsa->options, buf, sizeof(buf)); + if (use_json) { + json_object_string_add(json_obj, "options", buf); + json_object_int_add( + json_obj, "metric", + (unsigned long)OSPF6_ABR_SUMMARY_METRIC(router_lsa)); + } else { + vty_out(vty, " Options: %s\n", buf); + vty_out(vty, " Metric: %lu\n", + (unsigned long)OSPF6_ABR_SUMMARY_METRIC(router_lsa)); + } + + inet_ntop(AF_INET, &router_lsa->router_id, buf, sizeof(buf)); + if (use_json) + json_object_string_add(json_obj, "destinationRouterId", buf); + else + vty_out(vty, " Destination Router ID: %s\n", buf); + + return 0; +} + +/* Debug commands */ +DEFUN (debug_ospf6_abr, + debug_ospf6_abr_cmd, + "debug ospf6 abr", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ABR function\n" + ) +{ + OSPF6_DEBUG_ABR_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_abr, + no_debug_ospf6_abr_cmd, + "no debug ospf6 abr", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ABR function\n" + ) +{ + OSPF6_DEBUG_ABR_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_abr(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_ABR) + vty_out(vty, "debug ospf6 abr\n"); + return 0; +} + +void install_element_ospf6_debug_abr(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_abr_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_abr_cmd); + install_element(CONFIG_NODE, &debug_ospf6_abr_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_abr_cmd); +} + +static struct ospf6_lsa_handler inter_prefix_handler = { + .lh_type = OSPF6_LSTYPE_INTER_PREFIX, + .lh_name = "Inter-Prefix", + .lh_short_name = "IAP", + .lh_show = ospf6_inter_area_prefix_lsa_show, + .lh_get_prefix_str = ospf6_inter_area_prefix_lsa_get_prefix_str, + .lh_debug = 0}; + +static struct ospf6_lsa_handler inter_router_handler = { + .lh_type = OSPF6_LSTYPE_INTER_ROUTER, + .lh_name = "Inter-Router", + .lh_short_name = "IAR", + .lh_show = ospf6_inter_area_router_lsa_show, + .lh_get_prefix_str = ospf6_inter_area_router_lsa_get_prefix_str, + .lh_debug = 0}; + +void ospf6_abr_init(void) +{ + ospf6_install_lsa_handler(&inter_prefix_handler); + ospf6_install_lsa_handler(&inter_router_handler); +} diff --git a/ospf6d/ospf6_abr.h b/ospf6d/ospf6_abr.h new file mode 100644 index 0000000..52686ed --- /dev/null +++ b/ospf6d/ospf6_abr.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2004 Yasuhiro Ohara + */ + +#ifndef OSPF6_ABR_H +#define OSPF6_ABR_H + +/* for struct ospf6_route */ +#include "ospf6_route.h" +/* for struct ospf6_prefix */ +#include "ospf6_proto.h" + +/* Debug option */ +extern unsigned char conf_debug_ospf6_abr; +#define OSPF6_DEBUG_ABR_ON() (conf_debug_ospf6_abr = 1) +#define OSPF6_DEBUG_ABR_OFF() (conf_debug_ospf6_abr = 0) +#define IS_OSPF6_DEBUG_ABR (conf_debug_ospf6_abr) + +/* Inter-Area-Prefix-LSA */ +#define OSPF6_INTER_PREFIX_LSA_MIN_SIZE 4U /* w/o IPv6 prefix */ +struct ospf6_inter_prefix_lsa { + uint32_t metric; + struct ospf6_prefix prefix; +}; + +/* Inter-Area-Router-LSA */ +#define OSPF6_INTER_ROUTER_LSA_FIX_SIZE 12U +struct ospf6_inter_router_lsa { + uint8_t mbz; + uint8_t options[3]; + uint32_t metric; + uint32_t router_id; +}; + +#define OSPF6_ABR_SUMMARY_METRIC(E) \ + (ntohl((E)->metric & htonl(OSPF6_EXT_PATH_METRIC_MAX))) +#define OSPF6_ABR_SUMMARY_METRIC_SET(E, C) \ + { \ + (E)->metric &= htonl(0x00000000); \ + (E)->metric |= htonl(OSPF6_EXT_PATH_METRIC_MAX) & htonl(C); \ + } + +#define OSPF6_ABR_RANGE_CLEAR_COST(range) (range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC) +#define IS_OSPF6_ABR(o) ((o)->flag & OSPF6_FLAG_ABR) + +extern bool ospf6_check_and_set_router_abr(struct ospf6 *o); + +extern void ospf6_abr_enable_area(struct ospf6_area *oa); +extern void ospf6_abr_disable_area(struct ospf6_area *oa); + +extern int ospf6_abr_originate_summary_to_area(struct ospf6_route *route, + struct ospf6_area *area); +extern void ospf6_abr_originate_summary(struct ospf6_route *route, + struct ospf6 *ospf6); +extern void ospf6_abr_examin_summary(struct ospf6_lsa *lsa, + struct ospf6_area *oa); +extern void ospf6_abr_defaults_to_stub(struct ospf6 *ospf6); +extern void ospf6_abr_examin_brouter(uint32_t router_id, + struct ospf6_route *route, + struct ospf6 *ospf6); +extern void ospf6_abr_range_reset_cost(struct ospf6 *ospf6); +extern void ospf6_abr_prefix_resummarize(struct ospf6 *ospf6); + +extern int config_write_ospf6_debug_abr(struct vty *vty); +extern void install_element_ospf6_debug_abr(void); +extern int ospf6_abr_config_write(struct vty *vty); +extern void ospf6_abr_old_route_remove(struct ospf6_lsa *lsa, + struct ospf6_route *old, + struct ospf6_route_table *table); +extern void ospf6_abr_old_path_update(struct ospf6_route *old_route, + struct ospf6_route *route, + struct ospf6_route_table *table); +extern void ospf6_abr_init(void); +extern void ospf6_abr_range_update(struct ospf6_route *range, + struct ospf6 *ospf6); +extern void ospf6_abr_remove_unapproved_summaries(struct ospf6 *ospf6); +extern int ospf6_ls_origin_same(struct ospf6_path *o_path, + struct ospf6_path *r_path); + +#endif /*OSPF6_ABR_H*/ diff --git a/ospf6d/ospf6_area.c b/ospf6d/ospf6_area.c new file mode 100644 index 0000000..cf5479e --- /dev/null +++ b/ospf6d/ospf6_area.c @@ -0,0 +1,1463 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "linklist.h" +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "if.h" +#include "prefix.h" +#include "table.h" +#include "plist.h" +#include "filter.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_spf.h" +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_message.h" +#include "ospf6_neighbor.h" +#include "ospf6_interface.h" +#include "ospf6_intra.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6_zebra.h" +#include "ospf6d.h" +#include "lib/json.h" +#include "ospf6_nssa.h" +#include "ospf6d/ospf6_area_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_AREA, "OSPF6 area"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PLISTNAME, "Prefix list name"); + +int str2area_id(const char *str, uint32_t *area_id, int *area_id_fmt) +{ + char *ep; + + *area_id = htonl(strtoul(str, &ep, 10)); + if (*ep && inet_pton(AF_INET, str, area_id) != 1) + return -1; + + *area_id_fmt = + !*ep ? OSPF6_AREA_FMT_DECIMAL : OSPF6_AREA_FMT_DOTTEDQUAD; + + return 0; +} + +void area_id2str(char *buf, int len, uint32_t area_id, int area_id_fmt) +{ + if (area_id_fmt == OSPF6_AREA_FMT_DECIMAL) + snprintf(buf, len, "%u", ntohl(area_id)); + else + inet_ntop(AF_INET, &area_id, buf, len); +} + +int ospf6_area_cmp(void *va, void *vb) +{ + struct ospf6_area *oa = (struct ospf6_area *)va; + struct ospf6_area *ob = (struct ospf6_area *)vb; + return (ntohl(oa->area_id) < ntohl(ob->area_id) ? -1 : 1); +} + +/* schedule routing table recalculation */ +static void ospf6_area_lsdb_hook_add(struct ospf6_lsa *lsa) +{ + switch (ntohs(lsa->header->type)) { + + case OSPF6_LSTYPE_ROUTER: + case OSPF6_LSTYPE_NETWORK: + if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) { + zlog_debug("%s Examin LSA %s", __func__, lsa->name); + zlog_debug(" Schedule SPF Calculation for %s", + OSPF6_AREA(lsa->lsdb->data)->name); + } + ospf6_spf_schedule( + OSPF6_PROCESS(OSPF6_AREA(lsa->lsdb->data)->ospf6), + ospf6_lsadd_to_spf_reason(lsa)); + break; + + case OSPF6_LSTYPE_INTRA_PREFIX: + ospf6_intra_prefix_lsa_add(lsa); + break; + + case OSPF6_LSTYPE_INTER_PREFIX: + case OSPF6_LSTYPE_INTER_ROUTER: + ospf6_abr_examin_summary(lsa, + (struct ospf6_area *)lsa->lsdb->data); + break; + + case OSPF6_LSTYPE_TYPE_7: + ospf6_asbr_lsa_add(lsa); + break; + + default: + break; + } +} + +static void ospf6_area_lsdb_hook_remove(struct ospf6_lsa *lsa) +{ + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_ROUTER: + case OSPF6_LSTYPE_NETWORK: + if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) { + zlog_debug("LSA disappearing: %s", lsa->name); + zlog_debug("Schedule SPF Calculation for %s", + OSPF6_AREA(lsa->lsdb->data)->name); + } + ospf6_spf_schedule( + OSPF6_PROCESS(OSPF6_AREA(lsa->lsdb->data)->ospf6), + ospf6_lsremove_to_spf_reason(lsa)); + break; + + case OSPF6_LSTYPE_INTRA_PREFIX: + ospf6_intra_prefix_lsa_remove(lsa); + break; + + case OSPF6_LSTYPE_INTER_PREFIX: + case OSPF6_LSTYPE_INTER_ROUTER: + ospf6_abr_examin_summary(lsa, + (struct ospf6_area *)lsa->lsdb->data); + break; + case OSPF6_LSTYPE_TYPE_7: + ospf6_asbr_lsa_remove(lsa, NULL); + break; + default: + break; + } +} + +static void ospf6_area_route_hook_add(struct ospf6_route *route) +{ + struct ospf6_area *oa = route->table->scope; + struct ospf6 *ospf6 = oa->ospf6; + struct ospf6_route *copy; + + copy = ospf6_route_copy(route); + ospf6_route_add(copy, ospf6->route_table); +} + +static void ospf6_area_route_hook_remove(struct ospf6_route *route) +{ + struct ospf6_area *oa = route->table->scope; + struct ospf6 *ospf6 = oa->ospf6; + struct ospf6_route *copy; + + copy = ospf6_route_lookup_identical(route, ospf6->route_table); + if (copy) + ospf6_route_remove(copy, ospf6->route_table); +} + +static void ospf6_area_stub_update(struct ospf6_area *area) +{ + + if (IS_AREA_STUB(area)) { + if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) + zlog_debug("Stubbing out area for area %s", area->name); + OSPF6_OPT_CLEAR(area->options, OSPF6_OPT_E); + ospf6_asbr_remove_externals_from_area(area); + } else if (IS_AREA_ENABLED(area)) { + if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) + zlog_debug("Normal area for area %s", area->name); + OSPF6_OPT_SET(area->options, OSPF6_OPT_E); + ospf6_asbr_send_externals_to_area(area); + } + + OSPF6_ROUTER_LSA_SCHEDULE(area); +} + +static int ospf6_area_stub_set(struct ospf6 *ospf6, struct ospf6_area *area) +{ + if (!IS_AREA_STUB(area)) { + /* Disable NSSA first. */ + ospf6_area_nssa_unset(ospf6, area); + + SET_FLAG(area->flag, OSPF6_AREA_STUB); + ospf6_area_stub_update(area); + } + + return 1; +} + +void ospf6_area_stub_unset(struct ospf6 *ospf6, struct ospf6_area *area) +{ + if (IS_AREA_STUB(area)) { + UNSET_FLAG(area->flag, OSPF6_AREA_STUB); + ospf6_area_stub_update(area); + } +} + +static void ospf6_area_no_summary_set(struct ospf6 *ospf6, + struct ospf6_area *area) +{ + if (area) { + if (!area->no_summary) { + area->no_summary = 1; + ospf6_abr_range_reset_cost(ospf6); + ospf6_abr_prefix_resummarize(ospf6); + } + } +} + +static void ospf6_area_no_summary_unset(struct ospf6 *ospf6, + struct ospf6_area *area) +{ + if (area) { + if (area->no_summary) { + area->no_summary = 0; + ospf6_abr_range_reset_cost(ospf6); + ospf6_abr_prefix_resummarize(ospf6); + } + } +} + +static void ospf6_nssa_default_originate_set(struct ospf6 *ospf6, + struct ospf6_area *area, + int metric, int metric_type) +{ + if (!area->nssa_default_originate.enabled) { + area->nssa_default_originate.enabled = true; + if (++ospf6->nssa_default_import_check.refcnt == 1) { + ospf6->nssa_default_import_check.status = false; + ospf6_zebra_import_default_route(ospf6, false); + } + } + + area->nssa_default_originate.metric_value = metric; + area->nssa_default_originate.metric_type = metric_type; +} + +static void ospf6_nssa_default_originate_unset(struct ospf6 *ospf6, + struct ospf6_area *area) +{ + if (area->nssa_default_originate.enabled) { + area->nssa_default_originate.enabled = false; + if (--ospf6->nssa_default_import_check.refcnt == 0) { + ospf6->nssa_default_import_check.status = false; + ospf6_zebra_import_default_route(ospf6, true); + } + area->nssa_default_originate.metric_value = -1; + area->nssa_default_originate.metric_type = -1; + } +} + +/** + * Make new area structure. + * + * @param area_id - ospf6 area ID + * @param o - ospf6 instance + * @param df - display format for area ID + */ +struct ospf6_area *ospf6_area_create(uint32_t area_id, struct ospf6 *o, int df) +{ + struct ospf6_area *oa; + + oa = XCALLOC(MTYPE_OSPF6_AREA, sizeof(struct ospf6_area)); + + switch (df) { + case OSPF6_AREA_FMT_DECIMAL: + snprintf(oa->name, sizeof(oa->name), "%u", ntohl(area_id)); + break; + default: + case OSPF6_AREA_FMT_DOTTEDQUAD: + inet_ntop(AF_INET, &area_id, oa->name, sizeof(oa->name)); + break; + } + + oa->area_id = area_id; + oa->if_list = list_new(); + + oa->lsdb = ospf6_lsdb_create(oa); + oa->lsdb->hook_add = ospf6_area_lsdb_hook_add; + oa->lsdb->hook_remove = ospf6_area_lsdb_hook_remove; + oa->lsdb_self = ospf6_lsdb_create(oa); + oa->temp_router_lsa_lsdb = ospf6_lsdb_create(oa); + + oa->spf_table = OSPF6_ROUTE_TABLE_CREATE(AREA, SPF_RESULTS); + oa->spf_table->scope = oa; + oa->route_table = OSPF6_ROUTE_TABLE_CREATE(AREA, ROUTES); + oa->route_table->scope = oa; + oa->route_table->hook_add = ospf6_area_route_hook_add; + oa->route_table->hook_remove = ospf6_area_route_hook_remove; + + oa->range_table = OSPF6_ROUTE_TABLE_CREATE(AREA, PREFIX_RANGES); + oa->range_table->scope = oa; + oa->nssa_range_table = OSPF6_ROUTE_TABLE_CREATE(AREA, PREFIX_RANGES); + oa->nssa_range_table->scope = oa; + oa->summary_prefix = OSPF6_ROUTE_TABLE_CREATE(AREA, SUMMARY_PREFIXES); + oa->summary_prefix->scope = oa; + oa->summary_router = OSPF6_ROUTE_TABLE_CREATE(AREA, SUMMARY_ROUTERS); + oa->summary_router->scope = oa; + oa->router_lsa_size_limit = 1024 + 256; + + /* set default options */ + if (CHECK_FLAG(o->flag, OSPF6_STUB_ROUTER)) { + OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_V6); + OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_R); + } else { + OSPF6_OPT_SET(oa->options, OSPF6_OPT_V6); + OSPF6_OPT_SET(oa->options, OSPF6_OPT_R); + } + + OSPF6_OPT_SET(oa->options, OSPF6_OPT_E); + + SET_FLAG(oa->flag, OSPF6_AREA_ACTIVE); + SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); + + oa->ospf6 = o; + listnode_add_sort(o->area_list, oa); + + if (area_id == OSPF_AREA_BACKBONE) { + o->backbone = oa; + } + + return oa; +} + +void ospf6_area_delete(struct ospf6_area *oa) +{ + struct listnode *n; + struct ospf6_interface *oi; + + /* The ospf6_interface structs store configuration + * information which should not be lost/reset when + * deleting an area. + * So just detach the interface from the area and + * keep it around. */ + for (ALL_LIST_ELEMENTS_RO(oa->if_list, n, oi)) { + oi->area = NULL; + + struct listnode *node; + struct listnode *nnode; + struct ospf6_neighbor *on; + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) + ospf6_neighbor_delete(on); + } + + list_delete(&oa->if_list); + + ospf6_lsdb_delete(oa->lsdb); + ospf6_lsdb_delete(oa->lsdb_self); + ospf6_lsdb_delete(oa->temp_router_lsa_lsdb); + + ospf6_spf_table_finish(oa->spf_table); + ospf6_route_table_delete(oa->spf_table); + ospf6_route_table_delete(oa->route_table); + + ospf6_route_table_delete(oa->range_table); + ospf6_route_table_delete(oa->nssa_range_table); + ospf6_route_table_delete(oa->summary_prefix); + ospf6_route_table_delete(oa->summary_router); + + listnode_delete(oa->ospf6->area_list, oa); + oa->ospf6 = NULL; + + /* free area */ + XFREE(MTYPE_OSPF6_AREA, oa); +} + +struct ospf6_area *ospf6_area_lookup_by_area_id(uint32_t area_id) +{ + struct ospf6_area *oa; + struct listnode *n, *node, *nnode; + struct ospf6 *ospf6; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) + if (oa->area_id == area_id) + return oa; + } + return (struct ospf6_area *)NULL; +} + +struct ospf6_area *ospf6_area_lookup(uint32_t area_id, struct ospf6 *ospf6) +{ + struct ospf6_area *oa; + struct listnode *n; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) + if (oa->area_id == area_id) + return oa; + + return (struct ospf6_area *)NULL; +} + +void ospf6_area_enable(struct ospf6_area *oa) +{ + struct listnode *node, *nnode; + struct ospf6_interface *oi; + + SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) + ospf6_interface_enable(oi); + ospf6_abr_enable_area(oa); +} + +void ospf6_area_disable(struct ospf6_area *oa) +{ + struct listnode *node, *nnode; + struct ospf6_interface *oi; + + UNSET_FLAG(oa->flag, OSPF6_AREA_ENABLE); + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) + ospf6_interface_disable(oi); + + ospf6_abr_disable_area(oa); + ospf6_lsdb_remove_all(oa->lsdb); + ospf6_lsdb_remove_all(oa->lsdb_self); + + ospf6_spf_table_finish(oa->spf_table); + ospf6_route_remove_all(oa->route_table); + + EVENT_OFF(oa->thread_router_lsa); + EVENT_OFF(oa->thread_intra_prefix_lsa); +} + + +void ospf6_area_show(struct vty *vty, struct ospf6_area *oa, + json_object *json_areas, bool use_json) +{ + struct listnode *i; + struct ospf6_interface *oi; + unsigned long result; + json_object *json_area; + json_object *array_interfaces; + + if (use_json) { + json_area = json_object_new_object(); + json_object_boolean_add(json_area, "areaIsStub", + IS_AREA_STUB(oa)); + json_object_boolean_add(json_area, "areaIsNSSA", + IS_AREA_NSSA(oa)); + if (IS_AREA_STUB(oa) || IS_AREA_NSSA(oa)) { + json_object_boolean_add(json_area, "areaNoSummary", + oa->no_summary); + } + + json_object_int_add(json_area, "numberOfAreaScopedLsa", + oa->lsdb->count); + json_object_object_add( + json_area, "lsaStatistics", + JSON_OBJECT_NEW_ARRAY(json_object_new_int, + oa->lsdb->stats, + OSPF6_LSTYPE_SIZE)); + + /* Interfaces Attached */ + array_interfaces = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(oa->if_list, i, oi)) + json_object_array_add( + array_interfaces, + json_object_new_string(oi->interface->name)); + + json_object_object_add(json_area, "interfacesAttachedToArea", + array_interfaces); + + if (oa->ts_spf.tv_sec || oa->ts_spf.tv_usec) { + json_object_boolean_true_add(json_area, "spfHasRun"); + result = monotime_since(&oa->ts_spf, NULL); + if (result / TIMER_SECOND_MICRO > 0) { + json_object_int_add( + json_area, "spfLastExecutedSecs", + result / TIMER_SECOND_MICRO); + + json_object_int_add( + json_area, "spfLastExecutedMicroSecs", + result % TIMER_SECOND_MICRO); + } else { + json_object_int_add(json_area, + "spfLastExecutedSecs", 0); + json_object_int_add(json_area, + "spfLastExecutedMicroSecs", + result); + } + } else + json_object_boolean_false_add(json_area, "spfHasRun"); + + + json_object_object_add(json_areas, oa->name, json_area); + + } else { + + if (!IS_AREA_STUB(oa) && !IS_AREA_NSSA(oa)) + vty_out(vty, " Area %s\n", oa->name); + else { + if (oa->no_summary) { + vty_out(vty, " Area %s[%s, No Summary]\n", + oa->name, + IS_AREA_STUB(oa) ? "Stub" : "NSSA"); + } else { + vty_out(vty, " Area %s[%s]\n", oa->name, + IS_AREA_STUB(oa) ? "Stub" : "NSSA"); + } + } + vty_out(vty, " Number of Area scoped LSAs is %u\n", + oa->lsdb->count); + + vty_out(vty, " Interface attached to this area:"); + for (ALL_LIST_ELEMENTS_RO(oa->if_list, i, oi)) + vty_out(vty, " %s", oi->interface->name); + vty_out(vty, "\n"); + + if (oa->ts_spf.tv_sec || oa->ts_spf.tv_usec) { + result = monotime_since(&oa->ts_spf, NULL); + if (result / TIMER_SECOND_MICRO > 0) { + vty_out(vty, + " SPF last executed %ld.%lds ago\n", + result / TIMER_SECOND_MICRO, + result % TIMER_SECOND_MICRO); + } else { + vty_out(vty, + " SPF last executed %ldus ago\n", + result); + } + } else + vty_out(vty, "SPF has not been run\n"); + } +} + +DEFUN (area_range, + area_range_cmd, + "area range X:X::X:X/M []", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configured address range\n" + "Specify IPv6 prefix\n" + "Advertise\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + int idx_ipv4 = 1; + int idx_ipv6_prefixlen = 3; + int idx_type = 4; + int ret; + struct ospf6_area *oa; + struct prefix prefix; + struct ospf6_route *range; + uint32_t cost; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, oa, ospf6); + + ret = str2prefix(argv[idx_ipv6_prefixlen]->arg, &prefix); + if (ret != 1 || prefix.family != AF_INET6) { + vty_out(vty, "Malformed argument: %s\n", + argv[idx_ipv6_prefixlen]->arg); + return CMD_SUCCESS; + } + + range = ospf6_route_lookup(&prefix, oa->range_table); + if (range == NULL) { + range = ospf6_route_create(ospf6); + range->type = OSPF6_DEST_TYPE_RANGE; + range->prefix = prefix; + range->path.area_id = oa->area_id; + range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC; + } + + /* default settings */ + cost = OSPF_AREA_RANGE_COST_UNSPEC; + UNSET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); + + if (argc > idx_type) { + if (strmatch(argv[idx_type]->text, "not-advertise")) + SET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); + else if (strmatch(argv[idx_type]->text, "cost")) + cost = strtoul(argv[5]->arg, NULL, 10); + } + + range->path.u.cost_config = cost; + + if (range->rnode == NULL) { + ospf6_route_add(range, oa->range_table); + } + + if (ospf6_check_and_set_router_abr(ospf6)) { + /* Redo summaries if required */ + ospf6_abr_prefix_resummarize(ospf6); + } + + return CMD_SUCCESS; +} + +DEFUN (no_area_range, + no_area_range_cmd, + "no area range X:X::X:X/M []", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configured address range\n" + "Specify IPv6 prefix\n" + "Advertise\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + int idx_ipv4 = 2; + int idx_ipv6 = 4; + int ret; + struct ospf6_area *oa; + struct prefix prefix; + struct ospf6_route *range, *route; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, oa, ospf6); + + ret = str2prefix(argv[idx_ipv6]->arg, &prefix); + if (ret != 1 || prefix.family != AF_INET6) { + vty_out(vty, "Malformed argument: %s\n", argv[idx_ipv6]->arg); + return CMD_SUCCESS; + } + + range = ospf6_route_lookup(&prefix, oa->range_table); + if (range == NULL) { + vty_out(vty, "Range %s does not exists.\n", + argv[idx_ipv6]->arg); + return CMD_SUCCESS; + } + + if (ospf6_check_and_set_router_abr(oa->ospf6)) { + /* Blow away the aggregated LSA and route */ + SET_FLAG(range->flag, OSPF6_ROUTE_REMOVE); + + /* Redo summaries if required */ + for (route = ospf6_route_head(oa->ospf6->route_table); route; + route = ospf6_route_next(route)) + ospf6_abr_originate_summary(route, oa->ospf6); + + /* purge the old aggregated summary LSA */ + ospf6_abr_originate_summary(range, oa->ospf6); + } + ospf6_route_remove(range, oa->range_table); + + return CMD_SUCCESS; +} + +void ospf6_area_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + struct listnode *node; + struct ospf6_area *oa; + struct ospf6_route *range; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + for (range = ospf6_route_head(oa->range_table); range; + range = ospf6_route_next(range)) { + vty_out(vty, " area %s range %pFX", oa->name, + &range->prefix); + + if (CHECK_FLAG(range->flag, + OSPF6_ROUTE_DO_NOT_ADVERTISE)) { + vty_out(vty, " not-advertise"); + } else { + // "advertise" is the default so we do not + // display it + if (range->path.u.cost_config + != OSPF_AREA_RANGE_COST_UNSPEC) + vty_out(vty, " cost %d", + range->path.u.cost_config); + } + vty_out(vty, "\n"); + } + if (IS_AREA_STUB(oa)) { + if (oa->no_summary) + vty_out(vty, " area %s stub no-summary\n", + oa->name); + else + vty_out(vty, " area %s stub\n", oa->name); + } + if (IS_AREA_NSSA(oa)) { + vty_out(vty, " area %s nssa", oa->name); + if (oa->nssa_default_originate.enabled) { + vty_out(vty, " default-information-originate"); + if (oa->nssa_default_originate.metric_value + != -1) + vty_out(vty, " metric %d", + oa->nssa_default_originate + .metric_value); + if (oa->nssa_default_originate.metric_type + != DEFAULT_METRIC_TYPE) + vty_out(vty, " metric-type 1"); + } + if (oa->no_summary) + vty_out(vty, " no-summary"); + vty_out(vty, "\n"); + } + for (range = ospf6_route_head(oa->nssa_range_table); range; + range = ospf6_route_next(range)) { + vty_out(vty, " area %s nssa range %pFX", oa->name, + &range->prefix); + + if (CHECK_FLAG(range->flag, + OSPF6_ROUTE_DO_NOT_ADVERTISE)) { + vty_out(vty, " not-advertise"); + } else { + if (range->path.u.cost_config + != OSPF_AREA_RANGE_COST_UNSPEC) + vty_out(vty, " cost %u", + range->path.u.cost_config); + } + vty_out(vty, "\n"); + } + if (PREFIX_NAME_IN(oa)) + vty_out(vty, " area %s filter-list prefix %s in\n", + oa->name, PREFIX_NAME_IN(oa)); + if (PREFIX_NAME_OUT(oa)) + vty_out(vty, " area %s filter-list prefix %s out\n", + oa->name, PREFIX_NAME_OUT(oa)); + if (IMPORT_NAME(oa)) + vty_out(vty, " area %s import-list %s\n", oa->name, + IMPORT_NAME(oa)); + if (EXPORT_NAME(oa)) + vty_out(vty, " area %s export-list %s\n", oa->name, + EXPORT_NAME(oa)); + } +} + +DEFUN (area_filter_list, + area_filter_list_cmd, + "area filter-list prefix PREFIXLIST6_NAME ", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Filter networks between OSPF6 areas\n" + "Filter prefixes between OSPF6 areas\n" + "Name of an IPv6 prefix-list\n" + "Filter networks sent to this area\n" + "Filter networks sent from this area\n") +{ + char *inout = argv[argc - 1]->text; + char *areaid = argv[1]->arg; + char *plistname = argv[4]->arg; + + struct ospf6_area *area; + struct prefix_list *plist; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(areaid, area, ospf6); + + plist = prefix_list_lookup(AFI_IP6, plistname); + if (strmatch(inout, "in")) { + PREFIX_LIST_IN(area) = plist; + XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_IN(area)); + PREFIX_NAME_IN(area) = + XSTRDUP(MTYPE_OSPF6_PLISTNAME, plistname); + } else { + PREFIX_LIST_OUT(area) = plist; + XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_OUT(area)); + PREFIX_NAME_OUT(area) = + XSTRDUP(MTYPE_OSPF6_PLISTNAME, plistname); + } + + /* Redo summaries if required */ + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +DEFUN (no_area_filter_list, + no_area_filter_list_cmd, + "no area filter-list prefix PREFIXLIST6_NAME ", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Filter networks between OSPF6 areas\n" + "Filter prefixes between OSPF6 areas\n" + "Name of an IPv6 prefix-list\n" + "Filter networks sent to this area\n" + "Filter networks sent from this area\n") +{ + char *inout = argv[argc - 1]->text; + char *areaid = argv[2]->arg; + char *plistname = argv[5]->arg; + + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + OSPF6_CMD_AREA_GET(areaid, area, ospf6); + + if (strmatch(inout, "in")) { + if (PREFIX_NAME_IN(area)) + if (!strmatch(PREFIX_NAME_IN(area), plistname)) + return CMD_SUCCESS; + + PREFIX_LIST_IN(area) = NULL; + XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_IN(area)); + } else { + if (PREFIX_NAME_OUT(area)) + if (!strmatch(PREFIX_NAME_OUT(area), plistname)) + return CMD_SUCCESS; + + XFREE(MTYPE_OSPF6_PLISTNAME, PREFIX_NAME_OUT(area)); + PREFIX_LIST_OUT(area) = NULL; + } + + /* Redo summaries if required */ + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +void ospf6_filter_update(struct access_list *access) +{ + struct ospf6_area *oa; + struct listnode *n, *node, *nnode; + struct ospf6 *ospf6; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + bool update = false; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) { + if (IMPORT_NAME(oa) + && strcmp(IMPORT_NAME(oa), access->name) == 0) { + IMPORT_LIST(oa) = access_list_lookup( + AFI_IP6, IMPORT_NAME(oa)); + update = true; + } + + if (EXPORT_NAME(oa) + && strcmp(EXPORT_NAME(oa), access->name) == 0) { + EXPORT_LIST(oa) = access_list_lookup( + AFI_IP6, EXPORT_NAME(oa)); + update = true; + } + } + + if (update && ospf6_check_and_set_router_abr(ospf6)) + ospf6_schedule_abr_task(ospf6); + } +} + +void ospf6_plist_update(struct prefix_list *plist) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct listnode *n; + const char *name = prefix_list_name(plist); + struct ospf6 *ospf6 = NULL; + + if (prefix_list_afi(plist) != AFI_IP6) + return; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + bool update = false; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, n, oa)) { + if (PREFIX_NAME_IN(oa) + && !strcmp(PREFIX_NAME_IN(oa), name)) { + PREFIX_LIST_IN(oa) = prefix_list_lookup( + AFI_IP6, PREFIX_NAME_IN(oa)); + update = true; + } + if (PREFIX_NAME_OUT(oa) + && !strcmp(PREFIX_NAME_OUT(oa), name)) { + PREFIX_LIST_OUT(oa) = prefix_list_lookup( + AFI_IP6, PREFIX_NAME_OUT(oa)); + update = true; + } + } + + if (update && ospf6_check_and_set_router_abr(ospf6)) + ospf6_schedule_abr_task(ospf6); + } +} + +DEFUN (area_import_list, + area_import_list_cmd, + "area import-list ACCESSLIST6_NAME", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Set the filter for networks from other areas announced to the specified one\n" + "Name of the access-list\n") +{ + int idx_ipv4 = 1; + int idx_name = 3; + struct ospf6_area *area; + struct access_list *list; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area, ospf6); + + list = access_list_lookup(AFI_IP6, argv[idx_name]->arg); + + IMPORT_LIST(area) = list; + + if (IMPORT_NAME(area)) + free(IMPORT_NAME(area)); + + IMPORT_NAME(area) = strdup(argv[idx_name]->arg); + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +DEFUN (no_area_import_list, + no_area_import_list_cmd, + "no area import-list ACCESSLIST6_NAME", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Unset the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + int idx_ipv4 = 2; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area, ospf6); + + IMPORT_LIST(area) = NULL; + + if (IMPORT_NAME(area)) + free(IMPORT_NAME(area)); + + IMPORT_NAME(area) = NULL; + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +DEFUN (area_export_list, + area_export_list_cmd, + "area export-list ACCESSLIST6_NAME", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Set the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + int idx_ipv4 = 1; + int idx_name = 3; + struct ospf6_area *area; + struct access_list *list; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area, ospf6); + + list = access_list_lookup(AFI_IP6, argv[idx_name]->arg); + + EXPORT_LIST(area) = list; + + if (EXPORT_NAME(area)) + free(EXPORT_NAME(area)); + + EXPORT_NAME(area) = strdup(argv[idx_name]->arg); + + /* Redo summaries if required */ + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +DEFUN (no_area_export_list, + no_area_export_list_cmd, + "no area export-list ACCESSLIST6_NAME", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Unset the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + int idx_ipv4 = 2; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4]->arg, area, ospf6); + + EXPORT_LIST(area) = NULL; + + if (EXPORT_NAME(area)) + free(EXPORT_NAME(area)); + + EXPORT_NAME(area) = NULL; + if (ospf6_check_and_set_router_abr(area->ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +static int ipv6_ospf6_spf_tree_common(struct vty *vty, struct ospf6 *ospf6, + bool uj) +{ + struct listnode *node; + struct ospf6_area *oa; + struct prefix prefix; + struct ospf6_vertex *root; + struct ospf6_route *route; + json_object *json = NULL; + json_object *json_area = NULL; + json_object *json_head = NULL; + + if (uj) + json = json_object_new_object(); + ospf6_linkstate_prefix(ospf6->router_id, htonl(0), &prefix); + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (uj) { + json_area = json_object_new_object(); + json_head = json_object_new_object(); + } + route = ospf6_route_lookup(&prefix, oa->spf_table); + if (route == NULL) { + if (uj) { + json_object_string_add( + json, oa->name, + "LS entry for not not found"); + json_object_free(json_head); + json_object_free(json_area); + } else + vty_out(vty, + "LS entry for root not found in area %s\n", + oa->name); + continue; + } + root = (struct ospf6_vertex *)route->route_option; + ospf6_spf_display_subtree(vty, "", 0, root, json_head, uj); + + if (uj) { + json_object_object_add(json_area, root->name, + json_head); + json_object_object_add(json, oa->name, json_area); + } + } + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_spf_tree, show_ipv6_ospf6_spf_tree_cmd, + "show ipv6 ospf6 [vrf ] spf tree [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Shortest Path First calculation\n" + "Show SPF tree\n" JSON_STR) +{ + struct listnode *node; + struct ospf6 *ospf6; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ipv6_ospf6_spf_tree_common(vty, ospf6, uj); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int show_ospf6_area_spf_tree_common(struct vty *vty, + struct cmd_token **argv, + struct ospf6 *ospf6, + uint32_t area_id, int idx_ipv4) +{ + + struct ospf6_area *oa; + struct prefix prefix; + struct ospf6_vertex *root; + struct ospf6_route *route; + + ospf6_linkstate_prefix(ospf6->router_id, htonl(0), &prefix); + + oa = ospf6_area_lookup(area_id, ospf6); + if (oa == NULL) { + vty_out(vty, "No such Area: %s\n", argv[idx_ipv4]->arg); + return CMD_SUCCESS; + } + + route = ospf6_route_lookup(&prefix, oa->spf_table); + if (route == NULL) { + vty_out(vty, "LS entry for root not found in area %s\n", + oa->name); + return CMD_SUCCESS; + } + root = (struct ospf6_vertex *)route->route_option; + ospf6_spf_display_subtree(vty, "", 0, root, NULL, false); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_area_spf_tree, show_ipv6_ospf6_area_spf_tree_cmd, + "show ipv6 ospf6 [vrf ] area A.B.C.D spf tree", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" OSPF6_AREA_STR OSPF6_AREA_ID_STR + "Shortest Path First calculation\n" + "Show SPF tree\n") +{ + int idx_ipv4 = 4; + uint32_t area_id; + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_ipv4 += 2; + + if (inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id) != 1) { + vty_out(vty, "Malformed Area-ID: %s\n", argv[idx_ipv4]->arg); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + show_ospf6_area_spf_tree_common(vty, argv, ospf6, + area_id, idx_ipv4); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int +show_ospf6_simulate_spf_tree_commen(struct vty *vty, struct cmd_token **argv, + struct ospf6 *ospf6, uint32_t router_id, + uint32_t area_id, struct prefix prefix, + int idx_ipv4, int idx_ipv4_2) +{ + struct ospf6_area *oa; + struct ospf6_vertex *root; + struct ospf6_route *route; + struct ospf6_route_table *spf_table; + unsigned char tmp_debug_ospf6_spf = 0; + + oa = ospf6_area_lookup(area_id, ospf6); + if (oa == NULL) { + vty_out(vty, "No such Area: %s\n", argv[idx_ipv4_2]->arg); + return CMD_SUCCESS; + } + + tmp_debug_ospf6_spf = conf_debug_ospf6_spf; + conf_debug_ospf6_spf = 0; + + spf_table = OSPF6_ROUTE_TABLE_CREATE(NONE, SPF_RESULTS); + ospf6_spf_calculation(router_id, spf_table, oa); + + conf_debug_ospf6_spf = tmp_debug_ospf6_spf; + + route = ospf6_route_lookup(&prefix, spf_table); + if (route == NULL) { + ospf6_spf_table_finish(spf_table); + ospf6_route_table_delete(spf_table); + return CMD_SUCCESS; + } + root = (struct ospf6_vertex *)route->route_option; + ospf6_spf_display_subtree(vty, "", 0, root, NULL, false); + + ospf6_spf_table_finish(spf_table); + ospf6_route_table_delete(spf_table); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_simulate_spf_tree_root, + show_ipv6_ospf6_simulate_spf_tree_root_cmd, + "show ipv6 ospf6 [vrf ] simulate spf-tree A.B.C.D area A.B.C.D", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Shortest Path First calculation\n" + "Show SPF tree\n" + "Specify root's router-id to calculate another router's SPF tree\n" + "OSPF6 area parameters\n" OSPF6_AREA_ID_STR) +{ + int idx_ipv4 = 5; + int idx_ipv4_2 = 7; + uint32_t area_id; + struct prefix prefix; + uint32_t router_id; + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_ipv4 += 2; + idx_ipv4_2 += 2; + } + inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id); + ospf6_linkstate_prefix(router_id, htonl(0), &prefix); + + if (inet_pton(AF_INET, argv[idx_ipv4_2]->arg, &area_id) != 1) { + vty_out(vty, "Malformed Area-ID: %s\n", argv[idx_ipv4_2]->arg); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + show_ospf6_simulate_spf_tree_commen( + vty, argv, ospf6, router_id, area_id, prefix, + idx_ipv4, idx_ipv4_2); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN (ospf6_area_stub, + ospf6_area_stub_cmd, + "area stub", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as stub\n") +{ + int idx_ipv4_number = 1; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + + if (!ospf6_area_stub_set(ospf6, area)) { + vty_out(vty, + "First deconfigure all virtual link through this area\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf6_area_no_summary_unset(ospf6, area); + + return CMD_SUCCESS; +} + +DEFUN (ospf6_area_stub_no_summary, + ospf6_area_stub_no_summary_cmd, + "area stub no-summary", + "OSPF6 stub parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as stub\n" + "Do not inject inter-area routes into stub\n") +{ + int idx_ipv4_number = 1; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + + if (!ospf6_area_stub_set(ospf6, area)) { + vty_out(vty, + "First deconfigure all virtual link through this area\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf6_area_no_summary_set(ospf6, area); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_area_stub, + no_ospf6_area_stub_cmd, + "no area stub", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as stub\n") +{ + int idx_ipv4_number = 2; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + + ospf6_area_stub_unset(ospf6, area); + ospf6_area_no_summary_unset(ospf6, area); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_area_stub_no_summary, + no_ospf6_area_stub_no_summary_cmd, + "no area stub no-summary", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as stub\n" + "Do not inject inter-area routes into area\n") +{ + int idx_ipv4_number = 2; + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(argv[idx_ipv4_number]->arg, area, ospf6); + + ospf6_area_stub_unset(ospf6, area); + ospf6_area_no_summary_unset(ospf6, area); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_area_nssa, ospf6_area_nssa_cmd, + "area $area_str nssa\ + [{\ + default-information-originate$dflt_originate [{metric (0-16777214)$mval|metric-type (1-2)$mtype}]\ + |no-summary$no_summary\ + }]", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as nssa\n" + "Originate Type 7 default into NSSA area\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Do not inject inter-area routes into area\n") +{ + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + OSPF6_CMD_AREA_GET(area_str, area, ospf6); + + if (!ospf6_area_nssa_set(ospf6, area)) { + vty_out(vty, + "First deconfigure all virtual link through this area\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (dflt_originate) { + if (mval_str == NULL) + mval = -1; + if (mtype_str == NULL) + mtype = DEFAULT_METRIC_TYPE; + ospf6_nssa_default_originate_set(ospf6, area, mval, mtype); + } else + ospf6_nssa_default_originate_unset(ospf6, area); + + if (no_summary) + ospf6_area_no_summary_set(ospf6, area); + else + ospf6_area_no_summary_unset(ospf6, area); + + if (ospf6_check_and_set_router_abr(ospf6)) { + ospf6_abr_defaults_to_stub(ospf6); + ospf6_abr_nssa_type_7_defaults(ospf6); + } + + return CMD_SUCCESS; +} + +DEFPY(no_ospf6_area_nssa, no_ospf6_area_nssa_cmd, + "no area $area_str nssa\ + [{\ + default-information-originate [{metric (0-16777214)|metric-type (1-2)}]\ + |no-summary\ + }]", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as nssa\n" + "Originate Type 7 default into NSSA area\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Do not inject inter-area routes into area\n") +{ + struct ospf6_area *area; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + OSPF6_CMD_AREA_GET(area_str, area, ospf6); + + ospf6_area_nssa_unset(ospf6, area); + ospf6_area_no_summary_unset(ospf6, area); + ospf6_nssa_default_originate_unset(ospf6, area); + + return CMD_SUCCESS; +} + + +void ospf6_area_init(void) +{ + install_element(VIEW_NODE, &show_ipv6_ospf6_spf_tree_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_area_spf_tree_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_simulate_spf_tree_root_cmd); + + install_element(OSPF6_NODE, &area_range_cmd); + install_element(OSPF6_NODE, &no_area_range_cmd); + install_element(OSPF6_NODE, &ospf6_area_stub_no_summary_cmd); + install_element(OSPF6_NODE, &ospf6_area_stub_cmd); + install_element(OSPF6_NODE, &no_ospf6_area_stub_no_summary_cmd); + install_element(OSPF6_NODE, &no_ospf6_area_stub_cmd); + + + install_element(OSPF6_NODE, &area_import_list_cmd); + install_element(OSPF6_NODE, &no_area_import_list_cmd); + install_element(OSPF6_NODE, &area_export_list_cmd); + install_element(OSPF6_NODE, &no_area_export_list_cmd); + + install_element(OSPF6_NODE, &area_filter_list_cmd); + install_element(OSPF6_NODE, &no_area_filter_list_cmd); + + /* "area nssa" commands. */ + install_element(OSPF6_NODE, &ospf6_area_nssa_cmd); + install_element(OSPF6_NODE, &no_ospf6_area_nssa_cmd); +} + +void ospf6_area_interface_delete(struct ospf6_interface *oi) +{ + struct ospf6_area *oa; + struct listnode *node, *nnode; + struct ospf6 *ospf6; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) + if (listnode_lookup(oa->if_list, oi)) + listnode_delete(oa->if_list, oi); + } +} diff --git a/ospf6d/ospf6_area.h b/ospf6d/ospf6_area.h new file mode 100644 index 0000000..d9afd65 --- /dev/null +++ b/ospf6d/ospf6_area.h @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF_AREA_H +#define OSPF_AREA_H + +#include "ospf6_top.h" +#include "lib/json.h" + +struct ospf6_area { + /* Reference to Top data structure */ + struct ospf6 *ospf6; + + /* Area-ID */ + in_addr_t area_id; + +#define OSPF6_AREA_FMT_UNSET 0 +#define OSPF6_AREA_FMT_DOTTEDQUAD 1 +#define OSPF6_AREA_FMT_DECIMAL 2 + /* Area-ID string */ + char name[16]; + + /* flag */ + uint8_t flag; + + /* OSPF Option */ + uint8_t options[3]; + + /* Summary routes to be originated (includes Configured Address Ranges) + */ + struct ospf6_route_table *range_table; + struct ospf6_route_table *nssa_range_table; + struct ospf6_route_table *summary_prefix; + struct ospf6_route_table *summary_router; + + /* Area type */ + int no_summary; + + /* NSSA default-information-originate */ + struct { + bool enabled; + int metric_type; + int metric_value; + } nssa_default_originate; + + /* Brouter traversal protection */ + bool intra_brouter_calc; + + /* OSPF interface list */ + struct list *if_list; + + struct ospf6_lsdb *lsdb; + struct ospf6_lsdb *lsdb_self; + struct ospf6_lsdb *temp_router_lsa_lsdb; + + struct ospf6_route_table *spf_table; + struct ospf6_route_table *route_table; + + uint32_t spf_calculation; /* SPF calculation count */ + + struct event *thread_router_lsa; + struct event *thread_intra_prefix_lsa; + uint32_t router_lsa_size_limit; + + /* Area announce list */ + struct { + char *name; + struct access_list *list; + } _export; +#define EXPORT_NAME(A) (A)->_export.name +#define EXPORT_LIST(A) (A)->_export.list + + /* Area acceptance list */ + struct { + char *name; + struct access_list *list; + } import; +#define IMPORT_NAME(A) (A)->import.name +#define IMPORT_LIST(A) (A)->import.list + + /* Type 3 LSA Area prefix-list */ + struct { + char *name; + struct prefix_list *list; + } plist_in; +#define PREFIX_NAME_IN(A) (A)->plist_in.name +#define PREFIX_LIST_IN(A) (A)->plist_in.list + + struct { + char *name; + struct prefix_list *list; + } plist_out; +#define PREFIX_NAME_OUT(A) (A)->plist_out.name +#define PREFIX_LIST_OUT(A) (A)->plist_out.list + + /* Time stamps. */ + struct timeval ts_spf; /* SPF calculation time stamp. */ + + uint32_t full_nbrs; /* Fully adjacent neighbors. */ + uint8_t intra_prefix_originate; /* Force intra_prefix lsa originate */ + uint8_t NSSATranslatorRole; /* NSSA configured role */ +#define OSPF6_NSSA_ROLE_NEVER 0 +#define OSPF6_NSSA_ROLE_CANDIDATE 1 +#define OSPF6_NSSA_ROLE_ALWAYS 2 + uint8_t NSSATranslatorState; /* NSSA operational role */ +#define OSPF6_NSSA_TRANSLATE_DISABLED 0 +#define OSPF6_NSSA_TRANSLATE_ENABLED 1 +}; + +#define OSPF6_AREA_ENABLE 0x01 +#define OSPF6_AREA_ACTIVE 0x02 +#define OSPF6_AREA_TRANSIT 0x04 /* TransitCapability */ +#define OSPF6_AREA_STUB 0x08 +#define OSPF6_AREA_NSSA 0x10 + +#define IS_AREA_ENABLED(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_ENABLE)) +#define IS_AREA_ACTIVE(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_ACTIVE)) +#define IS_AREA_TRANSIT(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_TRANSIT)) +#define IS_AREA_STUB(oa) (CHECK_FLAG ((oa)->flag, OSPF6_AREA_STUB)) +#define IS_AREA_NSSA(oa) (CHECK_FLAG((oa)->flag, OSPF6_AREA_NSSA)) + +#define OSPF6_CMD_AREA_GET(str, oa, ospf6) \ + { \ + uint32_t area_id; \ + int format, ret; \ + ret = str2area_id(str, &area_id, &format); \ + if (ret) { \ + vty_out(vty, "Malformed Area-ID: %s\n", str); \ + return CMD_WARNING; \ + } \ + oa = ospf6_area_lookup(area_id, ospf6); \ + if (oa == NULL) \ + oa = ospf6_area_create(area_id, ospf6, format); \ + } + +/* prototypes */ +extern int str2area_id(const char *str, uint32_t *area_id, int *area_id_fmt); +extern void area_id2str(char *buf, int len, uint32_t area_id, int area_id_fmt); + +extern int ospf6_area_cmp(void *va, void *vb); + +extern struct ospf6_area *ospf6_area_create(uint32_t area_id, + struct ospf6 *ospf6, int df); +extern void ospf6_area_delete(struct ospf6_area *oa); +extern struct ospf6_area *ospf6_area_lookup(uint32_t area_id, + struct ospf6 *ospf6); +extern struct ospf6_area *ospf6_area_lookup_by_area_id(uint32_t area_id); + +extern void ospf6_area_stub_unset(struct ospf6 *ospf6, struct ospf6_area *area); +extern void ospf6_area_enable(struct ospf6_area *oa); +extern void ospf6_area_disable(struct ospf6_area *oa); + +extern void ospf6_area_show(struct vty *vty, struct ospf6_area *oa, + json_object *json_areas, bool use_json); + +extern void ospf6_plist_update(struct prefix_list *plist); +extern void ospf6_filter_update(struct access_list *access); +extern void ospf6_area_config_write(struct vty *vty, struct ospf6 *ospf6); +extern void ospf6_area_init(void); +struct ospf6_interface; +extern void ospf6_area_interface_delete(struct ospf6_interface *oi); + +#endif /* OSPF_AREA_H */ diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c new file mode 100644 index 0000000..2065527 --- /dev/null +++ b/ospf6d/ospf6_asbr.c @@ -0,0 +1,3776 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "prefix.h" +#include "command.h" +#include "vty.h" +#include "routemap.h" +#include "table.h" +#include "plist.h" +#include "frrevent.h" +#include "frrstr.h" +#include "linklist.h" +#include "lib/northbound_cli.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" +#include "ospf6_message.h" +#include "ospf6_spf.h" + +#include "ospf6_top.h" +#include "ospf6d.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6_intra.h" +#include "ospf6_flood.h" +#include "ospf6_nssa.h" +#include "ospf6d.h" +#include "ospf6_spf.h" +#include "ospf6_nssa.h" +#include "ospf6_gr.h" +#include "lib/json.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_INFO, "OSPF6 ext. info"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_DIST_ARGS, "OSPF6 Distribute arguments"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_REDISTRIBUTE, "OSPF6 Redistribute arguments"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_EXTERNAL_RT_AGGR, "OSPF6 ASBR Summarisation"); + +static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type); +static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6, + struct ospf6_redist *red, int type); + +#include "ospf6d/ospf6_asbr_clippy.c" + +unsigned char conf_debug_ospf6_asbr = 0; + +#define ZROUTE_NAME(x) zebra_route_string(x) + +/* Originate Type-5 and Type-7 LSA */ +static struct ospf6_lsa *ospf6_originate_type5_type7_lsas( + struct ospf6_route *route, + struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + struct listnode *lnode; + struct ospf6_area *oa = NULL; + + lsa = ospf6_as_external_lsa_originate(route, ospf6); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, lnode, oa)) { + if (IS_AREA_NSSA(oa)) + ospf6_nssa_lsa_originate(route, oa, true); + } + + return lsa; +} + +/* AS External LSA origination */ +struct ospf6_lsa *ospf6_as_external_lsa_originate(struct ospf6_route *route, + struct ospf6 *ospf6) +{ + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *lsa; + struct ospf6_external_info *info = route->route_option; + + struct ospf6_as_external_lsa *as_external_lsa; + caddr_t p; + + if (ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return NULL; + } + + if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) + zlog_debug("Originate AS-External-LSA for %pFX", + &route->prefix); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + as_external_lsa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa_header); + p = (caddr_t)((caddr_t)as_external_lsa + + sizeof(struct ospf6_as_external_lsa)); + + /* Fill AS-External-LSA */ + /* Metric type */ + if (route->path.metric_type == 2) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + + /* forwarding address */ + if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + + /* external route tag */ + if (info->tag) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + + /* Set metric */ + OSPF6_ASBR_METRIC_SET(as_external_lsa, route->path.cost); + + /* prefixlen */ + as_external_lsa->prefix.prefix_length = route->prefix.prefixlen; + + /* PrefixOptions */ + as_external_lsa->prefix.prefix_options = route->prefix_options; + + /* don't use refer LS-type */ + as_external_lsa->prefix.prefix_refer_lstype = htons(0); + + /* set Prefix */ + memcpy(p, &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); + ospf6_prefix_apply_mask(&as_external_lsa->prefix); + p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); + + /* Forwarding address */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) { + memcpy(p, &info->forwarding, sizeof(struct in6_addr)); + p += sizeof(struct in6_addr); + } + + /* External Route Tag */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) { + route_tag_t network_order = htonl(info->tag); + + memcpy(p, &network_order, sizeof(network_order)); + p += sizeof(network_order); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + lsa_header->id = route->path.origin.id; + lsa_header->adv_router = ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, ospf6->lsdb); + lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_process(lsa, ospf6); + + return lsa; +} + +void ospf6_orig_as_external_lsa(struct event *thread) +{ + struct ospf6_interface *oi; + struct ospf6_lsa *lsa; + uint32_t type, adv_router; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + if (oi->state == OSPF6_INTERFACE_DOWN) + return; + if (IS_AREA_NSSA(oi->area) || IS_AREA_STUB(oi->area)) + return; + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + adv_router = oi->area->ospf6->router_id; + for (ALL_LSDB_TYPED_ADVRTR(oi->area->ospf6->lsdb, type, adv_router, + lsa)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: Send update of AS-External LSA %s seq 0x%x", + __func__, lsa->name, + ntohl(lsa->header->seqnum)); + + ospf6_flood_interface(NULL, lsa, oi); + } +} + +static route_tag_t ospf6_as_external_lsa_get_tag(struct ospf6_lsa *lsa) +{ + struct ospf6_as_external_lsa *external; + ptrdiff_t tag_offset; + route_tag_t network_order; + + if (!lsa) + return 0; + + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (!CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) + return 0; + + tag_offset = sizeof(*external) + + OSPF6_PREFIX_SPACE(external->prefix.prefix_length); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) + tag_offset += sizeof(struct in6_addr); + + memcpy(&network_order, (caddr_t)external + tag_offset, + sizeof(network_order)); + return ntohl(network_order); +} + +void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, + struct ospf6_route *route, + struct ospf6 *ospf6) +{ + struct ospf6_route *old_route, *next_route; + struct ospf6_path *ecmp_path, *o_path = NULL; + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + bool route_found = false; + + /* check for old entry match with new route origin, + * delete old entry. + */ + for (old_route = old; old_route; old_route = next_route) { + bool route_updated = false; + + next_route = old_route->next; + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Current and New route has same origin, + * delete old entry. + */ + for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, + o_path)) { + /* Check old route path and route has same + * origin. + */ + if (o_path->area_id != route->path.area_id + || !ospf6_ls_origin_same(o_path, &route->path)) + continue; + + /* Cost is not same then delete current path */ + if ((o_path->cost == route->path.cost) + && (o_path->u.cost_e2 == route->path.u.cost_e2)) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX cost old %u new %u is not same, replace route", + __func__, &old_route->prefix, o_path->cost, + route->path.cost); + } + + /* Remove selected current rout path's nh from + * effective nh list. + */ + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { + for (ALL_LIST_ELEMENTS(old_route->nh_list, + rnode, rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, nh)) + continue; + listnode_delete(old_route->nh_list, + rnh); + ospf6_nexthop_delete(rnh); + } + } + + listnode_delete(old_route->paths, o_path); + ospf6_path_free(o_path); + route_updated = true; + + /* Current route's path (adv_router info) is similar + * to route being added. + * Replace current route's path with paths list head. + * Update FIB with effective NHs. + */ + if (listcount(old_route->paths)) { + for (ALL_LIST_ELEMENTS(old_route->paths, + anode, anext, o_path)) { + ospf6_merge_nexthops( + old_route->nh_list, + o_path->nh_list); + } + /* Update RIB/FIB with effective + * nh_list + */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add)( + old_route); + + if (old_route->path.origin.id + == route->path.origin.id + && old_route->path.origin.adv_router + == route->path.origin + .adv_router) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata(listhead( + old_route->paths)); + old_route->path.origin.type = + h_path->origin.type; + old_route->path.origin.id = + h_path->origin.id; + old_route->path.origin.adv_router = + h_path->origin.adv_router; + } + } else { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX old cost %u new cost %u, delete old entry.", + __func__, &old_route->prefix, + old_route->path.cost, + route->path.cost); + } + if (old == old_route) + old = next_route; + ospf6_route_remove(old_route, + ospf6->route_table); + } + } + if (route_updated) + break; + } + + /* Add new route */ + for (old_route = old; old_route; old_route = old_route->next) { + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Old Route and New Route have Equal Cost, Merge NHs */ + if ((old_route->path.cost == route->path.cost) + && (old_route->path.u.cost_e2 == route->path.u.cost_e2)) { + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: old route %pFX path cost %u e2 %u", + __func__, &old_route->prefix, + old_route->path.cost, + old_route->path.u.cost_e2); + } + route_found = true; + /* check if this path exists already in + * route->paths list, if so, replace nh_list + * from asbr_entry. + */ + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + if (o_path->area_id == route->path.area_id + && ospf6_ls_origin_same(o_path, &route->path)) + break; + } + /* If path is not found in old_route paths's list, + * add a new path to route paths list and merge + * nexthops in route->path->nh_list. + * Otherwise replace existing path's nh_list. + */ + if (o_path == NULL) { + ecmp_path = ospf6_path_dup(&route->path); + + /* Add a nh_list to new ecmp path */ + ospf6_copy_nexthops(ecmp_path->nh_list, + route->nh_list); + + /* Add the new path to route's path list */ + listnode_add_sort(old_route->paths, ecmp_path); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX another path added with nh %u, effective paths %u nh %u", + __func__, &route->prefix, + listcount(ecmp_path->nh_list), + old_route->paths ? listcount( + old_route->paths) + : 0, + listcount(old_route->nh_list)); + } + } else { + list_delete_all_node(o_path->nh_list); + ospf6_copy_nexthops(o_path->nh_list, + route->nh_list); + } + + /* Reset nexthop lists, rebuild from brouter table + * for each adv. router. + */ + list_delete_all_node(old_route->nh_list); + + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + struct ospf6_route *asbr_entry; + + asbr_entry = ospf6_route_lookup( + &o_path->ls_prefix, + ospf6->brouter_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "%s: ls_prfix %pFX asbr_entry not found.", + __func__, + &old_route->prefix); + continue; + } + ospf6_route_merge_nexthops(old_route, + asbr_entry); + } + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "%s: route %pFX with effective paths %u nh %u", + __func__, &route->prefix, + old_route->paths + ? listcount(old_route->paths) + : 0, + old_route->nh_list + ? listcount(old_route->nh_list) + : 0); + + /* Update RIB/FIB */ + if (ospf6->route_table->hook_add) + (*ospf6->route_table->hook_add)(old_route); + + /* Delete the new route its info added to existing + * route. + */ + ospf6_route_delete(route); + + break; + } + } + + if (!route_found) { + /* Add new route to existing node in ospf6 route table. */ + ospf6_route_add(route, ospf6->route_table); + } +} + +/* Check if the forwarding address is local address */ +static int ospf6_ase_forward_address_check(struct ospf6 *ospf6, + struct in6_addr *fwd_addr) +{ + struct listnode *anode, *node; + struct ospf6_interface *oi; + struct ospf6_area *oa; + struct interface *ifp; + struct connected *c; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, anode, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, node, oi)) { + if (!if_is_operative(oi->interface) + || oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + ifp = oi->interface; + frr_each (if_connected, ifp->connected, c) { + if (IPV6_ADDR_SAME(&c->address->u.prefix6, + fwd_addr)) + return 0; + } + } + } + + return 1; +} + +void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa) +{ + struct ospf6_as_external_lsa *external; + struct prefix asbr_id; + struct ospf6_route *asbr_entry, *route, *old = NULL; + struct ospf6_path *path; + struct ospf6 *ospf6; + int type; + struct ospf6_area *oa = NULL; + struct prefix fwd_addr; + ptrdiff_t offset; + + type = ntohs(lsa->header->type); + oa = lsa->lsdb->data; + + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Calculate AS-External route for %s", lsa->name); + + ospf6 = ospf6_get_by_lsdb(lsa); + + if (lsa->header->adv_router == ospf6->router_id) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore self-originated AS-External-LSA"); + return; + } + + if (OSPF6_ASBR_METRIC(external) == OSPF_LS_INFINITY) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore LSA with LSInfinity Metric"); + return; + } + + if (CHECK_FLAG(external->prefix.prefix_options, + OSPF6_PREFIX_OPTION_NU)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Ignore LSA with NU bit set Metric"); + return; + } + + ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), &asbr_id); + asbr_entry = ospf6_route_lookup(&asbr_id, ospf6->brouter_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("ASBR entry not found: %pFX", &asbr_id); + return; + } else { + /* The router advertising external LSA can be ASBR or ABR */ + if (!CHECK_FLAG(asbr_entry->path.router_bits, + OSPF6_ROUTER_BIT_E)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "External bit reset ASBR route entry : %pFX", + &asbr_id); + return; + } + + /* + * RFC 3101 - Section 2.5: + * "For a Type-7 LSA the matching routing table entry must + * specify an intra-area path through the LSA's originating + * NSSA". + */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && (asbr_entry->path.area_id != oa->area_id + || asbr_entry->path.type != OSPF6_PATH_TYPE_INTRA)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Intra-area route to NSSA ASBR not found: %pFX", + &asbr_id); + return; + } + } + + /* + * RFC 3101 - Section 2.5: + * "If the destination is a Type-7 default route (destination ID = + * DefaultDestination) and one of the following is true, then do + * nothing with this LSA and consider the next in the list: + * + * o The calculating router is a border router and the LSA has + * its P-bit clear. Appendix E describes a technique + * whereby an NSSA border router installs a Type-7 default + * LSA without propagating it. + * + * o The calculating router is a border router and is + * suppressing the import of summary routes as Type-3 + * summary-LSAs". + */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && external->prefix.prefix_length == 0 + && CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR) + && (CHECK_FLAG(external->prefix.prefix_options, + OSPF6_PREFIX_OPTION_P) + || oa->no_summary)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("Skipping Type-7 default route"); + return; + } + + /* Check the forwarding address */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { + offset = sizeof(*external) + + OSPF6_PREFIX_SPACE(external->prefix.prefix_length); + memset(&fwd_addr, 0, sizeof(fwd_addr)); + fwd_addr.family = AF_INET6; + fwd_addr.prefixlen = IPV6_MAX_BITLEN; + memcpy(&fwd_addr.u.prefix6, (caddr_t)external + offset, + sizeof(struct in6_addr)); + + if (!IN6_IS_ADDR_UNSPECIFIED(&fwd_addr.u.prefix6)) { + if (!ospf6_ase_forward_address_check( + ospf6, &fwd_addr.u.prefix6)) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Fwd address %pFX is local address", + &fwd_addr); + return; + } + + /* Find the forwarding entry */ + asbr_entry = ospf6_route_lookup_bestmatch( + &fwd_addr, ospf6->route_table); + if (asbr_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "Fwd address not found: %pFX", + &fwd_addr); + return; + } + } + } + + route = ospf6_route_create(ospf6); + route->type = OSPF6_DEST_TYPE_NETWORK; + route->prefix.family = AF_INET6; + route->prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&route->prefix.u.prefix6, external, + &external->prefix); + route->prefix_options = external->prefix.prefix_options; + + route->path.area_id = asbr_entry->path.area_id; + route->path.origin.type = lsa->header->type; + route->path.origin.id = lsa->header->id; + route->path.origin.adv_router = lsa->header->adv_router; + memcpy(&route->path.ls_prefix, &asbr_id, sizeof(struct prefix)); + + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { + route->path.type = OSPF6_PATH_TYPE_EXTERNAL2; + route->path.metric_type = 2; + route->path.cost = asbr_entry->path.cost; + route->path.u.cost_e2 = OSPF6_ASBR_METRIC(external); + } else { + route->path.type = OSPF6_PATH_TYPE_EXTERNAL1; + route->path.metric_type = 1; + route->path.cost = + asbr_entry->path.cost + OSPF6_ASBR_METRIC(external); + route->path.u.cost_e2 = 0; + } + + route->path.tag = ospf6_as_external_lsa_get_tag(lsa); + + ospf6_route_copy_nexthops(route, asbr_entry); + + path = ospf6_path_dup(&route->path); + ospf6_copy_nexthops(path->nh_list, asbr_entry->nh_list); + listnode_add_sort(route->paths, path); + + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug( + "%s: %s %u route add %pFX cost %u(%u) nh %u", __func__, + (type == OSPF6_LSTYPE_AS_EXTERNAL) ? "AS-External" + : "NSSA", + (route->path.type == OSPF6_PATH_TYPE_EXTERNAL1) ? 1 : 2, + &route->prefix, route->path.cost, route->path.u.cost_e2, + listcount(route->nh_list)); + + if (type == OSPF6_LSTYPE_AS_EXTERNAL) + old = ospf6_route_lookup(&route->prefix, ospf6->route_table); + else if (type == OSPF6_LSTYPE_TYPE_7) + old = ospf6_route_lookup(&route->prefix, oa->route_table); + if (!old) { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("%s: Adding new route", __func__); + /* Add the new route to ospf6 instance route table. */ + if (type == OSPF6_LSTYPE_AS_EXTERNAL) + ospf6_route_add(route, ospf6->route_table); + /* Add the route to the area route table */ + else if (type == OSPF6_LSTYPE_TYPE_7) { + ospf6_route_add(route, oa->route_table); + } + } else { + /* RFC 2328 16.4 (6) + * ECMP: Keep new equal preference path in current + * route's path list, update zebra with new effective + * list along with addition of ECMP path. + */ + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL)) + zlog_debug("%s : old route %pFX cost %u(%u) nh %u", + __func__, &route->prefix, route->path.cost, + route->path.u.cost_e2, + listcount(route->nh_list)); + ospf6_asbr_update_route_ecmp_path(old, route, ospf6); + } +} + +void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa, + struct ospf6_route *asbr_entry) +{ + struct ospf6_as_external_lsa *external; + struct prefix prefix; + struct ospf6_route *route, *nroute, *route_to_del; + struct ospf6_area *oa = NULL; + struct ospf6 *ospf6; + int type; + bool debug = false; + + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || (IS_OSPF6_DEBUG_NSSA)) + debug = true; + + ospf6 = ospf6_get_by_lsdb(lsa); + type = ntohs(lsa->header->type); + + if (type == OSPF6_LSTYPE_TYPE_7) { + if (debug) + zlog_debug("%s: Withdraw Type 7 route for %s", + __func__, lsa->name); + oa = lsa->lsdb->data; + } else { + if (debug) + zlog_debug("%s: Withdraw AS-External route for %s", + __func__, lsa->name); + + if (ospf6_check_and_set_router_abr(ospf6)) + oa = ospf6->backbone; + else + oa = listnode_head(ospf6->area_list); + } + + if (oa == NULL) { + if (debug) + zlog_debug("%s: Invalid area", __func__); + return; + } + + if (lsa->header->adv_router == oa->ospf6->router_id) { + if (debug) + zlog_debug("Ignore self-originated AS-External-LSA"); + return; + } + + route_to_del = ospf6_route_create(ospf6); + route_to_del->type = OSPF6_DEST_TYPE_NETWORK; + route_to_del->prefix.family = AF_INET6; + route_to_del->prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&route_to_del->prefix.u.prefix6, external, + &external->prefix); + + route_to_del->path.origin.type = lsa->header->type; + route_to_del->path.origin.id = lsa->header->id; + route_to_del->path.origin.adv_router = lsa->header->adv_router; + + if (asbr_entry) { + route_to_del->path.area_id = asbr_entry->path.area_id; + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E)) { + route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL2; + route_to_del->path.metric_type = 2; + route_to_del->path.cost = asbr_entry->path.cost; + route_to_del->path.u.cost_e2 = + OSPF6_ASBR_METRIC(external); + } else { + route_to_del->path.type = OSPF6_PATH_TYPE_EXTERNAL1; + route_to_del->path.metric_type = 1; + route_to_del->path.cost = asbr_entry->path.cost + + OSPF6_ASBR_METRIC(external); + route_to_del->path.u.cost_e2 = 0; + } + } + + memset(&prefix, 0, sizeof(struct prefix)); + prefix.family = AF_INET6; + prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, external, &external->prefix); + + if (type == OSPF6_LSTYPE_TYPE_7) + route = ospf6_route_lookup(&prefix, oa->route_table); + else + route = ospf6_route_lookup(&prefix, oa->ospf6->route_table); + + if (route == NULL) { + if (debug) + zlog_debug("AS-External route %pFX not found", &prefix); + ospf6_route_delete(route_to_del); + return; + } + + if (debug) + zlog_debug( + "%s: Current route %pFX cost %u e2 %u, route to del cost %u e2 %u", + __func__, &prefix, route->path.cost, route->path.u.cost_e2, + route_to_del->path.cost, route_to_del->path.u.cost_e2); + + for (ospf6_route_lock(route); + route && ospf6_route_is_prefix(&prefix, route); route = nroute) { + nroute = ospf6_route_next(route); + + if (route->type != OSPF6_DEST_TYPE_NETWORK) + continue; + + /* Route has multiple ECMP paths, remove matching + * path. Update current route's effective nh list + * after removal of one of the path. + */ + if (listcount(route->paths) > 1) { + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + struct ospf6_path *o_path; + bool nh_updated = false; + + /* Iterate all paths of route to find maching with LSA + * remove from route path list. If route->path is same, + * replace from paths list. + */ + for (ALL_LIST_ELEMENTS(route->paths, anode, anext, + o_path)) { + if ((o_path->origin.type != lsa->header->type) + || (o_path->origin.adv_router + != lsa->header->adv_router) + || (o_path->origin.id != lsa->header->id)) + continue; + + /* Compare LSA cost with current + * route info. + */ + if (asbr_entry + && (o_path->cost != route_to_del->path.cost + || o_path->u.cost_e2 + != route_to_del->path.u + .cost_e2)) { + if (IS_OSPF6_DEBUG_EXAMIN( + AS_EXTERNAL)) { + zlog_debug( + "%s: route %pFX to delete is not same, cost %u del cost %u. skip", + __func__, &prefix, + route->path.cost, + route_to_del->path + .cost); + } + continue; + } + + if (debug) { + zlog_debug( + "%s: route %pFX path found with cost %u nh %u to remove.", + __func__, &prefix, route->path.cost, + listcount(o_path->nh_list)); + } + + /* Remove found path's nh_list from + * the route's nh_list. + */ + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, + nnode, nh)) { + for (ALL_LIST_ELEMENTS(route->nh_list, + rnode, rnext, + rnh)) { + if (!ospf6_nexthop_is_same(rnh, + nh)) + continue; + listnode_delete(route->nh_list, + rnh); + ospf6_nexthop_delete(rnh); + } + } + /* Delete the path from route's path list */ + listnode_delete(route->paths, o_path); + ospf6_path_free(o_path); + nh_updated = true; + } + + if (nh_updated) { + /* Iterate all paths and merge nexthop, + * unlesss any of the nexthop similar to + * ones deleted as part of path deletion. + */ + + for (ALL_LIST_ELEMENTS(route->paths, anode, + anext, o_path)) { + ospf6_merge_nexthops(route->nh_list, + o_path->nh_list); + } + + if (debug) { + zlog_debug( + "%s: AS-External %u route %pFX update paths %u nh %u", + __func__, + (route->path.type + == OSPF6_PATH_TYPE_EXTERNAL1) + ? 1 + : 2, + &route->prefix, listcount(route->paths), + route->nh_list ? listcount( + route->nh_list) + : 0); + } + + if (listcount(route->paths)) { + /* Update RIB/FIB with effective + * nh_list + */ + if (oa->ospf6->route_table->hook_add) + (*oa->ospf6->route_table + ->hook_add)(route); + + /* route's primary path is similar + * to LSA, replace route's primary + * path with route's paths list head. + */ + if ((route->path.origin.id == + lsa->header->id) && + (route->path.origin.adv_router + == lsa->header->adv_router)) { + struct ospf6_path *h_path; + + h_path = (struct ospf6_path *) + listgetdata( + listhead(route->paths)); + route->path.origin.type = + h_path->origin.type; + route->path.origin.id = + h_path->origin.id; + route->path.origin.adv_router = + h_path->origin.adv_router; + } + } else { + if (type == OSPF6_LSTYPE_TYPE_7) + ospf6_route_remove( + route, oa->route_table); + else + ospf6_route_remove( + route, + oa->ospf6->route_table); + } + } + continue; + + } else { + /* Compare LSA origin and cost with current route info. + * if any check fails skip del this route node. + */ + if (asbr_entry + && (!ospf6_route_is_same_origin(route, route_to_del) + || (route->path.type != route_to_del->path.type) + || (route->path.cost != route_to_del->path.cost) + || (route->path.u.cost_e2 + != route_to_del->path.u.cost_e2))) { + if (debug) { + zlog_debug( + "%s: route %pFX to delete is not same, cost %u del cost %u. skip", + __func__, &prefix, route->path.cost, + route_to_del->path.cost); + } + continue; + } + + if ((route->path.origin.type != lsa->header->type) + || (route->path.origin.adv_router + != lsa->header->adv_router) + || (route->path.origin.id != lsa->header->id)) + continue; + } + if (debug) { + zlog_debug( + "%s: AS-External %u route remove %pFX cost %u(%u) nh %u", + __func__, + route->path.type == OSPF6_PATH_TYPE_EXTERNAL1 + ? 1 + : 2, + &route->prefix, route->path.cost, route->path.u.cost_e2, + listcount(route->nh_list)); + } + if (type == OSPF6_LSTYPE_TYPE_7) + ospf6_route_remove(route, oa->route_table); + else + ospf6_route_remove(route, oa->ospf6->route_table); + } + if (route != NULL) + ospf6_route_unlock(route); + + ospf6_route_delete(route_to_del); +} + +void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry, struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + uint32_t router; + + if (!CHECK_FLAG(asbr_entry->flag, OSPF6_ROUTE_BEST)) { + char buf[16]; + inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&asbr_entry->prefix), + buf, sizeof(buf)); + zlog_info("ignore non-best path: lsentry %s add", buf); + return; + } + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); + for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) { + if (!OSPF6_LSA_IS_MAXAGE(lsa)) + ospf6_asbr_lsa_add(lsa); + } +} + +void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry, + struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + uint32_t router; + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + router = ospf6_linkstate_prefix_adv_router(&asbr_entry->prefix); + for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, router, lsa)) + ospf6_asbr_lsa_remove(lsa, asbr_entry); +} + + +/* redistribute function */ +static void ospf6_asbr_routemap_set(struct ospf6_redist *red, + const char *mapname) +{ + if (ROUTEMAP_NAME(red)) { + route_map_counter_decrement(ROUTEMAP(red)); + free(ROUTEMAP_NAME(red)); + } + + ROUTEMAP_NAME(red) = strdup(mapname); + ROUTEMAP(red) = route_map_lookup_by_name(mapname); + route_map_counter_increment(ROUTEMAP(red)); +} + +static void ospf6_asbr_routemap_unset(struct ospf6_redist *red) +{ + if (ROUTEMAP_NAME(red)) + free(ROUTEMAP_NAME(red)); + + route_map_counter_decrement(ROUTEMAP(red)); + + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; +} + +static void ospf6_asbr_routemap_update_timer(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + struct ospf6_redist *red; + int type; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + continue; + + if (!CHECK_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED)) + continue; + + if (ROUTEMAP_NAME(red)) + ROUTEMAP(red) = + route_map_lookup_by_name(ROUTEMAP_NAME(red)); + + if (ROUTEMAP(red)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s update, reset redist %s", + __func__, ROUTEMAP_NAME(red), + ZROUTE_NAME(type)); + + ospf6_zebra_no_redistribute(type, ospf6->vrf_id); + ospf6_zebra_redistribute(type, ospf6->vrf_id); + } + + UNSET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED); + } +} + +void ospf6_asbr_distribute_list_update(struct ospf6 *ospf6, + struct ospf6_redist *red) +{ + SET_FLAG(red->flag, OSPF6_IS_RMAP_CHANGED); + + if (event_is_scheduled(ospf6->t_distribute_update)) + return; + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("%s: trigger redistribute reset thread", __func__); + + event_add_timer_msec(master, ospf6_asbr_routemap_update_timer, ospf6, + OSPF_MIN_LS_INTERVAL, &ospf6->t_distribute_update); +} + +void ospf6_asbr_routemap_update(const char *mapname) +{ + int type; + struct listnode *node, *nnode; + struct ospf6 *ospf6 = NULL; + struct ospf6_redist *red; + + if (om6 == NULL) + return; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red || (ROUTEMAP_NAME(red) == NULL)) + continue; + ROUTEMAP(red) = + route_map_lookup_by_name(ROUTEMAP_NAME(red)); + + if (mapname == NULL + || strcmp(ROUTEMAP_NAME(red), mapname)) + continue; + if (ROUTEMAP(red)) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s update, reset redist %s", + __func__, + mapname, + ZROUTE_NAME( + type)); + + route_map_counter_increment(ROUTEMAP(red)); + ospf6_asbr_distribute_list_update(ospf6, red); + } else { + /* + * if the mapname matches a + * route-map on ospf6 but the + * map doesn't exist, it is + * being deleted. flush and then + * readvertise + */ + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug( + "%s: route-map %s deleted, reset redist %s", + __func__, + mapname, + ZROUTE_NAME( + type)); + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_asbr_routemap_set(red, mapname); + ospf6_asbr_redistribute_set(ospf6, type); + } + } + } +} + +static void ospf6_asbr_routemap_event(const char *name) +{ + int type; + struct listnode *node, *nnode; + struct ospf6 *ospf6; + struct ospf6_redist *red; + + if (om6 == NULL) + return; + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (red && ROUTEMAP_NAME(red) + && (strcmp(ROUTEMAP_NAME(red), name) == 0)) + ospf6_asbr_distribute_list_update(ospf6, red); + } + } +} + +int ospf6_asbr_is_asbr(struct ospf6 *o) +{ + return (o->external_table->count || IS_OSPF6_ASBR(o)); +} + +struct ospf6_redist *ospf6_redist_lookup(struct ospf6 *ospf6, int type, + unsigned short instance) +{ + struct list *red_list; + struct listnode *node; + struct ospf6_redist *red; + + red_list = ospf6->redist[type]; + if (!red_list) + return (NULL); + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + if (red->instance == instance) + return red; + + return NULL; +} + +static struct ospf6_redist *ospf6_redist_add(struct ospf6 *ospf6, int type, + uint8_t instance) +{ + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, instance); + if (red) + return red; + + if (!ospf6->redist[type]) + ospf6->redist[type] = list_new(); + + red = XCALLOC(MTYPE_OSPF6_REDISTRIBUTE, sizeof(struct ospf6_redist)); + red->instance = instance; + red->dmetric.type = -1; + red->dmetric.value = -1; + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; + + listnode_add(ospf6->redist[type], red); + ospf6->redistribute++; + + return red; +} + +static void ospf6_redist_del(struct ospf6 *ospf6, struct ospf6_redist *red, + int type) +{ + if (red) { + listnode_delete(ospf6->redist[type], red); + if (!ospf6->redist[type]->count) { + list_delete(&ospf6->redist[type]); + } + XFREE(MTYPE_OSPF6_REDISTRIBUTE, red); + ospf6->redistribute--; + } +} + +/*Set the status of the ospf instance to ASBR based on the status parameter, + * rechedule SPF calculation, originate router LSA*/ +void ospf6_asbr_status_update(struct ospf6 *ospf6, int status) +{ + struct listnode *lnode, *lnnode; + struct ospf6_area *oa; + + zlog_info("ASBR[%s:Status:%d]: Update", ospf6->name, status); + + if (status) { + if (IS_OSPF6_ASBR(ospf6)) { + zlog_info("ASBR[%s:Status:%d]: Already ASBR", + ospf6->name, status); + return; + } + SET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR); + } else { + if (!IS_OSPF6_ASBR(ospf6)) { + zlog_info("ASBR[%s:Status:%d]: Already non ASBR", + ospf6->name, status); + return; + } + UNSET_FLAG(ospf6->flag, OSPF6_FLAG_ASBR); + } + + /* Transition from/to status ASBR, schedule timer. */ + ospf6_spf_schedule(ospf6, OSPF6_SPF_FLAGS_ASBR_STATUS_CHANGE); + + /* Reoriginate router LSA for all areas */ + for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, lnnode, oa)) + OSPF6_ROUTER_LSA_SCHEDULE(oa); +} + +static void ospf6_asbr_redistribute_set(struct ospf6 *ospf6, int type) +{ + ospf6_zebra_redistribute(type, ospf6->vrf_id); + + ++ospf6->redist_count; + ospf6_asbr_status_update(ospf6, ospf6->redist_count); +} + +static void ospf6_asbr_redistribute_unset(struct ospf6 *ospf6, + struct ospf6_redist *red, int type) +{ + struct ospf6_route *route; + struct ospf6_external_info *info; + + ospf6_zebra_no_redistribute(type, ospf6->vrf_id); + + for (route = ospf6_route_head(ospf6->external_table); route; + route = ospf6_route_next(route)) { + info = route->route_option; + if (info->type != type) + continue; + + ospf6_asbr_redistribute_remove(info->type, 0, &route->prefix, + ospf6); + } + + ospf6_asbr_routemap_unset(red); + --ospf6->redist_count; + ospf6_asbr_status_update(ospf6, ospf6->redist_count); +} + +/* When an area is unstubified, flood all the external LSAs in the area */ +void ospf6_asbr_send_externals_to_area(struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa, *lsanext; + + for (ALL_LSDB(oa->ospf6->lsdb, lsa, lsanext)) { + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("%s: Flooding AS-External LSA %s", + __func__, lsa->name); + + ospf6_flood_area(NULL, lsa, oa); + } + } +} + +/* When an area is stubified, remove all the external LSAs in the area */ +void ospf6_asbr_remove_externals_from_area(struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa, *lsanext; + struct listnode *node, *nnode; + struct ospf6_area *area; + struct ospf6 *ospf6 = oa->ospf6; + const struct route_node *iterend; + + /* skip if router is in other non-stub/non-NSSA areas */ + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) + if (!IS_AREA_STUB(area) && !IS_AREA_NSSA(area)) + return; + + /* if router is only in a stub area then purge AS-External LSAs */ + iterend = ospf6_lsdb_head(ospf6->lsdb, 0, 0, 0, &lsa); + while (lsa != NULL) { + assert(lsa->lock > 1); + lsanext = ospf6_lsdb_next(iterend, lsa); + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) + ospf6_lsdb_remove(lsa, ospf6->lsdb); + lsa = lsanext; + } +} + +static struct ospf6_external_aggr_rt * +ospf6_external_aggr_match(struct ospf6 *ospf6, struct prefix *p) +{ + struct route_node *node; + + node = route_node_match(ospf6->rt_aggr_tbl, p); + if (node == NULL) + return NULL; + + if (IS_OSPF6_DEBUG_AGGR) { + struct ospf6_external_aggr_rt *ag = node->info; + zlog_debug("%s: Matching aggregator found.prefix: %pFX Aggregator %pFX", + __func__, + p, + &ag->p); + } + + route_unlock_node(node); + + return node->info; +} + +static void ospf6_external_lsa_fwd_addr_set(struct ospf6 *ospf6, + const struct in6_addr *nexthop, + struct in6_addr *fwd_addr) +{ + struct vrf *vrf; + struct interface *ifp; + struct prefix nh; + + /* Initialize forwarding address to zero. */ + memset(fwd_addr, 0, sizeof(*fwd_addr)); + + vrf = vrf_lookup_by_id(ospf6->vrf_id); + if (!vrf) + return; + + nh.family = AF_INET6; + nh.u.prefix6 = *nexthop; + nh.prefixlen = IPV6_MAX_BITLEN; + + /* + * Use the route's nexthop as the forwarding address if it meets the + * following conditions: + * - It's a global address. + * - The associated nexthop interface is OSPF-enabled. + */ + if (IN6_IS_ADDR_UNSPECIFIED(nexthop) || IN6_IS_ADDR_LINKLOCAL(nexthop)) + return; + + FOR_ALL_INTERFACES (vrf, ifp) { + struct ospf6_interface *oi = ifp->info; + struct connected *connected; + + if (!oi || CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) + continue; + + frr_each (if_connected, ifp->connected, connected) { + if (connected->address->family != AF_INET6) + continue; + if (IN6_IS_ADDR_LINKLOCAL(&connected->address->u.prefix6)) + continue; + if (!prefix_match(connected->address, &nh)) + continue; + + *fwd_addr = *nexthop; + return; + } + } +} + +void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex, + struct prefix *prefix, unsigned int nexthop_num, + const struct in6_addr *nexthop, route_tag_t tag, + struct ospf6 *ospf6, uint32_t metric) +{ + route_map_result_t ret; + struct ospf6_route troute; + struct ospf6_external_info tinfo; + struct ospf6_route *route, *match; + struct ospf6_external_info *info; + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + return; + + if ((type != DEFAULT_ROUTE) + && !ospf6_zebra_is_redistribute(type, ospf6->vrf_id)) + return; + + memset(&troute, 0, sizeof(troute)); + memset(&tinfo, 0, sizeof(tinfo)); + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Redistribute %pFX (%s)", prefix, + type == DEFAULT_ROUTE + ? "default-information-originate" + : ZROUTE_NAME(type)); + + /* if route-map was specified but not found, do not advertise */ + if (ROUTEMAP_NAME(red)) { + if (ROUTEMAP(red) == NULL) + ospf6_asbr_routemap_update(NULL); + if (ROUTEMAP(red) == NULL) { + zlog_warn( + "route-map \"%s\" not found, suppress redistributing", + ROUTEMAP_NAME(red)); + return; + } + } + + /* apply route-map */ + if (ROUTEMAP(red)) { + troute.route_option = &tinfo; + troute.ospf6 = ospf6; + troute.path.redistribute_cost = metric; + tinfo.ifindex = ifindex; + tinfo.tag = tag; + + ret = route_map_apply(ROUTEMAP(red), prefix, &troute); + if (ret == RMAP_DENYMATCH) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Denied by route-map \"%s\"", + ROUTEMAP_NAME(red)); + ospf6_asbr_redistribute_remove(type, ifindex, prefix, + ospf6); + return; + } + } + + match = ospf6_route_lookup(prefix, ospf6->external_table); + if (match) { + info = match->route_option; + /* copy result of route-map */ + if (ROUTEMAP(red)) { + if (troute.path.metric_type) + match->path.metric_type = + troute.path.metric_type; + else + match->path.metric_type = + metric_type(ospf6, type, 0); + if (troute.path.cost) + match->path.cost = troute.path.cost; + else + match->path.cost = metric_value(ospf6, type, 0); + + if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) + memcpy(&info->forwarding, &tinfo.forwarding, + sizeof(struct in6_addr)); + info->tag = tinfo.tag; + } else { + /* If there is no route-map, simply update the tag and + * metric fields + */ + match->path.metric_type = metric_type(ospf6, type, 0); + match->path.cost = metric_value(ospf6, type, 0); + info->tag = tag; + } + + info->type = type; + + if (nexthop_num && nexthop) { + ospf6_route_add_nexthop(match, ifindex, nexthop); + ospf6_external_lsa_fwd_addr_set(ospf6, nexthop, + &info->forwarding); + } else + ospf6_route_add_nexthop(match, ifindex, NULL); + + match->path.origin.id = htonl(info->id); + ospf6_handle_external_lsa_origination(ospf6, match, prefix); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); + + return; + } + + /* create new entry */ + route = ospf6_route_create(ospf6); + route->type = OSPF6_DEST_TYPE_NETWORK; + prefix_copy(&route->prefix, prefix); + + info = (struct ospf6_external_info *)XCALLOC( + MTYPE_OSPF6_EXTERNAL_INFO, sizeof(struct ospf6_external_info)); + route->route_option = info; + + /* copy result of route-map */ + if (ROUTEMAP(red)) { + if (troute.path.metric_type) + route->path.metric_type = troute.path.metric_type; + else + route->path.metric_type = metric_type(ospf6, type, 0); + if (troute.path.cost) + route->path.cost = troute.path.cost; + else + route->path.cost = metric_value(ospf6, type, 0); + if (!IN6_IS_ADDR_UNSPECIFIED(&tinfo.forwarding)) + memcpy(&info->forwarding, &tinfo.forwarding, + sizeof(struct in6_addr)); + info->tag = tinfo.tag; + } else { + /* If there is no route-map, simply update the tag and metric + * fields + */ + route->path.metric_type = metric_type(ospf6, type, 0); + route->path.cost = metric_value(ospf6, type, 0); + info->tag = tag; + } + + info->type = type; + if (nexthop_num && nexthop) { + ospf6_route_add_nexthop(route, ifindex, nexthop); + ospf6_external_lsa_fwd_addr_set(ospf6, nexthop, + &info->forwarding); + } else + ospf6_route_add_nexthop(route, ifindex, NULL); + + route = ospf6_route_add(route, ospf6->external_table); + ospf6_handle_external_lsa_origination(ospf6, route, prefix); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); + +} + +static void ospf6_asbr_external_lsa_remove_by_id(struct ospf6 *ospf6, + uint32_t id) +{ + struct ospf6_lsa *lsa; + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(id), ospf6->router_id, ospf6->lsdb); + if (!lsa) + return; + + ospf6_external_lsa_purge(ospf6, lsa); + +} + +static void +ospf6_link_route_to_aggr(struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + (void)hash_get(aggr->match_extnl_hash, rt, hash_alloc_intern); + rt->aggr_route = aggr; +} + +static void +ospf6_asbr_summary_remove_lsa_and_route(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + + /* Send a Max age LSA if it is already originated.*/ + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) + return; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Flushing Aggregate route (%pFX)", + __func__, + &aggr->p); + + ospf6_asbr_external_lsa_remove_by_id(ospf6, aggr->id); + + if (aggr->route) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Remove the blackhole route", + __func__); + + ospf6_zebra_route_update_remove(aggr->route, ospf6); + if (aggr->route->route_option) + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, + aggr->route->route_option); + ospf6_route_delete(aggr->route); + aggr->route = NULL; + } + + aggr->id = 0; + /* Unset the Origination flag */ + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); +} + +static void +ospf6_unlink_route_from_aggr(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Unlinking external route(%pFX) from aggregator(%pFX), external route count:%ld", + __func__, + &rt->prefix, + &aggr->p, + OSPF6_EXTERNAL_RT_COUNT(aggr)); + + hash_release(aggr->match_extnl_hash, rt); + rt->aggr_route = NULL; + + /* Flush the aggregate route if matching + * external route count becomes zero. + */ + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); +} + +void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex, + struct prefix *prefix, struct ospf6 *ospf6) +{ + struct ospf6_route *match; + struct ospf6_external_info *info = NULL; + + match = ospf6_route_lookup(prefix, ospf6->external_table); + if (match == NULL) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("No such route %pFX to withdraw", prefix); + return; + } + + info = match->route_option; + assert(info); + + if (info->type != type) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Original protocol mismatch: %pFX", prefix); + return; + } + + /* This means aggregation on this route was not done, hence remove LSA + * if any originated for this prefix + */ + if (!match->aggr_route) + ospf6_asbr_external_lsa_remove_by_id(ospf6, info->id); + else + ospf6_unlink_route_from_aggr(ospf6, match->aggr_route, match); + + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Removing route from external table %pFX", + prefix); + + ospf6_route_remove(match, ospf6->external_table); + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, info); + + ospf6_asbr_status_update(ospf6, ospf6->redistribute); +} + +DEFPY (ospf6_redistribute, + ospf6_redistribute_cmd, + "redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)$metric_type|route-map RMAP_NAME$rmap_str}]", + "Redistribute\n" + FRR_REDIST_HELP_STR_OSPF6D + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Route map name\n") +{ + int type; + struct ospf6_redist *red; + int idx_protocol = 1; + char *proto = argv[idx_protocol]->text; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + type = proto_redistnum(AFI_IP6, proto); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + if (!metric_str) + metric = -1; + if (!metric_type_str) + metric_type = -1; + + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) { + red = ospf6_redist_add(ospf6, type, 0); + } else { + /* Check if nothing has changed. */ + if (red->dmetric.value == metric + && red->dmetric.type == metric_type + && ((!ROUTEMAP_NAME(red) && !rmap_str) + || (ROUTEMAP_NAME(red) && rmap_str + && strmatch(ROUTEMAP_NAME(red), rmap_str)))) + return CMD_SUCCESS; + + ospf6_asbr_redistribute_unset(ospf6, red, type); + } + + red->dmetric.value = metric; + red->dmetric.type = metric_type; + if (rmap_str) + ospf6_asbr_routemap_set(red, rmap_str); + else + ospf6_asbr_routemap_unset(red); + ospf6_asbr_redistribute_set(ospf6, type); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_redistribute, + no_ospf6_redistribute_cmd, + "no redistribute " FRR_REDIST_STR_OSPF6D "[{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + "Redistribute\n" + FRR_REDIST_HELP_STR_OSPF6D + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Route map name\n") +{ + int type; + struct ospf6_redist *red; + int idx_protocol = 2; + char *proto = argv[idx_protocol]->text; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + type = proto_redistnum(AFI_IP6, proto); + if (type < 0) + return CMD_WARNING_CONFIG_FAILED; + + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + return CMD_SUCCESS; + + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_redist_del(ospf6, red, type); + + return CMD_SUCCESS; +} + +int ospf6_redistribute_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + + vty_out(vty, " redistribute %s", ZROUTE_NAME(type)); + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", red->dmetric.value); + if (red->dmetric.type == 1) + vty_out(vty, " metric-type 1"); + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); + vty_out(vty, "\n"); + } + + return 0; +} + +static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6, + json_object *json_array, + json_object *json, bool use_json) +{ + int type; + int nroute[ZEBRA_ROUTE_MAX]; + int total; + struct ospf6_route *route; + struct ospf6_external_info *info; + json_object *json_route; + struct ospf6_redist *red; + + total = 0; + memset(nroute, 0, sizeof(nroute)); + for (route = ospf6_route_head(ospf6->external_table); route; + route = ospf6_route_next(route)) { + info = route->route_option; + nroute[info->type]++; + total++; + } + + if (!use_json) + vty_out(vty, "Redistributing External Routes from:\n"); + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + + red = ospf6_redist_lookup(ospf6, type, 0); + + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + + if (use_json) { + json_route = json_object_new_object(); + json_object_string_add(json_route, "routeType", + ZROUTE_NAME(type)); + json_object_int_add(json_route, "numberOfRoutes", + nroute[type]); + json_object_boolean_add(json_route, + "routeMapNamePresent", + ROUTEMAP_NAME(red)); + } + + if (ROUTEMAP_NAME(red)) { + if (use_json) { + json_object_string_add(json_route, + "routeMapName", + ROUTEMAP_NAME(red)); + json_object_boolean_add(json_route, + "routeMapFound", + ROUTEMAP(red)); + } else + vty_out(vty, + " %d: %s with route-map \"%s\"%s\n", + nroute[type], ZROUTE_NAME(type), + ROUTEMAP_NAME(red), + (ROUTEMAP(red) ? "" + : " (not found !)")); + } else { + if (!use_json) + vty_out(vty, " %d: %s\n", nroute[type], + ZROUTE_NAME(type)); + } + + if (use_json) + json_object_array_add(json_array, json_route); + } + if (use_json) { + json_object_object_add(json, "redistributedRoutes", json_array); + json_object_int_add(json, "totalRoutes", total); + } else + vty_out(vty, "Total %d routes\n", total); +} + +static void ospf6_redistribute_default_set(struct ospf6 *ospf6, int originate) +{ + struct prefix_ipv6 p = {}; + struct in6_addr nexthop = {}; + int cur_originate = ospf6->default_originate; + + p.family = AF_INET6; + p.prefixlen = 0; + + ospf6->default_originate = originate; + + switch (cur_originate) { + case DEFAULT_ORIGINATE_NONE: + break; + case DEFAULT_ORIGINATE_ZEBRA: + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, + zclient, AFI_IP6, ospf6->vrf_id); + ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0, + (struct prefix *)&p, ospf6); + + break; + case DEFAULT_ORIGINATE_ALWAYS: + ospf6_asbr_redistribute_remove(DEFAULT_ROUTE, 0, + (struct prefix *)&p, ospf6); + break; + } + + switch (originate) { + case DEFAULT_ORIGINATE_NONE: + break; + case DEFAULT_ORIGINATE_ZEBRA: + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + zclient, AFI_IP6, ospf6->vrf_id); + + break; + case DEFAULT_ORIGINATE_ALWAYS: + ospf6_asbr_redistribute_add(DEFAULT_ROUTE, 0, + (struct prefix *)&p, 0, &nexthop, 0, + ospf6, 0); + break; + } +} + +/* Default Route originate. */ +DEFPY (ospf6_default_route_originate, + ospf6_default_route_originate_cmd, + "default-information originate [{always$always|metric (0-16777214)$mval|metric-type (1-2)$mtype|route-map RMAP_NAME$rtmap}]", + "Control distribution of default route\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + int default_originate = DEFAULT_ORIGINATE_ZEBRA; + struct ospf6_redist *red; + bool sameRtmap = false; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + int cur_originate = ospf6->default_originate; + + red = ospf6_redist_add(ospf6, DEFAULT_ROUTE, 0); + + if (always != NULL) + default_originate = DEFAULT_ORIGINATE_ALWAYS; + + if (mval_str == NULL) + mval = -1; + + if (mtype_str == NULL) + mtype = -1; + + /* To check if user is providing same route map */ + if ((!rtmap && !ROUTEMAP_NAME(red)) || + (rtmap && ROUTEMAP_NAME(red) && + (strcmp(rtmap, ROUTEMAP_NAME(red)) == 0))) + sameRtmap = true; + + /* Don't allow if the same lsa is already originated. */ + if ((sameRtmap) && (red->dmetric.type == mtype) + && (red->dmetric.value == mval) + && (cur_originate == default_originate)) + return CMD_SUCCESS; + + /* Updating Metric details */ + red->dmetric.type = mtype; + red->dmetric.value = mval; + + /* updating route map details */ + if (rtmap) + ospf6_asbr_routemap_set(red, rtmap); + else + ospf6_asbr_routemap_unset(red); + + ospf6_redistribute_default_set(ospf6, default_originate); + return CMD_SUCCESS; +} + +DEFPY (no_ospf6_default_information_originate, + no_ospf6_default_information_originate_cmd, + "no default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + "Control distribution of default information\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPFv3 default metric\n" + "OSPFv3 metric\n" + "OSPFv3 metric type for default routes\n" + "Set OSPFv3 External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + struct ospf6_redist *red; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0); + if (!red) + return CMD_SUCCESS; + + ospf6_asbr_routemap_unset(red); + ospf6_redist_del(ospf6, red, DEFAULT_ROUTE); + + ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE); + return CMD_SUCCESS; +} + +/* Routemap Functions */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_address_prefixlist(void *rule, + const struct prefix *prefix, + + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP6, (char *)rule); + if (plist == NULL) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void * +ospf6_routemap_rule_match_address_prefixlist_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_match_address_prefixlist_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_address_prefixlist_cmd = { + "ipv6 address prefix-list", + ospf6_routemap_rule_match_address_prefixlist, + ospf6_routemap_rule_match_address_prefixlist_compile, + ospf6_routemap_rule_match_address_prefixlist_free, +}; + +/* `match interface IFNAME' */ +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_interface(void *rule, const struct prefix *prefix, + void *object) +{ + struct interface *ifp; + struct ospf6_route *route; + struct ospf6_external_info *ei; + + route = object; + ei = route->route_option; + ifp = if_lookup_by_name((char *)rule, route->ospf6->vrf_id); + + if (ifp != NULL && ei->ifindex == ifp->ifindex) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +/* Route map `interface' match statement. `arg' should be + interface name. */ +static void *ospf6_routemap_rule_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `interface' value. */ +static void ospf6_routemap_rule_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for interface matching. */ +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_interface_cmd = { + "interface", + ospf6_routemap_rule_match_interface, + ospf6_routemap_rule_match_interface_compile, + ospf6_routemap_rule_match_interface_free +}; + +/* Match function for matching route tags */ +static enum route_map_cmd_result_t +ospf6_routemap_rule_match_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + if (info->tag == *tag) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_match_tag_cmd = { + "tag", + ospf6_routemap_rule_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_metric_type(void *rule, const struct prefix *prefix, + void *object) +{ + char *metric_type = rule; + struct ospf6_route *route = object; + + if (strcmp(metric_type, "type-2") == 0) + route->path.metric_type = 2; + else + route->path.metric_type = 1; + + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_metric_type_compile(const char *arg) +{ + if (strcmp(arg, "type-2") && strcmp(arg, "type-1")) + return NULL; + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_set_metric_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_metric_type_cmd = { + "metric-type", + ospf6_routemap_rule_set_metric_type, + ospf6_routemap_rule_set_metric_type_compile, + ospf6_routemap_rule_set_metric_type_free, +}; + +struct ospf6_metric { + enum { metric_increment, metric_decrement, metric_absolute } type; + bool used; + uint32_t metric; +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_metric(void *rule, const struct prefix *prefix, + void *object) +{ + struct ospf6_metric *metric; + struct ospf6_route *route; + + /* Fetch routemap's rule information. */ + metric = rule; + route = object; + + /* Set metric out value. */ + if (!metric->used) + return RMAP_OKAY; + + if (route->path.redistribute_cost > OSPF6_EXT_PATH_METRIC_MAX) + route->path.redistribute_cost = OSPF6_EXT_PATH_METRIC_MAX; + + if (metric->type == metric_increment) { + route->path.cost = route->path.redistribute_cost + + metric->metric; + + /* Check overflow */ + if (route->path.cost > OSPF6_EXT_PATH_METRIC_MAX || + route->path.cost < metric->metric) + route->path.cost = OSPF6_EXT_PATH_METRIC_MAX; + } else if (metric->type == metric_decrement) { + route->path.cost = route->path.redistribute_cost - + metric->metric; + + /* Check overflow */ + if (route->path.cost == 0 || + route->path.cost > route->path.redistribute_cost) + route->path.cost = 1; + } else if (metric->type == metric_absolute) + route->path.cost = metric->metric; + + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_metric_compile(const char *arg) +{ + struct ospf6_metric *metric; + + metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*metric)); + metric->used = false; + + if (all_digit(arg)) + metric->type = metric_absolute; + + if ((arg[0] == '+') && all_digit(arg + 1)) { + metric->type = metric_increment; + arg++; + } + + if ((arg[0] == '-') && all_digit(arg + 1)) { + metric->type = metric_decrement; + arg++; + } + + metric->metric = strtoul(arg, NULL, 10); + + if (metric->metric > OSPF6_EXT_PATH_METRIC_MAX) + metric->metric = OSPF6_EXT_PATH_METRIC_MAX; + + if (metric->metric) + metric->used = true; + + return metric; +} + +static void ospf6_routemap_rule_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_metric_cmd = { + "metric", + ospf6_routemap_rule_set_metric, + ospf6_routemap_rule_set_metric_compile, + ospf6_routemap_rule_set_metric_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_forwarding(void *rule, const struct prefix *prefix, + void *object) +{ + char *forwarding = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + if (inet_pton(AF_INET6, forwarding, &info->forwarding) != 1) { + memset(&info->forwarding, 0, sizeof(struct in6_addr)); + return RMAP_ERROR; + } + + return RMAP_OKAY; +} + +static void *ospf6_routemap_rule_set_forwarding_compile(const char *arg) +{ + struct in6_addr a; + if (inet_pton(AF_INET6, arg, &a) != 1) + return NULL; + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void ospf6_routemap_rule_set_forwarding_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + ospf6_routemap_rule_set_forwarding_cmd = { + "forwarding-address", + ospf6_routemap_rule_set_forwarding, + ospf6_routemap_rule_set_forwarding_compile, + ospf6_routemap_rule_set_forwarding_free, +}; + +static enum route_map_cmd_result_t +ospf6_routemap_rule_set_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag = rule; + struct ospf6_route *route = object; + struct ospf6_external_info *info = route->route_option; + + info->tag = *tag; + return RMAP_OKAY; +} + +static const struct route_map_rule_cmd ospf6_routemap_rule_set_tag_cmd = { + "tag", + ospf6_routemap_rule_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* add "set metric-type" */ +DEFUN_YANG (ospf6_routemap_set_metric_type, ospf6_routemap_set_metric_type_cmd, + "set metric-type ", + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + char *ext = argv[2]->text; + + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-ospf-route-map:metric-type", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ext); + return nb_cli_apply_changes(vty, NULL); +} + +/* delete "set metric-type" */ +DEFUN_YANG (ospf6_routemap_no_set_metric_type, ospf6_routemap_no_set_metric_type_cmd, + "no set metric-type []", + NO_STR + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* add "set forwarding-address" */ +DEFUN_YANG (ospf6_routemap_set_forwarding, ospf6_routemap_set_forwarding_cmd, + "set forwarding-address X:X::X:X", + "Set value\n" + "Forwarding Address\n" + "IPv6 Address\n") +{ + int idx_ipv6 = 2; + const char *xpath = + "./set-action[action='frr-ospf6-route-map:forwarding-address']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-ospf6-route-map:ipv6-address", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, + argv[idx_ipv6]->arg); + return nb_cli_apply_changes(vty, NULL); +} + +/* delete "set forwarding-address" */ +DEFUN_YANG (ospf6_routemap_no_set_forwarding, ospf6_routemap_no_set_forwarding_cmd, + "no set forwarding-address [X:X::X:X]", + NO_STR + "Set value\n" + "Forwarding Address\n" + "IPv6 Address\n") +{ + const char *xpath = + "./set-action[action='frr-ospf6-route-map:forwarding-address']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static void ospf6_routemap_init(void) +{ + route_map_init(); + + route_map_add_hook(ospf6_asbr_routemap_update); + route_map_delete_hook(ospf6_asbr_routemap_update); + route_map_event_hook(ospf6_asbr_routemap_event); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_match_ipv6_address_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_install_match( + &ospf6_routemap_rule_match_address_prefixlist_cmd); + route_map_install_match(&ospf6_routemap_rule_match_interface_cmd); + route_map_install_match(&ospf6_routemap_rule_match_tag_cmd); + + route_map_install_set(&ospf6_routemap_rule_set_metric_type_cmd); + route_map_install_set(&ospf6_routemap_rule_set_metric_cmd); + route_map_install_set(&ospf6_routemap_rule_set_forwarding_cmd); + route_map_install_set(&ospf6_routemap_rule_set_tag_cmd); + + /* ASE Metric Type (e.g. Type-1/Type-2) */ + install_element(RMAP_NODE, &ospf6_routemap_set_metric_type_cmd); + install_element(RMAP_NODE, &ospf6_routemap_no_set_metric_type_cmd); + + /* ASE Metric */ + install_element(RMAP_NODE, &ospf6_routemap_set_forwarding_cmd); + install_element(RMAP_NODE, &ospf6_routemap_no_set_forwarding_cmd); +} + + +/* Display functions */ +static char *ospf6_as_external_lsa_get_prefix_str(struct ospf6_lsa *lsa, + char *buf, int buflen, + int pos) +{ + struct ospf6_as_external_lsa *external; + struct in6_addr in6; + int prefix_length = 0; + char tbuf[16]; + + if (lsa) { + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (pos == 0) { + ospf6_prefix_in6_addr(&in6, external, + &external->prefix); + prefix_length = external->prefix.prefix_length; + } else { + in6 = *((struct in6_addr + *)((caddr_t)external + + sizeof(struct + ospf6_as_external_lsa) + + OSPF6_PREFIX_SPACE( + external->prefix + .prefix_length))); + } + if (buf) { + inet_ntop(AF_INET6, &in6, buf, buflen); + if (prefix_length) { + snprintf(tbuf, sizeof(tbuf), "/%d", + prefix_length); + strlcat(buf, tbuf, buflen); + } + } + } + return (buf); +} + +static int ospf6_as_external_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + struct ospf6_as_external_lsa *external; + char buf[64]; + + assert(lsa->header); + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + /* bits */ + snprintf(buf, sizeof(buf), "%c%c%c", + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_E) ? 'E' + : '-'), + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F) ? 'F' + : '-'), + (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T) ? 'T' + : '-')); + + if (use_json) { + json_object_string_add(json_obj, "bits", buf); + json_object_int_add(json_obj, "metric", + (unsigned long)OSPF6_ASBR_METRIC(external)); + ospf6_prefix_options_printbuf(external->prefix.prefix_options, + buf, sizeof(buf)); + json_object_string_add(json_obj, "prefixOptions", buf); + json_object_int_add( + json_obj, "referenceLsType", + ntohs(external->prefix.prefix_refer_lstype)); + json_object_string_add(json_obj, "prefix", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 0)); + + /* Forwarding-Address */ + json_object_boolean_add( + json_obj, "forwardingAddressPresent", + CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) + json_object_string_add( + json_obj, "forwardingAddress", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 1)); + + /* Tag */ + json_object_boolean_add( + json_obj, "tagPresent", + CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)); + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) + json_object_int_add(json_obj, "tag", + ospf6_as_external_lsa_get_tag(lsa)); + } else { + vty_out(vty, " Bits: %s\n", buf); + vty_out(vty, " Metric: %5lu\n", + (unsigned long)OSPF6_ASBR_METRIC(external)); + + ospf6_prefix_options_printbuf(external->prefix.prefix_options, + buf, sizeof(buf)); + vty_out(vty, " Prefix Options: %s\n", buf); + + vty_out(vty, " Referenced LSType: %d\n", + ntohs(external->prefix.prefix_refer_lstype)); + + vty_out(vty, " Prefix: %s\n", + ospf6_as_external_lsa_get_prefix_str(lsa, buf, + sizeof(buf), 0)); + + /* Forwarding-Address */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_F)) { + vty_out(vty, " Forwarding-Address: %s\n", + ospf6_as_external_lsa_get_prefix_str( + lsa, buf, sizeof(buf), 1)); + } + + /* Tag */ + if (CHECK_FLAG(external->bits_metric, OSPF6_ASBR_BIT_T)) { + vty_out(vty, " Tag: %" ROUTE_TAG_PRI "\n", + ospf6_as_external_lsa_get_tag(lsa)); + } + } + + return 0; +} + +static void ospf6_asbr_external_route_show(struct vty *vty, + struct ospf6_route *route, + json_object *json_array, + bool use_json) +{ + struct ospf6_external_info *info = route->route_option; + char prefix[PREFIX2STR_BUFFER], id[16], forwarding[64]; + uint32_t tmp_id; + json_object *json_route; + char route_type[2]; + + prefix2str(&route->prefix, prefix, sizeof(prefix)); + tmp_id = ntohl(info->id); + inet_ntop(AF_INET, &tmp_id, id, sizeof(id)); + if (!IN6_IS_ADDR_UNSPECIFIED(&info->forwarding)) + inet_ntop(AF_INET6, &info->forwarding, forwarding, + sizeof(forwarding)); + else + snprintf(forwarding, sizeof(forwarding), ":: (ifindex %d)", + ospf6_route_get_first_nh_index(route)); + + if (use_json) { + json_route = json_object_new_object(); + snprintf(route_type, sizeof(route_type), "%c", + zebra_route_char(info->type)); + json_object_string_add(json_route, "routeType", route_type); + json_object_string_add(json_route, "destination", prefix); + json_object_string_add(json_route, "id", id); + json_object_int_add(json_route, "metricType", + route->path.metric_type); + json_object_int_add( + json_route, "routeCost", + (unsigned long)(route->path.metric_type == 2 + ? route->path.u.cost_e2 + : route->path.cost)); + json_object_string_add(json_route, "forwarding", forwarding); + + json_object_array_add(json_array, json_route); + } else + + vty_out(vty, "%c %-32pFX %-15s type-%d %5lu %s\n", + zebra_route_char(info->type), &route->prefix, id, + route->path.metric_type, + (unsigned long)(route->path.metric_type == 2 + ? route->path.u.cost_e2 + : route->path.cost), + forwarding); +} + +DEFUN(show_ipv6_ospf6_redistribute, show_ipv6_ospf6_redistribute_cmd, + "show ipv6 ospf6 [vrf ] redistribute [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "redistributing External information\n" JSON_STR) +{ + struct ospf6_route *route; + struct ospf6 *ospf6 = NULL; + json_object *json = NULL; + bool uj = use_json(argc, argv); + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + json_object *json_array_routes = NULL; + json_object *json_array_redistribute = NULL; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) { + json = json_object_new_object(); + json_array_routes = json_object_new_array(); + json_array_redistribute = json_object_new_array(); + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf + || ((ospf6->name == NULL && vrf_name == NULL) + || (ospf6->name && vrf_name + && strcmp(ospf6->name, vrf_name) == 0))) { + ospf6_redistribute_show_config( + vty, ospf6, json_array_redistribute, json, uj); + + for (route = ospf6_route_head(ospf6->external_table); + route; route = ospf6_route_next(route)) { + ospf6_asbr_external_route_show( + vty, route, json_array_routes, uj); + } + + if (uj) { + json_object_object_add(json, "routes", + json_array_routes); + vty_json(vty, json); + } + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static struct ospf6_lsa_handler as_external_handler = { + .lh_type = OSPF6_LSTYPE_AS_EXTERNAL, + .lh_name = "AS-External", + .lh_short_name = "ASE", + .lh_show = ospf6_as_external_lsa_show, + .lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str, + .lh_debug = 0}; + +static struct ospf6_lsa_handler nssa_external_handler = { + .lh_type = OSPF6_LSTYPE_TYPE_7, + .lh_name = "NSSA", + .lh_short_name = "Type7", + .lh_show = ospf6_as_external_lsa_show, + .lh_get_prefix_str = ospf6_as_external_lsa_get_prefix_str, + .lh_debug = 0}; + +void ospf6_asbr_init(void) +{ + ospf6_routemap_init(); + + ospf6_install_lsa_handler(&as_external_handler); + ospf6_install_lsa_handler(&nssa_external_handler); + + install_element(VIEW_NODE, &show_ipv6_ospf6_redistribute_cmd); + + install_element(OSPF6_NODE, &ospf6_default_route_originate_cmd); + install_element(OSPF6_NODE, + &no_ospf6_default_information_originate_cmd); + install_element(OSPF6_NODE, &ospf6_redistribute_cmd); + install_element(OSPF6_NODE, &no_ospf6_redistribute_cmd); +} + +void ospf6_asbr_redistribute_disable(struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + ospf6_asbr_redistribute_unset(ospf6, red, type); + ospf6_redist_del(ospf6, red, type); + } + red = ospf6_redist_lookup(ospf6, DEFAULT_ROUTE, 0); + if (red) { + ospf6_asbr_routemap_unset(red); + ospf6_redist_del(ospf6, red, type); + ospf6_redistribute_default_set(ospf6, DEFAULT_ORIGINATE_NONE); + } +} + +void ospf6_asbr_redistribute_reset(struct ospf6 *ospf6) +{ + int type; + struct ospf6_redist *red; + char buf[RMAP_NAME_MAXLEN]; + + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + buf[0] = '\0'; + if (type == ZEBRA_ROUTE_OSPF6) + continue; + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + continue; + + if (type == DEFAULT_ROUTE) { + ospf6_redistribute_default_set( + ospf6, ospf6->default_originate); + continue; + } + if (ROUTEMAP_NAME(red)) + strlcpy(buf, ROUTEMAP_NAME(red), sizeof(buf)); + + ospf6_asbr_redistribute_unset(ospf6, red, type); + if (buf[0]) + ospf6_asbr_routemap_set(red, buf); + ospf6_asbr_redistribute_set(ospf6, type); + } +} + +void ospf6_asbr_terminate(void) +{ + /* Cleanup route maps */ + route_map_finish(); +} + +DEFUN (debug_ospf6_asbr, + debug_ospf6_asbr_cmd, + "debug ospf6 asbr", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ASBR function\n" + ) +{ + OSPF6_DEBUG_ASBR_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_asbr, + no_debug_ospf6_asbr_cmd, + "no debug ospf6 asbr", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 ASBR function\n" + ) +{ + OSPF6_DEBUG_ASBR_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_asbr(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_ASBR) + vty_out(vty, "debug ospf6 asbr\n"); + return 0; +} + +static void ospf6_default_originate_write(struct vty *vty, struct ospf6 *o) +{ + struct ospf6_redist *red; + + vty_out(vty, " default-information originate"); + if (o->default_originate == DEFAULT_ORIGINATE_ALWAYS) + vty_out(vty, " always"); + + red = ospf6_redist_lookup(o, DEFAULT_ROUTE, 0); + if (red == NULL) { + vty_out(vty, "\n"); + return; + } + + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", red->dmetric.value); + + if (red->dmetric.type >= 0) + vty_out(vty, " metric-type %d", red->dmetric.type); + + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", ROUTEMAP_NAME(red)); + + vty_out(vty, "\n"); +} + +int ospf6_distribute_config_write(struct vty *vty, struct ospf6 *o) +{ + if (o == NULL) + return 0; + + /* Print default originate configuration. */ + if (o->default_originate != DEFAULT_ORIGINATE_NONE) + ospf6_default_originate_write(vty, o); + + return 0; +} + +void install_element_ospf6_debug_asbr(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_asbr_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_asbr_cmd); + install_element(CONFIG_NODE, &debug_ospf6_asbr_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_asbr_cmd); +} + +/* ASBR Summarisation */ +void ospf6_fill_aggr_route_details(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_route *rt_aggr = aggr->route; + struct ospf6_external_info *ei_aggr = rt_aggr->route_option; + + rt_aggr->prefix = aggr->p; + ei_aggr->tag = aggr->tag; + ei_aggr->type = 0; + ei_aggr->id = aggr->id; + + /* When metric is not configured, apply the default metric */ + rt_aggr->path.cost = ((aggr->metric == -1) ? + DEFAULT_DEFAULT_METRIC + : (unsigned int)(aggr->metric)); + rt_aggr->path.metric_type = aggr->mtype; + + rt_aggr->path.origin.id = htonl(aggr->id); +} + +static void +ospf6_summary_add_aggr_route_and_blackhole(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_route *rt_aggr; + struct ospf6_route *old_rt = NULL; + struct ospf6_external_info *info; + + /* Check if a route is already present. */ + if (aggr->route) + old_rt = aggr->route; + + /* Create summary route and save it. */ + rt_aggr = ospf6_route_create(ospf6); + rt_aggr->type = OSPF6_DEST_TYPE_NETWORK; + /* Needed to install route while calling zebra api */ + SET_FLAG(rt_aggr->flag, OSPF6_ROUTE_BEST); + + info = XCALLOC(MTYPE_OSPF6_EXTERNAL_INFO, sizeof(*info)); + rt_aggr->route_option = info; + aggr->route = rt_aggr; + + /* Prepare the external_info for aggregator + * Fill all the details which will get advertised + */ + ospf6_fill_aggr_route_details(ospf6, aggr); + + /* Add next-hop to Null interface. */ + ospf6_add_route_nexthop_blackhole(rt_aggr); + + /* Free the old route, if any. */ + if (old_rt) { + ospf6_zebra_route_update_remove(old_rt, ospf6); + + if (old_rt->route_option) + XFREE(MTYPE_OSPF6_EXTERNAL_INFO, old_rt->route_option); + + ospf6_route_delete(old_rt); + } + + ospf6_zebra_route_update_add(rt_aggr, ospf6); +} + +static void ospf6_originate_new_aggr_lsa(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct prefix prefix_id; + struct ospf6_lsa *lsa = NULL; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Originate new aggregate route(%pFX)", __func__, + &aggr->p); + + aggr->id = ospf6->external_id++; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "Advertise AS-External Id:%pI4 prefix %pFX metric %u", + &prefix_id.u.prefix4, &aggr->p, aggr->metric); + + ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr); + + /* Originate summary LSA */ + lsa = ospf6_originate_type5_type7_lsas(aggr->route, ospf6); + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Set the origination bit for aggregator", + __func__); + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } +} + +static void +ospf6_aggr_handle_advertise_change(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + /* Check if advertise option modified. */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + + return; + } + + /* There are no routes present under this aggregation config, hence + * nothing to originate here + */ + if (OSPF6_EXTERNAL_RT_COUNT(aggr) == 0) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: No routes present under this aggregation", + __func__); + return; + } + + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Now it is advertisable", + __func__); + + ospf6_originate_new_aggr_lsa(ospf6, aggr); + + return; + } +} + +static void +ospf6_originate_summary_lsa(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa = NULL, *aggr_lsa = NULL; + struct ospf6_external_info *info = NULL; + struct ospf6_external_aggr_rt *old_aggr; + struct ospf6_as_external_lsa *external; + struct ospf6_route *rt_aggr = NULL; + route_tag_t tag = 0; + unsigned int metric = 0; + int mtype; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Prepare to originate Summary route(%pFX)", + __func__, &aggr->p); + + /* This case to handle when the overlapping aggregator address + * is available. Best match will be considered.So need to delink + * from old aggregator and link to the new aggr. + */ + if (rt->aggr_route) { + if (rt->aggr_route != aggr) { + old_aggr = rt->aggr_route; + ospf6_unlink_route_from_aggr(ospf6, old_aggr, rt); + } + } + + /* Add the external route to hash table */ + ospf6_link_route_to_aggr(aggr, rt); + + /* The key for ID field is a running number and not prefix */ + info = rt->route_option; + assert(info); + if (info->id) + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, + ospf6->lsdb); + + aggr_lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(aggr->id), ospf6->router_id, ospf6->lsdb); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Aggr LSA ID: %d flags %x.", + __func__, aggr->id, aggr->aggrflags); + /* Don't originate external LSA, + * If it is configured not to advertise. + */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + /* If it is already originated as external LSA, + * But, it is configured not to advertise then + * flush the originated external lsa. + */ + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Purge the external LSA %s.", + __func__, lsa->name); + ospf6_external_lsa_purge(ospf6, lsa); + info->id = 0; + rt->path.origin.id = 0; + } + + if (aggr_lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Purge the aggr external LSA %s.", + __func__, lsa->name); + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + } + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + return; + } + + /* Summary route already originated, + * So, Do nothing. + */ + if (CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED)) { + if (!aggr_lsa) { + zlog_warn( + "%s: Could not refresh/originate %pFX", + __func__, + &aggr->p); + /* Remove the assert later */ + assert(aggr_lsa); + return; + } + + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + aggr_lsa->header); + metric = (unsigned long)OSPF6_ASBR_METRIC(external); + tag = ospf6_as_external_lsa_get_tag(aggr_lsa); + mtype = CHECK_FLAG(external->bits_metric, + OSPF6_ASBR_BIT_E) ? 2 : 1; + + /* Prepare the external_info for aggregator */ + ospf6_fill_aggr_route_details(ospf6, aggr); + rt_aggr = aggr->route; + /* If tag/metric/metric-type modified , then re-originate the + * route with modified tag/metric/metric-type details. + */ + if ((tag != aggr->tag) + || (metric != (unsigned int)rt_aggr->path.cost) + || (mtype != aggr->mtype)) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Routetag(old:%d new:%d)/Metric(o:%u,n:%u)/mtype(o:%d n:%d) modified,So refresh the summary route.(%pFX)", + __func__, tag, aggr->tag, + metric, + aggr->metric, + mtype, aggr->mtype, + &aggr->p); + + aggr_lsa = ospf6_originate_type5_type7_lsas(aggr->route, + ospf6); + if (aggr_lsa) + SET_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } + + return; + } + + /* If the external route prefix same as aggregate route + * and if external route is already originated as TYPE-5 + * then just update the aggr info and remove the route info + */ + if (lsa && prefix_same(&aggr->p, &rt->prefix)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Route prefix is same as aggr so no need to re-originate LSA(%pFX)", + __PRETTY_FUNCTION__, &aggr->p); + + aggr->id = info->id; + info->id = 0; + rt->path.origin.id = 0; + + ospf6_summary_add_aggr_route_and_blackhole(ospf6, aggr); + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + + return; + } + + ospf6_originate_new_aggr_lsa(ospf6, aggr); +} + +static void ospf6_aggr_handle_external_info(void *data) +{ + struct ospf6_route *rt = (struct ospf6_route *)data; + struct ospf6_external_aggr_rt *aggr = NULL; + struct ospf6_lsa *lsa = NULL; + struct ospf6_external_info *info; + struct ospf6 *ospf6 = NULL; + + rt->aggr_route = NULL; + + rt->to_be_processed = true; + + if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) + zlog_debug("%s: Handle external route for origination/refresh (%pFX)", + __func__, + &rt->prefix); + + ospf6 = rt->ospf6; + assert(ospf6); + + aggr = ospf6_external_aggr_match(ospf6, + &rt->prefix); + if (aggr) { + ospf6_originate_summary_lsa(ospf6, aggr, rt); + return; + } + + info = rt->route_option; + if (info->id) { + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, + ospf6->lsdb); + if (lsa) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: LSA found, refresh it", + __func__); + EVENT_OFF(lsa->refresh); + event_add_event(master, ospf6_lsa_refresh, lsa, 0, + &lsa->refresh); + return; + } + } + + info->id = ospf6->external_id++; + rt->path.origin.id = htonl(info->id); + + (void)ospf6_originate_type5_type7_lsas(rt, ospf6); +} + +void ospf6_asbr_summary_config_delete(struct ospf6 *ospf6, + struct route_node *rn) +{ + struct ospf6_external_aggr_rt *aggr = rn->info; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Deleting Aggregate route (%pFX)", + __func__, + &aggr->p); + + ospf6_asbr_summary_remove_lsa_and_route(ospf6, aggr); + + rn->info = NULL; + route_unlock_node(rn); +} + +static int +ospf6_handle_external_aggr_modify(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct ospf6_lsa *lsa = NULL; + struct ospf6_as_external_lsa *asel = NULL; + struct ospf6_route *rt_aggr; + unsigned int metric = 0; + route_tag_t tag = 0; + int mtype; + + lsa = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(aggr->id), ospf6->router_id, + ospf6->lsdb); + if (!lsa) { + zlog_warn( + "%s: Could not refresh/originate %pFX", + __func__, + &aggr->p); + + return OSPF6_FAILURE; + } + + asel = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end(lsa->header); + metric = (unsigned long)OSPF6_ASBR_METRIC(asel); + tag = ospf6_as_external_lsa_get_tag(lsa); + mtype = CHECK_FLAG(asel->bits_metric, + OSPF6_ASBR_BIT_E) ? 2 : 1; + + /* Fill all the details for advertisement */ + ospf6_fill_aggr_route_details(ospf6, aggr); + rt_aggr = aggr->route; + /* If tag/metric/metric-type modified , then + * re-originate the route with modified + * tag/metric/metric-type details. + */ + if ((tag != aggr->tag) + || (metric + != (unsigned int)rt_aggr->path.cost) + || (mtype + != aggr->mtype)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug( + "%s: Changed tag(old:%d new:%d)/metric(o:%u n:%d)/mtype(o:%d n:%d),So refresh the summary route.(%pFX)", + __func__, tag, + aggr->tag, + metric, + (unsigned int)rt_aggr->path.cost, + mtype, aggr->mtype, + &aggr->p); + + (void)ospf6_originate_type5_type7_lsas( + aggr->route, + ospf6); + } + + return OSPF6_SUCCESS; +} + +static void ospf6_handle_external_aggr_update(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + int ret; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Process modified aggregators.", __func__); + + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + struct ospf6_external_aggr_rt *aggr; + + if (!rn->info) + continue; + + aggr = rn->info; + + if (aggr->action == OSPF6_ROUTE_AGGR_DEL) { + aggr->action = OSPF6_ROUTE_AGGR_NONE; + ospf6_asbr_summary_config_delete(ospf6, rn); + + hash_clean_and_free(&aggr->match_extnl_hash, + ospf6_aggr_handle_external_info); + + XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr); + + } else if (aggr->action == OSPF6_ROUTE_AGGR_MODIFY) { + + aggr->action = OSPF6_ROUTE_AGGR_NONE; + + /* Check if tag/metric/metric-type modified */ + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_ORIGINATED) + && !CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) { + + ret = ospf6_handle_external_aggr_modify(ospf6, + aggr); + if (ret == OSPF6_FAILURE) + continue; + } + + /* Advertise option modified ? + * If so, handled it here. + */ + ospf6_aggr_handle_advertise_change(ospf6, aggr); + } + } +} + +static void ospf6_aggr_unlink_external_info(void *data) +{ + struct ospf6_route *rt = (struct ospf6_route *)data; + + rt->aggr_route = NULL; + + rt->to_be_processed = true; +} + +void ospf6_external_aggregator_free(struct ospf6_external_aggr_rt *aggr) +{ + hash_clean_and_free(&aggr->match_extnl_hash, + ospf6_aggr_unlink_external_info); + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Release the aggregator Address(%pFX)", + __func__, + &aggr->p); + + XFREE(MTYPE_OSPF6_EXTERNAL_RT_AGGR, aggr); +} + +static void +ospf6_delete_all_marked_aggregators(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + struct ospf6_external_aggr_rt *aggr; + + /* Loop through all the aggregators, Delete all aggregators + * which are marked as DELETE. Set action to NONE for remaining + * aggregators + */ + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + if (aggr->action != OSPF6_ROUTE_AGGR_DEL) { + aggr->action = OSPF6_ROUTE_AGGR_NONE; + continue; + } + ospf6_asbr_summary_config_delete(ospf6, rn); + ospf6_external_aggregator_free(aggr); + } +} + +static void ospf6_handle_exnl_rt_after_aggr_del(struct ospf6 *ospf6, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa; + + /* Process only marked external routes. + * These routes were part of a deleted + * aggregator.So, originate now. + */ + if (!rt->to_be_processed) + return; + + rt->to_be_processed = false; + + lsa = ospf6_find_external_lsa(ospf6, &rt->prefix); + + if (lsa) { + EVENT_OFF(lsa->refresh); + event_add_event(master, ospf6_lsa_refresh, lsa, 0, + &lsa->refresh); + } else { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Originate external route(%pFX)", + __func__, + &rt->prefix); + + (void)ospf6_originate_type5_type7_lsas(rt, ospf6); + } +} + +static void ospf6_handle_aggregated_exnl_rt(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + struct ospf6_route *rt) +{ + struct ospf6_lsa *lsa; + struct ospf6_as_external_lsa *ext_lsa; + struct ospf6_external_info *info; + + /* Handling the case where the external route prefix + * and aggegate prefix is same + * If same don't flush the originated external LSA. + */ + if (prefix_same(&aggr->p, &rt->prefix)) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: External Route prefix same as Aggregator(%pFX), so don't flush.", + __func__, + &rt->prefix); + + return; + } + + info = rt->route_option; + assert(info); + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, ospf6->lsdb); + if (lsa) { + ext_lsa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (rt->prefix.prefixlen != ext_lsa->prefix.prefix_length) + return; + + ospf6_external_lsa_purge(ospf6, lsa); + + /* Resetting the ID of route */ + rt->path.origin.id = 0; + info->id = 0; + } +} + +static void +ospf6_handle_external_aggr_add(struct ospf6 *ospf6) +{ + struct ospf6_route *rt = NULL; + struct ospf6_external_info *ei = NULL; + struct ospf6_external_aggr_rt *aggr; + + /* Delete all the aggregators which are marked as + * OSPF6_ROUTE_AGGR_DEL. + */ + ospf6_delete_all_marked_aggregators(ospf6); + + for (rt = ospf6_route_head(ospf6->external_table); rt; + rt = ospf6_route_next(rt)) { + ei = rt->route_option; + if (ei == NULL) + continue; + + if (is_default_prefix(&rt->prefix)) + continue; + + aggr = ospf6_external_aggr_match(ospf6, + &rt->prefix); + + /* If matching aggregator found, Add + * the external route refrenace to the + * aggregator and originate the aggr + * route if it is advertisable. + * flush the external LSA if it is + * already originated for this external + * prefix. + */ + if (aggr) { + ospf6_originate_summary_lsa(ospf6, aggr, rt); + + /* All aggregated external rts + * are handled here. + */ + ospf6_handle_aggregated_exnl_rt( + ospf6, aggr, rt); + continue; + } + + /* External routes which are only out + * of aggregation will be handled here. + */ + ospf6_handle_exnl_rt_after_aggr_del( + ospf6, rt); + } +} + +static void ospf6_asbr_summary_process(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + int operation = 0; + + operation = ospf6->aggr_action; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: operation:%d", + __func__, + operation); + + switch (operation) { + case OSPF6_ROUTE_AGGR_ADD: + ospf6_handle_external_aggr_add(ospf6); + break; + case OSPF6_ROUTE_AGGR_DEL: + case OSPF6_ROUTE_AGGR_MODIFY: + ospf6_handle_external_aggr_update(ospf6); + break; + default: + break; + } +} + +static void +ospf6_start_asbr_summary_delay_timer(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr, + ospf6_aggr_action_t operation) +{ + aggr->action = operation; + + if (event_is_scheduled(ospf6->t_external_aggr)) { + if (ospf6->aggr_action == OSPF6_ROUTE_AGGR_ADD) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Not required to restart timer,set is already added.", + __func__); + return; + } + + if (operation == OSPF6_ROUTE_AGGR_ADD) { + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s, Restarting Aggregator delay timer.", + __func__); + EVENT_OFF(ospf6->t_external_aggr); + } + } + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Start Aggregator delay timer %u(in seconds).", + __func__, ospf6->aggr_delay_interval); + + ospf6->aggr_action = operation; + event_add_timer(master, ospf6_asbr_summary_process, ospf6, + ospf6->aggr_delay_interval, &ospf6->t_external_aggr); +} + +int ospf6_asbr_external_rt_advertise(struct ospf6 *ospf6, + struct prefix *p) +{ + struct route_node *rn; + struct ospf6_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (!rn) + return OSPF6_INVALID; + + aggr = rn->info; + + route_unlock_node(rn); + + if (!CHECK_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF6_INVALID; + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + return OSPF6_SUCCESS; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_MODIFY); + + return OSPF6_SUCCESS; +} + +int ospf6_external_aggr_delay_timer_set(struct ospf6 *ospf6, uint16_t interval) +{ + ospf6->aggr_delay_interval = interval; + + return OSPF6_SUCCESS; +} + +static unsigned int ospf6_external_rt_hash_key(const void *data) +{ + const struct ospf6_route *rt = data; + unsigned int key = 0; + + key = prefix_hash_key(&rt->prefix); + return key; +} + +static bool ospf6_external_rt_hash_cmp(const void *d1, const void *d2) +{ + const struct ospf6_route *rt1 = d1; + const struct ospf6_route *rt2 = d2; + + return prefix_same(&rt1->prefix, &rt2->prefix); +} + +static struct ospf6_external_aggr_rt * +ospf6_external_aggr_new(struct prefix *p) +{ + struct ospf6_external_aggr_rt *aggr; + + aggr = XCALLOC(MTYPE_OSPF6_EXTERNAL_RT_AGGR, + sizeof(struct ospf6_external_aggr_rt)); + + prefix_copy(&aggr->p, p); + aggr->metric = -1; + aggr->mtype = DEFAULT_METRIC_TYPE; + aggr->match_extnl_hash = hash_create(ospf6_external_rt_hash_key, + ospf6_external_rt_hash_cmp, + "Ospf6 external route hash"); + return aggr; +} + +static void ospf6_external_aggr_add(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr) +{ + struct route_node *rn; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Adding Aggregate route to Aggr table (%pFX)", + __func__, + &aggr->p); + + rn = route_node_get(ospf6->rt_aggr_tbl, &aggr->p); + if (rn->info) + route_unlock_node(rn); + else + rn->info = aggr; +} + +int ospf6_asbr_external_rt_no_advertise(struct ospf6 *ospf6, + struct prefix *p) +{ + struct ospf6_external_aggr_rt *aggr; + route_tag_t tag = 0; + + aggr = ospf6_external_aggr_config_lookup(ospf6, p); + if (aggr) { + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF6_SUCCESS; + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + + aggr->tag = tag; + aggr->metric = -1; + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) + return OSPF6_SUCCESS; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_MODIFY); + } else { + aggr = ospf6_external_aggr_new(p); + + if (!aggr) + return OSPF6_FAILURE; + + SET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + ospf6_external_aggr_add(ospf6, aggr); + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_ADD); + } + + return OSPF6_SUCCESS; +} + +struct ospf6_external_aggr_rt * +ospf6_external_aggr_config_lookup(struct ospf6 *ospf6, struct prefix *p) +{ + struct route_node *rn; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (rn) { + route_unlock_node(rn); + return rn->info; + } + + return NULL; +} + + +int ospf6_external_aggr_config_set(struct ospf6 *ospf6, struct prefix *p, + route_tag_t tag, int metric, int mtype) +{ + struct ospf6_external_aggr_rt *aggregator; + + aggregator = ospf6_external_aggr_config_lookup(ospf6, p); + + if (aggregator) { + if (CHECK_FLAG(aggregator->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + UNSET_FLAG(aggregator->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE); + else if ((aggregator->tag == tag) + && (aggregator->metric == metric) + && (aggregator->mtype == mtype)) + return OSPF6_SUCCESS; + + aggregator->tag = tag; + aggregator->metric = metric; + aggregator->mtype = mtype; + + ospf6_start_asbr_summary_delay_timer(ospf6, aggregator, + OSPF6_ROUTE_AGGR_MODIFY); + } else { + aggregator = ospf6_external_aggr_new(p); + if (!aggregator) + return OSPF6_FAILURE; + + aggregator->tag = tag; + aggregator->metric = metric; + aggregator->mtype = mtype; + + ospf6_external_aggr_add(ospf6, aggregator); + ospf6_start_asbr_summary_delay_timer(ospf6, aggregator, + OSPF6_ROUTE_AGGR_ADD); + } + + return OSPF6_SUCCESS; +} + +int ospf6_external_aggr_config_unset(struct ospf6 *ospf6, + struct prefix *p) +{ + struct route_node *rn; + struct ospf6_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf6->rt_aggr_tbl, p); + if (!rn) + return OSPF6_INVALID; + + aggr = rn->info; + + route_unlock_node(rn); + + if (!OSPF6_EXTERNAL_RT_COUNT(aggr)) { + ospf6_asbr_summary_config_delete(ospf6, rn); + ospf6_external_aggregator_free(aggr); + return OSPF6_SUCCESS; + } + + ospf6_start_asbr_summary_delay_timer(ospf6, aggr, + OSPF6_ROUTE_AGGR_DEL); + + return OSPF6_SUCCESS; +} + +void ospf6_handle_external_lsa_origination(struct ospf6 *ospf6, + struct ospf6_route *rt, + struct prefix *p) +{ + + struct ospf6_external_aggr_rt *aggr; + struct ospf6_external_info *info; + struct prefix prefix_id; + + if (!is_default_prefix(p)) { + aggr = ospf6_external_aggr_match(ospf6, + p); + + if (aggr) { + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("%s: Send Aggregate LSA (%pFX)", + __func__, + &aggr->p); + + ospf6_originate_summary_lsa( + ospf6, aggr, rt); + + /* Handling the case where the + * external route prefix + * and aggegate prefix is same + * If same don't flush the + * originated + * external LSA. + */ + ospf6_handle_aggregated_exnl_rt( + ospf6, aggr, rt); + return; + } + } + + info = rt->route_option; + + /* When the info->id = 0, it means it is being originated for the + * first time. + */ + if (!info->id) { + info->id = ospf6->external_id++; + } else { + prefix_id.family = AF_INET; + prefix_id.prefixlen = 32; + prefix_id.u.prefix4.s_addr = htonl(info->id); + } + + rt->path.origin.id = htonl(info->id); + + if (IS_OSPF6_DEBUG_ASBR) { + zlog_debug("Advertise new AS-External Id:%pI4 prefix %pFX metric %u", + &prefix_id.u.prefix4, p, rt->path.metric_type); + } + + ospf6_originate_type5_type7_lsas(rt, ospf6); + +} + +void ospf6_unset_all_aggr_flag(struct ospf6 *ospf6) +{ + struct route_node *rn = NULL; + struct ospf6_external_aggr_rt *aggr; + + if (IS_OSPF6_DEBUG_AGGR) + zlog_debug("Unset the origination bit for all aggregator"); + + /* Resetting the running external ID counter so that the origination + * of external LSAs starts from the beginning 0.0.0.1 + */ + ospf6->external_id = OSPF6_EXT_INIT_LS_ID; + + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + UNSET_FLAG(aggr->aggrflags, OSPF6_EXTERNAL_AGGRT_ORIGINATED); + } +} diff --git a/ospf6d/ospf6_asbr.h b/ospf6d/ospf6_asbr.h new file mode 100644 index 0000000..21e6d89 --- /dev/null +++ b/ospf6d/ospf6_asbr.h @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2001 Yasuhiro Ohara + */ + +#ifndef OSPF6_ASBR_H +#define OSPF6_ASBR_H + +/* for struct ospf6_prefix */ +#include "ospf6_proto.h" +/* for struct ospf6_lsa */ +#include "ospf6_lsa.h" +/* for struct ospf6_route */ +#include "ospf6_route.h" + +/* Debug option */ +extern unsigned char conf_debug_ospf6_asbr; +#define OSPF6_DEBUG_ASBR_ON() (conf_debug_ospf6_asbr = 1) +#define OSPF6_DEBUG_ASBR_OFF() (conf_debug_ospf6_asbr = 0) +#define IS_OSPF6_DEBUG_ASBR (conf_debug_ospf6_asbr) + +struct ospf6_external_info { + /* External route type */ + int type; + + /* Originating Link State ID */ + uint32_t id; + + struct in6_addr forwarding; + + route_tag_t tag; + + ifindex_t ifindex; + +}; + +/* OSPF6 ASBR Summarisation */ +typedef enum { + OSPF6_ROUTE_AGGR_NONE = 0, + OSPF6_ROUTE_AGGR_ADD, + OSPF6_ROUTE_AGGR_DEL, + OSPF6_ROUTE_AGGR_MODIFY +} ospf6_aggr_action_t; + +#define OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE 0x1 +#define OSPF6_EXTERNAL_AGGRT_ORIGINATED 0x2 + +#define OSPF6_EXTERNAL_RT_COUNT(aggr) \ + (((struct ospf6_external_aggr_rt *)aggr)->match_extnl_hash->count) + +struct ospf6_external_aggr_rt { + /* range address and masklen */ + struct prefix p; + + /* use bits for OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE and + * OSPF6_EXTERNAL_AGGRT_ORIGINATED + */ + uint16_t aggrflags; + + /* To store external metric-type */ + uint8_t mtype; + + /* Route tag for summary address */ + route_tag_t tag; + + /* To store aggregated metric config */ + int metric; + + /* To Store the LS ID when LSA is originated */ + uint32_t id; + + /* Action to be done after delay timer expiry */ + int action; + + /* OSPFv3 route generated by summary address. */ + struct ospf6_route *route; + + /* Hash table of matching external routes */ + struct hash *match_extnl_hash; +}; + +/* AS-External-LSA */ +#define OSPF6_AS_EXTERNAL_LSA_MIN_SIZE 4U /* w/o IPv6 prefix */ +struct ospf6_as_external_lsa { + uint32_t bits_metric; + + struct ospf6_prefix prefix; + /* followed by none or one forwarding address */ + /* followed by none or one external route tag */ + /* followed by none or one referenced LS-ID */ +}; + +#define OSPF6_ASBR_BIT_T ntohl (0x01000000) +#define OSPF6_ASBR_BIT_F ntohl (0x02000000) +#define OSPF6_ASBR_BIT_E ntohl (0x04000000) + +#define OSPF6_ASBR_METRIC(E) \ + (ntohl((E)->bits_metric & htonl(OSPF6_EXT_PATH_METRIC_MAX))) +#define OSPF6_ASBR_METRIC_SET(E, C) \ + { \ + (E)->bits_metric &= htonl(~OSPF6_EXT_PATH_METRIC_MAX); \ + (E)->bits_metric |= htonl(OSPF6_EXT_PATH_METRIC_MAX) & \ + htonl(C); \ + } + +extern void ospf6_asbr_lsa_add(struct ospf6_lsa *lsa); + +extern void ospf6_asbr_lsa_remove(struct ospf6_lsa *lsa, + struct ospf6_route *asbr_entry); +extern void ospf6_asbr_lsentry_add(struct ospf6_route *asbr_entry, + struct ospf6 *ospf6); +extern void ospf6_asbr_lsentry_remove(struct ospf6_route *asbr_entry, + struct ospf6 *ospf6); + +extern int ospf6_asbr_is_asbr(struct ospf6 *o); +extern void ospf6_asbr_redistribute_add(int type, ifindex_t ifindex, + struct prefix *prefix, + unsigned int nexthop_num, + const struct in6_addr *nexthop, + route_tag_t tag, struct ospf6 *ospf6, + uint32_t metric); +extern void ospf6_asbr_redistribute_remove(int type, ifindex_t ifindex, + struct prefix *prefix, + struct ospf6 *ospf6); + +extern int ospf6_redistribute_config_write(struct vty *vty, + struct ospf6 *ospf6); + +extern void ospf6_asbr_init(void); +extern void ospf6_asbr_redistribute_disable(struct ospf6 *ospf6); +extern void ospf6_asbr_redistribute_reset(struct ospf6 *ospf6); +extern void ospf6_asbr_terminate(void); +extern void ospf6_asbr_send_externals_to_area(struct ospf6_area *); +extern void ospf6_asbr_remove_externals_from_area(struct ospf6_area *oa); + +extern int config_write_ospf6_debug_asbr(struct vty *vty); +extern int ospf6_distribute_config_write(struct vty *vty, struct ospf6 *ospf6); +extern void install_element_ospf6_debug_asbr(void); +extern void ospf6_asbr_update_route_ecmp_path(struct ospf6_route *old, + struct ospf6_route *route, + struct ospf6 *ospf6); +extern void ospf6_asbr_distribute_list_update(struct ospf6 *ospf6, + struct ospf6_redist *red); +struct ospf6_redist *ospf6_redist_lookup(struct ospf6 *ospf6, int type, + unsigned short instance); +extern void ospf6_asbr_routemap_update(const char *mapname); +extern struct ospf6_lsa * +ospf6_as_external_lsa_originate(struct ospf6_route *route, + struct ospf6 *ospf6); +extern void ospf6_asbr_status_update(struct ospf6 *ospf6, int status); + +int ospf6_asbr_external_rt_advertise(struct ospf6 *ospf6, + struct prefix *p); +int ospf6_external_aggr_delay_timer_set(struct ospf6 *ospf6, uint16_t interval); +int ospf6_asbr_external_rt_no_advertise(struct ospf6 *ospf6, + struct prefix *p); + +struct ospf6_external_aggr_rt * +ospf6_external_aggr_config_lookup(struct ospf6 *ospf6, struct prefix *p); + +int ospf6_external_aggr_config_set(struct ospf6 *ospf6, struct prefix *p, + route_tag_t tag, int metric, int mtype); + +int ospf6_external_aggr_config_unset(struct ospf6 *ospf6, + struct prefix *p); +void ospf6_handle_external_lsa_origination(struct ospf6 *ospf6, + struct ospf6_route *rt, + struct prefix *p); +void ospf6_external_aggregator_free(struct ospf6_external_aggr_rt *aggr); +void ospf6_unset_all_aggr_flag(struct ospf6 *ospf6); +void ospf6_fill_aggr_route_details(struct ospf6 *ospf6, + struct ospf6_external_aggr_rt *aggr); +void ospf6_asbr_summary_config_delete(struct ospf6 *ospf6, + struct route_node *rn); +#endif /* OSPF6_ASBR_H */ diff --git a/ospf6d/ospf6_auth_trailer.c b/ospf6d/ospf6_auth_trailer.c new file mode 100644 index 0000000..860d273 --- /dev/null +++ b/ospf6d/ospf6_auth_trailer.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Abhinay Ramesh + */ + +#include "zebra.h" +#include + +#ifdef CRYPTO_OPENSSL +#include +#include +#endif + +#include "config.h" +#include "memory.h" +#include "ospf6d.h" +#include "vty.h" +#include "command.h" +#include "md5.h" +#include "sha256.h" +#include "lib/zlog.h" +#include "ospf6_message.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_proto.h" +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_auth_trailer.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" +#include "lib/keychain.h" + +#define OSPF6D_COMPAT_AUTHSEQ_NAME "%s/ospf6d-at-seq-no.dat", frr_runstatedir + +unsigned char conf_debug_ospf6_auth[2]; +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_AUTH_HASH_XOR, "OSPF6 auth hash xor"); + +static void ospf6_auth_seqno_nvm_update(struct ospf6 *ospf6); + +/*Apad is the hexadecimal value 0x878FE1F3. */ +const uint8_t ospf6_hash_apad_max[KEYCHAIN_MAX_HASH_SIZE] = { + 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, + 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, + 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, + 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, + 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, + 0xf3, 0x87, 0x8f, 0xe1, 0xf3, 0x87, 0x8f, 0xe1, 0xf3, +}; + +const uint8_t ospf6_hash_ipad_max[KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE] = { + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, +}; + +const uint8_t ospf6_hash_opad_max[KEYCHAIN_ALGO_MAX_INTERNAL_BLK_SIZE] = { + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, +}; + +void ospf6_auth_hdr_dump_send(struct ospf6_header *ospfh, uint16_t length) +{ + struct ospf6_auth_hdr *ospf6_at_hdr; + uint16_t at_len, oh_len, at_hdr_len, hash_len; + unsigned char temp[KEYCHAIN_MAX_HASH_SIZE + 1]; + + oh_len = htons(ospfh->length); + at_len = length - oh_len; + if (at_len > 0) { + ospf6_at_hdr = (struct ospf6_auth_hdr *) + ((uint8_t *)ospfh + oh_len); + at_hdr_len = htons(ospf6_at_hdr->length); + hash_len = at_hdr_len - OSPF6_AUTH_HDR_MIN_SIZE; + memcpy(temp, ospf6_at_hdr->data, hash_len); + temp[hash_len] = '\0'; + zlog_debug("OSPF6 Authentication Trailer"); + zlog_debug(" Type %d", htons(ospf6_at_hdr->type)); + zlog_debug(" Length %d", at_hdr_len); + zlog_debug(" Reserved %d", ospf6_at_hdr->reserved); + zlog_debug(" SA ID %d", htons(ospf6_at_hdr->id)); + zlog_debug(" seqnum high 0x%08x", + htonl(ospf6_at_hdr->seqnum_h)); + zlog_debug(" seqnum high 0x%08x", + htonl(ospf6_at_hdr->seqnum_l)); + zlog_debug(" Data %s", temp); + } +} + +void ospf6_auth_hdr_dump_recv(struct ospf6_header *ospfh, uint16_t length, + unsigned int lls_len) +{ + struct ospf6_auth_hdr *ospf6_at_hdr; + uint16_t at_len, oh_len, at_hdr_len, hash_len; + unsigned char temp[KEYCHAIN_MAX_HASH_SIZE + 1]; + + oh_len = ntohs(ospfh->length); + at_len = length - (oh_len + lls_len); + if (at_len > 0) { + ospf6_at_hdr = + (struct ospf6_auth_hdr *)((uint8_t *)ospfh + oh_len); + at_hdr_len = ntohs(ospf6_at_hdr->length); + hash_len = at_hdr_len - (uint16_t)OSPF6_AUTH_HDR_MIN_SIZE; + if (hash_len > KEYCHAIN_MAX_HASH_SIZE) { + zlog_debug( + "Specified value for hash_len %u is greater than expected %u", + hash_len, KEYCHAIN_MAX_HASH_SIZE); + return; + } + memcpy(temp, ospf6_at_hdr->data, hash_len); + temp[hash_len] = '\0'; + zlog_debug("OSPF6 Authentication Trailer"); + zlog_debug(" Type %d", ntohs(ospf6_at_hdr->type)); + zlog_debug(" Length %d", at_hdr_len); + zlog_debug(" Reserved %d", ospf6_at_hdr->reserved); + zlog_debug(" SA ID %d", ntohs(ospf6_at_hdr->id)); + zlog_debug(" seqnum high 0x%08x", + ntohl(ospf6_at_hdr->seqnum_h)); + zlog_debug(" seqnum high 0x%08x", + ntohl(ospf6_at_hdr->seqnum_l)); + zlog_debug(" Data %s", temp); + } +} + +unsigned char *ospf6_hash_message_xor(unsigned char *mes1, + unsigned char *mes2, + uint32_t len) +{ + unsigned char *result; + uint32_t i; + + result = XCALLOC(MTYPE_OSPF6_AUTH_HASH_XOR, len); + + for (i = 0; i < len; i++) + result[i] = mes1[i] ^ mes2[i]; + + return result; +} + +static void md5_digest(unsigned char *mes, uint32_t len, + unsigned char *digest) +{ +#ifdef CRYPTO_OPENSSL + unsigned int size = KEYCHAIN_MD5_HASH_SIZE; + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + +#ifdef CRYPTO_OPENSSL + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, mes, len); + EVP_DigestFinal(ctx, digest, &size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, mes, len); + MD5Final(digest, &ctx); +#endif +} + +static void sha256_digest(unsigned char *mes, uint32_t len, + unsigned char *digest) +{ +#ifdef CRYPTO_OPENSSL + unsigned int size = KEYCHAIN_HMAC_SHA256_HASH_SIZE; + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + SHA256_CTX ctx; +#endif + +#ifdef CRYPTO_OPENSSL + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_sha256()); + EVP_DigestUpdate(ctx, mes, len); + EVP_DigestFinal(ctx, digest, &size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + SHA256_Init(&ctx); + SHA256_Update(&ctx, mes, len); + SHA256_Final(digest, &ctx); +#endif +} + +#ifdef CRYPTO_OPENSSL +static void sha1_digest(unsigned char *mes, uint32_t len, + unsigned char *digest) +{ + EVP_MD_CTX *ctx; + unsigned int size = KEYCHAIN_HMAC_SHA1_HASH_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_sha1()); + EVP_DigestUpdate(ctx, mes, len); + EVP_DigestFinal(ctx, digest, &size); + EVP_MD_CTX_free(ctx); +} + +static void sha384_digest(unsigned char *mes, uint32_t len, + unsigned char *digest) +{ + EVP_MD_CTX *ctx; + unsigned int size = KEYCHAIN_HMAC_SHA384_HASH_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_sha384()); + EVP_DigestUpdate(ctx, mes, len); + EVP_DigestFinal(ctx, digest, &size); + EVP_MD_CTX_free(ctx); +} + +static void sha512_digest(unsigned char *mes, uint32_t len, + unsigned char *digest) +{ + EVP_MD_CTX *ctx; + unsigned int size = KEYCHAIN_HMAC_SHA512_HASH_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_sha512()); + EVP_DigestUpdate(ctx, mes, len); + EVP_DigestFinal(ctx, digest, &size); + EVP_MD_CTX_free(ctx); +} +#endif /* CRYPTO_OPENSSL */ + +static void ospf6_hash_hmac_sha_digest(enum keychain_hash_algo key, + unsigned char *mes, uint32_t len, + unsigned char *digest) +{ + if ((key < KEYCHAIN_ALGO_NULL) || (key > KEYCHAIN_ALGO_MAX)) + return; + + switch (key) { + case KEYCHAIN_ALGO_MD5: + md5_digest(mes, len, digest); + break; + case KEYCHAIN_ALGO_HMAC_SHA1: +#ifdef CRYPTO_OPENSSL + sha1_digest(mes, len, digest); +#endif + break; + case KEYCHAIN_ALGO_HMAC_SHA256: + sha256_digest(mes, len, digest); + break; + case KEYCHAIN_ALGO_HMAC_SHA384: +#ifdef CRYPTO_OPENSSL + sha384_digest(mes, len, digest); +#endif + break; + case KEYCHAIN_ALGO_HMAC_SHA512: +#ifdef CRYPTO_OPENSSL + sha512_digest(mes, len, digest); +#endif + break; + case KEYCHAIN_ALGO_NULL: + case KEYCHAIN_ALGO_MAX: + default: + /* no action */ + break; + } +} + +uint16_t ospf6_auth_len_get(struct ospf6_interface *oi) +{ + uint16_t at_len = 0; + char *keychain_name = NULL; + struct keychain *keychain = NULL; + struct key *key = NULL; + + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) { + if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) { + at_len = OSPF6_AUTH_HDR_MIN_SIZE + + keychain_get_hash_len(oi->at_data.hash_algo); + } else { + keychain_name = oi->at_data.keychain; + keychain = keychain_lookup(keychain_name); + if (keychain) { + key = key_lookup_for_send(keychain); + if (key && key->string + && key->hash_algo != KEYCHAIN_ALGO_NULL) { + at_len = OSPF6_AUTH_HDR_MIN_SIZE + + keychain_get_hash_len( + key->hash_algo); + } + } + } + } else if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_MANUAL_KEY)) { + at_len = OSPF6_AUTH_HDR_MIN_SIZE + + keychain_get_hash_len(oi->at_data.hash_algo); + } + + return at_len; +} + +int ospf6_auth_validate_pkt(struct ospf6_interface *oi, unsigned int *pkt_len, + struct ospf6_header *oh, unsigned int *at_len, + unsigned int *lls_block_len) +{ + struct ospf6_hello *hello = NULL; + struct ospf6_dbdesc *dbdesc = NULL; + struct ospf6_neighbor *on = NULL; + struct ospf6_auth_hdr ospf6_auth_info; + uint16_t hdr_len = 0; + uint32_t oh_seqnum_h = 0; + uint32_t oh_seqnum_l = 0; + bool auth_present = false; + bool lls_present = false; + struct ospf6_lls_hdr *lls_hdr = NULL; + + on = ospf6_neighbor_lookup(oh->router_id, oi); + hdr_len = ntohs(oh->length); + if (*pkt_len < hdr_len) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] Received incomplete %s packet", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } else if (*pkt_len == hdr_len) { + if (oi->at_data.flags != 0) + return OSPF6_AUTH_VALIDATE_FAILURE; + /* No auth info to be considered. + */ + return OSPF6_AUTH_PROCESS_NORMAL; + } + + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + hello = (struct ospf6_hello *)((uint8_t *)oh + + sizeof(struct ospf6_header)); + if (OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_L)) + lls_present = true; + + if (OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_AT)) + auth_present = true; + break; + case OSPF6_MESSAGE_TYPE_DBDESC: + dbdesc = (struct ospf6_dbdesc *)((uint8_t *)oh + + sizeof(struct ospf6_header)); + if (OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_L)) + lls_present = true; + + if (OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_AT)) + auth_present = true; + break; + case OSPF6_MESSAGE_TYPE_LSREQ: + case OSPF6_MESSAGE_TYPE_LSUPDATE: + case OSPF6_MESSAGE_TYPE_LSACK: + if (on) { + lls_present = on->lls_present; + auth_present = on->auth_present; + } + break; + default: + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] : Wrong packet type %d", + oi->interface->name, oh->type); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + if ((oh->type == OSPF6_MESSAGE_TYPE_HELLO) + || (oh->type == OSPF6_MESSAGE_TYPE_DBDESC)) { + if (on) { + on->auth_present = auth_present; + on->lls_present = lls_present; + } + } + + if ((!auth_present && (oi->at_data.flags != 0)) + || (auth_present && (oi->at_data.flags == 0))) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] : Auth option miss-match in %s pkt", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + if (lls_present) { + lls_hdr = (struct ospf6_lls_hdr *)(oh + hdr_len); + *lls_block_len = ntohs(lls_hdr->length) * 4; + } + + if (*lls_block_len > (*pkt_len - hdr_len)) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] : Wrong lls data in %s packet", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + memset(&ospf6_auth_info, 0, sizeof(ospf6_auth_info)); + if ((*pkt_len - hdr_len - (*lls_block_len)) > sizeof(ospf6_auth_info)) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] : Wrong auth data in %s packet", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + memcpy(&ospf6_auth_info, ((uint8_t *)oh + hdr_len + (*lls_block_len)), + (*pkt_len - hdr_len - (*lls_block_len))); + if (ntohs(ospf6_auth_info.length) > OSPF6_AUTH_HDR_FULL) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s] : Wrong auth header length in %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + /* after authentication header validation is done + * reduce the auth hdr size from the packet length + */ + *at_len = ntohs(ospf6_auth_info.length); + *pkt_len = (*pkt_len) - (*at_len) - (*lls_block_len); + + if (on) { + oh_seqnum_h = ntohl(ospf6_auth_info.seqnum_h); + oh_seqnum_l = ntohl(ospf6_auth_info.seqnum_l); + if ((oh_seqnum_h >= on->seqnum_h[oh->type]) + && (oh_seqnum_l > on->seqnum_l[oh->type])) { + /* valid sequence number received */ + on->seqnum_h[oh->type] = oh_seqnum_h; + on->seqnum_l[oh->type] = oh_seqnum_l; + } else { + if (IS_OSPF6_DEBUG_AUTH_RX) { + zlog_err( + "RECV[%s] : Nbr(%s) Auth Sequence number mismatch in %s ", + oi->interface->name, on->name, + ospf6_message_type(oh->type)); + zlog_err( + "nbr_seq_l %u, nbr_seq_h %u, hdr_seq_l %u, hdr_seq_h %u", + on->seqnum_l[oh->type], + on->seqnum_h[oh->type], oh_seqnum_l, + oh_seqnum_h); + } + + return OSPF6_AUTH_VALIDATE_FAILURE; + } + } + + return OSPF6_AUTH_VALIDATE_SUCCESS; +} + +/* Starting point of packet process function. */ +int ospf6_auth_check_digest(struct ospf6_header *oh, struct ospf6_interface *oi, + struct in6_addr *src, unsigned int lls_block_len) +{ + uint32_t hash_len = KEYCHAIN_MAX_HASH_SIZE; + unsigned char apad[hash_len]; + unsigned char temp_hash[hash_len]; + struct ospf6_auth_hdr *ospf6_auth; + uint32_t ipv6_addr_size = sizeof(struct in6_addr); + struct keychain *keychain = NULL; + struct key *key = NULL; + char *auth_str = NULL; + uint16_t auth_len = 0; + uint8_t hash_algo = 0; + uint16_t oh_len = ntohs(oh->length); + int ret = 0; + + if (oi->at_data.flags == 0) + return OSPF6_AUTH_PROCESS_NORMAL; + + ospf6_auth = (struct ospf6_auth_hdr *)((uint8_t *)oh + + (oh_len + lls_block_len)); + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) { + keychain = keychain_lookup(oi->at_data.keychain); + if (!keychain) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err( + "RECV[%s]: Keychain doesn't exist for %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + key = key_lookup_for_accept(keychain, ntohs(ospf6_auth->id)); + if (!key) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s]: Auth, Invalid SA for %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + + if (key && key->string + && key->hash_algo != KEYCHAIN_ALGO_NULL) { + auth_str = key->string; + hash_algo = key->hash_algo; + } else { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err( + "RECV[%s]: Incomplete keychain config for %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + } else if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_MANUAL_KEY)) { + if (oi->at_data.key_id != ntohs(ospf6_auth->id)) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err("RECV[%s]: Auth SA ID mismatch for %s, received %u vs configured %u", + oi->interface->name, + ospf6_message_type(oh->type), + ntohs(ospf6_auth->id), + oi->at_data.key_id); + return OSPF6_AUTH_VALIDATE_FAILURE; + } + auth_str = oi->at_data.auth_key; + hash_algo = oi->at_data.hash_algo; + } + + if (!auth_str) + return OSPF6_AUTH_VALIDATE_FAILURE; + + hash_len = keychain_get_hash_len(hash_algo); + memset(apad, 0, sizeof(apad)); + memset(temp_hash, 0, sizeof(temp_hash)); + + /* start digest verification */ + memcpy(apad, src, ipv6_addr_size); + memcpy(apad + ipv6_addr_size, ospf6_hash_apad_max, + (hash_len - ipv6_addr_size)); + + auth_len = ntohs(ospf6_auth->length); + + memcpy(temp_hash, ospf6_auth->data, hash_len); + memcpy(ospf6_auth->data, apad, hash_len); + + ospf6_auth_update_digest(oi, oh, ospf6_auth, auth_str, + (oh_len + auth_len + lls_block_len), + hash_algo); + +#ifdef CRYPTO_OPENSSL + ret = CRYPTO_memcmp(temp_hash, ospf6_auth->data, hash_len); +#else + ret = memcmp(temp_hash, ospf6_auth->data, hash_len); +#endif + if (ret == 0) + return OSPF6_AUTH_VALIDATE_SUCCESS; + + return OSPF6_AUTH_VALIDATE_FAILURE; +} + +void ospf6_auth_digest_send(struct in6_addr *src, struct ospf6_interface *oi, + struct ospf6_header *oh, uint16_t auth_len, + uint32_t pkt_len) +{ + struct ospf6_auth_hdr *ospf6_auth; + char *keychain_name = NULL; + struct keychain *keychain = NULL; + struct key *key = NULL; + char *auth_str = NULL; + uint16_t key_id = 0; + enum keychain_hash_algo hash_algo = KEYCHAIN_ALGO_NULL; + uint32_t hash_len = KEYCHAIN_MAX_HASH_SIZE; + unsigned char apad[hash_len]; + int ipv6_addr_size = sizeof(struct in6_addr); + struct ospf6 *ospf6 = NULL; + + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) { + if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) { + auth_str = oi->at_data.auth_key; + hash_algo = oi->at_data.hash_algo; + key_id = oi->at_data.key_id; + } else { + keychain_name = oi->at_data.keychain; + keychain = keychain_lookup(keychain_name); + if (keychain) { + key = key_lookup_for_send(keychain); + if (key && key->string + && key->hash_algo != KEYCHAIN_ALGO_NULL) { + auth_str = key->string; + hash_algo = key->hash_algo; + key_id = key->index; + } + } + } + } else if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_MANUAL_KEY)) { + auth_str = oi->at_data.auth_key; + hash_algo = oi->at_data.hash_algo; + key_id = oi->at_data.key_id; + } else { + if (IS_OSPF6_DEBUG_AUTH_TX) + zlog_warn("SEND[%s]: Authentication not configured for %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return; + } + + if (!auth_str) { + if (IS_OSPF6_DEBUG_AUTH_TX) + zlog_warn("SEND[%s]: Authentication key is not configured for %s", + oi->interface->name, + ospf6_message_type(oh->type)); + return; + } + + hash_len = keychain_get_hash_len(hash_algo); + if (oi->area && oi->area->ospf6) + ospf6 = oi->area->ospf6; + else + return; + + if (ospf6->seqnum_l == 0xFFFFFFFF) { + if (ospf6->seqnum_h == 0xFFFFFFFF) { + /* Key must be reset, which is not handled as of now. */ + zlog_err("Sequence number wrapped; key must be reset."); + ospf6->seqnum_h = 0; + } else { + ospf6->seqnum_h++; + } + ospf6_auth_seqno_nvm_update(ospf6); + + ospf6->seqnum_l = 0; + } else { + ospf6->seqnum_l++; + } + + memset(apad, 0, sizeof(apad)); + + if (src) + memcpy(apad, src, ipv6_addr_size); + + memcpy(apad + ipv6_addr_size, ospf6_hash_apad_max, + (hash_len - ipv6_addr_size)); + + ospf6_auth = + (struct ospf6_auth_hdr *)((uint8_t *)oh + ntohs(oh->length)); + ospf6_auth->type = htons(OSPF6_AUTHENTICATION_CRYPTOGRAPHIC); + ospf6_auth->length = htons(auth_len); + ospf6_auth->reserved = 0; + ospf6_auth->id = htons(key_id); + ospf6_auth->seqnum_h = htonl(ospf6->seqnum_h); + ospf6_auth->seqnum_l = htonl(ospf6->seqnum_l); + memcpy(ospf6_auth->data, apad, hash_len); + + ospf6_auth_update_digest(oi, oh, ospf6_auth, auth_str, pkt_len, + hash_algo); + + /* There is a optimisation that is done to ensure that + * for every packet flow keychain lib API are called + * only once and the result are stored in oi->at_data. + * So, After processing the flow it is reset back here. + */ + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN_VALID)) { + oi->at_data.hash_algo = KEYCHAIN_ALGO_NULL; + if (oi->at_data.auth_key) { + XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY, + oi->at_data.auth_key); + oi->at_data.auth_key = NULL; + } + + oi->at_data.key_id = 0; + UNSET_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN_VALID); + } +} + +void ospf6_auth_update_digest(struct ospf6_interface *oi, + struct ospf6_header *oh, + struct ospf6_auth_hdr *ospf6_auth, char *auth_str, + uint32_t pkt_len, enum keychain_hash_algo algo) +{ + const uint16_t cpid = htons(OSPFV3_CRYPTO_PROTO_ID); + uint32_t hash_len = keychain_get_hash_len(algo); + uint32_t block_s = keychain_get_block_size(algo); + uint32_t k_len = strlen(auth_str); + uint32_t ks_len = strlen(auth_str) + sizeof(cpid); + unsigned char ipad[block_s]; + unsigned char opad[block_s]; + unsigned char ko[block_s], ks[ks_len], tmp[hash_len]; + unsigned char *first = NULL; + unsigned char *second = NULL; + unsigned char first_mes[block_s + pkt_len]; + unsigned char second_mes[block_s + pkt_len]; + unsigned char first_hash[hash_len]; + unsigned char second_hash[hash_len]; + + memset(ko, 0, sizeof(ko)); + memcpy(ks, auth_str, k_len); + memcpy(ks + k_len, &cpid, sizeof(cpid)); + if (ks_len > hash_len) { + ospf6_hash_hmac_sha_digest(algo, ks, ks_len, tmp); + memcpy(ko, tmp, hash_len); + } else + memcpy(ko, ks, ks_len); + + memcpy(ipad, ospf6_hash_ipad_max, block_s); + memcpy(opad, ospf6_hash_opad_max, block_s); + + first = ospf6_hash_message_xor((unsigned char *)&ipad, ko, block_s); + second = ospf6_hash_message_xor((unsigned char *)&opad, ko, block_s); + + memcpy(first_mes, first, block_s); + memcpy(first_mes + block_s, oh, pkt_len); + + ospf6_hash_hmac_sha_digest(algo, first_mes, (block_s + pkt_len), + first_hash); + + memcpy(second_mes, second, block_s); + memcpy(second_mes + block_s, first_hash, hash_len); + + ospf6_hash_hmac_sha_digest(algo, second_mes, (block_s + hash_len), + second_hash); + + memcpy(ospf6_auth->data, second_hash, hash_len); + XFREE(MTYPE_OSPF6_AUTH_HASH_XOR, first); + XFREE(MTYPE_OSPF6_AUTH_HASH_XOR, second); +} + +DEFUN (debug_ospf6_auth, + debug_ospf6_auth_cmd, + "debug ospf6 authentication []", + DEBUG_STR + OSPF6_STR + "debug OSPF6 authentication\n" + "debug authentication tx\n" + "debug authentication rx\n") +{ + int auth_opt_idx = 3; + + if (argc == 4) { + if (!strncmp(argv[auth_opt_idx]->arg, "t", 1)) + OSPF6_DEBUG_AUTH_TX_ON(); + else if (!strncmp(argv[auth_opt_idx]->arg, "r", 1)) + OSPF6_DEBUG_AUTH_RX_ON(); + } else { + OSPF6_DEBUG_AUTH_TX_ON(); + OSPF6_DEBUG_AUTH_RX_ON(); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_auth, + no_debug_ospf6_auth_cmd, + "no debug ospf6 authentication []", + NO_STR + DEBUG_STR + OSPF6_STR + "debug OSPF6 authentication\n" + "debug authentication tx\n" + "debug authentication rx\n") +{ + int auth_opt_idx = 3; + + if (argc == 5) { + if (!strncmp(argv[auth_opt_idx]->arg, "t", 1)) + OSPF6_DEBUG_AUTH_TX_OFF(); + else if (!strncmp(argv[auth_opt_idx]->arg, "r", 1)) + OSPF6_DEBUG_AUTH_RX_OFF(); + } else { + OSPF6_DEBUG_AUTH_TX_OFF(); + OSPF6_DEBUG_AUTH_RX_OFF(); + } + + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_auth(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_AUTH_TX) + vty_out(vty, "debug ospf6 authentication tx\n"); + if (IS_OSPF6_DEBUG_AUTH_RX) + vty_out(vty, "debug ospf6 authentication rx\n"); + return 0; +} + +void install_element_ospf6_debug_auth(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_auth_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_auth_cmd); + install_element(CONFIG_NODE, &debug_ospf6_auth_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_auth_cmd); +} + +/* Clear the specified interface structure */ +static void ospf6_intf_auth_clear(struct vty *vty, struct interface *ifp) +{ + struct ospf6_interface *oi; + + if (!if_is_operative(ifp)) + return; + + if (ifp->info == NULL) + return; + + oi = (struct ospf6_interface *)ifp->info; + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug( + "Interface %s: clear authentication rx/tx drop counters", + ifp->name); + + /* Reset the interface rx/tx drop counters */ + oi->at_data.tx_drop = 0; + oi->at_data.rx_drop = 0; +} + +/* Clear interface */ +DEFUN(clear_ipv6_ospf6_intf_auth, clear_ipv6_ospf6_intf_auth_cmd, + "clear ipv6 ospf6 [vrf VRF] auth-counters interface [IFNAME]", + CLEAR_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "authentication rx/tx drop counters\n" INTERFACE_STR IFNAME_STR) +{ + int idx_ifname = 0; + int idx_vrf = 0; + struct interface *ifp; + struct listnode *node; + struct ospf6 *ospf6 = NULL; + char *vrf_name = NULL; + vrf_id_t vrf_id = VRF_DEFAULT; + struct vrf *vrf = NULL; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + + if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME)) + vrf_name = NULL; + + if (vrf_name) { + vrf = vrf_lookup_by_name(vrf_name); + if (vrf) + vrf_id = vrf->vrf_id; + } + + if (!argv_find(argv, argc, "IFNAME", &idx_ifname)) { + /* Clear all the ospfv3 interfaces auth data. */ + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (vrf_id != ospf6->vrf_id) + continue; + + if (!vrf) + vrf = vrf_lookup_by_id(ospf6->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) + ospf6_intf_auth_clear(vty, ifp); + } + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); + if (ifp == NULL) + vty_out(vty, "No such interface name\n"); + else + ospf6_intf_auth_clear(vty, ifp); + } + + return CMD_SUCCESS; +} + +void install_element_ospf6_clear_intf_auth(void) +{ + install_element(ENABLE_NODE, &clear_ipv6_ospf6_intf_auth_cmd); +} + +/* + * Record in non-volatile memory the given ospf6 process, + * authentication trailer higher order sequence number. + */ +static void ospf6_auth_seqno_nvm_update(struct ospf6 *ospf6) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + + zlog_err("Higher order sequence number %d update for %s process", + ospf6->seqnum_h, ospf6->name); + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + /* + * Record higher order sequence number in non volatile memory. + */ + json_object_int_add(json_instance, "sequence_number", ospf6->seqnum_h); + + frr_daemon_state_save(&json); +} + +/* + * Delete authentication sequence number for a given OSPF6 process + * from non-volatile memory. + */ +__attribute__((unused)) static void +ospf6_auth_seqno_nvm_delete(struct ospf6 *ospf6) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + + zlog_err("Higher order sequence number delete for %s process", + ospf6->name); + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_object_put(json); + return; + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (json_instance) { + json_object_put(json); + return; + } + + json_object_object_del(json_instance, "sequence_number"); + + frr_daemon_state_save(&json); +} + + +static struct json_object *ospf6_auth_seqno_compat_read(const char *inst_name) +{ + /* try legacy location */ + char compat_path[512]; + json_object *json; + json_object *json_instances = NULL; + json_object *json_instance = NULL; + json_object *json_seqnum = NULL; + + snprintf(compat_path, sizeof(compat_path), OSPF6D_COMPAT_AUTHSEQ_NAME); + json = json_object_from_file(compat_path); + + if (json) + json_object_object_get_ex(json, "instances", &json_instances); + if (json_instances) + json_object_object_get_ex(json_instances, inst_name, + &json_instance); + if (json_instance) + json_object_object_get_ex(json_instance, "sequence_number", + &json_seqnum); + if (json_seqnum) + /* => free the file-level object and still return this */ + json_seqnum = json_object_get(json_seqnum); + + if (json) { + json_object_free(json); + unlink(compat_path); + } + return json_seqnum; +} + +/* + * Fetch from non-volatile memory the stored ospf6 process + * authentication sequence number. + */ +static void ospf6_auth_seqno_nvm_read(struct ospf6 *ospf6) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + json_object *json_seqnum; + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_object_get_ex(json_instance, "sequence_number", + &json_seqnum); + + if (json_seqnum) + /* cf. reference taken in compat_read above */ + json_seqnum = json_object_get(json_seqnum); + else + json_seqnum = ospf6_auth_seqno_compat_read(inst_name); + + ospf6->seqnum_l = 0; + if (json_seqnum) { + ospf6->seqnum_h = json_object_get_int(json_seqnum); + ospf6->seqnum_h += 1; + } else { + ospf6->seqnum_h = 0; + } + + if (json_seqnum) + json_object_put(json_seqnum); + + zlog_err("Higher order sequence number %d read for %s process %s", + ospf6->seqnum_h, ospf6->name, strerror(errno)); + + json_object_object_del(json_instance, "sequence_number"); + + frr_daemon_state_save(&json); +} + +void ospf6_auth_init(struct ospf6 *o) +{ + ospf6_auth_seqno_nvm_read(o); + ospf6_auth_seqno_nvm_update(o); +} diff --git a/ospf6d/ospf6_auth_trailer.h b/ospf6d/ospf6_auth_trailer.h new file mode 100644 index 0000000..9073ae4 --- /dev/null +++ b/ospf6d/ospf6_auth_trailer.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Abhinay Ramesh + */ + +#ifndef __OSPF6_AUTH_TRAILER_H__ +#define __OSPF6_AUTH_TRAILER_H__ + +#include "lib/keychain.h" +#include "ospf6_message.h" + +#define OSPF6_AUTH_HDR_MIN_SIZE 16 +#define OSPF6_AUTH_HDR_FULL KEYCHAIN_MAX_HASH_SIZE + OSPF6_AUTH_HDR_MIN_SIZE + +#define OSPF6_AUTHENTICATION_NULL 0 +#define OSPF6_AUTHENTICATION_CRYPTOGRAPHIC 1 + +#define OSPFV3_CRYPTO_PROTO_ID 1 + +/* Auth debug options */ +extern unsigned char conf_debug_ospf6_auth[2]; + +#define OSPF6_AUTH_TX 0 +#define OSPF6_AUTH_RX 1 +#define OSPF6_DEBUG_AUTH_TX_ON() (conf_debug_ospf6_auth[OSPF6_AUTH_TX] = 1) +#define OSPF6_DEBUG_AUTH_TX_OFF() (conf_debug_ospf6_auth[OSPF6_AUTH_TX] = 0) +#define OSPF6_DEBUG_AUTH_RX_ON() (conf_debug_ospf6_auth[OSPF6_AUTH_RX] = 1) +#define OSPF6_DEBUG_AUTH_RX_OFF() (conf_debug_ospf6_auth[OSPF6_AUTH_RX] = 0) +#define IS_OSPF6_DEBUG_AUTH_TX (conf_debug_ospf6_auth[OSPF6_AUTH_TX]) +#define IS_OSPF6_DEBUG_AUTH_RX (conf_debug_ospf6_auth[OSPF6_AUTH_RX]) + +#define OSPF6_AUTH_TRAILER_KEYCHAIN (1 << 0) +#define OSPF6_AUTH_TRAILER_MANUAL_KEY (1 << 1) +#define OSPF6_AUTH_TRAILER_KEYCHAIN_VALID (1 << 2) + +/* According to sesion 4.1 of RFC7166 defining the trailer struct */ +struct ospf6_auth_hdr { + uint16_t type; + uint16_t length; + uint16_t reserved; + uint16_t id; + uint32_t seqnum_h; + uint32_t seqnum_l; + unsigned char data[KEYCHAIN_MAX_HASH_SIZE]; +}; + +enum ospf6_auth_err { + OSPF6_AUTH_VALIDATE_SUCCESS = 0, + OSPF6_AUTH_VALIDATE_FAILURE, + OSPF6_AUTH_PROCESS_NORMAL, +}; + +void ospf6_auth_init(struct ospf6 *o); + +void ospf6_auth_hdr_dump_send(struct ospf6_header *ospfh, uint16_t length); +void ospf6_auth_hdr_dump_recv(struct ospf6_header *ospfh, uint16_t length, + unsigned int lls_len); +unsigned char *ospf6_hash_message_xor(unsigned char *mes1, unsigned char *mes2, + uint32_t len); +uint16_t ospf6_auth_len_get(struct ospf6_interface *oi); +int ospf6_auth_validate_pkt(struct ospf6_interface *oi, unsigned int *pkt_len, + struct ospf6_header *oh, unsigned int *at_len, + unsigned int *lls_block_len); +int ospf6_auth_check_digest(struct ospf6_header *oh, struct ospf6_interface *oi, + struct in6_addr *src, unsigned int lls_len); +void ospf6_auth_update_digest(struct ospf6_interface *oi, + struct ospf6_header *oh, + struct ospf6_auth_hdr *ospf6_auth, char *auth_str, + uint32_t pkt_len, enum keychain_hash_algo algo); +void ospf6_auth_digest_send(struct in6_addr *src, struct ospf6_interface *oi, + struct ospf6_header *oh, uint16_t auth_len, + uint32_t pkt_len); +void install_element_ospf6_debug_auth(void); +int config_write_ospf6_debug_auth(struct vty *vty); +void install_element_ospf6_clear_intf_auth(void); + +#endif /* __OSPF6_AUTH_TRAILER_H__ */ diff --git a/ospf6d/ospf6_bfd.c b/ospf6d/ospf6_bfd.c new file mode 100644 index 0000000..6379f9d --- /dev/null +++ b/ospf6d/ospf6_bfd.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * ospf6_bfd.c: IPv6 OSPF BFD handling routines + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "linklist.h" +#include "memory.h" +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "zclient.h" +#include "vty.h" +#include "table.h" +#include "bfd.h" +#include "if.h" +#include "ospf6d.h" +#include "ospf6_message.h" +#include "ospf6_neighbor.h" +#include "ospf6_interface.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" +#include "ospf6_bfd.h" + +extern struct zclient *zclient; + +/* + * ospf6_bfd_trigger_event - Neighbor is registered/deregistered with BFD when + * neighbor state is changed to/from 2way. + */ +void ospf6_bfd_trigger_event(struct ospf6_neighbor *on, int old_state, + int state) +{ + int family; + struct in6_addr src, dst; + + /* Skip sessions without BFD. */ + if (on->bfd_session == NULL) + return; + + if (old_state < OSPF6_NEIGHBOR_TWOWAY + && state >= OSPF6_NEIGHBOR_TWOWAY) { + /* + * Check if neighbor address changed. + * + * When the neighbor is configured BFD before having an existing + * connection, then the destination address will be set to `::` + * which will cause session installation failure. This piece of + * code updates the address in that case. + */ + bfd_sess_addresses(on->bfd_session, &family, &src, &dst); + if (memcmp(&on->linklocal_addr, &dst, sizeof(dst))) { + bfd_sess_set_ipv6_addrs(on->bfd_session, &src, + &on->linklocal_addr); + } + + bfd_sess_install(on->bfd_session); + } else if (old_state >= OSPF6_NEIGHBOR_TWOWAY + && state < OSPF6_NEIGHBOR_TWOWAY) + bfd_sess_uninstall(on->bfd_session); +} + +/* + * ospf6_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated + * with a interface with BFD through + * zebra for starting/stopping the monitoring of + * the neighbor rechahability. + */ +static void ospf6_bfd_reg_dereg_all_nbr(struct ospf6_interface *oi, + bool install) +{ + struct ospf6_neighbor *on; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { + /* Remove all sessions. */ + if (!install) { + bfd_sess_free(&on->bfd_session); + continue; + } + + /* Always allocate session data even if not enabled. */ + ospf6_bfd_info_nbr_create(oi, on); + + /* + * If not connected yet, don't create any session but defer it + * for later. See function `ospf6_bfd_trigger_event`. + */ + if (on->state < OSPF6_NEIGHBOR_TWOWAY) + continue; + + bfd_sess_install(on->bfd_session); + } +} + +static void ospf6_bfd_callback(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, void *arg) +{ + struct ospf6_neighbor *on = arg; + + if (bss->state == BFD_STATUS_DOWN + && bss->previous_state == BFD_STATUS_UP) { + EVENT_OFF(on->inactivity_timer); + event_add_event(master, inactivity_timer, on, 0, NULL); + } +} + +/* + * ospf6_bfd_info_nbr_create - Create/update BFD information for a neighbor. + */ +void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, + struct ospf6_neighbor *on) +{ + if (!oi->bfd_config.enabled) + return; + + if (on->bfd_session == NULL) + on->bfd_session = bfd_sess_new(ospf6_bfd_callback, on); + + bfd_sess_set_timers(on->bfd_session, + oi->bfd_config.detection_multiplier, + oi->bfd_config.min_rx, oi->bfd_config.min_tx); + bfd_sess_set_ipv6_addrs(on->bfd_session, on->ospf6_if->linklocal_addr, + &on->linklocal_addr); + bfd_sess_set_interface(on->bfd_session, oi->interface->name); + bfd_sess_set_vrf(on->bfd_session, oi->interface->vrf->vrf_id); + bfd_sess_set_profile(on->bfd_session, oi->bfd_config.profile); +} + +/* + * ospf6_bfd_write_config - Write the interface BFD configuration. + */ +void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi) +{ + if (!oi->bfd_config.enabled) + return; + +#if HAVE_BFDD == 0 + if (oi->bfd_config.detection_multiplier != BFD_DEF_DETECT_MULT + || oi->bfd_config.min_rx != BFD_DEF_MIN_RX + || oi->bfd_config.min_tx != BFD_DEF_MIN_TX) + vty_out(vty, " ipv6 ospf6 bfd %d %d %d\n", + oi->bfd_config.detection_multiplier, + oi->bfd_config.min_rx, oi->bfd_config.min_tx); + else +#endif /* ! HAVE_BFDD */ + vty_out(vty, " ipv6 ospf6 bfd\n"); + + if (oi->bfd_config.profile) + vty_out(vty, " ipv6 ospf6 bfd profile %s\n", + oi->bfd_config.profile); +} + +DEFUN(ipv6_ospf6_bfd, ipv6_ospf6_bfd_cmd, + "ipv6 ospf6 bfd [profile BFDPROF]", + IP6_STR OSPF6_STR + "Enables BFD support\n" + "BFD Profile selection\n" + "BFD Profile name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + int prof_idx = 4; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->bfd_config.detection_multiplier = BFD_DEF_DETECT_MULT; + oi->bfd_config.min_rx = BFD_DEF_MIN_RX; + oi->bfd_config.min_tx = BFD_DEF_MIN_TX; + oi->bfd_config.enabled = true; + if (argc > prof_idx) { + XFREE(MTYPE_TMP, oi->bfd_config.profile); + oi->bfd_config.profile = + XSTRDUP(MTYPE_TMP, argv[prof_idx]->arg); + } + + ospf6_bfd_reg_dereg_all_nbr(oi, true); + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_ospf6_bfd_profile, no_ipv6_ospf6_bfd_profile_cmd, + "no ipv6 ospf6 bfd profile [BFDPROF]", + NO_STR IP6_STR OSPF6_STR + "BFD support\n" + "BFD Profile selection\n" + "BFD Profile name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + /* BFD not enabled, nothing to do. */ + if (!oi->bfd_config.enabled) + return CMD_SUCCESS; + + /* Remove profile and apply new configuration. */ + XFREE(MTYPE_TMP, oi->bfd_config.profile); + ospf6_bfd_reg_dereg_all_nbr(oi, true); + + return CMD_SUCCESS; +} + +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + ipv6_ospf6_bfd_param, + ipv6_ospf6_bfd_param_cmd, + "ipv6 ospf6 bfd (2-255) (50-60000) (50-60000)", + IP6_STR + OSPF6_STR + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + struct ospf6_interface *oi; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->bfd_config.detection_multiplier = + strtoul(argv[idx_number]->arg, NULL, 10); + oi->bfd_config.min_rx = strtoul(argv[idx_number_2]->arg, NULL, 10); + oi->bfd_config.min_tx = strtoul(argv[idx_number_3]->arg, NULL, 10); + oi->bfd_config.enabled = true; + + ospf6_bfd_reg_dereg_all_nbr(oi, true); + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_bfd, + no_ipv6_ospf6_bfd_cmd, + "no ipv6 ospf6 bfd", + NO_STR + IP6_STR + OSPF6_STR + "Disables BFD support\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->bfd_config.enabled = false; + ospf6_bfd_reg_dereg_all_nbr(oi, false); + + return CMD_SUCCESS; +} + +void ospf6_bfd_init(void) +{ + bfd_protocol_integration_init(zclient, master); + + /* Install BFD command */ + install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_param_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_bfd_profile_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_bfd_cmd); +} diff --git a/ospf6d/ospf6_bfd.h b/ospf6d/ospf6_bfd.h new file mode 100644 index 0000000..e532ccc --- /dev/null +++ b/ospf6d/ospf6_bfd.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * ospf6_bfd.h: OSPF6 BFD definitions and structures + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ +#include "lib/json.h" +#ifndef OSPF6_BFD_H +#define OSPF6_BFD_H +#include "lib/json.h" + +/** + * Initialize BFD integration. + */ +extern void ospf6_bfd_init(void); + +extern void ospf6_bfd_trigger_event(struct ospf6_neighbor *nbr, int old_state, + int state); + +extern void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi); + +extern void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, + struct ospf6_neighbor *on); + +#endif /* OSPF6_BFD_H */ diff --git a/ospf6d/ospf6_flood.c b/ospf6d/ospf6_flood.c new file mode 100644 index 0000000..b87aa2f --- /dev/null +++ b/ospf6d/ospf6_flood.c @@ -0,0 +1,1280 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "command.h" + +#include "ospf6d.h" +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_route.h" +#include "ospf6_spf.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" + +#include "ospf6_flood.h" +#include "ospf6_nssa.h" +#include "ospf6_gr.h" + +unsigned char conf_debug_ospf6_flooding; + +struct ospf6_lsdb *ospf6_get_scoped_lsdb(struct ospf6_lsa *lsa) +{ + struct ospf6_lsdb *lsdb = NULL; + switch (OSPF6_LSA_SCOPE(lsa->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb = OSPF6_INTERFACE(lsa->lsdb->data)->lsdb; + break; + case OSPF6_SCOPE_AREA: + lsdb = OSPF6_AREA(lsa->lsdb->data)->lsdb; + break; + case OSPF6_SCOPE_AS: + lsdb = OSPF6_PROCESS(lsa->lsdb->data)->lsdb; + break; + default: + assert(0); + break; + } + return lsdb; +} + +struct ospf6_lsdb *ospf6_get_scoped_lsdb_self(struct ospf6_lsa *lsa) +{ + struct ospf6_lsdb *lsdb_self = NULL; + switch (OSPF6_LSA_SCOPE(lsa->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb_self = OSPF6_INTERFACE(lsa->lsdb->data)->lsdb_self; + break; + case OSPF6_SCOPE_AREA: + lsdb_self = OSPF6_AREA(lsa->lsdb->data)->lsdb_self; + break; + case OSPF6_SCOPE_AS: + lsdb_self = OSPF6_PROCESS(lsa->lsdb->data)->lsdb_self; + break; + default: + assert(0); + break; + } + return lsdb_self; +} + +void ospf6_lsa_originate(struct ospf6 *ospf6, struct ospf6_lsa *lsa) +{ + struct ospf6_lsa *old; + struct ospf6_lsdb *lsdb_self; + + if (lsa->header->adv_router == INADDR_ANY) { + if (IS_OSPF6_DEBUG_ORIGINATE_TYPE(lsa->header->type)) + zlog_debug( + "Refusing to originate LSA (zero router ID): %s", + lsa->name); + + ospf6_lsa_delete(lsa); + return; + } + + /* find previous LSA */ + old = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsa->lsdb); + + /* if the new LSA does not differ from previous, + suppress this update of the LSA */ + if (old && !OSPF6_LSA_IS_DIFFER(lsa, old) + && !ospf6->gr_info.finishing_restart) { + if (IS_OSPF6_DEBUG_ORIGINATE_TYPE(lsa->header->type)) + zlog_debug("Suppress updating LSA: %s", lsa->name); + ospf6_lsa_delete(lsa); + return; + } + + /* store it in the LSDB for self-originated LSAs */ + lsdb_self = ospf6_get_scoped_lsdb_self(lsa); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), lsdb_self); + + EVENT_OFF(lsa->refresh); + event_add_timer(master, ospf6_lsa_refresh, lsa, OSPF_LS_REFRESH_TIME, + &lsa->refresh); + + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type) + || IS_OSPF6_DEBUG_ORIGINATE_TYPE(lsa->header->type)) { + zlog_debug("LSA Originate:"); + ospf6_lsa_header_print(lsa); + } + + ospf6_install_lsa(lsa); + ospf6_flood(NULL, lsa); +} + +void ospf6_lsa_originate_process(struct ospf6_lsa *lsa, struct ospf6 *process) +{ + lsa->lsdb = process->lsdb; + ospf6_lsa_originate(process, lsa); +} + +void ospf6_lsa_originate_area(struct ospf6_lsa *lsa, struct ospf6_area *oa) +{ + lsa->lsdb = oa->lsdb; + ospf6_lsa_originate(oa->ospf6, lsa); +} + +void ospf6_lsa_originate_interface(struct ospf6_lsa *lsa, + struct ospf6_interface *oi) +{ + lsa->lsdb = oi->lsdb; + ospf6_lsa_originate(oi->area->ospf6, lsa); +} + +void ospf6_external_lsa_purge(struct ospf6 *ospf6, struct ospf6_lsa *lsa) +{ + uint32_t id = lsa->header->id; + struct ospf6_area *oa; + struct listnode *lnode; + + ospf6_lsa_purge(lsa); + + /* Delete the corresponding NSSA LSA */ + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, lnode, oa)) { + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_TYPE_7), id, + ospf6->router_id, oa->lsdb); + if (lsa) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("withdraw type 7 lsa, LS ID: %u", + htonl(id)); + + ospf6_lsa_purge(lsa); + } + } +} + +void ospf6_lsa_purge(struct ospf6_lsa *lsa) +{ + struct ospf6_lsa *self; + struct ospf6_lsdb *lsdb_self; + + /* remove it from the LSDB for self-originated LSAs */ + lsdb_self = ospf6_get_scoped_lsdb_self(lsa); + self = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsdb_self); + if (self) { + EVENT_OFF(self->expire); + EVENT_OFF(self->refresh); + ospf6_lsdb_remove(self, lsdb_self); + } + + ospf6_lsa_premature_aging(lsa); +} + +/* Puring Multi Link-State IDs LSAs: + * Same Advertising Router with Multiple Link-State IDs + * LSAs, purging require to traverse all Link-State IDs + */ +void ospf6_lsa_purge_multi_ls_id(struct ospf6_area *oa, struct ospf6_lsa *lsa) +{ + int ls_id = 0; + struct ospf6_lsa *lsa_next; + uint16_t type; + + type = lsa->header->type; + + ospf6_lsa_purge(lsa); + + lsa_next = ospf6_lsdb_lookup(type, htonl(++ls_id), + oa->ospf6->router_id, oa->lsdb); + while (lsa_next) { + ospf6_lsa_purge(lsa_next); + lsa_next = ospf6_lsdb_lookup(type, htonl(++ls_id), + oa->ospf6->router_id, oa->lsdb); + } +} + +void ospf6_increment_retrans_count(struct ospf6_lsa *lsa) +{ + /* The LSA must be the original one (see the description + in ospf6_decrement_retrans_count () below) */ + lsa->retrans_count++; +} + +void ospf6_decrement_retrans_count(struct ospf6_lsa *lsa) +{ + struct ospf6_lsdb *lsdb; + struct ospf6_lsa *orig; + + /* The LSA must be on the retrans-list of a neighbor. It means + the "lsa" is a copied one, and we have to decrement the + retransmission count of the original one (instead of this "lsa"'s). + In order to find the original LSA, first we have to find + appropriate LSDB that have the original LSA. */ + lsdb = ospf6_get_scoped_lsdb(lsa); + + /* Find the original LSA of which the retrans_count should be + * decremented */ + orig = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsdb); + if (orig) { + orig->retrans_count--; + assert(orig->retrans_count >= 0); + } +} + +/* RFC2328 section 13.2 Installing LSAs in the database */ +void ospf6_install_lsa(struct ospf6_lsa *lsa) +{ + struct ospf6 *ospf6; + struct timeval now; + struct ospf6_lsa *old; + struct ospf6_area *area = NULL; + + ospf6 = ospf6_get_by_lsdb(lsa); + assert(ospf6); + + /* Remove the old instance from all neighbors' Link state + retransmission list (RFC2328 13.2 last paragraph) */ + old = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsa->lsdb); + if (old) { + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : old LSA %s", __func__, + lsa->name); + lsa->external_lsa_id = old->external_lsa_id; + } + EVENT_OFF(old->expire); + EVENT_OFF(old->refresh); + ospf6_flood_clear(old); + } + + monotime(&now); + if (!OSPF6_LSA_IS_MAXAGE(lsa)) { + event_add_timer(master, ospf6_lsa_expire, lsa, + OSPF_LSA_MAXAGE + lsa->birth.tv_sec - + now.tv_sec, + &lsa->expire); + } else + lsa->expire = NULL; + + if (OSPF6_LSA_IS_SEQWRAP(lsa) + && !(CHECK_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED) + && lsa->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER))) { + if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) + zlog_debug("lsa install wrapping: sequence 0x%x", + ntohl(lsa->header->seqnum)); + SET_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED); + /* in lieu of premature_aging, since we do not want to recreate + * this lsa + * and/or mess with timers etc, we just want to wrap the + * sequence number + * and reflood the lsa before continuing. + * NOTE: Flood needs to be called right after this function + * call, by the + * caller + */ + lsa->header->seqnum = htonl(OSPF_MAX_SEQUENCE_NUMBER); + lsa->header->age = htons(OSPF_LSA_MAXAGE); + ospf6_lsa_checksum(lsa->header); + } + + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type) + || IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) + zlog_debug("%s Install LSA: %s age %d seqnum %x in LSDB.", + __func__, lsa->name, ntohs(lsa->header->age), + ntohl(lsa->header->seqnum)); + + /* actually install */ + lsa->installed = now; + + /* Topo change handling */ + if (CHECK_LSA_TOPO_CHG_ELIGIBLE(ntohs(lsa->header->type))) { + /* check if it is new lsa ? or existing lsa got modified ?*/ + if (!old || OSPF6_LSA_IS_CHANGED(old, lsa)) + ospf6_helper_handle_topo_chg(ospf6, lsa); + } + + ospf6_lsdb_add(lsa, lsa->lsdb); + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && lsa->header->adv_router != ospf6->router_id) { + area = OSPF6_AREA(lsa->lsdb->data); + ospf6_translated_nssa_refresh(area, lsa, NULL); + ospf6_schedule_abr_task(area->ospf6); + } + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_ROUTER) { + area = OSPF6_AREA(lsa->lsdb->data); + if (old == NULL) { + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type) + || IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa->header->type)) + zlog_debug("%s: New router LSA %s", __func__, + lsa->name); + ospf6_abr_nssa_check_status(area->ospf6); + } + } + return; +} + +/* RFC2740 section 3.5.2. Sending Link State Update packets */ +/* RFC2328 section 13.3 Next step in the flooding procedure */ +void ospf6_flood_interface(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, + struct ospf6_interface *oi) +{ + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + struct ospf6_lsa *req, *old; + int retrans_added = 0; + int is_debug = 0; + + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) { + is_debug++; + zlog_debug("Flooding on %s: %s", oi->interface->name, + lsa->name); + } + + /* (1) For each neighbor */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + if (is_debug) + zlog_debug("To neighbor %s", on->name); + + /* (a) if neighbor state < Exchange, examin next */ + if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { + if (is_debug) + zlog_debug( + "Neighbor state less than ExChange, next neighbor"); + continue; + } + + /* (b) if neighbor not yet Full, check request-list */ + if (on->state != OSPF6_NEIGHBOR_FULL) { + if (is_debug) + zlog_debug("Neighbor not yet Full"); + + req = ospf6_lsdb_lookup( + lsa->header->type, lsa->header->id, + lsa->header->adv_router, on->request_list); + if (req == NULL) { + if (is_debug) + zlog_debug( + "Not on request-list for this neighbor"); + /* fall through */ + } else { + /* If new LSA less recent, examin next neighbor + */ + if (ospf6_lsa_compare(lsa, req) > 0) { + if (is_debug) + zlog_debug( + "Requesting is older, next neighbor"); + continue; + } + + /* If the same instance, delete from + request-list and + examin next neighbor */ + if (ospf6_lsa_compare(lsa, req) == 0) { + if (is_debug) + zlog_debug( + "Requesting the same, remove it, next neighbor"); + if (req == on->last_ls_req) { + /* sanity check refcount */ + assert(req->lock >= 2); + ospf6_lsa_unlock(&req); + on->last_ls_req = NULL; + } + if (req) + ospf6_lsdb_remove( + req, on->request_list); + ospf6_check_nbr_loading(on); + continue; + } + + /* If the new LSA is more recent, delete from + * request-list */ + if (ospf6_lsa_compare(lsa, req) < 0) { + if (is_debug) + zlog_debug( + "Received is newer, remove requesting"); + if (req == on->last_ls_req) { + ospf6_lsa_unlock(&req); + on->last_ls_req = NULL; + } + if (req) + ospf6_lsdb_remove(req, + on->request_list); + ospf6_check_nbr_loading(on); + /* fall through */ + } + } + } + + /* (c) If the new LSA was received from this neighbor, + examin next neighbor */ + if (from == on) { + if (is_debug) + zlog_debug( + "Received is from the neighbor, next neighbor"); + continue; + } + + if ((oi->area->ospf6->inst_shutdown) + || CHECK_FLAG(lsa->flag, OSPF6_LSA_FLUSH)) { + if (is_debug) + zlog_debug( + "%s: Send LSA %s (age %d) update now", + __func__, lsa->name, + ntohs(lsa->header->age)); + ospf6_lsupdate_send_neighbor_now(on, lsa); + continue; + } else { + /* (d) add retrans-list, schedule retransmission */ + if (is_debug) + zlog_debug("Add retrans-list of neighbor %s ", + on->name); + + /* Do not increment the retrans count if the lsa is + * already present in the retrans list. + */ + old = ospf6_lsdb_lookup( + lsa->header->type, lsa->header->id, + lsa->header->adv_router, on->retrans_list); + if (!old) { + struct ospf6_lsa *orig; + struct ospf6_lsdb *lsdb; + + if (is_debug) + zlog_debug( + "Increment %s from retrans_list of %s", + lsa->name, on->name); + + /* Increment the retrans count on the original + * copy of LSA if present, to maintain the + * counter consistency. + */ + + lsdb = ospf6_get_scoped_lsdb(lsa); + orig = ospf6_lsdb_lookup( + lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsdb); + if (orig) + ospf6_increment_retrans_count(orig); + else + ospf6_increment_retrans_count(lsa); + + ospf6_lsdb_add(ospf6_lsa_copy(lsa), + on->retrans_list); + event_add_timer(master, + ospf6_lsupdate_send_neighbor, + on, on->ospf6_if->rxmt_interval, + &on->thread_send_lsupdate); + retrans_added++; + } + } + } + + /* (2) examin next interface if not added to retrans-list */ + if (retrans_added == 0) { + if (is_debug) + zlog_debug( + "No retransmission scheduled, next interface %s", + oi->interface->name); + return; + } + + /* (3) If the new LSA was received on this interface, + and it was from DR or BDR, examin next interface */ + if (from && from->ospf6_if == oi + && (from->router_id == oi->drouter + || from->router_id == oi->bdrouter)) { + if (is_debug) + zlog_debug( + "Received is from the I/F's DR or BDR, next interface"); + return; + } + + /* (4) If the new LSA was received on this interface, + and the interface state is BDR, examin next interface */ + if (from && from->ospf6_if == oi) { + if (oi->state == OSPF6_INTERFACE_BDR) { + if (is_debug) + zlog_debug( + "Received is from the I/F, itself BDR, next interface"); + return; + } + SET_FLAG(lsa->flag, OSPF6_LSA_FLOODBACK); + } + + /* (5) flood the LSA out the interface. */ + if (is_debug) + zlog_debug("Schedule flooding for the interface"); + if ((oi->type == OSPF_IFTYPE_BROADCAST) + || (oi->type == OSPF_IFTYPE_POINTOPOINT)) { + ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsupdate_list); + event_add_event(master, ospf6_lsupdate_send_interface, oi, 0, + &oi->thread_send_lsupdate); + } else { + /* reschedule retransmissions to all neighbors */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + EVENT_OFF(on->thread_send_lsupdate); + event_add_event(master, ospf6_lsupdate_send_neighbor, + on, 0, &on->thread_send_lsupdate); + } + } +} + +void ospf6_flood_area(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, + struct ospf6_area *oa) +{ + struct listnode *node, *nnode; + struct ospf6_interface *oi; + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL + && oi != OSPF6_INTERFACE(lsa->lsdb->data)) + continue; + + ospf6_flood_interface(from, lsa, oi); + } +} + +static void ospf6_flood_process(struct ospf6_neighbor *from, + struct ospf6_lsa *lsa, struct ospf6 *process) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + + for (ALL_LIST_ELEMENTS(process->area_list, node, nnode, oa)) { + + /* If unknown LSA and U-bit clear, treat as link local + * flooding scope + */ + if (!OSPF6_LSA_IS_KNOWN(lsa->header->type) + && !(ntohs(lsa->header->type) & OSPF6_LSTYPE_UBIT_MASK) + && (oa != OSPF6_INTERFACE(lsa->lsdb->data)->area)) { + + if (IS_OSPF6_DEBUG_FLOODING) + zlog_debug("Unknown LSA, do not flood"); + continue; + } + + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_AREA + && oa != OSPF6_AREA(lsa->lsdb->data)) + continue; + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL + && oa != OSPF6_INTERFACE(lsa->lsdb->data)->area) + continue; + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL + && (IS_AREA_STUB(oa) || IS_AREA_NSSA(oa))) + continue; + + /* Check for NSSA LSA */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && !IS_AREA_NSSA(oa) && !OSPF6_LSA_IS_MAXAGE(lsa)) + continue; + + ospf6_flood_area(from, lsa, oa); + } +} + +void ospf6_flood(struct ospf6_neighbor *from, struct ospf6_lsa *lsa) +{ + struct ospf6 *ospf6; + + ospf6 = ospf6_get_by_lsdb(lsa); + if (ospf6 == NULL) + return; + + ospf6_flood_process(from, lsa, ospf6); +} + +static void ospf6_flood_clear_interface(struct ospf6_lsa *lsa, + struct ospf6_interface *oi) +{ + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + struct ospf6_lsa *rem; + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + rem = ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, + on->retrans_list); + if (rem && !ospf6_lsa_compare(rem, lsa)) { + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) + zlog_debug("Remove %s from retrans_list of %s", + rem->name, on->name); + ospf6_decrement_retrans_count(rem); + ospf6_lsdb_remove(rem, on->retrans_list); + } + } +} + +void ospf6_flood_clear_area(struct ospf6_lsa *lsa, struct ospf6_area *oa) +{ + struct listnode *node, *nnode; + struct ospf6_interface *oi; + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL + && oi != OSPF6_INTERFACE(lsa->lsdb->data)) + continue; + + ospf6_flood_clear_interface(lsa, oi); + } +} + +static void ospf6_flood_clear_process(struct ospf6_lsa *lsa, + struct ospf6 *process) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + + for (ALL_LIST_ELEMENTS(process->area_list, node, nnode, oa)) { + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_AREA + && oa != OSPF6_AREA(lsa->lsdb->data)) + continue; + if (OSPF6_LSA_SCOPE(lsa->header->type) == OSPF6_SCOPE_LINKLOCAL + && oa != OSPF6_INTERFACE(lsa->lsdb->data)->area) + continue; + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL + && (IS_AREA_STUB(oa) || (IS_AREA_NSSA(oa)))) + continue; + /* Check for NSSA LSA */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7 + && !IS_AREA_NSSA(oa)) + continue; + + ospf6_flood_clear_area(lsa, oa); + } +} + +void ospf6_flood_clear(struct ospf6_lsa *lsa) +{ + struct ospf6 *ospf6; + + ospf6 = ospf6_get_by_lsdb(lsa); + if (ospf6 == NULL) + return; + ospf6_flood_clear_process(lsa, ospf6); +} + + +/* RFC2328 13.5 (Table 19): Sending link state acknowledgements. */ +static void ospf6_acknowledge_lsa_bdrouter(struct ospf6_lsa *lsa, + int ismore_recent, + struct ospf6_neighbor *from) +{ + struct ospf6_interface *oi; + int is_debug = 0; + + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) + is_debug++; + + assert(from && from->ospf6_if); + oi = from->ospf6_if; + + /* LSA is more recent than database copy, but was not flooded + back out receiving interface. Delayed acknowledgement sent + if advertisement received from Designated Router, + otherwide do nothing. */ + if (ismore_recent < 0) { + if (oi->drouter == from->router_id) { + if (is_debug) + zlog_debug( + "Delayed acknowledgement (BDR & MoreRecent & from DR)"); + /* Delayed acknowledgement */ + ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); + event_add_timer(master, ospf6_lsack_send_interface, oi, + 3, &oi->thread_send_lsack); + } else { + if (is_debug) + zlog_debug( + "No acknowledgement (BDR & MoreRecent & ! from DR)"); + } + return; + } + + /* LSA is a duplicate, and was treated as an implied acknowledgement. + Delayed acknowledgement sent if advertisement received from + Designated Router, otherwise do nothing */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) + && CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { + if (oi->drouter == from->router_id) { + if (is_debug) + zlog_debug( + "Delayed acknowledgement (BDR & Duplicate & ImpliedAck & from DR)"); + /* Delayed acknowledgement */ + ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); + event_add_timer(master, ospf6_lsack_send_interface, oi, + 3, &oi->thread_send_lsack); + } else { + if (is_debug) + zlog_debug( + "No acknowledgement (BDR & Duplicate & ImpliedAck & ! from DR)"); + } + return; + } + + /* LSA is a duplicate, and was not treated as an implied + acknowledgement. + Direct acknowledgement sent */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) + && !CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { + if (is_debug) + zlog_debug("Direct acknowledgement (BDR & Duplicate)"); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), from->lsack_list); + event_add_event(master, ospf6_lsack_send_neighbor, from, 0, + &from->thread_send_lsack); + return; + } + + /* LSA's LS age is equal to Maxage, and there is no current instance + of the LSA in the link state database, and none of router's + neighbors are in states Exchange or Loading */ + /* Direct acknowledgement sent, but this case is handled in + early of ospf6_receive_lsa () */ +} + +static void ospf6_acknowledge_lsa_allother(struct ospf6_lsa *lsa, + int ismore_recent, + struct ospf6_neighbor *from) +{ + struct ospf6_interface *oi; + int is_debug = 0; + + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) + is_debug++; + + assert(from && from->ospf6_if); + oi = from->ospf6_if; + + /* LSA has been flood back out receiving interface. + No acknowledgement sent. */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_FLOODBACK)) { + if (is_debug) + zlog_debug("No acknowledgement (AllOther & FloodBack)"); + return; + } + + /* LSA is more recent than database copy, but was not flooded + back out receiving interface. Delayed acknowledgement sent. */ + if (ismore_recent < 0) { + if (is_debug) + zlog_debug( + "Delayed acknowledgement (AllOther & MoreRecent)"); + /* Delayed acknowledgement */ + ospf6_lsdb_add(ospf6_lsa_copy(lsa), oi->lsack_list); + event_add_timer(master, ospf6_lsack_send_interface, oi, 3, + &oi->thread_send_lsack); + return; + } + + /* LSA is a duplicate, and was treated as an implied acknowledgement. + No acknowledgement sent. */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) + && CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { + if (is_debug) + zlog_debug( + "No acknowledgement (AllOther & Duplicate & ImpliedAck)"); + return; + } + + /* LSA is a duplicate, and was not treated as an implied + acknowledgement. + Direct acknowledgement sent */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_DUPLICATE) + && !CHECK_FLAG(lsa->flag, OSPF6_LSA_IMPLIEDACK)) { + if (is_debug) + zlog_debug( + "Direct acknowledgement (AllOther & Duplicate)"); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), from->lsack_list); + event_add_event(master, ospf6_lsack_send_neighbor, from, 0, + &from->thread_send_lsack); + return; + } + + /* LSA's LS age is equal to Maxage, and there is no current instance + of the LSA in the link state database, and none of router's + neighbors are in states Exchange or Loading */ + /* Direct acknowledgement sent, but this case is handled in + early of ospf6_receive_lsa () */ +} + +static void ospf6_acknowledge_lsa(struct ospf6_lsa *lsa, int ismore_recent, + struct ospf6_neighbor *from) +{ + struct ospf6_interface *oi; + + assert(from && from->ospf6_if); + oi = from->ospf6_if; + + if (oi->state == OSPF6_INTERFACE_BDR) + ospf6_acknowledge_lsa_bdrouter(lsa, ismore_recent, from); + else + ospf6_acknowledge_lsa_allother(lsa, ismore_recent, from); +} + +/* RFC2328 section 13 (4): + if MaxAge LSA and if we have no instance, and no neighbor + is in states Exchange or Loading + returns 1 if match this case, else returns 0 */ +static int ospf6_is_maxage_lsa_drop(struct ospf6_lsa *lsa, + struct ospf6_neighbor *from) +{ + struct ospf6_neighbor *on; + struct ospf6_interface *oi; + struct ospf6_area *oa; + struct ospf6 *process = NULL; + struct listnode *i, *j, *k; + int count = 0; + + if (!OSPF6_LSA_IS_MAXAGE(lsa)) + return 0; + + if (ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsa->lsdb)) + return 0; + + process = from->ospf6_if->area->ospf6; + + for (ALL_LIST_ELEMENTS_RO(process->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) + if (on->state == OSPF6_NEIGHBOR_EXCHANGE + || on->state == OSPF6_NEIGHBOR_LOADING) + count++; + + if (count == 0) + return 1; + return 0; +} + +static bool ospf6_lsa_check_min_arrival(struct ospf6_lsa *lsa, + struct ospf6_neighbor *from) +{ + struct timeval now, res; + unsigned int time_delta_ms; + + monotime(&now); + timersub(&now, &lsa->installed, &res); + time_delta_ms = (res.tv_sec * 1000) + (int)(res.tv_usec / 1000); + + if (time_delta_ms < from->ospf6_if->area->ospf6->lsa_minarrival) { + if (IS_OSPF6_DEBUG_FLOODING || + IS_OSPF6_DEBUG_FLOOD_TYPE(lsa->header->type)) + zlog_debug( + "LSA can't be updated within MinLSArrival, %dms < %dms, discard", + time_delta_ms, + from->ospf6_if->area->ospf6->lsa_minarrival); + return true; + } + return false; +} + +/* RFC2328 section 13 The Flooding Procedure */ +void ospf6_receive_lsa(struct ospf6_neighbor *from, + struct ospf6_lsa_header *lsa_header) +{ + struct ospf6_lsa *new = NULL, *old = NULL, *rem = NULL; + int ismore_recent; + int is_debug = 0; + + ismore_recent = 1; + assert(from); + + /* if we receive a LSA with invalid seqnum drop it */ + if (ntohl(lsa_header->seqnum) - 1 == OSPF_MAX_SEQUENCE_NUMBER) { + if (IS_OSPF6_DEBUG_EXAMIN_TYPE(lsa_header->type)) { + zlog_debug( + "received lsa [%s Id:%pI4 Adv:%pI4] with invalid seqnum 0x%x, ignore", + ospf6_lstype_name(lsa_header->type), + &lsa_header->id, &lsa_header->adv_router, + ntohl(lsa_header->seqnum)); + } + return; + } + + /* make lsa structure for received lsa */ + new = ospf6_lsa_create(lsa_header); + + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_FLOOD_TYPE(new->header->type)) { + is_debug++; + zlog_debug("LSA Receive from %s", from->name); + ospf6_lsa_header_print(new); + } + + /* (1) LSA Checksum */ + if (!ospf6_lsa_checksum_valid(new->header)) { + if (is_debug) + zlog_debug( + "Wrong LSA Checksum %s (Router-ID: %pI4) [Type:%s Checksum:%#06hx), discard", + from->name, &from->router_id, + ospf6_lstype_name(new->header->type), + ntohs(new->header->checksum)); + ospf6_lsa_delete(new); + return; + } + + /* (2) Examine the LSA's LS type. + RFC2470 3.5.1. Receiving Link State Update packets */ + if (IS_AREA_STUB(from->ospf6_if->area) + && OSPF6_LSA_SCOPE(new->header->type) == OSPF6_SCOPE_AS) { + if (is_debug) + zlog_debug( + "AS-External-LSA (or AS-scope LSA) in stub area, discard"); + ospf6_lsa_delete(new); + return; + } + + /* (3) LSA which have reserved scope is discarded + RFC2470 3.5.1. Receiving Link State Update packets */ + /* Flooding scope check. LSAs with unknown scope are discarded here. + Set appropriate LSDB for the LSA */ + switch (OSPF6_LSA_SCOPE(new->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + new->lsdb = from->ospf6_if->lsdb; + break; + case OSPF6_SCOPE_AREA: + new->lsdb = from->ospf6_if->area->lsdb; + break; + case OSPF6_SCOPE_AS: + new->lsdb = from->ospf6_if->area->ospf6->lsdb; + break; + default: + if (is_debug) + zlog_debug("LSA has reserved scope, discard"); + ospf6_lsa_delete(new); + return; + } + + /* (4) if MaxAge LSA and if we have no instance, and no neighbor + is in states Exchange or Loading */ + if (ospf6_is_maxage_lsa_drop(new, from)) { + /* log */ + if (is_debug) + zlog_debug( + "Drop MaxAge LSA with direct acknowledgement."); + + /* a) Acknowledge back to neighbor (Direct acknowledgement, + * 13.5) */ + ospf6_lsdb_add(ospf6_lsa_copy(new), from->lsack_list); + event_add_event(master, ospf6_lsack_send_neighbor, from, 0, + &from->thread_send_lsack); + + /* b) Discard */ + ospf6_lsa_delete(new); + return; + } + + /* (5) */ + /* lookup the same database copy in lsdb */ + old = ospf6_lsdb_lookup(new->header->type, new->header->id, + new->header->adv_router, new->lsdb); + if (old) { + ismore_recent = ospf6_lsa_compare(new, old); + if (ntohl(new->header->seqnum) == ntohl(old->header->seqnum)) { + if (is_debug) + zlog_debug("Received is duplicated LSA"); + SET_FLAG(new->flag, OSPF6_LSA_DUPLICATE); + } + } + + /* if no database copy or received is more recent */ + if (old == NULL || ismore_recent < 0) { + bool self_originated; + + /* in case we have no database copy */ + ismore_recent = -1; + + /* (a) MinLSArrival check */ + if (old) { + if (ospf6_lsa_check_min_arrival(old, from)) { + ospf6_lsa_delete(new); + return; /* examin next lsa */ + } + } + + monotime(&new->received); + + if (is_debug) + zlog_debug( + "Install, Flood, Possibly acknowledge the received LSA"); + + /* Remove older copies of this LSA from retx lists */ + if (old) + ospf6_flood_clear(old); + + self_originated = (new->header->adv_router + == from->ospf6_if->area->ospf6->router_id); + + /* Received non-self-originated Grace LSA. */ + if (IS_GRACE_LSA(new) && !self_originated) { + struct ospf6 *ospf6; + + ospf6 = ospf6_get_by_lsdb(new); + + assert(ospf6); + + if (OSPF6_LSA_IS_MAXAGE(new)) { + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Received a maxage GraceLSA from router %pI4", + __func__, + &new->header->adv_router); + if (old) { + ospf6_process_maxage_grace_lsa( + ospf6, new, from); + } else { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, GraceLSA doesn't exist in lsdb, so discarding GraceLSA", + __func__); + ospf6_lsa_delete(new); + return; + } + } else { + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Received a GraceLSA from router %pI4", + __func__, + &new->header->adv_router); + + if (ospf6_process_grace_lsa(ospf6, new, from) + == OSPF6_GR_NOT_HELPER) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Not moving to HELPER role, So dicarding GraceLSA", + __func__); + return; + } + } + } + + /* (b) immediately flood and (c) remove from all retrans-list */ + /* Prevent self-originated LSA to be flooded. this is to make + * reoriginated instance of the LSA not to be rejected by other + * routers due to MinLSArrival. + */ + if (!self_originated) + ospf6_flood(from, new); + + /* (d), installing lsdb, which may cause routing + table calculation (replacing database copy) */ + ospf6_install_lsa(new); + + if (OSPF6_LSA_IS_MAXAGE(new)) + ospf6_maxage_remove(from->ospf6_if->area->ospf6); + + /* (e) possibly acknowledge */ + ospf6_acknowledge_lsa(new, ismore_recent, from); + + /* (f) Self Originated LSA, section 13.4 */ + if (self_originated) { + if (from->ospf6_if->area->ospf6->gr_info + .restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress -- not flushing self-originated LSA: %s", + new->name); + return; + } + + /* Self-originated LSA (newer than ours) is received + from + another router. We have to make a new instance of the + LSA + or have to flush this LSA. */ + if (is_debug) { + zlog_debug( + "Newer instance of the self-originated LSA"); + zlog_debug("Schedule reorigination"); + } + event_add_event(master, ospf6_lsa_refresh, new, 0, + &new->refresh); + } + + /* GR: check for network topology change. */ + struct ospf6 *ospf6 = from->ospf6_if->area->ospf6; + struct ospf6_area *area = from->ospf6_if->area; + if (ospf6->gr_info.restart_in_progress && + (new->header->type == ntohs(OSPF6_LSTYPE_ROUTER) || + new->header->type == ntohs(OSPF6_LSTYPE_NETWORK))) + ospf6_gr_check_lsdb_consistency(ospf6, area); + + return; + } + + /* (6) if there is instance on sending neighbor's request list */ + if (ospf6_lsdb_lookup(new->header->type, new->header->id, + new->header->adv_router, from->request_list)) { + /* if no database copy, should go above state (5) */ + assert(old); + + zlog_warn( + "Received is not newer, on the neighbor %s request-list", + from->name); + zlog_warn( + "BadLSReq, discard the received LSA lsa %s send badLSReq", + new->name); + + /* BadLSReq */ + event_add_event(master, bad_lsreq, from, 0, NULL); + + ospf6_lsa_delete(new); + return; + } + + /* (7) if neither one is more recent */ + if (ismore_recent == 0) { + if (is_debug) + zlog_debug( + "The same instance as database copy (neither recent)"); + + /* (a) if on retrans-list, Treat this LSA as an Ack: Implied Ack + */ + rem = ospf6_lsdb_lookup(new->header->type, new->header->id, + new->header->adv_router, + from->retrans_list); + if (rem) { + if (is_debug) { + zlog_debug( + "It is on the neighbor's retrans-list."); + zlog_debug( + "Treat as an Implied acknowledgement"); + } + SET_FLAG(new->flag, OSPF6_LSA_IMPLIEDACK); + ospf6_decrement_retrans_count(rem); + ospf6_lsdb_remove(rem, from->retrans_list); + } + + if (is_debug) + zlog_debug("Possibly acknowledge and then discard"); + + /* (b) possibly acknowledge */ + ospf6_acknowledge_lsa(new, ismore_recent, from); + + ospf6_lsa_delete(new); + return; + } + + /* (8) previous database copy is more recent */ + { + assert(old); + + /* If database copy is in 'Seqnumber Wrapping', + simply discard the received LSA */ + if (OSPF6_LSA_IS_MAXAGE(old) + && old->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER)) { + if (is_debug) { + zlog_debug("The LSA is in Seqnumber Wrapping"); + zlog_debug("MaxAge & MaxSeqNum, discard"); + } + ospf6_lsa_delete(new); + return; + } + + /* Otherwise, Send database copy of this LSA to this neighbor */ + { + if (is_debug) { + zlog_debug("Database copy is more recent."); + zlog_debug( + "Send back directly and then discard"); + } + + /* Neighbor router sent recent age for LSA, + * Router could be restarted while current copy is + * MAXAGEd and not removed.*/ + if (OSPF6_LSA_IS_MAXAGE(old) + && !OSPF6_LSA_IS_MAXAGE(new)) { + if (new->header->adv_router + != from->ospf6_if->area->ospf6->router_id) { + if (is_debug) + zlog_debug( + "%s: Current copy of LSA %s is MAXAGE, but new has recent age, flooding/installing.", + __PRETTY_FUNCTION__, old->name); + ospf6_lsa_purge(old); + ospf6_flood(from, new); + ospf6_install_lsa(new); + return; + } + /* For self-originated LSA, only trust + * ourselves. Fall through and send + * LS Update with our current copy. + */ + if (is_debug) + zlog_debug( + "%s: Current copy of self-originated LSA %s is MAXAGE, but new has recent age, re-sending current one.", + __PRETTY_FUNCTION__, old->name); + } + + /* MinLSArrival check as per RFC 2328 13 (8) */ + if (ospf6_lsa_check_min_arrival(old, from)) { + ospf6_lsa_delete(new); + return; /* examin next lsa */ + } + + ospf6_lsdb_add(ospf6_lsa_copy(old), + from->lsupdate_list); + event_add_event(master, ospf6_lsupdate_send_neighbor, + from, 0, &from->thread_send_lsupdate); + + ospf6_lsa_delete(new); + return; + } + } +} + +DEFUN (debug_ospf6_flooding, + debug_ospf6_flooding_cmd, + "debug ospf6 flooding", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 flooding function\n" + ) +{ + OSPF6_DEBUG_FLOODING_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_flooding, + no_debug_ospf6_flooding_cmd, + "no debug ospf6 flooding", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 flooding function\n" + ) +{ + OSPF6_DEBUG_FLOODING_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_flood(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_FLOODING) + vty_out(vty, "debug ospf6 flooding\n"); + return 0; +} + +void install_element_ospf6_debug_flood(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_flooding_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_flooding_cmd); + install_element(CONFIG_NODE, &debug_ospf6_flooding_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_flooding_cmd); +} diff --git a/ospf6d/ospf6_flood.h b/ospf6d/ospf6_flood.h new file mode 100644 index 0000000..33c2c75 --- /dev/null +++ b/ospf6d/ospf6_flood.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_FLOOD_H +#define OSPF6_FLOOD_H + +/* Debug option */ +extern unsigned char conf_debug_ospf6_flooding; +#define OSPF6_DEBUG_FLOODING_ON() (conf_debug_ospf6_flooding = 1) +#define OSPF6_DEBUG_FLOODING_OFF() (conf_debug_ospf6_flooding = 0) +#define IS_OSPF6_DEBUG_FLOODING (conf_debug_ospf6_flooding) + +/* Function Prototypes */ +extern struct ospf6_lsdb *ospf6_get_scoped_lsdb(struct ospf6_lsa *lsa); +extern struct ospf6_lsdb *ospf6_get_scoped_lsdb_self(struct ospf6_lsa *lsa); + +/* origination & purging */ +extern void ospf6_lsa_originate(struct ospf6 *ospf6, struct ospf6_lsa *lsa); +extern void ospf6_lsa_originate_process(struct ospf6_lsa *lsa, + struct ospf6 *process); +extern void ospf6_lsa_originate_area(struct ospf6_lsa *lsa, + struct ospf6_area *oa); +extern void ospf6_lsa_originate_interface(struct ospf6_lsa *lsa, + struct ospf6_interface *oi); +void ospf6_external_lsa_purge(struct ospf6 *ospf6, struct ospf6_lsa *lsa); +extern void ospf6_lsa_purge(struct ospf6_lsa *lsa); + +extern void ospf6_lsa_purge_multi_ls_id(struct ospf6_area *oa, + struct ospf6_lsa *lsa); + +/* access method to retrans_count */ +extern void ospf6_increment_retrans_count(struct ospf6_lsa *lsa); +extern void ospf6_decrement_retrans_count(struct ospf6_lsa *lsa); + +/* flooding & clear flooding */ +extern void ospf6_flood_clear(struct ospf6_lsa *lsa); +extern void ospf6_flood(struct ospf6_neighbor *from, struct ospf6_lsa *lsa); +extern void ospf6_flood_area(struct ospf6_neighbor *from, struct ospf6_lsa *lsa, + struct ospf6_area *oa); + +/* receive & install */ +extern void ospf6_receive_lsa(struct ospf6_neighbor *from, + struct ospf6_lsa_header *header); +extern void ospf6_install_lsa(struct ospf6_lsa *lsa); + +extern int config_write_ospf6_debug_flood(struct vty *vty); +extern void install_element_ospf6_debug_flood(void); +extern void ospf6_flood_interface(struct ospf6_neighbor *from, + struct ospf6_lsa *lsa, + struct ospf6_interface *oi); +extern int ospf6_lsupdate_send_neighbor_now(struct ospf6_neighbor *on, + struct ospf6_lsa *lsa); + +extern void ospf6_flood_clear_area(struct ospf6_lsa *lsa, + struct ospf6_area *oa); +#endif /* OSPF6_FLOOD_H */ diff --git a/ospf6d/ospf6_gr.c b/ospf6d/ospf6_gr.c new file mode 100644 index 0000000..ab119a4 --- /dev/null +++ b/ospf6d/ospf6_gr.c @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC 5187 Graceful Restart. + * + * Copyright 2021 NetDEF (c), All rights reserved. + */ + +#include + +#include "memory.h" +#include "command.h" +#include "table.h" +#include "vty.h" +#include "log.h" +#include "hook.h" +#include "printfrr.h" +#include "lib_errors.h" + +#include "ospf6d/ospf6_lsa.h" +#include "ospf6d/ospf6_lsdb.h" +#include "ospf6d/ospf6_route.h" +#include "ospf6d/ospf6_area.h" +#include "ospf6d/ospf6_interface.h" +#include "ospf6d/ospf6d.h" +#include "ospf6d/ospf6_asbr.h" +#include "ospf6d/ospf6_zebra.h" +#include "ospf6d/ospf6_message.h" +#include "ospf6d/ospf6_neighbor.h" +#include "ospf6d/ospf6_network.h" +#include "ospf6d/ospf6_flood.h" +#include "ospf6d/ospf6_intra.h" +#include "ospf6d/ospf6_spf.h" +#include "ospf6d/ospf6_gr.h" +#include "ospf6d/ospf6_gr_clippy.c" + +static void ospf6_gr_grace_period_expired(struct event *thread); + +/* Originate and install Grace-LSA for a given interface. */ +static int ospf6_gr_lsa_originate(struct ospf6_interface *oi, + enum ospf6_gr_restart_reason reason) +{ + struct ospf6 *ospf6 = oi->area->ospf6; + struct ospf6_gr_info *gr_info = &ospf6->gr_info; + struct ospf6_lsa_header *lsa_header; + struct ospf6_grace_lsa *grace_lsa; + struct ospf6_lsa *lsa; + uint16_t lsa_length; + char buffer[OSPF6_MAX_LSASIZE]; + + if (IS_OSPF6_DEBUG_ORIGINATE(LINK)) + zlog_debug("Originate Grace-LSA for Interface %s", + oi->interface->name); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + grace_lsa = (struct ospf6_grace_lsa *)ospf6_lsa_header_end(lsa_header); + + /* Put grace period. */ + grace_lsa->tlv_period.header.type = htons(GRACE_PERIOD_TYPE); + grace_lsa->tlv_period.header.length = htons(GRACE_PERIOD_LENGTH); + grace_lsa->tlv_period.interval = htonl(gr_info->grace_period); + + /* Put restart reason. */ + grace_lsa->tlv_reason.header.type = htons(RESTART_REASON_TYPE); + grace_lsa->tlv_reason.header.length = htons(RESTART_REASON_LENGTH); + grace_lsa->tlv_reason.reason = reason; + + /* Fill LSA Header */ + lsa_length = sizeof(*lsa_header) + sizeof(*grace_lsa); + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_GRACE_LSA); + lsa_header->id = htonl(oi->interface->ifindex); + lsa_header->adv_router = ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oi->lsdb); + lsa_header->length = htons(lsa_length); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + if (reason == OSPF6_GR_UNKNOWN_RESTART) { + struct ospf6_header *oh; + uint32_t *uv32; + int n; + uint16_t length = OSPF6_HEADER_SIZE + 4 + lsa_length; + struct iovec iovector[2] = {}; + + /* Reserve space for OSPFv3 header. */ + memmove(&buffer[OSPF6_HEADER_SIZE + 4], buffer, lsa_length); + + /* Fill in the OSPFv3 header. */ + oh = (struct ospf6_header *)buffer; + oh->version = OSPFV3_VERSION; + oh->type = OSPF6_MESSAGE_TYPE_LSUPDATE; + oh->router_id = oi->area->ospf6->router_id; + oh->area_id = oi->area->area_id; + oh->instance_id = oi->instance_id; + oh->reserved = 0; + oh->length = htons(length); + + /* Fill LSA header. */ + uv32 = (uint32_t *)&buffer[sizeof(*oh)]; + *uv32 = htonl(1); + + /* Send packet. */ + iovector[0].iov_base = lsa_header; + iovector[0].iov_len = length; + n = ospf6_sendmsg(oi->linklocal_addr, &allspfrouters6, + oi->interface->ifindex, iovector, ospf6->fd); + if (n != length) + flog_err(EC_LIB_DEVELOPMENT, + "%s: could not send entire message", __func__); + } else { + /* Create and install LSA. */ + lsa = ospf6_lsa_create(lsa_header); + ospf6_lsa_originate_interface(lsa, oi); + } + + return 0; +} + +/* Flush all self-originated Grace-LSAs. */ +static void ospf6_gr_flush_grace_lsas(struct ospf6 *ospf6) +{ + struct ospf6_area *area; + struct listnode *anode; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, anode, area)) { + struct ospf6_lsa *lsa; + struct ospf6_interface *oi; + struct listnode *inode; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "GR: flushing self-originated Grace-LSAs [area %pI4]", + &area->area_id); + + for (ALL_LIST_ELEMENTS_RO(area->if_list, inode, oi)) { + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_GRACE_LSA), + htonl(oi->interface->ifindex), + oi->area->ospf6->router_id, + oi->lsdb); + if (!lsa) { + zlog_warn( + "%s: Grace-LSA not found [interface %s] [area %pI4]", + __func__, oi->interface->name, + &area->area_id); + continue; + } + + ospf6_lsa_purge(lsa); + } + } +} + +/* Exit from the Graceful Restart mode. */ +static void ospf6_gr_restart_exit(struct ospf6 *ospf6, const char *reason) +{ + struct ospf6_area *area; + struct listnode *onode, *anode; + struct ospf6_route *route; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("GR: exiting graceful restart: %s", reason); + + ospf6->gr_info.restart_in_progress = false; + ospf6->gr_info.finishing_restart = true; + XFREE(MTYPE_TMP, ospf6->gr_info.exit_reason); + ospf6->gr_info.exit_reason = XSTRDUP(MTYPE_TMP, reason); + EVENT_OFF(ospf6->gr_info.t_grace_period); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, onode, area)) { + struct ospf6_interface *oi; + + /* + * 1) The router should reoriginate its router-LSAs for all + * attached areas in order to make sure they have the correct + * contents. + */ + OSPF6_ROUTER_LSA_EXECUTE(area); + + /* + * Force reorigination of intra-area-prefix-LSAs to handle + * areas without any full adjacency. + */ + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(area); + + for (ALL_LIST_ELEMENTS_RO(area->if_list, anode, oi)) { + /* Disable hello delay. */ + if (oi->gr.hello_delay.t_grace_send) { + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + event_add_event(master, ospf6_hello_send, oi, 0, + &oi->thread_send_hello); + } + + /* Reoriginate Link-LSA. */ + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + OSPF6_LINK_LSA_EXECUTE(oi); + + /* + * 2) The router should reoriginate network-LSAs on all + * segments where it is the Designated Router. + */ + if (oi->state == OSPF6_INTERFACE_DR) + OSPF6_NETWORK_LSA_EXECUTE(oi); + } + } + + /* + * While all self-originated NSSA and AS-external LSAs were already + * learned from the helping neighbors, we need to reoriginate them in + * order to ensure they will be refreshed periodically. + */ + for (route = ospf6_route_head(ospf6->external_table); route; + route = ospf6_route_next(route)) + ospf6_handle_external_lsa_origination(ospf6, route, + &route->prefix); + + /* + * 3) The router reruns its OSPF routing calculations, this time + * installing the results into the system forwarding table, and + * originating summary-LSAs, Type-7 LSAs and AS-external-LSAs as + * necessary. + * + * 4) Any remnant entries in the system forwarding table that were + * installed before the restart, but that are no longer valid, + * should be removed. + */ + ospf6_spf_schedule(ospf6, OSPF6_SPF_FLAGS_GR_FINISH); + + /* 6) Any grace-LSAs that the router originated should be flushed. */ + ospf6_gr_flush_grace_lsas(ospf6); +} + +/* Enter the Graceful Restart mode. */ +void ospf6_gr_restart_enter(struct ospf6 *ospf6, + enum ospf6_gr_restart_reason reason, + time_t timestamp) +{ + unsigned long remaining_time; + + ospf6->gr_info.restart_in_progress = true; + ospf6->gr_info.reason = reason; + + /* Schedule grace period timeout. */ + remaining_time = timestamp - time(NULL); + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "GR: remaining time until grace period expires: %lu(s)", + remaining_time); + + event_add_timer(master, ospf6_gr_grace_period_expired, ospf6, + remaining_time, &ospf6->gr_info.t_grace_period); +} + +#define RTR_LSA_MISSING 0 +#define RTR_LSA_ADJ_FOUND 1 +#define RTR_LSA_ADJ_NOT_FOUND 2 + +/* Check if a Router-LSA exists and if it contains a given link. */ +static int ospf6_router_lsa_contains_adj(struct ospf6_area *area, + in_addr_t adv_router, + in_addr_t neighbor_router_id) +{ + uint16_t type; + struct ospf6_lsa *lsa; + bool empty = true; + + type = ntohs(OSPF6_LSTYPE_ROUTER); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, adv_router, lsa)) { + struct ospf6_router_lsa *router_lsa; + char *start, *end, *current; + + empty = false; + router_lsa = (struct ospf6_router_lsa + *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + + /* Iterate over all interfaces in the Router-LSA. */ + start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + for (current = start; + current + sizeof(struct ospf6_router_lsdesc) <= end; + current += sizeof(struct ospf6_router_lsdesc)) { + struct ospf6_router_lsdesc *lsdesc; + + lsdesc = (struct ospf6_router_lsdesc *)current; + if (lsdesc->type != OSPF6_ROUTER_LSDESC_POINTTOPOINT) + continue; + + if (lsdesc->neighbor_router_id == neighbor_router_id) { + ospf6_lsa_unlock(&lsa); + return RTR_LSA_ADJ_FOUND; + } + } + } + + if (empty) + return RTR_LSA_MISSING; + + return RTR_LSA_ADJ_NOT_FOUND; +} + +static bool ospf6_gr_check_router_lsa_consistency(struct ospf6 *ospf6, + struct ospf6_area *area, + struct ospf6_lsa *lsa) +{ + if (lsa->header->adv_router == ospf6->router_id) { + struct ospf6_router_lsa *router_lsa; + char *start, *end, *current; + + router_lsa = (struct ospf6_router_lsa + *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + + /* Iterate over all interfaces in the Router-LSA. */ + start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + for (current = start; + current + sizeof(struct ospf6_router_lsdesc) <= end; + current += sizeof(struct ospf6_router_lsdesc)) { + struct ospf6_router_lsdesc *lsdesc; + + lsdesc = (struct ospf6_router_lsdesc *)current; + if (lsdesc->type != OSPF6_ROUTER_LSDESC_POINTTOPOINT) + continue; + + if (ospf6_router_lsa_contains_adj( + area, lsdesc->neighbor_router_id, + ospf6->router_id) + == RTR_LSA_ADJ_NOT_FOUND) + return false; + } + } else { + int adj1, adj2; + + adj1 = ospf6_router_lsa_contains_adj(area, ospf6->router_id, + lsa->header->adv_router); + adj2 = ospf6_router_lsa_contains_adj( + area, lsa->header->adv_router, ospf6->router_id); + if ((adj1 == RTR_LSA_ADJ_FOUND && adj2 == RTR_LSA_ADJ_NOT_FOUND) + || (adj1 == RTR_LSA_ADJ_NOT_FOUND + && adj2 == RTR_LSA_ADJ_FOUND)) + return false; + } + + return true; +} + +/* + * Check for LSAs that are inconsistent with the pre-restart LSAs, and abort the + * ongoing graceful restart when that's the case. + */ +void ospf6_gr_check_lsdb_consistency(struct ospf6 *ospf6, + struct ospf6_area *area) +{ + uint16_t type; + struct ospf6_lsa *lsa; + + type = ntohs(OSPF6_LSTYPE_ROUTER); + for (ALL_LSDB_TYPED(area->lsdb, type, lsa)) { + if (!ospf6_gr_check_router_lsa_consistency(ospf6, area, lsa)) { + char reason[256]; + + snprintfrr(reason, sizeof(reason), + "detected inconsistent LSA %s [area %pI4]", + lsa->name, &area->area_id); + ospf6_gr_restart_exit(ospf6, reason); + return; + } + } +} + +/* Check if there's a fully formed adjacency with the given neighbor ID. */ +static bool ospf6_gr_check_adj_id(struct ospf6_area *area, + in_addr_t neighbor_router_id) +{ + struct ospf6_neighbor *nbr; + + nbr = ospf6_area_neighbor_lookup(area, neighbor_router_id); + if (!nbr || nbr->state < OSPF6_NEIGHBOR_FULL) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("GR: missing adjacency to router %pI4", + &neighbor_router_id); + return false; + } + + return true; +} + +static bool ospf6_gr_check_adjs_lsa_transit(struct ospf6_area *area, + in_addr_t neighbor_router_id, + uint32_t neighbor_interface_id) +{ + struct ospf6 *ospf6 = area->ospf6; + + /* Check if we are the DR. */ + if (neighbor_router_id == ospf6->router_id) { + struct ospf6_lsa *lsa; + char *start, *end, *current; + struct ospf6_network_lsa *network_lsa; + struct ospf6_network_lsdesc *lsdesc; + + /* Lookup Network LSA corresponding to this interface. */ + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_NETWORK), + neighbor_interface_id, + neighbor_router_id, area->lsdb); + if (!lsa) + return false; + + /* Iterate over all routers present in the network. */ + network_lsa = (struct ospf6_network_lsa + *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + start = (char *)network_lsa + sizeof(struct ospf6_network_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + for (current = start; + current + sizeof(struct ospf6_network_lsdesc) <= end; + current += sizeof(struct ospf6_network_lsdesc)) { + lsdesc = (struct ospf6_network_lsdesc *)current; + + /* Skip self in the pseudonode. */ + if (lsdesc->router_id == ospf6->router_id) + continue; + + /* + * Check if there's a fully formed adjacency with this + * router. + */ + if (!ospf6_gr_check_adj_id(area, lsdesc->router_id)) + return false; + } + } else { + struct ospf6_neighbor *nbr; + + /* Check if there's a fully formed adjacency with the DR. */ + nbr = ospf6_area_neighbor_lookup(area, neighbor_router_id); + if (!nbr || nbr->state < OSPF6_NEIGHBOR_FULL) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "GR: missing adjacency to DR router %pI4", + &neighbor_router_id); + return false; + } + } + + return true; +} + +static bool ospf6_gr_check_adjs_lsa(struct ospf6_area *area, + struct ospf6_lsa *lsa) +{ + struct ospf6_router_lsa *router_lsa; + char *start, *end, *current; + + router_lsa = + (struct ospf6_router_lsa *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + + /* Iterate over all interfaces in the Router-LSA. */ + start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + for (current = start; + current + sizeof(struct ospf6_router_lsdesc) <= end; + current += sizeof(struct ospf6_router_lsdesc)) { + struct ospf6_router_lsdesc *lsdesc; + + lsdesc = (struct ospf6_router_lsdesc *)current; + switch (lsdesc->type) { + case OSPF6_ROUTER_LSDESC_POINTTOPOINT: + if (!ospf6_gr_check_adj_id(area, + lsdesc->neighbor_router_id)) + return false; + break; + case OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK: + if (!ospf6_gr_check_adjs_lsa_transit( + area, lsdesc->neighbor_router_id, + lsdesc->neighbor_interface_id)) + return false; + break; + default: + break; + } + } + + return true; +} + +/* + * Check if all adjacencies prior to the restart were reestablished. + * + * This is done using pre-restart Router LSAs and pre-restart Network LSAs + * received from the helping neighbors. + */ +static bool ospf6_gr_check_adjs(struct ospf6 *ospf6) +{ + struct ospf6_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, area)) { + uint16_t type; + uint32_t router; + struct ospf6_lsa *lsa_self; + bool found = false; + + type = ntohs(OSPF6_LSTYPE_ROUTER); + router = ospf6->router_id; + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, router, + lsa_self)) { + found = true; + if (!ospf6_gr_check_adjs_lsa(area, lsa_self)) { + ospf6_lsa_unlock(&lsa_self); + return false; + } + } + if (!found) + return false; + } + + return true; +} + +/* Handling of grace period expiry. */ +static void ospf6_gr_grace_period_expired(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + + ospf6_gr_restart_exit(ospf6, "grace period has expired"); +} + +/* Send extra Grace-LSA out the interface (unplanned outages only). */ +void ospf6_gr_iface_send_grace_lsa(struct event *thread) +{ + struct ospf6_interface *oi = EVENT_ARG(thread); + + ospf6_gr_lsa_originate(oi, oi->area->ospf6->gr_info.reason); + + if (++oi->gr.hello_delay.elapsed_seconds < oi->gr.hello_delay.interval) + event_add_timer(master, ospf6_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); + else + event_add_event(master, ospf6_hello_send, oi, 0, + &oi->thread_send_hello); +} + +/* + * Record in non-volatile memory that the given OSPF instance is attempting to + * perform a graceful restart. + */ +static void ospf6_gr_nvm_update(struct ospf6 *ospf6, bool prepare) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_int_add(json_instance, "gracePeriod", + ospf6->gr_info.grace_period); + + /* + * Record not only the grace period, but also a UNIX timestamp + * corresponding to the end of that period. That way, once ospf6d is + * restarted, it will be possible to take into account the time that + * passed while ospf6d wasn't running. + */ + if (prepare) + json_object_int_add(json_instance, "timestamp", + time(NULL) + ospf6->gr_info.grace_period); + + frr_daemon_state_save(&json); +} + +/* + * Delete GR status information about the given OSPF instance from non-volatile + * memory. + */ +void ospf6_gr_nvm_delete(struct ospf6 *ospf6) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_del(json_instances, inst_name); + + frr_daemon_state_save(&json); +} + +/* + * Fetch from non-volatile memory whether the given OSPF instance is performing + * a graceful shutdown or not. + */ +void ospf6_gr_nvm_read(struct ospf6 *ospf6) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + json_object *json_timestamp; + json_object *json_grace_period; + time_t timestamp = 0; + + inst_name = ospf6->name ? ospf6->name : VRF_DEFAULT_NAME; + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_object_get_ex(json_instance, "gracePeriod", + &json_grace_period); + json_object_object_get_ex(json_instance, "timestamp", &json_timestamp); + if (json_timestamp) { + time_t now; + + /* Planned GR: check if the grace period has already expired. */ + now = time(NULL); + timestamp = json_object_get_int(json_timestamp); + if (now > timestamp) { + ospf6_gr_restart_exit( + ospf6, "grace period has expired already"); + } else + ospf6_gr_restart_enter(ospf6, OSPF6_GR_SW_RESTART, + timestamp); + } else if (json_grace_period) { + uint32_t grace_period; + + /* + * Unplanned GR: the Grace-LSAs will be sent later as soon as + * the interfaces are operational. + */ + grace_period = json_object_get_int(json_grace_period); + ospf6->gr_info.grace_period = grace_period; + ospf6_gr_restart_enter(ospf6, OSPF6_GR_UNKNOWN_RESTART, + time(NULL) + + ospf6->gr_info.grace_period); + } + + json_object_object_del(json_instance, "gracePeriod"); + json_object_object_del(json_instance, "timestamp"); + + frr_daemon_state_save(&json); +} + +void ospf6_gr_unplanned_start_interface(struct ospf6_interface *oi) +{ + /* + * Can't check OSPF interface state as the OSPF instance might not be + * enabled yet. + */ + if (!if_is_operative(oi->interface) || if_is_loopback(oi->interface)) + return; + + /* Send Grace-LSA. */ + ospf6_gr_lsa_originate(oi, oi->area->ospf6->gr_info.reason); + + /* Start GR hello-delay interval. */ + oi->gr.hello_delay.elapsed_seconds = 0; + event_add_timer(master, ospf6_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); +} + +/* Prepare to start a Graceful Restart. */ +static void ospf6_gr_prepare(void) +{ + struct ospf6 *ospf6; + struct ospf6_interface *oi; + struct listnode *onode, *anode, *inode; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, onode, ospf6)) { + struct ospf6_area *area; + + if (!ospf6->gr_info.restart_support + || ospf6->gr_info.prepare_in_progress) + continue; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "GR: preparing to perform a graceful restart [period %u second(s)] [vrf %s]", + ospf6->gr_info.grace_period, + ospf6_vrf_id_to_name(ospf6->vrf_id)); + + /* Send a Grace-LSA to all neighbors. */ + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, anode, area)) { + for (ALL_LIST_ELEMENTS_RO(area->if_list, inode, oi)) { + if (oi->state < OSPF6_INTERFACE_POINTTOPOINT) + continue; + ospf6_gr_lsa_originate(oi, OSPF6_GR_SW_RESTART); + } + } + + /* Record end of the grace period in non-volatile memory. */ + ospf6_gr_nvm_update(ospf6, true); + + /* + * Mark that a Graceful Restart preparation is in progress, to + * prevent ospf6d from flushing its self-originated LSAs on + * exit. + */ + ospf6->gr_info.prepare_in_progress = true; + } +} + +static int ospf6_gr_neighbor_change(struct ospf6_neighbor *on, int next_state, + int prev_state) +{ + struct ospf6 *ospf6 = on->ospf6_if->area->ospf6; + + if (next_state == OSPF6_NEIGHBOR_FULL + && ospf6->gr_info.restart_in_progress) { + if (ospf6_gr_check_adjs(ospf6)) { + ospf6_gr_restart_exit( + ospf6, "all adjacencies were reestablished"); + } else { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "GR: not all adjacencies were reestablished yet"); + } + } + + return 0; +} + +int config_write_ospf6_gr(struct vty *vty, struct ospf6 *ospf6) +{ + if (!ospf6->gr_info.restart_support) + return 0; + + if (ospf6->gr_info.grace_period == OSPF6_DFLT_GRACE_INTERVAL) + vty_out(vty, " graceful-restart\n"); + else + vty_out(vty, " graceful-restart grace-period %u\n", + ospf6->gr_info.grace_period); + + return 0; +} + +DEFPY(ospf6_graceful_restart_prepare, ospf6_graceful_restart_prepare_cmd, + "graceful-restart prepare ipv6 ospf", + "Graceful Restart commands\n" + "Prepare upcoming graceful restart\n" IPV6_STR + "Prepare to restart the OSPFv3 process\n") +{ + ospf6_gr_prepare(); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_graceful_restart, ospf6_graceful_restart_cmd, + "graceful-restart [grace-period (1-1800)$grace_period]", + OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf6, ospf6); + + /* Check and get restart period if present. */ + if (!grace_period_str) + grace_period = OSPF6_DFLT_GRACE_INTERVAL; + + ospf6->gr_info.restart_support = true; + ospf6->gr_info.grace_period = grace_period; + + /* Freeze OSPF routes in the RIB. */ + (void)ospf6_zebra_gr_enable(ospf6, ospf6->gr_info.grace_period); + + /* Record that GR is enabled in non-volatile memory. */ + ospf6_gr_nvm_update(ospf6, false); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_no_graceful_restart, ospf6_no_graceful_restart_cmd, + "no graceful-restart [period (1-1800)]", + NO_STR OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf6, ospf6); + + if (!ospf6->gr_info.restart_support) + return CMD_SUCCESS; + + if (ospf6->gr_info.prepare_in_progress) { + vty_out(vty, + "%% Error: Graceful Restart preparation in progress\n"); + return CMD_WARNING; + } + + ospf6->gr_info.restart_support = false; + ospf6->gr_info.grace_period = OSPF6_DFLT_GRACE_INTERVAL; + ospf6_gr_nvm_delete(ospf6); + ospf6_zebra_gr_disable(ospf6); + + return CMD_SUCCESS; +} + +void ospf6_gr_init(void) +{ + hook_register(ospf6_neighbor_change, ospf6_gr_neighbor_change); + + install_element(ENABLE_NODE, &ospf6_graceful_restart_prepare_cmd); + install_element(OSPF6_NODE, &ospf6_graceful_restart_cmd); + install_element(OSPF6_NODE, &ospf6_no_graceful_restart_cmd); +} diff --git a/ospf6d/ospf6_gr.h b/ospf6d/ospf6_gr.h new file mode 100644 index 0000000..84ef3ae --- /dev/null +++ b/ospf6d/ospf6_gr.h @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF6 Graceful Retsart helper functions. + * + * Copyright (C) 2021-22 Vmware, Inc. + * Rajesh Kumar Girada + */ + +#ifndef OSPF6_GR_H +#define OSPF6_GR_H + +#define OSPF6_GR_NOT_HELPER 0 +#define OSPF6_GR_ACTIVE_HELPER 1 + +#define OSPF6_GR_HELPER_NO_LSACHECK 0 +#define OSPF6_GR_HELPER_LSACHECK 1 + +#define OSPF6_MAX_GRACE_INTERVAL 1800 +#define OSPF6_MIN_GRACE_INTERVAL 1 +#define OSPF6_DFLT_GRACE_INTERVAL 120 + +/* Forward declaration(s). */ +struct ospf6_neighbor; + +/* Debug option */ +extern unsigned char conf_debug_ospf6_gr; + +#define OSPF6_DEBUG_GR 0x01 + +#define OSPF6_DEBUG_GR_ON() (conf_debug_ospf6_gr |= OSPF6_DEBUG_GR) + +#define OSPF6_DEBUG_GR_OFF() (conf_debug_ospf6_gr &= ~OSPF6_DEBUG_GR) + +#define IS_DEBUG_OSPF6_GR conf_debug_ospf6_gr + + +enum ospf6_helper_exit_reason { + OSPF6_GR_HELPER_EXIT_NONE = 0, + OSPF6_GR_HELPER_INPROGRESS, + OSPF6_GR_HELPER_TOPO_CHG, + OSPF6_GR_HELPER_GRACE_TIMEOUT, + OSPF6_GR_HELPER_COMPLETED +}; + +enum ospf6_gr_restart_reason { + OSPF6_GR_UNKNOWN_RESTART = 0, + OSPF6_GR_SW_RESTART = 1, + OSPF6_GR_SW_UPGRADE = 2, + OSPF6_GR_SWITCH_REDUNDANT_CARD = 3, + OSPF6_GR_INVALID_REASON_CODE = 4 +}; + +enum ospf6_gr_helper_rejected_reason { + OSPF6_HELPER_REJECTED_NONE, + OSPF6_HELPER_SUPPORT_DISABLED, + OSPF6_HELPER_NOT_A_VALID_NEIGHBOUR, + OSPF6_HELPER_PLANNED_ONLY_RESTART, + OSPF6_HELPER_TOPO_CHANGE_RTXMT_LIST, + OSPF6_HELPER_LSA_AGE_MORE, + OSPF6_HELPER_RESTARTING, +}; + +#ifdef roundup +#define ROUNDUP(val, gran) roundup(val, gran) +#else /* roundup */ +#define ROUNDUP(val, gran) (((val)-1 | (gran)-1) + 1) +#endif /* roundup */ + +/* + * Generic TLV (type, length, value) macros + */ +struct tlv_header { + uint16_t type; /* Type of Value */ + uint16_t length; /* Length of Value portion only, in bytes */ +}; + +#define TLV_HDR_SIZE (sizeof(struct tlv_header)) + +#define TLV_BODY_SIZE(tlvh) (ROUNDUP(ntohs((tlvh)->length), sizeof(uint32_t))) + +#define TLV_SIZE(tlvh) (uint32_t)(TLV_HDR_SIZE + TLV_BODY_SIZE(tlvh)) + +#define TLV_HDR_TOP(lsah) \ + (struct tlv_header *)((char *)(lsah) + OSPF6_LSA_HEADER_SIZE) + +#define TLV_HDR_NEXT(tlvh) \ + (struct tlv_header *)((char *)(tlvh) + TLV_SIZE(tlvh)) + +/* Ref RFC5187 appendix-A */ +/* Grace period TLV */ +#define GRACE_PERIOD_TYPE 1 +#define GRACE_PERIOD_LENGTH 4 +struct grace_tlv_graceperiod { + struct tlv_header header; + uint32_t interval; +}; +#define GRACE_PERIOD_TLV_SIZE sizeof(struct grace_tlv_graceperiod) + +/* Restart reason TLV */ +#define RESTART_REASON_TYPE 2 +#define RESTART_REASON_LENGTH 1 +struct grace_tlv_restart_reason { + struct tlv_header header; + uint8_t reason; + uint8_t reserved[3]; +}; +#define GRACE_RESTART_REASON_TLV_SIZE sizeof(struct grace_tlv_restart_reason) + +#define OSPF6_GRACE_LSA_MIN_SIZE \ + GRACE_PERIOD_TLV_SIZE + GRACE_RESTART_REASON_TLV_SIZE + +struct ospf6_grace_lsa { + struct grace_tlv_graceperiod tlv_period; + struct grace_tlv_restart_reason tlv_reason; +}; + +struct advRtr { + in_addr_t advRtrAddr; +}; + +#define OSPF6_HELPER_ENABLE_RTR_COUNT(ospf) \ + (ospf6->ospf6_helper_cfg.enable_rtr_list->count) + +/* Check , it is a planned restart */ +#define OSPF6_GR_IS_PLANNED_RESTART(reason) \ + ((reason == OSPF6_GR_SW_RESTART) || (reason == OSPF6_GR_SW_UPGRADE)) + +/* Check the router is HELPER for current neighbour */ +#define OSPF6_GR_IS_ACTIVE_HELPER(N) \ + ((N)->gr_helper_info.gr_helper_status == OSPF6_GR_ACTIVE_HELPER) + +/* Check the LSA is GRACE LSA */ +#define IS_GRACE_LSA(lsa) (ntohs(lsa->header->type) == OSPF6_LSTYPE_GRACE_LSA) + +/* Check neighbour is in FULL state */ +#define IS_NBR_STATE_FULL(nbr) (nbr->state == OSPF6_NEIGHBOR_FULL) + +extern const char *ospf6_exit_reason_desc[]; +extern const char *ospf6_restart_reason_desc[]; +extern const char *ospf6_rejected_reason_desc[]; + +extern void ospf6_gr_helper_config_init(void); +extern void ospf6_gr_helper_init(struct ospf6 *ospf6); +extern void ospf6_gr_helper_deinit(struct ospf6 *ospf6); +extern void ospf6_gr_helper_exit(struct ospf6_neighbor *nbr, + enum ospf6_helper_exit_reason reason); +extern int ospf6_process_grace_lsa(struct ospf6 *ospf6, struct ospf6_lsa *lsa, + struct ospf6_neighbor *nbr); +extern void ospf6_process_maxage_grace_lsa(struct ospf6 *ospf, + struct ospf6_lsa *lsa, + struct ospf6_neighbor *nbr); +extern void ospf6_helper_handle_topo_chg(struct ospf6 *ospf6, + struct ospf6_lsa *lsa); +extern int config_write_ospf6_gr(struct vty *vty, struct ospf6 *ospf6); +extern int config_write_ospf6_gr_helper(struct vty *vty, struct ospf6 *ospf6); +extern int config_write_ospf6_debug_gr_helper(struct vty *vty); + +extern void ospf6_gr_iface_send_grace_lsa(struct event *thread); +extern void ospf6_gr_restart_enter(struct ospf6 *ospf6, + enum ospf6_gr_restart_reason reason, + time_t timestamp); +extern void ospf6_gr_check_lsdb_consistency(struct ospf6 *ospf, + struct ospf6_area *area); +extern void ospf6_gr_nvm_read(struct ospf6 *ospf); +extern void ospf6_gr_nvm_delete(struct ospf6 *ospf6); +extern void ospf6_gr_unplanned_start_interface(struct ospf6_interface *oi); +extern void ospf6_gr_init(void); + +#endif /* OSPF6_GR_H */ diff --git a/ospf6d/ospf6_gr_helper.c b/ospf6d/ospf6_gr_helper.c new file mode 100644 index 0000000..f0e5d3a --- /dev/null +++ b/ospf6d/ospf6_gr_helper.c @@ -0,0 +1,1408 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF6 Graceful Restart helper functions. + * + * Copyright (C) 2021-22 Vmware, Inc. + * Rajesh Kumar Girada + */ + +#include + +#include "log.h" +#include "vty.h" +#include "command.h" +#include "prefix.h" +#include "stream.h" +#include "zclient.h" +#include "memory.h" +#include "table.h" +#include "lib/bfd.h" +#include "lib_errors.h" +#include "jhash.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_message.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_intra.h" +#include "ospf6d.h" +#include "ospf6_gr.h" +#include "lib/json.h" +#include "ospf6d/ospf6_gr_helper_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_GR_HELPER, "OSPF6 Graceful restart helper"); + +unsigned char conf_debug_ospf6_gr; + +static int ospf6_grace_lsa_show_info(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json); + +struct ospf6_lsa_handler grace_lsa_handler = {.lh_type = OSPF6_LSTYPE_GRACE_LSA, + .lh_name = "Grace", + .lh_short_name = "GR", + .lh_show = + ospf6_grace_lsa_show_info, + .lh_get_prefix_str = NULL, + .lh_debug = 0}; + +const char *ospf6_exit_reason_desc[] = { + "Unknown reason", + "Helper in progress", + "Topology Change", + "Grace timer expiry", + "Successful graceful restart", +}; + +const char *ospf6_restart_reason_desc[] = { + "Unknown restart", + "Software restart", + "Software reload/upgrade", + "Switch to redundant control processor", +}; + +const char *ospf6_rejected_reason_desc[] = { + "Unknown reason", + "Helper support disabled", + "Neighbour is not in FULL state", + "Supports only planned restart but received for unplanned", + "Topo change due to change in lsa rxmt list", + "LSA age is more than Grace interval", +}; + +static unsigned int ospf6_enable_rtr_hash_key(const void *data) +{ + const struct advRtr *rtr = data; + + return jhash_1word(rtr->advRtrAddr, 0); +} + +static bool ospf6_enable_rtr_hash_cmp(const void *d1, const void *d2) +{ + const struct advRtr *rtr1 = d1; + const struct advRtr *rtr2 = d2; + + return (rtr1->advRtrAddr == rtr2->advRtrAddr); +} + +static void *ospf6_enable_rtr_hash_alloc(void *p) +{ + struct advRtr *rid; + + rid = XCALLOC(MTYPE_OSPF6_GR_HELPER, sizeof(struct advRtr)); + rid->advRtrAddr = ((struct advRtr *)p)->advRtrAddr; + + return rid; +} + +static void ospf6_disable_rtr_hash_free(void *rtr) +{ + XFREE(MTYPE_OSPF6_GR_HELPER, rtr); +} + +static void ospf6_enable_rtr_hash_destroy(struct ospf6 *ospf6) +{ + if (ospf6->ospf6_helper_cfg.enable_rtr_list == NULL) + return; + + hash_clean_and_free(&ospf6->ospf6_helper_cfg.enable_rtr_list, + ospf6_disable_rtr_hash_free); +} + +/* + * Extracting tlv info from GRACE LSA. + * + * lsa + * ospf6 grace lsa + * + * Returns: + * interval : grace interval. + * reason : Restarting reason. + */ +static int ospf6_extract_grace_lsa_fields(struct ospf6_lsa *lsa, + uint32_t *interval, uint8_t *reason) +{ + struct ospf6_lsa_header *lsah = NULL; + struct tlv_header *tlvh = NULL; + struct grace_tlv_graceperiod *gracePeriod; + struct grace_tlv_restart_reason *grReason; + uint16_t length = 0; + int sum = 0; + + lsah = lsa->header; + if (ntohs(lsah->length) <= OSPF6_LSA_HEADER_SIZE) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s: undersized (%u B) lsa", __func__, + ntohs(lsah->length)); + return OSPF6_FAILURE; + } + + length = ntohs(lsah->length) - OSPF6_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + + /* Check TLV len against overall LSA */ + if (sum + TLV_SIZE(tlvh) > length) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s: Malformed packet: Invalid TLV len:%d", + __func__, TLV_SIZE(tlvh)); + return OSPF6_FAILURE; + } + + switch (ntohs(tlvh->type)) { + case GRACE_PERIOD_TYPE: + gracePeriod = (struct grace_tlv_graceperiod *)tlvh; + *interval = ntohl(gracePeriod->interval); + sum += TLV_SIZE(tlvh); + + /* Check if grace interval is valid */ + if (*interval > OSPF6_MAX_GRACE_INTERVAL + || *interval < OSPF6_MIN_GRACE_INTERVAL) + return OSPF6_FAILURE; + break; + case RESTART_REASON_TYPE: + grReason = (struct grace_tlv_restart_reason *)tlvh; + *reason = grReason->reason; + sum += TLV_SIZE(tlvh); + + if (*reason >= OSPF6_GR_INVALID_REASON_CODE) + return OSPF6_FAILURE; + break; + default: + sum += TLV_SIZE(tlvh); + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Ignoring unknown TLV type:%d", + __func__, ntohs(tlvh->type)); + } + } + + return OSPF6_SUCCESS; +} + +/* + * Grace timer expiry handler. + * HELPER aborts its role at grace timer expiry. + * + * thread + * thread pointer + * + * Returns: + * Nothing + */ +static void ospf6_handle_grace_timer_expiry(struct event *thread) +{ + struct ospf6_neighbor *nbr = EVENT_ARG(thread); + + ospf6_gr_helper_exit(nbr, OSPF6_GR_HELPER_GRACE_TIMEOUT); +} + +/* + * API to check any change in the neighbor's + * retransmission list. + * + * nbr + * ospf6 neighbor + * + * Returns: + * TRUE - if any change in the lsa. + * FALSE - no change in the lsas. + */ +static bool ospf6_check_chg_in_rxmt_list(struct ospf6_neighbor *nbr) +{ + struct ospf6_lsa *lsa, *lsanext; + + for (ALL_LSDB(nbr->retrans_list, lsa, lsanext)) { + struct ospf6_lsa *lsa_in_db = NULL; + + /* Fetching the same copy of LSA form LSDB to validate the + * topochange. + */ + lsa_in_db = + ospf6_lsdb_lookup(lsa->header->type, lsa->header->id, + lsa->header->adv_router, lsa->lsdb); + + if (lsa_in_db && lsa_in_db->tobe_acknowledged) { + ospf6_lsa_unlock(&lsa); + if (lsanext) + ospf6_lsa_unlock(&lsanext); + + return OSPF6_TRUE; + } + } + + return OSPF6_FALSE; +} + +/* + * Process Grace LSA.If it is eligible move to HELPER role. + * Ref rfc3623 section 3.1 and rfc5187 + * + * ospf + * Ospf6 pointer. + * + * lsa + * Grace LSA received from RESTARTER. + * + * restarter + * ospf6 neighbour which requests the router to act as + * HELPER. + * + * Returns: + * status. + * If supported as HELPER : OSPF_GR_HELPER_INPROGRESS + * If Not supported as HELPER : OSPF_GR_HELPER_NONE + */ +int ospf6_process_grace_lsa(struct ospf6 *ospf6, struct ospf6_lsa *lsa, + struct ospf6_neighbor *restarter) +{ + uint8_t restart_reason = 0; + uint32_t grace_interval = 0; + uint32_t actual_grace_interval = 0; + struct advRtr lookup; + int ret; + + /* Extract the grace lsa packet fields */ + ret = ospf6_extract_grace_lsa_fields(lsa, &grace_interval, + &restart_reason); + if (ret != OSPF6_SUCCESS) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Wrong Grace LSA packet.", __func__); + return OSPF6_GR_NOT_HELPER; + } + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Grace LSA received from %s(%pI4), grace interval:%u, restart reason:%s", + __func__, restarter->name, &restarter->router_id, + grace_interval, + ospf6_restart_reason_desc[restart_reason]); + + /* Verify Helper enabled globally */ + if (!ospf6->ospf6_helper_cfg.is_helper_supported) { + /* Verify Helper support is enabled for the + * current neighbour router-id. + */ + lookup.advRtrAddr = restarter->router_id; + + if (!hash_lookup(ospf6->ospf6_helper_cfg.enable_rtr_list, + &lookup)) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, HELPER support is disabled, So not a HELPER", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_SUPPORT_DISABLED; + return OSPF6_GR_NOT_HELPER; + } + } + + /* Check neighbour is in FULL state and + * became a adjacency. + */ + if (!IS_NBR_STATE_FULL(restarter)) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, This Neighbour %pI6 is not in FULL state.", + __func__, &restarter->linklocal_addr); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_NOT_A_VALID_NEIGHBOUR; + return OSPF6_GR_NOT_HELPER; + } + + /* Based on the restart reason from grace lsa + * check the current router is supporting or not + */ + if (ospf6->ospf6_helper_cfg.only_planned_restart + && !OSPF6_GR_IS_PLANNED_RESTART(restart_reason)) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Router supports only planned restarts but received the GRACE LSA due to an unplanned restart", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_PLANNED_ONLY_RESTART; + return OSPF6_GR_NOT_HELPER; + } + + /* Check the retransmission list of this + * neighbour, check any change in lsas. + */ + if (ospf6->ospf6_helper_cfg.strict_lsa_check + && restarter->retrans_list->count + && ospf6_check_chg_in_rxmt_list(restarter)) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Changed LSA in Rxmt list.So not Helper.", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_TOPO_CHANGE_RTXMT_LIST; + return OSPF6_GR_NOT_HELPER; + } + + /* LSA age must be less than the grace period */ + if (ntohs(lsa->header->age) >= grace_interval) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Grace LSA age(%d) is more than the grace interval(%d)", + __func__, lsa->header->age, grace_interval); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_LSA_AGE_MORE; + return OSPF6_GR_NOT_HELPER; + } + + if (ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s: router is in the process of graceful restart", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF6_HELPER_RESTARTING; + return OSPF6_GR_NOT_HELPER; + } + + /* check supported grace period configured + * if configured, use this to start the grace + * timer otherwise use the interval received + * in grace LSA packet. + */ + actual_grace_interval = grace_interval; + if (grace_interval > ospf6->ospf6_helper_cfg.supported_grace_time) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Received grace period %d is larger than supported grace %d", + __func__, grace_interval, + ospf6->ospf6_helper_cfg.supported_grace_time); + actual_grace_interval = + ospf6->ospf6_helper_cfg.supported_grace_time; + } + + if (OSPF6_GR_IS_ACTIVE_HELPER(restarter)) { + EVENT_OFF(restarter->gr_helper_info.t_grace_timer); + + if (ospf6->ospf6_helper_cfg.active_restarter_cnt > 0) + ospf6->ospf6_helper_cfg.active_restarter_cnt--; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Router is already acting as a HELPER for this nbr,so restart the grace timer", + __func__); + } else { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, This Router becomes a HELPER for the neighbour %pI6", + __func__, &restarter->linklocal_addr); + } + + /* Became a Helper to the RESTART neighbour. + * change the helper status. + */ + restarter->gr_helper_info.gr_helper_status = OSPF6_GR_ACTIVE_HELPER; + restarter->gr_helper_info.recvd_grace_period = grace_interval; + restarter->gr_helper_info.actual_grace_period = actual_grace_interval; + restarter->gr_helper_info.gr_restart_reason = restart_reason; + restarter->gr_helper_info.rejected_reason = OSPF6_HELPER_REJECTED_NONE; + + /* Increment the active restart nbr count */ + ospf6->ospf6_helper_cfg.active_restarter_cnt++; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Grace timer started.interval:%u", __func__, + actual_grace_interval); + + /* Start the grace timer */ + event_add_timer(master, ospf6_handle_grace_timer_expiry, restarter, + actual_grace_interval, + &restarter->gr_helper_info.t_grace_timer); + + return OSPF6_GR_ACTIVE_HELPER; +} + +/* + * Api to exit from HELPER role to take all actions + * required at exit. + * Ref rfc3623 section 3. and rfc51872 + * + * ospf6 + * Ospf6 pointer. + * + * nbr + * Ospf6 neighbour for which it is acting as HELPER. + * + * reason + * The reason for exiting from HELPER. + * + * Returns: + * Nothing. + */ +void ospf6_gr_helper_exit(struct ospf6_neighbor *nbr, + enum ospf6_helper_exit_reason reason) +{ + struct ospf6_interface *oi = nbr->ospf6_if; + struct ospf6 *ospf6; + + if (!oi) + return; + + ospf6 = oi->area->ospf6; + + if (!OSPF6_GR_IS_ACTIVE_HELPER(nbr)) + return; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Exiting from HELPER support to %pI6, due to %s", + __func__, &nbr->linklocal_addr, + ospf6_exit_reason_desc[reason]); + + /* Reset helper status*/ + nbr->gr_helper_info.gr_helper_status = OSPF6_GR_NOT_HELPER; + nbr->gr_helper_info.helper_exit_reason = reason; + nbr->gr_helper_info.actual_grace_period = 0; + nbr->gr_helper_info.recvd_grace_period = 0; + nbr->gr_helper_info.gr_restart_reason = 0; + ospf6->ospf6_helper_cfg.last_exit_reason = reason; + + /* If the exit not triggered due to grace timer + * expiry, stop the grace timer. + */ + if (reason != OSPF6_GR_HELPER_GRACE_TIMEOUT) + EVENT_OFF(nbr->gr_helper_info.t_grace_timer); + + if (ospf6->ospf6_helper_cfg.active_restarter_cnt <= 0) { + zlog_err( + "OSPF6 GR-Helper: Number of active Restarters should be greater than zero."); + return; + } + /* Decrement active restarter count */ + ospf6->ospf6_helper_cfg.active_restarter_cnt--; + + /* check exit triggered due to successful completion + * of graceful restart. + */ + if (reason != OSPF6_GR_HELPER_COMPLETED) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Unsuccessful GR exit. RESTARTER : %pI6", + __func__, &nbr->linklocal_addr); + } + + /*Recalculate the DR for the network segment */ + dr_election(oi); + + /* Originate a router LSA */ + OSPF6_ROUTER_LSA_SCHEDULE(nbr->ospf6_if->area); + + /* Originate network lsa if it is an DR in the LAN */ + if (nbr->ospf6_if->state == OSPF6_INTERFACE_DR) + OSPF6_NETWORK_LSA_SCHEDULE(nbr->ospf6_if); +} + +/* + * Process max age Grace LSA. + * It is a indication for successful completion of GR. + * If router acting as HELPER, It exits from helper role. + * + * ospf6 + * Ospf6 pointer. + * + * lsa + * Grace LSA received from RESTARTER. + * + * nbr + * ospf6 neighbour which request the router to act as + * HELPER. + * + * Returns: + * Nothing. + */ +void ospf6_process_maxage_grace_lsa(struct ospf6 *ospf6, struct ospf6_lsa *lsa, + struct ospf6_neighbor *restarter) +{ + uint8_t restart_reason = 0; + uint32_t grace_interval = 0; + int ret; + + /* Extract the grace lsa packet fields */ + ret = ospf6_extract_grace_lsa_fields(lsa, &grace_interval, + &restart_reason); + if (ret != OSPF6_SUCCESS) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Wrong Grace LSA packet.", __func__); + return; + } + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, GraceLSA received for neighbour %pI4.", + __func__, &restarter->router_id); + + ospf6_gr_helper_exit(restarter, OSPF6_GR_HELPER_COMPLETED); +} + +/* + * Actions to be taken when topo change detected + * HELPER will be exited upon a topo change. + * + * ospf6 + * ospf6 pointer + * lsa + * topo change occurred due to this lsa(type (1-5 and 7) + * + * Returns: + * Nothing + */ +void ospf6_helper_handle_topo_chg(struct ospf6 *ospf6, struct ospf6_lsa *lsa) +{ + struct listnode *i, *j, *k; + struct ospf6_neighbor *nbr = NULL; + struct ospf6_area *oa = NULL; + struct ospf6_interface *oi = NULL; + + if (!ospf6->ospf6_helper_cfg.active_restarter_cnt) + return; + + /* Topo change not required to be handled if strict + * LSA check is disabled for this router. + */ + if (!ospf6->ospf6_helper_cfg.strict_lsa_check) + return; + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Topo change detected due to lsa details : %s", + __func__, lsa->name); + + lsa->tobe_acknowledged = OSPF6_TRUE; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + + /* Ref rfc3623 section 3.2.3.b and rfc5187 + * If change due to external LSA and if the area is + * stub, then it is not a topo change. Since Type-5 + * lsas will not be flooded in stub area. + */ + if (IS_AREA_STUB(oi->area) + && ((lsa->header->type == OSPF6_LSTYPE_AS_EXTERNAL) + || (lsa->header->type == OSPF6_LSTYPE_TYPE_7) + || (lsa->header->type + == OSPF6_LSTYPE_INTER_ROUTER))) { + continue; + } + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, nbr)) { + + ospf6_gr_helper_exit(nbr, + OSPF6_GR_HELPER_TOPO_CHG); + } + } +} + +/* Configuration handlers */ +/* + * Disable/Enable HELPER support on router level. + * + * ospf6 + * Ospf6 pointer. + * + * status + * TRUE/FALSE + * + * Returns: + * Nothing. + */ +static void ospf6_gr_helper_support_set(struct ospf6 *ospf6, bool support) +{ + struct ospf6_interface *oi; + struct advRtr lookup; + struct listnode *i, *j, *k; + struct ospf6_neighbor *nbr = NULL; + struct ospf6_area *oa = NULL; + + if (ospf6->ospf6_helper_cfg.is_helper_supported == support) + return; + + ospf6->ospf6_helper_cfg.is_helper_supported = support; + + /* If helper support disabled, cease HELPER role for all + * supporting neighbors. + */ + if (support == OSPF6_FALSE) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, + nbr)) { + + lookup.advRtrAddr = nbr->router_id; + /* check if helper support enabled for + * the corresponding routerid. + * If enabled, + * dont exit from helper role. + */ + if (hash_lookup( + ospf6->ospf6_helper_cfg + .enable_rtr_list, + &lookup)) + continue; + + ospf6_gr_helper_exit( + nbr, OSPF6_GR_HELPER_TOPO_CHG); + } + } + } +} + +/* + * Api to enable/disable strict lsa check on the HELPER. + * + * ospf6 + * Ospf6 pointer. + * + * enabled + * True - disable the lsa check. + * False - enable the strict lsa check. + * + * Returns: + * Nothing. + */ +static void ospf6_gr_helper_lsacheck_set(struct ospf6 *ospf6, bool enabled) +{ + if (ospf6->ospf6_helper_cfg.strict_lsa_check == enabled) + return; + + ospf6->ospf6_helper_cfg.strict_lsa_check = enabled; +} + +/* + * Api to set the supported restart reason. + * + * ospf6 + * Ospf6 pointer. + * + * only_planned + * True: support only planned restart. + * False: support for planned/unplanned restarts. + * + * Returns: + * Nothing. + */ + +static void +ospf6_gr_helper_set_supported_onlyPlanned_restart(struct ospf6 *ospf6, + bool only_planned) +{ + ospf6->ospf6_helper_cfg.only_planned_restart = only_planned; +} + +/* + * Api to set the supported grace interval in this router. + * + * ospf6 + * Ospf6 pointer. + * + * interval + * The supported grace interval.. + * + * Returns: + * Nothing. + */ +static void ospf6_gr_helper_supported_gracetime_set(struct ospf6 *ospf6, + uint32_t interval) +{ + ospf6->ospf6_helper_cfg.supported_grace_time = interval; +} + +/* API to walk and print all the Helper supported router ids */ +static int ospf6_print_vty_helper_dis_rtr_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct advRtr *rtr = bucket->data; + struct vty *vty = (struct vty *)arg; + static unsigned int count; + + vty_out(vty, "%-6pI4,", &rtr->advRtrAddr); + count++; + + if (count % 5 == 0) + vty_out(vty, "\n"); + + return HASHWALK_CONTINUE; +} + +/* API to walk and print all the Helper supported router ids.*/ +static int ospf6_print_json_helper_dis_rtr_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct advRtr *rtr = bucket->data; + struct json_object *json_rid_array = (struct json_object *)arg; + struct json_object *json_rid; + char router_id[16]; + + inet_ntop(AF_INET, &rtr->advRtrAddr, router_id, sizeof(router_id)); + + json_rid = json_object_new_object(); + + json_object_string_add(json_rid, "routerId", router_id); + json_object_array_add(json_rid_array, json_rid); + + return HASHWALK_CONTINUE; +} + +/* + * Enable/Disable HELPER support on a specified advertisement + * router. + * + * ospf6 + * Ospf6 pointer. + * + * advRtr + * HELPER support for given Advertisement Router. + * + * support + * True - Enable Helper Support. + * False - Disable Helper Support. + * + * Returns: + * Nothing. + */ +static void ospf6_gr_helper_support_set_per_routerid(struct ospf6 *ospf6, + struct in_addr router_id, + bool support) +{ + struct advRtr temp; + struct advRtr *rtr; + struct listnode *i, *j, *k; + struct ospf6_interface *oi; + struct ospf6_neighbor *nbr; + struct ospf6_area *oa; + + temp.advRtrAddr = router_id.s_addr; + + if (support == OSPF6_FALSE) { + /*Delete the routerid from the enable router hash table */ + rtr = hash_lookup(ospf6->ospf6_helper_cfg.enable_rtr_list, + &temp); + + if (rtr) { + hash_release(ospf6->ospf6_helper_cfg.enable_rtr_list, + rtr); + ospf6_disable_rtr_hash_free(rtr); + } + + /* If helper support is enabled globally + * no action is required. + */ + if (ospf6->ospf6_helper_cfg.is_helper_supported) + return; + + /* Cease the HELPER role fore neighbours from the + * specified advertisement router. + */ + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, + nbr)) { + + if (nbr->router_id != router_id.s_addr) + continue; + + if (OSPF6_GR_IS_ACTIVE_HELPER(nbr)) + ospf6_gr_helper_exit( + nbr, + OSPF6_GR_HELPER_TOPO_CHG); + } + } + + } else { + /* Add the routerid to the enable router hash table */ + (void)hash_get(ospf6->ospf6_helper_cfg.enable_rtr_list, &temp, + ospf6_enable_rtr_hash_alloc); + } +} + +static void show_ospfv6_gr_helper_per_nbr(struct vty *vty, json_object *json, + bool uj, struct ospf6_neighbor *nbr) +{ + if (!uj) { + vty_out(vty, " Routerid : %pI4\n", &nbr->router_id); + vty_out(vty, " Received Grace period : %d(in seconds).\n", + nbr->gr_helper_info.recvd_grace_period); + vty_out(vty, " Actual Grace period : %d(in seconds)\n", + nbr->gr_helper_info.actual_grace_period); + vty_out(vty, " Remaining GraceTime:%ld(in seconds).\n", + event_timer_remain_second( + nbr->gr_helper_info.t_grace_timer)); + vty_out(vty, " Graceful Restart reason: %s.\n\n", + ospf6_restart_reason_desc[nbr->gr_helper_info + .gr_restart_reason]); + } else { + char nbrid[16]; + json_object *json_neigh = NULL; + + inet_ntop(AF_INET, &nbr->router_id, nbrid, sizeof(nbrid)); + json_neigh = json_object_new_object(); + json_object_string_add(json_neigh, "routerid", nbrid); + json_object_int_add(json_neigh, "recvdGraceInterval", + nbr->gr_helper_info.recvd_grace_period); + json_object_int_add(json_neigh, "actualGraceInterval", + nbr->gr_helper_info.actual_grace_period); + json_object_int_add(json_neigh, "remainGracetime", + event_timer_remain_second( + nbr->gr_helper_info.t_grace_timer)); + json_object_string_add(json_neigh, "restartReason", + ospf6_restart_reason_desc[ + nbr->gr_helper_info.gr_restart_reason]); + json_object_object_add(json, nbr->name, json_neigh); + } +} + +static void show_ospf6_gr_helper_details(struct vty *vty, struct ospf6 *ospf6, + json_object *json, bool uj, bool detail) +{ + struct ospf6_interface *oi; + + /* Show Router ID. */ + if (uj) { + char router_id[16]; + + inet_ntop(AF_INET, &ospf6->router_id, router_id, + sizeof(router_id)); + json_object_string_add(json, "routerId", router_id); + } else + vty_out(vty, + " OSPFv3 Routing Process (0) with Router-ID %pI4\n", + &ospf6->router_id); + + if (!uj) { + + if (ospf6->ospf6_helper_cfg.is_helper_supported) + vty_out(vty, + " Graceful restart helper support enabled.\n"); + else + vty_out(vty, + " Graceful restart helper support disabled.\n"); + + if (ospf6->ospf6_helper_cfg.strict_lsa_check) + vty_out(vty, " Strict LSA check is enabled.\n"); + else + vty_out(vty, " Strict LSA check is disabled.\n"); + + if (ospf6->ospf6_helper_cfg.only_planned_restart) + vty_out(vty, + " Helper supported for planned restarts only.\n"); + else + vty_out(vty, + " Helper supported for Planned and Unplanned Restarts.\n"); + + vty_out(vty, + " Supported Graceful restart interval: %d(in seconds).\n", + ospf6->ospf6_helper_cfg.supported_grace_time); + + if (OSPF6_HELPER_ENABLE_RTR_COUNT(ospf)) { + vty_out(vty, " Enable Router list:\n"); + vty_out(vty, " "); + hash_walk(ospf6->ospf6_helper_cfg.enable_rtr_list, + ospf6_print_vty_helper_dis_rtr_walkcb, vty); + vty_out(vty, "\n\n"); + } + + if (ospf6->ospf6_helper_cfg.last_exit_reason + != OSPF6_GR_HELPER_EXIT_NONE) { + vty_out(vty, " Last Helper exit Reason :%s\n", + ospf6_exit_reason_desc + [ospf6->ospf6_helper_cfg + .last_exit_reason]); + + if (ospf6->ospf6_helper_cfg.active_restarter_cnt) + vty_out(vty, + " Number of Active neighbours in graceful restart: %d\n", + ospf6->ospf6_helper_cfg + .active_restarter_cnt); + else + vty_out(vty, "\n"); + } + + + } else { + json_object_string_add( + json, "helperSupport", + (ospf6->ospf6_helper_cfg.is_helper_supported) + ? "Enabled" + : "Disabled"); + json_object_string_add( + json, "strictLsaCheck", + (ospf6->ospf6_helper_cfg.strict_lsa_check) + ? "Enabled" + : "Disabled"); + + json_object_string_add( + json, "restartSupport", + (ospf6->ospf6_helper_cfg.only_planned_restart) + ? "Planned Restart only" + : "Planned and Unplanned Restarts"); + + json_object_int_add( + json, "supportedGracePeriod", + ospf6->ospf6_helper_cfg.supported_grace_time); + + if (ospf6->ospf6_helper_cfg.last_exit_reason != + OSPF6_GR_HELPER_EXIT_NONE) + json_object_string_add( + json, "lastExitReason", + ospf6_exit_reason_desc + [ospf6->ospf6_helper_cfg + .last_exit_reason]); + + if (ospf6->ospf6_helper_cfg.active_restarter_cnt) + json_object_int_add( + json, "activeRestarterCnt", + ospf6->ospf6_helper_cfg.active_restarter_cnt); + + if (OSPF6_HELPER_ENABLE_RTR_COUNT(ospf6)) { + struct json_object *json_rid_array = + json_object_new_array(); + + json_object_object_add(json, "enabledRouterIds", + json_rid_array); + + hash_walk(ospf6->ospf6_helper_cfg.enable_rtr_list, + ospf6_print_json_helper_dis_rtr_walkcb, + json_rid_array); + } + } + + if (detail) { + int cnt = 1; + struct listnode *i, *j, *k; + struct ospf6_area *oa; + json_object *json_neighbors = NULL; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + struct ospf6_neighbor *nbr; + + if (uj) { + json_object_object_get_ex( + json, "neighbors", + &json_neighbors); + if (!json_neighbors) { + json_neighbors = + json_object_new_object(); + json_object_object_add( + json, "neighbors", + json_neighbors); + } + } + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, + nbr)) { + + if (!OSPF6_GR_IS_ACTIVE_HELPER(nbr)) + continue; + + if (!uj) + vty_out(vty, + " Neighbour %d :\n", + cnt++); + + show_ospfv6_gr_helper_per_nbr( + vty, json_neighbors, uj, nbr); + + } + } + } +} + +/* Graceful Restart HELPER config Commands */ +DEFPY(ospf6_gr_helper_enable, + ospf6_gr_helper_enable_cmd, + "graceful-restart helper enable [A.B.C.D$rtr_id]", + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "Enable Helper support\n" + "Advertisement Router-ID\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + if (rtr_id_str != NULL) { + + ospf6_gr_helper_support_set_per_routerid(ospf6, rtr_id, + OSPF6_TRUE); + + return CMD_SUCCESS; + } + + ospf6_gr_helper_support_set(ospf6, OSPF6_TRUE); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_gr_helper_disable, + ospf6_gr_helper_disable_cmd, + "no graceful-restart helper enable [A.B.C.D$rtr_id]", + NO_STR + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "Enable Helper support\n" + "Advertisement Router-ID\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + if (rtr_id_str != NULL) { + + ospf6_gr_helper_support_set_per_routerid(ospf6, rtr_id, + OSPF6_FALSE); + + return CMD_SUCCESS; + } + + ospf6_gr_helper_support_set(ospf6, OSPF6_FALSE); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_gr_helper_disable_lsacheck, + ospf6_gr_helper_disable_lsacheck_cmd, + "graceful-restart helper lsa-check-disable", + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "disable strict LSA check\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_lsacheck_set(ospf6, OSPF6_FALSE); + return CMD_SUCCESS; +} + +DEFPY(no_ospf6_gr_helper_disable_lsacheck, + no_ospf6_gr_helper_disable_lsacheck_cmd, + "no graceful-restart helper lsa-check-disable", + NO_STR + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "diasble strict LSA check\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_lsacheck_set(ospf6, OSPF6_TRUE); + return CMD_SUCCESS; +} + +DEFPY(ospf6_gr_helper_planned_only, + ospf6_gr_helper_planned_only_cmd, + "graceful-restart helper planned-only", + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "supported only planned restart\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_set_supported_onlyPlanned_restart(ospf6, OSPF6_TRUE); + + return CMD_SUCCESS; +} + +DEFPY(no_ospf6_gr_helper_planned_only, no_ospf6_gr_helper_planned_only_cmd, + "no graceful-restart helper planned-only", + NO_STR + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "supported only for planned restart\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_set_supported_onlyPlanned_restart(ospf6, OSPF6_FALSE); + + return CMD_SUCCESS; +} + +DEFPY(ospf6_gr_helper_supported_grace_time, + ospf6_gr_helper_supported_grace_time_cmd, + "graceful-restart helper supported-grace-time (10-1800)$interval", + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "supported grace timer\n" + "grace interval(in seconds)\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_supported_gracetime_set(ospf6, interval); + return CMD_SUCCESS; +} + +DEFPY(no_ospf6_gr_helper_supported_grace_time, + no_ospf6_gr_helper_supported_grace_time_cmd, + "no graceful-restart helper supported-grace-time (10-1800)$interval", + NO_STR + "ospf6 graceful restart\n" + "ospf6 GR Helper\n" + "supported grace timer\n" + "grace interval(in seconds)\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_gr_helper_supported_gracetime_set(ospf6, + OSPF6_MAX_GRACE_INTERVAL); + return CMD_SUCCESS; +} + +/* Show commands */ +DEFPY(show_ipv6_ospf6_gr_helper, + show_ipv6_ospf6_gr_helper_cmd, + "show ipv6 ospf6 graceful-restart helper [detail] [json]", + SHOW_STR + "Ipv6 Information\n" + "OSPF6 information\n" + "ospf6 graceful restart\n" + "helper details in the router\n" + "detailed information\n" JSON_STR) +{ + int idx = 0; + bool uj = use_json(argc, argv); + struct ospf6 *ospf6 = NULL; + json_object *json = NULL; + bool detail = false; + + ospf6 = ospf6_lookup_by_vrf_name(VRF_DEFAULT_NAME); + if (ospf6 == NULL) { + vty_out(vty, "OSPFv3 is not configured\n"); + return CMD_SUCCESS; + } + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + if (uj) + json = json_object_new_object(); + + show_ospf6_gr_helper_details(vty, ospf6, json, uj, detail); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* Debug commands */ +DEFPY(debug_ospf6_gr, debug_ospf6_gr_cmd, + "[no$no] debug ospf6 graceful-restart", + NO_STR DEBUG_STR OSPF6_STR "Graceful restart\n") +{ + if (!no) + OSPF6_DEBUG_GR_ON(); + else + OSPF6_DEBUG_GR_OFF(); + + return CMD_SUCCESS; +} + +/* + * Api to display the grace LSA information. + * + * vty + * vty pointer. + * lsa + * Grace LSA. + * json + * json object + * + * Returns: + * Nothing. + */ +static int ospf6_grace_lsa_show_info(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json) +{ + struct ospf6_lsa_header *lsah = NULL; + struct tlv_header *tlvh = NULL; + struct grace_tlv_graceperiod *gracePeriod; + struct grace_tlv_restart_reason *grReason; + uint16_t length = 0; + int sum = 0; + + lsah = lsa->header; + if (ntohs(lsah->length) <= OSPF6_LSA_HEADER_SIZE) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s: undersized (%u B) lsa", __func__, + ntohs(lsah->length)); + return OSPF6_FAILURE; + } + + length = ntohs(lsah->length) - OSPF6_LSA_HEADER_SIZE; + + if (vty) { + if (!use_json) + vty_out(vty, "TLV info:\n"); + } else { + zlog_debug(" TLV info:"); + } + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + + /* Check TLV len */ + if (sum + TLV_SIZE(tlvh) > length) { + if (vty) + vty_out(vty, "%% Invalid TLV length: %d\n", + TLV_SIZE(tlvh)); + else if (IS_DEBUG_OSPF6_GR) + zlog_debug("%% Invalid TLV length: %d", + TLV_SIZE(tlvh)); + return OSPF6_FAILURE; + } + + switch (ntohs(tlvh->type)) { + case GRACE_PERIOD_TYPE: + gracePeriod = (struct grace_tlv_graceperiod *)tlvh; + sum += TLV_SIZE(tlvh); + + if (vty) { + if (use_json) + json_object_int_add( + json, "gracePeriod", + ntohl(gracePeriod->interval)); + else + vty_out(vty, " Grace period:%d\n", + ntohl(gracePeriod->interval)); + } else { + zlog_debug(" Grace period:%d", + ntohl(gracePeriod->interval)); + } + break; + case RESTART_REASON_TYPE: + grReason = (struct grace_tlv_restart_reason *)tlvh; + sum += TLV_SIZE(tlvh); + if (vty) { + if (use_json) + json_object_string_add( + json, "restartReason", + ospf6_restart_reason_desc + [grReason->reason]); + else + vty_out(vty, " Restart reason:%s\n", + ospf6_restart_reason_desc + [grReason->reason]); + } else { + zlog_debug(" Restart reason:%s", + ospf6_restart_reason_desc + [grReason->reason]); + } + break; + default: + break; + } + } + + return 0; +} + +void ospf6_gr_helper_config_init(void) +{ + + ospf6_install_lsa_handler(&grace_lsa_handler); + + install_element(OSPF6_NODE, &ospf6_gr_helper_enable_cmd); + install_element(OSPF6_NODE, &ospf6_gr_helper_disable_cmd); + install_element(OSPF6_NODE, &ospf6_gr_helper_disable_lsacheck_cmd); + install_element(OSPF6_NODE, &no_ospf6_gr_helper_disable_lsacheck_cmd); + install_element(OSPF6_NODE, &ospf6_gr_helper_planned_only_cmd); + install_element(OSPF6_NODE, &no_ospf6_gr_helper_planned_only_cmd); + install_element(OSPF6_NODE, &ospf6_gr_helper_supported_grace_time_cmd); + install_element(OSPF6_NODE, + &no_ospf6_gr_helper_supported_grace_time_cmd); + + install_element(VIEW_NODE, &show_ipv6_ospf6_gr_helper_cmd); + + install_element(CONFIG_NODE, &debug_ospf6_gr_cmd); + install_element(ENABLE_NODE, &debug_ospf6_gr_cmd); +} + + +/* + * Initialize GR helper config data structure. + * + * ospf6 + * ospf6 pointer + * + * Returns: + * Nothing + */ +void ospf6_gr_helper_init(struct ospf6 *ospf6) +{ + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, GR Helper init.", __func__); + + ospf6->ospf6_helper_cfg.is_helper_supported = OSPF6_FALSE; + ospf6->ospf6_helper_cfg.strict_lsa_check = OSPF6_TRUE; + ospf6->ospf6_helper_cfg.only_planned_restart = OSPF6_FALSE; + ospf6->ospf6_helper_cfg.supported_grace_time = OSPF6_MAX_GRACE_INTERVAL; + ospf6->ospf6_helper_cfg.last_exit_reason = OSPF6_GR_HELPER_EXIT_NONE; + ospf6->ospf6_helper_cfg.active_restarter_cnt = 0; + + ospf6->ospf6_helper_cfg.enable_rtr_list = hash_create( + ospf6_enable_rtr_hash_key, ospf6_enable_rtr_hash_cmp, + "Ospf6 enable router hash"); +} + +/* + * De-initialize GR helper config data structure. + * + * ospf6 + * ospf6 pointer + * + * Returns: + * Nothing + */ +void ospf6_gr_helper_deinit(struct ospf6 *ospf6) +{ + + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, GR helper deinit.", __func__); + + ospf6_enable_rtr_hash_destroy(ospf6); +} + +static int ospf6_cfg_write_helper_enable_rtr_walkcb(struct hash_bucket *backet, + void *arg) +{ + struct advRtr *rtr = backet->data; + struct vty *vty = (struct vty *)arg; + + vty_out(vty, " graceful-restart helper enable %pI4\n", &rtr->advRtrAddr); + return HASHWALK_CONTINUE; +} + +int config_write_ospf6_gr_helper(struct vty *vty, struct ospf6 *ospf6) +{ + if (ospf6->ospf6_helper_cfg.is_helper_supported) + vty_out(vty, " graceful-restart helper enable\n"); + + if (!ospf6->ospf6_helper_cfg.strict_lsa_check) + vty_out(vty, " graceful-restart helper lsa-check-disable\n"); + + if (ospf6->ospf6_helper_cfg.only_planned_restart) + vty_out(vty, " graceful-restart helper planned-only\n"); + + if (ospf6->ospf6_helper_cfg.supported_grace_time + != OSPF6_MAX_GRACE_INTERVAL) + vty_out(vty, + " graceful-restart helper supported-grace-time %d\n", + ospf6->ospf6_helper_cfg.supported_grace_time); + + if (OSPF6_HELPER_ENABLE_RTR_COUNT(ospf6)) { + hash_walk(ospf6->ospf6_helper_cfg.enable_rtr_list, + ospf6_cfg_write_helper_enable_rtr_walkcb, vty); + } + + return 0; +} + +int config_write_ospf6_debug_gr_helper(struct vty *vty) +{ + if (IS_DEBUG_OSPF6_GR) + vty_out(vty, "debug ospf6 graceful-restart\n"); + return 0; +} diff --git a/ospf6d/ospf6_interface.c b/ospf6d/ospf6_interface.c new file mode 100644 index 0000000..7f813ce --- /dev/null +++ b/ospf6d/ospf6_interface.c @@ -0,0 +1,3316 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "memory.h" +#include "if.h" +#include "log.h" +#include "command.h" +#include "frrevent.h" +#include "prefix.h" +#include "plist.h" +#include "zclient.h" + +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_top.h" +#include "ospf6_network.h" +#include "ospf6_message.h" +#include "ospf6_route.h" +#include "ospf6_area.h" +#include "ospf6_abr.h" +#include "ospf6_nssa.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_intra.h" +#include "ospf6_spf.h" +#include "ospf6d.h" +#include "ospf6_bfd.h" +#include "ospf6_zebra.h" +#include "ospf6_gr.h" +#include "lib/json.h" +#include "ospf6_proto.h" +#include "lib/keychain.h" +#include "ospf6_auth_trailer.h" +#include "ospf6d/ospf6_interface_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_IF, "OSPF6 interface"); +DEFINE_MTYPE(OSPF6D, OSPF6_AUTH_KEYCHAIN, "OSPF6 auth keychain"); +DEFINE_MTYPE(OSPF6D, OSPF6_AUTH_MANUAL_KEY, "OSPF6 auth key"); +DEFINE_MTYPE_STATIC(OSPF6D, CFG_PLIST_NAME, "configured prefix list names"); +DEFINE_QOBJ_TYPE(ospf6_interface); +DEFINE_HOOK(ospf6_interface_change, + (struct ospf6_interface * oi, int state, int old_state), + (oi, state, old_state)); + +unsigned char conf_debug_ospf6_interface = 0; + +const char *const ospf6_interface_state_str[] = { + "None", "Down", "Loopback", "Waiting", "PointToPoint", + "PtMultipoint", "DROther", "BDR", "DR", NULL +}; + +int ospf6_interface_neighbor_count(struct ospf6_interface *oi) +{ + int count = 0; + struct ospf6_neighbor *nbr = NULL; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, nbr)) { + /* Down state is not shown. */ + if (nbr->state == OSPF6_NEIGHBOR_DOWN) + continue; + count++; + } + + return count; +} + +struct ospf6_interface *ospf6_interface_lookup_by_ifindex(ifindex_t ifindex, + vrf_id_t vrf_id) +{ + struct ospf6_interface *oi; + struct interface *ifp; + + ifp = if_lookup_by_index(ifindex, vrf_id); + if (ifp == NULL) + return (struct ospf6_interface *)NULL; + + oi = (struct ospf6_interface *)ifp->info; + return oi; +} + +/* schedule routing table recalculation */ +static void ospf6_interface_lsdb_hook(struct ospf6_lsa *lsa, unsigned int reason) +{ + struct ospf6_interface *oi; + + if (lsa == NULL) + return; + + oi = lsa->lsdb->data; + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_LINK: + if (oi->state == OSPF6_INTERFACE_DR) + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + if (oi->area) + ospf6_spf_schedule(oi->area->ospf6, reason); + break; + + default: + break; + } +} + +static void ospf6_interface_lsdb_hook_add(struct ospf6_lsa *lsa) +{ + ospf6_interface_lsdb_hook(lsa, ospf6_lsadd_to_spf_reason(lsa)); +} + +static void ospf6_interface_lsdb_hook_remove(struct ospf6_lsa *lsa) +{ + ospf6_interface_lsdb_hook(lsa, ospf6_lsremove_to_spf_reason(lsa)); +} + +static uint8_t ospf6_default_iftype(struct interface *ifp) +{ + if (if_is_pointopoint(ifp)) + return OSPF_IFTYPE_POINTOPOINT; + else if (if_is_loopback(ifp)) + return OSPF_IFTYPE_LOOPBACK; + else + return OSPF_IFTYPE_BROADCAST; +} + +static uint32_t ospf6_interface_get_cost(struct ospf6_interface *oi) +{ + /* If all else fails, use default OSPF cost */ + uint32_t cost; + uint32_t bw, refbw; + struct ospf6 *ospf6; + + /* interface speed and bw can be 0 in some platforms, + * use ospf default bw. If bw is configured then it would + * be used. + */ + if (!oi->interface->bandwidth && oi->interface->speed) { + bw = oi->interface->speed; + } else { + bw = oi->interface->bandwidth ? oi->interface->bandwidth + : OSPF6_INTERFACE_BANDWIDTH; + } + + ospf6 = oi->interface->vrf->info; + refbw = ospf6 ? ospf6->ref_bandwidth : OSPF6_REFERENCE_BANDWIDTH; + + /* A specified ip ospf cost overrides a calculated one. */ + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST)) + cost = oi->cost; + else { + cost = (uint32_t)((double)refbw / (double)bw + (double)0.5); + if (cost < 1) + cost = 1; + + /* If the interface type is point-to-multipoint or the interface + * is in the state Loopback, the global scope IPv6 addresses + * associated with the interface (if any) are copied into the + * intra-area-prefix-LSA with the PrefixOptions LA-bit set, the + * PrefixLength set to 128, and the metric set to 0. + */ + if (if_is_loopback(oi->interface)) + cost = 0; + } + + return cost; +} + +static void ospf6_interface_force_recalculate_cost(struct ospf6_interface *oi) +{ + /* update cost held in route_connected list in ospf6_interface */ + ospf6_interface_connected_route_update(oi->interface); + + /* execute LSA hooks */ + if (oi->area) { + OSPF6_LINK_LSA_SCHEDULE(oi); + OSPF6_ROUTER_LSA_SCHEDULE(oi->area); + OSPF6_NETWORK_LSA_SCHEDULE(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); + } +} + +static void ospf6_interface_recalculate_cost(struct ospf6_interface *oi) +{ + uint32_t newcost; + + newcost = ospf6_interface_get_cost(oi); + if (newcost == oi->cost) + return; + oi->cost = newcost; + + ospf6_interface_force_recalculate_cost(oi); +} + +/* Create new ospf6 interface structure */ +struct ospf6_interface *ospf6_interface_create(struct interface *ifp) +{ + struct ospf6_interface *oi; + unsigned int iobuflen; + + oi = XCALLOC(MTYPE_OSPF6_IF, sizeof(struct ospf6_interface)); + + oi->obuf = ospf6_fifo_new(); + + oi->area = (struct ospf6_area *)NULL; + oi->neighbor_list = list_new(); + oi->neighbor_list->cmp = ospf6_neighbor_cmp; + oi->linklocal_addr = (struct in6_addr *)NULL; + oi->instance_id = OSPF6_INTERFACE_INSTANCE_ID; + oi->transdelay = OSPF6_INTERFACE_TRANSDELAY; + oi->priority = OSPF6_INTERFACE_PRIORITY; + + oi->hello_interval = OSPF_HELLO_INTERVAL_DEFAULT; + oi->gr.hello_delay.interval = OSPF_HELLO_DELAY_DEFAULT; + oi->dead_interval = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; + oi->rxmt_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; + oi->type = ospf6_default_iftype(ifp); + oi->state = OSPF6_INTERFACE_DOWN; + oi->flag = 0; + oi->mtu_ignore = 0; + oi->c_ifmtu = 0; + + /* Try to adjust I/O buffer size with IfMtu */ + oi->ifmtu = ifp->mtu6; + iobuflen = ospf6_iobuf_size(ifp->mtu6); + if (oi->ifmtu > iobuflen) { + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface %s: IfMtu is adjusted to I/O buffer size: %d.", + ifp->name, iobuflen); + oi->ifmtu = iobuflen; + } + + QOBJ_REG(oi, ospf6_interface); + + oi->lsupdate_list = ospf6_lsdb_create(oi); + oi->lsack_list = ospf6_lsdb_create(oi); + oi->lsdb = ospf6_lsdb_create(oi); + oi->lsdb->hook_add = ospf6_interface_lsdb_hook_add; + oi->lsdb->hook_remove = ospf6_interface_lsdb_hook_remove; + oi->lsdb_self = ospf6_lsdb_create(oi); + + oi->route_connected = OSPF6_ROUTE_TABLE_CREATE(INTERFACE, + CONNECTED_ROUTES); + oi->route_connected->scope = oi; + + /* link both */ + oi->interface = ifp; + ifp->info = oi; + + /* Compute cost. */ + oi->cost = ospf6_interface_get_cost(oi); + + oi->at_data.flags = 0; + + return oi; +} + +void ospf6_interface_delete(struct ospf6_interface *oi) +{ + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + + QOBJ_UNREG(oi); + + ospf6_fifo_free(oi->obuf); + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) + ospf6_neighbor_delete(on); + + list_delete(&oi->neighbor_list); + + EVENT_OFF(oi->thread_send_hello); + EVENT_OFF(oi->thread_send_lsupdate); + EVENT_OFF(oi->thread_send_lsack); + EVENT_OFF(oi->thread_sso); + EVENT_OFF(oi->thread_wait_timer); + + ospf6_lsdb_remove_all(oi->lsdb); + ospf6_lsdb_remove_all(oi->lsupdate_list); + ospf6_lsdb_remove_all(oi->lsack_list); + + ospf6_lsdb_delete(oi->lsdb); + ospf6_lsdb_delete(oi->lsdb_self); + + ospf6_lsdb_delete(oi->lsupdate_list); + ospf6_lsdb_delete(oi->lsack_list); + + ospf6_route_table_delete(oi->route_connected); + + /* cut link */ + oi->interface->info = NULL; + + /* plist_name */ + if (oi->plist_name) + XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); + + /* disable from area list if possible */ + ospf6_area_interface_delete(oi); + + if (oi->at_data.auth_key) + XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY, oi->at_data.auth_key); + + /* Free BFD allocated data. */ + XFREE(MTYPE_TMP, oi->bfd_config.profile); + + XFREE(MTYPE_OSPF6_IF, oi); +} + +void ospf6_interface_enable(struct ospf6_interface *oi) +{ + UNSET_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE); + ospf6_interface_state_update(oi->interface); +} + +void ospf6_interface_disable(struct ospf6_interface *oi) +{ + SET_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE); + + event_execute(master, interface_down, oi, 0, NULL); + + ospf6_lsdb_remove_all(oi->lsdb); + ospf6_lsdb_remove_all(oi->lsdb_self); + ospf6_lsdb_remove_all(oi->lsupdate_list); + ospf6_lsdb_remove_all(oi->lsack_list); + + EVENT_OFF(oi->thread_send_hello); + EVENT_OFF(oi->thread_send_lsupdate); + EVENT_OFF(oi->thread_send_lsack); + EVENT_OFF(oi->thread_sso); + + EVENT_OFF(oi->thread_network_lsa); + EVENT_OFF(oi->thread_link_lsa); + EVENT_OFF(oi->thread_intra_prefix_lsa); + EVENT_OFF(oi->thread_as_extern_lsa); + EVENT_OFF(oi->thread_wait_timer); + + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); +} + +static struct in6_addr * +ospf6_interface_get_linklocal_address(struct interface *ifp) +{ + struct connected *c; + struct in6_addr *l = (struct in6_addr *)NULL; + + /* for each connected address */ + frr_each (if_connected, ifp->connected, c) { + /* if family not AF_INET6, ignore */ + if (c->address->family != AF_INET6) + continue; + + /* linklocal scope check */ + if (IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) + l = &c->address->u.prefix6; + } + return l; +} + +void ospf6_interface_state_update(struct interface *ifp) +{ + struct ospf6_interface *oi; + unsigned int iobuflen; + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + return; + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) + return; + + /* Adjust the mtu values if the kernel told us something new */ + if (ifp->mtu6 != oi->ifmtu) { + /* If nothing configured, accept it and check for buffer size */ + if (!oi->c_ifmtu) { + oi->ifmtu = ifp->mtu6; + iobuflen = ospf6_iobuf_size(ifp->mtu6); + if (oi->ifmtu > iobuflen) { + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface %s: IfMtu is adjusted to I/O buffer size: %d.", + ifp->name, iobuflen); + oi->ifmtu = iobuflen; + } + } else if (oi->c_ifmtu > ifp->mtu6) { + oi->ifmtu = ifp->mtu6; + zlog_warn("Configured mtu %u on %s overridden by kernel %u", + oi->c_ifmtu, ifp->name, ifp->mtu6); + } else + oi->ifmtu = oi->c_ifmtu; + } + + if (if_is_operative(ifp) && + (ospf6_interface_get_linklocal_address(oi->interface) || + if_is_loopback(oi->interface))) + event_execute(master, interface_up, oi, 0, NULL); + else + event_execute(master, interface_down, oi, 0, NULL); + + return; +} + +void ospf6_interface_connected_route_update(struct interface *ifp) +{ + struct ospf6_interface *oi; + struct connected *c; + struct in6_addr nh_addr; + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + return; + + /* reset linklocal pointer */ + oi->linklocal_addr = ospf6_interface_get_linklocal_address(ifp); + + /* if area is null, do not make connected-route list */ + if (oi->area == NULL) + return; + + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) + return; + + /* update "route to advertise" interface route table */ + ospf6_route_remove_all(oi->route_connected); + + frr_each (if_connected, ifp->connected, c) { + if (c->address->family != AF_INET6) + continue; + + CONTINUE_IF_ADDRESS_LINKLOCAL(IS_OSPF6_DEBUG_INTERFACE, + c->address); + CONTINUE_IF_ADDRESS_UNSPECIFIED(IS_OSPF6_DEBUG_INTERFACE, + c->address); + CONTINUE_IF_ADDRESS_LOOPBACK(IS_OSPF6_DEBUG_INTERFACE, + c->address); + CONTINUE_IF_ADDRESS_V4COMPAT(IS_OSPF6_DEBUG_INTERFACE, + c->address); + CONTINUE_IF_ADDRESS_V4MAPPED(IS_OSPF6_DEBUG_INTERFACE, + c->address); + + /* apply filter */ + if (oi->plist_name) { + struct prefix_list *plist; + enum prefix_list_type ret; + + plist = prefix_list_lookup(AFI_IP6, oi->plist_name); + ret = prefix_list_apply(plist, (void *)c->address); + if (ret == PREFIX_DENY) { + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("%pFX on %s filtered by prefix-list %s ", + c->address, + oi->interface->name, + oi->plist_name); + continue; + } + } + + if (oi->type == OSPF_IFTYPE_LOOPBACK || + oi->type == OSPF_IFTYPE_POINTOMULTIPOINT || + oi->type == OSPF_IFTYPE_POINTOPOINT) { + struct ospf6_route *la_route; + + la_route = ospf6_route_create(oi->area->ospf6); + la_route->prefix = *c->address; + la_route->prefix.prefixlen = 128; + la_route->prefix_options |= OSPF6_PREFIX_OPTION_LA; + + la_route->type = OSPF6_DEST_TYPE_NETWORK; + la_route->path.area_id = oi->area->area_id; + la_route->path.type = OSPF6_PATH_TYPE_INTRA; + la_route->path.cost = 0; + inet_pton(AF_INET6, "::1", &nh_addr); + ospf6_route_add_nexthop(la_route, oi->interface->ifindex, + &nh_addr); + ospf6_route_add(la_route, oi->route_connected); + } + + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT && + !oi->p2xp_connected_pfx_include) + continue; + if (oi->type == OSPF_IFTYPE_POINTOPOINT && + oi->p2xp_connected_pfx_exclude) + continue; + + struct ospf6_route *route; + + route = ospf6_route_create(oi->area->ospf6); + memcpy(&route->prefix, c->address, sizeof(struct prefix)); + apply_mask(&route->prefix); + route->type = OSPF6_DEST_TYPE_NETWORK; + route->path.area_id = oi->area->area_id; + route->path.type = OSPF6_PATH_TYPE_INTRA; + route->path.cost = oi->cost; + inet_pton(AF_INET6, "::1", &nh_addr); + ospf6_route_add_nexthop(route, oi->interface->ifindex, &nh_addr); + ospf6_route_add(route, oi->route_connected); + } + + /* create new Link-LSA */ + OSPF6_LINK_LSA_SCHEDULE(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); +} + +static int ospf6_interface_state_change(uint8_t next_state, + struct ospf6_interface *oi) +{ + uint8_t prev_state; + struct ospf6 *ospf6; + + prev_state = oi->state; + oi->state = next_state; + + if (prev_state == next_state) + return -1; + + if (!oi->area) + return -1; + + /* log */ + if (IS_OSPF6_DEBUG_INTERFACE) { + zlog_debug("Interface state change %s: %s -> %s", + oi->interface->name, + ospf6_interface_state_str[prev_state], + ospf6_interface_state_str[next_state]); + } + oi->state_change++; + + ospf6 = oi->area->ospf6; + + if ((prev_state == OSPF6_INTERFACE_DR || + prev_state == OSPF6_INTERFACE_BDR) && + (next_state != OSPF6_INTERFACE_DR && + next_state != OSPF6_INTERFACE_BDR)) + ospf6_sso(oi->interface->ifindex, &alldrouters6, + IPV6_LEAVE_GROUP, ospf6->fd); + + if ((prev_state != OSPF6_INTERFACE_DR && + prev_state != OSPF6_INTERFACE_BDR) && + (next_state == OSPF6_INTERFACE_DR || + next_state == OSPF6_INTERFACE_BDR)) + ospf6_sso(oi->interface->ifindex, &alldrouters6, + IPV6_JOIN_GROUP, ospf6->fd); + + OSPF6_ROUTER_LSA_SCHEDULE(oi->area); + OSPF6_LINK_LSA_SCHEDULE(oi); + if (next_state == OSPF6_INTERFACE_DOWN) { + OSPF6_NETWORK_LSA_EXECUTE(oi); + OSPF6_INTRA_PREFIX_LSA_EXECUTE_TRANSIT(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); + } else if (prev_state == OSPF6_INTERFACE_DR || + next_state == OSPF6_INTERFACE_DR) { + OSPF6_NETWORK_LSA_SCHEDULE(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); + } + + if (next_state == OSPF6_INTERFACE_POINTTOPOINT || + next_state == OSPF6_INTERFACE_POINTTOMULTIPOINT) + ospf6_if_p2xp_up(oi); + + hook_call(ospf6_interface_change, oi, next_state, prev_state); + + return 0; +} + + +/* DR Election, RFC2328 section 9.4 */ + +#define IS_ELIGIBLE(n) \ + ((n)->state >= OSPF6_NEIGHBOR_TWOWAY && (n)->priority != 0) + +static struct ospf6_neighbor *better_bdrouter(struct ospf6_neighbor *a, + struct ospf6_neighbor *b) +{ + if ((a == NULL || !IS_ELIGIBLE(a) || a->drouter == a->router_id) && + (b == NULL || !IS_ELIGIBLE(b) || b->drouter == b->router_id)) + return NULL; + else if (a == NULL || !IS_ELIGIBLE(a) || a->drouter == a->router_id) + return b; + else if (b == NULL || !IS_ELIGIBLE(b) || b->drouter == b->router_id) + return a; + + if (a->bdrouter == a->router_id && b->bdrouter != b->router_id) + return a; + if (a->bdrouter != a->router_id && b->bdrouter == b->router_id) + return b; + + if (a->priority > b->priority) + return a; + if (a->priority < b->priority) + return b; + + if (ntohl(a->router_id) > ntohl(b->router_id)) + return a; + if (ntohl(a->router_id) < ntohl(b->router_id)) + return b; + + zlog_warn("Router-ID duplicate ?"); + return a; +} + +static struct ospf6_neighbor *better_drouter(struct ospf6_neighbor *a, + struct ospf6_neighbor *b) +{ + if ((a == NULL || !IS_ELIGIBLE(a) || a->drouter != a->router_id) && + (b == NULL || !IS_ELIGIBLE(b) || b->drouter != b->router_id)) + return NULL; + else if (a == NULL || !IS_ELIGIBLE(a) || a->drouter != a->router_id) + return b; + else if (b == NULL || !IS_ELIGIBLE(b) || b->drouter != b->router_id) + return a; + + if (a->drouter == a->router_id && b->drouter != b->router_id) + return a; + if (a->drouter != a->router_id && b->drouter == b->router_id) + return b; + + if (a->priority > b->priority) + return a; + if (a->priority < b->priority) + return b; + + if (ntohl(a->router_id) > ntohl(b->router_id)) + return a; + if (ntohl(a->router_id) < ntohl(b->router_id)) + return b; + + zlog_warn("Router-ID duplicate ?"); + return a; +} + +uint8_t dr_election(struct ospf6_interface *oi) +{ + struct ospf6 *ospf6 = oi->area->ospf6; + struct listnode *node, *nnode; + struct ospf6_neighbor *on, *drouter, *bdrouter, myself; + struct ospf6_neighbor *best_drouter, *best_bdrouter; + uint8_t next_state = 0; + + drouter = bdrouter = NULL; + best_drouter = best_bdrouter = NULL; + + /* pseudo neighbor myself, including noting current DR/BDR (1) */ + memset(&myself, 0, sizeof(myself)); + inet_ntop(AF_INET, &ospf6->router_id, myself.name, sizeof(myself.name)); + myself.state = OSPF6_NEIGHBOR_TWOWAY; + myself.drouter = oi->drouter; + myself.bdrouter = oi->bdrouter; + myself.priority = oi->priority; + myself.router_id = ospf6->router_id; + + /* Electing BDR (2) */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) + bdrouter = better_bdrouter(bdrouter, on); + + best_bdrouter = bdrouter; + bdrouter = better_bdrouter(best_bdrouter, &myself); + + /* Electing DR (3) */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) + drouter = better_drouter(drouter, on); + + best_drouter = drouter; + drouter = better_drouter(best_drouter, &myself); + if (drouter == NULL) + drouter = bdrouter; + + /* the router itself is newly/no longer DR/BDR (4) */ + if ((drouter == &myself && myself.drouter != myself.router_id) || + (drouter != &myself && myself.drouter == myself.router_id) || + (bdrouter == &myself && myself.bdrouter != myself.router_id) || + (bdrouter != &myself && myself.bdrouter == myself.router_id)) { + myself.drouter = (drouter ? drouter->router_id : htonl(0)); + myself.bdrouter = (bdrouter ? bdrouter->router_id : htonl(0)); + + /* compatible to Electing BDR (2) */ + bdrouter = better_bdrouter(best_bdrouter, &myself); + + /* compatible to Electing DR (3) */ + drouter = better_drouter(best_drouter, &myself); + if (drouter == NULL) + drouter = bdrouter; + } + + /* Set interface state accordingly (5) */ + if (drouter && drouter == &myself) + next_state = OSPF6_INTERFACE_DR; + else if (bdrouter && bdrouter == &myself) + next_state = OSPF6_INTERFACE_BDR; + else + next_state = OSPF6_INTERFACE_DROTHER; + + /* If NBMA, schedule Start for each neighbor having priority of 0 (6) */ + /* XXX */ + + /* If DR or BDR change, invoke AdjOK? for each neighbor (7) */ + /* RFC 2328 section 12.4. Originating LSAs (3) will be handled + accordingly after AdjOK */ + + if (oi->drouter != (drouter ? drouter->router_id : htonl(0)) || + oi->bdrouter != (bdrouter ? bdrouter->router_id : htonl(0)) || + ospf6->gr_info.restart_in_progress) { + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("DR Election on %s: DR: %s BDR: %s", + oi->interface->name, + (drouter ? drouter->name : "0.0.0.0"), + (bdrouter ? bdrouter->name : "0.0.0.0")); + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { + if (on->state < OSPF6_NEIGHBOR_TWOWAY) + continue; + /* Schedule AdjOK. */ + event_add_event(master, adj_ok, on, 0, + &on->thread_adj_ok); + } + } + + oi->drouter = (drouter ? drouter->router_id : htonl(0)); + oi->bdrouter = (bdrouter ? bdrouter->router_id : htonl(0)); + return next_state; +} + +#ifdef __FreeBSD__ + +#include + +static bool ifmaddr_check(ifindex_t ifindex, struct in6_addr *addr) +{ + struct ifmaddrs *ifmap, *ifma; + struct sockaddr_dl *sdl; + struct sockaddr_in6 *sin6; + bool found = false; + + if (getifmaddrs(&ifmap) != 0) + return false; + + for (ifma = ifmap; ifma; ifma = ifma->ifma_next) { + if (ifma->ifma_name == NULL || ifma->ifma_addr == NULL) + continue; + if (ifma->ifma_name->sa_family != AF_LINK) + continue; + if (ifma->ifma_addr->sa_family != AF_INET6) + continue; + sdl = (struct sockaddr_dl *)ifma->ifma_name; + sin6 = (struct sockaddr_in6 *)ifma->ifma_addr; + if (sdl->sdl_index == ifindex && + memcmp(&sin6->sin6_addr, addr, IPV6_MAX_BYTELEN) == 0) { + found = true; + break; + } + } + + if (ifmap) + freeifmaddrs(ifmap); + + return found; +} + +#endif /* __FreeBSD__ */ + +/* Interface State Machine */ +void interface_up(struct event *thread) +{ + struct ospf6_interface *oi; + struct ospf6 *ospf6; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + assert(oi && oi->interface); + + if (!oi->type_cfg) + oi->type = ospf6_default_iftype(oi->interface); + + event_cancel(&oi->thread_sso); + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface Event %s: [InterfaceUp]", + oi->interface->name); + + /* check physical interface is up */ + if (!if_is_operative(oi->interface)) { + zlog_warn("Interface %s is down, can't execute [InterfaceUp]", + oi->interface->name); + return; + } + + /* check interface has a link-local address */ + if (!(ospf6_interface_get_linklocal_address(oi->interface) || + if_is_loopback(oi->interface))) { + zlog_warn("Interface %s has no link local address, can't execute [InterfaceUp]", + oi->interface->name); + return; + } + + /* Recompute cost & update connected LSAs */ + ospf6_interface_force_recalculate_cost(oi); + + /* if already enabled, do nothing */ + if (oi->state > OSPF6_INTERFACE_DOWN) { + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface %s already enabled", + oi->interface->name); + return; + } + + /* If no area assigned, return */ + if (oi->area == NULL) { + zlog_warn("%s: Not scheduling Hello for %s as there is no area assigned yet", + __func__, oi->interface->name); + return; + } + + /* + * RFC 3623 - Section 5 ("Unplanned Outages"): + * "The grace-LSAs are encapsulated in Link State Update Packets + * and sent out to all interfaces, even though the restarted + * router has no adjacencies and no knowledge of previous + * adjacencies". + */ + if (oi->area->ospf6->gr_info.restart_in_progress && + oi->area->ospf6->gr_info.reason == OSPF6_GR_UNKNOWN_RESTART) + ospf6_gr_unplanned_start_interface(oi); + +#ifdef __FreeBSD__ + /* + * There's a delay in FreeBSD between issuing a command to leave a + * multicast group and an actual leave. If we execute "no router ospf6" + * and "router ospf6" fast enough, we can end up in a situation when OS + * performs the leave later than it performs the join and the interface + * remains without a multicast group. We have to do the join only after + * the interface actually left the group. + */ + if (ifmaddr_check(oi->interface->ifindex, &allspfrouters6)) { + zlog_info("Interface %s is still in all routers group, rescheduling for SSO", + oi->interface->name); + event_add_timer(master, interface_up, oi, + OSPF6_INTERFACE_SSO_RETRY_INT, &oi->thread_sso); + return; + } +#endif /* __FreeBSD__ */ + + ospf6 = oi->area->ospf6; + + /* Join AllSPFRouters */ + if (ospf6_sso(oi->interface->ifindex, &allspfrouters6, IPV6_JOIN_GROUP, + ospf6->fd) < 0) { + if (oi->sso_try_cnt++ < OSPF6_INTERFACE_SSO_RETRY_MAX) { + zlog_info("Scheduling %s for sso retry, trial count: %d", + oi->interface->name, oi->sso_try_cnt); + event_add_timer(master, interface_up, oi, + OSPF6_INTERFACE_SSO_RETRY_INT, + &oi->thread_sso); + } + return; + } + oi->sso_try_cnt = 0; /* Reset on success */ + + /* Update interface route */ + ospf6_interface_connected_route_update(oi->interface); + + /* Schedule Hello */ + if (!CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE) && + !if_is_loopback(oi->interface)) { + event_add_timer(master, ospf6_hello_send, oi, 0, + &oi->thread_send_hello); + } + + /* decide next interface state */ + if (oi->type == OSPF_IFTYPE_LOOPBACK) { + ospf6_interface_state_change(OSPF6_INTERFACE_LOOPBACK, oi); + } else if (oi->type == OSPF_IFTYPE_POINTOPOINT) { + ospf6_interface_state_change(OSPF6_INTERFACE_POINTTOPOINT, oi); + } else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + ospf6_interface_state_change(OSPF6_INTERFACE_POINTTOMULTIPOINT, + oi); + } else if (oi->priority == 0) + ospf6_interface_state_change(OSPF6_INTERFACE_DROTHER, oi); + else { + ospf6_interface_state_change(OSPF6_INTERFACE_WAITING, oi); + event_add_timer(master, wait_timer, oi, oi->dead_interval, + &oi->thread_wait_timer); + } +} + +void wait_timer(struct event *thread) +{ + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + assert(oi && oi->interface); + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface Event %s: [WaitTimer]", + oi->interface->name); + + if (oi->state == OSPF6_INTERFACE_WAITING) + ospf6_interface_state_change(dr_election(oi), oi); +} + +void backup_seen(struct event *thread) +{ + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + assert(oi && oi->interface); + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface Event %s: [BackupSeen]", + oi->interface->name); + + if (oi->state == OSPF6_INTERFACE_WAITING) + ospf6_interface_state_change(dr_election(oi), oi); +} + +void neighbor_change(struct event *thread) +{ + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + assert(oi && oi->interface); + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface Event %s: [NeighborChange]", + oi->interface->name); + + if (oi->state == OSPF6_INTERFACE_DROTHER || + oi->state == OSPF6_INTERFACE_BDR || oi->state == OSPF6_INTERFACE_DR) + ospf6_interface_state_change(dr_election(oi), oi); +} + +void interface_down(struct event *thread) +{ + struct ospf6_interface *oi; + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + struct ospf6 *ospf6; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + assert(oi && oi->interface); + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface Event %s: [InterfaceDown]", + oi->interface->name); + + /* Stop Hellos */ + EVENT_OFF(oi->thread_send_hello); + + /* Stop trying to set socket options. */ + EVENT_OFF(oi->thread_sso); + + /* Cease the HELPER role for all the neighbours + * of this interface. + */ + if (ospf6_interface_neighbor_count(oi)) { + struct listnode *ln; + struct ospf6_neighbor *nbr = NULL; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, ln, nbr)) + ospf6_gr_helper_exit(nbr, OSPF6_GR_HELPER_TOPO_CHG); + } + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) + ospf6_neighbor_delete(on); + + list_delete_all_node(oi->neighbor_list); + + /* When interface state is reset, also reset information about + * DR election, as it is no longer valid. */ + oi->drouter = oi->prev_drouter = htonl(0); + oi->bdrouter = oi->prev_bdrouter = htonl(0); + + if (oi->area == NULL) + return; + + ospf6 = oi->area->ospf6; + /* Leave AllSPFRouters */ + if (oi->state > OSPF6_INTERFACE_DOWN) + ospf6_sso(oi->interface->ifindex, &allspfrouters6, + IPV6_LEAVE_GROUP, ospf6->fd); + + /* deal with write fifo */ + ospf6_fifo_flush(oi->obuf); + if (oi->on_write_q) { + listnode_delete(ospf6->oi_write_q, oi); + if (list_isempty(ospf6->oi_write_q)) + event_cancel(&ospf6->t_write); + oi->on_write_q = 0; + } + + ospf6_interface_state_change(OSPF6_INTERFACE_DOWN, oi); +} + + +static const char *ospf6_iftype_str(uint8_t iftype) +{ + switch (iftype) { + case OSPF_IFTYPE_LOOPBACK: + return "LOOPBACK"; + case OSPF_IFTYPE_BROADCAST: + return "BROADCAST"; + case OSPF_IFTYPE_POINTOPOINT: + return "POINTOPOINT"; + case OSPF_IFTYPE_POINTOMULTIPOINT: + return "POINTOMULTIPOINT"; + } + return "UNKNOWN"; +} + +/* show specified interface structure */ +static int ospf6_interface_show(struct vty *vty, struct interface *ifp, + json_object *json_obj, bool use_json) +{ + struct ospf6_interface *oi; + struct connected *c; + struct prefix *p; + char strbuf[PREFIX2STR_BUFFER], drouter[32], bdrouter[32]; + uint8_t default_iftype; + struct timeval res, now; + char duration[32]; + struct ospf6_lsa *lsa, *lsanext; + json_object *json_arr; + json_object *json_addr; + struct json_object *json_auth = NULL; + + default_iftype = ospf6_default_iftype(ifp); + + if (use_json) { + json_object_string_add(json_obj, "status", + (if_is_operative(ifp) ? "up" : "down")); + json_object_string_add(json_obj, "type", + ospf6_iftype_str(default_iftype)); + json_object_int_add(json_obj, "interfaceId", ifp->ifindex); + + if (ifp->info == NULL) + return 0; + + oi = (struct ospf6_interface *)ifp->info; + + if (if_is_operative(ifp) && oi->type != default_iftype) + json_object_string_add(json_obj, "operatingAsType", + ospf6_iftype_str(oi->type)); + + } else { + vty_out(vty, "%s is %s, type %s\n", ifp->name, + (if_is_operative(ifp) ? "up" : "down"), + ospf6_iftype_str(default_iftype)); + vty_out(vty, " Interface ID: %d\n", ifp->ifindex); + + if (ifp->info == NULL) { + vty_out(vty, " OSPF not enabled on this interface\n"); + return 0; + } + oi = (struct ospf6_interface *)ifp->info; + + if (if_is_operative(ifp) && oi->type != default_iftype) + vty_out(vty, " Operating as type %s\n", + ospf6_iftype_str(oi->type)); + } + + if (use_json) { + json_arr = json_object_new_array(); + frr_each (if_connected, ifp->connected, c) { + json_addr = json_object_new_object(); + p = c->address; + prefix2str(p, strbuf, sizeof(strbuf)); + switch (p->family) { + case AF_INET: + json_object_string_add(json_addr, "type", + "inet"); + json_object_string_add(json_addr, "address", + strbuf); + json_object_array_add(json_arr, json_addr); + break; + case AF_INET6: + json_object_string_add(json_addr, "type", + "inet6"); + json_object_string_add(json_addr, "address", + strbuf); + json_object_array_add(json_arr, json_addr); + break; + default: + json_object_string_add(json_addr, "type", + "unknown"); + json_object_string_add(json_addr, "address", + strbuf); + json_object_array_add(json_arr, json_addr); + break; + } + } + json_object_object_add(json_obj, "internetAddress", json_arr); + } else { + vty_out(vty, " Internet Address:\n"); + + frr_each (if_connected, ifp->connected, c) { + p = c->address; + prefix2str(p, strbuf, sizeof(strbuf)); + switch (p->family) { + case AF_INET: + vty_out(vty, " inet : %pFX\n", p); + break; + case AF_INET6: + vty_out(vty, " inet6: %pFX\n", p); + break; + default: + vty_out(vty, " ??? : %pFX\n", p); + break; + } + } + } + + if (use_json) { + if (oi->area) { + json_object_boolean_true_add(json_obj, "attachedToArea"); + json_object_int_add(json_obj, "instanceId", + oi->instance_id); + json_object_int_add(json_obj, "interfaceMtu", oi->ifmtu); + json_object_int_add(json_obj, "autoDetect", ifp->mtu6); + json_object_string_add(json_obj, "mtuMismatchDetection", + oi->mtu_ignore ? "disabled" + : "enabled"); + inet_ntop(AF_INET, &oi->area->area_id, strbuf, + sizeof(strbuf)); + json_object_string_add(json_obj, "areaId", strbuf); + json_object_int_add(json_obj, "cost", oi->cost); + } else + json_object_boolean_false_add(json_obj, + "attachedToArea"); + + } else { + if (oi->area) { + vty_out(vty, + " Instance ID %d, Interface MTU %d (autodetect: %d)\n", + oi->instance_id, oi->ifmtu, ifp->mtu6); + vty_out(vty, " MTU mismatch detection: %s\n", + oi->mtu_ignore ? "disabled" : "enabled"); + inet_ntop(AF_INET, &oi->area->area_id, strbuf, + sizeof(strbuf)); + vty_out(vty, " Area ID %s, Cost %u\n", strbuf, + oi->cost); + } else + vty_out(vty, " Not Attached to Area\n"); + } + + if (use_json) { + json_object_string_add(json_obj, "ospf6InterfaceState", + ospf6_interface_state_str[oi->state]); + json_object_int_add(json_obj, "transmitDelaySec", + oi->transdelay); + json_object_int_add(json_obj, "priority", oi->priority); + json_object_int_add(json_obj, "timerIntervalsConfigHello", + oi->hello_interval); + json_object_int_add(json_obj, "timerIntervalsConfigDead", + oi->dead_interval); + json_object_int_add(json_obj, "timerIntervalsConfigRetransmit", + oi->rxmt_interval); + json_object_boolean_add(json_obj, "timerPassiveIface", + !!CHECK_FLAG(oi->flag, + OSPF6_INTERFACE_PASSIVE)); + } else { + vty_out(vty, " State %s, Transmit Delay %d sec, Priority %d\n", + ospf6_interface_state_str[oi->state], oi->transdelay, + oi->priority); + vty_out(vty, " Timer intervals configured:\n"); + if (!CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE)) + vty_out(vty, + " Hello %d(%pTHd), Dead %d, Retransmit %d\n", + oi->hello_interval, oi->thread_send_hello, + oi->dead_interval, oi->rxmt_interval); + else + vty_out(vty, " No Hellos (Passive interface)\n"); + } + + inet_ntop(AF_INET, &oi->drouter, drouter, sizeof(drouter)); + inet_ntop(AF_INET, &oi->bdrouter, bdrouter, sizeof(bdrouter)); + if (use_json) { + json_object_string_add(json_obj, "dr", drouter); + json_object_string_add(json_obj, "bdr", bdrouter); + json_object_int_add(json_obj, "numberOfInterfaceScopedLsa", + oi->lsdb->count); + } else { + vty_out(vty, " DR: %s BDR: %s\n", drouter, bdrouter); + vty_out(vty, " Number of I/F scoped LSAs is %u\n", + oi->lsdb->count); + } + + monotime(&now); + + if (use_json) { + timerclear(&res); + if (event_is_scheduled(oi->thread_send_lsupdate)) + timersub(&oi->thread_send_lsupdate->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + json_object_int_add(json_obj, "pendingLsaLsUpdateCount", + oi->lsupdate_list->count); + json_object_string_add(json_obj, "pendingLsaLsUpdateTime", + duration); + json_object_string_add(json_obj, "lsUpdateSendThread", + (event_is_scheduled( + oi->thread_send_lsupdate) + ? "on" + : "off")); + + json_arr = json_object_new_array(); + for (ALL_LSDB(oi->lsupdate_list, lsa, lsanext)) + json_object_array_add(json_arr, + json_object_new_string(lsa->name)); + json_object_object_add(json_obj, "pendingLsaLsUpdate", json_arr); + + timerclear(&res); + if (event_is_scheduled(oi->thread_send_lsack)) + timersub(&oi->thread_send_lsack->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + + json_object_int_add(json_obj, "pendingLsaLsAckCount", + oi->lsack_list->count); + json_object_string_add(json_obj, "pendingLsaLsAckTime", + duration); + json_object_string_add(json_obj, "lsAckSendThread", + (event_is_scheduled(oi->thread_send_lsack) + ? "on" + : "off")); + + json_arr = json_object_new_array(); + for (ALL_LSDB(oi->lsack_list, lsa, lsanext)) + json_object_array_add(json_arr, + json_object_new_string(lsa->name)); + json_object_object_add(json_obj, "pendingLsaLsAck", json_arr); + + if (oi->gr.hello_delay.interval != 0) + json_object_int_add(json_obj, "grHelloDelaySecs", + oi->gr.hello_delay.interval); + } else { + timerclear(&res); + if (event_is_scheduled(oi->thread_send_lsupdate)) + timersub(&oi->thread_send_lsupdate->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for LSUpdate in Time %s [thread %s]\n", + oi->lsupdate_list->count, duration, + (event_is_scheduled(oi->thread_send_lsupdate) ? "on" + : "off")); + for (ALL_LSDB(oi->lsupdate_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + timerclear(&res); + if (event_is_scheduled(oi->thread_send_lsack)) + timersub(&oi->thread_send_lsack->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for LSAck in Time %s [thread %s]\n", + oi->lsack_list->count, duration, + (event_is_scheduled(oi->thread_send_lsack) ? "on" + : "off")); + for (ALL_LSDB(oi->lsack_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + if (oi->gr.hello_delay.interval != 0) + vty_out(vty, " Graceful Restart hello delay: %us\n", + oi->gr.hello_delay.interval); + } + + /* BFD specific. */ + if (oi->bfd_config.enabled) { + if (use_json) { + struct json_object *json_bfd = json_object_new_object(); + + json_object_int_add(json_bfd, "detectMultiplier", + oi->bfd_config.detection_multiplier); + json_object_int_add(json_bfd, "rxMinInterval", + oi->bfd_config.min_rx); + json_object_int_add(json_bfd, "txMinInterval", + oi->bfd_config.min_tx); + json_object_object_add(json_obj, "peerBfdInfo", + json_bfd); + } else { + vty_out(vty, + " BFD: Detect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n", + oi->bfd_config.detection_multiplier, + oi->bfd_config.min_rx, oi->bfd_config.min_tx); + } + } + + if (use_json) + json_auth = json_object_new_object(); + if (oi->at_data.flags != 0) { + if (use_json) { + if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN)) { + json_object_string_add(json_auth, "authType", + "keychain"); + json_object_string_add(json_auth, "keychainName", + oi->at_data.keychain); + } else if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_MANUAL_KEY)) + json_object_string_add(json_auth, "authType", + "manualkey"); + json_object_int_add(json_auth, "txPktDrop", + oi->at_data.tx_drop); + json_object_int_add(json_auth, "rxPktDrop", + oi->at_data.rx_drop); + } else { + if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN)) + vty_out(vty, + " Authentication Trailer is enabled with key-chain %s\n", + oi->at_data.keychain); + else if (CHECK_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_MANUAL_KEY)) + vty_out(vty, + " Authentication trailer is enabled with manual key\n"); + vty_out(vty, + " Packet drop Tx %u, Packet drop Rx %u\n", + oi->at_data.tx_drop, oi->at_data.rx_drop); + } + } else { + if (use_json) + json_object_string_add(json_auth, "authType", "NULL"); + else + vty_out(vty, " Authentication Trailer is disabled\n"); + } + + if (use_json) + json_object_object_add(json_obj, "authInfo", json_auth); + + return 0; +} + +/* Find the global address to be used as a forwarding address in NSSA LSA.*/ +struct in6_addr *ospf6_interface_get_global_address(struct interface *ifp) +{ + struct connected *c; + + /* for each connected address */ + frr_each (if_connected, ifp->connected, c) { + /* if family not AF_INET6, ignore */ + if (c->address->family != AF_INET6) + continue; + + if (!IN6_IS_ADDR_LINKLOCAL(&c->address->u.prefix6)) + return &c->address->u.prefix6; + } + + return NULL; +} + + +static int show_ospf6_interface_common(struct vty *vty, vrf_id_t vrf_id, + int argc, struct cmd_token **argv, + int idx_ifname, int intf_idx, + int json_idx, bool uj) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface *ifp; + json_object *json; + json_object *json_int; + + if (uj) { + json = json_object_new_object(); + if (argc == json_idx) { + ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); + json_int = json_object_new_object(); + if (ifp == NULL) { + json_object_string_add(json, "noSuchInterface", + argv[idx_ifname]->arg); + vty_json(vty, json); + json_object_free(json_int); + return CMD_WARNING; + } + ospf6_interface_show(vty, ifp, json_int, uj); + json_object_object_add(json, ifp->name, json_int); + } else { + FOR_ALL_INTERFACES (vrf, ifp) { + json_int = json_object_new_object(); + ospf6_interface_show(vty, ifp, json_int, uj); + json_object_object_add(json, ifp->name, + json_int); + } + } + vty_json(vty, json); + } else { + if (argc == intf_idx) { + ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); + if (ifp == NULL) { + vty_out(vty, "No such Interface: %s\n", + argv[idx_ifname]->arg); + return CMD_WARNING; + } + ospf6_interface_show(vty, ifp, NULL, uj); + } else { + FOR_ALL_INTERFACES (vrf, ifp) + ospf6_interface_show(vty, ifp, NULL, uj); + } + } + return CMD_SUCCESS; +} + +/* show interface */ +DEFUN(show_ipv6_ospf6_interface, + show_ipv6_ospf6_interface_ifname_cmd, + "show ipv6 ospf6 [vrf ] interface [IFNAME] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + INTERFACE_STR + IFNAME_STR + JSON_STR) +{ + int idx_ifname = 4; + int intf_idx = 5; + int json_idx = 6; + struct listnode *node; + struct ospf6 *ospf6; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_ifname += 2; + intf_idx += 2; + json_idx += 2; + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + show_ospf6_interface_common(vty, ospf6->vrf_id, argc, + argv, idx_ifname, intf_idx, + json_idx, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int ospf6_interface_show_traffic(struct vty *vty, + struct interface *intf_ifp, + int display_once, json_object *json, + bool use_json, vrf_id_t vrf_id) +{ + struct interface *ifp; + struct vrf *vrf = NULL; + struct ospf6_interface *oi = NULL; + json_object *json_interface; + + if (!display_once && !use_json) { + vty_out(vty, "\n"); + vty_out(vty, "%-12s%-17s%-17s%-17s%-17s%-17s\n", "Interface", + " HELLO", " DB-Desc", " LS-Req", " LS-Update", + " LS-Ack"); + vty_out(vty, "%-10s%-18s%-18s%-17s%-17s%-17s\n", "", + " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", + " Rx/Tx"); + vty_out(vty, + "--------------------------------------------------------------------------------------------\n"); + } + + if (intf_ifp == NULL) { + vrf = vrf_lookup_by_id(vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->info) + oi = (struct ospf6_interface *)ifp->info; + else + continue; + + if (use_json) { + json_interface = json_object_new_object(); + json_object_int_add(json_interface, "helloRx", + oi->hello_in); + json_object_int_add(json_interface, "helloTx", + oi->hello_out); + json_object_int_add(json_interface, "dbDescRx", + oi->db_desc_in); + json_object_int_add(json_interface, "dbDescTx", + oi->db_desc_out); + json_object_int_add(json_interface, "lsReqRx", + oi->ls_req_in); + json_object_int_add(json_interface, "lsReqTx", + oi->ls_req_out); + json_object_int_add(json_interface, + "lsUpdateRx", oi->ls_upd_in); + json_object_int_add(json_interface, "lsUpdateTx", + oi->ls_upd_out); + json_object_int_add(json_interface, "lsAckRx", + oi->ls_ack_in); + json_object_int_add(json_interface, "lsAckTx", + oi->ls_ack_out); + + json_object_object_add(json, oi->interface->name, + json_interface); + } else + vty_out(vty, + "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u\n", + oi->interface->name, oi->hello_in, + oi->hello_out, oi->db_desc_in, + oi->db_desc_out, oi->ls_req_in, + oi->ls_req_out, oi->ls_upd_in, + oi->ls_upd_out, oi->ls_ack_in, + oi->ls_ack_out); + } + } else { + oi = intf_ifp->info; + if (oi == NULL) + return CMD_WARNING; + + if (use_json) { + json_interface = json_object_new_object(); + json_object_int_add(json_interface, "helloRx", + oi->hello_in); + json_object_int_add(json_interface, "helloTx", + oi->hello_out); + json_object_int_add(json_interface, "dbDescRx", + oi->db_desc_in); + json_object_int_add(json_interface, "dbDescTx", + oi->db_desc_out); + json_object_int_add(json_interface, "lsReqRx", + oi->ls_req_in); + json_object_int_add(json_interface, "lsReqTx", + oi->ls_req_out); + json_object_int_add(json_interface, "lsUpdateRx", + oi->ls_upd_in); + json_object_int_add(json_interface, "lsUpdateTx", + oi->ls_upd_out); + json_object_int_add(json_interface, "lsAckRx", + oi->ls_ack_in); + json_object_int_add(json_interface, "lsAckTx", + oi->ls_ack_out); + + json_object_object_add(json, oi->interface->name, + json_interface); + } else + vty_out(vty, + "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u\n", + oi->interface->name, oi->hello_in, + oi->hello_out, oi->db_desc_in, oi->db_desc_out, + oi->ls_req_in, oi->ls_req_out, oi->ls_upd_in, + oi->ls_upd_out, oi->ls_ack_in, oi->ls_ack_out); + } + + return CMD_SUCCESS; +} + +static int ospf6_interface_show_traffic_common(struct vty *vty, int argc, + struct cmd_token **argv, + vrf_id_t vrf_id, bool uj) +{ + int idx_ifname = 0; + int display_once = 0; + char *intf_name = NULL; + struct interface *ifp = NULL; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + if (argv_find(argv, argc, "IFNAME", &idx_ifname)) { + intf_name = argv[idx_ifname]->arg; + ifp = if_lookup_by_name(intf_name, vrf_id); + if (uj) { + if (ifp == NULL) { + json_object_string_add(json, "status", + "No Such Interface"); + json_object_string_add(json, "interface", + intf_name); + vty_json(vty, json); + return CMD_WARNING; + } + if (ifp->info == NULL) { + json_object_string_add( + json, "status", + "OSPF not enabled on this interface"); + json_object_string_add(json, "interface", + intf_name); + vty_json(vty, json); + return 0; + } + } else { + if (ifp == NULL) { + vty_out(vty, "No such Interface: %s\n", + intf_name); + return CMD_WARNING; + } + if (ifp->info == NULL) { + vty_out(vty, + " OSPF not enabled on this interface %s\n", + intf_name); + return 0; + } + } + } + + ospf6_interface_show_traffic(vty, ifp, display_once, json, uj, vrf_id); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +/* show interface */ +DEFUN(show_ipv6_ospf6_interface_traffic, + show_ipv6_ospf6_interface_traffic_cmd, + "show ipv6 ospf6 [vrf ] interface traffic [IFNAME] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + INTERFACE_STR + "Protocol Packet counters\n" + IFNAME_STR + JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_interface_show_traffic_common(vty, argc, argv, + ospf6->vrf_id, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + + +DEFUN(show_ipv6_ospf6_interface_ifname_prefix, + show_ipv6_ospf6_interface_ifname_prefix_cmd, + "show ipv6 ospf6 [vrf ] interface IFNAME prefix " + "[ []>] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + INTERFACE_STR IFNAME_STR + "Display connected prefixes to advertise\n" + "Display details of the prefixes\n" + OSPF6_ROUTE_ADDRESS_STR + OSPF6_ROUTE_PREFIX_STR + OSPF6_ROUTE_MATCH_STR + "Display details of the prefixes\n" + JSON_STR) +{ + int idx_ifname = 4; + int idx_prefix = 6; + struct ospf6_interface *oi; + bool uj = use_json(argc, argv); + + struct ospf6 *ospf6; + struct listnode *node; + struct interface *ifp; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_ifname += 2; + idx_prefix += 2; + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ifp = if_lookup_by_name(argv[idx_ifname]->arg, + ospf6->vrf_id); + if (ifp == NULL) { + vty_out(vty, "No such Interface: %s\n", + argv[idx_ifname]->arg); + return CMD_WARNING; + } + + oi = ifp->info; + if (oi == NULL || + CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { + vty_out(vty, + "Interface %s not attached to area\n", + argv[idx_ifname]->arg); + return CMD_WARNING; + } + + ospf6_route_table_show(vty, idx_prefix, argc, argv, + oi->route_connected, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_interface_prefix, + show_ipv6_ospf6_interface_prefix_cmd, + "show ipv6 ospf6 [vrf ] interface prefix " + "[ []>] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + INTERFACE_STR + "Display connected prefixes to advertise\n" + "Display details of the prefixes\n" + OSPF6_ROUTE_ADDRESS_STR + OSPF6_ROUTE_PREFIX_STR + OSPF6_ROUTE_MATCH_STR + "Display details of the prefixes\n" + JSON_STR) +{ + struct vrf *vrf = NULL; + int idx_prefix = 5; + struct ospf6_interface *oi; + struct interface *ifp; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_prefix += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + vrf = vrf_lookup_by_id(ospf6->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) { + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL || + CHECK_FLAG(oi->flag, + OSPF6_INTERFACE_DISABLE)) + continue; + + ospf6_route_table_show(vty, idx_prefix, argc, + argv, + oi->route_connected, uj); + } + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +void ospf6_interface_start(struct ospf6_interface *oi) +{ + struct ospf6 *ospf6; + struct ospf6_area *oa; + + if (oi->area_id_format == OSPF6_AREA_FMT_UNSET) + return; + + if (oi->area) { + /* Recompute cost */ + ospf6_interface_recalculate_cost(oi); + return; + } + + ospf6 = oi->interface->vrf->info; + if (!ospf6) + return; + + oa = ospf6_area_lookup(oi->area_id, ospf6); + if (oa == NULL) + oa = ospf6_area_create(oi->area_id, ospf6, oi->area_id_format); + + /* attach interface to area */ + listnode_add(oa->if_list, oi); + oi->area = oa; + + SET_FLAG(oa->flag, OSPF6_AREA_ENABLE); + + /* start up */ + ospf6_interface_enable(oi); + + /* If the router is ABR, originate summary routes */ + if (ospf6_check_and_set_router_abr(ospf6)) { + ospf6_abr_enable_area(oa); + ospf6_schedule_abr_task(ospf6); + } +} + +void ospf6_interface_stop(struct ospf6_interface *oi) +{ + struct ospf6_area *oa; + + oa = oi->area; + if (!oa) + return; + + ospf6_interface_disable(oi); + + listnode_delete(oa->if_list, oi); + oi->area = NULL; + + if (oa->if_list->count == 0) { + UNSET_FLAG(oa->flag, OSPF6_AREA_ENABLE); + ospf6_abr_disable_area(oa); + } +} + +/* interface variable set command */ +DEFUN (ipv6_ospf6_area, + ipv6_ospf6_area_cmd, + "ipv6 ospf6 area ", + IP6_STR + OSPF6_STR + "Specify the OSPF6 area ID\n" + "OSPF6 area ID in IPv4 address notation\n" + "OSPF6 area ID in decimal notation\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + int idx_ipv4 = 3; + uint32_t area_id; + int format; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + if (oi->area) { + vty_out(vty, "%s already attached to Area %s\n", + oi->interface->name, oi->area->name); + return CMD_SUCCESS; + } + + if (str2area_id(argv[idx_ipv4]->arg, &area_id, &format)) { + vty_out(vty, "Malformed Area-ID: %s\n", argv[idx_ipv4]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + oi->area_id = area_id; + oi->area_id_format = format; + + ospf6_interface_start(oi); + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_area, + no_ipv6_ospf6_area_cmd, + "no ipv6 ospf6 area []", + NO_STR + IP6_STR + OSPF6_STR + "Specify the OSPF6 area ID\n" + "OSPF6 area ID in IPv4 address notation\n" + "OSPF6 area ID in decimal notation\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + ospf6_interface_stop(oi); + + oi->area_id = 0; + oi->area_id_format = OSPF6_AREA_FMT_UNSET; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_ospf6_ifmtu, + ipv6_ospf6_ifmtu_cmd, + "ipv6 ospf6 ifmtu (1-65535)", + IP6_STR + OSPF6_STR + "Interface MTU\n" + "OSPFv3 Interface MTU\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + unsigned int ifmtu, iobuflen; + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + ifmtu = strtol(argv[idx_number]->arg, NULL, 10); + + if (oi->c_ifmtu == ifmtu) + return CMD_SUCCESS; + + if (ifp->mtu6 != 0 && ifp->mtu6 < ifmtu) { + vty_out(vty, + "%s's ospf6 ifmtu cannot go beyond physical mtu (%d)\n", + ifp->name, ifp->mtu6); + return CMD_WARNING_CONFIG_FAILED; + } + + if (oi->ifmtu < ifmtu) { + iobuflen = ospf6_iobuf_size(ifmtu); + if (iobuflen < ifmtu) { + vty_out(vty, + "%s's ifmtu is adjusted to I/O buffer size (%d).\n", + ifp->name, iobuflen); + oi->ifmtu = oi->c_ifmtu = iobuflen; + } else + oi->ifmtu = oi->c_ifmtu = ifmtu; + } else + oi->ifmtu = oi->c_ifmtu = ifmtu; + + /* re-establish adjacencies */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + EVENT_OFF(on->inactivity_timer); + event_add_event(master, inactivity_timer, on, 0, NULL); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_ifmtu, + no_ipv6_ospf6_ifmtu_cmd, + "no ipv6 ospf6 ifmtu [(1-65535)]", + NO_STR + IP6_STR + OSPF6_STR + "Interface MTU\n" + "OSPFv3 Interface MTU\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + unsigned int iobuflen; + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + if (oi->ifmtu < ifp->mtu) { + iobuflen = ospf6_iobuf_size(ifp->mtu); + if (iobuflen < ifp->mtu) { + vty_out(vty, + "%s's ifmtu is adjusted to I/O buffer size (%d).\n", + ifp->name, iobuflen); + oi->ifmtu = iobuflen; + } else + oi->ifmtu = ifp->mtu; + } else + oi->ifmtu = ifp->mtu; + + oi->c_ifmtu = 0; + + /* re-establish adjacencies */ + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + EVENT_OFF(on->inactivity_timer); + event_add_event(master, inactivity_timer, on, 0, NULL); + } + + return CMD_SUCCESS; +} + +DEFUN (ipv6_ospf6_cost, + ipv6_ospf6_cost_cmd, + "ipv6 ospf6 cost (1-65535)", + IP6_STR + OSPF6_STR + "Interface cost\n" + "Outgoing metric of this interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + unsigned long int lcost; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + lcost = strtol(argv[idx_number]->arg, NULL, 10); + + if (lcost > UINT32_MAX) { + vty_out(vty, "Cost %ld is out of range\n", lcost); + return CMD_WARNING_CONFIG_FAILED; + } + + SET_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST); + if (oi->cost == lcost) + return CMD_SUCCESS; + + oi->cost = lcost; + ospf6_interface_force_recalculate_cost(oi); + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_cost, + no_ipv6_ospf6_cost_cmd, + "no ipv6 ospf6 cost [(1-65535)]", + NO_STR + IP6_STR + OSPF6_STR + "Calculate interface cost from bandwidth\n" + "Outgoing metric of this interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + UNSET_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST); + + ospf6_interface_recalculate_cost(oi); + + return CMD_SUCCESS; +} + +DEFUN (auto_cost_reference_bandwidth, + auto_cost_reference_bandwidth_cmd, + "auto-cost reference-bandwidth (1-4294967)", + "Calculate OSPF interface cost according to bandwidth\n" + "Use reference bandwidth method to assign OSPF cost\n" + "The reference bandwidth in terms of Mbits per second\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + int idx_number = 2; + struct ospf6_area *oa; + struct ospf6_interface *oi; + struct listnode *i, *j; + uint32_t refbw; + + refbw = strtol(argv[idx_number]->arg, NULL, 10); + if (refbw < 1 || refbw > 4294967) { + vty_out(vty, "reference-bandwidth value is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* If reference bandwidth is changed. */ + if ((refbw) == o->ref_bandwidth) + return CMD_SUCCESS; + + o->ref_bandwidth = refbw; + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) + ospf6_interface_recalculate_cost(oi); + + return CMD_SUCCESS; +} + +DEFUN (no_auto_cost_reference_bandwidth, + no_auto_cost_reference_bandwidth_cmd, + "no auto-cost reference-bandwidth [(1-4294967)]", + NO_STR + "Calculate OSPF interface cost according to bandwidth\n" + "Use reference bandwidth method to assign OSPF cost\n" + "The reference bandwidth in terms of Mbits per second\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + struct ospf6_area *oa; + struct ospf6_interface *oi; + struct listnode *i, *j; + + if (o->ref_bandwidth == OSPF6_REFERENCE_BANDWIDTH) + return CMD_SUCCESS; + + o->ref_bandwidth = OSPF6_REFERENCE_BANDWIDTH; + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) + ospf6_interface_recalculate_cost(oi); + + return CMD_SUCCESS; +} + + +DEFUN (ospf6_write_multiplier, + ospf6_write_multiplier_cmd, + "write-multiplier (1-100)", + "Write multiplier\n" + "Maximum number of interface serviced per write\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + uint32_t write_oi_count; + + write_oi_count = strtol(argv[1]->arg, NULL, 10); + if (write_oi_count < 1 || write_oi_count > 100) { + vty_out(vty, "write-multiplier value is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + o->write_oi_count = write_oi_count; + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_write_multiplier, + no_ospf6_write_multiplier_cmd, + "no write-multiplier (1-100)", + NO_STR + "Write multiplier\n" + "Maximum number of interface serviced per write\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + + o->write_oi_count = OSPF6_WRITE_INTERFACE_COUNT_DEFAULT; + return CMD_SUCCESS; +} + +DEFUN (ipv6_ospf6_hellointerval, + ipv6_ospf6_hellointerval_cmd, + "ipv6 ospf6 hello-interval (1-65535)", + IP6_STR + OSPF6_STR + "Time between HELLO packets\n" + SECONDS_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->hello_interval = strmatch(argv[0]->text, "no") + ? OSPF_HELLO_INTERVAL_DEFAULT + : strtoul(argv[idx_number]->arg, NULL, 10); + + /* + * If the thread is scheduled, send the new hello now. + */ + if (event_is_scheduled(oi->thread_send_hello)) { + EVENT_OFF(oi->thread_send_hello); + + event_add_timer(master, ospf6_hello_send, oi, 0, + &oi->thread_send_hello); + } + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_hellointerval, + no_ipv6_ospf6_hellointerval_cmd, + "no ipv6 ospf6 hello-interval [(1-65535)]", + NO_STR + IP6_STR + OSPF6_STR + "Time between HELLO packets\n" + SECONDS_STR) + +/* interface variable set command */ +DEFUN (ipv6_ospf6_deadinterval, + ipv6_ospf6_deadinterval_cmd, + "ipv6 ospf6 dead-interval (1-65535)", + IP6_STR + OSPF6_STR + "Interval time after which a neighbor is declared down\n" + SECONDS_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->dead_interval = strmatch(argv[0]->arg, "no") + ? OSPF_ROUTER_DEAD_INTERVAL_DEFAULT + : strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_deadinterval, + no_ipv6_ospf6_deadinterval_cmd, + "no ipv6 ospf6 dead-interval [(1-65535)]", + NO_STR + IP6_STR + OSPF6_STR + "Interval time after which a neighbor is declared down\n" + SECONDS_STR) + +DEFPY(ipv6_ospf6_gr_hdelay, + ipv6_ospf6_gr_hdelay_cmd, + "ipv6 ospf6 graceful-restart hello-delay (1-1800)", + IP6_STR + OSPF6_STR + "Graceful Restart parameters\n" + "Delay the sending of the first hello packets.\n" + "Delay in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + + oi = ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + /* Note: new or updated value won't affect ongoing graceful restart. */ + oi->gr.hello_delay.interval = hello_delay; + + return CMD_SUCCESS; +} + +DEFPY(no_ipv6_ospf6_gr_hdelay, + no_ipv6_ospf6_gr_hdelay_cmd, + "no ipv6 ospf6 graceful-restart hello-delay [(1-1800)]", + NO_STR + IP6_STR + OSPF6_STR + "Graceful Restart parameters\n" + "Delay the sending of the first hello packets.\n" + "Delay in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + + oi = ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + oi->gr.hello_delay.interval = OSPF_HELLO_DELAY_DEFAULT; + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + + return CMD_SUCCESS; +} + +/* interface variable set command */ +DEFUN (ipv6_ospf6_transmitdelay, + ipv6_ospf6_transmitdelay_cmd, + "ipv6 ospf6 transmit-delay (1-3600)", + IP6_STR + OSPF6_STR + "Link state transmit delay\n" + SECONDS_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->transdelay = strmatch(argv[0]->text, "no") + ? OSPF6_INTERFACE_TRANSDELAY + : strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_transmitdelay, + no_ipv6_ospf6_transmitdelay_cmd, + "no ipv6 ospf6 transmit-delay [(1-3600)]", + NO_STR + IP6_STR + OSPF6_STR + "Link state transmit delay\n" + SECONDS_STR) + +/* interface variable set command */ +DEFUN (ipv6_ospf6_retransmitinterval, + ipv6_ospf6_retransmitinterval_cmd, + "ipv6 ospf6 retransmit-interval (1-65535)", + IP6_STR + OSPF6_STR + "Time between retransmitting lost link state advertisements\n" + SECONDS_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->rxmt_interval = strmatch(argv[0]->text, "no") + ? OSPF_RETRANSMIT_INTERVAL_DEFAULT + : strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_retransmitinterval, + no_ipv6_ospf6_retransmitinterval_cmd, + "no ipv6 ospf6 retransmit-interval [(1-65535)]", + NO_STR + IP6_STR + OSPF6_STR + "Time between retransmitting lost link state advertisements\n" + SECONDS_STR) + +/* interface variable set command */ +DEFUN (ipv6_ospf6_priority, + ipv6_ospf6_priority_cmd, + "ipv6 ospf6 priority (0-255)", + IP6_STR + OSPF6_STR + "Router priority\n" + "Priority value\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->priority = strmatch(argv[0]->text, "no") + ? OSPF6_INTERFACE_PRIORITY + : strtoul(argv[idx_number]->arg, NULL, 10); + + if (oi->area && (oi->state == OSPF6_INTERFACE_DROTHER || + oi->state == OSPF6_INTERFACE_BDR || + oi->state == OSPF6_INTERFACE_DR)) { + if (ospf6_interface_state_change(dr_election(oi), oi) == -1) + OSPF6_LINK_LSA_SCHEDULE(oi); + } + + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_priority, + no_ipv6_ospf6_priority_cmd, + "no ipv6 ospf6 priority [(0-255)]", + NO_STR + IP6_STR + OSPF6_STR + "Router priority\n" + "Priority value\n") + +DEFUN (ipv6_ospf6_instance, + ipv6_ospf6_instance_cmd, + "ipv6 ospf6 instance-id (0-255)", + IP6_STR + OSPF6_STR + "Instance ID for this interface\n" + "Instance ID value\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_number = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->instance_id = strmatch(argv[0]->text, "no") + ? OSPF6_INTERFACE_INSTANCE_ID + : strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_instance, + no_ipv6_ospf6_instance_cmd, + "no ipv6 ospf6 instance-id [(0-255)]", + NO_STR + IP6_STR + OSPF6_STR + "Instance ID for this interface\n" + "Instance ID value\n") + +DEFUN (ipv6_ospf6_passive, + ipv6_ospf6_passive_cmd, + "ipv6 ospf6 passive", + IP6_STR + OSPF6_STR + "Passive interface; no adjacency will be formed on this interface\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + SET_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE); + EVENT_OFF(oi->thread_send_hello); + EVENT_OFF(oi->thread_sso); + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + EVENT_OFF(on->inactivity_timer); + event_add_event(master, inactivity_timer, on, 0, NULL); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_passive, + no_ipv6_ospf6_passive_cmd, + "no ipv6 ospf6 passive", + NO_STR + IP6_STR + OSPF6_STR + "passive interface: No Adjacency will be formed on this I/F\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + UNSET_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE); + EVENT_OFF(oi->thread_send_hello); + EVENT_OFF(oi->thread_sso); + + /* don't send hellos over loopback interface */ + if (!if_is_loopback(oi->interface)) + event_add_timer(master, ospf6_hello_send, oi, 0, + &oi->thread_send_hello); + + return CMD_SUCCESS; +} + +DEFUN (ipv6_ospf6_mtu_ignore, + ipv6_ospf6_mtu_ignore_cmd, + "ipv6 ospf6 mtu-ignore", + IP6_STR + OSPF6_STR + "Disable MTU mismatch detection on this interface\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->mtu_ignore = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_mtu_ignore, + no_ipv6_ospf6_mtu_ignore_cmd, + "no ipv6 ospf6 mtu-ignore", + NO_STR + IP6_STR + OSPF6_STR + "Disable MTU mismatch detection on this interface\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + oi->mtu_ignore = 0; + + return CMD_SUCCESS; +} + +DEFUN(ipv6_ospf6_advertise_prefix_list, + ipv6_ospf6_advertise_prefix_list_cmd, + "ipv6 ospf6 advertise prefix-list PREFIXLIST6_NAME", + IP6_STR + OSPF6_STR + "Advertising options\n" + "Filter prefix using prefix-list\n" + "Prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_word = 4; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + if (oi->plist_name) + XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); + oi->plist_name = XSTRDUP(MTYPE_CFG_PLIST_NAME, argv[idx_word]->arg); + + ospf6_interface_connected_route_update(oi->interface); + + if (oi->area) { + OSPF6_LINK_LSA_SCHEDULE(oi); + if (oi->state == OSPF6_INTERFACE_DR) { + OSPF6_NETWORK_LSA_SCHEDULE(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + } + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); + } + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_ospf6_advertise_prefix_list, + no_ipv6_ospf6_advertise_prefix_list_cmd, + "no ipv6 ospf6 advertise prefix-list [PREFIXLIST6_NAME]", + NO_STR + IP6_STR + OSPF6_STR + "Advertising options\n" + "Filter prefix using prefix-list\n" + "Prefix list name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + assert(oi); + + if (oi->plist_name) + XFREE(MTYPE_CFG_PLIST_NAME, oi->plist_name); + + ospf6_interface_connected_route_update(oi->interface); + + if (oi->area) { + OSPF6_LINK_LSA_SCHEDULE(oi); + if (oi->state == OSPF6_INTERFACE_DR) { + OSPF6_NETWORK_LSA_SCHEDULE(oi); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi); + } + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oi->area); + } + + return CMD_SUCCESS; +} + +DEFUN (ipv6_ospf6_network, + ipv6_ospf6_network_cmd, + "ipv6 ospf6 network ", + IP6_STR + OSPF6_STR + "Network type\n" + "Specify OSPF6 broadcast network\n" + "Specify OSPF6 point-to-point network\n" + "Specify OSPF6 point-to-multipoint network\n" + ) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_network = 3; + struct ospf6_interface *oi; + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) { + oi = ospf6_interface_create(ifp); + } + assert(oi); + + oi->type_cfg = true; + + if (strncmp(argv[idx_network]->arg, "b", 1) == 0) { + if (oi->type == OSPF_IFTYPE_BROADCAST) + return CMD_SUCCESS; + + oi->type = OSPF_IFTYPE_BROADCAST; + } else if (strncmp(argv[idx_network]->arg, "point-to-p", 10) == 0) { + if (oi->type == OSPF_IFTYPE_POINTOPOINT) { + return CMD_SUCCESS; + } + oi->type = OSPF_IFTYPE_POINTOPOINT; + } else if (strncmp(argv[idx_network]->arg, "point-to-m", 10) == 0) { + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + return CMD_SUCCESS; + } + oi->type = OSPF_IFTYPE_POINTOMULTIPOINT; + } + + /* Reset the interface */ + event_execute(master, interface_down, oi, 0, NULL); + event_execute(master, interface_up, oi, 0, NULL); + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_ospf6_network, + no_ipv6_ospf6_network_cmd, + "no ipv6 ospf6 network []", + NO_STR + IP6_STR + OSPF6_STR + "Set default network type\n" + "Specify OSPF6 broadcast network\n" + "Specify OSPF6 point-to-point network\n" + "Specify OSPF6 point-to-multipoint network\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + int type; + + assert(ifp); + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + return CMD_SUCCESS; + + oi->type_cfg = false; + + type = ospf6_default_iftype(ifp); + if (oi->type == type) { + return CMD_SUCCESS; + } + oi->type = type; + + /* Reset the interface */ + event_execute(master, interface_down, oi, 0, NULL); + event_execute(master, interface_up, oi, 0, NULL); + + return CMD_SUCCESS; +} + +DEFPY (ipv6_ospf6_p2xp_only_cfg_neigh, + ipv6_ospf6_p2xp_only_cfg_neigh_cmd, + "[no] ipv6 ospf6 p2p-p2mp config-neighbors-only", + NO_STR + IP6_STR + OSPF6_STR + "Point-to-point and Point-to-Multipoint parameters\n" + "Only form adjacencies with explicitly configured neighbors\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + + if (no) { + if (!oi) + return CMD_SUCCESS; + + oi->p2xp_only_cfg_neigh = false; + return CMD_SUCCESS; + } + + if (!oi) + oi = ospf6_interface_create(ifp); + + oi->p2xp_only_cfg_neigh = true; + return CMD_SUCCESS; +} + +DEFPY (ipv6_ospf6_p2xp_no_multicast_hello, + ipv6_ospf6_p2xp_no_multicast_hello_cmd, + "[no] ipv6 ospf6 p2p-p2mp disable-multicast-hello", + NO_STR + IP6_STR + OSPF6_STR + "Point-to-point and Point-to-Multipoint parameters\n" + "Do not send multicast hellos\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + + if (no) { + if (!oi) + return CMD_SUCCESS; + + oi->p2xp_no_multicast_hello = false; + return CMD_SUCCESS; + } + + if (!oi) + oi = ospf6_interface_create(ifp); + + oi->p2xp_no_multicast_hello = true; + return CMD_SUCCESS; +} + +DEFPY (ipv6_ospf6_p2xp_connected_pfx, + ipv6_ospf6_p2xp_connected_pfx_cmd, + "[no] ipv6 ospf6 p2p-p2mp connected-prefixes ", + NO_STR + IP6_STR + OSPF6_STR + "Point-to-point and Point-to-Multipoint parameters\n" + "Adjust handling of directly connected prefixes\n" + "Advertise prefixes and own /128 (default for PtP)\n" + "Ignore, only advertise own /128 (default for PtMP)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + bool old_incl, old_excl; + + if (no && !oi) + return CMD_SUCCESS; + + if (!oi) + oi = ospf6_interface_create(ifp); + + old_incl = oi->p2xp_connected_pfx_include; + old_excl = oi->p2xp_connected_pfx_exclude; + oi->p2xp_connected_pfx_include = false; + oi->p2xp_connected_pfx_exclude = false; + + if (incl && !no) + oi->p2xp_connected_pfx_include = true; + if (excl && !no) + oi->p2xp_connected_pfx_exclude = true; + + if (oi->p2xp_connected_pfx_include != old_incl || + oi->p2xp_connected_pfx_exclude != old_excl) + ospf6_interface_connected_route_update(ifp); + return CMD_SUCCESS; +} + +ALIAS (ipv6_ospf6_p2xp_connected_pfx, + no_ipv6_ospf6_p2xp_connected_pfx_cmd, + "no ipv6 ospf6 p2p-p2mp connected-prefixes", + NO_STR + IP6_STR + OSPF6_STR + "Point-to-point and Point-to-Multipoint parameters\n" + "Adjust handling of directly connected prefixes\n") + + +static int config_write_ospf6_interface(struct vty *vty, struct vrf *vrf) +{ + struct ospf6_interface *oi; + struct interface *ifp; + char buf[INET_ADDRSTRLEN]; + + FOR_ALL_INTERFACES (vrf, ifp) { + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + continue; + + if_vty_config_start(vty, ifp); + + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + if (oi->area_id_format != OSPF6_AREA_FMT_UNSET) { + area_id2str(buf, sizeof(buf), oi->area_id, + oi->area_id_format); + vty_out(vty, " ipv6 ospf6 area %s\n", buf); + } + if (oi->c_ifmtu) + vty_out(vty, " ipv6 ospf6 ifmtu %d\n", oi->c_ifmtu); + + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_NOAUTOCOST)) + vty_out(vty, " ipv6 ospf6 cost %d\n", oi->cost); + + if (oi->hello_interval != OSPF6_INTERFACE_HELLO_INTERVAL) + vty_out(vty, " ipv6 ospf6 hello-interval %d\n", + oi->hello_interval); + + if (oi->dead_interval != OSPF6_INTERFACE_DEAD_INTERVAL) + vty_out(vty, " ipv6 ospf6 dead-interval %d\n", + oi->dead_interval); + + if (oi->rxmt_interval != OSPF6_INTERFACE_RXMT_INTERVAL) + vty_out(vty, " ipv6 ospf6 retransmit-interval %d\n", + oi->rxmt_interval); + + if (oi->priority != OSPF6_INTERFACE_PRIORITY) + vty_out(vty, " ipv6 ospf6 priority %d\n", oi->priority); + + if (oi->transdelay != OSPF6_INTERFACE_TRANSDELAY) + vty_out(vty, " ipv6 ospf6 transmit-delay %d\n", + oi->transdelay); + + if (oi->instance_id != OSPF6_INTERFACE_INSTANCE_ID) + vty_out(vty, " ipv6 ospf6 instance-id %d\n", + oi->instance_id); + + if (oi->plist_name) + vty_out(vty, " ipv6 ospf6 advertise prefix-list %s\n", + oi->plist_name); + + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE)) + vty_out(vty, " ipv6 ospf6 passive\n"); + + if (oi->mtu_ignore) + vty_out(vty, " ipv6 ospf6 mtu-ignore\n"); + + if (oi->type_cfg && oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) + vty_out(vty, + " ipv6 ospf6 network point-to-multipoint\n"); + else if (oi->type_cfg && oi->type == OSPF_IFTYPE_POINTOPOINT) + vty_out(vty, " ipv6 ospf6 network point-to-point\n"); + else if (oi->type_cfg && oi->type == OSPF_IFTYPE_BROADCAST) + vty_out(vty, " ipv6 ospf6 network broadcast\n"); + + if (oi->gr.hello_delay.interval != OSPF_HELLO_DELAY_DEFAULT) + vty_out(vty, + " ipv6 ospf6 graceful-restart hello-delay %u\n", + oi->gr.hello_delay.interval); + if (oi->p2xp_only_cfg_neigh) + vty_out(vty, + " ipv6 ospf6 p2p-p2mp config-neighbors-only\n"); + + if (oi->p2xp_no_multicast_hello) + vty_out(vty, + " ipv6 ospf6 p2p-p2mp disable-multicast-hello\n"); + + if (oi->p2xp_connected_pfx_include) + vty_out(vty, + " ipv6 ospf6 p2p-p2mp connected-prefixes include\n"); + else if (oi->p2xp_connected_pfx_exclude) + vty_out(vty, + " ipv6 ospf6 p2p-p2mp connected-prefixes exclude\n"); + + config_write_ospf6_p2xp_neighbor(vty, oi); + ospf6_bfd_write_config(vty, oi); + + ospf6_auth_write_config(vty, &oi->at_data); + if_vty_config_end(vty); + } + return 0; +} + +/* Configuration write function for ospfd. */ +static int config_write_interface(struct vty *vty) +{ + int write = 0; + struct vrf *vrf = NULL; + + /* Display all VRF aware OSPF interface configuration */ + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + write += config_write_ospf6_interface(vty, vrf); + } + + return write; +} + +static int ospf6_ifp_create(struct interface *ifp) +{ + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface add: %s index %d mtu %d", ifp->name, + ifp->ifindex, ifp->mtu6); + + if (ifp->info) + ospf6_interface_start(ifp->info); + + return 0; +} + +static int ospf6_ifp_up(struct interface *ifp) +{ + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface state change: %s index %d flags %llx metric %d mtu %d bandwidth %d", + ifp->name, ifp->ifindex, + (unsigned long long)ifp->flags, ifp->metric, + ifp->mtu6, ifp->bandwidth); + + ospf6_interface_state_update(ifp); + + return 0; +} + +static int ospf6_ifp_down(struct interface *ifp) +{ + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface state change: %s index %d flags %llx metric %d mtu %d bandwidth %d", + ifp->name, ifp->ifindex, + (unsigned long long)ifp->flags, ifp->metric, + ifp->mtu6, ifp->bandwidth); + + ospf6_interface_state_update(ifp); + + return 0; +} + +static int ospf6_ifp_destroy(struct interface *ifp) +{ + if (if_is_up(ifp)) + zlog_warn("Zebra: got delete of %s, but interface is still up", + ifp->name); + + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface delete: %s index %d mtu %d", + ifp->name, ifp->ifindex, ifp->mtu6); + + if (ifp->info) + ospf6_interface_stop(ifp->info); + + return 0; +} + +void ospf6_interface_init(void) +{ + /* Install interface node. */ + if_cmd_init(config_write_interface); + hook_register_prio(if_real, 0, ospf6_ifp_create); + hook_register_prio(if_up, 0, ospf6_ifp_up); + hook_register_prio(if_down, 0, ospf6_ifp_down); + hook_register_prio(if_unreal, 0, ospf6_ifp_destroy); + + install_element(VIEW_NODE, &show_ipv6_ospf6_interface_prefix_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_interface_ifname_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_interface_ifname_prefix_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_interface_traffic_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_area_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_area_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_cost_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_cost_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_ifmtu_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_ifmtu_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_deadinterval_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_hellointerval_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_gr_hdelay_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_priority_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_retransmitinterval_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_transmitdelay_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_instance_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_deadinterval_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_hellointerval_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_gr_hdelay_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_priority_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_retransmitinterval_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_transmitdelay_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_instance_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_passive_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_passive_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_mtu_ignore_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_mtu_ignore_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_advertise_prefix_list_cmd); + install_element(INTERFACE_NODE, + &no_ipv6_ospf6_advertise_prefix_list_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_network_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_network_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_p2xp_only_cfg_neigh_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_p2xp_no_multicast_hello_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_p2xp_connected_pfx_cmd); + install_element(INTERFACE_NODE, &no_ipv6_ospf6_p2xp_connected_pfx_cmd); + + /* reference bandwidth commands */ + install_element(OSPF6_NODE, &auto_cost_reference_bandwidth_cmd); + install_element(OSPF6_NODE, &no_auto_cost_reference_bandwidth_cmd); + /* write-multiplier commands */ + install_element(OSPF6_NODE, &ospf6_write_multiplier_cmd); + install_element(OSPF6_NODE, &no_ospf6_write_multiplier_cmd); +} + +/* Clear the specified interface structure */ +void ospf6_interface_clear(struct interface *ifp) +{ + struct ospf6_interface *oi; + + if (!if_is_operative(ifp)) + return; + + if (ifp->info == NULL) + return; + + oi = (struct ospf6_interface *)ifp->info; + + if (IS_OSPF6_DEBUG_INTERFACE) + zlog_debug("Interface %s: clear by reset", ifp->name); + + /* Reset the interface */ + event_execute(master, interface_down, oi, 0, NULL); + event_execute(master, interface_up, oi, 0, NULL); +} + +/* Clear interface */ +DEFUN (clear_ipv6_ospf6_interface, + clear_ipv6_ospf6_interface_cmd, + "clear ipv6 ospf6 [vrf NAME] interface [IFNAME]", + CLEAR_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + INTERFACE_STR + IFNAME_STR + ) +{ + struct vrf *vrf; + int idx_vrf = 3; + int idx_ifname = 4; + struct interface *ifp; + const char *vrf_name; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + else + vrf_name = VRF_DEFAULT_NAME; + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "%% VRF %s not found\n", vrf_name); + return CMD_WARNING; + } + + if (!argv_find(argv, argc, "IFNAME", &idx_ifname)) { + /* Clear all the ospfv3 interfaces. */ + FOR_ALL_INTERFACES (vrf, ifp) + ospf6_interface_clear(ifp); + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name_vrf(argv[idx_ifname]->arg, vrf); + if (!ifp) { + vty_out(vty, "No such Interface: %s\n", + argv[idx_ifname]->arg); + return CMD_WARNING; + } + ospf6_interface_clear(ifp); + } + + return CMD_SUCCESS; +} + +void install_element_ospf6_clear_interface(void) +{ + install_element(ENABLE_NODE, &clear_ipv6_ospf6_interface_cmd); +} + +DEFUN (debug_ospf6_interface, + debug_ospf6_interface_cmd, + "debug ospf6 interface", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 Interface\n" + ) +{ + OSPF6_DEBUG_INTERFACE_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_interface, + no_debug_ospf6_interface_cmd, + "no debug ospf6 interface", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 Interface\n" + ) +{ + OSPF6_DEBUG_INTERFACE_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_interface(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_INTERFACE) + vty_out(vty, "debug ospf6 interface\n"); + return 0; +} + +void install_element_ospf6_debug_interface(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_interface_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_interface_cmd); + install_element(CONFIG_NODE, &debug_ospf6_interface_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_interface_cmd); +} + +void ospf6_auth_write_config(struct vty *vty, struct ospf6_auth_data *at_data) +{ + if (CHECK_FLAG(at_data->flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) + vty_out(vty, " ipv6 ospf6 authentication keychain %s\n", + at_data->keychain); + else if (CHECK_FLAG(at_data->flags, OSPF6_AUTH_TRAILER_MANUAL_KEY)) + vty_out(vty, + " ipv6 ospf6 authentication key-id %d hash-algo %s key %s\n", + at_data->key_id, + keychain_get_algo_name_by_id(at_data->hash_algo), + at_data->auth_key); +} + +DEFUN(ipv6_ospf6_intf_auth_trailer_keychain, + ipv6_ospf6_intf_auth_trailer_keychain_cmd, + "ipv6 ospf6 authentication keychain KEYCHAIN_NAME", + IP6_STR + OSPF6_STR + "Enable authentication on this interface\n" + "Keychain\n" + "Keychain name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int keychain_idx = 4; + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + assert(oi); + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_MANUAL_KEY)) { + vty_out(vty, + "Manual key configured, unconfigure it before configuring key chain\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + SET_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN); + if (oi->at_data.keychain) + XFREE(MTYPE_OSPF6_AUTH_KEYCHAIN, oi->at_data.keychain); + + oi->at_data.keychain = XSTRDUP(MTYPE_OSPF6_AUTH_KEYCHAIN, + argv[keychain_idx]->arg); + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_ospf6_intf_auth_trailer_keychain, + no_ipv6_ospf6_intf_auth_trailer_keychain_cmd, + "no ipv6 ospf6 authentication keychain [KEYCHAIN_NAME]", + NO_STR + IP6_STR + OSPF6_STR + "Enable authentication on this interface\n" + "Keychain\n" + "Keychain name\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + assert(oi); + if (!CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) + return CMD_SUCCESS; + + if (oi->at_data.keychain) { + oi->at_data.flags = 0; + XFREE(MTYPE_OSPF6_AUTH_KEYCHAIN, oi->at_data.keychain); + oi->at_data.keychain = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(ipv6_ospf6_intf_auth_trailer_key, + ipv6_ospf6_intf_auth_trailer_key_cmd, + "ipv6 ospf6 authentication key-id (1-65535) hash-algo " + " " + "key WORD", + IP6_STR + OSPF6_STR + "Authentication\n" + "Key ID\n" + "Key ID value\n" + "Cryptographic-algorithm\n" + "Use MD5 algorithm\n" + "Use HMAC-SHA-1 algorithm\n" + "Use HMAC-SHA-256 algorithm\n" + "Use HMAC-SHA-384 algorithm\n" + "Use HMAC-SHA-512 algorithm\n" + "Password\n" + "Password string (key)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int key_id_idx = 4; + int hash_algo_idx = 6; + int password_idx = 8; + struct ospf6_interface *oi; + uint8_t hash_algo = KEYCHAIN_ALGO_NULL; + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + assert(oi); + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) { + vty_out(vty, + "key chain configured, unconfigure it before configuring manual key\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + hash_algo = keychain_get_algo_id_by_name(argv[hash_algo_idx]->arg); +#ifndef CRYPTO_OPENSSL + if (hash_algo == KEYCHAIN_ALGO_NULL) { + vty_out(vty, + "Hash algorithm not supported, compile with --with-crypto=openssl\n"); + return CMD_WARNING_CONFIG_FAILED; + } +#endif /* CRYPTO_OPENSSL */ + + SET_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_MANUAL_KEY); + oi->at_data.hash_algo = hash_algo; + oi->at_data.key_id = (uint16_t)strtol(argv[key_id_idx]->arg, NULL, 10); + if (oi->at_data.auth_key) + XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY, oi->at_data.auth_key); + oi->at_data.auth_key = XSTRDUP(MTYPE_OSPF6_AUTH_MANUAL_KEY, + argv[password_idx]->arg); + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_ospf6_intf_auth_trailer_key, + no_ipv6_ospf6_intf_auth_trailer_key_cmd, + "no ipv6 ospf6 authentication key-id [(1-65535) hash-algo " + " " + "key WORD]", + NO_STR + IP6_STR + OSPF6_STR + "Authentication\n" + "Key ID\n" + "Key ID value\n" + "Cryptographic-algorithm\n" + "Use MD5 algorithm\n" + "Use HMAC-SHA-1 algorithm\n" + "Use HMAC-SHA-256 algorithm\n" + "Use HMAC-SHA-384 algorithm\n" + "Use HMAC-SHA-512 algorithm\n" + "Password\n" + "Password string (key)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi; +#ifndef CRYPTO_OPENSSL + int hash_algo_idx = 7; + uint8_t hash_algo = KEYCHAIN_ALGO_NULL; +#endif /* CRYPTO_OPENSSL */ + + oi = (struct ospf6_interface *)ifp->info; + if (oi == NULL) + oi = ospf6_interface_create(ifp); + + assert(oi); + if (!CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_MANUAL_KEY)) + return CMD_SUCCESS; + +#ifndef CRYPTO_OPENSSL + hash_algo = keychain_get_algo_id_by_name(argv[hash_algo_idx]->arg); + if (hash_algo == KEYCHAIN_ALGO_NULL) { + vty_out(vty, + "Hash algorithm not supported, compile with --with-crypto=openssl\n"); + return CMD_WARNING_CONFIG_FAILED; + } +#endif /* CRYPTO_OPENSSL */ + + if (oi->at_data.auth_key) { + oi->at_data.flags = 0; + XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY, oi->at_data.auth_key); + oi->at_data.auth_key = NULL; + } + + return CMD_SUCCESS; +} + +void ospf6_interface_auth_trailer_cmd_init(void) +{ + /*Install OSPF6 auth trailer commands at interface level */ + install_element(INTERFACE_NODE, + &ipv6_ospf6_intf_auth_trailer_keychain_cmd); + install_element(INTERFACE_NODE, + &no_ipv6_ospf6_intf_auth_trailer_keychain_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_intf_auth_trailer_key_cmd); + install_element(INTERFACE_NODE, + &no_ipv6_ospf6_intf_auth_trailer_key_cmd); +} diff --git a/ospf6d/ospf6_interface.h b/ospf6d/ospf6_interface.h new file mode 100644 index 0000000..46a7c90 --- /dev/null +++ b/ospf6d/ospf6_interface.h @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_INTERFACE_H +#define OSPF6_INTERFACE_H + +#include "qobj.h" +#include "hook.h" +#include "if.h" +#include "ospf6d.h" + +DECLARE_MTYPE(OSPF6_AUTH_MANUAL_KEY); + +/* Debug option */ +extern unsigned char conf_debug_ospf6_interface; +#define OSPF6_DEBUG_INTERFACE_ON() (conf_debug_ospf6_interface = 1) +#define OSPF6_DEBUG_INTERFACE_OFF() (conf_debug_ospf6_interface = 0) +#define IS_OSPF6_DEBUG_INTERFACE (conf_debug_ospf6_interface) + +struct ospf6_auth_data { + /* config data */ + uint8_t hash_algo; /* hash algorithm type */ + uint16_t key_id; /* key-id used as SA in auth packet */ + char *auth_key; /* Auth key */ + char *keychain; /* keychain name */ + + /* operational data */ + uint8_t flags; /* Flags related to auth config */ + + /* Counters and Statistics */ + uint32_t tx_drop; /* Pkt drop due to auth fail while sending */ + uint32_t rx_drop; /* Pkt drop due to auth fail while reading */ +}; + +PREDECL_RBTREE_UNIQ(ospf6_if_p2xp_neighcfgs); + +struct ospf6_if_p2xp_neighcfg { + struct ospf6_if_p2xp_neighcfgs_item item; + + struct ospf6_interface *ospf6_if; + struct in6_addr addr; + + bool cfg_cost : 1; + + uint32_t cost; + uint16_t poll_interval; + + /* NULL if down */ + struct ospf6_neighbor *active; + + struct event *t_unicast_hello; +}; + +/* Interface structure */ +struct ospf6_interface { + /* IF info from zebra */ + struct interface *interface; + + /* back pointer */ + struct ospf6_area *area; + + uint32_t area_id; + int area_id_format; + + /* list of ospf6 neighbor */ + struct list *neighbor_list; + + /* linklocal address of this I/F */ + struct in6_addr *linklocal_addr; + + /* Interface ID; use interface->ifindex */ + + /* ospf6 instance id */ + uint8_t instance_id; + + /* I/F transmission delay */ + uint32_t transdelay; + + /* Packet send buffer. */ + struct ospf6_fifo *obuf; /* Output queue */ + + /* Network Type */ + uint8_t type; + bool type_cfg; + + /* P2P/P2MP behavior: */ + + /* disable hellos on standard multicast? */ + bool p2xp_no_multicast_hello; + /* only allow explicitly configured neighbors? */ + bool p2xp_only_cfg_neigh; + /* override mode default for advertising connected prefixes. + * both false by default (= do include for PtP, exclude for PtMP) + */ + bool p2xp_connected_pfx_include; + bool p2xp_connected_pfx_exclude; + + struct ospf6_if_p2xp_neighcfgs_head p2xp_neighs; + + /* Router Priority */ + uint8_t priority; + + /* Time Interval */ + uint16_t hello_interval; + uint16_t dead_interval; + uint32_t rxmt_interval; + + /* Graceful-Restart data. */ + struct { + struct { + uint16_t interval; + uint16_t elapsed_seconds; + struct event *t_grace_send; + } hello_delay; + } gr; + + uint32_t state_change; + + /* Cost */ + uint32_t cost; + + /* I/F MTU */ + uint32_t ifmtu; + + /* Configured MTU */ + uint32_t c_ifmtu; + + /* Interface State */ + uint8_t state; + + /* Interface socket setting trial counter, resets on success */ + uint8_t sso_try_cnt; + struct event *thread_sso; + + /* OSPF6 Interface flag */ + char flag; + + /* MTU mismatch check */ + uint8_t mtu_ignore; + + /* Authentication trailer related config */ + struct ospf6_auth_data at_data; + + /* Decision of DR Election */ + in_addr_t drouter; + in_addr_t bdrouter; + in_addr_t prev_drouter; + in_addr_t prev_bdrouter; + + /* Linklocal LSA Database: includes Link-LSA */ + struct ospf6_lsdb *lsdb; + struct ospf6_lsdb *lsdb_self; + + struct ospf6_lsdb *lsupdate_list; + struct ospf6_lsdb *lsack_list; + + /* Ongoing Tasks */ + struct event *thread_send_hello; + struct event *thread_send_lsupdate; + struct event *thread_send_lsack; + + struct event *thread_network_lsa; + struct event *thread_link_lsa; + struct event *thread_intra_prefix_lsa; + struct event *thread_as_extern_lsa; + struct event *thread_wait_timer; + + struct ospf6_route_table *route_connected; + + /* last hello sent */ + struct timeval last_hello; + + /* prefix-list name to filter connected prefix */ + char *plist_name; + + /* BFD information */ + struct { + bool enabled; + uint8_t detection_multiplier; + uint32_t min_rx; + uint32_t min_tx; + char *profile; + } bfd_config; + + int on_write_q; + + /* Statistics Fields */ + uint32_t hello_in; + uint32_t hello_out; + uint32_t db_desc_in; + uint32_t db_desc_out; + uint32_t ls_req_in; + uint32_t ls_req_out; + uint32_t ls_upd_in; + uint32_t ls_upd_out; + uint32_t ls_ack_in; + uint32_t ls_ack_out; + uint32_t discarded; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(ospf6_interface); + +/* interface state */ +#define OSPF6_INTERFACE_NONE 0 +#define OSPF6_INTERFACE_DOWN 1 +#define OSPF6_INTERFACE_LOOPBACK 2 +#define OSPF6_INTERFACE_WAITING 3 +#define OSPF6_INTERFACE_POINTTOPOINT 4 +#define OSPF6_INTERFACE_POINTTOMULTIPOINT 5 +#define OSPF6_INTERFACE_DROTHER 6 +#define OSPF6_INTERFACE_BDR 7 +#define OSPF6_INTERFACE_DR 8 +#define OSPF6_INTERFACE_MAX 9 + +extern const char *const ospf6_interface_state_str[]; + +/* flags */ +#define OSPF6_INTERFACE_DISABLE 0x01 +#define OSPF6_INTERFACE_PASSIVE 0x02 +#define OSPF6_INTERFACE_NOAUTOCOST 0x04 + +/* default values */ +#define OSPF6_INTERFACE_HELLO_INTERVAL 10 +#define OSPF6_INTERFACE_DEAD_INTERVAL 40 +#define OSPF6_INTERFACE_RXMT_INTERVAL 5 +#define OSPF6_INTERFACE_COST 1 +#define OSPF6_INTERFACE_PRIORITY 1 +#define OSPF6_INTERFACE_TRANSDELAY 1 +#define OSPF6_INTERFACE_INSTANCE_ID 0 +#define OSPF6_INTERFACE_BANDWIDTH 10000 /* Mbps */ +#define OSPF6_REFERENCE_BANDWIDTH 100000 /* Kbps */ +#define OSPF6_INTERFACE_SSO_RETRY_INT 1 +#define OSPF6_INTERFACE_SSO_RETRY_MAX 5 + +/* Function Prototypes */ + +extern void ospf6_interface_start(struct ospf6_interface *oi); +extern void ospf6_interface_stop(struct ospf6_interface *oi); + +extern struct ospf6_interface * +ospf6_interface_lookup_by_ifindex(ifindex_t, vrf_id_t vrf_id); +extern struct ospf6_interface *ospf6_interface_create(struct interface *ifp); +extern void ospf6_interface_delete(struct ospf6_interface *oi); + +extern void ospf6_interface_enable(struct ospf6_interface *oi); +extern void ospf6_interface_disable(struct ospf6_interface *oi); + +extern void ospf6_interface_state_update(struct interface *ifp); +extern void ospf6_interface_connected_route_update(struct interface *ifp); +extern struct in6_addr * +ospf6_interface_get_global_address(struct interface *ifp); + +/* interface event */ +extern void interface_up(struct event *thread); +extern void interface_down(struct event *thread); +extern void wait_timer(struct event *thread); +extern void backup_seen(struct event *thread); +extern void neighbor_change(struct event *thread); + +extern void ospf6_interface_init(void); +extern void ospf6_interface_clear(struct interface *ifp); + +extern void install_element_ospf6_clear_interface(void); + +extern int config_write_ospf6_debug_interface(struct vty *vty); +extern void install_element_ospf6_debug_interface(void); +extern int ospf6_interface_neighbor_count(struct ospf6_interface *oi); +extern uint8_t dr_election(struct ospf6_interface *oi); + +extern void ospf6_interface_auth_trailer_cmd_init(void); +extern void ospf6_auth_write_config(struct vty *vty, + struct ospf6_auth_data *at_data); +DECLARE_HOOK(ospf6_interface_change, + (struct ospf6_interface * oi, int state, int old_state), + (oi, state, old_state)); + +#endif /* OSPF6_INTERFACE_H */ diff --git a/ospf6d/ospf6_intra.c b/ospf6d/ospf6_intra.c new file mode 100644 index 0000000..b06796a --- /dev/null +++ b/ospf6d/ospf6_intra.c @@ -0,0 +1,2513 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "linklist.h" +#include "frrevent.h" +#include "memory.h" +#include "if.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "vrf.h" + +#include "ospf6_proto.h" +#include "ospf6_message.h" +#include "ospf6_route.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_intra.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6_flood.h" +#include "ospf6d.h" +#include "ospf6_spf.h" +#include "ospf6_gr.h" + +unsigned char conf_debug_ospf6_brouter = 0; +uint32_t conf_debug_ospf6_brouter_specific_router_id; +uint32_t conf_debug_ospf6_brouter_specific_area_id; + +#define MAX_LSA_PAYLOAD (1024 + 256) +/******************************/ +/* RFC2740 3.4.3.1 Router-LSA */ +/******************************/ + +static char *ospf6_router_lsa_get_nbr_id(struct ospf6_lsa *lsa, char *buf, + int buflen, int pos) +{ + struct ospf6_router_lsa *router_lsa; + struct ospf6_router_lsdesc *lsdesc; + char *start, *end; + char buf1[INET_ADDRSTRLEN], buf2[INET_ADDRSTRLEN]; + + if (lsa) { + router_lsa = (struct ospf6_router_lsa + *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + + lsdesc = (struct ospf6_router_lsdesc + *)(start + + pos * (sizeof(struct + ospf6_router_lsdesc))); + if ((char *)lsdesc + sizeof(struct ospf6_router_lsdesc) + <= end) { + if (buf && (buflen > INET_ADDRSTRLEN * 2)) { + inet_ntop(AF_INET, + &lsdesc->neighbor_interface_id, buf1, + sizeof(buf1)); + inet_ntop(AF_INET, &lsdesc->neighbor_router_id, + buf2, sizeof(buf2)); + snprintf(buf, buflen, "%s/%s", buf2, buf1); + + return buf; + } + } + } + + return NULL; +} + +static int ospf6_router_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + char *start, *end, *current; + char buf[32], name[32], bits[16], options[32]; + struct ospf6_router_lsa *router_lsa; + struct ospf6_router_lsdesc *lsdesc; + json_object *json_arr = NULL; + json_object *json_loop; + + router_lsa = + (struct ospf6_router_lsa *)((char *)lsa->header + + sizeof(struct ospf6_lsa_header)); + + ospf6_capability_printbuf(router_lsa->bits, bits, sizeof(bits)); + ospf6_options_printbuf(router_lsa->options, options, sizeof(options)); + if (use_json) { + json_object_string_add(json_obj, "bits", bits); + json_object_string_add(json_obj, "options", options); + json_arr = json_object_new_array(); + } else + vty_out(vty, " Bits: %s Options: %s\n", bits, options); + + start = (char *)router_lsa + sizeof(struct ospf6_router_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + for (current = start; + current + sizeof(struct ospf6_router_lsdesc) <= end; + current += sizeof(struct ospf6_router_lsdesc)) { + lsdesc = (struct ospf6_router_lsdesc *)current; + + if (lsdesc->type == OSPF6_ROUTER_LSDESC_POINTTOPOINT) + snprintf(name, sizeof(name), "Point-To-Point"); + else if (lsdesc->type == OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK) + snprintf(name, sizeof(name), "Transit-Network"); + else if (lsdesc->type == OSPF6_ROUTER_LSDESC_STUB_NETWORK) + snprintf(name, sizeof(name), "Stub-Network"); + else if (lsdesc->type == OSPF6_ROUTER_LSDESC_VIRTUAL_LINK) + snprintf(name, sizeof(name), "Virtual-Link"); + else + snprintf(name, sizeof(name), "Unknown (%#x)", + lsdesc->type); + + if (use_json) { + json_loop = json_object_new_object(); + json_object_string_add(json_loop, "type", name); + json_object_int_add(json_loop, "metric", + ntohs(lsdesc->metric)); + json_object_string_addf( + json_loop, "interfaceId", "%pI4", + (in_addr_t *)&lsdesc->interface_id); + json_object_string_addf( + json_loop, "neighborInterfaceId", "%pI4", + (in_addr_t *)&lsdesc->neighbor_interface_id); + json_object_string_addf(json_loop, "neighborRouterId", + "%pI4", + &lsdesc->neighbor_router_id); + json_object_array_add(json_arr, json_loop); + } else { + vty_out(vty, " Type: %s Metric: %d\n", name, + ntohs(lsdesc->metric)); + vty_out(vty, " Interface ID: %s\n", + inet_ntop(AF_INET, &lsdesc->interface_id, buf, + sizeof(buf))); + vty_out(vty, " Neighbor Interface ID: %s\n", + inet_ntop(AF_INET, + &lsdesc->neighbor_interface_id, buf, + sizeof(buf))); + vty_out(vty, " Neighbor Router ID: %s\n", + inet_ntop(AF_INET, &lsdesc->neighbor_router_id, + buf, sizeof(buf))); + } + } + if (use_json) + json_object_object_add(json_obj, "lsaDescription", json_arr); + + return 0; +} + +static void ospf6_router_lsa_options_set(struct ospf6_area *oa, + struct ospf6_router_lsa *router_lsa) +{ + OSPF6_OPT_CLEAR_ALL(router_lsa->options); + memcpy(router_lsa->options, oa->options, 3); + + if (ospf6_check_and_set_router_abr(oa->ospf6)) + SET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_B); + else + UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_B); + + if (!IS_AREA_STUB(oa) && ospf6_asbr_is_asbr(oa->ospf6)) { + SET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_E); + } else { + UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_E); + } + + /* If the router is ASBR and the area-type is NSSA set the + * translate bit in router LSA. + */ + if (IS_AREA_NSSA(oa) + && (ospf6_asbr_is_asbr(oa->ospf6) || IS_OSPF6_ABR(oa->ospf6))) { + if (oa->NSSATranslatorRole == OSPF6_NSSA_ROLE_ALWAYS) + SET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_NT); + } else { + UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_NT); + } + + UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_V); + UNSET_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_W); +} + +int ospf6_router_is_stub_router(struct ospf6_lsa *lsa) +{ + struct ospf6_router_lsa *rtr_lsa; + + if (lsa != NULL && OSPF6_LSA_IS_TYPE(ROUTER, lsa)) { + rtr_lsa = (struct ospf6_router_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + if (!OSPF6_OPT_ISSET(rtr_lsa->options, OSPF6_OPT_R)) { + return OSPF6_IS_STUB_ROUTER; + } else if (!OSPF6_OPT_ISSET(rtr_lsa->options, OSPF6_OPT_V6)) { + return OSPF6_IS_STUB_ROUTER_V6; + } + } + + return OSPF6_NOT_STUB_ROUTER; +} + +void ospf6_router_lsa_originate(struct event *thread) +{ + struct ospf6_area *oa; + + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *lsa; + + uint32_t link_state_id = 0; + struct listnode *node, *nnode; + struct listnode *j; + struct ospf6_interface *oi; + struct ospf6_neighbor *on, *drouter = NULL; + struct ospf6_router_lsa *router_lsa; + struct ospf6_router_lsdesc *lsdesc; + uint16_t type; + uint32_t router; + int count; + + oa = (struct ospf6_area *)EVENT_ARG(thread); + + if (oa->ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) + zlog_debug("Originate Router-LSA for Area %s", oa->name); + + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + router_lsa = (struct ospf6_router_lsa *)ospf6_lsa_header_end(lsa_header); + + ospf6_router_lsa_options_set(oa, router_lsa); + + /* describe links for each interfaces */ + lsdesc = (struct ospf6_router_lsdesc + *)((caddr_t)router_lsa + + sizeof(struct ospf6_router_lsa)); + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { + /* Interfaces in state Down or Loopback are not described */ + if (oi->state == OSPF6_INTERFACE_DOWN + || oi->state == OSPF6_INTERFACE_LOOPBACK) + continue; + + /* Nor are interfaces without any full adjacencies described */ + count = 0; + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) + if (on->state == OSPF6_NEIGHBOR_FULL) + count++; + + if (count == 0) + continue; + + /* Multiple Router-LSA instance according to size limit setting + */ + if ((oa->router_lsa_size_limit != 0) + && ((size_t)((char *)lsdesc - buffer) + + sizeof(struct ospf6_router_lsdesc) + > oa->router_lsa_size_limit)) { + if ((caddr_t)lsdesc + == (caddr_t)router_lsa + + sizeof(struct ospf6_router_lsa)) { + zlog_warn( + "Size limit setting for Router-LSA too short"); + return; + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_ROUTER); + lsa_header->id = htonl(link_state_id); + lsa_header->adv_router = oa->ospf6->router_id; + lsa_header->seqnum = ospf6_new_ls_seqnum( + lsa_header->type, lsa_header->id, + lsa_header->adv_router, oa->lsdb); + lsa_header->length = + htons((caddr_t)lsdesc - (caddr_t)buffer); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oa); + + /* Reset Buffer to fill next Router LSA */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + router_lsa = (struct ospf6_router_lsa *) + ospf6_lsa_header_end(lsa_header); + + ospf6_router_lsa_options_set(oa, router_lsa); + + /* describe links for each interfaces */ + lsdesc = (struct ospf6_router_lsdesc + *)((caddr_t)router_lsa + + sizeof(struct ospf6_router_lsa)); + + link_state_id++; + } + + /* Point-to-Point interfaces */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT + || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) { + if (on->state != OSPF6_NEIGHBOR_FULL) + continue; + + lsdesc->type = OSPF6_ROUTER_LSDESC_POINTTOPOINT; + lsdesc->metric = htons(ospf6_neighbor_cost(on)); + lsdesc->interface_id = + htonl(oi->interface->ifindex); + lsdesc->neighbor_interface_id = + htonl(on->ifindex); + lsdesc->neighbor_router_id = on->router_id; + + lsdesc++; + } + } + + /* Broadcast and NBMA interfaces */ + else if (oi->type == OSPF_IFTYPE_BROADCAST) { + /* If this router is not DR, + and If this router not fully adjacent with DR, + this interface is not transit yet: ignore. */ + if (oi->state != OSPF6_INTERFACE_DR) { + drouter = + ospf6_neighbor_lookup(oi->drouter, oi); + if (drouter == NULL + || drouter->state != OSPF6_NEIGHBOR_FULL) + continue; + } + + lsdesc->type = OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK; + lsdesc->metric = htons(oi->cost); + lsdesc->interface_id = htonl(oi->interface->ifindex); + if (oi->state != OSPF6_INTERFACE_DR) { + lsdesc->neighbor_interface_id = + htonl(drouter->ifindex); + lsdesc->neighbor_router_id = drouter->router_id; + } else { + lsdesc->neighbor_interface_id = + htonl(oi->interface->ifindex); + lsdesc->neighbor_router_id = + oi->area->ospf6->router_id; + } + + lsdesc++; + } else { + assert(0); /* Unknown interface type */ + } + + /* Virtual links */ + /* xxx */ + /* Point-to-Multipoint interfaces */ + /* xxx */ + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_ROUTER); + lsa_header->id = htonl(link_state_id); + lsa_header->adv_router = oa->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oa->lsdb); + lsa_header->length = htons((caddr_t)lsdesc - (caddr_t)buffer); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oa); + + link_state_id++; + + /* Do premature-aging of rest, undesired Router-LSAs */ + type = ntohs(OSPF6_LSTYPE_ROUTER); + router = oa->ospf6->router_id; + count = 0; + for (ALL_LSDB_TYPED_ADVRTR(oa->lsdb, type, router, lsa)) { + if (ntohl(lsa->header->id) < link_state_id) + continue; + ospf6_lsa_purge(lsa); + count++; + } + + /* + * Waiting till the LSA is actually removed from the database to trigger + * SPF delays network convergence. Unlike IPv4, for an ABR, when all + * interfaces associated with an area are gone, triggering an SPF right + * away + * helps convergence with inter-area routes. + */ + if (count && !link_state_id) + ospf6_spf_schedule(oa->ospf6, + OSPF6_SPF_FLAGS_ROUTER_LSA_ORIGINATED); +} + +/*******************************/ +/* RFC2740 3.4.3.2 Network-LSA */ +/*******************************/ + +static char *ospf6_network_lsa_get_ar_id(struct ospf6_lsa *lsa, char *buf, + int buflen, int pos) +{ + char *start, *end, *current; + struct ospf6_network_lsa *network_lsa; + struct ospf6_network_lsdesc *lsdesc; + + if (lsa) { + network_lsa = (struct ospf6_network_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + start = (char *)network_lsa + sizeof(struct ospf6_network_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + current = start + pos * (sizeof(struct ospf6_network_lsdesc)); + + if ((current + sizeof(struct ospf6_network_lsdesc)) <= end) { + lsdesc = (struct ospf6_network_lsdesc *)current; + if (buf) { + inet_ntop(AF_INET, &lsdesc->router_id, buf, + buflen); + return buf; + } + } + } + + return NULL; +} + +static int ospf6_network_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + char *start, *end, *current; + struct ospf6_network_lsa *network_lsa; + struct ospf6_network_lsdesc *lsdesc; + char buf[128], options[32]; + json_object *json_arr = NULL; + + network_lsa = + (struct ospf6_network_lsa *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + ospf6_options_printbuf(network_lsa->options, options, sizeof(options)); + if (use_json) + json_object_string_add(json_obj, "options", options); + else + vty_out(vty, " Options: %s\n", options); + + start = (char *)network_lsa + sizeof(struct ospf6_network_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + if (use_json) + json_arr = json_object_new_array(); + + for (current = start; + current + sizeof(struct ospf6_network_lsdesc) <= end; + current += sizeof(struct ospf6_network_lsdesc)) { + lsdesc = (struct ospf6_network_lsdesc *)current; + inet_ntop(AF_INET, &lsdesc->router_id, buf, sizeof(buf)); + if (use_json) + json_object_array_add(json_arr, + json_object_new_string(buf)); + else + vty_out(vty, " Attached Router: %s\n", buf); + } + if (use_json) + json_object_object_add(json_obj, "attachedRouter", json_arr); + + return 0; +} + +void ospf6_network_lsa_originate(struct event *thread) +{ + struct ospf6_interface *oi; + + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + + int count; + struct ospf6_lsa *old, *lsa; + struct ospf6_network_lsa *network_lsa; + struct ospf6_network_lsdesc *lsdesc; + struct ospf6_neighbor *on; + struct ospf6_link_lsa *link_lsa; + struct listnode *i; + uint16_t type; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + /* The interface must be enabled until here. A Network-LSA of a + disabled interface (but was once enabled) should be flushed + by ospf6_lsa_refresh (), and does not come here. */ + assert(oi->area); + + if (oi->area->ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return; + } + + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_NETWORK), + htonl(oi->interface->ifindex), + oi->area->ospf6->router_id, oi->area->lsdb); + + /* Do not originate Network-LSA if not DR */ + if (oi->state != OSPF6_INTERFACE_DR) { + if (old) { + ospf6_lsa_purge(old); + /* + * Waiting till the LSA is actually removed from the + * database to + * trigger SPF delays network convergence. + */ + ospf6_spf_schedule( + oi->area->ospf6, + OSPF6_SPF_FLAGS_NETWORK_LSA_ORIGINATED); + } + return; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(NETWORK)) + zlog_debug("Originate Network-LSA for Interface %s", + oi->interface->name); + + /* If none of neighbor is adjacent to us */ + count = 0; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) + if (on->state == OSPF6_NEIGHBOR_FULL) + count++; + + if (count == 0) { + if (IS_OSPF6_DEBUG_ORIGINATE(NETWORK)) + zlog_debug("Interface stub, ignore"); + if (old) + ospf6_lsa_purge(old); + return; + } + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + network_lsa = + (struct ospf6_network_lsa *)ospf6_lsa_header_end(lsa_header); + + /* Collect the interface's Link-LSAs to describe + network's optional capabilities */ + type = htons(OSPF6_LSTYPE_LINK); + for (ALL_LSDB_TYPED(oi->lsdb, type, lsa)) { + link_lsa = (struct ospf6_link_lsa *)ospf6_lsa_header_end( + lsa->header); + network_lsa->options[0] |= link_lsa->options[0]; + network_lsa->options[1] |= link_lsa->options[1]; + network_lsa->options[2] |= link_lsa->options[2]; + } + + lsdesc = (struct ospf6_network_lsdesc + *)((caddr_t)network_lsa + + sizeof(struct ospf6_network_lsa)); + + /* set Link Description to the router itself */ + lsdesc->router_id = oi->area->ospf6->router_id; + lsdesc++; + + /* Walk through the neighbors */ + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) { + if (on->state != OSPF6_NEIGHBOR_FULL) + continue; + + /* set this neighbor's Router-ID to LSA */ + lsdesc->router_id = on->router_id; + lsdesc++; + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_NETWORK); + lsa_header->id = htonl(oi->interface->ifindex); + lsa_header->adv_router = oi->area->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oi->area->lsdb); + lsa_header->length = htons((caddr_t)lsdesc - (caddr_t)buffer); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oi->area); +} + + +/****************************/ +/* RFC2740 3.4.3.6 Link-LSA */ +/****************************/ + +static char *ospf6_link_lsa_get_prefix_str(struct ospf6_lsa *lsa, char *buf, + int buflen, int pos) +{ + char *start, *end, *current; + struct ospf6_link_lsa *link_lsa; + struct in6_addr in6; + struct ospf6_prefix *prefix; + int cnt = 0, prefixnum; + + if (lsa) { + link_lsa = (struct ospf6_link_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + if (pos == 0) { + inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, + buflen); + return (buf); + } + + prefixnum = ntohl(link_lsa->prefix_num); + if (pos > prefixnum) + return NULL; + + start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); + end = (char *)lsa->header + ntohs(lsa->header->length); + current = start; + + while (current + sizeof(struct ospf6_prefix) <= end) { + prefix = (struct ospf6_prefix *)current; + if (prefix->prefix_length == 0 + || current + OSPF6_PREFIX_SIZE(prefix) > end) { + return NULL; + } + + if (cnt < (pos - 1)) { + current += OSPF6_PREFIX_SIZE(prefix); + cnt++; + } else { + memset(&in6, 0, sizeof(in6)); + memcpy(&in6, OSPF6_PREFIX_BODY(prefix), + OSPF6_PREFIX_SPACE( + prefix->prefix_length)); + inet_ntop(AF_INET6, &in6, buf, buflen); + return (buf); + } + } + } + return NULL; +} + +static int ospf6_link_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + char *start, *end, *current; + struct ospf6_link_lsa *link_lsa; + int prefixnum; + char buf[128], options[32]; + struct ospf6_prefix *prefix; + struct in6_addr in6; + json_object *json_loop; + json_object *json_arr = NULL; + char prefix_string[133]; + + link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + ospf6_options_printbuf(link_lsa->options, options, sizeof(options)); + inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, sizeof(buf)); + prefixnum = ntohl(link_lsa->prefix_num); + + if (use_json) { + json_arr = json_object_new_array(); + json_object_int_add(json_obj, "priority", link_lsa->priority); + json_object_string_add(json_obj, "options", options); + json_object_string_add(json_obj, "linkLocalAddress", buf); + json_object_int_add(json_obj, "numberOfPrefix", prefixnum); + } else { + vty_out(vty, " Priority: %d Options: %s\n", + link_lsa->priority, options); + vty_out(vty, " LinkLocal Address: %s\n", buf); + vty_out(vty, " Number of Prefix: %d\n", prefixnum); + } + + start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); + end = ospf6_lsa_end(lsa->header); + + for (current = start; current < end; + current += OSPF6_PREFIX_SIZE(prefix)) { + prefix = (struct ospf6_prefix *)current; + if (prefix->prefix_length == 0 + || current + OSPF6_PREFIX_SIZE(prefix) > end) + break; + + ospf6_prefix_options_printbuf(prefix->prefix_options, buf, + sizeof(buf)); + if (use_json) { + json_loop = json_object_new_object(); + json_object_string_add(json_loop, "prefixOption", buf); + } else + vty_out(vty, " Prefix Options: %s\n", buf); + + memset(&in6, 0, sizeof(in6)); + memcpy(&in6, OSPF6_PREFIX_BODY(prefix), + OSPF6_PREFIX_SPACE(prefix->prefix_length)); + inet_ntop(AF_INET6, &in6, buf, sizeof(buf)); + if (use_json) { + snprintf(prefix_string, sizeof(prefix_string), "%s/%d", + buf, prefix->prefix_length); + json_object_string_add(json_loop, "prefix", + prefix_string); + json_object_array_add(json_arr, json_loop); + } else + vty_out(vty, " Prefix: %s/%d\n", buf, + prefix->prefix_length); + } + if (use_json) + json_object_object_add(json_obj, "prefix", json_arr); + + return 0; +} + +void ospf6_link_lsa_originate(struct event *thread) +{ + struct ospf6_interface *oi; + + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *old, *lsa; + + struct ospf6_link_lsa *link_lsa; + struct ospf6_route *route; + struct ospf6_prefix *op; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + assert(oi->area); + + if (oi->area->ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return; + } + + + /* find previous LSA */ + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_LINK), + htonl(oi->interface->ifindex), + oi->area->ospf6->router_id, oi->lsdb); + + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { + if (old) + ospf6_lsa_purge(old); + return; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(LINK)) + zlog_debug("Originate Link-LSA for Interface %s", + oi->interface->name); + + /* can't make Link-LSA if linklocal address not set */ + if (oi->linklocal_addr == NULL) { + if (IS_OSPF6_DEBUG_ORIGINATE(LINK)) + zlog_debug( + "No Linklocal address on %s, defer originating", + oi->interface->name); + if (old) + ospf6_lsa_purge(old); + return; + } + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + link_lsa = (struct ospf6_link_lsa *)ospf6_lsa_header_end(lsa_header); + + /* Fill Link-LSA */ + link_lsa->priority = oi->priority; + memcpy(link_lsa->options, oi->area->options, 3); + memcpy(&link_lsa->linklocal_addr, oi->linklocal_addr, + sizeof(struct in6_addr)); + link_lsa->prefix_num = htonl(oi->route_connected->count); + + op = (struct ospf6_prefix *)((caddr_t)link_lsa + + sizeof(struct ospf6_link_lsa)); + + /* connected prefix to advertise */ + for (route = ospf6_route_head(oi->route_connected); route; + route = ospf6_route_next(route)) { + op->prefix_length = route->prefix.prefixlen; + op->prefix_options = route->prefix_options; + op->prefix_metric = htons(0); + memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(op->prefix_length)); + op = OSPF6_PREFIX_NEXT(op); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_LINK); + lsa_header->id = htonl(oi->interface->ifindex); + lsa_header->adv_router = oi->area->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oi->lsdb); + lsa_header->length = htons((caddr_t)op - (caddr_t)buffer); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_interface(lsa, oi); +} + + +/*****************************************/ +/* RFC2740 3.4.3.7 Intra-Area-Prefix-LSA */ +/*****************************************/ +static char *ospf6_intra_prefix_lsa_get_prefix_str(struct ospf6_lsa *lsa, + char *buf, int buflen, + int pos) +{ + char *start, *end, *current; + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct in6_addr in6; + int prefixnum, cnt = 0; + struct ospf6_prefix *prefix; + char tbuf[16]; + + if (lsa) { + intra_prefix_lsa = + (struct ospf6_intra_prefix_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + prefixnum = ntohs(intra_prefix_lsa->prefix_num); + if ((pos + 1) > prefixnum) + return NULL; + + start = (char *)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa); + end = ospf6_lsa_end(lsa->header); + current = start; + + while (current + sizeof(struct ospf6_prefix) <= end) { + prefix = (struct ospf6_prefix *)current; + if (prefix->prefix_length == 0 + || current + OSPF6_PREFIX_SIZE(prefix) > end) { + return NULL; + } + + if (cnt < pos) { + current += OSPF6_PREFIX_SIZE(prefix); + cnt++; + } else { + memset(&in6, 0, sizeof(in6)); + memcpy(&in6, OSPF6_PREFIX_BODY(prefix), + OSPF6_PREFIX_SPACE( + prefix->prefix_length)); + inet_ntop(AF_INET6, &in6, buf, buflen); + snprintf(tbuf, sizeof(tbuf), "/%d", + prefix->prefix_length); + strlcat(buf, tbuf, buflen); + return (buf); + } + } + } + return NULL; +} + +static int ospf6_intra_prefix_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + char *start, *end, *current; + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + int prefixnum; + char buf[128]; + struct ospf6_prefix *prefix; + char id[16], adv_router[16]; + struct in6_addr in6; + json_object *json_loop; + json_object *json_arr = NULL; + char prefix_string[133]; + + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + prefixnum = ntohs(intra_prefix_lsa->prefix_num); + + if (use_json) { + json_arr = json_object_new_array(); + json_object_int_add(json_obj, "numberOfPrefix", prefixnum); + } else + vty_out(vty, " Number of Prefix: %d\n", prefixnum); + + inet_ntop(AF_INET, &intra_prefix_lsa->ref_id, id, sizeof(id)); + inet_ntop(AF_INET, &intra_prefix_lsa->ref_adv_router, adv_router, + sizeof(adv_router)); + if (use_json) { + json_object_string_add( + json_obj, "reference", + ospf6_lstype_name(intra_prefix_lsa->ref_type)); + json_object_string_add(json_obj, "referenceId", id); + json_object_string_add(json_obj, "referenceAdv", adv_router); + } else + vty_out(vty, " Reference: %s Id: %s Adv: %s\n", + ospf6_lstype_name(intra_prefix_lsa->ref_type), id, + adv_router); + + start = (char *)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa); + end = ospf6_lsa_end(lsa->header); + + for (current = start; current < end; + current += OSPF6_PREFIX_SIZE(prefix)) { + prefix = (struct ospf6_prefix *)current; + if (prefix->prefix_length == 0 + || current + OSPF6_PREFIX_SIZE(prefix) > end) + break; + + ospf6_prefix_options_printbuf(prefix->prefix_options, buf, + sizeof(buf)); + if (use_json) { + json_loop = json_object_new_object(); + json_object_string_add(json_loop, "prefixOption", buf); + } else + vty_out(vty, " Prefix Options: %s\n", buf); + + memset(&in6, 0, sizeof(in6)); + memcpy(&in6, OSPF6_PREFIX_BODY(prefix), + OSPF6_PREFIX_SPACE(prefix->prefix_length)); + inet_ntop(AF_INET6, &in6, buf, sizeof(buf)); + if (use_json) { + snprintf(prefix_string, sizeof(prefix_string), "%s/%d", + buf, prefix->prefix_length); + json_object_string_add(json_loop, "prefix", + prefix_string); + json_object_int_add(json_loop, "metric", + ntohs(prefix->prefix_metric)); + json_object_array_add(json_arr, json_loop); + } else { + vty_out(vty, " Prefix: %s/%d\n", buf, + prefix->prefix_length); + vty_out(vty, " Metric: %d\n", + ntohs(prefix->prefix_metric)); + } + } + if (use_json) + json_object_object_add(json_obj, "prefix", json_arr); + + return 0; +} + +void ospf6_intra_prefix_lsa_originate_stub(struct event *thread) +{ + struct ospf6_area *oa; + + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *old, *lsa, *old_next = NULL; + + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct ospf6_interface *oi; + struct ospf6_neighbor *on; + struct ospf6_route *route; + struct ospf6_prefix *op; + struct listnode *i, *j; + int full_count = 0; + unsigned short prefix_num = 0; + struct ospf6_route_table *route_advertise; + int ls_id = 0; + + oa = (struct ospf6_area *)EVENT_ARG(thread); + + if (oa->ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return; + } + + /* find previous LSA */ + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_INTRA_PREFIX), htonl(0), + oa->ospf6->router_id, oa->lsdb); + + if (!IS_AREA_ENABLED(oa)) { + if (old) { + ospf6_lsa_purge(old); + /* find previous LSA */ + old_next = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_INTRA_PREFIX), + htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); + + while (old_next) { + ospf6_lsa_purge(old_next); + old_next = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_INTRA_PREFIX), + htonl(++ls_id), oa->ospf6->router_id, + oa->lsdb); + } + } + return; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + "Originate Intra-Area-Prefix-LSA for area %s's stub prefix", + oa->name); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)ospf6_lsa_header_end( + lsa_header); + + /* Fill Intra-Area-Prefix-LSA */ + intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_ROUTER); + intra_prefix_lsa->ref_id = htonl(0); + intra_prefix_lsa->ref_adv_router = oa->ospf6->router_id; + + route_advertise = ospf6_route_table_create(0, 0); + + for (ALL_LIST_ELEMENTS_RO(oa->if_list, i, oi)) { + if (oi->state == OSPF6_INTERFACE_DOWN) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" Interface %s is down, ignore", + oi->interface->name); + continue; + } + + full_count = 0; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) + if (on->state == OSPF6_NEIGHBOR_FULL) + full_count++; + + if (oi->state != OSPF6_INTERFACE_LOOPBACK + && oi->state != OSPF6_INTERFACE_POINTTOPOINT + && oi->state != OSPF6_INTERFACE_POINTTOMULTIPOINT + && full_count != 0) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" Interface %s is not stub, ignore", + oi->interface->name); + continue; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" Interface %s:", oi->interface->name); + + /* connected prefix to advertise */ + for (route = ospf6_route_head(oi->route_connected); route; + route = ospf6_route_best_next(route)) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" include %pFX", &route->prefix); + ospf6_route_add(ospf6_route_copy(route), + route_advertise); + } + } + + if (route_advertise->count == 0) { + if (old) { + ls_id = 0; + ospf6_lsa_purge(old); + /* find previous LSA */ + old_next = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_INTRA_PREFIX), + htonl(++ls_id), oa->ospf6->router_id, oa->lsdb); + + while (old_next) { + ospf6_lsa_purge(old_next); + old_next = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_INTRA_PREFIX), + htonl(++ls_id), oa->ospf6->router_id, + oa->lsdb); + } + } + ospf6_route_table_delete(route_advertise); + return; + } + + /* Neighbor change to FULL, if INTRA-AREA-PREFIX LSA + * has not change, Flush old LSA and Re-Originate INP, + * as ospf6_flood() checks if LSA is same as DB, + * it won't be updated to neighbor's DB. + */ + if (oa->intra_prefix_originate) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + "%s: Re-originate intra prefix LSA, Current full nbrs %u", + __func__, oa->full_nbrs); + if (old) + ospf6_lsa_purge_multi_ls_id(oa, old); + oa->intra_prefix_originate = 0; + } + + /* put prefixes to advertise */ + prefix_num = 0; + op = (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa)); + for (route = ospf6_route_head(route_advertise); route; + route = ospf6_route_best_next(route)) { + if (((caddr_t)op - (caddr_t)lsa_header) > MAX_LSA_PAYLOAD) { + + intra_prefix_lsa->prefix_num = htons(prefix_num); + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); + lsa_header->id = htonl(ls_id++); + lsa_header->adv_router = oa->ospf6->router_id; + lsa_header->seqnum = ospf6_new_ls_seqnum( + lsa_header->type, lsa_header->id, + lsa_header->adv_router, oa->lsdb); + lsa_header->length = + htons((caddr_t)op - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* Create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oa); + + /* Prepare next buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *) + ospf6_lsa_header_end(lsa_header); + + /* Fill Intra-Area-Prefix-LSA */ + intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_ROUTER); + intra_prefix_lsa->ref_id = htonl(0); + intra_prefix_lsa->ref_adv_router = oa->ospf6->router_id; + + /* Put next set of prefixes to advertise */ + prefix_num = 0; + op = (struct ospf6_prefix + *)((caddr_t)intra_prefix_lsa + + sizeof(struct + ospf6_intra_prefix_lsa)); + } + + op->prefix_length = route->prefix.prefixlen; + op->prefix_options = route->prefix_options; + op->prefix_metric = htons(route->path.cost); + memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(op->prefix_length)); + prefix_num++; + + op = OSPF6_PREFIX_NEXT(op); + } + + ospf6_route_table_delete(route_advertise); + + if (prefix_num == 0) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + "Quit to Advertise Intra-Prefix: no route to advertise"); + return; + } + + intra_prefix_lsa->prefix_num = htons(prefix_num); + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); + lsa_header->id = htonl(ls_id++); + lsa_header->adv_router = oa->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oa->lsdb); + lsa_header->length = htons((caddr_t)op - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oa); +} + + +void ospf6_intra_prefix_lsa_originate_transit(struct event *thread) +{ + struct ospf6_interface *oi; + + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *old, *lsa; + + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct ospf6_neighbor *on; + struct ospf6_route *route; + struct ospf6_prefix *op; + struct listnode *i; + int full_count = 0; + unsigned short prefix_num = 0; + struct ospf6_route_table *route_advertise; + struct ospf6_link_lsa *link_lsa; + char *start, *end, *current; + uint16_t type; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + assert(oi->area); + + if (oi->area->ospf6->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Graceful Restart in progress, don't originate LSA"); + return; + } + + /* find previous LSA */ + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_INTRA_PREFIX), + htonl(oi->interface->ifindex), + oi->area->ospf6->router_id, oi->area->lsdb); + + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { + if (old) + ospf6_lsa_purge(old); + return; + } + + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + "Originate Intra-Area-Prefix-LSA for interface %s's prefix", + oi->interface->name); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)ospf6_lsa_header_end( + lsa_header); + + /* Fill Intra-Area-Prefix-LSA */ + intra_prefix_lsa->ref_type = htons(OSPF6_LSTYPE_NETWORK); + intra_prefix_lsa->ref_id = htonl(oi->interface->ifindex); + intra_prefix_lsa->ref_adv_router = oi->area->ospf6->router_id; + + if (oi->state != OSPF6_INTERFACE_DR) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" Interface is not DR"); + if (old) + ospf6_lsa_purge(old); + return; + } + + full_count = 0; + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, i, on)) + if (on->state == OSPF6_NEIGHBOR_FULL) + full_count++; + + if (full_count == 0) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" Interface is stub"); + if (old) + ospf6_lsa_purge(old); + return; + } + + /* connected prefix to advertise */ + route_advertise = ospf6_route_table_create(0, 0); + + type = ntohs(OSPF6_LSTYPE_LINK); + for (ALL_LSDB_TYPED(oi->lsdb, type, lsa)) { + if (OSPF6_LSA_IS_MAXAGE(lsa)) + continue; + + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" include prefix from %s", lsa->name); + + if (lsa->header->adv_router != oi->area->ospf6->router_id) { + on = ospf6_neighbor_lookup(lsa->header->adv_router, oi); + if (on == NULL || on->state != OSPF6_NEIGHBOR_FULL) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + " Neighbor not found or not Full, ignore"); + continue; + } + } + + link_lsa = (struct ospf6_link_lsa + *)((caddr_t)lsa->header + + sizeof(struct ospf6_lsa_header)); + + prefix_num = (unsigned short)ntohl(link_lsa->prefix_num); + start = (char *)link_lsa + sizeof(struct ospf6_link_lsa); + end = ospf6_lsa_end(lsa->header); + + for (current = start; current < end && prefix_num; + current += OSPF6_PREFIX_SIZE(op)) { + op = (struct ospf6_prefix *)current; + if (op->prefix_length == 0 + || current + OSPF6_PREFIX_SIZE(op) > end) + break; + + route = ospf6_route_create(oi->area->ospf6); + + route->type = OSPF6_DEST_TYPE_NETWORK; + route->prefix.family = AF_INET6; + route->prefix.prefixlen = op->prefix_length; + memset(&route->prefix.u.prefix6, 0, + sizeof(struct in6_addr)); + memcpy(&route->prefix.u.prefix6, OSPF6_PREFIX_BODY(op), + OSPF6_PREFIX_SPACE(op->prefix_length)); + route->prefix_options = op->prefix_options; + + route->path.origin.type = lsa->header->type; + route->path.origin.id = lsa->header->id; + route->path.origin.adv_router = lsa->header->adv_router; + route->path.options[0] = link_lsa->options[0]; + route->path.options[1] = link_lsa->options[1]; + route->path.options[2] = link_lsa->options[2]; + route->path.area_id = oi->area->area_id; + route->path.type = OSPF6_PATH_TYPE_INTRA; + + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug(" include %pFX", &route->prefix); + + ospf6_route_add(route, route_advertise); + prefix_num--; + } + if (current != end && IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug("Trailing garbage in %s", lsa->name); + } + + op = (struct ospf6_prefix *)((caddr_t)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa)); + + prefix_num = 0; + for (route = ospf6_route_head(route_advertise); route; + route = ospf6_route_best_next(route)) { + op->prefix_length = route->prefix.prefixlen; + op->prefix_options = route->prefix_options; + op->prefix_metric = htons(0); + memcpy(OSPF6_PREFIX_BODY(op), &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(op->prefix_length)); + op = OSPF6_PREFIX_NEXT(op); + prefix_num++; + } + + ospf6_route_table_delete(route_advertise); + + if (prefix_num == 0) { + if (IS_OSPF6_DEBUG_ORIGINATE(INTRA_PREFIX)) + zlog_debug( + "Quit to Advertise Intra-Prefix: no route to advertise"); + return; + } + + intra_prefix_lsa->prefix_num = htons(prefix_num); + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_INTRA_PREFIX); + lsa_header->id = htonl(oi->interface->ifindex); + lsa_header->adv_router = oi->area->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, oi->area->lsdb); + lsa_header->length = htons((caddr_t)op - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, oi->area); +} + +static void ospf6_intra_prefix_update_route_origin(struct ospf6_route *oa_route, + struct ospf6 *ospf6) +{ + struct ospf6_path *h_path; + struct ospf6_route *g_route, *nroute; + + /* Update Global ospf6 route path */ + g_route = ospf6_route_lookup(&oa_route->prefix, ospf6->route_table); + + assert(g_route); + + for (ospf6_route_lock(g_route); g_route && + ospf6_route_is_prefix(&oa_route->prefix, g_route); + g_route = nroute) { + nroute = ospf6_route_next(g_route); + if (g_route->type != oa_route->type) + continue; + if (g_route->path.area_id != oa_route->path.area_id) + continue; + if (g_route->path.type != OSPF6_PATH_TYPE_INTRA) + continue; + if (g_route->path.cost != oa_route->path.cost) + continue; + + if (ospf6_route_is_same_origin(g_route, oa_route)) { + h_path = (struct ospf6_path *)listgetdata( + listhead(g_route->paths)); + g_route->path.origin.type = h_path->origin.type; + g_route->path.origin.id = h_path->origin.id; + g_route->path.origin.adv_router = + h_path->origin.adv_router; + if (nroute) + ospf6_route_unlock(nroute); + break; + } + } + + h_path = (struct ospf6_path *)listgetdata( + listhead(oa_route->paths)); + oa_route->path.origin.type = h_path->origin.type; + oa_route->path.origin.id = h_path->origin.id; + oa_route->path.origin.adv_router = h_path->origin.adv_router; +} + +void ospf6_intra_prefix_route_ecmp_path(struct ospf6_area *oa, + struct ospf6_route *old, + struct ospf6_route *route) +{ + struct ospf6_route *old_route, *ls_entry; + struct ospf6_path *ecmp_path, *o_path = NULL; + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + bool route_found = false; + struct interface *ifp = NULL; + struct ospf6_lsa *lsa; + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + + /* check for old entry match with new route origin, + * delete old entry. + */ + for (old_route = old; old_route; old_route = old_route->next) { + bool route_updated = false; + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Current and New route has same origin, + * delete old entry. + */ + for (ALL_LIST_ELEMENTS(old_route->paths, anode, anext, + o_path)) { + /* Check old route path and route has same + * origin. + */ + if (o_path->area_id != route->path.area_id + || !ospf6_ls_origin_same(o_path, &route->path)) + continue; + + /* Cost is not same then delete current path */ + if (o_path->cost == route->path.cost) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + zlog_debug( + "%s: route %pFX cost old %u new %u is not same, replace route", + __func__, &old_route->prefix, o_path->cost, + route->path.cost); + } + + /* Remove selected current path's nh from + * effective nh list. + */ + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { + for (ALL_LIST_ELEMENTS(old_route->nh_list, + rnode, rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, nh)) + continue; + listnode_delete(old_route->nh_list, + rnh); + ospf6_nexthop_delete(rnh); + route_updated = true; + } + } + + listnode_delete(old_route->paths, o_path); + ospf6_path_free(o_path); + + /* Current route's path (adv_router info) is similar + * to route being added. + * Replace current route's path with paths list head. + * Update FIB with effective NHs. + */ + if (listcount(old_route->paths)) { + if (route_updated) { + for (ALL_LIST_ELEMENTS(old_route->paths, + anode, anext, o_path)) { + ospf6_merge_nexthops( + old_route->nh_list, + o_path->nh_list); + } + /* Update ospf6 route table and + * RIB/FIB with effective + * nh_list + */ + if (oa->route_table->hook_add) + (*oa->route_table->hook_add)( + old_route); + + if (old_route->path.origin.id == + route->path.origin.id && + old_route->path.origin.adv_router == + route->path.origin.adv_router) { + ospf6_intra_prefix_update_route_origin( + old_route, oa->ospf6); + } + break; + } + } else { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + zlog_debug( + "%s: route %pFX old cost %u new cost %u, delete old entry.", + __func__, &old_route->prefix, + old_route->path.cost, + route->path.cost); + } + if (oa->route_table->hook_remove) + ospf6_route_remove(old_route, + oa->route_table); + else + SET_FLAG(old_route->flag, + OSPF6_ROUTE_REMOVE); + break; + } + } + if (route_updated) + break; + } + + for (old_route = old; old_route; old_route = old_route->next) { + + /* The route linked-list is grouped in batches of prefix. + * If the new prefix is not the same as the one of interest + * then we have walked over the end of the batch and so we + * should break rather than continuing unnecessarily. + */ + if (!ospf6_route_is_same(old_route, route)) + break; + if (old_route->path.type != route->path.type) + continue; + + /* Old Route and New Route have Equal Cost, Merge NHs */ + if (old_route->path.cost == route->path.cost) { + route_found = true; + + /* check if this path exists already in + * route->paths list, if so, replace nh_list. + */ + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + if (o_path->area_id == route->path.area_id + && ospf6_ls_origin_same(o_path, &route->path)) + break; + } + /* If path is not found in old_route paths's list, + * add a new path to route paths list and merge + * nexthops in route->path->nh_list. + * Otherwise replace existing path's nh_list. + */ + if (o_path == NULL) { + ecmp_path = ospf6_path_dup(&route->path); + + /* Add a nh_list to new ecmp path */ + ospf6_copy_nexthops(ecmp_path->nh_list, + route->nh_list); + /* Add the new path to route's path list */ + listnode_add_sort(old_route->paths, ecmp_path); + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + zlog_debug( + "%s: route %pFX %p another path added with nh %u, effective paths %u nh %u", + __func__, &route->prefix, + (void *)old_route, + listcount(ecmp_path->nh_list), + old_route->paths ? listcount( + old_route->paths) + : 0, + listcount(old_route->nh_list)); + } + } else { + list_delete_all_node(o_path->nh_list); + ospf6_copy_nexthops(o_path->nh_list, + route->nh_list); + + } + + list_delete_all_node(old_route->nh_list); + + for (ALL_LIST_ELEMENTS_RO(old_route->paths, anode, + o_path)) { + ls_entry = ospf6_route_lookup( + &o_path->ls_prefix, + oa->spf_table); + if (ls_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug( + "%s: ls_prfix %pFX ls_entry not found.", + __func__, + &o_path->ls_prefix); + continue; + } + lsa = ospf6_lsdb_lookup(o_path->origin.type, + o_path->origin.id, + o_path->origin.adv_router, + oa->lsdb); + if (lsa == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN( + INTRA_PREFIX)) { + struct prefix adv_prefix; + + ospf6_linkstate_prefix( + o_path->origin.adv_router, + o_path->origin.id, &adv_prefix); + zlog_debug( + "%s: adv_router %pFX lsa not found", + __func__, &adv_prefix); + } + continue; + } + intra_prefix_lsa = + (struct ospf6_intra_prefix_lsa *) + ospf6_lsa_header_end( + lsa->header); + + if (intra_prefix_lsa->ref_adv_router + == oa->ospf6->router_id) { + ifp = if_lookup_prefix( + &old_route->prefix, + oa->ospf6->vrf_id); + } + + if (ifp) { + /* Nexthop interface found */ + ospf6_route_add_nexthop(old_route, + ifp->ifindex, + NULL); + } else { + /* The connected interfaces between + * routers can be in different networks. + * In this case the matching interface + * is not found. Copy nexthops from the + * link state entry + */ + ospf6_route_merge_nexthops(old_route, + ls_entry); + } + } + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug( + "%s: route %pFX %p with final effective paths %u nh %u", + __func__, &route->prefix, + (void *)old_route, + old_route->paths + ? listcount(old_route->paths) + : 0, + listcount(old_route->nh_list)); + + /* used in intra_route_calculation() to add to + * global ospf6 route table. + */ + UNSET_FLAG(old_route->flag, OSPF6_ROUTE_REMOVE); + SET_FLAG(old_route->flag, OSPF6_ROUTE_ADD); + /* Update ospf6 route table and RIB/FIB */ + if (oa->route_table->hook_add) + (*oa->route_table->hook_add)(old_route); + /* Delete the new route its info added to existing + * route. + */ + ospf6_route_delete(route); + + break; + } + } + + if (!route_found) { + /* Add new route to existing node in ospf6 route table. */ + ospf6_route_add(route, oa->route_table); + } +} + +void ospf6_intra_prefix_lsa_add(struct ospf6_lsa *lsa) +{ + struct ospf6_area *oa; + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct prefix ls_prefix; + struct ospf6_route *route, *ls_entry, *old; + int prefix_num; + struct ospf6_prefix *op; + char *start, *current, *end; + char buf[PREFIX2STR_BUFFER]; + struct interface *ifp = NULL; + int direct_connect = 0; + struct ospf6_path *path; + + if (OSPF6_LSA_IS_MAXAGE(lsa)) + return; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("%s: LSA %s found", __func__, lsa->name); + + oa = OSPF6_AREA(lsa->lsdb->data); + + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)ospf6_lsa_header_end( + lsa->header); + if (intra_prefix_lsa->ref_type == htons(OSPF6_LSTYPE_ROUTER) || + intra_prefix_lsa->ref_type == htons(OSPF6_LSTYPE_NETWORK)) + ospf6_linkstate_prefix(intra_prefix_lsa->ref_adv_router, + intra_prefix_lsa->ref_id, &ls_prefix); + else { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("Unknown reference LS-type: %#hx", + ntohs(intra_prefix_lsa->ref_type)); + return; + } + + ls_entry = ospf6_route_lookup(&ls_prefix, oa->spf_table); + if (ls_entry == NULL) { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + ospf6_linkstate_prefix2str(&ls_prefix, buf, + sizeof(buf)); + zlog_debug("LS entry does not exist: %s", buf); + } + return; + } + + if (intra_prefix_lsa->ref_adv_router == oa->ospf6->router_id) { + /* the intra-prefix are directly connected */ + direct_connect = 1; + } + + prefix_num = ntohs(intra_prefix_lsa->prefix_num); + start = (caddr_t)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa); + end = ospf6_lsa_end(lsa->header); + for (current = start; current < end; current += OSPF6_PREFIX_SIZE(op)) { + op = (struct ospf6_prefix *)current; + if (prefix_num == 0) + break; + if (end < current + OSPF6_PREFIX_SIZE(op)) + break; + + /* Appendix A.4.1.1 */ + if (CHECK_FLAG(op->prefix_options, OSPF6_PREFIX_OPTION_NU)) { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + ospf6_linkstate_prefix2str( + (struct prefix *)OSPF6_PREFIX_BODY(op), + buf, sizeof(buf)); + zlog_debug( + "%s: Skipping Prefix %s has NU option set", + __func__, buf); + } + continue; + } + + route = ospf6_route_create(oa->ospf6); + + memset(&route->prefix, 0, sizeof(struct prefix)); + route->prefix.family = AF_INET6; + route->prefix.prefixlen = op->prefix_length; + ospf6_prefix_in6_addr(&route->prefix.u.prefix6, + intra_prefix_lsa, op); + route->prefix_options = op->prefix_options; + + route->type = OSPF6_DEST_TYPE_NETWORK; + route->path.origin.type = lsa->header->type; + route->path.origin.id = lsa->header->id; + route->path.origin.adv_router = lsa->header->adv_router; + route->path.area_id = oa->area_id; + route->path.type = OSPF6_PATH_TYPE_INTRA; + route->path.metric_type = 1; + route->path.cost = + ls_entry->path.cost + ntohs(op->prefix_metric); + memcpy(&route->path.ls_prefix, &ls_prefix, + sizeof(struct prefix)); + if (direct_connect) { + ifp = if_lookup_prefix(&route->prefix, + oa->ospf6->vrf_id); + } + + if (ifp) { + /* Nexthop interface found */ + ospf6_route_add_nexthop(route, ifp->ifindex, NULL); + } else { + /* The connected interfaces between routers can be in + * different networks. In this case the matching + * interface is not found. Copy nexthops from the + * link state entry + */ + ospf6_route_copy_nexthops(route, ls_entry); + } + + path = ospf6_path_dup(&route->path); + ospf6_copy_nexthops(path->nh_list, route->path.nh_list); + listnode_add_sort(route->paths, path); + + old = ospf6_route_lookup(&route->prefix, oa->route_table); + if (old) { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug( + "%s Update route: %s old cost %u new cost %u paths %u nh %u", + __func__, buf, old->path.cost, + route->path.cost, + listcount(route->paths), + listcount(route->nh_list)); + } + ospf6_intra_prefix_route_ecmp_path(oa, old, route); + } else { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug( + "%s route %s add with cost %u paths %u nh %u", + __func__, buf, route->path.cost, + listcount(route->paths), + listcount(route->nh_list)); + } + ospf6_route_add(route, oa->route_table); + } + prefix_num--; + } + + if (current != end && IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("Trailing garbage ignored"); +} + +static void ospf6_intra_prefix_lsa_remove_update_route(struct ospf6_lsa *lsa, + struct ospf6_area *oa, + struct ospf6_route *route) +{ + struct listnode *anode, *anext; + struct listnode *nnode, *rnode, *rnext; + struct ospf6_nexthop *nh, *rnh; + struct ospf6_path *o_path; + bool nh_updated = false; + char buf[PREFIX2STR_BUFFER]; + + /* Iterate all paths of route to find maching + * with LSA remove info. + * If route->path is same, replace + * from paths list. + */ + for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) { + if ((o_path->origin.type != lsa->header->type) || + (o_path->origin.adv_router != lsa->header->adv_router) || + (o_path->origin.id != lsa->header->id)) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug( + "%s: route %s path found with cost %u nh %u to remove.", + __func__, buf, o_path->cost, + listcount(o_path->nh_list)); + } + + /* Remove found path's nh_list from + * the route's nh_list. + */ + for (ALL_LIST_ELEMENTS_RO(o_path->nh_list, nnode, nh)) { + for (ALL_LIST_ELEMENTS(route->nh_list, rnode, + rnext, rnh)) { + if (!ospf6_nexthop_is_same(rnh, nh)) + continue; + listnode_delete(route->nh_list, rnh); + ospf6_nexthop_delete(rnh); + } + } + /* Delete the path from route's + * path list + */ + listnode_delete(route->paths, o_path); + ospf6_path_free(o_path); + nh_updated = true; + break; + } + + if (nh_updated) { + /* Iterate all paths and merge nexthop, + * unlesss any of the nexthop similar to + * ones deleted as part of path deletion. + */ + for (ALL_LIST_ELEMENTS(route->paths, anode, anext, o_path)) + ospf6_merge_nexthops(route->nh_list, o_path->nh_list); + + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug( + "%s: route %s update paths %u nh %u", __func__, + buf, route->paths ? listcount(route->paths) : 0, + route->nh_list ? listcount(route->nh_list) : 0); + } + + /* Update Global Route table and + * RIB/FIB with effective + * nh_list + */ + if (oa->route_table->hook_add) + (*oa->route_table->hook_add)(route); + + /* route's primary path is similar + * to LSA, replace route's primary + * path with route's paths list + * head. + */ + if ((route->path.origin.id == lsa->header->id) && + (route->path.origin.adv_router == + lsa->header->adv_router)) { + ospf6_intra_prefix_update_route_origin(route, + oa->ospf6); + } + } + +} + +void ospf6_intra_prefix_lsa_remove(struct ospf6_lsa *lsa) +{ + struct ospf6_area *oa; + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct prefix prefix; + struct ospf6_route *route, *nroute; + int prefix_num; + struct ospf6_prefix *op; + char *start, *current, *end; + char buf[PREFIX2STR_BUFFER]; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("%s: %s disappearing", __func__, lsa->name); + + oa = OSPF6_AREA(lsa->lsdb->data); + + intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *)ospf6_lsa_header_end( + lsa->header); + + prefix_num = ntohs(intra_prefix_lsa->prefix_num); + start = (caddr_t)intra_prefix_lsa + + sizeof(struct ospf6_intra_prefix_lsa); + end = ospf6_lsa_end(lsa->header); + for (current = start; current < end; current += OSPF6_PREFIX_SIZE(op)) { + op = (struct ospf6_prefix *)current; + if (prefix_num == 0) + break; + if (end < current + OSPF6_PREFIX_SIZE(op)) + break; + prefix_num--; + + memset(&prefix, 0, sizeof(prefix)); + prefix.family = AF_INET6; + prefix.prefixlen = op->prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, intra_prefix_lsa, op); + + route = ospf6_route_lookup(&prefix, oa->route_table); + if (route == NULL) + continue; + + for (ospf6_route_lock(route); + route && ospf6_route_is_prefix(&prefix, route); + route = nroute) { + nroute = ospf6_route_next(route); + if (route->type != OSPF6_DEST_TYPE_NETWORK) + continue; + if (route->path.area_id != oa->area_id) + continue; + if (route->path.type != OSPF6_PATH_TYPE_INTRA) + continue; + /* Route has multiple ECMP paths, remove matching + * path. Update current route's effective nh list + * after removal of one of the path. + */ + if (listcount(route->paths) > 1) { + ospf6_intra_prefix_lsa_remove_update_route( + lsa, oa, route); + } else { + + if (route->path.origin.type != lsa->header->type + || route->path.origin.id != lsa->header->id + || route->path.origin.adv_router + != lsa->header->adv_router) + continue; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, + sizeof(buf)); + zlog_debug( + "%s: route remove %s with path type %u cost %u paths %u nh %u", + __func__, buf, route->path.type, + route->path.cost, + listcount(route->paths), + listcount(route->nh_list)); + } + ospf6_route_remove(route, oa->route_table); + } + } + if (route) + ospf6_route_unlock(route); + } + + if (current != end && IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("Trailing garbage ignored"); +} + +void ospf6_intra_route_calculation(struct ospf6_area *oa) +{ + struct ospf6_route *route, *nroute; + uint16_t type; + struct ospf6_lsa *lsa; + void (*hook_add)(struct ospf6_route *) = NULL; + void (*hook_remove)(struct ospf6_route *) = NULL; + char buf[PREFIX2STR_BUFFER]; + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("Re-examin intra-routes for area %s", oa->name); + + hook_add = oa->route_table->hook_add; + hook_remove = oa->route_table->hook_remove; + oa->route_table->hook_add = NULL; + oa->route_table->hook_remove = NULL; + + for (route = ospf6_route_head(oa->route_table); route; + route = ospf6_route_next(route)) + route->flag = OSPF6_ROUTE_REMOVE; + + type = htons(OSPF6_LSTYPE_INTRA_PREFIX); + for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) + ospf6_intra_prefix_lsa_add(lsa); + + oa->route_table->hook_add = hook_add; + oa->route_table->hook_remove = hook_remove; + + for (route = ospf6_route_head(oa->route_table); route; route = nroute) { + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) { + prefix2str(&route->prefix, buf, sizeof(buf)); + zlog_debug("%s: route %s, flag 0x%x", __func__, buf, + route->flag); + } + + nroute = ospf6_route_next(route); + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) + && CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD)) { + UNSET_FLAG(route->flag, OSPF6_ROUTE_REMOVE); + UNSET_FLAG(route->flag, OSPF6_ROUTE_ADD); + } + + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) + ospf6_route_remove(route, oa->route_table); + else if (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) + || CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE)) { + if (hook_add) + (*hook_add)(route); + route->flag = 0; + } else { + /* Redo the summaries as things might have changed */ + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("%s: Originate summary for route %s", + __func__, buf); + ospf6_abr_originate_summary(route, oa->ospf6); + route->flag = 0; + } + } + + if (IS_OSPF6_DEBUG_EXAMIN(INTRA_PREFIX)) + zlog_debug("Re-examin intra-routes for area %s: Done", + oa->name); +} + +static void ospf6_brouter_debug_print(struct ospf6_route *brouter) +{ + uint32_t brouter_id; + char brouter_name[16]; + char area_name[16]; + char destination[64]; + char installed[64], changed[64]; + struct timeval now, res; + char id[16], adv_router[16]; + char capa[16], options[32]; + + brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, sizeof(brouter_name)); + inet_ntop(AF_INET, &brouter->path.area_id, area_name, + sizeof(area_name)); + ospf6_linkstate_prefix2str(&brouter->prefix, destination, + sizeof(destination)); + + monotime(&now); + timersub(&now, &brouter->installed, &res); + timerstring(&res, installed, sizeof(installed)); + + monotime(&now); + timersub(&now, &brouter->changed, &res); + timerstring(&res, changed, sizeof(changed)); + + inet_ntop(AF_INET, &brouter->path.origin.id, id, sizeof(id)); + inet_ntop(AF_INET, &brouter->path.origin.adv_router, adv_router, + sizeof(adv_router)); + + ospf6_options_printbuf(brouter->path.options, options, sizeof(options)); + ospf6_capability_printbuf(brouter->path.router_bits, capa, + sizeof(capa)); + + zlog_info("Brouter: %s via area %s", brouter_name, area_name); + zlog_info(" memory: prev: %p this: %p next: %p parent rnode: %p", + (void *)brouter->prev, (void *)brouter, (void *)brouter->next, + (void *)brouter->rnode); + zlog_info(" type: %d prefix: %s installed: %s changed: %s", + brouter->type, destination, installed, changed); + zlog_info(" lock: %d flags: %s%s%s%s", brouter->lock, + (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_BEST) ? "B" : "-"), + (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD) ? "A" : "-"), + (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE) ? "R" : "-"), + (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE) ? "C" : "-")); + zlog_info(" path type: %s ls-origin %s id: %s adv-router %s", + OSPF6_PATH_TYPE_NAME(brouter->path.type), + ospf6_lstype_name(brouter->path.origin.type), id, adv_router); + zlog_info(" options: %s router-bits: %s metric-type: %d metric: %d/%d", + options, capa, brouter->path.metric_type, brouter->path.cost, + brouter->path.u.cost_e2); + zlog_info(" paths %u nh %u", listcount(brouter->paths), + listcount(brouter->nh_list)); +} + +void ospf6_intra_brouter_calculation(struct ospf6_area *oa) +{ + struct ospf6_route *brouter, *nbrouter, *copy; + void (*hook_add)(struct ospf6_route *) = NULL; + void (*hook_remove)(struct ospf6_route *) = NULL; + uint32_t brouter_id; + char brouter_name[16]; + + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(oa->area_id) || + IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s: border-router calculation for area %s", + __func__, oa->name); + + hook_add = oa->ospf6->brouter_table->hook_add; + hook_remove = oa->ospf6->brouter_table->hook_remove; + oa->ospf6->brouter_table->hook_add = NULL; + oa->ospf6->brouter_table->hook_remove = NULL; + + /* withdraw the previous router entries for the area */ + for (brouter = ospf6_route_head(oa->ospf6->brouter_table); brouter; + brouter = ospf6_route_next(brouter)) { + brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, + sizeof(brouter_name)); + + if (brouter->path.area_id != oa->area_id) + continue; + + SET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); + + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(brouter_id) + || IS_OSPF6_DEBUG_ROUTE(MEMORY)) { + zlog_debug("%p: mark as removing: area %s brouter %s", + (void *)brouter, oa->name, brouter_name); + ospf6_brouter_debug_print(brouter); + } + } + + for (brouter = ospf6_route_head(oa->spf_table); brouter; + brouter = ospf6_route_next(brouter)) { + brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, + sizeof(brouter_name)); + + if (brouter->type != OSPF6_DEST_TYPE_LINKSTATE) + continue; + + if (ospf6_linkstate_prefix_id(&brouter->prefix) != htonl(0)) + continue; + + if (!CHECK_FLAG(brouter->path.router_bits, OSPF6_ROUTER_BIT_E) + && !CHECK_FLAG(brouter->path.router_bits, + OSPF6_ROUTER_BIT_B)) + continue; + + if (!OSPF6_OPT_ISSET(brouter->path.options, OSPF6_OPT_V6) + || !OSPF6_OPT_ISSET(brouter->path.options, OSPF6_OPT_R)) + continue; + + copy = ospf6_route_copy(brouter); + copy->type = OSPF6_DEST_TYPE_ROUTER; + copy->path.area_id = oa->area_id; + ospf6_route_add(copy, oa->ospf6->brouter_table); + + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(brouter_id) + || IS_OSPF6_DEBUG_ROUTE(MEMORY)) { + zlog_debug("%p: transfer: area %s brouter %s", + (void *)brouter, oa->name, brouter_name); + ospf6_brouter_debug_print(brouter); + } + } + + oa->ospf6->brouter_table->hook_add = hook_add; + oa->ospf6->brouter_table->hook_remove = hook_remove; + + for (brouter = ospf6_route_head(oa->ospf6->brouter_table); brouter; + brouter = nbrouter) { + + /* + * brouter may have been "deleted" in the last loop iteration. + * If this is the case there is still 1 final refcount lock + * taken by ospf6_route_next, that will be released by the same + * call and result in deletion. To avoid heap UAF we must then + * skip processing the deleted route. + */ + if (brouter->lock == 1) { + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + ospf6_brouter_debug_print(brouter); + nbrouter = ospf6_route_next(brouter); + continue; + } else { + nbrouter = ospf6_route_next(brouter); + } + + brouter_id = ADV_ROUTER_IN_PREFIX(&brouter->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, + sizeof(brouter_name)); + + if (brouter->path.area_id != oa->area_id) + continue; + + if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_WAS_REMOVED)) + continue; + + /* After iterating spf_table for all routers including + * intra brouter, clear mark for remove flag for + * inter border router if its adv router present in + * SPF table. + */ + if (brouter->path.type == OSPF6_PATH_TYPE_INTER) { + struct prefix adv_prefix; + + ospf6_linkstate_prefix(brouter->path.origin.adv_router, + htonl(0), &adv_prefix); + + if (ospf6_route_lookup(&adv_prefix, oa->spf_table)) { + if (IS_OSPF6_DEBUG_BROUTER) { + zlog_debug( + "%s: keep inter brouter %s as adv router 0x%x found in spf", + __func__, brouter_name, + brouter->path.origin + .adv_router); + ospf6_brouter_debug_print(brouter); + } + UNSET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); + } + } + + if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE) + && CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD)) { + UNSET_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE); + UNSET_FLAG(brouter->flag, OSPF6_ROUTE_ADD); + } + + if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_REMOVE)) { + if (IS_OSPF6_DEBUG_BROUTER + || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( + brouter_id) + || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( + oa->area_id)) + zlog_debug( + "%s: brouter %s disappears via area %s", + __func__, brouter_name, oa->name); + /* This is used to protect nbrouter from removed from + * the table. For an example, ospf6_abr_examin_summary, + * removes brouters which are marked for remove. + */ + oa->intra_brouter_calc = true; + ospf6_route_remove(brouter, oa->ospf6->brouter_table); + brouter = NULL; + } else if (CHECK_FLAG(brouter->flag, OSPF6_ROUTE_ADD) + || CHECK_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE)) { + if (IS_OSPF6_DEBUG_BROUTER + || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( + brouter_id) + || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( + oa->area_id)) + zlog_info("%s: brouter %s appears via area %s", + __func__, brouter_name, oa->name); + + /* newly added */ + if (hook_add) + (*hook_add)(brouter); + } else { + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID( + brouter_id) + || IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID( + oa->area_id)) + zlog_debug( + "brouter %s still exists via area %s", + brouter_name, oa->name); + /* But re-originate summaries */ + ospf6_abr_originate_summary(brouter, oa->ospf6); + } + + if (brouter) { + UNSET_FLAG(brouter->flag, OSPF6_ROUTE_ADD); + UNSET_FLAG(brouter->flag, OSPF6_ROUTE_CHANGE); + } + /* Reset for nbrouter */ + oa->intra_brouter_calc = false; + } + + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(oa->area_id) || + IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s: border-router calculation for area %s: done", + __func__, oa->name); +} + +static struct ospf6_lsa_handler router_handler = { + .lh_type = OSPF6_LSTYPE_ROUTER, + .lh_name = "Router", + .lh_short_name = "Rtr", + .lh_show = ospf6_router_lsa_show, + .lh_get_prefix_str = ospf6_router_lsa_get_nbr_id, + .lh_debug = 0}; + +static struct ospf6_lsa_handler network_handler = { + .lh_type = OSPF6_LSTYPE_NETWORK, + .lh_name = "Network", + .lh_short_name = "Net", + .lh_show = ospf6_network_lsa_show, + .lh_get_prefix_str = ospf6_network_lsa_get_ar_id, + .lh_debug = 0}; + +static struct ospf6_lsa_handler link_handler = { + .lh_type = OSPF6_LSTYPE_LINK, + .lh_name = "Link", + .lh_short_name = "Lnk", + .lh_show = ospf6_link_lsa_show, + .lh_get_prefix_str = ospf6_link_lsa_get_prefix_str, + .lh_debug = 0}; + +static struct ospf6_lsa_handler intra_prefix_handler = { + .lh_type = OSPF6_LSTYPE_INTRA_PREFIX, + .lh_name = "Intra-Prefix", + .lh_short_name = "INP", + .lh_show = ospf6_intra_prefix_lsa_show, + .lh_get_prefix_str = ospf6_intra_prefix_lsa_get_prefix_str, + .lh_debug = 0}; + +void ospf6_intra_init(void) +{ + ospf6_install_lsa_handler(&router_handler); + ospf6_install_lsa_handler(&network_handler); + ospf6_install_lsa_handler(&link_handler); + ospf6_install_lsa_handler(&intra_prefix_handler); +} + +DEFUN (debug_ospf6_brouter, + debug_ospf6_brouter_cmd, + "debug ospf6 border-routers", + DEBUG_STR + OSPF6_STR + "Debug border router\n" + ) +{ + OSPF6_DEBUG_BROUTER_ON(); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_brouter, + no_debug_ospf6_brouter_cmd, + "no debug ospf6 border-routers", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug border router\n" + ) +{ + OSPF6_DEBUG_BROUTER_OFF(); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf6_brouter_router, + debug_ospf6_brouter_router_cmd, + "debug ospf6 border-routers router-id A.B.C.D", + DEBUG_STR + OSPF6_STR + "Debug border router\n" + "Debug specific border router\n" + "Specify border-router's router-id\n" + ) +{ + int idx_ipv4 = 4; + uint32_t router_id; + inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id); + OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ON(router_id); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_brouter_router, + no_debug_ospf6_brouter_router_cmd, + "no debug ospf6 border-routers router-id [A.B.C.D]", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug border router\n" + "Debug specific border router\n" + "Specify border-router's router-id\n" + ) +{ + OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF(); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf6_brouter_area, + debug_ospf6_brouter_area_cmd, + "debug ospf6 border-routers area-id A.B.C.D", + DEBUG_STR + OSPF6_STR + "Debug border router\n" + "Debug border routers in specific Area\n" + "Specify Area-ID\n" + ) +{ + int idx_ipv4 = 4; + uint32_t area_id; + inet_pton(AF_INET, argv[idx_ipv4]->arg, &area_id); + OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ON(area_id); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_brouter_area, + no_debug_ospf6_brouter_area_cmd, + "no debug ospf6 border-routers area-id [A.B.C.D]", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug border router\n" + "Debug border routers in specific Area\n" + "Specify Area-ID\n" + ) +{ + OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF(); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_brouter(struct vty *vty) +{ + char buf[16]; + if (IS_OSPF6_DEBUG_BROUTER) + vty_out(vty, "debug ospf6 border-routers\n"); + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER) { + inet_ntop(AF_INET, &conf_debug_ospf6_brouter_specific_router_id, + buf, sizeof(buf)); + vty_out(vty, "debug ospf6 border-routers router-id %s\n", buf); + } + if (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA) { + inet_ntop(AF_INET, &conf_debug_ospf6_brouter_specific_area_id, + buf, sizeof(buf)); + vty_out(vty, "debug ospf6 border-routers area-id %s\n", buf); + } + return 0; +} + +void install_element_ospf6_debug_brouter(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_brouter_cmd); + install_element(ENABLE_NODE, &debug_ospf6_brouter_router_cmd); + install_element(ENABLE_NODE, &debug_ospf6_brouter_area_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_brouter_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_brouter_router_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_brouter_area_cmd); + install_element(CONFIG_NODE, &debug_ospf6_brouter_cmd); + install_element(CONFIG_NODE, &debug_ospf6_brouter_router_cmd); + install_element(CONFIG_NODE, &debug_ospf6_brouter_area_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_brouter_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_brouter_router_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_brouter_area_cmd); +} diff --git a/ospf6d/ospf6_intra.h b/ospf6d/ospf6_intra.h new file mode 100644 index 0000000..7d154cb --- /dev/null +++ b/ospf6d/ospf6_intra.h @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_INTRA_H +#define OSPF6_INTRA_H + +/* Debug option */ +extern unsigned char conf_debug_ospf6_brouter; +extern in_addr_t conf_debug_ospf6_brouter_specific_router_id; +extern in_addr_t conf_debug_ospf6_brouter_specific_area_id; +#define OSPF6_DEBUG_BROUTER_SUMMARY 0x01 +#define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER 0x02 +#define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA 0x04 +#define OSPF6_DEBUG_BROUTER_ON() \ + (conf_debug_ospf6_brouter |= OSPF6_DEBUG_BROUTER_SUMMARY) +#define OSPF6_DEBUG_BROUTER_OFF() \ + (conf_debug_ospf6_brouter &= ~OSPF6_DEBUG_BROUTER_SUMMARY) +#define IS_OSPF6_DEBUG_BROUTER \ + (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SUMMARY) + +#define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ON(router_id) \ + do { \ + conf_debug_ospf6_brouter_specific_router_id = (router_id); \ + conf_debug_ospf6_brouter |= \ + OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER; \ + } while (0) +#define OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF() \ + do { \ + conf_debug_ospf6_brouter_specific_router_id = 0; \ + conf_debug_ospf6_brouter &= \ + ~OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER; \ + } while (0) +#define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER \ + (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER) +#define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_ID(router_id) \ + (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER \ + && conf_debug_ospf6_brouter_specific_router_id == (router_id)) + +#define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ON(area_id) \ + do { \ + conf_debug_ospf6_brouter_specific_area_id = (area_id); \ + conf_debug_ospf6_brouter |= OSPF6_DEBUG_BROUTER_SPECIFIC_AREA; \ + } while (0) +#define OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF() \ + do { \ + conf_debug_ospf6_brouter_specific_area_id = 0; \ + conf_debug_ospf6_brouter &= \ + ~OSPF6_DEBUG_BROUTER_SPECIFIC_AREA; \ + } while (0) +#define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA \ + (conf_debug_ospf6_brouter & OSPF6_DEBUG_BROUTER_SPECIFIC_AREA) +#define IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_ID(area_id) \ + (IS_OSPF6_DEBUG_BROUTER_SPECIFIC_AREA \ + && conf_debug_ospf6_brouter_specific_area_id == (area_id)) + +/* Router-LSA */ +#define OSPF6_ROUTER_LSA_MIN_SIZE 4U +struct ospf6_router_lsa { + uint8_t bits; + uint8_t options[3]; + /* followed by ospf6_router_lsdesc(s) */ +}; + +/* Link State Description in Router-LSA */ +#define OSPF6_ROUTER_LSDESC_FIX_SIZE 16U +struct ospf6_router_lsdesc { + uint8_t type; + uint8_t reserved; + uint16_t metric; /* output cost */ + uint32_t interface_id; + uint32_t neighbor_interface_id; + in_addr_t neighbor_router_id; +}; + +#define OSPF6_ROUTER_LSDESC_POINTTOPOINT 1 +#define OSPF6_ROUTER_LSDESC_TRANSIT_NETWORK 2 +#define OSPF6_ROUTER_LSDESC_STUB_NETWORK 3 +#define OSPF6_ROUTER_LSDESC_VIRTUAL_LINK 4 + +enum stub_router_mode { + OSPF6_NOT_STUB_ROUTER, + OSPF6_IS_STUB_ROUTER, + OSPF6_IS_STUB_ROUTER_V6, +}; + +#define ROUTER_LSDESC_IS_TYPE(t, x) \ + ((((struct ospf6_router_lsdesc *)(x))->type \ + == OSPF6_ROUTER_LSDESC_##t) \ + ? 1 \ + : 0) +#define ROUTER_LSDESC_GET_METRIC(x) \ + (ntohs(((struct ospf6_router_lsdesc *)(x))->metric)) +#define ROUTER_LSDESC_GET_IFID(x) \ + (ntohl(((struct ospf6_router_lsdesc *)(x))->interface_id)) +#define ROUTER_LSDESC_GET_NBR_IFID(x) \ + (ntohl(((struct ospf6_router_lsdesc *)(x))->neighbor_interface_id)) +#define ROUTER_LSDESC_GET_NBR_ROUTERID(x) \ + (((struct ospf6_router_lsdesc *)(x))->neighbor_router_id) + +/* Network-LSA */ +#define OSPF6_NETWORK_LSA_MIN_SIZE 4U +struct ospf6_network_lsa { + uint8_t reserved; + uint8_t options[3]; + /* followed by ospf6_netowrk_lsd(s) */ +}; + +/* Link State Description in Router-LSA */ +#define OSPF6_NETWORK_LSDESC_FIX_SIZE 4U +struct ospf6_network_lsdesc { + in_addr_t router_id; +}; +#define NETWORK_LSDESC_GET_NBR_ROUTERID(x) \ + (((struct ospf6_network_lsdesc *)(x))->router_id) + +/* Link-LSA */ +#define OSPF6_LINK_LSA_MIN_SIZE 24U /* w/o 1st IPv6 prefix */ +struct ospf6_link_lsa { + uint8_t priority; + uint8_t options[3]; + struct in6_addr linklocal_addr; + uint32_t prefix_num; + /* followed by ospf6 prefix(es) */ +}; + +/* Intra-Area-Prefix-LSA */ +#define OSPF6_INTRA_PREFIX_LSA_MIN_SIZE 12U /* w/o 1st IPv6 prefix */ +struct ospf6_intra_prefix_lsa { + uint16_t prefix_num; + uint16_t ref_type; + uint32_t ref_id; + in_addr_t ref_adv_router; + /* followed by ospf6 prefix(es) */ +}; + + +#define OSPF6_ROUTER_LSA_SCHEDULE(oa) \ + do { \ + if (CHECK_FLAG((oa)->flag, OSPF6_AREA_ENABLE)) \ + event_add_event(master, ospf6_router_lsa_originate, \ + oa, 0, &(oa)->thread_router_lsa); \ + } while (0) +#define OSPF6_NETWORK_LSA_SCHEDULE(oi) \ + do { \ + if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ + event_add_event(master, ospf6_network_lsa_originate, \ + oi, 0, &(oi)->thread_network_lsa); \ + } while (0) +#define OSPF6_LINK_LSA_SCHEDULE(oi) \ + do { \ + if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ + event_add_event(master, ospf6_link_lsa_originate, oi, \ + 0, &(oi)->thread_link_lsa); \ + } while (0) +#define OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(oa) \ + do { \ + if (CHECK_FLAG((oa)->flag, OSPF6_AREA_ENABLE)) \ + event_add_event( \ + master, ospf6_intra_prefix_lsa_originate_stub, \ + oa, 0, &(oa)->thread_intra_prefix_lsa); \ + } while (0) +#define OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT(oi) \ + do { \ + if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ + event_add_event( \ + master, \ + ospf6_intra_prefix_lsa_originate_transit, oi, \ + 0, &(oi)->thread_intra_prefix_lsa); \ + } while (0) + +#define OSPF6_AS_EXTERN_LSA_SCHEDULE(oi) \ + do { \ + if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ + event_add_event(master, ospf6_orig_as_external_lsa, \ + oi, 0, &(oi)->thread_as_extern_lsa); \ + } while (0) + +#define OSPF6_ROUTER_LSA_EXECUTE(oa) \ + do { \ + if (CHECK_FLAG((oa)->flag, OSPF6_AREA_ENABLE)) \ + event_execute(master, ospf6_router_lsa_originate, oa, \ + 0, NULL); \ + } while (0) + +#define OSPF6_NETWORK_LSA_EXECUTE(oi) \ + do { \ + EVENT_OFF((oi)->thread_network_lsa); \ + event_execute(master, ospf6_network_lsa_originate, oi, 0, \ + NULL); \ + } while (0) + +#define OSPF6_LINK_LSA_EXECUTE(oi) \ + do { \ + if (!CHECK_FLAG((oi)->flag, OSPF6_INTERFACE_DISABLE)) \ + event_execute(master, ospf6_link_lsa_originate, oi, \ + 0, NULL); \ + } while (0) + +#define OSPF6_INTRA_PREFIX_LSA_EXECUTE_TRANSIT(oi) \ + do { \ + EVENT_OFF((oi)->thread_intra_prefix_lsa); \ + event_execute(master, \ + ospf6_intra_prefix_lsa_originate_transit, oi, \ + 0, NULL); \ + } while (0) + +#define OSPF6_AS_EXTERN_LSA_EXECUTE(oi) \ + do { \ + EVENT_OFF((oi)->thread_as_extern_lsa); \ + event_execute(master, ospf6_orig_as_external_lsa, oi, 0, NULL);\ + } while (0) + +/* Function Prototypes */ +extern char *ospf6_router_lsdesc_lookup(uint8_t type, uint32_t interface_id, + uint32_t neighbor_interface_id, + uint32_t neighbor_router_id, + struct ospf6_lsa *lsa); +extern char *ospf6_network_lsdesc_lookup(uint32_t router_id, + struct ospf6_lsa *lsa); + +extern int ospf6_router_is_stub_router(struct ospf6_lsa *lsa); +extern void ospf6_router_lsa_originate(struct event *thread); +extern void ospf6_network_lsa_originate(struct event *thread); +extern void ospf6_link_lsa_originate(struct event *thread); +extern void ospf6_intra_prefix_lsa_originate_transit(struct event *thread); +extern void ospf6_intra_prefix_lsa_originate_stub(struct event *thread); +extern void ospf6_intra_prefix_lsa_add(struct ospf6_lsa *lsa); +extern void ospf6_intra_prefix_lsa_remove(struct ospf6_lsa *lsa); +extern void ospf6_orig_as_external_lsa(struct event *thread); +extern void ospf6_intra_route_calculation(struct ospf6_area *oa); +extern void ospf6_intra_brouter_calculation(struct ospf6_area *oa); +extern void ospf6_intra_prefix_route_ecmp_path(struct ospf6_area *oa, + struct ospf6_route *old, + struct ospf6_route *route); + +extern void ospf6_intra_init(void); + +extern int config_write_ospf6_debug_brouter(struct vty *vty); +extern void install_element_ospf6_debug_brouter(void); + +#endif /* OSPF6_LSA_H */ diff --git a/ospf6d/ospf6_lsa.c b/ospf6d/ospf6_lsa.c new file mode 100644 index 0000000..0177518 --- /dev/null +++ b/ospf6d/ospf6_lsa.c @@ -0,0 +1,1219 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +/* Include other stuffs */ +#include "log.h" +#include "linklist.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "frrevent.h" +#include "checksum.h" +#include "frrstr.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_asbr.h" +#include "ospf6_zebra.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" + +#include "ospf6_flood.h" +#include "ospf6d.h" + +#include "ospf6d/ospf6_lsa_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_LSA, "OSPF6 LSA"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_LSA_HEADER, "OSPF6 LSA header"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_LSA_SUMMARY, "OSPF6 LSA summary"); + +static struct ospf6_lsa_handler *lsa_handlers[OSPF6_LSTYPE_SIZE]; + +struct ospf6 *ospf6_get_by_lsdb(struct ospf6_lsa *lsa) +{ + struct ospf6 *ospf6 = NULL; + + switch (OSPF6_LSA_SCOPE(lsa->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + ospf6 = OSPF6_INTERFACE(lsa->lsdb->data)->area->ospf6; + break; + case OSPF6_SCOPE_AREA: + ospf6 = OSPF6_AREA(lsa->lsdb->data)->ospf6; + break; + case OSPF6_SCOPE_AS: + ospf6 = OSPF6_PROCESS(lsa->lsdb->data); + break; + default: + assert(0); + break; + } + return ospf6; +} + +static int ospf6_unknown_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_obj, bool use_json) +{ + char *start, *end, *current; + + start = ospf6_lsa_header_end(lsa->header); + end = ospf6_lsa_end(lsa->header); + + if (use_json) { + json_object_string_add(json_obj, "lsaType", "unknown"); + } else { + vty_out(vty, " Unknown contents:\n"); + for (current = start; current < end; current++) { + if ((current - start) % 16 == 0) + vty_out(vty, "\n "); + else if ((current - start) % 4 == 0) + vty_out(vty, " "); + + vty_out(vty, "%02x", *current); + } + + vty_out(vty, "\n\n"); + } + return 0; +} + +static struct ospf6_lsa_handler unknown_handler = { + .lh_type = OSPF6_LSTYPE_UNKNOWN, + .lh_name = "Unknown", + .lh_short_name = "Unk", + .lh_show = ospf6_unknown_lsa_show, + .lh_get_prefix_str = NULL, + .lh_debug = 0 /* No default debug */ +}; + +void ospf6_install_lsa_handler(struct ospf6_lsa_handler *handler) +{ + /* type in handler is host byte order */ + unsigned int index = handler->lh_type & OSPF6_LSTYPE_FCODE_MASK; + + assertf(index < array_size(lsa_handlers), "index=%x", index); + assertf(lsa_handlers[index] == NULL, "old=%s, new=%s", + lsa_handlers[index]->lh_name, handler->lh_name); + + lsa_handlers[index] = handler; +} + +struct ospf6_lsa_handler *ospf6_get_lsa_handler(uint16_t type) +{ + struct ospf6_lsa_handler *handler = NULL; + unsigned int index = ntohs(type) & OSPF6_LSTYPE_FCODE_MASK; + + if (index < array_size(lsa_handlers)) + handler = lsa_handlers[index]; + + if (handler == NULL) + handler = &unknown_handler; + + return handler; +} + +const char *ospf6_lstype_name(uint16_t type) +{ + static char buf[8]; + const struct ospf6_lsa_handler *handler; + + handler = ospf6_get_lsa_handler(type); + if (handler && handler != &unknown_handler) + return handler->lh_name; + + snprintf(buf, sizeof(buf), "0x%04hx", ntohs(type)); + return buf; +} + +const char *ospf6_lstype_short_name(uint16_t type) +{ + static char buf[8]; + const struct ospf6_lsa_handler *handler; + + handler = ospf6_get_lsa_handler(type); + if (handler) + return handler->lh_short_name; + + snprintf(buf, sizeof(buf), "0x%04hx", ntohs(type)); + return buf; +} + +uint8_t ospf6_lstype_debug(uint16_t type) +{ + const struct ospf6_lsa_handler *handler; + handler = ospf6_get_lsa_handler(type); + return handler->lh_debug; +} + +int metric_type(struct ospf6 *ospf6, int type, uint8_t instance) +{ + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, instance); + + return ((!red || red->dmetric.type < 0) ? DEFAULT_METRIC_TYPE + : red->dmetric.type); +} + +int metric_value(struct ospf6 *ospf6, int type, uint8_t instance) +{ + struct ospf6_redist *red; + + red = ospf6_redist_lookup(ospf6, type, instance); + if (!red || red->dmetric.value < 0) { + if (type == DEFAULT_ROUTE) { + if (ospf6->default_originate == DEFAULT_ORIGINATE_ZEBRA) + return DEFAULT_DEFAULT_ORIGINATE_METRIC; + else + return DEFAULT_DEFAULT_ALWAYS_METRIC; + } else + return DEFAULT_DEFAULT_METRIC; + } + + return red->dmetric.value; +} + +/* RFC2328: Section 13.2 */ +int ospf6_lsa_is_differ(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2) +{ + int len; + + assert(OSPF6_LSA_IS_SAME(lsa1, lsa2)); + + /* XXX, Options ??? */ + + ospf6_lsa_age_current(lsa1); + ospf6_lsa_age_current(lsa2); + if (ntohs(lsa1->header->age) == OSPF_LSA_MAXAGE + && ntohs(lsa2->header->age) != OSPF_LSA_MAXAGE) + return 1; + if (ntohs(lsa1->header->age) != OSPF_LSA_MAXAGE + && ntohs(lsa2->header->age) == OSPF_LSA_MAXAGE) + return 1; + + /* compare body */ + if (ospf6_lsa_size(lsa1->header) != ospf6_lsa_size(lsa2->header)) + return 1; + + len = ospf6_lsa_size(lsa1->header) - sizeof(struct ospf6_lsa_header); + return memcmp(lsa1->header + 1, lsa2->header + 1, len); +} + +int ospf6_lsa_is_changed(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2) +{ + int length; + + if (OSPF6_LSA_IS_MAXAGE(lsa1) ^ OSPF6_LSA_IS_MAXAGE(lsa2)) + return 1; + if (ospf6_lsa_size(lsa1->header) != ospf6_lsa_size(lsa2->header)) + return 1; + /* Going beyond LSA headers to compare the payload only makes sense, + * when both LSAs aren't header-only. */ + if (CHECK_FLAG(lsa1->flag, OSPF6_LSA_HEADERONLY) + != CHECK_FLAG(lsa2->flag, OSPF6_LSA_HEADERONLY)) { + zlog_warn( + "%s: only one of two (%s, %s) LSAs compared is header-only", + __func__, lsa1->name, lsa2->name); + return 1; + } + if (CHECK_FLAG(lsa1->flag, OSPF6_LSA_HEADERONLY)) + return 0; + + length = ospf6_lsa_size(lsa1->header) - sizeof(struct ospf6_lsa_header); + /* Once upper layer verifies LSAs received, length underrun should + * become a warning. */ + if (length <= 0) + return 0; + + return memcmp(ospf6_lsa_header_end(lsa1->header), + ospf6_lsa_header_end(lsa2->header), length); +} + +/* ospf6 age functions */ +/* calculate birth */ +void ospf6_lsa_age_set(struct ospf6_lsa *lsa) +{ + struct timeval now; + + assert(lsa && lsa->header); + + monotime(&now); + + lsa->birth.tv_sec = now.tv_sec - ntohs(lsa->header->age); + lsa->birth.tv_usec = now.tv_usec; + + return; +} + +/* this function calculates current age from its birth, + then update age field of LSA header. return value is current age */ +uint16_t ospf6_lsa_age_current(struct ospf6_lsa *lsa) +{ + struct timeval now; + uint32_t ulage; + uint16_t age; + + assert(lsa); + assert(lsa->header); + + /* current time */ + monotime(&now); + + if (ntohs(lsa->header->age) >= OSPF_LSA_MAXAGE) { + /* ospf6_lsa_premature_aging () sets age to MAXAGE; when using + relative time, we cannot compare against lsa birth time, so + we catch this special case here. */ + lsa->header->age = htons(OSPF_LSA_MAXAGE); + return OSPF_LSA_MAXAGE; + } + /* calculate age */ + ulage = now.tv_sec - lsa->birth.tv_sec; + + /* if over MAXAGE, set to it */ + age = (ulage > OSPF_LSA_MAXAGE ? OSPF_LSA_MAXAGE : ulage); + + lsa->header->age = htons(age); + return age; +} + +/* update age field of LSA header with adding InfTransDelay */ +void ospf6_lsa_age_update_to_send(struct ospf6_lsa *lsa, uint32_t transdelay) +{ + unsigned short age; + + age = ospf6_lsa_age_current(lsa) + transdelay; + if (age > OSPF_LSA_MAXAGE) + age = OSPF_LSA_MAXAGE; + lsa->header->age = htons(age); +} + +void ospf6_lsa_premature_aging(struct ospf6_lsa *lsa) +{ + /* log */ + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) + zlog_debug("LSA: Premature aging: %s", lsa->name); + + EVENT_OFF(lsa->expire); + EVENT_OFF(lsa->refresh); + + /* + * We clear the LSA from the neighbor retx lists now because it + * will not get deleted later. Essentially, changing the age to + * MaxAge will prevent this LSA from being matched with its + * existing entries in the retx list thereby causing those entries + * to be silently replaced with its MaxAged version, but with ever + * increasing retx count causing this LSA to remain forever and + * for the MaxAge remover thread to be called forever too. + * + * The reason the previous entry silently disappears is that when + * entry is added to a neighbor's retx list, it replaces the existing + * entry. But since the ospf6_lsdb_add() routine is generic and not + * aware + * of the special semantics of retx count, the retx count is not + * decremented when its replaced. Attempting to add the incr and decr + * retx count routines as the hook_add and hook_remove for the retx + * lists + * have a problem because the hook_remove routine is called for MaxAge + * entries (as will be the case in a traditional LSDB, unlike in this + * case + * where an LSDB is used as an efficient tree structure to store all + * kinds + * of data) that are added instead of calling the hook_add routine. + */ + + ospf6_flood_clear(lsa); + + lsa->header->age = htons(OSPF_LSA_MAXAGE); + event_execute(master, ospf6_lsa_expire, lsa, 0, NULL); +} + +/* check which is more recent. if a is more recent, return -1; + if the same, return 0; otherwise(b is more recent), return 1 */ +int ospf6_lsa_compare(struct ospf6_lsa *a, struct ospf6_lsa *b) +{ + int32_t seqnuma, seqnumb; + uint16_t cksuma, cksumb; + uint16_t agea, ageb; + + assert(a && a->header); + assert(b && b->header); + assert(OSPF6_LSA_IS_SAME(a, b)); + + seqnuma = (int32_t)ntohl(a->header->seqnum); + seqnumb = (int32_t)ntohl(b->header->seqnum); + + /* compare by sequence number */ + if (seqnuma > seqnumb) + return -1; + if (seqnuma < seqnumb) + return 1; + + /* Checksum */ + cksuma = ntohs(a->header->checksum); + cksumb = ntohs(b->header->checksum); + if (cksuma > cksumb) + return -1; + if (cksuma < cksumb) + return 0; + + /* Update Age */ + agea = ospf6_lsa_age_current(a); + ageb = ospf6_lsa_age_current(b); + + /* MaxAge check */ + if (agea == OSPF_LSA_MAXAGE && ageb != OSPF_LSA_MAXAGE) + return -1; + else if (agea != OSPF_LSA_MAXAGE && ageb == OSPF_LSA_MAXAGE) + return 1; + + /* Age check */ + if (agea > ageb && agea - ageb >= OSPF_LSA_MAXAGE_DIFF) + return 1; + else if (agea < ageb && ageb - agea >= OSPF_LSA_MAXAGE_DIFF) + return -1; + + /* neither recent */ + return 0; +} + +char *ospf6_lsa_printbuf(struct ospf6_lsa *lsa, char *buf, int size) +{ + char id[16], adv_router[16]; + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + snprintf(buf, size, "[%s Id:%s Adv:%s]", + ospf6_lstype_name(lsa->header->type), id, adv_router); + return buf; +} + +void ospf6_lsa_header_print_raw(struct ospf6_lsa_header *header) +{ + char id[16], adv_router[16]; + inet_ntop(AF_INET, &header->id, id, sizeof(id)); + inet_ntop(AF_INET, &header->adv_router, adv_router, sizeof(adv_router)); + zlog_debug(" [%s Id:%s Adv:%s]", ospf6_lstype_name(header->type), id, + adv_router); + zlog_debug(" Age: %4hu SeqNum: %#08lx Cksum: %04hx Len: %d", + ntohs(header->age), (unsigned long)ntohl(header->seqnum), + ntohs(header->checksum), ntohs(header->length)); +} + +void ospf6_lsa_header_print(struct ospf6_lsa *lsa) +{ + ospf6_lsa_age_current(lsa); + ospf6_lsa_header_print_raw(lsa->header); +} + +void ospf6_lsa_show_summary_header(struct vty *vty) +{ + vty_out(vty, "%-4s %-15s%-15s%4s %8s %30s\n", "Type", "LSId", + "AdvRouter", "Age", "SeqNum", "Payload"); +} + +void ospf6_lsa_show_summary(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_array, bool use_json) +{ + char adv_router[16], id[16]; + int type; + const struct ospf6_lsa_handler *handler; + char buf[64]; + int cnt = 0; + json_object *json_obj = NULL; + + assert(lsa); + assert(lsa->header); + + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + + type = ntohs(lsa->header->type); + handler = ospf6_get_lsa_handler(lsa->header->type); + + if (use_json) + json_obj = json_object_new_object(); + + switch (type) { + case OSPF6_LSTYPE_INTER_PREFIX: + case OSPF6_LSTYPE_INTER_ROUTER: + case OSPF6_LSTYPE_AS_EXTERNAL: + case OSPF6_LSTYPE_TYPE_7: + if (use_json) { + json_object_string_add( + json_obj, "type", + ospf6_lstype_short_name(lsa->header->type)); + json_object_string_add(json_obj, "lsId", id); + json_object_string_add(json_obj, "advRouter", + adv_router); + json_object_int_add(json_obj, "age", + ospf6_lsa_age_current(lsa)); + json_object_int_add( + json_obj, "seqNum", + (unsigned long)ntohl(lsa->header->seqnum)); + json_object_string_add( + json_obj, "payload", + handler->lh_get_prefix_str(lsa, buf, + sizeof(buf), 0)); + json_object_array_add(json_array, json_obj); + } else + vty_out(vty, "%-4s %-15s%-15s%4hu %8lx %30s\n", + ospf6_lstype_short_name(lsa->header->type), id, + adv_router, ospf6_lsa_age_current(lsa), + (unsigned long)ntohl(lsa->header->seqnum), + handler->lh_get_prefix_str(lsa, buf, + sizeof(buf), 0)); + break; + case OSPF6_LSTYPE_ROUTER: + case OSPF6_LSTYPE_NETWORK: + case OSPF6_LSTYPE_GROUP_MEMBERSHIP: + case OSPF6_LSTYPE_LINK: + case OSPF6_LSTYPE_INTRA_PREFIX: + while (handler->lh_get_prefix_str(lsa, buf, sizeof(buf), cnt) + != NULL) { + if (use_json) { + json_object_string_add( + json_obj, "type", + ospf6_lstype_short_name( + lsa->header->type)); + json_object_string_add(json_obj, "lsId", id); + json_object_string_add(json_obj, "advRouter", + adv_router); + json_object_int_add(json_obj, "age", + ospf6_lsa_age_current(lsa)); + json_object_int_add( + json_obj, "seqNum", + (unsigned long)ntohl( + lsa->header->seqnum)); + json_object_string_add(json_obj, "payload", + buf); + json_object_array_add(json_array, json_obj); + json_obj = json_object_new_object(); + } else + vty_out(vty, "%-4s %-15s%-15s%4hu %8lx %30s\n", + ospf6_lstype_short_name( + lsa->header->type), + id, adv_router, + ospf6_lsa_age_current(lsa), + (unsigned long)ntohl( + lsa->header->seqnum), + buf); + cnt++; + } + if (use_json) + json_object_free(json_obj); + break; + default: + if (use_json) { + json_object_string_add( + json_obj, "type", + ospf6_lstype_short_name(lsa->header->type)); + json_object_string_add(json_obj, "lsId", id); + json_object_string_add(json_obj, "advRouter", + adv_router); + json_object_int_add(json_obj, "age", + ospf6_lsa_age_current(lsa)); + json_object_int_add( + json_obj, "seqNum", + (unsigned long)ntohl(lsa->header->seqnum)); + json_object_array_add(json_array, json_obj); + } else + vty_out(vty, "%-4s %-15s%-15s%4hu %8lx\n", + ospf6_lstype_short_name(lsa->header->type), id, + adv_router, ospf6_lsa_age_current(lsa), + (unsigned long)ntohl(lsa->header->seqnum)); + break; + } +} + +void ospf6_lsa_show_dump(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_array, bool use_json) +{ + uint8_t *start = NULL; + uint8_t *end = NULL; + uint8_t *current = NULL; + char byte[4]; + char *header_str = NULL; + char adv_router[INET6_ADDRSTRLEN]; + char id[INET6_ADDRSTRLEN]; + json_object *json = NULL; + + start = (uint8_t *)lsa->header; + end = (uint8_t *)ospf6_lsa_end(lsa->header); + + if (use_json) { + json = json_object_new_object(); + size_t header_str_sz = (2 * (end - start)) + 1; + + header_str = XMALLOC(MTYPE_OSPF6_LSA_HEADER, header_str_sz); + + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + + frrstr_hex(header_str, header_str_sz, start, end - start); + + json_object_string_add(json, "linkStateId", id); + json_object_string_add(json, "advertisingRouter", adv_router); + json_object_string_add(json, "header", header_str); + json_object_array_add(json_array, json); + + XFREE(MTYPE_OSPF6_LSA_HEADER, header_str); + } else { + vty_out(vty, "\n%s:\n", lsa->name); + + for (current = start; current < end; current++) { + if ((current - start) % 16 == 0) + vty_out(vty, "\n "); + else if ((current - start) % 4 == 0) + vty_out(vty, " "); + + snprintf(byte, sizeof(byte), "%02x", *current); + vty_out(vty, "%s", byte); + } + + vty_out(vty, "\n\n"); + } + + return; +} + +void ospf6_lsa_show_internal(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_array, bool use_json) +{ + char adv_router[64], id[64]; + json_object *json_obj; + + assert(lsa && lsa->header); + + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + + if (use_json) { + json_obj = json_object_new_object(); + json_object_int_add(json_obj, "age", + ospf6_lsa_age_current(lsa)); + json_object_string_add(json_obj, "type", + ospf6_lstype_name(lsa->header->type)); + json_object_string_add(json_obj, "linkStateId", id); + json_object_string_add(json_obj, "advertisingRouter", + adv_router); + json_object_int_add(json_obj, "lsSequenceNumber", + (unsigned long)ntohl(lsa->header->seqnum)); + json_object_int_add(json_obj, "checksum", + ntohs(lsa->header->checksum)); + json_object_int_add(json_obj, "length", + ospf6_lsa_size(lsa->header)); + json_object_int_add(json_obj, "flag", lsa->flag); + json_object_int_add(json_obj, "lock", lsa->lock); + json_object_int_add(json_obj, "reTxCount", lsa->retrans_count); + + /* Threads Data not added */ + json_object_array_add(json_array, json_obj); + } else { + vty_out(vty, "\n"); + vty_out(vty, "Age: %4hu Type: %s\n", ospf6_lsa_age_current(lsa), + ospf6_lstype_name(lsa->header->type)); + vty_out(vty, "Link State ID: %s\n", id); + vty_out(vty, "Advertising Router: %s\n", adv_router); + vty_out(vty, "LS Sequence Number: %#010lx\n", + (unsigned long)ntohl(lsa->header->seqnum)); + vty_out(vty, "CheckSum: %#06hx Length: %hu\n", + ntohs(lsa->header->checksum), + ospf6_lsa_size(lsa->header)); + vty_out(vty, "Flag: %x \n", lsa->flag); + vty_out(vty, "Lock: %d \n", lsa->lock); + vty_out(vty, "ReTx Count: %d\n", lsa->retrans_count); + vty_out(vty, "Threads: Expire: %p, Refresh: %p\n", lsa->expire, + lsa->refresh); + vty_out(vty, "\n"); + } + return; +} + +void ospf6_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json_array, bool use_json) +{ + char adv_router[64], id[64]; + const struct ospf6_lsa_handler *handler; + struct timeval now, res; + char duration[64]; + json_object *json_obj = NULL; + + assert(lsa && lsa->header); + + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + + monotime(&now); + timersub(&now, &lsa->installed, &res); + timerstring(&res, duration, sizeof(duration)); + if (use_json) { + json_obj = json_object_new_object(); + json_object_int_add(json_obj, "age", + ospf6_lsa_age_current(lsa)); + json_object_string_add(json_obj, "type", + ospf6_lstype_name(lsa->header->type)); + json_object_string_add(json_obj, "linkStateId", id); + json_object_string_add(json_obj, "advertisingRouter", + adv_router); + json_object_int_add(json_obj, "lsSequenceNumber", + (unsigned long)ntohl(lsa->header->seqnum)); + json_object_int_add(json_obj, "checksum", + ntohs(lsa->header->checksum)); + json_object_int_add(json_obj, "length", + ntohs(lsa->header->length)); + json_object_string_add(json_obj, "duration", duration); + } else { + vty_out(vty, "Age: %4hu Type: %s\n", ospf6_lsa_age_current(lsa), + ospf6_lstype_name(lsa->header->type)); + vty_out(vty, "Link State ID: %s\n", id); + vty_out(vty, "Advertising Router: %s\n", adv_router); + vty_out(vty, "LS Sequence Number: %#010lx\n", + (unsigned long)ntohl(lsa->header->seqnum)); + vty_out(vty, "CheckSum: %#06hx Length: %hu\n", + ntohs(lsa->header->checksum), + ntohs(lsa->header->length)); + vty_out(vty, "Duration: %s\n", duration); + } + + handler = ospf6_get_lsa_handler(lsa->header->type); + + if (handler->lh_show != NULL) + handler->lh_show(vty, lsa, json_obj, use_json); + else { + assert(unknown_handler.lh_show != NULL); + unknown_handler.lh_show(vty, lsa, json_obj, use_json); + } + + if (use_json) + json_object_array_add(json_array, json_obj); + else + vty_out(vty, "\n"); +} + +struct ospf6_lsa *ospf6_lsa_alloc(size_t lsa_length) +{ + struct ospf6_lsa *lsa; + + lsa = XCALLOC(MTYPE_OSPF6_LSA, sizeof(struct ospf6_lsa)); + lsa->header = XMALLOC(MTYPE_OSPF6_LSA_HEADER, lsa_length); + + return lsa; +} + +/* OSPFv3 LSA creation/deletion function */ +struct ospf6_lsa *ospf6_lsa_create(struct ospf6_lsa_header *header) +{ + struct ospf6_lsa *lsa = NULL; + uint16_t lsa_size = 0; + + /* size of the entire LSA */ + lsa_size = ospf6_lsa_size(header); /* XXX vulnerable */ + + lsa = ospf6_lsa_alloc(lsa_size); + + /* copy LSA from original header */ + memcpy(lsa->header, header, lsa_size); + + /* dump string */ + ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); + + /* calculate birth of this lsa */ + ospf6_lsa_age_set(lsa); + + return lsa; +} + +struct ospf6_lsa *ospf6_lsa_create_headeronly(struct ospf6_lsa_header *header) +{ + struct ospf6_lsa *lsa = NULL; + + lsa = ospf6_lsa_alloc(sizeof(struct ospf6_lsa_header)); + + memcpy(lsa->header, header, sizeof(struct ospf6_lsa_header)); + + SET_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY); + + /* dump string */ + ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); + + /* calculate birth of this lsa */ + ospf6_lsa_age_set(lsa); + + return lsa; +} + +void ospf6_lsa_delete(struct ospf6_lsa *lsa) +{ + assert(lsa->lock == 0); + + /* cancel threads */ + EVENT_OFF(lsa->expire); + EVENT_OFF(lsa->refresh); + + /* do free */ + XFREE(MTYPE_OSPF6_LSA_HEADER, lsa->header); + XFREE(MTYPE_OSPF6_LSA, lsa); +} + +struct ospf6_lsa *ospf6_lsa_copy(struct ospf6_lsa *lsa) +{ + struct ospf6_lsa *copy = NULL; + + ospf6_lsa_age_current(lsa); + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY)) + copy = ospf6_lsa_create_headeronly(lsa->header); + else + copy = ospf6_lsa_create(lsa->header); + assert(copy->lock == 0); + + copy->birth = lsa->birth; + copy->originated = lsa->originated; + copy->received = lsa->received; + copy->installed = lsa->installed; + copy->lsdb = lsa->lsdb; + copy->rn = NULL; + + return copy; +} + +/* increment reference counter of struct ospf6_lsa */ +struct ospf6_lsa *ospf6_lsa_lock(struct ospf6_lsa *lsa) +{ + lsa->lock++; + return lsa; +} + +/* decrement reference counter of struct ospf6_lsa */ +void ospf6_lsa_unlock(struct ospf6_lsa **lsa) +{ + /* decrement reference counter */ + assert((*lsa)->lock > 0); + (*lsa)->lock--; + + if ((*lsa)->lock != 0) + return; + + ospf6_lsa_delete(*lsa); + *lsa = NULL; +} + + +/* ospf6 lsa expiry */ +void ospf6_lsa_expire(struct event *thread) +{ + struct ospf6_lsa *lsa; + struct ospf6 *ospf6; + + lsa = (struct ospf6_lsa *)EVENT_ARG(thread); + + assert(lsa && lsa->header); + assert(OSPF6_LSA_IS_MAXAGE(lsa)); + assert(!lsa->refresh); + + lsa->expire = (struct event *)NULL; + + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) { + zlog_debug("LSA Expire:"); + ospf6_lsa_header_print(lsa); + } + + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_HEADERONLY)) + return; /* dbexchange will do something ... */ + ospf6 = ospf6_get_by_lsdb(lsa); + assert(ospf6); + + /* reinstall lsa */ + ospf6_install_lsa(lsa); + + /* reflood lsa */ + ospf6_flood(NULL, lsa); + + /* schedule maxage remover */ + ospf6_maxage_remove(ospf6); +} + +void ospf6_lsa_refresh(struct event *thread) +{ + struct ospf6_lsa *old, *self, *new; + struct ospf6_lsdb *lsdb_self; + + old = (struct ospf6_lsa *)EVENT_ARG(thread); + assert(old && old->header); + + old->refresh = (struct event *)NULL; + + lsdb_self = ospf6_get_scoped_lsdb_self(old); + self = ospf6_lsdb_lookup(old->header->type, old->header->id, + old->header->adv_router, lsdb_self); + if (self == NULL) { + if (IS_OSPF6_DEBUG_LSA_TYPE(old->header->type)) + zlog_debug("Refresh: could not find self LSA, flush %s", + old->name); + ospf6_lsa_premature_aging(old); + return; + } + + /* Reset age, increment LS sequence number. */ + self->header->age = htons(0); + self->header->seqnum = + ospf6_new_ls_seqnum(self->header->type, self->header->id, + self->header->adv_router, old->lsdb); + ospf6_lsa_checksum(self->header); + + new = ospf6_lsa_create(self->header); + new->lsdb = old->lsdb; + event_add_timer(master, ospf6_lsa_refresh, new, OSPF_LS_REFRESH_TIME, + &new->refresh); + + /* store it in the LSDB for self-originated LSAs */ + ospf6_lsdb_add(ospf6_lsa_copy(new), lsdb_self); + + if (IS_OSPF6_DEBUG_LSA_TYPE(new->header->type)) { + zlog_debug("LSA Refresh:"); + ospf6_lsa_header_print(new); + } + + ospf6_install_lsa(new); + ospf6_flood(NULL, new); +} + +void ospf6_flush_self_originated_lsas_now(struct ospf6 *ospf6) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct ospf6_lsa *lsa; + const struct route_node *end = NULL; + uint32_t type, adv_router; + struct ospf6_interface *oi; + + ospf6->inst_shutdown = 1; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + end = ospf6_lsdb_head(oa->lsdb_self, 0, 0, ospf6->router_id, + &lsa); + while (lsa) { + /* RFC 2328 (14.1): Set MAXAGE */ + lsa->header->age = htons(OSPF_LSA_MAXAGE); + /* Flood MAXAGE LSA*/ + ospf6_flood(NULL, lsa); + + lsa = ospf6_lsdb_next(end, lsa); + } + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { + end = ospf6_lsdb_head(oi->lsdb_self, 0, 0, + ospf6->router_id, &lsa); + while (lsa) { + /* RFC 2328 (14.1): Set MAXAGE */ + lsa->header->age = htons(OSPF_LSA_MAXAGE); + /* Flood MAXAGE LSA*/ + ospf6_flood(NULL, lsa); + + lsa = ospf6_lsdb_next(end, lsa); + } + } + } + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + adv_router = ospf6->router_id; + for (ALL_LSDB_TYPED_ADVRTR(ospf6->lsdb, type, adv_router, lsa)) { + /* RFC 2328 (14.1): Set MAXAGE */ + lsa->header->age = htons(OSPF_LSA_MAXAGE); + ospf6_flood(NULL, lsa); + } +} + +/* Fletcher Checksum -- Refer to RFC1008. */ + +/* All the offsets are zero-based. The offsets in the RFC1008 are + one-based. */ +unsigned short ospf6_lsa_checksum(struct ospf6_lsa_header *lsa_header) +{ + uint8_t *buffer = (uint8_t *)&lsa_header->type; + int type_offset = + buffer - (uint8_t *)&lsa_header->age; /* should be 2 */ + + /* Skip the AGE field */ + uint16_t len = ospf6_lsa_size(lsa_header) - type_offset; + + /* Checksum offset starts from "type" field, not the beginning of the + lsa_header struct. The offset is 14, rather than 16. */ + int checksum_offset = (uint8_t *)&lsa_header->checksum - buffer; + + return (unsigned short)fletcher_checksum(buffer, len, checksum_offset); +} + +int ospf6_lsa_checksum_valid(struct ospf6_lsa_header *lsa_header) +{ + uint8_t *buffer = (uint8_t *)&lsa_header->type; + int type_offset = + buffer - (uint8_t *)&lsa_header->age; /* should be 2 */ + + /* Skip the AGE field */ + uint16_t len = ospf6_lsa_size(lsa_header) - type_offset; + + return (fletcher_checksum(buffer, len, FLETCHER_CHECKSUM_VALIDATE) + == 0); +} + +void ospf6_lsa_init(void) +{ + ospf6_install_lsa_handler(&unknown_handler); +} + +void ospf6_lsa_terminate(void) +{ +} + +static char *ospf6_lsa_handler_name(const struct ospf6_lsa_handler *h) +{ + static char buf[64]; + unsigned int i; + unsigned int size = strlen(h->lh_name); + + if (!strcmp(h->lh_name, "unknown") + && h->lh_type != OSPF6_LSTYPE_UNKNOWN) { + snprintf(buf, sizeof(buf), "%#04hx", h->lh_type); + return buf; + } + + for (i = 0; i < MIN(size, sizeof(buf)); i++) { + if (!islower((unsigned char)h->lh_name[i])) + buf[i] = tolower((unsigned char)h->lh_name[i]); + else + buf[i] = h->lh_name[i]; + } + buf[size] = '\0'; + return buf; +} + +void ospf6_lsa_debug_set_all(bool val) +{ + unsigned int i; + struct ospf6_lsa_handler *handler = NULL; + + for (i = 0; i < array_size(lsa_handlers); i++) { + handler = lsa_handlers[i]; + if (handler == NULL) + continue; + if (val) + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ALL); + else + UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ALL); + } +} + +DEFPY (debug_ospf6_lsa_all, + debug_ospf6_lsa_all_cmd, + "[no$no] debug ospf6 lsa all", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug Link State Advertisements (LSAs)\n" + "Display for all types of LSAs\n") +{ + ospf6_lsa_debug_set_all(!no); + return CMD_SUCCESS; +} + +DEFPY (debug_ospf6_lsa_aggregation, + debug_ospf6_lsa_aggregation_cmd, + "[no] debug ospf6 lsa aggregation", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug Link State Advertisements (LSAs)\n" + "External LSA Aggregation\n") +{ + + struct ospf6_lsa_handler *handler; + + handler = ospf6_get_lsa_handler(OSPF6_LSTYPE_AS_EXTERNAL); + if (handler == NULL) + return CMD_WARNING_CONFIG_FAILED; + + if (no) + UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_AGGR); + else + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_AGGR); + + return CMD_SUCCESS; +} + +DEFUN (debug_ospf6_lsa_type, + debug_ospf6_lsa_hex_cmd, + "debug ospf6 lsa []", + DEBUG_STR + OSPF6_STR + "Debug Link State Advertisements (LSAs)\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Router LSAs\n" + "Display As-External LSAs\n" + "Display NSSA LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Display LSAs of unknown origin\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n") +{ + int idx_lsa = 3; + int idx_type = 4; + unsigned int i; + struct ospf6_lsa_handler *handler = NULL; + + for (i = 0; i < array_size(lsa_handlers); i++) { + handler = lsa_handlers[i]; + if (handler == NULL) + continue; + if (strncmp(argv[idx_lsa]->arg, ospf6_lsa_handler_name(handler), + strlen(argv[idx_lsa]->arg)) + == 0) + break; + if (!strcasecmp(argv[idx_lsa]->arg, handler->lh_name)) + break; + handler = NULL; + } + + if (handler == NULL) + handler = &unknown_handler; + + if (argc == 5) { + if (strmatch(argv[idx_type]->text, "originate")) + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ORIGINATE); + else if (strmatch(argv[idx_type]->text, "examine")) + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN); + else if (strmatch(argv[idx_type]->text, "flooding")) + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD); + } else + SET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG); + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_lsa_type, + no_debug_ospf6_lsa_hex_cmd, + "no debug ospf6 lsa []", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug Link State Advertisements (LSAs)\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Router LSAs\n" + "Display As-External LSAs\n" + "Display NSSA LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Display LSAs of unknown origin\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n") +{ + int idx_lsa = 4; + int idx_type = 5; + unsigned int i; + struct ospf6_lsa_handler *handler = NULL; + + for (i = 0; i < array_size(lsa_handlers); i++) { + handler = lsa_handlers[i]; + if (handler == NULL) + continue; + if (strncmp(argv[idx_lsa]->arg, ospf6_lsa_handler_name(handler), + strlen(argv[idx_lsa]->arg)) + == 0) + break; + if (!strcasecmp(argv[idx_lsa]->arg, handler->lh_name)) + break; + } + + if (handler == NULL) + return CMD_SUCCESS; + + if (argc == 6) { + if (strmatch(argv[idx_type]->text, "originate")) + UNSET_FLAG(handler->lh_debug, + OSPF6_LSA_DEBUG_ORIGINATE); + if (strmatch(argv[idx_type]->text, "examine")) + UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN); + if (strmatch(argv[idx_type]->text, "flooding")) + UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD); + } else + UNSET_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG); + + return CMD_SUCCESS; +} + +void install_element_ospf6_debug_lsa(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_lsa_all_cmd); + install_element(CONFIG_NODE, &debug_ospf6_lsa_all_cmd); + install_element(ENABLE_NODE, &debug_ospf6_lsa_hex_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_lsa_hex_cmd); + install_element(CONFIG_NODE, &debug_ospf6_lsa_hex_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_lsa_hex_cmd); + + install_element(ENABLE_NODE, &debug_ospf6_lsa_aggregation_cmd); + install_element(CONFIG_NODE, &debug_ospf6_lsa_aggregation_cmd); +} + +int config_write_ospf6_debug_lsa(struct vty *vty) +{ + unsigned int i; + const struct ospf6_lsa_handler *handler; + bool debug_all = true; + + for (i = 0; i < array_size(lsa_handlers); i++) { + handler = lsa_handlers[i]; + if (handler == NULL) + continue; + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ALL) + < OSPF6_LSA_DEBUG_ALL) { + debug_all = false; + break; + } + } + + if (debug_all) { + vty_out(vty, "debug ospf6 lsa all\n"); + return 0; + } + + for (i = 0; i < array_size(lsa_handlers); i++) { + handler = lsa_handlers[i]; + if (handler == NULL) + continue; + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG)) + vty_out(vty, "debug ospf6 lsa %s\n", + ospf6_lsa_handler_name(handler)); + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_ORIGINATE)) + vty_out(vty, "debug ospf6 lsa %s originate\n", + ospf6_lsa_handler_name(handler)); + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_EXAMIN)) + vty_out(vty, "debug ospf6 lsa %s examine\n", + ospf6_lsa_handler_name(handler)); + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_FLOOD)) + vty_out(vty, "debug ospf6 lsa %s flooding\n", + ospf6_lsa_handler_name(handler)); + if (CHECK_FLAG(handler->lh_debug, OSPF6_LSA_DEBUG_AGGR)) + vty_out(vty, "debug ospf6 lsa aggregation\n"); + } + + return 0; +} diff --git a/ospf6d/ospf6_lsa.h b/ospf6d/ospf6_lsa.h new file mode 100644 index 0000000..4fc2f0d --- /dev/null +++ b/ospf6d/ospf6_lsa.h @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_LSA_H +#define OSPF6_LSA_H +#include "ospf6_top.h" +#include "lib/json.h" + +/* Debug option */ +#define OSPF6_LSA_DEBUG 0x01 +#define OSPF6_LSA_DEBUG_ORIGINATE 0x02 +#define OSPF6_LSA_DEBUG_EXAMIN 0x04 +#define OSPF6_LSA_DEBUG_FLOOD 0x08 +#define OSPF6_LSA_DEBUG_ALL \ + (OSPF6_LSA_DEBUG | OSPF6_LSA_DEBUG_ORIGINATE | OSPF6_LSA_DEBUG_EXAMIN \ + | OSPF6_LSA_DEBUG_FLOOD) +#define OSPF6_LSA_DEBUG_AGGR 0x10 + +/* OSPF LSA Default metric values */ +#define DEFAULT_DEFAULT_METRIC 20 +#define DEFAULT_DEFAULT_ORIGINATE_METRIC 10 +#define DEFAULT_DEFAULT_ALWAYS_METRIC 1 +#define DEFAULT_METRIC_TYPE 2 + +#define IS_OSPF6_DEBUG_LSA(name) \ + (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) & OSPF6_LSA_DEBUG) +#define IS_OSPF6_DEBUG_ORIGINATE(name) \ + (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) \ + & OSPF6_LSA_DEBUG_ORIGINATE) +#define IS_OSPF6_DEBUG_EXAMIN(name) \ + (ospf6_lstype_debug(htons(OSPF6_LSTYPE_##name)) \ + & OSPF6_LSA_DEBUG_EXAMIN) +#define IS_OSPF6_DEBUG_LSA_TYPE(type) \ + (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG) +#define IS_OSPF6_DEBUG_ORIGINATE_TYPE(type) \ + (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_ORIGINATE) +#define IS_OSPF6_DEBUG_EXAMIN_TYPE(type) \ + (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_EXAMIN) +#define IS_OSPF6_DEBUG_FLOOD_TYPE(type) \ + (ospf6_lstype_debug(type) & OSPF6_LSA_DEBUG_FLOOD) +#define IS_OSPF6_DEBUG_AGGR \ + (ospf6_lstype_debug(OSPF6_LSTYPE_AS_EXTERNAL) & OSPF6_LSA_DEBUG_AGGR) \ + +/* LSA definition */ + +#define OSPF6_MAX_LSASIZE 4096 + +/* Type */ +#define OSPF6_LSTYPE_UNKNOWN 0x0000 +#define OSPF6_LSTYPE_ROUTER 0x2001 +#define OSPF6_LSTYPE_NETWORK 0x2002 +#define OSPF6_LSTYPE_INTER_PREFIX 0x2003 +#define OSPF6_LSTYPE_INTER_ROUTER 0x2004 +#define OSPF6_LSTYPE_AS_EXTERNAL 0x4005 +#define OSPF6_LSTYPE_GROUP_MEMBERSHIP 0x2006 +#define OSPF6_LSTYPE_TYPE_7 0x2007 +#define OSPF6_LSTYPE_LINK 0x0008 +#define OSPF6_LSTYPE_INTRA_PREFIX 0x2009 +#define OSPF6_LSTYPE_GRACE_LSA 0x000b +#define OSPF6_LSTYPE_SIZE 0x000c + +/* Masks for LS Type : RFC 2740 A.4.2.1 "LS type" */ +#define OSPF6_LSTYPE_UBIT_MASK 0x8000 +#define OSPF6_LSTYPE_SCOPE_MASK 0x6000 +#define OSPF6_LSTYPE_FCODE_MASK 0x1fff + +/* LSA scope */ +#define OSPF6_SCOPE_LINKLOCAL 0x0000 +#define OSPF6_SCOPE_AREA 0x2000 +#define OSPF6_SCOPE_AS 0x4000 +#define OSPF6_SCOPE_RESERVED 0x6000 + +/* XXX U-bit handling should be treated here */ +#define OSPF6_LSA_SCOPE(type) (ntohs(type) & OSPF6_LSTYPE_SCOPE_MASK) + +/* LSA Header */ +#define OSPF6_LSA_HEADER_SIZE 20U +struct ospf6_lsa_header { + uint16_t age; /* LS age */ + uint16_t type; /* LS type */ + in_addr_t id; /* Link State ID */ + in_addr_t adv_router; /* Advertising Router */ + uint32_t seqnum; /* LS sequence number */ + uint16_t checksum; /* LS checksum */ + uint16_t length; /* LSA length */ +}; + +static inline char *ospf6_lsa_header_end(struct ospf6_lsa_header *header) +{ + return (char *)header + sizeof(struct ospf6_lsa_header); +} + +static inline char *ospf6_lsa_end(struct ospf6_lsa_header *header) +{ + return (char *)header + ntohs(header->length); +} + +static inline uint16_t ospf6_lsa_size(struct ospf6_lsa_header *header) +{ + return ntohs(header->length); +} + +#define OSPF6_LSA_IS_TYPE(t, L) \ + ((L)->header->type == htons(OSPF6_LSTYPE_##t) ? 1 : 0) +#define OSPF6_LSA_IS_SAME(L1, L2) \ + ((L1)->header->adv_router == (L2)->header->adv_router \ + && (L1)->header->id == (L2)->header->id \ + && (L1)->header->type == (L2)->header->type) +#define OSPF6_LSA_IS_MATCH(t, i, a, L) \ + ((L)->header->adv_router == (a) && (L)->header->id == (i) \ + && (L)->header->type == (t)) +#define OSPF6_LSA_IS_DIFFER(L1, L2) ospf6_lsa_is_differ (L1, L2) +#define OSPF6_LSA_IS_MAXAGE(L) (ospf6_lsa_age_current (L) == OSPF_LSA_MAXAGE) +#define OSPF6_LSA_IS_CHANGED(L1, L2) ospf6_lsa_is_changed (L1, L2) +#define OSPF6_LSA_IS_SEQWRAP(L) ((L)->header->seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER + 1)) + + +struct ospf6_lsa { + char name[64]; /* dump string */ + + struct route_node *rn; + + unsigned char lock; /* reference counter */ + unsigned char flag; /* special meaning (e.g. floodback) */ + + struct timeval birth; /* tv_sec when LS age 0 */ + struct timeval originated; /* used by MinLSInterval check */ + struct timeval received; /* used by MinLSArrival check */ + struct timeval installed; + + struct event *expire; + struct event *refresh; /* For self-originated LSA */ + + int retrans_count; + + struct ospf6_lsdb *lsdb; + + in_addr_t external_lsa_id; + + /* lsa instance */ + struct ospf6_lsa_header *header; + + /*For topo chg detection in HELPER role*/ + bool tobe_acknowledged; +}; + +#define OSPF6_LSA_HEADERONLY 0x01 +#define OSPF6_LSA_FLOODBACK 0x02 +#define OSPF6_LSA_DUPLICATE 0x04 +#define OSPF6_LSA_IMPLIEDACK 0x08 +#define OSPF6_LSA_UNAPPROVED 0x10 +#define OSPF6_LSA_SEQWRAPPED 0x20 +#define OSPF6_LSA_FLUSH 0x40 + +struct ospf6_lsa_handler { + uint16_t lh_type; /* host byte order */ + const char *lh_name; + const char *lh_short_name; + int (*lh_show)(struct vty *, struct ospf6_lsa *, json_object *json_obj, + bool use_json); + char *(*lh_get_prefix_str)(struct ospf6_lsa *, char *buf, int buflen, + int pos); + + uint8_t lh_debug; +}; + +#define OSPF6_LSA_IS_KNOWN(t) \ + (ospf6_get_lsa_handler(t)->lh_type != OSPF6_LSTYPE_UNKNOWN ? 1 : 0) + +/* Macro for LSA Origination */ +/* addr is (struct prefix *) */ +#define CONTINUE_IF_ADDRESS_LINKLOCAL(debug, addr) \ + if (IN6_IS_ADDR_LINKLOCAL(&(addr)->u.prefix6)) { \ + if (debug) \ + zlog_debug("Filter out Linklocal: %pFX", addr); \ + continue; \ + } + +#define CONTINUE_IF_ADDRESS_UNSPECIFIED(debug, addr) \ + if (IN6_IS_ADDR_UNSPECIFIED(&(addr)->u.prefix6)) { \ + if (debug) \ + zlog_debug("Filter out Unspecified: %pFX", addr); \ + continue; \ + } + +#define CONTINUE_IF_ADDRESS_LOOPBACK(debug, addr) \ + if (IN6_IS_ADDR_LOOPBACK(&(addr)->u.prefix6)) { \ + if (debug) \ + zlog_debug("Filter out Loopback: %pFX", addr); \ + continue; \ + } + +#define CONTINUE_IF_ADDRESS_V4COMPAT(debug, addr) \ + if (IN6_IS_ADDR_V4COMPAT(&(addr)->u.prefix6)) { \ + if (debug) \ + zlog_debug("Filter out V4Compat: %pFX", addr); \ + continue; \ + } + +#define CONTINUE_IF_ADDRESS_V4MAPPED(debug, addr) \ + if (IN6_IS_ADDR_V4MAPPED(&(addr)->u.prefix6)) { \ + if (debug) \ + zlog_debug("Filter out V4Mapped: %pFX", addr); \ + continue; \ + } + +#define CHECK_LSA_TOPO_CHG_ELIGIBLE(type) \ + ((type == OSPF6_LSTYPE_ROUTER) \ + || (type == OSPF6_LSTYPE_NETWORK) \ + || (type == OSPF6_LSTYPE_INTER_PREFIX) \ + || (type == OSPF6_LSTYPE_INTER_ROUTER) \ + || (type == OSPF6_LSTYPE_AS_EXTERNAL) \ + || (type == OSPF6_LSTYPE_TYPE_7) \ + || (type == OSPF6_LSTYPE_INTRA_PREFIX)) + +/* Function Prototypes */ +extern const char *ospf6_lstype_name(uint16_t type); +extern const char *ospf6_lstype_short_name(uint16_t type); +extern uint8_t ospf6_lstype_debug(uint16_t type); +extern int metric_type(struct ospf6 *ospf6, int type, uint8_t instance); +extern int metric_value(struct ospf6 *ospf6, int type, uint8_t instance); +extern int ospf6_lsa_is_differ(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2); +extern int ospf6_lsa_is_changed(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2); +extern uint16_t ospf6_lsa_age_current(struct ospf6_lsa *lsa); +extern void ospf6_lsa_age_update_to_send(struct ospf6_lsa *lsa, + uint32_t transdelay); +extern void ospf6_lsa_premature_aging(struct ospf6_lsa *lsa); +extern int ospf6_lsa_compare(struct ospf6_lsa *lsa1, struct ospf6_lsa *lsa2); + +extern char *ospf6_lsa_printbuf(struct ospf6_lsa *lsa, char *buf, int size); +extern void ospf6_lsa_header_print_raw(struct ospf6_lsa_header *header); +extern void ospf6_lsa_header_print(struct ospf6_lsa *lsa); +extern void ospf6_lsa_show_summary_header(struct vty *vty); +extern void ospf6_lsa_show_summary(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json); +extern void ospf6_lsa_show_dump(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json); +extern void ospf6_lsa_show_internal(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json); +extern void ospf6_lsa_show(struct vty *vty, struct ospf6_lsa *lsa, + json_object *json, bool use_json); + +extern struct ospf6_lsa *ospf6_lsa_alloc(size_t lsa_length); +extern struct ospf6_lsa *ospf6_lsa_create(struct ospf6_lsa_header *header); +extern struct ospf6_lsa * +ospf6_lsa_create_headeronly(struct ospf6_lsa_header *header); +extern void ospf6_lsa_delete(struct ospf6_lsa *lsa); +extern struct ospf6_lsa *ospf6_lsa_copy(struct ospf6_lsa *lsa); + +extern struct ospf6_lsa *ospf6_lsa_lock(struct ospf6_lsa *lsa); +extern void ospf6_lsa_unlock(struct ospf6_lsa **lsa); + +extern void ospf6_lsa_expire(struct event *thread); +extern void ospf6_lsa_refresh(struct event *thread); + +extern unsigned short ospf6_lsa_checksum(struct ospf6_lsa_header *lsah); +extern int ospf6_lsa_checksum_valid(struct ospf6_lsa_header *lsah); +extern int ospf6_lsa_prohibited_duration(uint16_t type, uint32_t id, + uint32_t adv_router, void *scope); + +extern void ospf6_install_lsa_handler(struct ospf6_lsa_handler *handler); +extern struct ospf6_lsa_handler *ospf6_get_lsa_handler(uint16_t type); +extern void ospf6_lsa_debug_set_all(bool val); + +extern void ospf6_lsa_init(void); +extern void ospf6_lsa_terminate(void); + +extern int config_write_ospf6_debug_lsa(struct vty *vty); +extern void install_element_ospf6_debug_lsa(void); +extern void ospf6_lsa_age_set(struct ospf6_lsa *lsa); +extern void ospf6_flush_self_originated_lsas_now(struct ospf6 *ospf6); +extern struct ospf6 *ospf6_get_by_lsdb(struct ospf6_lsa *lsa); +struct ospf6_lsa *ospf6_find_external_lsa(struct ospf6 *ospf6, + struct prefix *p); +#endif /* OSPF6_LSA_H */ diff --git a/ospf6d/ospf6_lsdb.c b/ospf6d/ospf6_lsdb.c new file mode 100644 index 0000000..9aca555 --- /dev/null +++ b/ospf6d/ospf6_lsdb.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" + +#include "ospf6_proto.h" +#include "ospf6_area.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6_route.h" +#include "ospf6d.h" +#include "bitfield.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_LSDB, "OSPF6 LSA database"); + +struct ospf6_lsdb *ospf6_lsdb_create(void *data) +{ + struct ospf6_lsdb *lsdb; + + lsdb = XCALLOC(MTYPE_OSPF6_LSDB, sizeof(struct ospf6_lsdb)); + memset(lsdb, 0, sizeof(struct ospf6_lsdb)); + + lsdb->data = data; + lsdb->table = route_table_init(); + return lsdb; +} + +void ospf6_lsdb_delete(struct ospf6_lsdb *lsdb) +{ + if (lsdb != NULL) { + ospf6_lsdb_remove_all(lsdb); + route_table_finish(lsdb->table); + XFREE(MTYPE_OSPF6_LSDB, lsdb); + } +} + +static void ospf6_lsdb_set_key(struct prefix_ipv6 *key, const void *value, + int len) +{ + assert(key->prefixlen % 8 == 0); + + memcpy((caddr_t)&key->prefix + key->prefixlen / 8, (caddr_t)value, len); + key->family = AF_INET6; + key->prefixlen += len * 8; +} + +#ifdef DEBUG +static void _lsdb_count_assert(struct ospf6_lsdb *lsdb) +{ + struct ospf6_lsa *debug, *debugnext; + unsigned int num = 0; + for (ALL_LSDB(lsdb, debug, debugnext)) + num++; + + if (num == lsdb->count) + return; + + zlog_debug("PANIC !! lsdb[%p]->count = %d, real = %d", lsdb, + lsdb->count, num); + for (ALL_LSDB(lsdb, debug, debugnext)) + zlog_debug("%s lsdb[%p]", debug->name, debug->lsdb); + zlog_debug("DUMP END"); + + assert(num == lsdb->count); +} +#define ospf6_lsdb_count_assert(t) (_lsdb_count_assert (t)) +#else /*DEBUG*/ +#define ospf6_lsdb_count_assert(t) ((void) 0) +#endif /*DEBUG*/ + +static inline void ospf6_lsdb_stats_update(struct ospf6_lsa *lsa, + struct ospf6_lsdb *lsdb, int count) +{ + uint16_t stat = ntohs(lsa->header->type) & OSPF6_LSTYPE_FCODE_MASK; + + if (stat >= OSPF6_LSTYPE_SIZE) + stat = OSPF6_LSTYPE_UNKNOWN; + lsdb->stats[stat] += count; +} + +void ospf6_lsdb_add(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb) +{ + struct prefix_ipv6 key; + struct route_node *current; + struct ospf6_lsa *old = NULL; + + memset(&key, 0, sizeof(key)); + ospf6_lsdb_set_key(&key, &lsa->header->type, sizeof(lsa->header->type)); + ospf6_lsdb_set_key(&key, &lsa->header->adv_router, + sizeof(lsa->header->adv_router)); + ospf6_lsdb_set_key(&key, &lsa->header->id, sizeof(lsa->header->id)); + + current = route_node_get(lsdb->table, (struct prefix *)&key); + old = current->info; + current->info = lsa; + lsa->rn = current; + ospf6_lsa_lock(lsa); + + if (!old) { + lsdb->count++; + ospf6_lsdb_stats_update(lsa, lsdb, 1); + + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + if (lsdb->hook_remove) + (*lsdb->hook_remove)(lsa); + } else { + if (lsdb->hook_add) + (*lsdb->hook_add)(lsa); + } + } else { + lsa->retrans_count = old->retrans_count; + + if (OSPF6_LSA_IS_CHANGED(old, lsa)) { + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + if (lsdb->hook_remove) { + (*lsdb->hook_remove)(old); + (*lsdb->hook_remove)(lsa); + } + } else if (OSPF6_LSA_IS_MAXAGE(old)) { + if (lsdb->hook_add) + (*lsdb->hook_add)(lsa); + } else { + if (lsdb->hook_remove) + (*lsdb->hook_remove)(old); + if (lsdb->hook_add) + (*lsdb->hook_add)(lsa); + } + } + /* to free the lookup lock in node get*/ + route_unlock_node(current); + ospf6_lsa_unlock(&old); + } + + ospf6_lsdb_count_assert(lsdb); +} + +void ospf6_lsdb_remove(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb) +{ + struct route_node *node; + struct prefix_ipv6 key; + + memset(&key, 0, sizeof(key)); + ospf6_lsdb_set_key(&key, &lsa->header->type, sizeof(lsa->header->type)); + ospf6_lsdb_set_key(&key, &lsa->header->adv_router, + sizeof(lsa->header->adv_router)); + ospf6_lsdb_set_key(&key, &lsa->header->id, sizeof(lsa->header->id)); + + node = route_node_lookup(lsdb->table, (struct prefix *)&key); + assert(node && node->info == lsa); + + node->info = NULL; + lsdb->count--; + ospf6_lsdb_stats_update(lsa, lsdb, -1); + + if (lsdb->hook_remove) + (*lsdb->hook_remove)(lsa); + + route_unlock_node(node); /* to free the lookup lock */ + route_unlock_node(node); /* to free the original lock */ + ospf6_lsa_unlock(&lsa); + + ospf6_lsdb_count_assert(lsdb); +} + +struct ospf6_lsa *ospf6_lsdb_lookup(uint16_t type, uint32_t id, + uint32_t adv_router, + struct ospf6_lsdb *lsdb) +{ + struct route_node *node; + struct prefix_ipv6 key; + + if (lsdb == NULL) + return NULL; + + memset(&key, 0, sizeof(key)); + ospf6_lsdb_set_key(&key, &type, sizeof(type)); + ospf6_lsdb_set_key(&key, &adv_router, sizeof(adv_router)); + ospf6_lsdb_set_key(&key, &id, sizeof(id)); + + node = route_node_lookup(lsdb->table, (struct prefix *)&key); + if (node == NULL || node->info == NULL) + return NULL; + + route_unlock_node(node); + return (struct ospf6_lsa *)node->info; +} + +struct ospf6_lsa *ospf6_find_external_lsa(struct ospf6 *ospf6, struct prefix *p) +{ + struct ospf6_route *match; + struct ospf6_lsa *lsa; + struct ospf6_external_info *info; + + match = ospf6_route_lookup(p, ospf6->external_table); + if (match == NULL) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("No such route %pFX to withdraw", p); + + return NULL; + } + + info = match->route_option; + assert(info); + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + htonl(info->id), ospf6->router_id, ospf6->lsdb); + return lsa; +} + +struct ospf6_lsa *ospf6_find_inter_prefix_lsa(struct ospf6 *ospf6, + struct ospf6_area *area, + struct prefix *p) +{ + struct ospf6_lsa *lsa; + uint16_t type = htons(OSPF6_LSTYPE_INTER_PREFIX); + + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, lsa)) { + struct ospf6_inter_prefix_lsa *prefix_lsa; + struct prefix prefix; + + prefix_lsa = (struct ospf6_inter_prefix_lsa *) + ospf6_lsa_header_end(lsa->header); + prefix.family = AF_INET6; + prefix.prefixlen = prefix_lsa->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, prefix_lsa, + &prefix_lsa->prefix); + if (prefix_same(p, &prefix)) { + ospf6_lsa_unlock(&lsa); + return lsa; + } + } + + return NULL; +} + +struct ospf6_lsa *ospf6_lsdb_lookup_next(uint16_t type, uint32_t id, + uint32_t adv_router, + struct ospf6_lsdb *lsdb) +{ + struct route_node *node; + struct prefix_ipv6 key; + + if (lsdb == NULL) + return NULL; + + memset(&key, 0, sizeof(key)); + ospf6_lsdb_set_key(&key, &type, sizeof(type)); + ospf6_lsdb_set_key(&key, &adv_router, sizeof(adv_router)); + ospf6_lsdb_set_key(&key, &id, sizeof(id)); + + zlog_debug("lsdb_lookup_next: key: %pFX", &key); + + node = route_table_get_next(lsdb->table, &key); + + /* skip to real existing entry */ + while (node && node->info == NULL) + node = route_next(node); + + if (!node) + return NULL; + + route_unlock_node(node); + if (!node->info) + return NULL; + + return (struct ospf6_lsa *)node->info; +} + +const struct route_node *ospf6_lsdb_head(struct ospf6_lsdb *lsdb, int argmode, + uint16_t type, uint32_t adv_router, + struct ospf6_lsa **lsa) +{ + struct route_node *node, *end; + + *lsa = NULL; + + if (argmode > 0) { + struct prefix_ipv6 key = {.family = AF_INET6, .prefixlen = 0}; + + ospf6_lsdb_set_key(&key, &type, sizeof(type)); + if (argmode > 1) + ospf6_lsdb_set_key(&key, &adv_router, + sizeof(adv_router)); + + node = route_table_get_next(lsdb->table, &key); + if (!node || !prefix_match((struct prefix *)&key, &node->p)) + return NULL; + + for (end = node; end && end->parent + && end->parent->p.prefixlen >= key.prefixlen; + end = end->parent) + ; + } else { + node = route_top(lsdb->table); + end = NULL; + } + + while (node && !node->info) + node = route_next_until(node, end); + + if (!node) + return NULL; + if (!node->info) { + route_unlock_node(node); + return NULL; + } + + *lsa = node->info; + ospf6_lsa_lock(*lsa); + + return end; +} + +struct ospf6_lsa *ospf6_lsdb_next(const struct route_node *iterend, + struct ospf6_lsa *lsa) +{ + struct route_node *node = lsa->rn; + + ospf6_lsa_unlock(&lsa); + + do + node = route_next_until(node, iterend); + while (node && !node->info); + + if (node && node->info) { + struct ospf6_lsa *next = node->info; + ospf6_lsa_lock(next); + return next; + } + + if (node) + route_unlock_node(node); + return NULL; +} + +void ospf6_lsdb_remove_all(struct ospf6_lsdb *lsdb) +{ + struct ospf6_lsa *lsa, *lsanext; + + if (lsdb == NULL) + return; + + for (ALL_LSDB(lsdb, lsa, lsanext)) + ospf6_lsdb_remove(lsa, lsdb); +} + +void ospf6_lsdb_lsa_unlock(struct ospf6_lsa *lsa) +{ + if (lsa != NULL) { + if (lsa->rn != NULL) + route_unlock_node(lsa->rn); + ospf6_lsa_unlock(&lsa); + } +} + +int ospf6_lsdb_maxage_remover(struct ospf6_lsdb *lsdb) +{ + int reschedule = 0; + struct ospf6_lsa *lsa, *lsanext; + + for (ALL_LSDB(lsdb, lsa, lsanext)) { + if (!OSPF6_LSA_IS_MAXAGE(lsa)) { + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) + zlog_debug("Not MaxAge %s", lsa->name); + continue; + } + + if (lsa->retrans_count != 0) { + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) + zlog_debug("Remove MaxAge %s retrans_count %d", + lsa->name, lsa->retrans_count); + + reschedule = 1; + continue; + } + if (IS_OSPF6_DEBUG_LSA_TYPE(lsa->header->type)) + zlog_debug("Remove MaxAge %s", lsa->name); + + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED)) { + UNSET_FLAG(lsa->flag, OSPF6_LSA_SEQWRAPPED); + /* + * lsa->header->age = 0; + */ + lsa->header->seqnum = + htonl(OSPF_MAX_SEQUENCE_NUMBER + 1); + ospf6_lsa_checksum(lsa->header); + + EVENT_OFF(lsa->refresh); + event_execute(master, ospf6_lsa_refresh, lsa, 0, NULL); + } else { + zlog_debug("calling ospf6_lsdb_remove %s", lsa->name); + ospf6_lsdb_remove(lsa, lsdb); + } + } + + return (reschedule); +} + +uint32_t ospf6_new_ls_id(uint16_t type, uint32_t adv_router, + struct ospf6_lsdb *lsdb) +{ + struct ospf6_lsa *lsa; + uint32_t id = 1, tmp_id; + + /* This routine is curently invoked only for Inter-Prefix LSAs for + * non-summarized routes (no area/range). + */ + for (ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv_router, lsa)) { + tmp_id = ntohl(lsa->header->id); + if (tmp_id < id) + continue; + + if (tmp_id > id) { + ospf6_lsdb_lsa_unlock(lsa); + break; + } + id++; + } + + return ((uint32_t)htonl(id)); +} + +/* Decide new LS sequence number to originate. + note return value is network byte order */ +uint32_t ospf6_new_ls_seqnum(uint16_t type, uint32_t id, uint32_t adv_router, + struct ospf6_lsdb *lsdb) +{ + struct ospf6_lsa *lsa; + signed long seqnum = 0; + + /* if current database copy not found, return InitialSequenceNumber */ + lsa = ospf6_lsdb_lookup(type, id, adv_router, lsdb); + if (lsa == NULL) + seqnum = OSPF_INITIAL_SEQUENCE_NUMBER; + else + seqnum = (signed long)ntohl(lsa->header->seqnum) + 1; + + return ((uint32_t)htonl(seqnum)); +} diff --git a/ospf6d/ospf6_lsdb.h b/ospf6d/ospf6_lsdb.h new file mode 100644 index 0000000..604406d --- /dev/null +++ b/ospf6d/ospf6_lsdb.h @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_LSDB_H +#define OSPF6_LSDB_H + +#include "prefix.h" +#include "table.h" +#include "ospf6_route.h" + +struct ospf6_lsdb { + void *data; /* data structure that holds this lsdb */ + struct route_table *table; + uint32_t count; + uint32_t stats[OSPF6_LSTYPE_SIZE]; + void (*hook_add)(struct ospf6_lsa *); + void (*hook_remove)(struct ospf6_lsa *); +}; + +/* Function Prototypes */ +extern struct ospf6_lsdb *ospf6_lsdb_create(void *data); +extern void ospf6_lsdb_delete(struct ospf6_lsdb *lsdb); + +extern struct ospf6_lsa *ospf6_lsdb_lookup(uint16_t type, uint32_t id, + uint32_t adv_router, + struct ospf6_lsdb *lsdb); +extern struct ospf6_lsa *ospf6_lsdb_lookup_next(uint16_t type, uint32_t id, + uint32_t adv_router, + struct ospf6_lsdb *lsdb); +extern struct ospf6_lsa *ospf6_find_inter_prefix_lsa(struct ospf6 *ospf6, + struct ospf6_area *area, + struct prefix *p); + +extern void ospf6_lsdb_add(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb); +extern void ospf6_lsdb_remove(struct ospf6_lsa *lsa, struct ospf6_lsdb *lsdb); + +extern const struct route_node *ospf6_lsdb_head(struct ospf6_lsdb *lsdb, + int argmode, uint16_t type, + uint32_t adv_router, + struct ospf6_lsa **lsa); +extern struct ospf6_lsa *ospf6_lsdb_next(const struct route_node *iterend, + struct ospf6_lsa *lsa); + +#define ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv_router, lsa) \ + const struct route_node *iterend = \ + ospf6_lsdb_head(lsdb, 2, type, adv_router, &lsa); \ + lsa; \ + lsa = ospf6_lsdb_next(iterend, lsa) + +#define ALL_LSDB_TYPED(lsdb, type, lsa) \ + const struct route_node *iterend = \ + ospf6_lsdb_head(lsdb, 1, type, 0, &lsa); \ + lsa; \ + lsa = ospf6_lsdb_next(iterend, lsa) + +/* + * Since we are locking the lsa in ospf6_lsdb_head + * and then unlocking it in ospf6_lsa_unlock, when + * we cache the next pointer we need to increment + * the lock for the lsa so we don't accidentally free + * it really early. + */ +#define ALL_LSDB(lsdb, lsa, lsanext) \ + const struct route_node *iterend = ospf6_lsdb_head(lsdb, 0, 0, 0, \ + &lsa); \ + (lsa) != NULL && ospf6_lsa_lock(lsa) && \ + ((lsanext) = ospf6_lsdb_next(iterend, (lsa)), 1); \ + ospf6_lsa_unlock(&lsa), (lsa) = (lsanext) + +extern void ospf6_lsdb_remove_all(struct ospf6_lsdb *lsdb); +extern void ospf6_lsdb_lsa_unlock(struct ospf6_lsa *lsa); + +enum ospf_lsdb_show_level { + OSPF6_LSDB_SHOW_LEVEL_NORMAL = 0, + OSPF6_LSDB_SHOW_LEVEL_DETAIL, + OSPF6_LSDB_SHOW_LEVEL_INTERNAL, + OSPF6_LSDB_SHOW_LEVEL_DUMP, +}; + +extern void ospf6_lsdb_show(struct vty *vty, enum ospf_lsdb_show_level level, + uint16_t *type, uint32_t *id, uint32_t *adv_router, + struct ospf6_lsdb *lsdb, json_object *json, + bool use_json); + +extern uint32_t ospf6_new_ls_id(uint16_t type, uint32_t adv_router, + struct ospf6_lsdb *lsdb); +extern uint32_t ospf6_new_ls_seqnum(uint16_t type, uint32_t id, + uint32_t adv_router, + struct ospf6_lsdb *lsdb); +extern int ospf6_lsdb_maxage_remover(struct ospf6_lsdb *lsdb); + +#endif /* OSPF6_LSDB_H */ diff --git a/ospf6d/ospf6_main.c b/ospf6d/ospf6_main.c new file mode 100644 index 0000000..8320f11 --- /dev/null +++ b/ospf6d/ospf6_main.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1999 Yasuhiro Ohara + */ + +#include +#include +#include +#include + +#include "getopt.h" +#include "frrevent.h" +#include "log.h" +#include "command.h" +#include "vty.h" +#include "memory.h" +#include "if.h" +#include "filter.h" +#include "prefix.h" +#include "plist.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "vrf.h" +#include "bfd.h" +#include "libfrr.h" +#include "libagentx.h" + +#include "ospf6d.h" +#include "ospf6_top.h" +#include "ospf6_message.h" +#include "ospf6_network.h" +#include "ospf6_asbr.h" +#include "ospf6_lsa.h" +#include "ospf6_interface.h" +#include "ospf6_zebra.h" +#include "ospf6_routemap_nb.h" + +/* Default configuration file name for ospf6d. */ +#define OSPF6_DEFAULT_CONFIG "ospf6d.conf" + +/* GR and auth trailer persistent state */ +#define OSPF6D_STATE_NAME "%s/ospf6d.json", frr_libstatedir +#define OSPF6D_COMPAT_STATE_NAME "%s/ospf6d-gr.json", frr_runstatedir +/* for extra confusion, "ospf6d-at-seq-no.dat" is handled directly in + * ospf6_auth_trailer.c; the alternative would be somehow merging JSON which + * is excessive for just supporting a legacy compatibility file location + */ + +/* ospf6d privileges */ +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; + +struct zebra_privs_t ospf6d_privs = { +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +/* ospf6d options, we use GNU getopt library. */ +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct event_loop *master; + +static void __attribute__((noreturn)) ospf6_exit(int status) +{ + struct vrf *vrf; + struct interface *ifp; + struct ospf6 *ospf6; + struct listnode *node, *nnode; + + frr_early_fini(); + + bfd_protocol_integration_set_shutdown(true); + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + vrf = vrf_lookup_by_id(ospf6->vrf_id); + ospf6_delete(ospf6); + ospf6 = NULL; + FOR_ALL_INTERFACES (vrf, ifp) + if (ifp->info != NULL) + ospf6_interface_delete(ifp->info); + } + + ospf6_message_terminate(); + ospf6_asbr_terminate(); + ospf6_lsa_terminate(); + + /* reverse access_list_init */ + access_list_reset(); + + /* reverse prefix_list_init */ + prefix_list_add_hook(NULL); + prefix_list_delete_hook(NULL); + prefix_list_reset(); + + vrf_terminate(); + + if (zclient) { + zclient_stop(zclient); + zclient_free(zclient); + } + + ospf6_master_delete(); + + keychain_terminate(); + + frr_fini(); + + exit(status); +} + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); +} + +/* SIGINT handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal SIGINT"); + ospf6_exit(0); +} + +/* SIGTERM handler. */ +static void sigterm(void) +{ + zlog_notice("Terminating on signal SIGTERM"); + ospf6_exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_info("SIGUSR1 received"); + zlog_rotate(); +} + +struct frr_signal_t ospf6_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigterm, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, +}; + +static const struct frr_yang_module_info *const ospf6d_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &frr_ospf_route_map_info, + &frr_ospf6_route_map_info, + &ietf_key_chain_info, + &ietf_key_chain_deviation_info, +}; + +/* actual paths filled in main() */ +static char state_path[512]; +static char state_compat_path[512]; +static char *state_paths[] = { + state_path, + state_compat_path, + NULL, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(ospf6d, OSPF6, + .vty_port = OSPF6_VTY_PORT, + .proghelp = "Implementation of the OSPFv3 routing protocol.", + + .signals = ospf6_signals, + .n_signals = array_size(ospf6_signals), + + .privs = &ospf6d_privs, + + .yang_modules = ospf6d_yang_modules, + .n_yang_modules = array_size(ospf6d_yang_modules), + + .state_paths = state_paths, + ); +/* clang-format on */ + +/* Max wait time for config to load before accepting hellos */ +#define OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS 600 + +static void ospf6_config_finish(struct event *t) +{ + zlog_err("OSPF6 configuration end timer expired after %d seconds.", + OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS); +} + +static void ospf6_config_start(void) +{ + if (IS_OSPF6_DEBUG_EVENT) + zlog_debug("ospf6d config start received"); + EVENT_OFF(t_ospf6_cfg); + event_add_timer(master, ospf6_config_finish, NULL, + OSPF6_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf6_cfg); +} + +static void ospf6_config_end(void) +{ + if (IS_OSPF6_DEBUG_EVENT) + zlog_debug("ospf6d config end received"); + + EVENT_OFF(t_ospf6_cfg); +} + +/* Main routine of ospf6d. Treatment of argument and starting ospf finite + state machine is handled here. */ +int main(int argc, char *argv[], char *envp[]) +{ + int opt; + + frr_preinit(&ospf6d_di, argc, argv); + frr_opt_add("", longopts, ""); + + /* Command line argument treatment. */ + while (1) { + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + if (geteuid() != 0) { + errno = EPERM; + perror(ospf6d_di.progname); + exit(1); + } + + snprintf(state_path, sizeof(state_path), OSPF6D_STATE_NAME); + snprintf(state_compat_path, sizeof(state_compat_path), + OSPF6D_COMPAT_STATE_NAME); + + /* OSPF6 master init. */ + ospf6_master_init(frr_init()); + + /* thread master */ + master = om6->master; + + libagentx_init(); + keychain_init(); + ospf6_vrf_init(); + access_list_init(); + prefix_list_init(); + + /* initialize ospf6 */ + ospf6_init(master); + + /* Configuration processing callback initialization. */ + cmd_init_config_callbacks(ospf6_config_start, ospf6_config_end); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + ospf6_exit(0); +} diff --git a/ospf6d/ospf6_message.c b/ospf6d/ospf6_message.c new file mode 100644 index 0000000..a6ee8d8 --- /dev/null +++ b/ospf6d/ospf6_message.c @@ -0,0 +1,3279 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "memory.h" +#include "log.h" +#include "vty.h" +#include "command.h" +#include "frrevent.h" +#include "linklist.h" +#include "lib_errors.h" +#include "checksum.h" +#include "network.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_top.h" +#include "ospf6_network.h" +#include "ospf6_message.h" + +#include "ospf6_area.h" +#include "ospf6_neighbor.h" +#include "ospf6_interface.h" + +/* for structures and macros ospf6_lsa_examin() needs */ +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6_intra.h" + +#include "ospf6_flood.h" +#include "ospf6d.h" +#include "ospf6_gr.h" +#include +#include "lib/libospf.h" +#include "lib/keychain.h" +#include "ospf6_auth_trailer.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_MESSAGE, "OSPF6 message"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PACKET, "OSPF6 packet"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_FIFO, "OSPF6 FIFO queue"); + +unsigned char conf_debug_ospf6_message[6] = {0x03, 0, 0, 0, 0, 0}; + +const char *ospf6_message_type(int type) +{ + switch (type) { + case OSPF6_MESSAGE_TYPE_HELLO: + return "Hello"; + case OSPF6_MESSAGE_TYPE_DBDESC: + return "DbDesc"; + case OSPF6_MESSAGE_TYPE_LSREQ: + return "LSReq"; + case OSPF6_MESSAGE_TYPE_LSUPDATE: + return "LSUpdate"; + case OSPF6_MESSAGE_TYPE_LSACK: + return "LSAck"; + case OSPF6_MESSAGE_TYPE_UNKNOWN: + default: + return "unknown"; + } +} + +/* Minimum (besides the standard OSPF packet header) lengths for OSPF + packets of particular types, offset is the "type" field. */ +const uint16_t ospf6_packet_minlen[OSPF6_MESSAGE_TYPE_ALL] = { + 0, + OSPF6_HELLO_MIN_SIZE, + OSPF6_DB_DESC_MIN_SIZE, + OSPF6_LS_REQ_MIN_SIZE, + OSPF6_LS_UPD_MIN_SIZE, + OSPF6_LS_ACK_MIN_SIZE}; + +/* Minimum (besides the standard LSA header) lengths for LSAs of particular + types, offset is the "LSA function code" portion of "LSA type" field. */ +const uint16_t ospf6_lsa_minlen[OSPF6_LSTYPE_SIZE] = { + 0, + /* 0x2001 */ OSPF6_ROUTER_LSA_MIN_SIZE, + /* 0x2002 */ OSPF6_NETWORK_LSA_MIN_SIZE, + /* 0x2003 */ OSPF6_INTER_PREFIX_LSA_MIN_SIZE, + /* 0x2004 */ OSPF6_INTER_ROUTER_LSA_FIX_SIZE, + /* 0x4005 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE, + /* 0x2006 */ 0, + /* 0x2007 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE, + /* 0x0008 */ OSPF6_LINK_LSA_MIN_SIZE, + /* 0x2009 */ OSPF6_INTRA_PREFIX_LSA_MIN_SIZE, + /* 0x200a */ 0, + /* 0x000b */ OSPF6_GRACE_LSA_MIN_SIZE}; + +/* print functions */ + +static void ospf6_header_print(struct ospf6_header *oh) +{ + zlog_debug(" OSPFv%d Type:%d Len:%hu Router-ID:%pI4", oh->version, + oh->type, ntohs(oh->length), &oh->router_id); + zlog_debug(" Area-ID:%pI4 Cksum:%hx Instance-ID:%d", &oh->area_id, + ntohs(oh->checksum), oh->instance_id); +} + +void ospf6_hello_print(struct ospf6_header *oh, int action) +{ + struct ospf6_hello *hello; + char options[32]; + char *p; + + ospf6_header_print(oh); + assert(oh->type == OSPF6_MESSAGE_TYPE_HELLO); + + hello = (struct ospf6_hello *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + ospf6_options_printbuf(hello->options, options, sizeof(options)); + + zlog_debug(" I/F-Id:%ld Priority:%d Option:%s", + (unsigned long)ntohl(hello->interface_id), hello->priority, + options); + zlog_debug(" HelloInterval:%hu DeadInterval:%hu", + ntohs(hello->hello_interval), ntohs(hello->dead_interval)); + zlog_debug(" DR:%pI4 BDR:%pI4", &hello->drouter, &hello->bdrouter); + + if ((IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV) + && action == OSPF6_ACTION_RECV) + || (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND) + && action == OSPF6_ACTION_SEND)) { + + for (p = (char *)((caddr_t)hello + sizeof(struct ospf6_hello)); + p + sizeof(uint32_t) <= OSPF6_MESSAGE_END(oh); + p += sizeof(uint32_t)) + zlog_debug(" Neighbor: %pI4", (in_addr_t *)p); + + assert(p == OSPF6_MESSAGE_END(oh)); + } +} + +void ospf6_dbdesc_print(struct ospf6_header *oh, int action) +{ + struct ospf6_dbdesc *dbdesc; + char options[32]; + char *p; + + ospf6_header_print(oh); + assert(oh->type == OSPF6_MESSAGE_TYPE_DBDESC); + + dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + ospf6_options_printbuf(dbdesc->options, options, sizeof(options)); + + zlog_debug(" MBZ: %#x Option: %s IfMTU: %hu", dbdesc->reserved1, + options, ntohs(dbdesc->ifmtu)); + zlog_debug(" MBZ: %#x Bits: %s%s%s SeqNum: %#lx", dbdesc->reserved2, + (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) ? "I" : "-"), + (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) ? "M" : "-"), + (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) ? "m" : "s"), + (unsigned long)ntohl(dbdesc->seqnum)); + + if ((IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV) + && action == OSPF6_ACTION_RECV) + || (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND) + && action == OSPF6_ACTION_SEND)) { + + for (p = (char *)((caddr_t)dbdesc + + sizeof(struct ospf6_dbdesc)); + p + sizeof(struct ospf6_lsa_header) + <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsa_header)) + ospf6_lsa_header_print_raw( + (struct ospf6_lsa_header *)p); + + assert(p == OSPF6_MESSAGE_END(oh)); + } +} + +void ospf6_lsreq_print(struct ospf6_header *oh, int action) +{ + char *p; + + ospf6_header_print(oh); + assert(oh->type == OSPF6_MESSAGE_TYPE_LSREQ); + + if ((IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV) + && action == OSPF6_ACTION_RECV) + || (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND) + && action == OSPF6_ACTION_SEND)) { + + for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); + p + sizeof(struct ospf6_lsreq_entry) + <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsreq_entry)) { + struct ospf6_lsreq_entry *e = + (struct ospf6_lsreq_entry *)p; + + zlog_debug(" [%s Id:%pI4 Adv:%pI4]", + ospf6_lstype_name(e->type), &e->id, + &e->adv_router); + } + + assert(p == OSPF6_MESSAGE_END(oh)); + } +} + +void ospf6_lsupdate_print(struct ospf6_header *oh, int action) +{ + struct ospf6_lsupdate *lsupdate; + unsigned long num; + char *p; + + ospf6_header_print(oh); + assert(oh->type == OSPF6_MESSAGE_TYPE_LSUPDATE); + + lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + num = ntohl(lsupdate->lsa_number); + zlog_debug(" Number of LSA: %ld", num); + + if ((IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV) + && action == OSPF6_ACTION_RECV) + || (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND) + && action == OSPF6_ACTION_SEND)) { + for (p = (char *)((caddr_t)lsupdate + + sizeof(struct ospf6_lsupdate)); + p < OSPF6_MESSAGE_END(oh) && + p + ospf6_lsa_size((struct ospf6_lsa_header *)p) <= + OSPF6_MESSAGE_END(oh); + p += ospf6_lsa_size((struct ospf6_lsa_header *)p)) { + ospf6_lsa_header_print_raw( + (struct ospf6_lsa_header *)p); + } + + assert(p == OSPF6_MESSAGE_END(oh)); + } +} + +void ospf6_lsack_print(struct ospf6_header *oh, int action) +{ + char *p; + + ospf6_header_print(oh); + assert(oh->type == OSPF6_MESSAGE_TYPE_LSACK); + + if ((IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV) + && action == OSPF6_ACTION_RECV) + || (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND) + && action == OSPF6_ACTION_SEND)) { + + for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); + p + sizeof(struct ospf6_lsa_header) + <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsa_header)) + ospf6_lsa_header_print_raw( + (struct ospf6_lsa_header *)p); + + assert(p == OSPF6_MESSAGE_END(oh)); + } +} + +static struct ospf6_packet *ospf6_packet_new(size_t size) +{ + struct ospf6_packet *new; + + new = XCALLOC(MTYPE_OSPF6_PACKET, sizeof(struct ospf6_packet)); + new->s = stream_new(size); + + return new; +} + +static struct ospf6_packet *ospf6_packet_dup(struct ospf6_packet *old) +{ + struct ospf6_packet *new; + + new = XCALLOC(MTYPE_OSPF6_PACKET, sizeof(struct ospf6_packet)); + new->s = stream_dup(old->s); + new->dst = old->dst; + new->length = old->length; + + return new; +} + +static void ospf6_packet_free(struct ospf6_packet *op) +{ + if (op->s) + stream_free(op->s); + + XFREE(MTYPE_OSPF6_PACKET, op); +} + +struct ospf6_fifo *ospf6_fifo_new(void) +{ + struct ospf6_fifo *new; + + new = XCALLOC(MTYPE_OSPF6_FIFO, sizeof(struct ospf6_fifo)); + return new; +} + +/* Add new packet to fifo. */ +static void ospf6_fifo_push(struct ospf6_fifo *fifo, struct ospf6_packet *op) +{ + if (fifo->tail) + fifo->tail->next = op; + else + fifo->head = op; + + fifo->tail = op; + + fifo->count++; +} + +/* Add new packet to head of fifo. */ +static void ospf6_fifo_push_head(struct ospf6_fifo *fifo, + struct ospf6_packet *op) +{ + op->next = fifo->head; + + if (fifo->tail == NULL) + fifo->tail = op; + + fifo->head = op; + + fifo->count++; +} + +/* Delete first packet from fifo. */ +static struct ospf6_packet *ospf6_fifo_pop(struct ospf6_fifo *fifo) +{ + struct ospf6_packet *op; + + op = fifo->head; + + if (op) { + fifo->head = op->next; + + if (fifo->head == NULL) + fifo->tail = NULL; + + fifo->count--; + } + + return op; +} + +/* Return first fifo entry. */ +static struct ospf6_packet *ospf6_fifo_head(struct ospf6_fifo *fifo) +{ + return fifo->head; +} + +/* Flush ospf packet fifo. */ +void ospf6_fifo_flush(struct ospf6_fifo *fifo) +{ + struct ospf6_packet *op; + struct ospf6_packet *next; + + for (op = fifo->head; op; op = next) { + next = op->next; + ospf6_packet_free(op); + } + fifo->head = fifo->tail = NULL; + fifo->count = 0; +} + +/* Free ospf packet fifo. */ +void ospf6_fifo_free(struct ospf6_fifo *fifo) +{ + ospf6_fifo_flush(fifo); + + XFREE(MTYPE_OSPF6_FIFO, fifo); +} + +static void ospf6_packet_add(struct ospf6_interface *oi, + struct ospf6_packet *op) +{ + /* Add packet to end of queue. */ + ospf6_fifo_push(oi->obuf, op); + + /* Debug of packet fifo*/ + /* ospf_fifo_debug (oi->obuf); */ +} + +static void ospf6_packet_add_top(struct ospf6_interface *oi, + struct ospf6_packet *op) +{ + /* Add packet to head of queue. */ + ospf6_fifo_push_head(oi->obuf, op); + + /* Debug of packet fifo*/ + /* ospf_fifo_debug (oi->obuf); */ +} + +static void ospf6_packet_delete(struct ospf6_interface *oi) +{ + struct ospf6_packet *op; + + op = ospf6_fifo_pop(oi->obuf); + + if (op) + ospf6_packet_free(op); +} + + +static void ospf6_hello_recv(struct in6_addr *src, struct in6_addr *dst, + struct ospf6_interface *oi, + struct ospf6_header *oh) +{ + struct ospf6_hello *hello; + struct ospf6_neighbor *on; + char *p; + int twoway = 0; + int neighborchange = 0; + int neighbor_ifindex_change = 0; + int backupseen = 0; + int64_t latency = 0; + struct timeval timestamp; + + monotime(×tamp); + hello = (struct ospf6_hello *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT + || oi->state == OSPF6_INTERFACE_POINTTOMULTIPOINT) + && oi->p2xp_only_cfg_neigh) { + /* NEVER, never, ever, do this on broadcast (or NBMA)! + * DR/BDR election requires everyone to talk to everyone else + * only for PtP/PtMP we can be selective in adjacencies! + */ + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + p2xp_cfg = ospf6_if_p2xp_find(oi, src); + if (!p2xp_cfg) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "ignoring PtP/PtMP hello from %pI6, neighbor not configured", + src); + return; + } + } + + /* HelloInterval check */ + if (ntohs(hello->hello_interval) != oi->hello_interval) { + zlog_warn( + "VRF %s: I/F %s HelloInterval mismatch from %pI6 (%pI4): (my %d, rcvd %d)", + oi->interface->vrf->name, oi->interface->name, src, + &oh->router_id, oi->hello_interval, + ntohs(hello->hello_interval)); + return; + } + + /* RouterDeadInterval check */ + if (ntohs(hello->dead_interval) != oi->dead_interval) { + zlog_warn( + "VRF %s: I/F %s DeadInterval mismatch from %pI6 (%pI4): (my %d, rcvd %d)", + oi->interface->vrf->name, oi->interface->name, src, + &oh->router_id, oi->dead_interval, + ntohs(hello->dead_interval)); + return; + } + + /* E-bit check */ + if (OSPF6_OPT_ISSET(hello->options, OSPF6_OPT_E) != + OSPF6_OPT_ISSET(oi->area->options, OSPF6_OPT_E)) { + zlog_warn("VRF %s: IF %s E-bit mismatch from %pI6 (%pI4)", + oi->interface->vrf->name, oi->interface->name, src, + &oh->router_id); + return; + } + + /* N-bit check */ + if (OSPF6_OPT_ISSET(hello->options, OSPF6_OPT_N) + != OSPF6_OPT_ISSET(oi->area->options, OSPF6_OPT_N)) { + zlog_warn("VRF %s: IF %s N-bit mismatch", + oi->interface->vrf->name, oi->interface->name); + return; + } + + if (((OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_AT) == + OSPF6_OPT_AT) && + (oi->at_data.flags == 0)) || + ((OSPF6_OPT_ISSET_EXT(hello->options, OSPF6_OPT_AT) != + OSPF6_OPT_AT) && + (oi->at_data.flags != 0))) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_warn( + "VRF %s: IF %s AT-bit mismatch in hello packet", + oi->interface->vrf->name, oi->interface->name); + oi->at_data.rx_drop++; + return; + } + + /* Find neighbor, create if not exist */ + on = ospf6_neighbor_lookup(oh->router_id, oi); + if (on == NULL) { + on = ospf6_neighbor_create(oh->router_id, oi); + on->prev_drouter = on->drouter = hello->drouter; + on->prev_bdrouter = on->bdrouter = hello->bdrouter; + on->priority = hello->priority; + } + + /* check latency against hello period */ + if (on->hello_in) + latency = monotime_since(&on->last_hello, NULL) + - ((int64_t)oi->hello_interval * 1000000); + /* log if latency exceeds the hello period */ + if (latency > ((int64_t)oi->hello_interval * 1000000)) + zlog_warn("%s RX %pI4 high latency %" PRId64 "us.", __func__, + &on->router_id, latency); + on->last_hello = timestamp; + on->hello_in++; + + /* Always override neighbor's source address */ + ospf6_neighbor_lladdr_set(on, src); + + /* Neighbor ifindex check */ + if (on->ifindex != (ifindex_t)ntohl(hello->interface_id)) { + on->ifindex = ntohl(hello->interface_id); + neighbor_ifindex_change++; + } + + /* TwoWay check */ + for (p = (char *)((caddr_t)hello + sizeof(struct ospf6_hello)); + p + sizeof(uint32_t) <= OSPF6_MESSAGE_END(oh); + p += sizeof(uint32_t)) { + uint32_t *router_id = (uint32_t *)p; + + if (*router_id == oi->area->ospf6->router_id) + twoway++; + } + + assert(p == OSPF6_MESSAGE_END(oh)); + + /* RouterPriority check */ + if (on->priority != hello->priority) { + on->priority = hello->priority; + neighborchange++; + } + + /* DR check */ + if (on->drouter != hello->drouter) { + on->prev_drouter = on->drouter; + on->drouter = hello->drouter; + if (on->prev_drouter == on->router_id + || on->drouter == on->router_id) + neighborchange++; + } + + /* BDR check */ + if (on->bdrouter != hello->bdrouter) { + on->prev_bdrouter = on->bdrouter; + on->bdrouter = hello->bdrouter; + if (on->prev_bdrouter == on->router_id + || on->bdrouter == on->router_id) + neighborchange++; + } + + /* BackupSeen check */ + if (oi->state == OSPF6_INTERFACE_WAITING) { + if (hello->bdrouter == on->router_id) + backupseen++; + else if (hello->drouter == on->router_id + && hello->bdrouter == htonl(0)) + backupseen++; + } + + oi->hello_in++; + + /* Execute neighbor events */ + event_execute(master, hello_received, on, 0, NULL); + if (twoway) + event_execute(master, twoway_received, on, 0, NULL); + else { + if (OSPF6_GR_IS_ACTIVE_HELPER(on)) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Received oneway hello from RESTARTER so ignore here.", + __PRETTY_FUNCTION__); + } else { + /* If the router is DR_OTHER, RESTARTER will not wait + * until it receives the hello from it if it receives + * from DR and BDR. + * So, helper might receives ONE_WAY hello from + * RESTARTER. So not allowing to change the state if it + * receives one_way hellow when it acts as HELPER for + * that specific neighbor. + */ + event_execute(master, oneway_received, on, 0, NULL); + } + } + + if (OSPF6_GR_IS_ACTIVE_HELPER(on)) { + /* As per the GR Conformance Test Case 7.2. Section 3 + * "Also, if X was the Designated Router on network segment S + * when the helping relationship began, Y maintains X as the + * Designated Router until the helping relationship is + * terminated." + * When it is a helper for this neighbor, It should not trigger + * the ISM Events. Also Intentionally not setting the priority + * and other fields so that when the neighbor exits the Grace + * period, it can handle if there is any change before GR and + * after GR. + */ + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "%s, Neighbor is under GR Restart, hence ignoring the ISM Events", + __PRETTY_FUNCTION__); + + return; + } + + /* + * RFC 3623 - Section 2: + * "If the restarting router determines that it was the Designated + * Router on a given segment prior to the restart, it elects + * itself as the Designated Router again. The restarting router + * knows that it was the Designated Router if, while the + * associated interface is in Waiting state, a Hello packet is + * received from a neighbor listing the router as the Designated + * Router". + */ + if (oi->area->ospf6->gr_info.restart_in_progress + && oi->state == OSPF6_INTERFACE_WAITING + && hello->drouter == oi->area->ospf6->router_id) + oi->drouter = hello->drouter; + + /* Schedule interface events */ + if (backupseen) + event_add_event(master, backup_seen, oi, 0, NULL); + if (neighborchange) + event_add_event(master, neighbor_change, oi, 0, NULL); + + if (neighbor_ifindex_change && on->state == OSPF6_NEIGHBOR_FULL) + OSPF6_ROUTER_LSA_SCHEDULE(oi->area); +} + +static void ospf6_dbdesc_recv_master(struct ospf6_header *oh, + struct ospf6_neighbor *on) +{ + struct ospf6_dbdesc *dbdesc; + char *p; + + dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + if (on->state < OSPF6_NEIGHBOR_INIT) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state less than Init, ignore"); + return; + } + + switch (on->state) { + case OSPF6_NEIGHBOR_TWOWAY: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state is 2-Way, ignore"); + return; + + case OSPF6_NEIGHBOR_INIT: + event_execute(master, twoway_received, on, 0, NULL); + if (on->state != OSPF6_NEIGHBOR_EXSTART) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Neighbor state is not ExStart, ignore"); + return; + } + /* else fall through to ExStart */ + fallthrough; + case OSPF6_NEIGHBOR_EXSTART: + /* if neighbor obeys us as our slave, schedule negotiation_done + and process LSA Headers. Otherwise, ignore this message */ + if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) + && !CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) + && ntohl(dbdesc->seqnum) == on->dbdesc_seqnum) { + /* execute NegotiationDone */ + event_execute(master, negotiation_done, on, 0, NULL); + + /* Record neighbor options */ + memcpy(on->options, dbdesc->options, + sizeof(on->options)); + } else { + zlog_warn("VRF %s: Nbr %s: Negotiation failed", + on->ospf6_if->interface->vrf->name, on->name); + return; + } + /* fall through to exchange */ + fallthrough; + case OSPF6_NEIGHBOR_EXCHANGE: + if (!memcmp(dbdesc, &on->dbdesc_last, + sizeof(struct ospf6_dbdesc))) { + /* Duplicated DatabaseDescription is dropped by master + */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Duplicated dbdesc discarded by Master, ignore"); + return; + } + + if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT)) { + zlog_warn( + "DbDesc recv: Master/Slave bit mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT)) { + zlog_warn("DbDesc recv: Initialize bit mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (memcmp(on->options, dbdesc->options, sizeof(on->options))) { + zlog_warn("DbDesc recv: Option field mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (ntohl(dbdesc->seqnum) != on->dbdesc_seqnum) { + zlog_warn( + "DbDesc recv: Sequence number mismatch Nbr %s (received %#lx, %#lx expected)", + on->name, (unsigned long)ntohl(dbdesc->seqnum), + (unsigned long)on->dbdesc_seqnum); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + break; + + case OSPF6_NEIGHBOR_LOADING: + case OSPF6_NEIGHBOR_FULL: + if (!memcmp(dbdesc, &on->dbdesc_last, + sizeof(struct ospf6_dbdesc))) { + /* Duplicated DatabaseDescription is dropped by master + */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Duplicated dbdesc discarded by Master, ignore"); + return; + } + + zlog_warn( + "DbDesc recv: Not duplicate dbdesc in state %s Nbr %s", + ospf6_neighbor_state_str[on->state], on->name); + event_add_event(master, seqnumber_mismatch, on, 0, NULL); + return; + + default: + assert(0); + break; + } + + /* Process LSA headers */ + for (p = (char *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); + p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsa_header)) { + struct ospf6_lsa *his, *mine; + struct ospf6_lsdb *lsdb = NULL; + + his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); + + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("%s", his->name); + + switch (OSPF6_LSA_SCOPE(his->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb = on->ospf6_if->lsdb; + break; + case OSPF6_SCOPE_AREA: + lsdb = on->ospf6_if->area->lsdb; + break; + case OSPF6_SCOPE_AS: + lsdb = on->ospf6_if->area->ospf6->lsdb; + break; + case OSPF6_SCOPE_RESERVED: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Ignoring LSA of reserved scope"); + ospf6_lsa_delete(his); + continue; + } + + if (ntohs(his->header->type) == OSPF6_LSTYPE_AS_EXTERNAL + && (IS_AREA_STUB(on->ospf6_if->area) + || IS_AREA_NSSA(on->ospf6_if->area))) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug( + "SeqNumMismatch (E-bit mismatch), discard"); + ospf6_lsa_delete(his); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + mine = ospf6_lsdb_lookup(his->header->type, his->header->id, + his->header->adv_router, lsdb); + if (mine == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Add request (No database copy)"); + ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); + } else if (ospf6_lsa_compare(his, mine) < 0) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Add request (Received MoreRecent)"); + ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); + } else { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Discard (Existing MoreRecent)"); + } + ospf6_lsa_delete(his); + } + + assert(p == OSPF6_MESSAGE_END(oh)); + + /* Increment sequence number */ + on->dbdesc_seqnum++; + + /* schedule send lsreq */ + if (on->request_list->count) + event_add_event(master, ospf6_lsreq_send, on, 0, + &on->thread_send_lsreq); + + EVENT_OFF(on->thread_send_dbdesc); + + /* More bit check */ + if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) + && !CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT)) + event_add_event(master, exchange_done, on, 0, + &on->thread_exchange_done); + else { + event_add_event(master, ospf6_dbdesc_send_newone, on, 0, + &on->thread_send_dbdesc); + } + + /* save last received dbdesc */ + memcpy(&on->dbdesc_last, dbdesc, sizeof(struct ospf6_dbdesc)); +} + +static void ospf6_dbdesc_recv_slave(struct ospf6_header *oh, + struct ospf6_neighbor *on) +{ + struct ospf6_dbdesc *dbdesc; + char *p; + + dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + if (on->state < OSPF6_NEIGHBOR_INIT) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state less than Init, ignore"); + return; + } + + switch (on->state) { + case OSPF6_NEIGHBOR_TWOWAY: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state is 2-Way, ignore"); + return; + + case OSPF6_NEIGHBOR_INIT: + event_execute(master, twoway_received, on, 0, NULL); + if (on->state != OSPF6_NEIGHBOR_EXSTART) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Neighbor state is not ExStart, ignore"); + return; + } + /* else fall through to ExStart */ + fallthrough; + case OSPF6_NEIGHBOR_EXSTART: + /* If the neighbor is Master, act as Slave. Schedule + negotiation_done + and process LSA Headers. Otherwise, ignore this message */ + if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT) + && CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MBIT) + && CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT) + && ntohs(oh->length) + == sizeof(struct ospf6_header) + + sizeof(struct ospf6_dbdesc)) { + /* set the master/slave bit to slave */ + UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); + + /* set the DD sequence number to one specified by master + */ + on->dbdesc_seqnum = ntohl(dbdesc->seqnum); + + /* schedule NegotiationDone */ + event_execute(master, negotiation_done, on, 0, NULL); + + /* Record neighbor options */ + memcpy(on->options, dbdesc->options, + sizeof(on->options)); + } else { + zlog_warn("VRF %s: Nbr %s Negotiation failed", + on->ospf6_if->interface->vrf->name, on->name); + return; + } + break; + + case OSPF6_NEIGHBOR_EXCHANGE: + if (!memcmp(dbdesc, &on->dbdesc_last, + sizeof(struct ospf6_dbdesc))) { + /* Duplicated DatabaseDescription causes slave to + * retransmit */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Duplicated dbdesc causes retransmit"); + EVENT_OFF(on->thread_send_dbdesc); + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); + return; + } + + if (!CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_MSBIT)) { + zlog_warn( + "DbDesc slave recv: Master/Slave bit mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (CHECK_FLAG(dbdesc->bits, OSPF6_DBDESC_IBIT)) { + zlog_warn( + "DbDesc slave recv: Initialize bit mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (memcmp(on->options, dbdesc->options, sizeof(on->options))) { + zlog_warn( + "DbDesc slave recv: Option field mismatch Nbr %s", + on->name); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + if (ntohl(dbdesc->seqnum) != on->dbdesc_seqnum + 1) { + zlog_warn( + "DbDesc slave recv: Sequence number mismatch Nbr %s (received %#lx, %#lx expected)", + on->name, (unsigned long)ntohl(dbdesc->seqnum), + (unsigned long)on->dbdesc_seqnum + 1); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + break; + + case OSPF6_NEIGHBOR_LOADING: + case OSPF6_NEIGHBOR_FULL: + if (!memcmp(dbdesc, &on->dbdesc_last, + sizeof(struct ospf6_dbdesc))) { + /* Duplicated DatabaseDescription causes slave to + * retransmit */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Duplicated dbdesc causes retransmit"); + EVENT_OFF(on->thread_send_dbdesc); + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); + return; + } + + zlog_warn( + "DbDesc slave recv: Not duplicate dbdesc in state %s Nbr %s", + ospf6_neighbor_state_str[on->state], on->name); + event_add_event(master, seqnumber_mismatch, on, 0, NULL); + return; + + default: + assert(0); + break; + } + + /* Process LSA headers */ + for (p = (char *)((caddr_t)dbdesc + sizeof(struct ospf6_dbdesc)); + p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsa_header)) { + struct ospf6_lsa *his, *mine; + struct ospf6_lsdb *lsdb = NULL; + + his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); + + switch (OSPF6_LSA_SCOPE(his->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb = on->ospf6_if->lsdb; + break; + case OSPF6_SCOPE_AREA: + lsdb = on->ospf6_if->area->lsdb; + break; + case OSPF6_SCOPE_AS: + lsdb = on->ospf6_if->area->ospf6->lsdb; + break; + case OSPF6_SCOPE_RESERVED: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Ignoring LSA of reserved scope"); + ospf6_lsa_delete(his); + continue; + } + + if (OSPF6_LSA_SCOPE(his->header->type) == OSPF6_SCOPE_AS + && (IS_AREA_STUB(on->ospf6_if->area) + || IS_AREA_NSSA(on->ospf6_if->area))) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("E-bit mismatch with LSA Headers"); + ospf6_lsa_delete(his); + event_add_event(master, seqnumber_mismatch, on, 0, + NULL); + return; + } + + mine = ospf6_lsdb_lookup(his->header->type, his->header->id, + his->header->adv_router, lsdb); + if (mine == NULL || ospf6_lsa_compare(his, mine) < 0) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Add request-list: %s", his->name); + ospf6_lsdb_add(ospf6_lsa_copy(his), on->request_list); + } + ospf6_lsa_delete(his); + } + + assert(p == OSPF6_MESSAGE_END(oh)); + + /* Set sequence number to Master's */ + on->dbdesc_seqnum = ntohl(dbdesc->seqnum); + + /* schedule send lsreq */ + if (on->request_list->count) + event_add_event(master, ospf6_lsreq_send, on, 0, + &on->thread_send_lsreq); + + EVENT_OFF(on->thread_send_dbdesc); + event_add_event(master, ospf6_dbdesc_send_newone, on, 0, + &on->thread_send_dbdesc); + + /* save last received dbdesc */ + memcpy(&on->dbdesc_last, dbdesc, sizeof(struct ospf6_dbdesc)); +} + +static void ospf6_dbdesc_recv(struct in6_addr *src, struct in6_addr *dst, + struct ospf6_interface *oi, + struct ospf6_header *oh) +{ + struct ospf6_neighbor *on; + struct ospf6_dbdesc *dbdesc; + + on = ospf6_neighbor_lookup(oh->router_id, oi); + if (on == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor not found, ignore"); + return; + } + + dbdesc = (struct ospf6_dbdesc *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + if (((OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_AT) == + OSPF6_OPT_AT) && + (oi->at_data.flags == 0)) || + ((OSPF6_OPT_ISSET_EXT(dbdesc->options, OSPF6_OPT_AT) != + OSPF6_OPT_AT) && + (oi->at_data.flags != 0))) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_warn( + "VRF %s: IF %s AT-bit mismatch in dbdesc packet", + oi->interface->vrf->name, oi->interface->name); + oi->at_data.rx_drop++; + return; + } + + /* Interface MTU check */ + if (!oi->mtu_ignore && ntohs(dbdesc->ifmtu) != oi->ifmtu) { + zlog_warn("VRF %s: I/F %s MTU mismatch (my %d rcvd %d)", + oi->interface->vrf->name, oi->interface->name, + oi->ifmtu, ntohs(dbdesc->ifmtu)); + return; + } + + if (dbdesc->reserved1 || dbdesc->reserved2) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug( + "Non-0 reserved field in %s's DbDesc, correct", + on->name); + dbdesc->reserved1 = 0; + dbdesc->reserved2 = 0; + } + + oi->db_desc_in++; + + if (ntohl(oh->router_id) < ntohl(oi->area->ospf6->router_id)) + ospf6_dbdesc_recv_master(oh, on); + else if (ntohl(oi->area->ospf6->router_id) < ntohl(oh->router_id)) + ospf6_dbdesc_recv_slave(oh, on); + else { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Can't decide which is master, ignore"); + } +} + +static void ospf6_lsreq_recv(struct in6_addr *src, struct in6_addr *dst, + struct ospf6_interface *oi, + struct ospf6_header *oh) +{ + struct ospf6_neighbor *on; + char *p; + struct ospf6_lsreq_entry *e; + struct ospf6_lsdb *lsdb = NULL; + struct ospf6_lsa *lsa; + + on = ospf6_neighbor_lookup(oh->router_id, oi); + if (on == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor not found, ignore"); + return; + } + + if (on->state != OSPF6_NEIGHBOR_EXCHANGE + && on->state != OSPF6_NEIGHBOR_LOADING + && on->state != OSPF6_NEIGHBOR_FULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state less than Exchange, ignore"); + return; + } + + oi->ls_req_in++; + + /* Process each request */ + for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); + p + sizeof(struct ospf6_lsreq_entry) <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsreq_entry)) { + e = (struct ospf6_lsreq_entry *)p; + + switch (OSPF6_LSA_SCOPE(e->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb = on->ospf6_if->lsdb; + break; + case OSPF6_SCOPE_AREA: + lsdb = on->ospf6_if->area->lsdb; + break; + case OSPF6_SCOPE_AS: + lsdb = on->ospf6_if->area->ospf6->lsdb; + break; + default: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Ignoring LSA of reserved scope"); + continue; + } + + /* Find database copy */ + lsa = ospf6_lsdb_lookup(e->type, e->id, e->adv_router, lsdb); + if (lsa == NULL) { + zlog_warn( + "Can't find requested lsa [%s Id:%pI4 Adv:%pI4] send badLSReq", + ospf6_lstype_name(e->type), &e->id, + &e->adv_router); + event_add_event(master, bad_lsreq, on, 0, NULL); + return; + } + + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->lsupdate_list); + } + + assert(p == OSPF6_MESSAGE_END(oh)); + + /* schedule send lsupdate */ + EVENT_OFF(on->thread_send_lsupdate); + event_add_event(master, ospf6_lsupdate_send_neighbor, on, 0, + &on->thread_send_lsupdate); +} + +/* Verify, that the specified memory area contains exactly N valid IPv6 + prefixes as specified by RFC5340, A.4.1. */ +static unsigned ospf6_prefixes_examin( + struct ospf6_prefix *current, /* start of buffer */ + unsigned length, + const uint32_t req_num_pfxs /* always compared with the actual number + of prefixes */ +) +{ + uint8_t requested_pfx_bytes; + uint32_t real_num_pfxs = 0; + + while (length) { + if (length < OSPF6_PREFIX_MIN_SIZE) { + zlog_warn("%s: undersized IPv6 prefix header", + __func__); + return MSG_NG; + } + /* safe to look deeper */ + if (current->prefix_length > IPV6_MAX_BITLEN) { + zlog_warn("%s: invalid PrefixLength (%u bits)", + __func__, current->prefix_length); + return MSG_NG; + } + /* covers both fixed- and variable-sized fields */ + requested_pfx_bytes = + OSPF6_PREFIX_MIN_SIZE + + OSPF6_PREFIX_SPACE(current->prefix_length); + if (requested_pfx_bytes > length) { + zlog_warn("%s: undersized IPv6 prefix", __func__); + return MSG_NG; + } + /* next prefix */ + length -= requested_pfx_bytes; + current = (struct ospf6_prefix *)((caddr_t)current + + requested_pfx_bytes); + real_num_pfxs++; + } + if (real_num_pfxs != req_num_pfxs) { + zlog_warn( + "%s: IPv6 prefix number mismatch (%u required, %u real)", + __func__, req_num_pfxs, real_num_pfxs); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify an LSA to have a valid length and dispatch further (where + appropriate) to check if the contents, including nested IPv6 prefixes, + is properly sized/aligned within the LSA. Note that this function gets + LSA type in network byte order, uses in host byte order and passes to + ospf6_lstype_name() in network byte order again. */ +static unsigned ospf6_lsa_examin(struct ospf6_lsa_header *lsah, + const uint16_t lsalen, + const uint8_t headeronly) +{ + struct ospf6_intra_prefix_lsa *intra_prefix_lsa; + struct ospf6_as_external_lsa *as_external_lsa; + struct ospf6_link_lsa *link_lsa; + unsigned exp_length; + uint8_t ltindex; + uint16_t lsatype; + + /* In case an additional minimum length constraint is defined for + current + LSA type, make sure that this constraint is met. */ + lsatype = ntohs(lsah->type); + ltindex = lsatype & OSPF6_LSTYPE_FCODE_MASK; + if (ltindex < OSPF6_LSTYPE_SIZE && ospf6_lsa_minlen[ltindex] + && lsalen < ospf6_lsa_minlen[ltindex] + OSPF6_LSA_HEADER_SIZE) { + zlog_warn("%s: undersized (%u B) LSA", __func__, lsalen); + return MSG_NG; + } + switch (lsatype) { + case OSPF6_LSTYPE_ROUTER: + /* RFC5340 A.4.3, LSA header + OSPF6_ROUTER_LSA_MIN_SIZE bytes + followed + by N>=0 interface descriptions. */ + if ((lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_ROUTER_LSA_MIN_SIZE) + % OSPF6_ROUTER_LSDESC_FIX_SIZE) { + zlog_warn( + "%s: Router LSA interface description alignment error", + __func__); + return MSG_NG; + } + break; + case OSPF6_LSTYPE_NETWORK: + /* RFC5340 A.4.4, LSA header + OSPF6_NETWORK_LSA_MIN_SIZE bytes + followed by N>=0 attached router descriptions. */ + if ((lsalen - OSPF6_LSA_HEADER_SIZE + - OSPF6_NETWORK_LSA_MIN_SIZE) + % OSPF6_NETWORK_LSDESC_FIX_SIZE) { + zlog_warn( + "%s: Network LSA router description alignment error", + __func__); + return MSG_NG; + } + break; + case OSPF6_LSTYPE_INTER_PREFIX: + /* RFC5340 A.4.5, LSA header + OSPF6_INTER_PREFIX_LSA_MIN_SIZE + bytes + followed by 3-4 fields of a single IPv6 prefix. */ + if (headeronly) + break; + return ospf6_prefixes_examin( + (struct ospf6_prefix + *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE + + OSPF6_INTER_PREFIX_LSA_MIN_SIZE), + lsalen - OSPF6_LSA_HEADER_SIZE + - OSPF6_INTER_PREFIX_LSA_MIN_SIZE, + 1); + case OSPF6_LSTYPE_INTER_ROUTER: + /* RFC5340 A.4.6, fixed-size LSA. */ + if (lsalen + > OSPF6_LSA_HEADER_SIZE + OSPF6_INTER_ROUTER_LSA_FIX_SIZE) { + zlog_warn("%s: Inter Router LSA oversized (%u B) LSA", + __func__, lsalen); + return MSG_NG; + } + break; + case OSPF6_LSTYPE_AS_EXTERNAL: /* RFC5340 A.4.7, same as A.4.8. */ + case OSPF6_LSTYPE_TYPE_7: + /* RFC5340 A.4.8, LSA header + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE + bytes + followed by 3-4 fields of IPv6 prefix and 3 conditional LSA + fields: + 16 bytes of forwarding address, 4 bytes of external route + tag, + 4 bytes of referenced link state ID. */ + if (headeronly) + break; + as_external_lsa = + (struct ospf6_as_external_lsa + *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); + exp_length = + OSPF6_LSA_HEADER_SIZE + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE; + /* To find out if the last optional field (Referenced Link State + ID) is + assumed in this LSA, we need to access fixed fields of the + IPv6 + prefix before ospf6_prefix_examin() confirms its sizing. */ + if (exp_length + OSPF6_PREFIX_MIN_SIZE > lsalen) { + zlog_warn( + "%s: AS External undersized (%u B) LSA header", + __func__, lsalen); + return MSG_NG; + } + /* forwarding address */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F)) + exp_length += 16; + /* external route tag */ + if (CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) + exp_length += 4; + /* referenced link state ID */ + if (as_external_lsa->prefix.u._prefix_referenced_lstype) + exp_length += 4; + /* All the fixed-size fields (mandatory and optional) must fit. + I.e., + this check does not include any IPv6 prefix fields. */ + if (exp_length > lsalen) { + zlog_warn( + "%s: AS External undersized (%u B) LSA header", + __func__, lsalen); + return MSG_NG; + } + /* The last call completely covers the remainder (IPv6 prefix). + */ + return ospf6_prefixes_examin( + (struct ospf6_prefix + *)((caddr_t)as_external_lsa + + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE), + lsalen - exp_length, 1); + case OSPF6_LSTYPE_LINK: + /* RFC5340 A.4.9, LSA header + OSPF6_LINK_LSA_MIN_SIZE bytes + followed + by N>=0 IPv6 prefix blocks (with N declared beforehand). */ + if (headeronly) + break; + link_lsa = (struct ospf6_link_lsa *)((caddr_t)lsah + + OSPF6_LSA_HEADER_SIZE); + return ospf6_prefixes_examin( + (struct ospf6_prefix *)((caddr_t)link_lsa + + OSPF6_LINK_LSA_MIN_SIZE), + lsalen - OSPF6_LSA_HEADER_SIZE + - OSPF6_LINK_LSA_MIN_SIZE, + ntohl(link_lsa->prefix_num) /* 32 bits */ + ); + case OSPF6_LSTYPE_INTRA_PREFIX: + /* RFC5340 A.4.10, LSA header + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE + bytes + followed by N>=0 IPv6 prefixes (with N declared beforehand). + */ + if (headeronly) + break; + intra_prefix_lsa = + (struct ospf6_intra_prefix_lsa + *)((caddr_t)lsah + OSPF6_LSA_HEADER_SIZE); + return ospf6_prefixes_examin( + (struct ospf6_prefix + *)((caddr_t)intra_prefix_lsa + + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE), + lsalen - OSPF6_LSA_HEADER_SIZE + - OSPF6_INTRA_PREFIX_LSA_MIN_SIZE, + ntohs(intra_prefix_lsa->prefix_num) /* 16 bits */ + ); + case OSPF6_LSTYPE_GRACE_LSA: + if (lsalen < OSPF6_LSA_HEADER_SIZE + GRACE_PERIOD_TLV_SIZE + + GRACE_RESTART_REASON_TLV_SIZE) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s: Undersized GraceLSA.", + __func__); + return MSG_NG; + } + } + /* No additional validation is possible for unknown LSA types, which are + themselves valid in OPSFv3, hence the default decision is to accept. + */ + return MSG_OK; +} + +/* Verify if the provided input buffer is a valid sequence of LSAs. This + includes verification of LSA blocks length/alignment and dispatching + of deeper-level checks. */ +static unsigned +ospf6_lsaseq_examin(struct ospf6_lsa_header *lsah, /* start of buffered data */ + size_t length, const uint8_t headeronly, + /* When declared_num_lsas is not 0, compare it to the real + number of LSAs + and treat the difference as an error. */ + const uint32_t declared_num_lsas) +{ + uint32_t counted_lsas = 0; + + while (length) { + uint16_t lsalen; + if (length < OSPF6_LSA_HEADER_SIZE) { + zlog_warn( + "%s: undersized (%zu B) trailing (#%u) LSA header", + __func__, length, counted_lsas); + return MSG_NG; + } + /* save on ntohs() calls here and in the LSA validator */ + lsalen = ospf6_lsa_size(lsah); + if (lsalen < OSPF6_LSA_HEADER_SIZE) { + zlog_warn( + "%s: malformed LSA header #%u, declared length is %u B", + __func__, counted_lsas, lsalen); + return MSG_NG; + } + if (headeronly) { + /* less checks here and in ospf6_lsa_examin() */ + if (MSG_OK != ospf6_lsa_examin(lsah, lsalen, 1)) { + zlog_warn( + "%s: anomaly in header-only %s LSA #%u", + __func__, ospf6_lstype_name(lsah->type), + counted_lsas); + return MSG_NG; + } + lsah = (struct ospf6_lsa_header + *)((caddr_t)lsah + + OSPF6_LSA_HEADER_SIZE); + length -= OSPF6_LSA_HEADER_SIZE; + } else { + /* make sure the input buffer is deep enough before + * further checks */ + if (lsalen > length) { + zlog_warn( + "%s: anomaly in %s LSA #%u: declared length is %u B, buffered length is %zu B", + __func__, ospf6_lstype_name(lsah->type), + counted_lsas, lsalen, length); + return MSG_NG; + } + if (MSG_OK != ospf6_lsa_examin(lsah, lsalen, 0)) { + zlog_warn("%s: anomaly in %s LSA #%u", __func__, + ospf6_lstype_name(lsah->type), + counted_lsas); + return MSG_NG; + } + lsah = (struct ospf6_lsa_header *)((caddr_t)lsah + + lsalen); + length -= lsalen; + } + counted_lsas++; + } + + if (declared_num_lsas && counted_lsas != declared_num_lsas) { + zlog_warn("%s: #LSAs declared (%u) does not match actual (%u)", + __func__, declared_num_lsas, counted_lsas); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify a complete OSPF packet for proper sizing/alignment. */ +static unsigned ospf6_packet_examin(struct ospf6_header *oh, + const unsigned bytesonwire) +{ + struct ospf6_lsupdate *lsupd; + unsigned test; + + /* length, 1st approximation */ + if (bytesonwire < OSPF6_HEADER_SIZE) { + zlog_warn("%s: undersized (%u B) packet", __func__, + bytesonwire); + return MSG_NG; + } + + /* Now it is safe to access header fields. */ + if (bytesonwire != ntohs(oh->length)) { + zlog_warn("%s: %s packet length error (%u real, %u declared)", + __func__, ospf6_message_type(oh->type), bytesonwire, + ntohs(oh->length)); + return MSG_NG; + } + + /* version check */ + if (oh->version != OSPFV3_VERSION) { + zlog_warn("%s: invalid (%u) protocol version", __func__, + oh->version); + return MSG_NG; + } + /* length, 2nd approximation */ + if (oh->type < OSPF6_MESSAGE_TYPE_ALL && ospf6_packet_minlen[oh->type] + && bytesonwire + < OSPF6_HEADER_SIZE + ospf6_packet_minlen[oh->type]) { + zlog_warn("%s: undersized (%u B) %s packet", __func__, + bytesonwire, ospf6_message_type(oh->type)); + return MSG_NG; + } + /* type-specific deeper validation */ + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + /* RFC5340 A.3.2, packet header + OSPF6_HELLO_MIN_SIZE bytes + followed + by N>=0 router-IDs. */ + if (0 + == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_HELLO_MIN_SIZE) + % 4) + return MSG_OK; + zlog_warn("%s: alignment error in %s packet", __func__, + ospf6_message_type(oh->type)); + return MSG_NG; + case OSPF6_MESSAGE_TYPE_DBDESC: + /* RFC5340 A.3.3, packet header + OSPF6_DB_DESC_MIN_SIZE bytes + followed + by N>=0 header-only LSAs. */ + test = ospf6_lsaseq_examin( + (struct ospf6_lsa_header *)((caddr_t)oh + + OSPF6_HEADER_SIZE + + OSPF6_DB_DESC_MIN_SIZE), + bytesonwire - OSPF6_HEADER_SIZE + - OSPF6_DB_DESC_MIN_SIZE, + 1, 0); + break; + case OSPF6_MESSAGE_TYPE_LSREQ: + /* RFC5340 A.3.4, packet header + N>=0 LS description blocks. */ + if (0 + == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_REQ_MIN_SIZE) + % OSPF6_LSREQ_LSDESC_FIX_SIZE) + return MSG_OK; + zlog_warn("%s: alignment error in %s packet", __func__, + ospf6_message_type(oh->type)); + return MSG_NG; + case OSPF6_MESSAGE_TYPE_LSUPDATE: + /* RFC5340 A.3.5, packet header + OSPF6_LS_UPD_MIN_SIZE bytes + followed + by N>=0 full LSAs (with N declared beforehand). */ + lsupd = (struct ospf6_lsupdate *)((caddr_t)oh + + OSPF6_HEADER_SIZE); + test = ospf6_lsaseq_examin( + (struct ospf6_lsa_header *)((caddr_t)lsupd + + OSPF6_LS_UPD_MIN_SIZE), + bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_UPD_MIN_SIZE, + 0, ntohl(lsupd->lsa_number) /* 32 bits */ + ); + break; + case OSPF6_MESSAGE_TYPE_LSACK: + /* RFC5340 A.3.6, packet header + N>=0 header-only LSAs. */ + test = ospf6_lsaseq_examin( + (struct ospf6_lsa_header *)((caddr_t)oh + + OSPF6_HEADER_SIZE + + OSPF6_LS_ACK_MIN_SIZE), + bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_ACK_MIN_SIZE, + 1, 0); + break; + default: + zlog_warn("%s: invalid (%u) message type", __func__, oh->type); + return MSG_NG; + } + if (test != MSG_OK) + zlog_warn("%s: anomaly in %s packet", __func__, + ospf6_message_type(oh->type)); + return test; +} + +/* Verify particular fields of otherwise correct received OSPF packet to + meet the requirements of RFC. */ +static int ospf6_rxpacket_examin(struct ospf6_interface *oi, + struct ospf6_header *oh, + const unsigned bytesonwire) +{ + struct ospf6_neighbor *on; + + if (MSG_OK != ospf6_packet_examin(oh, bytesonwire)) + return MSG_NG; + + on = ospf6_neighbor_lookup(oh->router_id, oi); + + /* Area-ID check */ + if (oh->area_id != oi->area->area_id) { + if (oh->area_id == OSPF_AREA_BACKBONE) + zlog_warn( + "VRF %s: I/F %s (%s, Router-ID: %pI4) Message may be via Virtual Link: not supported", + oi->interface->vrf->name, oi->interface->name, + on ? on->name : "null", &oh->router_id); + else + zlog_warn( + "VRF %s: I/F %s (%s, Router-ID: %pI4) Area-ID mismatch (my %pI4, rcvd %pI4)", + oi->interface->vrf->name, oi->interface->name, + on ? on->name : "null", &oh->router_id, + &oi->area->area_id, &oh->area_id); + return MSG_NG; + } + + /* Instance-ID check */ + if (oh->instance_id != oi->instance_id) { + zlog_warn( + "VRF %s: I/F %s (%s, Router-ID: %pI4) Instance-ID mismatch (my %u, rcvd %u)", + oi->interface->vrf->name, oi->interface->name, + on ? on->name : "null", &oh->router_id, oi->instance_id, + oh->instance_id); + return MSG_NG; + } + + /* Router-ID check */ + if (oh->router_id == oi->area->ospf6->router_id) { + zlog_warn("VRF %s: I/F %s Duplicate Router-ID (%pI4)", + oi->interface->vrf->name, oi->interface->name, + &oh->router_id); + return MSG_NG; + } + return MSG_OK; +} + +static void ospf6_lsupdate_recv(struct in6_addr *src, struct in6_addr *dst, + struct ospf6_interface *oi, + struct ospf6_header *oh) +{ + struct ospf6_neighbor *on; + struct ospf6_lsupdate *lsupdate; + char *p; + + on = ospf6_neighbor_lookup(oh->router_id, oi); + if (on == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor not found, ignore"); + return; + } + + if (on->state != OSPF6_NEIGHBOR_EXCHANGE + && on->state != OSPF6_NEIGHBOR_LOADING + && on->state != OSPF6_NEIGHBOR_FULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state less than Exchange, ignore"); + return; + } + + lsupdate = (struct ospf6_lsupdate *)((caddr_t)oh + + sizeof(struct ospf6_header)); + + oi->ls_upd_in++; + + /* Process LSAs */ + for (p = (char *)((caddr_t)lsupdate + sizeof(struct ospf6_lsupdate)); + p < OSPF6_MESSAGE_END(oh) && + p + ospf6_lsa_size((struct ospf6_lsa_header *)p) <= + OSPF6_MESSAGE_END(oh); + p += ospf6_lsa_size((struct ospf6_lsa_header *)p)) { + ospf6_receive_lsa(on, (struct ospf6_lsa_header *)p); + } + + assert(p == OSPF6_MESSAGE_END(oh)); +} + +static void ospf6_lsack_recv(struct in6_addr *src, struct in6_addr *dst, + struct ospf6_interface *oi, + struct ospf6_header *oh) +{ + struct ospf6_neighbor *on; + char *p; + struct ospf6_lsa *his, *mine; + struct ospf6_lsdb *lsdb = NULL; + + assert(oh->type == OSPF6_MESSAGE_TYPE_LSACK); + + on = ospf6_neighbor_lookup(oh->router_id, oi); + if (on == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor not found, ignore"); + return; + } + + if (on->state != OSPF6_NEIGHBOR_EXCHANGE + && on->state != OSPF6_NEIGHBOR_LOADING + && on->state != OSPF6_NEIGHBOR_FULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) + zlog_debug("Neighbor state less than Exchange, ignore"); + return; + } + + oi->ls_ack_in++; + + for (p = (char *)((caddr_t)oh + sizeof(struct ospf6_header)); + p + sizeof(struct ospf6_lsa_header) <= OSPF6_MESSAGE_END(oh); + p += sizeof(struct ospf6_lsa_header)) { + his = ospf6_lsa_create_headeronly((struct ospf6_lsa_header *)p); + + switch (OSPF6_LSA_SCOPE(his->header->type)) { + case OSPF6_SCOPE_LINKLOCAL: + lsdb = on->ospf6_if->lsdb; + break; + case OSPF6_SCOPE_AREA: + lsdb = on->ospf6_if->area->lsdb; + break; + case OSPF6_SCOPE_AS: + lsdb = on->ospf6_if->area->ospf6->lsdb; + break; + case OSPF6_SCOPE_RESERVED: + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Ignoring LSA of reserved scope"); + ospf6_lsa_delete(his); + continue; + } + + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("%s acknowledged by %s", his->name, + on->name); + + /* Find database copy */ + mine = ospf6_lsdb_lookup(his->header->type, his->header->id, + his->header->adv_router, lsdb); + if (mine == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("No database copy"); + ospf6_lsa_delete(his); + continue; + } + + /* Check if the LSA is on his retrans-list */ + mine = ospf6_lsdb_lookup(his->header->type, his->header->id, + his->header->adv_router, + on->retrans_list); + if (mine == NULL) { + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Not on %s's retrans-list", + on->name); + ospf6_lsa_delete(his); + continue; + } + + if (ospf6_lsa_compare(his, mine) != 0) { + /* Log this questionable acknowledgement, + and examine the next one. */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug("Questionable acknowledgement"); + ospf6_lsa_delete(his); + continue; + } + + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV)) + zlog_debug( + "Acknowledged, remove from %s's retrans-list", + on->name); + + ospf6_decrement_retrans_count(mine); + if (OSPF6_LSA_IS_MAXAGE(mine)) + ospf6_maxage_remove(on->ospf6_if->area->ospf6); + ospf6_lsdb_remove(mine, on->retrans_list); + ospf6_lsa_delete(his); + } + + assert(p == OSPF6_MESSAGE_END(oh)); +} + +static uint8_t *recvbuf = NULL; +static uint8_t *sendbuf = NULL; +static unsigned int iobuflen = 0; + +int ospf6_iobuf_size(unsigned int size) +{ + /* NB: there was previously code here that tried to dynamically size + * the buffer for whatever we see in MTU on interfaces. Which is + * _unconditionally wrong_ - we can always receive fragmented IPv6 + * up to the regular 64k length limit. (No jumbograms, thankfully.) + */ + + if (!iobuflen) { + /* the + 128 is to have some runway at the end */ + size_t alloc_size = 65536 + 128; + + assert(!recvbuf && !sendbuf); + + recvbuf = XMALLOC(MTYPE_OSPF6_MESSAGE, alloc_size); + sendbuf = XMALLOC(MTYPE_OSPF6_MESSAGE, alloc_size); + iobuflen = alloc_size; + } + + return iobuflen; +} + +void ospf6_message_terminate(void) +{ + XFREE(MTYPE_OSPF6_MESSAGE, recvbuf); + XFREE(MTYPE_OSPF6_MESSAGE, sendbuf); + + iobuflen = 0; +} + +enum ospf6_read_return_enum { + OSPF6_READ_ERROR, + OSPF6_READ_CONTINUE, +}; + +static int ospf6_read_helper(int sockfd, struct ospf6 *ospf6) +{ + int len; + struct in6_addr src, dst; + ifindex_t ifindex; + struct iovec iovector[2]; + struct ospf6_interface *oi; + struct ospf6_header *oh; + enum ospf6_auth_err ret = OSPF6_AUTH_PROCESS_NORMAL; + uint32_t at_len = 0; + uint32_t lls_len = 0; + + /* initialize */ + memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); + ifindex = 0; + iovector[0].iov_base = recvbuf; + iovector[0].iov_len = iobuflen; + iovector[1].iov_base = NULL; + iovector[1].iov_len = 0; + + /* receive message */ + len = ospf6_recvmsg(&src, &dst, &ifindex, iovector, sockfd); + if (len < 0) + return OSPF6_READ_ERROR; + + if ((uint)len > iobuflen) { + flog_err(EC_LIB_DEVELOPMENT, "Excess message read"); + return OSPF6_READ_ERROR; + } + + /* ensure some zeroes past the end, just as a security precaution */ + memset(recvbuf + len, 0, MIN(128, iobuflen - len)); + + oi = ospf6_interface_lookup_by_ifindex(ifindex, ospf6->vrf_id); + if (oi == NULL || oi->area == NULL + || CHECK_FLAG(oi->flag, OSPF6_INTERFACE_DISABLE)) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, + RECV_HDR)) + zlog_debug("Message received on disabled interface"); + return OSPF6_READ_CONTINUE; + } + if (CHECK_FLAG(oi->flag, OSPF6_INTERFACE_PASSIVE)) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, + RECV_HDR)) + zlog_debug("%s: Ignore message on passive interface %s", + __func__, oi->interface->name); + return OSPF6_READ_CONTINUE; + } + + /* + * Drop packet destined to another VRF. + * This happens when raw_l3mdev_accept is set to 1. + */ + if (ospf6->vrf_id != oi->interface->vrf->vrf_id) + return OSPF6_READ_CONTINUE; + + oh = (struct ospf6_header *)recvbuf; + ret = ospf6_auth_validate_pkt(oi, (uint32_t *)&len, oh, &at_len, + &lls_len); + if (ret == OSPF6_AUTH_VALIDATE_SUCCESS) { + ret = ospf6_auth_check_digest(oh, oi, &src, lls_len); + if (ret == OSPF6_AUTH_VALIDATE_FAILURE) { + if (IS_OSPF6_DEBUG_AUTH_RX) + zlog_err( + "RECV[%s]: OSPF packet auth digest miss-match on %s", + oi->interface->name, + ospf6_message_type(oh->type)); + oi->at_data.rx_drop++; + return OSPF6_READ_CONTINUE; + } + } else if (ret == OSPF6_AUTH_VALIDATE_FAILURE) { + oi->at_data.rx_drop++; + return OSPF6_READ_CONTINUE; + } + + if (ospf6_rxpacket_examin(oi, oh, len) != MSG_OK) + return OSPF6_READ_CONTINUE; + + /* Being here means, that no sizing/alignment issues were detected in + the input packet. This renders the additional checks performed below + and also in the type-specific dispatching functions a dead code, + which can be dismissed in a cleanup-focused review round later. */ + + /* Log */ + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, RECV_HDR)) { + zlog_debug("%s received on %s", ospf6_message_type(oh->type), + oi->interface->name); + zlog_debug(" src: %pI6", &src); + zlog_debug(" dst: %pI6", &dst); + + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + ospf6_hello_print(oh, OSPF6_ACTION_RECV); + break; + case OSPF6_MESSAGE_TYPE_DBDESC: + ospf6_dbdesc_print(oh, OSPF6_ACTION_RECV); + break; + case OSPF6_MESSAGE_TYPE_LSREQ: + ospf6_lsreq_print(oh, OSPF6_ACTION_RECV); + break; + case OSPF6_MESSAGE_TYPE_LSUPDATE: + ospf6_lsupdate_print(oh, OSPF6_ACTION_RECV); + break; + case OSPF6_MESSAGE_TYPE_LSACK: + ospf6_lsack_print(oh, OSPF6_ACTION_RECV); + break; + default: + assert(0); + } + + if ((at_len != 0) && IS_OSPF6_DEBUG_AUTH_RX) + ospf6_auth_hdr_dump_recv(oh, (len + at_len + lls_len), + lls_len); + } + + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + ospf6_hello_recv(&src, &dst, oi, oh); + break; + + case OSPF6_MESSAGE_TYPE_DBDESC: + ospf6_dbdesc_recv(&src, &dst, oi, oh); + break; + + case OSPF6_MESSAGE_TYPE_LSREQ: + ospf6_lsreq_recv(&src, &dst, oi, oh); + break; + + case OSPF6_MESSAGE_TYPE_LSUPDATE: + ospf6_lsupdate_recv(&src, &dst, oi, oh); + break; + + case OSPF6_MESSAGE_TYPE_LSACK: + ospf6_lsack_recv(&src, &dst, oi, oh); + break; + + default: + assert(0); + } + + return OSPF6_READ_CONTINUE; +} + +void ospf6_receive(struct event *thread) +{ + int sockfd; + struct ospf6 *ospf6; + int count = 0; + + /* add next read thread */ + ospf6 = EVENT_ARG(thread); + sockfd = EVENT_FD(thread); + + event_add_read(master, ospf6_receive, ospf6, ospf6->fd, + &ospf6->t_ospf6_receive); + + while (count < ospf6->write_oi_count) { + count++; + switch (ospf6_read_helper(sockfd, ospf6)) { + case OSPF6_READ_ERROR: + return; + case OSPF6_READ_CONTINUE: + break; + } + } +} + +static void ospf6_fill_hdr_checksum(struct ospf6_interface *oi, + struct ospf6_packet *op) +{ + struct ipv6_ph ph = {}; + struct ospf6_header *oh; + void *offset = NULL; + + if (oi->at_data.flags != 0) + return; + + memcpy(&ph.src, oi->linklocal_addr, sizeof(struct in6_addr)); + memcpy(&ph.dst, &op->dst, sizeof(struct in6_addr)); + ph.ulpl = htonl(op->length); + ph.next_hdr = IPPROTO_OSPFIGP; + + /* Suppress static analysis warnings about accessing icmp6 oob */ + oh = (struct ospf6_header *)STREAM_DATA(op->s); + offset = oh; + oh->checksum = in_cksum_with_ph6(&ph, offset, op->length); +} + +static void ospf6_make_header(uint8_t type, struct ospf6_interface *oi, + struct stream *s) +{ + struct ospf6_header *oh; + + oh = (struct ospf6_header *)STREAM_DATA(s); + + oh->version = (uint8_t)OSPFV3_VERSION; + oh->type = type; + oh->length = 0; + + oh->router_id = oi->area->ospf6->router_id; + oh->area_id = oi->area->area_id; + oh->checksum = 0; + oh->instance_id = oi->instance_id; + oh->reserved = 0; + + stream_forward_endp(s, OSPF6_HEADER_SIZE); +} + +static void ospf6_fill_header(struct ospf6_interface *oi, struct stream *s, + uint16_t length) +{ + struct ospf6_header *oh; + + oh = (struct ospf6_header *)STREAM_DATA(s); + + oh->length = htons(length); +} + +static void ospf6_fill_lsupdate_header(struct stream *s, uint32_t lsa_num) +{ + struct ospf6_header *oh; + struct ospf6_lsupdate *lsu; + + oh = (struct ospf6_header *)STREAM_DATA(s); + + lsu = (struct ospf6_lsupdate *)((caddr_t)oh + + sizeof(struct ospf6_header)); + lsu->lsa_number = htonl(lsa_num); +} + +static void ospf6_auth_trailer_copy_keychain_key(struct ospf6_interface *oi) +{ + char *keychain_name = NULL; + struct keychain *keychain = NULL; + struct key *key = NULL; + + keychain_name = oi->at_data.keychain; + keychain = keychain_lookup(keychain_name); + if (keychain) { + key = key_lookup_for_send(keychain); + if (key && key->string && + key->hash_algo != KEYCHAIN_ALGO_NULL) { + /* storing the values so that further + * lookup can be avoided. after + * processing the digest need to reset + * these values + */ + oi->at_data.hash_algo = key->hash_algo; + if (oi->at_data.auth_key) + XFREE(MTYPE_OSPF6_AUTH_MANUAL_KEY, + oi->at_data.auth_key); + oi->at_data.auth_key = XSTRDUP( + MTYPE_OSPF6_AUTH_MANUAL_KEY, key->string); + oi->at_data.key_id = key->index; + SET_FLAG(oi->at_data.flags, + OSPF6_AUTH_TRAILER_KEYCHAIN_VALID); + } + } +} + +static uint16_t ospf6_packet_max(struct ospf6_interface *oi) +{ + uint16_t at_len = 0; + + assert(oi->ifmtu > sizeof(struct ip6_hdr)); + + if (oi->at_data.flags != 0) { + if (CHECK_FLAG(oi->at_data.flags, OSPF6_AUTH_TRAILER_KEYCHAIN)) + ospf6_auth_trailer_copy_keychain_key(oi); + + at_len += OSPF6_AUTH_HDR_MIN_SIZE; + at_len += keychain_get_hash_len(oi->at_data.hash_algo); + return oi->ifmtu - (sizeof(struct ip6_hdr)) - at_len; + } + + return oi->ifmtu - (sizeof(struct ip6_hdr)); +} + +static uint16_t ospf6_make_hello(struct ospf6_interface *oi, struct stream *s) +{ + struct listnode *node, *nnode; + struct ospf6_neighbor *on; + uint16_t length = OSPF6_HELLO_MIN_SIZE; + uint8_t options1 = oi->area->options[1]; + + if (oi->at_data.flags != 0) + options1 |= OSPF6_OPT_AT; + + stream_putl(s, oi->interface->ifindex); + stream_putc(s, oi->priority); + stream_putc(s, oi->area->options[0]); + stream_putc(s, options1); + stream_putc(s, oi->area->options[2]); + stream_putw(s, oi->hello_interval); + stream_putw(s, oi->dead_interval); + stream_put_ipv4(s, oi->drouter); + stream_put_ipv4(s, oi->bdrouter); + + for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) { + if (on->state < OSPF6_NEIGHBOR_INIT) + continue; + + if ((length + sizeof(uint32_t) + OSPF6_HEADER_SIZE) + > ospf6_packet_max(oi)) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_HELLO, + SEND)) + zlog_debug( + "sending Hello message: exceeds I/F MTU"); + break; + } + + stream_put_ipv4(s, on->router_id); + length += sizeof(uint32_t); + } + + return length; +} + +static void ospf6_write(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + struct ospf6_interface *oi; + struct ospf6_header *oh; + struct ospf6_packet *op; + struct listnode *node; + struct iovec iovector[2]; + int pkt_count = 0; + int len; + int64_t latency = 0; + struct timeval timestamp; + uint16_t at_len = 0; + + if (ospf6->fd < 0) { + zlog_warn("ospf6_write failed to send, fd %d", ospf6->fd); + return; + } + + node = listhead(ospf6->oi_write_q); + assert(node); + oi = listgetdata(node); + + while ((pkt_count < ospf6->write_oi_count) && oi) { + op = ospf6_fifo_head(oi->obuf); + assert(op); + assert(op->length >= OSPF6_HEADER_SIZE); + + iovector[0].iov_base = (caddr_t)stream_pnt(op->s); + iovector[0].iov_len = op->length; + iovector[1].iov_base = NULL; + iovector[1].iov_len = 0; + + oh = (struct ospf6_header *)STREAM_DATA(op->s); + + if (oi->at_data.flags != 0) { + at_len = ospf6_auth_len_get(oi); + if (at_len) { + iovector[0].iov_len = + ntohs(oh->length) + at_len; + ospf6_auth_digest_send(oi->linklocal_addr, oi, + oh, at_len, + iovector[0].iov_len); + } else { + iovector[0].iov_len = ntohs(oh->length); + } + } else { + iovector[0].iov_len = ntohs(oh->length); + } + + len = ospf6_sendmsg(oi->linklocal_addr, &op->dst, + oi->interface->ifindex, iovector, + ospf6->fd); + + if (len != (op->length + (int)at_len)) + flog_err(EC_LIB_DEVELOPMENT, + "Could not send entire message"); + + if (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND_HDR)) { + zlog_debug("%s send on %s", + ospf6_message_type(oh->type), + oi->interface->name); + zlog_debug(" src: %pI6", oi->linklocal_addr); + zlog_debug(" dst: %pI6", &op->dst); + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + ospf6_hello_print(oh, OSPF6_ACTION_SEND); + break; + case OSPF6_MESSAGE_TYPE_DBDESC: + ospf6_dbdesc_print(oh, OSPF6_ACTION_SEND); + break; + case OSPF6_MESSAGE_TYPE_LSREQ: + ospf6_lsreq_print(oh, OSPF6_ACTION_SEND); + break; + case OSPF6_MESSAGE_TYPE_LSUPDATE: + ospf6_lsupdate_print(oh, OSPF6_ACTION_SEND); + break; + case OSPF6_MESSAGE_TYPE_LSACK: + ospf6_lsack_print(oh, OSPF6_ACTION_SEND); + break; + default: + zlog_debug("Unknown message"); + assert(0); + break; + } + } + switch (oh->type) { + case OSPF6_MESSAGE_TYPE_HELLO: + monotime(×tamp); + if (oi->hello_out) + latency = monotime_since(&oi->last_hello, NULL) + - ((int64_t)oi->hello_interval + * 1000000); + + /* log if latency exceeds the hello period */ + if (latency > ((int64_t)oi->hello_interval * 1000000)) + zlog_warn("%s hello TX high latency %" PRId64 + "us.", + __func__, latency); + oi->last_hello = timestamp; + oi->hello_out++; + break; + case OSPF6_MESSAGE_TYPE_DBDESC: + oi->db_desc_out++; + break; + case OSPF6_MESSAGE_TYPE_LSREQ: + oi->ls_req_out++; + break; + case OSPF6_MESSAGE_TYPE_LSUPDATE: + oi->ls_upd_out++; + break; + case OSPF6_MESSAGE_TYPE_LSACK: + oi->ls_ack_out++; + break; + default: + zlog_debug("Unknown message"); + assert(0); + break; + } + + if ((oi->at_data.flags != 0) && + (IS_OSPF6_DEBUG_MESSAGE(oh->type, SEND_HDR)) && + (IS_OSPF6_DEBUG_AUTH_TX)) + ospf6_auth_hdr_dump_send(oh, iovector[0].iov_len); + + /* initialize at_len to 0 for next packet */ + at_len = 0; + + /* Now delete packet from queue. */ + ospf6_packet_delete(oi); + + /* Move this interface to the tail of write_q to + serve everyone in a round robin fashion */ + list_delete_node(ospf6->oi_write_q, node); + if (ospf6_fifo_head(oi->obuf) == NULL) { + oi->on_write_q = 0; + oi = NULL; + } else { + listnode_add(ospf6->oi_write_q, oi); + } + + /* Setup to service from the head of the queue again */ + if (!list_isempty(ospf6->oi_write_q)) { + node = listhead(ospf6->oi_write_q); + oi = listgetdata(node); + } + } + + /* If packets still remain in queue, call write thread. */ + if (!list_isempty(ospf6->oi_write_q)) + event_add_write(master, ospf6_write, ospf6, ospf6->fd, + &ospf6->t_write); +} + +void ospf6_hello_send(struct event *thread) +{ + struct ospf6_interface *oi; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + /* Check if the GR hello-delay is active. */ + if (oi->gr.hello_delay.t_grace_send) + return; + + /* Check if config is still being processed */ + if (event_is_scheduled(t_ospf6_cfg)) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_HELLO, SEND)) + zlog_debug( + "Suppressing Hello on interface %s during config load", + oi->interface->name); + event_add_timer(master, ospf6_hello_send, oi, + oi->hello_interval, &oi->thread_send_hello); + return; + } + + if (oi->state <= OSPF6_INTERFACE_DOWN) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_HELLO, SEND_HDR)) + zlog_debug("Unable to send Hello on down interface %s", + oi->interface->name); + return; + } + + event_add_timer(master, ospf6_hello_send, oi, oi->hello_interval, + &oi->thread_send_hello); + + ospf6_hello_send_addr(oi, NULL); +} + +/* used to send polls for PtP/PtMP too */ +void ospf6_hello_send_addr(struct ospf6_interface *oi, + const struct in6_addr *addr) +{ + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + bool anything = false; + + op = ospf6_packet_new(oi->ifmtu); + + ospf6_make_header(OSPF6_MESSAGE_TYPE_HELLO, oi, op->s); + + /* Prepare OSPF Hello body */ + length += ospf6_make_hello(oi, op->s); + if (length == OSPF6_HEADER_SIZE) { + /* Hello overshooting MTU */ + ospf6_packet_free(op); + return; + } + + /* Fill OSPF header. */ + ospf6_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT + || oi->state == OSPF6_INTERFACE_POINTTOMULTIPOINT) + && !addr && oi->p2xp_no_multicast_hello) { + struct listnode *node; + struct ospf6_neighbor *on; + struct ospf6_packet *opdup; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) { + if (on->state < OSPF6_NEIGHBOR_INIT) + /* poll-interval for these */ + continue; + + opdup = ospf6_packet_dup(op); + opdup->dst = on->linklocal_addr; + ospf6_fill_hdr_checksum(oi, opdup); + ospf6_packet_add_top(oi, opdup); + anything = true; + } + + ospf6_packet_free(op); + } else { + op->dst = addr ? *addr : allspfrouters6; + + /* Add packet to the top of the interface output queue, so that + * they can't get delayed by things like long queues of LS + * Update packets + */ + ospf6_fill_hdr_checksum(oi, op); + ospf6_packet_add_top(oi, op); + anything = true; + } + + if (anything) + OSPF6_MESSAGE_WRITE_ON(oi); +} + +static uint16_t ospf6_make_dbdesc(struct ospf6_neighbor *on, struct stream *s) +{ + uint16_t length = OSPF6_DB_DESC_MIN_SIZE; + struct ospf6_lsa *lsa, *lsanext; + uint8_t options1 = on->ospf6_if->area->options[1]; + + if (on->ospf6_if->at_data.flags != 0) + options1 |= OSPF6_OPT_AT; + + /* if this is initial one, initialize sequence number for DbDesc */ + if (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT) + && (on->dbdesc_seqnum == 0)) { + on->dbdesc_seqnum = frr_sequence32_next(); + } + + /* reserved */ + stream_putc(s, 0); /* reserved 1 */ + stream_putc(s, on->ospf6_if->area->options[0]); + stream_putc(s, options1); + stream_putc(s, on->ospf6_if->area->options[2]); + stream_putw(s, on->ospf6_if->ifmtu); + stream_putc(s, 0); /* reserved 2 */ + stream_putc(s, on->dbdesc_bits); + stream_putl(s, on->dbdesc_seqnum); + + /* if this is not initial one, set LSA headers in dbdesc */ + if (!CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT)) { + for (ALL_LSDB(on->dbdesc_list, lsa, lsanext)) { + ospf6_lsa_age_update_to_send(lsa, + on->ospf6_if->transdelay); + + /* MTU check */ + if ((length + sizeof(struct ospf6_lsa_header) + + OSPF6_HEADER_SIZE) + > ospf6_packet_max(on->ospf6_if)) { + ospf6_lsa_unlock(&lsa); + if (lsanext) + ospf6_lsa_unlock(&lsanext); + break; + } + stream_put(s, lsa->header, + sizeof(struct ospf6_lsa_header)); + length += sizeof(struct ospf6_lsa_header); + } + } + return length; +} + +void ospf6_dbdesc_send(struct event *thread) +{ + struct ospf6_neighbor *on; + uint16_t length = OSPF6_HEADER_SIZE; + struct ospf6_packet *op; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + + if (on->state < OSPF6_NEIGHBOR_EXSTART) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_DBDESC, SEND)) + zlog_debug( + "Quit to send DbDesc to neighbor %s state %s", + on->name, ospf6_neighbor_state_str[on->state]); + return; + } + + /* set next thread if master */ + if (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT)) + event_add_timer(master, ospf6_dbdesc_send, on, + on->ospf6_if->rxmt_interval, + &on->thread_send_dbdesc); + + op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_DBDESC, on->ospf6_if, op->s); + + length += ospf6_make_dbdesc(on, op->s); + ospf6_fill_header(on->ospf6_if, op->s, length); + + /* Set packet length. */ + op->length = length; + + if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) + op->dst = allspfrouters6; + else + op->dst = on->linklocal_addr; + + ospf6_fill_hdr_checksum(on->ospf6_if, op); + + ospf6_packet_add(on->ospf6_if, op); + + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); +} + +void ospf6_dbdesc_send_newone(struct event *thread) +{ + struct ospf6_neighbor *on; + struct ospf6_lsa *lsa, *lsanext; + unsigned int size = 0; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + ospf6_lsdb_remove_all(on->dbdesc_list); + + /* move LSAs from summary_list to dbdesc_list (within neighbor + structure) + so that ospf6_send_dbdesc () can send those LSAs */ + size = sizeof(struct ospf6_lsa_header) + sizeof(struct ospf6_dbdesc); + for (ALL_LSDB(on->summary_list, lsa, lsanext)) { + /* if stub area then don't advertise AS-External LSAs */ + if ((IS_AREA_STUB(on->ospf6_if->area) + || IS_AREA_NSSA(on->ospf6_if->area)) + && ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) { + ospf6_lsdb_remove(lsa, on->summary_list); + continue; + } + + if (size + sizeof(struct ospf6_lsa_header) + > ospf6_packet_max(on->ospf6_if)) { + ospf6_lsa_unlock(&lsa); + if (lsanext) + ospf6_lsa_unlock(&lsanext); + break; + } + + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->dbdesc_list); + ospf6_lsdb_remove(lsa, on->summary_list); + size += sizeof(struct ospf6_lsa_header); + } + + if (on->summary_list->count == 0) + UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); + + /* If slave, More bit check must be done here */ + if (!CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT) && /* Slave */ + !CHECK_FLAG(on->dbdesc_last.bits, OSPF6_DBDESC_MBIT) + && !CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT)) + event_add_event(master, exchange_done, on, 0, + &on->thread_exchange_done); + + event_execute(master, ospf6_dbdesc_send, on, 0, NULL); +} + +static uint16_t ospf6_make_lsreq(struct ospf6_neighbor *on, struct stream *s) +{ + uint16_t length = 0; + struct ospf6_lsa *lsa, *lsanext, *last_req = NULL; + + for (ALL_LSDB(on->request_list, lsa, lsanext)) { + if ((length + OSPF6_HEADER_SIZE) + > ospf6_packet_max(on->ospf6_if)) { + ospf6_lsa_unlock(&lsa); + if (lsanext) + ospf6_lsa_unlock(&lsanext); + break; + } + stream_putw(s, 0); /* reserved */ + stream_putw(s, ntohs(lsa->header->type)); + stream_putl(s, ntohl(lsa->header->id)); + stream_putl(s, ntohl(lsa->header->adv_router)); + length += sizeof(struct ospf6_lsreq_entry); + last_req = lsa; + } + + if (last_req != NULL) { + if (on->last_ls_req != NULL) + ospf6_lsa_unlock(&on->last_ls_req); + + ospf6_lsa_lock(last_req); + on->last_ls_req = last_req; + } + + return length; +} + +static uint16_t ospf6_make_lsack_neighbor(struct ospf6_neighbor *on, + struct ospf6_packet **op) +{ + uint16_t length = 0; + struct ospf6_lsa *lsa, *lsanext; + int lsa_cnt = 0; + + for (ALL_LSDB(on->lsack_list, lsa, lsanext)) { + if ((length + sizeof(struct ospf6_lsa_header) + + OSPF6_HEADER_SIZE) + > ospf6_packet_max(on->ospf6_if)) { + /* if we run out of packet size/space here, + better to try again soon. */ + if (lsa_cnt) { + ospf6_fill_header(on->ospf6_if, (*op)->s, + length + OSPF6_HEADER_SIZE); + + (*op)->length = length + OSPF6_HEADER_SIZE; + (*op)->dst = on->linklocal_addr; + ospf6_fill_hdr_checksum(on->ospf6_if, *op); + ospf6_packet_add(on->ospf6_if, *op); + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); + /* new packet */ + *op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSACK, + on->ospf6_if, (*op)->s); + length = 0; + lsa_cnt = 0; + } + } + ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); + stream_put((*op)->s, lsa->header, + sizeof(struct ospf6_lsa_header)); + length += sizeof(struct ospf6_lsa_header); + + assert(lsa->lock == 2); + ospf6_lsdb_remove(lsa, on->lsack_list); + lsa_cnt++; + } + return length; +} + +void ospf6_lsreq_send(struct event *thread) +{ + struct ospf6_neighbor *on; + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + + /* LSReq will be sent only in ExStart or Loading */ + if (on->state != OSPF6_NEIGHBOR_EXCHANGE + && on->state != OSPF6_NEIGHBOR_LOADING) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSREQ, SEND_HDR)) + zlog_debug("Quit to send LSReq to neighbor %s state %s", + on->name, + ospf6_neighbor_state_str[on->state]); + return; + } + + /* schedule loading_done if request list is empty */ + if (on->request_list->count == 0) { + event_add_event(master, loading_done, on, 0, + &on->event_loading_done); + return; + } + + op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSREQ, on->ospf6_if, op->s); + + length += ospf6_make_lsreq(on, op->s); + + if (length == OSPF6_HEADER_SIZE) { + /* Hello overshooting MTU */ + ospf6_packet_free(op); + return; + } + + /* Fill OSPF header. */ + ospf6_fill_header(on->ospf6_if, op->s, length); + + /* Set packet length */ + op->length = length; + + if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) + op->dst = allspfrouters6; + else + op->dst = on->linklocal_addr; + + ospf6_fill_hdr_checksum(on->ospf6_if, op); + ospf6_packet_add(on->ospf6_if, op); + + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); + + /* set next thread */ + if (on->request_list->count != 0) { + event_add_timer(master, ospf6_lsreq_send, on, + on->ospf6_if->rxmt_interval, + &on->thread_send_lsreq); + } +} + +static void ospf6_send_lsupdate(struct ospf6_neighbor *on, + struct ospf6_interface *oi, + struct ospf6_packet *op) +{ + if (on) { + if ((on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) + || (on->ospf6_if->state == OSPF6_INTERFACE_DR) + || (on->ospf6_if->state == OSPF6_INTERFACE_BDR)) + op->dst = allspfrouters6; + else + op->dst = on->linklocal_addr; + oi = on->ospf6_if; + } else if (oi) { + if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT) + || (oi->state == OSPF6_INTERFACE_DR) + || (oi->state == OSPF6_INTERFACE_BDR)) + op->dst = allspfrouters6; + else + op->dst = alldrouters6; + } + if (oi) { + struct ospf6 *ospf6; + + ospf6_fill_hdr_checksum(oi, op); + ospf6_packet_add(oi, op); + /* If ospf instance is being deleted, send the packet + * immediately + */ + if ((oi->area == NULL) || (oi->area->ospf6 == NULL)) + return; + + ospf6 = oi->area->ospf6; + if (ospf6->inst_shutdown) { + if (oi->on_write_q == 0) { + listnode_add(ospf6->oi_write_q, oi); + oi->on_write_q = 1; + } + /* + * When ospf6d immediately calls event_execute + * for items in the oi_write_q. The event_execute + * will call ospf6_write and cause the oi_write_q + * to be emptied. *IF* there is already an event + * scheduled for the oi_write_q by something else + * then when it wakes up in the future and attempts + * to cycle through items in the queue it will + * assert. Let's stop the t_write event and + * if ospf6_write doesn't finish up the work + * it will schedule itself again. + */ + event_cancel(&ospf6->t_write); + event_execute(master, ospf6_write, ospf6, 0, NULL); + } else + OSPF6_MESSAGE_WRITE_ON(oi); + } +} + +static uint16_t ospf6_make_lsupdate_list(struct ospf6_neighbor *on, + struct ospf6_packet **op, int *lsa_cnt) +{ + uint16_t length = OSPF6_LS_UPD_MIN_SIZE; + struct ospf6_lsa *lsa, *lsanext; + + /* skip over fixed header */ + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + + for (ALL_LSDB(on->lsupdate_list, lsa, lsanext)) { + if ((length + ospf6_lsa_size(lsa->header) + OSPF6_HEADER_SIZE) > + ospf6_packet_max(on->ospf6_if)) { + ospf6_fill_header(on->ospf6_if, (*op)->s, + length + OSPF6_HEADER_SIZE); + (*op)->length = length + OSPF6_HEADER_SIZE; + ospf6_fill_lsupdate_header((*op)->s, *lsa_cnt); + ospf6_send_lsupdate(on, NULL, *op); + + /* refresh packet */ + *op = ospf6_packet_new(on->ospf6_if->ifmtu); + length = OSPF6_LS_UPD_MIN_SIZE; + *lsa_cnt = 0; + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, + on->ospf6_if, (*op)->s); + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + } + ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); + stream_put((*op)->s, lsa->header, ospf6_lsa_size(lsa->header)); + (*lsa_cnt)++; + length += ospf6_lsa_size(lsa->header); + assert(lsa->lock == 2); + ospf6_lsdb_remove(lsa, on->lsupdate_list); + } + return length; +} + +static uint16_t ospf6_make_ls_retrans_list(struct ospf6_neighbor *on, + struct ospf6_packet **op, + int *lsa_cnt) +{ + uint16_t length = OSPF6_LS_UPD_MIN_SIZE; + struct ospf6_lsa *lsa, *lsanext; + + /* skip over fixed header */ + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + + for (ALL_LSDB(on->retrans_list, lsa, lsanext)) { + if ((length + ospf6_lsa_size(lsa->header) + OSPF6_HEADER_SIZE) > + ospf6_packet_max(on->ospf6_if)) { + ospf6_fill_header(on->ospf6_if, (*op)->s, + length + OSPF6_HEADER_SIZE); + (*op)->length = length + OSPF6_HEADER_SIZE; + ospf6_fill_lsupdate_header((*op)->s, *lsa_cnt); + if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) + (*op)->dst = allspfrouters6; + else + (*op)->dst = on->linklocal_addr; + + ospf6_fill_hdr_checksum(on->ospf6_if, *op); + ospf6_packet_add(on->ospf6_if, *op); + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); + + /* refresh packet */ + *op = ospf6_packet_new(on->ospf6_if->ifmtu); + length = OSPF6_LS_UPD_MIN_SIZE; + *lsa_cnt = 0; + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, + on->ospf6_if, (*op)->s); + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + } + ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); + stream_put((*op)->s, lsa->header, ospf6_lsa_size(lsa->header)); + (*lsa_cnt)++; + length += ospf6_lsa_size(lsa->header); + } + return length; +} + +void ospf6_lsupdate_send_neighbor(struct event *thread) +{ + struct ospf6_neighbor *on; + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + int lsa_cnt = 0; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND_HDR)) + zlog_debug("LSUpdate to neighbor %s", on->name); + + if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, + SEND_HDR)) + zlog_debug("Quit to send (neighbor state %s)", + ospf6_neighbor_state_str[on->state]); + return; + } + + /* first do lsupdate_list */ + op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, on->ospf6_if, op->s); + length += ospf6_make_lsupdate_list(on, &op, &lsa_cnt); + if (lsa_cnt) { + /* Fill OSPF header. */ + ospf6_fill_header(on->ospf6_if, op->s, length); + ospf6_fill_lsupdate_header(op->s, lsa_cnt); + op->length = length; + ospf6_send_lsupdate(on, NULL, op); + + /* prepare new packet */ + op = ospf6_packet_new(on->ospf6_if->ifmtu); + length = OSPF6_HEADER_SIZE; + lsa_cnt = 0; + } else { + stream_reset(op->s); + length = OSPF6_HEADER_SIZE; + } + + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, on->ospf6_if, op->s); + /* now do retransmit list */ + length += ospf6_make_ls_retrans_list(on, &op, &lsa_cnt); + if (lsa_cnt) { + ospf6_fill_header(on->ospf6_if, op->s, length); + ospf6_fill_lsupdate_header(op->s, lsa_cnt); + op->length = length; + if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT) + op->dst = allspfrouters6; + else + op->dst = on->linklocal_addr; + ospf6_fill_hdr_checksum(on->ospf6_if, op); + ospf6_packet_add(on->ospf6_if, op); + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); + } else + ospf6_packet_free(op); + + if (on->lsupdate_list->count != 0) { + event_add_event(master, ospf6_lsupdate_send_neighbor, on, 0, + &on->thread_send_lsupdate); + } else if (on->retrans_list->count != 0) { + event_add_timer(master, ospf6_lsupdate_send_neighbor, on, + on->ospf6_if->rxmt_interval, + &on->thread_send_lsupdate); + } +} + +int ospf6_lsupdate_send_neighbor_now(struct ospf6_neighbor *on, + struct ospf6_lsa *lsa) +{ + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + + op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, on->ospf6_if, op->s); + + /* skip over fixed header */ + stream_forward_endp(op->s, OSPF6_LS_UPD_MIN_SIZE); + ospf6_lsa_age_update_to_send(lsa, on->ospf6_if->transdelay); + stream_put(op->s, lsa->header, ospf6_lsa_size(lsa->header)); + length = OSPF6_HEADER_SIZE + OSPF6_LS_UPD_MIN_SIZE + + ospf6_lsa_size(lsa->header); + ospf6_fill_header(on->ospf6_if, op->s, length); + ospf6_fill_lsupdate_header(op->s, 1); + op->length = length; + + if (IS_OSPF6_DEBUG_FLOODING + || IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, SEND_HDR)) + zlog_debug("%s: Send lsupdate with lsa %s (age %u)", __func__, + lsa->name, ntohs(lsa->header->age)); + + ospf6_send_lsupdate(on, NULL, op); + + return 0; +} + +static uint16_t ospf6_make_lsupdate_interface(struct ospf6_interface *oi, + struct ospf6_packet **op, + int *lsa_cnt) +{ + uint16_t length = OSPF6_LS_UPD_MIN_SIZE; + struct ospf6_lsa *lsa, *lsanext; + + /* skip over fixed header */ + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + + for (ALL_LSDB(oi->lsupdate_list, lsa, lsanext)) { + if (length + ospf6_lsa_size(lsa->header) + OSPF6_HEADER_SIZE > + ospf6_packet_max(oi)) { + ospf6_fill_header(oi, (*op)->s, + length + OSPF6_HEADER_SIZE); + (*op)->length = length + OSPF6_HEADER_SIZE; + ospf6_fill_lsupdate_header((*op)->s, *lsa_cnt); + ospf6_send_lsupdate(NULL, oi, *op); + + /* refresh packet */ + *op = ospf6_packet_new(oi->ifmtu); + length = OSPF6_LS_UPD_MIN_SIZE; + *lsa_cnt = 0; + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, oi, + (*op)->s); + stream_forward_endp((*op)->s, OSPF6_LS_UPD_MIN_SIZE); + } + + ospf6_lsa_age_update_to_send(lsa, oi->transdelay); + stream_put((*op)->s, lsa->header, ospf6_lsa_size(lsa->header)); + (*lsa_cnt)++; + length += ospf6_lsa_size(lsa->header); + + assert(lsa->lock == 2); + ospf6_lsdb_remove(lsa, oi->lsupdate_list); + } + return length; +} + +void ospf6_lsupdate_send_interface(struct event *thread) +{ + struct ospf6_interface *oi; + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + int lsa_cnt = 0; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + if (oi->state <= OSPF6_INTERFACE_WAITING) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSUPDATE, + SEND_HDR)) + zlog_debug( + "Quit to send LSUpdate to interface %s state %s", + oi->interface->name, + ospf6_interface_state_str[oi->state]); + return; + } + + /* if we have nothing to send, return */ + if (oi->lsupdate_list->count == 0) + return; + + op = ospf6_packet_new(oi->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSUPDATE, oi, op->s); + length += ospf6_make_lsupdate_interface(oi, &op, &lsa_cnt); + if (lsa_cnt) { + /* Fill OSPF header. */ + ospf6_fill_header(oi, op->s, length); + ospf6_fill_lsupdate_header(op->s, lsa_cnt); + op->length = length; + ospf6_send_lsupdate(NULL, oi, op); + } else + ospf6_packet_free(op); + + if (oi->lsupdate_list->count > 0) { + event_add_event(master, ospf6_lsupdate_send_interface, oi, 0, + &oi->thread_send_lsupdate); + } +} + +void ospf6_lsack_send_neighbor(struct event *thread) +{ + struct ospf6_neighbor *on; + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + + if (on->state < OSPF6_NEIGHBOR_EXCHANGE) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSACK, SEND_HDR)) + zlog_debug("Quit to send LSAck to neighbor %s state %s", + on->name, + ospf6_neighbor_state_str[on->state]); + return; + } + + /* if we have nothing to send, return */ + if (on->lsack_list->count == 0) + return; + + op = ospf6_packet_new(on->ospf6_if->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSACK, on->ospf6_if, op->s); + + length += ospf6_make_lsack_neighbor(on, &op); + + if (length == OSPF6_HEADER_SIZE) { + ospf6_packet_free(op); + return; + } + + /* Fill OSPF header. */ + ospf6_fill_header(on->ospf6_if, op->s, length); + + /* Set packet length, dst and queue to FIFO. */ + op->length = length; + op->dst = on->linklocal_addr; + ospf6_fill_hdr_checksum(on->ospf6_if, op); + ospf6_packet_add(on->ospf6_if, op); + OSPF6_MESSAGE_WRITE_ON(on->ospf6_if); + + if (on->lsack_list->count > 0) + event_add_event(master, ospf6_lsack_send_neighbor, on, 0, + &on->thread_send_lsack); +} + +static uint16_t ospf6_make_lsack_interface(struct ospf6_interface *oi, + struct ospf6_packet *op) +{ + uint16_t length = 0; + struct ospf6_lsa *lsa, *lsanext; + + for (ALL_LSDB(oi->lsack_list, lsa, lsanext)) { + if ((length + sizeof(struct ospf6_lsa_header) + + OSPF6_HEADER_SIZE) + > ospf6_packet_max(oi)) { + /* if we run out of packet size/space here, + better to try again soon. */ + EVENT_OFF(oi->thread_send_lsack); + event_add_event(master, ospf6_lsack_send_interface, oi, + 0, &oi->thread_send_lsack); + + ospf6_lsa_unlock(&lsa); + if (lsanext) + ospf6_lsa_unlock(&lsanext); + break; + } + ospf6_lsa_age_update_to_send(lsa, oi->transdelay); + stream_put(op->s, lsa->header, sizeof(struct ospf6_lsa_header)); + length += sizeof(struct ospf6_lsa_header); + + assert(lsa->lock == 2); + ospf6_lsdb_remove(lsa, oi->lsack_list); + } + return length; +} + +void ospf6_lsack_send_interface(struct event *thread) +{ + struct ospf6_interface *oi; + struct ospf6_packet *op; + uint16_t length = OSPF6_HEADER_SIZE; + + oi = (struct ospf6_interface *)EVENT_ARG(thread); + + if (oi->state <= OSPF6_INTERFACE_WAITING) { + if (IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_LSACK, SEND_HDR)) + zlog_debug( + "Quit to send LSAck to interface %s state %s", + oi->interface->name, + ospf6_interface_state_str[oi->state]); + return; + } + + /* if we have nothing to send, return */ + if (oi->lsack_list->count == 0) + return; + + op = ospf6_packet_new(oi->ifmtu); + ospf6_make_header(OSPF6_MESSAGE_TYPE_LSACK, oi, op->s); + + length += ospf6_make_lsack_interface(oi, op); + + if (length == OSPF6_HEADER_SIZE) { + ospf6_packet_free(op); + return; + } + /* Fill OSPF header. */ + ospf6_fill_header(oi, op->s, length); + + /* Set packet length, dst and queue to FIFO. */ + op->length = length; + if ((oi->state == OSPF6_INTERFACE_POINTTOPOINT) + || (oi->state == OSPF6_INTERFACE_DR) + || (oi->state == OSPF6_INTERFACE_BDR)) + op->dst = allspfrouters6; + else + op->dst = alldrouters6; + + ospf6_fill_hdr_checksum(oi, op); + ospf6_packet_add(oi, op); + OSPF6_MESSAGE_WRITE_ON(oi); + + if (oi->lsack_list->count > 0) + event_add_event(master, ospf6_lsack_send_interface, oi, 0, + &oi->thread_send_lsack); +} + +/* Commands */ +DEFUN(debug_ospf6_message, debug_ospf6_message_cmd, + "debug ospf6 message []", + DEBUG_STR OSPF6_STR + "Debug OSPFv3 message\n" + "Debug Unknown message\n" + "Debug Hello message\n" + "Debug Database Description message\n" + "Debug Link State Request message\n" + "Debug Link State Update message\n" + "Debug Link State Acknowledgement message\n" + "Debug All message\n" + "Debug only sending message, entire packet\n" + "Debug only receiving message, entire packet\n" + "Debug only sending message, header only\n" + "Debug only receiving message, header only\n") +{ + int idx_packet = 3; + int idx_send_recv = 4; + unsigned char level = 0; + int type = 0; + int i; + + /* check type */ + if (!strncmp(argv[idx_packet]->arg, "u", 1)) + type = OSPF6_MESSAGE_TYPE_UNKNOWN; + else if (!strncmp(argv[idx_packet]->arg, "h", 1)) + type = OSPF6_MESSAGE_TYPE_HELLO; + else if (!strncmp(argv[idx_packet]->arg, "d", 1)) + type = OSPF6_MESSAGE_TYPE_DBDESC; + else if (!strncmp(argv[idx_packet]->arg, "lsr", 3)) + type = OSPF6_MESSAGE_TYPE_LSREQ; + else if (!strncmp(argv[idx_packet]->arg, "lsu", 3)) + type = OSPF6_MESSAGE_TYPE_LSUPDATE; + else if (!strncmp(argv[idx_packet]->arg, "lsa", 3)) + type = OSPF6_MESSAGE_TYPE_LSACK; + else if (!strncmp(argv[idx_packet]->arg, "a", 1)) + type = OSPF6_MESSAGE_TYPE_ALL; + + if (argc == 4) + level = OSPF6_DEBUG_MESSAGE_SEND | OSPF6_DEBUG_MESSAGE_RECV; + else if (!strncmp(argv[idx_send_recv]->arg, "send-h", 6)) + level = OSPF6_DEBUG_MESSAGE_SEND_HDR; + else if (!strncmp(argv[idx_send_recv]->arg, "s", 1)) + level = OSPF6_DEBUG_MESSAGE_SEND; + else if (!strncmp(argv[idx_send_recv]->arg, "recv-h", 6)) + level = OSPF6_DEBUG_MESSAGE_RECV_HDR; + else if (!strncmp(argv[idx_send_recv]->arg, "r", 1)) + level = OSPF6_DEBUG_MESSAGE_RECV; + + if (type == OSPF6_MESSAGE_TYPE_ALL) { + for (i = 0; i < 6; i++) + OSPF6_DEBUG_MESSAGE_ON(i, level); + } else + OSPF6_DEBUG_MESSAGE_ON(type, level); + + return CMD_SUCCESS; +} + +DEFUN(no_debug_ospf6_message, no_debug_ospf6_message_cmd, + "no debug ospf6 message []", + NO_STR DEBUG_STR OSPF6_STR + "Debug OSPFv3 message\n" + "Debug Unknown message\n" + "Debug Hello message\n" + "Debug Database Description message\n" + "Debug Link State Request message\n" + "Debug Link State Update message\n" + "Debug Link State Acknowledgement message\n" + "Debug All message\n" + "Debug only sending message, entire pkt\n" + "Debug only receiving message, entire pkt\n" + "Debug only sending message, header only\n" + "Debug only receiving message, header only\n") +{ + int idx_packet = 4; + int idx_send_recv = 5; + unsigned char level = 0; + int type = 0; + int i; + + /* check type */ + if (!strncmp(argv[idx_packet]->arg, "u", 1)) + type = OSPF6_MESSAGE_TYPE_UNKNOWN; + else if (!strncmp(argv[idx_packet]->arg, "h", 1)) + type = OSPF6_MESSAGE_TYPE_HELLO; + else if (!strncmp(argv[idx_packet]->arg, "d", 1)) + type = OSPF6_MESSAGE_TYPE_DBDESC; + else if (!strncmp(argv[idx_packet]->arg, "lsr", 3)) + type = OSPF6_MESSAGE_TYPE_LSREQ; + else if (!strncmp(argv[idx_packet]->arg, "lsu", 3)) + type = OSPF6_MESSAGE_TYPE_LSUPDATE; + else if (!strncmp(argv[idx_packet]->arg, "lsa", 3)) + type = OSPF6_MESSAGE_TYPE_LSACK; + else if (!strncmp(argv[idx_packet]->arg, "a", 1)) + type = OSPF6_MESSAGE_TYPE_ALL; + + if (argc == 5) + level = OSPF6_DEBUG_MESSAGE_SEND | OSPF6_DEBUG_MESSAGE_RECV + | OSPF6_DEBUG_MESSAGE_SEND_HDR + | OSPF6_DEBUG_MESSAGE_RECV_HDR; + else if (!strncmp(argv[idx_send_recv]->arg, "send-h", 6)) + level = OSPF6_DEBUG_MESSAGE_SEND_HDR; + else if (!strncmp(argv[idx_send_recv]->arg, "s", 1)) + level = OSPF6_DEBUG_MESSAGE_SEND; + else if (!strncmp(argv[idx_send_recv]->arg, "recv-h", 6)) + level = OSPF6_DEBUG_MESSAGE_RECV_HDR; + else if (!strncmp(argv[idx_send_recv]->arg, "r", 1)) + level = OSPF6_DEBUG_MESSAGE_RECV; + + if (type == OSPF6_MESSAGE_TYPE_ALL) { + for (i = 0; i < 6; i++) + OSPF6_DEBUG_MESSAGE_OFF(i, level); + } else + OSPF6_DEBUG_MESSAGE_OFF(type, level); + + return CMD_SUCCESS; +} + + +int config_write_ospf6_debug_message(struct vty *vty) +{ + const char *type_str[] = {"unknown", "hello", "dbdesc", + "lsreq", "lsupdate", "lsack"}; + unsigned char s = 0, r = 0, sh = 0, rh = 0; + int i; + + for (i = 0; i < 6; i++) { + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, SEND)) + s |= 1 << i; + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, RECV)) + r |= 1 << i; + } + + for (i = 0; i < 6; i++) { + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, SEND_HDR)) + sh |= 1 << i; + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, RECV_HDR)) + rh |= 1 << i; + } + + if (s == 0x3f && r == 0x3f) { + vty_out(vty, "debug ospf6 message all\n"); + return 0; + } + + if (s == 0x3f && r == 0) { + vty_out(vty, "debug ospf6 message all send\n"); + return 0; + } else if (s == 0 && r == 0x3f) { + vty_out(vty, "debug ospf6 message all recv\n"); + return 0; + } + + if (sh == 0x3f && rh == 0) { + vty_out(vty, "debug ospf6 message all send-hdr\n"); + return 0; + } else if (sh == 0 && rh == 0x3f) { + vty_out(vty, "debug ospf6 message all recv-hdr\n"); + return 0; + } + + /* Unknown message is logged by default */ + if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, SEND) + && !IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) + vty_out(vty, "no debug ospf6 message unknown\n"); + else if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, SEND)) + vty_out(vty, "no debug ospf6 message unknown send\n"); + else if (!IS_OSPF6_DEBUG_MESSAGE(OSPF6_MESSAGE_TYPE_UNKNOWN, RECV)) + vty_out(vty, "no debug ospf6 message unknown recv\n"); + + for (i = 1; i < 6; i++) { + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, SEND) + && IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, RECV)) { + vty_out(vty, "debug ospf6 message %s\n", type_str[i]); + continue; + } + + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, SEND)) + vty_out(vty, "debug ospf6 message %s send\n", + type_str[i]); + else if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, SEND_HDR)) + vty_out(vty, "debug ospf6 message %s send-hdr\n", + type_str[i]); + + if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, RECV)) + vty_out(vty, "debug ospf6 message %s recv\n", + type_str[i]); + else if (IS_OSPF6_DEBUG_MESSAGE_ENABLED(i, RECV_HDR)) + vty_out(vty, "debug ospf6 message %s recv-hdr\n", + type_str[i]); + } + + return 0; +} + +void install_element_ospf6_debug_message(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_message_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_message_cmd); + install_element(CONFIG_NODE, &debug_ospf6_message_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_message_cmd); +} diff --git a/ospf6d/ospf6_message.h b/ospf6d/ospf6_message.h new file mode 100644 index 0000000..2434079 --- /dev/null +++ b/ospf6d/ospf6_message.h @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1999-2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_MESSAGE_H +#define OSPF6_MESSAGE_H + +#define OSPF6_MESSAGE_BUFSIZ 4096 + +/* Debug option */ +extern unsigned char conf_debug_ospf6_message[]; + +#define OSPF6_ACTION_SEND 0x01 +#define OSPF6_ACTION_RECV 0x02 +#define OSPF6_DEBUG_MESSAGE_SEND 0x01 +#define OSPF6_DEBUG_MESSAGE_RECV 0x02 +#define OSPF6_DEBUG_MESSAGE_SEND_HDR 0x04 +#define OSPF6_DEBUG_MESSAGE_RECV_HDR 0x08 +#define OSPF6_DEBUG_MESSAGE_SEND_BOTH \ + OSPF6_DEBUG_MESSAGE_SEND | OSPF6_DEBUG_MESSAGE_SEND_HDR +#define OSPF6_DEBUG_MESSAGE_RECV_BOTH \ + OSPF6_DEBUG_MESSAGE_RECV | OSPF6_DEBUG_MESSAGE_RECV_HDR + +#define OSPF6_DEBUG_MESSAGE_ON(type, level) \ + (conf_debug_ospf6_message[type] |= (level)) +#define OSPF6_DEBUG_MESSAGE_OFF(type, level) \ + (conf_debug_ospf6_message[type] &= ~(level)) + +#define IS_OSPF6_DEBUG_MESSAGE(t, e) \ + (((OSPF6_DEBUG_MESSAGE_##e) == OSPF6_DEBUG_MESSAGE_RECV_HDR) \ + ? (conf_debug_ospf6_message[t] \ + & (OSPF6_DEBUG_MESSAGE_RECV_BOTH)) \ + : (((OSPF6_DEBUG_MESSAGE_##e) == OSPF6_DEBUG_MESSAGE_SEND_HDR) \ + ? (conf_debug_ospf6_message[t] \ + & (OSPF6_DEBUG_MESSAGE_SEND_BOTH)) \ + : (conf_debug_ospf6_message[t] \ + & (OSPF6_DEBUG_MESSAGE_##e)))) + +#define IS_OSPF6_DEBUG_MESSAGE_ENABLED(type, e) \ + (conf_debug_ospf6_message[type] & (OSPF6_DEBUG_MESSAGE_##e)) + +/* Type */ +#define OSPF6_MESSAGE_TYPE_UNKNOWN 0x0 +#define OSPF6_MESSAGE_TYPE_HELLO 0x1 /* Discover/maintain neighbors */ +#define OSPF6_MESSAGE_TYPE_DBDESC 0x2 /* Summarize database contents */ +#define OSPF6_MESSAGE_TYPE_LSREQ 0x3 /* Database download request */ +#define OSPF6_MESSAGE_TYPE_LSUPDATE 0x4 /* Database update */ +#define OSPF6_MESSAGE_TYPE_LSACK 0x5 /* Flooding acknowledgment */ +#define OSPF6_MESSAGE_TYPE_ALL 0x6 /* For debug option */ +#define OSPF6_MESSAGE_TYPE_MAX 0x6 /* same as OSPF6_MESSAGE_TYPE_ALL */ + +struct ospf6_interface; + +struct ospf6_packet { + struct ospf6_packet *next; + + /* Pointer to data stream. */ + struct stream *s; + + /* IP destination address. */ + struct in6_addr dst; + + /* OSPF6 packet length. */ + uint16_t length; +}; + +/* OSPF packet queue structure. */ +struct ospf6_fifo { + unsigned long count; + + struct ospf6_packet *head; + struct ospf6_packet *tail; +}; + +/* OSPFv3 packet header */ +#define OSPF6_HEADER_SIZE 16U +struct ospf6_header { + uint8_t version; + uint8_t type; + uint16_t length; + in_addr_t router_id; + in_addr_t area_id; + uint16_t checksum; + uint8_t instance_id; + uint8_t reserved; +}; + +#define OSPF6_MESSAGE_END(H) ((caddr_t) (H) + ntohs ((H)->length)) + +/* Hello */ +#define OSPF6_HELLO_MIN_SIZE 20U +struct ospf6_hello { + ifindex_t interface_id; + uint8_t priority; + uint8_t options[3]; + uint16_t hello_interval; + uint16_t dead_interval; + in_addr_t drouter; + in_addr_t bdrouter; + /* Followed by Router-IDs */ +}; + +/* Database Description */ +#define OSPF6_DB_DESC_MIN_SIZE 12U +struct ospf6_dbdesc { + uint8_t reserved1; + uint8_t options[3]; + uint16_t ifmtu; + uint8_t reserved2; + uint8_t bits; + uint32_t seqnum; + /* Followed by LSA Headers */ +}; + +#define OSPF6_DBDESC_MSBIT (0x01) /* master/slave bit */ +#define OSPF6_DBDESC_MBIT (0x02) /* more bit */ +#define OSPF6_DBDESC_IBIT (0x04) /* initial bit */ + +/* Link State Request */ +#define OSPF6_LS_REQ_MIN_SIZE 0U +/* It is just a sequence of entries below */ +#define OSPF6_LSREQ_LSDESC_FIX_SIZE 12U +struct ospf6_lsreq_entry { + uint16_t reserved; /* Must Be Zero */ + uint16_t type; /* LS type */ + in_addr_t id; /* Link State ID */ + in_addr_t adv_router; /* Advertising Router */ +}; + +/* Link State Update */ +#define OSPF6_LS_UPD_MIN_SIZE 4U +struct ospf6_lsupdate { + uint32_t lsa_number; + /* Followed by LSAs */ +}; + +/* LLS is not supported, but used to derive + * offset of Auth_trailer + */ +struct ospf6_lls_hdr { + uint16_t checksum; + uint16_t length; +}; + +/* Link State Acknowledgement */ +#define OSPF6_LS_ACK_MIN_SIZE 0U +/* It is just a sequence of LSA Headers */ + +/* Function definition */ +extern void ospf6_hello_print(struct ospf6_header *, int action); +extern void ospf6_dbdesc_print(struct ospf6_header *, int action); +extern void ospf6_lsreq_print(struct ospf6_header *, int action); +extern void ospf6_lsupdate_print(struct ospf6_header *, int action); +extern void ospf6_lsack_print(struct ospf6_header *, int action); + +extern struct ospf6_fifo *ospf6_fifo_new(void); +extern void ospf6_fifo_flush(struct ospf6_fifo *fifo); +extern void ospf6_fifo_free(struct ospf6_fifo *fifo); + +extern int ospf6_iobuf_size(unsigned int size); +extern void ospf6_message_terminate(void); +extern void ospf6_receive(struct event *thread); + +extern void ospf6_hello_send(struct event *thread); +extern void ospf6_dbdesc_send(struct event *thread); +extern void ospf6_dbdesc_send_newone(struct event *thread); +extern void ospf6_lsreq_send(struct event *thread); +extern void ospf6_lsupdate_send_interface(struct event *thread); +extern void ospf6_lsupdate_send_neighbor(struct event *thread); +extern void ospf6_lsack_send_interface(struct event *thread); +extern void ospf6_lsack_send_neighbor(struct event *thread); + +extern void ospf6_hello_send_addr(struct ospf6_interface *oi, + const struct in6_addr *addr); + +extern int config_write_ospf6_debug_message(struct vty *); +extern void install_element_ospf6_debug_message(void); +extern const char *ospf6_message_type(int type); +#endif /* OSPF6_MESSAGE_H */ diff --git a/ospf6d/ospf6_neighbor.c b/ospf6d/ospf6_neighbor.c new file mode 100644 index 0000000..0e44f2a --- /dev/null +++ b/ospf6d/ospf6_neighbor.c @@ -0,0 +1,1610 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "command.h" +#include "lib/bfd.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_intra.h" +#include "ospf6_flood.h" +#include "ospf6d.h" +#include "ospf6_bfd.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6_lsa.h" +#include "ospf6_spf.h" +#include "ospf6_zebra.h" +#include "ospf6_gr.h" +#include "lib/json.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_NEIGHBOR, "OSPF6 neighbor"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_NEIGHBOR_P2XP_CFG, + "OSPF6 PtP/PtMP neighbor config"); + +static int ospf6_if_p2xp_neighcfg_cmp(const struct ospf6_if_p2xp_neighcfg *a, + const struct ospf6_if_p2xp_neighcfg *b); + +DECLARE_RBTREE_UNIQ(ospf6_if_p2xp_neighcfgs, struct ospf6_if_p2xp_neighcfg, + item, ospf6_if_p2xp_neighcfg_cmp); + +static void p2xp_neigh_refresh(struct ospf6_neighbor *on, uint32_t prev_cost); + +DEFINE_HOOK(ospf6_neighbor_change, + (struct ospf6_neighbor * on, int state, int next_state), + (on, state, next_state)); + +unsigned char conf_debug_ospf6_neighbor = 0; + +const char *const ospf6_neighbor_state_str[] = { + "None", "Down", "Attempt", "Init", "Twoway", + "ExStart", "ExChange", "Loading", "Full", NULL +}; + +const char *const ospf6_neighbor_event_str[] = { + "NoEvent", "HelloReceived", "2-WayReceived", "NegotiationDone", + "ExchangeDone", "LoadingDone", "AdjOK?", "SeqNumberMismatch", + "BadLSReq", "1-WayReceived", "InactivityTimer", +}; + +int ospf6_neighbor_cmp(void *va, void *vb) +{ + struct ospf6_neighbor *ona = (struct ospf6_neighbor *)va; + struct ospf6_neighbor *onb = (struct ospf6_neighbor *)vb; + + if (ona->router_id == onb->router_id) + return 0; + + return (ntohl(ona->router_id) < ntohl(onb->router_id)) ? -1 : 1; +} + +struct ospf6_neighbor *ospf6_neighbor_lookup(uint32_t router_id, + struct ospf6_interface *oi) +{ + struct listnode *n; + struct ospf6_neighbor *on; + + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, n, on)) + if (on->router_id == router_id) + return on; + + return (struct ospf6_neighbor *)NULL; +} + +struct ospf6_neighbor *ospf6_area_neighbor_lookup(struct ospf6_area *area, + uint32_t router_id) +{ + struct ospf6_interface *oi; + struct ospf6_neighbor *nbr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->if_list, node, oi)) { + nbr = ospf6_neighbor_lookup(router_id, oi); + if (nbr) + return nbr; + } + + return NULL; +} + +static void ospf6_neighbor_clear_ls_lists(struct ospf6_neighbor *on) +{ + struct ospf6_lsa *lsa; + struct ospf6_lsa *lsanext; + + ospf6_lsdb_remove_all(on->summary_list); + if (on->last_ls_req) { + ospf6_lsa_unlock(&on->last_ls_req); + on->last_ls_req = NULL; + } + + ospf6_lsdb_remove_all(on->request_list); + for (ALL_LSDB(on->retrans_list, lsa, lsanext)) { + ospf6_decrement_retrans_count(lsa); + ospf6_lsdb_remove(lsa, on->retrans_list); + } +} + +/* create ospf6_neighbor */ +struct ospf6_neighbor *ospf6_neighbor_create(uint32_t router_id, + struct ospf6_interface *oi) +{ + struct ospf6_neighbor *on; + char buf[16]; + int type; + + on = XCALLOC(MTYPE_OSPF6_NEIGHBOR, sizeof(struct ospf6_neighbor)); + inet_ntop(AF_INET, &router_id, buf, sizeof(buf)); + snprintf(on->name, sizeof(on->name), "%s%%%s", buf, oi->interface->name); + on->ospf6_if = oi; + on->state = OSPF6_NEIGHBOR_DOWN; + on->state_change = 0; + monotime(&on->last_changed); + on->router_id = router_id; + + on->summary_list = ospf6_lsdb_create(on); + on->request_list = ospf6_lsdb_create(on); + on->retrans_list = ospf6_lsdb_create(on); + + on->dbdesc_list = ospf6_lsdb_create(on); + on->lsupdate_list = ospf6_lsdb_create(on); + on->lsack_list = ospf6_lsdb_create(on); + + for (type = 0; type < OSPF6_MESSAGE_TYPE_MAX; type++) { + on->seqnum_l[type] = 0; + on->seqnum_h[type] = 0; + } + + on->auth_present = false; + + listnode_add_sort(oi->neighbor_list, on); + + ospf6_bfd_info_nbr_create(oi, on); + return on; +} + +void ospf6_neighbor_delete(struct ospf6_neighbor *on) +{ + if (on->p2xp_cfg) + on->p2xp_cfg->active = NULL; + + ospf6_neighbor_clear_ls_lists(on); + + ospf6_lsdb_remove_all(on->dbdesc_list); + ospf6_lsdb_remove_all(on->lsupdate_list); + ospf6_lsdb_remove_all(on->lsack_list); + + ospf6_lsdb_delete(on->summary_list); + ospf6_lsdb_delete(on->request_list); + ospf6_lsdb_delete(on->retrans_list); + + ospf6_lsdb_delete(on->dbdesc_list); + ospf6_lsdb_delete(on->lsupdate_list); + ospf6_lsdb_delete(on->lsack_list); + + EVENT_OFF(on->inactivity_timer); + + EVENT_OFF(on->last_dbdesc_release_timer); + + EVENT_OFF(on->thread_send_dbdesc); + EVENT_OFF(on->thread_send_lsreq); + EVENT_OFF(on->thread_send_lsupdate); + EVENT_OFF(on->thread_send_lsack); + EVENT_OFF(on->thread_exchange_done); + EVENT_OFF(on->thread_adj_ok); + EVENT_OFF(on->event_loading_done); + + EVENT_OFF(on->gr_helper_info.t_grace_timer); + + bfd_sess_free(&on->bfd_session); + XFREE(MTYPE_OSPF6_NEIGHBOR, on); +} + +void ospf6_neighbor_lladdr_set(struct ospf6_neighbor *on, + const struct in6_addr *addr) +{ + if (IPV6_ADDR_SAME(addr, &on->linklocal_addr)) + return; + + memcpy(&on->linklocal_addr, addr, sizeof(struct in6_addr)); + + if (on->ospf6_if->type == OSPF_IFTYPE_POINTOPOINT || + on->ospf6_if->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + uint32_t prev_cost = ospf6_neighbor_cost(on); + + p2xp_neigh_refresh(on, prev_cost); + } +} + +static void ospf6_neighbor_state_change(uint8_t next_state, + struct ospf6_neighbor *on, int event) +{ + uint8_t prev_state; + + prev_state = on->state; + on->state = next_state; + + if (prev_state == next_state) + return; + + on->state_change++; + monotime(&on->last_changed); + + /* log */ + if (IS_OSPF6_DEBUG_NEIGHBOR(STATE)) { + zlog_debug("Neighbor state change %s (Router-ID: %pI4): [%s]->[%s] (%s)", + on->name, &on->router_id, + ospf6_neighbor_state_str[prev_state], + ospf6_neighbor_state_str[next_state], + ospf6_neighbor_event_string(event)); + } + + /* Optionally notify about adjacency changes */ + if (CHECK_FLAG(on->ospf6_if->area->ospf6->config_flags, + OSPF6_LOG_ADJACENCY_CHANGES) && + (CHECK_FLAG(on->ospf6_if->area->ospf6->config_flags, + OSPF6_LOG_ADJACENCY_DETAIL) || + (next_state == OSPF6_NEIGHBOR_FULL) || (next_state < prev_state))) + zlog_notice("AdjChg: Nbr %pI4(%s) on %s: %s -> %s (%s)", + &on->router_id, + vrf_id_to_name(on->ospf6_if->interface->vrf->vrf_id), + on->name, ospf6_neighbor_state_str[prev_state], + ospf6_neighbor_state_str[next_state], + ospf6_neighbor_event_string(event)); + + if (prev_state == OSPF6_NEIGHBOR_FULL || + next_state == OSPF6_NEIGHBOR_FULL) { + if (!OSPF6_GR_IS_ACTIVE_HELPER(on)) { + OSPF6_ROUTER_LSA_SCHEDULE(on->ospf6_if->area); + if (on->ospf6_if->state == OSPF6_INTERFACE_DR) { + OSPF6_NETWORK_LSA_SCHEDULE(on->ospf6_if); + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_TRANSIT( + on->ospf6_if); + } + } + if (next_state == OSPF6_NEIGHBOR_FULL) + on->ospf6_if->area->intra_prefix_originate = 1; + + if (!OSPF6_GR_IS_ACTIVE_HELPER(on)) + OSPF6_INTRA_PREFIX_LSA_SCHEDULE_STUB(on->ospf6_if->area); + + if ((prev_state == OSPF6_NEIGHBOR_LOADING || + prev_state == OSPF6_NEIGHBOR_EXCHANGE) && + next_state == OSPF6_NEIGHBOR_FULL) { + OSPF6_AS_EXTERN_LSA_SCHEDULE(on->ospf6_if); + on->ospf6_if->area->full_nbrs++; + } + + if (prev_state == OSPF6_NEIGHBOR_FULL) + on->ospf6_if->area->full_nbrs--; + } + + if ((prev_state == OSPF6_NEIGHBOR_EXCHANGE || + prev_state == OSPF6_NEIGHBOR_LOADING) && + (next_state != OSPF6_NEIGHBOR_EXCHANGE && + next_state != OSPF6_NEIGHBOR_LOADING)) + ospf6_maxage_remove(on->ospf6_if->area->ospf6); + + hook_call(ospf6_neighbor_change, on, next_state, prev_state); + ospf6_bfd_trigger_event(on, prev_state, next_state); +} + +/* RFC2328 section 10.4 */ +static int need_adjacency(struct ospf6_neighbor *on) +{ + if (on->ospf6_if->state == OSPF6_INTERFACE_POINTTOPOINT || + on->ospf6_if->state == OSPF6_INTERFACE_POINTTOMULTIPOINT || + on->ospf6_if->state == OSPF6_INTERFACE_DR || + on->ospf6_if->state == OSPF6_INTERFACE_BDR) + return 1; + + if (on->ospf6_if->drouter == on->router_id || + on->ospf6_if->bdrouter == on->router_id) + return 1; + + return 0; +} + +void hello_received(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *HelloReceived*", on->name); + + /* reset Inactivity Timer */ + EVENT_OFF(on->inactivity_timer); + event_add_timer(master, inactivity_timer, on, + on->ospf6_if->dead_interval, &on->inactivity_timer); + + if (on->state <= OSPF6_NEIGHBOR_DOWN) + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_INIT, on, + OSPF6_NEIGHBOR_EVENT_HELLO_RCVD); +} + +void twoway_received(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state > OSPF6_NEIGHBOR_INIT) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *2Way-Received*", on->name); + + event_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); + + if (!need_adjacency(on)) { + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_TWOWAY, on, + OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD); + return; + } + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, + OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); + + EVENT_OFF(on->thread_send_dbdesc); + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); +} + +void negotiation_done(struct event *thread) +{ + struct ospf6_neighbor *on; + struct ospf6_lsa *lsa, *lsanext; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state != OSPF6_NEIGHBOR_EXSTART) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *NegotiationDone*", on->name); + + /* clear ls-list */ + ospf6_neighbor_clear_ls_lists(on); + + /* Interface scoped LSAs */ + for (ALL_LSDB(on->ospf6_if->lsdb, lsa, lsanext)) { + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + ospf6_increment_retrans_count(lsa); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); + } else + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); + } + + /* Area scoped LSAs */ + for (ALL_LSDB(on->ospf6_if->area->lsdb, lsa, lsanext)) { + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + ospf6_increment_retrans_count(lsa); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); + } else + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); + } + + /* AS scoped LSAs */ + for (ALL_LSDB(on->ospf6_if->area->ospf6->lsdb, lsa, lsanext)) { + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + ospf6_increment_retrans_count(lsa); + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->retrans_list); + } else + ospf6_lsdb_add(ospf6_lsa_copy(lsa), on->summary_list); + } + + UNSET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXCHANGE, on, + OSPF6_NEIGHBOR_EVENT_NEGOTIATION_DONE); +} + +static void ospf6_neighbor_last_dbdesc_release(struct event *thread) +{ + struct ospf6_neighbor *on = EVENT_ARG(thread); + + assert(on); + memset(&on->dbdesc_last, 0, sizeof(struct ospf6_dbdesc)); +} + +void exchange_done(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state != OSPF6_NEIGHBOR_EXCHANGE) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *ExchangeDone*", on->name); + + EVENT_OFF(on->thread_send_dbdesc); + ospf6_lsdb_remove_all(on->dbdesc_list); + + /* RFC 2328 (10.8): Release the last dbdesc after dead_interval */ + if (!CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT)) { + EVENT_OFF(on->last_dbdesc_release_timer); + event_add_timer(master, ospf6_neighbor_last_dbdesc_release, on, + on->ospf6_if->dead_interval, + &on->last_dbdesc_release_timer); + } + + if (on->request_list->count == 0) + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_FULL, on, + OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE); + else { + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_LOADING, on, + OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE); + + event_add_event(master, ospf6_lsreq_send, on, 0, + &on->thread_send_lsreq); + } +} + +/* Check loading state. */ +void ospf6_check_nbr_loading(struct ospf6_neighbor *on) +{ + /* RFC2328 Section 10.9: When the neighbor responds to these requests + with the proper Link State Update packet(s), the Link state request + list is truncated and a new Link State Request packet is sent. + */ + if ((on->state == OSPF6_NEIGHBOR_LOADING) || + (on->state == OSPF6_NEIGHBOR_EXCHANGE)) { + if (on->request_list->count == 0) + event_add_event(master, loading_done, on, 0, + &on->event_loading_done); + else if (on->last_ls_req == NULL) { + EVENT_OFF(on->thread_send_lsreq); + event_add_event(master, ospf6_lsreq_send, on, 0, + &on->thread_send_lsreq); + } + } +} + +void loading_done(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state != OSPF6_NEIGHBOR_LOADING) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *LoadingDone*", on->name); + + assert(on->request_list->count == 0); + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_FULL, on, + OSPF6_NEIGHBOR_EVENT_LOADING_DONE); +} + +void adj_ok(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *AdjOK?*", on->name); + + if (on->state == OSPF6_NEIGHBOR_TWOWAY && need_adjacency(on)) { + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, + OSPF6_NEIGHBOR_EVENT_ADJ_OK); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); + + EVENT_OFF(on->thread_send_dbdesc); + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); + + } else if (on->state >= OSPF6_NEIGHBOR_EXSTART && !need_adjacency(on)) { + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_TWOWAY, on, + OSPF6_NEIGHBOR_EVENT_ADJ_OK); + ospf6_neighbor_clear_ls_lists(on); + } +} + +void seqnumber_mismatch(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state < OSPF6_NEIGHBOR_EXCHANGE) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *SeqNumberMismatch*", on->name); + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, + OSPF6_NEIGHBOR_EVENT_SEQNUMBER_MISMATCH); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); + + ospf6_neighbor_clear_ls_lists(on); + + EVENT_OFF(on->thread_send_dbdesc); + on->dbdesc_seqnum++; /* Incr seqnum as per RFC2328, sec 10.3 */ + + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); +} + +void bad_lsreq(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state < OSPF6_NEIGHBOR_EXCHANGE) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *BadLSReq*", on->name); + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_EXSTART, on, + OSPF6_NEIGHBOR_EVENT_BAD_LSREQ); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT); + SET_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT); + + ospf6_neighbor_clear_ls_lists(on); + + EVENT_OFF(on->thread_send_dbdesc); + on->dbdesc_seqnum++; /* Incr seqnum as per RFC2328, sec 10.3 */ + + event_add_event(master, ospf6_dbdesc_send, on, 0, + &on->thread_send_dbdesc); +} + +void oneway_received(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (on->state < OSPF6_NEIGHBOR_TWOWAY) + return; + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *1Way-Received*", on->name); + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_INIT, on, + OSPF6_NEIGHBOR_EVENT_ONEWAY_RCVD); + event_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); + + ospf6_neighbor_clear_ls_lists(on); + + EVENT_OFF(on->thread_send_dbdesc); + EVENT_OFF(on->thread_send_lsreq); + EVENT_OFF(on->thread_send_lsupdate); + EVENT_OFF(on->thread_send_lsack); + EVENT_OFF(on->thread_exchange_done); + EVENT_OFF(on->thread_adj_ok); +} + +void inactivity_timer(struct event *thread) +{ + struct ospf6_neighbor *on; + + on = (struct ospf6_neighbor *)EVENT_ARG(thread); + assert(on); + + if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + zlog_debug("Neighbor Event %s: *InactivityTimer*", on->name); + + on->drouter = on->prev_drouter = 0; + on->bdrouter = on->prev_bdrouter = 0; + + if (!OSPF6_GR_IS_ACTIVE_HELPER(on)) { + on->drouter = on->prev_drouter = 0; + on->bdrouter = on->prev_bdrouter = 0; + + ospf6_neighbor_state_change(OSPF6_NEIGHBOR_DOWN, on, + OSPF6_NEIGHBOR_EVENT_INACTIVITY_TIMER); + event_add_event(master, neighbor_change, on->ospf6_if, 0, NULL); + + listnode_delete(on->ospf6_if->neighbor_list, on); + ospf6_neighbor_delete(on); + + } else { + if (IS_DEBUG_OSPF6_GR) + zlog_debug("%s, Acting as HELPER for this neighbour, So restart the dead timer.", + __PRETTY_FUNCTION__); + + event_add_timer(master, inactivity_timer, on, + on->ospf6_if->dead_interval, + &on->inactivity_timer); + } +} + +/* P2P/P2MP stuff */ + +uint32_t ospf6_neighbor_cost(struct ospf6_neighbor *on) +{ + if (on->p2xp_cfg && on->p2xp_cfg->cfg_cost) + return on->p2xp_cfg->cost; + return on->ospf6_if->cost; +} + +static int ospf6_if_p2xp_neighcfg_cmp(const struct ospf6_if_p2xp_neighcfg *a, + const struct ospf6_if_p2xp_neighcfg *b) +{ + return IPV6_ADDR_CMP(&a->addr, &b->addr); +} + +struct ospf6_if_p2xp_neighcfg *ospf6_if_p2xp_find(struct ospf6_interface *oi, + const struct in6_addr *addr) +{ + struct ospf6_if_p2xp_neighcfg ref; + + if (!oi) + return NULL; + + ref.addr = *addr; + return ospf6_if_p2xp_neighcfgs_find(&oi->p2xp_neighs, &ref); +} + +static struct ospf6_if_p2xp_neighcfg * +ospf6_if_p2xp_get(struct ospf6_interface *oi, const struct in6_addr *addr) +{ + struct ospf6_if_p2xp_neighcfg ref, *ret; + + if (!oi) + return NULL; + + ref.addr = *addr; + ret = ospf6_if_p2xp_neighcfgs_find(&oi->p2xp_neighs, &ref); + if (!ret) { + ret = XCALLOC(MTYPE_OSPF6_NEIGHBOR_P2XP_CFG, sizeof(*ret)); + ret->addr = *addr; + ret->ospf6_if = oi; + + ospf6_if_p2xp_neighcfgs_add(&oi->p2xp_neighs, ret); + } + + return ret; +} + +static void ospf6_if_p2xp_destroy(struct ospf6_if_p2xp_neighcfg *p2xp_cfg) +{ + EVENT_OFF(p2xp_cfg->t_unicast_hello); + ospf6_if_p2xp_neighcfgs_del(&p2xp_cfg->ospf6_if->p2xp_neighs, p2xp_cfg); + + XFREE(MTYPE_OSPF6_NEIGHBOR_P2XP_CFG, p2xp_cfg); +} + +static void p2xp_neigh_refresh(struct ospf6_neighbor *on, uint32_t prev_cost) +{ + if (on->p2xp_cfg) + on->p2xp_cfg->active = NULL; + on->p2xp_cfg = ospf6_if_p2xp_find(on->ospf6_if, &on->linklocal_addr); + if (on->p2xp_cfg) + on->p2xp_cfg->active = on; + + if (ospf6_neighbor_cost(on) != prev_cost) + OSPF6_ROUTER_LSA_SCHEDULE(on->ospf6_if->area); +} + +/* vty functions */ + +#ifndef VTYSH_EXTRACT_PL +#include "ospf6d/ospf6_neighbor_clippy.c" +#endif + +DEFPY (ipv6_ospf6_p2xp_neigh, + ipv6_ospf6_p2xp_neigh_cmd, + "[no] ipv6 ospf6 neighbor X:X::X:X", + NO_STR + IP6_STR + OSPF6_STR + "Configure static neighbor\n" + "Neighbor link-local address\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + if (!oi) { + if (no) + return CMD_SUCCESS; + oi = ospf6_interface_create(ifp); + } + + if (no) { + struct ospf6_neighbor *on; + uint32_t prev_cost = 0; + + p2xp_cfg = ospf6_if_p2xp_find(oi, &neighbor); + if (!p2xp_cfg) + return CMD_SUCCESS; + + on = p2xp_cfg->active; + if (on) + prev_cost = ospf6_neighbor_cost(on); + + p2xp_cfg->active = NULL; + ospf6_if_p2xp_destroy(p2xp_cfg); + + if (on) { + on->p2xp_cfg = NULL; + p2xp_neigh_refresh(on, prev_cost); + } + return CMD_SUCCESS; + } + + (void)ospf6_if_p2xp_get(oi, &neighbor); + return CMD_SUCCESS; +} + +DEFPY (ipv6_ospf6_p2xp_neigh_cost, + ipv6_ospf6_p2xp_neigh_cost_cmd, + "[no] ipv6 ospf6 neighbor X:X::X:X cost (1-65535)", + NO_STR + IP6_STR + OSPF6_STR + "Configure static neighbor\n" + "Neighbor link-local address\n" + "Outgoing metric for this neighbor\n" + "Outgoing metric for this neighbor\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + uint32_t prev_cost = 0; + + if (!oi) { + if (no) + return CMD_SUCCESS; + oi = ospf6_interface_create(ifp); + } + + p2xp_cfg = ospf6_if_p2xp_get(oi, &neighbor); + + if (p2xp_cfg->active) + prev_cost = ospf6_neighbor_cost(p2xp_cfg->active); + + if (no) { + p2xp_cfg->cfg_cost = false; + p2xp_cfg->cost = 0; + } else { + p2xp_cfg->cfg_cost = true; + p2xp_cfg->cost = cost; + } + + if (p2xp_cfg->active) + p2xp_neigh_refresh(p2xp_cfg->active, prev_cost); + return CMD_SUCCESS; +} + +static void p2xp_unicast_hello_send(struct event *event); + +static void p2xp_unicast_hello_sched(struct ospf6_if_p2xp_neighcfg *p2xp_cfg) +{ + if (!p2xp_cfg->poll_interval || + (p2xp_cfg->ospf6_if->state != OSPF6_INTERFACE_POINTTOMULTIPOINT && + p2xp_cfg->ospf6_if->state != OSPF6_INTERFACE_POINTTOPOINT)) + /* state check covers DOWN state too */ + EVENT_OFF(p2xp_cfg->t_unicast_hello); + else + event_add_timer(master, p2xp_unicast_hello_send, p2xp_cfg, + p2xp_cfg->poll_interval, + &p2xp_cfg->t_unicast_hello); +} + +void ospf6_if_p2xp_up(struct ospf6_interface *oi) +{ + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + frr_each (ospf6_if_p2xp_neighcfgs, &oi->p2xp_neighs, p2xp_cfg) + p2xp_unicast_hello_sched(p2xp_cfg); +} + +static void p2xp_unicast_hello_send(struct event *event) +{ + struct ospf6_if_p2xp_neighcfg *p2xp_cfg = EVENT_ARG(event); + struct ospf6_interface *oi = p2xp_cfg->ospf6_if; + + if (oi->state != OSPF6_INTERFACE_POINTTOPOINT && + oi->state != OSPF6_INTERFACE_POINTTOMULTIPOINT) + return; + + p2xp_unicast_hello_sched(p2xp_cfg); + + if (p2xp_cfg->active && p2xp_cfg->active->state >= OSPF6_NEIGHBOR_INIT) + return; + + ospf6_hello_send_addr(oi, &p2xp_cfg->addr); +} + +DEFPY (ipv6_ospf6_p2xp_neigh_poll_interval, + ipv6_ospf6_p2xp_neigh_poll_interval_cmd, + "[no] ipv6 ospf6 neighbor X:X::X:X poll-interval (1-65535)", + NO_STR + IP6_STR + OSPF6_STR + "Configure static neighbor\n" + "Neighbor link-local address\n" + "Send unicast hellos to neighbor when down\n" + "Unicast hello interval when down (seconds)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf6_interface *oi = ifp->info; + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + if (!oi) { + if (no) + return CMD_SUCCESS; + oi = ospf6_interface_create(ifp); + } + if (no) + poll_interval = 0; + + p2xp_cfg = ospf6_if_p2xp_get(oi, &neighbor); + p2xp_cfg->poll_interval = poll_interval; + + p2xp_unicast_hello_sched(p2xp_cfg); + return CMD_SUCCESS; +} + +/* show neighbor structure */ +static void ospf6_neighbor_show(struct vty *vty, struct ospf6_neighbor *on, + json_object *json_array, bool use_json) +{ + char router_id[16]; + char duration[64]; + struct timeval res; + char nstate[16]; + char deadtime[64]; + long h, m, s; + json_object *json_route; + + /* Router-ID (Name) */ + inet_ntop(AF_INET, &on->router_id, router_id, sizeof(router_id)); +#ifdef HAVE_GETNAMEINFO + { + } +#endif /*HAVE_GETNAMEINFO*/ + + /* Dead time */ + h = m = s = 0; + if (on->inactivity_timer) { + s = monotime_until(&on->inactivity_timer->u.sands, NULL) / + 1000000LL; + h = s / 3600; + s -= h * 3600; + m = s / 60; + s -= m * 60; + } + snprintf(deadtime, sizeof(deadtime), "%02ld:%02ld:%02ld", h, m, s); + + /* Neighbor State */ + if (on->ospf6_if->type == OSPF_IFTYPE_POINTOPOINT) + snprintf(nstate, sizeof(nstate), "PointToPoint"); + else if (on->ospf6_if->type == OSPF_IFTYPE_POINTOMULTIPOINT) + snprintf(nstate, sizeof(nstate), "PtMultipoint"); + else { + if (on->router_id == on->drouter) + snprintf(nstate, sizeof(nstate), "DR"); + else if (on->router_id == on->bdrouter) + snprintf(nstate, sizeof(nstate), "BDR"); + else + snprintf(nstate, sizeof(nstate), "DROther"); + } + + /* Duration */ + monotime_since(&on->last_changed, &res); + timerstring(&res, duration, sizeof(duration)); + + /* + vty_out (vty, "%-15s %3d %11s %6s/%-12s %11s %s[%s]\n", + "Neighbor ID", "Pri", "DeadTime", "State", "IfState", + "Duration", "I/F", "State"); + */ + if (use_json) { + json_route = json_object_new_object(); + + json_object_string_add(json_route, "neighborId", router_id); + json_object_int_add(json_route, "priority", on->priority); + json_object_string_add(json_route, "deadTime", deadtime); + json_object_string_add(json_route, "state", + ospf6_neighbor_state_str[on->state]); + json_object_string_add(json_route, "ifState", nstate); + json_object_string_add(json_route, "duration", duration); + json_object_string_add(json_route, "interfaceName", + on->ospf6_if->interface->name); + json_object_string_add(json_route, "interfaceState", + ospf6_interface_state_str + [on->ospf6_if->state]); + + json_object_array_add(json_array, json_route); + } else + vty_out(vty, "%-15s %3d %11s %8s/%-12s %11s %s[%s]\n", + router_id, on->priority, deadtime, + ospf6_neighbor_state_str[on->state], nstate, duration, + on->ospf6_if->interface->name, + ospf6_interface_state_str[on->ospf6_if->state]); +} + +static void ospf6_neighbor_show_drchoice(struct vty *vty, + struct ospf6_neighbor *on, + json_object *json_array, bool use_json) +{ + char router_id[16]; + char drouter[16], bdrouter[16]; + char duration[64]; + struct timeval now, res; + json_object *json_route; + + /* + vty_out (vty, "%-15s %6s/%-11s %-15s %-15s %s[%s]\n", + "RouterID", "State", "Duration", "DR", "BDR", "I/F", + "State"); + */ + + inet_ntop(AF_INET, &on->router_id, router_id, sizeof(router_id)); + inet_ntop(AF_INET, &on->drouter, drouter, sizeof(drouter)); + inet_ntop(AF_INET, &on->bdrouter, bdrouter, sizeof(bdrouter)); + + monotime(&now); + timersub(&now, &on->last_changed, &res); + timerstring(&res, duration, sizeof(duration)); + + if (use_json) { + json_route = json_object_new_object(); + json_object_string_add(json_route, "routerId", router_id); + json_object_string_add(json_route, "state", + ospf6_neighbor_state_str[on->state]); + json_object_string_add(json_route, "duration", duration); + json_object_string_add(json_route, "dRouter", drouter); + json_object_string_add(json_route, "bdRouter", bdrouter); + json_object_string_add(json_route, "interfaceName", + on->ospf6_if->interface->name); + json_object_string_add(json_route, "interfaceState", + ospf6_interface_state_str + [on->ospf6_if->state]); + + json_object_array_add(json_array, json_route); + } else + vty_out(vty, "%-15s %8s/%-11s %-15s %-15s %s[%s]\n", router_id, + ospf6_neighbor_state_str[on->state], duration, drouter, + bdrouter, on->ospf6_if->interface->name, + ospf6_interface_state_str[on->ospf6_if->state]); +} + +static void ospf6_neighbor_show_detail(struct vty *vty, + struct ospf6_neighbor *on, + json_object *json, bool use_json) +{ + char drouter[16], bdrouter[16]; + char linklocal_addr[64], duration[32]; + struct timeval now, res; + struct ospf6_lsa *lsa, *lsanext; + json_object *json_neighbor; + json_object *json_array; + char db_desc_str[20]; + + inet_ntop(AF_INET6, &on->linklocal_addr, linklocal_addr, + sizeof(linklocal_addr)); + inet_ntop(AF_INET, &on->drouter, drouter, sizeof(drouter)); + inet_ntop(AF_INET, &on->bdrouter, bdrouter, sizeof(bdrouter)); + + monotime(&now); + timersub(&now, &on->last_changed, &res); + timerstring(&res, duration, sizeof(duration)); + + if (use_json) { + json_neighbor = json_object_new_object(); + json_object_string_add(json_neighbor, "area", + on->ospf6_if->area->name); + json_object_string_add(json_neighbor, "interface", + on->ospf6_if->interface->name); + json_object_int_add(json_neighbor, "interfaceIndex", + on->ospf6_if->interface->ifindex); + json_object_int_add(json_neighbor, "neighborInterfaceIndex", + on->ifindex); + json_object_string_addf(json_neighbor, "localLinkLocalAddress", + "%pI6", on->ospf6_if->linklocal_addr); + json_object_string_add(json_neighbor, "linkLocalAddress", + linklocal_addr); + json_object_string_add(json_neighbor, "neighborState", + ospf6_neighbor_state_str[on->state]); + json_object_string_add(json_neighbor, "neighborStateDuration", + duration); + json_object_string_add(json_neighbor, "neighborDRouter", + drouter); + json_object_string_add(json_neighbor, "neighborBdRouter", + bdrouter); + snprintf(db_desc_str, sizeof(db_desc_str), "%s%s%s", + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT) + ? "Initial " + : ""), + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT) ? "More" + : ""), + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT) + ? "Master" + : "Slave")); + json_object_string_add(json_neighbor, "dbDescStatus", + db_desc_str); + + json_object_int_add(json_neighbor, "dbDescSeqNumber", + (unsigned long)ntohl(on->dbdesc_seqnum)); + + json_array = json_object_new_array(); + json_object_int_add(json_neighbor, "summaryListCount", + on->summary_list->count); + for (ALL_LSDB(on->summary_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "summaryListLsa", + json_array); + + json_array = json_object_new_array(); + json_object_int_add(json_neighbor, "requestListCount", + on->request_list->count); + for (ALL_LSDB(on->request_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "requestListLsa", + json_array); + + json_array = json_object_new_array(); + json_object_int_add(json_neighbor, "reTransListCount", + on->retrans_list->count); + for (ALL_LSDB(on->retrans_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "reTransListLsa", + json_array); + + + timerclear(&res); + if (event_is_scheduled(on->thread_send_dbdesc)) + timersub(&on->thread_send_dbdesc->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + json_object_int_add(json_neighbor, "pendingLsaDbDescCount", + on->dbdesc_list->count); + json_object_string_add(json_neighbor, "pendingLsaDbDescTime", + duration); + json_object_string_add(json_neighbor, "dbDescSendThread", + (event_is_scheduled(on->thread_send_dbdesc) + ? "on" + : "off")); + json_array = json_object_new_array(); + for (ALL_LSDB(on->dbdesc_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "pendingLsaDbDesc", + json_array); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsreq)) + timersub(&on->thread_send_lsreq->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + json_object_int_add(json_neighbor, "pendingLsaLsReqCount", + on->request_list->count); + json_object_string_add(json_neighbor, "pendingLsaLsReqTime", + duration); + json_object_string_add(json_neighbor, "lsReqSendThread", + (event_is_scheduled(on->thread_send_lsreq) + ? "on" + : "off")); + json_array = json_object_new_array(); + for (ALL_LSDB(on->request_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "pendingLsaLsReq", + json_array); + + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsupdate)) + timersub(&on->thread_send_lsupdate->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + json_object_int_add(json_neighbor, "pendingLsaLsUpdateCount", + on->lsupdate_list->count); + json_object_string_add(json_neighbor, "pendingLsaLsUpdateTime", + duration); + json_object_string_add(json_neighbor, "lsUpdateSendThread", + (event_is_scheduled( + on->thread_send_lsupdate) + ? "on" + : "off")); + json_array = json_object_new_array(); + for (ALL_LSDB(on->lsupdate_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "pendingLsaLsUpdate", + json_array); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsack)) + timersub(&on->thread_send_lsack->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + json_object_int_add(json_neighbor, "pendingLsaLsAckCount", + on->lsack_list->count); + json_object_string_add(json_neighbor, "pendingLsaLsAckTime", + duration); + json_object_string_add(json_neighbor, "lsAckSendThread", + (event_is_scheduled(on->thread_send_lsack) + ? "on" + : "off")); + json_array = json_object_new_array(); + for (ALL_LSDB(on->lsack_list, lsa, lsanext)) + json_object_array_add(json_array, + json_object_new_string(lsa->name)); + json_object_object_add(json_neighbor, "pendingLsaLsAck", + json_array); + + bfd_sess_show(vty, json_neighbor, on->bfd_session); + + if (on->auth_present == true) { + json_object_string_add(json_neighbor, "authStatus", + "enabled"); + json_object_int_add(json_neighbor, + "recvdHelloHigherSeqNo", + on->seqnum_h[OSPF6_MESSAGE_TYPE_HELLO]); + json_object_int_add(json_neighbor, + "recvdHelloLowerSeqNo", + on->seqnum_l[OSPF6_MESSAGE_TYPE_HELLO]); + json_object_int_add(json_neighbor, + "recvdDBDescHigherSeqNo", + on->seqnum_h[OSPF6_MESSAGE_TYPE_DBDESC]); + json_object_int_add(json_neighbor, + "recvdDBDescLowerSeqNo", + on->seqnum_l[OSPF6_MESSAGE_TYPE_DBDESC]); + json_object_int_add(json_neighbor, + "recvdLSReqHigherSeqNo", + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSREQ]); + json_object_int_add(json_neighbor, + "recvdLSReqLowerSeqNo", + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSREQ]); + json_object_int_add(json_neighbor, + "recvdLSUpdHigherSeqNo", + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSUPDATE]); + json_object_int_add(json_neighbor, + "recvdLSUpdLowerSeqNo", + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSUPDATE]); + json_object_int_add(json_neighbor, + "recvdLSAckHigherSeqNo", + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSACK]); + json_object_int_add(json_neighbor, + "recvdLSAckLowerSeqNo", + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSACK]); + } else + json_object_string_add(json_neighbor, "authStatus", + "disabled"); + + json_object_object_add(json, on->name, json_neighbor); + + } else { + vty_out(vty, " Neighbor %s\n", on->name); + vty_out(vty, " Area %s via interface %s (ifindex %d)\n", + on->ospf6_if->area->name, on->ospf6_if->interface->name, + on->ospf6_if->interface->ifindex); + vty_out(vty, " His IfIndex: %d Link-local address: %s\n", + on->ifindex, linklocal_addr); + vty_out(vty, " State %s for a duration of %s\n", + ospf6_neighbor_state_str[on->state], duration); + vty_out(vty, " His choice of DR/BDR %s/%s, Priority %d\n", + drouter, bdrouter, on->priority); + vty_out(vty, " DbDesc status: %s%s%s SeqNum: %#lx\n", + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_IBIT) + ? "Initial " + : ""), + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MBIT) ? "More " + : ""), + (CHECK_FLAG(on->dbdesc_bits, OSPF6_DBDESC_MSBIT) + ? "Master" + : "Slave"), + (unsigned long)ntohl(on->dbdesc_seqnum)); + + vty_out(vty, " Summary-List: %d LSAs\n", + on->summary_list->count); + for (ALL_LSDB(on->summary_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + vty_out(vty, " Request-List: %d LSAs\n", + on->request_list->count); + for (ALL_LSDB(on->request_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + vty_out(vty, " Retrans-List: %d LSAs\n", + on->retrans_list->count); + for (ALL_LSDB(on->retrans_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_dbdesc)) + timersub(&on->thread_send_dbdesc->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for DbDesc in Time %s [thread %s]\n", + on->dbdesc_list->count, duration, + (event_is_scheduled(on->thread_send_dbdesc) ? "on" + : "off")); + for (ALL_LSDB(on->dbdesc_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsreq)) + timersub(&on->thread_send_lsreq->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for LSReq in Time %s [thread %s]\n", + on->request_list->count, duration, + (event_is_scheduled(on->thread_send_lsreq) ? "on" + : "off")); + for (ALL_LSDB(on->request_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsupdate)) + timersub(&on->thread_send_lsupdate->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for LSUpdate in Time %s [thread %s]\n", + on->lsupdate_list->count, duration, + (event_is_scheduled(on->thread_send_lsupdate) ? "on" + : "off")); + for (ALL_LSDB(on->lsupdate_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + timerclear(&res); + if (event_is_scheduled(on->thread_send_lsack)) + timersub(&on->thread_send_lsack->u.sands, &now, &res); + timerstring(&res, duration, sizeof(duration)); + vty_out(vty, + " %d Pending LSAs for LSAck in Time %s [thread %s]\n", + on->lsack_list->count, duration, + (event_is_scheduled(on->thread_send_lsack) ? "on" + : "off")); + for (ALL_LSDB(on->lsack_list, lsa, lsanext)) + vty_out(vty, " %s\n", lsa->name); + + bfd_sess_show(vty, NULL, on->bfd_session); + + if (on->auth_present == true) { + vty_out(vty, " Authentication header present\n"); + vty_out(vty, + "\t\t\t hello DBDesc LSReq LSUpd LSAck\n"); + vty_out(vty, + " Higher sequence no 0x%-10X 0x%-10X 0x%-10X 0x%-10X 0x%-10X\n", + on->seqnum_h[OSPF6_MESSAGE_TYPE_HELLO], + on->seqnum_h[OSPF6_MESSAGE_TYPE_DBDESC], + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSREQ], + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSUPDATE], + on->seqnum_h[OSPF6_MESSAGE_TYPE_LSACK]); + vty_out(vty, + " Lower sequence no 0x%-10X 0x%-10X 0x%-10X 0x%-10X 0x%-10X\n", + on->seqnum_l[OSPF6_MESSAGE_TYPE_HELLO], + on->seqnum_l[OSPF6_MESSAGE_TYPE_DBDESC], + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSREQ], + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSUPDATE], + on->seqnum_l[OSPF6_MESSAGE_TYPE_LSACK]); + } else + vty_out(vty, " Authentication header not present\n"); + } +} + +static void ospf6_neighbor_show_detail_common(struct vty *vty, + struct ospf6 *ospf6, bool uj, + bool detail, bool drchoice) +{ + struct ospf6_neighbor *on; + struct ospf6_interface *oi; + struct ospf6_area *oa; + struct listnode *i, *j, *k; + json_object *json = NULL; + json_object *json_array = NULL; + void (*showfunc)(struct vty *, struct ospf6_neighbor *, + json_object *json, bool use_json); + + if (detail) + showfunc = ospf6_neighbor_show_detail; + else if (drchoice) + showfunc = ospf6_neighbor_show_drchoice; + else + showfunc = ospf6_neighbor_show; + + if (uj) { + json = json_object_new_object(); + json_array = json_object_new_array(); + } else { + if (showfunc == ospf6_neighbor_show) + vty_out(vty, "%-15s %3s %11s %8s/%-12s %11s %s[%s]\n", + "Neighbor ID", "Pri", "DeadTime", "State", + "IfState", "Duration", "I/F", "State"); + else if (showfunc == ospf6_neighbor_show_drchoice) + vty_out(vty, "%-15s %8s/%-11s %-15s %-15s %s[%s]\n", + "RouterID", "State", "Duration", "DR", "BDR", + "I/F", "State"); + } + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) { + if (showfunc == ospf6_neighbor_show_detail) + (*showfunc)(vty, on, json, uj); + else + (*showfunc)(vty, on, json_array, uj); + } + + if (uj) { + if (showfunc != ospf6_neighbor_show_detail) + json_object_object_add(json, "neighbors", json_array); + else + json_object_free(json_array); + vty_json(vty, json); + } +} + +DEFUN(show_ipv6_ospf6_neighbor, + show_ipv6_ospf6_neighbor_cmd, + "show ipv6 ospf6 [vrf ] neighbor [] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + "Display details\n" + "Display DR choices\n" + JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_type = 4; + bool uj = use_json(argc, argv); + bool detail = false; + bool drchoice = false; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (argv_find(argv, argc, "detail", &idx_type)) + detail = true; + else if (argv_find(argv, argc, "drchoice", &idx_type)) + drchoice = true; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_neighbor_show_detail_common(vty, ospf6, uj, + detail, drchoice); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int ospf6_neighbor_show_common(struct vty *vty, int argc, + struct cmd_token **argv, + struct ospf6 *ospf6, int idx_ipv4, bool uj) +{ + struct ospf6_neighbor *on; + struct ospf6_interface *oi; + struct ospf6_area *oa; + struct listnode *i, *j, *k; + void (*showfunc)(struct vty *, struct ospf6_neighbor *, + json_object *json, bool use_json); + uint32_t router_id; + json_object *json = NULL; + + showfunc = ospf6_neighbor_show_detail; + if (uj) + json = json_object_new_object(); + + if ((inet_pton(AF_INET, argv[idx_ipv4]->arg, &router_id)) != 1) { + vty_out(vty, "Router-ID is not parsable: %s\n", + argv[idx_ipv4]->arg); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) { + if (router_id == on->router_id) + (*showfunc)(vty, on, json, uj); + } + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_neighbor_one, + show_ipv6_ospf6_neighbor_one_cmd, + "show ipv6 ospf6 [vrf ] neighbor A.B.C.D [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + "Specify Router-ID as IPv4 address notation\n" + JSON_STR) +{ + int idx_ipv4 = 4; + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_ipv4 += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_neighbor_show_common(vty, argc, argv, ospf6, + idx_ipv4, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +void ospf6_neighbor_init(void) +{ + install_element(VIEW_NODE, &show_ipv6_ospf6_neighbor_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_neighbor_one_cmd); + + install_element(INTERFACE_NODE, &ipv6_ospf6_p2xp_neigh_cmd); + install_element(INTERFACE_NODE, &ipv6_ospf6_p2xp_neigh_cost_cmd); + install_element(INTERFACE_NODE, + &ipv6_ospf6_p2xp_neigh_poll_interval_cmd); +} + +DEFUN (debug_ospf6_neighbor, + debug_ospf6_neighbor_cmd, + "debug ospf6 neighbor []", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 Neighbor\n" + "Debug OSPFv3 Neighbor State Change\n" + "Debug OSPFv3 Neighbor Event\n") +{ + int idx_type = 3; + unsigned char level = 0; + + if (argc == 4) { + if (!strncmp(argv[idx_type]->arg, "s", 1)) + level = OSPF6_DEBUG_NEIGHBOR_STATE; + else if (!strncmp(argv[idx_type]->arg, "e", 1)) + level = OSPF6_DEBUG_NEIGHBOR_EVENT; + } else + level = OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT; + + OSPF6_DEBUG_NEIGHBOR_ON(level); + return CMD_SUCCESS; +} + + +DEFUN (no_debug_ospf6_neighbor, + no_debug_ospf6_neighbor_cmd, + "no debug ospf6 neighbor []", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 Neighbor\n" + "Debug OSPFv3 Neighbor State Change\n" + "Debug OSPFv3 Neighbor Event\n") +{ + int idx_type = 4; + unsigned char level = 0; + + if (argc == 5) { + if (!strncmp(argv[idx_type]->arg, "s", 1)) + level = OSPF6_DEBUG_NEIGHBOR_STATE; + if (!strncmp(argv[idx_type]->arg, "e", 1)) + level = OSPF6_DEBUG_NEIGHBOR_EVENT; + } else + level = OSPF6_DEBUG_NEIGHBOR_STATE | OSPF6_DEBUG_NEIGHBOR_EVENT; + + OSPF6_DEBUG_NEIGHBOR_OFF(level); + return CMD_SUCCESS; +} + + +DEFUN (no_debug_ospf6, + no_debug_ospf6_cmd, + "no debug ospf6", + NO_STR + DEBUG_STR + OSPF6_STR) +{ + unsigned int i; + + OSPF6_DEBUG_ABR_OFF(); + OSPF6_DEBUG_ASBR_OFF(); + OSPF6_DEBUG_BROUTER_OFF(); + OSPF6_DEBUG_BROUTER_SPECIFIC_ROUTER_OFF(); + OSPF6_DEBUG_BROUTER_SPECIFIC_AREA_OFF(); + OSPF6_DEBUG_FLOODING_OFF(); + OSPF6_DEBUG_INTERFACE_OFF(); + + ospf6_lsa_debug_set_all(false); + + for (i = 0; i < 6; i++) + OSPF6_DEBUG_MESSAGE_OFF(i, OSPF6_DEBUG_NEIGHBOR_STATE | + OSPF6_DEBUG_NEIGHBOR_EVENT); + + OSPF6_DEBUG_NEIGHBOR_OFF(OSPF6_DEBUG_NEIGHBOR_STATE | + OSPF6_DEBUG_NEIGHBOR_EVENT); + OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_TABLE); + OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_INTRA); + OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_INTER); + OSPF6_DEBUG_ROUTE_OFF(OSPF6_DEBUG_ROUTE_MEMORY); + OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_PROCESS); + OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_TIME); + OSPF6_DEBUG_SPF_OFF(OSPF6_DEBUG_SPF_DATABASE); + OSPF6_DEBUG_ZEBRA_OFF(OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV); + + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_neighbor(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_NEIGHBOR(STATE) && IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + vty_out(vty, "debug ospf6 neighbor\n"); + else if (IS_OSPF6_DEBUG_NEIGHBOR(STATE)) + vty_out(vty, "debug ospf6 neighbor state\n"); + else if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) + vty_out(vty, "debug ospf6 neighbor event\n"); + return 0; +} + +int config_write_ospf6_p2xp_neighbor(struct vty *vty, struct ospf6_interface *oi) +{ + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + frr_each (ospf6_if_p2xp_neighcfgs, &oi->p2xp_neighs, p2xp_cfg) { + vty_out(vty, " ipv6 ospf6 neighbor %pI6\n", &p2xp_cfg->addr); + + if (p2xp_cfg->poll_interval) + vty_out(vty, + " ipv6 ospf6 neighbor %pI6 poll-interval %u\n", + &p2xp_cfg->addr, p2xp_cfg->poll_interval); + + if (p2xp_cfg->cfg_cost) + vty_out(vty, " ipv6 ospf6 neighbor %pI6 cost %u\n", + &p2xp_cfg->addr, p2xp_cfg->cost); + } + return 0; +} + +void install_element_ospf6_debug_neighbor(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_neighbor_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_neighbor_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_cmd); + install_element(CONFIG_NODE, &debug_ospf6_neighbor_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_neighbor_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_cmd); +} diff --git a/ospf6d/ospf6_neighbor.h b/ospf6d/ospf6_neighbor.h new file mode 100644 index 0000000..60a7621 --- /dev/null +++ b/ospf6d/ospf6_neighbor.h @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_NEIGHBOR_H +#define OSPF6_NEIGHBOR_H + +#include "typesafe.h" +#include "hook.h" + +#include "ospf6_message.h" + +/* Forward declaration(s). */ +struct ospf6_area; + +/* Debug option */ +extern unsigned char conf_debug_ospf6_neighbor; +#define OSPF6_DEBUG_NEIGHBOR_STATE 0x01 +#define OSPF6_DEBUG_NEIGHBOR_EVENT 0x02 +#define OSPF6_DEBUG_NEIGHBOR_ON(level) (conf_debug_ospf6_neighbor |= (level)) +#define OSPF6_DEBUG_NEIGHBOR_OFF(level) (conf_debug_ospf6_neighbor &= ~(level)) +#define IS_OSPF6_DEBUG_NEIGHBOR(level) \ + (conf_debug_ospf6_neighbor & OSPF6_DEBUG_NEIGHBOR_##level) + +struct ospf6_helper_info { + + /* Grace interval received from + * Restarting Router. + */ + uint32_t recvd_grace_period; + + /* Grace interval used for grace + * gracetimer. + */ + uint32_t actual_grace_period; + + /* Grace timer,This Router acts as + * helper until this timer until + * this timer expires. + */ + struct event *t_grace_timer; + + /* Helper status */ + uint32_t gr_helper_status; + + /* Helper exit reason*/ + uint32_t helper_exit_reason; + + /* Planned/Unplanned restart*/ + uint32_t gr_restart_reason; + + + /* Helper rejected reason */ + uint32_t rejected_reason; +}; + +struct ospf6_if_p2xp_neighcfg; + +/* Neighbor structure */ +struct ospf6_neighbor { + /* Neighbor Router ID String */ + char name[36]; + + /* OSPFv3 Interface this neighbor belongs to */ + struct ospf6_interface *ospf6_if; + + /* P2P/P2MP config for this neighbor. + * can be NULL if not explicitly configured! + */ + struct ospf6_if_p2xp_neighcfg *p2xp_cfg; + + /* Neighbor state */ + uint8_t state; + + /* timestamp of last changing state */ + uint32_t state_change; + struct timeval last_changed; + + /* last received hello */ + struct timeval last_hello; + uint32_t hello_in; + + /* Neighbor Router ID */ + in_addr_t router_id; + + /* Neighbor Interface ID */ + ifindex_t ifindex; + + /* Router Priority of this neighbor */ + uint8_t priority; + + in_addr_t drouter; + in_addr_t bdrouter; + in_addr_t prev_drouter; + in_addr_t prev_bdrouter; + + /* Options field (Capability) */ + char options[3]; + + /* IPaddr of I/F on neighbour's link */ + struct in6_addr linklocal_addr; + + /* For Database Exchange */ + uint8_t dbdesc_bits; + uint32_t dbdesc_seqnum; + /* Last received Database Description packet */ + struct ospf6_dbdesc dbdesc_last; + + /* LS-list */ + struct ospf6_lsdb *summary_list; + struct ospf6_lsdb *request_list; + struct ospf6_lsdb *retrans_list; + + /* LSA list for message transmission */ + struct ospf6_lsdb *dbdesc_list; + struct ospf6_lsdb *lsreq_list; + struct ospf6_lsdb *lsupdate_list; + struct ospf6_lsdb *lsack_list; + + struct ospf6_lsa *last_ls_req; + + /* Inactivity timer */ + struct event *inactivity_timer; + + /* Timer to release the last dbdesc packet */ + struct event *last_dbdesc_release_timer; + + /* Thread for sending message */ + struct event *thread_send_dbdesc; + struct event *thread_send_lsreq; + struct event *thread_send_lsupdate; + struct event *thread_send_lsack; + struct event *thread_exchange_done; + struct event *thread_adj_ok; + struct event *event_loading_done; + + /* BFD information */ + struct bfd_session_params *bfd_session; + + /* ospf6 graceful restart HELPER info */ + struct ospf6_helper_info gr_helper_info; + + /* seqnum_h/l is used to compare sequence + * number in received packet Auth header + */ + uint32_t seqnum_h[OSPF6_MESSAGE_TYPE_MAX]; + uint32_t seqnum_l[OSPF6_MESSAGE_TYPE_MAX]; + bool auth_present; + bool lls_present; +}; + +/* Neighbor state */ +#define OSPF6_NEIGHBOR_DOWN 1 +#define OSPF6_NEIGHBOR_ATTEMPT 2 +#define OSPF6_NEIGHBOR_INIT 3 +#define OSPF6_NEIGHBOR_TWOWAY 4 +#define OSPF6_NEIGHBOR_EXSTART 5 +#define OSPF6_NEIGHBOR_EXCHANGE 6 +#define OSPF6_NEIGHBOR_LOADING 7 +#define OSPF6_NEIGHBOR_FULL 8 + +/* Neighbor Events */ +#define OSPF6_NEIGHBOR_EVENT_NO_EVENT 0 +#define OSPF6_NEIGHBOR_EVENT_HELLO_RCVD 1 +#define OSPF6_NEIGHBOR_EVENT_TWOWAY_RCVD 2 +#define OSPF6_NEIGHBOR_EVENT_NEGOTIATION_DONE 3 +#define OSPF6_NEIGHBOR_EVENT_EXCHANGE_DONE 4 +#define OSPF6_NEIGHBOR_EVENT_LOADING_DONE 5 +#define OSPF6_NEIGHBOR_EVENT_ADJ_OK 6 +#define OSPF6_NEIGHBOR_EVENT_SEQNUMBER_MISMATCH 7 +#define OSPF6_NEIGHBOR_EVENT_BAD_LSREQ 8 +#define OSPF6_NEIGHBOR_EVENT_ONEWAY_RCVD 9 +#define OSPF6_NEIGHBOR_EVENT_INACTIVITY_TIMER 10 +#define OSPF6_NEIGHBOR_EVENT_MAX_EVENT 11 + +extern const char *const ospf6_neighbor_event_str[]; + +static inline const char *ospf6_neighbor_event_string(int event) +{ +#define OSPF6_NEIGHBOR_UNKNOWN_EVENT_STRING "UnknownEvent" + + if (event < OSPF6_NEIGHBOR_EVENT_MAX_EVENT) + return ospf6_neighbor_event_str[event]; + return OSPF6_NEIGHBOR_UNKNOWN_EVENT_STRING; +} + +extern const char *const ospf6_neighbor_state_str[]; + + +/* Function Prototypes */ +int ospf6_neighbor_cmp(void *va, void *vb); +void ospf6_neighbor_dbex_init(struct ospf6_neighbor *on); + +struct ospf6_neighbor *ospf6_neighbor_lookup(uint32_t router_id, + struct ospf6_interface *oi); +struct ospf6_neighbor *ospf6_area_neighbor_lookup(struct ospf6_area *area, + uint32_t router_id); +struct ospf6_neighbor *ospf6_neighbor_create(uint32_t router_id, + struct ospf6_interface *oi); +void ospf6_neighbor_delete(struct ospf6_neighbor *on); + +void ospf6_neighbor_lladdr_set(struct ospf6_neighbor *on, + const struct in6_addr *addr); +struct ospf6_if_p2xp_neighcfg *ospf6_if_p2xp_find(struct ospf6_interface *oi, + const struct in6_addr *addr); +void ospf6_if_p2xp_up(struct ospf6_interface *oi); + +uint32_t ospf6_neighbor_cost(struct ospf6_neighbor *on); + +/* Neighbor event */ +extern void hello_received(struct event *thread); +extern void twoway_received(struct event *thread); +extern void negotiation_done(struct event *thread); +extern void exchange_done(struct event *thread); +extern void loading_done(struct event *thread); +extern void adj_ok(struct event *thread); +extern void seqnumber_mismatch(struct event *thread); +extern void bad_lsreq(struct event *thread); +extern void oneway_received(struct event *thread); +extern void inactivity_timer(struct event *thread); +extern void ospf6_check_nbr_loading(struct ospf6_neighbor *on); + +extern void ospf6_neighbor_init(void); +extern int config_write_ospf6_debug_neighbor(struct vty *vty); +extern int config_write_ospf6_p2xp_neighbor(struct vty *vty, + struct ospf6_interface *oi); +extern void install_element_ospf6_debug_neighbor(void); + +DECLARE_HOOK(ospf6_neighbor_change, + (struct ospf6_neighbor * on, int state, int next_state), + (on, state, next_state)); + +#endif /* OSPF6_NEIGHBOR_H */ diff --git a/ospf6d/ospf6_network.c b/ospf6d/ospf6_network.c new file mode 100644 index 0000000..eddf877 --- /dev/null +++ b/ospf6d/ospf6_network.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "sockunion.h" +#include "sockopt.h" +#include "privs.h" +#include "lib_errors.h" +#include "vrf.h" + +#include "libospf.h" +#include "ospf6_proto.h" +#include "ospf6_top.h" +#include "ospf6_network.h" +#include "ospf6d.h" +#include "ospf6_message.h" + +struct in6_addr allspfrouters6; +struct in6_addr alldrouters6; + +/* setsockopt MulticastLoop to off */ +static void ospf6_reset_mcastloop(int ospf6_sock) +{ + unsigned int off = 0; + if (setsockopt(ospf6_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, + sizeof(unsigned int)) + < 0) + zlog_warn("Network: reset IPV6_MULTICAST_LOOP failed: %s", + safe_strerror(errno)); +} + +static void ospf6_set_pktinfo(int ospf6_sock) +{ + setsockopt_ipv6_pktinfo(ospf6_sock, 1); +} + +static void ospf6_set_transport_class(int ospf6_sock) +{ +#ifdef IPTOS_PREC_INTERNETCONTROL + setsockopt_ipv6_tclass(ospf6_sock, IPTOS_PREC_INTERNETCONTROL); +#endif +} + +void ospf6_serv_close(int *ospf6_sock) +{ + if (*ospf6_sock != -1) { + close(*ospf6_sock); + *ospf6_sock = -1; + return; + } +} + +/* Make ospf6d's server socket. */ +int ospf6_serv_sock(struct ospf6 *ospf6) +{ + int ospf6_sock; + + if (ospf6->fd != -1) + return -1; + + if (ospf6->vrf_id == VRF_UNKNOWN) + return -1; + + frr_with_privs(&ospf6d_privs) { + + ospf6_sock = vrf_socket(AF_INET6, SOCK_RAW, IPPROTO_OSPFIGP, + ospf6->vrf_id, ospf6->name); + if (ospf6_sock < 0) { + zlog_warn("Network: can't create OSPF6 socket."); + return -1; + } + } + +/* set socket options */ +#if 1 + sockopt_reuseaddr(ospf6_sock); +#else + ospf6_set_reuseaddr(); +#endif /*1*/ + ospf6_reset_mcastloop(ospf6_sock); + ospf6_set_pktinfo(ospf6_sock); + ospf6_set_transport_class(ospf6_sock); + + ospf6->fd = ospf6_sock; + /* setup global in6_addr, allspf6 and alldr6 for later use */ + inet_pton(AF_INET6, ALLSPFROUTERS6, &allspfrouters6); + inet_pton(AF_INET6, ALLDROUTERS6, &alldrouters6); + + return 0; +} + +/* ospf6 set socket option */ +int ospf6_sso(ifindex_t ifindex, struct in6_addr *group, int option, int sockfd) +{ + struct ipv6_mreq mreq6; + int ret; + int bufsize = (8 * 1024 * 1024); + + if (sockfd == -1) + return -1; + + assert(ifindex); + mreq6.ipv6mr_interface = ifindex; + memcpy(&mreq6.ipv6mr_multiaddr, group, sizeof(struct in6_addr)); + + ret = setsockopt(sockfd, IPPROTO_IPV6, option, &mreq6, sizeof(mreq6)); + if (ret < 0) { + flog_err_sys( + EC_LIB_SOCKET, + "Network: setsockopt (%d) on ifindex %d failed: %s", + option, ifindex, safe_strerror(errno)); + return ret; + } + + setsockopt_so_sendbuf(sockfd, bufsize); + setsockopt_so_recvbuf(sockfd, bufsize); + + return 0; +} + +static int iov_count(struct iovec *iov) +{ + int i; + for (i = 0; iov[i].iov_base; i++) + ; + return i; +} + +static int iov_totallen(struct iovec *iov) +{ + int i; + int totallen = 0; + for (i = 0; iov[i].iov_base; i++) + totallen += iov[i].iov_len; + return totallen; +} + +int ospf6_sendmsg(struct in6_addr *src, struct in6_addr *dst, + ifindex_t ifindex, struct iovec *message, int ospf6_sock) +{ + int retval; + struct msghdr smsghdr; + struct cmsghdr *scmsgp; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf; + struct in6_pktinfo *pktinfo; + struct sockaddr_in6 dst_sin6; + + assert(dst); + + memset(&cmsgbuf, 0, sizeof(cmsgbuf)); + scmsgp = (struct cmsghdr *)&cmsgbuf; + pktinfo = (struct in6_pktinfo *)(CMSG_DATA(scmsgp)); + memset(&dst_sin6, 0, sizeof(dst_sin6)); + + /* source address */ + pktinfo->ipi6_ifindex = ifindex; + if (src) + memcpy(&pktinfo->ipi6_addr, src, sizeof(struct in6_addr)); + else + memset(&pktinfo->ipi6_addr, 0, sizeof(struct in6_addr)); + + /* destination address */ + dst_sin6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + dst_sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif /*SIN6_LEN*/ + memcpy(&dst_sin6.sin6_addr, dst, sizeof(struct in6_addr)); + dst_sin6.sin6_scope_id = ifindex; + + /* send control msg */ + scmsgp->cmsg_level = IPPROTO_IPV6; + scmsgp->cmsg_type = IPV6_PKTINFO; + scmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + /* scmsgp = CMSG_NXTHDR (&smsghdr, scmsgp); */ + + /* send msg hdr */ + memset(&smsghdr, 0, sizeof(smsghdr)); + smsghdr.msg_iov = message; + smsghdr.msg_iovlen = iov_count(message); + smsghdr.msg_name = (caddr_t)&dst_sin6; + smsghdr.msg_namelen = sizeof(struct sockaddr_in6); + smsghdr.msg_control = (caddr_t)&cmsgbuf.buf; + smsghdr.msg_controllen = sizeof(cmsgbuf.buf); + + retval = sendmsg(ospf6_sock, &smsghdr, 0); + if (retval != iov_totallen(message)) + zlog_warn("sendmsg failed: source: %pI6 Dest: %pI6 ifindex: %d: %s (%d)", + src, dst, ifindex, + safe_strerror(errno), errno); + + return retval; +} + +int ospf6_recvmsg(struct in6_addr *src, struct in6_addr *dst, + ifindex_t *ifindex, struct iovec *message, int ospf6_sock) +{ + int retval; + struct msghdr rmsghdr; + struct cmsghdr *rcmsgp; + uint8_t cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + struct in6_pktinfo *pktinfo; + struct sockaddr_in6 src_sin6; + + rcmsgp = (struct cmsghdr *)cmsgbuf; + pktinfo = (struct in6_pktinfo *)(CMSG_DATA(rcmsgp)); + memset(&src_sin6, 0, sizeof(src_sin6)); + + /* receive control msg */ + rcmsgp->cmsg_level = IPPROTO_IPV6; + rcmsgp->cmsg_type = IPV6_PKTINFO; + rcmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + /* rcmsgp = CMSG_NXTHDR (&rmsghdr, rcmsgp); */ + + /* receive msg hdr */ + memset(&rmsghdr, 0, sizeof(rmsghdr)); + rmsghdr.msg_iov = message; + rmsghdr.msg_iovlen = iov_count(message); + rmsghdr.msg_name = (caddr_t)&src_sin6; + rmsghdr.msg_namelen = sizeof(struct sockaddr_in6); + rmsghdr.msg_control = (caddr_t)cmsgbuf; + rmsghdr.msg_controllen = sizeof(cmsgbuf); + + retval = recvmsg(ospf6_sock, &rmsghdr, MSG_DONTWAIT); + if (retval < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + zlog_warn("stream_recvmsg failed: %s", + safe_strerror(errno)); + return retval; + } else if (retval == iov_totallen(message)) + zlog_warn("recvmsg read full buffer size: %d", retval); + + /* source address */ + assert(src); + memcpy(src, &src_sin6.sin6_addr, sizeof(struct in6_addr)); + + /* destination address */ + if (ifindex) + *ifindex = pktinfo->ipi6_ifindex; + if (dst) + memcpy(dst, &pktinfo->ipi6_addr, sizeof(struct in6_addr)); + + return retval; +} diff --git a/ospf6d/ospf6_network.h b/ospf6d/ospf6_network.h new file mode 100644 index 0000000..60b7cc5 --- /dev/null +++ b/ospf6d/ospf6_network.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_NETWORK_H +#define OSPF6_NETWORK_H + +extern struct in6_addr allspfrouters6; +extern struct in6_addr alldrouters6; + +extern int ospf6_serv_sock(struct ospf6 *ospf6); +extern void ospf6_serv_close(int *ospf6_sock); +extern int ospf6_sso(ifindex_t ifindex, struct in6_addr *group, int option, + int sockfd); + +extern int ospf6_sendmsg(struct in6_addr *src, struct in6_addr *dst, + ifindex_t ifindex, struct iovec *message, + int ospf6_sock); +extern int ospf6_recvmsg(struct in6_addr *src, struct in6_addr *dst, + ifindex_t *ifindex, struct iovec *message, + int ospf6_sock); + +#define OSPF6_MESSAGE_WRITE_ON(oi) \ + do { \ + bool list_was_empty = \ + list_isempty(oi->area->ospf6->oi_write_q); \ + if ((oi)->on_write_q == 0) { \ + listnode_add(oi->area->ospf6->oi_write_q, (oi)); \ + (oi)->on_write_q = 1; \ + } \ + if (list_was_empty && \ + !list_isempty(oi->area->ospf6->oi_write_q)) \ + event_add_write(master, ospf6_write, oi->area->ospf6, \ + oi->area->ospf6->fd, \ + &oi->area->ospf6->t_write); \ + } while (0) + +#endif /* OSPF6_NETWORK_H */ diff --git a/ospf6d/ospf6_nssa.c b/ospf6d/ospf6_nssa.c new file mode 100644 index 0000000..ea2be20 --- /dev/null +++ b/ospf6d/ospf6_nssa.c @@ -0,0 +1,1478 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFv3 Not So Stubby Area implementation. + * + * Copyright (C) 2021 Kaushik Nath + * Copyright (C) 2021 Soman K.S + */ + +#include +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "linklist.h" +#include "command.h" +#include "frrevent.h" +#include "plist.h" +#include "filter.h" + +#include "ospf6_proto.h" +#include "ospf6_route.h" +#include "ospf6_lsa.h" +#include "ospf6_route.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_zebra.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" + +#include "ospf6_flood.h" +#include "ospf6_intra.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6d.h" +#include "ospf6_nssa.h" +#include "ospf6d/ospf6_nssa_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_LSA, "OSPF6 LSA"); +unsigned char config_debug_ospf6_nssa = 0; +/* Determine whether this router is elected translator or not for area */ +static int ospf6_abr_nssa_am_elected(struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa; + struct ospf6_router_lsa *router_lsa; + in_addr_t *best = NULL; + uint16_t type; + + type = htons(OSPF6_LSTYPE_ROUTER); + + /* Verify all the router LSA to compare the router ID */ + for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) { + router_lsa = (struct ospf6_router_lsa *)ospf6_lsa_header_end( + lsa->header); + + /* ignore non-ABR routers */ + if (!CHECK_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_B)) + continue; + + /* Router has Nt flag - always translate */ + if (CHECK_FLAG(router_lsa->bits, OSPF6_ROUTER_BIT_NT)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: router %pI4 asserts Nt", + __func__, &lsa->header->id); + return 1; + } + + if (best == NULL) + best = &lsa->header->adv_router; + else if (IPV4_ADDR_CMP(best, &lsa->header->adv_router) < 0) + best = &lsa->header->adv_router; + } + + if (best == NULL) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: best electable ABR not found", + __func__); + return 0; + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: best electable ABR is: %pI4", __func__, best); + + if (IPV4_ADDR_CMP(best, &oa->ospf6->router_id) <= 0) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: elected ABR is: %pI4", __func__, best); + return 1; + } else { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: not elected best %pI4, router ID %pI4", + __func__, best, &oa->ospf6->router_id); + return 0; + } +} + +/* Flush the translated LSA when translation is disabled */ +static void ospf6_flush_translated_lsa(struct ospf6_area *area) +{ + uint16_t type; + struct ospf6_lsa *type7; + struct ospf6_lsa *type5; + struct ospf6 *ospf6 = area->ospf6; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: start area %s", __func__, area->name); + + type = htons(OSPF6_LSTYPE_TYPE_7); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, type7)) { + type5 = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + type7->external_lsa_id, + ospf6->router_id, ospf6->lsdb); + if (type5 && CHECK_FLAG(type5->flag, OSPF6_LSA_LOCAL_XLT)) + ospf6_lsa_premature_aging(type5); + } + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: finish area %s", __func__, area->name); +} + +/* Check NSSA status for all nssa areas */ +void ospf6_abr_nssa_check_status(struct ospf6 *ospf6) +{ + struct ospf6_area *area; + struct listnode *lnode, *nnode; + + for (ALL_LIST_ELEMENTS(ospf6->area_list, lnode, nnode, area)) { + uint8_t old_state = area->NSSATranslatorState; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: checking area %s flag %x", __func__, + area->name, area->flag); + + if (!IS_AREA_NSSA(area)) + continue; + + if (!CHECK_FLAG(area->ospf6->flag, OSPF6_FLAG_ABR)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: not ABR", __func__); + area->NSSATranslatorState = + OSPF6_NSSA_TRANSLATE_DISABLED; + ospf6_flush_translated_lsa(area); + } else { + /* Router is ABR */ + if (area->NSSATranslatorRole == OSPF6_NSSA_ROLE_ALWAYS) + area->NSSATranslatorState = + OSPF6_NSSA_TRANSLATE_ENABLED; + else { + /* We are a candidate for Translation */ + if (ospf6_abr_nssa_am_elected(area) > 0) { + area->NSSATranslatorState = + OSPF6_NSSA_TRANSLATE_ENABLED; + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s: elected translator", + __func__); + } else { + area->NSSATranslatorState = + OSPF6_NSSA_TRANSLATE_DISABLED; + ospf6_flush_translated_lsa(area); + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: not elected", + __func__); + } + } + } + + /* RFC3101, 3.1: + * All NSSA border routers must set the E-bit in the Type-1 + * router-LSAs of their directly attached non-stub areas, even + * when they are not translating. + */ + if (old_state != area->NSSATranslatorState) { + if (old_state == OSPF6_NSSA_TRANSLATE_DISABLED) { + ++ospf6->redist_count; + ospf6_asbr_status_update(ospf6, + ospf6->redist_count); + } else { + --ospf6->redist_count; + ospf6_asbr_status_update(ospf6, + ospf6->redist_count); + } + } + } +} + +/* Mark the summary LSA's as unapproved, when ABR status changes.*/ +static void ospf6_abr_unapprove_summaries(struct ospf6 *ospf6) +{ + struct listnode *node, *nnode; + struct ospf6_area *area; + struct ospf6_lsa *lsa; + uint16_t type; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Start", __func__); + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : considering area %pI4", __func__, + &area->area_id); + /* Inter area router LSA */ + type = htons(OSPF6_LSTYPE_INTER_ROUTER); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, + lsa)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s : approved unset on summary link id %pI4", + __func__, &lsa->header->id); + SET_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED); + } + /* Inter area prefix LSA */ + type = htons(OSPF6_LSTYPE_INTER_PREFIX); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, + lsa)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "%s : approved unset on asbr-summary link id %pI4", + __func__, &lsa->header->id); + SET_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED); + } + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Stop", __func__); +} + +/* Re-advertise inter-area router LSA's */ +void ospf6_asbr_prefix_readvertise(struct ospf6 *ospf6) +{ + struct ospf6_route *brouter; + struct listnode *node, *nnode; + struct ospf6_area *oa; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Re-examining Inter-Router prefixes"); + + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) { + for (brouter = ospf6_route_head(oa->ospf6->brouter_table); + brouter; brouter = ospf6_route_next(brouter)) + ospf6_abr_originate_summary_to_area(brouter, oa); + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Finished re-examining Inter-Router prefixes"); +} + +/* Advertise prefixes configured using area range command */ +static void ospf6_abr_announce_aggregates(struct ospf6 *ospf6) +{ + struct ospf6_area *area; + struct ospf6_route *range; + struct listnode *node; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, area)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug( + "ospf_abr_announce_aggregates(): looking at area %pI4", + &area->area_id); + + for (range = ospf6_route_head(area->range_table); range; + range = ospf6_route_next(range)) + ospf6_abr_range_update(range, ospf6); + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s: Stop", __func__); +} + +/* Flush the summary LSA's which are not approved.*/ +void ospf6_abr_remove_unapproved_summaries(struct ospf6 *ospf6) +{ + struct listnode *node, *nnode; + struct ospf6_area *area; + struct ospf6_lsa *lsa; + uint16_t type; + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Start", __func__); + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : looking at area %pI4", __func__, + &area->area_id); + + /* Inter area router LSA */ + type = htons(OSPF6_LSTYPE_INTER_ROUTER); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, + lsa)) { + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED)) + ospf6_lsa_premature_aging(lsa); + } + + /* Inter area prefix LSA */ + type = htons(OSPF6_LSTYPE_INTER_PREFIX); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, + lsa)) { + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED)) + ospf6_lsa_premature_aging(lsa); + } + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Stop", __func__); +} + +/* + * This is the function taking care about ABR stuff, i.e. + * summary-LSA origination and flooding. + */ +static void ospf6_abr_task(struct ospf6 *ospf6) +{ + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Start", __func__); + + if (ospf6->route_table == NULL || ospf6->brouter_table == NULL) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Routing tables are not yet ready", + __func__); + return; + } + + ospf6_abr_unapprove_summaries(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : prepare aggregates", __func__); + + ospf6_abr_range_reset_cost(ospf6); + + if (IS_OSPF6_ABR(ospf6)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : process network RT", __func__); + ospf6_abr_prefix_resummarize(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : process router RT", __func__); + ospf6_asbr_prefix_readvertise(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : announce aggregates", __func__); + ospf6_abr_announce_aggregates(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : announce stub defaults", __func__); + ospf6_abr_defaults_to_stub(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : announce NSSA Type-7 defaults", + __func__); + ospf6_abr_nssa_type_7_defaults(ospf6); + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : remove unapproved summaries", __func__); + ospf6_abr_remove_unapproved_summaries(ospf6); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("%s : Stop", __func__); +} + +/* For NSSA Translations + * Mark the translated LSA's as unapproved. */ +static void ospf6_abr_unapprove_translates(struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Start", __func__); + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + for (ALL_LSDB_TYPED(ospf6->lsdb, type, lsa)) { + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_LOCAL_XLT)) { + SET_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED); + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : approved unset on link id %pI4", + __func__, &lsa->header->id); + } + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Stop", __func__); +} + +/* Generate the translated external lsa from NSSA lsa */ +static struct ospf6_lsa *ospf6_lsa_translated_nssa_new(struct ospf6_area *area, + struct ospf6_lsa *type7) +{ + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa *lsa; + struct ospf6_as_external_lsa *ext, *extnew; + struct ospf6_lsa_header *lsa_header; + caddr_t old_ptr, new_ptr; + struct ospf6_as_external_lsa *nssa; + struct prefix prefix; + struct ospf6 *ospf6 = area->ospf6; + ptrdiff_t tag_offset = 0; + route_tag_t network_order; + struct ospf6_route *range; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : Start", __func__); + + if (area->NSSATranslatorState == OSPF6_NSSA_TRANSLATE_DISABLED) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Translation disabled for area %s", + __func__, area->name); + return NULL; + } + + /* find the translated Type-5 for this Type-7 */ + nssa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + type7->header); + prefix.family = AF_INET6; + prefix.prefixlen = nssa->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, nssa, &nssa->prefix); + + /* Check if the Type-7 LSA should be suppressed by aggregation. */ + range = ospf6_route_lookup_bestmatch(&prefix, area->nssa_range_table); + if (range && !prefix_same(&prefix, &range->prefix) + && !CHECK_FLAG(range->flag, OSPF6_ROUTE_REMOVE)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s: LSA %s suppressed by range %pFX of area %s", + __func__, type7->name, &range->prefix, + area->name); + return NULL; + } + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + extnew = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa_header); + ext = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + type7->header); + old_ptr = + (caddr_t)((caddr_t)ext + sizeof(struct ospf6_as_external_lsa)); + new_ptr = (caddr_t)((caddr_t)extnew + + sizeof(struct ospf6_as_external_lsa)); + + memcpy(extnew, ext, sizeof(struct ospf6_as_external_lsa)); + + /* set Prefix */ + memcpy(new_ptr, old_ptr, OSPF6_PREFIX_SPACE(ext->prefix.prefix_length)); + ospf6_prefix_apply_mask(&extnew->prefix); + new_ptr += OSPF6_PREFIX_SPACE(extnew->prefix.prefix_length); + + tag_offset = + sizeof(*ext) + OSPF6_PREFIX_SPACE(ext->prefix.prefix_length); + + /* Forwarding address */ + if (CHECK_FLAG(ext->bits_metric, OSPF6_ASBR_BIT_F)) { + memcpy(new_ptr, (caddr_t)ext + tag_offset, + sizeof(struct in6_addr)); + new_ptr += sizeof(struct in6_addr); + tag_offset += sizeof(struct in6_addr); + } + /* External Route Tag */ + if (CHECK_FLAG(ext->bits_metric, OSPF6_ASBR_BIT_T)) { + memcpy(&network_order, (caddr_t)ext + tag_offset, + sizeof(network_order)); + network_order = htonl(network_order); + memcpy(new_ptr, &network_order, sizeof(network_order)); + new_ptr += sizeof(network_order); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + lsa_header->id = htonl(ospf6->external_id); + ospf6->external_id++; + lsa_header->adv_router = ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, ospf6->lsdb); + lsa_header->length = htons((caddr_t)new_ptr - (caddr_t)lsa_header); + type7->external_lsa_id = lsa_header->id; + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + SET_FLAG(lsa->flag, OSPF6_LSA_LOCAL_XLT); + UNSET_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED); + + /* Originate */ + ospf6_lsa_originate_process(lsa, ospf6); + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Originated type5 LSA id %pI4", __func__, + &lsa_header->id); + return lsa; +} + +/* Delete LSA from retransmission list */ +static void ospf6_ls_retransmit_delete_nbr_as(struct ospf6 *ospf6, + struct ospf6_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf6_area *area; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : start lsa %s", __func__, lsa->name); + + /*The function ospf6_flood_clear_area removes LSA from + * retransmit list. + */ + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) + ospf6_flood_clear_area(lsa, area); + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : finish lsa %s", __func__, lsa->name); +} + +/* Refresh translated AS-external-LSA. */ +struct ospf6_lsa *ospf6_translated_nssa_refresh(struct ospf6_area *area, + struct ospf6_lsa *type7, + struct ospf6_lsa *type5) +{ + struct ospf6_lsa *new = NULL; + struct prefix prefix; + struct ospf6 *ospf6 = area->ospf6; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : start area %s", __func__, area->name); + + /* Sanity checks. */ + assert(type7); + + /* Find the AS external LSA */ + if (type5 == NULL) { + struct ospf6_as_external_lsa *ext_lsa; + struct ospf6_route *match; + + /* Find the AS external LSA from Type-7 LSA */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s: try to find translated Type-5 LSA for %s", + __func__, type7->name); + + ext_lsa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + type7->header); + prefix.family = AF_INET6; + prefix.prefixlen = ext_lsa->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, ext_lsa, + &ext_lsa->prefix); + + match = ospf6_route_lookup(&prefix, ospf6->external_table); + if (match) + type5 = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_AS_EXTERNAL), + match->path.origin.id, ospf6->router_id, + ospf6->lsdb); + } + + if (type5) { + if (CHECK_FLAG(type5->flag, OSPF6_LSA_LOCAL_XLT)) { + /* Delete LSA from neighbor retransmit-list. */ + ospf6_ls_retransmit_delete_nbr_as(ospf6, type5); + + /* Flush the LSA */ + ospf6_lsa_premature_aging(type5); + } else { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Invalid translated LSA %s", + __func__, type5->name); + return NULL; + } + } + + /* create new translated LSA */ + if (ospf6_lsa_age_current(type7) != OSPF_LSA_MAXAGE) { + if ((new = ospf6_lsa_translated_nssa_new(area, type7)) + == NULL) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s: Could not translate Type-7 for %pI4", + __func__, &type7->header->id); + return NULL; + } + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: finish", __func__); + + return new; +} + +static void ospf6_abr_translate_nssa(struct ospf6_area *area, + struct ospf6_lsa *lsa) +{ + /* Incoming Type-7 or aggregated Type-7 + * + * LSA is skipped if P-bit is off. + * + * The Type-7 is translated, Installed/Approved as a Type-5 into + * global LSDB, then Flooded through AS + * + * Later, any Unapproved Translated Type-5's are flushed/discarded + */ + + struct ospf6_lsa *old = NULL; + struct ospf6_as_external_lsa *nssa_lsa; + struct prefix prefix; + struct ospf6_route *match; + struct ospf6 *ospf6; + + ospf6 = area->ospf6; + nssa_lsa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + + if (!CHECK_FLAG(nssa_lsa->prefix.prefix_options, + OSPF6_PREFIX_OPTION_P)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : LSA Id %pI4, P-bit off, NO Translation", + __func__, &lsa->header->id); + return; + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : LSA Id %pI4 external ID %pI4, Translating type 7 to 5", + __func__, &lsa->header->id, &lsa->external_lsa_id); + + prefix.family = AF_INET6; + prefix.prefixlen = nssa_lsa->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, nssa_lsa, &nssa_lsa->prefix); + + if (!CHECK_FLAG(nssa_lsa->bits_metric, OSPF6_ASBR_BIT_F)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : LSA Id %pI4, Forward address is 0, NO Translation", + __func__, &lsa->header->id); + return; + } + + /* Find the type-5 LSA in the area-range table */ + match = ospf6_route_lookup_bestmatch(&prefix, area->nssa_range_table); + if (match && CHECK_FLAG(match->flag, OSPF6_ROUTE_NSSA_RANGE)) { + if (prefix_same(&prefix, &match->prefix)) { + /* The prefix range is being removed, + * no need to refresh + */ + if + CHECK_FLAG(match->flag, OSPF6_ROUTE_REMOVE) + return; + } else { + if (!CHECK_FLAG(match->flag, OSPF6_ROUTE_REMOVE)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s: LSA Id %pI4 suppressed by range %pFX of area %s", + __func__, &lsa->header->id, + &match->prefix, area->name); + /* LSA will be suppressed by area-range command, + * no need to refresh + */ + return; + } + } + } + + /* Find the existing AS-External LSA for this prefix */ + match = ospf6_route_lookup(&prefix, ospf6->route_table); + if (match) { + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + lsa->external_lsa_id, ospf6->router_id, + ospf6->lsdb); + } + + if (OSPF6_LSA_IS_MAXAGE(lsa)) { + if (old) + ospf6_lsa_premature_aging(old); + return; + } + + if (old && !OSPF6_LSA_IS_MAXAGE(old)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : found old translated LSA Id %pI4, skip", + __func__, &old->header->id); + + UNSET_FLAG(old->flag, OSPF6_LSA_UNAPPROVED); + return; + + } else { + /* no existing external route for this LSA Id + * originate translated LSA + */ + + if (ospf6_lsa_translated_nssa_new(area, lsa) == NULL) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "%s : Could not translate Type-7 for %pI4 to Type-5", + __func__, &lsa->header->id); + return; + } + } +} + +static void ospf6_abr_process_nssa_translates(struct ospf6 *ospf6) +{ + /* Scan through all NSSA_LSDB records for all areas; + * If P-bit is on, translate all Type-7's to 5's and aggregate or + * flood install as approved in Type-5 LSDB with XLATE Flag on + * later, do same for all aggregates... At end, DISCARD all + * remaining UNAPPROVED Type-5's (Aggregate is for future ) */ + + struct listnode *node; + struct ospf6_area *oa; + struct ospf6_lsa *lsa; + int type; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (!IS_AREA_NSSA(oa)) + continue; + + /* skip if not translator */ + if (oa->NSSATranslatorState == OSPF6_NSSA_TRANSLATE_DISABLED) { + zlog_debug("%s area %pI4 NSSATranslatorState %d", + __func__, &oa->area_id, + oa->NSSATranslatorState); + continue; + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : looking at area %pI4", __func__, + &oa->area_id); + + type = htons(OSPF6_LSTYPE_TYPE_7); + for (ALL_LSDB_TYPED(oa->lsdb, type, lsa)) { + zlog_debug("%s : lsa %s , id %pI4 , adv router %pI4", + __func__, lsa->name, &lsa->header->id, + &lsa->header->adv_router); + ospf6_abr_translate_nssa(oa, lsa); + } + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : Stop", __func__); +} + +static void ospf6_abr_send_nssa_aggregates(struct ospf6 *ospf6) +{ + struct listnode *node; + struct ospf6_area *area; + struct ospf6_route *range; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, area)) { + if (area->NSSATranslatorState == OSPF6_NSSA_TRANSLATE_DISABLED) + continue; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + for (range = ospf6_route_head(area->nssa_range_table); range; + range = ospf6_route_next(range)) + ospf6_abr_range_update(range, ospf6); + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf6_abr_remove_unapproved_translates(struct ospf6 *ospf6) +{ + struct ospf6_lsa *lsa; + uint16_t type; + + /* All AREA PROCESS should have APPROVED necessary LSAs */ + /* Remove any left over and not APPROVED */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Start", __func__); + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + for (ALL_LSDB_TYPED(ospf6->lsdb, type, lsa)) { + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_LOCAL_XLT) + && CHECK_FLAG(lsa->flag, OSPF6_LSA_UNAPPROVED)) { + zlog_debug( + "%s : removing unapproved translates, lsa : %s", + __func__, lsa->name); + + ospf6_lsa_premature_aging(lsa); + } + } + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf6_abr_nssa_type_7_default_create(struct ospf6 *ospf6, + struct ospf6_area *oa) +{ + struct ospf6_route *def; + int metric; + int metric_type; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Announcing Type-7 default route into NSSA area %s", + oa->name); + + def = ospf6_route_create(ospf6); + def->type = OSPF6_DEST_TYPE_NETWORK; + def->prefix.family = AF_INET6; + def->prefix.prefixlen = 0; + memset(&def->prefix.u.prefix6, 0, sizeof(struct in6_addr)); + def->type = OSPF6_DEST_TYPE_NETWORK; + def->path.subtype = OSPF6_PATH_SUBTYPE_DEFAULT_RT; + if (CHECK_FLAG(ospf6->flag, OSPF6_FLAG_ABR)) + def->path.area_id = ospf6->backbone->area_id; + else + def->path.area_id = oa->area_id; + + /* Compute default route type and metric. */ + if (oa->nssa_default_originate.metric_value != -1) + metric = oa->nssa_default_originate.metric_value; + else + metric = DEFAULT_DEFAULT_ALWAYS_METRIC; + if (oa->nssa_default_originate.metric_type != -1) + metric_type = oa->nssa_default_originate.metric_type; + else + metric_type = DEFAULT_METRIC_TYPE; + def->path.metric_type = metric_type; + def->path.cost = metric; + if (metric_type == 1) + def->path.type = OSPF6_PATH_TYPE_EXTERNAL1; + else + def->path.type = OSPF6_PATH_TYPE_EXTERNAL2; + + ospf6_nssa_lsa_originate(def, oa, false); + ospf6_route_delete(def); +} + +static void ospf6_abr_nssa_type_7_default_delete(struct ospf6 *ospf6, + struct ospf6_area *oa) +{ + struct ospf6_lsa *lsa; + + lsa = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_TYPE_7), 0, + oa->ospf6->router_id, oa->lsdb); + if (lsa && !OSPF6_LSA_IS_MAXAGE(lsa)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "Withdrawing Type-7 default route from area %s", + oa->name); + + ospf6_lsa_purge(lsa); + } +} + +/* NSSA Type-7 default route. */ +void ospf6_abr_nssa_type_7_defaults(struct ospf6 *ospf6) +{ + struct listnode *node; + struct ospf6_area *oa; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (IS_AREA_NSSA(oa) && oa->nssa_default_originate.enabled + && (IS_OSPF6_ABR(ospf6) + || (IS_OSPF6_ASBR(ospf6) + && ospf6->nssa_default_import_check.status))) + ospf6_abr_nssa_type_7_default_create(ospf6, oa); + else + ospf6_abr_nssa_type_7_default_delete(ospf6, oa); + } +} + +static void ospf6_abr_nssa_task(struct ospf6 *ospf6) +{ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Check for NSSA-ABR Tasks():"); + + if (!IS_OSPF6_ABR(ospf6)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s Not ABR", __func__); + return; + } + + if (!ospf6->anyNSSA) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s Not NSSA", __func__); + return; + } + + /* Each area must confirm TranslatorRole */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Start", __func__); + + /* For all Global Entries flagged "local-translate", unset APPROVED */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: unapprove translates", __func__); + + ospf6_abr_unapprove_translates(ospf6); + + /* Originate Type-7 aggregates */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: send NSSA aggregates", __func__); + ospf6_abr_send_nssa_aggregates(ospf6); + + /* For all NSSAs, Type-7s, translate to 5's, INSTALL/FLOOD, or + * Aggregate as Type-7 + * Install or Approve in Type-5 Global LSDB + */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: process translates", __func__); + ospf6_abr_process_nssa_translates(ospf6); + + /* Flush any unapproved previous translates from Global Data Base */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: remove unapproved translates", __func__); + ospf6_abr_remove_unapproved_translates(ospf6); + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: Stop", __func__); +} + +int ospf6_redistribute_check(struct ospf6 *ospf6, struct ospf6_route *route, + int type) +{ + route_map_result_t ret; + struct prefix *prefix; + struct ospf6_redist *red; + + if (!ospf6_zebra_is_redistribute(type, ospf6->vrf_id)) + return 0; + + prefix = &route->prefix; + + red = ospf6_redist_lookup(ospf6, type, 0); + if (!red) + return 0; + + /* Change to new redist structure */ + if (ROUTEMAP_NAME(red)) { + if (ROUTEMAP(red) == NULL) + ospf6_asbr_routemap_update(NULL); + if (ROUTEMAP(red) == NULL) { + zlog_warn( + "route-map \"%s\" not found, suppress redistributing", + ROUTEMAP_NAME(red)); + return 0; + } + } + + /* Change to new redist structure */ + if (ROUTEMAP(red)) { + ret = route_map_apply(ROUTEMAP(red), prefix, route); + if (ret == RMAP_DENYMATCH) { + if (IS_OSPF6_DEBUG_ASBR) + zlog_debug("Denied by route-map \"%s\"", + ROUTEMAP_NAME(red)); + return 0; + } + } + + return 1; +} + +/* This function performs ABR related processing */ +static void ospf6_abr_task_timer(struct event *thread) +{ + struct ospf6 *ospf6 = EVENT_ARG(thread); + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Running ABR task on timer"); + + (void)ospf6_check_and_set_router_abr(ospf6); + ospf6_abr_nssa_check_status(ospf6); + ospf6_abr_task(ospf6); + /* if nssa-abr, then scan Type-7 LSDB */ + ospf6_abr_nssa_task(ospf6); +} + +void ospf6_schedule_abr_task(struct ospf6 *ospf6) +{ + if (event_is_scheduled(ospf6->t_abr_task)) { + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("ABR task already scheduled"); + return; + } + + if (IS_OSPF6_DEBUG_ABR) + zlog_debug("Scheduling ABR task"); + + event_add_timer(master, ospf6_abr_task_timer, ospf6, + OSPF6_ABR_TASK_DELAY, &ospf6->t_abr_task); +} + +/* Flush the NSSA LSAs from the area */ +static void ospf6_nssa_flush_area(struct ospf6_area *area) +{ + uint16_t type; + struct ospf6_lsa *lsa = NULL, *type5 = NULL; + struct ospf6 *ospf6 = area->ospf6; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s: area %s", __func__, area->name); + + /* Flush the NSSA LSA */ + type = htons(OSPF6_LSTYPE_TYPE_7); + for (ALL_LSDB_TYPED_ADVRTR(area->lsdb, type, ospf6->router_id, lsa)) { + lsa->header->age = htons(OSPF_LSA_MAXAGE); + SET_FLAG(lsa->flag, OSPF6_LSA_FLUSH); + ospf6_flood(NULL, lsa); + + /* Flush the translated LSA */ + if (ospf6_check_and_set_router_abr(ospf6)) { + type5 = ospf6_lsdb_lookup( + htons(OSPF6_LSTYPE_AS_EXTERNAL), + lsa->external_lsa_id, ospf6->router_id, + ospf6->lsdb); + if (type5 + && CHECK_FLAG(type5->flag, OSPF6_LSA_LOCAL_XLT)) { + type5->header->age = htons(OSPF_LSA_MAXAGE); + SET_FLAG(type5->flag, OSPF6_LSA_FLUSH); + ospf6_flood(NULL, type5); + } + } + } +} + +static void ospf6_check_and_originate_type7_lsa(struct ospf6_area *area) +{ + struct ospf6_route *route; + struct route_node *rn = NULL; + struct ospf6_external_aggr_rt *aggr; + + /* Loop through the external_table to find the LSAs originated + * without aggregation and originate type-7 LSAs for them. + */ + for (route = ospf6_route_head( + area->ospf6->external_table); + route; route = ospf6_route_next(route)) { + struct ospf6_external_info *info = route->route_option; + + /* This means the Type-5 LSA was originated for this route */ + if (route->path.origin.id != 0 && info->type != DEFAULT_ROUTE) + ospf6_nssa_lsa_originate(route, area, true); + } + + /* Loop through the aggregation table to originate type-7 LSAs + * for the aggregated type-5 LSAs + */ + for (rn = route_top(area->ospf6->rt_aggr_tbl); rn; + rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_ORIGINATED)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug( + "Originating Type-7 LSAs for area %s", + area->name); + + ospf6_nssa_lsa_originate(aggr->route, area, true); + } + } +} + +static void ospf6_ase_lsa_refresh(struct ospf6 *o) +{ + struct ospf6_lsa *old; + + for (struct ospf6_route *route = ospf6_route_head(o->external_table); + route; route = ospf6_route_next(route)) { + old = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + route->path.origin.id, o->router_id, + o->lsdb); + if (old) { + EVENT_OFF(old->refresh); + event_add_event(master, ospf6_lsa_refresh, old, 0, + &old->refresh); + } else { + ospf6_as_external_lsa_originate(route, o); + } + } +} + +void ospf6_area_nssa_update(struct ospf6_area *area) +{ + if (IS_AREA_NSSA(area)) { + OSPF6_OPT_CLEAR(area->options, OSPF6_OPT_E); + area->ospf6->anyNSSA++; + OSPF6_OPT_SET(area->options, OSPF6_OPT_N); + area->NSSATranslatorRole = OSPF6_NSSA_ROLE_CANDIDATE; + } else if (IS_AREA_ENABLED(area)) { + if (IS_OSPF6_DEBUG_ORIGINATE(ROUTER)) + zlog_debug("Normal area for if %s", area->name); + OSPF6_OPT_CLEAR(area->options, OSPF6_OPT_N); + OSPF6_OPT_SET(area->options, OSPF6_OPT_E); + area->ospf6->anyNSSA--; + area->NSSATranslatorState = OSPF6_NSSA_TRANSLATE_DISABLED; + } + + /* Refresh router LSA */ + if (IS_AREA_NSSA(area)) { + OSPF6_ROUTER_LSA_SCHEDULE(area); + + /* Flush external LSAs. */ + ospf6_asbr_remove_externals_from_area(area); + + /* Check if router is ABR */ + if (ospf6_check_and_set_router_abr(area->ospf6)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Router is ABR area %s", area->name); + ospf6_schedule_abr_task(area->ospf6); + } else { + /* Router is not ABR */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("NSSA area %s", area->name); + + /* Originate NSSA LSA */ + ospf6_check_and_originate_type7_lsa(area); + } + } else { + /* Disable NSSA */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Normal area %s", area->name); + ospf6_nssa_flush_area(area); + + /* Check if router is ABR */ + if (ospf6_check_and_set_router_abr(area->ospf6)) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Router is ABR area %s", area->name); + ospf6_schedule_abr_task(area->ospf6); + ospf6_ase_lsa_refresh(area->ospf6); + } else { + uint16_t type; + struct ospf6_lsa *lsa = NULL; + + /* + * Refresh all type-5 LSAs so they get installed + * in the converted ares + */ + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("Refresh type-5 LSAs, area %s", + area->name); + + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + for (ALL_LSDB_TYPED_ADVRTR(area->ospf6->lsdb, type, + area->ospf6->router_id, + lsa)) { + if (IS_OSPF6_DEBUG_NSSA) + ospf6_lsa_header_print(lsa); + EVENT_OFF(lsa->refresh); + event_add_event(master, ospf6_lsa_refresh, lsa, + 0, &lsa->refresh); + } + } + } +} + +int ospf6_area_nssa_set(struct ospf6 *ospf6, struct ospf6_area *area) +{ + + if (!IS_AREA_NSSA(area)) { + /* Disable stub first. */ + ospf6_area_stub_unset(ospf6, area); + + SET_FLAG(area->flag, OSPF6_AREA_NSSA); + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("area %s nssa set", area->name); + ospf6_area_nssa_update(area); + } + + return 1; +} + +int ospf6_area_nssa_unset(struct ospf6 *ospf6, struct ospf6_area *area) +{ + if (IS_AREA_NSSA(area)) { + UNSET_FLAG(area->flag, OSPF6_AREA_NSSA); + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("area %s nssa reset", area->name); + + /* Clear the table of NSSA ranges. */ + ospf6_route_table_delete(area->nssa_range_table); + area->nssa_range_table = + OSPF6_ROUTE_TABLE_CREATE(AREA, PREFIX_RANGES); + area->nssa_range_table->scope = area; + + ospf6_area_nssa_update(area); + } + + return 1; +} + +/* Find the NSSA forwarding address */ +static struct in6_addr *ospf6_get_nssa_fwd_addr(struct ospf6_area *oa) +{ + struct listnode *node, *nnode; + struct ospf6_interface *oi; + + for (ALL_LIST_ELEMENTS(oa->if_list, node, nnode, oi)) { + struct in6_addr *addr; + + if (!if_is_operative(oi->interface)) + continue; + + addr = ospf6_interface_get_global_address(oi->interface); + if (addr) + return addr; + } + return NULL; +} + +void ospf6_nssa_lsa_originate(struct ospf6_route *route, + struct ospf6_area *area, bool p_bit) +{ + char buffer[OSPF6_MAX_LSASIZE]; + struct ospf6_lsa_header *lsa_header; + struct ospf6_lsa *lsa; + struct ospf6_external_info *info = route->route_option; + struct in6_addr *fwd_addr; + + struct ospf6_as_external_lsa *as_external_lsa; + caddr_t p; + + if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE(AS_EXTERNAL)) + zlog_debug("Originate NSSA-LSA for %pFX", &route->prefix); + + /* prepare buffer */ + memset(buffer, 0, sizeof(buffer)); + lsa_header = (struct ospf6_lsa_header *)buffer; + as_external_lsa = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa_header); + p = (caddr_t)((caddr_t)as_external_lsa + + sizeof(struct ospf6_as_external_lsa)); + + /* Fill AS-External-LSA */ + /* Metric type */ + if (route->path.metric_type == 2) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E); + + /* external route tag */ + if (info && info->tag) + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T); + + /* Set metric */ + OSPF6_ASBR_METRIC_SET(as_external_lsa, route->path.cost); + + /* prefixlen */ + as_external_lsa->prefix.prefix_length = route->prefix.prefixlen; + + /* PrefixOptions */ + as_external_lsa->prefix.prefix_options = route->prefix_options; + + /* Set the P bit */ + if (p_bit) + as_external_lsa->prefix.prefix_options |= OSPF6_PREFIX_OPTION_P; + + /* don't use refer LS-type */ + as_external_lsa->prefix.prefix_refer_lstype = htons(0); + + /* set Prefix */ + memcpy(p, &route->prefix.u.prefix6, + OSPF6_PREFIX_SPACE(route->prefix.prefixlen)); + ospf6_prefix_apply_mask(&as_external_lsa->prefix); + p += OSPF6_PREFIX_SPACE(route->prefix.prefixlen); + + /* Forwarding address */ + fwd_addr = ospf6_get_nssa_fwd_addr(area); + if (fwd_addr) { + memcpy(p, fwd_addr, sizeof(struct in6_addr)); + p += sizeof(struct in6_addr); + SET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + } else + UNSET_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F); + + /* External Route Tag */ + if (info + && CHECK_FLAG(as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T)) { + route_tag_t network_order = htonl(info->tag); + + memcpy(p, &network_order, sizeof(network_order)); + p += sizeof(network_order); + } + + /* Fill LSA Header */ + lsa_header->age = 0; + lsa_header->type = htons(OSPF6_LSTYPE_TYPE_7); + lsa_header->id = route->path.origin.id; + lsa_header->adv_router = area->ospf6->router_id; + lsa_header->seqnum = + ospf6_new_ls_seqnum(lsa_header->type, lsa_header->id, + lsa_header->adv_router, area->lsdb); + lsa_header->length = htons((caddr_t)p - (caddr_t)lsa_header); + + /* LSA checksum */ + ospf6_lsa_checksum(lsa_header); + /* create LSA */ + lsa = ospf6_lsa_create(lsa_header); + + /* Originate */ + ospf6_lsa_originate_area(lsa, area); +} + +void ospf6_abr_check_translate_nssa(struct ospf6_area *area, + struct ospf6_lsa *lsa) +{ + struct ospf6_lsa *type5 = NULL; + struct ospf6 *ospf6 = area->ospf6; + + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : start", __func__); + + if (!ospf6_check_and_set_router_abr(ospf6)) + return; + + type5 = ospf6_lsdb_lookup(htons(OSPF6_LSTYPE_AS_EXTERNAL), + lsa->external_lsa_id, ospf6->router_id, + ospf6->lsdb); + if (!type5) { + if (IS_OSPF6_DEBUG_NSSA) + zlog_debug("%s : Originating type5 LSA", __func__); + ospf6_lsa_translated_nssa_new(area, lsa); + } +} + +DEFPY (area_nssa_range, + area_nssa_range_cmd, + "area $area nssa range X:X::X:X/M$prefix []", + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as nssa\n" + "Configured address range\n" + "Specify IPv6 prefix\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + struct ospf6_area *oa; + struct ospf6_route *range; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(area, oa, ospf6); + + if (!IS_AREA_NSSA(oa)) { + vty_out(vty, "%% First configure %s as an NSSA area\n", area); + return CMD_WARNING; + } + + range = ospf6_route_lookup((struct prefix *)prefix, + oa->nssa_range_table); + if (range == NULL) { + range = ospf6_route_create(ospf6); + range->type = OSPF6_DEST_TYPE_RANGE; + SET_FLAG(range->flag, OSPF6_ROUTE_NSSA_RANGE); + prefix_copy(&range->prefix, prefix); + range->path.area_id = oa->area_id; + range->path.metric_type = 2; + range->path.cost = OSPF_AREA_RANGE_COST_UNSPEC; + range->path.origin.type = htons(OSPF6_LSTYPE_TYPE_7); + range->path.origin.id = htonl(ospf6->external_id++); + range->path.origin.adv_router = ospf6->router_id; + ospf6_route_add(range, oa->nssa_range_table); + } + + /* process "not-advertise" */ + if (not_adv) + SET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); + else + UNSET_FLAG(range->flag, OSPF6_ROUTE_DO_NOT_ADVERTISE); + + /* process "cost" */ + if (!cost_str) + cost = OSPF_AREA_RANGE_COST_UNSPEC; + range->path.u.cost_config = cost; + + /* Redo summaries if required */ + if (ospf6_check_and_set_router_abr(ospf6)) + ospf6_schedule_abr_task(ospf6); + + return CMD_SUCCESS; +} + +DEFPY (no_area_nssa_range, + no_area_nssa_range_cmd, + "no area $area nssa range X:X::X:X/M$prefix []", + NO_STR + "OSPF6 area parameters\n" + "OSPF6 area ID in IP address format\n" + "OSPF6 area ID as a decimal value\n" + "Configure OSPF6 area as nssa\n" + "Configured address range\n" + "Specify IPv6 prefix\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + struct ospf6_area *oa; + struct ospf6_route *range; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + OSPF6_CMD_AREA_GET(area, oa, ospf6); + + range = ospf6_route_lookup((struct prefix *)prefix, + oa->nssa_range_table); + if (range == NULL) { + vty_out(vty, "%% range %s does not exist.\n", prefix_str); + return CMD_SUCCESS; + } + + if (ospf6_check_and_set_router_abr(oa->ospf6)) { + /* Blow away the aggregated LSA and route */ + SET_FLAG(range->flag, OSPF6_ROUTE_REMOVE); + + /* Redo summaries if required */ + event_execute(master, ospf6_abr_task_timer, ospf6, 0, NULL); + } + + ospf6_route_remove(range, oa->nssa_range_table); + + return CMD_SUCCESS; +} + +DEFUN(debug_ospf6_nssa, debug_ospf6_nssa_cmd, + "debug ospf6 nssa", + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 NSSA function\n") +{ + OSPF6_DEBUG_NSSA_ON(); + return CMD_SUCCESS; +} + +DEFUN(no_debug_ospf6_nssa, no_debug_ospf6_nssa_cmd, + "no debug ospf6 nssa", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug OSPFv3 NSSA function\n") +{ + OSPF6_DEBUG_NSSA_OFF(); + return CMD_SUCCESS; +} + +void config_write_ospf6_debug_nssa(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_NSSA) + vty_out(vty, "debug ospf6 nssa\n"); +} + +void install_element_ospf6_debug_nssa(void) +{ + install_element(OSPF6_NODE, &area_nssa_range_cmd); + install_element(OSPF6_NODE, &no_area_nssa_range_cmd); + + install_element(ENABLE_NODE, &debug_ospf6_nssa_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_nssa_cmd); + install_element(CONFIG_NODE, &debug_ospf6_nssa_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_nssa_cmd); +} diff --git a/ospf6d/ospf6_nssa.h b/ospf6d/ospf6_nssa.h new file mode 100644 index 0000000..a6bafe5 --- /dev/null +++ b/ospf6d/ospf6_nssa.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFv3 Not So Stubby Area implementation. + * + * Copyright (C) 2021 Kaushik Nath + * Copyright (C) 2021 Soman K.S + */ + +#ifndef OSPF6_NSSA_H +#define OSPF6_NSSA_H + +#define OSPF6_OPTION_NP 0x08 +#define OSPF6_LS_INFINITY 0xffffff + +#define OSPF6_OPT_N (1 << 3) /* Handling Type-7 LSA Capability */ + +#define OSPF6_DEBUG_NSSA 0x09 +/* Debug option */ +extern unsigned char config_debug_ospf6_nssa; + +#define OSPF6_DEBUG_NSSA_ON() (config_debug_ospf6_nssa = 1) +#define OSPF6_DEBUG_NSSA_OFF() (config_debug_ospf6_nssa = 0) +#define IS_OSPF6_DEBUG_NSSA config_debug_ospf6_nssa + +#define CHECK_LSA_TYPE_1_TO_5_OR_7(type) \ + ((type == OSPF6_ROUTER_LSA_MIN_SIZE) \ + || (type == OSPF6_NETWORK_LSA_MIN_SIZE) \ + || (type == OSPF6_LINK_LSA_MIN_SIZE) \ + || (type == OSPF6_INTRA_PREFIX_LSA_MIN_SIZE) \ + || (type == OSPF6_AS_NSSA_LSA)) + +#define OSPF6_LSA_APPROVED 0x08 +#define OSPF6_LSA_LOCAL_XLT 0x40 + +#define OSPF6_ABR_TASK_DELAY 5 + +int ospf6_area_nssa_no_summary_set(struct ospf6 *ospf6, struct in_addr area_id); +int ospf6_area_nssa_unset(struct ospf6 *ospf6, struct ospf6_area *area); +int ospf6_area_nssa_set(struct ospf6 *ospf6, struct ospf6_area *area); + +extern void ospf6_nssa_lsa_flush(struct ospf6 *ospf6, struct prefix_ipv6 *p); +extern struct ospf6_lsa *ospf6_translated_nssa_refresh(struct ospf6_area *oa, + struct ospf6_lsa *type7, + struct ospf6_lsa *type5); + +extern void ospf6_asbr_nssa_redist_task(struct ospf6 *ospf6); + +extern void ospf6_schedule_abr_task(struct ospf6 *ospf6); +extern void ospf6_area_nssa_update(struct ospf6_area *area); +void ospf6_asbr_prefix_readvertise(struct ospf6 *ospf6); +extern void ospf6_nssa_lsa_originate(struct ospf6_route *route, + struct ospf6_area *area, bool p_bit); +extern void install_element_ospf6_debug_nssa(void); +extern void ospf6_abr_nssa_type_7_defaults(struct ospf6 *osof6); +int ospf6_redistribute_check(struct ospf6 *ospf6, struct ospf6_route *route, + int type); +extern void ospf6_abr_check_translate_nssa(struct ospf6_area *area, + struct ospf6_lsa *lsa); +extern void ospf6_abr_nssa_check_status(struct ospf6 *ospf6); +extern void config_write_ospf6_debug_nssa(struct vty *vty); +#endif /* OSPF6_NSSA_H */ diff --git a/ospf6d/ospf6_proto.c b/ospf6d/ospf6_proto.c new file mode 100644 index 0000000..09b7b21 --- /dev/null +++ b/ospf6d/ospf6_proto.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" + +#include "ospf6_proto.h" + +void ospf6_prefix_in6_addr(struct in6_addr *in6, const void *prefix_buf, + const struct ospf6_prefix *p) +{ + ptrdiff_t in6_off = (caddr_t)p->addr - (caddr_t)prefix_buf; + + memset(in6, 0, sizeof(struct in6_addr)); + memcpy(in6, (uint8_t *)prefix_buf + in6_off, + OSPF6_PREFIX_SPACE(p->prefix_length)); +} + +void ospf6_prefix_apply_mask(struct ospf6_prefix *op) +{ + uint8_t *pnt, mask; + int index, offset; + + pnt = (uint8_t *)((caddr_t)op + sizeof(struct ospf6_prefix)); + index = op->prefix_length / 8; + offset = op->prefix_length % 8; + mask = 0xff << (8 - offset); + + if (index > 16) { + zlog_warn("Prefix length too long: %d", op->prefix_length); + return; + } + + /* nonzero mask means no check for this byte because if it contains + * prefix bits it must be there for us to write */ + if (mask) + pnt[index++] &= mask; + + while (index < OSPF6_PREFIX_SPACE(op->prefix_length)) + pnt[index++] = 0; +} + +void ospf6_prefix_options_printbuf(uint8_t prefix_options, char *buf, int size) +{ + const char *dn, *p, *mc, *la, *nu; + + dn = (CHECK_FLAG(prefix_options, OSPF6_PREFIX_OPTION_DN) ? "DN" : "--"); + p = (CHECK_FLAG(prefix_options, OSPF6_PREFIX_OPTION_P) ? "P" : "--"); + mc = (CHECK_FLAG(prefix_options, OSPF6_PREFIX_OPTION_MC) ? "MC" : "--"); + la = (CHECK_FLAG(prefix_options, OSPF6_PREFIX_OPTION_LA) ? "LA" : "--"); + nu = (CHECK_FLAG(prefix_options, OSPF6_PREFIX_OPTION_NU) ? "NU" : "--"); + snprintf(buf, size, "%s|%s|%s|%s|%s", dn, p, mc, la, nu); +} + +void ospf6_capability_printbuf(char capability, char *buf, int size) +{ + char w, v, e, b; + w = (capability & OSPF6_ROUTER_BIT_W ? 'W' : '-'); + v = (capability & OSPF6_ROUTER_BIT_V ? 'V' : '-'); + e = (capability & OSPF6_ROUTER_BIT_E ? 'E' : '-'); + b = (capability & OSPF6_ROUTER_BIT_B ? 'B' : '-'); + snprintf(buf, size, "----%c%c%c%c", w, v, e, b); +} + +void ospf6_options_printbuf(uint8_t *options, char *buf, int size) +{ + const char *dc, *r, *n, *mc, *e, *v6, *af, *at, *l; + dc = (OSPF6_OPT_ISSET(options, OSPF6_OPT_DC) ? "DC" : "--"); + r = (OSPF6_OPT_ISSET(options, OSPF6_OPT_R) ? "R" : "-"); + n = (OSPF6_OPT_ISSET(options, OSPF6_OPT_N) ? "N" : "-"); + mc = (OSPF6_OPT_ISSET(options, OSPF6_OPT_MC) ? "MC" : "--"); + e = (OSPF6_OPT_ISSET(options, OSPF6_OPT_E) ? "E" : "-"); + v6 = (OSPF6_OPT_ISSET(options, OSPF6_OPT_V6) ? "V6" : "--"); + af = (OSPF6_OPT_ISSET_EXT(options, OSPF6_OPT_AF) ? "AF" : "--"); + at = (OSPF6_OPT_ISSET_EXT(options, OSPF6_OPT_AT) ? "AT" : "--"); + l = (OSPF6_OPT_ISSET_EXT(options, OSPF6_OPT_L) ? "L" : "-"); + snprintf(buf, size, "%s|%s|%s|-|-|%s|%s|%s|%s|%s|%s", at, l, af, dc, r, + n, mc, e, v6); +} diff --git a/ospf6d/ospf6_proto.h b/ospf6d/ospf6_proto.h new file mode 100644 index 0000000..4307ee3 --- /dev/null +++ b/ospf6d/ospf6_proto.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Common protocol data and data structures. + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_PROTO_H +#define OSPF6_PROTO_H + +/* OSPF protocol version */ +#define OSPFV3_VERSION 3 + +/* TOS field normaly null */ +#define DEFAULT_TOS_VALUE 0x0 + +#define ALLSPFROUTERS6 "ff02::5" +#define ALLDROUTERS6 "ff02::6" + +#define OSPF6_ROUTER_BIT_W (1 << 3) +#define OSPF6_ROUTER_BIT_V (1 << 2) +#define OSPF6_ROUTER_BIT_E (1 << 1) +#define OSPF6_ROUTER_BIT_B (1 << 0) +#define OSPF6_ROUTER_BIT_NT (1 << 4) + + +/* OSPF options */ +/* present in HELLO, DD, LSA */ +#define OSPF6_OPT_SET(x, opt) ((x)[2] |= (opt)) +#define OSPF6_OPT_ISSET(x, opt) ((x)[2] & (opt)) +#define OSPF6_OPT_CLEAR(x, opt) ((x)[2] &= ~(opt)) +#define OSPF6_OPT_SET_EXT(x, opt) ((x)[1] |= (opt)) +#define OSPF6_OPT_ISSET_EXT(x, opt) ((x)[1] & (opt)) +#define OSPF6_OPT_CLEAR_EXT(x, opt) ((x)[1] &= ~(opt)) +#define OSPF6_OPT_CLEAR_ALL(x) ((x)[0] = (x)[1] = (x)[2] = 0) + +#define OSPF6_OPT_AT (1 << 2) /* Authentication trailer Capability */ +#define OSPF6_OPT_L (1 << 1) /* Link local signalling Capability */ +#define OSPF6_OPT_AF (1 << 0) /* Address family Capability */ +/* 2 bits reserved for OSPFv2 migrated options */ +#define OSPF6_OPT_DC (1 << 5) /* Demand Circuit handling Capability */ +#define OSPF6_OPT_R (1 << 4) /* Forwarding Capability (Any Protocol) */ +#define OSPF6_OPT_N (1 << 3) /* Handling Type-7 LSA Capability */ +#define OSPF6_OPT_MC (1 << 2) /* Multicasting Capability */ +#define OSPF6_OPT_E (1 << 1) /* AS External Capability */ +#define OSPF6_OPT_V6 (1 << 0) /* IPv6 forwarding Capability */ + +/* OSPF6 Prefix */ +#define OSPF6_PREFIX_MIN_SIZE 4U /* .length == 0 */ +struct ospf6_prefix { + uint8_t prefix_length; + uint8_t prefix_options; + union { + uint16_t _prefix_metric; + uint16_t _prefix_referenced_lstype; + } u; +#define prefix_metric u._prefix_metric +#define prefix_refer_lstype u._prefix_referenced_lstype + /* followed by one address_prefix */ + struct in6_addr addr[]; +}; + +#define OSPF6_PREFIX_OPTION_NU (1 << 0) /* No Unicast */ +#define OSPF6_PREFIX_OPTION_LA (1 << 1) /* Local Address */ +#define OSPF6_PREFIX_OPTION_MC (1 << 2) /* MultiCast */ +#define OSPF6_PREFIX_OPTION_P (1 << 3) /* Propagate (NSSA) */ +#define OSPF6_PREFIX_OPTION_DN \ + (1 << 4) /* DN bit to prevent loops in VPN environment */ + +/* caddr_t OSPF6_PREFIX_BODY (struct ospf6_prefix *); */ +#define OSPF6_PREFIX_BODY(x) ((caddr_t)(x) + sizeof(struct ospf6_prefix)) + +/* size_t OSPF6_PREFIX_SPACE (int prefixlength); */ +#define OSPF6_PREFIX_SPACE(x) ((((x) + 31) / 32) * 4) + +/* size_t OSPF6_PREFIX_SIZE (struct ospf6_prefix *); */ +#define OSPF6_PREFIX_SIZE(x) \ + (OSPF6_PREFIX_SPACE((x)->prefix_length) + sizeof(struct ospf6_prefix)) + +/* struct ospf6_prefix *OSPF6_PREFIX_NEXT (struct ospf6_prefix *); */ +#define OSPF6_PREFIX_NEXT(x) \ + ((struct ospf6_prefix *)((caddr_t)(x) + OSPF6_PREFIX_SIZE(x))) + +extern void ospf6_prefix_in6_addr(struct in6_addr *in6, const void *prefix_buf, + const struct ospf6_prefix *p); +extern void ospf6_prefix_apply_mask(struct ospf6_prefix *op); +extern void ospf6_prefix_options_printbuf(uint8_t prefix_options, char *buf, + int size); +extern void ospf6_capability_printbuf(char capability, char *buf, int size); +extern void ospf6_options_printbuf(uint8_t *options, char *buf, int size); + +#endif /* OSPF6_PROTO_H */ diff --git a/ospf6d/ospf6_route.c b/ospf6d/ospf6_route.c new file mode 100644 index 0000000..10a1208 --- /dev/null +++ b/ospf6d/ospf6_route.c @@ -0,0 +1,1860 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "linklist.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6d.h" +#include "ospf6_zebra.h" +#include "ospf6d/ospf6_route_clippy.c" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_ROUTE, "OSPF6 route"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_ROUTE_TABLE, "OSPF6 route table"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_NEXTHOP, "OSPF6 nexthop"); +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_PATH, "OSPF6 Path"); + +unsigned char conf_debug_ospf6_route = 0; + +static char *ospf6_route_table_name(struct ospf6_route_table *table) +{ + static char name[64]; + switch (table->scope_type) { + case OSPF6_SCOPE_TYPE_GLOBAL: { + switch (table->table_type) { + case OSPF6_TABLE_TYPE_ROUTES: + snprintf(name, sizeof(name), "global route table"); + break; + case OSPF6_TABLE_TYPE_BORDER_ROUTERS: + snprintf(name, sizeof(name), "global brouter table"); + break; + case OSPF6_TABLE_TYPE_EXTERNAL_ROUTES: + snprintf(name, sizeof(name), "global external table"); + break; + default: + snprintf(name, sizeof(name), "global unknown table"); + break; + } + } break; + + case OSPF6_SCOPE_TYPE_AREA: { + struct ospf6_area *oa = (struct ospf6_area *)table->scope; + switch (table->table_type) { + case OSPF6_TABLE_TYPE_SPF_RESULTS: + snprintf(name, sizeof(name), "area %s spf table", + oa->name); + break; + case OSPF6_TABLE_TYPE_ROUTES: + snprintf(name, sizeof(name), "area %s route table", + oa->name); + break; + case OSPF6_TABLE_TYPE_PREFIX_RANGES: + snprintf(name, sizeof(name), "area %s range table", + oa->name); + break; + case OSPF6_TABLE_TYPE_SUMMARY_PREFIXES: + snprintf(name, sizeof(name), + "area %s summary prefix table", oa->name); + break; + case OSPF6_TABLE_TYPE_SUMMARY_ROUTERS: + snprintf(name, sizeof(name), + "area %s summary router table", oa->name); + break; + default: + snprintf(name, sizeof(name), "area %s unknown table", + oa->name); + break; + } + } break; + + case OSPF6_SCOPE_TYPE_INTERFACE: { + struct ospf6_interface *oi = + (struct ospf6_interface *)table->scope; + switch (table->table_type) { + case OSPF6_TABLE_TYPE_CONNECTED_ROUTES: + snprintf(name, sizeof(name), + "interface %s connected table", + oi->interface->name); + break; + default: + snprintf(name, sizeof(name), + "interface %s unknown table", + oi->interface->name); + break; + } + } break; + + default: { + switch (table->table_type) { + case OSPF6_TABLE_TYPE_SPF_RESULTS: + snprintf(name, sizeof(name), "temporary spf table"); + break; + default: + snprintf(name, sizeof(name), "temporary unknown table"); + break; + } + } break; + } + return name; +} + +void ospf6_linkstate_prefix(uint32_t adv_router, uint32_t id, + struct prefix *prefix) +{ + memset(prefix, 0, sizeof(struct prefix)); + prefix->family = AF_INET6; + prefix->prefixlen = 64; + memcpy(&prefix->u.prefix6.s6_addr[0], &adv_router, 4); + memcpy(&prefix->u.prefix6.s6_addr[4], &id, 4); +} + +void ospf6_linkstate_prefix2str(struct prefix *prefix, char *buf, int size) +{ + uint32_t adv_router, id; + char adv_router_str[16], id_str[16]; + memcpy(&adv_router, &prefix->u.prefix6.s6_addr[0], 4); + memcpy(&id, &prefix->u.prefix6.s6_addr[4], 4); + inet_ntop(AF_INET, &adv_router, adv_router_str, sizeof(adv_router_str)); + inet_ntop(AF_INET, &id, id_str, sizeof(id_str)); + if (ntohl(id)) + snprintf(buf, size, "%s Net-ID: %s", adv_router_str, id_str); + else + snprintf(buf, size, "%s", adv_router_str); +} + +/* Global strings for logging */ +const char *const ospf6_dest_type_str[OSPF6_DEST_TYPE_MAX] = { + "Unknown", "Router", "Network", "Discard", "Linkstate", "AddressRange", +}; + +const char *const ospf6_dest_type_substr[OSPF6_DEST_TYPE_MAX] = { + "?", "R", "N", "D", "L", "A", +}; + +const char *const ospf6_path_type_str[OSPF6_PATH_TYPE_MAX] = { + "Unknown", "Intra-Area", "Inter-Area", "External-1", "External-2", +}; + +const char *const ospf6_path_type_substr[OSPF6_PATH_TYPE_MAX] = { + "??", "IA", "IE", "E1", "E2", +}; + +const char *ospf6_path_type_json[OSPF6_PATH_TYPE_MAX] = { + "UnknownRoute", "IntraArea", "InterArea", "External1", "External2", +}; + + +struct ospf6_nexthop *ospf6_nexthop_create(void) +{ + struct ospf6_nexthop *nh; + + nh = XCALLOC(MTYPE_OSPF6_NEXTHOP, sizeof(struct ospf6_nexthop)); + return nh; +} + +void ospf6_nexthop_delete(struct ospf6_nexthop *nh) +{ + XFREE(MTYPE_OSPF6_NEXTHOP, nh); +} + +void ospf6_clear_nexthops(struct list *nh_list) +{ + struct listnode *node; + struct ospf6_nexthop *nh; + + if (nh_list) { + for (ALL_LIST_ELEMENTS_RO(nh_list, node, nh)) + ospf6_nexthop_clear(nh); + } +} + +static struct ospf6_nexthop * +ospf6_route_find_nexthop(struct list *nh_list, struct ospf6_nexthop *nh_match) +{ + struct listnode *node; + struct ospf6_nexthop *nh; + + if (nh_list && nh_match) { + for (ALL_LIST_ELEMENTS_RO(nh_list, node, nh)) { + if (ospf6_nexthop_is_same(nh, nh_match)) + return (nh); + } + } + + return (NULL); +} + +void ospf6_copy_nexthops(struct list *dst, struct list *src) +{ + struct ospf6_nexthop *nh_new, *nh; + struct listnode *node; + + if (dst && src) { + for (ALL_LIST_ELEMENTS_RO(src, node, nh)) { + if (ospf6_nexthop_is_set(nh)) { + nh_new = ospf6_nexthop_create(); + ospf6_nexthop_copy(nh_new, nh); + listnode_add_sort(dst, nh_new); + } + } + } +} + +void ospf6_merge_nexthops(struct list *dst, struct list *src) +{ + struct listnode *node; + struct ospf6_nexthop *nh, *nh_new; + + if (src && dst) { + for (ALL_LIST_ELEMENTS_RO(src, node, nh)) { + if (!ospf6_route_find_nexthop(dst, nh)) { + nh_new = ospf6_nexthop_create(); + ospf6_nexthop_copy(nh_new, nh); + listnode_add_sort(dst, nh_new); + } + } + } +} + +/* + * If the nexthops are the same return true + */ +bool ospf6_route_cmp_nexthops(struct ospf6_route *a, struct ospf6_route *b) +{ + struct listnode *anode, *bnode; + struct ospf6_nexthop *anh, *bnh; + bool identical = false; + + if (a && b) { + if (listcount(a->nh_list) == listcount(b->nh_list)) { + for (ALL_LIST_ELEMENTS_RO(a->nh_list, anode, anh)) { + identical = false; + for (ALL_LIST_ELEMENTS_RO(b->nh_list, bnode, + bnh)) { + if (ospf6_nexthop_is_same(anh, bnh)) + identical = true; + } + /* Currnet List A element not found List B + * Non-Identical lists return */ + if (identical == false) + return false; + } + return true; + } else + return false; + } + /* One of the routes doesn't exist ? */ + return false; +} + +int ospf6_num_nexthops(struct list *nh_list) +{ + return (listcount(nh_list)); +} + +void ospf6_add_nexthop(struct list *nh_list, int ifindex, + const struct in6_addr *addr) +{ + struct ospf6_nexthop *nh; + struct ospf6_nexthop nh_match; + + if (nh_list) { + if (addr) { + if (ifindex) + nh_match.type = NEXTHOP_TYPE_IPV6_IFINDEX; + else + nh_match.type = NEXTHOP_TYPE_IPV6; + + memcpy(&nh_match.address, addr, + sizeof(struct in6_addr)); + } else { + nh_match.type = NEXTHOP_TYPE_IFINDEX; + + memset(&nh_match.address, 0, sizeof(struct in6_addr)); + } + + nh_match.ifindex = ifindex; + + if (!ospf6_route_find_nexthop(nh_list, &nh_match)) { + nh = ospf6_nexthop_create(); + ospf6_nexthop_copy(nh, &nh_match); + listnode_add(nh_list, nh); + } + } +} + +void ospf6_add_route_nexthop_blackhole(struct ospf6_route *route) +{ + struct ospf6_nexthop *nh; + struct ospf6_nexthop nh_match = {}; + + /* List not allocated. */ + if (route->nh_list == NULL) + return; + + /* Entry already exists. */ + nh_match.type = NEXTHOP_TYPE_BLACKHOLE; + if (ospf6_route_find_nexthop(route->nh_list, &nh_match)) + return; + + nh = ospf6_nexthop_create(); + ospf6_nexthop_copy(nh, &nh_match); + listnode_add(route->nh_list, nh); +} + +void ospf6_route_zebra_copy_nexthops(struct ospf6_route *route, + struct zapi_nexthop nexthops[], + int entries, vrf_id_t vrf_id) +{ + struct ospf6_nexthop *nh; + struct listnode *node; + int i; + + if (route) { + i = 0; + for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) { + zlog_debug(" nexthop: %s %pI6%%%.*s(%d)", + nexthop_type_to_str(nh->type), + &nh->address, IFNAMSIZ, + ifindex2ifname(nh->ifindex, vrf_id), + nh->ifindex); + } + + if (i >= entries) + return; + + nexthops[i].vrf_id = vrf_id; + nexthops[i].type = nh->type; + + switch (nh->type) { + case NEXTHOP_TYPE_BLACKHOLE: + /* NOTHING */ + break; + + case NEXTHOP_TYPE_IFINDEX: + nexthops[i].ifindex = nh->ifindex; + break; + + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + /* + * OSPFv3 with IPv4 routes is not supported + * yet. Skip this next hop. + */ + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" Skipping IPv4 next hop"); + continue; + + case NEXTHOP_TYPE_IPV6_IFINDEX: + nexthops[i].ifindex = nh->ifindex; + fallthrough; + case NEXTHOP_TYPE_IPV6: + nexthops[i].gate.ipv6 = nh->address; + break; + } + i++; + } + } +} + +int ospf6_route_get_first_nh_index(struct ospf6_route *route) +{ + struct ospf6_nexthop *nh; + + if (route) { + nh = listnode_head(route->nh_list); + if (nh) + return nh->ifindex; + } + + return -1; +} + +int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b) +{ + if (a->ifindex < b->ifindex) + return -1; + else if (a->ifindex > b->ifindex) + return 1; + else + return memcmp(&a->address, &b->address, + sizeof(struct in6_addr)); +} + +static int ospf6_path_cmp(struct ospf6_path *a, struct ospf6_path *b) +{ + if (a->origin.adv_router < b->origin.adv_router) + return -1; + else if (a->origin.adv_router > b->origin.adv_router) + return 1; + else + return 0; +} + +void ospf6_path_free(struct ospf6_path *op) +{ + if (op->nh_list) + list_delete(&op->nh_list); + XFREE(MTYPE_OSPF6_PATH, op); +} + +struct ospf6_path *ospf6_path_dup(struct ospf6_path *path) +{ + struct ospf6_path *new; + + new = XCALLOC(MTYPE_OSPF6_PATH, sizeof(struct ospf6_path)); + memcpy(new, path, sizeof(struct ospf6_path)); + new->nh_list = list_new(); + new->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; + new->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; + + return new; +} + +void ospf6_copy_paths(struct list *dst, struct list *src) +{ + struct ospf6_path *path_new, *path; + struct listnode *node; + + if (dst && src) { + for (ALL_LIST_ELEMENTS_RO(src, node, path)) { + path_new = ospf6_path_dup(path); + ospf6_copy_nexthops(path_new->nh_list, path->nh_list); + listnode_add_sort(dst, path_new); + } + } +} + +struct ospf6_route *ospf6_route_create(struct ospf6 *ospf6) +{ + struct ospf6_route *route; + + route = XCALLOC(MTYPE_OSPF6_ROUTE, sizeof(struct ospf6_route)); + route->nh_list = list_new(); + route->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; + route->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; + route->paths = list_new(); + route->paths->cmp = (int (*)(void *, void *))ospf6_path_cmp; + route->paths->del = (void (*)(void *))ospf6_path_free; + route->ospf6 = ospf6; + + return route; +} + +void ospf6_route_delete(struct ospf6_route *route) +{ + if (route) { + if (route->nh_list) + list_delete(&route->nh_list); + if (route->paths) + list_delete(&route->paths); + XFREE(MTYPE_OSPF6_ROUTE, route); + } +} + +struct ospf6_route *ospf6_route_copy(struct ospf6_route *route) +{ + struct ospf6_route *new; + + new = ospf6_route_create(route->ospf6); + new->type = route->type; + memcpy(&new->prefix, &route->prefix, sizeof(struct prefix)); + new->prefix_options = route->prefix_options; + new->installed = route->installed; + new->changed = route->changed; + new->flag = route->flag; + new->route_option = route->route_option; + new->linkstate_id = route->linkstate_id; + new->path = route->path; + ospf6_copy_nexthops(new->nh_list, route->nh_list); + ospf6_copy_paths(new->paths, route->paths); + new->rnode = NULL; + new->prev = NULL; + new->next = NULL; + new->table = NULL; + new->lock = 0; + return new; +} + +void ospf6_route_lock(struct ospf6_route *route) +{ + route->lock++; +} + +void ospf6_route_unlock(struct ospf6_route *route) +{ + assert(route->lock > 0); + route->lock--; + if (route->lock == 0) { + /* Can't detach from the table until here + because ospf6_route_next () will use + the 'route->table' pointer for logging */ + route->table = NULL; + ospf6_route_delete(route); + } +} + +/* Route compare function. If ra is more preferred, it returns + less than 0. If rb is more preferred returns greater than 0. + Otherwise (neither one is preferred), returns 0 */ +int ospf6_route_cmp(struct ospf6_route *ra, struct ospf6_route *rb) +{ + assert(ospf6_route_is_same(ra, rb)); + assert(OSPF6_PATH_TYPE_NONE < ra->path.type + && ra->path.type < OSPF6_PATH_TYPE_MAX); + assert(OSPF6_PATH_TYPE_NONE < rb->path.type + && rb->path.type < OSPF6_PATH_TYPE_MAX); + + if (ra->type != rb->type) + return (ra->type - rb->type); + + if (ra->path.type != rb->path.type) + return (ra->path.type - rb->path.type); + + if (ra->path.type == OSPF6_PATH_TYPE_EXTERNAL2) { + if (ra->path.u.cost_e2 != rb->path.u.cost_e2) + return (ra->path.u.cost_e2 - rb->path.u.cost_e2); + else + return (ra->path.cost - rb->path.cost); + } else { + if (ra->path.cost != rb->path.cost) + return (ra->path.cost - rb->path.cost); + } + + if (ra->path.area_id != rb->path.area_id) + return (ntohl(ra->path.area_id) - ntohl(rb->path.area_id)); + + if ((ra->prefix_options & OSPF6_PREFIX_OPTION_LA) + != (rb->prefix_options & OSPF6_PREFIX_OPTION_LA)) + return ra->prefix_options & OSPF6_PREFIX_OPTION_LA ? -1 : 1; + + return 0; +} + +struct ospf6_route *ospf6_route_lookup(struct prefix *prefix, + struct ospf6_route_table *table) +{ + struct route_node *node; + struct ospf6_route *route; + + node = route_node_lookup(table->table, prefix); + if (node == NULL) + return NULL; + + route = (struct ospf6_route *)node->info; + route_unlock_node(node); /* to free the lookup lock */ + return route; +} + +struct ospf6_route * +ospf6_route_lookup_identical(struct ospf6_route *route, + struct ospf6_route_table *table) +{ + struct ospf6_route *target; + + for (target = ospf6_route_lookup(&route->prefix, table); target; + target = target->next) { + if (ospf6_route_is_identical(target, route)) + return target; + } + return NULL; +} + +struct ospf6_route * +ospf6_route_lookup_bestmatch(struct prefix *prefix, + struct ospf6_route_table *table) +{ + struct route_node *node; + struct ospf6_route *route; + + node = route_node_match(table->table, prefix); + if (node == NULL) + return NULL; + route_unlock_node(node); + + route = (struct ospf6_route *)node->info; + return route; +} + +#ifdef DEBUG +static void route_table_assert(struct ospf6_route_table *table) +{ + struct ospf6_route *prev, *r, *next; + unsigned int link_error = 0, num = 0; + + r = ospf6_route_head(table); + prev = NULL; + while (r) { + if (r->prev != prev) + link_error++; + + next = ospf6_route_next(r); + + if (r->next != next) + link_error++; + + prev = r; + r = next; + } + + for (r = ospf6_route_head(table); r; r = ospf6_route_next(r)) + num++; + + if (link_error == 0 && num == table->count) + return; + + flog_err(EC_LIB_DEVELOPMENT, "PANIC !!"); + flog_err(EC_LIB_DEVELOPMENT, + "Something has gone wrong with ospf6_route_table[%p]", table); + zlog_debug("table count = %d, real number = %d", table->count, num); + zlog_debug("DUMP START"); + for (r = ospf6_route_head(table); r; r = ospf6_route_next(r)) + zlog_info("%p<-[%p]->%p : %pFX", r->prev, r, r->next, + &r->prefix); + zlog_debug("DUMP END"); + + assert(link_error == 0 && num == table->count); +} +#define ospf6_route_table_assert(t) (route_table_assert (t)) +#else +#define ospf6_route_table_assert(t) ((void) 0) +#endif /*DEBUG*/ + +struct ospf6_route *ospf6_route_add(struct ospf6_route *route, + struct ospf6_route_table *table) +{ + struct route_node *node, *nextnode, *prevnode; + struct ospf6_route *current = NULL; + struct ospf6_route *prev = NULL, *old = NULL, *next = NULL; + char buf[PREFIX2STR_BUFFER]; + struct timeval now; + + assert(route->rnode == NULL); + assert(route->lock == 0); + assert(route->next == NULL); + assert(route->prev == NULL); + + if (route->type == OSPF6_DEST_TYPE_LINKSTATE) + ospf6_linkstate_prefix2str(&route->prefix, buf, sizeof(buf)); + else if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&route->prefix), buf, + sizeof(buf)); + else + prefix2str(&route->prefix, buf, sizeof(buf)); + + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s %p: route add %p: %s paths %u nh %u", + ospf6_route_table_name(table), (void *)table, + (void *)route, buf, listcount(route->paths), + listcount(route->nh_list)); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route add: %s", ospf6_route_table_name(table), + buf); + + monotime(&now); + + node = route_node_get(table->table, &route->prefix); + route->rnode = node; + + /* find place to insert */ + for (current = node->info; current; current = current->next) { + if (!ospf6_route_is_same(current, route)) + next = current; + else if (current->type != route->type) + prev = current; + else if (ospf6_route_is_same_origin(current, route)) + old = current; + else if (ospf6_route_cmp(current, route) > 0) + next = current; + else + prev = current; + + if (old || next) + break; + } + + if (old) { + /* if route does not actually change, return unchanged */ + if (ospf6_route_is_identical(old, route)) { + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug( + "%s %p: route add %p: needless update of %p old cost %u", + ospf6_route_table_name(table), + (void *)table, (void *)route, + (void *)old, old->path.cost); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route add: needless update", + ospf6_route_table_name(table)); + + ospf6_route_delete(route); + SET_FLAG(old->flag, OSPF6_ROUTE_ADD); + ospf6_route_table_assert(table); + + /* to free the lookup lock */ + route_unlock_node(node); + return old; + } + + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug( + "%s %p: route add %p cost %u paths %u nh %u: update of %p cost %u paths %u nh %u", + ospf6_route_table_name(table), (void *)table, + (void *)route, route->path.cost, + listcount(route->paths), + listcount(route->nh_list), (void *)old, + old->path.cost, listcount(old->paths), + listcount(old->nh_list)); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route add: update", + ospf6_route_table_name(table)); + + /* replace old one if exists */ + if (node->info == old) { + node->info = route; + SET_FLAG(route->flag, OSPF6_ROUTE_BEST); + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s: replace old route %s", + __func__, buf); + } + + if (old->prev) + old->prev->next = route; + route->prev = old->prev; + if (old->next) + old->next->prev = route; + route->next = old->next; + + route->installed = old->installed; + route->changed = now; + assert(route->table == NULL); + route->table = table; + + ospf6_route_unlock(old); /* will be deleted later */ + ospf6_route_lock(route); + + SET_FLAG(route->flag, OSPF6_ROUTE_CHANGE); + ospf6_route_table_assert(table); + + if (table->hook_add) + (*table->hook_add)(route); + + return route; + } + + /* insert if previous or next node found */ + if (prev || next) { + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug( + "%s %p: route add %p cost %u: another path: prev %p, next %p node ref %u", + ospf6_route_table_name(table), (void *)table, + (void *)route, route->path.cost, (void *)prev, + (void *)next, route_node_get_lock_count(node)); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route add cost %u: another path found", + ospf6_route_table_name(table), + route->path.cost); + + if (prev == NULL) + prev = next->prev; + if (next == NULL) + next = prev->next; + + if (prev) + prev->next = route; + route->prev = prev; + if (next) + next->prev = route; + route->next = next; + + if (node->info == next) { + assert(next && next->rnode == node); + node->info = route; + UNSET_FLAG(next->flag, OSPF6_ROUTE_BEST); + SET_FLAG(route->flag, OSPF6_ROUTE_BEST); + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug( + "%s %p: route add %p cost %u: replacing previous best: %p cost %u", + ospf6_route_table_name(table), + (void *)table, (void *)route, + route->path.cost, (void *)next, + next->path.cost); + } + + route->installed = now; + route->changed = now; + assert(route->table == NULL); + route->table = table; + + ospf6_route_lock(route); + table->count++; + ospf6_route_table_assert(table); + + SET_FLAG(route->flag, OSPF6_ROUTE_ADD); + if (table->hook_add) + (*table->hook_add)(route); + + return route; + } + + /* Else, this is the brand new route regarding to the prefix */ + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s %p: route add %p %s cost %u: brand new route", + ospf6_route_table_name(table), (void *)table, + (void *)route, buf, route->path.cost); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route add: brand new route", + ospf6_route_table_name(table)); + + assert(node->info == NULL); + node->info = route; + SET_FLAG(route->flag, OSPF6_ROUTE_BEST); + ospf6_route_lock(route); + route->installed = now; + route->changed = now; + assert(route->table == NULL); + route->table = table; + + /* lookup real existing next route */ + nextnode = node; + route_lock_node(nextnode); + do { + nextnode = route_next(nextnode); + } while (nextnode && nextnode->info == NULL); + + /* set next link */ + if (nextnode == NULL) + route->next = NULL; + else { + route_unlock_node(nextnode); + + next = nextnode->info; + route->next = next; + next->prev = route; + } + + /* lookup real existing prev route */ + prevnode = node; + route_lock_node(prevnode); + do { + prevnode = route_prev(prevnode); + } while (prevnode && prevnode->info == NULL); + + /* set prev link */ + if (prevnode == NULL) + route->prev = NULL; + else { + route_unlock_node(prevnode); + + prev = prevnode->info; + while (prev->next && ospf6_route_is_same(prev, prev->next)) + prev = prev->next; + route->prev = prev; + prev->next = route; + } + + table->count++; + ospf6_route_table_assert(table); + + SET_FLAG(route->flag, OSPF6_ROUTE_ADD); + if (table->hook_add) + (*table->hook_add)(route); + + return route; +} + +void ospf6_route_remove(struct ospf6_route *route, + struct ospf6_route_table *table) +{ + struct route_node *node; + struct ospf6_route *current; + char buf[PREFIX2STR_BUFFER]; + + if (route->type == OSPF6_DEST_TYPE_LINKSTATE) + ospf6_linkstate_prefix2str(&route->prefix, buf, sizeof(buf)); + else if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(AF_INET, &ADV_ROUTER_IN_PREFIX(&route->prefix), buf, + sizeof(buf)); + else + prefix2str(&route->prefix, buf, sizeof(buf)); + + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s %p: route remove %p: %s cost %u refcount %u", + ospf6_route_table_name(table), (void *)table, + (void *)route, buf, route->path.cost, route->lock); + else if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + zlog_debug("%s: route remove: %s", + ospf6_route_table_name(table), buf); + + node = route_node_lookup(table->table, &route->prefix); + assert(node); + + /* find the route to remove, making sure that the route pointer + is from the route table. */ + current = node->info; + while (current && current != route) + current = current->next; + + assert(current == route); + + /* adjust doubly linked list */ + if (route->prev) + route->prev->next = route->next; + if (route->next) + route->next->prev = route->prev; + + if (node->info == route) { + if (route->next && route->next->rnode == node) { + node->info = route->next; + SET_FLAG(route->next->flag, OSPF6_ROUTE_BEST); + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_debug("%s: remove route %s", __func__, + buf); + } else { + node->info = NULL; + route->rnode = NULL; + route_unlock_node(node); /* to free the original lock */ + } + } + + route_unlock_node(node); /* to free the lookup lock */ + table->count--; + ospf6_route_table_assert(table); + + SET_FLAG(route->flag, OSPF6_ROUTE_WAS_REMOVED); + + /* Note hook_remove may call ospf6_route_remove */ + if (table->hook_remove) + (*table->hook_remove)(route); + + ospf6_route_unlock(route); +} + +struct ospf6_route *ospf6_route_head(struct ospf6_route_table *table) +{ + struct route_node *node; + struct ospf6_route *route; + + node = route_top(table->table); + if (node == NULL) + return NULL; + + /* skip to the real existing entry */ + while (node && node->info == NULL) + node = route_next(node); + if (node == NULL) + return NULL; + + route_unlock_node(node); + assert(node->info); + + route = (struct ospf6_route *)node->info; + assert(route->prev == NULL); + assert(route->table == table); + ospf6_route_lock(route); + + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_info("%s %p: route head: %p<-[%p]->%p", + ospf6_route_table_name(table), (void *)table, + (void *)route->prev, (void *)route, + (void *)route->next); + + return route; +} + +struct ospf6_route *ospf6_route_next(struct ospf6_route *route) +{ + struct ospf6_route *next = route->next; + + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + zlog_info("%s %p: route next: %p<-[%p]->%p , route ref count %u", + ospf6_route_table_name(route->table), + (void *)route->table, (void *)route->prev, + (void *)route, (void *)route->next, + route->lock); + + ospf6_route_unlock(route); + if (next) + ospf6_route_lock(next); + + return next; +} + +struct ospf6_route *ospf6_route_best_next(struct ospf6_route *route) +{ + struct route_node *rnode; + struct ospf6_route *next; + + ospf6_route_unlock(route); + + rnode = route->rnode; + route_lock_node(rnode); + rnode = route_next(rnode); + while (rnode && rnode->info == NULL) + rnode = route_next(rnode); + if (rnode == NULL) + return NULL; + route_unlock_node(rnode); + + assert(rnode->info); + next = (struct ospf6_route *)rnode->info; + ospf6_route_lock(next); + return next; +} + +struct ospf6_route *ospf6_route_match_head(struct prefix *prefix, + struct ospf6_route_table *table) +{ + struct route_node *node; + struct ospf6_route *route; + + /* Walk down tree. */ + node = table->table->top; + while (node && node->p.prefixlen < prefix->prefixlen + && prefix_match(&node->p, prefix)) + node = node->link[prefix_bit(&prefix->u.prefix, + node->p.prefixlen)]; + + if (node) + route_lock_node(node); + while (node && node->info == NULL) + node = route_next(node); + if (node == NULL) + return NULL; + route_unlock_node(node); + + if (!prefix_match(prefix, &node->p)) + return NULL; + + route = node->info; + ospf6_route_lock(route); + return route; +} + +struct ospf6_route *ospf6_route_match_next(struct prefix *prefix, + struct ospf6_route *route) +{ + struct ospf6_route *next; + + next = ospf6_route_next(route); + if (next && !prefix_match(prefix, &next->prefix)) { + ospf6_route_unlock(next); + next = NULL; + } + + return next; +} + +void ospf6_route_remove_all(struct ospf6_route_table *table) +{ + struct ospf6_route *route; + for (route = ospf6_route_head(table); route; + route = ospf6_route_next(route)) + ospf6_route_remove(route, table); +} + +struct ospf6_route_table *ospf6_route_table_create(int s, int t) +{ + struct ospf6_route_table *new; + new = XCALLOC(MTYPE_OSPF6_ROUTE_TABLE, + sizeof(struct ospf6_route_table)); + new->table = route_table_init(); + new->scope_type = s; + new->table_type = t; + return new; +} + +void ospf6_route_table_delete(struct ospf6_route_table *table) +{ + ospf6_route_remove_all(table); + route_table_finish(table->table); + XFREE(MTYPE_OSPF6_ROUTE_TABLE, table); +} + + +/* VTY commands */ +void ospf6_route_show(struct vty *vty, struct ospf6_route *route, + json_object *json_routes, bool use_json) +{ + int i; + char destination[PREFIX2STR_BUFFER], nexthop[64]; + char duration[64]; + struct timeval now, res; + struct listnode *node; + struct ospf6_nexthop *nh; + json_object *json_route = NULL; + json_object *json_array_next_hops = NULL; + json_object *json_next_hop; + vrf_id_t vrf_id = route->ospf6->vrf_id; + + monotime(&now); + timersub(&now, &route->changed, &res); + timerstring(&res, duration, sizeof(duration)); + + /* destination */ + if (route->type == OSPF6_DEST_TYPE_LINKSTATE) + ospf6_linkstate_prefix2str(&route->prefix, destination, + sizeof(destination)); + else if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(route->prefix.family, &route->prefix.u.prefix, + destination, sizeof(destination)); + else + prefix2str(&route->prefix, destination, sizeof(destination)); + + if (use_json) { + json_route = json_object_new_object(); + json_object_boolean_add(json_route, "isBestRoute", + ospf6_route_is_best(route)); + json_object_string_add(json_route, "destinationType", + OSPF6_DEST_TYPE_SUBSTR(route->type)); + json_object_string_add( + json_route, "pathType", + OSPF6_PATH_TYPE_SUBSTR(route->path.type)); + json_object_string_add(json_route, "duration", duration); + } + + /* Nexthops */ + if (use_json) + json_array_next_hops = json_object_new_array(); + else + i = 0; + for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { + /* nexthop */ + inet_ntop(AF_INET6, &nh->address, nexthop, sizeof(nexthop)); + if (use_json) { + json_next_hop = json_object_new_object(); + json_object_string_add(json_next_hop, "nextHop", + nexthop); + json_object_string_add( + json_next_hop, "interfaceName", + ifindex2ifname(nh->ifindex, vrf_id)); + json_object_array_add(json_array_next_hops, + json_next_hop); + } else { + if (!i) { + vty_out(vty, "%c%1s %2s %-30s %-25s %6.*s %s\n", + (ospf6_route_is_best(route) ? '*' + : ' '), + OSPF6_DEST_TYPE_SUBSTR(route->type), + OSPF6_PATH_TYPE_SUBSTR( + route->path.type), + destination, nexthop, IFNAMSIZ, + ifindex2ifname(nh->ifindex, vrf_id), + duration); + i++; + } else + vty_out(vty, "%c%1s %2s %-30s %-25s %6.*s %s\n", + ' ', "", "", "", nexthop, IFNAMSIZ, + ifindex2ifname(nh->ifindex, vrf_id), + ""); + } + } + if (use_json) { + json_object_object_add(json_route, "nextHops", + json_array_next_hops); + json_object_object_add(json_routes, destination, json_route); + } +} + +void ospf6_route_show_detail(struct vty *vty, struct ospf6_route *route, + json_object *json_routes, bool use_json) +{ + char destination[PREFIX2STR_BUFFER], nexthop[64]; + char area_id[16], id[16], adv_router[16], capa[16], options[32]; + char pfx_options[16]; + struct timeval now, res; + char duration[64]; + struct listnode *node; + struct ospf6_nexthop *nh; + char flag[6]; + json_object *json_route = NULL; + json_object *json_array_next_hops = NULL; + json_object *json_next_hop; + vrf_id_t vrf_id = route->ospf6->vrf_id; + + monotime(&now); + + /* destination */ + if (route->type == OSPF6_DEST_TYPE_LINKSTATE) + ospf6_linkstate_prefix2str(&route->prefix, destination, + sizeof(destination)); + else if (route->type == OSPF6_DEST_TYPE_ROUTER) + inet_ntop(route->prefix.family, &route->prefix.u.prefix, + destination, sizeof(destination)); + else + prefix2str(&route->prefix, destination, sizeof(destination)); + + if (use_json) { + json_route = json_object_new_object(); + json_object_string_add(json_route, "destinationType", + OSPF6_DEST_TYPE_NAME(route->type)); + } else { + vty_out(vty, "Destination: %s\n", destination); + vty_out(vty, "Destination type: %s\n", + OSPF6_DEST_TYPE_NAME(route->type)); + } + + /* Time */ + timersub(&now, &route->installed, &res); + timerstring(&res, duration, sizeof(duration)); + if (use_json) + json_object_string_add(json_route, "installedTimeSince", + duration); + else + vty_out(vty, "Installed Time: %s ago\n", duration); + + timersub(&now, &route->changed, &res); + timerstring(&res, duration, sizeof(duration)); + if (use_json) + json_object_string_add(json_route, "changedTimeSince", + duration); + else + vty_out(vty, "Changed Time: %s ago\n", duration); + + /* Debugging info */ + if (use_json) { + json_object_int_add(json_route, "numberOfLock", route->lock); + snprintf( + flag, sizeof(flag), "%s%s%s%s", + (CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST) ? "B" : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) ? "A" : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) ? "R" + : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE) ? "C" + : "-")); + json_object_string_add(json_route, "flags", flag); + } else { + vty_out(vty, "Lock: %d Flags: %s%s%s%s\n", route->lock, + (CHECK_FLAG(route->flag, OSPF6_ROUTE_BEST) ? "B" : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) ? "A" : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) ? "R" + : "-"), + (CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE) ? "C" + : "-")); + vty_out(vty, "Memory: prev: %p this: %p next: %p\n", + (void *)route->prev, (void *)route, + (void *)route->next); + } + + /* Path section */ + + /* Area-ID */ + inet_ntop(AF_INET, &route->path.area_id, area_id, sizeof(area_id)); + if (use_json) + json_object_string_add(json_route, "associatedArea", area_id); + else + vty_out(vty, "Associated Area: %s\n", area_id); + + /* Path type */ + if (use_json) + json_object_string_add(json_route, "pathType", + OSPF6_PATH_TYPE_NAME(route->path.type)); + else + vty_out(vty, "Path Type: %s\n", + OSPF6_PATH_TYPE_NAME(route->path.type)); + + /* LS Origin */ + inet_ntop(AF_INET, &route->path.origin.id, id, sizeof(id)); + inet_ntop(AF_INET, &route->path.origin.adv_router, adv_router, + sizeof(adv_router)); + if (use_json) { + json_object_string_add( + json_route, "lsOriginRoutePathType", + ospf6_lstype_name(route->path.origin.type)); + json_object_string_add(json_route, "lsId", id); + json_object_string_add(json_route, "lsAdvertisingRouter", + adv_router); + } else { + vty_out(vty, "LS Origin: %s Id: %s Adv: %s\n", + ospf6_lstype_name(route->path.origin.type), id, + adv_router); + } + + /* Options */ + ospf6_options_printbuf(route->path.options, options, sizeof(options)); + if (use_json) + json_object_string_add(json_route, "options", options); + else + vty_out(vty, "Options: %s\n", options); + + /* Router Bits */ + ospf6_capability_printbuf(route->path.router_bits, capa, sizeof(capa)); + if (use_json) + json_object_string_add(json_route, "routerBits", capa); + else + vty_out(vty, "Router Bits: %s\n", capa); + + /* Prefix Options */ + ospf6_prefix_options_printbuf(route->prefix_options, pfx_options, + sizeof(pfx_options)); + if (use_json) + json_object_string_add(json_route, "prefixOptions", + pfx_options); + else + vty_out(vty, "Prefix Options: %s\n", pfx_options); + + /* Metrics */ + if (use_json) { + json_object_int_add(json_route, "metricType", + route->path.metric_type); + json_object_int_add(json_route, "metricCost", route->path.cost); + json_object_int_add(json_route, "metricCostE2", + route->path.u.cost_e2); + + json_object_int_add(json_route, "pathsCount", + route->paths->count); + json_object_int_add(json_route, "nextHopCount", + route->nh_list->count); + } else { + vty_out(vty, "Metric Type: %d\n", route->path.metric_type); + vty_out(vty, "Metric: %d (%d)\n", route->path.cost, + route->path.u.cost_e2); + + vty_out(vty, "Paths count: %u\n", route->paths->count); + vty_out(vty, "Nexthop count: %u\n", route->nh_list->count); + } + + /* Nexthops */ + if (use_json) + json_array_next_hops = json_object_new_array(); + else + vty_out(vty, "Nexthop:\n"); + + for (ALL_LIST_ELEMENTS_RO(route->nh_list, node, nh)) { + /* nexthop */ + if (use_json) { + inet_ntop(AF_INET6, &nh->address, nexthop, + sizeof(nexthop)); + json_next_hop = json_object_new_object(); + json_object_string_add(json_next_hop, "nextHop", + nexthop); + json_object_string_add( + json_next_hop, "interfaceName", + ifindex2ifname(nh->ifindex, vrf_id)); + json_object_array_add(json_array_next_hops, + json_next_hop); + } else + vty_out(vty, " %pI6 %.*s\n", &nh->address, IFNAMSIZ, + ifindex2ifname(nh->ifindex, vrf_id)); + } + if (use_json) { + json_object_object_add(json_route, "nextHops", + json_array_next_hops); + json_object_object_add(json_routes, destination, json_route); + } else + vty_out(vty, "\n"); +} + +static void ospf6_route_show_table_summary(struct vty *vty, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route, *prev = NULL; + int i, pathtype[OSPF6_PATH_TYPE_MAX]; + unsigned int number = 0; + int nh_count = 0, ecmp = 0; + int alternative = 0, destination = 0; + char path_str[30]; + + for (i = 0; i < OSPF6_PATH_TYPE_MAX; i++) + pathtype[i] = 0; + + for (route = ospf6_route_head(table); route; + route = ospf6_route_next(route)) { + if (prev == NULL || !ospf6_route_is_same(prev, route)) + destination++; + else + alternative++; + nh_count = ospf6_num_nexthops(route->nh_list); + if (nh_count > 1) + ecmp++; + pathtype[route->path.type]++; + number++; + + prev = route; + } + + assert(number == table->count); + if (use_json) { + json_object_int_add(json, "numberOfOspfv3Routes", number); + json_object_int_add(json, "numberOfDestination", destination); + json_object_int_add(json, "numberOfAlternativeRoutes", + alternative); + json_object_int_add(json, "numberOfEcmp", ecmp); + } else { + vty_out(vty, "Number of OSPFv3 routes: %d\n", number); + vty_out(vty, "Number of Destination: %d\n", destination); + vty_out(vty, "Number of Alternative routes: %d\n", alternative); + vty_out(vty, "Number of Equal Cost Multi Path: %d\n", ecmp); + } + for (i = OSPF6_PATH_TYPE_INTRA; i <= OSPF6_PATH_TYPE_EXTERNAL2; i++) { + if (use_json) { + snprintf(path_str, sizeof(path_str), "numberOf%sRoutes", + OSPF6_PATH_TYPE_JSON(i)); + json_object_int_add(json, path_str, pathtype[i]); + } else + vty_out(vty, "Number of %s routes: %d\n", + OSPF6_PATH_TYPE_NAME(i), pathtype[i]); + } +} + +static void ospf6_route_show_table_prefix(struct vty *vty, + struct prefix *prefix, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route; + json_object *json_routes = NULL; + + route = ospf6_route_lookup(prefix, table); + if (route == NULL) + return; + + if (use_json) + json_routes = json_object_new_object(); + ospf6_route_lock(route); + while (route && ospf6_route_is_prefix(prefix, route)) { + /* Specifying a prefix will always display details */ + ospf6_route_show_detail(vty, route, json_routes, use_json); + route = ospf6_route_next(route); + } + + if (use_json) + json_object_object_add(json, "routes", json_routes); + if (route) + ospf6_route_unlock(route); +} + +static void ospf6_route_show_table_address(struct vty *vty, + struct prefix *prefix, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route; + json_object *json_routes = NULL; + + route = ospf6_route_lookup_bestmatch(prefix, table); + if (route == NULL) + return; + + if (use_json) + json_routes = json_object_new_object(); + prefix = &route->prefix; + ospf6_route_lock(route); + while (route && ospf6_route_is_prefix(prefix, route)) { + /* Specifying a prefix will always display details */ + ospf6_route_show_detail(vty, route, json_routes, use_json); + route = ospf6_route_next(route); + } + if (use_json) + json_object_object_add(json, "routes", json_routes); + if (route) + ospf6_route_unlock(route); +} + +static void ospf6_route_show_table_match(struct vty *vty, int detail, + struct prefix *prefix, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route; + json_object *json_routes = NULL; + + assert(prefix->family); + + route = ospf6_route_match_head(prefix, table); + if (use_json) + json_routes = json_object_new_object(); + while (route) { + if (detail) + ospf6_route_show_detail(vty, route, json_routes, + use_json); + else + ospf6_route_show(vty, route, json_routes, use_json); + route = ospf6_route_match_next(prefix, route); + } + if (use_json) + json_object_object_add(json, "routes", json_routes); +} + +static void ospf6_route_show_table_type(struct vty *vty, int detail, + uint8_t type, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route; + json_object *json_routes = NULL; + + route = ospf6_route_head(table); + if (use_json) + json_routes = json_object_new_object(); + while (route) { + if (route->path.type == type) { + if (detail) + ospf6_route_show_detail(vty, route, json_routes, + use_json); + else + ospf6_route_show(vty, route, json_routes, + use_json); + } + route = ospf6_route_next(route); + } + if (use_json) + json_object_object_add(json, "routes", json_routes); +} + +static void ospf6_route_show_table(struct vty *vty, int detail, + struct ospf6_route_table *table, + json_object *json, bool use_json) +{ + struct ospf6_route *route; + json_object *json_routes = NULL; + + route = ospf6_route_head(table); + if (use_json) + json_routes = json_object_new_object(); + while (route) { + if (detail) + ospf6_route_show_detail(vty, route, json_routes, + use_json); + else + ospf6_route_show(vty, route, json_routes, use_json); + route = ospf6_route_next(route); + } + if (use_json) + json_object_object_add(json, "routes", json_routes); +} + +int ospf6_route_table_show(struct vty *vty, int argc_start, int argc, + struct cmd_token **argv, + struct ospf6_route_table *table, bool use_json) +{ + int summary = 0; + int match = 0; + int detail = 0; + int slash = 0; + int isprefix = 0; + int i, ret; + struct prefix prefix; + uint8_t type = 0; + int arg_end = use_json ? (argc - 1) : argc; + json_object *json = NULL; + + memset(&prefix, 0, sizeof(prefix)); + + if (use_json) + json = json_object_new_object(); + + for (i = argc_start; i < arg_end; i++) { + if (strmatch(argv[i]->text, "summary")) { + summary++; + continue; + } + + if (strmatch(argv[i]->text, "intra-area")) { + type = OSPF6_PATH_TYPE_INTRA; + continue; + } + + if (strmatch(argv[i]->text, "inter-area")) { + type = OSPF6_PATH_TYPE_INTER; + continue; + } + + if (strmatch(argv[i]->text, "external-1")) { + type = OSPF6_PATH_TYPE_EXTERNAL1; + continue; + } + + if (strmatch(argv[i]->text, "external-2")) { + type = OSPF6_PATH_TYPE_EXTERNAL2; + continue; + } + + if (strmatch(argv[i]->text, "detail")) { + detail++; + continue; + } + + if (strmatch(argv[i]->text, "match")) { + match++; + continue; + } + + ret = str2prefix(argv[i]->arg, &prefix); + if (ret == 1 && prefix.family == AF_INET6) { + isprefix++; + if (strchr(argv[i]->arg, '/')) + slash++; + continue; + } + if (use_json) + json_object_string_add(json, "malformedArgument", + argv[i]->arg); + else + vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); + + return CMD_SUCCESS; + } + + /* Give summary of this route table */ + if (summary) { + ospf6_route_show_table_summary(vty, table, json, use_json); + if (use_json) + vty_json(vty, json); + return CMD_SUCCESS; + } + + /* Give exact prefix-match route */ + if (isprefix && !match) { + /* If exact address, give best matching route */ + if (!slash) + ospf6_route_show_table_address(vty, &prefix, table, + json, use_json); + else + ospf6_route_show_table_prefix(vty, &prefix, table, json, + use_json); + + if (use_json) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (match) + ospf6_route_show_table_match(vty, detail, &prefix, table, json, + use_json); + else if (type) + ospf6_route_show_table_type(vty, detail, type, table, json, + use_json); + else + ospf6_route_show_table(vty, detail, table, json, use_json); + + if (use_json) + vty_json(vty, json); + return CMD_SUCCESS; +} + +static void ospf6_linkstate_show_header(struct vty *vty) +{ + vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %s\n", "Type", "Router-ID", + "Net-ID", "Rtr-Bits", "Options", "Cost"); +} + +static void ospf6_linkstate_show(struct vty *vty, struct ospf6_route *route) +{ + uint32_t router, id; + char routername[16], idname[16], rbits[16], options[32]; + + router = ospf6_linkstate_prefix_adv_router(&route->prefix); + inet_ntop(AF_INET, &router, routername, sizeof(routername)); + id = ospf6_linkstate_prefix_id(&route->prefix); + inet_ntop(AF_INET, &id, idname, sizeof(idname)); + + ospf6_capability_printbuf(route->path.router_bits, rbits, + sizeof(rbits)); + ospf6_options_printbuf(route->path.options, options, sizeof(options)); + + if (ntohl(id)) + vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %lu\n", "Network", + routername, idname, rbits, options, + (unsigned long)route->path.cost); + else + vty_out(vty, "%-7s %-15s %-15s %-8s %-14s %lu\n", "Router", + routername, idname, rbits, options, + (unsigned long)route->path.cost); +} + + +static void ospf6_linkstate_show_table_exact(struct vty *vty, + struct prefix *prefix, + struct ospf6_route_table *table) +{ + struct ospf6_route *route; + + route = ospf6_route_lookup(prefix, table); + if (route == NULL) + return; + + ospf6_route_lock(route); + while (route && ospf6_route_is_prefix(prefix, route)) { + /* Specifying a prefix will always display details */ + ospf6_route_show_detail(vty, route, NULL, false); + route = ospf6_route_next(route); + } + if (route) + ospf6_route_unlock(route); +} + +static void ospf6_linkstate_show_table(struct vty *vty, int detail, + struct ospf6_route_table *table) +{ + struct ospf6_route *route; + + if (!detail) + ospf6_linkstate_show_header(vty); + + route = ospf6_route_head(table); + while (route) { + if (detail) + ospf6_route_show_detail(vty, route, NULL, false); + else + ospf6_linkstate_show(vty, route); + route = ospf6_route_next(route); + } +} + +int ospf6_linkstate_table_show(struct vty *vty, int idx_ipv4, int argc, + struct cmd_token **argv, + struct ospf6_route_table *table) +{ + int detail = 0; + int is_id = 0; + int is_router = 0; + int i, ret; + struct prefix router, id, prefix; + + memset(&router, 0, sizeof(router)); + memset(&id, 0, sizeof(id)); + memset(&prefix, 0, sizeof(prefix)); + + for (i = idx_ipv4; i < argc; i++) { + if (strmatch(argv[i]->text, "detail")) { + detail++; + continue; + } + + if (!is_router) { + ret = str2prefix(argv[i]->arg, &router); + if (ret == 1 && router.family == AF_INET) { + is_router++; + continue; + } + vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); + return CMD_SUCCESS; + } + + if (!is_id) { + ret = str2prefix(argv[i]->arg, &id); + if (ret == 1 && id.family == AF_INET) { + is_id++; + continue; + } + vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); + return CMD_SUCCESS; + } + + vty_out(vty, "Malformed argument: %s\n", argv[i]->arg); + return CMD_SUCCESS; + } + + if (is_router) + ospf6_linkstate_prefix(router.u.prefix4.s_addr, + id.u.prefix4.s_addr, &prefix); + + if (prefix.family) + ospf6_linkstate_show_table_exact(vty, &prefix, table); + else + ospf6_linkstate_show_table(vty, detail, table); + + return CMD_SUCCESS; +} + + +void ospf6_brouter_show_header(struct vty *vty) +{ + vty_out(vty, "%-15s %-8s %-14s %-10s %-15s\n", "Router-ID", "Rtr-Bits", + "Options", "Path-Type", "Area"); +} + +void ospf6_brouter_show(struct vty *vty, struct ospf6_route *route) +{ + uint32_t adv_router; + char adv[16], rbits[16], options[32], area[16]; + + adv_router = ospf6_linkstate_prefix_adv_router(&route->prefix); + inet_ntop(AF_INET, &adv_router, adv, sizeof(adv)); + ospf6_capability_printbuf(route->path.router_bits, rbits, + sizeof(rbits)); + ospf6_options_printbuf(route->path.options, options, sizeof(options)); + inet_ntop(AF_INET, &route->path.area_id, area, sizeof(area)); + + /* vty_out (vty, "%-15s %-8s %-14s %-10s %-15s\n", + "Router-ID", "Rtr-Bits", "Options", "Path-Type", "Area"); */ + vty_out(vty, "%-15s %-8s %-14s %-10s %-15s\n", adv, rbits, options, + OSPF6_PATH_TYPE_NAME(route->path.type), area); +} + +DEFPY(debug_ospf6_route, + debug_ospf6_route_cmd, + "[no$no] debug ospf6 route ", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug routes\n" + "Debug for all types of route calculation\n" + "Debug route table calculation\n" + "Debug intra-area route calculation\n" + "Debug inter-area route calculation\n" + "Debug route memory use\n") +{ + int idx_type; + unsigned char level = 0; + + idx_type = ((no) ? 4 : 3); + + if (!strcmp(argv[idx_type]->text, "all")) + level = OSPF6_DEBUG_ROUTE_ALL; + else if (!strcmp(argv[idx_type]->text, "table")) + level = OSPF6_DEBUG_ROUTE_TABLE; + else if (!strcmp(argv[idx_type]->text, "intra-area")) + level = OSPF6_DEBUG_ROUTE_INTRA; + else if (!strcmp(argv[idx_type]->text, "inter-area")) + level = OSPF6_DEBUG_ROUTE_INTER; + else if (!strcmp(argv[idx_type]->text, "memory")) + level = OSPF6_DEBUG_ROUTE_MEMORY; + + if (no) + OSPF6_DEBUG_ROUTE_OFF(level); + else + OSPF6_DEBUG_ROUTE_ON(level); + return CMD_SUCCESS; +} + +int config_write_ospf6_debug_route(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_ROUTE(ALL) == OSPF6_DEBUG_ROUTE_ALL) { + vty_out(vty, "debug ospf6 route all\n"); + return 0; + } + if (IS_OSPF6_DEBUG_ROUTE(TABLE)) + vty_out(vty, "debug ospf6 route table\n"); + if (IS_OSPF6_DEBUG_ROUTE(INTRA)) + vty_out(vty, "debug ospf6 route intra-area\n"); + if (IS_OSPF6_DEBUG_ROUTE(INTER)) + vty_out(vty, "debug ospf6 route inter-area\n"); + if (IS_OSPF6_DEBUG_ROUTE(MEMORY)) + vty_out(vty, "debug ospf6 route memory\n"); + + return 0; +} + +void install_element_ospf6_debug_route(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_route_cmd); + install_element(CONFIG_NODE, &debug_ospf6_route_cmd); +} diff --git a/ospf6d/ospf6_route.h b/ospf6d/ospf6_route.h new file mode 100644 index 0000000..8881349 --- /dev/null +++ b/ospf6d/ospf6_route.h @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_ROUTE_H +#define OSPF6_ROUTE_H + +#include "command.h" +#include "zclient.h" +#include "lib/json.h" +#include "lib/nexthop.h" + +#define OSPF6_MULTI_PATH_LIMIT 4 + +/* Debug option */ +extern unsigned char conf_debug_ospf6_route; +#define OSPF6_DEBUG_ROUTE_TABLE 0x01 +#define OSPF6_DEBUG_ROUTE_INTRA 0x02 +#define OSPF6_DEBUG_ROUTE_INTER 0x04 +#define OSPF6_DEBUG_ROUTE_MEMORY 0x08 +#define OSPF6_DEBUG_ROUTE_ALL \ + (OSPF6_DEBUG_ROUTE_TABLE | OSPF6_DEBUG_ROUTE_INTRA \ + | OSPF6_DEBUG_ROUTE_INTER | OSPF6_DEBUG_ROUTE_MEMORY) +#define OSPF6_DEBUG_ROUTE_ON(level) (conf_debug_ospf6_route |= (level)) +#define OSPF6_DEBUG_ROUTE_OFF(level) (conf_debug_ospf6_route &= ~(level)) +#define IS_OSPF6_DEBUG_ROUTE(e) (conf_debug_ospf6_route & OSPF6_DEBUG_ROUTE_##e) + +/* Nexthop */ +struct ospf6_nexthop { + /* Interface index */ + ifindex_t ifindex; + + /* IP address, if any */ + struct in6_addr address; + + /** Next-hop type information. */ + enum nexthop_types_t type; +}; + +static inline bool ospf6_nexthop_is_set(const struct ospf6_nexthop *nh) +{ + return nh->type != 0; +} + +static inline bool ospf6_nexthop_is_same(const struct ospf6_nexthop *nha, + const struct ospf6_nexthop *nhb) +{ + if (nha->type != nhb->type) + return false; + + switch (nha->type) { + case NEXTHOP_TYPE_BLACKHOLE: + /* NOTHING */ + break; + + case NEXTHOP_TYPE_IFINDEX: + if (nha->ifindex != nhb->ifindex) + return false; + break; + + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + /* OSPFv3 does not support IPv4 next hops. */ + return false; + + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (nha->ifindex != nhb->ifindex) + return false; + fallthrough; + case NEXTHOP_TYPE_IPV6: + if (!IN6_ARE_ADDR_EQUAL(&nha->address, &nhb->address)) + return false; + break; + } + + return true; +} + +static inline void ospf6_nexthop_clear(struct ospf6_nexthop *nh) +{ + memset(nh, 0, sizeof(*nh)); +} + +static inline void ospf6_nexthop_copy(struct ospf6_nexthop *nha, + const struct ospf6_nexthop *nhb) +{ + memcpy(nha, nhb, sizeof(*nha)); +} + +/* Path */ +struct ospf6_ls_origin { + uint16_t type; + in_addr_t id; + in_addr_t adv_router; +}; + +struct ospf6_path { + /* Link State Origin */ + struct ospf6_ls_origin origin; + + /* Router bits */ + uint8_t router_bits; + + /* Optional Capabilities */ + uint8_t options[3]; + + /* Associated Area */ + in_addr_t area_id; + + /* Path-type */ + uint8_t type; + uint8_t subtype; /* only used for redistribute i.e ZEBRA_ROUTE_XXX */ + + /* Cost */ + uint8_t metric_type; + uint32_t cost; + uint32_t redistribute_cost; + + struct prefix ls_prefix; + + union { + uint32_t cost_e2; + uint32_t cost_config; + } u; + uint32_t tag; + + /* nh list for this path */ + struct list *nh_list; +}; + +#define OSPF6_PATH_TYPE_NONE 0 +#define OSPF6_PATH_TYPE_INTRA 1 +#define OSPF6_PATH_TYPE_INTER 2 +#define OSPF6_PATH_TYPE_EXTERNAL1 3 +#define OSPF6_PATH_TYPE_EXTERNAL2 4 +#define OSPF6_PATH_TYPE_MAX 5 + +#define OSPF6_PATH_SUBTYPE_DEFAULT_RT 1 + +#define OSPF6_PATH_COST_IS_CONFIGURED(path) (path.u.cost_config != OSPF_AREA_RANGE_COST_UNSPEC) + +#define OSPF6_EXT_PATH_METRIC_MAX 0x00ffffff + +#include "prefix.h" +#include "table.h" +#include "bitfield.h" + +struct ospf6_route { + struct route_node *rnode; + struct ospf6_route_table *table; + struct ospf6_route *prev; + struct ospf6_route *next; + + /* Back pointer to ospf6 */ + struct ospf6 *ospf6; + + unsigned int lock; + + /* Destination Type */ + uint8_t type; + + /* XXX: It would likely be better to use separate struct in_addr's + * for the advertising router-ID and prefix IDs, instead of stuffing + * them + * into one. See also XXX below. + */ + /* Destination ID */ + struct prefix prefix; + + /* Time */ + struct timeval installed; + struct timeval changed; + + /* flag */ + uint16_t flag; + + /* Prefix Options */ + uint8_t prefix_options; + + /* route option */ + void *route_option; + + /* link state id for advertising */ + uint32_t linkstate_id; + + /* path */ + struct ospf6_path path; + + /* List of Paths. */ + struct list *paths; + + /* nexthop */ + struct list *nh_list; + + /* points to the summarised route */ + struct ospf6_external_aggr_rt *aggr_route; + + /* For Aggr routes */ + bool to_be_processed; +}; + +#define OSPF6_DEST_TYPE_NONE 0 +#define OSPF6_DEST_TYPE_ROUTER 1 +#define OSPF6_DEST_TYPE_NETWORK 2 +#define OSPF6_DEST_TYPE_DISCARD 3 +#define OSPF6_DEST_TYPE_LINKSTATE 4 +#define OSPF6_DEST_TYPE_RANGE 5 +#define OSPF6_DEST_TYPE_MAX 6 + +#define OSPF6_ROUTE_CHANGE 0x0001 +#define OSPF6_ROUTE_ADD 0x0002 +#define OSPF6_ROUTE_REMOVE 0x0004 +#define OSPF6_ROUTE_BEST 0x0008 +#define OSPF6_ROUTE_ACTIVE_SUMMARY 0x0010 +#define OSPF6_ROUTE_DO_NOT_ADVERTISE 0x0020 +#define OSPF6_ROUTE_WAS_REMOVED 0x0040 +#define OSPF6_ROUTE_BLACKHOLE_ADDED 0x0080 +#define OSPF6_ROUTE_NSSA_RANGE 0x0100 +struct ospf6; + +struct ospf6_route_table { + int scope_type; + int table_type; + void *scope; + + /* patricia tree */ + struct route_table *table; + + uint32_t count; + + /* hooks */ + void (*hook_add)(struct ospf6_route *); + void (*hook_change)(struct ospf6_route *); + void (*hook_remove)(struct ospf6_route *); +}; + +#define OSPF6_SCOPE_TYPE_NONE 0 +#define OSPF6_SCOPE_TYPE_GLOBAL 1 +#define OSPF6_SCOPE_TYPE_AREA 2 +#define OSPF6_SCOPE_TYPE_INTERFACE 3 + +#define OSPF6_TABLE_TYPE_NONE 0 +#define OSPF6_TABLE_TYPE_ROUTES 1 +#define OSPF6_TABLE_TYPE_BORDER_ROUTERS 2 +#define OSPF6_TABLE_TYPE_CONNECTED_ROUTES 3 +#define OSPF6_TABLE_TYPE_EXTERNAL_ROUTES 4 +#define OSPF6_TABLE_TYPE_SPF_RESULTS 5 +#define OSPF6_TABLE_TYPE_PREFIX_RANGES 6 +#define OSPF6_TABLE_TYPE_SUMMARY_PREFIXES 7 +#define OSPF6_TABLE_TYPE_SUMMARY_ROUTERS 8 + +#define OSPF6_ROUTE_TABLE_CREATE(s, t) \ + ospf6_route_table_create(OSPF6_SCOPE_TYPE_##s, OSPF6_TABLE_TYPE_##t) + +extern const char *const ospf6_dest_type_str[OSPF6_DEST_TYPE_MAX]; +extern const char *const ospf6_dest_type_substr[OSPF6_DEST_TYPE_MAX]; +#define OSPF6_DEST_TYPE_NAME(x) \ + (0 < (x) && (x) < OSPF6_DEST_TYPE_MAX ? ospf6_dest_type_str[(x)] \ + : ospf6_dest_type_str[0]) +#define OSPF6_DEST_TYPE_SUBSTR(x) \ + (0 < (x) && (x) < OSPF6_DEST_TYPE_MAX ? ospf6_dest_type_substr[(x)] \ + : ospf6_dest_type_substr[0]) + +extern const char *const ospf6_path_type_str[OSPF6_PATH_TYPE_MAX]; +extern const char *const ospf6_path_type_substr[OSPF6_PATH_TYPE_MAX]; +#define OSPF6_PATH_TYPE_NAME(x) \ + (0 < (x) && (x) < OSPF6_PATH_TYPE_MAX ? ospf6_path_type_str[(x)] \ + : ospf6_path_type_str[0]) +#define OSPF6_PATH_TYPE_SUBSTR(x) \ + (0 < (x) && (x) < OSPF6_PATH_TYPE_MAX ? ospf6_path_type_substr[(x)] \ + : ospf6_path_type_substr[0]) +#define OSPF6_PATH_TYPE_JSON(x) \ + (0 < (x) && (x) < OSPF6_PATH_TYPE_MAX ? ospf6_path_type_json[(x)] \ + : ospf6_path_type_json[0]) + +#define OSPF6_ROUTE_ADDRESS_STR "Display the route bestmatches the address\n" +#define OSPF6_ROUTE_PREFIX_STR "Display the route\n" +#define OSPF6_ROUTE_MATCH_STR "Display the route matches the prefix\n" + +#define ospf6_route_is_prefix(p, r) (prefix_same(p, &(r)->prefix)) +#define ospf6_route_is_same(ra, rb) (prefix_same(&(ra)->prefix, &(rb)->prefix)) +#define ospf6_route_is_same_origin(ra, rb) \ + ((ra)->path.area_id == (rb)->path.area_id \ + && (ra)->path.origin.type == (rb)->path.origin.type \ + && (ra)->path.origin.id == (rb)->path.origin.id \ + && (ra)->path.origin.adv_router == (rb)->path.origin.adv_router) +#define ospf6_route_is_identical(ra, rb) \ + ((ra)->type == (rb)->type && \ + prefix_same(&(ra)->prefix, &(rb)->prefix) && \ + (ra)->path.type == (rb)->path.type && \ + (ra)->path.cost == (rb)->path.cost && \ + (ra)->path.router_bits == (rb)->path.router_bits && \ + (ra)->path.u.cost_e2 == (rb)->path.u.cost_e2 && \ + listcount(ra->paths) == listcount(rb->paths) && \ + ospf6_route_cmp_nexthops(ra, rb)) + +#define ospf6_route_is_best(r) (CHECK_FLAG ((r)->flag, OSPF6_ROUTE_BEST)) + +#define ospf6_linkstate_prefix_adv_router(x) ((x)->u.lp.id.s_addr) +#define ospf6_linkstate_prefix_id(x) ((x)->u.lp.adv_router.s_addr) + +#define ADV_ROUTER_IN_PREFIX(x) ((x)->u.lp.id.s_addr) + +/* Function prototype */ +extern void ospf6_linkstate_prefix(uint32_t adv_router, uint32_t id, + struct prefix *prefix); +extern void ospf6_linkstate_prefix2str(struct prefix *prefix, char *buf, + int size); + +extern struct ospf6_nexthop *ospf6_nexthop_create(void); +extern int ospf6_nexthop_cmp(struct ospf6_nexthop *a, struct ospf6_nexthop *b); +extern void ospf6_nexthop_delete(struct ospf6_nexthop *nh); +extern void ospf6_clear_nexthops(struct list *nh_list); +extern int ospf6_num_nexthops(struct list *nh_list); +extern void ospf6_copy_nexthops(struct list *dst, struct list *src); +extern void ospf6_merge_nexthops(struct list *dst, struct list *src); +extern void ospf6_add_nexthop(struct list *nh_list, int ifindex, + const struct in6_addr *addr); +extern void ospf6_add_route_nexthop_blackhole(struct ospf6_route *route); +extern int ospf6_num_nexthops(struct list *nh_list); +extern bool ospf6_route_cmp_nexthops(struct ospf6_route *a, + struct ospf6_route *b); +extern void ospf6_route_zebra_copy_nexthops(struct ospf6_route *route, + struct zapi_nexthop nexthops[], + int entries, vrf_id_t vrf_id); +extern int ospf6_route_get_first_nh_index(struct ospf6_route *route); + +/* Hide abstraction of nexthop implementation in route from outsiders */ +#define ospf6_route_copy_nexthops(dst, src) ospf6_copy_nexthops(dst->nh_list, src->nh_list) +#define ospf6_route_merge_nexthops(dst, src) ospf6_merge_nexthops(dst->nh_list, src->nh_list) +#define ospf6_route_num_nexthops(route) ospf6_num_nexthops(route->nh_list) +#define ospf6_route_add_nexthop(route, ifindex, addr) \ + ospf6_add_nexthop(route->nh_list, ifindex, addr) + +extern struct ospf6_route *ospf6_route_create(struct ospf6 *ospf6); +extern void ospf6_route_delete(struct ospf6_route *route); +extern struct ospf6_route *ospf6_route_copy(struct ospf6_route *route); +extern int ospf6_route_cmp(struct ospf6_route *ra, struct ospf6_route *rb); + +extern void ospf6_route_lock(struct ospf6_route *route); +extern void ospf6_route_unlock(struct ospf6_route *route); +extern struct ospf6_route *ospf6_route_lookup(struct prefix *prefix, + struct ospf6_route_table *table); +extern struct ospf6_route * +ospf6_route_lookup_identical(struct ospf6_route *route, + struct ospf6_route_table *table); +extern struct ospf6_route * +ospf6_route_lookup_bestmatch(struct prefix *prefix, + struct ospf6_route_table *table); + +extern struct ospf6_route *ospf6_route_add(struct ospf6_route *route, + struct ospf6_route_table *table); +extern void ospf6_route_remove(struct ospf6_route *route, + struct ospf6_route_table *table); + +extern struct ospf6_route *ospf6_route_head(struct ospf6_route_table *table); +extern struct ospf6_route *ospf6_route_next(struct ospf6_route *route); +extern struct ospf6_route *ospf6_route_best_next(struct ospf6_route *route); + +extern struct ospf6_route * +ospf6_route_match_head(struct prefix *prefix, struct ospf6_route_table *table); +extern struct ospf6_route *ospf6_route_match_next(struct prefix *prefix, + struct ospf6_route *route); + +extern void ospf6_route_remove_all(struct ospf6_route_table *table); +extern struct ospf6_route_table *ospf6_route_table_create(int s, int t); +extern void ospf6_route_table_delete(struct ospf6_route_table *table); +extern void ospf6_route_dump(struct ospf6_route_table *table); + + +extern void ospf6_route_show(struct vty *vty, struct ospf6_route *route, + json_object *json, bool use_json); +extern void ospf6_route_show_detail(struct vty *vty, struct ospf6_route *route, + json_object *json, bool use_json); + + +extern int ospf6_route_table_show(struct vty *vty, int argc_start, int argc, + struct cmd_token **argv, + struct ospf6_route_table *table, + bool use_json); +extern int ospf6_linkstate_table_show(struct vty *vty, int idx_ipv4, int argc, + struct cmd_token **argv, + struct ospf6_route_table *table); + +extern void ospf6_brouter_show_header(struct vty *vty); +extern void ospf6_brouter_show(struct vty *vty, struct ospf6_route *route); + +extern int config_write_ospf6_debug_route(struct vty *vty); +extern void install_element_ospf6_debug_route(void); +extern void ospf6_route_init(void); +extern void ospf6_path_free(struct ospf6_path *op); +extern struct ospf6_path *ospf6_path_dup(struct ospf6_path *path); +extern void ospf6_copy_paths(struct list *dst, struct list *src); + +#endif /* OSPF6_ROUTE_H */ diff --git a/ospf6d/ospf6_routemap_nb.c b/ospf6d/ospf6_routemap_nb.c new file mode 100644 index 0000000..4ccec4f --- /dev/null +++ b/ospf6d/ospf6_routemap_nb.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#include + +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "ospf6_routemap_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_ospf_route_map_info = { + .name = "frr-ospf-route-map", + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf-route-map:metric-type", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_metric_type_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + +const struct frr_yang_module_info frr_ospf6_route_map_info = { + .name = "frr-ospf6-route-map", + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf6-route-map:ipv6-address", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ospf6d/ospf6_routemap_nb.h b/ospf6d/ospf6_routemap_nb.h new file mode 100644 index 0000000..74c1827 --- /dev/null +++ b/ospf6d/ospf6_routemap_nb.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#ifndef _FRR_OSPF6_ROUTEMAP_NB_H_ +#define _FRR_OSPF6_ROUTEMAP_NB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct frr_yang_module_info frr_ospf_route_map_info; +extern const struct frr_yang_module_info frr_ospf6_route_map_info; + +/* prototypes */ +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy(struct nb_cb_destroy_args *args); +int lib_route_map_entry_set_action_rmap_set_action_metric_type_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy(struct nb_cb_destroy_args *args); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospf6d/ospf6_routemap_nb_config.c b/ospf6d/ospf6_routemap_nb_config.c new file mode 100644 index 0000000..ad2d571 --- /dev/null +++ b/ospf6d/ospf6_routemap_nb_config.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "ospf6_routemap_nb.h" + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf-route-map:metric-type + */ +int lib_route_map_entry_set_action_rmap_set_action_metric_type_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "metric-type"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "metric-type", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf6-route-map:ipv6-address + */ +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *ipv6_addr; + int rv; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + ipv6_addr = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "forwarding-address"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "forwarding-address", + ipv6_addr, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_ipv6_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_set_destroy(args); + } + + return NB_OK; +} diff --git a/ospf6d/ospf6_snmp.c b/ospf6d/ospf6_snmp.c new file mode 100644 index 0000000..36864d2 --- /dev/null +++ b/ospf6d/ospf6_snmp.c @@ -0,0 +1,1407 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPFv3 SNMP support + * Copyright (C) 2004 Yasuhiro Ohara + */ + +#include + +#include +#include + +#include "log.h" +#include "vty.h" +#include "linklist.h" +#include "vector.h" +#include "vrf.h" +#include "smux.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "ospf6_proto.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_message.h" +#include "ospf6_neighbor.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6d.h" + +/* OSPFv3-MIB */ +#define OSPFv3MIB 1,3,6,1,2,1,191 + +/* OSPFv3 MIB General Group values. */ +#define OSPFv3ROUTERID 1 +#define OSPFv3ADMINSTAT 2 +#define OSPFv3VERSIONNUMBER 3 +#define OSPFv3AREABDRRTRSTATUS 4 +#define OSPFv3ASBDRRTRSTATUS 5 +#define OSPFv3ASSCOPELSACOUNT 6 +#define OSPFv3ASSCOPELSACHECKSUMSUM 7 +#define OSPFv3ORIGINATENEWLSAS 8 +#define OSPFv3RXNEWLSAS 9 +#define OSPFv3EXTLSACOUNT 10 +#define OSPFv3EXTAREALSDBLIMIT 11 +#define OSPFv3EXITOVERFLOWINTERVAL 12 +#define OSPFv3DEMANDEXTENSIONS 13 +#define OSPFv3REFERENCEBANDWIDTH 14 +#define OSPFv3RESTARTSUPPORT 15 +#define OSPFv3RESTARTINTERVAL 16 +#define OSPFv3RESTARTSTRICTLSACHECKING 17 +#define OSPFv3RESTARTSTATUS 18 +#define OSPFv3RESTARTAGE 19 +#define OSPFv3RESTARTEXITREASON 20 +#define OSPFv3NOTIFICATIONENABLE 21 +#define OSPFv3STUBROUTERSUPPORT 22 +#define OSPFv3STUBROUTERADVERTISEMENT 23 +#define OSPFv3DISCONTINUITYTIME 24 +#define OSPFv3RESTARTTIME 25 + +/* OSPFv3 MIB Area Table values: ospfv3AreaTable */ +#define OSPFv3IMPORTASEXTERN 2 +#define OSPFv3AREASPFRUNS 3 +#define OSPFv3AREABDRRTRCOUNT 4 +#define OSPFv3AREAASBDRRTRCOUNT 5 +#define OSPFv3AREASCOPELSACOUNT 6 +#define OSPFv3AREASCOPELSACKSUMSUM 7 +#define OSPFv3AREASUMMARY 8 +#define OSPFv3AREAROWSTATUS 9 +#define OSPFv3AREASTUBMETRIC 10 +#define OSPFv3AREANSSATRANSLATORROLE 11 +#define OSPFv3AREANSSATRANSLATORSTATE 12 +#define OSPFv3AREANSSATRANSLATORSTABINTERVAL 13 +#define OSPFv3AREANSSATRANSLATOREVENTS 14 +#define OSPFv3AREASTUBMETRICTYPE 15 +#define OSPFv3AREATEENABLED 16 + +/* OSPFv3 MIB * Lsdb Table values: ospfv3*LsdbTable */ +#define OSPFv3WWLSDBSEQUENCE 1 +#define OSPFv3WWLSDBAGE 2 +#define OSPFv3WWLSDBCHECKSUM 3 +#define OSPFv3WWLSDBADVERTISEMENT 4 +#define OSPFv3WWLSDBTYPEKNOWN 5 + +/* Three first bits are to identify column */ +#define OSPFv3WWCOLUMN 0x7 +/* Then we use other bits to identify table */ +#define OSPFv3WWASTABLE (1 << 3) +#define OSPFv3WWAREATABLE (1 << 4) +#define OSPFv3WWLINKTABLE (1 << 5) +#define OSPFv3WWVIRTLINKTABLE (1 << 6) + +/* OSPFv3 MIB Host Table values: ospfv3HostTable */ +#define OSPFv3HOSTMETRIC 3 +#define OSPFv3HOSTROWSTATUS 4 +#define OSPFv3HOSTAREAID 5 + +/* OSPFv3 MIB Interface Table values: ospfv3IfTable */ +#define OSPFv3IFAREAID 3 +#define OSPFv3IFTYPE 4 +#define OSPFv3IFADMINSTATUS 5 +#define OSPFv3IFRTRPRIORITY 6 +#define OSPFv3IFTRANSITDELAY 7 +#define OSPFv3IFRETRANSINTERVAL 8 +#define OSPFv3IFHELLOINTERVAL 9 +#define OSPFv3IFRTRDEADINTERVAL 10 +#define OSPFv3IFPOLLINTERVAL 11 +#define OSPFv3IFSTATE 12 +#define OSPFv3IFDESIGNATEDROUTER 13 +#define OSPFv3IFBACKUPDESIGNATEDROUTER 14 +#define OSPFv3IFEVENTS 15 +#define OSPFv3IFROWSTATUS 16 +#define OSPFv3IFDEMAND 17 +#define OSPFv3IFMETRICVALUE 18 +#define OSPFv3IFLINKSCOPELSACOUNT 19 +#define OSPFv3IFLINKLSACKSUMSUM 20 +#define OSPFv3IFDEMANDNBRPROBE 21 +#define OSPFv3IFDEMANDNBRPROBERETRANSLIMIT 22 +#define OSPFv3IFDEMANDNBRPROBEINTERVAL 23 +#define OSPFv3IFTEDISABLED 24 +#define OSPFv3IFLINKLSASUPPRESSION 25 + +/* OSPFv3 MIB Virtual Interface Table values: ospfv3VirtIfTable */ +#define OSPFv3VIRTIFINDEX 3 +#define OSPFv3VIRTIFINSTID 4 +#define OSPFv3VIRTIFTRANSITDELAY 5 +#define OSPFv3VIRTIFRETRANSINTERVAL 6 +#define OSPFv3VIRTIFHELLOINTERVAL 7 +#define OSPFv3VIRTIFRTRDEADINTERVAL 8 +#define OSPFv3VIRTIFSTATE 9 +#define OSPFv3VIRTIFEVENTS 10 +#define OSPFv3VIRTIFROWSTATUS 11 +#define OSPFv3VIRTIFLINKSCOPELSACOUNT 12 +#define OSPFv3VIRTIFLINKLSACKSUMSUM 13 + +/* OSPFv3 MIB Neighbors Table values: ospfv3NbrTable */ +#define OSPFv3NBRADDRESSTYPE 4 +#define OSPFv3NBRADDRESS 5 +#define OSPFv3NBROPTIONS 6 +#define OSPFv3NBRPRIORITY 7 +#define OSPFv3NBRSTATE 8 +#define OSPFv3NBREVENTS 9 +#define OSPFv3NBRLSRETRANSQLEN 10 +#define OSPFv3NBRHELLOSUPPRESSED 11 +#define OSPFv3NBRIFID 12 +#define OSPFv3NBRRESTARTHELPERSTATUS 13 +#define OSPFv3NBRRESTARTHELPERAGE 14 +#define OSPFv3NBRRESTARTHELPEREXITREASON 15 + +/* OSPFv3 MIB Configured Neighbors Table values: ospfv3CfgNbrTable */ +#define OSPFv3CFGNBRPRIORITY 5 +#define OSPFv3CFGNBRROWSTATUS 6 + +/* OSPFv3 MIB Virtual Neighbors Table values: ospfv3VirtNbrTable */ +#define OSPFv3VIRTNBRIFINDEX 3 +#define OSPFv3VIRTNBRIFINSTID 4 +#define OSPFv3VIRTNBRADDRESSTYPE 5 +#define OSPFv3VIRTNBRADDRESS 6 +#define OSPFv3VIRTNBROPTIONS 7 +#define OSPFv3VIRTNBRSTATE 8 +#define OSPFv3VIRTNBREVENTS 9 +#define OSPFv3VIRTNBRLSRETRANSQLEN 10 +#define OSPFv3VIRTNBRHELLOSUPPRESSED 11 +#define OSPFv3VIRTNBRIFID 12 +#define OSPFv3VIRTNBRRESTARTHELPERSTATUS 13 +#define OSPFv3VIRTNBRRESTARTHELPERAGE 14 +#define OSPFv3VIRTNBRRESTARTHELPEREXITREASON 15 + +/* OSPFv3 MIB Area Aggregate Table values: ospfv3AreaAggregateTable */ +#define OSPFv3AREAAGGREGATEROWSTATUS 6 +#define OSPFv3AREAAGGREGATEEFFECT 7 +#define OSPFv3AREAAGGREGATEROUTETAG 8 + +/* SYNTAX Status from OSPF-MIB. */ +#define OSPF_STATUS_ENABLED 1 +#define OSPF_STATUS_DISABLED 2 + +/* SNMP value hack. */ +#define COUNTER ASN_COUNTER +#define INTEGER ASN_INTEGER +#define GAUGE ASN_GAUGE +#define UNSIGNED ASN_UNSIGNED +#define TIMETICKS ASN_TIMETICKS +#define IPADDRESS ASN_IPADDRESS +#define STRING ASN_OCTET_STR + +/* For return values e.g. SNMP_INTEGER macro */ +SNMP_LOCAL_VARIABLES + +/* OSPFv3-MIB instances. */ +static oid ospfv3_oid[] = {OSPFv3MIB}; +static oid ospfv3_trap_oid[] = {OSPFv3MIB, 0}; + +/* Hook functions. */ +static uint8_t *ospfv3GeneralGroup(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfv3AreaEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfv3WwLsdbEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfv3NbrEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfv3IfEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); + +static struct variable ospfv3_variables[] = { + /* OSPF general variables */ + {OSPFv3ROUTERID, UNSIGNED, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 1}}, + {OSPFv3ADMINSTAT, INTEGER, RWRITE, ospfv3GeneralGroup, 3, {1, 1, 2}}, + {OSPFv3VERSIONNUMBER, INTEGER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 3}}, + {OSPFv3AREABDRRTRSTATUS, + INTEGER, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 4}}, + {OSPFv3ASBDRRTRSTATUS, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 5}}, + {OSPFv3ASSCOPELSACOUNT, GAUGE, RONLY, ospfv3GeneralGroup, 3, {1, 1, 6}}, + {OSPFv3ASSCOPELSACHECKSUMSUM, + UNSIGNED, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 7}}, + {OSPFv3ORIGINATENEWLSAS, + COUNTER, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 8}}, + {OSPFv3RXNEWLSAS, COUNTER, RONLY, ospfv3GeneralGroup, 3, {1, 1, 9}}, + {OSPFv3EXTLSACOUNT, GAUGE, RONLY, ospfv3GeneralGroup, 3, {1, 1, 10}}, + {OSPFv3EXTAREALSDBLIMIT, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 11}}, + {OSPFv3EXITOVERFLOWINTERVAL, + UNSIGNED, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 12}}, + {OSPFv3DEMANDEXTENSIONS, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 13}}, + {OSPFv3REFERENCEBANDWIDTH, + UNSIGNED, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 14}}, + {OSPFv3RESTARTSUPPORT, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 15}}, + {OSPFv3RESTARTINTERVAL, + UNSIGNED, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 16}}, + {OSPFv3RESTARTSTRICTLSACHECKING, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 17}}, + {OSPFv3RESTARTSTATUS, + INTEGER, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 18}}, + {OSPFv3RESTARTAGE, UNSIGNED, RONLY, ospfv3GeneralGroup, 3, {1, 1, 19}}, + {OSPFv3RESTARTEXITREASON, + INTEGER, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 20}}, + {OSPFv3NOTIFICATIONENABLE, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 21}}, + {OSPFv3STUBROUTERSUPPORT, + INTEGER, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 22}}, + {OSPFv3STUBROUTERADVERTISEMENT, + INTEGER, + RWRITE, + ospfv3GeneralGroup, + 3, + {1, 1, 23}}, + {OSPFv3DISCONTINUITYTIME, + TIMETICKS, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 24}}, + {OSPFv3RESTARTTIME, + TIMETICKS, + RONLY, + ospfv3GeneralGroup, + 3, + {1, 1, 25}}, + + /* OSPFv3 Area Data Structure */ + {OSPFv3IMPORTASEXTERN, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 2}}, + {OSPFv3AREASPFRUNS, COUNTER, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 3}}, + {OSPFv3AREABDRRTRCOUNT, GAUGE, RONLY, ospfv3AreaEntry, 4, {1, 2, 1, 4}}, + {OSPFv3AREAASBDRRTRCOUNT, + GAUGE, + RONLY, + ospfv3AreaEntry, + 4, + {1, 2, 1, 5}}, + {OSPFv3AREASCOPELSACOUNT, + GAUGE, + RONLY, + ospfv3AreaEntry, + 4, + {1, 2, 1, 6}}, + {OSPFv3AREASCOPELSACKSUMSUM, + UNSIGNED, + RONLY, + ospfv3AreaEntry, + 4, + {1, 2, 1, 7}}, + {OSPFv3AREASUMMARY, INTEGER, RWRITE, ospfv3AreaEntry, 4, {1, 2, 1, 8}}, + {OSPFv3AREAROWSTATUS, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 9}}, + {OSPFv3AREASTUBMETRIC, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 10}}, + {OSPFv3AREANSSATRANSLATORROLE, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 11}}, + {OSPFv3AREANSSATRANSLATORSTATE, + INTEGER, + RONLY, + ospfv3AreaEntry, + 4, + {1, 2, 1, 12}}, + {OSPFv3AREANSSATRANSLATORSTABINTERVAL, + UNSIGNED, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 13}}, + {OSPFv3AREANSSATRANSLATOREVENTS, + COUNTER, + RONLY, + ospfv3AreaEntry, + 4, + {1, 2, 1, 14}}, + {OSPFv3AREASTUBMETRICTYPE, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 15}}, + {OSPFv3AREATEENABLED, + INTEGER, + RWRITE, + ospfv3AreaEntry, + 4, + {1, 2, 1, 16}}, + + /* OSPFv3 AS LSDB */ + {OSPFv3WWLSDBSEQUENCE | OSPFv3WWASTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 3, 1, 4}}, + {OSPFv3WWLSDBAGE | OSPFv3WWASTABLE, + UNSIGNED, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 3, 1, 5}}, + {OSPFv3WWLSDBCHECKSUM | OSPFv3WWASTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 3, 1, 6}}, + {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWASTABLE, + STRING, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 3, 1, 7}}, + {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWASTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 3, 1, 8}}, + + /* OSPFv3 Area LSDB */ + {OSPFv3WWLSDBSEQUENCE | OSPFv3WWAREATABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 4, 1, 5}}, + {OSPFv3WWLSDBAGE | OSPFv3WWAREATABLE, + UNSIGNED, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 4, 1, 6}}, + {OSPFv3WWLSDBCHECKSUM | OSPFv3WWAREATABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 4, 1, 7}}, + {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWAREATABLE, + STRING, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 4, 1, 8}}, + {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWAREATABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 4, 1, 9}}, + + /* OSPFv3 Link LSDB */ + {OSPFv3WWLSDBSEQUENCE | OSPFv3WWLINKTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 5, 1, 6}}, + {OSPFv3WWLSDBAGE | OSPFv3WWLINKTABLE, + UNSIGNED, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 5, 1, 7}}, + {OSPFv3WWLSDBCHECKSUM | OSPFv3WWLINKTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 5, 1, 8}}, + {OSPFv3WWLSDBADVERTISEMENT | OSPFv3WWLINKTABLE, + STRING, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 5, 1, 9}}, + {OSPFv3WWLSDBTYPEKNOWN | OSPFv3WWLINKTABLE, + INTEGER, + RONLY, + ospfv3WwLsdbEntry, + 4, + {1, 5, 1, 10}}, + + /* OSPFv3 interfaces */ + {OSPFv3IFAREAID, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 3}}, + {OSPFv3IFTYPE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 4}}, + {OSPFv3IFADMINSTATUS, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 5}}, + {OSPFv3IFRTRPRIORITY, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 6}}, + {OSPFv3IFTRANSITDELAY, UNSIGNED, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 7}}, + {OSPFv3IFRETRANSINTERVAL, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 8}}, + {OSPFv3IFHELLOINTERVAL, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 9}}, + {OSPFv3IFRTRDEADINTERVAL, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 10}}, + {OSPFv3IFPOLLINTERVAL, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 11}}, + {OSPFv3IFSTATE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 12}}, + {OSPFv3IFDESIGNATEDROUTER, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 13}}, + {OSPFv3IFBACKUPDESIGNATEDROUTER, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 14}}, + {OSPFv3IFEVENTS, COUNTER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 15}}, + {OSPFv3IFROWSTATUS, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 16}}, + {OSPFv3IFDEMAND, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 17}}, + {OSPFv3IFMETRICVALUE, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 18}}, + {OSPFv3IFLINKSCOPELSACOUNT, + GAUGE, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 19}}, + {OSPFv3IFLINKLSACKSUMSUM, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 20}}, + {OSPFv3IFDEMANDNBRPROBE, + INTEGER, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 21}}, + {OSPFv3IFDEMANDNBRPROBERETRANSLIMIT, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 22}}, + {OSPFv3IFDEMANDNBRPROBEINTERVAL, + UNSIGNED, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 23}}, + {OSPFv3IFTEDISABLED, INTEGER, RONLY, ospfv3IfEntry, 4, {1, 7, 1, 24}}, + {OSPFv3IFLINKLSASUPPRESSION, + INTEGER, + RONLY, + ospfv3IfEntry, + 4, + {1, 7, 1, 25}}, + + /* OSPFv3 neighbors */ + {OSPFv3NBRADDRESSTYPE, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 4}}, + {OSPFv3NBRADDRESS, STRING, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 5}}, + {OSPFv3NBROPTIONS, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 6}}, + {OSPFv3NBRPRIORITY, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 7}}, + {OSPFv3NBRSTATE, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 8}}, + {OSPFv3NBREVENTS, COUNTER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 9}}, + {OSPFv3NBRLSRETRANSQLEN, + GAUGE, + RONLY, + ospfv3NbrEntry, + 4, + {1, 9, 1, 10}}, + {OSPFv3NBRHELLOSUPPRESSED, + INTEGER, + RONLY, + ospfv3NbrEntry, + 4, + {1, 9, 1, 11}}, + {OSPFv3NBRIFID, INTEGER, RONLY, ospfv3NbrEntry, 4, {1, 9, 1, 12}}, + {OSPFv3NBRRESTARTHELPERSTATUS, + INTEGER, + RONLY, + ospfv3NbrEntry, + 4, + {1, 9, 1, 13}}, + {OSPFv3NBRRESTARTHELPERAGE, + UNSIGNED, + RONLY, + ospfv3NbrEntry, + 4, + {1, 9, 1, 14}}, + {OSPFv3NBRRESTARTHELPEREXITREASON, + INTEGER, + RONLY, + ospfv3NbrEntry, + 4, + {1, 9, 1, 15}}, +}; + +static uint8_t *ospfv3GeneralGroup(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + uint16_t sum; + uint32_t count; + struct ospf6_lsa *lsa = NULL, *lsanext; + struct ospf6 *ospf6; + + ospf6 = ospf6_lookup_by_vrf_id(VRF_DEFAULT); + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFv3ROUTERID: + /* Router-ID of this OSPF instance. */ + if (ospf6) + return SNMP_INTEGER(ntohl(ospf6->router_id)); + return SNMP_INTEGER(0); + case OSPFv3ADMINSTAT: + if (ospf6) + return SNMP_INTEGER( + CHECK_FLAG(ospf6->flag, OSPF6_DISABLED) + ? OSPF_STATUS_DISABLED + : OSPF_STATUS_ENABLED); + return SNMP_INTEGER(OSPF_STATUS_DISABLED); + case OSPFv3VERSIONNUMBER: + return SNMP_INTEGER(3); + case OSPFv3AREABDRRTRSTATUS: + if (ospf6) + return SNMP_INTEGER( + ospf6_check_and_set_router_abr(ospf6) + ? SNMP_TRUE + : SNMP_FALSE); + return SNMP_INTEGER(SNMP_FALSE); + case OSPFv3ASBDRRTRSTATUS: + if (ospf6) + return SNMP_INTEGER(ospf6_asbr_is_asbr(ospf6) + ? SNMP_TRUE + : SNMP_FALSE); + return SNMP_INTEGER(SNMP_FALSE); + case OSPFv3ASSCOPELSACOUNT: + if (ospf6) + return SNMP_INTEGER(ospf6->lsdb->count); + return SNMP_INTEGER(0); + case OSPFv3ASSCOPELSACHECKSUMSUM: + if (ospf6) { + sum = 0; + for (ALL_LSDB(ospf6->lsdb, lsa, lsanext)) + sum += ntohs(lsa->header->checksum); + return SNMP_INTEGER(sum); + } + return SNMP_INTEGER(0); + case OSPFv3ORIGINATENEWLSAS: + return SNMP_INTEGER( + 0); /* Don't know where to get this value... */ + case OSPFv3RXNEWLSAS: + return SNMP_INTEGER( + 0); /* Don't know where to get this value... */ + case OSPFv3EXTLSACOUNT: + if (ospf6) { + count = 0; + for (ALL_LSDB_TYPED(ospf6->lsdb, + htons(OSPF6_LSTYPE_AS_EXTERNAL), + lsa)) + count += 1; + return SNMP_INTEGER(count); + } + return SNMP_INTEGER(0); + case OSPFv3EXTAREALSDBLIMIT: + return SNMP_INTEGER(-1); + case OSPFv3EXITOVERFLOWINTERVAL: + return SNMP_INTEGER(0); /* Not supported */ + case OSPFv3DEMANDEXTENSIONS: + return SNMP_INTEGER(0); /* Not supported */ + case OSPFv3REFERENCEBANDWIDTH: + if (ospf6) + return SNMP_INTEGER(ospf6->ref_bandwidth); + /* Otherwise, like for "not implemented". */ + return NULL; + case OSPFv3RESTARTSUPPORT: + case OSPFv3RESTARTINTERVAL: + case OSPFv3RESTARTSTRICTLSACHECKING: + case OSPFv3RESTARTSTATUS: + case OSPFv3RESTARTAGE: + case OSPFv3RESTARTEXITREASON: + case OSPFv3NOTIFICATIONENABLE: + case OSPFv3STUBROUTERSUPPORT: + case OSPFv3STUBROUTERADVERTISEMENT: + case OSPFv3DISCONTINUITYTIME: + case OSPFv3RESTARTTIME: + /* TODO: Not implemented */ + return NULL; + } + return NULL; +} + +static uint8_t *ospfv3AreaEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf6_area *oa, *area = NULL; + struct ospf6_lsa *lsa = NULL, *lsanext; + uint32_t area_id = 0; + uint32_t count; + uint16_t sum; + struct listnode *node; + unsigned int len; + char a[16]; + struct ospf6_route *ro; + struct ospf6 *ospf6; + + ospf6 = ospf6_lookup_by_vrf_id(VRF_DEFAULT); + + if (ospf6 == NULL) + return NULL; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + len = *length - v->namelen; + len = (len >= 1 ? 1 : 0); + if (exact && len != 1) + return NULL; + if (len) + area_id = htonl(name[v->namelen]); + + inet_ntop(AF_INET, &area_id, a, sizeof(a)); + zlog_debug("SNMP access by area: %s, exact=%d len=%d length=%lu", a, + exact, len, (unsigned long)*length); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (area == NULL) { + if (len == 0) /* return first area entry */ + area = oa; + else if (exact && ntohl(oa->area_id) == ntohl(area_id)) + area = oa; + else if (ntohl(oa->area_id) > ntohl(area_id)) + area = oa; + } + } + + if (area == NULL) + return NULL; + + *length = v->namelen + 1; + name[v->namelen] = ntohl(area->area_id); + + inet_ntop(AF_INET, &area->area_id, a, sizeof(a)); + zlog_debug("SNMP found area: %s, exact=%d len=%d length=%lu", a, exact, + len, (unsigned long)*length); + + switch (v->magic) { + case OSPFv3IMPORTASEXTERN: + /* No NSSA support */ + return SNMP_INTEGER(IS_AREA_STUB(area) ? 2 : 1); + case OSPFv3AREASPFRUNS: + return SNMP_INTEGER(area->spf_calculation); + case OSPFv3AREABDRRTRCOUNT: + case OSPFv3AREAASBDRRTRCOUNT: + count = 0; + for (ro = ospf6_route_head(ospf6->brouter_table); ro; + ro = ospf6_route_next(ro)) { + if (ntohl(ro->path.area_id) != ntohl(area->area_id)) + continue; + if (v->magic == OSPFv3AREABDRRTRCOUNT + && CHECK_FLAG(ro->path.router_bits, + OSPF6_ROUTER_BIT_B)) + count++; + if (v->magic == OSPFv3AREAASBDRRTRCOUNT + && CHECK_FLAG(ro->path.router_bits, + OSPF6_ROUTER_BIT_E)) + count++; + } + return SNMP_INTEGER(count); + case OSPFv3AREASCOPELSACOUNT: + return SNMP_INTEGER(area->lsdb->count); + case OSPFv3AREASCOPELSACKSUMSUM: + sum = 0; + for (ALL_LSDB(area->lsdb, lsa, lsanext)) + sum += ntohs(lsa->header->checksum); + return SNMP_INTEGER(sum); + case OSPFv3AREASUMMARY: + return SNMP_INTEGER(2); /* sendAreaSummary */ + case OSPFv3AREAROWSTATUS: + return SNMP_INTEGER(1); /* Active */ + case OSPFv3AREASTUBMETRIC: + case OSPFv3AREANSSATRANSLATORROLE: + case OSPFv3AREANSSATRANSLATORSTATE: + case OSPFv3AREANSSATRANSLATORSTABINTERVAL: + case OSPFv3AREANSSATRANSLATOREVENTS: + case OSPFv3AREASTUBMETRICTYPE: + case OSPFv3AREATEENABLED: + /* Not implemented. */ + return NULL; + } + return NULL; +} + +static int if_icmp_func(struct interface *ifp1, struct interface *ifp2) +{ + return (ifp1->ifindex - ifp2->ifindex); +} + +static uint8_t *ospfv3WwLsdbEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct vrf *vrf; + struct ospf6_lsa *lsa = NULL; + ifindex_t ifindex; + uint32_t area_id, id, instid, adv_router; + uint16_t type; + int len; + oid *offset; + int offsetlen; + struct ospf6_area *oa = NULL; + struct listnode *node; + struct interface *iif; + struct ospf6_interface *oi = NULL; + struct list *ifslist; + struct ospf6 *ospf6; + + ospf6 = ospf6_lookup_by_vrf_id(VRF_DEFAULT); + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + instid = ifindex = area_id = type = id = adv_router = 0; + + /* Check OSPFv3 instance. */ + if (ospf6 == NULL) + return NULL; + + vrf = vrf_lookup_by_id(ospf6->vrf_id); + /* Get variable length. */ + offset = name + v->namelen; + offsetlen = *length - v->namelen; + + if (exact && (v->magic & OSPFv3WWASTABLE) && offsetlen != 3) + return NULL; + if (exact && (v->magic & OSPFv3WWAREATABLE) && offsetlen != 4) + return NULL; + if (exact && (v->magic & OSPFv3WWLINKTABLE) && offsetlen != 5) + return NULL; + + if (v->magic & OSPFv3WWLINKTABLE) { + /* Parse ifindex */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + ifindex = *offset; + offset += len; + offsetlen -= len; + + /* Parse instance ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + instid = *offset; + offset += len; + offsetlen -= len; + } else if (v->magic & OSPFv3WWAREATABLE) { + /* Parse area-id */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + area_id = htonl(*offset); + offset += len; + offsetlen -= len; + } + + /* Parse type */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + type = htons(*offset); + offset += len; + offsetlen -= len; + + /* Parse Router-ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + adv_router = htonl(*offset); + offset += len; + offsetlen -= len; + + /* Parse LS-ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + id = htonl(*offset); + offset += len; + // offsetlen -= len; // Add back in if we need it again + + if (exact) { + if (v->magic & OSPFv3WWASTABLE) { + lsa = ospf6_lsdb_lookup(type, id, adv_router, + ospf6->lsdb); + } else if (v->magic & OSPFv3WWAREATABLE) { + oa = ospf6_area_lookup(area_id, ospf6); + if (!oa) + return NULL; + lsa = ospf6_lsdb_lookup(type, id, adv_router, oa->lsdb); + } else if (v->magic & OSPFv3WWLINKTABLE) { + oi = ospf6_interface_lookup_by_ifindex(ifindex, + ospf6->vrf_id); + if (!oi || oi->instance_id != instid) + return NULL; + lsa = ospf6_lsdb_lookup(type, id, adv_router, oi->lsdb); + } + } else { + if (v->magic & OSPFv3WWASTABLE) { + if (ospf6->lsdb->count) + lsa = ospf6_lsdb_lookup_next( + type, id, adv_router, ospf6->lsdb); + } else if (v->magic & OSPFv3WWAREATABLE) + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (oa->area_id < area_id) + continue; + + if (oa->lsdb->count) + lsa = ospf6_lsdb_lookup_next( + type, id, adv_router, oa->lsdb); + if (lsa) + break; + type = 0; + id = 0; + adv_router = 0; + } + else if (v->magic & OSPFv3WWLINKTABLE) { + /* We build a sorted list of interfaces */ + ifslist = list_new(); + ifslist->cmp = (int (*)(void *, void *))if_icmp_func; + FOR_ALL_INTERFACES (vrf, iif) + listnode_add_sort(ifslist, iif); + + for (ALL_LIST_ELEMENTS_RO(ifslist, node, iif)) { + if (!iif->ifindex) + continue; + oi = iif->info; + if (!oi) + continue; + if (iif->ifindex < ifindex) + continue; + if (oi->instance_id < instid) + continue; + + if (oi->lsdb->count) + lsa = ospf6_lsdb_lookup_next( + type, id, adv_router, oi->lsdb); + if (lsa) + break; + type = 0; + id = 0; + adv_router = 0; + oi = NULL; + } + + list_delete_all_node(ifslist); + list_delete(&ifslist); + } + } + + if (!lsa) + return NULL; + + /* Add indexes */ + if (v->magic & OSPFv3WWASTABLE) { + *length = v->namelen + 3; + offset = name + v->namelen; + } else if (v->magic & OSPFv3WWAREATABLE) { + *length = v->namelen + 4; + offset = name + v->namelen; + *offset = ntohl(oa->area_id); + offset++; + } else if (v->magic & OSPFv3WWLINKTABLE) { + *length = v->namelen + 5; + offset = name + v->namelen; + *offset = oi->interface->ifindex; + offset++; + *offset = oi->instance_id; + offset++; + } + *offset = ntohs(lsa->header->type); + offset++; + *offset = ntohl(lsa->header->adv_router); + offset++; + *offset = ntohl(lsa->header->id); + offset++; + + /* Return the current value of the variable */ + switch (v->magic & OSPFv3WWCOLUMN) { + case OSPFv3WWLSDBSEQUENCE: + return SNMP_INTEGER(ntohl(lsa->header->seqnum)); + case OSPFv3WWLSDBAGE: + ospf6_lsa_age_current(lsa); + return SNMP_INTEGER(ntohs(lsa->header->age)); + case OSPFv3WWLSDBCHECKSUM: + return SNMP_INTEGER(ntohs(lsa->header->checksum)); + case OSPFv3WWLSDBADVERTISEMENT: + *var_len = ospf6_lsa_size(lsa->header); + return (uint8_t *)lsa->header; + case OSPFv3WWLSDBTYPEKNOWN: + return SNMP_INTEGER(OSPF6_LSA_IS_KNOWN(lsa->header->type) + ? SNMP_TRUE + : SNMP_FALSE); + } + return NULL; +} + +static uint8_t *ospfv3IfEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct vrf *vrf; + ifindex_t ifindex = 0; + unsigned int instid = 0; + struct ospf6_interface *oi = NULL; + struct ospf6_lsa *lsa = NULL, *lsanext; + struct interface *iif; + struct listnode *i; + struct list *ifslist; + oid *offset; + int offsetlen, len; + uint32_t sum; + struct ospf6 *ospf6; + + ospf6 = ospf6_lookup_by_vrf_id(VRF_DEFAULT); + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Check OSPFv3 instance. */ + if (ospf6 == NULL) + return NULL; + + vrf = vrf_lookup_by_id(ospf6->vrf_id); + /* Get variable length. */ + offset = name + v->namelen; + offsetlen = *length - v->namelen; + + if (exact && offsetlen != 2) + return NULL; + + /* Parse if index */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + ifindex = *offset; + offset += len; + offsetlen -= len; + + /* Parse instance ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + instid = *offset; + // offset += len; // Add back in if we ever start using again + // offsetlen -= len; + + if (exact) { + oi = ospf6_interface_lookup_by_ifindex(ifindex, ospf6->vrf_id); + if (!oi || oi->instance_id != instid) + return NULL; + } else { + /* We build a sorted list of interfaces */ + ifslist = list_new(); + ifslist->cmp = (int (*)(void *, void *))if_icmp_func; + FOR_ALL_INTERFACES (vrf, iif) + listnode_add_sort(ifslist, iif); + + for (ALL_LIST_ELEMENTS_RO(ifslist, i, iif)) { + if (!iif->ifindex) + continue; + oi = iif->info; + if (!oi) + continue; + if (iif->ifindex > ifindex + || (iif->ifindex == ifindex + && (oi->instance_id > instid))) + break; + oi = NULL; + } + + list_delete_all_node(ifslist); + list_delete(&ifslist); + } + + if (!oi) + return NULL; + + /* Add Index (IfIndex, IfInstId) */ + *length = v->namelen + 2; + offset = name + v->namelen; + *offset = oi->interface->ifindex; + offset++; + *offset = oi->instance_id; + offset++; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFv3IFAREAID: + if (oi->area) + return SNMP_INTEGER(ntohl(oi->area->area_id)); + break; + case OSPFv3IFTYPE: + if (oi->type == OSPF_IFTYPE_BROADCAST) + return SNMP_INTEGER(1); + else if (oi->type == OSPF_IFTYPE_POINTOPOINT) + return SNMP_INTEGER(3); + else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) + return SNMP_INTEGER(5); + else + break; /* Unknown, don't put anything */ + case OSPFv3IFADMINSTATUS: + if (oi->area) + return SNMP_INTEGER(OSPF_STATUS_ENABLED); + return SNMP_INTEGER(OSPF_STATUS_DISABLED); + case OSPFv3IFRTRPRIORITY: + return SNMP_INTEGER(oi->priority); + case OSPFv3IFTRANSITDELAY: + return SNMP_INTEGER(oi->transdelay); + case OSPFv3IFRETRANSINTERVAL: + return SNMP_INTEGER(oi->rxmt_interval); + case OSPFv3IFHELLOINTERVAL: + return SNMP_INTEGER(oi->hello_interval); + case OSPFv3IFRTRDEADINTERVAL: + return SNMP_INTEGER(oi->dead_interval); + case OSPFv3IFPOLLINTERVAL: + /* No support for NBMA */ + break; + case OSPFv3IFSTATE: + return SNMP_INTEGER(oi->state); + case OSPFv3IFDESIGNATEDROUTER: + return SNMP_INTEGER(ntohl(oi->drouter)); + case OSPFv3IFBACKUPDESIGNATEDROUTER: + return SNMP_INTEGER(ntohl(oi->bdrouter)); + case OSPFv3IFEVENTS: + return SNMP_INTEGER(oi->state_change); + case OSPFv3IFROWSTATUS: + return SNMP_INTEGER(1); + case OSPFv3IFDEMAND: + return SNMP_INTEGER(SNMP_FALSE); + case OSPFv3IFMETRICVALUE: + return SNMP_INTEGER(oi->cost); + case OSPFv3IFLINKSCOPELSACOUNT: + return SNMP_INTEGER(oi->lsdb->count); + case OSPFv3IFLINKLSACKSUMSUM: + sum = 0; + for (ALL_LSDB(oi->lsdb, lsa, lsanext)) + sum += ntohs(lsa->header->checksum); + return SNMP_INTEGER(sum); + case OSPFv3IFDEMANDNBRPROBE: + case OSPFv3IFDEMANDNBRPROBERETRANSLIMIT: + case OSPFv3IFDEMANDNBRPROBEINTERVAL: + case OSPFv3IFTEDISABLED: + case OSPFv3IFLINKLSASUPPRESSION: + /* Not implemented. Only works if all the last ones are not + implemented! */ + return NULL; + } + + /* Try an internal getnext. Some columns are missing in this table. */ + if (!exact && (name[*length - 1] < MAX_SUBID)) + return ospfv3IfEntry(v, name, length, exact, var_len, + write_method); + return NULL; +} + +static uint8_t *ospfv3NbrEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct vrf *vrf; + ifindex_t ifindex = 0; + unsigned int instid, rtrid; + struct ospf6_interface *oi = NULL; + struct ospf6_neighbor *on = NULL; + struct interface *iif; + struct listnode *i, *j; + struct list *ifslist; + oid *offset; + int offsetlen, len; + struct ospf6 *ospf6; + + ospf6 = ospf6_lookup_by_vrf_id(VRF_DEFAULT); + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + instid = rtrid = 0; + + /* Check OSPFv3 instance. */ + if (ospf6 == NULL) + return NULL; + + vrf = vrf_lookup_by_id(ospf6->vrf_id); + /* Get variable length. */ + offset = name + v->namelen; + offsetlen = *length - v->namelen; + + if (exact && offsetlen != 3) + return NULL; + + /* Parse if index */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + ifindex = *offset; + offset += len; + offsetlen -= len; + + /* Parse instance ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + instid = *offset; + offset += len; + offsetlen -= len; + + /* Parse router ID */ + len = (offsetlen < 1 ? 0 : 1); + if (len) + rtrid = htonl(*offset); + // offset += len; // Add back in if we ever start looking at data + // offsetlen -= len; + + if (exact) { + oi = ospf6_interface_lookup_by_ifindex(ifindex, ospf6->vrf_id); + if (!oi || oi->instance_id != instid) + return NULL; + on = ospf6_neighbor_lookup(rtrid, oi); + } else { + /* We build a sorted list of interfaces */ + ifslist = list_new(); + ifslist->cmp = (int (*)(void *, void *))if_icmp_func; + FOR_ALL_INTERFACES (vrf, iif) + listnode_add_sort(ifslist, iif); + + for (ALL_LIST_ELEMENTS_RO(ifslist, i, iif)) { + if (!iif->ifindex) + continue; + oi = iif->info; + if (!oi) + continue; + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, j, on)) { + if (iif->ifindex > ifindex + || (iif->ifindex == ifindex + && (oi->instance_id > instid + || (oi->instance_id == instid + && ntohl(on->router_id) + > ntohl(rtrid))))) + break; + } + if (on) + break; + oi = NULL; + on = NULL; + } + + list_delete_all_node(ifslist); + list_delete(&ifslist); + } + + if (!oi || !on) + return NULL; + + /* Add Index (IfIndex, IfInstId, RtrId) */ + *length = v->namelen + 3; + offset = name + v->namelen; + *offset = oi->interface->ifindex; + offset++; + *offset = oi->instance_id; + offset++; + *offset = ntohl(on->router_id); + offset++; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFv3NBRADDRESSTYPE: + return SNMP_INTEGER(2); /* IPv6 only */ + case OSPFv3NBRADDRESS: + *var_len = sizeof(struct in6_addr); + return (uint8_t *)&on->linklocal_addr; + case OSPFv3NBROPTIONS: + return SNMP_INTEGER(on->options[2]); + case OSPFv3NBRPRIORITY: + return SNMP_INTEGER(on->priority); + case OSPFv3NBRSTATE: + return SNMP_INTEGER(on->state); + case OSPFv3NBREVENTS: + return SNMP_INTEGER(on->state_change); + case OSPFv3NBRLSRETRANSQLEN: + return SNMP_INTEGER(on->retrans_list->count); + case OSPFv3NBRHELLOSUPPRESSED: + return SNMP_INTEGER(SNMP_FALSE); + case OSPFv3NBRIFID: + return SNMP_INTEGER(on->ifindex); + case OSPFv3NBRRESTARTHELPERSTATUS: + case OSPFv3NBRRESTARTHELPERAGE: + case OSPFv3NBRRESTARTHELPEREXITREASON: + /* Not implemented. Only works if all the last ones are not + implemented! */ + return NULL; + } + + return NULL; +} + +/* OSPF Traps. */ +#define NBRSTATECHANGE 2 +#define IFSTATECHANGE 10 + +static struct trap_object ospf6NbrTrapList[] = { + {-3, {1, 1, OSPFv3ROUTERID}}, + {4, {1, 9, 1, OSPFv3NBRADDRESSTYPE}}, + {4, {1, 9, 1, OSPFv3NBRADDRESS}}, + {4, {1, 9, 1, OSPFv3NBRSTATE}}}; + +static struct trap_object ospf6IfTrapList[] = { + {-3, {1, 1, OSPFv3ROUTERID}}, + {4, {1, 7, 1, OSPFv3IFSTATE}}, + {4, {1, 7, 1, OSPFv3IFADMINSTATUS}}, + {4, {1, 7, 1, OSPFv3IFAREAID}}}; + +static int ospf6TrapNbrStateChange(struct ospf6_neighbor *on, int next_state, + int prev_state) +{ + oid index[3]; + + /* Terminal state or regression */ + if ((next_state != OSPF6_NEIGHBOR_FULL) + && (next_state != OSPF6_NEIGHBOR_TWOWAY) + && (next_state >= prev_state)) + return 0; + + index[0] = on->ospf6_if->interface->ifindex; + index[1] = on->ospf6_if->instance_id; + index[2] = ntohl(on->router_id); + + smux_trap(ospfv3_variables, array_size(ospfv3_variables), + ospfv3_trap_oid, array_size(ospfv3_trap_oid), ospfv3_oid, + sizeof(ospfv3_oid) / sizeof(oid), index, 3, ospf6NbrTrapList, + array_size(ospf6NbrTrapList), NBRSTATECHANGE); + return 0; +} + +static int ospf6TrapIfStateChange(struct ospf6_interface *oi, int next_state, + int prev_state) +{ + oid index[2]; + + /* Terminal state or regression */ + if ((next_state != OSPF6_INTERFACE_POINTTOPOINT) + && (next_state != OSPF6_INTERFACE_POINTTOMULTIPOINT) + && (next_state != OSPF6_INTERFACE_DROTHER) + && (next_state != OSPF6_INTERFACE_BDR) + && (next_state != OSPF6_INTERFACE_DR) && (next_state >= prev_state)) + return 0; + + index[0] = oi->interface->ifindex; + index[1] = oi->instance_id; + + smux_trap(ospfv3_variables, array_size(ospfv3_variables), + ospfv3_trap_oid, array_size(ospfv3_trap_oid), ospfv3_oid, + sizeof(ospfv3_oid) / sizeof(oid), index, 2, ospf6IfTrapList, + array_size(ospf6IfTrapList), IFSTATECHANGE); + return 0; +} + +/* Register OSPFv3-MIB. */ +static int ospf6_snmp_init(struct event_loop *master) +{ + smux_init(master); + REGISTER_MIB("OSPFv3MIB", ospfv3_variables, variable, ospfv3_oid); + return 0; +} + +static int ospf6_snmp_module_init(void) +{ + hook_register(ospf6_interface_change, ospf6TrapIfStateChange); + hook_register(ospf6_neighbor_change, ospf6TrapNbrStateChange); + hook_register(frr_late_init, ospf6_snmp_init); + return 0; +} + +FRR_MODULE_SETUP(.name = "ospf6d_snmp", .version = FRR_VERSION, + .description = "ospf6d AgentX SNMP module", + .init = ospf6_snmp_module_init, +); diff --git a/ospf6d/ospf6_spf.c b/ospf6d/ospf6_spf.c new file mode 100644 index 0000000..7879dae --- /dev/null +++ b/ospf6d/ospf6_spf.c @@ -0,0 +1,1293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +/* Shortest Path First calculation for OSPFv3 */ + +#include + +#include "log.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "prefix.h" +#include "linklist.h" +#include "frrevent.h" +#include "lib_errors.h" + +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_area.h" +#include "ospf6_proto.h" +#include "ospf6_abr.h" +#include "ospf6_asbr.h" +#include "ospf6_spf.h" +#include "ospf6_intra.h" +#include "ospf6_interface.h" +#include "ospf6d.h" +#include "ospf6_abr.h" +#include "ospf6_nssa.h" +#include "ospf6_zebra.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_VERTEX, "OSPF6 vertex"); + +unsigned char conf_debug_ospf6_spf = 0; + +static void ospf6_spf_copy_nexthops_to_route(struct ospf6_route *rt, + struct ospf6_vertex *v) +{ + if (rt && v) + ospf6_copy_nexthops(rt->nh_list, v->nh_list); +} + +static void ospf6_spf_merge_nexthops_to_route(struct ospf6_route *rt, + struct ospf6_vertex *v) +{ + if (rt && v) + ospf6_merge_nexthops(rt->nh_list, v->nh_list); +} + +static unsigned int ospf6_spf_get_ifindex_from_nh(struct ospf6_vertex *v) +{ + struct ospf6_nexthop *nh; + struct listnode *node; + + if (v) { + node = listhead(v->nh_list); + if (node) { + nh = listgetdata(node); + if (nh) + return (nh->ifindex); + } + } + return 0; +} + +static int ospf6_vertex_cmp(const struct ospf6_vertex *va, + const struct ospf6_vertex *vb) +{ + /* ascending order */ + if (va->cost != vb->cost) + return (va->cost - vb->cost); + if (va->hops != vb->hops) + return (va->hops - vb->hops); + return 0; +} +DECLARE_SKIPLIST_NONUNIQ(vertex_pqueue, struct ospf6_vertex, pqi, + ospf6_vertex_cmp); + +static int ospf6_vertex_id_cmp(void *a, void *b) +{ + struct ospf6_vertex *va = (struct ospf6_vertex *)a; + struct ospf6_vertex *vb = (struct ospf6_vertex *)b; + int ret = 0; + + ret = ntohl(ospf6_linkstate_prefix_adv_router(&va->vertex_id)) + - ntohl(ospf6_linkstate_prefix_adv_router(&vb->vertex_id)); + if (ret) + return ret; + + ret = ntohl(ospf6_linkstate_prefix_id(&va->vertex_id)) + - ntohl(ospf6_linkstate_prefix_id(&vb->vertex_id)); + return ret; +} + +static struct ospf6_vertex *ospf6_vertex_create(struct ospf6_lsa *lsa) +{ + struct ospf6_vertex *v; + + v = XMALLOC(MTYPE_OSPF6_VERTEX, sizeof(struct ospf6_vertex)); + + /* type */ + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_ROUTER) { + v->type = OSPF6_VERTEX_TYPE_ROUTER; + /* Router LSA use Link ID 0 as base in vertex_id */ + ospf6_linkstate_prefix(lsa->header->adv_router, htonl(0), + &v->vertex_id); + } else if (ntohs(lsa->header->type) == OSPF6_LSTYPE_NETWORK) { + v->type = OSPF6_VERTEX_TYPE_NETWORK; + /* vertex_id */ + ospf6_linkstate_prefix(lsa->header->adv_router, lsa->header->id, + &v->vertex_id); + } else + assert(0); + + /* name */ + ospf6_linkstate_prefix2str(&v->vertex_id, v->name, sizeof(v->name)); + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: Creating vertex %s of type %s (0x%04hx) lsa %s", + __func__, v->name, + ((ntohs(lsa->header->type) == OSPF6_LSTYPE_ROUTER) + ? "Router" + : "N/W"), + ntohs(lsa->header->type), lsa->name); + + + /* Associated LSA */ + v->lsa = lsa; + + /* capability bits + options */ + v->capability = *(uint8_t *)(ospf6_lsa_header_end(lsa->header)); + v->options[0] = *(uint8_t *)(ospf6_lsa_header_end(lsa->header) + 1); + v->options[1] = *(uint8_t *)(ospf6_lsa_header_end(lsa->header) + 2); + v->options[2] = *(uint8_t *)(ospf6_lsa_header_end(lsa->header) + 3); + + v->nh_list = list_new(); + v->nh_list->cmp = (int (*)(void *, void *))ospf6_nexthop_cmp; + v->nh_list->del = (void (*)(void *))ospf6_nexthop_delete; + + v->parent = NULL; + v->child_list = list_new(); + v->child_list->cmp = ospf6_vertex_id_cmp; + + return v; +} + +static void ospf6_vertex_delete(struct ospf6_vertex *v) +{ + list_delete(&v->nh_list); + list_delete(&v->child_list); + XFREE(MTYPE_OSPF6_VERTEX, v); +} + +static struct ospf6_lsa *ospf6_lsdesc_lsa(caddr_t lsdesc, + struct ospf6_vertex *v) +{ + struct ospf6_lsa *lsa = NULL; + uint16_t type = 0; + uint32_t id = 0, adv_router = 0; + + if (VERTEX_IS_TYPE(NETWORK, v)) { + type = htons(OSPF6_LSTYPE_ROUTER); + id = htonl(0); + adv_router = NETWORK_LSDESC_GET_NBR_ROUTERID(lsdesc); + } else { + if (ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, lsdesc)) { + type = htons(OSPF6_LSTYPE_ROUTER); + id = htonl(0); + adv_router = ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc); + } else if (ROUTER_LSDESC_IS_TYPE(TRANSIT_NETWORK, lsdesc)) { + type = htons(OSPF6_LSTYPE_NETWORK); + id = htonl(ROUTER_LSDESC_GET_NBR_IFID(lsdesc)); + adv_router = ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc); + } + } + + if (type == htons(OSPF6_LSTYPE_NETWORK)) + lsa = ospf6_lsdb_lookup(type, id, adv_router, v->area->lsdb); + else + lsa = ospf6_create_single_router_lsa(v->area, v->area->lsdb, + adv_router); + if (IS_OSPF6_DEBUG_SPF(PROCESS)) { + char ibuf[16], abuf[16]; + inet_ntop(AF_INET, &id, ibuf, sizeof(ibuf)); + inet_ntop(AF_INET, &adv_router, abuf, sizeof(abuf)); + if (lsa) + zlog_debug(" Link to: %s len %u, V %s", lsa->name, + ospf6_lsa_size(lsa->header), v->name); + else + zlog_debug(" Link to: [%s Id:%s Adv:%s] No LSA , V %s", + ospf6_lstype_name(type), ibuf, abuf, + v->name); + } + + return lsa; +} + +static char *ospf6_lsdesc_backlink(struct ospf6_lsa *lsa, caddr_t lsdesc, + struct ospf6_vertex *v) +{ + caddr_t backlink, found = NULL; + int size; + + size = (OSPF6_LSA_IS_TYPE(ROUTER, lsa) + ? sizeof(struct ospf6_router_lsdesc) + : sizeof(struct ospf6_network_lsdesc)); + for (backlink = ospf6_lsa_header_end(lsa->header) + 4; + backlink + size <= ospf6_lsa_end(lsa->header); backlink += size) { + assert(!(OSPF6_LSA_IS_TYPE(NETWORK, lsa) + && VERTEX_IS_TYPE(NETWORK, v))); + + if (OSPF6_LSA_IS_TYPE(NETWORK, lsa)) { + if (NETWORK_LSDESC_GET_NBR_ROUTERID(backlink) + == v->lsa->header->adv_router) + found = backlink; + } else if (VERTEX_IS_TYPE(NETWORK, v)) { + if (ROUTER_LSDESC_IS_TYPE(TRANSIT_NETWORK, backlink) + && ROUTER_LSDESC_GET_NBR_ROUTERID(backlink) + == v->lsa->header->adv_router + && ROUTER_LSDESC_GET_NBR_IFID(backlink) + == ntohl(v->lsa->header->id)) + found = backlink; + } else { + assert(OSPF6_LSA_IS_TYPE(ROUTER, lsa) + && VERTEX_IS_TYPE(ROUTER, v)); + + if (!ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, backlink) + || !ROUTER_LSDESC_IS_TYPE(POINTTOPOINT, lsdesc)) + continue; + + if (ROUTER_LSDESC_GET_NBR_IFID(backlink) + != ROUTER_LSDESC_GET_IFID(lsdesc) + || ROUTER_LSDESC_GET_NBR_IFID(lsdesc) + != ROUTER_LSDESC_GET_IFID(backlink)) + continue; + if (ROUTER_LSDESC_GET_NBR_ROUTERID(backlink) + != v->lsa->header->adv_router + || ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc) + != lsa->header->adv_router) + continue; + found = backlink; + } + } + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("Vertex %s Lsa %s Backlink %s", v->name, lsa->name, + (found ? "OK" : "FAIL")); + + return found; +} + +static void ospf6_nexthop_calc(struct ospf6_vertex *w, struct ospf6_vertex *v, + caddr_t lsdesc, struct ospf6 *ospf6) +{ + int i; + ifindex_t ifindex; + struct ospf6_interface *oi; + uint16_t type; + uint32_t adv_router; + struct ospf6_lsa *lsa; + struct ospf6_link_lsa *link_lsa; + char buf[64]; + + assert(VERTEX_IS_TYPE(ROUTER, w)); + ifindex = (VERTEX_IS_TYPE(NETWORK, v) ? ospf6_spf_get_ifindex_from_nh(v) + : ROUTER_LSDESC_GET_IFID(lsdesc)); + if (ifindex == 0) { + flog_err(EC_LIB_DEVELOPMENT, "No nexthop ifindex at vertex %s", + v->name); + return; + } + + oi = ospf6_interface_lookup_by_ifindex(ifindex, ospf6->vrf_id); + if (oi == NULL) { + zlog_warn("Can't find interface in SPF: ifindex %d", ifindex); + return; + } + + type = htons(OSPF6_LSTYPE_LINK); + adv_router = (VERTEX_IS_TYPE(NETWORK, v) + ? NETWORK_LSDESC_GET_NBR_ROUTERID(lsdesc) + : ROUTER_LSDESC_GET_NBR_ROUTERID(lsdesc)); + + i = 0; + for (ALL_LSDB_TYPED_ADVRTR(oi->lsdb, type, adv_router, lsa)) { + if (VERTEX_IS_TYPE(ROUTER, v) + && htonl(ROUTER_LSDESC_GET_NBR_IFID(lsdesc)) + != lsa->header->id) + continue; + + link_lsa = (struct ospf6_link_lsa *)ospf6_lsa_header_end( + lsa->header); + if (IS_OSPF6_DEBUG_SPF(PROCESS)) { + inet_ntop(AF_INET6, &link_lsa->linklocal_addr, buf, + sizeof(buf)); + zlog_debug(" nexthop %s from %s", buf, lsa->name); + } + + ospf6_add_nexthop(w->nh_list, ifindex, + &link_lsa->linklocal_addr); + i++; + } + + if (i == 0 && IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("No nexthop for %s found", w->name); +} + +static int ospf6_spf_install(struct ospf6_vertex *v, + struct ospf6_route_table *result_table) +{ + struct ospf6_route *route; + struct ospf6_vertex *prev; + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("SPF install %s (lsa %s) hops %d cost %d", v->name, + v->lsa->name, v->hops, v->cost); + + route = ospf6_route_lookup(&v->vertex_id, result_table); + if (route && route->path.cost < v->cost) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + " already installed with lower cost (%d), ignore", + route->path.cost); + ospf6_vertex_delete(v); + return -1; + } else if (route && route->path.cost == v->cost) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + " another path found to route %pFX lsa %s, merge", + &route->prefix, v->lsa->name); + + /* merging the parent's nexthop information to the child's + * if the parent is not the root of the tree. + */ + if (!ospf6_merge_parents_nh_to_child(v, route, result_table)) + ospf6_spf_merge_nexthops_to_route(route, v); + + prev = (struct ospf6_vertex *)route->route_option; + assert(prev->hops <= v->hops); + + if ((VERTEX_IS_TYPE(ROUTER, v) + && route->path.origin.id != v->lsa->header->id)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) { + zlog_debug( + "%s: V lsa %s id %u, route id %u are different", + __func__, v->lsa->name, + ntohl(v->lsa->header->id), + ntohl(route->path.origin.id)); + } + return 0; + } + + ospf6_vertex_delete(v); + return -1; + } + + /* There should be no case where candidate being installed (variable + "v") is closer than the one in the SPF tree (variable "route"). + In the case something has gone wrong with the behavior of + Priority-Queue. */ + + /* the case where the route exists already is handled and returned + up to here. */ + assert(route == NULL); + + route = ospf6_route_create(v->area->ospf6); + memcpy(&route->prefix, &v->vertex_id, sizeof(struct prefix)); + route->type = OSPF6_DEST_TYPE_LINKSTATE; + route->path.type = OSPF6_PATH_TYPE_INTRA; + route->path.origin.type = v->lsa->header->type; + route->path.origin.id = v->lsa->header->id; + route->path.origin.adv_router = v->lsa->header->adv_router; + route->path.metric_type = 1; + route->path.cost = v->cost; + route->path.u.cost_e2 = v->hops; + route->path.router_bits = v->capability; + route->path.options[0] = v->options[0]; + route->path.options[1] = v->options[1]; + route->path.options[2] = v->options[2]; + + ospf6_spf_copy_nexthops_to_route(route, v); + + /* + * The SPF logic implementation does not transfer the multipathing + * properties + * of a parent to a child node. Thus if there was a 3-way multipath to a + * node's parent and a single hop from the parent to the child, the + * logic of + * creating new vertices and computing next hops prevents there from + * being 3 + * paths to the child node. This is primarily because the resolution of + * multipath is done in this routine, not in the main spf loop. + * + * The following logic addresses that problem by merging the parent's + * nexthop + * information with the child's, if the parent is not the root of the + * tree. + * This is based on the assumption that before a node's route is + * installed, + * its parent's route's nexthops have already been installed. + */ + ospf6_merge_parents_nh_to_child(v, route, result_table); + + if (v->parent) + listnode_add_sort(v->parent->child_list, v); + route->route_option = v; + + ospf6_route_add(route, result_table); + return 0; +} + +void ospf6_spf_table_finish(struct ospf6_route_table *result_table) +{ + struct ospf6_route *route, *nroute; + struct ospf6_vertex *v; + for (route = ospf6_route_head(result_table); route; route = nroute) { + nroute = ospf6_route_next(route); + v = (struct ospf6_vertex *)route->route_option; + ospf6_vertex_delete(v); + ospf6_route_remove(route, result_table); + } +} + +static const char *const ospf6_spf_reason_str[] = { + "R+", /* OSPF6_SPF_FLAGS_ROUTER_LSA_ADDED */ + "R-", /* OSPF6_SPF_FLAGS_ROUTER_LSA_REMOVED */ + "N+", /* OSPF6_SPF_FLAGS_NETWORK_LSA_ADDED */ + "N-", /* OSPF6_SPF_FLAGS_NETWORK_LSA_REMOVED */ + "L+", /* OSPF6_SPF_FLAGS_NETWORK_LINK_LSA_ADDED */ + "L-", /* OSPF6_SPF_FLAGS_NETWORK_LINK_LSA_REMOVED */ + "R*", /* OSPF6_SPF_FLAGS_ROUTER_LSA_ORIGINATED */ + "N*", /* OSPF6_SPF_FLAGS_NETWORK_LSA_ORIGINATED */ + "C", /* OSPF6_SPF_FLAGS_CONFIG_CHANGE */ + "A", /* OSPF6_SPF_FLAGS_ASBR_STATUS_CHANGE */ + "GR", /* OSPF6_SPF_FLAGS_GR_FINISH */ +}; + +void ospf6_spf_reason_string(uint32_t reason, char *buf, int size) +{ + uint32_t bit; + int len = 0; + + if (!buf) + return; + + if (!reason) { + buf[0] = '\0'; + return; + } + for (bit = 0; bit < array_size(ospf6_spf_reason_str); bit++) { + if ((reason & (1 << bit)) && (len < size)) { + len += snprintf((buf + len), (size - len), "%s%s", + (len > 0) ? ", " : "", + ospf6_spf_reason_str[bit]); + } + } +} + +/* RFC2328 16.1. Calculating the shortest-path tree for an area */ +/* RFC2740 3.8.1. Calculating the shortest path tree for an area */ +void ospf6_spf_calculation(uint32_t router_id, + struct ospf6_route_table *result_table, + struct ospf6_area *oa) +{ + struct vertex_pqueue_head candidate_list; + struct ospf6_vertex *root, *v, *w; + int size; + caddr_t lsdesc; + struct ospf6_lsa *lsa; + struct in6_addr address; + + ospf6_spf_table_finish(result_table); + + /* Install the calculating router itself as the root of the SPF tree */ + /* construct root vertex */ + lsa = ospf6_create_single_router_lsa(oa, oa->lsdb_self, router_id); + if (lsa == NULL) { + zlog_warn("%s: No router LSA for area %s", __func__, oa->name); + return; + } + + /* initialize */ + vertex_pqueue_init(&candidate_list); + + root = ospf6_vertex_create(lsa); + root->area = oa; + root->cost = 0; + root->hops = 0; + root->link_id = lsa->header->id; + inet_pton(AF_INET6, "::1", &address); + + /* Actually insert root to the candidate-list as the only candidate */ + vertex_pqueue_add(&candidate_list, root); + + /* Iterate until candidate-list becomes empty */ + while ((v = vertex_pqueue_pop(&candidate_list))) { + /* installing may result in merging or rejecting of the vertex + */ + if (ospf6_spf_install(v, result_table) < 0) + continue; + + /* Skip overloaded routers */ + if ((OSPF6_LSA_IS_TYPE(ROUTER, v->lsa) + && ospf6_router_is_stub_router(v->lsa))) + continue; + + /* For each LS description in the just-added vertex V's LSA */ + size = (VERTEX_IS_TYPE(ROUTER, v) + ? sizeof(struct ospf6_router_lsdesc) + : sizeof(struct ospf6_network_lsdesc)); + for (lsdesc = ospf6_lsa_header_end(v->lsa->header) + 4; + lsdesc + size <= ospf6_lsa_end(v->lsa->header); + lsdesc += size) { + lsa = ospf6_lsdesc_lsa(lsdesc, v); + if (lsa == NULL) + continue; + + if (OSPF6_LSA_IS_MAXAGE(lsa)) + continue; + + if (!ospf6_lsdesc_backlink(lsa, lsdesc, v)) + continue; + + w = ospf6_vertex_create(lsa); + w->area = oa; + w->parent = v; + if (VERTEX_IS_TYPE(ROUTER, v)) { + w->cost = v->cost + + ROUTER_LSDESC_GET_METRIC(lsdesc); + w->hops = + v->hops + + (VERTEX_IS_TYPE(NETWORK, w) ? 0 : 1); + } else { + /* NETWORK */ + w->cost = v->cost; + w->hops = v->hops + 1; + } + + /* nexthop calculation */ + if (w->hops == 0) + ospf6_add_nexthop( + w->nh_list, + ROUTER_LSDESC_GET_IFID(lsdesc), NULL); + else if (w->hops == 1 && v->hops == 0) + ospf6_nexthop_calc(w, v, lsdesc, oa->ospf6); + else + ospf6_copy_nexthops(w->nh_list, v->nh_list); + + + /* add new candidate to the candidate_list */ + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + " New candidate: %s hops %d cost %d", + w->name, w->hops, w->cost); + vertex_pqueue_add(&candidate_list, w); + } + } + + //vertex_pqueue_fini(&candidate_list); + + ospf6_remove_temp_router_lsa(oa); + + oa->spf_calculation++; +} + +static void ospf6_spf_log_database(struct ospf6_area *oa) +{ + char *p, *end, buffer[256]; + struct listnode *node; + struct ospf6_interface *oi; + + p = buffer; + end = buffer + sizeof(buffer); + + snprintf(p, end - p, "SPF on DB (#LSAs):"); + p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) : end); + snprintf(p, end - p, " Area %s: %d", oa->name, oa->lsdb->count); + p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) : end); + + for (ALL_LIST_ELEMENTS_RO(oa->if_list, node, oi)) { + snprintf(p, end - p, " I/F %s: %d", oi->interface->name, + oi->lsdb->count); + p = (buffer + strlen(buffer) < end ? buffer + strlen(buffer) + : end); + } + + zlog_debug("%s", buffer); +} + +static void ospf6_spf_calculation_thread(struct event *t) +{ + struct ospf6_area *oa; + struct ospf6 *ospf6; + struct timeval start, end, runtime; + struct listnode *node; + int areas_processed = 0; + char rbuf[32]; + + ospf6 = (struct ospf6 *)EVENT_ARG(t); + + /* execute SPF calculation */ + monotime(&start); + ospf6->ts_spf = start; + + if (ospf6_check_and_set_router_abr(ospf6)) + ospf6_abr_range_reset_cost(ospf6); + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + + if (oa == ospf6->backbone) + continue; + + monotime(&oa->ts_spf); + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("SPF calculation for Area %s", oa->name); + if (IS_OSPF6_DEBUG_SPF(DATABASE)) + ospf6_spf_log_database(oa); + + ospf6_spf_calculation(ospf6->router_id, oa->spf_table, oa); + ospf6_intra_route_calculation(oa); + ospf6_intra_brouter_calculation(oa); + + areas_processed++; + } + + if (ospf6->backbone) { + monotime(&ospf6->backbone->ts_spf); + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("SPF calculation for Backbone area %s", + ospf6->backbone->name); + if (IS_OSPF6_DEBUG_SPF(DATABASE)) + ospf6_spf_log_database(ospf6->backbone); + + ospf6_spf_calculation(ospf6->router_id, + ospf6->backbone->spf_table, + ospf6->backbone); + ospf6_intra_route_calculation(ospf6->backbone); + ospf6_intra_brouter_calculation(ospf6->backbone); + areas_processed++; + } + + /* External LSA calculation */ + ospf6_ase_calculate_timer_add(ospf6); + + if (ospf6_check_and_set_router_abr(ospf6)) { + ospf6_abr_defaults_to_stub(ospf6); + ospf6_abr_nssa_type_7_defaults(ospf6); + } + + monotime(&end); + timersub(&end, &start, &runtime); + + ospf6->ts_spf_duration = runtime; + + ospf6_spf_reason_string(ospf6->spf_reason, rbuf, sizeof(rbuf)); + + if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) + zlog_debug( + "SPF processing: # Areas: %d, SPF runtime: %lld sec %lld usec, Reason: %s", + areas_processed, (long long)runtime.tv_sec, + (long long)runtime.tv_usec, rbuf); + + ospf6->last_spf_reason = ospf6->spf_reason; + ospf6_reset_spf_reason(ospf6); +} + +/* Add schedule for SPF calculation. To avoid frequenst SPF calc, we + set timer for SPF calc. */ +void ospf6_spf_schedule(struct ospf6 *ospf6, unsigned int reason) +{ + unsigned long delay, elapsed, ht; + + /* OSPF instance does not exist. */ + if (ospf6 == NULL) + return; + + ospf6_set_spf_reason(ospf6, reason); + + if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) { + char rbuf[32]; + ospf6_spf_reason_string(reason, rbuf, sizeof(rbuf)); + zlog_debug("SPF: calculation timer scheduled (reason %s)", + rbuf); + } + + /* SPF calculation timer is already scheduled. */ + if (event_is_scheduled(ospf6->t_spf_calc)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) + zlog_debug( + "SPF: calculation timer is already scheduled: %p", + (void *)ospf6->t_spf_calc); + return; + } + + elapsed = monotime_since(&ospf6->ts_spf, NULL) / 1000LL; + ht = ospf6->spf_holdtime * ospf6->spf_hold_multiplier; + + if (ht > ospf6->spf_max_holdtime) + ht = ospf6->spf_max_holdtime; + + /* Get SPF calculation delay time. */ + if (elapsed < ht) { + /* Got an event within the hold time of last SPF. We need to + * increase the hold_multiplier, if it's not already at/past + * maximum value, and wasn't already increased.. + */ + if (ht < ospf6->spf_max_holdtime) + ospf6->spf_hold_multiplier++; + + /* always honour the SPF initial delay */ + if ((ht - elapsed) < ospf6->spf_delay) + delay = ospf6->spf_delay; + else + delay = ht - elapsed; + } else { + /* Event is past required hold-time of last SPF */ + delay = ospf6->spf_delay; + ospf6->spf_hold_multiplier = 1; + } + + if (IS_OSPF6_DEBUG_SPF(PROCESS) || IS_OSPF6_DEBUG_SPF(TIME)) + zlog_debug("SPF: Rescheduling in %ld msec", delay); + + EVENT_OFF(ospf6->t_spf_calc); + event_add_timer_msec(master, ospf6_spf_calculation_thread, ospf6, delay, + &ospf6->t_spf_calc); +} + +void ospf6_spf_display_subtree(struct vty *vty, const char *prefix, int rest, + struct ospf6_vertex *v, json_object *json_obj, + bool use_json) +{ + struct listnode *node, *nnode; + struct ospf6_vertex *c; + char *next_prefix; + int len; + int restnum; + json_object *json_childs = NULL; + json_object *json_child = NULL; + + if (use_json) { + json_childs = json_object_new_object(); + json_object_int_add(json_obj, "cost", v->cost); + } else { + /* "prefix" is the space prefix of the display line */ + vty_out(vty, "%s+-%s [%d]\n", prefix, v->name, v->cost); + } + + len = strlen(prefix) + 4; + next_prefix = (char *)malloc(len); + if (next_prefix == NULL) { + vty_out(vty, "malloc failed\n"); + return; + } + snprintf(next_prefix, len, "%s%s", prefix, (rest ? "| " : " ")); + + restnum = listcount(v->child_list); + for (ALL_LIST_ELEMENTS(v->child_list, node, nnode, c)) { + if (use_json) + json_child = json_object_new_object(); + else + restnum--; + + ospf6_spf_display_subtree(vty, next_prefix, restnum, c, + json_child, use_json); + + if (use_json) + json_object_object_add(json_childs, c->name, + json_child); + } + if (use_json) { + json_object_boolean_add(json_obj, "isLeafNode", + !listcount(v->child_list)); + if (listcount(v->child_list)) + json_object_object_add(json_obj, "children", + json_childs); + else + json_object_free(json_childs); + } + free(next_prefix); +} + +DEFUN (debug_ospf6_spf_process, + debug_ospf6_spf_process_cmd, + "debug ospf6 spf process", + DEBUG_STR + OSPF6_STR + "Debug SPF Calculation\n" + "Debug Detailed SPF Process\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_PROCESS; + OSPF6_DEBUG_SPF_ON(level); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf6_spf_time, + debug_ospf6_spf_time_cmd, + "debug ospf6 spf time", + DEBUG_STR + OSPF6_STR + "Debug SPF Calculation\n" + "Measure time taken by SPF Calculation\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_TIME; + OSPF6_DEBUG_SPF_ON(level); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf6_spf_database, + debug_ospf6_spf_database_cmd, + "debug ospf6 spf database", + DEBUG_STR + OSPF6_STR + "Debug SPF Calculation\n" + "Log number of LSAs at SPF Calculation time\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_DATABASE; + OSPF6_DEBUG_SPF_ON(level); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_spf_process, + no_debug_ospf6_spf_process_cmd, + "no debug ospf6 spf process", + NO_STR + DEBUG_STR + OSPF6_STR + "Quit Debugging SPF Calculation\n" + "Quit Debugging Detailed SPF Process\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_PROCESS; + OSPF6_DEBUG_SPF_OFF(level); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_spf_time, + no_debug_ospf6_spf_time_cmd, + "no debug ospf6 spf time", + NO_STR + DEBUG_STR + OSPF6_STR + "Quit Debugging SPF Calculation\n" + "Quit Measuring time taken by SPF Calculation\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_TIME; + OSPF6_DEBUG_SPF_OFF(level); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_spf_database, + no_debug_ospf6_spf_database_cmd, + "no debug ospf6 spf database", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug SPF Calculation\n" + "Quit Logging number of LSAs at SPF Calculation time\n" + ) +{ + unsigned char level = 0; + level = OSPF6_DEBUG_SPF_DATABASE; + OSPF6_DEBUG_SPF_OFF(level); + return CMD_SUCCESS; +} + +static int ospf6_timers_spf_set(struct vty *vty, unsigned int delay, + unsigned int hold, unsigned int max) +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf); + + ospf->spf_delay = delay; + ospf->spf_holdtime = hold; + ospf->spf_max_holdtime = max; + + return CMD_SUCCESS; +} + +DEFUN (ospf6_timers_throttle_spf, + ospf6_timers_throttle_spf_cmd, + "timers throttle spf (0-600000) (0-600000) (0-600000)", + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "OSPF6 SPF timers\n" + "Delay (msec) from first change received till SPF calculation\n" + "Initial hold time (msec) between consecutive SPF calculations\n" + "Maximum hold time (msec)\n") +{ + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + unsigned int delay, hold, max; + + delay = strtoul(argv[idx_number]->arg, NULL, 10); + hold = strtoul(argv[idx_number_2]->arg, NULL, 10); + max = strtoul(argv[idx_number_3]->arg, NULL, 10); + + return ospf6_timers_spf_set(vty, delay, hold, max); +} + +DEFUN (no_ospf6_timers_throttle_spf, + no_ospf6_timers_throttle_spf_cmd, + "no timers throttle spf [(0-600000) (0-600000) (0-600000)]", + NO_STR + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "OSPF6 SPF timers\n" + "Delay (msec) from first change received till SPF calculation\n" + "Initial hold time (msec) between consecutive SPF calculations\n" + "Maximum hold time (msec)\n") +{ + return ospf6_timers_spf_set(vty, OSPF_SPF_DELAY_DEFAULT, + OSPF_SPF_HOLDTIME_DEFAULT, + OSPF_SPF_MAX_HOLDTIME_DEFAULT); +} + + +int config_write_ospf6_debug_spf(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + vty_out(vty, "debug ospf6 spf process\n"); + if (IS_OSPF6_DEBUG_SPF(TIME)) + vty_out(vty, "debug ospf6 spf time\n"); + if (IS_OSPF6_DEBUG_SPF(DATABASE)) + vty_out(vty, "debug ospf6 spf database\n"); + return 0; +} + +void ospf6_spf_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + + if (ospf6->spf_delay != OSPF_SPF_DELAY_DEFAULT + || ospf6->spf_holdtime != OSPF_SPF_HOLDTIME_DEFAULT + || ospf6->spf_max_holdtime != OSPF_SPF_MAX_HOLDTIME_DEFAULT) + vty_out(vty, " timers throttle spf %d %d %d\n", + ospf6->spf_delay, ospf6->spf_holdtime, + ospf6->spf_max_holdtime); +} + +void install_element_ospf6_debug_spf(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_spf_process_cmd); + install_element(ENABLE_NODE, &debug_ospf6_spf_time_cmd); + install_element(ENABLE_NODE, &debug_ospf6_spf_database_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_spf_process_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_spf_time_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_spf_database_cmd); + install_element(CONFIG_NODE, &debug_ospf6_spf_process_cmd); + install_element(CONFIG_NODE, &debug_ospf6_spf_time_cmd); + install_element(CONFIG_NODE, &debug_ospf6_spf_database_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_spf_process_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_spf_time_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_spf_database_cmd); +} + +void ospf6_spf_init(void) +{ + install_element(OSPF6_NODE, &ospf6_timers_throttle_spf_cmd); + install_element(OSPF6_NODE, &no_ospf6_timers_throttle_spf_cmd); +} + +/* Create Aggregated Large Router-LSA from multiple Link-State IDs + * RFC 5340 A 4.3: + * When more than one router-LSA is received from a single router, + * the links are processed as if concatenated into a single LSA.*/ +struct ospf6_lsa *ospf6_create_single_router_lsa(struct ospf6_area *area, + struct ospf6_lsdb *lsdb, + uint32_t adv_router) +{ + struct ospf6_lsa *lsa = NULL; + struct ospf6_lsa *rtr_lsa = NULL; + struct ospf6_lsa_header *lsa_header = NULL; + uint8_t *new_header = NULL; + const struct route_node *end = NULL; + uint16_t lsa_length, total_lsa_length = 0, num_lsa = 0; + uint16_t type = 0; + char ifbuf[16]; + uint32_t interface_id; + caddr_t lsd; + + lsa_length = sizeof(struct ospf6_lsa_header) + + sizeof(struct ospf6_router_lsa); + total_lsa_length = lsa_length; + type = htons(OSPF6_LSTYPE_ROUTER); + + /* First check Aggregated LSA formed earlier in Cache */ + lsa = ospf6_lsdb_lookup(type, htonl(0), adv_router, + area->temp_router_lsa_lsdb); + if (lsa) + return lsa; + + inet_ntop(AF_INET, &adv_router, ifbuf, sizeof(ifbuf)); + + /* Determine total LSA length from all link state ids */ + end = ospf6_lsdb_head(lsdb, 2, type, adv_router, &rtr_lsa); + while (rtr_lsa) { + lsa = rtr_lsa; + if (OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { + rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); + continue; + } + lsa_header = rtr_lsa->header; + total_lsa_length += (ospf6_lsa_size(lsa_header) - lsa_length); + num_lsa++; + rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); + } + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: adv_router %s num_lsa %u to convert.", __func__, + ifbuf, num_lsa); + if (num_lsa == 1) + return lsa; + + if (num_lsa == 0) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: adv_router %s not found in LSDB.", + __func__, ifbuf); + return NULL; + } + + lsa = ospf6_lsa_alloc(total_lsa_length); + new_header = (uint8_t *)lsa->header; + + lsa->lsdb = area->temp_router_lsa_lsdb; + + /* Fill Larger LSA Payload */ + end = ospf6_lsdb_head(lsdb, 2, type, adv_router, &rtr_lsa); + + /* + * We assume at this point in time that rtr_lsa is + * a valid pointer. + */ + assert(rtr_lsa); + if (!OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { + /* Append first Link State ID LSA */ + lsa_header = rtr_lsa->header; + memcpy(new_header, lsa_header, ospf6_lsa_size(lsa_header)); + /* Assign new lsa length as aggregated length. */ + ((struct ospf6_lsa_header *)new_header)->length = + htons(total_lsa_length); + new_header += ospf6_lsa_size(lsa_header); + num_lsa--; + } + + /* Print LSA Name */ + ospf6_lsa_printbuf(lsa, lsa->name, sizeof(lsa->name)); + + rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); + while (rtr_lsa) { + if (OSPF6_LSA_IS_MAXAGE(rtr_lsa)) { + rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); + continue; + } + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) { + lsd = ospf6_lsa_header_end(rtr_lsa->header) + 4; + interface_id = ROUTER_LSDESC_GET_IFID(lsd); + inet_ntop(AF_INET, &interface_id, ifbuf, sizeof(ifbuf)); + zlog_debug("%s: Next Router LSA %s to aggreat with len %u interface_id %s", + __func__, rtr_lsa->name, + ospf6_lsa_size(lsa_header), ifbuf); + } + + /* Append Next Link State ID LSA */ + lsa_header = rtr_lsa->header; + memcpy(new_header, (ospf6_lsa_header_end(rtr_lsa->header) + 4), + (ospf6_lsa_size(lsa_header) - lsa_length)); + new_header += (ospf6_lsa_size(lsa_header) - lsa_length); + num_lsa--; + + rtr_lsa = ospf6_lsdb_next(end, rtr_lsa); + } + + /* Calculate birth of this lsa */ + ospf6_lsa_age_set(lsa); + + /* Store Aggregated LSA into area temp lsdb */ + ospf6_lsdb_add(lsa, area->temp_router_lsa_lsdb); + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: LSA %s id %u type 0%x len %u num_lsa %u", + __func__, lsa->name, ntohl(lsa->header->id), + ntohs(lsa->header->type), + ospf6_lsa_size(lsa->header), num_lsa); + + return lsa; +} + +void ospf6_remove_temp_router_lsa(struct ospf6_area *area) +{ + struct ospf6_lsa *lsa = NULL, *lsanext; + + for (ALL_LSDB(area->temp_router_lsa_lsdb, lsa, lsanext)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + "%s Remove LSA %s lsa->lock %u lsdb count %u", + __func__, lsa->name, lsa->lock, + area->temp_router_lsa_lsdb->count); + ospf6_lsdb_remove(lsa, area->temp_router_lsa_lsdb); + } +} + +int ospf6_ase_calculate_route(struct ospf6 *ospf6, struct ospf6_lsa *lsa, + struct ospf6_area *area) +{ + struct ospf6_route *route; + struct ospf6_as_external_lsa *external; + struct prefix prefix; + void (*hook_add)(struct ospf6_route *) = NULL; + void (*hook_remove)(struct ospf6_route *) = NULL; + + assert(lsa); + + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s : start", __func__); + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7) + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: Processing Type-7", __func__); + + /* Stay away from any Local Translated Type-7 LSAs */ + if (CHECK_FLAG(lsa->flag, OSPF6_LSA_LOCAL_XLT)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: Rejecting Local translated LSA", + __func__); + return 0; + } + + external = (struct ospf6_as_external_lsa *)ospf6_lsa_header_end( + lsa->header); + prefix.family = AF_INET6; + prefix.prefixlen = external->prefix.prefix_length; + ospf6_prefix_in6_addr(&prefix.u.prefix6, external, &external->prefix); + + if (ntohs(lsa->header->type) == OSPF6_LSTYPE_AS_EXTERNAL) { + hook_add = ospf6->route_table->hook_add; + hook_remove = ospf6->route_table->hook_remove; + ospf6->route_table->hook_add = NULL; + ospf6->route_table->hook_remove = NULL; + + if (!OSPF6_LSA_IS_MAXAGE(lsa)) + ospf6_asbr_lsa_add(lsa); + + ospf6->route_table->hook_add = hook_add; + ospf6->route_table->hook_remove = hook_remove; + + route = ospf6_route_lookup(&prefix, ospf6->route_table); + if (route == NULL) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: no external route %pFX", + __func__, &prefix); + return 0; + } + + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) + && CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD)) { + UNSET_FLAG(route->flag, OSPF6_ROUTE_REMOVE); + UNSET_FLAG(route->flag, OSPF6_ROUTE_ADD); + } + + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) + ospf6_route_remove(route, ospf6->route_table); + else if (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) + || CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE)) { + if (hook_add) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + "%s: add external route %pFX", + __func__, &prefix); + (*hook_add)(route); + } + } + } else if (ntohs(lsa->header->type) == OSPF6_LSTYPE_TYPE_7) { + hook_add = area->route_table->hook_add; + hook_remove = area->route_table->hook_remove; + area->route_table->hook_add = NULL; + area->route_table->hook_remove = NULL; + + if (!OSPF6_LSA_IS_MAXAGE(lsa)) + ospf6_asbr_lsa_add(lsa); + + area->route_table->hook_add = hook_add; + area->route_table->hook_remove = hook_remove; + + route = ospf6_route_lookup(&prefix, area->route_table); + if (route == NULL) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s: no route %pFX, area %s", + __func__, &prefix, area->name); + return 0; + } + + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE) + && CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD)) { + UNSET_FLAG(route->flag, OSPF6_ROUTE_REMOVE); + UNSET_FLAG(route->flag, OSPF6_ROUTE_ADD); + } + + if (CHECK_FLAG(route->flag, OSPF6_ROUTE_REMOVE)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s : remove route %pFX, area %s", + __func__, &prefix, area->name); + ospf6_route_remove(route, area->route_table); + } else if (CHECK_FLAG(route->flag, OSPF6_ROUTE_ADD) + || CHECK_FLAG(route->flag, OSPF6_ROUTE_CHANGE)) { + if (hook_add) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug( + "%s: add nssa route %pFX, area %s", + __func__, &prefix, area->name); + (*hook_add)(route); + } + ospf6_abr_check_translate_nssa(area, lsa); + } + } + return 0; +} + +static void ospf6_ase_calculate_timer(struct event *t) +{ + struct ospf6 *ospf6; + struct ospf6_lsa *lsa; + struct listnode *node, *nnode; + struct ospf6_area *area; + uint16_t type; + + ospf6 = EVENT_ARG(t); + + /* Calculate external route for each AS-external-LSA */ + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + for (ALL_LSDB_TYPED(ospf6->lsdb, type, lsa)) + ospf6_ase_calculate_route(ospf6, lsa, NULL); + + /* This version simple adds to the table all NSSA areas */ + if (ospf6->anyNSSA) { + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, area)) { + if (IS_OSPF6_DEBUG_SPF(PROCESS)) + zlog_debug("%s : looking at area %s", __func__, + area->name); + + type = htons(OSPF6_LSTYPE_TYPE_7); + for (ALL_LSDB_TYPED(area->lsdb, type, lsa)) + ospf6_ase_calculate_route(ospf6, lsa, area); + } + } + + if (ospf6->gr_info.finishing_restart) { + /* + * The routing table computation is complete. Uninstall remnant + * routes that were installed before the restart, but that are + * no longer valid. + */ + ospf6_zebra_gr_disable(ospf6); + ospf6_zebra_gr_enable(ospf6, ospf6->gr_info.grace_period); + ospf6->gr_info.finishing_restart = false; + } +} + +void ospf6_ase_calculate_timer_add(struct ospf6 *ospf6) +{ + if (ospf6 == NULL) + return; + + event_add_timer(master, ospf6_ase_calculate_timer, ospf6, + OSPF6_ASE_CALC_INTERVAL, &ospf6->t_ase_calc); +} + +bool ospf6_merge_parents_nh_to_child(struct ospf6_vertex *v, + struct ospf6_route *route, + struct ospf6_route_table *result_table) +{ + struct ospf6_route *parent_route; + + if (v->parent && v->parent->hops) { + parent_route = + ospf6_route_lookup(&v->parent->vertex_id, result_table); + if (parent_route) { + ospf6_route_merge_nexthops(route, parent_route); + return true; + } + } + return false; +} diff --git a/ospf6d/ospf6_spf.h b/ospf6d/ospf6_spf.h new file mode 100644 index 0000000..c2fd5d9 --- /dev/null +++ b/ospf6d/ospf6_spf.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_SPF_H +#define OSPF6_SPF_H + +#include "typesafe.h" +#include "ospf6_top.h" +#include "lib/json.h" + +/* Debug option */ +extern unsigned char conf_debug_ospf6_spf; +#define OSPF6_DEBUG_SPF_PROCESS 0x01 +#define OSPF6_DEBUG_SPF_TIME 0x02 +#define OSPF6_DEBUG_SPF_DATABASE 0x04 +#define OSPF6_DEBUG_SPF_ON(level) (conf_debug_ospf6_spf |= (level)) +#define OSPF6_DEBUG_SPF_OFF(level) (conf_debug_ospf6_spf &= ~(level)) +#define IS_OSPF6_DEBUG_SPF(level) \ + (conf_debug_ospf6_spf & OSPF6_DEBUG_SPF_##level) + +#define OSPF6_ASE_CALC_INTERVAL 1 + +PREDECL_SKIPLIST_NONUNIQ(vertex_pqueue); +/* Transit Vertex */ +struct ospf6_vertex { + /* type of this vertex */ + uint8_t type; + + /* Vertex Identifier */ + struct prefix vertex_id; + + struct vertex_pqueue_item pqi; + + /* Identifier String */ + char name[128]; + + /* Associated Area */ + struct ospf6_area *area; + + /* Associated LSA */ + struct ospf6_lsa *lsa; + + /* Distance from Root (i.e. Cost) */ + uint32_t cost; + + /* Router hops to this node */ + uint8_t hops; + + /* capability bits */ + uint8_t capability; + + /* Optional capabilities */ + uint8_t options[3]; + + /* For tree display */ + struct ospf6_vertex *parent; + struct list *child_list; + + /* nexthops to this node */ + struct list *nh_list; + uint32_t link_id; +}; + +#define OSPF6_VERTEX_TYPE_ROUTER 0x01 +#define OSPF6_VERTEX_TYPE_NETWORK 0x02 +#define VERTEX_IS_TYPE(t, v) ((v)->type == OSPF6_VERTEX_TYPE_##t ? 1 : 0) + +/* What triggered the SPF? */ +#define OSPF6_SPF_FLAGS_ROUTER_LSA_ADDED (1 << 0) +#define OSPF6_SPF_FLAGS_ROUTER_LSA_REMOVED (1 << 1) +#define OSPF6_SPF_FLAGS_NETWORK_LSA_ADDED (1 << 2) +#define OSPF6_SPF_FLAGS_NETWORK_LSA_REMOVED (1 << 3) +#define OSPF6_SPF_FLAGS_LINK_LSA_ADDED (1 << 4) +#define OSPF6_SPF_FLAGS_LINK_LSA_REMOVED (1 << 5) +#define OSPF6_SPF_FLAGS_ROUTER_LSA_ORIGINATED (1 << 6) +#define OSPF6_SPF_FLAGS_NETWORK_LSA_ORIGINATED (1 << 7) +#define OSPF6_SPF_FLAGS_CONFIG_CHANGE (1 << 8) +#define OSPF6_SPF_FLAGS_ASBR_STATUS_CHANGE (1 << 9) +#define OSPF6_SPF_FLAGS_GR_FINISH (1 << 10) + +static inline void ospf6_set_spf_reason(struct ospf6 *ospf, unsigned int reason) +{ + ospf->spf_reason |= reason; +} + +static inline void ospf6_reset_spf_reason(struct ospf6 *ospf) +{ + ospf->spf_reason = 0; +} + +static inline unsigned int ospf6_lsadd_to_spf_reason(struct ospf6_lsa *lsa) +{ + unsigned int reason = 0; + + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_ROUTER: + reason = OSPF6_SPF_FLAGS_ROUTER_LSA_ADDED; + break; + case OSPF6_LSTYPE_NETWORK: + reason = OSPF6_SPF_FLAGS_NETWORK_LSA_ADDED; + break; + case OSPF6_LSTYPE_LINK: + reason = OSPF6_SPF_FLAGS_LINK_LSA_ADDED; + break; + default: + break; + } + return (reason); +} + +static inline unsigned int ospf6_lsremove_to_spf_reason(struct ospf6_lsa *lsa) +{ + unsigned int reason = 0; + + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_ROUTER: + reason = OSPF6_SPF_FLAGS_ROUTER_LSA_REMOVED; + break; + case OSPF6_LSTYPE_NETWORK: + reason = OSPF6_SPF_FLAGS_NETWORK_LSA_REMOVED; + break; + case OSPF6_LSTYPE_LINK: + reason = OSPF6_SPF_FLAGS_LINK_LSA_REMOVED; + break; + default: + break; + } + return (reason); +} + +extern void ospf6_spf_table_finish(struct ospf6_route_table *result_table); +extern void ospf6_spf_calculation(uint32_t router_id, + struct ospf6_route_table *result_table, + struct ospf6_area *oa); +extern void ospf6_spf_schedule(struct ospf6 *ospf, unsigned int reason); + +extern void ospf6_spf_display_subtree(struct vty *vty, const char *prefix, + int rest, struct ospf6_vertex *v, + json_object *json_obj, bool use_json); + +extern void ospf6_spf_config_write(struct vty *vty, struct ospf6 *ospf6); +extern int config_write_ospf6_debug_spf(struct vty *vty); +extern void install_element_ospf6_debug_spf(void); +extern void ospf6_spf_init(void); +extern void ospf6_spf_reason_string(unsigned int reason, char *buf, int size); +extern struct ospf6_lsa *ospf6_create_single_router_lsa(struct ospf6_area *area, + struct ospf6_lsdb *lsdb, + uint32_t adv_router); +extern void ospf6_remove_temp_router_lsa(struct ospf6_area *area); +extern void ospf6_ase_calculate_timer_add(struct ospf6 *ospf6); +extern int ospf6_ase_calculate_route(struct ospf6 *ospf6, struct ospf6_lsa *lsa, + struct ospf6_area *area); +extern bool +ospf6_merge_parents_nh_to_child(struct ospf6_vertex *v, + struct ospf6_route *route, + struct ospf6_route_table *result_table); +#endif /* OSPF6_SPF_H */ diff --git a/ospf6d/ospf6_top.c b/ospf6d/ospf6_top.c new file mode 100644 index 0000000..a3fb205 --- /dev/null +++ b/ospf6d/ospf6_top.c @@ -0,0 +1,2233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "memory.h" +#include "vty.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "frrevent.h" +#include "command.h" +#include "defaults.h" +#include "lib/json.h" +#include "lib_errors.h" +#include "frrdistance.h" + +#include "ospf6_proto.h" +#include "ospf6_message.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" + +#include "ospf6_top.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_network.h" + +#include "ospf6_flood.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6_intra.h" +#include "ospf6_spf.h" +#include "ospf6d.h" +#include "ospf6_gr.h" +#include "lib/json.h" +#include "ospf6_nssa.h" +#include "ospf6_auth_trailer.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_TOP, "OSPF6 top"); + +DEFINE_QOBJ_TYPE(ospf6); + +FRR_CFG_DEFAULT_BOOL(OSPF6_LOG_ADJACENCY_CHANGES, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); + +#include "ospf6d/ospf6_top_clippy.c" + +/* global ospf6d variable */ +static struct ospf6_master ospf6_master; +struct ospf6_master *om6; + +static void ospf6_disable(struct ospf6 *o); + +static void ospf6_add(struct ospf6 *ospf6) +{ + listnode_add(om6->ospf6, ospf6); +} + +static void ospf6_del(struct ospf6 *ospf6) +{ + listnode_delete(om6->ospf6, ospf6); +} + +const char *ospf6_vrf_id_to_name(vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + return vrf ? vrf->name : "NIL"; +} + +/* Link OSPF instance to VRF. */ +void ospf6_vrf_link(struct ospf6 *ospf6, struct vrf *vrf) +{ + ospf6->vrf_id = vrf->vrf_id; + if (vrf->info != (void *)ospf6) + vrf->info = (void *)ospf6; +} + +/* Unlink OSPF instance from VRF. */ +void ospf6_vrf_unlink(struct ospf6 *ospf6, struct vrf *vrf) +{ + if (vrf->info == (void *)ospf6) + vrf->info = NULL; + ospf6->vrf_id = VRF_UNKNOWN; +} + +struct ospf6 *ospf6_lookup_by_vrf_id(vrf_id_t vrf_id) +{ + struct vrf *vrf = NULL; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + return (vrf->info) ? (struct ospf6 *)vrf->info : NULL; +} + +struct ospf6 *ospf6_lookup_by_vrf_name(const char *name) +{ + struct ospf6 *o = NULL; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, o)) { + if (((o->name == NULL && name == NULL) + || (o->name && name && strcmp(o->name, name) == 0))) + return o; + } + return NULL; +} + +/* This is hook function for vrf create called as part of vrf_init */ +static int ospf6_vrf_new(struct vrf *vrf) +{ + return 0; +} + +/* This is hook function for vrf delete call as part of vrf_init */ +static int ospf6_vrf_delete(struct vrf *vrf) +{ + return 0; +} + +static void ospf6_set_redist_vrf_bitmaps(struct ospf6 *ospf6, bool set) +{ + int type; + struct list *red_list; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red_list = ospf6->redist[type]; + if (!red_list) + continue; + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug( + "%s: setting redist vrf %d bitmap for type %d", + __func__, ospf6->vrf_id, type); + if (set) + vrf_bitmap_set(&zclient->redist[AFI_IP6][type], + ospf6->vrf_id); + else + vrf_bitmap_unset(&zclient->redist[AFI_IP6][type], + ospf6->vrf_id); + } + + red_list = ospf6->redist[DEFAULT_ROUTE]; + if (red_list) { + if (set) + vrf_bitmap_set(&zclient->default_information[AFI_IP6], + ospf6->vrf_id); + else + vrf_bitmap_unset(&zclient->default_information[AFI_IP6], + ospf6->vrf_id); + } +} + +/* Disable OSPF6 VRF instance */ +static int ospf6_vrf_disable(struct vrf *vrf) +{ + struct ospf6 *ospf6 = NULL; + + if (vrf->vrf_id == VRF_DEFAULT) + return 0; + + ospf6 = ospf6_lookup_by_vrf_name(vrf->name); + if (ospf6) { + ospf6_zebra_vrf_deregister(ospf6); + + ospf6_set_redist_vrf_bitmaps(ospf6, false); + + /* We have instance configured, unlink + * from VRF and make it "down". + */ + ospf6_vrf_unlink(ospf6, vrf); + event_cancel(&ospf6->t_ospf6_receive); + close(ospf6->fd); + ospf6->fd = -1; + } + + /* Note: This is a callback, the VRF will be deleted by the caller. */ + return 0; +} + +/* Enable OSPF6 VRF instance */ +static int ospf6_vrf_enable(struct vrf *vrf) +{ + struct ospf6 *ospf6 = NULL; + vrf_id_t old_vrf_id; + int ret = 0; + + ospf6 = ospf6_lookup_by_vrf_name(vrf->name); + if (ospf6) { + old_vrf_id = ospf6->vrf_id; + /* We have instance configured, link to VRF and make it "up". */ + ospf6_vrf_link(ospf6, vrf); + + if (old_vrf_id != ospf6->vrf_id) { + ospf6_set_redist_vrf_bitmaps(ospf6, true); + + /* start zebra redist to us for new vrf */ + ospf6_zebra_vrf_register(ospf6); + + ret = ospf6_serv_sock(ospf6); + if (ret < 0 || ospf6->fd <= 0) + return 0; + event_add_read(master, ospf6_receive, ospf6, ospf6->fd, + &ospf6->t_ospf6_receive); + + ospf6_router_id_update(ospf6, true); + } + } + + return 0; +} + +void ospf6_vrf_init(void) +{ + vrf_init(ospf6_vrf_new, ospf6_vrf_enable, ospf6_vrf_disable, + ospf6_vrf_delete); + + vrf_cmd_init(NULL); +} + +static void ospf6_top_lsdb_hook_add(struct ospf6_lsa *lsa) +{ + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_AS_EXTERNAL: + ospf6_asbr_lsa_add(lsa); + break; + + default: + break; + } +} + +static void ospf6_top_lsdb_hook_remove(struct ospf6_lsa *lsa) +{ + switch (ntohs(lsa->header->type)) { + case OSPF6_LSTYPE_AS_EXTERNAL: + ospf6_asbr_lsa_remove(lsa, NULL); + break; + + default: + break; + } +} + +static void ospf6_top_route_hook_add(struct ospf6_route *route) +{ + struct ospf6 *ospf6 = NULL; + struct ospf6_area *oa = NULL; + + if (route->table->scope_type == OSPF6_SCOPE_TYPE_GLOBAL) + ospf6 = route->table->scope; + else if (route->table->scope_type == OSPF6_SCOPE_TYPE_AREA) { + oa = (struct ospf6_area *)route->table->scope; + ospf6 = oa->ospf6; + } else { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) + || IS_OSPF6_DEBUG_BROUTER) + zlog_debug( + "%s: Route is not GLOBAL or scope is not of TYPE_AREA: %pFX", + __func__, &route->prefix); + return; + } + + ospf6_abr_originate_summary(route, ospf6); + ospf6_zebra_route_update_add(route, ospf6); +} + +static void ospf6_top_route_hook_remove(struct ospf6_route *route) +{ + struct ospf6 *ospf6 = NULL; + struct ospf6_area *oa = NULL; + + if (route->table->scope_type == OSPF6_SCOPE_TYPE_GLOBAL) + ospf6 = route->table->scope; + else if (route->table->scope_type == OSPF6_SCOPE_TYPE_AREA) { + oa = (struct ospf6_area *)route->table->scope; + ospf6 = oa->ospf6; + } else { + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) + || IS_OSPF6_DEBUG_BROUTER) + zlog_debug( + "%s: Route is not GLOBAL or scope is not of TYPE_AREA: %pFX", + __func__, &route->prefix); + return; + } + + route->flag |= OSPF6_ROUTE_REMOVE; + ospf6_abr_originate_summary(route, ospf6); + ospf6_zebra_route_update_remove(route, ospf6); +} + +static void ospf6_top_brouter_hook_add(struct ospf6_route *route) +{ + struct ospf6 *ospf6 = route->table->scope; + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || + IS_OSPF6_DEBUG_BROUTER) { + uint32_t brouter_id; + char brouter_name[16]; + + brouter_id = ADV_ROUTER_IN_PREFIX(&route->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, + sizeof(brouter_name)); + zlog_debug("%s: brouter %s add with adv router %x nh count %u", + __func__, brouter_name, + route->path.origin.adv_router, + listcount(route->nh_list)); + } + ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix), route, + ospf6); + ospf6_asbr_lsentry_add(route, ospf6); + ospf6_abr_originate_summary(route, ospf6); +} + +static void ospf6_top_brouter_hook_remove(struct ospf6_route *route) +{ + struct ospf6 *ospf6 = route->table->scope; + + if (IS_OSPF6_DEBUG_EXAMIN(AS_EXTERNAL) || + IS_OSPF6_DEBUG_BROUTER) { + uint32_t brouter_id; + char brouter_name[16]; + + brouter_id = ADV_ROUTER_IN_PREFIX(&route->prefix); + inet_ntop(AF_INET, &brouter_id, brouter_name, + sizeof(brouter_name)); + zlog_debug("%s: brouter %p %s del with adv router %x nh %u", + __func__, (void *)route, brouter_name, + route->path.origin.adv_router, + listcount(route->nh_list)); + } + route->flag |= OSPF6_ROUTE_REMOVE; + ospf6_abr_examin_brouter(ADV_ROUTER_IN_PREFIX(&route->prefix), route, + ospf6); + ospf6_asbr_lsentry_remove(route, ospf6); + ospf6_abr_originate_summary(route, ospf6); +} + +static struct ospf6 *ospf6_create(const char *name) +{ + struct ospf6 *o; + struct vrf *vrf = NULL; + + o = XCALLOC(MTYPE_OSPF6_TOP, sizeof(struct ospf6)); + + vrf = vrf_lookup_by_name(name); + if (vrf) { + o->vrf_id = vrf->vrf_id; + } else + o->vrf_id = VRF_UNKNOWN; + + /* Freed in ospf6_delete */ + o->name = XSTRDUP(MTYPE_OSPF6_TOP, name); + if (vrf) + ospf6_vrf_link(o, vrf); + + ospf6_zebra_vrf_register(o); + + /* initialize */ + monotime(&o->starttime); + o->area_list = list_new(); + o->area_list->cmp = ospf6_area_cmp; + o->lsdb = ospf6_lsdb_create(o); + o->lsdb_self = ospf6_lsdb_create(o); + o->lsdb->hook_add = ospf6_top_lsdb_hook_add; + o->lsdb->hook_remove = ospf6_top_lsdb_hook_remove; + + o->spf_delay = OSPF_SPF_DELAY_DEFAULT; + o->spf_holdtime = OSPF_SPF_HOLDTIME_DEFAULT; + o->spf_max_holdtime = OSPF_SPF_MAX_HOLDTIME_DEFAULT; + o->spf_hold_multiplier = 1; + + o->default_originate = DEFAULT_ORIGINATE_NONE; + o->redistribute = 0; + /* LSA timers value init */ + o->lsa_minarrival = OSPF_MIN_LS_ARRIVAL; + + o->route_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, ROUTES); + o->route_table->scope = o; + o->route_table->hook_add = ospf6_top_route_hook_add; + o->route_table->hook_remove = ospf6_top_route_hook_remove; + + o->brouter_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, BORDER_ROUTERS); + o->brouter_table->scope = o; + o->brouter_table->hook_add = ospf6_top_brouter_hook_add; + o->brouter_table->hook_remove = ospf6_top_brouter_hook_remove; + + o->external_table = OSPF6_ROUTE_TABLE_CREATE(GLOBAL, EXTERNAL_ROUTES); + o->external_table->scope = o; + /* Setting this to 1, so that the LS ID 0 can be considered as invalid + * for self originated external LSAs. This helps in differentiating if + * an LSA is originated for any route or not in the route data. + * rt->route_option->id is by default 0 + * Consider a route having id as 0 and prefix as 1::1, an external LSA + * is originated with ID 0.0.0.0. Now consider another route 2::2 + * and for this LSA was not originated because of some configuration + * but the ID field rt->route_option->id is still 0.Consider now this + * 2::2 is being deleted, it will search LSA with LS ID as 0 and it + * will find the LSA and hence delete it but the LSA belonged to prefix + * 1::1, this happened because of LS ID 0. + */ + o->external_id = OSPF6_EXT_INIT_LS_ID; + + o->write_oi_count = OSPF6_WRITE_INTERFACE_COUNT_DEFAULT; + o->ref_bandwidth = OSPF6_REFERENCE_BANDWIDTH; + + o->distance_table = route_table_init(); + + o->rt_aggr_tbl = route_table_init(); + o->aggr_delay_interval = OSPF6_EXTL_AGGR_DEFAULT_DELAY; + o->aggr_action = OSPF6_ROUTE_AGGR_NONE; + + o->fd = -1; + + o->max_multipath = MULTIPATH_NUM; + + o->oi_write_q = list_new(); + + ospf6_gr_helper_init(o); + QOBJ_REG(o, ospf6); + + /* Make ospf protocol socket. */ + ospf6_serv_sock(o); + + ospf6_auth_init(o); + return o; +} + +struct ospf6 *ospf6_instance_create(const char *name) +{ + struct ospf6 *ospf6; + struct vrf *vrf; + struct interface *ifp; + + ospf6 = ospf6_create(name); + if (DFLT_OSPF6_LOG_ADJACENCY_CHANGES) + SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); + if (ospf6->router_id == 0) + ospf6_router_id_update(ospf6, true); + ospf6_add(ospf6); + + /* + * Read from non-volatile memory whether this instance is performing a + * graceful restart or not. + */ + ospf6_gr_nvm_read(ospf6); + + if (ospf6->vrf_id != VRF_UNKNOWN) { + vrf = vrf_lookup_by_id(ospf6->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->info) + ospf6_interface_start(ifp->info); + } + } + if (ospf6->fd < 0) + return ospf6; + + event_add_read(master, ospf6_receive, ospf6, ospf6->fd, + &ospf6->t_ospf6_receive); + + return ospf6; +} + +void ospf6_delete(struct ospf6 *o) +{ + struct listnode *node, *nnode; + struct route_node *rn = NULL; + struct ospf6_area *oa; + struct vrf *vrf; + struct ospf6_external_aggr_rt *aggr; + uint32_t i; + + QOBJ_UNREG(o); + + ospf6_gr_helper_deinit(o); + if (!o->gr_info.prepare_in_progress) + ospf6_flush_self_originated_lsas_now(o); + XFREE(MTYPE_TMP, o->gr_info.exit_reason); + ospf6_disable(o); + ospf6_del(o); + + ospf6_zebra_vrf_deregister(o); + + ospf6_serv_close(&o->fd); + + for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) + ospf6_area_delete(oa); + + + list_delete(&o->area_list); + + ospf6_lsdb_delete(o->lsdb); + ospf6_lsdb_delete(o->lsdb_self); + + ospf6_route_table_delete(o->route_table); + ospf6_route_table_delete(o->brouter_table); + + ospf6_route_table_delete(o->external_table); + + ospf6_distance_reset(o); + route_table_finish(o->distance_table); + list_delete(&o->oi_write_q); + + if (o->vrf_id != VRF_UNKNOWN) { + vrf = vrf_lookup_by_id(o->vrf_id); + if (vrf) + ospf6_vrf_unlink(o, vrf); + } + + for (rn = route_top(o->rt_aggr_tbl); rn; rn = route_next(rn)) + if (rn->info) { + aggr = rn->info; + ospf6_asbr_summary_config_delete(o, rn); + ospf6_external_aggregator_free(aggr); + } + route_table_finish(o->rt_aggr_tbl); + + for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) { + if (!o->redist[i]) + continue; + + list_delete(&o->redist[i]); + } + + XFREE(MTYPE_OSPF6_TOP, o->name); + XFREE(MTYPE_OSPF6_TOP, o); +} + +static void ospf6_disable(struct ospf6 *o) +{ + struct listnode *node, *nnode; + struct ospf6_area *oa; + + if (!CHECK_FLAG(o->flag, OSPF6_DISABLED)) { + SET_FLAG(o->flag, OSPF6_DISABLED); + + for (ALL_LIST_ELEMENTS(o->area_list, node, nnode, oa)) + ospf6_area_disable(oa); + + /* XXX: This also changes persistent settings */ + /* Unregister redistribution */ + ospf6_asbr_redistribute_disable(o); + + ospf6_lsdb_remove_all(o->lsdb); + ospf6_route_remove_all(o->route_table); + ospf6_route_remove_all(o->brouter_table); + + EVENT_OFF(o->maxage_remover); + EVENT_OFF(o->t_spf_calc); + EVENT_OFF(o->t_ase_calc); + EVENT_OFF(o->t_distribute_update); + EVENT_OFF(o->t_ospf6_receive); + EVENT_OFF(o->t_external_aggr); + EVENT_OFF(o->gr_info.t_grace_period); + EVENT_OFF(o->t_write); + EVENT_OFF(o->t_abr_task); + } +} + +void ospf6_master_init(struct event_loop *master) +{ + memset(&ospf6_master, 0, sizeof(ospf6_master)); + + om6 = &ospf6_master; + om6->ospf6 = list_new(); + om6->master = master; +} + +void ospf6_master_delete(void) +{ + list_delete(&om6->ospf6); +} + +static void ospf6_maxage_remover(struct event *thread) +{ + struct ospf6 *o = (struct ospf6 *)EVENT_ARG(thread); + struct ospf6_area *oa; + struct ospf6_interface *oi; + struct ospf6_neighbor *on; + struct listnode *i, *j, *k; + int reschedule = 0; + + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, k, on)) { + if (on->state != OSPF6_NEIGHBOR_EXCHANGE + && on->state != OSPF6_NEIGHBOR_LOADING) + continue; + + ospf6_maxage_remove(o); + return; + } + } + } + + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + if (ospf6_lsdb_maxage_remover(oi->lsdb)) { + reschedule = 1; + } + } + + if (ospf6_lsdb_maxage_remover(oa->lsdb)) { + reschedule = 1; + } + } + + if (ospf6_lsdb_maxage_remover(o->lsdb)) { + reschedule = 1; + } + + if (reschedule) { + ospf6_maxage_remove(o); + } +} + +void ospf6_maxage_remove(struct ospf6 *o) +{ + if (o) + event_add_timer(master, ospf6_maxage_remover, o, + OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT, + &o->maxage_remover); +} + +bool ospf6_router_id_update(struct ospf6 *ospf6, bool init) +{ + in_addr_t new_router_id; + struct listnode *node; + struct ospf6_area *oa; + + if (!ospf6) + return true; + + if (ospf6->router_id_static != 0) + new_router_id = ospf6->router_id_static; + else + new_router_id = ospf6->router_id_zebra; + + if (ospf6->router_id == new_router_id) + return true; + + if (!init) + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + if (oa->full_nbrs) { + zlog_err( + "%s: cannot update router-id. Run the \"clear ipv6 ospf6 process\" command", + __func__); + return false; + } + } + + ospf6->router_id = new_router_id; + return true; +} + +/* start ospf6 */ +DEFUN_NOSH(router_ospf6, router_ospf6_cmd, "router ospf6 [vrf NAME]", + ROUTER_STR OSPF6_STR VRF_CMD_HELP_STR) +{ + struct ospf6 *ospf6; + const char *vrf_name = VRF_DEFAULT_NAME; + int idx_vrf = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) { + vrf_name = argv[idx_vrf + 1]->arg; + } + + ospf6 = ospf6_lookup_by_vrf_name(vrf_name); + if (ospf6 == NULL) + ospf6 = ospf6_instance_create(vrf_name); + + /* set current ospf point. */ + VTY_PUSH_CONTEXT(OSPF6_NODE, ospf6); + + return CMD_SUCCESS; +} + +/* stop ospf6 */ +DEFUN(no_router_ospf6, no_router_ospf6_cmd, "no router ospf6 [vrf NAME]", + NO_STR ROUTER_STR OSPF6_STR VRF_CMD_HELP_STR) +{ + struct ospf6 *ospf6; + const char *vrf_name = VRF_DEFAULT_NAME; + int idx_vrf = 0; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) { + vrf_name = argv[idx_vrf + 1]->arg; + } + + ospf6 = ospf6_lookup_by_vrf_name(vrf_name); + if (ospf6 == NULL) + vty_out(vty, "OSPFv3 is not configured\n"); + else { + if (ospf6->gr_info.restart_support) + ospf6_gr_nvm_delete(ospf6); + + ospf6_delete(ospf6); + ospf6 = NULL; + } + + /* return to config node . */ + VTY_PUSH_CONTEXT_NULL(CONFIG_NODE); + + return CMD_SUCCESS; +} + +static void ospf6_db_clear(struct ospf6 *ospf6) +{ + struct ospf6_interface *oi; + struct interface *ifp; + struct vrf *vrf = vrf_lookup_by_id(ospf6->vrf_id); + struct listnode *node, *nnode; + struct ospf6_area *oa; + + FOR_ALL_INTERFACES (vrf, ifp) { + if (if_is_operative(ifp) && ifp->info != NULL) { + oi = (struct ospf6_interface *)ifp->info; + ospf6_lsdb_remove_all(oi->lsdb); + ospf6_lsdb_remove_all(oi->lsdb_self); + ospf6_lsdb_remove_all(oi->lsupdate_list); + ospf6_lsdb_remove_all(oi->lsack_list); + } + } + + for (ALL_LIST_ELEMENTS(ospf6->area_list, node, nnode, oa)) { + ospf6_lsdb_remove_all(oa->lsdb); + ospf6_lsdb_remove_all(oa->lsdb_self); + + ospf6_spf_table_finish(oa->spf_table); + ospf6_route_remove_all(oa->route_table); + } + + ospf6_lsdb_remove_all(ospf6->lsdb); + ospf6_lsdb_remove_all(ospf6->lsdb_self); + ospf6_route_remove_all(ospf6->route_table); + ospf6_route_remove_all(ospf6->brouter_table); +} + +static void ospf6_process_reset(struct ospf6 *ospf6) +{ + struct interface *ifp; + struct vrf *vrf = vrf_lookup_by_id(ospf6->vrf_id); + + ospf6_unset_all_aggr_flag(ospf6); + ospf6_flush_self_originated_lsas_now(ospf6); + ospf6->inst_shutdown = 0; + ospf6_db_clear(ospf6); + + ospf6_asbr_redistribute_reset(ospf6); + FOR_ALL_INTERFACES (vrf, ifp) + ospf6_interface_clear(ifp); +} + +DEFPY (clear_router_ospf6, + clear_router_ospf6_cmd, + "clear ipv6 ospf6 process [vrf NAME$name]", + CLEAR_STR + IP6_STR + OSPF6_STR + "Reset OSPF Process\n" + VRF_CMD_HELP_STR) +{ + struct ospf6 *ospf6; + const char *vrf_name = VRF_DEFAULT_NAME; + + if (name != NULL) + vrf_name = name; + + ospf6 = ospf6_lookup_by_vrf_name(vrf_name); + if (ospf6 == NULL) { + vty_out(vty, "OSPFv3 is not configured\n"); + } else { + ospf6_router_id_update(ospf6, true); + ospf6_process_reset(ospf6); + } + + return CMD_SUCCESS; +} + +/* change Router_ID commands. */ +DEFUN(ospf6_router_id, + ospf6_router_id_cmd, + "ospf6 router-id A.B.C.D", + OSPF6_STR + "Configure OSPF6 Router-ID\n" + V4NOTATION_STR) +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + int idx = 0; + int ret; + const char *router_id_str; + uint32_t router_id; + + argv_find(argv, argc, "A.B.C.D", &idx); + router_id_str = argv[idx]->arg; + + ret = inet_pton(AF_INET, router_id_str, &router_id); + if (ret == 0) { + vty_out(vty, "malformed OSPF Router-ID: %s\n", router_id_str); + return CMD_SUCCESS; + } + + o->router_id_static = router_id; + + if (ospf6_router_id_update(o, false)) + ospf6_process_reset(o); + else + vty_out(vty, + "For this router-id change to take effect run the \"clear ipv6 ospf6 process\" command\n"); + + return CMD_SUCCESS; +} + +DEFUN(no_ospf6_router_id, + no_ospf6_router_id_cmd, + "no ospf6 router-id [A.B.C.D]", + NO_STR OSPF6_STR + "Configure OSPF6 Router-ID\n" + V4NOTATION_STR) +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + + o->router_id_static = 0; + + + if (ospf6_router_id_update(o, false)) + ospf6_process_reset(o); + else + vty_out(vty, + "For this router-id change to take effect run the \"clear ipv6 ospf6 process\" command\n"); + + return CMD_SUCCESS; +} + +DEFUN (ospf6_log_adjacency_changes, + ospf6_log_adjacency_changes_cmd, + "log-adjacency-changes", + "Log changes in adjacency state\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); + UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +DEFUN (ospf6_log_adjacency_changes_detail, + ospf6_log_adjacency_changes_detail_cmd, + "log-adjacency-changes detail", + "Log changes in adjacency state\n" + "Log all state changes\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); + SET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_log_adjacency_changes, + no_ospf6_log_adjacency_changes_cmd, + "no log-adjacency-changes", + NO_STR + "Log changes in adjacency state\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); + UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_CHANGES); + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_log_adjacency_changes_detail, + no_ospf6_log_adjacency_changes_detail_cmd, + "no log-adjacency-changes detail", + NO_STR + "Log changes in adjacency state\n" + "Log all state changes\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + UNSET_FLAG(ospf6->config_flags, OSPF6_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +static void ospf6_reinstall_routes(struct ospf6 *ospf6) +{ + struct ospf6_route *route; + + for (route = ospf6_route_head(ospf6->route_table); route; + route = ospf6_route_next(route)) + ospf6_zebra_route_update_add(route, ospf6); +} + +DEFPY (ospf6_send_extra_data, + ospf6_send_extra_data_cmd, + "[no] ospf6 send-extra-data zebra", + NO_STR + OSPF6_STR + "Extra data to Zebra for display/use\n" + "To zebra\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + if (no + && CHECK_FLAG(ospf6->config_flags, + OSPF6_SEND_EXTRA_DATA_TO_ZEBRA)) { + UNSET_FLAG(ospf6->config_flags, OSPF6_SEND_EXTRA_DATA_TO_ZEBRA); + ospf6_reinstall_routes(ospf6); + } else if (!CHECK_FLAG(ospf6->config_flags, + OSPF6_SEND_EXTRA_DATA_TO_ZEBRA)) { + SET_FLAG(ospf6->config_flags, OSPF6_SEND_EXTRA_DATA_TO_ZEBRA); + ospf6_reinstall_routes(ospf6); + } + + return CMD_SUCCESS; +} + +DEFUN (ospf6_timers_lsa, + ospf6_timers_lsa_cmd, + "timers lsa min-arrival (0-600000)", + "Adjust routing timers\n" + "OSPF6 LSA timers\n" + "Minimum delay in receiving new version of a LSA\n" + "Delay in milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf); + int idx_number = 3; + unsigned int minarrival; + + minarrival = strtoul(argv[idx_number]->arg, NULL, 10); + ospf->lsa_minarrival = minarrival; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_timers_lsa, + no_ospf6_timers_lsa_cmd, + "no timers lsa min-arrival [(0-600000)]", + NO_STR + "Adjust routing timers\n" + "OSPF6 LSA timers\n" + "Minimum delay in receiving new version of a LSA\n" + "Delay in milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf); + int idx_number = 4; + unsigned int minarrival; + + if (argc == 5) { + minarrival = strtoul(argv[idx_number]->arg, NULL, 10); + + if (ospf->lsa_minarrival != minarrival + || minarrival == OSPF_MIN_LS_ARRIVAL) + return CMD_SUCCESS; + } + + ospf->lsa_minarrival = OSPF_MIN_LS_ARRIVAL; + + return CMD_SUCCESS; +} + + +DEFUN (ospf6_distance, + ospf6_distance_cmd, + "distance (1-255)", + "Administrative distance\n" + "OSPF6 Administrative distance\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + uint8_t distance; + + distance = atoi(argv[1]->arg); + if (o->distance_all != distance) { + o->distance_all = distance; + ospf6_restart_spf(o); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_distance, + no_ospf6_distance_cmd, + "no distance (1-255)", + NO_STR + "Administrative distance\n" + "OSPF6 Administrative distance\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + + if (o->distance_all) { + o->distance_all = 0; + ospf6_restart_spf(o); + } + return CMD_SUCCESS; +} + +DEFUN (ospf6_distance_ospf6, + ospf6_distance_ospf6_cmd, + "distance ospf6 {intra-area (1-255)|inter-area (1-255)|external (1-255)}", + "Administrative distance\n" + "OSPF6 administrative distance\n" + "Intra-area routes\n" + "Distance for intra-area routes\n" + "Inter-area routes\n" + "Distance for inter-area routes\n" + "External routes\n" + "Distance for external routes\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + int idx = 0; + + o->distance_intra = 0; + o->distance_inter = 0; + o->distance_external = 0; + + if (argv_find(argv, argc, "intra-area", &idx)) + o->distance_intra = atoi(argv[idx + 1]->arg); + idx = 0; + if (argv_find(argv, argc, "inter-area", &idx)) + o->distance_inter = atoi(argv[idx + 1]->arg); + idx = 0; + if (argv_find(argv, argc, "external", &idx)) + o->distance_external = atoi(argv[idx + 1]->arg); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_distance_ospf6, + no_ospf6_distance_ospf6_cmd, + "no distance ospf6 [{intra-area [(1-255)]|inter-area [(1-255)]|external [(1-255)]}]", + NO_STR + "Administrative distance\n" + "OSPF6 distance\n" + "Intra-area routes\n" + "Distance for intra-area routes\n" + "Inter-area routes\n" + "Distance for inter-area routes\n" + "External routes\n" + "Distance for external routes\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, o); + int idx = 0; + + if (argv_find(argv, argc, "intra-area", &idx) || argc == 3) + idx = o->distance_intra = 0; + if (argv_find(argv, argc, "inter-area", &idx) || argc == 3) + idx = o->distance_inter = 0; + if (argv_find(argv, argc, "external", &idx) || argc == 3) + o->distance_external = 0; + + return CMD_SUCCESS; +} + +DEFUN (ospf6_stub_router_admin, + ospf6_stub_router_admin_cmd, + "stub-router administrative", + "Make router a stub router\n" + "Administratively applied, for an indefinite period\n") +{ + struct listnode *node; + struct ospf6_area *oa; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + if (!CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_V6); + OSPF6_OPT_CLEAR(oa->options, OSPF6_OPT_R); + OSPF6_ROUTER_LSA_SCHEDULE(oa); + } + SET_FLAG(ospf6->flag, OSPF6_STUB_ROUTER); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf6_stub_router_admin, + no_ospf6_stub_router_admin_cmd, + "no stub-router administrative", + NO_STR + "Make router a stub router\n" + "Administratively applied, for an indefinite period\n") +{ + struct listnode *node; + struct ospf6_area *oa; + + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + if (CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + OSPF6_OPT_SET(oa->options, OSPF6_OPT_V6); + OSPF6_OPT_SET(oa->options, OSPF6_OPT_R); + OSPF6_ROUTER_LSA_SCHEDULE(oa); + } + UNSET_FLAG(ospf6->flag, OSPF6_STUB_ROUTER); + } + + return CMD_SUCCESS; +} + +/* Restart OSPF SPF algorithm*/ +void ospf6_restart_spf(struct ospf6 *ospf6) +{ + ospf6_route_remove_all(ospf6->route_table); + ospf6_route_remove_all(ospf6->brouter_table); + + /* Trigger SPF */ + ospf6_spf_schedule(ospf6, OSPF6_SPF_FLAGS_CONFIG_CHANGE); +} + +/* Set the max paths */ +static void ospf6_maxpath_set(struct ospf6 *ospf6, uint16_t paths) +{ + if (ospf6->max_multipath == paths) + return; + + ospf6->max_multipath = paths; + + /* Send deletion to zebra to delete all + * ospf specific routes and reinitiate + * SPF to reflect the new max multipath. + */ + ospf6_restart_spf(ospf6); +} + +/* Ospf Maximum-paths config support */ +DEFUN(ospf6_max_multipath, + ospf6_max_multipath_cmd, + "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Max no of multiple paths for ECMP support\n" + "Number of paths\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + int idx_number = 1; + int maximum_paths = strtol(argv[idx_number]->arg, NULL, 10); + + ospf6_maxpath_set(ospf6, maximum_paths); + + return CMD_SUCCESS; +} + +DEFUN(no_ospf6_max_multipath, + no_ospf6_max_multipath_cmd, + "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM)"]", + NO_STR + "Max no of multiple paths for ECMP support\n" + "Number of paths\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_maxpath_set(ospf6, MULTIPATH_NUM); + + return CMD_SUCCESS; +} + +static void ospf6_show(struct vty *vty, struct ospf6 *o, json_object *json, + bool use_json) +{ + struct listnode *n; + struct ospf6_area *oa; + char router_id[16], duration[32]; + struct timeval now, running, result; + char buf[32], rbuf[32]; + json_object *json_areas = NULL; + const char *adjacency; + + if (use_json) { + json_areas = json_object_new_object(); + + /* process id, router id */ + inet_ntop(AF_INET, &o->router_id, router_id, sizeof(router_id)); + json_object_string_add(json, "routerId", router_id); + + /* running time */ + monotime(&now); + timersub(&now, &o->starttime, &running); + timerstring(&running, duration, sizeof(duration)); + json_object_string_add(json, "running", duration); + + /* Redistribute configuration */ + /* XXX */ + json_object_int_add(json, "lsaMinimumArrivalMsecs", + o->lsa_minarrival); + + /* Show SPF parameters */ + json_object_int_add(json, "spfScheduleDelayMsecs", + o->spf_delay); + json_object_int_add(json, "holdTimeMinMsecs", o->spf_holdtime); + json_object_int_add(json, "holdTimeMaxMsecs", + o->spf_max_holdtime); + json_object_int_add(json, "holdTimeMultiplier", + o->spf_hold_multiplier); + + json_object_int_add(json, "maximumPaths", o->max_multipath); + json_object_int_add(json, "preference", + o->distance_all + ? o->distance_all + : ZEBRA_OSPF6_DISTANCE_DEFAULT); + + if (o->ts_spf.tv_sec || o->ts_spf.tv_usec) { + timersub(&now, &o->ts_spf, &result); + timerstring(&result, buf, sizeof(buf)); + ospf6_spf_reason_string(o->last_spf_reason, rbuf, + sizeof(rbuf)); + json_object_boolean_true_add(json, "spfHasRun"); + json_object_string_add(json, "spfLastExecutedMsecs", + buf); + json_object_string_add(json, "spfLastExecutedReason", + rbuf); + + json_object_int_add( + json, "spfLastDurationSecs", + (long long)o->ts_spf_duration.tv_sec); + + json_object_int_add( + json, "spfLastDurationMsecs", + (long long)o->ts_spf_duration.tv_usec); + } else + json_object_boolean_false_add(json, "spfHasRun"); + + if (event_is_scheduled(o->t_spf_calc)) { + long time_store; + + json_object_boolean_true_add(json, "spfTimerActive"); + time_store = + monotime_until(&o->t_spf_calc->u.sands, NULL) + / 1000LL; + json_object_int_add(json, "spfTimerDueInMsecs", + time_store); + } else + json_object_boolean_false_add(json, "spfTimerActive"); + + json_object_boolean_add(json, "routerIsStubRouter", + CHECK_FLAG(o->flag, OSPF6_STUB_ROUTER)); + + /* LSAs */ + json_object_int_add(json, "numberOfAsScopedLsa", + o->lsdb->count); + /* Areas */ + json_object_int_add(json, "numberOfAreaInRouter", + listcount(o->area_list)); + + json_object_int_add(json, "AuthTrailerHigherSeqNo", + o->seqnum_h); + json_object_int_add(json, "AuthTrailerLowerSeqNo", o->seqnum_l); + + if (CHECK_FLAG(o->config_flags, OSPF6_LOG_ADJACENCY_CHANGES)) { + if (CHECK_FLAG(o->config_flags, + OSPF6_LOG_ADJACENCY_DETAIL)) + adjacency = "LoggedAll"; + else + adjacency = "Logged"; + } else + adjacency = "NotLogged"; + json_object_string_add(json, "adjacencyChanges", adjacency); + + for (ALL_LIST_ELEMENTS_RO(o->area_list, n, oa)) + ospf6_area_show(vty, oa, json_areas, use_json); + + json_object_object_add(json, "areas", json_areas); + + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + + } else { + /* process id, router id */ + inet_ntop(AF_INET, &o->router_id, router_id, sizeof(router_id)); + vty_out(vty, " OSPFv3 Routing Process (0) with Router-ID %s\n", + router_id); + + /* running time */ + monotime(&now); + timersub(&now, &o->starttime, &running); + timerstring(&running, duration, sizeof(duration)); + vty_out(vty, " Running %s\n", duration); + + /* Redistribute configuration */ + /* XXX */ + vty_out(vty, " LSA minimum arrival %d msecs\n", + o->lsa_minarrival); + + vty_out(vty, " Maximum-paths %u\n", o->max_multipath); + vty_out(vty, " Administrative distance %u\n", + o->distance_all ? o->distance_all + : ZEBRA_OSPF6_DISTANCE_DEFAULT); + + /* Show SPF parameters */ + vty_out(vty, + " Initial SPF scheduling delay %d millisec(s)\n" + " Minimum hold time between consecutive SPFs %d millsecond(s)\n" + " Maximum hold time between consecutive SPFs %d millsecond(s)\n" + " Hold time multiplier is currently %d\n", + o->spf_delay, o->spf_holdtime, o->spf_max_holdtime, + o->spf_hold_multiplier); + + + vty_out(vty, " SPF algorithm "); + if (o->ts_spf.tv_sec || o->ts_spf.tv_usec) { + timersub(&now, &o->ts_spf, &result); + timerstring(&result, buf, sizeof(buf)); + ospf6_spf_reason_string(o->last_spf_reason, rbuf, + sizeof(rbuf)); + vty_out(vty, "last executed %s ago, reason %s\n", buf, + rbuf); + vty_out(vty, " Last SPF duration %lld sec %lld usec\n", + (long long)o->ts_spf_duration.tv_sec, + (long long)o->ts_spf_duration.tv_usec); + } else + vty_out(vty, "has not been run\n"); + + threadtimer_string(now, o->t_spf_calc, buf, sizeof(buf)); + vty_out(vty, " SPF timer %s%s\n", + (event_is_scheduled(o->t_spf_calc) ? "due in " : "is "), + buf); + + if (CHECK_FLAG(o->flag, OSPF6_STUB_ROUTER)) + vty_out(vty, " Router Is Stub Router\n"); + + /* LSAs */ + vty_out(vty, " Number of AS scoped LSAs is %u\n", + o->lsdb->count); + + /* Areas */ + vty_out(vty, " Number of areas in this router is %u\n", + listcount(o->area_list)); + + vty_out(vty, " Authentication Sequence number info\n"); + vty_out(vty, " Higher sequence no %u, Lower sequence no %u\n", + o->seqnum_h, o->seqnum_l); + + if (CHECK_FLAG(o->config_flags, OSPF6_LOG_ADJACENCY_CHANGES)) { + if (CHECK_FLAG(o->config_flags, + OSPF6_LOG_ADJACENCY_DETAIL)) + vty_out(vty, + " All adjacency changes are logged\n"); + else + vty_out(vty, " Adjacency changes are logged\n"); + } + + + vty_out(vty, "\n"); + + for (ALL_LIST_ELEMENTS_RO(o->area_list, n, oa)) + ospf6_area_show(vty, oa, json_areas, use_json); + } +} + +DEFUN(show_ipv6_ospf6_vrfs, show_ipv6_ospf6_vrfs_cmd, + "show ipv6 ospf6 vrfs [json]", + SHOW_STR IP6_STR OSPF6_STR "Show OSPF6 VRFs \n" JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL; + json_object *json_vrfs = NULL; + struct ospf6 *ospf6 = NULL; + struct listnode *node = NULL; + int count = 0; + char buf[PREFIX_STRLEN]; + static const char header[] = + "Name Id RouterId "; + + if (uj) { + json = json_object_new_object(); + json_vrfs = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + json_object *json_vrf = NULL; + const char *name = NULL; + int64_t vrf_id_ui = 0; + struct in_addr router_id; + + router_id.s_addr = ospf6->router_id; + count++; + + if (!uj && count == 1) + vty_out(vty, "%s\n", header); + if (uj) + json_vrf = json_object_new_object(); + + if (ospf6->vrf_id == VRF_DEFAULT) + name = VRF_DEFAULT_NAME; + else + name = ospf6->name; + + vrf_id_ui = (ospf6->vrf_id == VRF_UNKNOWN) + ? -1 + : (int64_t)ospf6->vrf_id; + + if (uj) { + json_object_int_add(json_vrf, "vrfId", vrf_id_ui); + json_object_string_addf(json_vrf, "routerId", "%pI4", + &router_id); + json_object_object_add(json_vrfs, name, json_vrf); + + } else { + vty_out(vty, "%-25s %-5d %-16s \n", name, + ospf6->vrf_id, + inet_ntop(AF_INET, &router_id, buf, + sizeof(buf))); + } + } + + if (uj) { + json_object_object_add(json, "vrfs", json_vrfs); + json_object_int_add(json, "totalVrfs", count); + + vty_json(vty, json); + } else { + if (count) + vty_out(vty, "\nTotal number of OSPF VRFs: %d\n", + count); + } + + return CMD_SUCCESS; +} + +/* show top level structures */ +DEFUN(show_ipv6_ospf6, show_ipv6_ospf6_cmd, + "show ipv6 ospf6 [vrf ] [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR "All VRFs\n" JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + bool uj = use_json(argc, argv); + json_object *json = NULL; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + if (uj) + json = json_object_new_object(); + ospf6_show(vty, ospf6, json, uj); + + if (!all_vrf) + break; + } + } + + if (uj) + json_object_free(json); + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_route, show_ipv6_ospf6_route_cmd, + "show ipv6 ospf6 [vrf ] route [] [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" ROUTE_STR + "Display Intra-Area routes\n" + "Display Inter-Area routes\n" + "Display Type-1 External routes\n" + "Display Type-2 External routes\n" + "Specify IPv6 address\n" + "Specify IPv6 prefix\n" + "Detailed information\n" + "Summary of route table\n" JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_arg_start = 4; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_arg_start += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_route_table_show(vty, idx_arg_start, argc, argv, + ospf6->route_table, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_route_match, show_ipv6_ospf6_route_match_cmd, + "show ipv6 ospf6 [vrf ] route X:X::X:X/M [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" ROUTE_STR + "Specify IPv6 prefix\n" + "Display routes which match the specified route\n" + "Display routes longer than the specified route\n" JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_start_arg = 4; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_start_arg += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_route_table_show(vty, idx_start_arg, argc, argv, + ospf6->route_table, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_route_match_detail, + show_ipv6_ospf6_route_match_detail_cmd, + "show ipv6 ospf6 [vrf ] route X:X::X:X/M match detail [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" ROUTE_STR + "Specify IPv6 prefix\n" + "Display routes which match the specified route\n" + "Detailed information\n" JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_start_arg = 4; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_start_arg += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_route_table_show(vty, idx_start_arg, argc, argv, + ospf6->route_table, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_route_type_detail, show_ipv6_ospf6_route_type_detail_cmd, + "show ipv6 ospf6 [vrf ] route detail [json]", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" ROUTE_STR + "Display Intra-Area routes\n" + "Display Inter-Area routes\n" + "Display Type-1 External routes\n" + "Display Type-2 External routes\n" + "Detailed information\n" JSON_STR) +{ + struct ospf6 *ospf6; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_start_arg = 4; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_start_arg += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_route_table_show(vty, idx_start_arg, argc, argv, + ospf6->route_table, uj); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +bool ospf6_is_valid_summary_addr(struct vty *vty, struct prefix *p) +{ + /* Default prefix validation*/ + if (is_default_prefix(p)) { + vty_out(vty, + "Default address should not be configured as summary address.\n"); + return false; + } + + /* Host route should not be configured as summary address */ + if (p->prefixlen == IPV6_MAX_BITLEN) { + vty_out(vty, "Host route should not be configured as summary address.\n"); + return false; + } + + return true; +} + +/* External Route Aggregation */ +DEFPY (ospf6_external_route_aggregation, + ospf6_external_route_aggregation_cmd, + "summary-address X:X::X:X/M$prefix [tag (1-4294967295)] [{metric (0-16777215) | metric-type (1-2)$mtype}]", + "External summary address\n" + "Specify IPv6 prefix\n" + "Router tag \n" + "Router tag value\n" + "Metric \n" + "Advertised metric for this route\n" + "OSPFv3 exterior metric type for summarised routes\n" + "Set OSPFv3 External Type 1/2 metrics\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + struct prefix p; + int ret = CMD_SUCCESS; + + p.family = AF_INET6; + ret = str2prefix(prefix_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!ospf6_is_valid_summary_addr(vty, &p)) + return CMD_WARNING_CONFIG_FAILED; + + if (!tag_str) + tag = 0; + + if (!metric_str) + metric = -1; + + if (!mtype_str) + mtype = DEFAULT_METRIC_TYPE; + + ret = ospf6_external_aggr_config_set(ospf6, &p, tag, metric, mtype); + if (ret == OSPF6_FAILURE) { + vty_out(vty, "Invalid configuration!!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFPY(no_ospf6_external_route_aggregation, + no_ospf6_external_route_aggregation_cmd, + "no summary-address X:X::X:X/M$prefix [tag (1-4294967295)] [{metric (0-16777215) | metric-type (1-2)}]", + NO_STR + "External summary address\n" + "Specify IPv6 prefix\n" + "Router tag\n" + "Router tag value\n" + "Metric \n" + "Advertised metric for this route\n" + "OSPFv3 exterior metric type for summarised routes\n" + "Set OSPFv3 External Type 1/2 metrics\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + struct prefix p; + int ret = CMD_SUCCESS; + + ret = str2prefix(prefix_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!ospf6_is_valid_summary_addr(vty, &p)) + return CMD_WARNING_CONFIG_FAILED; + + ret = ospf6_external_aggr_config_unset(ospf6, &p); + if (ret == OSPF6_INVALID) + vty_out(vty, "Invalid configuration!!\n"); + + return CMD_SUCCESS; +} + +DEFPY (ospf6_external_route_aggregation_no_advertise, + ospf6_external_route_aggregation_no_advertise_cmd, + "summary-address X:X::X:X/M$prefix no-advertise", + "External summary address\n" + "Specify IPv6 prefix\n" + "Don't advertise summary route \n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + struct prefix p; + int ret = CMD_SUCCESS; + + ret = str2prefix(prefix_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!ospf6_is_valid_summary_addr(vty, &p)) + return CMD_WARNING_CONFIG_FAILED; + + ret = ospf6_asbr_external_rt_no_advertise(ospf6, &p); + if (ret == OSPF6_INVALID) + vty_out(vty, "!!Invalid configuration\n"); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf6_external_route_aggregation_no_advertise, + no_ospf6_external_route_aggregation_no_advertise_cmd, + "no summary-address X:X::X:X/M$prefix no-advertise", + NO_STR + "External summary address\n" + "Specify IPv6 prefix\n" + "Adverise summary route to the AS \n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + struct prefix p; + int ret = CMD_SUCCESS; + + ret = str2prefix(prefix_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!ospf6_is_valid_summary_addr(vty, &p)) + return CMD_WARNING_CONFIG_FAILED; + + ret = ospf6_asbr_external_rt_advertise(ospf6, &p); + if (ret == OSPF6_INVALID) + vty_out(vty, "!!Invalid configuration\n"); + + return CMD_SUCCESS; +} + +DEFPY (ospf6_route_aggregation_timer, + ospf6_route_aggregation_timer_cmd, + "aggregation timer (5-1800)", + "External route aggregation\n" + "Delay timer (in seconds)\n" + "Timer interval(in seconds)\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_external_aggr_delay_timer_set(ospf6, timer); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf6_route_aggregation_timer, + no_ospf6_route_aggregation_timer_cmd, + "no aggregation timer [5-1800]", + NO_STR + "External route aggregation\n" + "Delay timer\n" + "Timer interval(in seconds)\n") +{ + VTY_DECLVAR_CONTEXT(ospf6, ospf6); + + ospf6_external_aggr_delay_timer_set(ospf6, + OSPF6_EXTL_AGGR_DEFAULT_DELAY); + return CMD_SUCCESS; +} + +static int +ospf6_print_vty_external_routes_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct ospf6_route *rt = bucket->data; + struct vty *vty = (struct vty *)arg; + static unsigned int count; + + vty_out(vty, "%pFX ", &rt->prefix); + + count++; + + if (count%5 == 0) + vty_out(vty, "\n"); + + if (OSPF6_EXTERNAL_RT_COUNT(rt->aggr_route) == count) + count = 0; + + return HASHWALK_CONTINUE; +} + +static int +ospf6_print_json_external_routes_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct ospf6_route *rt = bucket->data; + struct json_object *json = (struct json_object *)arg; + char buf[PREFIX2STR_BUFFER]; + char exnalbuf[20]; + static unsigned int count; + + prefix2str(&rt->prefix, buf, sizeof(buf)); + + snprintf(exnalbuf, sizeof(exnalbuf), "Exnl Addr-%d", count); + + json_object_string_add(json, exnalbuf, buf); + + count++; + + if (OSPF6_EXTERNAL_RT_COUNT(rt->aggr_route) == count) + count = 0; + + return HASHWALK_CONTINUE; +} + +static void +ospf6_show_vrf_name(struct vty *vty, struct ospf6 *ospf6, + json_object *json) +{ + if (json) { + if (ospf6->vrf_id == VRF_DEFAULT) + json_object_string_add(json, "vrfName", + "default"); + else + json_object_string_add(json, "vrfName", + ospf6->name); + json_object_int_add(json, "vrfId", ospf6->vrf_id); + } else { + if (ospf6->vrf_id == VRF_DEFAULT) + vty_out(vty, "VRF Name: %s\n", "default"); + else if (ospf6->name) + vty_out(vty, "VRF Name: %s\n", ospf6->name); + } +} + +static int +ospf6_show_summary_address(struct vty *vty, struct ospf6 *ospf6, + json_object *json, + bool uj, const char *detail) +{ + struct route_node *rn; + static const char header[] = "Summary-address Metric-type Metric Tag External_Rt_count\n"; + json_object *json_vrf = NULL; + + if (!uj) { + ospf6_show_vrf_name(vty, ospf6, json_vrf); + vty_out(vty, "aggregation delay interval: %u(in seconds)\n\n", + ospf6->aggr_delay_interval); + vty_out(vty, "%s\n", header); + } else { + json_vrf = json_object_new_object(); + + ospf6_show_vrf_name(vty, ospf6, json_vrf); + + json_object_int_add(json_vrf, "aggregationDelayInterval", + ospf6->aggr_delay_interval); + } + + + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + struct ospf6_external_aggr_rt *aggr = rn->info; + json_object *json_aggr = NULL; + char buf[PREFIX2STR_BUFFER]; + + prefix2str(&aggr->p, buf, sizeof(buf)); + + if (uj) { + + json_aggr = json_object_new_object(); + + json_object_object_add(json_vrf, + buf, + json_aggr); + + json_object_string_add(json_aggr, "summaryAddress", + buf); + + json_object_string_add( + json_aggr, "metricType", + (aggr->mtype == DEFAULT_METRIC_TYPE) ? "E2" + : "E1"); + + json_object_int_add(json_aggr, "Metric", + (aggr->metric != -1) + ? aggr->metric + : DEFAULT_DEFAULT_METRIC); + + json_object_int_add(json_aggr, "Tag", + aggr->tag); + + json_object_int_add(json_aggr, "externalRouteCount", + OSPF6_EXTERNAL_RT_COUNT(aggr)); + + if (OSPF6_EXTERNAL_RT_COUNT(aggr) && detail) { + json_object_int_add(json_aggr, "ID", + aggr->id); + json_object_int_add(json_aggr, "Flags", + aggr->aggrflags); + hash_walk(aggr->match_extnl_hash, + ospf6_print_json_external_routes_walkcb, + json_aggr); + } + + } else { + vty_out(vty, "%-22s", buf); + + (aggr->mtype == DEFAULT_METRIC_TYPE) + ? vty_out(vty, "%-16s", "E2") + : vty_out(vty, "%-16s", "E1"); + vty_out(vty, "%-11d", (aggr->metric != -1) + ? aggr->metric + : DEFAULT_DEFAULT_METRIC); + + vty_out(vty, "%-12u", aggr->tag); + + vty_out(vty, "%-5ld\n", + OSPF6_EXTERNAL_RT_COUNT(aggr)); + + if (OSPF6_EXTERNAL_RT_COUNT(aggr) && detail) { + vty_out(vty, + "Matched External routes:\n"); + hash_walk(aggr->match_extnl_hash, + ospf6_print_vty_external_routes_walkcb, + vty); + vty_out(vty, "\n"); + } + + vty_out(vty, "\n"); + } + } + + if (uj) + json_object_object_add(json, ospf6->name, + json_vrf); + + return CMD_SUCCESS; +} + +DEFPY (show_ipv6_ospf6_external_aggregator, + show_ipv6_ospf6_external_aggregator_cmd, + "show ipv6 ospf6 [vrf ] summary-address [detail$detail] [json]", + SHOW_STR + IP6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + "Show external summary addresses\n" + "detailed information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct ospf6 *ospf6 = NULL; + json_object *json = NULL; + const char *vrf_name = NULL; + struct listnode *node; + bool all_vrf = false; + int idx_vrf = 0; + + if (uj) + json = json_object_new_object(); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + + ospf6_show_summary_address(vty, ospf6, json, uj, + detail); + + if (!all_vrf) + break; + } + } + + if (uj) { + vty_json(vty, json); + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static void ospf6_stub_router_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + if (CHECK_FLAG(ospf6->flag, OSPF6_STUB_ROUTER)) + vty_out(vty, " stub-router administrative\n"); + return; +} + +static int ospf6_distance_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + struct route_node *rn; + struct ospf6_distance *odistance; + + if (ospf6->distance_all) + vty_out(vty, " distance %u\n", ospf6->distance_all); + + if (ospf6->distance_intra || ospf6->distance_inter + || ospf6->distance_external) { + vty_out(vty, " distance ospf6"); + + if (ospf6->distance_intra) + vty_out(vty, " intra-area %u", ospf6->distance_intra); + if (ospf6->distance_inter) + vty_out(vty, " inter-area %u", ospf6->distance_inter); + if (ospf6->distance_external) + vty_out(vty, " external %u", ospf6->distance_external); + + vty_out(vty, "\n"); + } + + for (rn = route_top(ospf6->distance_table); rn; rn = route_next(rn)) + if ((odistance = rn->info) != NULL) + vty_out(vty, " distance %u %pFX %s\n", + odistance->distance, &rn->p, + odistance->access_list ? odistance->access_list + : ""); + return 0; +} + +static int ospf6_asbr_summary_config_write(struct vty *vty, struct ospf6 *ospf6) +{ + struct route_node *rn; + struct ospf6_external_aggr_rt *aggr; + char buf[PREFIX2STR_BUFFER]; + + if (ospf6->aggr_delay_interval != OSPF6_EXTL_AGGR_DEFAULT_DELAY) + vty_out(vty, " aggregation timer %u\n", + ospf6->aggr_delay_interval); + + /* print 'summary-address A:B::C:D/M' */ + for (rn = route_top(ospf6->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + aggr = rn->info; + + prefix2str(&aggr->p, buf, sizeof(buf)); + vty_out(vty, " summary-address %s", buf); + if (aggr->tag) + vty_out(vty, " tag %u", aggr->tag); + + if (aggr->metric != -1) + vty_out(vty, " metric %d", aggr->metric); + + if (aggr->mtype != DEFAULT_METRIC_TYPE) + vty_out(vty, " metric-type %d", aggr->mtype); + + if (CHECK_FLAG(aggr->aggrflags, + OSPF6_EXTERNAL_AGGRT_NO_ADVERTISE)) + vty_out(vty, " no-advertise"); + + vty_out(vty, "\n"); + } + + return 0; +} + +/* OSPF configuration write function. */ +static int config_write_ospf6(struct vty *vty) +{ + struct ospf6 *ospf6; + struct listnode *node, *nnode; + + /* OSPFv3 configuration. */ + if (om6 == NULL) + return CMD_SUCCESS; + + for (ALL_LIST_ELEMENTS(om6->ospf6, node, nnode, ospf6)) { + if (ospf6->name && strcmp(ospf6->name, VRF_DEFAULT_NAME)) + vty_out(vty, "router ospf6 vrf %s\n", ospf6->name); + else + vty_out(vty, "router ospf6\n"); + + if (ospf6->router_id_static != 0) + vty_out(vty, " ospf6 router-id %pI4\n", + &ospf6->router_id_static); + + if (CHECK_FLAG(ospf6->config_flags, + OSPF6_SEND_EXTRA_DATA_TO_ZEBRA)) + vty_out(vty, " ospf6 send-extra-data zebra\n"); + + /* log-adjacency-changes flag print. */ + if (CHECK_FLAG(ospf6->config_flags, + OSPF6_LOG_ADJACENCY_CHANGES)) { + if (CHECK_FLAG(ospf6->config_flags, + OSPF6_LOG_ADJACENCY_DETAIL)) + vty_out(vty, " log-adjacency-changes detail\n"); + else if (!SAVE_OSPF6_LOG_ADJACENCY_CHANGES) + vty_out(vty, " log-adjacency-changes\n"); + } else if (SAVE_OSPF6_LOG_ADJACENCY_CHANGES) { + vty_out(vty, " no log-adjacency-changes\n"); + } + + if (ospf6->ref_bandwidth != OSPF6_REFERENCE_BANDWIDTH) + vty_out(vty, " auto-cost reference-bandwidth %d\n", + ospf6->ref_bandwidth); + + if (ospf6->write_oi_count + != OSPF6_WRITE_INTERFACE_COUNT_DEFAULT) + vty_out(vty, " write-multiplier %d\n", + ospf6->write_oi_count); + + /* LSA timers print. */ + if (ospf6->lsa_minarrival != OSPF_MIN_LS_ARRIVAL) + vty_out(vty, " timers lsa min-arrival %d\n", + ospf6->lsa_minarrival); + + /* ECMP max path config */ + if (ospf6->max_multipath != MULTIPATH_NUM) + vty_out(vty, " maximum-paths %d\n", + ospf6->max_multipath); + + ospf6_stub_router_config_write(vty, ospf6); + ospf6_redistribute_config_write(vty, ospf6); + ospf6_area_config_write(vty, ospf6); + ospf6_spf_config_write(vty, ospf6); + ospf6_distance_config_write(vty, ospf6); + ospf6_distribute_config_write(vty, ospf6); + ospf6_asbr_summary_config_write(vty, ospf6); + config_write_ospf6_gr(vty, ospf6); + config_write_ospf6_gr_helper(vty, ospf6); + + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); + } + return 0; +} + +static int config_write_ospf6(struct vty *vty); +/* OSPF6 node structure. */ +static struct cmd_node ospf6_node = { + .name = "ospf6", + .node = OSPF6_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-ospf6)# ", + .config_write = config_write_ospf6, +}; + +void install_element_ospf6_clear_process(void) +{ + install_element(ENABLE_NODE, &clear_router_ospf6_cmd); +} + +/* Install ospf related commands. */ +void ospf6_top_init(void) +{ + /* Install ospf6 top node. */ + install_node(&ospf6_node); + + install_element(VIEW_NODE, &show_ipv6_ospf6_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_vrfs_cmd); + install_element(CONFIG_NODE, &router_ospf6_cmd); + install_element(CONFIG_NODE, &no_router_ospf6_cmd); + + install_element(VIEW_NODE, &show_ipv6_ospf6_route_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_route_match_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_route_match_detail_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_route_type_detail_cmd); + + install_default(OSPF6_NODE); + install_element(OSPF6_NODE, &ospf6_router_id_cmd); + install_element(OSPF6_NODE, &no_ospf6_router_id_cmd); + install_element(OSPF6_NODE, &ospf6_log_adjacency_changes_cmd); + install_element(OSPF6_NODE, &ospf6_log_adjacency_changes_detail_cmd); + install_element(OSPF6_NODE, &no_ospf6_log_adjacency_changes_cmd); + install_element(OSPF6_NODE, &no_ospf6_log_adjacency_changes_detail_cmd); + install_element(OSPF6_NODE, &ospf6_send_extra_data_cmd); + + /* LSA timers commands */ + install_element(OSPF6_NODE, &ospf6_timers_lsa_cmd); + install_element(OSPF6_NODE, &no_ospf6_timers_lsa_cmd); + + install_element(OSPF6_NODE, &ospf6_stub_router_admin_cmd); + install_element(OSPF6_NODE, &no_ospf6_stub_router_admin_cmd); + + /* maximum-paths command */ + install_element(OSPF6_NODE, &ospf6_max_multipath_cmd); + install_element(OSPF6_NODE, &no_ospf6_max_multipath_cmd); + + /* ASBR Summarisation */ + install_element(OSPF6_NODE, &ospf6_external_route_aggregation_cmd); + install_element(OSPF6_NODE, &no_ospf6_external_route_aggregation_cmd); + install_element(OSPF6_NODE, + &ospf6_external_route_aggregation_no_advertise_cmd); + install_element(OSPF6_NODE, + &no_ospf6_external_route_aggregation_no_advertise_cmd); + install_element(OSPF6_NODE, &ospf6_route_aggregation_timer_cmd); + install_element(OSPF6_NODE, &no_ospf6_route_aggregation_timer_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_external_aggregator_cmd); + + install_element(OSPF6_NODE, &ospf6_distance_cmd); + install_element(OSPF6_NODE, &no_ospf6_distance_cmd); + install_element(OSPF6_NODE, &ospf6_distance_ospf6_cmd); + install_element(OSPF6_NODE, &no_ospf6_distance_ospf6_cmd); +} diff --git a/ospf6d/ospf6_top.h b/ospf6d/ospf6_top.h new file mode 100644 index 0000000..8288413 --- /dev/null +++ b/ospf6d/ospf6_top.h @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_TOP_H +#define OSPF6_TOP_H + +#include "qobj.h" +#include "routemap.h" +struct ospf6_master { + + /* OSPFv3 instance. */ + struct list *ospf6; + /* OSPFv3 thread master. */ + struct event_loop *master; +}; + +/* ospf6->config_flags */ +enum { OSPF6_LOG_ADJACENCY_CHANGES = (1 << 0), + OSPF6_LOG_ADJACENCY_DETAIL = (1 << 1), + OSPF6_SEND_EXTRA_DATA_TO_ZEBRA = (1 << 2), +}; + +/* For processing route-map change update in the callback */ +#define OSPF6_IS_RMAP_CHANGED 0x01 +struct ospf6_redist { + uint8_t instance; + + uint8_t flag; + /* Redistribute metric info. */ + struct { + int type; /* External metric type (E1 or E2). */ + int value; /* Value for static metric (24-bit). + * -1 means metric value is not set. + */ + } dmetric; + + /* For redistribute route map. */ + struct { + char *name; + struct route_map *map; + } route_map; +#define ROUTEMAP_NAME(R) (R->route_map.name) +#define ROUTEMAP(R) (R->route_map.map) +}; + +struct ospf6_gr_info { + bool restart_support; + bool restart_in_progress; + bool prepare_in_progress; + bool finishing_restart; + uint32_t grace_period; + int reason; + char *exit_reason; + struct event *t_grace_period; +}; + +struct ospf6_gr_helper { + /* Graceful restart Helper supported configs*/ + /* Supported grace interval*/ + uint32_t supported_grace_time; + + /* Helper support + * Supported : True + * Not Supported : False. + */ + bool is_helper_supported; + + /* Support for strict LSA check. + * if it is set,Helper aborted + * upon a TOPO change. + */ + bool strict_lsa_check; + + /* Support as HELPER only for + * planned restarts. + */ + bool only_planned_restart; + + /* This list contains the advertisement + * routerids for which Helper support is + * enabled. + */ + struct hash *enable_rtr_list; + + /* HELPER for number of active + * RESTARTERs. + */ + int active_restarter_cnt; + + /* last HELPER exit reason */ + uint32_t last_exit_reason; +}; + +/* OSPFv3 top level data structure */ +struct ospf6 { + /* The relevant vrf_id */ + vrf_id_t vrf_id; + + char *name; /* VRF name */ + + /* my router id */ + in_addr_t router_id; + + /* static router id */ + in_addr_t router_id_static; + + in_addr_t router_id_zebra; + + /* start time */ + struct timeval starttime; + + /* list of areas */ + struct list *area_list; + struct ospf6_area *backbone; + + /* AS scope link state database */ + struct ospf6_lsdb *lsdb; + struct ospf6_lsdb *lsdb_self; + + struct ospf6_route_table *route_table; + struct ospf6_route_table *brouter_table; + + struct ospf6_route_table *external_table; +#define OSPF6_EXT_INIT_LS_ID 1 + uint32_t external_id; + + /* OSPF6 redistribute configuration */ + struct list *redist[ZEBRA_ROUTE_MAX + 1]; + + /* NSSA default-information-originate */ + struct { + /* # of NSSA areas requesting default information */ + uint16_t refcnt; + + /* + * Whether a default route known through non-OSPF protocol is + * present in the RIB. + */ + bool status; + } nssa_default_import_check; + + uint8_t flag; +#define OSPF6_FLAG_ABR 0x04 +#define OSPF6_FLAG_ASBR 0x08 + + int redistribute; /* Num of redistributed protocols. */ + + /* Configuration bitmask, refer to enum above */ + uint8_t config_flags; + int default_originate; /* Default information originate. */ +#define DEFAULT_ORIGINATE_NONE 0 +#define DEFAULT_ORIGINATE_ZEBRA 1 +#define DEFAULT_ORIGINATE_ALWAYS 2 + /* LSA timer parameters */ + unsigned int lsa_minarrival; /* LSA minimum arrival in milliseconds. */ + + /* SPF parameters */ + unsigned int spf_delay; /* SPF delay time. */ + unsigned int spf_holdtime; /* SPF hold time. */ + unsigned int spf_max_holdtime; /* SPF maximum-holdtime */ + unsigned int + spf_hold_multiplier; /* Adaptive multiplier for hold time */ + unsigned int spf_reason; /* reason bits while scheduling SPF */ + + struct timeval ts_spf; /* SPF calculation time stamp. */ + struct timeval ts_spf_duration; /* Execution time of last SPF */ + unsigned int last_spf_reason; /* Last SPF reason */ + + int fd; + /* Threads */ + struct event *t_spf_calc; /* SPF calculation timer. */ + struct event *t_ase_calc; /* ASE calculation timer. */ + struct event *maxage_remover; + struct event *t_distribute_update; /* Distirbute update timer. */ + struct event *t_ospf6_receive; /* OSPF6 receive timer */ + struct event *t_external_aggr; /* OSPF6 aggregation timer */ +#define OSPF6_WRITE_INTERFACE_COUNT_DEFAULT 20 + struct event *t_write; + + int write_oi_count; /* Num of packets sent per thread invocation */ + uint32_t ref_bandwidth; + + /* Distance parameters */ + uint8_t distance_all; + uint8_t distance_intra; + uint8_t distance_inter; + uint8_t distance_external; + + struct route_table *distance_table; + + /* Used during ospf instance going down send LSDB + * update to neighbors immediatly */ + uint8_t inst_shutdown; + + /* Max number of multiple paths + * to support ECMP. + */ + uint16_t max_multipath; + + /* OSPF Graceful Restart info (restarting mode) */ + struct ospf6_gr_info gr_info; + + /*ospf6 Graceful restart helper info */ + struct ospf6_gr_helper ospf6_helper_cfg; + + /* Count of NSSA areas */ + uint8_t anyNSSA; + struct event *t_abr_task; /* ABR task timer. */ + struct list *oi_write_q; + + uint32_t redist_count; + + /* Action for aggregation of external LSAs */ + int aggr_action; + + uint32_t seqnum_l; /* lower order Sequence Number */ + uint32_t seqnum_h; /* higher order Sequence Number */ +#define OSPF6_EXTL_AGGR_DEFAULT_DELAY 5 + /* For ASBR summary delay timer */ + uint16_t aggr_delay_interval; + /* Table of configured Aggregate addresses */ + struct route_table *rt_aggr_tbl; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(ospf6); + +#define OSPF6_DISABLED 0x01 +#define OSPF6_STUB_ROUTER 0x02 + +/* global pointer for OSPF top data structure */ +extern struct ospf6 *ospf6; +extern struct ospf6_master *om6; + +/* prototypes */ +extern void ospf6_master_init(struct event_loop *master); +extern void ospf6_master_delete(void); + +extern void install_element_ospf6_clear_process(void); +extern void ospf6_top_init(void); +extern void ospf6_delete(struct ospf6 *o); +extern bool ospf6_router_id_update(struct ospf6 *ospf6, bool init); +void ospf6_restart_spf(struct ospf6 *ospf6); + +extern void ospf6_maxage_remove(struct ospf6 *o); +extern struct ospf6 *ospf6_instance_create(const char *name); +void ospf6_vrf_link(struct ospf6 *ospf6, struct vrf *vrf); +void ospf6_vrf_unlink(struct ospf6 *ospf6, struct vrf *vrf); +struct ospf6 *ospf6_lookup_by_vrf_id(vrf_id_t vrf_id); +struct ospf6 *ospf6_lookup_by_vrf_name(const char *name); +const char *ospf6_vrf_id_to_name(vrf_id_t vrf_id); +void ospf6_vrf_init(void); +bool ospf6_is_valid_summary_addr(struct vty *vty, struct prefix *p); +#endif /* OSPF6_TOP_H */ diff --git a/ospf6d/ospf6_zebra.c b/ospf6d/ospf6_zebra.c new file mode 100644 index 0000000..911f356 --- /dev/null +++ b/ospf6d/ospf6_zebra.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "log.h" +#include "vty.h" +#include "command.h" +#include "prefix.h" +#include "stream.h" +#include "zclient.h" +#include "memory.h" +#include "route_opaque.h" +#include "lib/bfd.h" +#include "lib_errors.h" + +#include "ospf6_proto.h" +#include "ospf6_top.h" +#include "ospf6_interface.h" +#include "ospf6_route.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_asbr.h" +#include "ospf6_nssa.h" +#include "ospf6_zebra.h" +#include "ospf6d.h" +#include "ospf6_area.h" +#include "ospf6_gr.h" +#include "lib/json.h" + +DEFINE_MTYPE_STATIC(OSPF6D, OSPF6_DISTANCE, "OSPF6 distance"); + +unsigned char conf_debug_ospf6_zebra = 0; + +/* information about zebra. */ +struct zclient *zclient = NULL; + +void ospf6_zebra_vrf_register(struct ospf6 *ospf6) +{ + if (!zclient || zclient->sock < 0 || !ospf6) + return; + + if (ospf6->vrf_id != VRF_UNKNOWN) { + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) { + zlog_debug("%s: Register VRF %s id %u", __func__, + ospf6_vrf_id_to_name(ospf6->vrf_id), + ospf6->vrf_id); + } + zclient_send_reg_requests(zclient, ospf6->vrf_id); + } +} + +void ospf6_zebra_vrf_deregister(struct ospf6 *ospf6) +{ + if (!zclient || zclient->sock < 0 || !ospf6) + return; + + if (ospf6->vrf_id != VRF_DEFAULT && ospf6->vrf_id != VRF_UNKNOWN) { + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) { + zlog_debug("%s: De-Register VRF %s id %u to Zebra.", + __func__, + ospf6_vrf_id_to_name(ospf6->vrf_id), + ospf6->vrf_id); + } + /* Deregister for router-id, interfaces, + * redistributed routes. */ + zclient_send_dereg_requests(zclient, ospf6->vrf_id); + } +} + +/* Router-id update message from zebra. */ +static int ospf6_router_id_update_zebra(ZAPI_CALLBACK_ARGS) +{ + struct prefix router_id; + struct ospf6 *o; + + zebra_router_id_update_read(zclient->ibuf, &router_id); + + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra router-id update %pI4 vrf %s id %u", + &router_id.u.prefix4, ospf6_vrf_id_to_name(vrf_id), + vrf_id); + + o = ospf6_lookup_by_vrf_id(vrf_id); + if (o == NULL) + return 0; + + o->router_id_zebra = router_id.u.prefix4.s_addr; + + ospf6_router_id_update(o, false); + + return 0; +} + +/* redistribute function */ +void ospf6_zebra_redistribute(int type, vrf_id_t vrf_id) +{ + if (vrf_bitmap_check(&zclient->redist[AFI_IP6][type], vrf_id)) + return; + vrf_bitmap_set(&zclient->redist[AFI_IP6][type], vrf_id); + + if (zclient->sock > 0) + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, + AFI_IP6, type, 0, vrf_id); +} + +void ospf6_zebra_no_redistribute(int type, vrf_id_t vrf_id) +{ + if (!vrf_bitmap_check(&zclient->redist[AFI_IP6][type], vrf_id)) + return; + vrf_bitmap_unset(&zclient->redist[AFI_IP6][type], vrf_id); + if (zclient->sock > 0) + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP6, type, 0, vrf_id); +} + +void ospf6_zebra_import_default_route(struct ospf6 *ospf6, bool unreg) +{ + struct prefix prefix = {}; + int command; + + if (zclient->sock < 0) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" Not connected to Zebra"); + return; + } + + prefix.family = AF_INET6; + prefix.prefixlen = 0; + + if (unreg) + command = ZEBRA_NEXTHOP_UNREGISTER; + else + command = ZEBRA_NEXTHOP_REGISTER; + + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug("%s: sending cmd %s for %pFX (vrf %u)", __func__, + zserv_command_string(command), &prefix, + ospf6->vrf_id); + + if (zclient_send_rnh(zclient, command, &prefix, SAFI_UNICAST, false, + true, ospf6->vrf_id) + == ZCLIENT_SEND_FAILURE) + flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient_send_rnh() failed", + __func__); +} + +static void ospf6_zebra_import_check_update(struct vrf *vrf, + struct prefix *matched, + struct zapi_route *nhr) +{ + struct ospf6 *ospf6; + + ospf6 = (struct ospf6 *)vrf->info; + if (ospf6 == NULL || !IS_OSPF6_ASBR(ospf6)) + return; + + if (matched->family != AF_INET6 || matched->prefixlen != 0 || + nhr->type == ZEBRA_ROUTE_OSPF6) + return; + + ospf6->nssa_default_import_check.status = !!nhr->nexthop_num; + ospf6_abr_nssa_type_7_defaults(ospf6); +} + +static int ospf6_zebra_if_address_update_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, + zclient->ibuf, vrf_id); + if (c == NULL) + return 0; + + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface address add: %s %5s %pFX", + c->ifp->name, prefix_family_str(c->address), + c->address); + + if (c->address->family == AF_INET6) { + ospf6_interface_state_update(c->ifp); + ospf6_interface_connected_route_update(c->ifp); + } + return 0; +} + +static int ospf6_zebra_if_address_update_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, + zclient->ibuf, vrf_id); + if (c == NULL) + return 0; + + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug("Zebra Interface address delete: %s %5s %pFX", + c->ifp->name, prefix_family_str(c->address), + c->address); + + if (c->address->family == AF_INET6) { + ospf6_interface_connected_route_update(c->ifp); + ospf6_interface_state_update(c->ifp); + } + + connected_free(&c); + + return 0; +} + +static int ospf6_zebra_gr_update(struct ospf6 *ospf6, int command, + uint32_t stale_time) +{ + struct zapi_cap api; + + if (!zclient || zclient->sock < 0 || !ospf6) + return 1; + + memset(&api, 0, sizeof(api)); + api.cap = command; + api.stale_removal_time = stale_time; + api.vrf_id = ospf6->vrf_id; + + (void)zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, + &api); + + return 0; +} + +int ospf6_zebra_gr_enable(struct ospf6 *ospf6, uint32_t stale_time) +{ + if (IS_DEBUG_OSPF6_GR) + zlog_debug("Zebra enable GR [stale time %u]", stale_time); + + return ospf6_zebra_gr_update(ospf6, ZEBRA_CLIENT_GR_CAPABILITIES, + stale_time); +} + +int ospf6_zebra_gr_disable(struct ospf6 *ospf6) +{ + if (IS_DEBUG_OSPF6_GR) + zlog_debug("Zebra disable GR"); + + return ospf6_zebra_gr_update(ospf6, ZEBRA_CLIENT_GR_DISABLE, 0); +} + +static int ospf6_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + unsigned long ifindex; + const struct in6_addr *nexthop = &in6addr_any; + struct ospf6 *ospf6; + struct prefix_ipv6 p; + + ospf6 = ospf6_lookup_by_vrf_id(vrf_id); + + if (ospf6 == NULL) + return 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + if (IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) + return 0; + + ifindex = api.nexthops[0].ifindex; + if (api.nexthops[0].type == NEXTHOP_TYPE_IPV6 + || api.nexthops[0].type == NEXTHOP_TYPE_IPV6_IFINDEX) + nexthop = &api.nexthops[0].gate.ipv6; + + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + zlog_debug( + "Zebra Receive route %s: %s %pFX nexthop %pI6 ifindex %ld tag %" ROUTE_TAG_PRI, + (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD ? "add" + : "delete"), + zebra_route_string(api.type), &api.prefix, nexthop, + ifindex, api.tag); + + memcpy(&p, &api.prefix, sizeof(p)); + if (is_default_prefix6(&p)) + api.type = DEFAULT_ROUTE; + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) + ospf6_asbr_redistribute_add(api.type, ifindex, &api.prefix, + api.nexthop_num, nexthop, api.tag, + ospf6, api.metric); + else + ospf6_asbr_redistribute_remove(api.type, ifindex, &api.prefix, + ospf6); + + return 0; +} + +DEFUN(show_zebra, + show_ospf6_zebra_cmd, + "show ipv6 ospf6 zebra [json]", + SHOW_STR + IPV6_STR + OSPF6_STR + ZEBRA_STR + JSON_STR) +{ + int i; + bool uj = use_json(argc, argv); + json_object *json; + json_object *json_zebra; + json_object *json_array; + + if (zclient == NULL) { + vty_out(vty, "Not connected to zebra\n"); + return CMD_SUCCESS; + } + + if (uj) { + json = json_object_new_object(); + json_zebra = json_object_new_object(); + json_array = json_object_new_array(); + + json_object_int_add(json_zebra, "fail", zclient->fail); + json_object_int_add( + json_zebra, "redistributeDefault", + vrf_bitmap_check(&zclient->default_information[AFI_IP6], + VRF_DEFAULT)); + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (vrf_bitmap_check(&zclient->redist[AFI_IP6][i], + VRF_DEFAULT)) + json_object_array_add( + json_array, + json_object_new_string( + zebra_route_string(i))); + } + json_object_object_add(json_zebra, "redistribute", json_array); + json_object_object_add(json, "zebraInformation", json_zebra); + + vty_json(vty, json); + } else { + vty_out(vty, "Zebra Information\n"); + vty_out(vty, " fail: %d\n", zclient->fail); + vty_out(vty, " redistribute default: %d\n", + vrf_bitmap_check(&zclient->default_information[AFI_IP6], + VRF_DEFAULT)); + vty_out(vty, " redistribute:"); + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (vrf_bitmap_check(&zclient->redist[AFI_IP6][i], + VRF_DEFAULT)) + vty_out(vty, " %s", zebra_route_string(i)); + } + vty_out(vty, "\n"); + } + return CMD_SUCCESS; +} + +static void ospf6_zebra_append_opaque_attr(struct ospf6_route *request, + struct zapi_route *api) +{ + struct ospf_zebra_opaque ospf_opaque = {}; + + /* OSPF path type */ + snprintf(ospf_opaque.path_type, sizeof(ospf_opaque.path_type), "%s", + OSPF6_PATH_TYPE_NAME(request->path.type)); + + switch (request->path.type) { + case OSPF6_PATH_TYPE_INTRA: + case OSPF6_PATH_TYPE_INTER: + /* OSPF area ID */ + (void)inet_ntop(AF_INET, &request->path.area_id, + ospf_opaque.area_id, + sizeof(ospf_opaque.area_id)); + break; + case OSPF6_PATH_TYPE_EXTERNAL1: + case OSPF6_PATH_TYPE_EXTERNAL2: + /* OSPF route tag */ + snprintf(ospf_opaque.tag, sizeof(ospf_opaque.tag), "%u", + request->path.tag); + break; + default: + break; + } + + SET_FLAG(api->message, ZAPI_MESSAGE_OPAQUE); + api->opaque.length = sizeof(struct ospf_zebra_opaque); + memcpy(api->opaque.data, &ospf_opaque, api->opaque.length); +} + +#define ADD 0 +#define REM 1 +static void ospf6_zebra_route_update(int type, struct ospf6_route *request, + struct ospf6 *ospf6) +{ + struct zapi_route api; + int nhcount; + int ret = 0; + struct prefix *dest; + + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug("Zebra Send %s route: %pFX", + (type == REM ? "remove" : "add"), &request->prefix); + + if (zclient->sock < 0) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" Not connected to Zebra"); + return; + } + + if (request->path.origin.adv_router == ospf6->router_id + && (request->path.type == OSPF6_PATH_TYPE_EXTERNAL1 + || request->path.type == OSPF6_PATH_TYPE_EXTERNAL2)) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" Ignore self-originated external route"); + return; + } + + /* If removing is the best path and if there's another path, + * treat this request as add the secondary path - if there are + * nexthops. + */ + if (type == REM && ospf6_route_is_best(request) && request->next && + ospf6_route_is_same(request, request->next) && + ospf6_route_num_nexthops(request->next) > 0) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug( + " Best-path removal resulted Secondary addition"); + type = ADD; + request = request->next; + } + + /* Only the best path will be sent to zebra. */ + if (!ospf6_route_is_best(request)) { + /* this is not preferred best route, ignore */ + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" Ignore non-best route"); + return; + } + + nhcount = ospf6_route_num_nexthops(request); + if (nhcount == 0) { + if (type == ADD) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" No nexthop, ignore"); + return; + } else if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug(" No nexthop, rem ok"); + } + + dest = &request->prefix; + + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf6->vrf_id; + api.type = ZEBRA_ROUTE_OSPF6; + api.safi = SAFI_UNICAST; + api.prefix = *dest; + + if (nhcount > ospf6->max_multipath) { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug( + " Nexthop count is greater than configured maximum-path, hence ignore the extra nexthops"); + } + + api.nexthop_num = MIN(nhcount, ospf6->max_multipath); + if (api.nexthop_num > 0) { + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + ospf6_route_zebra_copy_nexthops(request, api.nexthops, + api.nexthop_num, api.vrf_id); + } + + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = (request->path.metric_type == 2 ? request->path.u.cost_e2 + : request->path.cost); + if (request->path.tag) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = request->path.tag; + } + + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + api.distance = ospf6_distance_apply((struct prefix_ipv6 *)dest, request, + ospf6); + + if (type == ADD + && CHECK_FLAG(ospf6->config_flags, OSPF6_SEND_EXTRA_DATA_TO_ZEBRA)) + ospf6_zebra_append_opaque_attr(request, &api); + + if (type == REM) + ret = zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + else + ret = zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); + + if (ret == ZCLIENT_SEND_FAILURE) + flog_err(EC_LIB_ZAPI_SOCKET, + "zclient_route_send() %s failed: %s", + (type == REM ? "delete" : "add"), + safe_strerror(errno)); + + return; +} + +void ospf6_zebra_route_update_add(struct ospf6_route *request, + struct ospf6 *ospf6) +{ + if (ospf6->gr_info.restart_in_progress + || ospf6->gr_info.prepare_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + &request->prefix); + return; + } + + ospf6_zebra_route_update(ADD, request, ospf6); +} + +void ospf6_zebra_route_update_remove(struct ospf6_route *request, + struct ospf6 *ospf6) +{ + if (ospf6->gr_info.restart_in_progress + || ospf6->gr_info.prepare_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + &request->prefix); + return; + } + + ospf6_zebra_route_update(REM, request, ospf6); +} + +void ospf6_zebra_add_discard(struct ospf6_route *request, struct ospf6 *ospf6) +{ + struct zapi_route api; + struct prefix *dest = &request->prefix; + + if (ospf6->gr_info.restart_in_progress + || ospf6->gr_info.prepare_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + &request->prefix); + return; + } + + if (!CHECK_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf6->vrf_id; + api.type = ZEBRA_ROUTE_OSPF6; + api.safi = SAFI_UNICAST; + api.prefix = *dest; + zapi_route_set_blackhole(&api, BLACKHOLE_NULL); + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); + + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug("Zebra: Route add discard %pFX", dest); + + SET_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED); + } else { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug( + "Zebra: Blackhole route present already %pFX", + dest); + } +} + +void ospf6_zebra_delete_discard(struct ospf6_route *request, + struct ospf6 *ospf6) +{ + struct zapi_route api; + struct prefix *dest = &request->prefix; + + if (ospf6->gr_info.restart_in_progress + || ospf6->gr_info.prepare_in_progress) { + if (IS_DEBUG_OSPF6_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + &request->prefix); + return; + } + + if (CHECK_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED)) { + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf6->vrf_id; + api.type = ZEBRA_ROUTE_OSPF6; + api.safi = SAFI_UNICAST; + api.prefix = *dest; + zapi_route_set_blackhole(&api, BLACKHOLE_NULL); + + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug("Zebra: Route delete discard %pFX", dest); + + UNSET_FLAG(request->flag, OSPF6_ROUTE_BLACKHOLE_ADDED); + } else { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + zlog_debug( + "Zebra: Blackhole route already deleted %pFX", + dest); + } +} + +static struct ospf6_distance *ospf6_distance_new(void) +{ + return XCALLOC(MTYPE_OSPF6_DISTANCE, sizeof(struct ospf6_distance)); +} + +static void ospf6_distance_free(struct ospf6_distance *odistance) +{ + XFREE(MTYPE_OSPF6_DISTANCE, odistance); +} + +int ospf6_distance_set(struct vty *vty, struct ospf6 *o, + const char *distance_str, const char *ip_str, + const char *access_list_str) +{ + int ret; + struct prefix_ipv6 p; + uint8_t distance; + struct route_node *rn; + struct ospf6_distance *odistance; + + ret = str2prefix_ipv6(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + distance = atoi(distance_str); + + /* Get OSPF6 distance node. */ + rn = route_node_get(o->distance_table, (struct prefix *)&p); + if (rn->info) { + odistance = rn->info; + route_unlock_node(rn); + } else { + odistance = ospf6_distance_new(); + rn->info = odistance; + } + + /* Set distance value. */ + odistance->distance = distance; + + /* Reset access-list configuration. */ + if (odistance->access_list) { + free(odistance->access_list); + odistance->access_list = NULL; + } + if (access_list_str) + odistance->access_list = strdup(access_list_str); + + return CMD_SUCCESS; +} + +int ospf6_distance_unset(struct vty *vty, struct ospf6 *o, + const char *distance_str, const char *ip_str, + const char *access_list_str) +{ + int ret; + struct prefix_ipv6 p; + struct route_node *rn; + struct ospf6_distance *odistance; + + ret = str2prefix_ipv6(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rn = route_node_lookup(o->distance_table, (struct prefix *)&p); + if (!rn) { + vty_out(vty, "Cant't find specified prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + odistance = rn->info; + + if (odistance->access_list) + free(odistance->access_list); + ospf6_distance_free(odistance); + + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + + return CMD_SUCCESS; +} + +void ospf6_distance_reset(struct ospf6 *o) +{ + struct route_node *rn; + struct ospf6_distance *odistance; + + for (rn = route_top(o->distance_table); rn; rn = route_next(rn)) + if ((odistance = rn->info) != NULL) { + if (odistance->access_list) + free(odistance->access_list); + ospf6_distance_free(odistance); + rn->info = NULL; + route_unlock_node(rn); + } +} + +uint8_t ospf6_distance_apply(struct prefix_ipv6 *p, struct ospf6_route * or, + struct ospf6 *ospf6) +{ + struct ospf6 *o; + + o = ospf6; + if (o == NULL) + return 0; + + if (o->distance_intra) + if (or->path.type == OSPF6_PATH_TYPE_INTRA) + return o->distance_intra; + + if (o->distance_inter) + if (or->path.type == OSPF6_PATH_TYPE_INTER) + return o->distance_inter; + + if (o->distance_external) + if (or->path.type == OSPF6_PATH_TYPE_EXTERNAL1 || + or->path.type == OSPF6_PATH_TYPE_EXTERNAL2) + return o->distance_external; + + if (o->distance_all) + return o->distance_all; + + return 0; +} + +static void ospf6_zebra_connected(struct zclient *zclient) +{ + struct ospf6 *ospf6; + struct listnode *node; + + /* Send the client registration */ + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); + + zclient_send_reg_requests(zclient, VRF_DEFAULT); + + /* Activate graceful restart if configured. */ + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (!ospf6->gr_info.restart_support) + continue; + (void)ospf6_zebra_gr_enable(ospf6, ospf6->gr_info.grace_period); + } +} + +static zclient_handler *const ospf6_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = ospf6_router_id_update_zebra, + [ZEBRA_INTERFACE_ADDRESS_ADD] = ospf6_zebra_if_address_update_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = ospf6_zebra_if_address_update_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = ospf6_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = ospf6_zebra_read_route, +}; + +void ospf6_zebra_init(struct event_loop *master) +{ + /* Allocate zebra structure. */ + zclient = zclient_new(master, &zclient_options_default, ospf6_handlers, + array_size(ospf6_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_OSPF6, 0, &ospf6d_privs); + zclient->zebra_connected = ospf6_zebra_connected; + zclient->nexthop_update = ospf6_zebra_import_check_update; + + /* Install command element for zebra node. */ + install_element(VIEW_NODE, &show_ospf6_zebra_cmd); +} + +/* Debug */ + +DEFUN (debug_ospf6_zebra_sendrecv, + debug_ospf6_zebra_sendrecv_cmd, + "debug ospf6 zebra []", + DEBUG_STR + OSPF6_STR + "Debug connection between zebra\n" + "Debug Sending zebra\n" + "Debug Receiving zebra\n" + ) +{ + int idx_send_recv = 3; + unsigned char level = 0; + + if (argc == 4) { + if (strmatch(argv[idx_send_recv]->text, "send")) + level = OSPF6_DEBUG_ZEBRA_SEND; + else if (strmatch(argv[idx_send_recv]->text, "recv")) + level = OSPF6_DEBUG_ZEBRA_RECV; + } else + level = OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV; + + OSPF6_DEBUG_ZEBRA_ON(level); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf6_zebra_sendrecv, + no_debug_ospf6_zebra_sendrecv_cmd, + "no debug ospf6 zebra []", + NO_STR + DEBUG_STR + OSPF6_STR + "Debug connection between zebra\n" + "Debug Sending zebra\n" + "Debug Receiving zebra\n" + ) +{ + int idx_send_recv = 4; + unsigned char level = 0; + + if (argc == 5) { + if (strmatch(argv[idx_send_recv]->text, "send")) + level = OSPF6_DEBUG_ZEBRA_SEND; + else if (strmatch(argv[idx_send_recv]->text, "recv")) + level = OSPF6_DEBUG_ZEBRA_RECV; + } else + level = OSPF6_DEBUG_ZEBRA_SEND | OSPF6_DEBUG_ZEBRA_RECV; + + OSPF6_DEBUG_ZEBRA_OFF(level); + return CMD_SUCCESS; +} + + +int config_write_ospf6_debug_zebra(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_ZEBRA(SEND) && IS_OSPF6_DEBUG_ZEBRA(RECV)) + vty_out(vty, "debug ospf6 zebra\n"); + else { + if (IS_OSPF6_DEBUG_ZEBRA(SEND)) + vty_out(vty, "debug ospf6 zebra send\n"); + if (IS_OSPF6_DEBUG_ZEBRA(RECV)) + vty_out(vty, "debug ospf6 zebra recv\n"); + } + return 0; +} + +void install_element_ospf6_debug_zebra(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_zebra_sendrecv_cmd); + install_element(ENABLE_NODE, &no_debug_ospf6_zebra_sendrecv_cmd); + install_element(CONFIG_NODE, &debug_ospf6_zebra_sendrecv_cmd); + install_element(CONFIG_NODE, &no_debug_ospf6_zebra_sendrecv_cmd); +} diff --git a/ospf6d/ospf6_zebra.h b/ospf6d/ospf6_zebra.h new file mode 100644 index 0000000..7669b5e --- /dev/null +++ b/ospf6d/ospf6_zebra.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6_ZEBRA_H +#define OSPF6_ZEBRA_H + +#include "zclient.h" + +#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX + +/* Debug option */ +extern unsigned char conf_debug_ospf6_zebra; +#define OSPF6_DEBUG_ZEBRA_SEND 0x01 +#define OSPF6_DEBUG_ZEBRA_RECV 0x02 +#define OSPF6_DEBUG_ZEBRA_ON(level) (conf_debug_ospf6_zebra |= level) +#define OSPF6_DEBUG_ZEBRA_OFF(level) (conf_debug_ospf6_zebra &= ~(level)) +#define IS_OSPF6_DEBUG_ZEBRA(e) (conf_debug_ospf6_zebra & OSPF6_DEBUG_ZEBRA_##e) + +/* OSPF6 distance */ +struct ospf6_distance { + /* Distance value for the IP source prefix */ + uint8_t distance; + + /* Name of the access-list to be matched */ + char *access_list; +}; + +extern struct zclient *zclient; +struct ospf6; + +extern void ospf6_zebra_route_update_add(struct ospf6_route *request, + struct ospf6 *ospf6); +extern void ospf6_zebra_route_update_remove(struct ospf6_route *request, + struct ospf6 *ospf6); + +extern void ospf6_zebra_redistribute(int, vrf_id_t vrf_id); +extern void ospf6_zebra_no_redistribute(int, vrf_id_t vrf_id); +#define ospf6_zebra_is_redistribute(type, vrf_id) \ + vrf_bitmap_check(&zclient->redist[AFI_IP6][type], vrf_id) +extern void ospf6_zebra_init(struct event_loop *tm); +extern void ospf6_zebra_import_default_route(struct ospf6 *ospf6, bool unreg); +extern void ospf6_zebra_add_discard(struct ospf6_route *request, + struct ospf6 *ospf6); +extern void ospf6_zebra_delete_discard(struct ospf6_route *request, + struct ospf6 *ospf6); + +extern void ospf6_distance_reset(struct ospf6 *ospf6); +extern uint8_t ospf6_distance_apply(struct prefix_ipv6 *p, + struct ospf6_route * or, + struct ospf6 *ospf6); + +extern int ospf6_zebra_gr_enable(struct ospf6 *ospf6, uint32_t stale_time); +extern int ospf6_zebra_gr_disable(struct ospf6 *ospf6); +extern int ospf6_distance_set(struct vty *vty, struct ospf6 *ospf6, + const char *distance_str, const char *ip_str, + const char *access_list_str); +extern int ospf6_distance_unset(struct vty *vty, struct ospf6 *ospf6, + const char *distance_str, const char *ip_str, + const char *access_list_str); + +extern int config_write_ospf6_debug_zebra(struct vty *vty); +extern void install_element_ospf6_debug_zebra(void); +extern void ospf6_zebra_vrf_register(struct ospf6 *ospf6); +extern void ospf6_zebra_vrf_deregister(struct ospf6 *ospf6); +#endif /*OSPF6_ZEBRA_H*/ diff --git a/ospf6d/ospf6d.c b/ospf6d/ospf6d.c new file mode 100644 index 0000000..d90a950 --- /dev/null +++ b/ospf6d/ospf6d.c @@ -0,0 +1,1486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "command.h" +#include "plist.h" +#include "filter.h" + +#include "ospf6_proto.h" +#include "ospf6_top.h" +#include "ospf6_network.h" +#include "ospf6_lsa.h" +#include "ospf6_lsdb.h" +#include "ospf6_message.h" +#include "ospf6_route.h" +#include "ospf6_zebra.h" +#include "ospf6_spf.h" +#include "ospf6_area.h" +#include "ospf6_interface.h" +#include "ospf6_neighbor.h" +#include "ospf6_intra.h" +#include "ospf6_asbr.h" +#include "ospf6_abr.h" +#include "ospf6_flood.h" +#include "ospf6d.h" +#include "ospf6_bfd.h" +#include "ospf6_gr.h" +#include "lib/json.h" +#include "ospf6_nssa.h" +#include "ospf6_auth_trailer.h" +#include "ospf6d/ospf6d_clippy.c" + +DEFINE_MGROUP(OSPF6D, "ospf6d"); + +/* OSPF6 config processing timer thread */ +struct event *t_ospf6_cfg; + +/* OSPF6 debug event state */ +unsigned char conf_debug_ospf6_event; + +struct route_node *route_prev(struct route_node *node) +{ + struct route_node *end; + struct route_node *prev = NULL; + + end = node; + node = node->parent; + if (node) + route_lock_node(node); + while (node) { + prev = node; + node = route_next(node); + if (node == end) { + route_unlock_node(node); + node = NULL; + } + } + route_unlock_node(end); + if (prev) + route_lock_node(prev); + + return prev; +} + +static int config_write_ospf6_debug(struct vty *vty); +static int config_write_ospf6_debug_event(struct vty *vty); +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_ospf6_debug, +}; + +static int config_write_ospf6_debug(struct vty *vty) +{ + config_write_ospf6_debug_message(vty); + config_write_ospf6_debug_lsa(vty); + config_write_ospf6_debug_zebra(vty); + config_write_ospf6_debug_interface(vty); + config_write_ospf6_debug_neighbor(vty); + config_write_ospf6_debug_spf(vty); + config_write_ospf6_debug_route(vty); + config_write_ospf6_debug_brouter(vty); + config_write_ospf6_debug_asbr(vty); + config_write_ospf6_debug_abr(vty); + config_write_ospf6_debug_flood(vty); + config_write_ospf6_debug_nssa(vty); + config_write_ospf6_debug_gr_helper(vty); + config_write_ospf6_debug_auth(vty); + config_write_ospf6_debug_event(vty); + + return 0; +} + +DEFUN_NOSH (show_debugging_ospf6, + show_debugging_ospf6_cmd, + "show debugging [ospf6]", + SHOW_STR + DEBUG_STR + OSPF6_STR) +{ + vty_out(vty, "OSPF6 debugging status:\n"); + + config_write_ospf6_debug(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +#define AREA_LSDB_TITLE_FORMAT \ + "\n Area Scoped Link State Database (Area %s)\n\n" +#define IF_LSDB_TITLE_FORMAT \ + "\n I/F Scoped Link State Database (I/F %s in Area %s)\n\n" +#define AS_LSDB_TITLE_FORMAT "\n AS Scoped Link State Database\n\n" + +static int parse_show_level(int idx_level, int argc, struct cmd_token **argv) +{ + int level = OSPF6_LSDB_SHOW_LEVEL_NORMAL; + + if (argc > idx_level) { + if (strmatch(argv[idx_level]->text, "detail")) + level = OSPF6_LSDB_SHOW_LEVEL_DETAIL; + else if (strmatch(argv[idx_level]->text, "dump")) + level = OSPF6_LSDB_SHOW_LEVEL_DUMP; + else if (strmatch(argv[idx_level]->text, "internal")) + level = OSPF6_LSDB_SHOW_LEVEL_INTERNAL; + } + + return level; +} + +static uint16_t parse_type_spec(int idx_lsa, int argc, struct cmd_token **argv) +{ + uint16_t type = 0; + + if (argc > idx_lsa) { + if (strmatch(argv[idx_lsa]->text, "router")) + type = htons(OSPF6_LSTYPE_ROUTER); + else if (strmatch(argv[idx_lsa]->text, "network")) + type = htons(OSPF6_LSTYPE_NETWORK); + else if (strmatch(argv[idx_lsa]->text, "as-external")) + type = htons(OSPF6_LSTYPE_AS_EXTERNAL); + else if (strmatch(argv[idx_lsa]->text, "intra-prefix")) + type = htons(OSPF6_LSTYPE_INTRA_PREFIX); + else if (strmatch(argv[idx_lsa]->text, "inter-router")) + type = htons(OSPF6_LSTYPE_INTER_ROUTER); + else if (strmatch(argv[idx_lsa]->text, "inter-prefix")) + type = htons(OSPF6_LSTYPE_INTER_PREFIX); + else if (strmatch(argv[idx_lsa]->text, "link")) + type = htons(OSPF6_LSTYPE_LINK); + else if (strmatch(argv[idx_lsa]->text, "type-7")) + type = htons(OSPF6_LSTYPE_TYPE_7); + } + + return type; +} + +void ospf6_lsdb_show(struct vty *vty, enum ospf_lsdb_show_level level, + uint16_t *type, uint32_t *id, uint32_t *adv_router, + struct ospf6_lsdb *lsdb, json_object *json_obj, + bool use_json) +{ + struct ospf6_lsa *lsa; + const struct route_node *end = NULL; + void (*showfunc)(struct vty *, struct ospf6_lsa *, json_object *, + bool) = NULL; + json_object *json_array = NULL; + + switch (level) { + case OSPF6_LSDB_SHOW_LEVEL_DETAIL: + showfunc = ospf6_lsa_show; + break; + case OSPF6_LSDB_SHOW_LEVEL_INTERNAL: + showfunc = ospf6_lsa_show_internal; + break; + case OSPF6_LSDB_SHOW_LEVEL_DUMP: + showfunc = ospf6_lsa_show_dump; + break; + case OSPF6_LSDB_SHOW_LEVEL_NORMAL: + default: + showfunc = ospf6_lsa_show_summary; + } + + if (use_json) + json_array = json_object_new_array(); + + if (type && id && adv_router) { + lsa = ospf6_lsdb_lookup(*type, *id, *adv_router, lsdb); + if (lsa) { + if (level == OSPF6_LSDB_SHOW_LEVEL_NORMAL) + ospf6_lsa_show(vty, lsa, json_array, use_json); + else + (*showfunc)(vty, lsa, json_array, use_json); + } + + if (use_json) + json_object_object_add(json_obj, "lsa", json_array); + return; + } + + if ((level == OSPF6_LSDB_SHOW_LEVEL_NORMAL) && !use_json) + ospf6_lsa_show_summary_header(vty); + + end = ospf6_lsdb_head(lsdb, !!type + !!(type && adv_router), + type ? *type : 0, adv_router ? *adv_router : 0, + &lsa); + while (lsa) { + if ((!adv_router || lsa->header->adv_router == *adv_router) + && (!id || lsa->header->id == *id)) + (*showfunc)(vty, lsa, json_array, use_json); + lsa = ospf6_lsdb_next(end, lsa); + } + + if (use_json) + json_object_object_add(json_obj, "lsa", json_array); +} + +static void ospf6_lsdb_show_wrapper(struct vty *vty, + enum ospf_lsdb_show_level level, + uint16_t *type, uint32_t *id, + uint32_t *adv_router, bool uj, + struct ospf6 *ospf6) +{ + struct listnode *i, *j; + struct ospf6 *o = ospf6; + struct ospf6_area *oa; + struct ospf6_interface *oi; + json_object *json = NULL; + json_object *json_array = NULL; + json_object *json_obj = NULL; + + if (uj) { + json = json_object_new_object(); + json_array = json_object_new_array(); + } + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + if (uj) { + json_obj = json_object_new_object(); + json_object_string_add(json_obj, "areaId", oa->name); + } else + vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); + ospf6_lsdb_show(vty, level, type, id, adv_router, oa->lsdb, + json_obj, uj); + if (uj) + json_object_array_add(json_array, json_obj); + } + if (uj) + json_object_object_add(json, "areaScopedLinkStateDb", + json_array); + + if (uj) + json_array = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + if (uj) { + json_obj = json_object_new_object(); + json_object_string_add(json_obj, "areaId", + oa->name); + json_object_string_add(json_obj, "interface", + oi->interface->name); + } else + vty_out(vty, IF_LSDB_TITLE_FORMAT, + oi->interface->name, oa->name); + ospf6_lsdb_show(vty, level, type, id, adv_router, + oi->lsdb, json_obj, uj); + if (uj) + json_object_array_add(json_array, json_obj); + } + } + if (uj) + json_object_object_add(json, "interfaceScopedLinkStateDb", + json_array); + if (uj) { + json_array = json_object_new_array(); + json_obj = json_object_new_object(); + } else + vty_out(vty, AS_LSDB_TITLE_FORMAT); + + ospf6_lsdb_show(vty, level, type, id, adv_router, o->lsdb, json_obj, + uj); + + if (uj) { + json_object_array_add(json_array, json_obj); + json_object_object_add(json, "asScopedLinkStateDb", json_array); + + vty_json(vty, json); + } else + vty_out(vty, "\n"); +} + +static void ospf6_lsdb_type_show_wrapper(struct vty *vty, + enum ospf_lsdb_show_level level, + uint16_t *type, uint32_t *id, + uint32_t *adv_router, bool uj, + struct ospf6 *ospf6) +{ + struct listnode *i, *j; + struct ospf6 *o = ospf6; + struct ospf6_area *oa; + struct ospf6_interface *oi; + json_object *json = NULL; + json_object *json_array = NULL; + json_object *json_obj = NULL; + + if (uj) { + json = json_object_new_object(); + json_array = json_object_new_array(); + } + + switch (OSPF6_LSA_SCOPE(*type)) { + case OSPF6_SCOPE_AREA: + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + if (uj) { + json_obj = json_object_new_object(); + json_object_string_add(json_obj, "areaId", + oa->name); + } else + vty_out(vty, AREA_LSDB_TITLE_FORMAT, oa->name); + + ospf6_lsdb_show(vty, level, type, id, adv_router, + oa->lsdb, json_obj, uj); + if (uj) + json_object_array_add(json_array, json_obj); + } + if (uj) + json_object_object_add(json, "areaScopedLinkStateDb", + json_array); + break; + + case OSPF6_SCOPE_LINKLOCAL: + for (ALL_LIST_ELEMENTS_RO(o->area_list, i, oa)) { + for (ALL_LIST_ELEMENTS_RO(oa->if_list, j, oi)) { + if (uj) { + json_obj = json_object_new_object(); + json_object_string_add( + json_obj, "areaId", oa->name); + json_object_string_add( + json_obj, "interface", + oi->interface->name); + } else + vty_out(vty, IF_LSDB_TITLE_FORMAT, + oi->interface->name, oa->name); + + ospf6_lsdb_show(vty, level, type, id, + adv_router, oi->lsdb, json_obj, + uj); + + if (uj) + json_object_array_add(json_array, + json_obj); + } + } + if (uj) + json_object_object_add( + json, "interfaceScopedLinkStateDb", json_array); + break; + + case OSPF6_SCOPE_AS: + if (uj) + json_obj = json_object_new_object(); + else + vty_out(vty, AS_LSDB_TITLE_FORMAT); + + ospf6_lsdb_show(vty, level, type, id, adv_router, o->lsdb, + json_obj, uj); + if (uj) { + json_object_array_add(json_array, json_obj); + json_object_object_add(json, "asScopedLinkStateDb", + json_array); + } + break; + + default: + assert(0); + break; + } + if (uj) + vty_json(vty, json); + else + vty_out(vty, "\n"); +} + +DEFUN(show_ipv6_ospf6_database, show_ipv6_ospf6_database_cmd, + "show ipv6 ospf6 [vrf ] database [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int level; + int idx_level = 4; + struct listnode *node; + struct ospf6 *ospf6; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_level += 2; + + level = parse_show_level(idx_level, argc, argv); + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, NULL, NULL, + uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type, show_ipv6_ospf6_database_type_cmd, + "show ipv6 ospf6 [vrf ] database [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_level = 5; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_type_show_wrapper(vty, level, &type, NULL, + NULL, uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_id, show_ipv6_ospf6_database_id_cmd, + "show ipv6 ospf6 [vrf ] database <*|linkstate-id> A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Any Link state Type\n" + "Search by Link state ID\n" + "Specify Link state ID as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_ipv4 = 5; + int idx_level = 6; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint32_t id = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (argv[idx_ipv4]->type == IPV4_TKN) + inet_pton(AF_INET, argv[idx_ipv4]->arg, &id); + + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, &id, NULL, uj, + ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_router, show_ipv6_ospf6_database_router_cmd, + "show ipv6 ospf6 [vrf ] database <*|adv-router> * A.B.C.D [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Any Link state Type\n" + "Search by Advertising Router\n" + "Any Link state ID\n" + "Specify Advertising Router as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_ipv4 = 6; + int idx_level = 7; + int level; + struct listnode *node; + struct ospf6 *ospf6; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_ipv4 += 2; + idx_level += 2; + } + + inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, NULL, + &adv_router, uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int ipv6_ospf6_database_aggr_router_common(struct vty *vty, + uint32_t adv_router, + struct ospf6 *ospf6) +{ + int level = OSPF6_LSDB_SHOW_LEVEL_DETAIL; + uint16_t type = htons(OSPF6_LSTYPE_ROUTER); + struct listnode *i; + struct ospf6_area *oa; + struct ospf6_lsdb *lsdb; + + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, i, oa)) { + if (adv_router == ospf6->router_id) + lsdb = oa->lsdb_self; + else + lsdb = oa->lsdb; + if (ospf6_create_single_router_lsa(oa, lsdb, adv_router) + == NULL) { + vty_out(vty, "Adv router is not found in LSDB."); + return CMD_SUCCESS; + } + ospf6_lsdb_show(vty, level, &type, NULL, NULL, + oa->temp_router_lsa_lsdb, NULL, false); + /* Remove the temp cache */ + ospf6_remove_temp_router_lsa(oa); + } + + vty_out(vty, "\n"); + return CMD_SUCCESS; +} + +DEFUN_HIDDEN( + show_ipv6_ospf6_database_aggr_router, + show_ipv6_ospf6_database_aggr_router_cmd, + "show ipv6 ospf6 [vrf ] database aggr adv-router A.B.C.D", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Aggregated Router LSA\n" + "Search by Advertising Router\n" + "Specify Advertising Router as IPv4 address notation\n") +{ + int idx_ipv4 = 6; + struct listnode *node; + struct ospf6 *ospf6; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_ipv4 += 2; + + inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ipv6_ospf6_database_aggr_router_common(vty, adv_router, + ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type_id, show_ipv6_ospf6_database_type_id_cmd, + "show ipv6 ospf6 [vrf ] database linkstate-id A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Search by Link state ID\n" + "Specify Link state ID as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_ipv4 = 6; + int idx_level = 7; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t id = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_ipv4 += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_ipv4]->arg, &id); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_type_show_wrapper(vty, level, &type, &id, + NULL, uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type_router, + show_ipv6_ospf6_database_type_router_cmd, + "show ipv6 ospf6 [vrf ] database <*|adv-router> A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Any Link state ID\n" + "Search by Advertising Router\n" + "Specify Advertising Router as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_ipv4 = 6; + int idx_level = 7; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_ipv4 += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_type_show_wrapper(vty, level, &type, NULL, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_id_router, + show_ipv6_ospf6_database_id_router_cmd, + "show ipv6 ospf6 [vrf ] database * A.B.C.D A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Any Link state Type\n" + "Specify Link state ID as IPv4 address notation\n" + "Specify Advertising Router as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_ls_id = 5; + int idx_adv_rtr = 6; + int idx_level = 7; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint32_t id = 0; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_ls_id += 2; + idx_adv_rtr += 2; + idx_level += 2; + } + + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, &id, + &adv_router, uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_adv_router_linkstate_id, + show_ipv6_ospf6_database_adv_router_linkstate_id_cmd, + "show ipv6 ospf6 [vrf ] database adv-router A.B.C.D linkstate-id A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Search by Advertising Router\n" + "Specify Advertising Router as IPv4 address notation\n" + "Search by Link state ID\n" + "Specify Link state ID as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_adv_rtr = 5; + int idx_ls_id = 7; + int idx_level = 8; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint32_t id = 0; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_adv_rtr += 2; + idx_ls_id += 2; + idx_level += 2; + } + inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, &id, + &adv_router, uj, ospf6); + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type_id_router, + show_ipv6_ospf6_database_type_id_router_cmd, + "show ipv6 ospf6 [vrf ] database A.B.C.D A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Specify Link state ID as IPv4 address notation\n" + "Specify Advertising Router as IPv4 address notation\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_ls_id = 5; + int idx_adv_rtr = 6; + int idx_level = 7; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t id = 0; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_ls_id += 2; + idx_adv_rtr += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_type_show_wrapper(vty, level, &type, &id, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + + +DEFUN (show_ipv6_ospf6_database_type_adv_router_linkstate_id, + show_ipv6_ospf6_database_type_adv_router_linkstate_id_cmd, + "show ipv6 ospf6 [vrf ] database adv-router A.B.C.D linkstate-id A.B.C.D [] [json]", + SHOW_STR + IPV6_STR + OSPF6_STR + VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Search by Advertising Router\n" + "Specify Advertising Router as IPv4 address notation\n" + "Search by Link state ID\n" + "Specify Link state ID as IPv4 address notation\n" + "Dump LSAs\n" + "Display LSA's internal information\n" + JSON_STR) +{ + int idx_lsa = 4; + int idx_adv_rtr = 6; + int idx_ls_id = 8; + int idx_level = 9; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t id = 0; + uint32_t adv_router = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_adv_rtr += 2; + idx_ls_id += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_adv_rtr]->arg, &adv_router); + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_type_show_wrapper(vty, level, &type, &id, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_self_originated, + show_ipv6_ospf6_database_self_originated_cmd, + "show ipv6 ospf6 [vrf ] database self-originated [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Self-originated LSAs\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_level = 5; + int level; + struct listnode *node; + struct ospf6 *ospf6; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + uint32_t adv_router = 0; + bool uj = use_json(argc, argv); + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_level += 2; + + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + adv_router = ospf6->router_id; + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + ospf6_lsdb_show_wrapper(vty, level, NULL, NULL, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + + +DEFUN(show_ipv6_ospf6_database_type_self_originated, + show_ipv6_ospf6_database_type_self_originated_cmd, + "show ipv6 ospf6 [vrf ] database self-originated [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Display Self-originated LSAs\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_level = 6; + int level; + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t adv_router = 0; + bool uj = use_json(argc, argv); + + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + adv_router = ospf6->router_id; + ospf6_lsdb_type_show_wrapper(vty, level, &type, NULL, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type_self_originated_linkstate_id, + show_ipv6_ospf6_database_type_self_originated_linkstate_id_cmd, + "show ipv6 ospf6 [vrf ] database self-originated linkstate-id A.B.C.D [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Display Self-originated LSAs\n" + "Search by Link state ID\n" + "Specify Link state ID as IPv4 address notation\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_ls_id = 7; + int idx_level = 8; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t adv_router = 0; + uint32_t id = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_ls_id += 2; + idx_level += 2; + } + + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + adv_router = ospf6->router_id; + ospf6_lsdb_type_show_wrapper(vty, level, &type, &id, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_database_type_id_self_originated, + show_ipv6_ospf6_database_type_id_self_originated_cmd, + "show ipv6 ospf6 [vrf ] database A.B.C.D self-originated [] [json]", + SHOW_STR IPV6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display Link state database\n" + "Display Router LSAs\n" + "Display Network LSAs\n" + "Display Inter-Area-Prefix LSAs\n" + "Display Inter-Area-Router LSAs\n" + "Display As-External LSAs\n" + "Display Group-Membership LSAs\n" + "Display Type-7 LSAs\n" + "Display Link LSAs\n" + "Display Intra-Area-Prefix LSAs\n" + "Specify Link state ID as IPv4 address notation\n" + "Display Self-originated LSAs\n" + "Display details of LSAs\n" + "Dump LSAs\n" + "Display LSA's internal information\n" JSON_STR) +{ + int idx_lsa = 4; + int idx_ls_id = 5; + int idx_level = 7; + int level; + bool uj = use_json(argc, argv); + struct listnode *node; + struct ospf6 *ospf6; + uint16_t type = 0; + uint32_t adv_router = 0; + uint32_t id = 0; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_lsa += 2; + idx_ls_id += 2; + idx_level += 2; + } + + type = parse_type_spec(idx_lsa, argc, argv); + inet_pton(AF_INET, argv[idx_ls_id]->arg, &id); + level = parse_show_level(idx_level, argc, argv); + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + adv_router = ospf6->router_id; + ospf6_lsdb_type_show_wrapper(vty, level, &type, &id, + &adv_router, uj, ospf6); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +static int show_ospf6_border_routers_common(struct vty *vty, int argc, + struct cmd_token **argv, + struct ospf6 *ospf6, int idx_ipv4, + int idx_argc) +{ + uint32_t adv_router; + struct ospf6_route *ro; + struct prefix prefix; + + + if (argc == idx_argc) { + if (strmatch(argv[idx_ipv4]->text, "detail")) { + for (ro = ospf6_route_head(ospf6->brouter_table); ro; + ro = ospf6_route_next(ro)) + ospf6_route_show_detail(vty, ro, NULL, false); + } else { + inet_pton(AF_INET, argv[idx_ipv4]->arg, &adv_router); + + ospf6_linkstate_prefix(adv_router, 0, &prefix); + ro = ospf6_route_lookup(&prefix, ospf6->brouter_table); + if (!ro) { + vty_out(vty, + "No Route found for Router ID: %s\n", + argv[idx_ipv4]->arg); + return CMD_SUCCESS; + } + + ospf6_route_show_detail(vty, ro, NULL, false); + return CMD_SUCCESS; + } + } else { + ospf6_brouter_show_header(vty); + + for (ro = ospf6_route_head(ospf6->brouter_table); ro; + ro = ospf6_route_next(ro)) + ospf6_brouter_show(vty, ro); + } + + return CMD_SUCCESS; +} + +DEFUN(show_ipv6_ospf6_border_routers, show_ipv6_ospf6_border_routers_cmd, + "show ipv6 ospf6 [vrf ] border-routers []", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display routing table for ABR and ASBR\n" + "Router ID\n" + "Show detailed output\n") +{ + int idx_ipv4 = 4; + struct ospf6 *ospf6 = NULL; + struct listnode *node; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + int idx_argc = 5; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) { + idx_argc += 2; + idx_ipv4 += 2; + } + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + show_ospf6_border_routers_common(vty, argc, argv, ospf6, + idx_ipv4, idx_argc); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + + +DEFUN(show_ipv6_ospf6_linkstate, show_ipv6_ospf6_linkstate_cmd, + "show ipv6 ospf6 [vrf ] linkstate ", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display linkstate routing table\n" + "Display Router Entry\n" + "Specify Router ID as IPv4 address notation\n" + "Display Network Entry\n" + "Specify Router ID as IPv4 address notation\n" + "Specify Link state ID as IPv4 address notation\n") +{ + int idx_ipv4 = 5; + struct listnode *node, *nnode; + struct ospf6_area *oa; + struct ospf6 *ospf6 = NULL; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_ipv4 += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, nnode, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + vty_out(vty, + "\n SPF Result in Area %s\n\n", + oa->name); + ospf6_linkstate_table_show(vty, idx_ipv4, argc, + argv, oa->spf_table); + } + vty_out(vty, "\n"); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + + +DEFUN(show_ipv6_ospf6_linkstate_detail, show_ipv6_ospf6_linkstate_detail_cmd, + "show ipv6 ospf6 [vrf ] linkstate detail", + SHOW_STR IP6_STR OSPF6_STR VRF_CMD_HELP_STR + "All VRFs\n" + "Display linkstate routing table\n" + "Display detailed information\n") +{ + int idx_detail = 4; + struct listnode *node; + struct ospf6_area *oa; + struct ospf6 *ospf6 = NULL; + const char *vrf_name = NULL; + bool all_vrf = false; + int idx_vrf = 0; + + OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + if (idx_vrf > 0) + idx_detail += 2; + + for (ALL_LIST_ELEMENTS_RO(om6->ospf6, node, ospf6)) { + if (all_vrf || strcmp(ospf6->name, vrf_name) == 0) { + for (ALL_LIST_ELEMENTS_RO(ospf6->area_list, node, oa)) { + vty_out(vty, + "\n SPF Result in Area %s\n\n", + oa->name); + ospf6_linkstate_table_show(vty, idx_detail, + argc, argv, + oa->spf_table); + } + vty_out(vty, "\n"); + + if (!all_vrf) + break; + } + } + + OSPF6_CMD_CHECK_VRF(false, all_vrf, ospf6); + + return CMD_SUCCESS; +} + +DEFPY(debug_ospf6_event, debug_ospf6_event_cmd, "[no] debug ospf6 event", + NO_STR DEBUG_STR OSPF6_STR "Debug OSPFv3 event function\n") +{ + if (!no) + OSPF6_DEBUG_EVENT_ON(); + else + OSPF6_DEBUG_EVENT_OFF(); + return CMD_SUCCESS; +} + +static int config_write_ospf6_debug_event(struct vty *vty) +{ + if (IS_OSPF6_DEBUG_EVENT) + vty_out(vty, "debug ospf6 event\n"); + return 0; +} + +static void install_element_ospf6_debug_event(void) +{ + install_element(ENABLE_NODE, &debug_ospf6_event_cmd); + install_element(CONFIG_NODE, &debug_ospf6_event_cmd); +} + +/* Install ospf related commands. */ +void ospf6_init(struct event_loop *master) +{ + ospf6_top_init(); + ospf6_area_init(); + ospf6_interface_init(); + ospf6_neighbor_init(); + ospf6_zebra_init(master); + + ospf6_lsa_init(); + ospf6_spf_init(); + ospf6_intra_init(); + ospf6_asbr_init(); + ospf6_abr_init(); + ospf6_gr_init(); + ospf6_gr_helper_config_init(); + + /* initialize hooks for modifying filter rules */ + prefix_list_add_hook(ospf6_plist_update); + prefix_list_delete_hook(ospf6_plist_update); + access_list_add_hook(ospf6_filter_update); + access_list_delete_hook(ospf6_filter_update); + + ospf6_bfd_init(); + install_node(&debug_node); + + install_element_ospf6_debug_message(); + install_element_ospf6_debug_lsa(); + install_element_ospf6_debug_interface(); + install_element_ospf6_debug_neighbor(); + install_element_ospf6_debug_zebra(); + install_element_ospf6_debug_spf(); + install_element_ospf6_debug_route(); + install_element_ospf6_debug_brouter(); + install_element_ospf6_debug_asbr(); + install_element_ospf6_debug_abr(); + install_element_ospf6_debug_flood(); + install_element_ospf6_debug_nssa(); + + install_element_ospf6_clear_process(); + install_element_ospf6_clear_interface(); + + install_element(ENABLE_NODE, &show_debugging_ospf6_cmd); + + install_element(VIEW_NODE, &show_ipv6_ospf6_border_routers_cmd); + + install_element(VIEW_NODE, &show_ipv6_ospf6_linkstate_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_linkstate_detail_cmd); + + install_element(VIEW_NODE, &show_ipv6_ospf6_database_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_id_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_router_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_id_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_type_router_cmd); + install_element(VIEW_NODE, + &show_ipv6_ospf6_database_adv_router_linkstate_id_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_id_router_cmd); + install_element(VIEW_NODE, + &show_ipv6_ospf6_database_type_id_router_cmd); + install_element( + VIEW_NODE, + &show_ipv6_ospf6_database_type_adv_router_linkstate_id_cmd); + install_element(VIEW_NODE, + &show_ipv6_ospf6_database_self_originated_cmd); + install_element(VIEW_NODE, + &show_ipv6_ospf6_database_type_self_originated_cmd); + install_element(VIEW_NODE, + &show_ipv6_ospf6_database_type_id_self_originated_cmd); + install_element( + VIEW_NODE, + &show_ipv6_ospf6_database_type_self_originated_linkstate_id_cmd); + install_element(VIEW_NODE, &show_ipv6_ospf6_database_aggr_router_cmd); + install_element_ospf6_debug_event(); + install_element_ospf6_debug_auth(); + ospf6_interface_auth_trailer_cmd_init(); + install_element_ospf6_clear_intf_auth(); +} diff --git a/ospf6d/ospf6d.h b/ospf6d/ospf6d.h new file mode 100644 index 0000000..c927ee7 --- /dev/null +++ b/ospf6d/ospf6d.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2003 Yasuhiro Ohara + */ + +#ifndef OSPF6D_H +#define OSPF6D_H + +#include "libospf.h" +#include "frrevent.h" +#include "memory.h" + +DECLARE_MGROUP(OSPF6D); + +/* global variables */ +extern struct event_loop *master; + +/* OSPF config processing timer thread */ +extern struct event *t_ospf6_cfg; + +/* Historical for KAME. */ +#ifndef IPV6_JOIN_GROUP +#ifdef IPV6_ADD_MEMBERSHIP +#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP +#endif /* IPV6_ADD_MEMBERSHIP. */ +#ifdef IPV6_JOIN_MEMBERSHIP +#define IPV6_JOIN_GROUP IPV6_JOIN_MEMBERSHIP +#endif /* IPV6_JOIN_MEMBERSHIP. */ +#endif /* ! IPV6_JOIN_GROUP*/ + +#ifndef IPV6_LEAVE_GROUP +#ifdef IPV6_DROP_MEMBERSHIP +#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP +#endif /* IPV6_DROP_MEMBERSHIP */ +#endif /* ! IPV6_LEAVE_GROUP */ + +#define MSG_OK 0 +#define MSG_NG 1 + +#define OSPF6_SUCCESS 1 +#define OSPF6_FAILURE 0 +#define OSPF6_INVALID -1 + +/* cast macro: XXX - these *must* die, ick ick. */ +#define OSPF6_PROCESS(x) ((struct ospf6 *) (x)) +#define OSPF6_AREA(x) ((struct ospf6_area *) (x)) +#define OSPF6_INTERFACE(x) ((struct ospf6_interface *) (x)) +#define OSPF6_NEIGHBOR(x) ((struct ospf6_neighbor *) (x)) + +/* operation on timeval structure */ +#define timerstring(tv, buf, size) \ + do { \ + if ((tv)->tv_sec / 60 / 60 / 24) \ + snprintf(buf, size, "%lldd%02lld:%02lld:%02lld", \ + (tv)->tv_sec / 60LL / 60 / 24, \ + (tv)->tv_sec / 60LL / 60 % 24, \ + (tv)->tv_sec / 60LL % 60, \ + (tv)->tv_sec % 60LL); \ + else \ + snprintf(buf, size, "%02lld:%02lld:%02lld", \ + (tv)->tv_sec / 60LL / 60 % 24, \ + (tv)->tv_sec / 60LL % 60, \ + (tv)->tv_sec % 60LL); \ + } while (0) + +#define threadtimer_string(now, t, buf, size) \ + do { \ + struct timeval _result; \ + if (!t) \ + snprintf(buf, size, "inactive"); \ + else { \ + timersub(&t->u.sands, &now, &_result); \ + timerstring(&_result, buf, size); \ + } \ + } while (0) + +/* for commands */ +#define OSPF6_AREA_STR "Area information\n" +#define OSPF6_AREA_ID_STR "Area ID (as an IPv4 notation)\n" +#define OSPF6_SPF_STR "Shortest Path First tree information\n" +#define OSPF6_ROUTER_ID_STR "Specify Router-ID\n" +#define OSPF6_LS_ID_STR "Specify Link State ID\n" + +#define OSPF6_CMD_CHECK_VRF(uj, all_vrf, ospf6) \ + do { \ + if (uj == false && all_vrf == false && ospf6 == NULL) { \ + vty_out(vty, "%% OSPFv3 instance not found\n"); \ + return CMD_SUCCESS; \ + } \ + } while (0) + +#define IS_OSPF6_ASBR(O) ((O)->flag & OSPF6_FLAG_ASBR) +#define OSPF6_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) \ + do { \ + if (argv_find(argv, argc, "vrf", &idx_vrf)) { \ + vrf_name = argv[idx_vrf + 1]->arg; \ + all_vrf = strmatch(vrf_name, "all"); \ + } else { \ + vrf_name = VRF_DEFAULT_NAME; \ + } \ + } while (0) + +#define OSPF6_FALSE false +#define OSPF6_TRUE true +#define OSPF6_SUCCESS 1 +#define OSPF6_FAILURE 0 +#define OSPF6_INVALID -1 + +extern struct zebra_privs_t ospf6d_privs; + +/* Event Debug option */ +extern unsigned char conf_debug_ospf6_event; +#define OSPF6_DEBUG_EVENT_ON() (conf_debug_ospf6_event = 1) +#define OSPF6_DEBUG_EVENT_OFF() (conf_debug_ospf6_event = 0) +#define IS_OSPF6_DEBUG_EVENT (conf_debug_ospf6_event) + +/* Function Prototypes */ +extern struct route_node *route_prev(struct route_node *node); + +extern void ospf6_debug(void); +extern void ospf6_init(struct event_loop *master); + +#endif /* OSPF6D_H */ diff --git a/ospf6d/subdir.am b/ospf6d/subdir.am new file mode 100644 index 0000000..5f89af9 --- /dev/null +++ b/ospf6d/subdir.am @@ -0,0 +1,95 @@ +# +# ospf6d +# + +if OSPF6D +noinst_LIBRARIES += ospf6d/libospf6.a +sbin_PROGRAMS += ospf6d/ospf6d +vtysh_daemons += ospf6d +if SNMP +module_LTLIBRARIES += ospf6d/ospf6d_snmp.la +endif +man8 += $(MANBUILD)/frr-ospf6d.8 +endif + +ospf6d_libospf6_a_SOURCES = \ + ospf6d/ospf6_nssa.c \ + ospf6d/ospf6_abr.c \ + ospf6d/ospf6_area.c \ + ospf6d/ospf6_asbr.c \ + ospf6d/ospf6_routemap_nb.c \ + ospf6d/ospf6_routemap_nb_config.c \ + ospf6d/ospf6_bfd.c \ + ospf6d/ospf6_flood.c \ + ospf6d/ospf6_gr.c \ + ospf6d/ospf6_gr_helper.c \ + ospf6d/ospf6_interface.c \ + ospf6d/ospf6_intra.c \ + ospf6d/ospf6_lsa.c \ + ospf6d/ospf6_lsdb.c \ + ospf6d/ospf6_message.c \ + ospf6d/ospf6_neighbor.c \ + ospf6d/ospf6_network.c \ + ospf6d/ospf6_proto.c \ + ospf6d/ospf6_route.c \ + ospf6d/ospf6_spf.c \ + ospf6d/ospf6_top.c \ + ospf6d/ospf6_zebra.c \ + ospf6d/ospf6d.c \ + ospf6d/ospf6_auth_trailer.c \ + # end + +noinst_HEADERS += \ + ospf6d/ospf6_nssa.h \ + ospf6d/ospf6_abr.h \ + ospf6d/ospf6_area.h \ + ospf6d/ospf6_asbr.h \ + ospf6d/ospf6_bfd.h \ + ospf6d/ospf6_flood.h \ + ospf6d/ospf6_gr.h \ + ospf6d/ospf6_interface.h \ + ospf6d/ospf6_intra.h \ + ospf6d/ospf6_lsa.h \ + ospf6d/ospf6_lsdb.h \ + ospf6d/ospf6_message.h \ + ospf6d/ospf6_neighbor.h \ + ospf6d/ospf6_network.h \ + ospf6d/ospf6_proto.h \ + ospf6d/ospf6_route.h \ + ospf6d/ospf6_routemap_nb.h \ + ospf6d/ospf6_spf.h \ + ospf6d/ospf6_top.h \ + ospf6d/ospf6_zebra.h \ + ospf6d/ospf6d.h \ + ospf6d/ospf6_auth_trailer.h \ + # end + +ospf6d_ospf6d_LDADD = ospf6d/libospf6.a lib/libfrr.la $(LIBCAP) +ospf6d_ospf6d_SOURCES = \ + ospf6d/ospf6_main.c \ + # end + +ospf6d_ospf6d_snmp_la_SOURCES = ospf6d/ospf6_snmp.c +ospf6d_ospf6d_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +ospf6d_ospf6d_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +ospf6d_ospf6d_snmp_la_LIBADD = lib/libfrrsnmp.la + +clippy_scan += \ + ospf6d/ospf6d.c \ + ospf6d/ospf6_top.c \ + ospf6d/ospf6_area.c \ + ospf6d/ospf6_asbr.c \ + ospf6d/ospf6_interface.c \ + ospf6d/ospf6_lsa.c \ + ospf6d/ospf6_gr_helper.c \ + ospf6d/ospf6_gr.c \ + ospf6d/ospf6_interface.c \ + ospf6d/ospf6_nssa.c \ + ospf6d/ospf6_route.c \ + ospf6d/ospf6_neighbor.c \ + # end + +nodist_ospf6d_ospf6d_SOURCES = \ + yang/frr-ospf-route-map.yang.c \ + yang/frr-ospf6-route-map.yang.c \ + # end diff --git a/ospfclient/.gitignore b/ospfclient/.gitignore new file mode 100644 index 0000000..9be7045 --- /dev/null +++ b/ospfclient/.gitignore @@ -0,0 +1 @@ +ospfclient diff --git a/ospfclient/AUTHORS b/ospfclient/AUTHORS new file mode 100644 index 0000000..b865c55 --- /dev/null +++ b/ospfclient/AUTHORS @@ -0,0 +1 @@ +Ralph Keller diff --git a/ospfclient/INSTALL b/ospfclient/INSTALL new file mode 100644 index 0000000..b42a17a --- /dev/null +++ b/ospfclient/INSTALL @@ -0,0 +1,182 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. diff --git a/ospfclient/Makefile b/ospfclient/Makefile new file mode 100644 index 0000000..3da2a5b --- /dev/null +++ b/ospfclient/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ospfclient/ospfclient +%: ALWAYS + @$(MAKE) -s -C .. ospfclient/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ospfclient/NEWS b/ospfclient/NEWS new file mode 100644 index 0000000..5b1ec4f --- /dev/null +++ b/ospfclient/NEWS @@ -0,0 +1 @@ +This file contains news. diff --git a/ospfclient/README b/ospfclient/README new file mode 100644 index 0000000..5f6d050 --- /dev/null +++ b/ospfclient/README @@ -0,0 +1,3 @@ +For more information checkout the developer guide at: + +https://docs.frrouting.org/projects/dev-guide/en/latest/ospf-api.html diff --git a/ospfclient/ospf_apiclient.c b/ospfclient/ospf_apiclient.c new file mode 100644 index 0000000..a119300 --- /dev/null +++ b/ospfclient/ospf_apiclient.c @@ -0,0 +1,715 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Client side of OSPF API. + * Copyright (C) 2001, 2002, 2003 Ralph Keller + */ + +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "xref.h" + +/* work around gcc bug 69981, disable MTYPEs in libospf */ +#define _QUAGGA_OSPF_MEMORY_H + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_opaque.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_api.h" +#include "ospfd/ospf_errors.h" + +#include "ospf_apiclient.h" + +XREF_SETUP(); + +DEFINE_MGROUP(OSPFCLIENT, "libospfapiclient"); +DEFINE_MTYPE_STATIC(OSPFCLIENT, OSPF_APICLIENT, "OSPF-API client"); + +/* Backlog for listen */ +#define BACKLOG 5 + +/* ----------------------------------------------------------- + * Forward declarations + * ----------------------------------------------------------- + */ + +void ospf_apiclient_handle_reply(struct ospf_apiclient *oclient, + struct msg *msg); +void ospf_apiclient_handle_update_notify(struct ospf_apiclient *oclient, + struct msg *msg); +void ospf_apiclient_handle_delete_notify(struct ospf_apiclient *oclient, + struct msg *msg); + +/* ----------------------------------------------------------- + * Initialization + * ----------------------------------------------------------- + */ + +static unsigned short ospf_apiclient_getport(void) +{ + struct servent *sp = getservbyname("ospfapi", "tcp"); + + return sp ? ntohs(sp->s_port) : OSPF_API_SYNC_PORT; +} + +/* ----------------------------------------------------------- + * Following are functions for connection management + * ----------------------------------------------------------- + */ + +struct ospf_apiclient *ospf_apiclient_connect(char *host, int syncport) +{ + struct sockaddr_in myaddr_sync; + struct sockaddr_in myaddr_async; + struct sockaddr_in peeraddr; + struct hostent *hp; + struct ospf_apiclient *new; + int size = 0; + unsigned int peeraddrlen; + int async_server_sock; + int fd1, fd2; + int ret; + int on = 1; + + /* There are two connections between the client and the server. + First the client opens a connection for synchronous requests/replies + to the server. The server will accept this connection and + as a reaction open a reverse connection channel for + asynchronous messages. */ + + async_server_sock = socket(AF_INET, SOCK_STREAM, 0); + if (async_server_sock < 0) { + fprintf(stderr, + "ospf_apiclient_connect: creating async socket failed\n"); + return NULL; + } + + /* Prepare socket for asynchronous messages */ + /* Initialize async address structure */ + memset(&myaddr_async, 0, sizeof(myaddr_async)); + myaddr_async.sin_family = AF_INET; + myaddr_async.sin_addr.s_addr = htonl(INADDR_ANY); + myaddr_async.sin_port = htons(syncport + 1); + size = sizeof(struct sockaddr_in); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + myaddr_async.sin_len = size; +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + + /* This is a server socket, reuse addr and port */ + ret = setsockopt(async_server_sock, SOL_SOCKET, SO_REUSEADDR, + (void *)&on, sizeof(on)); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: SO_REUSEADDR failed\n"); + close(async_server_sock); + return NULL; + } + +#ifdef SO_REUSEPORT + ret = setsockopt(async_server_sock, SOL_SOCKET, SO_REUSEPORT, + (void *)&on, sizeof(on)); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: SO_REUSEPORT failed\n"); + close(async_server_sock); + return NULL; + } +#endif /* SO_REUSEPORT */ + + /* Bind socket to address structure */ + ret = bind(async_server_sock, (struct sockaddr *)&myaddr_async, size); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: bind async socket failed\n"); + close(async_server_sock); + return NULL; + } + + /* Wait for reverse channel connection establishment from server */ + ret = listen(async_server_sock, BACKLOG); + if (ret < 0) { + fprintf(stderr, "ospf_apiclient_connect: listen: %s\n", + safe_strerror(errno)); + close(async_server_sock); + return NULL; + } + + /* Make connection for synchronous requests and connect to server */ + /* Resolve address of server */ + hp = gethostbyname(host); + if (!hp) { + fprintf(stderr, "ospf_apiclient_connect: no such host %s\n", + host); + close(async_server_sock); + return NULL; + } + + fd1 = socket(AF_INET, SOCK_STREAM, 0); + if (fd1 < 0) { + close(async_server_sock); + fprintf(stderr, + "ospf_apiclient_connect: creating sync socket failed\n"); + return NULL; + } + + + /* Reuse addr and port */ + ret = setsockopt(fd1, SOL_SOCKET, SO_REUSEADDR, (void *)&on, + sizeof(on)); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: SO_REUSEADDR failed\n"); + close(fd1); + close(async_server_sock); + return NULL; + } + +#ifdef SO_REUSEPORT + ret = setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, (void *)&on, + sizeof(on)); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: SO_REUSEPORT failed\n"); + close(fd1); + close(async_server_sock); + return NULL; + } +#endif /* SO_REUSEPORT */ + + + /* Bind sync socket to address structure. This is needed since we + want the sync port number on a fixed port number. The reverse + async channel will be at this port+1 */ + + memset(&myaddr_sync, 0, sizeof(myaddr_sync)); + myaddr_sync.sin_family = AF_INET; + myaddr_sync.sin_port = htons(syncport); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + myaddr_sync.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + + ret = bind(fd1, (struct sockaddr *)&myaddr_sync, size); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: bind sync socket failed\n"); + close(fd1); + close(async_server_sock); + return NULL; + } + + /* Prepare address structure for connect */ + memcpy(&myaddr_sync.sin_addr, hp->h_addr, hp->h_length); + myaddr_sync.sin_family = AF_INET; + myaddr_sync.sin_port = htons(ospf_apiclient_getport()); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + myaddr_sync.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + + /* Now establish synchronous channel with OSPF daemon */ + ret = connect(fd1, (struct sockaddr *)&myaddr_sync, + sizeof(struct sockaddr_in)); + if (ret < 0) { + fprintf(stderr, + "ospf_apiclient_connect: sync connect failed\n"); + close(async_server_sock); + close(fd1); + return NULL; + } + + /* Accept reverse connection */ + peeraddrlen = sizeof(struct sockaddr_in); + memset(&peeraddr, 0, peeraddrlen); + + fd2 = accept(async_server_sock, (struct sockaddr *)&peeraddr, + &peeraddrlen); + if (fd2 < 0) { + fprintf(stderr, + "ospf_apiclient_connect: accept async failed\n"); + close(async_server_sock); + close(fd1); + close(fd2); + return NULL; + } + + /* Server socket is not needed anymore since we are not accepting more + connections */ + close(async_server_sock); + + /* Create new client-side instance */ + new = XCALLOC(MTYPE_OSPF_APICLIENT, sizeof(struct ospf_apiclient)); + + /* Initialize socket descriptors for sync and async channels */ + new->fd_sync = fd1; + new->fd_async = fd2; + + return new; +} + +int ospf_apiclient_close(struct ospf_apiclient *oclient) +{ + + if (oclient->fd_sync >= 0) { + close(oclient->fd_sync); + } + + if (oclient->fd_async >= 0) { + close(oclient->fd_async); + } + + /* Free client structure */ + XFREE(MTYPE_OSPF_APICLIENT, oclient); + return 0; +} + +/* ----------------------------------------------------------- + * Following are functions to send a request to OSPFd + * ----------------------------------------------------------- + */ + +/* Send synchronous request, wait for reply */ +static int ospf_apiclient_send_request(struct ospf_apiclient *oclient, + struct msg *msg) +{ + uint32_t reqseq; + struct msg_reply *msgreply; + int rc; + + /* NB: Given "msg" is freed inside this function. */ + + /* Remember the sequence number of the request */ + reqseq = ntohl(msg->hdr.msgseq); + + /* Write message to OSPFd */ + rc = msg_write(oclient->fd_sync, msg); + msg_free(msg); + + if (rc < 0) { + return -1; + } + + /* Wait for reply */ /* NB: New "msg" is allocated by "msg_read()". */ + msg = msg_read(oclient->fd_sync); + if (!msg) + return -1; + + assert(msg->hdr.msgtype == MSG_REPLY); + assert(ntohl(msg->hdr.msgseq) == reqseq); + + msgreply = (struct msg_reply *)STREAM_DATA(msg->s); + rc = msgreply->errcode; + msg_free(msg); + + return rc; +} + + +/* ----------------------------------------------------------- + * Helper functions + * ----------------------------------------------------------- + */ + +static uint32_t ospf_apiclient_get_seqnr(void) +{ + static uint32_t seqnr = MIN_SEQ; + uint32_t tmp; + + tmp = seqnr; + /* Increment sequence number */ + if (seqnr < MAX_SEQ) { + seqnr++; + } else { + seqnr = MIN_SEQ; + } + return tmp; +} + +/* ----------------------------------------------------------- + * API to access OSPF daemon by client applications. + * ----------------------------------------------------------- + */ + +/* + * Synchronous request to register opaque type. + */ +int ospf_apiclient_register_opaque_type(struct ospf_apiclient *cl, + uint8_t ltype, uint8_t otype) +{ + struct msg *msg; + int rc; + + /* just put 1 as a sequence number. */ + msg = new_msg_register_opaque_type(ospf_apiclient_get_seqnr(), ltype, + otype); + if (!msg) { + fprintf(stderr, "new_msg_register_opaque_type failed\n"); + return -1; + } + + rc = ospf_apiclient_send_request(cl, msg); + return rc; +} + +/* + * Synchronous request to synchronize with OSPF's LSDB. + * Two steps required: register_event in order to get + * dynamic updates and LSDB_Sync. + */ +int ospf_apiclient_sync_lsdb(struct ospf_apiclient *oclient) +{ + struct msg *msg; + int rc; + struct lsa_filter_type filter; + + filter.typemask = 0xFFFF; /* all LSAs */ + filter.origin = ANY_ORIGIN; + filter.num_areas = 0; /* all Areas. */ + + msg = new_msg_register_event(ospf_apiclient_get_seqnr(), &filter); + if (!msg) { + fprintf(stderr, "new_msg_register_event failed\n"); + return -1; + } + rc = ospf_apiclient_send_request(oclient, msg); + + if (rc != 0) + goto out; + + msg = new_msg_sync_lsdb(ospf_apiclient_get_seqnr(), &filter); + if (!msg) { + fprintf(stderr, "new_msg_sync_lsdb failed\n"); + return -1; + } + rc = ospf_apiclient_send_request(oclient, msg); + +out: + return rc; +} + +/* + * Synchronous request to originate or update an LSA. + */ + +int ospf_apiclient_lsa_originate(struct ospf_apiclient *oclient, + struct in_addr ifaddr, struct in_addr area_id, + uint8_t lsa_type, uint8_t opaque_type, + uint32_t opaque_id, void *opaquedata, + int opaquelen) +{ + struct msg *msg; + int rc; + uint8_t buf[OSPF_MAX_LSA_SIZE]; + struct lsa_header *lsah; + uint32_t tmp; + + /* Validate opaque LSA length */ + if ((size_t)opaquelen > sizeof(buf) - sizeof(struct lsa_header)) { + fprintf(stderr, "opaquelen(%d) is larger than buf size %zu\n", + opaquelen, sizeof(buf)); + return OSPF_API_NOMEMORY; + } + + /* We can only originate opaque LSAs */ + if (!IS_OPAQUE_LSA(lsa_type)) { + fprintf(stderr, "Cannot originate non-opaque LSA type %d\n", + lsa_type); + return OSPF_API_ILLEGALLSATYPE; + } + + /* Make a new LSA from parameters */ + lsah = (struct lsa_header *)buf; + lsah->ls_age = 0; + lsah->options = 0; + lsah->type = lsa_type; + + tmp = SET_OPAQUE_LSID(opaque_type, opaque_id); + lsah->id.s_addr = htonl(tmp); + lsah->adv_router.s_addr = INADDR_ANY; + lsah->ls_seqnum = 0; + lsah->checksum = 0; + lsah->length = htons(sizeof(struct lsa_header) + opaquelen); + + memcpy(((uint8_t *)lsah) + sizeof(struct lsa_header), opaquedata, + opaquelen); + + msg = new_msg_originate_request(ospf_apiclient_get_seqnr(), ifaddr, + area_id, lsah); + if (!msg) { + fprintf(stderr, "new_msg_originate_request failed\n"); + return OSPF_API_NOMEMORY; + } + + rc = ospf_apiclient_send_request(oclient, msg); + return rc; +} + +int ospf_apiclient_lsa_delete(struct ospf_apiclient *oclient, + struct in_addr addr, uint8_t lsa_type, + uint8_t opaque_type, uint32_t opaque_id, + uint8_t flags) +{ + struct msg *msg; + int rc; + + /* Only opaque LSA can be deleted */ + if (!IS_OPAQUE_LSA(lsa_type)) { + fprintf(stderr, "Cannot delete non-opaque LSA type %d\n", + lsa_type); + return OSPF_API_ILLEGALLSATYPE; + } + + /* opaque_id is in host byte order and will be converted + * to network byte order by new_msg_delete_request */ + msg = new_msg_delete_request(ospf_apiclient_get_seqnr(), addr, lsa_type, + opaque_type, opaque_id, flags); + + rc = ospf_apiclient_send_request(oclient, msg); + return rc; +} + +/* ----------------------------------------------------------- + * Following are handlers for messages from OSPF daemon + * ----------------------------------------------------------- + */ + +static void ospf_apiclient_handle_ready(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_ready_notify *r; + r = (struct msg_ready_notify *)STREAM_DATA(msg->s); + + /* Invoke registered callback function. */ + if (oclient->ready_notify) { + (oclient->ready_notify)(r->lsa_type, r->opaque_type, r->addr); + } +} + +static void ospf_apiclient_handle_new_if(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_new_if *n; + n = (struct msg_new_if *)STREAM_DATA(msg->s); + + /* Invoke registered callback function. */ + if (oclient->new_if) { + (oclient->new_if)(n->ifaddr, n->area_id); + } +} + +static void ospf_apiclient_handle_del_if(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_del_if *d; + d = (struct msg_del_if *)STREAM_DATA(msg->s); + + /* Invoke registered callback function. */ + if (oclient->del_if) { + (oclient->del_if)(d->ifaddr); + } +} + +static void ospf_apiclient_handle_ism_change(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_ism_change *m; + m = (struct msg_ism_change *)STREAM_DATA(msg->s); + + /* Invoke registered callback function. */ + if (oclient->ism_change) { + (oclient->ism_change)(m->ifaddr, m->area_id, m->status); + } +} + +static void ospf_apiclient_handle_nsm_change(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_nsm_change *m; + m = (struct msg_nsm_change *)STREAM_DATA(msg->s); + + /* Invoke registered callback function. */ + if (oclient->nsm_change) { + (oclient->nsm_change)(m->ifaddr, m->nbraddr, m->router_id, + m->status); + } +} + +static void ospf_apiclient_handle_lsa_update(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_lsa_change_notify *cn; + struct lsa_header *lsa; + void *p; + uint16_t lsalen; + + cn = (struct msg_lsa_change_notify *)STREAM_DATA(msg->s); + + /* Extract LSA from message */ + lsalen = ntohs(cn->data.length); + if (lsalen > OSPF_MAX_LSA_SIZE) { + flog_warn( + EC_OSPF_LARGE_LSA, + "%s: message received size: %d is greater than a LSA size: %d", + __func__, lsalen, OSPF_MAX_LSA_SIZE); + return; + } + + p = XMALLOC(MTYPE_OSPF_APICLIENT, lsalen); + + memcpy(p, &(cn->data), lsalen); + lsa = p; + + /* Invoke registered update callback function */ + if (oclient->update_notify) { + (oclient->update_notify)(cn->ifaddr, cn->area_id, + cn->is_self_originated, lsa); + } + + /* free memory allocated by ospf apiclient library */ + XFREE(MTYPE_OSPF_APICLIENT, p); +} + +static void ospf_apiclient_handle_lsa_delete(struct ospf_apiclient *oclient, + struct msg *msg) +{ + struct msg_lsa_change_notify *cn; + struct lsa_header *lsa; + void *p; + uint16_t lsalen; + + cn = (struct msg_lsa_change_notify *)STREAM_DATA(msg->s); + + /* Extract LSA from message */ + lsalen = ntohs(cn->data.length); + if (lsalen > OSPF_MAX_LSA_SIZE) { + flog_warn( + EC_OSPF_LARGE_LSA, + "%s: message received size: %d is greater than a LSA size: %d", + __func__, lsalen, OSPF_MAX_LSA_SIZE); + return; + } + + p = XMALLOC(MTYPE_OSPF_APICLIENT, lsalen); + + memcpy(p, &(cn->data), lsalen); + lsa = p; + + /* Invoke registered update callback function */ + if (oclient->delete_notify) { + (oclient->delete_notify)(cn->ifaddr, cn->area_id, + cn->is_self_originated, lsa); + } + + /* free memory allocated by ospf apiclient library */ + XFREE(MTYPE_OSPF_APICLIENT, p); +} + +static void ospf_apiclient_msghandle(struct ospf_apiclient *oclient, + struct msg *msg) +{ + /* Call message handler function. */ + switch (msg->hdr.msgtype) { + case MSG_READY_NOTIFY: + ospf_apiclient_handle_ready(oclient, msg); + break; + case MSG_NEW_IF: + ospf_apiclient_handle_new_if(oclient, msg); + break; + case MSG_DEL_IF: + ospf_apiclient_handle_del_if(oclient, msg); + break; + case MSG_ISM_CHANGE: + ospf_apiclient_handle_ism_change(oclient, msg); + break; + case MSG_NSM_CHANGE: + ospf_apiclient_handle_nsm_change(oclient, msg); + break; + case MSG_LSA_UPDATE_NOTIFY: + ospf_apiclient_handle_lsa_update(oclient, msg); + break; + case MSG_LSA_DELETE_NOTIFY: + ospf_apiclient_handle_lsa_delete(oclient, msg); + break; + default: + fprintf(stderr, + "ospf_apiclient_read: Unknown message type: %d\n", + msg->hdr.msgtype); + break; + } +} + +/* ----------------------------------------------------------- + * Callback handler registration + * ----------------------------------------------------------- + */ + +void ospf_apiclient_register_callback( + struct ospf_apiclient *oclient, + void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, + struct in_addr addr), + void (*new_if)(struct in_addr ifaddr, struct in_addr area_id), + void (*del_if)(struct in_addr ifaddr), + void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t status), + void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, + struct in_addr router_id, uint8_t status), + void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t self_origin, struct lsa_header *lsa), + void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t self_origin, struct lsa_header *lsa)) +{ + assert(oclient); + assert(update_notify); + + /* Register callback function */ + oclient->ready_notify = ready_notify; + oclient->new_if = new_if; + oclient->del_if = del_if; + oclient->ism_change = ism_change; + oclient->nsm_change = nsm_change; + oclient->update_notify = update_notify; + oclient->delete_notify = delete_notify; +} + +/* ----------------------------------------------------------- + * Asynchronous message handling + * ----------------------------------------------------------- + */ + +int ospf_apiclient_handle_async(struct ospf_apiclient *oclient) +{ + struct msg *msg; + + /* Get a message */ + msg = msg_read(oclient->fd_async); + + if (!msg) { + /* Connection broke down */ + return -1; + } + + /* Handle message */ + ospf_apiclient_msghandle(oclient, msg); + + /* Don't forget to free this message */ + msg_free(msg); + + return 0; +} diff --git a/ospfclient/ospf_apiclient.h b/ospfclient/ospf_apiclient.h new file mode 100644 index 0000000..c90b1fb --- /dev/null +++ b/ospfclient/ospf_apiclient.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Client side of OSPF API. + * Copyright (C) 2001, 2002, 2003 Ralph Keller + */ + +#ifndef _OSPF_APICLIENT_H +#define _OSPF_APICLIENT_H + +/* Structure for the OSPF API client */ +struct ospf_apiclient { + + /* Sockets for sync requests and async notifications */ + int fd_sync; + int fd_async; + + /* Pointer to callback functions */ + void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, + struct in_addr addr); + void (*new_if)(struct in_addr ifaddr, struct in_addr area_id); + void (*del_if)(struct in_addr ifaddr); + void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t status); + void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, + struct in_addr router_id, uint8_t status); + void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t self_origin, struct lsa_header *lsa); + void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t self_origin, struct lsa_header *lsa); +}; + + +/* --------------------------------------------------------- + * API function prototypes. + * --------------------------------------------------------- */ + +/* Open connection to OSPF daemon. Two ports will be allocated on + client, sync channel at syncport and reverse channel at syncport+1 */ +struct ospf_apiclient *ospf_apiclient_connect(char *host, int syncport); + +/* Shutdown connection to OSPF daemon. */ +int ospf_apiclient_close(struct ospf_apiclient *oclient); + +/* Synchronous request to register opaque type. */ +int ospf_apiclient_register_opaque_type(struct ospf_apiclient *oclient, + uint8_t ltype, uint8_t otype); + +/* Synchronous request to register event mask. */ +int ospf_apiclient_register_events(struct ospf_apiclient *oclient, + uint32_t mask); + +/* Register callback functions.*/ +void ospf_apiclient_register_callback( + struct ospf_apiclient *oclient, + void (*ready_notify)(uint8_t lsa_type, uint8_t opaque_type, + struct in_addr addr), + void (*new_if)(struct in_addr ifaddr, struct in_addr area_id), + void (*del_if)(struct in_addr ifaddr), + void (*ism_change)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t status), + void (*nsm_change)(struct in_addr ifaddr, struct in_addr nbraddr, + struct in_addr router_id, uint8_t status), + void (*update_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t selforig, struct lsa_header *lsa), + void (*delete_notify)(struct in_addr ifaddr, struct in_addr area_id, + uint8_t selforig, struct lsa_header *lsa)); + +/* Synchronous request to synchronize LSDB. */ +int ospf_apiclient_sync_lsdb(struct ospf_apiclient *oclient); + +/* Synchronous request to originate or update opaque LSA. */ +int ospf_apiclient_lsa_originate(struct ospf_apiclient *oclient, + struct in_addr ifaddr, struct in_addr area_id, + uint8_t lsa_type, uint8_t opaque_type, + uint32_t opaque_id, void *opaquedata, + int opaquelen); + + +/* Synchronous request to delete opaque LSA. Parameter opaque_id is in + host byte order */ +int ospf_apiclient_lsa_delete(struct ospf_apiclient *oclient, + struct in_addr addr, uint8_t lsa_type, + uint8_t opaque_type, uint32_t opaque_id, + uint8_t flags); + +/* Fetch async message and handle it */ +int ospf_apiclient_handle_async(struct ospf_apiclient *oclient); + +#endif /* _OSPF_APICLIENT_H */ diff --git a/ospfclient/ospfclient.c b/ospfclient/ospfclient.c new file mode 100644 index 0000000..24ff085 --- /dev/null +++ b/ospfclient/ospfclient.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* This file is part of Quagga. + */ + +/* + * Simple program to demonstrate how OSPF API can be used. This + * application retrieves the LSDB from the OSPF daemon and then + * originates, updates and finally deletes an application-specific + * opaque LSA. You can use this application as a template when writing + * your own application. + */ + +/* The following includes are needed in all OSPF API client + applications. */ + +#include +#include "prefix.h" /* needed by ospf_asbr.h */ +#include "privs.h" +#include "log.h" +#include "lib/printfrr.h" + +/* work around gcc bug 69981, disable MTYPEs in libospf */ +#define _QUAGGA_OSPF_MEMORY_H + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_opaque.h" +#include "ospfd/ospf_api.h" +#include "ospf_apiclient.h" + +/* privileges struct. + * set cap_num_* and uid/gid to nothing to use NULL privs + * as ospfapiclient links in libospf.a which uses privs. + */ +struct zebra_privs_t ospfd_privs = {.user = NULL, + .group = NULL, + .cap_num_p = 0, + .cap_num_i = 0}; + +/* The following includes are specific to this application. For + example it uses threads from libfrr, however your application is + free to use any thread library (like pthreads). */ + +#include "ospfd/ospf_dump.h" /* for ospf_lsa_header_dump */ +#include "frrevent.h" +#include "log.h" + +/* Local portnumber for async channel. Note that OSPF API library will also + allocate a sync channel at ASYNCPORT+1. */ +#define ASYNCPORT 4000 + +/* Master thread */ +struct event_loop *master; + +/* Global variables */ +struct ospf_apiclient *oclient; +char **args; + +/* Our opaque LSAs have the following format. */ +struct my_opaque_lsa { + struct lsa_header hdr; /* include common LSA header */ + uint8_t data[4]; /* our own data format then follows here */ +}; + + +/* --------------------------------------------------------- + * Threads for asynchronous messages and LSA update/delete + * --------------------------------------------------------- + */ + +static void lsa_delete(struct event *t) +{ + struct ospf_apiclient *oclient; + struct in_addr area_id; + int rc; + + oclient = EVENT_ARG(t); + + rc = inet_aton(args[6], &area_id); + if (rc <= 0) { + printf("Address Specified: %s is invalid\n", args[6]); + return; + } + + printf("Deleting LSA... "); + rc = ospf_apiclient_lsa_delete(oclient, area_id, + atoi(args[2]), /* lsa type */ + atoi(args[3]), /* opaque type */ + atoi(args[4]), /* opaque ID */ + 0); /* send data in withdrawals */ + printf("done, return code is = %d\n", rc); +} + +static void lsa_inject(struct event *t) +{ + struct ospf_apiclient *cl; + struct in_addr ifaddr; + struct in_addr area_id; + uint8_t lsa_type; + uint8_t opaque_type; + uint32_t opaque_id; + void *opaquedata; + int opaquelen; + + static uint32_t counter = 1; /* Incremented each time invoked */ + int rc; + + cl = EVENT_ARG(t); + + rc = inet_aton(args[5], &ifaddr); + if (rc <= 0) { + printf("Ifaddr specified %s is invalid\n", args[5]); + return; + } + + rc = inet_aton(args[6], &area_id); + if (rc <= 0) { + printf("Area ID specified %s is invalid\n", args[6]); + return; + } + lsa_type = atoi(args[2]); + opaque_type = atoi(args[3]); + opaque_id = atoi(args[4]); + opaquedata = &counter; + opaquelen = sizeof(uint32_t); + + printf("Originating/updating LSA with counter=%d... ", counter); + rc = ospf_apiclient_lsa_originate(cl, ifaddr, area_id, lsa_type, + opaque_type, opaque_id, opaquedata, + opaquelen); + + printf("done, return code is %d\n", rc); + + counter++; +} + + +/* This thread handles asynchronous messages coming in from the OSPF + API server */ +static void lsa_read(struct event *thread) +{ + struct ospf_apiclient *oclient; + int fd; + int ret; + + printf("lsa_read called\n"); + + oclient = EVENT_ARG(thread); + fd = EVENT_FD(thread); + + /* Handle asynchronous message */ + ret = ospf_apiclient_handle_async(oclient); + if (ret < 0) { + printf("Connection closed, exiting..."); + exit(0); + } + + /* Reschedule read thread */ + event_add_read(master, lsa_read, oclient, fd, NULL); +} + +/* --------------------------------------------------------- + * Callback functions for asynchronous events + * --------------------------------------------------------- + */ + +static void lsa_update_callback(struct in_addr ifaddr, struct in_addr area_id, + uint8_t is_self_originated, + struct lsa_header *lsa) +{ + printf("lsa_update_callback: "); + printfrr("ifaddr: %pI4 ", &ifaddr); + printfrr("area: %pI4\n", &area_id); + printf("is_self_origin: %u\n", is_self_originated); + + /* It is important to note that lsa_header does indeed include the + header and the LSA payload. To access the payload, first check + the LSA type and then typecast lsa into the corresponding type, + e.g.: + + if (lsa->type == OSPF_ROUTER_LSA) { + struct router_lsa *rl = (struct router_lsa) lsa; + ... + uint16_t links = rl->links; + ... + } + */ + + ospf_lsa_header_dump(lsa); +} + +static void lsa_delete_callback(struct in_addr ifaddr, struct in_addr area_id, + uint8_t is_self_originated, + struct lsa_header *lsa) +{ + printf("lsa_delete_callback: "); + printf("ifaddr: %pI4 ", &ifaddr); + printf("area: %pI4\n", &area_id); + printf("is_self_origin: %u\n", is_self_originated); + + ospf_lsa_header_dump(lsa); +} + +static void ready_callback(uint8_t lsa_type, uint8_t opaque_type, + struct in_addr addr) +{ + printfrr("ready_callback: lsa_type: %d opaque_type: %d addr=%pI4\n", + lsa_type, opaque_type, &addr); + + /* Schedule opaque LSA originate in 5 secs */ + event_add_timer(master, lsa_inject, oclient, 5, NULL); + + /* Schedule opaque LSA update with new value */ + event_add_timer(master, lsa_inject, oclient, 10, NULL); + + /* Schedule delete */ + event_add_timer(master, lsa_delete, oclient, 30, NULL); +} + +static void new_if_callback(struct in_addr ifaddr, struct in_addr area_id) +{ + printfrr("new_if_callback: ifaddr: %pI4 ", &ifaddr); + printfrr("area_id: %pI4\n", &area_id); +} + +static void del_if_callback(struct in_addr ifaddr) +{ + printfrr("new_if_callback: ifaddr: %pI4\n ", &ifaddr); +} + +static void ism_change_callback(struct in_addr ifaddr, struct in_addr area_id, + uint8_t state) +{ + printfrr("ism_change: ifaddr: %pI4 ", &ifaddr); + printfrr("area_id: %pI4\n", &area_id); + printf("state: %d [%s]\n", state, + lookup_msg(ospf_ism_state_msg, state, NULL)); +} + +static void nsm_change_callback(struct in_addr ifaddr, struct in_addr nbraddr, + struct in_addr router_id, uint8_t state) +{ + printfrr("nsm_change: ifaddr: %pI4 ", &ifaddr); + printfrr("nbraddr: %pI4\n", &nbraddr); + printfrr("router_id: %pI4\n", &router_id); + printf("state: %d [%s]\n", state, + lookup_msg(ospf_nsm_state_msg, state, NULL)); +} + + +/* --------------------------------------------------------- + * Main program + * --------------------------------------------------------- + */ + +static int usage(void) +{ + printf("Usage: ospfclient \n"); + printf("where ospfd : router where API-enabled OSPF daemon is running\n"); + printf(" lsatype : either 9, 10, or 11 depending on flooding scope\n"); + printf(" opaquetype: 0-255 (e.g., experimental applications use > 128)\n"); + printf(" opaqueid : arbitrary application instance (24 bits)\n"); + printf(" ifaddr : interface IP address (for type 9) otherwise ignored\n"); + printf(" areaid : area in IP address format (for type 10) otherwise ignored\n"); + + exit(1); +} + +int main(int argc, char *argv[]) +{ + struct event thread; + + args = argv; + + /* ospfclient should be started with the following arguments: + * + * (1) host (2) lsa_type (3) opaque_type (4) opaque_id (5) if_addr + * (6) area_id + * + * host: name or IP of host where ospfd is running + * lsa_type: 9, 10, or 11 + * opaque_type: 0-255 (e.g., experimental applications use > 128) + * opaque_id: arbitrary application instance (24 bits) + * if_addr: interface IP address (for type 9) otherwise ignored + * area_id: area in IP address format (for type 10) otherwise ignored + */ + + if (argc != 7) { + usage(); + } + + /* Initialization */ + zprivs_preinit(&ospfd_privs); + zprivs_init(&ospfd_privs); + master = event_master_create(NULL); + + /* Open connection to OSPF daemon */ + oclient = ospf_apiclient_connect(args[1], ASYNCPORT); + if (!oclient) { + printf("Connecting to OSPF daemon on %s failed!\n", args[1]); + exit(1); + } + + /* Register callback functions. */ + ospf_apiclient_register_callback( + oclient, ready_callback, new_if_callback, del_if_callback, + ism_change_callback, nsm_change_callback, lsa_update_callback, + lsa_delete_callback); + + /* Register LSA type and opaque type. */ + ospf_apiclient_register_opaque_type(oclient, atoi(args[2]), + atoi(args[3])); + + /* Synchronize database with OSPF daemon. */ + ospf_apiclient_sync_lsdb(oclient); + + /* Schedule thread that handles asynchronous messages */ + event_add_read(master, lsa_read, oclient, oclient->fd_async, NULL); + + /* Now connection is established, run loop */ + while (1) { + event_fetch(master, &thread); + event_call(&thread); + } + + /* Never reached */ + return 0; +} diff --git a/ospfclient/ospfclient.py b/ospfclient/ospfclient.py new file mode 100755 index 0000000..7477ef8 --- /dev/null +++ b/ospfclient/ospfclient.py @@ -0,0 +1,1227 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# December 22 2021, Christian Hopps +# +# Copyright 2021-2022, LabN Consulting, L.L.C. +# + +import argparse +import asyncio +import errno +import logging +import socket +import struct +import sys +from asyncio import Event, Lock +from ipaddress import ip_address as ip + +FMT_APIMSGHDR = ">BBHL" +FMT_APIMSGHDR_SIZE = struct.calcsize(FMT_APIMSGHDR) + +FMT_LSA_FILTER = ">HBB" # + plus x"I" areas +LSAF_ORIGIN_NON_SELF = 0 +LSAF_ORIGIN_SELF = 1 +LSAF_ORIGIN_ANY = 2 + +FMT_LSA_HEADER = ">HBBIILHH" +FMT_LSA_HEADER_SIZE = struct.calcsize(FMT_LSA_HEADER) + +# ------------------------ +# Messages to OSPF daemon. +# ------------------------ + +MSG_REGISTER_OPAQUETYPE = 1 +MSG_UNREGISTER_OPAQUETYPE = 2 +MSG_REGISTER_EVENT = 3 +MSG_SYNC_LSDB = 4 +MSG_ORIGINATE_REQUEST = 5 +MSG_DELETE_REQUEST = 6 +MSG_SYNC_REACHABLE = 7 +MSG_SYNC_ISM = 8 +MSG_SYNC_NSM = 9 +MSG_SYNC_ROUTER_ID = 19 + +smsg_info = { + MSG_REGISTER_OPAQUETYPE: ("REGISTER_OPAQUETYPE", "BBxx"), + MSG_UNREGISTER_OPAQUETYPE: ("UNREGISTER_OPAQUETYPE", "BBxx"), + MSG_REGISTER_EVENT: ("REGISTER_EVENT", FMT_LSA_FILTER), + MSG_SYNC_LSDB: ("SYNC_LSDB", FMT_LSA_FILTER), + MSG_ORIGINATE_REQUEST: ("ORIGINATE_REQUEST", ">II" + FMT_LSA_HEADER[1:]), + MSG_DELETE_REQUEST: ("DELETE_REQUEST", ">IBBxBL"), + MSG_SYNC_REACHABLE: ("MSG_SYNC_REACHABLE", ""), + MSG_SYNC_ISM: ("MSG_SYNC_ISM", ""), + MSG_SYNC_NSM: ("MSG_SYNC_NSM", ""), + MSG_SYNC_ROUTER_ID: ("MSG_SYNC_ROUTER_ID", ""), +} + +# OSPF API MSG Delete Flag. +OSPF_API_DEL_ZERO_LEN_LSA = 0x01 # send withdrawal with no LSA data + +# -------------------------- +# Messages from OSPF daemon. +# -------------------------- + +MSG_REPLY = 10 +MSG_READY_NOTIFY = 11 +MSG_LSA_UPDATE_NOTIFY = 12 +MSG_LSA_DELETE_NOTIFY = 13 +MSG_NEW_IF = 14 +MSG_DEL_IF = 15 +MSG_ISM_CHANGE = 16 +MSG_NSM_CHANGE = 17 +MSG_REACHABLE_CHANGE = 18 +MSG_ROUTER_ID_CHANGE = 20 + +amsg_info = { + MSG_REPLY: ("REPLY", "bxxx"), + MSG_READY_NOTIFY: ("READY_NOTIFY", ">BBxxI"), + MSG_LSA_UPDATE_NOTIFY: ("LSA_UPDATE_NOTIFY", ">IIBxxx" + FMT_LSA_HEADER[1:]), + MSG_LSA_DELETE_NOTIFY: ("LSA_DELETE_NOTIFY", ">IIBxxx" + FMT_LSA_HEADER[1:]), + MSG_NEW_IF: ("NEW_IF", ">II"), + MSG_DEL_IF: ("DEL_IF", ">I"), + MSG_ISM_CHANGE: ("ISM_CHANGE", ">IIBxxx"), + MSG_NSM_CHANGE: ("NSM_CHANGE", ">IIIBxxx"), + MSG_REACHABLE_CHANGE: ("REACHABLE_CHANGE", ">HH"), + MSG_ROUTER_ID_CHANGE: ("ROUTER_ID_CHANGE", ">I"), +} + +OSPF_API_OK = 0 +OSPF_API_NOSUCHINTERFACE = -1 +OSPF_API_NOSUCHAREA = -2 +OSPF_API_NOSUCHLSA = -3 +OSPF_API_ILLEGALLSATYPE = -4 +OSPF_API_OPAQUETYPEINUSE = -5 +OSPF_API_OPAQUETYPENOTREGISTERED = -6 +OSPF_API_NOTREADY = -7 +OSPF_API_NOMEMORY = -8 +OSPF_API_ERROR = -9 +OSPF_API_UNDEF = -10 + +msg_errname = { + OSPF_API_OK: "OSPF_API_OK", + OSPF_API_NOSUCHINTERFACE: "OSPF_API_NOSUCHINTERFACE", + OSPF_API_NOSUCHAREA: "OSPF_API_NOSUCHAREA", + OSPF_API_NOSUCHLSA: "OSPF_API_NOSUCHLSA", + OSPF_API_ILLEGALLSATYPE: "OSPF_API_ILLEGALLSATYPE", + OSPF_API_OPAQUETYPEINUSE: "OSPF_API_OPAQUETYPEINUSE", + OSPF_API_OPAQUETYPENOTREGISTERED: "OSPF_API_OPAQUETYPENOTREGISTERED", + OSPF_API_NOTREADY: "OSPF_API_NOTREADY", + OSPF_API_NOMEMORY: "OSPF_API_NOMEMORY", + OSPF_API_ERROR: "OSPF_API_ERROR", + OSPF_API_UNDEF: "OSPF_API_UNDEF", +} + +# msg_info = {**smsg_info, **amsg_info} +msg_info = {} +msg_info.update(smsg_info) +msg_info.update(amsg_info) +msg_name = {k: v[0] for k, v in msg_info.items()} +msg_fmt = {k: v[1] for k, v in msg_info.items()} +msg_size = {k: struct.calcsize(v) for k, v in msg_fmt.items()} + + +def api_msgname(mt): + return msg_name.get(mt, str(mt)) + + +def api_errname(ecode): + return msg_errname.get(ecode, str(ecode)) + + +# ------------------- +# API Semantic Errors +# ------------------- + + +class APIError(Exception): + pass + + +class MsgTypeError(Exception): + pass + + +class SeqNumError(Exception): + pass + + +# --------- +# LSA Types +# --------- + +LSA_TYPE_UNKNOWN = 0 +LSA_TYPE_ROUTER = 1 +LSA_TYPE_NETWORK = 2 +LSA_TYPE_SUMMARY = 3 +LSA_TYPE_ASBR_SUMMARY = 4 +LSA_TYPE_AS_EXTERNAL = 5 +LSA_TYPE_GROUP_MEMBER = 6 +LSA_TYPE_AS_NSSA = 7 +LSA_TYPE_EXTERNAL_ATTRIBUTES = 8 +LSA_TYPE_OPAQUE_LINK = 9 +LSA_TYPE_OPAQUE_AREA = 10 +LSA_TYPE_OPAQUE_AS = 11 + + +def lsa_typename(lsa_type): + names = { + LSA_TYPE_ROUTER: "LSA:ROUTER", + LSA_TYPE_NETWORK: "LSA:NETWORK", + LSA_TYPE_SUMMARY: "LSA:SUMMARY", + LSA_TYPE_ASBR_SUMMARY: "LSA:ASBR_SUMMARY", + LSA_TYPE_AS_EXTERNAL: "LSA:AS_EXTERNAL", + LSA_TYPE_GROUP_MEMBER: "LSA:GROUP_MEMBER", + LSA_TYPE_AS_NSSA: "LSA:AS_NSSA", + LSA_TYPE_EXTERNAL_ATTRIBUTES: "LSA:EXTERNAL_ATTRIBUTES", + LSA_TYPE_OPAQUE_LINK: "LSA:OPAQUE_LINK", + LSA_TYPE_OPAQUE_AREA: "LSA:OPAQUE_AREA", + LSA_TYPE_OPAQUE_AS: "LSA:OPAQUE_AS", + } + return names.get(lsa_type, str(lsa_type)) + + +# ------------------------------ +# Interface State Machine States +# ------------------------------ + +ISM_DEPENDUPON = 0 +ISM_DOWN = 1 +ISM_LOOPBACK = 2 +ISM_WAITING = 3 +ISM_POINTTOPOINT = 4 +ISM_DROTHER = 5 +ISM_BACKUP = 6 +ISM_DR = 7 + + +def ism_name(state): + names = { + ISM_DEPENDUPON: "ISM_DEPENDUPON", + ISM_DOWN: "ISM_DOWN", + ISM_LOOPBACK: "ISM_LOOPBACK", + ISM_WAITING: "ISM_WAITING", + ISM_POINTTOPOINT: "ISM_POINTTOPOINT", + ISM_DROTHER: "ISM_DROTHER", + ISM_BACKUP: "ISM_BACKUP", + ISM_DR: "ISM_DR", + } + return names.get(state, str(state)) + + +# ----------------------------- +# Neighbor State Machine States +# ----------------------------- + +NSM_DEPENDUPON = 0 +NSM_DELETED = 1 +NSM_DOWN = 2 +NSM_ATTEMPT = 3 +NSM_INIT = 4 +NSM_TWOWAY = 5 +NSM_EXSTART = 6 +NSM_EXCHANGE = 7 +NSM_LOADING = 8 +NSM_FULL = 9 + + +def nsm_name(state): + names = { + NSM_DEPENDUPON: "NSM_DEPENDUPON", + NSM_DELETED: "NSM_DELETED", + NSM_DOWN: "NSM_DOWN", + NSM_ATTEMPT: "NSM_ATTEMPT", + NSM_INIT: "NSM_INIT", + NSM_TWOWAY: "NSM_TWOWAY", + NSM_EXSTART: "NSM_EXSTART", + NSM_EXCHANGE: "NSM_EXCHANGE", + NSM_LOADING: "NSM_LOADING", + NSM_FULL: "NSM_FULL", + } + return names.get(state, str(state)) + + +class WithNothing: + "An object that does nothing when used with `with` statement." + + async def __aenter__(self): + return + + async def __aexit__(self, *args, **kwargs): + return + + +# -------------- +# Client Classes +# -------------- + + +class OspfApiClient: + def __str__(self): + return "OspfApiClient({})".format(self.server) + + @staticmethod + def _get_bound_sockets(port): + s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + try: + s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + s1.bind(("", port)) + s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + try: + s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + # s2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + s2.bind(("", port + 1)) + return s1, s2 + except Exception: + s2.close() + raise + except Exception: + s1.close() + raise + + def __init__(self, server="localhost", handlers=None): + """A client connection to OSPF Daemon using the OSPF API + + The client object is not created in a connected state. To connect to the server + the `connect` method should be called. If an error is encountered when sending + messages to the server an exception will be raised and the connection will be + closed. When this happens `connect` may be called again to restore the + connection. + + Args: + server: hostname or IP address of server default is "localhost" + handlers: dict of message handlers, the key is the API message + type, the value is a function. The functions signature is: + `handler(msg_type, msg, msg_extra, *params)`, where `msg` is the + message data after the API header, `*params` will be the + unpacked message values, and msg_extra are any bytes beyond the + fixed parameters of the message. + Raises: + Will raise exceptions for failures with various `socket` modules + functions such as `socket.socket`, `socket.setsockopt`, `socket.bind`. + """ + self._seq = 0 + self._s = None + self._as = None + self._ls = None + self._ar = self._r = self._w = None + self.server = server + self.handlers = handlers if handlers is not None else dict() + self.write_lock = Lock() + + # try and get consecutive 2 ports + PORTSTART = 49152 + PORTEND = 65534 + for port in range(PORTSTART, PORTEND + 2, 2): + try: + logging.debug("%s: binding to ports %s, %s", self, port, port + 1) + self._s, self._ls = self._get_bound_sockets(port) + break + except OSError as error: + if error.errno != errno.EADDRINUSE or port == PORTEND: + logging.warning("%s: binding port %s error %s", self, port, error) + raise + logging.debug("%s: ports %s, %s in use.", self, port, port + 1) + else: + assert False, "Should not reach this code execution point" + + async def _connect_locked(self): + logging.debug("%s: connect to OSPF API", self) + + loop = asyncio.get_event_loop() + + self._ls.listen() + try: + logging.debug("%s: connecting sync socket to server", self) + await loop.sock_connect(self._s, (self.server, 2607)) + + logging.debug("%s: accepting connect from server", self) + self._as, _ = await loop.sock_accept(self._ls) + except Exception: + await self._close_locked() + raise + + logging.debug("%s: success", self) + self._r, self._w = await asyncio.open_connection(sock=self._s) + self._ar, _ = await asyncio.open_connection(sock=self._as) + self._seq = 1 + + async def connect(self): + async with self.write_lock: + await self._connect_locked() + + @property + def closed(self): + "True if the connection is closed." + return self._seq == 0 + + async def _close_locked(self): + logging.debug("%s: closing", self) + if self._s: + if self._w: + self._w.close() + await self._w.wait_closed() + self._w = None + else: + self._s.close() + self._s = None + self._r = None + assert self._w is None + if self._as: + self._as.close() + self._as = None + self._ar = None + if self._ls: + self._ls.close() + self._ls = None + self._seq = 0 + + async def close(self): + async with self.write_lock: + await self._close_locked() + + @staticmethod + async def _msg_read(r, expseq=-1): + """Read an OSPF API message from the socket `r` + + Args: + r: socket to read msg from + expseq: sequence number to expect or -1 for any. + Raises: + Will raise exceptions for failures with various `socket` modules, + Additionally may raise SeqNumError if unexpected seqnum is received. + """ + try: + mh = await r.readexactly(FMT_APIMSGHDR_SIZE) + v, mt, l, seq = struct.unpack(FMT_APIMSGHDR, mh) + if v != 1: + raise Exception("received unexpected OSPF API version {}".format(v)) + if expseq == -1: + logging.debug("_msg_read: got seq: 0x%x on async read", seq) + elif seq != expseq: + raise SeqNumError("rx {} != {}".format(seq, expseq)) + msg = await r.readexactly(l) if l else b"" + return mt, msg + except asyncio.IncompleteReadError: + raise EOFError + + async def msg_read(self): + """Read a message from the async notify channel. + + Raises: + May raise exceptions for failures with various `socket` modules. + """ + return await OspfApiClient._msg_read(self._ar, -1) + + async def msg_send(self, mt, mp): + """Send a message to OSPF API and wait for error code reply. + + Args: + mt: the messaage type + mp: the message payload + Returns: + error: an OSPF_API_XXX error code, 0 for OK. + Raises: + Raises SeqNumError if the synchronous reply is the wrong sequence number; + MsgTypeError if the synchronous reply is not MSG_REPLY. Also, + may raise exceptions for failures with various `socket` modules, + + The connection will be closed. + """ + logging.debug("SEND: %s: sending %s seq 0x%x", self, api_msgname(mt), self._seq) + mh = struct.pack(FMT_APIMSGHDR, 1, mt, len(mp), self._seq) + + seq = self._seq + self._seq = seq + 1 + + try: + async with self.write_lock: + self._w.write(mh + mp) + await self._w.drain() + mt, mp = await OspfApiClient._msg_read(self._r, seq) + + if mt != MSG_REPLY: + raise MsgTypeError( + "rx {} != {}".format(api_msgname(mt), api_msgname(MSG_REPLY)) + ) + + return struct.unpack(msg_fmt[MSG_REPLY], mp)[0] + except Exception: + # We've written data with a sequence number + await self.close() + raise + + async def msg_send_raises(self, mt, mp=b"\x00" * 4): + """Send a message to OSPF API and wait for error code reply. + + Args: + mt: the messaage type + mp: the message payload + Raises: + APIError if the server replies with an error. + + Also may raise exceptions for failures with various `socket` modules, + as well as MsgTypeError if the synchronous reply is incorrect. + The connection will be closed for these non-API error exceptions. + """ + ecode = await self.msg_send(mt, mp) + if ecode: + raise APIError("{} error {}".format(api_msgname(mt), api_errname(ecode))) + + async def handle_async_msg(self, mt, msg): + if mt not in msg_fmt: + logging.debug("RECV: %s: unknown async msg type %s", self, mt) + return + + fmt = msg_fmt[mt] + sz = msg_size[mt] + tup = struct.unpack(fmt, msg[:sz]) + extra = msg[sz:] + + if mt not in self.handlers: + logging.debug( + "RECV: %s: no handlers for msg type %s", self, api_msgname(mt) + ) + return + + logging.debug("RECV: %s: calling handler for %s", self, api_msgname(mt)) + await self.handlers[mt](mt, msg, extra, *tup) + + # + # Client to Server Messaging + # + @staticmethod + def lsa_type_mask(*lsa_types): + "Return a 16 bit mask for each LSA type passed." + if not lsa_types: + return 0xFFFF + mask = 0 + for t in lsa_types: + assert 0 < t < 16, "LSA type {} out of range [1, 15]".format(t) + mask |= 1 << t + return mask + + @staticmethod + def lsa_filter(origin, areas, lsa_types): + """Return an LSA filter. + + Return the filter message bytes based on `origin` the `areas` list and the LSAs + types in the `lsa_types` list. + """ + mask = OspfApiClient.lsa_type_mask(*lsa_types) + narea = len(areas) + fmt = FMT_LSA_FILTER + ("{}I".format(narea) if narea else "") + # lsa type mask, origin, number of areas, each area + return struct.pack(fmt, mask, origin, narea, *areas) + + async def req_lsdb_sync(self): + "Register for all LSA notifications and request an LSDB synchronoization." + logging.debug("SEND: %s: request LSDB events", self) + mp = OspfApiClient.lsa_filter(LSAF_ORIGIN_ANY, [], []) + await self.msg_send_raises(MSG_REGISTER_EVENT, mp) + + logging.debug("SEND: %s: request LSDB sync", self) + await self.msg_send_raises(MSG_SYNC_LSDB, mp) + + async def req_reachable_routers(self): + "Request a dump of all reachable routers." + logging.debug("SEND: %s: request reachable changes", self) + await self.msg_send_raises(MSG_SYNC_REACHABLE) + + async def req_ism_states(self): + "Request a dump of the current ISM states of all interfaces." + logging.debug("SEND: %s: request ISM changes", self) + await self.msg_send_raises(MSG_SYNC_ISM) + + async def req_nsm_states(self): + "Request a dump of the current NSM states of all neighbors." + logging.debug("SEND: %s: request NSM changes", self) + await self.msg_send_raises(MSG_SYNC_NSM) + + async def req_router_id_sync(self): + "Request a dump of the current NSM states of all neighbors." + logging.debug("SEND: %s: request router ID sync", self) + await self.msg_send_raises(MSG_SYNC_ROUTER_ID) + + +class OspfOpaqueClient(OspfApiClient): + """A client connection to OSPF Daemon for manipulating Opaque LSA data. + + The client object is not created in a connected state. To connect to the server + the `connect` method should be called. If an error is encountered when sending + messages to the server an exception will be raised and the connection will be + closed. When this happens `connect` may be called again to restore the + connection. + + Args: + server: hostname or IP address of server default is "localhost" + wait_ready: if True then wait for OSPF to signal ready, in newer versions + FRR ospfd is always ready so this overhead can be skipped. + default is False. + + Raises: + Will raise exceptions for failures with various `socket` modules + functions such as `socket.socket`, `socket.setsockopt`, `socket.bind`. + """ + + def __init__(self, server="localhost", wait_ready=False): + handlers = { + MSG_LSA_UPDATE_NOTIFY: self._lsa_change_msg, + MSG_LSA_DELETE_NOTIFY: self._lsa_change_msg, + MSG_NEW_IF: self._if_msg, + MSG_DEL_IF: self._if_msg, + MSG_ISM_CHANGE: self._if_change_msg, + MSG_NSM_CHANGE: self._nbr_change_msg, + MSG_REACHABLE_CHANGE: self._reachable_msg, + MSG_ROUTER_ID_CHANGE: self._router_id_msg, + } + if wait_ready: + handlers[MSG_READY_NOTIFY] = self._ready_msg + + super().__init__(server, handlers) + + self.wait_ready = wait_ready + self.ready_lock = Lock() if wait_ready else WithNothing() + self.ready_cond = { + LSA_TYPE_OPAQUE_LINK: {}, + LSA_TYPE_OPAQUE_AREA: {}, + LSA_TYPE_OPAQUE_AS: {}, + } + self.router_id = ip(0) + self.router_id_change_cb = None + + self.lsid_seq_num = {} + + self.lsa_change_cb = None + self.opaque_change_cb = {} + + self.reachable_routers = set() + self.reachable_change_cb = None + + self.if_area = {} + self.ism_states = {} + self.ism_change_cb = None + + self.nsm_states = {} + self.nsm_change_cb = None + + async def _register_opaque_data(self, lsa_type, otype): + async with self.ready_lock: + cond = self.ready_cond[lsa_type].get(otype) + assert cond is None, "multiple registers for {} opaque-type {}".format( + lsa_typename(lsa_type), otype + ) + + logging.debug("register %s opaque-type %s", lsa_typename(lsa_type), otype) + + mt = MSG_REGISTER_OPAQUETYPE + mp = struct.pack(msg_fmt[mt], lsa_type, otype) + await self.msg_send_raises(mt, mp) + + # If we are not waiting, mark ready for register check + if not self.wait_ready: + self.ready_cond[lsa_type][otype] = True + + async def _handle_msg_loop(self): + try: + logging.debug("entering async msg handling loop") + while True: + mt, msg = await self.msg_read() + if mt in amsg_info: + await self.handle_async_msg(mt, msg) + else: + mts = api_msgname(mt) + logging.warning( + "ignoring unexpected msg: %s len: %s", mts, len(msg) + ) + except EOFError: + logging.info("Got EOF from OSPF API server on async notify socket") + return 2 + + @staticmethod + def _opaque_args(lsa_type, otype, oid, mp): + lsid = (otype << 24) | oid + return 0, 0, lsa_type, lsid, 0, 0, 0, FMT_LSA_HEADER_SIZE + len(mp) + + @staticmethod + def _make_opaque_lsa(lsa_type, otype, oid, mp): + # /* Make a new LSA from parameters */ + lsa = struct.pack( + FMT_LSA_HEADER, *OspfOpaqueClient._opaque_args(lsa_type, otype, oid, mp) + ) + lsa += mp + return lsa + + async def _ready_msg(self, mt, msg, extra, lsa_type, otype, addr): + assert self.wait_ready + + if lsa_type == LSA_TYPE_OPAQUE_LINK: + e = "ifaddr {}".format(ip(addr)) + elif lsa_type == LSA_TYPE_OPAQUE_AREA: + e = "area {}".format(ip(addr)) + else: + e = "" + logging.info( + "RECV: %s ready notify for %s opaque-type %s%s", + self, + lsa_typename(lsa_type), + otype, + e, + ) + + # Signal all waiting senders they can send now. + async with self.ready_lock: + cond = self.ready_cond[lsa_type].get(otype) + self.ready_cond[lsa_type][otype] = True + + if cond is True: + logging.warning( + "RECV: dup ready received for %s opaque-type %s", + lsa_typename(lsa_type), + otype, + ) + elif cond: + for evt in cond: + evt.set() + + async def _if_msg(self, mt, msg, extra, *args): + if mt == MSG_NEW_IF: + ifaddr, aid = args + else: + assert mt == MSG_DEL_IF + ifaddr, aid = args[0], 0 + logging.info( + "RECV: %s ifaddr %s areaid %s", api_msgname(mt), ip(ifaddr), ip(aid) + ) + + async def _if_change_msg(self, mt, msg, extra, ifaddr, aid, state): + ifaddr = ip(ifaddr) + aid = ip(aid) + + logging.info( + "RECV: %s ifaddr %s areaid %s state %s", + api_msgname(mt), + ifaddr, + aid, + ism_name(state), + ) + + self.if_area[ifaddr] = aid + self.ism_states[ifaddr] = state + + if self.ism_change_cb: + self.ism_change_cb(ifaddr, aid, state) + + async def _nbr_change_msg(self, mt, msg, extra, ifaddr, nbraddr, router_id, state): + ifaddr = ip(ifaddr) + nbraddr = ip(nbraddr) + router_id = ip(router_id) + + logging.info( + "RECV: %s ifaddr %s nbraddr %s router_id %s state %s", + api_msgname(mt), + ifaddr, + nbraddr, + router_id, + nsm_name(state), + ) + + if ifaddr not in self.nsm_states: + self.nsm_states[ifaddr] = {} + self.nsm_states[ifaddr][(nbraddr, router_id)] = state + + if self.nsm_change_cb: + self.nsm_change_cb(ifaddr, nbraddr, router_id, state) + + async def _lsa_change_msg(self, mt, msg, extra, ifaddr, aid, is_self, *ls_header): + ( + lsa_age, # ls_age, + _, # ls_options, + lsa_type, + ls_id, + _, # ls_adv_router, + ls_seq, + _, # ls_cksum, + ls_len, + ) = ls_header + + otype = (ls_id >> 24) & 0xFF + + if mt == MSG_LSA_UPDATE_NOTIFY: + ts = "update" + else: + assert mt == MSG_LSA_DELETE_NOTIFY + ts = "delete" + + logging.info( + "RECV: LSA %s msg for LSA %s in area %s seq 0x%x len %s age %s", + ts, + ip(ls_id), + ip(aid), + ls_seq, + ls_len, + lsa_age, + ) + idx = (lsa_type, otype) + + pre_lsa_size = msg_size[mt] - FMT_LSA_HEADER_SIZE + lsa = msg[pre_lsa_size:] + + if idx in self.opaque_change_cb: + self.opaque_change_cb[idx](mt, ifaddr, aid, ls_header, extra, lsa) + + if self.lsa_change_cb: + self.lsa_change_cb(mt, ifaddr, aid, ls_header, extra, lsa) + + async def _reachable_msg(self, mt, msg, extra, nadd, nremove): + router_ids = struct.unpack(">{}I".format(nadd + nremove), extra) + router_ids = [ip(x) for x in router_ids] + logging.info( + "RECV: %s added %s removed %s", + api_msgname(mt), + router_ids[:nadd], + router_ids[nadd:], + ) + self.reachable_routers |= set(router_ids[:nadd]) + self.reachable_routers -= set(router_ids[nadd:]) + logging.info("RECV: %s new set %s", api_msgname(mt), self.reachable_routers) + + if self.reachable_change_cb: + logging.info("RECV: %s calling callback", api_msgname(mt)) + await self.reachable_change_cb(router_ids[:nadd], router_ids[nadd:]) + + async def _router_id_msg(self, mt, msg, extra, router_id): + router_id = ip(router_id) + logging.info("RECV: %s router ID %s", api_msgname(mt), router_id) + old_router_id = self.router_id + if old_router_id == router_id: + return + + self.router_id = router_id + logging.info( + "RECV: %s new router ID %s older router ID %s", + api_msgname(mt), + router_id, + old_router_id, + ) + + if self.router_id_change_cb: + logging.info("RECV: %s calling callback", api_msgname(mt)) + await self.router_id_change_cb(router_id, old_router_id) + + async def add_opaque_data(self, addr, lsa_type, otype, oid, data): + """Add an instance of opaque data. + + Add an instance of opaque data. This call will register for the given + LSA and opaque type if not already done. + + Args: + addr: depends on lsa_type, LINK => ifaddr, AREA => area ID, AS => ignored + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type + oid: (3 octets) ID of this opaque data + data: the opaque data + Raises: + See `msg_send_raises` + """ + assert self.ready_cond.get(lsa_type, {}).get(otype) is True, "Not Registered!" + + if lsa_type == LSA_TYPE_OPAQUE_LINK: + ifaddr, aid = int(addr), 0 + elif lsa_type == LSA_TYPE_OPAQUE_AREA: + ifaddr, aid = 0, int(addr) + else: + assert lsa_type == LSA_TYPE_OPAQUE_AS + ifaddr, aid = 0, 0 + + mt = MSG_ORIGINATE_REQUEST + msg = struct.pack( + msg_fmt[mt], + ifaddr, + aid, + *OspfOpaqueClient._opaque_args(lsa_type, otype, oid, data), + ) + msg += data + await self.msg_send_raises(mt, msg) + + async def delete_opaque_data(self, addr, lsa_type, otype, oid, flags=0): + """Delete an instance of opaque data. + + Delete an instance of opaque data. This call will register for the given + LSA and opaque type if not already done. + + Args: + addr: depends on lsa_type, LINK => ifaddr, AREA => area ID, AS => ignored + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type. + oid: (3 octets) ID of this opaque data + flags: (octet) optional flags (e.g., OSPF_API_DEL_ZERO_LEN_LSA, defaults to no flags) + Raises: + See `msg_send_raises` + """ + assert self.ready_cond.get(lsa_type, {}).get(otype) is True, "Not Registered!" + + mt = MSG_DELETE_REQUEST + mp = struct.pack(msg_fmt[mt], int(addr), lsa_type, otype, flags, oid) + await self.msg_send_raises(mt, mp) + + async def is_registered(self, lsa_type, otype): + """Determine if an (lsa_type, otype) tuple has been registered with FRR + + This determines if the type has been registered, but not necessarily if it is + ready, if that is required use the `wait_opaque_ready` metheod. + + Args: + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type. + """ + async with self.ready_lock: + return self.ready_cond.get(lsa_type, {}).get(otype) is not None + + async def register_opaque_data(self, lsa_type, otype, callback=None): + """Register intent to advertise opaque data. + + The application should wait for the async notificaiton that the server is + ready to advertise the given opaque data type. The API currently only allows + a single "owner" of each unique (lsa_type,otype). To wait call `wait_opaque_ready` + + Args: + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type. + callback: if given, callback will be called when changes are received for + LSA of the given (lsa_type, otype). The callbacks signature is: + + `callback(msg_type, ifaddr, area_id, lsa_header, data, lsa)` + + Args: + msg_type: MSG_LSA_UPDATE_NOTIFY or MSG_LSA_DELETE_NOTIFY + ifaddr: integer identifying an interface (by IP address) + area_id: integer identifying an area + lsa_header: the LSA header as an unpacked tuple (fmt: ">HBBIILHH") + data: the opaque data that follows the LSA header + lsa: the octets of the full lsa + Raises: + See `msg_send_raises` + """ + assert not await self.is_registered( + lsa_type, otype + ), "Registering registered type" + + if callback: + self.opaque_change_cb[(lsa_type, otype)] = callback + elif (lsa_type, otype) in self.opaque_change_cb: + logging.warning( + "OSPFCLIENT: register: removing callback for %s opaque-type %s", + lsa_typename(lsa_type), + otype, + ) + del self.opaque_change_cb[(lsa_type, otype)] + + await self._register_opaque_data(lsa_type, otype) + + async def wait_opaque_ready(self, lsa_type, otype): + async with self.ready_lock: + cond = self.ready_cond[lsa_type].get(otype) + if cond is True: + return + + assert self.wait_ready + + logging.debug( + "waiting for ready %s opaque-type %s", lsa_typename(lsa_type), otype + ) + + if not cond: + cond = self.ready_cond[lsa_type][otype] = [] + + evt = Event() + cond.append(evt) + + await evt.wait() + logging.debug("READY for %s opaque-type %s", lsa_typename(lsa_type), otype) + + async def register_opaque_data_wait(self, lsa_type, otype, callback=None): + """Register intent to advertise opaque data and wait for ready. + + The API currently only allows a single "owner" of each unique (lsa_type,otype). + + Args: + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type. + callback: if given, callback will be called when changes are received for + LSA of the given (lsa_type, otype). The callbacks signature is: + + `callback(msg_type, ifaddr, area_id, lsa_header, data, lsa)` + + Args: + msg_type: MSG_LSA_UPDATE_NOTIFY or MSG_LSA_DELETE_NOTIFY + ifaddr: integer identifying an interface (by IP address) + area_id: integer identifying an area + lsa_header: the LSA header as an unpacked tuple (fmt: ">HBBIILHH") + data: the opaque data that follows the LSA header + lsa: the octets of the full lsa + Raises: + + See `msg_send_raises` + """ + await self.register_opaque_data(lsa_type, otype, callback) + await self.wait_opaque_ready(lsa_type, otype) + + async def unregister_opaque_data(self, lsa_type, otype): + """Unregister intent to advertise opaque data. + + This will also cause the server to flush/delete all opaque data of + the given (lsa_type,otype). + + Args: + lsa_type: LSA_TYPE_OPAQUE_{LINK,AREA,AS} + otype: (octet) opaque type. + Raises: + See `msg_send_raises` + """ + assert await self.is_registered( + lsa_type, otype + ), "Unregistering unregistered type" + + if (lsa_type, otype) in self.opaque_change_cb: + del self.opaque_change_cb[(lsa_type, otype)] + + mt = MSG_UNREGISTER_OPAQUETYPE + mp = struct.pack(msg_fmt[mt], lsa_type, otype) + await self.msg_send_raises(mt, mp) + + async def monitor_lsa(self, callback=None): + """Monitor changes to LSAs. + + Args: + callback: if given, callback will be called when changes are received for + any LSA. The callback signature is: + + `callback(msg_type, ifaddr, area_id, lsa_header, extra, lsa)` + + Args: + msg_type: MSG_LSA_UPDATE_NOTIFY or MSG_LSA_DELETE_NOTIFY + ifaddr: integer identifying an interface (by IP address) + area_id: integer identifying an area + lsa_header: the LSA header as an unpacked tuple (fmt: ">HBBIILHH") + extra: the octets that follow the LSA header + lsa: the octets of the full lsa + """ + self.lsa_change_cb = callback + await self.req_lsdb_sync() + + async def monitor_reachable(self, callback=None): + """Monitor the set of reachable routers. + + The property `reachable_routers` contains the set() of reachable router IDs + as integers. This set is updated prior to calling the `callback` + + Args: + callback: callback will be called when the set of reachable + routers changes. The callback signature is: + + `callback(added, removed)` + + Args: + added: list of integer router IDs being added + removed: list of integer router IDs being removed + """ + self.reachable_change_cb = callback + await self.req_reachable_routers() + + async def monitor_ism(self, callback=None): + """Monitor the state of OSPF enabled interfaces. + + Args: + callback: callback will be called when an interface changes state. + The callback signature is: + + `callback(ifaddr, area_id, state)` + + Args: + ifaddr: integer identifying an interface (by IP address) + area_id: integer identifying an area + state: ISM_* + """ + self.ism_change_cb = callback + await self.req_ism_states() + + async def monitor_nsm(self, callback=None): + """Monitor the state of OSPF neighbors. + + Args: + callback: callback will be called when a neighbor changes state. + The callback signature is: + + `callback(ifaddr, nbr_addr, router_id, state)` + + Args: + ifaddr: integer identifying an interface (by IP address) + nbr_addr: integer identifying neighbor by IP address + router_id: integer identifying neighbor router ID + state: NSM_* + """ + self.nsm_change_cb = callback + await self.req_nsm_states() + + async def monitor_router_id(self, callback=None): + """Monitor the OSPF router ID. + + The property `router_id` contains the OSPF urouter ID. + This value is updated prior to calling the `callback` + + Args: + callback: callback will be called when the router ID changes. + The callback signature is: + + `callback(new_router_id, old_router_id)` + + Args: + new_router_id: the new router ID + old_router_id: the old router ID + """ + self.router_id_change_cb = callback + await self.req_router_id_sync() + + +# ================ +# CLI/Script Usage +# ================ +def next_action(action_list=None): + "Get next action from list or STDIN" + if action_list: + for action in action_list: + yield action + else: + while True: + action = input("") + if not action: + break + yield action.strip() + + +async def async_main(args): + c = OspfOpaqueClient(args.server) + await c.connect() + + try: + # Start handling async messages from server. + if sys.version_info[1] > 6: + asyncio.create_task(c._handle_msg_loop()) + else: + asyncio.get_event_loop().create_task(c._handle_msg_loop()) + + await c.req_lsdb_sync() + await c.req_reachable_routers() + await c.req_ism_states() + await c.req_nsm_states() + + for action in next_action(args.actions): + _s = action.split(",") + what = _s.pop(False) + if what.casefold() == "wait": + stime = int(_s.pop(False)) + logging.info("waiting %s seconds", stime) + await asyncio.sleep(stime) + logging.info("wait complete: %s seconds", stime) + continue + ltype = int(_s.pop(False)) + if ltype == 11: + addr = ip(0) + else: + aval = _s.pop(False) + try: + addr = ip(int(aval)) + except ValueError: + addr = ip(aval) + oargs = [addr, ltype, int(_s.pop(False)), int(_s.pop(False))] + + if not await c.is_registered(oargs[1], oargs[2]): + await c.register_opaque_data_wait(oargs[1], oargs[2]) + + if what.casefold() == "add": + try: + b = bytes.fromhex(_s.pop(False)) + except IndexError: + b = b"" + logging.info("opaque data is %s octets", len(b)) + # Needs to be multiple of 4 in length + mod = len(b) % 4 + if mod: + b += b"\x00" * (4 - mod) + logging.info("opaque padding to %s octets", len(b)) + + await c.add_opaque_data(*oargs, b) + else: + assert what.casefold().startswith("del") + f = 0 + if len(_s) >= 1: + try: + f = int(_s.pop(False)) + except IndexError: + f = 0 + await c.delete_opaque_data(*oargs, f) + if not args.actions or args.exit: + return 0 + except Exception as error: + logging.error("async_main: unexpected error: %s", error, exc_info=True) + return 2 + + try: + logging.info("Sleeping forever") + while True: + await asyncio.sleep(120) + except EOFError: + logging.info("Got EOF from OSPF API server on async notify socket") + return 2 + + +def main(*args): + ap = argparse.ArgumentParser(args) + ap.add_argument("--logtag", default="CLIENT", help="tag to identify log messages") + ap.add_argument("--exit", action="store_true", help="Exit after commands") + ap.add_argument("--server", default="localhost", help="OSPF API server") + ap.add_argument("-v", "--verbose", action="store_true", help="be verbose") + ap.add_argument( + "actions", + nargs="*", + help="WAIT,SEC|(ADD|DEL),LSATYPE,[ADDR,],OTYPE,OID,[HEXDATA|DEL_FLAG]", + ) + args = ap.parse_args() + + level = logging.DEBUG if args.verbose else logging.INFO + logging.basicConfig( + level=level, + format="%(asctime)s %(levelname)s: {}: %(name)s %(message)s".format( + args.logtag + ), + ) + + logging.info("ospfclient: starting") + + status = 3 + try: + if sys.version_info[1] > 6: + # python >= 3.7 + status = asyncio.run(async_main(args)) + else: + loop = asyncio.get_event_loop() + try: + status = loop.run_until_complete(async_main(args)) + finally: + loop.close() + except KeyboardInterrupt: + logging.info("Exiting, received KeyboardInterrupt in main") + except Exception as error: + logging.info("Exiting, unexpected exception %s", error, exc_info=True) + else: + logging.info("ospfclient: clean exit") + + return status + + +if __name__ == "__main__": + exit_status = main() + sys.exit(exit_status) diff --git a/ospfclient/subdir.am b/ospfclient/subdir.am new file mode 100644 index 0000000..289ddd0 --- /dev/null +++ b/ospfclient/subdir.am @@ -0,0 +1,52 @@ +# +# ospfclient +# + +if OSPFCLIENT +lib_LTLIBRARIES += ospfclient/libfrrospfapiclient.la +noinst_PROGRAMS += ospfclient/ospfclient +#man8 += $(MANBUILD)/frr-ospfclient.8 + +sbin_SCRIPTS += \ + ospfclient/ospfclient.py \ + # end +endif + +ospfclient_libfrrospfapiclient_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 +ospfclient_libfrrospfapiclient_la_LIBADD = lib/libfrr.la +ospfclient_libfrrospfapiclient_la_SOURCES = \ + ospfclient/ospf_apiclient.c \ + # end + +if OSPFCLIENT +ospfapiheaderdir = $(pkgincludedir)/ospfapi +ospfapiheader_HEADERS = \ + ospfclient/ospf_apiclient.h \ + # end +endif + +ospfclient_ospfclient_LDADD = \ + ospfclient/libfrrospfapiclient.la \ + ospfd/libfrrospfclient.a \ + $(LIBCAP) \ + # end + +if STATIC_BIN +# libfrr is linked in through libfrrospfapiclient. If we list it here too, +# it gets linked twice and we get a ton of symbol collisions. + +else # !STATIC_BIN +# For most systems we don't need this, except Debian, who patch their linker +# to disallow transitive references *while* *als* not patching their libtool +# to work appropriately. RedHat has the same linker behaviour, but things +# work as expected since they also patch libtool. +ospfclient_ospfclient_LDADD += lib/libfrr.la +endif + +ospfclient_ospfclient_SOURCES = \ + ospfclient/ospfclient.c \ + # end + +EXTRA_DIST += \ + ospfclient/ospfclient.py \ + # end diff --git a/ospfd/.gitignore b/ospfd/.gitignore new file mode 100644 index 0000000..fc65db3 --- /dev/null +++ b/ospfd/.gitignore @@ -0,0 +1,2 @@ +ospfd +ospfd.conf diff --git a/ospfd/ChangeLog.opaque.txt b/ospfd/ChangeLog.opaque.txt new file mode 100644 index 0000000..782e332 --- /dev/null +++ b/ospfd/ChangeLog.opaque.txt @@ -0,0 +1,221 @@ +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2002.12.20 + +1. Bug fixes + + 1.1 When an opaque LSA is being removed from (or added to) the LSDB, + it does not mean a change in network topology. Therefore, SPF + recalculation should not be triggered in that case. + There was an assertion failure problem "assert (rn && rn->info)" + inside the function "ospf_ase_incremental_update()", because + the upper function "ospf_lsa_maxage_walker_remover()" called it + when a type-11 opaque LSA is removed due to MaxAge. + + 1.2 Type-9 LSA is defined to have "link-local" flooding scope. + In the Database exchange procedure with a new neighbor, a type-9 + LSA was added in the database summary of a DD message, even if + the link is different from the one that have bound to. + +2. Feature enhancements + + 2.1 Though a "wildcard" concept to handle type-9/10/11 LSAs altogether + has introduced about a year ago, it was only a symbol definition + and actual handling mechanism was not implemented. Now it works. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2002.7.8 + +1. Bug fixes + + 1.1 When "ospf_delete_opaque_functab()" is called, internal structure + "oipt" remain unfreed. If register/delete functab is repeated, + illegal memory access happens due to this "oipt". + + 1.2 In "free_opaque_info_per_id()", there was a crucial typo which + ignores a condition test. + + "if (oipi->lsa != NULL);" <-- semicolon! + +2. Feature enhancements + + None. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.12.03 + +1. Bug fixes + + 1.1 Though a new member "oi" has added to "struct ospf_lsa" to control + flooding scope of type-9 Opaque-LSAs, the value was always NULL + because no one set it. + + 1.2 In the function "show_ip_ospf_database_summary()" and "show_lsa_ + detail_adv_router()", VTY output for type-11 Opaque-LSAs did not + work properly. + + 1.3 URL for the opaque-type assignment reference has changed. + + 1.4 In the file "ospf_mpls_te.c", printf formats have changed to + avoid compiler warning messages; "%lu" -> "%u", "%lx" -> "%x". + Note that this hack depends on OS, compiler and their versions. + + 1.5 One of attached documentation "opaque_lsa.txt" has changed to + reflect the latest coding. + +2. Feature enhancements + + 2.1 Knowing that it is an ugly hack, an "officially unallocated" + opaque-type value 0 has newly introduced as a "wildcard", + which matches to all opaque-type. + This value must not be flooded to the network, of course. + + 2.2 The Opaque-core module makes use of newly introduced hooks to + dispatch every LSDB change (LSA installation and deletion) to + preregistered opaque users. + Therefore, by providing appropriate callback functions as new + parameters of "ospf_register_opaque_functab()", an opaque user + can refer to every LSA instance to be installed into, or to be + deleted from, the LSDB. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.10.31 + +1. Bug fixes + + 1.1 Since each LSA has their own lifetime, they will remain in a + routing domain (being stored in LSDB of each router), until their + age naturally reach to MaxAge or explicitly being flushed by the + originated router. Therefore, if a router restarted with a short + downtime, it is possible that previously flooded self-originated + LSAs might received if the NSM status is not less than Exchange. + + There were some problems in the way of handling self-originated + Opaque-LSAs if they are contained in a received LSUpd message, + but not installed to the local LSDB yet. + Regardless of some conditions to start originating Opaque-LSAs + (there should be at least one opaque-capable full-state neighbor), + the function "ospf_flood()" will be called to flood and install + this brand-new looking LSA. + As the result, when the NSM of an opaque-capable neighbor gets + full, internal state inconsistency happens; a user of Opaque-LSA + such as MPLS-TE can refer to self-originated LSAs in the local + LSDB, but cannot modify their contents... + + Above problems have fixed with a policy "flush it from the whole + routing domain and keep silent until the flushing completed". + By using this sweeping technique, we can be free from confusion + caused by self-originated LSAs received via network. + + 1.2 The function "ospf_opaque_type_name()" contained massive ifdefs + corresponding to each "opaque-type". + These unnecessary ifdefs are removed completely. + + 1.3 In the function "ospf_delete_opaque_functab()", there was an + improper loop control that causes illegal memory access. + Original coding was "next = nextnode (node)". + + 1.4 The function "ospf_mpls_te_ism_change()" could not handle the + case when the ISM changes from Waiting to DR/BDR/Other. + So, there was a case that even if one of an ISM become + operational and MPLS-TE module has started, the corresponding + Opaque-LSA cannot be originated. + + 1.5 The function "ospf_opaque_lsa_reoriginate_schedule()" did not + allow to be called multiple times, simply because handling + module for the given "lsa-type & opaque-type" already exists. + But this assumption seems to be wrong. + Change the policy to allow this function to be called multiple + times and let the caller to decide what should do when the + corresponding callback function "(* functab->lsa_originator)()" + is called. + +2. Feature enhancements + + 2.1 The global bitmap "opaque" has introduced instead of former flag + "OpaqueCapable", to store complex conditions to handle Opaque-LSAs. + + 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic + -06.txt", no significant changes with 05 version, though. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.08.03 + +1. Bug fixes + + 1.1 Even if the ospfd started with opaque capability enabled, when + the ospfd receives an unknown opaque-type (unregistered by the + function "ospf_register_opaque_functab()" beforehand), the LSA + was discarded. As the result, only the opaque-LSAs that have + commonly registered by opaque-capable ospf routers can be + flooded in a routing domain. + + This behavior has fixed so that arbitrary opaque-type LSAs can + be flooded among opaque-capable ospf routers. + If the ospfd has opaque-LSA capability but disabled at runtime, + received opaque-LSAs can be accepted and registered to LSDB as + is, but not be flooded to the network; those opaque LSAs will + remain in LSDB until explicitly flushed by incoming LSUpd + messages with MaxAge, or their age naturally reaches to MaxAge. + + 1.2 The function "ospf_register_opaque_functab()" did not check + if the entry corresponding to the given "lsa-type, opaque-type" + combination already exists or not. + This problem has fixed not to allow multiple registration. + + 1.3 Since type-11 (AS external) LSAs will be flooded beyond areas, + there is little relationship between "struct lsa" and "struct + area". More specifically, the pointer address "lsa->area" can + be NULL if the lsa-type is 11, thus an illegal memory access + will happen. This problem has fixed. + + 1.4 When self-originated opaque-LSAs are received via network and + if the corresponding opaque-type functions are not available + (they have already deleted) at that time, those LSAs were + dropped due to "unknown opaque-type" error. + After the problem 1.1 has fixed, those "self-originated" LSAs + were registered to LSDB and then flooded to the network, even + if the processing functions did not exist... + + After all, this problem has fixed so that those LSAs should + explicitly be flushed from the routing domain immediately, if + the processing functions cannot find at that time. + + 1.5 Some typo have fixed. + + --- EXAMPLE --- + static int + opaque_lsa_originate_callback (list funclist, void *lsa_type_dependent) + ^^^^^ + --- EXAMPLE --- + +2. Feature enhancements + + 2.1 According to the description of rfc2328 in section 10.8, any + change in the router's optional capabilities should trigger + the option re-negotiation procedures with neighbors. + + --- EXCERPT --- + If for some reason the router's optional + capabilities change, the Database Exchange procedure should be + restarted by reverting to neighbor state ExStart. + --- EXCERPT --- + + For the opaque-capability changes, this feature has implemented. + More specifically, if "ospf opaque-lsa" or "no ospf opaque-lsa" + VTY command is given at runtime, all self-originated LSAs will + be flushed immediately and then all neighbor status will be + forced to ExStart by generating SeqNumberMismatch events. + + 2.1 When we change opaque-capability dynamically (ON -> OFF -> ON), + there was no trigger at "OFF->ON" timing to reactivate opaque + LSA handling modules (such as MPLS-TE) that have once forcibly + stopped at "ON->OFF" timing. + Now this dynamic reactivation feature has added. + + 2.2 The MPLS-TE module now referes to "draft-katz-yeung-ospf-traffic + -05.txt", no significant changes with 04 version, though. + +----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- * ----- +Changes 2001.03.28 + + Initial release of Opaque-LSA/MPLS-TE extensions for the zebra/ospfd. diff --git a/ospfd/Makefile b/ospfd/Makefile new file mode 100644 index 0000000..e4fab30 --- /dev/null +++ b/ospfd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ospfd/ospfd +%: ALWAYS + @$(MAKE) -s -C .. ospfd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ospfd/OSPF-ALIGNMENT.txt b/ospfd/OSPF-ALIGNMENT.txt new file mode 100644 index 0000000..905bd22 --- /dev/null +++ b/ospfd/OSPF-ALIGNMENT.txt @@ -0,0 +1,117 @@ +Greg Troxel +2004-11-17 + +The OSPF specification (RFC2328) and the OSPF Opaque LSA specification +(RFC2370) are ambiguous about LSAs whose data section is not an +integral multiple of 4 octets. This note examines the issue and +proposes clarifications to ensure interoperability. + +RFC2328 does not specify that LSA lengths be a multiple of 4. +It does not require that LSAs in update packets be aligned. +However, all structures defined by RFC2328 are multiples of 4, and +thus update packets with those structures must be aligned. +LSA length is defined in Appendix A.4 as + + length + The length in bytes of the LSA. This includes the 20 byte LSA + header. + +RFC2370 defines Opaque LSAs, which are intended to contain arbitrary +data: + + This memo defines enhancements to the OSPF protocol to support a new + class of link-state advertisements (LSA) called Opaque LSAs. Opaque + LSAs provide a generalized mechanism to allow for the future + extensibility of OSPF. Opaque LSAs consist of a standard LSA header + followed by application-specific information. The information field + may be used directly by OSPF or by other applications. Standard OSPF + link-state database flooding mechanisms are used to distribute Opaque + LSAs to all or some limited portion of the OSPF topology. + + +Later, 2370 says: + + Opaque LSAs contain some number of octets (of application-specific + data) padded to 32-bit alignment. + +This can be interpreted in several ways: + +A) The payload may be any number of octets, and the length field +reflects the payload length (e.g. length 23 for 3 octets of payload), +but there are padding octets following the LSA in packets, so that the +next LSA starts on a 4-octet boundary. (This approach is common in +the BSD user/kernel interface.) + +B) The payload must be a multiple of 4 octets, so that the length is a +multiple of 4 octets. This corresponds to an implementation that +treats an Opaque LSA publish request that is not a multiple of 4 +octets as an error. + +C) The payload can be any number of octets, but padding is added and +included in the length field. This interpretation corresponds to an +OSPF implementation that accepts a publish request for an Opaque LSA +that is not a multiple of 4 octets. This interpretation is +nonsensical, because it claims to represent arbitrary lengths, but +does not actually do so --- the receiver cannot distinguish padding +from supplied data. + +D) Accept according to A, and transmit according to B. + +Option A arguably violates RFC 2328, which doesn't say anything about +adding padding (A.3.5 shows a diagram of adjacent LSAs which are shown +as all multiples of 4). This option is thus likely to lead to a lack +of interoperability. + +Option B restricts what data can be represented as an Opaque LSA, but +probably not in a serious way. It is likely to lead to +interoperability in that the complex case of non-multiple-of-4 lengths +will not arise. + +However, an implementation that follows A and emits an LSA with +payload length not a multiple of 4 will not interoperate with an +Option B implementation. + +Given that all known and documented uses of Opaque LSAs seem to be +multiples of 4 octets, we choose Option B as the clarification. + +CLARIFYING TEXT + +In RFC2328: + +In section A.4, add a second sentence about length: + + length + The length in bytes of the LSA. This includes the 20 byte LSA + header. The length must be an integral multiple of 4 bytes. + +Add to the list in Section 13: + + Verify that the length of the LSA is a multiple of 4 bytes. If + not, discard the entire Link State Update Packet. + +In RFC2380: + +Change text: + + Opaque LSAs contain some number of octets (of application-specific + data) padded to 32-bit alignment. + +to: + + Opaque LSAs contain some a number of octets (of + application-specific data). The number of octets must be a + multiple of four. + + +HOW THIS ISSUE AROSE + +At BBN, we use Opaque LSAs to exchange data among routers; the format +of the data is naturally aligned to 4 bytes, and thus does not raise +this issue. We created a test program to publish Opaque data via IPC +to the OSPF daemon (quagga), and this program accepts strings on the +command line to publish. We then used this test program to publish +software version strings. Quagga's ospfd then crashed on a +NetBSD/sparc64 machine with an alignment fault, because the odd-length +LSAs were marshalled into a link-state update packet with no padding. +While this behavior was a clear violation of RFC2380, it was not clear +how to remedy the problem. diff --git a/ospfd/ospf_abr.c b/ospfd/ospf_abr.c new file mode 100644 index 0000000..28d5268 --- /dev/null +++ b/ospfd/ospf_abr.c @@ -0,0 +1,2178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF ABR functions. + * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada + */ + + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "vty.h" +#include "filter.h" +#include "plist.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ia.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_errors.h" + +static struct ospf_area_range *ospf_area_range_new(struct prefix_ipv4 *p) +{ + struct ospf_area_range *range; + + range = XCALLOC(MTYPE_OSPF_AREA_RANGE, sizeof(struct ospf_area_range)); + range->addr = p->prefix; + range->masklen = p->prefixlen; + range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; + + return range; +} + +static void ospf_area_range_free(struct ospf_area_range *range) +{ + XFREE(MTYPE_OSPF_AREA_RANGE, range); +} + +static void ospf_area_range_add(struct ospf_area *area, + struct route_table *ranges, + struct ospf_area_range *range) +{ + struct route_node *rn; + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefixlen = range->masklen; + p.prefix = range->addr; + apply_mask_ipv4(&p); + + rn = route_node_get(ranges, (struct prefix *)&p); + if (rn->info) { + route_unlock_node(rn); + ospf_area_range_free(rn->info); + rn->info = range; + } else + rn->info = range; +} + +static void ospf_area_range_delete(struct ospf_area *area, + struct route_node *rn) +{ + struct ospf_area_range *range = rn->info; + bool nssa = CHECK_FLAG(range->flags, OSPF_AREA_RANGE_NSSA); + + if (ospf_area_range_active(range) && + CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE)) + ospf_delete_discard_route(area->ospf, area->ospf->new_table, + (struct prefix_ipv4 *)&rn->p, nssa); + + ospf_area_range_free(range); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); +} + +struct ospf_area_range *ospf_area_range_lookup(struct ospf_area *area, + struct route_table *ranges, + struct prefix_ipv4 *p) +{ + struct route_node *rn; + + rn = route_node_lookup(ranges, (struct prefix *)p); + if (rn) { + route_unlock_node(rn); + return rn->info; + } + return NULL; +} + +struct ospf_area_range *ospf_area_range_lookup_next(struct ospf_area *area, + struct in_addr *range_net, + int first) +{ + struct route_node *rn; + struct prefix_ipv4 p; + struct ospf_area_range *find; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = *range_net; + apply_mask_ipv4(&p); + + if (first) + rn = route_top(area->ranges); + else { + rn = route_node_get(area->ranges, (struct prefix *)&p); + rn = route_next(rn); + } + + for (; rn; rn = route_next(rn)) + if (rn->info) + break; + + if (rn && rn->info) { + find = rn->info; + *range_net = rn->p.u.prefix4; + route_unlock_node(rn); + return find; + } + return NULL; +} + +static struct ospf_area_range *ospf_area_range_match(struct ospf_area *area, + struct route_table *ranges, + struct prefix_ipv4 *p) +{ + struct route_node *node; + + node = route_node_match(ranges, (struct prefix *)p); + if (node) { + route_unlock_node(node); + return node->info; + } + return NULL; +} + +struct ospf_area_range *ospf_area_range_match_any(struct ospf *ospf, + struct prefix_ipv4 *p) +{ + struct ospf_area_range *range; + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if ((range = ospf_area_range_match(area, area->ranges, p))) + return range; + + return NULL; +} + +int ospf_area_range_active(struct ospf_area_range *range) +{ + return range->specifics; +} + +static int ospf_area_actively_attached(struct ospf_area *area) +{ + return area->act_ints; +} + +int ospf_area_range_set(struct ospf *ospf, struct ospf_area *area, + struct route_table *ranges, struct prefix_ipv4 *p, + int advertise, bool nssa) +{ + struct ospf_area_range *range; + + range = ospf_area_range_lookup(area, ranges, p); + if (range != NULL) { + if (!CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) + range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; + if ((CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) + && !CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) + || (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) + && CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE))) + ospf_schedule_abr_task(ospf); + } else { + range = ospf_area_range_new(p); + ospf_area_range_add(area, ranges, range); + ospf_schedule_abr_task(ospf); + } + + if (CHECK_FLAG(advertise, OSPF_AREA_RANGE_ADVERTISE)) + SET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); + else { + UNSET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); + range->cost_config = OSPF_AREA_RANGE_COST_UNSPEC; + } + + if (nssa) + SET_FLAG(range->flags, OSPF_AREA_RANGE_NSSA); + + return 1; +} + +int ospf_area_range_cost_set(struct ospf *ospf, struct ospf_area *area, + struct route_table *ranges, struct prefix_ipv4 *p, + uint32_t cost) +{ + struct ospf_area_range *range; + + range = ospf_area_range_lookup(area, ranges, p); + if (range == NULL) + return 0; + + if (range->cost_config != cost) { + range->cost_config = cost; + if (ospf_area_range_active(range)) + ospf_schedule_abr_task(ospf); + } + + return 1; +} + +int ospf_area_range_unset(struct ospf *ospf, struct ospf_area *area, + struct route_table *ranges, struct prefix_ipv4 *p) +{ + struct route_node *rn; + + rn = route_node_lookup(ranges, (struct prefix *)p); + if (rn == NULL) + return 0; + + if (ospf_area_range_active(rn->info)) + ospf_schedule_abr_task(ospf); + + ospf_area_range_delete(area, rn); + + return 1; +} + +int ospf_area_range_substitute_set(struct ospf *ospf, struct ospf_area *area, + struct prefix_ipv4 *p, struct prefix_ipv4 *s) +{ + struct ospf_area_range *range; + + range = ospf_area_range_lookup(area, area->ranges, p); + + if (range != NULL) { + if (!CHECK_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE) + || !CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) + ospf_schedule_abr_task(ospf); + } else { + range = ospf_area_range_new(p); + ospf_area_range_add(area, area->ranges, range); + ospf_schedule_abr_task(ospf); + } + + SET_FLAG(range->flags, OSPF_AREA_RANGE_ADVERTISE); + SET_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE); + range->subst_addr = s->prefix; + range->subst_masklen = s->prefixlen; + + return 1; +} + +int ospf_area_range_substitute_unset(struct ospf *ospf, struct ospf_area *area, + struct prefix_ipv4 *p) +{ + struct ospf_area_range *range; + + range = ospf_area_range_lookup(area, area->ranges, p); + if (range == NULL) + return 0; + + if (CHECK_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE)) + if (ospf_area_range_active(range)) + ospf_schedule_abr_task(ospf); + + UNSET_FLAG(range->flags, OSPF_AREA_RANGE_SUBSTITUTE); + range->subst_addr.s_addr = INADDR_ANY; + range->subst_masklen = 0; + + return 1; +} + +int ospf_act_bb_connection(struct ospf *ospf) +{ + struct ospf_interface *oi; + struct listnode *node; + int full_nbrs = 0; + + if (ospf->backbone == NULL) + return 0; + + for (ALL_LIST_ELEMENTS_RO(ospf->backbone->oiflist, node, oi)) { + struct ospf_neighbor *nbr; + struct route_node *rn; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (!nbr) + continue; + + if (nbr->state == NSM_Full + || OSPF_GR_IS_ACTIVE_HELPER(nbr)) + full_nbrs++; + } + } + + return full_nbrs; +} + +/* Determine whether this router is elected translator or not for area */ +static int ospf_abr_nssa_am_elected(struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + struct router_lsa *rlsa; + struct in_addr *best = NULL; + + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) { + /* sanity checks */ + if (!lsa || (lsa->data->type != OSPF_ROUTER_LSA) + || IS_LSA_SELF(lsa)) + continue; + + rlsa = (struct router_lsa *)lsa->data; + + /* ignore non-ABR routers */ + if (!IS_ROUTER_LSA_BORDER(rlsa)) + continue; + + /* Router has Nt flag - always translate */ + if (IS_ROUTER_LSA_NT(rlsa)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: router %pI4 asserts Nt", + __func__, &lsa->data->id); + return 0; + } + + if (best == NULL) + best = &lsa->data->id; + else if (IPV4_ADDR_CMP(&best->s_addr, &lsa->data->id.s_addr) + < 0) + best = &lsa->data->id; + } + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: best electable ABR is: %pI4", __func__, best); + + if (best == NULL) + return 1; + + if (IPV4_ADDR_CMP(&best->s_addr, &area->ospf->router_id.s_addr) < 0) + return 1; + else + return 0; +} + +/* Check NSSA ABR status + * assumes there are nssa areas + */ +void ospf_abr_nssa_check_status(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *lnode, *nnode; + + for (ALL_LIST_ELEMENTS(ospf->areas, lnode, nnode, area)) { + uint8_t old_state = area->NSSATranslatorState; + + if (area->external_routing != OSPF_AREA_NSSA) + continue; + + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug("%s: checking area %pI4", __func__, + &area->area_id); + + if (!IS_OSPF_ABR(area->ospf)) { + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug("%s: not ABR", __func__); + area->NSSATranslatorState = + OSPF_NSSA_TRANSLATE_DISABLED; + } else { + switch (area->NSSATranslatorRole) { + case OSPF_NSSA_ROLE_NEVER: + /* We never Translate Type-7 LSA. */ + /* TODO: check previous state and flush? */ + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug("%s: never translate", + __func__); + area->NSSATranslatorState = + OSPF_NSSA_TRANSLATE_DISABLED; + break; + + case OSPF_NSSA_ROLE_ALWAYS: + /* We always translate if we are an ABR + * TODO: originate new LSAs if state change? + * or let the nssa abr task take care of it? + */ + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug("%s: translate always", + __func__); + area->NSSATranslatorState = + OSPF_NSSA_TRANSLATE_ENABLED; + break; + + case OSPF_NSSA_ROLE_CANDIDATE: + /* We are a candidate for Translation */ + if (ospf_abr_nssa_am_elected(area) > 0) { + area->NSSATranslatorState = + OSPF_NSSA_TRANSLATE_ENABLED; + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug( + "%s: elected translator", + __func__); + } else { + area->NSSATranslatorState = + OSPF_NSSA_TRANSLATE_DISABLED; + if (IS_DEBUG_OSPF(nssa, NSSA)) + zlog_debug("%s: not elected", + __func__); + } + break; + } + } + /* RFC3101, 3.1: + * All NSSA border routers must set the E-bit in the Type-1 + * router-LSAs + * of their directly attached non-stub areas, even when they are + * not + * translating. + */ + if (old_state != area->NSSATranslatorState) { + if (old_state == OSPF_NSSA_TRANSLATE_DISABLED) + ospf_asbr_status_update(ospf, + ++ospf->redistribute); + else if (area->NSSATranslatorState + == OSPF_NSSA_TRANSLATE_DISABLED) + ospf_asbr_status_update(ospf, + --ospf->redistribute); + } + } +} + +/* Check area border router status. */ +void ospf_check_abr_status(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *node, *nnode; + int bb_configured = 0; + int bb_act_attached = 0; + int areas_configured = 0; + int areas_act_attached = 0; + uint8_t new_flags = ospf->flags; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + if (listcount(area->oiflist)) { + areas_configured++; + + if (OSPF_IS_AREA_BACKBONE(area)) + bb_configured = 1; + } + + if (ospf_area_actively_attached(area)) { + areas_act_attached++; + + if (OSPF_IS_AREA_BACKBONE(area)) + bb_act_attached = 1; + } + } + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: looked through areas", __func__); + zlog_debug("%s: bb_configured: %d", __func__, bb_configured); + zlog_debug("%s: bb_act_attached: %d", __func__, + bb_act_attached); + zlog_debug("%s: areas_configured: %d", __func__, + areas_configured); + zlog_debug("%s: areas_act_attached: %d", __func__, + areas_act_attached); + } + + switch (ospf->abr_type) { + case OSPF_ABR_SHORTCUT: + case OSPF_ABR_STAND: + if (areas_act_attached > 1) + SET_FLAG(new_flags, OSPF_FLAG_ABR); + else + UNSET_FLAG(new_flags, OSPF_FLAG_ABR); + break; + + case OSPF_ABR_IBM: + if ((areas_act_attached > 1) && bb_configured) + SET_FLAG(new_flags, OSPF_FLAG_ABR); + else + UNSET_FLAG(new_flags, OSPF_FLAG_ABR); + break; + + case OSPF_ABR_CISCO: + if ((areas_configured > 1) && bb_act_attached) + SET_FLAG(new_flags, OSPF_FLAG_ABR); + else + UNSET_FLAG(new_flags, OSPF_FLAG_ABR); + break; + default: + break; + } + + if (new_flags != ospf->flags) { + ospf_spf_calculate_schedule(ospf, SPF_FLAG_ABR_STATUS_CHANGE); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: new router flags: %x", __func__, + new_flags); + ospf->flags = new_flags; + ospf_router_lsa_update(ospf); + } +} + +static void ospf_abr_update_aggregate(struct ospf_area_range *range, + uint32_t cost, struct ospf_area *area) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED) + && (range->cost != OSPF_STUB_MAX_METRIC_SUMMARY_COST)) { + range->cost = OSPF_STUB_MAX_METRIC_SUMMARY_COST; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: use summary max-metric 0x%08x", + __func__, range->cost); + } else if (range->cost_config != OSPF_AREA_RANGE_COST_UNSPEC) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: use configured cost %d", __func__, + range->cost_config); + + range->cost = range->cost_config; + } else { + if (!ospf_area_range_active(range)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: use cost %d", __func__, cost); + + range->cost = cost; /* 1st time get 1st cost */ + } + + if (cost > range->cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: update to %d", __func__, cost); + + range->cost = cost; + } + } + + range->specifics++; +} + +static void set_metric(struct ospf_lsa *lsa, uint32_t metric) +{ + struct summary_lsa *header; + uint8_t *mp; + metric = htonl(metric); + mp = (uint8_t *)&metric; + mp++; + header = (struct summary_lsa *)lsa->data; + memcpy(header->metric, mp, 3); +} + +/* ospf_abr_translate_nssa */ +static int ospf_abr_translate_nssa(struct ospf_area *area, struct ospf_lsa *lsa) +{ + /* Incoming Type-7 or later aggregated Type-7 + * + * LSA is skipped if P-bit is off. + * LSA is aggregated if within range. + * + * The Type-7 is translated, Installed/Approved as a Type-5 into + * global LSDB, then Flooded through AS + * + * Later, any Unapproved Translated Type-5's are flushed/discarded + */ + + struct ospf_lsa *old = NULL, *new = NULL; + struct as_external_lsa *ext7; + struct prefix_ipv4 p; + struct ospf_area_range *range; + + if (!CHECK_FLAG(lsa->data->options, OSPF_OPTION_NP)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: LSA Id %pI4, P-bit off, NO Translation", + __func__, &lsa->data->id); + return 1; + } + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: LSA Id %pI4, TRANSLATING 7 to 5", __func__, + &lsa->data->id); + + ext7 = (struct as_external_lsa *)(lsa->data); + p.prefix = lsa->data->id; + p.prefixlen = ip_masklen(ext7->mask); + + if (ext7->e[0].fwd_addr.s_addr == OSPF_DEFAULT_DESTINATION) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: LSA Id %pI4, Forward address is 0, NO Translation", + __func__, &lsa->data->id); + return 1; + } + + /* try find existing AS-External LSA for this prefix */ + old = ospf_external_info_find_lsa(area->ospf, &p); + + if (CHECK_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE)) { + /* if type-7 is removed, remove old translated type-5 lsa */ + if (old) { + UNSET_FLAG(old->flags, OSPF_LSA_APPROVED); + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: remove old translated LSA id %pI4", + __func__, &old->data->id); + } + /* if type-7 is removed and type-5 does not exist, do not + * originate */ + return 1; + } + + range = ospf_area_range_match(area, area->nssa_ranges, &p); + if (range) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("Suppressed by range %pI4/%u of area %pI4", + &range->addr, range->masklen, + &area->area_id); + + ospf_abr_update_aggregate(range, GET_METRIC(ext7->e[0].metric), + area); + return 1; + } + + if (old && CHECK_FLAG(old->flags, OSPF_LSA_APPROVED)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: found old translated LSA Id %pI4, refreshing", + __func__, &old->data->id); + + /* refresh */ + new = ospf_translated_nssa_refresh(area->ospf, lsa, old); + if (!new) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: could not refresh translated LSA Id %pI4", + __func__, &old->data->id); + } + } else { + /* no existing external route for this LSA Id + * originate translated LSA + */ + + if (ospf_translated_nssa_originate(area->ospf, lsa, old) + == NULL) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: Could not translate Type-7 for %pI4 to Type-5", + __func__, &lsa->data->id); + return 1; + } + } + + return 0; +} + +static void ospf_abr_translate_nssa_range(struct ospf *ospf, + struct prefix_ipv4 *p, uint32_t cost) +{ + struct external_info ei = {}; + struct ospf_lsa *lsa; + + prefix_copy(&ei.p, p); + ei.type = ZEBRA_ROUTE_OSPF; + ei.route_map_set.metric = cost; + ei.route_map_set.metric_type = -1; + + lsa = ospf_external_info_find_lsa(ospf, p); + if (lsa) + lsa = ospf_external_lsa_refresh(ospf, lsa, &ei, + LSA_REFRESH_FORCE, true); + else + lsa = ospf_external_lsa_originate(ospf, &ei); + SET_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT); +} + +void ospf_abr_announce_network_to_area(struct prefix_ipv4 *p, uint32_t cost, + struct ospf_area *area) +{ + struct ospf_lsa *lsa, *old = NULL; + struct summary_lsa *sl = NULL; + uint32_t full_cost; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) + full_cost = OSPF_STUB_MAX_METRIC_SUMMARY_COST; + else + full_cost = cost; + + old = ospf_lsa_lookup_by_prefix(area->lsdb, OSPF_SUMMARY_LSA, p, + area->ospf->router_id); + if (old) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old summary found", __func__); + + sl = (struct summary_lsa *)old->data; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old metric: %d, new metric: %d", + __func__, GET_METRIC(sl->metric), cost); + + if ((GET_METRIC(sl->metric) == full_cost) + && ((old->flags & OSPF_LSA_IN_MAXAGE) == 0)) { + /* unchanged. simply reapprove it */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old summary approved", + __func__); + SET_FLAG(old->flags, OSPF_LSA_APPROVED); + } else { + /* LSA is changed, refresh it */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: refreshing summary", __func__); + set_metric(old, full_cost); + lsa = ospf_lsa_refresh(area->ospf, old); + + if (!lsa) { + flog_warn(EC_OSPF_LSA_MISSING, + "%s: Could not refresh %pFX to %pI4", + __func__, (struct prefix *)p, + &area->area_id); + return; + } + + SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + /* This will flood through area. */ + } + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: creating new summary", __func__); + lsa = ospf_summary_lsa_originate(p, full_cost, area); + /* This will flood through area. */ + + if (!lsa) { + flog_warn(EC_OSPF_LSA_MISSING, + "%s: Could not originate %pFX to %pi4", + __func__, (struct prefix *)p, + &area->area_id); + return; + } + + SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: flooding new version of summary", + __func__); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static int ospf_abr_nexthops_belong_to_area(struct ospf_route * or, + struct ospf_area *area) +{ + struct listnode *node, *nnode; + struct ospf_path *path; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) + for (ALL_LIST_ELEMENTS_RO(area->oiflist, nnode, oi)) + if (oi->ifp && oi->ifp->ifindex == path->ifindex) + return 1; + + return 0; +} + +static int ospf_abr_should_accept(struct prefix_ipv4 *p, struct ospf_area *area) +{ + if (IMPORT_NAME(area)) { + if (IMPORT_LIST(area) == NULL) + IMPORT_LIST(area) = + access_list_lookup(AFI_IP, IMPORT_NAME(area)); + + if (IMPORT_LIST(area)) + if (access_list_apply(IMPORT_LIST(area), p) + == FILTER_DENY) + return 0; + } + + return 1; +} + +static int ospf_abr_plist_in_check(struct ospf_area *area, + struct ospf_route * or, + struct prefix_ipv4 *p) +{ + if (PREFIX_NAME_IN(area)) { + if (PREFIX_LIST_IN(area) == NULL) + PREFIX_LIST_IN(area) = prefix_list_lookup( + AFI_IP, PREFIX_NAME_IN(area)); + if (PREFIX_LIST_IN(area)) + if (prefix_list_apply(PREFIX_LIST_IN(area), p) + != PREFIX_PERMIT) + return 0; + } + return 1; +} + +static int ospf_abr_plist_out_check(struct ospf_area *area, + struct ospf_route * or, + struct prefix_ipv4 *p) +{ + if (PREFIX_NAME_OUT(area)) { + if (PREFIX_LIST_OUT(area) == NULL) + PREFIX_LIST_OUT(area) = prefix_list_lookup( + AFI_IP, PREFIX_NAME_OUT(area)); + if (PREFIX_LIST_OUT(area)) + if (prefix_list_apply(PREFIX_LIST_OUT(area), p) + != PREFIX_PERMIT) + return 0; + } + return 1; +} + +static void ospf_abr_announce_network(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + struct ospf_area_range *range; + struct ospf_area *area, *or_area; + struct listnode *node; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + or_area = ospf_area_lookup_by_area_id(ospf, or->u.std.area_id); + assert(or_area); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) + continue; + + if (ospf_abr_nexthops_belong_to_area(or, area)) + continue; + + if (!ospf_abr_should_accept(p, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: prefix %pFX was denied by import-list", + __func__, p); + continue; + } + + if (!ospf_abr_plist_in_check(area, or, p)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: prefix %pFX was denied by prefix-list", + __func__, p); + continue; + } + + if (area->external_routing != OSPF_AREA_DEFAULT + && area->no_summary) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: area %pI4 is stub and no_summary", + __func__, &area->area_id); + continue; + } + + if (or->path_type == OSPF_PATH_INTER_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is inter-area route to %pFX", + __func__, p); + + if (!OSPF_IS_AREA_BACKBONE(area)) + ospf_abr_announce_network_to_area(p, or->cost, + area); + } + + if (or->path_type == OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is intra-area route to %pFX", + __func__, p); + if ((range = ospf_area_range_match( + or_area, or_area->ranges, p)) && + !ospf_area_is_transit(area)) + ospf_abr_update_aggregate(range, or->cost, + area); + else + ospf_abr_announce_network_to_area(p, or->cost, + area); + } + } +} + +static int ospf_abr_should_announce(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, or->u.std.area_id); + + assert(area); + + if (EXPORT_NAME(area)) { + if (EXPORT_LIST(area) == NULL) + EXPORT_LIST(area) = + access_list_lookup(AFI_IP, EXPORT_NAME(area)); + + if (EXPORT_LIST(area)) + if (access_list_apply(EXPORT_LIST(area), p) + == FILTER_DENY) + return 0; + } + + return 1; +} + +static void ospf_abr_process_nssa_translates(struct ospf *ospf) +{ + /* Scan through all NSSA_LSDB records for all areas; + + If P-bit is on, translate all Type-7's to 5's and aggregate or + flood install as approved in Type-5 LSDB with XLATE Flag on + later, do same for all aggregates... At end, DISCARD all + remaining UNAPPROVED Type-5's (Aggregate is for future ) */ + struct listnode *node; + struct ospf_area *area; + struct route_node *rn; + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (!area->NSSATranslatorState) + continue; /* skip if not translator */ + + if (area->external_routing != OSPF_AREA_NSSA) + continue; /* skip if not Nssa Area */ + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s(): looking at area %pI4", __func__, + &area->area_id); + + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + ospf_abr_translate_nssa(area, lsa); + } + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_process_network_rt(struct ospf *ospf, + struct route_table *rt) +{ + struct ospf_area *area; + struct ospf_route * or ; + struct route_node *rn; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + if ((or = rn->info) == NULL) + continue; + + if (!(area = ospf_area_lookup_by_area_id(ospf, + or->u.std.area_id))) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: area %pI4 no longer exists", __func__, + &or->u.std.area_id); + continue; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this is a route to %pFX", __func__, + &rn->p); + if (or->path_type >= OSPF_PATH_TYPE1_EXTERNAL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is an External router, skipping", + __func__); + continue; + } + + if (or->cost >= OSPF_LS_INFINITY) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this route's cost is infinity, skipping", + __func__); + continue; + } + + if (or->type == OSPF_DESTINATION_DISCARD) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is a discard entry, skipping", + __func__); + continue; + } + + if ( + or->path_type == OSPF_PATH_INTRA_AREA + && !ospf_abr_should_announce( + ospf, (struct prefix_ipv4 *)&rn->p, + or)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: denied by export-list", + __func__); + continue; + } + + if ( + or->path_type == OSPF_PATH_INTRA_AREA + && !ospf_abr_plist_out_check( + area, or, + (struct prefix_ipv4 *)&rn->p)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: denied by prefix-list", + __func__); + continue; + } + + if ((or->path_type == OSPF_PATH_INTER_AREA) + && !OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this route is not backbone one, skipping", + __func__); + continue; + } + + + if ((ospf->abr_type == OSPF_ABR_CISCO) + || (ospf->abr_type == OSPF_ABR_IBM)) + + if (!ospf_act_bb_connection(ospf) && + or->path_type != OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ALT ABR: No BB connection, skip not intra-area routes", + __func__); + continue; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: announcing", __func__); + ospf_abr_announce_network(ospf, (struct prefix_ipv4 *)&rn->p, + or); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_announce_rtr_to_area(struct prefix_ipv4 *p, uint32_t cost, + struct ospf_area *area) +{ + struct ospf_lsa *lsa, *old = NULL; + struct summary_lsa *slsa = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + old = ospf_lsa_lookup_by_prefix(area->lsdb, OSPF_ASBR_SUMMARY_LSA, p, + area->ospf->router_id); + if (old) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old summary found", __func__); + slsa = (struct summary_lsa *)old->data; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old metric: %d, new metric: %d", + __func__, GET_METRIC(slsa->metric), cost); + } + + if (old && (GET_METRIC(slsa->metric) == cost) + && ((old->flags & OSPF_LSA_IN_MAXAGE) == 0)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old summary approved", __func__); + SET_FLAG(old->flags, OSPF_LSA_APPROVED); + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: 2.2", __func__); + + if (old) { + set_metric(old, cost); + lsa = ospf_lsa_refresh(area->ospf, old); + } else + lsa = ospf_summary_asbr_lsa_originate(p, cost, area); + if (!lsa) { + flog_warn(EC_OSPF_LSA_MISSING, + "%s: Could not refresh/originate %pFX to %pI4", + __func__, (struct prefix *)p, + &area->area_id); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: flooding new version of summary", + __func__); + + /* + zlog_info ("ospf_abr_announce_rtr_to_area(): creating new + summary"); + lsa = ospf_summary_asbr_lsa (p, cost, area, old); */ + + SET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + /* ospf_flood_through_area (area, NULL, lsa);*/ + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + + +static void ospf_abr_announce_rtr(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + struct listnode *node; + struct ospf_area *area; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) + continue; + + if (ospf_abr_nexthops_belong_to_area(or, area)) + continue; + + /* RFC3101: Do not generate ASBR type 4 LSA if NSSA ABR */ + if (or->u.std.external_routing == OSPF_AREA_NSSA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: do not generate LSA Type-4 %pI4 from NSSA", + __func__, &p->prefix); + continue; + } + + if (area->external_routing != OSPF_AREA_DEFAULT) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: area %pI4 doesn't support external routing", + __func__, &area->area_id); + continue; + } + + if (or->path_type == OSPF_PATH_INTER_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is inter-area route to %pI4", + __func__, &p->prefix); + if (!OSPF_IS_AREA_BACKBONE(area)) + ospf_abr_announce_rtr_to_area(p, or->cost, + area); + } + + if (or->path_type == OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this is intra-area route to %pI4", + __func__, &p->prefix); + ospf_abr_announce_rtr_to_area(p, or->cost, area); + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_process_router_rt(struct ospf *ospf, + struct route_table *rt) +{ + struct ospf_route * or ; + struct route_node *rn; + struct list *l; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + struct listnode *node, *nnode; + char flag = 0; + struct ospf_route *best = NULL; + + if (rn->info == NULL) + continue; + + l = rn->info; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this is a route to %pI4", __func__, + &rn->p.u.prefix4); + + for (ALL_LIST_ELEMENTS(l, node, nnode, or)) { + if (!ospf_area_lookup_by_area_id(ospf, + or->u.std.area_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: area %pI4 no longer exists", __func__, + &or->u.std.area_id); + continue; + } + + + if (!CHECK_FLAG(or->u.std.flags, ROUTER_LSA_EXTERNAL)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: This is not an ASBR, skipping", + __func__); + continue; + } + + if (!flag) { + best = ospf_find_asbr_route( + ospf, rt, (struct prefix_ipv4 *)&rn->p); + flag = 1; + } + + if (best == NULL) + continue; + + if (or != best) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: This route is not the best among possible, skipping", + __func__); + continue; + } + + if ( + or->path_type == OSPF_PATH_INTER_AREA + && !OSPF_IS_AREA_ID_BACKBONE( + or->u.std.area_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: This route is not a backbone one, skipping", + __func__); + continue; + } + + if (or->cost >= OSPF_LS_INFINITY) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: This route has LS_INFINITY metric, skipping", + __func__); + continue; + } + + if (ospf->abr_type == OSPF_ABR_CISCO + || ospf->abr_type == OSPF_ABR_IBM) + if (!ospf_act_bb_connection(ospf) && + or->path_type != OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ALT ABR: No BB connection, skip not intra-area routes", + __func__); + continue; + } + + ospf_abr_announce_rtr(ospf, + (struct prefix_ipv4 *)&rn->p, or); + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void +ospf_abr_unapprove_translates(struct ospf *ospf) /* For NSSA Translations */ +{ + struct ospf_lsa *lsa; + struct route_node *rn; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Start", __func__); + + /* NSSA Translator is not checked, because it may have gone away, + and we would want to flush any residuals anyway */ + + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) { + UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: approved unset on link id %pI4", + __func__, &lsa->data->id); + } + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_unapprove_summaries(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_area *area; + struct route_node *rn; + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: considering area %pI4", __func__, + &area->area_id); + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + if (ospf_lsa_is_self_originated(ospf, lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: approved unset on summary link id %pI4", + __func__, &lsa->data->id); + UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + } + + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + if (ospf_lsa_is_self_originated(ospf, lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: approved unset on asbr-summary link id %pI4", + __func__, &lsa->data->id); + UNSET_FLAG(lsa->flags, OSPF_LSA_APPROVED); + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_prepare_aggregates(struct ospf *ospf, bool nssa) +{ + struct listnode *node; + struct route_node *rn; + struct ospf_area_range *range; + struct ospf_area *area; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + struct route_table *ranges; + + if (nssa) + ranges = area->nssa_ranges; + else + ranges = area->ranges; + + for (rn = route_top(ranges); rn; rn = route_next(rn)) + if ((range = rn->info) != NULL) { + range->cost = 0; + range->specifics = 0; + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_announce_aggregates(struct ospf *ospf) +{ + struct ospf_area *area, *ar; + struct ospf_area_range *range; + struct route_node *rn; + struct prefix p; + struct listnode *node, *n; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + for (rn = route_top(area->ranges); rn; rn = route_next(rn)) + if ((range = rn->info)) { + if (!CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_ADVERTISE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: discarding suppress-ranges", + __func__); + continue; + } + + p.family = AF_INET; + p.u.prefix4 = range->addr; + p.prefixlen = range->masklen; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this is range: %pFX", + __func__, &p); + + if (CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_SUBSTITUTE)) { + p.family = AF_INET; + p.u.prefix4 = range->subst_addr; + p.prefixlen = range->subst_masklen; + } + + if (ospf_area_range_active(range)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: active range", + __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, + n, ar)) { + if (ar == area) + continue; + + /* We do not check nexthops + here, because + intra-area routes can be + associated with + one area only */ + + /* backbone routes are not + summarized + when announced into transit + areas */ + + if (ospf_area_is_transit(ar) + && OSPF_IS_AREA_BACKBONE( + area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Skipping announcement of BB aggregate into a transit area", + __func__); + continue; + } + ospf_abr_announce_network_to_area( + (struct prefix_ipv4 + *)&p, + range->cost, ar); + } + } + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_send_nssa_aggregates(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_area *area; + struct route_node *rn; + struct prefix_ipv4 p; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (!area->NSSATranslatorState) + continue; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + for (rn = route_top(area->nssa_ranges); rn; + rn = route_next(rn)) { + struct ospf_area_range *range; + + range = rn->info; + if (!range) + continue; + + p.family = AF_INET; + p.prefix = range->addr; + p.prefixlen = range->masklen; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: this is range: %pFX", __func__, + &p); + + if (ospf_area_range_active(range) + && CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_ADVERTISE)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: active range", + __func__); + + /* Fetch LSA-Type-7 from aggregate prefix, and + * then + * translate, Install (as Type-5), Approve, and + * Flood + */ + ospf_abr_translate_nssa_range(ospf, &p, + range->cost); + } + } /* all area ranges*/ + } /* all areas */ + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_announce_stub_defaults(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_area *area; + struct prefix_ipv4 p; + + if (!IS_OSPF_ABR(ospf)) + return; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + p.family = AF_INET; + p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; + p.prefixlen = 0; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + if ((area->external_routing != OSPF_AREA_STUB) + && (area->external_routing != OSPF_AREA_NSSA)) + continue; + + if (OSPF_IS_AREA_BACKBONE(area)) + continue; /* Sanity Check */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: announcing 0.0.0.0/0 to area %pI4", + __func__, &area->area_id); + ospf_abr_announce_network_to_area(&p, area->default_cost, area); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +/** @brief Function to check and generate indication + * LSA for area on which we received + * indication LSA flush. + * @param Ospf instance. + * @param Area on which indication lsa flush is to be generated. + * @return Void. + */ +void ospf_generate_indication_lsa(struct ospf *ospf, struct ospf_area *area) +{ + bool area_fr_not_supp = false; + + /* Check if you have any area which doesn't support + * flood reduction. + */ + + area_fr_not_supp = ospf_check_fr_enabled_all(ospf) ? false : true; + + /* If any one of the area doestn't support FR, generate + * indication LSA on behalf of that area. + */ + + if (area_fr_not_supp && !area->fr_info.area_ind_lsa_recvd && + !area->fr_info.indication_lsa_self && + !area->fr_info.area_dc_clear) { + + struct prefix_ipv4 p; + struct ospf_lsa *new; + + p.family = AF_INET; + p.prefix = ospf->router_id; + p.prefixlen = IPV4_MAX_BITLEN; + + new = ospf_summary_asbr_lsa_originate(&p, OSPF_LS_INFINITY, + area); + if (!new) { + zlog_debug("%s: Indication lsa originate failed", + __func__); + return; + } + /* save the indication lsa for that area */ + area->fr_info.indication_lsa_self = new; + } +} + +/** @brief Function to receive and process indication LSA + * flush from area. + * @param lsa being flushed. + * @return Void. + */ +void ospf_recv_indication_lsa_flush(struct ospf_lsa *lsa) +{ + if (!IS_LSA_SELF(lsa) && IS_LSA_MAXAGE(lsa) && + ospf_check_indication_lsa(lsa)) { + lsa->area->fr_info.area_ind_lsa_recvd = false; + + OSPF_LOG_INFO("%s: Received an ind lsa: %pI4 area %pI4", + __func__, &lsa->data->id, &lsa->area->area_id); + + if (!IS_OSPF_ABR(lsa->area->ospf)) + return; + + /* If the LSA received is a indication LSA with maxage on + * the network, then check and regenerate indication + * LSA if any of our areas don't support flood reduction. + */ + ospf_generate_indication_lsa(lsa->area->ospf, lsa->area); + } +} + +/** @brief Function to generate indication LSAs. + * @param Ospf instance. + * @param Area on behalf of which indication + * LSA is generated LSA. + * @return Void. + */ +void ospf_abr_generate_indication_lsa(struct ospf *ospf, + const struct ospf_area *area) +{ + struct ospf_lsa *new; + struct listnode *node; + struct ospf_area *o_area; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, o_area)) { + if (o_area == area) + continue; + + if (o_area->fr_info.indication_lsa_self || + o_area->fr_info.area_ind_lsa_recvd || + o_area->fr_info.area_dc_clear) { + /* if the area has already received an + * indication LSA or if area already has + * LSAs with DC bit 0 other than + * indication LSA then don't generate + * indication LSA in those areas. + */ + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "Area %pI4 has LSAs with dc bit clear", + &o_area->area_id); + continue; + + } else { + + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefix = ospf->router_id; + p.prefixlen = IPV4_MAX_BITLEN; + + new = ospf_summary_asbr_lsa_originate( + &p, OSPF_LS_INFINITY, o_area); + if (!new) { + zlog_debug( + "%s: Indication lsa originate Failed", + __func__); + return; + } + /* save the indication lsa for that area */ + o_area->fr_info.indication_lsa_self = new; + } + } +} + +/** @brief Flush the indication LSA from all the areas + * of ospf instance. + * @param Ospf instance. + * @return Void. + */ +void ospf_flush_indication_lsas(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (area->fr_info.indication_lsa_self) { + OSPF_LOG_INFO( + "Flushing ind lsa: %pI4 area %pI4", + &area->fr_info.indication_lsa_self->data->id, + &area->area_id); + ospf_schedule_lsa_flush_area( + area, area->fr_info.indication_lsa_self); + area->fr_info.indication_lsa_self = NULL; + } + } +} + +/** @brief Check if flood reduction is enabled on + * all the areas. + * @param Ospf instance. + * @return Void. + */ +bool ospf_check_fr_enabled_all(struct ospf *ospf) +{ + const struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (!ospf_check_area_fr_enabled(area)) + return false; + + return true; +} + +/** @brief Abr function to check conditions for generation + * of indication. LSAs/announcing non-DNA routers + * in the area. + * @param thread + * @return 0. + */ +static void ospf_abr_announce_non_dna_routers(struct event *thread) +{ + struct ospf_area *area; + struct listnode *node; + struct ospf *ospf = EVENT_ARG(thread); + + EVENT_OFF(ospf->t_abr_fr); + + if (!IS_OSPF_ABR(ospf)) + return; + + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, "%s(): Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "%s: Area %pI4 FR enabled: %d", __func__, + &area->area_id, area->fr_info.enabled); + OSPF_LOG_DEBUG( + IS_DEBUG_OSPF_EVENT, + "LSA with DC bit clear: %d Recived indication LSA: %d", + area->fr_info.area_dc_clear, + area->fr_info.area_ind_lsa_recvd); + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, "FR state change: %d", + area->fr_info.state_changed); + if (!OSPF_IS_AREA_BACKBONE(area) && + area->fr_info.area_dc_clear) { + /* rfc4136 rfc1793: Suppose if the abr is connected to + * a regular non-backbone OSPF area, Furthermore if + * the area has LSAs with the DC-bit clear, other + * than indication-LSAs. Then originate indication-LSAs + * into all other directly-connected "regular" areas, + * including the backbone area. + */ + ospf_abr_generate_indication_lsa(ospf, area); + } + + if (OSPF_IS_AREA_BACKBONE(area) && + (area->fr_info.area_dc_clear || + area->fr_info.area_ind_lsa_recvd)) { + /* rfc4136 rfc1793: Suppose if the abr is connected to + * backbone OSPF area. Furthermore, if backbone has + * LSAs with the DC-bit clear that are either + * a) not indication-LSAs or indication-LSAs or + * b) indication-LSAs that have been originated by + * other routers, + * then originate indication-LSAs into all other + * directly-connected "regular" non-backbone areas. + */ + ospf_abr_generate_indication_lsa(ospf, area); + } + + if (area->fr_info.enabled && area->fr_info.state_changed && + area->fr_info.indication_lsa_self) { + /* Ospf area flood reduction state changed + * area now supports flood reduction. + * check if all other areas support flood reduction + * if yes then flush indication LSAs generated in + * all the areas. + */ + if (ospf_check_fr_enabled_all(ospf)) + ospf_flush_indication_lsas(ospf); + + area->fr_info.state_changed = false; + } + + /* If previously we had generated indication lsa + * but now area has lsas with dc bit set to 0 + * apart from indication lsa, we'll clear indication lsa + */ + if (area->fr_info.area_dc_clear && + area->fr_info.indication_lsa_self) { + ospf_schedule_lsa_flush_area( + area, area->fr_info.indication_lsa_self); + area->fr_info.indication_lsa_self = NULL; + } + } + + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, "%s(): Stop", __func__); +} + +static void ospf_abr_nssa_type7_default_create(struct ospf *ospf, + struct ospf_area *area, + struct ospf_lsa *lsa) +{ + struct external_info ei; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "Announcing Type-7 default route into NSSA area %pI4", + &area->area_id); + + /* Prepare the extrenal_info for aggregator */ + memset(&ei, 0, sizeof(struct external_info)); + ei.p.family = AF_INET; + ei.p.prefixlen = 0; + ei.tag = 0; + ei.type = 0; + ei.instance = ospf->instance; + + /* Compute default route type and metric. */ + if (area->nssa_default_originate.metric_value != -1) + ei.route_map_set.metric = + area->nssa_default_originate.metric_value; + else + ei.route_map_set.metric = DEFAULT_DEFAULT_ALWAYS_METRIC; + if (area->nssa_default_originate.metric_type != -1) + ei.route_map_set.metric_type = + area->nssa_default_originate.metric_type; + else + ei.route_map_set.metric_type = DEFAULT_METRIC_TYPE; + + if (!lsa) + ospf_nssa_lsa_originate(area, &ei); + else + ospf_nssa_lsa_refresh(area, lsa, &ei); +} + +static void ospf_abr_nssa_type7_default_delete(struct ospf *ospf, + struct ospf_area *area, + struct ospf_lsa *lsa) +{ + if (lsa && !CHECK_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "Withdrawing Type-7 default route from area %pI4", + &area->area_id); + + ospf_ls_retransmit_delete_nbr_area(area, lsa); + ospf_refresher_unregister_lsa(ospf, lsa); + ospf_lsa_flush_area(lsa, area); + } +} + +/* NSSA Type-7 default route. */ +void ospf_abr_nssa_type7_defaults(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + struct in_addr id = {}; + struct ospf_lsa *lsa; + + lsa = ospf_lsdb_lookup_by_id(area->lsdb, OSPF_AS_NSSA_LSA, id, + area->ospf->router_id); + if (area->external_routing == OSPF_AREA_NSSA + && area->nssa_default_originate.enabled + && (IS_OSPF_ABR(ospf) + || (IS_OSPF_ASBR(ospf) + && ospf->nssa_default_import_check.status))) + ospf_abr_nssa_type7_default_create(ospf, area, lsa); + else + ospf_abr_nssa_type7_default_delete(ospf, area, lsa); + } +} + +static int ospf_abr_remove_unapproved_translates_apply(struct ospf *ospf, + struct ospf_lsa *lsa) +{ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT) + && !CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED)) { + zlog_info("%s: removing unapproved translates, ID: %pI4", + __func__, &lsa->data->id); + + /* FLUSH THROUGHOUT AS */ + ospf_lsa_flush_as(ospf, lsa); + + /* DISCARD from LSDB */ + } + return 0; +} + +static void ospf_abr_remove_unapproved_translates(struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + + /* All AREA PROCESS should have APPROVED necessary LSAs */ + /* Remove any left over and not APPROVED */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Start", __func__); + + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + ospf_abr_remove_unapproved_translates_apply(ospf, lsa); + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_remove_unapproved_summaries(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_area *area; + struct route_node *rn; + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: looking at area %pI4", __func__, + &area->area_id); + + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + if (ospf_lsa_is_self_originated(ospf, lsa)) + if (!CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED)) + ospf_lsa_flush_area(lsa, area); + + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + if (ospf_lsa_is_self_originated(ospf, lsa) && + !CHECK_FLAG(lsa->flags, OSPF_LSA_APPROVED) && + /* Do not remove indication LSAs while + * flushing unapproved summaries. + */ + !ospf_check_indication_lsa(lsa)) + ospf_lsa_flush_area(lsa, area); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_manage_discard_routes(struct ospf *ospf, bool nssa) +{ + struct listnode *node, *nnode; + struct route_node *rn; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + struct route_table *ranges; + + if (nssa) + ranges = area->nssa_ranges; + else + ranges = area->ranges; + + for (rn = route_top(ranges); rn; rn = route_next(rn)) { + struct ospf_area_range *range; + + range = rn->info; + if (!range) + continue; + + if (ospf_area_range_active(range) + && CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_ADVERTISE)) + ospf_add_discard_route( + ospf, ospf->new_table, area, + (struct prefix_ipv4 *)&rn->p, nssa); + else + ospf_delete_discard_route( + ospf, ospf->new_table, + (struct prefix_ipv4 *)&rn->p, nssa); + } + } +} + +/* This is the function taking care about ABR NSSA, i.e. NSSA + Translator, -LSA aggregation and flooding. For all NSSAs + + Any SELF-AS-LSA is in the Type-5 LSDB and Type-7 LSDB. These LSA's + are refreshed from the Type-5 LSDB, installed into the Type-7 LSDB + with the P-bit set. + + Any received Type-5s are legal for an ABR, else illegal for IR. + Received Type-7s are installed, by area, with incoming P-bit. They + are flooded; if the Elected NSSA Translator, then P-bit off. + + Additionally, this ABR will place "translated type-7's" into the + Type-5 LSDB in order to keep track of APPROVAL or not. + + It will scan through every area, looking for Type-7 LSAs with P-Bit + SET. The Type-7's are either AS-FLOODED & 5-INSTALLED or + AGGREGATED. Later, the AGGREGATED LSAs are AS-FLOODED & + 5-INSTALLED. + + 5-INSTALLED is into the Type-5 LSDB; Any UNAPPROVED Type-5 LSAs + left over are FLUSHED and DISCARDED. + + For External Calculations, any NSSA areas use the Type-7 AREA-LSDB, + any ABR-non-NSSA areas use the Type-5 GLOBAL-LSDB. */ + +void ospf_abr_nssa_task(struct ospf *ospf) /* called only if any_nssa */ +{ + if (ospf->gr_info.restart_in_progress) + return; + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("Check for NSSA-ABR Tasks():"); + + if (!IS_OSPF_ABR(ospf)) + return; + + if (!ospf->anyNSSA) + return; + + /* Each area must confirm TranslatorRole */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Start", __func__); + + /* For all Global Entries flagged "local-translate", unset APPROVED */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: unapprove translates", __func__); + + ospf_abr_unapprove_translates(ospf); + + /* RESET all Ranges in every Area, same as summaries */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: NSSA initialize aggregates", __func__); + ospf_abr_prepare_aggregates(ospf, true); + + /* For all NSSAs, Type-7s, translate to 5's, INSTALL/FLOOD, or + * Aggregate as Type-7 + * Install or Approve in Type-5 Global LSDB + */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: process translates", __func__); + ospf_abr_process_nssa_translates(ospf); + + /* Translate/Send any "ranged" aggregates, and also 5-Install and + * Approve + * Scan Type-7's for aggregates, translate to Type-5's, + * Install/Flood/Approve + */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: send NSSA aggregates", __func__); + ospf_abr_send_nssa_aggregates(ospf); /*TURNED OFF FOR NOW */ + + /* Send any NSSA defaults as Type-5 + *if (IS_DEBUG_OSPF_NSSA) + * zlog_debug ("ospf_abr_nssa_task(): announce nssa defaults"); + *ospf_abr_announce_nssa_defaults (ospf); + * havnt a clue what above is supposed to do. + */ + + /* Flush any unapproved previous translates from Global Data Base */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: remove unapproved translates", __func__); + ospf_abr_remove_unapproved_translates(ospf); + + ospf_abr_manage_discard_routes(ospf, true); + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Stop", __func__); +} + +/* This is the function taking care about ABR stuff, i.e. + summary-LSA origination and flooding. */ +void ospf_abr_task(struct ospf *ospf) +{ + if (ospf->gr_info.restart_in_progress) + return; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + if (ospf->new_table == NULL || ospf->new_rtrs == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Routing tables are not yet ready", + __func__); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: unapprove summaries", __func__); + ospf_abr_unapprove_summaries(ospf); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: prepare aggregates", __func__); + ospf_abr_prepare_aggregates(ospf, false); + + if (IS_OSPF_ABR(ospf)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: process network RT", __func__); + ospf_abr_process_network_rt(ospf, ospf->new_table); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: process router RT", __func__); + ospf_abr_process_router_rt(ospf, ospf->new_rtrs); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: announce aggregates", __func__); + ospf_abr_announce_aggregates(ospf); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: announce stub defaults", __func__); + ospf_abr_announce_stub_defaults(ospf); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: announce NSSA Type-7 defaults", + __func__); + ospf_abr_nssa_type7_defaults(ospf); + + if (ospf->fr_configured) { + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "%s(): announce non-DNArouters", + __func__); + /* + * Schedule indication lsa generation timer, + * giving time for route synchronization in + * all the routers. + */ + event_add_timer(master, + ospf_abr_announce_non_dna_routers, ospf, + OSPF_ABR_DNA_TIMER, &ospf->t_abr_fr); + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: remove unapproved summaries", __func__); + ospf_abr_remove_unapproved_summaries(ospf); + + ospf_abr_manage_discard_routes(ospf, false); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static void ospf_abr_task_timer(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + + ospf->t_abr_task = 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Running ABR task on timer"); + + ospf_check_abr_status(ospf); + ospf_abr_nssa_check_status(ospf); + + ospf_abr_task(ospf); + ospf_abr_nssa_task(ospf); /* if nssa-abr, then scan Type-7 LSDB */ +} + +void ospf_schedule_abr_task(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Scheduling ABR task"); + + event_add_timer(master, ospf_abr_task_timer, ospf, OSPF_ABR_TASK_DELAY, + &ospf->t_abr_task); +} diff --git a/ospfd/ospf_abr.h b/ospfd/ospf_abr.h new file mode 100644 index 0000000..cc2b2b0 --- /dev/null +++ b/ospfd/ospf_abr.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF ABR functions. + * Copyright (C) 1999 Alex Zinin + */ + +#ifndef _ZEBRA_OSPF_ABR_H +#define _ZEBRA_OSPF_ABR_H + +#define OSPF_ABR_TASK_DELAY 5 +#define OSPF_ABR_DNA_TIMER 10 +/* Delay in announceing Non-DNA routers + * so that LSAs are completely synced + * before generating indication LSAs. + */ + +#define OSPF_AREA_RANGE_ADVERTISE (1 << 0) +#define OSPF_AREA_RANGE_SUBSTITUTE (1 << 1) +#define OSPF_AREA_RANGE_NSSA (1 << 2) + +/* Area range. */ +struct ospf_area_range { + /* Area range address. */ + struct in_addr addr; + + /* Area range masklen. */ + uint8_t masklen; + + /* Flags. */ + uint8_t flags; + + /* Number of more specific prefixes. */ + int specifics; + + /* Addr and masklen to substitute. */ + struct in_addr subst_addr; + uint8_t subst_masklen; + + /* Range cost. */ + uint32_t cost; + + /* Configured range cost. */ + uint32_t cost_config; +}; + +/* Prototypes. */ +extern struct ospf_area_range *ospf_area_range_lookup(struct ospf_area *, + struct route_table *, + struct prefix_ipv4 *); +extern struct ospf_area_range * +ospf_area_range_lookup_next(struct ospf_area *, struct in_addr *, int); + +extern int ospf_area_range_set(struct ospf *, struct ospf_area *, + struct route_table *, struct prefix_ipv4 *, int, + bool); +extern int ospf_area_range_cost_set(struct ospf *, struct ospf_area *, + struct route_table *, struct prefix_ipv4 *, + uint32_t); +extern int ospf_area_range_unset(struct ospf *, struct ospf_area *, + struct route_table *, struct prefix_ipv4 *); +extern int ospf_area_range_substitute_set(struct ospf *, struct ospf_area *, + struct prefix_ipv4 *, + struct prefix_ipv4 *); +extern int ospf_area_range_substitute_unset(struct ospf *, struct ospf_area *, + struct prefix_ipv4 *); +extern struct ospf_area_range *ospf_area_range_match_any(struct ospf *, + struct prefix_ipv4 *); +extern int ospf_area_range_active(struct ospf_area_range *); +extern int ospf_act_bb_connection(struct ospf *); + +extern void ospf_check_abr_status(struct ospf *); +extern void ospf_abr_task(struct ospf *); +extern void ospf_abr_nssa_task(struct ospf *ospf); +extern void ospf_schedule_abr_task(struct ospf *); + +extern void ospf_abr_announce_network_to_area(struct prefix_ipv4 *, uint32_t, + struct ospf_area *); +extern void ospf_abr_nssa_type7_defaults(struct ospf *ospf); +extern void ospf_abr_nssa_check_status(struct ospf *ospf); +extern void ospf_abr_generate_indication_lsa(struct ospf *ospf, + const struct ospf_area *area); +extern void ospf_flush_indication_lsas(struct ospf *ospf); +extern void ospf_generate_indication_lsa(struct ospf *ospf, + struct ospf_area *area); +extern bool ospf_check_fr_enabled_all(struct ospf *ospf); +extern void ospf_recv_indication_lsa_flush(struct ospf_lsa *lsa); + +/** @brief Static inline functions. + * @param Area pointer. + * @return area Flood Reduction status. + */ +static inline bool ospf_check_area_fr_enabled(const struct ospf_area *area) +{ + return area->fr_info.enabled ? true : false; +} +#endif /* _ZEBRA_OSPF_ABR_H */ diff --git a/ospfd/ospf_api.c b/ospfd/ospf_api.c new file mode 100644 index 0000000..213ee8c --- /dev/null +++ b/ospfd/ospf_api.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * API message handling module for OSPF daemon and client. + * Copyright (C) 2001, 2002 Ralph Keller + * Copyright (c) 2022, LabN Consulting, L.L.C. + */ + +#include + +#ifdef SUPPORT_OSPF_API + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "buffer.h" +#include "network.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" + +#include "ospfd/ospf_api.h" + + +/* For debugging only, will be removed */ +void api_opaque_lsa_print(struct ospf_lsa *lsa) +{ + struct opaque_lsa { + struct lsa_header header; + uint8_t mydata[]; + }; + + struct opaque_lsa *olsa; + int opaquelen; + int i; + + ospf_lsa_header_dump(lsa->data); + + olsa = (struct opaque_lsa *)lsa->data; + + opaquelen = lsa->size - OSPF_LSA_HEADER_SIZE; + zlog_debug("apiserver_lsa_print: opaquelen=%d", opaquelen); + + for (i = 0; i < opaquelen; i++) { + zlog_debug("0x%x ", olsa->mydata[i]); + } + zlog_debug(" "); +} + +/* ----------------------------------------------------------- + * Generic messages + * ----------------------------------------------------------- + */ + +struct msg *msg_new(uint8_t msgtype, void *msgbody, uint32_t seqnum, + uint16_t msglen) +{ + struct msg *new; + + new = XCALLOC(MTYPE_OSPF_API_MSG, sizeof(struct msg)); + + new->hdr.version = OSPF_API_VERSION; + new->hdr.msgtype = msgtype; + new->hdr.msglen = htons(msglen); + new->hdr.msgseq = htonl(seqnum); + + new->s = stream_new(msglen); + assert(new->s); + stream_put(new->s, msgbody, msglen); + + return new; +} + + +/* Duplicate a message by copying content. */ +struct msg *msg_dup(struct msg *msg) +{ + struct msg *new; + size_t size; + + assert(msg); + + size = ntohs(msg->hdr.msglen); + if (size > OSPF_MAX_LSA_SIZE) + return NULL; + + new = msg_new(msg->hdr.msgtype, STREAM_DATA(msg->s), + ntohl(msg->hdr.msgseq), size); + return new; +} + + +/* XXX only for testing, will be removed */ + +struct nametab { + int value; + const char *name; +}; + +const char *ospf_api_typename(int msgtype) +{ + struct nametab NameTab[] = { + { + MSG_REGISTER_OPAQUETYPE, "Register opaque-type", + }, + { + MSG_UNREGISTER_OPAQUETYPE, "Unregister opaque-type", + }, + { + MSG_REGISTER_EVENT, "Register event", + }, + { + MSG_SYNC_LSDB, "Sync LSDB", + }, + { + MSG_ORIGINATE_REQUEST, "Originate request", + }, + { + MSG_DELETE_REQUEST, "Delete request", + }, + { + MSG_REPLY, "Reply", + }, + { + MSG_READY_NOTIFY, "Ready notify", + }, + { + MSG_LSA_UPDATE_NOTIFY, "LSA update notify", + }, + { + MSG_LSA_DELETE_NOTIFY, "LSA delete notify", + }, + { + MSG_NEW_IF, "New interface", + }, + { + MSG_DEL_IF, "Del interface", + }, + { + MSG_ISM_CHANGE, "ISM change", + }, + { + MSG_NSM_CHANGE, "NSM change", + }, + { + MSG_REACHABLE_CHANGE, + "Reachable change", + }, + }; + + int i, n = array_size(NameTab); + const char *name = NULL; + + for (i = 0; i < n; i++) { + if (NameTab[i].value == msgtype) { + name = NameTab[i].name; + break; + } + } + + return name ? name : "?"; +} + +const char *ospf_api_errname(int errcode) +{ + struct nametab NameTab[] = { + { + OSPF_API_OK, "OK", + }, + { + OSPF_API_NOSUCHINTERFACE, "No such interface", + }, + { + OSPF_API_NOSUCHAREA, "No such area", + }, + { + OSPF_API_NOSUCHLSA, "No such LSA", + }, + { + OSPF_API_ILLEGALLSATYPE, "Illegal LSA type", + }, + { + OSPF_API_OPAQUETYPEINUSE, "Opaque type in use", + }, + { + OSPF_API_OPAQUETYPENOTREGISTERED, + "Opaque type not registered", + }, + { + OSPF_API_NOTREADY, "Not ready", + }, + { + OSPF_API_NOMEMORY, "No memory", + }, + { + OSPF_API_ERROR, "Other error", + }, + { + OSPF_API_UNDEF, "Undefined", + }, + }; + + int i, n = array_size(NameTab); + const char *name = NULL; + + for (i = 0; i < n; i++) { + if (NameTab[i].value == errcode) { + name = NameTab[i].name; + break; + } + } + + return name ? name : "?"; +} + +void msg_print(struct msg *msg) +{ + if (!msg) { + zlog_debug("msg_print msg=NULL!"); + return; + } + + /* API message common header part. */ + zlog_debug("API-msg [%s]: type(%d),len(%d),seq(%lu),data(%p),size(%zd)", + ospf_api_typename(msg->hdr.msgtype), msg->hdr.msgtype, + ntohs(msg->hdr.msglen), + (unsigned long)ntohl(msg->hdr.msgseq), STREAM_DATA(msg->s), + STREAM_SIZE(msg->s)); + + return; +} + +void msg_free(struct msg *msg) +{ + if (msg->s) + stream_free(msg->s); + + XFREE(MTYPE_OSPF_API_MSG, msg); +} + + +/* Set sequence number of message */ +void msg_set_seq(struct msg *msg, uint32_t seqnr) +{ + assert(msg); + msg->hdr.msgseq = htonl(seqnr); +} + +/* Get sequence number of message */ +uint32_t msg_get_seq(struct msg *msg) +{ + assert(msg); + return ntohl(msg->hdr.msgseq); +} + +/* ----------------------------------------------------------- + * Message fifo queues + * ----------------------------------------------------------- + */ + +struct msg_fifo *msg_fifo_new(void) +{ + return XCALLOC(MTYPE_OSPF_API_FIFO, sizeof(struct msg_fifo)); +} + +/* Add new message to fifo. */ +void msg_fifo_push(struct msg_fifo *fifo, struct msg *msg) +{ + if (fifo->tail) + fifo->tail->next = msg; + else + fifo->head = msg; + + fifo->tail = msg; + fifo->count++; +} + + +/* Remove first message from fifo. */ +struct msg *msg_fifo_pop(struct msg_fifo *fifo) +{ + struct msg *msg; + + msg = fifo->head; + if (msg) { + fifo->head = msg->next; + + if (fifo->head == NULL) + fifo->tail = NULL; + + fifo->count--; + } + return msg; +} + +/* Return first fifo entry but do not remove it. */ +struct msg *msg_fifo_head(struct msg_fifo *fifo) +{ + return fifo->head; +} + +/* Flush message fifo. */ +void msg_fifo_flush(struct msg_fifo *fifo) +{ + struct msg *op; + struct msg *next; + + for (op = fifo->head; op; op = next) { + next = op->next; + msg_free(op); + } + + fifo->head = fifo->tail = NULL; + fifo->count = 0; +} + +/* Free API message fifo. */ +void msg_fifo_free(struct msg_fifo *fifo) +{ + msg_fifo_flush(fifo); + + XFREE(MTYPE_OSPF_API_FIFO, fifo); +} + +struct msg *msg_read(int fd) +{ + struct msg *msg; + struct apimsghdr hdr; + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + ssize_t bodylen; + ssize_t rlen; + + /* Read message header */ + rlen = readn(fd, (uint8_t *)&hdr, sizeof(struct apimsghdr)); + + if (rlen < 0) { + zlog_warn("msg_read: readn %s", safe_strerror(errno)); + return NULL; + } else if (rlen == 0) { + zlog_warn("msg_read: Connection closed by peer"); + return NULL; + } else if (rlen != sizeof(struct apimsghdr)) { + zlog_warn("msg_read: Cannot read message header!"); + return NULL; + } + + /* Check version of API protocol */ + if (hdr.version != OSPF_API_VERSION) { + zlog_warn("msg_read: OSPF API protocol version mismatch"); + return NULL; + } + + /* Determine body length. */ + bodylen = ntohs(hdr.msglen); + if (bodylen > (ssize_t)sizeof(buf)) { + zlog_warn("%s: Body Length of message greater than what we can read", + __func__); + return NULL; + } + + if (bodylen > 0) { + /* Read message body */ + rlen = readn(fd, buf, bodylen); + if (rlen < 0) { + zlog_warn("msg_read: readn %s", safe_strerror(errno)); + return NULL; + } else if (rlen == 0) { + zlog_warn("msg_read: Connection closed by peer"); + return NULL; + } else if (rlen != bodylen) { + zlog_warn("msg_read: Cannot read message body!"); + return NULL; + } + } + + /* Allocate new message */ + msg = msg_new(hdr.msgtype, buf, ntohl(hdr.msgseq), bodylen); + + return msg; +} + +int msg_write(int fd, struct msg *msg) +{ + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + uint16_t l; + int wlen; + + assert(msg); + assert(msg->s); + + /* Length of OSPF LSA payload */ + l = ntohs(msg->hdr.msglen); + if (l > OSPF_MAX_LSA_SIZE) { + zlog_warn("%s: wrong LSA size %d", __func__, l); + return -1; + } + + /* Make contiguous memory buffer for message */ + memcpy(buf, &msg->hdr, sizeof(struct apimsghdr)); + memcpy(buf + sizeof(struct apimsghdr), STREAM_DATA(msg->s), l); + + /* Total length of OSPF API Message */ + l += sizeof(struct apimsghdr); + wlen = writen(fd, buf, l); + if (wlen < 0) { + zlog_warn("%s: writen %s", __func__, safe_strerror(errno)); + return -1; + } else if (wlen == 0) { + zlog_warn("%s: Connection closed by peer", __func__); + return -1; + } else if (wlen != l) { + zlog_warn("%s: Cannot write API message", __func__); + return -1; + } + return 0; +} + +/* ----------------------------------------------------------- + * Specific messages + * ----------------------------------------------------------- + */ + +struct msg *new_msg_register_opaque_type(uint32_t seqnum, uint8_t ltype, + uint8_t otype) +{ + struct msg_register_opaque_type rmsg; + + rmsg.lsatype = ltype; + rmsg.opaquetype = otype; + memset(&rmsg.pad, 0, sizeof(rmsg.pad)); + + return msg_new(MSG_REGISTER_OPAQUETYPE, &rmsg, seqnum, + sizeof(struct msg_register_opaque_type)); +} + +struct msg *new_msg_register_event(uint32_t seqnum, + struct lsa_filter_type *filter) +{ + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + struct msg_register_event *emsg; + unsigned int len; + + emsg = (struct msg_register_event *)buf; + len = sizeof(struct msg_register_event) + + filter->num_areas * sizeof(struct in_addr); + emsg->filter.typemask = htons(filter->typemask); + emsg->filter.origin = filter->origin; + emsg->filter.num_areas = filter->num_areas; + if (len > sizeof(buf)) + len = sizeof(buf); + /* API broken - missing memcpy to fill data */ + return msg_new(MSG_REGISTER_EVENT, emsg, seqnum, len); +} + +struct msg *new_msg_sync_lsdb(uint32_t seqnum, struct lsa_filter_type *filter) +{ + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + struct msg_sync_lsdb *smsg; + unsigned int len; + + smsg = (struct msg_sync_lsdb *)buf; + len = sizeof(struct msg_sync_lsdb) + + filter->num_areas * sizeof(struct in_addr); + smsg->filter.typemask = htons(filter->typemask); + smsg->filter.origin = filter->origin; + smsg->filter.num_areas = filter->num_areas; + if (len > sizeof(buf)) + len = sizeof(buf); + /* API broken - missing memcpy to fill data */ + return msg_new(MSG_SYNC_LSDB, smsg, seqnum, len); +} + + +struct msg *new_msg_originate_request(uint32_t seqnum, struct in_addr ifaddr, + struct in_addr area_id, + struct lsa_header *data) +{ + struct msg_originate_request *omsg; + unsigned int omsglen; + char buf[OSPF_API_MAX_MSG_SIZE]; + size_t off_data = offsetof(struct msg_originate_request, data); + size_t data_maxs = sizeof(buf) - off_data; + struct lsa_header *omsg_data = (struct lsa_header *)&buf[off_data]; + + omsg = (struct msg_originate_request *)buf; + omsg->ifaddr = ifaddr; + omsg->area_id = area_id; + + omsglen = ntohs(data->length); + if (omsglen > data_maxs) + omsglen = data_maxs; + memcpy(omsg_data, data, omsglen); + omsglen += sizeof(struct msg_originate_request) + - sizeof(struct lsa_header); + + return msg_new(MSG_ORIGINATE_REQUEST, omsg, seqnum, omsglen); +} + +struct msg *new_msg_delete_request(uint32_t seqnum, struct in_addr addr, + uint8_t lsa_type, uint8_t opaque_type, + uint32_t opaque_id, uint8_t flags) +{ + struct msg_delete_request dmsg; + dmsg.addr = addr; + dmsg.lsa_type = lsa_type; + dmsg.opaque_type = opaque_type; + dmsg.opaque_id = htonl(opaque_id); + memset(&dmsg.pad, 0, sizeof(dmsg.pad)); + dmsg.flags = flags; + + return msg_new(MSG_DELETE_REQUEST, &dmsg, seqnum, + sizeof(struct msg_delete_request)); +} + + +struct msg *new_msg_reply(uint32_t seqnr, uint8_t rc) +{ + struct msg *msg; + struct msg_reply rmsg; + + /* Set return code */ + rmsg.errcode = rc; + memset(&rmsg.pad, 0, sizeof(rmsg.pad)); + + msg = msg_new(MSG_REPLY, &rmsg, seqnr, sizeof(struct msg_reply)); + + return msg; +} + +struct msg *new_msg_ready_notify(uint32_t seqnr, uint8_t lsa_type, + uint8_t opaque_type, struct in_addr addr) +{ + struct msg_ready_notify rmsg; + + rmsg.lsa_type = lsa_type; + rmsg.opaque_type = opaque_type; + memset(&rmsg.pad, 0, sizeof(rmsg.pad)); + rmsg.addr = addr; + + return msg_new(MSG_READY_NOTIFY, &rmsg, seqnr, + sizeof(struct msg_ready_notify)); +} + +struct msg *new_msg_new_if(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr area_id) +{ + struct msg_new_if nmsg; + + nmsg.ifaddr = ifaddr; + nmsg.area_id = area_id; + + return msg_new(MSG_NEW_IF, &nmsg, seqnr, sizeof(struct msg_new_if)); +} + +struct msg *new_msg_del_if(uint32_t seqnr, struct in_addr ifaddr) +{ + struct msg_del_if dmsg; + + dmsg.ifaddr = ifaddr; + + return msg_new(MSG_DEL_IF, &dmsg, seqnr, sizeof(struct msg_del_if)); +} + +struct msg *new_msg_ism_change(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr area_id, uint8_t status) +{ + struct msg_ism_change imsg; + + imsg.ifaddr = ifaddr; + imsg.area_id = area_id; + imsg.status = status; + memset(&imsg.pad, 0, sizeof(imsg.pad)); + + return msg_new(MSG_ISM_CHANGE, &imsg, seqnr, + sizeof(struct msg_ism_change)); +} + +struct msg *new_msg_nsm_change(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr nbraddr, struct in_addr router_id, + uint8_t status) +{ + struct msg_nsm_change nmsg; + + nmsg.ifaddr = ifaddr; + nmsg.nbraddr = nbraddr; + nmsg.router_id = router_id; + nmsg.status = status; + memset(&nmsg.pad, 0, sizeof(nmsg.pad)); + + return msg_new(MSG_NSM_CHANGE, &nmsg, seqnr, + sizeof(struct msg_nsm_change)); +} + +struct msg *new_msg_lsa_change_notify(uint8_t msgtype, uint32_t seqnum, + struct in_addr ifaddr, + struct in_addr area_id, + uint8_t is_self_originated, + struct lsa_header *data) +{ + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + struct msg_lsa_change_notify *nmsg; + unsigned int len; + size_t off_data = offsetof(struct msg_lsa_change_notify, data); + size_t data_maxs = sizeof(buf) - off_data; + struct lsa_header *nmsg_data = (struct lsa_header *)&buf[off_data]; + + assert(data); + + nmsg = (struct msg_lsa_change_notify *)buf; + nmsg->ifaddr = ifaddr; + nmsg->area_id = area_id; + nmsg->is_self_originated = is_self_originated; + memset(&nmsg->pad, 0, sizeof(nmsg->pad)); + + len = ntohs(data->length); + if (len > data_maxs) + len = data_maxs; + memcpy(nmsg_data, data, len); + len += sizeof(struct msg_lsa_change_notify) - sizeof(struct lsa_header); + + return msg_new(msgtype, nmsg, seqnum, len); +} + +struct msg *new_msg_reachable_change(uint32_t seqnum, uint16_t nadd, + struct in_addr *add, uint16_t nremove, + struct in_addr *remove) +{ + uint8_t buf[OSPF_API_MAX_MSG_SIZE]; + struct msg_reachable_change *nmsg = (void *)buf; + const uint insz = sizeof(*nmsg->router_ids); + const uint nmax = (sizeof(buf) - sizeof(*nmsg)) / insz; + uint len; + + if (nadd > nmax) + nadd = nmax; + if (nremove > (nmax - nadd)) + nremove = (nmax - nadd); + + if (nadd) + memcpy(nmsg->router_ids, add, nadd * insz); + if (nremove) + memcpy(&nmsg->router_ids[nadd], remove, nremove * insz); + + nmsg->nadd = htons(nadd); + nmsg->nremove = htons(nremove); + len = sizeof(*nmsg) + insz * (nadd + nremove); + + return msg_new(MSG_REACHABLE_CHANGE, nmsg, seqnum, len); +} + +struct msg *new_msg_router_id_change(uint32_t seqnum, struct in_addr router_id) +{ + struct msg_router_id_change rmsg = {.router_id = router_id}; + + return msg_new(MSG_ROUTER_ID_CHANGE, &rmsg, seqnum, + sizeof(struct msg_router_id_change)); +} + +#endif /* SUPPORT_OSPF_API */ diff --git a/ospfd/ospf_api.h b/ospfd/ospf_api.h new file mode 100644 index 0000000..6160a0f --- /dev/null +++ b/ospfd/ospf_api.h @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * API message handling module for OSPF daemon and client. + * Copyright (C) 2001, 2002 Ralph Keller + * Copyright (c) 2022, LabN Consulting, L.L.C. + */ + + +/* This file is used both by the OSPFd and client applications to + define message formats used for communication. */ + +#ifndef _OSPF_API_H +#define _OSPF_API_H + +#include +#include "ospf_lsa.h" + +#define OSPF_API_VERSION 1 + +/* MTYPE definition is not reflected to "memory.h". */ +#define MTYPE_OSPF_API_MSG MTYPE_TMP +#define MTYPE_OSPF_API_FIFO MTYPE_TMP + +/* ----------------------------------------------------------- + * Generic messages + * ----------------------------------------------------------- + */ + +/* Message header structure, fields are in network byte order and + aligned to four octets. */ +struct apimsghdr { + uint8_t version; /* OSPF API protocol version */ + uint8_t msgtype; /* Type of message */ + uint16_t msglen; /* Length of message w/o header */ + uint32_t msgseq; /* Sequence number */ +}; + +/* Message representation with header and body */ +struct msg { + struct msg *next; /* to link into fifo */ + + /* Message header */ + struct apimsghdr hdr; + + /* Message body */ + struct stream *s; +}; + +/* Prototypes for generic messages. */ +extern struct msg *msg_new(uint8_t msgtype, void *msgbody, uint32_t seqnum, + uint16_t msglen); +extern struct msg *msg_dup(struct msg *msg); +extern void msg_print(struct msg *msg); /* XXX debug only */ +extern void msg_free(struct msg *msg); +struct msg *msg_read(int fd); +extern int msg_write(int fd, struct msg *msg); + +/* For requests, the message sequence number is between MIN_SEQ and + MAX_SEQ. For notifications, the sequence number is 0. */ + +#define MIN_SEQ 1 +#define MAX_SEQ 2147483647 + +extern void msg_set_seq(struct msg *msg, uint32_t seqnr); +extern uint32_t msg_get_seq(struct msg *msg); + +/* ----------------------------------------------------------- + * Message fifo queues + * ----------------------------------------------------------- + */ + +/* Message queue structure. */ +struct msg_fifo { + unsigned long count; + + struct msg *head; + struct msg *tail; +}; + +/* Prototype for message fifo queues. */ +extern struct msg_fifo *msg_fifo_new(void); +extern void msg_fifo_push(struct msg_fifo *, struct msg *msg); +extern struct msg *msg_fifo_pop(struct msg_fifo *fifo); +extern struct msg *msg_fifo_head(struct msg_fifo *fifo); +extern void msg_fifo_flush(struct msg_fifo *fifo); +extern void msg_fifo_free(struct msg_fifo *fifo); + +/* ----------------------------------------------------------- + * Specific message type and format definitions + * ----------------------------------------------------------- + */ + +/* Messages to OSPF daemon. */ +#define MSG_REGISTER_OPAQUETYPE 1 +#define MSG_UNREGISTER_OPAQUETYPE 2 +#define MSG_REGISTER_EVENT 3 +#define MSG_SYNC_LSDB 4 +#define MSG_ORIGINATE_REQUEST 5 +#define MSG_DELETE_REQUEST 6 +#define MSG_SYNC_REACHABLE 7 +#define MSG_SYNC_ISM 8 +#define MSG_SYNC_NSM 9 +#define MSG_SYNC_ROUTER_ID 19 + +/* Messages from OSPF daemon. */ +#define MSG_REPLY 10 +#define MSG_READY_NOTIFY 11 +#define MSG_LSA_UPDATE_NOTIFY 12 +#define MSG_LSA_DELETE_NOTIFY 13 +#define MSG_NEW_IF 14 +#define MSG_DEL_IF 15 +#define MSG_ISM_CHANGE 16 +#define MSG_NSM_CHANGE 17 +#define MSG_REACHABLE_CHANGE 18 +#define MSG_ROUTER_ID_CHANGE 20 + +struct msg_register_opaque_type { + uint8_t lsatype; + uint8_t opaquetype; + uint8_t pad[2]; /* padding */ +}; + +struct msg_unregister_opaque_type { + uint8_t lsatype; + uint8_t opaquetype; + uint8_t pad[2]; /* padding */ +}; + +/* Power2 is needed to convert LSA types into bit positions, + * see typemask below. Type definition starts at 1, so + * Power2[0] is not used. */ + + +static const uint16_t Power2[] = { + 0, (1 << 0), (1 << 1), (1 << 2), (1 << 3), (1 << 4), + (1 << 5), (1 << 6), (1 << 7), (1 << 8), (1 << 9), (1 << 10), + (1 << 11), (1 << 12), (1 << 13), (1 << 14), (1 << 15)}; + +struct lsa_filter_type { + uint16_t typemask; /* bitmask for selecting LSA types (1..16) */ + uint8_t origin; /* selects according to origin. */ +#define NON_SELF_ORIGINATED 0 +#define SELF_ORIGINATED (OSPF_LSA_SELF) +#define ANY_ORIGIN 2 + + uint8_t num_areas; /* number of areas in the filter. */ + /* areas, if any, go here. */ +}; + +struct msg_register_event { + struct lsa_filter_type filter; +}; + +struct msg_sync_lsdb { + struct lsa_filter_type filter; +}; + +struct msg_originate_request { + /* Used for LSA type 9 otherwise ignored */ + struct in_addr ifaddr; + + /* Used for LSA type 10 otherwise ignored */ + struct in_addr area_id; + + /* LSA header and LSA-specific part */ + struct lsa_header data; +}; + + +/* OSPF API MSG Delete Flag. */ +#define OSPF_API_DEL_ZERO_LEN_LSA 0x01 /* send withdrawal with no LSA data */ + +#define IS_DEL_ZERO_LEN_LSA(x) ((x)->flags & OSPF_API_DEL_ZERO_LEN_LSA) + +struct msg_delete_request { + struct in_addr addr; /* intf IP for link local, area for type 10, + "0.0.0.0" for AS-external */ + uint8_t lsa_type; + uint8_t opaque_type; + uint8_t pad; /* padding */ + uint8_t flags; /* delete flags */ + uint32_t opaque_id; +}; + +struct msg_reply { + signed char errcode; +#define OSPF_API_OK 0 +#define OSPF_API_NOSUCHINTERFACE (-1) +#define OSPF_API_NOSUCHAREA (-2) +#define OSPF_API_NOSUCHLSA (-3) +#define OSPF_API_ILLEGALLSATYPE (-4) +#define OSPF_API_OPAQUETYPEINUSE (-5) +#define OSPF_API_OPAQUETYPENOTREGISTERED (-6) +#define OSPF_API_NOTREADY (-7) +#define OSPF_API_NOMEMORY (-8) +#define OSPF_API_ERROR (-9) +#define OSPF_API_UNDEF (-10) + uint8_t pad[3]; /* padding to four byte alignment */ +}; + +/* Message to tell client application that it ospf daemon is + * ready to accept opaque LSAs for a given interface or area. */ + +struct msg_ready_notify { + uint8_t lsa_type; + uint8_t opaque_type; + uint8_t pad[2]; /* padding */ + struct in_addr addr; /* interface address or area address */ +}; + +/* These messages have a dynamic length depending on the embodied LSA. + They are aligned to four octets. msg_lsa_change_notify is used for + both LSA update and LSAs delete. */ + +struct msg_lsa_change_notify { + /* Used for LSA type 9 otherwise ignored */ + struct in_addr ifaddr; + /* Area ID. Not valid for AS-External and Opaque11 LSAs. */ + struct in_addr area_id; + uint8_t is_self_originated; /* 1 if self originated. */ + uint8_t pad[3]; + struct lsa_header data; +}; + +struct msg_new_if { + struct in_addr ifaddr; /* interface IP address */ + struct in_addr area_id; /* area this interface belongs to */ +}; + +struct msg_del_if { + struct in_addr ifaddr; /* interface IP address */ +}; + +struct msg_ism_change { + struct in_addr ifaddr; /* interface IP address */ + struct in_addr area_id; /* area this interface belongs to */ + uint8_t status; /* interface status (up/down) */ + uint8_t pad[3]; /* not used */ +}; + +struct msg_nsm_change { + struct in_addr ifaddr; /* attached interface */ + struct in_addr nbraddr; /* Neighbor interface address */ + struct in_addr router_id; /* Router ID of neighbor */ + uint8_t status; /* NSM status */ + uint8_t pad[3]; +}; + +struct msg_reachable_change { + uint16_t nadd; + uint16_t nremove; + struct in_addr router_ids[]; /* add followed by remove */ +}; + +struct msg_router_id_change { + struct in_addr router_id; /* this systems router id */ +}; + +/* We make use of a union to define a structure that covers all + possible API messages. This allows us to find out how much memory + needs to be reserved for the largest API message. */ +struct apimsg { + struct apimsghdr hdr; + union { + struct msg_register_opaque_type register_opaque_type; + struct msg_register_event register_event; + struct msg_sync_lsdb sync_lsdb; + struct msg_originate_request originate_request; + struct msg_delete_request delete_request; + struct msg_reply reply; + struct msg_ready_notify ready_notify; + struct msg_new_if new_if; + struct msg_del_if del_if; + struct msg_ism_change ism_change; + struct msg_nsm_change nsm_change; + struct msg_lsa_change_notify lsa_change_notify; + struct msg_reachable_change reachable_change; + struct msg_router_id_change router_id_change; + } u; +}; + +#define OSPF_API_MAX_MSG_SIZE (sizeof(struct apimsg) + OSPF_MAX_PACKET_SIZE) + +/* ----------------------------------------------------------- + * Prototypes for specific messages + * ----------------------------------------------------------- + */ + +/* For debugging only. */ +extern void api_opaque_lsa_print(struct ospf_lsa *lsa); + +/* Messages sent by client */ +extern struct msg *new_msg_register_opaque_type(uint32_t seqnum, uint8_t ltype, + uint8_t otype); +extern struct msg *new_msg_register_event(uint32_t seqnum, + struct lsa_filter_type *filter); +extern struct msg *new_msg_sync_lsdb(uint32_t seqnum, + struct lsa_filter_type *filter); +extern struct msg *new_msg_originate_request(uint32_t seqnum, + struct in_addr ifaddr, + struct in_addr area_id, + struct lsa_header *data); +extern struct msg *new_msg_delete_request(uint32_t seqnum, struct in_addr addr, + uint8_t lsa_type, uint8_t opaque_type, + uint32_t opaque_id, uint8_t flags); + +/* Messages sent by OSPF daemon */ +extern struct msg *new_msg_reply(uint32_t seqnum, uint8_t rc); + +extern struct msg *new_msg_ready_notify(uint32_t seqnr, uint8_t lsa_type, + uint8_t opaque_type, + struct in_addr addr); + +extern struct msg *new_msg_new_if(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr area); + +extern struct msg *new_msg_del_if(uint32_t seqnr, struct in_addr ifaddr); + +extern struct msg *new_msg_ism_change(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr area, uint8_t status); + +extern struct msg *new_msg_nsm_change(uint32_t seqnr, struct in_addr ifaddr, + struct in_addr nbraddr, + struct in_addr router_id, uint8_t status); + +/* msgtype is MSG_LSA_UPDATE_NOTIFY or MSG_LSA_DELETE_NOTIFY */ +extern struct msg *new_msg_lsa_change_notify(uint8_t msgtype, uint32_t seqnum, + struct in_addr ifaddr, + struct in_addr area_id, + uint8_t is_self_originated, + struct lsa_header *data); + +extern struct msg *new_msg_reachable_change(uint32_t seqnum, uint16_t nadd, + struct in_addr *add, + uint16_t nremove, + struct in_addr *remove); + +extern struct msg *new_msg_router_id_change(uint32_t seqnr, + struct in_addr router_id); +/* string printing functions */ +extern const char *ospf_api_errname(int errcode); +extern const char *ospf_api_typename(int msgtype); + +#endif /* _OSPF_API_H */ diff --git a/ospfd/ospf_apiserver.c b/ospfd/ospf_apiserver.c new file mode 100644 index 0000000..fcc28c6 --- /dev/null +++ b/ospfd/ospf_apiserver.c @@ -0,0 +1,2744 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Server side of OSPF API. + * Copyright (C) 2001, 2002 Ralph Keller + * Copyright (c) 2022, LabN Consulting, L.L.C. + */ + +#include + +#ifdef SUPPORT_OSPF_API + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "buffer.h" + +#include + +#include "ospfd/ospfd.h" /* for "struct event_loop" */ +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_memory.h" + +#include "ospfd/ospf_api.h" +#include "ospfd/ospf_apiserver.h" + +DEFINE_MTYPE_STATIC(OSPFD, APISERVER, "API Server"); +DEFINE_MTYPE_STATIC(OSPFD, APISERVER_MSGFILTER, "API Server Message Filter"); + +/* This is an implementation of an API to the OSPF daemon that allows + * external applications to access the OSPF daemon through socket + * connections. The application can use this API to inject its own + * opaque LSAs and flood them to other OSPF daemons. Other OSPF + * daemons then receive these LSAs and inform applications through the + * API by sending a corresponding message. The application can also + * register to receive all LSA types (in addition to opaque types) and + * use this information to reconstruct the OSPF's LSDB. The OSPF + * daemon supports multiple applications concurrently. */ + +/* List of all active connections. */ +struct list *apiserver_list; + +/* Indicates that API the server socket local addresss has been + * specified. + */ +struct in_addr ospf_apiserver_addr; + +/* ----------------------------------------------------------- + * Functions to lookup interfaces + * ----------------------------------------------------------- + */ + +struct ospf_interface *ospf_apiserver_if_lookup_by_addr(struct in_addr address) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + struct ospf *ospf = NULL; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (!ospf) + return NULL; + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + if (IPV4_ADDR_SAME(&address, &oi->address->u.prefix4)) + return oi; + + return NULL; +} + +struct ospf_interface *ospf_apiserver_if_lookup_by_ifp(struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + struct ospf *ospf = NULL; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (!ospf) + return NULL; + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) + if (oi->ifp == ifp) + return oi; + + return NULL; +} + +/* ----------------------------------------------------------- + * Initialization + * ----------------------------------------------------------- + */ + +unsigned short ospf_apiserver_getport(void) +{ + struct servent *sp = NULL; + char sbuf[16]; + + /* + * Allow the OSPF API server port to be specified per-instance by + * including the instance ID in the /etc/services name. Use the + * prior name if no per-instance service is specified. + */ + if (ospf_instance) { + snprintfrr(sbuf, sizeof(sbuf), "ospfapi-%d", ospf_instance); + sp = getservbyname(sbuf, "tcp"); + } + + if (!sp) + sp = getservbyname("ospfapi", "tcp"); + + return sp ? ntohs(sp->s_port) : OSPF_API_SYNC_PORT; +} + +/* Initialize OSPF API module. Invoked from ospf_opaque_init() */ +int ospf_apiserver_init(void) +{ + int fd; + int rc = -1; + + /* Create new socket for synchronous messages. */ + fd = ospf_apiserver_serv_sock_family(ospf_apiserver_getport(), AF_INET); + + if (fd < 0) + goto out; + + /* Schedule new thread that handles accepted connections. */ + ospf_apiserver_event(OSPF_APISERVER_ACCEPT, fd, NULL); + + /* Initialize list that keeps track of all connections. */ + apiserver_list = list_new(); + + /* Register opaque-independent call back functions. These functions + are invoked on ISM, NSM changes and LSA update and LSA deletes */ + rc = ospf_register_opaque_functab( + 0 /* all LSAs */, 0 /* all opaque types */, + ospf_apiserver_new_if, ospf_apiserver_del_if, + ospf_apiserver_ism_change, ospf_apiserver_nsm_change, NULL, + NULL, NULL, NULL, /* ospf_apiserver_show_info */ + NULL, /* originator_func */ + NULL, /* ospf_apiserver_lsa_refresher */ + ospf_apiserver_lsa_update, ospf_apiserver_lsa_delete); + if (rc != 0) { + flog_warn( + EC_OSPF_OPAQUE_REGISTRATION, + "ospf_apiserver_init: Failed to register opaque type [0/0]"); + } + + rc = 0; + +out: + return rc; +} + +/* Terminate OSPF API module. */ +void ospf_apiserver_term(void) +{ + struct ospf_apiserver *apiserv; + + /* Unregister wildcard [0/0] type */ + ospf_delete_opaque_functab(0 /* all LSAs */, 0 /* all opaque types */); + + /* + * Free all client instances. ospf_apiserver_free removes the node + * from the list, so we examine the head of the list anew each time. + */ + if (!apiserver_list) + return; + + while (listcount(apiserver_list)) { + apiserv = listgetdata(listhead(apiserver_list)); + + ospf_apiserver_free(apiserv); + } + + /* Free client list itself */ + if (apiserver_list) + list_delete(&apiserver_list); + + /* Free wildcard list */ + /* XXX */ +} + +static struct ospf_apiserver *lookup_apiserver(uint8_t lsa_type, + uint8_t opaque_type) +{ + struct listnode *n1, *n2; + struct registered_opaque_type *r; + struct ospf_apiserver *apiserv, *found = NULL; + + /* XXX: this approaches O(n**2) */ + for (ALL_LIST_ELEMENTS_RO(apiserver_list, n1, apiserv)) { + for (ALL_LIST_ELEMENTS_RO(apiserv->opaque_types, n2, r)) + if (r->lsa_type == lsa_type + && r->opaque_type == opaque_type) { + found = apiserv; + goto out; + } + } +out: + return found; +} + +static struct ospf_apiserver *lookup_apiserver_by_lsa(struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = lsa->data; + struct ospf_apiserver *found = NULL; + + if (IS_OPAQUE_LSA(lsah->type)) { + found = lookup_apiserver( + lsah->type, GET_OPAQUE_TYPE(ntohl(lsah->id.s_addr))); + } + return found; +} + +/* ----------------------------------------------------------- + * Following are functions to manage client connections. + * ----------------------------------------------------------- + */ +static int ospf_apiserver_new_lsa_hook(struct ospf_lsa *lsa) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: Put LSA(%p)[%s] into reserve, total=%ld", + (void *)lsa, dump_lsa_key(lsa), lsa->lsdb->total); + return 0; +} + +static int ospf_apiserver_del_lsa_hook(struct ospf_lsa *lsa) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: Get LSA(%p)[%s] from reserve, total=%ld", + (void *)lsa, dump_lsa_key(lsa), lsa->lsdb->total); + return 0; +} + +/* Allocate new connection structure. */ +struct ospf_apiserver *ospf_apiserver_new(int fd_sync, int fd_async) +{ + struct ospf_apiserver *new = + XMALLOC(MTYPE_APISERVER, sizeof(struct ospf_apiserver)); + + new->filter = XMALLOC(MTYPE_APISERVER_MSGFILTER, + sizeof(struct lsa_filter_type)); + + new->fd_sync = fd_sync; + new->fd_async = fd_async; + + /* list of registered opaque types that application uses */ + new->opaque_types = list_new(); + + /* Initialize temporary strage for LSA instances to be refreshed. */ + memset(&new->reserve, 0, sizeof(struct ospf_lsdb)); + ospf_lsdb_init(&new->reserve); + + new->reserve.new_lsa_hook = ospf_apiserver_new_lsa_hook; /* debug */ + new->reserve.del_lsa_hook = ospf_apiserver_del_lsa_hook; /* debug */ + + new->out_sync_fifo = msg_fifo_new(); + new->out_async_fifo = msg_fifo_new(); + new->t_sync_read = NULL; +#ifdef USE_ASYNC_READ + new->t_async_read = NULL; +#endif /* USE_ASYNC_READ */ + new->t_sync_write = NULL; + new->t_async_write = NULL; + + new->filter->typemask = 0; /* filter all LSAs */ + new->filter->origin = ANY_ORIGIN; + new->filter->num_areas = 0; + + return new; +} + +void ospf_apiserver_event(enum ospf_apiserver_event event, int fd, + struct ospf_apiserver *apiserv) +{ + switch (event) { + case OSPF_APISERVER_ACCEPT: + (void)event_add_read(master, ospf_apiserver_accept, apiserv, fd, + NULL); + break; + case OSPF_APISERVER_SYNC_READ: + apiserv->t_sync_read = NULL; + event_add_read(master, ospf_apiserver_read, apiserv, fd, + &apiserv->t_sync_read); + break; +#ifdef USE_ASYNC_READ + case OSPF_APISERVER_ASYNC_READ: + apiserv->t_async_read = NULL; + event_add_read(master, ospf_apiserver_read, apiserv, fd, + &apiserv->t_async_read); + break; +#endif /* USE_ASYNC_READ */ + case OSPF_APISERVER_SYNC_WRITE: + event_add_write(master, ospf_apiserver_sync_write, apiserv, fd, + &apiserv->t_sync_write); + break; + case OSPF_APISERVER_ASYNC_WRITE: + event_add_write(master, ospf_apiserver_async_write, apiserv, fd, + &apiserv->t_async_write); + break; + } +} + +/* Free instance. First unregister all opaque types used by + application, flush opaque LSAs injected by application + from network and close connection. */ +void ospf_apiserver_free(struct ospf_apiserver *apiserv) +{ + struct listnode *node; + + /* Cancel read and write threads. */ + EVENT_OFF(apiserv->t_sync_read); +#ifdef USE_ASYNC_READ + EVENT_OFF(apiserv->t_async_read); +#endif /* USE_ASYNC_READ */ + EVENT_OFF(apiserv->t_sync_write); + EVENT_OFF(apiserv->t_async_write); + + /* Unregister all opaque types that application registered + and flush opaque LSAs if still in LSDB. */ + + while ((node = listhead(apiserv->opaque_types)) != NULL) { + struct registered_opaque_type *regtype = listgetdata(node); + + ospf_apiserver_unregister_opaque_type( + apiserv, regtype->lsa_type, regtype->opaque_type); + } + list_delete(&apiserv->opaque_types); + + /* Close connections to OSPFd. */ + if (apiserv->fd_sync > 0) { + close(apiserv->fd_sync); + } + + if (apiserv->fd_async > 0) { + close(apiserv->fd_async); + } + + /* Free fifos */ + msg_fifo_free(apiserv->out_sync_fifo); + msg_fifo_free(apiserv->out_async_fifo); + + /* Clear temporary strage for LSA instances to be refreshed. */ + ospf_lsdb_delete_all(&apiserv->reserve); + ospf_lsdb_cleanup(&apiserv->reserve); + + /* Remove from the list of active clients. */ + listnode_delete(apiserver_list, apiserv); + + XFREE(MTYPE_APISERVER_MSGFILTER, apiserv->filter); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: Delete apiserv(%p), total#(%d)", + (void *)apiserv, apiserver_list->count); + + /* And free instance. */ + XFREE(MTYPE_APISERVER, apiserv); +} + +void ospf_apiserver_read(struct event *thread) +{ + struct ospf_apiserver *apiserv; + struct msg *msg; + int fd; + enum ospf_apiserver_event event; + + apiserv = EVENT_ARG(thread); + fd = EVENT_FD(thread); + + if (fd == apiserv->fd_sync) { + event = OSPF_APISERVER_SYNC_READ; + apiserv->t_sync_read = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: %s: Peer: %pI4/%u", __func__, + &apiserv->peer_sync.sin_addr, + ntohs(apiserv->peer_sync.sin_port)); + } +#ifdef USE_ASYNC_READ + else if (fd == apiserv->fd_async) { + event = OSPF_APISERVER_ASYNC_READ; + apiserv->t_async_read = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: %s: Peer: %pI4/%u", __func__, + &apiserv->peer_async.sin_addr, + ntohs(apiserv->peer_async.sin_port)); + } +#endif /* USE_ASYNC_READ */ + else { + zlog_warn("%s: Unknown fd(%d)", __func__, fd); + ospf_apiserver_free(apiserv); + return; + } + + /* Read message from fd. */ + msg = msg_read(fd); + if (msg == NULL) { + zlog_warn("%s: read failed on fd=%d, closing connection", + __func__, fd); + + /* Perform cleanup. */ + ospf_apiserver_free(apiserv); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + msg_print(msg); + + /* Dispatch to corresponding message handler. */ + ospf_apiserver_handle_msg(apiserv, msg); + + /* Prepare for next message, add read thread. */ + ospf_apiserver_event(event, fd, apiserv); + + msg_free(msg); +} + +void ospf_apiserver_sync_write(struct event *thread) +{ + struct ospf_apiserver *apiserv; + struct msg *msg; + int fd; + int rc = -1; + + apiserv = EVENT_ARG(thread); + assert(apiserv); + fd = EVENT_FD(thread); + + apiserv->t_sync_write = NULL; + + /* Sanity check */ + if (fd != apiserv->fd_sync) { + zlog_warn("%s: Unknown fd=%d", __func__, fd); + goto out; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: %s: Peer: %pI4/%u", __func__, + &apiserv->peer_sync.sin_addr, + ntohs(apiserv->peer_sync.sin_port)); + + /* Check whether there is really a message in the fifo. */ + msg = msg_fifo_pop(apiserv->out_sync_fifo); + if (!msg) { + zlog_warn("API: %s: No message in Sync-FIFO?", __func__); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + msg_print(msg); + + rc = msg_write(fd, msg); + + /* Once a message is dequeued, it should be freed anyway. */ + msg_free(msg); + + if (rc < 0) { + zlog_warn("%s: write failed on fd=%d", __func__, fd); + goto out; + } + + + /* If more messages are in sync message fifo, schedule write thread. */ + if (msg_fifo_head(apiserv->out_sync_fifo)) { + ospf_apiserver_event(OSPF_APISERVER_SYNC_WRITE, + apiserv->fd_sync, apiserv); + } + +out: + + if (rc < 0) { + /* Perform cleanup and disconnect with peer */ + ospf_apiserver_free(apiserv); + } +} + + +void ospf_apiserver_async_write(struct event *thread) +{ + struct ospf_apiserver *apiserv; + struct msg *msg; + int fd; + int rc = -1; + + apiserv = EVENT_ARG(thread); + assert(apiserv); + fd = EVENT_FD(thread); + + apiserv->t_async_write = NULL; + + /* Sanity check */ + if (fd != apiserv->fd_async) { + zlog_warn("%s: Unknown fd=%d", __func__, fd); + goto out; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: %s: Peer: %pI4/%u", __func__, + &apiserv->peer_async.sin_addr, + ntohs(apiserv->peer_async.sin_port)); + + /* Check whether there is really a message in the fifo. */ + msg = msg_fifo_pop(apiserv->out_async_fifo); + if (!msg) { + zlog_warn("API: %s: No message in Async-FIFO?", __func__); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + msg_print(msg); + + rc = msg_write(fd, msg); + + /* Once a message is dequeued, it should be freed anyway. */ + msg_free(msg); + + if (rc < 0) { + zlog_warn("%s: write failed on fd=%d", __func__, fd); + goto out; + } + + + /* If more messages are in async message fifo, schedule write thread. */ + if (msg_fifo_head(apiserv->out_async_fifo)) { + ospf_apiserver_event(OSPF_APISERVER_ASYNC_WRITE, + apiserv->fd_async, apiserv); + } + +out: + + if (rc < 0) { + /* Perform cleanup and disconnect with peer */ + ospf_apiserver_free(apiserv); + } +} + + +int ospf_apiserver_serv_sock_family(unsigned short port, int family) +{ + union sockunion su; + int accept_sock; + int rc; + + memset(&su, 0, sizeof(union sockunion)); + su.sa.sa_family = family; + + /* Make new socket */ + accept_sock = sockunion_stream_socket(&su); + if (accept_sock < 0) + return accept_sock; + + /* This is a server, so reuse address and port */ + sockopt_reuseaddr(accept_sock); + sockopt_reuseport(accept_sock); + + /* Bind socket to optional lcoal address and port. */ + if (ospf_apiserver_addr.s_addr) + sockunion2ip(&su) = ospf_apiserver_addr.s_addr; + rc = sockunion_bind(accept_sock, &su, port, &su); + if (rc < 0) { + close(accept_sock); /* Close socket */ + return rc; + } + + /* Listen socket under queue length 3. */ + rc = listen(accept_sock, 3); + if (rc < 0) { + zlog_warn("%s: listen: %s", __func__, safe_strerror(errno)); + close(accept_sock); /* Close socket */ + return rc; + } + return accept_sock; +} + + +/* Accept connection request from external applications. For each + accepted connection allocate own connection instance. */ +void ospf_apiserver_accept(struct event *thread) +{ + int accept_sock; + int new_sync_sock; + int new_async_sock; + union sockunion su; + struct ospf_apiserver *apiserv; + struct sockaddr_in peer_async; + struct sockaddr_in peer_sync; + unsigned int peerlen; + int ret; + + /* EVENT_ARG (thread) is NULL */ + accept_sock = EVENT_FD(thread); + + /* Keep hearing on socket for further connections. */ + ospf_apiserver_event(OSPF_APISERVER_ACCEPT, accept_sock, NULL); + + memset(&su, 0, sizeof(union sockunion)); + /* Accept connection for synchronous messages */ + new_sync_sock = sockunion_accept(accept_sock, &su); + if (new_sync_sock < 0) { + zlog_warn("%s: accept: %s", __func__, safe_strerror(errno)); + return; + } + + /* Get port address and port number of peer to make reverse connection. + The reverse channel uses the port number of the peer port+1. */ + + memset(&peer_sync, 0, sizeof(peer_sync)); + peerlen = sizeof(struct sockaddr_in); + + ret = getpeername(new_sync_sock, (struct sockaddr *)&peer_sync, + &peerlen); + if (ret < 0) { + zlog_warn("%s: getpeername: %s", __func__, + safe_strerror(errno)); + close(new_sync_sock); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: %s: New peer: %pI4/%u", __func__, + &peer_sync.sin_addr, ntohs(peer_sync.sin_port)); + + /* Create new socket for asynchronous messages. */ + peer_async = peer_sync; + peer_async.sin_port = htons(ntohs(peer_sync.sin_port) + 1); + + /* Check if remote port number to make reverse connection is valid one. + */ + if (ntohs(peer_async.sin_port) == ospf_apiserver_getport()) { + zlog_warn("API: %s: Peer(%pI4/%u): Invalid async port number?", + __func__, &peer_async.sin_addr, + ntohs(peer_async.sin_port)); + close(new_sync_sock); + return; + } + + new_async_sock = socket(AF_INET, SOCK_STREAM, 0); + if (new_async_sock < 0) { + zlog_warn("%s: socket: %s", __func__, safe_strerror(errno)); + close(new_sync_sock); + return; + } + + ret = connect(new_async_sock, (struct sockaddr *)&peer_async, + sizeof(struct sockaddr_in)); + + if (ret < 0) { + zlog_warn("%s: connect: %s", __func__, safe_strerror(errno)); + close(new_sync_sock); + close(new_async_sock); + return; + } + +#ifdef USE_ASYNC_READ +#else /* USE_ASYNC_READ */ + /* Make the asynchronous channel write-only. */ + ret = shutdown(new_async_sock, SHUT_RD); + if (ret < 0) { + zlog_warn("%s: shutdown: %s", __func__, safe_strerror(errno)); + close(new_sync_sock); + close(new_async_sock); + return; + } +#endif /* USE_ASYNC_READ */ + + /* Allocate new server-side connection structure */ + apiserv = ospf_apiserver_new(new_sync_sock, new_async_sock); + + /* Add to active connection list */ + listnode_add(apiserver_list, apiserv); + apiserv->peer_sync = peer_sync; + apiserv->peer_async = peer_async; + + /* And add read threads for new connection */ + ospf_apiserver_event(OSPF_APISERVER_SYNC_READ, new_sync_sock, apiserv); +#ifdef USE_ASYNC_READ + ospf_apiserver_event(OSPF_APISERVER_ASYNC_READ, new_async_sock, + apiserv); +#endif /* USE_ASYNC_READ */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("API: New apiserv(%p), total#(%d)", (void *)apiserv, + apiserver_list->count); +} + + +/* ----------------------------------------------------------- + * Send reply with return code to client application + * ----------------------------------------------------------- + */ + +static int ospf_apiserver_send_msg(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_fifo *fifo; + struct msg *msg2; + enum ospf_apiserver_event event; + int fd; + + switch (msg->hdr.msgtype) { + case MSG_REPLY: + fifo = apiserv->out_sync_fifo; + fd = apiserv->fd_sync; + event = OSPF_APISERVER_SYNC_WRITE; + break; + case MSG_READY_NOTIFY: + case MSG_LSA_UPDATE_NOTIFY: + case MSG_LSA_DELETE_NOTIFY: + case MSG_NEW_IF: + case MSG_DEL_IF: + case MSG_ISM_CHANGE: + case MSG_NSM_CHANGE: + case MSG_REACHABLE_CHANGE: + case MSG_ROUTER_ID_CHANGE: + fifo = apiserv->out_async_fifo; + fd = apiserv->fd_async; + event = OSPF_APISERVER_ASYNC_WRITE; + break; + default: + zlog_warn("%s: Unknown message type %d", __func__, + msg->hdr.msgtype); + return -1; + } + + /* Make a copy of the message and put in the fifo. Once the fifo + gets drained by the write thread, the message will be freed. */ + /* NB: Given "msg" is untouched in this function. */ + msg2 = msg_dup(msg); + + /* Enqueue message into corresponding fifo queue */ + msg_fifo_push(fifo, msg2); + + /* Schedule write thread */ + ospf_apiserver_event(event, fd, apiserv); + return 0; +} + +int ospf_apiserver_send_reply(struct ospf_apiserver *apiserv, uint32_t seqnr, + uint8_t rc) +{ + struct msg *msg = new_msg_reply(seqnr, rc); + int ret; + + if (!msg) { + zlog_warn("%s: msg_new failed", __func__); +#ifdef NOTYET + /* Cannot allocate new message. What should we do? */ + ospf_apiserver_free(apiserv); +#endif + return -1; + } + + ret = ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + return ret; +} + + +/* ----------------------------------------------------------- + * Generic message dispatching handler function + * ----------------------------------------------------------- + */ + +int ospf_apiserver_handle_msg(struct ospf_apiserver *apiserv, struct msg *msg) +{ + int rc; + + /* Call corresponding message handler function. */ + switch (msg->hdr.msgtype) { + case MSG_REGISTER_OPAQUETYPE: + rc = ospf_apiserver_handle_register_opaque_type(apiserv, msg); + break; + case MSG_UNREGISTER_OPAQUETYPE: + rc = ospf_apiserver_handle_unregister_opaque_type(apiserv, msg); + break; + case MSG_REGISTER_EVENT: + rc = ospf_apiserver_handle_register_event(apiserv, msg); + break; + case MSG_SYNC_LSDB: + rc = ospf_apiserver_handle_sync_lsdb(apiserv, msg); + break; + case MSG_ORIGINATE_REQUEST: + rc = ospf_apiserver_handle_originate_request(apiserv, msg); + break; + case MSG_DELETE_REQUEST: + rc = ospf_apiserver_handle_delete_request(apiserv, msg); + break; + case MSG_SYNC_REACHABLE: + rc = ospf_apiserver_handle_sync_reachable(apiserv, msg); + break; + case MSG_SYNC_ISM: + rc = ospf_apiserver_handle_sync_ism(apiserv, msg); + break; + case MSG_SYNC_NSM: + rc = ospf_apiserver_handle_sync_nsm(apiserv, msg); + break; + case MSG_SYNC_ROUTER_ID: + rc = ospf_apiserver_handle_sync_router_id(apiserv, msg); + break; + default: + zlog_warn("%s: Unknown message type: %d", __func__, + msg->hdr.msgtype); + rc = -1; + } + return rc; +} + + +/* ----------------------------------------------------------- + * Following are functions for opaque type registration + * ----------------------------------------------------------- + */ + +int ospf_apiserver_register_opaque_type(struct ospf_apiserver *apiserv, + uint8_t lsa_type, uint8_t opaque_type) +{ + struct registered_opaque_type *regtype; + int (*originator_func)(void *arg); + int rc; + + switch (lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + originator_func = ospf_apiserver_lsa9_originator; + break; + case OSPF_OPAQUE_AREA_LSA: + originator_func = ospf_apiserver_lsa10_originator; + break; + case OSPF_OPAQUE_AS_LSA: + originator_func = ospf_apiserver_lsa11_originator; + break; + default: + zlog_warn("%s: lsa_type(%d)", __func__, lsa_type); + return OSPF_API_ILLEGALLSATYPE; + } + + + /* Register opaque function table */ + /* NB: Duplicated registration will be detected inside the function. */ + rc = ospf_register_opaque_functab( + lsa_type, opaque_type, NULL, /* ospf_apiserver_new_if */ + NULL, /* ospf_apiserver_del_if */ + NULL, /* ospf_apiserver_ism_change */ + NULL, /* ospf_apiserver_nsm_change */ + NULL, NULL, NULL, ospf_apiserver_show_info, originator_func, + ospf_apiserver_lsa_refresher, + NULL, /* ospf_apiserver_lsa_update */ + NULL /* ospf_apiserver_lsa_delete */); + + if (rc != 0) { + flog_warn(EC_OSPF_OPAQUE_REGISTRATION, + "Failed to register opaque type [%d/%d]", lsa_type, + opaque_type); + return OSPF_API_OPAQUETYPEINUSE; + } + + /* Remember the opaque type that application registers so when + connection shuts down, we can flush all LSAs of this opaque + type. */ + + regtype = + XCALLOC(MTYPE_APISERVER, sizeof(struct registered_opaque_type)); + regtype->lsa_type = lsa_type; + regtype->opaque_type = opaque_type; + + /* Add to list of registered opaque types */ + listnode_add(apiserv->opaque_types, regtype); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "API: Add LSA-type(%d)/Opaque-type(%d) into apiserv(%p), total#(%d)", + lsa_type, opaque_type, (void *)apiserv, + listcount(apiserv->opaque_types)); + + return 0; +} + +int ospf_apiserver_unregister_opaque_type(struct ospf_apiserver *apiserv, + uint8_t lsa_type, uint8_t opaque_type) +{ + struct listnode *node, *nnode; + struct registered_opaque_type *regtype; + + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, regtype)) { + /* Check if we really registered this opaque type */ + if (regtype->lsa_type == lsa_type + && regtype->opaque_type == opaque_type) { + + /* Yes, we registered this opaque type. Flush + all existing opaque LSAs of this type */ + + ospf_apiserver_flush_opaque_lsa(apiserv, lsa_type, + opaque_type); + ospf_delete_opaque_functab(lsa_type, opaque_type); + + /* Remove from list of registered opaque types */ + listnode_delete(apiserv->opaque_types, regtype); + + XFREE(MTYPE_APISERVER, regtype); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "API: Del LSA-type(%d)/Opaque-type(%d) from apiserv(%p), total#(%d)", + lsa_type, opaque_type, (void *)apiserv, + listcount(apiserv->opaque_types)); + + return 0; + } + } + + /* Opaque type is not registered */ + zlog_warn("Failed to unregister opaque type [%d/%d]", lsa_type, + opaque_type); + return OSPF_API_OPAQUETYPENOTREGISTERED; +} + + +static int apiserver_is_opaque_type_registered(struct ospf_apiserver *apiserv, + uint8_t lsa_type, + uint8_t opaque_type) +{ + struct listnode *node, *nnode; + struct registered_opaque_type *regtype; + + /* XXX: how many types are there? if few, why not just a bitmap? */ + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, regtype)) { + /* Check if we really registered this opaque type */ + if (regtype->lsa_type == lsa_type + && regtype->opaque_type == opaque_type) { + /* Yes registered */ + return 1; + } + } + /* Not registered */ + return 0; +} + +int ospf_apiserver_handle_register_opaque_type(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_register_opaque_type *rmsg; + uint8_t lsa_type; + uint8_t opaque_type; + int rc = 0; + + /* Extract parameters from register opaque type message */ + rmsg = (struct msg_register_opaque_type *)STREAM_DATA(msg->s); + + lsa_type = rmsg->lsatype; + opaque_type = rmsg->opaquetype; + + rc = ospf_apiserver_register_opaque_type(apiserv, lsa_type, + opaque_type); + + /* Send a reply back to client including return code */ + rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); + if (rc < 0) + goto out; + + /* Now inform application about opaque types that are ready */ + switch (lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + ospf_apiserver_notify_ready_type9(apiserv); + break; + case OSPF_OPAQUE_AREA_LSA: + ospf_apiserver_notify_ready_type10(apiserv); + break; + case OSPF_OPAQUE_AS_LSA: + ospf_apiserver_notify_ready_type11(apiserv); + break; + } +out: + return rc; +} + + +/* Notify specific client about all opaque types 9 that are ready. */ +void ospf_apiserver_notify_ready_type9(struct ospf_apiserver *apiserv) +{ + struct listnode *node, *nnode; + struct listnode *node2, *nnode2; + struct ospf *ospf; + struct ospf_interface *oi; + struct registered_opaque_type *r; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { + /* Check if this interface is indeed ready for type 9 */ + if (!ospf_apiserver_is_ready_type9(oi)) + continue; + + /* Check for registered opaque type 9 types */ + /* XXX: loop-de-loop - optimise me */ + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, + r)) { + struct msg *msg; + + if (r->lsa_type == OSPF_OPAQUE_LINK_LSA) { + + /* Yes, this opaque type is ready */ + msg = new_msg_ready_notify( + 0, OSPF_OPAQUE_LINK_LSA, r->opaque_type, + oi->address->u.prefix4); + if (!msg) { + zlog_warn("%s: msg_new failed", + __func__); +#ifdef NOTYET + /* Cannot allocate new message. What + * should we do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + } + +out: + return; +} + + +/* Notify specific client about all opaque types 10 that are ready. */ +void ospf_apiserver_notify_ready_type10(struct ospf_apiserver *apiserv) +{ + struct listnode *node, *nnode; + struct listnode *node2, *nnode2; + struct ospf *ospf; + struct ospf_area *area; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + struct registered_opaque_type *r; + + if (!ospf_apiserver_is_ready_type10(area)) { + continue; + } + + /* Check for registered opaque type 10 types */ + /* XXX: loop in loop - optimise me */ + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, + r)) { + struct msg *msg; + + if (r->lsa_type == OSPF_OPAQUE_AREA_LSA) { + /* Yes, this opaque type is ready */ + msg = new_msg_ready_notify( + 0, OSPF_OPAQUE_AREA_LSA, r->opaque_type, + area->area_id); + if (!msg) { + zlog_warn("%s: msg_new failed", + __func__); +#ifdef NOTYET + /* Cannot allocate new message. What + * should we do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + } + +out: + return; +} + +/* Notify specific client about all opaque types 11 that are ready */ +void ospf_apiserver_notify_ready_type11(struct ospf_apiserver *apiserv) +{ + struct listnode *node, *nnode; + struct ospf *ospf; + struct registered_opaque_type *r; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Can type 11 be originated? */ + if (!ospf_apiserver_is_ready_type11(ospf)) + goto out; + + /* Check for registered opaque type 11 types */ + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node, nnode, r)) { + struct msg *msg; + struct in_addr noarea_id = {.s_addr = 0L}; + + if (r->lsa_type == OSPF_OPAQUE_AS_LSA) { + /* Yes, this opaque type is ready */ + msg = new_msg_ready_notify(0, OSPF_OPAQUE_AS_LSA, + r->opaque_type, noarea_id); + + if (!msg) { + zlog_warn("%s: msg_new failed", __func__); +#ifdef NOTYET + /* Cannot allocate new message. What should we + * do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + +out: + return; +} + +int ospf_apiserver_handle_unregister_opaque_type(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_unregister_opaque_type *umsg; + uint8_t ltype; + uint8_t otype; + int rc = 0; + + /* Extract parameters from unregister opaque type message */ + umsg = (struct msg_unregister_opaque_type *)STREAM_DATA(msg->s); + + ltype = umsg->lsatype; + otype = umsg->opaquetype; + + rc = ospf_apiserver_unregister_opaque_type(apiserv, ltype, otype); + + /* Send a reply back to client including return code */ + rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); + + return rc; +} + + +/* ----------------------------------------------------------- + * Following are functions for event (filter) registration. + * ----------------------------------------------------------- + */ +int ospf_apiserver_handle_register_event(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_register_event *rmsg; + int rc; + uint32_t seqnum; + size_t size; + + rmsg = (struct msg_register_event *)STREAM_DATA(msg->s); + + /* Get request sequence number */ + seqnum = msg_get_seq(msg); + + /* Free existing filter in apiserv. */ + XFREE(MTYPE_APISERVER_MSGFILTER, apiserv->filter); + /* Alloc new space for filter. */ + size = ntohs(msg->hdr.msglen); + if (size < OSPF_MAX_LSA_SIZE) { + + apiserv->filter = XMALLOC(MTYPE_APISERVER_MSGFILTER, size); + + /* copy it over. */ + memcpy(apiserv->filter, &rmsg->filter, size); + rc = OSPF_API_OK; + } else + rc = OSPF_API_NOMEMORY; + + /* Send a reply back to client with return code */ + rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + return rc; +} + + +/* ----------------------------------------------------------- + * Following are functions for LSDB synchronization. + * ----------------------------------------------------------- + */ + +static int apiserver_sync_callback(struct ospf_lsa *lsa, void *p_arg, + int int_arg) +{ + struct ospf_apiserver *apiserv; + int seqnum; + struct msg *msg; + struct param_t { + struct ospf_apiserver *apiserv; + struct lsa_filter_type *filter; + } * param; + int rc = -1; + + /* Sanity check */ + assert(lsa->data); + assert(p_arg); + + param = (struct param_t *)p_arg; + apiserv = param->apiserv; + seqnum = (uint32_t)int_arg; + + /* Check origin in filter. */ + if ((param->filter->origin == ANY_ORIGIN) + || (param->filter->origin == (lsa->flags & OSPF_LSA_SELF))) { + + /* Default area for AS-External and Opaque11 LSAs */ + struct in_addr area_id = {.s_addr = 0L}; + + /* Default interface for non Opaque9 LSAs */ + struct in_addr ifaddr = {.s_addr = 0L}; + + if (lsa->area) { + area_id = lsa->area->area_id; + } + if (lsa->data->type == OSPF_OPAQUE_LINK_LSA) { + ifaddr = lsa->oi->address->u.prefix4; + } + + msg = new_msg_lsa_change_notify( + MSG_LSA_UPDATE_NOTIFY, seqnum, ifaddr, area_id, + lsa->flags & OSPF_LSA_SELF, lsa->data); + if (!msg) { + zlog_warn("%s: new_msg_update failed", __func__); +#ifdef NOTYET + /* Cannot allocate new message. What should we do? */ + /* ospf_apiserver_free (apiserv);*/ /* Do nothing + here XXX + */ +#endif + goto out; + } + + /* Send LSA */ + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + rc = 0; + +out: + return rc; +} + +int ospf_apiserver_handle_sync_lsdb(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct listnode *node, *nnode; + uint32_t seqnum; + int rc = 0; + struct msg_sync_lsdb *smsg; + struct ospf_apiserver_param_t { + struct ospf_apiserver *apiserv; + struct lsa_filter_type *filter; + } param; + uint16_t mask; + struct route_node *rn; + struct ospf_lsa *lsa; + struct ospf *ospf; + struct ospf_area *area; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Get request sequence number */ + seqnum = msg_get_seq(msg); + /* Set sync msg. */ + smsg = (struct msg_sync_lsdb *)STREAM_DATA(msg->s); + + /* Set parameter struct. */ + param.apiserv = apiserv; + param.filter = &smsg->filter; + + /* Remember mask. */ + mask = ntohs(smsg->filter.typemask); + + /* Iterate over all areas. */ + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + int i; + uint32_t *area_id = NULL; + + /* Compare area_id with area_ids in sync request. */ + if ((i = smsg->filter.num_areas) > 0) { + /* Let area_id point to the list of area IDs, + * which is at the end of smsg->filter. */ + area_id = (uint32_t *)(&smsg->filter + 1); + while (i) { + if (*area_id == area->area_id.s_addr) { + break; + } + i--; + area_id++; + } + } else { + i = 1; + } + + /* If area was found, then i>0 here. */ + if (i) { + /* Check msg type. */ + if (mask & Power2[OSPF_ROUTER_LSA]) + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + if (mask & Power2[OSPF_NETWORK_LSA]) + LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + if (mask & Power2[OSPF_SUMMARY_LSA]) + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + if (mask & Power2[OSPF_ASBR_SUMMARY_LSA]) + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + if (mask & Power2[OSPF_OPAQUE_LINK_LSA]) + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + if (mask & Power2[OSPF_OPAQUE_AREA_LSA]) + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + apiserver_sync_callback( + lsa, (void *)¶m, seqnum); + } + } + + /* For AS-external LSAs */ + if (ospf->lsdb) { + if (mask & Power2[OSPF_AS_EXTERNAL_LSA]) + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + apiserver_sync_callback(lsa, (void *)¶m, + seqnum); + } + + /* For AS-external opaque LSAs */ + if (ospf->lsdb) { + if (mask & Power2[OSPF_OPAQUE_AS_LSA]) + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + apiserver_sync_callback(lsa, (void *)¶m, + seqnum); + } + + /* Send a reply back to client with return code */ + rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + return rc; +} + +/* + * ----------------------------------------------------------- + * Followings are functions for synchronization. + * ----------------------------------------------------------- + */ + +int ospf_apiserver_handle_sync_reachable(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct route_table *rt = ospf->all_rtrs; + uint32_t seqnum = msg_get_seq(msg); + struct in_addr *a, *abuf; + struct msg_reachable_change *areach; + struct msg *amsg; + uint mcount, count; + int _rc, rc = 0; + + if (!rt) + goto out; + + /* send all adds based on current reachable routers */ + a = abuf = XCALLOC(MTYPE_APISERVER, sizeof(struct in_addr) * rt->count); + for (struct route_node *rn = route_top(rt); rn; rn = route_next(rn)) + if (listhead((struct list *)rn->info)) + *a++ = rn->p.u.prefix4; + + assert((a - abuf) <= (long)rt->count); + count = (a - abuf); + + a = abuf; + while (count && !rc) { + amsg = new_msg_reachable_change(seqnum, count, a, 0, NULL); + areach = (struct msg_reachable_change *)STREAM_DATA(amsg->s); + mcount = ntohs(areach->nadd) + ntohs(areach->nremove); + assert(mcount <= count); + a = a + mcount; + count -= mcount; + rc = ospf_apiserver_send_msg(apiserv, amsg); + msg_free(amsg); + } + XFREE(MTYPE_APISERVER, abuf); + +out: + /* Send a reply back to client with return code */ + _rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + rc = rc ? rc : _rc; + apiserv->reachable_sync = !rc; + return rc; +} + +int ospf_apiserver_handle_sync_ism(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct listnode *anode, *inode; + struct ospf_area *area; + struct ospf_interface *oi; + struct msg *m; + uint32_t seqnum = msg_get_seq(msg); + int _rc, rc = 0; + + /* walk all areas */ + for (ALL_LIST_ELEMENTS_RO(ospf->areas, anode, area)) { + /* walk all interfaces */ + for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi)) { + m = new_msg_ism_change(seqnum, oi->address->u.prefix4, + area->area_id, oi->state); + rc = ospf_apiserver_send_msg(apiserv, m); + msg_free(m); + if (rc) + break; + } + if (rc) + break; + } + /* Send a reply back to client with return code */ + _rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + return rc ? rc : _rc; +} + + +int ospf_apiserver_handle_sync_nsm(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct listnode *anode, *inode; + struct ospf_area *area; + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct route_node *rn; + struct msg *m; + uint32_t seqnum = msg_get_seq(msg); + int _rc, rc = 0; + + /* walk all areas */ + for (ALL_LIST_ELEMENTS_RO(ospf->areas, anode, area)) { + /* walk all interfaces */ + for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi)) { + /* walk all neighbors */ + for (rn = route_top(oi->nbrs); rn; + rn = route_next(rn)) { + nbr = rn->info; + if (!nbr) + continue; + m = new_msg_nsm_change( + seqnum, oi->address->u.prefix4, + nbr->src, nbr->router_id, nbr->state); + rc = ospf_apiserver_send_msg(apiserv, m); + msg_free(m); + if (rc) + break; + } + if (rc) + break; + } + if (rc) + break; + } + /* Send a reply back to client with return code */ + _rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + return rc ? rc : _rc; +} + + +int ospf_apiserver_handle_sync_router_id(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + uint32_t seqnum = msg_get_seq(msg); + struct msg *m; + int _rc, rc = 0; + + m = new_msg_router_id_change(seqnum, ospf->router_id); + rc = ospf_apiserver_send_msg(apiserv, m); + msg_free(m); + + /* Send a reply back to client with return code */ + _rc = ospf_apiserver_send_reply(apiserv, seqnum, rc); + return rc ? rc : _rc; +} + +/* ----------------------------------------------------------- + * Following are functions to originate or update LSA + * from an application. + * ----------------------------------------------------------- + */ + +/* Create a new internal opaque LSA by taking prototype and filling in + missing fields such as age, sequence number, advertising router, + checksum and so on. The interface parameter is used for type 9 + LSAs, area parameter for type 10. Type 11 LSAs do neither need area + nor interface. */ + +struct ospf_lsa *ospf_apiserver_opaque_lsa_new(struct ospf_area *area, + struct ospf_interface *oi, + struct lsa_header *protolsa) +{ + struct stream *s; + struct lsa_header *newlsa; + struct ospf_lsa *new = NULL; + uint8_t options = 0x0; + uint16_t length; + + struct ospf *ospf; + + if (oi && oi->ospf) + ospf = oi->ospf; + else + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + assert(ospf); + + /* Create a stream for internal opaque LSA */ + if ((s = stream_new(OSPF_MAX_LSA_SIZE)) == NULL) { + zlog_warn("%s: stream_new failed", __func__); + return NULL; + } + + newlsa = (struct lsa_header *)STREAM_DATA(s); + + /* XXX If this is a link-local LSA or an AS-external LSA, how do we + have to set options? */ + + if (area) { + options = LSA_OPTIONS_GET(area); + options |= LSA_OPTIONS_NSSA_GET(area); + } + + options |= OSPF_OPTION_O; /* Don't forget to set option bit */ + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Creating an Opaque-LSA instance", + protolsa->type, &protolsa->id); + } + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, protolsa->type, protolsa->id, + ospf->router_id); + + /* Set opaque-LSA body fields. */ + stream_put(s, ((uint8_t *)protolsa) + sizeof(struct lsa_header), + ntohs(protolsa->length) - sizeof(struct lsa_header)); + + /* Determine length of LSA. */ + length = stream_get_endp(s); + newlsa->length = htons(length); + + /* Create OSPF LSA. */ + new = ospf_lsa_new_and_data(length); + + new->area = area; + new->oi = oi; + new->vrf_id = ospf->vrf_id; + + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, newlsa, length); + stream_free(s); + + return new; +} + + +int ospf_apiserver_is_ready_type9(struct ospf_interface *oi) +{ + /* We can always handle getting opaque's even if we can't flood them */ + return 1; +} + +int ospf_apiserver_is_ready_type10(struct ospf_area *area) +{ + /* We can always handle getting opaque's even if we can't flood them */ + return 1; +} + +int ospf_apiserver_is_ready_type11(struct ospf *ospf) +{ + /* We can always handle getting opaque's even if we can't flood them */ + return 1; +} + + +int ospf_apiserver_handle_originate_request(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_originate_request *omsg; + struct lsa_header *data; + struct ospf_lsa *new; + struct ospf_lsa *old; + struct ospf_area *area = NULL; + struct ospf_interface *oi = NULL; + struct ospf_lsdb *lsdb = NULL; + struct ospf *ospf; + int lsa_type, opaque_type; + int ready = 0; + int rc = 0; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Extract opaque LSA data from message */ + omsg = (struct msg_originate_request *)STREAM_DATA(msg->s); + data = &omsg->data; + + /* Determine interface for type9 or area for type10 LSAs. */ + switch (data->type) { + case OSPF_OPAQUE_LINK_LSA: + oi = ospf_apiserver_if_lookup_by_addr(omsg->ifaddr); + if (!oi) { + zlog_warn("%s: unknown interface %pI4", __func__, + &omsg->ifaddr); + rc = OSPF_API_NOSUCHINTERFACE; + goto out; + } + area = oi->area; + lsdb = area->lsdb; + break; + case OSPF_OPAQUE_AREA_LSA: + area = ospf_area_lookup_by_area_id(ospf, omsg->area_id); + if (!area) { + zlog_warn("%s: unknown area %pI4", __func__, + &omsg->area_id); + rc = OSPF_API_NOSUCHAREA; + goto out; + } + lsdb = area->lsdb; + break; + case OSPF_OPAQUE_AS_LSA: + lsdb = ospf->lsdb; + break; + default: + /* We can only handle opaque types here */ + zlog_warn("%s: Cannot originate non-opaque LSA type %d", + __func__, data->type); + rc = OSPF_API_ILLEGALLSATYPE; + goto out; + } + + /* Check if we registered this opaque type */ + lsa_type = data->type; + opaque_type = GET_OPAQUE_TYPE(ntohl(data->id.s_addr)); + + if (!apiserver_is_opaque_type_registered(apiserv, lsa_type, + opaque_type)) { + zlog_warn("%s: LSA-type(%d)/Opaque-type(%d): Not registered", + __func__, lsa_type, opaque_type); + rc = OSPF_API_OPAQUETYPENOTREGISTERED; + goto out; + } + + /* Make sure that the neighbors are ready before we can originate */ + switch (data->type) { + case OSPF_OPAQUE_LINK_LSA: + ready = ospf_apiserver_is_ready_type9(oi); + break; + case OSPF_OPAQUE_AREA_LSA: + ready = ospf_apiserver_is_ready_type10(area); + break; + case OSPF_OPAQUE_AS_LSA: + ready = ospf_apiserver_is_ready_type11(ospf); + break; + default: + break; + } + + if (!ready) { + zlog_warn("Neighbors not ready to originate type %d", + data->type); + rc = OSPF_API_NOTREADY; + goto out; + } + + /* Create OSPF's internal opaque LSA representation */ + new = ospf_apiserver_opaque_lsa_new(area, oi, data); + if (!new) { + rc = OSPF_API_NOMEMORY; /* XXX */ + goto out; + } + + /* Determine if LSA is new or an update for an existing one. */ + old = ospf_lsdb_lookup(lsdb, new); + + if (!old || !ospf_opaque_is_owned(old)) { + /* New LSA install in LSDB. */ + rc = ospf_apiserver_originate1(new, old); + } else { + /* + * Keep the new LSA instance in the "waiting place" until the + * next + * refresh timing. If several LSA update requests for the same + * LSID + * have issued by peer, the last one takes effect. + */ + new->lsdb = &apiserv->reserve; + ospf_lsdb_add(&apiserv->reserve, new); + + /* Kick the scheduler function. */ + ospf_opaque_lsa_refresh_schedule(old); + } + +out: + + /* Send a reply back to client with return code */ + rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); + return rc; +} + + +/* ----------------------------------------------------------- + * Flood an LSA within its flooding scope. + * ----------------------------------------------------------- + */ + +/* XXX We can probably use ospf_flood_through instead of this function + but then we need the neighbor parameter. If we set nbr to + NULL then ospf_flood_through crashes due to dereferencing NULL. */ + +void ospf_apiserver_flood_opaque_lsa(struct ospf_lsa *lsa) +{ + assert(lsa); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + /* Increment counters? XXX */ + + /* Flood LSA through local network. */ + ospf_flood_through_area(lsa->area, NULL /*nbr */, lsa); + break; + case OSPF_OPAQUE_AREA_LSA: + /* Update LSA origination count. */ + assert(lsa->area); + lsa->area->ospf->lsa_originate_count++; + + /* Flood LSA through area. */ + ospf_flood_through_area(lsa->area, NULL /*nbr */, lsa); + break; + case OSPF_OPAQUE_AS_LSA: { + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + assert(ospf); + + /* Increment counters? XXX */ + + /* Flood LSA through AS. */ + ospf_flood_through_as(ospf, NULL /*nbr */, lsa); + break; + } + } +} + +int ospf_apiserver_originate1(struct ospf_lsa *lsa, struct ospf_lsa *old) +{ + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + assert(ospf); + + if (old) { + /* + * An old LSA exists that we didn't originate it in this + * session. Dump it, but increment past it's seqnum. + */ + assert(!ospf_opaque_is_owned(old)); + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug( + "LSA[Type%d:%pI4]: OSPF API Server Originate LSA Old Seq: 0x%x Age: %d", + old->data->type, &old->data->id, + ntohl(old->data->ls_seqnum), + ntohl(old->data->ls_age)); + if (IS_LSA_MAX_SEQ(old)) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "%s: old LSA at maxseq", __func__); + return -1; + } + lsa->data->ls_seqnum = lsa_seqnum_increment(old); + ospf_discard_from_db(ospf, old->lsdb, old); + } + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug( + "LSA[Type%d:%pI4]: OSPF API Server Originate LSA New Seq: 0x%x Age: %d", + lsa->data->type, &lsa->data->id, + ntohl(lsa->data->ls_seqnum), ntohl(lsa->data->ls_age)); + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(ospf, lsa->oi, lsa) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "%s: ospf_lsa_install failed", __func__); + return -1; + } + +/* Flood LSA within scope */ + +#ifdef NOTYET + /* + * NB: Modified version of "ospf_flood_though ()" accepts NULL "inbr" + * parameter, and thus it does not cause SIGSEGV error. + */ + ospf_flood_through(NULL /*nbr */, lsa); +#else /* NOTYET */ + + ospf_apiserver_flood_opaque_lsa(lsa); +#endif /* NOTYET */ + + return 0; +} + + +/* Opaque LSAs of type 9 on a specific interface can now be + originated. Tell clients that registered type 9. */ +int ospf_apiserver_lsa9_originator(void *arg) +{ + struct ospf_interface *oi; + + oi = (struct ospf_interface *)arg; + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_ready_type9(oi); + } + return 0; +} + +int ospf_apiserver_lsa10_originator(void *arg) +{ + struct ospf_area *area; + + area = (struct ospf_area *)arg; + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_ready_type10(area); + } + return 0; +} + +int ospf_apiserver_lsa11_originator(void *arg) +{ + struct ospf *ospf; + + ospf = (struct ospf *)arg; + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_ready_type11(ospf); + } + return 0; +} + + +/* Periodically refresh opaque LSAs so that they do not expire in + other routers. */ +struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa) +{ + struct ospf_apiserver *apiserv; + struct ospf_lsa *new = NULL; + struct ospf *ospf; + + assert(lsa); + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + assert(ospf); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: OSPF API Server LSA Refresher", + lsa->data->type, &lsa->data->id); + } + + apiserv = lookup_apiserver_by_lsa(lsa); + if (!apiserv) { + zlog_warn("%s: LSA[%s]: No apiserver?", __func__, + dump_lsa_key(lsa)); + lsa->data->ls_age = + htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ + goto out; + } + + /* Check if updated version of LSA instance has already prepared. */ + new = ospf_lsdb_lookup(&apiserv->reserve, lsa); + if (!new) { + if (IS_LSA_MAXAGE(lsa)) { + ospf_opaque_lsa_flush_schedule(lsa); + goto out; + } + + /* This is a periodic refresh, driven by core OSPF mechanism. */ + new = ospf_apiserver_opaque_lsa_new(lsa->area, lsa->oi, + lsa->data); + if (!new) { + zlog_warn("%s: Cannot create a new LSA?", __func__); + goto out; + } + } else { + /* This is a forcible refresh, requested by OSPF-API client. */ + ospf_lsdb_delete(&apiserv->reserve, new); + new->lsdb = NULL; + } + + /* Increment sequence number */ + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* New LSA is in same area. */ + new->area = lsa->area; + SET_FLAG(new->flags, OSPF_LSA_SELF); + + /* Install LSA into LSDB. */ + if (ospf_lsa_install(ospf, new->oi, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "%s: ospf_lsa_install failed", __func__); + ospf_lsa_unlock(&new); + goto out; + } + +/* Flood updated LSA through interface, area or AS */ + +#ifdef NOTYET + ospf_flood_through(NULL /*nbr */, new); +#endif /* NOTYET */ + ospf_apiserver_flood_opaque_lsa(new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Refresh Opaque LSA", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + +out: + return new; +} + + +/* ----------------------------------------------------------- + * Following are functions to delete LSAs + * ----------------------------------------------------------- + */ + +int ospf_apiserver_handle_delete_request(struct ospf_apiserver *apiserv, + struct msg *msg) +{ + struct msg_delete_request *dmsg; + struct ospf_lsa *old; + struct ospf_area *area = NULL; + struct ospf_interface *oi = NULL; + struct in_addr id; + int lsa_type, opaque_type; + int rc = 0; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + assert(ospf); + + /* Extract opaque LSA from message */ + dmsg = (struct msg_delete_request *)STREAM_DATA(msg->s); + + /* Lookup area for link-local and area-local opaque LSAs */ + switch (dmsg->lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + oi = ospf_apiserver_if_lookup_by_addr(dmsg->addr); + if (!oi) { + zlog_warn("%s: unknown interface %pI4", __func__, + &dmsg->addr); + rc = OSPF_API_NOSUCHINTERFACE; + goto out; + } + area = oi->area; + break; + case OSPF_OPAQUE_AREA_LSA: + area = ospf_area_lookup_by_area_id(ospf, dmsg->addr); + if (!area) { + zlog_warn("%s: unknown area %pI4", __func__, + &dmsg->addr); + rc = OSPF_API_NOSUCHAREA; + goto out; + } + break; + case OSPF_OPAQUE_AS_LSA: + /* AS-external opaque LSAs have no designated area */ + area = NULL; + break; + default: + zlog_warn("%s: Cannot delete non-opaque LSA type %d", __func__, + dmsg->lsa_type); + rc = OSPF_API_ILLEGALLSATYPE; + goto out; + } + + /* Check if we registered this opaque type */ + lsa_type = dmsg->lsa_type; + opaque_type = dmsg->opaque_type; + + if (!apiserver_is_opaque_type_registered(apiserv, lsa_type, + opaque_type)) { + zlog_warn("%s: LSA-type(%d)/Opaque-type(%d): Not registered", + __func__, lsa_type, opaque_type); + rc = OSPF_API_OPAQUETYPENOTREGISTERED; + goto out; + } + + /* opaque_id is in network byte order */ + id.s_addr = htonl( + SET_OPAQUE_LSID(dmsg->opaque_type, ntohl(dmsg->opaque_id))); + + /* + * Even if the target LSA has once scheduled to flush, it remains in + * the LSDB until it is finally handled by the maxage remover thread. + * Therefore, the lookup function below may return non-NULL result. + */ + old = ospf_lsa_lookup(ospf, area, dmsg->lsa_type, id, ospf->router_id); + if (!old) { + zlog_warn("%s: LSA[Type%d:%pI4] not in LSDB", __func__, + dmsg->lsa_type, &id); + rc = OSPF_API_NOSUCHLSA; + goto out; + } + + if (IS_DEL_ZERO_LEN_LSA(dmsg)) { + /* minimize the size of the withdrawal: */ + old->opaque_zero_len_delete = 1; + } + + /* Schedule flushing of LSA from LSDB */ + /* NB: Multiple scheduling will produce a warning message, but harmless. + */ + ospf_opaque_lsa_flush_schedule(old); + +out: + + /* Send reply back to client including return code */ + rc = ospf_apiserver_send_reply(apiserv, ntohl(msg->hdr.msgseq), rc); + return rc; +} + +/* Flush self-originated opaque LSA */ +static int apiserver_flush_opaque_type_callback(struct ospf_lsa *lsa, + void *p_arg, int int_arg) +{ + struct param_t { + struct ospf_apiserver *apiserv; + uint8_t lsa_type; + uint8_t opaque_type; + } * param; + + /* Sanity check */ + assert(lsa->data); + assert(p_arg); + param = (struct param_t *)p_arg; + + /* If LSA matches type and opaque type then delete it */ + if (IS_LSA_SELF(lsa) && lsa->data->type == param->lsa_type + && GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + == param->opaque_type) { + ospf_opaque_lsa_flush_schedule(lsa); + } + return 0; +} + +/* Delete self-originated opaque LSAs of a given opaque type. This + function is called when an application unregisters a given opaque + type or a connection to an application closes and all those opaque + LSAs need to be flushed the LSDB. */ +void ospf_apiserver_flush_opaque_lsa(struct ospf_apiserver *apiserv, + uint8_t lsa_type, uint8_t opaque_type) +{ + struct param_t { + struct ospf_apiserver *apiserv; + uint8_t lsa_type; + uint8_t opaque_type; + } param; + struct listnode *node, *nnode; + struct ospf *ospf; + struct ospf_area *area; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + assert(ospf); + + /* Set parameter struct. */ + param.apiserv = apiserv; + param.lsa_type = lsa_type; + param.opaque_type = opaque_type; + + switch (lsa_type) { + struct route_node *rn; + struct ospf_lsa *lsa; + + case OSPF_OPAQUE_LINK_LSA: + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + apiserver_flush_opaque_type_callback( + lsa, (void *)¶m, 0); + break; + case OSPF_OPAQUE_AREA_LSA: + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + apiserver_flush_opaque_type_callback( + lsa, (void *)¶m, 0); + break; + case OSPF_OPAQUE_AS_LSA: + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + apiserver_flush_opaque_type_callback(lsa, + (void *)¶m, 0); + break; + default: + break; + } + return; +} + + +/* ----------------------------------------------------------- + * Following are callback functions to handle opaque types + * ----------------------------------------------------------- + */ + +int ospf_apiserver_new_if(struct interface *ifp) +{ + struct ospf_interface *oi; + + /* For some strange reason it seems possible that we are invoked + with an interface that has no name. This seems to happen during + initialization. Return if this happens */ + + if (ifp->name[0] == '\0') { + /* interface has empty name */ + zlog_warn("%s: interface has no name?", __func__); + return 0; + } + + /* zlog_warn for debugging */ + zlog_warn("ospf_apiserver_new_if"); + zlog_warn("ifp name=%s status=%d index=%d", ifp->name, ifp->status, + ifp->ifindex); + + if (ifp->name[0] == '\0') { + /* interface has empty name */ + zlog_warn("%s: interface has no name?", __func__); + return 0; + } + + oi = ospf_apiserver_if_lookup_by_ifp(ifp); + + if (!oi) { + /* This interface is known to Zebra but not to OSPF daemon yet. + */ + zlog_warn("%s: interface %s not known to OSPFd?", __func__, + ifp->name); + return 0; + } + + assert(oi); + + /* New interface added to OSPF, tell clients about it */ + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_new_if(oi); + } + return 0; +} + +int ospf_apiserver_del_if(struct interface *ifp) +{ + struct ospf_interface *oi; + + /* zlog_warn for debugging */ + zlog_warn("%s ifp name=%s status=%d index=%d", __func__, ifp->name, + ifp->status, ifp->ifindex); + + oi = ospf_apiserver_if_lookup_by_ifp(ifp); + + if (!oi) { + /* This interface is known to Zebra but not to OSPF daemon + anymore. No need to tell clients about it */ + zlog_warn("ifp name=%s not known to OSPFd", ifp->name); + return 0; + } + + /* Interface deleted, tell clients about it */ + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_del_if(oi); + } + return 0; +} + +void ospf_apiserver_ism_change(struct ospf_interface *oi, int old_state) +{ + /* Tell clients about interface change */ + + /* zlog_warn for debugging */ + zlog_warn("%s", __func__); + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_ism_change(oi); + } + + zlog_warn("%s oi->ifp->name=%s old_state=%d oi->state=%d", __func__, + oi->ifp->name, old_state, oi->state); +} + +void ospf_apiserver_nsm_change(struct ospf_neighbor *nbr, int old_status) +{ + /* Neighbor status changed, tell clients about it */ + zlog_warn("%s", __func__); + if (listcount(apiserver_list) > 0) { + ospf_apiserver_clients_notify_nsm_change(nbr); + } +} + +void ospf_apiserver_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa) +{ + struct opaque_lsa { + struct lsa_header header; + uint8_t data[1]; /* opaque data have variable length. This is + start + address */ + }; + struct opaque_lsa *olsa; + int opaquelen; + + olsa = (struct opaque_lsa *)lsa->data; + + if (VALID_OPAQUE_INFO_LEN(lsa->data)) + opaquelen = ntohs(lsa->data->length) - OSPF_LSA_HEADER_SIZE; + else + opaquelen = 0; + + /* Output information about opaque LSAs */ + if (json) + json_object_string_addf(json, "opaqueData", "%*pHXn", + (int)opaquelen, olsa->data); + else if (vty != NULL) { + int i; + vty_out(vty, + " Added using OSPF API: %u octets of opaque data %s\n", + opaquelen, + VALID_OPAQUE_INFO_LEN(lsa->data) ? "" + : "(Invalid length?)"); + vty_out(vty, " Opaque data: "); + + for (i = 0; i < opaquelen; i++) { + vty_out(vty, "0x%x ", olsa->data[i]); + } + vty_out(vty, "\n"); + } else { + int i; + zlog_debug( + " Added using OSPF API: %u octets of opaque data %s", + opaquelen, + VALID_OPAQUE_INFO_LEN(lsa->data) ? "" + : "(Invalid length?)"); + zlog_debug(" Opaque data: "); + + for (i = 0; i < opaquelen; i++) { + zlog_debug("0x%x ", olsa->data[i]); + } + } + return; +} + +/* ----------------------------------------------------------- + * Following are functions to notify clients about events + * ----------------------------------------------------------- + */ + +/* Send a message to all clients. This is useful for messages + that need to be notified to all clients (such as interface + changes) */ + +void ospf_apiserver_clients_notify_all(struct msg *msg) +{ + struct listnode *node, *nnode; + struct ospf_apiserver *apiserv; + + /* Send message to all clients */ + for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) + ospf_apiserver_send_msg(apiserv, msg); +} + +/* An interface is now ready to accept opaque LSAs. Notify all + clients that registered to use this opaque type */ +void ospf_apiserver_clients_notify_ready_type9(struct ospf_interface *oi) +{ + struct listnode *node, *nnode; + struct msg *msg; + struct ospf_apiserver *apiserv; + + assert(oi); + if (!oi->address) { + zlog_warn("Interface has no address?"); + return; + } + + if (!ospf_apiserver_is_ready_type9(oi)) { + zlog_warn("Interface not ready for type 9?"); + return; + } + + for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { + struct listnode *node2, *nnode2; + struct registered_opaque_type *r; + + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, + r)) { + if (r->lsa_type == OSPF_OPAQUE_LINK_LSA) { + msg = new_msg_ready_notify( + 0, OSPF_OPAQUE_LINK_LSA, r->opaque_type, + oi->address->u.prefix4); + if (!msg) { + zlog_warn( + "%s: new_msg_ready_notify failed", + __func__); +#ifdef NOTYET + /* Cannot allocate new message. What + * should we do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + } + +out: + return; +} + +void ospf_apiserver_clients_notify_ready_type10(struct ospf_area *area) +{ + struct listnode *node, *nnode; + struct msg *msg; + struct ospf_apiserver *apiserv; + + assert(area); + + if (!ospf_apiserver_is_ready_type10(area)) { + zlog_warn("Area not ready for type 10?"); + return; + } + + for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { + struct listnode *node2, *nnode2; + struct registered_opaque_type *r; + + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, + r)) { + if (r->lsa_type == OSPF_OPAQUE_AREA_LSA) { + msg = new_msg_ready_notify( + 0, OSPF_OPAQUE_AREA_LSA, r->opaque_type, + area->area_id); + if (!msg) { + zlog_warn( + "%s: new_msg_ready_nofity failed", + __func__); +#ifdef NOTYET + /* Cannot allocate new message. What + * should we do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + } + +out: + return; +} + + +void ospf_apiserver_clients_notify_ready_type11(struct ospf *top) +{ + struct listnode *node, *nnode; + struct msg *msg; + struct in_addr id_null = {.s_addr = 0L}; + struct ospf_apiserver *apiserv; + + assert(top); + + if (!ospf_apiserver_is_ready_type11(top)) { + zlog_warn("AS not ready for type 11?"); + return; + } + + for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { + struct listnode *node2, *nnode2; + struct registered_opaque_type *r; + + for (ALL_LIST_ELEMENTS(apiserv->opaque_types, node2, nnode2, + r)) { + if (r->lsa_type == OSPF_OPAQUE_AS_LSA) { + msg = new_msg_ready_notify( + 0, OSPF_OPAQUE_AS_LSA, r->opaque_type, + id_null); + if (!msg) { + zlog_warn( + "%s: new_msg_ready_notify failed", + __func__); +#ifdef NOTYET + /* Cannot allocate new message. What + * should we do? */ + ospf_apiserver_free(apiserv); +#endif + goto out; + } + + ospf_apiserver_send_msg(apiserv, msg); + msg_free(msg); + } + } + } + +out: + return; +} + +void ospf_apiserver_clients_notify_new_if(struct ospf_interface *oi) +{ + struct msg *msg; + + msg = new_msg_new_if(0, oi->address->u.prefix4, oi->area->area_id); + if (msg != NULL) { + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); + } +} + +void ospf_apiserver_clients_notify_del_if(struct ospf_interface *oi) +{ + struct msg *msg; + + msg = new_msg_del_if(0, oi->address->u.prefix4); + if (msg != NULL) { + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); + } +} + +void ospf_apiserver_clients_notify_ism_change(struct ospf_interface *oi) +{ + struct msg *msg; + struct in_addr ifaddr = {.s_addr = 0L}; + struct in_addr area_id = {.s_addr = 0L}; + + assert(oi); + assert(oi->ifp); + + if (oi->address) { + ifaddr = oi->address->u.prefix4; + } + if (oi->area) { + area_id = oi->area->area_id; + } + + msg = new_msg_ism_change(0, ifaddr, area_id, oi->state); + if (!msg) { + zlog_warn("%s: msg_new failed", __func__); + return; + } + + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); +} + +void ospf_apiserver_clients_notify_nsm_change(struct ospf_neighbor *nbr) +{ + struct msg *msg; + struct in_addr ifaddr; + struct in_addr nbraddr; + + assert(nbr); + + ifaddr = nbr->oi->address->u.prefix4; + + nbraddr = nbr->address.u.prefix4; + + msg = new_msg_nsm_change(0, ifaddr, nbraddr, nbr->router_id, + nbr->state); + if (!msg) { + zlog_warn("%s: msg_new failed", __func__); + return; + } + + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); +} + +static int apiserver_clients_lsa_change_notify(uint8_t msgtype, + struct ospf_lsa *lsa) +{ + struct msg *msg; + struct listnode *node, *nnode; + struct ospf_apiserver *apiserv; + + /* Default area for AS-External and Opaque11 LSAs */ + struct in_addr area_id = {.s_addr = 0L}; + + /* Default interface for non Opaque9 LSAs */ + struct in_addr ifaddr = {.s_addr = 0L}; + + if (lsa->area) { + area_id = lsa->area->area_id; + } + if (lsa->data->type == OSPF_OPAQUE_LINK_LSA) { + assert(lsa->oi); + ifaddr = lsa->oi->address->u.prefix4; + } + + /* Prepare message that can be sent to clients that have a matching + filter */ + msg = new_msg_lsa_change_notify(msgtype, 0L, /* no sequence number */ + ifaddr, area_id, + lsa->flags & OSPF_LSA_SELF, lsa->data); + if (!msg) { + zlog_warn("%s: msg_new failed", __func__); + return -1; + } + + /* Now send message to all clients with a matching filter */ + for (ALL_LIST_ELEMENTS(apiserver_list, node, nnode, apiserv)) { + struct lsa_filter_type *filter; + uint16_t mask; + uint32_t *area; + int i; + + /* Check filter for this client. */ + filter = apiserv->filter; + + /* Check area IDs in case of non AS-E LSAs. + * If filter has areas (num_areas > 0), + * then one of the areas must match the area ID of this LSA. */ + + i = filter->num_areas; + if ((lsa->data->type == OSPF_AS_EXTERNAL_LSA) + || (lsa->data->type == OSPF_OPAQUE_AS_LSA)) { + i = 0; + } + + if (i > 0) { + area = (uint32_t *)(filter + 1); + while (i) { + if (*area == area_id.s_addr) { + break; + } + i--; + area++; + } + } else { + i = 1; + } + + if (i > 0) { + /* Area match. Check LSA type. */ + mask = ntohs(filter->typemask); + + if (mask & Power2[lsa->data->type]) { + /* Type also matches. Check origin. */ + if ((filter->origin == ANY_ORIGIN) + || (filter->origin == IS_LSA_SELF(lsa))) { + ospf_apiserver_send_msg(apiserv, msg); + } + } + } + } + /* Free message since it is not used anymore */ + msg_free(msg); + + return 0; +} + + +/* ------------------------------------------------------------- + * Following are hooks invoked when LSAs are updated or deleted + * ------------------------------------------------------------- + */ + + +int ospf_apiserver_lsa_update(struct ospf_lsa *lsa) +{ + + /* Only notify this update if the LSA's age is smaller than + MAXAGE. Otherwise clients would see LSA updates with max age just + before they are deleted from the LSDB. LSA delete messages have + MAXAGE too but should not be filtered. */ + if (IS_LSA_MAXAGE(lsa)) + return 0; + return apiserver_clients_lsa_change_notify(MSG_LSA_UPDATE_NOTIFY, lsa); +} + +int ospf_apiserver_lsa_delete(struct ospf_lsa *lsa) +{ + return apiserver_clients_lsa_change_notify(MSG_LSA_DELETE_NOTIFY, lsa); +} + +/* ------------------------------------------------------------- + * Reachable functions + * ------------------------------------------------------------- + */ + +static inline int cmp_route_nodes(struct route_node *orn, + struct route_node *nrn) +{ + if (!orn) + return 1; + else if (!nrn) + return -1; + + uint32_t opn = ntohl(orn->p.u.prefix4.s_addr); + uint32_t npn = ntohl(nrn->p.u.prefix4.s_addr); + if (opn < npn) + return -1; + else if (opn > npn) + return 1; + else + return 0; +} + +void ospf_apiserver_notify_reachable(struct route_table *ort, + struct route_table *nrt) +{ + struct msg *msg; + struct msg_reachable_change *areach; + struct route_node *orn, *nrn; + const uint insz = sizeof(struct in_addr); + struct in_addr *abuf = NULL, *dbuf = NULL; + struct in_addr *a = NULL, *d = NULL; + uint nadd, nremove; + int cmp; + + if (!ort && !nrt) { + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug("%s: no routing tables", __func__); + return; + } + if (nrt && nrt->count) + a = abuf = XCALLOC(MTYPE_APISERVER, insz * nrt->count); + if (ort && ort->count) + d = dbuf = XCALLOC(MTYPE_APISERVER, insz * ort->count); + + /* walk both tables */ + orn = ort ? route_top(ort) : NULL; + nrn = nrt ? route_top(nrt) : NULL; + while (orn || nrn) { + if (orn && !listhead((struct list *)orn->info)) { + orn = route_next(orn); + continue; + } + if (nrn && !listhead((struct list *)nrn->info)) { + nrn = route_next(nrn); + continue; + } + cmp = cmp_route_nodes(orn, nrn); + if (!cmp) { + /* if old == new advance old and new */ + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug("keeping router id: %pI4", + &orn->p.u.prefix4); + orn = route_next(orn); + nrn = route_next(nrn); + } else if (cmp < 0) { + assert(d != NULL); /* Silence SA warning */ + + /* if old < new, delete old, advance old */ + *d++ = orn->p.u.prefix4; + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug("removing router id: %pI4", + &orn->p.u.prefix4); + orn = route_next(orn); + } else { + assert(a != NULL); /* Silence SA warning */ + + /* if new < old, add new, advance new */ + *a++ = nrn->p.u.prefix4; + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug("adding router id: %pI4", + &nrn->p.u.prefix4); + nrn = route_next(nrn); + } + } + + nadd = abuf ? (a - abuf) : 0; + nremove = dbuf ? (d - dbuf) : 0; + a = abuf; + d = dbuf; + + while (nadd + nremove) { + msg = new_msg_reachable_change(0, nadd, a, nremove, d); + areach = (struct msg_reachable_change *)STREAM_DATA(msg->s); + + a += ntohs(areach->nadd); + nadd = nadd - ntohs(areach->nadd); + + d += ntohs(areach->nremove); + nremove = nremove - ntohs(areach->nremove); + + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug("%s: adding %d removing %d", __func__, + ntohs(areach->nadd), ntohs(areach->nremove)); + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); + } + if (abuf) + XFREE(MTYPE_APISERVER, abuf); + if (dbuf) + XFREE(MTYPE_APISERVER, dbuf); +} + + +void ospf_apiserver_clients_notify_router_id_change(struct in_addr router_id) +{ + struct msg *msg; + + msg = new_msg_router_id_change(0, router_id); + if (!msg) { + zlog_warn("%s: new_msg_router_id_change failed", __func__); + return; + } + + ospf_apiserver_clients_notify_all(msg); + msg_free(msg); +} + + +#endif /* SUPPORT_OSPF_API */ diff --git a/ospfd/ospf_apiserver.h b/ospfd/ospf_apiserver.h new file mode 100644 index 0000000..4341a9d --- /dev/null +++ b/ospfd/ospf_apiserver.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Server side of OSPF API. + * Copyright (C) 2001, 2002 Ralph Keller + */ + +#ifndef _OSPF_APISERVER_H +#define _OSPF_APISERVER_H + +#include +#include "ospf_api.h" +#include "ospf_lsdb.h" + +/* List of opaque types that application registered */ +struct registered_opaque_type { + uint8_t lsa_type; + uint8_t opaque_type; +}; + + +/* Server instance for each accepted client connection. */ +struct ospf_apiserver { + /* Socket connections for synchronous commands and asynchronous + notifications */ + int fd_sync; /* synchronous requests */ + struct sockaddr_in peer_sync; + + int fd_async; /* asynchronous notifications */ + struct sockaddr_in peer_async; + + /* List of all opaque types that application registers to use. Using + a single connection with the OSPF daemon, multiple + pairs can be registered. However, each + combination can only be registered once by all applications. */ + struct list *opaque_types; /* of type registered_opaque_type */ + + /* Temporary storage for LSA instances to be refreshed. */ + struct ospf_lsdb reserve; + + /* Sync reachable routers */ + bool reachable_sync; + + /* filter for LSA update/delete notifies */ + struct lsa_filter_type *filter; + + /* Fifo buffers for outgoing messages */ + struct msg_fifo *out_sync_fifo; + struct msg_fifo *out_async_fifo; + + /* Read and write threads */ + struct event *t_sync_read; +#ifdef USE_ASYNC_READ + struct event *t_async_read; +#endif /* USE_ASYNC_READ */ + struct event *t_sync_write; + struct event *t_async_write; +}; + +enum ospf_apiserver_event { + OSPF_APISERVER_ACCEPT, + OSPF_APISERVER_SYNC_READ, +#ifdef USE_ASYNC_READ + OSPF_APISERVER_ASYNC_READ, +#endif /* USE_ASYNC_READ */ + OSPF_APISERVER_SYNC_WRITE, + OSPF_APISERVER_ASYNC_WRITE +}; + +/* ----------------------------------------------------------- + * External definitions for OSPF API ospfd parameters. + * ----------------------------------------------------------- + */ + +extern int ospf_apiserver_enable; +extern struct in_addr ospf_apiserver_addr; + +/* ----------------------------------------------------------- + * Following are functions to manage client connections. + * ----------------------------------------------------------- + */ + +extern unsigned short ospf_apiserver_getport(void); +extern int ospf_apiserver_init(void); +extern void ospf_apiserver_term(void); +extern struct ospf_apiserver *ospf_apiserver_new(int fd_sync, int fd_async); +extern void ospf_apiserver_free(struct ospf_apiserver *apiserv); +extern void ospf_apiserver_event(enum ospf_apiserver_event event, int fd, + struct ospf_apiserver *apiserv); +extern int ospf_apiserver_serv_sock_family(unsigned short port, int family); +extern void ospf_apiserver_accept(struct event *thread); +extern void ospf_apiserver_read(struct event *thread); +extern void ospf_apiserver_sync_write(struct event *thread); +extern void ospf_apiserver_async_write(struct event *thread); +extern int ospf_apiserver_send_reply(struct ospf_apiserver *apiserv, + uint32_t seqnr, uint8_t rc); + +/* ----------------------------------------------------------- + * Following are message handler functions + * ----------------------------------------------------------- + */ + +extern int ospf_apiserver_lsa9_originator(void *arg); +extern int ospf_apiserver_lsa10_originator(void *arg); +extern int ospf_apiserver_lsa11_originator(void *arg); + +extern void ospf_apiserver_clients_notify_all(struct msg *msg); + +extern void +ospf_apiserver_clients_notify_ready_type9(struct ospf_interface *oi); +extern void ospf_apiserver_clients_notify_ready_type10(struct ospf_area *area); +extern void ospf_apiserver_clients_notify_ready_type11(struct ospf *top); + +extern void ospf_apiserver_clients_notify_new_if(struct ospf_interface *oi); +extern void ospf_apiserver_clients_notify_del_if(struct ospf_interface *oi); +extern void ospf_apiserver_clients_notify_ism_change(struct ospf_interface *oi); +extern void ospf_apiserver_clients_notify_nsm_change(struct ospf_neighbor *nbr); +extern void +ospf_apiserver_clients_notify_router_id_change(struct in_addr router_id); + +extern int ospf_apiserver_is_ready_type9(struct ospf_interface *oi); +extern int ospf_apiserver_is_ready_type10(struct ospf_area *area); +extern int ospf_apiserver_is_ready_type11(struct ospf *ospf); + +extern void ospf_apiserver_notify_ready_type9(struct ospf_apiserver *apiserv); +extern void ospf_apiserver_notify_ready_type10(struct ospf_apiserver *apiserv); +extern void ospf_apiserver_notify_ready_type11(struct ospf_apiserver *apiserv); + +extern int ospf_apiserver_handle_msg(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int +ospf_apiserver_handle_register_opaque_type(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int +ospf_apiserver_handle_unregister_opaque_type(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_register_event(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int +ospf_apiserver_handle_originate_request(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_delete_request(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_sync_lsdb(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_sync_reachable(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_sync_ism(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_sync_nsm(struct ospf_apiserver *apiserv, + struct msg *msg); +extern int ospf_apiserver_handle_sync_router_id(struct ospf_apiserver *apiserv, + struct msg *msg); + +extern void ospf_apiserver_notify_reachable(struct route_table *ort, + struct route_table *nrt); + +/* ----------------------------------------------------------- + * Following are functions for LSA origination/deletion + * ----------------------------------------------------------- + */ + +extern int ospf_apiserver_register_opaque_type(struct ospf_apiserver *apiserver, + uint8_t lsa_type, + uint8_t opaque_type); +extern int +ospf_apiserver_unregister_opaque_type(struct ospf_apiserver *apiserver, + uint8_t lsa_type, uint8_t opaque_type); +extern struct ospf_lsa * +ospf_apiserver_opaque_lsa_new(struct ospf_area *area, struct ospf_interface *oi, + struct lsa_header *protolsa); +extern struct ospf_interface * +ospf_apiserver_if_lookup_by_addr(struct in_addr address); +extern struct ospf_interface * +ospf_apiserver_if_lookup_by_ifp(struct interface *ifp); +extern int ospf_apiserver_originate1(struct ospf_lsa *lsa, + struct ospf_lsa *old); +extern void ospf_apiserver_flood_opaque_lsa(struct ospf_lsa *lsa); + + +/* ----------------------------------------------------------- + * Following are callback functions to handle opaque types + * ----------------------------------------------------------- + */ + +extern int ospf_apiserver_new_if(struct interface *ifp); +extern int ospf_apiserver_del_if(struct interface *ifp); +extern void ospf_apiserver_ism_change(struct ospf_interface *oi, + int old_status); +extern void ospf_apiserver_nsm_change(struct ospf_neighbor *nbr, + int old_status); +extern void ospf_apiserver_config_write_router(struct vty *vty); +extern void ospf_apiserver_config_write_if(struct vty *vty, + struct interface *ifp); +extern void ospf_apiserver_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); +extern int ospf_ospf_apiserver_lsa_originator(void *arg); +extern struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa); +extern void ospf_apiserver_flush_opaque_lsa(struct ospf_apiserver *apiserv, + uint8_t lsa_type, + uint8_t opaque_type); + +/* ----------------------------------------------------------- + * Following are hooks when LSAs are updated or deleted + * ----------------------------------------------------------- + */ + + +/* Hooks that are invoked from ospf opaque module */ + +extern int ospf_apiserver_lsa_update(struct ospf_lsa *lsa); +extern int ospf_apiserver_lsa_delete(struct ospf_lsa *lsa); + +#endif /* _OSPF_APISERVER_H */ diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c new file mode 100644 index 0000000..9b62f36 --- /dev/null +++ b/ospfd/ospf_asbr.c @@ -0,0 +1,1225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF AS Boundary Router functions. + * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "vty.h" +#include "filter.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_errors.h" + +/* Remove external route. */ +void ospf_external_route_remove(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct route_node *rn; + struct ospf_route * or ; + + rn = route_node_lookup(ospf->old_external_route, (struct prefix *)p); + if (rn) + if ((or = rn->info)) { + zlog_info("Route[%pFX]: external path deleted", p); + + /* Remove route from zebra. */ + if (or->type == OSPF_DESTINATION_NETWORK) + ospf_zebra_delete( + ospf, (struct prefix_ipv4 *)&rn->p, or); + + ospf_route_free(or); + rn->info = NULL; + + route_unlock_node(rn); + route_unlock_node(rn); + return; + } + + zlog_info("Route[%pFX]: no such external path", p); +} + +/* Add an External info for AS-external-LSA. */ +struct external_info *ospf_external_info_new(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct external_info *new; + + new = XCALLOC(MTYPE_OSPF_EXTERNAL_INFO, sizeof(struct external_info)); + new->ospf = ospf; + new->type = type; + new->instance = instance; + new->to_be_processed = 0; + + ospf_reset_route_map_set_values(&new->route_map_set); + return new; +} + +static void ospf_external_info_free(struct external_info *ei) +{ + XFREE(MTYPE_OSPF_EXTERNAL_INFO, ei); +} + +void ospf_reset_route_map_set_values(struct route_map_set_values *values) +{ + values->metric = -1; + values->metric_type = -1; +} + +int ospf_route_map_set_compare(struct route_map_set_values *values1, + struct route_map_set_values *values2) +{ + return values1->metric == values2->metric + && values1->metric_type == values2->metric_type; +} + +/* Add an External info for AS-external-LSA. */ +struct external_info * +ospf_external_info_add(struct ospf *ospf, uint8_t type, unsigned short instance, + struct prefix_ipv4 p, ifindex_t ifindex, + struct in_addr nexthop, route_tag_t tag, uint32_t metric) +{ + struct external_info *new; + struct route_node *rn; + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + if (!ext) + ext = ospf_external_add(ospf, type, instance); + + rn = route_node_get(EXTERNAL_INFO(ext), (struct prefix *)&p); + /* If old info exists, -- discard new one or overwrite with new one? */ + if (rn && rn->info) { + new = rn->info; + if ((new->ifindex == ifindex) + && (new->nexthop.s_addr == nexthop.s_addr) + && (new->tag == tag) + && (new->metric == metric)) { + route_unlock_node(rn); + return NULL; /* NULL => no LSA to refresh */ + } + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "Redistribute[%s][%d][%u]: %pFX discarding old info with NH %pI4.", + ospf_redist_string(type), instance, + ospf->vrf_id, &p, &nexthop.s_addr); + XFREE(MTYPE_OSPF_EXTERNAL_INFO, rn->info); + } + + /* Create new External info instance. */ + new = ospf_external_info_new(ospf, type, instance); + new->p = p; + new->ifindex = ifindex; + new->nexthop = nexthop; + new->tag = tag; + new->orig_tag = tag; + new->aggr_route = NULL; + new->metric = metric; + new->min_metric = 0; + new->max_metric = OSPF_LS_INFINITY; + + /* we don't unlock rn from the get() because we're attaching the info */ + if (rn) + rn->info = new; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "Redistribute[%s][%u]: %pFX external info created, with NH %pI4, metric:%u", + ospf_redist_string(type), ospf->vrf_id, &p, + &nexthop.s_addr, metric); + } + return new; +} + +void ospf_external_info_delete(struct ospf *ospf, uint8_t type, + unsigned short instance, struct prefix_ipv4 p) +{ + struct route_node *rn; + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + if (!ext) + return; + + rn = route_node_lookup(EXTERNAL_INFO(ext), (struct prefix *)&p); + if (rn) { + ospf_external_info_free(rn->info); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } +} + +struct external_info *ospf_external_info_lookup(struct ospf *ospf, uint8_t type, + unsigned short instance, + struct prefix_ipv4 *p) +{ + struct route_node *rn; + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + if (!ext) + return NULL; + + rn = route_node_lookup(EXTERNAL_INFO(ext), (struct prefix *)p); + if (rn) { + route_unlock_node(rn); + if (rn->info) + return rn->info; + } + + return NULL; +} + +struct ospf_lsa *ospf_external_info_find_lsa(struct ospf *ospf, + struct prefix_ipv4 *p) +{ + struct ospf_lsa *lsa; + struct as_external_lsa *al; + struct in_addr mask, id; + + /* First search the lsdb with address specific LSID + * where all the host bits are set, if there a matched + * LSA, return. + * Ex: For route 10.0.0.0/16, LSID is 10.0.255.255 + * If no lsa with above LSID, use received address as + * LSID and check if any LSA in LSDB. + * If LSA found, check if the mask is same b/w the matched + * LSA and received prefix, if same then it is the LSA for + * this prefix. + * Ex: For route 10.0.0.0/16, LSID is 10.0.0.0 + */ + + masklen2ip(p->prefixlen, &mask); + id.s_addr = p->prefix.s_addr | (~mask.s_addr); + lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, OSPF_AS_EXTERNAL_LSA, id, + ospf->router_id); + if (lsa) { + if (p->prefixlen == IPV4_MAX_BITLEN) { + al = (struct as_external_lsa *)lsa->data; + + if (mask.s_addr != al->mask.s_addr) + return NULL; + } + return lsa; + } + + lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, OSPF_AS_EXTERNAL_LSA, + p->prefix, ospf->router_id); + + if (lsa) { + al = (struct as_external_lsa *)lsa->data; + if (mask.s_addr == al->mask.s_addr) + return lsa; + } + + return NULL; +} + + +/* Update ASBR status. */ +void ospf_asbr_status_update(struct ospf *ospf, uint8_t status) +{ + zlog_info("ASBR[%s:Status:%d]: Update", + ospf_get_name(ospf), status); + + /* ASBR on. */ + if (status) { + /* Already ASBR. */ + if (IS_OSPF_ASBR(ospf)) { + zlog_info("ASBR[%s:Status:%d]: Already ASBR", + ospf_get_name(ospf), status); + return; + } + SET_FLAG(ospf->flags, OSPF_FLAG_ASBR); + } else { + /* Already non ASBR. */ + if (!IS_OSPF_ASBR(ospf)) { + zlog_info("ASBR[%s:Status:%d]: Already non ASBR", + ospf_get_name(ospf), status); + return; + } + UNSET_FLAG(ospf->flags, OSPF_FLAG_ASBR); + } + + /* Transition from/to status ASBR, schedule timer. */ + ospf_spf_calculate_schedule(ospf, SPF_FLAG_ASBR_STATUS_CHANGE); + ospf_router_lsa_update(ospf); +} + +/* If there's redistribution configured, we need to refresh external + * LSAs (e.g. when default-metric changes or NSSA settings change). + */ +static void ospf_asbr_redist_update_timer(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + int type; + + ospf->t_asbr_redist_update = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Running ASBR redistribution update on timer"); + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct listnode *node; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + ospf_external_lsa_refresh_type(ospf, type, + red->instance, + LSA_REFRESH_FORCE); + } + + ospf_external_lsa_refresh_default(ospf); +} + +void ospf_schedule_asbr_redist_update(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Scheduling ASBR redistribution update"); + + event_add_timer(master, ospf_asbr_redist_update_timer, ospf, + OSPF_ASBR_REDIST_UPDATE_DELAY, + &ospf->t_asbr_redist_update); +} + +void ospf_redistribute_withdraw(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct route_node *rn; + struct external_info *ei; + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + if (!ext) + return; + + /* Delete external info for specified type. */ + if (!EXTERNAL_INFO(ext)) + return; + + for (rn = route_top(EXTERNAL_INFO(ext)); rn; rn = route_next(rn)) { + ei = rn->info; + + if (!ei) + continue; + + struct ospf_external_aggr_rt *aggr; + + if (is_default_prefix4(&ei->p) + && ospf->default_originate != DEFAULT_ORIGINATE_NONE) + continue; + + aggr = ei->aggr_route; + + if (aggr) + ospf_unlink_ei_from_aggr(ospf, aggr, ei); + else if (ospf_external_info_find_lsa(ospf, &ei->p)) + ospf_external_lsa_flush(ospf, type, &ei->p, + ei->ifindex /*, ei->nexthop */); + + ospf_external_info_free(ei); + route_unlock_node(rn); + rn->info = NULL; + } +} + + +/* External Route Aggregator Handlers */ +bool is_valid_summary_addr(struct prefix_ipv4 *p) +{ + /* Default prefix validation*/ + if (p->prefix.s_addr == INADDR_ANY) + return false; + + /*Host route shouldn't be configured as summary addres*/ + if (p->prefixlen == IPV4_MAX_BITLEN) + return false; + + return true; +} +void ospf_asbr_external_aggregator_init(struct ospf *instance) +{ + instance->rt_aggr_tbl = route_table_init(); + + instance->t_external_aggr = NULL; + + instance->aggr_action = 0; + + instance->aggr_delay_interval = OSPF_EXTL_AGGR_DEFAULT_DELAY; +} + +static unsigned int ospf_external_rt_hash_key(const void *data) +{ + const struct external_info *ei = data; + unsigned int key = 0; + + key = prefix_hash_key(&ei->p); + return key; +} + +static bool ospf_external_rt_hash_cmp(const void *d1, const void *d2) +{ + const struct external_info *ei1 = d1; + const struct external_info *ei2 = d2; + + return prefix_same((struct prefix *)&ei1->p, (struct prefix *)&ei2->p); +} + +static struct ospf_external_aggr_rt * +ospf_external_aggregator_new(struct prefix_ipv4 *p) +{ + struct ospf_external_aggr_rt *aggr; + + aggr = (struct ospf_external_aggr_rt *)XCALLOC( + MTYPE_OSPF_EXTERNAL_RT_AGGR, + sizeof(struct ospf_external_aggr_rt)); + + if (!aggr) + return NULL; + + aggr->p.family = p->family; + aggr->p.prefix = p->prefix; + aggr->p.prefixlen = p->prefixlen; + aggr->match_extnl_hash = hash_create(ospf_external_rt_hash_key, + ospf_external_rt_hash_cmp, + "Ospf external route hash"); + return aggr; +} + +static void ospf_aggr_handle_external_info(void *data) +{ + struct external_info *ei = (struct external_info *)data; + struct ospf_external_aggr_rt *aggr = NULL; + struct ospf *ospf = ei->ospf; + struct ospf_lsa *lsa = NULL; + + ei->aggr_route = NULL; + + ei->to_be_processed = true; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Handle extrenal route(%pI4/%d)", __func__, + &ei->p.prefix, ei->p.prefixlen); + + assert(ospf); + + if (!ospf_redistribute_check(ospf, ei, NULL)) + return; + + aggr = ospf_external_aggr_match(ospf, &ei->p); + if (aggr) { + (void)ospf_originate_summary_lsa(ospf, aggr, ei); + return; + } + + lsa = ospf_external_info_find_lsa(ospf, &ei->p); + if (lsa) + ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE, 1); + else + (void)ospf_external_lsa_originate(ospf, ei); +} + +static void ospf_aggr_unlink_external_info(void *data) +{ + struct external_info *ei = (struct external_info *)data; + + ei->aggr_route = NULL; + + ei->to_be_processed = true; +} + +void ospf_external_aggregator_free(struct ospf_external_aggr_rt *aggr) +{ + hash_clean_and_free(&aggr->match_extnl_hash, + (void *)ospf_aggr_unlink_external_info); + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Release the aggregator Address(%pI4/%d)", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + + XFREE(MTYPE_OSPF_EXTERNAL_RT_AGGR, aggr); +} + +static void ospf_external_aggr_add(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr) +{ + struct route_node *rn; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Adding Aggregate route to Aggr table (%pI4/%d)", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + rn = route_node_get(ospf->rt_aggr_tbl, (struct prefix *)&aggr->p); + if (rn->info) + route_unlock_node(rn); + else + rn->info = aggr; +} + +static void ospf_external_aggr_delete(struct ospf *ospf, struct route_node *rn) +{ + struct ospf_external_aggr_rt *aggr = rn->info; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Deleting Aggregate route (%pI4/%d)", __func__, + &aggr->p.prefix, aggr->p.prefixlen); + + /* Sent a Max age LSA if it is already originated. */ + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Flushing Aggregate route (%pI4/%d)", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + ospf_external_lsa_flush(ospf, 0, &aggr->p, 0); + } + + rn->info = NULL; + route_unlock_node(rn); +} + +struct ospf_external_aggr_rt * +ospf_extrenal_aggregator_lookup(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct route_node *rn; + struct ospf_external_aggr_rt *summary_rt = NULL; + + rn = route_node_lookup(ospf->rt_aggr_tbl, (struct prefix *)p); + if (rn) { + summary_rt = rn->info; + route_unlock_node(rn); + return summary_rt; + } + return NULL; +} + +struct ospf_external_aggr_rt *ospf_external_aggr_match(struct ospf *ospf, + struct prefix_ipv4 *p) +{ + struct route_node *node; + struct ospf_external_aggr_rt *summary_rt = NULL; + + node = route_node_match(ospf->rt_aggr_tbl, (struct prefix *)p); + if (node) { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + if (node->info) { + struct ospf_external_aggr_rt *ag = node->info; + + zlog_debug( + "%s: Matching aggregator found.prefix:%pI4/%d Aggregator %pI4/%d", + __func__, &p->prefix, p->prefixlen, + &ag->p.prefix, ag->p.prefixlen); + } + + summary_rt = node->info; + route_unlock_node(node); + return summary_rt; + } + return NULL; +} + +void ospf_unlink_ei_from_aggr(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei) +{ + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Unlinking extrenal route(%pI4/%d) from aggregator(%pI4/%d), external route count:%ld", + __func__, &ei->p.prefix, ei->p.prefixlen, + &aggr->p.prefix, aggr->p.prefixlen, + OSPF_EXTERNAL_RT_COUNT(aggr)); + hash_release(aggr->match_extnl_hash, ei); + ei->aggr_route = NULL; + + /* Flush the aggreagte route if matching + * external route count becomes zero. + */ + if (!OSPF_EXTERNAL_RT_COUNT(aggr) + && CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Flushing the aggreagte route (%pI4/%d)", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + + /* Flush the aggregate LSA */ + ospf_external_lsa_flush(ospf, 0, &aggr->p, 0); + + /* Unset the Origination flag */ + UNSET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + } +} + +static void ospf_link_ei_to_aggr(struct ospf_external_aggr_rt *aggr, + struct external_info *ei) +{ + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Linking extrenal route(%pI4/%d) to aggregator(%pI4/%d)", + __func__, &ei->p.prefix, ei->p.prefixlen, + &aggr->p.prefix, aggr->p.prefixlen); + (void)hash_get(aggr->match_extnl_hash, ei, hash_alloc_intern); + ei->aggr_route = aggr; +} + +struct ospf_lsa *ospf_originate_summary_lsa(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei) +{ + struct ospf_lsa *lsa; + struct external_info ei_aggr; + struct as_external_lsa *asel; + struct ospf_external_aggr_rt *old_aggr; + route_tag_t tag = 0; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Prepare to originate Summary route(%pI4/%d)", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + + /* This case to handle when the overlapping aggregator address + * is availbe.Best match will be considered.So need to delink + * from old aggregator and link to the new aggr. + */ + if (ei->aggr_route) { + if (ei->aggr_route != aggr) { + old_aggr = ei->aggr_route; + ospf_unlink_ei_from_aggr(ospf, old_aggr, ei); + } + } + + /* Add the external route to hash table */ + ospf_link_ei_to_aggr(aggr, ei); + + lsa = ospf_external_info_find_lsa(ospf, &aggr->p); + /* Don't originate external LSA, + * If it is configured not to advertise. + */ + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) { + /* If it is already originated as external LSA, + * But, it is configured not to advertise then + * flush the originated external lsa. + */ + if (lsa) + ospf_external_lsa_flush(ospf, 0, &aggr->p, 0); + UNSET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + return NULL; + } + + /* Prepare the extrenal_info for aggregator */ + memset(&ei_aggr, 0, sizeof(ei_aggr)); + ei_aggr.p = aggr->p; + ei_aggr.tag = aggr->tag; + ei_aggr.type = 0; + ei_aggr.instance = ospf->instance; + ei_aggr.route_map_set.metric = -1; + ei_aggr.route_map_set.metric_type = -1; + + /* Summary route already originated, + * So, Do nothing. + */ + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + if (!lsa) { + flog_warn(EC_OSPF_LSA_MISSING, + "%s: Could not refresh/originate %pI4/%d", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + return NULL; + } + + asel = (struct as_external_lsa *)lsa->data; + tag = (unsigned long)ntohl(asel->e[0].route_tag); + + /* If tag modified , then re-originate the route + * with modified tag details. + */ + if (tag != ei_aggr.tag) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Route tag changed(old:%d new:%d,So refresh the summary route.(%pI4/%d)", + __func__, tag, ei_aggr.tag, + &aggr->p.prefix, aggr->p.prefixlen); + + ospf_external_lsa_refresh(ospf, lsa, &ei_aggr, + LSA_REFRESH_FORCE, 1); + } + return lsa; + } + + if (lsa && IS_LSA_MAXAGE(lsa)) { + /* This is special case. + * If a summary route need to be originated but where + * summary route already exist in lsdb with maxage, then + * it need to be refreshed. + */ + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: LSA is in MAX-AGE so refreshing LSA(%pI4/%d)", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + + ospf_external_lsa_refresh(ospf, lsa, &ei_aggr, + LSA_REFRESH_FORCE, 1); + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + return lsa; + } + + /* If the external route prefix same as aggregate route + * and if external route is already originated as TYPE-5 + * then it need to be refreshed and originate bit should + * be set. + */ + if (lsa && prefix_same((struct prefix *)&ei_aggr.p, + (struct prefix *)&ei->p)) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: External route prefix is same as aggr so refreshing LSA(%pI4/%d)", + __func__, &aggr->p.prefix, aggr->p.prefixlen); + ospf_external_lsa_refresh(ospf, lsa, &ei_aggr, + LSA_REFRESH_FORCE, 1); + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + return lsa; + } + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Originate Summary route(%pI4/%d)", __func__, + &aggr->p.prefix, aggr->p.prefixlen); + + /* Originate summary LSA */ + lsa = ospf_external_lsa_originate(ospf, &ei_aggr); + if (lsa) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Set the origination bit for aggregator", + __func__); + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + } + + return lsa; +} +void ospf_unset_all_aggr_flag(struct ospf *ospf) +{ + struct route_node *rn = NULL; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("Unset the origination bit for all aggregator"); + + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + struct ospf_external_aggr_rt *aggr = rn->info; + + UNSET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + } +} + +static void ospf_delete_all_marked_aggregators(struct ospf *ospf) +{ + struct route_node *rn = NULL; + + /* Loop through all the aggregators, Delete all aggregators + * which are marked as DELETE. Set action to NONE for remaining + * aggregators + */ + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + struct ospf_external_aggr_rt *aggr = rn->info; + + if (aggr->action != OSPF_ROUTE_AGGR_DEL) { + aggr->action = OSPF_ROUTE_AGGR_NONE; + continue; + } + ospf_external_aggr_delete(ospf, rn); + ospf_external_aggregator_free(aggr); + } +} + +static void ospf_handle_aggregated_exnl_rt(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei) +{ + struct ospf_lsa *lsa; + struct as_external_lsa *al; + struct in_addr mask; + + /* Handling the case where the external route prefix + * and aggregate prefix is same + * If same don't flush the originated external LSA. + */ + if (prefix_same((struct prefix *)&aggr->p, (struct prefix *)&ei->p)) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: External Route prefix same as Aggregator(%pI4/%d), so don't flush.", + __func__, &ei->p.prefix, ei->p.prefixlen); + return; + } + + lsa = ospf_external_info_find_lsa(ospf, &ei->p); + if (lsa) { + al = (struct as_external_lsa *)lsa->data; + masklen2ip(ei->p.prefixlen, &mask); + + if (mask.s_addr != al->mask.s_addr) + return; + + ospf_external_lsa_flush(ospf, ei->type, &ei->p, 0); + } +} + +static void ospf_handle_exnl_rt_after_aggr_del(struct ospf *ospf, + struct external_info *ei) +{ + struct ospf_lsa *lsa; + + /* Process only marked external routes. + * These routes were part of a deleted + * aggregator.So, originate now. + */ + if (!ei->to_be_processed) + return; + + ei->to_be_processed = false; + + lsa = ospf_external_info_find_lsa(ospf, &ei->p); + + if (lsa) + ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE, 0); + else { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Originate external route(%pI4/%d)", + __func__, &ei->p.prefix, ei->p.prefixlen); + + ospf_external_lsa_originate(ospf, ei); + } +} + +static void ospf_handle_external_aggr_add(struct ospf *ospf) +{ + struct external_info *ei; + struct route_node *rn = NULL; + struct route_table *rt = NULL; + int type = 0; + + /* Delete all the aggregators which are marked as + * OSPF_ROUTE_AGGR_DEL. + */ + ospf_delete_all_marked_aggregators(ospf); + + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *ext_list; + struct listnode *node; + struct ospf_external *ext; + struct ospf_external_aggr_rt *aggr; + + ext_list = ospf->external[type]; + if (!ext_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + rt = ext->external_info; + if (!rt) + continue; + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + if (!rn->info) + continue; + + ei = rn->info; + if (is_default_prefix4(&ei->p)) + continue; + + /* Check the AS-external-LSA + * should be originated. + */ + if (!ospf_redistribute_check(ospf, ei, NULL)) + continue; + + aggr = ospf_external_aggr_match(ospf, &ei->p); + + /* If matching aggregator found, Add + * the external route reference to the + * aggregator and originate the aggr + * route if it is advertisable. + * flush the external LSA if it is + * already originated for this external + * prefix. + */ + if (aggr) { + ospf_originate_summary_lsa(ospf, aggr, + ei); + + /* All aggregated external rts + * are handled here. + */ + ospf_handle_aggregated_exnl_rt( + ospf, aggr, ei); + continue; + } + + /* External routes which are only out + * of aggregation will be handled here. + */ + ospf_handle_exnl_rt_after_aggr_del(ospf, ei); + } + } + } +} + +static void +ospf_aggr_handle_advertise_change(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei_aggr) +{ + struct ospf_lsa *lsa; + + /* Check if advertise option modified. */ + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Don't originate the summary address,It is configured to not-advertise.", + __func__); + + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: No-advertise,So Flush the Aggregate route(%pI4/%d)", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + + ospf_external_lsa_flush(ospf, 0, &aggr->p, 0); + + UNSET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + } + return; + } + + if (!CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Now it is advatisable", __func__); + + lsa = ospf_external_info_find_lsa(ospf, &ei_aggr->p); + if (lsa && IS_LSA_MAXAGE(lsa)) { + /* This is special case. + * If a summary route need to be originated but where + * summary route already exist in lsdb with maxage, then + * it need to be refreshed. + */ + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: It is already with Maxage, So refresh it (%pI4/%d)", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + + ospf_external_lsa_refresh(ospf, lsa, ei_aggr, + LSA_REFRESH_FORCE, 1); + + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_ORIGINATED); + + } else { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Originate Aggregate LSA (%pI4/%d)", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + + /* Originate summary LSA */ + lsa = ospf_external_lsa_originate(ospf, ei_aggr); + if (lsa) + SET_FLAG(aggr->flags, + OSPF_EXTERNAL_AGGRT_ORIGINATED); + } + } +} + +static void ospf_handle_external_aggr_update(struct ospf *ospf) +{ + struct route_node *rn = NULL; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Process modified aggregators.", __func__); + + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) { + struct ospf_external_aggr_rt *aggr; + struct ospf_lsa *lsa = NULL; + struct as_external_lsa *asel = NULL; + struct external_info ei_aggr; + route_tag_t tag = 0; + + if (!rn->info) + continue; + + aggr = rn->info; + + if (aggr->action == OSPF_ROUTE_AGGR_DEL) { + aggr->action = OSPF_ROUTE_AGGR_NONE; + ospf_external_aggr_delete(ospf, rn); + + hash_clean_and_free( + &aggr->match_extnl_hash, + (void *)ospf_aggr_handle_external_info); + + ospf_external_aggregator_free(aggr); + } else if (aggr->action == OSPF_ROUTE_AGGR_MODIFY) { + + aggr->action = OSPF_ROUTE_AGGR_NONE; + + /* Prepare the extrenal_info for aggregator */ + memset(&ei_aggr, 0, sizeof(ei_aggr)); + ei_aggr.p = aggr->p; + ei_aggr.tag = aggr->tag; + ei_aggr.type = 0; + ei_aggr.instance = ospf->instance; + ei_aggr.route_map_set.metric = -1; + ei_aggr.route_map_set.metric_type = -1; + + /* Check if tag modified */ + if (CHECK_FLAG(aggr->flags, + OSPF_EXTERNAL_AGGRT_ORIGINATED)) { + lsa = ospf_external_info_find_lsa(ospf, + &ei_aggr.p); + if (!lsa) { + flog_warn(EC_OSPF_LSA_MISSING, + "%s: Could not refresh/originate %pI4/%d", + __func__, &aggr->p.prefix, + aggr->p.prefixlen); + continue; + } + + asel = (struct as_external_lsa *)lsa->data; + tag = (unsigned long)ntohl( + asel->e[0].route_tag); + + /* If tag modified , then re-originate the + * route with modified tag details. + */ + if (tag != ei_aggr.tag) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Route tag changed(old:%d new:%d,So refresh the summary route.(%pI4/%d)", + __func__, tag, + ei_aggr.tag, + &aggr->p.prefix, + aggr->p.prefixlen); + + ospf_external_lsa_refresh( + ospf, lsa, &ei_aggr, + LSA_REFRESH_FORCE, 1); + } + } + + /* Advertise option modified ? + * If so, handled it here. + */ + ospf_aggr_handle_advertise_change(ospf, aggr, &ei_aggr); + } + } +} + +static void ospf_asbr_external_aggr_process(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + int operation = 0; + + ospf->t_external_aggr = NULL; + operation = ospf->aggr_action; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: operation:%d", __func__, operation); + + switch (operation) { + case OSPF_ROUTE_AGGR_ADD: + ospf_handle_external_aggr_add(ospf); + break; + case OSPF_ROUTE_AGGR_DEL: + case OSPF_ROUTE_AGGR_MODIFY: + ospf_handle_external_aggr_update(ospf); + break; + default: + break; + } +} +static void ospf_external_aggr_timer(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + enum ospf_aggr_action_t operation) +{ + aggr->action = operation; + + if (ospf->t_external_aggr) { + if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD) { + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Not required to retsart timer,set is already added.", + __func__); + return; + } + + if (operation == OSPF_ROUTE_AGGR_ADD) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s, Restarting Aggregator delay timer.", + __func__); + EVENT_OFF(ospf->t_external_aggr); + } + } + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug("%s: Start Aggregator delay timer %u(in seconds).", + __func__, ospf->aggr_delay_interval); + + ospf->aggr_action = operation; + event_add_timer(master, ospf_asbr_external_aggr_process, ospf, + ospf->aggr_delay_interval, &ospf->t_external_aggr); +} + +int ospf_asbr_external_aggregator_set(struct ospf *ospf, struct prefix_ipv4 *p, + route_tag_t tag) +{ + struct ospf_external_aggr_rt *aggregator; + + aggregator = ospf_extrenal_aggregator_lookup(ospf, p); + + if (aggregator) { + if (CHECK_FLAG(aggregator->flags, + OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) + UNSET_FLAG(aggregator->flags, + OSPF_EXTERNAL_AGGRT_NO_ADVERTISE); + else if (aggregator->tag == tag) + return OSPF_SUCCESS; + + aggregator->tag = tag; + + ospf_external_aggr_timer(ospf, aggregator, + OSPF_ROUTE_AGGR_MODIFY); + } else { + aggregator = ospf_external_aggregator_new(p); + if (!aggregator) + return OSPF_FAILURE; + + aggregator->tag = tag; + + ospf_external_aggr_add(ospf, aggregator); + ospf_external_aggr_timer(ospf, aggregator, OSPF_ROUTE_AGGR_ADD); + } + + return OSPF_SUCCESS; +} + +int ospf_asbr_external_aggregator_unset(struct ospf *ospf, + struct prefix_ipv4 *p, route_tag_t tag) +{ + struct route_node *rn; + struct ospf_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf->rt_aggr_tbl, (struct prefix *)p); + if (!rn) + return OSPF_INVALID; + route_unlock_node(rn); + + aggr = rn->info; + + if (tag && (tag != aggr->tag)) + return OSPF_INVALID; + + if (!OSPF_EXTERNAL_RT_COUNT(aggr)) { + ospf_external_aggr_delete(ospf, rn); + ospf_external_aggregator_free(aggr); + return OSPF_SUCCESS; + } + + ospf_external_aggr_timer(ospf, aggr, OSPF_ROUTE_AGGR_DEL); + + return OSPF_SUCCESS; +} + +int ospf_asbr_external_rt_no_advertise(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct ospf_external_aggr_rt *aggr; + route_tag_t tag = 0; + + aggr = ospf_extrenal_aggregator_lookup(ospf, p); + if (aggr) { + if (CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF_SUCCESS; + + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE); + + aggr->tag = tag; + + if (!OSPF_EXTERNAL_RT_COUNT(aggr)) + return OSPF_SUCCESS; + + ospf_external_aggr_timer(ospf, aggr, OSPF_ROUTE_AGGR_MODIFY); + } else { + aggr = ospf_external_aggregator_new(p); + + if (!aggr) + return OSPF_FAILURE; + + SET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE); + ospf_external_aggr_add(ospf, aggr); + ospf_external_aggr_timer(ospf, aggr, OSPF_ROUTE_AGGR_ADD); + } + + return OSPF_SUCCESS; +} + +int ospf_asbr_external_rt_advertise(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct route_node *rn; + struct ospf_external_aggr_rt *aggr; + + rn = route_node_lookup(ospf->rt_aggr_tbl, (struct prefix *)p); + if (!rn) + return OSPF_INVALID; + route_unlock_node(rn); + + aggr = rn->info; + + if (!CHECK_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) + return OSPF_INVALID; + + UNSET_FLAG(aggr->flags, OSPF_EXTERNAL_AGGRT_NO_ADVERTISE); + + if (!OSPF_EXTERNAL_RT_COUNT(aggr)) + return OSPF_SUCCESS; + + ospf_external_aggr_timer(ospf, aggr, OSPF_ROUTE_AGGR_MODIFY); + return OSPF_SUCCESS; +} + +int ospf_external_aggregator_timer_set(struct ospf *ospf, uint16_t interval) +{ + ospf->aggr_delay_interval = interval; + return OSPF_SUCCESS; +} diff --git a/ospfd/ospf_asbr.h b/ospfd/ospf_asbr.h new file mode 100644 index 0000000..6158d65 --- /dev/null +++ b/ospfd/ospf_asbr.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF AS Boundary Router functions. + * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_ASBR_H +#define _ZEBRA_OSPF_ASBR_H + +struct route_map_set_values { + int32_t metric; + int32_t metric_type; +}; + +/* Redistributed external information. */ +struct external_info { + struct ospf *ospf; + + /* Type of source protocol. */ + uint8_t type; + + unsigned short instance; + + /* Prefix. */ + struct prefix_ipv4 p; + + /* Interface index. */ + ifindex_t ifindex; + + /* Nexthop address. */ + struct in_addr nexthop; + + /* Additional Route tag. */ + route_tag_t tag; + + /* Actual tag received from zebra*/ + route_tag_t orig_tag; + + uint32_t metric; + uint32_t min_metric; + uint32_t max_metric; + + struct route_map_set_values route_map_set; +#define ROUTEMAP_METRIC(E) (E)->route_map_set.metric +#define ROUTEMAP_METRIC_TYPE(E) (E)->route_map_set.metric_type + + /* Back pointer to summary address */ + struct ospf_external_aggr_rt *aggr_route; + + /* To identify the routes to be originated + * after a summary address deletion. + */ + bool to_be_processed; +}; + +#define OSPF_EXTL_AGGR_DEFAULT_DELAY 5 + +#define OSPF_EXTERNAL_RT_COUNT(aggr) \ + (((struct ospf_external_aggr_rt *)aggr)->match_extnl_hash->count) + +enum ospf_aggr_action_t { + OSPF_ROUTE_AGGR_NONE = 0, + OSPF_ROUTE_AGGR_ADD, + OSPF_ROUTE_AGGR_DEL, + OSPF_ROUTE_AGGR_MODIFY +}; + +#define OSPF_SUCCESS 1 +#define OSPF_FAILURE 0 +#define OSPF_INVALID -1 + +#define OSPF_EXTERNAL_AGGRT_NO_ADVERTISE 0x1 +#define OSPF_EXTERNAL_AGGRT_ORIGINATED 0x2 + +/* Data structures for external route aggregator */ +struct ospf_external_aggr_rt { + /* Prefix. */ + struct prefix_ipv4 p; + + /* Bit 1 : Dont advertise. + * Bit 2 : Originated as Type-5 + */ + uint8_t flags; + + /* Tag for summary route */ + route_tag_t tag; + + /* Action to be done at the delay + * timer expairy. + */ + enum ospf_aggr_action_t action; + + /* Hash Table of external routes */ + struct hash *match_extnl_hash; +}; + +#define OSPF_ASBR_CHECK_DELAY 30 +#define OSPF_ASBR_REDIST_UPDATE_DELAY 9 + +extern void ospf_external_route_remove(struct ospf *, struct prefix_ipv4 *); +extern struct external_info *ospf_external_info_new(struct ospf *, uint8_t, + unsigned short); +extern void ospf_reset_route_map_set_values(struct route_map_set_values *); +extern int ospf_route_map_set_compare(struct route_map_set_values *, + struct route_map_set_values *); +extern struct external_info * +ospf_external_info_add(struct ospf *, uint8_t, unsigned short, + struct prefix_ipv4, ifindex_t, struct in_addr, + route_tag_t, uint32_t metric); +extern void ospf_external_info_delete(struct ospf *, uint8_t, unsigned short, + struct prefix_ipv4); +extern struct external_info *ospf_external_info_lookup(struct ospf *, uint8_t, + unsigned short, + struct prefix_ipv4 *); +extern void ospf_asbr_status_update(struct ospf *, uint8_t); +extern void ospf_schedule_asbr_redist_update(struct ospf *ospf); + +extern void ospf_redistribute_withdraw(struct ospf *, uint8_t, unsigned short); +extern void ospf_asbr_check(void); +extern void ospf_schedule_asbr_check(void); +extern void ospf_asbr_route_install_lsa(struct ospf_lsa *); +extern struct ospf_lsa *ospf_external_info_find_lsa(struct ospf *, + struct prefix_ipv4 *p); + +/* External Route Aggregator */ +extern void ospf_asbr_external_aggregator_init(struct ospf *instance); +extern void ospf_external_aggregator_free(struct ospf_external_aggr_rt *aggr); +extern bool is_valid_summary_addr(struct prefix_ipv4 *p); +extern struct ospf_external_aggr_rt * +ospf_external_aggr_match(struct ospf *ospf, struct prefix_ipv4 *p); +extern void ospf_unlink_ei_from_aggr(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei); +extern struct ospf_lsa * +ospf_originate_summary_lsa(struct ospf *ospf, + struct ospf_external_aggr_rt *aggr, + struct external_info *ei); +extern int ospf_external_aggregator_timer_set(struct ospf *ospf, + uint16_t interval); +extern void ospf_external_aggrigator_free(struct ospf_external_aggr_rt *aggr); + +extern struct ospf_external_aggr_rt * +ospf_extrenal_aggregator_lookup(struct ospf *ospf, struct prefix_ipv4 *p); + +void ospf_unset_all_aggr_flag(struct ospf *ospf); + +extern int ospf_asbr_external_aggregator_set(struct ospf *ospf, + struct prefix_ipv4 *p, + route_tag_t tag); +extern int ospf_asbr_external_aggregator_unset(struct ospf *ospf, + struct prefix_ipv4 *p, + route_tag_t tag); +extern int ospf_asbr_external_rt_no_advertise(struct ospf *ospf, + struct prefix_ipv4 *p); +extern int ospf_asbr_external_rt_advertise(struct ospf *ospf, + struct prefix_ipv4 *p); +#endif /* _ZEBRA_OSPF_ASBR_H */ diff --git a/ospfd/ospf_ase.c b/ospfd/ospf_ase.c new file mode 100644 index 0000000..9e26a2a --- /dev/null +++ b/ospfd/ospf_ase.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF AS external route calculation. + * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "hash.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "vty.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" + +struct ospf_route *ospf_find_asbr_route(struct ospf *ospf, + struct route_table *rtrs, + struct prefix_ipv4 *asbr) +{ + struct route_node *rn; + struct ospf_route * or, *best = NULL; + struct listnode *node; + struct list *chosen; + + /* Sanity check. */ + if (rtrs == NULL) + return NULL; + + rn = route_node_lookup(rtrs, (struct prefix *)asbr); + if (!rn) + return NULL; + + route_unlock_node(rn); + + chosen = list_new(); + + /* First try to find intra-area non-bb paths. */ + if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) + for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) + if (or->cost < OSPF_LS_INFINITY) + if (!OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id) + && + or->path_type == OSPF_PATH_INTRA_AREA) + listnode_add(chosen, or); + + /* If none is found -- look through all. */ + if (listcount(chosen) == 0) { + list_delete(&chosen); + chosen = rn->info; + } + + /* Now find the route with least cost. */ + for (ALL_LIST_ELEMENTS_RO(chosen, node, or)) + if (or->cost < OSPF_LS_INFINITY) { + if (best == NULL) + best = or ; + else if (best->cost > or->cost) + best = or ; + else if (best->cost == + or->cost + && IPV4_ADDR_CMP( + &best->u.std.area_id, + & or->u.std.area_id) + < 0) + best = or ; + } + + if (chosen != rn->info) + list_delete(&chosen); + + return best; +} + +struct ospf_route *ospf_find_asbr_route_through_area(struct route_table *rtrs, + struct prefix_ipv4 *asbr, + struct ospf_area *area) +{ + struct route_node *rn; + + /* Sanity check. */ + if (rtrs == NULL) + return NULL; + + rn = route_node_lookup(rtrs, (struct prefix *)asbr); + + if (rn) { + struct listnode *node; + struct ospf_route * or ; + + route_unlock_node(rn); + + for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) + if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id)) + return or ; + } + + return NULL; +} + +static void ospf_ase_complete_direct_routes(struct ospf_route *ro, + struct in_addr nexthop) +{ + struct listnode *node; + struct ospf_path *op; + + for (ALL_LIST_ELEMENTS_RO(ro->paths, node, op)) + if (op->nexthop.s_addr == INADDR_ANY) + op->nexthop.s_addr = nexthop.s_addr; +} + +static int ospf_ase_forward_address_check(struct ospf *ospf, + struct in_addr fwd_addr) +{ + struct listnode *ifn; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, ifn, oi)) + if (if_is_operative(oi->ifp)) + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + if (IPV4_ADDR_SAME(&oi->address->u.prefix4, + &fwd_addr)) + return 0; + + return 1; +} + +static struct ospf_route * +ospf_ase_calculate_new_route(struct ospf_lsa *lsa, + struct ospf_route *asbr_route, uint32_t metric) +{ + struct as_external_lsa *al; + struct ospf_route *new; + + al = (struct as_external_lsa *)lsa->data; + + new = ospf_route_new(); + + /* Set redistributed type -- does make sense? */ + /* new->type = type; */ + new->id = al->header.id; + new->mask = al->mask; + + if (!IS_EXTERNAL_METRIC(al->e[0].tos)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: type-1 created, asbr cost:%d metric:%d.", + asbr_route->cost, metric); + new->path_type = OSPF_PATH_TYPE1_EXTERNAL; + new->cost = asbr_route->cost + metric; /* X + Y */ + } else { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("Route[External]: type-2 created."); + new->path_type = OSPF_PATH_TYPE2_EXTERNAL; + new->cost = asbr_route->cost; /* X */ + new->u.ext.type2_cost = metric; /* Y */ + } + + new->type = OSPF_DESTINATION_NETWORK; + new->u.ext.origin = lsa; + new->u.ext.tag = ntohl(al->e[0].route_tag); + new->u.ext.asbr = asbr_route; + + assert(new != asbr_route); + + return new; +} + +#define OSPF_ASE_CALC_INTERVAL 1 + +int ospf_ase_calculate_route(struct ospf *ospf, struct ospf_lsa *lsa) +{ + uint32_t metric; + struct as_external_lsa *al; + struct ospf_route *asbr_route; + struct prefix_ipv4 asbr, p; + struct route_node *rn; + struct ospf_route *new, * or ; + int ret; + + assert(lsa); + al = (struct as_external_lsa *)lsa->data; + + if (lsa->data->type == OSPF_AS_NSSA_LSA) + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Processing Type-7", __func__); + + /* Stay away from any Local Translated Type-7 LSAs */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Rejecting Local Xlt'd", __func__); + return 0; + } + + if (IS_DEBUG_OSPF(lsa, LSA)) { + zlog_debug( + "Route[External]: Calculate AS-external-LSA to %pI4/%d adv_router %pI4", + &al->header.id, ip_masklen(al->mask), + &al->header.adv_router); + } + + /* (1) If the cost specified by the LSA is LSInfinity, or if the + LSA's LS age is equal to MaxAge, then examine the next LSA. */ + if ((metric = GET_METRIC(al->e[0].metric)) >= OSPF_LS_INFINITY) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Metric is OSPF_LS_INFINITY"); + return 0; + } + if (IS_LSA_MAXAGE(lsa)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: AS-external-LSA is MAXAGE"); + return 0; + } + + /* (2) If the LSA was originated by the calculating router itself, + examine the next LSA. */ + if (IS_LSA_SELF(lsa)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: AS-external-LSA is self originated"); + return 0; + } + + /* (3) Call the destination described by the LSA N. N's address is + obtained by masking the LSA's Link State ID with the + network/subnet mask contained in the body of the LSA. Look + up the routing table entries (potentially one per attached + area) for the AS boundary router (ASBR) that originated the + LSA. If no entries exist for router ASBR (i.e., ASBR is + unreachable), do nothing with this LSA and consider the next + in the list. */ + + asbr.family = AF_INET; + asbr.prefix = al->header.adv_router; + asbr.prefixlen = IPV4_MAX_BITLEN; + apply_mask_ipv4(&asbr); + + asbr_route = ospf_find_asbr_route(ospf, ospf->new_rtrs, &asbr); + if (asbr_route == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Can't find originating ASBR route"); + return 0; + } + if (!(asbr_route->u.std.flags & ROUTER_LSA_EXTERNAL)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Originating router is not an ASBR"); + return 0; + } + + /* Type-5 shouldn't be calculated if it is originated from NSSA ASBR. + * As per RFC 3101, expectation is to receive type-7 lsas from + * NSSA ASBR. Ignore calculation, if the current LSA is type-5 and + * originated ASBR's area is NSSA. + */ + if ((lsa->data->type == OSPF_AS_EXTERNAL_LSA) + && (asbr_route->u.std.external_routing != OSPF_AREA_DEFAULT)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Ignore, If type-5 LSA from NSSA area."); + return 0; + } + + /* Else, this LSA describes an AS external path to destination + N. Examine the forwarding address specified in the AS- + external-LSA. This indicates the IP address to which + packets for the destination should be forwarded. */ + + if (al->e[0].fwd_addr.s_addr == INADDR_ANY) { + /* If the forwarding address is set to 0.0.0.0, packets should + be sent to the ASBR itself. Among the multiple routing table + entries for the ASBR, select the preferred entry as follows. + If RFC1583Compatibility is set to "disabled", prune the set + of routing table entries for the ASBR as described in + Section 16.4.1. In any case, among the remaining routing + table entries, select the routing table entry with the least + cost; when there are multiple least cost routing table + entries the entry whose associated area has the largest OSPF + Area ID (when considered as an unsigned 32-bit integer) is + chosen. */ + + /* asbr_route already contains the requested route */ + } else { + /* If the forwarding address is non-zero, look up the + forwarding address in the routing table.[24] The matching + routing table entry must specify an intra-area or inter-area + path; if no such path exists, do nothing with the LSA and + consider the next in the list. */ + if (!ospf_ase_forward_address_check(ospf, al->e[0].fwd_addr)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Forwarding address is our router address"); + return 0; + } + + asbr.family = AF_INET; + asbr.prefix = al->e[0].fwd_addr; + asbr.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_match(ospf->new_table, (struct prefix *)&asbr); + + if (rn == NULL || (asbr_route = rn->info) == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Can't find route to forwarding address"); + if (rn) + route_unlock_node(rn); + return 0; + } + + route_unlock_node(rn); + } + + /* (4) Let X be the cost specified by the preferred routing table + entry for the ASBR/forwarding address, and Y the cost + specified in the LSA. X is in terms of the link state + metric, and Y is a type 1 or 2 external metric. */ + + + /* (5) Look up the routing table entry for the destination N. If + no entry exists for N, install the AS external path to N, + with next hop equal to the list of next hops to the + forwarding address, and advertising router equal to ASBR. + If the external metric type is 1, then the path-type is set + to type 1 external and the cost is equal to X+Y. If the + external metric type is 2, the path-type is set to type 2 + external, the link state component of the route's cost is X, + and the type 2 cost is Y. */ + new = ospf_ase_calculate_new_route(lsa, asbr_route, metric); + + /* (6) Compare the AS external path described by the LSA with the + existing paths in N's routing table entry, as follows. If + the new path is preferred, it replaces the present paths in + N's routing table entry. If the new path is of equal + preference, it is added to N's routing table entry's list of + paths. */ + + /* Set prefix. */ + p.family = AF_INET; + p.prefix = al->header.id; + p.prefixlen = ip_masklen(al->mask); + + /* if there is a Intra/Inter area route to the N + do not install external route */ + if ((rn = route_node_lookup(ospf->new_table, (struct prefix *)&p))) { + route_unlock_node(rn); + if (rn->info == NULL) + zlog_info("Route[External]: rn->info NULL"); + if (new) + ospf_route_free(new); + return 0; + } + /* Find a route to the same dest */ + /* If there is no route, create new one. */ + if ((rn = route_node_lookup(ospf->new_external_route, + (struct prefix *)&p))) + route_unlock_node(rn); + + if (!rn || (or = rn->info) == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("Route[External]: Adding a new route %pFX with paths %u", + &p, listcount(asbr_route->paths)); + + ospf_route_add(ospf->new_external_route, &p, new, asbr_route); + + if (al->e[0].fwd_addr.s_addr != INADDR_ANY) + ospf_ase_complete_direct_routes(new, al->e[0].fwd_addr); + return 0; + } else { + /* (a) Intra-area and inter-area paths are always preferred + over AS external paths. + + (b) Type 1 external paths are always preferred over type 2 + external paths. When all paths are type 2 external + paths, the paths with the smallest advertised type 2 + metric are always preferred. */ + ret = ospf_route_cmp(ospf, new, or); + + /* (c) If the new AS external path is still + indistinguishable + from the current paths in the N's routing table + entry, + and RFC1583Compatibility is set to "disabled", select + the preferred paths based on the intra-AS paths to + the + ASBR/forwarding addresses, as specified in Section + 16.4.1. + + (d) If the new AS external path is still + indistinguishable + from the current paths in the N's routing table + entry, + select the preferred path based on a least cost + comparison. Type 1 external paths are compared by + looking at the sum of the distance to the forwarding + address and the advertised type 1 metric (X+Y). Type + 2 + external paths advertising equal type 2 metrics are + compared by looking at the distance to the forwarding + addresses. + */ + /* New route is better */ + if (ret < 0) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: New route is better"); + ospf_route_subst(rn, new, asbr_route); + if (al->e[0].fwd_addr.s_addr != INADDR_ANY) + ospf_ase_complete_direct_routes( + new, al->e[0].fwd_addr); + or = new; + new = NULL; + } + /* Old route is better */ + else if (ret > 0) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Route[External]: Old route is better"); + /* do nothing */ + } + /* Routes are equal */ + else { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("Route[External]: Routes are equal"); + ospf_route_copy_nexthops(or, asbr_route->paths); + if (al->e[0].fwd_addr.s_addr != INADDR_ANY) + ospf_ase_complete_direct_routes( + or, al->e[0].fwd_addr); + } + } + /* Make sure setting newly calculated ASBR route.*/ + or->u.ext.asbr = asbr_route; + if (new) + ospf_route_free(new); + + lsa->route = or ; + return 0; +} + +static int ospf_ase_route_match_same(struct route_table *rt, + struct prefix *prefix, + struct ospf_route *newor) +{ + struct route_node *rn; + struct ospf_route *or; + struct ospf_path *op; + struct ospf_path *newop; + struct listnode *n1; + struct listnode *n2; + + if (!rt || !prefix) + return 0; + + rn = route_node_lookup(rt, prefix); + if (!rn) + return 0; + + route_unlock_node(rn); + + or = rn->info; + + assert(or); + + if (or->changed || (or->path_type != newor->path_type)) + return 0; + + switch (or->path_type) { + case OSPF_PATH_TYPE1_EXTERNAL: + if (or->cost != newor->cost) + return 0; + break; + case OSPF_PATH_TYPE2_EXTERNAL: + if ((or->cost != newor->cost) + || (or->u.ext.type2_cost != newor->u.ext.type2_cost)) + return 0; + break; + default: + assert(0); + return 0; + } + + assert(or->paths); + + if (or->paths->count != newor->paths->count) + return 0; + + /* Check each path. */ + for (n1 = listhead(or->paths), n2 = listhead(newor->paths); n1 && n2; + n1 = listnextnode_unchecked(n1), n2 = listnextnode_unchecked(n2)) { + op = listgetdata(n1); + newop = listgetdata(n2); + + if (!IPV4_ADDR_SAME(&op->nexthop, &newop->nexthop)) + return 0; + if (op->ifindex != newop->ifindex) + return 0; + } + + if (or->u.ext.tag != newor->u.ext.tag) + return 0; + + return 1; +} + +static int ospf_ase_compare_tables(struct ospf *ospf, + struct route_table *new_external_route, + struct route_table *old_external_route) +{ + struct route_node *rn, *new_rn; + struct ospf_route * or ; + + /* Remove deleted routes */ + for (rn = route_top(old_external_route); rn; rn = route_next(rn)) + if ((or = rn->info)) { + if (!(new_rn = route_node_lookup(new_external_route, + &rn->p))) + ospf_zebra_delete( + ospf, (struct prefix_ipv4 *)&rn->p, or); + else + route_unlock_node(new_rn); + } + + + /* Install new routes */ + for (rn = route_top(new_external_route); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) + if (!ospf_ase_route_match_same(old_external_route, + &rn->p, or)) + ospf_zebra_add( + ospf, (struct prefix_ipv4 *)&rn->p, or); + + return 0; +} + +static void ospf_ase_calculate_timer(struct event *t) +{ + struct ospf *ospf; + struct ospf_lsa *lsa; + struct route_node *rn; + struct listnode *node; + struct ospf_area *area; + struct timeval start_time, stop_time; + + ospf = EVENT_ARG(t); + ospf->t_ase_calc = NULL; + + if (ospf->ase_calc) { + ospf->ase_calc = 0; + + monotime(&start_time); + + /* Calculate external route for each AS-external-LSA */ + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + ospf_ase_calculate_route(ospf, lsa); + + /* This version simple adds to the table all NSSA areas */ + if (ospf->anyNSSA) + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: looking at area %pI4", + __func__, &area->area_id); + + if (area->external_routing == OSPF_AREA_NSSA) + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + ospf_ase_calculate_route(ospf, + lsa); + } + /* kevinm: And add the NSSA routes in ospf_top */ + LSDB_LOOP (NSSA_LSDB(ospf), rn, lsa) + ospf_ase_calculate_route(ospf, lsa); + + /* Compare old and new external routing table and install the + difference info zebra/kernel */ + ospf_ase_compare_tables(ospf, ospf->new_external_route, + ospf->old_external_route); + + /* Delete old external routing table */ + ospf_route_table_free(ospf->old_external_route); + ospf->old_external_route = ospf->new_external_route; + ospf->new_external_route = route_table_init(); + + monotime(&stop_time); + + if (IS_DEBUG_OSPF_EVENT) + zlog_info( + "SPF Processing Time(usecs): External Routes: %lld", + (stop_time.tv_sec - start_time.tv_sec) + * 1000000LL + + (stop_time.tv_usec + - start_time.tv_usec)); + } + + /* + * Uninstall remnant routes that were installed before the restart, but + * that are no longer valid. + */ + if (ospf->gr_info.finishing_restart) { + ospf_zebra_gr_disable(ospf); + ospf_zebra_gr_enable(ospf, ospf->gr_info.grace_period); + ospf->gr_info.finishing_restart = false; + } +} + +void ospf_ase_calculate_schedule(struct ospf *ospf) +{ + if (ospf == NULL) + return; + + ospf->ase_calc = 1; +} + +void ospf_ase_calculate_timer_add(struct ospf *ospf) +{ + if (ospf == NULL) + return; + + event_add_timer(master, ospf_ase_calculate_timer, ospf, + OSPF_ASE_CALC_INTERVAL, &ospf->t_ase_calc); +} + +void ospf_ase_register_external_lsa(struct ospf_lsa *lsa, struct ospf *top) +{ + struct route_node *rn; + struct prefix_ipv4 p; + struct list *lst; + struct as_external_lsa *al; + + al = (struct as_external_lsa *)lsa->data; + p.family = AF_INET; + p.prefix = lsa->data->id; + p.prefixlen = ip_masklen(al->mask); + apply_mask_ipv4(&p); + + rn = route_node_get(top->external_lsas, (struct prefix *)&p); + if ((lst = rn->info) == NULL) + rn->info = lst = list_new(); + else + route_unlock_node(rn); + + /* We assume that if LSA is deleted from DB + is is also deleted from this RT */ + listnode_add(lst, ospf_lsa_lock(lsa)); /* external_lsas lst */ +} + +void ospf_ase_unregister_external_lsa(struct ospf_lsa *lsa, struct ospf *top) +{ + struct route_node *rn; + struct prefix_ipv4 p; + struct list *lst; + struct as_external_lsa *al; + + al = (struct as_external_lsa *)lsa->data; + p.family = AF_INET; + p.prefix = lsa->data->id; + p.prefixlen = ip_masklen(al->mask); + apply_mask_ipv4(&p); + + rn = route_node_lookup(top->external_lsas, (struct prefix *)&p); + + if (rn) { + lst = rn->info; + struct listnode *node = listnode_lookup(lst, lsa); + /* Unlock lsa only if node is present in the list */ + if (node) { + listnode_delete(lst, lsa); + ospf_lsa_unlock(&lsa); /* external_lsas list */ + } + + route_unlock_node(rn); + } +} + +void ospf_ase_external_lsas_finish(struct route_table *rt) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + struct list *lst; + struct listnode *node, *nnode; + + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((lst = rn->info) != NULL) { + for (ALL_LIST_ELEMENTS(lst, node, nnode, lsa)) + ospf_lsa_unlock(&lsa); /* external_lsas lst */ + list_delete(&lst); + } + + route_table_finish(rt); +} + +void ospf_ase_incremental_update(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct list *lsas; + struct listnode *node; + struct route_node *rn, *rn2; + struct prefix_ipv4 p; + struct route_table *tmp_old; + struct as_external_lsa *al; + + al = (struct as_external_lsa *)lsa->data; + p.family = AF_INET; + p.prefix = lsa->data->id; + p.prefixlen = ip_masklen(al->mask); + apply_mask_ipv4(&p); + + /* if new_table is NULL, there was no spf calculation, thus + incremental update is unneeded */ + if (!ospf->new_table) + return; + + /* If there is already an intra-area or inter-area route + to the destination, no recalculation is necessary + (internal routes take precedence). */ + + rn = route_node_lookup(ospf->new_table, (struct prefix *)&p); + if (rn) { + route_unlock_node(rn); + if (rn->info) + return; + } + + rn = route_node_lookup(ospf->external_lsas, (struct prefix *)&p); + assert(rn); + assert(rn->info); + lsas = rn->info; + route_unlock_node(rn); + + for (ALL_LIST_ELEMENTS_RO(lsas, node, lsa)) + ospf_ase_calculate_route(ospf, lsa); + + /* prepare temporary old routing table for compare */ + tmp_old = route_table_init(); + rn = route_node_lookup(ospf->old_external_route, (struct prefix *)&p); + if (rn && rn->info) { + rn2 = route_node_get(tmp_old, (struct prefix *)&p); + rn2->info = rn->info; + route_unlock_node(rn); + } + + /* install changes to zebra */ + ospf_ase_compare_tables(ospf, ospf->new_external_route, tmp_old); + + /* update ospf->old_external_route table */ + if (rn && rn->info) + ospf_route_free((struct ospf_route *)rn->info); + + rn2 = route_node_lookup(ospf->new_external_route, (struct prefix *)&p); + /* if new route exists, install it to ospf->old_external_route */ + if (rn2 && rn2->info) { + if (!rn) + rn = route_node_get(ospf->old_external_route, + (struct prefix *)&p); + rn->info = rn2->info; + } else { + /* remove route node from ospf->old_external_route */ + if (rn) { + rn->info = NULL; + route_unlock_node(rn); + } + } + + if (rn2) { + /* rn2->info is stored in route node of ospf->old_external_route + */ + rn2->info = NULL; + route_unlock_node(rn2); + route_unlock_node(rn2); + } + + route_table_finish(tmp_old); +} diff --git a/ospfd/ospf_ase.h b/ospfd/ospf_ase.h new file mode 100644 index 0000000..d59a470 --- /dev/null +++ b/ospfd/ospf_ase.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF AS External route calculation. + * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_ASE_H +#define _ZEBRA_OSPF_ASE_H + +extern struct ospf_route * +ospf_find_asbr_route(struct ospf *, struct route_table *, struct prefix_ipv4 *); +extern struct ospf_route * +ospf_find_asbr_route_through_area(struct route_table *, struct prefix_ipv4 *, + struct ospf_area *); + +extern int ospf_ase_calculate_route(struct ospf *, struct ospf_lsa *); +extern void ospf_ase_calculate_schedule(struct ospf *); +extern void ospf_ase_calculate_timer_add(struct ospf *); + +extern void ospf_ase_external_lsas_finish(struct route_table *); +extern void ospf_ase_incremental_update(struct ospf *, struct ospf_lsa *); +extern void ospf_ase_register_external_lsa(struct ospf_lsa *, struct ospf *); +extern void ospf_ase_unregister_external_lsa(struct ospf_lsa *, struct ospf *); + +#endif /* _ZEBRA_OSPF_ASE_H */ diff --git a/ospfd/ospf_auth.c b/ospfd/ospf_auth.c new file mode 100644 index 0000000..2b090dc --- /dev/null +++ b/ospfd/ospf_auth.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Amnesh Inc. + * Mahdi Varasteh + */ + +#include +#include + +#ifdef CRYPTO_OPENSSL +#include +#include +#endif + +#include "linklist.h" +#include "if.h" +#include "checksum.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_auth.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" +#ifdef CRYPTO_INTERNAL +#include "sha256.h" +#include "md5.h" +#endif + +const uint8_t ospf_auth_apad[KEYCHAIN_MAX_HASH_SIZE] = { + 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, + 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, + 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, + 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, + 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, + 0xF3, 0x87, 0x8F, 0xE1, 0xF3, 0x87, 0x8F, 0xE1, 0xF3 +}; + +static int ospf_check_sum(struct ospf_header *ospfh) +{ + uint32_t ret; + uint16_t sum; + + /* clear auth_data for checksum. */ + memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); + + /* keep checksum and clear. */ + sum = ospfh->checksum; + memset(&ospfh->checksum, 0, sizeof(uint16_t)); + + /* calculate checksum. */ + ret = in_cksum(ospfh, ntohs(ospfh->length)); + + if (ret != sum) { + zlog_info("%s: checksum mismatch, my %X, his %X", __func__, ret, + sum); + return 0; + } + + return 1; +} + +#ifdef CRYPTO_OPENSSL +static const EVP_MD *ospf_auth_get_openssl_evp_md_from_key(struct key *key) +{ + if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA1) + return EVP_get_digestbyname("sha1"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA256) + return EVP_get_digestbyname("sha256"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA384) + return EVP_get_digestbyname("sha384"); + else if (key->hash_algo == KEYCHAIN_ALGO_HMAC_SHA512) + return EVP_get_digestbyname("sha512"); + return NULL; +} +#endif + +static int ospf_auth_check_hmac_sha_digest(struct ospf_interface *oi, + struct ospf_header *ospfh, + struct ip *iph, + struct key *key) +{ + unsigned char digest[KEYCHAIN_MAX_HASH_SIZE]; + struct ospf_neighbor *nbr; + uint16_t length = ntohs(ospfh->length); + uint16_t hash_length = keychain_get_hash_len(key->hash_algo); +#ifdef CRYPTO_OPENSSL + uint32_t openssl_hash_length = hash_length; + HMAC_CTX *ctx; + const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key); + + if (!md_alg) { + flog_warn(EC_OSPF_AUTH, + "interface %s: invalid HMAC algorithm, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#elif CRYPTO_INTERNAL + HMAC_SHA256_CTX ctx; + + if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) { + flog_warn(EC_OSPF_AUTH, + "interface %s: HMAC algorithm not supported, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#endif + /* check crypto seqnum. */ + nbr = ospf_nbr_lookup(oi, iph, ospfh); + + if (nbr && + ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_hmac_sha bad sequence %u (expect %d), Router-ID: %pI4", + IF_NAME(oi), ntohl(ospfh->u.crypt.crypt_seqnum), + ntohl(nbr->crypt_seqnum), &ospfh->router_id); + return 0; + } +#ifdef CRYPTO_OPENSSL + ctx = HMAC_CTX_new(); + HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL); + HMAC_Update(ctx, (const unsigned char *)ospfh, length); + HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length); + HMAC_Final(ctx, digest, &openssl_hash_length); + HMAC_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + HMAC__SHA256_Init(&ctx, key->string, strlen(key->string)); + HMAC__SHA256_Update(&ctx, ospfh, length); + HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length); + HMAC__SHA256_Final(digest, &ctx); +#endif + if (memcmp((caddr_t)ospfh + length, digest, hash_length)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_hmac_sha checksum mismatch %u, Router-ID: %pI4", + IF_NAME(oi), length, &ospfh->router_id); + return 0; + } + if (nbr) + nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; + return 1; +} + +static int ospf_auth_check_md5_digest(struct ospf_interface *oi, + struct ospf_header *ospfh, struct ip *iph, struct key *key) +{ +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + char auth_key[OSPF_AUTH_MD5_SIZE + 1]; + unsigned char digest[OSPF_AUTH_MD5_SIZE]; + struct ospf_neighbor *nbr; + struct crypt_key *ck = NULL; + uint16_t length = ntohs(ospfh->length); + + if (length < sizeof(struct ospf_header)) {/* for coverity's sake */ + flog_warn(EC_OSPF_AUTH, + "%s: Invalid packet length of %u received on interface %s, Router-ID: %pI4", + __func__, length, IF_NAME(oi), &ospfh->router_id); + return 0; + } + + if (key == NULL) { + ck = ospf_crypt_key_lookup(OSPF_IF_PARAM(oi, auth_crypt), + ospfh->u.crypt.key_id); + if (ck == NULL) { + flog_warn( + EC_OSPF_AUTH, + "interface %s: %s no key %d, Router-ID: %pI4", + IF_NAME(oi), __func__, ospfh->u.crypt.key_id, &ospfh->router_id); + return 0; + } + } + /* check crypto seqnum. */ + nbr = ospf_nbr_lookup(oi, iph, ospfh); + + if (nbr && + ntohl(nbr->crypt_seqnum) > ntohl(ospfh->u.crypt.crypt_seqnum)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: %s bad sequence %d (expect %d), Router-ID: %pI4", + IF_NAME(oi), __func__, ntohl(ospfh->u.crypt.crypt_seqnum), + ntohl(nbr->crypt_seqnum), &ospfh->router_id); + return 0; + } + + memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); + if (ck == NULL) + strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1); + else + strlcpy(auth_key, (char *)ck->auth_key, OSPF_AUTH_MD5_SIZE + 1); + /* Generate a digest for the ospf packet - their digest + our digest. */ +#ifdef CRYPTO_OPENSSL + uint32_t md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ospfh, length); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ospfh, length); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + /* compare the two */ + if (memcmp((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) { + flog_warn(EC_OSPF_AUTH, + "interface %s: ospf_check_md5 checksum mismatch, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + + /* save neighbor's crypt_seqnum */ + if (nbr) + nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; + return 1; +} + +static int ospf_auth_make_md5_digest(struct ospf_interface *oi, + struct ospf_packet *op, struct key *key) +{ + void *ibuf = STREAM_DATA(op->s); + struct ospf_header *ospfh = (struct ospf_header *)ibuf; + unsigned char digest[OSPF_AUTH_MD5_SIZE]; + uint16_t length = ntohs(ospfh->length); +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + char auth_key[OSPF_AUTH_MD5_SIZE + 1]; + + if ((length < (sizeof(struct ospf_header))) || (length > op->length)) { /* for coverity's sake */ + flog_warn(EC_OSPF_AUTH, + "%s: Invalid packet length of %u received on interface %s, Router-ID: %pI4", + __func__, length, IF_NAME(oi), &ospfh->router_id); + return 0; + } + + memset(auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); + strlcpy(auth_key, key->string, OSPF_AUTH_MD5_SIZE + 1); + /* Generate a digest for the ospf packet - their digest + our digest. */ +#ifdef CRYPTO_OPENSSL + uint32_t md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ospfh, length); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ospfh, length); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); + + op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn(EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return OSPF_AUTH_MD5_SIZE; +} + +static int ospf_auth_make_hmac_sha_digest(struct ospf_interface *oi, + struct ospf_packet *op, + struct key *key) +{ + void *ibuf; + struct ospf_header *ospfh; + unsigned char digest[KEYCHAIN_MAX_HASH_SIZE] = { 0 }; + uint16_t hash_length = keychain_get_hash_len(key->hash_algo); + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; +#ifdef CRYPTO_OPENSSL + uint32_t openssl_hash_length = hash_length; + HMAC_CTX *ctx; + const EVP_MD *md_alg = ospf_auth_get_openssl_evp_md_from_key(key); + + if (!md_alg) { + flog_warn(EC_OSPF_AUTH, + "interface %s: invalid HMAC algorithm, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#elif CRYPTO_INTERNAL + HMAC_SHA256_CTX ctx; + + if (key->hash_algo != KEYCHAIN_ALGO_HMAC_SHA256) { + flog_warn(EC_OSPF_AUTH, + "interface %s: HMAC algorithm not supported, Router-ID: %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } +#endif +#ifdef CRYPTO_OPENSSL + ctx = HMAC_CTX_new(); + HMAC_Init_ex(ctx, key->string, strlen(key->string), md_alg, NULL); + HMAC_Update(ctx, (const unsigned char *)ospfh, ntohs(ospfh->length)); + HMAC_Update(ctx, (const unsigned char *)ospf_auth_apad, hash_length); + HMAC_Final(ctx, digest, &openssl_hash_length); + HMAC_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + HMAC__SHA256_Init(&ctx, key->string, strlen(key->string)); + HMAC__SHA256_Update(&ctx, ospfh, ntohs(ospfh->length)); + HMAC__SHA256_Update(&ctx, ospf_auth_apad, hash_length); + HMAC__SHA256_Final(digest, &ctx); +#endif + stream_put(op->s, digest, hash_length); + + op->length = ntohs(ospfh->length) + hash_length; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn(EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return hash_length; +} + +int ospf_auth_check_digest(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh) +{ + struct keychain *keychain = NULL; + struct key *key = NULL; + int key_id = ospfh->u.crypt.key_id; + uint8_t auth_data_len = ospfh->u.crypt.auth_data_len; + + if (!OSPF_IF_PARAM(oi, keychain_name)) + return ospf_auth_check_md5_digest(oi, ospfh, iph, NULL); + + keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + if (!keychain) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain %s is not available, Router-ID %pI4", + IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name), + &ospfh->router_id); + return 0; + } + + key = key_lookup_for_accept(keychain, key_id); + if (!key) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (keychain_get_hash_len(key->hash_algo) != auth_data_len) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s hash length mismatch, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + /* Backward compatibility with RFC 2328 keyed-MD5 authentication */ + if (key->hash_algo == KEYCHAIN_ALGO_MD5) + return ospf_auth_check_md5_digest(oi, ospfh, iph, key); + + return ospf_auth_check_hmac_sha_digest(oi, ospfh, iph, key); +} + +int ospf_auth_make_digest(struct ospf_interface *oi, struct ospf_packet *op) +{ + struct ospf_header *ospfh; + void *ibuf; + struct keychain *keychain = NULL; + struct key *key = NULL; + int key_id; + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; + + key_id = ospfh->u.crypt.key_id; + + if (!OSPF_IF_PARAM(oi, keychain_name)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain is not set, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + + keychain = oi->keychain; + if (!keychain) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Keychain %s is not available to send, Router-ID %pI4", + IF_NAME(oi), OSPF_IF_PARAM(oi, keychain_name), + &ospfh->router_id); + return 0; + } + + key = oi->key; + if (!key) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d not found in keychain %s, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + if (key->string == NULL || key->hash_algo == KEYCHAIN_ALGO_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, SEND)) + flog_warn(EC_OSPF_AUTH, + "interface %s: Key ID %d in keychain %s is incomplete, Router-ID %pI4", + IF_NAME(oi), key_id, keychain->name, + &ospfh->router_id); + return 0; + } + + /* Backward compatibility with RFC 2328 keyed-MD5 authentication */ + if (key->hash_algo == KEYCHAIN_ALGO_MD5) + return ospf_auth_make_md5_digest(oi, op, key); + else + return ospf_auth_make_hmac_sha_digest(oi, op, key); +} + +/* This function is called from ospf_write(), it will detect the + * authentication scheme and if it is MD5, it will change the sequence + * and update the MD5 digest. + */ + +int ospf_auth_make(struct ospf_interface *oi, struct ospf_packet *op) +{ + struct ospf_header *ospfh; + unsigned char digest[OSPF_AUTH_MD5_SIZE] = {0}; +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + void *ibuf; + uint32_t t; + struct crypt_key *ck; + const uint8_t *auth_key = NULL; + + ibuf = STREAM_DATA(op->s); + ospfh = (struct ospf_header *)ibuf; + + if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) + return 0; + + /* We do this here so when we dup a packet, we don't have to + * waste CPU rewriting other headers. + + Note that frr_time /deliberately/ is not used here. + */ + t = (time(NULL) & 0xFFFFFFFF); + if (t > oi->crypt_seqnum) + oi->crypt_seqnum = t; + else + oi->crypt_seqnum++; + + ospfh->u.crypt.crypt_seqnum = htonl(oi->crypt_seqnum); + + /* Get MD5 Authentication key from auth_key list. */ + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt)) && OSPF_IF_PARAM(oi, keychain_name) == NULL) + auth_key = (const uint8_t *)digest; + else if (!list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { + ck = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); + auth_key = ck->auth_key; + } + + if (auth_key) { + /* Generate a digest for the entire packet + our secret key. */ +#ifdef CRYPTO_OPENSSL + uint32_t md5_size = OSPF_AUTH_MD5_SIZE; + + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, ibuf, ntohs(ospfh->length)); + EVP_DigestUpdate(ctx, auth_key, OSPF_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, ibuf, ntohs(ospfh->length)); + MD5Update(&ctx, auth_key, OSPF_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + /* Append md5 digest to the end of the stream. */ + stream_put(op->s, digest, OSPF_AUTH_MD5_SIZE); + + /* We do *NOT* increment the OSPF header length. */ + op->length = ntohs(ospfh->length) + OSPF_AUTH_MD5_SIZE; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + flog_warn( + EC_OSPF_AUTH, + "%s: length mismatch stream %lu ospf_packet %u, Router-ID %pI4", + __func__, (unsigned long)stream_get_endp(op->s), + op->length, &ospfh->router_id); + + return OSPF_AUTH_MD5_SIZE; + } else + return ospf_auth_make_digest(oi, op); +} + +/* Return 1, if the packet is properly authenticated and checksummed, + * 0 otherwise. In particular, check that AuType header field is valid and + * matches the locally configured AuType, and that D.5 requirements are met. + */ +int ospf_auth_check(struct ospf_interface *oi, struct ip *iph, + struct ospf_header *ospfh) +{ + uint16_t iface_auth_type; + uint16_t pkt_auth_type = ntohs(ospfh->auth_type); + + iface_auth_type = ospf_auth_type(oi); + + switch (pkt_auth_type) { + case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */ + if (iface_auth_type != OSPF_AUTH_NULL) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Null, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (!ospf_check_sum(ospfh)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Null auth OK, but checksum error, Router-ID %pI4", + IF_NAME(oi), + &ospfh->router_id); + return 0; + } + return 1; + case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */ + if (iface_auth_type != OSPF_AUTH_SIMPLE) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Simple, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (memcmp(OSPF_IF_PARAM(oi, auth_simple), ospfh->u.auth_data, + OSPF_AUTH_SIMPLE_SIZE)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Simple auth failed, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + if (!ospf_check_sum(ospfh)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: Simple auth OK, checksum error, Router-ID %pI4", + IF_NAME(oi), + &ospfh->router_id); + return 0; + } + return 1; + case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */ + if (iface_auth_type != OSPF_AUTH_CRYPTOGRAPHIC) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: auth-type mismatch, local %s, rcvd Cryptographic, Router-ID %pI4", + IF_NAME(oi), + lookup_msg(ospf_auth_type_str, + iface_auth_type, NULL), + &ospfh->router_id); + return 0; + } + if (ospfh->checksum) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: OSPF header checksum is not 0, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + } + /* If `authentication message-digest` key is not set, we try keychain crypto */ + if (OSPF_IF_PARAM(oi, keychain_name) || !list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) + return ospf_auth_check_digest(oi, iph, ospfh); + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_AUTH, + "interface %s: MD5 auth failed, Router-ID %pI4", + IF_NAME(oi), &ospfh->router_id); + return 0; + default: + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) + flog_warn( + EC_OSPF_PACKET, + "interface %s: invalid packet auth-type (%02x), Router-ID %pI4", + IF_NAME(oi), pkt_auth_type, &ospfh->router_id); + return 0; + } +} + +/* OSPF authentication checking function */ +int ospf_auth_type(struct ospf_interface *oi) +{ + int auth_type; + + if (OSPF_IF_PARAM(oi, auth_type) == OSPF_AUTH_NOTSET) + auth_type = oi->area->auth_type; + else + auth_type = OSPF_IF_PARAM(oi, auth_type); + + /* Handle case where MD5 key list, or a key-chain, is not configured aka Cisco */ + if (auth_type == OSPF_AUTH_CRYPTOGRAPHIC + && (list_isempty(OSPF_IF_PARAM(oi, auth_crypt)) + && OSPF_IF_PARAM(oi, keychain_name) == NULL)) + return OSPF_AUTH_NULL; + + return auth_type; +} + +/* Make Authentication Data. */ +int ospf_auth_make_data(struct ospf_interface *oi, struct ospf_header *ospfh) +{ + struct crypt_key *ck; + + switch (ospf_auth_type(oi)) { + case OSPF_AUTH_NULL: + /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); + */ + break; + case OSPF_AUTH_SIMPLE: + memcpy(ospfh->u.auth_data, OSPF_IF_PARAM(oi, auth_simple), + OSPF_AUTH_SIMPLE_SIZE); + break; + case OSPF_AUTH_CRYPTOGRAPHIC: + if (OSPF_IF_PARAM(oi, keychain_name)) { + oi->keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + if (oi->keychain) + oi->key = key_lookup_for_send(oi->keychain); + if (oi->key) { + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = oi->key->index; + ospfh->u.crypt.auth_data_len = keychain_get_hash_len(oi->key->hash_algo); + } else { + /* If key is not set, then set 0. */ + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = 0; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } + } else { + /* If key is not set, then set 0. */ + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) { + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = 0; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } else { + ck = listgetdata( + listtail(OSPF_IF_PARAM(oi, auth_crypt))); + ospfh->u.crypt.zero = 0; + ospfh->u.crypt.key_id = ck->key_id; + ospfh->u.crypt.auth_data_len = OSPF_AUTH_MD5_SIZE; + } + } + /* note: the seq is done in ospf_auth_make() */ + break; + default: + /* memset (ospfh->u.auth_data, 0, sizeof(ospfh->u.auth_data)); + */ + break; + } + + return 0; +} diff --git a/ospfd/ospf_auth.h b/ospfd/ospf_auth.h new file mode 100644 index 0000000..6f6d3db --- /dev/null +++ b/ospfd/ospf_auth.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Amnesh Inc. + * Mahdi Varasteh + */ + +#ifndef _ZEBRA_OSPF_AUTH_H +#define _ZEBRA_OSPF_AUTH_H + +#include +#include + +int ospf_auth_check(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh); +int ospf_auth_check_digest(struct ospf_interface *oi, struct ip *iph, struct ospf_header *ospfh); +int ospf_auth_make(struct ospf_interface *oi, struct ospf_packet *op); +int ospf_auth_make_digest(struct ospf_interface *oi, struct ospf_packet *op); +int ospf_auth_type(struct ospf_interface *oi); +int ospf_auth_make_data(struct ospf_interface *oi, struct ospf_header *ospfh); + +#endif /* _ZEBRA_OSPF_AUTH_H */ diff --git a/ospfd/ospf_bfd.c b/ospfd/ospf_bfd.c new file mode 100644 index 0000000..7d4c7c0 --- /dev/null +++ b/ospfd/ospf_bfd.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * ospf_bfd.c: OSPF BFD handling routines + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#include + +#include "command.h" +#include "json.h" +#include "linklist.h" +#include "memory.h" +#include "prefix.h" +#include "frrevent.h" +#include "buffer.h" +#include "stream.h" +#include "zclient.h" +#include "vty.h" +#include "table.h" +#include "bfd.h" +#include "ospfd.h" +#include "ospf_asbr.h" +#include "ospf_lsa.h" +#include "ospf_lsdb.h" +#include "ospf_neighbor.h" +#include "ospf_interface.h" +#include "ospf_nsm.h" +#include "ospf_bfd.h" +#include "ospf_dump.h" +#include "ospf_vty.h" + +DEFINE_MTYPE_STATIC(OSPFD, BFD_CONFIG, "BFD configuration data"); + +/* + * ospf_bfd_trigger_event - Neighbor is registered/deregistered with BFD when + * neighbor state is changed to/from 2way. + */ +void ospf_bfd_trigger_event(struct ospf_neighbor *nbr, int old_state, int state) +{ + if ((old_state < NSM_TwoWay) && (state >= NSM_TwoWay)) + bfd_sess_install(nbr->bfd_session); + else if ((old_state >= NSM_TwoWay) && (state < NSM_TwoWay)) + bfd_sess_uninstall(nbr->bfd_session); +} + +static void ospf_bfd_session_change(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, + void *arg) +{ + struct ospf_neighbor *nbr = arg; + + /* BFD peer went down. */ + if (bss->state == BFD_STATUS_DOWN + && bss->previous_state == BFD_STATUS_UP) { + if (IS_DEBUG_OSPF(bfd, BFD_LIB)) + zlog_debug("%s: NSM[%s:%pI4]: BFD Down", __func__, + IF_NAME(nbr->oi), &nbr->address.u.prefix4); + + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_InactivityTimer); + } + + /* BFD peer went up. */ + if (bss->state == BSS_UP && bss->previous_state == BSS_DOWN) + if (IS_DEBUG_OSPF(bfd, BFD_LIB)) + zlog_debug("%s: NSM[%s:%pI4]: BFD Up", __func__, + IF_NAME(nbr->oi), &nbr->address.u.prefix4); +} + +void ospf_neighbor_bfd_apply(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi = nbr->oi; + struct ospf_if_params *oip = IF_DEF_PARAMS(oi->ifp); + + /* BFD configuration was removed. */ + if (oip->bfd_config == NULL) { + bfd_sess_free(&nbr->bfd_session); + return; + } + + /* New BFD session. */ + if (nbr->bfd_session == NULL) { + nbr->bfd_session = bfd_sess_new(ospf_bfd_session_change, nbr); + bfd_sess_set_ipv4_addrs(nbr->bfd_session, NULL, &nbr->src); + bfd_sess_set_interface(nbr->bfd_session, oi->ifp->name); + bfd_sess_set_vrf(nbr->bfd_session, oi->ospf->vrf_id); + } + + /* Set new configuration. */ + bfd_sess_set_timers(nbr->bfd_session, + oip->bfd_config->detection_multiplier, + oip->bfd_config->min_rx, oip->bfd_config->min_tx); + bfd_sess_set_profile(nbr->bfd_session, oip->bfd_config->profile); + + /* Don't start sessions on down OSPF sessions. */ + if (nbr->state < NSM_TwoWay) + return; + + bfd_sess_install(nbr->bfd_session); +} + +static void ospf_interface_bfd_apply(struct interface *ifp) +{ + struct ospf_interface *oi; + struct route_table *nbrs; + struct ospf_neighbor *nbr; + struct route_node *irn; + struct route_node *nrn; + + /* Iterate over all interfaces and set neighbors BFD session. */ + for (irn = route_top(IF_OIFS(ifp)); irn; irn = route_next(irn)) { + if ((oi = irn->info) == NULL) + continue; + if ((nbrs = oi->nbrs) == NULL) + continue; + for (nrn = route_top(nbrs); nrn; nrn = route_next(nrn)) { + if ((nbr = nrn->info) == NULL || nbr == oi->nbr_self) + continue; + + ospf_neighbor_bfd_apply(nbr); + } + } +} + +static void ospf_interface_enable_bfd(struct interface *ifp) +{ + struct ospf_if_params *oip = IF_DEF_PARAMS(ifp); + + if (oip->bfd_config) + return; + + /* Allocate memory for configurations and set defaults. */ + oip->bfd_config = XCALLOC(MTYPE_BFD_CONFIG, sizeof(*oip->bfd_config)); + oip->bfd_config->detection_multiplier = BFD_DEF_DETECT_MULT; + oip->bfd_config->min_rx = BFD_DEF_MIN_RX; + oip->bfd_config->min_tx = BFD_DEF_MIN_TX; +} + +void ospf_interface_disable_bfd(struct interface *ifp, + struct ospf_if_params *oip) +{ + XFREE(MTYPE_BFD_CONFIG, oip->bfd_config); + ospf_interface_bfd_apply(ifp); +} + +/* + * ospf_bfd_write_config - Write the interface BFD configuration. + */ +void ospf_bfd_write_config(struct vty *vty, const struct ospf_if_params *params + __attribute__((unused))) +{ +#if HAVE_BFDD == 0 + if (params->bfd_config->detection_multiplier != BFD_DEF_DETECT_MULT + || params->bfd_config->min_rx != BFD_DEF_MIN_RX + || params->bfd_config->min_tx != BFD_DEF_MIN_TX) + vty_out(vty, " ip ospf bfd %d %d %d\n", + params->bfd_config->detection_multiplier, + params->bfd_config->min_rx, params->bfd_config->min_tx); + else +#endif /* ! HAVE_BFDD */ + vty_out(vty, " ip ospf bfd\n"); + + if (params->bfd_config->profile[0]) + vty_out(vty, " ip ospf bfd profile %s\n", + params->bfd_config->profile); +} + +void ospf_interface_bfd_show(struct vty *vty, const struct interface *ifp, + struct json_object *json) +{ + struct ospf_if_params *params = IF_DEF_PARAMS(ifp); + struct bfd_configuration *bfd_config = params->bfd_config; + struct json_object *json_bfd; + + if (bfd_config == NULL) + return; + + if (json) { + json_bfd = json_object_new_object(); + json_object_int_add(json_bfd, "detectionMultiplier", + bfd_config->detection_multiplier); + json_object_int_add(json_bfd, "rxMinInterval", + bfd_config->min_rx); + json_object_int_add(json_bfd, "txMinInterval", + bfd_config->min_tx); + json_object_object_add(json, "peerBfdInfo", json_bfd); + } else + vty_out(vty, + " BFD: Detect Multiplier: %d, Min Rx interval: %d, Min Tx interval: %d\n", + bfd_config->detection_multiplier, bfd_config->min_rx, + bfd_config->min_tx); +} + +DEFUN (ip_ospf_bfd, + ip_ospf_bfd_cmd, + "ip ospf bfd", + "IP Information\n" + "OSPF interface commands\n" + "Enables BFD support\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + ospf_interface_enable_bfd(ifp); + ospf_interface_bfd_apply(ifp); + return CMD_SUCCESS; +} + +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + ip_ospf_bfd_param, + ip_ospf_bfd_param_cmd, + "ip ospf bfd (2-255) (50-60000) (50-60000)", + "IP Information\n" + "OSPF interface commands\n" + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + + ospf_interface_enable_bfd(ifp); + + params = IF_DEF_PARAMS(ifp); + params->bfd_config->detection_multiplier = + strtol(argv[idx_number]->arg, NULL, 10); + params->bfd_config->min_rx = strtol(argv[idx_number_2]->arg, NULL, 10); + params->bfd_config->min_tx = strtol(argv[idx_number_3]->arg, NULL, 10); + + ospf_interface_bfd_apply(ifp); + + return CMD_SUCCESS; +} + +DEFUN (ip_ospf_bfd_prof, + ip_ospf_bfd_prof_cmd, + "ip ospf bfd profile BFDPROF", + "IP Information\n" + "OSPF interface commands\n" + "Enables BFD support\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + int idx_prof = 4; + + params = IF_DEF_PARAMS(ifp); + if (!params->bfd_config) { + vty_out(vty, "ip ospf bfd has not been set\n"); + return CMD_WARNING; + } + + strlcpy(params->bfd_config->profile, argv[idx_prof]->arg, + sizeof(params->bfd_config->profile)); + ospf_interface_bfd_apply(ifp); + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_bfd_prof, + no_ip_ospf_bfd_prof_cmd, + "no ip ospf bfd profile [BFDPROF]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Enables BFD support\n" + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + if (!params->bfd_config) + return CMD_SUCCESS; + + params->bfd_config->profile[0] = 0; + ospf_interface_bfd_apply(ifp); + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_bfd, + no_ip_ospf_bfd_cmd, +#if HAVE_BFDD > 0 + "no ip ospf bfd", +#else + "no ip ospf bfd [(2-255) (50-60000) (50-60000)]", +#endif /* HAVE_BFDD */ + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Disables BFD support\n" +#if HAVE_BFDD == 0 + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n" +#endif /* !HAVE_BFDD */ +) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + ospf_interface_disable_bfd(ifp, IF_DEF_PARAMS(ifp)); + return CMD_SUCCESS; +} + +void ospf_bfd_init(struct event_loop *tm) +{ + bfd_protocol_integration_init(zclient, tm); + + /* Install BFD command */ + install_element(INTERFACE_NODE, &ip_ospf_bfd_cmd); + install_element(INTERFACE_NODE, &ip_ospf_bfd_param_cmd); + install_element(INTERFACE_NODE, &ip_ospf_bfd_prof_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_bfd_prof_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_bfd_cmd); +} diff --git a/ospfd/ospf_bfd.h b/ospfd/ospf_bfd.h new file mode 100644 index 0000000..d454f9c --- /dev/null +++ b/ospfd/ospf_bfd.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * ospf_bfd.h: OSPF BFD definitions and structures + * + * @copyright Copyright (C) 2015 Cumulus Networks, Inc. + */ + +#ifndef _ZEBRA_OSPF_BFD_H +#define _ZEBRA_OSPF_BFD_H + +#include "ospfd/ospf_interface.h" +#include "json.h" + +extern void ospf_bfd_init(struct event_loop *tm); + +extern void ospf_bfd_write_config(struct vty *vty, + const struct ospf_if_params *params); + +extern void ospf_bfd_trigger_event(struct ospf_neighbor *nbr, int old_state, + int state); + +/** + * Legacy information: it is the peers who actually have this information + * and the protocol should not need to know about timers. + */ +extern void ospf_interface_bfd_show(struct vty *vty, + const struct interface *ifp, + struct json_object *json); + +/** + * Disables interface BFD configuration and remove settings from all peers. + */ +extern void ospf_interface_disable_bfd(struct interface *ifp, + struct ospf_if_params *oip); + +/** + * Create/update BFD session for this OSPF neighbor. + */ +extern void ospf_neighbor_bfd_apply(struct ospf_neighbor *nbr); + +#endif /* _ZEBRA_OSPF_BFD_H */ diff --git a/ospfd/ospf_dump.c b/ospfd/ospf_dump.c new file mode 100644 index 0000000..dbe6dd9 --- /dev/null +++ b/ospfd/ospf_dump.c @@ -0,0 +1,2072 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd dump routine. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "lib/bfd.h" +#include "monotime.h" +#include "linklist.h" +#include "frrevent.h" +#include "prefix.h" +#include "command.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_dump_clippy.c" + +/* Configuration debug option variables. */ +unsigned long conf_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; +unsigned long conf_debug_ospf_event = 0; +unsigned long conf_debug_ospf_ism = 0; +unsigned long conf_debug_ospf_nsm = 0; +unsigned long conf_debug_ospf_lsa = 0; +unsigned long conf_debug_ospf_zebra = 0; +unsigned long conf_debug_ospf_nssa = 0; +unsigned long conf_debug_ospf_te; +unsigned long conf_debug_ospf_ext = 0; +unsigned long conf_debug_ospf_sr; +unsigned long conf_debug_ospf_ti_lfa; +unsigned long conf_debug_ospf_defaultinfo; +unsigned long conf_debug_ospf_ldp_sync; +unsigned long conf_debug_ospf_gr; +unsigned long conf_debug_ospf_bfd; +unsigned long conf_debug_ospf_client_api; + +/* Enable debug option variables -- valid only session. */ +unsigned long term_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; +unsigned long term_debug_ospf_event; +unsigned long term_debug_ospf_ism = 0; +unsigned long term_debug_ospf_nsm = 0; +unsigned long term_debug_ospf_lsa = 0; +unsigned long term_debug_ospf_zebra = 0; +unsigned long term_debug_ospf_nssa = 0; +unsigned long term_debug_ospf_te; +unsigned long term_debug_ospf_ext = 0; +unsigned long term_debug_ospf_sr; +unsigned long term_debug_ospf_ti_lfa; +unsigned long term_debug_ospf_defaultinfo; +unsigned long term_debug_ospf_ldp_sync; +unsigned long term_debug_ospf_gr; +unsigned long term_debug_ospf_bfd; +unsigned long term_debug_ospf_client_api; + +const char *ospf_redist_string(unsigned int route_type) +{ + return (route_type == ZEBRA_ROUTE_MAX) ? "Default" + : zebra_route_string(route_type); +} + +#define OSPF_AREA_STRING_MAXLEN 16 +const char *ospf_area_name_string(struct ospf_area *area) +{ + static char buf[OSPF_AREA_STRING_MAXLEN] = ""; + uint32_t area_id; + + if (!area) + return "-"; + + area_id = ntohl(area->area_id.s_addr); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", (area_id >> 24) & 0xff, + (area_id >> 16) & 0xff, (area_id >> 8) & 0xff, area_id & 0xff); + return buf; +} + +#define OSPF_AREA_DESC_STRING_MAXLEN 23 +const char *ospf_area_desc_string(struct ospf_area *area) +{ + static char buf[OSPF_AREA_DESC_STRING_MAXLEN] = ""; + uint8_t type; + + if (!area) + return "(incomplete)"; + + type = area->external_routing; + switch (type) { + case OSPF_AREA_NSSA: + snprintf(buf, sizeof(buf), "%s [NSSA]", + ospf_area_name_string(area)); + break; + case OSPF_AREA_STUB: + snprintf(buf, sizeof(buf), "%s [Stub]", + ospf_area_name_string(area)); + break; + default: + return ospf_area_name_string(area); + } + + return buf; +} + +#define OSPF_IF_STRING_MAXLEN 40 + +/* Display both nbr and ism state of the ospf neighbor.*/ +const char *ospf_if_name_string(struct ospf_interface *oi) +{ + static char buf[OSPF_IF_STRING_MAXLEN] = ""; + uint32_t ifaddr; + + if (!oi || !oi->address) + return "inactive"; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + return oi->ifp->name; + + ifaddr = ntohl(oi->address->u.prefix4.s_addr); + snprintf(buf, sizeof(buf), "%s:%d.%d.%d.%d", oi->ifp->name, + (ifaddr >> 24) & 0xff, (ifaddr >> 16) & 0xff, + (ifaddr >> 8) & 0xff, ifaddr & 0xff); + return buf; +} + +int ospf_nbr_ism_state(struct ospf_neighbor *nbr) +{ + int state; + struct ospf_interface *oi = nbr->oi; + + if (IPV4_ADDR_SAME(&DR(oi), &nbr->address.u.prefix4)) + state = ISM_DR; + else if (IPV4_ADDR_SAME(&BDR(oi), &nbr->address.u.prefix4)) + state = ISM_Backup; + else + state = ISM_DROther; + + return state; +} + +void ospf_nbr_ism_state_message(struct ospf_neighbor *nbr, char *buf, + size_t size) +{ + int state; + struct ospf_interface *oi = nbr->oi; + + if (!oi) + return; + + /* network type is point-to-point */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT) { + snprintf(buf, size, "%s/-", + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); + return; + } + + state = ospf_nbr_ism_state(nbr); + + snprintf(buf, size, "%s/%s", + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + lookup_msg(ospf_ism_state_msg, state, NULL)); +} + +const char *ospf_timeval_dump(struct timeval *t, char *buf, size_t size) +{ +/* Making formatted timer strings. */ +#define MINUTE_IN_SECONDS 60 +#define HOUR_IN_SECONDS (60*MINUTE_IN_SECONDS) + + unsigned long w, d, h, m, ms, us; + + if (!t) + return "inactive"; + + w = d = h = m = ms = 0; + memset(buf, 0, size); + + us = t->tv_usec; + if (us >= 1000) { + ms = us / 1000; + us %= 1000; + (void)us; /* unused */ + } + + if (ms >= 1000) { + t->tv_sec += ms / 1000; + ms %= 1000; + } + + if (t->tv_sec > ONE_WEEK_SECOND) { + w = t->tv_sec / ONE_WEEK_SECOND; + t->tv_sec -= w * ONE_WEEK_SECOND; + } + + if (t->tv_sec > ONE_DAY_SECOND) { + d = t->tv_sec / ONE_DAY_SECOND; + t->tv_sec -= d * ONE_DAY_SECOND; + } + + if (t->tv_sec >= HOUR_IN_SECONDS) { + h = t->tv_sec / HOUR_IN_SECONDS; + t->tv_sec -= h * HOUR_IN_SECONDS; + } + + if (t->tv_sec >= MINUTE_IN_SECONDS) { + m = t->tv_sec / MINUTE_IN_SECONDS; + t->tv_sec -= m * MINUTE_IN_SECONDS; + } + + if (w > 99) + snprintf(buf, size, "%luw%1lud", w, d); + else if (w) + snprintf(buf, size, "%luw%1lud%02luh", w, d, h); + else if (d) + snprintf(buf, size, "%1lud%02luh%02lum", d, h, m); + else if (h) + snprintf(buf, size, "%luh%02lum%02lds", h, m, (long)t->tv_sec); + else if (m) + snprintf(buf, size, "%lum%02lds", m, (long)t->tv_sec); + else if (t->tv_sec > 0 || ms > 0) + snprintf(buf, size, "%ld.%03lus", (long)t->tv_sec, ms); + else + snprintf(buf, size, "%ld usecs", (long)t->tv_usec); + + return buf; +} + +const char *ospf_timer_dump(struct event *t, char *buf, size_t size) +{ + struct timeval result; + if (!t) + return "inactive"; + + monotime_until(&t->u.sands, &result); + return ospf_timeval_dump(&result, buf, size); +} + +static void ospf_packet_hello_dump(struct stream *s, uint16_t length) +{ + struct ospf_hello *hello; + int i, len; + + hello = (struct ospf_hello *)stream_pnt(s); + + zlog_debug("Hello"); + zlog_debug(" NetworkMask %pI4", &hello->network_mask); + zlog_debug(" HelloInterval %d", ntohs(hello->hello_interval)); + zlog_debug(" Options %d (%s)", hello->options, + ospf_options_dump(hello->options)); + zlog_debug(" RtrPriority %d", hello->priority); + zlog_debug(" RtrDeadInterval %ld", + (unsigned long)ntohl(hello->dead_interval)); + zlog_debug(" DRouter %pI4", &hello->d_router); + zlog_debug(" BDRouter %pI4", &hello->bd_router); + + len = length - OSPF_HEADER_SIZE - OSPF_HELLO_MIN_SIZE; + zlog_debug(" # Neighbors %d", len / 4); + for (i = 0; len > 0; i++, len -= sizeof(struct in_addr)) + zlog_debug(" Neighbor %pI4", &hello->neighbors[i]); +} + +static char *ospf_dd_flags_dump(uint8_t flags, char *buf, size_t size) +{ + snprintf(buf, size, "%s|%s|%s", (flags & OSPF_DD_FLAG_I) ? "I" : "-", + (flags & OSPF_DD_FLAG_M) ? "M" : "-", + (flags & OSPF_DD_FLAG_MS) ? "MS" : "-"); + + return buf; +} + +static char *ospf_router_lsa_flags_dump(uint8_t flags, char *buf, size_t size) +{ + snprintf(buf, size, "%s|%s|%s", + (flags & ROUTER_LSA_VIRTUAL) ? "V" : "-", + (flags & ROUTER_LSA_EXTERNAL) ? "E" : "-", + (flags & ROUTER_LSA_BORDER) ? "B" : "-"); + + return buf; +} + +static void ospf_router_lsa_dump(struct stream *s, uint16_t length) +{ + char buf[BUFSIZ]; + struct router_lsa *rl; + struct router_link *rlnk; + int i, len, sum; + + rl = (struct router_lsa *)stream_pnt(s); + + zlog_debug(" Router-LSA"); + zlog_debug(" flags %s", + ospf_router_lsa_flags_dump(rl->flags, buf, BUFSIZ)); + zlog_debug(" # links %d", ntohs(rl->links)); + + len = length - OSPF_LSA_HEADER_SIZE - 4; + rlnk = &rl->link[0]; + sum = 0; + for (i = 0; sum < len && rlnk; sum += 12, rlnk = &rl->link[++i]) { + zlog_debug(" Link ID %pI4", &rlnk->link_id); + zlog_debug(" Link Data %pI4", &rlnk->link_data); + zlog_debug(" Type %d", (uint8_t)rlnk->type); + zlog_debug(" TOS %d", (uint8_t)rlnk->tos); + zlog_debug(" metric %d", ntohs(rlnk->metric)); + } +} + +static void ospf_network_lsa_dump(struct stream *s, uint16_t length) +{ + struct network_lsa *nl; + int i, cnt; + + zlog_debug(" Network-LSA"); + + nl = (struct network_lsa *)stream_pnt(s); + cnt = (length - (OSPF_LSA_HEADER_SIZE + 4)) / 4; + + /* + zlog_debug ("LSA total size %d", ntohs (nl->header.length)); + zlog_debug ("Network-LSA size %d", + ntohs (nl->header.length) - OSPF_LSA_HEADER_SIZE); + */ + zlog_debug(" Network Mask %pI4", &nl->mask); + zlog_debug(" # Attached Routers %d", cnt); + for (i = 0; i < cnt; i++) + zlog_debug(" Attached Router %pI4", + &nl->routers[i]); +} + +static void ospf_summary_lsa_dump(struct stream *s, uint16_t length) +{ + struct summary_lsa *sl; + + sl = (struct summary_lsa *)stream_pnt(s); + + zlog_debug(" Summary-LSA"); + zlog_debug(" Network Mask %pI4", &sl->mask); + zlog_debug(" TOS=%d metric %d", sl->tos, GET_METRIC(sl->metric)); +} + +static void ospf_as_external_lsa_dump(struct stream *s, uint16_t length) +{ + struct as_external_lsa *al; + struct as_route *asr; + int size, sum; + int i; + + al = (struct as_external_lsa *)stream_pnt(s); + zlog_debug(" %s", ospf_lsa_type_msg[al->header.type].str); + zlog_debug(" Network Mask %pI4", &al->mask); + + size = length - OSPF_LSA_HEADER_SIZE - 4; + asr = &al->e[0]; + sum = 0; + for (i = 0; sum < size && asr; sum += 12, asr = &al->e[++i]) { + zlog_debug(" bit %s TOS=%d metric %d", + IS_EXTERNAL_METRIC(asr->tos) ? "E" : "-", + asr->tos & 0x7f, GET_METRIC(asr->metric)); + zlog_debug(" Forwarding address %pI4", &asr->fwd_addr); + zlog_debug(" External Route Tag %" ROUTE_TAG_PRI, + ntohl(asr->route_tag)); + } +} + +static void ospf_lsa_header_list_dump(struct stream *s, uint16_t length) +{ + struct lsa_header *lsa; + int len; + + zlog_debug(" # LSA Headers %d", length / OSPF_LSA_HEADER_SIZE); + + /* LSA Headers. */ + len = length; + while (len > 0) { + lsa = (struct lsa_header *)stream_pnt(s); + ospf_lsa_header_dump(lsa); + + stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); + len -= OSPF_LSA_HEADER_SIZE; + } +} + +static void ospf_packet_db_desc_dump(struct stream *s, uint16_t length) +{ + struct ospf_db_desc *dd; + char dd_flags[8]; + + uint32_t gp; + + gp = stream_get_getp(s); + dd = (struct ospf_db_desc *)stream_pnt(s); + + zlog_debug("Database Description"); + zlog_debug(" Interface MTU %d", ntohs(dd->mtu)); + zlog_debug(" Options %d (%s)", dd->options, + ospf_options_dump(dd->options)); + zlog_debug(" Flags %d (%s)", dd->flags, + ospf_dd_flags_dump(dd->flags, dd_flags, sizeof(dd_flags))); + zlog_debug(" Sequence Number 0x%08lx", + (unsigned long)ntohl(dd->dd_seqnum)); + + length -= OSPF_HEADER_SIZE + OSPF_DB_DESC_MIN_SIZE; + + stream_forward_getp(s, OSPF_DB_DESC_MIN_SIZE); + + ospf_lsa_header_list_dump(s, length); + + stream_set_getp(s, gp); +} + +static void ospf_packet_ls_req_dump(struct stream *s, uint16_t length) +{ + uint32_t sp; + uint32_t ls_type; + struct in_addr ls_id; + struct in_addr adv_router; + int sum; + + sp = stream_get_getp(s); + + length -= OSPF_HEADER_SIZE; + + zlog_debug("Link State Request"); + zlog_debug(" # Requests %d", length / 12); + + sum = 0; + for (; sum < length; sum += 12) { + ls_type = stream_getl(s); + ls_id.s_addr = stream_get_ipv4(s); + adv_router.s_addr = stream_get_ipv4(s); + + zlog_debug(" LS type %d", ls_type); + zlog_debug(" Link State ID %pI4", &ls_id); + zlog_debug(" Advertising Router %pI4", &adv_router); + } + + stream_set_getp(s, sp); +} + +static void ospf_packet_ls_upd_dump(struct stream *s, uint16_t length) +{ + uint32_t sp; + struct lsa_header *lsa; + int lsa_len, len; + uint32_t count; + + len = length - OSPF_HEADER_SIZE; + + sp = stream_get_getp(s); + + count = stream_getl(s); + len -= 4; + + zlog_debug("Link State Update"); + zlog_debug(" # LSAs %d", count); + + while (len > 0 && count > 0) { + if ((uint16_t)len < OSPF_LSA_HEADER_SIZE || len % 4 != 0) { + zlog_debug(" Remaining %d bytes; Incorrect length.", + len); + break; + } + + lsa = (struct lsa_header *)stream_pnt(s); + lsa_len = ntohs(lsa->length); + ospf_lsa_header_dump(lsa); + + /* Check that LSA length is valid */ + if (lsa_len > len || lsa_len % 4 != 0) { + zlog_debug(" LSA length %d is incorrect!", lsa_len); + break; + } + switch (lsa->type) { + case OSPF_ROUTER_LSA: + ospf_router_lsa_dump(s, lsa_len); + break; + case OSPF_NETWORK_LSA: + ospf_network_lsa_dump(s, lsa_len); + break; + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + ospf_summary_lsa_dump(s, lsa_len); + break; + case OSPF_AS_EXTERNAL_LSA: + ospf_as_external_lsa_dump(s, lsa_len); + break; + case OSPF_AS_NSSA_LSA: + ospf_as_external_lsa_dump(s, lsa_len); + break; + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + ospf_opaque_lsa_dump(s, lsa_len); + break; + default: + break; + } + + stream_forward_getp(s, lsa_len); + len -= lsa_len; + count--; + } + + stream_set_getp(s, sp); +} + +static void ospf_packet_ls_ack_dump(struct stream *s, uint16_t length) +{ + uint32_t sp; + + length -= OSPF_HEADER_SIZE; + sp = stream_get_getp(s); + + zlog_debug("Link State Acknowledgment"); + ospf_lsa_header_list_dump(s, length); + + stream_set_getp(s, sp); +} + +static void ospf_header_dump(struct ospf_header *ospfh) +{ + char buf[9]; + uint16_t auth_type = ntohs(ospfh->auth_type); + + zlog_debug("Header"); + zlog_debug(" Version %d", ospfh->version); + zlog_debug(" Type %d (%s)", ospfh->type, + lookup_msg(ospf_packet_type_str, ospfh->type, NULL)); + zlog_debug(" Packet Len %d", ntohs(ospfh->length)); + zlog_debug(" Router ID %pI4", &ospfh->router_id); + zlog_debug(" Area ID %pI4", &ospfh->area_id); + zlog_debug(" Checksum 0x%x", ntohs(ospfh->checksum)); + zlog_debug(" AuType %s", + lookup_msg(ospf_auth_type_str, auth_type, NULL)); + + switch (auth_type) { + case OSPF_AUTH_NULL: + break; + case OSPF_AUTH_SIMPLE: + strlcpy(buf, (char *)ospfh->u.auth_data, sizeof(buf)); + zlog_debug(" Simple Password %s", buf); + break; + case OSPF_AUTH_CRYPTOGRAPHIC: + zlog_debug(" Cryptographic Authentication"); + zlog_debug(" Key ID %d", ospfh->u.crypt.key_id); + zlog_debug(" Auth Data Len %d", ospfh->u.crypt.auth_data_len); + zlog_debug(" Sequence number %ld", + (unsigned long)ntohl(ospfh->u.crypt.crypt_seqnum)); + break; + default: + zlog_debug("* This is not supported authentication type"); + break; + } +} + +void ospf_packet_dump(struct stream *s) +{ + struct ospf_header *ospfh; + unsigned long gp; + + /* Preserve pointer. */ + gp = stream_get_getp(s); + + /* OSPF Header dump. */ + ospfh = (struct ospf_header *)stream_pnt(s); + + /* Until detail flag is set, return. */ + if (!(term_debug_ospf_packet[ospfh->type - 1] & OSPF_DEBUG_DETAIL)) + return; + + /* Show OSPF header detail. */ + ospf_header_dump(ospfh); + stream_forward_getp(s, OSPF_HEADER_SIZE); + + switch (ospfh->type) { + case OSPF_MSG_HELLO: + ospf_packet_hello_dump(s, ntohs(ospfh->length)); + break; + case OSPF_MSG_DB_DESC: + ospf_packet_db_desc_dump(s, ntohs(ospfh->length)); + break; + case OSPF_MSG_LS_REQ: + ospf_packet_ls_req_dump(s, ntohs(ospfh->length)); + break; + case OSPF_MSG_LS_UPD: + ospf_packet_ls_upd_dump(s, ntohs(ospfh->length)); + break; + case OSPF_MSG_LS_ACK: + ospf_packet_ls_ack_dump(s, ntohs(ospfh->length)); + break; + default: + break; + } + + stream_set_getp(s, gp); +} + +DEFPY (debug_ospf_packet, + debug_ospf_packet_cmd, + "[no$no] debug ospf [(1-65535)$inst] packet $packet []", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF packets\n" + "OSPF Hello\n" + "OSPF Database Description\n" + "OSPF Link State Request\n" + "OSPF Link State Update\n" + "OSPF Link State Acknowledgment\n" + "OSPF all packets\n" + "Packet sent\n" + "Detail Information\n" + "Packet received\n" + "Detail Information\n" + "Detail Information\n") +{ + int type = 0; + int flag = 0; + int i; + + if (inst && inst != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + /* Check packet type. */ + if (strmatch(packet, "hello")) + type = OSPF_DEBUG_HELLO; + else if (strmatch(packet, "dd")) + type = OSPF_DEBUG_DB_DESC; + else if (strmatch(packet, "ls-request")) + type = OSPF_DEBUG_LS_REQ; + else if (strmatch(packet, "ls-update")) + type = OSPF_DEBUG_LS_UPD; + else if (strmatch(packet, "ls-ack")) + type = OSPF_DEBUG_LS_ACK; + else if (strmatch(packet, "all")) + type = OSPF_DEBUG_ALL; + + /* Cases: + * (none) = send + recv + * detail = send + recv + detail + * recv = recv + * send = send + * recv detail = recv + detail + * send detail = send + detail + */ + if (!send && !recv) { + flag |= OSPF_DEBUG_SEND; + flag |= OSPF_DEBUG_RECV; + } + + flag |= (send) ? OSPF_DEBUG_SEND : 0; + flag |= (recv) ? OSPF_DEBUG_RECV : 0; + flag |= (detail) ? OSPF_DEBUG_DETAIL : 0; + + for (i = 0; i < 5; i++) + if (type & (0x01 << i)) { + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_PACKET_OFF(i, flag); + else + DEBUG_PACKET_ON(i, flag); + } else { + if (no) + TERM_DEBUG_PACKET_OFF(i, flag); + else + TERM_DEBUG_PACKET_ON(i, flag); + } + } + +#ifdef DEBUG +/* +for (i = 0; i < 5; i++) + zlog_debug ("flag[%d] = %d", i, ospf_debug_packet[i]); +*/ +#endif /* DEBUG */ + + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_ism, + debug_ospf_ism_cmd, + "debug ospf [(1-65535)] ism []", + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Interface State Machine\n" + "ISM Status Information\n" + "ISM Event Information\n" + "ISM TImer Information\n") +{ + int inst = (argv[2]->type == RANGE_TKN); + char *dbgparam = (argc == 4 + inst) ? argv[argc - 1]->text : NULL; + + if (inst) // user passed instance ID + { + if (inst != ospf_instance) + return CMD_NOT_MY_INSTANCE; + } + + if (vty->node == CONFIG_NODE) { + if (!dbgparam) + DEBUG_ON(ism, ISM); + else { + if (strmatch(dbgparam, "status")) + DEBUG_ON(ism, ISM_STATUS); + else if (strmatch(dbgparam, "events")) + DEBUG_ON(ism, ISM_EVENTS); + else if (strmatch(dbgparam, "timers")) + DEBUG_ON(ism, ISM_TIMERS); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (!dbgparam) + TERM_DEBUG_ON(ism, ISM); + else { + if (strmatch(dbgparam, "status")) + TERM_DEBUG_ON(ism, ISM_STATUS); + else if (strmatch(dbgparam, "events")) + TERM_DEBUG_ON(ism, ISM_EVENTS); + else if (strmatch(dbgparam, "timers")) + TERM_DEBUG_ON(ism, ISM_TIMERS); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_ism, + no_debug_ospf_ism_cmd, + "no debug ospf [(1-65535)] ism []", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Interface State Machine\n" + "ISM Status Information\n" + "ISM Event Information\n" + "ISM TImer Information\n") +{ + int inst = (argv[3]->type == RANGE_TKN); + char *dbgparam = (argc == 5 + inst) ? argv[argc - 1]->text : NULL; + + if (inst) // user passed instance ID + { + if (inst != ospf_instance) + return CMD_NOT_MY_INSTANCE; + } + + if (vty->node == CONFIG_NODE) { + if (!dbgparam) + DEBUG_OFF(ism, ISM); + else { + if (strmatch(dbgparam, "status")) + DEBUG_OFF(ism, ISM_STATUS); + else if (strmatch(dbgparam, "events")) + DEBUG_OFF(ism, ISM_EVENTS); + else if (strmatch(dbgparam, "timers")) + DEBUG_OFF(ism, ISM_TIMERS); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (!dbgparam) + TERM_DEBUG_OFF(ism, ISM); + else { + if (strmatch(dbgparam, "status")) + TERM_DEBUG_OFF(ism, ISM_STATUS); + else if (strmatch(dbgparam, "events")) + TERM_DEBUG_OFF(ism, ISM_EVENTS); + else if (strmatch(dbgparam, "timers")) + TERM_DEBUG_OFF(ism, ISM_TIMERS); + } + + return CMD_SUCCESS; +} + +static int debug_ospf_nsm_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_ON(nsm, NSM); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "status")) + DEBUG_ON(nsm, NSM_STATUS); + else if (strmatch(argv[arg_base]->text, "events")) + DEBUG_ON(nsm, NSM_EVENTS); + else if (strmatch(argv[arg_base]->text, "timers")) + DEBUG_ON(nsm, NSM_TIMERS); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_ON(nsm, NSM); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "status")) + TERM_DEBUG_ON(nsm, NSM_STATUS); + else if (strmatch(argv[arg_base]->text, "events")) + TERM_DEBUG_ON(nsm, NSM_EVENTS); + else if (strmatch(argv[arg_base]->text, "timers")) + TERM_DEBUG_ON(nsm, NSM_TIMERS); + } + + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_nsm, + debug_ospf_nsm_cmd, + "debug ospf nsm []", + DEBUG_STR + OSPF_STR + "OSPF Neighbor State Machine\n" + "NSM Status Information\n" + "NSM Event Information\n" + "NSM Timer Information\n") +{ + return debug_ospf_nsm_common(vty, 3, argc, argv); +} + +DEFUN (debug_ospf_instance_nsm, + debug_ospf_instance_nsm_cmd, + "debug ospf (1-65535) nsm []", + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Neighbor State Machine\n" + "NSM Status Information\n" + "NSM Event Information\n" + "NSM Timer Information\n") +{ + int idx_number = 2; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return debug_ospf_nsm_common(vty, 4, argc, argv); +} + + +static int no_debug_ospf_nsm_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + /* XXX qlyoung */ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_OFF(nsm, NSM); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "status")) + DEBUG_OFF(nsm, NSM_STATUS); + else if (strmatch(argv[arg_base]->text, "events")) + DEBUG_OFF(nsm, NSM_EVENTS); + else if (strmatch(argv[arg_base]->text, "timers")) + DEBUG_OFF(nsm, NSM_TIMERS); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_OFF(nsm, NSM); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "status")) + TERM_DEBUG_OFF(nsm, NSM_STATUS); + else if (strmatch(argv[arg_base]->text, "events")) + TERM_DEBUG_OFF(nsm, NSM_EVENTS); + else if (strmatch(argv[arg_base]->text, "timers")) + TERM_DEBUG_OFF(nsm, NSM_TIMERS); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_nsm, + no_debug_ospf_nsm_cmd, + "no debug ospf nsm []", + NO_STR + DEBUG_STR + OSPF_STR + "OSPF Neighbor State Machine\n" + "NSM Status Information\n" + "NSM Event Information\n" + "NSM Timer Information\n") +{ + return no_debug_ospf_nsm_common(vty, 4, argc, argv); +} + + +DEFUN (no_debug_ospf_instance_nsm, + no_debug_ospf_instance_nsm_cmd, + "no debug ospf (1-65535) nsm []", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Neighbor State Machine\n" + "NSM Status Information\n" + "NSM Event Information\n" + "NSM Timer Information\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return no_debug_ospf_nsm_common(vty, 5, argc, argv); +} + + +static int debug_ospf_lsa_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_ON(lsa, LSA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "generate")) + DEBUG_ON(lsa, LSA_GENERATE); + else if (strmatch(argv[arg_base]->text, "flooding")) + DEBUG_ON(lsa, LSA_FLOODING); + else if (strmatch(argv[arg_base]->text, "install")) + DEBUG_ON(lsa, LSA_INSTALL); + else if (strmatch(argv[arg_base]->text, "refresh")) + DEBUG_ON(lsa, LSA_REFRESH); + else if (strmatch(argv[arg_base]->text, "aggregate")) + DEBUG_ON(lsa, EXTNL_LSA_AGGR); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_ON(lsa, LSA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "generate")) + TERM_DEBUG_ON(lsa, LSA_GENERATE); + else if (strmatch(argv[arg_base]->text, "flooding")) + TERM_DEBUG_ON(lsa, LSA_FLOODING); + else if (strmatch(argv[arg_base]->text, "install")) + TERM_DEBUG_ON(lsa, LSA_INSTALL); + else if (strmatch(argv[arg_base]->text, "refresh")) + TERM_DEBUG_ON(lsa, LSA_REFRESH); + else if (strmatch(argv[arg_base]->text, "aggregate")) + TERM_DEBUG_ON(lsa, EXTNL_LSA_AGGR); + } + + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_lsa, + debug_ospf_lsa_cmd, + "debug ospf lsa []", + DEBUG_STR + OSPF_STR + "OSPF Link State Advertisement\n" + "LSA Generation\n" + "LSA Flooding\n" + "LSA Install/Delete\n" + "LSA Refresh\n" + "External LSA Aggregation\n") +{ + return debug_ospf_lsa_common(vty, 3, argc, argv); +} + +DEFUN (debug_ospf_instance_lsa, + debug_ospf_instance_lsa_cmd, + "debug ospf (1-65535) lsa " + "[]", + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Link State Advertisement\n" + "LSA Generation\n" + "LSA Flooding\n" + "LSA Install/Delete\n" + "LSA Refresh\n" + "External LSA Aggregation\n") +{ + int idx_number = 2; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return debug_ospf_lsa_common(vty, 4, argc, argv); +} + + +static int no_debug_ospf_lsa_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_OFF(lsa, LSA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "generate")) + DEBUG_OFF(lsa, LSA_GENERATE); + else if (strmatch(argv[arg_base]->text, "flooding")) + DEBUG_OFF(lsa, LSA_FLOODING); + else if (strmatch(argv[arg_base]->text, "install")) + DEBUG_OFF(lsa, LSA_INSTALL); + else if (strmatch(argv[arg_base]->text, "refresh")) + DEBUG_OFF(lsa, LSA_REFRESH); + else if (strmatch(argv[arg_base]->text, "aggregate")) + DEBUG_OFF(lsa, EXTNL_LSA_AGGR); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_OFF(lsa, LSA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "generate")) + TERM_DEBUG_OFF(lsa, LSA_GENERATE); + else if (strmatch(argv[arg_base]->text, "flooding")) + TERM_DEBUG_OFF(lsa, LSA_FLOODING); + else if (strmatch(argv[arg_base]->text, "install")) + TERM_DEBUG_OFF(lsa, LSA_INSTALL); + else if (strmatch(argv[arg_base]->text, "refresh")) + TERM_DEBUG_OFF(lsa, LSA_REFRESH); + else if (strmatch(argv[arg_base]->text, "aggregate")) + TERM_DEBUG_OFF(lsa, EXTNL_LSA_AGGR); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_lsa, + no_debug_ospf_lsa_cmd, + "no debug ospf lsa []", + NO_STR + DEBUG_STR + OSPF_STR + "OSPF Link State Advertisement\n" + "LSA Generation\n" + "LSA Flooding\n" + "LSA Install/Delete\n" + "LSA Refres\n" + "External LSA Aggregation\n") +{ + return no_debug_ospf_lsa_common(vty, 4, argc, argv); +} + +DEFUN (no_debug_ospf_instance_lsa, + no_debug_ospf_instance_lsa_cmd, + "no debug ospf (1-65535) lsa " + "[]", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Link State Advertisement\n" + "LSA Generation\n" + "LSA Flooding\n" + "LSA Install/Delete\n" + "LSA Refres\n" + "External LSA Aggregation\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return no_debug_ospf_lsa_common(vty, 5, argc, argv); +} + + +static int debug_ospf_zebra_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_ON(zebra, ZEBRA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "interface")) + DEBUG_ON(zebra, ZEBRA_INTERFACE); + else if (strmatch(argv[arg_base]->text, "redistribute")) + DEBUG_ON(zebra, ZEBRA_REDISTRIBUTE); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_ON(zebra, ZEBRA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "interface")) + TERM_DEBUG_ON(zebra, ZEBRA_INTERFACE); + else if (strmatch(argv[arg_base]->text, "redistribute")) + TERM_DEBUG_ON(zebra, ZEBRA_REDISTRIBUTE); + } + + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_zebra, + debug_ospf_zebra_cmd, + "debug ospf zebra []", + DEBUG_STR + OSPF_STR + ZEBRA_STR + "Zebra interface\n" + "Zebra redistribute\n") +{ + return debug_ospf_zebra_common(vty, 3, argc, argv); +} + +DEFUN (debug_ospf_instance_zebra, + debug_ospf_instance_zebra_cmd, + "debug ospf (1-65535) zebra []", + DEBUG_STR + OSPF_STR + "Instance ID\n" + ZEBRA_STR + "Zebra interface\n" + "Zebra redistribute\n") +{ + int idx_number = 2; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return debug_ospf_zebra_common(vty, 4, argc, argv); +} + + +static int no_debug_ospf_zebra_common(struct vty *vty, int arg_base, int argc, + struct cmd_token **argv) +{ + if (vty->node == CONFIG_NODE) { + if (argc == arg_base + 0) + DEBUG_OFF(zebra, ZEBRA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "interface")) + DEBUG_OFF(zebra, ZEBRA_INTERFACE); + else if (strmatch(argv[arg_base]->text, "redistribute")) + DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); + } + + return CMD_SUCCESS; + } + + /* ENABLE_NODE. */ + if (argc == arg_base + 0) + TERM_DEBUG_OFF(zebra, ZEBRA); + else if (argc == arg_base + 1) { + if (strmatch(argv[arg_base]->text, "interface")) + TERM_DEBUG_OFF(zebra, ZEBRA_INTERFACE); + else if (strmatch(argv[arg_base]->text, "redistribute")) + TERM_DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_zebra, + no_debug_ospf_zebra_cmd, + "no debug ospf zebra []", + NO_STR + DEBUG_STR + OSPF_STR + ZEBRA_STR + "Zebra interface\n" + "Zebra redistribute\n") +{ + return no_debug_ospf_zebra_common(vty, 4, argc, argv); +} + +DEFUN (no_debug_ospf_instance_zebra, + no_debug_ospf_instance_zebra_cmd, + "no debug ospf (1-65535) zebra []", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + ZEBRA_STR + "Zebra interface\n" + "Zebra redistribute\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + return no_debug_ospf_zebra_common(vty, 5, argc, argv); +} + + +DEFUN (debug_ospf_event, + debug_ospf_event_cmd, + "debug ospf event", + DEBUG_STR + OSPF_STR + "OSPF event information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(event, EVENT); + TERM_DEBUG_ON(event, EVENT); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_event, + no_debug_ospf_event_cmd, + "no debug ospf event", + NO_STR + DEBUG_STR + OSPF_STR + "OSPF event information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(event, EVENT); + TERM_DEBUG_OFF(event, EVENT); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_instance_event, + debug_ospf_instance_event_cmd, + "debug ospf (1-65535) event", + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF event information\n") +{ + int idx_number = 2; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(event, EVENT); + TERM_DEBUG_ON(event, EVENT); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_instance_event, + no_debug_ospf_instance_event_cmd, + "no debug ospf (1-65535) event", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF event information\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(event, EVENT); + TERM_DEBUG_OFF(event, EVENT); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_nssa, + debug_ospf_nssa_cmd, + "debug ospf nssa", + DEBUG_STR + OSPF_STR + "OSPF nssa information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(nssa, NSSA); + TERM_DEBUG_ON(nssa, NSSA); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_nssa, + no_debug_ospf_nssa_cmd, + "no debug ospf nssa", + NO_STR + DEBUG_STR + OSPF_STR + "OSPF nssa information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(nssa, NSSA); + TERM_DEBUG_OFF(nssa, NSSA); + return CMD_SUCCESS; +} + +DEFUN (debug_ospf_instance_nssa, + debug_ospf_instance_nssa_cmd, + "debug ospf (1-65535) nssa", + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF nssa information\n") +{ + int idx_number = 2; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(nssa, NSSA); + TERM_DEBUG_ON(nssa, NSSA); + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf_instance_nssa, + no_debug_ospf_instance_nssa_cmd, + "no debug ospf (1-65535) nssa", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF nssa information\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(nssa, NSSA); + TERM_DEBUG_OFF(nssa, NSSA); + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_te, + debug_ospf_te_cmd, + "[no$no] debug ospf [(1-65535)$instance] te", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF-TE information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(te, TE); + else + DEBUG_ON(te, TE); + } else { + if (no) + TERM_DEBUG_OFF(te, TE); + else + TERM_DEBUG_ON(te, TE); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_sr, + debug_ospf_sr_cmd, + "[no$no] debug ospf [(1-65535)$instance] sr", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF-SR information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(sr, SR); + else + DEBUG_ON(sr, SR); + } else { + if (no) + TERM_DEBUG_OFF(sr, SR); + else + TERM_DEBUG_ON(sr, SR); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_ti_lfa, + debug_ospf_ti_lfa_cmd, + "[no$no] debug ospf [(1-65535)$instance] ti-lfa", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF-SR TI-LFA information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(ti_lfa, TI_LFA); + else + DEBUG_ON(ti_lfa, TI_LFA); + } else { + if (no) + TERM_DEBUG_OFF(ti_lfa, TI_LFA); + else + TERM_DEBUG_ON(ti_lfa, TI_LFA); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_default_info, + debug_ospf_default_info_cmd, + "[no$no] debug ospf [(1-65535)$instance] default-information", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF default information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(defaultinfo, DEFAULTINFO); + else + DEBUG_ON(defaultinfo, DEFAULTINFO); + } else { + if (no) + TERM_DEBUG_OFF(defaultinfo, DEFAULTINFO); + else + TERM_DEBUG_ON(defaultinfo, DEFAULTINFO); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_ldp_sync, + debug_ospf_ldp_sync_cmd, + "[no$no] debug ospf [(1-65535)$instance] ldp-sync", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF LDP-Sync information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(ldp_sync, LDP_SYNC); + else + DEBUG_ON(ldp_sync, LDP_SYNC); + } else { + if (no) + TERM_DEBUG_OFF(ldp_sync, LDP_SYNC); + else + TERM_DEBUG_ON(ldp_sync, LDP_SYNC); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_gr, + debug_ospf_gr_cmd, + "[no$no] debug ospf [(1-65535)$instance] graceful-restart", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF Graceful Restart\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + CONF_DEBUG_OFF(gr, GR); + else + CONF_DEBUG_ON(gr, GR); + } + + if (no) + TERM_DEBUG_OFF(gr, GR); + else + TERM_DEBUG_ON(gr, GR); + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_bfd, + debug_ospf_bfd_cmd, + "[no] debug ospf [(1-65535)$instance] bfd", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "Bidirection Forwarding Detection\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) { + bfd_protocol_integration_set_debug(false); + DEBUG_OFF(bfd, BFD_LIB); + } else { + bfd_protocol_integration_set_debug(true); + DEBUG_ON(bfd, BFD_LIB); + } + } else { + if (no) + TERM_DEBUG_OFF(bfd, BFD_LIB); + else + TERM_DEBUG_ON(bfd, BFD_LIB); + } + + return CMD_SUCCESS; +} + +DEFPY (debug_ospf_client_api, + debug_ospf_client_api_cmd, + "[no$no] debug ospf [(1-65535)$instance] client-api", + NO_STR + DEBUG_STR + OSPF_STR + "Instance ID\n" + "OSPF client API information\n") +{ + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(client_api, CLIENT_API); + else + DEBUG_ON(client_api, CLIENT_API); + } else { + if (no) + TERM_DEBUG_OFF(client_api, CLIENT_API); + else + TERM_DEBUG_ON(client_api, CLIENT_API); + } + + return CMD_SUCCESS; +} + +DEFUN (no_debug_ospf, + no_debug_ospf_cmd, + "no debug ospf", + NO_STR + DEBUG_STR + OSPF_STR) +{ + int flag = OSPF_DEBUG_SEND | OSPF_DEBUG_RECV | OSPF_DEBUG_DETAIL; + int i; + + if (vty->node == CONFIG_NODE) { + CONF_DEBUG_OFF(event, EVENT); + CONF_DEBUG_OFF(nssa, NSSA); + DEBUG_OFF(ism, ISM_EVENTS); + DEBUG_OFF(ism, ISM_STATUS); + DEBUG_OFF(ism, ISM_TIMERS); + DEBUG_OFF(lsa, LSA); + DEBUG_OFF(lsa, LSA_FLOODING); + DEBUG_OFF(lsa, LSA_GENERATE); + DEBUG_OFF(lsa, LSA_INSTALL); + DEBUG_OFF(lsa, LSA_REFRESH); + DEBUG_OFF(nsm, NSM); + DEBUG_OFF(nsm, NSM_EVENTS); + DEBUG_OFF(nsm, NSM_STATUS); + DEBUG_OFF(nsm, NSM_TIMERS); + DEBUG_OFF(event, EVENT); + DEBUG_OFF(zebra, ZEBRA); + DEBUG_OFF(zebra, ZEBRA_INTERFACE); + DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); + DEBUG_OFF(defaultinfo, DEFAULTINFO); + DEBUG_OFF(ldp_sync, LDP_SYNC); + DEBUG_OFF(te, TE); + DEBUG_OFF(sr, SR); + DEBUG_OFF(ti_lfa, TI_LFA); + DEBUG_OFF(client_api, CLIENT_API); + + /* BFD debugging is two parts: OSPF and library. */ + DEBUG_OFF(bfd, BFD_LIB); + bfd_protocol_integration_set_debug(false); + + for (i = 0; i < 5; i++) + DEBUG_PACKET_OFF(i, flag); + } + + for (i = 0; i < 5; i++) + TERM_DEBUG_PACKET_OFF(i, flag); + + TERM_DEBUG_OFF(event, EVENT); + TERM_DEBUG_OFF(ism, ISM); + TERM_DEBUG_OFF(ism, ISM_EVENTS); + TERM_DEBUG_OFF(ism, ISM_STATUS); + TERM_DEBUG_OFF(ism, ISM_TIMERS); + TERM_DEBUG_OFF(lsa, LSA); + TERM_DEBUG_OFF(lsa, LSA_FLOODING); + TERM_DEBUG_OFF(lsa, LSA_GENERATE); + TERM_DEBUG_OFF(lsa, LSA_INSTALL); + TERM_DEBUG_OFF(lsa, LSA_REFRESH); + TERM_DEBUG_OFF(nsm, NSM); + TERM_DEBUG_OFF(nsm, NSM_EVENTS); + TERM_DEBUG_OFF(nsm, NSM_STATUS); + TERM_DEBUG_OFF(nsm, NSM_TIMERS); + TERM_DEBUG_OFF(nssa, NSSA); + TERM_DEBUG_OFF(zebra, ZEBRA); + TERM_DEBUG_OFF(zebra, ZEBRA_INTERFACE); + TERM_DEBUG_OFF(zebra, ZEBRA_REDISTRIBUTE); + TERM_DEBUG_OFF(defaultinfo, DEFAULTINFO); + TERM_DEBUG_OFF(ldp_sync, LDP_SYNC); + TERM_DEBUG_OFF(te, TE); + TERM_DEBUG_OFF(sr, SR); + TERM_DEBUG_OFF(ti_lfa, TI_LFA); + TERM_DEBUG_OFF(bfd, BFD_LIB); + TERM_DEBUG_OFF(client_api, CLIENT_API); + + return CMD_SUCCESS; +} + +static int show_debugging_ospf_common(struct vty *vty) +{ + int i; + + if (ospf_instance) + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf_instance); + + vty_out(vty, "OSPF debugging status:\n"); + + /* Show debug status for events. */ + if (IS_DEBUG_OSPF(event, EVENT)) + vty_out(vty, " OSPF event debugging is on\n"); + + /* Show debug status for ISM. */ + if (IS_DEBUG_OSPF(ism, ISM) == OSPF_DEBUG_ISM) + vty_out(vty, " OSPF ISM debugging is on\n"); + else { + if (IS_DEBUG_OSPF(ism, ISM_STATUS)) + vty_out(vty, " OSPF ISM status debugging is on\n"); + if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + vty_out(vty, " OSPF ISM event debugging is on\n"); + if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) + vty_out(vty, " OSPF ISM timer debugging is on\n"); + } + + /* Show debug status for NSM. */ + if (IS_DEBUG_OSPF(nsm, NSM) == OSPF_DEBUG_NSM) + vty_out(vty, " OSPF NSM debugging is on\n"); + else { + if (IS_DEBUG_OSPF(nsm, NSM_STATUS)) + vty_out(vty, " OSPF NSM status debugging is on\n"); + if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) + vty_out(vty, " OSPF NSM event debugging is on\n"); + if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) + vty_out(vty, " OSPF NSM timer debugging is on\n"); + } + + /* Show debug status for OSPF Packets. */ + for (i = 0; i < 5; i++) + if (IS_DEBUG_OSPF_PACKET(i, SEND) + && IS_DEBUG_OSPF_PACKET(i, RECV)) { + vty_out(vty, " OSPF packet %s%s debugging is on\n", + lookup_msg(ospf_packet_type_str, i + 1, NULL), + IS_DEBUG_OSPF_PACKET(i, DETAIL) ? " detail" + : ""); + } else { + if (IS_DEBUG_OSPF_PACKET(i, SEND)) + vty_out(vty, + " OSPF packet %s send%s debugging is on\n", + lookup_msg(ospf_packet_type_str, i + 1, + NULL), + IS_DEBUG_OSPF_PACKET(i, DETAIL) + ? " detail" + : ""); + if (IS_DEBUG_OSPF_PACKET(i, RECV)) + vty_out(vty, + " OSPF packet %s receive%s debugging is on\n", + lookup_msg(ospf_packet_type_str, i + 1, + NULL), + IS_DEBUG_OSPF_PACKET(i, DETAIL) + ? " detail" + : ""); + } + + /* Show debug status for OSPF LSAs. */ + if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) + vty_out(vty, " OSPF LSA debugging is on\n"); + else { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + vty_out(vty, " OSPF LSA generation debugging is on\n"); + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + vty_out(vty, " OSPF LSA flooding debugging is on\n"); + if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) + vty_out(vty, " OSPF LSA install debugging is on\n"); + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + vty_out(vty, " OSPF LSA refresh debugging is on\n"); + } + + /* Show debug status for Zebra. */ + if (IS_DEBUG_OSPF(zebra, ZEBRA) == OSPF_DEBUG_ZEBRA) + vty_out(vty, " OSPF Zebra debugging is on\n"); + else { + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + vty_out(vty, + " OSPF Zebra interface debugging is on\n"); + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + vty_out(vty, + " OSPF Zebra redistribute debugging is on\n"); + } + + if (IS_DEBUG_OSPF(defaultinfo, DEFAULTINFO) == OSPF_DEBUG_DEFAULTINFO) + vty_out(vty, " OSPF default information is on\n"); + + /* Show debug status for NSSA. */ + if (IS_DEBUG_OSPF(nssa, NSSA) == OSPF_DEBUG_NSSA) + vty_out(vty, " OSPF NSSA debugging is on\n"); + + /* Show debug status for LDP-SYNC. */ + if (IS_DEBUG_OSPF(ldp_sync, LDP_SYNC) == OSPF_DEBUG_LDP_SYNC) + vty_out(vty, " OSPF ldp-sync debugging is on\n"); + + /* Show debug status for GR. */ + if (IS_DEBUG_OSPF(gr, GR) == OSPF_DEBUG_GR) + vty_out(vty, " OSPF Graceful Restart debugging is on\n"); + + /* Show debug status for TE */ + if (IS_DEBUG_OSPF(te, TE) == OSPF_DEBUG_TE) + vty_out(vty, " OSPF TE debugging is on\n"); + + /* Show debug status for SR */ + if (IS_DEBUG_OSPF(sr, SR) == OSPF_DEBUG_SR) + vty_out(vty, " OSPF SR debugging is on\n"); + + /* Show debug status for TI-LFA */ + if (IS_DEBUG_OSPF(ti_lfa, TI_LFA) == OSPF_DEBUG_TI_LFA) + vty_out(vty, " OSPF TI-LFA debugging is on\n"); + + if (IS_DEBUG_OSPF(bfd, BFD_LIB) == OSPF_DEBUG_BFD_LIB) + vty_out(vty, + " OSPF BFD integration library debugging is on\n"); + + /* Show debug status for LDP-SYNC. */ + if (IS_DEBUG_OSPF(client_api, CLIENT_API) == OSPF_DEBUG_CLIENT_API) + vty_out(vty, " OSPF client-api debugging is on\n"); + + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_ospf, + show_debugging_ospf_cmd, + "show debugging [ospf]", + SHOW_STR + DEBUG_STR + OSPF_STR) +{ + show_debugging_ospf_common(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_ospf_instance, + show_debugging_ospf_instance_cmd, + "show debugging ospf (1-65535)", + SHOW_STR + DEBUG_STR + OSPF_STR + "Instance ID\n") +{ + int idx_number = 3; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + show_debugging_ospf_common(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static int config_write_debug(struct vty *vty); +/* Debug node. */ +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_debug, +}; + +static int config_write_debug(struct vty *vty) +{ + int write = 0; + int i, r; + + const char *type_str[] = {"hello", "dd", "ls-request", "ls-update", + "ls-ack"}; + const char *detail_str[] = { + "", " send", " recv", "", + " detail", " send detail", " recv detail", " detail"}; + + char str[16]; + memset(str, 0, 16); + + if (ospf_instance) + snprintf(str, sizeof(str), " %u", ospf_instance); + + /* debug ospf ism (status|events|timers). */ + if (IS_CONF_DEBUG_OSPF(ism, ISM) == OSPF_DEBUG_ISM) + vty_out(vty, "debug ospf%s ism\n", str); + else { + if (IS_CONF_DEBUG_OSPF(ism, ISM_STATUS)) + vty_out(vty, "debug ospf%s ism status\n", str); + if (IS_CONF_DEBUG_OSPF(ism, ISM_EVENTS)) + vty_out(vty, "debug ospf%s ism event\n", str); + if (IS_CONF_DEBUG_OSPF(ism, ISM_TIMERS)) + vty_out(vty, "debug ospf%s ism timer\n", str); + } + + /* debug ospf nsm (status|events|timers). */ + if (IS_CONF_DEBUG_OSPF(nsm, NSM) == OSPF_DEBUG_NSM) + vty_out(vty, "debug ospf%s nsm\n", str); + else { + if (IS_CONF_DEBUG_OSPF(nsm, NSM_STATUS)) + vty_out(vty, "debug ospf%s nsm status\n", str); + if (IS_CONF_DEBUG_OSPF(nsm, NSM_EVENTS)) + vty_out(vty, "debug ospf%s nsm event\n", str); + if (IS_CONF_DEBUG_OSPF(nsm, NSM_TIMERS)) + vty_out(vty, "debug ospf%s nsm timer\n", str); + } + + /* debug ospf lsa (generate|flooding|install|refresh). */ + if (IS_CONF_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) + vty_out(vty, "debug ospf%s lsa\n", str); + else { + if (IS_CONF_DEBUG_OSPF(lsa, LSA_GENERATE)) + vty_out(vty, "debug ospf%s lsa generate\n", str); + if (IS_CONF_DEBUG_OSPF(lsa, LSA_FLOODING)) + vty_out(vty, "debug ospf%s lsa flooding\n", str); + if (IS_CONF_DEBUG_OSPF(lsa, LSA_INSTALL)) + vty_out(vty, "debug ospf%s lsa install\n", str); + if (IS_CONF_DEBUG_OSPF(lsa, LSA_REFRESH)) + vty_out(vty, "debug ospf%s lsa refresh\n", str); + + write = 1; + } + + /* debug ospf zebra (interface|redistribute). */ + if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA) == OSPF_DEBUG_ZEBRA) + vty_out(vty, "debug ospf%s zebra\n", str); + else { + if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + vty_out(vty, "debug ospf%s zebra interface\n", str); + if (IS_CONF_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + vty_out(vty, "debug ospf%s zebra redistribute\n", str); + + write = 1; + } + + /* debug ospf event. */ + if (IS_CONF_DEBUG_OSPF(event, EVENT) == OSPF_DEBUG_EVENT) { + vty_out(vty, "debug ospf%s event\n", str); + write = 1; + } + + /* debug ospf nssa. */ + if (IS_CONF_DEBUG_OSPF(nssa, NSSA) == OSPF_DEBUG_NSSA) { + vty_out(vty, "debug ospf%s nssa\n", str); + write = 1; + } + + /* debug ospf packet all detail. */ + r = OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL; + for (i = 0; i < 5; i++) + r &= conf_debug_ospf_packet[i] + & (OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL); + if (r == (OSPF_DEBUG_SEND_RECV | OSPF_DEBUG_DETAIL)) { + vty_out(vty, "debug ospf%s packet all detail\n", str); + write = 1; + } + + /* debug ospf packet all. */ + r = OSPF_DEBUG_SEND_RECV; + for (i = 0; i < 5; i++) + r &= conf_debug_ospf_packet[i] & OSPF_DEBUG_SEND_RECV; + if (r == OSPF_DEBUG_SEND_RECV) { + vty_out(vty, "debug ospf%s packet all\n", str); + for (i = 0; i < 5; i++) + if (conf_debug_ospf_packet[i] & OSPF_DEBUG_DETAIL) + vty_out(vty, "debug ospf%s packet %s detail\n", + str, type_str[i]); + write = 1; + } + + /* debug ospf packet (hello|dd|ls-request|ls-update|ls-ack) + (send|recv) (detail). */ + for (i = 0; i < 5; i++) { + if (conf_debug_ospf_packet[i] == 0) + continue; + + vty_out(vty, "debug ospf%s packet %s%s\n", str, type_str[i], + detail_str[conf_debug_ospf_packet[i]]); + write = 1; + } + + /* debug ospf te */ + if (IS_CONF_DEBUG_OSPF(te, TE) == OSPF_DEBUG_TE) { + vty_out(vty, "debug ospf%s te\n", str); + write = 1; + } + + /* debug ospf sr */ + if (IS_CONF_DEBUG_OSPF(sr, SR) == OSPF_DEBUG_SR) { + vty_out(vty, "debug ospf%s sr\n", str); + write = 1; + } + + /* debug ospf sr ti-lfa */ + if (IS_CONF_DEBUG_OSPF(ti_lfa, TI_LFA) == OSPF_DEBUG_TI_LFA) { + vty_out(vty, "debug ospf%s ti-lfa\n", str); + write = 1; + } + + /* debug ospf ldp-sync */ + if (IS_CONF_DEBUG_OSPF(ldp_sync, LDP_SYNC) == OSPF_DEBUG_LDP_SYNC) { + vty_out(vty, "debug ospf%s ldp-sync\n", str); + write = 1; + } + + /* debug ospf gr */ + if (IS_CONF_DEBUG_OSPF(gr, GR) == OSPF_DEBUG_GR) { + vty_out(vty, "debug ospf%s graceful-restart\n", str); + write = 1; + } + + if (IS_CONF_DEBUG_OSPF(bfd, BFD_LIB) == OSPF_DEBUG_BFD_LIB) { + vty_out(vty, "debug ospf%s bfd\n", str); + write = 1; + } + + /* debug ospf client-api */ + if (IS_CONF_DEBUG_OSPF(client_api, CLIENT_API) == + OSPF_DEBUG_CLIENT_API) { + vty_out(vty, "debug ospf%s client-api\n", str); + write = 1; + } + + /* debug ospf default-information */ + if (IS_CONF_DEBUG_OSPF(defaultinfo, DEFAULTINFO) == + OSPF_DEBUG_DEFAULTINFO) { + vty_out(vty, "debug ospf%s default-information\n", str); + write = 1; + } + + return write; +} + +/* Initialize debug commands. */ +void ospf_debug_init(void) +{ + install_node(&debug_node); + + install_element(ENABLE_NODE, &show_debugging_ospf_cmd); + install_element(ENABLE_NODE, &debug_ospf_ism_cmd); + install_element(ENABLE_NODE, &debug_ospf_nsm_cmd); + install_element(ENABLE_NODE, &debug_ospf_lsa_cmd); + install_element(ENABLE_NODE, &debug_ospf_zebra_cmd); + install_element(ENABLE_NODE, &debug_ospf_event_cmd); + install_element(ENABLE_NODE, &debug_ospf_nssa_cmd); + install_element(ENABLE_NODE, &debug_ospf_te_cmd); + install_element(ENABLE_NODE, &debug_ospf_sr_cmd); + install_element(ENABLE_NODE, &debug_ospf_ti_lfa_cmd); + install_element(ENABLE_NODE, &debug_ospf_default_info_cmd); + install_element(ENABLE_NODE, &debug_ospf_ldp_sync_cmd); + install_element(ENABLE_NODE, &debug_ospf_client_api_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_ism_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_nsm_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_lsa_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_zebra_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_event_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_nssa_cmd); + install_element(ENABLE_NODE, &debug_ospf_gr_cmd); + install_element(ENABLE_NODE, &debug_ospf_bfd_cmd); + + install_element(ENABLE_NODE, &show_debugging_ospf_instance_cmd); + install_element(ENABLE_NODE, &debug_ospf_packet_cmd); + + install_element(ENABLE_NODE, &debug_ospf_instance_nsm_cmd); + install_element(ENABLE_NODE, &debug_ospf_instance_lsa_cmd); + install_element(ENABLE_NODE, &debug_ospf_instance_zebra_cmd); + install_element(ENABLE_NODE, &debug_ospf_instance_event_cmd); + install_element(ENABLE_NODE, &debug_ospf_instance_nssa_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_instance_nsm_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_instance_lsa_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_instance_zebra_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_instance_event_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_instance_nssa_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_cmd); + + install_element(CONFIG_NODE, &debug_ospf_packet_cmd); + install_element(CONFIG_NODE, &debug_ospf_ism_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_ism_cmd); + + install_element(CONFIG_NODE, &debug_ospf_nsm_cmd); + install_element(CONFIG_NODE, &debug_ospf_lsa_cmd); + install_element(CONFIG_NODE, &debug_ospf_zebra_cmd); + install_element(CONFIG_NODE, &debug_ospf_event_cmd); + install_element(CONFIG_NODE, &debug_ospf_nssa_cmd); + install_element(CONFIG_NODE, &debug_ospf_te_cmd); + install_element(CONFIG_NODE, &debug_ospf_sr_cmd); + install_element(CONFIG_NODE, &debug_ospf_ti_lfa_cmd); + install_element(CONFIG_NODE, &debug_ospf_default_info_cmd); + install_element(CONFIG_NODE, &debug_ospf_ldp_sync_cmd); + install_element(CONFIG_NODE, &debug_ospf_client_api_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_nsm_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_lsa_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_zebra_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_event_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_nssa_cmd); + install_element(CONFIG_NODE, &debug_ospf_gr_cmd); + install_element(CONFIG_NODE, &debug_ospf_bfd_cmd); + + install_element(CONFIG_NODE, &debug_ospf_instance_nsm_cmd); + install_element(CONFIG_NODE, &debug_ospf_instance_lsa_cmd); + install_element(CONFIG_NODE, &debug_ospf_instance_zebra_cmd); + install_element(CONFIG_NODE, &debug_ospf_instance_event_cmd); + install_element(CONFIG_NODE, &debug_ospf_instance_nssa_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_instance_nsm_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_instance_lsa_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_instance_zebra_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_instance_event_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_instance_nssa_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_cmd); +} diff --git a/ospfd/ospf_dump.h b/ospfd/ospf_dump.h new file mode 100644 index 0000000..0d47be2 --- /dev/null +++ b/ospfd/ospf_dump.h @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd dump routine. + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_DUMP_H +#define _ZEBRA_OSPF_DUMP_H + +/* Debug Flags. */ +#define OSPF_DEBUG_HELLO 0x01 +#define OSPF_DEBUG_DB_DESC 0x02 +#define OSPF_DEBUG_LS_REQ 0x04 +#define OSPF_DEBUG_LS_UPD 0x08 +#define OSPF_DEBUG_LS_ACK 0x10 +#define OSPF_DEBUG_ALL 0x1f + +#define OSPF_DEBUG_SEND 0x01 +#define OSPF_DEBUG_RECV 0x02 +#define OSPF_DEBUG_SEND_RECV 0x03 +#define OSPF_DEBUG_DETAIL 0x04 + +#define OSPF_DEBUG_ISM_STATUS 0x01 +#define OSPF_DEBUG_ISM_EVENTS 0x02 +#define OSPF_DEBUG_ISM_TIMERS 0x04 +#define OSPF_DEBUG_ISM 0x07 +#define OSPF_DEBUG_NSM_STATUS 0x01 +#define OSPF_DEBUG_NSM_EVENTS 0x02 +#define OSPF_DEBUG_NSM_TIMERS 0x04 +#define OSPF_DEBUG_NSM 0x07 + +#define OSPF_DEBUG_LSA_GENERATE 0x01 +#define OSPF_DEBUG_LSA_FLOODING 0x02 +#define OSPF_DEBUG_LSA_INSTALL 0x04 +#define OSPF_DEBUG_LSA_REFRESH 0x08 +#define OSPF_DEBUG_LSA 0x0F +#define OSPF_DEBUG_EXTNL_LSA_AGGR 0x10 + +#define OSPF_DEBUG_ZEBRA_INTERFACE 0x01 +#define OSPF_DEBUG_ZEBRA_REDISTRIBUTE 0x02 +#define OSPF_DEBUG_ZEBRA 0x03 + +#define OSPF_DEBUG_EVENT 0x01 +#define OSPF_DEBUG_NSSA 0x02 +#define OSPF_DEBUG_TE 0x04 +#define OSPF_DEBUG_EXT 0x08 +#define OSPF_DEBUG_SR 0x10 +#define OSPF_DEBUG_TI_LFA 0x11 +#define OSPF_DEBUG_DEFAULTINFO 0x20 +#define OSPF_DEBUG_LDP_SYNC 0x40 + +#define OSPF_DEBUG_GR 0x01 + +#define OSPF_DEBUG_BFD_LIB 0x01 + +#define OSPF_DEBUG_CLIENT_API 0x01 + +/* Macro for setting debug option. */ +#define CONF_DEBUG_PACKET_ON(a, b) conf_debug_ospf_packet[a] |= (b) +#define CONF_DEBUG_PACKET_OFF(a, b) conf_debug_ospf_packet[a] &= ~(b) +#define TERM_DEBUG_PACKET_ON(a, b) term_debug_ospf_packet[a] |= (b) +#define TERM_DEBUG_PACKET_OFF(a, b) term_debug_ospf_packet[a] &= ~(b) +#define DEBUG_PACKET_ON(a, b) \ + do { \ + CONF_DEBUG_PACKET_ON(a, b); \ + TERM_DEBUG_PACKET_ON(a, b); \ + } while (0) +#define DEBUG_PACKET_OFF(a, b) \ + do { \ + CONF_DEBUG_PACKET_OFF(a, b); \ + TERM_DEBUG_PACKET_OFF(a, b); \ + } while (0) + +#define CONF_DEBUG_ON(a, b) conf_debug_ospf_ ## a |= (OSPF_DEBUG_ ## b) +#define CONF_DEBUG_OFF(a, b) conf_debug_ospf_ ## a &= ~(OSPF_DEBUG_ ## b) +#define TERM_DEBUG_ON(a, b) term_debug_ospf_ ## a |= (OSPF_DEBUG_ ## b) +#define TERM_DEBUG_OFF(a, b) term_debug_ospf_ ## a &= ~(OSPF_DEBUG_ ## b) +#define DEBUG_ON(a, b) \ + do { \ + CONF_DEBUG_ON(a, b); \ + TERM_DEBUG_ON(a, b); \ + } while (0) +#define DEBUG_OFF(a, b) \ + do { \ + CONF_DEBUG_OFF(a, b); \ + TERM_DEBUG_OFF(a, b); \ + } while (0) + +/* Macro for checking debug option. */ +#define IS_DEBUG_OSPF_PACKET(a, b) (term_debug_ospf_packet[a] & OSPF_DEBUG_##b) +#define IS_DEBUG_OSPF(a, b) (term_debug_ospf_##a & OSPF_DEBUG_##b) +#define IS_DEBUG_OSPF_EVENT IS_DEBUG_OSPF(event, EVENT) + +#define IS_DEBUG_OSPF_NSSA IS_DEBUG_OSPF(nssa, NSSA) + +#define IS_DEBUG_OSPF_TE IS_DEBUG_OSPF(te, TE) + +#define IS_DEBUG_OSPF_EXT IS_DEBUG_OSPF(ext, EXT) + +#define IS_DEBUG_OSPF_SR IS_DEBUG_OSPF(sr, SR) + +#define IS_DEBUG_OSPF_TI_LFA IS_DEBUG_OSPF(ti_lfa, TI_LFA) + +#define IS_DEBUG_OSPF_DEFAULT_INFO IS_DEBUG_OSPF(defaultinfo, DEFAULTINFO) + +#define IS_DEBUG_OSPF_LDP_SYNC IS_DEBUG_OSPF(ldp_sync, LDP_SYNC) +#define IS_DEBUG_OSPF_GR IS_DEBUG_OSPF(gr, GR) +#define IS_DEBUG_OSPF_CLIENT_API IS_DEBUG_OSPF(client_api, CLIENT_API) + +#define IS_CONF_DEBUG_OSPF_PACKET(a, b) \ + (conf_debug_ospf_packet[a] & OSPF_DEBUG_##b) +#define IS_CONF_DEBUG_OSPF(a, b) (conf_debug_ospf_##a & OSPF_DEBUG_##b) + +#define AREA_NAME(A) ospf_area_name_string ((A)) +#define IF_NAME(I) ospf_if_name_string ((I)) + +/* Extern debug flag. */ +extern unsigned long term_debug_ospf_packet[]; +extern unsigned long term_debug_ospf_event; +extern unsigned long term_debug_ospf_ism; +extern unsigned long term_debug_ospf_nsm; +extern unsigned long term_debug_ospf_lsa; +extern unsigned long term_debug_ospf_zebra; +extern unsigned long term_debug_ospf_nssa; +extern unsigned long term_debug_ospf_te; +extern unsigned long term_debug_ospf_ext; +extern unsigned long term_debug_ospf_sr; +extern unsigned long term_debug_ospf_ti_lfa; +extern unsigned long term_debug_ospf_defaultinfo; +extern unsigned long term_debug_ospf_ldp_sync; +extern unsigned long term_debug_ospf_gr; +extern unsigned long term_debug_ospf_bfd; +extern unsigned long term_debug_ospf_client_api; + +/* Message Strings. */ +extern char *ospf_lsa_type_str[]; + +/* Prototypes. */ +extern const char *ospf_area_name_string(struct ospf_area *area); +extern const char *ospf_area_desc_string(struct ospf_area *area); +extern const char *ospf_if_name_string(struct ospf_interface *oip); +extern int ospf_nbr_ism_state(struct ospf_neighbor *nbr); +extern void ospf_nbr_ism_state_message(struct ospf_neighbor *nbr, char *buf, + size_t size); +extern const char *ospf_timer_dump(struct event *e, char *buf, size_t size); +extern const char *ospf_timeval_dump(struct timeval *t, char *buf, size_t size); +extern void ospf_packet_dump(struct stream *s); +extern void ospf_debug_init(void); + +/* Appropriate buffer size to use with ospf_timer_dump and ospf_timeval_dump: */ +#define OSPF_TIME_DUMP_SIZE 16 + +#endif /* _ZEBRA_OSPF_DUMP_H */ diff --git a/ospfd/ospf_dump_api.c b/ospfd/ospf_dump_api.c new file mode 100644 index 0000000..8400310 --- /dev/null +++ b/ospfd/ospf_dump_api.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd dump routine (parts used by ospfclient). + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "log.h" +#include "prefix.h" + +#include "ospf_dump_api.h" +#include "ospfd.h" +#include "ospf_asbr.h" +#include "ospf_lsa.h" +#include "ospf_nsm.h" +#include "ospf_ism.h" + +const struct message ospf_ism_state_msg[] = { + {ISM_DependUpon, "DependUpon"}, + {ISM_Down, "Down"}, + {ISM_Loopback, "Loopback"}, + {ISM_Waiting, "Waiting"}, + {ISM_PointToPoint, "Point-To-Point"}, + {ISM_DROther, "DROther"}, + {ISM_Backup, "Backup"}, + {ISM_DR, "DR"}, + {0}}; + +const struct message ospf_nsm_state_msg[] = {{NSM_DependUpon, "DependUpon"}, + {NSM_Deleted, "Deleted"}, + {NSM_Down, "Down"}, + {NSM_Attempt, "Attempt"}, + {NSM_Init, "Init"}, + {NSM_TwoWay, "2-Way"}, + {NSM_ExStart, "ExStart"}, + {NSM_Exchange, "Exchange"}, + {NSM_Loading, "Loading"}, + {NSM_Full, "Full"}, + {0}}; + +const struct message ospf_lsa_type_msg[] = { + {OSPF_UNKNOWN_LSA, "unknown"}, + {OSPF_ROUTER_LSA, "router-LSA"}, + {OSPF_NETWORK_LSA, "network-LSA"}, + {OSPF_SUMMARY_LSA, "summary-LSA"}, + {OSPF_ASBR_SUMMARY_LSA, "summary-LSA"}, + {OSPF_AS_EXTERNAL_LSA, "AS-external-LSA"}, + {OSPF_GROUP_MEMBER_LSA, "GROUP MEMBER LSA"}, + {OSPF_AS_NSSA_LSA, "NSSA-LSA"}, + {8, "Type-8 LSA"}, + {OSPF_OPAQUE_LINK_LSA, "Link-Local Opaque-LSA"}, + {OSPF_OPAQUE_AREA_LSA, "Area-Local Opaque-LSA"}, + {OSPF_OPAQUE_AS_LSA, "AS-external Opaque-LSA"}, + {0}}; + +const struct message ospf_link_state_id_type_msg[] = { + {OSPF_UNKNOWN_LSA, "(unknown)"}, + {OSPF_ROUTER_LSA, ""}, + {OSPF_NETWORK_LSA, "(address of Designated Router)"}, + {OSPF_SUMMARY_LSA, "(summary Network Number)"}, + {OSPF_ASBR_SUMMARY_LSA, "(AS Boundary Router address)"}, + {OSPF_AS_EXTERNAL_LSA, "(External Network Number)"}, + {OSPF_GROUP_MEMBER_LSA, "(Group membership information)"}, + {OSPF_AS_NSSA_LSA, "(External Network Number for NSSA)"}, + {8, "(Type-8 LSID)"}, + {OSPF_OPAQUE_LINK_LSA, "(Link-Local Opaque-Type/ID)"}, + {OSPF_OPAQUE_AREA_LSA, "(Area-Local Opaque-Type/ID)"}, + {OSPF_OPAQUE_AS_LSA, "(AS-external Opaque-Type/ID)"}, + {0}}; + +const struct message ospf_network_type_msg[] = { + {OSPF_IFTYPE_NONE, "NONE"}, + {OSPF_IFTYPE_POINTOPOINT, "Point-to-Point"}, + {OSPF_IFTYPE_BROADCAST, "Broadcast"}, + {OSPF_IFTYPE_NBMA, "NBMA"}, + {OSPF_IFTYPE_POINTOMULTIPOINT, "Point-to-MultiPoint"}, + {OSPF_IFTYPE_VIRTUALLINK, "Virtual-Link"}, + {0}}; + +/* AuType */ +const struct message ospf_auth_type_str[] = { + {OSPF_AUTH_NULL, "Null"}, + {OSPF_AUTH_SIMPLE, "Simple"}, + {OSPF_AUTH_CRYPTOGRAPHIC, "Cryptographic"}, + {0}}; + +#define OSPF_OPTION_STR_MAXLEN 24 + +char *ospf_options_dump(uint8_t options) +{ + static char buf[OSPF_OPTION_STR_MAXLEN]; + + snprintf(buf, sizeof(buf), "*|%s|%s|%s|%s|%s|%s|%s", + (options & OSPF_OPTION_O) ? "O" : "-", + (options & OSPF_OPTION_DC) ? "DC" : "-", + (options & OSPF_OPTION_EA) ? "EA" : "-", + (options & OSPF_OPTION_NP) ? "N/P" : "-", + (options & OSPF_OPTION_MC) ? "MC" : "-", + (options & OSPF_OPTION_E) ? "E" : "-", + (options & OSPF_OPTION_MT) ? "M/T" : "-"); + + return buf; +} + +void ospf_lsa_header_dump(struct lsa_header *lsah) +{ + const char *lsah_type = lookup_msg(ospf_lsa_type_msg, lsah->type, NULL); + + zlog_debug(" LSA Header"); + zlog_debug(" LS age %d", ntohs(lsah->ls_age)); + zlog_debug(" Options %d (%s)", lsah->options, + ospf_options_dump(lsah->options)); + zlog_debug(" LS type %d (%s)", lsah->type, + (lsah->type ? lsah_type : "unknown type")); + zlog_debug(" Link State ID %pI4", &lsah->id); + zlog_debug(" Advertising Router %pI4", &lsah->adv_router); + zlog_debug(" LS sequence number 0x%lx", + (unsigned long)ntohl(lsah->ls_seqnum)); + zlog_debug(" LS checksum 0x%x", ntohs(lsah->checksum)); + zlog_debug(" length %d", ntohs(lsah->length)); +} diff --git a/ospfd/ospf_dump_api.h b/ospfd/ospf_dump_api.h new file mode 100644 index 0000000..34da113 --- /dev/null +++ b/ospfd/ospf_dump_api.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd dump routine (parts used by ospfclient). + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_DUMP_API_H +#define _ZEBRA_OSPF_DUMP_API_H + +#include "log.h" +struct lsa_header; + +extern const struct message ospf_ism_state_msg[]; +extern const struct message ospf_nsm_state_msg[]; +extern const struct message ospf_lsa_type_msg[]; +extern const struct message ospf_link_state_id_type_msg[]; +extern const struct message ospf_network_type_msg[]; +extern const struct message ospf_auth_type_str[]; +extern const int ospf_ism_state_msg_max; +extern const int ospf_nsm_state_msg_max; +extern const int ospf_lsa_type_msg_max; +extern const int ospf_link_state_id_type_msg_max; +extern const int ospf_network_type_msg_max; +extern const size_t ospf_auth_type_str_max; + +extern char *ospf_options_dump(uint8_t); +extern void ospf_lsa_header_dump(struct lsa_header *); + +#endif /* _ZEBRA_OSPF_DUMP_API_H */ diff --git a/ospfd/ospf_errors.c b/ospfd/ospf_errors.c new file mode 100644 index 0000000..cf58c6a --- /dev/null +++ b/ospfd/ospf_errors.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Chirag Shah + */ + +#include + +#include "lib/ferr.h" +#include "ospf_errors.h" + +/* clang-format off */ +static struct log_ref ferr_ospf_warn[] = { + { + .code = EC_OSPF_SET_METRIC_PLUS, + .title = "OSPF does not support `set metric +rtt/-rtt`", + .description = "This implementation of OSPF does not currently support `set metric +rtt/-rtt`", + .suggestion = "Do not use this particular set command for an ospf route-map", + }, + { + .code = EC_OSPF_AUTH, + .title = "OSPF has noticed an authentication issue", + .description = "Something has gone wrong with the calculation of the authentication data", + .suggestion = "Ensure your key is correct, gather log data from this side as well as peer and open an Issue", + }, + { + .code = EC_OSPF_PACKET, + .title = "OSPF has detected packet information mismatch", + .description = "OSPF has detected that packet information received is incorrect", + .suggestion = "Ensure interface configuration is correct, gather log files from here and the peer and open an Issue", + }, + { + .code = EC_OSPF_LARGE_LSA, + .title = "OSPF has determined that a LSA packet will be too large", + .description = "OSPF has received data that is causing it to recalculate how large packets should be and has discovered that the packet size needed would be too large and we will need to fragment packets", + .suggestion = "Further divide up your network with areas", + }, + { + .code = EC_OSPF_LSA_UNEXPECTED, + .title = "OSPF has received a LSA-type that it was not expecting", + .description = "OSPF has received a LSA-type that it was not expecting during processing", + .suggestion = "Gather log data from this machine and it's peer and open an Issue", + }, + { + .code = EC_OSPF_LSA, + .title = "OSPF has discovered inconsistent internal state for a LSA", + .description = "During handling of a LSA, OSPF has discovered that the LSA's internal state is inconsistent", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_OSPF_OPAQUE_REGISTRATION, + .title = "OSPF has failed to properly register Opaque Handler", + .description = "During initialization OSPF has detected a failure to install an opaque handler", + .suggestion = "Gather log data and open an Issue", + }, + { + .code = EC_OSPF_TE_UNEXPECTED, + .title = "OSPF has received TE information that it was not expecting", + .description = "OSPF has received TE information that it was not expecting during normal processing of data", + .suggestion = "Gather log data from this machine and it's peer and open an Issue", + }, + { + .code = EC_OSPF_LSA_INSTALL_FAILURE, + .title = "OSPF was unable to save the LSA into it's database", + .description = "During processing of a new lsa and attempting to save the lsa into the OSPF database, this process failed.", + .suggestion = "Gather log data from this machine and open an Issue", + }, + { + .code = EC_OSPF_LSA_NULL, + .title = "OSPF has received a NULL lsa pointer", + .description = "When processing a LSA update we have found and noticed an internal error where we are passing around a NULL pointer", + .suggestion = "Gather data from this machine and it's peer and open an Issue", + }, + { + .code = EC_OSPF_EXT_LSA_UNEXPECTED, + .title = "OSPF has received EXT information that leaves it in an unexpected state", + .description = "While processing EXT LSA information, OSPF has noticed that the internal state of OSPF has gotten inconsistent", + .suggestion = "Gather data from this machine and it's peer and open an Issue", + }, + { + .code = EC_OSPF_LSA_MISSING, + .title = "OSPF attempted to look up a LSA and it was not found", + .description = "During processing of new LSA information, we attempted to look up an old LSA and it was not found", + .suggestion = "Gather data from this machine and open an Issue", + }, + { + .code = EC_OSPF_PTP_NEIGHBOR, + .title = "OSPF has detected more than 1 neighbor on a P2P interface", + .description = "If you configure a ospf interface as P2P we should not detect more than one neighbor on the interface", + .suggestion = "Fix your config", + }, + { + .code = EC_OSPF_LSA_SIZE, + .title = "OSPF has detected an invalid LSA size", + .description = "OSPF has detected a state where we are attempting to grow a LSA but the LSA has reached it's maximum size", + .suggestion = "Gather data and open an Issue", + }, + { + .code = END_FERR, + } +}; + +static struct log_ref ferr_ospf_err[] = { + { + .code = EC_OSPF_PKT_PROCESS, + .title = "Failure to process a packet", + .description = "OSPF attempted to process a received packet but could not", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_ROUTER_LSA_MISMATCH, + .title = "Failure to process Router LSA", + .description = "OSPF attempted to process a Router LSA but Advertising ID mismatch with link id", + .suggestion = "Check OSPF network config for any config issue, If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_DOMAIN_CORRUPT, + .title = "OSPF Domain Corruption", + .description = "OSPF attempted to process a Router LSA but Advertising ID mismatch with link id", + .suggestion = "Check OSPF network Database for corrupted LSA, If the problem persists, shutdown OSPF domain and report the problem for troubleshooting" + }, + { + .code = EC_OSPF_INIT_FAIL, + .title = "OSPF Initialization failure", + .description = "OSPF failed to initialized OSPF default insance", + .suggestion = "Ensure there is adequate memory on the device. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_SR_INVALID_DB, + .title = "OSPF SR Invalid DB", + .description = "OSPF Segment Routing Database is invalid", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_SR_NODE_CREATE, + .title = "OSPF SR hash node creation failed", + .description = "OSPF Segment Routing node creation failed", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_SR_INVALID_LSA_ID, + .title = "OSPF SR Invalid LSA ID", + .description = "OSPF Segment Routing invalid lsa id", + .suggestion = "Restart OSPF instance, If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_SR_SID_OVERFLOW, + .title = "OSPF SR Segment-ID overflow", + .description = "OSPF Segment Routing ID index or label exceed Global or Local Block Range", + .suggestion = "Restart OSPF instance, If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_INVALID_ALGORITHM, + .title = "OSPF SR Invalid Algorithm", + .description = "OSPF Segment Routing invalid Algorithm", + .suggestion = "Most likely a bug. If the problem persists, report the problem for troubleshooting" + }, + { + .code = EC_OSPF_FSM_INVALID_STATE, + .title = "OSPF FSM invalid state detected", + .description = "OSPF has attempted to change states when it should not be able to", + .suggestion = "Gather log files and open an issue", + }, + { + .code = EC_OSPF_LARGE_HELLO, + .title = "OSPF Encountered a Large Hello", + .description = "OSPF attempted to send a Hello larger than MTU but did not", + .suggestion = "Too many neighbors configured on a single interface. Suggestion is to decrease the number of neighbors on a single interface/subnet" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void ospf_error_init(void) +{ + log_ref_add(ferr_ospf_warn); + log_ref_add(ferr_ospf_err); +} diff --git a/ospfd/ospf_errors.h b/ospfd/ospf_errors.h new file mode 100644 index 0000000..e63cb74 --- /dev/null +++ b/ospfd/ospf_errors.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Chirag Shah + */ + +#ifndef __OSPF_ERRORS_H__ +#define __OSPF_ERRORS_H__ + +#include "lib/ferr.h" + +enum ospf_log_refs { + EC_OSPF_PKT_PROCESS = OSPF_FERR_START, + EC_OSPF_ROUTER_LSA_MISMATCH, + EC_OSPF_DOMAIN_CORRUPT, + EC_OSPF_INIT_FAIL, + EC_OSPF_SR_INVALID_DB, + EC_OSPF_SR_NODE_CREATE, + EC_OSPF_SR_INVALID_LSA_ID, + EC_OSPF_SR_SID_OVERFLOW, + EC_OSPF_INVALID_ALGORITHM, + EC_OSPF_FSM_INVALID_STATE, + EC_OSPF_SET_METRIC_PLUS, + EC_OSPF_AUTH, + EC_OSPF_PACKET, + EC_OSPF_LARGE_LSA, + EC_OSPF_LSA_UNEXPECTED, + EC_OSPF_LSA, + EC_OSPF_OPAQUE_REGISTRATION, + EC_OSPF_TE_UNEXPECTED, + EC_OSPF_LSA_INSTALL_FAILURE, + EC_OSPF_LSA_NULL, + EC_OSPF_EXT_LSA_UNEXPECTED, + EC_OSPF_LSA_MISSING, + EC_OSPF_PTP_NEIGHBOR, + EC_OSPF_LSA_SIZE, + EC_OSPF_LARGE_HELLO, +}; + +extern void ospf_error_init(void); + +#endif diff --git a/ospfd/ospf_ext.c b/ospfd/ospf_ext.c new file mode 100644 index 0000000..df0b3b9 --- /dev/null +++ b/ospfd/ospf_ext.c @@ -0,0 +1,2065 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute + * Advertisement + * + * Module name: Extended Prefix/Link Opaque LSA + * + * Author: Olivier Dugeon + * Author: Anselme Sawadogo + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + */ + +#include +#include +#include +#include + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "network.h" +#include "if.h" +#include "libospf.h" /* for ospf interface types */ +#include + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_errors.h" + +/* Following structure are internal use only. */ + +/* + * Global variable to manage Extended Prefix/Link Opaque LSA on this node. + * Note that all parameter values are stored in network byte order. + */ +static struct ospf_ext_lp OspfEXT; + +/* + * ----------------------------------------------------------------------- + * Following are initialize/terminate functions for Extended Prefix/Link + * Opaque LSA handling. + * ----------------------------------------------------------------------- + */ + +/* Extended Prefix Opaque LSA related callback functions */ +static void ospf_ext_pref_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); +static int ospf_ext_pref_lsa_originate(void *arg); +static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa); +static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode); +/* Extended Link Opaque LSA related callback functions */ +static int ospf_ext_link_new_if(struct interface *ifp); +static int ospf_ext_link_del_if(struct interface *ifp); +static void ospf_ext_ism_change(struct ospf_interface *oi, int old_status); +static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status); +static void ospf_ext_link_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); +static int ospf_ext_link_lsa_originate(void *arg); +static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa); +static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode); +static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op); +static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa); +static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa); +static void ospf_ext_link_delete_adj_sid(struct ext_itf *exti); +static void del_ext_info(void *val); + +/* + * Extended Link/Prefix initialization + * + * @param - none + * + * @return - 0 if OK, <> 0 otherwise + */ +int ospf_ext_init(void) +{ + int rc = 0; + + memset(&OspfEXT, 0, sizeof(OspfEXT)); + OspfEXT.enabled = false; + /* Only Area flooding is supported yet */ + OspfEXT.scope = OSPF_OPAQUE_AREA_LSA; + /* Initialize interface list */ + OspfEXT.iflist = list_new(); + OspfEXT.iflist->del = del_ext_info; + + zlog_info("EXT (%s): Register Extended Link Opaque LSA", __func__); + rc = ospf_register_opaque_functab( + OSPF_OPAQUE_AREA_LSA, OPAQUE_TYPE_EXTENDED_LINK_LSA, + ospf_ext_link_new_if, /* new if */ + ospf_ext_link_del_if, /* del if */ + ospf_ext_ism_change, /* ism change */ + ospf_ext_link_nsm_change, /* nsm change */ + NULL, /* Write router config. */ + NULL, /* Write interface conf. */ + NULL, /* Write debug config. */ + ospf_ext_link_show_info, /* Show LSA info */ + ospf_ext_link_lsa_originate, /* Originate LSA */ + ospf_ext_link_lsa_refresh, /* Refresh LSA */ + ospf_ext_link_lsa_update, /* new_lsa_hook */ + NULL); /* del_lsa_hook */ + + if (rc != 0) { + flog_warn(EC_OSPF_OPAQUE_REGISTRATION, + "EXT (%s): Failed to register Extended Link LSA", + __func__); + return rc; + } + + zlog_info("EXT (%s): Register Extended Prefix Opaque LSA", __func__); + rc = ospf_register_opaque_functab( + OspfEXT.scope, OPAQUE_TYPE_EXTENDED_PREFIX_LSA, + NULL, /* new if handle by link */ + NULL, /* del if handle by link */ + NULL, /* ism change */ + NULL, /* nsm change */ + ospf_sr_config_write_router, /* Write router config. */ + NULL, /* Write interface conf. */ + NULL, /* Write debug config. */ + ospf_ext_pref_show_info, /* Show LSA info */ + ospf_ext_pref_lsa_originate, /* Originate LSA */ + ospf_ext_pref_lsa_refresh, /* Refresh LSA */ + ospf_ext_pref_lsa_update, /* new_lsa_hook */ + NULL); /* del_lsa_hook */ + if (rc != 0) { + flog_warn(EC_OSPF_OPAQUE_REGISTRATION, + "EXT (%s): Failed to register Extended Prefix LSA", + __func__); + return rc; + } + + return rc; +} + +/* + * Extended Link/Prefix termination function + * + * @param - none + * @return - none + */ +void ospf_ext_term(void) +{ + + if ((OspfEXT.scope == OSPF_OPAQUE_AREA_LSA) + || (OspfEXT.scope == OSPF_OPAQUE_AS_LSA)) + ospf_delete_opaque_functab(OspfEXT.scope, + OPAQUE_TYPE_EXTENDED_PREFIX_LSA); + + ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_LINK_LSA); + + list_delete(&OspfEXT.iflist); + OspfEXT.scope = 0; + OspfEXT.enabled = false; + + return; +} + +/* + * Extended Link/Prefix finish function + * + * @param - none + * @return - none + */ +void ospf_ext_finish(void) +{ + + struct listnode *node; + struct ext_itf *exti; + + /* Flush Router Info LSA */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_lsa_schedule(exti, FLUSH_THIS_LSA); + + OspfEXT.enabled = false; +} + +/* + * --------------------------------------------------------------------- + * Following are control functions for Extended Prefix/Link Opaque LSA + * parameters management. + * --------------------------------------------------------------------- + */ + +/* Functions to free memory space */ +static void del_ext_info(void *val) +{ + XFREE(MTYPE_OSPF_EXT_PARAMS, val); +} + +/* Increment instance value for Extended Prefix Opaque LSAs Opaque ID field */ +static uint32_t get_ext_pref_instance_value(void) +{ + static uint32_t seqno = 0; + + if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) + seqno += 1; + else + seqno = 1; /* Avoid zero. */ + + return seqno; +} + +/* Increment instance value for Extended Link Opaque LSAs Opaque ID field */ +static uint32_t get_ext_link_instance_value(void) +{ + static uint32_t seqno = 0; + + if (seqno < MAX_LEGAL_EXT_INSTANCE_NUM) + seqno += 1; + else + seqno = 1; /* Avoid zero. */ + + return seqno; +} + +/* Lookup Extended Prefix/Links by ifp from OspfEXT struct iflist */ +static struct ext_itf *lookup_ext_by_ifp(struct interface *ifp) +{ + struct listnode *node; + struct ext_itf *exti; + + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if (exti->ifp == ifp) + return exti; + + return NULL; +} + +/* Lookup Extended Prefix/Links by LSA ID from OspfEXT struct iflist */ +static struct ext_itf *lookup_ext_by_instance(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct ext_itf *exti; + uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); + uint8_t type = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + if ((exti->instance == key) && (exti->type == type)) + return exti; + + return NULL; +} + +/* + * ---------------------------------------------------------------------- + * The underlying subsection defines setters and unsetters to create and + * delete tlvs and subtlvs + * ---------------------------------------------------------------------- + */ + +/* Extended Prefix TLV - RFC7684 section 2.1 */ +static void set_ext_prefix(struct ext_itf *exti, uint8_t route_type, + uint8_t flags, struct prefix_ipv4 p) +{ + + TLV_TYPE(exti->prefix) = htons(EXT_TLV_PREFIX); + /* Warning: Size must be adjust depending of subTLV's */ + TLV_LEN(exti->prefix) = htons(EXT_TLV_PREFIX_SIZE); + exti->prefix.route_type = route_type; + exti->prefix.flags = flags; + /* Only Address Family Ipv4 (0) is defined in RFC 7684 */ + exti->prefix.af = 0; + exti->prefix.pref_length = p.prefixlen; + exti->prefix.address = p.prefix; + + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); +} + +/* Extended Link TLV - RFC7684 section 3.1 */ +static void set_ext_link(struct ext_itf *exti, uint8_t type, struct in_addr id, + struct in_addr data) +{ + + TLV_TYPE(exti->link) = htons(EXT_TLV_LINK); + /* Warning: Size must be adjust depending of subTLV's */ + TLV_LEN(exti->link) = htons(EXT_TLV_LINK_SIZE); + exti->link.link_type = type; + exti->link.link_id = id; + exti->link.link_data = data; +} + +/* Prefix SID SubTLV - section 5 */ +static void set_prefix_sid(struct ext_itf *exti, uint8_t algorithm, + uint32_t value, int value_type, uint8_t flags) +{ + + if ((algorithm != SR_ALGORITHM_SPF) + && (algorithm != SR_ALGORITHM_STRICT_SPF)) { + flog_err(EC_OSPF_INVALID_ALGORITHM, + "EXT (%s): unrecognized algorithm, not SPF or S-SPF", + __func__); + return; + } + + /* Update flags according to the type of value field: label or index */ + if (value_type == SID_LABEL) + SET_FLAG(flags, EXT_SUBTLV_PREFIX_SID_VFLG); + + /* set prefix sid subtlv for an extended prefix tlv */ + TLV_TYPE(exti->node_sid) = htons(EXT_SUBTLV_PREFIX_SID); + exti->node_sid.algorithm = algorithm; + exti->node_sid.flags = flags; + exti->node_sid.mtid = 0; /* Multi-Topology is not supported */ + + /* Set Label or Index value */ + if (value_type == SID_LABEL) { + TLV_LEN(exti->node_sid) = + htons(SID_LABEL_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE)); + exti->node_sid.value = htonl(SET_LABEL(value)); + } else { + TLV_LEN(exti->node_sid) = + htons(SID_INDEX_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE)); + exti->node_sid.value = htonl(value); + } +} + +/* Adjacency SID SubTLV - section 6.1 */ +static void set_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, + int value_type) +{ + int index; + uint8_t flags; + + /* Determine which ADJ_SID must be set: nominal or backup */ + if (backup) { + flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; + index = 1; + } else { + index = 0; + flags = 0; + } + + /* Set Header */ + TLV_TYPE(exti->adj_sid[index]) = htons(EXT_SUBTLV_ADJ_SID); + + /* Only Local ADJ-SID is supported for the moment */ + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); + + exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ + + /* Adjust Length, Flags and Value depending on the type of Label */ + if (value_type == SID_LABEL) { + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->adj_sid[index]) = + htons(SID_LABEL_SIZE(EXT_SUBTLV_ADJ_SID_SIZE)); + exti->adj_sid[index].value = htonl(SET_LABEL(value)); + } else { + UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->adj_sid[index]) = + htons(SID_INDEX_SIZE(EXT_SUBTLV_ADJ_SID_SIZE)); + exti->adj_sid[index].value = htonl(value); + } + + exti->adj_sid[index].flags = flags; /* Set computed flags */ + exti->adj_sid[index].mtid = 0; /* Multi-Topology is not supported */ + exti->adj_sid[index].weight = 0; /* Load-Balancing is not supported */ +} + +/* LAN Adjacency SID SubTLV - section 6.2 */ +static void set_lan_adj_sid(struct ext_itf *exti, bool backup, uint32_t value, + int value_type, struct in_addr neighbor_id) +{ + + int index; + uint8_t flags; + + /* Determine which ADJ_SID must be set: nominal or backup */ + if (backup) { + flags = EXT_SUBTLV_LINK_ADJ_SID_BFLG; + index = 1; + } else { + index = 0; + flags = 0; + } + + /* Set Header */ + TLV_TYPE(exti->lan_sid[index]) = htons(EXT_SUBTLV_LAN_ADJ_SID); + + /* Only Local ADJ-SID is supported for the moment */ + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); + + /* Adjust Length, Flags and Value depending on the type of Label */ + if (value_type == SID_LABEL) { + SET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->lan_sid[index]) = + htons(SID_LABEL_SIZE(EXT_SUBTLV_PREFIX_RANGE_SIZE)); + exti->lan_sid[index].value = htonl(SET_LABEL(value)); + } else { + UNSET_FLAG(flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + TLV_LEN(exti->lan_sid[index]) = + htons(SID_INDEX_SIZE(EXT_SUBTLV_PREFIX_RANGE_SIZE)); + exti->lan_sid[index].value = htonl(value); + } + + exti->lan_sid[index].flags = flags; /* Set computed flags */ + exti->lan_sid[index].mtid = 0; /* Multi-Topology is not supported */ + exti->lan_sid[index].weight = 0; /* Load-Balancing is not supported */ + exti->lan_sid[index].neighbor_id = neighbor_id; +} + +static void unset_adjacency_sid(struct ext_itf *exti) +{ + /* Reset Adjacency TLV */ + if (exti->type == ADJ_SID) { + TLV_TYPE(exti->adj_sid[0]) = 0; + TLV_TYPE(exti->adj_sid[1]) = 0; + } + /* or Lan-Adjacency TLV */ + if (exti->type == LAN_ADJ_SID) { + TLV_TYPE(exti->lan_sid[0]) = 0; + TLV_TYPE(exti->lan_sid[1]) = 0; + } +} + +/* Experimental SubTLV from Cisco */ +static void set_rmt_itf_addr(struct ext_itf *exti, struct in_addr rmtif) +{ + + TLV_TYPE(exti->rmt_itf_addr) = htons(EXT_SUBTLV_RMT_ITF_ADDR); + TLV_LEN(exti->rmt_itf_addr) = htons(sizeof(struct in_addr)); + exti->rmt_itf_addr.value = rmtif; +} + +/* Delete Extended LSA */ +static void ospf_extended_lsa_delete(struct ext_itf *exti) +{ + + /* Avoid deleting LSA if Extended is not enable */ + if (!OspfEXT.enabled) + return; + + /* Process only Active Extended Prefix/Link LSA */ + if (!CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) + return; + + osr_debug("EXT (%s): Disable %s%s%s-SID on interface %s", __func__, + exti->stype == LOCAL_SID ? "Prefix" : "", + exti->stype == ADJ_SID ? "Adjacency" : "", + exti->stype == LAN_ADJ_SID ? "LAN-Adjacency" : "", + exti->ifp->name); + + /* Flush LSA if already engaged */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + ospf_ext_lsa_schedule(exti, FLUSH_THIS_LSA); + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + } + + /* De-activate this Extended Prefix/Link and remove corresponding + * Segment-Routing Prefix-SID or (LAN)-ADJ-SID */ + if (exti->stype == ADJ_SID || exti->stype == LAN_ADJ_SID) + ospf_ext_link_delete_adj_sid(exti); + else + ospf_sr_ext_itf_delete(exti); +} + +/* + * Update Extended prefix SID index for Loopback interface type + * + * @param ifname - Loopback interface name + * @param index - new value for the prefix SID of this interface + * @param p - prefix for this interface or NULL if Extended Prefix + * should be remove + * + * @return instance number if update is OK, 0 otherwise + */ +uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, uint32_t index, + struct prefix_ipv4 *p, uint8_t flags) +{ + int rc = 0; + struct ext_itf *exti; + + /* Find Extended Prefix interface */ + exti = lookup_ext_by_ifp(ifp); + if (exti == NULL) + return rc; + + if (p != NULL) { + osr_debug("EXT (%s): Schedule new prefix %pFX with index %u on interface %s", __func__, p, index, ifp->name); + + /* Set first Extended Prefix then the Prefix SID information */ + set_ext_prefix(exti, OSPF_PATH_INTRA_AREA, EXT_TLV_PREF_NFLG, + *p); + set_prefix_sid(exti, SR_ALGORITHM_SPF, index, SID_INDEX, flags); + + /* Try to Schedule LSA */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_pref_lsa_schedule(exti, + REFRESH_THIS_LSA); + else + ospf_ext_pref_lsa_schedule( + exti, REORIGINATE_THIS_LSA); + } + } else { + osr_debug("EXT (%s): Remove prefix for interface %s", __func__, + ifp->name); + + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_pref_lsa_schedule(exti, FLUSH_THIS_LSA); + } + + return SET_OPAQUE_LSID(exti->type, exti->instance); +} + +/** + * Update Adjacecny-SID for Extended Link LSA + * + * @param exti Extended Link information + */ +static void ospf_ext_link_update_adj_sid(struct ext_itf *exti) +{ + mpls_label_t label; + mpls_label_t bck_label; + + /* Process only (LAN)Adjacency-SID Type */ + if (exti->stype != ADJ_SID && exti->stype != LAN_ADJ_SID) + return; + + /* Request Primary & Backup Labels from Label Manager */ + bck_label = ospf_sr_local_block_request_label(); + label = ospf_sr_local_block_request_label(); + if (bck_label == MPLS_INVALID_LABEL || label == MPLS_INVALID_LABEL) { + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_lsa_schedule(exti, FLUSH_THIS_LSA); + return; + } + + /* Set Adjacency-SID, backup first */ + if (exti->stype == ADJ_SID) { + set_adj_sid(exti, true, bck_label, SID_LABEL); + set_adj_sid(exti, false, label, SID_LABEL); + } else { + set_lan_adj_sid(exti, true, bck_label, SID_LABEL, + exti->lan_sid[0].neighbor_id); + set_lan_adj_sid(exti, false, label, SID_LABEL, + exti->lan_sid[1].neighbor_id); + } + + /* Finally, add corresponding SR Link in SRDB & MPLS LFIB */ + SET_FLAG(exti->flags, EXT_LPFLG_FIB_ENTRY_SET); + ospf_sr_ext_itf_add(exti); +} + +/** + * Delete Adjacecny-SID for Extended Link LSA + * + * @param exti Extended Link information + */ +static void ospf_ext_link_delete_adj_sid(struct ext_itf *exti) +{ + /* Process only (LAN)Adjacency-SID Type */ + if (exti->stype != ADJ_SID && exti->stype != LAN_ADJ_SID) + return; + + /* Release Primary & Backup Labels from Label Manager */ + if (exti->stype == ADJ_SID) { + ospf_sr_local_block_release_label(exti->adj_sid[0].value); + ospf_sr_local_block_release_label(exti->adj_sid[1].value); + } else { + ospf_sr_local_block_release_label(exti->lan_sid[0].value); + ospf_sr_local_block_release_label(exti->lan_sid[1].value); + } + /* And reset corresponding TLV */ + unset_adjacency_sid(exti); + + /* Finally, remove corresponding SR Link in SRDB & MPLS LFIB */ + UNSET_FLAG(exti->flags, EXT_LPFLG_FIB_ENTRY_SET); + ospf_sr_ext_itf_delete(exti); +} + +/** + * Update Extended Link LSA once Segment Routing Label Block has been changed. + */ +void ospf_ext_link_srlb_update(void) +{ + struct listnode *node; + struct ext_itf *exti; + + + osr_debug("EXT (%s): Update Extended Links with new SRLB", __func__); + + /* Update all Extended Link Adjaceny-SID */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + /* Skip Extended Prefix */ + if (exti->stype == PREF_SID || exti->stype == LOCAL_SID) + continue; + + /* Skip inactive Extended Link */ + if (!CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) + continue; + + ospf_ext_link_update_adj_sid(exti); + } +} + +/* + * Used by Segment Routing to activate/deactivate Extended Link/Prefix flooding + * + * @param enable To activate or not Segment Routing Extended LSA flooding + * + * @return none + */ +void ospf_ext_update_sr(bool enable) +{ + struct listnode *node; + struct ext_itf *exti; + + osr_debug("EXT (%s): %s Extended LSAs for Segment Routing ", __func__, + enable ? "Enable" : "Disable"); + + if (enable) { + OspfEXT.enabled = true; + + /* Refresh LSAs if already engaged or originate */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + /* Skip Inactive Extended Link */ + if (!CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) + continue; + + /* Update Extended Link (LAN)Adj-SID if not set */ + if (!CHECK_FLAG(exti->flags, EXT_LPFLG_FIB_ENTRY_SET)) + ospf_ext_link_update_adj_sid(exti); + + /* Finally, flood the extended Link */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_lsa_schedule(exti, REFRESH_THIS_LSA); + else + ospf_ext_lsa_schedule(exti, + REORIGINATE_THIS_LSA); + } + } else { + /* Start by Removing Extended LSA */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) + ospf_extended_lsa_delete(exti); + + /* And then disable Extended Link/Prefix */ + OspfEXT.enabled = false; + } +} + +/* + * ----------------------------------------------------------------------- + * Following are callback functions against generic Opaque-LSAs handling + * ----------------------------------------------------------------------- + */ + +/* Add new Interface in Extended Interface List */ +static int ospf_ext_link_new_if(struct interface *ifp) +{ + struct ext_itf *new; + int rc = -1; + + if (lookup_ext_by_ifp(ifp) != NULL) { + rc = 0; /* Do nothing here. */ + return rc; + } + + new = XCALLOC(MTYPE_OSPF_EXT_PARAMS, sizeof(struct ext_itf)); + + /* initialize new information and link back the interface */ + new->ifp = ifp; + new->flags = EXT_LPFLG_LSA_INACTIVE; + + listnode_add(OspfEXT.iflist, new); + + rc = 0; + return rc; +} + +/* Remove existing Interface from Extended Interface List */ +static int ospf_ext_link_del_if(struct interface *ifp) +{ + struct ext_itf *exti; + int rc = -1; + + exti = lookup_ext_by_ifp(ifp); + if (exti != NULL) { + /* Flush LSA and remove Adjacency SID */ + ospf_extended_lsa_delete(exti); + + /* Dequeue listnode entry from the list. */ + listnode_delete(OspfEXT.iflist, exti); + + XFREE(MTYPE_OSPF_EXT_PARAMS, exti); + + rc = 0; + } else { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): interface %s is not found", __func__, + ifp ? ifp->name : "-"); + } + + return rc; +} + +/* + * Determine if an Interface belongs to an Extended Link Adjacency or + * Extended Prefix SID type and allocate new instance value accordingly + */ +static void ospf_ext_ism_change(struct ospf_interface *oi, int old_status) +{ + struct ext_itf *exti; + + /* Get interface information for Segment Routing */ + exti = lookup_ext_by_ifp(oi->ifp); + if (exti == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Cannot get Extended info. from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Reset Extended information if ospf interface goes Down */ + if (oi->state == ISM_Down) { + ospf_extended_lsa_delete(exti); + exti->area = NULL; + exti->flags = EXT_LPFLG_LSA_INACTIVE; + return; + } + + /* Determine if interface is related to a Prefix or an Adjacency SID */ + if (oi->type == OSPF_IFTYPE_LOOPBACK) { + exti->stype = PREF_SID; + exti->type = OPAQUE_TYPE_EXTENDED_PREFIX_LSA; + exti->instance = get_ext_pref_instance_value(); + exti->area = oi->area; + + /* Complete SRDB if the interface belongs to a Prefix */ + if (OspfEXT.enabled) { + osr_debug("EXT (%s): Set Prefix SID to interface %s ", + __func__, oi->ifp->name); + ospf_sr_update_local_prefix(oi->ifp, oi->address); + } + } else { + /* Determine if interface is related to Adj. or LAN Adj. SID */ + if (oi->state == ISM_DR) + exti->stype = LAN_ADJ_SID; + else + exti->stype = ADJ_SID; + + exti->type = OPAQUE_TYPE_EXTENDED_LINK_LSA; + exti->instance = get_ext_link_instance_value(); + exti->area = oi->area; + + /* + * Note: Adjacency SID information are completed when ospf + * adjacency become up see ospf_ext_link_nsm_change() + */ + if (OspfEXT.enabled) + osr_debug( + "EXT (%s): Set %sAdjacency SID for interface %s ", + __func__, exti->stype == ADJ_SID ? "" : "LAN-", + oi->ifp->name); + } +} + +/* + * Finish Extended Link configuration and flood corresponding LSA + * when OSPF adjacency on this link fire up + */ +static void ospf_ext_link_nsm_change(struct ospf_neighbor *nbr, int old_status) +{ + struct ospf_interface *oi = nbr->oi; + struct ext_itf *exti; + + /* Process Link only when neighbor old or new state is NSM Full */ + if (nbr->state != NSM_Full && old_status != NSM_Full) + return; + + /* Get interface information for Segment Routing */ + exti = lookup_ext_by_ifp(oi->ifp); + if (exti == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Cannot get Extended info. from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Check that we have a valid area and ospf context */ + if (oi->area == NULL || oi->area->ospf == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Cannot refer to OSPF from OI(%s)", + __func__, IF_NAME(oi)); + return; + } + + /* Remove Extended Link if Neighbor State goes Down or Deleted */ + if (OspfEXT.enabled + && (nbr->state == NSM_Down || nbr->state == NSM_Deleted)) { + ospf_ext_link_delete_adj_sid(exti); + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_link_lsa_schedule(exti, FLUSH_THIS_LSA); + exti->flags = EXT_LPFLG_LSA_INACTIVE; + return; + } + + /* Keep Area information in combination with SR info. */ + exti->area = oi->area; + + /* Process only Adjacency/LAN SID */ + if (exti->stype == PREF_SID) + return; + + switch (oi->state) { + case ISM_PointToPoint: + /* Segment ID is an Adjacency one */ + exti->stype = ADJ_SID; + + /* Set Extended Link TLV with link_id == Nbr Router ID */ + set_ext_link(exti, OSPF_IFTYPE_POINTOPOINT, nbr->router_id, + oi->address->u.prefix4); + + /* And Remote Interface address */ + set_rmt_itf_addr(exti, nbr->address.u.prefix4); + + break; + + case ISM_DR: + /* Segment ID is a LAN Adjacency for the DR only */ + exti->stype = LAN_ADJ_SID; + + /* Set Extended Link TLV with link_id == DR */ + set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), + oi->address->u.prefix4); + + /* Set Neighbor ID */ + exti->lan_sid[0].neighbor_id = nbr->router_id; + exti->lan_sid[1].neighbor_id = nbr->router_id; + + break; + + case ISM_DROther: + case ISM_Backup: + /* Segment ID is an Adjacency if not the DR */ + exti->stype = ADJ_SID; + + /* Set Extended Link TLV with link_id == DR */ + set_ext_link(exti, OSPF_IFTYPE_BROADCAST, DR(oi), + oi->address->u.prefix4); + + break; + + default: + if (CHECK_FLAG(exti->flags, EXT_LPFLG_FIB_ENTRY_SET)) + ospf_ext_link_delete_adj_sid(exti); + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_link_lsa_schedule(exti, FLUSH_THIS_LSA); + exti->flags = EXT_LPFLG_LSA_INACTIVE; + return; + } + + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE); + + if (OspfEXT.enabled) { + osr_debug("EXT (%s): Set %sAdjacency SID for interface %s ", + __func__, exti->stype == ADJ_SID ? "" : "LAN-", + oi->ifp->name); + + /* Update (LAN)Adjacency SID */ + ospf_ext_link_update_adj_sid(exti); + + /* flood this links params if everything is ok */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) + ospf_ext_link_lsa_schedule(exti, REFRESH_THIS_LSA); + else + ospf_ext_link_lsa_schedule(exti, REORIGINATE_THIS_LSA); + } +} + +/* Callbacks to handle Extended Link Segment Routing LSA information */ +static int ospf_ext_link_lsa_update(struct ospf_lsa *lsa) +{ + /* Sanity Check */ + if (lsa == NULL) { + flog_warn(EC_OSPF_LSA_NULL, "EXT (%s): Abort! LSA is NULL", + __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Extended Link LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + != OPAQUE_TYPE_EXTENDED_LINK_LSA) + return 0; + + /* Check if it is not my LSA */ + if (IS_LSA_SELF(lsa)) + return 0; + + /* Check if Extended is enable */ + if (!OspfEXT.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ext_link_lsa_update(lsa); + else + ospf_sr_ext_link_lsa_delete(lsa); + + return 0; +} + +/* Callbacks to handle Extended Prefix Segment Routing LSA information */ +static int ospf_ext_pref_lsa_update(struct ospf_lsa *lsa) +{ + + /* Sanity Check */ + if (lsa == NULL) { + flog_warn(EC_OSPF_LSA_NULL, "EXT (%s): Abort! LSA is NULL", + __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Extended Prefix LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + != OPAQUE_TYPE_EXTENDED_PREFIX_LSA) + return 0; + + /* Check if it is not my LSA */ + if (IS_LSA_SELF(lsa)) + return 0; + + /* Check if Extended is enable */ + if (!OspfEXT.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ext_prefix_lsa_update(lsa); + else + ospf_sr_ext_prefix_lsa_delete(lsa); + + return 0; +} + +/* + * ------------------------------------------------------- + * Following are OSPF protocol processing functions for + * Extended Prefix/Link Opaque LSA + * ------------------------------------------------------- + */ + +static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) +{ + stream_put(s, tlvh, sizeof(struct tlv_header)); +} + +static void build_tlv(struct stream *s, struct tlv_header *tlvh) +{ + + if ((tlvh != NULL) && (ntohs(tlvh->type) != 0)) { + build_tlv_header(s, tlvh); + stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); + } +} + +/* Build an Extended Prefix Opaque LSA body for extended prefix TLV */ +static void ospf_ext_pref_lsa_body_set(struct stream *s, struct ext_itf *exti) +{ + + /* Sanity check */ + if ((exti == NULL) || (exti->stype != PREF_SID)) + return; + + /* Adjust Extended Prefix TLV size */ + TLV_LEN(exti->prefix) = htons(ntohs(TLV_LEN(exti->node_sid)) + + EXT_TLV_PREFIX_SIZE + TLV_HDR_SIZE); + + /* Build LSA body for an Extended Prefix TLV */ + build_tlv_header(s, &exti->prefix.header); + stream_put(s, TLV_DATA(&exti->prefix.header), EXT_TLV_PREFIX_SIZE); + /* Then add Prefix SID SubTLV */ + build_tlv(s, &exti->node_sid.header); +} + +/* Build an Extended Link Opaque LSA body for extended link TLV */ +static void ospf_ext_link_lsa_body_set(struct stream *s, struct ext_itf *exti) +{ + size_t size; + + /* Sanity check */ + if ((exti == NULL) + || ((exti->stype != ADJ_SID) && (exti->stype != LAN_ADJ_SID))) + return; + + if (exti->stype == ADJ_SID) { + /* Adjust Extended Link TLV size for Adj. SID */ + size = EXT_TLV_LINK_SIZE + 2 * EXT_SUBTLV_ADJ_SID_SIZE + + 2 * TLV_HDR_SIZE; + if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) + size = size + EXT_SUBTLV_RMT_ITF_ADDR_SIZE + + TLV_HDR_SIZE; + TLV_LEN(exti->link) = htons(size); + + /* Build LSA body for an Extended Link TLV with Adj. SID */ + build_tlv_header(s, &exti->link.header); + stream_put(s, TLV_DATA(&exti->link.header), EXT_TLV_LINK_SIZE); + /* then add Adjacency SubTLVs */ + build_tlv(s, &exti->adj_sid[1].header); + build_tlv(s, &exti->adj_sid[0].header); + + /* Add Cisco experimental SubTLV if interface is PtoP */ + if (ntohs(TLV_TYPE(exti->rmt_itf_addr)) != 0) + build_tlv(s, &exti->rmt_itf_addr.header); + } else { + /* Adjust Extended Link TLV size for LAN SID */ + size = EXT_TLV_LINK_SIZE + + 2 * (EXT_SUBTLV_LAN_ADJ_SID_SIZE + TLV_HDR_SIZE); + TLV_LEN(exti->link) = htons(size); + + /* Build LSA body for an Extended Link TLV with LAN SID */ + build_tlv_header(s, &exti->link.header); + stream_put(s, TLV_DATA(&exti->link.header), EXT_TLV_LINK_SIZE); + /* then add LAN-Adjacency SubTLVs */ + build_tlv(s, &exti->lan_sid[1].header); + build_tlv(s, &exti->lan_sid[0].header); + } +} + +/* Create new Extended Prefix opaque-LSA for every extended prefix */ +static struct ospf_lsa *ospf_ext_pref_lsa_new(struct ospf_area *area, + struct ext_itf *exti) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + struct ospf *top; + uint8_t options, lsa_type; + struct in_addr lsa_id; + struct in_addr router_id; + uint32_t tmp; + uint16_t length; + + /* Sanity Check */ + if (exti == NULL) + return NULL; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + + /* Prepare LSA Header */ + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsa_type = OspfEXT.scope; + + /* + * LSA ID is a variable number identifying different instances of + * Extended Prefix Opaque LSA from the same router see RFC 7684 + */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); + lsa_id.s_addr = htonl(tmp); + + options = OSPF_OPTION_O; /* Don't forget this :-) */ + + /* Fix Options and Router ID depending of the flooding scope */ + if ((OspfEXT.scope == OSPF_OPAQUE_AS_LSA) || (area == NULL)) { + options = OSPF_OPTION_E; + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + router_id.s_addr = top ? top->router_id.s_addr : 0; + } else { + options |= LSA_OPTIONS_GET(area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + router_id = area->ospf->router_id; + } + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, router_id); + + osr_debug( + "EXT (%s): LSA[Type%u:%pI4]: Create an Opaque-LSA Extended Prefix Opaque LSA instance", + __func__, lsa_type, &lsa_id); + + /* Set opaque-LSA body fields. */ + ospf_ext_pref_lsa_body_set(s, exti); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + /* Segment Routing belongs only to default VRF */ + new->vrf_id = VRF_DEFAULT; + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Create new Extended Link opaque-LSA for every extended link TLV */ +static struct ospf_lsa *ospf_ext_link_lsa_new(struct ospf_area *area, + struct ext_itf *exti) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + uint8_t options, lsa_type; + struct in_addr lsa_id; + uint32_t tmp; + uint16_t length; + + /* Sanity Check */ + if (exti == NULL) + return NULL; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = OSPF_OPTION_O; /* Don't forget this :-) */ + options |= LSA_OPTIONS_GET(area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + /* Extended Link Opaque LSA are only flooded within an area */ + lsa_type = OSPF_OPAQUE_AREA_LSA; + + /* + * LSA ID is a variable number identifying different instances of + * Extended Link Opaque LSA from the same router see RFC 7684 + */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + lsa_id.s_addr = htonl(tmp); + + osr_debug( + "EXT (%s) LSA[Type%u:%pI4]: Create an Opaque-LSA Extended Link Opaque LSA instance", + __func__, lsa_type, &lsa_id); + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, area->ospf->router_id); + + /* Set opaque-LSA body fields. */ + ospf_ext_link_lsa_body_set(s, exti); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + /* Segment Routing belongs only to default VRF */ + new->vrf_id = VRF_DEFAULT; + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* + * Process the origination of an Extended Prefix Opaque LSA + * for every extended prefix TLV + */ +static int ospf_ext_pref_lsa_originate1(struct ospf_area *area, + struct ext_itf *exti) +{ + struct ospf_lsa *new; + int rc = -1; + + + /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ + new = ospf_ext_pref_lsa_new(area, exti); + if (new == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): ospf_ext_pref_lsa_new() error", __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this Extended Prefix Opaque LSA info parameter entry has + * associated LSA. + */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + + /* Update new LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flood new LSA through area. */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + osr_debug( + "EXT (%s): LSA[Type%u:%pI4]: Originate Opaque-LSAExtended Prefix Opaque LSA: Area(%pI4), Link(%s)", + __func__, new->data->type, &new->data->id, + &area->area_id, exti->ifp->name); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + rc = 0; + + return rc; +} + +/* + * Process the origination of an Extended Link Opaque LSA + * for every extended link TLV + */ +static int ospf_ext_link_lsa_originate1(struct ospf_area *area, + struct ext_itf *exti) +{ + struct ospf_lsa *new; + int rc = -1; + + /* Create new Opaque-LSA/Extended Link Opaque LSA instance. */ + new = ospf_ext_link_lsa_new(area, exti); + if (new == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): ospf_ext_link_lsa_new() error", __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(area->ospf, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this link-parameter entry has associated LSA. */ + SET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + + /* Update new LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flood new LSA through area. */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + osr_debug( + "EXT (%s): LSA[Type%u:%pI4]: Originate Opaque-LSA Extended Link Opaque LSA: Area(%pI4), Link(%s)", + __func__, new->data->type, &new->data->id, + &area->area_id, exti->ifp->name); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + rc = 0; + + return rc; +} + +/* Trigger the origination of Extended Prefix Opaque LSAs */ +static int ospf_ext_pref_lsa_originate(void *arg) +{ + struct ospf_area *area = (struct ospf_area *)arg; + struct listnode *node; + struct ext_itf *exti; + int rc = -1; + + if (!OspfEXT.enabled) { + zlog_info( + "EXT (%s): Segment Routing functionality is Disabled now", + __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + osr_debug("EXT (%s): Start Originate Prefix LSA for area %pI4", + __func__, &area->area_id); + + /* Check if Extended Prefix Opaque LSA is already engaged */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + + /* Process only Prefix SID */ + if (exti->stype != PREF_SID) + continue; + + /* Process only Extended Prefix with valid Area ID */ + if ((exti->area == NULL) + || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) + continue; + + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH)) { + flog_warn( + EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Refresh instead of Originate", + __func__); + UNSET_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH); + ospf_ext_pref_lsa_schedule(exti, + REFRESH_THIS_LSA); + } + continue; + } + + /* Ok, let's try to originate an LSA */ + osr_debug( + "EXT (%s): Let's finally re-originate the LSA 7.0.0.%u for Itf %s", __func__, exti->instance, + exti->ifp ? exti->ifp->name : ""); + ospf_ext_pref_lsa_originate1(area, exti); + } + + rc = 0; + return rc; +} + +/* Trigger the origination of Extended Link Opaque LSAs */ +static int ospf_ext_link_lsa_originate(void *arg) +{ + struct ospf_area *area = (struct ospf_area *)arg; + struct listnode *node; + struct ext_itf *exti; + int rc = -1; + + if (!OspfEXT.enabled) { + zlog_info( + "EXT (%s): Segment Routing functionality is Disabled now", + __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + + /* Check if Extended Prefix Opaque LSA is already engaged */ + for (ALL_LIST_ELEMENTS_RO(OspfEXT.iflist, node, exti)) { + /* Process only Adjacency or LAN SID */ + if (exti->stype == PREF_SID) + continue; + + /* Skip Inactive Extended Link */ + if (!CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) + continue; + + /* Process only Extended Link with valid Area ID */ + if ((exti->area == NULL) + || (!IPV4_ADDR_SAME(&exti->area->area_id, &area->area_id))) + continue; + + /* Check if LSA not already engaged */ + if (CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH)) { + flog_warn( + EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Refresh instead of Originate", + __func__); + UNSET_FLAG(exti->flags, + EXT_LPFLG_LSA_FORCED_REFRESH); + ospf_ext_link_lsa_schedule(exti, + REFRESH_THIS_LSA); + } + continue; + } + + /* Ok, let's try to originate an LSA */ + osr_debug( + "EXT (%s): Let's finally reoriginate the LSA 8.0.0.%u for Itf %s through the Area %pI4", __func__, + exti->instance, exti->ifp ? exti->ifp->name : "-", + &area->area_id); + ospf_ext_link_lsa_originate1(area, exti); + } + + rc = 0; + return rc; +} + +/* Refresh an Extended Prefix Opaque LSA */ +static struct ospf_lsa *ospf_ext_pref_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf_lsa *new = NULL; + struct ospf_area *area = lsa->area; + struct ospf *top; + struct ext_itf *exti; + + if (!OspfEXT.enabled) { + /* + * This LSA must have flushed before due to Extended Prefix + * Opaque LSA status change. + * It seems a slip among routers in the routing domain. + */ + zlog_info( + "EXT (%s): Segment Routing functionality is Disabled", + __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Lookup this lsa corresponding Extended parameters */ + exti = lookup_ext_by_instance(lsa); + if (exti == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Invalid parameter LSA ID", __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Check if Interface was not disable in the interval */ + if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Interface was Disabled: Flush it!", + __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* If the lsa's age reached to MaxAge, start flushing procedure. */ + if (IS_LSA_MAXAGE(lsa)) { + if (exti) + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + + /* Create new Opaque-LSA/Extended Prefix Opaque LSA instance. */ + new = ospf_ext_pref_lsa_new(area, exti); + + if (new == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): ospf_ext_pref_lsa_new() error", __func__); + return NULL; + } + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* + * Install this LSA into LSDB + * Given "lsa" will be freed in the next function + * As area could be NULL i.e. when using OPAQUE_LSA_AS, we prefer to use + * ospf_lookup() to get ospf instance + */ + if (area) + top = area->ospf; + else + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "EXT (%s): ospf_lsa_install() error", __func__); + ospf_lsa_unlock(&new); + return NULL; + } + + /* Flood updated LSA through the Prefix Area according to the RFC7684 */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + /* Debug logging. */ + osr_debug("EXT (%s): LSA[Type%u:%pI4] Refresh Extended Prefix LSA", + __func__, new->data->type, &new->data->id); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + + return new; +} + +/* Refresh an Extended Link Opaque LSA */ +static struct ospf_lsa *ospf_ext_link_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ext_itf *exti; + struct ospf_area *area = lsa->area; + struct ospf *top = area->ospf; + struct ospf_lsa *new = NULL; + + if (!OspfEXT.enabled) { + /* + * This LSA must have flushed before due to OSPF-SR status + * change. It seems a slip among routers in the routing domain. + */ + zlog_info("EXT (%s): Segment Routing functionality is Disabled", + __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Lookup this LSA corresponding Extended parameters */ + exti = lookup_ext_by_instance(lsa); + if (exti == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Invalid parameter LSA ID", __func__); + /* Flush it anyway. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* Check if Interface was not disable in the interval */ + if ((exti != NULL) && !CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE)) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Interface was Disabled: Flush it!", + __func__); + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + } + + /* If the lsa's age reached to MaxAge, start flushing procedure */ + if (IS_LSA_MAXAGE(lsa)) { + if (exti) + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + + /* Create new Opaque-LSA/Extended Link instance */ + new = ospf_ext_link_lsa_new(area, exti); + if (new == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Error creating new LSA", __func__); + return NULL; + } + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* Install this LSA into LSDB. */ + /* Given "lsa" will be freed in the next function */ + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "EXT (%s): Error installing new LSA", __func__); + ospf_lsa_unlock(&new); + return NULL; + } + + /* Flood updated LSA through the link Area according to the RFC7684 */ + ospf_flood_through_area(area, NULL /*nbr */, new); + + /* Debug logging. */ + osr_debug("EXT (%s): LSA[Type%u:%pI4]: Refresh Extended Link LSA", + __func__, new->data->type, &new->data->id); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + return new; +} + +/* Schedule Extended Prefix Opaque LSA origination/refreshment/flushing */ +static void ospf_ext_pref_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + + /* Sanity Check */ + if (exti == NULL) + return; + + /* Check if the corresponding link is ready to be flooded */ + if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) + return; + + osr_debug("EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : "", + exti->ifp ? exti->ifp->name : "-"); + + /* Verify Area */ + if (exti->area == NULL) { + osr_debug( + "EXT (%s): Area is not yet set. Try to use Backbone Area", + __func__); + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct in_addr backbone = {.s_addr = INADDR_ANY}; + exti->area = ospf_area_lookup_by_area_id(top, backbone); + if (exti->area == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Unable to set Area", __func__); + return; + } + } + /* Set LSA header information */ + lsa.area = exti->area; + lsa.data = &lsah; + lsah.type = OSPF_OPAQUE_AREA_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, exti->instance); + lsah.id.s_addr = htonl(tmp); + + switch (opcode) { + case REORIGINATE_THIS_LSA: + ospf_opaque_lsa_reoriginate_schedule( + (void *)exti->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_PREFIX_LSA); + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + UNSET_FLAG(exti->flags, EXT_LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(&lsa); + break; + } +} + +/* Schedule Extended Link Opaque LSA origination/refreshment/flushing */ +static void ospf_ext_link_lsa_schedule(struct ext_itf *exti, + enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + + /* Sanity Check */ + if (exti == NULL) + return; + + /* Check if the corresponding link is ready to be flooded */ + if (!(CHECK_FLAG(exti->flags, EXT_LPFLG_LSA_ACTIVE))) + return; + + osr_debug("EXT (%s): Schedule %s%s%s LSA for interface %s", __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : "", + exti->ifp ? exti->ifp->name : "-"); + + /* Verify Area */ + if (exti->area == NULL) { + osr_debug( + "EXT (%s): Area is not yet set. Try to use Backbone Area", + __func__); + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct in_addr backbone = {.s_addr = INADDR_ANY}; + exti->area = ospf_area_lookup_by_area_id(top, backbone); + if (exti->area == NULL) { + flog_warn(EC_OSPF_EXT_LSA_UNEXPECTED, + "EXT (%s): Unable to set Area", __func__); + return; + } + } + /* Set LSA header information */ + lsa.area = exti->area; + lsa.data = &lsah; + lsah.type = OSPF_OPAQUE_AREA_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + lsah.id.s_addr = htonl(tmp); + + switch (opcode) { + case REORIGINATE_THIS_LSA: + ospf_opaque_lsa_reoriginate_schedule( + (void *)exti->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_EXTENDED_LINK_LSA); + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + ospf_opaque_lsa_flush_schedule(&lsa); + break; + } +} + +/* Schedule Extended Link or Prefix depending of the Type of LSA */ +static void ospf_ext_lsa_schedule(struct ext_itf *exti, enum lsa_opcode op) +{ + + if (exti->stype == PREF_SID) + ospf_ext_pref_lsa_schedule(exti, op); + else + ospf_ext_link_lsa_schedule(exti, op); +} + +/* + * ------------------------------------ + * Following are vty show functions. + * ------------------------------------ + */ + +#define check_tlv_size(size, msg) \ + do { \ + if (ntohs(tlvh->length) != size) { \ + vty_out(vty, " Wrong %s TLV size: %d(%d). Abort!\n", \ + msg, ntohs(tlvh->length), size); \ + return size + TLV_HDR_SIZE; \ + } \ + } while (0) + +/* Cisco experimental SubTLV */ +static uint16_t show_vty_ext_link_rmt_itf_addr(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ext_subtlv_rmt_itf_addr *top = + (struct ext_subtlv_rmt_itf_addr *)tlvh; + + check_tlv_size(EXT_SUBTLV_RMT_ITF_ADDR_SIZE, "Remote Itf. Address"); + + if (!json) + vty_out(vty, + " Remote Interface Address Sub-TLV: Length %u\n Address: %pI4\n", + ntohs(top->header.length), &top->value); + else + json_object_string_addf(json, "remoteInterfaceAddress", "%pI4", + &top->value); + + return TLV_SIZE(tlvh); +} + +/* Adjacency SID SubTLV */ +static uint16_t show_vty_ext_link_adj_sid(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ext_subtlv_adj_sid *top = (struct ext_subtlv_adj_sid *)tlvh; + uint8_t tlv_size; + + tlv_size = CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? SID_LABEL_SIZE(EXT_SUBTLV_ADJ_SID_SIZE) + : SID_INDEX_SIZE(EXT_SUBTLV_ADJ_SID_SIZE); + check_tlv_size(tlv_size, "Adjacency SID"); + + if (!json) + vty_out(vty, + " Adj-SID Sub-TLV: Length %u\n\tFlags: 0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\t%s: %u\n", + ntohs(top->header.length), top->flags, top->mtid, + top->weight, + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + else { + json_object_string_addf(json, "flags", "0x%x", top->flags); + json_object_string_addf(json, "mtID", "0x%x", top->mtid); + json_object_string_addf(json, "weight", "0x%x", top->weight); + if (CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + json_object_int_add(json, "label", + GET_LABEL(ntohl(top->value))); + else + json_object_int_add(json, "index", ntohl(top->value)); + } + + return TLV_SIZE(tlvh); +} + +/* LAN Adjacency SubTLV */ +static uint16_t show_vty_ext_link_lan_adj_sid(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ext_subtlv_lan_adj_sid *top = + (struct ext_subtlv_lan_adj_sid *)tlvh; + uint8_t tlv_size; + + tlv_size = CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? SID_LABEL_SIZE(EXT_SUBTLV_LAN_ADJ_SID_SIZE) + : SID_INDEX_SIZE(EXT_SUBTLV_LAN_ADJ_SID_SIZE); + check_tlv_size(tlv_size, "LAN-Adjacency SID"); + + if (!json) + vty_out(vty, + " LAN-Adj-SID Sub-TLV: Length %u\n\tFlags: 0x%x\n\tMT-ID:0x%x\n\tWeight: 0x%x\n\tNeighbor ID: %pI4\n\t%s: %u\n", + ntohs(top->header.length), top->flags, top->mtid, + top->weight, &top->neighbor_id, + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + else { + json_object_string_addf(json, "flags", "0x%x", top->flags); + json_object_string_addf(json, "mtID", "0x%x", top->mtid); + json_object_string_addf(json, "weight", "0x%x", top->weight); + json_object_string_addf(json, "neighborID", "%pI4", + &top->neighbor_id); + if (CHECK_FLAG(top->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + json_object_int_add(json, "label", + GET_LABEL(ntohl(top->value))); + else + json_object_int_add(json, "index", ntohl(top->value)); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh, + size_t buf_size, json_object *json) +{ + json_object *obj; + + if (TLV_SIZE(tlvh) > buf_size) { + vty_out(vty, " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + if (!json) + vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", + ntohs(tlvh->type), ntohs(tlvh->length)); + else { + obj = json_object_new_object(); + json_object_string_addf(obj, "type", "0x%x", + ntohs(tlvh->type)); + json_object_string_addf(obj, "length", "0x%x", + ntohs(tlvh->length)); + json_object_object_add(json, "unknownTLV", obj); + } + + return TLV_SIZE(tlvh); +} + +/* Extended Link Sub TLVs */ +static uint16_t show_vty_link_info(struct vty *vty, struct tlv_header *ext, + size_t buf_size, json_object *json) +{ + struct ext_tlv_link *top = (struct ext_tlv_link *)ext; + struct tlv_header *tlvh; + uint16_t length = ntohs(top->header.length); + uint16_t sum = 0; + json_object *jadj = NULL, *obj = NULL; + + /* Verify that TLV length is valid against remaining buffer size */ + if (length > buf_size) { + vty_out(vty, + " Extended Link TLV size %d exceeds buffer size. Abort!\n", + length); + return buf_size; + } + + if (!json) { + vty_out(vty, + " Extended Link TLV: Length %u\n Link Type: 0x%x\n" + " Link ID: %pI4\n", + ntohs(top->header.length), top->link_type, + &top->link_id); + vty_out(vty, " Link data: %pI4\n", &top->link_data); + } else { + json_object_string_addf(json, "linkType", "0x%x", + top->link_type); + json_object_string_addf(json, "linkID", "%pI4", &top->link_id); + json_object_string_addf(json, "linkData", "%pI4", + &top->link_data); + jadj = json_object_new_array(); + json_object_object_add(json, "adjacencySID", jadj); + } + + /* Skip Extended TLV and parse sub-TLVs */ + length -= EXT_TLV_LINK_SIZE; + tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_LINK_SIZE); + for (; sum < length && tlvh; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_SUBTLV_ADJ_SID: + if (json) { + obj = json_object_new_object(); + json_object_array_add(jadj, obj); + } else + obj = NULL; + sum += show_vty_ext_link_adj_sid(vty, tlvh, obj); + break; + case EXT_SUBTLV_LAN_ADJ_SID: + if (json) { + obj = json_object_new_object(); + json_object_array_add(jadj, obj); + } else + obj = NULL; + sum += show_vty_ext_link_lan_adj_sid(vty, tlvh, obj); + break; + case EXT_SUBTLV_RMT_ITF_ADDR: + sum += show_vty_ext_link_rmt_itf_addr(vty, tlvh, json); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, length - sum, + json); + break; + } + } + + return sum + sizeof(struct ext_tlv_link); +} + +/* Extended Link TLVs */ +static void ospf_ext_link_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = lsa->data; + struct tlv_header *tlvh; + uint16_t length = 0, sum = 0; + json_object *jlink = NULL; + + if (json) { + jlink = json_object_new_object(); + json_object_object_add(json, "extendedLink", jlink); + } + + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_TLV_LINK: + sum += show_vty_link_info(vty, tlvh, length - sum, + jlink); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, length - sum, + jlink); + break; + } + } +} + +/* Prefix SID SubTLV */ +static uint16_t show_vty_ext_pref_pref_sid(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ext_subtlv_prefix_sid *top = + (struct ext_subtlv_prefix_sid *)tlvh; + uint8_t tlv_size; + + tlv_size = CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) + ? SID_LABEL_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE) + : SID_INDEX_SIZE(EXT_SUBTLV_PREFIX_SID_SIZE); + check_tlv_size(tlv_size, "Prefix SID"); + + if (!json) + vty_out(vty, + " Prefix SID Sub-TLV: Length %u\n\tAlgorithm: %u\n\tFlags: 0x%x\n\tMT-ID:0x%x\n\t%s: %u\n", + ntohs(top->header.length), top->algorithm, top->flags, + top->mtid, + CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) + ? "Label" + : "Index", + CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG) + ? GET_LABEL(ntohl(top->value)) + : ntohl(top->value)); + else { + json_object_int_add(json, "algorithm", top->algorithm); + json_object_string_addf(json, "flags", "0x%x", top->flags); + json_object_string_addf(json, "mtID", "0x%x", top->mtid); + if (CHECK_FLAG(top->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + json_object_int_add(json, "label", + GET_LABEL(ntohl(top->value))); + else + json_object_int_add(json, "index", ntohl(top->value)); + } + return TLV_SIZE(tlvh); +} + +/* Extended Prefix SubTLVs */ +static uint16_t show_vty_pref_info(struct vty *vty, struct tlv_header *ext, + size_t buf_size, json_object *json) +{ + struct ext_tlv_prefix *top = (struct ext_tlv_prefix *)ext; + struct tlv_header *tlvh; + uint16_t length = ntohs(top->header.length); + uint16_t sum = 0; + json_object *jsid = NULL; + + /* Verify that TLV length is valid against remaining buffer size */ + if (length > buf_size) { + vty_out(vty, + " Extended Link TLV size %d exceeds buffer size. Abort!\n", + length); + return buf_size; + } + + if (!json) + vty_out(vty, + " Extended Prefix TLV: Length %u\n\tRoute Type: %u\n" + "\tAddress Family: 0x%x\n\tFlags: 0x%x\n\tAddress: %pI4/%u\n", + ntohs(top->header.length), top->route_type, top->af, + top->flags, &top->address, top->pref_length); + else { + json_object_int_add(json, "routeType", top->route_type); + json_object_string_addf(json, "addressFamily", "0x%x", top->af); + json_object_string_addf(json, "flags", "0x%x", top->flags); + json_object_string_addf(json, "address", "%pI4", &top->address); + json_object_int_add(json, "prefixLength", top->pref_length); + jsid = json_object_new_object(); + json_object_object_add(json, "prefixSID", jsid); + } + + /* Skip Extended Prefix TLV and parse sub-TLVs */ + length -= EXT_TLV_PREFIX_SIZE; + tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_PREFIX_SIZE); + for (; sum < length && tlvh; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_SUBTLV_PREFIX_SID: + sum += show_vty_ext_pref_pref_sid(vty, tlvh, jsid); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, length - sum, + json); + break; + } + } + + return sum + sizeof(struct ext_tlv_prefix); +} + +/* Extended Prefix TLVs */ +static void ospf_ext_pref_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = lsa->data; + struct tlv_header *tlvh; + uint16_t length = 0, sum = 0; + json_object *jpref = NULL; + + if (json) { + jpref = json_object_new_object(); + json_object_object_add(json, "extendedPrefix", jpref); + } + + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case EXT_TLV_PREFIX: + sum += show_vty_pref_info(vty, tlvh, length - sum, + jpref); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, length - sum, + jpref); + break; + } + } +} diff --git a/ospfd/ospf_ext.h b/ospfd/ospf_ext.h new file mode 100644 index 0000000..535e548 --- /dev/null +++ b/ospfd/ospf_ext.h @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC7684 OSPFv2 Prefix/Link Attribute + * Advertisement + * + * Module name: Extended Prefix/Link Opaque LSA header definition + * + * Author: Olivier Dugeon + * Author: Anselme Sawadogo + * + * Copyright (C) 2016 - 2018 Orange Labs http://www.orange.com + */ + +#ifndef _FRR_OSPF_EXT_PREF_H_ +#define _FRR_OSPF_EXT_PREF_H_ + +/* + * Opaque LSA's link state ID for Extended Prefix/Link is + * structured as follows. + * + * 24 16 8 0 + * +--------+--------+--------+--------+ + * | 7/8 |........|........|........| + * +--------+--------+--------+--------+ + * |<-Type->|<------- Instance ------->| + * + * + * Type: IANA has assigned '7' for Extended Prefix Opaque LSA + * and '8' for Extended Link Opaque LSA + * Instance: User may select arbitrary 24-bit values to identify + * different instances of Extended Prefix/Link Opaque LSA + * + */ + +/* + * 24 16 8 0 + * +--------+--------+--------+--------+ --- + * | LS age |Options | 10,11 | A + * +--------+--------+--------+--------+ | Standard (Opaque) LSA header; + * | 7/8 | Instance | | + * +--------+--------+--------+--------+ | Type 10 or 11 are used for Extended + * | Advertising router | | Prefix Opaque LSA + * +--------+--------+--------+--------+ | + * | LS sequence number | | Type 10 only is used for Extended + * +--------+--------+--------+--------+ | Link Opaque LSA + * | LS checksum | Length | V + * +--------+--------+--------+--------+ --- + * | Type | Length | A + * +--------+--------+--------+--------+ | TLV part for Extended Prefix/Link + * | | | Opaque LSA; + * ~ Values ... ~ | Values might be structured as a set + * | | V of sub-TLVs. + * +--------+--------+--------+--------+ --- + */ + +/* Global use constant numbers */ + +#define MAX_LEGAL_EXT_INSTANCE_NUM (0xffff) +#define LEGAL_EXT_INSTANCE_RANGE(i) (0 <= (i) && (i) <= 0xffff) + +/* Flags to manage Extended Link/Prefix Opaque LSA */ +#define EXT_LPFLG_LSA_INACTIVE 0x00 +#define EXT_LPFLG_LSA_ACTIVE 0x01 +#define EXT_LPFLG_LSA_ENGAGED 0x02 +#define EXT_LPFLG_LSA_LOOKUP_DONE 0x04 +#define EXT_LPFLG_LSA_FORCED_REFRESH 0x08 +#define EXT_LPFLG_FIB_ENTRY_SET 0x10 + +/* + * Following section defines TLV (tag, length, value) structures, + * used in Extended Prefix/Link Opaque LSA. + */ + +/* Extended Prefix TLV Route Types */ +#define EXT_TLV_PREF_ROUTE_UNSPEC 0 +#define EXT_TLV_PREF_ROUTE_INTRA_AREA 1 +#define EXT_TLV_PREF_ROUTE_INTER_AREA 3 +#define EXT_TLV_PREF_ROUTE_AS_EXT 5 +#define EXT_TLV_PREF_ROUTE_NSSA_EXT 7 + +/* + * Extended Prefix and Extended Prefix Range TLVs' + * Address family flag for IPv4 + */ +#define EXT_TLV_PREF_AF_IPV4 0 + +/* Extended Prefix TLV Flags */ +#define EXT_TLV_PREF_AFLG 0x80 +#define EXT_TLV_PREF_NFLG 0x40 + +/* Extended Prefix Range TLV Flags */ +#define EXT_TLV_PREF_RANGE_IAFLG 0x80 + +/* ERO subtlvs Flags */ +#define EXT_SUBTLV_ERO_LFLG 0x80 + +/* Extended Prefix TLV see RFC 7684 section 2.1 */ +#define EXT_TLV_PREFIX 1 +#define EXT_TLV_PREFIX_SIZE 8 +struct ext_tlv_prefix { + struct tlv_header header; + uint8_t route_type; + uint8_t pref_length; + uint8_t af; + uint8_t flags; + struct in_addr address; +}; + +/* Extended Link TLV see RFC 7684 section 3.1 */ +#define EXT_TLV_LINK 1 +#define EXT_TLV_LINK_SIZE 12 +struct ext_tlv_link { + struct tlv_header header; + uint8_t link_type; + uint8_t reserved[3]; + struct in_addr link_id; + struct in_addr link_data; +}; + +/* Remote Interface Address Sub-TLV, Cisco experimental use Sub-TLV */ +#define EXT_SUBTLV_RMT_ITF_ADDR 32768 +#define EXT_SUBTLV_RMT_ITF_ADDR_SIZE 4 +struct ext_subtlv_rmt_itf_addr { + struct tlv_header header; + struct in_addr value; +}; + +/* Internal structure to manage Extended Link/Prefix Opaque LSA */ +struct ospf_ext_lp { + bool enabled; + + /* Flags to manage this Extended Prefix/Link Opaque LSA */ + uint32_t flags; + + /* + * Scope is area Opaque Type 10 or AS Opaque LSA Type 11 for + * Extended Prefix and area Opaque Type 10 for Extended Link + */ + uint8_t scope; + + /* List of interface with Segment Routing enable */ + struct list *iflist; +}; + +/* Structure to aggregate interfaces information for Extended Prefix/Link */ +struct ext_itf { + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + uint8_t type; /* Extended Prefix (7) or Link (8) */ + + /* Reference pointer to a Zebra-interface. */ + struct interface *ifp; + + /* Area info in which this SR link belongs to. */ + struct ospf_area *area; + + /* Flags to manage this link parameters. */ + uint32_t flags; + + /* SID type: Node, Adjacency or LAN Adjacency */ + enum sid_type stype; + + /* extended link/prefix TLV information */ + struct ext_tlv_prefix prefix; + struct ext_subtlv_prefix_sid node_sid; + struct ext_tlv_link link; + struct ext_subtlv_adj_sid adj_sid[2]; + struct ext_subtlv_lan_adj_sid lan_sid[2]; + + /* cisco experimental subtlv */ + struct ext_subtlv_rmt_itf_addr rmt_itf_addr; +}; + +/* Prototypes. */ +extern int ospf_ext_init(void); +extern void ospf_ext_term(void); +extern void ospf_ext_finish(void); +extern void ospf_ext_update_sr(bool enable); +extern void ospf_ext_link_srlb_update(void); +extern uint32_t ospf_ext_schedule_prefix_index(struct interface *ifp, + uint32_t index, + struct prefix_ipv4 *p, + uint8_t flags); +#endif /* _FRR_OSPF_EXT_PREF_H_ */ diff --git a/ospfd/ospf_flood.c b/ospfd/ospf_flood.c new file mode 100644 index 0000000..e15871a --- /dev/null +++ b/ospfd/ospf_flood.c @@ -0,0 +1,1263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Flooding -- RFC2328 Section 13. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "monotime.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "command.h" +#include "table.h" +#include "frrevent.h" +#include "memory.h" +#include "log.h" +#include "zclient.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" + +extern struct zclient *zclient; + +/** @brief Function to refresh type-5 and type-7 DNA + * LSAs when we receive an indication LSA. + * @param Ospf instance. + * @return Void. + */ +void ospf_refresh_dna_type5_and_type7_lsas(struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_lsa *lsa = NULL; + + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + if (IS_LSA_SELF(lsa) && + CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE)) + ospf_lsa_refresh(ospf, lsa); + + LSDB_LOOP (NSSA_LSDB(ospf), rn, lsa) + if (IS_LSA_SELF(lsa) && + CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE)) + ospf_lsa_refresh(ospf, lsa); +} + +/** @brief Function to update area flood reduction states. + * @param area pointer. + * @return Void. + */ +void ospf_area_update_fr_state(struct ospf_area *area) +{ + unsigned int count_router_lsas = 0; + + if (area == NULL) + return; + + count_router_lsas = + (unsigned int)(ospf_lsdb_count(area->lsdb, OSPF_ROUTER_LSA) - + ospf_lsdb_count_self(area->lsdb, + OSPF_ROUTER_LSA)); + + if (count_router_lsas > + (unsigned int)area->fr_info.router_lsas_recv_dc_bit) { + area->fr_info.enabled = false; + area->fr_info.area_dc_clear = true; + return; + } else if (count_router_lsas < + (unsigned int)area->fr_info.router_lsas_recv_dc_bit) { + /* This can never happen, total number of router lsas received + * can never be less than router lsas received with dc bit set + */ + OSPF_LOG_ERR("%s: Counter mismatch for area %pI4", __func__, + &area->area_id); + OSPF_LOG_ERR( + "%s: router LSAs in lsdb %d router LSAs recvd with dc bit set %d", + __func__, count_router_lsas, + area->fr_info.router_lsas_recv_dc_bit); + return; + } + + area->fr_info.area_dc_clear = false; + + if (OSPF_FR_CONFIG(area->ospf, area)) { + if (!area->fr_info.enabled) { + area->fr_info.enabled = true; + area->fr_info.state_changed = true; + } + } else { + area->fr_info.enabled = false; + area->fr_info.area_dc_clear = true; + } +} + +/* Do the LSA acking specified in table 19, Section 13.5, row 2 + * This get called from ospf_flood_out_interface. Declared inline + * for speed. */ +static void ospf_flood_delayed_lsa_ack(struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) +{ + /* LSA is more recent than database copy, but was not + flooded back out receiving interface. Delayed + acknowledgment sent. If interface is in Backup state + delayed acknowledgment sent only if advertisement + received from Designated Router, otherwise do nothing See + RFC 2328 Section 13.5 */ + + /* Whether LSA is more recent or not, and whether this is in + response to the LSA being sent out recieving interface has been + worked out previously */ + + /* Deal with router as BDR */ + if (inbr->oi->state == ISM_Backup && !NBR_IS_DR(inbr)) + return; + + /* Schedule a delayed LSA Ack to be sent */ + listnode_add(inbr->oi->ls_ack, + ospf_lsa_lock(lsa)); /* delayed LSA Ack */ +} + +/* Check LSA is related to external info. */ +struct external_info *ospf_external_info_check(struct ospf *ospf, + struct ospf_lsa *lsa) +{ + struct as_external_lsa *al; + struct prefix_ipv4 p; + struct route_node *rn; + struct list *ext_list; + struct listnode *node; + struct ospf_external *ext; + int type; + + al = (struct as_external_lsa *)lsa->data; + + p.family = AF_INET; + p.prefix = lsa->data->id; + p.prefixlen = ip_masklen(al->mask); + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + int redist_on = 0; + + redist_on = + is_default_prefix4(&p) + ? vrf_bitmap_check( + &zclient->default_information[AFI_IP], + ospf->vrf_id) + : (zclient->mi_redist[AFI_IP][type].enabled || + vrf_bitmap_check( + &zclient->redist[AFI_IP][type], + ospf->vrf_id)); + // Pending: check for MI above. + if (redist_on) { + ext_list = ospf->external[type]; + if (!ext_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + rn = NULL; + if (ext->external_info) + rn = route_node_lookup( + ext->external_info, + (struct prefix *)&p); + if (rn) { + route_unlock_node(rn); + if (rn->info != NULL) + return (struct external_info *) + rn->info; + } + } + } + } + + if (is_default_prefix4(&p) && ospf->external[DEFAULT_ROUTE]) { + ext_list = ospf->external[DEFAULT_ROUTE]; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + if (!ext->external_info) + continue; + + rn = route_node_lookup(ext->external_info, + (struct prefix *)&p); + if (!rn) + continue; + route_unlock_node(rn); + if (rn->info != NULL) + return (struct external_info *)rn->info; + } + } + return NULL; +} + +static void ospf_process_self_originated_lsa(struct ospf *ospf, + struct ospf_lsa *new, + struct ospf_area *area) +{ + struct ospf_interface *oi; + struct external_info *ei; + struct listnode *node; + struct as_external_lsa *al; + struct prefix_ipv4 p; + struct ospf_external_aggr_rt *aggr; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s:LSA[Type%d:%pI4]: Process self-originated LSA seq 0x%x", + ospf_get_name(ospf), new->data->type, + &new->data->id, ntohl(new->data->ls_seqnum)); + + /* If we're here, we installed a self-originated LSA that we received + from a neighbor, i.e. it's more recent. We must see whether we want + to originate it. + If yes, we should use this LSA's sequence number and reoriginate + a new instance. + if not --- we must flush this LSA from the domain. */ + switch (new->data->type) { + case OSPF_ROUTER_LSA: + /* Originate a new instance and schedule flooding */ + if (area->router_lsa_self) + area->router_lsa_self->data->ls_seqnum = + new->data->ls_seqnum; + ospf_router_lsa_update_area(area); + return; + case OSPF_NETWORK_LSA: + case OSPF_OPAQUE_LINK_LSA: + /* We must find the interface the LSA could belong to. + If the interface is no more a broadcast type or we are no + more + the DR, we flush the LSA otherwise -- create the new instance + and + schedule flooding. */ + + /* Look through all interfaces, not just area, since interface + could be moved from one area to another. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + /* These are sanity check. */ + if (IPV4_ADDR_SAME(&oi->address->u.prefix4, + &new->data->id)) { + if (oi->area != area + || oi->type != OSPF_IFTYPE_BROADCAST + || !IPV4_ADDR_SAME(&oi->address->u.prefix4, + &DR(oi))) { + ospf_schedule_lsa_flush_area(area, new); + return; + } + + if (new->data->type == OSPF_OPAQUE_LINK_LSA) { + ospf_opaque_lsa_refresh(new); + return; + } + + if (oi->network_lsa_self) + oi->network_lsa_self->data->ls_seqnum = + new->data->ls_seqnum; + /* Schedule network-LSA origination. */ + ospf_network_lsa_update(oi); + return; + } + break; + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + ospf_schedule_abr_task(ospf); + break; + case OSPF_AS_EXTERNAL_LSA: + case OSPF_AS_NSSA_LSA: + if ((new->data->type == OSPF_AS_EXTERNAL_LSA) + && CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) { + ospf_translated_nssa_refresh(ospf, NULL, new); + return; + } + + al = (struct as_external_lsa *)new->data; + p.family = AF_INET; + p.prefixlen = ip_masklen(al->mask); + p.prefix = new->data->id; + + ei = ospf_external_info_check(ospf, new); + if (ei) { + if (ospf_external_aggr_match(ospf, &ei->p)) { + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s, Matching external aggregate route found for %pI4, so don't refresh it.", + __func__, + &ei->p.prefix); + + /* Aggregated external route shouldn't + * be in LSDB. + */ + if (!IS_LSA_MAXAGE(new)) + ospf_lsa_flush_as(ospf, new); + + return; + } + + ospf_external_lsa_refresh(ospf, new, ei, + LSA_REFRESH_FORCE, false); + } else { + aggr = (struct ospf_external_aggr_rt *) + ospf_extrenal_aggregator_lookup(ospf, &p); + if (aggr) { + struct external_info ei_aggr; + + memset(&ei_aggr, 0, + sizeof(struct external_info)); + ei_aggr.p = aggr->p; + ei_aggr.tag = aggr->tag; + ei_aggr.instance = ospf->instance; + ei_aggr.route_map_set.metric = -1; + ei_aggr.route_map_set.metric_type = -1; + + ospf_external_lsa_refresh(ospf, new, &ei_aggr, + LSA_REFRESH_FORCE, true); + SET_FLAG(aggr->flags, + OSPF_EXTERNAL_AGGRT_ORIGINATED); + } else + ospf_lsa_flush_as(ospf, new); + } + break; + case OSPF_OPAQUE_AREA_LSA: + ospf_opaque_lsa_refresh(new); + break; + case OSPF_OPAQUE_AS_LSA: + ospf_opaque_lsa_refresh(new); + /* Reconsideration may needed. */ /* XXX */ + break; + default: + break; + } +} + +/* OSPF LSA flooding -- RFC2328 Section 13.(5). */ + +/* Now Updated for NSSA operation, as follows: + + + Type-5's have no change. Blocked to STUB or NSSA. + + Type-7's can be received, and if a DR + they will also flood the local NSSA Area as Type-7's + + If a Self-Originated LSA (now an ASBR), + The LSDB will be updated as Type-5's, (for continual re-fresh) + + If an NSSA-IR it is installed/flooded as Type-7, P-bit on. + if an NSSA-ABR it is installed/flooded as Type-7, P-bit off. + + Later, during the ABR TASK, if the ABR is the Elected NSSA + translator, then All Type-7s (with P-bit ON) are Translated to + Type-5's and flooded to all non-NSSA/STUB areas. + + During ASE Calculations, + non-ABRs calculate external routes from Type-7's + ABRs calculate external routes from Type-5's and non-self Type-7s +*/ +int ospf_flood(struct ospf *ospf, struct ospf_neighbor *nbr, + struct ospf_lsa *current, struct ospf_lsa *new) +{ + struct ospf_interface *oi; + int lsa_ack_flag; + + /* Type-7 LSA's will be flooded throughout their native NSSA area, + but will also be flooded as Type-5's into ABR capable links. */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s:LSA[Flooding]: start, NBR %pI4 (%s), cur(%p), New-LSA[%s]", + ospf_get_name(ospf), &nbr->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + (void *)current, dump_lsa_key(new)); + + oi = nbr->oi; + + /* If there is already a database copy, and if the + database copy was received via flooding and installed less + than MinLSArrival seconds ago, discard the new LSA + (without acknowledging it). */ + if (current != NULL) /* -- endo. */ + { + if (IS_LSA_SELF(current) + && (ntohs(current->data->ls_age) == 0 + && ntohl(current->data->ls_seqnum) + == OSPF_INITIAL_SEQUENCE_NUMBER)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s:LSA[Flooding]: Got a self-originated LSA, while local one is initial instance.", + ospf_get_name(ospf)); + ; /* Accept this LSA for quick LSDB resynchronization. + */ + } else if (monotime_since(¤t->tv_recv, NULL) + < ospf->min_ls_arrival * 1000LL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s:LSA[Flooding]: LSA is received recently.", + ospf_get_name(ospf)); + return -1; + } + } + + /* Flood the new LSA out some subset of the router's interfaces. + In some cases (e.g., the state of the receiving interface is + DR and the LSA was received from a router other than the + Backup DR) the LSA will be flooded back out the receiving + interface. */ + lsa_ack_flag = ospf_flood_through(ospf, nbr, new); + + /* Remove the current database copy from all neighbors' Link state + retransmission lists. AS_EXTERNAL and AS_EXTERNAL_OPAQUE does + ^^^^^^^^^^^^^^^^^^^^^^^ + not have area ID. + All other (even NSSA's) do have area ID. */ + if (current) { + switch (current->data->type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + ospf_ls_retransmit_delete_nbr_as(ospf, current); + break; + default: + ospf_ls_retransmit_delete_nbr_area(oi->area, current); + break; + } + } + + /* Do some internal house keeping that is needed here */ + SET_FLAG(new->flags, OSPF_LSA_RECEIVED); + (void)ospf_lsa_is_self_originated(ospf, new); /* Let it set the flag */ + + /* Received non-self-originated Grace LSA */ + if (IS_GRACE_LSA(new) && !IS_LSA_SELF(new)) { + + if (IS_LSA_MAXAGE(new)) { + + /* Handling Max age grace LSA.*/ + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Received a maxage GRACE-LSA from router %pI4", + __func__, &new->data->adv_router); + + if (current) { + ospf_process_maxage_grace_lsa(ospf, new, nbr); + } else { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Grace LSA doesn't exist in lsdb, so discarding grace lsa", + __func__); + return -1; + } + } else { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Received a GRACE-LSA from router %pI4", + __func__, &new->data->adv_router); + + if (ospf_process_grace_lsa(ospf, new, nbr) + == OSPF_GR_NOT_HELPER) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Not moving to HELPER role, So discarding grace LSA", + __func__); + return -1; + } + } + } + + /* Install the new LSA in the link state database + (replacing the current database copy). This may cause the + routing table calculation to be scheduled. In addition, + timestamp the new LSA with the current time. The flooding + procedure cannot overwrite the newly installed LSA until + MinLSArrival seconds have elapsed. */ + + if (!(new = ospf_lsa_install(ospf, oi, new))) + return -1; /* unknown LSA type or any other error condition */ + + /* check if the installed LSA is an indication LSA */ + if (ospf_check_indication_lsa(new) && !IS_LSA_SELF(new) && + !IS_LSA_MAXAGE(new)) { + new->area->fr_info.area_ind_lsa_recvd = true; + /* check if there are already type 5 LSAs originated + * with DNA bit set, if yes reoriginate those LSAs. + */ + ospf_refresh_dna_type5_and_type7_lsas(ospf); + } + + /* Check if we recived an indication LSA flush on backbone + * network. + */ + ospf_recv_indication_lsa_flush(new); + + if (new->area && OSPF_FR_CONFIG(ospf, new->area)) { + struct lsa_header const *lsah = new->data; + + if (!CHECK_FLAG(lsah->options, OSPF_OPTION_DC) && + !ospf_check_indication_lsa(new)) { + + new->area->fr_info.area_dc_clear = true; + /* check of previously area supported flood reduction */ + if (new->area->fr_info.enabled) { + new->area->fr_info.enabled = false; + OSPF_LOG_DEBUG( + IS_DEBUG_OSPF_EVENT, + "Flood Reduction STATE on -> off by %s LSA", + dump_lsa_key(new)); + /* if yes update all the lsa to the area the + * new LSAs will have DNA bit set to 0. + */ + ospf_refresh_area_self_lsas(new->area); + } + } else if (!new->area->fr_info.enabled) { + /* check again after installing new LSA that area + * supports flood reduction. + */ + ospf_area_update_fr_state(new->area); + if (new->area->fr_info.enabled) { + OSPF_LOG_DEBUG( + IS_DEBUG_OSPF_EVENT, + "Flood Reduction STATE off -> on by %s LSA", + dump_lsa_key(new)); + ospf_refresh_area_self_lsas(new->area); + } + } + } + + /* Acknowledge the receipt of the LSA by sending a Link State + Acknowledgment packet back out the receiving interface. */ + if (lsa_ack_flag) + ospf_flood_delayed_lsa_ack(nbr, new); + + /* If this new LSA indicates that it was originated by the + receiving router itself, the router must take special action, + either updating the LSA or in some cases flushing it from + the routing domain. */ + if (ospf_lsa_is_self_originated(ospf, new)) + ospf_process_self_originated_lsa(ospf, new, oi->area); + else + /* Update statistics value for OSPF-MIB. */ + ospf->rx_lsa_count++; + + return 0; +} + +/* OSPF LSA flooding -- RFC2328 Section 13.3. */ +int ospf_flood_through_interface(struct ospf_interface *oi, + struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) +{ + struct ospf_neighbor *onbr; + struct route_node *rn; + int retx_flag; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: considering int %s (%s), INBR(%pI4), LSA[%s] AGE %u", + __func__, IF_NAME(oi), ospf_get_name(oi->ospf), + inbr ? &inbr->router_id : NULL, dump_lsa_key(lsa), + ntohs(lsa->data->ls_age)); + + if (!ospf_if_is_enable(oi)) + return 0; + + if (IS_OPAQUE_LSA(lsa->data->type) && + !OSPF_IF_PARAM(oi, opaque_capable)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: Skipping interface %s (%s) with opaque disabled.", + __func__, IF_NAME(oi), ospf_get_name(oi->ospf)); + return 0; + } + + /* If flood reduction is configured, set the DC bit on the lsa. */ + if (IS_LSA_SELF(lsa)) { + if (OSPF_FR_CONFIG(oi->area->ospf, oi->area)) { + if (!ospf_check_indication_lsa(lsa)) { + SET_FLAG(lsa->data->options, OSPF_OPTION_DC); + ospf_lsa_checksum(lsa->data); + } + } else if (CHECK_FLAG(lsa->data->options, OSPF_OPTION_DC)) { + UNSET_FLAG(lsa->data->options, OSPF_OPTION_DC); + ospf_lsa_checksum(lsa->data); + } + + /* If flood reduction is enabled then set DNA bit on the + * self lsas. + */ + if (oi->area->fr_info.enabled) + SET_FLAG(lsa->data->ls_age, DO_NOT_AGE); + } + + /* Remember if new LSA is added to a retransmit list. */ + retx_flag = 0; + + /* Each of the neighbors attached to this interface are examined, + to determine whether they must receive the new LSA. The following + steps are executed for each neighbor: */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + struct ospf_lsa *ls_req; + + if (rn->info == NULL) + continue; + + onbr = rn->info; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: considering nbr %pI4 via %s (%s), state: %s", + __func__, &onbr->router_id, IF_NAME(oi), + ospf_get_name(oi->ospf), + lookup_msg(ospf_nsm_state_msg, onbr->state, + NULL)); + + /* If the neighbor is in a lesser state than Exchange, it + does not participate in flooding, and the next neighbor + should be examined. */ + if (onbr->state < NSM_Exchange) + continue; + + /* If the adjacency is not yet full (neighbor state is + Exchange or Loading), examine the Link state request + list associated with this adjacency. If there is an + instance of the new LSA on the list, it indicates that + the neighboring router has an instance of the LSA + already. Compare the new LSA to the neighbor's copy: */ + if (onbr->state < NSM_Full) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: adj to onbr %pI4 is not Full (%s)", + __func__, &onbr->router_id, + lookup_msg(ospf_nsm_state_msg, + onbr->state, NULL)); + ls_req = ospf_ls_request_lookup(onbr, lsa); + if (ls_req != NULL) { + int ret; + + ret = ospf_lsa_more_recent(ls_req, lsa); + /* The new LSA is less recent. */ + if (ret > 0) + continue; + /* The two copies are the same instance, then + delete + the LSA from the Link state request list. */ + else if (ret == 0) { + ospf_ls_request_delete(onbr, ls_req); + ospf_check_nbr_loading(onbr); + continue; + } + /* The new LSA is more recent. Delete the LSA + from the Link state request list. */ + else { + ospf_ls_request_delete(onbr, ls_req); + ospf_check_nbr_loading(onbr); + } + } + } + + if (IS_OPAQUE_LSA(lsa->data->type)) { + if (!CHECK_FLAG(onbr->options, OSPF_OPTION_O)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: Skipping neighbor %s via %pI4 -- Not Opaque-capable.", + __func__, IF_NAME(oi), + &onbr->router_id); + continue; + } + } + + /* If the new LSA was received from this neighbor, + examine the next neighbor. */ + if (inbr) { + /* + * Triggered by LSUpd message parser "ospf_ls_upd ()". + * E.g., all LSAs handling here is received via network. + */ + if (IPV4_ADDR_SAME(&inbr->router_id, + &onbr->router_id)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: Skipping neighbor %s via %pI4 -- inbr == onbr.", + __func__, IF_NAME(oi), + &inbr->router_id); + continue; + } + } else { + /* + * Triggered by MaxAge remover, so far. + * NULL "inbr" means flooding starts from this node. + */ + if (IPV4_ADDR_SAME(&lsa->data->adv_router, + &onbr->router_id)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: Skipping neighbor %s via %pI4 -- lsah->adv_router == onbr.", + __func__, IF_NAME(oi), + &onbr->router_id); + continue; + } + } + + /* Add the new LSA to the Link state retransmission list + for the adjacency. The LSA will be retransmitted + at intervals until an acknowledgment is seen from + the neighbor. */ + ospf_ls_retransmit_add(onbr, lsa); + retx_flag = 1; + } + + /* If in the previous step, the LSA was NOT added to any of + the Link state retransmission lists, there is no need to + flood the LSA out the interface. */ + if (retx_flag == 0) { + return (inbr && inbr->oi == oi); + } + + /* if we've received the lsa on this interface we need to perform + additional checking */ + if (inbr && (inbr->oi == oi)) { + /* If the new LSA was received on this interface, and it was + received from either the Designated Router or the Backup + Designated Router, chances are that all the neighbors have + received the LSA already. */ + if (NBR_IS_DR(inbr) || NBR_IS_BDR(inbr)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("%s: DR/BDR NOT SEND to int %s (%s)", + __func__, IF_NAME(oi), + ospf_get_name(oi->ospf)); + return 1; + } + + /* If the new LSA was received on this interface, and the + interface state is Backup, examine the next interface. The + Designated Router will do the flooding on this interface. + However, if the Designated Router fails the router will + end up retransmitting the updates. */ + + if (oi->state == ISM_Backup) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: ISM_Backup NOT SEND to int %s (%s)", + __func__, IF_NAME(oi), + ospf_get_name(oi->ospf)); + return 1; + } + } + + /* The LSA must be flooded out the interface. Send a Link State + Update packet (including the new LSA as contents) out the + interface. The LSA's LS age must be incremented by InfTransDelay + (which must be > 0) when it is copied into the outgoing Link + State Update packet (until the LS age field reaches the maximum + value of MaxAge). */ + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("%s: DR/BDR sending upd to int %s (%s)", __func__, + IF_NAME(oi), ospf_get_name(oi->ospf)); + + /* RFC2328 Section 13.3 + On non-broadcast networks, separate Link State Update + packets must be sent, as unicasts, to each adjacent neighbor + (i.e., those in state Exchange or greater). The destination + IP addresses for these packets are the neighbors' IP + addresses. This behavior is extended to P2MP networks which + don't support broadcast. */ + if (OSPF_IF_NON_BROADCAST(oi)) { + struct ospf_neighbor *nbr; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + if (nbr != oi->nbr_self && nbr->state >= NSM_Exchange) + ospf_ls_upd_send_lsa(nbr, lsa, + OSPF_SEND_PACKET_DIRECT); + } + } else + /* If P2MP delayed reflooding is configured and the LSA was + received from a neighbor on the P2MP interface, do not flood + if back out on the interface. The LSA will be retransmitted + upon expiration of each neighbor's retransmission timer. This + will allow time to receive a multicast multicast link state + acknoweldgement and remove the LSA from each neighbor's link + state retransmission list. */ + if (oi->p2mp_delay_reflood && + (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) && + (inbr != NULL) && (oi == inbr->oi)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "Delay reflooding for LSA[%s] from NBR %pI4 on interface %s", + dump_lsa_key(lsa), + inbr ? &(inbr->router_id) + : &(oi->ospf->router_id), + IF_NAME(oi)); + } else + ospf_ls_upd_send_lsa(oi->nbr_self, lsa, + OSPF_SEND_PACKET_INDIRECT); + + return 0; +} + +int ospf_flood_through_area(struct ospf_area *area, struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + int lsa_ack_flag = 0; + + assert(area); + /* All other types are specific to a single area (Area A). The + eligible interfaces are all those interfaces attaching to the + Area A. If Area A is the backbone, this includes all the virtual + links. */ + for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) { + if (area->area_id.s_addr != OSPF_AREA_BACKBONE + && oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + if ((lsa->data->type == OSPF_OPAQUE_LINK_LSA) + && (lsa->oi != oi)) { + /* + * Link local scoped Opaque-LSA should only be flooded + * for the link on which the LSA has received. + */ + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "Type-9 Opaque-LSA: lsa->oi(%p) != oi(%p)", + (void *)lsa->oi, (void *)oi); + continue; + } + + if (ospf_flood_through_interface(oi, inbr, lsa)) + lsa_ack_flag = 1; + } + + return (lsa_ack_flag); +} + +int ospf_flood_through_as(struct ospf *ospf, struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) +{ + struct listnode *node; + struct ospf_area *area; + int lsa_ack_flag; + + lsa_ack_flag = 0; + + /* The incoming LSA is type 5 or type 7 (AS-EXTERNAL or AS-NSSA ) + + Divert the Type-5 LSA's to all non-NSSA/STUB areas + + Divert the Type-7 LSA's to all NSSA areas + + AS-external-LSAs are flooded throughout the entire AS, with the + exception of stub areas (see Section 3.6). The eligible + interfaces are all the router's interfaces, excluding virtual + links and those interfaces attaching to stub areas. */ + + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) /* Translated from 7 */ + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("Flood/AS: NSSA TRANSLATED LSA"); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + int continue_flag = 0; + struct listnode *if_node; + struct ospf_interface *oi; + + switch (area->external_routing) { + /* Don't send AS externals into stub areas. Various types + of support for partial stub areas can be implemented + here. NSSA's will receive Type-7's that have areas + matching the originl LSA. */ + case OSPF_AREA_NSSA: /* Sending Type 5 or 7 into NSSA area */ + /* Type-7, flood NSSA area */ + if (lsa->data->type == OSPF_AS_NSSA_LSA + && area == lsa->area) + /* We will send it. */ + continue_flag = 0; + else + continue_flag = 1; /* Skip this NSSA area for + Type-5's et al */ + break; + + case OSPF_AREA_TYPE_MAX: + case OSPF_AREA_STUB: + continue_flag = 1; /* Skip this area. */ + break; + + case OSPF_AREA_DEFAULT: + default: + /* No Type-7 into normal area */ + if (lsa->data->type == OSPF_AS_NSSA_LSA) + continue_flag = 1; /* skip Type-7 */ + else + continue_flag = 0; /* Do this area. */ + break; + } + + /* Do continue for above switch. Saves a big if then mess */ + if (continue_flag) + continue; /* main for-loop */ + + /* send to every interface in this area */ + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, if_node, oi)) { + /* Skip virtual links */ + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + if (ospf_flood_through_interface(oi, inbr, + lsa)) /* lsa */ + lsa_ack_flag = 1; + } + } /* main area for-loop */ + + return (lsa_ack_flag); +} + +int ospf_flood_through(struct ospf *ospf, struct ospf_neighbor *inbr, + struct ospf_lsa *lsa) +{ + int lsa_ack_flag = 0; + + /* Type-7 LSA's for NSSA are flooded throughout the AS here, and + upon return are updated in the LSDB for Type-7's. Later, + re-fresh will re-send them (and also, if ABR, packet code will + translate to Type-5's) + + As usual, Type-5 LSA's (if not DISCARDED because we are STUB or + NSSA) are flooded throughout the AS, and are updated in the + global table. */ + /* + * At the common sub-sub-function "ospf_flood_through_interface()", + * a parameter "inbr" will be used to distinguish the called context + * whether the given LSA was received from the neighbor, or the + * flooding for the LSA starts from this node (e.g. the LSA was self- + * originated, or the LSA is going to be flushed from routing domain). + * + * So, for consistency reasons, this function "ospf_flood_through()" + * should also allow the usage that the given "inbr" parameter to be + * NULL. If we do so, corresponding AREA parameter should be referred + * by "lsa->area", instead of "inbr->oi->area". + */ + switch (lsa->data->type) { + case OSPF_AS_EXTERNAL_LSA: /* Type-5 */ + case OSPF_OPAQUE_AS_LSA: + lsa_ack_flag = ospf_flood_through_as(ospf, inbr, lsa); + break; + /* Type-7 Only received within NSSA, then flooded */ + case OSPF_AS_NSSA_LSA: + /* Any P-bit was installed with the Type-7. */ + + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: LOCAL NSSA FLOOD of Type-7.", __func__); + fallthrough; + default: + lsa_ack_flag = ospf_flood_through_area(lsa->area, inbr, lsa); + break; + } + + /* always need to send ack when incoming intf is PTP or P2MP */ + if (inbr != NULL && (inbr->oi->type == OSPF_IFTYPE_POINTOMULTIPOINT || + inbr->oi->type == OSPF_IFTYPE_POINTOPOINT)) + lsa_ack_flag = 1; + + return (lsa_ack_flag); +} + + +/* Management functions for neighbor's Link State Request list. */ +void ospf_ls_request_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + /* + * We cannot make use of the newly introduced callback function + * "lsdb->new_lsa_hook" to replace debug output below, just because + * it seems no simple and smart way to pass neighbor information to + * the common function "ospf_lsdb_add()" -- endo. + */ + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("RqstL(%lu)++, NBR(%pI4(%s)), LSA[%s]", + ospf_ls_request_count(nbr), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), dump_lsa_key(lsa)); + + ospf_lsdb_add(&nbr->ls_req, lsa); +} + +unsigned long ospf_ls_request_count(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_count_all(&nbr->ls_req); +} + +int ospf_ls_request_isempty(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_isempty(&nbr->ls_req); +} + +/* Remove LSA from neighbor's ls-request list. */ +void ospf_ls_request_delete(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + if (nbr->ls_req_last == lsa) { + ospf_lsa_unlock(&nbr->ls_req_last); + nbr->ls_req_last = NULL; + } + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) /* -- endo. */ + zlog_debug("RqstL(%lu)--, NBR(%pI4(%s)), LSA[%s]", + ospf_ls_request_count(nbr), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), dump_lsa_key(lsa)); + + ospf_lsdb_delete(&nbr->ls_req, lsa); +} + +/* Remove all LSA from neighbor's ls-requenst list. */ +void ospf_ls_request_delete_all(struct ospf_neighbor *nbr) +{ + ospf_lsa_unlock(&nbr->ls_req_last); + nbr->ls_req_last = NULL; + ospf_lsdb_delete_all(&nbr->ls_req); +} + +/* Lookup LSA from neighbor's ls-request list. */ +struct ospf_lsa *ospf_ls_request_lookup(struct ospf_neighbor *nbr, + struct ospf_lsa *lsa) +{ + return ospf_lsdb_lookup(&nbr->ls_req, lsa); +} + +struct ospf_lsa *ospf_ls_request_new(struct lsa_header *lsah) +{ + struct ospf_lsa *new; + + new = ospf_lsa_new_and_data(OSPF_LSA_HEADER_SIZE); + memcpy(new->data, lsah, OSPF_LSA_HEADER_SIZE); + + return new; +} + + +/* Management functions for neighbor's ls-retransmit list. */ +unsigned long ospf_ls_retransmit_count(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_count_all(&nbr->ls_rxmt); +} + +unsigned long ospf_ls_retransmit_count_self(struct ospf_neighbor *nbr, + int lsa_type) +{ + return ospf_lsdb_count_self(&nbr->ls_rxmt, lsa_type); +} + +int ospf_ls_retransmit_isempty(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_isempty(&nbr->ls_rxmt); +} + +/* Add LSA to be retransmitted to neighbor's ls-retransmit list. */ +void ospf_ls_retransmit_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + struct ospf_lsa *old; + + old = ospf_ls_retransmit_lookup(nbr, lsa); + + if (ospf_lsa_more_recent(old, lsa) < 0) { + if (old) { + old->retransmit_counter--; + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("RXmtL(%lu)--, NBR(%pI4(%s)), LSA[%s]", + ospf_ls_retransmit_count(nbr), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + dump_lsa_key(old)); + ospf_lsdb_delete(&nbr->ls_rxmt, old); + } + lsa->retransmit_counter++; + /* + * We cannot make use of the newly introduced callback function + * "lsdb->new_lsa_hook" to replace debug output below, just + * because + * it seems no simple and smart way to pass neighbor information + * to + * the common function "ospf_lsdb_add()" -- endo. + */ + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("RXmtL(%lu)++, NBR(%pI4(%s)), LSA[%s]", + ospf_ls_retransmit_count(nbr), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + dump_lsa_key(lsa)); + ospf_lsdb_add(&nbr->ls_rxmt, lsa); + } +} + +/* Remove LSA from neibghbor's ls-retransmit list. */ +void ospf_ls_retransmit_delete(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + if (ospf_ls_retransmit_lookup(nbr, lsa)) { + lsa->retransmit_counter--; + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) /* -- endo. */ + zlog_debug("RXmtL(%lu)--, NBR(%pI4(%s)), LSA[%s]", + ospf_ls_retransmit_count(nbr), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + dump_lsa_key(lsa)); + ospf_lsdb_delete(&nbr->ls_rxmt, lsa); + } +} + +/* Clear neighbor's ls-retransmit list. */ +void ospf_ls_retransmit_clear(struct ospf_neighbor *nbr) +{ + struct ospf_lsdb *lsdb; + int i; + + lsdb = &nbr->ls_rxmt; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + struct route_table *table = lsdb->type[i].db; + struct route_node *rn; + struct ospf_lsa *lsa; + + for (rn = route_top(table); rn; rn = route_next(rn)) + if ((lsa = rn->info) != NULL) + ospf_ls_retransmit_delete(nbr, lsa); + } + + ospf_lsa_unlock(&nbr->ls_req_last); + nbr->ls_req_last = NULL; +} + +/* Lookup LSA from neighbor's ls-retransmit list. */ +struct ospf_lsa *ospf_ls_retransmit_lookup(struct ospf_neighbor *nbr, + struct ospf_lsa *lsa) +{ + return ospf_lsdb_lookup(&nbr->ls_rxmt, lsa); +} + +static void ospf_ls_retransmit_delete_nbr_if(struct ospf_interface *oi, + struct ospf_lsa *lsa) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + struct ospf_lsa *lsr; + + if (ospf_if_is_enable(oi)) + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + /* If LSA find in LS-retransmit list, then remove it. */ + nbr = rn->info; + + if (!nbr) + continue; + + lsr = ospf_ls_retransmit_lookup(nbr, lsa); + + /* If LSA find in ls-retransmit list, remove it. */ + if (lsr != NULL && + lsr->data->ls_seqnum == lsa->data->ls_seqnum) + ospf_ls_retransmit_delete(nbr, lsr); + } +} + +void ospf_ls_retransmit_delete_nbr_area(struct ospf_area *area, + struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) + ospf_ls_retransmit_delete_nbr_if(oi, lsa); +} + +void ospf_ls_retransmit_delete_nbr_as(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) + ospf_ls_retransmit_delete_nbr_if(oi, lsa); +} + + +/* Sets ls_age to MaxAge and floods throu the area. + When we implement ASE routing, there will be another function + flushing an LSA from the whole domain. */ +void ospf_lsa_flush_area(struct ospf_lsa *lsa, struct ospf_area *area) +{ + struct ospf *ospf = area->ospf; + + if (ospf_lsa_is_self_originated(ospf, lsa) + && ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s:LSA[Type%d:%pI4]: Graceful Restart in progress -- not flushing self-originated LSA", + ospf_get_name(ospf), lsa->data->type, + &lsa->data->id); + return; + } + + /* Reset the lsa origination time such that it gives + more time for the ACK to be received and avoid + retransmissions */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: MaxAge set to LSA[%s]", __func__, + dump_lsa_key(lsa)); + monotime(&lsa->tv_recv); + lsa->tv_orig = lsa->tv_recv; + ospf_flood_through_area(area, NULL, lsa); + ospf_lsa_maxage(ospf, lsa); +} + +void ospf_lsa_flush_as(struct ospf *ospf, struct ospf_lsa *lsa) +{ + if (ospf_lsa_is_self_originated(ospf, lsa) + && ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s:LSA[Type%d:%pI4]: Graceful Restart in progress -- not flushing self-originated LSA", + ospf_get_name(ospf), lsa->data->type, + &lsa->data->id); + return; + } + + /* Reset the lsa origination time such that it gives + more time for the ACK to be received and avoid + retransmissions */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: MaxAge set to LSA[%s]", __func__, + dump_lsa_key(lsa)); + monotime(&lsa->tv_recv); + lsa->tv_orig = lsa->tv_recv; + ospf_flood_through_as(ospf, NULL, lsa); + ospf_lsa_maxage(ospf, lsa); +} + +void ospf_lsa_flush(struct ospf *ospf, struct ospf_lsa *lsa) +{ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + case OSPF_NETWORK_LSA: + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + case OSPF_AS_NSSA_LSA: + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_lsa_flush_area(lsa, lsa->area); + break; + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + ospf_lsa_flush_as(ospf, lsa); + break; + default: + zlog_info("%s: Unknown LSA type %u", __func__, lsa->data->type); + break; + } +} diff --git a/ospfd/ospf_flood.h b/ospfd/ospf_flood.h new file mode 100644 index 0000000..3757400 --- /dev/null +++ b/ospfd/ospf_flood.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Flooding -- RFC2328 Section 13. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_FLOOD_H +#define _ZEBRA_OSPF_FLOOD_H + +extern int ospf_flood(struct ospf *, struct ospf_neighbor *, struct ospf_lsa *, + struct ospf_lsa *); +extern int ospf_flood_through(struct ospf *, struct ospf_neighbor *, + struct ospf_lsa *); +extern int ospf_flood_through_area(struct ospf_area *, struct ospf_neighbor *, + struct ospf_lsa *); +extern int ospf_flood_through_as(struct ospf *, struct ospf_neighbor *, + struct ospf_lsa *); +extern int ospf_flood_through_interface(struct ospf_interface *oi, + struct ospf_neighbor *inbr, + struct ospf_lsa *lsa); + +extern unsigned long ospf_ls_request_count(struct ospf_neighbor *); +extern int ospf_ls_request_isempty(struct ospf_neighbor *); +extern struct ospf_lsa *ospf_ls_request_new(struct lsa_header *); +extern void ospf_ls_request_free(struct ospf_lsa *); +extern void ospf_ls_request_add(struct ospf_neighbor *, struct ospf_lsa *); +extern void ospf_ls_request_delete(struct ospf_neighbor *, struct ospf_lsa *); +extern void ospf_ls_request_delete_all(struct ospf_neighbor *); +extern struct ospf_lsa *ospf_ls_request_lookup(struct ospf_neighbor *, + struct ospf_lsa *); + +extern unsigned long ospf_ls_retransmit_count(struct ospf_neighbor *); +extern unsigned long ospf_ls_retransmit_count_self(struct ospf_neighbor *, int); +extern int ospf_ls_retransmit_isempty(struct ospf_neighbor *); +extern void ospf_ls_retransmit_add(struct ospf_neighbor *, struct ospf_lsa *); +extern void ospf_ls_retransmit_delete(struct ospf_neighbor *, + struct ospf_lsa *); +extern void ospf_ls_retransmit_clear(struct ospf_neighbor *); +extern struct ospf_lsa *ospf_ls_retransmit_lookup(struct ospf_neighbor *, + struct ospf_lsa *); +extern void ospf_ls_retransmit_delete_nbr_area(struct ospf_area *, + struct ospf_lsa *); +extern void ospf_ls_retransmit_delete_nbr_as(struct ospf *, struct ospf_lsa *); +extern void ospf_ls_retransmit_add_nbr_all(struct ospf_interface *, + struct ospf_lsa *); + +extern void ospf_flood_lsa_area(struct ospf_lsa *, struct ospf_area *); +extern void ospf_flood_lsa_as(struct ospf_lsa *); +extern void ospf_lsa_flush_area(struct ospf_lsa *, struct ospf_area *); +extern void ospf_lsa_flush_as(struct ospf *, struct ospf_lsa *); +extern void ospf_lsa_flush(struct ospf *, struct ospf_lsa *); +extern struct external_info *ospf_external_info_check(struct ospf *, + struct ospf_lsa *); + +extern void ospf_lsdb_init(struct ospf_lsdb *); +extern void ospf_area_update_fr_state(struct ospf_area *area); +extern void ospf_refresh_dna_type5_and_type7_lsas(struct ospf *ospf); + +#endif /* _ZEBRA_OSPF_FLOOD_H */ diff --git a/ospfd/ospf_gr.c b/ospfd/ospf_gr.c new file mode 100644 index 0000000..0a4d579 --- /dev/null +++ b/ospfd/ospf_gr.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC 3623 Graceful OSPF Restart. + * + * Copyright 2021 NetDEF (c), All rights reserved. + * Copyright 2020 6WIND (c), All rights reserved. + */ + +#include + +#include "memory.h" +#include "command.h" +#include "table.h" +#include "vty.h" +#include "log.h" +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_opaque.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_gr_clippy.c" + +static void ospf_gr_grace_period_expired(struct event *thread); + +/* Lookup self-originated Grace-LSA in the LSDB. */ +static struct ospf_lsa *ospf_gr_lsa_lookup(struct ospf *ospf, + struct ospf_area *area) +{ + struct ospf_lsa *lsa; + struct in_addr lsa_id; + uint32_t lsa_id_host_byte_order; + + lsa_id_host_byte_order = SET_OPAQUE_LSID(OPAQUE_TYPE_GRACE_LSA, 0); + lsa_id.s_addr = htonl(lsa_id_host_byte_order); + lsa = ospf_lsa_lookup(ospf, area, OSPF_OPAQUE_LINK_LSA, lsa_id, + ospf->router_id); + + return lsa; +} + +/* Fill in fields of the Grace-LSA that is being originated. */ +static void ospf_gr_lsa_body_set(struct ospf_gr_info *gr_info, + struct ospf_interface *oi, + enum ospf_gr_restart_reason reason, + struct stream *s) +{ + struct grace_tlv_graceperiod tlv_period = {}; + struct grace_tlv_restart_reason tlv_reason = {}; + struct grace_tlv_restart_addr tlv_address = {}; + + /* Put grace period. */ + tlv_period.header.type = htons(GRACE_PERIOD_TYPE); + tlv_period.header.length = htons(GRACE_PERIOD_LENGTH); + tlv_period.interval = htonl(gr_info->grace_period); + stream_put(s, &tlv_period, sizeof(tlv_period)); + + /* Put restart reason. */ + tlv_reason.header.type = htons(RESTART_REASON_TYPE); + tlv_reason.header.length = htons(RESTART_REASON_LENGTH); + tlv_reason.reason = reason; + stream_put(s, &tlv_reason, sizeof(tlv_reason)); + + /* Put IP address. */ + if (oi->type == OSPF_IFTYPE_BROADCAST || oi->type == OSPF_IFTYPE_NBMA + || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + tlv_address.header.type = htons(RESTARTER_IP_ADDR_TYPE); + tlv_address.header.length = htons(RESTARTER_IP_ADDR_LEN); + tlv_address.addr = oi->address->u.prefix4; + stream_put(s, &tlv_address, sizeof(tlv_address)); + } +} + +/* Generate Grace-LSA for a given interface. */ +static struct ospf_lsa *ospf_gr_lsa_new(struct ospf_interface *oi, + enum ospf_gr_restart_reason reason) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + uint8_t options, lsa_type; + struct in_addr lsa_id; + uint32_t lsa_id_host_byte_order; + uint16_t length; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = LSA_OPTIONS_GET(oi->area); + options |= LSA_OPTIONS_NSSA_GET(oi->area); + options |= OSPF_OPTION_O; + + lsa_type = OSPF_OPAQUE_LINK_LSA; + lsa_id_host_byte_order = SET_OPAQUE_LSID(OPAQUE_TYPE_GRACE_LSA, 0); + lsa_id.s_addr = htonl(lsa_id_host_byte_order); + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, oi->ospf->router_id); + + /* Set opaque-LSA body fields. */ + ospf_gr_lsa_body_set(&oi->ospf->gr_info, oi, reason, s); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + if (IS_DEBUG_OSPF_GR) + zlog_debug("LSA[Type%d:%pI4]: Create an Opaque-LSA/GR instance", + lsa_type, &lsa_id); + + new->area = oi->area; + new->oi = oi; + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Originate and install Grace-LSA for a given interface. */ +static void ospf_gr_lsa_originate(struct ospf_interface *oi, + enum ospf_gr_restart_reason reason, + bool maxage) +{ + struct ospf_lsa *lsa, *old; + + /* Skip originating a Grace-LSA when not necessary. */ + if (!if_is_operative(oi->ifp) || if_is_loopback(oi->ifp) || + (reason != OSPF_GR_UNKNOWN_RESTART && + ospf_interface_neighbor_count(oi) == 0)) + return; + + /* Create new Grace-LSA. */ + lsa = ospf_gr_lsa_new(oi, reason); + if (!lsa) { + zlog_warn("%s: ospf_gr_lsa_new() failed", __func__); + return; + } + + if (maxage) + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + + /* Find the old LSA and increase the seqno. */ + old = ospf_gr_lsa_lookup(oi->ospf, oi->area); + if (old) + lsa->data->ls_seqnum = lsa_seqnum_increment(old); + + if (!maxage && reason == OSPF_GR_UNKNOWN_RESTART) { + struct list *update; + struct in_addr addr; + + /* + * When performing an unplanned restart, send a handcrafted + * Grace-LSA since the interface isn't fully initialized yet. + */ + ospf_lsa_checksum(lsa->data); + ospf_lsa_lock(lsa); + update = list_new(); + listnode_add(update, lsa); + addr.s_addr = htonl(OSPF_ALLSPFROUTERS); + ospf_ls_upd_queue_send(oi, update, addr, true); + list_delete(&update); + ospf_lsa_discard(lsa); + } else { + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(oi->ospf, oi, lsa) == NULL) { + zlog_warn("%s: ospf_lsa_install() failed", __func__); + ospf_lsa_unlock(&lsa); + return; + } + + /* Flood the LSA through out the interface */ + ospf_flood_through_interface(oi, NULL, lsa); + } + + /* Update new LSA origination count. */ + oi->ospf->lsa_originate_count++; +} + +/* Flush all self-originated Grace-LSAs. */ +static void ospf_gr_flush_grace_lsas(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *anode; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, anode, area)) { + struct ospf_interface *oi; + struct listnode *inode; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, inode, oi)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: flushing self-originated Grace-LSA [area %pI4] [interface %s]", + &area->area_id, oi->ifp->name); + + ospf_gr_lsa_originate(oi, ospf->gr_info.reason, true); + } + } +} + +/* Exit from the Graceful Restart mode. */ +static void ospf_gr_restart_exit(struct ospf *ospf, const char *reason) +{ + struct ospf_area *area; + struct listnode *onode, *anode; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("GR: exiting graceful restart: %s", reason); + + ospf->gr_info.restart_in_progress = false; + EVENT_OFF(ospf->gr_info.t_grace_period); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, onode, area)) { + struct ospf_interface *oi; + + /* + * 1) The router should reoriginate its router-LSAs for all + * attached areas in order to make sure they have the correct + * contents. + */ + ospf_router_lsa_update_area(area); + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, anode, oi)) { + /* Disable hello delay. */ + if (oi->gr.hello_delay.t_grace_send) { + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, + ospf_hello_timer, 1); + } + + /* + * 2) The router should reoriginate network-LSAs on all + * segments where it is the Designated Router. + */ + if (oi->state == ISM_DR) + ospf_network_lsa_update(oi); + } + } + + /* + * 5) Any received self-originated LSAs that are no longer valid should + * be flushed. + */ + ospf_schedule_abr_task(ospf); + + /* + * 3) The router reruns its OSPF routing calculations, this time + * installing the results into the system forwarding table, and + * originating summary-LSAs, Type-7 LSAs and AS-external-LSAs as + * necessary. + * + * 4) Any remnant entries in the system forwarding table that were + * installed before the restart, but that are no longer valid, + * should be removed. + */ + ospf->gr_info.finishing_restart = true; + XFREE(MTYPE_TMP, ospf->gr_info.exit_reason); + ospf->gr_info.exit_reason = XSTRDUP(MTYPE_TMP, reason); + ospf_spf_calculate_schedule(ospf, SPF_FLAG_GR_FINISH); + + /* 6) Any grace-LSAs that the router originated should be flushed. */ + ospf_gr_flush_grace_lsas(ospf); +} + +/* Enter the Graceful Restart mode. */ +void ospf_gr_restart_enter(struct ospf *ospf, + enum ospf_gr_restart_reason reason, time_t timestamp) +{ + unsigned long remaining_time; + + ospf->gr_info.restart_in_progress = true; + ospf->gr_info.reason = reason; + + /* Schedule grace period timeout. */ + remaining_time = timestamp - time(NULL); + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: remaining time until grace period expires: %lu(s)", + remaining_time); + + event_add_timer(master, ospf_gr_grace_period_expired, ospf, + remaining_time, &ospf->gr_info.t_grace_period); +} + +/* Check if a Router-LSA contains a given link. */ +static bool ospf_router_lsa_contains_adj(struct ospf_lsa *lsa, + struct in_addr *id) +{ + struct router_lsa *rl; + + rl = (struct router_lsa *)lsa->data; + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + + if (rl->link[i].type != LSA_LINK_TYPE_POINTOPOINT) + continue; + + if (IPV4_ADDR_SAME(id, link_id)) + return true; + } + + return false; +} + +static bool ospf_gr_check_router_lsa_consistency(struct ospf *ospf, + struct ospf_area *area, + struct ospf_lsa *lsa) +{ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_SELF)) { + struct ospf_lsa *lsa_self = lsa; + struct router_lsa *rl = (struct router_lsa *)lsa->data; + + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + struct ospf_lsa *lsa_adj; + + if (rl->link[i].type != LSA_LINK_TYPE_POINTOPOINT) + continue; + + lsa_adj = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + *link_id); + if (!lsa_adj) + continue; + + if (!ospf_router_lsa_contains_adj(lsa_adj, + &lsa_self->data->id)) + return false; + } + } else { + struct ospf_lsa *lsa_self; + + lsa_self = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + ospf->router_id); + if (!lsa_self + || !CHECK_FLAG(lsa_self->flags, OSPF_LSA_RECEIVED)) + return true; + + if (ospf_router_lsa_contains_adj(lsa, &ospf->router_id) + != ospf_router_lsa_contains_adj(lsa_self, &lsa->data->id)) + return false; + } + + return true; +} + +/* + * Check for LSAs that are inconsistent with the pre-restart LSAs, and abort the + * ongoing graceful restart when that's the case. + */ +void ospf_gr_check_lsdb_consistency(struct ospf *ospf, struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + + for (rn = route_top(ROUTER_LSDB(area)); rn; rn = route_next(rn)) { + lsa = rn->info; + if (!lsa) + continue; + + if (!ospf_gr_check_router_lsa_consistency(ospf, area, lsa)) { + char reason[256]; + + snprintfrr(reason, sizeof(reason), + "detected inconsistent LSA[%s] [area %pI4]", + dump_lsa_key(lsa), &area->area_id); + ospf_gr_restart_exit(ospf, reason); + route_unlock_node(rn); + return; + } + } +} + +/* Lookup neighbor by address in a given OSPF area. */ +static struct ospf_neighbor * +ospf_area_nbr_lookup_by_addr(struct ospf_area *area, struct in_addr *addr) +{ + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_addr(oi->nbrs, addr); + if (nbr) + return nbr; + } + + return NULL; +} + +/* Lookup neighbor by Router ID in a given OSPF area. */ +static struct ospf_neighbor * +ospf_area_nbr_lookup_by_routerid(struct ospf_area *area, struct in_addr *id) +{ + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, id); + if (nbr) + return nbr; + } + + return NULL; +} + +/* Check if there's a fully formed adjacency with the given neighbor ID. */ +static bool ospf_gr_check_adj_id(struct ospf_area *area, + struct in_addr *nbr_id) +{ + struct ospf_neighbor *nbr; + + nbr = ospf_area_nbr_lookup_by_routerid(area, nbr_id); + if (!nbr || nbr->state < NSM_Full) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("GR: missing adjacency to router %pI4", + nbr_id); + return false; + } + + return true; +} + +static bool ospf_gr_check_adjs_lsa_transit(struct ospf_area *area, + struct in_addr *link_id) +{ + struct ospf *ospf = area->ospf; + struct ospf_interface *oi; + + /* + * Check if the transit network refers to a local interface (in which + * case it must be a DR for that network). + */ + oi = ospf_if_lookup_by_local_addr(ospf, NULL, *link_id); + if (oi) { + struct ospf_lsa *lsa; + struct network_lsa *nlsa; + size_t cnt; + + /* Lookup Network LSA corresponding to this interface. */ + lsa = ospf_lsa_lookup_by_id(area, OSPF_NETWORK_LSA, *link_id); + if (!lsa) + return false; + + /* Iterate over all routers present in the network. */ + nlsa = (struct network_lsa *)lsa->data; + cnt = (lsa->size - (OSPF_LSA_HEADER_SIZE + 4)) / 4; + for (size_t i = 0; i < cnt; i++) { + struct in_addr *nbr_id = &nlsa->routers[i]; + + /* Skip self in the pseudonode. */ + if (IPV4_ADDR_SAME(nbr_id, &ospf->router_id)) + continue; + + /* + * Check if there's a fully formed adjacency with this + * router. + */ + if (!ospf_gr_check_adj_id(area, nbr_id)) + return false; + } + } else { + struct ospf_neighbor *nbr; + + /* Check if there's a fully formed adjacency with the DR. */ + nbr = ospf_area_nbr_lookup_by_addr(area, link_id); + if (!nbr || nbr->state < NSM_Full) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: missing adjacency to DR router %pI4", + link_id); + return false; + } + } + + return true; +} + +static bool ospf_gr_check_adjs_lsa(struct ospf_area *area, struct ospf_lsa *lsa) +{ + struct router_lsa *rl = (struct router_lsa *)lsa->data; + + for (int i = 0; i < ntohs(rl->links); i++) { + struct in_addr *link_id = &rl->link[i].link_id; + + switch (rl->link[i].type) { + case LSA_LINK_TYPE_POINTOPOINT: + if (!ospf_gr_check_adj_id(area, link_id)) + return false; + break; + case LSA_LINK_TYPE_TRANSIT: + if (!ospf_gr_check_adjs_lsa_transit(area, link_id)) + return false; + break; + default: + break; + } + } + + return true; +} + +/* + * Check if all adjacencies prior to the restart were reestablished. + * + * This is done using pre-restart Router LSAs and pre-restart Network LSAs + * received from the helping neighbors. + */ +void ospf_gr_check_adjs(struct ospf *ospf) +{ + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + struct ospf_lsa *lsa_self; + + lsa_self = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + ospf->router_id); + if (!lsa_self || !ospf_gr_check_adjs_lsa(area, lsa_self)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: not all adjacencies were reestablished yet [area %pI4]", + &area->area_id); + return; + } + } + + ospf_gr_restart_exit(ospf, "all adjacencies were reestablished"); +} + +/* Handling of grace period expiry. */ +static void ospf_gr_grace_period_expired(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + + ospf->gr_info.t_grace_period = NULL; + ospf_gr_restart_exit(ospf, "grace period has expired"); +} + +/* Send extra Grace-LSA out the interface (unplanned outages only). */ +void ospf_gr_iface_send_grace_lsa(struct event *thread) +{ + struct ospf_interface *oi = EVENT_ARG(thread); + struct ospf_if_params *params = IF_DEF_PARAMS(oi->ifp); + + ospf_gr_lsa_originate(oi, oi->ospf->gr_info.reason, false); + + if (++oi->gr.hello_delay.elapsed_seconds < params->v_gr_hello_delay) + event_add_timer(master, ospf_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); + else + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); +} + +/* + * Record in non-volatile memory that the given OSPF instance is attempting to + * perform a graceful restart. + */ +static void ospf_gr_nvm_update(struct ospf *ospf, bool prepare) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + + inst_name = ospf_get_name(ospf); + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_int_add(json_instance, "gracePeriod", + ospf->gr_info.grace_period); + + /* + * Record not only the grace period, but also a UNIX timestamp + * corresponding to the end of that period. That way, once ospfd is + * restarted, it will be possible to take into account the time that + * passed while ospfd wasn't running. + */ + if (prepare) + json_object_int_add(json_instance, "timestamp", + time(NULL) + ospf->gr_info.grace_period); + + frr_daemon_state_save(&json); +} + +/* + * Delete GR status information about the given OSPF instance from non-volatile + * memory. + */ +void ospf_gr_nvm_delete(struct ospf *ospf) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + + inst_name = ospf_get_name(ospf); + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_del(json_instances, inst_name); + + frr_daemon_state_save(&json); +} + +/* + * Fetch from non-volatile memory whether the given OSPF instance is performing + * a graceful shutdown or not. + */ +void ospf_gr_nvm_read(struct ospf *ospf) +{ + const char *inst_name; + json_object *json; + json_object *json_instances; + json_object *json_instance; + json_object *json_timestamp; + json_object *json_grace_period; + time_t timestamp = 0; + + inst_name = ospf_get_name(ospf); + + json = frr_daemon_state_load(); + + json_object_object_get_ex(json, "instances", &json_instances); + if (!json_instances) { + json_instances = json_object_new_object(); + json_object_object_add(json, "instances", json_instances); + } + + json_object_object_get_ex(json_instances, inst_name, &json_instance); + if (!json_instance) { + json_instance = json_object_new_object(); + json_object_object_add(json_instances, inst_name, + json_instance); + } + + json_object_object_get_ex(json_instance, "gracePeriod", + &json_grace_period); + json_object_object_get_ex(json_instance, "timestamp", &json_timestamp); + + if (json_timestamp) { + time_t now; + + /* Planned GR: check if the grace period has already expired. */ + now = time(NULL); + timestamp = json_object_get_int(json_timestamp); + if (now > timestamp) { + ospf_gr_restart_exit( + ospf, "grace period has expired already"); + } else + ospf_gr_restart_enter(ospf, OSPF_GR_SW_RESTART, + timestamp); + } else if (json_grace_period) { + uint32_t grace_period; + + /* + * Unplanned GR: the Grace-LSAs will be sent later as soon as + * the interfaces are operational. + */ + grace_period = json_object_get_int(json_grace_period); + ospf->gr_info.grace_period = grace_period; + ospf_gr_restart_enter(ospf, OSPF_GR_UNKNOWN_RESTART, + time(NULL) + ospf->gr_info.grace_period); + } + + json_object_object_del(json_instances, inst_name); + + frr_daemon_state_save(&json); +} + +void ospf_gr_unplanned_start_interface(struct ospf_interface *oi) +{ + /* Send Grace-LSA. */ + ospf_gr_lsa_originate(oi, oi->ospf->gr_info.reason, false); + + /* Start GR hello-delay interval. */ + oi->gr.hello_delay.elapsed_seconds = 0; + event_add_timer(master, ospf_gr_iface_send_grace_lsa, oi, 1, + &oi->gr.hello_delay.t_grace_send); +} + +/* Prepare to start a Graceful Restart. */ +static void ospf_gr_prepare(void) +{ + struct ospf *ospf; + struct ospf_interface *oi; + struct listnode *onode; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, onode, ospf)) { + struct listnode *inode; + + if (!ospf->gr_info.restart_support + || ospf->gr_info.prepare_in_progress) + continue; + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "GR: preparing to perform a graceful restart [period %u second(s)] [vrf %s]", + ospf->gr_info.grace_period, + ospf_vrf_id_to_name(ospf->vrf_id)); + + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + zlog_warn( + "%s: failed to activate graceful restart: opaque capability not enabled", + __func__); + continue; + } + + /* Send a Grace-LSA to all neighbors. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, inode, oi)) { + if (OSPF_IF_PARAM(oi, opaque_capable)) + ospf_gr_lsa_originate(oi, OSPF_GR_SW_RESTART, + false); + else + zlog_debug( + "GR: skipping grace LSA on interface %s (%s) with opaque capability disabled", + IF_NAME(oi), ospf_get_name(oi->ospf)); + } + + /* Record end of the grace period in non-volatile memory. */ + ospf_gr_nvm_update(ospf, true); + + /* + * Mark that a Graceful Restart preparation is in progress, to + * prevent ospfd from flushing its self-originated LSAs on exit. + */ + ospf->gr_info.prepare_in_progress = true; + } +} + +DEFPY(graceful_restart_prepare, graceful_restart_prepare_cmd, + "graceful-restart prepare ip ospf", + "Graceful Restart commands\n" + "Prepare upcoming graceful restart\n" + IP_STR + "Prepare to restart the OSPF process\n") +{ + struct ospf *ospf; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + vty_out(vty, + "%% Can't start graceful restart: opaque capability not enabled (VRF %s)\n\n", + ospf_get_name(ospf)); + return CMD_WARNING; + } + } + + ospf_gr_prepare(); + + return CMD_SUCCESS; +} + +DEFPY(graceful_restart, graceful_restart_cmd, + "graceful-restart [grace-period (1-1800)$grace_period]", + OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Check and get restart period if present. */ + if (!grace_period_str) + grace_period = OSPF_DFLT_GRACE_INTERVAL; + + ospf->gr_info.restart_support = true; + ospf->gr_info.grace_period = grace_period; + + /* Freeze OSPF routes in the RIB. */ + (void)ospf_zebra_gr_enable(ospf, ospf->gr_info.grace_period); + + /* Record that GR is enabled in non-volatile memory. */ + ospf_gr_nvm_update(ospf, false); + + return CMD_SUCCESS; +} + +DEFPY(no_graceful_restart, no_graceful_restart_cmd, + "no graceful-restart [grace-period (1-1800)]", + NO_STR OSPF_GR_STR + "Maximum length of the 'grace period'\n" + "Maximum length of the 'grace period' in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (!ospf->gr_info.restart_support) + return CMD_SUCCESS; + + if (ospf->gr_info.prepare_in_progress) { + vty_out(vty, + "%% Error: Graceful Restart preparation in progress\n"); + return CMD_WARNING; + } + + ospf->gr_info.restart_support = false; + ospf->gr_info.grace_period = OSPF_DFLT_GRACE_INTERVAL; + ospf_gr_nvm_delete(ospf); + ospf_zebra_gr_disable(ospf); + + return CMD_SUCCESS; +} + +void ospf_gr_init(void) +{ + install_element(ENABLE_NODE, &graceful_restart_prepare_cmd); + install_element(OSPF_NODE, &graceful_restart_cmd); + install_element(OSPF_NODE, &no_graceful_restart_cmd); +} diff --git a/ospfd/ospf_gr.h b/ospfd/ospf_gr.h new file mode 100644 index 0000000..22f9e1e --- /dev/null +++ b/ospfd/ospf_gr.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Graceful Restart helper functions. + * + * Copyright (C) 2020-21 Vmware, Inc. + * Rajesh Kumar Girada + */ + +#ifndef _ZEBRA_OSPF_GR_H +#define _ZEBRA_OSPF_GR_H + +#define OSPF_GR_NOT_HELPER 0 +#define OSPF_GR_ACTIVE_HELPER 1 + +#define OSPF_GR_HELPER_NO_LSACHECK 0 +#define OSPF_GR_HELPER_LSACHECK 1 + +#define OSPF_MAX_GRACE_INTERVAL 1800 +#define OSPF_MIN_GRACE_INTERVAL 1 +#define OSPF_DFLT_GRACE_INTERVAL 120 + +enum ospf_helper_exit_reason { + OSPF_GR_HELPER_EXIT_NONE = 0, + OSPF_GR_HELPER_INPROGRESS, + OSPF_GR_HELPER_TOPO_CHG, + OSPF_GR_HELPER_GRACE_TIMEOUT, + OSPF_GR_HELPER_COMPLETED +}; + +enum ospf_gr_restart_reason { + OSPF_GR_UNKNOWN_RESTART = 0, + OSPF_GR_SW_RESTART = 1, + OSPF_GR_SW_UPGRADE = 2, + OSPF_GR_SWITCH_REDUNDANT_CARD = 3, + OSPF_GR_INVALID_REASON_CODE = 4 +}; + +enum ospf_gr_helper_rejected_reason { + OSPF_HELPER_REJECTED_NONE, + OSPF_HELPER_SUPPORT_DISABLED, + OSPF_HELPER_NOT_A_VALID_NEIGHBOUR, + OSPF_HELPER_PLANNED_ONLY_RESTART, + OSPF_HELPER_TOPO_CHANGE_RTXMT_LIST, + OSPF_HELPER_LSA_AGE_MORE, + OSPF_HELPER_RESTARTING, +}; + +/* Ref RFC3623 appendex-A */ +/* Grace period TLV */ +#define GRACE_PERIOD_TYPE 1 +#define GRACE_PERIOD_LENGTH 4 + +struct grace_tlv_graceperiod { + struct tlv_header header; + uint32_t interval; +}; + +/* Restart reason TLV */ +#define RESTART_REASON_TYPE 2 +#define RESTART_REASON_LENGTH 1 + +struct grace_tlv_restart_reason { + struct tlv_header header; + uint8_t reason; + uint8_t reserved[3]; +}; + +/* Restarter ip address TLV */ +#define RESTARTER_IP_ADDR_TYPE 3 +#define RESTARTER_IP_ADDR_LEN 4 + +struct grace_tlv_restart_addr { + struct tlv_header header; + struct in_addr addr; +}; + +struct ospf_helper_info { + + /* Grace interval received from + * Restarting Router. + */ + uint32_t recvd_grace_period; + + /* Grace interval used for grace + * gracetimer. + */ + uint32_t actual_grace_period; + + /* Grace timer,This Router acts as + * helper until this timer until + * this timer expires. + */ + struct event *t_grace_timer; + + /* Helper status */ + uint32_t gr_helper_status; + + /* Helper exit reason*/ + enum ospf_helper_exit_reason helper_exit_reason; + + /* Planned/Unplanned restart*/ + enum ospf_gr_restart_reason gr_restart_reason; + + /* Helper rejected reason */ + enum ospf_gr_helper_rejected_reason rejected_reason; +}; + +struct advRtr { + struct in_addr advRtrAddr; +}; + +#define OSPF_HELPER_ENABLE_RTR_COUNT(ospf) (ospf->enable_rtr_list->count) + +/* Check for planned restart */ +#define OSPF_GR_IS_PLANNED_RESTART(reason) \ + ((reason == OSPF_GR_SW_RESTART) || (reason == OSPF_GR_SW_UPGRADE)) + +/* Check the router is HELPER for current neighbour */ +#define OSPF_GR_IS_ACTIVE_HELPER(N) \ + ((N)->gr_helper_info.gr_helper_status == OSPF_GR_ACTIVE_HELPER) + +/* Check the LSA is GRACE LSA */ +#define IS_GRACE_LSA(lsa) \ + ((lsa->data->type == OSPF_OPAQUE_LINK_LSA) \ + && (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) \ + == OPAQUE_TYPE_GRACE_LSA)) + +/* Check neighbour is in FULL state */ +#define IS_NBR_STATE_FULL(nbr) (nsm_should_adj(nbr) && (nbr->state == NSM_Full)) + +/* Check neighbour is DR_OTHER and state is 2_WAY */ +#define IS_NBR_STATE_2_WAY_WITH_DROTHER(nbr) \ + ((ospf_get_nbr_ism_role(nbr) == ISM_DROther) \ + && (nbr->state == NSM_TwoWay)) + +#define OSPF_GR_FALSE false +#define OSPF_GR_TRUE true + +#define OSPF_GR_SUCCESS 1 +#define OSPF_GR_FAILURE 0 +#define OSPF_GR_INVALID -1 + +const char *ospf_exit_reason2str(unsigned int reason); +const char *ospf_restart_reason2str(unsigned int reason); +const char *ospf_rejected_reason2str(unsigned int reason); + +extern void ospf_gr_helper_instance_init(struct ospf *ospf); +extern void ospf_gr_helper_instance_stop(struct ospf *ospf); +extern void ospf_gr_helper_init(void); +extern void ospf_gr_helper_stop(void); +extern int ospf_process_grace_lsa(struct ospf *ospf, struct ospf_lsa *lsa, + struct ospf_neighbor *nbr); +extern void ospf_gr_helper_exit(struct ospf_neighbor *nbr, + enum ospf_helper_exit_reason reason); +extern void ospf_process_maxage_grace_lsa(struct ospf *ospf, + struct ospf_lsa *lsa, + struct ospf_neighbor *nbr); +extern void ospf_helper_handle_topo_chg(struct ospf *ospf, + struct ospf_lsa *lsa); +extern void ospf_gr_helper_support_set(struct ospf *ospf, bool support); +extern void ospf_gr_helper_support_set_per_routerid(struct ospf *ospf, + struct in_addr *rid, + bool support); +extern void ospf_gr_helper_lsa_check_set(struct ospf *ospf, bool lsacheck); +extern void ospf_gr_helper_supported_gracetime_set(struct ospf *ospf, + uint32_t interval); +extern void ospf_gr_helper_set_supported_planned_only_restart(struct ospf *ospf, + bool planned_only); +extern void ospf_gr_iface_send_grace_lsa(struct event *thread); +extern void ospf_gr_restart_enter(struct ospf *ospf, + enum ospf_gr_restart_reason reason, + time_t timestamp); +extern void ospf_gr_check_lsdb_consistency(struct ospf *ospf, + struct ospf_area *area); +extern void ospf_gr_check_adjs(struct ospf *ospf); +extern void ospf_gr_nvm_read(struct ospf *ospf); +extern void ospf_gr_nvm_delete(struct ospf *ospf); +extern void ospf_gr_unplanned_start_interface(struct ospf_interface *oi); +extern void ospf_gr_init(void); + +#endif /* _ZEBRA_OSPF_GR_H */ diff --git a/ospfd/ospf_gr_helper.c b/ospfd/ospf_gr_helper.c new file mode 100644 index 0000000..b97b680 --- /dev/null +++ b/ospfd/ospf_gr_helper.c @@ -0,0 +1,1125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Graceful Restart helper functions. + * + * Copyright (C) 2020-21 Vmware, Inc. + * Rajesh Kumar Girada + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "vty.h" +#include "filter.h" +#include "log.h" +#include "jhash.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_gr.h" + +static const char * const ospf_exit_reason_desc[] = { + "Unknown reason", + "Helper in progress", + "Topology Change", + "Grace timer expiry", + "Successful graceful restart", +}; + +static const char * const ospf_restart_reason_desc[] = { + "Unknown restart", + "Software restart", + "Software reload/upgrade", + "Switch to redundant control processor", +}; + +static const char * const ospf_rejected_reason_desc[] = { + "Unknown reason", + "Helper support disabled", + "Neighbour is not in FULL state", + "Supports only planned restart but received unplanned", + "Topo change due to change in lsa rxmt list", + "LSA age is more than Grace interval", + "Router is in the process of graceful restart", +}; + +static void show_ospf_grace_lsa_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); +static bool ospf_check_change_in_rxmt_list(struct ospf_neighbor *nbr); + +static unsigned int ospf_enable_rtr_hash_key(const void *data) +{ + const struct advRtr *rtr = data; + + return jhash_1word(rtr->advRtrAddr.s_addr, 0); +} + +static bool ospf_enable_rtr_hash_cmp(const void *d1, const void *d2) +{ + const struct advRtr *rtr1 = (struct advRtr *)d1; + const struct advRtr *rtr2 = (struct advRtr *)d2; + + return (rtr1->advRtrAddr.s_addr == rtr2->advRtrAddr.s_addr); +} + +static void *ospf_enable_rtr_hash_alloc(void *p) +{ + struct advRtr *rid; + + rid = XCALLOC(MTYPE_OSPF_GR_HELPER, sizeof(struct advRtr)); + rid->advRtrAddr.s_addr = ((struct in_addr *)p)->s_addr; + + return rid; +} + +static void ospf_disable_rtr_hash_free(void *rtr) +{ + XFREE(MTYPE_OSPF_GR_HELPER, rtr); +} + +static void ospf_enable_rtr_hash_destroy(struct ospf *ospf) +{ + if (ospf->enable_rtr_list == NULL) + return; + + hash_clean_and_free(&ospf->enable_rtr_list, ospf_disable_rtr_hash_free); +} + +/* + * GR exit reason strings + */ +const char *ospf_exit_reason2str(unsigned int reason) +{ + if (reason < array_size(ospf_exit_reason_desc)) + return(ospf_exit_reason_desc[reason]); + else + return "Invalid reason"; +} + +/* + * GR restart reason strings + */ +const char *ospf_restart_reason2str(unsigned int reason) +{ + if (reason < array_size(ospf_restart_reason_desc)) + return(ospf_restart_reason_desc[reason]); + else + return "Invalid reason"; +} + +/* + * GR rejected reason strings + */ +const char *ospf_rejected_reason2str(unsigned int reason) +{ + if (reason < array_size(ospf_rejected_reason_desc)) + return(ospf_rejected_reason_desc[reason]); + else + return "Invalid reason"; +} + +/* + * Initialize GR helper config data structures. + * + * OSPF + * OSPF pointer + * + * Returns: + * Nothing + */ +void ospf_gr_helper_instance_init(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, GR Helper init.", __func__); + + ospf->is_helper_supported = OSPF_GR_FALSE; + ospf->strict_lsa_check = OSPF_GR_TRUE; + ospf->only_planned_restart = OSPF_GR_FALSE; + ospf->supported_grace_time = OSPF_MAX_GRACE_INTERVAL; + ospf->last_exit_reason = OSPF_GR_HELPER_EXIT_NONE; + ospf->active_restarter_cnt = 0; + + ospf->enable_rtr_list = + hash_create(ospf_enable_rtr_hash_key, ospf_enable_rtr_hash_cmp, + "OSPF enable router hash"); +} + +/* + * De-Initialize GR helper config data structures. + * + * OSPF + * OSPF pointer + * + * Returns: + * Nothing + */ +void ospf_gr_helper_instance_stop(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, GR helper deinit.", __func__); + + ospf_enable_rtr_hash_destroy(ospf); +} + +/* + * Initialize GR helper config data structures. + * + * Returns: + * Nothing + */ +void ospf_gr_helper_init(void) +{ + int rc; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, GR Helper init.", __func__); + + rc = ospf_register_opaque_functab( + OSPF_OPAQUE_LINK_LSA, OPAQUE_TYPE_GRACE_LSA, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, show_ospf_grace_lsa_info, NULL, NULL, + NULL, NULL); + if (rc != 0) { + flog_warn(EC_OSPF_OPAQUE_REGISTRATION, + "%s: Failed to register Grace LSA functions", + __func__); + } +} + +/* + * De-Initialize GR helper config data structures. + * + * Returns: + * Nothing + */ +void ospf_gr_helper_stop(void) +{ + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, GR helper deinit.", __func__); + + ospf_delete_opaque_functab(OSPF_OPAQUE_LINK_LSA, OPAQUE_TYPE_GRACE_LSA); +} + +/* + * Extracting tlv info from GRACE LSA. + * + * lsa + * ospf grace lsa + * + * Returns: + * interval : grace interval. + * addr : RESTARTER address. + * reason : Restarting reason. + */ +static int ospf_extract_grace_lsa_fields(struct ospf_lsa *lsa, + uint32_t *interval, + struct in_addr *addr, uint8_t *reason) +{ + struct lsa_header *lsah = NULL; + struct tlv_header *tlvh = NULL; + struct grace_tlv_graceperiod *grace_period; + struct grace_tlv_restart_reason *gr_reason; + struct grace_tlv_restart_addr *restart_addr; + uint16_t length = 0; + int sum = 0; + + lsah = (struct lsa_header *)lsa->data; + + /* Check LSA len */ + if (lsa->size <= OSPF_LSA_HEADER_SIZE) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s: Malformed packet: Invalid LSA len:%d", + __func__, length); + return OSPF_GR_FAILURE; + } + + length = lsa->size - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + + /* Check TLV len against overall LSA */ + if (sum + TLV_SIZE(tlvh) > length) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s: Malformed packet: Invalid TLV len:%u", + __func__, TLV_SIZE(tlvh)); + return OSPF_GR_FAILURE; + } + + switch (ntohs(tlvh->type)) { + case GRACE_PERIOD_TYPE: + if (TLV_SIZE(tlvh) < + sizeof(struct grace_tlv_graceperiod)) { + zlog_debug("%s: Malformed packet: Invalid grace TLV len:%u", + __func__, TLV_SIZE(tlvh)); + return OSPF_GR_FAILURE; + } + + grace_period = (struct grace_tlv_graceperiod *)tlvh; + *interval = ntohl(grace_period->interval); + sum += TLV_SIZE(tlvh); + + /* Check if grace interval is valid */ + if (*interval > OSPF_MAX_GRACE_INTERVAL + || *interval < OSPF_MIN_GRACE_INTERVAL) + return OSPF_GR_FAILURE; + break; + case RESTART_REASON_TYPE: + if (TLV_SIZE(tlvh) < + sizeof(struct grace_tlv_restart_reason)) { + zlog_debug("%s: Malformed packet: Invalid reason TLV len:%u", + __func__, TLV_SIZE(tlvh)); + return OSPF_GR_FAILURE; + } + + gr_reason = (struct grace_tlv_restart_reason *)tlvh; + *reason = gr_reason->reason; + sum += TLV_SIZE(tlvh); + + if (*reason >= OSPF_GR_INVALID_REASON_CODE) + return OSPF_GR_FAILURE; + break; + case RESTARTER_IP_ADDR_TYPE: + if (TLV_SIZE(tlvh) < + sizeof(struct grace_tlv_restart_addr)) { + zlog_debug("%s: Malformed packet: Invalid addr TLV len:%u", + __func__, TLV_SIZE(tlvh)); + return OSPF_GR_FAILURE; + } + + restart_addr = (struct grace_tlv_restart_addr *)tlvh; + addr->s_addr = restart_addr->addr.s_addr; + sum += TLV_SIZE(tlvh); + break; + default: + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Malformed packet.Invalid TLV type:%d", + __func__, ntohs(tlvh->type)); + return OSPF_GR_FAILURE; + } + } + + return OSPF_GR_SUCCESS; +} + +/* + * Grace timer expiry handler. + * HELPER aborts its role at grace timer expiry. + * + * thread + * thread pointer + * + * Returns: + * Nothing + */ +static void ospf_handle_grace_timer_expiry(struct event *thread) +{ + struct ospf_neighbor *nbr = EVENT_ARG(thread); + + nbr->gr_helper_info.t_grace_timer = NULL; + + ospf_gr_helper_exit(nbr, OSPF_GR_HELPER_GRACE_TIMEOUT); +} + +/* + * Process Grace LSA.If it is eligible move to HELPER role. + * Ref rfc3623 section 3.1 + * + * ospf + * OSPF pointer. + * + * lsa + * Grace LSA received from RESTARTER. + * + * nbr + * OSPF neighbour which requests the router to act as + * HELPER. + * + * Returns: + * status. + * If supported as HELPER : OSPF_GR_HELPER_INPROGRESS + * If Not supported as HELPER : OSPF_GR_HELPER_NONE + */ +int ospf_process_grace_lsa(struct ospf *ospf, struct ospf_lsa *lsa, + struct ospf_neighbor *nbr) +{ + struct in_addr restart_addr = {0}; + uint8_t restart_reason = 0; + uint32_t grace_interval = 0; + uint32_t actual_grace_interval = 0; + struct advRtr lookup; + struct ospf_neighbor *restarter = NULL; + struct ospf_interface *oi = nbr->oi; + int ret; + + + /* Extract the grace lsa packet fields */ + ret = ospf_extract_grace_lsa_fields(lsa, &grace_interval, &restart_addr, + &restart_reason); + if (ret != OSPF_GR_SUCCESS) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Wrong Grace LSA packet.", __func__); + return OSPF_GR_NOT_HELPER; + } + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Grace LSA received from %pI4, grace interval:%u, restart reason:%s", + __func__, &restart_addr, grace_interval, + ospf_restart_reason2str(restart_reason)); + + /* In case of broadcast links, if RESTARTER is DR_OTHER, + * grace LSA might be received from DR, so need to get + * actual neighbour info , here RESTARTER. + */ + if (oi->type != OSPF_IFTYPE_POINTOPOINT) { + restarter = ospf_nbr_lookup_by_addr(oi->nbrs, &restart_addr); + + if (!restarter) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Restarter is not a nbr(%pI4) for this router.", + __func__, &restart_addr); + return OSPF_GR_NOT_HELPER; + } + } else + restarter = nbr; + + /* Verify Helper enabled globally */ + if (!ospf->is_helper_supported) { + /* Verify that Helper support is enabled for the + * current neighbour router-id. + */ + lookup.advRtrAddr.s_addr = restarter->router_id.s_addr; + + if (!hash_lookup(ospf->enable_rtr_list, &lookup)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, HELPER support is disabled, So not a HELPER", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_SUPPORT_DISABLED; + return OSPF_GR_NOT_HELPER; + } + } + + + /* Check neighbour is in FULL state and + * became a adjacency. + */ + if (!IS_NBR_STATE_FULL(restarter)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, This Neighbour %pI4 is not in FULL state.", + __func__, &restarter->src); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_NOT_A_VALID_NEIGHBOUR; + return OSPF_GR_NOT_HELPER; + } + + /* Based on the restart reason from grace lsa + * check the current router is supporting or not + */ + if (ospf->only_planned_restart + && !OSPF_GR_IS_PLANNED_RESTART(restart_reason)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Router supports only planned restarts but received the GRACE LSA for an unplanned restart.", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_PLANNED_ONLY_RESTART; + return OSPF_GR_NOT_HELPER; + } + + /* Check the retransmission list of this + * neighbour, check any change in lsas. + */ + if (ospf->strict_lsa_check && !ospf_ls_retransmit_isempty(restarter) + && ospf_check_change_in_rxmt_list(restarter)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Changed LSA in Rxmt list. So not Helper.", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_TOPO_CHANGE_RTXMT_LIST; + return OSPF_GR_NOT_HELPER; + } + + /*LSA age must be less than the grace period */ + if (ntohs(lsa->data->ls_age) >= grace_interval) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Grace LSA age(%d) is more than the grace interval(%d)", + __func__, lsa->data->ls_age, grace_interval); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_LSA_AGE_MORE; + return OSPF_GR_NOT_HELPER; + } + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s: router is in the process of graceful restart", + __func__); + restarter->gr_helper_info.rejected_reason = + OSPF_HELPER_RESTARTING; + return OSPF_GR_NOT_HELPER; + } + + /* check supported grace period configured + * if configured, use this to start the grace + * timer otherwise use the interval received + * in grace LSA packet. + */ + actual_grace_interval = grace_interval; + if (grace_interval > ospf->supported_grace_time) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Received grace period %d is larger than supported grace %d", + __func__, grace_interval, + ospf->supported_grace_time); + actual_grace_interval = ospf->supported_grace_time; + } + + if (OSPF_GR_IS_ACTIVE_HELPER(restarter)) { + if (restarter->gr_helper_info.t_grace_timer) + EVENT_OFF(restarter->gr_helper_info.t_grace_timer); + + if (ospf->active_restarter_cnt > 0) + ospf->active_restarter_cnt--; + + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Router is already acting as a HELPER for this nbr,so restart the grace timer", + __func__); + } else { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, This Router becomes a HELPER for the neighbour %pI4", + __func__, &restarter->src); + } + + /* Became a Helper to the RESTART neighbour. + * Change the helper status. + */ + restarter->gr_helper_info.gr_helper_status = OSPF_GR_ACTIVE_HELPER; + restarter->gr_helper_info.recvd_grace_period = grace_interval; + restarter->gr_helper_info.actual_grace_period = actual_grace_interval; + restarter->gr_helper_info.gr_restart_reason = restart_reason; + restarter->gr_helper_info.rejected_reason = OSPF_HELPER_REJECTED_NONE; + + /* Increment the active restarter count */ + ospf->active_restarter_cnt++; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Grace timer started.interval:%d", __func__, + actual_grace_interval); + + /* Start the grace timer */ + event_add_timer(master, ospf_handle_grace_timer_expiry, restarter, + actual_grace_interval, + &restarter->gr_helper_info.t_grace_timer); + + return OSPF_GR_ACTIVE_HELPER; +} + +/* + * API to check any change in the neighbor's + * retransmission list. + * + * nbr + * OSPF neighbor + * + * Returns: + * TRUE - if any change in the lsa. + * FALSE - no change in the lsas. + */ +static bool ospf_check_change_in_rxmt_list(struct ospf_neighbor *nbr) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + struct route_table *tbl; + + tbl = nbr->ls_rxmt.type[OSPF_ROUTER_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + tbl = nbr->ls_rxmt.type[OSPF_NETWORK_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + + tbl = nbr->ls_rxmt.type[OSPF_SUMMARY_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + + tbl = nbr->ls_rxmt.type[OSPF_ASBR_SUMMARY_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + + tbl = nbr->ls_rxmt.type[OSPF_AS_EXTERNAL_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + + tbl = nbr->ls_rxmt.type[OSPF_AS_NSSA_LSA].db; + LSDB_LOOP (tbl, rn, lsa) + if (lsa->to_be_acknowledged) + return OSPF_GR_TRUE; + + return OSPF_GR_FALSE; +} + +/* + * Actions to be taken when topo change detected + * HELPER will exit upon topo change. + * + * ospf + * ospf pointer + * lsa + * topo change occurred due to this lsa type (1 to 5 and 7) + * + * Returns: + * Nothing + */ +void ospf_helper_handle_topo_chg(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct listnode *node; + struct ospf_interface *oi; + + /* Topo change not required to be handled if strict + * LSA check is disabled for this router. + */ + if (!ospf->strict_lsa_check) + return; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s: Topo change detected due to LSA[%s]", __func__, + dump_lsa_key(lsa)); + + lsa->to_be_acknowledged = OSPF_GR_TRUE; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn = NULL; + + if (ospf_interface_neighbor_count(oi) == 0) + continue; + + /* Ref rfc3623 section 3.2.3.b + * If change due to external LSA and if the area is + * stub, then it is not a topo change. Since Type-5 + * lsas will not be flooded in stub area. + */ + if ((oi->area->external_routing == OSPF_AREA_STUB) + && (lsa->data->type == OSPF_AS_EXTERNAL_LSA)) { + continue; + } + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + struct ospf_neighbor *nbr = NULL; + + if (!rn->info) + continue; + + nbr = rn->info; + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) + ospf_gr_helper_exit(nbr, + OSPF_GR_HELPER_TOPO_CHG); + } + } +} + +/* + * Api to exit from HELPER role to take all actions + * required at exit. + * Ref rfc3623 section 3.2 + * + * ospf + * OSPF pointer. + * + * nbr + * OSPF neighbour for which it is acting as HELPER. + * + * reason + * The reason for exiting from HELPER. + * + * Returns: + * Nothing. + */ +void ospf_gr_helper_exit(struct ospf_neighbor *nbr, + enum ospf_helper_exit_reason reason) +{ + struct ospf_interface *oi = nbr->oi; + struct ospf *ospf = oi->ospf; + + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) + return; + + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Exiting from HELPER support to %pI4, due to %s", + __func__, &nbr->src, ospf_exit_reason2str(reason)); + + /* Reset helper status*/ + nbr->gr_helper_info.gr_helper_status = OSPF_GR_NOT_HELPER; + nbr->gr_helper_info.helper_exit_reason = reason; + nbr->gr_helper_info.actual_grace_period = 0; + nbr->gr_helper_info.recvd_grace_period = 0; + nbr->gr_helper_info.gr_restart_reason = 0; + ospf->last_exit_reason = reason; + + if (ospf->active_restarter_cnt <= 0) { + zlog_err( + "OSPF GR-Helper: active_restarter_cnt should be greater than zero here."); + return; + } + /* Decrement active Restarter count */ + ospf->active_restarter_cnt--; + + /* If the exit not triggered due to grace timer + * expiry, stop the grace timer. + */ + if (reason != OSPF_GR_HELPER_GRACE_TIMEOUT) + EVENT_OFF(nbr->gr_helper_info.t_grace_timer); + + /* check exit triggered due to successful completion + * of graceful restart. + */ + if (reason != OSPF_GR_HELPER_COMPLETED) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Unsuccessful GR exit", __func__); + } + + /*Recalculate the DR for the network segment */ + if (oi->type == OSPF_IFTYPE_BROADCAST || oi->type == OSPF_IFTYPE_NBMA) + ospf_dr_election(oi); + + /* Originate a router LSA */ + ospf_router_lsa_update_area(oi->area); + + /* Originate network lsa if it is an DR in the LAN */ + if (oi->state == ISM_DR) + ospf_network_lsa_update(oi); +} + +/* + * Process MaxAge Grace LSA. + * It is a indication for successful completion of GR. + * If router acting as HELPER, It exits from helper role. + * + * ospf + * OSPF pointer. + * + * lsa + * Grace LSA received from RESTARTER. + * + * nbr + * OSPF neighbour which requests the router to act as + * HELPER. + * + * Returns: + * Nothing. + */ +void ospf_process_maxage_grace_lsa(struct ospf *ospf, struct ospf_lsa *lsa, + struct ospf_neighbor *nbr) +{ + struct in_addr restartAddr = {0}; + uint8_t restartReason = 0; + uint32_t graceInterval = 0; + struct ospf_neighbor *restarter = NULL; + struct ospf_interface *oi = nbr->oi; + int ret; + + /* Extract the grace lsa packet fields */ + ret = ospf_extract_grace_lsa_fields(lsa, &graceInterval, &restartAddr, + &restartReason); + if (ret != OSPF_GR_SUCCESS) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Wrong Grace LSA packet.", __func__); + return; + } + + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, GraceLSA received for neighbour %pI4", __func__, + &restartAddr); + + /* In case of broadcast links, if RESTARTER is DR_OTHER, + * grace LSA might be received from DR, so fetching the + * actual neighbour information using restarter address. + */ + if (oi->type != OSPF_IFTYPE_POINTOPOINT) { + restarter = ospf_nbr_lookup_by_addr(oi->nbrs, &restartAddr); + + if (!restarter) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Restarter is not a neighbour for this router.", + __func__); + return; + } + } else { + restarter = nbr; + } + + ospf_gr_helper_exit(restarter, OSPF_GR_HELPER_COMPLETED); +} + +/* Configuration handlers */ +/* + * Disable/Enable HELPER support on router level. + * + * ospf + * OSPF pointer. + * + * status + * TRUE/FALSE + * + * Returns: + * Nothing. + */ +void ospf_gr_helper_support_set(struct ospf *ospf, bool support) +{ + struct ospf_interface *oi; + struct listnode *node; + struct advRtr lookup; + + if (ospf->is_helper_supported == support) + return; + + ospf->is_helper_supported = support; + + /* If helper support disabled, cease HELPER role for all + * supporting neighbors. + */ + if (support == OSPF_GR_FALSE) { + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn = NULL; + + if (ospf_interface_neighbor_count(oi) == 0) + continue; + + for (rn = route_top(oi->nbrs); rn; + rn = route_next(rn)) { + struct ospf_neighbor *nbr = NULL; + + if (!rn->info) + continue; + + nbr = rn->info; + + lookup.advRtrAddr.s_addr = + nbr->router_id.s_addr; + /* check if helper support enabled for the + * corresponding routerid.If enabled, don't + * exit from helper role. + */ + if (hash_lookup(ospf->enable_rtr_list, &lookup)) + continue; + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) + ospf_gr_helper_exit( + nbr, OSPF_GR_HELPER_TOPO_CHG); + } + } + } +} + +/* + * Enable/Disable HELPER support on a specified advertagement + * router. + * + * ospf + * OSPF pointer. + * + * advRtr + * HELPER support for given Advertisement Router. + * + * support + * True - Enable Helper Support. + * False - Disable Helper Support. + * + * Returns: + * Nothing. + */ + +void ospf_gr_helper_support_set_per_routerid(struct ospf *ospf, + struct in_addr *advrtr, + bool support) +{ + struct advRtr temp; + struct advRtr *rtr; + struct ospf_interface *oi; + struct listnode *node; + + temp.advRtrAddr.s_addr = advrtr->s_addr; + + if (support == OSPF_GR_FALSE) { + /*Delete the routerid from the enable router hash table */ + rtr = hash_lookup(ospf->enable_rtr_list, &temp); + + if (rtr) { + hash_release(ospf->enable_rtr_list, rtr); + ospf_disable_rtr_hash_free(rtr); + } + + /* If helper support is enabled globally + * no action is required. + */ + if (ospf->is_helper_supported) + return; + + /* Cease the HELPER role fore neighbours from the + * specified advertisement router. + */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn = NULL; + + if (ospf_interface_neighbor_count(oi) == 0) + continue; + + for (rn = route_top(oi->nbrs); rn; + rn = route_next(rn)) { + struct ospf_neighbor *nbr = NULL; + + if (!rn->info) + continue; + + nbr = rn->info; + + if (nbr->router_id.s_addr != advrtr->s_addr) + continue; + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) + ospf_gr_helper_exit( + nbr, OSPF_GR_HELPER_TOPO_CHG); + } + } + + } else { + /* Add the routerid to the enable router hash table */ + (void)hash_get(ospf->enable_rtr_list, &temp, + ospf_enable_rtr_hash_alloc); + } +} + +/* + * Api to enable/disable strict lsa check on the HELPER. + * + * ospf + * OSPF pointer. + * + * enabled + * True - disable the lsa check. + * False - enable the strict lsa check. + * + * Returns: + * Nothing. + */ +void ospf_gr_helper_lsa_check_set(struct ospf *ospf, bool enabled) +{ + if (ospf->strict_lsa_check == enabled) + return; + + ospf->strict_lsa_check = enabled; +} + +/* + * Api to set the supported grace interval in this router. + * + * ospf + * OSPF pointer. + * + * interval + * The supported grace interval.. + * + * Returns: + * Nothing. + */ +void ospf_gr_helper_supported_gracetime_set(struct ospf *ospf, + uint32_t interval) +{ + ospf->supported_grace_time = interval; +} + +/* + * Api to set the supported restart reason. + * + * ospf + * OSPF pointer. + * + * planned_only + * True: support only planned restart. + * False: support for planned/unplanned restarts. + * + * Returns: + * Nothing. + */ +void ospf_gr_helper_set_supported_planned_only_restart(struct ospf *ospf, + bool planned_only) +{ + ospf->only_planned_restart = planned_only; +} + +/* + * Api to display the grace LSA information. + * + * vty + * vty pointer. + * lsa + * Grace LSA. + * json + * json object + * + * Returns: + * Nothing. + */ +static void show_ospf_grace_lsa_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = NULL; + struct tlv_header *tlvh = NULL; + struct grace_tlv_graceperiod *gracePeriod; + struct grace_tlv_restart_reason *grReason; + struct grace_tlv_restart_addr *restartAddr; + uint16_t length = 0; + int sum = 0; + + if (json) + return; + + lsah = (struct lsa_header *)lsa->data; + + if (lsa->size <= OSPF_LSA_HEADER_SIZE) { + if (vty) + vty_out(vty, "%% Invalid LSA length: %d\n", length); + else + zlog_debug("%% Invalid LSA length: %d", length); + return; + } + + length = lsa->size - OSPF_LSA_HEADER_SIZE; + + if (vty) + vty_out(vty, " TLV info:\n"); + else + zlog_debug(" TLV info:"); + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + /* Check TLV len */ + if (sum + TLV_SIZE(tlvh) > length) { + if (vty) + vty_out(vty, "%% Invalid TLV length: %u\n", + TLV_SIZE(tlvh)); + else + zlog_debug("%% Invalid TLV length: %u", + TLV_SIZE(tlvh)); + return; + } + + switch (ntohs(tlvh->type)) { + case GRACE_PERIOD_TYPE: + if (TLV_SIZE(tlvh) + < sizeof(struct grace_tlv_graceperiod)) { + if (vty) + vty_out(vty, + "%% Invalid grace TLV length %u\n", + TLV_SIZE(tlvh)); + else + zlog_debug( + "%% Invalid grace TLV length %u", + TLV_SIZE(tlvh)); + return; + } + + gracePeriod = (struct grace_tlv_graceperiod *)tlvh; + sum += TLV_SIZE(tlvh); + + if (vty) + vty_out(vty, " Grace period:%d\n", + ntohl(gracePeriod->interval)); + else + zlog_debug(" Grace period:%d", + ntohl(gracePeriod->interval)); + break; + case RESTART_REASON_TYPE: + if (TLV_SIZE(tlvh) + < sizeof(struct grace_tlv_restart_reason)) { + if (vty) + vty_out(vty, + "%% Invalid reason TLV length %u\n", + TLV_SIZE(tlvh)); + else + zlog_debug( + "%% Invalid reason TLV length %u", + TLV_SIZE(tlvh)); + return; + } + + grReason = (struct grace_tlv_restart_reason *)tlvh; + sum += TLV_SIZE(tlvh); + + if (vty) + vty_out(vty, " Restart reason:%s\n", + ospf_restart_reason2str( + grReason->reason)); + else + zlog_debug(" Restart reason:%s", + ospf_restart_reason2str( + grReason->reason)); + break; + case RESTARTER_IP_ADDR_TYPE: + if (TLV_SIZE(tlvh) + < sizeof(struct grace_tlv_restart_addr)) { + if (vty) + vty_out(vty, + "%% Invalid addr TLV length %u\n", + TLV_SIZE(tlvh)); + else + zlog_debug( + "%% Invalid addr TLV length %u", + TLV_SIZE(tlvh)); + return; + } + + restartAddr = (struct grace_tlv_restart_addr *)tlvh; + sum += TLV_SIZE(tlvh); + + if (vty) + vty_out(vty, " Restarter address:%pI4\n", + &restartAddr->addr); + else + zlog_debug(" Restarter address:%pI4", + &restartAddr->addr); + break; + default: + if (vty) + vty_out(vty, " Unknown TLV type %d\n", + ntohs(tlvh->type)); + else + zlog_debug(" Unknown TLV type %d", + ntohs(tlvh->type)); + + break; + } + } +} diff --git a/ospfd/ospf_ia.c b/ospfd/ospf_ia.c new file mode 100644 index 0000000..59112b2 --- /dev/null +++ b/ospfd/ospf_ia.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF inter-area routing. + * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada + */ + + +#include + +#include "frrevent.h" +#include "memory.h" +#include "hash.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_ia.h" +#include "ospfd/ospf_dump.h" + +static struct ospf_route *ospf_find_abr_route(struct route_table *rtrs, + struct prefix_ipv4 *abr, + struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *node; + + if ((rn = route_node_lookup(rtrs, (struct prefix *)abr)) == NULL) + return NULL; + + route_unlock_node(rn); + + for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) + if (IPV4_ADDR_SAME(& or->u.std.area_id, &area->area_id) + && (or->u.std.flags & ROUTER_LSA_BORDER)) + return or ; + + return NULL; +} + +static void ospf_ia_network_route(struct ospf *ospf, struct route_table *rt, + struct prefix_ipv4 *p, + struct ospf_route *new_or, + struct ospf_route *abr_or) +{ + struct route_node *rn1; + struct ospf_route * or ; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: processing summary route to %pFX", __func__, p); + + /* Find a route to the same dest */ + if ((rn1 = route_node_lookup(rt, (struct prefix *)p))) { + int res; + + route_unlock_node(rn1); + + if ((or = rn1->info)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Found a route to the same network", + __func__); + /* Check the existing route. */ + if ((res = ospf_route_cmp(ospf, new_or, or)) < 0) { + /* New route is better, so replace old one. */ + ospf_route_subst(rn1, new_or, abr_or); + } else if (res == 0) { + /* New and old route are equal, so next hops can + * be added. */ + route_lock_node(rn1); + ospf_route_copy_nexthops(or, abr_or->paths); + route_unlock_node(rn1); + + /* new route can be deleted, because existing + * route has been updated. */ + ospf_route_free(new_or); + } else { + /* New route is worse, so free it. */ + ospf_route_free(new_or); + return; + } + } /* if (or)*/ + } /*if (rn1)*/ + else { /* no route */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: add new route to %pFX", __func__, p); + ospf_route_add(rt, p, new_or, abr_or); + } +} + +static void ospf_ia_router_route(struct ospf *ospf, struct route_table *rtrs, + struct prefix_ipv4 *p, + struct ospf_route *new_or, + struct ospf_route *abr_or) +{ + struct ospf_route * or = NULL; + struct route_node *rn; + int ret; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: considering %pFX", __func__, p); + + /* Find a route to the same dest */ + rn = route_node_get(rtrs, (struct prefix *)p); + + if (rn->info == NULL) + /* This is a new route */ + rn->info = list_new(); + else { + struct ospf_area *or_area; + or_area = ospf_area_lookup_by_area_id(ospf, + new_or->u.std.area_id); + assert(or_area); + /* This is an additional route */ + route_unlock_node(rn); + or = ospf_find_asbr_route_through_area(rtrs, p, or_area); + } + + if (or) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: a route to the same ABR through the same area exists", + __func__); + /* New route is better */ + if ((ret = ospf_route_cmp(ospf, new_or, or)) < 0) { + listnode_delete(rn->info, or); + ospf_route_free(or); + /* proceed down */ + } + /* Routes are the same */ + else if (ret == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: merging the new route", + __func__); + + ospf_route_copy_nexthops(or, abr_or->paths); + ospf_route_free(new_or); + return; + } + /* New route is worse */ + else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: skipping the new route", + __func__); + ospf_route_free(new_or); + return; + } + } + + ospf_route_copy_nexthops(new_or, abr_or->paths); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: adding the new route", __func__); + + listnode_add(rn->info, new_or); +} + + +static int process_summary_lsa(struct ospf_area *area, struct route_table *rt, + struct route_table *rtrs, struct ospf_lsa *lsa) +{ + struct ospf *ospf = area->ospf; + struct ospf_area_range *range; + struct ospf_route *abr_or, *new_or; + struct summary_lsa *sl; + struct prefix_ipv4 p, abr; + uint32_t metric; + + if (lsa == NULL) + return 0; + + sl = (struct summary_lsa *)lsa->data; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: LS ID: %pI4", __func__, &sl->header.id); + + metric = GET_METRIC(sl->metric); + + if (metric == OSPF_LS_INFINITY) + return 0; + + if (IS_LSA_MAXAGE(lsa)) + return 0; + + if (ospf_lsa_is_self_originated(area->ospf, lsa)) + return 0; + + p.family = AF_INET; + p.prefix = sl->header.id; + + if (sl->header.type == OSPF_SUMMARY_LSA) + p.prefixlen = ip_masklen(sl->mask); + else + p.prefixlen = IPV4_MAX_BITLEN; + + apply_mask_ipv4(&p); + + if (sl->header.type == OSPF_SUMMARY_LSA + && (range = ospf_area_range_match_any(ospf, &p)) + && ospf_area_range_active(range)) + return 0; + + /* XXX: This check seems dubious to me. If an ABR has already decided + * to consider summaries received in this area, then why would one wish + * to exclude default? + */ + if (IS_OSPF_ABR(ospf) && ospf->abr_type != OSPF_ABR_STAND + && area->external_routing != OSPF_AREA_DEFAULT + && p.prefix.s_addr == OSPF_DEFAULT_DESTINATION && p.prefixlen == 0) + return 0; /* Ignore summary default from a stub area */ + + abr.family = AF_INET; + abr.prefix = sl->header.adv_router; + abr.prefixlen = IPV4_MAX_BITLEN; + apply_mask_ipv4(&abr); + + abr_or = ospf_find_abr_route(rtrs, &abr, area); + + if (abr_or == NULL) + return 0; + + new_or = ospf_route_new(); + new_or->type = OSPF_DESTINATION_NETWORK; + new_or->id = sl->header.id; + new_or->mask = sl->mask; + new_or->u.std.options = sl->header.options; + new_or->u.std.origin = (struct lsa_header *)sl; + new_or->cost = abr_or->cost + metric; + new_or->u.std.area_id = area->area_id; + new_or->u.std.external_routing = area->external_routing; + new_or->path_type = OSPF_PATH_INTER_AREA; + + if (sl->header.type == OSPF_SUMMARY_LSA) + ospf_ia_network_route(ospf, rt, &p, new_or, abr_or); + else { + new_or->type = OSPF_DESTINATION_ROUTER; + new_or->u.std.flags = ROUTER_LSA_EXTERNAL; + ospf_ia_router_route(ospf, rtrs, &p, new_or, abr_or); + } + + return 0; +} + +static void ospf_examine_summaries(struct ospf_area *area, + struct route_table *lsdb_rt, + struct route_table *rt, + struct route_table *rtrs) +{ + struct ospf_lsa *lsa; + struct route_node *rn; + + LSDB_LOOP (lsdb_rt, rn, lsa) + process_summary_lsa(area, rt, rtrs, lsa); +} + +int ospf_area_is_transit(struct ospf_area *area) +{ + return (area->transit == OSPF_TRANSIT_TRUE) + || ospf_full_virtual_nbrs( + area); /* Cisco forgets to set the V-bit :( */ +} + +static void ospf_update_network_route(struct ospf *ospf, struct route_table *rt, + struct route_table *rtrs, + struct summary_lsa *lsa, + struct prefix_ipv4 *p, + struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_route * or, *abr_or, *new_or; + struct prefix_ipv4 abr; + uint32_t cost; + + abr.family = AF_INET; + abr.prefix = lsa->header.adv_router; + abr.prefixlen = IPV4_MAX_BITLEN; + apply_mask_ipv4(&abr); + + abr_or = ospf_find_abr_route(rtrs, &abr, area); + + if (abr_or == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: can't find a route to the ABR", + __func__); + return; + } + + cost = abr_or->cost + GET_METRIC(lsa->metric); + + rn = route_node_lookup(rt, (struct prefix *)p); + + if (!rn) { + if (ospf->abr_type != OSPF_ABR_SHORTCUT) + return; /* Standard ABR can update only already + installed + backbone paths */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Allowing Shortcut ABR to add new route", + __func__); + new_or = ospf_route_new(); + new_or->type = OSPF_DESTINATION_NETWORK; + new_or->id = lsa->header.id; + new_or->mask = lsa->mask; + new_or->u.std.options = lsa->header.options; + new_or->u.std.origin = (struct lsa_header *)lsa; + new_or->cost = cost; + new_or->u.std.area_id = area->area_id; + new_or->u.std.external_routing = area->external_routing; + new_or->path_type = OSPF_PATH_INTER_AREA; + ospf_route_add(rt, p, new_or, abr_or); + + return; + } else { + route_unlock_node(rn); + if (rn->info == NULL) + return; + } + + or = rn->info; + + if (or->path_type != OSPF_PATH_INTRA_AREA && + or->path_type != OSPF_PATH_INTER_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ERR: path type is wrong", __func__); + return; + } + + if (ospf->abr_type == OSPF_ABR_SHORTCUT) { + if ( + or->path_type == OSPF_PATH_INTRA_AREA + && !OSPF_IS_AREA_ID_BACKBONE( + or->u.std.area_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Shortcut: this intra-area path is not backbone", + __func__); + return; + } + } else /* Not Shortcut ABR */ + { + if (!OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: route is not BB-associated", + __func__); + return; /* We can update only BB routes */ + } + } + + if (or->cost < cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: new route is worse", __func__); + return; + } + + if (or->cost == cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: new route is same distance, adding nexthops", + __func__); + ospf_route_copy_nexthops(or, abr_or->paths); + } + + if (or->cost > cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: new route is better, overriding nexthops", + __func__); + ospf_route_subst_nexthops(or, abr_or->paths); + or->cost = cost; + + if ((ospf->abr_type == OSPF_ABR_SHORTCUT) + && !OSPF_IS_AREA_ID_BACKBONE(or->u.std.area_id)) { + or->path_type = OSPF_PATH_INTER_AREA; + or->u.std.area_id = area->area_id; + or->u.std.external_routing = area->external_routing; + /* Note that we can do this only in Shortcut ABR mode, + because standard ABR must leave the route type and + area + unchanged + */ + } + } +} + +static void ospf_update_router_route(struct ospf *ospf, + struct route_table *rtrs, + struct summary_lsa *lsa, + struct prefix_ipv4 *p, + struct ospf_area *area) +{ + struct ospf_route * or, *abr_or, *new_or; + struct prefix_ipv4 abr; + uint32_t cost; + + abr.family = AF_INET; + abr.prefix = lsa->header.adv_router; + abr.prefixlen = IPV4_MAX_BITLEN; + apply_mask_ipv4(&abr); + + abr_or = ospf_find_abr_route(rtrs, &abr, area); + + if (abr_or == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: can't find a route to the ABR", + __func__); + return; + } + + cost = abr_or->cost + GET_METRIC(lsa->metric); + + /* First try to find a backbone path, + because standard ABR can update only BB-associated paths */ + + if ((ospf->backbone == NULL) && (ospf->abr_type != OSPF_ABR_SHORTCUT)) + return; /* no BB area, not Shortcut ABR, exiting */ + + /* find the backbone route, if possible */ + if ((ospf->backbone == NULL) + || !(or = ospf_find_asbr_route_through_area(rtrs, p, + ospf->backbone))) { + if (ospf->abr_type != OSPF_ABR_SHORTCUT) + + /* route to ASBR through the BB not found + the router is not Shortcut ABR, exiting */ + + return; + else + /* We're a Shortcut ABR*/ + { + /* Let it either add a new router or update the route + through the same (non-BB) area. */ + + new_or = ospf_route_new(); + new_or->type = OSPF_DESTINATION_ROUTER; + new_or->id = lsa->header.id; + new_or->mask = lsa->mask; + new_or->u.std.options = lsa->header.options; + new_or->u.std.origin = (struct lsa_header *)lsa; + new_or->cost = cost; + new_or->u.std.area_id = area->area_id; + new_or->u.std.external_routing = area->external_routing; + new_or->path_type = OSPF_PATH_INTER_AREA; + new_or->u.std.flags = ROUTER_LSA_EXTERNAL; + ospf_ia_router_route(ospf, rtrs, p, new_or, abr_or); + + return; + } + } + + /* At this point the "or" is always bb-associated */ + + if (!(or->u.std.flags & ROUTER_LSA_EXTERNAL)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: the remote router is not an ASBR", + __func__); + return; + } + + if (or->path_type != OSPF_PATH_INTRA_AREA && + or->path_type != OSPF_PATH_INTER_AREA) + return; + + if (or->cost < cost) + return; + + else if (or->cost == cost) + ospf_route_copy_nexthops(or, abr_or->paths); + + else if (or->cost > cost) { + ospf_route_subst_nexthops(or, abr_or->paths); + or->cost = cost; + + /* Even if the ABR runs in Shortcut mode, we can't change + the path type and area, because the "or" is always + bb-associated + at this point and even Shortcut ABR can't change these + attributes */ + } +} + +static int process_transit_summary_lsa(struct ospf_area *area, + struct route_table *rt, + struct route_table *rtrs, + struct ospf_lsa *lsa) +{ + struct ospf *ospf = area->ospf; + struct summary_lsa *sl; + struct prefix_ipv4 p; + uint32_t metric; + + if (lsa == NULL) + return 0; + + sl = (struct summary_lsa *)lsa->data; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: LS ID: %pI4", __func__, &lsa->data->id); + metric = GET_METRIC(sl->metric); + + if (metric == OSPF_LS_INFINITY) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: metric is infinity, skip", __func__); + return 0; + } + + if (IS_LSA_MAXAGE(lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: This LSA is too old", __func__); + return 0; + } + + if (ospf_lsa_is_self_originated(area->ospf, lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: This LSA is mine, skip", __func__); + return 0; + } + + p.family = AF_INET; + p.prefix = sl->header.id; + + if (sl->header.type == OSPF_SUMMARY_LSA) + p.prefixlen = ip_masklen(sl->mask); + else + p.prefixlen = IPV4_MAX_BITLEN; + + apply_mask_ipv4(&p); + + if (sl->header.type == OSPF_SUMMARY_LSA) + ospf_update_network_route(ospf, rt, rtrs, sl, &p, area); + else + ospf_update_router_route(ospf, rtrs, sl, &p, area); + + return 0; +} + +static void ospf_examine_transit_summaries(struct ospf_area *area, + struct route_table *lsdb_rt, + struct route_table *rt, + struct route_table *rtrs) +{ + struct ospf_lsa *lsa; + struct route_node *rn; + + LSDB_LOOP (lsdb_rt, rn, lsa) + process_transit_summary_lsa(area, rt, rtrs, lsa); +} + +void ospf_ia_routing(struct ospf *ospf, struct route_table *rt, + struct route_table *rtrs) +{ + struct listnode *node; + struct ospf_area *area; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:start", __func__); + + if (IS_OSPF_ABR(ospf)) { + switch (ospf->abr_type) { + case OSPF_ABR_STAND: + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:Standard ABR", __func__); + + if ((area = ospf->backbone)) { + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug( + "%s:backbone area found, examining summaries", + __func__); + } + + OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, + area)) + if (area != ospf->backbone) + if (ospf_area_is_transit(area)) + OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( + area, rt, rtrs); + } else if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:backbone area NOT found", + __func__); + break; + case OSPF_ABR_IBM: + case OSPF_ABR_CISCO: + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:Alternative Cisco/IBM ABR", + __func__); + area = ospf->backbone; /* Find the BB */ + + /* If we have an active BB connection */ + if (area && ospf_act_bb_connection(ospf)) { + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug( + "%s: backbone area found, examining BB summaries", + __func__); + } + + OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, + area)) + if (area != ospf->backbone) + if (ospf_area_is_transit(area)) + OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( + area, rt, rtrs); + } else { /* No active BB connection--consider all areas + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Active BB connection not found", + __func__); + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, + area)) + OSPF_EXAMINE_SUMMARIES_ALL(area, rt, + rtrs); + } + break; + case OSPF_ABR_SHORTCUT: + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:Alternative Shortcut", __func__); + area = ospf->backbone; /* Find the BB */ + + /* If we have an active BB connection */ + if (area && ospf_act_bb_connection(ospf)) { + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug( + "%s: backbone area found, examining BB summaries", + __func__); + } + OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); + } + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (area != ospf->backbone) + if (ospf_area_is_transit(area) + || ((area->shortcut_configured + != OSPF_SHORTCUT_DISABLE) + && ((ospf->backbone == NULL) + || ((area->shortcut_configured + == OSPF_SHORTCUT_ENABLE) + && area->shortcut_capability)))) + OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL( + area, rt, rtrs); + break; + default: + break; + } + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s:not ABR, considering all areas", + __func__); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + OSPF_EXAMINE_SUMMARIES_ALL(area, rt, rtrs); + } +} diff --git a/ospfd/ospf_ia.h b/ospfd/ospf_ia.h new file mode 100644 index 0000000..1005f2b --- /dev/null +++ b/ospfd/ospf_ia.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF inter-area routing. + * Copyright (C) 1999, 2000 Alex Zinin, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_IA_H +#define _ZEBRA_OSPF_IA_H + +/* Macros. */ +#define OSPF_EXAMINE_SUMMARIES_ALL(A, N, R) \ + { \ + ospf_examine_summaries((A), SUMMARY_LSDB((A)), (N), (R)); \ + ospf_examine_summaries((A), ASBR_SUMMARY_LSDB((A)), (N), (R)); \ + } + +#define OSPF_EXAMINE_TRANSIT_SUMMARIES_ALL(A, N, R) \ + { \ + ospf_examine_transit_summaries((A), SUMMARY_LSDB((A)), (N), \ + (R)); \ + ospf_examine_transit_summaries((A), ASBR_SUMMARY_LSDB((A)), \ + (N), (R)); \ + } + +extern void ospf_ia_routing(struct ospf *, struct route_table *, + struct route_table *); +extern int ospf_area_is_transit(struct ospf_area *); + +#endif /* _ZEBRA_OSPF_IA_H */ diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c new file mode 100644 index 0000000..11ac7af --- /dev/null +++ b/ospfd/ospf_interface.c @@ -0,0 +1,1597 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Interface functions. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "stream.h" +#include "log.h" +#include "network.h" +#include "zclient.h" +#include "bfd.h" +#include "ldp_sync.h" +#include "plist.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_ldp_sync.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_te.h" + +DEFINE_QOBJ_TYPE(ospf_interface); +DEFINE_HOOK(ospf_vl_add, (struct ospf_vl_data * vd), (vd)); +DEFINE_HOOK(ospf_vl_delete, (struct ospf_vl_data * vd), (vd)); +DEFINE_HOOK(ospf_if_update, (struct interface * ifp), (ifp)); +DEFINE_HOOK(ospf_if_delete, (struct interface * ifp), (ifp)); + +int ospf_interface_neighbor_count(struct ospf_interface *oi) +{ + int count = 0; + struct route_node *rn; + struct ospf_neighbor *nbr = NULL; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (!nbr) + continue; + + /* Do not show myself. */ + if (nbr == oi->nbr_self) + continue; + /* Down state is not shown. */ + if (nbr->state == NSM_Down) + continue; + count++; + } + + return count; +} + + +void ospf_intf_neighbor_filter_apply(struct ospf_interface *oi) +{ + struct route_node *rn; + struct ospf_neighbor *nbr = NULL; + struct prefix nbr_src_prefix = { AF_INET, IPV4_MAX_BITLEN, { 0 } }; + + if (!oi->nbr_filter) + return; + + /* + * Kill neighbors that don't match the neighbor filter prefix-list + * excluding the neighbor for the router itself and any neighbors + * that are already down. + */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (nbr && nbr != oi->nbr_self && nbr->state != NSM_Down) { + nbr_src_prefix.u.prefix4 = nbr->src; + if (prefix_list_apply(oi->nbr_filter, + (struct prefix *)&( + nbr_src_prefix)) != + PREFIX_PERMIT) + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr); + } + } +} + +int ospf_if_get_output_cost(struct ospf_interface *oi) +{ + /* If all else fails, use default OSPF cost */ + uint32_t cost; + uint32_t bw, refbw; + + /* if LDP-IGP Sync is running on interface set cost so interface + * is used only as last resort + */ + if (ldp_sync_if_is_enabled(IF_DEF_PARAMS(oi->ifp)->ldp_sync_info)) + return (LDP_OSPF_LSINFINITY); + + /* ifp speed and bw can be 0 in some platforms, use ospf default bw + if bw is configured under interface it would be used. + */ + if (!oi->ifp->bandwidth && oi->ifp->speed) + bw = oi->ifp->speed; + else + bw = oi->ifp->bandwidth ? oi->ifp->bandwidth + : OSPF_DEFAULT_BANDWIDTH; + refbw = oi->ospf->ref_bandwidth; + + /* A specified ip ospf cost overrides a calculated one. */ + if (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(oi->ifp), output_cost_cmd) + || OSPF_IF_PARAM_CONFIGURED(oi->params, output_cost_cmd)) + cost = OSPF_IF_PARAM(oi, output_cost_cmd); + /* See if a cost can be calculated from the zebra processes + interface bandwidth field. */ + else { + cost = (uint32_t)((double)refbw / (double)bw + (double)0.5); + if (cost < 1) + cost = 1; + else if (cost > 65535) + cost = 65535; + + if (if_is_loopback(oi->ifp)) + cost = 0; + } + + return cost; +} + +void ospf_if_recalculate_output_cost(struct interface *ifp) +{ + uint32_t newcost; + struct route_node *rn; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi; + + if ((oi = rn->info) == NULL) + continue; + + newcost = ospf_if_get_output_cost(oi); + + /* Is actual output cost changed? */ + if (oi->output_cost != newcost) { + oi->output_cost = newcost; + ospf_router_lsa_update_area(oi->area); + } + } +} + +/* Simulate down/up on the interface. This is needed, for example, when + the MTU changes. */ +void ospf_if_reset(struct interface *ifp) +{ + struct route_node *rn; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi; + + if ((oi = rn->info) == NULL) + continue; + + ospf_if_down(oi); + ospf_if_up(oi); + } +} + +static void ospf_if_default_variables(struct ospf_interface *oi) +{ + /* Set default values. */ + + oi->type = OSPF_IFTYPE_BROADCAST; + + oi->state = ISM_Down; + + oi->crypt_seqnum = 0; + + /* This must be short, (less than RxmtInterval) + - RFC 2328 Section 13.5 para 3. Set to 1 second to avoid Acks being + held back for too long - MAG */ + oi->v_ls_ack = 1; +} + +/* lookup oi for specified prefix/ifp */ +struct ospf_interface *ospf_if_table_lookup(struct interface *ifp, + struct prefix *prefix) +{ + struct prefix p; + struct route_node *rn; + struct ospf_interface *rninfo = NULL; + + p = *prefix; + p.prefixlen = IPV4_MAX_BITLEN; + + /* route_node_get implicitely locks */ + if ((rn = route_node_lookup(IF_OIFS(ifp), &p))) { + rninfo = (struct ospf_interface *)rn->info; + route_unlock_node(rn); + } + + return rninfo; +} + +static void ospf_add_to_if(struct interface *ifp, struct ospf_interface *oi) +{ + struct route_node *rn; + struct prefix p; + + p = *oi->address; + p.prefixlen = IPV4_MAX_BITLEN; + apply_mask(&p); + + rn = route_node_get(IF_OIFS(ifp), &p); + /* rn->info should either be NULL or equal to this oi + * as route_node_get may return an existing node + */ + assert(!rn->info || rn->info == oi); + rn->info = oi; +} + +static void ospf_delete_from_if(struct interface *ifp, + struct ospf_interface *oi) +{ + struct route_node *rn; + struct prefix p; + + p = *oi->address; + p.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_lookup(IF_OIFS(oi->ifp), &p); + assert(rn); + assert(rn->info); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); +} + +struct ospf_interface *ospf_if_new(struct ospf *ospf, struct interface *ifp, + struct prefix *p) +{ + struct ospf_interface *oi; + + oi = ospf_if_table_lookup(ifp, p); + if (oi) + return oi; + + oi = XCALLOC(MTYPE_OSPF_IF, sizeof(struct ospf_interface)); + + oi->obuf = ospf_fifo_new(); + + /* Set zebra interface pointer. */ + oi->ifp = ifp; + oi->address = p; + + ospf_add_to_if(ifp, oi); + listnode_add(ospf->oiflist, oi); + + /* Initialize neighbor list. */ + oi->nbrs = route_table_init(); + + /* Initialize static neighbor list. */ + oi->nbr_nbma = list_new(); + + /* Initialize Link State Acknowledgment list. */ + oi->ls_ack = list_new(); + oi->ls_ack_direct.ls_ack = list_new(); + + /* Set default values. */ + ospf_if_default_variables(oi); + + /* Set pseudo neighbor to Null */ + oi->nbr_self = NULL; + + oi->ls_upd_queue = route_table_init(); + oi->t_ls_upd_event = NULL; + oi->t_ls_ack_direct = NULL; + + oi->crypt_seqnum = frr_sequence32_next(); + + ospf_opaque_type9_lsa_init(oi); + + oi->ospf = ospf; + + QOBJ_REG(oi, ospf_interface); + + /* If first oi, check per-intf write socket */ + if (ospf->oi_running && ospf->intf_socket_enabled) + ospf_ifp_sock_init(ifp); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ospf interface %s vrf %s id %u created", + __func__, ifp->name, ospf_get_name(ospf), + ospf->vrf_id); + + return oi; +} + +/* Restore an interface to its pre UP state + Used from ism_interface_down only */ +void ospf_if_cleanup(struct ospf_interface *oi) +{ + struct route_node *rn; + struct listnode *node, *nnode; + struct ospf_neighbor *nbr; + struct ospf_nbr_nbma *nbr_nbma; + struct ospf_lsa *lsa; + + /* oi->nbrs and oi->nbr_nbma should be deleted on InterfaceDown event */ + /* delete all static neighbors attached to this interface */ + for (ALL_LIST_ELEMENTS(oi->nbr_nbma, node, nnode, nbr_nbma)) { + EVENT_OFF(nbr_nbma->t_poll); + + if (nbr_nbma->nbr) { + nbr_nbma->nbr->nbr_nbma = NULL; + nbr_nbma->nbr = NULL; + } + + nbr_nbma->oi = NULL; + + listnode_delete(oi->nbr_nbma, nbr_nbma); + } + + /* send Neighbor event KillNbr to all associated neighbors. */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + if ((nbr = rn->info) != NULL) + if (nbr != oi->nbr_self) + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr); + } + + /* Cleanup Link State Acknowlegdment list. */ + for (ALL_LIST_ELEMENTS(oi->ls_ack, node, nnode, lsa)) + ospf_lsa_unlock(&lsa); /* oi->ls_ack */ + list_delete_all_node(oi->ls_ack); + + oi->crypt_seqnum = 0; + + /* Empty link state update queue */ + ospf_ls_upd_queue_empty(oi); + + /* Reset pseudo neighbor. */ + ospf_nbr_self_reset(oi, oi->ospf->router_id); +} + +void ospf_if_free(struct ospf_interface *oi) +{ + struct interface *ifp = oi->ifp; + + ospf_if_down(oi); + + ospf_fifo_free(oi->obuf); + + assert(oi->state == ISM_Down); + + ospf_opaque_type9_lsa_if_cleanup(oi); + + ospf_opaque_type9_lsa_term(oi); + + QOBJ_UNREG(oi); + + /* Free Pseudo Neighbour */ + ospf_nbr_delete(oi->nbr_self); + + route_table_finish(oi->nbrs); + route_table_finish(oi->ls_upd_queue); + + /* Free any lists that should be freed */ + list_delete(&oi->nbr_nbma); + + list_delete(&oi->ls_ack); + list_delete(&oi->ls_ack_direct.ls_ack); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ospf interface %s vrf %s id %u deleted", + __func__, oi->ifp->name, oi->ifp->vrf->name, + oi->ifp->vrf->vrf_id); + + ospf_delete_from_if(oi->ifp, oi); + + listnode_delete(oi->ospf->oiflist, oi); + listnode_delete(oi->area->oiflist, oi); + + event_cancel_event(master, oi); + + /* If last oi, close per-interface socket */ + if (ospf_oi_count(ifp) == 0) + ospf_ifp_sock_close(ifp); + + memset(oi, 0, sizeof(*oi)); + XFREE(MTYPE_OSPF_IF, oi); +} + +int ospf_if_is_up(struct ospf_interface *oi) +{ + return if_is_up(oi->ifp); +} + +/* Lookup OSPF interface by router LSA posistion */ +struct ospf_interface *ospf_if_lookup_by_lsa_pos(struct ospf_area *area, + int lsa_pos) +{ + struct listnode *node; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + if (lsa_pos >= oi->lsa_pos_beg && lsa_pos < oi->lsa_pos_end) + return oi; + } + return NULL; +} + +struct ospf_interface *ospf_if_lookup_by_local_addr(struct ospf *ospf, + struct interface *ifp, + struct in_addr address) +{ + struct listnode *node; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) { + if (ifp && oi->ifp != ifp) + continue; + + if (IPV4_ADDR_SAME(&address, &oi->address->u.prefix4)) + return oi; + } + + return NULL; +} + +struct ospf_interface *ospf_if_lookup_by_prefix(struct ospf *ospf, + struct prefix_ipv4 *p) +{ + struct listnode *node; + struct ospf_interface *oi; + + /* Check each Interface. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) { + struct prefix ptmp; + + prefix_copy(&ptmp, CONNECTED_PREFIX(oi->connected)); + apply_mask(&ptmp); + if (prefix_same(&ptmp, (struct prefix *)p)) + return oi; + } + } + return NULL; +} + +/* determine receiving interface by ifp and source address */ +struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf, + struct in_addr src, + struct interface *ifp) +{ + struct route_node *rn; + struct prefix_ipv4 addr; + struct ospf_interface *oi, *match, *unnumbered_match; + + addr.family = AF_INET; + addr.prefix = src; + addr.prefixlen = IPV4_MAX_BITLEN; + + match = unnumbered_match = NULL; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + oi = rn->info; + + if (!oi) /* oi can be NULL for PtP aliases */ + continue; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + if (if_is_loopback(oi->ifp)) + continue; + + if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) + unnumbered_match = oi; + else if (prefix_match(CONNECTED_PREFIX(oi->connected), + (struct prefix *)&addr)) { + if ((match == NULL) || (match->address->prefixlen + < oi->address->prefixlen)) + match = oi; + } + } + + if (match) + return match; + + return unnumbered_match; +} + +void ospf_interface_fifo_flush(struct ospf_interface *oi) +{ + struct ospf *ospf = oi->ospf; + + ospf_fifo_flush(oi->obuf); + + if (oi->on_write_q) { + listnode_delete(ospf->oi_write_q, oi); + if (list_isempty(ospf->oi_write_q)) + EVENT_OFF(ospf->t_write); + oi->on_write_q = 0; + } +} + +static void ospf_if_reset_stats(struct ospf_interface *oi) +{ + oi->hello_in = oi->hello_out = 0; + oi->db_desc_in = oi->db_desc_out = 0; + oi->ls_req_in = oi->ls_req_out = 0; + oi->ls_upd_in = oi->ls_upd_out = 0; + oi->ls_ack_in = oi->ls_ack_out = 0; +} + +void ospf_if_stream_unset(struct ospf_interface *oi) +{ + /* flush the interface packet queue */ + ospf_interface_fifo_flush(oi); + /*reset protocol stats */ + ospf_if_reset_stats(oi); +} + + +static struct ospf_if_params *ospf_new_if_params(void) +{ + struct ospf_if_params *oip; + + oip = XCALLOC(MTYPE_OSPF_IF_PARAMS, sizeof(struct ospf_if_params)); + + UNSET_IF_PARAM(oip, output_cost_cmd); + UNSET_IF_PARAM(oip, transmit_delay); + UNSET_IF_PARAM(oip, retransmit_interval); + UNSET_IF_PARAM(oip, passive_interface); + UNSET_IF_PARAM(oip, v_hello); + UNSET_IF_PARAM(oip, fast_hello); + UNSET_IF_PARAM(oip, v_gr_hello_delay); + UNSET_IF_PARAM(oip, v_wait); + UNSET_IF_PARAM(oip, priority); + UNSET_IF_PARAM(oip, type); + UNSET_IF_PARAM(oip, auth_simple); + UNSET_IF_PARAM(oip, auth_crypt); + UNSET_IF_PARAM(oip, auth_type); + UNSET_IF_PARAM(oip, if_area); + UNSET_IF_PARAM(oip, opaque_capable); + UNSET_IF_PARAM(oip, keychain_name); + UNSET_IF_PARAM(oip, nbr_filter_name); + + oip->auth_crypt = list_new(); + + oip->network_lsa_seqnum = htonl(OSPF_INITIAL_SEQUENCE_NUMBER); + oip->is_v_wait_set = false; + + oip->ptp_dmvpn = 0; + oip->p2mp_delay_reflood = OSPF_P2MP_DELAY_REFLOOD_DEFAULT; + oip->opaque_capable = OSPF_OPAQUE_CAPABLE_DEFAULT; + + return oip; +} + +static void ospf_del_if_params(struct interface *ifp, + struct ospf_if_params *oip) +{ + list_delete(&oip->auth_crypt); + XFREE(MTYPE_OSPF_IF_PARAMS, oip->keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, oip->nbr_filter_name); + ospf_interface_disable_bfd(ifp, oip); + ldp_sync_info_free(&(oip->ldp_sync_info)); + XFREE(MTYPE_OSPF_IF_PARAMS, oip); +} + +void ospf_free_if_params(struct interface *ifp, struct in_addr addr) +{ + struct ospf_if_params *oip; + struct prefix_ipv4 p; + struct route_node *rn; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = addr; + rn = route_node_lookup(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); + if (!rn || !rn->info) + return; + + oip = rn->info; + route_unlock_node(rn); + + if (!OSPF_IF_PARAM_CONFIGURED(oip, output_cost_cmd) && + !OSPF_IF_PARAM_CONFIGURED(oip, transmit_delay) && + !OSPF_IF_PARAM_CONFIGURED(oip, retransmit_interval) && + !OSPF_IF_PARAM_CONFIGURED(oip, passive_interface) && + !OSPF_IF_PARAM_CONFIGURED(oip, v_hello) && + !OSPF_IF_PARAM_CONFIGURED(oip, fast_hello) && + !OSPF_IF_PARAM_CONFIGURED(oip, v_wait) && + !OSPF_IF_PARAM_CONFIGURED(oip, priority) && + !OSPF_IF_PARAM_CONFIGURED(oip, type) && + !OSPF_IF_PARAM_CONFIGURED(oip, auth_simple) && + !OSPF_IF_PARAM_CONFIGURED(oip, auth_type) && + !OSPF_IF_PARAM_CONFIGURED(oip, if_area) && + !OSPF_IF_PARAM_CONFIGURED(oip, opaque_capable) && + !OSPF_IF_PARAM_CONFIGURED(oip, prefix_suppression) && + !OSPF_IF_PARAM_CONFIGURED(oip, keychain_name) && + !OSPF_IF_PARAM_CONFIGURED(oip, nbr_filter_name) && + listcount(oip->auth_crypt) == 0) { + ospf_del_if_params(ifp, oip); + rn->info = NULL; + route_unlock_node(rn); + } +} + +struct ospf_if_params *ospf_lookup_if_params(struct interface *ifp, + struct in_addr addr) +{ + struct prefix_ipv4 p; + struct route_node *rn; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = addr; + + rn = route_node_lookup(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); + + if (rn) { + route_unlock_node(rn); + return rn->info; + } + + return NULL; +} + +struct ospf_if_params *ospf_get_if_params(struct interface *ifp, + struct in_addr addr) +{ + struct prefix_ipv4 p; + struct route_node *rn; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = addr; + apply_mask_ipv4(&p); + + rn = route_node_get(IF_OIFS_PARAMS(ifp), (struct prefix *)&p); + + if (rn->info == NULL) + rn->info = ospf_new_if_params(); + else + route_unlock_node(rn); + + return rn->info; +} + +void ospf_if_update_params(struct interface *ifp, struct in_addr addr) +{ + struct route_node *rn; + struct ospf_interface *oi; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + if ((oi = rn->info) == NULL) + continue; + + if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &addr)) + oi->params = ospf_lookup_if_params( + ifp, oi->address->u.prefix4); + } +} + +int ospf_if_new_hook(struct interface *ifp) +{ + int rc = 0; + + ifp->info = XCALLOC(MTYPE_OSPF_IF_INFO, sizeof(struct ospf_if_info)); + + IF_OSPF_IF_INFO(ifp)->oii_fd = -1; + + IF_OIFS(ifp) = route_table_init(); + IF_OIFS_PARAMS(ifp) = route_table_init(); + + IF_DEF_PARAMS(ifp) = ospf_new_if_params(); + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), transmit_delay); + IF_DEF_PARAMS(ifp)->transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), retransmit_interval); + IF_DEF_PARAMS(ifp)->retransmit_interval = + OSPF_RETRANSMIT_INTERVAL_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), priority); + IF_DEF_PARAMS(ifp)->priority = OSPF_ROUTER_PRIORITY_DEFAULT; + + IF_DEF_PARAMS(ifp)->mtu_ignore = OSPF_MTU_IGNORE_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_hello); + IF_DEF_PARAMS(ifp)->v_hello = OSPF_HELLO_INTERVAL_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), fast_hello); + IF_DEF_PARAMS(ifp)->fast_hello = OSPF_FAST_HELLO_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_gr_hello_delay); + IF_DEF_PARAMS(ifp)->v_gr_hello_delay = OSPF_HELLO_DELAY_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_wait); + IF_DEF_PARAMS(ifp)->v_wait = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_simple); + memset(IF_DEF_PARAMS(ifp)->auth_simple, 0, OSPF_AUTH_SIMPLE_SIZE); + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_type); + IF_DEF_PARAMS(ifp)->auth_type = OSPF_AUTH_NOTSET; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), opaque_capable); + IF_DEF_PARAMS(ifp)->opaque_capable = OSPF_OPAQUE_CAPABLE_DEFAULT; + + IF_DEF_PARAMS(ifp)->prefix_suppression = OSPF_PREFIX_SUPPRESSION_DEFAULT; + + rc = ospf_opaque_new_if(ifp); + return rc; +} + +static int ospf_if_delete_hook(struct interface *ifp) +{ + int rc = 0; + struct route_node *rn; + struct ospf_if_info *oii; + + rc = ospf_opaque_del_if(ifp); + + /* + * This function must be called before `route_table_finish` due to + * BFD integration need to iterate over the interface neighbors to + * remove all registrations. + */ + ospf_del_if_params(ifp, IF_DEF_PARAMS(ifp)); + + for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) + if (rn->info) + ospf_del_if_params(ifp, rn->info); + + route_table_finish(IF_OIFS(ifp)); + route_table_finish(IF_OIFS_PARAMS(ifp)); + + /* Close per-interface socket */ + oii = ifp->info; + if (oii && oii->oii_fd > 0) { + close(oii->oii_fd); + oii->oii_fd = -1; + } + + XFREE(MTYPE_OSPF_IF_INFO, ifp->info); + + return rc; +} + +int ospf_if_is_enable(struct ospf_interface *oi) +{ + if (!(if_is_loopback(oi->ifp))) + if (if_is_up(oi->ifp)) + return 1; + + return 0; +} + +void ospf_if_set_multicast(struct ospf_interface *oi) +{ + if ((oi->state > ISM_Loopback) && (oi->type != OSPF_IFTYPE_LOOPBACK) + && (oi->type != OSPF_IFTYPE_VIRTUALLINK) + && (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE)) { + /* The interface should belong to the OSPF-all-routers group. */ + if (!OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) + && (ospf_if_add_allspfrouters(oi->ospf, oi->address, + oi->ifp->ifindex) + >= 0)) + /* Set the flag only if the system call to join + * succeeded. */ + OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); + } else { + /* The interface should NOT belong to the OSPF-all-routers + * group. */ + if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) { + /* Only actually drop if this is the last reference */ + if (OI_MEMBER_COUNT(oi, MEMBER_ALLROUTERS) == 1) + ospf_if_drop_allspfrouters(oi->ospf, + oi->address, + oi->ifp->ifindex); + /* Unset the flag regardless of whether the system call + to leave + the group succeeded, since it's much safer to assume + that + we are not a member. */ + OI_MEMBER_LEFT(oi, MEMBER_ALLROUTERS); + } + } + + if (((oi->type == OSPF_IFTYPE_BROADCAST) + || (oi->type == OSPF_IFTYPE_POINTOPOINT)) + && ((oi->state == ISM_DR) || (oi->state == ISM_Backup)) + && (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE)) { + /* The interface should belong to the OSPF-designated-routers + * group. */ + if (!OI_MEMBER_CHECK(oi, MEMBER_DROUTERS) + && (ospf_if_add_alldrouters(oi->ospf, oi->address, + oi->ifp->ifindex) + >= 0)) + /* Set the flag only if the system call to join + * succeeded. */ + OI_MEMBER_JOINED(oi, MEMBER_DROUTERS); + } else { + /* The interface should NOT belong to the + * OSPF-designated-routers group */ + if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { + /* drop only if last reference */ + if (OI_MEMBER_COUNT(oi, MEMBER_DROUTERS) == 1) + ospf_if_drop_alldrouters(oi->ospf, oi->address, + oi->ifp->ifindex); + + /* Unset the flag regardless of whether the system call + to leave + the group succeeded, since it's much safer to assume + that + we are not a member. */ + OI_MEMBER_LEFT(oi, MEMBER_DROUTERS); + } + } +} + +int ospf_if_up(struct ospf_interface *oi) +{ + if (oi == NULL) + return 0; + + if (oi->type == OSPF_IFTYPE_LOOPBACK) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_LoopInd); + else { + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_InterfaceUp); + } + + return 1; +} + +/* This function will mark routes with next-hops matching the down + * OSPF interface as changed. It is used to assure routes that get + * removed from the zebra RIB when an interface goes down are + * reinstalled if the interface comes back up prior to an intervening + * SPF calculation. + */ +static void ospf_if_down_mark_routes_changed(struct route_table *table, + struct ospf_interface *oi) +{ + struct route_node *rn; + struct ospf_route *or; + struct listnode *nh; + struct ospf_path *op; + + for (rn = route_top(table); rn; rn = route_next(rn)) { + or = rn->info; + + if (or == NULL) + continue; + + for (nh = listhead(or->paths); nh; + nh = listnextnode_unchecked(nh)) { + op = listgetdata(nh); + if (op->ifindex == oi->ifp->ifindex) { + or->changed = true; + break; + } + } + } +} + +int ospf_if_down(struct ospf_interface *oi) +{ + struct ospf *ospf; + + if (oi == NULL) + return 0; + + ospf = oi->ospf; + + /* Cease the HELPER role for all the neighbours + * of this interface. + */ + if (ospf->is_helper_supported) { + struct route_node *rn = NULL; + + if (ospf_interface_neighbor_count(oi)) { + for (rn = route_top(oi->nbrs); rn; + rn = route_next(rn)) { + struct ospf_neighbor *nbr = NULL; + + if (!rn->info) + continue; + + nbr = rn->info; + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) + ospf_gr_helper_exit( + nbr, OSPF_GR_HELPER_TOPO_CHG); + } + } + } + + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + /* delete position in router LSA */ + oi->lsa_pos_beg = 0; + oi->lsa_pos_end = 0; + /* Shutdown packet reception and sending */ + ospf_if_stream_unset(oi); + + if (ospf->new_table) + ospf_if_down_mark_routes_changed(ospf->new_table, oi); + + if (ospf->new_external_route) + ospf_if_down_mark_routes_changed(ospf->new_external_route, oi); + + return 1; +} + + +/* Virtual Link related functions. */ + +struct ospf_vl_data *ospf_vl_data_new(struct ospf_area *area, + struct in_addr vl_peer) +{ + struct ospf_vl_data *vl_data; + + vl_data = XCALLOC(MTYPE_OSPF_VL_DATA, sizeof(struct ospf_vl_data)); + + vl_data->vl_peer.s_addr = vl_peer.s_addr; + vl_data->vl_area_id = area->area_id; + vl_data->vl_area_id_fmt = area->area_id_fmt; + + return vl_data; +} + +void ospf_vl_data_free(struct ospf_vl_data *vl_data) +{ + XFREE(MTYPE_OSPF_VL_DATA, vl_data); +} + +unsigned int vlink_count = 0; + +struct ospf_interface *ospf_vl_new(struct ospf *ospf, + struct ospf_vl_data *vl_data) +{ + struct ospf_interface *voi; + struct interface *vi; + char ifname[IFNAMSIZ]; + struct ospf_area *area; + struct in_addr area_id; + struct connected *co; + struct prefix_ipv4 *p; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: (%s): Start", __func__, ospf_get_name(ospf)); + if (vlink_count == OSPF_VL_MAX_COUNT) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Alarm: cannot create more than OSPF_MAX_VL_COUNT virtual links", + __func__); + + return NULL; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: creating pseudo zebra interface vrf id %u", + __func__, ospf->vrf_id); + + snprintf(ifname, sizeof(ifname), "VLINK%u", vlink_count); + vi = if_get_by_name(ifname, ospf->vrf_id, ospf->name); + /* + * if_get_by_name sets ZEBRA_INTERFACE_LINKDETECTION + * virtual links don't need this. + */ + UNSET_FLAG(vi->status, ZEBRA_INTERFACE_LINKDETECTION); + co = connected_new(); + co->ifp = vi; + if_connected_add_tail(vi->connected, co); + + p = prefix_ipv4_new(); + p->family = AF_INET; + p->prefix.s_addr = INADDR_ANY; + p->prefixlen = 0; + + co->address = (struct prefix *)p; + + voi = ospf_if_new(ospf, vi, co->address); + if (voi == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Alarm: OSPF int structure is not created", + __func__); + + return NULL; + } + voi->connected = co; + voi->vl_data = vl_data; + voi->ifp->mtu = OSPF_VL_MTU; + voi->type = OSPF_IFTYPE_VIRTUALLINK; + + vlink_count++; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Created name: %s set if->name to %s", __func__, + ifname, vi->name); + + area_id.s_addr = INADDR_ANY; + area = ospf_area_get(ospf, area_id); + voi->area = area; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: set associated area to the backbone", __func__); + + /* Add pseudo neighbor. */ + ospf_nbr_self_reset(voi, voi->ospf->router_id); + + ospf_area_add_if(voi->area, voi); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); + return voi; +} + +static void ospf_vl_if_delete(struct ospf_vl_data *vl_data) +{ + struct interface *ifp = vl_data->vl_oi->ifp; + struct vrf *vrf = ifp->vrf; + + vl_data->vl_oi->address->u.prefix4.s_addr = INADDR_ANY; + vl_data->vl_oi->address->prefixlen = 0; + ospf_if_free(vl_data->vl_oi); + if_delete(&ifp); + if (!vrf_is_enabled(vrf)) + vrf_delete(vrf); +} + +/* for a defined area, count the number of configured vl + */ +int ospf_vl_count(struct ospf *ospf, struct ospf_area *area) +{ + int count = 0; + struct ospf_vl_data *vl_data; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { + if (area + && !IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) + continue; + count++; + } + return count; +} + +/* Look up vl_data for given peer, optionally qualified to be in the + * specified area. NULL area returns first found.. + */ +struct ospf_vl_data *ospf_vl_lookup(struct ospf *ospf, struct ospf_area *area, + struct in_addr vl_peer) +{ + struct ospf_vl_data *vl_data; + struct listnode *node; + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: Looking for %pI4", __func__, &vl_peer); + if (area) + zlog_debug("%s: in area %pI4", __func__, + &area->area_id); + } + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: VL %s, peer %pI4", __func__, + vl_data->vl_oi->ifp->name, + &vl_data->vl_peer); + + if (area + && !IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) + continue; + + if (IPV4_ADDR_SAME(&vl_data->vl_peer, &vl_peer)) + return vl_data; + } + + return NULL; +} + +static void ospf_vl_shutdown(struct ospf_vl_data *vl_data) +{ + struct ospf_interface *oi; + + if ((oi = vl_data->vl_oi) == NULL) + return; + + oi->address->u.prefix4.s_addr = INADDR_ANY; + oi->address->prefixlen = 0; + + UNSET_FLAG(oi->ifp->flags, IFF_UP); + /* OSPF_ISM_EVENT_SCHEDULE (oi, ISM_InterfaceDown); */ + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); +} + +void ospf_vl_add(struct ospf *ospf, struct ospf_vl_data *vl_data) +{ + listnode_add(ospf->vlinks, vl_data); + hook_call(ospf_vl_add, vl_data); +} + +void ospf_vl_delete(struct ospf *ospf, struct ospf_vl_data *vl_data) +{ + ospf_vl_shutdown(vl_data); + ospf_vl_if_delete(vl_data); + + hook_call(ospf_vl_delete, vl_data); + listnode_delete(ospf->vlinks, vl_data); + + ospf_vl_data_free(vl_data); +} + +static int ospf_vl_set_params(struct ospf_area *area, + struct ospf_vl_data *vl_data, struct vertex *v) +{ + int changed = 0; + struct ospf_interface *voi; + struct listnode *node; + struct vertex_parent *vp = NULL; + unsigned int i; + struct router_lsa *rl; + struct ospf_interface *oi; + + voi = vl_data->vl_oi; + + if (voi->output_cost != v->distance) { + + voi->output_cost = v->distance; + changed = 1; + } + + for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { + vl_data->nexthop.lsa_pos = vp->nexthop->lsa_pos; + vl_data->nexthop.router = vp->nexthop->router; + + /* + * Only deal with interface data when the local + * (calculating) node is the SPF root node + */ + if (!area->spf_dry_run) { + oi = ospf_if_lookup_by_lsa_pos( + area, vl_data->nexthop.lsa_pos); + + if (!IPV4_ADDR_SAME(&voi->address->u.prefix4, + &oi->address->u.prefix4)) + changed = 1; + + voi->address->u.prefix4 = oi->address->u.prefix4; + voi->address->prefixlen = oi->address->prefixlen; + } + + break; /* We take the first interface. */ + } + + rl = (struct router_lsa *)v->lsa; + + /* use SPF determined backlink index in struct vertex + * for virtual link destination address + */ + if (vp && vp->backlink >= 0) { + if (!IPV4_ADDR_SAME(&vl_data->peer_addr, + &rl->link[vp->backlink].link_data)) + changed = 1; + vl_data->peer_addr = rl->link[vp->backlink].link_data; + } else { + /* This is highly odd, there is no backlink index + * there should be due to the ospf_spf_has_link() check + * in SPF. Lets warn and try pick a link anyway. + */ + zlog_info("ospf_vl_set_params: No backlink for %s!", + vl_data->vl_oi->ifp->name); + for (i = 0; i < ntohs(rl->links); i++) { + switch (rl->link[i].type) { + case LSA_LINK_TYPE_VIRTUALLINK: + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "found back link through VL"); + fallthrough; + case LSA_LINK_TYPE_TRANSIT: + case LSA_LINK_TYPE_POINTOPOINT: + if (!IPV4_ADDR_SAME(&vl_data->peer_addr, + &rl->link[i].link_data)) + changed = 1; + vl_data->peer_addr = rl->link[i].link_data; + } + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: %s peer address: %pI4, cost: %d,%schanged", + __func__, vl_data->vl_oi->ifp->name, + &vl_data->peer_addr, voi->output_cost, + (changed ? " " : " un")); + + return changed; +} + + +void ospf_vl_up_check(struct ospf_area *area, struct in_addr rid, + struct vertex *v) +{ + struct ospf *ospf = area->ospf; + struct listnode *node; + struct ospf_vl_data *vl_data; + struct ospf_interface *oi; + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: Start", __func__); + zlog_debug("%s: Router ID is %pI4 Area is %pI4", __func__, &rid, + &area->area_id); + } + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: considering VL, %s in area %pI4", + __func__, vl_data->vl_oi->ifp->name, + &vl_data->vl_area_id); + zlog_debug("%s: peer ID: %pI4", __func__, + &vl_data->vl_peer); + } + + if (IPV4_ADDR_SAME(&vl_data->vl_peer, &rid) + && IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) { + oi = vl_data->vl_oi; + SET_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this VL matched", __func__); + + if (oi->state == ISM_Down) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: VL is down, waking it up", + __func__); + SET_FLAG(oi->ifp->flags, IFF_UP); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } + + if (ospf_vl_set_params(area, vl_data, v)) { + if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + zlog_debug( + "%s: VL cost change, scheduling router lsa refresh", + __func__); + if (ospf->backbone) + ospf_router_lsa_update_area( + ospf->backbone); + else if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + zlog_debug( + "%s: VL cost change, no backbone!", + __func__); + } + } + } +} + +void ospf_vl_unapprove(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_vl_data *vl_data; + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) + UNSET_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED); +} + +void ospf_vl_shut_unapproved(struct ospf *ospf) +{ + struct listnode *node, *nnode; + struct ospf_vl_data *vl_data; + + for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) + if (!CHECK_FLAG(vl_data->flags, OSPF_VL_FLAG_APPROVED)) + ospf_vl_shutdown(vl_data); +} + +int ospf_full_virtual_nbrs(struct ospf_area *area) +{ + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug( + "counting fully adjacent virtual neighbors in area %pI4", + &area->area_id); + zlog_debug("there are %d of them", area->full_vls); + } + + return area->full_vls; +} + +int ospf_vls_in_area(struct ospf_area *area) +{ + struct listnode *node; + struct ospf_vl_data *vl_data; + int c = 0; + + for (ALL_LIST_ELEMENTS_RO(area->ospf->vlinks, node, vl_data)) + if (IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) + c++; + + return c; +} + + +struct crypt_key *ospf_crypt_key_new(void) +{ + return XCALLOC(MTYPE_OSPF_CRYPT_KEY, sizeof(struct crypt_key)); +} + +void ospf_crypt_key_add(struct list *crypt, struct crypt_key *ck) +{ + listnode_add(crypt, ck); +} + +struct crypt_key *ospf_crypt_key_lookup(struct list *auth_crypt, uint8_t key_id) +{ + struct listnode *node; + struct crypt_key *ck; + + for (ALL_LIST_ELEMENTS_RO(auth_crypt, node, ck)) + if (ck->key_id == key_id) + return ck; + + return NULL; +} + +int ospf_crypt_key_delete(struct list *auth_crypt, uint8_t key_id) +{ + struct listnode *node, *nnode; + struct crypt_key *ck; + + for (ALL_LIST_ELEMENTS(auth_crypt, node, nnode, ck)) { + if (ck->key_id == key_id) { + listnode_delete(auth_crypt, ck); + XFREE(MTYPE_OSPF_CRYPT_KEY, ck); + return 1; + } + } + + return 0; +} + +uint8_t ospf_default_iftype(struct interface *ifp) +{ + if (if_is_pointopoint(ifp)) + return OSPF_IFTYPE_POINTOPOINT; + else if (if_is_loopback(ifp)) + return OSPF_IFTYPE_LOOPBACK; + else + return OSPF_IFTYPE_BROADCAST; +} + +void ospf_if_interface(struct interface *ifp) +{ + hook_call(ospf_if_update, ifp); +} + +uint32_t ospf_if_count_area_params(struct interface *ifp) +{ + struct ospf_if_params *params; + struct route_node *rn; + uint32_t count = 0; + + params = IF_DEF_PARAMS(ifp); + if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) + count++; + + for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) + if ((params = rn->info) + && OSPF_IF_PARAM_CONFIGURED(params, if_area)) + count++; + + return count; +} + +static int ospf_ifp_create(struct interface *ifp) +{ + struct ospf *ospf = NULL; + struct ospf_if_info *oii; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: interface add %s vrf %s[%u] index %d flags %llx metric %d mtu %d speed %u status 0x%x", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu, ifp->speed, ifp->status); + + assert(ifp->info); + + oii = ifp->info; + oii->curr_mtu = ifp->mtu; + + /* Change ospf type param based on following + * condition: + * ospf type params is not set (first creation), + * OR ospf param type is changed based on + * link event, currently only handle for + * loopback interface type, for other ospf interface, + * type can be set from user config which needs to be + * preserved. + */ + if (IF_DEF_PARAMS(ifp) && + (!OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS(ifp), type) || + if_is_loopback(ifp))) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), type); + if (!IF_DEF_PARAMS(ifp)->type_cfg) + IF_DEF_PARAMS(ifp)->type = ospf_default_iftype(ifp); + } + + ospf = ifp->vrf->info; + if (!ospf) + return 0; + + if (ospf_if_count_area_params(ifp) > 0) + ospf_interface_area_set(ospf, ifp); + + ospf_if_recalculate_output_cost(ifp); + + ospf_if_update(ospf, ifp); + + if (HAS_LINK_PARAMS(ifp)) + ospf_mpls_te_update_if(ifp); + + hook_call(ospf_if_update, ifp); + + return 0; +} + +static int ospf_ifp_up(struct interface *ifp) +{ + struct ospf_interface *oi; + struct route_node *rn; + struct ospf_if_info *oii = ifp->info; + struct ospf *ospf; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: Interface[%s] state change to up.", + ifp->name); + + /* Open per-intf write socket if configured */ + ospf = ifp->vrf->info; + + if (ospf && ospf->oi_running && ospf->intf_socket_enabled) + ospf_ifp_sock_init(ifp); + + ospf_if_recalculate_output_cost(ifp); + + if (oii && oii->curr_mtu != ifp->mtu) { + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: Interface[%s] MTU change %u -> %u.", + ifp->name, oii->curr_mtu, ifp->mtu); + + oii->curr_mtu = ifp->mtu; + /* Must reset the interface (simulate down/up) when MTU + * changes. */ + ospf_if_reset(ifp); + + return 0; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + if ((oi = rn->info) == NULL) + continue; + + ospf_if_up(oi); + } + + if (HAS_LINK_PARAMS(ifp)) + ospf_mpls_te_update_if(ifp); + + return 0; +} + +static int ospf_ifp_down(struct interface *ifp) +{ + struct ospf_interface *oi; + struct route_node *node; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: Interface[%s] state change to down.", + ifp->name); + + for (node = route_top(IF_OIFS(ifp)); node; node = route_next(node)) { + if ((oi = node->info) == NULL) + continue; + ospf_if_down(oi); + } + + /* Close per-interface write socket if configured */ + ospf_ifp_sock_close(ifp); + + return 0; +} + +static int ospf_ifp_destroy(struct interface *ifp) +{ + struct ospf *ospf; + struct route_node *rn; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug( + "Zebra: interface delete %s vrf %s[%u] index %d flags %llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + hook_call(ospf_if_delete, ifp); + + ospf = ifp->vrf->info; + if (ospf) { + if (ospf_if_count_area_params(ifp) > 0) + ospf_interface_area_unset(ospf, ifp); + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) + if (rn->info) + ospf_if_free((struct ospf_interface *)rn->info); + + return 0; +} + +/* Resetting ospf hello timer */ +void ospf_reset_hello_timer(struct interface *ifp, struct in_addr addr, + bool is_addr) +{ + struct route_node *rn; + + if (is_addr) { + struct prefix p; + struct ospf_interface *oi = NULL; + + p.u.prefix4 = addr; + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + + oi = ospf_if_table_lookup(ifp, &p); + + if (oi) { + /* Send hello before restart the hello timer + * to avoid session flaps in case of bigger + * hello interval configurations. + */ + ospf_hello_send(oi); + + /* Restart hello timer for this interface */ + EVENT_OFF(oi->t_hello); + OSPF_HELLO_TIMER_ON(oi); + } + + return; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + /* If hello interval configured on this oi, don't restart. */ + if (OSPF_IF_PARAM_CONFIGURED(oi->params, v_hello)) + continue; + + /* Send hello before restart the hello timer + * to avoid session flaps in case of bigger + * hello interval configurations. + */ + ospf_hello_send(oi); + + /* Restart the hello timer. */ + EVENT_OFF(oi->t_hello); + OSPF_HELLO_TIMER_ON(oi); + } +} + +void ospf_if_init(void) +{ + hook_register_prio(if_real, 0, ospf_ifp_create); + hook_register_prio(if_up, 0, ospf_ifp_up); + hook_register_prio(if_down, 0, ospf_ifp_down); + hook_register_prio(if_unreal, 0, ospf_ifp_destroy); + + /* Initialize Zebra interface data structure. */ + hook_register_prio(if_add, 0, ospf_if_new_hook); + hook_register_prio(if_del, 0, ospf_if_delete_hook); +} diff --git a/ospfd/ospf_interface.h b/ospfd/ospf_interface.h new file mode 100644 index 0000000..45d0b79 --- /dev/null +++ b/ospfd/ospf_interface.h @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Interface functions. + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_INTERFACE_H +#define _ZEBRA_OSPF_INTERFACE_H + +#include "lib/bfd.h" +#include "qobj.h" +#include "hook.h" +#include "keychain.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" + +#define IF_OSPF_IF_INFO(I) ((struct ospf_if_info *)((I)->info)) +#define IF_DEF_PARAMS(I) (IF_OSPF_IF_INFO (I)->def_params) +#define IF_OIFS(I) (IF_OSPF_IF_INFO (I)->oifs) +#define IF_OIFS_PARAMS(I) (IF_OSPF_IF_INFO (I)->params) + +/* Despite the name, this macro probably is for specialist use only */ +#define OSPF_IF_PARAM_CONFIGURED(S, P) ((S) && (S)->P##__config) + +/* Test whether an OSPF interface parameter is set, generally, given some + * existing ospf interface + */ +#define OSPF_IF_PARAM_IS_SET(O, P) \ + (OSPF_IF_PARAM_CONFIGURED((O)->params, P) \ + || OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS((O)->ifp)->P)) + +#define OSPF_IF_PARAM(O, P) \ + (OSPF_IF_PARAM_CONFIGURED((O)->params, P) \ + ? (O)->params->P \ + : IF_DEF_PARAMS((O)->ifp)->P) + +#define DECLARE_IF_PARAM(T, P) \ + T P; \ + uint8_t P##__config : 1 +#define UNSET_IF_PARAM(S, P) ((S)->P##__config) = 0 +#define SET_IF_PARAM(S, P) ((S)->P##__config) = 1 + +struct ospf_if_params { + DECLARE_IF_PARAM(uint32_t, + transmit_delay); /* Interface Transmisson Delay */ + DECLARE_IF_PARAM(uint32_t, + output_cost_cmd); /* Command Interface Output Cost */ + DECLARE_IF_PARAM(uint32_t, + retransmit_interval); /* Retransmission Interval */ + DECLARE_IF_PARAM(uint8_t, passive_interface); /* OSPF Interface is + passive: no sending or + receiving (no need to + join multicast groups) + */ + DECLARE_IF_PARAM(uint8_t, priority); /* OSPF Interface priority */ + /* Enable OSPF on this interface with area if_area */ + DECLARE_IF_PARAM(struct in_addr, if_area); + uint32_t if_area_id_fmt; + + bool type_cfg; + DECLARE_IF_PARAM(uint8_t, type); /* type of interface */ +#define OSPF_IF_ACTIVE 0 +#define OSPF_IF_PASSIVE 1 + +#define OSPF_IF_PASSIVE_STATUS(O) \ + (OSPF_IF_PARAM_CONFIGURED((O)->params, passive_interface) \ + ? (O)->params->passive_interface \ + : (OSPF_IF_PARAM_CONFIGURED(IF_DEF_PARAMS((O)->ifp), \ + passive_interface) \ + ? IF_DEF_PARAMS((O)->ifp)->passive_interface \ + : (O)->ospf->passive_interface_default)) + + DECLARE_IF_PARAM(uint32_t, v_hello); /* Hello Interval */ + DECLARE_IF_PARAM(uint32_t, v_wait); /* Router Dead Interval */ + bool is_v_wait_set; /* Check for Dead Interval set */ + + /* GR Hello Delay Interval */ + DECLARE_IF_PARAM(uint16_t, v_gr_hello_delay); + + /* MTU mismatch check (see RFC2328, chap 10.6) */ + DECLARE_IF_PARAM(uint8_t, mtu_ignore); + + /* Fast-Hellos */ + DECLARE_IF_PARAM(uint8_t, fast_hello); + + /* Prefix-Suppression */ + DECLARE_IF_PARAM(bool, prefix_suppression); + + /* Authentication data. */ + uint8_t auth_simple[OSPF_AUTH_SIMPLE_SIZE + 1]; /* Simple password. */ + uint8_t auth_simple__config : 1; + + DECLARE_IF_PARAM(struct list *, + auth_crypt); /* List of Auth cryptographic data. */ + DECLARE_IF_PARAM(int, auth_type); /* OSPF authentication type */ + + DECLARE_IF_PARAM(char*, keychain_name); /* OSPF HMAC Cryptographic Authentication*/ + + /* Other, non-configuration state */ + uint32_t network_lsa_seqnum; /* Network LSA seqnum */ + + /* BFD configuration */ + struct bfd_configuration { + /** BFD session detection multiplier. */ + uint8_t detection_multiplier; + /** BFD session minimum required receive interval. */ + uint32_t min_rx; + /** BFD session minimum required transmission interval. */ + uint32_t min_tx; + /** BFD profile. */ + char profile[BFD_PROFILE_NAME_LEN]; + } *bfd_config; + + /* MPLS LDP-IGP Sync configuration */ + struct ldp_sync_info *ldp_sync_info; + + /* point-to-point DMVPN configuration */ + uint8_t ptp_dmvpn; + + /* point-to-multipoint delayed reflooding configuration */ + bool p2mp_delay_reflood; + + /* point-to-multipoint doesn't support broadcast */ + bool p2mp_non_broadcast; + + /* Opaque LSA capability at interface level (see RFC5250) */ + DECLARE_IF_PARAM(bool, opaque_capable); + + /* Name of prefix-list name for packet source address filtering. */ + DECLARE_IF_PARAM(char *, nbr_filter_name); +}; + +enum { MEMBER_ALLROUTERS = 0, + MEMBER_DROUTERS, + MEMBER_MAX, +}; + +struct ospf_if_info { + struct ospf_if_params *def_params; + struct route_table *params; + struct route_table *oifs; + unsigned int + membership_counts[MEMBER_MAX]; /* multicast group refcnts */ + + uint32_t curr_mtu; + + /* Per-interface write socket, configured via 'ospf' object */ + int oii_fd; +}; + +struct ospf_interface; + +struct ospf_vl_data { + struct in_addr vl_peer; /* Router-ID of the peer */ + struct in_addr vl_area_id; /* Transit area */ + int vl_area_id_fmt; /* Area ID format */ + struct ospf_interface *vl_oi; /* Interface data structure */ + struct vertex_nexthop nexthop; /* Nexthop router and oi to use */ + struct in_addr peer_addr; /* Address used to reach the peer */ + uint8_t flags; +}; + + +#define OSPF_VL_MAX_COUNT 256 +#define OSPF_VL_MTU 1500 + +#define OSPF_VL_FLAG_APPROVED 0x01 + +struct crypt_key { + uint8_t key_id; + uint8_t auth_key[OSPF_AUTH_MD5_SIZE + 1]; +}; + +/* OSPF interface structure. */ +struct ospf_interface { + /* This interface's parent ospf instance. */ + struct ospf *ospf; + + /* OSPF Area. */ + struct ospf_area *area; + + /* Position range in Router LSA */ + uint16_t lsa_pos_beg; /* inclusive, >= */ + uint16_t lsa_pos_end; /* exclusive, < */ + + /* Interface data from zebra. */ + struct interface *ifp; + struct ospf_vl_data *vl_data; /* Data for Virtual Link */ + + /* Packet send buffer. */ + struct ospf_fifo *obuf; /* Output queue */ + + /* OSPF Network Type. */ + uint8_t type; +#define OSPF_IF_NON_BROADCAST(O) \ + (((O)->type == OSPF_IFTYPE_NBMA) || \ + ((((O)->type == OSPF_IFTYPE_POINTOMULTIPOINT) && \ + (O)->p2mp_non_broadcast))) + + /* point-to-point DMVPN configuration */ + uint8_t ptp_dmvpn; + + /* point-to-multipoint delayed reflooding */ + bool p2mp_delay_reflood; + + /* point-to-multipoint doesn't support broadcast */ + bool p2mp_non_broadcast; + + /* State of Interface State Machine. */ + uint8_t state; + + /* To which multicast groups do we currently belong? */ + uint8_t multicast_memberships; +#define OI_MEMBER_FLAG(M) (1 << (M)) +#define OI_MEMBER_COUNT(O,M) (IF_OSPF_IF_INFO(oi->ifp)->membership_counts[(M)]) +#define OI_MEMBER_CHECK(O, M) \ + (CHECK_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M))) +#define OI_MEMBER_JOINED(O, M) \ + do { \ + SET_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M)); \ + IF_OSPF_IF_INFO((O)->ifp)->membership_counts[(M)]++; \ + } while (0) +#define OI_MEMBER_LEFT(O, M) \ + do { \ + UNSET_FLAG((O)->multicast_memberships, OI_MEMBER_FLAG(M)); \ + IF_OSPF_IF_INFO((O)->ifp)->membership_counts[(M)]--; \ + } while (0) + + struct prefix *address; /* Interface prefix */ + struct connected *connected; /* Pointer to connected */ + + /* Configured varables. */ + struct ospf_if_params *params; + + uint32_t crypt_seqnum; /* Cryptographic Sequence Number */ + uint32_t output_cost; /* Acutual Interface Output Cost */ + + /* Neighbor information. */ + struct route_table *nbrs; /* OSPF Neighbor List */ + struct ospf_neighbor *nbr_self; /* Neighbor Self */ +#define DR(I) ((I)->nbr_self->d_router) +#define BDR(I) ((I)->nbr_self->bd_router) +#define OPTIONS(I) ((I)->nbr_self->options) +#define PRIORITY(I) ((I)->nbr_self->priority) + + /* List of configured NBMA neighbor. */ + struct list *nbr_nbma; + + /* Configured prefix-list for filtering neighbors. */ + struct prefix_list *nbr_filter; + + /* Graceful-Restart data. */ + struct { + struct { + uint16_t elapsed_seconds; + struct event *t_grace_send; + } hello_delay; + } gr; + + /* self-originated LSAs. */ + struct ospf_lsa *network_lsa_self; /* network-LSA. */ + struct list *opaque_lsa_self; /* Type-9 Opaque-LSAs */ + + struct route_table *ls_upd_queue; + + struct list *ls_ack; /* Link State Acknowledgment list. */ + + struct { + struct list *ls_ack; + struct in_addr dst; + } ls_ack_direct; + + /* Timer values. */ + uint32_t v_ls_ack; /* Delayed Link State Acknowledgment */ + + /* Threads. */ + struct event *t_hello; /* timer */ + struct event *t_wait; /* timer */ + struct event *t_ls_ack; /* timer */ + struct event *t_ls_ack_direct; /* event */ + struct event *t_ls_upd_event; /* event */ + struct event *t_opaque_lsa_self; /* Type-9 Opaque-LSAs */ + + int on_write_q; + + /* Statistics fields. */ + uint32_t hello_in; /* Hello message input count. */ + uint32_t hello_out; /* Hello message output count. */ + uint32_t db_desc_in; /* database desc. message input count. */ + uint32_t db_desc_out; /* database desc. message output count. */ + uint32_t ls_req_in; /* LS request message input count. */ + uint32_t ls_req_out; /* LS request message output count. */ + uint32_t ls_upd_in; /* LS update message input count. */ + uint32_t ls_upd_out; /* LS update message output count. */ + uint32_t ls_ack_in; /* LS Ack message input count. */ + uint32_t ls_ack_out; /* LS Ack message output count. */ + uint32_t discarded; /* discarded input count by error. */ + uint32_t state_change; /* Number of status change. */ + + uint32_t full_nbrs; + + /* Buffered values for keychain and key */ + struct keychain *keychain; + struct key *key; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(ospf_interface); + +/* Prototypes. */ +extern char *ospf_if_name(struct ospf_interface *oi); +extern struct ospf_interface * +ospf_if_new(struct ospf *ospf, struct interface *ifp, struct prefix *p); +extern void ospf_if_cleanup(struct ospf_interface *oi); +extern void ospf_if_free(struct ospf_interface *oi); +extern int ospf_if_up(struct ospf_interface *oi); +extern int ospf_if_down(struct ospf_interface *oi); + +extern int ospf_if_is_up(struct ospf_interface *oi); +extern struct ospf_interface *ospf_if_lookup_by_lsa_pos(struct ospf_area *area, + int lsa_pos); +extern struct ospf_interface * +ospf_if_lookup_by_local_addr(struct ospf *ospf, struct interface *ifp, + struct in_addr addr); +extern struct ospf_interface *ospf_if_lookup_by_prefix(struct ospf *ospf, + struct prefix_ipv4 *p); +extern struct ospf_interface *ospf_if_table_lookup(struct interface *ifp, + struct prefix *p); +extern struct ospf_interface *ospf_if_addr_local(struct in_addr addr); +extern struct ospf_interface *ospf_if_lookup_recv_if(struct ospf *ospf, + struct in_addr addr, + struct interface *ifp); +extern struct ospf_interface *ospf_if_is_configured(struct ospf *ospf, + struct in_addr *addr); + +extern struct ospf_if_params *ospf_lookup_if_params(struct interface *ifp, + struct in_addr addr); +extern struct ospf_if_params *ospf_get_if_params(struct interface *ifp, + struct in_addr addr); +extern void ospf_free_if_params(struct interface *ifp, struct in_addr addr); +extern void ospf_if_update_params(struct interface *ifp, struct in_addr addr); + +extern int ospf_if_new_hook(struct interface *ifp); +extern void ospf_if_init(void); +extern void ospf_if_stream_unset(struct ospf_interface *oi); +extern int ospf_if_is_enable(struct ospf_interface *oi); +extern int ospf_if_get_output_cost(struct ospf_interface *oi); +extern void ospf_if_recalculate_output_cost(struct interface *ifp); + +/* Simulate down/up on the interface. */ +extern void ospf_if_reset(struct interface *ifp); + +extern struct ospf_interface *ospf_vl_new(struct ospf *ospf, + struct ospf_vl_data *vl_data); +extern struct ospf_vl_data *ospf_vl_data_new(struct ospf_area *area, + struct in_addr addr); +extern struct ospf_vl_data * +ospf_vl_lookup(struct ospf *ospf, struct ospf_area *area, struct in_addr addr); +extern int ospf_vl_count(struct ospf *ospf, struct ospf_area *area); +extern void ospf_vl_data_free(struct ospf_vl_data *vl_data); +extern void ospf_vl_add(struct ospf *ospf, struct ospf_vl_data *vl_data); +extern void ospf_vl_delete(struct ospf *ospf, struct ospf_vl_data *vl_data); +extern void ospf_vl_up_check(struct ospf_area *area, struct in_addr addr, + struct vertex *vertex); +extern void ospf_vl_unapprove(struct ospf *ospf); +extern void ospf_vl_shut_unapproved(struct ospf *ospf); +extern int ospf_full_virtual_nbrs(struct ospf_area *area); +extern int ospf_vls_in_area(struct ospf_area *area); + +extern struct crypt_key *ospf_crypt_key_lookup(struct list *list, + uint8_t key_id); +extern struct crypt_key *ospf_crypt_key_new(void); +extern void ospf_crypt_key_add(struct list *list, struct crypt_key *key); +extern int ospf_crypt_key_delete(struct list *list, uint8_t key_id); +extern uint8_t ospf_default_iftype(struct interface *ifp); +extern int ospf_interface_neighbor_count(struct ospf_interface *oi); +extern void ospf_intf_neighbor_filter_apply(struct ospf_interface *oi); + +/* Set all multicast memberships appropriately based on the type and + state of the interface. */ +extern void ospf_if_set_multicast(struct ospf_interface *oi); + +extern void ospf_if_interface(struct interface *ifp); + +extern uint32_t ospf_if_count_area_params(struct interface *ifp); +extern void ospf_reset_hello_timer(struct interface *ifp, struct in_addr addr, + bool is_addr); + +extern void ospf_interface_fifo_flush(struct ospf_interface *oi); +DECLARE_HOOK(ospf_vl_add, (struct ospf_vl_data * vd), (vd)); +DECLARE_HOOK(ospf_vl_delete, (struct ospf_vl_data * vd), (vd)); + +DECLARE_HOOK(ospf_if_update, (struct interface * ifp), (ifp)); +DECLARE_HOOK(ospf_if_delete, (struct interface * ifp), (ifp)); + +#endif /* _ZEBRA_OSPF_INTERFACE_H */ diff --git a/ospfd/ospf_ism.c b/ospfd/ospf_ism.c new file mode 100644 index 0000000..878ab72 --- /dev/null +++ b/ospfd/ospf_ism.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF version 2 Interface State Machine + * From RFC2328 [OSPF Version 2] + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_abr.h" + +DEFINE_HOOK(ospf_ism_change, + (struct ospf_interface * oi, int state, int oldstate), + (oi, state, oldstate)); + +/* elect DR and BDR. Refer to RFC2319 section 9.4 */ +static struct ospf_neighbor *ospf_dr_election_sub(struct list *routers) +{ + struct listnode *node; + struct ospf_neighbor *nbr, *max = NULL; + + /* Choose highest router priority. + In case of tie, choose highest Router ID. */ + for (ALL_LIST_ELEMENTS_RO(routers, node, nbr)) { + if (max == NULL) + max = nbr; + else { + if (max->priority < nbr->priority) + max = nbr; + else if (max->priority == nbr->priority) + if (IPV4_ADDR_CMP(&max->router_id, + &nbr->router_id) + < 0) + max = nbr; + } + } + + return max; +} + +static struct ospf_neighbor *ospf_elect_dr(struct ospf_interface *oi, + struct list *el_list) +{ + struct list *dr_list; + struct listnode *node; + struct ospf_neighbor *nbr, *dr = NULL, *bdr = NULL; + + dr_list = list_new(); + + /* Add neighbors to the list. */ + for (ALL_LIST_ELEMENTS_RO(el_list, node, nbr)) { + /* neighbor declared to be DR. */ + if (NBR_IS_DR(nbr)) + listnode_add(dr_list, nbr); + + /* Preserve neighbor BDR. */ + if (IPV4_ADDR_SAME(&BDR(oi), &nbr->address.u.prefix4)) + bdr = nbr; + } + + /* Elect Designated Router. */ + if (listcount(dr_list) > 0) + dr = ospf_dr_election_sub(dr_list); + else + dr = bdr; + + /* Set DR to interface. */ + if (dr) + DR(oi) = dr->address.u.prefix4; + else + DR(oi).s_addr = 0; + + list_delete(&dr_list); + + return dr; +} + +static struct ospf_neighbor *ospf_elect_bdr(struct ospf_interface *oi, + struct list *el_list) +{ + struct list *bdr_list, *no_dr_list; + struct listnode *node; + struct ospf_neighbor *nbr, *bdr = NULL; + + bdr_list = list_new(); + no_dr_list = list_new(); + + /* Add neighbors to the list. */ + for (ALL_LIST_ELEMENTS_RO(el_list, node, nbr)) { + /* neighbor declared to be DR. */ + if (NBR_IS_DR(nbr)) + continue; + + /* neighbor declared to be BDR. */ + if (NBR_IS_BDR(nbr)) + listnode_add(bdr_list, nbr); + + listnode_add(no_dr_list, nbr); + } + + /* Elect Backup Designated Router. */ + if (listcount(bdr_list) > 0) + bdr = ospf_dr_election_sub(bdr_list); + else + bdr = ospf_dr_election_sub(no_dr_list); + + /* Set BDR to interface. */ + if (bdr) + BDR(oi) = bdr->address.u.prefix4; + else + BDR(oi).s_addr = 0; + + list_delete(&bdr_list); + list_delete(&no_dr_list); + + return bdr; +} + +static int ospf_ism_state(struct ospf_interface *oi) +{ + if (IPV4_ADDR_SAME(&DR(oi), &oi->address->u.prefix4)) + return ISM_DR; + else if (IPV4_ADDR_SAME(&BDR(oi), &oi->address->u.prefix4)) + return ISM_Backup; + else + return ISM_DROther; +} + +static void ospf_dr_eligible_routers(struct route_table *nbrs, + struct list *el_list) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + + for (rn = route_top(nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL) + /* Ignore 0.0.0.0 node*/ + if (nbr->router_id.s_addr != INADDR_ANY) + /* Is neighbor eligible? */ + if (nbr->priority > 0) + /* Is neighbor upper 2-Way? */ + if (nbr->state >= NSM_TwoWay) + listnode_add(el_list, nbr); +} + +/* Generate AdjOK? NSM event. */ +static void ospf_dr_change(struct ospf *ospf, struct route_table *nbrs) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + + for (rn = route_top(nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + /* + * Ignore 0.0.0.0 node + * Is neighbor 2-Way? + * Ignore myself + */ + if (nbr->router_id.s_addr != INADDR_ANY + && nbr->state >= NSM_TwoWay + && !IPV4_ADDR_SAME(&nbr->router_id, &ospf->router_id)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_AdjOK); + } +} + +int ospf_dr_election(struct ospf_interface *oi) +{ + struct in_addr old_dr, old_bdr; + int old_state, new_state; + struct list *el_list; + + /* backup current values. */ + old_dr = DR(oi); + old_bdr = BDR(oi); + old_state = oi->state; + + el_list = list_new(); + + /* List eligible routers. */ + ospf_dr_eligible_routers(oi->nbrs, el_list); + + /* First election of DR and BDR. */ + ospf_elect_bdr(oi, el_list); + ospf_elect_dr(oi, el_list); + + new_state = ospf_ism_state(oi); + + if (IS_DEBUG_OSPF(ism, ISM_STATUS)) { + zlog_debug("DR-Election[1st]: Backup %pI4", &BDR(oi)); + zlog_debug("DR-Election[1st]: DR %pI4", &DR(oi)); + } + + if (new_state != old_state + && !(new_state == ISM_DROther && old_state < ISM_DROther)) { + ospf_elect_bdr(oi, el_list); + ospf_elect_dr(oi, el_list); + + new_state = ospf_ism_state(oi); + + if (IS_DEBUG_OSPF(ism, ISM_STATUS)) { + zlog_debug("DR-Election[2nd]: Backup %pI4", &BDR(oi)); + zlog_debug("DR-Election[2nd]: DR %pI4", &DR(oi)); + } + } + + list_delete(&el_list); + + /* if DR or BDR changes, cause AdjOK? neighbor event. */ + if (!IPV4_ADDR_SAME(&old_dr, &DR(oi)) + || !IPV4_ADDR_SAME(&old_bdr, &BDR(oi))) + ospf_dr_change(oi->ospf, oi->nbrs); + + return new_state; +} + + +void ospf_hello_timer(struct event *thread) +{ + struct ospf_interface *oi; + + oi = EVENT_ARG(thread); + oi->t_hello = NULL; + + /* Check if the GR hello-delay is active. */ + if (oi->gr.hello_delay.t_grace_send) + return; + + if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) + zlog_debug("ISM[%s]: Timer (Hello timer expire)", IF_NAME(oi)); + + /* Sending hello packet. */ + ospf_hello_send(oi); + + /* Hello timer set. */ + OSPF_HELLO_TIMER_ON(oi); +} + +static void ospf_wait_timer(struct event *thread) +{ + struct ospf_interface *oi; + + oi = EVENT_ARG(thread); + oi->t_wait = NULL; + + if (IS_DEBUG_OSPF(ism, ISM_TIMERS)) + zlog_debug("ISM[%s]: Timer (Wait timer expire)", IF_NAME(oi)); + + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_WaitTimer); +} + +/* Hook function called after ospf ISM event is occurred. And vty's + network command invoke this function after making interface + structure. */ +static void ism_timer_set(struct ospf_interface *oi) +{ + switch (oi->state) { + case ISM_Down: + /* First entry point of ospf interface state machine. In this + state + interface parameters must be set to initial values, and + timers are + reset also. */ + EVENT_OFF(oi->t_hello); + EVENT_OFF(oi->t_wait); + EVENT_OFF(oi->t_ls_ack); + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + break; + case ISM_Loopback: + /* In this state, the interface may be looped back and will be + unavailable for regular data traffic. */ + EVENT_OFF(oi->t_hello); + EVENT_OFF(oi->t_wait); + EVENT_OFF(oi->t_ls_ack); + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + break; + case ISM_Waiting: + /* The router is trying to determine the identity of DRouter and + BDRouter. The router begin to receive and send Hello Packets. + */ + /* send first hello immediately */ + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); + OSPF_ISM_TIMER_ON(oi->t_wait, ospf_wait_timer, + OSPF_IF_PARAM(oi, v_wait)); + EVENT_OFF(oi->t_ls_ack); + break; + case ISM_PointToPoint: + /* The interface connects to a physical Point-to-point network + or + virtual link. The router attempts to form an adjacency with + neighboring router. Hello packets are also sent. */ + /* send first hello immediately */ + OSPF_ISM_TIMER_MSEC_ON(oi->t_hello, ospf_hello_timer, 1); + EVENT_OFF(oi->t_wait); + OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, + oi->v_ls_ack); + break; + case ISM_DROther: + /* The network type of the interface is broadcast or NBMA + network, + and the router itself is neither Designated Router nor + Backup Designated Router. */ + OSPF_HELLO_TIMER_ON(oi); + EVENT_OFF(oi->t_wait); + OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, + oi->v_ls_ack); + break; + case ISM_Backup: + /* The network type of the interface is broadcast os NBMA + network, + and the router is Backup Designated Router. */ + OSPF_HELLO_TIMER_ON(oi); + EVENT_OFF(oi->t_wait); + OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, + oi->v_ls_ack); + break; + case ISM_DR: + /* The network type of the interface is broadcast or NBMA + network, + and the router is Designated Router. */ + OSPF_HELLO_TIMER_ON(oi); + EVENT_OFF(oi->t_wait); + OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, + oi->v_ls_ack); + break; + } +} + +static int ism_interface_up(struct ospf_interface *oi) +{ + int next_state = 0; + + /* if network type is point-to-point, Point-to-MultiPoint or virtual + link, + the state transitions to Point-to-Point. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT + || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT + || oi->type == OSPF_IFTYPE_VIRTUALLINK) + next_state = ISM_PointToPoint; + /* Else if the router is not eligible to DR, the state transitions to + DROther. */ + else if (PRIORITY(oi) == 0) /* router is eligible? */ + next_state = ISM_DROther; + else + /* Otherwise, the state transitions to Waiting. */ + next_state = ISM_Waiting; + + if (OSPF_IF_NON_BROADCAST(oi)) + ospf_nbr_nbma_if_update(oi->ospf, oi); + + /* ospf_ism_event (t); */ + return next_state; +} + +static int ism_loop_ind(struct ospf_interface *oi) +{ + /* call ism_interface_down. */ + /* ret = ism_interface_down (oi); */ + + return 0; +} + +/* Interface down event handler. */ +static int ism_interface_down(struct ospf_interface *oi) +{ + ospf_if_cleanup(oi); + return 0; +} + + +static int ism_backup_seen(struct ospf_interface *oi) +{ + return ospf_dr_election(oi); +} + +static int ism_wait_timer(struct ospf_interface *oi) +{ + return ospf_dr_election(oi); +} + +static int ism_neighbor_change(struct ospf_interface *oi) +{ + return ospf_dr_election(oi); +} + +static int ism_ignore(struct ospf_interface *oi) +{ + if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + zlog_debug("ISM[%s]: ism_ignore called", IF_NAME(oi)); + + return 0; +} + +/* Interface State Machine */ +const struct { + int (*func)(struct ospf_interface *); + int next_state; +} ISM[OSPF_ISM_STATE_MAX][OSPF_ISM_EVENT_MAX] = { + { + /* DependUpon: dummy state. */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_DependUpon}, /* InterfaceUp */ + {ism_ignore, ISM_DependUpon}, /* WaitTimer */ + {ism_ignore, ISM_DependUpon}, /* BackupSeen */ + {ism_ignore, ISM_DependUpon}, /* NeighborChange */ + {ism_ignore, ISM_DependUpon}, /* LoopInd */ + {ism_ignore, ISM_DependUpon}, /* UnloopInd */ + {ism_ignore, ISM_DependUpon}, /* InterfaceDown */ + }, + { + /* Down:*/ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_interface_up, ISM_DependUpon}, /* InterfaceUp */ + {ism_ignore, ISM_Down}, /* WaitTimer */ + {ism_ignore, ISM_Down}, /* BackupSeen */ + {ism_ignore, ISM_Down}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_Down}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* Loopback: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_Loopback}, /* InterfaceUp */ + {ism_ignore, ISM_Loopback}, /* WaitTimer */ + {ism_ignore, ISM_Loopback}, /* BackupSeen */ + {ism_ignore, ISM_Loopback}, /* NeighborChange */ + {ism_ignore, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_Down}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* Waiting: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_Waiting}, /* InterfaceUp */ + {ism_wait_timer, ISM_DependUpon}, /* WaitTimer */ + {ism_backup_seen, ISM_DependUpon}, /* BackupSeen */ + {ism_ignore, ISM_Waiting}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_Waiting}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* Point-to-Point: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_PointToPoint}, /* InterfaceUp */ + {ism_ignore, ISM_PointToPoint}, /* WaitTimer */ + {ism_ignore, ISM_PointToPoint}, /* BackupSeen */ + {ism_ignore, ISM_PointToPoint}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_PointToPoint}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* DROther: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_DROther}, /* InterfaceUp */ + {ism_ignore, ISM_DROther}, /* WaitTimer */ + {ism_ignore, ISM_DROther}, /* BackupSeen */ + {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_DROther}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* Backup: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_Backup}, /* InterfaceUp */ + {ism_ignore, ISM_Backup}, /* WaitTimer */ + {ism_ignore, ISM_Backup}, /* BackupSeen */ + {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_Backup}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, + { + /* DR: */ + {ism_ignore, ISM_DependUpon}, /* NoEvent */ + {ism_ignore, ISM_DR}, /* InterfaceUp */ + {ism_ignore, ISM_DR}, /* WaitTimer */ + {ism_ignore, ISM_DR}, /* BackupSeen */ + {ism_neighbor_change, ISM_DependUpon}, /* NeighborChange */ + {ism_loop_ind, ISM_Loopback}, /* LoopInd */ + {ism_ignore, ISM_DR}, /* UnloopInd */ + {ism_interface_down, ISM_Down}, /* InterfaceDown */ + }, +}; + +static const char *const ospf_ism_event_str[] = { + "NoEvent", "InterfaceUp", "WaitTimer", "BackupSeen", + "NeighborChange", "LoopInd", "UnLoopInd", "InterfaceDown", +}; + +static void ism_change_state(struct ospf_interface *oi, int state) +{ + int old_state; + struct ospf_lsa *lsa; + + /* Logging change of state. */ + if (IS_DEBUG_OSPF(ism, ISM_STATUS)) + zlog_debug("ISM[%s]: State change %s -> %s", IF_NAME(oi), + lookup_msg(ospf_ism_state_msg, oi->state, NULL), + lookup_msg(ospf_ism_state_msg, state, NULL)); + + old_state = oi->state; + oi->state = state; + oi->state_change++; + + hook_call(ospf_ism_change, oi, state, old_state); + + /* Set multicast memberships appropriately for new state. */ + ospf_if_set_multicast(oi); + + if (old_state == ISM_Down || state == ISM_Down) + ospf_check_abr_status(oi->ospf); + + /* Originate router-LSA. */ + if (state == ISM_Down) { + if (oi->area->act_ints > 0) + oi->area->act_ints--; + } else if (old_state == ISM_Down) + oi->area->act_ints++; + + /* schedule router-LSA originate. */ + ospf_router_lsa_update_area(oi->area); + + /* Originate network-LSA. */ + if (old_state != ISM_DR && state == ISM_DR) + ospf_network_lsa_update(oi); + else if (old_state == ISM_DR && state != ISM_DR) { + /* Free self originated network LSA. */ + lsa = oi->network_lsa_self; + if (lsa) + ospf_lsa_flush_area(lsa, oi->area); + + ospf_lsa_unlock(&oi->network_lsa_self); + oi->network_lsa_self = NULL; + } + + ospf_opaque_ism_change(oi, old_state); + + /* Check area border status. */ + ospf_check_abr_status(oi->ospf); +} + +/* Execute ISM event process. */ +void ospf_ism_event(struct event *thread) +{ + int event; + int next_state; + struct ospf_interface *oi; + + oi = EVENT_ARG(thread); + event = EVENT_VAL(thread); + + /* Call function. */ + next_state = (*(ISM[oi->state][event].func))(oi); + + if (!next_state) + next_state = ISM[oi->state][event].next_state; + + if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + zlog_debug("ISM[%s]: %s (%s)", IF_NAME(oi), + lookup_msg(ospf_ism_state_msg, oi->state, NULL), + ospf_ism_event_str[event]); + + /* If state is changed. */ + if (next_state != oi->state) + ism_change_state(oi, next_state); + + /* Make sure timer is set. */ + ism_timer_set(oi); +} diff --git a/ospfd/ospf_ism.h b/ospfd/ospf_ism.h new file mode 100644 index 0000000..bbb059c --- /dev/null +++ b/ospfd/ospf_ism.h @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF version 2 Interface State Machine. + * From RFC2328 [OSPF Version 2] + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_ISM_H +#define _ZEBRA_OSPF_ISM_H + +#include "hook.h" + +/* OSPF Interface State Machine Status. */ +#define ISM_DependUpon 0 +#define ISM_Down 1 +#define ISM_Loopback 2 +#define ISM_Waiting 3 +#define ISM_PointToPoint 4 +#define ISM_DROther 5 +#define ISM_Backup 6 +#define ISM_DR 7 +#define OSPF_ISM_STATE_MAX 8 + +/* OSPF Interface State Machine Event. */ +#define ISM_NoEvent 0 +#define ISM_InterfaceUp 1 +#define ISM_WaitTimer 2 +#define ISM_BackupSeen 3 +#define ISM_NeighborChange 4 +#define ISM_LoopInd 5 +#define ISM_UnloopInd 6 +#define ISM_InterfaceDown 7 +#define OSPF_ISM_EVENT_MAX 8 + +#define OSPF_ISM_WRITE_ON(O) \ + do { \ + if (oi->on_write_q == 0) { \ + listnode_add((O)->oi_write_q, oi); \ + oi->on_write_q = 1; \ + } \ + if (!list_isempty((O)->oi_write_q)) \ + event_add_write(master, ospf_write, (O), (O)->fd, \ + &(O)->t_write); \ + } while (0) + +/* Macro for OSPF ISM timer turn on. */ +#define OSPF_ISM_TIMER_ON(T, F, V) event_add_timer(master, (F), oi, (V), &(T)) + +#define OSPF_ISM_TIMER_MSEC_ON(T, F, V) \ + event_add_timer_msec(master, (F), oi, (V), &(T)) + +/* convenience macro to set hello timer correctly, according to + * whether fast-hello is set or not + */ +#define OSPF_HELLO_TIMER_ON(O) \ + do { \ + if (OSPF_IF_PARAM((O), fast_hello)) \ + OSPF_ISM_TIMER_MSEC_ON( \ + (O)->t_hello, ospf_hello_timer, \ + 1000 / OSPF_IF_PARAM((O), fast_hello)); \ + else \ + OSPF_ISM_TIMER_ON((O)->t_hello, ospf_hello_timer, \ + OSPF_IF_PARAM((O), v_hello)); \ + } while (0) + +/* Macro for OSPF schedule event. */ +#define OSPF_ISM_EVENT_SCHEDULE(I, E) \ + event_add_event(master, ospf_ism_event, (I), (E), NULL) + +/* Macro for OSPF execute event. */ +#define OSPF_ISM_EVENT_EXECUTE(I, E) \ + event_execute(master, ospf_ism_event, (I), (E), NULL) + +/* Prototypes. */ +extern void ospf_ism_event(struct event *thread); +extern void ism_change_status(struct ospf_interface *, int); +extern void ospf_hello_timer(struct event *thread); +extern int ospf_dr_election(struct ospf_interface *oi); + +DECLARE_HOOK(ospf_ism_change, + (struct ospf_interface * oi, int state, int oldstate), + (oi, state, oldstate)); + +#endif /* _ZEBRA_OSPF_ISM_H */ diff --git a/ospfd/ospf_ldp_sync.c b/ospfd/ospf_ldp_sync.c new file mode 100644 index 0000000..496ae5b --- /dev/null +++ b/ospfd/ospf_ldp_sync.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ospf_ldp_sync.c: OSPF LDP-IGP Sync handling routines + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#include +#include + +#include "monotime.h" +#include "memory.h" +#include "frrevent.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "plist.h" +#include "log.h" +#include "zclient.h" +#include +#include "defaults.h" +#include "ldp_sync.h" + +#include "ospfd.h" +#include "ospf_interface.h" +#include "ospf_vty.h" +#include "ospf_ldp_sync.h" +#include "ospf_dump.h" +#include "ospf_ism.h" + +extern struct zclient *zclient; + +/* + * LDP-SYNC msg between IGP and LDP + */ +int ospf_ldp_sync_state_update(struct ldp_igp_sync_if_state state) +{ + struct ospf *ospf; + struct interface *ifp; + + /* if ospf is not enabled or LDP-SYNC is not configured ignore */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || + !CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return 0; + + /* received ldp-sync interface state from LDP */ + ifp = if_lookup_by_index(state.ifindex, VRF_DEFAULT); + if (ifp == NULL || if_is_loopback(ifp)) + return 0; + + ols_debug("%s: rcvd %s from LDP if %s", __func__, + state.sync_start ? "sync-start" : "sync-complete", ifp->name); + if (state.sync_start) + ospf_ldp_sync_if_start(ifp, false); + else + ospf_ldp_sync_if_complete(ifp); + + return 0; +} + +int ospf_ldp_sync_announce_update(struct ldp_igp_sync_announce announce) +{ + struct ospf *ospf; + struct vrf *vrf; + struct interface *ifp; + + /* if ospf is not enabled or LDP-SYNC is not configured ignore */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || + !CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return 0; + + if (announce.proto != ZEBRA_ROUTE_LDP) + return 0; + + ols_debug("%s: rcvd announce from LDP", __func__); + + /* LDP just started up: + * set cost to LSInfinity + * send request to LDP for LDP-SYNC state for each interface + */ + vrf = vrf_lookup_by_id(ospf->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) + ospf_ldp_sync_if_start(ifp, true); + + return 0; +} + +void ospf_ldp_sync_state_req_msg(struct interface *ifp) +{ + struct ldp_igp_sync_if_state_req request; + + ols_debug("%s: send state request to LDP for %s", __func__, ifp->name); + + memset(&request, 0, sizeof(request)); + strlcpy(request.name, ifp->name, sizeof(ifp->name)); + request.proto = LDP_IGP_SYNC_IF_STATE_REQUEST; + request.ifindex = ifp->ifindex; + + zclient_send_opaque(zclient, LDP_IGP_SYNC_IF_STATE_REQUEST, + (uint8_t *)&request, sizeof(request)); +} + +/* + * LDP-SYNC general interface routines + */ +void ospf_ldp_sync_if_init(struct ospf_interface *oi) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + struct interface *ifp = oi->ifp; + + /* called when OSPF is configured on an interface: + * if LDP-IGP Sync is configured globally set state + * if ptop interface inform LDP LDP-SYNC is enabled + */ + if (if_is_loopback(ifp) || (ifp->vrf->vrf_id != VRF_DEFAULT) + || !(CHECK_FLAG(oi->ospf->ldp_sync_cmd.flags, + LDP_SYNC_FLAG_ENABLE))) + return; + + ols_debug("%s: init if %s", __func__, ifp->name); + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + + ldp_sync_info = params->ldp_sync_info; + + /* specified on interface overrides global config. */ + if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) + ldp_sync_info->holddown = oi->ospf->ldp_sync_cmd.holddown; + + if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG)) + ldp_sync_info->enabled = LDP_IGP_SYNC_ENABLED; + + if ((params->type == OSPF_IFTYPE_POINTOPOINT || + if_is_pointopoint(ifp)) && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; +} + +void ospf_ldp_sync_if_start(struct interface *ifp, bool send_state_req) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + + /* Start LDP-SYNC on this interface: + * set cost of interface to LSInfinity so traffic will use different + * interface until LDP has learned all labels from peer + * start holddown timer if configured + * send msg to LDP to get LDP-SYNC state + */ + if (ldp_sync_info && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED && + ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) { + ols_debug("%s: start on if %s state: %s", __func__, ifp->name, + "Holding down until Sync"); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + ospf_if_recalculate_output_cost(ifp); + ospf_ldp_sync_holddown_timer_add(ifp); + + if (send_state_req) + ospf_ldp_sync_state_req_msg(ifp); + } +} + +void ospf_ldp_sync_if_complete(struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + + /* received sync-complete from LDP: + * set state to up + * stop timer + * restore interface cost to original value + */ + if (ldp_sync_info && ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) { + if (ldp_sync_info->state == LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP) + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP; + EVENT_OFF(ldp_sync_info->t_holddown); + ospf_if_recalculate_output_cost(ifp); + } +} + +void ospf_ldp_sync_handle_client_close(struct zapi_client_close_info *info) +{ + struct ospf *ospf; + struct vrf *vrf; + struct interface *ifp; + + /* if ospf is not enabled or LDP-SYNC is not configured ignore */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL + || !CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + return; + + /* Check if the LDP main client session closed */ + if (info->proto != ZEBRA_ROUTE_LDP || info->session_id == 0) + return; + + /* Handle the zebra notification that the LDP client session closed. + * set cost to LSInfinity + * send request to LDP for LDP-SYNC state for each interface + */ + zlog_err("%s: LDP down", __func__); + + vrf = vrf_lookup_by_id(ospf->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) + ospf_ldp_sync_ldp_fail(ifp); +} + +void ospf_ldp_sync_ldp_fail(struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + + /* LDP client close detected: + * stop holddown timer + * set cost of interface to LSInfinity so traffic will use different + * interface until LDP has learned all labels from peer + */ + if (ldp_sync_info && + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED && + ldp_sync_info->state != LDP_IGP_SYNC_STATE_NOT_REQUIRED) { + EVENT_OFF(ldp_sync_info->t_holddown); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + ospf_if_recalculate_output_cost(ifp); + } +} + +void ospf_ldp_sync_if_down(struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + + if (ldp_sync_if_down(ldp_sync_info) == false) + return; + + ols_debug("%s: down on if %s", __func__, ifp->name); + + /* Interface down: + * can occur from a link down or changing config + * ospf network type change interface is brought down/up + */ + switch (ldp_sync_info->state) { + case LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP: + case LDP_IGP_SYNC_STATE_REQUIRED_UP: + if (params->type != OSPF_IFTYPE_POINTOPOINT && + !if_is_pointopoint(ifp)) + /* LDP-SYNC not able to run on non-ptop interface */ + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + break; + case LDP_IGP_SYNC_STATE_NOT_REQUIRED: + if (params->type == OSPF_IFTYPE_POINTOPOINT || + if_is_pointopoint(ifp)) + /* LDP-SYNC is able to run on ptop interface */ + ldp_sync_info->state = + LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + break; + default: + break; + } +} + +void ospf_ldp_sync_if_remove(struct interface *ifp, bool remove) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + return; + + ldp_sync_info = params->ldp_sync_info; + + /* Stop LDP-SYNC on this interface: + * if holddown timer is running stop it + * delete ldp instance on interface + * restore cost + */ + ols_debug("%s: Removed from if %s", __func__, ifp->name); + + EVENT_OFF(ldp_sync_info->t_holddown); + + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + ospf_if_recalculate_output_cost(ifp); + if (!CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG)) + ldp_sync_info->enabled = LDP_IGP_SYNC_DEFAULT; + if (remove) { + ldp_sync_info_free(&ldp_sync_info); + params->ldp_sync_info = NULL; + } +} + +static int ospf_ldp_sync_ism_change(struct ospf_interface *oi, int state, + int old_state) +{ + /* Terminal state or regression */ + switch (state) { + case ISM_PointToPoint: + /* If LDP-SYNC is configure on interface then start */ + ospf_ldp_sync_if_start(oi->ifp, true); + break; + case ISM_Down: + /* If LDP-SYNC is configure on this interface then stop it */ + ospf_ldp_sync_if_down(oi->ifp); + break; + default: + break; + } + return 0; +} + +/* + * LDP-SYNC holddown timer routines + */ +static void ospf_ldp_sync_holddown_timer(struct event *thread) +{ + struct interface *ifp; + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + /* holddown timer expired: + * didn't receive msg from LDP indicating sync-complete + * restore interface cost to original value + */ + ifp = EVENT_ARG(thread); + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info) { + ldp_sync_info = params->ldp_sync_info; + + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_UP; + + ols_debug("%s: holddown timer expired for %s state: %s", + __func__, ifp->name, "Sync achieved"); + + ospf_if_recalculate_output_cost(ifp); + } +} + +void ospf_ldp_sync_holddown_timer_add(struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + + /* Start holddown timer: + * this timer is used to keep interface cost at LSInfinity + * once expires returns cost to original value + * if timer is already running or holddown time is off just return + */ + if (ldp_sync_info->t_holddown || + ldp_sync_info->holddown == LDP_IGP_SYNC_HOLDDOWN_DEFAULT) + return; + + ols_debug("%s: start holddown timer for %s time %d", __func__, + ifp->name, ldp_sync_info->holddown); + + event_add_timer(master, ospf_ldp_sync_holddown_timer, ifp, + ldp_sync_info->holddown, &ldp_sync_info->t_holddown); +} + +/* + * LDP-SYNC exit routes. + */ +void ospf_ldp_sync_gbl_exit(struct ospf *ospf, bool remove) +{ + struct interface *ifp; + struct vrf *vrf; + + /* ospf is being removed + * stop any holddown timers + */ + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + /* unregister with opaque client to recv LDP-IGP Sync msgs */ + zclient_unregister_opaque(zclient, + LDP_IGP_SYNC_IF_STATE_UPDATE); + zclient_unregister_opaque(zclient, + LDP_IGP_SYNC_ANNOUNCE_UPDATE); + + /* disable LDP globally */ + UNSET_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE); + UNSET_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + ospf->ldp_sync_cmd.holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; + + /* turn off LDP-IGP Sync on all OSPF interfaces */ + vrf = vrf_lookup_by_id(ospf->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) + ospf_ldp_sync_if_remove(ifp, remove); + } +} + +/* + * LDP-SYNC routes used by set commands. + */ +void ospf_if_set_ldp_sync_enable(struct ospf *ospf, struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + /* called when setting LDP-SYNC at the global level: + * specified on interface overrides global config + * if ptop link send msg to LDP indicating ldp-sync enabled + */ + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + ldp_sync_info = params->ldp_sync_info; + + /* config on interface, overrides global config. */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG)) + if (ldp_sync_info->enabled != LDP_IGP_SYNC_ENABLED) + return; + + ldp_sync_info->enabled = LDP_IGP_SYNC_ENABLED; + + ols_debug("%s: enable if %s", __func__, ifp->name); + + /* send message to LDP if ptop link */ + if (params->type == OSPF_IFTYPE_POINTOPOINT || + if_is_pointopoint(ifp)) { + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + ospf_ldp_sync_state_req_msg(ifp); + } else { + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + zlog_debug("%s: Sync only runs on P2P links %s", __func__, + ifp->name); + } +} + +void ospf_if_set_ldp_sync_holddown(struct ospf *ospf, struct interface *ifp) +{ + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + /* called when setting LDP-SYNC at the global level: + * specified on interface overrides global config. + */ + if (if_is_loopback(ifp)) + return; + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + ldp_sync_info = params->ldp_sync_info; + + /* config on interface, overrides global config. */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) + return; + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN)) + ldp_sync_info->holddown = ospf->ldp_sync_cmd.holddown; + else + ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; +} + +/* + * LDP-SYNC routines used by show commands. + */ + +void ospf_ldp_sync_show_info(struct vty *vty, struct ospf *ospf, + json_object *json_vrf, bool use_json) +{ + + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + if (use_json) { + json_object_boolean_true_add(json_vrf, + "mplsLdpIgpSyncEnabled"); + json_object_int_add(json_vrf, "mplsLdpIgpSyncHolddown", + ospf->ldp_sync_cmd.holddown); + } else { + vty_out(vty, " MPLS LDP-IGP Sync is enabled\n"); + if (ospf->ldp_sync_cmd.holddown == 0) + vty_out(vty, + " MPLS LDP-IGP Sync holddown timer is disabled\n"); + else + vty_out(vty, + " MPLS LDP-IGP Sync holddown timer %d sec\n", + ospf->ldp_sync_cmd.holddown); + } + } +} + +static void show_ip_ospf_mpls_ldp_interface_sub(struct vty *vty, + struct ospf_interface *oi, + struct interface *ifp, + json_object *json_interface_sub, + bool use_json) +{ + const char *ldp_state; + struct ospf_if_params *params; + char timebuf[OSPF_TIME_DUMP_SIZE]; + struct ldp_sync_info *ldp_sync_info; + + params = IF_DEF_PARAMS(oi->ifp); + if (params->ldp_sync_info == NULL) + return; + + ldp_sync_info = params->ldp_sync_info; + if (use_json) { + if (ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) + json_object_boolean_true_add(json_interface_sub, + "ldpIgpSyncEnabled"); + else + json_object_boolean_false_add(json_interface_sub, + "ldpIgpSyncEnabled"); + + json_object_int_add(json_interface_sub, "holdDownTimeInSec", + ldp_sync_info->holddown); + + } else { + vty_out(vty, "%-10s\n", ifp->name); + vty_out(vty, " LDP-IGP Synchronization enabled: %s\n", + ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED + ? "yes" + : "no"); + vty_out(vty, " Holddown timer in seconds: %u\n", + ldp_sync_info->holddown); + } + + switch (ldp_sync_info->state) { + case LDP_IGP_SYNC_STATE_REQUIRED_UP: + if (use_json) + json_object_string_add(json_interface_sub, + "ldpIgpSyncState", + "Sync achieved"); + else + vty_out(vty, " State: Sync achieved\n"); + break; + case LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP: + if (ldp_sync_info->t_holddown != NULL) { + if (use_json) { + long time_store; + + time_store = monotime_until( + &ldp_sync_info->t_holddown->u.sands, + NULL) + /1000LL; + + json_object_int_add(json_interface_sub, + "ldpIgpSyncTimeRemainInMsec", + time_store); + + json_object_string_add(json_interface_sub, + "ldpIgpSyncState", + "Holding down until Sync"); + } else { + vty_out(vty, + " Holddown timer is running %s remaining\n", + ospf_timer_dump( + ldp_sync_info->t_holddown, + timebuf, + sizeof(timebuf))); + + vty_out(vty, + " State: Holding down until Sync\n"); + } + } else { + if (use_json) + json_object_string_add(json_interface_sub, + "ldpIgpSyncState", + "Sync not achieved"); + else + vty_out(vty, " State: Sync not achieved\n"); + } + break; + case LDP_IGP_SYNC_STATE_NOT_REQUIRED: + default: + if (IF_DEF_PARAMS(ifp)->type != OSPF_IFTYPE_POINTOPOINT && + !if_is_pointopoint(ifp)) + ldp_state = "Sync not required: non-p2p link"; + else + ldp_state = "Sync not required"; + + if (use_json) + json_object_string_add(json_interface_sub, + "ldpIgpSyncState", + ldp_state); + else + vty_out(vty, " State: %s\n", ldp_state); + break; + } +} + +static int show_ip_ospf_mpls_ldp_interface_common(struct vty *vty, + struct ospf *ospf, + char *intf_name, + json_object *json, + bool use_json) +{ + struct interface *ifp; + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + json_object *json_interface_sub = NULL; + + if (intf_name == NULL) { + /* Show All Interfaces.*/ + FOR_ALL_INTERFACES (vrf, ifp) { + struct route_node *rn; + struct ospf_interface *oi; + + if (ospf_oi_count(ifp) == 0 && !use_json) { + if (!if_is_up(ifp)) + vty_out(vty, "%s\n Interface down\n", + ifp->name); + continue; + } + for (rn = route_top(IF_OIFS(ifp)); rn; + rn = route_next(rn)) { + oi = rn->info; + + if (oi == NULL) + continue; + + if (use_json) { + json_interface_sub = + json_object_new_object(); + } + show_ip_ospf_mpls_ldp_interface_sub( + vty, oi, ifp, json_interface_sub, + use_json); + + if (use_json) { + json_object_object_add( + json, ifp->name, + json_interface_sub); + } + } + } + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(intf_name, ospf->vrf_id); + if (ifp != NULL) { + struct route_node *rn; + struct ospf_interface *oi; + + if (ospf_oi_count(ifp) == 0 && !use_json) { + if (if_is_up(ifp)) + vty_out(vty, "%s\n OSPF not enabled\n", + ifp->name); + else + vty_out(vty, "%s\n Interface down\n", + ifp->name); + return CMD_SUCCESS; + } + for (rn = route_top(IF_OIFS(ifp)); rn; + rn = route_next(rn)) { + oi = rn->info; + + if (oi == NULL) + continue; + + if (use_json) + json_interface_sub = + json_object_new_object(); + + show_ip_ospf_mpls_ldp_interface_sub( + vty, oi, ifp, json_interface_sub, + use_json); + + if (use_json) { + json_object_object_add( + json, ifp->name, + json_interface_sub); + } + } + } + } + return CMD_SUCCESS; +} + +/* + * Write the global LDP-SYNC configuration. + */ +void ospf_ldp_sync_write_config(struct vty *vty, struct ospf *ospf) +{ + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) + vty_out(vty, " mpls ldp-sync\n"); + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN)) + vty_out(vty, " mpls ldp-sync holddown %u\n", + ospf->ldp_sync_cmd.holddown); +} + +/* + * Write the interface LDP-SYNC configuration. + */ +void ospf_ldp_sync_if_write_config(struct vty *vty, + struct ospf_if_params *params) + +{ + struct ldp_sync_info *ldp_sync_info; + + ldp_sync_info = params->ldp_sync_info; + if (ldp_sync_info == NULL) + return; + + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG)) { + if (ldp_sync_info->enabled == LDP_IGP_SYNC_ENABLED) + vty_out(vty, " ip ospf mpls ldp-sync\n"); + else + vty_out(vty, " no ip ospf mpls ldp-sync\n"); + } + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) + vty_out(vty, " ip ospf mpls ldp-sync holddown %u\n", + ldp_sync_info->holddown); +} + +/* + * LDP-SYNC commands. + */ +#include "ospfd/ospf_ldp_sync_clippy.c" + +DEFPY (ospf_mpls_ldp_sync, + ospf_mpls_ldp_sync_cmd, + "mpls ldp-sync", + "MPLS specific commands\n" + "Enable MPLS LDP-IGP Sync\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct interface *ifp; + + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + /* register with opaque client to recv LDP-IGP Sync msgs */ + zclient_register_opaque(zclient, LDP_IGP_SYNC_IF_STATE_UPDATE); + zclient_register_opaque(zclient, LDP_IGP_SYNC_ANNOUNCE_UPDATE); + + if (!CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + SET_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE); + /* turn on LDP-IGP Sync on all ptop OSPF interfaces */ + FOR_ALL_INTERFACES (vrf, ifp) + ospf_if_set_ldp_sync_enable(ospf, ifp); + } + return CMD_SUCCESS; +} + +DEFPY (no_ospf_mpls_ldp_sync, + no_ospf_mpls_ldp_sync_cmd, + "no mpls ldp-sync", + NO_STR + "MPLS specific commands\n" + "Disable MPLS LDP-IGP Sync\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + ospf_ldp_sync_gbl_exit(ospf, true); + return CMD_SUCCESS; +} + +DEFPY (ospf_mpls_ldp_sync_holddown, + ospf_mpls_ldp_sync_holddown_cmd, + "mpls ldp-sync holddown (1-10000)", + "MPLS specific commands\n" + "Enable MPLS LDP-IGP Sync\n" + "Set holddown timer\n" + "seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct interface *ifp; + + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + SET_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + ospf->ldp_sync_cmd.holddown = holddown; + /* set holddown time on all OSPF interfaces */ + FOR_ALL_INTERFACES (vrf, ifp) + ospf_if_set_ldp_sync_holddown(ospf, ifp); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf_mpls_ldp_sync_holddown, + no_ospf_mpls_ldp_sync_holddown_cmd, + "no mpls ldp-sync holddown [<(1-10000)>]", + NO_STR + "MPLS specific commands\n" + "Disable MPLS LDP-IGP Sync\n" + "holddown timer disable\n" + "Time in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct interface *ifp; + + if (CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN)) { + UNSET_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_HOLDDOWN); + ospf->ldp_sync_cmd.holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; + /* turn off holddown timer on all OSPF interfaces */ + FOR_ALL_INTERFACES (vrf, ifp) + ospf_if_set_ldp_sync_holddown(ospf, ifp); + } + return CMD_SUCCESS; +} + + +DEFPY (mpls_ldp_sync, + mpls_ldp_sync_cmd, + "ip ospf mpls ldp-sync", + IP_STR + "OSPF interface commands\n" + MPLS_STR + MPLS_LDP_SYNC_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) { + vty_out(vty, "ldp-sync does not run on loopback interface\n"); + return CMD_ERR_NOTHING_TODO; + } + + if (ifp->vrf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + + ldp_sync_info = params->ldp_sync_info; + + SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG); + ldp_sync_info->enabled = LDP_IGP_SYNC_ENABLED; + if (params->type == OSPF_IFTYPE_POINTOPOINT || if_is_pointopoint(ifp)) { + ldp_sync_info->state = LDP_IGP_SYNC_STATE_REQUIRED_NOT_UP; + ospf_ldp_sync_state_req_msg(ifp); + } else { + zlog_debug("ldp_sync: only runs on P2P links %s", ifp->name); + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + } + return CMD_SUCCESS; +} + +DEFPY (no_mpls_ldp_sync, + no_mpls_ldp_sync_cmd, + "no ip ospf mpls ldp-sync", + NO_STR + IP_STR + "OSPF interface commands\n" + MPLS_STR + NO_MPLS_LDP_SYNC_STR) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) { + vty_out(vty, "ldp-sync: does not run on loopback interface\n"); + return CMD_ERR_NOTHING_TODO; + } + + if (ifp->vrf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + + ldp_sync_info = params->ldp_sync_info; + + /* disable LDP-SYNC on an interface + * stop holddown timer if running + * restore ospf cost + */ + UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_IF_CONFIG); + ldp_sync_info->enabled = LDP_IGP_SYNC_DEFAULT; + ldp_sync_info->state = LDP_IGP_SYNC_STATE_NOT_REQUIRED; + EVENT_OFF(ldp_sync_info->t_holddown); + ospf_if_recalculate_output_cost(ifp); + + return CMD_SUCCESS; +} + +DEFPY (mpls_ldp_sync_holddown, + mpls_ldp_sync_holddown_cmd, + "ip ospf mpls ldp-sync holddown (0-10000)", + IP_STR + "OSPF interface commands\n" + MPLS_STR + MPLS_LDP_SYNC_STR + "Time to wait for LDP-SYNC to occur before restoring interface cost\n" + "Time in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + + if (if_is_loopback(ifp)) { + vty_out(vty, "ldp-sync: does not run on loopback interface\n"); + return CMD_ERR_NOTHING_TODO; + } + + if (ifp->vrf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + params = IF_DEF_PARAMS(ifp); + if (params->ldp_sync_info == NULL) + params->ldp_sync_info = ldp_sync_info_create(); + + ldp_sync_info = params->ldp_sync_info; + + SET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN); + ldp_sync_info->holddown = holddown; + + return CMD_SUCCESS; +} + +DEFPY (no_mpls_ldp_sync_holddown, + no_mpls_ldp_sync_holddown_cmd, + "no ip ospf mpls ldp-sync holddown [<(1-10000)>]", + NO_STR + IP_STR + "OSPF interface commands\n" + MPLS_STR + NO_MPLS_LDP_SYNC_STR + NO_MPLS_LDP_SYNC_HOLDDOWN_STR + "Time in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct ldp_sync_info *ldp_sync_info; + struct ospf *ospf; + + if (if_is_loopback(ifp)) { + vty_out(vty, "ldp-sync: does not run on loopback interface\n"); + return CMD_ERR_NOTHING_TODO; + } + + if (ifp->vrf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "ldp-sync only runs on DEFAULT VRF\n"); + return CMD_ERR_NOTHING_TODO; + } + + params = IF_DEF_PARAMS(ifp); + ldp_sync_info = params->ldp_sync_info; + if (ldp_sync_info == NULL) + return CMD_SUCCESS; + + /* use global configured value if set */ + if (CHECK_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN)) { + UNSET_FLAG(ldp_sync_info->flags, LDP_SYNC_FLAG_HOLDDOWN); + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf && CHECK_FLAG(ospf->ldp_sync_cmd.flags, + LDP_SYNC_FLAG_HOLDDOWN)) + ldp_sync_info->holddown = ospf->ldp_sync_cmd.holddown; + else + ldp_sync_info->holddown = LDP_IGP_SYNC_HOLDDOWN_DEFAULT; + } + return CMD_SUCCESS; +} + +DEFPY (show_ip_ospf_mpls_ldp_interface, + show_ip_ospf_mpls_ldp_interface_cmd, + "show ip ospf mpls ldp-sync [interface ] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + MPLS_STR + "LDP-IGP Sync information\n" + "Interface information\n" + "Interface name\n" + "All interfaces\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + char *intf_name = NULL; + int ret = CMD_SUCCESS; + int idx_intf = 0; + json_object *json = NULL; + + if (argv_find(argv, argc, "INTERFACE", &idx_intf)) + intf_name = argv[idx_intf]->arg; + + if (uj) + json = json_object_new_object(); + + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, "%% OSPF instance not found\n"); + return CMD_SUCCESS; + } + + if (!CHECK_FLAG(ospf->ldp_sync_cmd.flags, LDP_SYNC_FLAG_ENABLE)) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, "LDP-sync is disabled\n"); + return CMD_SUCCESS; + } + + ret = show_ip_ospf_mpls_ldp_interface_common(vty, ospf, intf_name, + json, uj); + if (uj) + vty_json(vty, json); + + return ret; +} + +void ospf_ldp_sync_init(void) +{ + /* Install global ldp-igp sync commands */ + install_element(OSPF_NODE, &ospf_mpls_ldp_sync_cmd); + install_element(OSPF_NODE, &no_ospf_mpls_ldp_sync_cmd); + install_element(OSPF_NODE, &ospf_mpls_ldp_sync_holddown_cmd); + install_element(OSPF_NODE, &no_ospf_mpls_ldp_sync_holddown_cmd); + + /* Interface lsp-igp sync commands */ + install_element(INTERFACE_NODE, &mpls_ldp_sync_cmd); + install_element(INTERFACE_NODE, &no_mpls_ldp_sync_cmd); + install_element(INTERFACE_NODE, &mpls_ldp_sync_holddown_cmd); + install_element(INTERFACE_NODE, &no_mpls_ldp_sync_holddown_cmd); + + /* "show ip ospf mpls ldp interface" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_mpls_ldp_interface_cmd); + + hook_register(ospf_ism_change, ospf_ldp_sync_ism_change); + +} diff --git a/ospfd/ospf_ldp_sync.h b/ospfd/ospf_ldp_sync.h new file mode 100644 index 0000000..88fdcba --- /dev/null +++ b/ospfd/ospf_ldp_sync.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ospf_ldp_sync.h: OSPF LDP-IGP Sync handling routines + * Copyright (C) 2020 Volta Networks, Inc. + */ + +#ifndef _ZEBRA_OSPF_LDP_SYNC_H +#define _ZEBRA_OSPF_LDP_SYNC_H + +#define LDP_OSPF_LSINFINITY 65535 + +/* Macro to log debug message */ +#define ols_debug(...) \ + do { \ + if (IS_DEBUG_OSPF_LDP_SYNC) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + + +extern void ospf_if_set_ldp_sync_enable(struct ospf *ospf, + struct interface *ifp); +extern void ospf_if_set_ldp_sync_holddown(struct ospf *ospf, + struct interface *ifp); +extern void ospf_ldp_sync_if_init(struct ospf_interface *ospf); +extern void ospf_ldp_sync_if_start(struct interface *ifp, bool send_state_req); +extern void ospf_ldp_sync_if_remove(struct interface *ifp, bool remove); +extern void ospf_ldp_sync_if_down(struct interface *ifp); +extern void ospf_ldp_sync_if_complete(struct interface *ifp); +extern void ospf_ldp_sync_holddown_timer_add(struct interface *ifp); +extern void ospf_ldp_sync_ldp_fail(struct interface *ifp); +extern void ospf_ldp_sync_show_info(struct vty *vty, struct ospf *ospf, + json_object *json_vrf, bool use_json); +extern void ospf_ldp_sync_write_config(struct vty *vty, struct ospf *ospf); +extern void ospf_ldp_sync_if_write_config(struct vty *vty, + struct ospf_if_params *params); +extern int ospf_ldp_sync_state_update(struct ldp_igp_sync_if_state state); +extern int ospf_ldp_sync_announce_update(struct ldp_igp_sync_announce announce); +extern void +ospf_ldp_sync_handle_client_close(struct zapi_client_close_info *info); +extern void ospf_ldp_sync_state_req_msg(struct interface *ifp); +extern void ospf_ldp_sync_init(void); +extern void ospf_ldp_sync_gbl_exit(struct ospf *ospf, bool remove); +#endif /* _ZEBRA_OSPF_LDP_SYNC_H */ diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c new file mode 100644 index 0000000..f125fa9 --- /dev/null +++ b/ospfd/ospf_lsa.c @@ -0,0 +1,4272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Link State Advertisement + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "monotime.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "checksum.h" +#include "network.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_errors.h" + +static struct ospf_lsa *ospf_handle_summarylsa_lsId_chg(struct ospf_area *area, + struct prefix_ipv4 *p, + uint8_t type, + uint32_t metric, + struct in_addr old_id); +static struct ospf_lsa * +ospf_summary_lsa_prepare_and_flood(struct prefix_ipv4 *p, uint32_t metric, + struct ospf_area *area, struct in_addr id); +static struct ospf_lsa *ospf_summary_lsa_refresh(struct ospf *ospf, + struct ospf_lsa *lsa); +static struct ospf_lsa * +ospf_asbr_summary_lsa_prepare_and_flood(struct prefix_ipv4 *p, uint32_t metric, + struct ospf_area *area, + struct in_addr id); +static struct ospf_lsa *ospf_summary_asbr_lsa_refresh(struct ospf *ospf, + struct ospf_lsa *lsa); +static struct ospf_lsa *ospf_handle_exnl_lsa_lsId_chg(struct ospf *ospf, + struct external_info *ei, + struct in_addr id); +static struct ospf_lsa * +ospf_exnl_lsa_prepare_and_flood(struct ospf *ospf, struct external_info *ei, + struct in_addr id); + +uint32_t get_metric(uint8_t *metric) +{ + uint32_t m; + m = metric[0]; + m = (m << 8) + metric[1]; + m = (m << 8) + metric[2]; + return m; +} + +/** @brief The Function checks self generated DoNotAge. + * @param lsa pointer. + * @return true or false. + */ +bool ospf_check_dna_lsa(const struct ospf_lsa *lsa) +{ + return ((IS_LSA_SELF(lsa) && CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE)) + ? true + : false); +} + +struct timeval int2tv(int a) +{ + struct timeval ret; + + ret.tv_sec = a; + ret.tv_usec = 0; + + return ret; +} + +struct timeval msec2tv(int a) +{ + struct timeval ret; + + ret.tv_sec = a / 1000; + ret.tv_usec = (a % 1000) * 1000; + + return ret; +} + +int ospf_lsa_refresh_delay(struct ospf_lsa *lsa) +{ + struct timeval delta; + int delay = 0; + + if (monotime_since(&lsa->tv_orig, &delta) + < OSPF_MIN_LS_INTERVAL * 1000LL) { + struct timeval minv = msec2tv(OSPF_MIN_LS_INTERVAL); + timersub(&minv, &delta, &minv); + + /* TBD: remove padding to full sec, return timeval instead */ + delay = minv.tv_sec + !!minv.tv_usec; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d:%pI4]: Refresh timer delay %d seconds", + lsa->data->type, &lsa->data->id, + delay); + + assert(delay > 0); + } + + return delay; +} + + +int get_age(struct ospf_lsa *lsa) +{ + struct timeval rel; + + /* As per rfc4136, the self-originated LSAs in their + * own database keep aging, however rfc doesn't tell + * till how long the LSA should be aged, as of now + * we are capping it for OSPF_LSA_MAXAGE. + */ + + /* If LSA is marked as donotage */ + if (CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE) && !IS_LSA_SELF(lsa)) + return ntohs(lsa->data->ls_age); + + monotime_since(&lsa->tv_recv, &rel); + return ntohs(lsa->data->ls_age) + rel.tv_sec; +} + + +/* Fletcher Checksum -- Refer to RFC1008. */ + +/* All the offsets are zero-based. The offsets in the RFC1008 are + one-based. */ +uint16_t ospf_lsa_checksum(struct lsa_header *lsa) +{ + uint8_t *buffer = &lsa->options; + int options_offset = buffer - (uint8_t *)&lsa->ls_age; /* should be 2 */ + + /* Skip the AGE field */ + uint16_t len = ntohs(lsa->length) - options_offset; + + /* Checksum offset starts from "options" field, not the beginning of the + lsa_header struct. The offset is 14, rather than 16. */ + int checksum_offset = (uint8_t *)&lsa->checksum - buffer; + + return fletcher_checksum(buffer, len, checksum_offset); +} + +int ospf_lsa_checksum_valid(struct lsa_header *lsa) +{ + uint8_t *buffer = &lsa->options; + int options_offset = buffer - (uint8_t *)&lsa->ls_age; /* should be 2 */ + + /* Skip the AGE field */ + uint16_t len = ntohs(lsa->length) - options_offset; + + return (fletcher_checksum(buffer, len, FLETCHER_CHECKSUM_VALIDATE) + == 0); +} + + +/* Create OSPF LSA. */ +struct ospf_lsa *ospf_lsa_new(void) +{ + struct ospf_lsa *new; + + new = XCALLOC(MTYPE_OSPF_LSA, sizeof(struct ospf_lsa)); + + new->flags = 0; + new->lock = 1; + new->retransmit_counter = 0; + monotime(&new->tv_recv); + new->tv_orig = new->tv_recv; + new->refresh_list = -1; + new->vrf_id = VRF_DEFAULT; + new->to_be_acknowledged = 0; + new->opaque_zero_len_delete = 0; + + return new; +} + +struct ospf_lsa *ospf_lsa_new_and_data(size_t size) +{ + struct ospf_lsa *new; + + new = ospf_lsa_new(); + new->data = ospf_lsa_data_new(size); + new->size = size; + + return new; +} + +/* Duplicate OSPF LSA. */ +struct ospf_lsa *ospf_lsa_dup(struct ospf_lsa *lsa) +{ + struct ospf_lsa *new; + + if (lsa == NULL) + return NULL; + + new = XCALLOC(MTYPE_OSPF_LSA, sizeof(struct ospf_lsa)); + + memcpy(new, lsa, sizeof(struct ospf_lsa)); + UNSET_FLAG(new->flags, OSPF_LSA_DISCARD); + new->lock = 1; + new->retransmit_counter = 0; + new->data = ospf_lsa_data_dup(lsa->data); + + /* kevinm: Clear the refresh_list, otherwise there are going + to be problems when we try to remove the LSA from the + queue (which it's not a member of.) + XXX: Should we add the LSA to the refresh_list queue? */ + new->refresh_list = -1; + + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("LSA: duplicated %p (new: %p)", (void *)lsa, + (void *)new); + + return new; +} + +/* Free OSPF LSA. */ +void ospf_lsa_free(struct ospf_lsa *lsa) +{ + assert(lsa->lock == 0); + + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("LSA: freed %p", (void *)lsa); + + /* Delete LSA data. */ + if (lsa->data != NULL) + ospf_lsa_data_free(lsa->data); + + assert(lsa->refresh_list < 0); + + memset(lsa, 0, sizeof(struct ospf_lsa)); + XFREE(MTYPE_OSPF_LSA, lsa); +} + +/* Lock LSA. */ +struct ospf_lsa *ospf_lsa_lock(struct ospf_lsa *lsa) +{ + lsa->lock++; + return lsa; +} + +/* Unlock LSA. */ +void ospf_lsa_unlock(struct ospf_lsa **lsa) +{ + /* This is sanity check. */ + if (!lsa || !*lsa) + return; + + (*lsa)->lock--; + + assert((*lsa)->lock >= 0); + + if ((*lsa)->lock == 0) { + assert(CHECK_FLAG((*lsa)->flags, OSPF_LSA_DISCARD)); + ospf_lsa_free(*lsa); + *lsa = NULL; + } +} + +/* Check discard flag. */ +void ospf_lsa_discard(struct ospf_lsa *lsa) +{ + if (!CHECK_FLAG(lsa->flags, OSPF_LSA_DISCARD)) { + SET_FLAG(lsa->flags, OSPF_LSA_DISCARD); + ospf_lsa_unlock(&lsa); + } +} + +/* Create LSA data. */ +struct lsa_header *ospf_lsa_data_new(size_t size) +{ + return XCALLOC(MTYPE_OSPF_LSA_DATA, size); +} + +/* Duplicate LSA data. */ +struct lsa_header *ospf_lsa_data_dup(struct lsa_header *lsah) +{ + struct lsa_header *new; + + new = ospf_lsa_data_new(ntohs(lsah->length)); + memcpy(new, lsah, ntohs(lsah->length)); + + return new; +} + +/* Free LSA data. */ +void ospf_lsa_data_free(struct lsa_header *lsah) +{ + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("LSA[Type%d:%pI4]: data freed %p", lsah->type, + &lsah->id, (void *)lsah); + + XFREE(MTYPE_OSPF_LSA_DATA, lsah); +} + + +/* LSA general functions. */ + +const char *dump_lsa_key(struct ospf_lsa *lsa) +{ + static char buf[sizeof("Type255,id(255.255.255.255),ar(255.255.255.255)")+1]; + struct lsa_header *lsah; + + if (lsa != NULL && (lsah = lsa->data) != NULL) { + char id[INET_ADDRSTRLEN], ar[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &lsah->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsah->adv_router, ar, sizeof(ar)); + + snprintf(buf, sizeof(buf), "Type%d,id(%s),ar(%s)", lsah->type, + id, ar); + } else + strlcpy(buf, "NULL", sizeof(buf)); + + return buf; +} + +uint32_t lsa_seqnum_increment(struct ospf_lsa *lsa) +{ + uint32_t seqnum; + + seqnum = ntohl(lsa->data->ls_seqnum) + 1; + + return htonl(seqnum); +} + +void lsa_header_set(struct stream *s, uint8_t options, uint8_t type, + struct in_addr id, struct in_addr router_id) +{ + struct lsa_header *lsah; + + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsah->ls_age = htons(OSPF_LSA_INITIAL_AGE); + lsah->options = options; + lsah->type = type; + lsah->id = id; + lsah->adv_router = router_id; + lsah->ls_seqnum = htonl(OSPF_INITIAL_SEQUENCE_NUMBER); + + stream_forward_endp(s, OSPF_LSA_HEADER_SIZE); +} + + +/* router-LSA related functions. */ +/* Get router-LSA flags. */ +uint8_t router_lsa_flags(struct ospf_area *area) +{ + uint8_t flags; + + flags = area->ospf->flags; + + /* Set virtual link flag. */ + if (ospf_full_virtual_nbrs(area)) + SET_FLAG(flags, ROUTER_LSA_VIRTUAL); + else + /* Just sanity check */ + UNSET_FLAG(flags, ROUTER_LSA_VIRTUAL); + + /* Set Shortcut ABR behabiour flag. */ + UNSET_FLAG(flags, ROUTER_LSA_SHORTCUT); + if (area->ospf->abr_type == OSPF_ABR_SHORTCUT) + if (!OSPF_IS_AREA_BACKBONE(area)) + if ((area->shortcut_configured == OSPF_SHORTCUT_DEFAULT + && area->ospf->backbone == NULL) + || area->shortcut_configured + == OSPF_SHORTCUT_ENABLE) + SET_FLAG(flags, ROUTER_LSA_SHORTCUT); + + /* ASBR can't exit in stub area. */ + if (area->external_routing == OSPF_AREA_STUB) + UNSET_FLAG(flags, ROUTER_LSA_EXTERNAL); + /* If ASBR set External flag */ + else if (IS_OSPF_ASBR(area->ospf)) + SET_FLAG(flags, ROUTER_LSA_EXTERNAL); + + /* Set ABR dependent flags */ + if (IS_OSPF_ABR(area->ospf)) { + SET_FLAG(flags, ROUTER_LSA_BORDER); + /* If Area is NSSA and we are both ABR and unconditional + * translator, + * set Nt bit to inform other routers. + */ + if ((area->external_routing == OSPF_AREA_NSSA) + && (area->NSSATranslatorRole == OSPF_NSSA_ROLE_ALWAYS)) + SET_FLAG(flags, ROUTER_LSA_NT); + } + return flags; +} + +/* Lookup neighbor other than myself. + And check neighbor count, + Point-to-Point link must have only 1 neighbor. */ +struct ospf_neighbor *ospf_nbr_lookup_ptop(struct ospf_interface *oi) +{ + struct ospf_neighbor *nbr = NULL; + struct route_node *rn; + + /* Search neighbor, there must be one of two nbrs. */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info)) + if (!IPV4_ADDR_SAME(&nbr->router_id, + &oi->ospf->router_id)) + if (nbr->state == NSM_Full) { + route_unlock_node(rn); + break; + } + + /* PtoP link must have only 1 neighbor. */ + if (ospf_nbr_count(oi, 0) > 1) + flog_warn( + EC_OSPF_PTP_NEIGHBOR, + "Point-to-Point link on interface %s has more than 1 neighbor.", + oi->ifp->name); + + return nbr; +} + +/* Determine cost of link, taking RFC3137 stub-router support into + * consideration + */ +static uint16_t ospf_link_cost(struct ospf_interface *oi) +{ + /* RFC3137 stub router support */ + if (!CHECK_FLAG(oi->area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) + return oi->output_cost; + else + return OSPF_OUTPUT_COST_INFINITE; +} + +/* Set a link information. */ +char link_info_set(struct stream **s, struct in_addr id, struct in_addr data, + uint8_t type, uint8_t tos, uint16_t cost) +{ + /* LSA stream is initially allocated to OSPF_MAX_LSA_SIZE, suits + * vast majority of cases. Some rare routers with lots of links need + * more. + * we try accommodate those here. + */ + if (STREAM_WRITEABLE(*s) < OSPF_ROUTER_LSA_LINK_SIZE) { + size_t ret = OSPF_MAX_LSA_SIZE; + + /* Can we enlarge the stream still? */ + if (STREAM_SIZE(*s) == OSPF_MAX_LSA_SIZE) { + /* we futz the size here for simplicity, really we need + * to account + * for just: + * IP Header - (sizeof(struct ip)) + * OSPF Header - OSPF_HEADER_SIZE + * LSA Header - OSPF_LSA_HEADER_SIZE + * MD5 auth data, if MD5 is configured - + * OSPF_AUTH_MD5_SIZE. + * + * Simpler just to subtract OSPF_MAX_LSA_SIZE though. + */ + ret = stream_resize_inplace( + s, OSPF_MAX_PACKET_SIZE - OSPF_MAX_LSA_SIZE); + } + + if (ret == OSPF_MAX_LSA_SIZE) { + flog_warn( + EC_OSPF_LSA_SIZE, + "%s: Out of space in LSA stream, left %zd, size %zd", + __func__, STREAM_WRITEABLE(*s), + STREAM_SIZE(*s)); + return 0; + } + } + + /* TOS based routing is not supported. */ + stream_put_ipv4(*s, id.s_addr); /* Link ID. */ + stream_put_ipv4(*s, data.s_addr); /* Link Data. */ + stream_putc(*s, type); /* Link Type. */ + stream_putc(*s, tos); /* TOS = 0. */ + stream_putw(*s, cost); /* Link Cost. */ + + return 1; +} + +/* Describe Point-to-Point link (Section 12.4.1.1). */ + +/* Note: If the interface is configured as point-to-point dmvpn then the other + * end of link is dmvpn hub with point-to-multipoint ospf network type. The + * hub then expects this router to populate the stub network and also Link Data + * Field set to IP Address and not MIB-II ifIndex + */ +static int lsa_link_ptop_set(struct stream **s, struct ospf_interface *oi) +{ + int links = 0; + struct ospf_neighbor *nbr; + struct in_addr id, mask, data; + uint16_t cost = ospf_link_cost(oi); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Set link Point-to-Point"); + + if ((nbr = ospf_nbr_lookup_ptop(oi))) + if (nbr->state == NSM_Full) { + if (CHECK_FLAG(oi->connected->flags, + ZEBRA_IFA_UNNUMBERED) + && !oi->ptp_dmvpn) { + /* For unnumbered point-to-point networks, the + Link Data field + should specify the interface's MIB-II ifIndex + value. */ + data.s_addr = htonl(oi->ifp->ifindex); + links += link_info_set( + s, nbr->router_id, data, + LSA_LINK_TYPE_POINTOPOINT, 0, cost); + } else { + links += link_info_set( + s, nbr->router_id, + oi->address->u.prefix4, + LSA_LINK_TYPE_POINTOPOINT, 0, cost); + } + } + + /* no need for a stub link for unnumbered interfaces */ + if (OSPF_IF_PARAM(oi, prefix_suppression)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s stub link omitted due prefix-suppression", + oi->ifp->name); + } else { + if (oi->ptp_dmvpn || + !CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) { + /* Regardless of the state of the neighboring router, we must + add a Type 3 link (stub network). + N.B. Options 1 & 2 share basically the same logic. */ + masklen2ip(oi->address->prefixlen, &mask); + id.s_addr = + CONNECTED_PREFIX(oi->connected)->u.prefix4.s_addr & + mask.s_addr; + links += link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, + 0, oi->output_cost); + } + } + + return links; +} + +/* Describe Broadcast Link. */ +static int lsa_link_broadcast_set(struct stream **s, struct ospf_interface *oi) +{ + struct ospf_neighbor *dr; + struct in_addr id, mask; + uint16_t cost = ospf_link_cost(oi); + + /* Describe Type 3 Link. */ + if (oi->state == ISM_Waiting) { + if (OSPF_IF_PARAM(oi, prefix_suppression)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s stub link omitted due prefix-suppression", + oi->ifp->name); + return 0; + } + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s is in state Waiting. Adding stub interface", + oi->ifp->name); + masklen2ip(oi->address->prefixlen, &mask); + id.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; + return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, + oi->output_cost); + } + + dr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi)); + /* Describe Type 2 link. */ + if (dr && (dr->state == NSM_Full + || IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi))) + && ospf_nbr_count(oi, NSM_Full) > 0) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type1]: Interface %s has a DR. Adding transit interface", + oi->ifp->name); + return link_info_set(s, DR(oi), oi->address->u.prefix4, + LSA_LINK_TYPE_TRANSIT, 0, cost); + } + /* Describe type 3 link. */ + else { + if (OSPF_IF_PARAM(oi, prefix_suppression)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s stub link omitted due prefix-suppression", + oi->ifp->name); + return 0; + } + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s has no DR. Adding stub interface", + oi->ifp->name); + masklen2ip(oi->address->prefixlen, &mask); + id.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; + return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, + oi->output_cost); + } +} + +static int lsa_link_loopback_set(struct stream **s, struct ospf_interface *oi) +{ + struct in_addr id, mask; + + /* Describe Type 3 Link. */ + if ((oi->state != ISM_Loopback) || OSPF_IF_PARAM(oi, prefix_suppression)) + return 0; + + mask.s_addr = 0xffffffff; + id.s_addr = oi->address->u.prefix4.s_addr; + return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, + oi->output_cost); +} + +/* Describe Virtual Link. */ +static int lsa_link_virtuallink_set(struct stream **s, + struct ospf_interface *oi) +{ + struct ospf_neighbor *nbr; + uint16_t cost = ospf_link_cost(oi); + + if (oi->state == ISM_PointToPoint) + if ((nbr = ospf_nbr_lookup_ptop(oi))) + if (nbr->state == NSM_Full) { + return link_info_set(s, nbr->router_id, + oi->address->u.prefix4, + LSA_LINK_TYPE_VIRTUALLINK, + 0, cost); + } + + return 0; +} + +#define lsa_link_nbma_set(S,O) lsa_link_broadcast_set (S, O) + +/* this function add for support point-to-multipoint ,see rfc2328 +12.4.1.4.*/ +/* from "edward rrr" + http://marc.theaimsgroup.com/?l=zebra&m=100739222210507&w=2 */ +static int lsa_link_ptomp_set(struct stream **s, struct ospf_interface *oi) +{ + int links = 0; + struct route_node *rn; + struct ospf_neighbor *nbr = NULL; + struct in_addr id, mask; + uint16_t cost = ospf_link_cost(oi); + + if (OSPF_IF_PARAM(oi, prefix_suppression)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Interface %s stub link omitted due prefix-suppression", + oi->ifp->name); + } else { + mask.s_addr = 0xffffffff; + id.s_addr = oi->address->u.prefix4.s_addr; + links += link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, 0); + } + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("PointToMultipoint: running ptomultip_set"); + + /* Search neighbor, */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL) + /* Ignore myself. */ + if (!IPV4_ADDR_SAME(&nbr->router_id, + &oi->ospf->router_id)) + if (nbr->state == NSM_Full) + + { + links += link_info_set( + s, nbr->router_id, + oi->address->u.prefix4, + LSA_LINK_TYPE_POINTOPOINT, 0, + cost); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "PointToMultipoint: set link to %pI4", + &oi->address->u.prefix4); + } + + return links; +} + +/* Set router-LSA link information. */ +static int router_lsa_link_set(struct stream **s, struct ospf_area *area) +{ + struct listnode *node; + struct ospf_interface *oi; + int links = 0; + + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) { + struct interface *ifp = oi->ifp; + + /* Check interface is up, OSPF is enable. */ + if (if_is_operative(ifp)) { + if (oi->state != ISM_Down) { + oi->lsa_pos_beg = links; + /* Describe each link. */ + switch (oi->type) { + case OSPF_IFTYPE_POINTOPOINT: + links += lsa_link_ptop_set(s, oi); + break; + case OSPF_IFTYPE_BROADCAST: + links += lsa_link_broadcast_set(s, oi); + break; + case OSPF_IFTYPE_NBMA: + links += lsa_link_nbma_set(s, oi); + break; + case OSPF_IFTYPE_POINTOMULTIPOINT: + links += lsa_link_ptomp_set(s, oi); + break; + case OSPF_IFTYPE_VIRTUALLINK: + links += + lsa_link_virtuallink_set(s, oi); + break; + case OSPF_IFTYPE_LOOPBACK: + links += lsa_link_loopback_set(s, oi); + } + oi->lsa_pos_end = links; + } + } + } + + return links; +} + +/* Set router-LSA body. */ +void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area) +{ + unsigned long putp; + uint16_t cnt; + + /* Set flags. */ + stream_putc(*s, router_lsa_flags(area)); + + /* Set Zero fields. */ + stream_putc(*s, 0); + + /* Keep pointer to # links. */ + putp = stream_get_endp(*s); + + /* Forward word */ + stream_putw(*s, 0); + + /* Set all link information. */ + cnt = router_lsa_link_set(s, area); + + /* Set # of links here. */ + stream_putw_at(*s, putp, cnt); +} + +static void ospf_stub_router_timer(struct event *t) +{ + struct ospf_area *area = EVENT_ARG(t); + + area->t_stub_router = NULL; + + SET_FLAG(area->stub_router_state, OSPF_AREA_WAS_START_STUB_ROUTED); + + /* clear stub route state and generate router-lsa refresh, don't + * clobber an administratively set stub-router state though. + */ + if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) + return; + + UNSET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); + + ospf_router_lsa_update_area(area); +} + +static void ospf_stub_router_check(struct ospf_area *area) +{ + /* area must either be administratively configured to be stub + * or startup-time stub-router must be configured and we must in a + * pre-stub + * state. + */ + if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED)) { + SET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); + return; + } + + /* not admin-stubbed, check whether startup stubbing is configured and + * whether it's not been done yet + */ + if (CHECK_FLAG(area->stub_router_state, + OSPF_AREA_WAS_START_STUB_ROUTED)) + return; + + if (area->ospf->stub_router_startup_time + == OSPF_STUB_ROUTER_UNCONFIGURED) { + /* stub-router is hence done forever for this area, even if + * someone + * tries configure it (take effect next restart). + */ + SET_FLAG(area->stub_router_state, + OSPF_AREA_WAS_START_STUB_ROUTED); + return; + } + + /* startup stub-router configured and not yet done */ + SET_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED); + + OSPF_AREA_TIMER_ON(area->t_stub_router, ospf_stub_router_timer, + area->ospf->stub_router_startup_time); +} + +/* Create new router-LSA. */ +static struct ospf_lsa *ospf_router_lsa_new(struct ospf_area *area) +{ + struct ospf *ospf = area->ospf; + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + int length; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type1]: Create router-LSA instance"); + + /* check whether stub-router is desired, and if this is the first + * router LSA. + */ + ospf_stub_router_check(area); + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + /* Set LSA common header fields. */ + lsa_header_set(s, LSA_OPTIONS_GET(area) | LSA_OPTIONS_NSSA_GET(area), + OSPF_ROUTER_LSA, ospf->router_id, ospf->router_id); + + /* Set router-LSA body fields. */ + ospf_router_lsa_body_set(&s, area); + + /* Set length. */ + length = stream_get_endp(s); + lsah = (struct lsa_header *)STREAM_DATA(s); + lsah->length = htons(length); + + /* Now, create OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + new->vrf_id = area->ospf->vrf_id; + + /* Copy LSA data to store, discard stream. */ + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Originate Router-LSA. */ +static struct ospf_lsa *ospf_router_lsa_originate(struct ospf_area *area) +{ + struct ospf_lsa *new; + + if (area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_ROUTER_LSA); + return NULL; + } + + /* Create new router-LSA instance. */ + if ((new = ospf_router_lsa_new(area)) == NULL) { + zlog_err("%s: ospf_router_lsa_new returned NULL", __func__); + return NULL; + } + + /* Sanity check. */ + if (new->data->adv_router.s_addr == INADDR_ANY) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA[Type1]: AdvRouter is 0, discard"); + ospf_lsa_discard(new); + return NULL; + } + + /* Install LSA to LSDB. */ + new = ospf_lsa_install(area->ospf, NULL, new); + + /* Update LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flooding new LSA through area. */ + ospf_flood_through_area(area, NULL, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate router-LSA %p", + new->data->type, &new->data->id, + (void *)new); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* Refresh router-LSA. */ +static struct ospf_lsa *ospf_router_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf_area *area = lsa->area; + struct ospf_lsa *new; + + /* Sanity check. */ + assert(lsa->data); + + /* Delete LSA from neighbor retransmit-list. */ + ospf_ls_retransmit_delete_nbr_area(area, lsa); + + /* Unregister LSA from refresh-list */ + ospf_refresher_unregister_lsa(area->ospf, lsa); + + /* Create new router-LSA instance. */ + if ((new = ospf_router_lsa_new(area)) == NULL) { + zlog_err("%s: ospf_router_lsa_new returned NULL", __func__); + return NULL; + } + + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + ospf_lsa_install(area->ospf, NULL, new); + + /* Flood LSA through area. */ + ospf_flood_through_area(area, NULL, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: router-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return NULL; +} + +int ospf_router_lsa_update_area(struct ospf_area *area) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("[router-LSA]: (router-LSA area update)"); + + /* Now refresh router-LSA. */ + if (area->router_lsa_self) + ospf_lsa_refresh(area->ospf, area->router_lsa_self); + /* Newly originate router-LSA. */ + else + ospf_router_lsa_originate(area); + + return 0; +} + +int ospf_router_lsa_update(struct ospf *ospf) +{ + struct listnode *node, *nnode; + struct ospf_area *area; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("Timer[router-LSA Update]: (timer expire)"); + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + struct ospf_lsa *lsa = area->router_lsa_self; + struct router_lsa *rl; + const char *area_str; + + /* Keep Area ID string. */ + area_str = AREA_NAME(area); + + /* If LSA not exist in this Area, originate new. */ + if (lsa == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type1]: Create router-LSA for Area %s", + area_str); + + ospf_router_lsa_originate(area); + } + /* If router-ID is changed, Link ID must change. + First flush old LSA, then originate new. */ + else if (!IPV4_ADDR_SAME(&lsa->data->id, &ospf->router_id)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d:%pI4]: Refresh router-LSA for Area %s", + lsa->data->type, + &lsa->data->id, area_str); + ospf_refresher_unregister_lsa(ospf, lsa); + ospf_lsa_flush_area(lsa, area); + ospf_lsa_unlock(&area->router_lsa_self); + area->router_lsa_self = NULL; + + /* Refresh router-LSA, (not install) and flood through + * area. */ + ospf_router_lsa_update_area(area); + } else { + rl = (struct router_lsa *)lsa->data; + /* Refresh router-LSA, (not install) and flood through + * area. */ + if (rl->flags != ospf->flags) + ospf_router_lsa_update_area(area); + } + } + + return 0; +} + + +/* network-LSA related functions. */ +/* Originate Network-LSA. */ +static void ospf_network_lsa_body_set(struct stream *s, + struct ospf_interface *oi) +{ + struct in_addr mask; + struct route_node *rn; + struct ospf_neighbor *nbr; + + if (OSPF_IF_PARAM(oi, prefix_suppression)) { + mask.s_addr = 0xffffffff; + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type2]: Interface %s network mask set to host mask due prefix-suppression", + oi->ifp->name); + } else { + masklen2ip(oi->address->prefixlen, &mask); + } + stream_put_ipv4(s, mask.s_addr); + + /* The network-LSA lists those routers that are fully adjacent to + the Designated Router; each fully adjacent router is identified by + its OSPF Router ID. The Designated Router includes itself in this + list. RFC2328, Section 12.4.2 */ + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL) + if (nbr->state == NSM_Full || nbr == oi->nbr_self) + stream_put_ipv4(s, nbr->router_id.s_addr); +} + +static struct ospf_lsa *ospf_network_lsa_new(struct ospf_interface *oi) +{ + struct stream *s; + struct ospf_lsa *new; + struct lsa_header *lsah; + struct ospf_if_params *oip; + int length; + + /* If there are no neighbours on this network (the net is stub), + the router does not originate network-LSA (see RFC 12.4.2) */ + if (oi->full_nbrs == 0) + return NULL; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type2]: Create network-LSA instance"); + + /* Create new stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsa_header_set(s, (OPTIONS(oi) | LSA_OPTIONS_GET(oi->area)), + OSPF_NETWORK_LSA, DR(oi), oi->ospf->router_id); + + /* Set network-LSA body fields. */ + ospf_network_lsa_body_set(s, oi); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Create OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + new->area = oi->area; + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + new->vrf_id = oi->ospf->vrf_id; + + /* Copy LSA to store. */ + memcpy(new->data, lsah, length); + stream_free(s); + + /* Remember prior network LSA sequence numbers, even if we stop + * originating one for this oi, to try avoid re-originating LSAs with a + * prior sequence number, and thus speed up adjency forming & + * convergence. + */ + if ((oip = ospf_lookup_if_params(oi->ifp, oi->address->u.prefix4))) { + new->data->ls_seqnum = oip->network_lsa_seqnum; + new->data->ls_seqnum = lsa_seqnum_increment(new); + } else { + oip = ospf_get_if_params(oi->ifp, oi->address->u.prefix4); + ospf_if_update_params(oi->ifp, oi->address->u.prefix4); + } + oip->network_lsa_seqnum = new->data->ls_seqnum; + + return new; +} + +/* Originate network-LSA. */ +void ospf_network_lsa_update(struct ospf_interface *oi) +{ + struct ospf_lsa *new; + + if (oi->area->ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Graceful Restart in progress, don't originate", + OSPF_NETWORK_LSA); + return; + } + + if (oi->network_lsa_self != NULL) { + ospf_lsa_refresh(oi->ospf, oi->network_lsa_self); + return; + } + + /* Create new network-LSA instance. */ + new = ospf_network_lsa_new(oi); + if (new == NULL) + return; + + /* Install LSA to LSDB. */ + new = ospf_lsa_install(oi->ospf, oi, new); + + /* Update LSA origination count. */ + oi->ospf->lsa_originate_count++; + + /* Flooding new LSA through area. */ + ospf_flood_through_area(oi->area, NULL, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate network-LSA %p", + new->data->type, &new->data->id, + (void *)new); + ospf_lsa_header_dump(new->data); + } + + return; +} + +static struct ospf_lsa *ospf_network_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf_area *area = lsa->area; + struct ospf_lsa *new, *new2; + struct ospf_if_params *oip; + struct ospf_interface *oi; + + assert(lsa->data); + + /* Retrieve the oi for the network LSA */ + oi = ospf_if_lookup_by_local_addr(area->ospf, NULL, lsa->data->id); + if (oi == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "LSA[Type%d:%pI4]: network-LSA refresh: no oi found, ick, ignoring.", + lsa->data->type, &lsa->data->id); + ospf_lsa_header_dump(lsa->data); + } + return NULL; + } + + if (oi->state != ISM_DR) + return NULL; + + /* Delete LSA from neighbor retransmit-list. */ + ospf_ls_retransmit_delete_nbr_area(area, lsa); + + /* Unregister LSA from refresh-list */ + ospf_refresher_unregister_lsa(area->ospf, lsa); + + /* Create new network-LSA instance. */ + new = ospf_network_lsa_new(oi); + if (new == NULL) + return NULL; + + oip = ospf_lookup_if_params(oi->ifp, oi->address->u.prefix4); + assert(oip != NULL); + oip->network_lsa_seqnum = new->data->ls_seqnum = + lsa_seqnum_increment(lsa); + + new2 = ospf_lsa_install(area->ospf, oi, new); + + assert(new2 == new); + + /* Flood LSA through aera. */ + ospf_flood_through_area(area, NULL, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: network-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +static void stream_put_ospf_metric(struct stream *s, uint32_t metric_value) +{ + uint32_t metric; + char *mp; + + /* Put 0 metric. TOS metric is not supported. */ + metric = htonl(metric_value); + mp = (char *)&metric; + mp++; + stream_put(s, mp, 3); +} + +/* summary-LSA related functions. */ +static void ospf_summary_lsa_body_set(struct stream *s, struct prefix *p, + uint32_t metric) +{ + struct in_addr mask; + + masklen2ip(p->prefixlen, &mask); + + /* Put Network Mask. */ + stream_put_ipv4(s, mask.s_addr); + + /* Set # TOS. */ + stream_putc(s, (uint8_t)0); + + /* Set metric. */ + stream_put_ospf_metric(s, metric); +} + +static struct ospf_lsa *ospf_summary_lsa_new(struct ospf_area *area, + struct prefix *p, uint32_t metric, + struct in_addr id) +{ + struct stream *s; + struct ospf_lsa *new; + struct lsa_header *lsah; + int length; + + if (id.s_addr == 0xffffffff) { + /* Maybe Link State ID not available. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Link ID not available, can't originate", + OSPF_SUMMARY_LSA); + return NULL; + } + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type3]: Create summary-LSA instance"); + + /* Create new stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsa_header_set(s, LSA_OPTIONS_GET(area), OSPF_SUMMARY_LSA, id, + area->ospf->router_id); + + /* Set summary-LSA body fields. */ + ospf_summary_lsa_body_set(s, p, metric); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Create OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + new->vrf_id = area->ospf->vrf_id; + + /* Copy LSA to store. */ + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Originate Summary-LSA. */ +static struct ospf_lsa * +ospf_summary_lsa_prepare_and_flood(struct prefix_ipv4 *p, uint32_t metric, + struct ospf_area *area, struct in_addr id) +{ + struct ospf_lsa *new; + + /* Create new summary-LSA instance. */ + if (!(new = ospf_summary_lsa_new(area, (struct prefix *)p, metric, id))) + return NULL; + + /* Instlal LSA to LSDB. */ + new = ospf_lsa_install(area->ospf, NULL, new); + + /* Update LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flooding new LSA through area. */ + ospf_flood_through_area(area, NULL, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate summary-LSA %p", + new->data->type, &new->data->id, + (void *)new); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +static struct ospf_lsa *ospf_handle_summarylsa_lsId_chg(struct ospf_area *area, + struct prefix_ipv4 *p, + uint8_t type, + uint32_t metric, + struct in_addr old_id) +{ + struct ospf_lsa *lsa = NULL; + struct ospf_lsa *summary_lsa = NULL; + struct summary_lsa *sl = NULL; + struct ospf_area *old_area = NULL; + struct ospf *ospf = area->ospf; + struct prefix_ipv4 old_prefix; + uint32_t old_metric; + struct in_addr mask; + uint32_t metric_val; + char *metric_buf; + + lsa = ospf_lsdb_lookup_by_id(area->lsdb, type, p->prefix, + ospf->router_id); + + if (!lsa) { + flog_warn(EC_OSPF_LSA_NULL, "(%s): LSA not found", __func__); + return NULL; + } + + sl = (struct summary_lsa *)lsa->data; + + old_area = lsa->area; + old_metric = GET_METRIC(sl->metric); + old_prefix.prefix = sl->header.id; + old_prefix.prefixlen = ip_masklen(sl->mask); + old_prefix.family = AF_INET; + + + /* change the mask */ + masklen2ip(p->prefixlen, &mask); + sl->mask.s_addr = mask.s_addr; + + /* Copy the metric*/ + metric_val = htonl(metric); + metric_buf = (char *)&metric_val; + memcpy(sl->metric, metric_buf, sizeof(metric_val)); + + if (type == OSPF_SUMMARY_LSA) { + /*Refresh the LSA with new LSA*/ + summary_lsa = ospf_summary_lsa_refresh(ospf, lsa); + + ospf_summary_lsa_prepare_and_flood(&old_prefix, old_metric, + old_area, old_id); + } else { + /*Refresh the LSA with new LSA*/ + summary_lsa = ospf_summary_asbr_lsa_refresh(ospf, lsa); + + ospf_asbr_summary_lsa_prepare_and_flood(&old_prefix, old_metric, + old_area, old_id); + } + + return summary_lsa; +} + +/* Originate Summary-LSA. */ +struct ospf_lsa *ospf_summary_lsa_originate(struct prefix_ipv4 *p, + uint32_t metric, + struct ospf_area *area) +{ + struct in_addr id; + enum lsid_status status; + struct ospf_lsa *new = NULL; + + status = ospf_lsa_unique_id(area->ospf, area->lsdb, OSPF_SUMMARY_LSA, p, + &id); + + if (status == LSID_CHANGE) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("Link ID has to be changed."); + + new = ospf_handle_summarylsa_lsId_chg(area, p, OSPF_SUMMARY_LSA, + metric, id); + return new; + } else if (status == LSID_NOT_AVAILABLE) { + /* Link State ID not available. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: Link ID not available, can't originate"); + + return NULL; + } + + new = ospf_summary_lsa_prepare_and_flood(p, metric, area, id); + return new; +} + +static struct ospf_lsa *ospf_summary_lsa_refresh(struct ospf *ospf, + struct ospf_lsa *lsa) +{ + struct ospf_lsa *new; + struct summary_lsa *sl; + struct prefix p; + + /* Sanity check. */ + assert(lsa->data); + + sl = (struct summary_lsa *)lsa->data; + p.prefixlen = ip_masklen(sl->mask); + new = ospf_summary_lsa_new(lsa->area, &p, GET_METRIC(sl->metric), + sl->header.id); + + if (!new) + return NULL; + + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + ospf_lsa_install(ospf, NULL, new); + + /* Flood LSA through AS. */ + ospf_flood_through_area(new->area, NULL, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: summary-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + + +/* summary-ASBR-LSA related functions. */ +static void ospf_summary_asbr_lsa_body_set(struct stream *s, struct prefix *p, + uint32_t metric) +{ + /* Put Network Mask. */ + stream_put_ipv4(s, (uint32_t)0); + + /* Set # TOS. */ + stream_putc(s, (uint8_t)0); + + /* Set metric. */ + stream_put_ospf_metric(s, metric); +} + +static struct ospf_lsa *ospf_summary_asbr_lsa_new(struct ospf_area *area, + struct prefix *p, + uint32_t metric, + struct in_addr id) +{ + struct stream *s; + struct ospf_lsa *new; + struct lsa_header *lsah; + int length; + + if (id.s_addr == 0xffffffff) { + /* Maybe Link State ID not available. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d]: Link ID not available, can't originate", + OSPF_ASBR_SUMMARY_LSA); + return NULL; + } + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type3]: Create summary-LSA instance"); + + /* Create new stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + lsa_header_set(s, LSA_OPTIONS_GET(area), OSPF_ASBR_SUMMARY_LSA, id, + area->ospf->router_id); + + /* Set summary-LSA body fields. */ + ospf_summary_asbr_lsa_body_set(s, p, metric); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Create OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + new->area = area; + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + new->vrf_id = area->ospf->vrf_id; + + /* Copy LSA to store. */ + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +/* Originate summary-ASBR-LSA. */ +static struct ospf_lsa * +ospf_asbr_summary_lsa_prepare_and_flood(struct prefix_ipv4 *p, uint32_t metric, + struct ospf_area *area, + struct in_addr id) +{ + struct ospf_lsa *new; + + /* Create new summary-LSA instance. */ + new = ospf_summary_asbr_lsa_new(area, (struct prefix *)p, metric, id); + if (!new) + return NULL; + + /* Install LSA to LSDB. */ + new = ospf_lsa_install(area->ospf, NULL, new); + + /* Update LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flooding new LSA through area. */ + ospf_flood_through_area(area, NULL, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate summary-ASBR-LSA %p", + new->data->type, &new->data->id, + (void *)new); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +struct ospf_lsa *ospf_summary_asbr_lsa_originate(struct prefix_ipv4 *p, + uint32_t metric, + struct ospf_area *area) +{ + struct ospf_lsa *new; + struct in_addr id; + enum lsid_status status; + + status = ospf_lsa_unique_id(area->ospf, area->lsdb, + OSPF_ASBR_SUMMARY_LSA, p, &id); + + if (status == LSID_CHANGE) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("Link ID has to be changed."); + + new = ospf_handle_summarylsa_lsId_chg( + area, p, OSPF_ASBR_SUMMARY_LSA, metric, id); + return new; + } else if (status == LSID_NOT_AVAILABLE) { + /* Link State ID not available. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: Link ID not available, can't originate"); + + return NULL; + } + + new = ospf_asbr_summary_lsa_prepare_and_flood(p, metric, area, id); + return new; +} + +static struct ospf_lsa *ospf_summary_asbr_lsa_refresh(struct ospf *ospf, + struct ospf_lsa *lsa) +{ + struct ospf_lsa *new; + struct summary_lsa *sl; + struct prefix p; + bool ind_lsa = false; + + /* Sanity check. */ + assert(lsa->data); + + if (lsa->area->fr_info.indication_lsa_self && + (lsa->area->fr_info.indication_lsa_self == lsa)) + ind_lsa = true; + + sl = (struct summary_lsa *)lsa->data; + p.prefixlen = ip_masklen(sl->mask); + new = ospf_summary_asbr_lsa_new(lsa->area, &p, GET_METRIC(sl->metric), + sl->header.id); + if (!new) + return NULL; + + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + ospf_lsa_install(ospf, NULL, new); + + /* Flood LSA through area. */ + ospf_flood_through_area(new->area, NULL, new); + + if (ind_lsa) + new->area->fr_info.indication_lsa_self = new; + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: summary-ASBR-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* AS-external-LSA related functions. */ + +/* Get nexthop for AS-external-LSAs. Return nexthop if its interface + is connected, else 0*/ +static struct in_addr ospf_external_lsa_nexthop_get(struct ospf *ospf, + struct in_addr nexthop) +{ + struct in_addr fwd; + struct prefix nh; + struct listnode *node; + struct ospf_interface *oi; + + fwd.s_addr = 0; + + if (!nexthop.s_addr) + return fwd; + + /* Check whether nexthop is covered by OSPF network. */ + nh.family = AF_INET; + nh.u.prefix4 = nexthop; + nh.prefixlen = IPV4_MAX_BITLEN; + + /* XXX/SCALE: If there were a lot of oi's on an ifp, then it'd be + * better to make use of the per-ifp table of ois. + */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + if (if_is_operative(oi->ifp)) + if (oi->address->family == AF_INET) + if (prefix_match(oi->address, &nh)) + return nexthop; + + return fwd; +} + +/* NSSA-external-LSA related functions. */ + +/* Get 1st IP connection for Forward Addr */ + +struct in_addr ospf_get_ip_from_ifp(struct ospf_interface *oi) +{ + struct in_addr fwd; + + fwd.s_addr = INADDR_ANY; + + if (if_is_operative(oi->ifp)) + return oi->address->u.prefix4; + + return fwd; +} + +/* Get 1st IP connection for Forward Addr */ +struct in_addr ospf_get_nssa_ip(struct ospf_area *area) +{ + struct in_addr fwd; + struct in_addr best_default; + struct listnode *node; + struct ospf_interface *oi; + + fwd.s_addr = 0; + best_default.s_addr = 0; + + for (ALL_LIST_ELEMENTS_RO(area->ospf->oiflist, node, oi)) { + if (if_is_operative(oi->ifp)) + if (oi->area->external_routing == OSPF_AREA_NSSA) + if (oi->address + && oi->address->family == AF_INET) { + if (best_default.s_addr == INADDR_ANY) + best_default = + oi->address->u.prefix4; + if (oi->area == area) + return oi->address->u.prefix4; + } + } + if (best_default.s_addr != INADDR_ANY) + return best_default; + + return fwd; +} + +int metric_type(struct ospf *ospf, uint8_t src, unsigned short instance) +{ + struct ospf_redist *red; + + red = ospf_redist_lookup(ospf, src, instance); + + return ((!red || red->dmetric.type < 0) ? DEFAULT_METRIC_TYPE + : red->dmetric.type); +} + +int metric_value(struct ospf *ospf, uint8_t src, unsigned short instance) +{ + struct ospf_redist *red; + + red = ospf_redist_lookup(ospf, src, instance); + if (!red || red->dmetric.value < 0) { + if (src == DEFAULT_ROUTE) { + if (ospf->default_originate == DEFAULT_ORIGINATE_ZEBRA) + return DEFAULT_DEFAULT_ORIGINATE_METRIC; + else + return DEFAULT_DEFAULT_ALWAYS_METRIC; + } else if (ospf->default_metric < 0) + return DEFAULT_DEFAULT_METRIC; + else + return ospf->default_metric; + } + + return red->dmetric.value; +} + +/* Set AS-external-LSA body. */ +static void ospf_external_lsa_body_set(struct stream *s, + struct external_info *ei, + struct ospf *ospf) +{ + struct prefix_ipv4 *p = &ei->p; + struct in_addr mask, fwd_addr; + uint32_t mvalue; + int mtype; + int type; + unsigned short instance; + + /* Put Network Mask. */ + masklen2ip(p->prefixlen, &mask); + stream_put_ipv4(s, mask.s_addr); + + /* If prefix is default, specify DEFAULT_ROUTE. */ + type = is_default_prefix4(&ei->p) ? DEFAULT_ROUTE : ei->type; + instance = is_default_prefix4(&ei->p) ? 0 : ei->instance; + + mtype = (ROUTEMAP_METRIC_TYPE(ei) != -1) + ? ROUTEMAP_METRIC_TYPE(ei) + : metric_type(ospf, type, instance); + + mvalue = (ROUTEMAP_METRIC(ei) != -1) + ? ROUTEMAP_METRIC(ei) + : metric_value(ospf, type, instance); + + /* Put type of external metric. */ + stream_putc(s, (mtype == EXTERNAL_METRIC_TYPE_2 ? 0x80 : 0)); + + /* Put 0 metric. TOS metric is not supported. */ + stream_put_ospf_metric(s, mvalue); + + /* Get forwarding address to nexthop if on the Connection List, else 0. + */ + fwd_addr = ospf_external_lsa_nexthop_get(ospf, ei->nexthop); + + /* Put forwarding address. */ + stream_put_ipv4(s, fwd_addr.s_addr); + + /* Put route tag */ + stream_putl(s, ei->tag); +} + +/* Create new external-LSA. */ +static struct ospf_lsa * +ospf_exnl_lsa_prepare_and_flood(struct ospf *ospf, struct external_info *ei, + struct in_addr id) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + int length; + + /* Create new stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + /* Set LSA common header fields. */ + lsa_header_set(s, OSPF_OPTION_E, OSPF_AS_EXTERNAL_LSA, id, + ospf->router_id); + + /* Set AS-external-LSA body fields. */ + ospf_external_lsa_body_set(s, ei, ospf); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + new->area = NULL; + SET_FLAG(new->flags, + OSPF_LSA_SELF | OSPF_LSA_APPROVED | OSPF_LSA_SELF_CHECKED); + new->vrf_id = ospf->vrf_id; + + /* Copy LSA data to store, discard stream. */ + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +static struct ospf_lsa *ospf_handle_exnl_lsa_lsId_chg(struct ospf *ospf, + struct external_info *ei, + struct in_addr id) +{ + struct ospf_lsa *lsa; + struct as_external_lsa *al; + struct in_addr mask; + struct ospf_lsa *new; + struct external_info ei_summary = {}; + struct external_info *ei_old; + + lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, OSPF_AS_EXTERNAL_LSA, + ei->p.prefix, ospf->router_id); + + if (!lsa) { + flog_warn(EC_OSPF_LSA_NULL, "(%s): LSA not found", __func__); + return NULL; + } + + ei_old = ospf_external_info_check(ospf, lsa); + + al = (struct as_external_lsa *)lsa->data; + + if (!ei_old) { + /* eii_old pointer of LSA is NULL, this + * must be external aggregate route. + */ + ei_summary.p.family = AF_INET; + ei_summary.p.prefix = al->header.id; + ei_summary.p.prefixlen = ip_masklen(al->mask); + ei_summary.tag = (unsigned long)ntohl(al->e[0].route_tag); + ei_old = &ei_summary; + } + + /* change the mask */ + masklen2ip(ei->p.prefixlen, &mask); + al->mask.s_addr = mask.s_addr; + + /*Refresh the LSA with new LSA*/ + ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE, 0); + + /*Originate the old LSA with changed LSID*/ + new = ospf_exnl_lsa_prepare_and_flood(ospf, ei_old, id); + + return new; +} + +static struct ospf_lsa *ospf_external_lsa_new(struct ospf *ospf, + struct external_info *ei, + struct in_addr *old_id) +{ + struct ospf_lsa *new; + struct in_addr id; + enum lsid_status status; + + if (ei == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: External info is NULL, can't originate"); + return NULL; + } + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type5]: Originate AS-external-LSA instance"); + + /* If old Link State ID is specified, refresh LSA with same ID. */ + if (old_id) + id = *old_id; + /* Get Link State with unique ID. */ + else { + status = ospf_lsa_unique_id(ospf, ospf->lsdb, + OSPF_AS_EXTERNAL_LSA, &ei->p, &id); + + if (status == LSID_CHANGE) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("Link ID has to be changed."); + + new = ospf_handle_exnl_lsa_lsId_chg(ospf, ei, id); + return new; + } else if (status == LSID_NOT_AVAILABLE) { + /* Link State ID not available. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: Link ID not available, can't originate"); + return NULL; + } + } + + new = ospf_exnl_lsa_prepare_and_flood(ospf, ei, id); + + return new; +} + +/* As Type-7 */ +static void ospf_install_flood_nssa(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct ospf_lsa *new; + struct as_external_lsa *extlsa; + struct ospf_area *area; + struct listnode *node, *nnode; + + /* LSA may be a Type-5 originated via translation of a Type-7 LSA + * which originated from an NSSA area. In which case it should not be + * flooded back to NSSA areas. + */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) + return; + + /* NSSA Originate or Refresh (If anyNSSA) + + LSA is self-originated. And just installed as Type-5. + Additionally, install as Type-7 LSDB for every attached NSSA. + + P-Bit controls which ABR performs translation to outside world; If + we are an ABR....do not set the P-bit, because we send the Type-5, + not as the ABR Translator, but as the ASBR owner within the AS! + + If we are NOT ABR, Flood through NSSA as Type-7 w/P-bit set. The + elected ABR Translator will see the P-bit, Translate, and re-flood. + + Later, ABR_TASK and P-bit will scan Type-7 LSDB and translate to + Type-5's to non-NSSA Areas. (it will also attempt a re-install) */ + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + /* Don't install Type-7 LSA's into nonNSSA area */ + if (area->external_routing != OSPF_AREA_NSSA) + continue; + + /* make lsa duplicate, lock=1 */ + new = ospf_lsa_dup(lsa); + new->area = area; + new->data->type = OSPF_AS_NSSA_LSA; + + /* set P-bit if not ABR */ + if (!IS_OSPF_ABR(ospf)) { + SET_FLAG(new->data->options, OSPF_OPTION_NP); + + /* set non-zero FWD ADDR + + draft-ietf-ospf-nssa-update-09.txt + + if the network between the NSSA AS boundary router and + the + adjacent AS is advertised into OSPF as an internal OSPF + route, + the forwarding address should be the next op address as + is cu + currently done with type-5 LSAs. If the intervening + network is + not adversited into OSPF as an internal OSPF route and + the + type-7 LSA's P-bit is set a forwarding address should be + selected from one of the router's active OSPF interface + addresses + which belong to the NSSA. If no such addresses exist, + then + no type-7 LSA's with the P-bit set should originate from + this + router. */ + + /* kevinm: not updating lsa anymore, just new */ + extlsa = (struct as_external_lsa *)(new->data); + + if (extlsa->e[0].fwd_addr.s_addr == INADDR_ANY) + extlsa->e[0].fwd_addr = ospf_get_nssa_ip( + area); /* this NSSA area in ifp */ + + if (extlsa->e[0].fwd_addr.s_addr == INADDR_ANY) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "LSA[Type-7]: Could not build FWD-ADDR"); + ospf_lsa_discard(new); + return; + } + } + + /* install also as Type-7 */ + ospf_lsa_install(ospf, NULL, + new); /* Remove Old, Lock New = 2 */ + + /* will send each copy, lock=2+n */ + ospf_flood_through_as( + ospf, NULL, new); /* all attached NSSA's, no AS/STUBs */ + } +} + +static struct ospf_lsa *ospf_lsa_translated_nssa_new(struct ospf *ospf, + struct ospf_lsa *type7) +{ + + struct ospf_lsa *new; + struct as_external_lsa *ext, *extnew; + struct external_info ei; + + ext = (struct as_external_lsa *)(type7->data); + + /* need external_info struct, fill in bare minimum */ + ei.p.family = AF_INET; + ei.p.prefix = type7->data->id; + ei.p.prefixlen = ip_masklen(ext->mask); + ei.type = ZEBRA_ROUTE_OSPF; + ei.nexthop = ext->header.adv_router; + ei.route_map_set.metric = -1; + ei.route_map_set.metric_type = -1; + ei.metric = DEFAULT_DEFAULT_METRIC; + ei.max_metric = OSPF_LS_INFINITY; + ei.min_metric = 0; + ei.tag = 0; + ei.instance = 0; + + if ((new = ospf_external_lsa_new(ospf, &ei, &type7->data->id)) + == NULL) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: Could not originate Translated Type-5 for %pI4", + __func__, &ei.p.prefix); + return NULL; + } + + extnew = (struct as_external_lsa *)(new->data); + + /* copy over Type-7 data to new */ + extnew->e[0].tos = ext->e[0].tos; + extnew->e[0].route_tag = ext->e[0].route_tag; + if (type7->area->suppress_fa) { + extnew->e[0].fwd_addr.s_addr = 0; + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: Suppress forwarding address for %pI4", + __func__, &ei.p.prefix); + } else + extnew->e[0].fwd_addr.s_addr = ext->e[0].fwd_addr.s_addr; + new->data->ls_seqnum = type7->data->ls_seqnum; + + /* add translated flag, checksum and lock new lsa */ + SET_FLAG(new->flags, OSPF_LSA_LOCAL_XLT); /* Translated from 7 */ + + return new; +} + +/* Originate Translated Type-5 for supplied Type-7 NSSA LSA */ +struct ospf_lsa *ospf_translated_nssa_originate(struct ospf *ospf, + struct ospf_lsa *type7, + struct ospf_lsa *type5) +{ + struct ospf_lsa *new, *translated_lsa; + struct as_external_lsa *extnew; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Translated Type5]: Graceful Restart in progress, don't originate"); + return NULL; + } + + /* we cant use ospf_external_lsa_originate() as we need to set + * the OSPF_LSA_LOCAL_XLT flag, must originate by hand + */ + + if ((translated_lsa = ospf_lsa_translated_nssa_new(ospf, type7)) == + NULL) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: Could not translate Type-7, Id %pI4, to Type-5", + __func__, &type7->data->id); + return NULL; + } + + extnew = (struct as_external_lsa *)translated_lsa->data; + + /* Update LSA sequence number from translated Type-5 LSA */ + if (type5) + translated_lsa->data->ls_seqnum = lsa_seqnum_increment(type5); + + if ((new = ospf_lsa_install(ospf, NULL, translated_lsa)) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "%s: Could not install LSA id %pI4", __func__, + &type7->data->id); + ospf_lsa_free(translated_lsa); + return NULL; + } + + if (IS_DEBUG_OSPF_NSSA) { + zlog_debug("%s: translated Type 7, installed", __func__); + ospf_lsa_header_dump(new->data); + zlog_debug(" Network mask: %d", ip_masklen(extnew->mask)); + zlog_debug(" Forward addr: %pI4", + &extnew->e[0].fwd_addr); + } + + ospf->lsa_originate_count++; + ospf_flood_through_as(ospf, NULL, new); + + return new; +} + +/* Refresh Translated from NSSA AS-external-LSA. */ +struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *ospf, + struct ospf_lsa *type7, + struct ospf_lsa *type5) +{ + struct ospf_lsa *new = NULL, *translated_lsa = NULL; + struct as_external_lsa *extold = NULL; + uint32_t ls_seqnum = 0; + + /* Sanity checks. */ + assert(type7 || type5); + if (!(type7 || type5)) + return NULL; + if (type7) + assert(type7->data); + if (type5) + assert(type5->data); + assert(ospf->anyNSSA); + + /* get required data according to what has been given */ + if (type7 && type5 == NULL) { + /* find the translated Type-5 for this Type-7 */ + struct as_external_lsa *ext = + (struct as_external_lsa *)(type7->data); + struct prefix_ipv4 p = { + .prefix = type7->data->id, + .prefixlen = ip_masklen(ext->mask), + .family = AF_INET, + }; + + type5 = ospf_external_info_find_lsa(ospf, &p); + } else if (type5 && type7 == NULL) { + /* find the type-7 from which supplied type-5 was translated, + * ie find first type-7 with same LSA Id. + */ + struct listnode *ln, *lnn; + struct route_node *rn; + struct ospf_lsa *lsa; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS(ospf->areas, ln, lnn, area)) { + if (area->external_routing != OSPF_AREA_NSSA && !type7) + continue; + + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) { + if (lsa->data->id.s_addr + == type5->data->id.s_addr) { + type7 = lsa; + break; + } + } + } + } + + /* do we have type7? */ + if (!type7) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("%s: no Type-7 found for Type-5 LSA Id %pI4", + __func__, &type5->data->id); + return NULL; + } + + /* do we have valid translated type5? */ + if (type5 == NULL || !CHECK_FLAG(type5->flags, OSPF_LSA_LOCAL_XLT)) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: No translated Type-5 found for Type-7 with Id %pI4", + __func__, &type7->data->id); + return NULL; + } + + extold = (struct as_external_lsa *)type5->data; + if (type7->area->suppress_fa == 1) { + if (extold->e[0].fwd_addr.s_addr == 0) + ls_seqnum = ntohl(type5->data->ls_seqnum); + } + + /* Delete LSA from neighbor retransmit-list. */ + ospf_ls_retransmit_delete_nbr_as(ospf, type5); + + /* create new translated LSA */ + if ((translated_lsa = ospf_lsa_translated_nssa_new(ospf, type7)) == + NULL) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "%s: Could not translate Type-7 for %pI4 to Type-5", + __func__, &type7->data->id); + return NULL; + } + + if (type7->area->suppress_fa == 1) { + if (extold->e[0].fwd_addr.s_addr == 0) + translated_lsa->data->ls_seqnum = htonl(ls_seqnum + 1); + } + + if (!(new = ospf_lsa_install(ospf, NULL, translated_lsa))) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "%s: Could not install translated LSA, Id %pI4", + __func__, &type7->data->id); + ospf_lsa_free(translated_lsa); + return NULL; + } + + /* Flood LSA through area. */ + ospf_flood_through_as(ospf, NULL, new); + + return new; +} + +/* Originate an AS-external-LSA, install and flood. */ +struct ospf_lsa *ospf_external_lsa_originate(struct ospf *ospf, + struct external_info *ei) +{ + struct ospf_lsa *new; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type5]: Graceful Restart in progress, don't originate"); + return NULL; + } + + /* Added for NSSA project.... + + External LSAs are originated in ASBRs as usual, but for NSSA + systems. + there is the global Type-5 LSDB and a Type-7 LSDB installed for + every area. The Type-7's are flooded to every IR and every ABR; We + install the Type-5 LSDB so that the normal "refresh" code operates + as usual, and flag them as not used during ASE calculations. The + Type-7 LSDB is used for calculations. Each Type-7 has a Forwarding + Address of non-zero. + + If an ABR is the elected NSSA translator, following SPF and during + the ABR task it will translate all the scanned Type-7's, with P-bit + ON and not-self generated, and translate to Type-5's throughout the + non-NSSA/STUB AS. + + A difference in operation depends whether this ASBR is an ABR + or not. If not an ABR, the P-bit is ON, to indicate that any + elected NSSA-ABR can perform its translation. + + If an ABR, the P-bit is OFF; No ABR will perform translation and + this ASBR will flood the Type-5 LSA as usual. + + For the case where this ASBR is not an ABR, the ASE calculations + are based on the Type-5 LSDB; The Type-7 LSDB exists just to + demonstrate to the user that there are LSA's that belong to any + attached NSSA. + + Finally, it just so happens that when the ABR is translating every + Type-7 into Type-5, it installs it into the Type-5 LSDB as an + approved Type-5 (translated from Type-7); at the end of translation + if any Translated Type-5's remain unapproved, then they must be + flushed from the AS. + + */ + + if (ospf->router_id.s_addr == INADDR_ANY) { + if (ei && IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type5:%pI4]: deferring AS-external-LSA origination, router ID is zero", + &ei->p.prefix); + return NULL; + } + + /* Create new AS-external-LSA instance. */ + if ((new = ospf_external_lsa_new(ospf, ei, NULL)) == NULL) { + if (ei && IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type5:%pI4]: Could not originate AS-external-LSA", + &ei->p.prefix); + return NULL; + } + + /* Install newly created LSA into Type-5 LSDB, lock = 1. */ + ospf_lsa_install(ospf, NULL, new); + + /* Update LSA origination count. */ + ospf->lsa_originate_count++; + + /* Flooding new LSA. only to AS (non-NSSA/STUB) */ + ospf_flood_through_as(ospf, NULL, new); + + /* If there is any attached NSSA, do special handling */ + if (ospf->anyNSSA && + /* stay away from translated LSAs! */ + !(CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT))) + ospf_install_flood_nssa( + ospf, new); /* Install/Flood Type-7 to all NSSAs */ + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate AS-external-LSA %p", + new->data->type, &new->data->id, + (void *)new); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* Originate an NSSA-LSA, install and flood. */ +struct ospf_lsa *ospf_nssa_lsa_originate(struct ospf_area *area, + struct external_info *ei) +{ + struct ospf *ospf = area->ospf; + struct ospf_lsa *new; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type7]: Graceful Restart in progress, don't originate"); + return NULL; + } + + if (ospf->router_id.s_addr == INADDR_ANY) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type7:%pI4]: deferring NSSA-LSA origination, router ID is zero", + &ei->p.prefix); + return NULL; + } + + /* Create new NSSA-LSA instance. */ + if ((new = ospf_external_lsa_new(ospf, ei, NULL)) == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type7:%pI4]: Could not originate NSSA-LSA", + &ei->p.prefix); + return NULL; + } + new->data->type = OSPF_AS_NSSA_LSA; + new->area = area; + + /* Install newly created LSA into Type-7 LSDB. */ + ospf_lsa_install(ospf, NULL, new); + + /* Update LSA origination count. */ + ospf->lsa_originate_count++; + + /* Flooding new LSA */ + ospf_flood_through_area(area, NULL, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: Originate NSSA-LSA %p", + new->data->type, &new->data->id, (void *)new); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +/* Refresh NSSA-LSA. */ +struct ospf_lsa *ospf_nssa_lsa_refresh(struct ospf_area *area, + struct ospf_lsa *lsa, + struct external_info *ei) +{ + struct ospf *ospf = area->ospf; + struct ospf_lsa *new; + + /* Delete LSA from neighbor retransmit-list. */ + ospf_ls_retransmit_delete_nbr_as(ospf, lsa); + + /* Unregister AS-external-LSA from refresh-list. */ + ospf_refresher_unregister_lsa(ospf, lsa); + + /* Create new NSSA-LSA instance. */ + if ((new = ospf_external_lsa_new(ospf, ei, NULL)) == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type7:%pI4]: Could not originate NSSA-LSA", + &ei->p.prefix); + return NULL; + } + new->data->type = OSPF_AS_NSSA_LSA; + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + new->area = area; + + /* Install newly created LSA into Type-7 LSDB. */ + ospf_lsa_install(ospf, NULL, new); + + /* Flooding new LSA */ + ospf_flood_through_area(area, NULL, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: NSSA-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +static struct external_info *ospf_default_external_info(struct ospf *ospf) +{ + int type; + struct prefix_ipv4 p; + struct external_info *default_ei; + int ret = 0; + + p.family = AF_INET; + p.prefix.s_addr = 0; + p.prefixlen = 0; + + default_ei = ospf_external_info_lookup(ospf, DEFAULT_ROUTE, 0, &p); + if (!default_ei) + return NULL; + + /* First, lookup redistributed default route. */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *ext_list; + + if (type == ZEBRA_ROUTE_OSPF) + continue; + + ext_list = ospf->external[type]; + if (!ext_list) + continue; + + ret = ospf_external_default_routemap_apply_walk(ospf, ext_list, + default_ei); + if (ret) + return default_ei; + } + + return NULL; +} + +void ospf_external_lsa_rid_change(struct ospf *ospf) +{ + struct external_info *ei; + struct ospf_external_aggr_rt *aggr; + struct ospf_lsa *lsa = NULL; + int force; + int type; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + struct route_node *rn; + struct route_table *rt; + struct list *ext_list; + struct listnode *node; + struct ospf_external *ext; + + ext_list = ospf->external[type]; + if (!ext_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + /* Originate As-external-LSA from all type of + * distribute source. + */ + rt = ext->external_info; + if (!rt) + continue; + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + ei = rn->info; + + if (!ei) + continue; + + if (is_default_prefix4(&ei->p)) + continue; + + lsa = ospf_external_info_find_lsa(ospf, &ei->p); + + aggr = ospf_external_aggr_match(ospf, &ei->p); + if (aggr) { + + if (!ospf_redistribute_check(ospf, ei, + NULL)) + continue; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "Originate Summary LSA after reset/router-ID change"); + + /* Here the LSA is originated as new */ + ospf_originate_summary_lsa(ospf, aggr, + ei); + } else if (lsa) { + /* LSA needs to be refreshed even if + * there is no change in the route + * params if the LSA is in maxage. + */ + if (IS_LSA_MAXAGE(lsa)) + force = LSA_REFRESH_FORCE; + else + force = LSA_REFRESH_IF_CHANGED; + + ospf_external_lsa_refresh(ospf, lsa, + ei, force, 0); + } else { + if (!ospf_redistribute_check(ospf, ei, + NULL)) + continue; + + if (!ospf_external_lsa_originate(ospf, + ei)) + flog_warn( + EC_OSPF_LSA_INSTALL_FAILURE, + "LSA: AS-external-LSA was not originated."); + } + } + } + } + + ei = ospf_default_external_info(ospf); + if (ei && !ospf_external_lsa_originate(ospf, ei)) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "LSA: AS-external-LSA for default route was not originated."); + } +} + +/* Flush any NSSA LSAs for given prefix */ +void ospf_nssa_lsa_flush(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct listnode *node, *nnode; + struct ospf_lsa *lsa = NULL; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + if (area->external_routing == OSPF_AREA_NSSA) { + lsa = ospf_lsa_lookup(ospf, area, OSPF_AS_NSSA_LSA, + p->prefix, ospf->router_id); + if (!lsa) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA: There is no such AS-NSSA-LSA %pFX in LSDB", + p); + continue; + } + ospf_ls_retransmit_delete_nbr_area(area, lsa); + if (!IS_LSA_MAXAGE(lsa)) { + ospf_refresher_unregister_lsa(ospf, lsa); + ospf_lsa_flush_area(lsa, area); + } + } + } +} + +/* Flush an AS-external-LSA from LSDB and routing domain. */ +void ospf_external_lsa_flush(struct ospf *ospf, uint8_t type, + struct prefix_ipv4 *p, + ifindex_t ifindex /*, struct in_addr nexthop */) +{ + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("LSA: Flushing AS-external-LSA %pFX", p); + + /* First lookup LSA from LSDB. */ + if (!(lsa = ospf_external_info_find_lsa(ospf, p))) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA: There is no such AS-external-LSA %pFX in LSDB", + p); + return; + } + + /* If LSA is selforiginated, not a translated LSA, and there is + * NSSA area, flush Type-7 LSA's at first. + */ + if (IS_LSA_SELF(lsa) && (ospf->anyNSSA) + && !(CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT))) + ospf_nssa_lsa_flush(ospf, p); + + if (!IS_LSA_MAXAGE(lsa)) { + /* Sweep LSA from Link State Retransmit List. */ + ospf_ls_retransmit_delete_nbr_as(ospf, lsa); + + /* Unregister LSA from Refresh queue. */ + ospf_refresher_unregister_lsa(ospf, lsa); + + /* Flush AS-external-LSA through AS. */ + ospf_lsa_flush_as(ospf, lsa); + } + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("%s: stop", __func__); +} + +void ospf_external_lsa_refresh_default(struct ospf *ospf) +{ + struct prefix_ipv4 p; + struct external_info *ei; + struct ospf_lsa *lsa; + + p.family = AF_INET; + p.prefixlen = 0; + p.prefix.s_addr = INADDR_ANY; + + ei = ospf_default_external_info(ospf); + lsa = ospf_external_info_find_lsa(ospf, &p); + + if (ei && lsa) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA[Type5:0.0.0.0]: Refresh AS-external-LSA %p", + (void *)lsa); + ospf_external_lsa_refresh(ospf, lsa, ei, LSA_REFRESH_FORCE, + false); + } else if (ei && !lsa) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type5:0.0.0.0]: Originate AS-external-LSA"); + ospf_external_lsa_originate(ospf, ei); + } else if (lsa) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA[Type5:0.0.0.0]: Flush AS-external-LSA"); + ospf_external_lsa_flush(ospf, DEFAULT_ROUTE, &p, 0); + } +} + +void ospf_external_lsa_refresh_type(struct ospf *ospf, uint8_t type, + unsigned short instance, int force) +{ + struct route_node *rn; + struct external_info *ei; + struct ospf_external *ext; + + if (type == DEFAULT_ROUTE) + return; + + ext = ospf_external_lookup(ospf, type, instance); + + if (ext && EXTERNAL_INFO(ext)) { + /* Refresh each redistributed AS-external-LSAs. */ + for (rn = route_top(EXTERNAL_INFO(ext)); rn; + rn = route_next(rn)) { + ei = rn->info; + if (ei) { + if (!is_default_prefix4(&ei->p)) { + struct ospf_lsa *lsa; + struct ospf_external_aggr_rt *aggr; + + aggr = ospf_external_aggr_match(ospf, + &ei->p); + lsa = ospf_external_info_find_lsa( + ospf, &ei->p); + if (aggr) { + /* Check the AS-external-LSA + * should be originated. + */ + if (!ospf_redistribute_check( + ospf, ei, NULL)) { + + ospf_unlink_ei_from_aggr( + ospf, aggr, ei); + continue; + } + + if (IS_DEBUG_OSPF( + lsa, + EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Send Aggreate LSA (%pFX)", + __func__, + &aggr->p); + + ospf_originate_summary_lsa( + ospf, aggr, ei); + + } else if (lsa) { + + if (IS_LSA_MAXAGE(lsa)) + force = LSA_REFRESH_FORCE; + + ospf_external_lsa_refresh( + ospf, lsa, ei, force, + false); + } else { + if (!ospf_redistribute_check( + ospf, ei, NULL)) + continue; + ospf_external_lsa_originate( + ospf, ei); + } + } + } + } + } +} + +/* Refresh AS-external-LSA. */ +struct ospf_lsa *ospf_external_lsa_refresh(struct ospf *ospf, + struct ospf_lsa *lsa, + struct external_info *ei, int force, + bool is_aggr) +{ + struct ospf_lsa *new; + int changed = 0; + + /* Check the AS-external-LSA should be originated. */ + if (!is_aggr) + if (!ospf_redistribute_check(ospf, ei, &changed)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d:%pI4] Could not be refreshed, redist check fail", + lsa->data->type, + &lsa->data->id); + + ospf_external_lsa_flush(ospf, ei->type, &ei->p, + ei->ifindex /*, ei->nexthop */); + return NULL; + } + + if (!changed && !force) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d:%pI4]: Not refreshed, not changed/forced", + lsa->data->type, &lsa->data->id); + return NULL; + } + + /* Delete LSA from neighbor retransmit-list. */ + ospf_ls_retransmit_delete_nbr_as(ospf, lsa); + + /* Unregister AS-external-LSA from refresh-list. */ + ospf_refresher_unregister_lsa(ospf, lsa); + + new = ospf_external_lsa_new(ospf, ei, &lsa->data->id); + + if (new == NULL) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug("LSA[Type%d:%pI4]: Could not be refreshed", + lsa->data->type, &lsa->data->id); + return NULL; + } + + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + ospf_lsa_install(ospf, NULL, new); /* As type-5. */ + + /* Flood LSA through AS. */ + ospf_flood_through_as(ospf, NULL, new); + + /* If any attached NSSA, install as Type-7, flood to all NSSA Areas */ + if (ospf->anyNSSA && !(CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT))) + ospf_install_flood_nssa(ospf, + new); /* Install/Flood per new rules */ + + /* Register self-originated LSA to refresh queue. + * Translated LSAs should not be registered, but refreshed upon + * refresh of the Type-7 + */ + if (!CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) + ospf_refresher_register_lsa(ospf, new); + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: AS-external-LSA refresh", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + + +/* LSA installation functions. */ + +/* Install router-LSA to an area. */ +static struct ospf_lsa * +ospf_router_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) +{ + struct ospf_area *area = new->area; + + /* RFC 2328 Section 13.2 Router-LSAs and network-LSAs + The entire routing table must be recalculated, starting with + the shortest path calculations for each area (not just the + area whose link-state database has changed). + */ + + if (IS_LSA_SELF(new)) { + + /* Only install LSA if it is originated/refreshed by us. + * If LSA was received by flooding, the RECEIVED flag is set so + * do + * not link the LSA */ + if (CHECK_FLAG(new->flags, OSPF_LSA_RECEIVED)) + return new; /* ignore stale LSA */ + + /* Set self-originated router-LSA. */ + ospf_lsa_unlock(&area->router_lsa_self); + area->router_lsa_self = ospf_lsa_lock(new); + + ospf_refresher_register_lsa(ospf, new); + } + if (rt_recalc) + ospf_spf_calculate_schedule(ospf, SPF_FLAG_ROUTER_LSA_INSTALL); + return new; +} + +/* Install network-LSA to an area. */ +static struct ospf_lsa *ospf_network_lsa_install(struct ospf *ospf, + struct ospf_interface *oi, + struct ospf_lsa *new, + int rt_recalc) +{ + + /* RFC 2328 Section 13.2 Router-LSAs and network-LSAs + The entire routing table must be recalculated, starting with + the shortest path calculations for each area (not just the + area whose link-state database has changed). + */ + if (IS_LSA_SELF(new)) { + /* We supposed that when LSA is originated by us, we pass the + int + for which it was originated. If LSA was received by flooding, + the RECEIVED flag is set, so we do not link the LSA to the + int. */ + if (CHECK_FLAG(new->flags, OSPF_LSA_RECEIVED)) + return new; /* ignore stale LSA */ + + ospf_lsa_unlock(&oi->network_lsa_self); + oi->network_lsa_self = ospf_lsa_lock(new); + ospf_refresher_register_lsa(ospf, new); + } + if (rt_recalc) + ospf_spf_calculate_schedule(ospf, SPF_FLAG_NETWORK_LSA_INSTALL); + + return new; +} + +/* Install summary-LSA to an area. */ +static struct ospf_lsa * +ospf_summary_lsa_install(struct ospf *ospf, struct ospf_lsa *new, int rt_recalc) +{ + if (rt_recalc && !IS_LSA_SELF(new)) { +/* RFC 2328 Section 13.2 Summary-LSAs + The best route to the destination described by the summary- + LSA must be recalculated (see Section 16.5). If this + destination is an AS boundary router, it may also be + necessary to re-examine all the AS-external-LSAs. +*/ + + ospf_spf_calculate_schedule(ospf, SPF_FLAG_SUMMARY_LSA_INSTALL); + } + + if (IS_LSA_SELF(new)) + ospf_refresher_register_lsa(ospf, new); + + return new; +} + +/* Install ASBR-summary-LSA to an area. */ +static struct ospf_lsa *ospf_summary_asbr_lsa_install(struct ospf *ospf, + struct ospf_lsa *new, + int rt_recalc) +{ + if (rt_recalc && !IS_LSA_SELF(new)) { +/* RFC 2328 Section 13.2 Summary-LSAs + The best route to the destination described by the summary- + LSA must be recalculated (see Section 16.5). If this + destination is an AS boundary router, it may also be + necessary to re-examine all the AS-external-LSAs. +*/ + ospf_spf_calculate_schedule(ospf, + SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL); + } + + /* register LSA to refresh-list. */ + if (IS_LSA_SELF(new)) + ospf_refresher_register_lsa(ospf, new); + + return new; +} + +/* Install AS-external-LSA. */ +static struct ospf_lsa *ospf_external_lsa_install(struct ospf *ospf, + struct ospf_lsa *new, + int rt_recalc) +{ + ospf_ase_register_external_lsa(new, ospf); + /* If LSA is not self-originated, calculate an external route. */ + if (rt_recalc) { + /* RFC 2328 Section 13.2 AS-external-LSAs + The best route to the destination described by the AS- + external-LSA must be recalculated (see Section 16.6). + */ + + if (!IS_LSA_SELF(new)) + ospf_ase_incremental_update(ospf, new); + } + + if (new->data->type == OSPF_AS_NSSA_LSA) { + /* There is no point to register selforiginate Type-7 LSA for + * refreshing. We rely on refreshing Type-5 LSA's + */ + if (IS_LSA_SELF(new)) + return new; + else { + /* Try refresh type-5 translated LSA for this LSA, if + * one exists. + * New translations will be taken care of by the + * abr_task. + */ + ospf_translated_nssa_refresh(ospf, new, NULL); + ospf_schedule_abr_task(ospf); + } + } + + /* Register self-originated LSA to refresh queue. + * Leave Translated LSAs alone if NSSA is enabled + */ + if (IS_LSA_SELF(new) && !CHECK_FLAG(new->flags, OSPF_LSA_LOCAL_XLT)) + ospf_refresher_register_lsa(ospf, new); + + return new; +} + +void ospf_discard_from_db(struct ospf *ospf, struct ospf_lsdb *lsdb, + struct ospf_lsa *lsa) +{ + struct ospf_lsa *old; + + if (!lsdb) + return; + + old = ospf_lsdb_lookup(lsdb, lsa); + + if (!old) + return; + + if (old->refresh_list >= 0) + ospf_refresher_unregister_lsa(ospf, old); + + switch (old->data->type) { + case OSPF_AS_EXTERNAL_LSA: + ospf_ase_unregister_external_lsa(old, ospf); + ospf_ls_retransmit_delete_nbr_as(ospf, old); + break; + case OSPF_OPAQUE_AS_LSA: + ospf_ls_retransmit_delete_nbr_as(ospf, old); + break; + case OSPF_AS_NSSA_LSA: + ospf_ls_retransmit_delete_nbr_area(old->area, old); + ospf_ase_unregister_external_lsa(old, ospf); + break; + default: + ospf_ls_retransmit_delete_nbr_area(old->area, old); + break; + } + + ospf_lsa_maxage_delete(ospf, old); + ospf_lsa_discard(old); +} + +struct ospf_lsa *ospf_lsa_install(struct ospf *ospf, struct ospf_interface *oi, + struct ospf_lsa *lsa) +{ + struct ospf_lsa *new = NULL; + struct ospf_lsa *old = NULL; + struct ospf_lsdb *lsdb = NULL; + int rt_recalc; + + /* Set LSDB. */ + switch (lsa->data->type) { + /* kevinm */ + case OSPF_AS_NSSA_LSA: + if (lsa->area) + lsdb = lsa->area->lsdb; + else + lsdb = ospf->lsdb; + break; + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + lsdb = ospf->lsdb; + break; + default: + if (lsa->area) + lsdb = lsa->area->lsdb; + break; + } + + assert(lsdb); + + /* RFC 2328 13.2. Installing LSAs in the database + + Installing a new LSA in the database, either as the result of + flooding or a newly self-originated LSA, may cause the OSPF + routing table structure to be recalculated. The contents of the + new LSA should be compared to the old instance, if present. If + there is no difference, there is no need to recalculate the + routing table. When comparing an LSA to its previous instance, + the following are all considered to be differences in contents: + + o The LSA's Options field has changed. + + o One of the LSA instances has LS age set to MaxAge, and + the other does not. + + o The length field in the LSA header has changed. + + o The body of the LSA (i.e., anything outside the 20-byte + LSA header) has changed. Note that this excludes changes + in LS Sequence Number and LS Checksum. + + */ + /* Look up old LSA and determine if any SPF calculation or incremental + update is needed */ + old = ospf_lsdb_lookup(lsdb, lsa); + + /* Do comparison and record if recalc needed. */ + rt_recalc = 0; + if (old == NULL || ospf_lsa_different(old, lsa, false)) { + /* Ref rfc3623 section 3.2.3 + * Installing new lsa or change in the existing LSA + * or flushing existing LSA leads to topo change + * and trigger SPF caculation. + * So, router should be aborted from HELPER role + * if it is detected as TOPO change. + */ + if (ospf->active_restarter_cnt && + CHECK_LSA_TYPE_1_TO_5_OR_7(lsa->data->type)) { + if (old == NULL || ospf_lsa_different(old, lsa, true)) + ospf_helper_handle_topo_chg(ospf, lsa); + } + + rt_recalc = 1; + } + + /* + Sequence number check (Section 14.1 of rfc 2328) + "Premature aging is used when it is time for a self-originated + LSA's sequence number field to wrap. At this point, the current + LSA instance (having LS sequence number MaxSequenceNumber) must + be prematurely aged and flushed from the routing domain before a + new instance with sequence number equal to InitialSequenceNumber + can be originated. " + */ + + if (ntohl(lsa->data->ls_seqnum) - 1 == OSPF_MAX_SEQUENCE_NUMBER) { + if (ospf_lsa_is_self_originated(ospf, lsa)) { + lsa->data->ls_seqnum = htonl(OSPF_MAX_SEQUENCE_NUMBER); + + if (!IS_LSA_MAXAGE(lsa)) + lsa->flags |= OSPF_LSA_PREMATURE_AGE; + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) { + zlog_debug( + "%s() Premature Aging lsa %p, seqnum 0x%x", + __func__, lsa, + ntohl(lsa->data->ls_seqnum)); + ospf_lsa_header_dump(lsa->data); + } + } else { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "%s() got an lsa with seq 0x80000000 that was not self originated. Ignoring", + __func__); + ospf_lsa_header_dump(lsa->data); + } + return old; + } + } + + /* discard old LSA from LSDB */ + if (old != NULL) + ospf_discard_from_db(ospf, lsdb, lsa); + + /* Calculate Checksum if self-originated?. */ + if (IS_LSA_SELF(lsa)) + ospf_lsa_checksum(lsa->data); + + /* Insert LSA to LSDB. */ + ospf_lsdb_add(lsdb, lsa); + lsa->lsdb = lsdb; + + /* Do LSA specific installation process. */ + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + new = ospf_router_lsa_install(ospf, lsa, rt_recalc); + break; + case OSPF_NETWORK_LSA: + assert(oi); + new = ospf_network_lsa_install(ospf, oi, lsa, rt_recalc); + break; + case OSPF_SUMMARY_LSA: + new = ospf_summary_lsa_install(ospf, lsa, rt_recalc); + break; + case OSPF_ASBR_SUMMARY_LSA: + new = ospf_summary_asbr_lsa_install(ospf, lsa, rt_recalc); + break; + case OSPF_AS_EXTERNAL_LSA: + new = ospf_external_lsa_install(ospf, lsa, rt_recalc); + break; + case OSPF_OPAQUE_LINK_LSA: + if (IS_LSA_SELF(lsa)) + lsa->oi = oi; /* Specify outgoing ospf-interface for + this LSA. */ + else { + /* Incoming "oi" for this LSA has set at LSUpd + * reception. */ + } + fallthrough; + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + new = ospf_opaque_lsa_install(lsa, rt_recalc); + break; + case OSPF_AS_NSSA_LSA: + new = ospf_external_lsa_install(ospf, lsa, rt_recalc); + break; + default: /* type-6,8,9....nothing special */ + break; + } + + if (new == NULL) + return new; /* Installation failed, cannot proceed further -- + endo. */ + + /* Debug logs. */ + if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) { + switch (lsa->data->type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + case OSPF_AS_NSSA_LSA: + zlog_debug("LSA[%s]: Install %s", dump_lsa_key(new), + lookup_msg(ospf_lsa_type_msg, + new->data->type, NULL)); + break; + default: + zlog_debug("LSA[%s]: Install %s to Area %pI4", + dump_lsa_key(new), + lookup_msg(ospf_lsa_type_msg, + new->data->type, NULL), + &new->area->area_id); + break; + } + } + + /* + If received LSA' ls_age is MaxAge, or lsa is being prematurely aged + (it's getting flushed out of the area), set LSA on MaxAge LSA list. + */ + if (IS_LSA_MAXAGE(new)) { + if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) + zlog_debug("LSA[%s]: Install LSA %p, MaxAge", + dump_lsa_key(new), lsa); + ospf_lsa_maxage(ospf, lsa); + } + + return new; +} + + +int ospf_check_nbr_status(struct ospf *ospf) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { + struct route_node *rn; + struct ospf_neighbor *nbr; + + if (ospf_if_is_enable(oi)) + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL) + if (nbr->state == NSM_Exchange + || nbr->state == NSM_Loading) { + route_unlock_node(rn); + return 0; + } + } + + return 1; +} + + +void ospf_maxage_lsa_remover(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + struct ospf_lsa *lsa, *old; + struct route_node *rn; + int reschedule = 0; + + ospf->t_maxage = NULL; + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("LSA[MaxAge]: remover Start"); + + reschedule = !ospf_check_nbr_status(ospf); + + if (!reschedule) + for (rn = route_top(ospf->maxage_lsa); rn; + rn = route_next(rn)) { + if ((lsa = rn->info) == NULL) { + continue; + } + + /* There is at least one neighbor from which we still + * await an ack + * for that LSA, so we are not allowed to remove it from + * our lsdb yet + * as per RFC 2328 section 14 para 4 a) */ + if (lsa->retransmit_counter > 0) { + reschedule = 1; + continue; + } + + /* TODO: maybe convert this function to a work-queue */ + if (event_should_yield(thread)) { + OSPF_TIMER_ON(ospf->t_maxage, + ospf_maxage_lsa_remover, 0); + route_unlock_node( + rn); /* route_top/route_next */ + return; + } + + /* Remove LSA from the LSDB */ + if (IS_LSA_SELF(lsa)) + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA[Type%d:%pI4]: LSA 0x%lx is self-originated: ", + lsa->data->type, + &lsa->data->id, + (unsigned long)lsa); + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA[%s]: MaxAge LSA removed from list", + dump_lsa_key(lsa)); + + if (CHECK_FLAG(lsa->flags, OSPF_LSA_PREMATURE_AGE)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "originating new lsa for lsa %p", + lsa); + ospf_lsa_refresh(ospf, lsa); + } + + /* Remove from lsdb. */ + if (lsa->lsdb) { + old = ospf_lsdb_lookup(lsa->lsdb, lsa); + /* The max age LSA here must be the same + * as the LSA in LSDB + */ + if (old != lsa) { + flog_err(EC_OSPF_LSA_MISSING, + "%s: LSA[%s]: LSA not in LSDB", + __func__, dump_lsa_key(lsa)); + + continue; + } + ospf_discard_from_db(ospf, lsa->lsdb, lsa); + ospf_lsdb_delete(lsa->lsdb, lsa); + } else { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "%s: LSA[%s]: No associated LSDB!", + __func__, dump_lsa_key(lsa)); + } + } + + /* A MaxAge LSA must be removed immediately from the router's link + state database as soon as both a) it is no longer contained on any + neighbor Link state retransmission lists and b) none of the + router's + neighbors are in states Exchange or Loading. */ + if (reschedule) + OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, + ospf->maxage_delay); +} + +/* This function checks whether an LSA with initial sequence number should be + * originated after a wrap in sequence number + */ +void ospf_check_and_gen_init_seq_lsa(struct ospf_interface *oi, + struct ospf_lsa *recv_lsa) +{ + struct ospf_lsa *lsa = NULL; + struct ospf *ospf = oi->ospf; + + lsa = ospf_lsa_lookup_by_header(oi->area, recv_lsa->data); + + if ((lsa == NULL) || (!CHECK_FLAG(lsa->flags, OSPF_LSA_PREMATURE_AGE)) + || (lsa->retransmit_counter != 0)) { + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug( + "Do not generate LSA with initial seqence number."); + return; + } + + ospf_lsa_maxage_delete(ospf, lsa); + + lsa->data->ls_seqnum = lsa_seqnum_increment(lsa); + + ospf_lsa_refresh(ospf, lsa); +} + +void ospf_lsa_maxage_delete(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct route_node *rn; + struct prefix lsa_prefix; + + memset(&lsa_prefix, 0, sizeof(lsa_prefix)); + lsa_prefix.family = AF_UNSPEC; + lsa_prefix.prefixlen = sizeof(lsa_prefix.u.ptr) * CHAR_BIT; + lsa_prefix.u.ptr = (uintptr_t)lsa; + + if ((rn = route_node_lookup(ospf->maxage_lsa, &lsa_prefix))) { + if (rn->info == lsa) { + UNSET_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE); + ospf_lsa_unlock(&lsa); /* maxage_lsa */ + rn->info = NULL; + route_unlock_node( + rn); /* unlock node because lsa is deleted */ + } + route_unlock_node(rn); /* route_node_lookup */ + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: lsa %s is not found in maxage db.", + __func__, dump_lsa_key(lsa)); + } +} + +/* Add LSA onto the MaxAge list, and schedule for removal. + * This does *not* lead to the LSA being flooded, that must be taken + * care of elsewhere, see, e.g., ospf_lsa_flush* (which are callers of this + * function). + */ +void ospf_lsa_maxage(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct prefix lsa_prefix; + struct route_node *rn; + + /* When we saw a MaxAge LSA flooded to us, we put it on the list + and schedule the MaxAge LSA remover. */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA[%s]: %p already exists on MaxAge LSA list", + dump_lsa_key(lsa), lsa); + return; + } + + memset(&lsa_prefix, 0, sizeof(lsa_prefix)); + lsa_prefix.family = AF_UNSPEC; + lsa_prefix.prefixlen = sizeof(lsa_prefix.u.ptr) * CHAR_BIT; + lsa_prefix.u.ptr = (uintptr_t)lsa; + + rn = route_node_get(ospf->maxage_lsa, &lsa_prefix); + if (rn->info != NULL) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug( + "LSA[%s]: found LSA (%p) in table for LSA %p %d", + dump_lsa_key(lsa), rn->info, + (void *)lsa, lsa_prefix.prefixlen); + route_unlock_node(rn); + } else { + rn->info = ospf_lsa_lock(lsa); + SET_FLAG(lsa->flags, OSPF_LSA_IN_MAXAGE); + } + + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("LSA[%s]: MaxAge LSA remover scheduled.", + dump_lsa_key(lsa)); + + OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, + ospf->maxage_delay); +} + +static int ospf_lsa_maxage_walker_remover(struct ospf *ospf, + struct ospf_lsa *lsa) +{ + /* Stay away from any Local Translated Type-7 LSAs */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) + return 0; + + if (IS_LSA_MAXAGE(lsa)) + /* Self-originated LSAs should NOT time-out instead, + they're flushed and submitted to the max_age list explicitly. + */ + if (!ospf_lsa_is_self_originated(ospf, lsa)) { + if (IS_DEBUG_OSPF(lsa, LSA_FLOODING)) + zlog_debug("LSA[%s]: is MaxAge", + dump_lsa_key(lsa)); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + /* + * As a general rule, whenever network topology + * has changed + * (due to an LSA removal in this case), routing + * recalculation + * should be triggered. However, this is not + * true for opaque + * LSAs. Even if an opaque LSA instance is going + * to be removed + * from the routing domain, it does not mean a + * change in network + * topology, and thus, routing recalculation is + * not needed here. + */ + break; + case OSPF_AS_EXTERNAL_LSA: + case OSPF_AS_NSSA_LSA: + ospf_ase_incremental_update(ospf, lsa); + break; + default: + ospf_spf_calculate_schedule(ospf, + SPF_FLAG_MAXAGE); + break; + } + ospf_lsa_maxage(ospf, lsa); + } + + if (IS_LSA_MAXAGE(lsa) && !ospf_lsa_is_self_originated(ospf, lsa)) + if (LS_AGE(lsa) > OSPF_LSA_MAXAGE + 30) + printf("Eek! Shouldn't happen!\n"); + + return 0; +} + +/* Periodical check of MaxAge LSA. */ +void ospf_lsa_maxage_walker(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + struct route_node *rn; + struct ospf_lsa *lsa; + struct ospf_area *area; + struct listnode *node, *nnode; + + ospf->t_maxage_walker = NULL; + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + } + + /* for AS-external-LSAs. */ + if (ospf->lsdb) { + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + ospf_lsa_maxage_walker_remover(ospf, lsa); + } + + OSPF_TIMER_ON(ospf->t_maxage_walker, ospf_lsa_maxage_walker, + OSPF_LSA_MAXAGE_CHECK_INTERVAL); +} + +struct ospf_lsa *ospf_lsa_lookup_by_prefix(struct ospf_lsdb *lsdb, uint8_t type, + struct prefix_ipv4 *p, + struct in_addr router_id) +{ + struct ospf_lsa *lsa; + struct in_addr mask, id; + struct lsa_header_mask { + struct lsa_header header; + struct in_addr mask; + } * hmask; + + lsa = ospf_lsdb_lookup_by_id(lsdb, type, p->prefix, router_id); + if (lsa == NULL) + return NULL; + + masklen2ip(p->prefixlen, &mask); + + hmask = (struct lsa_header_mask *)lsa->data; + + if (mask.s_addr != hmask->mask.s_addr) { + id.s_addr = p->prefix.s_addr | (~mask.s_addr); + lsa = ospf_lsdb_lookup_by_id(lsdb, type, id, router_id); + if (!lsa) + return NULL; + } + + return lsa; +} + +struct ospf_lsa *ospf_lsa_lookup(struct ospf *ospf, struct ospf_area *area, + uint32_t type, struct in_addr id, + struct in_addr adv_router) +{ + if (!ospf) + return NULL; + + switch (type) { + case OSPF_ROUTER_LSA: + case OSPF_NETWORK_LSA: + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + case OSPF_AS_NSSA_LSA: + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + return ospf_lsdb_lookup_by_id(area->lsdb, type, id, adv_router); + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + return ospf_lsdb_lookup_by_id(ospf->lsdb, type, id, adv_router); + default: + break; + } + + return NULL; +} + +struct ospf_lsa *ospf_lsa_lookup_by_id(struct ospf_area *area, uint32_t type, + struct in_addr id) +{ + struct ospf_lsa *lsa; + struct route_node *rn; + + switch (type) { + case OSPF_ROUTER_LSA: + return ospf_lsdb_lookup_by_id(area->lsdb, type, id, id); + case OSPF_NETWORK_LSA: + for (rn = route_top(NETWORK_LSDB(area)); rn; + rn = route_next(rn)) + if ((lsa = rn->info)) + if (IPV4_ADDR_SAME(&lsa->data->id, &id)) { + route_unlock_node(rn); + return lsa; + } + break; + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + /* Currently not used. */ + assert(1); + return ospf_lsdb_lookup_by_id(area->lsdb, type, id, id); + case OSPF_AS_EXTERNAL_LSA: + case OSPF_AS_NSSA_LSA: + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + /* Currently not used. */ + break; + default: + break; + } + + return NULL; +} + +struct ospf_lsa *ospf_lsa_lookup_by_header(struct ospf_area *area, + struct lsa_header *lsah) +{ + struct ospf_lsa *match; + + /* + * Strictly speaking, the LSA-ID field for Opaque-LSAs (type-9/10/11) + * is redefined to have two subfields; opaque-type and opaque-id. + * However, it is harmless to treat the two sub fields together, as if + * they two were forming a unique LSA-ID. + */ + + match = ospf_lsa_lookup(area->ospf, area, lsah->type, lsah->id, + lsah->adv_router); + + if (match == NULL) + if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) + zlog_debug("LSA[Type%d:%pI4]: Lookup by header, NO MATCH", + lsah->type, &lsah->id); + + return match; +} + +/* return +n, l1 is more recent. + return -n, l2 is more recent. + return 0, l1 and l2 is identical. */ +int ospf_lsa_more_recent(struct ospf_lsa *l1, struct ospf_lsa *l2) +{ + int r; + int x, y; + + if (l1 == NULL && l2 == NULL) + return 0; + if (l1 == NULL) + return -1; + if (l2 == NULL) + return 1; + + /* compare LS sequence number. */ + x = (int)ntohl(l1->data->ls_seqnum); + y = (int)ntohl(l2->data->ls_seqnum); + if (x > y) + return 1; + if (x < y) + return -1; + + /* compare LS checksum. */ + r = ntohs(l1->data->checksum) - ntohs(l2->data->checksum); + if (r) + return r; + + /* compare LS age. */ + if (IS_LSA_MAXAGE(l1) && !IS_LSA_MAXAGE(l2)) + return 1; + else if (!IS_LSA_MAXAGE(l1) && IS_LSA_MAXAGE(l2)) + return -1; + + /* compare LS age with MaxAgeDiff. */ + if (LS_AGE(l1) - LS_AGE(l2) > OSPF_LSA_MAXAGE_DIFF) + return -1; + else if (LS_AGE(l2) - LS_AGE(l1) > OSPF_LSA_MAXAGE_DIFF) + return 1; + + /* LSAs are identical. */ + return 0; +} + +/* + * Check if two LSAs are different. + * + * l1 + * The first LSA to compare. + * + * l2 + * The second LSA to compare. + * + * ignore_rcvd_flag + * When set to true, ignore whether the LSAs were received from the network + * or not. This parameter should be set to true when checking for topology + * changes as part of the Graceful Restart helper neighbor procedures. + * + * Returns: + * true if the LSAs are different, false otherwise. + */ +int ospf_lsa_different(struct ospf_lsa *l1, struct ospf_lsa *l2, + bool ignore_rcvd_flag) +{ + char *p1, *p2; + assert(l1); + assert(l2); + assert(l1->data); + assert(l2->data); + + if (l1->data->options != l2->data->options) + return 1; + + if (IS_LSA_MAXAGE(l1) && !IS_LSA_MAXAGE(l2)) + return 1; + + if (IS_LSA_MAXAGE(l2) && !IS_LSA_MAXAGE(l1)) + return 1; + + if (l1->size != l2->size) + return 1; + + if (l1->size == 0) + return 1; + + if (!ignore_rcvd_flag + && CHECK_FLAG((l1->flags ^ l2->flags), OSPF_LSA_RECEIVED)) + return 1; /* May be a stale LSA in the LSBD */ + + if (l1->size == OSPF_LSA_HEADER_SIZE) + return 0; /* nothing to compare */ + + p1 = (char *)l1->data; + p2 = (char *)l2->data; + + if (memcmp(p1 + OSPF_LSA_HEADER_SIZE, p2 + OSPF_LSA_HEADER_SIZE, + l1->size - OSPF_LSA_HEADER_SIZE) + != 0) + return 1; + + return 0; +} + +int ospf_lsa_flush_schedule(struct ospf *ospf, struct ospf_lsa *lsa) +{ + if (lsa == NULL || !IS_LSA_SELF(lsa)) + return 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: Schedule self-originated LSA to FLUSH", + lsa->data->type, &lsa->data->id); + + /* Force given lsa's age to MaxAge. */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + + switch (lsa->data->type) { + /* Opaque wants to be notified of flushes */ + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + ospf_opaque_lsa_refresh(lsa); + break; + default: + ospf_refresher_unregister_lsa(ospf, lsa); + ospf_lsa_flush(ospf, lsa); + break; + } + + return 0; +} + +void ospf_flush_self_originated_lsas_now(struct ospf *ospf) +{ + struct listnode *node, *nnode; + struct listnode *node2, *nnode2; + struct ospf_area *area; + struct ospf_interface *oi; + struct ospf_lsa *lsa; + struct route_node *rn; + struct ospf_if_params *oip; + int need_to_flush_ase = 0; + + ospf->inst_shutdown = 1; + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + if ((lsa = area->router_lsa_self) != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: Schedule self-originated LSA to FLUSH", + lsa->data->type, + &lsa->data->id); + + ospf_refresher_unregister_lsa(ospf, lsa); + ospf_lsa_flush_area(lsa, area); + ospf_lsa_unlock(&area->router_lsa_self); + area->router_lsa_self = NULL; + } + + for (ALL_LIST_ELEMENTS(area->oiflist, node2, nnode2, oi)) { + if ((lsa = oi->network_lsa_self) != NULL + && oi->state == ISM_DR && oi->full_nbrs > 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: Schedule self-originated LSA to FLUSH", + lsa->data->type, + &lsa->data->id); + + ospf_refresher_unregister_lsa( + ospf, oi->network_lsa_self); + ospf_lsa_flush_area(oi->network_lsa_self, area); + ospf_lsa_unlock(&oi->network_lsa_self); + oi->network_lsa_self = NULL; + + oip = ospf_lookup_if_params( + oi->ifp, oi->address->u.prefix4); + if (oip) + oip->network_lsa_seqnum = htonl( + OSPF_INVALID_SEQUENCE_NUMBER); + } + + if (oi->type != OSPF_IFTYPE_VIRTUALLINK + && area->external_routing == OSPF_AREA_DEFAULT) + need_to_flush_ase = 1; + } + + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + } + + if (need_to_flush_ase) { + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + ospf_lsa_flush_schedule(ospf, lsa); + } + + /* + * Make sure that the MaxAge LSA remover is executed immediately, + * without conflicting to other threads. + */ + if (ospf->t_maxage != NULL) { + EVENT_OFF(ospf->t_maxage); + event_execute(master, ospf_maxage_lsa_remover, ospf, 0, NULL); + } + + return; +} + +/** @brief Function to refresh all the self originated + * LSAs for area, when FR state change happens. + * @param area pointer. + * @return Void. + */ +void ospf_refresh_area_self_lsas(struct ospf_area *area) +{ + struct listnode *node2; + struct listnode *nnode2; + struct ospf_interface *oi; + struct route_node *rn; + struct ospf_lsa *lsa; + + if (!area) + return; + + if (area->router_lsa_self) + ospf_lsa_refresh(area->ospf, area->router_lsa_self); + + for (ALL_LIST_ELEMENTS(area->oiflist, node2, nnode2, oi)) + if (oi->network_lsa_self) + ospf_lsa_refresh(oi->ospf, oi->network_lsa_self); + + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); + LSDB_LOOP (EXTERNAL_LSDB(area->ospf), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); + LSDB_LOOP (OPAQUE_AS_LSDB(area->ospf), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_refresh(area->ospf, lsa); +} + +/* If there is self-originated LSA, then return 1, otherwise return 0. */ +/* An interface-independent version of ospf_lsa_is_self_originated */ +int ospf_lsa_is_self_originated(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct listnode *node; + struct ospf_interface *oi; + + /* This LSA is already checked. */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_SELF_CHECKED)) + return IS_LSA_SELF(lsa); + + /* Make sure LSA is self-checked. */ + SET_FLAG(lsa->flags, OSPF_LSA_SELF_CHECKED); + + /* AdvRouter and Router ID is the same. */ + if (IPV4_ADDR_SAME(&lsa->data->adv_router, &ospf->router_id)) + SET_FLAG(lsa->flags, OSPF_LSA_SELF); + + /* LSA is router-LSA. */ + else if (lsa->data->type == OSPF_ROUTER_LSA + && IPV4_ADDR_SAME(&lsa->data->id, &ospf->router_id)) + SET_FLAG(lsa->flags, OSPF_LSA_SELF); + + /* LSA is network-LSA. Compare Link ID with all interfaces. */ + else if (lsa->data->type == OSPF_NETWORK_LSA) + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + /* Ignore virtual link. */ + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + if (oi->address->family == AF_INET) + if (IPV4_ADDR_SAME( + &lsa->data->id, + &oi->address->u.prefix4)) { + /* to make it easier later */ + SET_FLAG(lsa->flags, + OSPF_LSA_SELF); + return IS_LSA_SELF(lsa); + } + } + + return IS_LSA_SELF(lsa); +} + +/* Get unique Link State ID. */ +enum lsid_status ospf_lsa_unique_id(struct ospf *ospf, struct ospf_lsdb *lsdb, + uint8_t type, struct prefix_ipv4 *p, + struct in_addr *id) +{ + struct ospf_lsa *lsa; + struct in_addr mask; + + *id = p->prefix; + + /* Check existence of LSA instance. */ + lsa = ospf_lsdb_lookup_by_id(lsdb, type, *id, ospf->router_id); + if (lsa) { + struct as_external_lsa *al = + (struct as_external_lsa *)lsa->data; + /* Ref rfc2328,Appendex E.1 + * If router already originated the external lsa with lsid + * as the current prefix, and the masklens are same then + * terminate the LSID algorithem. + */ + if (ip_masklen(al->mask) == p->prefixlen) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s: Can't get Link State ID for %pFX", + __func__, p); + /* id.s_addr = 0; */ + id->s_addr = 0xffffffff; + return LSID_NOT_AVAILABLE; + } else if (ip_masklen(al->mask) < p->prefixlen) { + /* Ref rfc2328,Appendex E.2 + * the current prefix masklen is greater than the + * existing LSA, then generate the Link state ID, + * by setting all host bits in prefix addressa and + * originate. + * + * Eg: 1st Route : 10.0.0.0/16 - LSID:10.0.0.0 + * 2nd Route : 10.0.0.0/24 - LSID:10.0.0.255 + */ + masklen2ip(p->prefixlen, &mask); + + id->s_addr = p->prefix.s_addr | (~mask.s_addr); + lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, type, *id, + ospf->router_id); + if (lsa) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s: Can't get Link State ID for %pFX", + __func__, p); + id->s_addr = 0xffffffff; + return LSID_NOT_AVAILABLE; + } + } else { + /* Ref rfc2328,Appendex E.3 + * the current prefix masklen is lesser than the + * existing LSA,then the originated LSA has to be + * refreshed by modifying masklen, cost and tag. + * Originate the old route info with new LSID by + * setting the host bits in prefix address. + * + * Eg: 1st Route : 10.0.0.0/24 - LSID:10.0.0.0 + * 2nd Route : 10.0.0.0/16 - ? + * Since 2nd route mask len is less than firstone + * LSID has to be changed. + * 1st route LSID:10.0.0.255 + * 2nd route LSID:10.0.0.0 + */ + id->s_addr = lsa->data->id.s_addr | (~al->mask.s_addr); + lsa = ospf_lsdb_lookup_by_id(ospf->lsdb, type, *id, + ospf->router_id); + if (lsa && (ip_masklen(al->mask) != IPV4_MAX_BITLEN)) { + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "%s: Can't get Link State ID for %pFX", + __func__, p); + id->s_addr = 0xffffffff; + return LSID_NOT_AVAILABLE; + } + return LSID_CHANGE; + } + } + + return LSID_AVAILABLE; +} + + +#define LSA_ACTION_FLOOD_AREA 1 +#define LSA_ACTION_FLUSH_AREA 2 + +struct lsa_action { + uint8_t action; + struct ospf_area *area; + struct ospf_lsa *lsa; +}; + +static void ospf_lsa_action(struct event *t) +{ + struct lsa_action *data; + + data = EVENT_ARG(t); + + if (IS_DEBUG_OSPF(lsa, LSA) == OSPF_DEBUG_LSA) + zlog_debug("LSA[Action]: Performing scheduled LSA action: %d", + data->action); + + switch (data->action) { + case LSA_ACTION_FLOOD_AREA: + ospf_flood_through_area(data->area, NULL, data->lsa); + break; + case LSA_ACTION_FLUSH_AREA: + ospf_lsa_flush_area(data->lsa, data->area); + break; + } + + ospf_lsa_unlock(&data->lsa); /* Message */ + XFREE(MTYPE_OSPF_MESSAGE, data); +} + +void ospf_schedule_lsa_flood_area(struct ospf_area *area, struct ospf_lsa *lsa) +{ + struct lsa_action *data; + + data = XCALLOC(MTYPE_OSPF_MESSAGE, sizeof(struct lsa_action)); + data->action = LSA_ACTION_FLOOD_AREA; + data->area = area; + data->lsa = ospf_lsa_lock(lsa); /* Message / Flood area */ + + event_add_event(master, ospf_lsa_action, data, 0, NULL); +} + +void ospf_schedule_lsa_flush_area(struct ospf_area *area, struct ospf_lsa *lsa) +{ + struct lsa_action *data; + + data = XCALLOC(MTYPE_OSPF_MESSAGE, sizeof(struct lsa_action)); + data->action = LSA_ACTION_FLUSH_AREA; + data->area = area; + data->lsa = ospf_lsa_lock(lsa); /* Message / Flush area */ + + event_add_event(master, ospf_lsa_action, data, 0, NULL); +} + + +/* LSA Refreshment functions. */ +struct ospf_lsa *ospf_lsa_refresh(struct ospf *ospf, struct ospf_lsa *lsa) +{ + struct external_info *ei; + struct ospf_external_aggr_rt *aggr; + struct ospf_lsa *new = NULL; + struct as_external_lsa *al; + struct prefix_ipv4 p; + + assert(CHECK_FLAG(lsa->flags, OSPF_LSA_SELF)); + assert(IS_LSA_SELF(lsa)); + assert(lsa->lock > 0); + + switch (lsa->data->type) { + /* Router and Network LSAs are processed differently. */ + case OSPF_ROUTER_LSA: + new = ospf_router_lsa_refresh(lsa); + break; + case OSPF_NETWORK_LSA: + new = ospf_network_lsa_refresh(lsa); + break; + case OSPF_SUMMARY_LSA: + new = ospf_summary_lsa_refresh(ospf, lsa); + break; + case OSPF_ASBR_SUMMARY_LSA: + new = ospf_summary_asbr_lsa_refresh(ospf, lsa); + break; + case OSPF_AS_EXTERNAL_LSA: + /* Translated from NSSA Type-5s are refreshed when + * from refresh of Type-7 - do not refresh these directly. + */ + + al = (struct as_external_lsa *)lsa->data; + p.family = AF_INET; + p.prefixlen = ip_masklen(al->mask); + p.prefix = lsa->data->id; + + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) + break; + ei = ospf_external_info_check(ospf, lsa); + if (ei) + new = ospf_external_lsa_refresh( + ospf, lsa, ei, LSA_REFRESH_FORCE, false); + else { + aggr = (struct ospf_external_aggr_rt *) + ospf_extrenal_aggregator_lookup(ospf, &p); + if (aggr) { + struct external_info ei_aggr; + + memset(&ei_aggr, 0, sizeof(ei_aggr)); + ei_aggr.p = aggr->p; + ei_aggr.tag = aggr->tag; + ei_aggr.instance = ospf->instance; + ei_aggr.route_map_set.metric = -1; + ei_aggr.route_map_set.metric_type = -1; + + ospf_external_lsa_refresh(ospf, lsa, &ei_aggr, + LSA_REFRESH_FORCE, true); + SET_FLAG(aggr->flags, + OSPF_EXTERNAL_AGGRT_ORIGINATED); + } else + ospf_lsa_flush_as(ospf, lsa); + } + break; + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + new = ospf_opaque_lsa_refresh(lsa); + break; + default: + break; + } + return new; +} + +void ospf_refresher_register_lsa(struct ospf *ospf, struct ospf_lsa *lsa) +{ + uint16_t index, current_index; + + assert(lsa->lock > 0); + assert(IS_LSA_SELF(lsa)); + + if (lsa->refresh_list < 0) { + int delay; + int min_delay = + ospf->lsa_refresh_timer - (2 * OSPF_LS_REFRESH_JITTER); + int max_delay = + ospf->lsa_refresh_timer - OSPF_LS_REFRESH_JITTER; + + /* We want to refresh the LSA within OSPF_LS_REFRESH_TIME which + * is + * 1800s. Use jitter so that we send the LSA sometime between + * 1680s + * and 1740s. + */ + delay = (frr_weak_random() % (max_delay - min_delay)) + + min_delay; + + current_index = ospf->lsa_refresh_queue.index + + (monotime(NULL) - ospf->lsa_refresher_started) + / OSPF_LSA_REFRESHER_GRANULARITY; + + index = (current_index + delay / OSPF_LSA_REFRESHER_GRANULARITY) + % (OSPF_LSA_REFRESHER_SLOTS); + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug( + "LSA[Refresh:Type%d:%pI4]: age %d, added to index %d", + lsa->data->type, &lsa->data->id, + LS_AGE(lsa), index); + + if (!ospf->lsa_refresh_queue.qs[index]) + ospf->lsa_refresh_queue.qs[index] = list_new(); + + listnode_add(ospf->lsa_refresh_queue.qs[index], + ospf_lsa_lock(lsa)); /* lsa_refresh_queue */ + lsa->refresh_list = index; + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug( + "LSA[Refresh:Type%d:%pI4]: %s: setting refresh_list on lsa %p (slot %d)", + lsa->data->type, &lsa->data->id, __func__, + (void *)lsa, index); + } +} + +void ospf_refresher_unregister_lsa(struct ospf *ospf, struct ospf_lsa *lsa) +{ + assert(lsa->lock > 0); + assert(IS_LSA_SELF(lsa)); + if (lsa->refresh_list >= 0) { + struct list *refresh_list = + ospf->lsa_refresh_queue.qs[lsa->refresh_list]; + listnode_delete(refresh_list, lsa); + if (!listcount(refresh_list)) { + list_delete(&refresh_list); + ospf->lsa_refresh_queue.qs[lsa->refresh_list] = NULL; + } + lsa->refresh_list = -1; + ospf_lsa_unlock(&lsa); /* lsa_refresh_queue */ + } +} + +void ospf_lsa_refresh_walker(struct event *t) +{ + struct list *refresh_list; + struct listnode *node, *nnode; + struct ospf *ospf = EVENT_ARG(t); + struct ospf_lsa *lsa; + int i; + struct list *lsa_to_refresh = list_new(); + bool dna_lsa; + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug("LSA[Refresh]: %s: start", __func__); + + + i = ospf->lsa_refresh_queue.index; + + /* Note: if clock has jumped backwards, then time change could be + negative, + so we are careful to cast the expression to unsigned before taking + modulus. */ + ospf->lsa_refresh_queue.index = + ((unsigned long)(ospf->lsa_refresh_queue.index + + (monotime(NULL) + - ospf->lsa_refresher_started) + / OSPF_LSA_REFRESHER_GRANULARITY)) + % OSPF_LSA_REFRESHER_SLOTS; + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug("LSA[Refresh]: %s: next index %d", __func__, + ospf->lsa_refresh_queue.index); + + for (; i != ospf->lsa_refresh_queue.index; + i = (i + 1) % OSPF_LSA_REFRESHER_SLOTS) { + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug("LSA[Refresh]: %s: refresh index %d", + __func__, i); + + refresh_list = ospf->lsa_refresh_queue.qs[i]; + + assert(i >= 0); + + ospf->lsa_refresh_queue.qs[i] = NULL; + + if (refresh_list) { + for (ALL_LIST_ELEMENTS(refresh_list, node, nnode, + lsa)) { + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug( + "LSA[Refresh:Type%d:%pI4]: %s: refresh lsa %p (slot %d)", + lsa->data->type, &lsa->data->id, + __func__, (void *)lsa, i); + + assert(lsa->lock > 0); + list_delete_node(refresh_list, node); + lsa->refresh_list = -1; + listnode_add(lsa_to_refresh, lsa); + } + list_delete(&refresh_list); + } + } + + ospf->t_lsa_refresher = NULL; + event_add_timer(master, ospf_lsa_refresh_walker, ospf, + ospf->lsa_refresh_interval, &ospf->t_lsa_refresher); + ospf->lsa_refresher_started = monotime(NULL); + + for (ALL_LIST_ELEMENTS(lsa_to_refresh, node, nnode, lsa)) { + dna_lsa = ospf_check_dna_lsa(lsa); + if (!dna_lsa) { /* refresh only non-DNA LSAs */ + ospf_lsa_refresh(ospf, lsa); + } + assert(lsa->lock > 0); + ospf_lsa_unlock(&lsa); /* lsa_refresh_queue & temp for + * lsa_to_refresh. + */ + } + + list_delete(&lsa_to_refresh); + + if (IS_DEBUG_OSPF(lsa, LSA_REFRESH)) + zlog_debug("LSA[Refresh]: %s: end", __func__); +} + +/* Flush the LSAs for the specific area */ +void ospf_flush_lsa_from_area(struct ospf *ospf, struct in_addr area_id, + int type) +{ + struct ospf_area *area; + struct route_node *rn; + struct ospf_lsa *lsa; + + area = ospf_area_get(ospf, area_id); + + switch (type) { + case OSPF_AS_EXTERNAL_LSA: + if ((area->external_routing == OSPF_AREA_NSSA) || + (area->external_routing == OSPF_AREA_STUB)) { + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + if (IS_LSA_SELF(lsa) && + !(CHECK_FLAG(lsa->flags, + OSPF_LSA_LOCAL_XLT))) + ospf_lsa_flush_area(lsa, area); + } + break; + case OSPF_AS_NSSA_LSA: + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + if (IS_LSA_SELF(lsa)) + ospf_lsa_flush_area(lsa, area); + break; + default: + break; + } +} diff --git a/ospfd/ospf_lsa.h b/ospfd/ospf_lsa.h new file mode 100644 index 0000000..d5ca069 --- /dev/null +++ b/ospfd/ospf_lsa.h @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Link State Advertisement + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_LSA_H +#define _ZEBRA_OSPF_LSA_H + +#include "stream.h" + +/* OSPF LSA Default metric values */ +#define DEFAULT_DEFAULT_METRIC 20 +#define DEFAULT_DEFAULT_ORIGINATE_METRIC 10 +#define DEFAULT_DEFAULT_ALWAYS_METRIC 1 +#define DEFAULT_METRIC_TYPE EXTERNAL_METRIC_TYPE_2 + +/* OSPF LSA Range definition. */ +#define OSPF_MIN_LSA 1 /* begin range here */ +#define OSPF_MAX_LSA 12 + +/* OSPF LSA Type definition. */ +#define OSPF_UNKNOWN_LSA 0 +#define OSPF_ROUTER_LSA 1 +#define OSPF_NETWORK_LSA 2 +#define OSPF_SUMMARY_LSA 3 +#define OSPF_ASBR_SUMMARY_LSA 4 +#define OSPF_AS_EXTERNAL_LSA 5 +#define OSPF_GROUP_MEMBER_LSA 6 /* Not supported. */ +#define OSPF_AS_NSSA_LSA 7 +#define OSPF_EXTERNAL_ATTRIBUTES_LSA 8 /* Not supported. */ +#define OSPF_OPAQUE_LINK_LSA 9 +#define OSPF_OPAQUE_AREA_LSA 10 +#define OSPF_OPAQUE_AS_LSA 11 + +#define OSPF_LSA_HEADER_SIZE 20U +#define OSPF_ROUTER_LSA_LINK_SIZE 12U +#define OSPF_ROUTER_LSA_TOS_SIZE 4U +#define OSPF_MAX_LSA_SIZE 1500U + +/* AS-external-LSA refresh method. */ +#define LSA_REFRESH_IF_CHANGED 0 +#define LSA_REFRESH_FORCE 1 + +/* OSPF LSA header. */ +struct lsa_header { + uint16_t ls_age; +#define DO_NOT_AGE 0x8000 + uint8_t options; + uint8_t type; + struct in_addr id; + struct in_addr adv_router; + uint32_t ls_seqnum; + uint16_t checksum; + uint16_t length; +}; + +struct vertex; + +/* OSPF LSA. */ +struct ospf_lsa { + /* LSA origination flag. */ + uint8_t flags; +#define OSPF_LSA_SELF 0x01 +#define OSPF_LSA_SELF_CHECKED 0x02 +#define OSPF_LSA_RECEIVED 0x04 +#define OSPF_LSA_APPROVED 0x08 +#define OSPF_LSA_DISCARD 0x10 +#define OSPF_LSA_LOCAL_XLT 0x20 +#define OSPF_LSA_PREMATURE_AGE 0x40 +#define OSPF_LSA_IN_MAXAGE 0x80 + + /* LSA data. and size */ + struct lsa_header *data; + size_t size; + + /* Received time stamp. */ + struct timeval tv_recv; + + /* Last time it was originated */ + struct timeval tv_orig; + + /* All of reference count, also lock to remove. */ + int lock; + + /* Flags for the SPF calculation. */ + struct vertex *stat; + + /* References to this LSA in neighbor retransmission lists*/ + int retransmit_counter; + + /* Area the LSA belongs to, may be NULL if AS-external-LSA. */ + struct ospf_area *area; + + /* Parent LSDB. */ + struct ospf_lsdb *lsdb; + + /* Related Route. */ + void *route; + + /* Refreshement List or Queue */ + int refresh_list; + + /* For Type-9 Opaque-LSAs */ + struct ospf_interface *oi; + + /* VRF Id */ + vrf_id_t vrf_id; + + /*For topo chg detection in HELPER role*/ + bool to_be_acknowledged; + + /* send maxage with no data */ + bool opaque_zero_len_delete; +}; + +/* OSPF LSA Link Type. */ +#define LSA_LINK_TYPE_POINTOPOINT 1 +#define LSA_LINK_TYPE_TRANSIT 2 +#define LSA_LINK_TYPE_STUB 3 +#define LSA_LINK_TYPE_VIRTUALLINK 4 + +/* OSPF Router LSA Flag. */ +#define ROUTER_LSA_BORDER 0x01 /* The router is an ABR */ +#define ROUTER_LSA_EXTERNAL 0x02 /* The router is an ASBR */ +#define ROUTER_LSA_VIRTUAL 0x04 /* The router has a VL in this area */ +#define ROUTER_LSA_NT 0x10 /* The routers always translates Type-7 */ +#define ROUTER_LSA_SHORTCUT 0x20 /* Shortcut-ABR specific flag */ + +#define IS_ROUTER_LSA_VIRTUAL(x) ((x)->flags & ROUTER_LSA_VIRTUAL) +#define IS_ROUTER_LSA_EXTERNAL(x) ((x)->flags & ROUTER_LSA_EXTERNAL) +#define IS_ROUTER_LSA_BORDER(x) ((x)->flags & ROUTER_LSA_BORDER) +#define IS_ROUTER_LSA_SHORTCUT(x) ((x)->flags & ROUTER_LSA_SHORTCUT) +#define IS_ROUTER_LSA_NT(x) ((x)->flags & ROUTER_LSA_NT) + +/* OSPF Router-LSA Link information. */ +struct router_lsa_link { + struct in_addr link_id; + struct in_addr link_data; + struct { + uint8_t type; + uint8_t tos_count; + uint16_t metric; + } m[1]; +}; + +/* OSPF Router-LSAs structure. */ +#define OSPF_ROUTER_LSA_MIN_SIZE 4U /* w/0 link descriptors */ +/* There is an edge case, when number of links in a Router-LSA may be 0 without + breaking the specification. A router, which has no other links to backbone + area besides one virtual link, will not put any VL descriptor blocks into + the Router-LSA generated for area 0 until a full adjacency over the VL is + reached (RFC2328 12.4.1.3). In this case the Router-LSA initially received + by the other end of the VL will have 0 link descriptor blocks, but soon will + be replaced with the next revision having 1 descriptor block. */ +struct router_lsa { + struct lsa_header header; + uint8_t flags; + uint8_t zero; + uint16_t links; + struct router_link { + struct in_addr link_id; + struct in_addr link_data; + uint8_t type; + uint8_t tos; + uint16_t metric; + } link[1]; +}; + +/* OSPF Network-LSAs structure. */ +#define OSPF_NETWORK_LSA_MIN_SIZE 8U /* w/1 router-ID */ +struct network_lsa { + struct lsa_header header; + struct in_addr mask; + struct in_addr routers[1]; +}; + +/* OSPF Summary-LSAs structure. */ +#define OSPF_SUMMARY_LSA_MIN_SIZE 8U /* w/1 TOS metric block */ +struct summary_lsa { + struct lsa_header header; + struct in_addr mask; + uint8_t tos; + uint8_t metric[3]; +}; + +/* OSPF AS-external-LSAs structure. */ +#define OSPF_AS_EXTERNAL_LSA_MIN_SIZE 16U /* w/1 TOS forwarding block */ +struct as_external_lsa { + struct lsa_header header; + struct in_addr mask; + struct as_route { + uint8_t tos; + uint8_t metric[3]; + struct in_addr fwd_addr; + uint32_t route_tag; + } e[1]; +}; + +enum lsid_status { LSID_AVAILABLE = 0, LSID_CHANGE, LSID_NOT_AVAILABLE }; + +#include "ospfd/ospf_opaque.h" + +/* Macros. */ +#define GET_METRIC(x) get_metric(x) +#define IS_EXTERNAL_METRIC(x) ((x) & 0x80) + +#define GET_AGE(x) (ntohs ((x)->data->ls_age) + time (NULL) - (x)->tv_recv) +#define LS_AGE(x) (OSPF_LSA_MAXAGE < get_age(x) ? OSPF_LSA_MAXAGE : get_age(x)) +#define IS_LSA_SELF(L) (CHECK_FLAG ((L)->flags, OSPF_LSA_SELF)) +#define IS_LSA_MAXAGE(L) (LS_AGE ((L)) == OSPF_LSA_MAXAGE) +#define IS_LSA_MAX_SEQ(L) \ + ((L)->data->ls_seqnum == htonl(OSPF_MAX_SEQUENCE_NUMBER)) + +#define OSPF_LSA_UPDATE_DELAY 2 + +#define CHECK_LSA_TYPE_1_TO_5_OR_7(type) \ + ((type == OSPF_ROUTER_LSA) || (type == OSPF_NETWORK_LSA) \ + || (type == OSPF_SUMMARY_LSA) || (type == OSPF_ASBR_SUMMARY_LSA) \ + || (type == OSPF_AS_EXTERNAL_LSA) || (type == OSPF_AS_NSSA_LSA)) + +#define OSPF_FR_CONFIG(o, a) \ + (o->fr_configured || ((a != NULL) ? a->fr_info.configured : 0)) + +/* Prototypes. */ +/* XXX: Eek, time functions, similar are in lib/thread.c */ +extern struct timeval int2tv(int); +extern struct timeval msec2tv(int); + +extern int get_age(struct ospf_lsa *); +extern uint16_t ospf_lsa_checksum(struct lsa_header *); +extern int ospf_lsa_checksum_valid(struct lsa_header *); +extern int ospf_lsa_refresh_delay(struct ospf_lsa *); + +extern const char *dump_lsa_key(struct ospf_lsa *); +extern uint32_t lsa_seqnum_increment(struct ospf_lsa *); +extern void lsa_header_set(struct stream *, uint8_t, uint8_t, struct in_addr, + struct in_addr); +extern struct ospf_neighbor *ospf_nbr_lookup_ptop(struct ospf_interface *); +extern int ospf_check_nbr_status(struct ospf *); + +/* Prototype for LSA primitive. */ +extern struct ospf_lsa *ospf_lsa_new(void); +extern struct ospf_lsa *ospf_lsa_new_and_data(size_t size); +extern struct ospf_lsa *ospf_lsa_dup(struct ospf_lsa *); +extern void ospf_lsa_free(struct ospf_lsa *); +extern struct ospf_lsa *ospf_lsa_lock(struct ospf_lsa *); +extern void ospf_lsa_unlock(struct ospf_lsa **); +extern void ospf_lsa_discard(struct ospf_lsa *); +extern int ospf_lsa_flush_schedule(struct ospf *, struct ospf_lsa *); +extern struct lsa_header *ospf_lsa_data_new(size_t); +extern struct lsa_header *ospf_lsa_data_dup(struct lsa_header *); +extern void ospf_lsa_data_free(struct lsa_header *); + +/* Prototype for various LSAs */ +extern void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area); +extern uint8_t router_lsa_flags(struct ospf_area *area); +extern int ospf_router_lsa_update(struct ospf *); +extern int ospf_router_lsa_update_area(struct ospf_area *); + +extern void ospf_network_lsa_update(struct ospf_interface *); + +extern struct ospf_lsa * +ospf_summary_lsa_originate(struct prefix_ipv4 *, uint32_t, struct ospf_area *); +extern struct ospf_lsa *ospf_summary_asbr_lsa_originate(struct prefix_ipv4 *, + uint32_t, + struct ospf_area *); + +extern struct ospf_lsa *ospf_lsa_install(struct ospf *, struct ospf_interface *, + struct ospf_lsa *); + +extern void ospf_nssa_lsa_flush(struct ospf *ospf, struct prefix_ipv4 *p); +extern void ospf_external_lsa_flush(struct ospf *, uint8_t, + struct prefix_ipv4 *, + ifindex_t /* , struct in_addr nexthop */); + +extern struct in_addr ospf_get_ip_from_ifp(struct ospf_interface *); + +extern struct ospf_lsa *ospf_external_lsa_originate(struct ospf *, + struct external_info *); +extern struct ospf_lsa *ospf_nssa_lsa_originate(struct ospf_area *area, + struct external_info *ei); +extern struct ospf_lsa *ospf_nssa_lsa_refresh(struct ospf_area *area, + struct ospf_lsa *lsa, + struct external_info *ei); +extern void ospf_external_lsa_rid_change(struct ospf *ospf); +extern struct ospf_lsa *ospf_lsa_lookup(struct ospf *ospf, struct ospf_area *, + uint32_t, struct in_addr, + struct in_addr); +extern struct ospf_lsa *ospf_lsa_lookup_by_id(struct ospf_area *, uint32_t, + struct in_addr); +extern struct ospf_lsa *ospf_lsa_lookup_by_header(struct ospf_area *, + struct lsa_header *); +extern int ospf_lsa_more_recent(struct ospf_lsa *, struct ospf_lsa *); +extern int ospf_lsa_different(struct ospf_lsa *, struct ospf_lsa *, + bool ignore_rcvd_flag); +extern void ospf_flush_self_originated_lsas_now(struct ospf *); + +extern int ospf_lsa_is_self_originated(struct ospf *, struct ospf_lsa *); + +extern struct ospf_lsa *ospf_lsa_lookup_by_prefix(struct ospf_lsdb *, uint8_t, + struct prefix_ipv4 *, + struct in_addr); + +extern void ospf_lsa_maxage(struct ospf *, struct ospf_lsa *); +extern uint32_t get_metric(uint8_t *); + +extern void ospf_lsa_maxage_walker(struct event *thread); +extern struct ospf_lsa *ospf_lsa_refresh(struct ospf *, struct ospf_lsa *); + +extern void ospf_external_lsa_refresh_default(struct ospf *); + +extern void ospf_external_lsa_refresh_type(struct ospf *, uint8_t, + unsigned short, int); +extern struct ospf_lsa *ospf_external_lsa_refresh(struct ospf *, + struct ospf_lsa *, + struct external_info *, int, + bool aggr); +extern enum lsid_status ospf_lsa_unique_id(struct ospf *ospf, + struct ospf_lsdb *lsdb, + uint8_t type, struct prefix_ipv4 *p, + struct in_addr *addr); +extern void ospf_schedule_lsa_flood_area(struct ospf_area *, struct ospf_lsa *); +extern void ospf_schedule_lsa_flush_area(struct ospf_area *, struct ospf_lsa *); + +extern void ospf_refresher_register_lsa(struct ospf *, struct ospf_lsa *); +extern void ospf_refresher_unregister_lsa(struct ospf *, struct ospf_lsa *); +extern void ospf_lsa_refresh_walker(struct event *thread); + +extern void ospf_lsa_maxage_delete(struct ospf *, struct ospf_lsa *); + +extern void ospf_discard_from_db(struct ospf *, struct ospf_lsdb *, + struct ospf_lsa *); + +extern int metric_type(struct ospf *, uint8_t, unsigned short); +extern int metric_value(struct ospf *, uint8_t, unsigned short); + +extern char link_info_set(struct stream **s, struct in_addr id, + struct in_addr data, uint8_t type, uint8_t tos, + uint16_t cost); + +extern struct in_addr ospf_get_nssa_ip(struct ospf_area *); +extern int ospf_translated_nssa_compare(struct ospf_lsa *, struct ospf_lsa *); +extern struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *ospf, + struct ospf_lsa *type7, + struct ospf_lsa *type5); +extern struct ospf_lsa *ospf_translated_nssa_originate(struct ospf *ospf, + struct ospf_lsa *type7, + struct ospf_lsa *type5); +extern void ospf_check_and_gen_init_seq_lsa(struct ospf_interface *oi, + struct ospf_lsa *lsa); +extern void ospf_flush_lsa_from_area(struct ospf *ospf, struct in_addr area_id, + int type); +extern void ospf_maxage_lsa_remover(struct event *thread); +extern bool ospf_check_dna_lsa(const struct ospf_lsa *lsa); +extern void ospf_refresh_area_self_lsas(struct ospf_area *area); + +/** @brief Check if the LSA is an indication LSA. + * @param lsa pointer. + * @return true or false based on lsa info. + */ +static inline bool ospf_check_indication_lsa(struct ospf_lsa *lsa) +{ + struct summary_lsa *sl = NULL; + + if (lsa->data->type == OSPF_ASBR_SUMMARY_LSA) { + sl = (struct summary_lsa *)lsa->data; + if ((GET_METRIC(sl->metric) == OSPF_LS_INFINITY) && + !CHECK_FLAG(lsa->data->options, OSPF_OPTION_DC)) + return true; + } + + return false; +} +#endif /* _ZEBRA_OSPF_LSA_H */ diff --git a/ospfd/ospf_lsdb.c b/ospfd/ospf_lsdb.c new file mode 100644 index 0000000..0111c49 --- /dev/null +++ b/ospfd/ospf_lsdb.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF LSDB support. + * Copyright (C) 1999, 2000 Alex Zinin, Kunihiro Ishiguro, Toshiaki Takada + */ + +#include + +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "log.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" + +struct ospf_lsdb *ospf_lsdb_new(void) +{ + struct ospf_lsdb *new; + + new = XCALLOC(MTYPE_OSPF_LSDB, sizeof(struct ospf_lsdb)); + ospf_lsdb_init(new); + + return new; +} + +void ospf_lsdb_init(struct ospf_lsdb *lsdb) +{ + int i; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) + lsdb->type[i].db = route_table_init(); +} + +void ospf_lsdb_free(struct ospf_lsdb *lsdb) +{ + ospf_lsdb_cleanup(lsdb); + XFREE(MTYPE_OSPF_LSDB, lsdb); +} + +void ospf_lsdb_cleanup(struct ospf_lsdb *lsdb) +{ + int i; + assert(lsdb); + assert(lsdb->total == 0); + + ospf_lsdb_delete_all(lsdb); + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) + route_table_finish(lsdb->type[i].db); +} + +void ls_prefix_set(struct prefix_ls *lp, struct ospf_lsa *lsa) +{ + if (lp && lsa && lsa->data) { + lp->family = AF_UNSPEC; + lp->prefixlen = 64; + lp->id = lsa->data->id; + lp->adv_router = lsa->data->adv_router; + } +} + +static void ospf_lsdb_delete_entry(struct ospf_lsdb *lsdb, + struct route_node *rn) +{ + struct ospf_lsa *lsa = rn->info; + + if (!lsa) + return; + + assert(rn->table == lsdb->type[lsa->data->type].db); + + if (IS_LSA_SELF(lsa)) + lsdb->type[lsa->data->type].count_self--; + lsdb->type[lsa->data->type].count--; + lsdb->type[lsa->data->type].checksum -= ntohs(lsa->data->checksum); + lsdb->total--; + + /* Decrement number of router LSAs received with DC bit set */ + if (lsa->area && (lsa->area->lsdb == lsdb) && !IS_LSA_SELF(lsa) && + (lsa->data->type == OSPF_ROUTER_LSA) && + CHECK_FLAG(lsa->data->options, OSPF_OPTION_DC)) + lsa->area->fr_info.router_lsas_recv_dc_bit--; + + /* + * If the LSA being deleted is indication LSA, then set the + * pointer to NULL. + */ + if (lsa->area && lsa->area->fr_info.indication_lsa_self && + (lsa->area->fr_info.indication_lsa_self == lsa)) + lsa->area->fr_info.indication_lsa_self = NULL; + + rn->info = NULL; + route_unlock_node(rn); +#ifdef MONITOR_LSDB_CHANGE + if (lsdb->del_lsa_hook != NULL) + (*lsdb->del_lsa_hook)(lsa); +#endif /* MONITOR_LSDB_CHANGE */ + ospf_lsa_unlock(&lsa); /* lsdb */ + return; +} + +/* Add new LSA to lsdb. */ +void ospf_lsdb_add(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) +{ + struct route_table *table; + struct prefix_ls lp; + struct route_node *rn; + + table = lsdb->type[lsa->data->type].db; + ls_prefix_set(&lp, lsa); + rn = route_node_get(table, (struct prefix *)&lp); + + /* nothing to do? */ + if (rn->info && rn->info == lsa) { + route_unlock_node(rn); + return; + } + + /* purge old entry? */ + if (rn->info) + ospf_lsdb_delete_entry(lsdb, rn); + + if (IS_LSA_SELF(lsa)) + lsdb->type[lsa->data->type].count_self++; + lsdb->type[lsa->data->type].count++; + lsdb->total++; + + /* Increment number of router LSAs received with DC bit set */ + if (lsa->area && (lsa->area->lsdb == lsdb) && !IS_LSA_SELF(lsa) && + (lsa->data->type == OSPF_ROUTER_LSA) && + CHECK_FLAG(lsa->data->options, OSPF_OPTION_DC)) + lsa->area->fr_info.router_lsas_recv_dc_bit++; + +#ifdef MONITOR_LSDB_CHANGE + if (lsdb->new_lsa_hook != NULL) + (*lsdb->new_lsa_hook)(lsa); +#endif /* MONITOR_LSDB_CHANGE */ + lsdb->type[lsa->data->type].checksum += ntohs(lsa->data->checksum); + rn->info = ospf_lsa_lock(lsa); /* lsdb */ +} + +void ospf_lsdb_delete(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) +{ + struct route_table *table; + struct prefix_ls lp; + struct route_node *rn; + + if (!lsdb || !lsa) + return; + + assert(lsa->data->type < OSPF_MAX_LSA); + table = lsdb->type[lsa->data->type].db; + ls_prefix_set(&lp, lsa); + if ((rn = route_node_lookup(table, (struct prefix *)&lp))) { + if (rn->info == lsa) + ospf_lsdb_delete_entry(lsdb, rn); + route_unlock_node(rn); /* route_node_lookup */ + } +} + +void ospf_lsdb_delete_all(struct ospf_lsdb *lsdb) +{ + struct route_table *table; + struct route_node *rn; + int i; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + table = lsdb->type[i].db; + for (rn = route_top(table); rn; rn = route_next(rn)) + if (rn->info != NULL) + ospf_lsdb_delete_entry(lsdb, rn); + } +} + +struct ospf_lsa *ospf_lsdb_lookup(struct ospf_lsdb *lsdb, struct ospf_lsa *lsa) +{ + struct route_table *table; + struct prefix_ls lp; + struct route_node *rn; + struct ospf_lsa *find; + + table = lsdb->type[lsa->data->type].db; + ls_prefix_set(&lp, lsa); + rn = route_node_lookup(table, (struct prefix *)&lp); + if (rn) { + find = rn->info; + route_unlock_node(rn); + return find; + } + return NULL; +} + +struct ospf_lsa *ospf_lsdb_lookup_by_id(struct ospf_lsdb *lsdb, uint8_t type, + struct in_addr id, + struct in_addr adv_router) +{ + struct route_table *table; + struct prefix_ls lp; + struct route_node *rn; + struct ospf_lsa *find; + + table = lsdb->type[type].db; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = id; + lp.adv_router = adv_router; + + rn = route_node_lookup(table, (struct prefix *)&lp); + if (rn) { + find = rn->info; + route_unlock_node(rn); + return find; + } + return NULL; +} + +struct ospf_lsa *ospf_lsdb_lookup_by_id_next(struct ospf_lsdb *lsdb, + uint8_t type, struct in_addr id, + struct in_addr adv_router, + int first) +{ + struct route_table *table; + struct prefix_ls lp; + struct route_node *rn; + struct ospf_lsa *find; + + table = lsdb->type[type].db; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = id; + lp.adv_router = adv_router; + + if (first) + rn = route_top(table); + else { + if ((rn = route_node_lookup(table, (struct prefix *)&lp)) + == NULL) + return NULL; + rn = route_next(rn); + } + + for (; rn; rn = route_next(rn)) + if (rn->info) + break; + + if (rn && rn->info) { + find = rn->info; + route_unlock_node(rn); + return find; + } + return NULL; +} + +unsigned long ospf_lsdb_count_all(struct ospf_lsdb *lsdb) +{ + return lsdb->total; +} + +unsigned long ospf_lsdb_count(struct ospf_lsdb *lsdb, int type) +{ + return lsdb->type[type].count; +} + +unsigned long ospf_lsdb_count_self(struct ospf_lsdb *lsdb, int type) +{ + return lsdb->type[type].count_self; +} + +unsigned int ospf_lsdb_checksum(struct ospf_lsdb *lsdb, int type) +{ + return lsdb->type[type].checksum; +} + +unsigned long ospf_lsdb_isempty(struct ospf_lsdb *lsdb) +{ + return (lsdb->total == 0); +} diff --git a/ospfd/ospf_lsdb.h b/ospfd/ospf_lsdb.h new file mode 100644 index 0000000..bf295ca --- /dev/null +++ b/ospfd/ospf_lsdb.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF LSDB support. + * Copyright (C) 1999, 2000 Alex Zinin, Kunihiro Ishiguro, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_LSDB_H +#define _ZEBRA_OSPF_LSDB_H + +/* OSPF LSDB structure. */ +struct ospf_lsdb { + struct { + unsigned long count; + unsigned long count_self; + unsigned int checksum; + struct route_table *db; + } type[OSPF_MAX_LSA]; + unsigned long total; +#define MONITOR_LSDB_CHANGE 1 /* XXX */ +#ifdef MONITOR_LSDB_CHANGE + /* Hooks for callback functions to catch every add/del event. */ + int (*new_lsa_hook)(struct ospf_lsa *); + int (*del_lsa_hook)(struct ospf_lsa *); +#endif /* MONITOR_LSDB_CHANGE */ +}; + +/* Macros. */ +#define LSDB_LOOP(T, N, L) \ + if ((T) != NULL) \ + for ((N) = route_top((T)); ((N)); ((N)) = route_next((N))) \ + if (((L) = (N)->info)) + +#define ROUTER_LSDB(A) ((A)->lsdb->type[OSPF_ROUTER_LSA].db) +#define NETWORK_LSDB(A) ((A)->lsdb->type[OSPF_NETWORK_LSA].db) +#define SUMMARY_LSDB(A) ((A)->lsdb->type[OSPF_SUMMARY_LSA].db) +#define ASBR_SUMMARY_LSDB(A) ((A)->lsdb->type[OSPF_ASBR_SUMMARY_LSA].db) +#define EXTERNAL_LSDB(O) ((O)->lsdb->type[OSPF_AS_EXTERNAL_LSA].db) +#define NSSA_LSDB(A) ((A)->lsdb->type[OSPF_AS_NSSA_LSA].db) +#define OPAQUE_LINK_LSDB(A) ((A)->lsdb->type[OSPF_OPAQUE_LINK_LSA].db) +#define OPAQUE_AREA_LSDB(A) ((A)->lsdb->type[OSPF_OPAQUE_AREA_LSA].db) +#define OPAQUE_AS_LSDB(O) ((O)->lsdb->type[OSPF_OPAQUE_AS_LSA].db) + +#define AREA_LSDB(A,T) ((A)->lsdb->type[(T)].db) +#define AS_LSDB(O,T) ((O)->lsdb->type[(T)].db) + +/* OSPF LSDB related functions. */ +extern struct ospf_lsdb *ospf_lsdb_new(void); +extern void ospf_lsdb_init(struct ospf_lsdb *); +extern void ospf_lsdb_free(struct ospf_lsdb *); +extern void ospf_lsdb_cleanup(struct ospf_lsdb *); +extern void ls_prefix_set(struct prefix_ls *lp, struct ospf_lsa *lsa); +extern void ospf_lsdb_add(struct ospf_lsdb *, struct ospf_lsa *); +extern void ospf_lsdb_delete(struct ospf_lsdb *, struct ospf_lsa *); +extern void ospf_lsdb_delete_all(struct ospf_lsdb *); +extern struct ospf_lsa *ospf_lsdb_lookup(struct ospf_lsdb *, struct ospf_lsa *); +extern struct ospf_lsa *ospf_lsdb_lookup_by_id(struct ospf_lsdb *, uint8_t, + struct in_addr, struct in_addr); +extern struct ospf_lsa *ospf_lsdb_lookup_by_id_next(struct ospf_lsdb *, uint8_t, + struct in_addr, + struct in_addr, int); +extern unsigned long ospf_lsdb_count_all(struct ospf_lsdb *); +extern unsigned long ospf_lsdb_count(struct ospf_lsdb *, int); +extern unsigned long ospf_lsdb_count_self(struct ospf_lsdb *, int); +extern unsigned int ospf_lsdb_checksum(struct ospf_lsdb *, int); +extern unsigned long ospf_lsdb_isempty(struct ospf_lsdb *); + +#endif /* _ZEBRA_OSPF_LSDB_H */ diff --git a/ospfd/ospf_main.c b/ospfd/ospf_main.c new file mode 100644 index 0000000..fdb4e5c --- /dev/null +++ b/ospfd/ospf_main.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd main routine. + * Copyright (C) 1998, 99 Kunihiro Ishiguro, Toshiaki Takada + */ + +#include + +#include +#include "bfd.h" +#include "getopt.h" +#include "frrevent.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "vrf.h" +#include "libfrr.h" +#include "routemap.h" +#include "keychain.h" +#include "libagentx.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_vty.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_ldp_sync.h" +#include "ospfd/ospf_routemap_nb.h" +#include "ospfd/ospf_apiserver.h" + +#define OSPFD_STATE_NAME "%s/ospfd.json", frr_libstatedir +#define OSPFD_INST_STATE_NAME(i) "%s/ospfd-%d.json", frr_runstatedir, i + +/* this one includes the path... because the instance number was in the path + * before :( ... which totally didn't have a mkdir anywhere. + */ +#define OSPFD_COMPAT_STATE_NAME "%s/ospfd-gr.json", frr_libstatedir +#define OSPFD_COMPAT_INST_STATE_NAME(i) \ + "%s-%d/ospfd-gr.json", frr_runstatedir, i + +/* ospfd privileges */ +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, + ZCAP_SYS_ADMIN}; + +struct zebra_privs_t ospfd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +/* OSPFd options. */ +const struct option longopts[] = { + {"instance", required_argument, NULL, 'n'}, + {"apiserver", no_argument, NULL, 'a'}, + {"apiserver_addr", required_argument, NULL, 'l'}, + {0} +}; + +/* OSPFd program name */ + +/* Master of threads. */ +struct event_loop *master; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + bfd_protocol_integration_set_shutdown(true); + ospf_terminate(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t ospf_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const ospfd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &frr_ospf_route_map_info, + &ietf_key_chain_info, + &ietf_key_chain_deviation_info, +}; + +/* actual paths filled in main() */ +static char state_path[512]; +static char state_compat_path[512]; +static char *state_paths[] = { + state_path, + state_compat_path, + NULL, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(ospfd, OSPF, + .vty_port = OSPF_VTY_PORT, + .proghelp = "Implementation of the OSPFv2 routing protocol.", + + .signals = ospf_signals, + .n_signals = array_size(ospf_signals), + + .privs = &ospfd_privs, + + .yang_modules = ospfd_yang_modules, + .n_yang_modules = array_size(ospfd_yang_modules), + + .state_paths = state_paths, +); +/* clang-format on */ + +/** Max wait time for config to load before accepting hellos */ +#define OSPF_PRE_CONFIG_MAX_WAIT_SECONDS 600 + +static void ospf_config_finish(struct event *t) +{ + zlog_err("OSPF configuration end timer expired after %d seconds.", + OSPF_PRE_CONFIG_MAX_WAIT_SECONDS); +} + +static void ospf_config_start(void) +{ + EVENT_OFF(t_ospf_cfg); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("ospfd config start callback received."); + event_add_timer(master, ospf_config_finish, NULL, + OSPF_PRE_CONFIG_MAX_WAIT_SECONDS, &t_ospf_cfg); +} + +static void ospf_config_end(void) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("ospfd config end callback received."); + + EVENT_OFF(t_ospf_cfg); +} + +/* OSPFd main routine. */ +int main(int argc, char **argv) +{ + frr_preinit(&ospfd_di, argc, argv); + frr_opt_add("n:al:", longopts, + " -n, --instance Set the instance id\n" + " -a, --apiserver Enable OSPF apiserver\n" + " -l, --apiserver_addr Set OSPF apiserver bind address\n"); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 'n': + ospfd_di.instance = ospf_instance = atoi(optarg); + if (ospf_instance < 1) + exit(0); + break; + case 0: + break; +#ifdef SUPPORT_OSPF_API + case 'a': + ospf_apiserver_enable = 1; + break; + case 'l': + if (inet_pton(AF_INET, optarg, &ospf_apiserver_addr) <= + 0) { + zlog_err("OSPF: Invalid API Server IPv4 address %s specified", + optarg); + exit(0); + } + break; +#endif /* SUPPORT_OSPF_API */ + default: + frr_help_exit(1); + } + } + + /* Invoked by a priviledged user? -- endo. */ + if (geteuid() != 0) { + errno = EPERM; + perror(ospfd_di.progname); + exit(1); + } + + if (ospf_instance) { + snprintf(state_path, sizeof(state_path), + OSPFD_INST_STATE_NAME(ospf_instance)); + snprintf(state_compat_path, sizeof(state_compat_path), + OSPFD_COMPAT_INST_STATE_NAME(ospf_instance)); + } else { + snprintf(state_path, sizeof(state_path), OSPFD_STATE_NAME); + snprintf(state_compat_path, sizeof(state_compat_path), + OSPFD_COMPAT_STATE_NAME); + } + + /* OSPF master init. */ + ospf_master_init(frr_init()); + + /* Initializations. */ + master = om->master; + + /* Library inits. */ + libagentx_init(); + ospf_debug_init(); + ospf_vrf_init(); + + access_list_init(); + prefix_list_init(); + keychain_init(); + + /* Configuration processing callback initialization. */ + cmd_init_config_callbacks(ospf_config_start, ospf_config_end); + + /* OSPFd inits. */ + ospf_if_init(); + ospf_zebra_init(master, ospf_instance); + + /* OSPF vty inits. */ + ospf_vty_init(); + ospf_vty_show_init(); + ospf_vty_clear_init(); + + /* OSPF BFD init */ + ospf_bfd_init(master); + + /* OSPF LDP IGP Sync init */ + ospf_ldp_sync_init(); + + ospf_route_map_init(); + ospf_opaque_init(); + ospf_gr_init(); + ospf_gr_helper_init(); + + /* OSPF errors init */ + ospf_error_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/ospfd/ospf_memory.c b/ospfd/ospf_memory.c new file mode 100644 index 0000000..9854c8c --- /dev/null +++ b/ospfd/ospf_memory.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ospfd memory type definitions + * + * Copyright (C) 2015 David Lamparter + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ospf_memory.h" + +DEFINE_MGROUP(OSPFD, "ospfd"); +DEFINE_MTYPE(OSPFD, OSPF_TOP, "OSPF top"); +DEFINE_MTYPE(OSPFD, OSPF_AREA, "OSPF area"); +DEFINE_MTYPE(OSPFD, OSPF_AREA_RANGE, "OSPF area range"); +DEFINE_MTYPE(OSPFD, OSPF_NETWORK, "OSPF network"); +DEFINE_MTYPE(OSPFD, OSPF_NEIGHBOR_STATIC, "OSPF static nbr"); +DEFINE_MTYPE(OSPFD, OSPF_IF, "OSPF interface"); +DEFINE_MTYPE(OSPFD, OSPF_NEIGHBOR, "OSPF neighbor"); +DEFINE_MTYPE(OSPFD, OSPF_ROUTE, "OSPF route"); +DEFINE_MTYPE(OSPFD, OSPF_TMP, "OSPF tmp mem"); +DEFINE_MTYPE(OSPFD, OSPF_LSA, "OSPF LSA"); +DEFINE_MTYPE(OSPFD, OSPF_LSA_DATA, "OSPF LSA data"); +DEFINE_MTYPE(OSPFD, OSPF_LSDB, "OSPF LSDB"); +DEFINE_MTYPE(OSPFD, OSPF_PACKET, "OSPF packet"); +DEFINE_MTYPE(OSPFD, OSPF_FIFO, "OSPF FIFO queue"); +DEFINE_MTYPE(OSPFD, OSPF_VERTEX, "OSPF vertex"); +DEFINE_MTYPE(OSPFD, OSPF_VERTEX_PARENT, "OSPF vertex parent"); +DEFINE_MTYPE(OSPFD, OSPF_NEXTHOP, "OSPF nexthop"); +DEFINE_MTYPE(OSPFD, OSPF_PATH, "OSPF path"); +DEFINE_MTYPE(OSPFD, OSPF_VL_DATA, "OSPF VL data"); +DEFINE_MTYPE(OSPFD, OSPF_CRYPT_KEY, "OSPF crypt key"); +DEFINE_MTYPE(OSPFD, OSPF_EXTERNAL_INFO, "OSPF ext. info"); +DEFINE_MTYPE(OSPFD, OSPF_DISTANCE, "OSPF distance"); +DEFINE_MTYPE(OSPFD, OSPF_IF_INFO, "OSPF if info"); +DEFINE_MTYPE(OSPFD, OSPF_IF_PARAMS, "OSPF if params"); +DEFINE_MTYPE(OSPFD, OSPF_MESSAGE, "OSPF message"); +DEFINE_MTYPE(OSPFD, OSPF_MPLS_TE, "OSPF MPLS parameters"); +DEFINE_MTYPE(OSPFD, OSPF_ROUTER_INFO, "OSPF Router Info parameters"); +DEFINE_MTYPE(OSPFD, OSPF_PCE_PARAMS, "OSPF PCE parameters"); +DEFINE_MTYPE(OSPFD, OSPF_EXT_PARAMS, "OSPF Extended parameters"); +DEFINE_MTYPE(OSPFD, OSPF_SR_PARAMS, "OSPF Segment Routing parameters"); +DEFINE_MTYPE(OSPFD, OSPF_GR_HELPER, "OSPF Graceful Restart Helper"); +DEFINE_MTYPE(OSPFD, OSPF_EXTERNAL_RT_AGGR, "OSPF External Route Summarisation"); +DEFINE_MTYPE(OSPFD, OSPF_P_SPACE, "OSPF TI-LFA P-Space"); +DEFINE_MTYPE(OSPFD, OSPF_Q_SPACE, "OSPF TI-LFA Q-Space"); diff --git a/ospfd/ospf_memory.h b/ospfd/ospf_memory.h new file mode 100644 index 0000000..d11b69a --- /dev/null +++ b/ospfd/ospf_memory.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ospfd memory type declarations + * + * Copyright (C) 2015 David Lamparter + */ + +#ifndef _QUAGGA_OSPF_MEMORY_H +#define _QUAGGA_OSPF_MEMORY_H + +#include "memory.h" + +DECLARE_MGROUP(OSPFD); +DECLARE_MTYPE(OSPF_TOP); +DECLARE_MTYPE(OSPF_AREA); +DECLARE_MTYPE(OSPF_AREA_RANGE); +DECLARE_MTYPE(OSPF_NETWORK); +DECLARE_MTYPE(OSPF_NEIGHBOR_STATIC); +DECLARE_MTYPE(OSPF_IF); +DECLARE_MTYPE(OSPF_NEIGHBOR); +DECLARE_MTYPE(OSPF_ROUTE); +DECLARE_MTYPE(OSPF_TMP); +DECLARE_MTYPE(OSPF_LSA); +DECLARE_MTYPE(OSPF_LSA_DATA); +DECLARE_MTYPE(OSPF_LSDB); +DECLARE_MTYPE(OSPF_PACKET); +DECLARE_MTYPE(OSPF_FIFO); +DECLARE_MTYPE(OSPF_VERTEX); +DECLARE_MTYPE(OSPF_VERTEX_PARENT); +DECLARE_MTYPE(OSPF_NEXTHOP); +DECLARE_MTYPE(OSPF_PATH); +DECLARE_MTYPE(OSPF_VL_DATA); +DECLARE_MTYPE(OSPF_CRYPT_KEY); +DECLARE_MTYPE(OSPF_EXTERNAL_INFO); +DECLARE_MTYPE(OSPF_DISTANCE); +DECLARE_MTYPE(OSPF_IF_INFO); +DECLARE_MTYPE(OSPF_IF_PARAMS); +DECLARE_MTYPE(OSPF_MESSAGE); +DECLARE_MTYPE(OSPF_MPLS_TE); +DECLARE_MTYPE(OSPF_ROUTER_INFO); +DECLARE_MTYPE(OSPF_PCE_PARAMS); +DECLARE_MTYPE(OSPF_SR_PARAMS); +DECLARE_MTYPE(OSPF_EXT_PARAMS); +DECLARE_MTYPE(OSPF_GR_HELPER); +DECLARE_MTYPE(OSPF_EXTERNAL_RT_AGGR); +DECLARE_MTYPE(OSPF_P_SPACE); +DECLARE_MTYPE(OSPF_Q_SPACE); + +#endif /* _QUAGGA_OSPF_MEMORY_H */ diff --git a/ospfd/ospf_neighbor.c b/ospfd/ospf_neighbor.c new file mode 100644 index 0000000..d47d581 --- /dev/null +++ b/ospfd/ospf_neighbor.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Neighbor functions. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "lib/bfd.h" +#include "linklist.h" +#include "prefix.h" +#include "memory.h" +#include "command.h" +#include "frrevent.h" +#include "stream.h" +#include "table.h" +#include "log.h" +#include "json.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" + +/* Fill in the the 'key' as appropriate to retrieve the entry for nbr + * from the ospf_interface's nbrs table. Indexed by interface address + * for all cases except Virtual-link and PointToPoint interfaces, where + * neighbours are indexed by router-ID instead. + */ +static void ospf_nbr_key(struct ospf_interface *oi, struct ospf_neighbor *nbr, + struct prefix *key) +{ + key->family = AF_INET; + key->prefixlen = IPV4_MAX_BITLEN; + + /* vlinks are indexed by router-id */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK + || oi->type == OSPF_IFTYPE_POINTOPOINT) + key->u.prefix4 = nbr->router_id; + else + key->u.prefix4 = nbr->src; + return; +} + +struct ospf_neighbor *ospf_nbr_new(struct ospf_interface *oi) +{ + struct ospf_neighbor *nbr; + + /* Allcate new neighbor. */ + nbr = XCALLOC(MTYPE_OSPF_NEIGHBOR, sizeof(struct ospf_neighbor)); + + /* Relate neighbor to the interface. */ + nbr->oi = oi; + + /* Set default values. */ + nbr->state = NSM_Down; + + /* Set inheritance values. */ + nbr->v_inactivity = OSPF_IF_PARAM(oi, v_wait); + nbr->v_db_desc = OSPF_IF_PARAM(oi, retransmit_interval); + nbr->v_ls_req = OSPF_IF_PARAM(oi, retransmit_interval); + nbr->v_ls_upd = OSPF_IF_PARAM(oi, retransmit_interval); + nbr->priority = -1; + + /* DD flags. */ + nbr->dd_flags = OSPF_DD_FLAG_MS | OSPF_DD_FLAG_M | OSPF_DD_FLAG_I; + + /* Last received and sent DD. */ + nbr->last_send = NULL; + + nbr->nbr_nbma = NULL; + + ospf_lsdb_init(&nbr->db_sum); + ospf_lsdb_init(&nbr->ls_rxmt); + ospf_lsdb_init(&nbr->ls_req); + + nbr->crypt_seqnum = 0; + + /* Initialize GR Helper info*/ + nbr->gr_helper_info.recvd_grace_period = 0; + nbr->gr_helper_info.actual_grace_period = 0; + nbr->gr_helper_info.gr_helper_status = OSPF_GR_NOT_HELPER; + nbr->gr_helper_info.helper_exit_reason = OSPF_GR_HELPER_EXIT_NONE; + nbr->gr_helper_info.gr_restart_reason = OSPF_GR_UNKNOWN_RESTART; + + return nbr; +} + +void ospf_nbr_free(struct ospf_neighbor *nbr) +{ + /* Free DB summary list. */ + if (ospf_db_summary_count(nbr)) + ospf_db_summary_clear(nbr); + /* ospf_db_summary_delete_all (nbr); */ + + /* Free ls request list. */ + if (ospf_ls_request_count(nbr)) + ospf_ls_request_delete_all(nbr); + + /* Free retransmit list. */ + if (ospf_ls_retransmit_count(nbr)) + ospf_ls_retransmit_clear(nbr); + + /* Cleanup LSDBs. */ + ospf_lsdb_cleanup(&nbr->db_sum); + ospf_lsdb_cleanup(&nbr->ls_req); + ospf_lsdb_cleanup(&nbr->ls_rxmt); + + /* Clear last send packet. */ + if (nbr->last_send) + ospf_packet_free(nbr->last_send); + + if (nbr->nbr_nbma) { + nbr->nbr_nbma->nbr = NULL; + nbr->nbr_nbma = NULL; + } + + /* Cancel all timers. */ + EVENT_OFF(nbr->t_inactivity); + EVENT_OFF(nbr->t_db_desc); + EVENT_OFF(nbr->t_ls_req); + EVENT_OFF(nbr->t_ls_upd); + + /* Cancel all events. */ /* Thread lookup cost would be negligible. */ + event_cancel_event(master, nbr); + + bfd_sess_free(&nbr->bfd_session); + + EVENT_OFF(nbr->gr_helper_info.t_grace_timer); + + nbr->oi = NULL; + XFREE(MTYPE_OSPF_NEIGHBOR, nbr); +} + +/* Delete specified OSPF neighbor from interface. */ +void ospf_nbr_delete(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi; + struct route_node *rn; + struct prefix p; + + oi = nbr->oi; + + /* get appropriate prefix 'key' */ + ospf_nbr_key(oi, nbr, &p); + + rn = route_node_lookup(oi->nbrs, &p); + if (rn) { + /* If lookup for a NBR succeeds, the leaf route_node could + * only exist because there is (or was) a nbr there. + * If the nbr was deleted, the leaf route_node should have + * lost its last refcount too, and be deleted. + * Therefore a looked-up leaf route_node in nbrs table + * should never have NULL info. + */ + assert(rn->info); + + if (rn->info) { + rn->info = NULL; + route_unlock_node(rn); + } else + zlog_info("Can't find neighbor %pI4 in the interface %s", + &nbr->src, IF_NAME(oi)); + + route_unlock_node(rn); + } else { + /* + * This neighbor was not found, but before we move on and + * free the neighbor structre, make sure that it was not + * indexed incorrectly and ended up in the "worng" place + */ + + /* Reverse the lookup rules */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK + || oi->type == OSPF_IFTYPE_POINTOPOINT) + p.u.prefix4 = nbr->src; + else + p.u.prefix4 = nbr->router_id; + + rn = route_node_lookup(oi->nbrs, &p); + if (rn) { + /* We found the neighbor! + * Now make sure it is not the exact same neighbor + * structure that we are about to free + */ + if (nbr == rn->info) { + /* Same neighbor, drop the reference to it */ + rn->info = NULL; + route_unlock_node(rn); + } + route_unlock_node(rn); + } + } + + /* Free ospf_neighbor structure. */ + ospf_nbr_free(nbr); +} + +/* Check myself is in the neighbor list. */ +int ospf_nbr_bidirectional(struct in_addr *router_id, struct in_addr *neighbors, + int size) +{ + int i; + int max; + + max = size / sizeof(struct in_addr); + + for (i = 0; i < max; i++) + if (IPV4_ADDR_SAME(router_id, &neighbors[i])) + return 1; + + return 0; +} + +/* reset nbr_self */ +void ospf_nbr_self_reset(struct ospf_interface *oi, struct in_addr router_id) +{ + if (oi->nbr_self) + ospf_nbr_delete(oi->nbr_self); + + oi->nbr_self = ospf_nbr_new(oi); + ospf_nbr_add_self(oi, router_id); +} + +/* Add self to nbr list. */ +void ospf_nbr_add_self(struct ospf_interface *oi, struct in_addr router_id) +{ + struct prefix p; + struct route_node *rn; + + if (!oi->nbr_self) + oi->nbr_self = ospf_nbr_new(oi); + + /* Initial state */ + oi->nbr_self->address = *oi->address; + oi->nbr_self->priority = OSPF_IF_PARAM(oi, priority); + oi->nbr_self->router_id = router_id; + oi->nbr_self->src = oi->address->u.prefix4; + oi->nbr_self->state = NSM_TwoWay; + + switch (oi->area->external_routing) { + case OSPF_AREA_DEFAULT: + SET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); + break; + case OSPF_AREA_STUB: + UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); + break; + case OSPF_AREA_NSSA: + UNSET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); + SET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); + break; + } + + /* Add nbr_self to nbrs table */ + ospf_nbr_key(oi, oi->nbr_self, &p); + + rn = route_node_get(oi->nbrs, &p); + if (rn->info) { + /* There is already pseudo neighbor. */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "router_id %pI4 already present in neighbor table. node refcount %u", + &router_id, route_node_get_lock_count(rn)); + route_unlock_node(rn); + } else + rn->info = oi->nbr_self; +} + +/* Get neighbor count by status. + Specify status = 0, get all neighbor other than myself. */ +int ospf_nbr_count(struct ospf_interface *oi, int state) +{ + struct ospf_neighbor *nbr; + struct route_node *rn; + int count = 0; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info)) + if (!IPV4_ADDR_SAME(&nbr->router_id, + &oi->ospf->router_id)) + if (state == 0 || nbr->state == state) + count++; + + return count; +} + +int ospf_nbr_count_opaque_capable(struct ospf_interface *oi) +{ + struct ospf_neighbor *nbr; + struct route_node *rn; + int count = 0; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info)) + if (!IPV4_ADDR_SAME(&nbr->router_id, + &oi->ospf->router_id)) + if (nbr->state == NSM_Full) + if (CHECK_FLAG(nbr->options, + OSPF_OPTION_O)) + count++; + + return count; +} + +/* lookup nbr by address - use this only if you know you must + * otherwise use the ospf_nbr_lookup() wrapper, which deals + * with virtual link and PointToPoint neighbours + */ +struct ospf_neighbor *ospf_nbr_lookup_by_addr(struct route_table *nbrs, + struct in_addr *addr) +{ + struct prefix p; + struct route_node *rn; + struct ospf_neighbor *nbr; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = *addr; + + rn = route_node_lookup(nbrs, &p); + if (!rn) + return NULL; + + /* See comment in ospf_nbr_delete */ + assert(rn->info); + + if (rn->info == NULL) { + route_unlock_node(rn); + return NULL; + } + + nbr = (struct ospf_neighbor *)rn->info; + route_unlock_node(rn); + + return nbr; +} + +struct ospf_neighbor *ospf_nbr_lookup_by_routerid(struct route_table *nbrs, + struct in_addr *id) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + + for (rn = route_top(nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL) + if (IPV4_ADDR_SAME(&nbr->router_id, id)) { + route_unlock_node(rn); + return nbr; + } + + return NULL; +} + +void ospf_renegotiate_optional_capabilities(struct ospf *top) +{ + struct listnode *node; + struct ospf_interface *oi; + struct route_table *nbrs; + struct route_node *rn; + struct ospf_neighbor *nbr; + uint8_t shutdown_save = top->inst_shutdown; + + /* At first, flush self-originated LSAs from routing domain. */ + ospf_flush_self_originated_lsas_now(top); + + /* ospf_flush_self_originated_lsas_now is primarily intended for shut + * down scenarios. Reset the inst_shutdown flag that it sets. We are + * just changing configuration, and the flag can change the scheduling + * of when maxage LSAs are sent. */ + top->inst_shutdown = shutdown_save; + + /* Revert all neighbor status to ExStart. */ + for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) { + if ((nbrs = oi->nbrs) == NULL) + continue; + + for (rn = route_top(nbrs); rn; rn = route_next(rn)) { + if ((nbr = rn->info) == NULL || nbr == oi->nbr_self) + continue; + + if (nbr->state < NSM_ExStart) + continue; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Renegotiate optional capabilities with neighbor(%pI4)", + &nbr->router_id); + + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + } + } + + /* Refresh/Re-originate external LSAs (Type-7 and Type-5).*/ + ospf_external_lsa_rid_change(top); + + return; +} + + +struct ospf_neighbor *ospf_nbr_lookup(struct ospf_interface *oi, struct ip *iph, + struct ospf_header *ospfh) +{ + struct in_addr srcaddr = iph->ip_src; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK + || oi->type == OSPF_IFTYPE_POINTOPOINT) + return (ospf_nbr_lookup_by_routerid(oi->nbrs, + &ospfh->router_id)); + else + return (ospf_nbr_lookup_by_addr(oi->nbrs, &srcaddr)); +} + +static struct ospf_neighbor *ospf_nbr_add(struct ospf_interface *oi, + struct ospf_header *ospfh, + struct prefix *p) +{ + struct ospf_neighbor *nbr; + + nbr = ospf_nbr_new(oi); + nbr->state = NSM_Down; + nbr->src = p->u.prefix4; + memcpy(&nbr->address, p, sizeof(struct prefix)); + + nbr->nbr_nbma = NULL; + if (OSPF_IF_NON_BROADCAST(oi)) { + struct ospf_nbr_nbma *nbr_nbma; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, node, nbr_nbma)) { + if (IPV4_ADDR_SAME(&nbr_nbma->addr, &nbr->src)) { + nbr_nbma->nbr = nbr; + nbr->nbr_nbma = nbr_nbma; + + if (nbr_nbma->t_poll) + EVENT_OFF(nbr_nbma->t_poll); + + nbr->state_change = nbr_nbma->state_change + 1; + } + } + } + + /* New nbr, save the crypto sequence number if necessary */ + if (ntohs(ospfh->auth_type) == OSPF_AUTH_CRYPTOGRAPHIC) + nbr->crypt_seqnum = ospfh->u.crypt.crypt_seqnum; + + /* Configure BFD if interface has it. */ + ospf_neighbor_bfd_apply(nbr); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("NSM[%s:%pI4]: start", IF_NAME(oi), + &nbr->router_id); + + return nbr; +} + +struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *oi, + struct ospf_header *ospfh, struct ip *iph, + struct prefix *p) +{ + struct route_node *rn; + struct prefix key; + struct ospf_neighbor *nbr; + + key.family = AF_INET; + key.prefixlen = IPV4_MAX_BITLEN; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK + || oi->type == OSPF_IFTYPE_POINTOPOINT) + key.u.prefix4 = ospfh->router_id; /* index vlink and ptp nbrs by + router-id */ + else + key.u.prefix4 = iph->ip_src; + + rn = route_node_get(oi->nbrs, &key); + if (rn->info) { + route_unlock_node(rn); + nbr = rn->info; + + if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->state == NSM_Attempt) { + nbr->src = iph->ip_src; + memcpy(&nbr->address, p, sizeof(struct prefix)); + } + } else { + rn->info = nbr = ospf_nbr_add(oi, ospfh, p); + } + + nbr->router_id = ospfh->router_id; + + return nbr; +} diff --git a/ospfd/ospf_neighbor.h b/ospfd/ospf_neighbor.h new file mode 100644 index 0000000..07d095f --- /dev/null +++ b/ospfd/ospf_neighbor.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Neighbor functions. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_NEIGHBOR_H +#define _ZEBRA_OSPF_NEIGHBOR_H + +#include +#include + +/* Neighbor Data Structure */ +struct ospf_neighbor { + /* This neighbor's parent ospf interface. */ + struct ospf_interface *oi; + + /* OSPF neighbor Information */ + uint8_t state; /* NSM status. */ + uint8_t dd_flags; /* DD bit flags. */ + uint32_t dd_seqnum; /* DD Sequence Number. */ + + /* Neighbor Information from Hello. */ + struct prefix address; /* Neighbor Interface Address. */ + + struct in_addr src; /* Src address. */ + struct in_addr router_id; /* Router ID. */ + uint8_t options; /* Options. */ + int priority; /* Router Priority. */ + struct in_addr d_router; /* Designated Router. */ + struct in_addr bd_router; /* Backup Designated Router. */ + + /* Last sent Database Description packet. */ + struct ospf_packet *last_send; + /* Timestemp when last Database Description packet was sent */ + struct timeval last_send_ts; + + /* Last received Databse Description packet. */ + struct { + uint8_t options; + uint8_t flags; + uint32_t dd_seqnum; + } last_recv; + + /* LSA data. */ + struct ospf_lsdb ls_rxmt; + struct ospf_lsdb db_sum; + struct ospf_lsdb ls_req; + struct ospf_lsa *ls_req_last; + + uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */ + + /* Timer values. */ + uint32_t v_inactivity; + uint32_t v_db_desc; + uint32_t v_ls_req; + uint32_t v_ls_upd; + + /* Threads. */ + struct event *t_inactivity; + struct event *t_db_desc; + struct event *t_ls_req; + struct event *t_ls_upd; + struct event *t_hello_reply; + + /* NBMA configured neighbour */ + struct ospf_nbr_nbma *nbr_nbma; + + /* Statistics */ + struct timeval ts_last_progress; /* last advance of NSM */ + struct timeval ts_last_regress; /* last regressive NSM change */ + const char *last_regress_str; /* Event which last regressed NSM */ + uint32_t state_change; /* NSM state change counter */ + + /* BFD information */ + struct bfd_session_params *bfd_session; + + /* ospf graceful restart HELPER info */ + struct ospf_helper_info gr_helper_info; +}; + +/* Macros. */ +#define NBR_IS_DR(n) IPV4_ADDR_SAME (&n->address.u.prefix4, &n->d_router) +#define NBR_IS_BDR(n) IPV4_ADDR_SAME (&n->address.u.prefix4, &n->bd_router) + +/* Prototypes. */ +extern struct ospf_neighbor *ospf_nbr_new(struct ospf_interface *); +extern void ospf_nbr_free(struct ospf_neighbor *); +extern void ospf_nbr_delete(struct ospf_neighbor *); +extern int ospf_nbr_bidirectional(struct in_addr *, struct in_addr *, int); +extern void ospf_nbr_self_reset(struct ospf_interface *, struct in_addr); +extern void ospf_nbr_add_self(struct ospf_interface *, struct in_addr); +extern int ospf_nbr_count(struct ospf_interface *, int); +extern int ospf_nbr_count_opaque_capable(struct ospf_interface *); +extern struct ospf_neighbor *ospf_nbr_get(struct ospf_interface *, + struct ospf_header *, struct ip *, + struct prefix *); +extern struct ospf_neighbor *ospf_nbr_lookup(struct ospf_interface *, + struct ip *, struct ospf_header *); +extern struct ospf_neighbor *ospf_nbr_lookup_by_addr(struct route_table *, + struct in_addr *); +extern struct ospf_neighbor *ospf_nbr_lookup_by_routerid(struct route_table *, + struct in_addr *); +extern void ospf_renegotiate_optional_capabilities(struct ospf *top); +#endif /* _ZEBRA_OSPF_NEIGHBOR_H */ diff --git a/ospfd/ospf_network.c b/ospfd/ospf_network.c new file mode 100644 index 0000000..801f75a --- /dev/null +++ b/ospfd/ospf_network.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF network related functions + * Copyright (C) 1999 Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "sockunion.h" +#include "log.h" +#include "sockopt.h" +#include "privs.h" +#include "lib_errors.h" +#include "lib/table.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_dump.h" + +/* Join to the OSPF ALL SPF ROUTERS multicast group. */ +int ospf_if_add_allspfrouters(struct ospf *top, struct prefix *p, + ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(top->fd, IP_ADD_MEMBERSHIP, + p->u.prefix4, htonl(OSPF_ALLSPFROUTERS), + ifindex); + if (ret < 0) + flog_err( + EC_LIB_SOCKET, + "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllSPFRouters): %s; perhaps a kernel limit on # of multicast group memberships has been exceeded?", + top->fd, &p->u.prefix4, ifindex, + safe_strerror(errno)); + else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "interface %pI4 [%u] join AllSPFRouters Multicast group.", + &p->u.prefix4, ifindex); + } + + return ret; +} + +int ospf_if_drop_allspfrouters(struct ospf *top, struct prefix *p, + ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(top->fd, IP_DROP_MEMBERSHIP, + p->u.prefix4, htonl(OSPF_ALLSPFROUTERS), + ifindex); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllSPFRouters): %s", + top->fd, &p->u.prefix4, ifindex, + safe_strerror(errno)); + else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "interface %pI4 [%u] leave AllSPFRouters Multicast group.", + &p->u.prefix4, ifindex); + } + + return ret; +} + +/* Join to the OSPF ALL Designated ROUTERS multicast group. */ +int ospf_if_add_alldrouters(struct ospf *top, struct prefix *p, + ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(top->fd, IP_ADD_MEMBERSHIP, + p->u.prefix4, htonl(OSPF_ALLDROUTERS), + ifindex); + if (ret < 0) + flog_err( + EC_LIB_SOCKET, + "can't setsockopt IP_ADD_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllDRouters): %s; perhaps a kernel limit on # of multicast group memberships has been exceeded?", + top->fd, &p->u.prefix4, ifindex, + safe_strerror(errno)); + else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "interface %pI4 [%u] join AllDRouters Multicast group.", + &p->u.prefix4, ifindex); + } + return ret; +} + +int ospf_if_drop_alldrouters(struct ospf *top, struct prefix *p, + ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(top->fd, IP_DROP_MEMBERSHIP, + p->u.prefix4, htonl(OSPF_ALLDROUTERS), + ifindex); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IP_DROP_MEMBERSHIP (fd %d, addr %pI4, ifindex %u, AllDRouters): %s", + top->fd, &p->u.prefix4, ifindex, + safe_strerror(errno)); + else if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "interface %pI4 [%u] leave AllDRouters Multicast group.", + &p->u.prefix4, ifindex); + + return ret; +} + +int ospf_if_ipmulticast(int fd, struct prefix *p, ifindex_t ifindex) +{ + uint8_t val; + int ret, len; + + /* Prevent receiving self-origined multicast packets. */ + ret = setsockopt_ipv4_multicast_loop(fd, 0); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IP_MULTICAST_LOOP(0) for fd %d: %s", + fd, safe_strerror(errno)); + + /* Explicitly set multicast ttl to 1 -- endo. */ + val = 1; + len = sizeof(val); + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&val, len); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IP_MULTICAST_TTL(1) for fd %d: %s", + fd, safe_strerror(errno)); +#ifndef GNU_LINUX + /* For GNU LINUX ospf_write uses IP_PKTINFO, in_pktinfo to send + * packet out of ifindex. Below would be used Non Linux system. + */ + ret = setsockopt_ipv4_multicast_if(fd, p->u.prefix4, ifindex); + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "can't setsockopt IP_MULTICAST_IF(fd %d, addr %pI4, ifindex %u): %s", + fd, &p->u.prefix4, ifindex, + safe_strerror(errno)); +#endif + + return ret; +} + +/* + * Helper to open and set up a socket; returns the new fd on success, + * -1 on error. + */ +static int sock_init_common(vrf_id_t vrf_id, const char *name, int proto, + int *pfd) +{ + int ospf_sock; + int ret, hincl = 1; + + if (vrf_id == VRF_UNKNOWN) { + /* silently return since VRF is not ready */ + return -1; + } + + frr_with_privs(&ospfd_privs) { + ospf_sock = vrf_socket(AF_INET, SOCK_RAW, proto, vrf_id, name); + if (ospf_sock < 0) { + flog_err(EC_LIB_SOCKET, "%s: socket: %s", __func__, + safe_strerror(errno)); + return -1; + } + +#ifdef IP_HDRINCL + /* we will include IP header with packet */ + ret = setsockopt(ospf_sock, IPPROTO_IP, IP_HDRINCL, &hincl, + sizeof(hincl)); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "Can't set IP_HDRINCL option for fd %d: %s", + ospf_sock, safe_strerror(errno)); + break; + } +#elif defined(IPTOS_PREC_INTERNETCONTROL) +#warning "IP_HDRINCL not available on this system" +#warning "using IPTOS_PREC_INTERNETCONTROL" + ret = setsockopt_ipv4_tos(ospf_sock, + IPTOS_PREC_INTERNETCONTROL); + if (ret < 0) { + flog_err(EC_LIB_SOCKET, + "can't set sockopt IP_TOS %d to socket %d: %s", + tos, ospf_sock, safe_strerror(errno)); + break; + } +#else /* !IPTOS_PREC_INTERNETCONTROL */ +#warning "IP_HDRINCL not available, nor is IPTOS_PREC_INTERNETCONTROL" + flog_err(EC_LIB_UNAVAILABLE, "IP_HDRINCL option not available"); +#endif /* IP_HDRINCL */ + + ret = setsockopt_ifindex(AF_INET, ospf_sock, 1); + + if (ret < 0) + flog_err(EC_LIB_SOCKET, + "Can't set pktinfo option for fd %d", + ospf_sock); + } + + *pfd = ospf_sock; + + return ret; +} + +/* + * Update a socket bufsize(s), based on its ospf instance + */ +void ospf_sock_bufsize_update(const struct ospf *ospf, int sock, + enum ospf_sock_type_e type) +{ + int bufsize; + + if (type == OSPF_SOCK_BOTH || type == OSPF_SOCK_RECV) { + bufsize = ospf->recv_sock_bufsize; + setsockopt_so_recvbuf(sock, bufsize); + } + + if (type == OSPF_SOCK_BOTH || type == OSPF_SOCK_SEND) { + bufsize = ospf->send_sock_bufsize; + setsockopt_so_sendbuf(sock, bufsize); + } +} + +int ospf_sock_init(struct ospf *ospf) +{ + int ret; + + /* silently ignore. already done */ + if (ospf->fd > 0) + return -1; + + ret = sock_init_common(ospf->vrf_id, ospf->name, IPPROTO_OSPFIGP, + &(ospf->fd)); + + if (ret >= 0) /* Update socket buffer sizes */ + ospf_sock_bufsize_update(ospf, ospf->fd, OSPF_SOCK_BOTH); + + return ret; +} + +/* + * Open per-interface write socket + */ +int ospf_ifp_sock_init(struct interface *ifp) +{ + struct ospf_if_info *oii; + struct ospf_interface *oi = NULL; + struct ospf *ospf = NULL; + struct route_node *rn; + int ret; + + oii = IF_OSPF_IF_INFO(ifp); + if (oii == NULL) + return -1; + + if (oii->oii_fd > 0) + return 0; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + if (rn && rn->info) { + oi = rn->info; + ospf = oi->ospf; + break; + } + } + + if (ospf == NULL) + return -1; + + ret = sock_init_common(ifp->vrf->vrf_id, ifp->name, IPPROTO_OSPFIGP, + &oii->oii_fd); + + if (ret >= 0) { /* Update socket buffer sizes */ + /* Write-only, so no recv buf */ + setsockopt_so_recvbuf(oii->oii_fd, 0); + + ospf_sock_bufsize_update(ospf, oii->oii_fd, OSPF_SOCK_SEND); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ifp %s, oii %p, fd %d", __func__, ifp->name, + oii, oii->oii_fd); + + return ret; +} + +/* + * Close per-interface write socket + */ +int ospf_ifp_sock_close(struct interface *ifp) +{ + struct ospf_if_info *oii; + + oii = IF_OSPF_IF_INFO(ifp); + if (oii == NULL) + return 0; + + if (oii->oii_fd > 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ifp %s, oii %p, fd %d", __func__, + ifp->name, oii, oii->oii_fd); + + close(oii->oii_fd); + oii->oii_fd = -1; + } + + return 0; +} diff --git a/ospfd/ospf_network.h b/ospfd/ospf_network.h new file mode 100644 index 0000000..b810bad --- /dev/null +++ b/ospfd/ospf_network.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF network related functions. + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_NETWORK_H +#define _ZEBRA_OSPF_NETWORK_H + +/* Prototypes. */ +extern int ospf_if_add_allspfrouters(struct ospf *, struct prefix *, ifindex_t); +extern int ospf_if_drop_allspfrouters(struct ospf *, struct prefix *, + ifindex_t); +extern int ospf_if_add_alldrouters(struct ospf *, struct prefix *, ifindex_t); +extern int ospf_if_drop_alldrouters(struct ospf *, struct prefix *, ifindex_t); +extern int ospf_if_ipmulticast(int fd, struct prefix *, ifindex_t); +extern int ospf_sock_init(struct ospf *ospf); +/* Open, close per-interface write socket */ +int ospf_ifp_sock_init(struct interface *ifp); +int ospf_ifp_sock_close(struct interface *ifp); + +enum ospf_sock_type_e { + OSPF_SOCK_NONE = 0, + OSPF_SOCK_RECV, + OSPF_SOCK_SEND, + OSPF_SOCK_BOTH +}; + +void ospf_sock_bufsize_update(const struct ospf *ospf, int sock, + enum ospf_sock_type_e type); + +#endif /* _ZEBRA_OSPF_NETWORK_H */ diff --git a/ospfd/ospf_nsm.c b/ospfd/ospf_nsm.c new file mode 100644 index 0000000..c466ddc --- /dev/null +++ b/ospfd/ospf_nsm.c @@ -0,0 +1,875 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF version 2 Neighbor State Machine + * From RFC2328 [OSPF Version 2] + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "memory.h" +#include "hash.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "stream.h" +#include "table.h" +#include "log.h" +#include "command.h" +#include "network.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_errors.h" + +DEFINE_HOOK(ospf_nsm_change, + (struct ospf_neighbor * on, int state, int oldstate), + (on, state, oldstate)); + +static void nsm_clear_adj(struct ospf_neighbor *); + +/* OSPF NSM Timer functions. */ +static void ospf_inactivity_timer(struct event *thread) +{ + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + nbr->t_inactivity = NULL; + + if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) + zlog_debug("NSM[%s:%pI4:%s]: Timer (Inactivity timer expire)", + IF_NAME(nbr->oi), &nbr->router_id, + ospf_get_name(nbr->oi->ospf)); + + /* Dont trigger NSM_InactivityTimer event , if the current + * router acting as HELPER for this neighbour. + */ + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_InactivityTimer); + else { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Acting as HELPER for this neighbour, So restart the dead timer", + __func__); + OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer, + nbr->v_inactivity); + } +} + +static void ospf_db_desc_timer(struct event *thread) +{ + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + nbr->t_db_desc = NULL; + + if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) + zlog_debug("NSM[%s:%pI4:%s]: Timer (DD Retransmit timer expire)", + IF_NAME(nbr->oi), &nbr->src, + ospf_get_name(nbr->oi->ospf)); + + /* resent last send DD packet. */ + assert(nbr->last_send); + ospf_db_desc_resend(nbr); + + /* DD Retransmit timer set. */ + OSPF_NSM_TIMER_ON(nbr->t_db_desc, ospf_db_desc_timer, nbr->v_db_desc); +} + +/* Hook function called after ospf NSM event is occurred. + * + * Set/clear any timers whose condition is implicit to the neighbour + * state. There may be other timers which are set/unset according to other + * state. + * + * We rely on this function to properly clear timers in lower states, + * particularly before deleting a neighbour. + */ +static void nsm_timer_set(struct ospf_neighbor *nbr) +{ + switch (nbr->state) { + case NSM_Deleted: + case NSM_Down: + EVENT_OFF(nbr->t_inactivity); + EVENT_OFF(nbr->t_hello_reply); + fallthrough; + case NSM_Attempt: + case NSM_Init: + case NSM_TwoWay: + EVENT_OFF(nbr->t_db_desc); + EVENT_OFF(nbr->t_ls_upd); + EVENT_OFF(nbr->t_ls_req); + break; + case NSM_ExStart: + OSPF_NSM_TIMER_ON(nbr->t_db_desc, ospf_db_desc_timer, + nbr->v_db_desc); + EVENT_OFF(nbr->t_ls_upd); + EVENT_OFF(nbr->t_ls_req); + break; + case NSM_Exchange: + OSPF_NSM_TIMER_ON(nbr->t_ls_upd, ospf_ls_upd_timer, + nbr->v_ls_upd); + if (!IS_SET_DD_MS(nbr->dd_flags)) + EVENT_OFF(nbr->t_db_desc); + break; + case NSM_Loading: + case NSM_Full: + default: + EVENT_OFF(nbr->t_db_desc); + break; + } +} + +/* 10.4 of RFC2328, indicate whether an adjacency is appropriate with + * the given neighbour + */ +int nsm_should_adj(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi = nbr->oi; + + /* These network types must always form adjacencies. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT + || oi->type == OSPF_IFTYPE_POINTOMULTIPOINT + || oi->type == OSPF_IFTYPE_VIRTUALLINK + /* Router itself is the DRouter or the BDRouter. */ + || IPV4_ADDR_SAME(&oi->address->u.prefix4, &DR(oi)) + || IPV4_ADDR_SAME(&oi->address->u.prefix4, &BDR(oi)) + /* Neighboring Router is the DRouter or the BDRouter. */ + || IPV4_ADDR_SAME(&nbr->address.u.prefix4, &DR(oi)) + || IPV4_ADDR_SAME(&nbr->address.u.prefix4, &BDR(oi))) + return 1; + + return 0; +} + +/* OSPF NSM functions. */ +static int nsm_hello_received(struct ospf_neighbor *nbr) +{ + /* Start or Restart Inactivity Timer. */ + EVENT_OFF(nbr->t_inactivity); + + OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer, + nbr->v_inactivity); + + if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->nbr_nbma != NULL) + EVENT_OFF(nbr->nbr_nbma->t_poll); + + /* Send proactive ARP requests */ + if (nbr->state < NSM_Exchange) + ospf_proactively_arp(nbr); + + return 0; +} + +static int nsm_start(struct ospf_neighbor *nbr) +{ + if (nbr->nbr_nbma) + EVENT_OFF(nbr->nbr_nbma->t_poll); + + EVENT_OFF(nbr->t_inactivity); + + OSPF_NSM_TIMER_ON(nbr->t_inactivity, ospf_inactivity_timer, + nbr->v_inactivity); + + /* Send proactive ARP requests */ + ospf_proactively_arp(nbr); + + return 0; +} + +static int nsm_twoway_received(struct ospf_neighbor *nbr) +{ + int adj = nsm_should_adj(nbr); + + /* Send proactive ARP requests */ + if (adj) + ospf_proactively_arp(nbr); + + return (adj ? NSM_ExStart : NSM_TwoWay); +} + +int ospf_db_summary_count(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_count_all(&nbr->db_sum); +} + +int ospf_db_summary_isempty(struct ospf_neighbor *nbr) +{ + return ospf_lsdb_isempty(&nbr->db_sum); +} + +static int ospf_db_summary_add(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + /* Exclude type-9 LSAs that does not have the same "oi" with + * "nbr". */ + if (lsa->oi != nbr->oi) + return 0; + break; + case OSPF_OPAQUE_AREA_LSA: + /* + * It is assured by the caller function "nsm_negotiation_done()" + * that every given LSA belongs to the same area with "nbr". + */ + break; + case OSPF_OPAQUE_AS_LSA: + default: + break; + } + + /* Stay away from any Local Translated Type-7 LSAs */ + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) + return 0; + + if (IS_LSA_MAXAGE(lsa)) + ospf_ls_retransmit_add(nbr, lsa); + else + ospf_lsdb_add(&nbr->db_sum, lsa); + + return 0; +} + +void ospf_db_summary_clear(struct ospf_neighbor *nbr) +{ + struct ospf_lsdb *lsdb; + int i; + + lsdb = &nbr->db_sum; + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + struct route_table *table = lsdb->type[i].db; + struct route_node *rn; + + for (rn = route_top(table); rn; rn = route_next(rn)) + if (rn->info) + ospf_lsdb_delete(&nbr->db_sum, rn->info); + } +} + + +/* The area link state database consists of the router-LSAs, + network-LSAs and summary-LSAs contained in the area structure, + along with the AS-external-LSAs contained in the global structure. + AS-external-LSAs are omitted from a virtual neighbor's Database + summary list. AS-external-LSAs are omitted from the Database + summary list if the area has been configured as a stub. */ +static int nsm_negotiation_done(struct ospf_neighbor *nbr) +{ + struct ospf_area *area = nbr->oi->area; + struct ospf_lsa *lsa; + struct route_node *rn; + + /* Send proactive ARP requests */ + ospf_proactively_arp(nbr); + + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + + /* Process only if the neighbor is opaque capable. */ + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + } + + if (CHECK_FLAG(nbr->options, OSPF_OPTION_NP)) { + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + } + + /* For Stub/NSSA area, we should not send Type-4 and Type-5 LSAs */ + if (nbr->oi->type != OSPF_IFTYPE_VIRTUALLINK + && area->external_routing == OSPF_AREA_DEFAULT) { + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + ospf_db_summary_add(nbr, lsa); + LSDB_LOOP (EXTERNAL_LSDB(nbr->oi->ospf), rn, lsa) + ospf_db_summary_add(nbr, lsa); + } + + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O) + && (nbr->oi->type != OSPF_IFTYPE_VIRTUALLINK + && area->external_routing == OSPF_AREA_DEFAULT)) + LSDB_LOOP (OPAQUE_AS_LSDB(nbr->oi->ospf), rn, lsa) + ospf_db_summary_add(nbr, lsa); + + return 0; +} + +static int nsm_exchange_done(struct ospf_neighbor *nbr) +{ + if (ospf_ls_request_isempty(nbr)) + return NSM_Full; + + /* Send Link State Request. */ + if (nbr->t_ls_req == NULL) + ospf_ls_req_send(nbr); + + return NSM_Loading; +} + +static int nsm_adj_ok(struct ospf_neighbor *nbr) +{ + int next_state = nbr->state; + int adj = nsm_should_adj(nbr); + + if (nbr->state == NSM_TwoWay && adj == 1) { + next_state = NSM_ExStart; + + /* Send proactive ARP requests */ + ospf_proactively_arp(nbr); + } else if (nbr->state >= NSM_ExStart && adj == 0) + next_state = NSM_TwoWay; + + return next_state; +} + +/* Clear adjacency related state for a neighbour, intended where nbr + * transitions from > ExStart (i.e. a Full or forming adjacency) + * to <= ExStart. + */ +static void nsm_clear_adj(struct ospf_neighbor *nbr) +{ + /* Clear Database Summary list. */ + if (!ospf_db_summary_isempty(nbr)) + ospf_db_summary_clear(nbr); + + /* Clear Link State Request list. */ + if (!ospf_ls_request_isempty(nbr)) + ospf_ls_request_delete_all(nbr); + + /* Clear Link State Retransmission list. */ + if (!ospf_ls_retransmit_isempty(nbr)) + ospf_ls_retransmit_clear(nbr); + + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) + UNSET_FLAG(nbr->options, OSPF_OPTION_O); +} + +static int nsm_kill_nbr(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi = nbr->oi; + struct ospf_neighbor *on; + struct route_node *rn; + + /* killing nbr_self is invalid */ + if (nbr == nbr->oi->nbr_self) { + assert(nbr != nbr->oi->nbr_self); + return 0; + } + + if (OSPF_IF_NON_BROADCAST(nbr->oi) && nbr->nbr_nbma != NULL) { + struct ospf_nbr_nbma *nbr_nbma = nbr->nbr_nbma; + + nbr_nbma->nbr = NULL; + nbr_nbma->state_change = nbr->state_change; + + nbr->nbr_nbma = NULL; + + OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, + nbr_nbma->v_poll); + + if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) + zlog_debug( + "NSM[%s:%pI4:%s]: Down (PollIntervalTimer scheduled)", + IF_NAME(nbr->oi), + &nbr->address.u.prefix4, + ospf_get_name(nbr->oi->ospf)); + } + + /* + * Do we have any neighbors that are also operating + * on this interface? + */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + on = rn->info; + + if (!on) + continue; + + if (on == nbr || on == oi->nbr_self) + continue; + + /* + * on is in some state where we might be + * sending packets on this interface + */ + if (on->state > NSM_Down) { + route_unlock_node(rn); + return 0; + } + } + /* + * If we get here we know that this interface + * has no neighbors in a state where we could + * be sending packets. Let's flush anything + * we got. + */ + ospf_interface_fifo_flush(oi); + return 0; +} + +/* Neighbor State Machine */ +const struct { + int (*func)(struct ospf_neighbor *); + int next_state; +} NSM[OSPF_NSM_STATE_MAX][OSPF_NSM_EVENT_MAX] = { + { + /* DependUpon: dummy state. */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {NULL, NSM_DependUpon}, /* HelloReceived */ + {NULL, NSM_DependUpon}, /* Start */ + {NULL, NSM_DependUpon}, /* 2-WayReceived */ + {NULL, NSM_DependUpon}, /* NegotiationDone */ + {NULL, NSM_DependUpon}, /* ExchangeDone */ + {NULL, NSM_DependUpon}, /* BadLSReq */ + {NULL, NSM_DependUpon}, /* LoadingDone */ + {NULL, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_DependUpon}, /* SeqNumberMismatch */ + {NULL, NSM_DependUpon}, /* 1-WayReceived */ + {NULL, NSM_DependUpon}, /* KillNbr */ + {NULL, NSM_DependUpon}, /* InactivityTimer */ + {NULL, NSM_DependUpon}, /* LLDown */ + }, + { + /* Deleted: dummy state. */ + {NULL, NSM_Deleted}, /* NoEvent */ + {NULL, NSM_Deleted}, /* HelloReceived */ + {NULL, NSM_Deleted}, /* Start */ + {NULL, NSM_Deleted}, /* 2-WayReceived */ + {NULL, NSM_Deleted}, /* NegotiationDone */ + {NULL, NSM_Deleted}, /* ExchangeDone */ + {NULL, NSM_Deleted}, /* BadLSReq */ + {NULL, NSM_Deleted}, /* LoadingDone */ + {NULL, NSM_Deleted}, /* AdjOK? */ + {NULL, NSM_Deleted}, /* SeqNumberMismatch */ + {NULL, NSM_Deleted}, /* 1-WayReceived */ + {NULL, NSM_Deleted}, /* KillNbr */ + {NULL, NSM_Deleted}, /* InactivityTimer */ + {NULL, NSM_Deleted}, /* LLDown */ + }, + { + /* Down: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Init}, /* HelloReceived */ + {nsm_start, NSM_Attempt}, /* Start */ + {NULL, NSM_Down}, /* 2-WayReceived */ + {NULL, NSM_Down}, /* NegotiationDone */ + {NULL, NSM_Down}, /* ExchangeDone */ + {NULL, NSM_Down}, /* BadLSReq */ + {NULL, NSM_Down}, /* LoadingDone */ + {NULL, NSM_Down}, /* AdjOK? */ + {NULL, NSM_Down}, /* SeqNumberMismatch */ + {NULL, NSM_Down}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* Attempt: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Init}, /* HelloReceived */ + {NULL, NSM_Attempt}, /* Start */ + {NULL, NSM_Attempt}, /* 2-WayReceived */ + {NULL, NSM_Attempt}, /* NegotiationDone */ + {NULL, NSM_Attempt}, /* ExchangeDone */ + {NULL, NSM_Attempt}, /* BadLSReq */ + {NULL, NSM_Attempt}, /* LoadingDone */ + {NULL, NSM_Attempt}, /* AdjOK? */ + {NULL, NSM_Attempt}, /* SeqNumberMismatch */ + {NULL, NSM_Attempt}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* Init: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Init}, /* HelloReceived */ + {NULL, NSM_Init}, /* Start */ + {nsm_twoway_received, NSM_DependUpon}, /* 2-WayReceived */ + {NULL, NSM_Init}, /* NegotiationDone */ + {NULL, NSM_Init}, /* ExchangeDone */ + {NULL, NSM_Init}, /* BadLSReq */ + {NULL, NSM_Init}, /* LoadingDone */ + {NULL, NSM_Init}, /* AdjOK? */ + {NULL, NSM_Init}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* 2-Way: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_TwoWay}, /* HelloReceived */ + {NULL, NSM_TwoWay}, /* Start */ + {NULL, NSM_TwoWay}, /* 2-WayReceived */ + {NULL, NSM_TwoWay}, /* NegotiationDone */ + {NULL, NSM_TwoWay}, /* ExchangeDone */ + {NULL, NSM_TwoWay}, /* BadLSReq */ + {NULL, NSM_TwoWay}, /* LoadingDone */ + {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_TwoWay}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* ExStart: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_ExStart}, /* HelloReceived */ + {NULL, NSM_ExStart}, /* Start */ + {NULL, NSM_ExStart}, /* 2-WayReceived */ + {nsm_negotiation_done, NSM_Exchange}, /* NegotiationDone */ + {NULL, NSM_ExStart}, /* ExchangeDone */ + {NULL, NSM_ExStart}, /* BadLSReq */ + {NULL, NSM_ExStart}, /* LoadingDone */ + {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_ExStart}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* Exchange: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Exchange}, /* HelloReceived */ + {NULL, NSM_Exchange}, /* Start */ + {NULL, NSM_Exchange}, /* 2-WayReceived */ + {NULL, NSM_Exchange}, /* NegotiationDone */ + {nsm_exchange_done, NSM_DependUpon}, /* ExchangeDone */ + {NULL, NSM_ExStart}, /* BadLSReq */ + {NULL, NSM_Exchange}, /* LoadingDone */ + {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_ExStart}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* Loading: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Loading}, /* HelloReceived */ + {NULL, NSM_Loading}, /* Start */ + {NULL, NSM_Loading}, /* 2-WayReceived */ + {NULL, NSM_Loading}, /* NegotiationDone */ + {NULL, NSM_Loading}, /* ExchangeDone */ + {NULL, NSM_ExStart}, /* BadLSReq */ + {NULL, NSM_Full}, /* LoadingDone */ + {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_ExStart}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, + { + /* Full: */ + {NULL, NSM_DependUpon}, /* NoEvent */ + {nsm_hello_received, NSM_Full}, /* HelloReceived */ + {NULL, NSM_Full}, /* Start */ + {NULL, NSM_Full}, /* 2-WayReceived */ + {NULL, NSM_Full}, /* NegotiationDone */ + {NULL, NSM_Full}, /* ExchangeDone */ + {NULL, NSM_ExStart}, /* BadLSReq */ + {NULL, NSM_Full}, /* LoadingDone */ + {nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */ + {NULL, NSM_ExStart}, /* SeqNumberMismatch */ + {NULL, NSM_Init}, /* 1-WayReceived */ + {nsm_kill_nbr, NSM_Deleted}, /* KillNbr */ + {nsm_kill_nbr, NSM_Deleted}, /* InactivityTimer */ + {nsm_kill_nbr, NSM_Deleted}, /* LLDown */ + }, +}; + +static const char *const ospf_nsm_event_str[] = { + "NoEvent", "HelloReceived", "Start", + "2-WayReceived", "NegotiationDone", "ExchangeDone", + "BadLSReq", "LoadingDone", "AdjOK?", + "SeqNumberMismatch", "1-WayReceived", "KillNbr", + "InactivityTimer", "LLDown", +}; + +static void nsm_notice_state_change(struct ospf_neighbor *nbr, int next_state, + int event) +{ + /* Logging change of status. */ + if (IS_DEBUG_OSPF(nsm, NSM_STATUS)) + zlog_debug("NSM[%s:%pI4:%s]: State change %s -> %s (%s)", + IF_NAME(nbr->oi), &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + lookup_msg(ospf_nsm_state_msg, next_state, NULL), + ospf_nsm_event_str[event]); + + /* Optionally notify about adjacency changes */ + if (CHECK_FLAG(nbr->oi->ospf->config, OSPF_LOG_ADJACENCY_CHANGES) + && (CHECK_FLAG(nbr->oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL) + || (next_state == NSM_Full) || (next_state < nbr->state))) + zlog_notice( + "AdjChg: Nbr %pI4, NbrIP %pI4 (%s) on %s: %s -> %s (%s)", + &nbr->router_id, &nbr->src, + ospf_get_name(nbr->oi->ospf), IF_NAME(nbr->oi), + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + lookup_msg(ospf_nsm_state_msg, next_state, NULL), + ospf_nsm_event_str[event]); + + /* Advance in NSM */ + if (next_state > nbr->state) + monotime(&nbr->ts_last_progress); + else /* regression in NSM */ + { + monotime(&nbr->ts_last_regress); + nbr->last_regress_str = ospf_nsm_event_str[event]; + } +} + +static void nsm_change_state(struct ospf_neighbor *nbr, int state) +{ + struct ospf_interface *oi = nbr->oi; + struct ospf_area *vl_area = NULL; + uint8_t old_state; + + /* Preserve old status. */ + old_state = nbr->state; + + /* Change to new status. */ + nbr->state = state; + + /* Statistics. */ + nbr->state_change++; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + vl_area = ospf_area_lookup_by_area_id(oi->ospf, + oi->vl_data->vl_area_id); + + /* Generate NeighborChange ISM event. + * + * In response to NeighborChange, DR election is rerun. The information + * from the election process is required by the router-lsa construction. + * + * Therefore, trigger the event prior to refreshing the LSAs. */ + switch (oi->state) { + case ISM_DROther: + case ISM_Backup: + case ISM_DR: + if ((old_state < NSM_TwoWay && state >= NSM_TwoWay) + || (old_state >= NSM_TwoWay && state < NSM_TwoWay)) + OSPF_ISM_EVENT_EXECUTE(oi, ISM_NeighborChange); + break; + default: + /* ISM_PointToPoint -> ISM_Down, ISM_Loopback -> ISM_Down, etc. + */ + break; + } + + /* One of the neighboring routers changes to/from the FULL state. */ + if ((old_state != NSM_Full && state == NSM_Full) + || (old_state == NSM_Full && state != NSM_Full)) { + if (state == NSM_Full) { + oi->full_nbrs++; + oi->area->full_nbrs++; + + ospf_check_abr_status(oi->ospf); + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK && vl_area) + if (++vl_area->full_vls == 1) + ospf_schedule_abr_task(oi->ospf); + } else { + oi->full_nbrs--; + oi->area->full_nbrs--; + + ospf_check_abr_status(oi->ospf); + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK && vl_area) + if (vl_area->full_vls > 0) + if (--vl_area->full_vls == 0) + ospf_schedule_abr_task( + oi->ospf); + } + + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "%s:[%pI4:%s], %s -> %s): scheduling new router-LSA origination", + __func__, &nbr->router_id, + ospf_get_name(oi->ospf), + lookup_msg(ospf_nsm_state_msg, old_state, NULL), + lookup_msg(ospf_nsm_state_msg, state, NULL)); + + /* Dont originate router LSA if the current + * router is acting as a HELPER for this neighbour. + */ + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) + ospf_router_lsa_update_area(oi->area); + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) { + vl_area = ospf_area_lookup_by_area_id( + oi->ospf, oi->vl_data->vl_area_id); + + if (vl_area) + ospf_router_lsa_update_area(vl_area); + } + + /* Dont originate/flush network LSA if the current + * router is acting as a HELPER for this neighbour. + */ + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) { + /* Originate network-LSA. */ + if (oi->state == ISM_DR) { + if (oi->network_lsa_self + && oi->full_nbrs == 0) { + ospf_lsa_flush_area( + oi->network_lsa_self, oi->area); + ospf_lsa_unlock(&oi->network_lsa_self); + oi->network_lsa_self = NULL; + } else + ospf_network_lsa_update(oi); + } + } + + if (state == NSM_Full && oi->ospf->gr_info.restart_in_progress) + ospf_gr_check_adjs(oi->ospf); + } + + ospf_opaque_nsm_change(nbr, old_state); + + /* State changes from > ExStart to <= ExStart should clear any Exchange + * or Full/LSA Update related lists and state. + * Potential causal events: BadLSReq, SeqNumberMismatch, AdjOK? + */ + if ((old_state > NSM_ExStart) && (state <= NSM_ExStart)) + nsm_clear_adj(nbr); + + /* Start DD exchange protocol */ + if (state == NSM_ExStart) { + if (nbr->dd_seqnum == 0) + nbr->dd_seqnum = (uint32_t)frr_weak_random(); + else + nbr->dd_seqnum++; + + nbr->dd_flags = + OSPF_DD_FLAG_I | OSPF_DD_FLAG_M | OSPF_DD_FLAG_MS; + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "%s: Initializing [DD]: %pI4 with seqnum:%x , flags:%x", + ospf_get_name(oi->ospf), &nbr->router_id, + nbr->dd_seqnum, nbr->dd_flags); + ospf_db_desc_send(nbr); + } + + /* clear cryptographic sequence number */ + if (state == NSM_Down) + nbr->crypt_seqnum = 0; + + if (nbr->bfd_session) + ospf_bfd_trigger_event(nbr, old_state, state); + + /* Preserve old status? */ +} + +/* Execute NSM event process. */ +void ospf_nsm_event(struct event *thread) +{ + int event; + int next_state; + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + event = EVENT_VAL(thread); + + if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) + zlog_debug("NSM[%s:%pI4:%s]: %s (%s)", IF_NAME(nbr->oi), + &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + ospf_nsm_event_str[event]); + + next_state = NSM[nbr->state][event].next_state; + + /* Call function. */ + if (NSM[nbr->state][event].func != NULL) { + int func_state = (*(NSM[nbr->state][event].func))(nbr); + + if (NSM[nbr->state][event].next_state == NSM_DependUpon) + next_state = func_state; + else if (func_state) { + /* There's a mismatch between the FSM tables and what an + * FSM + * action/state-change function returned. State changes + * which + * do not have conditional/DependUpon next-states should + * not + * try set next_state. + */ + flog_err( + EC_OSPF_FSM_INVALID_STATE, + "NSM[%s:%pI4:%s]: %s (%s): Warning: action tried to change next_state to %s", + IF_NAME(nbr->oi), &nbr->router_id, + ospf_get_name(nbr->oi->ospf), + lookup_msg(ospf_nsm_state_msg, nbr->state, + NULL), + ospf_nsm_event_str[event], + lookup_msg(ospf_nsm_state_msg, func_state, + NULL)); + } + } + + assert(next_state != NSM_DependUpon); + + /* If state is changed. */ + if (next_state != nbr->state) { + int old_state = nbr->state; + + nsm_notice_state_change(nbr, next_state, event); + nsm_change_state(nbr, next_state); + + hook_call(ospf_nsm_change, nbr, next_state, old_state); + } + + /* Make sure timer is set. */ + nsm_timer_set(nbr); + + /* When event is NSM_KillNbr, InactivityTimer or LLDown, the neighbor + * is deleted. + * + * Rather than encode knowledge here of which events lead to NBR + * delete, we take our cue from the NSM table, via the dummy + * 'Deleted' neighbour state. + */ + if (nbr->state == NSM_Deleted) + ospf_nbr_delete(nbr); +} + +/* Check loading state. */ +void ospf_check_nbr_loading(struct ospf_neighbor *nbr) +{ + if (nbr->state == NSM_Loading) { + if (ospf_ls_request_isempty(nbr)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_LoadingDone); + else if (nbr->ls_req_last == NULL) + ospf_ls_req_event(nbr); + } +} diff --git a/ospfd/ospf_nsm.h b/ospfd/ospf_nsm.h new file mode 100644 index 0000000..82e1720 --- /dev/null +++ b/ospfd/ospf_nsm.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF version 2 Neighbor State Machine + * From RFC2328 [OSPF Version 2] + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_NSM_H +#define _ZEBRA_OSPF_NSM_H + +#include "hook.h" + +/* OSPF Neighbor State Machine State. */ +#define NSM_DependUpon 0 +#define NSM_Deleted 1 +#define NSM_Down 2 +#define NSM_Attempt 3 +#define NSM_Init 4 +#define NSM_TwoWay 5 +#define NSM_ExStart 6 +#define NSM_Exchange 7 +#define NSM_Loading 8 +#define NSM_Full 9 +#define OSPF_NSM_STATE_MAX 10 + +/* OSPF Neighbor State Machine Event. */ +#define NSM_NoEvent 0 +#define NSM_HelloReceived 1 /* HelloReceived in the protocol */ +#define NSM_Start 2 +#define NSM_TwoWayReceived 3 +#define NSM_NegotiationDone 4 +#define NSM_ExchangeDone 5 +#define NSM_BadLSReq 6 +#define NSM_LoadingDone 7 +#define NSM_AdjOK 8 +#define NSM_SeqNumberMismatch 9 +#define NSM_OneWayReceived 10 +#define NSM_KillNbr 11 +#define NSM_InactivityTimer 12 +#define NSM_LLDown 13 +#define OSPF_NSM_EVENT_MAX 14 + +/* Macro for OSPF NSM timer turn on. */ +#define OSPF_NSM_TIMER_ON(T, F, V) event_add_timer(master, (F), nbr, (V), &(T)) + +/* Macro for OSPF NSM schedule event. */ +#define OSPF_NSM_EVENT_SCHEDULE(N, E) \ + event_add_event(master, ospf_nsm_event, (N), (E), NULL) + +/* Macro for OSPF NSM execute event. */ +#define OSPF_NSM_EVENT_EXECUTE(N, E) \ + event_execute(master, ospf_nsm_event, (N), (E), NULL) + +/* Prototypes. */ +extern void ospf_nsm_event(struct event *e); +extern void ospf_check_nbr_loading(struct ospf_neighbor *nbr); +extern int ospf_db_summary_isempty(struct ospf_neighbor *nbr); +extern int ospf_db_summary_count(struct ospf_neighbor *nbr); +extern void ospf_db_summary_clear(struct ospf_neighbor *nbr); +extern int nsm_should_adj(struct ospf_neighbor *nbr); +DECLARE_HOOK(ospf_nsm_change, + (struct ospf_neighbor * on, int state, int oldstate), + (on, state, oldstate)); + +#endif /* _ZEBRA_OSPF_NSM_H */ diff --git a/ospfd/ospf_opaque.c b/ospfd/ospf_opaque.c new file mode 100644 index 0000000..5d2d656 --- /dev/null +++ b/ospfd/ospf_opaque.c @@ -0,0 +1,2255 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of rfc2370. + * Copyright (C) 2001 KDD R&D Laboratories, Inc. + * http://www.kddlabs.co.jp/ + */ + +#include + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_te.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_errors.h" + +DEFINE_MTYPE_STATIC(OSPFD, OSPF_OPAQUE_FUNCTAB, "OSPF opaque function table"); +DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_TYPE, "OSPF opaque per-type info"); +DEFINE_MTYPE_STATIC(OSPFD, OPAQUE_INFO_PER_ID, "OSPF opaque per-ID info"); + +/*------------------------------------------------------------------------* + * Following are initialize/terminate functions for Opaque-LSAs handling. + *------------------------------------------------------------------------*/ + +#ifdef SUPPORT_OSPF_API +int ospf_apiserver_init(void); +void ospf_apiserver_term(void); +/* Init apiserver? It's disabled by default. */ +int ospf_apiserver_enable; +#endif /* SUPPORT_OSPF_API */ + +static void ospf_opaque_register_vty(void); +static void ospf_opaque_funclist_init(void); +static void ospf_opaque_funclist_term(void); +static void free_opaque_info_per_type_del(void *val); +static void free_opaque_info_per_id(void *val); +static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa); +static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa); + +void ospf_opaque_init(void) +{ + ospf_opaque_register_vty(); + ospf_opaque_funclist_init(); + + if (ospf_mpls_te_init() != 0) + exit(1); + + /* Segment Routing init */ + if (ospf_sr_init() != 0) + exit(1); + + if (ospf_router_info_init() != 0) + exit(1); + + if (ospf_ext_init() != 0) + exit(1); + +#ifdef SUPPORT_OSPF_API + if ((ospf_apiserver_enable) && (ospf_apiserver_init() != 0)) + exit(1); +#endif /* SUPPORT_OSPF_API */ + + return; +} + +void ospf_opaque_term(void) +{ + ospf_mpls_te_term(); + + ospf_router_info_term(); + + ospf_ext_term(); + + ospf_sr_term(); + +#ifdef SUPPORT_OSPF_API + ospf_apiserver_term(); +#endif /* SUPPORT_OSPF_API */ + + ospf_opaque_funclist_term(); + return; +} + +void ospf_opaque_finish(void) +{ + ospf_mpls_te_finish(); + + ospf_router_info_finish(); + + ospf_ext_finish(); + +#ifdef SUPPORT_OSPF_API + ospf_apiserver_term(); +#endif + + ospf_sr_finish(); +} + +int ospf_opaque_type9_lsa_init(struct ospf_interface *oi) +{ + if (oi->opaque_lsa_self != NULL) + list_delete(&oi->opaque_lsa_self); + + oi->opaque_lsa_self = list_new(); + oi->opaque_lsa_self->del = free_opaque_info_per_type_del; + oi->t_opaque_lsa_self = NULL; + return 0; +} + +void ospf_opaque_type9_lsa_term(struct ospf_interface *oi) +{ + EVENT_OFF(oi->t_opaque_lsa_self); + if (oi->opaque_lsa_self != NULL) + list_delete(&oi->opaque_lsa_self); + oi->opaque_lsa_self = NULL; + return; +} + +int ospf_opaque_type10_lsa_init(struct ospf_area *area) +{ + if (area->opaque_lsa_self != NULL) + list_delete(&area->opaque_lsa_self); + + area->opaque_lsa_self = list_new(); + area->opaque_lsa_self->del = free_opaque_info_per_type_del; + area->t_opaque_lsa_self = NULL; + +#ifdef MONITOR_LSDB_CHANGE + area->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; + area->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; +#endif /* MONITOR_LSDB_CHANGE */ + return 0; +} + +void ospf_opaque_type10_lsa_term(struct ospf_area *area) +{ +#ifdef MONITOR_LSDB_CHANGE + area->lsdb->new_lsa_hook = area->lsdb->del_lsa_hook = NULL; +#endif /* MONITOR_LSDB_CHANGE */ + + EVENT_OFF(area->t_opaque_lsa_self); + if (area->opaque_lsa_self != NULL) + list_delete(&area->opaque_lsa_self); + return; +} + +int ospf_opaque_type11_lsa_init(struct ospf *top) +{ + if (top->opaque_lsa_self != NULL) + list_delete(&top->opaque_lsa_self); + + top->opaque_lsa_self = list_new(); + top->opaque_lsa_self->del = free_opaque_info_per_type_del; + top->t_opaque_lsa_self = NULL; + +#ifdef MONITOR_LSDB_CHANGE + top->lsdb->new_lsa_hook = ospf_opaque_lsa_install_hook; + top->lsdb->del_lsa_hook = ospf_opaque_lsa_delete_hook; +#endif /* MONITOR_LSDB_CHANGE */ + return 0; +} + +void ospf_opaque_type11_lsa_term(struct ospf *top) +{ +#ifdef MONITOR_LSDB_CHANGE + top->lsdb->new_lsa_hook = top->lsdb->del_lsa_hook = NULL; +#endif /* MONITOR_LSDB_CHANGE */ + + EVENT_OFF(top->t_opaque_lsa_self); + if (top->opaque_lsa_self != NULL) + list_delete(&top->opaque_lsa_self); + return; +} + +static const char *ospf_opaque_type_name(uint8_t opaque_type) +{ + const char *name = "Unknown"; + + switch (opaque_type) { + case OPAQUE_TYPE_WILDCARD: /* This is a special assignment! */ + name = "Wildcard"; + break; + case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA: + name = "Traffic Engineering LSA"; + break; + case OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC: + name = "Sycamore optical topology description"; + break; + case OPAQUE_TYPE_GRACE_LSA: + name = "Grace-LSA"; + break; + case OPAQUE_TYPE_INTER_AS_LSA: + name = "Inter-AS TE-v2 LSA"; + break; + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + name = "Router Information LSA"; + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + name = "Extended Prefix Opaque LSA"; + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + name = "Extended Link Opaque LSA"; + break; + default: + if (OPAQUE_TYPE_RANGE_UNASSIGNED(opaque_type)) + name = "Unassigned"; + else { + uint32_t bigger_range = opaque_type; + /* + * Get around type-limits warning: comparison is always + * true due to limited range of data type + */ + if (OPAQUE_TYPE_RANGE_RESERVED(bigger_range)) + name = "Private/Experimental"; + } + break; + } + return name; +} + +/*------------------------------------------------------------------------* + * Following are management functions to store user specified callbacks. + *------------------------------------------------------------------------*/ + +struct opaque_info_per_type; /* Forward declaration. */ + +static void free_opaque_info_per_type(struct opaque_info_per_type *oipt, + bool cleanup_owner); + +struct ospf_opaque_functab { + uint8_t opaque_type; + uint32_t ref_count; + + int (*new_if_hook)(struct interface *ifp); + int (*del_if_hook)(struct interface *ifp); + void (*ism_change_hook)(struct ospf_interface *oi, int old_status); + void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status); + void (*config_write_router)(struct vty *vty); + void (*config_write_if)(struct vty *vty, struct interface *ifp); + void (*config_write_debug)(struct vty *vty); + void (*show_opaque_info)(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); + int (*lsa_originator)(void *arg); + struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa); + int (*new_lsa_hook)(struct ospf_lsa *lsa); + int (*del_lsa_hook)(struct ospf_lsa *lsa); +}; + +/* Handle LSA-9/10/11 altogether. */ +static struct list *ospf_opaque_wildcard_funclist; +static struct list *ospf_opaque_type9_funclist; +static struct list *ospf_opaque_type10_funclist; +static struct list *ospf_opaque_type11_funclist; + +static void ospf_opaque_functab_ref(struct ospf_opaque_functab *functab) +{ + functab->ref_count++; +} + +static void ospf_opaque_functab_deref(struct ospf_opaque_functab *functab) +{ + assert(functab->ref_count); + functab->ref_count--; + if (functab->ref_count == 0) + XFREE(MTYPE_OSPF_OPAQUE_FUNCTAB, functab); +} + +static void ospf_opaque_del_functab(void *val) +{ + struct ospf_opaque_functab *functab = (struct ospf_opaque_functab *)val; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Opaque LSA functab list deletion callback type %u (%p)", + __func__, functab->opaque_type, functab); + + ospf_opaque_functab_deref(functab); + return; +} + +static void ospf_opaque_funclist_init(void) +{ + struct list *funclist; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Function list initialize", __func__); + + funclist = ospf_opaque_wildcard_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type9_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type10_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + + funclist = ospf_opaque_type11_funclist = list_new(); + funclist->del = ospf_opaque_del_functab; + return; +} + +static void ospf_opaque_funclist_term(void) +{ + struct list *funclist; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Function list terminate", __func__); + + funclist = ospf_opaque_wildcard_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type9_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type10_funclist; + list_delete(&funclist); + + funclist = ospf_opaque_type11_funclist; + list_delete(&funclist); + return; +} + +static struct list *ospf_get_opaque_funclist(uint8_t lsa_type) +{ + struct list *funclist = NULL; + + switch (lsa_type) { + case OPAQUE_TYPE_WILDCARD: + /* XXX + * This is an ugly trick to handle type-9/10/11 LSA altogether. + * Yes, "OPAQUE_TYPE_WILDCARD (value 0)" is not an LSA-type, nor + * an officially assigned opaque-type. + * Though it is possible that the value might be officially used + * in the future, we use it internally as a special label, for + * now. + */ + funclist = ospf_opaque_wildcard_funclist; + break; + case OSPF_OPAQUE_LINK_LSA: + funclist = ospf_opaque_type9_funclist; + break; + case OSPF_OPAQUE_AREA_LSA: + funclist = ospf_opaque_type10_funclist; + break; + case OSPF_OPAQUE_AS_LSA: + funclist = ospf_opaque_type11_funclist; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa_type); + break; + } + return funclist; +} + +/* XXX: such a huge argument list can /not/ be healthy... */ +int ospf_register_opaque_functab( + uint8_t lsa_type, uint8_t opaque_type, + int (*new_if_hook)(struct interface *ifp), + int (*del_if_hook)(struct interface *ifp), + void (*ism_change_hook)(struct ospf_interface *oi, int old_status), + void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status), + void (*config_write_router)(struct vty *vty), + void (*config_write_if)(struct vty *vty, struct interface *ifp), + void (*config_write_debug)(struct vty *vty), + void (*show_opaque_info)(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa), + int (*lsa_originator)(void *arg), + struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa), + int (*new_lsa_hook)(struct ospf_lsa *lsa), + int (*del_lsa_hook)(struct ospf_lsa *lsa)) +{ + struct list *funclist; + struct ospf_opaque_functab *new; + + if ((funclist = ospf_get_opaque_funclist(lsa_type)) == NULL) + return -1; + + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->opaque_type == opaque_type) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Opaque LSA functab found type %u, (%p)", + __func__, functab->opaque_type, + functab); + break; + } + + if (functab == NULL) + new = XCALLOC(MTYPE_OSPF_OPAQUE_FUNCTAB, + sizeof(struct ospf_opaque_functab)); + else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Re-register Opaque LSA type %u, opaque type %u, (%p)", + __func__, lsa_type, opaque_type, functab); + return 0; + } + + new->opaque_type = opaque_type; + new->new_if_hook = new_if_hook; + new->del_if_hook = del_if_hook; + new->ism_change_hook = ism_change_hook; + new->nsm_change_hook = nsm_change_hook; + new->config_write_router = config_write_router; + new->config_write_if = config_write_if; + new->config_write_debug = config_write_debug; + new->show_opaque_info = show_opaque_info; + new->lsa_originator = lsa_originator; + new->lsa_refresher = lsa_refresher; + new->new_lsa_hook = new_lsa_hook; + new->del_lsa_hook = del_lsa_hook; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Register Opaque LSA type %u, opaque type %u, (%p)", + __func__, lsa_type, opaque_type, new); + + listnode_add(funclist, new); + ospf_opaque_functab_ref(new); + + return 0; +} + +void ospf_delete_opaque_functab(uint8_t lsa_type, uint8_t opaque_type) +{ + struct list *funclist; + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + if ((funclist = ospf_get_opaque_funclist(lsa_type)) != NULL) + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) { + if (functab->opaque_type == opaque_type) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Delete Opaque functab LSA type %u, opaque type %u, (%p)", + __func__, lsa_type, + opaque_type, functab); + + /* Dequeue listnode entry from the function table + * list coreesponding to the opaque LSA type. + * Note that the list deletion callback frees + * the functab entry memory. + */ + listnode_delete(funclist, functab); + ospf_opaque_functab_deref(functab); + break; + } + } + + return; +} + +static struct ospf_opaque_functab * +ospf_opaque_functab_lookup(struct ospf_lsa *lsa) +{ + struct list *funclist; + struct listnode *node; + struct ospf_opaque_functab *functab; + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + if ((funclist = ospf_get_opaque_funclist(lsa->data->type)) != NULL) + for (ALL_LIST_ELEMENTS_RO(funclist, node, functab)) + if (functab->opaque_type == key) + return functab; + + return NULL; +} + +/*------------------------------------------------------------------------* + * Following are management functions for self-originated LSA entries. + *------------------------------------------------------------------------*/ + +/* + * Opaque-LSA control information per opaque-type. + * Single Opaque-Type may have multiple instances; each of them will be + * identified by their opaque-id. + */ +struct opaque_info_per_type { + uint8_t lsa_type; + uint8_t opaque_type; + + enum { PROC_NORMAL, PROC_SUSPEND } status; + + /* + * Thread for (re-)origination scheduling for this opaque-type. + * + * Initial origination of Opaque-LSAs is controlled by generic + * Opaque-LSA handling module so that same opaque-type entries are + * called all at once when certain conditions are met. + * However, there might be cases that some Opaque-LSA clients need + * to (re-)originate their own Opaque-LSAs out-of-sync with others. + * This thread is prepared for that specific purpose. + */ + struct event *t_opaque_lsa_self; + + /* + * Backpointer to an "owner" which is LSA-type dependent. + * type-9: struct ospf_interface + * type-10: struct ospf_area + * type-11: struct ospf + */ + void *owner; + + /* Collection of callback functions for this opaque-type. */ + struct ospf_opaque_functab *functab; + + /* List of Opaque-LSA control information per opaque-id. */ + struct list *id_list; +}; + +/* Opaque-LSA control information per opaque-id. */ +struct opaque_info_per_id { + uint32_t opaque_id; + + /* Thread for refresh/flush scheduling for this opaque-type/id. */ + struct event *t_opaque_lsa_self; + + /* Backpointer to Opaque-LSA control information per opaque-type. */ + struct opaque_info_per_type *opqctl_type; + + /* Here comes an actual Opaque-LSA entry for this opaque-type/id. */ + struct ospf_lsa *lsa; +}; + +static struct opaque_info_per_type * +register_opaque_info_per_type(struct ospf_opaque_functab *functab, + struct ospf_lsa *new); +static struct opaque_info_per_type * +lookup_opaque_info_by_type(struct ospf_lsa *lsa); +static struct opaque_info_per_id * +register_opaque_info_per_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *new); +static struct opaque_info_per_id * +lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *lsa); +static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new); + + +static struct opaque_info_per_type * +register_opaque_info_per_type(struct ospf_opaque_functab *functab, + struct ospf_lsa *new) +{ + struct ospf *top; + struct opaque_info_per_type *oipt; + + oipt = XCALLOC(MTYPE_OPAQUE_INFO_PER_TYPE, + sizeof(struct opaque_info_per_type)); + + switch (new->data->type) { + case OSPF_OPAQUE_LINK_LSA: + oipt->owner = new->oi; + listnode_add(new->oi->opaque_lsa_self, oipt); + break; + case OSPF_OPAQUE_AREA_LSA: + oipt->owner = new->area; + listnode_add(new->area->opaque_lsa_self, oipt); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (new->area != NULL && (top = new->area->ospf) == NULL) { + free_opaque_info_per_type(oipt, true); + oipt = NULL; + goto out; /* This case may not exist. */ + } + oipt->owner = top; + listnode_add(top->opaque_lsa_self, oipt); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, new->data->type); + free_opaque_info_per_type(oipt, true); + oipt = NULL; + goto out; /* This case may not exist. */ + } + + oipt->lsa_type = new->data->type; + oipt->opaque_type = GET_OPAQUE_TYPE(ntohl(new->data->id.s_addr)); + oipt->status = PROC_NORMAL; + oipt->functab = functab; + ospf_opaque_functab_ref(functab); + oipt->id_list = list_new(); + oipt->id_list->del = free_opaque_info_per_id; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Register Opaque info-per-type LSA type %u, opaque type %u, (%p), Functab (%p)", + __func__, oipt->lsa_type, oipt->opaque_type, oipt, + oipt->functab); + +out: + return oipt; +} + +static void free_opaque_info_per_type(struct opaque_info_per_type *oipt, + bool cleanup_owner) +{ + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct listnode *node, *nnode; + struct list *l; + + /* Control information per opaque-id may still exist. */ + for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) { + if ((lsa = oipi->lsa) == NULL) + continue; + if (IS_LSA_MAXAGE(lsa)) + continue; + ospf_opaque_lsa_flush_schedule(lsa); + } + + EVENT_OFF(oipt->t_opaque_lsa_self); + list_delete(&oipt->id_list); + if (cleanup_owner) { + /* Remove from its owner's self-originated LSA list. */ + switch (oipt->lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + l = ((struct ospf_interface *)oipt->owner) + ->opaque_lsa_self; + break; + case OSPF_OPAQUE_AREA_LSA: + l = ((struct ospf_area *)oipt->owner)->opaque_lsa_self; + break; + case OSPF_OPAQUE_AS_LSA: + l = ((struct ospf *)oipt->owner)->opaque_lsa_self; + break; + default: + flog_warn( + EC_OSPF_LSA_UNEXPECTED, + "free_opaque_info_owner: Unexpected LSA-type(%u)", + oipt->lsa_type); + return; + } + listnode_delete(l, oipt); + } + + if (oipt->functab) + ospf_opaque_functab_deref(oipt->functab); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Free Opaque info-per-type LSA type %u, opaque type %u, (%p), Functab (%p)", + __func__, oipt->lsa_type, oipt->opaque_type, oipt, + oipt->functab); + + XFREE(MTYPE_OPAQUE_INFO_PER_TYPE, oipt); + return; +} + +static void free_opaque_info_per_type_del(void *val) +{ + free_opaque_info_per_type((struct opaque_info_per_type *)val, false); +} + +static struct opaque_info_per_type * +lookup_opaque_info_by_type(struct ospf_lsa *lsa) +{ + struct ospf *top; + struct ospf_area *area; + struct ospf_interface *oi; + struct list *listtop = NULL; + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt = NULL; + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + if ((oi = lsa->oi) != NULL) + listtop = oi->opaque_lsa_self; + else + flog_warn( + EC_OSPF_LSA, + "Type-9 Opaque-LSA: Reference to OI is missing?"); + break; + case OSPF_OPAQUE_AREA_LSA: + if ((area = lsa->area) != NULL) + listtop = area->opaque_lsa_self; + else + flog_warn( + EC_OSPF_LSA, + "Type-10 Opaque-LSA: Reference to AREA is missing?"); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if ((area = lsa->area) != NULL && (top = area->ospf) == NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-11 Opaque-LSA: Reference to OSPF is missing?"); + break; /* Unlikely to happen. */ + } + listtop = top->opaque_lsa_self; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + break; + } + + if (listtop != NULL) + for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) + if (oipt->opaque_type == key) + return oipt; + + return NULL; +} + +static struct opaque_info_per_id * +register_opaque_info_per_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *new) +{ + struct opaque_info_per_id *oipi; + + oipi = XCALLOC(MTYPE_OPAQUE_INFO_PER_ID, + sizeof(struct opaque_info_per_id)); + + oipi->opaque_id = GET_OPAQUE_ID(ntohl(new->data->id.s_addr)); + oipi->opqctl_type = oipt; + oipi->lsa = ospf_lsa_lock(new); + + listnode_add(oipt->id_list, oipi); + + return oipi; +} + +static void free_opaque_info_per_id(void *val) +{ + struct opaque_info_per_id *oipi = (struct opaque_info_per_id *)val; + + EVENT_OFF(oipi->t_opaque_lsa_self); + if (oipi->lsa != NULL) + ospf_lsa_unlock(&oipi->lsa); + XFREE(MTYPE_OPAQUE_INFO_PER_ID, oipi); + return; +} + +static struct opaque_info_per_id * +lookup_opaque_info_by_id(struct opaque_info_per_type *oipt, + struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct opaque_info_per_id *oipi; + uint32_t key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); + + for (ALL_LIST_ELEMENTS(oipt->id_list, node, nnode, oipi)) + if (oipi->opaque_id == key) + return oipi; + + return NULL; +} + +static struct opaque_info_per_id *register_opaque_lsa(struct ospf_lsa *new) +{ + struct ospf_opaque_functab *functab; + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi = NULL; + + if ((functab = ospf_opaque_functab_lookup(new)) == NULL) + goto out; + + if ((oipt = lookup_opaque_info_by_type(new)) == NULL + && (oipt = register_opaque_info_per_type(functab, new)) == NULL) + goto out; + + if ((oipi = register_opaque_info_per_id(oipt, new)) == NULL) + goto out; + +out: + return oipi; +} + +int ospf_opaque_is_owned(struct ospf_lsa *lsa) +{ + struct opaque_info_per_type *oipt = lookup_opaque_info_by_type(lsa); + + return (oipt != NULL && lookup_opaque_info_by_id(oipt, lsa) != NULL); +} + +/* + * Cleanup Link-Local LSAs assocaited with an interface that is being deleted. + * Since these LSAs are stored in the area link state database (LSDB) as opposed + * to a separate per-interface, they must be deleted from the area database. + * Since their flooding scope is solely the deleted OSPF interface, there is no + * need to attempt to flush them from the routing domain. For link local LSAs + * originated via the OSPF server API, LSA deletion before interface deletion + * is required so that the callback can access the OSPF interface address. + */ +void ospf_opaque_type9_lsa_if_cleanup(struct ospf_interface *oi) +{ + struct route_node *rn; + struct ospf_lsdb *lsdb; + struct ospf_lsa *lsa; + + lsdb = oi->area->lsdb; + LSDB_LOOP (OPAQUE_LINK_LSDB(oi->area), rn, lsa) + /* + * While the LSA shouldn't be referenced on any LSA + * lists since the flooding scoped is confined to the + * interface being deleted, clear the pointer to the + * deleted interface to avoid references and set the + * age to MAXAGE to avoid flush processing when the LSA + * is removed from the interface opaque info list. + */ + if (lsa->oi == oi) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Delete Type-9 Opaque-LSA on interface delete: [opaque-type=%u, opaque-id=%x]", + GET_OPAQUE_TYPE( + ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl( + lsa->data->id.s_addr))); + ospf_lsdb_delete(lsdb, lsa); + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + lsa->oi = NULL; + ospf_lsa_discard(lsa); + } +} + +/*------------------------------------------------------------------------* + * Following are (vty) configuration functions for Opaque-LSAs handling. + *------------------------------------------------------------------------*/ + +DEFUN (capability_opaque, + capability_opaque_cmd, + "capability opaque", + "Enable specific OSPF feature\n" + "Opaque LSA\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Check that OSPF is using default VRF */ + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "OSPF Opaque LSA is only supported in default VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Turn on the "master switch" of opaque-lsa capability. */ + if (!CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Opaque capability: OFF -> ON"); + + SET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); + ospf_renegotiate_optional_capabilities(ospf); + } + return CMD_SUCCESS; +} + +DEFUN (ospf_opaque, + ospf_opaque_cmd, + "ospf opaque-lsa", + "OSPF specific commands\n" + "Enable the Opaque-LSA capability (rfc2370)\n") +{ + return capability_opaque(self, vty, argc, argv); +} + +DEFUN (no_capability_opaque, + no_capability_opaque_cmd, + "no capability opaque", + NO_STR + "Enable specific OSPF feature\n" + "Opaque LSA\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + /* Turn off the "master switch" of opaque-lsa capability. */ + if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Opaque capability: ON -> OFF"); + + UNSET_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE); + ospf_renegotiate_optional_capabilities(ospf); + } + return CMD_SUCCESS; +} + +DEFUN (no_ospf_opaque, + no_ospf_opaque_cmd, + "no ospf opaque-lsa", + NO_STR + "OSPF specific commands\n" + "Enable the Opaque-LSA capability (rfc2370)\n") +{ + return no_capability_opaque(self, vty, argc, argv); +} + +static void ospf_opaque_register_vty(void) +{ + install_element(OSPF_NODE, &capability_opaque_cmd); + install_element(OSPF_NODE, &no_capability_opaque_cmd); + install_element(OSPF_NODE, &ospf_opaque_cmd); + install_element(OSPF_NODE, &no_ospf_opaque_cmd); + return; +} + +/*------------------------------------------------------------------------* + * Following are collection of user-registered function callers. + *------------------------------------------------------------------------*/ + +static int opaque_lsa_new_if_callback(struct list *funclist, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->new_if_hook != NULL) + if ((*functab->new_if_hook)(ifp) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int opaque_lsa_del_if_callback(struct list *funclist, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->del_if_hook != NULL) + if ((*functab->del_if_hook)(ifp) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static void opaque_lsa_ism_change_callback(struct list *funclist, + struct ospf_interface *oi, + int old_status) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->ism_change_hook != NULL) + (*functab->ism_change_hook)(oi, old_status); + + return; +} + +static void opaque_lsa_nsm_change_callback(struct list *funclist, + struct ospf_neighbor *nbr, + int old_status) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->nsm_change_hook != NULL) + (*functab->nsm_change_hook)(nbr, old_status); + return; +} + +static void opaque_lsa_config_write_router_callback(struct list *funclist, + struct vty *vty) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_router != NULL) + (*functab->config_write_router)(vty); + return; +} + +static void opaque_lsa_config_write_if_callback(struct list *funclist, + struct vty *vty, + struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_if != NULL) + (*functab->config_write_if)(vty, ifp); + return; +} + +static void opaque_lsa_config_write_debug_callback(struct list *funclist, + struct vty *vty) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->config_write_debug != NULL) + (*functab->config_write_debug)(vty); + return; +} + +static int opaque_lsa_originate_callback(struct list *funclist, + void *lsa_type_dependent) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->lsa_originator != NULL) + if ((*functab->lsa_originator)(lsa_type_dependent) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int new_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + /* This function handles ALL types of LSAs, not only opaque ones. */ + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->new_lsa_hook != NULL) + if ((*functab->new_lsa_hook)(lsa) != 0) + goto out; + rc = 0; +out: + return rc; +} + +static int del_lsa_callback(struct list *funclist, struct ospf_lsa *lsa) +{ + struct listnode *node, *nnode; + struct ospf_opaque_functab *functab; + int rc = -1; + + /* This function handles ALL types of LSAs, not only opaque ones. */ + for (ALL_LIST_ELEMENTS(funclist, node, nnode, functab)) + if (functab->del_lsa_hook != NULL) + if ((*functab->del_lsa_hook)(lsa) != 0) + goto out; + rc = 0; +out: + return rc; +} + +/*------------------------------------------------------------------------* + * Following are glue functions to call Opaque-LSA specific processing. + *------------------------------------------------------------------------*/ + +int ospf_opaque_new_if(struct interface *ifp) +{ + struct list *funclist; + int rc = -1; + + funclist = ospf_opaque_wildcard_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (opaque_lsa_new_if_callback(funclist, ifp) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +int ospf_opaque_del_if(struct interface *ifp) +{ + struct list *funclist; + int rc = -1; + + funclist = ospf_opaque_wildcard_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (opaque_lsa_del_if_callback(funclist, ifp) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +void ospf_opaque_ism_change(struct ospf_interface *oi, int old_status) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_ism_change_callback(funclist, oi, old_status); + + return; +} + +void ospf_opaque_nsm_change(struct ospf_neighbor *nbr, int old_state) +{ + struct ospf *top; + struct list *funclist; + + if ((top = oi_to_top(nbr->oi)) == NULL) + goto out; + + if (old_state != NSM_Full && nbr->state == NSM_Full) { + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { + if (!CHECK_FLAG(top->opaque, + OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Opaque-LSA: Now get operational!"); + + SET_FLAG(top->opaque, + OPAQUE_OPERATION_READY_BIT); + } + + ospf_opaque_lsa_originate_schedule(nbr->oi, NULL); + } + } else if (old_state == NSM_Full && nbr->state != NSM_Full) { +#ifdef NOTYET +/* + * If no more opaque-capable full-state neighbor remains in the + * flooding scope which corresponds to Opaque-LSA type, periodic + * LS flooding should be stopped. + */ +#endif /* NOTYET */ + ; + } + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_nsm_change_callback(funclist, nbr, old_state); + +out: + return; +} + +void ospf_opaque_config_write_router(struct vty *vty, struct ospf *ospf) +{ + struct list *funclist; + + if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) + vty_out(vty, " capability opaque\n"); + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_router_callback(funclist, vty); + + return; +} + +void ospf_opaque_config_write_if(struct vty *vty, struct interface *ifp) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_if_callback(funclist, vty, ifp); + + return; +} + +void ospf_opaque_config_write_debug(struct vty *vty) +{ + struct list *funclist; + + funclist = ospf_opaque_wildcard_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type9_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type10_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + funclist = ospf_opaque_type11_funclist; + opaque_lsa_config_write_debug_callback(funclist, vty); + + return; +} + +void show_opaque_info_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + struct lsa_header *lsah = lsa->data; + uint32_t lsid = ntohl(lsah->id.s_addr); + uint8_t opaque_type = GET_OPAQUE_TYPE(lsid); + uint32_t opaque_id = GET_OPAQUE_ID(lsid); + struct ospf_opaque_functab *functab; + json_object *jopaque = NULL; + int len, lenValid; + + /* Switch output functionality by vty address. */ + if (vty != NULL) { + if (!json) { + vty_out(vty, " Opaque-Type %u (%s)\n", opaque_type, + ospf_opaque_type_name(opaque_type)); + vty_out(vty, " Opaque-ID 0x%x\n", opaque_id); + + vty_out(vty, " Opaque-Info: %u octets of data%s\n", + ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, + VALID_OPAQUE_INFO_LEN(lsah) + ? "" + : "(Invalid length?)"); + } else { + json_object_string_add( + json, "opaqueType", + ospf_opaque_type_name(opaque_type)); + json_object_int_add(json, "opaqueId", opaque_id); + len = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE; + json_object_int_add(json, "opaqueLength", len); + lenValid = VALID_OPAQUE_INFO_LEN(lsah); + json_object_boolean_add(json, "opaqueLengthValid", + lenValid); + if (lenValid) { + jopaque = json_object_new_object(); + json_object_object_add(json, "opaqueValues", + jopaque); + } + } + } else { + zlog_debug(" Opaque-Type %u (%s)", opaque_type, + ospf_opaque_type_name(opaque_type)); + zlog_debug(" Opaque-ID 0x%x", opaque_id); + + zlog_debug(" Opaque-Info: %u octets of data%s", + ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE, + VALID_OPAQUE_INFO_LEN(lsah) ? "" + : "(Invalid length?)"); + } + + /* Call individual output functions. */ + if ((functab = ospf_opaque_functab_lookup(lsa)) != NULL) + if (functab->show_opaque_info != NULL) + (*functab->show_opaque_info)(vty, jopaque, lsa); + + return; +} + +void ospf_opaque_lsa_dump(struct stream *s, uint16_t length) +{ + struct ospf_lsa lsa = {}; + + lsa.data = (struct lsa_header *)stream_pnt(s); + lsa.size = length; + show_opaque_info_detail(NULL, &lsa, NULL); + return; +} + +static int ospf_opaque_lsa_install_hook(struct ospf_lsa *lsa) +{ + struct list *funclist; + int rc = -1; + + /* + * Some Opaque-LSA user may want to monitor every LSA installation + * into the LSDB, regardless with target LSA type. + */ + funclist = ospf_opaque_wildcard_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (new_lsa_callback(funclist, lsa) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +static int ospf_opaque_lsa_delete_hook(struct ospf_lsa *lsa) +{ + struct list *funclist; + int rc = -1; + + /* + * Some Opaque-LSA user may want to monitor every LSA deletion + * from the LSDB, regardless with target LSA type. + */ + funclist = ospf_opaque_wildcard_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type9_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type10_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + funclist = ospf_opaque_type11_funclist; + if (del_lsa_callback(funclist, lsa) != 0) + goto out; + + rc = 0; +out: + return rc; +} + +/*------------------------------------------------------------------------* + * Following are Opaque-LSA origination/refresh management functions. + *------------------------------------------------------------------------*/ + +static void ospf_opaque_type9_lsa_originate(struct event *t); +static void ospf_opaque_type10_lsa_originate(struct event *t); +static void ospf_opaque_type11_lsa_originate(struct event *t); +static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg); + +void ospf_opaque_lsa_originate_schedule(struct ospf_interface *oi, int *delay0) +{ + struct ospf *top; + struct ospf_area *area; + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt; + int delay = 0; + + if ((top = oi_to_top(oi)) == NULL || (area = oi->area) == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Invalid argument?", __func__); + return; + } + + /* It may not a right time to schedule origination now. */ + if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Not operational.", __func__); + return; /* This is not an error. */ + } + + if (delay0 != NULL) + delay = *delay0; + + /* + * There might be some entries that have been waiting for triggering + * of per opaque-type re-origination get resumed. + */ + ospf_opaque_lsa_reoriginate_resume(oi->opaque_lsa_self, (void *)oi); + ospf_opaque_lsa_reoriginate_resume(area->opaque_lsa_self, (void *)area); + ospf_opaque_lsa_reoriginate_resume(top->opaque_lsa_self, (void *)top); + + /* + * Now, schedule origination of all Opaque-LSAs per opaque-type. + */ + if (!list_isempty(ospf_opaque_type9_funclist) + && list_isempty(oi->opaque_lsa_self) + && oi->t_opaque_lsa_self == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-9 Opaque-LSA origination in %d ms later.", + delay); + oi->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type9_lsa_originate, + oi, delay, &oi->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + if (!list_isempty(ospf_opaque_type10_funclist) + && list_isempty(area->opaque_lsa_self) + && area->t_opaque_lsa_self == NULL) { + /* + * One AREA may contain multiple OIs, but above 2nd and 3rd + * conditions prevent from scheduling the originate function + * again and again. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-10 Opaque-LSA origination in %d ms later.", + delay); + area->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type10_lsa_originate, + area, delay, &area->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + if (!list_isempty(ospf_opaque_type11_funclist) + && list_isempty(top->opaque_lsa_self) + && top->t_opaque_lsa_self == NULL) { + /* + * One OSPF may contain multiple AREAs, but above 2nd and 3rd + * conditions prevent from scheduling the originate function + * again and again. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-11 Opaque-LSA origination in %d ms later.", + delay); + top->t_opaque_lsa_self = NULL; + event_add_timer_msec(master, ospf_opaque_type11_lsa_originate, + top, delay, &top->t_opaque_lsa_self); + delay += top->min_ls_interval; + } + + /* + * Following section treats a special situation that this node's + * opaque capability has changed as "ON -> OFF -> ON". + */ + if (!list_isempty(ospf_opaque_type9_funclist) + && !list_isempty(oi->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(oi->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule( + (void *)oi, OSPF_OPAQUE_LINK_LSA, + oipt->opaque_type); + } + } + + if (!list_isempty(ospf_opaque_type10_funclist) + && !list_isempty(area->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(area->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule( + (void *)area, OSPF_OPAQUE_AREA_LSA, + oipt->opaque_type); + } + } + + if (!list_isempty(ospf_opaque_type11_funclist) + && !list_isempty(top->opaque_lsa_self)) { + for (ALL_LIST_ELEMENTS(top->opaque_lsa_self, node, nnode, + oipt)) { + /* + * removed the test for + * (! list_isempty (oipt->id_list)) * Handler is + * already active. * + * because opaque cababilities ON -> OFF -> ON result in + * list_isempty (oipt->id_list) + * not being empty. + */ + if (oipt->t_opaque_lsa_self + != NULL /* Waiting for a thread call. */ + || oipt->status == PROC_SUSPEND) /* Cannot + originate + now. */ + continue; + + ospf_opaque_lsa_reoriginate_schedule((void *)top, + OSPF_OPAQUE_AS_LSA, + oipt->opaque_type); + } + } + + if (delay0 != NULL) + *delay0 = delay; +} + +static void ospf_opaque_type9_lsa_originate(struct event *t) +{ + struct ospf_interface *oi; + + oi = EVENT_ARG(t); + oi->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Timer[Type9-LSA]: Originate Opaque-LSAs for OI %s", + IF_NAME(oi)); + + opaque_lsa_originate_callback(ospf_opaque_type9_funclist, oi); +} + +static void ospf_opaque_type10_lsa_originate(struct event *t) +{ + struct ospf_area *area; + + area = EVENT_ARG(t); + area->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type10-LSA]: Originate Opaque-LSAs for Area %pI4", + &area->area_id); + + opaque_lsa_originate_callback(ospf_opaque_type10_funclist, area); +} + +static void ospf_opaque_type11_lsa_originate(struct event *t) +{ + struct ospf *top; + + top = EVENT_ARG(t); + top->t_opaque_lsa_self = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type11-LSA]: Originate AS-External Opaque-LSAs"); + + opaque_lsa_originate_callback(ospf_opaque_type11_funclist, top); +} + +static void ospf_opaque_lsa_reoriginate_resume(struct list *listtop, void *arg) +{ + struct listnode *node, *nnode; + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + + if (listtop == NULL) + goto out; + + /* + * Pickup oipt entries those which in SUSPEND status, and give + * them a chance to start re-origination now. + */ + for (ALL_LIST_ELEMENTS(listtop, node, nnode, oipt)) { + if (oipt->status != PROC_SUSPEND) + continue; + + oipt->status = PROC_NORMAL; + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) + continue; + + if ((*functab->lsa_originator)(arg) != 0) { + flog_warn(EC_OSPF_LSA, "%s: Failed (opaque-type=%u)", + __func__, oipt->opaque_type); + continue; + } + } + +out: + return; +} + +struct ospf_lsa *ospf_opaque_lsa_install(struct ospf_lsa *lsa, int rt_recalc) +{ + struct ospf_lsa *new = NULL; + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf *top; + + /* Don't take "rt_recalc" into consideration for now. */ /* XXX */ + + if (!IS_LSA_SELF(lsa)) { + new = lsa; /* Don't touch this LSA. */ + goto out; + } + + if (IS_DEBUG_OSPF(lsa, LSA_INSTALL)) + zlog_debug( + "Install Type-%u Opaque-LSA: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + /* Replace the existing lsa with the new one. */ + if ((oipt = lookup_opaque_info_by_type(lsa)) != NULL + && (oipi = lookup_opaque_info_by_id(oipt, lsa)) != NULL) { + ospf_lsa_unlock(&oipi->lsa); + oipi->lsa = ospf_lsa_lock(lsa); + } + /* Register the new lsa entry */ + else if (register_opaque_lsa(lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: register_opaque_lsa() ?", __func__); + goto out; + } + + /* + * Make use of a common mechanism (ospf_lsa_refresh_walker) + * for periodic refresh of self-originated Opaque-LSAs. + */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + if ((top = oi_to_top(lsa->oi)) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + case OSPF_OPAQUE_AREA_LSA: + if (lsa->area == NULL || (top = lsa->area->ospf) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (lsa->area != NULL && (top = lsa->area->ospf) == NULL) { + /* Above conditions must have passed. */ + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", + __func__); + goto out; + } + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + ospf_refresher_register_lsa(top, lsa); + new = lsa; + +out: + return new; +} + +struct ospf_lsa *ospf_opaque_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf *ospf; + struct ospf_opaque_functab *functab; + struct ospf_lsa *new = NULL; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL + || functab->lsa_refresher == NULL) { + /* + * Though this LSA seems to have originated on this node, the + * handling module for this "lsa-type and opaque-type" was + * already deleted sometime ago. + * Anyway, this node still has a responsibility to flush this + * LSA from the routing domain. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA[Type%d:%pI4]: Flush stray Opaque-LSA", + lsa->data->type, &lsa->data->id); + + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + ospf_lsa_flush(ospf, lsa); + } else + new = (*functab->lsa_refresher)(lsa); + + return new; +} + +/*------------------------------------------------------------------------* + * Following are re-origination/refresh/flush operations of Opaque-LSAs, + * triggered by external interventions (vty session, signaling, etc). + *------------------------------------------------------------------------*/ + +#define OSPF_OPAQUE_TIMER_ON(T, F, L, V) \ + event_add_timer_msec(master, (F), (L), (V), &(T)) + +static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, + struct ospf_area *area, uint8_t lsa_type, + uint8_t opaque_type); +static void ospf_opaque_type9_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_type10_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_type11_lsa_reoriginate_timer(struct event *t); +static void ospf_opaque_lsa_refresh_timer(struct event *t); + +void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, + uint8_t lsa_type, uint8_t opaque_type) +{ + struct ospf *top = NULL; + struct ospf_area dummy, *area = NULL; + struct ospf_interface *oi = NULL; + + struct ospf_lsa *lsa; + struct opaque_info_per_type *oipt; + void (*func)(struct event * t) = NULL; + int delay; + + switch (lsa_type) { + case OSPF_OPAQUE_LINK_LSA: + if ((oi = (struct ospf_interface *)lsa_type_dependent) + == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-9 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if ((top = oi_to_top(oi)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: OI(%s) -> TOP?", __func__, + IF_NAME(oi)); + goto out; + } + if (!list_isempty(ospf_opaque_type9_funclist) + && list_isempty(oi->opaque_lsa_self) + && oi->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-9 Opaque-LSA (opaque_type=%u): Common origination for OI(%s) has already started", + opaque_type, IF_NAME(oi)); + goto out; + } + func = ospf_opaque_type9_lsa_reoriginate_timer; + break; + case OSPF_OPAQUE_AREA_LSA: + if ((area = (struct ospf_area *)lsa_type_dependent) == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-10 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if ((top = area->ospf) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: AREA(%pI4) -> TOP?", + __func__, &area->area_id); + goto out; + } + if (!list_isempty(ospf_opaque_type10_funclist) + && list_isempty(area->opaque_lsa_self) + && area->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-10 Opaque-LSA (opaque_type=%u): Common origination for AREA(%pI4) has already started", + opaque_type, &area->area_id); + goto out; + } + func = ospf_opaque_type10_lsa_reoriginate_timer; + break; + case OSPF_OPAQUE_AS_LSA: + if ((top = (struct ospf *)lsa_type_dependent) == NULL) { + flog_warn(EC_OSPF_LSA, + "%s: Type-11 Opaque-LSA: Invalid parameter?", + __func__); + goto out; + } + if (!list_isempty(ospf_opaque_type11_funclist) + && list_isempty(top->opaque_lsa_self) + && top->t_opaque_lsa_self != NULL) { + flog_warn( + EC_OSPF_LSA, + "Type-11 Opaque-LSA (opaque_type=%u): Common origination has already started", + opaque_type); + goto out; + } + + /* Fake "area" to pass "ospf" to a lookup function later. */ + dummy.ospf = top; + area = &dummy; + + func = ospf_opaque_type11_lsa_reoriginate_timer; + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa_type); + goto out; + } + + /* It may not a right time to schedule reorigination now. */ + if (!CHECK_FLAG(top->opaque, OPAQUE_OPERATION_READY_BIT)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Not operational.", __func__); + goto out; /* This is not an error. */ + } + + /* Generate a dummy lsa to be passed for a lookup function. */ + lsa = pseudo_lsa(oi, area, lsa_type, opaque_type); + lsa->vrf_id = VRF_DEFAULT; + + if ((oipt = lookup_opaque_info_by_type(lsa)) == NULL) { + struct ospf_opaque_functab *functab; + if ((functab = ospf_opaque_functab_lookup(lsa)) == NULL) { + flog_warn( + EC_OSPF_LSA, + "%s: No associated function?: lsa_type(%u), opaque_type(%u)", + __func__, lsa_type, opaque_type); + goto out; + } + if ((oipt = register_opaque_info_per_type(functab, lsa)) + == NULL) { + flog_warn( + EC_OSPF_LSA, + "%s: Cannot get a control info?: lsa_type(%u), opaque_type(%u)", + __func__, lsa_type, opaque_type); + goto out; + } + } + + if (oipt->t_opaque_lsa_self != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Type-%u Opaque-LSA has already scheduled to RE-ORIGINATE: [opaque-type=%u]", + lsa_type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); + goto out; + } + + /* + * Different from initial origination time, in which various conditions + * (opaque capability, neighbor status etc) are assured by caller of + * the originating function "ospf_opaque_lsa_originate_schedule ()", + * it is highly possible that these conditions might not be satisfied + * at the time of re-origination function is to be called. + */ + delay = top->min_ls_interval; /* XXX */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to RE-ORIGINATE in %d ms later: [opaque-type=%u]", + lsa_type, delay, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))); + + OSPF_OPAQUE_TIMER_ON(oipt->t_opaque_lsa_self, func, oipt, delay); + +out: + return; +} + +static struct ospf_lsa *pseudo_lsa(struct ospf_interface *oi, + struct ospf_area *area, uint8_t lsa_type, + uint8_t opaque_type) +{ + static struct ospf_lsa lsa = {0}; + static struct lsa_header lsah = {0}; + uint32_t tmp; + + lsa.oi = oi; + lsa.area = area; + lsa.data = &lsah; + lsa.vrf_id = VRF_DEFAULT; + + lsah.type = lsa_type; + tmp = SET_OPAQUE_LSID(opaque_type, 0); /* Opaque-ID is unused here. */ + lsah.id.s_addr = htonl(tmp); + + return &lsa; +} + +static void ospf_opaque_type9_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct ospf *top; + struct ospf_interface *oi; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + oi = (struct ospf_interface *)oipt->owner; + if ((top = oi_to_top(oi)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE) || + !OSPF_IF_PARAM(oi, opaque_capable) || !ospf_if_is_enable(oi) || + ospf_nbr_count_opaque_capable(oi) == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-9 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type9-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for OI (%s)", + oipt->opaque_type, IF_NAME(oi)); + + (*functab->lsa_originator)(oi); +} + +static void ospf_opaque_type10_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct listnode *node, *nnode; + struct ospf *top; + struct ospf_area *area; + struct ospf_interface *oi; + int n; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + area = (struct ospf_area *)oipt->owner; + if (area == NULL || (top = area->ospf) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + /* There must be at least one "opaque-capable, full-state" neighbor. */ + n = 0; + for (ALL_LIST_ELEMENTS(area->oiflist, node, nnode, oi)) { + if ((n = ospf_nbr_count_opaque_capable(oi)) > 0) + break; + } + + if (n == 0 || !CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-10 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type10-LSA]: Re-originate Opaque-LSAs (opaque-type=%u) for Area %pI4", + oipt->opaque_type, &area->area_id); + + (*functab->lsa_originator)(area); +} + +static void ospf_opaque_type11_lsa_reoriginate_timer(struct event *t) +{ + struct opaque_info_per_type *oipt; + struct ospf_opaque_functab *functab; + struct ospf *top; + + oipt = EVENT_ARG(t); + + if ((functab = oipt->functab) == NULL + || functab->lsa_originator == NULL) { + flog_warn(EC_OSPF_LSA, "%s: No associated function?", __func__); + return; + } + + if ((top = (struct ospf *)oipt->owner) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + return; + } + + if (!CHECK_FLAG(top->config, OSPF_OPAQUE_CAPABLE)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Suspend re-origination of Type-11 Opaque-LSAs (opaque-type=%u) for a while...", + oipt->opaque_type); + + oipt->status = PROC_SUSPEND; + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Timer[Type11-LSA]: Re-originate Opaque-LSAs (opaque-type=%u).", + oipt->opaque_type); + + (*functab->lsa_originator)(top); +} + +void ospf_opaque_lsa_refresh_schedule(struct ospf_lsa *lsa0) +{ + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct ospf *top; + int delay; + + if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL + || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Invalid parameter?", __func__); + goto out; + } + + /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ + if ((lsa = oipi->lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + goto out; + } + + if (oipi->t_opaque_lsa_self != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Type-%u Opaque-LSA has already scheduled to REFRESH: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + goto out; + } + + /* Delete this lsa from neighbor retransmit-list. */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) + top = lsa0->area->ospf; + ospf_ls_retransmit_delete_nbr_as(top, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + delay = ospf_lsa_refresh_delay(lsa); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to REFRESH in %d sec later: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, delay, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + OSPF_OPAQUE_TIMER_ON(oipi->t_opaque_lsa_self, + ospf_opaque_lsa_refresh_timer, oipi, delay * 1000); +out: + return; +} + +static void ospf_opaque_lsa_refresh_timer(struct event *t) +{ + struct opaque_info_per_id *oipi; + struct ospf_opaque_functab *functab; + struct ospf_lsa *lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Timer[Opaque-LSA]: (Opaque-LSA Refresh expire)"); + + oipi = EVENT_ARG(t); + + if ((lsa = oipi->lsa) != NULL) + if ((functab = oipi->opqctl_type->functab) != NULL) + if (functab->lsa_refresher != NULL) + (*functab->lsa_refresher)(lsa); +} + +void ospf_opaque_lsa_flush_schedule(struct ospf_lsa *lsa0) +{ + struct opaque_info_per_type *oipt; + struct opaque_info_per_id *oipi; + struct ospf_lsa *lsa; + struct ospf *top; + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if ((oipt = lookup_opaque_info_by_type(lsa0)) == NULL + || (oipi = lookup_opaque_info_by_id(oipt, lsa0)) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Invalid parameter?", __func__); + goto out; + } + + /* Given "lsa0" and current "oipi->lsa" may different, but harmless. */ + if ((lsa = oipi->lsa) == NULL) { + flog_warn(EC_OSPF_LSA, "%s: Something wrong?", __func__); + goto out; + } + + if (lsa->opaque_zero_len_delete && + lsa->data->length != htons(sizeof(struct lsa_header))) { + /* minimize the size of the withdrawal: */ + /* increment the sequence number and make len just header */ + /* and update checksum */ + lsa->data->ls_seqnum = lsa_seqnum_increment(lsa); + lsa->data->length = htons(sizeof(struct lsa_header)); + lsa->data->checksum = 0; + lsa->data->checksum = ospf_lsa_checksum(lsa->data); + } + + /* Delete this lsa from neighbor retransmit-list. */ + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_ls_retransmit_delete_nbr_area(lsa->area, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + if ((lsa0->area != NULL) && (lsa0->area->ospf != NULL)) + top = lsa0->area->ospf; + ospf_ls_retransmit_delete_nbr_as(top, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + goto out; + } + + /* This lsa will be flushed and removed eventually. */ + ospf_lsa_flush(top, lsa); + + /* Dequeue listnode entry from the list. */ + listnode_delete(oipt->id_list, oipi); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Schedule Type-%u Opaque-LSA to FLUSH: [opaque-type=%u, opaque-id=%x]", + lsa->data->type, + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)), + GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr))); + + /* Disassociate internal control information with the given lsa. */ + free_opaque_info_per_id((void *)oipi); + +out: + return; +} + +void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, + struct ospf_lsa *lsa) +{ + struct ospf *top; + + if ((top = oi_to_top(nbr->oi)) == NULL) + return; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: processing self-originated Opaque-LSA", + lsa->data->type, &lsa->data->id); + + /* + * Install the stale LSA into the Link State Database, add it to the + * MaxAge list, and flush it from the OSPF routing domain. For other + * LSA types, the installation is done in the refresh function. It is + * done inline here since the opaque refresh function is dynamically + * registered when opaque LSAs are originated (which is not the case + * for stale LSAs). + */ + lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + ospf_lsa_install( + top, (lsa->data->type == OSPF_OPAQUE_LINK_LSA) ? nbr->oi : NULL, + lsa); + ospf_lsa_maxage(top, lsa); + + switch (lsa->data->type) { + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); + break; + case OSPF_OPAQUE_AS_LSA: + ospf_flood_through_as(top, NULL /*inbr*/, lsa); + break; + default: + flog_warn(EC_OSPF_LSA_UNEXPECTED, "%s: Unexpected LSA-type(%u)", + __func__, lsa->data->type); + return; + } +} + +/*------------------------------------------------------------------------* + * Following are util functions; probably be used by Opaque-LSAs only... + *------------------------------------------------------------------------*/ + +struct ospf *oi_to_top(struct ospf_interface *oi) +{ + struct ospf *top = NULL; + struct ospf_area *area; + + if (oi == NULL || (area = oi->area) == NULL + || (top = area->ospf) == NULL) + flog_warn(EC_OSPF_LSA, + "Broken relationship for \"OI -> AREA -> OSPF\"?"); + + return top; +} diff --git a/ospfd/ospf_opaque.h b/ospfd/ospf_opaque.h new file mode 100644 index 0000000..54651f8 --- /dev/null +++ b/ospfd/ospf_opaque.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of rfc2370. + * Copyright (C) 2001 KDD R&D Laboratories, Inc. + * http://www.kddlabs.co.jp/ + */ + +#ifndef _ZEBRA_OSPF_OPAQUE_H +#define _ZEBRA_OSPF_OPAQUE_H + +#include "vty.h" +#include + +#define IS_OPAQUE_LSA(type) \ + ((type) == OSPF_OPAQUE_LINK_LSA || (type) == OSPF_OPAQUE_AREA_LSA \ + || (type) == OSPF_OPAQUE_AS_LSA) + +/* + * Opaque LSA's link state ID is redefined as follows. + * + * 24 16 8 0 + * +--------+--------+--------+--------+ + * |tttttttt|........|........|........| + * +--------+--------+--------+--------+ + * |<-Type->|<------- Opaque ID ------>| + */ +#define LSID_OPAQUE_TYPE_MASK 0xff000000 /* 8 bits */ +#define LSID_OPAQUE_ID_MASK 0x00ffffff /* 24 bits */ + +#define GET_OPAQUE_TYPE(lsid) (((uint32_t)(lsid)&LSID_OPAQUE_TYPE_MASK) >> 24) + +#define GET_OPAQUE_ID(lsid) ((uint32_t)(lsid)&LSID_OPAQUE_ID_MASK) + +#define SET_OPAQUE_LSID(type, id) \ + ((((unsigned)(type) << 24) & LSID_OPAQUE_TYPE_MASK) \ + | ((id)&LSID_OPAQUE_ID_MASK)) + +/* + * Opaque LSA types will be assigned by IANA. + * + */ +#define OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA 1 +#define OPAQUE_TYPE_SYCAMORE_OPTICAL_TOPOLOGY_DESC 2 +#define OPAQUE_TYPE_GRACE_LSA 3 +#define OPAQUE_TYPE_L1VPN_LSA 5 +#define OPAQUE_TYPE_ROUTER_INFORMATION_LSA 4 +#define OPAQUE_TYPE_INTER_AS_LSA 6 +#define OPAQUE_TYPE_EXTENDED_PREFIX_LSA 7 +#define OPAQUE_TYPE_EXTENDED_LINK_LSA 8 +#define OPAQUE_TYPE_MAX 8 + +/* Following types are proposed in internet-draft documents. */ +#define OPAQUE_TYPE_8021_QOSPF 129 +#define OPAQUE_TYPE_SECONDARY_NEIGHBOR_DISCOVERY 224 +#define OPAQUE_TYPE_FLOODGATE 225 + +/* Ugly hack to make use of an unallocated value for wildcard matching! */ +#define OPAQUE_TYPE_WILDCARD 0 + +#define OPAQUE_TYPE_RANGE_UNASSIGNED(type) \ + (OPAQUE_TYPE_MAX <= (type) && (type) <= 127) + +#define OPAQUE_TYPE_RANGE_RESERVED(type) (127 < (type) && (type) <= 255) + +#define OSPF_OPAQUE_LSA_MIN_SIZE 0 /* RFC5250 imposes no minimum */ + +#define VALID_OPAQUE_INFO_LEN(lsahdr) \ + ((ntohs((lsahdr)->length) >= sizeof(struct lsa_header)) \ + && ((ntohs((lsahdr)->length) < OSPF_MAX_LSA_SIZE)) \ + && ((ntohs((lsahdr)->length) % sizeof(uint32_t)) == 0)) + +/* + * Following section defines generic TLV (type, length, value) macros, + * used for various LSA opaque usage e.g. Traffic Engineering. + */ +struct tlv_header { + uint16_t type; /* Type of Value */ + uint16_t length; /* Length of Value portion only, in bytes */ +}; + +#define TLV_HDR_SIZE (sizeof(struct tlv_header)) + +#define TLV_BODY_SIZE(tlvh) (ROUNDUP(ntohs((tlvh)->length), sizeof(uint32_t))) + +#define TLV_SIZE(tlvh) (uint32_t)(TLV_HDR_SIZE + TLV_BODY_SIZE(tlvh)) + +#define TLV_HDR_TOP(lsah) \ + (struct tlv_header *)((char *)(lsah) + OSPF_LSA_HEADER_SIZE) + +#define TLV_HDR_NEXT(tlvh) \ + (struct tlv_header *)((char *)(tlvh) + TLV_SIZE(tlvh)) + +#define TLV_HDR_SUBTLV(tlvh) \ + (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE) + +#define TLV_DATA(tlvh) (void *)((char *)(tlvh) + TLV_HDR_SIZE) + +#define TLV_TYPE(tlvh) tlvh.header.type +#define TLV_LEN(tlvh) tlvh.header.length +#define TLV_HDR(tlvh) tlvh.header + +/* Following declaration concerns the Opaque LSA management */ +enum lsa_opcode { REORIGINATE_THIS_LSA, REFRESH_THIS_LSA, FLUSH_THIS_LSA }; + +/* Prototypes. */ + +extern void ospf_opaque_init(void); +extern void ospf_opaque_term(void); +extern void ospf_opaque_finish(void); +extern int ospf_opaque_type9_lsa_init(struct ospf_interface *oi); +extern void ospf_opaque_type9_lsa_term(struct ospf_interface *oi); +extern void ospf_opaque_type9_lsa_if_cleanup(struct ospf_interface *oi); +extern int ospf_opaque_type10_lsa_init(struct ospf_area *area); +extern void ospf_opaque_type10_lsa_term(struct ospf_area *area); +extern int ospf_opaque_type11_lsa_init(struct ospf *ospf); +extern void ospf_opaque_type11_lsa_term(struct ospf *ospf); + +extern int ospf_register_opaque_functab( + uint8_t lsa_type, uint8_t opaque_type, + int (*new_if_hook)(struct interface *ifp), + int (*del_if_hook)(struct interface *ifp), + void (*ism_change_hook)(struct ospf_interface *oi, int old_status), + void (*nsm_change_hook)(struct ospf_neighbor *nbr, int old_status), + void (*config_write_router)(struct vty *vty), + void (*config_write_if)(struct vty *vty, struct interface *ifp), + void (*config_write_debug)(struct vty *vty), + void (*show_opaque_info)(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa), + int (*lsa_originator)(void *arg), + struct ospf_lsa *(*lsa_refresher)(struct ospf_lsa *lsa), + int (*new_lsa_hook)(struct ospf_lsa *lsa), + int (*del_lsa_hook)(struct ospf_lsa *lsa)); +extern void ospf_delete_opaque_functab(uint8_t lsa_type, uint8_t opaque_type); + +extern int ospf_opaque_new_if(struct interface *ifp); +extern int ospf_opaque_del_if(struct interface *ifp); +extern void ospf_opaque_ism_change(struct ospf_interface *oi, int old_status); +extern void ospf_opaque_nsm_change(struct ospf_neighbor *nbr, int old_status); +extern void ospf_opaque_config_write_router(struct vty *vty, struct ospf *ospf); +extern void ospf_opaque_config_write_if(struct vty *vty, struct interface *ifp); +extern void ospf_opaque_config_write_debug(struct vty *vty); +extern void show_opaque_info_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json); +extern void ospf_opaque_lsa_dump(struct stream *s, uint16_t length); + +extern void ospf_opaque_lsa_originate_schedule(struct ospf_interface *oi, + int *init_delay); +extern struct ospf_lsa *ospf_opaque_lsa_install(struct ospf_lsa *lsa, + int rt_recalc); +extern struct ospf_lsa *ospf_opaque_lsa_refresh(struct ospf_lsa *lsa); + +extern void ospf_opaque_lsa_reoriginate_schedule(void *lsa_type_dependent, + uint8_t lsa_type, + uint8_t opaque_type); +extern void ospf_opaque_lsa_refresh_schedule(struct ospf_lsa *lsa); +extern void ospf_opaque_lsa_flush_schedule(struct ospf_lsa *lsa); + +extern void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, + struct ospf_lsa *lsa); +extern struct ospf *oi_to_top(struct ospf_interface *oi); + +extern int ospf_opaque_is_owned(struct ospf_lsa *lsa); + +#endif /* _ZEBRA_OSPF_OPAQUE_H */ diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c new file mode 100644 index 0000000..87aacca --- /dev/null +++ b/ospfd/ospf_packet.c @@ -0,0 +1,4044 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Sending and Receiving OSPF Packets. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "monotime.h" +#include "frrevent.h" +#include "memory.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "sockunion.h" +#include "stream.h" +#include "log.h" +#include "sockopt.h" +#include "checksum.h" +#ifdef CRYPTO_INTERNAL +#include "md5.h" +#endif +#include "vrf.h" +#include "lib_errors.h" +#include "plist.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_errors.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_auth.h" + +/* + * OSPF Fragmentation / fragmented writes + * + * ospfd can support writing fragmented packets, for cases where + * kernel will not fragment IP_HDRINCL and/or multicast destined + * packets (ie TTBOMK all kernels, BSD, SunOS, Linux). However, + * SunOS, probably BSD too, clobber the user supplied IP ID and IP + * flags fields, hence user-space fragmentation will not work. + * Only Linux is known to leave IP header unmolested. + * Further, fragmentation really should be done the kernel, which already + * supports it, and which avoids nasty IP ID state problems. + * + * Fragmentation of OSPF packets can be required on networks with router + * with many many interfaces active in one area, or on networks with links + * with low MTUs. + */ +#ifdef GNU_LINUX +#define WANT_OSPF_WRITE_FRAGMENT +#endif + +/* Packet Type String. */ +const struct message ospf_packet_type_str[] = { + {OSPF_MSG_HELLO, "Hello"}, + {OSPF_MSG_DB_DESC, "Database Description"}, + {OSPF_MSG_LS_REQ, "Link State Request"}, + {OSPF_MSG_LS_UPD, "Link State Update"}, + {OSPF_MSG_LS_ACK, "Link State Acknowledgment"}, + {0}}; + +/* Minimum (besides OSPF_HEADER_SIZE) lengths for OSPF packets of + particular types, offset is the "type" field of a packet. */ +static const uint16_t ospf_packet_minlen[] = { + 0, + OSPF_HELLO_MIN_SIZE, + OSPF_DB_DESC_MIN_SIZE, + OSPF_LS_REQ_MIN_SIZE, + OSPF_LS_UPD_MIN_SIZE, + OSPF_LS_ACK_MIN_SIZE, +}; + +/* Minimum (besides OSPF_LSA_HEADER_SIZE) lengths for LSAs of particular + types, offset is the "LSA type" field. */ +static const uint16_t ospf_lsa_minlen[] = { + 0, /* OSPF_UNKNOWN_LSA */ + OSPF_ROUTER_LSA_MIN_SIZE, /* OSPF_ROUTER_LSA */ + OSPF_NETWORK_LSA_MIN_SIZE, /* OSPF_NETWORK_LSA */ + OSPF_SUMMARY_LSA_MIN_SIZE, /* OSPF_SUMMARY_LSA */ + OSPF_SUMMARY_LSA_MIN_SIZE, /* OSPF_ASBR_SUMMARY_LSA */ + OSPF_AS_EXTERNAL_LSA_MIN_SIZE, /* OSPF_AS_EXTERNAL_LSA */ + 0, /* Unsupported, OSPF_GROUP_MEMBER_LSA */ + OSPF_AS_EXTERNAL_LSA_MIN_SIZE, /* OSPF_AS_NSSA_LSA */ + 0, /* Unsupported, OSPF_EXTERNAL_ATTRIBURES_LSA */ + OSPF_OPAQUE_LSA_MIN_SIZE, /* OSPF_OPAQUE_LINK_LSA */ + OSPF_OPAQUE_LSA_MIN_SIZE, /* OSPF_OPAQUE_AREA_LSA */ + OSPF_OPAQUE_LSA_MIN_SIZE, /* OSPF_OPAQUE_AS_LSA */ +}; + +static struct ospf_packet *ospf_packet_new(size_t size) +{ + struct ospf_packet *new; + + new = XCALLOC(MTYPE_OSPF_PACKET, sizeof(struct ospf_packet)); + new->s = stream_new(size); + + return new; +} + +void ospf_packet_free(struct ospf_packet *op) +{ + if (op->s) + stream_free(op->s); + + XFREE(MTYPE_OSPF_PACKET, op); +} + +struct ospf_fifo *ospf_fifo_new(void) +{ + struct ospf_fifo *new; + + new = XCALLOC(MTYPE_OSPF_FIFO, sizeof(struct ospf_fifo)); + return new; +} + +/* Add new packet to fifo. */ +void ospf_fifo_push(struct ospf_fifo *fifo, struct ospf_packet *op) +{ + if (fifo->tail) + fifo->tail->next = op; + else + fifo->head = op; + + fifo->tail = op; + + fifo->count++; +} + +/* Add new packet to head of fifo. */ +static void ospf_fifo_push_head(struct ospf_fifo *fifo, struct ospf_packet *op) +{ + op->next = fifo->head; + + if (fifo->tail == NULL) + fifo->tail = op; + + fifo->head = op; + + fifo->count++; +} + +/* Delete first packet from fifo. */ +struct ospf_packet *ospf_fifo_pop(struct ospf_fifo *fifo) +{ + struct ospf_packet *op; + + op = fifo->head; + + if (op) { + fifo->head = op->next; + + if (fifo->head == NULL) + fifo->tail = NULL; + + fifo->count--; + } + + return op; +} + +/* Return first fifo entry. */ +struct ospf_packet *ospf_fifo_head(struct ospf_fifo *fifo) +{ + return fifo->head; +} + +/* Flush ospf packet fifo. */ +void ospf_fifo_flush(struct ospf_fifo *fifo) +{ + struct ospf_packet *op; + struct ospf_packet *next; + + for (op = fifo->head; op; op = next) { + next = op->next; + ospf_packet_free(op); + } + fifo->head = fifo->tail = NULL; + fifo->count = 0; +} + +/* Free ospf packet fifo. */ +void ospf_fifo_free(struct ospf_fifo *fifo) +{ + ospf_fifo_flush(fifo); + + XFREE(MTYPE_OSPF_FIFO, fifo); +} + +static void ospf_packet_add(struct ospf_interface *oi, struct ospf_packet *op) +{ + /* Add packet to end of queue. */ + ospf_fifo_push(oi->obuf, op); + + /* Debug of packet fifo*/ + /* ospf_fifo_debug (oi->obuf); */ +} + +static void ospf_packet_add_top(struct ospf_interface *oi, + struct ospf_packet *op) +{ + /* Add packet to head of queue. */ + ospf_fifo_push_head(oi->obuf, op); + + /* Debug of packet fifo*/ + /* ospf_fifo_debug (oi->obuf); */ +} + +static void ospf_packet_delete(struct ospf_interface *oi) +{ + struct ospf_packet *op; + + op = ospf_fifo_pop(oi->obuf); + + if (op) + ospf_packet_free(op); +} + +static struct ospf_packet *ospf_packet_dup(struct ospf_packet *op) +{ + struct ospf_packet *new; + + if (stream_get_endp(op->s) != op->length) + /* XXX size_t */ + zlog_debug( + "ospf_packet_dup stream %lu ospf_packet %u size mismatch", + (unsigned long)STREAM_SIZE(op->s), op->length); + + /* Reserve space for MD5/HMAC SHA authentication that may be added later. */ + new = ospf_packet_new(stream_get_endp(op->s) + KEYCHAIN_MAX_HASH_SIZE); + stream_copy(new->s, op->s); + + new->dst = op->dst; + new->length = op->length; + + return new; +} + +/* XXX inline */ +static unsigned int ospf_packet_authspace(struct ospf_interface *oi) +{ + int auth = 0; + + if (ospf_auth_type(oi) == OSPF_AUTH_CRYPTOGRAPHIC) + auth = KEYCHAIN_MAX_HASH_SIZE; + + return auth; +} + +static unsigned int ospf_packet_max(struct ospf_interface *oi) +{ + int max; + + max = oi->ifp->mtu - ospf_packet_authspace(oi); + + max -= (OSPF_HEADER_SIZE + sizeof(struct ip)); + + return max; +} + +static void ospf_ls_req_timer(struct event *thread) +{ + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + nbr->t_ls_req = NULL; + + /* Send Link State Request. */ + if (ospf_ls_request_count(nbr)) + ospf_ls_req_send(nbr); + + /* Set Link State Request retransmission timer. */ + OSPF_NSM_TIMER_ON(nbr->t_ls_req, ospf_ls_req_timer, nbr->v_ls_req); +} + +void ospf_ls_req_event(struct ospf_neighbor *nbr) +{ + EVENT_OFF(nbr->t_ls_req); + event_add_event(master, ospf_ls_req_timer, nbr, 0, &nbr->t_ls_req); +} + +/* Cyclic timer function. Fist registered in ospf_nbr_new () in + ospf_neighbor.c */ +void ospf_ls_upd_timer(struct event *thread) +{ + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + nbr->t_ls_upd = NULL; + + /* Send Link State Update. */ + if (ospf_ls_retransmit_count(nbr) > 0) { + struct list *update; + struct ospf_lsdb *lsdb; + int i; + int retransmit_interval; + + retransmit_interval = + OSPF_IF_PARAM(nbr->oi, retransmit_interval); + + lsdb = &nbr->ls_rxmt; + update = list_new(); + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + struct route_table *table = lsdb->type[i].db; + struct route_node *rn; + + for (rn = route_top(table); rn; rn = route_next(rn)) { + struct ospf_lsa *lsa; + + if ((lsa = rn->info) != NULL) { + /* Don't retransmit an LSA if we + received it within + the last RxmtInterval seconds - this + is to allow the + neighbour a chance to acknowledge the + LSA as it may + have ben just received before the + retransmit timer + fired. This is a small tweak to what + is in the RFC, + but it will cut out out a lot of + retransmit traffic + - MAG */ + if (monotime_since(&lsa->tv_recv, NULL) + >= retransmit_interval * 1000000LL) + listnode_add(update, rn->info); + } + } + } + + if (listcount(update) > 0) + ospf_ls_upd_send(nbr, update, OSPF_SEND_PACKET_DIRECT, + 0); + list_delete(&update); + } + + /* Set LS Update retransmission timer. */ + OSPF_NSM_TIMER_ON(nbr->t_ls_upd, ospf_ls_upd_timer, nbr->v_ls_upd); +} + +void ospf_ls_ack_timer(struct event *thread) +{ + struct ospf_interface *oi; + + oi = EVENT_ARG(thread); + oi->t_ls_ack = NULL; + + /* Send Link State Acknowledgment. */ + if (listcount(oi->ls_ack) > 0) + ospf_ls_ack_send_delayed(oi); + + /* Set LS Ack timer. */ + OSPF_ISM_TIMER_ON(oi->t_ls_ack, ospf_ls_ack_timer, oi->v_ls_ack); +} + +#ifdef WANT_OSPF_WRITE_FRAGMENT +static void ospf_write_frags(int fd, struct ospf_packet *op, struct ip *iph, + struct msghdr *msg, unsigned int maxdatasize, + unsigned int mtu, int flags, uint8_t type) +{ +#define OSPF_WRITE_FRAG_SHIFT 3 + uint16_t offset; + struct iovec *iovp; + int ret; + + assert(op->length == stream_get_endp(op->s)); + assert(msg->msg_iovlen == 2); + + /* we can but try. + * + * SunOS, BSD and BSD derived kernels likely will clear ip_id, as + * well as the IP_MF flag, making this all quite pointless. + * + * However, for a system on which IP_MF is left alone, and ip_id left + * alone or else which sets same ip_id for each fragment this might + * work, eg linux. + * + * XXX-TODO: It would be much nicer to have the kernel's use their + * existing fragmentation support to do this for us. Bugs/RFEs need to + * be raised against the various kernels. + */ + + /* set More Frag */ + iph->ip_off |= IP_MF; + + /* ip frag offset is expressed in units of 8byte words */ + offset = maxdatasize >> OSPF_WRITE_FRAG_SHIFT; + + iovp = &msg->msg_iov[1]; + + while ((stream_get_endp(op->s) - stream_get_getp(op->s)) + > maxdatasize) { + /* data length of this frag is to next offset value */ + iovp->iov_len = offset << OSPF_WRITE_FRAG_SHIFT; + iph->ip_len = iovp->iov_len + sizeof(struct ip); + assert(iph->ip_len <= mtu); + + sockopt_iphdrincl_swab_htosys(iph); + + ret = sendmsg(fd, msg, flags); + + sockopt_iphdrincl_swab_systoh(iph); + + if (ret < 0) + flog_err( + EC_LIB_SOCKET, + "*** %s: sendmsg failed to %pI4, id %d, off %d, len %d, mtu %u failed with %s", + __func__, &iph->ip_dst, iph->ip_id, iph->ip_off, + iph->ip_len, mtu, safe_strerror(errno)); + + if (IS_DEBUG_OSPF_PACKET(type - 1, SEND)) { + zlog_debug("%s: sent id %d, off %d, len %d to %pI4", + __func__, iph->ip_id, iph->ip_off, + iph->ip_len, &iph->ip_dst); + } + + iph->ip_off += offset; + stream_forward_getp(op->s, iovp->iov_len); + iovp->iov_base = stream_pnt(op->s); + } + + /* setup for final fragment */ + iovp->iov_len = stream_get_endp(op->s) - stream_get_getp(op->s); + iph->ip_len = iovp->iov_len + sizeof(struct ip); + iph->ip_off &= (~IP_MF); +} +#endif /* WANT_OSPF_WRITE_FRAGMENT */ + +static void ospf_write(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + struct ospf_interface *oi; + struct ospf_packet *op; + struct sockaddr_in sa_dst; + struct ip iph; + struct msghdr msg; + struct iovec iov[2]; + uint8_t type; + int ret, fd; + int flags = 0; + struct listnode *node; +#ifdef WANT_OSPF_WRITE_FRAGMENT + static uint16_t ipid = 0; + uint16_t maxdatasize; +#endif /* WANT_OSPF_WRITE_FRAGMENT */ +#define OSPF_WRITE_IPHL_SHIFT 2 + int pkt_count = 0; + +#ifdef GNU_LINUX + unsigned char cmsgbuf[64] = {}; + struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf; + struct in_pktinfo *pi; +#endif + fd = ospf->fd; + + if (fd < 0 || ospf->oi_running == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s failed to send, fd %d, instance %u", + __func__, fd, ospf->oi_running); + return; + } + + node = listhead(ospf->oi_write_q); + assert(node); + oi = listgetdata(node); + +#ifdef WANT_OSPF_WRITE_FRAGMENT + /* seed ipid static with low order bits of time */ + if (ipid == 0) + ipid = (time(NULL) & 0xffff); +#endif /* WANT_OSPF_WRITE_FRAGMENT */ + + while ((pkt_count < ospf->write_oi_count) && oi) { + pkt_count++; +#ifdef WANT_OSPF_WRITE_FRAGMENT + /* convenience - max OSPF data per packet */ + maxdatasize = oi->ifp->mtu - sizeof(struct ip); +#endif /* WANT_OSPF_WRITE_FRAGMENT */ + + /* Reset socket fd to use. */ + fd = ospf->fd; + + /* Check for per-interface socket */ + if (ospf->intf_socket_enabled && + (IF_OSPF_IF_INFO(oi->ifp))->oii_fd > 0) + fd = (IF_OSPF_IF_INFO(oi->ifp))->oii_fd; + + /* Get one packet from queue. */ + op = ospf_fifo_head(oi->obuf); + assert(op); + assert(op->length >= OSPF_HEADER_SIZE); + + if (op->dst.s_addr == htonl(OSPF_ALLSPFROUTERS) + || op->dst.s_addr == htonl(OSPF_ALLDROUTERS)) + ospf_if_ipmulticast(fd, oi->address, oi->ifp->ifindex); + + /* Rewrite the md5 signature & update the seq */ + ospf_auth_make(oi, op); + + /* Retrieve OSPF packet type. */ + stream_set_getp(op->s, 1); + type = stream_getc(op->s); + + /* reset get pointer */ + stream_set_getp(op->s, 0); + + memset(&iph, 0, sizeof(iph)); + memset(&sa_dst, 0, sizeof(sa_dst)); + + sa_dst.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sa_dst.sin_len = sizeof(sa_dst); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + sa_dst.sin_addr = op->dst; + sa_dst.sin_port = htons(0); + + /* Set DONTROUTE flag if dst is unicast. */ + if (oi->type != OSPF_IFTYPE_VIRTUALLINK) + if (!IN_MULTICAST(htonl(op->dst.s_addr))) + flags = MSG_DONTROUTE; + + iph.ip_hl = sizeof(struct ip) >> OSPF_WRITE_IPHL_SHIFT; + /* it'd be very strange for header to not be 4byte-word aligned + * but.. */ + if (sizeof(struct ip) + > (unsigned int)(iph.ip_hl << OSPF_WRITE_IPHL_SHIFT)) + iph.ip_hl++; /* we presume sizeof(struct ip) cant + overflow ip_hl.. */ + + iph.ip_v = IPVERSION; + iph.ip_tos = IPTOS_PREC_INTERNETCONTROL; + iph.ip_len = (iph.ip_hl << OSPF_WRITE_IPHL_SHIFT) + op->length; + +#if defined(__DragonFly__) + /* + * DragonFly's raw socket expects ip_len/ip_off in network byte + * order. + */ + iph.ip_len = htons(iph.ip_len); +#endif + +#ifdef WANT_OSPF_WRITE_FRAGMENT + /* XXX-MT: not thread-safe at all.. + * XXX: this presumes this is only programme sending OSPF + * packets + * otherwise, no guarantee ipid will be unique + */ + iph.ip_id = ++ipid; +#endif /* WANT_OSPF_WRITE_FRAGMENT */ + + iph.ip_off = 0; + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + iph.ip_ttl = OSPF_VL_IP_TTL; + else + iph.ip_ttl = OSPF_IP_TTL; + iph.ip_p = IPPROTO_OSPFIGP; + iph.ip_sum = 0; + iph.ip_src.s_addr = oi->address->u.prefix4.s_addr; + iph.ip_dst.s_addr = op->dst.s_addr; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (caddr_t)&sa_dst; + msg.msg_namelen = sizeof(sa_dst); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + iov[0].iov_base = (char *)&iph; + iov[0].iov_len = iph.ip_hl << OSPF_WRITE_IPHL_SHIFT; + iov[1].iov_base = stream_pnt(op->s); + iov[1].iov_len = op->length; + +#ifdef GNU_LINUX + msg.msg_control = (caddr_t)cm; + cm->cmsg_level = SOL_IP; + cm->cmsg_type = IP_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + pi = (struct in_pktinfo *)CMSG_DATA(cm); + pi->ipi_ifindex = oi->ifp->ifindex; + + msg.msg_controllen = cm->cmsg_len; +#endif + +/* Sadly we can not rely on kernels to fragment packets + * because of either IP_HDRINCL and/or multicast + * destination being set. + */ + +#ifdef WANT_OSPF_WRITE_FRAGMENT + if (op->length > maxdatasize) + ospf_write_frags(fd, op, &iph, &msg, maxdatasize, + oi->ifp->mtu, flags, type); +#endif /* WANT_OSPF_WRITE_FRAGMENT */ + + /* send final fragment (could be first) */ + sockopt_iphdrincl_swab_htosys(&iph); + ret = sendmsg(fd, &msg, flags); + sockopt_iphdrincl_swab_systoh(&iph); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s to %pI4, id %d, off %d, len %d, interface %s, mtu %u:", + __func__, &iph.ip_dst, iph.ip_id, iph.ip_off, + iph.ip_len, oi->ifp->name, oi->ifp->mtu); + + /* sendmsg will return EPERM if firewall is blocking sending. + * This is a normal situation when 'ip nhrp map multicast xxx' + * is being used to send multicast packets to DMVPN peers. In + * that case the original message is blocked with iptables rule + * causing the EPERM result + */ + if (ret < 0 && errno != EPERM) + flog_err( + EC_LIB_SOCKET, + "*** sendmsg in %s failed to %pI4, id %d, off %d, len %d, interface %s, mtu %u: %s", + __func__, &iph.ip_dst, iph.ip_id, iph.ip_off, + iph.ip_len, oi->ifp->name, oi->ifp->mtu, + safe_strerror(errno)); + + /* Show debug sending packet. */ + if (IS_DEBUG_OSPF_PACKET(type - 1, SEND)) { + if (IS_DEBUG_OSPF_PACKET(type - 1, DETAIL)) { + zlog_debug( + "-----------------------------------------------------"); + stream_set_getp(op->s, 0); + ospf_packet_dump(op->s); + } + + zlog_debug("%s sent to [%pI4] via [%s].", + lookup_msg(ospf_packet_type_str, type, NULL), + &op->dst, IF_NAME(oi)); + + if (IS_DEBUG_OSPF_PACKET(type - 1, DETAIL)) + zlog_debug( + "-----------------------------------------------------"); + } + + switch (type) { + case OSPF_MSG_HELLO: + oi->hello_out++; + break; + case OSPF_MSG_DB_DESC: + oi->db_desc_out++; + break; + case OSPF_MSG_LS_REQ: + oi->ls_req_out++; + break; + case OSPF_MSG_LS_UPD: + oi->ls_upd_out++; + break; + case OSPF_MSG_LS_ACK: + oi->ls_ack_out++; + break; + default: + break; + } + + /* Now delete packet from queue. */ + ospf_packet_delete(oi); + + /* Move this interface to the tail of write_q to + serve everyone in a round robin fashion */ + list_delete_node(ospf->oi_write_q, node); + if (ospf_fifo_head(oi->obuf) == NULL) { + oi->on_write_q = 0; + oi = NULL; + } else + listnode_add(ospf->oi_write_q, oi); + + /* Setup to service from the head of the queue again */ + if (!list_isempty(ospf->oi_write_q)) { + node = listhead(ospf->oi_write_q); + oi = listgetdata(node); + } + } + + /* If packets still remain in queue, call write thread. */ + if (!list_isempty(ospf->oi_write_q)) + event_add_write(master, ospf_write, ospf, ospf->fd, + &ospf->t_write); +} + +/* OSPF Hello message read -- RFC2328 Section 10.5. */ +static void ospf_hello(struct ip *iph, struct ospf_header *ospfh, + struct stream *s, struct ospf_interface *oi, int size) +{ + struct ospf_hello *hello; + struct ospf_neighbor *nbr; + int old_state; + struct prefix p; + + /* increment statistics. */ + oi->hello_in++; + + hello = (struct ospf_hello *)stream_pnt(s); + + /* If Hello is myself, silently discard. */ + if (IPV4_ADDR_SAME(&ospfh->router_id, &oi->ospf->router_id)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) { + zlog_debug( + "ospf_header[%s/%pI4]: selforiginated, dropping.", + lookup_msg(ospf_packet_type_str, ospfh->type, + NULL), + &iph->ip_src); + } + return; + } + + /* get neighbor prefix. */ + p.family = AF_INET; + p.prefixlen = ip_masklen(hello->network_mask); + p.u.prefix4 = iph->ip_src; + + /* Compare network mask. */ + /* Checking is ignored for Point-to-Point and Virtual link. */ + /* Checking is also ignored for Point-to-Multipoint with /32 prefix */ + if (oi->type != OSPF_IFTYPE_POINTOPOINT + && oi->type != OSPF_IFTYPE_VIRTUALLINK + && !(oi->type == OSPF_IFTYPE_POINTOMULTIPOINT + && oi->address->prefixlen == IPV4_MAX_BITLEN)) + if (oi->address->prefixlen != p.prefixlen) { + flog_warn( + EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: NetworkMask mismatch on %s (configured prefix length is %d, but hello packet indicates %d).", + &ospfh->router_id, IF_NAME(oi), + (int)oi->address->prefixlen, (int)p.prefixlen); + return; + } + + /* Compare Router Dead Interval. */ + if (OSPF_IF_PARAM(oi, v_wait) != ntohl(hello->dead_interval)) { + flog_warn( + EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: RouterDeadInterval mismatch on %s (expected %u, but received %u).", + &ospfh->router_id, IF_NAME(oi), + OSPF_IF_PARAM(oi, v_wait), ntohl(hello->dead_interval)); + return; + } + + /* Compare Hello Interval - ignored if fast-hellos are set. */ + if (OSPF_IF_PARAM(oi, fast_hello) == 0) { + if (OSPF_IF_PARAM(oi, v_hello) + != ntohs(hello->hello_interval)) { + flog_warn( + EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: HelloInterval mismatch on %s (expected %u, but received %u).", + &ospfh->router_id, IF_NAME(oi), + OSPF_IF_PARAM(oi, v_hello), + ntohs(hello->hello_interval)); + return; + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Packet %pI4 [Hello:RECV]: Options on %s %s vrf %s", + &ospfh->router_id, IF_NAME(oi), + ospf_options_dump(hello->options), + ospf_vrf_id_to_name(oi->ospf->vrf_id)); + +/* Compare options. */ +#define REJECT_IF_TBIT_ON 1 /* XXX */ +#ifdef REJECT_IF_TBIT_ON + if (CHECK_FLAG(hello->options, OSPF_OPTION_MT)) { + /* + * This router does not support non-zero TOS. + * Drop this Hello packet not to establish neighbor + * relationship. + */ + flog_warn(EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: T-bit ON on %s, drop it.", + &ospfh->router_id, IF_NAME(oi)); + return; + } +#endif /* REJECT_IF_TBIT_ON */ + + if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE) && + OSPF_IF_PARAM(oi, opaque_capable) && + CHECK_FLAG(hello->options, OSPF_OPTION_O)) { + /* + * This router does know the correct usage of O-bit + * the bit should be set in DD packet only. + */ + flog_warn(EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: O-bit abuse? on %s", + &ospfh->router_id, IF_NAME(oi)); +#ifdef STRICT_OBIT_USAGE_CHECK + return; /* Reject this packet. */ +#else /* STRICT_OBIT_USAGE_CHECK */ + UNSET_FLAG(hello->options, OSPF_OPTION_O); /* Ignore O-bit. */ +#endif /* STRICT_OBIT_USAGE_CHECK */ + } + + /* new for NSSA is to ensure that NP is on and E is off */ + + if (oi->area->external_routing == OSPF_AREA_NSSA) { + if (!(CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_NP) + && CHECK_FLAG(hello->options, OSPF_OPTION_NP) + && !CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_E) + && !CHECK_FLAG(hello->options, OSPF_OPTION_E))) { + flog_warn( + EC_OSPF_PACKET, + "NSSA-Packet-%pI4[Hello:RECV]: my options: %x, his options %x", + &ospfh->router_id, OPTIONS(oi), + hello->options); + return; + } + if (IS_DEBUG_OSPF_NSSA) + zlog_debug("NSSA-Hello:RECV:Packet from %pI4:", + &ospfh->router_id); + } else + /* The setting of the E-bit found in the Hello Packet's Options + field must match this area's ExternalRoutingCapability A + mismatch causes processing to stop and the packet to be + dropped. The setting of the rest of the bits in the Hello + Packet's Options field should be ignored. */ + if (CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_E) + != CHECK_FLAG(hello->options, OSPF_OPTION_E)) { + flog_warn( + EC_OSPF_PACKET, + "Packet %pI4 [Hello:RECV]: my options: %x, his options %x", + &ospfh->router_id, OPTIONS(oi), + hello->options); + return; + } + + /* get neighbour struct */ + nbr = ospf_nbr_get(oi, ospfh, iph, &p); + + /* neighbour must be valid, ospf_nbr_get creates if none existed */ + assert(nbr); + + old_state = nbr->state; + + /* Add event to thread. */ + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_HelloReceived); + + /* RFC2328 Section 9.5.1 + If the router is not eligible to become Designated Router, + (snip) It must also send an Hello Packet in reply to an + Hello Packet received from any eligible neighbor (other than + the current Designated Router and Backup Designated Router). */ + if (oi->type == OSPF_IFTYPE_NBMA) + if (PRIORITY(oi) == 0 && hello->priority > 0 + && IPV4_ADDR_CMP(&DR(oi), &iph->ip_src) + && IPV4_ADDR_CMP(&BDR(oi), &iph->ip_src)) + OSPF_NSM_TIMER_ON(nbr->t_hello_reply, + ospf_hello_reply_timer, + OSPF_HELLO_REPLY_DELAY); + + /* on NBMA network type, it happens to receive bidirectional Hello + packet + without advance 1-Way Received event. + To avoid incorrect DR-seletion, raise 1-Way Received event.*/ + if (oi->type == OSPF_IFTYPE_NBMA + && (old_state == NSM_Down || old_state == NSM_Attempt)) { + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_OneWayReceived); + nbr->priority = hello->priority; + nbr->d_router = hello->d_router; + nbr->bd_router = hello->bd_router; + return; + } + + if (ospf_nbr_bidirectional(&oi->ospf->router_id, hello->neighbors, + size - OSPF_HELLO_MIN_SIZE)) { + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_TwoWayReceived); + nbr->options |= hello->options; + } else { + /* If the router is DR_OTHER, RESTARTER will not wait + * until it receives the hello from it if it receives + * from DR and BDR. + * So, helper might receives ONW_WAY hello from + * RESTARTER. So not allowing to change the state if it + * receives one_way hellow when it acts as HELPER for + * that specific neighbor. + */ + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_OneWayReceived); + /* Set neighbor information. */ + nbr->priority = hello->priority; + nbr->d_router = hello->d_router; + nbr->bd_router = hello->bd_router; + return; + } + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) { + /* As per the GR Conformance Test Case 7.2. Section 3 + * "Also, if X was the Designated Router on network segment S + * when the helping relationship began, Y maintains X as the + * Designated Router until the helping relationship is + * terminated." + * When I am helper for this neighbor, I should not trigger the + * ISM Events. Also Intentionally not setting the priority and + * other fields so that when the neighbor exits the Grace + * period, it can handle if there is any change before GR and + * after GR. */ + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "%s, Neighbor is under GR Restart, hence ignoring the ISM Events", + __PRETTY_FUNCTION__); + } else { + /* If neighbor itself declares DR and no BDR exists, + cause event BackupSeen */ + if (IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->d_router)) + if (hello->bd_router.s_addr == INADDR_ANY + && oi->state == ISM_Waiting) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_BackupSeen); + + /* neighbor itself declares BDR. */ + if (oi->state == ISM_Waiting + && IPV4_ADDR_SAME(&nbr->address.u.prefix4, + &hello->bd_router)) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_BackupSeen); + + /* had not previously. */ + if ((IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->d_router) + && IPV4_ADDR_CMP(&nbr->address.u.prefix4, &nbr->d_router)) + || (IPV4_ADDR_CMP(&nbr->address.u.prefix4, &hello->d_router) + && IPV4_ADDR_SAME(&nbr->address.u.prefix4, + &nbr->d_router))) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); + + /* had not previously. */ + if ((IPV4_ADDR_SAME(&nbr->address.u.prefix4, &hello->bd_router) + && IPV4_ADDR_CMP(&nbr->address.u.prefix4, &nbr->bd_router)) + || (IPV4_ADDR_CMP(&nbr->address.u.prefix4, + &hello->bd_router) + && IPV4_ADDR_SAME(&nbr->address.u.prefix4, + &nbr->bd_router))) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); + + /* Neighbor priority check. */ + if (nbr->priority >= 0 && nbr->priority != hello->priority) + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); + } + + /* Set neighbor information. */ + nbr->priority = hello->priority; + nbr->d_router = hello->d_router; + nbr->bd_router = hello->bd_router; + + /* + * RFC 3623 - Section 2: + * "If the restarting router determines that it was the Designated + * Router on a given segment prior to the restart, it elects + * itself as the Designated Router again. The restarting router + * knows that it was the Designated Router if, while the + * associated interface is in Waiting state, a Hello packet is + * received from a neighbor listing the router as the Designated + * Router". + */ + if (oi->area->ospf->gr_info.restart_in_progress + && oi->state == ISM_Waiting + && IPV4_ADDR_SAME(&hello->d_router, &oi->address->u.prefix4)) + DR(oi) = hello->d_router; +} + +/* Save DD flags/options/Seqnum received. */ +static void ospf_db_desc_save_current(struct ospf_neighbor *nbr, + struct ospf_db_desc *dd) +{ + nbr->last_recv.flags = dd->flags; + nbr->last_recv.options = dd->options; + nbr->last_recv.dd_seqnum = ntohl(dd->dd_seqnum); +} + +/* Process rest of DD packet. */ +static void ospf_db_desc_proc(struct stream *s, struct ospf_interface *oi, + struct ospf_neighbor *nbr, + struct ospf_db_desc *dd, uint16_t size) +{ + struct ospf_lsa *new, *find; + struct lsa_header *lsah; + + stream_forward_getp(s, OSPF_DB_DESC_MIN_SIZE); + for (size -= OSPF_DB_DESC_MIN_SIZE; size >= OSPF_LSA_HEADER_SIZE; + size -= OSPF_LSA_HEADER_SIZE) { + lsah = (struct lsa_header *)stream_pnt(s); + stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); + + /* Unknown LS type. */ + if (lsah->type < OSPF_MIN_LSA || lsah->type >= OSPF_MAX_LSA) { + flog_warn(EC_OSPF_PACKET, + "Packet [DD:RECV]: Unknown LS type %d.", + lsah->type); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + return; + } + + if (IS_OPAQUE_LSA(lsah->type) + && !CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { + flog_warn(EC_OSPF_PACKET, + "LSA[Type%d:%pI4] from %pI4: Opaque capability mismatch?", + lsah->type, &lsah->id, &lsah->adv_router); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + return; + } + + switch (lsah->type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + /* Check for stub area. Reject if AS-External from stub + but + allow if from NSSA. */ + if (oi->area->external_routing == OSPF_AREA_STUB) { + flog_warn( + EC_OSPF_PACKET, + "Packet [DD:RECV]: LSA[Type%d:%pI4] from %s area.", + lsah->type, &lsah->id, + (oi->area->external_routing + == OSPF_AREA_STUB) + ? "STUB" + : "NSSA"); + OSPF_NSM_EVENT_SCHEDULE(nbr, + NSM_SeqNumberMismatch); + return; + } + break; + default: + break; + } + + /* Create LS-request object. */ + new = ospf_ls_request_new(lsah); + + /* Lookup received LSA, then add LS request list. */ + find = ospf_lsa_lookup_by_header(oi->area, lsah); + + /* ospf_lsa_more_recent is fine with NULL pointers */ + switch (ospf_lsa_more_recent(find, new)) { + case -1: + /* Neighbour has a more recent LSA, we must request it + */ + ospf_ls_request_add(nbr, new); + fallthrough; + case 0: + /* If we have a copy of this LSA, it's either less + * recent + * and we're requesting it from neighbour (the case + * above), or + * it's as recent and we both have same copy (this + * case). + * + * In neither of these two cases is there any point in + * describing our copy of the LSA to the neighbour in a + * DB-Summary packet, if we're still intending to do so. + * + * See: draft-ogier-ospf-dbex-opt-00.txt, describing the + * backward compatible optimisation to OSPF DB Exchange + * / + * DB Description process implemented here. + */ + if (find) + ospf_lsdb_delete(&nbr->db_sum, find); + ospf_lsa_discard(new); + break; + default: + /* We have the more recent copy, nothing specific to do: + * - no need to request neighbours stale copy + * - must leave DB summary list copy alone + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Packet [DD:RECV]: LSA received Type %d, ID %pI4 is not recent.", + lsah->type, &lsah->id); + ospf_lsa_discard(new); + } + } + + /* Master */ + if (IS_SET_DD_MS(nbr->dd_flags)) { + nbr->dd_seqnum++; + + /* Both sides have no More, then we're done with Exchange */ + if (!IS_SET_DD_M(dd->flags) && !IS_SET_DD_M(nbr->dd_flags)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_ExchangeDone); + else + ospf_db_desc_send(nbr); + } + /* Slave */ + else { + nbr->dd_seqnum = ntohl(dd->dd_seqnum); + + /* Send DD packet in reply. + * + * Must be done to acknowledge the Master's DD, regardless of + * whether we have more LSAs ourselves to describe. + * + * This function will clear the 'More' bit, if after this DD + * we have no more LSAs to describe to the master.. + */ + ospf_db_desc_send(nbr); + + /* Slave can raise ExchangeDone now, if master is also done */ + if (!IS_SET_DD_M(dd->flags) && !IS_SET_DD_M(nbr->dd_flags)) + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_ExchangeDone); + } + + /* Save received neighbor values from DD. */ + ospf_db_desc_save_current(nbr, dd); + + if (!nbr->t_ls_req) + ospf_ls_req_send(nbr); +} + +static int ospf_db_desc_is_dup(struct ospf_db_desc *dd, + struct ospf_neighbor *nbr) +{ + /* Is DD duplicated? */ + if (dd->options == nbr->last_recv.options + && dd->flags == nbr->last_recv.flags + && dd->dd_seqnum == htonl(nbr->last_recv.dd_seqnum)) + return 1; + + return 0; +} + +/* OSPF Database Description message read -- RFC2328 Section 10.6. */ +static void ospf_db_desc(struct ip *iph, struct ospf_header *ospfh, + struct stream *s, struct ospf_interface *oi, + uint16_t size) +{ + struct ospf_db_desc *dd; + struct ospf_neighbor *nbr; + + /* Increment statistics. */ + oi->db_desc_in++; + + dd = (struct ospf_db_desc *)stream_pnt(s); + + nbr = ospf_nbr_lookup(oi, iph, ospfh); + if (nbr == NULL) { + flog_warn(EC_OSPF_PACKET, "Packet[DD]: Unknown Neighbor %pI4", + &ospfh->router_id); + return; + } + + /* Check MTU. */ + if ((OSPF_IF_PARAM(oi, mtu_ignore) == 0) + && (ntohs(dd->mtu) > oi->ifp->mtu)) { + flog_warn( + EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 MTU %u is larger than [%s]'s MTU %u", + &nbr->router_id, ntohs(dd->mtu), IF_NAME(oi), + oi->ifp->mtu); + return; + } + + /* + * XXX HACK by Hasso Tepper. Setting N/P bit in NSSA area DD packets is + * not + * required. In fact at least JunOS sends DD packets with P bit clear. + * Until proper solution is developped, this hack should help. + * + * Update: According to the RFCs, N bit is specified /only/ for Hello + * options, unfortunately its use in DD options is not specified. Hence + * some + * implementations follow E-bit semantics and set it in DD options, and + * some + * treat it as unspecified and hence follow the directive "default for + * options is clear", ie unset. + * + * Reset the flag, as ospfd follows E-bit semantics. + */ + if ((oi->area->external_routing == OSPF_AREA_NSSA) + && (CHECK_FLAG(nbr->options, OSPF_OPTION_NP)) + && (!CHECK_FLAG(dd->options, OSPF_OPTION_NP))) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Packet[DD]: Neighbour %pI4: Has NSSA capability, sends with N bit clear in DD options", + &nbr->router_id); + SET_FLAG(dd->options, OSPF_OPTION_NP); + } + +#ifdef REJECT_IF_TBIT_ON + if (CHECK_FLAG(dd->options, OSPF_OPTION_MT)) { + /* + * In Hello protocol, optional capability must have checked + * to prevent this T-bit enabled router be my neighbor. + */ + flog_warn(EC_OSPF_PACKET, "Packet[DD]: Neighbor %pI4: T-bit on?", + &nbr->router_id); + return; + } +#endif /* REJECT_IF_TBIT_ON */ + + if (CHECK_FLAG(dd->options, OSPF_OPTION_O) && + (!CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE) || + !OSPF_IF_PARAM(oi, opaque_capable))) { + /* + * This node is not configured to handle O-bit, for now. + * Clear it to ignore unsupported capability proposed by + * neighbor. + */ + UNSET_FLAG(dd->options, OSPF_OPTION_O); + } + + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "%s:Packet[DD]: Neighbor %pI4 state is %s, seq_num:0x%x, local:0x%x", + ospf_get_name(oi->ospf), &nbr->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL), + ntohl(dd->dd_seqnum), nbr->dd_seqnum); + + /* Process DD packet by neighbor status. */ + switch (nbr->state) { + case NSM_Down: + case NSM_Attempt: + case NSM_TwoWay: + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "Packet[DD]: Neighbor %pI4 state is %s, packet discarded.", + &nbr->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, + NULL)); + break; + case NSM_Init: + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_TwoWayReceived); + /* If the new state is ExStart, the processing of the current + packet should then continue in this new state by falling + through to case ExStart below. */ + if (nbr->state != NSM_ExStart) + break; + fallthrough; + case NSM_ExStart: + /* Initial DBD */ + if ((IS_SET_DD_ALL(dd->flags) == OSPF_DD_FLAG_ALL) + && (size == OSPF_DB_DESC_MIN_SIZE)) { + if (IPV4_ADDR_CMP(&nbr->router_id, &oi->ospf->router_id) + > 0) { + /* We're Slave---obey */ + if (CHECK_FLAG(oi->ospf->config, + OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "Packet[DD]: Neighbor %pI4 Negotiation done (Slave).", + &nbr->router_id); + + nbr->dd_seqnum = ntohl(dd->dd_seqnum); + + /* Reset I/MS */ + UNSET_FLAG(nbr->dd_flags, + (OSPF_DD_FLAG_MS | OSPF_DD_FLAG_I)); + } else { + /* We're Master, ignore the initial DBD from + * Slave */ + if (CHECK_FLAG(oi->ospf->config, + OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "Packet[DD]: Neighbor %pI4: Initial DBD from Slave, ignoring.", + &nbr->router_id); + break; + } + } + /* Ack from the Slave */ + else if (!IS_SET_DD_MS(dd->flags) && !IS_SET_DD_I(dd->flags) + && ntohl(dd->dd_seqnum) == nbr->dd_seqnum + && IPV4_ADDR_CMP(&nbr->router_id, &oi->ospf->router_id) + < 0) { + zlog_info( + "Packet[DD]: Neighbor %pI4 Negotiation done (Master).", + &nbr->router_id); + /* Reset I, leaving MS */ + UNSET_FLAG(nbr->dd_flags, OSPF_DD_FLAG_I); + } else { + flog_warn(EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 Negotiation fails.", + &nbr->router_id); + break; + } + + /* This is where the real Options are saved */ + nbr->options = dd->options; + + if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE) && + OSPF_IF_PARAM(oi, opaque_capable)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Neighbor[%pI4] is %sOpaque-capable.", + &nbr->router_id, + CHECK_FLAG(nbr->options, OSPF_OPTION_O) + ? "" + : "NOT "); + + if (!CHECK_FLAG(nbr->options, OSPF_OPTION_O) + && IPV4_ADDR_SAME(&DR(oi), + &nbr->address.u.prefix4)) { + flog_warn( + EC_OSPF_PACKET, + "DR-neighbor[%pI4] is NOT opaque-capable; Opaque-LSAs cannot be reliably advertised in this network.", + &nbr->router_id); + /* This situation is undesirable, but not a real + * error. */ + } + } + + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_NegotiationDone); + + /* continue processing rest of packet. */ + ospf_db_desc_proc(s, oi, nbr, dd, size); + break; + case NSM_Exchange: + if (ospf_db_desc_is_dup(dd, nbr)) { + if (IS_SET_DD_MS(nbr->dd_flags)) + /* Master: discard duplicated DD packet. */ + zlog_info( + "Packet[DD] (Master): Neighbor %pI4 packet duplicated.", + &nbr->router_id); + else + /* Slave: cause to retransmit the last Database + Description. */ + { + zlog_info( + "Packet[DD] [Slave]: Neighbor %pI4 packet duplicated.", + &nbr->router_id); + ospf_db_desc_resend(nbr); + } + break; + } + + /* Otherwise DD packet should be checked. */ + /* Check Master/Slave bit mismatch */ + if (IS_SET_DD_MS(dd->flags) + != IS_SET_DD_MS(nbr->last_recv.flags)) { + flog_warn(EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 MS-bit mismatch.", + &nbr->router_id); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Packet[DD]: dd->flags=%d, nbr->dd_flags=%d", + dd->flags, nbr->dd_flags); + break; + } + + /* Check initialize bit is set. */ + if (IS_SET_DD_I(dd->flags)) { + zlog_info("Packet[DD]: Neighbor %pI4 I-bit set.", + &nbr->router_id); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + break; + } + + /* Check DD Options. */ + if (dd->options != nbr->options) { + flog_warn(EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 options mismatch.", + &nbr->router_id); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + break; + } + + /* Check DD sequence number. */ + if ((IS_SET_DD_MS(nbr->dd_flags) + && ntohl(dd->dd_seqnum) != nbr->dd_seqnum) + || (!IS_SET_DD_MS(nbr->dd_flags) + && ntohl(dd->dd_seqnum) != nbr->dd_seqnum + 1)) { + flog_warn( + EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 sequence number mismatch.", + &nbr->router_id); + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + break; + } + + /* Continue processing rest of packet. */ + ospf_db_desc_proc(s, oi, nbr, dd, size); + break; + case NSM_Loading: + case NSM_Full: + if (ospf_db_desc_is_dup(dd, nbr)) { + if (IS_SET_DD_MS(nbr->dd_flags)) { + /* Master should discard duplicate DD packet. */ + zlog_info( + "Packet[DD]: Neighbor %pI4 duplicated, packet discarded.", + &nbr->router_id); + break; + } else { + if (monotime_since(&nbr->last_send_ts, NULL) + < nbr->v_inactivity * 1000000LL) { + /* In states Loading and Full the slave + must resend + its last Database Description packet + in response to + duplicate Database Description + packets received + from the master. For this reason the + slave must + wait RouterDeadInterval seconds + before freeing the + last Database Description packet. + Reception of a + Database Description packet from the + master after + this interval will generate a + SeqNumberMismatch + neighbor event. RFC2328 Section 10.8 + */ + ospf_db_desc_resend(nbr); + break; + } + } + } + + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_SeqNumberMismatch); + break; + default: + flog_warn(EC_OSPF_PACKET, + "Packet[DD]: Neighbor %pI4 NSM illegal status %u.", + &nbr->router_id, nbr->state); + break; + } +} + +#define OSPF_LSA_KEY_SIZE 12 /* type(4) + id(4) + ar(4) */ + +/* OSPF Link State Request Read -- RFC2328 Section 10.7. */ +static void ospf_ls_req(struct ip *iph, struct ospf_header *ospfh, + struct stream *s, struct ospf_interface *oi, + uint16_t size) +{ + struct ospf_neighbor *nbr; + uint32_t ls_type; + struct in_addr ls_id; + struct in_addr adv_router; + struct ospf_lsa *find; + struct list *ls_upd; + unsigned int length; + + /* Increment statistics. */ + oi->ls_req_in++; + + nbr = ospf_nbr_lookup(oi, iph, ospfh); + if (nbr == NULL) { + flog_warn(EC_OSPF_PACKET, + "Link State Request: Unknown Neighbor %pI4", + &ospfh->router_id); + return; + } + + /* Neighbor State should be Exchange or later. */ + if (nbr->state != NSM_Exchange && nbr->state != NSM_Loading + && nbr->state != NSM_Full) { + flog_warn( + EC_OSPF_PACKET, + "Link State Request received from %pI4: Neighbor state is %s, packet discarded.", + &ospfh->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); + return; + } + + /* Send Link State Update for ALL requested LSAs. */ + ls_upd = list_new(); + length = OSPF_HEADER_SIZE + OSPF_LS_UPD_MIN_SIZE; + + while (size >= OSPF_LSA_KEY_SIZE) { + /* Get one slice of Link State Request. */ + ls_type = stream_getl(s); + ls_id.s_addr = stream_get_ipv4(s); + adv_router.s_addr = stream_get_ipv4(s); + + /* Verify LSA type. */ + if (ls_type < OSPF_MIN_LSA || ls_type >= OSPF_MAX_LSA) { + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); + list_delete(&ls_upd); + return; + } + + /* Search proper LSA in LSDB. */ + find = ospf_lsa_lookup(oi->ospf, oi->area, ls_type, ls_id, + adv_router); + if (find == NULL) { + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); + list_delete(&ls_upd); + return; + } + + /* Packet overflows MTU size, send immediately. */ + if (length + ntohs(find->data->length) > ospf_packet_max(oi)) { + ospf_ls_upd_send(nbr, ls_upd, + OSPF_SEND_PACKET_DIRECT, 0); + + /* Only remove list contents. Keep ls_upd. */ + list_delete_all_node(ls_upd); + + length = OSPF_HEADER_SIZE + OSPF_LS_UPD_MIN_SIZE; + } + + /* Append LSA to update list. */ + listnode_add(ls_upd, find); + length += ntohs(find->data->length); + + size -= OSPF_LSA_KEY_SIZE; + } + + /* Send rest of Link State Update. */ + if (listcount(ls_upd) > 0) { + ospf_ls_upd_send(nbr, ls_upd, OSPF_SEND_PACKET_DIRECT, 0); + + list_delete(&ls_upd); + } else + list_delete(&ls_upd); +} + +/* Get the list of LSAs from Link State Update packet. + And process some validation -- RFC2328 Section 13. (1)-(2). */ +static struct list *ospf_ls_upd_list_lsa(struct ospf_neighbor *nbr, + struct stream *s, + struct ospf_interface *oi, size_t size) +{ + uint16_t count, sum; + uint32_t length; + struct lsa_header *lsah; + struct ospf_lsa *lsa; + struct list *lsas; + + lsas = list_new(); + + count = stream_getl(s); + size -= OSPF_LS_UPD_MIN_SIZE; /* # LSAs */ + + for (; size >= OSPF_LSA_HEADER_SIZE && count > 0; + size -= length, stream_forward_getp(s, length), count--) { + lsah = (struct lsa_header *)stream_pnt(s); + length = ntohs(lsah->length); + + if (length > size) { + flog_warn( + EC_OSPF_PACKET, + "Link State Update: LSA length exceeds packet size."); + break; + } + + if (length < OSPF_LSA_HEADER_SIZE) { + flog_warn(EC_OSPF_PACKET, + "Link State Update: LSA length too small."); + break; + } + + /* Validate the LSA's LS checksum. */ + sum = lsah->checksum; + if (!ospf_lsa_checksum_valid(lsah)) { + /* (bug #685) more details in a one-line message make it + * possible + * to identify problem source on the one hand and to + * have a better + * chance to compress repeated messages in syslog on the + * other */ + flog_warn( + EC_OSPF_PACKET, + "Link State Update: LSA checksum error %x/%x, ID=%pI4 from: nbr %pI4, router ID %pI4, adv router %pI4", + sum, lsah->checksum, &lsah->id, + &nbr->src, &nbr->router_id, + &lsah->adv_router); + continue; + } + + /* Examine the LSA's LS type. */ + if (lsah->type < OSPF_MIN_LSA || lsah->type >= OSPF_MAX_LSA) { + flog_warn(EC_OSPF_PACKET, + "Link State Update: Unknown LS type %d", + lsah->type); + continue; + } + + /* + * What if the received LSA's age is greater than MaxAge? + * Treat it as a MaxAge case -- endo. + */ + if (ntohs(lsah->ls_age) > OSPF_LSA_MAXAGE) + lsah->ls_age = htons(OSPF_LSA_MAXAGE); + + if (CHECK_FLAG(nbr->options, OSPF_OPTION_O)) { +#ifdef STRICT_OBIT_USAGE_CHECK + if ((IS_OPAQUE_LSA(lsah->type) + && !CHECK_FLAG(lsah->options, OSPF_OPTION_O)) + || (!IS_OPAQUE_LSA(lsah->type) + && CHECK_FLAG(lsah->options, OSPF_OPTION_O))) { + /* + * This neighbor must know the exact usage of + * O-bit; + * the bit will be set in Type-9,10,11 LSAs + * only. + */ + flog_warn(EC_OSPF_PACKET, + "LSA[Type%d:%pI4]: O-bit abuse?", + lsah->type, &lsah->id); + continue; + } +#endif /* STRICT_OBIT_USAGE_CHECK */ + + /* Do not take in AS External Opaque-LSAs if we are a + * stub. */ + if (lsah->type == OSPF_OPAQUE_AS_LSA + && nbr->oi->area->external_routing + != OSPF_AREA_DEFAULT) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: We are a stub, don't take this LSA.", + lsah->type, + &lsah->id); + continue; + } + } else if (IS_OPAQUE_LSA(lsah->type)) { + flog_warn( + EC_OSPF_PACKET, + "LSA[Type%d:%pI4] from %pI4: Opaque capability mismatch?", + lsah->type, &lsah->id, &lsah->adv_router); + continue; + } + + /* Create OSPF LSA instance. */ + lsa = ospf_lsa_new_and_data(length); + + lsa->vrf_id = oi->ospf->vrf_id; + /* We may wish to put some error checking if type NSSA comes in + and area not in NSSA mode */ + switch (lsah->type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + lsa->area = NULL; + break; + case OSPF_OPAQUE_LINK_LSA: + lsa->oi = oi; /* Remember incoming interface for + flooding control. */ + fallthrough; + default: + lsa->area = oi->area; + break; + } + + memcpy(lsa->data, lsah, length); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[Type%d:%pI4]: %p new LSA created with Link State Update", + lsa->data->type, &lsa->data->id, + (void *)lsa); + listnode_add(lsas, lsa); + } + + return lsas; +} + +/* Cleanup Update list. */ +static void ospf_upd_list_clean(struct list *lsas) +{ + struct listnode *node, *nnode; + struct ospf_lsa *lsa; + + for (ALL_LIST_ELEMENTS(lsas, node, nnode, lsa)) + ospf_lsa_discard(lsa); + + list_delete(&lsas); +} + +/* OSPF Link State Update message read -- RFC2328 Section 13. */ +static void ospf_ls_upd(struct ospf *ospf, struct ip *iph, + struct ospf_header *ospfh, struct stream *s, + struct ospf_interface *oi, uint16_t size) +{ + struct ospf_neighbor *nbr; + struct list *lsas; + struct listnode *node, *nnode; + struct ospf_lsa *lsa = NULL; + /* unsigned long ls_req_found = 0; */ + + /* Dis-assemble the stream, update each entry, re-encapsulate for + * flooding */ + + /* Increment statistics. */ + oi->ls_upd_in++; + + /* Check neighbor. */ + nbr = ospf_nbr_lookup(oi, iph, ospfh); + if (nbr == NULL) { + flog_warn(EC_OSPF_PACKET, + "Link State Update: Unknown Neighbor %pI4 on int: %s", + &ospfh->router_id, IF_NAME(oi)); + return; + } + + /* Check neighbor state. */ + if (nbr->state < NSM_Exchange) { + if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) + zlog_debug( + "Link State Update: Neighbor[%pI4] state %s is less than Exchange", + &ospfh->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, + NULL)); + return; + } + + /* Get list of LSAs from Link State Update packet. - Also performs + * Stages 1 (validate LSA checksum) and 2 (check for LSA consistent + * type) of section 13. + */ + lsas = ospf_ls_upd_list_lsa(nbr, s, oi, size); + + if (lsas == NULL) + return; +#define DISCARD_LSA(L, N) \ + { \ + if (IS_DEBUG_OSPF_EVENT) \ + zlog_debug( \ + "ospf_lsa_discard() in ospf_ls_upd() point %d: lsa %p" \ + " Type-%d", \ + N, (void *)lsa, (int)lsa->data->type); \ + ospf_lsa_discard(L); \ + continue; \ + } + + /* Process each LSA received in the one packet. + * + * Numbers in parentheses, e.g. (1), (2), etc., and the corresponding + * text below are from the steps in RFC 2328, Section 13. + */ + for (ALL_LIST_ELEMENTS(lsas, node, nnode, lsa)) { + struct ospf_lsa *ls_ret, *current; + int ret = 1; + + if (IS_DEBUG_OSPF(lsa, LSA)) + zlog_debug("LSA Type-%d from %pI4, ID: %pI4, ADV: %pI4", + lsa->data->type, &ospfh->router_id, + &lsa->data->id, &lsa->data->adv_router); + + listnode_delete(lsas, + lsa); /* We don't need it in list anymore */ + + /* (1) Validate Checksum - Done above by ospf_ls_upd_list_lsa() + */ + + /* (2) LSA Type - Done above by ospf_ls_upd_list_lsa() */ + + /* (3) Do not take in AS External LSAs if we are a stub or NSSA. + */ + + /* Do not take in AS NSSA if this neighbor and we are not NSSA + */ + + /* Do take in Type-7's if we are an NSSA */ + + /* If we are also an ABR, later translate them to a Type-5 + * packet */ + + /* Later, an NSSA Re-fresh can Re-fresh Type-7's and an ABR will + translate them to a separate Type-5 packet. */ + + if (lsa->data->type == OSPF_AS_EXTERNAL_LSA) + /* Reject from STUB or NSSA */ + if (nbr->oi->area->external_routing + != OSPF_AREA_DEFAULT) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "Incoming External LSA Discarded: We are NSSA/STUB Area"); + DISCARD_LSA(lsa, 1); + } + + if (lsa->data->type == OSPF_AS_NSSA_LSA) + if (nbr->oi->area->external_routing != OSPF_AREA_NSSA) { + if (IS_DEBUG_OSPF_NSSA) + zlog_debug( + "Incoming NSSA LSA Discarded: Not NSSA Area"); + DISCARD_LSA(lsa, 2); + } + + /* VU229804: Router-LSA Adv-ID must be equal to LS-ID */ + if (lsa->data->type == OSPF_ROUTER_LSA) + if (!IPV4_ADDR_SAME(&lsa->data->id, + &lsa->data->adv_router)) { + flog_err( + EC_OSPF_ROUTER_LSA_MISMATCH, + "Incoming Router-LSA from %pI4 with Adv-ID[%pI4] != LS-ID[%pI4]", + &ospfh->router_id, &lsa->data->id, + &lsa->data->adv_router); + flog_err( + EC_OSPF_DOMAIN_CORRUPT, + "OSPF domain compromised by attack or corruption. Verify correct operation of -ALL- OSPF routers."); + DISCARD_LSA(lsa, 0); + } + + /* Find the LSA in the current database. */ + + current = ospf_lsa_lookup_by_header(oi->area, lsa->data); + + /* (4) If the LSA's LS age is equal to MaxAge, and there is + currently + no instance of the LSA in the router's link state database, + and none of router's neighbors are in states Exchange or + Loading, + then take the following actions: */ + + if (IS_LSA_MAXAGE(lsa) && !current + && ospf_check_nbr_status(oi->ospf)) { + /* (4a) Response Link State Acknowledgment. */ + ospf_ls_ack_send(nbr, lsa); + + /* (4b) Discard LSA. */ + if (IS_DEBUG_OSPF(lsa, LSA)) { + zlog_debug( + "Link State Update[%s]: LS age is equal to MaxAge.", + dump_lsa_key(lsa)); + } + DISCARD_LSA(lsa, 3); + } + + if (IS_OPAQUE_LSA(lsa->data->type) + && IPV4_ADDR_SAME(&lsa->data->adv_router, + &oi->ospf->router_id)) { + /* + * Even if initial flushing seems to be completed, there + * might + * be a case that self-originated LSA with MaxAge still + * remain + * in the routing domain. + * Just send an LSAck message to cease retransmission. + */ + if (IS_LSA_MAXAGE(lsa)) { + zlog_info("LSA[%s]: Boomerang effect?", + dump_lsa_key(lsa)); + ospf_ls_ack_send(nbr, lsa); + ospf_lsa_discard(lsa); + + if (current != NULL && !IS_LSA_MAXAGE(current)) + ospf_opaque_lsa_refresh_schedule( + current); + continue; + } + + /* + * If an instance of self-originated Opaque-LSA is not + * found + * in the LSDB, there are some possible cases here. + * + * 1) This node lost opaque-capability after restart. + * 2) Else, a part of opaque-type is no more supported. + * 3) Else, a part of opaque-id is no more supported. + * + * Anyway, it is still this node's responsibility to + * flush it. + * Otherwise, the LSA instance remains in the routing + * domain + * until its age reaches to MaxAge. + */ + /* XXX: We should deal with this for *ALL* LSAs, not + * just opaque */ + if (current == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "LSA[%s]: Previously originated Opaque-LSA, not found in the LSDB.", + dump_lsa_key(lsa)); + + SET_FLAG(lsa->flags, OSPF_LSA_SELF); + + ospf_ls_ack_send(nbr, lsa); + + if (!ospf->gr_info.restart_in_progress) { + ospf_opaque_self_originated_lsa_received( + nbr, lsa); + continue; + } + } + } + + /* It might be happen that received LSA is self-originated + * network LSA, but + * router ID is changed. So, we should check if LSA is a + * network-LSA whose + * Link State ID is one of the router's own IP interface + * addresses but whose + * Advertising Router is not equal to the router's own Router ID + * According to RFC 2328 12.4.2 and 13.4 this LSA should be + * flushed. + */ + + if (lsa->data->type == OSPF_NETWORK_LSA) { + struct listnode *oinode, *oinnode; + struct ospf_interface *out_if; + int Flag = 0; + + for (ALL_LIST_ELEMENTS(oi->ospf->oiflist, oinode, + oinnode, out_if)) { + if (out_if == NULL) + break; + + if ((IPV4_ADDR_SAME(&out_if->address->u.prefix4, + &lsa->data->id)) + && (!(IPV4_ADDR_SAME( + &oi->ospf->router_id, + &lsa->data->adv_router)))) { + if (out_if->network_lsa_self) { + ospf_lsa_flush_area( + lsa, out_if->area); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "ospf_lsa_discard() in ospf_ls_upd() point 9: lsa %p Type-%d", + (void *)lsa, + (int)lsa->data + ->type); + ospf_lsa_discard(lsa); + Flag = 1; + } + break; + } + } + if (Flag) + continue; + } + + /* (5) Find the instance of this LSA that is currently contained + in the router's link state database. If there is no + database copy, or the received LSA is more recent than + the database copy the following steps must be performed. + (The sub steps from RFC 2328 section 13 step (5) will be + performed in + ospf_flood() ) */ + + if (current == NULL + || (ret = ospf_lsa_more_recent(current, lsa)) < 0) { + /* CVE-2017-3224 */ + if (current && (IS_LSA_MAX_SEQ(current)) + && (IS_LSA_MAX_SEQ(lsa)) && !IS_LSA_MAXAGE(lsa)) { + zlog_debug( + "Link State Update[%s]: has Max Seq and higher checksum but not MaxAge. Dropping it", + dump_lsa_key(lsa)); + + DISCARD_LSA(lsa, 4); + } + + /* Actual flooding procedure. */ + if (ospf_flood(oi->ospf, nbr, current, lsa) + < 0) /* Trap NSSA later. */ + DISCARD_LSA(lsa, 5); + + /* GR: check for network topology change. */ + if (ospf->gr_info.restart_in_progress && + ((lsa->data->type == OSPF_ROUTER_LSA || + lsa->data->type == OSPF_NETWORK_LSA))) + ospf_gr_check_lsdb_consistency(oi->ospf, + oi->area); + + continue; + } + + /* (6) Else, If there is an instance of the LSA on the sending + neighbor's Link state request list, an error has occurred in + the Database Exchange process. In this case, restart the + Database Exchange process by generating the neighbor event + BadLSReq for the sending neighbor and stop processing the + Link State Update packet. */ + + if (ospf_ls_request_lookup(nbr, lsa)) { + OSPF_NSM_EVENT_SCHEDULE(nbr, NSM_BadLSReq); + flog_warn( + EC_OSPF_PACKET, + "LSA[%s] instance exists on Link state request list", + dump_lsa_key(lsa)); + + /* Clean list of LSAs. */ + ospf_upd_list_clean(lsas); + /* this lsa is not on lsas list already. */ + ospf_lsa_discard(lsa); + return; + } + + /* If the received LSA is the same instance as the database copy + (i.e., neither one is more recent) the following two steps + should be performed: */ + + if (ret == 0) { + /* If the LSA is listed in the Link state retransmission + list + for the receiving adjacency, the router itself is + expecting + an acknowledgment for this LSA. The router should + treat the + received LSA as an acknowledgment by removing the LSA + from + the Link state retransmission list. This is termed + an + "implied acknowledgment". */ + + ls_ret = ospf_ls_retransmit_lookup(nbr, lsa); + + if (ls_ret != NULL) { + ospf_ls_retransmit_delete(nbr, ls_ret); + + /* Delayed acknowledgment sent if advertisement + received + from Designated Router, otherwise do nothing. + */ + if (oi->state == ISM_Backup) + if (NBR_IS_DR(nbr)) + listnode_add( + oi->ls_ack, + ospf_lsa_lock(lsa)); + + DISCARD_LSA(lsa, 6); + } else + /* Acknowledge the receipt of the LSA by sending a + Link State Acknowledgment packet back out the + receiving + interface. */ + { + ospf_ls_ack_send(nbr, lsa); + DISCARD_LSA(lsa, 7); + } + } + + /* The database copy is more recent. If the database copy + has LS age equal to MaxAge and LS sequence number equal to + MaxSequenceNumber, simply discard the received LSA without + acknowledging it. (In this case, the LSA's LS sequence number + is + wrapping, and the MaxSequenceNumber LSA must be completely + flushed before any new LSA instance can be introduced). */ + + else if (ret > 0) /* Database copy is more recent */ + { + if (IS_LSA_MAXAGE(current) + && current->data->ls_seqnum + == htonl(OSPF_MAX_SEQUENCE_NUMBER)) { + DISCARD_LSA(lsa, 8); + } + /* Otherwise, as long as the database copy has not been + sent in a + Link State Update within the last MinLSArrival + seconds, send the + database copy back to the sending neighbor, + encapsulated within + a Link State Update Packet. The Link State Update + Packet should + be sent directly to the neighbor. In so doing, do not + put the + database copy of the LSA on the neighbor's link state + retransmission list, and do not acknowledge the + received (less + recent) LSA instance. */ + else { + if (monotime_since(¤t->tv_orig, NULL) + >= ospf->min_ls_arrival * 1000LL) + /* Trap NSSA type later.*/ + ospf_ls_upd_send_lsa( + nbr, current, + OSPF_SEND_PACKET_DIRECT); + DISCARD_LSA(lsa, 9); + } + } + } +#undef DISCARD_LSA + + assert(listcount(lsas) == 0); + list_delete(&lsas); +} + +/* OSPF Link State Acknowledgment message read -- RFC2328 Section 13.7. */ +static void ospf_ls_ack(struct ip *iph, struct ospf_header *ospfh, + struct stream *s, struct ospf_interface *oi, + uint16_t size) +{ + struct ospf_neighbor *nbr; + + /* increment statistics. */ + oi->ls_ack_in++; + + nbr = ospf_nbr_lookup(oi, iph, ospfh); + if (nbr == NULL) { + flog_warn(EC_OSPF_PACKET, + "Link State Acknowledgment: Unknown Neighbor %pI4", + &ospfh->router_id); + return; + } + + if (nbr->state < NSM_Exchange) { + if (IS_DEBUG_OSPF(nsm, NSM_EVENTS)) + zlog_debug( + "Link State Acknowledgment: Neighbor[%pI4] state %s is less than Exchange", + &ospfh->router_id, + lookup_msg(ospf_nsm_state_msg, nbr->state, + NULL)); + return; + } + + while (size >= OSPF_LSA_HEADER_SIZE) { + struct ospf_lsa *lsa, *lsr; + + lsa = ospf_lsa_new(); + lsa->data = (struct lsa_header *)stream_pnt(s); + lsa->vrf_id = oi->ospf->vrf_id; + + /* lsah = (struct lsa_header *) stream_pnt (s); */ + size -= OSPF_LSA_HEADER_SIZE; + stream_forward_getp(s, OSPF_LSA_HEADER_SIZE); + + if (lsa->data->type < OSPF_MIN_LSA + || lsa->data->type >= OSPF_MAX_LSA) { + lsa->data = NULL; + ospf_lsa_discard(lsa); + continue; + } + + lsr = ospf_ls_retransmit_lookup(nbr, lsa); + + if (lsr != NULL && ospf_lsa_more_recent(lsr, lsa) == 0) { + ospf_ls_retransmit_delete(nbr, lsr); + ospf_check_and_gen_init_seq_lsa(oi, lsa); + } + + lsa->data = NULL; + ospf_lsa_discard(lsa); + } + + return; +} + +static struct stream *ospf_recv_packet(struct ospf *ospf, int fd, + struct interface **ifp, + struct stream *ibuf) +{ + int ret; + struct ip *iph; + uint16_t ip_len; + ifindex_t ifindex = 0; + struct iovec iov; + /* Header and data both require alignment. */ + char buff[CMSG_SPACE(SOPT_SIZE_CMSG_IFINDEX_IPV4())]; + struct msghdr msgh; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = (caddr_t)buff; + msgh.msg_controllen = sizeof(buff); + + ret = stream_recvmsg(ibuf, fd, &msgh, MSG_DONTWAIT, + OSPF_MAX_PACKET_SIZE + 1); + if (ret < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + flog_warn(EC_OSPF_PACKET, "stream_recvmsg failed: %s", + safe_strerror(errno)); + return NULL; + } + if ((unsigned int)ret < sizeof(struct ip)) { + flog_warn( + EC_OSPF_PACKET, + "%s: discarding runt packet of length %d (ip header size is %u)", + __func__, ret, (unsigned int)sizeof(iph)); + return NULL; + } + + /* Note that there should not be alignment problems with this assignment + because this is at the beginning of the stream data buffer. */ + iph = (struct ip *)STREAM_DATA(ibuf); + sockopt_iphdrincl_swab_systoh(iph); + + ip_len = iph->ip_len; + +#if defined(__FreeBSD__) && (__FreeBSD_version < 1000000) + /* + * Kernel network code touches incoming IP header parameters, + * before protocol specific processing. + * + * 1) Convert byteorder to host representation. + * --> ip_len, ip_id, ip_off + * + * 2) Adjust ip_len to strip IP header size! + * --> If user process receives entire IP packet via RAW + * socket, it must consider adding IP header size to + * the "ip_len" field of "ip" structure. + * + * For more details, see . + */ + ip_len = ip_len + (iph->ip_hl << 2); +#endif + +#if defined(__DragonFly__) + /* + * in DragonFly's raw socket, ip_len/ip_off are read + * in network byte order. + * As OpenBSD < 200311 adjust ip_len to strip IP header size! + */ + ip_len = ntohs(iph->ip_len) + (iph->ip_hl << 2); +#endif + + ifindex = getsockopt_ifindex(AF_INET, &msgh); + + *ifp = if_lookup_by_index(ifindex, ospf->vrf_id); + + if (ret != ip_len) { + flog_warn( + EC_OSPF_PACKET, + "%s read length mismatch: ip_len is %d, but recvmsg returned %d", + __func__, ip_len, ret); + return NULL; + } + + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: fd %d(%s) on interface %d(%s)", __func__, fd, + ospf_get_name(ospf), ifindex, + *ifp ? (*ifp)->name : "Unknown"); + return ibuf; +} + +static struct ospf_interface * +ospf_associate_packet_vl(struct ospf *ospf, struct interface *ifp, + struct ip *iph, struct ospf_header *ospfh) +{ + struct ospf_interface *rcv_oi; + struct ospf_vl_data *vl_data; + struct ospf_area *vl_area; + struct listnode *node; + + if (IN_MULTICAST(ntohl(iph->ip_dst.s_addr)) + || !OSPF_IS_AREA_BACKBONE(ospfh)) + return NULL; + + /* look for local OSPF interface matching the destination + * to determine Area ID. We presume therefore the destination address + * is unique, or at least (for "unnumbered" links), not used in other + * areas + */ + if ((rcv_oi = ospf_if_lookup_by_local_addr(ospf, NULL, iph->ip_dst)) + == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { + vl_area = + ospf_area_lookup_by_area_id(ospf, vl_data->vl_area_id); + if (!vl_area) + continue; + + if (OSPF_AREA_SAME(&vl_area, &rcv_oi->area) + && IPV4_ADDR_SAME(&vl_data->vl_peer, &ospfh->router_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("associating packet with %s", + IF_NAME(vl_data->vl_oi)); + if (!CHECK_FLAG(vl_data->vl_oi->ifp->flags, IFF_UP)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "This VL is not up yet, sorry"); + return NULL; + } + + return vl_data->vl_oi; + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("couldn't find any VL to associate the packet with"); + + return NULL; +} + +static int ospf_check_area_id(struct ospf_interface *oi, + struct ospf_header *ospfh) +{ + /* Check match the Area ID of the receiving interface. */ + if (OSPF_AREA_SAME(&oi->area, &ospfh)) + return 1; + + return 0; +} + +/* Unbound socket will accept any Raw IP packets if proto is matched. + To prevent it, compare src IP address and i/f address with masking + i/f network mask. */ +static int ospf_check_network_mask(struct ospf_interface *oi, + struct in_addr ip_src) +{ + struct in_addr mask, me, him; + + if (oi->type == OSPF_IFTYPE_POINTOPOINT + || oi->type == OSPF_IFTYPE_VIRTUALLINK) + return 1; + + /* Ignore mask check for max prefix length (32) */ + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT + && oi->address->prefixlen == IPV4_MAX_BITLEN) + return 1; + + masklen2ip(oi->address->prefixlen, &mask); + + me.s_addr = oi->address->u.prefix4.s_addr & mask.s_addr; + him.s_addr = ip_src.s_addr & mask.s_addr; + + if (IPV4_ADDR_SAME(&me, &him)) + return 1; + + return 0; +} + +/* Verify, that given link/TOS records are properly sized/aligned and match + Router-LSA "# links" and "# TOS" fields as specified in RFC2328 A.4.2. */ +static unsigned ospf_router_lsa_links_examin(struct router_lsa_link *link, + uint16_t linkbytes, + const uint16_t num_links) +{ + unsigned counted_links = 0, thislinklen; + + while (linkbytes >= OSPF_ROUTER_LSA_LINK_SIZE) { + thislinklen = + OSPF_ROUTER_LSA_LINK_SIZE + 4 * link->m[0].tos_count; + if (thislinklen > linkbytes) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: length error in link block #%u", + __func__, counted_links); + return MSG_NG; + } + link = (struct router_lsa_link *)((caddr_t)link + thislinklen); + linkbytes -= thislinklen; + counted_links++; + } + if (counted_links != num_links) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: %u link blocks declared, %u present", + __func__, num_links, counted_links); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify, that the given LSA is properly sized/aligned (including type-specific + minimum length constraint). */ +static unsigned ospf_lsa_examin(struct lsa_header *lsah, const uint16_t lsalen, + const uint8_t headeronly) +{ + unsigned ret; + struct router_lsa *rlsa; + if (lsah->type < OSPF_MAX_LSA && ospf_lsa_minlen[lsah->type] + && lsalen < OSPF_LSA_HEADER_SIZE + ospf_lsa_minlen[lsah->type]) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: undersized (%u B) %s", __func__, lsalen, + lookup_msg(ospf_lsa_type_msg, lsah->type, + NULL)); + return MSG_NG; + } + switch (lsah->type) { + case OSPF_ROUTER_LSA: { + /* + * RFC2328 A.4.2, LSA header + 4 bytes followed by N>=0 + * (12+)-byte link blocks + */ + size_t linkbytes_len = lsalen - OSPF_LSA_HEADER_SIZE + - OSPF_ROUTER_LSA_MIN_SIZE; + + /* + * LSA link blocks are variable length but always multiples of + * 4; basic sanity check + */ + if (linkbytes_len % 4 != 0) + return MSG_NG; + + if (headeronly) + return MSG_OK; + + rlsa = (struct router_lsa *)lsah; + + ret = ospf_router_lsa_links_examin( + (struct router_lsa_link *)rlsa->link, + linkbytes_len, + ntohs(rlsa->links)); + break; + } + case OSPF_AS_EXTERNAL_LSA: + /* RFC2328 A.4.5, LSA header + 4 bytes followed by N>=1 12-bytes long + * blocks */ + case OSPF_AS_NSSA_LSA: + /* RFC3101 C, idem */ + ret = (lsalen - OSPF_LSA_HEADER_SIZE + - OSPF_AS_EXTERNAL_LSA_MIN_SIZE) + % 12 + ? MSG_NG + : MSG_OK; + break; + /* Following LSA types are considered OK length-wise as soon as their + * minimum + * length constraint is met and length of the whole LSA is a multiple of + * 4 + * (basic LSA header size is already a multiple of 4). */ + case OSPF_NETWORK_LSA: + /* RFC2328 A.4.3, LSA header + 4 bytes followed by N>=1 router-IDs */ + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + /* RFC2328 A.4.4, LSA header + 4 bytes followed by N>=1 4-bytes TOS + * blocks */ + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + /* RFC5250 A.2, "some number of octets (of application-specific + * data) padded to 32-bit alignment." This is considered + * equivalent + * to 4-byte alignment of all other LSA types, see + * OSPF-ALIGNMENT.txt + * file for the detailed analysis of this passage. */ + ret = lsalen % 4 ? MSG_NG : MSG_OK; + break; + default: + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: unsupported LSA type 0x%02x", __func__, + lsah->type); + return MSG_NG; + } + if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: alignment error in %s", __func__, + lookup_msg(ospf_lsa_type_msg, lsah->type, NULL)); + return ret; +} + +/* Verify if the provided input buffer is a valid sequence of LSAs. This + includes verification of LSA blocks length/alignment and dispatching + of deeper-level checks. */ +static unsigned +ospf_lsaseq_examin(struct lsa_header *lsah, /* start of buffered data */ + size_t length, const uint8_t headeronly, + /* When declared_num_lsas is not 0, compare it to the real + number of LSAs + and treat the difference as an error. */ + const uint32_t declared_num_lsas) +{ + uint32_t counted_lsas = 0; + + while (length) { + uint16_t lsalen; + if (length < OSPF_LSA_HEADER_SIZE) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: undersized (%zu B) trailing (#%u) LSA header", + __func__, length, counted_lsas); + return MSG_NG; + } + /* save on ntohs() calls here and in the LSA validator */ + lsalen = ntohs(lsah->length); + if (lsalen < OSPF_LSA_HEADER_SIZE) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: malformed LSA header #%u, declared length is %u B", + __func__, counted_lsas, lsalen); + return MSG_NG; + } + if (headeronly) { + /* less checks here and in ospf_lsa_examin() */ + if (MSG_OK != ospf_lsa_examin(lsah, lsalen, 1)) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: malformed header-only LSA #%u", + __func__, counted_lsas); + return MSG_NG; + } + lsah = (struct lsa_header *)((caddr_t)lsah + + OSPF_LSA_HEADER_SIZE); + length -= OSPF_LSA_HEADER_SIZE; + } else { + /* make sure the input buffer is deep enough before + * further checks */ + if (lsalen > length) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: anomaly in LSA #%u: declared length is %u B, buffered length is %zu B", + __func__, counted_lsas, lsalen, + length); + return MSG_NG; + } + if (MSG_OK != ospf_lsa_examin(lsah, lsalen, 0)) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: malformed LSA #%u", + __func__, counted_lsas); + return MSG_NG; + } + lsah = (struct lsa_header *)((caddr_t)lsah + lsalen); + length -= lsalen; + } + counted_lsas++; + } + + if (declared_num_lsas && counted_lsas != declared_num_lsas) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: #LSAs declared (%u) does not match actual (%u)", + __func__, declared_num_lsas, counted_lsas); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify a complete OSPF packet for proper sizing/alignment. */ +static unsigned ospf_packet_examin(struct ospf_header *oh, + const unsigned bytesonwire) +{ + uint16_t bytesdeclared, bytesauth; + unsigned ret; + struct ospf_ls_update *lsupd; + + /* Length, 1st approximation. */ + if (bytesonwire < OSPF_HEADER_SIZE) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: undersized (%u B) packet", __func__, + bytesonwire); + return MSG_NG; + } + /* Now it is safe to access header fields. Performing length check, + * allow + * for possible extra bytes of crypto auth/padding, which are not + * counted + * in the OSPF header "length" field. */ + if (oh->version != OSPF_VERSION) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: invalid (%u) protocol version", + __func__, oh->version); + return MSG_NG; + } + bytesdeclared = ntohs(oh->length); + if (ntohs(oh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) + bytesauth = 0; + else { + if (oh->u.crypt.auth_data_len > KEYCHAIN_MAX_HASH_SIZE) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: unsupported crypto auth length (%u B)", + __func__, oh->u.crypt.auth_data_len); + return MSG_NG; + } + bytesauth = oh->u.crypt.auth_data_len; + } + if (bytesdeclared + bytesauth > bytesonwire) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: packet length error (%u real, %u+%u declared)", + __func__, bytesonwire, bytesdeclared, + bytesauth); + return MSG_NG; + } + /* Length, 2nd approximation. The type-specific constraint is checked + against declared length, not amount of bytes on wire. */ + if (oh->type >= OSPF_MSG_HELLO && oh->type <= OSPF_MSG_LS_ACK + && bytesdeclared + < OSPF_HEADER_SIZE + ospf_packet_minlen[oh->type]) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: undersized (%u B) %s packet", __func__, + bytesdeclared, + lookup_msg(ospf_packet_type_str, oh->type, + NULL)); + return MSG_NG; + } + switch (oh->type) { + case OSPF_MSG_HELLO: + /* RFC2328 A.3.2, packet header + OSPF_HELLO_MIN_SIZE bytes + followed + by N>=0 router-IDs. */ + ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_HELLO_MIN_SIZE) + % 4 + ? MSG_NG + : MSG_OK; + break; + case OSPF_MSG_DB_DESC: + /* RFC2328 A.3.3, packet header + OSPF_DB_DESC_MIN_SIZE bytes + followed + by N>=0 header-only LSAs. */ + ret = ospf_lsaseq_examin( + (struct lsa_header *)((caddr_t)oh + OSPF_HEADER_SIZE + + OSPF_DB_DESC_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE + - OSPF_DB_DESC_MIN_SIZE, + 1, /* header-only LSAs */ + 0); + break; + case OSPF_MSG_LS_REQ: + /* RFC2328 A.3.4, packet header followed by N>=0 12-bytes + * request blocks. */ + ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_REQ_MIN_SIZE) + % OSPF_LSA_KEY_SIZE + ? MSG_NG + : MSG_OK; + break; + case OSPF_MSG_LS_UPD: + /* RFC2328 A.3.5, packet header + OSPF_LS_UPD_MIN_SIZE bytes + followed + by N>=0 full LSAs (with N declared beforehand). */ + lsupd = (struct ospf_ls_update *)((caddr_t)oh + + OSPF_HEADER_SIZE); + ret = ospf_lsaseq_examin( + (struct lsa_header *)((caddr_t)lsupd + + OSPF_LS_UPD_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_UPD_MIN_SIZE, + 0, /* full LSAs */ + ntohl(lsupd->num_lsas) /* 32 bits */ + ); + break; + case OSPF_MSG_LS_ACK: + /* RFC2328 A.3.6, packet header followed by N>=0 header-only + * LSAs. */ + ret = ospf_lsaseq_examin( + (struct lsa_header *)((caddr_t)oh + OSPF_HEADER_SIZE + + OSPF_LS_ACK_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_ACK_MIN_SIZE, + 1, /* header-only LSAs */ + 0); + break; + default: + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: invalid packet type 0x%02x", __func__, + oh->type); + return MSG_NG; + } + if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug("%s: malformed %s packet", __func__, + lookup_msg(ospf_packet_type_str, oh->type, NULL)); + return ret; +} + +/* OSPF Header verification. */ +static int ospf_verify_header(struct stream *ibuf, struct ospf_interface *oi, + struct ip *iph, struct ospf_header *ospfh) +{ + /* Check Area ID. */ + if (!ospf_check_area_id(oi, ospfh)) { + flog_warn(EC_OSPF_PACKET, + "interface %s: ospf_read invalid Area ID %pI4", + IF_NAME(oi), &ospfh->area_id); + return -1; + } + + /* Check network mask, Silently discarded. */ + if (!ospf_check_network_mask(oi, iph->ip_src)) { + flog_warn( + EC_OSPF_PACKET, + "interface %s: ospf_read network address is not same [%pI4]", + IF_NAME(oi), &iph->ip_src); + return -1; + } + + /* Check authentication. The function handles logging actions, where + * required. */ + if (!ospf_auth_check(oi, iph, ospfh)) + return -1; + + return 0; +} + +enum ospf_read_return_enum { + OSPF_READ_ERROR, + OSPF_READ_CONTINUE, +}; + +static enum ospf_read_return_enum ospf_read_helper(struct ospf *ospf) +{ + int ret; + struct stream *ibuf; + struct ospf_interface *oi; + struct ip *iph; + struct ospf_header *ospfh; + uint16_t length; + struct connected *c; + struct interface *ifp = NULL; + + stream_reset(ospf->ibuf); + ibuf = ospf_recv_packet(ospf, ospf->fd, &ifp, ospf->ibuf); + if (ibuf == NULL) + return OSPF_READ_ERROR; + + /* + * This raw packet is known to be at least as big as its + * IP header. Note that there should not be alignment problems with + * this assignment because this is at the beginning of the + * stream data buffer. + */ + iph = (struct ip *)STREAM_DATA(ibuf); + /* + * Note that sockopt_iphdrincl_swab_systoh was called in + * ospf_recv_packet. + */ + if (ifp == NULL) { + /* + * Handle cases where the platform does not support + * retrieving the ifindex, and also platforms (such as + * Solaris 8) that claim to support ifindex retrieval but do + * not. + */ + c = if_lookup_address((void *)&iph->ip_src, AF_INET, + ospf->vrf_id); + if (c) + ifp = c->ifp; + if (ifp == NULL) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "%s: Unable to determine incoming interface from: %pI4(%s)", + __func__, &iph->ip_src, + ospf_get_name(ospf)); + return OSPF_READ_CONTINUE; + } + } + + if (ospf->vrf_id == VRF_DEFAULT && ospf->vrf_id != ifp->vrf->vrf_id) { + /* + * We may have a situation where l3mdev_accept == 1 + * let's just kindly drop the packet and move on. + * ospf really really really does not like when + * we receive the same packet multiple times. + */ + return OSPF_READ_CONTINUE; + } + + /* Self-originated packet should be discarded silently. */ + if (ospf_if_lookup_by_local_addr(ospf, NULL, iph->ip_src)) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) { + zlog_debug( + "ospf_read[%pI4]: Dropping self-originated packet", + &iph->ip_src); + } + return OSPF_READ_CONTINUE; + } + + /* Check that we have enough for an IP header */ + if ((unsigned int)(iph->ip_hl << 2) >= STREAM_READABLE(ibuf)) { + if ((unsigned int)(iph->ip_hl << 2) == STREAM_READABLE(ibuf)) { + flog_warn( + EC_OSPF_PACKET, + "Rx'd IP packet with OSPF protocol number but no payload"); + } else { + flog_warn( + EC_OSPF_PACKET, + "IP header length field claims header is %u bytes, but we only have %zu", + (unsigned int)(iph->ip_hl << 2), + STREAM_READABLE(ibuf)); + } + + return OSPF_READ_ERROR; + } + stream_forward_getp(ibuf, iph->ip_hl << 2); + + ospfh = (struct ospf_header *)stream_pnt(ibuf); + if (MSG_OK + != ospf_packet_examin(ospfh, stream_get_endp(ibuf) + - stream_get_getp(ibuf))) + return OSPF_READ_CONTINUE; + /* Now it is safe to access all fields of OSPF packet header. */ + + /* associate packet with ospf interface */ + oi = ospf_if_lookup_recv_if(ospf, iph->ip_src, ifp); + + /* + * If a neighbor filter prefix-list is configured, apply it to the IP + * source address and ignore the packet if it doesn't match. + */ + if (oi && oi->nbr_filter) { + struct prefix ip_src_prefix = { AF_INET, IPV4_MAX_BITLEN, { 0 } }; + + ip_src_prefix.u.prefix4 = iph->ip_src; + if (prefix_list_apply(oi->nbr_filter, + (struct prefix *)&(ip_src_prefix)) != + PREFIX_PERMIT) + return OSPF_READ_CONTINUE; + } + + /* + * ospf_verify_header() relies on a valid "oi" and thus can be called + * only after the passive/backbone/other checks below are passed. + * These checks in turn access the fields of unverified "ospfh" + * structure for their own purposes and must remain very accurate + * in doing this. + */ + + /* If incoming interface is passive one, ignore it. */ + if (oi && OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "ignoring packet from router %pI4 sent to %pI4, received on a passive interface, %pI4", + &ospfh->router_id, &iph->ip_dst, + &oi->address->u.prefix4); + + if (iph->ip_dst.s_addr == htonl(OSPF_ALLSPFROUTERS)) { + /* Try to fix multicast membership. + * Some OS:es may have problems in this area, + * make sure it is removed. + */ + OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); + ospf_if_set_multicast(oi); + } + return OSPF_READ_CONTINUE; + } + + + /* if no local ospf_interface, + * or header area is backbone but ospf_interface is not + * check for VLINK interface + */ + if ((oi == NULL) + || (OSPF_IS_AREA_ID_BACKBONE(ospfh->area_id) + && !OSPF_IS_AREA_ID_BACKBONE(oi->area->area_id))) { + if ((oi = ospf_associate_packet_vl(ospf, ifp, iph, ospfh)) + == NULL) { + if (!ospf->instance && IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Packet from [%pI4] received on link %s but no ospf_interface", + &iph->ip_src, ifp->name); + return OSPF_READ_CONTINUE; + } + } + + /* + * else it must be a local ospf interface, check it was + * received on correct link + */ + else if (oi->ifp != ifp) { + if (IS_DEBUG_OSPF_EVENT) + flog_warn(EC_OSPF_PACKET, + "Packet from [%pI4] received on wrong link %s", + &iph->ip_src, ifp->name); + return OSPF_READ_CONTINUE; + } else if (oi->state == ISM_Down) { + flog_warn( + EC_OSPF_PACKET, + "Ignoring packet from %pI4 to %pI4 received on interface that is down [%s]; interface flags are %s", + &iph->ip_src, &iph->ip_dst, ifp->name, + if_flag_dump(ifp->flags)); + /* Fix multicast memberships? */ + if (iph->ip_dst.s_addr == htonl(OSPF_ALLSPFROUTERS)) + OI_MEMBER_JOINED(oi, MEMBER_ALLROUTERS); + else if (iph->ip_dst.s_addr == htonl(OSPF_ALLDROUTERS)) + OI_MEMBER_JOINED(oi, MEMBER_DROUTERS); + if (oi->multicast_memberships) + ospf_if_set_multicast(oi); + return OSPF_READ_CONTINUE; + } + + /* + * If the received packet is destined for AllDRouters, the + * packet should be accepted only if the received ospf + * interface state is either DR or Backup -- endo. + * + * I wonder who endo is? + */ + if (iph->ip_dst.s_addr == htonl(OSPF_ALLDROUTERS) + && (oi->state != ISM_DR && oi->state != ISM_Backup)) { + flog_warn( + EC_OSPF_PACKET, + "Dropping packet for AllDRouters from [%pI4] via [%s] (ISM: %s)", + &iph->ip_src, IF_NAME(oi), + lookup_msg(ospf_ism_state_msg, oi->state, NULL)); + /* Try to fix multicast membership. */ + SET_FLAG(oi->multicast_memberships, MEMBER_DROUTERS); + ospf_if_set_multicast(oi); + return OSPF_READ_CONTINUE; + } + + /* Verify more OSPF header fields. */ + ret = ospf_verify_header(ibuf, oi, iph, ospfh); + if (ret < 0) { + if (IS_DEBUG_OSPF_PACKET(0, RECV)) + zlog_debug( + "ospf_read[%pI4]: Header check failed, dropping.", + &iph->ip_src); + return OSPF_READ_CONTINUE; + } + + /* Show debug receiving packet. */ + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, RECV)) { + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, DETAIL)) { + zlog_debug( + "-----------------------------------------------------"); + ospf_packet_dump(ibuf); + } + + zlog_debug("%s received from [%pI4] via [%s]", + lookup_msg(ospf_packet_type_str, ospfh->type, NULL), + &ospfh->router_id, IF_NAME(oi)); + zlog_debug(" src [%pI4],", &iph->ip_src); + zlog_debug(" dst [%pI4]", &iph->ip_dst); + + if (IS_DEBUG_OSPF_PACKET(ospfh->type - 1, DETAIL)) + zlog_debug( + "-----------------------------------------------------"); + } + + stream_forward_getp(ibuf, OSPF_HEADER_SIZE); + + /* Adjust size to message length. */ + length = ntohs(ospfh->length) - OSPF_HEADER_SIZE; + + /* Read rest of the packet and call each sort of packet routine. + */ + switch (ospfh->type) { + case OSPF_MSG_HELLO: + ospf_hello(iph, ospfh, ibuf, oi, length); + break; + case OSPF_MSG_DB_DESC: + ospf_db_desc(iph, ospfh, ibuf, oi, length); + break; + case OSPF_MSG_LS_REQ: + ospf_ls_req(iph, ospfh, ibuf, oi, length); + break; + case OSPF_MSG_LS_UPD: + ospf_ls_upd(ospf, iph, ospfh, ibuf, oi, length); + break; + case OSPF_MSG_LS_ACK: + ospf_ls_ack(iph, ospfh, ibuf, oi, length); + break; + default: + flog_warn( + EC_OSPF_PACKET, + "interface %s(%s): OSPF packet header type %d is illegal", + IF_NAME(oi), ospf_get_name(ospf), ospfh->type); + break; + } + + return OSPF_READ_CONTINUE; +} + +/* Starting point of packet process function. */ +void ospf_read(struct event *thread) +{ + struct ospf *ospf; + int32_t count = 0; + enum ospf_read_return_enum ret; + + /* first of all get interface pointer. */ + ospf = EVENT_ARG(thread); + + /* prepare for next packet. */ + event_add_read(master, ospf_read, ospf, ospf->fd, &ospf->t_read); + + while (count < ospf->write_oi_count) { + count++; + ret = ospf_read_helper(ospf); + switch (ret) { + case OSPF_READ_ERROR: + return; + case OSPF_READ_CONTINUE: + break; + } + } +} + +/* Make OSPF header. */ +static void ospf_make_header(int type, struct ospf_interface *oi, + struct stream *s) +{ + struct ospf_header *ospfh; + + ospfh = (struct ospf_header *)STREAM_DATA(s); + + ospfh->version = (uint8_t)OSPF_VERSION; + ospfh->type = (uint8_t)type; + + ospfh->router_id = oi->ospf->router_id; + + ospfh->checksum = 0; + ospfh->area_id = oi->area->area_id; + ospfh->auth_type = htons(ospf_auth_type(oi)); + + memset(ospfh->u.auth_data, 0, OSPF_AUTH_SIMPLE_SIZE); + + stream_forward_endp(s, OSPF_HEADER_SIZE); +} + +/* Fill rest of OSPF header. */ +static void ospf_fill_header(struct ospf_interface *oi, struct stream *s, + uint16_t length) +{ + struct ospf_header *ospfh; + + ospfh = (struct ospf_header *)STREAM_DATA(s); + + /* Fill length. */ + ospfh->length = htons(length); + + /* Calculate checksum. */ + if (ntohs(ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) + ospfh->checksum = in_cksum(ospfh, length); + else + ospfh->checksum = 0; + + /* Add Authentication Data. */ + oi->keychain = NULL; + oi->key = NULL; + ospf_auth_make_data(oi, ospfh); +} + +static int ospf_make_hello(struct ospf_interface *oi, struct stream *s) +{ + struct ospf_neighbor *nbr; + struct route_node *rn; + uint16_t length = OSPF_HELLO_MIN_SIZE; + struct in_addr mask; + unsigned long p; + int flag = 0; + + /* Set netmask of interface. */ + if (!(CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED) + && oi->type == OSPF_IFTYPE_POINTOPOINT) + && oi->type != OSPF_IFTYPE_VIRTUALLINK) + masklen2ip(oi->address->prefixlen, &mask); + else + memset((char *)&mask, 0, sizeof(struct in_addr)); + stream_put_ipv4(s, mask.s_addr); + + /* Set Hello Interval. */ + if (OSPF_IF_PARAM(oi, fast_hello) == 0) + stream_putw(s, OSPF_IF_PARAM(oi, v_hello)); + else + stream_putw(s, 0); /* hello-interval of 0 for fast-hellos */ + + /* Check if flood-reduction is enabled, + * if yes set the DC bit in the options. + */ + if (OSPF_FR_CONFIG(oi->ospf, oi->area)) + SET_FLAG(OPTIONS(oi), OSPF_OPTION_DC); + else if (CHECK_FLAG(OPTIONS(oi), OSPF_OPTION_DC)) + UNSET_FLAG(OPTIONS(oi), OSPF_OPTION_DC); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: options: %x, int: %s", __func__, OPTIONS(oi), + IF_NAME(oi)); + + /* Set Options. */ + stream_putc(s, OPTIONS(oi)); + + /* Set Router Priority. */ + stream_putc(s, PRIORITY(oi)); + + /* Set Router Dead Interval. */ + stream_putl(s, OSPF_IF_PARAM(oi, v_wait)); + + /* Set Designated Router. */ + stream_put_ipv4(s, DR(oi).s_addr); + + p = stream_get_endp(s); + + /* Set Backup Designated Router. */ + stream_put_ipv4(s, BDR(oi).s_addr); + + /* Add neighbor seen. */ + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + /* Ignore the 0.0.0.0 node */ + if (nbr->router_id.s_addr == INADDR_ANY) + continue; + + /* Ignore Down neighbor */ + if (nbr->state == NSM_Attempt) + continue; + + /* This is myself for DR election */ + if (nbr->state == NSM_Down) + continue; + + if (IPV4_ADDR_SAME(&nbr->router_id, &oi->ospf->router_id)) + continue; + /* Check neighbor is sane? */ + if (nbr->d_router.s_addr != INADDR_ANY && + IPV4_ADDR_SAME(&nbr->d_router, &oi->address->u.prefix4) && + IPV4_ADDR_SAME(&nbr->bd_router, &oi->address->u.prefix4)) + flag = 1; + + /* Hello packet overflows interface MTU. + */ + if (length + sizeof(uint32_t) > ospf_packet_max(oi)) { + flog_err( + EC_OSPF_LARGE_HELLO, + "Oversized Hello packet! Larger than MTU. Not sending it out"); + return 0; + } + + stream_put_ipv4(s, nbr->router_id.s_addr); + length += 4; + } + + /* Let neighbor generate BackupSeen. */ + if (flag == 1) + stream_putl_at(s, p, 0); /* ipv4 address, normally */ + + return length; +} + +static int ospf_make_db_desc(struct ospf_interface *oi, + struct ospf_neighbor *nbr, struct stream *s) +{ + struct ospf_lsa *lsa; + uint16_t length = OSPF_DB_DESC_MIN_SIZE; + uint8_t options; + unsigned long pp; + int i; + struct ospf_lsdb *lsdb; + + /* Set Interface MTU. */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + stream_putw(s, 0); + else + stream_putw(s, oi->ifp->mtu); + + /* Set Options. */ + options = OPTIONS(oi); + if (CHECK_FLAG(oi->ospf->config, OSPF_OPAQUE_CAPABLE) && + OSPF_IF_PARAM(oi, opaque_capable)) + SET_FLAG(options, OSPF_OPTION_O); + if (OSPF_FR_CONFIG(oi->ospf, oi->area)) + SET_FLAG(options, OSPF_OPTION_DC); + stream_putc(s, options); + + /* DD flags */ + pp = stream_get_endp(s); + stream_putc(s, nbr->dd_flags); + + /* Set DD Sequence Number. */ + stream_putl(s, nbr->dd_seqnum); + + /* shortcut unneeded walk of (empty) summary LSDBs */ + if (ospf_db_summary_isempty(nbr)) + goto empty; + + /* Describe LSA Header from Database Summary List. */ + lsdb = &nbr->db_sum; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + struct route_table *table = lsdb->type[i].db; + struct route_node *rn; + + for (rn = route_top(table); rn; rn = route_next(rn)) + if ((lsa = rn->info) != NULL) { + if (IS_OPAQUE_LSA(lsa->data->type) + && (!CHECK_FLAG(options, OSPF_OPTION_O))) { + /* Suppress advertising + * opaque-information. */ + /* Remove LSA from DB summary list. */ + ospf_lsdb_delete(lsdb, lsa); + continue; + } + + if (!CHECK_FLAG(lsa->flags, OSPF_LSA_DISCARD)) { + struct lsa_header *lsah; + uint16_t ls_age; + + /* DD packet overflows interface MTU. */ + if (length + OSPF_LSA_HEADER_SIZE + > ospf_packet_max(oi)) + break; + + /* Keep pointer to LS age. */ + lsah = (struct lsa_header + *)(STREAM_DATA(s) + + stream_get_endp( + s)); + + /* Proceed stream pointer. */ + stream_put(s, lsa->data, + OSPF_LSA_HEADER_SIZE); + length += OSPF_LSA_HEADER_SIZE; + + /* Set LS age. */ + ls_age = LS_AGE(lsa); + lsah->ls_age = htons(ls_age); + } + + /* Remove LSA from DB summary list. */ + ospf_lsdb_delete(lsdb, lsa); + } + } + + /* Update 'More' bit */ + if (ospf_db_summary_isempty(nbr)) { + empty: + if (nbr->state >= NSM_Exchange) { + UNSET_FLAG(nbr->dd_flags, OSPF_DD_FLAG_M); + /* Rewrite DD flags */ + stream_putc_at(s, pp, nbr->dd_flags); + } else { + assert(IS_SET_DD_M(nbr->dd_flags)); + } + } + return length; +} + +static int ospf_make_ls_req_func(struct stream *s, uint16_t *length, + unsigned long delta, struct ospf_neighbor *nbr, + struct ospf_lsa *lsa) +{ + struct ospf_interface *oi; + + oi = nbr->oi; + + /* LS Request packet overflows interface MTU + * delta is just number of bytes required for 1 LS Req + * ospf_packet_max will return the number of bytes can + * be accommodated without ospf header. So length+delta + * can be compared to ospf_packet_max + * to check if it can fit another lsreq in the same packet. + */ + + if (*length + delta > ospf_packet_max(oi)) + return 0; + + stream_putl(s, lsa->data->type); + stream_put_ipv4(s, lsa->data->id.s_addr); + stream_put_ipv4(s, lsa->data->adv_router.s_addr); + + ospf_lsa_unlock(&nbr->ls_req_last); + nbr->ls_req_last = ospf_lsa_lock(lsa); + + *length += 12; + return 1; +} + +static int ospf_make_ls_req(struct ospf_neighbor *nbr, struct stream *s) +{ + struct ospf_lsa *lsa; + uint16_t length = OSPF_LS_REQ_MIN_SIZE; + unsigned long delta = 12; + struct route_table *table; + struct route_node *rn; + int i; + struct ospf_lsdb *lsdb; + + lsdb = &nbr->ls_req; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + table = lsdb->type[i].db; + for (rn = route_top(table); rn; rn = route_next(rn)) + if ((lsa = (rn->info)) != NULL) + if (ospf_make_ls_req_func(s, &length, delta, + nbr, lsa) + == 0) { + route_unlock_node(rn); + break; + } + } + return length; +} + +static int ls_age_increment(struct ospf_lsa *lsa, int delay) +{ + int age; + + age = IS_LSA_MAXAGE(lsa) ? OSPF_LSA_MAXAGE : LS_AGE(lsa) + delay; + + return (age > OSPF_LSA_MAXAGE ? OSPF_LSA_MAXAGE : age); +} + +static int ospf_make_ls_upd(struct ospf_interface *oi, struct list *update, + struct stream *s) +{ + struct ospf_lsa *lsa; + struct listnode *node; + uint16_t length = 0; + unsigned int size_noauth; + unsigned long delta = stream_get_endp(s); + unsigned long pp; + int count = 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + pp = stream_get_endp(s); + stream_forward_endp(s, OSPF_LS_UPD_MIN_SIZE); + length += OSPF_LS_UPD_MIN_SIZE; + + /* Calculate amount of packet usable for data. */ + size_noauth = stream_get_size(s) - ospf_packet_authspace(oi); + + while ((node = listhead(update)) != NULL) { + struct lsa_header *lsah; + uint16_t ls_age; + + lsa = listgetdata(node); + assert(lsa->data); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: List Iteration %d LSA[%s]", __func__, + count, dump_lsa_key(lsa)); + + /* Will it fit? Minimum it has to fit at least one */ + if ((length + delta + ntohs(lsa->data->length) > size_noauth) && + (count > 0)) + break; + + /* Keep pointer to LS age. */ + lsah = (struct lsa_header *)(STREAM_DATA(s) + + stream_get_endp(s)); + + /* Put LSA to Link State Request. */ + stream_put(s, lsa->data, ntohs(lsa->data->length)); + + /* Set LS age. */ + /* each hop must increment an lsa_age by transmit_delay + of OSPF interface */ + ls_age = ls_age_increment(lsa, + OSPF_IF_PARAM(oi, transmit_delay)); + lsah->ls_age = htons(ls_age); + + length += ntohs(lsa->data->length); + count++; + + list_delete_node(update, node); + ospf_lsa_unlock(&lsa); /* oi->ls_upd_queue */ + } + + /* Now set #LSAs. */ + stream_putl_at(s, pp, count); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); + return length; +} + +static int ospf_make_ls_ack(struct ospf_interface *oi, struct list *ack, + struct stream *s) +{ + struct listnode *node, *nnode; + uint16_t length = OSPF_LS_ACK_MIN_SIZE; + unsigned long delta = OSPF_LSA_HEADER_SIZE; + struct ospf_lsa *lsa; + + for (ALL_LIST_ELEMENTS(ack, node, nnode, lsa)) { + assert(lsa); + + /* LS Ack packet overflows interface MTU + * delta is just number of bytes required for + * 1 LS Ack(1 LS Hdr) ospf_packet_max will return + * the number of bytes can be accommodated without + * ospf header. So length+delta can be compared + * against ospf_packet_max to check if it can fit + * another ls header in the same packet. + */ + if ((length + delta) > ospf_packet_max(oi)) + break; + + stream_put(s, lsa->data, OSPF_LSA_HEADER_SIZE); + length += OSPF_LSA_HEADER_SIZE; + + listnode_delete(ack, lsa); + ospf_lsa_unlock(&lsa); /* oi->ls_ack_direct.ls_ack */ + } + + return length; +} + +static void ospf_hello_send_sub(struct ospf_interface *oi, in_addr_t addr) +{ + struct ospf_packet *op; + uint16_t length = OSPF_HEADER_SIZE; + + /* Check if config is still being processed */ + if (event_is_scheduled(t_ospf_cfg)) { + if (IS_DEBUG_OSPF_PACKET(0, SEND)) + zlog_debug( + "Suppressing hello to %pI4 on %s during config load", + &(addr), IF_NAME(oi)); + + return; + } + + op = ospf_packet_new(oi->ifp->mtu); + + /* Prepare OSPF common header. */ + ospf_make_header(OSPF_MSG_HELLO, oi, op->s); + + /* Prepare OSPF Hello body. */ + length += ospf_make_hello(oi, op->s); + if (length == OSPF_HEADER_SIZE) { + /* Hello overshooting MTU */ + ospf_packet_free(op); + return; + } + + /* Fill OSPF header. */ + ospf_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + op->dst.s_addr = addr; + + if (IS_DEBUG_OSPF_EVENT) { + if (oi->ospf->vrf_id) + zlog_debug( + "%s: Hello Tx interface %s ospf vrf %s id %u", + __func__, oi->ifp->name, + ospf_vrf_id_to_name(oi->ospf->vrf_id), + oi->ospf->vrf_id); + } + /* Add packet to the top of the interface output queue, so that they + * can't get delayed by things like long queues of LS Update packets + */ + ospf_packet_add_top(oi, op); + + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); +} + +static void ospf_poll_send(struct ospf_nbr_nbma *nbr_nbma) +{ + struct ospf_interface *oi; + + oi = nbr_nbma->oi; + assert(oi); + + /* If this is passive interface, do not send OSPF Hello. */ + if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) + return; + + if (nbr_nbma->nbr != NULL && nbr_nbma->nbr->state != NSM_Down) + return; + + if (oi->type == OSPF_IFTYPE_NBMA) { + if (PRIORITY(oi) == 0) + return; + + if (nbr_nbma->priority == 0 && oi->state != ISM_DR && + oi->state != ISM_Backup) + return; + + } else if (oi->type != OSPF_IFTYPE_POINTOMULTIPOINT || + !oi->p2mp_non_broadcast) + return; + + ospf_hello_send_sub(oi, nbr_nbma->addr.s_addr); +} + +void ospf_poll_timer(struct event *thread) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = EVENT_ARG(thread); + nbr_nbma->t_poll = NULL; + + if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) + zlog_debug("NSM[%s:%pI4]: Timer (Poll timer expire)", + IF_NAME(nbr_nbma->oi), &nbr_nbma->addr); + + ospf_poll_send(nbr_nbma); + + if (nbr_nbma->v_poll > 0) + OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, + nbr_nbma->v_poll); +} + + +void ospf_hello_reply_timer(struct event *thread) +{ + struct ospf_neighbor *nbr; + + nbr = EVENT_ARG(thread); + nbr->t_hello_reply = NULL; + + if (IS_DEBUG_OSPF(nsm, NSM_TIMERS)) + zlog_debug("NSM[%s:%pI4]: Timer (hello-reply timer expire)", + IF_NAME(nbr->oi), &nbr->router_id); + + ospf_hello_send_sub(nbr->oi, nbr->address.u.prefix4.s_addr); +} + +/* Send OSPF Hello. */ +void ospf_hello_send(struct ospf_interface *oi) +{ + /* If this is passive interface, do not send OSPF Hello. */ + if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_PASSIVE) + return; + + if (OSPF_IF_NON_BROADCAST(oi)) { + struct ospf_neighbor *nbr; + struct route_node *rn; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (!nbr) + continue; + + if (nbr == oi->nbr_self) + continue; + + if (nbr->state == NSM_Down) + continue; + + /* + * Always send to all neighbors on Point-to-Multipoint + * non-braodcast networks. + */ + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) + ospf_hello_send_sub(oi, nbr->address.u.prefix4 + .s_addr); + else { + /* + * RFC 2328 Section 9.5.1 + * If the router is not eligible to become Designated + * Router, it must periodically send Hello Packets to + * both the Designated Router and the Backup + * Designated Router (if they exist). + */ + if (PRIORITY(oi) == 0 && + IPV4_ADDR_CMP(&DR(oi), + &nbr->address.u.prefix4) && + IPV4_ADDR_CMP(&BDR(oi), + &nbr->address.u.prefix4)) + continue; + + /* + * If the router is eligible to become Designated + * Router, it must periodically send Hello Packets to + * all neighbors that are also eligible. In addition, + * if the router is itself the Designated Router or + * Backup Designated Router, it must also send periodic + * Hello Packets to all other neighbors. + */ + if (nbr->priority == 0 && + oi->state == ISM_DROther) + continue; + + /* if oi->state == Waiting, send + * hello to all neighbors */ + ospf_hello_send_sub(oi, nbr->address.u.prefix4 + .s_addr); + } + } + } else { + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + ospf_hello_send_sub(oi, oi->vl_data->peer_addr.s_addr); + else + ospf_hello_send_sub(oi, htonl(OSPF_ALLSPFROUTERS)); + } +} + +/* Send OSPF Database Description. */ +void ospf_db_desc_send(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi; + struct ospf_packet *op; + uint16_t length = OSPF_HEADER_SIZE; + + oi = nbr->oi; + op = ospf_packet_new(oi->ifp->mtu); + + /* Prepare OSPF common header. */ + ospf_make_header(OSPF_MSG_DB_DESC, oi, op->s); + + /* Prepare OSPF Database Description body. */ + length += ospf_make_db_desc(oi, nbr, op->s); + + /* Fill OSPF header. */ + ospf_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT) + op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + op->dst = nbr->address.u.prefix4; + + /* Add packet to the interface output queue. */ + ospf_packet_add(oi, op); + + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); + + /* Remove old DD packet, then copy new one and keep in neighbor + * structure. */ + if (nbr->last_send) + ospf_packet_free(nbr->last_send); + nbr->last_send = ospf_packet_dup(op); + monotime(&nbr->last_send_ts); + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "%s:Packet[DD]: %pI4 DB Desc send with seqnum:%x , flags:%x", + ospf_get_name(oi->ospf), &nbr->router_id, + nbr->dd_seqnum, nbr->dd_flags); +} + +/* Re-send Database Description. */ +void ospf_db_desc_resend(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi; + + oi = nbr->oi; + + /* Add packet to the interface output queue. */ + ospf_packet_add(oi, ospf_packet_dup(nbr->last_send)); + + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); + if (CHECK_FLAG(oi->ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + zlog_info( + "%s:Packet[DD]: %pI4 DB Desc resend with seqnum:%x , flags:%x", + ospf_get_name(oi->ospf), &nbr->router_id, + nbr->dd_seqnum, nbr->dd_flags); +} + +/* Send Link State Request. */ +void ospf_ls_req_send(struct ospf_neighbor *nbr) +{ + struct ospf_interface *oi; + struct ospf_packet *op; + uint16_t length = OSPF_HEADER_SIZE; + + oi = nbr->oi; + op = ospf_packet_new(oi->ifp->mtu); + + /* Prepare OSPF common header. */ + ospf_make_header(OSPF_MSG_LS_REQ, oi, op->s); + + /* Prepare OSPF Link State Request body. */ + length += ospf_make_ls_req(nbr, op->s); + if (length == OSPF_HEADER_SIZE) { + ospf_packet_free(op); + return; + } + + /* Fill OSPF header. */ + ospf_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT) + op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + op->dst = nbr->address.u.prefix4; + + /* Add packet to the interface output queue. */ + ospf_packet_add(oi, op); + + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); + + /* Add Link State Request Retransmission Timer. */ + OSPF_NSM_TIMER_ON(nbr->t_ls_req, ospf_ls_req_timer, nbr->v_ls_req); +} + +/* Send Link State Update with an LSA. */ +void ospf_ls_upd_send_lsa(struct ospf_neighbor *nbr, struct ospf_lsa *lsa, + int flag) +{ + struct list *update; + + update = list_new(); + + listnode_add(update, lsa); + + /*ospf instance is going down, send self originated + * MAXAGE LSA update to neighbors to remove from LSDB */ + if (nbr->oi->ospf->inst_shutdown && IS_LSA_MAXAGE(lsa)) + ospf_ls_upd_send(nbr, update, flag, 1); + else + ospf_ls_upd_send(nbr, update, flag, 0); + + list_delete(&update); +} + +/* Determine size for packet. Must be at least big enough to accommodate next + * LSA on list, which may be bigger than MTU size. + * + * Return pointer to new ospf_packet + * NULL if we can not allocate, eg because LSA is bigger than imposed limit + * on packet sizes (in which case offending LSA is deleted from update list) + */ +static struct ospf_packet *ospf_ls_upd_packet_new(struct list *update, + struct ospf_interface *oi) +{ + struct ospf_lsa *lsa; + struct listnode *ln; + size_t size; + static char warned = 0; + + lsa = listgetdata((ln = listhead(update))); + assert(lsa->data); + + if ((OSPF_LS_UPD_MIN_SIZE + ntohs(lsa->data->length)) + > ospf_packet_max(oi)) { + if (!warned) { + flog_warn( + EC_OSPF_LARGE_LSA, + "%s: oversized LSA encountered!will need to fragment. Not optimal. Try divide up your network with areas. Use 'debug ospf packet send' to see details, or look at 'show ip ospf database ..'", + __func__); + warned = 1; + } + + if (IS_DEBUG_OSPF_PACKET(0, SEND)) + zlog_debug( + "%s: oversized LSA id:%pI4, %d bytes originated by %pI4, will be fragmented!", + __func__, &lsa->data->id, + ntohs(lsa->data->length), + &lsa->data->adv_router); + + /* + * Allocate just enough to fit this LSA only, to avoid including + * other + * LSAs in fragmented LSA Updates. + */ + size = ntohs(lsa->data->length) + + (oi->ifp->mtu - ospf_packet_max(oi)) + + OSPF_LS_UPD_MIN_SIZE; + } else + size = oi->ifp->mtu; + + if (size > OSPF_MAX_PACKET_SIZE) { + flog_warn( + EC_OSPF_LARGE_LSA, + "%s: oversized LSA id:%pI4 too big, %d bytes, packet size %ld, dropping it completely. OSPF routing is broken!", + __func__, &lsa->data->id, ntohs(lsa->data->length), + (long int)size); + list_delete_node(update, ln); + return NULL; + } + + /* IP header is built up separately by ospf_write(). This means, that we + * must + * reduce the "affordable" size just calculated by length of an IP + * header. + * This makes sure, that even if we manage to fill the payload with LSA + * data + * completely, the final packet (our data plus IP header) still fits + * into + * outgoing interface MTU. This correction isn't really meaningful for + * an + * oversized LSA, but for consistency the correction is done for both + * cases. + * + * P.S. OSPF_MAX_PACKET_SIZE above already includes IP header size + */ + return ospf_packet_new(size - sizeof(struct ip)); +} + +void ospf_ls_upd_queue_send(struct ospf_interface *oi, struct list *update, + struct in_addr addr, int send_lsupd_now) +{ + struct ospf_packet *op; + uint16_t length = OSPF_HEADER_SIZE; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("listcount = %d, [%s]dst %pI4", listcount(update), + IF_NAME(oi), &addr); + + /* Check that we have really something to process */ + if (listcount(update) == 0) + return; + + op = ospf_ls_upd_packet_new(update, oi); + + /* Prepare OSPF common header. */ + ospf_make_header(OSPF_MSG_LS_UPD, oi, op->s); + + /* Prepare OSPF Link State Update body. + * Includes Type-7 translation. + */ + length += ospf_make_ls_upd(oi, update, op->s); + + /* Fill OSPF header. */ + ospf_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT) + op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + op->dst.s_addr = addr.s_addr; + + /* Add packet to the interface output queue. */ + ospf_packet_add(oi, op); + /* Call ospf_write() right away to send ospf packets to neighbors */ + if (send_lsupd_now) { + struct event os_packet_thd; + + os_packet_thd.arg = (void *)oi->ospf; + if (oi->on_write_q == 0) { + listnode_add(oi->ospf->oi_write_q, oi); + oi->on_write_q = 1; + } + ospf_write(&os_packet_thd); + /* + * We are fake calling ospf_write with a fake + * thread. Imagine that we have oi_a already + * enqueued and we have turned on the write + * thread(t_write). + * Now this function calls this for oi_b + * so the on_write_q has oi_a and oi_b on + * it, ospf_write runs and clears the packets + * for both oi_a and oi_b. Removing them from + * the on_write_q. After this thread of execution + * finishes we will execute the t_write thread + * with nothing in the on_write_q causing an + * assert. So just make sure that the t_write + * is actually turned off. + */ + if (list_isempty(oi->ospf->oi_write_q)) + EVENT_OFF(oi->ospf->t_write); + } else { + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); + } +} + +static void ospf_ls_upd_send_queue_event(struct event *thread) +{ + struct ospf_interface *oi = EVENT_ARG(thread); + struct route_node *rn; + struct route_node *rnext; + struct list *update; + char again = 0; + + oi->t_ls_upd_event = NULL; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s start", __func__); + + for (rn = route_top(oi->ls_upd_queue); rn; rn = rnext) { + rnext = route_next(rn); + + if (rn->info == NULL) + continue; + + update = (struct list *)rn->info; + + ospf_ls_upd_queue_send(oi, update, rn->p.u.prefix4, 0); + + /* list might not be empty. */ + if (listcount(update) == 0) { + list_delete((struct list **)&rn->info); + route_unlock_node(rn); + } else + again = 1; + } + + if (again != 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: update lists not cleared, %d nodes to try again, raising new event", + __func__, again); + oi->t_ls_upd_event = NULL; + event_add_event(master, ospf_ls_upd_send_queue_event, oi, 0, + &oi->t_ls_upd_event); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s stop", __func__); +} + +void ospf_ls_upd_send(struct ospf_neighbor *nbr, struct list *update, int flag, + int send_lsupd_now) +{ + struct ospf_interface *oi; + struct ospf_lsa *lsa; + struct prefix_ipv4 p; + struct route_node *rn; + struct listnode *node; + + oi = nbr->oi; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + p.prefix = oi->vl_data->peer_addr; + else if (oi->type == OSPF_IFTYPE_POINTOPOINT) + p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); + else if (flag == OSPF_SEND_PACKET_DIRECT) + p.prefix = nbr->address.u.prefix4; + else if (oi->state == ISM_DR || oi->state == ISM_Backup) + p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); + else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) + p.prefix.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + p.prefix.s_addr = htonl(OSPF_ALLDROUTERS); + + if (OSPF_IF_NON_BROADCAST(oi)) { + if (flag == OSPF_SEND_PACKET_INDIRECT) + flog_warn(EC_OSPF_PACKET, + "* LS-Update is directly sent on non-broadcast network."); + if (IPV4_ADDR_SAME(&oi->address->u.prefix4, &p.prefix)) + flog_warn(EC_OSPF_PACKET, + "* LS-Update is sent to myself."); + } + + rn = route_node_get(oi->ls_upd_queue, (struct prefix *)&p); + + if (rn->info == NULL) + rn->info = list_new(); + else + route_unlock_node(rn); + + for (ALL_LIST_ELEMENTS_RO(update, node, lsa)) + listnode_add(rn->info, + ospf_lsa_lock(lsa)); /* oi->ls_upd_queue */ + if (send_lsupd_now) { + struct list *send_update_list; + struct route_node *rnext; + + for (rn = route_top(oi->ls_upd_queue); rn; rn = rnext) { + rnext = route_next(rn); + + if (rn->info == NULL) + continue; + + send_update_list = (struct list *)rn->info; + + ospf_ls_upd_queue_send(oi, send_update_list, + rn->p.u.prefix4, 1); + } + } else + event_add_event(master, ospf_ls_upd_send_queue_event, oi, 0, + &oi->t_ls_upd_event); +} + +static void ospf_ls_ack_send_list(struct ospf_interface *oi, struct list *ack, + struct in_addr dst) +{ + struct ospf_packet *op; + uint16_t length = OSPF_HEADER_SIZE; + + op = ospf_packet_new(oi->ifp->mtu); + + /* Prepare OSPF common header. */ + ospf_make_header(OSPF_MSG_LS_ACK, oi, op->s); + + /* Prepare OSPF Link State Acknowledgment body. */ + length += ospf_make_ls_ack(oi, ack, op->s); + + /* Fill OSPF header. */ + ospf_fill_header(oi, op->s, length); + + /* Set packet length. */ + op->length = length; + + /* Decide destination address. */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT || + (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT && + !oi->p2mp_non_broadcast)) + op->dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + op->dst.s_addr = dst.s_addr; + + /* Add packet to the interface output queue. */ + ospf_packet_add(oi, op); + + /* Hook thread to write packet. */ + OSPF_ISM_WRITE_ON(oi->ospf); +} + +static void ospf_ls_ack_send_event(struct event *thread) +{ + struct ospf_interface *oi = EVENT_ARG(thread); + + oi->t_ls_ack_direct = NULL; + + while (listcount(oi->ls_ack_direct.ls_ack)) + ospf_ls_ack_send_list(oi, oi->ls_ack_direct.ls_ack, + oi->ls_ack_direct.dst); +} + +void ospf_ls_ack_send(struct ospf_neighbor *nbr, struct ospf_lsa *lsa) +{ + struct ospf_interface *oi = nbr->oi; + + if (IS_GRACE_LSA(lsa)) { + if (IS_DEBUG_OSPF_GR) + zlog_debug("%s, Sending GRACE ACK to Restarter.", + __func__); + } + + if (listcount(oi->ls_ack_direct.ls_ack) == 0) + oi->ls_ack_direct.dst = nbr->address.u.prefix4; + + listnode_add(oi->ls_ack_direct.ls_ack, ospf_lsa_lock(lsa)); + + event_add_event(master, ospf_ls_ack_send_event, oi, 0, + &oi->t_ls_ack_direct); +} + +/* Send Link State Acknowledgment delayed. */ +void ospf_ls_ack_send_delayed(struct ospf_interface *oi) +{ + struct in_addr dst; + + /* Decide destination address. */ + /* RFC2328 Section 13.5 On non-broadcast + networks, delayed Link State Acknowledgment packets must be + unicast separately over each adjacency (i.e., neighbor whose + state is >= Exchange). */ + if (OSPF_IF_NON_BROADCAST(oi)) { + struct ospf_neighbor *nbr; + struct route_node *rn; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + if (nbr != oi->nbr_self && nbr->state >= NSM_Exchange) + while (listcount(oi->ls_ack)) + ospf_ls_ack_send_list( + oi, oi->ls_ack, + nbr->address.u.prefix4); + } + return; + } + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + dst.s_addr = oi->vl_data->peer_addr.s_addr; + else if (oi->state == ISM_DR || oi->state == ISM_Backup) + dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else if (oi->type == OSPF_IFTYPE_POINTOPOINT) + dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) + dst.s_addr = htonl(OSPF_ALLSPFROUTERS); + else + dst.s_addr = htonl(OSPF_ALLDROUTERS); + + while (listcount(oi->ls_ack)) + ospf_ls_ack_send_list(oi, oi->ls_ack, dst); +} + +/* + * On pt-to-pt links, all OSPF control packets are sent to the multicast + * address. As a result, the kernel does not need to learn the interface + * MAC of the OSPF neighbor. However, in our world, this will delay + * convergence. Take the case when due to a link flap, all routes now + * want to use an interface which was deemed to be costlier prior to this + * event. For routes that will be installed, the missing MAC will have + * punt-to-CPU set on them. This may overload the CPU control path that + * can be avoided if the MAC was known apriori. + */ +void ospf_proactively_arp(struct ospf_neighbor *nbr) +{ + if (!nbr || !nbr->oi->ospf->proactive_arp) + return; + + ospf_zebra_send_arp(nbr->oi->ifp, &nbr->address); +} diff --git a/ospfd/ospf_packet.h b/ospfd/ospf_packet.h new file mode 100644 index 0000000..2347389 --- /dev/null +++ b/ospfd/ospf_packet.h @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF Sending and Receiving OSPF Packets. + * Copyright (C) 1999 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_PACKET_H +#define _ZEBRA_OSPF_PACKET_H + +#define OSPF_HEADER_SIZE 24U +#define OSPF_AUTH_SIMPLE_SIZE 8U +#define OSPF_AUTH_MD5_SIZE 16U + +#define OSPF_MAX_PACKET_SIZE 65535U /* includes IP Header size. */ +#define OSPF_HELLO_MIN_SIZE 20U /* not including neighbors */ +#define OSPF_DB_DESC_MIN_SIZE 8U +#define OSPF_LS_REQ_MIN_SIZE 0U +#define OSPF_LS_UPD_MIN_SIZE 4U +#define OSPF_LS_ACK_MIN_SIZE 0U + +#define OSPF_MSG_HELLO 1 /* OSPF Hello Message. */ +#define OSPF_MSG_DB_DESC 2 /* OSPF Database Description Message. */ +#define OSPF_MSG_LS_REQ 3 /* OSPF Link State Request Message. */ +#define OSPF_MSG_LS_UPD 4 /* OSPF Link State Update Message. */ +#define OSPF_MSG_LS_ACK 5 /* OSPF Link State Acknowledgement Message. */ + +#define OSPF_SEND_PACKET_DIRECT 1 +#define OSPF_SEND_PACKET_INDIRECT 2 +#define OSPF_SEND_PACKET_LOOP 3 + +#define OSPF_HELLO_REPLY_DELAY 1 + +/* Return values of functions involved in packet verification, see ospf6d. */ +#define MSG_OK 0 +#define MSG_NG 1 + +struct ospf_packet { + struct ospf_packet *next; + + /* Pointer to data stream. */ + struct stream *s; + + /* IP destination address. */ + struct in_addr dst; + + /* OSPF packet length. */ + uint16_t length; +}; + +/* OSPF packet queue structure. */ +struct ospf_fifo { + unsigned long count; + + struct ospf_packet *head; + struct ospf_packet *tail; +}; + +/* OSPF packet header structure. */ +struct ospf_header { + uint8_t version; /* OSPF Version. */ + uint8_t type; /* Packet Type. */ + uint16_t length; /* Packet Length. */ + struct in_addr router_id; /* Router ID. */ + struct in_addr area_id; /* Area ID. */ + uint16_t checksum; /* Check Sum. */ + uint16_t auth_type; /* Authentication Type. */ + /* Authentication Data. */ + union { + /* Simple Authentication. */ + uint8_t auth_data[OSPF_AUTH_SIMPLE_SIZE]; + /* Cryptographic Authentication. */ + struct { + uint16_t zero; /* Should be 0. */ + uint8_t key_id; /* Key ID. */ + uint8_t auth_data_len; /* Auth Data Length. */ + uint32_t crypt_seqnum; /* Cryptographic Sequence + Number. */ + } crypt; + } u; +}; + +/* OSPF Hello body format. */ +struct ospf_hello { + struct in_addr network_mask; + uint16_t hello_interval; + uint8_t options; + uint8_t priority; + uint32_t dead_interval; + struct in_addr d_router; + struct in_addr bd_router; + struct in_addr neighbors[1]; +}; + +/* OSPF Database Description body format. */ +struct ospf_db_desc { + uint16_t mtu; + uint8_t options; + uint8_t flags; + uint32_t dd_seqnum; +}; + +struct ospf_ls_update { + uint32_t num_lsas; +}; + +/* Macros. */ +/* XXX Perhaps obsolete; function in ospf_packet.c */ +#define OSPF_PACKET_MAX(oi) ospf_packet_max (oi) + +#define OSPF_OUTPUT_PNT(S) ((S)->data + (S)->putp) +#define OSPF_OUTPUT_LENGTH(S) ((S)->endp) + +#define IS_SET_DD_MS(X) ((X) & OSPF_DD_FLAG_MS) +#define IS_SET_DD_M(X) ((X) & OSPF_DD_FLAG_M) +#define IS_SET_DD_I(X) ((X) & OSPF_DD_FLAG_I) +#define IS_SET_DD_ALL(X) ((X) & OSPF_DD_FLAG_ALL) + +/* Prototypes. */ +extern void ospf_packet_free(struct ospf_packet *); +extern struct ospf_fifo *ospf_fifo_new(void); +extern void ospf_fifo_push(struct ospf_fifo *, struct ospf_packet *); +extern struct ospf_packet *ospf_fifo_pop(struct ospf_fifo *); +extern struct ospf_packet *ospf_fifo_head(struct ospf_fifo *); +extern void ospf_fifo_flush(struct ospf_fifo *); +extern void ospf_fifo_free(struct ospf_fifo *); + +extern void ospf_read(struct event *thread); +extern void ospf_hello_send(struct ospf_interface *); +extern void ospf_db_desc_send(struct ospf_neighbor *); +extern void ospf_db_desc_resend(struct ospf_neighbor *); +extern void ospf_ls_req_send(struct ospf_neighbor *); +extern void ospf_ls_upd_send_lsa(struct ospf_neighbor *, struct ospf_lsa *, + int); +extern void ospf_ls_upd_send(struct ospf_neighbor *, struct list *, int, int); +extern void ospf_ls_upd_queue_send(struct ospf_interface *oi, + struct list *update, struct in_addr addr, + int send_lsupd_now); +extern void ospf_ls_ack_send(struct ospf_neighbor *, struct ospf_lsa *); +extern void ospf_ls_ack_send_delayed(struct ospf_interface *); +extern void ospf_ls_retransmit(struct ospf_interface *, struct ospf_lsa *); +extern void ospf_ls_req_event(struct ospf_neighbor *); + +extern void ospf_ls_upd_timer(struct event *thread); +extern void ospf_ls_ack_timer(struct event *thread); +extern void ospf_poll_timer(struct event *thread); +extern void ospf_hello_reply_timer(struct event *thread); + +extern const struct message ospf_packet_type_str[]; +extern const size_t ospf_packet_type_str_max; + +extern void ospf_proactively_arp(struct ospf_neighbor *); + +#endif /* _ZEBRA_OSPF_PACKET_H */ diff --git a/ospfd/ospf_ri.c b/ospfd/ospf_ri.c new file mode 100644 index 0000000..dbe44f7 --- /dev/null +++ b/ospfd/ospf_ri.c @@ -0,0 +1,2264 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC4970 Router Information + * with support of RFC5088 PCE Capabilites announcement + * + * Module name: Router Information + * Author: Olivier Dugeon + * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ + */ + +#include +#include + +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "mpls.h" +#include + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_errors.h" + +/* + * Global variable to manage Opaque-LSA/Router Information on this node. + * Note that all parameter values are stored in network byte order. + */ +static struct ospf_router_info OspfRI; + +/*------------------------------------------------------------------------------* + * Following are initialize/terminate functions for Router Information + *handling. + *------------------------------------------------------------------------------*/ + +static void ospf_router_info_ism_change(struct ospf_interface *oi, + int old_status); +static void ospf_router_info_config_write_router(struct vty *vty); +static void ospf_router_info_show_info(struct vty *vty, + struct json_object *json, + struct ospf_lsa *lsa); +static int ospf_router_info_lsa_originate(void *arg); +static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa); +static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai, + enum lsa_opcode opcode); +static void ospf_router_info_register_vty(void); +static int ospf_router_info_lsa_update(struct ospf_lsa *lsa); +static void del_area_info(void *val); +static void del_pce_info(void *val); + +int ospf_router_info_init(void) +{ + + zlog_info("RI (%s): Initialize Router Information", __func__); + + memset(&OspfRI, 0, sizeof(OspfRI)); + OspfRI.enabled = false; + OspfRI.registered = 0; + OspfRI.scope = OSPF_OPAQUE_AS_LSA; + OspfRI.as_flags = RIFLG_LSA_INACTIVE; + OspfRI.area_info = list_new(); + OspfRI.area_info->del = del_area_info; + + /* Initialize pce domain and neighbor list */ + OspfRI.pce_info.enabled = false; + OspfRI.pce_info.pce_domain = list_new(); + OspfRI.pce_info.pce_domain->del = del_pce_info; + OspfRI.pce_info.pce_neighbor = list_new(); + OspfRI.pce_info.pce_neighbor->del = del_pce_info; + + /* Initialize Segment Routing information structure */ + OspfRI.sr_info.enabled = false; + + ospf_router_info_register_vty(); + + return 0; +} + +static int ospf_router_info_register(uint8_t scope) +{ + int rc = 0; + + if (OspfRI.registered) + return rc; + + zlog_info("RI (%s): Register Router Information with scope %s(%d)", + __func__, + scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS", scope); + rc = ospf_register_opaque_functab( + scope, OPAQUE_TYPE_ROUTER_INFORMATION_LSA, + NULL, /* new interface */ + NULL, /* del interface */ + ospf_router_info_ism_change, + NULL, /* NSM change */ + ospf_router_info_config_write_router, + NULL, /* Config. write interface */ + NULL, /* Config. write debug */ + ospf_router_info_show_info, ospf_router_info_lsa_originate, + ospf_router_info_lsa_refresh, ospf_router_info_lsa_update, + NULL); /* del_lsa_hook */ + + if (rc != 0) { + flog_warn( + EC_OSPF_OPAQUE_REGISTRATION, + "RI (%s): Failed to register functions", __func__); + return rc; + } + + OspfRI.registered = 1; + OspfRI.scope = scope; + return rc; +} + +static int ospf_router_info_unregister(void) +{ + + if ((OspfRI.scope != OSPF_OPAQUE_AS_LSA) + && (OspfRI.scope != OSPF_OPAQUE_AREA_LSA)) { + assert("Unable to unregister Router Info functions: Wrong scope!" + == NULL); + return -1; + } + + ospf_delete_opaque_functab(OspfRI.scope, + OPAQUE_TYPE_ROUTER_INFORMATION_LSA); + + OspfRI.registered = 0; + return 0; +} + +void ospf_router_info_term(void) +{ + + list_delete(&OspfRI.area_info); + list_delete(&OspfRI.pce_info.pce_domain); + list_delete(&OspfRI.pce_info.pce_neighbor); + + OspfRI.enabled = false; + + ospf_router_info_unregister(); + + return; +} + +void ospf_router_info_finish(void) +{ + struct listnode *node, *nnode; + struct ospf_ri_area_info *ai; + + /* Flush Router Info LSA */ + for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) + if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) + ospf_router_info_lsa_schedule(ai, FLUSH_THIS_LSA); + + list_delete_all_node(OspfRI.pce_info.pce_domain); + list_delete_all_node(OspfRI.pce_info.pce_neighbor); + + OspfRI.enabled = false; +} + +static void del_area_info(void *val) +{ + XFREE(MTYPE_OSPF_ROUTER_INFO, val); +} + +static void del_pce_info(void *val) +{ + XFREE(MTYPE_OSPF_PCE_PARAMS, val); +} + +/* Catch RI LSA flooding Scope for ospf_ext.[h,c] code */ +struct scope_info ospf_router_info_get_flooding_scope(void) +{ + struct scope_info flooding_scope; + + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { + flooding_scope.scope = OSPF_OPAQUE_AS_LSA; + flooding_scope.areas = NULL; + return flooding_scope; + } + flooding_scope.scope = OSPF_OPAQUE_AREA_LSA; + flooding_scope.areas = OspfRI.area_info; + return flooding_scope; +} + +static struct ospf_ri_area_info *lookup_by_area(struct ospf_area *area) +{ + struct listnode *node, *nnode; + struct ospf_ri_area_info *ai; + + for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) + if (ai->area == area) + return ai; + + return NULL; +} + +/*------------------------------------------------------------------------* + * Following are control functions for ROUTER INFORMATION parameters + *management. + *------------------------------------------------------------------------*/ + +static void set_router_info_capabilities(struct ri_tlv_router_cap *ric, + uint32_t cap) +{ + ric->header.type = htons(RI_TLV_CAPABILITIES); + ric->header.length = htons(RI_TLV_LENGTH); + ric->value = htonl(cap); + return; +} + +static int set_pce_header(struct ospf_pce_info *pce) +{ + uint16_t length = 0; + struct listnode *node; + struct ri_pce_subtlv_domain *domain; + struct ri_pce_subtlv_neighbor *neighbor; + + /* PCE Address */ + if (ntohs(pce->pce_address.header.type) != 0) + length += TLV_SIZE(&pce->pce_address.header); + + /* PCE Path Scope */ + if (ntohs(pce->pce_scope.header.type) != 0) + length += TLV_SIZE(&pce->pce_scope.header); + + /* PCE Domain */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { + if (ntohs(domain->header.type) != 0) + length += TLV_SIZE(&domain->header); + } + + /* PCE Neighbor */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { + if (ntohs(neighbor->header.type) != 0) + length += TLV_SIZE(&neighbor->header); + } + + /* PCE Capabilities */ + if (ntohs(pce->pce_cap_flag.header.type) != 0) + length += TLV_SIZE(&pce->pce_cap_flag.header); + + if (length != 0) { + pce->pce_header.header.type = htons(RI_TLV_PCE); + pce->pce_header.header.length = htons(length); + pce->enabled = true; + } else { + pce->pce_header.header.type = 0; + pce->pce_header.header.length = 0; + pce->enabled = false; + } + + return length; +} + +static void set_pce_address(struct in_addr ipv4, struct ospf_pce_info *pce) +{ + + /* Enable PCE Info */ + pce->pce_header.header.type = htons(RI_TLV_PCE); + /* Set PCE Address */ + pce->pce_address.header.type = htons(RI_PCE_SUBTLV_ADDRESS); + pce->pce_address.header.length = htons(PCE_ADDRESS_IPV4_SIZE); + pce->pce_address.address.type = htons(PCE_ADDRESS_IPV4); + pce->pce_address.address.value = ipv4; + + return; +} + +static void set_pce_path_scope(uint32_t scope, struct ospf_pce_info *pce) +{ + + /* Set PCE Scope */ + pce->pce_scope.header.type = htons(RI_PCE_SUBTLV_PATH_SCOPE); + pce->pce_scope.header.length = htons(RI_TLV_LENGTH); + pce->pce_scope.value = htonl(scope); + + return; +} + +static void set_pce_domain(uint16_t type, uint32_t domain, + struct ospf_pce_info *pce) +{ + + struct ri_pce_subtlv_domain *new; + + /* Create new domain info */ + new = XCALLOC(MTYPE_OSPF_PCE_PARAMS, + sizeof(struct ri_pce_subtlv_domain)); + + new->header.type = htons(RI_PCE_SUBTLV_DOMAIN); + new->header.length = htons(PCE_ADDRESS_IPV4_SIZE); + new->type = htons(type); + new->value = htonl(domain); + + /* Add new domain to the list */ + listnode_add(pce->pce_domain, new); + + return; +} + +static void unset_pce_domain(uint16_t type, uint32_t domain, + struct ospf_pce_info *pce) +{ + struct listnode *node; + struct ri_pce_subtlv_domain *old = NULL; + int found = 0; + + /* Search the corresponding node */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, old)) { + if ((old->type == htons(type)) + && (old->value == htonl(domain))) { + found = 1; + break; + } + } + + /* if found remove it */ + if (found) { + listnode_delete(pce->pce_domain, old); + + /* Finally free the old domain */ + XFREE(MTYPE_OSPF_PCE_PARAMS, old); + } +} + +static void set_pce_neighbor(uint16_t type, uint32_t domain, + struct ospf_pce_info *pce) +{ + + struct ri_pce_subtlv_neighbor *new; + + /* Create new neighbor info */ + new = XCALLOC(MTYPE_OSPF_PCE_PARAMS, + sizeof(struct ri_pce_subtlv_neighbor)); + + new->header.type = htons(RI_PCE_SUBTLV_NEIGHBOR); + new->header.length = htons(PCE_ADDRESS_IPV4_SIZE); + new->type = htons(type); + new->value = htonl(domain); + + /* Add new domain to the list */ + listnode_add(pce->pce_neighbor, new); + + return; +} + +static void unset_pce_neighbor(uint16_t type, uint32_t domain, + struct ospf_pce_info *pce) +{ + struct listnode *node; + struct ri_pce_subtlv_neighbor *old = NULL; + int found = 0; + + /* Search the corresponding node */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, old)) { + if ((old->type == htons(type)) + && (old->value == htonl(domain))) { + found = 1; + break; + } + } + + /* if found remove it */ + if (found) { + listnode_delete(pce->pce_neighbor, old); + + /* Finally free the old domain */ + XFREE(MTYPE_OSPF_PCE_PARAMS, old); + } +} + +static void set_pce_cap_flag(uint32_t cap, struct ospf_pce_info *pce) +{ + + /* Set PCE Capabilities flag */ + pce->pce_cap_flag.header.type = htons(RI_PCE_SUBTLV_CAP_FLAG); + pce->pce_cap_flag.header.length = htons(RI_TLV_LENGTH); + pce->pce_cap_flag.value = htonl(cap); + + return; +} + +/* Segment Routing TLV setter */ + +/* Algorithm SubTLV - section 3.1 */ +static void set_sr_algorithm(uint8_t algo) +{ + + OspfRI.sr_info.algo.value[0] = algo; + for (int i = 1; i < ALGORITHM_COUNT; i++) + OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; + + /* Set TLV type and length == only 1 Algorithm */ + TLV_TYPE(OspfRI.sr_info.algo) = htons(RI_SR_TLV_SR_ALGORITHM); + TLV_LEN(OspfRI.sr_info.algo) = htons(sizeof(uint8_t)); +} + +/* unset Aglogithm SubTLV */ +static void unset_sr_algorithm(uint8_t algo) +{ + + for (int i = 0; i < ALGORITHM_COUNT; i++) + OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET; + + /* Unset TLV type and length */ + TLV_TYPE(OspfRI.sr_info.algo) = htons(0); + TLV_LEN(OspfRI.sr_info.algo) = htons(0); +} + +/* Set Segment Routing Global Block SubTLV - section 3.2 */ +static void set_sr_global_label_range(struct sr_block srgb) +{ + /* Set Header */ + TLV_TYPE(OspfRI.sr_info.srgb) = htons(RI_SR_TLV_SRGB_LABEL_RANGE); + TLV_LEN(OspfRI.sr_info.srgb) = htons(RI_SR_TLV_LABEL_RANGE_SIZE); + /* Set Range Size */ + OspfRI.sr_info.srgb.size = htonl(SET_RANGE_SIZE(srgb.range_size)); + /* Set Lower bound label SubTLV */ + TLV_TYPE(OspfRI.sr_info.srgb.lower) = htons(SUBTLV_SID_LABEL); + TLV_LEN(OspfRI.sr_info.srgb.lower) = htons(SID_RANGE_LABEL_LENGTH); + OspfRI.sr_info.srgb.lower.value = htonl(SET_LABEL(srgb.lower_bound)); +} + +/* Unset Segment Routing Global Block SubTLV */ +static void unset_sr_global_label_range(void) +{ + TLV_TYPE(OspfRI.sr_info.srgb) = htons(0); + TLV_LEN(OspfRI.sr_info.srgb) = htons(0); + TLV_TYPE(OspfRI.sr_info.srgb.lower) = htons(0); + TLV_LEN(OspfRI.sr_info.srgb.lower) = htons(0); +} + +/* Set Segment Routing Local Block SubTLV - section 3.2 */ +static void set_sr_local_label_range(struct sr_block srlb) +{ + /* Set Header */ + TLV_TYPE(OspfRI.sr_info.srlb) = htons(RI_SR_TLV_SRLB_LABEL_RANGE); + TLV_LEN(OspfRI.sr_info.srlb) = htons(RI_SR_TLV_LABEL_RANGE_SIZE); + /* Set Range Size */ + OspfRI.sr_info.srlb.size = htonl(SET_RANGE_SIZE(srlb.range_size)); + /* Set Lower bound label SubTLV */ + TLV_TYPE(OspfRI.sr_info.srlb.lower) = htons(SUBTLV_SID_LABEL); + TLV_LEN(OspfRI.sr_info.srlb.lower) = htons(SID_RANGE_LABEL_LENGTH); + OspfRI.sr_info.srlb.lower.value = htonl(SET_LABEL(srlb.lower_bound)); +} + +/* Unset Segment Routing Local Block SubTLV */ +static void unset_sr_local_label_range(void) +{ + TLV_TYPE(OspfRI.sr_info.srlb) = htons(0); + TLV_LEN(OspfRI.sr_info.srlb) = htons(0); + TLV_TYPE(OspfRI.sr_info.srlb.lower) = htons(0); + TLV_LEN(OspfRI.sr_info.srlb.lower) = htons(0); +} + +/* Set Maximum Stack Depth for this router */ +static void set_sr_node_msd(uint8_t msd) +{ + TLV_TYPE(OspfRI.sr_info.msd) = htons(RI_SR_TLV_NODE_MSD); + TLV_LEN(OspfRI.sr_info.msd) = htons(sizeof(uint32_t)); + OspfRI.sr_info.msd.value = msd; +} + +/* Unset this router MSD */ +static void unset_sr_node_msd(void) +{ + TLV_TYPE(OspfRI.sr_info.msd) = htons(0); + TLV_LEN(OspfRI.sr_info.msd) = htons(0); +} + +static void unset_param(void *tlv_buffer) +{ + struct tlv_header *tlv = (struct tlv_header *)tlv_buffer; + + tlv->type = 0; + /* Fill the Value to 0 */ + memset(TLV_DATA(tlv_buffer), 0, TLV_BODY_SIZE(tlv)); + tlv->length = 0; + + return; +} + +static void initialize_params(struct ospf_router_info *ori) +{ + uint32_t cap = 0; + struct ospf *top; + struct listnode *node, *nnode; + struct ospf_area *area; + struct ospf_ri_area_info *new; + + /* + * Initialize default Router Information Capabilities. + */ + cap = RI_TE_SUPPORT; + + set_router_info_capabilities(&ori->router_cap, cap); + + /* If Area address is not null and exist, retrieve corresponding + * structure */ + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + zlog_info("RI (%s): Initialize Router Info for %s scope", __func__, + OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS"); + + /* Try to get available Area's context from ospf at this step. + * Do it latter if not available */ + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { + if (!list_isempty(OspfRI.area_info)) + list_delete_all_node(OspfRI.area_info); + for (ALL_LIST_ELEMENTS(top->areas, node, nnode, area)) { + zlog_debug("RI (%s): Add area %pI4 to Router Information", + __func__, &area->area_id); + new = XCALLOC(MTYPE_OSPF_ROUTER_INFO, + sizeof(struct ospf_ri_area_info)); + new->area = area; + new->flags = RIFLG_LSA_INACTIVE; + listnode_add(OspfRI.area_info, new); + } + } + + /* + * Initialize default PCE Information values + */ + /* PCE address == OSPF Router ID */ + set_pce_address(top->router_id, &ori->pce_info); + + /* PCE scope */ + cap = 7; /* Set L, R and Rd bits to one = intra & inter-area path + computation */ + set_pce_path_scope(cap, &ori->pce_info); + + /* PCE Capabilities */ + cap = PCE_CAP_BIDIRECTIONAL | PCE_CAP_DIVERSE_PATH | PCE_CAP_OBJECTIVES + | PCE_CAP_ADDITIVE | PCE_CAP_MULTIPLE_REQ; + set_pce_cap_flag(cap, &ori->pce_info); + + return; +} + +static int is_mandated_params_set(struct ospf_router_info *ori) +{ + int rc = 0; + + if (ori == NULL) + return rc; + + if (ntohs(ori->router_cap.header.type) == 0) + return rc; + + if ((ntohs(ori->pce_info.pce_header.header.type) == RI_TLV_PCE) + && (ntohs(ori->pce_info.pce_address.header.type) == 0) + && (ntohs(ori->pce_info.pce_cap_flag.header.type) == 0)) + return rc; + + if ((ori->sr_info.enabled) && (ntohs(TLV_TYPE(ori->sr_info.algo)) == 0) + && (ntohs(TLV_TYPE(ori->sr_info.srgb)) == 0)) + return rc; + + rc = 1; + + return rc; +} + +/* + * Used by Segment Routing to set new TLVs and Sub-TLVs values + * + * @param enable To activate or not Segment Routing router Information flooding + * @param srn Self Segment Routing node + * + * @return none + */ +void ospf_router_info_update_sr(bool enable, struct sr_node *srn) +{ + struct listnode *node, *nnode; + struct ospf_ri_area_info *ai; + + /* First, check if Router Information is registered or not */ + if (!OspfRI.registered) + ospf_router_info_register(OSPF_OPAQUE_AREA_LSA); + + /* Verify that scope is AREA */ + if (OspfRI.scope != OSPF_OPAQUE_AREA_LSA) { + zlog_err( + "RI (%s): Router Info is %s flooding: Change scope to Area flooding for Segment Routing", + __func__, + OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS"); + return; + } + + /* Then, activate and initialize Router Information if necessary */ + if (!OspfRI.enabled) { + OspfRI.enabled = true; + initialize_params(&OspfRI); + } + + /* Check that SR node is valid */ + if (srn == NULL) + return; + + if (IS_DEBUG_OSPF_SR) + zlog_debug("RI (%s): %s Routing Information for Segment Routing", + __func__, enable ? "Enable" : "Disable"); + + /* Unset or Set SR parameters */ + if (!enable) { + unset_sr_algorithm(SR_ALGORITHM_SPF); + unset_sr_global_label_range(); + unset_sr_local_label_range(); + unset_sr_node_msd(); + OspfRI.sr_info.enabled = false; + } else { + // Only SR_ALGORITHM_SPF is supported + set_sr_algorithm(SR_ALGORITHM_SPF); + set_sr_global_label_range(srn->srgb); + set_sr_local_label_range(srn->srlb); + if (srn->msd != 0) + set_sr_node_msd(srn->msd); + else + unset_sr_node_msd(); + OspfRI.sr_info.enabled = true; + } + + /* Refresh if already engaged or originate RI LSA */ + for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) { + if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) + ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA); + else + ospf_router_info_lsa_schedule(ai, + REORIGINATE_THIS_LSA); + + } +} + +/*------------------------------------------------------------------------* + * Following are callback functions against generic Opaque-LSAs handling. + *------------------------------------------------------------------------*/ +static void ospf_router_info_ism_change(struct ospf_interface *oi, + int old_state) +{ + + struct ospf_ri_area_info *ai; + + /* Collect area information */ + ai = lookup_by_area(oi->area); + + /* Check if area is not yet registered */ + if (ai != NULL) + return; + + /* Add this new area to the list */ + ai = XCALLOC(MTYPE_OSPF_ROUTER_INFO, sizeof(struct ospf_ri_area_info)); + ai->area = oi->area; + ai->flags = RIFLG_LSA_INACTIVE; + listnode_add(OspfRI.area_info, ai); + + return; +} + +/*------------------------------------------------------------------------* + * Following are OSPF protocol processing functions for ROUTER INFORMATION + *------------------------------------------------------------------------*/ + +static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) +{ + + stream_put(s, tlvh, sizeof(struct tlv_header)); + return; +} + +static void build_tlv(struct stream *s, struct tlv_header *tlvh) +{ + + if (ntohs(tlvh->type) != 0) { + build_tlv_header(s, tlvh); + stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); + } + return; +} + +static void ospf_router_info_lsa_body_set(struct stream *s) +{ + + struct listnode *node; + struct ri_pce_subtlv_domain *domain; + struct ri_pce_subtlv_neighbor *neighbor; + + /* Build Router Information TLV */ + build_tlv(s, &OspfRI.router_cap.header); + + /* Build Segment Routing TLVs if enabled */ + if (OspfRI.sr_info.enabled) { + /* Build Algorithm TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.algo)); + /* Build SRGB TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.srgb)); + /* Build SRLB TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.srlb)); + /* Build MSD TLV */ + build_tlv(s, &TLV_HDR(OspfRI.sr_info.msd)); + } + + /* Add RI PCE TLV if it is set */ + if (OspfRI.pce_info.enabled) { + + /* Compute PCE Info header first */ + set_pce_header(&OspfRI.pce_info); + + /* Build PCE TLV */ + build_tlv_header(s, &OspfRI.pce_info.pce_header.header); + + /* Build PCE address sub-tlv */ + build_tlv(s, &OspfRI.pce_info.pce_address.header); + + /* Build PCE path scope sub-tlv */ + build_tlv(s, &OspfRI.pce_info.pce_scope.header); + + /* Build PCE domain sub-tlv */ + for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_domain, node, + domain)) + build_tlv(s, &domain->header); + + /* Build PCE neighbor sub-tlv */ + for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_neighbor, node, + neighbor)) + build_tlv(s, &neighbor->header); + + /* Build PCE cap flag sub-tlv */ + build_tlv(s, &OspfRI.pce_info.pce_cap_flag.header); + } + + return; +} + +/* Create new opaque-LSA. */ +static struct ospf_lsa *ospf_router_info_lsa_new(struct ospf_area *area) +{ + struct ospf *top; + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + uint8_t options, lsa_type; + struct in_addr lsa_id; + uint32_t tmp; + uint16_t length; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = OSPF_OPTION_E; /* Enable AS external as we flood RI with + Opaque Type 11 */ + options |= OSPF_OPTION_O; /* Don't forget this :-) */ + + lsa_type = OspfRI.scope; + /* LSA ID == 0 for Router Information see RFC 4970 */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0); + lsa_id.s_addr = htonl(tmp); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + zlog_debug( + "LSA[Type%d:%pI4]: Create an Opaque-LSA/ROUTER INFORMATION instance", + lsa_type, &lsa_id); + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Set opaque-LSA header fields. */ + lsa_header_set(s, options, lsa_type, lsa_id, top->router_id); + + /* Set opaque-LSA body fields. */ + ospf_router_info_lsa_body_set(s); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + /* Routing Information is only supported for default VRF */ + new->vrf_id = VRF_DEFAULT; + new->area = area; + + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +static int ospf_router_info_lsa_originate_as(void *arg) +{ + struct ospf_lsa *new; + struct ospf *top; + int rc = -1; + + /* Sanity Check */ + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { + flog_warn( + EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): wrong flooding scope AREA instead of AS ?", + __func__); + return rc; + } + + /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ + new = ospf_router_info_lsa_new(NULL); + top = (struct ospf *)arg; + + /* Check ospf info */ + if (top == NULL) { + zlog_debug("RI (%s): ospf instance not found for vrf id %u", + __func__, VRF_DEFAULT); + ospf_lsa_unlock(&new); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn( + EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Update new LSA origination count. */ + top->lsa_originate_count++; + + /* Flood new LSA through AREA or AS. */ + SET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); + ospf_flood_through_as(top, NULL /*nbr */, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "LSA[Type%d:%pI4]: Originate Opaque-LSA/ROUTER INFORMATION", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + rc = 0; + return rc; +} + +static int ospf_router_info_lsa_originate_area(void *arg) +{ + struct ospf_lsa *new; + struct ospf *top; + struct ospf_ri_area_info *ai = NULL; + int rc = -1; + + /* Sanity Check */ + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { + flog_warn( + EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): wrong flooding scope AS instead of AREA ?", + __func__); + return rc; + } + + /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ + ai = lookup_by_area((struct ospf_area *)arg); + if (ai == NULL) { + zlog_debug( + "RI (%s): There is no context for this Router Information. Stop processing", + __func__); + return rc; + } + + if (ai->area->ospf) + top = ai->area->ospf; + else + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + new = ospf_router_info_lsa_new(ai->area); + + /* Check ospf info */ + if (top == NULL) { + zlog_debug("RI (%s): ospf instance not found for vrf id %u", + __func__, VRF_DEFAULT); + ospf_lsa_unlock(&new); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn( + EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Update new LSA origination count. */ + top->lsa_originate_count++; + + /* Flood new LSA through AREA or AS. */ + SET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); + ospf_flood_through_area(ai->area, NULL /*nbr */, new); + + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "LSA[Type%d:%pI4]: Originate Opaque-LSA/ROUTER INFORMATION", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + rc = 0; + return rc; +} + +static int ospf_router_info_lsa_originate(void *arg) +{ + + struct ospf_ri_area_info *ai; + int rc = -1; + + if (!OspfRI.enabled) { + zlog_info("RI (%s): ROUTER INFORMATION is disabled now.", + __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + + /* Check if Router Information LSA is already engaged */ + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { + if ((CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)) + && (CHECK_FLAG(OspfRI.as_flags, + RIFLG_LSA_FORCED_REFRESH))) { + UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_FORCED_REFRESH); + ospf_router_info_lsa_schedule(NULL, REFRESH_THIS_LSA); + rc = 0; + return rc; + } + } else { + ai = lookup_by_area((struct ospf_area *)arg); + if (ai == NULL) { + flog_warn( + EC_OSPF_LSA, + "RI (%s): Missing area information", __func__); + return rc; + } + if ((CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) + && (CHECK_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH))) { + UNSET_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH); + ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA); + rc = 0; + return rc; + } + } + + /* Router Information is not yet Engaged, check parameters */ + if (!is_mandated_params_set(&OspfRI)) + flog_warn( + EC_OSPF_LSA, + "RI (%s): lacks mandated ROUTER INFORMATION parameters", + __func__); + + /* Ok, let's try to originate an LSA */ + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) + rc = ospf_router_info_lsa_originate_as(arg); + else + rc = ospf_router_info_lsa_originate_area(arg); + + return rc; +} + +static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa) +{ + struct ospf_ri_area_info *ai = NULL; + struct ospf_lsa *new = NULL; + struct ospf *top; + + if (!OspfRI.enabled) { + /* + * This LSA must have flushed before due to ROUTER INFORMATION + * status change. + * It seems a slip among routers in the routing domain. + */ + zlog_info("RI (%s): ROUTER INFORMATION is disabled now.", + __func__); + lsa->data->ls_age = + htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ + } + + /* Verify that the Router Information ID is supported */ + if (GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)) != 0) { + flog_warn( + EC_OSPF_LSA, + "RI (%s): Unsupported Router Information ID", + __func__); + return NULL; + } + + /* Process LSA depending of the flooding scope */ + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { + /* Get context AREA context */ + ai = lookup_by_area(lsa->area); + if (ai == NULL) { + flog_warn( + EC_OSPF_LSA, + "RI (%s): No associated Area", __func__); + return NULL; + } + /* Flush LSA, if the lsa's age reached to MaxAge. */ + if (IS_LSA_MAXAGE(lsa)) { + UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ + new = ospf_router_info_lsa_new(ai->area); + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + /* Install this LSA into LSDB. */ + /* Given "lsa" will be freed in the next function. */ + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return new; + } + /* Flood updated LSA through AREA */ + ospf_flood_through_area(ai->area, NULL /*nbr */, new); + + } else { /* AS Flooding scope */ + /* Flush LSA, if the lsa's age reached to MaxAge. */ + if (IS_LSA_MAXAGE(lsa)) { + UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + /* Create new Opaque-LSA/ROUTER INFORMATION instance. */ + new = ospf_router_info_lsa_new(NULL); + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + /* Install this LSA into LSDB. */ + /* Given "lsa" will be freed in the next function. */ + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "RI (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return new; + } + /* Flood updated LSA through AS */ + ospf_flood_through_as(top, NULL /*nbr */, new); + } + + /* Debug logging. */ + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug( + "LSA[Type%d:%pI4]: Refresh Opaque-LSA/ROUTER INFORMATION", + new->data->type, &new->data->id); + ospf_lsa_header_dump(new->data); + } + + return new; +} + +static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai, + enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + + zlog_debug("RI (%s): LSA schedule %s%s%s", __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : ""); + + /* Check LSA flags state coherence and collect area information */ + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) { + if ((ai == NULL) || (ai->area == NULL)) { + flog_warn( + EC_OSPF_LSA, + "RI (%s): Router Info is Area scope flooding but area is not set", + __func__); + return; + } + + if (!CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED) + && (opcode != REORIGINATE_THIS_LSA)) + return; + + if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED) + && (opcode == REORIGINATE_THIS_LSA)) + opcode = REFRESH_THIS_LSA; + + lsa.area = ai->area; + top = ai->area->ospf; + } else { + if (!CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED) + && (opcode != REORIGINATE_THIS_LSA)) + return; + + if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED) + && (opcode == REORIGINATE_THIS_LSA)) + opcode = REFRESH_THIS_LSA; + + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + lsa.area = NULL; + } + + lsa.data = &lsah; + lsah.type = OspfRI.scope; + + /* LSA ID is set to 0 for the Router Information. See RFC 4970 */ + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0); + lsah.id.s_addr = htonl(tmp); + + switch (opcode) { + case REORIGINATE_THIS_LSA: + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) + ospf_opaque_lsa_reoriginate_schedule( + (void *)ai->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_ROUTER_INFORMATION_LSA); + else + ospf_opaque_lsa_reoriginate_schedule( + (void *)top, OSPF_OPAQUE_AS_LSA, + OPAQUE_TYPE_ROUTER_INFORMATION_LSA); + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) + UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED); + else + UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(&lsa); + break; + } + + return; +} + +/* Callback to handle Segment Routing information */ +static int ospf_router_info_lsa_update(struct ospf_lsa *lsa) +{ + + /* Sanity Check */ + if (lsa == NULL) { + flog_warn(EC_OSPF_LSA, "RI (%s): Abort! LSA is NULL", + __func__); + return -1; + } + + /* Process only Opaque LSA */ + if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA) + && (lsa->data->type != OSPF_OPAQUE_AS_LSA)) + return 0; + + /* Process only Router Information LSA */ + if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)) + != OPAQUE_TYPE_ROUTER_INFORMATION_LSA) + return 0; + + /* Check if it is not my LSA */ + if (IS_LSA_SELF(lsa)) + return 0; + + /* Check if Router Info & Segment Routing are enable */ + if (!OspfRI.enabled || !OspfRI.sr_info.enabled) + return 0; + + /* Call Segment Routing LSA update or deletion */ + if (!IS_LSA_MAXAGE(lsa)) + ospf_sr_ri_lsa_update(lsa); + else + ospf_sr_ri_lsa_delete(lsa); + + return 0; +} + +/*------------------------------------------------------------------------* + * Following are vty session control functions. + *------------------------------------------------------------------------*/ + +#define check_tlv_size(size, msg) \ + do { \ + if (ntohs(tlvh->length) > size) { \ + if (vty != NULL) \ + vty_out(vty, " Wrong %s TLV size: %d(%d)\n", \ + msg, ntohs(tlvh->length), size); \ + else \ + zlog_debug(" Wrong %s TLV size: %d(%d)", \ + msg, ntohs(tlvh->length), size); \ + return size + TLV_HDR_SIZE; \ + } \ + } while (0) + +static uint16_t show_vty_router_cap(struct vty *vty, struct tlv_header *tlvh, + json_object *json) +{ + struct ri_tlv_router_cap *top = (struct ri_tlv_router_cap *)tlvh; + + check_tlv_size(RI_TLV_CAPABILITIES_SIZE, "Router Capabilities"); + + if (vty != NULL) + if (!json) + vty_out(vty, " Router Capabilities: 0x%x\n", + ntohl(top->value)); + else + json_object_string_addf(json, "routerCapabilities", + "0x%x", ntohl(top->value)); + else + zlog_debug(" Router Capabilities: 0x%x", ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_subtlv_address(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ri_pce_subtlv_address *top = + (struct ri_pce_subtlv_address *)tlvh; + + if (ntohs(top->address.type) == PCE_ADDRESS_IPV4) { + check_tlv_size(PCE_ADDRESS_IPV4_SIZE, "PCE Address"); + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Address: %pI4\n", + &top->address.value); + else + json_object_string_addf(json, "pceAddress", + "%pI4", + &top->address.value); + else + zlog_debug(" PCE Address: %pI4", + &top->address.value); + } else if (ntohs(top->address.type) == PCE_ADDRESS_IPV6) { + check_tlv_size(PCE_ADDRESS_IPV6_SIZE, "PCE Address"); + if (vty != NULL) + if (!json) + vty_out(vty, + " PCE Address: unsupported IPv6\n"); + else + json_object_string_add(json, "pceAddress", + "unsupported IPv6"); + + else + zlog_debug(" PCE Address: unsupported IPv6"); + } else { + if (vty != NULL) + vty_out(vty, " Wrong PCE Address type: 0x%x\n", + ntohl(top->address.type)); + else + zlog_debug(" Wrong PCE Address type: 0x%x", + ntohl(top->address.type)); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_subtlv_path_scope(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ri_pce_subtlv_path_scope *top = + (struct ri_pce_subtlv_path_scope *)tlvh; + + check_tlv_size(RI_PCE_SUBTLV_PATH_SCOPE_SIZE, "PCE Path Scope"); + + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Path Scope: 0x%x\n", + ntohl(top->value)); + else + json_object_string_addf(json, "pcePathScope", "0x%x", + ntohl(top->value)); + else + zlog_debug(" PCE Path Scope: 0x%x", ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_subtlv_domain(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ri_pce_subtlv_domain *top = (struct ri_pce_subtlv_domain *)tlvh; + struct in_addr tmp; + + check_tlv_size(RI_PCE_SUBTLV_DOMAIN_SIZE, "PCE Domain"); + + if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = top->value; + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Domain Area: %pI4\n", &tmp); + else + json_object_string_addf(json, "pceDomainArea", + "%pI4", &tmp); + else + zlog_debug(" PCE Domain Area: %pI4", &tmp); + } else if (ntohs(top->type) == PCE_DOMAIN_TYPE_AS) { + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Domain AS: %d\n", + ntohl(top->value)); + else + json_object_int_add(json, "pceDomainAS", + ntohl(top->value)); + else + zlog_debug(" PCE Domain AS: %d", ntohl(top->value)); + } else { + if (vty != NULL) + vty_out(vty, " Wrong PCE Domain type: %d\n", + ntohl(top->type)); + else + zlog_debug(" Wrong PCE Domain type: %d", + ntohl(top->type)); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_subtlv_neighbor(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + + struct ri_pce_subtlv_neighbor *top = + (struct ri_pce_subtlv_neighbor *)tlvh; + struct in_addr tmp; + + check_tlv_size(RI_PCE_SUBTLV_NEIGHBOR_SIZE, "PCE Neighbor"); + + if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = top->value; + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Neighbor Area: %pI4\n", + &tmp); + else + json_object_string_addf(json, "pceNeighborArea", + "%pI4", &tmp); + else + zlog_debug(" PCE Neighbor Area: %pI4", &tmp); + } else if (ntohs(top->type) == PCE_DOMAIN_TYPE_AS) { + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Neighbor AS: %d\n", + ntohl(top->value)); + else + json_object_int_add(json, "pceNeighborAS", + ntohl(top->value)); + else + zlog_debug(" PCE Neighbor AS: %d", + ntohl(top->value)); + } else { + if (vty != NULL) + vty_out(vty, " Wrong PCE Neighbor type: %d\n", + ntohl(top->type)); + else + zlog_debug(" Wrong PCE Neighbor type: %d", + ntohl(top->type)); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_subtlv_cap_flag(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct ri_pce_subtlv_cap_flag *top = + (struct ri_pce_subtlv_cap_flag *)tlvh; + + check_tlv_size(RI_PCE_SUBTLV_CAP_FLAG_SIZE, "PCE Capabilities"); + + if (vty != NULL) + if (!json) + vty_out(vty, " PCE Capabilities Flag: 0x%x\n", + ntohl(top->value)); + else + json_object_string_addf(json, "pceCapabilities", + "0x%x", ntohl(top->value)); + else + zlog_debug(" PCE Capabilities Flag: 0x%x", + ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh, + size_t buf_size, json_object *json) +{ + json_object *obj; + + if (TLV_SIZE(tlvh) > buf_size) { + if (vty != NULL) + vty_out(vty, + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + else + zlog_debug( + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + + if (vty != NULL) + if (!json) + vty_out(vty, + " Unknown TLV: [type(0x%x), length(0x%x)]\n", + ntohs(tlvh->type), ntohs(tlvh->length)); + else { + obj = json_object_new_object(); + json_object_string_addf(obj, "type", "0x%x", + ntohs(tlvh->type)); + json_object_string_addf(obj, "length", "0x%x", + ntohs(tlvh->length)); + json_object_object_add(json, "unknownTLV", obj); + } + else + zlog_debug(" Unknown TLV: [type(0x%x), length(0x%x)]", + ntohs(tlvh->type), ntohs(tlvh->length)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_pce_info(struct vty *vty, struct tlv_header *ri, + size_t buf_size, json_object *json) +{ + struct tlv_header *tlvh; + uint16_t length = ntohs(ri->length); + uint16_t sum = 0; + + /* Verify that TLV length is valid against remaining buffer size */ + if (length > buf_size) { + vty_out(vty, + " PCE Info TLV size %d exceeds buffer size. Abort!\n", + length); + return buf_size; + } + + for (tlvh = ri; sum < length; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case RI_PCE_SUBTLV_ADDRESS: + sum += show_vty_pce_subtlv_address(vty, tlvh, json); + break; + case RI_PCE_SUBTLV_PATH_SCOPE: + sum += show_vty_pce_subtlv_path_scope(vty, tlvh, json); + break; + case RI_PCE_SUBTLV_DOMAIN: + sum += show_vty_pce_subtlv_domain(vty, tlvh, json); + break; + case RI_PCE_SUBTLV_NEIGHBOR: + sum += show_vty_pce_subtlv_neighbor(vty, tlvh, json); + break; + case RI_PCE_SUBTLV_CAP_FLAG: + sum += show_vty_pce_subtlv_cap_flag(vty, tlvh, json); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, length - sum, + json); + break; + } + } + return sum; +} + +/* Display Segment Routing Algorithm TLV information */ +static uint16_t show_vty_sr_algorithm(struct vty *vty, struct tlv_header *tlvh, + json_object *json) +{ + struct ri_sr_tlv_sr_algorithm *algo = + (struct ri_sr_tlv_sr_algorithm *)tlvh; + int i; + json_object *json_algo, *obj; + char buf[2]; + + check_tlv_size(ALGORITHM_COUNT, "Segment Routing Algorithm"); + + if (vty != NULL) + if (!json) { + vty_out(vty, " Segment Routing Algorithm TLV:\n"); + for (i = 0; i < ntohs(algo->header.length); i++) { + switch (algo->value[i]) { + case 0: + vty_out(vty, + " Algorithm %d: SPF\n", i); + break; + case 1: + vty_out(vty, + " Algorithm %d: Strict SPF\n", + i); + break; + default: + vty_out(vty, + " Algorithm %d: Unknown value %d\n", i, + algo->value[i]); + break; + } + } + } else { + json_algo = json_object_new_array(); + json_object_object_add(json, "algorithms", + json_algo); + for (i = 0; i < ntohs(algo->header.length); i++) { + obj = json_object_new_object(); + snprintfrr(buf, 2, "%d", i); + switch (algo->value[i]) { + case 0: + json_object_string_add(obj, buf, "SPF"); + break; + case 1: + json_object_string_add(obj, buf, + "strictSPF"); + break; + default: + json_object_string_add(obj, buf, + "unknown"); + break; + } + json_object_array_add(json_algo, obj); + } + } + else { + zlog_debug(" Segment Routing Algorithm TLV:"); + for (i = 0; i < ntohs(algo->header.length); i++) + switch (algo->value[i]) { + case 0: + zlog_debug(" Algorithm %d: SPF", i); + break; + case 1: + zlog_debug(" Algorithm %d: Strict SPF", i); + break; + default: + zlog_debug(" Algorithm %d: Unknown value %d", + i, algo->value[i]); + break; + } + } + + return TLV_SIZE(tlvh); +} + +/* Display Segment Routing SID/Label Range TLV information */ +static uint16_t show_vty_sr_range(struct vty *vty, struct tlv_header *tlvh, + json_object *json) +{ + struct ri_sr_tlv_sid_label_range *range = + (struct ri_sr_tlv_sid_label_range *)tlvh; + json_object *obj; + uint32_t upper; + + check_tlv_size(RI_SR_TLV_LABEL_RANGE_SIZE, "SR Label Range"); + + if (vty != NULL) + if (!json) { + vty_out(vty, + " Segment Routing %s Range TLV:\n" + " Range Size = %d\n" + " SID Label = %d\n\n", + ntohs(range->header.type) == + RI_SR_TLV_SRGB_LABEL_RANGE + ? "Global" + : "Local", + GET_RANGE_SIZE(ntohl(range->size)), + GET_LABEL(ntohl(range->lower.value))); + } else { + /* + * According to draft-ietf-teas-yang-sr-te-topo, SRGB + * and SRLB are describe with lower and upper bounds + */ + upper = GET_LABEL(ntohl(range->lower.value)) + + GET_RANGE_SIZE(ntohl(range->size)) - 1; + obj = json_object_new_object(); + json_object_int_add(obj, "upperBound", upper); + json_object_int_add(obj, "lowerBound", + GET_LABEL(ntohl(range->lower.value))); + json_object_object_add(json, + ntohs(range->header.type) == + RI_SR_TLV_SRGB_LABEL_RANGE + ? "srgb" + : "srlb", + obj); + } + else { + zlog_debug( + " Segment Routing %s Range TLV: Range Size = %d SID Label = %d", + ntohs(range->header.type) == RI_SR_TLV_SRGB_LABEL_RANGE + ? "Global" + : "Local", + GET_RANGE_SIZE(ntohl(range->size)), + GET_LABEL(ntohl(range->lower.value))); + } + + return TLV_SIZE(tlvh); +} + +/* Display Segment Routing Maximum Stack Depth TLV information */ +static uint16_t show_vty_sr_msd(struct vty *vty, struct tlv_header *tlvh, + json_object *json) +{ + struct ri_sr_tlv_node_msd *msd = (struct ri_sr_tlv_node_msd *)tlvh; + + check_tlv_size(RI_SR_TLV_NODE_MSD_SIZE, "Node Maximum Stack Depth"); + + if (vty != NULL) + if (!json) + vty_out(vty, + " Segment Routing MSD TLV:\n" + " Node Maximum Stack Depth = %d\n", + msd->value); + else + json_object_int_add(json, "nodeMsd", msd->value); + else + zlog_debug( + " Segment Routing MSD TLV: Node Maximum Stack Depth = %d", + msd->value); + + return TLV_SIZE(tlvh); +} + +static void ospf_router_info_show_info(struct vty *vty, + struct json_object *json, + struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = lsa->data; + struct tlv_header *tlvh; + uint16_t length = 0, sum = 0; + json_object *jri = NULL, *jpce = NULL, *jsr = NULL; + + if (json) { + jri = json_object_new_object(); + json_object_object_add(json, "routerInformation", jri); + jpce = json_object_new_object(); + jsr = json_object_new_object(); + } + + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < length && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case RI_TLV_CAPABILITIES: + sum += show_vty_router_cap(vty, tlvh, jri); + break; + case RI_TLV_PCE: + tlvh++; + sum += TLV_HDR_SIZE; + sum += show_vty_pce_info(vty, tlvh, length - sum, jpce); + break; + case RI_SR_TLV_SR_ALGORITHM: + sum += show_vty_sr_algorithm(vty, tlvh, jsr); + break; + case RI_SR_TLV_SRGB_LABEL_RANGE: + case RI_SR_TLV_SRLB_LABEL_RANGE: + sum += show_vty_sr_range(vty, tlvh, jsr); + break; + case RI_SR_TLV_NODE_MSD: + sum += show_vty_sr_msd(vty, tlvh, jsr); + break; + + default: + sum += show_vty_unknown_tlv(vty, tlvh, length, jri); + break; + } + } + + if (json) { + if (json_object_object_length(jpce) > 1) + json_object_object_add(jri, "pceInformation", jpce); + if (json_object_object_length(jsr) > 1) + json_object_object_add(jri, "segmentRouting", jsr); + } + return; +} + +static void ospf_router_info_config_write_router(struct vty *vty) +{ + struct ospf_pce_info *pce = &OspfRI.pce_info; + struct listnode *node; + struct ri_pce_subtlv_domain *domain; + struct ri_pce_subtlv_neighbor *neighbor; + struct in_addr tmp; + + if (!OspfRI.enabled) + return; + + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) + vty_out(vty, " router-info as\n"); + else + vty_out(vty, " router-info area\n"); + + if (OspfRI.pce_info.enabled) { + + if (pce->pce_address.header.type != 0) + vty_out(vty, " pce address %pI4\n", + &pce->pce_address.address.value); + + if (pce->pce_cap_flag.header.type != 0) + vty_out(vty, " pce flag 0x%x\n", + ntohl(pce->pce_cap_flag.value)); + + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { + if (domain->header.type != 0) { + if (domain->type == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = domain->value; + vty_out(vty, " pce domain area %pI4\n", + &tmp); + } else { + vty_out(vty, " pce domain as %d\n", + ntohl(domain->value)); + } + } + } + + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { + if (neighbor->header.type != 0) { + if (neighbor->type == PCE_DOMAIN_TYPE_AREA) { + tmp.s_addr = neighbor->value; + vty_out(vty, + " pce neighbor area %pI4\n", + &tmp); + } else { + vty_out(vty, " pce neighbor as %d\n", + ntohl(neighbor->value)); + } + } + } + + if (pce->pce_scope.header.type != 0) + vty_out(vty, " pce scope 0x%x\n", + ntohl(OspfRI.pce_info.pce_scope.value)); + } + return; +} + +/*------------------------------------------------------------------------* + * Following are vty command functions. + *------------------------------------------------------------------------*/ +/* Simple wrapper schedule RI LSA action in function of the scope */ +static void ospf_router_info_schedule(enum lsa_opcode opcode) +{ + struct listnode *node, *nnode; + struct ospf_ri_area_info *ai; + + if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) { + if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)) + ospf_router_info_lsa_schedule(NULL, opcode); + else if (opcode == REORIGINATE_THIS_LSA) + ospf_router_info_lsa_schedule(NULL, opcode); + } else { + for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) { + if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)) + ospf_router_info_lsa_schedule(ai, opcode); + } + } +} + +DEFUN (router_info, + router_info_area_cmd, + "router-info ", + OSPF_RI_STR + "Enable the Router Information functionality with AS flooding scope\n" + "Enable the Router Information functionality with Area flooding scope\n") +{ + int idx_mode = 1; + uint8_t scope; + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (OspfRI.enabled) + return CMD_SUCCESS; + + /* Check that the OSPF is using default VRF */ + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "Router Information is only supported in default VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check and get Area value if present */ + if (strncmp(argv[idx_mode]->arg, "as", 2) == 0) + scope = OSPF_OPAQUE_AS_LSA; + else + scope = OSPF_OPAQUE_AREA_LSA; + + /* First start to register Router Information callbacks */ + if (!OspfRI.registered && (ospf_router_info_register(scope)) != 0) { + vty_out(vty, + "%% Unable to register Router Information callbacks."); + flog_err( + EC_OSPF_INIT_FAIL, + "RI (%s): Unable to register Router Information callbacks. Abort!", + __func__); + return CMD_WARNING_CONFIG_FAILED; + } + + OspfRI.enabled = true; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("RI-> Router Information (%s flooding): OFF -> ON", + OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" + : "AS"); + + /* + * Following code is intended to handle two cases; + * + * 1) Router Information was disabled at startup time, but now become + * enabled. + * 2) Router Information was once enabled then disabled, and now enabled + * again. + */ + + initialize_params(&OspfRI); + + /* Originate or Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REORIGINATE_THIS_LSA); + return CMD_SUCCESS; +} + +#if CONFDATE > 20240809 +CPP_NOTICE("Drop deprecated router_info_area_id_cmd") +#endif +ALIAS_HIDDEN (router_info, + router_info_area_id_cmd, + "router-info area A.B.C.D", + OSPF_RI_STR + "Enable the Router Information functionality with Area flooding scope\n" + "OSPF area ID in IP format (deprecated)\n") + +DEFUN (no_router_info, + no_router_info_cmd, + "no router-info []", + NO_STR + "Disable the Router Information functionality\n" + "Disable the Router Information functionality with AS flooding scope\n" + "Disable the Router Information functionality with Area flooding scope\n") +{ + + if (!OspfRI.enabled) + return CMD_SUCCESS; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("RI-> Router Information: ON -> OFF"); + + ospf_router_info_schedule(FLUSH_THIS_LSA); + + OspfRI.enabled = false; + + return CMD_SUCCESS; +} + +static int ospf_ri_enabled(struct vty *vty) +{ + if (OspfRI.enabled) + return 1; + + if (vty) + vty_out(vty, "%% OSPF RI is not turned on\n"); + + return 0; +} + +DEFUN (pce_address, + pce_address_cmd, + "pce address A.B.C.D", + PCE_STR + "Stable IP address of the PCE\n" + "PCE address in IPv4 address format\n") +{ + int idx_ipv4 = 2; + struct in_addr value; + struct ospf_pce_info *pi = &OspfRI.pce_info; + + if (!ospf_ri_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (!inet_aton(argv[idx_ipv4]->arg, &value)) { + vty_out(vty, "Please specify PCE Address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ntohs(pi->pce_address.header.type) == 0 + || ntohl(pi->pce_address.address.value.s_addr) + != ntohl(value.s_addr)) { + + set_pce_address(value, pi); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + } + + return CMD_SUCCESS; +} + +DEFUN (no_pce_address, + no_pce_address_cmd, + "no pce address [A.B.C.D]", + NO_STR + PCE_STR + "Disable PCE address\n" + "PCE address in IPv4 address format\n") +{ + + unset_param(&OspfRI.pce_info.pce_address); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (pce_path_scope, + pce_path_scope_cmd, + "pce scope BITPATTERN", + PCE_STR + "Path scope visibilities of the PCE for path computation\n" + "32-bit Hexadecimal value\n") +{ + int idx_bitpattern = 2; + uint32_t scope; + struct ospf_pce_info *pi = &OspfRI.pce_info; + + if (!ospf_ri_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &scope) != 1) { + vty_out(vty, "pce_path_scope: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ntohl(pi->pce_scope.header.type) == 0 + || scope != pi->pce_scope.value) { + set_pce_path_scope(scope, pi); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + } + + return CMD_SUCCESS; +} + +DEFUN (no_pce_path_scope, + no_pce_path_scope_cmd, + "no pce scope [BITPATTERN]", + NO_STR + PCE_STR + "Disable PCE path scope\n" + "32-bit Hexadecimal value\n") +{ + + unset_param(&OspfRI.pce_info.pce_address); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (pce_domain, + pce_domain_cmd, + "pce domain as (0-65535)", + PCE_STR + "Configure PCE domain AS number\n" + "AS number where the PCE as visibilities for path computation\n" + "AS number in decimal <0-65535>\n") +{ + int idx_number = 3; + + uint32_t as; + struct ospf_pce_info *pce = &OspfRI.pce_info; + struct listnode *node; + struct ri_pce_subtlv_domain *domain; + + if (!ospf_ri_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { + vty_out(vty, "pce_domain: fscanf: %s\n", safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check if the domain is not already in the domain list */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { + if (ntohl(domain->header.type) == 0 && as == domain->value) + return CMD_SUCCESS; + } + + /* Create new domain if not found */ + set_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (no_pce_domain, + no_pce_domain_cmd, + "no pce domain as (0-65535)", + NO_STR + PCE_STR + "Disable PCE domain AS number\n" + "AS number where the PCE as visibilities for path computation\n" + "AS number in decimal <0-65535>\n") +{ + int idx_number = 4; + + uint32_t as; + struct ospf_pce_info *pce = &OspfRI.pce_info; + + if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { + vty_out(vty, "no_pce_domain: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Unset corresponding PCE domain */ + unset_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (pce_neigbhor, + pce_neighbor_cmd, + "pce neighbor as (0-65535)", + PCE_STR + "Configure PCE neighbor domain AS number\n" + "AS number of PCE neighbors\n" + "AS number in decimal <0-65535>\n") +{ + int idx_number = 3; + + uint32_t as; + struct ospf_pce_info *pce = &OspfRI.pce_info; + struct listnode *node; + struct ri_pce_subtlv_neighbor *neighbor; + + if (!ospf_ri_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { + vty_out(vty, "pce_neighbor: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check if the domain is not already in the domain list */ + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { + if (ntohl(neighbor->header.type) == 0 && as == neighbor->value) + return CMD_SUCCESS; + } + + /* Create new domain if not found */ + set_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (no_pce_neighbor, + no_pce_neighbor_cmd, + "no pce neighbor as (0-65535)", + NO_STR + PCE_STR + "Disable PCE neighbor AS number\n" + "AS number of PCE neighbor\n" + "AS number in decimal <0-65535>\n") +{ + int idx_number = 4; + + uint32_t as; + struct ospf_pce_info *pce = &OspfRI.pce_info; + + if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) { + vty_out(vty, "no_pce_neighbor: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Unset corresponding PCE domain */ + unset_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (pce_cap_flag, + pce_cap_flag_cmd, + "pce flag BITPATTERN", + PCE_STR + "Capabilities of the PCE for path computation\n" + "32-bit Hexadecimal value\n") +{ + int idx_bitpattern = 2; + + uint32_t cap; + struct ospf_pce_info *pce = &OspfRI.pce_info; + + if (!ospf_ri_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &cap) != 1) { + vty_out(vty, "pce_cap_flag: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ntohl(pce->pce_cap_flag.header.type) == 0 + || cap != pce->pce_cap_flag.value) { + set_pce_cap_flag(cap, pce); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + } + + return CMD_SUCCESS; +} + +DEFUN (no_pce_cap_flag, + no_pce_cap_flag_cmd, + "no pce flag", + NO_STR + PCE_STR + "Disable PCE capabilities\n") +{ + + unset_param(&OspfRI.pce_info.pce_cap_flag); + + /* Refresh RI LSA if already engaged */ + ospf_router_info_schedule(REFRESH_THIS_LSA); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_router_info, + show_ip_ospf_router_info_cmd, + "show ip ospf router-info", + SHOW_STR + IP_STR + OSPF_STR + "Router Information\n") +{ + + if (OspfRI.enabled) { + vty_out(vty, "--- Router Information parameters ---\n"); + show_vty_router_cap(vty, &OspfRI.router_cap.header, NULL); + } else { + if (vty != NULL) + vty_out(vty, + " Router Information is disabled on this router\n"); + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_opsf_router_info_pce, + show_ip_ospf_router_info_pce_cmd, + "show ip ospf router-info pce", + SHOW_STR + IP_STR + OSPF_STR + "Router Information\n" + "PCE information\n") +{ + + struct ospf_pce_info *pce = &OspfRI.pce_info; + struct listnode *node; + struct ri_pce_subtlv_domain *domain; + struct ri_pce_subtlv_neighbor *neighbor; + + if ((OspfRI.enabled) && (OspfRI.pce_info.enabled)) { + vty_out(vty, "--- PCE parameters ---\n"); + + if (pce->pce_address.header.type != 0) + show_vty_pce_subtlv_address(vty, + &pce->pce_address.header, + NULL); + + if (pce->pce_scope.header.type != 0) + show_vty_pce_subtlv_path_scope(vty, + &pce->pce_scope.header, + NULL); + + for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) { + if (domain->header.type != 0) + show_vty_pce_subtlv_domain(vty, + &domain->header, + NULL); + } + + for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) { + if (neighbor->header.type != 0) + show_vty_pce_subtlv_neighbor(vty, + &neighbor->header, + NULL); + } + + if (pce->pce_cap_flag.header.type != 0) + show_vty_pce_subtlv_cap_flag(vty, + &pce->pce_cap_flag.header, + NULL); + + } else { + vty_out(vty, " PCE info is disabled on this router\n"); + } + + return CMD_SUCCESS; +} + +/* Install new CLI commands */ +static void ospf_router_info_register_vty(void) +{ + install_element(VIEW_NODE, &show_ip_ospf_router_info_cmd); + install_element(VIEW_NODE, &show_ip_ospf_router_info_pce_cmd); + + install_element(OSPF_NODE, &router_info_area_cmd); + install_element(OSPF_NODE, &router_info_area_id_cmd); + install_element(OSPF_NODE, &no_router_info_cmd); + install_element(OSPF_NODE, &pce_address_cmd); + install_element(OSPF_NODE, &no_pce_address_cmd); + install_element(OSPF_NODE, &pce_path_scope_cmd); + install_element(OSPF_NODE, &no_pce_path_scope_cmd); + install_element(OSPF_NODE, &pce_domain_cmd); + install_element(OSPF_NODE, &no_pce_domain_cmd); + install_element(OSPF_NODE, &pce_neighbor_cmd); + install_element(OSPF_NODE, &no_pce_neighbor_cmd); + install_element(OSPF_NODE, &pce_cap_flag_cmd); + install_element(OSPF_NODE, &no_pce_cap_flag_cmd); + + return; +} diff --git a/ospfd/ospf_ri.h b/ospfd/ospf_ri.h new file mode 100644 index 0000000..0870938 --- /dev/null +++ b/ospfd/ospf_ri.h @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC4970 Router Information + * with support of RFC5088 PCE Capabilites announcement + * and support of draft-ietf-ospf-segment-routing-extensions-18 + * for Segment Routing Capabilities announcement + * + * + * Module name: Router Information + * Author: Olivier Dugeon + * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/ + */ + +#ifndef _ZEBRA_OSPF_ROUTER_INFO_H +#define _ZEBRA_OSPF_ROUTER_INFO_H + +/* + * Opaque LSA's link state ID for Router Information is + * structured as follows. + * + * 24 16 8 0 + * +--------+--------+--------+--------+ + * | 4 | MBZ |........|........| + * +--------+--------+--------+--------+ + * |<-Type->||<-- Instance --->| + * + * + * Type: IANA has assigned '4' for Router Information. + * MBZ: Reserved, must be set to zero. + * Instance: User may select an arbitrary 16-bit value. + * + */ + +/* + * 24 16 8 0 + * +--------+--------+--------+--------+ --- + * | LS age |Options | 9,10,11| A + * +--------+--------+--------+--------+ | + * | 4 | 0 | Instance | | + * +--------+--------+--------+--------+ | + * | Advertising router | | Standard (Opaque) LSA header; + * +--------+--------+--------+--------+ | Type 9,10 or 11 are used. + * | LS sequence number | | + * +--------+--------+--------+--------+ | + * | LS checksum | Length | V + * +--------+--------+--------+--------+ --- + * | Type | Length | A TLV part for Router Information; + * +--------+--------+--------+--------+ | Values might be + * | Values ... | V structured as a set of sub-TLVs. + * +--------+--------+--------+--------+ --- + */ + +/* + * Following section defines TLV body parts. + */ + +/* Up to now, 11 code points have been assigned to Router Information */ +/* Only type 1 Router Capabilities and 6 PCE are supported with this code */ +#define RI_IANA_MAX_TYPE 11 + +/* RFC4970: Router Information Capabilities TLV */ /* Mandatory */ +#define RI_TLV_CAPABILITIES 1 +#define RI_TLV_CAPABILITIES_SIZE 4 +struct ri_tlv_router_cap { + struct tlv_header header; /* Value length is 4 bytes. */ + uint32_t value; +}; + +/* Capabilities bits are left align */ +#define RI_GRACE_RESTART 0x80000000 +#define RI_GRACE_HELPER 0x40000000 +#define RI_STUB_SUPPORT 0x20000000 +#define RI_TE_SUPPORT 0x10000000 +#define RI_P2P_OVER_LAN 0x08000000 +#define RI_TE_EXPERIMENTAL 0x04000000 + +#define RI_TLV_LENGTH 4 + +/* RFC5088: PCE Capabilities TLV */ /* Optional */ +/* RI PCE TLV */ +#define RI_TLV_PCE 6 + +struct ri_tlv_pce { + struct tlv_header header; + /* A set of PCE-sub-TLVs will follow. */ +}; + +/* PCE Address Sub-TLV */ /* Mandatory */ +#define RI_PCE_SUBTLV_ADDRESS 1 +struct ri_pce_subtlv_address { + /* Type = 1; Length is 8 (IPv4) or 20 (IPv6) bytes. */ + struct tlv_header header; +#define PCE_ADDRESS_IPV4_SIZE 8 +#define PCE_ADDRESS_IPV6_SIZE 20 + struct { + uint16_t type; /* Address type: 1 = IPv4, 2 = IPv6 */ +#define PCE_ADDRESS_IPV4 1 +#define PCE_ADDRESS_IPV6 2 + uint16_t reserved; + struct in_addr value; /* PCE address */ + } address; +}; + +/* PCE Path-Scope Sub-TLV */ /* Mandatory */ +#define RI_PCE_SUBTLV_PATH_SCOPE 2 +#define RI_PCE_SUBTLV_PATH_SCOPE_SIZE 4 +struct ri_pce_subtlv_path_scope { + struct tlv_header header; /* Type = 2; Length = 4 bytes. */ + /* + * L, R, Rd, S, Sd, Y, PrefL, PrefR, PrefS and PrefY bits: + * see RFC5088 page 9 + */ + uint32_t value; +}; + +/* PCE Domain Sub-TLV */ /* Optional */ +#define PCE_DOMAIN_TYPE_AREA 1 +#define PCE_DOMAIN_TYPE_AS 2 + +#define RI_PCE_SUBTLV_DOMAIN 3 +#define RI_PCE_SUBTLV_DOMAIN_SIZE 8 +struct ri_pce_subtlv_domain { + struct tlv_header header; /* Type = 3; Length = 8 bytes. */ + uint16_t type; /* Domain type: 1 = OSPF Area ID, 2 = AS Number */ + uint16_t reserved; + uint32_t value; +}; + +/* PCE Neighbor Sub-TLV */ /* Mandatory if R or S bit is set */ +#define RI_PCE_SUBTLV_NEIGHBOR 4 +#define RI_PCE_SUBTLV_NEIGHBOR_SIZE 8 +struct ri_pce_subtlv_neighbor { + struct tlv_header header; /* Type = 4; Length = 8 bytes. */ + uint16_t type; /* Domain type: 1 = OSPF Area ID, 2 = AS Number */ + uint16_t reserved; + uint32_t value; +}; + +/* PCE Capabilities Flags Sub-TLV */ /* Optional */ +#define RI_PCE_SUBTLV_CAP_FLAG 5 +#define RI_PCE_SUBTLV_CAP_FLAG_SIZE 4 + +#define PCE_CAP_GMPLS_LINK 0x0001 +#define PCE_CAP_BIDIRECTIONAL 0x0002 +#define PCE_CAP_DIVERSE_PATH 0x0004 +#define PCE_CAP_LOAD_BALANCE 0x0008 +#define PCE_CAP_SYNCHRONIZED 0x0010 +#define PCE_CAP_OBJECTIVES 0x0020 +#define PCE_CAP_ADDITIVE 0x0040 +#define PCE_CAP_PRIORIZATION 0x0080 +#define PCE_CAP_MULTIPLE_REQ 0x0100 + +struct ri_pce_subtlv_cap_flag { + struct tlv_header header; /* Type = 5; Length = n x 4 bytes. */ + uint32_t value; +}; + +/* Structure to share flooding scope info for Segment Routing */ +struct scope_info { + uint8_t scope; + struct list *areas; +}; + +/* Flags to manage the Router Information LSA. */ +#define RIFLG_LSA_INACTIVE 0x0 +#define RIFLG_LSA_ENGAGED 0x1 +#define RIFLG_LSA_FORCED_REFRESH 0x2 + +/* Store Router Information PCE TLV and SubTLV in network byte order. */ +struct ospf_pce_info { + bool enabled; + struct ri_tlv_pce pce_header; + struct ri_pce_subtlv_address pce_address; + struct ri_pce_subtlv_path_scope pce_scope; + struct list *pce_domain; + struct list *pce_neighbor; + struct ri_pce_subtlv_cap_flag pce_cap_flag; +}; + +/* + * Store Router Information Segment Routing TLV and SubTLV + * in network byte order + */ +struct ospf_ri_sr_info { + bool enabled; + /* Algorithms supported by the node */ + struct ri_sr_tlv_sr_algorithm algo; + /* + * Segment Routing Global Block i.e. label range + * Only one range supported in this code + */ + struct ri_sr_tlv_sid_label_range srgb; + /* + * Segment Routing Local Block. + * Only one block is authorized - see section 3.3 + */ + struct ri_sr_tlv_sid_label_range srlb; + /* Maximum SID Depth supported by the node */ + struct ri_sr_tlv_node_msd msd; +}; + +/* Store area information to flood LSA per area */ +struct ospf_ri_area_info { + + uint32_t flags; + + /* area pointer if flooding is Type 10 Null if flooding is AS scope */ + struct ospf_area *area; +}; + +/* Following structure are internal use only. */ +struct ospf_router_info { + bool enabled; + + uint8_t registered; + uint8_t scope; + /* LSA flags are only used when scope is AS flooding */ + uint32_t as_flags; + + /* List of area info to flood RI LSA */ + struct list *area_info; + + /* Store Router Information Capabilities LSA */ + struct ri_tlv_router_cap router_cap; + + /* Store PCE capability LSA */ + struct ospf_pce_info pce_info; + + /* Store SR capability LSA */ + struct ospf_ri_sr_info sr_info; +}; + +/* Prototypes. */ +extern int ospf_router_info_init(void); +extern void ospf_router_info_term(void); +extern void ospf_router_info_finish(void); +extern int ospf_router_info_enable(void); +extern void ospf_router_info_update_sr(bool enable, struct sr_node *self); +extern struct scope_info ospf_router_info_get_flooding_scope(void); +#endif /* _ZEBRA_OSPF_ROUTER_INFO_H */ diff --git a/ospfd/ospf_route.c b/ospfd/ospf_route.c new file mode 100644 index 0000000..170909f --- /dev/null +++ b/ospfd/ospf_route.c @@ -0,0 +1,1121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF routing table. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "prefix.h" +#include "table.h" +#include "memory.h" +#include "linklist.h" +#include "log.h" +#include "if.h" +#include "command.h" +#include "sockunion.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_dump.h" + +const char *ospf_path_type_name(int path_type) +{ + switch (path_type) { + case OSPF_PATH_INTRA_AREA: + return "Intra-Area"; + case OSPF_PATH_INTER_AREA: + return "Inter-Area"; + case OSPF_PATH_TYPE1_EXTERNAL: + return "External-1"; + case OSPF_PATH_TYPE2_EXTERNAL: + return "External-2"; + default: + return "Unknown"; + } +} + +struct ospf_route *ospf_route_new(void) +{ + struct ospf_route *new; + + new = XCALLOC(MTYPE_OSPF_ROUTE, sizeof(struct ospf_route)); + + new->paths = list_new(); + new->paths->del = (void (*)(void *))ospf_path_free; + new->u.std.transit = false; + + return new; +} + +void ospf_route_free(struct ospf_route *or) +{ + if (or->paths) + list_delete(& or->paths); + + XFREE(MTYPE_OSPF_ROUTE, or); +} + +struct ospf_path *ospf_path_new(void) +{ + struct ospf_path *new; + + new = XCALLOC(MTYPE_OSPF_PATH, sizeof(struct ospf_path)); + + return new; +} + +static struct ospf_path *ospf_path_dup(struct ospf_path *path) +{ + struct ospf_path *new; + int memsize; + + new = ospf_path_new(); + memcpy(new, path, sizeof(struct ospf_path)); + + /* optional TI-LFA backup paths */ + if (path->srni.backup_label_stack) { + memsize = sizeof(struct mpls_label_stack) + + (sizeof(mpls_label_t) + * path->srni.backup_label_stack->num_labels); + new->srni.backup_label_stack = + XCALLOC(MTYPE_OSPF_PATH, memsize); + memcpy(new->srni.backup_label_stack, + path->srni.backup_label_stack, memsize); + } + + return new; +} + +void ospf_path_free(struct ospf_path *op) +{ + /* optional TI-LFA backup paths */ + if (op->srni.backup_label_stack) + XFREE(MTYPE_OSPF_PATH, op->srni.backup_label_stack); + + XFREE(MTYPE_OSPF_PATH, op); +} + +void ospf_route_delete(struct ospf *ospf, struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) { + if (or->type == OSPF_DESTINATION_NETWORK) + ospf_zebra_delete( + ospf, (struct prefix_ipv4 *)&rn->p, or); + else if (or->type == OSPF_DESTINATION_DISCARD) + ospf_zebra_delete_discard( + ospf, (struct prefix_ipv4 *)&rn->p); + } +} + +void ospf_route_table_free(struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) { + ospf_route_free(or); + + rn->info = NULL; + route_unlock_node(rn); + } + + route_table_finish(rt); +} + +/* If a prefix exists in the new routing table, then return 1, + otherwise return 0. Since the ZEBRA-RIB does an implicit + withdraw, it is not necessary to send a delete, an add later + will act like an implicit delete. */ +static int ospf_route_exist_new_table(struct route_table *rt, + struct prefix_ipv4 *prefix) +{ + struct route_node *rn; + + assert(rt); + assert(prefix); + + rn = route_node_lookup(rt, (struct prefix *)prefix); + if (!rn) { + return 0; + } + route_unlock_node(rn); + + if (!rn->info) { + return 0; + } + + return 1; +} + +static int ospf_route_backup_path_same(struct sr_nexthop_info *srni1, + struct sr_nexthop_info *srni2) +{ + struct mpls_label_stack *ls1, *ls2; + uint8_t label_count; + + ls1 = srni1->backup_label_stack; + ls2 = srni2->backup_label_stack; + + if (!ls1 && !ls2) + return 1; + + if ((ls1 && !ls2) || (!ls1 && ls2)) + return 0; + + if (ls1->num_labels != ls2->num_labels) + return 0; + + for (label_count = 0; label_count < ls1->num_labels; label_count++) { + if (ls1->label[label_count] != ls2->label[label_count]) + return 0; + } + + if (!IPV4_ADDR_SAME(&srni1->backup_nexthop, &srni2->backup_nexthop)) + return 0; + + return 1; +} + +/* If a prefix and a nexthop match any route in the routing table, + then return 1, otherwise return 0. */ +int ospf_route_match_same(struct route_table *rt, struct prefix_ipv4 *prefix, + struct ospf_route *newor) +{ + struct route_node *rn; + struct ospf_route * or ; + struct ospf_path *op; + struct ospf_path *newop; + struct listnode *n1; + struct listnode *n2; + + if (!rt || !prefix) + return 0; + + rn = route_node_lookup(rt, (struct prefix *)prefix); + if (!rn || !rn->info) + return 0; + + route_unlock_node(rn); + + or = rn->info; + if (or->type == newor->type && or->cost == newor->cost) { + if (or->changed) + return 0; + + if (or->type == OSPF_DESTINATION_NETWORK) { + if (or->paths->count != newor->paths->count) + return 0; + + /* Check each path. */ + for (n1 = listhead(or->paths), + n2 = listhead(newor->paths); + n1 && n2; n1 = listnextnode_unchecked(n1), + n2 = listnextnode_unchecked(n2)) { + op = listgetdata(n1); + newop = listgetdata(n2); + + if (!IPV4_ADDR_SAME(&op->nexthop, + &newop->nexthop)) + return 0; + if (op->ifindex != newop->ifindex) + return 0; + + /* check TI-LFA backup paths */ + if (!ospf_route_backup_path_same(&op->srni, + &newop->srni)) + return 0; + } + return 1; + } else if (prefix_same(&rn->p, (struct prefix *)prefix)) + return 1; + } + return 0; +} + +/* delete routes generated from AS-External routes if there is a inter/intra + * area route + */ +static void ospf_route_delete_same_ext(struct ospf *ospf, + struct route_table *external_routes, + struct route_table *routes) +{ + struct route_node *rn, *ext_rn; + + if ((external_routes == NULL) || (routes == NULL)) + return; + + /* Remove deleted routes */ + for (rn = route_top(routes); rn; rn = route_next(rn)) { + if (rn && rn->info) { + struct prefix_ipv4 *p = (struct prefix_ipv4 *)(&rn->p); + if ((ext_rn = route_node_lookup(external_routes, + (struct prefix *)p))) { + if (ext_rn->info) { + ospf_zebra_delete(ospf, p, + ext_rn->info); + ospf_route_free(ext_rn->info); + ext_rn->info = NULL; + } + route_unlock_node(ext_rn); + } + } + } +} + +/* rt: Old, cmprt: New */ +static void ospf_route_delete_uniq(struct ospf *ospf, struct route_table *rt, + struct route_table *cmprt) +{ + struct route_node *rn; + struct ospf_route * or ; + + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) + if (or->path_type == OSPF_PATH_INTRA_AREA || + or->path_type == OSPF_PATH_INTER_AREA) { + if (or->type == OSPF_DESTINATION_NETWORK) { + if (!ospf_route_exist_new_table( + cmprt, + (struct prefix_ipv4 *)&rn + ->p)) + ospf_zebra_delete( + ospf, + (struct prefix_ipv4 + *)&rn->p, + or); + } else if (or->type == OSPF_DESTINATION_DISCARD) + if (!ospf_route_exist_new_table( + cmprt, + (struct prefix_ipv4 *)&rn + ->p)) + ospf_zebra_delete_discard( + ospf, + (struct prefix_ipv4 + *)&rn->p); + } +} + +/* Install routes to table. */ +void ospf_route_install(struct ospf *ospf, struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + + /* rt contains new routing table, new_table contains an old one. + updating pointers */ + if (ospf->old_table) + ospf_route_table_free(ospf->old_table); + + ospf->old_table = ospf->new_table; + ospf->new_table = rt; + + /* Delete old routes. */ + if (ospf->old_table) + ospf_route_delete_uniq(ospf, ospf->old_table, rt); + if (ospf->old_external_route) + ospf_route_delete_same_ext(ospf, ospf->old_external_route, rt); + + /* Install new routes. */ + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) { + if (or->type == OSPF_DESTINATION_NETWORK) { + if (!ospf_route_match_same( + ospf->old_table, + (struct prefix_ipv4 *)&rn->p, or)) + ospf_zebra_add( + ospf, + (struct prefix_ipv4 *)&rn->p, + or); + } else if (or->type == OSPF_DESTINATION_DISCARD) + if (!ospf_route_match_same( + ospf->old_table, + (struct prefix_ipv4 *)&rn->p, or)) + ospf_zebra_add_discard( + ospf, + (struct prefix_ipv4 *)&rn->p); + } +} + +/* RFC2328 16.1. (4). For "router". */ +void ospf_intra_add_router(struct route_table *rt, struct vertex *v, + struct ospf_area *area, bool add_only) +{ + struct route_node *rn; + struct ospf_route * or ; + struct prefix_ipv4 p; + struct router_lsa *lsa; + + if (IS_DEBUG_OSPF_EVENT) { + if (!add_only) + zlog_debug("%s: Start", __func__); + else + zlog_debug("%s: REACHRUN: Start", __func__); + } + lsa = (struct router_lsa *)v->lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: LS ID: %pI4", __func__, &lsa->header.id); + + if (!add_only) { + if (!OSPF_IS_AREA_BACKBONE(area)) + ospf_vl_up_check(area, lsa->header.id, v); + + if (!CHECK_FLAG(lsa->flags, ROUTER_LSA_SHORTCUT)) + area->shortcut_capability = 0; + + /* If the newly added vertex is an area border router or AS + boundary router, a routing table entry is added whose + destination type is "router". */ + if (!IS_ROUTER_LSA_BORDER(lsa) && + !IS_ROUTER_LSA_EXTERNAL(lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: this router is neither ASBR nor ABR, skipping it", + __func__); + return; + } + + /* Update ABR and ASBR count in this area. */ + if (IS_ROUTER_LSA_BORDER(lsa)) + area->abr_count++; + if (IS_ROUTER_LSA_EXTERNAL(lsa)) + area->asbr_count++; + } + + /* The Options field found in the associated router-LSA is copied + into the routing table entry's Optional capabilities field. Call + the newly added vertex Router X. */ + or = ospf_route_new(); + + or->id = v->id; + or->u.std.area_id = area->area_id; + or->u.std.external_routing = area->external_routing; + or->path_type = OSPF_PATH_INTRA_AREA; + or->cost = v->distance; + or->type = OSPF_DESTINATION_ROUTER; + or->u.std.origin = (struct lsa_header *)lsa; + or->u.std.options = lsa->header.options; + or->u.std.flags = lsa->flags; + + /* If Router X is the endpoint of one of the calculating router's + virtual links, and the virtual link uses Area A as Transit area: + the virtual link is declared up, the IP address of the virtual + interface is set to the IP address of the outgoing interface + calculated above for Router X, and the virtual neighbor's IP + address is set to Router X's interface address (contained in + Router X's router-LSA) that points back to the root of the + shortest- path tree; equivalently, this is the interface that + points back to Router X's parent vertex on the shortest-path tree + (similar to the calculation in Section 16.1.1). */ + + p.family = AF_INET; + p.prefix = v->id; + p.prefixlen = IPV4_MAX_BITLEN; + apply_mask_ipv4(&p); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: talking about %pFX", __func__, &p); + + rn = route_node_get(rt, (struct prefix *)&p); + + /* Note that we keep all routes to ABRs and ASBRs, not only the best */ + if (rn->info == NULL) + rn->info = list_new(); + else + route_unlock_node(rn); + + ospf_route_copy_nexthops_from_vertex(area, or, v); + + listnode_add(rn->info, or); + + if (IS_DEBUG_OSPF_EVENT) { + if (!add_only) + zlog_debug("%s: Stop", __func__); + else + zlog_debug("%s: REACHRUN: Stop", __func__); + } +} + +/* RFC2328 16.1. (4). For transit network. */ +void ospf_intra_add_transit(struct route_table *rt, struct vertex *v, + struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_route * or ; + struct prefix_ipv4 p; + struct network_lsa *lsa; + + lsa = (struct network_lsa *)v->lsa; + + /* If the newly added vertex is a transit network, the routing table + entry for the network is located. The entry's Destination ID is + the IP network number, which can be obtained by masking the + Vertex ID (Link State ID) with its associated subnet mask (found + in the body of the associated network-LSA). */ + if (lsa->mask.s_addr == 0xffffffff) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Suppress installing LSA[Type2,%pI4] route due to host mask", + &(lsa->header.id)); + return; + } + p.family = AF_INET; + p.prefix = v->id; + p.prefixlen = ip_masklen(lsa->mask); + apply_mask_ipv4(&p); + + rn = route_node_get(rt, (struct prefix *)&p); + + /* If the routing table entry already exists (i.e., there is already + an intra-area route to the destination installed in the routing + table), multiple vertices have mapped to the same IP network. + For example, this can occur when a new Designated Router is being + established. In this case, the current routing table entry + should be overwritten if and only if the newly found path is just + as short and the current routing table entry's Link State Origin + has a smaller Link State ID than the newly added vertex' LSA. */ + if (rn->info) { + struct ospf_route *cur_or; + + route_unlock_node(rn); + cur_or = rn->info; + + if (v->distance > cur_or->cost + || IPV4_ADDR_CMP(&cur_or->u.std.origin->id, &lsa->header.id) + > 0) + return; + + ospf_route_free(rn->info); + } + + or = ospf_route_new(); + + or->id = v->id; + or->u.std.area_id = area->area_id; + or->u.std.external_routing = area->external_routing; + or->path_type = OSPF_PATH_INTRA_AREA; + or->cost = v->distance; + or->type = OSPF_DESTINATION_NETWORK; + or->u.std.origin = (struct lsa_header *)lsa; + or->u.std.transit = true; + + ospf_route_copy_nexthops_from_vertex(area, or, v); + + rn->info = or ; +} + +/* RFC2328 16.1. second stage. */ +void ospf_intra_add_stub(struct route_table *rt, struct router_lsa_link *link, + struct vertex *v, struct ospf_area *area, + int parent_is_root, int lsa_pos) +{ + uint32_t cost; + struct route_node *rn; + struct ospf_route * or ; + struct prefix_ipv4 p; + struct router_lsa *lsa; + struct ospf_interface *oi = NULL; + struct ospf_path *path; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Start", __func__); + + lsa = (struct router_lsa *)v->lsa; + + p.family = AF_INET; + p.prefix = link->link_id; + p.prefixlen = ip_masklen(link->link_data); + apply_mask_ipv4(&p); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: processing route to %pFX", __func__, &p); + + /* (1) Calculate the distance D of stub network from the root. D is + equal to the distance from the root to the router vertex + (calculated in stage 1), plus the stub network link's advertised + cost. */ + cost = v->distance + ntohs(link->m[0].metric); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: calculated cost is %d + %d = %d", __func__, + v->distance, ntohs(link->m[0].metric), cost); + + /* PtP links with /32 masks adds host routes to remote, directly + * connected hosts, see RFC 2328, 12.4.1.1, Option 1. + * Such routes can just be ignored for the sake of tidyness. + */ + if (parent_is_root && link->link_data.s_addr == 0xffffffff + && ospf_if_lookup_by_local_addr(area->ospf, NULL, link->link_id)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ignoring host route %pI4/32 to self.", + __func__, &link->link_id); + return; + } + + rn = route_node_get(rt, (struct prefix *)&p); + + /* Lookup current routing table. */ + if (rn->info) { + struct ospf_route *cur_or; + + route_unlock_node(rn); + + cur_or = rn->info; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: another route to the same prefix found with cost %u", + __func__, cur_or->cost); + + /* Compare this distance to the current best cost to the stub + network. This is done by looking up the stub network's + current routing table entry. If the calculated distance D is + larger, go on to examine the next stub network link in the + LSA. */ + if (cost > cur_or->cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: old route is better, exit", + __func__); + return; + } + + /* (2) If this step is reached, the stub network's routing table + entry must be updated. Calculate the set of next hops that + would result from using the stub network link. This + calculation is shown in Section 16.1.1; input to this + calculation is the destination (the stub network) and the + parent vertex (the router vertex). If the distance D is the + same as the current routing table cost, simply add this set + of next hops to the routing table entry's list of next hops. + In this case, the routing table already has a Link State + Origin. If this Link State Origin is a router-LSA whose Link + State ID is smaller than V's Router ID, reset the Link State + Origin to V's router-LSA. */ + + if (cost == cur_or->cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: routes are equal, merge", + __func__); + + ospf_route_copy_nexthops_from_vertex(area, cur_or, v); + + if (IPV4_ADDR_CMP(&cur_or->u.std.origin->id, + &lsa->header.id) + < 0) + cur_or->u.std.origin = (struct lsa_header *)lsa; + return; + } + + /* Otherwise D is smaller than the routing table cost. + Overwrite the current routing table entry by setting the + routing table entry's cost to D, and by setting the entry's + list of next hops to the newly calculated set. Set the + routing table entry's Link State Origin to V's router-LSA. + Then go on to examine the next stub network link. */ + + if (cost < cur_or->cost) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: new route is better, set it", + __func__); + + cur_or->cost = cost; + + list_delete_all_node(cur_or->paths); + + ospf_route_copy_nexthops_from_vertex(area, cur_or, v); + + cur_or->u.std.origin = (struct lsa_header *)lsa; + return; + } + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: installing new route", __func__); + + or = ospf_route_new(); + + or->id = v->id; + or->u.std.area_id = area->area_id; + or->u.std.external_routing = area->external_routing; + or->path_type = OSPF_PATH_INTRA_AREA; + or->cost = cost; + or->type = OSPF_DESTINATION_NETWORK; + or->u.std.origin = (struct lsa_header *)lsa; + + /* Nexthop is depend on connection type. */ + if (v != area->spf) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this network is on remote router", + __func__); + ospf_route_copy_nexthops_from_vertex(area, or, v); + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: this network is on this router", + __func__); + + /* + * Only deal with interface data when we + * don't do a dry run + */ + if (!area->spf_dry_run) + oi = ospf_if_lookup_by_lsa_pos(area, lsa_pos); + + if (oi || area->spf_dry_run) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: the lsa pos is %d", __func__, + lsa_pos); + + path = ospf_path_new(); + path->nexthop.s_addr = INADDR_ANY; + + if (oi) { + path->ifindex = oi->ifp->ifindex; + if (CHECK_FLAG(oi->connected->flags, + ZEBRA_IFA_UNNUMBERED)) + path->unnumbered = 1; + } + + listnode_add(or->paths, path); + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: where's the interface ?", + __func__); + } + } + if (rn->info) + ospf_route_free(rn->info); + + rn->info = or ; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop", __func__); +} + +static const char *const ospf_path_type_str[] = { + "unknown-type", "intra-area", "inter-area", "type1-external", + "type2-external" +}; + +void ospf_route_table_dump(struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *pnode; + struct ospf_path *path; + + zlog_debug("========== OSPF routing table =========="); + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((or = rn->info) != NULL) { + if (or->type == OSPF_DESTINATION_NETWORK) { + zlog_debug("N %-18pFX %-15pI4 %s %d", &rn->p, + &or->u.std.area_id, + ospf_path_type_str[or->path_type], + or->cost); + for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, + path)) + zlog_debug(" -> %pI4", + &path->nexthop); + } else + zlog_debug("R %-18pI4 %-15pI4 %s %d", + &rn->p.u.prefix4, + &or->u.std.area_id, + ospf_path_type_str[or->path_type], + or->cost); + } + zlog_debug("========================================"); +} + +void ospf_router_route_table_dump(struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route *or; + struct listnode *node; + + zlog_debug("========== OSPF routing table =========="); + for (rn = route_top(rt); rn; rn = route_next(rn)) { + for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) { + assert(or->type == OSPF_DESTINATION_ROUTER); + zlog_debug("R %-18pI4 %-15pI4 %s %d", &rn->p.u.prefix4, + &or->u.std.area_id, + ospf_path_type_str[or->path_type], or->cost); + } + } + zlog_debug("========================================"); +} + +/* This is 16.4.1 implementation. + o Intra-area paths using non-backbone areas are always the most preferred. + o The other paths, intra-area backbone paths and inter-area paths, + are of equal preference. */ +static int ospf_asbr_route_cmp(struct ospf *ospf, struct ospf_route *r1, + struct ospf_route *r2) +{ + uint8_t r1_type, r2_type; + + r1_type = r1->path_type; + r2_type = r2->path_type; + + /* r1/r2 itself is backbone, and it's Inter-area path. */ + if (OSPF_IS_AREA_ID_BACKBONE(r1->u.std.area_id)) + r1_type = OSPF_PATH_INTER_AREA; + if (OSPF_IS_AREA_ID_BACKBONE(r2->u.std.area_id)) + r2_type = OSPF_PATH_INTER_AREA; + + return (r1_type - r2_type); +} + +/* Compare two routes. + ret < 0 -- r1 is better. + ret == 0 -- r1 and r2 are the same. + ret > 0 -- r2 is better. */ +int ospf_route_cmp(struct ospf *ospf, struct ospf_route *r1, + struct ospf_route *r2) +{ + int ret = 0; + + /* Path types of r1 and r2 are not the same. */ + if ((ret = (r1->path_type - r2->path_type))) + return ret; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Route[Compare]: Path types are the same."); + /* Path types are the same, compare any cost. */ + switch (r1->path_type) { + case OSPF_PATH_INTRA_AREA: + case OSPF_PATH_INTER_AREA: + break; + case OSPF_PATH_TYPE1_EXTERNAL: + if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { + ret = ospf_asbr_route_cmp(ospf, r1->u.ext.asbr, + r2->u.ext.asbr); + if (ret != 0) + return ret; + } + break; + case OSPF_PATH_TYPE2_EXTERNAL: + if ((ret = (r1->u.ext.type2_cost - r2->u.ext.type2_cost))) + return ret; + + if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { + ret = ospf_asbr_route_cmp(ospf, r1->u.ext.asbr, + r2->u.ext.asbr); + if (ret != 0) + return ret; + } + break; + } + + /* Anyway, compare the costs. */ + return (r1->cost - r2->cost); +} + +static int ospf_path_exist(struct list *plist, struct in_addr nexthop, + struct ospf_interface *oi) +{ + struct listnode *node, *nnode; + struct ospf_path *path; + + for (ALL_LIST_ELEMENTS(plist, node, nnode, path)) + if (IPV4_ADDR_SAME(&path->nexthop, &nexthop) + && path->ifindex == oi->ifp->ifindex) + return 1; + + return 0; +} + +void ospf_route_copy_nexthops_from_vertex(struct ospf_area *area, + struct ospf_route *to, + struct vertex *v) +{ + struct listnode *node; + struct ospf_path *path; + struct vertex_nexthop *nexthop; + struct vertex_parent *vp; + struct ospf_interface *oi = NULL; + + assert(to->paths); + + for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { + nexthop = vp->nexthop; + + /* + * Only deal with interface data when we + * don't do a dry run + */ + if (!area->spf_dry_run) + oi = ospf_if_lookup_by_lsa_pos(area, nexthop->lsa_pos); + + if ((oi && !ospf_path_exist(to->paths, nexthop->router, oi)) + || area->spf_dry_run) { + path = ospf_path_new(); + path->nexthop = nexthop->router; + path->adv_router = v->lsa->adv_router; + + if (oi) { + path->ifindex = oi->ifp->ifindex; + if (CHECK_FLAG(oi->connected->flags, + ZEBRA_IFA_UNNUMBERED)) + path->unnumbered = 1; + } + + listnode_add(to->paths, path); + } + } +} + +struct ospf_path *ospf_path_lookup(struct list *plist, struct ospf_path *path) +{ + struct listnode *node; + struct ospf_path *op; + + for (ALL_LIST_ELEMENTS_RO(plist, node, op)) { + if (!IPV4_ADDR_SAME(&op->nexthop, &path->nexthop)) + continue; + if (!IPV4_ADDR_SAME(&op->adv_router, &path->adv_router)) + continue; + if (op->ifindex != path->ifindex) + continue; + return op; + } + return NULL; +} + +void ospf_route_copy_nexthops(struct ospf_route *to, struct list *from) +{ + struct listnode *node, *nnode; + struct ospf_path *path; + + assert(to->paths); + + for (ALL_LIST_ELEMENTS(from, node, nnode, path)) + /* The same routes are just discarded. */ + if (!ospf_path_lookup(to->paths, path)) + listnode_add(to->paths, ospf_path_dup(path)); +} + +void ospf_route_subst_nexthops(struct ospf_route *to, struct list *from) +{ + + list_delete_all_node(to->paths); + ospf_route_copy_nexthops(to, from); +} + +void ospf_route_subst(struct route_node *rn, struct ospf_route *new_or, + struct ospf_route *over) +{ + route_lock_node(rn); + ospf_route_free(rn->info); + + ospf_route_copy_nexthops(new_or, over->paths); + rn->info = new_or; + route_unlock_node(rn); +} + +void ospf_route_add(struct route_table *rt, struct prefix_ipv4 *p, + struct ospf_route *new_or, struct ospf_route *over) +{ + struct route_node *rn; + + rn = route_node_get(rt, (struct prefix *)p); + + ospf_route_copy_nexthops(new_or, over->paths); + + if (rn->info) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: something's wrong !", __func__); + route_unlock_node(rn); + return; + } + + rn->info = new_or; +} + +void ospf_prune_unreachable_networks(struct route_table *rt) +{ + struct route_node *rn, *next; + struct ospf_route * or ; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Pruning unreachable networks"); + + for (rn = route_top(rt); rn; rn = next) { + next = route_next(rn); + if (rn->info != NULL) { + or = rn->info; + if (listcount(or->paths) == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Pruning route to %pFX", + &rn->p); + + ospf_route_free(or); + rn->info = NULL; + route_unlock_node(rn); + } + } + } +} + +void ospf_prune_unreachable_routers(struct route_table *rtrs) +{ + struct route_node *rn, *next; + struct ospf_route * or ; + struct listnode *node, *nnode; + struct list *paths; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Pruning unreachable routers"); + + for (rn = route_top(rtrs); rn; rn = next) { + next = route_next(rn); + if ((paths = rn->info) == NULL) + continue; + + for (ALL_LIST_ELEMENTS(paths, node, nnode, or)) { + if (listcount(or->paths) == 0) { + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("Pruning route to rtr %pI4", + &rn->p.u.prefix4); + zlog_debug( + " via area %pI4", + &or->u.std.area_id); + } + + /* Unset the DNA flag on lsa, if the router + * which generated this lsa is no longer + * reachabele. + */ + (CHECK_FLAG(or->u.std.origin->ls_age, + DO_NOT_AGE)) + ? UNSET_FLAG(or->u.std.origin->ls_age, + DO_NOT_AGE) + : 0; + + listnode_delete(paths, or); + ospf_route_free(or); + } + } + + if (listcount(paths) == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Pruning router node %pI4", + &rn->p.u.prefix4); + + list_delete(&paths); + rn->info = NULL; + route_unlock_node(rn); + } + } +} + +int ospf_add_discard_route(struct ospf *ospf, struct route_table *rt, + struct ospf_area *area, struct prefix_ipv4 *p, + bool nssa) +{ + struct route_node *rn; + struct ospf_route * or, *new_or; + + rn = route_node_get(rt, (struct prefix *)p); + + if (rn == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: router installation error", __func__); + return 0; + } + + if (rn->info) /* If the route to the same destination is found */ + { + route_unlock_node(rn); + + or = rn->info; + + if (!nssa && or->path_type == OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: an intra-area route exists", + __func__); + return 0; + } + + if (or->type == OSPF_DESTINATION_DISCARD) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: discard entry already installed", + __func__); + return 0; + } + + ospf_route_free(rn->info); + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: adding %pFX", __func__, p); + + new_or = ospf_route_new(); + new_or->type = OSPF_DESTINATION_DISCARD; + new_or->id.s_addr = INADDR_ANY; + new_or->cost = 0; + new_or->u.std.area_id = area->area_id; + new_or->u.std.external_routing = area->external_routing; + if (nssa) + new_or->path_type = OSPF_PATH_TYPE2_EXTERNAL; + else + new_or->path_type = OSPF_PATH_INTER_AREA; + rn->info = new_or; + + ospf_zebra_add_discard(ospf, p); + + return 1; +} + +void ospf_delete_discard_route(struct ospf *ospf, struct route_table *rt, + struct prefix_ipv4 *p, bool nssa) +{ + struct route_node *rn; + struct ospf_route * or ; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: deleting %pFX", __func__, p); + + rn = route_node_lookup(rt, (struct prefix *)p); + + if (rn == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: no route found", __func__); + return; + } + + or = rn->info; + + if (!nssa && or->path_type == OSPF_PATH_INTRA_AREA) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: an intra-area route exists", __func__); + return; + } + + if (or->type != OSPF_DESTINATION_DISCARD) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: not a discard entry", __func__); + return; + } + + /* free the route entry and the route node */ + ospf_route_free(rn->info); + + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + + /* remove the discard entry from the rib */ + ospf_zebra_delete_discard(ospf, p); + + return; +} diff --git a/ospfd/ospf_route.h b/ospfd/ospf_route.h new file mode 100644 index 0000000..44e8021 --- /dev/null +++ b/ospfd/ospf_route.h @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF routing table. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_ROUTE_H +#define _ZEBRA_OSPF_ROUTE_H + +#define OSPF_DESTINATION_ROUTER 1 +#define OSPF_DESTINATION_NETWORK 2 +#define OSPF_DESTINATION_DISCARD 3 + +#define OSPF_PATH_MIN 0 +#define OSPF_PATH_INTRA_AREA 1 +#define OSPF_PATH_INTER_AREA 2 +#define OSPF_PATH_TYPE1_EXTERNAL 3 +#define OSPF_PATH_TYPE2_EXTERNAL 4 +#define OSPF_PATH_MAX 5 + +/* Segment Routing information to complement ospf_path structure */ +struct sr_nexthop_info { + /* Output label associated to this route */ + mpls_label_t label_out; + /* + * Pointer to SR Node which is the next hop for this route + * or NULL if next hop is the destination of the prefix + */ + struct sr_node *nexthop; + + /* TI-LFA */ + struct mpls_label_stack *backup_label_stack; + struct in_addr backup_nexthop; +}; + +/* OSPF Path. */ +struct ospf_path { + struct in_addr nexthop; + struct in_addr adv_router; + ifindex_t ifindex; + unsigned char unnumbered; + struct sr_nexthop_info srni; +}; + +/* Below is the structure linked to every + route node. Note that for Network routing + entries a single ospf_route is kept, while + for ABRs and ASBRs (Router routing entries), + we link an instance of ospf_router_route + where a list of paths is maintained, so + + nr->info is a (struct ospf_route *) for OSPF_DESTINATION_NETWORK + but + nr->info is a (struct ospf_router_route *) for OSPF_DESTINATION_ROUTER +*/ + +struct route_standard { + /* Link Sate Origin. */ + struct lsa_header *origin; + + /* Associated Area. */ + struct in_addr area_id; /* The area the route belongs to */ + + /* Area Type */ + int external_routing; + + /* Optional Capability. */ + uint8_t options; /* Get from LSA header. */ + + /* */ + uint8_t flags; /* From router-LSA */ + + bool transit; /* Transit network or not */ +}; + +struct route_external { + /* Link State Origin. */ + struct ospf_lsa *origin; + + /* Link State Cost Type2. */ + uint32_t type2_cost; + + /* Tag value. */ + uint32_t tag; + + /* ASBR route. */ + struct ospf_route *asbr; +}; + +struct ospf_route { + /* Destination Type. */ + uint8_t type; + + /* Destination ID. */ /* i.e. Link State ID. */ + struct in_addr id; + + /* Address Mask. */ + struct in_addr mask; /* Only valid for networks. */ + + /* Path Type. */ + uint8_t path_type; + + /* List of Paths. */ + struct list *paths; + + /* Link State Cost. */ + uint32_t cost; /* i.e. metric. */ + + /* Route specific info. */ + union { + struct route_standard std; + struct route_external ext; + } u; + + bool changed; +}; + +extern const char *ospf_path_type_name(int path_type); +extern struct ospf_path *ospf_path_new(void); +extern void ospf_path_free(struct ospf_path *); +extern struct ospf_path *ospf_path_lookup(struct list *, struct ospf_path *); +extern struct ospf_route *ospf_route_new(void); +extern void ospf_route_free(struct ospf_route *); +extern void ospf_route_delete(struct ospf *, struct route_table *); +extern void ospf_route_table_free(struct route_table *); + +extern void ospf_route_install(struct ospf *, struct route_table *); +extern void ospf_route_table_dump(struct route_table *); +extern void ospf_router_route_table_dump(struct route_table *rt); + +extern void ospf_intra_add_router(struct route_table *rt, struct vertex *v, + struct ospf_area *area, bool add_all); + +extern void ospf_intra_add_transit(struct route_table *, struct vertex *, + struct ospf_area *); + +extern void ospf_intra_add_stub(struct route_table *, struct router_lsa_link *, + struct vertex *, struct ospf_area *, + int parent_is_root, int); + +extern int ospf_route_cmp(struct ospf *, struct ospf_route *, + struct ospf_route *); +extern void ospf_route_copy_nexthops(struct ospf_route *, struct list *); +extern void ospf_route_copy_nexthops_from_vertex(struct ospf_area *area, + struct ospf_route *, + struct vertex *); + +extern void ospf_route_subst(struct route_node *, struct ospf_route *, + struct ospf_route *); +extern void ospf_route_add(struct route_table *, struct prefix_ipv4 *, + struct ospf_route *, struct ospf_route *); + +extern void ospf_route_subst_nexthops(struct ospf_route *, struct list *); +extern void ospf_prune_unreachable_networks(struct route_table *); +extern void ospf_prune_unreachable_routers(struct route_table *); +extern int ospf_add_discard_route(struct ospf *, struct route_table *, + struct ospf_area *, struct prefix_ipv4 *, + bool); +extern void ospf_delete_discard_route(struct ospf *, struct route_table *, + struct prefix_ipv4 *, bool); +extern int ospf_route_match_same(struct route_table *, struct prefix_ipv4 *, + struct ospf_route *); + +#endif /* _ZEBRA_OSPF_ROUTE_H */ diff --git a/ospfd/ospf_routemap.c b/ospfd/ospf_routemap.c new file mode 100644 index 0000000..d2f6390 --- /dev/null +++ b/ospfd/ospf_routemap.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Route map function of ospfd. + * Copyright (C) 2000 IP Infusion Inc. + * + * Written by Toshiaki Takada. + */ + +#include + +#include "memory.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "routemap.h" +#include "command.h" +#include "log.h" +#include "plist.h" +#include "vrf.h" +#include "frrstr.h" +#include "northbound_cli.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_errors.h" + +/* Hook function for updating route_map assignment. */ +static void ospf_route_map_update(const char *name) +{ + struct ospf *ospf; + int type; + struct listnode *n1 = NULL; + + /* If OSPF instatnce does not exist, return right now. */ + if (listcount(om->ospf) == 0) + return; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { + /* Update route-map */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct listnode *node; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + if (ROUTEMAP_NAME(red) + && strcmp(ROUTEMAP_NAME(red), name) == 0) { + /* Keep old route-map. */ + struct route_map *old = ROUTEMAP(red); + + ROUTEMAP(red) = + route_map_lookup_by_name( + ROUTEMAP_NAME(red)); + + if (!old) + route_map_counter_increment( + ROUTEMAP(red)); + + /* No update for this distribute type. + */ + if (old == NULL + && ROUTEMAP(red) == NULL) + continue; + + ospf_distribute_list_update( + ospf, type, red->instance); + } + } + } + } +} + +static void ospf_route_map_event(const char *name) +{ + struct ospf *ospf; + int type; + struct listnode *n1 = NULL; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct listnode *node; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + if (ROUTEMAP_NAME(red) && ROUTEMAP(red) + && !strcmp(ROUTEMAP_NAME(red), name)) { + ospf_distribute_list_update( + ospf, type, red->instance); + } + } + } + } +} + +/* `match ip netxthop ' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_ip_nexthop(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + struct external_info *ei = object; + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefix = ei->nexthop; + p.prefixlen = IPV4_MAX_BITLEN; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Route map `ip next-hop' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_nexthop_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_nexthop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_ip_nexthop_cmd = { + "ip next-hop", + route_match_ip_nexthop, + route_match_ip_nexthop_compile, + route_match_ip_nexthop_free +}; + +/* `match ip next-hop prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + struct external_info *ei = object; + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefix = ei->nexthop; + p.prefixlen = IPV4_MAX_BITLEN; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_prefix_list_cmd = { + "ip next-hop prefix-list", + route_match_ip_next_hop_prefix_list, + route_match_ip_next_hop_prefix_list_compile, + route_match_ip_next_hop_prefix_list_free +}; + +/* `match ip next-hop type ' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, + void *object) +{ + struct external_info *ei = object; + + if (prefix->family == AF_INET) { + ei = (struct external_info *)object; + if (!ei) + return RMAP_NOMATCH; + + if (ei->nexthop.s_addr == INADDR_ANY && !ei->ifindex) + return RMAP_MATCH; + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_next_hop_type_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_type_cmd = { + "ip next-hop type", + route_match_ip_next_hop_type, + route_match_ip_next_hop_type_compile, + route_match_ip_next_hop_type_free +}; + +/* `match ip address IP_ACCESS_LIST' */ +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_ip_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + /* struct prefix_ipv4 match; */ + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Access-List Specified: %s does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + return RMAP_NOMATCH; + } + + return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Route map `ip address' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_ip_address_cmd = { + "ip address", + route_match_ip_address, + route_match_ip_address_compile, + route_match_ip_address_free +}; + +/* `match ip address prefix-list PREFIX_LIST' */ +static enum route_map_cmd_result_t +route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) { + if (unlikely(CHECK_FLAG(rmap_debug, DEBUG_ROUTEMAP_DETAIL))) + zlog_debug( + "%s: Prefix List %s specified does not exist defaulting to NO_MATCH", + __func__, (char *)rule); + + return RMAP_NOMATCH; + } + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void *route_match_ip_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_address_prefix_list_cmd = { + "ip address prefix-list", + route_match_ip_address_prefix_list, + route_match_ip_address_prefix_list_compile, + route_match_ip_address_prefix_list_free +}; + +/* `match interface IFNAME' */ +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_interface(void *rule, const struct prefix *prefix, void *object) +{ + struct interface *ifp; + struct external_info *ei; + + ei = object; + ifp = if_lookup_by_name((char *)rule, ei->ospf->vrf_id); + + if (ifp == NULL || ifp->ifindex != ei->ifindex) + return RMAP_NOMATCH; + + return RMAP_MATCH; +} + +/* Route map `interface' match statement. `arg' should be + interface name. */ +static void *route_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `interface' value. */ +static void route_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_interface_cmd = { + "interface", + route_match_interface, + route_match_interface_compile, + route_match_interface_free +}; + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct external_info *ei; + + tag = rule; + ei = object; + + return ((ei->tag == *tag) ? RMAP_MATCH : RMAP_NOMATCH); +} + +/* Route map commands for tag matching. */ +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +struct ospf_metric { + enum { metric_increment, metric_decrement, metric_absolute } type; + bool used; + uint32_t metric; +}; + +/* `set metric METRIC' */ +/* Set metric to attribute. */ +static enum route_map_cmd_result_t +route_set_metric(void *rule, const struct prefix *prefix, void *object) +{ + struct ospf_metric *metric; + struct external_info *ei; + + /* Fetch routemap's rule information. */ + metric = rule; + ei = object; + + /* Set metric out value. */ + if (!metric->used) + return RMAP_OKAY; + + ROUTEMAP_METRIC(ei) = ei->metric; + + if (metric->type == metric_increment) + ROUTEMAP_METRIC(ei) += metric->metric; + else if (metric->type == metric_decrement) + ROUTEMAP_METRIC(ei) -= metric->metric; + else if (metric->type == metric_absolute) + ROUTEMAP_METRIC(ei) = metric->metric; + + if ((uint32_t)ROUTEMAP_METRIC(ei) < ei->min_metric) + ROUTEMAP_METRIC(ei) = ei->min_metric; + if ((uint32_t)ROUTEMAP_METRIC(ei) > ei->max_metric) + ROUTEMAP_METRIC(ei) = ei->max_metric; + + return RMAP_OKAY; +} + +/* set metric compilation. */ +static void *route_set_metric_compile(const char *arg) +{ + struct ospf_metric *metric; + + metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*metric)); + metric->used = false; + + if (all_digit(arg)) + metric->type = metric_absolute; + + if (strmatch(arg, "+rtt") || strmatch(arg, "-rtt")) { + flog_warn(EC_OSPF_SET_METRIC_PLUS, + "OSPF does not support 'set metric +rtt / -rtt'"); + return metric; + } + + if ((arg[0] == '+') && all_digit(arg + 1)) { + metric->type = metric_increment; + arg++; + } + + if ((arg[0] == '-') && all_digit(arg + 1)) { + metric->type = metric_decrement; + arg++; + } + + metric->metric = strtoul(arg, NULL, 10); + + if (metric->metric) + metric->used = true; + + return metric; +} + +/* Free route map's compiled `set metric' value. */ +static void route_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_set_metric_compile, + route_set_metric_free, +}; + +/* `set min-metric METRIC' */ +/* Set min-metric to attribute. */ +static enum route_map_cmd_result_t +route_set_min_metric(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *min_metric; + struct external_info *ei; + + /* Fetch routemap's rule information. */ + min_metric = rule; + ei = object; + + ei->min_metric = *min_metric; + + if (ei->min_metric > OSPF_LS_INFINITY) + ei->min_metric = OSPF_LS_INFINITY; + + if ((uint32_t)ROUTEMAP_METRIC(ei) < ei->min_metric) + ROUTEMAP_METRIC(ei) = ei->min_metric; + + return RMAP_OKAY; +} + +/* set min-metric compilation. */ +static void *route_set_min_metric_compile(const char *arg) +{ + + uint32_t *min_metric; + + min_metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + + *min_metric = strtoul(arg, NULL, 10); + + if (*min_metric) + return min_metric; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, min_metric); + return NULL; +} + +/* Free route map's compiled `set min-metric' value. */ +static void route_set_min_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_min_metric_cmd = { + "min-metric", + route_set_min_metric, + route_set_min_metric_compile, + route_set_min_metric_free, +}; + + +/* `set max-metric METRIC' */ +/* Set max-metric to attribute. */ +static enum route_map_cmd_result_t +route_set_max_metric(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *max_metric; + struct external_info *ei; + + /* Fetch routemap's rule information. */ + max_metric = rule; + ei = object; + + ei->max_metric = *max_metric; + + if (ei->max_metric > OSPF_LS_INFINITY) + ei->max_metric = OSPF_LS_INFINITY; + + if ((uint32_t)ROUTEMAP_METRIC(ei) > ei->max_metric) + ROUTEMAP_METRIC(ei) = ei->max_metric; + + return RMAP_OKAY; +} + +/* set max-metric compilation. */ +static void *route_set_max_metric_compile(const char *arg) +{ + + uint32_t *max_metric; + + max_metric = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + + *max_metric = strtoul(arg, NULL, 10); + + if (*max_metric) + return max_metric; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, max_metric); + return NULL; +} + +/* Free route map's compiled `set max-metric' value. */ +static void route_set_max_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_max_metric_cmd = { + "max-metric", + route_set_max_metric, + route_set_max_metric_compile, + route_set_max_metric_free, +}; + +/* `set metric-type TYPE' */ +/* Set metric-type to attribute. */ +static enum route_map_cmd_result_t +route_set_metric_type(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *metric_type; + struct external_info *ei; + + /* Fetch routemap's rule information. */ + metric_type = rule; + ei = object; + + /* Set metric out value. */ + ROUTEMAP_METRIC_TYPE(ei) = *metric_type; + + return RMAP_OKAY; +} + +/* set metric-type compilation. */ +static void *route_set_metric_type_compile(const char *arg) +{ + uint32_t *metric_type; + + metric_type = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + if (strcmp(arg, "type-1") == 0) + *metric_type = EXTERNAL_METRIC_TYPE_1; + else if (strcmp(arg, "type-2") == 0) + *metric_type = EXTERNAL_METRIC_TYPE_2; + + if (*metric_type == EXTERNAL_METRIC_TYPE_1 + || *metric_type == EXTERNAL_METRIC_TYPE_2) + return metric_type; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, metric_type); + return NULL; +} + +/* Free route map's compiled `set metric-type' value. */ +static void route_set_metric_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_metric_type_cmd = { + "metric-type", + route_set_metric_type, + route_set_metric_type_compile, + route_set_metric_type_free, +}; + +static enum route_map_cmd_result_t +route_set_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct external_info *ei; + + tag = rule; + ei = object; + + /* Set tag value */ + ei->tag = *tag; + + return RMAP_OKAY; +} + +/* Route map commands for tag set. */ +static const struct route_map_rule_cmd route_set_tag_cmd = { + "tag", + route_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +DEFUN_YANG (set_metric_type, + set_metric_type_cmd, + "set metric-type ", + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + char *ext = argv[2]->text; + + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-set-action/frr-ospf-route-map:metric-type", xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, ext); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_set_metric_type, + no_set_metric_type_cmd, + "no set metric-type []", + NO_STR + SET_STR + "Type of metric for destination routing protocol\n" + "OSPF[6] external type 1 metric\n" + "OSPF[6] external type 2 metric\n") +{ + const char *xpath = + "./set-action[action='frr-ospf-route-map:metric-type']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* Route-map init */ +void ospf_route_map_init(void) +{ + route_map_init(); + + route_map_add_hook(ospf_route_map_update); + route_map_delete_hook(ospf_route_map_update); + route_map_event_hook(ospf_route_map_event); + + route_map_match_ip_next_hop_hook(generic_match_add); + route_map_no_match_ip_next_hop_hook(generic_match_delete); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_match_ip_address_hook(generic_match_add); + route_map_no_match_ip_address_hook(generic_match_delete); + + route_map_match_ip_address_prefix_list_hook(generic_match_add); + route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); + route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_type_hook(generic_match_add); + route_map_no_match_ip_next_hop_type_hook(generic_match_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_min_metric_hook(generic_set_add); + route_map_no_set_min_metric_hook(generic_set_delete); + + route_map_set_max_metric_hook(generic_set_add); + route_map_no_set_max_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_install_match(&route_match_ip_nexthop_cmd); + route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); + route_map_install_match(&route_match_ip_address_cmd); + route_map_install_match(&route_match_ip_address_prefix_list_cmd); + route_map_install_match(&route_match_ip_next_hop_type_cmd); + route_map_install_match(&route_match_interface_cmd); + route_map_install_match(&route_match_tag_cmd); + + route_map_install_set(&route_set_metric_cmd); + route_map_install_set(&route_set_min_metric_cmd); + route_map_install_set(&route_set_max_metric_cmd); + route_map_install_set(&route_set_metric_type_cmd); + route_map_install_set(&route_set_tag_cmd); + + install_element(RMAP_NODE, &set_metric_type_cmd); + install_element(RMAP_NODE, &no_set_metric_type_cmd); +} diff --git a/ospfd/ospf_routemap_nb.c b/ospfd/ospf_routemap_nb.c new file mode 100644 index 0000000..8ccd0e5 --- /dev/null +++ b/ospfd/ospf_routemap_nb.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#include + +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "ospf_routemap_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_ospf_route_map_info = { + .name = "frr-ospf-route-map", + .nodes = { + { + .xpath = "/frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf-route-map:metric-type", + .cbs = { + .modify = lib_route_map_entry_set_action_rmap_set_action_metric_type_modify, + .destroy = lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ospfd/ospf_routemap_nb.h b/ospfd/ospf_routemap_nb.h new file mode 100644 index 0000000..5bba784 --- /dev/null +++ b/ospfd/ospf_routemap_nb.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#ifndef _FRR_OSPF_ROUTEMAP_NB_H_ +#define _FRR_OSPF_ROUTEMAP_NB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct frr_yang_module_info frr_ospf_route_map_info; + +/* prototypes */ +int lib_route_map_entry_set_action_rmap_set_action_metric_type_modify(struct nb_cb_modify_args *args); +int lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy(struct nb_cb_destroy_args *args); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospfd/ospf_routemap_nb_config.c b/ospfd/ospf_routemap_nb_config.c new file mode 100644 index 0000000..a35a1ef --- /dev/null +++ b/ospfd/ospf_routemap_nb_config.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Vmware + * Sarita Patra + */ + +#include + +#include "lib/command.h" +#include "lib/log.h" +#include "lib/northbound.h" +#include "lib/routemap.h" +#include "ospf_routemap_nb.h" + +/* + * XPath: + * /frr-route-map:lib/route-map/entry/set-action/rmap-set-action/frr-ospf-route-map:metric-type + */ +int lib_route_map_entry_set_action_rmap_set_action_metric_type_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + const char *type; + int rv; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_shook = generic_set_delete; + rhc->rhc_rule = "metric-type"; + rhc->rhc_event = RMAP_EVENT_SET_DELETED; + + rv = generic_set_add(rhc->rhc_rmi, "metric-type", type, + args->errmsg, args->errmsg_len); + if (rv != CMD_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +int lib_route_map_entry_set_action_rmap_set_action_metric_type_destroy( + struct nb_cb_destroy_args *args) +{ + return lib_route_map_entry_set_destroy(args); +} diff --git a/ospfd/ospf_snmp.c b/ospfd/ospf_snmp.c new file mode 100644 index 0000000..4e1f153 --- /dev/null +++ b/ospfd/ospf_snmp.c @@ -0,0 +1,2552 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPFv2 SNMP support + * Copyright (C) 2005 6WIND + * Copyright (C) 2000 IP Infusion Inc. + * + * Written by Kunihiro Ishiguro + */ + +#include + +#include +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "memory.h" +#include "smux.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" + +DEFINE_MTYPE_STATIC(OSPFD, SNMP, "OSPF SNMP"); + +/* OSPF2-MIB. */ +#define OSPF2MIB 1,3,6,1,2,1,14 + +/* OSPF MIB General Group values. */ +#define OSPFROUTERID 1 +#define OSPFADMINSTAT 2 +#define OSPFVERSIONNUMBER 3 +#define OSPFAREABDRRTRSTATUS 4 +#define OSPFASBDRRTRSTATUS 5 +#define OSPFEXTERNLSACOUNT 6 +#define OSPFEXTERNLSACKSUMSUM 7 +#define OSPFTOSSUPPORT 8 +#define OSPFORIGINATENEWLSAS 9 +#define OSPFRXNEWLSAS 10 +#define OSPFEXTLSDBLIMIT 11 +#define OSPFMULTICASTEXTENSIONS 12 +#define OSPFEXITOVERFLOWINTERVAL 13 +#define OSPFDEMANDEXTENSIONS 14 + +/* OSPF MIB ospfAreaTable. */ +#define OSPFAREAID 1 +#define OSPFAUTHTYPE 2 +#define OSPFIMPORTASEXTERN 3 +#define OSPFSPFRUNS 4 +#define OSPFAREABDRRTRCOUNT 5 +#define OSPFASBDRRTRCOUNT 6 +#define OSPFAREALSACOUNT 7 +#define OSPFAREALSACKSUMSUM 8 +#define OSPFAREASUMMARY 9 +#define OSPFAREASTATUS 10 + +/* OSPF MIB ospfStubAreaTable. */ +#define OSPFSTUBAREAID 1 +#define OSPFSTUBTOS 2 +#define OSPFSTUBMETRIC 3 +#define OSPFSTUBSTATUS 4 +#define OSPFSTUBMETRICTYPE 5 + +/* OSPF MIB ospfLsdbTable. */ +#define OSPFLSDBAREAID 1 +#define OSPFLSDBTYPE 2 +#define OSPFLSDBLSID 3 +#define OSPFLSDBROUTERID 4 +#define OSPFLSDBSEQUENCE 5 +#define OSPFLSDBAGE 6 +#define OSPFLSDBCHECKSUM 7 +#define OSPFLSDBADVERTISEMENT 8 + +/* OSPF MIB ospfAreaRangeTable. */ +#define OSPFAREARANGEAREAID 1 +#define OSPFAREARANGENET 2 +#define OSPFAREARANGEMASK 3 +#define OSPFAREARANGESTATUS 4 +#define OSPFAREARANGEEFFECT 5 + +/* OSPF MIB ospfHostTable. */ +#define OSPFHOSTIPADDRESS 1 +#define OSPFHOSTTOS 2 +#define OSPFHOSTMETRIC 3 +#define OSPFHOSTSTATUS 4 +#define OSPFHOSTAREAID 5 + +/* OSPF MIB ospfIfTable. */ +#define OSPFIFIPADDRESS 1 +#define OSPFADDRESSLESSIF 2 +#define OSPFIFAREAID 3 +#define OSPFIFTYPE 4 +#define OSPFIFADMINSTAT 5 +#define OSPFIFRTRPRIORITY 6 +#define OSPFIFTRANSITDELAY 7 +#define OSPFIFRETRANSINTERVAL 8 +#define OSPFIFHELLOINTERVAL 9 +#define OSPFIFRTRDEADINTERVAL 10 +#define OSPFIFPOLLINTERVAL 11 +#define OSPFIFSTATE 12 +#define OSPFIFDESIGNATEDROUTER 13 +#define OSPFIFBACKUPDESIGNATEDROUTER 14 +#define OSPFIFEVENTS 15 +#define OSPFIFAUTHKEY 16 +#define OSPFIFSTATUS 17 +#define OSPFIFMULTICASTFORWARDING 18 +#define OSPFIFDEMAND 19 +#define OSPFIFAUTHTYPE 20 + +/* OSPF MIB ospfIfMetricTable. */ +#define OSPFIFMETRICIPADDRESS 1 +#define OSPFIFMETRICADDRESSLESSIF 2 +#define OSPFIFMETRICTOS 3 +#define OSPFIFMETRICVALUE 4 +#define OSPFIFMETRICSTATUS 5 + +/* OSPF MIB ospfVirtIfTable. */ +#define OSPFVIRTIFAREAID 1 +#define OSPFVIRTIFNEIGHBOR 2 +#define OSPFVIRTIFTRANSITDELAY 3 +#define OSPFVIRTIFRETRANSINTERVAL 4 +#define OSPFVIRTIFHELLOINTERVAL 5 +#define OSPFVIRTIFRTRDEADINTERVAL 6 +#define OSPFVIRTIFSTATE 7 +#define OSPFVIRTIFEVENTS 8 +#define OSPFVIRTIFAUTHKEY 9 +#define OSPFVIRTIFSTATUS 10 +#define OSPFVIRTIFAUTHTYPE 11 + +/* OSPF MIB ospfNbrTable. */ +#define OSPFNBRIPADDR 1 +#define OSPFNBRADDRESSLESSINDEX 2 +#define OSPFNBRRTRID 3 +#define OSPFNBROPTIONS 4 +#define OSPFNBRPRIORITY 5 +#define OSPFNBRSTATE 6 +#define OSPFNBREVENTS 7 +#define OSPFNBRLSRETRANSQLEN 8 +#define OSPFNBMANBRSTATUS 9 +#define OSPFNBMANBRPERMANENCE 10 +#define OSPFNBRHELLOSUPPRESSED 11 + +/* OSPF MIB ospfVirtNbrTable. */ +#define OSPFVIRTNBRAREA 1 +#define OSPFVIRTNBRRTRID 2 +#define OSPFVIRTNBRIPADDR 3 +#define OSPFVIRTNBROPTIONS 4 +#define OSPFVIRTNBRSTATE 5 +#define OSPFVIRTNBREVENTS 6 +#define OSPFVIRTNBRLSRETRANSQLEN 7 +#define OSPFVIRTNBRHELLOSUPPRESSED 8 + +/* OSPF MIB ospfExtLsdbTable. */ +#define OSPFEXTLSDBTYPE 1 +#define OSPFEXTLSDBLSID 2 +#define OSPFEXTLSDBROUTERID 3 +#define OSPFEXTLSDBSEQUENCE 4 +#define OSPFEXTLSDBAGE 5 +#define OSPFEXTLSDBCHECKSUM 6 +#define OSPFEXTLSDBADVERTISEMENT 7 + +/* OSPF MIB ospfAreaAggregateTable. */ +#define OSPFAREAAGGREGATEAREAID 1 +#define OSPFAREAAGGREGATELSDBTYPE 2 +#define OSPFAREAAGGREGATENET 3 +#define OSPFAREAAGGREGATEMASK 4 +#define OSPFAREAAGGREGATESTATUS 5 +#define OSPFAREAAGGREGATEEFFECT 6 + +/* SYNTAX Status from OSPF-MIB. */ +#define OSPF_STATUS_ENABLED 1 +#define OSPF_STATUS_DISABLED 2 + +/* SNMP value hack. */ +#define COUNTER ASN_COUNTER +#define INTEGER ASN_INTEGER +#define GAUGE ASN_GAUGE +#define TIMETICKS ASN_TIMETICKS +#define IPADDRESS ASN_IPADDRESS +#define STRING ASN_OCTET_STR + +/* Because DR/DROther values are exhanged wrt RFC */ +#define ISM_SNMP(x) \ + (((x) == ISM_DROther) ? ISM_DR : ((x) == ISM_DR) ? ISM_DROther : (x)) + +/* Declare static local variables for convenience. */ +SNMP_LOCAL_VARIABLES + +/* OSPF-MIB instances. */ +static oid ospf_oid[] = {OSPF2MIB}; +static oid ospf_trap_oid[] = {OSPF2MIB, 16, 2}; /* Not reverse mappable! */ + +/* IP address 0.0.0.0. */ +static struct in_addr ospf_empty_addr = {.s_addr = 0}; + +/* Hook functions. */ +static uint8_t *ospfGeneralGroup(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfAreaEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *ospfStubAreaEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfLsdbEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *ospfAreaRangeEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfHostEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *ospfIfEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *ospfIfMetricEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfVirtIfEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfNbrEntry(struct variable *, oid *, size_t *, int, size_t *, + WriteMethod **); +static uint8_t *ospfVirtNbrEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfExtLsdbEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); +static uint8_t *ospfAreaAggregateEntry(struct variable *, oid *, size_t *, int, + size_t *, WriteMethod **); + +static struct variable ospf_variables[] = { + /* OSPF general variables */ + {OSPFROUTERID, IPADDRESS, RWRITE, ospfGeneralGroup, 2, {1, 1}}, + {OSPFADMINSTAT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 2}}, + {OSPFVERSIONNUMBER, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 3}}, + {OSPFAREABDRRTRSTATUS, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 4}}, + {OSPFASBDRRTRSTATUS, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 5}}, + {OSPFEXTERNLSACOUNT, GAUGE, RONLY, ospfGeneralGroup, 2, {1, 6}}, + {OSPFEXTERNLSACKSUMSUM, INTEGER, RONLY, ospfGeneralGroup, 2, {1, 7}}, + {OSPFTOSSUPPORT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 8}}, + {OSPFORIGINATENEWLSAS, COUNTER, RONLY, ospfGeneralGroup, 2, {1, 9}}, + {OSPFRXNEWLSAS, COUNTER, RONLY, ospfGeneralGroup, 2, {1, 10}}, + {OSPFEXTLSDBLIMIT, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 11}}, + {OSPFMULTICASTEXTENSIONS, + INTEGER, + RWRITE, + ospfGeneralGroup, + 2, + {1, 12}}, + {OSPFEXITOVERFLOWINTERVAL, + INTEGER, + RWRITE, + ospfGeneralGroup, + 2, + {1, 13}}, + {OSPFDEMANDEXTENSIONS, INTEGER, RWRITE, ospfGeneralGroup, 2, {1, 14}}, + + /* OSPF area data structure. */ + {OSPFAREAID, IPADDRESS, RONLY, ospfAreaEntry, 3, {2, 1, 1}}, + {OSPFAUTHTYPE, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 2}}, + {OSPFIMPORTASEXTERN, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 3}}, + {OSPFSPFRUNS, COUNTER, RONLY, ospfAreaEntry, 3, {2, 1, 4}}, + {OSPFAREABDRRTRCOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 5}}, + {OSPFASBDRRTRCOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 6}}, + {OSPFAREALSACOUNT, GAUGE, RONLY, ospfAreaEntry, 3, {2, 1, 7}}, + {OSPFAREALSACKSUMSUM, INTEGER, RONLY, ospfAreaEntry, 3, {2, 1, 8}}, + {OSPFAREASUMMARY, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 9}}, + {OSPFAREASTATUS, INTEGER, RWRITE, ospfAreaEntry, 3, {2, 1, 10}}, + + /* OSPF stub area information. */ + {OSPFSTUBAREAID, IPADDRESS, RONLY, ospfStubAreaEntry, 3, {3, 1, 1}}, + {OSPFSTUBTOS, INTEGER, RONLY, ospfStubAreaEntry, 3, {3, 1, 2}}, + {OSPFSTUBMETRIC, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 3}}, + {OSPFSTUBSTATUS, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 4}}, + {OSPFSTUBMETRICTYPE, INTEGER, RWRITE, ospfStubAreaEntry, 3, {3, 1, 5}}, + + /* OSPF link state database. */ + {OSPFLSDBAREAID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 1}}, + {OSPFLSDBTYPE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 2}}, + {OSPFLSDBLSID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 3}}, + {OSPFLSDBROUTERID, IPADDRESS, RONLY, ospfLsdbEntry, 3, {4, 1, 4}}, + {OSPFLSDBSEQUENCE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 5}}, + {OSPFLSDBAGE, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 6}}, + {OSPFLSDBCHECKSUM, INTEGER, RONLY, ospfLsdbEntry, 3, {4, 1, 7}}, + {OSPFLSDBADVERTISEMENT, STRING, RONLY, ospfLsdbEntry, 3, {4, 1, 8}}, + + /* Area range table. */ + {OSPFAREARANGEAREAID, + IPADDRESS, + RONLY, + ospfAreaRangeEntry, + 3, + {5, 1, 1}}, + {OSPFAREARANGENET, IPADDRESS, RONLY, ospfAreaRangeEntry, 3, {5, 1, 2}}, + {OSPFAREARANGEMASK, + IPADDRESS, + RWRITE, + ospfAreaRangeEntry, + 3, + {5, 1, 3}}, + {OSPFAREARANGESTATUS, + INTEGER, + RWRITE, + ospfAreaRangeEntry, + 3, + {5, 1, 4}}, + {OSPFAREARANGEEFFECT, + INTEGER, + RWRITE, + ospfAreaRangeEntry, + 3, + {5, 1, 5}}, + + /* OSPF host table. */ + {OSPFHOSTIPADDRESS, IPADDRESS, RONLY, ospfHostEntry, 3, {6, 1, 1}}, + {OSPFHOSTTOS, INTEGER, RONLY, ospfHostEntry, 3, {6, 1, 2}}, + {OSPFHOSTMETRIC, INTEGER, RWRITE, ospfHostEntry, 3, {6, 1, 3}}, + {OSPFHOSTSTATUS, INTEGER, RWRITE, ospfHostEntry, 3, {6, 1, 4}}, + {OSPFHOSTAREAID, IPADDRESS, RONLY, ospfHostEntry, 3, {6, 1, 5}}, + + /* OSPF interface table. */ + {OSPFIFIPADDRESS, IPADDRESS, RONLY, ospfIfEntry, 3, {7, 1, 1}}, + {OSPFADDRESSLESSIF, INTEGER, RONLY, ospfIfEntry, 3, {7, 1, 2}}, + {OSPFIFAREAID, IPADDRESS, RWRITE, ospfIfEntry, 3, {7, 1, 3}}, + {OSPFIFTYPE, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 4}}, + {OSPFIFADMINSTAT, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 5}}, + {OSPFIFRTRPRIORITY, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 6}}, + {OSPFIFTRANSITDELAY, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 7}}, + {OSPFIFRETRANSINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 8}}, + {OSPFIFHELLOINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 9}}, + {OSPFIFRTRDEADINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 10}}, + {OSPFIFPOLLINTERVAL, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 11}}, + {OSPFIFSTATE, INTEGER, RONLY, ospfIfEntry, 3, {7, 1, 12}}, + {OSPFIFDESIGNATEDROUTER, IPADDRESS, RONLY, ospfIfEntry, 3, {7, 1, 13}}, + {OSPFIFBACKUPDESIGNATEDROUTER, + IPADDRESS, + RONLY, + ospfIfEntry, + 3, + {7, 1, 14}}, + {OSPFIFEVENTS, COUNTER, RONLY, ospfIfEntry, 3, {7, 1, 15}}, + {OSPFIFAUTHKEY, STRING, RWRITE, ospfIfEntry, 3, {7, 1, 16}}, + {OSPFIFSTATUS, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 17}}, + {OSPFIFMULTICASTFORWARDING, + INTEGER, + RWRITE, + ospfIfEntry, + 3, + {7, 1, 18}}, + {OSPFIFDEMAND, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 19}}, + {OSPFIFAUTHTYPE, INTEGER, RWRITE, ospfIfEntry, 3, {7, 1, 20}}, + + /* OSPF interface metric table. */ + {OSPFIFMETRICIPADDRESS, + IPADDRESS, + RONLY, + ospfIfMetricEntry, + 3, + {8, 1, 1}}, + {OSPFIFMETRICADDRESSLESSIF, + INTEGER, + RONLY, + ospfIfMetricEntry, + 3, + {8, 1, 2}}, + {OSPFIFMETRICTOS, INTEGER, RONLY, ospfIfMetricEntry, 3, {8, 1, 3}}, + {OSPFIFMETRICVALUE, INTEGER, RWRITE, ospfIfMetricEntry, 3, {8, 1, 4}}, + {OSPFIFMETRICSTATUS, INTEGER, RWRITE, ospfIfMetricEntry, 3, {8, 1, 5}}, + + /* OSPF virtual interface table. */ + {OSPFVIRTIFAREAID, IPADDRESS, RONLY, ospfVirtIfEntry, 3, {9, 1, 1}}, + {OSPFVIRTIFNEIGHBOR, IPADDRESS, RONLY, ospfVirtIfEntry, 3, {9, 1, 2}}, + {OSPFVIRTIFTRANSITDELAY, + INTEGER, + RWRITE, + ospfVirtIfEntry, + 3, + {9, 1, 3}}, + {OSPFVIRTIFRETRANSINTERVAL, + INTEGER, + RWRITE, + ospfVirtIfEntry, + 3, + {9, 1, 4}}, + {OSPFVIRTIFHELLOINTERVAL, + INTEGER, + RWRITE, + ospfVirtIfEntry, + 3, + {9, 1, 5}}, + {OSPFVIRTIFRTRDEADINTERVAL, + INTEGER, + RWRITE, + ospfVirtIfEntry, + 3, + {9, 1, 6}}, + {OSPFVIRTIFSTATE, INTEGER, RONLY, ospfVirtIfEntry, 3, {9, 1, 7}}, + {OSPFVIRTIFEVENTS, COUNTER, RONLY, ospfVirtIfEntry, 3, {9, 1, 8}}, + {OSPFVIRTIFAUTHKEY, STRING, RWRITE, ospfVirtIfEntry, 3, {9, 1, 9}}, + {OSPFVIRTIFSTATUS, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 10}}, + {OSPFVIRTIFAUTHTYPE, INTEGER, RWRITE, ospfVirtIfEntry, 3, {9, 1, 11}}, + + /* OSPF neighbor table. */ + {OSPFNBRIPADDR, IPADDRESS, RONLY, ospfNbrEntry, 3, {10, 1, 1}}, + {OSPFNBRADDRESSLESSINDEX, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 2}}, + {OSPFNBRRTRID, IPADDRESS, RONLY, ospfNbrEntry, 3, {10, 1, 3}}, + {OSPFNBROPTIONS, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 4}}, + {OSPFNBRPRIORITY, INTEGER, RWRITE, ospfNbrEntry, 3, {10, 1, 5}}, + {OSPFNBRSTATE, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 6}}, + {OSPFNBREVENTS, COUNTER, RONLY, ospfNbrEntry, 3, {10, 1, 7}}, + {OSPFNBRLSRETRANSQLEN, GAUGE, RONLY, ospfNbrEntry, 3, {10, 1, 8}}, + {OSPFNBMANBRSTATUS, INTEGER, RWRITE, ospfNbrEntry, 3, {10, 1, 9}}, + {OSPFNBMANBRPERMANENCE, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 10}}, + {OSPFNBRHELLOSUPPRESSED, INTEGER, RONLY, ospfNbrEntry, 3, {10, 1, 11}}, + + /* OSPF virtual neighbor table. */ + {OSPFVIRTNBRAREA, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 1}}, + {OSPFVIRTNBRRTRID, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 2}}, + {OSPFVIRTNBRIPADDR, IPADDRESS, RONLY, ospfVirtNbrEntry, 3, {11, 1, 3}}, + {OSPFVIRTNBROPTIONS, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 4}}, + {OSPFVIRTNBRSTATE, INTEGER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 5}}, + {OSPFVIRTNBREVENTS, COUNTER, RONLY, ospfVirtNbrEntry, 3, {11, 1, 6}}, + {OSPFVIRTNBRLSRETRANSQLEN, + INTEGER, + RONLY, + ospfVirtNbrEntry, + 3, + {11, 1, 7}}, + {OSPFVIRTNBRHELLOSUPPRESSED, + INTEGER, + RONLY, + ospfVirtNbrEntry, + 3, + {11, 1, 8}}, + + /* OSPF link state database, external. */ + {OSPFEXTLSDBTYPE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 1}}, + {OSPFEXTLSDBLSID, IPADDRESS, RONLY, ospfExtLsdbEntry, 3, {12, 1, 2}}, + {OSPFEXTLSDBROUTERID, + IPADDRESS, + RONLY, + ospfExtLsdbEntry, + 3, + {12, 1, 3}}, + {OSPFEXTLSDBSEQUENCE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 4}}, + {OSPFEXTLSDBAGE, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 5}}, + {OSPFEXTLSDBCHECKSUM, INTEGER, RONLY, ospfExtLsdbEntry, 3, {12, 1, 6}}, + {OSPFEXTLSDBADVERTISEMENT, + STRING, + RONLY, + ospfExtLsdbEntry, + 3, + {12, 1, 7}}, + + /* OSPF area aggregate table. */ + {OSPFAREAAGGREGATEAREAID, + IPADDRESS, + RONLY, + ospfAreaAggregateEntry, + 3, + {14, 1, 1}}, + {OSPFAREAAGGREGATELSDBTYPE, + INTEGER, + RONLY, + ospfAreaAggregateEntry, + 3, + {14, 1, 2}}, + {OSPFAREAAGGREGATENET, + IPADDRESS, + RONLY, + ospfAreaAggregateEntry, + 3, + {14, 1, 3}}, + {OSPFAREAAGGREGATEMASK, + IPADDRESS, + RONLY, + ospfAreaAggregateEntry, + 3, + {14, 1, 4}}, + {OSPFAREAAGGREGATESTATUS, + INTEGER, + RWRITE, + ospfAreaAggregateEntry, + 3, + {14, 1, 5}}, + {OSPFAREAAGGREGATEEFFECT, + INTEGER, + RWRITE, + ospfAreaAggregateEntry, + 3, + {14, 1, 6}}}; + +/* The administrative status of OSPF. When OSPF is enbled on at least + one interface return 1. */ +static int ospf_admin_stat(struct ospf *ospf) +{ + struct listnode *node; + struct ospf_interface *oi; + + if (ospf == NULL) + return 0; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + if (oi && oi->address) + return 1; + + return 0; +} + +static uint8_t *ospfGeneralGroup(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Check whether the instance identifier is valid */ + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFROUTERID: /* 1 */ + /* Router-ID of this OSPF instance. */ + if (ospf) + return SNMP_IPADDRESS(ospf->router_id); + else + return SNMP_IPADDRESS(ospf_empty_addr); + case OSPFADMINSTAT: /* 2 */ + /* The administrative status of OSPF in the router. */ + if (ospf_admin_stat(ospf)) + return SNMP_INTEGER(OSPF_STATUS_ENABLED); + else + return SNMP_INTEGER(OSPF_STATUS_DISABLED); + case OSPFVERSIONNUMBER: /* 3 */ + /* OSPF version 2. */ + return SNMP_INTEGER(OSPF_VERSION); + case OSPFAREABDRRTRSTATUS: /* 4 */ + /* Area Border router status. */ + if (ospf && CHECK_FLAG(ospf->flags, OSPF_FLAG_ABR)) + return SNMP_INTEGER(SNMP_TRUE); + else + return SNMP_INTEGER(SNMP_FALSE); + case OSPFASBDRRTRSTATUS: /* 5 */ + /* AS Border router status. */ + if (ospf && CHECK_FLAG(ospf->flags, OSPF_FLAG_ASBR)) + return SNMP_INTEGER(SNMP_TRUE); + else + return SNMP_INTEGER(SNMP_FALSE); + case OSPFEXTERNLSACOUNT: /* 6 */ + /* External LSA counts. */ + if (ospf) + return SNMP_INTEGER(ospf_lsdb_count_all(ospf->lsdb)); + else + return SNMP_INTEGER(0); + case OSPFEXTERNLSACKSUMSUM: /* 7 */ + /* External LSA checksum. */ + return SNMP_INTEGER(0); + case OSPFTOSSUPPORT: /* 8 */ + /* TOS is not supported. */ + return SNMP_INTEGER(SNMP_FALSE); + case OSPFORIGINATENEWLSAS: /* 9 */ + /* The number of new link-state advertisements. */ + if (ospf) + return SNMP_INTEGER(ospf->lsa_originate_count); + else + return SNMP_INTEGER(0); + case OSPFRXNEWLSAS: /* 10 */ + /* The number of link-state advertisements received determined + to be new instantiations. */ + if (ospf) + return SNMP_INTEGER(ospf->rx_lsa_count); + else + return SNMP_INTEGER(0); + case OSPFEXTLSDBLIMIT: /* 11 */ + /* There is no limit for the number of non-default + AS-external-LSAs. */ + return SNMP_INTEGER(-1); + case OSPFMULTICASTEXTENSIONS: /* 12 */ + /* Multicast Extensions to OSPF is not supported. */ + return SNMP_INTEGER(0); + case OSPFEXITOVERFLOWINTERVAL: /* 13 */ + /* Overflow is not supported. */ + return SNMP_INTEGER(0); + case OSPFDEMANDEXTENSIONS: /* 14 */ + /* Demand routing is not supported. */ + return SNMP_INTEGER(SNMP_FALSE); + default: + return NULL; + } + return NULL; +} + +static struct ospf_area * +ospf_area_lookup_next(struct ospf *ospf, struct in_addr *area_id, int first) +{ + struct ospf_area *area; + struct listnode *node; + + if (ospf == NULL) + return NULL; + + if (first) { + node = listhead(ospf->areas); + if (node) { + area = listgetdata(node); + *area_id = area->area_id; + return area; + } + return NULL; + } + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (ntohl(area->area_id.s_addr) > ntohl(area_id->s_addr)) { + *area_id = area->area_id; + return area; + } + } + return NULL; +} + +static struct ospf_area *ospfAreaLookup(struct variable *v, oid name[], + size_t *length, struct in_addr *addr, + int exact) +{ + struct ospf *ospf; + struct ospf_area *area; + int len; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + if (exact) { + /* Length is insufficient to lookup OSPF area. */ + if (*length - v->namelen != sizeof(struct in_addr)) + return NULL; + + oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); + + area = ospf_area_lookup_by_area_id(ospf, *addr); + + return area; + } else { + len = *length - v->namelen; + if (len > 4) + len = 4; + + oid2in_addr(name + v->namelen, len, addr); + + area = ospf_area_lookup_next(ospf, addr, len == 0 ? 1 : 0); + + if (area == NULL) + return NULL; + + oid_copy_in_addr(name + v->namelen, addr); + *length = sizeof(struct in_addr) + v->namelen; + + return area; + } + return NULL; +} + +static uint8_t *ospfAreaEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_area *area; + struct in_addr addr; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + area = ospfAreaLookup(v, name, length, &addr, exact); + if (!area) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFAREAID: /* 1 */ + return SNMP_IPADDRESS(area->area_id); + case OSPFAUTHTYPE: /* 2 */ + return SNMP_INTEGER(area->auth_type); + case OSPFIMPORTASEXTERN: /* 3 */ + return SNMP_INTEGER(area->external_routing + 1); + case OSPFSPFRUNS: /* 4 */ + return SNMP_INTEGER(area->spf_calculation); + case OSPFAREABDRRTRCOUNT: /* 5 */ + return SNMP_INTEGER(area->abr_count); + case OSPFASBDRRTRCOUNT: /* 6 */ + return SNMP_INTEGER(area->asbr_count); + case OSPFAREALSACOUNT: /* 7 */ + return SNMP_INTEGER(area->lsdb->total); + case OSPFAREALSACKSUMSUM: /* 8 */ + return SNMP_INTEGER(0); + case OSPFAREASUMMARY: /* 9 */ +#define OSPF_noAreaSummary 1 +#define OSPF_sendAreaSummary 2 + if (area->no_summary) + return SNMP_INTEGER(OSPF_noAreaSummary); + else + return SNMP_INTEGER(OSPF_sendAreaSummary); + case OSPFAREASTATUS: /* 10 */ + return SNMP_INTEGER(SNMP_VALID); + default: + return NULL; + } + return NULL; +} + +static struct ospf_area *ospf_stub_area_lookup_next(struct in_addr *area_id, + int first) +{ + struct ospf_area *area; + struct listnode *node; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (area->external_routing == OSPF_AREA_STUB) { + if (first) { + *area_id = area->area_id; + return area; + } else if (ntohl(area->area_id.s_addr) + > ntohl(area_id->s_addr)) { + *area_id = area->area_id; + return area; + } + } + } + return NULL; +} + +static struct ospf_area *ospfStubAreaLookup(struct variable *v, oid name[], + size_t *length, + struct in_addr *addr, int exact) +{ + struct ospf *ospf; + struct ospf_area *area; + int len; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + /* Exact lookup. */ + if (exact) { + /* ospfStubAreaID + ospfStubTOS. */ + if (*length != v->namelen + sizeof(struct in_addr) + 1) + return NULL; + + /* Check ospfStubTOS is zero. */ + if (name[*length - 1] != 0) + return NULL; + + oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); + + area = ospf_area_lookup_by_area_id(ospf, *addr); + + if (area && area->external_routing == OSPF_AREA_STUB) + return area; + else + return NULL; + } else { + len = *length - v->namelen; + if (len > 4) + len = 4; + + oid2in_addr(name + v->namelen, len, addr); + + area = ospf_stub_area_lookup_next(addr, len == 0 ? 1 : 0); + + if (area == NULL) + return NULL; + + oid_copy_in_addr(name + v->namelen, addr); + /* Set TOS 0. */ + name[v->namelen + sizeof(struct in_addr)] = 0; + *length = v->namelen + sizeof(struct in_addr) + 1; + + return area; + } + return NULL; +} + +static uint8_t *ospfStubAreaEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_area *area; + struct in_addr addr; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + area = ospfStubAreaLookup(v, name, length, &addr, exact); + if (!area) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFSTUBAREAID: /* 1 */ + /* OSPF stub area id. */ + return SNMP_IPADDRESS(area->area_id); + case OSPFSTUBTOS: /* 2 */ + /* TOS value is not supported. */ + return SNMP_INTEGER(0); + case OSPFSTUBMETRIC: /* 3 */ + /* Default cost to stub area. */ + return SNMP_INTEGER(area->default_cost); + case OSPFSTUBSTATUS: /* 4 */ + /* Status of the stub area. */ + return SNMP_INTEGER(SNMP_VALID); + case OSPFSTUBMETRICTYPE: /* 5 */ + /* OSPF Metric type. */ +#define OSPF_ospfMetric 1 +#define OSPF_comparableCost 2 +#define OSPF_nonComparable 3 + return SNMP_INTEGER(OSPF_ospfMetric); + default: + return NULL; + } + return NULL; +} + +static struct ospf_lsa *lsdb_lookup_next(struct ospf_area *area, uint8_t *type, + int type_next, struct in_addr *ls_id, + int ls_id_next, + struct in_addr *router_id, + int router_id_next) +{ + struct ospf_lsa *lsa; + int i; + + if (type_next) + i = OSPF_MIN_LSA; + else + i = *type; + + /* Sanity check, if LSA type unknwon + merley skip any LSA */ + if ((i < OSPF_MIN_LSA) || (i >= OSPF_MAX_LSA)) { + zlog_debug("Strange request with LSA type %d", i); + return NULL; + } + + for (; i < OSPF_MAX_LSA; i++) { + *type = i; + + lsa = ospf_lsdb_lookup_by_id_next(area->lsdb, *type, *ls_id, + *router_id, ls_id_next); + if (lsa) + return lsa; + + ls_id_next = 1; + } + return NULL; +} + +static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, + size_t *length, struct in_addr *area_id, + uint8_t *type, struct in_addr *ls_id, + struct in_addr *router_id, int exact) +{ + struct ospf *ospf; + struct ospf_area *area; + struct ospf_lsa *lsa; + int len; + int type_next; + int ls_id_next; + int router_id_next; + oid *offset; + int offsetlen; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + +#define OSPF_LSDB_ENTRY_OFFSET (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE) + + if (exact) { + /* Area ID + Type + LS ID + Router ID. */ + if (*length - v->namelen != OSPF_LSDB_ENTRY_OFFSET) + return NULL; + + /* Set OID offset for Area ID. */ + offset = name + v->namelen; + + /* Lookup area first. */ + oid2in_addr(offset, IN_ADDR_SIZE, area_id); + area = ospf_area_lookup_by_area_id(ospf, *area_id); + if (!area) + return NULL; + offset += IN_ADDR_SIZE; + + /* Type. */ + *type = *offset; + offset++; + + /* LS ID. */ + oid2in_addr(offset, IN_ADDR_SIZE, ls_id); + offset += IN_ADDR_SIZE; + + /* Router ID. */ + oid2in_addr(offset, IN_ADDR_SIZE, router_id); + + /* Lookup LSDB. */ + return ospf_lsdb_lookup_by_id(area->lsdb, *type, *ls_id, + *router_id); + } else { + /* Get variable length. */ + offset = name + v->namelen; + offsetlen = *length - v->namelen; + len = offsetlen; + + if (len > (int)IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, area_id); + + /* First we search area. */ + if (len == IN_ADDR_SIZE) + area = ospf_area_lookup_by_area_id(ospf, *area_id); + else + area = ospf_area_lookup_next(ospf, area_id, 1); + + if (area == NULL) + return NULL; + + do { + /* Next we lookup type. */ + offset += len; + offsetlen -= len; + len = offsetlen; + + if (len <= 0) + type_next = 1; + else { + type_next = 0; + *type = *offset; + } + + /* LS ID. */ + offset++; + offsetlen--; + len = offsetlen; + + if (len <= 0) + ls_id_next = 1; + else { + ls_id_next = 0; + if (len > (int)IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, ls_id); + } + + /* Router ID. */ + offset += IN_ADDR_SIZE; + offsetlen -= IN_ADDR_SIZE; + len = offsetlen; + + if (len <= 0) + router_id_next = 1; + else { + router_id_next = 0; + if (len > (int)IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, router_id); + } + + lsa = lsdb_lookup_next(area, type, type_next, ls_id, + ls_id_next, router_id, + router_id_next); + + if (lsa) { + /* Fill in length. */ + *length = v->namelen + OSPF_LSDB_ENTRY_OFFSET; + + /* Fill in value. */ + offset = name + v->namelen; + oid_copy_in_addr(offset, area_id); + offset += IN_ADDR_SIZE; + *offset = lsa->data->type; + offset++; + oid_copy_in_addr(offset, &lsa->data->id); + offset += IN_ADDR_SIZE; + oid_copy_in_addr(offset, + &lsa->data->adv_router); + + return lsa; + } + } while ((area = ospf_area_lookup_next(ospf, area_id, 0)) + != NULL); + } + return NULL; +} + +static uint8_t *ospfLsdbEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_lsa *lsa; + struct lsa_header *lsah; + struct in_addr area_id; + uint8_t type; + struct in_addr ls_id; + struct in_addr router_id; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* INDEX { ospfLsdbAreaId, ospfLsdbType, + ospfLsdbLsid, ospfLsdbRouterId } */ + + memset(&area_id, 0, sizeof(area_id)); + type = 0; + memset(&ls_id, 0, sizeof(ls_id)); + memset(&router_id, 0, sizeof(router_id)); + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + lsa = ospfLsdbLookup(v, name, length, &area_id, &type, &ls_id, + &router_id, exact); + if (!lsa) + return NULL; + + lsah = lsa->data; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFLSDBAREAID: /* 1 */ + return SNMP_IPADDRESS(lsa->area->area_id); + case OSPFLSDBTYPE: /* 2 */ + return SNMP_INTEGER(lsah->type); + case OSPFLSDBLSID: /* 3 */ + return SNMP_IPADDRESS(lsah->id); + case OSPFLSDBROUTERID: /* 4 */ + return SNMP_IPADDRESS(lsah->adv_router); + case OSPFLSDBSEQUENCE: /* 5 */ + return SNMP_INTEGER(lsah->ls_seqnum); + case OSPFLSDBAGE: /* 6 */ + return SNMP_INTEGER(lsah->ls_age); + case OSPFLSDBCHECKSUM: /* 7 */ + return SNMP_INTEGER(lsah->checksum); + case OSPFLSDBADVERTISEMENT: /* 8 */ + *var_len = ntohs(lsah->length); + return (uint8_t *)lsah; + default: + return NULL; + } + return NULL; +} + +static struct ospf_area_range *ospfAreaRangeLookup(struct variable *v, + oid *name, size_t *length, + struct in_addr *area_id, + struct in_addr *range_net, + int exact) +{ + oid *offset; + int offsetlen; + int len; + struct ospf *ospf; + struct ospf_area *area; + struct ospf_area_range *range; + struct prefix_ipv4 p; + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if (exact) { + /* Area ID + Range Network. */ + if (v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE != *length) + return NULL; + + /* Set OID offset for Area ID. */ + offset = name + v->namelen; + + /* Lookup area first. */ + oid2in_addr(offset, IN_ADDR_SIZE, area_id); + + area = ospf_area_lookup_by_area_id(ospf, *area_id); + if (!area) + return NULL; + + offset += IN_ADDR_SIZE; + + /* Lookup area range. */ + oid2in_addr(offset, IN_ADDR_SIZE, range_net); + p.prefix = *range_net; + + return ospf_area_range_lookup(area, area->ranges, &p); + } else { + /* Set OID offset for Area ID. */ + offset = name + v->namelen; + offsetlen = *length - v->namelen; + + len = offsetlen; + if (len > (int)IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, area_id); + + /* First we search area. */ + if (len == IN_ADDR_SIZE) + area = ospf_area_lookup_by_area_id(ospf, *area_id); + else + area = ospf_area_lookup_next(ospf, area_id, + len == 0 ? 1 : 0); + + if (area == NULL) + return NULL; + + do { + offset += IN_ADDR_SIZE; + offsetlen -= IN_ADDR_SIZE; + len = offsetlen; + + if (len < 0) + len = 0; + if (len > (int)IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, range_net); + + range = ospf_area_range_lookup_next(area, range_net, + len == 0 ? 1 : 0); + + if (range) { + /* Fill in length. */ + *length = v->namelen + IN_ADDR_SIZE + + IN_ADDR_SIZE; + + /* Fill in value. */ + offset = name + v->namelen; + oid_copy_in_addr(offset, area_id); + offset += IN_ADDR_SIZE; + oid_copy_in_addr(offset, range_net); + + return range; + } + } while ((area = ospf_area_lookup_next(ospf, area_id, 0)) + != NULL); + } + return NULL; +} + +static uint8_t *ospfAreaRangeEntry(struct variable *v, oid *name, + size_t *length, int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_area_range *range; + struct in_addr area_id; + struct in_addr range_net; + struct in_addr mask; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + memset(&area_id, 0, IN_ADDR_SIZE); + memset(&range_net, 0, IN_ADDR_SIZE); + + range = ospfAreaRangeLookup(v, name, length, &area_id, &range_net, + exact); + if (!range) + return NULL; + + /* Convert prefixlen to network mask format. */ + masklen2ip(range->subst_masklen, &mask); + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFAREARANGEAREAID: /* 1 */ + return SNMP_IPADDRESS(area_id); + case OSPFAREARANGENET: /* 2 */ + return SNMP_IPADDRESS(range_net); + case OSPFAREARANGEMASK: /* 3 */ + return SNMP_IPADDRESS(mask); + case OSPFAREARANGESTATUS: /* 4 */ + return SNMP_INTEGER(SNMP_VALID); + case OSPFAREARANGEEFFECT: /* 5 */ +#define OSPF_advertiseMatching 1 +#define OSPF_doNotAdvertiseMatching 2 + return SNMP_INTEGER(OSPF_advertiseMatching); + default: + return NULL; + } + return NULL; +} + +static struct ospf_nbr_nbma *ospfHostLookup(struct variable *v, oid *name, + size_t *length, + struct in_addr *addr, int exact) +{ + struct ospf_nbr_nbma *nbr_nbma; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + if (exact) { + /* INDEX { ospfHostIpAddress, ospfHostTOS } */ + if (*length != v->namelen + IN_ADDR_SIZE + 1) + return NULL; + + /* Check ospfHostTOS. */ + if (name[*length - 1] != 0) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, addr); + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, *addr); + + return nbr_nbma; + } + + return NULL; +} + +static uint8_t *ospfHostEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_nbr_nbma *nbr_nbma; + struct ospf_interface *oi; + struct in_addr addr; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + nbr_nbma = ospfHostLookup(v, name, length, &addr, exact); + if (nbr_nbma == NULL) + return NULL; + + oi = nbr_nbma->oi; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFHOSTIPADDRESS: /* 1 */ + return SNMP_IPADDRESS(nbr_nbma->addr); + case OSPFHOSTTOS: /* 2 */ + return SNMP_INTEGER(0); + case OSPFHOSTMETRIC: /* 3 */ + if (oi) + return SNMP_INTEGER(oi->output_cost); + else + return SNMP_INTEGER(1); + case OSPFHOSTSTATUS: /* 4 */ + return SNMP_INTEGER(SNMP_VALID); + case OSPFHOSTAREAID: /* 5 */ + if (oi && oi->area) + return SNMP_IPADDRESS(oi->area->area_id); + else + return SNMP_IPADDRESS(ospf_empty_addr); + default: + return NULL; + } + return NULL; +} + +static struct list *ospf_snmp_iflist; + +struct ospf_snmp_if { + struct in_addr addr; + ifindex_t ifindex; + struct interface *ifp; +}; + +static struct ospf_snmp_if *ospf_snmp_if_new(void) +{ + return XCALLOC(MTYPE_SNMP, sizeof(struct ospf_snmp_if)); +} + +static void ospf_snmp_if_free(struct ospf_snmp_if *osif) +{ + XFREE(MTYPE_SNMP, osif); +} + +static int ospf_snmp_if_delete(struct interface *ifp) +{ + struct listnode *node, *nnode; + struct ospf_snmp_if *osif; + + for (ALL_LIST_ELEMENTS(ospf_snmp_iflist, node, nnode, osif)) { + if (osif->ifp == ifp) { + list_delete_node(ospf_snmp_iflist, node); + ospf_snmp_if_free(osif); + break; + } + } + return 0; +} + +static int ospf_snmp_if_update(struct interface *ifp) +{ + struct listnode *node; + struct listnode *pn; + struct connected *ifc; + struct prefix *p; + struct ospf_snmp_if *osif; + struct in_addr *addr; + ifindex_t ifindex; + + ospf_snmp_if_delete(ifp); + + p = NULL; + addr = NULL; + ifindex = 0; + + /* Lookup first IPv4 address entry. */ + frr_each (if_connected, ifp->connected, ifc) { + p = CONNECTED_ID(ifc); + + if (p->family == AF_INET) { + addr = &p->u.prefix4; + break; + } + } + if (!addr) + ifindex = ifp->ifindex; + + /* Add interface to the list. */ + pn = NULL; + for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, node, osif)) { + if (addr) { + /* Usual interfaces --> Sort them based on interface + * IPv4 addresses */ + if (ntohl(osif->addr.s_addr) > ntohl(addr->s_addr)) + break; + } else { + /* Unnumbered interfaces --> Sort them based on + * interface indexes */ + if (osif->addr.s_addr != INADDR_ANY + || osif->ifindex > ifindex) + break; + } + pn = node; + } + + osif = ospf_snmp_if_new(); + if (addr) /* Usual interface */ + { + osif->addr = *addr; + + /* This field is used for storing ospfAddressLessIf OID value, + * conform to RFC1850 OSPF-MIB specification, it must be 0 for + * usual interface */ + osif->ifindex = 0; + } else /* Unnumbered interface */ + osif->ifindex = ifindex; + osif->ifp = ifp; + + listnode_add_after(ospf_snmp_iflist, pn, osif); + return 0; +} + +static int ospf_snmp_is_if_have_addr(struct interface *ifp) +{ + struct connected *ifc; + + /* Is this interface having any connected IPv4 address ? */ + frr_each (if_connected, ifp->connected, ifc) { + if (CONNECTED_PREFIX(ifc)->family == AF_INET) + return 1; + } + + return 0; +} + +static struct ospf_interface *ospf_snmp_if_lookup(struct in_addr *ifaddr, + ifindex_t *ifindex) +{ + struct listnode *node; + struct ospf_snmp_if *osif; + struct ospf_interface *oi = NULL; + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, node, osif)) { + if (ifaddr->s_addr) { + if (IPV4_ADDR_SAME(&osif->addr, ifaddr)) + oi = ospf_if_lookup_by_local_addr( + ospf, osif->ifp, *ifaddr); + } else { + if (osif->ifindex == *ifindex) + oi = ospf_if_lookup_by_local_addr( + ospf, osif->ifp, *ifaddr); + } + } + return oi; +} + +static struct ospf_interface *ospf_snmp_if_lookup_next(struct in_addr *ifaddr, + ifindex_t *ifindex, + int ifaddr_next, + ifindex_t ifindex_next) +{ + struct ospf_snmp_if *osif; + struct listnode *nn; + struct ospf *ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct ospf_interface *oi = NULL; + + if (ospf == NULL) + return NULL; + + /* No instance is specified --> Return the first OSPF interface */ + if (ifaddr_next) { + for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, nn, osif)) { + osif = listgetdata(nn); + *ifaddr = osif->addr; + *ifindex = osif->ifindex; + /* Because no instance is specified, we don't care about + * the kind of + * interface (usual or unnumbered), just returning the + * first valid + * OSPF interface */ + oi = ospf_if_lookup_by_local_addr(ospf, osif->ifp, + *ifaddr); + if (oi) + return (oi); + } + return NULL; + } + + /* An instance is specified --> Return the next OSPF interface */ + for (ALL_LIST_ELEMENTS_RO(ospf_snmp_iflist, nn, osif)) { + /* Usual interface */ + if (ifaddr->s_addr) { + /* The interface must have valid AF_INET connected + * address */ + /* it must have lager IPv4 address value than the lookup + * entry */ + if ((ospf_snmp_is_if_have_addr(osif->ifp)) + && (ntohl(osif->addr.s_addr) + > ntohl(ifaddr->s_addr))) { + *ifaddr = osif->addr; + *ifindex = osif->ifindex; + + /* and it must be an OSPF interface */ + oi = ospf_if_lookup_by_local_addr( + ospf, osif->ifp, *ifaddr); + if (oi) + return oi; + } + } + /* Unnumbered interface */ + else + /* The interface must NOT have valid AF_INET connected + address */ + /* it must have lager interface index than the lookup + entry */ + if ((!ospf_snmp_is_if_have_addr(osif->ifp)) + && (osif->ifindex > *ifindex)) { + *ifaddr = osif->addr; + *ifindex = osif->ifindex; + + /* and it must be an OSPF interface */ + oi = ospf_if_lookup_by_local_addr(ospf, osif->ifp, + *ifaddr); + if (oi) + return oi; + } + } + return NULL; +} + +static int ospf_snmp_iftype(struct interface *ifp) +{ +#define ospf_snmp_iftype_broadcast 1 +#define ospf_snmp_iftype_nbma 2 +#define ospf_snmp_iftype_pointToPoint 3 +#define ospf_snmp_iftype_pointToMultipoint 5 + if (if_is_broadcast(ifp)) + return ospf_snmp_iftype_broadcast; + if (if_is_pointopoint(ifp)) + return ospf_snmp_iftype_pointToPoint; + return ospf_snmp_iftype_broadcast; +} + +static struct ospf_interface *ospfIfLookup(struct variable *v, oid *name, + size_t *length, + struct in_addr *ifaddr, + ifindex_t *ifindex, int exact) +{ + unsigned int len; + int ifaddr_next = 0; + ifindex_t ifindex_next = 0; + struct ospf_interface *oi; + oid *offset; + + if (exact) { + if (*length != v->namelen + IN_ADDR_SIZE + 1) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, ifaddr); + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + return ospf_snmp_if_lookup(ifaddr, ifindex); + } else { + len = *length - v->namelen; + if (len >= IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + if (len == 0) + ifaddr_next = 1; + + oid2in_addr(name + v->namelen, len, ifaddr); + + len = *length - v->namelen - IN_ADDR_SIZE; + if (len >= 1) + len = 1; + else + ifindex_next = 1; + + if (len == 1) + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + oi = ospf_snmp_if_lookup_next(ifaddr, ifindex, ifaddr_next, + ifindex_next); + if (oi) { + *length = v->namelen + IN_ADDR_SIZE + 1; + offset = name + v->namelen; + oid_copy_in_addr(offset, ifaddr); + offset += IN_ADDR_SIZE; + *offset = *ifindex; + return oi; + } + } + return NULL; +} + +static uint8_t *ospfIfEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + ifindex_t ifindex; + struct in_addr ifaddr; + struct ospf_interface *oi; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + ifindex = 0; + memset(&ifaddr, 0, sizeof(ifaddr)); + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + oi = ospfIfLookup(v, name, length, &ifaddr, &ifindex, exact); + if (oi == NULL) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFIFIPADDRESS: /* 1 */ + return SNMP_IPADDRESS(ifaddr); + case OSPFADDRESSLESSIF: /* 2 */ + return SNMP_INTEGER(ifindex); + case OSPFIFAREAID: /* 3 */ + if (oi->area) + return SNMP_IPADDRESS(oi->area->area_id); + else + return SNMP_IPADDRESS(ospf_empty_addr); + case OSPFIFTYPE: /* 4 */ + return SNMP_INTEGER(ospf_snmp_iftype(oi->ifp)); + case OSPFIFADMINSTAT: /* 5 */ + if (oi) + return SNMP_INTEGER(OSPF_STATUS_ENABLED); + else + return SNMP_INTEGER(OSPF_STATUS_DISABLED); + case OSPFIFRTRPRIORITY: /* 6 */ + return SNMP_INTEGER(PRIORITY(oi)); + case OSPFIFTRANSITDELAY: /* 7 */ + return SNMP_INTEGER(OSPF_IF_PARAM(oi, transmit_delay)); + case OSPFIFRETRANSINTERVAL: /* 8 */ + return SNMP_INTEGER(OSPF_IF_PARAM(oi, retransmit_interval)); + case OSPFIFHELLOINTERVAL: /* 9 */ + return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_hello)); + case OSPFIFRTRDEADINTERVAL: /* 10 */ + return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_wait)); + case OSPFIFPOLLINTERVAL: /* 11 */ + return SNMP_INTEGER(OSPF_POLL_INTERVAL_DEFAULT); + case OSPFIFSTATE: /* 12 */ + return SNMP_INTEGER(ISM_SNMP(oi->state)); + case OSPFIFDESIGNATEDROUTER: /* 13 */ + return SNMP_IPADDRESS(DR(oi)); + case OSPFIFBACKUPDESIGNATEDROUTER: /* 14 */ + return SNMP_IPADDRESS(BDR(oi)); + case OSPFIFEVENTS: /* 15 */ + return SNMP_INTEGER(oi->state_change); + case OSPFIFAUTHKEY: /* 16 */ + *var_len = 0; + return (uint8_t *)OSPF_IF_PARAM(oi, auth_simple); + case OSPFIFSTATUS: /* 17 */ + return SNMP_INTEGER(SNMP_VALID); + case OSPFIFMULTICASTFORWARDING: /* 18 */ +#define ospf_snmp_multiforward_blocked 1 +#define ospf_snmp_multiforward_multicast 2 +#define ospf_snmp_multiforward_unicast 3 + return SNMP_INTEGER(ospf_snmp_multiforward_blocked); + case OSPFIFDEMAND: /* 19 */ + return SNMP_INTEGER(SNMP_FALSE); + case OSPFIFAUTHTYPE: /* 20 */ + if (oi->area) + return SNMP_INTEGER(oi->area->auth_type); + else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +#define OSPF_SNMP_METRIC_VALUE 1 + +static struct ospf_interface *ospfIfMetricLookup(struct variable *v, oid *name, + size_t *length, + struct in_addr *ifaddr, + ifindex_t *ifindex, int exact) +{ + unsigned int len; + int ifaddr_next = 0; + ifindex_t ifindex_next = 0; + struct ospf_interface *oi; + oid *offset; + int metric; + + if (exact) { + if (*length != v->namelen + IN_ADDR_SIZE + 1 + 1) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, ifaddr); + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + metric = name[v->namelen + IN_ADDR_SIZE + 1]; + + if (metric != OSPF_SNMP_METRIC_VALUE) + return NULL; + + return ospf_snmp_if_lookup(ifaddr, ifindex); + } else { + len = *length - v->namelen; + if (len >= IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + else + ifaddr_next = 1; + + oid2in_addr(name + v->namelen, len, ifaddr); + + len = *length - v->namelen - IN_ADDR_SIZE; + if (len >= 1) + len = 1; + else + ifindex_next = 1; + + if (len == 1) + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + oi = ospf_snmp_if_lookup_next(ifaddr, ifindex, ifaddr_next, + ifindex_next); + if (oi) { + *length = v->namelen + IN_ADDR_SIZE + 1 + 1; + offset = name + v->namelen; + oid_copy_in_addr(offset, ifaddr); + offset += IN_ADDR_SIZE; + *offset = *ifindex; + offset++; + *offset = OSPF_SNMP_METRIC_VALUE; + return oi; + } + } + return NULL; +} + +static uint8_t *ospfIfMetricEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + /* Currently we support metric 1 only. */ + ifindex_t ifindex; + struct in_addr ifaddr; + struct ospf_interface *oi; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + ifindex = 0; + memset(&ifaddr, 0, sizeof(ifaddr)); + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + oi = ospfIfMetricLookup(v, name, length, &ifaddr, &ifindex, exact); + if (oi == NULL) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFIFMETRICIPADDRESS: + return SNMP_IPADDRESS(ifaddr); + case OSPFIFMETRICADDRESSLESSIF: + return SNMP_INTEGER(ifindex); + case OSPFIFMETRICTOS: + return SNMP_INTEGER(0); + case OSPFIFMETRICVALUE: + return SNMP_INTEGER(OSPF_SNMP_METRIC_VALUE); + case OSPFIFMETRICSTATUS: + return SNMP_INTEGER(1); + default: + return NULL; + } + return NULL; +} + +static struct route_table *ospf_snmp_vl_table; + +static int ospf_snmp_vl_add(struct ospf_vl_data *vl_data) +{ + struct prefix_ls lp; + struct route_node *rn; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = vl_data->vl_area_id; + lp.adv_router = vl_data->vl_peer; + + rn = route_node_get(ospf_snmp_vl_table, (struct prefix *)&lp); + if (rn->info) + route_unlock_node(rn); + + rn->info = vl_data; + return 0; +} + +static int ospf_snmp_vl_delete(struct ospf_vl_data *vl_data) +{ + struct prefix_ls lp; + struct route_node *rn; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = vl_data->vl_area_id; + lp.adv_router = vl_data->vl_peer; + + rn = route_node_lookup(ospf_snmp_vl_table, (struct prefix *)&lp); + if (!rn) + return 0; + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + return 0; +} + +static struct ospf_vl_data *ospf_snmp_vl_lookup(struct in_addr *area_id, + struct in_addr *neighbor) +{ + struct prefix_ls lp; + struct route_node *rn; + struct ospf_vl_data *vl_data; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = *area_id; + lp.adv_router = *neighbor; + + rn = route_node_lookup(ospf_snmp_vl_table, (struct prefix *)&lp); + if (rn) { + vl_data = rn->info; + route_unlock_node(rn); + return vl_data; + } + return NULL; +} + +static struct ospf_vl_data *ospf_snmp_vl_lookup_next(struct in_addr *area_id, + struct in_addr *neighbor, + int first) +{ + struct prefix_ls lp; + struct route_node *rn; + struct ospf_vl_data *vl_data; + + memset(&lp, 0, sizeof(lp)); + lp.family = AF_UNSPEC; + lp.prefixlen = 64; + lp.id = *area_id; + lp.adv_router = *neighbor; + + if (first) + rn = route_top(ospf_snmp_vl_table); + else { + rn = route_node_get(ospf_snmp_vl_table, (struct prefix *)&lp); + rn = route_next(rn); + } + + for (; rn; rn = route_next(rn)) + if (rn->info) + break; + + if (rn && rn->info) { + vl_data = rn->info; + *area_id = vl_data->vl_area_id; + *neighbor = vl_data->vl_peer; + route_unlock_node(rn); + return vl_data; + } + return NULL; +} + +static struct ospf_vl_data * +ospfVirtIfLookup(struct variable *v, oid *name, size_t *length, + struct in_addr *area_id, struct in_addr *neighbor, int exact) +{ + int first; + unsigned int len; + struct ospf_vl_data *vl_data; + + if (exact) { + if (*length != v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, area_id); + oid2in_addr(name + v->namelen + IN_ADDR_SIZE, IN_ADDR_SIZE, + neighbor); + + return ospf_snmp_vl_lookup(area_id, neighbor); + } else { + first = 0; + + len = *length - v->namelen; + if (len == 0) + first = 1; + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + oid2in_addr(name + v->namelen, len, area_id); + + len = *length - v->namelen - IN_ADDR_SIZE; + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + oid2in_addr(name + v->namelen + IN_ADDR_SIZE, len, neighbor); + + vl_data = ospf_snmp_vl_lookup_next(area_id, neighbor, first); + + if (vl_data) { + *length = v->namelen + IN_ADDR_SIZE + IN_ADDR_SIZE; + oid_copy_in_addr(name + v->namelen, area_id); + oid_copy_in_addr(name + v->namelen + IN_ADDR_SIZE, + neighbor); + return vl_data; + } + } + return NULL; +} + +static uint8_t *ospfVirtIfEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_vl_data *vl_data; + struct ospf_interface *oi; + struct in_addr area_id; + struct in_addr neighbor; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&area_id, 0, sizeof(area_id)); + memset(&neighbor, 0, sizeof(neighbor)); + + vl_data = ospfVirtIfLookup(v, name, length, &area_id, &neighbor, exact); + if (!vl_data) + return NULL; + oi = vl_data->vl_oi; + if (!oi) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFVIRTIFAREAID: + return SNMP_IPADDRESS(area_id); + case OSPFVIRTIFNEIGHBOR: + return SNMP_IPADDRESS(neighbor); + case OSPFVIRTIFTRANSITDELAY: + return SNMP_INTEGER(OSPF_IF_PARAM(oi, transmit_delay)); + case OSPFVIRTIFRETRANSINTERVAL: + return SNMP_INTEGER(OSPF_IF_PARAM(oi, retransmit_interval)); + case OSPFVIRTIFHELLOINTERVAL: + return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_hello)); + case OSPFVIRTIFRTRDEADINTERVAL: + return SNMP_INTEGER(OSPF_IF_PARAM(oi, v_wait)); + case OSPFVIRTIFSTATE: + return SNMP_INTEGER(oi->state); + case OSPFVIRTIFEVENTS: + return SNMP_INTEGER(oi->state_change); + case OSPFVIRTIFAUTHKEY: + *var_len = 0; + return (uint8_t *)OSPF_IF_PARAM(oi, auth_simple); + case OSPFVIRTIFSTATUS: + return SNMP_INTEGER(SNMP_VALID); + case OSPFVIRTIFAUTHTYPE: + if (oi->area) + return SNMP_INTEGER(oi->area->auth_type); + else + return SNMP_INTEGER(0); + default: + return NULL; + } + return NULL; +} + +static struct ospf_neighbor *ospf_snmp_nbr_lookup(struct ospf *ospf, + struct in_addr *nbr_addr, + ifindex_t *ifindex) +{ + struct listnode *node, *nnode; + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct route_node *rn; + + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) { + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL + && nbr != oi->nbr_self + /* If EXACT match is needed, provide ALL entry found + && nbr->state != NSM_Down + */ + && nbr->src.s_addr != INADDR_ANY) { + if (IPV4_ADDR_SAME(&nbr->src, nbr_addr)) { + route_unlock_node(rn); + return nbr; + } + } + } + return NULL; +} + +static struct ospf_neighbor *ospf_snmp_nbr_lookup_next(struct in_addr *nbr_addr, + ifindex_t *ifindex, + int first) +{ + struct listnode *nn; + struct ospf_interface *oi; + struct ospf_neighbor *nbr; + struct route_node *rn; + struct ospf_neighbor *min = NULL; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, nn, oi)) { + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) + if ((nbr = rn->info) != NULL && nbr != oi->nbr_self + && nbr->state != NSM_Down + && nbr->src.s_addr != INADDR_ANY) { + if (first) { + if (!min) + min = nbr; + else if (ntohl(nbr->src.s_addr) + < ntohl(min->src.s_addr)) + min = nbr; + } else if (ntohl(nbr->src.s_addr) + > ntohl(nbr_addr->s_addr)) { + if (!min) + min = nbr; + else if (ntohl(nbr->src.s_addr) + < ntohl(min->src.s_addr)) + min = nbr; + } + } + } + if (min) { + *nbr_addr = min->src; + *ifindex = 0; + return min; + } + return NULL; +} + +static struct ospf_neighbor *ospfNbrLookup(struct variable *v, oid *name, + size_t *length, + struct in_addr *nbr_addr, + ifindex_t *ifindex, int exact) +{ + unsigned int len; + int first; + struct ospf_neighbor *nbr; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + if (!ospf) + return NULL; + + if (exact) { + if (*length != v->namelen + IN_ADDR_SIZE + 1) + return NULL; + + oid2in_addr(name + v->namelen, IN_ADDR_SIZE, nbr_addr); + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + return ospf_snmp_nbr_lookup(ospf, nbr_addr, ifindex); + } else { + first = 0; + len = *length - v->namelen; + + if (len == 0) + first = 1; + + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(name + v->namelen, len, nbr_addr); + + len = *length - v->namelen - IN_ADDR_SIZE; + if (len >= 1) + *ifindex = name[v->namelen + IN_ADDR_SIZE]; + + nbr = ospf_snmp_nbr_lookup_next(nbr_addr, ifindex, first); + + if (nbr) { + *length = v->namelen + IN_ADDR_SIZE + 1; + oid_copy_in_addr(name + v->namelen, nbr_addr); + name[v->namelen + IN_ADDR_SIZE] = *ifindex; + return nbr; + } + } + return NULL; +} + +/* map internal frr neighbor states to official MIB values: + +ospfNbrState OBJECT-TYPE + SYNTAX INTEGER { + down (1), + attempt (2), + init (3), + twoWay (4), + exchangeStart (5), + exchange (6), + loading (7), + full (8) + } +*/ +static int32_t ospf_snmp_neighbor_state(uint8_t nst) +{ + switch (nst) { + case NSM_Attempt: + return 2; + case NSM_Init: + return 3; + case NSM_TwoWay: + return 4; + case NSM_ExStart: + return 5; + case NSM_Exchange: + return 6; + case NSM_Loading: + return 7; + case NSM_Full: + return 8; + default: + return 1; /* down */ + } +} + +static uint8_t *ospfNbrEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct in_addr nbr_addr; + ifindex_t ifindex; + struct ospf_neighbor *nbr; + struct ospf_interface *oi; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&nbr_addr, 0, sizeof(nbr_addr)); + ifindex = 0; + + nbr = ospfNbrLookup(v, name, length, &nbr_addr, &ifindex, exact); + if (!nbr) + return NULL; + oi = nbr->oi; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFNBRIPADDR: + return SNMP_IPADDRESS(nbr_addr); + case OSPFNBRADDRESSLESSINDEX: + return SNMP_INTEGER(ifindex); + case OSPFNBRRTRID: + return SNMP_IPADDRESS(nbr->router_id); + case OSPFNBROPTIONS: + return SNMP_INTEGER(oi->nbr_self->options); + case OSPFNBRPRIORITY: + return SNMP_INTEGER(nbr->priority); + case OSPFNBRSTATE: + return SNMP_INTEGER(ospf_snmp_neighbor_state(nbr->state)); + case OSPFNBREVENTS: + return SNMP_INTEGER(nbr->state_change); + case OSPFNBRLSRETRANSQLEN: + return SNMP_INTEGER(ospf_ls_retransmit_count(nbr)); + case OSPFNBMANBRSTATUS: + return SNMP_INTEGER(SNMP_VALID); + case OSPFNBMANBRPERMANENCE: + return SNMP_INTEGER(2); + case OSPFNBRHELLOSUPPRESSED: + return SNMP_INTEGER(SNMP_FALSE); + default: + return NULL; + } + return NULL; +} + +static uint8_t *ospfVirtNbrEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_vl_data *vl_data; + struct in_addr area_id; + struct in_addr neighbor; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&area_id, 0, sizeof(area_id)); + memset(&neighbor, 0, sizeof(neighbor)); + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + vl_data = ospfVirtIfLookup(v, name, length, &area_id, &neighbor, exact); + if (!vl_data) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFVIRTNBRAREA: + return (uint8_t *)NULL; + case OSPFVIRTNBRRTRID: + return (uint8_t *)NULL; + case OSPFVIRTNBRIPADDR: + return (uint8_t *)NULL; + case OSPFVIRTNBROPTIONS: + return (uint8_t *)NULL; + case OSPFVIRTNBRSTATE: + return (uint8_t *)NULL; + case OSPFVIRTNBREVENTS: + return (uint8_t *)NULL; + case OSPFVIRTNBRLSRETRANSQLEN: + return (uint8_t *)NULL; + case OSPFVIRTNBRHELLOSUPPRESSED: + return (uint8_t *)NULL; + default: + return NULL; + } + return NULL; +} + +static struct ospf_lsa *ospfExtLsdbLookup(struct variable *v, oid *name, + size_t *length, uint8_t *type, + struct in_addr *ls_id, + struct in_addr *router_id, int exact) +{ + int first; + oid *offset; + int offsetlen; + uint8_t lsa_type; + unsigned int len; + struct ospf_lsa *lsa; + struct ospf *ospf; + + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (exact) { + if (*length != v->namelen + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE) + return NULL; + + offset = name + v->namelen; + + /* Make it sure given value match to type. */ + lsa_type = *offset; + offset++; + + if (lsa_type != *type) + return NULL; + + /* LS ID. */ + oid2in_addr(offset, IN_ADDR_SIZE, ls_id); + offset += IN_ADDR_SIZE; + + /* Router ID. */ + oid2in_addr(offset, IN_ADDR_SIZE, router_id); + + return ospf_lsdb_lookup_by_id(ospf->lsdb, *type, *ls_id, + *router_id); + } else { + /* Get variable length. */ + first = 0; + offset = name + v->namelen; + offsetlen = *length - v->namelen; + + /* LSA type value. */ + lsa_type = *offset; + offset++; + offsetlen--; + + if (offsetlen <= 0 || lsa_type < OSPF_AS_EXTERNAL_LSA) + first = 1; + + /* LS ID. */ + len = offsetlen; + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, ls_id); + + offset += IN_ADDR_SIZE; + offsetlen -= IN_ADDR_SIZE; + + /* Router ID. */ + len = offsetlen; + if (len > IN_ADDR_SIZE) + len = IN_ADDR_SIZE; + + oid2in_addr(offset, len, router_id); + + lsa = ospf_lsdb_lookup_by_id_next(ospf->lsdb, *type, *ls_id, + *router_id, first); + + if (lsa) { + /* Fill in length. */ + *length = v->namelen + 1 + IN_ADDR_SIZE + IN_ADDR_SIZE; + + /* Fill in value. */ + offset = name + v->namelen; + + *offset = OSPF_AS_EXTERNAL_LSA; + offset++; + oid_copy_in_addr(offset, &lsa->data->id); + offset += IN_ADDR_SIZE; + oid_copy_in_addr(offset, &lsa->data->adv_router); + + return lsa; + } + } + return NULL; +} + +static uint8_t *ospfExtLsdbEntry(struct variable *v, oid *name, size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct ospf_lsa *lsa; + struct lsa_header *lsah; + uint8_t type; + struct in_addr ls_id; + struct in_addr router_id; + struct ospf *ospf; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + type = OSPF_AS_EXTERNAL_LSA; + memset(&ls_id, 0, sizeof(ls_id)); + memset(&router_id, 0, sizeof(router_id)); + + /* Check OSPF instance. */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL) + return NULL; + + lsa = ospfExtLsdbLookup(v, name, length, &type, &ls_id, &router_id, + exact); + if (!lsa) + return NULL; + + lsah = lsa->data; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFEXTLSDBTYPE: + return SNMP_INTEGER(OSPF_AS_EXTERNAL_LSA); + case OSPFEXTLSDBLSID: + return SNMP_IPADDRESS(lsah->id); + case OSPFEXTLSDBROUTERID: + return SNMP_IPADDRESS(lsah->adv_router); + case OSPFEXTLSDBSEQUENCE: + return SNMP_INTEGER(lsah->ls_seqnum); + case OSPFEXTLSDBAGE: + return SNMP_INTEGER(lsah->ls_age); + case OSPFEXTLSDBCHECKSUM: + return SNMP_INTEGER(lsah->checksum); + case OSPFEXTLSDBADVERTISEMENT: + *var_len = ntohs(lsah->length); + return (uint8_t *)lsah; + default: + return NULL; + } + return NULL; +} + +static uint8_t *ospfAreaAggregateEntry(struct variable *v, oid *name, + size_t *length, int exact, + size_t *var_len, + WriteMethod **write_method) +{ + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + /* Return the current value of the variable */ + switch (v->magic) { + case OSPFAREAAGGREGATEAREAID: + return (uint8_t *)NULL; + case OSPFAREAAGGREGATELSDBTYPE: + return (uint8_t *)NULL; + case OSPFAREAAGGREGATENET: + return (uint8_t *)NULL; + case OSPFAREAAGGREGATEMASK: + return (uint8_t *)NULL; + case OSPFAREAAGGREGATESTATUS: + return (uint8_t *)NULL; + case OSPFAREAAGGREGATEEFFECT: + return (uint8_t *)NULL; + default: + return NULL; + } + return NULL; +} + +/* OSPF Traps. */ +#define IFSTATECHANGE 16 +#define VIRTIFSTATECHANGE 1 +#define NBRSTATECHANGE 2 +#define VIRTNBRSTATECHANGE 3 + +static struct trap_object ospfNbrTrapList[] = {{-2, {1, OSPFROUTERID}}, + {3, {10, 1, OSPFNBRIPADDR}}, + {3, {10, 1, OSPFNBRRTRID}}, + {3, {10, 1, OSPFNBRSTATE}}}; + + +static struct trap_object ospfVirtNbrTrapList[] = { + {-2, {1, 1}}, + {3, {11, 1, OSPFVIRTNBRAREA}}, + {3, {11, 1, OSPFVIRTNBRRTRID}}, + {3, {11, 1, OSPFVIRTNBRSTATE}}}; + +static struct trap_object ospfIfTrapList[] = {{-2, {1, OSPFROUTERID}}, + {3, {7, 1, OSPFIFIPADDRESS}}, + {3, {7, 1, OSPFADDRESSLESSIF}}, + {3, {7, 1, OSPFIFSTATE}}}; + +static struct trap_object ospfVirtIfTrapList[] = { + {-2, {1, OSPFROUTERID}}, + {3, {9, 1, OSPFVIRTIFAREAID}}, + {3, {9, 1, OSPFVIRTIFNEIGHBOR}}, + {3, {9, 1, OSPFVIRTIFSTATE}}}; + +static void ospfTrapNbrStateChange(struct ospf_neighbor *on) +{ + oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; + char msgbuf[16]; + + ospf_nbr_ism_state_message(on, msgbuf, sizeof(msgbuf)); + if (IS_DEBUG_OSPF_EVENT) + zlog_info("%s: trap sent: %pI4 now %s", __func__, + &on->address.u.prefix4, msgbuf); + + oid_copy_in_addr(index, &(on->address.u.prefix4)); + index[IN_ADDR_SIZE] = 0; + + smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, + array_size(ospf_trap_oid), ospf_oid, + sizeof(ospf_oid) / sizeof(oid), index, IN_ADDR_SIZE + 1, + ospfNbrTrapList, array_size(ospfNbrTrapList), NBRSTATECHANGE); +} + +static void ospfTrapVirtNbrStateChange(struct ospf_neighbor *on) +{ + oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; + + zlog_info("ospfTrapVirtNbrStateChange trap sent"); + + oid_copy_in_addr(index, &(on->address.u.prefix4)); + index[IN_ADDR_SIZE] = 0; + + smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, + array_size(ospf_trap_oid), ospf_oid, + sizeof(ospf_oid) / sizeof(oid), index, IN_ADDR_SIZE + 1, + ospfVirtNbrTrapList, array_size(ospfVirtNbrTrapList), + VIRTNBRSTATECHANGE); +} + +static int ospf_snmp_nsm_change(struct ospf_neighbor *nbr, int next_state, + int old_state) +{ + /* Transition to/from state Full should be handled only by + * DR when in Broadcast or Non-Brodcast Multi-Access networks + */ + if ((next_state == NSM_Full || old_state == NSM_Full) + && (nbr->oi->state != ISM_DR) + && (nbr->oi->type == OSPF_IFTYPE_BROADCAST + || nbr->oi->type == OSPF_IFTYPE_NBMA)) + return 0; + + /* State progression to non-terminal state */ + if (next_state > old_state && next_state != NSM_Full + && next_state != NSM_TwoWay) + return 0; + + if (nbr->oi->type == OSPF_IFTYPE_VIRTUALLINK) + ospfTrapVirtNbrStateChange(nbr); + else + ospfTrapNbrStateChange(nbr); + + return 0; +} + +static void ospfTrapIfStateChange(struct ospf_interface *oi) +{ + oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; + + if (IS_DEBUG_OSPF_EVENT) + zlog_info("%s: trap sent: %pI4 now %s", __func__, + &oi->address->u.prefix4, + lookup_msg(ospf_ism_state_msg, oi->state, NULL)); + + oid_copy_in_addr(index, &(oi->address->u.prefix4)); + index[IN_ADDR_SIZE] = 0; + + smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, + array_size(ospf_trap_oid), ospf_oid, + sizeof(ospf_oid) / sizeof(oid), index, IN_ADDR_SIZE + 1, + ospfIfTrapList, array_size(ospfIfTrapList), IFSTATECHANGE); +} + +static void ospfTrapVirtIfStateChange(struct ospf_interface *oi) +{ + oid index[sizeof(oid) * (IN_ADDR_SIZE + 1)]; + + zlog_info("ospfTrapVirtIfStateChange trap sent"); + + oid_copy_in_addr(index, &(oi->address->u.prefix4)); + index[IN_ADDR_SIZE] = 0; + + smux_trap(ospf_variables, array_size(ospf_variables), ospf_trap_oid, + array_size(ospf_trap_oid), ospf_oid, + sizeof(ospf_oid) / sizeof(oid), index, IN_ADDR_SIZE + 1, + ospfVirtIfTrapList, array_size(ospfVirtIfTrapList), + VIRTIFSTATECHANGE); +} + +static int ospf_snmp_ism_change(struct ospf_interface *oi, int state, + int old_state) +{ + /* Terminal state or regression */ + if ((state == ISM_DR) || (state == ISM_Backup) || (state == ISM_DROther) + || (state == ISM_PointToPoint) || (state < old_state)) { + /* ospfVirtIfStateChange */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + ospfTrapVirtIfStateChange(oi); + /* ospfIfStateChange */ + else + ospfTrapIfStateChange(oi); + } + return 0; +} + +/* Register OSPF2-MIB. */ +static int ospf_snmp_init(struct event_loop *tm) +{ + ospf_snmp_iflist = list_new(); + ospf_snmp_vl_table = route_table_init(); + smux_init(tm); + REGISTER_MIB("mibII/ospf", ospf_variables, variable, ospf_oid); + return 0; +} + +static int ospf_snmp_module_init(void) +{ + hook_register(ospf_if_update, ospf_snmp_if_update); + hook_register(ospf_if_delete, ospf_snmp_if_delete); + hook_register(ospf_vl_add, ospf_snmp_vl_add); + hook_register(ospf_vl_delete, ospf_snmp_vl_delete); + hook_register(ospf_ism_change, ospf_snmp_ism_change); + hook_register(ospf_nsm_change, ospf_snmp_nsm_change); + + hook_register(frr_late_init, ospf_snmp_init); + return 0; +} + +FRR_MODULE_SETUP(.name = "ospfd_snmp", .version = FRR_VERSION, + .description = "ospfd AgentX SNMP module", + .init = ospf_snmp_module_init, +); diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c new file mode 100644 index 0000000..274d5bc --- /dev/null +++ b/ospfd/ospf_spf.c @@ -0,0 +1,2073 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPF SPF calculation. + * Copyright (C) 1999, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#include + +#include "monotime.h" +#include "frrevent.h" +#include "memory.h" +#include "hash.h" +#include "linklist.h" +#include "prefix.h" +#include "if.h" +#include "table.h" +#include "log.h" +#include "sockunion.h" /* for inet_ntop () */ + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ia.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ti_lfa.h" +#include "ospfd/ospf_errors.h" + +#ifdef SUPPORT_OSPF_API +#include "ospfd/ospf_apiserver.h" +#endif + +/* Variables to ensure a SPF scheduled log message is printed only once */ + +static unsigned int spf_reason_flags = 0; + +/* dummy vertex to flag "in spftree" */ +static const struct vertex vertex_in_spftree = {}; +#define LSA_SPF_IN_SPFTREE (struct vertex *)&vertex_in_spftree +#define LSA_SPF_NOT_EXPLORED NULL + +static void ospf_clear_spf_reason_flags(void) +{ + spf_reason_flags = 0; +} + +static void ospf_spf_set_reason(ospf_spf_reason_t reason) +{ + spf_reason_flags |= 1 << reason; +} + +static void ospf_vertex_free(void *); + +/* + * Heap related functions, for the managment of the candidates, to + * be used with pqueue. + */ +static int vertex_cmp(const struct vertex *v1, const struct vertex *v2) +{ + if (v1->distance != v2->distance) + return v1->distance - v2->distance; + + if (v1->type != v2->type) { + switch (v1->type) { + case OSPF_VERTEX_NETWORK: + return -1; + case OSPF_VERTEX_ROUTER: + return 1; + } + } + return 0; +} +DECLARE_SKIPLIST_NONUNIQ(vertex_pqueue, struct vertex, pqi, vertex_cmp); + +static void lsdb_clean_stat(struct ospf_lsdb *lsdb) +{ + struct route_table *table; + struct route_node *rn; + struct ospf_lsa *lsa; + int i; + + for (i = OSPF_MIN_LSA; i < OSPF_MAX_LSA; i++) { + table = lsdb->type[i].db; + for (rn = route_top(table); rn; rn = route_next(rn)) + if ((lsa = (rn->info)) != NULL) + lsa->stat = LSA_SPF_NOT_EXPLORED; + } +} + +static struct vertex_nexthop *vertex_nexthop_new(void) +{ + return XCALLOC(MTYPE_OSPF_NEXTHOP, sizeof(struct vertex_nexthop)); +} + +static void vertex_nexthop_free(struct vertex_nexthop *nh) +{ + XFREE(MTYPE_OSPF_NEXTHOP, nh); +} + +/* + * Free the canonical nexthop objects for an area, ie the nexthop objects + * attached to the first-hop router vertices, and any intervening network + * vertices. + */ +static void ospf_canonical_nexthops_free(struct vertex *root) +{ + struct listnode *node, *nnode; + struct vertex *child; + + for (ALL_LIST_ELEMENTS(root->children, node, nnode, child)) { + struct listnode *n2, *nn2; + struct vertex_parent *vp; + + /* + * router vertices through an attached network each + * have a distinct (canonical / not inherited) nexthop + * which must be freed. + * + * A network vertex can only have router vertices as its + * children, so only one level of recursion is possible. + */ + if (child->type == OSPF_VERTEX_NETWORK) + ospf_canonical_nexthops_free(child); + + /* Free child nexthops pointing back to this root vertex */ + for (ALL_LIST_ELEMENTS(child->parents, n2, nn2, vp)) { + if (vp->parent == root && vp->nexthop) { + vertex_nexthop_free(vp->nexthop); + vp->nexthop = NULL; + if (vp->local_nexthop) { + vertex_nexthop_free(vp->local_nexthop); + vp->local_nexthop = NULL; + } + } + } + } +} + +/* + * TODO: Parent list should be excised, in favour of maintaining only + * vertex_nexthop, with refcounts. + */ +static struct vertex_parent *vertex_parent_new(struct vertex *v, int backlink, + struct vertex_nexthop *hop, + struct vertex_nexthop *lhop) +{ + struct vertex_parent *new; + + new = XMALLOC(MTYPE_OSPF_VERTEX_PARENT, sizeof(struct vertex_parent)); + + new->parent = v; + new->backlink = backlink; + new->nexthop = hop; + new->local_nexthop = lhop; + + return new; +} + +static void vertex_parent_free(struct vertex_parent *p) +{ + vertex_nexthop_free(p->local_nexthop); + vertex_nexthop_free(p->nexthop); + XFREE(MTYPE_OSPF_VERTEX_PARENT, p); +} + +int vertex_parent_cmp(void *aa, void *bb) +{ + struct vertex_parent *a = aa, *b = bb; + return IPV4_ADDR_CMP(&a->nexthop->router, &b->nexthop->router); +} + +static struct vertex *ospf_vertex_new(struct ospf_area *area, + struct ospf_lsa *lsa) +{ + struct vertex *new; + + new = XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex)); + + new->flags = 0; + new->type = lsa->data->type; + new->id = lsa->data->id; + new->lsa = lsa->data; + new->children = list_new(); + new->parents = list_new(); + new->parents->del = (void (*)(void *))vertex_parent_free; + new->parents->cmp = vertex_parent_cmp; + new->lsa_p = lsa; + + lsa->stat = new; + + listnode_add(area->spf_vertex_list, new); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Created %s vertex %pI4", __func__, + new->type == OSPF_VERTEX_ROUTER ? "Router" + : "Network", + &new->lsa->id); + + return new; +} + +static void ospf_vertex_free(void *data) +{ + struct vertex *v = data; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Free %s vertex %pI4", __func__, + v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", + &v->lsa->id); + + if (v->children) + list_delete(&v->children); + + if (v->parents) + list_delete(&v->parents); + + v->lsa = NULL; + + XFREE(MTYPE_OSPF_VERTEX, v); +} + +static void ospf_vertex_dump(const char *msg, struct vertex *v, + int print_parents, int print_children) +{ + if (!IS_DEBUG_OSPF_EVENT) + return; + + zlog_debug("%s %s vertex %pI4 distance %u flags %u", msg, + v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", + &v->lsa->id, v->distance, (unsigned int)v->flags); + + if (print_parents) { + struct listnode *node; + struct vertex_parent *vp; + + for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { + if (vp) { + zlog_debug( + "parent %pI4 backlink %d nexthop %pI4 lsa pos %d", + &vp->parent->lsa->id, vp->backlink, + &vp->nexthop->router, + vp->nexthop->lsa_pos); + } + } + } + + if (print_children) { + struct listnode *cnode; + struct vertex *cv; + + for (ALL_LIST_ELEMENTS_RO(v->children, cnode, cv)) + ospf_vertex_dump(" child:", cv, 0, 0); + } +} + + +/* Add a vertex to the list of children in each of its parents. */ +static void ospf_vertex_add_parent(struct vertex *v) +{ + struct vertex_parent *vp; + struct listnode *node; + + assert(v && v->parents); + + for (ALL_LIST_ELEMENTS_RO(v->parents, node, vp)) { + assert(vp->parent && vp->parent->children); + + /* No need to add two links from the same parent. */ + if (listnode_lookup(vp->parent->children, v) == NULL) + listnode_add(vp->parent->children, v); + } +} + +/* Find a vertex according to its router id */ +struct vertex *ospf_spf_vertex_find(struct in_addr id, struct list *vertex_list) +{ + struct listnode *node; + struct vertex *found; + + for (ALL_LIST_ELEMENTS_RO(vertex_list, node, found)) { + if (found->id.s_addr == id.s_addr) + return found; + } + + return NULL; +} + +/* Find a vertex parent according to its router id */ +struct vertex_parent *ospf_spf_vertex_parent_find(struct in_addr id, + struct vertex *vertex) +{ + struct listnode *node; + struct vertex_parent *found; + + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, found)) { + if (found->parent->id.s_addr == id.s_addr) + return found; + } + + return NULL; +} + +struct vertex *ospf_spf_vertex_by_nexthop(struct vertex *root, + struct in_addr *nexthop) +{ + struct listnode *node; + struct vertex *child; + struct vertex_parent *vertex_parent; + + for (ALL_LIST_ELEMENTS_RO(root->children, node, child)) { + vertex_parent = ospf_spf_vertex_parent_find(root->id, child); + if (vertex_parent->nexthop->router.s_addr == nexthop->s_addr) + return child; + } + + return NULL; +} + +/* Create a deep copy of a SPF vertex without children and parents */ +static struct vertex *ospf_spf_vertex_copy(struct vertex *vertex) +{ + struct vertex *copy; + + copy = XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex)); + + memcpy(copy, vertex, sizeof(struct vertex)); + copy->parents = list_new(); + copy->parents->del = (void (*)(void *))vertex_parent_free; + copy->parents->cmp = vertex_parent_cmp; + copy->children = list_new(); + + return copy; +} + +/* Create a deep copy of a SPF vertex_parent */ +static struct vertex_parent * +ospf_spf_vertex_parent_copy(struct vertex_parent *vertex_parent) +{ + struct vertex_parent *vertex_parent_copy; + struct vertex_nexthop *nexthop_copy, *local_nexthop_copy; + + vertex_parent_copy = + XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex_parent)); + + nexthop_copy = vertex_nexthop_new(); + local_nexthop_copy = vertex_nexthop_new(); + + memcpy(vertex_parent_copy, vertex_parent, sizeof(struct vertex_parent)); + memcpy(nexthop_copy, vertex_parent->nexthop, + sizeof(struct vertex_nexthop)); + memcpy(local_nexthop_copy, vertex_parent->local_nexthop, + sizeof(struct vertex_nexthop)); + + vertex_parent_copy->nexthop = nexthop_copy; + vertex_parent_copy->local_nexthop = local_nexthop_copy; + + return vertex_parent_copy; +} + +/* Create a deep copy of a SPF tree */ +void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list) +{ + struct listnode *node; + struct vertex *vertex_copy, *child, *child_copy, *parent_copy; + struct vertex_parent *vertex_parent, *vertex_parent_copy; + + /* First check if the node is already in the vertex list */ + vertex_copy = ospf_spf_vertex_find(vertex->id, vertex_list); + if (!vertex_copy) { + vertex_copy = ospf_spf_vertex_copy(vertex); + listnode_add(vertex_list, vertex_copy); + } + + /* Copy all parents, create parent nodes if necessary */ + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, vertex_parent)) { + parent_copy = ospf_spf_vertex_find(vertex_parent->parent->id, + vertex_list); + if (!parent_copy) { + parent_copy = + ospf_spf_vertex_copy(vertex_parent->parent); + listnode_add(vertex_list, parent_copy); + } + vertex_parent_copy = ospf_spf_vertex_parent_copy(vertex_parent); + vertex_parent_copy->parent = parent_copy; + listnode_add(vertex_copy->parents, vertex_parent_copy); + } + + /* Copy all children, create child nodes if necessary */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + child_copy = ospf_spf_vertex_find(child->id, vertex_list); + if (!child_copy) { + child_copy = ospf_spf_vertex_copy(child); + listnode_add(vertex_list, child_copy); + } + listnode_add(vertex_copy->children, child_copy); + } + + /* Finally continue copying with child nodes */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) + ospf_spf_copy(child, vertex_list); +} + +static void ospf_spf_remove_branch(struct vertex_parent *vertex_parent, + struct vertex *child, + struct list *vertex_list) +{ + struct listnode *node, *nnode, *inner_node, *inner_nnode; + struct vertex *grandchild; + struct vertex_parent *vertex_parent_found; + bool has_more_links = false; + + /* + * First check if there are more nexthops for that parent to that child + */ + for (ALL_LIST_ELEMENTS_RO(child->parents, node, vertex_parent_found)) { + if (vertex_parent_found->parent->id.s_addr + == vertex_parent->parent->id.s_addr + && vertex_parent_found->nexthop->router.s_addr + != vertex_parent->nexthop->router.s_addr) + has_more_links = true; + } + + /* + * No more links from that parent? Then delete the child from its + * children list. + */ + if (!has_more_links) + listnode_delete(vertex_parent->parent->children, child); + + /* + * Delete the vertex_parent from the child parents list, this needs to + * be done anyway. + */ + listnode_delete(child->parents, vertex_parent); + + /* + * Are there actually more parents left? If not, then delete the child! + * This is done by recursively removing the links to the grandchildren, + * such that finally the child can be removed without leaving unused + * partial branches. + */ + if (child->parents->count == 0) { + for (ALL_LIST_ELEMENTS(child->children, node, nnode, + grandchild)) { + for (ALL_LIST_ELEMENTS(grandchild->parents, inner_node, + inner_nnode, + vertex_parent_found)) { + ospf_spf_remove_branch(vertex_parent_found, + grandchild, vertex_list); + } + } + listnode_delete(vertex_list, child); + ospf_vertex_free(child); + } +} + +static int ospf_spf_remove_link(struct vertex *vertex, struct list *vertex_list, + struct router_lsa_link *link) +{ + struct listnode *node, *inner_node; + struct vertex *child; + struct vertex_parent *vertex_parent; + + /* + * Identify the node who shares a subnet (given by the link) with a + * child and remove the branch of this particular child. + */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node, + vertex_parent)) { + if ((vertex_parent->local_nexthop->router.s_addr + & link->link_data.s_addr) + == (link->link_id.s_addr + & link->link_data.s_addr)) { + ospf_spf_remove_branch(vertex_parent, child, + vertex_list); + return 0; + } + } + } + + /* No link found yet, move on recursively */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + if (ospf_spf_remove_link(child, vertex_list, link) == 0) + return 0; + } + + /* link was not removed yet */ + return 1; +} + +void ospf_spf_remove_resource(struct vertex *vertex, struct list *vertex_list, + struct protected_resource *resource) +{ + struct listnode *node, *nnode; + struct vertex *found; + struct vertex_parent *vertex_parent; + + switch (resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + ospf_spf_remove_link(vertex, vertex_list, resource->link); + break; + case OSPF_TI_LFA_NODE_PROTECTION: + found = ospf_spf_vertex_find(resource->router_id, vertex_list); + if (!found) + break; + + /* + * Remove the node by removing all links from its parents. Note + * that the child is automatically removed here with the last + * link from a parent, hence no explicit removal of the node. + */ + for (ALL_LIST_ELEMENTS(found->parents, node, nnode, + vertex_parent)) + ospf_spf_remove_branch(vertex_parent, found, + vertex_list); + + break; + case OSPF_TI_LFA_UNDEFINED_PROTECTION: + /* do nothing */ + break; + } +} + +static void ospf_spf_init(struct ospf_area *area, struct ospf_lsa *root_lsa, + bool is_dry_run, bool is_root_node) +{ + struct list *vertex_list; + struct vertex *v; + + /* Create vertex list */ + vertex_list = list_new(); + vertex_list->del = ospf_vertex_free; + area->spf_vertex_list = vertex_list; + + /* Create root node. */ + v = ospf_vertex_new(area, root_lsa); + area->spf = v; + + area->spf_dry_run = is_dry_run; + area->spf_root_node = is_root_node; + + /* Reset ABR and ASBR router counts. */ + area->abr_count = 0; + area->asbr_count = 0; +} + +/* return index of link back to V from W, or -1 if no link found */ +static int ospf_lsa_has_link(struct lsa_header *w, struct lsa_header *v) +{ + unsigned int i, length; + struct router_lsa *rl; + struct network_lsa *nl; + + /* In case of W is Network LSA. */ + if (w->type == OSPF_NETWORK_LSA) { + if (v->type == OSPF_NETWORK_LSA) + return -1; + + nl = (struct network_lsa *)w; + length = (ntohs(w->length) - OSPF_LSA_HEADER_SIZE - 4) / 4; + + for (i = 0; i < length; i++) + if (IPV4_ADDR_SAME(&nl->routers[i], &v->id)) + return i; + return -1; + } + + /* In case of W is Router LSA. */ + if (w->type == OSPF_ROUTER_LSA) { + rl = (struct router_lsa *)w; + + length = ntohs(w->length); + + for (i = 0; i < ntohs(rl->links) + && length >= sizeof(struct router_lsa); + i++, length -= 12) { + switch (rl->link[i].type) { + case LSA_LINK_TYPE_POINTOPOINT: + case LSA_LINK_TYPE_VIRTUALLINK: + /* Router LSA ID. */ + if (v->type == OSPF_ROUTER_LSA + && IPV4_ADDR_SAME(&rl->link[i].link_id, + &v->id)) { + return i; + } + break; + case LSA_LINK_TYPE_TRANSIT: + /* Network LSA ID. */ + if (v->type == OSPF_NETWORK_LSA + && IPV4_ADDR_SAME(&rl->link[i].link_id, + &v->id)) { + return i; + } + break; + case LSA_LINK_TYPE_STUB: + /* Stub can't lead anywhere, carry on */ + continue; + default: + break; + } + } + } + return -1; +} + +/* + * Find the next link after prev_link from v to w. If prev_link is + * NULL, return the first link from v to w. Ignore stub and virtual links; + * these link types will never be returned. + */ +static struct router_lsa_link * +ospf_get_next_link(struct vertex *v, struct vertex *w, + struct router_lsa_link *prev_link) +{ + uint8_t *p; + uint8_t *lim; + uint8_t lsa_type = LSA_LINK_TYPE_TRANSIT; + struct router_lsa_link *l; + + if (w->type == OSPF_VERTEX_ROUTER) + lsa_type = LSA_LINK_TYPE_POINTOPOINT; + + if (prev_link == NULL) + p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; + else { + p = (uint8_t *)prev_link; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (prev_link->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + } + + lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); + + while (p < lim) { + l = (struct router_lsa_link *)p; + + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + if (l->m[0].type != lsa_type) + continue; + + if (IPV4_ADDR_SAME(&l->link_id, &w->id)) + return l; + } + + return NULL; +} + +static void ospf_spf_flush_parents(struct vertex *w) +{ + struct vertex_parent *vp; + struct listnode *ln, *nn; + + /* delete the existing nexthops */ + for (ALL_LIST_ELEMENTS(w->parents, ln, nn, vp)) { + list_delete_node(w->parents, ln); + vertex_parent_free(vp); + } +} + +/* + * Consider supplied next-hop for inclusion to the supplied list of + * equal-cost next-hops, adjust list as necessary. + * + * Returns vertex parent pointer if created otherwise `NULL` if it already + * exists. + */ +static struct vertex_parent *ospf_spf_add_parent(struct vertex *v, + struct vertex *w, + struct vertex_nexthop *newhop, + struct vertex_nexthop *newlhop, + unsigned int distance) +{ + struct vertex_parent *vp, *wp; + struct listnode *node; + + /* we must have a newhop, and a distance */ + assert(v && w && newhop); + assert(distance); + + /* + * IFF w has already been assigned a distance, then we shouldn't get + * here unless callers have determined V(l)->W is shortest / + * equal-shortest path (0 is a special case distance (no distance yet + * assigned)). + */ + if (w->distance) + assert(distance <= w->distance); + else + w->distance = distance; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Adding %pI4 as parent of %pI4", __func__, + &v->lsa->id, &w->lsa->id); + + /* + * Adding parent for a new, better path: flush existing parents from W. + */ + if (distance < w->distance) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: distance %d better than %d, flushing existing parents", + __func__, distance, w->distance); + ospf_spf_flush_parents(w); + w->distance = distance; + } + + /* + * new parent is <= existing parents, add it to parent list (if nexthop + * not on parent list) + */ + for (ALL_LIST_ELEMENTS_RO(w->parents, node, wp)) { + if (memcmp(newhop, wp->nexthop, sizeof(*newhop)) == 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ... nexthop already on parent list, skipping add", + __func__); + + return NULL; + } + } + + vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop, + newlhop); + listnode_add_sort(w->parents, vp); + + return vp; +} + +static int match_stub_prefix(struct lsa_header *lsa, struct in_addr v_link_addr, + struct in_addr w_link_addr) +{ + uint8_t *p, *lim; + struct router_lsa_link *l = NULL; + struct in_addr masked_lsa_addr; + + if (lsa->type != OSPF_ROUTER_LSA) + return 0; + + p = ((uint8_t *)lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)lsa) + ntohs(lsa->length); + + while (p < lim) { + l = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + if (l->m[0].type != LSA_LINK_TYPE_STUB) + continue; + + masked_lsa_addr.s_addr = + (l->link_id.s_addr & l->link_data.s_addr); + + /* check that both links belong to the same stub subnet */ + if ((masked_lsa_addr.s_addr + == (v_link_addr.s_addr & l->link_data.s_addr)) + && (masked_lsa_addr.s_addr + == (w_link_addr.s_addr & l->link_data.s_addr))) + return 1; + } + + return 0; +} + +/* + * 16.1.1. Calculate nexthop from root through V (parent) to + * vertex W (destination), with given distance from root->W. + * + * The link must be supplied if V is the root vertex. In all other cases + * it may be NULL. + * + * Note that this function may fail, hence the state of the destination + * vertex, W, should /not/ be modified in a dependent manner until + * this function returns. This function will update the W vertex with the + * provided distance as appropriate. + */ +static unsigned int ospf_nexthop_calculation(struct ospf_area *area, + struct vertex *v, struct vertex *w, + struct router_lsa_link *l, + unsigned int distance, int lsa_pos) +{ + struct listnode *node, *nnode; + struct vertex_nexthop *nh, *lnh; + struct vertex_parent *vp; + unsigned int added = 0; + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: Start", __func__); + ospf_vertex_dump("V (parent):", v, 1, 1); + ospf_vertex_dump("W (dest) :", w, 1, 1); + zlog_debug("V->W distance: %d", distance); + } + + if (v == area->spf) { + /* + * 16.1.1 para 4. In the first case, the parent vertex (V) is + * the root (the calculating router itself). This means that + * the destination is either a directly connected network or + * directly connected router. The outgoing interface in this + * case is simply the OSPF interface connecting to the + * destination network/router. + */ + + /* we *must* be supplied with the link data */ + assert(l != NULL); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: considering link type:%d link_id:%pI4 link_data:%pI4", + __func__, l->m[0].type, &l->link_id, + &l->link_data); + + if (w->type == OSPF_VERTEX_ROUTER) { + /* + * l is a link from v to w l2 will be link from w to v + */ + struct router_lsa_link *l2 = NULL; + + if (l->m[0].type == LSA_LINK_TYPE_POINTOPOINT) { + struct ospf_interface *oi = NULL; + struct in_addr nexthop = {.s_addr = 0}; + + if (area->spf_root_node) { + oi = ospf_if_lookup_by_lsa_pos(area, + lsa_pos); + if (!oi) { + zlog_debug( + "%s: OI not found in LSA: lsa_pos: %d link_id:%pI4 link_data:%pI4", + __func__, lsa_pos, + &l->link_id, + &l->link_data); + return 0; + } + } + + /* + * If the destination is a router which connects + * to the calculating router via a + * Point-to-MultiPoint network, the + * destination's next hop IP address(es) can be + * determined by examining the destination's + * router-LSA: each link pointing back to the + * calculating router and having a Link Data + * field belonging to the Point-to-MultiPoint + * network provides an IP address of the next + * hop router. + * + * At this point l is a link from V to W, and V + * is the root ("us"). If it is a point-to- + * multipoint interface, then look through the + * links in the opposite direction (W to V). + * If any of them have an address that lands + * within the subnet declared by the PtMP link, + * then that link is a constituent of the PtMP + * link, and its address is a nexthop address + * for V. + * + * Note for point-to-point interfaces: + * + * Having nexthop = 0 (as proposed in the RFC) + * is tempting, but NOT acceptable. It breaks + * AS-External routes with a forwarding address, + * since ospf_ase_complete_direct_routes() will + * mistakenly assume we've reached the last hop + * and should place the forwarding address as + * nexthop. Also, users may configure multi- + * access links in p2p mode, so we need the IP + * to ARP the nexthop. + * + * If the calculating router is the SPF root + * node and the link is P2P then access the + * interface information directly. This can be + * crucial when e.g. IP unnumbered is used + * where 'correct' nexthop information are not + * available via Router LSAs. + * + * Otherwise handle P2P and P2MP the same way + * as described above using a reverse lookup to + * figure out the nexthop. + */ + + /* + * HACK: we don't know (yet) how to distinguish + * between P2P and P2MP interfaces by just + * looking at LSAs, which is important for + * TI-LFA since you want to do SPF calculations + * from the perspective of other nodes. Since + * TI-LFA is currently not implemented for P2MP + * we just check here if it is enabled and then + * blindly assume that P2P is used. Ultimately + * the interface code needs to be removed + * somehow. + */ + if (area->ospf->ti_lfa_enabled + || (oi && oi->type == OSPF_IFTYPE_POINTOPOINT) + || (oi && oi->type == OSPF_IFTYPE_POINTOMULTIPOINT + && oi->address->prefixlen == IPV4_MAX_BITLEN)) { + struct ospf_neighbor *nbr_w = NULL; + + /* Calculating node is root node, link + * is P2P */ + if (area->spf_root_node) { + nbr_w = ospf_nbr_lookup_by_routerid( + oi->nbrs, &l->link_id); + if (nbr_w) { + added = 1; + nexthop = nbr_w->src; + } + } + + /* Reverse lookup */ + if (!added) { + while ((l2 = ospf_get_next_link( + w, v, l2))) { + if (match_stub_prefix( + v->lsa, + l->link_data, + l2->link_data)) { + added = 1; + nexthop = + l2->link_data; + break; + } + } + } + } else if (oi && oi->type + == OSPF_IFTYPE_POINTOMULTIPOINT) { + struct prefix_ipv4 la; + + la.family = AF_INET; + la.prefixlen = oi->address->prefixlen; + + /* + * V links to W on PtMP interface; + * find the interface address on W + */ + while ((l2 = ospf_get_next_link(w, v, + l2))) { + la.prefix = l2->link_data; + + if (prefix_cmp((struct prefix + *)&la, + oi->address) + != 0) + continue; + added = 1; + nexthop = l2->link_data; + break; + } + } + + if (added) { + nh = vertex_nexthop_new(); + nh->router = nexthop; + nh->lsa_pos = lsa_pos; + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + + if (ospf_spf_add_parent(v, w, nh, lnh, + distance) == + NULL) { + vertex_nexthop_free(nh); + vertex_nexthop_free(lnh); + } + return 1; + } else + zlog_info( + "%s: could not determine nexthop for link %s", + __func__, oi ? oi->ifp->name : ""); + } /* end point-to-point link from V to W */ + else if (l->m[0].type == LSA_LINK_TYPE_VIRTUALLINK) { + /* + * VLink implementation limitations: + * a) vl_data can only reference one nexthop, + * so no ECMP to backbone through VLinks. + * Though transit-area summaries may be + * considered, and those can be ECMP. + * b) We can only use /one/ VLink, even if + * multiple ones exist this router through + * multiple transit-areas. + */ + + struct ospf_vl_data *vl_data; + + vl_data = ospf_vl_lookup(area->ospf, NULL, + l->link_id); + + if (vl_data + && CHECK_FLAG(vl_data->flags, + OSPF_VL_FLAG_APPROVED)) { + nh = vertex_nexthop_new(); + nh->router = vl_data->nexthop.router; + nh->lsa_pos = vl_data->nexthop.lsa_pos; + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + + if (ospf_spf_add_parent(v, w, nh, lnh, + distance) == + NULL) { + vertex_nexthop_free(nh); + vertex_nexthop_free(lnh); + } + + return 1; + } else + zlog_info( + "%s: vl_data for VL link not found", + __func__); + } /* end virtual-link from V to W */ + return 0; + } /* end W is a Router vertex */ + else { + assert(w->type == OSPF_VERTEX_NETWORK); + + nh = vertex_nexthop_new(); + nh->router.s_addr = 0; /* Nexthop not required */ + nh->lsa_pos = lsa_pos; + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, sizeof(struct vertex_nexthop)); + + if (ospf_spf_add_parent(v, w, nh, lnh, distance) == + NULL) { + vertex_nexthop_free(nh); + vertex_nexthop_free(lnh); + } + + return 1; + } + } /* end V is the root */ + /* Check if W's parent is a network connected to root. */ + else if (v->type == OSPF_VERTEX_NETWORK) { + /* See if any of V's parents are the root. */ + for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) { + if (vp->parent == area->spf) { + /* + * 16.1.1 para 5. ...the parent vertex is a + * network that directly connects the + * calculating router to the destination + * router. The list of next hops is then + * determined by examining the destination's + * router-LSA ... + */ + + assert(w->type == OSPF_VERTEX_ROUTER); + while ((l = ospf_get_next_link(w, v, l))) { + /* + * ... For each link in the router-LSA + * that points back to the parent + * network, the link's Link Data field + * provides the IP address of a next hop + * router. The outgoing interface to use + * can then be derived from the next + * hop IP address (or it can be + * inherited from the parent network). + */ + nh = vertex_nexthop_new(); + nh->router = l->link_data; + nh->lsa_pos = vp->nexthop->lsa_pos; + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + + added = 1; + if (ospf_spf_add_parent(v, w, nh, lnh, + distance) == + NULL) { + vertex_nexthop_free(nh); + vertex_nexthop_free(lnh); + } + } + /* + * Note lack of return is deliberate. See next + * comment. + */ + } + } + /* + * NB: This code is non-trivial. + * + * E.g. it is not enough to know that V connects to the root. It + * is also important that the while above, looping through all + * links from W->V found at least one link, so that we know + * there is bi-directional connectivity between V and W (which + * need not be the case, e.g. when OSPF has not yet converged + * fully). Otherwise, if we /always/ return here, without having + * checked that root->V->-W actually resulted in a valid nexthop + * being created, then we we will prevent SPF from finding/using + * higher cost paths. + * + * It is important, if root->V->W has not been added, that we + * continue through to the intervening-router nexthop code + * below. So as to ensure other paths to V may be used. This + * avoids unnecessary blackholes while OSPF is converging. + * + * I.e. we may have arrived at this function, examining V -> W, + * via workable paths other than root -> V, and it's important + * to avoid getting "confused" by non-working root->V->W path + * - it's important to *not* lose the working non-root paths, + * just because of a non-viable root->V->W. + */ + if (added) + return added; + } + + /* + * 16.1.1 para 4. If there is at least one intervening router in the + * current shortest path between the destination and the root, the + * destination simply inherits the set of next hops from the + * parent. + */ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Intervening routers, adding parent(s)", + __func__); + + for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) { + added = 1; + + /* + * The nexthop is inherited, but the local nexthop still needs + * to be created. + */ + if (l) { + lnh = vertex_nexthop_new(); + lnh->router = l->link_data; + lnh->lsa_pos = lsa_pos; + } else { + lnh = NULL; + } + + nh = vertex_nexthop_new(); + *nh = *vp->nexthop; + + if (ospf_spf_add_parent(v, w, nh, lnh, distance) == NULL) { + vertex_nexthop_free(nh); + vertex_nexthop_free(lnh); + } + } + + return added; +} + +static int ospf_spf_is_protected_resource(struct ospf_area *area, + struct router_lsa_link *link, + struct lsa_header *lsa) +{ + uint8_t *p, *lim; + struct router_lsa_link *p_link; + struct router_lsa_link *l = NULL; + struct in_addr router_id; + int link_type; + + if (!area->spf_protected_resource) + return 0; + + link_type = link->m[0].type; + + switch (area->spf_protected_resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + p_link = area->spf_protected_resource->link; + if (!p_link) + return 0; + + /* For P2P: check if the link belongs to the same subnet */ + if (link_type == LSA_LINK_TYPE_POINTOPOINT + && (p_link->link_id.s_addr & p_link->link_data.s_addr) + == (link->link_data.s_addr + & p_link->link_data.s_addr)) + return 1; + + /* For stub: check if this the same subnet */ + if (link_type == LSA_LINK_TYPE_STUB + && (p_link->link_id.s_addr == link->link_id.s_addr) + && (p_link->link_data.s_addr == link->link_data.s_addr)) + return 1; + + break; + case OSPF_TI_LFA_NODE_PROTECTION: + router_id = area->spf_protected_resource->router_id; + if (router_id.s_addr == INADDR_ANY) + return 0; + + /* For P2P: check if the link leads to the protected node */ + if (link_type == LSA_LINK_TYPE_POINTOPOINT + && link->link_id.s_addr == router_id.s_addr) + return 1; + + /* The rest is about stub links! */ + if (link_type != LSA_LINK_TYPE_STUB) + return 0; + + /* + * Check if there's a P2P link in the router LSA with the + * corresponding link data in the same subnet. + */ + + p = ((uint8_t *)lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)lsa) + ntohs(lsa->length); + + while (p < lim) { + l = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* We only care about P2P with the proper link id */ + if ((l->m[0].type != LSA_LINK_TYPE_POINTOPOINT) + || (l->link_id.s_addr != router_id.s_addr)) + continue; + + /* Link data in the subnet given by the link? */ + if ((link->link_id.s_addr & link->link_data.s_addr) + == (l->link_data.s_addr & link->link_data.s_addr)) + return 1; + } + + break; + case OSPF_TI_LFA_UNDEFINED_PROTECTION: + break; + } + + return 0; +} + +/* + * For TI-LFA we need the reverse SPF for Q spaces. The reverse SPF is created + * by honoring the weight of the reverse 'edge', e.g. the edge from W to V, and + * NOT the weight of the 'edge' from V to W as usual. Hence we need to find the + * corresponding link in the LSA of W and extract the particular weight. + * + * TODO: Only P2P supported by now! + */ +static uint16_t get_reverse_distance(struct vertex *v, + struct router_lsa_link *l, + struct ospf_lsa *w_lsa) +{ + uint8_t *p, *lim; + struct router_lsa_link *w_link; + uint16_t distance = 0; + + assert(w_lsa && w_lsa->data); + + p = ((uint8_t *)w_lsa->data) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)w_lsa->data) + ntohs(w_lsa->data->length); + + while (p < lim) { + w_link = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (w_link->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* Only care about P2P with link ID equal to V's router id */ + if (w_link->m[0].type == LSA_LINK_TYPE_POINTOPOINT + && w_link->link_id.s_addr == v->id.s_addr) { + distance = ntohs(w_link->m[0].metric); + break; + } + } + + /* + * This might happen if the LSA for W is not complete yet. In this + * case we take the weight of the 'forward' link from V. When the LSA + * for W is completed the reverse SPF is run again anyway. + */ + if (distance == 0) + distance = ntohs(l->m[0].metric); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: reversed distance is %u", __func__, distance); + + return distance; +} + +/* + * RFC2328 16.1 (2). + * v is on the SPF tree. Examine the links in v's LSA. Update the list of + * candidates with any vertices not already on the list. If a lower-cost path + * is found to a vertex already on the candidate list, store the new cost. + */ +static void ospf_spf_next(struct vertex *v, struct ospf_area *area, + struct vertex_pqueue_head *candidate) +{ + struct ospf_lsa *w_lsa = NULL; + uint8_t *p; + uint8_t *lim; + struct router_lsa_link *l = NULL; + struct in_addr *r; + int type = 0, lsa_pos = -1, lsa_pos_next = 0; + uint16_t link_distance; + + /* + * If this is a router-LSA, and bit V of the router-LSA (see Section + * A.4.2:RFC2328) is set, set Area A's TransitCapability to true. + */ + if (v->type == OSPF_VERTEX_ROUTER) { + if (IS_ROUTER_LSA_VIRTUAL((struct router_lsa *)v->lsa)) + area->transit = OSPF_TRANSIT_TRUE; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Next vertex of %s vertex %pI4", __func__, + v->type == OSPF_VERTEX_ROUTER ? "Router" : "Network", + &v->lsa->id); + + p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); + + while (p < lim) { + struct vertex *w; + unsigned int distance; + + /* In case of V is Router-LSA. */ + if (v->lsa->type == OSPF_ROUTER_LSA) { + l = (struct router_lsa_link *)p; + + lsa_pos = lsa_pos_next; /* LSA link position */ + lsa_pos_next++; + + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* + * (a) If this is a link to a stub network, examine the + * next link in V's LSA. Links to stub networks will + * be considered in the second stage of the shortest + * path calculation. + */ + if ((type = l->m[0].type) == LSA_LINK_TYPE_STUB) + continue; + + /* + * Don't process TI-LFA protected resources. + * + * TODO: Replace this by a proper solution, e.g. remove + * corresponding links from the LSDB and run the SPF + * algo with the stripped-down LSDB. + */ + if (ospf_spf_is_protected_resource(area, l, v->lsa)) + continue; + + /* + * (b) Otherwise, W is a transit vertex (router or + * transit network). Look up the vertex W's LSA + * (router-LSA or network-LSA) in Area A's link state + * database. + */ + switch (type) { + case LSA_LINK_TYPE_POINTOPOINT: + case LSA_LINK_TYPE_VIRTUALLINK: + if (type == LSA_LINK_TYPE_VIRTUALLINK + && IS_DEBUG_OSPF_EVENT) + zlog_debug( + "looking up LSA through VL: %pI4", + &l->link_id); + w_lsa = ospf_lsa_lookup(area->ospf, area, + OSPF_ROUTER_LSA, + l->link_id, l->link_id); + if (w_lsa && IS_DEBUG_OSPF_EVENT) + zlog_debug("found Router LSA %pI4", + &l->link_id); + break; + case LSA_LINK_TYPE_TRANSIT: + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Looking up Network LSA, ID: %pI4", + &l->link_id); + w_lsa = ospf_lsa_lookup_by_id( + area, OSPF_NETWORK_LSA, l->link_id); + if (w_lsa && IS_DEBUG_OSPF_EVENT) + zlog_debug("found the LSA"); + break; + default: + flog_warn(EC_OSPF_LSA, + "Invalid LSA link type %d", type); + continue; + } + + /* + * For TI-LFA we might need the reverse SPF. + * Currently only works with P2P! + */ + if (type == LSA_LINK_TYPE_POINTOPOINT + && area->spf_reversed) + link_distance = + get_reverse_distance(v, l, w_lsa); + else + link_distance = ntohs(l->m[0].metric); + + /* step (d) below */ + distance = v->distance + link_distance; + } else { + /* In case of V is Network-LSA. */ + r = (struct in_addr *)p; + p += sizeof(struct in_addr); + + /* Lookup the vertex W's LSA. */ + w_lsa = ospf_lsa_lookup_by_id(area, OSPF_ROUTER_LSA, + *r); + if (w_lsa && IS_DEBUG_OSPF_EVENT) + zlog_debug("found Router LSA %pI4", + &w_lsa->data->id); + + /* step (d) below */ + distance = v->distance; + } + + /* + * (b cont.) If the LSA does not exist, or its LS age is equal + * to MaxAge, or it does not have a link back to vertex V, + * examine the next link in V's LSA.[23] + */ + if (w_lsa == NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("No LSA found"); + continue; + } + + if (IS_LSA_MAXAGE(w_lsa)) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("LSA is MaxAge"); + continue; + } + + if (ospf_lsa_has_link(w_lsa->data, v->lsa) < 0) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("The LSA doesn't have a link back"); + continue; + } + + /* + * (c) If vertex W is already on the shortest-path tree, examine + * the next link in the LSA. + */ + if (w_lsa->stat == LSA_SPF_IN_SPFTREE) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("The LSA is already in SPF"); + continue; + } + + /* + * (d) Calculate the link state cost D of the resulting path + * from the root to vertex W. D is equal to the sum of the link + * state cost of the (already calculated) shortest path to + * vertex V and the advertised cost of the link between vertices + * V and W. If D is: + */ + + /* calculate link cost D -- moved above */ + + /* Is there already vertex W in candidate list? */ + if (w_lsa->stat == LSA_SPF_NOT_EXPLORED) { + /* prepare vertex W. */ + w = ospf_vertex_new(area, w_lsa); + + /* Calculate nexthop to W. */ + if (ospf_nexthop_calculation(area, v, w, l, distance, + lsa_pos)) + vertex_pqueue_add(candidate, w); + else { + listnode_delete(area->spf_vertex_list, w); + ospf_vertex_free(w); + w_lsa->stat = LSA_SPF_NOT_EXPLORED; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Nexthop Calc failed"); + } + } else if (w_lsa->stat != LSA_SPF_IN_SPFTREE) { + w = w_lsa->stat; + if (w->distance < distance) { + continue; + } + else if (w->distance == distance) { + /* + * Found an equal-cost path to W. + * Calculate nexthop of to W from V. + */ + ospf_nexthop_calculation(area, v, w, l, + distance, lsa_pos); + } + else { + /* + * Found a lower-cost path to W. + * nexthop_calculation is conditional, if it + * finds valid nexthop it will call + * spf_add_parents, which will flush the old + * parents. + */ + vertex_pqueue_del(candidate, w); + ospf_nexthop_calculation(area, v, w, l, + distance, lsa_pos); + vertex_pqueue_add(candidate, w); + } + } /* end W is already on the candidate list */ + } /* end loop over the links in V's LSA */ +} + +static void ospf_spf_dump(struct vertex *v, int i) +{ + struct listnode *cnode; + struct listnode *nnode; + struct vertex_parent *parent; + + if (v->type == OSPF_VERTEX_ROUTER) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SPF Result: %d [R] %pI4", i, + &v->lsa->id); + } else { + struct network_lsa *lsa = (struct network_lsa *)v->lsa; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SPF Result: %d [N] %pI4/%d", i, + &v->lsa->id, + ip_masklen(lsa->mask)); + } + + if (IS_DEBUG_OSPF_EVENT) + for (ALL_LIST_ELEMENTS_RO(v->parents, nnode, parent)) { + zlog_debug(" nexthop %p %pI4 %d", + (void *)parent->nexthop, + &parent->nexthop->router, + parent->nexthop->lsa_pos); + } + + i++; + + for (ALL_LIST_ELEMENTS_RO(v->children, cnode, v)) + ospf_spf_dump(v, i); +} + +void ospf_spf_print(struct vty *vty, struct vertex *v, int i) +{ + struct listnode *cnode; + struct listnode *nnode; + struct vertex_parent *parent; + + if (v->type == OSPF_VERTEX_ROUTER) { + vty_out(vty, "SPF Result: depth %d [R] %pI4\n", i, &v->lsa->id); + } else { + struct network_lsa *lsa = (struct network_lsa *)v->lsa; + vty_out(vty, "SPF Result: depth %d [N] %pI4/%d\n", i, + &v->lsa->id, ip_masklen(lsa->mask)); + } + + for (ALL_LIST_ELEMENTS_RO(v->parents, nnode, parent)) { + vty_out(vty, + " nexthop %pI4 lsa pos %d -- local nexthop %pI4 lsa pos %d\n", + &parent->nexthop->router, parent->nexthop->lsa_pos, + &parent->local_nexthop->router, + parent->local_nexthop->lsa_pos); + } + + i++; + + for (ALL_LIST_ELEMENTS_RO(v->children, cnode, v)) + ospf_spf_print(vty, v, i); +} + +/* Second stage of SPF calculation. */ +static void ospf_spf_process_stubs(struct ospf_area *area, struct vertex *v, + struct route_table *rt, int parent_is_root) +{ + struct listnode *cnode, *cnnode; + struct vertex *child; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: processing stubs for area %pI4", __func__, + &area->area_id); + + if (v->type == OSPF_VERTEX_ROUTER) { + uint8_t *p; + uint8_t *lim; + struct router_lsa_link *l; + struct router_lsa *router_lsa; + int lsa_pos = 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: processing router LSA, id: %pI4", + __func__, &v->lsa->id); + + router_lsa = (struct router_lsa *)v->lsa; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: we have %d links to process", __func__, + ntohs(router_lsa->links)); + + p = ((uint8_t *)v->lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)v->lsa) + ntohs(v->lsa->length); + + while (p < lim) { + l = (struct router_lsa_link *)p; + + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* Don't process TI-LFA protected resources */ + if (l->m[0].type == LSA_LINK_TYPE_STUB + && !ospf_spf_is_protected_resource(area, l, v->lsa)) + ospf_intra_add_stub(rt, l, v, area, + parent_is_root, lsa_pos); + lsa_pos++; + } + } + + ospf_vertex_dump("ospf_process_stubs(): after examining links: ", v, 1, + 1); + + for (ALL_LIST_ELEMENTS(v->children, cnode, cnnode, child)) { + if (CHECK_FLAG(child->flags, OSPF_VERTEX_PROCESSED)) + continue; + + /* + * The first level of routers connected to the root + * should have 'parent_is_root' set, including those + * connected via a network vertex. + */ + if (area->spf == v) + parent_is_root = 1; + else if (v->type == OSPF_VERTEX_ROUTER) + parent_is_root = 0; + + ospf_spf_process_stubs(area, child, rt, parent_is_root); + + SET_FLAG(child->flags, OSPF_VERTEX_PROCESSED); + } +} + +void ospf_rtrs_free(struct route_table *rtrs) +{ + struct route_node *rn; + struct list *or_list; + struct ospf_route * or ; + struct listnode *node, *nnode; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Route: Router Routing Table free"); + + for (rn = route_top(rtrs); rn; rn = route_next(rn)) + if ((or_list = rn->info) != NULL) { + for (ALL_LIST_ELEMENTS(or_list, node, nnode, or)) + ospf_route_free(or); + + list_delete(&or_list); + + /* Unlock the node. */ + rn->info = NULL; + route_unlock_node(rn); + } + + route_table_finish(rtrs); +} + +void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list) +{ + /* + * Free nexthop information, canonical versions of which are + * attached the first level of router vertices attached to the + * root vertex, see ospf_nexthop_calculation. + */ + if (spf) + ospf_canonical_nexthops_free(spf); + + /* Free SPF vertices list with deconstructor ospf_vertex_free. */ + if (vertex_list) + list_delete(&vertex_list); +} + +/* Calculating the shortest-path tree for an area, see RFC2328 16.1. */ +void ospf_spf_calculate(struct ospf_area *area, struct ospf_lsa *root_lsa, + struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs, bool is_dry_run, + bool is_root_node) +{ + struct vertex_pqueue_head candidate; + struct vertex *v; + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: Start: running Dijkstra for area %pI4", + __func__, &area->area_id); + } + + /* + * If the router LSA of the root is not yet allocated, return this + * area's calculation. In the 'usual' case the root_lsa is the + * self-originated router LSA of the node itself. + */ + if (!root_lsa) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Skip area %pI4's calculation due to empty root LSA", + __func__, &area->area_id); + return; + } + + /* Initialize the algorithm's data structures, see RFC2328 16.1. (1). */ + + /* + * This function scans all the LSA database and set the stat field to + * LSA_SPF_NOT_EXPLORED. + */ + lsdb_clean_stat(area->lsdb); + + /* Create a new heap for the candidates. */ + vertex_pqueue_init(&candidate); + + /* + * Initialize the shortest-path tree to only the root (which is usually + * the router doing the calculation). + */ + ospf_spf_init(area, root_lsa, is_dry_run, is_root_node); + + /* Set Area A's TransitCapability to false. */ + area->transit = OSPF_TRANSIT_FALSE; + area->shortcut_capability = 1; + + /* + * Use the root vertex for the start of the SPF algorithm and make it + * part of the tree. + */ + v = area->spf; + v->lsa_p->stat = LSA_SPF_IN_SPFTREE; + + for (;;) { + /* RFC2328 16.1. (2). */ + ospf_spf_next(v, area, &candidate); + + /* RFC2328 16.1. (3). */ + v = vertex_pqueue_pop(&candidate); + if (!v) + /* No more vertices left. */ + break; + + v->lsa_p->stat = LSA_SPF_IN_SPFTREE; + + ospf_vertex_add_parent(v); + + /* RFC2328 16.1. (4). */ + if (v->type != OSPF_VERTEX_ROUTER) + ospf_intra_add_transit(new_table, v, area); + else { + if (new_rtrs) + ospf_intra_add_router(new_rtrs, v, area, false); + if (all_rtrs) + ospf_intra_add_router(all_rtrs, v, area, true); + } + + /* Iterate back to (2), see RFC2328 16.1. (5). */ + } + + if (IS_DEBUG_OSPF_EVENT) { + ospf_spf_dump(area->spf, 0); + ospf_route_table_dump(new_table); + if (all_rtrs) + ospf_router_route_table_dump(all_rtrs); + } + + /* + * Second stage of SPF calculation procedure's, add leaves to the tree + * for stub networks. + */ + ospf_spf_process_stubs(area, area->spf, new_table, 0); + + ospf_vertex_dump(__func__, area->spf, 0, 1); + + /* Increment SPF Calculation Counter. */ + area->spf_calculation++; + + monotime(&area->ospf->ts_spf); + area->ts_spf = area->ospf->ts_spf; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Stop. %zd vertices", __func__, + mtype_stats_alloc(MTYPE_OSPF_VERTEX)); +} + +void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area, + struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs) +{ + ospf_spf_calculate(area, area->router_lsa_self, new_table, all_rtrs, + new_rtrs, false, true); + + if (ospf->ti_lfa_enabled) + ospf_ti_lfa_compute(area, new_table, + ospf->ti_lfa_protection_type); + + ospf_spf_cleanup(area->spf, area->spf_vertex_list); + + area->spf = NULL; + area->spf_vertex_list = NULL; +} + +void ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs) +{ + struct ospf_area *area; + struct listnode *node, *nnode; + + /* Calculate SPF for each area. */ + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + /* Do backbone last, so as to first discover intra-area paths + * for any back-bone virtual-links */ + if (ospf->backbone && ospf->backbone == area) + continue; + + ospf_spf_calculate_area(ospf, area, new_table, all_rtrs, + new_rtrs); + } + + /* SPF for backbone, if required */ + if (ospf->backbone) + ospf_spf_calculate_area(ospf, ospf->backbone, new_table, + all_rtrs, new_rtrs); +} + +/* Worker for SPF calculation scheduler. */ +static void ospf_spf_calculate_schedule_worker(struct event *thread) +{ + struct ospf *ospf = EVENT_ARG(thread); + struct route_table *new_table, *new_rtrs; + struct route_table *all_rtrs = NULL; + struct timeval start_time, spf_start_time; + unsigned long ia_time, prune_time, rt_time; + unsigned long abr_time, total_spf_time, spf_time; + char rbuf[32]; /* reason_buf */ + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SPF: Timer (SPF calculation expire)"); + + ospf->t_spf_calc = NULL; + + ospf_vl_unapprove(ospf); + + /* Execute SPF for each area including backbone, see RFC 2328 16.1. */ + monotime(&spf_start_time); + new_table = route_table_init(); /* routing table */ + new_rtrs = route_table_init(); /* ABR/ASBR routing table */ + + /* If we have opaque enabled then track all router reachability */ + if (CHECK_FLAG(ospf->opaque, OPAQUE_OPERATION_READY_BIT)) + all_rtrs = route_table_init(); + + ospf_spf_calculate_areas(ospf, new_table, all_rtrs, new_rtrs); + spf_time = monotime_since(&spf_start_time, NULL); + + ospf_vl_shut_unapproved(ospf); + + /* Calculate inter-area routes, see RFC 2328 16.2. */ + monotime(&start_time); + ospf_ia_routing(ospf, new_table, new_rtrs); + ia_time = monotime_since(&start_time, NULL); + + /* Get rid of transit networks and routers we cannot reach anyway. */ + monotime(&start_time); + ospf_prune_unreachable_networks(new_table); + if (all_rtrs) + ospf_prune_unreachable_routers(all_rtrs); + ospf_prune_unreachable_routers(new_rtrs); + prune_time = monotime_since(&start_time, NULL); + + /* Note: RFC 2328 16.3. is apparently missing. */ + + /* + * Calculate AS external routes, see RFC 2328 16.4. + * There is a dedicated routing table for external routes which is not + * handled here directly + */ + ospf_ase_calculate_schedule(ospf); + ospf_ase_calculate_timer_add(ospf); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ospf install new route, vrf %s id %u new_table count %lu", + __func__, ospf_vrf_id_to_name(ospf->vrf_id), + ospf->vrf_id, new_table->count); + + /* Update routing table. */ + monotime(&start_time); + ospf_route_install(ospf, new_table); + rt_time = monotime_since(&start_time, NULL); + + /* Free old all routers routing table */ + if (ospf->oall_rtrs) { + ospf_rtrs_free(ospf->oall_rtrs); + ospf->oall_rtrs = NULL; + } + + /* Update all routers routing table */ + ospf->oall_rtrs = ospf->all_rtrs; + ospf->all_rtrs = all_rtrs; +#ifdef SUPPORT_OSPF_API + ospf_apiserver_notify_reachable(ospf->oall_rtrs, ospf->all_rtrs); +#endif + + /* Free old ABR/ASBR routing table */ + if (ospf->old_rtrs) { + ospf_rtrs_free(ospf->old_rtrs); + ospf->old_rtrs = NULL; + } + + /* Update ABR/ASBR routing table */ + ospf->old_rtrs = ospf->new_rtrs; + ospf->new_rtrs = new_rtrs; + + /* ABRs may require additional changes, see RFC 2328 16.7. */ + monotime(&start_time); + if (IS_OSPF_ABR(ospf)) { + if (ospf->anyNSSA) + ospf_abr_nssa_check_status(ospf); + ospf_abr_task(ospf); + } + abr_time = monotime_since(&start_time, NULL); + + /* Schedule Segment Routing update */ + ospf_sr_update_task(ospf); + + total_spf_time = + monotime_since(&spf_start_time, &ospf->ts_spf_duration); + + rbuf[0] = '\0'; + if (spf_reason_flags) { + if (spf_reason_flags & (1 << SPF_FLAG_ROUTER_LSA_INSTALL)) + strlcat(rbuf, "R, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_NETWORK_LSA_INSTALL)) + strlcat(rbuf, "N, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_SUMMARY_LSA_INSTALL)) + strlcat(rbuf, "S, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL)) + strlcat(rbuf, "AS, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_ABR_STATUS_CHANGE)) + strlcat(rbuf, "ABR, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_ASBR_STATUS_CHANGE)) + strlcat(rbuf, "ASBR, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_MAXAGE)) + strlcat(rbuf, "M, ", sizeof(rbuf)); + if (spf_reason_flags & (1 << SPF_FLAG_GR_FINISH)) + strlcat(rbuf, "GR, ", sizeof(rbuf)); + + size_t rbuflen = strlen(rbuf); + if (rbuflen >= 2) + rbuf[rbuflen - 2] = '\0'; /* skip the last ", " */ + else + rbuf[0] = '\0'; + } + + if (IS_DEBUG_OSPF_EVENT) { + zlog_info("SPF Processing Time(usecs): %ld", total_spf_time); + zlog_info(" SPF Time: %ld", spf_time); + zlog_info(" InterArea: %ld", ia_time); + zlog_info(" Prune: %ld", prune_time); + zlog_info(" RouteInstall: %ld", rt_time); + if (IS_OSPF_ABR(ospf)) + zlog_info(" ABR: %ld (%d areas)", + abr_time, ospf->areas->count); + zlog_info("Reason(s) for SPF: %s", rbuf); + } + + ospf_clear_spf_reason_flags(); +} + +/* + * Add schedule for SPF calculation. To avoid frequenst SPF calc, we set timer + * for SPF calc. + */ +void ospf_spf_calculate_schedule(struct ospf *ospf, ospf_spf_reason_t reason) +{ + unsigned long delay, elapsed, ht; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SPF: calculation timer scheduled"); + + /* OSPF instance does not exist. */ + if (ospf == NULL) + return; + + ospf_spf_set_reason(reason); + + /* SPF calculation timer is already scheduled. */ + if (ospf->t_spf_calc) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "SPF: calculation timer is already scheduled: %p", + (void *)ospf->t_spf_calc); + return; + } + + elapsed = monotime_since(&ospf->ts_spf, NULL) / 1000; + + ht = ospf->spf_holdtime * ospf->spf_hold_multiplier; + + if (ht > ospf->spf_max_holdtime) + ht = ospf->spf_max_holdtime; + + /* Get SPF calculation delay time. */ + if (elapsed < ht) { + /* + * Got an event within the hold time of last SPF. We need to + * increase the hold_multiplier, if it's not already at/past + * maximum value, and wasn't already increased. + */ + if (ht < ospf->spf_max_holdtime) + ospf->spf_hold_multiplier++; + + /* always honour the SPF initial delay */ + if ((ht - elapsed) < ospf->spf_delay) + delay = ospf->spf_delay; + else + delay = ht - elapsed; + } else { + /* Event is past required hold-time of last SPF */ + delay = ospf->spf_delay; + ospf->spf_hold_multiplier = 1; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("SPF: calculation timer delay = %ld msec", delay); + + ospf->t_spf_calc = NULL; + event_add_timer_msec(master, ospf_spf_calculate_schedule_worker, ospf, + delay, &ospf->t_spf_calc); +} + +/* Restart OSPF SPF algorithm*/ +void ospf_restart_spf(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Restart SPF.", __func__); + + /* Handling inter area and intra area routes*/ + if (ospf->new_table) { + ospf_route_delete(ospf, ospf->new_table); + ospf_route_table_free(ospf->new_table); + ospf->new_table = route_table_init(); + } + + /* Handling of TYPE-5 lsa(external routes) */ + if (ospf->old_external_route) { + ospf_route_delete(ospf, ospf->old_external_route); + ospf_route_table_free(ospf->old_external_route); + ospf->old_external_route = route_table_init(); + } + + /* Trigger SPF */ + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); +} diff --git a/ospfd/ospf_spf.h b/ospfd/ospf_spf.h new file mode 100644 index 0000000..0837748 --- /dev/null +++ b/ospfd/ospf_spf.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF calculation. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _QUAGGA_OSPF_SPF_H +#define _QUAGGA_OSPF_SPF_H + +#include "typesafe.h" + +/* values for vertex->type */ +#define OSPF_VERTEX_ROUTER 1 /* for a Router-LSA */ +#define OSPF_VERTEX_NETWORK 2 /* for a Network-LSA */ + +/* values for vertex->flags */ +#define OSPF_VERTEX_PROCESSED 0x01 + +/* The "root" is the node running the SPF calculation */ + +PREDECL_SKIPLIST_NONUNIQ(vertex_pqueue); +/* A router or network in an area */ +struct vertex { + struct vertex_pqueue_item pqi; + uint8_t flags; + uint8_t type; /* copied from LSA header */ + struct in_addr id; /* copied from LSA header */ + struct ospf_lsa *lsa_p; + struct lsa_header *lsa; /* Router or Network LSA */ + uint32_t distance; /* from root to this vertex */ + struct list *parents; /* list of parents in SPF tree */ + struct list *children; /* list of children in SPF tree*/ +}; + +struct vertex_nexthop { + struct in_addr router; /* router address to send to */ + int lsa_pos; /* LSA position for resolving the interface */ +}; + +struct vertex_parent { + struct vertex_nexthop *nexthop; /* nexthop taken on the root node */ + struct vertex_nexthop *local_nexthop; /* local nexthop of the parent */ + struct vertex *parent; /* parent vertex */ + int backlink; /* index back to parent for router-lsa's */ +}; + +/* What triggered the SPF ? */ +typedef enum { + SPF_FLAG_ROUTER_LSA_INSTALL = 1, + SPF_FLAG_NETWORK_LSA_INSTALL, + SPF_FLAG_SUMMARY_LSA_INSTALL, + SPF_FLAG_ASBR_SUMMARY_LSA_INSTALL, + SPF_FLAG_MAXAGE, + SPF_FLAG_ABR_STATUS_CHANGE, + SPF_FLAG_ASBR_STATUS_CHANGE, + SPF_FLAG_CONFIG_CHANGE, + SPF_FLAG_GR_FINISH, +} ospf_spf_reason_t; + +extern void ospf_spf_calculate_schedule(struct ospf *, ospf_spf_reason_t); +extern void ospf_spf_calculate(struct ospf_area *area, + struct ospf_lsa *root_lsa, + struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs, bool is_dry_run, + bool is_root_node); +extern void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area, + struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs); +extern void ospf_spf_calculate_areas(struct ospf *ospf, + struct route_table *new_table, + struct route_table *all_rtrs, + struct route_table *new_rtrs); +extern void ospf_rtrs_free(struct route_table *); +extern void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list); +extern void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list); +extern void ospf_spf_remove_resource(struct vertex *vertex, + struct list *vertex_list, + struct protected_resource *resource); +extern struct vertex *ospf_spf_vertex_find(struct in_addr id, + struct list *vertex_list); +extern struct vertex *ospf_spf_vertex_by_nexthop(struct vertex *root, + struct in_addr *nexthop); +extern struct vertex_parent *ospf_spf_vertex_parent_find(struct in_addr id, + struct vertex *vertex); +extern int vertex_parent_cmp(void *aa, void *bb); + +extern void ospf_spf_print(struct vty *vty, struct vertex *v, int i); +extern void ospf_restart_spf(struct ospf *ospf); +/* void ospf_spf_calculate_timer_add (); */ +#endif /* _QUAGGA_OSPF_SPF_H */ diff --git a/ospfd/ospf_sr.c b/ospfd/ospf_sr.c new file mode 100644 index 0000000..198309c --- /dev/null +++ b/ospfd/ospf_sr.c @@ -0,0 +1,2962 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing + * as per RFC 8665 - OSPF Extensions for Segment Routing + * and RFC 8476 - Signaling Maximum SID Depth (MSD) Using OSPF + * + * Module name: Segment Routing + * + * Author: Olivier Dugeon + * Author: Anselme Sawadogo + * + * Copyright (C) 2016 - 2020 Orange Labs http://www.orange.com + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "printfrr.h" +#include "command.h" +#include "hash.h" +#include "if.h" +#include "if.h" +#include "jhash.h" +#include "libospf.h" /* for ospf interface types */ +#include "linklist.h" +#include "log.h" +#include "memory.h" +#include "monotime.h" +#include "network.h" +#include "prefix.h" +#include "sockunion.h" /* for inet_aton() */ +#include "stream.h" +#include "table.h" +#include "frrevent.h" +#include "vty.h" +#include "zclient.h" +#include "sbuf.h" +#include +#include "ospf_errors.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_zebra.h" + +/* + * Global variable to manage Segment Routing on this node. + * Note that all parameter values are stored in network byte order. + */ +static struct ospf_sr_db OspfSR; +static void ospf_sr_register_vty(void); +static inline void del_adj_sid(struct sr_nhlfe nhlfe); +static int ospf_sr_start(struct ospf *ospf); + +/* + * Segment Routing Data Base functions + */ + +/* Hash function for Segment Routing entry */ +static unsigned int sr_hash(const void *p) +{ + const struct in_addr *rid = p; + + return jhash_1word(rid->s_addr, 0); +} + +/* Compare 2 Router ID hash entries based on SR Node */ +static bool sr_cmp(const void *p1, const void *p2) +{ + const struct sr_node *srn = p1; + const struct in_addr *rid = p2; + + return IPV4_ADDR_SAME(&srn->adv_router, rid); +} + +/* Functions to remove an SR Link */ +static void del_sr_link(void *val) +{ + struct sr_link *srl = (struct sr_link *)val; + + del_adj_sid(srl->nhlfe[0]); + del_adj_sid(srl->nhlfe[1]); + XFREE(MTYPE_OSPF_SR_PARAMS, val); +} + +/* Functions to remove an SR Prefix */ +static void del_sr_pref(void *val) +{ + struct sr_prefix *srp = (struct sr_prefix *)val; + + ospf_zebra_delete_prefix_sid(srp); + XFREE(MTYPE_OSPF_SR_PARAMS, val); +} + +/* Allocate new Segment Routine node */ +static struct sr_node *sr_node_new(struct in_addr *rid) +{ + + if (rid == NULL) + return NULL; + + struct sr_node *new; + + /* Allocate Segment Routing node memory */ + new = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_node)); + + /* Default Algorithm, SRGB and MSD */ + for (int i = 0; i < ALGORITHM_COUNT; i++) + new->algo[i] = SR_ALGORITHM_UNSET; + + new->srgb.range_size = 0; + new->srgb.lower_bound = 0; + new->msd = 0; + + /* Create Link, Prefix and Range TLVs list */ + new->ext_link = list_new(); + new->ext_prefix = list_new(); + new->ext_link->del = del_sr_link; + new->ext_prefix->del = del_sr_pref; + + IPV4_ADDR_COPY(&new->adv_router, rid); + new->neighbor = NULL; + new->instance = 0; + + osr_debug(" |- Created new SR node for %pI4", &new->adv_router); + return new; +} + +/* Supposed to be used for testing */ +struct sr_node *ospf_sr_node_create(struct in_addr *rid) +{ + struct sr_node *srn; + + srn = hash_get(OspfSR.neighbors, (void *)rid, (void *)sr_node_new); + + return srn; +} + +/* Delete Segment Routing node */ +static void sr_node_del(struct sr_node *srn) +{ + /* Sanity Check */ + if (srn == NULL) + return; + + osr_debug(" |- Delete SR node for %pI4", &srn->adv_router); + + /* Clean Extended Link */ + list_delete(&srn->ext_link); + + /* Clean Prefix List */ + list_delete(&srn->ext_prefix); + + XFREE(MTYPE_OSPF_SR_PARAMS, srn); +} + +/* Get SR Node for a given nexthop */ +static struct sr_node *get_sr_node_by_nexthop(struct ospf *ospf, + struct in_addr nexthop) +{ + struct ospf_interface *oi = NULL; + struct ospf_neighbor *nbr = NULL; + struct listnode *node; + struct route_node *rn; + struct sr_node *srn; + bool found; + + /* Sanity check */ + if (OspfSR.neighbors == NULL) + return NULL; + + osr_debug(" |- Search SR-Node for nexthop %pI4", &nexthop); + + /* First, search neighbor Router ID for this nexthop */ + found = false; + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if ((nbr) && (IPV4_ADDR_SAME(&nexthop, &nbr->src))) { + found = true; + break; + } + } + if (found) + break; + } + + if (!found) + return NULL; + + osr_debug(" |- Found nexthop Router ID %pI4", &nbr->router_id); + + /* Then, search SR Node */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, &nbr->router_id); + + return srn; +} + +/* + * Segment Routing Local Block management functions + */ + +/** + * It is necessary to known which label is already allocated to manage the range + * of SRLB. This is particular useful when an interface flap (goes up / down + * frequently). Here, SR will release and then allocate label for the Adjacency + * for each concerned interface. If we don't care, there is a risk to run out of + * label. + * + * For that purpose, a similar principle as already provided to manage chunk of + * label is proposed. But, here, the label chunk has not a fix range of 64 + * labels that could be easily manage with a single variable of 64 bits size. + * So, used_mark is used as a bit wise to mark label reserved (bit set) or not + * (bit unset). Its size is equal to the number of label of the SRLB range round + * up to 64 bits. + * + * - sr__local_block_init() computes the number of 64 bits variables that are + * needed to manage the SRLB range and allocates this number. + * - ospf_sr_local_block_request_label() pick up the first available label and + * set corresponding bit + * - ospf_sr_local_block_release_label() release label by reseting the + * corresponding bit and set the next label to the first free position + */ + +/** + * Initialize Segment Routing Local Block from SRDB configuration and reserve + * block of bits to manage label allocation. + * + * @param lower_bound The lower bound of the SRLB range + * @param upper_bound The upper bound of the SRLB range + * + * @return 0 on success, -1 otherwise + */ +static int sr_local_block_init(uint32_t lower_bound, uint32_t upper_bound) +{ + struct sr_local_block *srlb = &OspfSR.srlb; + uint32_t size; + + /* Check if SRLB is not already configured */ + if (srlb->reserved) + return 0; + + /* + * Request SRLB to the label manager. If the allocation fails, return + * an error to disable SR until a new SRLB is successfully allocated. + */ + size = upper_bound - lower_bound + 1; + if (ospf_zebra_request_label_range(lower_bound, size)) { + zlog_err("SR: Error reserving SRLB [%u/%u] %u labels", + lower_bound, upper_bound, size); + return -1; + } + + osr_debug("SR: Got new SRLB [%u/%u], %u labels", lower_bound, + upper_bound, size); + + /* Initialize the SRLB */ + srlb->start = lower_bound; + srlb->end = upper_bound; + srlb->current = 0; + + /* Compute the needed Used Mark number and allocate them */ + srlb->max_block = size / SRLB_BLOCK_SIZE; + if ((size % SRLB_BLOCK_SIZE) != 0) + srlb->max_block++; + srlb->used_mark = XCALLOC(MTYPE_OSPF_SR_PARAMS, + srlb->max_block * SRLB_BLOCK_SIZE); + srlb->reserved = true; + + return 0; +} + +static int sr_global_block_init(uint32_t start, uint32_t size) +{ + struct sr_global_block *srgb = &OspfSR.srgb; + + /* Check if already configured */ + if (srgb->reserved) + return 0; + + /* request chunk */ + uint32_t end = start + size - 1; + if (ospf_zebra_request_label_range(start, size) < 0) { + zlog_err("SR: Error reserving SRGB [%u/%u], %u labels", start, + end, size); + return -1; + } + + osr_debug("SR: Got new SRGB [%u/%u], %u labels", start, end, size); + + /* success */ + srgb->start = start; + srgb->size = size; + srgb->reserved = true; + return 0; +} + +/** + * Remove Segment Routing Local Block. + * + */ +static void sr_local_block_delete(void) +{ + struct sr_local_block *srlb = &OspfSR.srlb; + + /* Check if SRLB is not already delete */ + if (!srlb->reserved) + return; + + osr_debug("SR (%s): Remove SRLB [%u/%u]", __func__, srlb->start, + srlb->end); + + /* First release the label block */ + ospf_zebra_release_label_range(srlb->start, srlb->end); + + /* Then reset SRLB structure */ + if (srlb->used_mark != NULL) + XFREE(MTYPE_OSPF_SR_PARAMS, srlb->used_mark); + + srlb->reserved = false; +} + +/** + * Remove Segment Routing Global block + */ +static void sr_global_block_delete(void) +{ + struct sr_global_block *srgb = &OspfSR.srgb; + + if (!srgb->reserved) + return; + + osr_debug("SR (%s): Remove SRGB [%u/%u]", __func__, srgb->start, + srgb->start + srgb->size - 1); + + ospf_zebra_release_label_range(srgb->start, + srgb->start + srgb->size - 1); + + srgb->reserved = false; +} + + +/** + * Request a label from the Segment Routing Local Block. + * + * @return First available label on success or MPLS_INVALID_LABEL if the + * block of labels is full + */ +mpls_label_t ospf_sr_local_block_request_label(void) +{ + struct sr_local_block *srlb = &OspfSR.srlb; + mpls_label_t label; + uint32_t index; + uint32_t pos; + uint32_t size = srlb->end - srlb->start + 1; + + /* Check if we ran out of available labels */ + if (srlb->current >= size) + return MPLS_INVALID_LABEL; + + /* Get first available label and mark it used */ + label = srlb->current + srlb->start; + index = srlb->current / SRLB_BLOCK_SIZE; + pos = 1ULL << (srlb->current % SRLB_BLOCK_SIZE); + srlb->used_mark[index] |= pos; + + /* Jump to the next free position */ + srlb->current++; + pos = srlb->current % SRLB_BLOCK_SIZE; + while (srlb->current < size) { + if (pos == 0) + index++; + if (!((1ULL << pos) & srlb->used_mark[index])) + break; + else { + srlb->current++; + pos = srlb->current % SRLB_BLOCK_SIZE; + } + } + + if (srlb->current == size) + zlog_warn( + "SR: Warning, SRLB is depleted and next label request will fail"); + + return label; +} + +/** + * Release label in the Segment Routing Local Block. + * + * @param label Label to be release + * + * @return 0 on success or -1 if label falls outside SRLB + */ +int ospf_sr_local_block_release_label(mpls_label_t label) +{ + struct sr_local_block *srlb = &OspfSR.srlb; + uint32_t index; + uint32_t pos; + + /* Check that label falls inside the SRLB */ + if ((label < srlb->start) || (label > srlb->end)) { + flog_warn(EC_OSPF_SR_SID_OVERFLOW, + "%s: Returning label %u is outside SRLB [%u/%u]", + __func__, label, srlb->start, srlb->end); + return -1; + } + + index = (label - srlb->start) / SRLB_BLOCK_SIZE; + pos = 1ULL << ((label - srlb->start) % SRLB_BLOCK_SIZE); + srlb->used_mark[index] &= ~pos; + /* Reset current to the first available position */ + for (index = 0; index < srlb->max_block; index++) { + if (srlb->used_mark[index] != 0xFFFFFFFFFFFFFFFF) { + for (pos = 0; pos < SRLB_BLOCK_SIZE; pos++) + if (!((1ULL << pos) & srlb->used_mark[index])) { + srlb->current = + index * SRLB_BLOCK_SIZE + pos; + break; + } + break; + } + } + + return 0; +} + +/* + * Segment Routing Initialization functions + */ + +/** + * Thread function to re-attempt connection to the Label Manager and thus be + * able to start Segment Routing. + * + * @param start Thread structure that contains area as argument + * + * @return 1 on success + */ +static void sr_start_label_manager(struct event *start) +{ + struct ospf *ospf; + + ospf = EVENT_ARG(start); + + /* re-attempt to start SR & Label Manager connection */ + ospf_sr_start(ospf); +} + +/* Segment Routing starter function */ +static int ospf_sr_start(struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + struct sr_node *srn; + int rc = 0; + + osr_debug("SR (%s): Start Segment Routing", __func__); + + /* Initialize self SR Node if not already done */ + if (OspfSR.self == NULL) { + srn = hash_get(OspfSR.neighbors, (void *)&(ospf->router_id), + (void *)sr_node_new); + + /* Complete & Store self SR Node */ + srn->srgb.range_size = OspfSR.srgb.size; + srn->srgb.lower_bound = OspfSR.srgb.start; + srn->srlb.lower_bound = OspfSR.srlb.start; + srn->srlb.range_size = OspfSR.srlb.end - OspfSR.srlb.start + 1; + srn->algo[0] = OspfSR.algo[0]; + srn->msd = OspfSR.msd; + OspfSR.self = srn; + } + + /* Then, start Label Manager if not ready */ + if (!ospf_zebra_label_manager_ready()) + if (ospf_zebra_label_manager_connect() < 0) { + /* Re-attempt to connect to Label Manager in 1 sec. */ + event_add_timer(master, sr_start_label_manager, ospf, 1, + &OspfSR.t_start_lm); + osr_debug(" |- Failed to start the Label Manager"); + return -1; + } + + /* + * Request SRLB & SGRB to the label manager if not already reserved. + * If the allocation fails, return an error to disable SR until a new + * SRLB and/or SRGB are successfully allocated. + */ + if (sr_local_block_init(OspfSR.srlb.start, OspfSR.srlb.end) < 0) + return -1; + + if (sr_global_block_init(OspfSR.srgb.start, OspfSR.srgb.size) < 0) + return -1; + + /* SR is UP and ready to flood LSA */ + OspfSR.status = SR_UP; + + /* Set Router Information SR parameters */ + osr_debug("SR: Activate SR for Router Information LSA"); + + ospf_router_info_update_sr(true, OspfSR.self); + + /* Update Ext LSA */ + osr_debug("SR: Activate SR for Extended Link/Prefix LSA"); + + ospf_ext_update_sr(true); + + osr_debug("SR (%s): Update SR-DB from LSDB", __func__); + + /* Start by looking to Router Info & Extended LSA in lsdb */ + if ((ospf != NULL) && (ospf->backbone != NULL)) { + LSDB_LOOP (OPAQUE_AREA_LSDB(ospf->backbone), rn, lsa) { + if (IS_LSA_MAXAGE(lsa) || IS_LSA_SELF(lsa)) + continue; + int lsa_id = + GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + switch (lsa_id) { + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + ospf_sr_ri_lsa_update(lsa); + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + ospf_sr_ext_prefix_lsa_update(lsa); + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + ospf_sr_ext_link_lsa_update(lsa); + break; + default: + break; + } + } + } + + rc = 1; + return rc; +} + +/* Stop Segment Routing */ +static void ospf_sr_stop(void) +{ + + if (OspfSR.status == SR_OFF) + return; + + osr_debug("SR (%s): Stop Segment Routing", __func__); + + /* Disable any re-attempt to connect to Label Manager */ + EVENT_OFF(OspfSR.t_start_lm); + + /* Release SRGB if active */ + sr_global_block_delete(); + + /* Release SRLB if active */ + sr_local_block_delete(); + + /* + * Remove all SR Nodes from the Hash table. Prefix and Link SID will + * be remove though list_delete() call. See sr_node_del() + */ + hash_clean(OspfSR.neighbors, (void *)sr_node_del); + OspfSR.self = NULL; + OspfSR.status = SR_OFF; + OspfSR.msd = 0; +} + +/* + * Segment Routing initialize function + * + * @param - nothing + * + * @return 0 if OK, -1 otherwise + */ +int ospf_sr_init(void) +{ + int rc = -1; + + osr_debug("SR (%s): Initialize SR Data Base", __func__); + + memset(&OspfSR, 0, sizeof(OspfSR)); + OspfSR.status = SR_OFF; + /* Only AREA flooding is supported in this release */ + OspfSR.scope = OSPF_OPAQUE_AREA_LSA; + + /* Initialize Algorithms, SRGB, SRLB and MSD TLVs */ + /* Only Algorithm SPF is supported */ + OspfSR.algo[0] = SR_ALGORITHM_SPF; + for (int i = 1; i < ALGORITHM_COUNT; i++) + OspfSR.algo[i] = SR_ALGORITHM_UNSET; + + OspfSR.srgb.size = DEFAULT_SRGB_SIZE; + OspfSR.srgb.start = DEFAULT_SRGB_LABEL; + OspfSR.srgb.reserved = false; + + OspfSR.srlb.start = DEFAULT_SRLB_LABEL; + OspfSR.srlb.end = DEFAULT_SRLB_END; + OspfSR.srlb.reserved = false; + OspfSR.msd = 0; + + /* Initialize Hash table for neighbor SR nodes */ + OspfSR.neighbors = hash_create(sr_hash, sr_cmp, "OSPF_SR"); + if (OspfSR.neighbors == NULL) + return rc; + + /* Register Segment Routing VTY command */ + ospf_sr_register_vty(); + + rc = 0; + return rc; +} + +/* + * Segment Routing termination function + * + * @param - nothing + * @return - nothing + */ +void ospf_sr_term(void) +{ + + /* Stop Segment Routing */ + ospf_sr_stop(); + + hash_clean_and_free(&OspfSR.neighbors, (void *)sr_node_del); +} + +/* + * Segment Routing finish function + * + * @param - nothing + * @return - nothing + */ +void ospf_sr_finish(void) +{ + /* Stop Segment Routing */ + ospf_sr_stop(); +} + +/* + * Following functions are used to manipulate the + * Next Hop Label Forwarding entry (NHLFE) + */ + +/* Compute label from index */ +static mpls_label_t index2label(uint32_t index, struct sr_block srgb) +{ + mpls_label_t label; + + label = srgb.lower_bound + index; + if (label > (srgb.lower_bound + srgb.range_size)) { + flog_warn(EC_OSPF_SR_SID_OVERFLOW, + "%s: SID index %u falls outside SRGB range", + __func__, index); + return MPLS_INVALID_LABEL; + } else + return label; +} + +/* Get the prefix sid for a specific router id */ +mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id) +{ + struct sr_node *srn; + struct sr_prefix *srp; + mpls_label_t label; + + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, id); + + if (srn) { + /* + * TODO: Here we assume that the SRGBs are the same, + * and that the node's prefix SID is at the head of + * the list, probably needs tweaking. + */ + srp = listnode_head(srn->ext_prefix); + label = index2label(srp->sid, srn->srgb); + } else { + label = MPLS_INVALID_LABEL; + } + + return label; +} + +/* Get the adjacency sid for a specific 'root' id and 'neighbor' id */ +mpls_label_t ospf_sr_get_adj_sid_by_id(struct in_addr *root_id, + struct in_addr *neighbor_id) +{ + struct sr_node *srn; + struct sr_link *srl; + mpls_label_t label; + struct listnode *node; + + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, root_id); + + label = MPLS_INVALID_LABEL; + + if (srn) { + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) { + if (srl->type == ADJ_SID + && srl->remote_id.s_addr == neighbor_id->s_addr) { + label = srl->sid[0]; + break; + } + } + } + + return label; +} + +/* Get neighbor full structure from address */ +static struct ospf_neighbor *get_neighbor_by_addr(struct ospf *top, + struct in_addr addr) +{ + struct ospf_neighbor *nbr; + struct ospf_interface *oi; + struct listnode *node; + struct route_node *rn; + + /* Sanity Check */ + if (top == NULL) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(top->oiflist, node, oi)) + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (!nbr) + continue; + + if (IPV4_ADDR_SAME(&nbr->address.u.prefix4, &addr) || + IPV4_ADDR_SAME(&nbr->router_id, &addr)) { + route_unlock_node(rn); + return nbr; + } + } + return NULL; +} + +/* Get OSPF Path from address */ +static struct ospf_route *get_nexthop_by_addr(struct ospf *top, + struct prefix_ipv4 p) +{ + struct route_node *rn; + + /* Sanity Check */ + if (top == NULL) + return NULL; + + osr_debug(" |- Search Nexthop for prefix %pFX", + (struct prefix *)&p); + + rn = route_node_lookup(top->new_table, (struct prefix *)&p); + + /* + * Check if we found an OSPF route. May be NULL if SPF has not + * yet populate routing table for this prefix. + */ + if (rn == NULL) + return NULL; + + route_unlock_node(rn); + return rn->info; +} + +/* Compute NHLFE entry for Extended Link */ +static int compute_link_nhlfe(struct sr_link *srl) +{ + struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct ospf_neighbor *nh; + int rc = 0; + + osr_debug(" |- Compute NHLFE for link %pI4", &srl->itf_addr); + + /* First determine the OSPF Neighbor */ + nh = get_neighbor_by_addr(top, srl->nhlfe[0].nexthop); + + /* Neighbor could be not found when OSPF Adjacency just fire up + * because SPF don't yet populate routing table. This NHLFE will + * be fixed later when SR SPF schedule will be called. + */ + if (nh == NULL) + return rc; + + osr_debug(" |- Found nexthop %pI4", &nh->router_id); + + /* Set ifindex for this neighbor */ + srl->nhlfe[0].ifindex = nh->oi->ifp->ifindex; + srl->nhlfe[1].ifindex = nh->oi->ifp->ifindex; + + /* Update neighbor address for LAN_ADJ_SID */ + if (srl->type == LAN_ADJ_SID) { + IPV4_ADDR_COPY(&srl->nhlfe[0].nexthop, &nh->src); + IPV4_ADDR_COPY(&srl->nhlfe[1].nexthop, &nh->src); + } + + /* Set Input & Output Label */ + if (CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->nhlfe[0].label_in = srl->sid[0]; + else + srl->nhlfe[0].label_in = + index2label(srl->sid[0], srl->srn->srgb); + if (CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->nhlfe[1].label_in = srl->sid[1]; + else + srl->nhlfe[1].label_in = + index2label(srl->sid[1], srl->srn->srgb); + + srl->nhlfe[0].label_out = MPLS_LABEL_IMPLICIT_NULL; + srl->nhlfe[1].label_out = MPLS_LABEL_IMPLICIT_NULL; + + rc = 1; + return rc; +} + +/** + * Compute output label for the given Prefix-SID. + * + * @param srp Segment Routing Prefix + * @param srnext Segment Routing nexthop node + * + * @return MPLS label or MPLS_INVALID_LABEL in case of error + */ +static mpls_label_t sr_prefix_out_label(const struct sr_prefix *srp, + const struct sr_node *srnext) +{ + /* Check if the nexthop SR Node is the last hop? */ + if (srnext == srp->srn) { + /* SR-Node doesn't request NO-PHP. Return Implicit NULL label */ + if (!CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) + return MPLS_LABEL_IMPLICIT_NULL; + + /* SR-Node requests Explicit NULL Label */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_EFLG)) + return MPLS_LABEL_IPV4_EXPLICIT_NULL; + /* Fallthrough */ + } + + /* Return SID value as MPLS label if it is an Absolute SID */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG + | EXT_SUBTLV_PREFIX_SID_LFLG)) { + /* + * V/L SIDs have local significance, so only adjacent routers + * can use them (RFC8665 section #5) + */ + if (srp->srn != srnext) + return MPLS_INVALID_LABEL; + return srp->sid; + } + + /* Return MPLS label as SRGB lower bound + SID index as per RFC 8665 */ + return (index2label(srp->sid, srnext->srgb)); +} + +/* + * Compute NHLFE entry for Extended Prefix + * + * @param srp - Segment Routing Prefix + * + * @return -1 if no route is found, 0 if there is no SR route ready + * and 1 if success or update + */ +static int compute_prefix_nhlfe(struct sr_prefix *srp) +{ + struct ospf *top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + struct ospf_path *path; + struct listnode *node; + struct sr_node *srnext; + int rc = -1; + + osr_debug(" |- Compute NHLFE for prefix %pFX", + (struct prefix *)&srp->prefv4); + + + /* First determine the nexthop */ + srp->route = get_nexthop_by_addr(top, srp->prefv4); + + /* Nexthop could be not found when OSPF Adjacency just fire up + * because SPF don't yet populate routing table. This NHLFE will + * be fixed later when SR SPF schedule will be called. + */ + if (srp->route == NULL) + return rc; + + /* Compute Input Label with self SRGB */ + srp->label_in = index2label(srp->sid, OspfSR.self->srgb); + + rc = 0; + for (ALL_LIST_ELEMENTS_RO(srp->route->paths, node, path)) { + + osr_debug(" |- Process new route via %pI4 for this prefix", + &path->nexthop); + + /* + * Get SR-Node for this nexthop. Could be not yet available + * as Extended Link / Prefix and Router Information are flooded + * after LSA Type 1 & 2 which populate the OSPF Route Table + */ + srnext = get_sr_node_by_nexthop(top, path->nexthop); + if (srnext == NULL) + continue; + + /* And store this information for later update */ + srnext->neighbor = OspfSR.self; + path->srni.nexthop = srnext; + + /* + * SR Node could be known, but SRGB could be not initialize + * This is due to the fact that Extended Link / Prefix could + * be received before corresponding Router Information LSA + */ + if (srnext == NULL || srnext->srgb.lower_bound == 0 + || srnext->srgb.range_size == 0) { + osr_debug( + " |- SR-Node %pI4 not ready. Stop process", + &srnext->adv_router); + path->srni.label_out = MPLS_INVALID_LABEL; + continue; + } + + osr_debug(" |- Found SRGB %u/%u for next hop SR-Node %pI4", + srnext->srgb.range_size, srnext->srgb.lower_bound, + &srnext->adv_router); + + /* Compute Output Label with Nexthop SR Node SRGB */ + path->srni.label_out = sr_prefix_out_label(srp, srnext); + + osr_debug(" |- Computed new labels in: %u out: %u", + srp->label_in, path->srni.label_out); + rc = 1; + } + return rc; +} + +/* Add new NHLFE entry for Adjacency SID */ +static inline void add_adj_sid(struct sr_nhlfe nhlfe) +{ + if (nhlfe.label_in != 0) + ospf_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_ADD, nhlfe); +} + +/* Remove NHLFE entry for Adjacency SID */ +static inline void del_adj_sid(struct sr_nhlfe nhlfe) +{ + if (nhlfe.label_in != 0) + ospf_zebra_send_adjacency_sid(ZEBRA_MPLS_LABELS_DELETE, nhlfe); +} + +/* Update NHLFE entry for Adjacency SID */ +static inline void update_adj_sid(struct sr_nhlfe n1, struct sr_nhlfe n2) +{ + del_adj_sid(n1); + add_adj_sid(n2); +} + +/* + * Functions to parse and get Extended Link / Prefix + * TLVs and SubTLVs + */ + +/* Extended Link SubTLVs Getter */ +static struct sr_link *get_ext_link_sid(struct tlv_header *tlvh, size_t size) +{ + + struct sr_link *srl; + struct ext_tlv_link *link = (struct ext_tlv_link *)tlvh; + struct ext_subtlv_adj_sid *adj_sid; + struct ext_subtlv_lan_adj_sid *lan_sid; + struct ext_subtlv_rmt_itf_addr *rmt_itf; + + struct tlv_header *sub_tlvh; + uint16_t length = 0, sum = 0, i = 0; + + /* Check TLV size */ + if ((ntohs(tlvh->length) > size) + || ntohs(tlvh->length) < EXT_TLV_LINK_SIZE) { + zlog_warn("Wrong Extended Link TLV size. Abort!"); + return NULL; + } + + srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); + + /* Initialize TLV browsing */ + length = ntohs(tlvh->length) - EXT_TLV_LINK_SIZE; + sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + + EXT_TLV_LINK_SIZE); + for (; sum < length && sub_tlvh; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { + switch (ntohs(sub_tlvh->type)) { + case EXT_SUBTLV_ADJ_SID: + adj_sid = (struct ext_subtlv_adj_sid *)sub_tlvh; + srl->type = ADJ_SID; + i = CHECK_FLAG(adj_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) + ? 1 + : 0; + srl->flags[i] = adj_sid->flags; + if (CHECK_FLAG(adj_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[i] = GET_LABEL(ntohl(adj_sid->value)); + else + srl->sid[i] = ntohl(adj_sid->value); + IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, &link->link_id); + break; + case EXT_SUBTLV_LAN_ADJ_SID: + lan_sid = (struct ext_subtlv_lan_adj_sid *)sub_tlvh; + srl->type = LAN_ADJ_SID; + i = CHECK_FLAG(lan_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) + ? 1 + : 0; + srl->flags[i] = lan_sid->flags; + if (CHECK_FLAG(lan_sid->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[i] = GET_LABEL(ntohl(lan_sid->value)); + else + srl->sid[i] = ntohl(lan_sid->value); + IPV4_ADDR_COPY(&srl->nhlfe[i].nexthop, + &lan_sid->neighbor_id); + break; + case EXT_SUBTLV_RMT_ITF_ADDR: + rmt_itf = (struct ext_subtlv_rmt_itf_addr *)sub_tlvh; + IPV4_ADDR_COPY(&srl->nhlfe[0].nexthop, &rmt_itf->value); + IPV4_ADDR_COPY(&srl->nhlfe[1].nexthop, &rmt_itf->value); + break; + default: + break; + } + sum += TLV_SIZE(sub_tlvh); + } + + IPV4_ADDR_COPY(&srl->itf_addr, &link->link_data); + + osr_debug(" |- Found primary %u and backup %u Adj/Lan Sid for %pI4", + srl->sid[0], srl->sid[1], &srl->itf_addr); + + return srl; +} + +/* Extended Prefix SubTLVs Getter */ +static struct sr_prefix *get_ext_prefix_sid(struct tlv_header *tlvh, + size_t size) +{ + + struct sr_prefix *srp; + struct ext_tlv_prefix *pref = (struct ext_tlv_prefix *)tlvh; + struct ext_subtlv_prefix_sid *psid; + + struct tlv_header *sub_tlvh; + uint16_t length = 0, sum = 0; + + /* Check TLV size */ + if ((ntohs(tlvh->length) > size) + || ntohs(tlvh->length) < EXT_TLV_PREFIX_SIZE) { + zlog_warn("Wrong Extended Link TLV size. Abort!"); + return NULL; + } + + srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + + /* Initialize TLV browsing */ + length = ntohs(tlvh->length) - EXT_TLV_PREFIX_SIZE; + sub_tlvh = (struct tlv_header *)((char *)(tlvh) + TLV_HDR_SIZE + + EXT_TLV_PREFIX_SIZE); + for (; sum < length && sub_tlvh; sub_tlvh = TLV_HDR_NEXT(sub_tlvh)) { + switch (ntohs(sub_tlvh->type)) { + case EXT_SUBTLV_PREFIX_SID: + psid = (struct ext_subtlv_prefix_sid *)sub_tlvh; + if (psid->algorithm != SR_ALGORITHM_SPF) { + flog_err(EC_OSPF_INVALID_ALGORITHM, + "SR (%s): Unsupported Algorithm", + __func__); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + return NULL; + } + srp->type = PREF_SID; + srp->flags = psid->flags; + if (CHECK_FLAG(psid->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + srp->sid = GET_LABEL(ntohl(psid->value)); + else + srp->sid = ntohl(psid->value); + IPV4_ADDR_COPY(&srp->prefv4.prefix, &pref->address); + srp->prefv4.prefixlen = pref->pref_length; + srp->prefv4.family = AF_INET; + apply_mask_ipv4(&srp->prefv4); + break; + default: + break; + } + sum += TLV_SIZE(sub_tlvh); + } + + osr_debug(" |- Found SID %u for prefix %pFX", srp->sid, + (struct prefix *)&srp->prefv4); + + return srp; +} + +/* + * Functions to manipulate Segment Routing Link & Prefix structures + */ + +/* Compare two Segment Link: return 0 if equal, 1 otherwise */ +static inline int sr_link_cmp(struct sr_link *srl1, struct sr_link *srl2) +{ + if ((srl1->sid[0] == srl2->sid[0]) && (srl1->sid[1] == srl2->sid[1]) + && (srl1->type == srl2->type) && (srl1->flags[0] == srl2->flags[0]) + && (srl1->flags[1] == srl2->flags[1])) + return 0; + else + return 1; +} + +/* Compare two Segment Prefix: return 0 if equal, 1 otherwise */ +static inline int sr_prefix_cmp(struct sr_prefix *srp1, struct sr_prefix *srp2) +{ + if ((srp1->sid == srp2->sid) && (srp1->flags == srp2->flags)) + return 0; + else + return 1; +} + +/* Update Segment Link of given Segment Routing Node */ +static void update_ext_link_sid(struct sr_node *srn, struct sr_link *srl, + uint8_t lsa_flags) +{ + struct listnode *node; + struct sr_link *lk; + bool found = false; + bool config = true; + + /* Sanity check */ + if ((srn == NULL) || (srl == NULL)) + return; + + osr_debug(" |- Process Extended Link Adj/Lan-SID"); + + /* Detect if Adj/Lan_Adj SID must be configured */ + if (!CHECK_FLAG(lsa_flags, OSPF_LSA_SELF) + && (CHECK_FLAG(srl->flags[0], EXT_SUBTLV_LINK_ADJ_SID_LFLG) + || CHECK_FLAG(srl->flags[1], EXT_SUBTLV_LINK_ADJ_SID_LFLG))) + config = false; + + /* Search for existing Segment Link */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, lk)) + if (lk->instance == srl->instance) { + found = true; + break; + } + + osr_debug(" |- %s SR Link 8.0.0.%u for SR node %pI4", + found ? "Update" : "Add", GET_OPAQUE_ID(srl->instance), + &srn->adv_router); + + /* if not found, add new Segment Link and install NHLFE */ + if (!found) { + /* Complete SR-Link and add it to SR-Node list */ + srl->srn = srn; + IPV4_ADDR_COPY(&srl->adv_router, &srn->adv_router); + listnode_add(srn->ext_link, srl); + /* Try to set MPLS table */ + if (config && compute_link_nhlfe(srl)) { + add_adj_sid(srl->nhlfe[0]); + add_adj_sid(srl->nhlfe[1]); + } + } else { + /* Update SR-Link if they are different */ + if (sr_link_cmp(lk, srl)) { + /* Try to set MPLS table */ + if (config) { + if (compute_link_nhlfe(srl)) { + update_adj_sid(lk->nhlfe[0], + srl->nhlfe[0]); + update_adj_sid(lk->nhlfe[1], + srl->nhlfe[1]); + } else { + del_adj_sid(lk->nhlfe[0]); + del_adj_sid(lk->nhlfe[1]); + } + } + /* Replace SR-Link in SR-Node Adjacency List */ + listnode_delete(srn->ext_link, lk); + XFREE(MTYPE_OSPF_SR_PARAMS, lk); + srl->srn = srn; + IPV4_ADDR_COPY(&srl->adv_router, &srn->adv_router); + listnode_add(srn->ext_link, srl); + } else { + /* + * This is just an LSA refresh. + * Stop processing and free SR Link + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } + } +} + +/* Update Segment Prefix of given Segment Routing Node */ +static void update_ext_prefix_sid(struct sr_node *srn, struct sr_prefix *srp) +{ + + struct listnode *node; + struct sr_prefix *pref; + bool found = false; + + /* Sanity check */ + if (srn == NULL || srp == NULL) + return; + + osr_debug(" |- Process Extended Prefix SID %u", srp->sid); + + /* Process only Global Prefix SID */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_LFLG)) + return; + + /* Search for existing Segment Prefix */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, pref)) + if (pref->instance == srp->instance + && prefix_same((struct prefix *)&srp->prefv4, + &pref->prefv4)) { + found = true; + break; + } + + osr_debug(" |- %s SR LSA ID 7.0.0.%u for SR node %pI4", + found ? "Update" : "Add", GET_OPAQUE_ID(srp->instance), + &srn->adv_router); + + /* Complete SR-Prefix */ + srp->srn = srn; + IPV4_ADDR_COPY(&srp->adv_router, &srn->adv_router); + + /* if not found, add new Segment Prefix and install NHLFE */ + if (!found) { + /* Add it to SR-Node list ... */ + listnode_add(srn->ext_prefix, srp); + /* ... and try to set MPLS table */ + if (compute_prefix_nhlfe(srp) == 1) + ospf_zebra_update_prefix_sid(srp); + } else { + /* + * An old SR prefix exist. Check if something changes or if it + * is just a refresh. + */ + if (sr_prefix_cmp(pref, srp)) { + if (compute_prefix_nhlfe(srp) == 1) { + ospf_zebra_delete_prefix_sid(pref); + /* Replace Segment Prefix */ + listnode_delete(srn->ext_prefix, pref); + XFREE(MTYPE_OSPF_SR_PARAMS, pref); + listnode_add(srn->ext_prefix, srp); + ospf_zebra_update_prefix_sid(srp); + } else { + /* New NHLFE was not found. + * Just free the SR Prefix + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } + } else { + /* This is just an LSA refresh. + * Stop processing and free SR Prefix + */ + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } + } +} + +/* + * When change the FRR Self SRGB, update the NHLFE Input Label + * for all Extended Prefix with SID index through hash_iterate() + */ +static void update_in_nhlfe(struct hash_bucket *bucket, void *args) +{ + struct listnode *node; + struct sr_node *srn = (struct sr_node *)bucket->data; + struct sr_prefix *srp; + + /* Process Every Extended Prefix for this SR-Node */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + /* Process Self SRN only if NO-PHP is requested */ + if ((srn == OspfSR.self) + && !CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG)) + continue; + + /* Process only SID Index */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_VFLG)) + continue; + + /* First, remove old MPLS table entries ... */ + ospf_zebra_delete_prefix_sid(srp); + /* ... then compute new input label ... */ + srp->label_in = index2label(srp->sid, OspfSR.self->srgb); + /* ... and install new MPLS LFIB */ + ospf_zebra_update_prefix_sid(srp); + } +} + +/* + * When SRGB has changed, update NHLFE Output Label for all Extended Prefix + * with SID index which use the given SR-Node as nexthop through hash_iterate() + */ +static void update_out_nhlfe(struct hash_bucket *bucket, void *args) +{ + struct listnode *node, *pnode; + struct sr_node *srn = (struct sr_node *)bucket->data; + struct sr_node *srnext = (struct sr_node *)args; + struct sr_prefix *srp; + struct ospf_path *path; + + /* Skip Self SR-Node */ + if (srn == OspfSR.self) + return; + + osr_debug("SR (%s): Update Out NHLFE for neighbor SR-Node %pI4", + __func__, &srn->adv_router); + + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + /* Skip Prefix that has not yet a valid route */ + if (srp->route == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(srp->route->paths, pnode, path)) { + /* Skip path that has not next SR-Node as nexthop */ + if (path->srni.nexthop != srnext) + continue; + + /* Compute new Output Label */ + path->srni.label_out = sr_prefix_out_label(srp, srnext); + } + + /* Finally update MPLS table */ + ospf_zebra_update_prefix_sid(srp); + } +} + +/* + * Following functions are call when new Segment Routing LSA are received + * - Router Information: ospf_sr_ri_lsa_update() & ospf_sr_ri_lsa_delete() + * - Extended Link: ospf_sr_ext_link_update() & ospf_sr_ext_link_delete() + * - Extended Prefix: ospf_ext_prefix_update() & ospf_sr_ext_prefix_delete() + */ + +/* Update Segment Routing from Router Information LSA */ +void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = lsa->data; + struct ri_sr_tlv_sid_label_range *ri_srgb = NULL; + struct ri_sr_tlv_sid_label_range *ri_srlb = NULL; + struct ri_sr_tlv_sr_algorithm *algo = NULL; + struct sr_block srgb; + uint16_t length = 0, sum = 0; + uint8_t msd = 0; + + osr_debug("SR (%s): Process Router Information LSA 4.0.0.%u from %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + + /* Sanity check */ + if (IS_LSA_SELF(lsa)) + return; + + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Search SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + &lsah->adv_router); + + + /* Collect Router Information Sub TLVs */ + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + srgb.range_size = 0; + srgb.lower_bound = 0; + + for (tlvh = TLV_HDR_TOP(lsah); (sum < length) && (tlvh != NULL); + tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case RI_SR_TLV_SR_ALGORITHM: + algo = (struct ri_sr_tlv_sr_algorithm *)tlvh; + break; + case RI_SR_TLV_SRGB_LABEL_RANGE: + ri_srgb = (struct ri_sr_tlv_sid_label_range *)tlvh; + break; + case RI_SR_TLV_SRLB_LABEL_RANGE: + ri_srlb = (struct ri_sr_tlv_sid_label_range *)tlvh; + break; + case RI_SR_TLV_NODE_MSD: + msd = ((struct ri_sr_tlv_node_msd *)(tlvh))->value; + break; + default: + break; + } + sum += TLV_SIZE(tlvh); + } + + /* Check if Segment Routing Capabilities has been found */ + if (ri_srgb == NULL) { + /* Skip Router Information without SR capabilities + * advertise by a non SR Node */ + if (srn == NULL) { + return; + } else { + /* Remove SR Node that advertise Router Information + * without SR capabilities. This could correspond to a + * Node stopping Segment Routing */ + hash_release(OspfSR.neighbors, &(srn->adv_router)); + sr_node_del(srn); + return; + } + } + + /* Check that RI LSA belongs to the correct SR Node */ + if ((srn != NULL) && (srn->instance != 0) + && (srn->instance != ntohl(lsah->id.s_addr))) { + flog_err(EC_OSPF_SR_INVALID_LSA_ID, + "SR (%s): Abort! Wrong LSA ID 4.0.0.%u for SR node %pI4/%u", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router, srn->instance); + return; + } + + /* OK. All things look good. Get SRGB */ + srgb.range_size = GET_RANGE_SIZE(ntohl(ri_srgb->size)); + srgb.lower_bound = GET_LABEL(ntohl(ri_srgb->lower.value)); + + /* Check if it is a new SR Node or not */ + if (srn == NULL) { + /* Get a new SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_get(OspfSR.neighbors, + &lsah->adv_router, + (void *)sr_node_new); + /* update LSA ID */ + srn->instance = ntohl(lsah->id.s_addr); + /* Copy SRGB */ + srn->srgb.range_size = srgb.range_size; + srn->srgb.lower_bound = srgb.lower_bound; + } + + /* Update Algorithm, SRLB and MSD if present */ + if (algo != NULL) { + int i; + for (i = 0; i < ntohs(algo->header.length); i++) + srn->algo[i] = algo->value[0]; + for (; i < ALGORITHM_COUNT; i++) + srn->algo[i] = SR_ALGORITHM_UNSET; + } else { + srn->algo[0] = SR_ALGORITHM_SPF; + } + srn->msd = msd; + if (ri_srlb != NULL) { + srn->srlb.range_size = GET_RANGE_SIZE(ntohl(ri_srlb->size)); + srn->srlb.lower_bound = GET_LABEL(ntohl(ri_srlb->lower.value)); + } + + /* Check if SRGB has changed */ + if ((srn->srgb.range_size == srgb.range_size) + && (srn->srgb.lower_bound == srgb.lower_bound)) + return; + + /* Copy SRGB */ + srn->srgb.range_size = srgb.range_size; + srn->srgb.lower_bound = srgb.lower_bound; + + osr_debug(" |- Update SR-Node[%pI4], SRGB[%u/%u], SRLB[%u/%u], Algo[%u], MSD[%u]", + &srn->adv_router, srn->srgb.lower_bound, srn->srgb.range_size, + srn->srlb.lower_bound, srn->srlb.range_size, srn->algo[0], + srn->msd); + + /* ... and NHLFE if it is a neighbor SR node */ + if (srn->neighbor == OspfSR.self) + hash_iterate(OspfSR.neighbors, update_out_nhlfe, srn); +} + +/* + * Delete SR Node entry in hash table information corresponding to an expired + * Router Information LSA + */ +void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct lsa_header *lsah = lsa->data; + + osr_debug("SR (%s): Remove SR node %pI4 from lsa_id 4.0.0.%u", __func__, + &lsah->adv_router, GET_OPAQUE_ID(ntohl(lsah->id.s_addr))); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR Data Base", __func__); + return; + } + + /* Release Router ID entry in SRDB hash table */ + srn = hash_release(OspfSR.neighbors, &(lsah->adv_router)); + + /* Sanity check */ + if (srn == NULL) { + flog_err(EC_OSPF_SR_NODE_CREATE, + "SR (%s): Abort! no entry in SRDB for SR Node %pI4", + __func__, &lsah->adv_router); + return; + } + + if ((srn->instance != 0) && (srn->instance != ntohl(lsah->id.s_addr))) { + flog_err( + EC_OSPF_SR_INVALID_LSA_ID, + "SR (%s): Abort! Wrong LSA ID 4.0.0.%u for SR node %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + return; + } + + /* Remove SR node */ + sr_node_del(srn); +} + +/* Update Segment Routing from Extended Link LSA */ +void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = lsa->data; + struct sr_link *srl; + + int length; + + osr_debug("SR (%s): Process Extended Link LSA 8.0.0.%u from %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Get SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_get(OspfSR.neighbors, + (void *)&(lsah->adv_router), + (void *)sr_node_new); + + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + for (tlvh = TLV_HDR_TOP(lsah); length > 0 && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + if (ntohs(tlvh->type) == EXT_TLV_LINK) { + /* Got Extended Link information */ + srl = get_ext_link_sid(tlvh, length); + /* Update SID if not null */ + if (srl != NULL) { + srl->instance = ntohl(lsah->id.s_addr); + update_ext_link_sid(srn, srl, lsa->flags); + } + } + length -= TLV_SIZE(tlvh); + } +} + +/* Delete Segment Routing from Extended Link LSA */ +void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct sr_link *srl; + struct sr_node *srn; + struct lsa_header *lsah = lsa->data; + uint32_t instance = ntohl(lsah->id.s_addr); + + osr_debug("SR (%s): Remove Extended Link LSA 8.0.0.%u from %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Search SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&(lsah->adv_router)); + + /* + * SR-Node may be NULL if it has been remove previously when + * processing Router Information LSA deletion + */ + if (srn == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Stop! no entry in SRDB for SR Node %pI4", + __func__, &lsah->adv_router); + return; + } + + /* Search for corresponding Segment Link */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) + if (srl->instance == instance) + break; + + /* Remove Segment Link if found. Note that for Neighbors, only Global + * Adj/Lan-Adj SID are stored in the SR-DB */ + if ((srl != NULL) && (srl->instance == instance)) { + del_adj_sid(srl->nhlfe[0]); + del_adj_sid(srl->nhlfe[1]); + listnode_delete(srn->ext_link, srl); + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } +} + +/* Add (LAN)Adjacency-SID from Extended Link Information */ +void ospf_sr_ext_itf_add(struct ext_itf *exti) +{ + struct sr_node *srn = OspfSR.self; + struct sr_link *srl; + + osr_debug("SR (%s): Add Extended Link LSA 8.0.0.%u from self", __func__, + exti->instance); + + /* Sanity check */ + if (srn == NULL) + return; + + /* Initialize new Segment Routing Link */ + srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); + srl->srn = srn; + srl->adv_router = srn->adv_router; + srl->itf_addr = exti->link.link_data; + srl->instance = + SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + srl->remote_id = exti->link.link_id; + switch (exti->stype) { + case ADJ_SID: + srl->type = ADJ_SID; + /* Primary information */ + srl->flags[0] = exti->adj_sid[0].flags; + if (CHECK_FLAG(exti->adj_sid[0].flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[0] = GET_LABEL(ntohl(exti->adj_sid[0].value)); + else + srl->sid[0] = ntohl(exti->adj_sid[0].value); + if (exti->rmt_itf_addr.header.type == 0) + srl->nhlfe[0].nexthop = exti->link.link_id; + else + srl->nhlfe[0].nexthop = exti->rmt_itf_addr.value; + /* Backup Information if set */ + if (exti->adj_sid[1].header.type == 0) + break; + srl->flags[1] = exti->adj_sid[1].flags; + if (CHECK_FLAG(exti->adj_sid[1].flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[1] = GET_LABEL(ntohl(exti->adj_sid[1].value)); + else + srl->sid[1] = ntohl(exti->adj_sid[1].value); + if (exti->rmt_itf_addr.header.type == 0) + srl->nhlfe[1].nexthop = exti->link.link_id; + else + srl->nhlfe[1].nexthop = exti->rmt_itf_addr.value; + break; + case LAN_ADJ_SID: + srl->type = LAN_ADJ_SID; + /* Primary information */ + srl->flags[0] = exti->lan_sid[0].flags; + if (CHECK_FLAG(exti->lan_sid[0].flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[0] = GET_LABEL(ntohl(exti->lan_sid[0].value)); + else + srl->sid[0] = ntohl(exti->lan_sid[0].value); + if (exti->rmt_itf_addr.header.type == 0) + srl->nhlfe[0].nexthop = exti->lan_sid[0].neighbor_id; + else + srl->nhlfe[0].nexthop = exti->rmt_itf_addr.value; + /* Backup Information if set */ + if (exti->lan_sid[1].header.type == 0) + break; + srl->flags[1] = exti->lan_sid[1].flags; + if (CHECK_FLAG(exti->lan_sid[1].flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG)) + srl->sid[1] = GET_LABEL(ntohl(exti->lan_sid[1].value)); + else + srl->sid[1] = ntohl(exti->lan_sid[1].value); + if (exti->rmt_itf_addr.header.type == 0) + srl->nhlfe[1].nexthop = exti->lan_sid[1].neighbor_id; + else + srl->nhlfe[1].nexthop = exti->rmt_itf_addr.value; + break; + case PREF_SID: + case LOCAL_SID: + /* Wrong SID Type. Abort! */ + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + return; + } + + /* Segment Routing Link is ready, update it */ + update_ext_link_sid(srn, srl, OSPF_LSA_SELF); +} + +/* Delete Prefix or (LAN)Adjacency-SID from Extended Link Information */ +void ospf_sr_ext_itf_delete(struct ext_itf *exti) +{ + struct listnode *node; + struct sr_node *srn = OspfSR.self; + struct sr_prefix *srp = NULL; + struct sr_link *srl = NULL; + uint32_t instance; + + osr_debug("SR (%s): Remove Extended LSA %u.0.0.%u from self", + __func__, exti->stype == PREF_SID ? 7 : 8, exti->instance); + + /* Sanity check: SR-Node and Extended Prefix/Link list may have been + * removed earlier when stopping OSPF or OSPF-SR */ + if (srn == NULL || srn->ext_prefix == NULL || srn->ext_link == NULL) + return; + + if (exti->stype == PREF_SID) { + instance = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_PREFIX_LSA, + exti->instance); + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) + if (srp->instance == instance) + break; + + /* Uninstall Segment Prefix SID if found */ + if ((srp != NULL) && (srp->instance == instance)) + ospf_zebra_delete_prefix_sid(srp); + } else { + /* Search for corresponding Segment Link for self SR-Node */ + instance = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, + exti->instance); + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) + if (srl->instance == instance) + break; + + /* Remove Segment Link if found */ + if ((srl != NULL) && (srl->instance == instance)) { + del_adj_sid(srl->nhlfe[0]); + del_adj_sid(srl->nhlfe[1]); + listnode_delete(srn->ext_link, srl); + XFREE(MTYPE_OSPF_SR_PARAMS, srl); + } + } +} + +/* Update Segment Routing from Extended Prefix LSA */ +void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa) +{ + struct sr_node *srn; + struct tlv_header *tlvh; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + struct sr_prefix *srp; + + int length; + + osr_debug("SR (%s): Process Extended Prefix LSA 7.0.0.%u from %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Get SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_get(OspfSR.neighbors, + (void *)&(lsah->adv_router), + (void *)sr_node_new); + /* Initialize TLV browsing */ + length = lsa->size - OSPF_LSA_HEADER_SIZE; + for (tlvh = TLV_HDR_TOP(lsah); length > 0 && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + if (ntohs(tlvh->type) == EXT_TLV_LINK) { + /* Got Extended Link information */ + srp = get_ext_prefix_sid(tlvh, length); + /* Update SID if not null */ + if (srp != NULL) { + srp->instance = ntohl(lsah->id.s_addr); + update_ext_prefix_sid(srn, srp); + } + } + length -= TLV_SIZE(tlvh); + } +} + +/* Delete Segment Routing from Extended Prefix LSA */ +void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct sr_prefix *srp; + struct sr_node *srn; + struct lsa_header *lsah = (struct lsa_header *)lsa->data; + uint32_t instance = ntohl(lsah->id.s_addr); + + osr_debug("SR (%s): Remove Extended Prefix LSA 7.0.0.%u from %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + + /* Sanity check */ + if (OspfSR.neighbors == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Abort! no valid SR DataBase", __func__); + return; + } + + /* Search SR Node in hash table from Router ID */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&(lsah->adv_router)); + + /* + * SR-Node may be NULL if it has been remove previously when + * processing Router Information LSA deletion + */ + if (srn == NULL) { + flog_err(EC_OSPF_SR_INVALID_DB, + "SR (%s): Stop! no entry in SRDB for SR Node %pI4", + __func__, &lsah->adv_router); + return; + } + + /* Search for corresponding Segment Prefix */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) + if (srp->instance == instance) + break; + + /* Remove Prefix if found */ + if ((srp != NULL) && (srp->instance == instance)) { + ospf_zebra_delete_prefix_sid(srp); + listnode_delete(srn->ext_prefix, srp); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + } else { + flog_err( + EC_OSPF_SR_INVALID_DB, + "SR (%s): Didn't found corresponding SR Prefix 7.0.0.%u for SR Node %pI4", + __func__, GET_OPAQUE_ID(ntohl(lsah->id.s_addr)), + &lsah->adv_router); + } +} + +/* + * Update Prefix SID. Call by ospf_ext_pref_ism_change to + * complete initial CLI command at startup. + * + * @param ifp - Loopback interface + * @param pref - Prefix address of this interface + * + * @return - void + */ +void ospf_sr_update_local_prefix(struct interface *ifp, struct prefix *p) +{ + struct listnode *node; + struct sr_prefix *srp; + + /* Sanity Check */ + if ((ifp == NULL) || (p == NULL)) + return; + + /* + * Search if there is a Segment Prefix that correspond to this + * interface or prefix, and update it if found + */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + if ((srp->nhlfe.ifindex == ifp->ifindex) + || ((IPV4_ADDR_SAME(&srp->prefv4.prefix, &p->u.prefix4)) + && (srp->prefv4.prefixlen == p->prefixlen))) { + + /* Update Interface & Prefix info */ + srp->nhlfe.ifindex = ifp->ifindex; + IPV4_ADDR_COPY(&srp->prefv4.prefix, &p->u.prefix4); + srp->prefv4.prefixlen = p->prefixlen; + srp->prefv4.family = p->family; + IPV4_ADDR_COPY(&srp->nhlfe.nexthop, &p->u.prefix4); + + /* OK. Let's Schedule Extended Prefix LSA */ + srp->instance = ospf_ext_schedule_prefix_index( + ifp, srp->sid, &srp->prefv4, srp->flags); + + osr_debug( + " |- Update Node SID %pFX - %u for self SR Node", + (struct prefix *)&srp->prefv4, srp->sid); + + /* Install SID if NO-PHP is set and not EXPLICIT-NULL */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG) + && !CHECK_FLAG(srp->flags, + EXT_SUBTLV_PREFIX_SID_EFLG)) { + srp->label_in = index2label(srp->sid, + OspfSR.self->srgb); + srp->nhlfe.label_out = MPLS_LABEL_IMPLICIT_NULL; + ospf_zebra_update_prefix_sid(srp); + } + } + } +} + +/* + * Following functions are used to update MPLS LFIB after a SPF run + */ + +static void ospf_sr_nhlfe_update(struct hash_bucket *bucket, void *args) +{ + + struct sr_node *srn = (struct sr_node *)bucket->data; + struct listnode *node; + struct sr_prefix *srp; + bool old; + int rc; + + osr_debug(" |- Update Prefix for SR Node %pI4", &srn->adv_router); + + /* Skip Self SR Node */ + if (srn == OspfSR.self) + return; + + /* Update Extended Prefix */ + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + + /* Keep track of valid route */ + old = srp->route != NULL; + + /* Compute the new NHLFE */ + rc = compute_prefix_nhlfe(srp); + + /* Check computation result */ + switch (rc) { + /* Routes are not know, remove old NHLFE if any to avoid loop */ + case -1: + if (old) + ospf_zebra_delete_prefix_sid(srp); + break; + /* Routes exist but are not ready, skip it */ + case 0: + break; + /* There is at least one route, update NHLFE */ + case 1: + ospf_zebra_update_prefix_sid(srp); + break; + default: + break; + } + } +} + +void ospf_sr_update_task(struct ospf *ospf) +{ + + struct timeval start_time, stop_time; + + /* Check ospf and SR status */ + if ((ospf == NULL) || (OspfSR.status != SR_UP)) + return; + + monotime(&start_time); + + osr_debug("SR (%s): Start SPF update", __func__); + + hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, + void *))ospf_sr_nhlfe_update, + NULL); + + monotime(&stop_time); + + osr_debug("SR (%s): SPF Processing Time(usecs): %lld", __func__, + (stop_time.tv_sec - start_time.tv_sec) * 1000000LL + + (stop_time.tv_usec - start_time.tv_usec)); +} + +/* + * -------------------------------------- + * Following are vty command functions. + * -------------------------------------- + */ + +/* + * Segment Routing Router configuration + * + * Must be centralize as it concerns both Extended Link/Prefix LSA + * and Router Information LSA. Choose to call it from Extended Prefix + * write_config() call back. + * + * @param vty VTY output + * + * @return none + */ +void ospf_sr_config_write_router(struct vty *vty) +{ + struct listnode *node; + struct sr_prefix *srp; + uint32_t upper; + + if (OspfSR.status == SR_UP) + vty_out(vty, " segment-routing on\n"); + + upper = OspfSR.srgb.start + OspfSR.srgb.size - 1; + if ((OspfSR.srgb.start != DEFAULT_SRGB_LABEL) + || (OspfSR.srgb.size != DEFAULT_SRGB_SIZE)) + vty_out(vty, " segment-routing global-block %u %u", + OspfSR.srgb.start, upper); + + if ((OspfSR.srlb.start != DEFAULT_SRLB_LABEL) || + (OspfSR.srlb.end != DEFAULT_SRLB_END)) { + if ((OspfSR.srgb.start == DEFAULT_SRGB_LABEL) && + (OspfSR.srgb.size == DEFAULT_SRGB_SIZE)) + vty_out(vty, " segment-routing global-block %u %u", + OspfSR.srgb.start, upper); + vty_out(vty, " local-block %u %u\n", OspfSR.srlb.start, + OspfSR.srlb.end); + } else + vty_out(vty, "\n"); + + if (OspfSR.msd != 0) + vty_out(vty, " segment-routing node-msd %u\n", OspfSR.msd); + + if (OspfSR.self != NULL) { + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + vty_out(vty, " segment-routing prefix %pFX index %u", + &srp->prefv4, srp->sid); + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_EFLG)) + vty_out(vty, " explicit-null\n"); + else if (CHECK_FLAG(srp->flags, + EXT_SUBTLV_PREFIX_SID_NPFLG)) + vty_out(vty, " no-php-flag\n"); + else + vty_out(vty, "\n"); + } + } +} + +DEFUN(ospf_sr_enable, + ospf_sr_enable_cmd, + "segment-routing on", + SR_STR + "Enable Segment Routing\n") +{ + + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (OspfSR.status != SR_OFF) + return CMD_SUCCESS; + + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, + "Segment Routing is only supported in default VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + osr_debug("SR: Segment Routing: OFF -> ON"); + + /* Start Segment Routing */ + OspfSR.status = SR_ON; + ospf_sr_start(ospf); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_sr_enable, + no_ospf_sr_enable_cmd, + "no segment-routing [on]", + NO_STR + SR_STR + "Disable Segment Routing\n") +{ + + if (OspfSR.status == SR_OFF) + return CMD_SUCCESS; + + osr_debug("SR: Segment Routing: ON -> OFF"); + + /* Start by Disabling Extended Link & Prefix LSA */ + ospf_ext_update_sr(false); + + /* then, disable Router Information SR parameters */ + ospf_router_info_update_sr(false, OspfSR.self); + + /* Finally, stop Segment Routing */ + ospf_sr_stop(); + + return CMD_SUCCESS; +} + +static int ospf_sr_enabled(struct vty *vty) +{ + if (OspfSR.status != SR_OFF) + return 1; + + if (vty) + vty_out(vty, "%% OSPF SR is not turned on\n"); + + return 0; +} + +/* tell if two ranges [r1_lower, r1_upper] and [r2_lower,r2_upper] overlap */ +static bool ranges_overlap(uint32_t r1_lower, uint32_t r1_upper, + uint32_t r2_lower, uint32_t r2_upper) +{ + return !((r1_upper < r2_lower) || (r1_lower > r2_upper)); +} + + +/* tell if a range is valid */ +static bool sr_range_is_valid(uint32_t lower, uint32_t upper, uint32_t min_size) +{ + return (upper >= lower + min_size); +} + +/** + * Update SRGB and/or SRLB using new CLI values. + * + * @param gb_lower Lower bound of the SRGB + * @param gb_upper Upper bound of the SRGB + * @param lb_lower Lower bound of the SRLB + * @param lb_upper Upper bound of the SRLB + * + * @return 0 on success, -1 otherwise + */ +static int update_sr_blocks(uint32_t gb_lower, uint32_t gb_upper, + uint32_t lb_lower, uint32_t lb_upper) +{ + + /* Check if values have changed */ + bool gb_changed, lb_changed; + uint32_t gb_size = gb_upper - gb_lower + 1; + uint32_t lb_size = lb_upper - lb_lower + 1; + + gb_changed = + (OspfSR.srgb.size != gb_size || OspfSR.srgb.start != gb_lower); + lb_changed = + (OspfSR.srlb.end != lb_upper || OspfSR.srlb.start != lb_lower); + if (!gb_changed && !lb_changed) + return 0; + + /* Check if SR is correctly started i.e. Label Manager connected */ + if (OspfSR.status != SR_UP) { + OspfSR.srgb.size = gb_size; + OspfSR.srgb.start = gb_lower; + OspfSR.srlb.end = lb_upper; + OspfSR.srlb.start = lb_lower; + return 0; + } + + /* Release old SRGB if it has changed and is active. */ + if (gb_changed) { + + sr_global_block_delete(); + + /* Set new SRGB values - but do not reserve yet (we need to + * release the SRLB too) */ + OspfSR.srgb.size = gb_size; + OspfSR.srgb.start = gb_lower; + if (OspfSR.self != NULL) { + OspfSR.self->srgb.range_size = gb_size; + OspfSR.self->srgb.lower_bound = gb_lower; + } + } + /* Release old SRLB if it has changed and reserve new block as needed. + */ + if (lb_changed) { + + sr_local_block_delete(); + + /* Set new SRLB values */ + if (sr_local_block_init(lb_lower, lb_upper) < 0) { + ospf_sr_stop(); + return -1; + } + if (OspfSR.self != NULL) { + OspfSR.self->srlb.lower_bound = lb_lower; + OspfSR.self->srlb.range_size = lb_size; + } + } + + /* + * Try to reserve the new SRGB from the Label Manger. If the + * allocation fails, disable SR until new blocks are successfully + * allocated. + */ + if (gb_changed) { + if (sr_global_block_init(OspfSR.srgb.start, OspfSR.srgb.size) + < 0) { + ospf_sr_stop(); + return -1; + } + } + + /* Update Self SR-Node */ + if (OspfSR.self != NULL) { + /* SRGB is reserved, set Router Information parameters */ + ospf_router_info_update_sr(true, OspfSR.self); + + /* and update NHLFE entries */ + if (gb_changed) + hash_iterate(OspfSR.neighbors, + (void (*)(struct hash_bucket *, + void *))update_in_nhlfe, + NULL); + + /* and update (LAN)-Adjacency SID */ + if (lb_changed) + ospf_ext_link_srlb_update(); + } + + return 0; +} + +DEFUN(sr_global_label_range, sr_global_label_range_cmd, + "segment-routing global-block (16-1048575) (16-1048575) [local-block (16-1048575) (16-1048575)]", + SR_STR + "Segment Routing Global Block label range\n" + "Lower-bound range in decimal (16-1048575)\n" + "Upper-bound range in decimal (16-1048575)\n" + "Segment Routing Local Block label range\n" + "Lower-bound range in decimal (16-1048575)\n" + "Upper-bound range in decimal (16-1048575)\n") +{ + uint32_t lb_upper, lb_lower; + uint32_t gb_upper, gb_lower; + int idx_gb_low = 2, idx_gb_up = 3; + int idx_lb_low = 5, idx_lb_up = 6; + + /* Get lower and upper bound for mandatory global-block */ + gb_lower = strtoul(argv[idx_gb_low]->arg, NULL, 10); + gb_upper = strtoul(argv[idx_gb_up]->arg, NULL, 10); + + /* SRLB values are taken from vtysh if there, else use the known ones */ + lb_upper = argc > idx_lb_up ? strtoul(argv[idx_lb_up]->arg, NULL, 10) + : OspfSR.srlb.end; + lb_lower = argc > idx_lb_low ? strtoul(argv[idx_lb_low]->arg, NULL, 10) + : OspfSR.srlb.start; + + /* check correctness of input SRGB */ + if (!sr_range_is_valid(gb_lower, gb_upper, MIN_SRGB_SIZE)) { + vty_out(vty, "Invalid SRGB range\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* check correctness of SRLB */ + if (!sr_range_is_valid(lb_lower, lb_upper, MIN_SRLB_SIZE)) { + vty_out(vty, "Invalid SRLB range\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Validate SRGB against SRLB */ + if (ranges_overlap(gb_lower, gb_upper, lb_lower, lb_upper)) { + vty_out(vty, + "New SR Global Block (%u/%u) conflicts with Local Block (%u/%u)\n", + gb_lower, gb_upper, lb_lower, lb_upper); + return CMD_WARNING_CONFIG_FAILED; + } + + if (update_sr_blocks(gb_lower, gb_upper, lb_lower, lb_upper) < 0) + return CMD_WARNING_CONFIG_FAILED; + else + return CMD_SUCCESS; +} + +DEFUN(no_sr_global_label_range, no_sr_global_label_range_cmd, + "no segment-routing global-block [(16-1048575) (16-1048575) local-block (16-1048575) (16-1048575)]", + NO_STR SR_STR + "Segment Routing Global Block label range\n" + "Lower-bound range in decimal (16-1048575)\n" + "Upper-bound range in decimal (16-1048575)\n" + "Segment Routing Local Block label range\n" + "Lower-bound range in decimal (16-1048575)\n" + "Upper-bound range in decimal (16-1048575)\n") +{ + if (update_sr_blocks(DEFAULT_SRGB_LABEL, DEFAULT_SRGB_END, + DEFAULT_SRLB_LABEL, DEFAULT_SRLB_END) + < 0) + return CMD_WARNING_CONFIG_FAILED; + else + return CMD_SUCCESS; +} + +DEFUN (sr_node_msd, + sr_node_msd_cmd, + "segment-routing node-msd (1-16)", + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that could be stack (1-16)\n") +{ + uint32_t msd; + int idx = 1; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Get MSD */ + argv_find(argv, argc, "(1-16)", &idx); + msd = strtoul(argv[idx]->arg, NULL, 10); + if (msd < 1 || msd > MPLS_MAX_LABELS) { + vty_out(vty, "MSD must be comprise between 1 and %u\n", + MPLS_MAX_LABELS); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Check if value has changed */ + if (OspfSR.msd == msd) + return CMD_SUCCESS; + + /* Set this router MSD */ + OspfSR.msd = msd; + if (OspfSR.self != NULL) { + OspfSR.self->msd = msd; + + /* Set Router Information parameters if SR is UP */ + if (OspfSR.status == SR_UP) + ospf_router_info_update_sr(true, OspfSR.self); + } + + return CMD_SUCCESS; +} + +DEFUN (no_sr_node_msd, + no_sr_node_msd_cmd, + "no segment-routing node-msd [(1-16)]", + NO_STR + SR_STR + "Maximum Stack Depth for this router\n" + "Maximum number of label that could be stack (1-16)\n") +{ + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* unset this router MSD */ + OspfSR.msd = 0; + if (OspfSR.self != NULL) { + OspfSR.self->msd = 0; + + /* Set Router Information parameters if SR is UP */ + if (OspfSR.status == SR_UP) + ospf_router_info_update_sr(true, OspfSR.self); + } + + return CMD_SUCCESS; +} + +DEFUN (sr_prefix_sid, + sr_prefix_sid_cmd, + "segment-routing prefix A.B.C.D/M index (0-65535) [no-php-flag|explicit-null]", + SR_STR + "Prefix SID\n" + "IPv4 Prefix as A.B.C.D/M\n" + "SID index for this prefix in decimal (0-65535)\n" + "Index value inside SRGB (lower_bound < index < upper_bound)\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n") +{ + int idx = 0; + struct prefix p, pexist; + uint32_t index; + struct listnode *node; + struct sr_prefix *srp, *exist = NULL; + struct interface *ifp; + bool no_php_flag = false; + bool exp_null = false; + bool index_in_use = false; + uint8_t desired_flags = 0; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + /* Get network prefix */ + argv_find(argv, argc, "A.B.C.D/M", &idx); + if (!str2prefix(argv[idx]->arg, &p)) { + vty_out(vty, "Invalid prefix format %s\n", argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get & verify index value */ + argv_find(argv, argc, "(0-65535)", &idx); + index = strtoul(argv[idx]->arg, NULL, 10); + if (index > OspfSR.srgb.size - 1) { + vty_out(vty, "Index %u must be lower than range size %u\n", + index, OspfSR.srgb.size); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get options */ + no_php_flag = argv_find(argv, argc, "no-php-flag", &idx); + exp_null = argv_find(argv, argc, "explicit-null", &idx); + + desired_flags |= no_php_flag ? EXT_SUBTLV_PREFIX_SID_NPFLG : 0; + desired_flags |= exp_null ? EXT_SUBTLV_PREFIX_SID_NPFLG : 0; + desired_flags |= exp_null ? EXT_SUBTLV_PREFIX_SID_EFLG : 0; + + /* Search for an existing Prefix-SID */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) { + if (prefix_same((struct prefix *)&srp->prefv4, &p)) + exist = srp; + if (srp->sid == index) { + index_in_use = true; + pexist = p; + } + } + + /* done if prefix segment already there with same index and flags */ + if (exist && exist->sid == index && exist->flags == desired_flags) + return CMD_SUCCESS; + + /* deny if index is already in use by a distinct prefix */ + if (!exist && index_in_use) { + vty_out(vty, "Index %u is already used by %pFX\n", index, + &pexist); + return CMD_WARNING_CONFIG_FAILED; + } + + /* First, remove old NHLFE if installed */ + if (exist && CHECK_FLAG(exist->flags, EXT_SUBTLV_PREFIX_SID_NPFLG) + && !CHECK_FLAG(exist->flags, EXT_SUBTLV_PREFIX_SID_EFLG)) + ospf_zebra_delete_prefix_sid(exist); + + /* Create new Extended Prefix to SRDB if not found */ + if (exist == NULL) { + srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + IPV4_ADDR_COPY(&srp->prefv4.prefix, &p.u.prefix4); + srp->prefv4.prefixlen = p.prefixlen; + srp->prefv4.family = p.family; + srp->sid = index; + srp->type = LOCAL_SID; + } else { + /* we work on the existing SR prefix */ + srp = exist; + } + + /* Reset labels to handle flag update */ + srp->label_in = 0; + srp->nhlfe.label_out = 0; + srp->sid = index; + srp->flags = desired_flags; + + /* If NO PHP flag is present, compute NHLFE and set label */ + if (no_php_flag) { + srp->label_in = index2label(srp->sid, OspfSR.self->srgb); + srp->nhlfe.label_out = MPLS_LABEL_IMPLICIT_NULL; + } + + osr_debug("SR (%s): Add new index %u to Prefix %pFX", __func__, index, + (struct prefix *)&srp->prefv4); + + /* Get Interface and check if it is a Loopback */ + ifp = if_lookup_prefix(&p, VRF_DEFAULT); + if (ifp == NULL) { + /* + * Interface could be not yet available i.e. when this + * command is in the configuration file, OSPF is not yet + * ready. In this case, store the prefix SID for latter + * update of this Extended Prefix + */ + if (exist == NULL) + listnode_add(OspfSR.self->ext_prefix, srp); + zlog_info( + "Interface for prefix %pFX not found. Deferred LSA flooding", + &p); + return CMD_SUCCESS; + } + + if (!if_is_loopback(ifp)) { + vty_out(vty, "interface %s is not a Loopback\n", ifp->name); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + return CMD_WARNING_CONFIG_FAILED; + } + srp->nhlfe.ifindex = ifp->ifindex; + + /* Add SR Prefix if new */ + if (!exist) + listnode_add(OspfSR.self->ext_prefix, srp); + + /* Update Prefix SID if SR is UP */ + if (OspfSR.status == SR_UP) { + if (no_php_flag && !exp_null) + ospf_zebra_update_prefix_sid(srp); + } else + return CMD_SUCCESS; + + /* Finally, update Extended Prefix LSA id SR is UP */ + srp->instance = ospf_ext_schedule_prefix_index( + ifp, srp->sid, &srp->prefv4, srp->flags); + if (srp->instance == 0) { + vty_out(vty, "Unable to set index %u for prefix %pFX\n", + index, &p); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN (no_sr_prefix_sid, + no_sr_prefix_sid_cmd, + "no segment-routing prefix A.B.C.D/M [index (0-65535)|no-php-flag|explicit-null]", + NO_STR + SR_STR + "Prefix SID\n" + "IPv4 Prefix as A.B.C.D/M\n" + "SID index for this prefix in decimal (0-65535)\n" + "Index value inside SRGB (lower_bound < index < upper_bound)\n" + "Don't request Penultimate Hop Popping (PHP)\n" + "Upstream neighbor must replace prefix-sid with explicit null label\n") +{ + int idx = 0; + struct prefix p; + struct listnode *node; + struct sr_prefix *srp; + struct interface *ifp; + bool found = false; + int rc; + + if (!ospf_sr_enabled(vty)) + return CMD_WARNING_CONFIG_FAILED; + + if (OspfSR.status != SR_UP) + return CMD_SUCCESS; + + /* Get network prefix */ + argv_find(argv, argc, "A.B.C.D/M", &idx); + rc = str2prefix(argv[idx]->arg, &p); + if (!rc) { + vty_out(vty, "Invalid prefix format %s\n", argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + /* check that the prefix is already set */ + for (ALL_LIST_ELEMENTS_RO(OspfSR.self->ext_prefix, node, srp)) + if (IPV4_ADDR_SAME(&srp->prefv4.prefix, &p.u.prefix4) + && (srp->prefv4.prefixlen == p.prefixlen)) { + found = true; + break; + } + + if (!found) { + vty_out(vty, "Prefix %s is not found. Abort!\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + osr_debug("SR (%s): Remove Prefix %pFX with index %u", __func__, + (struct prefix *)&srp->prefv4, srp->sid); + + /* Get Interface */ + ifp = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); + if (ifp == NULL) { + vty_out(vty, "interface for prefix %s not found.\n", + argv[idx]->arg); + /* silently remove from list */ + listnode_delete(OspfSR.self->ext_prefix, srp); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + return CMD_SUCCESS; + } + + /* Update Extended Prefix LSA */ + if (!ospf_ext_schedule_prefix_index(ifp, 0, NULL, 0)) { + vty_out(vty, "No corresponding loopback interface. Abort!\n"); + return CMD_WARNING; + } + + /* Delete NHLFE if NO-PHP is set and EXPLICIT NULL not set */ + if (CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_NPFLG) + && !CHECK_FLAG(srp->flags, EXT_SUBTLV_PREFIX_SID_EFLG)) + ospf_zebra_delete_prefix_sid(srp); + + /* OK, all is clean, remove SRP from SRDB */ + listnode_delete(OspfSR.self->ext_prefix, srp); + XFREE(MTYPE_OSPF_SR_PARAMS, srp); + + return CMD_SUCCESS; +} + + +static char *sr_op2str(char *buf, size_t size, mpls_label_t label_in, + mpls_label_t label_out) +{ + if (size < 24) + return NULL; + + switch (label_out) { + case MPLS_LABEL_IMPLICIT_NULL: + snprintf(buf, size, "Pop(%u)", label_in); + break; + case MPLS_LABEL_IPV4_EXPLICIT_NULL: + if (label_in == MPLS_LABEL_IPV4_EXPLICIT_NULL) + snprintf(buf, size, "no-op."); + else + snprintf(buf, size, "Swap(%u, null)", label_in); + break; + case MPLS_INVALID_LABEL: + snprintf(buf, size, "no-op."); + break; + default: + snprintf(buf, size, "Swap(%u, %u)", label_in, label_out); + break; + } + return buf; +} + +static void show_sr_prefix(struct sbuf *sbuf, struct json_object *json, + struct sr_prefix *srp) +{ + + struct listnode *node; + struct ospf_path *path; + struct interface *itf; + json_object *json_route = NULL, *json_obj; + char pref[19]; + char sid[22]; + char op[32]; + char buf[PREFIX_STRLEN]; + int indent = 0; + + snprintfrr(pref, 19, "%pFX", (struct prefix *)&srp->prefv4); + snprintf(sid, 22, "SR Pfx (idx %u)", srp->sid); + if (json) { + json_object_string_add(json, "prefix", pref); + json_object_int_add(json, "sid", srp->sid); + json_object_int_add(json, "inputLabel", srp->label_in); + } else { + sbuf_push(sbuf, 0, "%18s %21s ", pref, sid); + } + + /* Check if it is a Local Node SID */ + if (srp->type == LOCAL_SID) { + itf = if_lookup_by_index(srp->nhlfe.ifindex, VRF_DEFAULT); + if (json) { + if (!json_route) { + json_route = json_object_new_array(); + json_object_object_add(json, "prefixRoute", + json_route); + } + json_obj = json_object_new_object(); + json_object_int_add(json_obj, "outputLabel", + srp->nhlfe.label_out); + json_object_string_add(json_obj, "interface", + itf ? itf->name : "-"); + json_object_string_addf(json_obj, "nexthop", "%pI4", + &srp->nhlfe.nexthop); + json_object_array_add(json_route, json_obj); + } else { + sbuf_push(sbuf, 0, "%20s %9s %15s\n", + sr_op2str(op, 32, srp->label_in, + srp->nhlfe.label_out), + itf ? itf->name : "-", + inet_ntop(AF_INET, &srp->nhlfe.nexthop, + buf, sizeof(buf))); + } + return; + } + + /* Check if we have a valid path for this prefix */ + if (srp->route == NULL) { + if (!json) { + sbuf_push(sbuf, 0, "\n"); + } + return; + } + + /* Process list of OSPF paths */ + for (ALL_LIST_ELEMENTS_RO(srp->route->paths, node, path)) { + itf = if_lookup_by_index(path->ifindex, VRF_DEFAULT); + if (json) { + if (!json_route) { + json_route = json_object_new_array(); + json_object_object_add(json, "prefixRoute", + json_route); + } + json_obj = json_object_new_object(); + json_object_int_add(json_obj, "outputLabel", + path->srni.label_out); + json_object_string_add(json_obj, "interface", + itf ? itf->name : "-"); + json_object_string_addf(json_obj, "nexthop", "%pI4", + &path->nexthop); + json_object_array_add(json_route, json_obj); + } else { + sbuf_push(sbuf, indent, "%20s %9s %15s\n", + sr_op2str(op, 32, srp->label_in, + path->srni.label_out), + itf ? itf->name : "-", + inet_ntop(AF_INET, &path->nexthop, buf, + sizeof(buf))); + /* Offset to align information for ECMP */ + indent = 43; + } + } +} + +static void show_sr_node(struct vty *vty, struct json_object *json, + struct sr_node *srn) +{ + + struct listnode *node; + struct sr_link *srl; + struct sr_prefix *srp; + struct interface *itf; + struct sbuf sbuf; + char pref[19]; + char sid[22]; + char op[32]; + char buf[PREFIX_STRLEN]; + uint32_t upper; + json_object *json_node = NULL, *json_algo, *json_obj; + json_object *json_prefix = NULL, *json_link = NULL; + + /* Sanity Check */ + if (srn == NULL) + return; + + sbuf_init(&sbuf, NULL, 0); + + if (json) { + json_node = json_object_new_object(); + json_object_string_addf(json_node, "routerID", "%pI4", + &srn->adv_router); + json_object_int_add(json_node, "srgbSize", + srn->srgb.range_size); + json_object_int_add(json_node, "srgbLabel", + srn->srgb.lower_bound); + json_object_int_add(json_node, "srlbSize", + srn->srlb.range_size); + json_object_int_add(json_node, "srlbLabel", + srn->srlb.lower_bound); + json_algo = json_object_new_array(); + json_object_object_add(json_node, "algorithms", json_algo); + for (int i = 0; i < ALGORITHM_COUNT; i++) { + if (srn->algo[i] == SR_ALGORITHM_UNSET) + continue; + json_obj = json_object_new_object(); + char tmp[12]; + + snprintf(tmp, sizeof(tmp), "%d", i); + json_object_string_add(json_obj, tmp, + srn->algo[i] == SR_ALGORITHM_SPF + ? "SPF" + : "S-SPF"); + json_object_array_add(json_algo, json_obj); + } + if (srn->msd != 0) + json_object_int_add(json_node, "nodeMsd", srn->msd); + } else { + sbuf_push(&sbuf, 0, "SR-Node: %pI4", &srn->adv_router); + upper = srn->srgb.lower_bound + srn->srgb.range_size - 1; + sbuf_push(&sbuf, 0, "\tSRGB: [%u/%u]", + srn->srgb.lower_bound, upper); + upper = srn->srlb.lower_bound + srn->srlb.range_size - 1; + sbuf_push(&sbuf, 0, "\tSRLB: [%u/%u]", + srn->srlb.lower_bound, upper); + sbuf_push(&sbuf, 0, "\tAlgo.(s): %s", + srn->algo[0] == SR_ALGORITHM_SPF ? "SPF" : "S-SPF"); + for (int i = 1; i < ALGORITHM_COUNT; i++) { + if (srn->algo[i] == SR_ALGORITHM_UNSET) + continue; + sbuf_push(&sbuf, 0, "/%s", + srn->algo[i] == SR_ALGORITHM_SPF ? "SPF" + : "S-SPF"); + } + if (srn->msd != 0) + sbuf_push(&sbuf, 0, "\tMSD: %u", srn->msd); + } + + if (!json) { + sbuf_push(&sbuf, 0, + "\n\n Prefix or Link Node or Adj. SID Label Operation Interface Nexthop\n"); + sbuf_push(&sbuf, 0, + "------------------ --------------------- -------------------- --------- ---------------\n"); + } + for (ALL_LIST_ELEMENTS_RO(srn->ext_prefix, node, srp)) { + if (json) { + if (!json_prefix) { + json_prefix = json_object_new_array(); + json_object_object_add(json_node, + "extendedPrefix", + json_prefix); + } + json_obj = json_object_new_object(); + show_sr_prefix(NULL, json_obj, srp); + json_object_array_add(json_prefix, json_obj); + } else { + show_sr_prefix(&sbuf, NULL, srp); + } + } + + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) { + snprintfrr(pref, 19, "%pI4/32", &srl->itf_addr); + snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[0]); + itf = if_lookup_by_index(srl->nhlfe[0].ifindex, VRF_DEFAULT); + if (json) { + if (!json_link) { + json_link = json_object_new_array(); + json_object_object_add( + json_node, "extendedLink", json_link); + } + /* Primary Link */ + json_obj = json_object_new_object(); + json_object_string_add(json_obj, "prefix", pref); + json_object_int_add(json_obj, "sid", srl->sid[0]); + json_object_int_add(json_obj, "inputLabel", + srl->nhlfe[0].label_in); + json_object_int_add(json_obj, "outputLabel", + srl->nhlfe[0].label_out); + json_object_string_add(json_obj, "interface", + itf ? itf->name : "-"); + json_object_string_addf(json_obj, "nexthop", "%pI4", + &srl->nhlfe[0].nexthop); + json_object_array_add(json_link, json_obj); + /* Backup Link */ + json_obj = json_object_new_object(); + snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]); + json_object_string_add(json_obj, "prefix", pref); + json_object_int_add(json_obj, "sid", srl->sid[1]); + json_object_int_add(json_obj, "inputLabel", + srl->nhlfe[1].label_in); + json_object_int_add(json_obj, "outputLabel", + srl->nhlfe[1].label_out); + json_object_string_add(json_obj, "interface", + itf ? itf->name : "-"); + json_object_string_addf(json_obj, "nexthop", "%pI4", + &srl->nhlfe[1].nexthop); + json_object_array_add(json_link, json_obj); + } else { + sbuf_push(&sbuf, 0, "%18s %21s %20s %9s %15s\n", + pref, sid, + sr_op2str(op, 32, srl->nhlfe[0].label_in, + srl->nhlfe[0].label_out), + itf ? itf->name : "-", + inet_ntop(AF_INET, &srl->nhlfe[0].nexthop, + buf, sizeof(buf))); + snprintf(sid, 22, "SR Adj. (lbl %u)", srl->sid[1]); + sbuf_push(&sbuf, 0, "%18s %21s %20s %9s %15s\n", + pref, sid, + sr_op2str(op, 32, srl->nhlfe[1].label_in, + srl->nhlfe[1].label_out), + itf ? itf->name : "-", + inet_ntop(AF_INET, &srl->nhlfe[1].nexthop, + buf, sizeof(buf))); + } + } + if (json) + json_object_array_add(json, json_node); + else + vty_out(vty, "%s\n", sbuf_buf(&sbuf)); + + sbuf_free(&sbuf); +} + +static void show_vty_srdb(struct hash_bucket *bucket, void *args) +{ + struct vty *vty = (struct vty *)args; + struct sr_node *srn = (struct sr_node *)bucket->data; + + show_sr_node(vty, NULL, srn); +} + +static void show_json_srdb(struct hash_bucket *bucket, void *args) +{ + struct json_object *json = (struct json_object *)args; + struct sr_node *srn = (struct sr_node *)bucket->data; + + show_sr_node(NULL, json, srn); +} + +DEFUN (show_ip_opsf_srdb, + show_ip_ospf_srdb_cmd, + "show ip ospf database segment-routing [adv-router A.B.C.D|self-originate] [json]", + SHOW_STR + IP_STR + OSPF_STR + "Database summary\n" + "Show Segment Routing Data Base\n" + "Advertising SR node\n" + "Advertising SR node ID (as an IP address)\n" + "Self-originated SR node\n" + JSON_STR) +{ + int idx = 0; + struct in_addr rid; + struct sr_node *srn; + bool uj = use_json(argc, argv); + json_object *json = NULL, *json_node_array = NULL; + + if (OspfSR.status == SR_OFF) { + vty_out(vty, "Segment Routing is disabled on this router\n"); + return CMD_WARNING; + } + + if (uj) { + json = json_object_new_object(); + json_node_array = json_object_new_array(); + json_object_string_addf(json, "srdbID", "%pI4", + &OspfSR.self->adv_router); + json_object_object_add(json, "srNodes", json_node_array); + } else { + vty_out(vty, + "\n\t\tOSPF Segment Routing database for ID %pI4\n\n", + &OspfSR.self->adv_router); + } + + if (argv_find(argv, argc, "self-originate", &idx)) { + srn = OspfSR.self; + show_sr_node(vty, json_node_array, srn); + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; + } + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &rid)) { + vty_out(vty, "Specified Router ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the SR Node from the SRDB */ + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, + (void *)&rid); + show_sr_node(vty, json_node_array, srn); + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; + } + + /* No parameters have been provided, Iterate through all the SRDB */ + if (uj) { + hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, + void *))show_json_srdb, + (void *)json_node_array); + vty_json(vty, json); + } else { + hash_iterate(OspfSR.neighbors, (void (*)(struct hash_bucket *, + void *))show_vty_srdb, + (void *)vty); + } + return CMD_SUCCESS; +} + +/* Install new CLI commands */ +void ospf_sr_register_vty(void) +{ + install_element(VIEW_NODE, &show_ip_ospf_srdb_cmd); + + install_element(OSPF_NODE, &ospf_sr_enable_cmd); + install_element(OSPF_NODE, &no_ospf_sr_enable_cmd); + install_element(OSPF_NODE, &sr_global_label_range_cmd); + install_element(OSPF_NODE, &no_sr_global_label_range_cmd); + install_element(OSPF_NODE, &sr_node_msd_cmd); + install_element(OSPF_NODE, &no_sr_node_msd_cmd); + install_element(OSPF_NODE, &sr_prefix_sid_cmd); + install_element(OSPF_NODE, &no_sr_prefix_sid_cmd); +} diff --git a/ospfd/ospf_sr.h b/ospfd/ospf_sr.h new file mode 100644 index 0000000..fa61f66 --- /dev/null +++ b/ospfd/ospf_sr.h @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of Segment Routing + * as per RFC 8665 - OSPF Extensions for Segment Routing + * and RFC 8476 - Signaling Maximum SID Depth (MSD) Using OSPF + * + * Module name: Segment Routing header definitions + * + * Author: Olivier Dugeon + * Author: Anselme Sawadogo + * + * Copyright (C) 2016 - 2020 Orange Labs http://www.orange.com + */ + +#ifndef _FRR_OSPF_SR_H +#define _FRR_OSPF_SR_H + +/* macros and constants for segment routing */ +#define SET_RANGE_SIZE_MASK 0xffffff00 +#define GET_RANGE_SIZE_MASK 0x00ffffff +#define SET_LABEL_MASK 0xffffff00 +#define GET_LABEL_MASK 0x00ffffff +#define SET_RANGE_SIZE(range_size) ((range_size << 8) & SET_RANGE_SIZE_MASK) +#define GET_RANGE_SIZE(range_size) ((range_size >> 8) & GET_RANGE_SIZE_MASK) +#define SET_LABEL(label) ((label << 8) & SET_LABEL_MASK) +#define GET_LABEL(label) ((label >> 8) & GET_LABEL_MASK) + +/* smallest configurable SRGB / SRLB sizes */ +#define MIN_SRLB_SIZE 16 +#define MIN_SRGB_SIZE 16 + +/* Segment Routing TLVs as per RFC 8665 */ + +/* Segment ID could be a Label (3 bytes) or an Index (4 bytes) */ +#define SID_LABEL 3 +#define SID_LABEL_SIZE(U) (U - 1) +#define SID_INDEX 4 +#define SID_INDEX_SIZE(U) (U) + +/* Macro to log debug message */ +#define osr_debug(...) \ + do { \ + if (IS_DEBUG_OSPF_SR) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +/* Macro to check if SR Prefix has no valid route */ +#define IS_NO_ROUTE(srp) ((srp->route == NULL) || (srp->route->paths == NULL) \ + || list_isempty(srp->route->paths)) + +/* SID/Label Sub TLV - section 2.1 */ +#define SUBTLV_SID_LABEL 1 +#define SUBTLV_SID_LABEL_SIZE 4 +struct subtlv_sid_label { + /* Length is 3 (20 rightmost bits MPLS label) or 4 (32 bits SID) */ + struct tlv_header header; + uint32_t value; +}; + +/* + * Following section defines Segment Routing TLV (tag, length, value) + * structures, used in Router Information Opaque LSA. + */ + +/* RI SR-Algorithm TLV - section 3.1 */ +#define RI_SR_TLV_SR_ALGORITHM 8 +struct ri_sr_tlv_sr_algorithm { + struct tlv_header header; +#define SR_ALGORITHM_SPF 0 +#define SR_ALGORITHM_STRICT_SPF 1 +#define SR_ALGORITHM_UNSET 255 +#define ALGORITHM_COUNT 4 + /* Only 4 algorithms supported in this code */ + uint8_t value[ALGORITHM_COUNT]; +}; + +/* RI SID/Label Range TLV used for SRGB & SRLB - section 3.2 & 3.3 */ +#define RI_SR_TLV_SRGB_LABEL_RANGE 9 +#define RI_SR_TLV_SRLB_LABEL_RANGE 14 +#define RI_SR_TLV_LABEL_RANGE_SIZE 12 +struct ri_sr_tlv_sid_label_range { + struct tlv_header header; +/* Only 24 upper most bits are significant */ +#define SID_RANGE_LABEL_LENGTH 3 + uint32_t size; + /* A SID/Label sub-TLV will follow. */ + struct subtlv_sid_label lower; +}; + +/* RI Node/MSD TLV as per RFC 8476 */ +#define RI_SR_TLV_NODE_MSD 12 +#define RI_SR_TLV_NODE_MSD_SIZE 4 +struct ri_sr_tlv_node_msd { + struct tlv_header header; + uint8_t subtype; /* always = 1 */ + uint8_t value; + uint16_t padding; +}; + +/* + * Following section defines Segment Routing TLV (tag, length, value) + * structures, used in Extended Prefix/Link Opaque LSA. + */ + +/* Adj-SID and LAN-Ajd-SID subtlvs' flags */ +#define EXT_SUBTLV_LINK_ADJ_SID_BFLG 0x80 +#define EXT_SUBTLV_LINK_ADJ_SID_VFLG 0x40 +#define EXT_SUBTLV_LINK_ADJ_SID_LFLG 0x20 +#define EXT_SUBTLV_LINK_ADJ_SID_SFLG 0x10 + +/* Prefix SID subtlv Flags */ +#define EXT_SUBTLV_PREFIX_SID_NPFLG 0x40 +#define EXT_SUBTLV_PREFIX_SID_MFLG 0x20 +#define EXT_SUBTLV_PREFIX_SID_EFLG 0x10 +#define EXT_SUBTLV_PREFIX_SID_VFLG 0x08 +#define EXT_SUBTLV_PREFIX_SID_LFLG 0x04 + +/* SID/Label Binding subtlv Flags */ +#define EXT_SUBTLV_SID_BINDING_MFLG 0x80 + +/* Extended Prefix Range TLV - section 4 */ +#define EXT_TLV_PREF_RANGE 2 +#define EXT_SUBTLV_PREFIX_RANGE_SIZE 12 +struct ext_tlv_prefix_range { + struct tlv_header header; + uint8_t pref_length; + uint8_t af; + uint16_t range_size; + uint8_t flags; + uint8_t reserved[3]; + struct in_addr address; +}; + +/* Prefix SID Sub-TLV - section 5 */ +#define EXT_SUBTLV_PREFIX_SID 2 +#define EXT_SUBTLV_PREFIX_SID_SIZE 8 +struct ext_subtlv_prefix_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t algorithm; + uint32_t value; +}; + +/* Adj-SID Sub-TLV - section 6.1 */ +#define EXT_SUBTLV_ADJ_SID 2 +#define EXT_SUBTLV_ADJ_SID_SIZE 8 +struct ext_subtlv_adj_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t weight; + uint32_t value; +}; + +/* LAN Adj-SID Sub-TLV - section 6.2 */ +#define EXT_SUBTLV_LAN_ADJ_SID 3 +#define EXT_SUBTLV_LAN_ADJ_SID_SIZE 12 +struct ext_subtlv_lan_adj_sid { + struct tlv_header header; + uint8_t flags; + uint8_t reserved; + uint8_t mtid; + uint8_t weight; + struct in_addr neighbor_id; + uint32_t value; +}; + +/* + * Following section define structure used to manage Segment Routing + * information and TLVs / SubTLVs + */ +/* Default min and size of SR Global Block label range */ +#define DEFAULT_SRGB_LABEL 16000 +#define DEFAULT_SRGB_SIZE 8000 +#define DEFAULT_SRGB_END (DEFAULT_SRGB_LABEL + DEFAULT_SRGB_SIZE - 1) + +/* Default min and size of SR Local Block label range */ +#define DEFAULT_SRLB_LABEL 15000 +#define DEFAULT_SRLB_SIZE 1000 +#define DEFAULT_SRLB_END (DEFAULT_SRLB_LABEL + DEFAULT_SRLB_SIZE - 1) + +/* Structure aggregating SR Range Block info retrieved from an lsa */ +struct sr_block { + uint32_t range_size; + uint32_t lower_bound; +}; + +/* Segment Routing Global Block allocation */ +struct sr_global_block { + bool reserved; + uint32_t start; + uint32_t size; +}; + +/* Segment Routing Local Block allocation */ +struct sr_local_block { + bool reserved; + uint32_t start; + uint32_t end; + uint32_t current; + uint32_t max_block; + uint64_t *used_mark; +}; +#define SRLB_BLOCK_SIZE 64 + +/* SID type to make difference between loopback interfaces and others */ +enum sid_type { PREF_SID, LOCAL_SID, ADJ_SID, LAN_ADJ_SID }; + +/* Status of Segment Routing: Off (Disable), On (Enable), (Up) Started */ +enum sr_status { SR_OFF, SR_ON, SR_UP }; + +/* Structure aggregating all OSPF Segment Routing information for the node */ +struct ospf_sr_db { + /* Status of Segment Routing */ + enum sr_status status; + + /* Flooding Scope: Area = 10 or AS = 11 */ + uint8_t scope; + + /* FRR SR node */ + struct sr_node *self; + + /* List of neighbour SR nodes */ + struct hash *neighbors; + + /* Local SR info announced in Router Info LSA */ + + /* Algorithms supported by the node */ + uint8_t algo[ALGORITHM_COUNT]; + /* + * Segment Routing Global Block i.e. label range + * Only one range supported in this code + */ + struct sr_global_block srgb; + + /* Segment Routing Local Block */ + struct sr_local_block srlb; + + /* Maximum SID Depth supported by the node */ + uint8_t msd; + + /* Thread timer to start Label Manager */ + struct event *t_start_lm; +}; + +/* Structure aggregating all received SR info from LSAs by node */ +struct sr_node { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + uint8_t algo[ALGORITHM_COUNT]; /* Algorithms supported by the node */ + struct sr_block srgb; /* Segment Routing Global Block */ + struct sr_block srlb; /* Segment Routing Local Block */ + uint8_t msd; /* Maximum SID Depth */ + + /* List of Prefix & Link advertise by this node */ + struct list *ext_prefix; /* For Node SID */ + struct list *ext_link; /* For Adjacency SID */ + + /* Pointer to FRR SR-Node or NULL if it is not a neighbor */ + struct sr_node *neighbor; +}; + +/* Segment Routing - NHLFE info: support IPv4 Only */ +struct sr_nhlfe { + struct in_addr nexthop; + ifindex_t ifindex; + mpls_label_t label_in; + mpls_label_t label_out; +}; + +/* Structure aggregating all Segment Routing Link information */ +/* Link are generally advertised by pair: primary + backup */ +struct sr_link { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + /* Addressed (remote) router id */ + struct in_addr remote_id; + + /* Interface address */ + struct in_addr itf_addr; + + /* Flags to manage this link parameters. */ + uint8_t flags[2]; + + /* Segment Routing ID */ + uint32_t sid[2]; + enum sid_type type; + + /* SR NHLFE (Primary + Backup) for this link */ + struct sr_nhlfe nhlfe[2]; + + /* Back pointer to SR Node which advertise this Link */ + struct sr_node *srn; +}; + +/* Structure aggregating all Segment Routing Prefix information */ +struct sr_prefix { + struct in_addr adv_router; /* used to identify sender of LSA */ + /* 24-bit Opaque-ID field value according to RFC 7684 specification */ + uint32_t instance; + + /* Prefix itself */ + struct prefix_ipv4 prefv4; + + /* Flags to manage this prefix parameters. */ + uint8_t flags; + + /* Segment Routing ID */ + uint32_t sid; + enum sid_type type; + + /* Incoming label for this prefix */ + mpls_label_t label_in; + + /* Back pointer to OSPF Route for remote prefix */ + struct ospf_route *route; + + /* NHLFE for local prefix */ + struct sr_nhlfe nhlfe; + + /* Back pointer to SR Node which advertise this Prefix */ + struct sr_node *srn; +}; + +/* Prototypes definition */ +/* Segment Routing initialisation functions */ +extern int ospf_sr_init(void); +extern void ospf_sr_term(void); +extern void ospf_sr_finish(void); +/* Segment Routing label allocation functions */ +extern mpls_label_t ospf_sr_local_block_request_label(void); +extern int ospf_sr_local_block_release_label(mpls_label_t label); +/* Segment Routing LSA update & delete functions */ +extern void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ri_lsa_delete(struct ospf_lsa *lsa); +extern void ospf_sr_ext_link_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ext_link_lsa_delete(struct ospf_lsa *lsa); +extern void ospf_sr_ext_prefix_lsa_update(struct ospf_lsa *lsa); +extern void ospf_sr_ext_prefix_lsa_delete(struct ospf_lsa *lsa); +/* Segment Routing Extending Link management */ +struct ext_itf; +extern void ospf_sr_ext_itf_add(struct ext_itf *exti); +extern void ospf_sr_ext_itf_delete(struct ext_itf *exti); +/* Segment Routing configuration functions */ +extern void ospf_sr_config_write_router(struct vty *vty); +extern void ospf_sr_update_local_prefix(struct interface *ifp, + struct prefix *p); +/* Segment Routing re-routing function */ +extern void ospf_sr_update_task(struct ospf *ospf); + +/* Support for TI-LFA */ +extern mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id); +extern mpls_label_t ospf_sr_get_adj_sid_by_id(struct in_addr *root_id, + struct in_addr *neighbor_id); +extern struct sr_node *ospf_sr_node_create(struct in_addr *rid); + +#endif /* _FRR_OSPF_SR_H */ diff --git a/ospfd/ospf_te.c b/ospfd/ospf_te.c new file mode 100644 index 0000000..d57990e --- /dev/null +++ b/ospfd/ospf_te.c @@ -0,0 +1,4683 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC3630 + * Copyright (C) 2001 KDD R&D Laboratories, Inc. + * http://www.kddlabs.co.jp/ + * + * Copyright (C) 2012 Orange Labs + * http://www.orange.com + */ + +/* Add support of RFC7471 */ +/* Add support of RFC5392, RFC6827 */ + +#include +#include + +#include "linklist.h" +#include "prefix.h" +#include "vrf.h" +#include "if.h" +#include "table.h" +#include "memory.h" +#include "command.h" +#include "vty.h" +#include "stream.h" +#include "log.h" +#include "frrevent.h" +#include "hash.h" +#include "sockunion.h" /* for inet_aton() */ +#include "network.h" +#include "link_state.h" +#include "zclient.h" +#include "printfrr.h" +#include + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_te.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ri.h" +#include "ospfd/ospf_ext.h" +#include "ospfd/ospf_vty.h" +#include "ospfd/ospf_errors.h" + +/* + * Global variable to manage Opaque-LSA/MPLS-TE on this node. + * Note that all parameter values are stored in network byte order. + */ +struct ospf_mpls_te OspfMplsTE; + +static const char *const mode2text[] = {"Off", "AS", "Area"}; + + +/*------------------------------------------------------------------------* + * Following are initialize/terminate functions for MPLS-TE handling. + *------------------------------------------------------------------------*/ + +static int ospf_mpls_te_new_if(struct interface *ifp); +static int ospf_mpls_te_del_if(struct interface *ifp); +static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_status); +static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_status); +static void ospf_mpls_te_config_write_router(struct vty *vty); +static void ospf_mpls_te_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa); +static int ospf_mpls_te_lsa_originate_area(void *arg); +static int ospf_mpls_te_lsa_inter_as_as(void *arg); +static int ospf_mpls_te_lsa_inter_as_area(void *arg); +static struct ospf_lsa *ospf_mpls_te_lsa_refresh(struct ospf_lsa *lsa); +static int ospf_mpls_te_lsa_update(struct ospf_lsa *lsa); +static int ospf_mpls_te_lsa_delete(struct ospf_lsa *lsa); + +static void del_mpls_te_link(void *val); +static void ospf_mpls_te_register_vty(void); + +int ospf_mpls_te_init(void) +{ + int rc; + + /* Register Opaque AREA LSA Type 1 for Traffic Engineering */ + rc = ospf_register_opaque_functab( + OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, + ospf_mpls_te_new_if, + ospf_mpls_te_del_if, + ospf_mpls_te_ism_change, + ospf_mpls_te_nsm_change, + ospf_mpls_te_config_write_router, + NULL, /* ospf_mpls_te_config_write_if */ + NULL, /* ospf_mpls_te_config_write_debug */ + ospf_mpls_te_show_info, ospf_mpls_te_lsa_originate_area, + ospf_mpls_te_lsa_refresh, + ospf_mpls_te_lsa_update, /* ospf_mpls_te_new_lsa_hook */ + ospf_mpls_te_lsa_delete /* ospf_mpls_te_del_lsa_hook */); + if (rc != 0) { + flog_warn( + EC_OSPF_OPAQUE_REGISTRATION, + "MPLS-TE (%s): Failed to register Traffic Engineering functions", + __func__); + return rc; + } + + /* + * Wee need also to register Opaque LSA Type 6 i.e. Inter-AS RFC5392 for + * both AREA and AS at least to have the possibility to call the show() + * function when looking to the opaque LSA of the OSPF database. + */ + rc = ospf_register_opaque_functab(OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_INTER_AS_LSA, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + ospf_mpls_te_show_info, + ospf_mpls_te_lsa_inter_as_area, + ospf_mpls_te_lsa_refresh, NULL, NULL); + if (rc != 0) { + flog_warn( + EC_OSPF_OPAQUE_REGISTRATION, + "MPLS-TE (%s): Failed to register Inter-AS with Area scope", + __func__); + return rc; + } + + rc = ospf_register_opaque_functab(OSPF_OPAQUE_AS_LSA, + OPAQUE_TYPE_INTER_AS_LSA, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + ospf_mpls_te_show_info, + ospf_mpls_te_lsa_inter_as_as, + ospf_mpls_te_lsa_refresh, NULL, NULL); + if (rc != 0) { + flog_warn( + EC_OSPF_OPAQUE_REGISTRATION, + "MPLS-TE (%s): Failed to register Inter-AS with AS scope", + __func__); + return rc; + } + + memset(&OspfMplsTE, 0, sizeof(OspfMplsTE)); + OspfMplsTE.enabled = false; + OspfMplsTE.export = false; + OspfMplsTE.inter_as = Off; + OspfMplsTE.iflist = list_new(); + OspfMplsTE.iflist->del = del_mpls_te_link; + + ospf_mpls_te_register_vty(); + + return rc; +} + +void ospf_mpls_te_term(void) +{ + list_delete(&OspfMplsTE.iflist); + + ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA); + ospf_delete_opaque_functab(OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_INTER_AS_LSA); + ospf_delete_opaque_functab(OSPF_OPAQUE_AS_LSA, + OPAQUE_TYPE_INTER_AS_LSA); + + OspfMplsTE.enabled = false; + OspfMplsTE.inter_as = Off; + OspfMplsTE.export = false; + + return; +} + +void ospf_mpls_te_finish(void) +{ + OspfMplsTE.enabled = false; + OspfMplsTE.inter_as = Off; + OspfMplsTE.export = false; +} + +/*------------------------------------------------------------------------* + * Following are control functions for MPLS-TE parameters management. + *------------------------------------------------------------------------*/ +static void del_mpls_te_link(void *val) +{ + XFREE(MTYPE_OSPF_MPLS_TE, val); + return; +} + +static uint32_t get_mpls_te_instance_value(void) +{ + static uint32_t seqno = 0; + + if (seqno < MAX_LEGAL_TE_INSTANCE_NUM) + seqno += 1; + else + seqno = 1; /* Avoid zero. */ + + return seqno; +} + +static struct mpls_te_link *lookup_linkparams_by_ifp(struct interface *ifp) +{ + struct listnode *node, *nnode; + struct mpls_te_link *lp; + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) + if (lp->ifp == ifp) + return lp; + + return NULL; +} + +static struct mpls_te_link *lookup_linkparams_by_instance(struct ospf_lsa *lsa) +{ + struct listnode *node; + struct mpls_te_link *lp; + unsigned int key = GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)); + + for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) + if (lp->instance == key) + return lp; + + ote_debug("MPLS-TE (%s): Entry not found: key(%x)", __func__, key); + return NULL; +} + +static void ospf_mpls_te_foreach_area( + void (*func)(struct mpls_te_link *lp, enum lsa_opcode sched_opcode), + enum lsa_opcode sched_opcode) +{ + struct listnode *node, *nnode; + struct listnode *node2; + struct mpls_te_link *lp; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { + /* Skip Inter-AS TEv2 Links */ + if (IS_INTER_AS(lp->type)) + continue; + if ((area = lp->area) == NULL) + continue; + if (CHECK_FLAG(lp->flags, LPFLG_LOOKUP_DONE)) + continue; + + if (func != NULL) + (*func)(lp, sched_opcode); + + for (node2 = listnextnode(node); node2; + node2 = listnextnode(node2)) + if ((lp = listgetdata(node2)) != NULL) + if (lp->area != NULL) + if (IPV4_ADDR_SAME(&lp->area->area_id, + &area->area_id)) + SET_FLAG(lp->flags, + LPFLG_LOOKUP_DONE); + } + + for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) + if (lp->area != NULL) + UNSET_FLAG(lp->flags, LPFLG_LOOKUP_DONE); + + return; +} + +static void set_mpls_te_router_addr(struct in_addr ipv4) +{ + OspfMplsTE.router_addr.header.type = htons(TE_TLV_ROUTER_ADDR); + OspfMplsTE.router_addr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + OspfMplsTE.router_addr.value = ipv4; + return; +} + +static void set_linkparams_link_header(struct mpls_te_link *lp) +{ + uint16_t length = 0; + + /* TE_LINK_SUBTLV_LINK_TYPE */ + if (ntohs(lp->link_type.header.type) != 0) + length += TLV_SIZE(&lp->link_type.header); + + /* TE_LINK_SUBTLV_LINK_ID */ + if (ntohs(lp->link_id.header.type) != 0) + length += TLV_SIZE(&lp->link_id.header); + + /* TE_LINK_SUBTLV_LCLIF_IPADDR */ + if (lp->lclif_ipaddr.header.type != 0) + length += TLV_SIZE(&lp->lclif_ipaddr.header); + + /* TE_LINK_SUBTLV_RMTIF_IPADDR */ + if (lp->rmtif_ipaddr.header.type != 0) + length += TLV_SIZE(&lp->rmtif_ipaddr.header); + + /* TE_LINK_SUBTLV_TE_METRIC */ + if (ntohs(lp->te_metric.header.type) != 0) + length += TLV_SIZE(&lp->te_metric.header); + + /* TE_LINK_SUBTLV_MAX_BW */ + if (ntohs(lp->max_bw.header.type) != 0) + length += TLV_SIZE(&lp->max_bw.header); + + /* TE_LINK_SUBTLV_MAX_RSV_BW */ + if (ntohs(lp->max_rsv_bw.header.type) != 0) + length += TLV_SIZE(&lp->max_rsv_bw.header); + + /* TE_LINK_SUBTLV_UNRSV_BW */ + if (ntohs(lp->unrsv_bw.header.type) != 0) + length += TLV_SIZE(&lp->unrsv_bw.header); + + /* TE_LINK_SUBTLV_RSC_CLSCLR */ + if (ntohs(lp->rsc_clsclr.header.type) != 0) + length += TLV_SIZE(&lp->rsc_clsclr.header); + + /* TE_LINK_SUBTLV_LLRI */ + if (ntohs(lp->llri.header.type) != 0) + length += TLV_SIZE(&lp->llri.header); + + /* TE_LINK_SUBTLV_RIP */ + if (ntohs(lp->rip.header.type) != 0) + length += TLV_SIZE(&lp->rip.header); + + /* TE_LINK_SUBTLV_RAS */ + if (ntohs(lp->ras.header.type) != 0) + length += TLV_SIZE(&lp->ras.header); + + /* TE_LINK_SUBTLV_LRRID */ + if (ntohs(lp->lrrid.header.type) != 0) + length += TLV_SIZE(&lp->lrrid.header); + + /* TE_LINK_SUBTLV_AV_DELAY */ + if (ntohs(lp->av_delay.header.type) != 0) + length += TLV_SIZE(&lp->av_delay.header); + + /* TE_LINK_SUBTLV_MM_DELAY */ + if (ntohs(lp->mm_delay.header.type) != 0) + length += TLV_SIZE(&lp->mm_delay.header); + + /* TE_LINK_SUBTLV_DELAY_VAR */ + if (ntohs(lp->delay_var.header.type) != 0) + length += TLV_SIZE(&lp->delay_var.header); + + /* TE_LINK_SUBTLV_PKT_LOSS */ + if (ntohs(lp->pkt_loss.header.type) != 0) + length += TLV_SIZE(&lp->pkt_loss.header); + + /* TE_LINK_SUBTLV_RES_BW */ + if (ntohs(lp->res_bw.header.type) != 0) + length += TLV_SIZE(&lp->res_bw.header); + + /* TE_LINK_SUBTLV_AVA_BW */ + if (ntohs(lp->ava_bw.header.type) != 0) + length += TLV_SIZE(&lp->ava_bw.header); + + /* TE_LINK_SUBTLV_USE_BW */ + if (ntohs(lp->use_bw.header.type) != 0) + length += TLV_SIZE(&lp->use_bw.header); + + lp->link_header.header.type = htons(TE_TLV_LINK); + lp->link_header.header.length = htons(length); + + return; +} + +static void set_linkparams_link_type(struct ospf_interface *oi, + struct mpls_te_link *lp) +{ + lp->link_type.header.type = htons(TE_LINK_SUBTLV_LINK_TYPE); + lp->link_type.header.length = htons(TE_LINK_SUBTLV_TYPE_SIZE); + + switch (oi->type) { + case OSPF_IFTYPE_POINTOPOINT: + lp->link_type.link_type.value = LINK_TYPE_SUBTLV_VALUE_PTP; + break; + case OSPF_IFTYPE_BROADCAST: + case OSPF_IFTYPE_NBMA: + lp->link_type.link_type.value = LINK_TYPE_SUBTLV_VALUE_MA; + break; + default: + /* Not supported yet. */ /* XXX */ + lp->link_type.header.type = htons(0); + break; + } + return; +} + +static void set_linkparams_link_id(struct mpls_te_link *lp, + struct in_addr link_id) +{ + + lp->link_id.header.type = htons(TE_LINK_SUBTLV_LINK_ID); + lp->link_id.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->link_id.value = link_id; + return; +} + +static void set_linkparams_lclif_ipaddr(struct mpls_te_link *lp, + struct in_addr lclif) +{ + + lp->lclif_ipaddr.header.type = htons(TE_LINK_SUBTLV_LCLIF_IPADDR); + lp->lclif_ipaddr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->lclif_ipaddr.value[0] = lclif; + return; +} + +static void set_linkparams_rmtif_ipaddr(struct mpls_te_link *lp, + struct in_addr rmtif) +{ + + lp->rmtif_ipaddr.header.type = htons(TE_LINK_SUBTLV_RMTIF_IPADDR); + lp->rmtif_ipaddr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->rmtif_ipaddr.value[0] = rmtif; + return; +} + +static void set_linkparams_te_metric(struct mpls_te_link *lp, + uint32_t te_metric) +{ + lp->te_metric.header.type = htons(TE_LINK_SUBTLV_TE_METRIC); + lp->te_metric.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->te_metric.value = htonl(te_metric); + return; +} + +static void set_linkparams_max_bw(struct mpls_te_link *lp, float fp) +{ + lp->max_bw.header.type = htons(TE_LINK_SUBTLV_MAX_BW); + lp->max_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->max_bw.value = htonf(fp); + return; +} + +static void set_linkparams_max_rsv_bw(struct mpls_te_link *lp, float fp) +{ + lp->max_rsv_bw.header.type = htons(TE_LINK_SUBTLV_MAX_RSV_BW); + lp->max_rsv_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->max_rsv_bw.value = htonf(fp); + return; +} + +static void set_linkparams_unrsv_bw(struct mpls_te_link *lp, int priority, + float fp) +{ + /* Note that TLV-length field is the size of array. */ + lp->unrsv_bw.header.type = htons(TE_LINK_SUBTLV_UNRSV_BW); + lp->unrsv_bw.header.length = htons(TE_LINK_SUBTLV_UNRSV_SIZE); + lp->unrsv_bw.value[priority] = htonf(fp); + return; +} + +static void set_linkparams_rsc_clsclr(struct mpls_te_link *lp, + uint32_t classcolor) +{ + lp->rsc_clsclr.header.type = htons(TE_LINK_SUBTLV_RSC_CLSCLR); + lp->rsc_clsclr.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->rsc_clsclr.value = htonl(classcolor); + return; +} + +static void set_linkparams_inter_as(struct mpls_te_link *lp, + struct in_addr addr, uint32_t as) +{ + + /* Set the Remote ASBR IP address and then the associated AS number */ + lp->rip.header.type = htons(TE_LINK_SUBTLV_RIP); + lp->rip.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->rip.value = addr; + + lp->ras.header.type = htons(TE_LINK_SUBTLV_RAS); + lp->ras.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->ras.value = htonl(as); + + /* Set Type & Flooding flag accordingly */ + lp->type = INTER_AS; + if (OspfMplsTE.inter_as == AS) + SET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); + else + UNSET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); +} + +static void unset_linkparams_inter_as(struct mpls_te_link *lp) +{ + + /* Reset the Remote ASBR IP address and then the associated AS number */ + lp->rip.header.type = htons(0); + lp->rip.header.length = htons(0); + lp->rip.value.s_addr = htonl(0); + + lp->ras.header.type = htons(0); + lp->ras.header.length = htons(0); + lp->ras.value = htonl(0); + + /* Reset Type & Flooding flag accordingly */ + lp->type = STD_TE; + UNSET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); +} + +void set_linkparams_llri(struct mpls_te_link *lp, uint32_t local, + uint32_t remote) +{ + + lp->llri.header.type = htons(TE_LINK_SUBTLV_LLRI); + lp->llri.header.length = htons(TE_LINK_SUBTLV_LLRI_SIZE); + lp->llri.local = htonl(local); + lp->llri.remote = htonl(remote); +} + +void set_linkparams_lrrid(struct mpls_te_link *lp, struct in_addr local, + struct in_addr remote) +{ + + lp->lrrid.header.type = htons(TE_LINK_SUBTLV_LRRID); + lp->lrrid.header.length = htons(TE_LINK_SUBTLV_LRRID_SIZE); + lp->lrrid.local.s_addr = local.s_addr; + lp->lrrid.remote.s_addr = remote.s_addr; +} + +static void set_linkparams_av_delay(struct mpls_te_link *lp, uint32_t delay, + uint8_t anormal) +{ + uint32_t tmp; + /* Note that TLV-length field is the size of array. */ + lp->av_delay.header.type = htons(TE_LINK_SUBTLV_AV_DELAY); + lp->av_delay.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + tmp = delay & TE_EXT_MASK; + if (anormal) + tmp |= TE_EXT_ANORMAL; + lp->av_delay.value = htonl(tmp); + return; +} + +static void set_linkparams_mm_delay(struct mpls_te_link *lp, uint32_t low, + uint32_t high, uint8_t anormal) +{ + uint32_t tmp; + /* Note that TLV-length field is the size of array. */ + lp->mm_delay.header.type = htons(TE_LINK_SUBTLV_MM_DELAY); + lp->mm_delay.header.length = htons(TE_LINK_SUBTLV_MM_DELAY_SIZE); + tmp = low & TE_EXT_MASK; + if (anormal) + tmp |= TE_EXT_ANORMAL; + lp->mm_delay.low = htonl(tmp); + lp->mm_delay.high = htonl(high); + return; +} + +static void set_linkparams_delay_var(struct mpls_te_link *lp, uint32_t jitter) +{ + /* Note that TLV-length field is the size of array. */ + lp->delay_var.header.type = htons(TE_LINK_SUBTLV_DELAY_VAR); + lp->delay_var.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->delay_var.value = htonl(jitter & TE_EXT_MASK); + return; +} + +static void set_linkparams_pkt_loss(struct mpls_te_link *lp, uint32_t loss, + uint8_t anormal) +{ + uint32_t tmp; + /* Note that TLV-length field is the size of array. */ + lp->pkt_loss.header.type = htons(TE_LINK_SUBTLV_PKT_LOSS); + lp->pkt_loss.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + tmp = loss & TE_EXT_MASK; + if (anormal) + tmp |= TE_EXT_ANORMAL; + lp->pkt_loss.value = htonl(tmp); + return; +} + +static void set_linkparams_res_bw(struct mpls_te_link *lp, float fp) +{ + /* Note that TLV-length field is the size of array. */ + lp->res_bw.header.type = htons(TE_LINK_SUBTLV_RES_BW); + lp->res_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->res_bw.value = htonf(fp); + return; +} + +static void set_linkparams_ava_bw(struct mpls_te_link *lp, float fp) +{ + /* Note that TLV-length field is the size of array. */ + lp->ava_bw.header.type = htons(TE_LINK_SUBTLV_AVA_BW); + lp->ava_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->ava_bw.value = htonf(fp); + return; +} + +static void set_linkparams_use_bw(struct mpls_te_link *lp, float fp) +{ + /* Note that TLV-length field is the size of array. */ + lp->use_bw.header.type = htons(TE_LINK_SUBTLV_USE_BW); + lp->use_bw.header.length = htons(TE_LINK_SUBTLV_DEF_SIZE); + lp->use_bw.value = htonf(fp); + return; +} + +/* Update TE parameters from Interface */ +static void update_linkparams(struct mpls_te_link *lp) +{ + int i; + struct interface *ifp; + + /* Get the Interface structure */ + if ((ifp = lp->ifp) == NULL) { + ote_debug( + "MPLS-TE (%s): Abort update TE parameters: no interface associated to Link Parameters", + __func__); + return; + } + if (!HAS_LINK_PARAMS(ifp)) { + ote_debug( + "MPLS-TE (%s): Abort update TE parameters: no Link Parameters for interface", + __func__); + return; + } + + /* RFC3630 metrics */ + if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) + set_linkparams_rsc_clsclr(lp, ifp->link_params->admin_grp); + else + TLV_TYPE(lp->rsc_clsclr) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) + set_linkparams_max_bw(lp, ifp->link_params->max_bw); + else + TLV_TYPE(lp->max_bw) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) + set_linkparams_max_rsv_bw(lp, ifp->link_params->max_rsv_bw); + else + TLV_TYPE(lp->max_rsv_bw) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) + for (i = 0; i < MAX_CLASS_TYPE; i++) + set_linkparams_unrsv_bw(lp, i, + ifp->link_params->unrsv_bw[i]); + else + TLV_TYPE(lp->unrsv_bw) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) + set_linkparams_te_metric(lp, ifp->link_params->te_metric); + else + TLV_TYPE(lp->te_metric) = 0; + + /* TE metric Extensions */ + if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) + set_linkparams_av_delay(lp, ifp->link_params->av_delay, 0); + else + TLV_TYPE(lp->av_delay) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) + set_linkparams_mm_delay(lp, ifp->link_params->min_delay, + ifp->link_params->max_delay, 0); + else + TLV_TYPE(lp->mm_delay) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) + set_linkparams_delay_var(lp, ifp->link_params->delay_var); + else + TLV_TYPE(lp->delay_var) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) + set_linkparams_pkt_loss(lp, ifp->link_params->pkt_loss, 0); + else + TLV_TYPE(lp->pkt_loss) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) + set_linkparams_res_bw(lp, ifp->link_params->res_bw); + else + TLV_TYPE(lp->res_bw) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) + set_linkparams_ava_bw(lp, ifp->link_params->ava_bw); + else + TLV_TYPE(lp->ava_bw) = 0; + + if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) + set_linkparams_use_bw(lp, ifp->link_params->use_bw); + else + TLV_TYPE(lp->use_bw) = 0; + + /* RFC5392 */ + if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) { + /* Flush LSA if it engaged and was previously a STD_TE one */ + if (IS_STD_TE(lp->type) + && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + ote_debug( + "MPLS-TE (%s): Update IF: Switch from Standard LSA to INTER-AS for %s[%d/%d]", + __func__, ifp->name, lp->flags, lp->type); + + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + /* Then, switch it to INTER-AS */ + if (OspfMplsTE.inter_as == AS) { + lp->type = INTER_AS; + SET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); + } else { + lp->type = INTER_AS; + UNSET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); + lp->area = ospf_area_lookup_by_area_id( + ospf_lookup_by_vrf_id(VRF_DEFAULT), + OspfMplsTE.interas_areaid); + } + } + set_linkparams_inter_as(lp, ifp->link_params->rmt_ip, + ifp->link_params->rmt_as); + } else { + ote_debug( + "MPLS-TE (%s): Update IF: Switch from INTER-AS LSA to Standard for %s[%d/%d]", + __func__, ifp->name, lp->flags, lp->type); + + /* reset inter-as TE params */ + /* Flush LSA if it engaged and was previously an INTER_AS one */ + if (IS_INTER_AS(lp->type) + && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + /* Then, switch it to Standard TE */ + lp->flags = STD_TE; + UNSET_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS); + } + unset_linkparams_inter_as(lp); + } +} + +static void initialize_linkparams(struct mpls_te_link *lp) +{ + struct interface *ifp = lp->ifp; + struct ospf_interface *oi = NULL; + struct route_node *rn; + + ote_debug("MPLS-TE (%s): Initialize Link Parameters for interface %s", + __func__, ifp->name); + + /* Search OSPF Interface parameters for this interface */ + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + + if ((oi = rn->info) == NULL) + continue; + + if (oi->ifp == ifp) + break; + } + + if ((oi == NULL) || (oi->ifp != ifp)) { + ote_debug( + "MPLS-TE (%s): Could not find corresponding OSPF Interface for %s", + __func__, ifp->name); + return; + } + + /* + * Try to set initial values those can be derived from + * zebra-interface information. + */ + set_linkparams_link_type(oi, lp); + + /* Set local IP addr */ + set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4); + + /* Set Remote IP addr if Point to Point Interface */ + if (oi->type == OSPF_IFTYPE_POINTOPOINT) { + struct prefix *pref = CONNECTED_PREFIX(oi->connected); + if (pref != NULL) + set_linkparams_rmtif_ipaddr(lp, pref->u.prefix4); + } + + /* Keep Area information in combination with link parameters. */ + lp->area = oi->area; + + return; +} + +static int is_mandated_params_set(struct mpls_te_link *lp) +{ + int rc = 0; + + if (ntohs(OspfMplsTE.router_addr.header.type) == 0) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Missing Router Address", __func__); + return rc; + } + + if (ntohs(lp->link_type.header.type) == 0) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Missing Link Type", __func__); + return rc; + } + + if (!IS_INTER_AS(lp->type) && (ntohs(lp->link_id.header.type) == 0)) { + flog_warn(EC_OSPF_TE_UNEXPECTED, "MPLS-TE (%s) Missing Link ID", + __func__); + return rc; + } + + rc = 1; + return rc; +} + +/*------------------------------------------------------------------------* + * Following are callback functions against generic Opaque-LSAs handling. + *------------------------------------------------------------------------*/ + +static int ospf_mpls_te_new_if(struct interface *ifp) +{ + struct mpls_te_link *new; + + ote_debug("MPLS-TE (%s): Add new %s interface %s to MPLS-TE list", + __func__, ifp->link_params ? "Active" : "Inactive", + ifp->name); + + if (lookup_linkparams_by_ifp(ifp) != NULL) + return 0; + + new = XCALLOC(MTYPE_OSPF_MPLS_TE, sizeof(struct mpls_te_link)); + + new->instance = get_mpls_te_instance_value(); + new->ifp = ifp; + /* By default TE-Link is RFC3630 compatible flooding in Area and not + * active */ + /* This default behavior will be adapted with call to + * ospf_mpls_te_update_if() */ + new->type = STD_TE; + new->flags = LPFLG_LSA_INACTIVE; + + /* Initialize Link Parameters from Interface */ + initialize_linkparams(new); + + /* Set TE Parameters from Interface */ + update_linkparams(new); + + /* Add Link Parameters structure to the list */ + listnode_add(OspfMplsTE.iflist, new); + + ote_debug("MPLS-TE (%s): Add new LP context for %s[%d/%d]", __func__, + ifp->name, new->flags, new->type); + + /* Schedule Opaque-LSA refresh. */ /* XXX */ + return 0; +} + +static int ospf_mpls_te_del_if(struct interface *ifp) +{ + struct mpls_te_link *lp; + int rc = -1; + + if ((lp = lookup_linkparams_by_ifp(ifp)) != NULL) { + struct list *iflist = OspfMplsTE.iflist; + + /* Dequeue listnode entry from the list. */ + listnode_delete(iflist, lp); + + XFREE(MTYPE_OSPF_MPLS_TE, lp); + } + + /* Schedule Opaque-LSA refresh. */ /* XXX */ + + rc = 0; + return rc; +} + +/* Main initialization / update function of the MPLS TE Link context */ + +/* Call when interface TE Link parameters are modified */ +void ospf_mpls_te_update_if(struct interface *ifp) +{ + struct mpls_te_link *lp; + + ote_debug("MPLS-TE (%s): Update LSA parameters for interface %s [%s]", + __func__, ifp->name, HAS_LINK_PARAMS(ifp) ? "ON" : "OFF"); + + /* Get Link context from interface */ + if ((lp = lookup_linkparams_by_ifp(ifp)) == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Did not find Link Parameters context for interface %s", + __func__, ifp->name); + return; + } + + /* Fulfill MPLS-TE Link TLV from Interface TE Link parameters */ + if (HAS_LINK_PARAMS(ifp)) { + SET_FLAG(lp->flags, LPFLG_LSA_ACTIVE); + + /* Update TE parameters */ + update_linkparams(lp); + + /* Finally Re-Originate or Refresh Opaque LSA if MPLS_TE is + * enabled */ + if (OspfMplsTE.enabled) + if (lp->area != NULL) { + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) + ospf_mpls_te_lsa_schedule( + lp, REFRESH_THIS_LSA); + else + ospf_mpls_te_lsa_schedule( + lp, REORIGINATE_THIS_LSA); + } + } else { + /* If MPLS TE is disable on this interface, flush LSA if it is + * already engaged */ + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + else + /* Reset Activity flag */ + lp->flags = LPFLG_LSA_INACTIVE; + } + + return; +} + +/* + * Just add interface and set available information. Other information + * and flooding of LSA will be done later when adjacency will be up + * See ospf_mpls_te_nsm_change() after + */ +static void ospf_mpls_te_ism_change(struct ospf_interface *oi, int old_state) +{ + + struct mpls_te_link *lp; + + lp = lookup_linkparams_by_ifp(oi->ifp); + if (lp == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Cannot get linkparams from OI(%s)?", + __func__, IF_NAME(oi)); + return; + } + + if (oi->area == NULL || oi->area->ospf == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", + __func__, IF_NAME(oi)); + return; + } + + /* Keep Area information in combination with linkparams. */ + lp->area = oi->area; + + switch (oi->state) { + case ISM_PointToPoint: + case ISM_DROther: + case ISM_Backup: + case ISM_DR: + /* Set Link type and Local IP addr */ + set_linkparams_link_type(oi, lp); + set_linkparams_lclif_ipaddr(lp, oi->address->u.prefix4); + + break; + case ISM_Down: + /* Interface goes Down: Flush LSA if engaged */ + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + ote_debug( + "MPLS-TE (%s): Interface %s goes down: flush LSA", + __func__, IF_NAME(oi)); + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + return; + } + break; + default: + break; + } + + ote_debug("MPLS-TE (%s): Update Link parameters for interface %s", + __func__, IF_NAME(oi)); + + return; +} + +/* + * Complete TE info and schedule LSA flooding + * Link-ID and Remote IP address must be set with neighbor info + * which are only valid once NSM state is FULL + */ +static void ospf_mpls_te_nsm_change(struct ospf_neighbor *nbr, int old_state) +{ + struct ospf_interface *oi = nbr->oi; + struct mpls_te_link *lp; + + /* Process Link only when neighbor old or new state is NSM Full */ + if (nbr->state != NSM_Full && old_state != NSM_Full) + return; + + /* Get interface information for Traffic Engineering */ + lp = lookup_linkparams_by_ifp(oi->ifp); + if (lp == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Cannot get linkparams from OI(%s)?", + __func__, IF_NAME(oi)); + return; + } + + if (oi->area == NULL || oi->area->ospf == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Cannot refer to OSPF from OI(%s)?", + __func__, IF_NAME(oi)); + return; + } + + /* Flush TE Opaque LSA if Neighbor State goes Down or Deleted */ + if (OspfMplsTE.enabled + && (nbr->state == NSM_Down || nbr->state == NSM_Deleted)) { + if (CHECK_FLAG(lp->flags, EXT_LPFLG_LSA_ENGAGED)) { + ote_debug( + "MPLS-TE (%s): Interface %s goes down: flush LSA", + __func__, IF_NAME(oi)); + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + } + return; + } + + /* Keep Area information in combination with SR info. */ + lp->area = oi->area; + + /* + * The Link ID is identical to the contents of the Link ID field + * in the Router LSA for these link types. + */ + switch (oi->state) { + case ISM_PointToPoint: + /* Set Link ID with neighbor Router ID */ + set_linkparams_link_id(lp, nbr->router_id); + /* Set Remote IP address */ + set_linkparams_rmtif_ipaddr(lp, nbr->address.u.prefix4); + break; + + case ISM_DR: + case ISM_DROther: + case ISM_Backup: + /* Set Link ID with the Designated Router ID */ + set_linkparams_link_id(lp, DR(oi)); + break; + + case ISM_Down: + /* State goes Down: Flush LSA if engaged */ + if (OspfMplsTE.enabled + && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + ote_debug( + "MPLS-TE (%s): Interface %s goes down: flush LSA", + __func__, IF_NAME(oi)); + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + } + return; + default: + break; + } + + ote_debug("MPLS-TE (%s): Add Link-ID %pI4 for interface %s ", __func__, + &lp->link_id.value, oi->ifp->name); + + /* Try to Schedule LSA */ + if (OspfMplsTE.enabled) { + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) + ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); + else + ospf_mpls_te_lsa_schedule(lp, REORIGINATE_THIS_LSA); + } + return; +} + +/*------------------------------------------------------------------------* + * Following are OSPF protocol processing functions for MPLS-TE LSA. + *------------------------------------------------------------------------*/ + +static void build_tlv_header(struct stream *s, struct tlv_header *tlvh) +{ + stream_put(s, tlvh, sizeof(struct tlv_header)); + return; +} + +static void build_router_tlv(struct stream *s) +{ + struct tlv_header *tlvh = &OspfMplsTE.router_addr.header; + if (ntohs(tlvh->type) != 0) { + build_tlv_header(s, tlvh); + stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); + } + return; +} + +static void build_link_subtlv(struct stream *s, struct tlv_header *tlvh) +{ + + if ((tlvh != NULL) && (ntohs(tlvh->type) != 0)) { + build_tlv_header(s, tlvh); + stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh)); + } + return; +} + +static void build_link_tlv(struct stream *s, struct mpls_te_link *lp) +{ + set_linkparams_link_header(lp); + build_tlv_header(s, &lp->link_header.header); + + build_link_subtlv(s, &lp->link_type.header); + build_link_subtlv(s, &lp->link_id.header); + build_link_subtlv(s, &lp->lclif_ipaddr.header); + build_link_subtlv(s, &lp->rmtif_ipaddr.header); + build_link_subtlv(s, &lp->te_metric.header); + build_link_subtlv(s, &lp->max_bw.header); + build_link_subtlv(s, &lp->max_rsv_bw.header); + build_link_subtlv(s, &lp->unrsv_bw.header); + build_link_subtlv(s, &lp->rsc_clsclr.header); + build_link_subtlv(s, &lp->lrrid.header); + build_link_subtlv(s, &lp->llri.header); + build_link_subtlv(s, &lp->rip.header); + build_link_subtlv(s, &lp->ras.header); + build_link_subtlv(s, &lp->av_delay.header); + build_link_subtlv(s, &lp->mm_delay.header); + build_link_subtlv(s, &lp->delay_var.header); + build_link_subtlv(s, &lp->pkt_loss.header); + build_link_subtlv(s, &lp->res_bw.header); + build_link_subtlv(s, &lp->ava_bw.header); + build_link_subtlv(s, &lp->use_bw.header); + + return; +} + +static void ospf_mpls_te_lsa_body_set(struct stream *s, struct mpls_te_link *lp) +{ + /* + * The router address TLV is type 1, and ... It must appear in exactly + * one Traffic Engineering LSA originated by a router but not in + * Inter-AS TLV. + */ + if (!IS_INTER_AS(lp->type)) + build_router_tlv(s); + + /* + * Only one Link TLV shall be carried in each LSA, allowing for fine + * granularity changes in topology. + */ + build_link_tlv(s, lp); + return; +} + +/* Create new opaque-LSA. */ +static struct ospf_lsa *ospf_mpls_te_lsa_new(struct ospf *ospf, + struct ospf_area *area, + struct mpls_te_link *lp) +{ + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new = NULL; + uint8_t options, lsa_type = 0; + struct in_addr lsa_id; + uint32_t tmp; + uint16_t length; + + /* Create a stream for LSA. */ + s = stream_new(OSPF_MAX_LSA_SIZE); + lsah = (struct lsa_header *)STREAM_DATA(s); + + options = OSPF_OPTION_O; /* Don't forget this :-) */ + + /* Set opaque-LSA header fields depending of the type of RFC */ + if (IS_INTER_AS(lp->type)) { + if (IS_FLOOD_AS(lp->flags)) { + /* Enable AS external as we flood Inter-AS with Opaque + * Type 11 + */ + options |= OSPF_OPTION_E; + lsa_type = OSPF_OPAQUE_AS_LSA; + } else { + options |= LSA_OPTIONS_GET( + area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + lsa_type = OSPF_OPAQUE_AREA_LSA; + } + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, lp->instance); + lsa_id.s_addr = htonl(tmp); + + if (!ospf) { + stream_free(s); + return NULL; + } + + lsa_header_set(s, options, lsa_type, lsa_id, ospf->router_id); + } else { + options |= LSA_OPTIONS_GET(area); /* Get area default option */ + options |= LSA_OPTIONS_NSSA_GET(area); + lsa_type = OSPF_OPAQUE_AREA_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, + lp->instance); + lsa_id.s_addr = htonl(tmp); + lsa_header_set(s, options, lsa_type, lsa_id, + area->ospf->router_id); + } + + ote_debug( + "MPLS-TE (%s): LSA[Type%d:%pI4]: Create an Opaque-LSA/MPLS-TE instance", + __func__, lsa_type, &lsa_id); + + /* Set opaque-LSA body fields. */ + ospf_mpls_te_lsa_body_set(s, lp); + + /* Set length. */ + length = stream_get_endp(s); + lsah->length = htons(length); + + /* Now, create an OSPF LSA instance. */ + new = ospf_lsa_new_and_data(length); + + new->area = area; + new->vrf_id = VRF_DEFAULT; + + SET_FLAG(new->flags, OSPF_LSA_SELF); + memcpy(new->data, lsah, length); + stream_free(s); + + return new; +} + +static int ospf_mpls_te_lsa_originate1(struct ospf_area *area, + struct mpls_te_link *lp) +{ + struct ospf_lsa *new = NULL; + int rc = -1; + + /* Create new Opaque-LSA/MPLS-TE instance. */ + new = ospf_mpls_te_lsa_new(area->ospf, area, lp); + if (new == NULL) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): ospf_mpls_te_lsa_new() ?", __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(area->ospf, NULL /*oi*/, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "MPLS-TE (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this link-parameter entry has associated LSA. */ + SET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); + /* Update new LSA origination count. */ + area->ospf->lsa_originate_count++; + + /* Flood new LSA through area. */ + ospf_flood_through_area(area, NULL /*nbr*/, new); + + ote_debug( + "MPLS-TE (%s): LSA[Type%d:%pI4]: Originate Opaque-LSA/MPLS-TE: Area(%pI4), Link(%s)", + __func__, new->data->type, &new->data->id, &area->area_id, + lp->ifp->name); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + rc = 0; + return rc; +} + +static int ospf_mpls_te_lsa_originate_area(void *arg) +{ + struct ospf_area *area = (struct ospf_area *)arg; + struct listnode *node, *nnode; + struct mpls_te_link *lp; + int rc = -1; + + if (!OspfMplsTE.enabled) { + ote_debug("MPLS-TE (%s): MPLS-TE is disabled now.", __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { + /* Process only enabled LSA with area scope flooding */ + if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE) + || IS_FLOOD_AS(lp->flags)) + continue; + + if (lp->area == NULL) + continue; + + if (!IPV4_ADDR_SAME(&lp->area->area_id, &area->area_id)) + continue; + + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH)) { + UNSET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); + ote_debug( + "MPLS-TE (%s): Refresh instead of Originate", + __func__); + ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); + } + continue; + } + + if (!is_mandated_params_set(lp)) { + ote_debug( + "MPLS-TE (%s): Link(%s) lacks some mandated MPLS-TE parameters.", + __func__, lp->ifp ? lp->ifp->name : "?"); + continue; + } + + /* Ok, let's try to originate an LSA for this area and Link. */ + ote_debug( + "MPLS-TE (%s): Let's finally reoriginate the LSA %d through the Area %pI4 for Link %s", + __func__, lp->instance, &area->area_id, + lp->ifp ? lp->ifp->name : "?"); + if (ospf_mpls_te_lsa_originate1(area, lp) != 0) + return rc; + } + + rc = 0; + return rc; +} + +static int ospf_mpls_te_lsa_originate2(struct ospf *top, + struct mpls_te_link *lp) +{ + struct ospf_lsa *new; + int rc = -1; + + /* Create new Opaque-LSA/Inter-AS instance. */ + new = ospf_mpls_te_lsa_new(top, NULL, lp); + if (new == NULL) { + flog_warn(EC_OSPF_LSA_UNEXPECTED, + "MPLS-TE (%s): ospf_router_info_lsa_new() ?", + __func__); + return rc; + } + + /* Install this LSA into LSDB. */ + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "MPLS-TE (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return rc; + } + + /* Now this Router Info parameter entry has associated LSA. */ + SET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); + /* Update new LSA origination count. */ + top->lsa_originate_count++; + + /* Flood new LSA through AS. */ + ospf_flood_through_as(top, NULL /*nbr */, new); + + ote_debug( + "MPLS-TE (%s): LSA[Type%d:%pI4]: Originate Opaque-LSA/MPLS-TE Inter-AS", + __func__, new->data->type, &new->data->id); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + + rc = 0; + return rc; +} + +static int ospf_mpls_te_lsa_originate_as(void *arg) +{ + struct ospf *top; + struct ospf_area *area; + struct listnode *node, *nnode; + struct mpls_te_link *lp; + int rc = -1; + + if ((!OspfMplsTE.enabled) || (OspfMplsTE.inter_as == Off)) { + ote_debug("MPLS-TE (%s): Inter-AS is disabled for now", + __func__); + rc = 0; /* This is not an error case. */ + return rc; + } + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { + /* Process only enabled INTER_AS Links or Pseudo-Links */ + if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE) + || !CHECK_FLAG(lp->flags, LPFLG_LSA_FLOOD_AS) + || !IS_INTER_AS(lp->type)) + continue; + + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + if (CHECK_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH)) { + UNSET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); + ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); + } + continue; + } + + if (!is_mandated_params_set(lp)) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Link(%s) lacks some mandated MPLS-TE parameters.", + __func__, lp->ifp ? lp->ifp->name : "?"); + continue; + } + + /* Ok, let's try to originate an LSA for this AS and Link. */ + ote_debug( + "MPLS-TE (%s): Let's finally re-originate the Inter-AS LSA %d through the %s for Link %s", + __func__, lp->instance, + IS_FLOOD_AS(lp->flags) ? "AS" : "Area", + lp->ifp ? lp->ifp->name : "Unknown"); + + if (IS_FLOOD_AS(lp->flags)) { + top = (struct ospf *)arg; + ospf_mpls_te_lsa_originate2(top, lp); + } else { + area = (struct ospf_area *)arg; + ospf_mpls_te_lsa_originate1(area, lp); + } + } + + rc = 0; + return rc; +} + +/* + * As Inter-AS LSA must be registered with both AREA and AS flooding, and + * because all origination callback functions are call (disregarding the Opaque + * LSA type and Flooding scope) it is necessary to determine which flooding + * scope is associated with the LSA origination as parameter is of type void and + * must be cast to struct *ospf for AS flooding and to struct *ospf_area for + * Area flooding. + */ +static int ospf_mpls_te_lsa_inter_as_as(void *arg) +{ + if (OspfMplsTE.inter_as == AS) + return ospf_mpls_te_lsa_originate_as(arg); + else + return 0; +} + +static int ospf_mpls_te_lsa_inter_as_area(void *arg) +{ + if (OspfMplsTE.inter_as == Area) + return ospf_mpls_te_lsa_originate_area(arg); + else + return 0; +} + +static struct ospf_lsa *ospf_mpls_te_lsa_refresh(struct ospf_lsa *lsa) +{ + struct mpls_te_link *lp; + struct ospf_area *area = lsa->area; + struct ospf *top; + struct ospf_lsa *new = NULL; + + if (!OspfMplsTE.enabled) { + /* + * This LSA must have flushed before due to MPLS-TE status + * change. + * It seems a slip among routers in the routing domain. + */ + ote_debug("MPLS-TE (%s): MPLS-TE is disabled now", __func__); + lsa->data->ls_age = + htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ + } + + /* At first, resolve lsa/lp relationship. */ + if ((lp = lookup_linkparams_by_instance(lsa)) == NULL) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Invalid parameter?", __func__); + lsa->data->ls_age = + htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + + /* Check if lp was not disable in the interval */ + if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE)) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): lp was disabled: Flush it!", __func__); + lsa->data->ls_age = + htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */ + } + + /* If the lsa's age reached to MaxAge, start flushing procedure. */ + if (IS_LSA_MAXAGE(lsa)) { + UNSET_FLAG(lp->flags, LPFLG_LSA_ENGAGED); + ospf_opaque_lsa_flush_schedule(lsa); + return NULL; + } + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + /* Create new Opaque-LSA/MPLS-TE instance. */ + new = ospf_mpls_te_lsa_new(top, area, lp); + if (new == NULL) { + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): ospf_mpls_te_lsa_new() ?", __func__); + return NULL; + } + new->data->ls_seqnum = lsa_seqnum_increment(lsa); + + /* Install this LSA into LSDB. */ + /* Given "lsa" will be freed in the next function. */ + /* As area could be NULL i.e. when using OPAQUE_LSA_AS, we prefer to use + * ospf_lookup() to get ospf instance */ + if (area) + top = area->ospf; + + if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) { + flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, + "MPLS-TE (%s): ospf_lsa_install() ?", __func__); + ospf_lsa_unlock(&new); + return NULL; + } + + /* Flood updated LSA through AS or Area depending of the RFC of the link + */ + if (IS_FLOOD_AS(lp->flags)) + ospf_flood_through_as(top, NULL, new); + else + ospf_flood_through_area(area, NULL /*nbr*/, new); + + /* Debug logging. */ + ote_debug("MPLS-TE (%s): LSA[Type%d:%pI4]: Refresh Opaque-LSA/MPLS-TE", + __func__, new->data->type, &new->data->id); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) + ospf_lsa_header_dump(new->data); + + return new; +} + +void ospf_mpls_te_lsa_schedule(struct mpls_te_link *lp, enum lsa_opcode opcode) +{ + struct ospf_lsa lsa; + struct lsa_header lsah; + struct ospf *top; + uint32_t tmp; + + memset(&lsa, 0, sizeof(lsa)); + memset(&lsah, 0, sizeof(lsah)); + top = ospf_lookup_by_vrf_id(VRF_DEFAULT); + + /* Check if the pseudo link is ready to flood */ + if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ACTIVE)) + return; + + ote_debug("MPLS-TE (%s): Schedule %s%s%s LSA for interface %s", + __func__, + opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "", + opcode == REFRESH_THIS_LSA ? "Refresh" : "", + opcode == FLUSH_THIS_LSA ? "Flush" : "", + lp->ifp ? lp->ifp->name : "-"); + + lsa.area = lp->area; + lsa.data = &lsah; + if (IS_FLOOD_AS(lp->flags)) { + lsah.type = OSPF_OPAQUE_AS_LSA; + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, lp->instance); + lsah.id.s_addr = htonl(tmp); + } else { + lsah.type = OSPF_OPAQUE_AREA_LSA; + if (IS_INTER_AS(lp->type)) { + /* Set the area context if not know */ + if (lp->area == NULL) + lp->area = ospf_area_lookup_by_area_id( + top, OspfMplsTE.interas_areaid); + /* Unable to set the area context. Abort! */ + if (lp->area == NULL) { + flog_warn( + EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Area context is null. Abort !", + __func__); + return; + } + tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_INTER_AS_LSA, + lp->instance); + } else + tmp = SET_OPAQUE_LSID( + OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA, + lp->instance); + lsah.id.s_addr = htonl(tmp); + } + + switch (opcode) { + case REORIGINATE_THIS_LSA: + if (IS_FLOOD_AS(lp->flags)) { + ospf_opaque_lsa_reoriginate_schedule( + (void *)top, OSPF_OPAQUE_AS_LSA, + OPAQUE_TYPE_INTER_AS_LSA); + } else { + if (IS_INTER_AS(lp->type)) + ospf_opaque_lsa_reoriginate_schedule( + (void *)lp->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_INTER_AS_LSA); + else + ospf_opaque_lsa_reoriginate_schedule( + (void *)lp->area, OSPF_OPAQUE_AREA_LSA, + OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA); + } + break; + case REFRESH_THIS_LSA: + ospf_opaque_lsa_refresh_schedule(&lsa); + break; + case FLUSH_THIS_LSA: + /* Reset Activity flag */ + lp->flags = LPFLG_LSA_INACTIVE; + ospf_opaque_lsa_flush_schedule(&lsa); + break; + default: + flog_warn(EC_OSPF_TE_UNEXPECTED, + "MPLS-TE (%s): Unknown opcode (%u)", __func__, + opcode); + break; + } +} + +/** + * ------------------------------------------------------ + * Following are Link State Data Base control functions. + * ------------------------------------------------------ + */ + +/** + * Get Vertex from TED by the router which advertised the LSA. A new Vertex and + * associated Link State Node are created if Vertex is not found. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return Link State Vertex + */ +static struct ls_vertex *get_vertex(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_node_id lnid; + struct ls_node *lnode; + struct ls_vertex *vertex; + + /* Sanity Check */ + if (!ted || !lsa || !lsa->data || !lsa->area) + return NULL; + + /* Search if a Link State Vertex already exist */ + lnid.origin = OSPFv2; + lnid.id.ip.addr = lsa->data->adv_router; + lnid.id.ip.area_id = lsa->area->area_id; + vertex = ls_find_vertex_by_id(ted, lnid); + + /* Create Node & Vertex in the Link State Date Base if not found */ + if (!vertex) { + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + lnode = ls_node_new(lnid, inaddr_any, in6addr_any); + snprintfrr(lnode->name, MAX_NAME_LENGTH, "%pI4", + &lnid.id.ip.addr); + vertex = ls_vertex_add(ted, lnode); + } + + if (IS_LSA_SELF(lsa)) + ted->self = vertex; + + return vertex; +} + +/** + * Get Edge from TED by Link State Attribute ID. A new Edge and associated Link + * State Attributes are created if not found. + * + * @param ted Link State Traffic Engineering Database + * @param adv Link State Node ID of router which advertised Edge + * @param link_id Link State Attribute ID + * + * @return Link State Edge + */ +static struct ls_edge *get_edge(struct ls_ted *ted, struct ls_node_id adv, + struct in_addr link_id) +{ + struct ls_edge_key key; + struct ls_edge *edge; + struct ls_attributes *attr; + + /* Check that Link ID and Node ID are valid */ + if (IPV4_NET0(link_id.s_addr) || IPV4_NET0(adv.id.ip.addr.s_addr) || + adv.origin != OSPFv2) + return NULL; + + /* Search Edge that corresponds to the Link ID */ + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &link_id); + edge = ls_find_edge_by_key(ted, key); + + /* Create new one if not exist */ + if (!edge) { + attr = ls_attributes_new(adv, link_id, in6addr_any, 0); + edge = ls_edge_add(ted, attr); + } + + return edge; +} + +/** + * Export Link State information to consumer daemon through ZAPI Link State + * Opaque Message. + * + * @param type Type of Link State Element i.e. Vertex, Edge or Subnet + * @param link_state Pointer to Link State Vertex, Edge or Subnet + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_export(uint8_t type, void *link_state) +{ + struct ls_message msg = {}; + int rc = 0; + + if (!OspfMplsTE.export) + return rc; + + switch (type) { + case LS_MSG_TYPE_NODE: + ls_vertex2msg(&msg, (struct ls_vertex *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + case LS_MSG_TYPE_ATTRIBUTES: + ls_edge2msg(&msg, (struct ls_edge *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + case LS_MSG_TYPE_PREFIX: + ls_subnet2msg(&msg, (struct ls_subnet *)link_state); + rc = ls_send_msg(zclient, &msg, NULL); + break; + default: + rc = -1; + break; + } + + return rc; +} + +/** + * Update Link State Edge & Attributes from the given Link State Attributes ID + * and metric. This function is called when parsing Router LSA. + * + * @param ted Link State Traffic Engineering Database + * @param vertex Vertex where the Edge is attached as source + * @param link_data Link State Edge ID + * @param metric Standard metric attached to this Edge + */ +static void ospf_te_update_link(struct ls_ted *ted, struct ls_vertex *vertex, + struct in_addr link_data, uint8_t metric) +{ + struct ls_edge *edge; + struct ls_attributes *attr; + + /* Sanity check */ + if (!ted || !vertex || !vertex->node) + return; + + /* Get Corresponding Edge from Link State Data Base */ + edge = get_edge(ted, vertex->node->adv, link_data); + if (!edge) { + ote_debug(" |- Found no edge from Link Data. Abort!"); + return; + } + attr = edge->attributes; + + /* re-attached edge to vertex if needed */ + if (!edge->source) + edge->source = vertex; + + /* Check if it is just an LSA refresh */ + if ((CHECK_FLAG(attr->flags, LS_ATTR_METRIC) + && (attr->metric == metric))) { + edge->status = SYNC; + return; + } + + /* Update metric value */ + attr->metric = metric; + SET_FLAG(attr->flags, LS_ATTR_METRIC); + if (edge->status != NEW) + edge->status = UPDATE; + + ote_debug(" |- %s Edge %pI4 with metric %d", + edge->status == NEW ? "Add" : "Update", &attr->standard.local, + attr->metric); + + /* Export Link State Edge */ + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; +} + +/** + * Update Link State Subnet & Prefix from the given prefix and metric. This + * function is called when parsing Router LSA. + * + * @param ted Link State Traffic Engineering Database + * @param vertex Vertex where the Edge is attached as source + * @param p Prefix associated to the Subnet + * @param metric Standard metric attached to this Edge + */ +static void ospf_te_update_subnet(struct ls_ted *ted, struct ls_vertex *vertex, + struct prefix *p, uint8_t metric) +{ + struct ls_subnet *subnet; + struct ls_prefix *ls_pref; + + /* Search if there is a Subnet for this prefix */ + subnet = ls_find_subnet(ted, p); + + /* If found a Subnet, check if it is attached to this Vertex */ + if (subnet) { + /* Re-attach the subnet to the vertex if necessary */ + if (subnet->vertex != vertex) { + subnet->vertex = vertex; + listnode_add_sort_nodup(vertex->prefixes, subnet); + } + /* Check if it is a simple refresh */ + ls_pref = subnet->ls_pref; + if ((CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)) + && (ls_pref->metric == metric)) { + subnet->status = SYNC; + return; + } + ls_pref->metric = metric; + SET_FLAG(ls_pref->flags, LS_PREF_METRIC); + subnet->status = UPDATE; + } else { + /* Create new Link State Prefix */ + ls_pref = ls_prefix_new(vertex->node->adv, p); + ls_pref->metric = metric; + SET_FLAG(ls_pref->flags, LS_PREF_METRIC); + /* and add it to the TED */ + subnet = ls_subnet_add(ted, ls_pref); + } + + ote_debug(" |- %s subnet %pFX with metric %d", + subnet->status == NEW ? "Add" : "Update", &subnet->key, + ls_pref->metric); + + /* Export Link State Subnet */ + ospf_te_export(LS_MSG_TYPE_PREFIX, subnet); + subnet->status = SYNC; +} + +/** + * Delete Subnet that correspond to the given IPv4 address and export deletion + * information before removal. Prefix length is fixed to IPV4_MAX_BITLEN. + * + * @param ted Links State Database + * @param addr IPv4 address + */ +static void ospf_te_delete_subnet(struct ls_ted *ted, struct in_addr addr) +{ + struct prefix p; + struct ls_subnet *subnet; + + /* Search subnet that correspond to the address/32 as prefix */ + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = addr; + ote_debug(" |- Delete Subnet info. for Prefix %pFX", &p); + subnet = ls_find_subnet(ted, &p); + + /* Remove subnet if found */ + if (subnet) { + subnet->status = DELETE; + ospf_te_export(LS_MSG_TYPE_PREFIX, subnet); + ls_subnet_del_all(ted, subnet); + } +} + +/** + * Parse Router LSA. This function will create or update corresponding Vertex, + * Edge and Subnet. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_router_lsa(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct router_lsa *rl; + enum ls_node_type type; + struct ls_vertex *vertex; + int len, links; + + /* Sanity Check */ + if (!ted || !lsa || !lsa->data) + return -1; + + ote_debug("MPLS-TE (%s): Parse Router LSA[%pI4] from Router[%pI4]", + __func__, &lsa->data->id, &lsa->data->adv_router); + + /* Get vertex from LSA Advertise Router ID */ + vertex = get_vertex(ted, lsa); + + /* Set Node type information if it has changed */ + rl = (struct router_lsa *)lsa->data; + if (IS_ROUTER_LSA_VIRTUAL(rl)) + type = PSEUDO; + else if (IS_ROUTER_LSA_EXTERNAL(rl)) + type = ASBR; + else if (IS_ROUTER_LSA_BORDER(rl)) + type = ABR; + else + type = STANDARD; + + if (vertex->status == NEW) { + vertex->node->type = type; + SET_FLAG(vertex->node->flags, LS_NODE_TYPE); + } else if (vertex->node->type != type) { + vertex->node->type = type; + vertex->status = UPDATE; + } + + /* Check if Vertex has been modified */ + if (vertex->status != SYNC) { + ote_debug(" |- %s Vertex %pI4", + vertex->status == NEW ? "Add" : "Update", + &vertex->node->router_id); + + /* Vertex is out of sync: export it */ + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + } + + /* Then, process Link Information */ + len = lsa->size - OSPF_LSA_HEADER_SIZE - OSPF_ROUTER_LSA_MIN_SIZE; + links = ntohs(rl->links); + for (int i = 0; i < links && len > 0; len -= 12, i++) { + struct prefix p; + uint32_t metric; + + switch (rl->link[i].type) { + case LSA_LINK_TYPE_POINTOPOINT: + ospf_te_update_link(ted, vertex, rl->link[i].link_data, + ntohs(rl->link[i].metric)); + /* Add corresponding subnet */ + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = rl->link[i].link_data; + metric = ntohs(rl->link[i].metric); + ospf_te_update_subnet(ted, vertex, &p, metric); + break; + case LSA_LINK_TYPE_STUB: + /* Keep only /32 prefix */ + p.prefixlen = ip_masklen(rl->link[i].link_data); + if (p.prefixlen == IPV4_MAX_BITLEN) { + p.family = AF_INET; + p.u.prefix4 = rl->link[i].link_id; + metric = ntohs(rl->link[i].metric); + ospf_te_update_subnet(ted, vertex, &p, metric); + } + break; + default: + break; + } + } + + return 0; +} + +/** + * Delete Vertex, Edge and Subnet associated to this Router LSA. This function + * is called when the router received such LSA with MAX_AGE (Flush) or when the + * router stop OSPF. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_router_lsa(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_node_id lnid; + struct ls_vertex *vertex; + + /* Sanity Check */ + if (!ted || !lsa || !lsa->data) + return -1; + + /* Search Vertex that corresponds to this LSA */ + lnid.origin = OSPFv2; + lnid.id.ip.addr = lsa->data->adv_router; + lnid.id.ip.area_id = lsa->area->area_id; + vertex = ls_find_vertex_by_id(ted, lnid); + if (!vertex) + return -1; + + ote_debug("MPLS-TE (%s): Delete Vertex %pI4 from Router LSA[%pI4]", + __func__, &vertex->node->router_id, &lsa->data->id); + + /* Export deleted vertex ... */ + vertex->status = DELETE; + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + + /* ... and remove Node & Vertex from Link State Date Base */ + ls_vertex_del_all(ted, vertex); + + return 0; +} + +/** + * Create or update Remote Vertex that corresponds to the remote ASBR of the + * foreign network if Edge is associated to an Inter-AS LSA (Type 6). + * + * @param ted Link State Traffic Engineering Database + * @param edge Link State Edge + */ +static void ospf_te_update_remote_asbr(struct ls_ted *ted, struct ls_edge *edge) +{ + struct ls_node_id lnid; + struct ls_vertex *vertex; + struct ls_node *lnode; + struct ls_attributes *attr; + struct prefix p; + + /* Sanity Check */ + if (!ted || !edge) + return; + + /* Search if a Link State Vertex already exist */ + attr = edge->attributes; + lnid.origin = OSPFv2; + lnid.id.ip.addr = attr->standard.remote_addr; + lnid.id.ip.area_id = attr->adv.id.ip.area_id; + vertex = ls_find_vertex_by_id(ted, lnid); + + /* Create Node & Vertex in the Link State Date Base if not found */ + if (!vertex) { + const struct in_addr inaddr_any = {.s_addr = INADDR_ANY}; + + lnode = ls_node_new(lnid, inaddr_any, in6addr_any); + snprintfrr(lnode->name, MAX_NAME_LENGTH, "%pI4", + &lnid.id.ip.addr); + vertex = ls_vertex_add(ted, lnode); + } + + /* Update Node information */ + lnode = vertex->node; + if (CHECK_FLAG(lnode->flags, LS_NODE_TYPE)) { + if (lnode->type != RMT_ASBR) { + lnode->type = RMT_ASBR; + if (vertex->status != NEW) + vertex->status = UPDATE; + } + } else { + lnode->type = RMT_ASBR; + SET_FLAG(lnode->flags, LS_NODE_TYPE); + if (vertex->status != NEW) + vertex->status = UPDATE; + } + if (CHECK_FLAG(lnode->flags, LS_NODE_AS_NUMBER)) { + if (lnode->as_number != attr->standard.remote_as) { + lnode->as_number = attr->standard.remote_as; + if (vertex->status != NEW) + vertex->status = UPDATE; + } + } else { + lnode->as_number = attr->standard.remote_as; + SET_FLAG(lnode->flags, LS_NODE_AS_NUMBER); + if (vertex->status != NEW) + vertex->status = UPDATE; + } + + /* Export Link State Vertex if needed */ + if (vertex->status == NEW || vertex->status == UPDATE) { + ote_debug(" |- %s Remote Vertex %pI4 for AS %u", + vertex->status == NEW ? "Add" : "Update", + &lnode->router_id, lnode->as_number); + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + } + + /* Update corresponding Subnets */ + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = attr->standard.local; + ospf_te_update_subnet(ted, edge->source, &p, attr->standard.te_metric); + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = attr->standard.remote_addr; + ospf_te_update_subnet(ted, vertex, &p, attr->standard.te_metric); + + /* Connect Edge to the remote Vertex */ + if (edge->destination == NULL) { + edge->destination = vertex; + listnode_add_sort_nodup(vertex->incoming_edges, edge); + } + + /* Finally set type to ASBR the node that advertised this Edge ... */ + vertex = edge->source; + lnode = vertex->node; + if (CHECK_FLAG(lnode->flags, LS_NODE_TYPE)) { + if (lnode->type != ASBR) { + lnode->type = ASBR; + if (vertex->status != NEW) + vertex->status = UPDATE; + } + } else { + lnode->type = ASBR; + SET_FLAG(lnode->flags, LS_NODE_TYPE); + if (vertex->status != NEW) + vertex->status = UPDATE; + } + + /* ... and Export it if needed */ + if (vertex->status == NEW || vertex->status == UPDATE) { + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + } +} + +/** + * Parse Opaque Traffic Engineering LSA (Type 1) TLVs and create or update the + * corresponding Link State Edge and Attributes. Vertex connections are also + * updated if needed based on the remote IP address of the Edge and existing + * reverse Edge. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_te(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_edge *edge; + struct ls_vertex *vertex; + struct ls_attributes *old, attr = {}; + struct tlv_header *tlvh; + void *value; + uint16_t len, sum; + uint8_t lsa_id; + + /* Initialize Attribute */ + attr.adv.origin = OSPFv2; + attr.adv.id.ip.addr = lsa->data->adv_router; + if (lsa->data->type != OSPF_OPAQUE_AS_LSA) + attr.adv.id.ip.area_id = lsa->area->area_id; + + /* Initialize TLV browsing */ + tlvh = TLV_HDR_TOP(lsa->data); + len = lsa->size - OSPF_LSA_HEADER_SIZE; + + /* Check if TE Router-ID TLV is present */ + if (ntohs(tlvh->type) == TE_TLV_ROUTER_ADDR) { + /* if TE Router-ID is alone, we are done ... */ + if (len == TE_LINK_SUBTLV_DEF_SIZE) + return 0; + + /* ... otherwise, skip it */ + len -= TE_LINK_SUBTLV_DEF_SIZE + TLV_HDR_SIZE; + tlvh = TLV_HDR_NEXT(tlvh); + } + + /* Check if we have a valid TE Link TLV */ + if ((len == 0) || (ntohs(tlvh->type) != TE_TLV_LINK)) + return 0; + + sum = sizeof(struct tlv_header); + /* Browse sub-TLV and fulfill Link State Attributes */ + for (tlvh = TLV_DATA(tlvh); sum < len; tlvh = TLV_HDR_NEXT(tlvh)) { + uint32_t val32, tab32[2]; + float valf, tabf[8]; + struct in_addr addr; + + value = TLV_DATA(tlvh); + switch (ntohs(tlvh->type)) { + case TE_LINK_SUBTLV_LCLIF_IPADDR: + memcpy(&addr, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.local = addr; + SET_FLAG(attr.flags, LS_ATTR_LOCAL_ADDR); + break; + case TE_LINK_SUBTLV_RMTIF_IPADDR: + memcpy(&addr, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.remote = addr; + SET_FLAG(attr.flags, LS_ATTR_NEIGH_ADDR); + break; + case TE_LINK_SUBTLV_TE_METRIC: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.te_metric = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_TE_METRIC); + break; + case TE_LINK_SUBTLV_MAX_BW: + memcpy(&valf, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.max_bw = ntohf(valf); + SET_FLAG(attr.flags, LS_ATTR_MAX_BW); + break; + case TE_LINK_SUBTLV_MAX_RSV_BW: + memcpy(&valf, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.max_rsv_bw = ntohf(valf); + SET_FLAG(attr.flags, LS_ATTR_MAX_RSV_BW); + break; + case TE_LINK_SUBTLV_UNRSV_BW: + memcpy(tabf, value, TE_LINK_SUBTLV_UNRSV_SIZE); + for (int i = 0; i < MAX_CLASS_TYPE; i++) + attr.standard.unrsv_bw[i] = ntohf(tabf[i]); + SET_FLAG(attr.flags, LS_ATTR_UNRSV_BW); + break; + case TE_LINK_SUBTLV_RSC_CLSCLR: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.admin_group = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_ADM_GRP); + break; + case TE_LINK_SUBTLV_LLRI: + memcpy(tab32, value, TE_LINK_SUBTLV_LLRI_SIZE); + attr.standard.local_id = ntohl(tab32[0]); + attr.standard.remote_id = ntohl(tab32[1]); + SET_FLAG(attr.flags, LS_ATTR_LOCAL_ID); + SET_FLAG(attr.flags, LS_ATTR_NEIGH_ID); + break; + case TE_LINK_SUBTLV_RIP: + memcpy(&addr, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.remote_addr = addr; + SET_FLAG(attr.flags, LS_ATTR_REMOTE_ADDR); + break; + case TE_LINK_SUBTLV_RAS: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.standard.remote_as = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_REMOTE_AS); + break; + case TE_LINK_SUBTLV_AV_DELAY: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.delay = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_DELAY); + break; + case TE_LINK_SUBTLV_MM_DELAY: + memcpy(tab32, value, TE_LINK_SUBTLV_MM_DELAY_SIZE); + attr.extended.min_delay = ntohl(tab32[0]); + attr.extended.max_delay = ntohl(tab32[1]); + SET_FLAG(attr.flags, LS_ATTR_MIN_MAX_DELAY); + break; + case TE_LINK_SUBTLV_DELAY_VAR: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.jitter = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_JITTER); + break; + case TE_LINK_SUBTLV_PKT_LOSS: + memcpy(&val32, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.pkt_loss = ntohl(val32); + SET_FLAG(attr.flags, LS_ATTR_PACKET_LOSS); + break; + case TE_LINK_SUBTLV_RES_BW: + memcpy(&valf, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.rsv_bw = ntohf(valf); + SET_FLAG(attr.flags, LS_ATTR_RSV_BW); + break; + case TE_LINK_SUBTLV_AVA_BW: + memcpy(&valf, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.ava_bw = ntohf(valf); + SET_FLAG(attr.flags, LS_ATTR_AVA_BW); + break; + case TE_LINK_SUBTLV_USE_BW: + memcpy(&valf, value, TE_LINK_SUBTLV_DEF_SIZE); + attr.extended.used_bw = ntohf(valf); + SET_FLAG(attr.flags, LS_ATTR_USE_BW); + break; + default: + break; + } + sum += TLV_SIZE(tlvh); + } + + /* Get corresponding Edge from Link State Data Base */ + edge = get_edge(ted, attr.adv, attr.standard.local); + if (!edge) { + ote_debug(" |- Found no edge from Link local add./ID. Abort!"); + return -1; + } + old = edge->attributes; + + ote_debug(" |- Process Traffic Engineering LSA %pI4 for Edge %pI4", + &lsa->data->id, &attr.standard.local); + + /* Update standard fields */ + len = sizeof(struct ls_standard); + if ((attr.flags & 0x0FFFF) == (old->flags & 0x0FFFF)) { + if (memcmp(&attr.standard, &old->standard, len) != 0) { + memcpy(&old->standard, &attr.standard, len); + if (edge->status != NEW) + edge->status = UPDATE; + } + } else { + memcpy(&old->standard, &attr.standard, len); + old->flags |= attr.flags & 0x0FFFF; + if (edge->status != NEW) + edge->status = UPDATE; + } + /* Update extended fields */ + len = sizeof(struct ls_extended); + if ((attr.flags & 0x0FF0000) == (old->flags & 0x0FF0000)) { + if (memcmp(&attr.extended, &old->extended, len) != 0) { + memcpy(&old->extended, &attr.extended, len); + if (edge->status != NEW) + edge->status = UPDATE; + } + } else { + memcpy(&old->extended, &attr.extended, len); + old->flags |= attr.flags & 0x0FF0000; + if (edge->status != NEW) + edge->status = UPDATE; + } + + /* If LSA is an Opaque Inter-AS, Add Node and Subnet */ + lsa_id = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + if (lsa_id == OPAQUE_TYPE_INTER_AS_LSA) + ospf_te_update_remote_asbr(ted, edge); + + /* Update remote Link if remote IP addr is known */ + if (CHECK_FLAG(old->flags, LS_ATTR_NEIGH_ADDR)) { + struct ls_edge *dst; + + dst = ls_find_edge_by_destination(ted, old); + /* Attach remote link if not set */ + if (dst && edge->source && dst->destination == NULL) { + vertex = edge->source; + if (vertex->incoming_edges) + listnode_add_sort_nodup(vertex->incoming_edges, + dst); + dst->destination = vertex; + } + /* and destination vertex to this edge */ + if (dst && dst->source && edge->destination == NULL) { + vertex = dst->source; + if (vertex->incoming_edges) + listnode_add_sort_nodup(vertex->incoming_edges, + edge); + edge->destination = vertex; + } + } + + /* Export Link State Edge if needed */ + if (edge->status == NEW || edge->status == UPDATE) { + ote_debug(" |- %s TE info. for Edge %pI4", + edge->status == NEW ? "Add" : "Update", + &edge->attributes->standard.local); + + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; + } + + return 0; +} + +/** + * Delete Link State Attributes information that correspond to the Opaque + * Traffic Engineering LSA (Type 1) TLVs. Note that the Edge is not removed. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_te(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_edge *edge; + struct ls_attributes *attr; + struct tlv_header *tlvh; + struct in_addr addr; + struct ls_edge_key key = {.family = AF_UNSPEC}; + uint16_t len, sum; + uint8_t lsa_id; + + /* Initialize TLV browsing */ + tlvh = TLV_HDR_TOP(lsa->data); + /* Skip Router TE ID if present */ + if (ntohs(tlvh->type) == TE_TLV_ROUTER_ADDR) + tlvh = TLV_HDR_NEXT(tlvh); + len = TLV_BODY_SIZE(tlvh); + sum = sizeof(struct tlv_header); + + /* Browse sub-TLV to find Link ID */ + for (tlvh = TLV_DATA(tlvh); sum < len; tlvh = TLV_HDR_NEXT(tlvh)) { + if (ntohs(tlvh->type) == TE_LINK_SUBTLV_LCLIF_IPADDR) { + memcpy(&addr, TLV_DATA(tlvh), TE_LINK_SUBTLV_DEF_SIZE); + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &addr); + break; + } + sum += TLV_SIZE(tlvh); + } + if (key.family == AF_UNSPEC) + return 0; + + /* Search Edge that corresponds to the Link ID */ + edge = ls_find_edge_by_key(ted, key); + if (!edge || !edge->attributes) + return 0; + attr = edge->attributes; + + /* First, remove Remote ASBR and associated Edge & Subnet if any */ + lsa_id = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + if (lsa_id == OPAQUE_TYPE_INTER_AS_LSA) { + ote_debug(" |- Delete remote ASBR, Edge and Subnet"); + + if (edge->destination) { + edge->destination->status = DELETE; + ospf_te_export(LS_MSG_TYPE_NODE, edge->destination); + ls_vertex_del_all(ted, edge->destination); + } + + ospf_te_delete_subnet(ted, attr->standard.local); + + edge->status = DELETE; + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + ls_edge_del_all(ted, edge); + + return 0; + } + + ote_debug(" |- Delete TE info. for Edge %pI4", + &edge->attributes->standard.local); + + /* First remove the associated Subnet */ + ospf_te_delete_subnet(ted, attr->standard.local); + + /* Then ,remove Link State Attributes TE information */ + memset(&attr->standard, 0, sizeof(struct ls_standard)); + attr->flags &= 0x0FFFF; + memset(&attr->extended, 0, sizeof(struct ls_extended)); + attr->flags &= 0x0FF0000; + ls_attributes_srlg_del(attr); + + /* Export Edge that has been updated */ + if (CHECK_FLAG(attr->flags, LS_ATTR_ADJ_SID) + || CHECK_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID)) { + edge->status = UPDATE; + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; + } else { + /* Remove completely the Edge if Segment Routing is not set */ + edge->status = DELETE; + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + ls_edge_del_all(ted, edge); + } + + return 0; +} + +/** + * Parse Opaque Router Information LSA (Type 4) TLVs and update the + * corresponding Link State Vertex with these information (Segment Routing). + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_ri(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_vertex *vertex; + struct ls_node *node; + struct lsa_header *lsah = lsa->data; + struct tlv_header *tlvh; + uint16_t len = 0, sum = 0; + + /* Get vertex / Node from LSA Advertised Router ID */ + vertex = get_vertex(ted, lsa); + node = vertex->node; + + ote_debug(" |- Process Router Information LSA %pI4 for Vertex %pI4", + &lsa->data->id, &node->router_id); + + /* Initialize TLV browsing */ + len = lsa->size - OSPF_LSA_HEADER_SIZE; + for (tlvh = TLV_HDR_TOP(lsah); sum < len && tlvh; + tlvh = TLV_HDR_NEXT(tlvh)) { + struct ri_sr_tlv_sr_algorithm *algo; + struct ri_sr_tlv_sid_label_range *range; + struct ri_sr_tlv_node_msd *msd; + uint32_t size, lower; + + switch (ntohs(tlvh->type)) { + case RI_SR_TLV_SR_ALGORITHM: + if (TLV_BODY_SIZE(tlvh) < 1 || + TLV_BODY_SIZE(tlvh) > ALGORITHM_COUNT) + break; + algo = (struct ri_sr_tlv_sr_algorithm *)tlvh; + + for (int i = 0; i < ntohs(algo->header.length); i++) { + if (CHECK_FLAG(node->flags, LS_NODE_SR) + && (node->algo[i] == algo->value[i])) + continue; + + node->algo[i] = algo->value[i]; + SET_FLAG(node->flags, LS_NODE_SR); + if (vertex->status != NEW) + vertex->status = UPDATE; + } + + /* Reset other Algorithms */ + for (int i = ntohs(algo->header.length); i < 2; i++) { + if (vertex->status != NEW + && node->algo[i] != SR_ALGORITHM_UNSET) + vertex->status = UPDATE; + node->algo[i] = SR_ALGORITHM_UNSET; + } + + break; + + case RI_SR_TLV_SRGB_LABEL_RANGE: + if (TLV_BODY_SIZE(tlvh) != RI_SR_TLV_LABEL_RANGE_SIZE) + break; + range = (struct ri_sr_tlv_sid_label_range *)tlvh; + size = GET_RANGE_SIZE(ntohl(range->size)); + lower = GET_LABEL(ntohl(range->lower.value)); + if ((CHECK_FLAG(node->flags, LS_NODE_SR)) + && ((node->srgb.range_size == size) + && (node->srgb.lower_bound == lower))) + break; + + node->srgb.range_size = size; + node->srgb.lower_bound = lower; + SET_FLAG(node->flags, LS_NODE_SR); + if (vertex->status != NEW) + vertex->status = UPDATE; + + break; + + case RI_SR_TLV_SRLB_LABEL_RANGE: + if (TLV_BODY_SIZE(tlvh) != RI_SR_TLV_LABEL_RANGE_SIZE) + break; + range = (struct ri_sr_tlv_sid_label_range *)tlvh; + size = GET_RANGE_SIZE(ntohl(range->size)); + lower = GET_LABEL(ntohl(range->lower.value)); + if ((CHECK_FLAG(node->flags, LS_NODE_SRLB)) + && ((node->srlb.range_size == size) + && (node->srlb.lower_bound == lower))) + break; + + node->srlb.range_size = size; + node->srlb.lower_bound = lower; + SET_FLAG(node->flags, LS_NODE_SRLB); + if (vertex->status != NEW) + vertex->status = UPDATE; + + break; + + case RI_SR_TLV_NODE_MSD: + if (TLV_BODY_SIZE(tlvh) < RI_SR_TLV_NODE_MSD_SIZE) + break; + msd = (struct ri_sr_tlv_node_msd *)tlvh; + if ((CHECK_FLAG(node->flags, LS_NODE_MSD)) + && (node->msd == msd->value)) + break; + + node->msd = msd->value; + SET_FLAG(node->flags, LS_NODE_MSD); + if (vertex->status != NEW) + vertex->status = UPDATE; + + break; + + default: + break; + } + sum += TLV_SIZE(tlvh); + } + + /* Vertex has been created or updated: export it */ + if (vertex->status == NEW || vertex->status == UPDATE) { + ote_debug(" |- %s SR info - SRGB[%d/%d] for Vertex %pI4", + vertex->status == NEW ? "Add" : "Update", + vertex->node->srgb.lower_bound, + vertex->node->srgb.range_size, + &vertex->node->router_id); + + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + } + + return 0; +} + +/** + * Delete Link State Node information (Segment Routing) that correspond to the + * Opaque Router Information LSA (Type 4) TLVs. Note that the Vertex is not + * removed. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_ri(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_node_id lnid; + struct ls_vertex *vertex; + struct ls_node *node; + + /* Search if a Link State Vertex already exist */ + lnid.origin = OSPFv2; + lnid.id.ip.addr = lsa->data->adv_router; + lnid.id.ip.area_id = lsa->area->area_id; + vertex = ls_find_vertex_by_id(ted, lnid); + if (!vertex) + return -1; + + /* Remove Segment Routing Information if any */ + node = vertex->node; + UNSET_FLAG(node->flags, LS_NODE_SR); + memset(&node->srgb, 0, sizeof(struct ls_srgb)); + node->algo[0] = SR_ALGORITHM_UNSET; + node->algo[1] = SR_ALGORITHM_UNSET; + UNSET_FLAG(node->flags, LS_NODE_SRLB); + memset(&node->srlb, 0, sizeof(struct ls_srlb)); + UNSET_FLAG(node->flags, LS_NODE_MSD); + node->msd = 0; + vertex->status = UPDATE; + + ote_debug(" |- Delete SR info. for Vertex %pI4", + &vertex->node->router_id); + + /* Vertex has been updated: export it */ + ospf_te_export(LS_MSG_TYPE_NODE, vertex); + vertex->status = SYNC; + + return 0; +} + +/** + * Parse Opaque Extended Prefix LSA (Type 7) TLVs and update the corresponding + * Link State Subnet with these information (Segment Routing ID). + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_ext_pref(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_node_id lnid; + struct ls_subnet *subnet; + struct ls_prefix *ls_pref; + struct prefix pref; + struct ext_tlv_prefix *ext; + struct ext_subtlv_prefix_sid *pref_sid; + uint32_t label; + uint16_t len, size; + + /* Get corresponding Subnet from Link State Data Base */ + ext = (struct ext_tlv_prefix *)TLV_HDR_TOP(lsa->data); + pref.family = AF_INET; + pref.prefixlen = ext->pref_length; + pref.u.prefix4 = ext->address; + subnet = ls_find_subnet(ted, &pref); + + /* Create new Link State Prefix if not found */ + if (!subnet) { + lnid.origin = OSPFv2; + lnid.id.ip.addr = lsa->data->adv_router; + lnid.id.ip.area_id = lsa->area->area_id; + ls_pref = ls_prefix_new(lnid, &pref); + /* and add it to the TED */ + subnet = ls_subnet_add(ted, ls_pref); + } + + ote_debug(" |- Process Extended Prefix LSA %pI4 for subnet %pFX", + &lsa->data->id, &pref); + + /* + * Check Extended Prefix TLV size against LSA size + * as only one TLV is allowed per LSA + */ + len = TLV_BODY_SIZE(&ext->header); + size = lsa->size - (OSPF_LSA_HEADER_SIZE + TLV_HDR_SIZE); + if (len != size || len <= 0) { + ote_debug(" |- Wrong TLV size: %u instead of %u", + (uint32_t)len, (uint32_t)size); + return -1; + } + + /* Initialize TLV browsing */ + ls_pref = subnet->ls_pref; + pref_sid = (struct ext_subtlv_prefix_sid *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_PREFIX_SIZE); + label = CHECK_FLAG(pref_sid->flags, EXT_SUBTLV_PREFIX_SID_VFLG) + ? GET_LABEL(ntohl(pref_sid->value)) + : ntohl(pref_sid->value); + + /* Check if it is a simple refresh */ + if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR) + && ls_pref->sr.algo == pref_sid->algorithm + && ls_pref->sr.sid_flag == pref_sid->flags + && ls_pref->sr.sid == label) + return 0; + + /* Fulfill SR information */ + ls_pref->sr.algo = pref_sid->algorithm; + ls_pref->sr.sid_flag = pref_sid->flags; + ls_pref->sr.sid = label; + SET_FLAG(ls_pref->flags, LS_PREF_SR); + if (subnet->status != NEW) + subnet->status = UPDATE; + + /* Export Subnet if needed */ + if (subnet->status == NEW || subnet->status == UPDATE) { + ote_debug(" |- %s SID %d to subnet %pFX", + subnet->status == NEW ? "Add" : "Update", + ls_pref->sr.sid, &ls_pref->pref); + + ospf_te_export(LS_MSG_TYPE_PREFIX, subnet); + subnet->status = SYNC; + } + + return 0; +} + +/** + * Delete Link State Subnet information (Segment Routing ID) that correspond to + * the Opaque Extended Prefix LSA (Type 7) TLVs. Note that the Subnet is not + * removed. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_ext_pref(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_subnet *subnet; + struct ls_prefix *ls_pref; + struct prefix pref; + struct ext_tlv_prefix *ext; + + /* Get corresponding Subnet from Link State Data Base */ + ext = (struct ext_tlv_prefix *)TLV_HDR_TOP(lsa->data); + pref.family = AF_INET; + pref.prefixlen = ext->pref_length; + pref.u.prefix4 = ext->address; + subnet = ls_find_subnet(ted, &pref); + + /* Check if there is a corresponding subnet */ + if (!subnet) + return -1; + + ote_debug(" |- Delete SID %d to subnet %pFX", subnet->ls_pref->sr.sid, + &subnet->ls_pref->pref); + + /* Remove Segment Routing information */ + ls_pref = subnet->ls_pref; + UNSET_FLAG(ls_pref->flags, LS_PREF_SR); + memset(&ls_pref->sr, 0, sizeof(struct ls_sid)); + subnet->status = UPDATE; + + /* Subnet has been updated: export it */ + ospf_te_export(LS_MSG_TYPE_PREFIX, subnet); + subnet->status = SYNC; + + return 0; +} + +/** + * Parse Opaque Extended Link LSA (Type 8) TLVs and update the corresponding + * Link State Edge with these information (Segment Routing Adjacency). + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_ext_link(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_node_id lnid; + struct tlv_header *tlvh; + struct ext_tlv_link *ext; + struct ls_edge *edge; + struct ls_attributes *atr; + uint16_t len = 0, sum = 0, i; + uint32_t label; + + /* Get corresponding Edge from Link State Data Base */ + lnid.origin = OSPFv2; + lnid.id.ip.addr = lsa->data->adv_router; + lnid.id.ip.area_id = lsa->area->area_id; + ext = (struct ext_tlv_link *)TLV_HDR_TOP(lsa->data); + edge = get_edge(ted, lnid, ext->link_data); + if (!edge) { + ote_debug(" |- Found no edge from Extended Link Data. Abort!"); + return -1; + } + atr = edge->attributes; + + ote_debug(" |- Process Extended Link LSA %pI4 for edge %pI4", + &lsa->data->id, &edge->attributes->standard.local); + + /* + * Check Extended Link TLV size against LSA size + * as only one TLV is allowed per LSA + */ + len = TLV_BODY_SIZE(&ext->header); + i = lsa->size - (OSPF_LSA_HEADER_SIZE + TLV_HDR_SIZE); + if (len != i || len <= 0) { + ote_debug(" |- Wrong TLV size: %u instead of %u", + (uint32_t)len, (uint32_t)i); + return -1; + } + + /* Initialize subTLVs browsing */ + len -= EXT_TLV_LINK_SIZE; + tlvh = (struct tlv_header *)((char *)(ext) + TLV_HDR_SIZE + + EXT_TLV_LINK_SIZE); + for (; sum < len; tlvh = TLV_HDR_NEXT(tlvh)) { + struct ext_subtlv_adj_sid *adj; + struct ext_subtlv_lan_adj_sid *ladj; + struct ext_subtlv_rmt_itf_addr *rmt; + + switch (ntohs(tlvh->type)) { + case EXT_SUBTLV_ADJ_SID: + if (TLV_BODY_SIZE(tlvh) != EXT_SUBTLV_ADJ_SID_SIZE) + break; + adj = (struct ext_subtlv_adj_sid *)tlvh; + label = CHECK_FLAG(adj->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(adj->value)) + : ntohl(adj->value); + i = CHECK_FLAG(adj->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) ? 1 : 0; + if (((i && CHECK_FLAG(atr->flags, LS_ATTR_BCK_ADJ_SID)) + || (!i && CHECK_FLAG(atr->flags, LS_ATTR_ADJ_SID))) + && atr->adj_sid[i].flags == adj->flags + && atr->adj_sid[i].sid == label + && atr->adj_sid[i].weight == adj->weight) + break; + + atr->adj_sid[i].flags = adj->flags; + atr->adj_sid[i].sid = label; + atr->adj_sid[i].weight = adj->weight; + if (i == 0) + SET_FLAG(atr->flags, LS_ATTR_ADJ_SID); + else + SET_FLAG(atr->flags, LS_ATTR_BCK_ADJ_SID); + if (edge->status != NEW) + edge->status = UPDATE; + + break; + case EXT_SUBTLV_LAN_ADJ_SID: + if (TLV_BODY_SIZE(tlvh) != EXT_SUBTLV_LAN_ADJ_SID_SIZE) + break; + ladj = (struct ext_subtlv_lan_adj_sid *)tlvh; + label = CHECK_FLAG(ladj->flags, + EXT_SUBTLV_LINK_ADJ_SID_VFLG) + ? GET_LABEL(ntohl(ladj->value)) + : ntohl(ladj->value); + i = CHECK_FLAG(ladj->flags, + EXT_SUBTLV_LINK_ADJ_SID_BFLG) ? 1 : 0; + if (((i && CHECK_FLAG(atr->flags, LS_ATTR_BCK_ADJ_SID)) + || (!i && CHECK_FLAG(atr->flags, LS_ATTR_ADJ_SID))) + && atr->adj_sid[i].flags == ladj->flags + && atr->adj_sid[i].sid == label + && atr->adj_sid[i].weight == ladj->weight + && IPV4_ADDR_SAME(&atr->adj_sid[1].neighbor.addr, + &ladj->neighbor_id)) + break; + + atr->adj_sid[i].flags = ladj->flags; + atr->adj_sid[i].sid = label; + atr->adj_sid[i].weight = ladj->weight; + atr->adj_sid[i].neighbor.addr = ladj->neighbor_id; + if (i == 0) + SET_FLAG(atr->flags, LS_ATTR_ADJ_SID); + else + SET_FLAG(atr->flags, LS_ATTR_BCK_ADJ_SID); + if (edge->status != NEW) + edge->status = UPDATE; + + break; + case EXT_SUBTLV_RMT_ITF_ADDR: + if (TLV_BODY_SIZE(tlvh) != EXT_SUBTLV_RMT_ITF_ADDR_SIZE) + break; + rmt = (struct ext_subtlv_rmt_itf_addr *)tlvh; + if (CHECK_FLAG(atr->flags, LS_ATTR_NEIGH_ADDR) + && IPV4_ADDR_SAME(&atr->standard.remote, + &rmt->value)) + break; + + atr->standard.remote = rmt->value; + SET_FLAG(atr->flags, LS_ATTR_NEIGH_ADDR); + if (edge->status != NEW) + edge->status = UPDATE; + + break; + default: + break; + } + sum += TLV_SIZE(tlvh); + } + + /* Export Link State Edge if needed */ + if (edge->status == NEW || edge->status == UPDATE) { + ote_debug(" |- %s Adj-SID %d & %d to edge %pI4", + edge->status == NEW ? "Add" : "Update", + edge->attributes->adj_sid[0].sid, + edge->attributes->adj_sid[1].sid, + &edge->attributes->standard.local); + + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; + } + + return 0; +} + +/** + * Delete Link State Edge information (Segment Routing Adjacency) that + * correspond to the Opaque Extended Link LSA (Type 8) TLVs. Note that the Edge + * is not removed. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_ext_link(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + struct ls_edge *edge; + struct ls_attributes *atr; + struct ext_tlv_link *ext; + struct ls_edge_key key; + + /* Search for corresponding Edge from Link State Data Base */ + ext = (struct ext_tlv_link *)TLV_HDR_TOP(lsa->data); + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &ext->link_data); + edge = ls_find_edge_by_key(ted, key); + + /* Check if there is a corresponding Edge */ + if (!edge) + return -1; + + ote_debug(" |- Delete Adj-SID %d to edge %pI4", + edge->attributes->adj_sid[0].sid, + &edge->attributes->standard.local); + + /* Remove Segment Routing information */ + atr = edge->attributes; + UNSET_FLAG(atr->flags, LS_ATTR_ADJ_SID); + UNSET_FLAG(atr->flags, LS_ATTR_BCK_ADJ_SID); + memset(atr->adj_sid, 0, 2 * sizeof(struct ls_sid)); + edge->status = UPDATE; + + /* Edge has been updated: export it */ + ospf_te_export(LS_MSG_TYPE_ATTRIBUTES, edge); + edge->status = SYNC; + + return 0; +} + +/** + * Parse Opaque LSA Type and call corresponding parser. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_parse_opaque_lsa(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + int rc = -1; + + ote_debug("MPLS-TE (%s): Parse Opaque LSA[%pI4] from Router[%pI4]", + __func__, &lsa->data->id, &lsa->data->adv_router); + + switch (key) { + case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA: + case OPAQUE_TYPE_INTER_AS_LSA: + rc = ospf_te_parse_te(ted, lsa); + break; + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + rc = ospf_te_parse_ri(ted, lsa); + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + rc = ospf_te_parse_ext_pref(ted, lsa); + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + rc = ospf_te_parse_ext_link(ted, lsa); + break; + default: + break; + } + + return rc; +} + +/** + * Parse Opaque LSA Type and call corresponding deletion function. + * + * @param ted Link State Traffic Engineering Database + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_te_delete_opaque_lsa(struct ls_ted *ted, struct ospf_lsa *lsa) +{ + uint8_t key = GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr)); + int rc = -1; + + ote_debug("MPLS-TE (%s): Parse Opaque LSA[%pI4] from Router[%pI4]", + __func__, &lsa->data->id, &lsa->data->adv_router); + + switch (key) { + case OPAQUE_TYPE_TRAFFIC_ENGINEERING_LSA: + case OPAQUE_TYPE_INTER_AS_LSA: + rc = ospf_te_delete_te(ted, lsa); + break; + case OPAQUE_TYPE_ROUTER_INFORMATION_LSA: + rc = ospf_te_delete_ri(ted, lsa); + break; + case OPAQUE_TYPE_EXTENDED_PREFIX_LSA: + rc = ospf_te_delete_ext_pref(ted, lsa); + break; + case OPAQUE_TYPE_EXTENDED_LINK_LSA: + rc = ospf_te_delete_ext_link(ted, lsa); + break; + default: + break; + } + + return rc; +} + +/** + * Update Traffic Engineering Database Elements that correspond to the received + * OSPF LSA. If LSA age is equal to MAX_AGE, call deletion function instead. + * + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_mpls_te_lsa_update(struct ospf_lsa *lsa) +{ + + uint8_t rc; + + /* Check that MPLS-TE is active */ + if (!OspfMplsTE.enabled || !OspfMplsTE.ted) + return 0; + + /* Sanity Check */ + if (lsa == NULL) { + flog_warn(EC_OSPF_LSA_NULL, "TE (%s): Abort! LSA is NULL", + __func__); + return -1; + } + + /* If LSA is MAX_AGE, remove corresponding Link State element */ + if (IS_LSA_MAXAGE(lsa)) { + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + rc = ospf_te_delete_router_lsa(OspfMplsTE.ted, lsa); + break; + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + rc = ospf_te_delete_opaque_lsa(OspfMplsTE.ted, lsa); + break; + default: + rc = 0; + break; + } + } else { + /* Parse LSA to Update corresponding Link State element */ + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + rc = ospf_te_parse_router_lsa(OspfMplsTE.ted, lsa); + break; + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + rc = ospf_te_parse_opaque_lsa(OspfMplsTE.ted, lsa); + break; + default: + rc = 0; + break; + } + } + + return rc; +} + +/** + * Delete Traffic Engineering Database element from OSPF LSA. This function + * process only self LSA (i.e. advertised by the router) which reach MAX_AGE + * as LSA deleted by neighbor routers are Flushed (i.e. advertised with + * age == MAX_AGE) and processed by ospf_mpls_te_lsa_update() function. + * + * @param lsa OSPF Link State Advertisement + * + * @return 0 if success, -1 otherwise + */ +static int ospf_mpls_te_lsa_delete(struct ospf_lsa *lsa) +{ + + uint8_t rc; + + /* Check that MPLS-TE is active */ + if (!OspfMplsTE.enabled || !OspfMplsTE.ted) + return 0; + + /* Sanity Check */ + if (lsa == NULL) { + flog_warn(EC_OSPF_LSA_NULL, "TE (%s): Abort! LSA is NULL", + __func__); + return -1; + } + + /* + * Process only self LSAs that reach MAX_AGE. Indeed, when the router + * need to update or refresh an LSA, it first removes the old LSA from + * the LSDB and then insert the new one. Thus, to avoid removing + * corresponding Link State element and loosing some parameters + * instead of just updating it, only self LSAs that reach MAX_AGE are + * processed here. Other LSAs are processed by ospf_mpls_te_lsa_update() + * and eventually removed when LSA age is MAX_AGE i.e. LSA is flushed + * by the originator. + */ + if (!IS_LSA_SELF(lsa) || !IS_LSA_MAXAGE(lsa)) + return 0; + + /* Parse Link State information */ + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + rc = ospf_te_delete_router_lsa(OspfMplsTE.ted, lsa); + break; + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + rc = ospf_te_delete_opaque_lsa(OspfMplsTE.ted, lsa); + break; + default: + rc = 0; + break; + } + + return rc; +} + +/** + * Send the whole Link State Traffic Engineering Database to the consumer that + * request it through a ZAPI Link State Synchronous Opaque Message. + * + * @param info ZAPI Opaque message + * + * @return 0 if success, -1 otherwise + */ +int ospf_te_sync_ted(struct zapi_opaque_reg_info dst) +{ + int rc = -1; + + /* Check that MPLS-TE and TE distribution are enabled */ + if (!OspfMplsTE.enabled || !OspfMplsTE.export) + return rc; + + rc = ls_sync_ted(OspfMplsTE.ted, zclient, &dst); + + return rc; +} + +/** + * Initialize Traffic Engineering Database from the various OSPF Link State + * Database (LSDB). + * + * @param ted Link State Traffice Engineering Database + * @param ospf OSPF main structure + */ +static void ospf_te_init_ted(struct ls_ted *ted, struct ospf *ospf) +{ + struct listnode *node, *nnode; + struct route_node *rn; + struct ospf_area *area; + struct ospf_lsa *lsa; + + /* Iterate over all areas. */ + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + if (!area->lsdb) + continue; + + /* Parse all Router LSAs from the area LSDB */ + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) + ospf_te_parse_router_lsa(ted, lsa); + + /* Parse all Opaque LSAs from the area LSDB */ + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + ospf_te_parse_opaque_lsa(ted, lsa); + } + + /* Parse AS-external opaque LSAs from OSPF LSDB */ + if (ospf->lsdb) { + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + ospf_te_parse_opaque_lsa(ted, lsa); + } + +} + +/*------------------------------------------------------------------------* + * Following are vty session control functions. + *------------------------------------------------------------------------*/ +#define check_tlv_size(size, msg) \ + do { \ + if (ntohs(tlvh->length) > size) { \ + if (vty != NULL) \ + vty_out(vty, " Wrong %s TLV size: %d(%d)\n", \ + msg, ntohs(tlvh->length), size); \ + else \ + zlog_debug(" Wrong %s TLV size: %d(%d)", \ + msg, ntohs(tlvh->length), size); \ + return size + TLV_HDR_SIZE; \ + } \ + } while (0) + +static uint16_t show_vty_router_addr(struct vty *vty, struct tlv_header *tlvh, + json_object *json) +{ + struct te_tlv_router_addr *top = (struct te_tlv_router_addr *)tlvh; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Router Address"); + + if (vty != NULL) + if (!json) + vty_out(vty, " Router-Address: %pI4\n", &top->value); + else + json_object_string_addf(json, "routerAddress", "%pI4", + &top->value); + else + zlog_debug(" Router-Address: %pI4", &top->value); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_header(struct vty *vty, struct tlv_header *tlvh, + size_t buf_size, json_object *json) +{ + struct te_tlv_link *top = (struct te_tlv_link *)tlvh; + + if (TLV_SIZE(tlvh) > buf_size) { + if (vty != NULL) + vty_out(vty, + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + else + zlog_debug( + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + + if (vty != NULL) + if (!json) + vty_out(vty, " Link: %u octets of data\n", + ntohs(top->header.length)); + else + json_object_int_add(json, "teLinkDataLength", + ntohs(top->header.length)); + else + zlog_debug(" Link: %u octets of data", + ntohs(top->header.length)); + + return TLV_HDR_SIZE; /* Here is special, not "TLV_SIZE". */ +} + +static uint16_t show_vty_link_subtlv_link_type(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_link_type *top; + const char *cp = "Unknown"; + + check_tlv_size(TE_LINK_SUBTLV_TYPE_SIZE, "Link Type"); + + top = (struct te_link_subtlv_link_type *)tlvh; + switch (top->link_type.value) { + case LINK_TYPE_SUBTLV_VALUE_PTP: + cp = "Point-to-point"; + break; + case LINK_TYPE_SUBTLV_VALUE_MA: + cp = "Multiaccess"; + break; + default: + break; + } + + if (vty != NULL) + if (!json) + vty_out(vty, " Link-Type: %s (%u)\n", cp, + top->link_type.value); + else + json_object_string_add(json, "accessType", cp); + else + zlog_debug(" Link-Type: %s (%u)", cp, top->link_type.value); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_link_id(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_link_id *top; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Link ID"); + + top = (struct te_link_subtlv_link_id *)tlvh; + if (vty != NULL) + if (!json) + vty_out(vty, " Link-ID: %pI4\n", &top->value); + else + json_object_string_addf(json, "linkID", "%pI4", + &top->value); + else + zlog_debug(" Link-ID: %pI4", &top->value); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_lclif_ipaddr(struct vty *vty, + struct tlv_header *tlvh, + size_t buf_size, + json_object *json) +{ + struct te_link_subtlv_lclif_ipaddr *top; + json_object *json_addr, *json_obj; + char buf[4]; + int i, n; + + if (TLV_SIZE(tlvh) > buf_size) { + if (vty != NULL) + vty_out(vty, + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + else + zlog_debug( + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + + top = (struct te_link_subtlv_lclif_ipaddr *)tlvh; + n = ntohs(tlvh->length) / sizeof(top->value[0]); + + if (vty != NULL) + if (!json) + vty_out(vty, " Local Interface IP Address(es): %d\n", + n); + else { + json_addr = json_object_new_array(); + json_object_object_add(json, "localIPAddresses", + json_addr); + } + else + zlog_debug(" Local Interface IP Address(es): %d", n); + + for (i = 0; i < n; i++) { + if (vty != NULL) + if (!json) + vty_out(vty, " #%d: %pI4\n", i, + &top->value[i]); + else { + json_obj = json_object_new_object(); + snprintfrr(buf, 2, "%d", i); + json_object_string_addf(json_obj, buf, "%pI4", + &top->value[i]); + json_object_array_add(json_addr, json_obj); + } + else + zlog_debug(" #%d: %pI4", i, &top->value[i]); + } + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_rmtif_ipaddr(struct vty *vty, + struct tlv_header *tlvh, + size_t buf_size, + json_object *json) +{ + struct te_link_subtlv_rmtif_ipaddr *top; + json_object *json_addr, *json_obj; + char buf[4]; + int i, n; + + if (TLV_SIZE(tlvh) > buf_size) { + if (vty != NULL) + vty_out(vty, + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + else + zlog_debug( + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + + top = (struct te_link_subtlv_rmtif_ipaddr *)tlvh; + n = ntohs(tlvh->length) / sizeof(top->value[0]); + if (vty != NULL) + if (!json) + vty_out(vty, " Remote Interface IP Address(es): %d\n", + n); + else { + json_addr = json_object_new_array(); + json_object_object_add(json, "remoteIPAddresses", + json_addr); + } + else + zlog_debug(" Remote Interface IP Address(es): %d", n); + + for (i = 0; i < n; i++) { + if (vty != NULL) + if (!json) + vty_out(vty, " #%d: %pI4\n", i, + &top->value[i]); + else { + json_obj = json_object_new_object(); + snprintfrr(buf, 2, "%d", i); + json_object_string_addf(json_obj, buf, "%pI4", + &top->value[i]); + json_object_array_add(json_addr, json_obj); + } + else + zlog_debug(" #%d: %pI4", i, &top->value[i]); + } + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_te_metric(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_te_metric *top; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "TE Metric"); + + top = (struct te_link_subtlv_te_metric *)tlvh; + if (vty != NULL) + if (!json) + vty_out(vty, " Traffic Engineering Metric: %u\n", + (uint32_t)ntohl(top->value)); + else + json_object_int_add(json, "teDefaultMetric", + (uint32_t)ntohl(top->value)); + else + zlog_debug(" Traffic Engineering Metric: %u", + (uint32_t)ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_max_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_max_bw *top; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Maximum Bandwidth"); + + top = (struct te_link_subtlv_max_bw *)tlvh; + fval = ntohf(top->value); + + if (vty != NULL) + if (!json) + vty_out(vty, " Maximum Bandwidth: %g (Bytes/sec)\n", + fval); + else + json_object_double_add(json, "maxLinkBandwidth", fval); + else + zlog_debug(" Maximum Bandwidth: %g (Bytes/sec)", fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_max_rsv_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_max_rsv_bw *top; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Maximum Reservable Bandwidth"); + + top = (struct te_link_subtlv_max_rsv_bw *)tlvh; + fval = ntohf(top->value); + + if (vty != NULL) + if (!json) + vty_out(vty, " Maximum Reservable Bandwidth: %g (Bytes/sec)\n", + fval); + else + json_object_double_add(json, "maxResvLinkBandwidth", + fval); + else + zlog_debug(" Maximum Reservable Bandwidth: %g (Bytes/sec)", + fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_unrsv_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_unrsv_bw *top; + json_object *json_bw, *json_obj; + float fval1, fval2; + char buf[16]; + int i; + + check_tlv_size(TE_LINK_SUBTLV_UNRSV_SIZE, "Unreserved Bandwidth"); + + top = (struct te_link_subtlv_unrsv_bw *)tlvh; + if (vty != NULL) + if (!json) + vty_out(vty, + " Unreserved Bandwidth per Class Type in Byte/s:\n"); + else { + json_bw = json_object_new_array(); + json_object_object_add(json, "unreservedBandwidth", + json_bw); + } + else + zlog_debug( + " Unreserved Bandwidth per Class Type in Byte/s:"); + for (i = 0; i < MAX_CLASS_TYPE; i += 2) { + fval1 = ntohf(top->value[i]); + fval2 = ntohf(top->value[i + 1]); + + if (vty != NULL) + if (!json) + vty_out(vty, + " [%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n", + i, fval1, i + 1, fval2); + else { + json_obj = json_object_new_object(); + snprintfrr(buf, 12, "classType-%u", i); + json_object_double_add(json_obj, buf, fval1); + json_object_array_add(json_bw, json_obj); + json_obj = json_object_new_object(); + snprintfrr(buf, 12, "classType-%u", i + 1); + json_object_double_add(json_obj, buf, fval2); + json_object_array_add(json_bw, json_obj); + } + else + zlog_debug( + " [%d]: %g (Bytes/sec), [%d]: %g (Bytes/sec)", + i, fval1, i + 1, fval2); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_rsc_clsclr(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_rsc_clsclr *top; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Resource class/color"); + + top = (struct te_link_subtlv_rsc_clsclr *)tlvh; + if (vty != NULL) + if (!json) + vty_out(vty, " Resource class/color: 0x%x\n", + (uint32_t)ntohl(top->value)); + else + json_object_string_addf(json, "administrativeGroup", + "0x%x", + (uint32_t)ntohl(top->value)); + else + zlog_debug(" Resource Class/Color: 0x%x", + (uint32_t)ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_lrrid(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_lrrid *top; + + check_tlv_size(TE_LINK_SUBTLV_LRRID_SIZE, "Local/Remote Router ID"); + + top = (struct te_link_subtlv_lrrid *)tlvh; + + if (vty != NULL) { + if (!json) { + vty_out(vty, " Local TE Router ID: %pI4\n", + &top->local); + vty_out(vty, " Remote TE Router ID: %pI4\n", + &top->remote); + } else { + json_object_string_addf(json, "localTeRouterID", "%pI4", + &top->local); + json_object_string_addf(json, "remoteTeRouterID", + "%pI4", &top->remote); + } + } else { + zlog_debug(" Local TE Router ID: %pI4", + &top->local); + zlog_debug(" Remote TE Router ID: %pI4", + &top->remote); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_llri(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_llri *top; + + check_tlv_size(TE_LINK_SUBTLV_LLRI_SIZE, "Link Local/Remote ID"); + + top = (struct te_link_subtlv_llri *)tlvh; + + if (vty != NULL) { + if (!json) { + vty_out(vty, " Link Local ID: %d\n", + (uint32_t)ntohl(top->local)); + vty_out(vty, " Link Remote ID: %d\n", + (uint32_t)ntohl(top->remote)); + } else { + json_object_int_add(json, "localLinkID", + (uint32_t)ntohl(top->local)); + json_object_int_add(json, "remoteLinkID", + (uint32_t)ntohl(top->remote)); + } + } else { + zlog_debug(" Link Local ID: %d", + (uint32_t)ntohl(top->local)); + zlog_debug(" Link Remote ID: %d", + (uint32_t)ntohl(top->remote)); + } + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_rip(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_rip *top; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Remote ASBR Address"); + + top = (struct te_link_subtlv_rip *)tlvh; + + if (vty != NULL) + if (!json) + vty_out(vty, " Inter-AS TE Remote ASBR IP address: %pI4\n", + &top->value); + else + json_object_string_addf(json, "remoteAsbrAddress", + "%pI4", &top->value); + else + zlog_debug(" Inter-AS TE Remote ASBR IP address: %pI4", + &top->value); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_ras(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_ras *top; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Remote AS number"); + + top = (struct te_link_subtlv_ras *)tlvh; + + if (vty != NULL) + if (!json) + vty_out(vty, " Inter-AS TE Remote AS number: %u\n", + ntohl(top->value)); + else + json_object_int_add(json, "remoteAsbrNumber", + ntohl(top->value)); + else + zlog_debug(" Inter-AS TE Remote AS number: %u", + ntohl(top->value)); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_av_delay(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_av_delay *top; + uint32_t delay; + uint32_t anomalous; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Average Link Delay"); + + top = (struct te_link_subtlv_av_delay *)tlvh; + delay = (uint32_t)ntohl(top->value) & TE_EXT_MASK; + anomalous = (uint32_t)ntohl(top->value) & TE_EXT_ANORMAL; + + if (vty != NULL) + if (!json) + vty_out(vty, " %s Average Link Delay: %d (micro-sec)\n", + anomalous ? "Anomalous" : "Normal", delay); + else { + json_object_int_add(json, "oneWayDelay", delay); + json_object_string_add(json, "oneWayDelayNormality", + anomalous ? "abnormal" + : "normal"); + } + else + zlog_debug(" %s Average Link Delay: %d (micro-sec)", + anomalous ? "Anomalous" : "Normal", delay); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_mm_delay(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_mm_delay *top; + uint32_t low, high; + uint32_t anomalous; + + check_tlv_size(TE_LINK_SUBTLV_MM_DELAY_SIZE, "Min/Max Link Delay"); + + top = (struct te_link_subtlv_mm_delay *)tlvh; + low = (uint32_t)ntohl(top->low) & TE_EXT_MASK; + anomalous = (uint32_t)ntohl(top->low) & TE_EXT_ANORMAL; + high = (uint32_t)ntohl(top->high); + + if (vty != NULL) + if (!json) + vty_out(vty, + " %s Min/Max Link Delay: %d/%d (micro-sec)\n", + anomalous ? "Anomalous" : "Normal", low, high); + else { + json_object_int_add(json, "oneWayMinDelay", low); + json_object_string_add(json, "oneWayMinDelayNormality", + anomalous ? "abnormal" + : "normal"); + json_object_int_add(json, "oneWayMaxDelay", high); + json_object_string_add(json, "oneWayMaxDelayNormality", + anomalous ? "abnormal" + : "normal"); + } + else + zlog_debug(" %s Min/Max Link Delay: %d/%d (micro-sec)", + anomalous ? "Anomalous" : "Normal", low, high); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_delay_var(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_delay_var *top; + uint32_t jitter; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Link Delay Variation"); + + top = (struct te_link_subtlv_delay_var *)tlvh; + jitter = (uint32_t)ntohl(top->value) & TE_EXT_MASK; + + if (vty != NULL) + if (!json) + vty_out(vty, " Delay Variation: %d (micro-sec)\n", + jitter); + else + json_object_int_add(json, "oneWayDelayVariation", + jitter); + else + zlog_debug(" Delay Variation: %d (micro-sec)", jitter); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_pkt_loss(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_pkt_loss *top; + uint32_t loss; + uint32_t anomalous; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Link Loss"); + + top = (struct te_link_subtlv_pkt_loss *)tlvh; + loss = (uint32_t)ntohl(top->value) & TE_EXT_MASK; + fval = (float)(loss * LOSS_PRECISION); + anomalous = (uint32_t)ntohl(top->value) & TE_EXT_ANORMAL; + + if (vty != NULL) + if (!json) + vty_out(vty, " %s Link Loss: %g (%%)\n", + anomalous ? "Anomalous" : "Normal", fval); + else { + json_object_double_add(json, "oneWayPacketLoss", fval); + json_object_string_add(json, + "oneWayPacketLossNormality", + anomalous ? "abnormal" + : "normal"); + } + else + zlog_debug(" %s Link Loss: %g (%%)", + anomalous ? "Anomalous" : "Normal", fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_res_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_res_bw *top; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Residual Bandwidth"); + + top = (struct te_link_subtlv_res_bw *)tlvh; + fval = ntohf(top->value); + + if (vty != NULL) + if (!json) + vty_out(vty, + " Unidirectional Residual Bandwidth: %g (Bytes/sec)\n", + fval); + else + json_object_double_add(json, "oneWayResidualBandwidth", + fval); + else + zlog_debug( + " Unidirectional Residual Bandwidth: %g (Bytes/sec)", + fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_ava_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_ava_bw *top; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Available Bandwidth"); + + top = (struct te_link_subtlv_ava_bw *)tlvh; + fval = ntohf(top->value); + + if (vty != NULL) + if (!json) + vty_out(vty, + " Unidirectional Available Bandwidth: %g (Bytes/sec)\n", + fval); + else + json_object_double_add(json, "oneWayAvailableBandwidth", + fval); + else + zlog_debug( + " Unidirectional Available Bandwidth: %g (Bytes/sec)", + fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_link_subtlv_use_bw(struct vty *vty, + struct tlv_header *tlvh, + json_object *json) +{ + struct te_link_subtlv_use_bw *top; + float fval; + + check_tlv_size(TE_LINK_SUBTLV_DEF_SIZE, "Utilized Bandwidth"); + + top = (struct te_link_subtlv_use_bw *)tlvh; + fval = ntohf(top->value); + + if (vty != NULL) + if (!json) + vty_out(vty, + " Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n", + fval); + else + json_object_double_add(json, "oneWayUtilizedBandwidth", + fval); + else + zlog_debug( + " Unidirectional Utilized Bandwidth: %g (Bytes/sec)", + fval); + + return TLV_SIZE(tlvh); +} + +static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh, + size_t buf_size, json_object *json) +{ + json_object *obj; + + if (TLV_SIZE(tlvh) > buf_size) { + if (vty != NULL) + vty_out(vty, + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + else + zlog_debug( + " TLV size %d exceeds buffer size. Abort!", + TLV_SIZE(tlvh)); + return buf_size; + } + + if (vty != NULL) + if (!json) + vty_out(vty, " Unknown TLV: [type(0x%x), length(0x%x)]\n", + ntohs(tlvh->type), ntohs(tlvh->length)); + else { + obj = json_object_new_object(); + json_object_string_addf(obj, "type", "0x%x", + ntohs(tlvh->type)); + json_object_string_addf(obj, "length", "0x%x", + ntohs(tlvh->length)); + json_object_object_add(json, "unknownTLV", obj); + } + else + zlog_debug(" Unknown TLV: [type(0x%x), length(0x%x)]", + ntohs(tlvh->type), ntohs(tlvh->length)); + + return TLV_SIZE(tlvh); +} + +static uint16_t ospf_mpls_te_show_link_subtlv(struct vty *vty, + struct tlv_header *tlvh0, + uint16_t subtotal, uint16_t total, + json_object *json) +{ + struct tlv_header *tlvh; + uint16_t sum = subtotal; + + for (tlvh = tlvh0; sum < total; tlvh = TLV_HDR_NEXT(tlvh)) { + switch (ntohs(tlvh->type)) { + case TE_LINK_SUBTLV_LINK_TYPE: + sum += show_vty_link_subtlv_link_type(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_LINK_ID: + sum += show_vty_link_subtlv_link_id(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_LCLIF_IPADDR: + sum += show_vty_link_subtlv_lclif_ipaddr(vty, tlvh, + total - sum, + json); + break; + case TE_LINK_SUBTLV_RMTIF_IPADDR: + sum += show_vty_link_subtlv_rmtif_ipaddr(vty, tlvh, + total - sum, + json); + break; + case TE_LINK_SUBTLV_TE_METRIC: + sum += show_vty_link_subtlv_te_metric(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_MAX_BW: + sum += show_vty_link_subtlv_max_bw(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_MAX_RSV_BW: + sum += show_vty_link_subtlv_max_rsv_bw(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_UNRSV_BW: + sum += show_vty_link_subtlv_unrsv_bw(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_RSC_CLSCLR: + sum += show_vty_link_subtlv_rsc_clsclr(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_LRRID: + sum += show_vty_link_subtlv_lrrid(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_LLRI: + sum += show_vty_link_subtlv_llri(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_RIP: + sum += show_vty_link_subtlv_rip(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_RAS: + sum += show_vty_link_subtlv_ras(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_AV_DELAY: + sum += show_vty_link_subtlv_av_delay(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_MM_DELAY: + sum += show_vty_link_subtlv_mm_delay(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_DELAY_VAR: + sum += show_vty_link_subtlv_delay_var(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_PKT_LOSS: + sum += show_vty_link_subtlv_pkt_loss(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_RES_BW: + sum += show_vty_link_subtlv_res_bw(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_AVA_BW: + sum += show_vty_link_subtlv_ava_bw(vty, tlvh, json); + break; + case TE_LINK_SUBTLV_USE_BW: + sum += show_vty_link_subtlv_use_bw(vty, tlvh, json); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, total - sum, + json); + break; + } + } + return sum; +} + +static void ospf_mpls_te_show_info(struct vty *vty, struct json_object *json, + struct ospf_lsa *lsa) +{ + struct lsa_header *lsah = lsa->data; + struct tlv_header *tlvh, *next; + uint16_t sum, sub, total; + uint16_t (*subfunc)(struct vty * vty, struct tlv_header * tlvh, + uint16_t subtotal, uint16_t total, + struct json_object *json) = NULL; + json_object *jobj = NULL; + + sum = 0; + total = lsa->size - OSPF_LSA_HEADER_SIZE; + + for (tlvh = TLV_HDR_TOP(lsah); sum < total && tlvh; + tlvh = (next ? next : TLV_HDR_NEXT(tlvh))) { + if (subfunc != NULL) { + sum = (*subfunc)(vty, tlvh, sum, total, jobj); + next = (struct tlv_header *)((char *)tlvh + sum); + subfunc = NULL; + continue; + } + + next = NULL; + sub = total - sum; + switch (ntohs(tlvh->type)) { + case TE_TLV_ROUTER_ADDR: + if (json) { + jobj = json_object_new_object(); + json_object_object_add(json, "teRouterAddress", + jobj); + } + sum += show_vty_router_addr(vty, tlvh, jobj); + break; + case TE_TLV_LINK: + if (json) { + jobj = json_object_new_object(); + json_object_object_add(json, "teLink", jobj); + } + sum += show_vty_link_header(vty, tlvh, sub, jobj); + subfunc = ospf_mpls_te_show_link_subtlv; + next = TLV_DATA(tlvh); + break; + default: + sum += show_vty_unknown_tlv(vty, tlvh, sub, json); + break; + } + } + return; +} + +static void ospf_mpls_te_config_write_router(struct vty *vty) +{ + + if (OspfMplsTE.enabled) { + vty_out(vty, " mpls-te on\n"); + vty_out(vty, " mpls-te router-address %pI4\n", + &OspfMplsTE.router_addr.value); + + if (OspfMplsTE.inter_as == AS) + vty_out(vty, " mpls-te inter-as as\n"); + if (OspfMplsTE.inter_as == Area) + vty_out(vty, " mpls-te inter-as area %pI4 \n", + &OspfMplsTE.interas_areaid); + if (OspfMplsTE.export) + vty_out(vty, " mpls-te export\n"); + } + return; +} + +/*------------------------------------------------------------------------* + * Following are vty command functions. + *------------------------------------------------------------------------*/ + +DEFUN (ospf_mpls_te_on, + ospf_mpls_te_on_cmd, + "mpls-te on", + MPLS_TE_STR + "Enable the MPLS-TE functionality\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *node; + struct mpls_te_link *lp; + + if (OspfMplsTE.enabled) + return CMD_SUCCESS; + + /* Check that the OSPF is using default VRF */ + if (ospf->vrf_id != VRF_DEFAULT) { + vty_out(vty, "MPLS TE is only supported in default VRF\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ote_debug("MPLS-TE: OFF -> ON"); + + OspfMplsTE.enabled = true; + + /* Reoriginate RFC3630 & RFC6827 Links */ + ospf_mpls_te_foreach_area(ospf_mpls_te_lsa_schedule, + REORIGINATE_THIS_LSA); + + /* Reoriginate LSA if INTER-AS is always on */ + if (OspfMplsTE.inter_as != Off) { + for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, lp)) { + if (IS_INTER_AS(lp->type)) { + ospf_mpls_te_lsa_schedule(lp, + REORIGINATE_THIS_LSA); + } + } + } + + /* Create TED and initialize it */ + OspfMplsTE.ted = ls_ted_new(1, "OSPF", 0); + if (!OspfMplsTE.ted) { + vty_out(vty, "Unable to create Link State Data Base\n"); + return CMD_WARNING; + } + ospf_te_init_ted(OspfMplsTE.ted, ospf); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_mpls_te, + no_ospf_mpls_te_cmd, + "no mpls-te [on]", + NO_STR + MPLS_TE_STR + "Disable the MPLS-TE functionality\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *node, *nnode; + struct mpls_te_link *lp; + + if (!OspfMplsTE.enabled) + return CMD_SUCCESS; + + ote_debug("MPLS-TE: ON -> OFF"); + + /* Remove TED */ + ls_ted_del_all(&OspfMplsTE.ted); + OspfMplsTE.enabled = false; + + /* Flush all TE Opaque LSAs */ + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) + if (CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + + /* + * This resets the OspfMplsTE.inter_as to its initial state. + * This is to avoid having an inter-as value different from + * Off when mpls-te gets restarted (after being removed) + */ + OspfMplsTE.inter_as = Off; + + return CMD_SUCCESS; +} + +DEFUN (ospf_mpls_te_router_addr, + ospf_mpls_te_router_addr_cmd, + "mpls-te router-address A.B.C.D", + MPLS_TE_STR + "Stable IP address of the advertising router\n" + "MPLS-TE router address in IPv4 address format\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4 = 2; + struct te_tlv_router_addr *ra = &OspfMplsTE.router_addr; + struct in_addr value; + + if (!inet_aton(argv[idx_ipv4]->arg, &value)) { + vty_out(vty, "Please specify Router-Addr by A.B.C.D\n"); + return CMD_WARNING; + } + + if (ntohs(ra->header.type) == 0 + || ntohl(ra->value.s_addr) != ntohl(value.s_addr)) { + struct listnode *node, *nnode; + struct mpls_te_link *lp; + int need_to_reoriginate = 0; + + set_mpls_te_router_addr(value); + + if (!OspfMplsTE.enabled) + return CMD_SUCCESS; + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { + if ((lp->area == NULL) || IS_FLOOD_AS(lp->flags)) + continue; + + if (!CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) { + need_to_reoriginate = 1; + break; + } + } + + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) { + if ((lp->area == NULL) || IS_FLOOD_AS(lp->flags)) + continue; + + if (need_to_reoriginate) + SET_FLAG(lp->flags, LPFLG_LSA_FORCED_REFRESH); + else + ospf_mpls_te_lsa_schedule(lp, REFRESH_THIS_LSA); + } + + if (need_to_reoriginate) + ospf_mpls_te_foreach_area(ospf_mpls_te_lsa_schedule, + REORIGINATE_THIS_LSA); + } + + return CMD_SUCCESS; +} + +static int set_inter_as_mode(struct vty *vty, const char *mode_name, + const char *area_id) +{ + enum inter_as_mode mode; + struct listnode *node; + struct mpls_te_link *lp; + int format; + + if (OspfMplsTE.enabled) { + + /* Read and Check inter_as mode */ + if (strcmp(mode_name, "as") == 0) + mode = AS; + else if (strcmp(mode_name, "area") == 0) { + mode = Area; + VTY_GET_OSPF_AREA_ID(OspfMplsTE.interas_areaid, format, + area_id); + } else { + vty_out(vty, + "Unknown mode. Please choose between as or area\n"); + return CMD_WARNING; + } + + ote_debug( + "MPLS-TE (%s): Inter-AS enable with %s flooding support", + __func__, mode2text[mode]); + + /* Enable mode and re-originate LSA if needed */ + if ((OspfMplsTE.inter_as == Off) + && (mode != OspfMplsTE.inter_as)) { + OspfMplsTE.inter_as = mode; + /* Re-originate all InterAS-TEv2 LSA */ + for (ALL_LIST_ELEMENTS_RO(OspfMplsTE.iflist, node, + lp)) { + if (IS_INTER_AS(lp->type)) { + if (mode == AS) + SET_FLAG(lp->flags, + LPFLG_LSA_FLOOD_AS); + else + UNSET_FLAG(lp->flags, + LPFLG_LSA_FLOOD_AS); + ospf_mpls_te_lsa_schedule( + lp, REORIGINATE_THIS_LSA); + } + } + } else { + vty_out(vty, + "Please change Inter-AS support to disable first before going to mode %s\n", + mode2text[mode]); + return CMD_WARNING; + } + } else { + vty_out(vty, "mpls-te has not been turned on\n"); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + + +DEFUN (ospf_mpls_te_inter_as_as, + ospf_mpls_te_inter_as_cmd, + "mpls-te inter-as as", + MPLS_TE_STR + "Configure MPLS-TE Inter-AS support\n" + "AS native mode self originate INTER_AS LSA with Type 11 (as flooding scope)\n") +{ + return set_inter_as_mode(vty, "as", ""); +} + +DEFUN (ospf_mpls_te_inter_as_area, + ospf_mpls_te_inter_as_area_cmd, + "mpls-te inter-as area ", + MPLS_TE_STR + "Configure MPLS-TE Inter-AS support\n" + "AREA native mode self originate INTER_AS LSA with Type 10 (area flooding scope)\n" + "OSPF area ID in IP format\n" + "OSPF area ID as decimal value\n") +{ + int idx_ipv4_number = 3; + return set_inter_as_mode(vty, "area", argv[idx_ipv4_number]->arg); +} + +DEFUN (no_ospf_mpls_te_inter_as, + no_ospf_mpls_te_inter_as_cmd, + "no mpls-te inter-as", + NO_STR + MPLS_TE_STR + "Disable MPLS-TE Inter-AS support\n") +{ + + struct listnode *node, *nnode; + struct mpls_te_link *lp; + + ote_debug("MPLS-TE: Inter-AS support OFF"); + + if ((OspfMplsTE.enabled) && (OspfMplsTE.inter_as != Off)) { + /* Flush all Inter-AS LSA */ + for (ALL_LIST_ELEMENTS(OspfMplsTE.iflist, node, nnode, lp)) + if (IS_INTER_AS(lp->type) + && CHECK_FLAG(lp->flags, LPFLG_LSA_ENGAGED)) + ospf_mpls_te_lsa_schedule(lp, FLUSH_THIS_LSA); + + OspfMplsTE.inter_as = Off; + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_mpls_te_export, + ospf_mpls_te_export_cmd, + "mpls-te export", + MPLS_TE_STR + "Export the MPLS-TE information as Link State\n") +{ + + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (OspfMplsTE.enabled) { + if (ls_register(zclient, true) != 0) { + vty_out(vty, "Unable to register Link State\n"); + return CMD_WARNING; + } + OspfMplsTE.export = true; + } else { + vty_out(vty, "mpls-te has not been turned on\n"); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + + +DEFUN (no_ospf_mpls_te_export, + no_ospf_mpls_te_export_cmd, + "no mpls-te export", + NO_STR + MPLS_TE_STR + "Stop export of the MPLS-TE information as Link State\n") +{ + + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (OspfMplsTE.export) { + if (ls_unregister(zclient, true) != 0) { + vty_out(vty, "Unable to unregister Link State\n"); + return CMD_WARNING; + } + OspfMplsTE.export = false; + } + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_mpls_te_router, + show_ip_ospf_mpls_te_router_cmd, + "show ip ospf mpls-te router", + SHOW_STR + IP_STR + OSPF_STR + "MPLS-TE information\n" + "MPLS-TE Router parameters\n") +{ + if (OspfMplsTE.enabled) { + vty_out(vty, "--- MPLS-TE router parameters ---\n"); + + if (ntohs(OspfMplsTE.router_addr.header.type) != 0) + show_vty_router_addr(vty, + &OspfMplsTE.router_addr.header, + NULL); + else + vty_out(vty, " Router address is not set\n"); + vty_out(vty, " Link State distribution is %s\n", + OspfMplsTE.export ? "Active" : "Inactive"); + } + return CMD_SUCCESS; +} + +static void show_mpls_te_link_sub(struct vty *vty, struct interface *ifp, + json_object *json) +{ + struct mpls_te_link *lp; + + if ((OspfMplsTE.enabled) && HAS_LINK_PARAMS(ifp) && !if_is_loopback(ifp) + && if_is_up(ifp) + && ((lp = lookup_linkparams_by_ifp(ifp)) != NULL)) { + /* Continue only if interface is not passive or support Inter-AS + * TEv2 */ + if (!(ospf_oi_count(ifp) > 0)) { + if (IS_INTER_AS(lp->type)) { + vty_out(vty, + "-- Inter-AS TEv2 link parameters for %s --\n", + ifp->name); + } else { + /* MPLS-TE is not activate on this interface */ + /* or this interface is passive and Inter-AS + * TEv2 is not activate */ + vty_out(vty, + " %s: MPLS-TE is disabled on this interface\n", + ifp->name); + return; + } + } else { + vty_out(vty, "-- MPLS-TE link parameters for %s --\n", + ifp->name); + } + + if (TLV_TYPE(lp->link_type) != 0) + show_vty_link_subtlv_link_type(vty, + &lp->link_type.header, + json); + if (TLV_TYPE(lp->link_id) != 0) + show_vty_link_subtlv_link_id(vty, &lp->link_id.header, + json); + if (TLV_TYPE(lp->lclif_ipaddr) != 0) + show_vty_link_subtlv_lclif_ipaddr( + vty, &lp->lclif_ipaddr.header, + lp->lclif_ipaddr.header.length, + json); + if (TLV_TYPE(lp->rmtif_ipaddr) != 0) + show_vty_link_subtlv_rmtif_ipaddr( + vty, &lp->rmtif_ipaddr.header, + lp->rmtif_ipaddr.header.length, + json); + if (TLV_TYPE(lp->rip) != 0) + show_vty_link_subtlv_rip(vty, &lp->rip.header, json); + if (TLV_TYPE(lp->ras) != 0) + show_vty_link_subtlv_ras(vty, &lp->ras.header, json); + if (TLV_TYPE(lp->te_metric) != 0) + show_vty_link_subtlv_te_metric(vty, + &lp->te_metric.header, + json); + if (TLV_TYPE(lp->max_bw) != 0) + show_vty_link_subtlv_max_bw(vty, &lp->max_bw.header, + json); + if (TLV_TYPE(lp->max_rsv_bw) != 0) + show_vty_link_subtlv_max_rsv_bw(vty, + &lp->max_rsv_bw.header, + json); + if (TLV_TYPE(lp->unrsv_bw) != 0) + show_vty_link_subtlv_unrsv_bw(vty, + &lp->unrsv_bw.header, + json); + if (TLV_TYPE(lp->rsc_clsclr) != 0) + show_vty_link_subtlv_rsc_clsclr(vty, + &lp->rsc_clsclr.header, + json); + if (TLV_TYPE(lp->av_delay) != 0) + show_vty_link_subtlv_av_delay(vty, + &lp->av_delay.header, + json); + if (TLV_TYPE(lp->mm_delay) != 0) + show_vty_link_subtlv_mm_delay(vty, + &lp->mm_delay.header, + json); + if (TLV_TYPE(lp->delay_var) != 0) + show_vty_link_subtlv_delay_var(vty, + &lp->delay_var.header, + json); + if (TLV_TYPE(lp->pkt_loss) != 0) + show_vty_link_subtlv_pkt_loss(vty, + &lp->pkt_loss.header, + json); + if (TLV_TYPE(lp->res_bw) != 0) + show_vty_link_subtlv_res_bw(vty, &lp->res_bw.header, + json); + if (TLV_TYPE(lp->ava_bw) != 0) + show_vty_link_subtlv_ava_bw(vty, &lp->ava_bw.header, + json); + if (TLV_TYPE(lp->use_bw) != 0) + show_vty_link_subtlv_use_bw(vty, &lp->use_bw.header, + json); + vty_out(vty, "---------------\n\n"); + } else { + vty_out(vty, " %s: MPLS-TE is disabled on this interface\n", + ifp->name); + } + + return; +} + +DEFUN (show_ip_ospf_mpls_te_link, + show_ip_ospf_mpls_te_link_cmd, + "show ip ospf mpls-te interface [INTERFACE]", + SHOW_STR + IP_STR + OSPF_STR + "MPLS-TE information\n" + "Interface information\n" + "Interface name\n") +{ + struct vrf *vrf; + int idx_interface = 0; + struct interface *ifp = NULL; + struct ospf *ospf = NULL; + + argv_find(argv, argc, "INTERFACE", &idx_interface); + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) + return CMD_SUCCESS; + vrf = vrf_lookup_by_id(VRF_DEFAULT); + if (!vrf) + return CMD_SUCCESS; + if (idx_interface) { + ifp = if_lookup_by_name(argv[idx_interface]->arg, VRF_DEFAULT); + if (ifp == NULL) { + vty_out(vty, "No such interface name in vrf %s\n", + vrf->name); + return CMD_SUCCESS; + } + } + if (!ifp) { + FOR_ALL_INTERFACES (vrf, ifp) + show_mpls_te_link_sub(vty, ifp, NULL); + return CMD_SUCCESS; + } + + show_mpls_te_link_sub(vty, ifp, NULL); + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_mpls_te_db, + show_ip_ospf_mpls_te_db_cmd, + "show ip ospf mpls-te database []|edge [A.B.C.D]|subnet [A.B.C.D/M]>] [verbose|json]", + SHOW_STR + IP_STR + OSPF_STR + "MPLS-TE information\n" + "MPLS-TE database\n" + "MPLS-TE Vertex\n" + "Self-originated MPLS-TE router\n" + "Advertised MPLS-TE router\n" + "MPLS-TE router ID (as an IP address)\n" + "MPLS-TE Edge\n" + "MPLS-TE Edge ID (as an IP address)\n" + "MPLS-TE Subnet\n" + "MPLS-TE Subnet ID (as an IP prefix)\n" + "Verbose output\n" + JSON_STR) +{ + int idx = 0; + struct in_addr ip_addr; + struct prefix pref; + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + uint64_t key; + struct ls_edge_key ekey; + bool verbose = false; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (!OspfMplsTE.enabled || !OspfMplsTE.ted) { + vty_out(vty, "MPLS-TE database is not enabled\n"); + return CMD_WARNING; + } + + if (uj) + json = json_object_new_object(); + + if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "verbose")) + verbose = true; + + idx = 5; + if (argv_find(argv, argc, "vertex", &idx)) { + /* Show Vertex */ + if (argv_find(argv, argc, "self-originate", &idx)) + vertex = OspfMplsTE.ted->self; + else if (argv_find(argv, argc, "adv-router", &idx)) { + if (!inet_aton(argv[idx + 1]->arg, &ip_addr)) { + vty_out(vty, + "Specified Router ID %s is invalid\n", + argv[idx + 1]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Vertex from the Link State Database */ + key = ((uint64_t)ntohl(ip_addr.s_addr)) & 0xffffffff; + vertex = ls_find_vertex_by_key(OspfMplsTE.ted, key); + if (!vertex) { + vty_out(vty, "No vertex found for ID %pI4\n", + &ip_addr); + return CMD_WARNING; + } + } else + vertex = NULL; + + if (vertex) + ls_show_vertex(vertex, vty, json, verbose); + else + ls_show_vertices(OspfMplsTE.ted, vty, json, verbose); + + } else if (argv_find(argv, argc, "edge", &idx)) { + /* Show Edge */ + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &ip_addr)) { + vty_out(vty, + "Specified Edge ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Edge from the Link State Database */ + ekey.family = AF_INET; + IPV4_ADDR_COPY(&ekey.k.addr, &ip_addr); + edge = ls_find_edge_by_key(OspfMplsTE.ted, ekey); + if (!edge) { + vty_out(vty, "No edge found for ID %pI4\n", + &ip_addr); + return CMD_WARNING; + } + } else + edge = NULL; + + if (edge) + ls_show_edge(edge, vty, json, verbose); + else + ls_show_edges(OspfMplsTE.ted, vty, json, verbose); + + } else if (argv_find(argv, argc, "subnet", &idx)) { + /* Show Subnet */ + if (argv_find(argv, argc, "A.B.C.D/M", &idx)) { + if (!str2prefix(argv[idx]->arg, &pref)) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Subnet from the Link State Database */ + subnet = ls_find_subnet(OspfMplsTE.ted, &pref); + if (!subnet) { + vty_out(vty, "No subnet found for ID %pFX\n", + &pref); + return CMD_WARNING; + } + } else + subnet = NULL; + + if (subnet) + ls_show_subnet(subnet, vty, json, verbose); + else + ls_show_subnets(OspfMplsTE.ted, vty, json, verbose); + + } else { + /* Show the complete TED */ + ls_show_ted(OspfMplsTE.ted, vty, json, verbose); + } + + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +static void ospf_mpls_te_register_vty(void) +{ + install_element(VIEW_NODE, &show_ip_ospf_mpls_te_router_cmd); + install_element(VIEW_NODE, &show_ip_ospf_mpls_te_link_cmd); + install_element(VIEW_NODE, &show_ip_ospf_mpls_te_db_cmd); + + install_element(OSPF_NODE, &ospf_mpls_te_on_cmd); + install_element(OSPF_NODE, &no_ospf_mpls_te_cmd); + install_element(OSPF_NODE, &ospf_mpls_te_router_addr_cmd); + install_element(OSPF_NODE, &ospf_mpls_te_inter_as_cmd); + install_element(OSPF_NODE, &ospf_mpls_te_inter_as_area_cmd); + install_element(OSPF_NODE, &no_ospf_mpls_te_inter_as_cmd); + install_element(OSPF_NODE, &ospf_mpls_te_export_cmd); + install_element(OSPF_NODE, &no_ospf_mpls_te_export_cmd); + + return; +} diff --git a/ospfd/ospf_te.h b/ospfd/ospf_te.h new file mode 100644 index 0000000..49906ef --- /dev/null +++ b/ospfd/ospf_te.h @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of RFC3630, RFC5392 & RFC6827 + * Copyright (C) 2001 KDD R&D Laboratories, Inc. + * http://www.kddlabs.co.jp/ + * + * Copyright (C) 2012 Orange Labs + * http://www.orange.com + */ + +/* Add support of RFC7471 */ +/* Add support of RFC5392 */ +/* Add support of RFC6827 (partial) */ + +#ifndef _ZEBRA_OSPF_MPLS_TE_H +#define _ZEBRA_OSPF_MPLS_TE_H + +/* + * Opaque LSA's link state ID for Traffic Engineering is + * structured as follows. + * + * 24 16 8 0 + * +--------+--------+--------+--------+ + * | 1 | MBZ |........|........| + * +--------+--------+--------+--------+ + * |<-Type->||<-- Instance --->| + * + * + * Type: IANA has assigned '1' for Traffic Engineering. + * MBZ: Reserved, must be set to zero. + * Instance: User may select an arbitrary 16-bit value. + * + */ + +#define MAX_LEGAL_TE_INSTANCE_NUM (0xffff) +#define LEGAL_TE_INSTANCE_RANGE(i) (0 <= (i) && (i) <= 0xffff) + +/* + * 24 16 8 0 + * +--------+--------+--------+--------+ --- + * | LS age |Options | 10 | A + * +--------+--------+--------+--------+ | + * | 1 | 0 | Instance | | + * +--------+--------+--------+--------+ | + * | Advertising router | | Standard (Opaque) LSA header; + * +--------+--------+--------+--------+ | Only type-10 is used. + * | LS sequence number | | + * +--------+--------+--------+--------+ | + * | LS checksum | Length | V + * +--------+--------+--------+--------+ --- + * | Type | Length | A + * +--------+--------+--------+--------+ | TLV part for TE; Values might be + * | Values ... | V structured as a set of sub-TLVs. + * +--------+--------+--------+--------+ --- + */ + +/* Following define the type of TE link regarding the various RFC */ +#define STD_TE 0x01 +#define GMPLS 0x02 +#define INTER_AS 0x04 +#define PSEUDO_TE 0x08 +#define EMULATED 0x10 + +#define IS_STD_TE(x) (x & STD_TE) +#define IS_PSEUDO_TE(x) (x & PSEUDO_TE) +#define IS_INTER_AS(x) (x & INTER_AS) +#define IS_EMULATED(x) (x & EMULATED) + +/* Flags to manage TE Link LSA */ +#define LPFLG_LSA_INACTIVE 0x00 +#define LPFLG_LSA_ACTIVE 0x01 +#define LPFLG_LSA_ENGAGED 0x02 +#define LPFLG_LOOKUP_DONE 0x04 +#define LPFLG_LSA_FORCED_REFRESH 0x08 +#define LPFLG_LSA_FLOOD_AS 0x10 + +#define IS_FLOOD_AS(x) (x & LPFLG_LSA_FLOOD_AS) + +/* Macro to log debug message */ +#define ote_debug(...) \ + do { \ + if (IS_DEBUG_OSPF_TE) \ + zlog_debug(__VA_ARGS__); \ + } while (0) + +/* + * Following section defines TLV body parts. + */ + +/* Router Address TLV */ /* Mandatory */ +#define TE_TLV_ROUTER_ADDR 1 +struct te_tlv_router_addr { + struct tlv_header header; /* Value length is 4 octets. */ + struct in_addr value; +}; + +/* Link TLV */ +#define TE_TLV_LINK 2 +struct te_tlv_link { + struct tlv_header header; + /* A set of link-sub-TLVs will follow. */ +}; + +/* Default TE TLV size */ +#define TE_LINK_SUBTLV_DEF_SIZE 4 + +/* Link Type Sub-TLV */ /* Mandatory */ +#define TE_LINK_SUBTLV_LINK_TYPE 1 +#define TE_LINK_SUBTLV_TYPE_SIZE 1 +struct te_link_subtlv_link_type { + struct tlv_header header; /* Value length is 1 octet. */ + struct { +#define LINK_TYPE_SUBTLV_VALUE_PTP 1 +#define LINK_TYPE_SUBTLV_VALUE_MA 2 + uint8_t value; + uint8_t padding[3]; + } link_type; +}; + +/* Link Sub-TLV: Link ID */ /* Mandatory */ +#define TE_LINK_SUBTLV_LINK_ID 2 +struct te_link_subtlv_link_id { + struct tlv_header header; /* Value length is 4 octets. */ + struct in_addr value; /* Same as router-lsa's link-id. */ +}; + +/* Link Sub-TLV: Local Interface IP Address */ /* Optional */ +#define TE_LINK_SUBTLV_LCLIF_IPADDR 3 +struct te_link_subtlv_lclif_ipaddr { + struct tlv_header header; /* Value length is 4 x N octets. */ + struct in_addr value[1]; /* Local IP address(es). */ +}; + +/* Link Sub-TLV: Remote Interface IP Address */ /* Optional */ +#define TE_LINK_SUBTLV_RMTIF_IPADDR 4 +struct te_link_subtlv_rmtif_ipaddr { + struct tlv_header header; /* Value length is 4 x N octets. */ + struct in_addr value[1]; /* Neighbor's IP address(es). */ +}; + +/* Link Sub-TLV: Traffic Engineering Metric */ /* Optional */ +#define TE_LINK_SUBTLV_TE_METRIC 5 +struct te_link_subtlv_te_metric { + struct tlv_header header; /* Value length is 4 octets. */ + uint32_t value; /* Link metric for TE purpose. */ +}; + +/* Link Sub-TLV: Maximum Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_MAX_BW 6 +struct te_link_subtlv_max_bw { + struct tlv_header header; /* Value length is 4 octets. */ + float value; /* bytes/sec */ +}; + +/* Link Sub-TLV: Maximum Reservable Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_MAX_RSV_BW 7 +struct te_link_subtlv_max_rsv_bw { + struct tlv_header header; /* Value length is 4 octets. */ + float value; /* bytes/sec */ +}; + +/* Link Sub-TLV: Unreserved Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_UNRSV_BW 8 +#define TE_LINK_SUBTLV_UNRSV_SIZE 32 +struct te_link_subtlv_unrsv_bw { + struct tlv_header header; /* Value length is 32 octets. */ + float value[MAX_CLASS_TYPE]; /* One for each priority level. */ +}; + +/* Link Sub-TLV: Resource Class/Color */ /* Optional */ +#define TE_LINK_SUBTLV_RSC_CLSCLR 9 +struct te_link_subtlv_rsc_clsclr { + struct tlv_header header; /* Value length is 4 octets. */ + uint32_t value; /* Admin. group membership. */ +}; + +/* For RFC6827 */ +/* Local and Remote TE Router ID */ +#define TE_LINK_SUBTLV_LRRID 10 +#define TE_LINK_SUBTLV_LRRID_SIZE 8 +struct te_link_subtlv_lrrid { + struct tlv_header header; /* Value length is 8 octets. */ + struct in_addr local; /* Local TE Router Identifier */ + struct in_addr remote; /* Remote TE Router Identifier */ +}; + +/* RFC4203: Link Local/Remote Identifiers */ +#define TE_LINK_SUBTLV_LLRI 11 +#define TE_LINK_SUBTLV_LLRI_SIZE 8 +struct te_link_subtlv_llri { + struct tlv_header header; /* Value length is 8 octets. */ + uint32_t local; /* Link Local Identifier */ + uint32_t remote; /* Link Remote Identifier */ +}; + +/* Inter-RA Export Upward sub-TLV (12) and Inter-RA Export Downward sub-TLV (13) + * (RFC6827bis) are not yet supported */ +/* SUBTLV 14-16 (RFC4203) are not yet supported */ +/* Bandwidth Constraints sub-TLV (17) (RFC4124) is not yet supported */ +/* SUBLV 18-20 are for OSPFv3 TE (RFC5329). see ospf6d */ + +/* For RFC 5392 */ +/* Remote AS Number sub-TLV */ +#define TE_LINK_SUBTLV_RAS 21 +struct te_link_subtlv_ras { + struct tlv_header header; /* Value length is 4 octets. */ + uint32_t value; /* Remote AS number */ +}; + +/* IPv4 Remote ASBR ID Sub-TLV */ +#define TE_LINK_SUBTLV_RIP 22 +struct te_link_subtlv_rip { + struct tlv_header header; /* Value length is 4 octets. */ + struct in_addr value; /* Remote ASBR IP address */ +}; + +/* SUBTLV 24 is IPv6 Remote ASBR ID (RFC5392). see ospf6d */ + +/* SUBTLV 23 (RFC5330) and 25 (RFC6001) are not yet supported */ + +/* SUBTLV 26 (RFC7308) is not yet supported */ + +/* RFC7471 */ +/* Link Sub-TLV: Average Link Delay */ /* Optional */ +#define TE_LINK_SUBTLV_AV_DELAY 27 +struct te_link_subtlv_av_delay { + struct tlv_header header; /* Value length is 4 bytes. */ + /* + * delay in micro-seconds only 24 bits => 0 ... 16777215 + * with Anomalous Bit as Upper most bit + */ + uint32_t value; +}; + +/* Link Sub-TLV: Low/High Link Delay */ +#define TE_LINK_SUBTLV_MM_DELAY 28 +#define TE_LINK_SUBTLV_MM_DELAY_SIZE 8 +struct te_link_subtlv_mm_delay { + struct tlv_header header; /* Value length is 8 bytes. */ + /* + * low delay in micro-seconds only 24 bits => 0 ... 16777215 + * with Anomalous Bit (A) as Upper most bit + */ + uint32_t low; + /* high delay in micro-seconds only 24 bits => 0 ... 16777215 */ + uint32_t high; +}; + +/* Link Sub-TLV: Link Delay Variation i.e. Jitter */ +#define TE_LINK_SUBTLV_DELAY_VAR 29 +struct te_link_subtlv_delay_var { + struct tlv_header header; /* Value length is 4 bytes. */ + /* interval in micro-seconds only 24 bits => 0 ... 16777215 */ + uint32_t value; +}; + +/* Link Sub-TLV: Routine Unidirectional Link Packet Loss */ +#define TE_LINK_SUBTLV_PKT_LOSS 30 +struct te_link_subtlv_pkt_loss { + struct tlv_header header; /* Value length is 4 bytes. */ + /* + * in percentage of total traffic only 24 bits (2^24 - 2) + * with Anomalous Bit as Upper most bit + */ + uint32_t value; +}; + +/* Link Sub-TLV: Unidirectional Residual Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_RES_BW 31 +struct te_link_subtlv_res_bw { + struct tlv_header header; /* Value length is 4 bytes. */ + /* bandwidth in IEEE floating point format with units in bytes/second */ + float value; +}; + +/* Link Sub-TLV: Unidirectional Available Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_AVA_BW 32 +struct te_link_subtlv_ava_bw { + struct tlv_header header; /* Value length is 4 octets. */ + /* bandwidth in IEEE floating point format with units in bytes/second */ + float value; +}; + +/* Link Sub-TLV: Unidirectional Utilized Bandwidth */ /* Optional */ +#define TE_LINK_SUBTLV_USE_BW 33 +struct te_link_subtlv_use_bw { + struct tlv_header header; /* Value length is 4 octets. */ + /* bandwidth in IEEE floating point format with units in bytes/second */ + float value; +}; + +#define TE_LINK_SUBTLV_MAX 34 /* Last SUBTLV + 1 */ + +/* Here are "non-official" architectural constants. */ +#define MPLS_TE_MINIMUM_BANDWIDTH 1.0 /* Reasonable? *//* XXX */ + +/* Mode for Inter-AS Opaque-LSA */ +enum inter_as_mode { Off, AS, Area }; + +struct te_link_subtlv { + struct tlv_header header; + union { + uint32_t link_type; + struct in_addr link_id; + struct in_addr lclif; + struct in_addr rmtif; + uint32_t te_metric; + float max_bw; + float max_rsv_bw; + float unrsv[8]; + uint32_t rsc_clsclr; + uint32_t llri[2]; + uint32_t ras; + struct in_addr rip; + struct in_addr lrrid[2]; + uint32_t av_delay; + uint32_t mm_delay; + uint32_t delay_var; + uint32_t pkt_loss; + float res_bw; + float ava_bw; + float use_bw; + } value; +}; + +/* Following structure are internal use only. */ +struct ospf_mpls_te { + /* Status of MPLS-TE: enable or disable */ + bool enabled; + + /* Traffic Engineering Database i.e. Link State */ + struct ls_ted *ted; + bool export; + + /* RFC5392 */ + enum inter_as_mode inter_as; + struct in_addr interas_areaid; + + /* List elements are zebra-interfaces (ifp), not ospf-interfaces (oi). + */ + struct list *iflist; + + /* Store Router-TLV in network byte order. */ + struct te_tlv_router_addr router_addr; +}; + +struct mpls_te_link { + /* + * According to MPLS-TE (draft) specification, 24-bit Opaque-ID field + * is subdivided into 8-bit "unused" field and 16-bit "instance" field. + * In this implementation, each Link-TLV has its own instance. + */ + uint32_t instance; + + /* Reference pointer to a Zebra-interface. */ + struct interface *ifp; + + /* Area info in which this MPLS-TE link belongs to. */ + struct ospf_area *area; + + /* Flags to manage this link parameters. */ + uint32_t flags; + + /* Type of MPLS-TE link: RFC3630, RFC5392, RFC5392 emulated, RFC6827 */ + uint8_t type; + + /* Store Link-TLV in network byte order. */ + /* RFC3630 & RFC6827 / RFC 6827 */ + struct te_tlv_link link_header; + struct te_link_subtlv_link_type link_type; + struct te_link_subtlv_link_id link_id; + struct te_link_subtlv_lclif_ipaddr lclif_ipaddr; + struct te_link_subtlv_rmtif_ipaddr rmtif_ipaddr; + struct te_link_subtlv_te_metric te_metric; + struct te_link_subtlv_max_bw max_bw; + struct te_link_subtlv_max_rsv_bw max_rsv_bw; + struct te_link_subtlv_unrsv_bw unrsv_bw; + struct te_link_subtlv_rsc_clsclr rsc_clsclr; + /* RFC4203 */ + struct te_link_subtlv_llri llri; + /* RFC5392 */ + struct te_link_subtlv_ras ras; + struct te_link_subtlv_rip rip; + /* RFC6827 */ + struct te_link_subtlv_lrrid lrrid; + /* RFC7471 */ + struct te_link_subtlv_av_delay av_delay; + struct te_link_subtlv_mm_delay mm_delay; + struct te_link_subtlv_delay_var delay_var; + struct te_link_subtlv_pkt_loss pkt_loss; + struct te_link_subtlv_res_bw res_bw; + struct te_link_subtlv_ava_bw ava_bw; + struct te_link_subtlv_use_bw use_bw; + + struct in_addr adv_router; + struct in_addr id; +}; + +/* Prototypes. */ +extern int ospf_mpls_te_init(void); +extern void ospf_mpls_te_term(void); +extern void ospf_mpls_te_finish(void); +extern struct ospf_mpls_te *get_ospf_mpls_te(void); +extern void ospf_mpls_te_update_if(struct interface *); +extern void ospf_mpls_te_lsa_schedule(struct mpls_te_link *, enum lsa_opcode); +extern void set_linkparams_llri(struct mpls_te_link *, uint32_t, uint32_t); +extern void set_linkparams_lrrid(struct mpls_te_link *, struct in_addr, + struct in_addr); + +struct zapi_opaque_reg_info; +/** + * Call when a client send a Link State Sync message. In turn, OSPF will send + * the contain of the Link State Data base. + * + * @param info ZAPI Opaque message information + * + * @return 0 on success, -1 otherwise + */ +extern int ospf_te_sync_ted(struct zapi_opaque_reg_info dst); + +#endif /* _ZEBRA_OSPF_MPLS_TE_H */ diff --git a/ospfd/ospf_ti_lfa.c b/ospfd/ospf_ti_lfa.c new file mode 100644 index 0000000..d8a2613 --- /dev/null +++ b/ospfd/ospf_ti_lfa.c @@ -0,0 +1,1108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF TI-LFA + * Copyright (C) 2020 NetDEF, Inc. + * Sascha Kattelmann + */ + +#include + +#include "prefix.h" +#include "table.h" +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ti_lfa.h" +#include "ospfd/ospf_dump.h" + + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func); +DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item, + q_spaces_compare_func); + +static void +ospf_ti_lfa_generate_p_space(struct ospf_area *area, struct vertex *child, + struct protected_resource *protected_resource, + bool recursive, struct list *pc_path); + +void ospf_print_protected_resource( + struct protected_resource *protected_resource, char *buf) +{ + struct router_lsa_link *link; + + switch (protected_resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + link = protected_resource->link; + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "protected link: %pI4 %pI4", &link->link_id, + &link->link_data); + break; + case OSPF_TI_LFA_NODE_PROTECTION: + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "protected node: %pI4", + &protected_resource->router_id); + break; + case OSPF_TI_LFA_UNDEFINED_PROTECTION: + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "undefined protected resource"); + break; + } +} + +static enum ospf_ti_lfa_p_q_space_adjacency +ospf_ti_lfa_find_p_node(struct vertex *pc_node, struct p_space *p_space, + struct q_space *q_space) +{ + struct listnode *curr_node; + struct vertex *p_node = NULL, *pc_node_parent, *p_node_pc_parent; + struct vertex_parent *pc_vertex_parent; + + curr_node = listnode_lookup(q_space->pc_path, pc_node); + assert(curr_node); + pc_node_parent = listgetdata(curr_node->next); + + q_space->p_node_info->type = OSPF_TI_LFA_UNDEFINED_NODE; + + p_node = ospf_spf_vertex_find(pc_node_parent->id, p_space->vertex_list); + + if (p_node) { + q_space->p_node_info->node = p_node; + q_space->p_node_info->type = OSPF_TI_LFA_P_NODE; + + if (curr_node->next->next) { + p_node_pc_parent = listgetdata(curr_node->next->next); + pc_vertex_parent = ospf_spf_vertex_parent_find( + p_node_pc_parent->id, pc_node_parent); + q_space->p_node_info->nexthop = + pc_vertex_parent->nexthop->router; + } else { + /* + * It can happen that the P node is the root node itself + * (hence there can be no parents). In this case we + * don't need to set a nexthop. + */ + q_space->p_node_info->nexthop.s_addr = INADDR_ANY; + } + + return OSPF_TI_LFA_P_Q_SPACE_ADJACENT; + } + + ospf_ti_lfa_find_p_node(pc_node_parent, p_space, q_space); + return OSPF_TI_LFA_P_Q_SPACE_NON_ADJACENT; +} + +static void ospf_ti_lfa_find_q_node(struct vertex *pc_node, + struct p_space *p_space, + struct q_space *q_space) +{ + struct listnode *curr_node, *next_node; + struct vertex *p_node, *q_node, *q_space_parent = NULL, *pc_node_parent; + struct vertex_parent *pc_vertex_parent; + + curr_node = listnode_lookup(q_space->pc_path, pc_node); + assert(curr_node); + next_node = curr_node->next; + pc_node_parent = listgetdata(next_node); + pc_vertex_parent = + ospf_spf_vertex_parent_find(pc_node_parent->id, pc_node); + + p_node = ospf_spf_vertex_find(pc_node->id, p_space->vertex_list); + q_node = ospf_spf_vertex_find(pc_node->id, q_space->vertex_list); + + /* The Q node is always present. */ + assert(q_node); + + q_space->q_node_info->type = OSPF_TI_LFA_UNDEFINED_NODE; + + if (p_node && q_node) { + q_space->q_node_info->node = pc_node; + q_space->q_node_info->type = OSPF_TI_LFA_PQ_NODE; + q_space->q_node_info->nexthop = + pc_vertex_parent->nexthop->router; + return; + } + + /* + * Note that the Q space has the 'reverse' direction of the PC + * SPF. Hence compare PC SPF parent to Q space children. + */ + q_space_parent = + ospf_spf_vertex_find(pc_node_parent->id, q_node->children); + + /* + * If the Q space parent doesn't exist we 'hit' the border to the P + * space and hence got our Q node. + */ + if (!q_space_parent) { + q_space->q_node_info->node = pc_node; + q_space->q_node_info->type = OSPF_TI_LFA_Q_NODE; + q_space->q_node_info->nexthop = + pc_vertex_parent->nexthop->router; + return; + } + + return ospf_ti_lfa_find_q_node(pc_node_parent, p_space, q_space); +} + +static void ospf_ti_lfa_append_label_stack(struct mpls_label_stack *label_stack, + mpls_label_t labels[], + uint32_t num_labels) +{ + int i, offset, limit; + + limit = label_stack->num_labels + num_labels; + offset = label_stack->num_labels; + + for (i = label_stack->num_labels; i < limit; i++) { + label_stack->label[i] = labels[i - offset]; + label_stack->num_labels++; + } +} + + +static struct mpls_label_stack * +ospf_ti_lfa_create_label_stack(mpls_label_t labels[], uint32_t num_labels) +{ + struct mpls_label_stack *label_stack; + uint32_t i; + + /* Sanity check */ + for (i = 0; i < num_labels; i++) { + if (labels[i] == MPLS_INVALID_LABEL) + return NULL; + } + + label_stack = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct mpls_label_stack) + + MPLS_MAX_LABELS * sizeof(mpls_label_t)); + label_stack->num_labels = num_labels; + + for (i = 0; i < num_labels; i++) + label_stack->label[i] = labels[i]; + + return label_stack; +} + +static struct list * +ospf_ti_lfa_map_path_to_pc_vertices(struct list *path, + struct list *pc_vertex_list) +{ + struct listnode *node; + struct vertex *vertex, *pc_vertex; + struct list *pc_path; + + pc_path = list_new(); + + for (ALL_LIST_ELEMENTS_RO(path, node, vertex)) { + pc_vertex = ospf_spf_vertex_find(vertex->id, pc_vertex_list); + listnode_add(pc_path, pc_vertex); + } + + return pc_path; +} + +static struct list *ospf_ti_lfa_cut_out_pc_path(struct list *pc_vertex_list, + struct list *pc_path, + struct vertex *p_node, + struct vertex *q_node) +{ + struct list *inner_pc_path; + struct vertex *current_vertex; + struct listnode *current_listnode; + + inner_pc_path = list_new(); + current_vertex = ospf_spf_vertex_find(q_node->id, pc_vertex_list); + current_listnode = listnode_lookup(pc_path, current_vertex); + + /* Note that the post-convergence paths are reversed. */ + while (current_listnode) { + current_vertex = listgetdata(current_listnode); + listnode_add(inner_pc_path, current_vertex); + + if (current_vertex->id.s_addr == p_node->id.s_addr) + break; + + current_listnode = current_listnode->next; + } + + return inner_pc_path; +} + +static void ospf_ti_lfa_generate_inner_label_stack( + struct ospf_area *area, struct p_space *p_space, + struct q_space *q_space, + struct ospf_ti_lfa_inner_backup_path_info *inner_backup_path_info) +{ + struct route_table *new_table; + struct vertex *q_node; + struct vertex *start_vertex, *end_vertex; + struct vertex_parent *vertex_parent; + struct listnode *pc_p_node, *pc_q_node; + struct vertex *spf_orig; + struct list *vertex_list_orig; + struct p_spaces_head *p_spaces_orig; + struct p_space *inner_p_space; + struct q_space *inner_q_space; + struct ospf_ti_lfa_node_info *p_node_info, *q_node_info; + struct protected_resource *protected_resource; + struct list *inner_pc_path; + mpls_label_t start_label, end_label; + + p_node_info = q_space->p_node_info; + q_node_info = q_space->q_node_info; + protected_resource = p_space->protected_resource; + + start_vertex = p_node_info->node; + end_vertex = q_node_info->node; + + /* + * It can happen that the P node and/or the Q node are the root or + * the destination, therefore we need to force one step forward (resp. + * backward) using an Adjacency-SID. + */ + start_label = MPLS_INVALID_LABEL; + end_label = MPLS_INVALID_LABEL; + if (p_node_info->node->id.s_addr == p_space->root->id.s_addr) { + pc_p_node = listnode_lookup(q_space->pc_path, p_space->pc_spf); + assert(pc_p_node); + start_vertex = listgetdata(pc_p_node->prev); + start_label = ospf_sr_get_adj_sid_by_id(&p_node_info->node->id, + &start_vertex->id); + } + if (q_node_info->node->id.s_addr == q_space->root->id.s_addr) { + pc_q_node = listnode_lookup(q_space->pc_path, + listnode_head(q_space->pc_path)); + assert(pc_q_node); + end_vertex = listgetdata(pc_q_node->next); + end_label = ospf_sr_get_adj_sid_by_id(&end_vertex->id, + &q_node_info->node->id); + } + + /* Corner case: inner path is just one node */ + if (start_vertex->id.s_addr == end_vertex->id.s_addr) { + inner_backup_path_info->label_stack = + ospf_ti_lfa_create_label_stack(&start_label, 1); + inner_backup_path_info->q_node_info.node = end_vertex; + inner_backup_path_info->q_node_info.type = OSPF_TI_LFA_PQ_NODE; + inner_backup_path_info->p_node_info.type = + OSPF_TI_LFA_UNDEFINED_NODE; + vertex_parent = ospf_spf_vertex_parent_find(p_space->root->id, + end_vertex); + inner_backup_path_info->p_node_info.nexthop = + vertex_parent->nexthop->router; + return; + } + + inner_pc_path = ospf_ti_lfa_cut_out_pc_path(p_space->pc_vertex_list, + q_space->pc_path, + start_vertex, end_vertex); + + new_table = route_table_init(); + + /* Copy the current state ... */ + spf_orig = area->spf; + vertex_list_orig = area->spf_vertex_list; + p_spaces_orig = area->p_spaces; + + area->p_spaces = + XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_spaces_head)); + + /* dry run true, root node false */ + ospf_spf_calculate(area, start_vertex->lsa_p, new_table, NULL, NULL, + true, false); + + q_node = ospf_spf_vertex_find(end_vertex->id, area->spf_vertex_list); + + ospf_ti_lfa_generate_p_space(area, q_node, protected_resource, false, + inner_pc_path); + + /* There's just one P and Q space */ + inner_p_space = p_spaces_pop(area->p_spaces); + inner_q_space = q_spaces_pop(inner_p_space->q_spaces); + + /* Copy over inner backup path information from the inner q_space */ + + /* In case the outer P node is also the root of the P space */ + if (start_label != MPLS_INVALID_LABEL) { + inner_backup_path_info->label_stack = + ospf_ti_lfa_create_label_stack(&start_label, 1); + ospf_ti_lfa_append_label_stack( + inner_backup_path_info->label_stack, + inner_q_space->label_stack->label, + inner_q_space->label_stack->num_labels); + inner_backup_path_info->p_node_info.node = start_vertex; + inner_backup_path_info->p_node_info.type = OSPF_TI_LFA_P_NODE; + vertex_parent = ospf_spf_vertex_parent_find(p_space->root->id, + start_vertex); + inner_backup_path_info->p_node_info.nexthop = + vertex_parent->nexthop->router; + } else { + memcpy(inner_backup_path_info->label_stack, + inner_q_space->label_stack, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) + * inner_q_space->label_stack + ->num_labels); + memcpy(&inner_backup_path_info->p_node_info, + inner_q_space->p_node_info, + sizeof(struct ospf_ti_lfa_node_info)); + } + + /* In case the outer Q node is also the root of the Q space */ + if (end_label != MPLS_INVALID_LABEL) { + inner_backup_path_info->q_node_info.node = end_vertex; + inner_backup_path_info->q_node_info.type = OSPF_TI_LFA_Q_NODE; + } else { + memcpy(&inner_backup_path_info->q_node_info, + inner_q_space->q_node_info, + sizeof(struct ospf_ti_lfa_node_info)); + } + + /* Cleanup */ + ospf_ti_lfa_free_p_spaces(area); + ospf_spf_cleanup(area->spf, area->spf_vertex_list); + + /* ... and copy the current state back. */ + area->spf = spf_orig; + area->spf_vertex_list = vertex_list_orig; + area->p_spaces = p_spaces_orig; +} + +static void ospf_ti_lfa_generate_label_stack(struct ospf_area *area, + struct p_space *p_space, + struct q_space *q_space) +{ + enum ospf_ti_lfa_p_q_space_adjacency adjacency_result; + mpls_label_t labels[MPLS_MAX_LABELS]; + struct vertex *pc_node; + struct ospf_ti_lfa_inner_backup_path_info inner_backup_path_info; + + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: Generating Label stack for src %pI4 and dest %pI4.", + __func__, &p_space->root->id, &q_space->root->id); + + pc_node = listnode_head(q_space->pc_path); + + if (!pc_node) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: There seems to be no post convergence path (yet).", + __func__); + return; + } + + ospf_ti_lfa_find_q_node(pc_node, p_space, q_space); + if (q_space->q_node_info->type == OSPF_TI_LFA_UNDEFINED_NODE) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug("%s: Q node not found!", __func__); + return; + } + + /* Found a PQ node? Then we are done here. */ + if (q_space->q_node_info->type == OSPF_TI_LFA_PQ_NODE) { + /* + * If the PQ node is a child of the root, then we can use an + * adjacency SID instead of a prefix SID for the backup path. + */ + if (ospf_spf_vertex_parent_find(p_space->root->id, + q_space->q_node_info->node)) + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->q_node_info->node->id); + else + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->q_node_info->node->id); + + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + q_space->nexthop = q_space->q_node_info->nexthop; + + return; + } + + /* Otherwise find a (hopefully adjacent) P node. */ + pc_node = ospf_spf_vertex_find(q_space->q_node_info->node->id, + p_space->pc_vertex_list); + adjacency_result = ospf_ti_lfa_find_p_node(pc_node, p_space, q_space); + + if (q_space->p_node_info->type == OSPF_TI_LFA_UNDEFINED_NODE) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug("%s: P node not found!", __func__); + return; + } + + /* + * This should be the regular case: P and Q space are adjacent or even + * overlapping. This is guaranteed for link protection when used with + * symmetric weights. + */ + if (adjacency_result == OSPF_TI_LFA_P_Q_SPACE_ADJACENT) { + /* + * It can happen that the P node is the root itself, therefore + * we don't need a label for it. So just one adjacency SID for + * the Q node. + */ + if (q_space->p_node_info->node->id.s_addr + == p_space->root->id.s_addr) { + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->q_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + q_space->nexthop = q_space->q_node_info->nexthop; + return; + } + + /* + * Otherwise we have a P and also a Q node (which are adjacent). + * + * It can happen that the P node is a child of the root, + * therefore we might just need the adjacency SID for the P node + * instead of the prefix SID. For the Q node always take the + * adjacency SID. + */ + if (ospf_spf_vertex_parent_find(p_space->root->id, + q_space->p_node_info->node)) + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->p_node_info->node->id); + else + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->p_node_info->node->id); + + labels[1] = ospf_sr_get_adj_sid_by_id( + &q_space->p_node_info->node->id, + &q_space->q_node_info->node->id); + + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 2); + q_space->nexthop = q_space->p_node_info->nexthop; + + } else { + /* + * It can happen that the P and Q space are not adjacent when + * e.g. node protection or asymmetric weights are used. In this + * case the found P and Q nodes are used as a reference for + * another run of the algorithm! + * + * After having found the inner label stack it is stitched + * together with the outer labels. + */ + inner_backup_path_info.label_stack = XCALLOC( + MTYPE_OSPF_PATH, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) * MPLS_MAX_LABELS); + ospf_ti_lfa_generate_inner_label_stack(area, p_space, q_space, + &inner_backup_path_info); + + /* + * First stitch together the outer P node label with the inner + * label stack. + */ + if (q_space->p_node_info->node->id.s_addr + == p_space->root->id.s_addr) { + /* + * It can happen that the P node is the root itself, + * therefore we don't need a label for it. Just take + * the inner label stack first. + */ + q_space->label_stack = ospf_ti_lfa_create_label_stack( + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + + /* Use the inner P or Q node for the nexthop */ + if (inner_backup_path_info.p_node_info.type + != OSPF_TI_LFA_UNDEFINED_NODE) + q_space->nexthop = inner_backup_path_info + .p_node_info.nexthop; + else + q_space->nexthop = inner_backup_path_info + .q_node_info.nexthop; + + } else if (ospf_spf_vertex_parent_find( + p_space->root->id, + q_space->p_node_info->node)) { + /* + * It can happen that the outer P node is a child of + * the root, therefore we might just need the + * adjacency SID for the outer P node instead of the + * prefix SID. Then just append the inner label stack. + */ + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->p_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + ospf_ti_lfa_append_label_stack( + q_space->label_stack, + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + q_space->nexthop = q_space->p_node_info->nexthop; + } else { + /* The outer P node needs a Prefix-SID here */ + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->p_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + ospf_ti_lfa_append_label_stack( + q_space->label_stack, + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + q_space->nexthop = q_space->p_node_info->nexthop; + } + + /* Now the outer Q node needs to be considered */ + if (ospf_spf_vertex_parent_find( + inner_backup_path_info.q_node_info.node->id, + q_space->q_node_info->node)) { + /* + * The outer Q node can be a child of the inner Q node, + * hence just add an Adjacency-SID. + */ + labels[0] = ospf_sr_get_adj_sid_by_id( + &inner_backup_path_info.q_node_info.node->id, + &q_space->q_node_info->node->id); + ospf_ti_lfa_append_label_stack(q_space->label_stack, + labels, 1); + } else { + /* Otherwise a Prefix-SID is needed */ + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->q_node_info->node->id); + ospf_ti_lfa_append_label_stack(q_space->label_stack, + labels, 1); + } + /* + * Note that there's also the case where the inner and outer Q + * node are the same, but then there's nothing to do! + */ + } +} + +static struct list * +ospf_ti_lfa_generate_post_convergence_path(struct list *pc_vertex_list, + struct vertex *dest) +{ + struct list *pc_path; + struct vertex *current_vertex; + struct vertex_parent *parent; + + current_vertex = ospf_spf_vertex_find(dest->id, pc_vertex_list); + if (!current_vertex) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: There seems to be no post convergence path (yet).", + __func__); + return NULL; + } + + pc_path = list_new(); + listnode_add(pc_path, current_vertex); + + /* Generate a backup path in reverse order */ + for (;;) { + parent = listnode_head(current_vertex->parents); + if (!parent) + break; + + listnode_add(pc_path, parent->parent); + current_vertex = parent->parent; + } + + return pc_path; +} + +static void ospf_ti_lfa_generate_q_spaces(struct ospf_area *area, + struct p_space *p_space, + struct vertex *dest, bool recursive, + struct list *pc_path) +{ + struct listnode *node; + struct vertex *child; + struct route_table *new_table; + struct q_space *q_space, q_space_search; + char label_buf[MPLS_LABEL_STRLEN]; + char res_buf[PROTECTED_RESOURCE_STRLEN]; + bool node_protected; + + ospf_print_protected_resource(p_space->protected_resource, res_buf); + node_protected = + p_space->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION + && dest->id.s_addr + == p_space->protected_resource->router_id.s_addr; + + /* + * If node protection is used, don't build a Q space for the protected + * node of that particular P space. Move on with children instead. + */ + if (node_protected) { + if (recursive) { + /* Recursively generate Q spaces for all children */ + for (ALL_LIST_ELEMENTS_RO(dest->children, node, child)) + ospf_ti_lfa_generate_q_spaces(area, p_space, + child, recursive, + pc_path); + } + return; + } + + /* Check if we already have a Q space for this destination */ + q_space_search.root = dest; + if (q_spaces_find(p_space->q_spaces, &q_space_search)) + return; + + q_space = XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_space)); + q_space->p_node_info = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct ospf_ti_lfa_node_info)); + q_space->q_node_info = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct ospf_ti_lfa_node_info)); + + new_table = route_table_init(); + + /* + * Generate a new (reversed!) SPF tree for this vertex, + * dry run true, root node false + */ + area->spf_reversed = true; + ospf_spf_calculate(area, dest->lsa_p, new_table, NULL, NULL, true, + false); + + /* Reset the flag for reverse SPF */ + area->spf_reversed = false; + + q_space->root = area->spf; + q_space->vertex_list = area->spf_vertex_list; + q_space->label_stack = NULL; + + if (pc_path) + q_space->pc_path = ospf_ti_lfa_map_path_to_pc_vertices( + pc_path, p_space->pc_vertex_list); + else + q_space->pc_path = ospf_ti_lfa_generate_post_convergence_path( + p_space->pc_vertex_list, q_space->root); + + /* If there's no backup path available then we are done here. */ + if (!q_space->pc_path) { + zlog_info( + "%s: NO backup path found for root %pI4 and destination %pI4 for %s, aborting ...", + __func__, &p_space->root->id, &q_space->root->id, + res_buf); + + list_delete(&q_space->vertex_list); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->p_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->q_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space); + + return; + } + + /* 'Cut' the protected resource out of the new SPF tree */ + ospf_spf_remove_resource(q_space->root, q_space->vertex_list, + p_space->protected_resource); + + /* + * Generate the smallest possible label stack from the root of the P + * space to the root of the Q space. + */ + ospf_ti_lfa_generate_label_stack(area, p_space, q_space); + + if (q_space->label_stack) { + mpls_label2str(q_space->label_stack->num_labels, + q_space->label_stack->label, label_buf, + MPLS_LABEL_STRLEN, 0, true); + zlog_info( + "%s: Generated label stack %s for root %pI4 and destination %pI4 for %s", + __func__, label_buf, &p_space->root->id, + &q_space->root->id, res_buf); + } else { + zlog_info( + "%s: NO label stack generated for root %pI4 and destination %pI4 for %s", + __func__, &p_space->root->id, &q_space->root->id, + res_buf); + } + + /* We are finished, store the new Q space in the P space struct */ + q_spaces_add(p_space->q_spaces, q_space); + + /* Recursively generate Q spaces for all children */ + if (recursive) { + for (ALL_LIST_ELEMENTS_RO(dest->children, node, child)) + ospf_ti_lfa_generate_q_spaces(area, p_space, child, + recursive, pc_path); + } +} + +static void ospf_ti_lfa_generate_post_convergence_spf(struct ospf_area *area, + struct p_space *p_space) +{ + struct route_table *new_table; + + new_table = route_table_init(); + + area->spf_protected_resource = p_space->protected_resource; + + /* + * The 'post convergence' SPF tree is generated here + * dry run true, root node false + * + * So how does this work? During the SPF calculation the algorithm + * checks if a link belongs to a protected resource and then just + * ignores it. + * This is actually _NOT_ a good way to calculate the post + * convergence SPF tree. The preferred way would be to delete the + * relevant links (and nodes) from a copy of the LSDB and then just run + * the SPF algorithm on that as usual. + * However, removing links from router LSAs appears to be its own + * endeavour (because LSAs are stored as a 'raw' stream), so we go with + * this rather hacky way for now. + */ + ospf_spf_calculate(area, area->router_lsa_self, new_table, NULL, NULL, + true, false); + + p_space->pc_spf = area->spf; + p_space->pc_vertex_list = area->spf_vertex_list; + + area->spf_protected_resource = NULL; +} + +static void +ospf_ti_lfa_generate_p_space(struct ospf_area *area, struct vertex *child, + struct protected_resource *protected_resource, + bool recursive, struct list *pc_path) +{ + struct vertex *spf_orig; + struct list *vertex_list, *vertex_list_orig; + struct p_space *p_space; + + p_space = XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_space)); + vertex_list = list_new(); + + /* The P-space will get its own SPF tree, so copy the old one */ + ospf_spf_copy(area->spf, vertex_list); + p_space->root = listnode_head(vertex_list); + p_space->vertex_list = vertex_list; + p_space->protected_resource = protected_resource; + + /* Initialize the Q spaces for this P space and protected resource */ + p_space->q_spaces = + XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_spaces_head)); + q_spaces_init(p_space->q_spaces); + + /* 'Cut' the protected resource out of the new SPF tree */ + ospf_spf_remove_resource(p_space->root, p_space->vertex_list, + p_space->protected_resource); + + /* + * Since we are going to calculate more SPF trees for Q spaces, keep the + * 'original' one here temporarily + */ + spf_orig = area->spf; + vertex_list_orig = area->spf_vertex_list; + + /* Generate the post convergence SPF as a blueprint for backup paths */ + ospf_ti_lfa_generate_post_convergence_spf(area, p_space); + + /* Generate the relevant Q spaces for this particular P space */ + ospf_ti_lfa_generate_q_spaces(area, p_space, child, recursive, pc_path); + + /* Put the 'original' SPF tree back in place */ + area->spf = spf_orig; + area->spf_vertex_list = vertex_list_orig; + + /* We are finished, store the new P space */ + p_spaces_add(area->p_spaces, p_space); +} + +void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area, + enum protection_type protection_type) +{ + struct listnode *node, *inner_node; + struct vertex *root, *child; + struct vertex_parent *vertex_parent; + uint8_t *p, *lim; + struct router_lsa_link *l = NULL; + struct prefix stub_prefix, child_prefix; + struct protected_resource *protected_resource; + + area->p_spaces = + XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_spaces_head)); + p_spaces_init(area->p_spaces); + + root = area->spf; + + /* Root or its router LSA was not created yet? */ + if (!root || !root->lsa) + return; + + stub_prefix.family = AF_INET; + child_prefix.family = AF_INET; + child_prefix.prefixlen = IPV4_MAX_BITLEN; + + p = ((uint8_t *)root->lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)root->lsa) + ntohs(root->lsa->length); + + zlog_info("%s: Generating P spaces for area %pI4", __func__, + &area->area_id); + + /* + * Iterate over all stub networks which target other OSPF neighbors. + * Check the nexthop of the child vertex if a stub network is relevant. + */ + while (p < lim) { + l = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* First comes node protection */ + if (protection_type == OSPF_TI_LFA_NODE_PROTECTION) { + if (l->m[0].type == LSA_LINK_TYPE_POINTOPOINT) { + protected_resource = XCALLOC( + MTYPE_OSPF_P_SPACE, + sizeof(struct protected_resource)); + protected_resource->type = protection_type; + protected_resource->router_id = l->link_id; + child = ospf_spf_vertex_find( + protected_resource->router_id, + root->children); + if (child) + ospf_ti_lfa_generate_p_space( + area, child, protected_resource, + true, NULL); + } + + continue; + } + + /* The rest is about link protection */ + if (protection_type != OSPF_TI_LFA_LINK_PROTECTION) + continue; + + if (l->m[0].type != LSA_LINK_TYPE_STUB) + continue; + + stub_prefix.prefixlen = ip_masklen(l->link_data); + stub_prefix.u.prefix4 = l->link_id; + + for (ALL_LIST_ELEMENTS_RO(root->children, node, child)) { + + if (child->type != OSPF_VERTEX_ROUTER) + continue; + + for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node, + vertex_parent)) { + + child_prefix.u.prefix4 = + vertex_parent->nexthop->router; + + /* + * If there's a link for that stub network then + * we will protect it. Hence generate a P space + * for that particular link including the + * Q spaces so we can later on generate a + * backup path for the link. + */ + if (prefix_match(&stub_prefix, &child_prefix)) { + zlog_info( + "%s: Generating P space for %pI4", + __func__, &l->link_id); + + protected_resource = XCALLOC( + MTYPE_OSPF_P_SPACE, + sizeof(struct + protected_resource)); + protected_resource->type = + protection_type; + protected_resource->link = l; + + ospf_ti_lfa_generate_p_space( + area, child, protected_resource, + true, NULL); + } + } + } + } +} + +static struct p_space *ospf_ti_lfa_get_p_space_by_path(struct ospf_area *area, + struct ospf_path *path) +{ + struct p_space *p_space; + struct router_lsa_link *link; + struct vertex *child; + int type; + + frr_each(p_spaces, area->p_spaces, p_space) { + type = p_space->protected_resource->type; + + if (type == OSPF_TI_LFA_LINK_PROTECTION) { + link = p_space->protected_resource->link; + if ((path->nexthop.s_addr & link->link_data.s_addr) + == (link->link_id.s_addr & link->link_data.s_addr)) + return p_space; + } + + if (type == OSPF_TI_LFA_NODE_PROTECTION) { + child = ospf_spf_vertex_by_nexthop(area->spf, + &path->nexthop); + if (child + && p_space->protected_resource->router_id.s_addr + == child->id.s_addr) + return p_space; + } + } + + return NULL; +} + +void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area, + struct route_table *new_table) +{ + struct route_node *rn; + struct ospf_route *or; + struct ospf_path *path; + struct listnode *node; + struct p_space *p_space; + struct q_space *q_space, q_space_search; + struct vertex root_search; + char label_buf[MPLS_LABEL_STRLEN]; + + for (rn = route_top(new_table); rn; rn = route_next(rn)) { + or = rn->info; + if (or == NULL) + continue; + + /* Insert a backup path for all OSPF paths */ + for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) { + + if (path->adv_router.s_addr == INADDR_ANY + || path->nexthop.s_addr == INADDR_ANY) + continue; + + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: attempting to insert backup path for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, &rn->p, &path->adv_router, + &path->nexthop); + + p_space = ospf_ti_lfa_get_p_space_by_path(area, path); + if (!p_space) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: P space not found for router id %pI4 and nexthop %pI4.", + __func__, &path->adv_router, + &path->nexthop); + continue; + } + + root_search.id = path->adv_router; + q_space_search.root = &root_search; + q_space = q_spaces_find(p_space->q_spaces, + &q_space_search); + if (!q_space) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: Q space not found for advertising router %pI4.", + __func__, &path->adv_router); + continue; + } + + /* If there's a backup label stack, insert it*/ + if (q_space->label_stack) { + /* Init the backup path data in path */ + path->srni.backup_label_stack = XCALLOC( + MTYPE_OSPF_PATH, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) + * q_space->label_stack + ->num_labels); + + /* Copy over the label stack */ + path->srni.backup_label_stack->num_labels = + q_space->label_stack->num_labels; + memcpy(path->srni.backup_label_stack->label, + q_space->label_stack->label, + sizeof(mpls_label_t) + * q_space->label_stack + ->num_labels); + + /* Set the backup nexthop too */ + path->srni.backup_nexthop = q_space->nexthop; + } + + if (path->srni.backup_label_stack) { + mpls_label2str( + path->srni.backup_label_stack + ->num_labels, + path->srni.backup_label_stack->label, + label_buf, MPLS_LABEL_STRLEN, 0, true); + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: inserted backup path %s for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, label_buf, &rn->p, + &path->adv_router, + &path->nexthop); + } else { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: inserted NO backup path for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, &rn->p, + &path->adv_router, + &path->nexthop); + } + } + } +} + +void ospf_ti_lfa_free_p_spaces(struct ospf_area *area) +{ + struct p_space *p_space; + struct q_space *q_space; + + while ((p_space = p_spaces_pop(area->p_spaces))) { + while ((q_space = q_spaces_pop(p_space->q_spaces))) { + ospf_spf_cleanup(q_space->root, q_space->vertex_list); + + if (q_space->pc_path) + list_delete(&q_space->pc_path); + + XFREE(MTYPE_OSPF_Q_SPACE, q_space->p_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->q_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->label_stack); + XFREE(MTYPE_OSPF_Q_SPACE, q_space); + } + + ospf_spf_cleanup(p_space->root, p_space->vertex_list); + ospf_spf_cleanup(p_space->pc_spf, p_space->pc_vertex_list); + XFREE(MTYPE_OSPF_P_SPACE, p_space->protected_resource); + + q_spaces_fini(p_space->q_spaces); + XFREE(MTYPE_OSPF_Q_SPACE, p_space->q_spaces); + XFREE(MTYPE_OSPF_P_SPACE, p_space); + } + + p_spaces_fini(area->p_spaces); + XFREE(MTYPE_OSPF_P_SPACE, area->p_spaces); +} + +void ospf_ti_lfa_compute(struct ospf_area *area, struct route_table *new_table, + enum protection_type protection_type) +{ + /* + * Generate P spaces per protected link/node and their respective Q + * spaces, generate backup paths (MPLS label stacks) by finding P/Q + * nodes. + */ + ospf_ti_lfa_generate_p_spaces(area, protection_type); + + /* Insert the generated backup paths into the routing table. */ + ospf_ti_lfa_insert_backup_paths(area, new_table); + + /* Cleanup P spaces and related datastructures including Q spaces. */ + ospf_ti_lfa_free_p_spaces(area); +} diff --git a/ospfd/ospf_ti_lfa.h b/ospfd/ospf_ti_lfa.h new file mode 100644 index 0000000..ea37da6 --- /dev/null +++ b/ospfd/ospf_ti_lfa.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPF calculation. + * Copyright (C) 2020 NetDEF, Inc. + * Sascha Kattelmann + */ + +#ifndef _OSPF_TI_LFA_H +#define _OSPF_TI_LFA_H + +#define PROTECTED_RESOURCE_STRLEN 100 + +extern void ospf_ti_lfa_compute(struct ospf_area *area, + struct route_table *new_table, + enum protection_type protection_type); + +/* unit testing */ +extern void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area, + enum protection_type protection_type); +extern void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area, + struct route_table *new_table); +extern void ospf_ti_lfa_free_p_spaces(struct ospf_area *area); +void ospf_print_protected_resource( + struct protected_resource *protected_resource, char *buf); + +#endif /* _OSPF_TI_LFA_H */ diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c new file mode 100644 index 0000000..7cb5197 --- /dev/null +++ b/ospfd/ospf_vty.c @@ -0,0 +1,13820 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPF VTY interface. + * Copyright (C) 2005 6WIND + * Copyright (C) 2000 Toshiaki Takada + */ + +#include +#include + +#include "printfrr.h" +#include "monotime.h" +#include "memory.h" +#include "frrevent.h" +#include "prefix.h" +#include "table.h" +#include "vty.h" +#include "command.h" +#include "plist.h" +#include "log.h" +#include "zclient.h" +#include +#include "defaults.h" +#include "lib/printfrr.h" +#include "keychain.h" +#include "frrdistance.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_vty.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_ldp_sync.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_memory.h" + +FRR_CFG_DEFAULT_BOOL(OSPF_LOG_ADJACENCY_CHANGES, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); + +static const char *const ospf_network_type_str[] = { + "Null", "POINTOPOINT", "BROADCAST", "NBMA", "POINTOMULTIPOINT", + "VIRTUALLINK", "LOOPBACK"}; + +/* Utility functions. */ +int str2area_id(const char *str, struct in_addr *area_id, int *area_id_fmt) +{ + char *ep; + + area_id->s_addr = htonl(strtoul(str, &ep, 10)); + if (*ep && !inet_aton(str, area_id)) + return -1; + + *area_id_fmt = + *ep ? OSPF_AREA_ID_FMT_DOTTEDQUAD : OSPF_AREA_ID_FMT_DECIMAL; + + return 0; +} + +static void area_id2str(char *buf, int length, struct in_addr *area_id, + int area_id_fmt) +{ + if (area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) + inet_ntop(AF_INET, area_id, buf, length); + else + snprintf(buf, length, "%lu", + (unsigned long)ntohl(area_id->s_addr)); +} + +static int str2metric(const char *str, int *metric) +{ + /* Sanity check. */ + if (str == NULL) + return 0; + + *metric = strtol(str, NULL, 10); + if (*metric < 0 || *metric > 16777214) { + /* vty_out (vty, "OSPF metric value is invalid\n"); */ + return 0; + } + + return 1; +} + +static int str2metric_type(const char *str, int *metric_type) +{ + /* Sanity check. */ + if (str == NULL) + return 0; + + if (strncmp(str, "1", 1) == 0) + *metric_type = EXTERNAL_METRIC_TYPE_1; + else if (strncmp(str, "2", 1) == 0) + *metric_type = EXTERNAL_METRIC_TYPE_2; + else + return 0; + + return 1; +} + +int ospf_oi_count(struct interface *ifp) +{ + struct route_node *rn; + int i = 0; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) + if (rn->info) + i++; + + return i; +} + +#define OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf) \ + if (argv_find(argv, argc, "vrf", &idx_vrf)) { \ + vrf_name = argv[idx_vrf + 1]->arg; \ + all_vrf = strmatch(vrf_name, "all"); \ + } + +static int ospf_router_cmd_parse(struct vty *vty, struct cmd_token *argv[], + const int argc, unsigned short *instance, + const char **vrf_name) +{ + int idx_vrf = 0, idx_inst = 0; + + *instance = 0; + if (argv_find(argv, argc, "(1-65535)", &idx_inst)) { + if (ospf_instance == 0) { + vty_out(vty, + "%% OSPF is not running in instance mode\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + *instance = strtoul(argv[idx_inst]->arg, NULL, 10); + } + + *vrf_name = VRF_DEFAULT_NAME; + if (argv_find(argv, argc, "vrf", &idx_vrf)) { + if (ospf_instance != 0) { + vty_out(vty, + "%% VRF is not supported in instance mode\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + *vrf_name = argv[idx_vrf + 1]->arg; + } + + return CMD_SUCCESS; +} + +static void ospf_show_vrf_name(struct ospf *ospf, struct vty *vty, + json_object *json, uint8_t use_vrf) +{ + if (use_vrf) { + if (json) { + json_object_string_add(json, "vrfName", + ospf_get_name(ospf)); + json_object_int_add(json, "vrfId", ospf->vrf_id); + } else + vty_out(vty, "VRF Name: %s\n", ospf_get_name(ospf)); + } +} + +#include "ospfd/ospf_vty_clippy.c" + +DEFUN_NOSH (router_ospf, + router_ospf_cmd, + "router ospf [{(1-65535)|vrf NAME}]", + "Enable a routing process\n" + "Start OSPF configuration\n" + "Instance ID\n" + VRF_CMD_HELP_STR) +{ + unsigned short instance; + const char *vrf_name; + bool created = false; + struct ospf *ospf; + int ret; + + ret = ospf_router_cmd_parse(vty, argv, argc, &instance, &vrf_name); + if (ret != CMD_SUCCESS) + return ret; + + if (instance != ospf_instance) { + VTY_PUSH_CONTEXT_NULL(OSPF_NODE); + return CMD_NOT_MY_INSTANCE; + } + + ospf = ospf_get(instance, vrf_name, &created); + + if (created) + if (DFLT_OSPF_LOG_ADJACENCY_CHANGES) + SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Config command 'router ospf %d' received, vrf %s id %u oi_running %u", + ospf->instance, ospf_get_name(ospf), ospf->vrf_id, + ospf->oi_running); + + VTY_PUSH_CONTEXT(OSPF_NODE, ospf); + + return ret; +} + +DEFUN (no_router_ospf, + no_router_ospf_cmd, + "no router ospf [{(1-65535)|vrf NAME}]", + NO_STR + "Enable a routing process\n" + "Start OSPF configuration\n" + "Instance ID\n" + VRF_CMD_HELP_STR) +{ + unsigned short instance; + const char *vrf_name; + struct ospf *ospf; + int ret; + + ret = ospf_router_cmd_parse(vty, argv, argc, &instance, &vrf_name); + if (ret != CMD_SUCCESS) + return ret; + + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup(instance, vrf_name); + if (ospf) { + if (ospf->gr_info.restart_support) + ospf_gr_nvm_delete(ospf); + + ospf_finish(ospf); + } else + ret = CMD_WARNING_CONFIG_FAILED; + + return ret; +} + + +DEFPY (ospf_router_id, + ospf_router_id_cmd, + "ospf router-id A.B.C.D", + "OSPF specific commands\n" + "router-id for the OSPF process\n" + "OSPF router-id in IP address format\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + struct listnode *node; + struct ospf_area *area; + + ospf->router_id_static = router_id; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (area->full_nbrs) { + vty_out(vty, + "For this router-id change to take effect, use \"clear ip ospf process\" command\n"); + return CMD_SUCCESS; + } + + ospf_router_id_update(ospf); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_router_id_old, + ospf_router_id_old_cmd, + "router-id A.B.C.D", + "router-id for the OSPF process\n" + "OSPF router-id in IP address format\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4 = 1; + struct listnode *node; + struct ospf_area *area; + struct in_addr router_id; + int ret; + + ret = inet_aton(argv[idx_ipv4]->arg, &router_id); + if (!ret) { + vty_out(vty, "Please specify Router ID by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf->router_id_static = router_id; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (area->full_nbrs) { + vty_out(vty, + "For this router-id change to take effect, use \"clear ip ospf process\" command\n"); + return CMD_SUCCESS; + } + + ospf_router_id_update(ospf); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf_router_id, + no_ospf_router_id_cmd, + "no ospf router-id [A.B.C.D]", + NO_STR + "OSPF specific commands\n" + "router-id for the OSPF process\n" + "OSPF router-id in IP address format\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *node; + struct ospf_area *area; + + if (router_id_str) { + if (!IPV4_ADDR_SAME(&ospf->router_id_static, &router_id)) { + vty_out(vty, "%% OSPF router-id doesn't match\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + ospf->router_id_static.s_addr = 0; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (area->full_nbrs) { + vty_out(vty, + "For this router-id change to take effect, use \"clear ip ospf process\" command\n"); + return CMD_SUCCESS; + } + + ospf_router_id_update(ospf); + + return CMD_SUCCESS; +} + + +static void ospf_passive_interface_default_update(struct ospf *ospf, + uint8_t newval) +{ + struct listnode *ln; + struct ospf_interface *oi; + + ospf->passive_interface_default = newval; + + /* update multicast memberships */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, ln, oi)) + ospf_if_set_multicast(oi); +} + +static void ospf_passive_interface_update(struct interface *ifp, + struct ospf_if_params *params, + struct in_addr addr, uint8_t newval) +{ + struct route_node *rn; + + if (OSPF_IF_PARAM_CONFIGURED(params, passive_interface)) { + if (params->passive_interface == newval) + return; + + params->passive_interface = newval; + UNSET_IF_PARAM(params, passive_interface); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + } else { + params->passive_interface = newval; + SET_IF_PARAM(params, passive_interface); + } + + /* + * XXX We should call ospf_if_set_multicast on exactly those + * interfaces for which the passive property changed. It is too much + * work to determine this set, so we do this for every interface. + * This is safe and reasonable because ospf_if_set_multicast uses a + * record of joined groups to avoid systems calls if the desired + * memberships match the current memership. + */ + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi) + ospf_if_set_multicast(oi); + } + + /* + * XXX It is not clear what state transitions the interface needs to + * undergo when going from active to passive and vice versa. Fixing + * this will require precise identification of interfaces having such a + * transition. + */ +} + +DEFUN (ospf_passive_interface_default, + ospf_passive_interface_default_cmd, + "passive-interface default", + "Suppress routing updates on an interface\n" + "Suppress routing updates on interfaces by default\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_passive_interface_default_update(ospf, OSPF_IF_PASSIVE); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_passive_interface_addr, + ospf_passive_interface_addr_cmd, + "passive-interface IFNAME [A.B.C.D]", + "Suppress routing updates on an interface\n" + "Interface's name\n" + "IPv4 address\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4 = 2; + struct interface *ifp = NULL; + struct in_addr addr = {.s_addr = INADDR_ANY}; + struct ospf_if_params *params; + int ret; + + vty_out(vty, + "This command is deprecated, because it is not VRF-aware.\n"); + vty_out(vty, + "Please, use \"ip ospf passive\" on an interface instead.\n"); + + if (ospf->vrf_id != VRF_UNKNOWN) + ifp = if_get_by_name(argv[1]->arg, ospf->vrf_id, ospf->name); + + if (ifp == NULL) { + vty_out(vty, "interface %s not found.\n", (char *)argv[1]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc == 3) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } else { + params = IF_DEF_PARAMS(ifp); + } + + ospf_passive_interface_update(ifp, params, addr, OSPF_IF_PASSIVE); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_passive_interface_default, + no_ospf_passive_interface_default_cmd, + "no passive-interface default", + NO_STR + "Allow routing updates on an interface\n" + "Allow routing updates on interfaces by default\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_passive_interface_default_update(ospf, OSPF_IF_ACTIVE); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_passive_interface, + no_ospf_passive_interface_addr_cmd, + "no passive-interface IFNAME [A.B.C.D]", + NO_STR + "Allow routing updates on an interface\n" + "Interface's name\n" + "IPv4 address\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4 = 3; + struct interface *ifp = NULL; + struct in_addr addr = {.s_addr = INADDR_ANY}; + struct ospf_if_params *params; + int ret; + + vty_out(vty, + "This command is deprecated, because it is not VRF-aware.\n"); + vty_out(vty, + "Please, use \"no ip ospf passive\" on an interface instead.\n"); + + if (ospf->vrf_id != VRF_UNKNOWN) + ifp = if_get_by_name(argv[2]->arg, ospf->vrf_id, ospf->name); + + if (ifp == NULL) { + vty_out(vty, "interface %s not found.\n", (char *)argv[2]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc == 4) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } else { + params = IF_DEF_PARAMS(ifp); + } + + ospf_passive_interface_update(ifp, params, addr, OSPF_IF_ACTIVE); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_network_area, + ospf_network_area_cmd, + "network A.B.C.D/M area ", + "Enable routing on an IP network\n" + "OSPF network prefix\n" + "Set the OSPF area ID\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_prefixlen = 1; + int idx_ipv4_number = 3; + struct prefix_ipv4 p; + struct in_addr area_id; + int ret, format; + uint32_t count; + + if (ospf->instance) { + vty_out(vty, + "The network command is not supported in multi-instance ospf\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + count = ospf_count_area_params(ospf); + if (count > 0) { + vty_out(vty, + "Please remove all ip ospf area x.x.x.x commands first.\n"); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s ospf vrf %s num of %u ip ospf area x config", + __func__, ospf_get_name(ospf), count); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get network prefix and Area ID. */ + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + ret = ospf_network_set(ospf, &p, area_id, format); + if (ret == 0) { + vty_out(vty, "There is already same network statement.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_network_area, + no_ospf_network_area_cmd, + "no network A.B.C.D/M area ", + NO_STR + "Enable routing on an IP network\n" + "OSPF network prefix\n" + "Set the OSPF area ID\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_prefixlen = 2; + int idx_ipv4_number = 4; + struct prefix_ipv4 p; + struct in_addr area_id; + int ret, format; + + if (ospf->instance) { + vty_out(vty, + "The network command is not supported in multi-instance ospf\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get network prefix and Area ID. */ + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + ret = ospf_network_unset(ospf, &p, area_id); + if (ret == 0) { + vty_out(vty, + "Can't find specified network area configuration.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_range, + ospf_area_range_cmd, + "area range A.B.C.D/M [advertise [cost (0-16777215)]]", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Summarize routes matching address/mask (border routers only)\n" + "Area range prefix\n" + "Advertise this range (default)\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + int idx_ipv4_number = 1; + int idx_ipv4_prefixlen = 3; + int idx_cost = 6; + struct prefix_ipv4 p; + struct in_addr area_id; + int format; + uint32_t cost; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + ospf_area_range_set(ospf, area, area->ranges, &p, + OSPF_AREA_RANGE_ADVERTISE, false); + if (argc > 5) { + cost = strtoul(argv[idx_cost]->arg, NULL, 10); + ospf_area_range_cost_set(ospf, area, area->ranges, &p, cost); + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_range_cost, + ospf_area_range_cost_cmd, + "area range A.B.C.D/M {cost (0-16777215)|substitute A.B.C.D/M}", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Summarize routes matching address/mask (border routers only)\n" + "Area range prefix\n" + "User specified metric for this range\n" + "Advertised metric for this range\n" + "Announce area range as another prefix\n" + "Network prefix to be announced instead of range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + int idx_ipv4_number = 1; + int idx_ipv4_prefixlen = 3; + int idx = 4; + struct prefix_ipv4 p, s; + struct in_addr area_id; + int format; + uint32_t cost; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + ospf_area_range_set(ospf, area, area->ranges, &p, + OSPF_AREA_RANGE_ADVERTISE, false); + if (argv_find(argv, argc, "cost", &idx)) { + cost = strtoul(argv[idx + 1]->arg, NULL, 10); + ospf_area_range_cost_set(ospf, area, area->ranges, &p, cost); + } + + idx = 4; + if (argv_find(argv, argc, "substitute", &idx)) { + str2prefix_ipv4(argv[idx + 1]->arg, &s); + ospf_area_range_substitute_set(ospf, area, &p, &s); + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_range_not_advertise, + ospf_area_range_not_advertise_cmd, + "area range A.B.C.D/M not-advertise", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Summarize routes matching address/mask (border routers only)\n" + "Area range prefix\n" + "DoNotAdvertise this range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + int idx_ipv4_number = 1; + int idx_ipv4_prefixlen = 3; + struct prefix_ipv4 p; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + ospf_area_range_set(ospf, area, area->ranges, &p, 0, false); + ospf_area_range_substitute_unset(ospf, area, &p); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_range, + no_ospf_area_range_cmd, + "no area range A.B.C.D/M []", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Summarize routes matching address/mask (border routers only)\n" + "Area range prefix\n" + "User specified metric for this range\n" + "Advertised metric for this range\n" + "Advertise this range (default)\n" + "User specified metric for this range\n" + "Advertised metric for this range\n" + "DoNotAdvertise this range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + int idx_ipv4_number = 2; + int idx_ipv4_prefixlen = 4; + struct prefix_ipv4 p; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + ospf_area_range_unset(ospf, area, area->ranges, &p); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_range_substitute, + no_ospf_area_range_substitute_cmd, + "no area range A.B.C.D/M substitute A.B.C.D/M", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Summarize routes matching address/mask (border routers only)\n" + "Area range prefix\n" + "Announce area range as another prefix\n" + "Network prefix to be announced instead of range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + int idx_ipv4_number = 2; + int idx_ipv4_prefixlen = 4; + int idx_ipv4_prefixlen_2 = 6; + struct prefix_ipv4 p, s; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + str2prefix_ipv4(argv[idx_ipv4_prefixlen]->arg, &p); + str2prefix_ipv4(argv[idx_ipv4_prefixlen_2]->arg, &s); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + ospf_area_range_substitute_unset(ospf, area, &p); + + return CMD_SUCCESS; +} + + +/* Command Handler Logic in VLink stuff is delicate!! + + ALTER AT YOUR OWN RISK!!!! + + Various dummy values are used to represent 'NoChange' state for + VLink configuration NOT being changed by a VLink command, and + special syntax is used within the command strings so that the + typed in command verbs can be seen in the configuration command + bacckend handler. This is to drastically reduce the verbeage + required to coe up with a reasonably compatible Cisco VLink command + + - Matthew Grant + Wed, 21 Feb 2001 15:13:52 +1300 + */ + +/* Configuration data for virtual links + */ +struct ospf_vl_config_data { + struct vty *vty; /* vty stuff */ + struct in_addr area_id; /* area ID from command line */ + int area_id_fmt; /* command line area ID format */ + struct in_addr vl_peer; /* command line vl_peer */ + int auth_type; /* Authehntication type, if given */ + char *auth_key; /* simple password if present */ + int crypto_key_id; /* Cryptographic key ID */ + char *md5_key; /* MD5 authentication key */ + char *keychain; /* Cryptographic keychain */ + int del_keychain; + int hello_interval; /* Obvious what these are... */ + int retransmit_interval; + int transmit_delay; + int dead_interval; +}; + +static void ospf_vl_config_data_init(struct ospf_vl_config_data *vl_config, + struct vty *vty) +{ + memset(vl_config, 0, sizeof(struct ospf_vl_config_data)); + vl_config->auth_type = OSPF_AUTH_CMD_NOTSEEN; + vl_config->vty = vty; +} + +static struct ospf_vl_data * +ospf_find_vl_data(struct ospf *ospf, struct ospf_vl_config_data *vl_config) +{ + struct ospf_area *area; + struct ospf_vl_data *vl_data; + struct vty *vty; + struct in_addr area_id; + + vty = vl_config->vty; + area_id = vl_config->area_id; + + if (area_id.s_addr == OSPF_AREA_BACKBONE) { + vty_out(vty, + "Configuring VLs over the backbone is not allowed\n"); + return NULL; + } + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, vl_config->area_id_fmt); + + if (area->external_routing != OSPF_AREA_DEFAULT) { + if (vl_config->area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) + vty_out(vty, "Area %pI4 is %s\n", &area_id, + area->external_routing == OSPF_AREA_NSSA + ? "nssa" + : "stub"); + else + vty_out(vty, "Area %ld is %s\n", + (unsigned long)ntohl(area_id.s_addr), + area->external_routing == OSPF_AREA_NSSA + ? "nssa" + : "stub"); + return NULL; + } + + if ((vl_data = ospf_vl_lookup(ospf, area, vl_config->vl_peer)) + == NULL) { + vl_data = ospf_vl_data_new(area, vl_config->vl_peer); + if (vl_data->vl_oi == NULL) { + vl_data->vl_oi = ospf_vl_new(ospf, vl_data); + ospf_vl_add(ospf, vl_data); + ospf_spf_calculate_schedule(ospf, + SPF_FLAG_CONFIG_CHANGE); + } + } + return vl_data; +} + + +static int ospf_vl_set_security(struct ospf_vl_data *vl_data, + struct ospf_vl_config_data *vl_config) +{ + struct crypt_key *ck; + struct vty *vty; + struct interface *ifp = vl_data->vl_oi->ifp; + + vty = vl_config->vty; + + if (vl_config->auth_type != OSPF_AUTH_CMD_NOTSEEN) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), auth_type); + IF_DEF_PARAMS(ifp)->auth_type = vl_config->auth_type; + } + + if (vl_config->auth_key) { + memset(IF_DEF_PARAMS(ifp)->auth_simple, 0, + OSPF_AUTH_SIMPLE_SIZE + 1); + strlcpy((char *)IF_DEF_PARAMS(ifp)->auth_simple, + vl_config->auth_key, + sizeof(IF_DEF_PARAMS(ifp)->auth_simple)); + } else if (vl_config->keychain) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, IF_DEF_PARAMS(ifp)->keychain_name); + IF_DEF_PARAMS(ifp)->keychain_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS, vl_config->keychain); + } else if (vl_config->md5_key) { + if (ospf_crypt_key_lookup(IF_DEF_PARAMS(ifp)->auth_crypt, + vl_config->crypto_key_id) + != NULL) { + vty_out(vty, "OSPF: Key %d already exists\n", + vl_config->crypto_key_id); + return CMD_WARNING; + } + ck = ospf_crypt_key_new(); + ck->key_id = vl_config->crypto_key_id; + memset(ck->auth_key, 0, OSPF_AUTH_MD5_SIZE + 1); + strlcpy((char *)ck->auth_key, vl_config->md5_key, + sizeof(ck->auth_key)); + + ospf_crypt_key_add(IF_DEF_PARAMS(ifp)->auth_crypt, ck); + } else if (vl_config->crypto_key_id != 0) { + /* Delete a key */ + + if (ospf_crypt_key_lookup(IF_DEF_PARAMS(ifp)->auth_crypt, + vl_config->crypto_key_id) + == NULL) { + vty_out(vty, "OSPF: Key %d does not exist\n", + vl_config->crypto_key_id); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf_crypt_key_delete(IF_DEF_PARAMS(ifp)->auth_crypt, + vl_config->crypto_key_id); + } else if (vl_config->del_keychain) { + UNSET_IF_PARAM(IF_DEF_PARAMS(ifp), keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, IF_DEF_PARAMS(ifp)->keychain_name); + } + + return CMD_SUCCESS; +} + +static int ospf_vl_set_timers(struct ospf_vl_data *vl_data, + struct ospf_vl_config_data *vl_config) +{ + struct interface *ifp = vl_data->vl_oi->ifp; + /* Virtual Link data initialised to defaults, so only set + if a value given */ + if (vl_config->hello_interval) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_hello); + IF_DEF_PARAMS(ifp)->v_hello = vl_config->hello_interval; + } + + if (vl_config->dead_interval) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), v_wait); + IF_DEF_PARAMS(ifp)->v_wait = vl_config->dead_interval; + } + + if (vl_config->retransmit_interval) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), retransmit_interval); + IF_DEF_PARAMS(ifp)->retransmit_interval = + vl_config->retransmit_interval; + } + + if (vl_config->transmit_delay) { + SET_IF_PARAM(IF_DEF_PARAMS(ifp), transmit_delay); + IF_DEF_PARAMS(ifp)->transmit_delay = vl_config->transmit_delay; + } + + return CMD_SUCCESS; +} + + +/* The business end of all of the above */ +static int ospf_vl_set(struct ospf *ospf, struct ospf_vl_config_data *vl_config) +{ + struct ospf_vl_data *vl_data; + int ret; + + vl_data = ospf_find_vl_data(ospf, vl_config); + if (!vl_data) + return CMD_WARNING_CONFIG_FAILED; + + /* Process this one first as it can have a fatal result, which can + only logically occur if the virtual link exists already + Thus a command error does not result in a change to the + running configuration such as unexpectedly altered timer + values etc.*/ + ret = ospf_vl_set_security(vl_data, vl_config); + if (ret != CMD_SUCCESS) + return ret; + + /* Set any time based parameters, these area already range checked */ + + ret = ospf_vl_set_timers(vl_data, vl_config); + if (ret != CMD_SUCCESS) + return ret; + + return CMD_SUCCESS; +} + +/* This stuff exists to make specifying all the alias commands A LOT simpler + */ +#define VLINK_HELPSTR_IPADDR \ + "OSPF area parameters\n" \ + "OSPF area ID in IP address format\n" \ + "OSPF area ID as a decimal value\n" \ + "Configure a virtual link\n" \ + "Router ID of the remote ABR\n" + +#define VLINK_HELPSTR_AUTHTYPE_SIMPLE \ + "Enable authentication on this virtual link\n" \ + "dummy string \n" + +#define VLINK_HELPSTR_AUTHTYPE_ALL \ + VLINK_HELPSTR_AUTHTYPE_SIMPLE \ + "Use null authentication\n" \ + "Use message-digest authentication\n" + +#define VLINK_HELPSTR_TIME_PARAM \ + "Time between HELLO packets\n" \ + "Seconds\n" \ + "Time between retransmitting lost link state advertisements\n" \ + "Seconds\n" \ + "Link state transmit delay\n" \ + "Seconds\n" \ + "Interval time after which a neighbor is declared down\n" \ + "Seconds\n" + +#define VLINK_HELPSTR_AUTH_SIMPLE \ + "Authentication password (key)\n" \ + "The OSPF password (key)\n" + +#define VLINK_HELPSTR_AUTH_MD5 \ + "Message digest authentication password (key)\n" \ + "Key ID\n" \ + "Use MD5 algorithm\n" \ + "The OSPF password (key)\n" + +DEFUN (ospf_area_vlink, + ospf_area_vlink_cmd, + "area virtual-link A.B.C.D [authentication []] []", + VLINK_HELPSTR_IPADDR + "Enable authentication on this virtual link\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" + "Use message-digest authentication\n" + "Use null authentication\n" + VLINK_HELPSTR_AUTH_MD5 + VLINK_HELPSTR_AUTH_SIMPLE) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + int idx_ipv4 = 3; + struct ospf_vl_config_data vl_config; + char auth_key[OSPF_AUTH_SIMPLE_SIZE + 1]; + char md5_key[OSPF_AUTH_MD5_SIZE + 1]; + int ret; + int idx = 0; + + ospf_vl_config_data_init(&vl_config, vty); + + /* Read off first 2 parameters and check them */ + ret = str2area_id(argv[idx_ipv4_number]->arg, &vl_config.area_id, + &vl_config.area_id_fmt); + if (ret < 0) { + vty_out(vty, "OSPF area ID is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = inet_aton(argv[idx_ipv4]->arg, &vl_config.vl_peer); + if (!ret) { + vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc <= 4) { + /* Thats all folks! - BUGS B. strikes again!!!*/ + + return ospf_vl_set(ospf, &vl_config); + } + + if (argv_find(argv, argc, "authentication", &idx)) { + /* authentication - this option can only occur + at start of command line */ + vl_config.auth_type = OSPF_AUTH_SIMPLE; + } + + if (argv_find(argv, argc, "key-chain", &idx)) { + vl_config.auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + vl_config.keychain = argv[idx+1]->arg; + } else if (argv_find(argv, argc, "message-digest", &idx)) { + /* authentication message-digest */ + vl_config.auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + } else if (argv_find(argv, argc, "null", &idx)) { + /* "authentication null" */ + vl_config.auth_type = OSPF_AUTH_NULL; + } + + if (argv_find(argv, argc, "message-digest-key", &idx)) { + vl_config.md5_key = NULL; + vl_config.crypto_key_id = strtol(argv[idx + 1]->arg, NULL, 10); + if (vl_config.crypto_key_id < 0) + return CMD_WARNING_CONFIG_FAILED; + + strlcpy(md5_key, argv[idx + 3]->arg, sizeof(md5_key)); + vl_config.md5_key = md5_key; + } + + if (argv_find(argv, argc, "authentication-key", &idx)) { + strlcpy(auth_key, argv[idx + 1]->arg, sizeof(auth_key)); + vl_config.auth_key = auth_key; + } + + /* Action configuration */ + + return ospf_vl_set(ospf, &vl_config); +} + +DEFUN (no_ospf_area_vlink, + no_ospf_area_vlink_cmd, + "no area virtual-link A.B.C.D [authentication []] []", + NO_STR + VLINK_HELPSTR_IPADDR + "Enable authentication on this virtual link\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" + "Use message-digest authentication\n" + "Use null authentication\n" + VLINK_HELPSTR_AUTH_MD5 + VLINK_HELPSTR_AUTH_SIMPLE) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + int idx_ipv4 = 4; + struct ospf_area *area; + struct ospf_vl_config_data vl_config; + struct ospf_vl_data *vl_data = NULL; + char auth_key[OSPF_AUTH_SIMPLE_SIZE + 1]; + int idx = 0; + int ret, format; + + ospf_vl_config_data_init(&vl_config, vty); + + ret = str2area_id(argv[idx_ipv4_number]->arg, &vl_config.area_id, + &format); + if (ret < 0) { + vty_out(vty, "OSPF area ID is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + area = ospf_area_lookup_by_area_id(ospf, vl_config.area_id); + if (!area) { + vty_out(vty, "Area does not exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = inet_aton(argv[idx_ipv4]->arg, &vl_config.vl_peer); + if (!ret) { + vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + vl_data = ospf_vl_lookup(ospf, area, vl_config.vl_peer); + if (!vl_data) { + vty_out(vty, "Virtual link does not exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc <= 5) { + /* Basic VLink no command */ + /* Thats all folks! - BUGS B. strikes again!!!*/ + ospf_vl_delete(ospf, vl_data); + ospf_area_check_free(ospf, vl_config.area_id); + return CMD_SUCCESS; + } + + /* If we are down here, we are reseting parameters */ + /* Deal with other parameters */ + + if (argv_find(argv, argc, "authentication", &idx)) { + /* authentication - this option can only occur + at start of command line */ + vl_config.auth_type = OSPF_AUTH_NOTSET; + } + + if (argv_find(argv, argc, "key-chain", &idx)) { + vl_config.del_keychain = 1; + vl_config.keychain = NULL; + } + + if (argv_find(argv, argc, "message-digest-key", &idx)) { + vl_config.md5_key = NULL; + vl_config.crypto_key_id = strtol(argv[idx + 1]->arg, NULL, 10); + if (vl_config.crypto_key_id < 0) + return CMD_WARNING_CONFIG_FAILED; + } + + if (argv_find(argv, argc, "authentication-key", &idx)) { + /* Reset authentication-key to 0 */ + memset(auth_key, 0, OSPF_AUTH_SIMPLE_SIZE + 1); + vl_config.auth_key = auth_key; + } + + /* Action configuration */ + + return ospf_vl_set(ospf, &vl_config); +} + +DEFUN (ospf_area_vlink_intervals, + ospf_area_vlink_intervals_cmd, + "area virtual-link A.B.C.D {hello-interval (1-65535)|retransmit-interval (1-65535)|transmit-delay (1-65535)|dead-interval (1-65535)}", + VLINK_HELPSTR_IPADDR + VLINK_HELPSTR_TIME_PARAM) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_vl_config_data vl_config; + int ret = 0; + + ospf_vl_config_data_init(&vl_config, vty); + + char *area_id = argv[1]->arg; + char *router_id = argv[3]->arg; + + ret = str2area_id(area_id, &vl_config.area_id, &vl_config.area_id_fmt); + if (ret < 0) { + vty_out(vty, "OSPF area ID is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = inet_aton(router_id, &vl_config.vl_peer); + if (!ret) { + vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + for (int idx = 4; idx < argc; idx++) { + if (strmatch(argv[idx]->text, "hello-interval")) + vl_config.hello_interval = + strtol(argv[++idx]->arg, NULL, 10); + else if (strmatch(argv[idx]->text, "retransmit-interval")) + vl_config.retransmit_interval = + strtol(argv[++idx]->arg, NULL, 10); + else if (strmatch(argv[idx]->text, "transmit-delay")) + vl_config.transmit_delay = + strtol(argv[++idx]->arg, NULL, 10); + else if (strmatch(argv[idx]->text, "dead-interval")) + vl_config.dead_interval = + strtol(argv[++idx]->arg, NULL, 10); + } + + /* Action configuration */ + return ospf_vl_set(ospf, &vl_config); +} + +DEFUN (no_ospf_area_vlink_intervals, + no_ospf_area_vlink_intervals_cmd, + "no area virtual-link A.B.C.D {hello-interval (1-65535)|retransmit-interval (1-65535)|transmit-delay (1-65535)|dead-interval (1-65535)}", + NO_STR + VLINK_HELPSTR_IPADDR + VLINK_HELPSTR_TIME_PARAM) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_vl_config_data vl_config; + int ret = 0; + + ospf_vl_config_data_init(&vl_config, vty); + + char *area_id = argv[2]->arg; + char *router_id = argv[4]->arg; + + ret = str2area_id(area_id, &vl_config.area_id, &vl_config.area_id_fmt); + if (ret < 0) { + vty_out(vty, "OSPF area ID is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = inet_aton(router_id, &vl_config.vl_peer); + if (!ret) { + vty_out(vty, "Please specify valid Router ID as a.b.c.d\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + for (int idx = 5; idx < argc; idx++) { + if (strmatch(argv[idx]->text, "hello-interval")) + vl_config.hello_interval = OSPF_HELLO_INTERVAL_DEFAULT; + else if (strmatch(argv[idx]->text, "retransmit-interval")) + vl_config.retransmit_interval = + OSPF_RETRANSMIT_INTERVAL_DEFAULT; + else if (strmatch(argv[idx]->text, "transmit-delay")) + vl_config.transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; + else if (strmatch(argv[idx]->text, "dead-interval")) + vl_config.dead_interval = + OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; + } + + /* Action configuration */ + return ospf_vl_set(ospf, &vl_config); +} + +DEFUN (ospf_area_shortcut, + ospf_area_shortcut_cmd, + "area shortcut ", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure the area's shortcutting mode\n" + "Set default shortcutting behavior\n" + "Enable shortcutting through the area\n" + "Disable shortcutting through the area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + int idx_enable_disable = 3; + struct ospf_area *area; + struct in_addr area_id; + int mode; + int format; + + VTY_GET_OSPF_AREA_ID_NO_BB("shortcut", area_id, format, + argv[idx_ipv4_number]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + if (strncmp(argv[idx_enable_disable]->arg, "de", 2) == 0) + mode = OSPF_SHORTCUT_DEFAULT; + else if (strncmp(argv[idx_enable_disable]->arg, "di", 2) == 0) + mode = OSPF_SHORTCUT_DISABLE; + else if (strncmp(argv[idx_enable_disable]->arg, "e", 1) == 0) + mode = OSPF_SHORTCUT_ENABLE; + else + return CMD_WARNING_CONFIG_FAILED; + + ospf_area_shortcut_set(ospf, area, mode); + + if (ospf->abr_type != OSPF_ABR_SHORTCUT) + vty_out(vty, + "Shortcut area setting will take effect only when the router is configured as Shortcut ABR\n"); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_shortcut, + no_ospf_area_shortcut_cmd, + "no area shortcut ", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Deconfigure the area's shortcutting mode\n" + "Deconfigure enabled shortcutting through the area\n" + "Deconfigure disabled shortcutting through the area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID_NO_BB("shortcut", area_id, format, + argv[idx_ipv4_number]->arg); + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (!area) + return CMD_SUCCESS; + + ospf_area_shortcut_unset(ospf, area); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_area_stub, + ospf_area_stub_cmd, + "area stub", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as stub\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + struct in_addr area_id; + int ret, format; + + VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, + argv[idx_ipv4_number]->arg); + + ret = ospf_area_stub_set(ospf, area_id); + ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), + format); + if (ret == 0) { + vty_out(vty, + "First deconfigure all virtual link through this area\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Flush the external LSAs from the specified area */ + ospf_flush_lsa_from_area(ospf, area_id, OSPF_AS_EXTERNAL_LSA); + ospf_area_no_summary_unset(ospf, area_id); + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_stub_no_summary, + ospf_area_stub_no_summary_cmd, + "area stub no-summary", + "OSPF stub parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as stub\n" + "Do not inject inter-area routes into stub\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + struct in_addr area_id; + int ret, format; + + VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, + argv[idx_ipv4_number]->arg); + + ret = ospf_area_stub_set(ospf, area_id); + ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), + format); + if (ret == 0) { + vty_out(vty, + "%% Area cannot be stub as it contains a virtual link\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf_area_no_summary_set(ospf, area_id); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_stub, + no_ospf_area_stub_cmd, + "no area stub", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as stub\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, + argv[idx_ipv4_number]->arg); + + ospf_area_stub_unset(ospf, area_id); + ospf_area_no_summary_unset(ospf, area_id); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_stub_no_summary, + no_ospf_area_stub_no_summary_cmd, + "no area stub no-summary", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as stub\n" + "Do not inject inter-area routes into area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID_NO_BB("stub", area_id, format, + argv[idx_ipv4_number]->arg); + ospf_area_no_summary_unset(ospf, area_id); + + return CMD_SUCCESS; +} + +DEFPY (ospf_area_nssa, + ospf_area_nssa_cmd, + "area $area_str nssa\ + [{\ + $translator_role\ + |default-information-originate$dflt_originate [{metric (0-16777214)$mval|metric-type (1-2)$mtype}]\ + |no-summary$no_summary\ + |suppress-fa$suppress_fa\ + }]", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as nssa\n" + "Configure NSSA-ABR for translate election (default)\n" + "Configure NSSA-ABR to never translate\n" + "Configure NSSA-ABR to always translate\n" + "Originate Type 7 default into NSSA area\n" + "OSPF default metric\n" + "OSPF metric\n" + "OSPF metric type for default routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Do not inject inter-area routes into nssa\n" + "Suppress forwarding address\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct in_addr area_id; + int ret, format; + + VTY_GET_OSPF_AREA_ID_NO_BB("NSSA", area_id, format, area_str); + + ret = ospf_area_nssa_set(ospf, area_id); + ospf_area_display_format_set(ospf, ospf_area_get(ospf, area_id), + format); + if (ret == 0) { + vty_out(vty, + "%% Area cannot be nssa as it contains a virtual link\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (translator_role) { + if (strncmp(translator_role, "translate-c", 11) == 0) + ospf_area_nssa_translator_role_set( + ospf, area_id, OSPF_NSSA_ROLE_CANDIDATE); + else if (strncmp(translator_role, "translate-n", 11) == 0) + ospf_area_nssa_translator_role_set( + ospf, area_id, OSPF_NSSA_ROLE_NEVER); + else if (strncmp(translator_role, "translate-a", 11) == 0) + ospf_area_nssa_translator_role_set( + ospf, area_id, OSPF_NSSA_ROLE_ALWAYS); + } else { + ospf_area_nssa_translator_role_set(ospf, area_id, + OSPF_NSSA_ROLE_CANDIDATE); + } + + if (dflt_originate) { + int metric_type = DEFAULT_METRIC_TYPE; + + if (mval_str == NULL) + mval = -1; + if (mtype_str) + (void)str2metric_type(mtype_str, &metric_type); + ospf_area_nssa_default_originate_set(ospf, area_id, mval, + metric_type); + } else + ospf_area_nssa_default_originate_unset(ospf, area_id); + + if (no_summary) + ospf_area_nssa_no_summary_set(ospf, area_id); + else + ospf_area_no_summary_unset(ospf, area_id); + + if (suppress_fa) + ospf_area_nssa_suppress_fa_set(ospf, area_id); + else + ospf_area_nssa_suppress_fa_unset(ospf, area_id); + + /* Flush the external LSA for the specified area */ + ospf_flush_lsa_from_area(ospf, area_id, OSPF_AS_EXTERNAL_LSA); + ospf_schedule_abr_task(ospf); + ospf_schedule_asbr_redist_update(ospf); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf_area_nssa, + no_ospf_area_nssa_cmd, + "no area $area_str nssa\ + [{\ + \ + |default-information-originate [{metric (0-16777214)|metric-type (1-2)}]\ + |no-summary\ + |suppress-fa\ + }]", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as nssa\n" + "Configure NSSA-ABR for translate election (default)\n" + "Configure NSSA-ABR to never translate\n" + "Configure NSSA-ABR to always translate\n" + "Originate Type 7 default into NSSA area\n" + "OSPF default metric\n" + "OSPF metric\n" + "OSPF metric type for default routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Do not inject inter-area routes into nssa\n" + "Suppress forwarding address\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID_NO_BB("NSSA", area_id, format, area_str); + + /* Flush the NSSA LSA for the specified area */ + ospf_flush_lsa_from_area(ospf, area_id, OSPF_AS_NSSA_LSA); + ospf_area_no_summary_unset(ospf, area_id); + ospf_area_nssa_default_originate_unset(ospf, area_id); + ospf_area_nssa_suppress_fa_unset(ospf, area_id); + ospf_area_nssa_unset(ospf, area_id); + + ospf_schedule_abr_task(ospf); + + return CMD_SUCCESS; +} + +DEFPY (ospf_area_nssa_range, + ospf_area_nssa_range_cmd, + "area $area_str nssa range A.B.C.D/M$prefix []", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as nssa\n" + "Configured address range\n" + "Specify IPv4 prefix\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + struct in_addr area_id; + int format; + int advertise = 0; + + VTY_GET_OSPF_AREA_ID(area_id, format, area_str); + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + if (area->external_routing != OSPF_AREA_NSSA) { + vty_out(vty, "%% First configure %s as an NSSA area\n", + area_str); + return CMD_WARNING; + } + + if (!not_adv) + advertise = OSPF_AREA_RANGE_ADVERTISE; + + ospf_area_range_set(ospf, area, area->nssa_ranges, + (struct prefix_ipv4 *)prefix, advertise, true); + if (cost_str) + ospf_area_range_cost_set(ospf, area, area->nssa_ranges, + (struct prefix_ipv4 *)prefix, cost); + + return CMD_SUCCESS; +} + +DEFPY (no_ospf_area_nssa_range, + no_ospf_area_nssa_range_cmd, + "no area $area_str nssa range A.B.C.D/M$prefix []", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Configure OSPF area as nssa\n" + "Configured address range\n" + "Specify IPv4 prefix\n" + "Do not advertise\n" + "User specified metric for this range\n" + "Advertised metric for this range\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, area_str); + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + if (area->external_routing != OSPF_AREA_NSSA) { + vty_out(vty, "%% First configure %s as an NSSA area\n", + area_str); + return CMD_WARNING; + } + + ospf_area_range_unset(ospf, area, area->nssa_ranges, + (struct prefix_ipv4 *)prefix); + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_default_cost, + ospf_area_default_cost_cmd, + "area default-cost (0-16777215)", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Set the summary-default cost of a NSSA or stub area\n" + "Stub's advertised default summary cost\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + int idx_number = 3; + struct ospf_area *area; + struct in_addr area_id; + uint32_t cost; + int format; + struct prefix_ipv4 p; + + VTY_GET_OSPF_AREA_ID_NO_BB("default-cost", area_id, format, + argv[idx_ipv4_number]->arg); + cost = strtoul(argv[idx_number]->arg, NULL, 10); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + + if (area->external_routing == OSPF_AREA_DEFAULT) { + vty_out(vty, "The area is neither stub, nor NSSA\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + area->default_cost = cost; + + p.family = AF_INET; + p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; + p.prefixlen = 0; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "ospf_abr_announce_stub_defaults(): announcing 0.0.0.0/0 to area %pI4", + &area->area_id); + ospf_abr_announce_network_to_area(&p, area->default_cost, area); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_default_cost, + no_ospf_area_default_cost_cmd, + "no area default-cost (0-16777215)", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Set the summary-default cost of a NSSA or stub area\n" + "Stub's advertised default summary cost\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct ospf_area *area; + struct in_addr area_id; + int format; + struct prefix_ipv4 p; + + VTY_GET_OSPF_AREA_ID_NO_BB("default-cost", area_id, format, + argv[idx_ipv4_number]->arg); + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return CMD_SUCCESS; + + if (area->external_routing == OSPF_AREA_DEFAULT) { + vty_out(vty, "The area is neither stub, nor NSSA\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + area->default_cost = 1; + + p.family = AF_INET; + p.prefix.s_addr = OSPF_DEFAULT_DESTINATION; + p.prefixlen = 0; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "ospf_abr_announce_stub_defaults(): announcing 0.0.0.0/0 to area %pI4", + &area->area_id); + ospf_abr_announce_network_to_area(&p, area->default_cost, area); + + + ospf_area_check_free(ospf, area_id); + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_export_list, + ospf_area_export_list_cmd, + "area export-list ACCESSLIST4_NAME", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Set the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + ospf_area_export_list_set(ospf, area, argv[3]->arg); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_export_list, + no_ospf_area_export_list_cmd, + "no area export-list ACCESSLIST4_NAME", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Unset the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return CMD_SUCCESS; + + ospf_area_export_list_unset(ospf, area); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_area_import_list, + ospf_area_import_list_cmd, + "area import-list ACCESSLIST4_NAME", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Set the filter for networks from other areas announced to the specified one\n" + "Name of the access-list\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + ospf_area_import_list_set(ospf, area, argv[3]->arg); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_import_list, + no_ospf_area_import_list_cmd, + "no area import-list ACCESSLIST4_NAME", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Unset the filter for networks announced to other areas\n" + "Name of the access-list\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return CMD_SUCCESS; + + ospf_area_import_list_unset(ospf, area); + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_filter_list, + ospf_area_filter_list_cmd, + "area filter-list prefix PREFIXLIST4_NAME ", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Filter networks between OSPF areas\n" + "Filter prefixes between OSPF areas\n" + "Name of an IP prefix-list\n" + "Filter networks sent to this area\n" + "Filter networks sent from this area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + int idx_word = 4; + int idx_in_out = 5; + struct ospf_area *area; + struct in_addr area_id; + struct prefix_list *plist; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + plist = prefix_list_lookup(AFI_IP, argv[idx_word]->arg); + if (strncmp(argv[idx_in_out]->arg, "in", 2) == 0) { + PREFIX_LIST_IN(area) = plist; + if (PREFIX_NAME_IN(area)) + free(PREFIX_NAME_IN(area)); + + PREFIX_NAME_IN(area) = strdup(argv[idx_word]->arg); + ospf_schedule_abr_task(ospf); + } else { + PREFIX_LIST_OUT(area) = plist; + if (PREFIX_NAME_OUT(area)) + free(PREFIX_NAME_OUT(area)); + + PREFIX_NAME_OUT(area) = strdup(argv[idx_word]->arg); + ospf_schedule_abr_task(ospf); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_filter_list, + no_ospf_area_filter_list_cmd, + "no area filter-list prefix PREFIXLIST4_NAME ", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Filter networks between OSPF areas\n" + "Filter prefixes between OSPF areas\n" + "Name of an IP prefix-list\n" + "Filter networks sent to this area\n" + "Filter networks sent from this area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + int idx_word = 5; + int idx_in_out = 6; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + if ((area = ospf_area_lookup_by_area_id(ospf, area_id)) == NULL) + return CMD_SUCCESS; + + if (strncmp(argv[idx_in_out]->arg, "in", 2) == 0) { + if (PREFIX_NAME_IN(area)) + if (strcmp(PREFIX_NAME_IN(area), argv[idx_word]->arg) + != 0) + return CMD_SUCCESS; + + PREFIX_LIST_IN(area) = NULL; + if (PREFIX_NAME_IN(area)) + free(PREFIX_NAME_IN(area)); + + PREFIX_NAME_IN(area) = NULL; + + ospf_schedule_abr_task(ospf); + } else { + if (PREFIX_NAME_OUT(area)) + if (strcmp(PREFIX_NAME_OUT(area), argv[idx_word]->arg) + != 0) + return CMD_SUCCESS; + + PREFIX_LIST_OUT(area) = NULL; + if (PREFIX_NAME_OUT(area)) + free(PREFIX_NAME_OUT(area)); + + PREFIX_NAME_OUT(area) = NULL; + + ospf_schedule_abr_task(ospf); + } + + return CMD_SUCCESS; +} + + +DEFUN (ospf_area_authentication_message_digest, + ospf_area_authentication_message_digest_cmd, + "[no] area authentication message-digest", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Enable authentication\n" + "Use message-digest authentication\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx = 0; + struct ospf_area *area; + struct in_addr area_id; + int format; + + argv_find(argv, argc, "area", &idx); + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx + 1]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + area->auth_type = strmatch(argv[0]->text, "no") + ? OSPF_AUTH_NULL + : OSPF_AUTH_CRYPTOGRAPHIC; + + return CMD_SUCCESS; +} + +DEFUN (ospf_area_authentication, + ospf_area_authentication_cmd, + "area authentication", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Enable authentication\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 1; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, format); + area->auth_type = OSPF_AUTH_SIMPLE; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_area_authentication, + no_ospf_area_authentication_cmd, + "no area authentication", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Enable authentication\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ipv4_number = 2; + struct ospf_area *area; + struct in_addr area_id; + int format; + + VTY_GET_OSPF_AREA_ID(area_id, format, argv[idx_ipv4_number]->arg); + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return CMD_SUCCESS; + + area->auth_type = OSPF_AUTH_NULL; + + ospf_area_check_free(ospf, area_id); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_abr_type, + ospf_abr_type_cmd, + "ospf abr-type ", + "OSPF specific commands\n" + "Set OSPF ABR type\n" + "Alternative ABR, cisco implementation\n" + "Alternative ABR, IBM implementation\n" + "Shortcut ABR\n" + "Standard behavior (RFC2328)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_vendor = 2; + uint8_t abr_type = OSPF_ABR_UNKNOWN; + + if (strncmp(argv[idx_vendor]->arg, "c", 1) == 0) + abr_type = OSPF_ABR_CISCO; + else if (strncmp(argv[idx_vendor]->arg, "i", 1) == 0) + abr_type = OSPF_ABR_IBM; + else if (strncmp(argv[idx_vendor]->arg, "sh", 2) == 0) + abr_type = OSPF_ABR_SHORTCUT; + else if (strncmp(argv[idx_vendor]->arg, "st", 2) == 0) + abr_type = OSPF_ABR_STAND; + else + return CMD_WARNING_CONFIG_FAILED; + + /* If ABR type value is changed, schedule ABR task. */ + if (ospf->abr_type != abr_type) { + ospf->abr_type = abr_type; + ospf_schedule_abr_task(ospf); + + /* The ABR task might not initiate SPF recalculation if the + * OSPF flags remain the same. And inter-area routes would not + * be added/deleted according to the new ABR type. So this + * needs to be done here too. + */ + ospf_spf_calculate_schedule(ospf, SPF_FLAG_ABR_STATUS_CHANGE); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_abr_type, + no_ospf_abr_type_cmd, + "no ospf abr-type ", + NO_STR + "OSPF specific commands\n" + "Set OSPF ABR type\n" + "Alternative ABR, cisco implementation\n" + "Alternative ABR, IBM implementation\n" + "Shortcut ABR\n" + "Standard ABR\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_vendor = 3; + uint8_t abr_type = OSPF_ABR_UNKNOWN; + + if (strncmp(argv[idx_vendor]->arg, "c", 1) == 0) + abr_type = OSPF_ABR_CISCO; + else if (strncmp(argv[idx_vendor]->arg, "i", 1) == 0) + abr_type = OSPF_ABR_IBM; + else if (strncmp(argv[idx_vendor]->arg, "sh", 2) == 0) + abr_type = OSPF_ABR_SHORTCUT; + else if (strncmp(argv[idx_vendor]->arg, "st", 2) == 0) + abr_type = OSPF_ABR_STAND; + else + return CMD_WARNING_CONFIG_FAILED; + + /* If ABR type value is changed, schedule ABR task. */ + if (ospf->abr_type == abr_type) { + ospf->abr_type = OSPF_ABR_DEFAULT; + ospf_schedule_abr_task(ospf); + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_log_adjacency_changes, + ospf_log_adjacency_changes_cmd, + "log-adjacency-changes", + "Log changes in adjacency state\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); + UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +DEFUN (ospf_log_adjacency_changes_detail, + ospf_log_adjacency_changes_detail_cmd, + "log-adjacency-changes detail", + "Log changes in adjacency state\n" + "Log all state changes\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); + SET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +DEFUN (no_ospf_log_adjacency_changes, + no_ospf_log_adjacency_changes_cmd, + "no log-adjacency-changes", + NO_STR + "Log changes in adjacency state\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); + UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES); + return CMD_SUCCESS; +} + +DEFUN (no_ospf_log_adjacency_changes_detail, + no_ospf_log_adjacency_changes_detail_cmd, + "no log-adjacency-changes detail", + NO_STR + "Log changes in adjacency state\n" + "Log all state changes\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + UNSET_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL); + return CMD_SUCCESS; +} + +DEFUN (ospf_compatible_rfc1583, + ospf_compatible_rfc1583_cmd, + "compatible rfc1583", + "OSPF compatibility list\n" + "compatible with RFC 1583\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (!CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { + SET_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE); + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + } + return CMD_SUCCESS; +} + +DEFUN (no_ospf_compatible_rfc1583, + no_ospf_compatible_rfc1583_cmd, + "no compatible rfc1583", + NO_STR + "OSPF compatibility list\n" + "compatible with RFC 1583\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { + UNSET_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE); + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + } + return CMD_SUCCESS; +} + +ALIAS(ospf_compatible_rfc1583, ospf_rfc1583_flag_cmd, + "ospf rfc1583compatibility", + "OSPF specific commands\n" + "Enable the RFC1583Compatibility flag\n") + +ALIAS(no_ospf_compatible_rfc1583, no_ospf_rfc1583_flag_cmd, + "no ospf rfc1583compatibility", NO_STR + "OSPF specific commands\n" + "Disable the RFC1583Compatibility flag\n") + +static void ospf_table_reinstall_routes(struct ospf *ospf, + struct route_table *rt) +{ + struct route_node *rn; + + if (!rt) + return; + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + struct ospf_route *or; + + or = rn->info; + if (!or) + continue; + + if (or->type == OSPF_DESTINATION_NETWORK) + ospf_zebra_add(ospf, (struct prefix_ipv4 *)&rn->p, or); + else if (or->type == OSPF_DESTINATION_DISCARD) + ospf_zebra_add_discard(ospf, + (struct prefix_ipv4 *)&rn->p); + } +} + +static void ospf_reinstall_routes(struct ospf *ospf) +{ + ospf_table_reinstall_routes(ospf, ospf->new_table); + ospf_table_reinstall_routes(ospf, ospf->new_external_route); +} + +DEFPY (ospf_send_extra_data, + ospf_send_extra_data_cmd, + "[no] ospf send-extra-data zebra", + NO_STR + OSPF_STR + "Extra data to Zebra for display/use\n" + "To zebra\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (no && CHECK_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA)) { + UNSET_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA); + ospf_reinstall_routes(ospf); + } else if (!CHECK_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA)) { + SET_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA); + ospf_reinstall_routes(ospf); + } + + return CMD_SUCCESS; +} + +static int ospf_timers_spf_set(struct vty *vty, unsigned int delay, + unsigned int hold, unsigned int max) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->spf_delay = delay; + ospf->spf_holdtime = hold; + ospf->spf_max_holdtime = max; + + return CMD_SUCCESS; +} + +DEFUN (ospf_timers_min_ls_interval, + ospf_timers_min_ls_interval_cmd, + "timers throttle lsa all (0-5000)", + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "LSA delay between transmissions\n" + "All LSA types\n" + "Delay (msec) between sending LSAs\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 4; + unsigned int interval; + + if (argc < 5) { + vty_out(vty, "Insufficient arguments\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + interval = strtoul(argv[idx_number]->arg, NULL, 10); + + ospf->min_ls_interval = interval; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_timers_min_ls_interval, + no_ospf_timers_min_ls_interval_cmd, + "no timers throttle lsa all [(0-5000)]", + NO_STR + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "LSA delay between transmissions\n" + "All LSA types\n" + "Delay (msec) between sending LSAs\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + ospf->min_ls_interval = OSPF_MIN_LS_INTERVAL; + + return CMD_SUCCESS; +} + +DEFUN (ospf_timers_throttle_spf, + ospf_timers_throttle_spf_cmd, + "timers throttle spf (0-600000) (0-600000) (0-600000)", + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "OSPF SPF timers\n" + "Delay (msec) from first change received till SPF calculation\n" + "Initial hold time (msec) between consecutive SPF calculations\n" + "Maximum hold time (msec)\n") +{ + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + unsigned int delay, hold, max; + + if (argc < 6) { + vty_out(vty, "Insufficient arguments\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + delay = strtoul(argv[idx_number]->arg, NULL, 10); + hold = strtoul(argv[idx_number_2]->arg, NULL, 10); + max = strtoul(argv[idx_number_3]->arg, NULL, 10); + + return ospf_timers_spf_set(vty, delay, hold, max); +} + +DEFUN (no_ospf_timers_throttle_spf, + no_ospf_timers_throttle_spf_cmd, + "no timers throttle spf [(0-600000)(0-600000)(0-600000)]", + NO_STR + "Adjust routing timers\n" + "Throttling adaptive timer\n" + "OSPF SPF timers\n" + "Delay (msec) from first change received till SPF calculation\n" + "Initial hold time (msec) between consecutive SPF calculations\n" + "Maximum hold time (msec)\n") +{ + return ospf_timers_spf_set(vty, OSPF_SPF_DELAY_DEFAULT, + OSPF_SPF_HOLDTIME_DEFAULT, + OSPF_SPF_MAX_HOLDTIME_DEFAULT); +} + + +DEFUN (ospf_timers_lsa_min_arrival, + ospf_timers_lsa_min_arrival_cmd, + "timers lsa min-arrival (0-600000)", + "Adjust routing timers\n" + "OSPF LSA timers\n" + "Minimum delay in receiving new version of a LSA\n" + "Delay in milliseconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + ospf->min_ls_arrival = strtoul(argv[argc - 1]->arg, NULL, 10); + return CMD_SUCCESS; +} + +DEFUN (no_ospf_timers_lsa_min_arrival, + no_ospf_timers_lsa_min_arrival_cmd, + "no timers lsa min-arrival [(0-600000)]", + NO_STR + "Adjust routing timers\n" + "OSPF LSA timers\n" + "Minimum delay in receiving new version of a LSA\n" + "Delay in milliseconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + unsigned int minarrival; + + if (argc > 4) { + minarrival = strtoul(argv[argc - 1]->arg, NULL, 10); + + if (ospf->min_ls_arrival != minarrival + || minarrival == OSPF_MIN_LS_ARRIVAL) + return CMD_SUCCESS; + } + + ospf->min_ls_arrival = OSPF_MIN_LS_ARRIVAL; + + return CMD_SUCCESS; +} + +DEFPY(ospf_neighbor, ospf_neighbor_cmd, + "[no] neighbor A.B.C.D$nbr_address [{priority (0-255)$priority | poll-interval (1-65535)$interval}]", + NO_STR + NEIGHBOR_STR + "Neighbor IP address\n" + "Neighbor Priority\n" + "Priority\n" + "Dead Neighbor Polling interval\n" + "Seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (no) + ospf_nbr_nbma_unset(ospf, nbr_address); + else { + ospf_nbr_nbma_set(ospf, nbr_address); + if (priority_str) + ospf_nbr_nbma_priority_set(ospf, nbr_address, priority); + + if (interval_str) + ospf_nbr_nbma_poll_interval_set(ospf, nbr_address, + interval); + } + + return CMD_SUCCESS; +} + +DEFUN (ospf_refresh_timer, + ospf_refresh_timer_cmd, + "refresh timer (10-1800)", + "Adjust refresh parameters\n" + "Set refresh timer\n" + "Timer value in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 2; + unsigned int interval; + + interval = strtoul(argv[idx_number]->arg, NULL, 10); + interval = (interval / OSPF_LSA_REFRESHER_GRANULARITY) + * OSPF_LSA_REFRESHER_GRANULARITY; + + ospf_timers_refresh_set(ospf, interval); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_refresh_timer, + no_ospf_refresh_timer_val_cmd, + "no refresh timer [(10-1800)]", + NO_STR + "Adjust refresh parameters\n" + "Unset refresh timer\n" + "Timer value in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 3; + unsigned int interval; + + if (argc == 1) { + interval = strtoul(argv[idx_number]->arg, NULL, 10); + + if (ospf->lsa_refresh_interval != interval + || interval == OSPF_LSA_REFRESH_INTERVAL_DEFAULT) + return CMD_SUCCESS; + } + + ospf_timers_refresh_unset(ospf); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_auto_cost_reference_bandwidth, + ospf_auto_cost_reference_bandwidth_cmd, + "auto-cost reference-bandwidth (1-4294967)", + "Calculate OSPF interface cost according to bandwidth\n" + "Use reference bandwidth method to assign OSPF cost\n" + "The reference bandwidth in terms of Mbits per second\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + int idx_number = 2; + uint32_t refbw; + struct interface *ifp; + + refbw = strtol(argv[idx_number]->arg, NULL, 10); + if (refbw < 1 || refbw > 4294967) { + vty_out(vty, "reference-bandwidth value is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* If reference bandwidth is changed. */ + if ((refbw) == ospf->ref_bandwidth) + return CMD_SUCCESS; + + ospf->ref_bandwidth = refbw; + FOR_ALL_INTERFACES (vrf, ifp) + ospf_if_recalculate_output_cost(ifp); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_auto_cost_reference_bandwidth, + no_ospf_auto_cost_reference_bandwidth_cmd, + "no auto-cost reference-bandwidth [(1-4294967)]", + NO_STR + "Calculate OSPF interface cost according to bandwidth\n" + "Use reference bandwidth method to assign OSPF cost\n" + "The reference bandwidth in terms of Mbits per second\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct interface *ifp; + + if (ospf->ref_bandwidth == OSPF_DEFAULT_REF_BANDWIDTH) + return CMD_SUCCESS; + + ospf->ref_bandwidth = OSPF_DEFAULT_REF_BANDWIDTH; + vty_out(vty, "%% OSPF: Reference bandwidth is changed.\n"); + vty_out(vty, + " Please ensure reference bandwidth is consistent across all routers\n"); + + FOR_ALL_INTERFACES (vrf, ifp) + ospf_if_recalculate_output_cost(ifp); + + return CMD_SUCCESS; +} + +DEFUN (ospf_write_multiplier, + ospf_write_multiplier_cmd, + "ospf write-multiplier (1-100)", + "OSPF specific commands\n" + "Write multiplier\n" + "Maximum number of interface serviced per write\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number; + uint32_t write_oi_count; + + if (argc == 3) + idx_number = 2; + else + idx_number = 1; + + write_oi_count = strtol(argv[idx_number]->arg, NULL, 10); + if (write_oi_count < 1 || write_oi_count > 100) { + vty_out(vty, "write-multiplier value is invalid\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf->write_oi_count = write_oi_count; + return CMD_SUCCESS; +} + +ALIAS(ospf_write_multiplier, write_multiplier_cmd, "write-multiplier (1-100)", + "Write multiplier\n" + "Maximum number of interface serviced per write\n") + +DEFUN (no_ospf_write_multiplier, + no_ospf_write_multiplier_cmd, + "no ospf write-multiplier (1-100)", + NO_STR + "OSPF specific commands\n" + "Write multiplier\n" + "Maximum number of interface serviced per write\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->write_oi_count = OSPF_WRITE_INTERFACE_COUNT_DEFAULT; + return CMD_SUCCESS; +} + +ALIAS(no_ospf_write_multiplier, no_write_multiplier_cmd, + "no write-multiplier [(1-100)]", NO_STR + "Write multiplier\n" + "Maximum number of interface serviced per write\n") + +DEFUN(ospf_ti_lfa, ospf_ti_lfa_cmd, "fast-reroute ti-lfa [node-protection]", + "Fast Reroute for MPLS and IP resilience\n" + "Topology Independent LFA (Loop-Free Alternate)\n" + "TI-LFA node protection (default is link protection)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->ti_lfa_enabled = true; + + if (argc == 3) + ospf->ti_lfa_protection_type = OSPF_TI_LFA_NODE_PROTECTION; + else + ospf->ti_lfa_protection_type = OSPF_TI_LFA_LINK_PROTECTION; + + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + + return CMD_SUCCESS; +} + +DEFUN(no_ospf_ti_lfa, no_ospf_ti_lfa_cmd, + "no fast-reroute ti-lfa [node-protection]", + NO_STR + "Fast Reroute for MPLS and IP resilience\n" + "Topology Independent LFA (Loop-Free Alternate)\n" + "TI-LFA node protection (default is link protection)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->ti_lfa_enabled = false; + + ospf->ti_lfa_protection_type = OSPF_TI_LFA_UNDEFINED_PROTECTION; + + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + + return CMD_SUCCESS; +} + +static void ospf_maxpath_set(struct vty *vty, struct ospf *ospf, uint16_t paths) +{ + if (ospf->max_multipath == paths) + return; + + ospf->max_multipath = paths; + + /* Send deletion notification to zebra to delete all + * ospf specific routes and reinitiat SPF to reflect + * the new max multipath. + */ + ospf_restart_spf(ospf); +} + +/* Ospf Maximum multiple paths config support */ +DEFUN (ospf_max_multipath, + ospf_max_multipath_cmd, + "maximum-paths " CMD_RANGE_STR(1, MULTIPATH_NUM), + "Max no of multiple paths for ECMP support\n" + "Number of paths\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 1; + uint16_t maxpaths; + + maxpaths = strtol(argv[idx_number]->arg, NULL, 10); + + ospf_maxpath_set(vty, ospf, maxpaths); + return CMD_SUCCESS; +} + +DEFUN (no_ospf_max_multipath, + no_ospf_max_multipath_cmd, + "no maximum-paths [" CMD_RANGE_STR(1, MULTIPATH_NUM)"]", + NO_STR + "Max no of multiple paths for ECMP support\n" + "Number of paths\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + uint16_t maxpaths = MULTIPATH_NUM; + + ospf_maxpath_set(vty, ospf, maxpaths); + return CMD_SUCCESS; +} + +static const char *const ospf_abr_type_descr_str[] = { + "Unknown", "Standard (RFC2328)", "Alternative IBM", + "Alternative Cisco", "Alternative Shortcut" +}; + +static const char *const ospf_shortcut_mode_descr_str[] = { + "Default", "Enabled", "Disabled" +}; + +static void show_ip_ospf_area(struct vty *vty, struct ospf_area *area, + json_object *json_areas, bool use_json) +{ + json_object *json_area = NULL; + char buf[PREFIX_STRLEN]; + + if (use_json) + json_area = json_object_new_object(); + + /* Show Area ID. */ + if (!use_json) + vty_out(vty, " Area ID: %pI4", &area->area_id); + + /* Show Area type/mode. */ + if (OSPF_IS_AREA_BACKBONE(area)) { + if (use_json) + json_object_boolean_true_add(json_area, "backbone"); + else + vty_out(vty, " (Backbone)\n"); + } else { + if (use_json) { + if (area->external_routing == OSPF_AREA_STUB) { + if (area->no_summary) + json_object_boolean_true_add( + json_area, "stubNoSummary"); + if (area->shortcut_configured) + json_object_boolean_true_add( + json_area, "stubShortcut"); + } else if (area->external_routing == OSPF_AREA_NSSA) { + if (area->no_summary) + json_object_boolean_true_add( + json_area, "nssaNoSummary"); + if (area->shortcut_configured) + json_object_boolean_true_add( + json_area, "nssaShortcut"); + } + + json_object_string_add( + json_area, "shortcuttingMode", + ospf_shortcut_mode_descr_str + [area->shortcut_configured]); + if (area->shortcut_capability) + json_object_boolean_true_add(json_area, + "sBitConcensus"); + } else { + if (area->external_routing == OSPF_AREA_STUB) + vty_out(vty, " (Stub%s%s)", + area->no_summary ? ", no summary" : "", + area->shortcut_configured ? "; " : ""); + else if (area->external_routing == OSPF_AREA_NSSA) + vty_out(vty, " (NSSA%s%s)", + area->no_summary ? ", no summary" : "", + area->shortcut_configured ? "; " : ""); + + vty_out(vty, "\n"); + vty_out(vty, " Shortcutting mode: %s", + ospf_shortcut_mode_descr_str + [area->shortcut_configured]); + vty_out(vty, ", S-bit consensus: %s\n", + area->shortcut_capability ? "ok" : "no"); + } + } + + /* Show number of interfaces */ + if (use_json) { + json_object_int_add(json_area, "areaIfTotalCounter", + listcount(area->oiflist)); + json_object_int_add(json_area, "areaIfActiveCounter", + area->act_ints); + } else + vty_out(vty, + " Number of interfaces in this area: Total: %d, Active: %d\n", + listcount(area->oiflist), area->act_ints); + + if (area->external_routing == OSPF_AREA_NSSA) { + if (use_json) { + json_object_boolean_true_add(json_area, "nssa"); + if (!IS_OSPF_ABR(area->ospf)) + json_object_boolean_false_add(json_area, "abr"); + else if (area->NSSATranslatorState) { + json_object_boolean_true_add(json_area, "abr"); + if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_CANDIDATE) + json_object_boolean_true_add( + json_area, + "nssaTranslatorElected"); + else if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_ALWAYS) + json_object_boolean_true_add( + json_area, + "nssaTranslatorAlways"); + else + json_object_boolean_true_add( + json_area, + "nssaTranslatorNever"); + } else { + json_object_boolean_true_add(json_area, "abr"); + if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_CANDIDATE) + json_object_boolean_false_add( + json_area, + "nssaTranslatorElected"); + else + json_object_boolean_true_add( + json_area, + "nssaTranslatorNever"); + } + } else { + vty_out(vty, + " It is an NSSA configuration.\n Elected NSSA/ABR performs type-7/type-5 LSA translation.\n"); + if (!IS_OSPF_ABR(area->ospf)) + vty_out(vty, + " It is not ABR, therefore not Translator.\n"); + else if (area->NSSATranslatorState) { + vty_out(vty, " We are an ABR and "); + if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_CANDIDATE) + vty_out(vty, + "the NSSA Elected Translator.\n"); + else if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_ALWAYS) + vty_out(vty, + "always an NSSA Translator.\n"); + else + vty_out(vty, + "never an NSSA Translator.\n"); + } else { + vty_out(vty, " We are an ABR, but "); + if (area->NSSATranslatorRole + == OSPF_NSSA_ROLE_CANDIDATE) + vty_out(vty, + "not the NSSA Elected Translator.\n"); + else + vty_out(vty, + "never an NSSA Translator.\n"); + } + } + } + + /* Stub-router state for this area */ + if (CHECK_FLAG(area->stub_router_state, OSPF_AREA_IS_STUB_ROUTED)) { + char timebuf[OSPF_TIME_DUMP_SIZE]; + + if (use_json) { + json_object_boolean_true_add( + json_area, "originStubMaxDistRouterLsa"); + if (CHECK_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED)) + json_object_boolean_true_add( + json_area, "indefiniteActiveAdmin"); + if (area->t_stub_router) { + long time_store; + time_store = + monotime_until( + &area->t_stub_router->u.sands, + NULL) + / 1000LL; + json_object_int_add( + json_area, + "activeStartupRemainderMsecs", + time_store); + } + } else { + vty_out(vty, + " Originating stub / maximum-distance Router-LSA\n"); + if (CHECK_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED)) + vty_out(vty, + " Administratively activated (indefinitely)\n"); + if (area->t_stub_router) + vty_out(vty, + " Active from startup, %s remaining\n", + ospf_timer_dump(area->t_stub_router, + timebuf, + sizeof(timebuf))); + } + } + + if (use_json) { + /* Show number of fully adjacent neighbors. */ + json_object_int_add(json_area, "nbrFullAdjacentCounter", + area->full_nbrs); + + /* Show authentication type. */ + if (area->auth_type == OSPF_AUTH_NULL) + json_object_string_add(json_area, "authentication", + "authenticationNone"); + else if (area->auth_type == OSPF_AUTH_SIMPLE) + json_object_string_add(json_area, "authentication", + "authenticationSimplePassword"); + else if (area->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) + json_object_string_add(json_area, "authentication", + "authenticationMessageDigest"); + + if (!OSPF_IS_AREA_BACKBONE(area)) + json_object_int_add(json_area, + "virtualAdjacenciesPassingCounter", + area->full_vls); + + /* Show SPF calculation times. */ + json_object_int_add(json_area, "spfExecutedCounter", + area->spf_calculation); + json_object_int_add(json_area, "lsaNumber", area->lsdb->total); + json_object_int_add( + json_area, "lsaRouterNumber", + ospf_lsdb_count(area->lsdb, OSPF_ROUTER_LSA)); + json_object_int_add( + json_area, "lsaRouterChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_ROUTER_LSA)); + json_object_int_add( + json_area, "lsaNetworkNumber", + ospf_lsdb_count(area->lsdb, OSPF_NETWORK_LSA)); + json_object_int_add( + json_area, "lsaNetworkChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_NETWORK_LSA)); + json_object_int_add( + json_area, "lsaSummaryNumber", + ospf_lsdb_count(area->lsdb, OSPF_SUMMARY_LSA)); + json_object_int_add( + json_area, "lsaSummaryChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_SUMMARY_LSA)); + json_object_int_add( + json_area, "lsaAsbrNumber", + ospf_lsdb_count(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); + json_object_int_add( + json_area, "lsaAsbrChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); + json_object_int_add( + json_area, "lsaNssaNumber", + ospf_lsdb_count(area->lsdb, OSPF_AS_NSSA_LSA)); + json_object_int_add( + json_area, "lsaNssaChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_AS_NSSA_LSA)); + } else { + /* Show number of fully adjacent neighbors. */ + vty_out(vty, + " Number of fully adjacent neighbors in this area: %d\n", + area->full_nbrs); + + /* Show authentication type. */ + vty_out(vty, " Area has "); + if (area->auth_type == OSPF_AUTH_NULL) + vty_out(vty, "no authentication\n"); + else if (area->auth_type == OSPF_AUTH_SIMPLE) + vty_out(vty, "simple password authentication\n"); + else if (area->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) + vty_out(vty, "message digest authentication\n"); + + if (!OSPF_IS_AREA_BACKBONE(area)) + vty_out(vty, + " Number of full virtual adjacencies going through this area: %d\n", + area->full_vls); + + /* Show SPF calculation times. */ + vty_out(vty, " SPF algorithm executed %d times\n", + area->spf_calculation); + + /* Show number of LSA. */ + vty_out(vty, " Number of LSA %ld\n", area->lsdb->total); + vty_out(vty, + " Number of router LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_ROUTER_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_ROUTER_LSA)); + vty_out(vty, + " Number of network LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_NETWORK_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_NETWORK_LSA)); + vty_out(vty, + " Number of summary LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_SUMMARY_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_SUMMARY_LSA)); + vty_out(vty, + " Number of ASBR summary LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_ASBR_SUMMARY_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_ASBR_SUMMARY_LSA)); + vty_out(vty, " Number of NSSA LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_AS_NSSA_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_AS_NSSA_LSA)); + } + + if (use_json) { + json_object_int_add( + json_area, "lsaOpaqueLinkNumber", + ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_LINK_LSA)); + json_object_int_add( + json_area, "lsaOpaqueLinkChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_LINK_LSA)); + json_object_int_add( + json_area, "lsaOpaqueAreaNumber", + ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_AREA_LSA)); + json_object_int_add( + json_area, "lsaOpaqueAreaChecksum", + ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_AREA_LSA)); + } else { + vty_out(vty, + " Number of opaque link LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_LINK_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_LINK_LSA)); + vty_out(vty, + " Number of opaque area LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(area->lsdb, OSPF_OPAQUE_AREA_LSA), + ospf_lsdb_checksum(area->lsdb, OSPF_OPAQUE_AREA_LSA)); + } + + if (area->fr_info.configured) { + if (use_json) + json_object_string_add(json_area, "areaFloodReduction", + "configured"); + else + vty_out(vty, " Flood Reduction is configured.\n"); + } + + if (area->fr_info.enabled) { + if (use_json) { + json_object_boolean_true_add( + json_area, "areaFloodReductionEnabled"); + if (area->fr_info.router_lsas_recv_dc_bit) + json_object_boolean_true_add( + json_area, "lsasRecvDCbitSet"); + if (area->fr_info.area_ind_lsa_recvd) + json_object_string_add(json_area, + "areaIndicationLsaRecv", + "received"); + if (area->fr_info.indication_lsa_self) + json_object_string_addf( + json_area, "areaIndicationLsa", "%pI4", + &area->fr_info.indication_lsa_self->data + ->id); + } else { + vty_out(vty, " Flood Reduction is enabled.\n"); + vty_out(vty, " No of LSAs rcv'd with DC bit set %d\n", + area->fr_info.router_lsas_recv_dc_bit); + if (area->fr_info.area_ind_lsa_recvd) + vty_out(vty, " Ind LSA by other abr.\n"); + if (area->fr_info.indication_lsa_self) + vty_out(vty, " Ind LSA generated %pI4\n", + &area->fr_info.indication_lsa_self->data + ->id); + } + } + + if (use_json) + json_object_object_add(json_areas, + inet_ntop(AF_INET, &area->area_id, + buf, sizeof(buf)), + json_area); + else + vty_out(vty, "\n"); +} + +static int show_ip_ospf_common(struct vty *vty, struct ospf *ospf, + json_object *json, uint8_t use_vrf) +{ + struct listnode *node, *nnode; + struct ospf_area *area; + struct timeval result; + char timebuf[OSPF_TIME_DUMP_SIZE]; + json_object *json_vrf = NULL; + json_object *json_areas = NULL; + + if (json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + json_areas = json_object_new_object(); + } + + if (ospf->instance) { + if (json) { + json_object_int_add(json, "ospfInstance", + ospf->instance); + } else { + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + /* Show Router ID. */ + if (json) { + json_object_string_addf(json_vrf, "routerId", "%pI4", + &ospf->router_id); + } else { + vty_out(vty, " OSPF Routing Process, Router ID: %pI4\n", + &ospf->router_id); + } + + /* Graceful shutdown */ + if (ospf->t_deferred_shutdown) { + if (json) { + long time_store; + time_store = + monotime_until( + &ospf->t_deferred_shutdown->u.sands, + NULL) + / 1000LL; + json_object_int_add(json_vrf, "deferredShutdownMsecs", + time_store); + } else { + vty_out(vty, + " Deferred shutdown in progress, %s remaining\n", + ospf_timer_dump(ospf->t_deferred_shutdown, + timebuf, sizeof(timebuf))); + } + } + + /* Show capability. */ + if (json) { + json_object_boolean_true_add(json_vrf, "tosRoutesOnly"); + json_object_boolean_true_add(json_vrf, "rfc2328Conform"); + if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) { + json_object_boolean_true_add(json_vrf, + "rfc1583Compatibility"); + } + } else { + vty_out(vty, " Supports only single TOS (TOS0) routes\n"); + vty_out(vty, " This implementation conforms to RFC2328\n"); + vty_out(vty, " RFC1583Compatibility flag is %s\n", + CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE) + ? "enabled" + : "disabled"); + } + + if (json) { + if (CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE)) { + json_object_boolean_true_add(json_vrf, "opaqueCapable"); + } + } else { + vty_out(vty, " OpaqueCapability flag is %s\n", + CHECK_FLAG(ospf->config, OSPF_OPAQUE_CAPABLE) + ? "enabled" + : "disabled"); + } + + /* Show stub-router configuration */ + if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED + || ospf->stub_router_shutdown_time + != OSPF_STUB_ROUTER_UNCONFIGURED) { + if (json) { + json_object_boolean_true_add(json_vrf, + "stubAdvertisement"); + if (ospf->stub_router_startup_time + != OSPF_STUB_ROUTER_UNCONFIGURED) + json_object_int_add( + json_vrf, "postStartEnabledSecs", + ospf->stub_router_startup_time); + if (ospf->stub_router_shutdown_time + != OSPF_STUB_ROUTER_UNCONFIGURED) + json_object_int_add( + json_vrf, "preShutdownEnabledSecs", + ospf->stub_router_shutdown_time); + } else { + vty_out(vty, + " Stub router advertisement is configured\n"); + if (ospf->stub_router_startup_time + != OSPF_STUB_ROUTER_UNCONFIGURED) + vty_out(vty, + " Enabled for %us after start-up\n", + ospf->stub_router_startup_time); + if (ospf->stub_router_shutdown_time + != OSPF_STUB_ROUTER_UNCONFIGURED) + vty_out(vty, + " Enabled for %us prior to full shutdown\n", + ospf->stub_router_shutdown_time); + } + } + + /* Show SPF timers. */ + if (json) { + json_object_int_add(json_vrf, "spfScheduleDelayMsecs", + ospf->spf_delay); + json_object_int_add(json_vrf, "holdtimeMinMsecs", + ospf->spf_holdtime); + json_object_int_add(json_vrf, "holdtimeMaxMsecs", + ospf->spf_max_holdtime); + json_object_int_add(json_vrf, "holdtimeMultplier", + ospf->spf_hold_multiplier); + } else { + vty_out(vty, + " Initial SPF scheduling delay %d millisec(s)\n" + " Minimum hold time between consecutive SPFs %d millisec(s)\n" + " Maximum hold time between consecutive SPFs %d millisec(s)\n" + " Hold time multiplier is currently %d\n", + ospf->spf_delay, ospf->spf_holdtime, + ospf->spf_max_holdtime, ospf->spf_hold_multiplier); + } + + if (json) { + if (ospf->ts_spf.tv_sec || ospf->ts_spf.tv_usec) { + long time_store = 0; + + time_store = + monotime_since(&ospf->ts_spf, NULL) / 1000LL; + json_object_int_add(json_vrf, "spfLastExecutedMsecs", + time_store); + + time_store = (1000 * ospf->ts_spf_duration.tv_sec) + + (ospf->ts_spf_duration.tv_usec / 1000); + json_object_int_add(json_vrf, "spfLastDurationMsecs", + time_store); + } else + json_object_boolean_true_add(json_vrf, "spfHasNotRun"); + } else { + vty_out(vty, " SPF algorithm "); + if (ospf->ts_spf.tv_sec || ospf->ts_spf.tv_usec) { + monotime_since(&ospf->ts_spf, &result); + vty_out(vty, "last executed %s ago\n", + ospf_timeval_dump(&result, timebuf, + sizeof(timebuf))); + vty_out(vty, " Last SPF duration %s\n", + ospf_timeval_dump(&ospf->ts_spf_duration, + timebuf, sizeof(timebuf))); + } else + vty_out(vty, "has not been run\n"); + } + + if (json) { + if (ospf->t_spf_calc) { + long time_store; + time_store = + monotime_until(&ospf->t_spf_calc->u.sands, NULL) + / 1000LL; + json_object_int_add(json_vrf, "spfTimerDueInMsecs", + time_store); + } + + json_object_int_add(json_vrf, "lsaMinIntervalMsecs", + ospf->min_ls_interval); + json_object_int_add(json_vrf, "lsaMinArrivalMsecs", + ospf->min_ls_arrival); + /* Show write multiplier values */ + json_object_int_add(json_vrf, "writeMultiplier", + ospf->write_oi_count); + /* Show refresh parameters. */ + json_object_int_add(json_vrf, "refreshTimerMsecs", + ospf->lsa_refresh_interval * 1000); + + /* show max multipath */ + json_object_int_add(json_vrf, "maximumPaths", + ospf->max_multipath); + + /* show administrative distance */ + json_object_int_add(json_vrf, "preference", + ospf->distance_all + ? ospf->distance_all + : ZEBRA_OSPF_DISTANCE_DEFAULT); + } else { + vty_out(vty, " SPF timer %s%s\n", + (ospf->t_spf_calc ? "due in " : "is "), + ospf_timer_dump(ospf->t_spf_calc, timebuf, + sizeof(timebuf))); + + vty_out(vty, " LSA minimum interval %d msecs\n", + ospf->min_ls_interval); + vty_out(vty, " LSA minimum arrival %d msecs\n", + ospf->min_ls_arrival); + + /* Show write multiplier values */ + vty_out(vty, " Write Multiplier set to %d \n", + ospf->write_oi_count); + + /* Show refresh parameters. */ + vty_out(vty, " Refresh timer %d secs\n", + ospf->lsa_refresh_interval); + + /* show max multipath */ + vty_out(vty, " Maximum multiple paths(ECMP) supported %d\n", + ospf->max_multipath); + + /* show administrative distance */ + vty_out(vty, " Administrative distance %u\n", + ospf->distance_all ? ospf->distance_all + : ZEBRA_OSPF_DISTANCE_DEFAULT); + } + + if (ospf->fr_configured) { + if (json) + json_object_string_add(json_vrf, "floodReduction", + "configured"); + else + vty_out(vty, " Flood Reduction is configured.\n"); + } + + /* Show ABR/ASBR flags. */ + if (CHECK_FLAG(ospf->flags, OSPF_FLAG_ABR)) { + if (json) + json_object_string_add( + json_vrf, "abrType", + ospf_abr_type_descr_str[ospf->abr_type]); + else + vty_out(vty, + " This router is an ABR, ABR type is: %s\n", + ospf_abr_type_descr_str[ospf->abr_type]); + } + if (CHECK_FLAG(ospf->flags, OSPF_FLAG_ASBR)) { + if (json) + json_object_string_add( + json_vrf, "asbrRouter", + "injectingExternalRoutingInformation"); + else + vty_out(vty, + " This router is an ASBR (injecting external routing information)\n"); + } + + /* Show Number of AS-external-LSAs. */ + if (json) { + json_object_int_add( + json_vrf, "lsaExternalCounter", + ospf_lsdb_count(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); + json_object_int_add( + json_vrf, "lsaExternalChecksum", + ospf_lsdb_checksum(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); + } else { + vty_out(vty, + " Number of external LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(ospf->lsdb, OSPF_AS_EXTERNAL_LSA), + ospf_lsdb_checksum(ospf->lsdb, OSPF_AS_EXTERNAL_LSA)); + } + + if (json) { + json_object_int_add( + json_vrf, "lsaAsopaqueCounter", + ospf_lsdb_count(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); + json_object_int_add( + json_vrf, "lsaAsOpaqueChecksum", + ospf_lsdb_checksum(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); + } else { + vty_out(vty, + " Number of opaque AS LSA %ld. Checksum Sum 0x%08x\n", + ospf_lsdb_count(ospf->lsdb, OSPF_OPAQUE_AS_LSA), + ospf_lsdb_checksum(ospf->lsdb, OSPF_OPAQUE_AS_LSA)); + } + + /* Show number of areas attached. */ + if (json) + json_object_int_add(json_vrf, "attachedAreaCounter", + listcount(ospf->areas)); + else + vty_out(vty, " Number of areas attached to this router: %d\n", + listcount(ospf->areas)); + + if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES)) { + if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) { + if (json) + json_object_boolean_true_add( + json_vrf, "adjacencyChangesLoggedAll"); + else + vty_out(vty, + " All adjacency changes are logged\n"); + } else { + if (json) + json_object_boolean_true_add( + json_vrf, "adjacencyChangesLogged"); + else + vty_out(vty, " Adjacency changes are logged\n"); + } + } + + /* show LDP-Sync status */ + ospf_ldp_sync_show_info(vty, ospf, json_vrf, json ? 1 : 0); + + /* Socket buffer sizes */ + if (json) { + if (ospf->recv_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE) + json_object_int_add(json_vrf, "recvSockBufsize", + ospf->recv_sock_bufsize); + if (ospf->send_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE) + json_object_int_add(json_vrf, "sendSockBufsize", + ospf->send_sock_bufsize); + } else { + if (ospf->recv_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE) + vty_out(vty, " Receive socket bufsize: %u\n", + ospf->recv_sock_bufsize); + if (ospf->send_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE) + vty_out(vty, " Send socket bufsize: %u\n", + ospf->send_sock_bufsize); + } + + /* Show each area status. */ + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) + show_ip_ospf_area(vty, area, json_areas, json ? 1 : 0); + + if (json) { + if (use_vrf) { + json_object_object_add(json_vrf, "areas", json_areas); + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else { + json_object_object_add(json, "areas", json_areas); + } + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf, + show_ip_ospf_cmd, + "show ip ospf [vrf ] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + json_object *json = NULL; + uint8_t use_vrf = 0; + + if (listcount(om->ospf) == 0) + return CMD_SUCCESS; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + bool ospf_output = false; + + use_vrf = 1; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ospf_output = true; + ret = show_ip_ospf_common(vty, ospf, json, + use_vrf); + } + if (uj) + vty_json(vty, json); + else if (!ospf_output) + vty_out(vty, "%% OSPF is not enabled\n"); + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if ((ospf == NULL) || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + } else { + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + /* Display default ospf (instance 0) info */ + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + } + + if (ospf) { + show_ip_ospf_common(vty, ospf, json, use_vrf); + if (uj) + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + } + + if (uj) + json_object_free(json); + + return ret; +} + +DEFUN (show_ip_ospf_instance, + show_ip_ospf_instance_cmd, + "show ip ospf (1-65535) [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + JSON_STR) +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + int ret = CMD_SUCCESS; + json_object *json = NULL; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (uj) + json = json_object_new_object(); + + ret = show_ip_ospf_common(vty, ospf, json, 0); + + if (uj) + vty_json(vty, json); + + return ret; +} + +static void ospf_interface_auth_show(struct vty *vty, struct ospf_interface *oi, + json_object *json, bool use_json) +{ + int auth_type; + + auth_type = OSPF_IF_PARAM(oi, auth_type); + + switch (auth_type) { + case OSPF_AUTH_NULL: + if (use_json) + json_object_string_add(json, "authentication", + "authenticationNone"); + else + vty_out(vty, " Authentication NULL is enabled\n"); + break; + case OSPF_AUTH_SIMPLE: { + if (use_json) + json_object_string_add(json, "authentication", + "authenticationSimplePassword"); + else + vty_out(vty, + " Simple password authentication enabled\n"); + break; + } + case OSPF_AUTH_CRYPTOGRAPHIC: { + struct crypt_key *ckey; + + if (OSPF_IF_PARAM(oi, keychain_name)) { + if (use_json) { + json_object_string_add(json, "authentication", + "authenticationKeyChain"); + json_object_string_add(json, "keychain", + OSPF_IF_PARAM(oi, keychain_name)); + } else { + vty_out(vty, + " Cryptographic authentication enabled\n"); + struct keychain *keychain = keychain_lookup(OSPF_IF_PARAM(oi, keychain_name)); + + if (keychain) { + struct key *key = key_lookup_for_send(keychain); + + if (key) { + vty_out(vty, " Sending SA: Key %u, Algorithm %s - key chain %s\n", + key->index, keychain_get_algo_name_by_id(key->hash_algo), + OSPF_IF_PARAM(oi, keychain_name)); + } + } + } + } else { + if (list_isempty(OSPF_IF_PARAM(oi, auth_crypt))) + return; + + ckey = listgetdata(listtail(OSPF_IF_PARAM(oi, auth_crypt))); + if (ckey) { + if (use_json) { + json_object_string_add(json, "authentication", + "authenticationMessageDigest"); + } else { + vty_out(vty, + " Cryptographic authentication enabled\n"); + vty_out(vty, " Algorithm:MD5\n"); + } + } + } + break; + } + default: + break; + } +} + +static void show_ip_ospf_interface_sub(struct vty *vty, struct ospf *ospf, + struct interface *ifp, + json_object *json_interface_sub, + bool use_json) +{ + int is_up; + struct ospf_neighbor *nbr; + struct route_node *rn; + uint32_t bandwidth = ifp->bandwidth ? ifp->bandwidth : ifp->speed; + struct ospf_if_params *params; + json_object *json_ois = NULL; + json_object *json_oi = NULL; + + /* Is interface up? */ + if (use_json) { + is_up = if_is_operative(ifp); + if (is_up) + json_object_boolean_true_add(json_interface_sub, + "ifUp"); + else + json_object_boolean_false_add(json_interface_sub, + "ifDown"); + + json_object_int_add(json_interface_sub, "ifIndex", + ifp->ifindex); + json_object_int_add(json_interface_sub, "mtuBytes", ifp->mtu); + json_object_int_add(json_interface_sub, "bandwidthMbit", + bandwidth); + json_object_string_add(json_interface_sub, "ifFlags", + if_flag_dump(ifp->flags)); + } else { + vty_out(vty, "%s is %s\n", ifp->name, + ((is_up = if_is_operative(ifp)) ? "up" : "down")); + vty_out(vty, " ifindex %u, MTU %u bytes, BW %u Mbit %s\n", + ifp->ifindex, ifp->mtu, bandwidth, + if_flag_dump(ifp->flags)); + } + + /* Is interface OSPF enabled? */ + if (use_json) { + if (ospf_oi_count(ifp) == 0) { + json_object_boolean_false_add(json_interface_sub, + "ospfEnabled"); + return; + } else if (!is_up) { + json_object_boolean_false_add(json_interface_sub, + "ospfRunning"); + return; + } else + json_object_boolean_true_add(json_interface_sub, + "ospfEnabled"); + } else { + if (ospf_oi_count(ifp) == 0) { + vty_out(vty, " OSPF not enabled on this interface\n"); + return; + } else if (!is_up) { + vty_out(vty, + " OSPF is enabled, but not running on this interface\n"); + return; + } + } + + if (use_json) { + json_ois = json_object_new_object(); + json_object_object_add(json_interface_sub, "interfaceIp", + json_ois); + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi == NULL) + continue; + +#if CONFDATE > 20240601 + CPP_NOTICE( + "Use all fields following ospfEnabled from interfaceIp hierarchy") +#endif + + if (use_json) + json_oi = json_object_new_object(); + + if (CHECK_FLAG(oi->connected->flags, ZEBRA_IFA_UNNUMBERED)) { + if (use_json) { + json_object_boolean_true_add(json_interface_sub, + "ifUnnumbered"); + json_object_boolean_true_add(json_oi, + "ifUnnumbered"); + } else + vty_out(vty, " This interface is UNNUMBERED,"); + } else { + struct in_addr dest; + const char *dstr; + + /* Show OSPF interface information. */ + if (use_json) { + json_object_string_addf( + json_interface_sub, "ipAddress", "%pI4", + &oi->address->u.prefix4); + json_object_int_add(json_interface_sub, + "ipAddressPrefixlen", + oi->address->prefixlen); + + json_object_string_addf( + json_oi, "ipAddress", "%pI4", + &oi->address->u.prefix4); + json_object_int_add(json_oi, + "ipAddressPrefixlen", + oi->address->prefixlen); + } else + vty_out(vty, " Internet Address %pFX,", + oi->address); + + /* For Vlinks, showing the peer address is + * probably more informative than the local + * interface that is being used */ + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) { + dstr = "Peer"; + dest = oi->vl_data->peer_addr; + } else if (CONNECTED_PEER(oi->connected) + && oi->connected->destination) { + dstr = "Peer"; + dest = oi->connected->destination->u.prefix4; + } else { + dstr = "Broadcast"; + dest.s_addr = ipv4_broadcast_addr( + oi->connected->address->u.prefix4.s_addr, + oi->connected->address->prefixlen); + } + + if (use_json) { + json_object_string_add(json_interface_sub, + "ospfIfType", dstr); + + json_object_string_add(json_oi, "ospfIfType", + dstr); + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) { + json_object_string_addf( + json_interface_sub, "vlinkPeer", + "%pI4", &dest); + + json_object_string_addf(json_oi, + "vlinkPeer", + "%pI4", &dest); + } else { + json_object_string_addf( + json_interface_sub, + "localIfUsed", "%pI4", &dest); + + json_object_string_addf(json_oi, + "localIfUsed", + "%pI4", &dest); + } + } else + vty_out(vty, " %s %pI4,", dstr, + &dest); + } + if (use_json) { + json_object_string_add(json_interface_sub, "area", + ospf_area_desc_string(oi->area)); + + json_object_string_add(json_oi, "area", + ospf_area_desc_string(oi->area)); + + if (OSPF_IF_PARAM(oi, mtu_ignore)) { + json_object_boolean_true_add( + json_oi, "mtuMismatchDetect"); + json_object_boolean_true_add( + json_interface_sub, + "mtuMismatchDetect"); + } + + json_object_string_addf(json_interface_sub, "routerId", + "%pI4", &ospf->router_id); + json_object_string_add(json_interface_sub, + "networkType", + ospf_network_type_str[oi->type]); + json_object_int_add(json_interface_sub, "cost", + oi->output_cost); + json_object_int_add(json_interface_sub, + "transmitDelaySecs", + OSPF_IF_PARAM(oi, transmit_delay)); + json_object_string_add(json_interface_sub, "state", + lookup_msg(ospf_ism_state_msg, + oi->state, NULL)); + json_object_int_add(json_interface_sub, "priority", + PRIORITY(oi)); + + json_object_string_addf(json_oi, "routerId", "%pI4", + &ospf->router_id); + json_object_string_add(json_oi, "networkType", + ospf_network_type_str[oi->type]); + json_object_int_add(json_oi, "cost", oi->output_cost); + json_object_int_add(json_oi, "transmitDelaySecs", + OSPF_IF_PARAM(oi, transmit_delay)); + json_object_string_add(json_oi, "state", + lookup_msg(ospf_ism_state_msg, + oi->state, NULL)); + json_object_int_add(json_oi, "priority", PRIORITY(oi)); + json_object_boolean_add( + json_interface_sub, "opaqueCapable", + OSPF_IF_PARAM(oi, opaque_capable)); + } else { + vty_out(vty, " Area %s\n", + ospf_area_desc_string(oi->area)); + + vty_out(vty, " MTU mismatch detection: %s\n", + OSPF_IF_PARAM(oi, mtu_ignore) ? "disabled" + : "enabled"); + + vty_out(vty, + " Router ID %pI4, Network Type %s, Cost: %d\n", + &ospf->router_id, + ospf_network_type_str[oi->type], + oi->output_cost); + + vty_out(vty, + " Transmit Delay is %d sec, State %s, Priority %d\n", + OSPF_IF_PARAM(oi, transmit_delay), + lookup_msg(ospf_ism_state_msg, oi->state, NULL), + PRIORITY(oi)); + if (!OSPF_IF_PARAM(oi, opaque_capable)) + vty_out(vty, + " Opaque LSA capability disabled on interface\n"); + } + + /* Show DR information. */ + if (DR(oi).s_addr == INADDR_ANY) { + if (!use_json) + vty_out(vty, + " No backup designated router on this network\n"); + } else { + nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi)); + if (nbr) { + if (use_json) { + json_object_string_addf( + json_interface_sub, "drId", + "%pI4", &nbr->router_id); + json_object_string_addf( + json_interface_sub, "drAddress", + "%pI4", + &nbr->address.u.prefix4); + + json_object_string_addf( + json_oi, "drId", "%pI4", + &nbr->router_id); + json_object_string_addf( + json_oi, "drAddress", "%pI4", + &nbr->address.u.prefix4); + } else { + vty_out(vty, + " Designated Router (ID) %pI4", + &nbr->router_id); + vty_out(vty, + " Interface Address %pFX\n", + &nbr->address); + } + } + nbr = NULL; + + nbr = ospf_nbr_lookup_by_addr(oi->nbrs, &BDR(oi)); + if (nbr == NULL) { + if (!use_json) + vty_out(vty, + " No backup designated router on this network\n"); + } else { + if (use_json) { + json_object_string_addf( + json_interface_sub, "bdrId", + "%pI4", &nbr->router_id); + json_object_string_addf( + json_interface_sub, + "bdrAddress", "%pI4", + &nbr->address.u.prefix4); + + json_object_string_addf( + json_oi, "bdrId", "%pI4", + &nbr->router_id); + json_object_string_addf( + json_oi, "bdrAddress", "%pI4", + &nbr->address.u.prefix4); + } else { + vty_out(vty, + " Backup Designated Router (ID) %pI4,", + &nbr->router_id); + vty_out(vty, " Interface Address %pI4\n", + &nbr->address.u.prefix4); + } + } + } + + /* Next network-LSA sequence number we'll use, if we're elected + * DR */ + if (oi->params + && ntohl(oi->params->network_lsa_seqnum) + != OSPF_INITIAL_SEQUENCE_NUMBER) { + if (use_json) { + json_object_int_add( + json_interface_sub, + "networkLsaSequence", + ntohl(oi->params->network_lsa_seqnum)); + + json_object_int_add( + json_oi, "networkLsaSequence", + ntohl(oi->params->network_lsa_seqnum)); + } else { + vty_out(vty, + " Saved Network-LSA sequence number 0x%x\n", + ntohl(oi->params->network_lsa_seqnum)); + } + } + + if (use_json) { + if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) + || OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { + if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) { + json_object_boolean_true_add( + json_interface_sub, + "mcastMemberOspfAllRouters"); + + json_object_boolean_true_add( + json_oi, + "mcastMemberOspfAllRouters"); + } + if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { + json_object_boolean_true_add( + json_interface_sub, + "mcastMemberOspfDesignatedRouters"); + + json_object_boolean_true_add( + json_oi, + "mcastMemberOspfDesignatedRouters"); + } + } + } else { + vty_out(vty, " Multicast group memberships:"); + if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS) + || OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) { + if (OI_MEMBER_CHECK(oi, MEMBER_ALLROUTERS)) + vty_out(vty, " OSPFAllRouters"); + if (OI_MEMBER_CHECK(oi, MEMBER_DROUTERS)) + vty_out(vty, " OSPFDesignatedRouters"); + } else + vty_out(vty, " "); + vty_out(vty, "\n"); + } + + if (use_json) { + if (OSPF_IF_PARAM(oi, fast_hello) == 0) { + json_object_int_add( + json_interface_sub, "timerMsecs", + OSPF_IF_PARAM(oi, v_hello) * 1000); + + json_object_int_add(json_oi, "timerMsecs", + OSPF_IF_PARAM(oi, v_hello) * + 1000); + } else { + json_object_int_add( + json_interface_sub, "timerMsecs", + 1000 / OSPF_IF_PARAM(oi, fast_hello)); + + json_object_int_add( + json_oi, "timerMsecs", + 1000 / OSPF_IF_PARAM(oi, fast_hello)); + } + json_object_int_add(json_interface_sub, "timerDeadSecs", + OSPF_IF_PARAM(oi, v_wait)); + json_object_int_add(json_interface_sub, "timerWaitSecs", + OSPF_IF_PARAM(oi, v_wait)); + json_object_int_add( + json_interface_sub, "timerRetransmitSecs", + OSPF_IF_PARAM(oi, retransmit_interval)); + + json_object_int_add(json_oi, "timerDeadSecs", + OSPF_IF_PARAM(oi, v_wait)); + json_object_int_add(json_oi, "timerWaitSecs", + OSPF_IF_PARAM(oi, v_wait)); + json_object_int_add( + json_oi, "timerRetransmitSecs", + OSPF_IF_PARAM(oi, retransmit_interval)); + } else { + vty_out(vty, " Timer intervals configured,"); + vty_out(vty, " Hello "); + if (OSPF_IF_PARAM(oi, fast_hello) == 0) + vty_out(vty, "%ds,", + OSPF_IF_PARAM(oi, v_hello)); + else + vty_out(vty, "%dms,", + 1000 / OSPF_IF_PARAM(oi, fast_hello)); + vty_out(vty, " Dead %ds, Wait %ds, Retransmit %d\n", + OSPF_IF_PARAM(oi, v_wait), + OSPF_IF_PARAM(oi, v_wait), + OSPF_IF_PARAM(oi, retransmit_interval)); + } + + if (OSPF_IF_PASSIVE_STATUS(oi) == OSPF_IF_ACTIVE) { + char timebuf[OSPF_TIME_DUMP_SIZE]; + if (use_json) { + long time_store = 0; + if (oi->t_hello) + time_store = + monotime_until( + &oi->t_hello->u.sands, + NULL) + / 1000LL; + json_object_int_add(json_interface_sub, + "timerHelloInMsecs", + time_store); + json_object_int_add(json_oi, + "timerHelloInMsecs", + time_store); + } else + vty_out(vty, " Hello due in %s\n", + ospf_timer_dump(oi->t_hello, timebuf, + sizeof(timebuf))); + } else /* passive-interface is set */ + { + if (use_json) { + json_object_boolean_true_add( + json_interface_sub, + "timerPassiveIface"); + + json_object_boolean_true_add( + json_oi, "timerPassiveIface"); + } else + vty_out(vty, + " No Hellos (Passive interface)\n"); + } + + if (use_json) { + json_object_int_add(json_interface_sub, "nbrCount", + ospf_nbr_count(oi, 0)); + json_object_int_add(json_interface_sub, + "nbrAdjacentCount", + ospf_nbr_count(oi, NSM_Full)); + + json_object_int_add(json_oi, "nbrCount", + ospf_nbr_count(oi, 0)); + json_object_int_add(json_oi, "nbrAdjacentCount", + ospf_nbr_count(oi, NSM_Full)); + } else + vty_out(vty, + " Neighbor Count is %d, Adjacent neighbor count is %d\n", + ospf_nbr_count(oi, 0), + ospf_nbr_count(oi, NSM_Full)); + + params = IF_DEF_PARAMS(ifp); + if (params && + OSPF_IF_PARAM_CONFIGURED(params, v_gr_hello_delay)) { + if (use_json) { + json_object_int_add(json_interface_sub, + "grHelloDelaySecs", + params->v_gr_hello_delay); + + json_object_int_add(json_oi, "grHelloDelaySecs", + params->v_gr_hello_delay); + } else + vty_out(vty, + " Graceful Restart hello delay: %us\n", + params->v_gr_hello_delay); + } + + ospf_interface_bfd_show(vty, ifp, json_interface_sub); + + if (use_json) { + json_object_boolean_add(json_interface_sub, + "prefixSuppression", + OSPF_IF_PARAM(oi, + prefix_suppression)); + json_object_boolean_add(json_oi, "prefixSuppression", + OSPF_IF_PARAM(oi, + prefix_suppression)); + } else { + if (OSPF_IF_PARAM(oi, prefix_suppression)) + vty_out(vty, + " Suppress advertisement of interface IP prefix\n"); + } + + /* OSPF Authentication information */ + ospf_interface_auth_show(vty, oi, json_interface_sub, use_json); + + ospf_interface_auth_show(vty, oi, json_oi, use_json); + + /* Point-to-Multipoint Interface options. */ + if (oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { + if (use_json) { + json_object_boolean_add(json_interface_sub, + "p2mpDelayReflood", + oi->p2mp_delay_reflood); + + json_object_boolean_add(json_oi, + "p2mpDelayReflood", + oi->p2mp_delay_reflood); + } else { + vty_out(vty, + " %sDelay reflooding LSAs received on P2MP interface\n", + oi->p2mp_delay_reflood ? "" : "Don't "); + } + if (use_json) { + json_object_boolean_add(json_interface_sub, + "p2mpNonBroadcast", + oi->p2mp_non_broadcast); + + json_object_boolean_add(json_oi, + "p2mpNonBroadcast", + oi->p2mp_non_broadcast); + } else { + vty_out(vty, + " P2MP interface does %ssupport broadcast\n", + oi->p2mp_non_broadcast ? "not " : ""); + } + } + + /* Add ospf_interface object to main json blob using SIP as key + */ + if (use_json) + json_object_object_addf(json_ois, json_oi, "%pI4", + &oi->address->u.prefix4); + + if (oi->nbr_filter) { + if (use_json) { + json_object_string_add(json_interface_sub, + "nbrFilterPrefixList", + prefix_list_name( + oi->nbr_filter)); + json_object_string_add(json_oi, + "nbrFilterPrefixList", + prefix_list_name( + oi->nbr_filter)); + } else + vty_out(vty, + " Neighbor filter prefix-list: %s\n", + prefix_list_name(oi->nbr_filter)); + } else { + if (use_json) { + json_object_string_add(json_interface_sub, + "nbrFilterPrefixList", + "N/A"); + json_object_string_add(json_oi, + "nbrFilterPrefixList", + "N/A"); + } + } + } +} + +static int show_ip_ospf_interface_common(struct vty *vty, struct ospf *ospf, + char *intf_name, uint8_t use_vrf, + json_object *json, bool use_json) +{ + struct interface *ifp; + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + json_object *json_vrf = NULL; + json_object *json_interface_sub = NULL, *json_interface = NULL; + + if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + json_interface = json_object_new_object(); + } + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (intf_name == NULL) { + /* Show All Interfaces.*/ + FOR_ALL_INTERFACES (vrf, ifp) { + if (ospf_oi_count(ifp)) { + if (use_json) { + json_interface_sub = + json_object_new_object(); + } + show_ip_ospf_interface_sub(vty, ospf, ifp, + json_interface_sub, + use_json); + + if (use_json) { + json_object_object_add( + json_interface, ifp->name, + json_interface_sub); + } + } + } + if (use_json) + json_object_object_add(json_vrf, "interfaces", + json_interface); + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(intf_name, ospf->vrf_id); + if (ifp == NULL) { + if (use_json) { + json_object_boolean_true_add(json_vrf, + "noSuchIface"); + json_object_free(json_interface); + } else + vty_out(vty, "No such interface name\n"); + } else { + if (use_json) + json_interface_sub = json_object_new_object(); + + show_ip_ospf_interface_sub( + vty, ospf, ifp, json_interface_sub, use_json); + + if (use_json) { + json_object_object_add(json_interface, + ifp->name, + json_interface_sub); + json_object_object_add(json_vrf, "interfaces", + json_interface); + } + } + } + + if (use_json) { + if (use_vrf) { + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +static void show_ip_ospf_interface_traffic_sub(struct vty *vty, + struct ospf_interface *oi, + json_object *json_interface_sub, + bool use_json) +{ + if (use_json) { + json_object_int_add(json_interface_sub, "ifIndex", + oi->ifp->ifindex); + json_object_int_add(json_interface_sub, "helloIn", + oi->hello_in); + json_object_int_add(json_interface_sub, "helloOut", + oi->hello_out); + json_object_int_add(json_interface_sub, "dbDescIn", + oi->db_desc_in); + json_object_int_add(json_interface_sub, "dbDescOut", + oi->db_desc_out); + json_object_int_add(json_interface_sub, "lsReqIn", + oi->ls_req_in); + json_object_int_add(json_interface_sub, "lsReqOut", + oi->ls_req_out); + json_object_int_add(json_interface_sub, "lsUpdIn", + oi->ls_upd_in); + json_object_int_add(json_interface_sub, "lsUpdOut", + oi->ls_upd_out); + json_object_int_add(json_interface_sub, "lsAckIn", + oi->ls_ack_in); + json_object_int_add(json_interface_sub, "lsAckOut", + oi->ls_ack_out); + json_object_int_add(json_interface_sub, "packetsQueued", + listcount(oi->obuf)); + } else { + vty_out(vty, + "%-10s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %12lu\n", + oi->ifp->name, oi->hello_in, oi->hello_out, + oi->db_desc_in, oi->db_desc_out, oi->ls_req_in, + oi->ls_req_out, oi->ls_upd_in, oi->ls_upd_out, + oi->ls_ack_in, oi->ls_ack_out, listcount(oi->obuf)); + } +} + +/* OSPFv2 Packet Counters */ +static int show_ip_ospf_interface_traffic_common( + struct vty *vty, struct ospf *ospf, char *intf_name, json_object *json, + int display_once, uint8_t use_vrf, bool use_json) +{ + struct vrf *vrf = NULL; + struct interface *ifp = NULL; + json_object *json_vrf = NULL; + json_object *json_interface_sub = NULL; + + if (!use_json && !display_once) { + vty_out(vty, "\n"); + vty_out(vty, "%-12s%-17s%-17s%-17s%-17s%-17s%-17s\n", + "Interface", " HELLO", " DB-Desc", " LS-Req", + " LS-Update", " LS-Ack", " Packets"); + vty_out(vty, "%-10s%-18s%-18s%-17s%-17s%-17s%-17s\n", "", + " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", + " Rx/Tx", " Queued"); + vty_out(vty, + "-------------------------------------------------------------------------------------------------------------\n"); + } else if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (intf_name == NULL) { + vrf = vrf_lookup_by_id(ospf->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) { + struct route_node *rn; + struct ospf_interface *oi; + + if (ospf_oi_count(ifp) == 0) + continue; + + for (rn = route_top(IF_OIFS(ifp)); rn; + rn = route_next(rn)) { + oi = rn->info; + + if (oi == NULL) + continue; + + if (use_json) { + json_interface_sub = + json_object_new_object(); + } + + show_ip_ospf_interface_traffic_sub( + vty, oi, json_interface_sub, use_json); + if (use_json) { + json_object_object_add( + json_vrf, ifp->name, + json_interface_sub); + } + } + } + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(intf_name, ospf->vrf_id); + if (ifp != NULL) { + struct route_node *rn; + struct ospf_interface *oi; + + if (ospf_oi_count(ifp) == 0) { + vty_out(vty, + " OSPF not enabled on this interface %s\n", + ifp->name); + return CMD_SUCCESS; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; + rn = route_next(rn)) { + oi = rn->info; + + if (oi == NULL) + continue; + + if (use_json) { + json_interface_sub = + json_object_new_object(); + } + + show_ip_ospf_interface_traffic_sub( + vty, oi, json_interface_sub, use_json); + if (use_json) { + json_object_object_add( + json_vrf, ifp->name, + json_interface_sub); + } + } + } + } + + if (use_json) { + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_interface, + show_ip_ospf_interface_cmd, + "show ip ospf [vrf ] interface [INTERFACE] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Interface information\n" + "Interface name\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + struct listnode *node = NULL; + char *vrf_name = NULL, *intf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0, idx_intf = 0; + uint8_t use_vrf = 0; + json_object *json = NULL; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (argv_find(argv, argc, "INTERFACE", &idx_intf)) + intf_name = argv[idx_intf]->arg; + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_interface_common( + vty, ospf, intf_name, use_vrf, json, + uj); + } + + if (uj) + vty_json(vty, json); + else if (!ospf) + vty_out(vty, "%% OSPF is not enabled\n"); + + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + ret = show_ip_ospf_interface_common(vty, ospf, intf_name, + use_vrf, json, uj); + + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + ret = show_ip_ospf_interface_common(vty, ospf, intf_name, + use_vrf, json, uj); + } + + if (uj) + vty_json(vty, json); + + return ret; +} + +DEFUN (show_ip_ospf_instance_interface, + show_ip_ospf_instance_interface_cmd, + "show ip ospf (1-65535) interface [INTERFACE] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Interface information\n" + "Interface name\n" + JSON_STR) +{ + int idx_number = 3; + int idx_intf = 0; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + char *intf_name = NULL; + int ret = CMD_SUCCESS; + json_object *json = NULL; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (uj) + json = json_object_new_object(); + + if (argv_find(argv, argc, "INTERFACE", &idx_intf)) + intf_name = argv[idx_intf]->arg; + + ret = show_ip_ospf_interface_common(vty, ospf, intf_name, 0, json, uj); + + if (uj) + vty_json(vty, json); + + return ret; +} + +DEFUN (show_ip_ospf_interface_traffic, + show_ip_ospf_interface_traffic_cmd, + "show ip ospf [vrf ] interface traffic [INTERFACE] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Interface information\n" + "Protocol Packet counters\n" + "Interface name\n" + JSON_STR) +{ + struct ospf *ospf = NULL; + struct listnode *node = NULL; + char *vrf_name = NULL, *intf_name = NULL; + bool all_vrf = false; + int inst = 0; + int idx_vrf = 0, idx_intf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + int ret = CMD_SUCCESS; + int display_once = 0; + uint8_t use_vrf = 0; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (argv_find(argv, argc, "INTERFACE", &idx_intf)) + intf_name = argv[idx_intf]->arg; + + if (uj) + json = json_object_new_object(); + + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + ret = show_ip_ospf_interface_traffic_common( + vty, ospf, intf_name, json, + display_once, use_vrf, uj); + display_once = 1; + } + + if (uj) + vty_json(vty, json); + + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + + ret = show_ip_ospf_interface_traffic_common( + vty, ospf, intf_name, json, display_once, use_vrf, uj); + } else { + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + + ret = show_ip_ospf_interface_traffic_common( + vty, ospf, intf_name, json, display_once, use_vrf, uj); + } + + if (uj) + vty_json(vty, json); + + return ret; +} + + +static void show_ip_ospf_neighbour_header(struct vty *vty) +{ + vty_out(vty, "\n%-15s %-3s %-15s %-15s %-9s %-15s %-32s %5s %5s %5s\n", + "Neighbor ID", "Pri", "State", "Up Time", "Dead Time", + "Address", "Interface", "RXmtL", "RqstL", "DBsmL"); +} + +static void show_ip_ospf_neighbour_brief(struct vty *vty, + struct ospf_neighbor *nbr, + struct ospf_neighbor *prev_nbr, + json_object *json, bool use_json) +{ + char msgbuf[16]; + char timebuf[OSPF_TIME_DUMP_SIZE]; + json_object *json_neighbor = NULL, *json_neigh_array = NULL; + struct timeval res = {.tv_sec = 0, .tv_usec = 0}; + long time_val = 0; + char uptime[OSPF_TIME_DUMP_SIZE]; + + if (nbr->ts_last_progress.tv_sec || nbr->ts_last_progress.tv_usec) + time_val = + monotime_since(&nbr->ts_last_progress, &res) / 1000LL; + + if (use_json) { + char neigh_str[INET_ADDRSTRLEN]; + + if (prev_nbr && !IPV4_ADDR_SAME(&prev_nbr->src, &nbr->src)) { + /* Start new neigh list */ + json_neigh_array = NULL; + } + + if (nbr->state == NSM_Attempt && + nbr->router_id.s_addr == INADDR_ANY) + strlcpy(neigh_str, "neighbor", sizeof(neigh_str)); + else + inet_ntop(AF_INET, &nbr->router_id, neigh_str, + sizeof(neigh_str)); + + json_object_object_get_ex(json, neigh_str, &json_neigh_array); + + if (!json_neigh_array) { + json_neigh_array = json_object_new_array(); + json_object_object_add(json, neigh_str, + json_neigh_array); + } + + json_neighbor = json_object_new_object(); + + ospf_nbr_ism_state_message(nbr, msgbuf, sizeof(msgbuf)); + json_object_string_add(json_neighbor, "nbrState", msgbuf); + + json_object_int_add(json_neighbor, "nbrPriority", + nbr->priority); + + json_object_string_add( + json_neighbor, "converged", + lookup_msg(ospf_nsm_state_msg, nbr->state, NULL)); + json_object_string_add(json_neighbor, "role", + lookup_msg(ospf_ism_state_msg, + ospf_nbr_ism_state(nbr), + NULL)); + if (nbr->t_inactivity) { + long time_store; + + time_store = monotime_until(&nbr->t_inactivity->u.sands, + NULL) / + 1000LL; + json_object_int_add(json_neighbor, "upTimeInMsec", + time_val); + json_object_int_add(json_neighbor, + "routerDeadIntervalTimerDueMsec", + time_store); + json_object_string_add( + json_neighbor, "upTime", + ospf_timeval_dump(&res, uptime, + sizeof(uptime))); + json_object_string_add( + json_neighbor, "deadTime", + ospf_timer_dump(nbr->t_inactivity, timebuf, + sizeof(timebuf))); + } else { + json_object_string_add(json_neighbor, "deadTimeMsecs", + "inactive"); + json_object_string_add(json_neighbor, + "routerDeadIntervalTimerDueMsec", + "inactive"); + } + json_object_string_addf(json_neighbor, "ifaceAddress", "%pI4", + &nbr->src); + json_object_string_add(json_neighbor, "ifaceName", + IF_NAME(nbr->oi)); + json_object_int_add(json_neighbor, + "linkStateRetransmissionListCounter", + ospf_ls_retransmit_count(nbr)); + json_object_int_add(json_neighbor, + "linkStateRequestListCounter", + ospf_ls_request_count(nbr)); + json_object_int_add(json_neighbor, "databaseSummaryListCounter", + ospf_db_summary_count(nbr)); + + json_object_array_add(json_neigh_array, json_neighbor); + } else { + ospf_nbr_ism_state_message(nbr, msgbuf, sizeof(msgbuf)); + + if (nbr->state == NSM_Attempt && + nbr->router_id.s_addr == INADDR_ANY) + vty_out(vty, "%-15s %3d %-15s ", "-", nbr->priority, + msgbuf); + else + vty_out(vty, "%-15pI4 %3d %-15s ", &nbr->router_id, + nbr->priority, msgbuf); + + vty_out(vty, "%-15s ", + ospf_timeval_dump(&res, uptime, sizeof(uptime))); + + vty_out(vty, "%9s ", + ospf_timer_dump(nbr->t_inactivity, timebuf, + sizeof(timebuf))); + vty_out(vty, "%-15pI4 ", &nbr->src); + vty_out(vty, "%-32s %5ld %5ld %5d\n", IF_NAME(nbr->oi), + ospf_ls_retransmit_count(nbr), + ospf_ls_request_count(nbr), ospf_db_summary_count(nbr)); + } +} + +static void show_ip_ospf_neighbor_sub(struct vty *vty, + struct ospf_interface *oi, + json_object *json, bool use_json) +{ + struct route_node *rn; + struct ospf_neighbor *nbr, *prev_nbr = NULL; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + /* Do not show myself. */ + if (nbr == oi->nbr_self) + continue; + /* Down state is not shown. */ + if (nbr->state == NSM_Down) + continue; + + prev_nbr = nbr; + + show_ip_ospf_neighbour_brief(vty, nbr, prev_nbr, json, + use_json); + } +} + +static int show_ip_ospf_neighbor_common(struct vty *vty, struct ospf *ospf, + json_object *json, bool use_json, + uint8_t use_vrf) +{ + struct ospf_interface *oi; + struct listnode *node; + json_object *json_vrf = NULL; + json_object *json_nbr_sub = NULL; + + if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + json_nbr_sub = json_object_new_object(); + } + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + if (!use_json) + show_ip_ospf_neighbour_header(vty); + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + if (ospf_interface_neighbor_count(oi) == 0) + continue; + show_ip_ospf_neighbor_sub(vty, oi, json_nbr_sub, use_json); + } + + if (use_json) { + json_object_object_add(json_vrf, "neighbors", json_nbr_sub); + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_neighbor, + show_ip_ospf_neighbor_cmd, + "show ip ospf [vrf ] neighbor [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + json_object *json = NULL; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_neighbor_common( + vty, ospf, json, uj, use_vrf); + } + + if (uj) + vty_json(vty, json); + else if (!ospf) + vty_out(vty, "OSPF is not enabled\n"); + + return ret; + } + + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + } + + if (ospf) { + ret = show_ip_ospf_neighbor_common(vty, ospf, json, uj, + use_vrf); + + if (uj) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + } + } + + if (uj) + json_object_free(json); + + return ret; +} + + +DEFUN (show_ip_ospf_instance_neighbor, + show_ip_ospf_instance_neighbor_cmd, + "show ip ospf (1-65535) neighbor [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + JSON_STR) +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + int ret = CMD_SUCCESS; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (uj) + json = json_object_new_object(); + + ret = show_ip_ospf_neighbor_common(vty, ospf, json, uj, 0); + + if (uj) + vty_json(vty, json); + + return ret; +} + +static int show_ip_ospf_neighbor_all_common(struct vty *vty, struct ospf *ospf, + json_object *json, bool use_json, + uint8_t use_vrf) +{ + struct listnode *node; + struct ospf_interface *oi; + char buf[PREFIX_STRLEN]; + json_object *json_vrf = NULL; + json_object *json_neighbor_sub = NULL; + + if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + if (!use_json) + show_ip_ospf_neighbour_header(vty); + + if (ospf->instance) { + if (use_json) + json_object_int_add(json_vrf, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct listnode *nbr_node; + struct ospf_nbr_nbma *nbr_nbma; + + show_ip_ospf_neighbor_sub(vty, oi, json_vrf, use_json); + + /* print Down neighbor status */ + for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, nbr_node, nbr_nbma)) { + if (nbr_nbma->nbr == NULL + || nbr_nbma->nbr->state == NSM_Down) { + if (use_json) { + json_neighbor_sub = + json_object_new_object(); + json_object_int_add(json_neighbor_sub, + "nbrNbmaPriority", + nbr_nbma->priority); + json_object_boolean_true_add( + json_neighbor_sub, + "nbrNbmaDown"); + json_object_string_add( + json_neighbor_sub, + "nbrNbmaIfaceName", + IF_NAME(oi)); + json_object_int_add( + json_neighbor_sub, + "nbrNbmaRetransmitCounter", 0); + json_object_int_add( + json_neighbor_sub, + "nbrNbmaRequestCounter", 0); + json_object_int_add( + json_neighbor_sub, + "nbrNbmaDbSummaryCounter", 0); + json_object_object_add( + json_vrf, + inet_ntop(AF_INET, + &nbr_nbma->addr, buf, + sizeof(buf)), + json_neighbor_sub); + } else { + vty_out(vty, "%-15s %3d %-15s %9s ", + "-", nbr_nbma->priority, "Down", + "-"); + vty_out(vty, + "%-32pI4 %-20s %5d %5d %5d\n", + &nbr_nbma->addr, + IF_NAME(oi), 0, 0, 0); + } + } + } + } + + if (use_json) { + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_neighbor_all, + show_ip_ospf_neighbor_all_cmd, + "show ip ospf [vrf ] neighbor all [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + "include down status neighbor\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + json_object *json = NULL; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_neighbor_all_common( + vty, ospf, json, uj, use_vrf); + } + + if (uj) + vty_json(vty, json); + + return ret; + } + + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + } + + if (ospf) { + ret = show_ip_ospf_neighbor_all_common(vty, ospf, json, uj, + use_vrf); + if (uj) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + } + } + + if (uj) + json_object_free(json); + + return ret; +} + +DEFUN (show_ip_ospf_instance_neighbor_all, + show_ip_ospf_instance_neighbor_all_cmd, + "show ip ospf (1-65535) neighbor all [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "include down status neighbor\n" + JSON_STR) +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + int ret = CMD_SUCCESS; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + if (uj) + json = json_object_new_object(); + + ret = show_ip_ospf_neighbor_all_common(vty, ospf, json, uj, 0); + + if (uj) + vty_json(vty, json); + + return ret; +} + +static int show_ip_ospf_neighbor_int_common(struct vty *vty, struct ospf *ospf, + const char *ifname, bool use_json, + uint8_t use_vrf) +{ + struct interface *ifp; + struct route_node *rn; + json_object *json = NULL; + + if (use_json) + json = json_object_new_object(); + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json, use_vrf); + + ifp = if_lookup_by_name(ifname, ospf->vrf_id); + if (!ifp) { + if (use_json) + json_object_boolean_true_add(json, "noSuchIface"); + else + vty_out(vty, "No such interface.\n"); + return CMD_WARNING; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi == NULL) + continue; + + show_ip_ospf_neighbor_sub(vty, oi, json, use_json); + } + + if (use_json) + vty_json(vty, json); + else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_ospf_instance_neighbor_int, + show_ip_ospf_instance_neighbor_int_cmd, + "show ip ospf (1-65535)$instance neighbor IFNAME$ifname [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "Interface name\n" + JSON_STR) +{ + struct ospf *ospf; + + if (!json) + show_ip_ospf_neighbour_header(vty); + + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (!json) + show_ip_ospf_neighbour_header(vty); + + return show_ip_ospf_neighbor_int_common(vty, ospf, ifname, !!json, 0); +} + +static void show_ip_ospf_nbr_nbma_detail_sub(struct vty *vty, + struct ospf_interface *oi, + struct ospf_nbr_nbma *nbr_nbma, + bool use_json, json_object *json) +{ + char timebuf[OSPF_TIME_DUMP_SIZE]; + json_object *json_sub = NULL; + + if (use_json) + json_sub = json_object_new_object(); + else /* Show neighbor ID. */ + vty_out(vty, " Neighbor %s,", "-"); + + /* Show interface address. */ + if (use_json) + json_object_string_addf(json_sub, "ifaceAddress", "%pI4", + &nbr_nbma->addr); + else + vty_out(vty, " interface address %pI4\n", + &nbr_nbma->addr); + + /* Show Area ID. */ + if (use_json) { + json_object_string_add(json_sub, "areaId", + ospf_area_desc_string(oi->area)); + json_object_string_add(json_sub, "iface", IF_NAME(oi)); + } else + vty_out(vty, " In the area %s via interface %s\n", + ospf_area_desc_string(oi->area), IF_NAME(oi)); + + /* Show neighbor priority and state. */ + if (use_json) { + json_object_int_add(json_sub, "nbrPriority", + nbr_nbma->priority); + json_object_string_add(json_sub, "nbrState", "down"); + } else + vty_out(vty, " Neighbor priority is %d, State is %s,", + nbr_nbma->priority, "Down"); + + /* Show state changes. */ + if (use_json) + json_object_int_add(json_sub, "stateChangeCounter", + nbr_nbma->state_change); + else + vty_out(vty, " %d state changes\n", nbr_nbma->state_change); + + /* Show PollInterval */ + if (use_json) + json_object_int_add(json_sub, "pollInterval", nbr_nbma->v_poll); + else + vty_out(vty, " Poll interval %d\n", nbr_nbma->v_poll); + + /* Show poll-interval timer. */ + if (nbr_nbma->t_poll) { + if (use_json) { + long time_store; + time_store = monotime_until(&nbr_nbma->t_poll->u.sands, + NULL) / 1000LL; + json_object_int_add(json_sub, + "pollIntervalTimerDueMsec", + time_store); + } else + vty_out(vty, " Poll timer due in %s\n", + ospf_timer_dump(nbr_nbma->t_poll, timebuf, + sizeof(timebuf))); + } + + /* Show poll-interval timer thread. */ + if (use_json) { + if (nbr_nbma->t_poll != NULL) + json_object_string_add(json_sub, + "pollIntervalTimerThread", "on"); + } else + vty_out(vty, " Thread Poll Timer %s\n", + nbr_nbma->t_poll != NULL ? "on" : "off"); + + if (use_json) + json_object_object_add(json, "noNbrId", json_sub); +} + +static void show_ip_ospf_neighbor_detail_sub(struct vty *vty, + struct ospf_interface *oi, + struct ospf_neighbor *nbr, + struct ospf_neighbor *prev_nbr, + json_object *json, bool use_json) +{ + char timebuf[OSPF_TIME_DUMP_SIZE]; + json_object *json_neigh = NULL, *json_neigh_array = NULL; + char neigh_str[INET_ADDRSTRLEN] = {0}; + char neigh_state[16] = {0}; + struct ospf_neighbor *nbr_dr, *nbr_bdr; + + if (use_json) { + if (prev_nbr && + !IPV4_ADDR_SAME(&prev_nbr->src, &nbr->src)) { + json_neigh_array = NULL; + } + + if (nbr->state == NSM_Attempt + && nbr->router_id.s_addr == INADDR_ANY) + strlcpy(neigh_str, "noNbrId", sizeof(neigh_str)); + else + inet_ntop(AF_INET, &nbr->router_id, + neigh_str, sizeof(neigh_str)); + + json_object_object_get_ex(json, neigh_str, &json_neigh_array); + + if (!json_neigh_array) { + json_neigh_array = json_object_new_array(); + json_object_object_add(json, neigh_str, + json_neigh_array); + } + + json_neigh = json_object_new_object(); + + } else { + /* Show neighbor ID. */ + if (nbr->state == NSM_Attempt + && nbr->router_id.s_addr == INADDR_ANY) + vty_out(vty, " Neighbor %s,", "-"); + else + vty_out(vty, " Neighbor %pI4,", + &nbr->router_id); + } + + /* Show interface address. */ + if (use_json) + json_object_string_addf(json_neigh, "ifaceAddress", "%pI4", + &nbr->address.u.prefix4); + else + vty_out(vty, " interface address %pI4\n", + &nbr->address.u.prefix4); + + /* Show Area ID. */ + if (use_json) { + json_object_string_add(json_neigh, "areaId", + ospf_area_desc_string(oi->area)); + json_object_string_add(json_neigh, "ifaceName", oi->ifp->name); + if (oi->address) + json_object_string_addf(json_neigh, "localIfaceAddress", + "%pI4", + &oi->address->u.prefix4); + } else { + vty_out(vty, " In the area %s via interface %s", + ospf_area_desc_string(oi->area), oi->ifp->name); + if (oi->address) + vty_out(vty, " local interface IP %pI4\n", + &oi->address->u.prefix4); + else + vty_out(vty, "\n"); + } + + /* Show neighbor priority and state. */ + ospf_nbr_ism_state_message(nbr, neigh_state, sizeof(neigh_state)); + if (use_json) { + json_object_int_add(json_neigh, "nbrPriority", nbr->priority); + json_object_string_add(json_neigh, "nbrState", neigh_state); + json_object_string_add(json_neigh, "role", + lookup_msg(ospf_ism_state_msg, + ospf_nbr_ism_state(nbr), + NULL)); + } else { + vty_out(vty, + " Neighbor priority is %d, State is %s, Role is %s,", + nbr->priority, neigh_state, + lookup_msg(ospf_ism_state_msg, ospf_nbr_ism_state(nbr), + NULL)); + } + /* Show state changes. */ + if (use_json) + json_object_int_add(json_neigh, "stateChangeCounter", + nbr->state_change); + else + vty_out(vty, " %d state changes\n", nbr->state_change); + + if (nbr->ts_last_progress.tv_sec || nbr->ts_last_progress.tv_usec) { + struct timeval res; + long time_store; + + time_store = + monotime_since(&nbr->ts_last_progress, &res) / 1000LL; + if (use_json) { + json_object_int_add(json_neigh, "lastPrgrsvChangeMsec", + time_store); + } else { + vty_out(vty, + " Most recent state change statistics:\n"); + vty_out(vty, " Progressive change %s ago\n", + ospf_timeval_dump(&res, timebuf, + sizeof(timebuf))); + } + } + + if (nbr->ts_last_regress.tv_sec || nbr->ts_last_regress.tv_usec) { + struct timeval res; + long time_store; + + time_store = + monotime_since(&nbr->ts_last_regress, &res) / 1000LL; + if (use_json) { + json_object_int_add(json_neigh, + "lastRegressiveChangeMsec", + time_store); + if (nbr->last_regress_str) + json_object_string_add( + json_neigh, + "lastRegressiveChangeReason", + nbr->last_regress_str); + } else { + vty_out(vty, + " Regressive change %s ago, due to %s\n", + ospf_timeval_dump(&res, timebuf, + sizeof(timebuf)), + (nbr->last_regress_str ? nbr->last_regress_str + : "??")); + } + } + + /* Show Designated Router ID. */ + if (DR(oi).s_addr == INADDR_ANY) { + if (!use_json) + vty_out(vty, + " No designated router on this network\n"); + } else { + nbr_dr = ospf_nbr_lookup_by_addr(oi->nbrs, &DR(oi)); + if (nbr_dr) { + if (use_json) + json_object_string_addf( + json_neigh, "routerDesignatedId", + "%pI4", &nbr_dr->router_id); + else + vty_out(vty, " DR is %pI4,", + &nbr_dr->router_id); + } + } + + /* Show Backup Designated Router ID. */ + nbr_bdr = ospf_nbr_lookup_by_addr(oi->nbrs, &BDR(oi)); + if (nbr_bdr == NULL) { + if (!use_json) + vty_out(vty, + " No backup designated router on this network\n"); + } else { + if (use_json) + json_object_string_addf(json_neigh, + "routerDesignatedBackupId", + "%pI4", &nbr_bdr->router_id); + else + vty_out(vty, " BDR is %pI4\n", &nbr_bdr->router_id); + } + + /* Show options. */ + if (use_json) { + json_object_int_add(json_neigh, "optionsCounter", nbr->options); + json_object_string_add(json_neigh, "optionsList", + ospf_options_dump(nbr->options)); + } else + vty_out(vty, " Options %d %s\n", nbr->options, + ospf_options_dump(nbr->options)); + + /* Show Router Dead interval timer. */ + if (use_json) { + if (nbr->t_inactivity) { + long time_store; + time_store = monotime_until(&nbr->t_inactivity->u.sands, + NULL) + / 1000LL; + json_object_int_add(json_neigh, + "routerDeadIntervalTimerDueMsec", + time_store); + } else + json_object_int_add( + json_neigh, + "routerDeadIntervalTimerDueMsec", -1); + } else + vty_out(vty, " Dead timer due in %s\n", + ospf_timer_dump(nbr->t_inactivity, timebuf, + sizeof(timebuf))); + + /* Show Database Summary list. */ + if (use_json) + json_object_int_add(json_neigh, "databaseSummaryListCounter", + ospf_db_summary_count(nbr)); + else + vty_out(vty, " Database Summary List %d\n", + ospf_db_summary_count(nbr)); + + /* Show Link State Request list. */ + if (use_json) + json_object_int_add(json_neigh, "linkStateRequestListCounter", + ospf_ls_request_count(nbr)); + else + vty_out(vty, " Link State Request List %ld\n", + ospf_ls_request_count(nbr)); + + /* Show Link State Retransmission list. */ + if (use_json) + json_object_int_add(json_neigh, + "linkStateRetransmissionListCounter", + ospf_ls_retransmit_count(nbr)); + else + vty_out(vty, " Link State Retransmission List %ld\n", + ospf_ls_retransmit_count(nbr)); + + /* Show inactivity timer thread. */ + if (use_json) { + if (nbr->t_inactivity != NULL) + json_object_string_add(json_neigh, + "threadInactivityTimer", "on"); + } else + vty_out(vty, " Thread Inactivity Timer %s\n", + nbr->t_inactivity != NULL ? "on" : "off"); + + /* Show Database Description retransmission thread. */ + if (use_json) { + if (nbr->t_db_desc != NULL) + json_object_string_add( + json_neigh, + "threadDatabaseDescriptionRetransmission", + "on"); + } else + vty_out(vty, + " Thread Database Description Retransmision %s\n", + nbr->t_db_desc != NULL ? "on" : "off"); + + /* Show Link State Request Retransmission thread. */ + if (use_json) { + if (nbr->t_ls_req != NULL) + json_object_string_add( + json_neigh, + "threadLinkStateRequestRetransmission", "on"); + } else + vty_out(vty, + " Thread Link State Request Retransmission %s\n", + nbr->t_ls_req != NULL ? "on" : "off"); + + /* Show Link State Update Retransmission thread. */ + if (use_json) { + if (nbr->t_ls_upd != NULL) + json_object_string_add( + json_neigh, + "threadLinkStateUpdateRetransmission", + "on"); + } else + vty_out(vty, + " Thread Link State Update Retransmission %s\n\n", + nbr->t_ls_upd != NULL ? "on" : "off"); + + if (!use_json) { + vty_out(vty, " Graceful restart Helper info:\n"); + + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) { + vty_out(vty, + " Graceful Restart HELPER Status : Inprogress.\n"); + + vty_out(vty, + " Graceful Restart grace period time: %d (seconds).\n", + nbr->gr_helper_info.recvd_grace_period); + vty_out(vty, " Graceful Restart reason: %s.\n", + ospf_restart_reason2str( + nbr->gr_helper_info.gr_restart_reason)); + } else { + vty_out(vty, + " Graceful Restart HELPER Status : None\n"); + } + + if (nbr->gr_helper_info.rejected_reason + != OSPF_HELPER_REJECTED_NONE) + vty_out(vty, " Helper rejected reason: %s.\n", + ospf_rejected_reason2str( + nbr->gr_helper_info.rejected_reason)); + + if (nbr->gr_helper_info.helper_exit_reason + != OSPF_GR_HELPER_EXIT_NONE) + vty_out(vty, " Last helper exit reason: %s.\n\n", + ospf_exit_reason2str( + nbr->gr_helper_info.helper_exit_reason)); + else + vty_out(vty, "\n"); + } else { + json_object_string_add(json_neigh, "grHelperStatus", + OSPF_GR_IS_ACTIVE_HELPER(nbr) ? + "Inprogress" + : "None"); + if (OSPF_GR_IS_ACTIVE_HELPER(nbr)) { + json_object_int_add( + json_neigh, "graceInterval", + nbr->gr_helper_info.recvd_grace_period); + json_object_string_add( + json_neigh, "grRestartReason", + ospf_restart_reason2str( + nbr->gr_helper_info.gr_restart_reason)); + } + + if (nbr->gr_helper_info.rejected_reason + != OSPF_HELPER_REJECTED_NONE) + json_object_string_add( + json_neigh, "helperRejectReason", + ospf_rejected_reason2str( + nbr->gr_helper_info.rejected_reason)); + + if (nbr->gr_helper_info.helper_exit_reason + != OSPF_GR_HELPER_EXIT_NONE) + json_object_string_add( + json_neigh, "helperExitReason", + ospf_exit_reason2str( + nbr->gr_helper_info + .helper_exit_reason)); + } + + bfd_sess_show(vty, json_neigh, nbr->bfd_session); + + if (use_json) + json_object_array_add(json_neigh_array, json_neigh); + +} + +static int show_ip_ospf_neighbor_id_common(struct vty *vty, struct ospf *ospf, + struct in_addr *router_id, + bool use_json, uint8_t use_vrf, + bool is_detail, + json_object *json_vrf) +{ + struct listnode *node; + struct ospf_neighbor *nbr; + struct ospf_interface *oi; + json_object *json = NULL; + + if (use_json) + json = json_object_new_object(); + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json, use_vrf); + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, router_id); + + if (!nbr) + continue; + + if (is_detail) + show_ip_ospf_neighbor_detail_sub(vty, oi, nbr, NULL, + json, use_json); + else + show_ip_ospf_neighbour_brief(vty, nbr, NULL, json, + use_json); + } + + if (json_vrf && use_json) { + json_object_object_add( + json_vrf, + (ospf->vrf_id == VRF_DEFAULT) ? "default" : ospf->name, + json); + return CMD_SUCCESS; + } + + if (use_json) + vty_json(vty, json); + else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_ospf_neighbor_id, + show_ip_ospf_neighbor_id_cmd, + "show ip ospf [vrf NAME$vrf_name] neighbor A.B.C.D$router_id [detail$detail] [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "Neighbor list\n" + "Neighbor ID\n" + "Detailed output\n" + JSON_STR) +{ + struct ospf *ospf; + struct listnode *node; + int ret = CMD_SUCCESS; + int inst = 0; + + if (vrf_name && !strmatch(vrf_name, "all")) { + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (!json) + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + else + vty_json_empty(vty, NULL); + return CMD_SUCCESS; + } + ret = show_ip_ospf_neighbor_id_common( + vty, ospf, &router_id, !!json, 0, !!detail, NULL); + } else { + json_object *json_vrf = NULL; + + if (json) + json_vrf = json_object_new_object(); + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_neighbor_id_common( + vty, ospf, &router_id, !!json, 0, !!detail, + json_vrf); + } + if (json) + vty_json(vty, json_vrf); + } + + return ret; +} + +DEFPY(show_ip_ospf_instance_neighbor_id, show_ip_ospf_instance_neighbor_id_cmd, + "show ip ospf (1-65535)$instance neighbor A.B.C.D$router_id [detail$detail] [json$json]", + SHOW_STR IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "Neighbor ID\n" + "Detailed output\n" JSON_STR) +{ + struct ospf *ospf; + + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + return show_ip_ospf_neighbor_id_common(vty, ospf, &router_id, !!json, 0, + !!detail, NULL); +} + +static int show_ip_ospf_neighbor_detail_common(struct vty *vty, + struct ospf *ospf, + json_object *json, bool use_json, + uint8_t use_vrf) +{ + struct ospf_interface *oi; + struct listnode *node; + json_object *json_vrf = NULL; + json_object *json_nbr_sub = NULL; + + if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + + json_nbr_sub = json_object_new_object(); + } + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn; + struct ospf_neighbor *nbr, *prev_nbr = NULL; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + if (nbr != oi->nbr_self) { + if (nbr->state != NSM_Down) { + show_ip_ospf_neighbor_detail_sub( + vty, oi, nbr, prev_nbr, + json_nbr_sub, use_json); + } + } + prev_nbr = nbr; + } + } + + if (use_json) { + json_object_object_add(json_vrf, "neighbors", + json_nbr_sub); + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_ospf_neighbor_detail, + show_ip_ospf_neighbor_detail_cmd, + "show ip ospf [vrf $vrf_name] neighbor detail [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + "detail of all neighbors\n" + JSON_STR) +{ + struct ospf *ospf; + struct listnode *node = NULL; + int ret = CMD_SUCCESS; + int inst = 0; + uint8_t use_vrf = 0; + json_object *json_vrf = NULL; + + if (json) + json_vrf = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + use_vrf = 1; + if (strmatch(vrf_name, "all")) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_neighbor_detail_common( + vty, ospf, json_vrf, !!json, use_vrf); + } + if (json) + vty_json(vty, json_vrf); + + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (json) + vty_json(vty, json_vrf); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + return CMD_SUCCESS; + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (json) + vty_json(vty, json_vrf); + else + vty_out(vty, "%% OSPF is not enabled\n"); + return CMD_SUCCESS; + } + } + + if (ospf) + ret = show_ip_ospf_neighbor_detail_common(vty, ospf, json_vrf, + !!json, use_vrf); + + if (json) + vty_json(vty, json_vrf); + + return ret; +} + +DEFUN (show_ip_ospf_instance_neighbor_detail, + show_ip_ospf_instance_neighbor_detail_cmd, + "show ip ospf (1-65535) neighbor detail [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "detail of all neighbors\n" + JSON_STR) +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + int ret = CMD_SUCCESS; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (uj) + json = json_object_new_object(); + + ret = show_ip_ospf_neighbor_detail_common(vty, ospf, json, uj, 0); + + if (uj) + vty_json(vty, json); + + return ret; +} + +static int show_ip_ospf_neighbor_detail_all_common(struct vty *vty, + struct ospf *ospf, + json_object *json, + bool use_json, + uint8_t use_vrf) +{ + struct listnode *node; + struct ospf_interface *oi; + json_object *json_vrf = NULL; + + if (use_json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn; + struct ospf_neighbor *nbr, *prev_nbr = NULL; + struct ospf_nbr_nbma *nbr_nbma; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + if (nbr != oi->nbr_self) + if (nbr->state != NSM_Down) + show_ip_ospf_neighbor_detail_sub( + vty, oi, rn->info, prev_nbr, + json_vrf, use_json); + prev_nbr = nbr; + } + + if (!OSPF_IF_NON_BROADCAST(oi)) + continue; + + struct listnode *nd; + + for (ALL_LIST_ELEMENTS_RO(oi->nbr_nbma, nd, nbr_nbma)) { + if (nbr_nbma->nbr == NULL || + nbr_nbma->nbr->state == NSM_Down) + show_ip_ospf_nbr_nbma_detail_sub( + vty, oi, nbr_nbma, use_json, json_vrf); + } + } + + if (use_json) { + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else { + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_neighbor_detail_all, + show_ip_ospf_neighbor_detail_all_cmd, + "show ip ospf [vrf ] neighbor detail all [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Neighbor list\n" + "detail of all neighbors\n" + "include down status neighbor\n" + JSON_STR) +{ + struct ospf *ospf; + bool uj = use_json(argc, argv); + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + json_object *json = NULL; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = show_ip_ospf_neighbor_detail_all_common( + vty, ospf, json, uj, use_vrf); + } + + if (uj) + vty_json(vty, json); + + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + json_object_free(json); + return CMD_SUCCESS; + } + } + + if (ospf) { + ret = show_ip_ospf_neighbor_detail_all_common(vty, ospf, json, + uj, use_vrf); + if (uj) { + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_PRETTY)); + } + } + + if (uj) + json_object_free(json); + + return ret; +} + +DEFUN (show_ip_ospf_instance_neighbor_detail_all, + show_ip_ospf_instance_neighbor_detail_all_cmd, + "show ip ospf (1-65535) neighbor detail all [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "detail of all neighbors\n" + "include down status neighbor\n" + JSON_STR) +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + int ret = CMD_SUCCESS; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + if (uj) + json = json_object_new_object(); + + ret = show_ip_ospf_neighbor_detail_all_common(vty, ospf, json, uj, 0); + + if (uj) + vty_json(vty, json); + + return ret; +} + +static int show_ip_ospf_neighbor_int_detail_common(struct vty *vty, + struct ospf *ospf, + const char *ifname, + bool use_json, + json_object *json_vrf) +{ + struct ospf_interface *oi; + struct interface *ifp; + struct route_node *rn, *nrn; + struct ospf_neighbor *nbr; + json_object *json = NULL; + + if (use_json) { + json = json_object_new_object(); + if (json_vrf) + json_object_object_add(json_vrf, + (ospf->vrf_id == VRF_DEFAULT) + ? "default" + : ospf->name, + json); + } + + if (ospf->instance) { + if (use_json) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ifp = if_lookup_by_name(ifname, ospf->vrf_id); + if (!ifp) { + if (!use_json) { + vty_out(vty, "No such interface.\n"); + } else { + if (!json_vrf) + vty_json(vty, json); + } + return CMD_WARNING; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + oi = rn->info; + + if (!oi) + continue; + + for (nrn = route_top(oi->nbrs); nrn; nrn = route_next(nrn)) { + nbr = nrn->info; + + if (!nbr) + continue; + + if (nbr == oi->nbr_self) + continue; + + if (nbr->state == NSM_Down) + continue; + + show_ip_ospf_neighbor_detail_sub(vty, oi, nbr, NULL, + json, use_json); + } + } + + if (use_json) { + if (!json_vrf) + vty_json(vty, json); + } else { + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFPY(show_ip_ospf_neighbor_int, + show_ip_ospf_neighbor_int_cmd, + "show ip ospf [vrf NAME$vrf_name] neighbor IFNAME$ifname [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "Neighbor list\n" + "Interface name\n" + JSON_STR) +{ + struct ospf *ospf; + int ret = CMD_SUCCESS; + struct interface *ifp = NULL; + vrf_id_t vrf_id = VRF_DEFAULT; + struct vrf *vrf = NULL; + + if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME)) + vrf_name = NULL; + if (vrf_name) { + vrf = vrf_lookup_by_name(vrf_name); + if (vrf) + vrf_id = vrf->vrf_id; + } + ospf = ospf_lookup_by_vrf_id(vrf_id); + + if (!ospf || !ospf->oi_running) { + if (json) + vty_json_empty(vty, NULL); + return ret; + } + + if (!json) + show_ip_ospf_neighbour_header(vty); + + ifp = if_lookup_by_name(ifname, vrf_id); + if (!ifp) { + if (json) + vty_json_empty(vty, NULL); + else + vty_out(vty, "No such interface.\n"); + return ret; + } + + ret = show_ip_ospf_neighbor_int_common(vty, ospf, ifname, !!json, 0); + return ret; +} + +DEFPY(show_ip_ospf_neighbor_int_detail, + show_ip_ospf_neighbor_int_detail_cmd, + "show ip ospf [vrf NAME$vrf_name] neighbor IFNAME$ifname detail [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "Neighbor list\n" + "Interface name\n" + "detail of all neighbors\n" + JSON_STR) +{ + struct ospf *ospf; + struct listnode *node = NULL; + int ret = CMD_SUCCESS; + bool ospf_output = false; + + if (vrf_name && !strmatch(vrf_name, "all")) { + int inst = 0; + + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (!json) + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + else + vty_json_empty(vty, NULL); + return CMD_SUCCESS; + } + return show_ip_ospf_neighbor_int_detail_common( + vty, ospf, ifname, !!json, NULL); + } + + json_object *json_vrf = NULL; + + if (json) + json_vrf = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ospf_output = true; + ret = show_ip_ospf_neighbor_int_detail_common(vty, ospf, ifname, + !!json, json_vrf); + } + + if (json) { + vty_json(vty, json_vrf); + return ret; + } + + if (!ospf_output) + vty_out(vty, "%% OSPF instance not found\n"); + + return ret; +} + +DEFPY(show_ip_ospf_instance_neighbor_int_detail, + show_ip_ospf_instance_neighbor_int_detail_cmd, + "show ip ospf (1-65535)$instance neighbor IFNAME$ifname detail [json$json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Neighbor list\n" + "Interface name\n" + "detail of all neighbors\n" + JSON_STR) +{ + struct ospf *ospf; + + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + return show_ip_ospf_neighbor_int_detail_common(vty, ospf, ifname, + !!json, NULL); +} + +/* Show functions */ +static int show_lsa_summary(struct vty *vty, struct ospf_lsa *lsa, int self, + json_object *json_lsa) +{ + struct router_lsa *rl; + struct summary_lsa *sl; + struct as_external_lsa *asel; + struct prefix_ipv4 p; + + if (lsa == NULL) + return 0; + + /* If self option is set, check LSA self flag. */ + if (self == 0 || IS_LSA_SELF(lsa)) { + + if (!json_lsa) { + /* LSA common part show. */ + vty_out(vty, "%-15pI4", &lsa->data->id); + vty_out(vty, "%-15pI4 %4d 0x%08lx 0x%04x", + &lsa->data->adv_router, LS_AGE(lsa), + (unsigned long)ntohl(lsa->data->ls_seqnum), + ntohs(lsa->data->checksum)); + } else { + char seqnum[10]; + char checksum[10]; + + snprintf(seqnum, sizeof(seqnum), "%x", + ntohl(lsa->data->ls_seqnum)); + snprintf(checksum, sizeof(checksum), "%x", + ntohs(lsa->data->checksum)); + json_object_string_addf(json_lsa, "lsId", "%pI4", + &lsa->data->id); + json_object_string_addf(json_lsa, "advertisedRouter", + "%pI4", &lsa->data->adv_router); + json_object_int_add(json_lsa, "lsaAge", LS_AGE(lsa)); + json_object_string_add(json_lsa, "sequenceNumber", + seqnum); + json_object_string_add(json_lsa, "checksum", checksum); + } + + /* LSA specific part show. */ + switch (lsa->data->type) { + case OSPF_ROUTER_LSA: + rl = (struct router_lsa *)lsa->data; + + if (!json_lsa) + vty_out(vty, " %-d", ntohs(rl->links)); + else + json_object_int_add(json_lsa, + "numOfRouterLinks", + ntohs(rl->links)); + break; + case OSPF_SUMMARY_LSA: + sl = (struct summary_lsa *)lsa->data; + + p.family = AF_INET; + p.prefix = sl->header.id; + p.prefixlen = ip_masklen(sl->mask); + apply_mask_ipv4(&p); + + if (!json_lsa) + vty_out(vty, " %pFX", &p); + else { + json_object_string_addf( + json_lsa, "summaryAddress", "%pFX", &p); + } + break; + case OSPF_AS_EXTERNAL_LSA: + case OSPF_AS_NSSA_LSA: + asel = (struct as_external_lsa *)lsa->data; + + p.family = AF_INET; + p.prefix = asel->header.id; + p.prefixlen = ip_masklen(asel->mask); + apply_mask_ipv4(&p); + + if (!json_lsa) + vty_out(vty, " %s %pFX [0x%lx]", + IS_EXTERNAL_METRIC(asel->e[0].tos) + ? "E2" + : "E1", + &p, + (unsigned long)ntohl( + asel->e[0].route_tag)); + else { + json_object_string_add( + json_lsa, "metricType", + IS_EXTERNAL_METRIC(asel->e[0].tos) + ? "E2" + : "E1"); + json_object_string_addf(json_lsa, "route", + "%pFX", &p); + json_object_int_add( + json_lsa, "tag", + (unsigned long)ntohl( + asel->e[0].route_tag)); + } + break; + case OSPF_NETWORK_LSA: + case OSPF_ASBR_SUMMARY_LSA: + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + default: + break; + } + + if (!json_lsa) + vty_out(vty, "\n"); + } + + return 1; +} + +static const char *const show_database_desc[] = { + "unknown", + "Router Link States", + "Net Link States", + "Summary Link States", + "ASBR-Summary Link States", + "AS External Link States", + "Group Membership LSA", + "NSSA-external Link States", + "Type-8 LSA", + "Link-Local Opaque-LSA", + "Area-Local Opaque-LSA", + "AS-external Opaque-LSA", +}; + +static const char * const show_database_desc_json[] = { + "unknown", + "routerLinkStates", + "networkLinkStates", + "summaryLinkStates", + "asbrSummaryLinkStates", + "asExternalLinkStates", + "groupMembershipLsa", + "nssaExternalLinkStates", + "type8Lsa", + "linkLocalOpaqueLsa", + "areaLocalOpaqueLsa", + "asExternalOpaqueLsa", +}; + +static const char *const show_database_desc_count_json[] = { + "unknownCount", + "routerLinkStatesCount", + "networkLinkStatesCount", + "summaryLinkStatesCount", + "asbrSummaryLinkStatesCount", + "asExternalLinkStatesCount", + "groupMembershipLsaCount", + "nssaExternalLinkStatesCount", + "type8LsaCount", + "linkLocalOpaqueLsaCount", + "areaLocalOpaqueLsaCount", + "asExternalOpaqueLsaCount", +}; + +static const char *const show_database_header[] = { + "", + "Link ID ADV Router Age Seq# CkSum Link count", + "Link ID ADV Router Age Seq# CkSum", + "Link ID ADV Router Age Seq# CkSum Route", + "Link ID ADV Router Age Seq# CkSum", + "Link ID ADV Router Age Seq# CkSum Route", + " --- header for Group Member ----", + "Link ID ADV Router Age Seq# CkSum Route", + " --- type-8 ---", + "Opaque-Type/Id ADV Router Age Seq# CkSum", + "Opaque-Type/Id ADV Router Age Seq# CkSum", + "Opaque-Type/Id ADV Router Age Seq# CkSum", +}; + +static void show_ip_ospf_database_header(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + struct router_lsa *rlsa = (struct router_lsa *)lsa->data; + + if (!json) { + if (IS_LSA_SELF(lsa)) + vty_out(vty, " LS age: %d%s\n", LS_AGE(lsa), + CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE) + ? "(S-DNA)" + : ""); + else + vty_out(vty, " LS age: %d%s\n", LS_AGE(lsa), + CHECK_FLAG(lsa->data->ls_age, DO_NOT_AGE) + ? "(DNA)" + : ""); + vty_out(vty, " Options: 0x%-2x : %s\n", lsa->data->options, + ospf_options_dump(lsa->data->options)); + vty_out(vty, " LS Flags: 0x%-2x %s\n", lsa->flags, + ((lsa->flags & OSPF_LSA_LOCAL_XLT) + ? "(Translated from Type-7)" + : "")); + + if (lsa->data->type == OSPF_ROUTER_LSA) { + vty_out(vty, " Flags: 0x%x", rlsa->flags); + + if (rlsa->flags) + vty_out(vty, " :%s%s%s%s", + IS_ROUTER_LSA_BORDER(rlsa) ? " ABR" + : "", + IS_ROUTER_LSA_EXTERNAL(rlsa) ? " ASBR" + : "", + IS_ROUTER_LSA_VIRTUAL(rlsa) + ? " VL-endpoint" + : "", + IS_ROUTER_LSA_SHORTCUT(rlsa) + ? " Shortcut" + : ""); + + vty_out(vty, "\n"); + } + vty_out(vty, " LS Type: %s\n", + lookup_msg(ospf_lsa_type_msg, lsa->data->type, NULL)); + vty_out(vty, " Link State ID: %pI4 %s\n", + &lsa->data->id, + lookup_msg(ospf_link_state_id_type_msg, lsa->data->type, + NULL)); + vty_out(vty, " Advertising Router: %pI4\n", + &lsa->data->adv_router); + vty_out(vty, " LS Seq Number: %08lx\n", + (unsigned long)ntohl(lsa->data->ls_seqnum)); + vty_out(vty, " Checksum: 0x%04x\n", + ntohs(lsa->data->checksum)); + vty_out(vty, " Length: %d\n\n", ntohs(lsa->data->length)); + } else { + char seqnum[10]; + char checksum[10]; + + snprintf(seqnum, 10, "%x", ntohl(lsa->data->ls_seqnum)); + snprintf(checksum, 10, "%x", ntohs(lsa->data->checksum)); + + json_object_int_add(json, "lsaAge", LS_AGE(lsa)); + json_object_string_add(json, "options", + ospf_options_dump(lsa->data->options)); + json_object_int_add(json, "lsaFlags", lsa->flags); + + if (lsa->flags & OSPF_LSA_LOCAL_XLT) + json_object_boolean_true_add(json, + "translatedFromType7"); + + if (lsa->data->type == OSPF_ROUTER_LSA) { + json_object_int_add(json, "flags", rlsa->flags); + + if (rlsa->flags) { + if (IS_ROUTER_LSA_BORDER(rlsa)) + json_object_boolean_true_add(json, + "abr"); + if (IS_ROUTER_LSA_EXTERNAL(rlsa)) + json_object_boolean_true_add(json, + "asbr"); + if (IS_ROUTER_LSA_VIRTUAL(rlsa)) + json_object_boolean_true_add( + json, "vlEndpoint"); + if (IS_ROUTER_LSA_SHORTCUT(rlsa)) + json_object_boolean_true_add( + json, "shortcut"); + } + } + + json_object_string_add( + json, "lsaType", + lookup_msg(ospf_lsa_type_msg, lsa->data->type, NULL)); + json_object_string_addf(json, "linkStateId", "%pI4", + &lsa->data->id); + json_object_string_addf(json, "advertisingRouter", "%pI4", + &lsa->data->adv_router); + json_object_string_add(json, "lsaSeqNumber", seqnum); + json_object_string_add(json, "checksum", checksum); + json_object_int_add(json, "length", ntohs(lsa->data->length)); + } +} + +static const char *const link_type_desc[] = { + "(null)", + "another Router (point-to-point)", + "a Transit Network", + "Stub Network", + "a Virtual Link", +}; + +static const char *const link_id_desc[] = { + "(null)", "Neighboring Router ID", "Designated Router address", + "Net", "Neighboring Router ID", +}; + +static const char *const link_data_desc[] = { + "(null)", "Router Interface address", "Router Interface address", + "Network Mask", "Router Interface address", +}; + +static const char *const link_id_desc_json[] = { + "null", "neighborRouterId", "designatedRouterAddress", + "networkAddress", "neighborRouterId", +}; + +static const char *const link_data_desc_json[] = { + "null", "routerInterfaceAddress", "routerInterfaceAddress", + "networkMask", "routerInterfaceAddress", +}; + +/* Show router-LSA each Link information. */ +static void show_ip_ospf_database_router_links(struct vty *vty, + struct router_lsa *rl, + json_object *json) +{ + int len, type; + unsigned short i; + json_object *json_links = NULL; + json_object *json_link = NULL; + int metric = 0; + char buf[PREFIX_STRLEN]; + + if (json) + json_links = json_object_new_object(); + + len = ntohs(rl->header.length) - 4; + for (i = 0; i < ntohs(rl->links) && len > 0; len -= 12, i++) { + type = rl->link[i].type; + + if (json) { + char link[16]; + + snprintf(link, sizeof(link), "link%u", i); + json_link = json_object_new_object(); + json_object_string_add(json_link, "linkType", + link_type_desc[type]); + json_object_string_add(json_link, + link_id_desc_json[type], + inet_ntop(AF_INET, + &rl->link[i].link_id, + buf, sizeof(buf))); + json_object_string_add( + json_link, link_data_desc_json[type], + inet_ntop(AF_INET, &rl->link[i].link_data, + buf, sizeof(buf))); + json_object_int_add(json_link, "numOfTosMetrics", + metric); + json_object_int_add(json_link, "tos0Metric", + ntohs(rl->link[i].metric)); + json_object_object_add(json_links, link, json_link); + } else { + vty_out(vty, " Link connected to: %s\n", + link_type_desc[type]); + vty_out(vty, " (Link ID) %s: %pI4\n", + link_id_desc[type], + &rl->link[i].link_id); + vty_out(vty, " (Link Data) %s: %pI4\n", + link_data_desc[type], + &rl->link[i].link_data); + vty_out(vty, " Number of TOS metrics: 0\n"); + vty_out(vty, " TOS 0 Metric: %d\n", + ntohs(rl->link[i].metric)); + vty_out(vty, "\n"); + } + } + if (json) + json_object_object_add(json, "routerLinks", json_links); +} + +/* Show router-LSA detail information. */ +static int show_router_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + if (lsa != NULL) { + struct router_lsa *rl = (struct router_lsa *)lsa->data; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) + vty_out(vty, " Number of Links: %d\n\n", + ntohs(rl->links)); + else + json_object_int_add(json, "numOfLinks", + ntohs(rl->links)); + + show_ip_ospf_database_router_links(vty, rl, json); + + if (!json) + vty_out(vty, "\n"); + } + + return 0; +} + +/* Show network-LSA detail information. */ +static int show_network_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + int length, i; + char buf[PREFIX_STRLEN]; + json_object *json_attached_rt = NULL; + json_object *json_router = NULL; + + if (json) + json_attached_rt = json_object_new_object(); + + if (lsa != NULL) { + struct network_lsa *nl = (struct network_lsa *)lsa->data; + struct in_addr *addr; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) + vty_out(vty, " Network Mask: /%d\n", + ip_masklen(nl->mask)); + else + json_object_int_add(json, "networkMask", + ip_masklen(nl->mask)); + + length = lsa->size - OSPF_LSA_HEADER_SIZE - 4; + addr = &nl->routers[0]; + for (i = 0; length > 0 && addr; + length -= 4, addr = &nl->routers[++i]) + if (!json) { + vty_out(vty, " Attached Router: %pI4\n", + addr); + vty_out(vty, "\n"); + } else { + json_router = json_object_new_object(); + json_object_string_add( + json_router, "attachedRouterId", + inet_ntop(AF_INET, addr, buf, + sizeof(buf))); + json_object_object_add(json_attached_rt, + inet_ntop(AF_INET, addr, + buf, + sizeof(buf)), + json_router); + } + } + + if (json) + json_object_object_add(json, "attchedRouters", + json_attached_rt); + + return 0; +} + +/* Show summary-LSA detail information. */ +static int show_summary_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + if (lsa != NULL) { + struct summary_lsa *sl = (struct summary_lsa *)lsa->data; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) { + vty_out(vty, " Network Mask: /%d\n", + ip_masklen(sl->mask)); + vty_out(vty, " TOS: 0 Metric: %d\n", + GET_METRIC(sl->metric)); + vty_out(vty, "\n"); + } else { + json_object_int_add(json, "networkMask", + ip_masklen(sl->mask)); + json_object_int_add(json, "tos0Metric", + GET_METRIC(sl->metric)); + } + } + + return 0; +} + +/* Show summary-ASBR-LSA detail information. */ +static int show_summary_asbr_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + if (lsa != NULL) { + struct summary_lsa *sl = (struct summary_lsa *)lsa->data; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) { + vty_out(vty, " Network Mask: /%d\n", + ip_masklen(sl->mask)); + vty_out(vty, " TOS: 0 Metric: %d\n", + GET_METRIC(sl->metric)); + vty_out(vty, "\n"); + } else { + json_object_int_add(json, "networkMask", + ip_masklen(sl->mask)); + json_object_int_add(json, "tos0Metric", + GET_METRIC(sl->metric)); + } + } + + return 0; +} + +/* Show AS-external-LSA detail information. */ +static int show_as_external_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + int tos = 0; + + if (lsa != NULL) { + struct as_external_lsa *al = + (struct as_external_lsa *)lsa->data; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) { + vty_out(vty, " Network Mask: /%d\n", + ip_masklen(al->mask)); + vty_out(vty, " Metric Type: %s\n", + IS_EXTERNAL_METRIC(al->e[0].tos) + ? "2 (Larger than any link state path)" + : "1"); + vty_out(vty, " TOS: 0\n"); + vty_out(vty, " Metric: %d\n", + GET_METRIC(al->e[0].metric)); + vty_out(vty, " Forward Address: %pI4\n", + &al->e[0].fwd_addr); + vty_out(vty, + " External Route Tag: %" ROUTE_TAG_PRI "\n\n", + (route_tag_t)ntohl(al->e[0].route_tag)); + } else { + json_object_int_add(json, "networkMask", + ip_masklen(al->mask)); + json_object_string_add( + json, "metricType", + IS_EXTERNAL_METRIC(al->e[0].tos) + ? "E2 (Larger than any link state path)" + : "E1"); + json_object_int_add(json, "tos", tos); + json_object_int_add(json, "metric", + GET_METRIC(al->e[0].metric)); + json_object_string_addf(json, "forwardAddress", "%pI4", + &(al->e[0].fwd_addr)); + json_object_int_add( + json, "externalRouteTag", + (route_tag_t)ntohl(al->e[0].route_tag)); + } + } + + return 0; +} + +/* Show AS-NSSA-LSA detail information. */ +static int show_as_nssa_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + int tos = 0; + + if (lsa != NULL) { + struct as_external_lsa *al = + (struct as_external_lsa *)lsa->data; + + show_ip_ospf_database_header(vty, lsa, json); + + if (!json) { + vty_out(vty, " Network Mask: /%d\n", + ip_masklen(al->mask)); + vty_out(vty, " Metric Type: %s\n", + IS_EXTERNAL_METRIC(al->e[0].tos) + ? "2 (Larger than any link state path)" + : "1"); + vty_out(vty, " TOS: 0\n"); + vty_out(vty, " Metric: %d\n", + GET_METRIC(al->e[0].metric)); + vty_out(vty, " NSSA: Forward Address: %pI4\n", + &al->e[0].fwd_addr); + vty_out(vty, + " External Route Tag: %" ROUTE_TAG_PRI + "\n\n", + (route_tag_t)ntohl(al->e[0].route_tag)); + } else { + json_object_int_add(json, "networkMask", + ip_masklen(al->mask)); + json_object_string_add( + json, "metricType", + IS_EXTERNAL_METRIC(al->e[0].tos) + ? "E2 (Larger than any link state path)" + : "E1"); + json_object_int_add(json, "tos", tos); + json_object_int_add(json, "metric", + GET_METRIC(al->e[0].metric)); + json_object_string_addf(json, "nssaForwardAddress", + "%pI4", &al->e[0].fwd_addr); + json_object_int_add( + json, "externalRouteTag", + (route_tag_t)ntohl(al->e[0].route_tag)); + } + } + + return 0; +} + +static int show_func_dummy(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + return 0; +} + +static int show_opaque_lsa_detail(struct vty *vty, struct ospf_lsa *lsa, + json_object *json) +{ + if (lsa != NULL) { + show_ip_ospf_database_header(vty, lsa, json); + show_opaque_info_detail(vty, lsa, json); + if (!json) + vty_out(vty, "\n"); + } + return 0; +} + +int (*show_function[])(struct vty *, struct ospf_lsa *, json_object *) = { + NULL, + show_router_lsa_detail, + show_network_lsa_detail, + show_summary_lsa_detail, + show_summary_asbr_lsa_detail, + show_as_external_lsa_detail, + show_func_dummy, + show_as_nssa_lsa_detail, /* almost same as external */ + NULL, /* type-8 */ + show_opaque_lsa_detail, + show_opaque_lsa_detail, + show_opaque_lsa_detail, +}; + +static void show_lsa_prefix_set(struct vty *vty, struct prefix_ls *lp, + struct in_addr *id, struct in_addr *adv_router) +{ + memset(lp, 0, sizeof(struct prefix_ls)); + lp->family = AF_UNSPEC; + if (id == NULL) + lp->prefixlen = 0; + else if (adv_router == NULL) { + lp->prefixlen = IPV4_MAX_BITLEN; + lp->id = *id; + } else { + lp->prefixlen = 64; + lp->id = *id; + lp->adv_router = *adv_router; + } +} + +static void show_lsa_detail_proc(struct vty *vty, struct route_table *rt, + struct in_addr *id, struct in_addr *adv_router, + json_object *json) +{ + struct prefix_ls lp; + struct route_node *rn, *start; + struct ospf_lsa *lsa; + json_object *json_lsa = NULL; + + show_lsa_prefix_set(vty, &lp, id, adv_router); + start = route_node_get(rt, (struct prefix *)&lp); + if (start) { + route_lock_node(start); + for (rn = start; rn; rn = route_next_until(rn, start)) + if ((lsa = rn->info)) { + if (show_function[lsa->data->type] != NULL) { + if (json) { + json_lsa = + json_object_new_object(); + json_object_array_add(json, + json_lsa); + } + + show_function[lsa->data->type]( + vty, lsa, json_lsa); + } + } + route_unlock_node(start); + } +} + +/* Show detail LSA information + -- if id is NULL then show all LSAs. */ +static void show_lsa_detail(struct vty *vty, struct ospf *ospf, int type, + struct in_addr *id, struct in_addr *adv_router, + json_object *json) +{ + struct listnode *node; + struct ospf_area *area; + char buf[PREFIX_STRLEN]; + json_object *json_lsa_type = NULL; + json_object *json_areas = NULL; + json_object *json_lsa_array = NULL; + + switch (type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + if (!json) + vty_out(vty, " %s \n\n", + show_database_desc[type]); + else + json_lsa_array = json_object_new_array(); + + show_lsa_detail_proc(vty, AS_LSDB(ospf, type), id, adv_router, + json_lsa_array); + if (json) + json_object_object_add(json, + show_database_desc_json[type], + json_lsa_array); + + break; + default: + if (json) + json_areas = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (!json) { + vty_out(vty, + "\n %s (Area %s)\n\n", + show_database_desc[type], + ospf_area_desc_string(area)); + } else { + json_lsa_array = json_object_new_array(); + json_object_object_add(json_areas, + inet_ntop(AF_INET, + &area->area_id, + buf, + sizeof(buf)), + json_lsa_array); + } + + show_lsa_detail_proc(vty, AREA_LSDB(area, type), id, + adv_router, json_lsa_array); + } + + if (json) { + json_lsa_type = json_object_new_object(); + json_object_object_add(json_lsa_type, "areas", + json_areas); + json_object_object_add(json, + show_database_desc_json[type], + json_lsa_type); + } + break; + } +} + +static void show_lsa_detail_adv_router_proc(struct vty *vty, + struct route_table *rt, + struct in_addr *adv_router, + json_object *json) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + json_object *json_lsa = NULL; + + for (rn = route_top(rt); rn; rn = route_next(rn)) + if ((lsa = rn->info)) { + if (IPV4_ADDR_SAME(adv_router, + &lsa->data->adv_router)) { + if (CHECK_FLAG(lsa->flags, OSPF_LSA_LOCAL_XLT)) + continue; + if (json) { + json_lsa = json_object_new_object(); + json_object_array_add(json, json_lsa); + } + + if (show_function[lsa->data->type] != NULL) + show_function[lsa->data->type]( + vty, lsa, json_lsa); + } + } +} + +/* Show detail LSA information. */ +static void show_lsa_detail_adv_router(struct vty *vty, struct ospf *ospf, + int type, struct in_addr *adv_router, + json_object *json) +{ + struct listnode *node; + struct ospf_area *area; + char buf[PREFIX_STRLEN]; + json_object *json_lsa_type = NULL; + json_object *json_areas = NULL; + json_object *json_lsa_array = NULL; + + if (json) + json_lsa_type = json_object_new_object(); + + switch (type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + if (!json) + vty_out(vty, " %s \n\n", + show_database_desc[type]); + else + json_lsa_array = json_object_new_array(); + + show_lsa_detail_adv_router_proc(vty, AS_LSDB(ospf, type), + adv_router, json_lsa_array); + if (json) + json_object_object_add(json, + show_database_desc_json[type], + json_lsa_array); + break; + default: + if (json) + json_areas = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (!json) { + vty_out(vty, + "\n %s (Area %s)\n\n", + show_database_desc[type], + ospf_area_desc_string(area)); + } else { + json_lsa_array = json_object_new_array(); + json_object_object_add( + json_areas, + inet_ntop(AF_INET, &area->area_id, buf, + sizeof(buf)), + json_lsa_array); + } + + show_lsa_detail_adv_router_proc( + vty, AREA_LSDB(area, type), adv_router, + json_lsa_array); + } + + if (json) { + json_object_object_add(json_lsa_type, "areas", + json_areas); + json_object_object_add(json, + show_database_desc_json[type], + json_lsa_type); + } + break; + } +} + +void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self, + json_object *json) +{ + struct ospf_lsa *lsa; + struct route_node *rn; + struct ospf_area *area; + struct listnode *node; + char buf[PREFIX_STRLEN]; + json_object *json_areas = NULL; + json_object *json_area = NULL; + json_object *json_lsa = NULL; + int type; + json_object *json_lsa_array = NULL; + uint32_t count; + + if (json) + json_areas = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (json) + json_area = json_object_new_object(); + + for (type = OSPF_MIN_LSA; type < OSPF_MAX_LSA; type++) { + count = 0; + switch (type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + continue; + default: + break; + } + if (ospf_lsdb_count_self(area->lsdb, type) > 0 + || (!self + && ospf_lsdb_count(area->lsdb, type) > 0)) { + + if (!json) { + vty_out(vty, + " %s (Area %s)\n\n", + show_database_desc[type], + ospf_area_desc_string(area)); + vty_out(vty, "%s\n", + show_database_header[type]); + } else { + json_lsa_array = + json_object_new_array(); + json_object_object_add( + json_area, + show_database_desc_json[type], + json_lsa_array); + } + + LSDB_LOOP (AREA_LSDB(area, type), rn, lsa) { + if (json) { + json_lsa = + json_object_new_object(); + json_object_array_add( + json_lsa_array, + json_lsa); + } + + count += show_lsa_summary( + vty, lsa, self, json_lsa); + } + + if (!json) + vty_out(vty, "\n"); + else + json_object_int_add( + json_area, + + show_database_desc_count_json + [type], + count); + } + } + if (json) + json_object_object_add(json_areas, + inet_ntop(AF_INET, + &area->area_id, + buf, sizeof(buf)), + json_area); + } + + if (json) + json_object_object_add(json, "areas", json_areas); + + for (type = OSPF_MIN_LSA; type < OSPF_MAX_LSA; type++) { + count = 0; + switch (type) { + case OSPF_AS_EXTERNAL_LSA: + case OSPF_OPAQUE_AS_LSA: + break; + default: + continue; + } + if (ospf_lsdb_count_self(ospf->lsdb, type) + || (!self && ospf_lsdb_count(ospf->lsdb, type))) { + if (!json) { + vty_out(vty, " %s\n\n", + show_database_desc[type]); + vty_out(vty, "%s\n", + show_database_header[type]); + } else { + json_lsa_array = json_object_new_array(); + json_object_object_add( + json, show_database_desc_json[type], + json_lsa_array); + } + + LSDB_LOOP (AS_LSDB(ospf, type), rn, lsa) { + if (json) { + json_lsa = json_object_new_object(); + json_object_array_add(json_lsa_array, + json_lsa); + } + + count += show_lsa_summary(vty, lsa, self, + json_lsa); + } + + if (!json) + vty_out(vty, "\n"); + else + json_object_int_add( + json, + show_database_desc_count_json[type], + count); + } + } + + if (!json) + vty_out(vty, "\n"); +} + +static void show_ip_ospf_database_maxage(struct vty *vty, struct ospf *ospf, + json_object *json) +{ + struct route_node *rn; + char buf[PREFIX_STRLEN]; + json_object *json_maxage = NULL; + + if (!json) + vty_out(vty, "\n MaxAge Link States:\n\n"); + else + json_maxage = json_object_new_object(); + + for (rn = route_top(ospf->maxage_lsa); rn; rn = route_next(rn)) { + struct ospf_lsa *lsa; + json_object *json_lsa = NULL; + + if ((lsa = rn->info) != NULL) { + if (!json) { + vty_out(vty, "Link type: %d\n", + lsa->data->type); + vty_out(vty, "Link State ID: %pI4\n", + &lsa->data->id); + vty_out(vty, "Advertising Router: %pI4\n", + &lsa->data->adv_router); + vty_out(vty, "LSA lock count: %d\n", lsa->lock); + vty_out(vty, "\n"); + } else { + json_lsa = json_object_new_object(); + json_object_int_add(json_lsa, "linkType", + lsa->data->type); + json_object_string_addf(json_lsa, "linkStateId", + "%pI4", &lsa->data->id); + json_object_string_addf( + json_lsa, "advertisingRouter", "%pI4", + &lsa->data->adv_router); + json_object_int_add(json_lsa, "lsaLockCount", + lsa->lock); + json_object_object_add( + json_maxage, + inet_ntop(AF_INET, + &lsa->data->id, + buf, sizeof(buf)), + json_lsa); + } + } + } + if (json) + json_object_object_add(json, "maxAgeLinkStates", json_maxage); +} + +#define OSPF_LSA_TYPE_NSSA_DESC "NSSA external link state\n" +#define OSPF_LSA_TYPE_NSSA_CMD_STR "|nssa-external" + +#define OSPF_LSA_TYPE_OPAQUE_LINK_DESC "Link local Opaque-LSA\n" +#define OSPF_LSA_TYPE_OPAQUE_AREA_DESC "Link area Opaque-LSA\n" +#define OSPF_LSA_TYPE_OPAQUE_AS_DESC "Link AS Opaque-LSA\n" +#define OSPF_LSA_TYPE_OPAQUE_CMD_STR "|opaque-link|opaque-area|opaque-as" + +#define OSPF_LSA_TYPES_DESC \ + "ASBR summary link states\n" \ + "External link states\n" \ + "Network link states\n" \ + "Router link states\n" \ + "Network summary link states\n" OSPF_LSA_TYPE_NSSA_DESC \ + OSPF_LSA_TYPE_OPAQUE_LINK_DESC OSPF_LSA_TYPE_OPAQUE_AREA_DESC \ + OSPF_LSA_TYPE_OPAQUE_AS_DESC + +static int +show_ip_ospf_database_common(struct vty *vty, struct ospf *ospf, bool maxage, + bool self, bool detail, const char *type_name, + struct in_addr *lsid, struct in_addr *adv_router, + bool use_vrf, json_object *json, bool uj) +{ + int type; + json_object *json_vrf = NULL; + + if (uj) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + if (ospf->instance) { + if (uj) + json_object_int_add(json_vrf, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + /* Show Router ID. */ + if (uj) { + json_object_string_addf(json_vrf, "routerId", "%pI4", + &ospf->router_id); + } else { + vty_out(vty, "\n OSPF Router with ID (%pI4)\n\n", + &ospf->router_id); + } + + /* Show MaxAge LSAs */ + if (maxage) { + show_ip_ospf_database_maxage(vty, ospf, json_vrf); + if (json) { + if (use_vrf) { + if (ospf->vrf_id == VRF_DEFAULT) + json_object_object_add(json, "default", + json_vrf); + else + json_object_object_add(json, ospf->name, + json_vrf); + } + } + return CMD_SUCCESS; + } + + /* Show all LSAs. */ + if (!type_name) { + if (detail) { + for (int i = OSPF_ROUTER_LSA; i <= OSPF_OPAQUE_AS_LSA; + i++) { + switch (i) { + case OSPF_GROUP_MEMBER_LSA: + case OSPF_EXTERNAL_ATTRIBUTES_LSA: + /* ignore deprecated LSA types */ + continue; + default: + break; + } + + if (adv_router && !lsid) + show_lsa_detail_adv_router(vty, ospf, i, + adv_router, + json_vrf); + else + show_lsa_detail(vty, ospf, i, lsid, + adv_router, json_vrf); + } + } else + show_ip_ospf_database_summary(vty, ospf, self, + json_vrf); + + if (json) { + if (use_vrf) { + if (ospf->vrf_id == VRF_DEFAULT) + json_object_object_add(json, "default", + json_vrf); + else + json_object_object_add(json, ospf->name, + json_vrf); + } + } + return CMD_SUCCESS; + } + + /* Set database type to show. */ + if (strncmp(type_name, "r", 1) == 0) + type = OSPF_ROUTER_LSA; + else if (strncmp(type_name, "ne", 2) == 0) + type = OSPF_NETWORK_LSA; + else if (strncmp(type_name, "ns", 2) == 0) + type = OSPF_AS_NSSA_LSA; + else if (strncmp(type_name, "su", 2) == 0) + type = OSPF_SUMMARY_LSA; + else if (strncmp(type_name, "a", 1) == 0) + type = OSPF_ASBR_SUMMARY_LSA; + else if (strncmp(type_name, "e", 1) == 0) + type = OSPF_AS_EXTERNAL_LSA; + else if (strncmp(type_name, "opaque-l", 8) == 0) + type = OSPF_OPAQUE_LINK_LSA; + else if (strncmp(type_name, "opaque-ar", 9) == 0) + type = OSPF_OPAQUE_AREA_LSA; + else if (strncmp(type_name, "opaque-as", 9) == 0) + type = OSPF_OPAQUE_AS_LSA; + else { + if (uj) { + if (use_vrf) + json_object_free(json_vrf); + } + return CMD_WARNING; + } + + if (adv_router && !lsid) + show_lsa_detail_adv_router(vty, ospf, type, adv_router, + json_vrf); + else + show_lsa_detail(vty, ospf, type, lsid, adv_router, json_vrf); + + if (json) { + if (use_vrf) { + if (ospf->vrf_id == VRF_DEFAULT) + json_object_object_add(json, "default", + json_vrf); + else + json_object_object_add(json, ospf->name, + json_vrf); + } + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_ospf_database, + show_ip_ospf_database_cmd, + "show ip ospf [(1-65535)$instance_id] [vrf $vrf_name] database\ + [<\ + max-age$maxage\ + |self-originate$selforig\ + |<\ + detail$detail\ + |$type_name\ + >\ + [{\ + A.B.C.D$lsid\ + |\ + }]\ + >]\ + [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Database summary\n" + "LSAs in MaxAge list\n" + "Self-originated link states\n" + "Show detailed information\n" + OSPF_LSA_TYPES_DESC + "Link State ID (as an IP address)\n" + "Advertising Router link states\n" + "Advertising Router (as an IP address)\n" + "Self-originated link states\n" + JSON_STR) +{ + struct ospf *ospf; + int ret = CMD_SUCCESS; + bool use_vrf = !!vrf_name; + bool uj = use_json(argc, argv); + struct in_addr *lsid_p = NULL; + struct in_addr *adv_router_p = NULL; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + if (lsid_str) + lsid_p = &lsid; + if (adv_router_str) + adv_router_p = &adv_router; + + if (vrf_name && strmatch(vrf_name, "all")) { + struct listnode *node; + bool ospf_output = false; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + if (ospf->instance != instance_id) + continue; + + if (adv_router_self) + adv_router_p = &ospf->router_id; + + ospf_output = true; + ret = show_ip_ospf_database_common( + vty, ospf, !!maxage, !!selforig, !!detail, + type_name, lsid_p, adv_router_p, use_vrf, json, + uj); + } + + if (!ospf_output && !uj) + vty_out(vty, "%% OSPF is not enabled\n"); + } else { + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + ospf = ospf_lookup_by_inst_name(instance_id, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, "%% OSPF instance not found\n"); + return CMD_SUCCESS; + } + if (adv_router_self) + adv_router_p = &ospf->router_id; + + ret = (show_ip_ospf_database_common( + vty, ospf, !!maxage, !!selforig, !!detail, type_name, + lsid_p, adv_router_p, use_vrf, json, uj)); + } + + if (uj) + vty_json(vty, json); + + return ret; +} + +DEFUN (ip_ospf_authentication_args, + ip_ospf_authentication_args_addr_cmd, + "ip ospf authentication [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Enable authentication on this interface\n" + "Use null authentication\n" + "Use message-digest authentication\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_encryption = 3; + int idx_ipv4 = argc-1; + struct in_addr addr; + int ret; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argv[idx_ipv4]->type == IPV4_TKN) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + /* Handle null authentication */ + if (argv[idx_encryption]->arg[0] == 'n') { + SET_IF_PARAM(params, auth_type); + params->auth_type = OSPF_AUTH_NULL; + return CMD_SUCCESS; + } + + /* Handle message-digest authentication */ + if (argv[idx_encryption]->arg[0] == 'm') { + SET_IF_PARAM(params, auth_type); + params->auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + UNSET_IF_PARAM(params, keychain_name); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + return CMD_SUCCESS; + } + + if (argv[idx_encryption]->arg[0] == 'k') { + SET_IF_PARAM(params, auth_type); + params->auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + SET_IF_PARAM(params, keychain_name); + params->keychain_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS, argv[idx_encryption+1]->arg); + UNSET_IF_PARAM(params, auth_crypt); + return CMD_SUCCESS; + } + + vty_out(vty, "You shouldn't get here!\n"); + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (ip_ospf_authentication, + ip_ospf_authentication_addr_cmd, + "ip ospf authentication [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Enable authentication on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 3; + struct in_addr addr; + int ret; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argc == 4) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + SET_IF_PARAM(params, auth_type); + params->auth_type = OSPF_AUTH_SIMPLE; + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_authentication_args, + no_ip_ospf_authentication_args_addr_cmd, + "no ip ospf authentication [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Enable authentication on this interface\n" + "Use null authentication\n" + "Use message-digest authentication\n" + "Use a key-chain for cryptographic authentication keys\n" + "Key-chain name\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_encryption = 4; + int idx_ipv4 = argc-1; + struct in_addr addr; + int ret; + struct ospf_if_params *params; + struct route_node *rn; + int auth_type; + + params = IF_DEF_PARAMS(ifp); + + if (argv[idx_ipv4]->type == IPV4_TKN) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) { + vty_out(vty, "Ip Address specified is unknown\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + } else { + if (argv[idx_encryption]->arg[0] == 'n') { + auth_type = OSPF_AUTH_NULL; + } else if (argv[idx_encryption]->arg[0] == 'm' || + argv[idx_encryption]->arg[0] == 'k') { + auth_type = OSPF_AUTH_CRYPTOGRAPHIC; + } else { + vty_out(vty, "Unexpected input encountered\n"); + return CMD_WARNING_CONFIG_FAILED; + } + /* + * Here we have a case where the user has entered + * 'no ip ospf authentication (null | message_digest )' + * we need to find if we have any ip addresses underneath it + * that + * correspond to the associated type. + */ + if (params->auth_type == auth_type) { + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); + } + + for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; + rn = route_next(rn)) { + if ((params = rn->info)) { + if (params->auth_type == auth_type) { + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + XFREE(MTYPE_OSPF_IF_PARAMS, params->keychain_name); + UNSET_IF_PARAM(params, keychain_name); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params( + ifp, rn->p.u.prefix4); + ospf_if_update_params( + ifp, rn->p.u.prefix4); + } + } + } + } + } + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_authentication, + no_ip_ospf_authentication_addr_cmd, + "no ip ospf authentication [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Enable authentication on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 4; + struct in_addr addr; + int ret; + struct ospf_if_params *params; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + + if (argc == 5) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) { + vty_out(vty, "Ip Address specified is unknown\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + } else { + /* + * When a user enters 'no ip ospf authentication' + * We should remove all authentication types from + * the interface. + */ + if ((params->auth_type == OSPF_AUTH_NULL) + || (params->auth_type == OSPF_AUTH_CRYPTOGRAPHIC) + || (params->auth_type == OSPF_AUTH_SIMPLE)) { + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + } + + for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; + rn = route_next(rn)) { + if ((params = rn->info)) { + + if ((params->auth_type == OSPF_AUTH_NULL) + || (params->auth_type + == OSPF_AUTH_CRYPTOGRAPHIC) + || (params->auth_type + == OSPF_AUTH_SIMPLE)) { + params->auth_type = OSPF_AUTH_NOTSET; + UNSET_IF_PARAM(params, auth_type); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params( + ifp, rn->p.u.prefix4); + ospf_if_update_params( + ifp, rn->p.u.prefix4); + } + } + } + } + } + + return CMD_SUCCESS; +} + + +DEFUN (ip_ospf_authentication_key, + ip_ospf_authentication_key_addr_cmd, + "ip ospf authentication-key AUTH_KEY [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Authentication password (key)\n" + "The OSPF password (key)\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + strlcpy((char *)params->auth_simple, argv[3]->arg, + sizeof(params->auth_simple)); + SET_IF_PARAM(params, auth_simple); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_authentication_key, + ospf_authentication_key_cmd, + "ospf authentication-key AUTH_KEY [A.B.C.D]", + "OSPF interface commands\n" + VLINK_HELPSTR_AUTH_SIMPLE + "Address of interface\n") +{ + return ip_ospf_authentication_key(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_authentication_key, + no_ip_ospf_authentication_key_authkey_addr_cmd, + "no ip ospf authentication-key [AUTH_KEY [A.B.C.D]]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + VLINK_HELPSTR_AUTH_SIMPLE + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + memset(params->auth_simple, 0, OSPF_AUTH_SIMPLE_SIZE); + UNSET_IF_PARAM(params, auth_simple); + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_authentication_key, + no_ospf_authentication_key_authkey_addr_cmd, + "no ospf authentication-key [AUTH_KEY [A.B.C.D]]", + NO_STR + "OSPF interface commands\n" + VLINK_HELPSTR_AUTH_SIMPLE + "Address of interface\n") +{ + return no_ip_ospf_authentication_key(self, vty, argc, argv); +} + +DEFUN (ip_ospf_message_digest_key, + ip_ospf_message_digest_key_cmd, + "ip ospf message-digest-key (1-255) md5 KEY [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Message digest authentication password (key)\n" + "Key ID\n" + "Use MD5 algorithm\n" + "The OSPF password (key)\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct crypt_key *ck; + uint8_t key_id; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + int idx = 0; + + argv_find(argv, argc, "(1-255)", &idx); + char *keyid = argv[idx]->arg; + argv_find(argv, argc, "KEY", &idx); + char *cryptkey = argv[idx]->arg; + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + key_id = strtol(keyid, NULL, 10); + + /* Remove existing key, if any */ + ospf_crypt_key_delete(params->auth_crypt, key_id); + + ck = ospf_crypt_key_new(); + ck->key_id = (uint8_t)key_id; + strlcpy((char *)ck->auth_key, cryptkey, sizeof(ck->auth_key)); + + ospf_crypt_key_add(params->auth_crypt, ck); + SET_IF_PARAM(params, auth_crypt); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_message_digest_key, + ospf_message_digest_key_cmd, + "ospf message-digest-key (1-255) md5 KEY [A.B.C.D]", + "OSPF interface commands\n" + "Message digest authentication password (key)\n" + "Key ID\n" + "Use MD5 algorithm\n" + "The OSPF password (key)\n" + "Address of interface\n") +{ + return ip_ospf_message_digest_key(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_message_digest_key, + no_ip_ospf_message_digest_key_cmd, + "no ip ospf message-digest-key (1-255) [md5 KEY] [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Message digest authentication password (key)\n" + "Key ID\n" + "Use MD5 algorithm\n" + "The OSPF password (key)\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct crypt_key *ck; + int key_id; + struct in_addr addr; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + argv_find(argv, argc, "(1-255)", &idx); + char *keyid = argv[idx]->arg; + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + key_id = strtol(keyid, NULL, 10); + ck = ospf_crypt_key_lookup(params->auth_crypt, key_id); + if (ck == NULL) { + vty_out(vty, "OSPF: Key %d does not exist\n", key_id); + return CMD_WARNING_CONFIG_FAILED; + } + + ospf_crypt_key_delete(params->auth_crypt, key_id); + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_message_digest_key, + no_ospf_message_digest_key_cmd, + "no ospf message-digest-key (1-255) [md5 KEY] [A.B.C.D]", + NO_STR + "OSPF interface commands\n" + "Message digest authentication password (key)\n" + "Key ID\n" + "Use MD5 algorithm\n" + "The OSPF password (key)\n" + "Address of interface\n") +{ + return no_ip_ospf_message_digest_key(self, vty, argc, argv); +} + +DEFUN (ip_ospf_cost, + ip_ospf_cost_cmd, + "ip ospf cost (1-65535) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Interface cost\n" + "Cost\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + uint32_t cost = OSPF_OUTPUT_COST_DEFAULT; + struct in_addr addr; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + // get arguments + char *coststr = NULL, *ifaddr = NULL; + + argv_find(argv, argc, "(1-65535)", &idx); + coststr = argv[idx]->arg; + cost = strtol(coststr, NULL, 10); + + ifaddr = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; + if (ifaddr) { + if (!inet_aton(ifaddr, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + SET_IF_PARAM(params, output_cost_cmd); + params->output_cost_cmd = cost; + + ospf_if_recalculate_output_cost(ifp); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_cost, + ospf_cost_cmd, + "ospf cost (1-65535) [A.B.C.D]", + "OSPF interface commands\n" + "Interface cost\n" + "Cost\n" + "Address of interface\n") +{ + return ip_ospf_cost(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_cost, + no_ip_ospf_cost_cmd, + "no ip ospf cost [(1-65535)] [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Interface cost\n" + "Cost\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + // get arguments + char *ifaddr = NULL; + ifaddr = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; + + /* According to the semantics we are mimicking "no ip ospf cost N" is + * always treated as "no ip ospf cost" regardless of the actual value + * of N already configured for the interface. Thus ignore cost. */ + + if (ifaddr) { + if (!inet_aton(ifaddr, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, output_cost_cmd); + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + ospf_if_recalculate_output_cost(ifp); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_cost, + no_ospf_cost_cmd, + "no ospf cost [(1-65535)] [A.B.C.D]", + NO_STR + "OSPF interface commands\n" + "Interface cost\n" + "Cost\n" + "Address of interface\n") +{ + return no_ip_ospf_cost(self, vty, argc, argv); +} + +static void ospf_nbr_timer_update(struct ospf_interface *oi) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + + if (!nbr) + continue; + + nbr->v_inactivity = OSPF_IF_PARAM(oi, v_wait); + nbr->v_db_desc = OSPF_IF_PARAM(oi, retransmit_interval); + nbr->v_ls_req = OSPF_IF_PARAM(oi, retransmit_interval); + nbr->v_ls_upd = OSPF_IF_PARAM(oi, retransmit_interval); + } +} + +static int ospf_vty_dead_interval_set(struct vty *vty, const char *interval_str, + const char *nbr_str, + const char *fast_hello_str) +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + uint32_t seconds; + uint8_t hellomult; + struct in_addr addr; + int ret; + struct ospf_if_params *params; + struct ospf_interface *oi; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + + if (nbr_str) { + ret = inet_aton(nbr_str, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + if (interval_str) { + seconds = strtoul(interval_str, NULL, 10); + + /* reset fast_hello too, just to be sure */ + UNSET_IF_PARAM(params, fast_hello); + params->fast_hello = OSPF_FAST_HELLO_DEFAULT; + } else if (fast_hello_str) { + hellomult = strtoul(fast_hello_str, NULL, 10); + /* 1s dead-interval with sub-second hellos desired */ + seconds = OSPF_ROUTER_DEAD_INTERVAL_MINIMAL; + SET_IF_PARAM(params, fast_hello); + params->fast_hello = hellomult; + } else { + vty_out(vty, + "Please specify dead-interval or hello-multiplier\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + SET_IF_PARAM(params, v_wait); + params->v_wait = seconds; + params->is_v_wait_set = true; + + /* Update timer values in neighbor structure. */ + if (nbr_str) { + struct ospf *ospf = NULL; + + ospf = ifp->vrf->info; + if (ospf) { + oi = ospf_if_lookup_by_local_addr(ospf, ifp, addr); + if (oi) + ospf_nbr_timer_update(oi); + } + } else { + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) + if ((oi = rn->info)) + ospf_nbr_timer_update(oi); + } + + if (params->fast_hello != OSPF_FAST_HELLO_DEFAULT) + ospf_reset_hello_timer(ifp, addr, false); + return CMD_SUCCESS; +} + +DEFUN (ip_ospf_dead_interval, + ip_ospf_dead_interval_cmd, + "ip ospf dead-interval (1-65535) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Interval time after which a neighbor is declared down\n" + "Seconds\n" + "Address of interface\n") +{ + int idx = 0; + char *interval = argv_find(argv, argc, "(1-65535)", &idx) + ? argv[idx]->arg + : NULL; + char *ifaddr = + argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx]->arg : NULL; + return ospf_vty_dead_interval_set(vty, interval, ifaddr, NULL); +} + + +DEFUN_HIDDEN (ospf_dead_interval, + ospf_dead_interval_cmd, + "ospf dead-interval (1-65535) [A.B.C.D]", + "OSPF interface commands\n" + "Interval time after which a neighbor is declared down\n" + "Seconds\n" + "Address of interface\n") +{ + return ip_ospf_dead_interval(self, vty, argc, argv); +} + +DEFUN (ip_ospf_dead_interval_minimal, + ip_ospf_dead_interval_minimal_addr_cmd, + "ip ospf dead-interval minimal hello-multiplier (2-20) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Interval time after which a neighbor is declared down\n" + "Minimal 1s dead-interval with fast sub-second hellos\n" + "Hello multiplier factor\n" + "Number of Hellos to send each second\n" + "Address of interface\n") +{ + int idx_number = 5; + int idx_ipv4 = 6; + if (argc == 7) + return ospf_vty_dead_interval_set( + vty, NULL, argv[idx_ipv4]->arg, argv[idx_number]->arg); + else + return ospf_vty_dead_interval_set(vty, NULL, NULL, + argv[idx_number]->arg); +} + +DEFUN (no_ip_ospf_dead_interval, + no_ip_ospf_dead_interval_cmd, + "no ip ospf dead-interval [<(1-65535)|minimal hello-multiplier (2-20)> [A.B.C.D]]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Interval time after which a neighbor is declared down\n" + "Seconds\n" + "Minimal 1s dead-interval with fast sub-second hellos\n" + "Hello multiplier factor\n" + "Number of Hellos to send each second\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = argc - 1; + struct in_addr addr = {.s_addr = 0L}; + int ret; + struct ospf_if_params *params; + struct ospf_interface *oi; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + + if (argv[idx_ipv4]->type == IPV4_TKN) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, v_wait); + params->v_wait = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; + params->is_v_wait_set = false; + + UNSET_IF_PARAM(params, fast_hello); + params->fast_hello = OSPF_FAST_HELLO_DEFAULT; + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + /* Update timer values in neighbor structure. */ + if (argc == 1) { + struct ospf *ospf = NULL; + + ospf = ifp->vrf->info; + if (ospf) { + oi = ospf_if_lookup_by_local_addr(ospf, ifp, addr); + if (oi) + ospf_nbr_timer_update(oi); + } + } else { + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) + if ((oi = rn->info)) + ospf_nbr_timer_update(oi); + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_dead_interval, + no_ospf_dead_interval_cmd, + "no ospf dead-interval [<(1-65535)|minimal hello-multiplier (2-20)> [A.B.C.D]]", + NO_STR + "OSPF interface commands\n" + "Interval time after which a neighbor is declared down\n" + "Seconds\n" + "Minimal 1s dead-interval with fast sub-second hellos\n" + "Hello multiplier factor\n" + "Number of Hellos to send each second\n" + "Address of interface\n") +{ + return no_ip_ospf_dead_interval(self, vty, argc, argv); +} + +DEFUN (ip_ospf_hello_interval, + ip_ospf_hello_interval_cmd, + "ip ospf hello-interval (1-65535) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Time between HELLO packets\n" + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr = {.s_addr = 0L}; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + uint32_t seconds = 0; + bool is_addr = false; + uint32_t old_interval = 0; + + argv_find(argv, argc, "(1-65535)", &idx); + seconds = strtol(argv[idx]->arg, NULL, 10); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + is_addr = true; + } + + old_interval = params->v_hello; + + /* Return, if same interval is configured. */ + if (old_interval == seconds) + return CMD_SUCCESS; + + SET_IF_PARAM(params, v_hello); + params->v_hello = seconds; + + if (!params->is_v_wait_set) { + SET_IF_PARAM(params, v_wait); + /* As per RFC 4062 + * The router dead interval should + * be some multiple of the HelloInterval (perhaps 4 times the + * hello interval) and must be the same for all routers + * attached to a common network. + */ + params->v_wait = 4 * seconds; + } + + ospf_reset_hello_timer(ifp, addr, is_addr); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_hello_interval, + ospf_hello_interval_cmd, + "ospf hello-interval (1-65535) [A.B.C.D]", + "OSPF interface commands\n" + "Time between HELLO packets\n" + "Seconds\n" + "Address of interface\n") +{ + return ip_ospf_hello_interval(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_hello_interval, + no_ip_ospf_hello_interval_cmd, + "no ip ospf hello-interval [(1-65535) [A.B.C.D]]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Time between HELLO packets\n" // ignored + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr = {.s_addr = 0L}; + struct ospf_if_params *params; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, v_hello); + params->v_hello = OSPF_HELLO_INTERVAL_DEFAULT; + + if (!params->is_v_wait_set) { + UNSET_IF_PARAM(params, v_wait); + params->v_wait = OSPF_ROUTER_DEAD_INTERVAL_DEFAULT; + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + oi->type = IF_DEF_PARAMS(ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + + if (oi->state > ISM_Down) { + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } + } + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_hello_interval, + no_ospf_hello_interval_cmd, + "no ospf hello-interval [(1-65535) [A.B.C.D]]", + NO_STR + "OSPF interface commands\n" + "Time between HELLO packets\n" // ignored + "Seconds\n" + "Address of interface\n") +{ + return no_ip_ospf_hello_interval(self, vty, argc, argv); +} + +DEFUN(ip_ospf_network, ip_ospf_network_cmd, + "ip ospf network ", + "IP Information\n" + "OSPF interface commands\n" + "Network type\n" + "Specify OSPF broadcast multi-access network\n" + "Specify OSPF NBMA network\n" + "Specify OSPF point-to-multipoint network\n" + "Specify OSPF delayed reflooding of LSAs received on P2MP interface\n" + "Specify OSPF point-to-multipoint network doesn't support broadcast\n" + "Specify OSPF point-to-point network\n" + "Specify OSPF point-to-point DMVPN network\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + int old_type = IF_DEF_PARAMS(ifp)->type; + uint8_t old_ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + uint8_t old_p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; + uint8_t old_p2mp_non_broadcast = IF_DEF_PARAMS(ifp)->p2mp_non_broadcast; + struct route_node *rn; + + if (old_type == OSPF_IFTYPE_LOOPBACK) { + vty_out(vty, + "This is a loopback interface. Can't set network type.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + IF_DEF_PARAMS(ifp)->ptp_dmvpn = 0; + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = + OSPF_P2MP_DELAY_REFLOOD_DEFAULT; + IF_DEF_PARAMS(ifp)->p2mp_non_broadcast = OSPF_P2MP_NON_BROADCAST_DEFAULT; + + if (argv_find(argv, argc, "broadcast", &idx)) + IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_BROADCAST; + else if (argv_find(argv, argc, "point-to-multipoint", &idx)) { + IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOMULTIPOINT; + if (argv_find(argv, argc, "delay-reflood", &idx)) + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = true; + if (argv_find(argv, argc, "non-broadcast", &idx)) + IF_DEF_PARAMS(ifp)->p2mp_non_broadcast = true; + } else if (argv_find(argv, argc, "non-broadcast", &idx)) + IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_NBMA; + else if (argv_find(argv, argc, "point-to-point", &idx)) { + IF_DEF_PARAMS(ifp)->type = OSPF_IFTYPE_POINTOPOINT; + if (argv_find(argv, argc, "dmvpn", &idx)) + IF_DEF_PARAMS(ifp)->ptp_dmvpn = 1; + } + + IF_DEF_PARAMS(ifp)->type_cfg = true; + + if (IF_DEF_PARAMS(ifp)->type == old_type && + IF_DEF_PARAMS(ifp)->ptp_dmvpn == old_ptp_dmvpn && + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood == old_p2mp_delay_reflood && + IF_DEF_PARAMS(ifp)->p2mp_non_broadcast == old_p2mp_non_broadcast) + return CMD_SUCCESS; + + SET_IF_PARAM(IF_DEF_PARAMS(ifp), type); + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + oi->type = IF_DEF_PARAMS(ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; + oi->p2mp_non_broadcast = IF_DEF_PARAMS(ifp)->p2mp_non_broadcast; + + /* + * The OSPF interface only needs to be flapped if the network + * type or DMVPN parameter changes. + */ + if (IF_DEF_PARAMS(ifp)->type != old_type || + IF_DEF_PARAMS(ifp)->ptp_dmvpn != old_ptp_dmvpn || + IF_DEF_PARAMS(ifp)->p2mp_non_broadcast != + old_p2mp_non_broadcast) { + if (oi->state > ISM_Down) { + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } + } + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_network, + ospf_network_cmd, + "ospf network ", + "OSPF interface commands\n" + "Network type\n" + "Specify OSPF broadcast multi-access network\n" + "Specify OSPF NBMA network\n" + "Specify OSPF point-to-multipoint network\n" + "Specify OSPF point-to-point network\n") +{ + return ip_ospf_network(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_network, + no_ip_ospf_network_cmd, + "no ip ospf network []", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Network type\n" + "Specify OSPF broadcast multi-access network\n" + "Specify OSPF NBMA network\n" + "Specify OSPF point-to-multipoint network\n" + "Specify OSPF point-to-point network\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int old_type = IF_DEF_PARAMS(ifp)->type; + struct route_node *rn; + + IF_DEF_PARAMS(ifp)->type = ospf_default_iftype(ifp); + IF_DEF_PARAMS(ifp)->type_cfg = false; + IF_DEF_PARAMS(ifp)->ptp_dmvpn = 0; + IF_DEF_PARAMS(ifp)->p2mp_delay_reflood = + OSPF_P2MP_DELAY_REFLOOD_DEFAULT; + IF_DEF_PARAMS(ifp)->p2mp_non_broadcast = OSPF_P2MP_NON_BROADCAST_DEFAULT; + + if (IF_DEF_PARAMS(ifp)->type == old_type) + return CMD_SUCCESS; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + oi->type = IF_DEF_PARAMS(ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(ifp)->p2mp_delay_reflood; + + if (oi->state > ISM_Down) { + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_network, + no_ospf_network_cmd, + "no ospf network []", + NO_STR + "OSPF interface commands\n" + "Network type\n" + "Specify OSPF broadcast multi-access network\n" + "Specify OSPF NBMA network\n" + "Specify OSPF point-to-multipoint network\n" + "Specify OSPF point-to-point network\n") +{ + return no_ip_ospf_network(self, vty, argc, argv); +} + +DEFUN (ip_ospf_priority, + ip_ospf_priority_cmd, + "ip ospf priority (0-255) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Router priority\n" + "Priority\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + long priority; + struct route_node *rn; + struct in_addr addr; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + argv_find(argv, argc, "(0-255)", &idx); + priority = strtol(argv[idx]->arg, NULL, 10); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + SET_IF_PARAM(params, priority); + params->priority = priority; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + if (PRIORITY(oi) != OSPF_IF_PARAM(oi, priority)) { + PRIORITY(oi) = OSPF_IF_PARAM(oi, priority); + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); + } + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_priority, + ospf_priority_cmd, + "ospf priority (0-255) [A.B.C.D]", + "OSPF interface commands\n" + "Router priority\n" + "Priority\n" + "Address of interface\n") +{ + return ip_ospf_priority(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_priority, + no_ip_ospf_priority_cmd, + "no ip ospf priority [(0-255) [A.B.C.D]]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Router priority\n" // ignored + "Priority\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct route_node *rn; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, priority); + params->priority = OSPF_ROUTER_PRIORITY_DEFAULT; + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (!oi) + continue; + + if (PRIORITY(oi) != OSPF_IF_PARAM(oi, priority)) { + PRIORITY(oi) = OSPF_IF_PARAM(oi, priority); + OSPF_ISM_EVENT_SCHEDULE(oi, ISM_NeighborChange); + } + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_priority, + no_ospf_priority_cmd, + "no ospf priority [(0-255) [A.B.C.D]]", + NO_STR + "OSPF interface commands\n" + "Router priority\n" + "Priority\n" + "Address of interface\n") +{ + return no_ip_ospf_priority(self, vty, argc, argv); +} + +DEFUN (ip_ospf_retransmit_interval, + ip_ospf_retransmit_interval_addr_cmd, + "ip ospf retransmit-interval (1-65535) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Time between retransmitting lost link state advertisements\n" + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + uint32_t seconds; + struct in_addr addr; + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + argv_find(argv, argc, "(1-65535)", &idx); + seconds = strtol(argv[idx]->arg, NULL, 10); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + SET_IF_PARAM(params, retransmit_interval); + params->retransmit_interval = seconds; + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_retransmit_interval, + ospf_retransmit_interval_cmd, + "ospf retransmit-interval (1-65535) [A.B.C.D]", + "OSPF interface commands\n" + "Time between retransmitting lost link state advertisements\n" + "Seconds\n" + "Address of interface\n") +{ + return ip_ospf_retransmit_interval(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_retransmit_interval, + no_ip_ospf_retransmit_interval_addr_cmd, + "no ip ospf retransmit-interval [(1-65535)] [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Time between retransmitting lost link state advertisements\n" + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, retransmit_interval); + params->retransmit_interval = OSPF_RETRANSMIT_INTERVAL_DEFAULT; + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ospf_retransmit_interval, + no_ospf_retransmit_interval_cmd, + "no ospf retransmit-interval [(1-65535)] [A.B.C.D]", + NO_STR + "OSPF interface commands\n" + "Time between retransmitting lost link state advertisements\n" + "Seconds\n" + "Address of interface\n") +{ + return no_ip_ospf_retransmit_interval(self, vty, argc, argv); +} + +DEFPY (ip_ospf_gr_hdelay, + ip_ospf_gr_hdelay_cmd, + "ip ospf graceful-restart hello-delay (1-1800)", + IP_STR + "OSPF interface commands\n" + "Graceful Restart parameters\n" + "Delay the sending of the first hello packets.\n" + "Delay in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + /* Note: new or updated value won't affect ongoing graceful restart. */ + SET_IF_PARAM(params, v_gr_hello_delay); + params->v_gr_hello_delay = hello_delay; + + return CMD_SUCCESS; +} + +DEFPY (no_ip_ospf_gr_hdelay, + no_ip_ospf_gr_hdelay_cmd, + "no ip ospf graceful-restart hello-delay [(1-1800)]", + NO_STR + IP_STR + "OSPF interface commands\n" + "Graceful Restart parameters\n" + "Delay the sending of the first hello packets.\n" + "Delay in seconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + UNSET_IF_PARAM(params, v_gr_hello_delay); + params->v_gr_hello_delay = OSPF_HELLO_DELAY_DEFAULT; + + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi; + + oi = rn->info; + if (!oi) + continue; + + oi->gr.hello_delay.elapsed_seconds = 0; + EVENT_OFF(oi->gr.hello_delay.t_grace_send); + } + + return CMD_SUCCESS; +} + +DEFUN (ip_ospf_transmit_delay, + ip_ospf_transmit_delay_addr_cmd, + "ip ospf transmit-delay (1-65535) [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Link state transmit delay\n" + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + uint32_t seconds; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + argv_find(argv, argc, "(1-65535)", &idx); + seconds = strtol(argv[idx]->arg, NULL, 10); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + SET_IF_PARAM(params, transmit_delay); + params->transmit_delay = seconds; + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (ospf_transmit_delay, + ospf_transmit_delay_cmd, + "ospf transmit-delay (1-65535) [A.B.C.D]", + "OSPF interface commands\n" + "Link state transmit delay\n" + "Seconds\n" + "Address of interface\n") +{ + return ip_ospf_transmit_delay(self, vty, argc, argv); +} + +DEFUN (no_ip_ospf_transmit_delay, + no_ip_ospf_transmit_delay_addr_cmd, + "no ip ospf transmit-delay [(1-65535)] [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Link state transmit delay\n" + "Seconds\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct in_addr addr; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &addr)) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } + + UNSET_IF_PARAM(params, transmit_delay); + params->transmit_delay = OSPF_TRANSMIT_DELAY_DEFAULT; + + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + + return CMD_SUCCESS; +} + + +DEFUN_HIDDEN (no_ospf_transmit_delay, + no_ospf_transmit_delay_cmd, + "no ospf transmit-delay [(1-65535) [A.B.C.D]]", + NO_STR + "OSPF interface commands\n" + "Link state transmit delay\n" + "Seconds\n" + "Address of interface\n") +{ + return no_ip_ospf_transmit_delay(self, vty, argc, argv); +} + +DEFUN (ip_ospf_area, + ip_ospf_area_cmd, + "ip ospf [(1-65535)] area [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Instance ID\n" + "Enable OSPF on this interface\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + int format, ret; + struct in_addr area_id; + struct in_addr addr; + struct ospf_if_params *params = NULL; + struct route_node *rn; + struct ospf *ospf = NULL; + unsigned short instance = 0; + char *areaid; + uint32_t count = 0; + + if (argv_find(argv, argc, "(1-65535)", &idx)) + instance = strtol(argv[idx]->arg, NULL, 10); + + argv_find(argv, argc, "area", &idx); + areaid = argv[idx + 1]->arg; + + if (!instance) + ospf = ifp->vrf->info; + else + ospf = ospf_lookup_instance(instance); + + if (instance && instance != ospf_instance) { + /* + * At this point we know we have received + * an instance and there is no ospf instance + * associated with it. This means we are + * in a situation where we have an + * ospf command that is setup for a different + * process(instance). We need to safely + * remove the command from ourselves and + * allow the other instance(process) handle + * the configuration command. + */ + count = 0; + + params = IF_DEF_PARAMS(ifp); + if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { + UNSET_IF_PARAM(params, if_area); + count++; + } + + for (rn = route_top(IF_OIFS_PARAMS(ifp)); rn; rn = route_next(rn)) + if ((params = rn->info) && OSPF_IF_PARAM_CONFIGURED(params, if_area)) { + UNSET_IF_PARAM(params, if_area); + count++; + } + + if (count > 0) { + ospf = ifp->vrf->info; + if (ospf) + ospf_interface_area_unset(ospf, ifp); + } + + return CMD_NOT_MY_INSTANCE; + } + + ret = str2area_id(areaid, &area_id, &format); + if (ret < 0) { + vty_out(vty, "Please specify area by A.B.C.D|<0-4294967295>\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (memcmp(ifp->name, "VLINK", 5) == 0) { + vty_out(vty, "Cannot enable OSPF on a virtual link.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ospf) { + for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) { + if (rn->info != NULL) { + vty_out(vty, + "Please remove all network commands first.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + } + + params = IF_DEF_PARAMS(ifp); + if (OSPF_IF_PARAM_CONFIGURED(params, if_area) + && !IPV4_ADDR_SAME(¶ms->if_area, &area_id)) { + vty_out(vty, + "Must remove previous area config before changing ospf area \n"); + return CMD_WARNING_CONFIG_FAILED; + } + + // Check if we have an address arg and proccess it + if (argc == idx + 3) { + if (!inet_aton(argv[idx + 2]->arg, &addr)) { + vty_out(vty, + "Please specify Intf Address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + // update/create address-level params + params = ospf_get_if_params((ifp), (addr)); + if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { + if (!IPV4_ADDR_SAME(¶ms->if_area, &area_id)) { + vty_out(vty, + "Must remove previous area/address config before changing ospf area\n"); + return CMD_WARNING_CONFIG_FAILED; + } else + return CMD_SUCCESS; + } + ospf_if_update_params((ifp), (addr)); + } + + /* enable ospf on this interface with area_id */ + if (params) { + SET_IF_PARAM(params, if_area); + params->if_area = area_id; + params->if_area_id_fmt = format; + } + + if (ospf) + ospf_interface_area_set(ospf, ifp); + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_area, + no_ip_ospf_area_cmd, + "no ip ospf [(1-65535)] area [ [A.B.C.D]]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Instance ID\n" + "Disable OSPF on this interface\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx = 0; + struct ospf *ospf; + struct ospf_if_params *params; + unsigned short instance = 0; + struct in_addr addr; + struct in_addr area_id; + + if (argv_find(argv, argc, "(1-65535)", &idx)) + instance = strtol(argv[idx]->arg, NULL, 10); + + if (!instance) + ospf = ifp->vrf->info; + else + ospf = ospf_lookup_instance(instance); + + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + argv_find(argv, argc, "area", &idx); + + // Check if we have an address arg and proccess it + if (argc == idx + 3) { + if (!inet_aton(argv[idx + 2]->arg, &addr)) { + vty_out(vty, + "Please specify Intf Address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_lookup_if_params(ifp, addr); + if ((params) == NULL) + return CMD_SUCCESS; + } else + params = IF_DEF_PARAMS(ifp); + + area_id = params->if_area; + if (!OSPF_IF_PARAM_CONFIGURED(params, if_area)) { + vty_out(vty, + "Can't find specified interface area configuration.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + UNSET_IF_PARAM(params, if_area); + if (params != IF_DEF_PARAMS((ifp))) { + ospf_free_if_params((ifp), (addr)); + ospf_if_update_params((ifp), (addr)); + } + + if (ospf) { + ospf_interface_area_unset(ospf, ifp); + ospf_area_check_free(ospf, area_id); + } + + return CMD_SUCCESS; +} + +DEFUN (ip_ospf_passive, + ip_ospf_passive_cmd, + "ip ospf passive [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Suppress routing updates on an interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 3; + struct in_addr addr = {.s_addr = INADDR_ANY}; + struct ospf_if_params *params; + int ret; + + if (argc == 4) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } else { + params = IF_DEF_PARAMS(ifp); + } + + ospf_passive_interface_update(ifp, params, addr, OSPF_IF_PASSIVE); + + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_passive, + no_ip_ospf_passive_cmd, + "no ip ospf passive [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Enable routing updates on an interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 4; + struct in_addr addr = {.s_addr = INADDR_ANY}; + struct ospf_if_params *params; + int ret; + + if (argc == 5) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_lookup_if_params(ifp, addr); + if (params == NULL) + return CMD_SUCCESS; + } else { + params = IF_DEF_PARAMS(ifp); + } + + ospf_passive_interface_update(ifp, params, addr, OSPF_IF_ACTIVE); + + return CMD_SUCCESS; +} + +DEFUN (ospf_redistribute_source, + ospf_redistribute_source_cmd, + "redistribute " FRR_REDIST_STR_OSPFD " [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + REDIST_STR + FRR_REDIST_HELP_STR_OSPFD + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_protocol = 1; + int source; + int type = -1; + int metric = -1; + struct ospf_redist *red; + int idx = 0; + bool update = false; + + /* Get distribute source. */ + source = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (source < 0) + return CMD_WARNING_CONFIG_FAILED; + + /* Get metric value. */ + if (argv_find(argv, argc, "(0-16777214)", &idx)) { + if (!str2metric(argv[idx]->arg, &metric)) + return CMD_WARNING_CONFIG_FAILED; + } + idx = 1; + /* Get metric type. */ + if (argv_find(argv, argc, "(1-2)", &idx)) { + if (!str2metric_type(argv[idx]->arg, &type)) + return CMD_WARNING_CONFIG_FAILED; + } + idx = 1; + + red = ospf_redist_lookup(ospf, source, 0); + if (!red) + red = ospf_redist_add(ospf, source, 0); + else + update = true; + + /* Get route-map */ + if (argv_find(argv, argc, "route-map", &idx)) { + ospf_routemap_set(red, argv[idx + 1]->arg); + } else + ospf_routemap_unset(red); + + if (update) + return ospf_redistribute_update(ospf, red, source, 0, type, + metric); + else + return ospf_redistribute_set(ospf, red, source, 0, type, + metric); +} + +DEFUN (no_ospf_redistribute_source, + no_ospf_redistribute_source_cmd, + "no redistribute " FRR_REDIST_STR_OSPFD " [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + REDIST_STR + FRR_REDIST_HELP_STR_OSPFD + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_protocol = 2; + int source; + struct ospf_redist *red; + + source = proto_redistnum(AFI_IP, argv[idx_protocol]->text); + if (source < 0) + return CMD_WARNING_CONFIG_FAILED; + + red = ospf_redist_lookup(ospf, source, 0); + if (!red) + return CMD_SUCCESS; + + ospf_routemap_unset(red); + ospf_redist_del(ospf, source, 0); + + return ospf_redistribute_unset(ospf, source, 0); +} + +DEFUN (ospf_redistribute_instance_source, + ospf_redistribute_instance_source_cmd, + "redistribute (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + REDIST_STR + "Open Shortest Path First\n" + "Non-main Kernel Routing Table\n" + "Instance ID/Table ID\n" + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ospf_table = 1; + int idx_number = 2; + int idx = 3; + int source; + int type = -1; + int metric = -1; + unsigned short instance; + struct ospf_redist *red; + bool update = false; + + source = proto_redistnum(AFI_IP, argv[idx_ospf_table]->text); + + if (source < 0) { + vty_out(vty, "Unknown instance redistribution\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if ((source == ZEBRA_ROUTE_OSPF) && !ospf->instance) { + vty_out(vty, + "Instance redistribution in non-instanced OSPF not allowed\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if ((source == ZEBRA_ROUTE_OSPF) && (ospf->instance == instance)) { + vty_out(vty, "Same instance OSPF redistribution not allowed\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Get metric value. */ + if (argv_find(argv, argc, "metric", &idx)) + if (!str2metric(argv[idx + 1]->arg, &metric)) + return CMD_WARNING_CONFIG_FAILED; + + idx = 3; + /* Get metric type. */ + if (argv_find(argv, argc, "metric-type", &idx)) + if (!str2metric_type(argv[idx + 1]->arg, &type)) + return CMD_WARNING_CONFIG_FAILED; + + red = ospf_redist_lookup(ospf, source, instance); + if (!red) + red = ospf_redist_add(ospf, source, instance); + else + update = true; + + idx = 3; + if (argv_find(argv, argc, "route-map", &idx)) + ospf_routemap_set(red, argv[idx + 1]->arg); + else + ospf_routemap_unset(red); + + if (update) + return ospf_redistribute_update(ospf, red, source, instance, + type, metric); + else + return ospf_redistribute_set(ospf, red, source, instance, type, + metric); +} + +DEFUN (no_ospf_redistribute_instance_source, + no_ospf_redistribute_instance_source_cmd, + "no redistribute (1-65535) [{metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + REDIST_STR + "Open Shortest Path First\n" + "Non-main Kernel Routing Table\n" + "Instance ID/Table Id\n" + "Metric for redistributed routes\n" + "OSPF default metric\n" + "OSPF exterior metric type for redistributed routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_ospf_table = 2; + int idx_number = 3; + unsigned int instance; + struct ospf_redist *red; + int source; + + if (strncmp(argv[idx_ospf_table]->arg, "o", 1) == 0) + source = ZEBRA_ROUTE_OSPF; + else + source = ZEBRA_ROUTE_TABLE; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + + if ((source == ZEBRA_ROUTE_OSPF) && !ospf->instance) { + vty_out(vty, + "Instance redistribution in non-instanced OSPF not allowed\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if ((source == ZEBRA_ROUTE_OSPF) && (ospf->instance == instance)) { + vty_out(vty, "Same instance OSPF redistribution not allowed\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + red = ospf_redist_lookup(ospf, source, instance); + if (!red) + return CMD_SUCCESS; + + ospf_routemap_unset(red); + ospf_redist_del(ospf, source, instance); + + return ospf_redistribute_unset(ospf, source, instance); +} + +DEFUN (ospf_distribute_list_out, + ospf_distribute_list_out_cmd, + "distribute-list ACCESSLIST4_NAME out " FRR_REDIST_STR_OSPFD, + "Filter networks in routing updates\n" + "Access-list name\n" + OUT_STR + FRR_REDIST_HELP_STR_OSPFD) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_word = 1; + int source; + + char *proto = argv[argc - 1]->text; + + /* Get distribute source. */ + source = proto_redistnum(AFI_IP, proto); + if (source < 0) + return CMD_WARNING_CONFIG_FAILED; + + return ospf_distribute_list_out_set(ospf, source, argv[idx_word]->arg); +} + +DEFUN (no_ospf_distribute_list_out, + no_ospf_distribute_list_out_cmd, + "no distribute-list ACCESSLIST4_NAME out " FRR_REDIST_STR_OSPFD, + NO_STR + "Filter networks in routing updates\n" + "Access-list name\n" + OUT_STR + FRR_REDIST_HELP_STR_OSPFD) +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_word = 2; + int source; + + char *proto = argv[argc - 1]->text; + source = proto_redistnum(AFI_IP, proto); + if (source < 0) + return CMD_WARNING_CONFIG_FAILED; + + return ospf_distribute_list_out_unset(ospf, source, + argv[idx_word]->arg); +} + +/* Default information originate. */ +DEFUN (ospf_default_information_originate, + ospf_default_information_originate_cmd, + "default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + "Control distribution of default information\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPF default metric\n" + "OSPF metric\n" + "OSPF metric type for default routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int default_originate = DEFAULT_ORIGINATE_ZEBRA; + int type = -1; + int metric = -1; + struct ospf_redist *red; + int idx = 0; + int cur_originate = ospf->default_originate; + bool sameRtmap = false; + char *rtmap = NULL; + + red = ospf_redist_add(ospf, DEFAULT_ROUTE, 0); + + /* Check whether "always" was specified */ + if (argv_find(argv, argc, "always", &idx)) + default_originate = DEFAULT_ORIGINATE_ALWAYS; + idx = 1; + /* Get metric value */ + if (argv_find(argv, argc, "(0-16777214)", &idx)) { + if (!str2metric(argv[idx]->arg, &metric)) + return CMD_WARNING_CONFIG_FAILED; + } + idx = 1; + /* Get metric type. */ + if (argv_find(argv, argc, "(1-2)", &idx)) { + if (!str2metric_type(argv[idx]->arg, &type)) + return CMD_WARNING_CONFIG_FAILED; + } + idx = 1; + /* Get route-map */ + if (argv_find(argv, argc, "route-map", &idx)) + rtmap = argv[idx + 1]->arg; + + /* To check if user is providing same route map */ + if ((!rtmap && !ROUTEMAP_NAME(red)) || + (rtmap && ROUTEMAP_NAME(red) && + (strcmp(rtmap, ROUTEMAP_NAME(red)) == 0))) + sameRtmap = true; + + /* Don't allow if the same lsa is already originated. */ + if ((sameRtmap) + && (red->dmetric.type == type) + && (red->dmetric.value == metric) + && (cur_originate == default_originate)) + return CMD_SUCCESS; + + /* Updating Metric details */ + red->dmetric.type = type; + red->dmetric.value = metric; + + /* updating route map details */ + if (rtmap) + ospf_routemap_set(red, rtmap); + else + ospf_routemap_unset(red); + + return ospf_redistribute_default_set(ospf, default_originate, type, + metric); +} + +DEFUN (no_ospf_default_information_originate, + no_ospf_default_information_originate_cmd, + "no default-information originate [{always|metric (0-16777214)|metric-type (1-2)|route-map RMAP_NAME}]", + NO_STR + "Control distribution of default information\n" + "Distribute a default route\n" + "Always advertise default route\n" + "OSPF default metric\n" + "OSPF metric\n" + "OSPF metric type for default routes\n" + "Set OSPF External Type 1/2 metrics\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct ospf_redist *red; + + red = ospf_redist_lookup(ospf, DEFAULT_ROUTE, 0); + if (!red) + return CMD_SUCCESS; + + ospf_routemap_unset(red); + ospf_redist_del(ospf, DEFAULT_ROUTE, 0); + + return ospf_redistribute_default_set(ospf, DEFAULT_ORIGINATE_NONE, + 0, 0); +} + +DEFUN (ospf_default_metric, + ospf_default_metric_cmd, + "default-metric (0-16777214)", + "Set metric of redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 1; + int metric = -1; + + if (!str2metric(argv[idx_number]->arg, &metric)) + return CMD_WARNING_CONFIG_FAILED; + + ospf->default_metric = metric; + + ospf_schedule_asbr_redist_update(ospf); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_default_metric, + no_ospf_default_metric_cmd, + "no default-metric [(0-16777214)]", + NO_STR + "Set metric of redistributed routes\n" + "Default metric\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->default_metric = -1; + + ospf_schedule_asbr_redist_update(ospf); + + return CMD_SUCCESS; +} + + +DEFUN (ospf_distance, + ospf_distance_cmd, + "distance (1-255)", + "Administrative distance\n" + "OSPF Administrative distance\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 1; + uint8_t distance; + + distance = atoi(argv[idx_number]->arg); + if (ospf->distance_all != distance) { + ospf->distance_all = distance; + ospf_restart_spf(ospf); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_distance, + no_ospf_distance_cmd, + "no distance (1-255)", + NO_STR + "Administrative distance\n" + "OSPF Administrative distance\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (ospf->distance_all) { + ospf->distance_all = 0; + ospf_restart_spf(ospf); + } + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_distance_ospf, + no_ospf_distance_ospf_cmd, + "no distance ospf [{intra-area [(1-255)]|inter-area [(1-255)]|external [(1-255)]}]", + NO_STR + "Administrative distance\n" + "OSPF administrative distance\n" + "Intra-area routes\n" + "Distance for intra-area routes\n" + "Inter-area routes\n" + "Distance for inter-area routes\n" + "External routes\n" + "Distance for external routes\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx = 0; + + if (argv_find(argv, argc, "intra-area", &idx) || argc == 3) + idx = ospf->distance_intra = 0; + if (argv_find(argv, argc, "inter-area", &idx) || argc == 3) + idx = ospf->distance_inter = 0; + if (argv_find(argv, argc, "external", &idx) || argc == 3) + ospf->distance_external = 0; + + return CMD_SUCCESS; +} + +DEFUN (ospf_distance_ospf, + ospf_distance_ospf_cmd, + "distance ospf {intra-area (1-255)|inter-area (1-255)|external (1-255)}", + "Administrative distance\n" + "OSPF administrative distance\n" + "Intra-area routes\n" + "Distance for intra-area routes\n" + "Inter-area routes\n" + "Distance for inter-area routes\n" + "External routes\n" + "Distance for external routes\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx = 0; + + ospf->distance_intra = 0; + ospf->distance_inter = 0; + ospf->distance_external = 0; + + if (argv_find(argv, argc, "intra-area", &idx)) + ospf->distance_intra = atoi(argv[idx + 1]->arg); + idx = 0; + if (argv_find(argv, argc, "inter-area", &idx)) + ospf->distance_inter = atoi(argv[idx + 1]->arg); + idx = 0; + if (argv_find(argv, argc, "external", &idx)) + ospf->distance_external = atoi(argv[idx + 1]->arg); + + return CMD_SUCCESS; +} + +DEFUN (ip_ospf_mtu_ignore, + ip_ospf_mtu_ignore_addr_cmd, + "ip ospf mtu-ignore [A.B.C.D]", + "IP Information\n" + "OSPF interface commands\n" + "Disable MTU mismatch detection on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 3; + struct in_addr addr; + int ret; + + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + if (argc == 4) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + params->mtu_ignore = 1; + if (params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) + SET_IF_PARAM(params, mtu_ignore); + else { + UNSET_IF_PARAM(params, mtu_ignore); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + } + return CMD_SUCCESS; +} + +DEFUN (no_ip_ospf_mtu_ignore, + no_ip_ospf_mtu_ignore_addr_cmd, + "no ip ospf mtu-ignore [A.B.C.D]", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Disable MTU mismatch detection on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + int idx_ipv4 = 4; + struct in_addr addr; + int ret; + + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + if (argc == 5) { + ret = inet_aton(argv[idx_ipv4]->arg, &addr); + if (!ret) { + vty_out(vty, + "Please specify interface address by A.B.C.D\n"); + return CMD_WARNING_CONFIG_FAILED; + } + params = ospf_get_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + params->mtu_ignore = 0; + if (params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) + SET_IF_PARAM(params, mtu_ignore); + else { + UNSET_IF_PARAM(params, mtu_ignore); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, addr); + ospf_if_update_params(ifp, addr); + } + } + return CMD_SUCCESS; +} + +DEFPY(ip_ospf_capability_opaque, ip_ospf_capability_opaque_addr_cmd, + "[no] ip ospf capability opaque [A.B.C.D]$ip_addr", + NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Disable OSPF capability on this interface\n" + "Disable OSPF opaque LSA capability on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct route_node *rn; + bool old_opaque_capable; + bool opaque_capable_change; + + struct ospf_if_params *params; + params = IF_DEF_PARAMS(ifp); + + if (ip_addr.s_addr != INADDR_ANY) { + params = ospf_get_if_params(ifp, ip_addr); + ospf_if_update_params(ifp, ip_addr); + } + + old_opaque_capable = params->opaque_capable; + params->opaque_capable = (no) ? false : true; + opaque_capable_change = (old_opaque_capable != params->opaque_capable); + if (params->opaque_capable != OSPF_OPAQUE_CAPABLE_DEFAULT) + SET_IF_PARAM(params, opaque_capable); + else { + UNSET_IF_PARAM(params, opaque_capable); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, ip_addr); + ospf_if_update_params(ifp, ip_addr); + } + } + + /* + * If there is a change to the opaque capability, flap the interface + * to reset all the neighbor adjacencies. + */ + if (opaque_capable_change) { + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi && (oi->state > ISM_Down) && + (ip_addr.s_addr == INADDR_ANY || + IPV4_ADDR_SAME(&oi->address->u.prefix4, + &ip_addr))) { + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceDown); + OSPF_ISM_EVENT_EXECUTE(oi, ISM_InterfaceUp); + } + } + } + return CMD_SUCCESS; +} + + +DEFPY(ip_ospf_prefix_suppression, ip_ospf_prefix_suppression_addr_cmd, + "[no] ip ospf prefix-suppression [A.B.C.D]$ip_addr", NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Supress OSPF prefix advertisement on this interface\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct route_node *rn; + bool prefix_suppression_change; + struct ospf_if_params *params; + + params = IF_DEF_PARAMS(ifp); + + if (ip_addr.s_addr != INADDR_ANY) { + params = ospf_get_if_params(ifp, ip_addr); + ospf_if_update_params(ifp, ip_addr); + } + + prefix_suppression_change = (params->prefix_suppression == (bool)no); + params->prefix_suppression = (no) ? false : true; + if (params->prefix_suppression != OSPF_PREFIX_SUPPRESSION_DEFAULT) + SET_IF_PARAM(params, prefix_suppression); + else { + UNSET_IF_PARAM(params, prefix_suppression); + if (params != IF_DEF_PARAMS(ifp)) { + ospf_free_if_params(ifp, ip_addr); + ospf_if_update_params(ifp, ip_addr); + } + } + + /* + * If there is a change to the prefix suppression, update the Router-LSA. + */ + if (prefix_suppression_change) { + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi && (oi->state > ISM_Down) && + (ip_addr.s_addr == INADDR_ANY || + IPV4_ADDR_SAME(&oi->address->u.prefix4, &ip_addr))) { + (void)ospf_router_lsa_update_area(oi->area); + if (oi->state == ISM_DR) + ospf_network_lsa_update(oi); + } + } + } + return CMD_SUCCESS; +} + +DEFPY(ip_ospf_neighbor_filter, ip_ospf_neighbor_filter_addr_cmd, + "[no] ip ospf neighbor-filter ![PREFIXLIST4_NAME]$prefix_list [A.B.C.D]$ip_addr", NO_STR + "IP Information\n" + "OSPF interface commands\n" + "Filter OSPF neighbor packets\n" + "Prefix-List used for filtering\n" + "Address of interface\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct ospf_if_params *params; + struct prefix_list *nbr_filter = NULL; + struct route_node *rn; + + params = IF_DEF_PARAMS(ifp); + + if (ip_addr.s_addr != INADDR_ANY) { + params = ospf_get_if_params(ifp, ip_addr); + ospf_if_update_params(ifp, ip_addr); + } + + if (params->nbr_filter_name) + XFREE(MTYPE_OSPF_IF_PARAMS, params->nbr_filter_name); + + if (no) { + UNSET_IF_PARAM(params, nbr_filter_name); + params->nbr_filter_name = NULL; + } else { + SET_IF_PARAM(params, nbr_filter_name); + params->nbr_filter_name = XSTRDUP(MTYPE_OSPF_IF_PARAMS, + prefix_list); + nbr_filter = prefix_list_lookup(AFI_IP, params->nbr_filter_name); + } + + /* + * Determine if there is a change in neighbor filter prefix-list for the + * interface. + */ + for (rn = route_top(IF_OIFS(ifp)); rn; rn = route_next(rn)) { + struct ospf_interface *oi = rn->info; + + if (oi && + (ip_addr.s_addr == INADDR_ANY || + IPV4_ADDR_SAME(&oi->address->u.prefix4, &ip_addr)) && + oi->nbr_filter != nbr_filter) { + oi->nbr_filter = nbr_filter; + if (oi->nbr_filter) + ospf_intf_neighbor_filter_apply(oi); + } + } + return CMD_SUCCESS; +} + +DEFUN (ospf_max_metric_router_lsa_admin, + ospf_max_metric_router_lsa_admin_cmd, + "max-metric router-lsa administrative", + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Administratively applied, for an indefinite period\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *ln; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { + SET_FLAG(area->stub_router_state, OSPF_AREA_ADMIN_STUB_ROUTED); + + if (!CHECK_FLAG(area->stub_router_state, + OSPF_AREA_IS_STUB_ROUTED)) + ospf_router_lsa_update_area(area); + } + + /* Allows for areas configured later to get the property */ + ospf->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_SET; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_max_metric_router_lsa_admin, + no_ospf_max_metric_router_lsa_admin_cmd, + "no max-metric router-lsa administrative", + NO_STR + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Administratively applied, for an indefinite period\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *ln; + struct ospf_area *area; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { + UNSET_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED); + + /* Don't trample on the start-up stub timer */ + if (CHECK_FLAG(area->stub_router_state, + OSPF_AREA_IS_STUB_ROUTED) + && !area->t_stub_router) { + UNSET_FLAG(area->stub_router_state, + OSPF_AREA_IS_STUB_ROUTED); + ospf_router_lsa_update_area(area); + } + } + ospf->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET; + return CMD_SUCCESS; +} + +DEFUN (ospf_max_metric_router_lsa_startup, + ospf_max_metric_router_lsa_startup_cmd, + "max-metric router-lsa on-startup (5-86400)", + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Automatically advertise stub Router-LSA on startup of OSPF\n" + "Time (seconds) to advertise self as stub-router\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 3; + unsigned int seconds; + + if (argc < 4) { + vty_out(vty, "%% Must supply stub-router period\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + seconds = strtoul(argv[idx_number]->arg, NULL, 10); + + ospf->stub_router_startup_time = seconds; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_max_metric_router_lsa_startup, + no_ospf_max_metric_router_lsa_startup_cmd, + "no max-metric router-lsa on-startup [(5-86400)]", + NO_STR + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Automatically advertise stub Router-LSA on startup of OSPF\n" + "Time (seconds) to advertise self as stub-router\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *ln; + struct ospf_area *area; + + ospf->stub_router_startup_time = OSPF_STUB_ROUTER_UNCONFIGURED; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { + SET_FLAG(area->stub_router_state, + OSPF_AREA_WAS_START_STUB_ROUTED); + EVENT_OFF(area->t_stub_router); + + /* Don't trample on admin stub routed */ + if (!CHECK_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED)) { + UNSET_FLAG(area->stub_router_state, + OSPF_AREA_IS_STUB_ROUTED); + ospf_router_lsa_update_area(area); + } + } + return CMD_SUCCESS; +} + + +DEFUN (ospf_max_metric_router_lsa_shutdown, + ospf_max_metric_router_lsa_shutdown_cmd, + "max-metric router-lsa on-shutdown (5-100)", + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Advertise stub-router prior to full shutdown of OSPF\n" + "Time (seconds) to wait till full shutdown\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + int idx_number = 3; + unsigned int seconds; + + if (argc < 4) { + vty_out(vty, "%% Must supply stub-router shutdown period\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + seconds = strtoul(argv[idx_number]->arg, NULL, 10); + + ospf->stub_router_shutdown_time = seconds; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_max_metric_router_lsa_shutdown, + no_ospf_max_metric_router_lsa_shutdown_cmd, + "no max-metric router-lsa on-shutdown [(5-100)]", + NO_STR + "OSPF maximum / infinite-distance metric\n" + "Advertise own Router-LSA with infinite distance (stub router)\n" + "Advertise stub-router prior to full shutdown of OSPF\n" + "Time (seconds) to wait till full shutdown\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->stub_router_shutdown_time = OSPF_STUB_ROUTER_UNCONFIGURED; + + return CMD_SUCCESS; +} + +DEFUN (ospf_proactive_arp, + ospf_proactive_arp_cmd, + "proactive-arp", + "Allow sending ARP requests proactively\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->proactive_arp = true; + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_proactive_arp, + no_ospf_proactive_arp_cmd, + "no proactive-arp", + NO_STR + "Disallow sending ARP requests proactively\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->proactive_arp = false; + + return CMD_SUCCESS; +} + +/* Graceful Restart HELPER Commands */ +DEFPY(ospf_gr_helper_enable, ospf_gr_helper_enable_cmd, + "graceful-restart helper enable [A.B.C.D$address]", + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Enable Helper support\n" + "Advertising Router-ID\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (address_str) { + ospf_gr_helper_support_set_per_routerid(ospf, &address, + OSPF_GR_TRUE); + return CMD_SUCCESS; + } + + ospf_gr_helper_support_set(ospf, OSPF_GR_TRUE); + + return CMD_SUCCESS; +} + +DEFPY(no_ospf_gr_helper_enable, + no_ospf_gr_helper_enable_cmd, + "no graceful-restart helper enable [A.B.C.D$address]", + NO_STR + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Enable Helper support\n" + "Advertising Router-ID\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + if (address_str) { + ospf_gr_helper_support_set_per_routerid(ospf, &address, + OSPF_GR_FALSE); + return CMD_SUCCESS; + } + + ospf_gr_helper_support_set(ospf, OSPF_GR_FALSE); + return CMD_SUCCESS; +} + +DEFPY(ospf_gr_helper_enable_lsacheck, + ospf_gr_helper_enable_lsacheck_cmd, + "graceful-restart helper strict-lsa-checking", + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Enable strict LSA check\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_lsa_check_set(ospf, OSPF_GR_TRUE); + return CMD_SUCCESS; +} + +DEFPY(no_ospf_gr_helper_enable_lsacheck, + no_ospf_gr_helper_enable_lsacheck_cmd, + "no graceful-restart helper strict-lsa-checking", + NO_STR + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Disable strict LSA check\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_lsa_check_set(ospf, OSPF_GR_FALSE); + return CMD_SUCCESS; +} + +DEFPY(ospf_gr_helper_supported_grace_time, + ospf_gr_helper_supported_grace_time_cmd, + "graceful-restart helper supported-grace-time (10-1800)$interval", + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Supported grace timer\n" + "Grace interval(in seconds)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_supported_gracetime_set(ospf, interval); + return CMD_SUCCESS; +} + +DEFPY(no_ospf_gr_helper_supported_grace_time, + no_ospf_gr_helper_supported_grace_time_cmd, + "no graceful-restart helper supported-grace-time (10-1800)$interval", + NO_STR + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Supported grace timer\n" + "Grace interval(in seconds)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_supported_gracetime_set(ospf, OSPF_MAX_GRACE_INTERVAL); + return CMD_SUCCESS; +} + +DEFPY(ospf_gr_helper_planned_only, + ospf_gr_helper_planned_only_cmd, + "graceful-restart helper planned-only", + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Supported only planned restart\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_set_supported_planned_only_restart(ospf, OSPF_GR_TRUE); + + return CMD_SUCCESS; +} + +/* External Route Aggregation */ +DEFUN (ospf_external_route_aggregation, + ospf_external_route_aggregation_cmd, + "summary-address A.B.C.D/M [tag (1-4294967295)]", + "External summary address\n" + "Summary address prefix\n" + "Router tag \n" + "Router tag value\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct prefix_ipv4 p; + int idx = 1; + route_tag_t tag = 0; + int ret = OSPF_SUCCESS; + + str2prefix_ipv4(argv[idx]->arg, &p); + + if (is_default_prefix4(&p)) { + vty_out(vty, + "Default address shouldn't be configured as summary address.\n"); + return CMD_SUCCESS; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!is_valid_summary_addr(&p)) { + vty_out(vty, "Not a valid summary address.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc > 2) + tag = strtoul(argv[idx + 2]->arg, NULL, 10); + + ret = ospf_asbr_external_aggregator_set(ospf, &p, tag); + if (ret == OSPF_INVALID) + vty_out(vty, "Invalid configuration!!\n"); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_external_route_aggregation, + no_ospf_external_route_aggregation_cmd, + "no summary-address A.B.C.D/M [tag (1-4294967295)]", + NO_STR + "External summary address\n" + "Summary address prefix\n" + "Router tag\n" + "Router tag value\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct prefix_ipv4 p; + int idx = 2; + route_tag_t tag = 0; + int ret = OSPF_SUCCESS; + + str2prefix_ipv4(argv[idx]->arg, &p); + + if (is_default_prefix4(&p)) { + vty_out(vty, + "Default address shouldn't be configured as summary address.\n"); + return CMD_SUCCESS; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!is_valid_summary_addr(&p)) { + vty_out(vty, "Not a valid summary address.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (argc > 3) + tag = strtoul(argv[idx + 2]->arg, NULL, 10); + + ret = ospf_asbr_external_aggregator_unset(ospf, &p, tag); + if (ret == OSPF_INVALID) + vty_out(vty, "Invalid configuration!!\n"); + + return CMD_SUCCESS; +} + +DEFPY(no_ospf_gr_helper_planned_only, + no_ospf_gr_helper_planned_only_cmd, + "no graceful-restart helper planned-only", + NO_STR + "OSPF Graceful Restart\n" + "OSPF GR Helper\n" + "Supported only for planned restart\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_gr_helper_set_supported_planned_only_restart(ospf, OSPF_GR_FALSE); + + return CMD_SUCCESS; +} + +static int ospf_print_vty_helper_dis_rtr_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct advRtr *rtr = bucket->data; + struct vty *vty = (struct vty *)arg; + static unsigned int count; + + vty_out(vty, "%-6pI4,", &rtr->advRtrAddr); + count++; + + if (count % 5 == 0) + vty_out(vty, "\n"); + + return HASHWALK_CONTINUE; +} + +static int ospf_print_json_helper_enabled_rtr_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct advRtr *rtr = bucket->data; + struct json_object *json_rid_array = arg; + struct json_object *json_rid; + + json_rid = json_object_new_object(); + + json_object_string_addf(json_rid, "routerId", "%pI4", &rtr->advRtrAddr); + json_object_array_add(json_rid_array, json_rid); + + return HASHWALK_CONTINUE; +} + +static int ospf_show_gr_helper_details(struct vty *vty, struct ospf *ospf, + uint8_t use_vrf, json_object *json, + bool uj, bool detail) +{ + struct listnode *node; + struct ospf_interface *oi; + char buf[PREFIX_STRLEN]; + json_object *json_vrf = NULL; + + if (uj) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + if (ospf->instance) { + if (uj) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (uj) { + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + /* Show Router ID. */ + if (uj) { + json_object_string_add(json_vrf, "routerId", + inet_ntop(AF_INET, &ospf->router_id, + buf, sizeof(buf))); + } else { + vty_out(vty, "\n OSPF Router with ID (%pI4)\n\n", + &ospf->router_id); + } + + if (!uj) { + + if (ospf->is_helper_supported) + vty_out(vty, + " Graceful restart helper support enabled.\n"); + else + vty_out(vty, + " Graceful restart helper support disabled.\n"); + + if (ospf->strict_lsa_check) + vty_out(vty, " Strict LSA check is enabled.\n"); + else + vty_out(vty, " Strict LSA check is disabled.\n"); + + if (ospf->only_planned_restart) + vty_out(vty, + " Helper supported for planned restarts only.\n"); + else + vty_out(vty, + " Helper supported for Planned and Unplanned Restarts.\n"); + + vty_out(vty, + " Supported Graceful restart interval: %d(in seconds).\n", + ospf->supported_grace_time); + + if (OSPF_HELPER_ENABLE_RTR_COUNT(ospf)) { + vty_out(vty, " Enable Router list:\n"); + vty_out(vty, " "); + hash_walk(ospf->enable_rtr_list, + ospf_print_vty_helper_dis_rtr_walkcb, vty); + vty_out(vty, "\n\n"); + } + + if (ospf->last_exit_reason != OSPF_GR_HELPER_EXIT_NONE) { + vty_out(vty, " Last Helper exit Reason :%s\n", + ospf_exit_reason2str(ospf->last_exit_reason)); + } + + if (ospf->active_restarter_cnt) + vty_out(vty, + " Number of Active neighbours in graceful restart: %d\n", + ospf->active_restarter_cnt); + else + vty_out(vty, "\n"); + + } else { + json_object_string_add( + json_vrf, "helperSupport", + (ospf->is_helper_supported) ? "Enabled" : "Disabled"); + json_object_string_add(json_vrf, "strictLsaCheck", + (ospf->strict_lsa_check) ? "Enabled" + : "Disabled"); + json_object_string_add( + json_vrf, "restartSupport", + (ospf->only_planned_restart) + ? "Planned Restart only" + : "Planned and Unplanned Restarts"); + + json_object_int_add(json_vrf, "supportedGracePeriod", + ospf->supported_grace_time); + + if (ospf->last_exit_reason != OSPF_GR_HELPER_EXIT_NONE) + json_object_string_add( + json_vrf, "lastExitReason", + ospf_exit_reason2str(ospf->last_exit_reason)); + + if (ospf->active_restarter_cnt) + json_object_int_add(json_vrf, "activeRestarterCnt", + ospf->active_restarter_cnt); + + if (OSPF_HELPER_ENABLE_RTR_COUNT(ospf)) { + struct json_object *json_rid_array = + json_object_new_array(); + + json_object_object_add(json_vrf, "enabledRouterIds", + json_rid_array); + + hash_walk(ospf->enable_rtr_list, + ospf_print_json_helper_enabled_rtr_walkcb, + json_rid_array); + } + } + + + if (detail) { + int cnt = 1; + json_object *json_neighbors = NULL; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + struct route_node *rn; + struct ospf_neighbor *nbr; + json_object *json_neigh; + + if (ospf_interface_neighbor_count(oi) == 0) + continue; + + if (uj) { + json_object_object_get_ex(json_vrf, "neighbors", + &json_neighbors); + if (!json_neighbors) { + json_neighbors = + json_object_new_object(); + json_object_object_add(json_vrf, + "neighbors", + json_neighbors); + } + } + + for (rn = route_top(oi->nbrs); rn; + rn = route_next(rn)) { + + if (!rn->info) + continue; + + nbr = rn->info; + + if (!OSPF_GR_IS_ACTIVE_HELPER(nbr)) + continue; + + if (!uj) { + vty_out(vty, " Neighbour %d :\n", cnt); + vty_out(vty, " Address : %pI4\n", + &nbr->address.u.prefix4); + vty_out(vty, " Routerid : %pI4\n", + &nbr->router_id); + vty_out(vty, + " Received Grace period : %d(in seconds).\n", + nbr->gr_helper_info + .recvd_grace_period); + vty_out(vty, + " Actual Grace period : %d(in seconds)\n", + nbr->gr_helper_info + .actual_grace_period); + vty_out(vty, + " Remaining GraceTime:%ld(in seconds).\n", + event_timer_remain_second( + nbr->gr_helper_info + .t_grace_timer)); + vty_out(vty, + " Graceful Restart reason: %s.\n\n", + ospf_restart_reason2str( + nbr->gr_helper_info + .gr_restart_reason)); + cnt++; + } else { + json_neigh = json_object_new_object(); + json_object_string_add( + json_neigh, "srcAddr", + inet_ntop(AF_INET, &nbr->src, + buf, sizeof(buf))); + + json_object_string_add( + json_neigh, "routerid", + inet_ntop(AF_INET, + &nbr->router_id, + buf, sizeof(buf))); + json_object_int_add( + json_neigh, + "recvdGraceInterval", + nbr->gr_helper_info + .recvd_grace_period); + json_object_int_add( + json_neigh, + "actualGraceInterval", + nbr->gr_helper_info + .actual_grace_period); + json_object_int_add( + json_neigh, "remainGracetime", + event_timer_remain_second( + nbr->gr_helper_info + .t_grace_timer)); + json_object_string_add( + json_neigh, "restartReason", + ospf_restart_reason2str( + nbr->gr_helper_info + .gr_restart_reason)); + json_object_object_add( + json_neighbors, + inet_ntop(AF_INET, &nbr->src, + buf, sizeof(buf)), + json_neigh); + } + } + } + } + return CMD_SUCCESS; +} + +DEFUN (ospf_external_route_aggregation_no_adrvertise, + ospf_external_route_aggregation_no_adrvertise_cmd, + "summary-address A.B.C.D/M no-advertise", + "External summary address\n" + "Summary address prefix\n" + "Don't advertise summary route \n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct prefix_ipv4 p; + int idx = 1; + int ret = OSPF_SUCCESS; + + str2prefix_ipv4(argv[idx]->arg, &p); + + if (is_default_prefix4(&p)) { + vty_out(vty, + "Default address shouldn't be configured as summary address.\n"); + return CMD_SUCCESS; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!is_valid_summary_addr(&p)) { + vty_out(vty, "Not a valid summary address.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = ospf_asbr_external_rt_no_advertise(ospf, &p); + if (ret == OSPF_INVALID) + vty_out(vty, "Invalid configuration!!\n"); + + return CMD_SUCCESS; +} + +DEFUN (no_ospf_external_route_aggregation_no_adrvertise, + no_ospf_external_route_aggregation_no_adrvertise_cmd, + "no summary-address A.B.C.D/M no-advertise", + NO_STR + "External summary address\n" + "Summary address prefix\n" + "Advertise summary route to the AS \n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct prefix_ipv4 p; + int idx = 2; + int ret = OSPF_SUCCESS; + + str2prefix_ipv4(argv[idx]->arg, &p); + + if (is_default_prefix4(&p)) { + vty_out(vty, + "Default address shouldn't be configured as summary address.\n"); + return CMD_SUCCESS; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + + if (!is_valid_summary_addr(&p)) { + vty_out(vty, "Not a valid summary address.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ret = ospf_asbr_external_rt_advertise(ospf, &p); + if (ret == OSPF_INVALID) + vty_out(vty, "Invalid configuration!!\n"); + + return CMD_SUCCESS; +} + +DEFUN (ospf_route_aggregation_timer, + ospf_route_aggregation_timer_cmd, + "aggregation timer (5-1800)", + "External route aggregation\n" + "Delay timer (in seconds)\n" + "Timer interval(in seconds)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + uint16_t interval = 0; + + interval = strtoul(argv[2]->arg, NULL, 10); + + ospf_external_aggregator_timer_set(ospf, interval); + + return CMD_SUCCESS; +} + +DEFPY (show_ip_ospf_gr_helper, + show_ip_ospf_gr_helper_cmd, + "show ip ospf [{(1-65535)$instance|vrf }] graceful-restart helper [detail] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "OSPF Graceful Restart\n" + "Helper details in the router\n" + "Detailed information\n" + JSON_STR) +{ + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int idx_vrf = 0; + int idx = 0; + uint8_t use_vrf = 0; + bool uj = use_json(argc, argv); + struct ospf *ospf = NULL; + json_object *json = NULL; + struct listnode *node = NULL; + int inst = 0; + bool detail = false; + + if (instance && instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (instance && vrf_name) { + vty_out(vty, "%% VRF is not supported in instance mode\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided */ + if (vrf_name) { + use_vrf = 1; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + ret = ospf_show_gr_helper_details( + vty, ospf, use_vrf, json, uj, detail); + } + + if (uj) + vty_json(vty, json); + + return ret; + } + + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + } else { + /* Default Vrf */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + } + + if (ospf == NULL || !ospf->oi_running) { + + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", vrf_name ? vrf_name : "default"); + + return CMD_SUCCESS; + } + + ospf_show_gr_helper_details(vty, ospf, use_vrf, json, uj, detail); + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} +/* Graceful Restart HELPER commands end */ +DEFUN (no_ospf_route_aggregation_timer, + no_ospf_route_aggregation_timer_cmd, + "no aggregation timer", + NO_STR + "External route aggregation\n" + "Delay timer\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf_external_aggregator_timer_set(ospf, OSPF_EXTL_AGGR_DEFAULT_DELAY); + + return CMD_SUCCESS; +} + +/* External Route Aggregation End */ + +static void config_write_stub_router(struct vty *vty, struct ospf *ospf) +{ + if (ospf->stub_router_startup_time != OSPF_STUB_ROUTER_UNCONFIGURED) + vty_out(vty, " max-metric router-lsa on-startup %u\n", + ospf->stub_router_startup_time); + if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) + vty_out(vty, " max-metric router-lsa on-shutdown %u\n", + ospf->stub_router_shutdown_time); + if (ospf->stub_router_admin_set == OSPF_STUB_ROUTER_ADMINISTRATIVE_SET) + vty_out(vty, " max-metric router-lsa administrative\n"); + + return; +} + +static void show_ip_ospf_route_network(struct vty *vty, struct ospf *ospf, + struct route_table *rt, + json_object *json, bool detail) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *pnode, *pnnode; + struct ospf_path *path; + json_object *json_route = NULL, *json_nexthop_array = NULL, + *json_nexthop = NULL; + + if (!json) + vty_out(vty, + "============ OSPF network routing table ============\n"); + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + char buf1[PREFIX2STR_BUFFER]; + + if ((or = rn->info) == NULL) + continue; + + prefix2str(&rn->p, buf1, sizeof(buf1)); + + if (json) { + json_route = json_object_new_object(); + json_object_object_add(json, buf1, json_route); + } + + switch (or->path_type) { + case OSPF_PATH_INTER_AREA: + if (or->type == OSPF_DESTINATION_NETWORK) { + if (json) { + json_object_string_add(json_route, + "routeType", + "N IA"); + json_object_int_add(json_route, "cost", + or->cost); + json_object_string_addf( + json_route, "area", "%pI4", + &or->u.std.area_id); + } else { + vty_out(vty, + "N IA %-18s [%d] area: %pI4\n", + buf1, or->cost, + &or->u.std.area_id); + } + } else if (or->type == OSPF_DESTINATION_DISCARD) { + if (json) { + json_object_string_add(json_route, + "routeType", + "D IA"); + } else { + vty_out(vty, + "D IA %-18s Discard entry\n", + buf1); + } + } + break; + case OSPF_PATH_INTRA_AREA: + if (json) { + json_object_string_add(json_route, "routeType", + "N"); + json_object_boolean_add(json_route, "transit", + or->u.std.transit); + json_object_int_add(json_route, "cost", + or->cost); + json_object_string_addf(json_route, "area", + "%pI4", + &or->u.std.area_id); + } else { + vty_out(vty, "N %s %-18s [%d] area: %pI4\n", + or->u.std.transit && detail ? "T" : " ", + buf1, or->cost, &or->u.std.area_id); + } + break; + default: + break; + } + + if (or->type == OSPF_DESTINATION_NETWORK) { + if (json) { + json_nexthop_array = json_object_new_array(); + json_object_object_add(json_route, "nexthops", + json_nexthop_array); + } + + for (ALL_LIST_ELEMENTS(or->paths, pnode, pnnode, + path)) { + if (json) { + json_nexthop = json_object_new_object(); + json_object_array_add( + json_nexthop_array, + json_nexthop); + } + if (if_lookup_by_index(path->ifindex, + ospf->vrf_id)) { + + if (path->nexthop.s_addr + == INADDR_ANY) { + if (json) { + json_object_string_add( + json_nexthop, + "ip", " "); + json_object_string_add( + json_nexthop, + "directlyAttachedTo", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } else { + vty_out(vty, + "%24s directly attached to %s\n", + "", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + } else { + if (json) { + json_object_string_addf( + json_nexthop, + "ip", "%pI4", + &path->nexthop); + json_object_string_add( + json_nexthop, + "via", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + json_object_string_addf( + json_nexthop, + "advertisedRouter", + "%pI4", + &path->adv_router); + } else { + vty_out(vty, + "%24s via %pI4, %s\n", + "", + &path->nexthop, + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + if (detail && !json) + vty_out(vty, + "%24s adv %pI4\n", + "", + &path->adv_router); + } + } + } + } + } + if (!json) + vty_out(vty, "\n"); +} + +static void show_ip_ospf_route_router(struct vty *vty, struct ospf *ospf, + struct route_table *rtrs, + json_object *json) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *pnode; + struct listnode *node; + struct ospf_path *path; + char buf[PREFIX_STRLEN]; + json_object *json_route = NULL, *json_nexthop_array = NULL, + *json_nexthop = NULL; + + if (!json) + vty_out(vty, "============ OSPF %s table =============\n", + ospf->all_rtrs == rtrs ? "reachable routers" + : "router routing"); + + for (rn = route_top(rtrs); rn; rn = route_next(rn)) { + if (rn->info == NULL) + continue; + int flag = 0; + + if (json) { + json_route = json_object_new_object(); + json_object_object_add( + json, inet_ntop(AF_INET, &rn->p.u.prefix4, + buf, sizeof(buf)), + json_route); + json_object_string_add(json_route, "routeType", "R "); + } else { + vty_out(vty, "R %-15pI4 ", + &rn->p.u.prefix4); + } + + for (ALL_LIST_ELEMENTS_RO((struct list *)rn->info, node, or)) { + if (flag++) { + if (!json) + vty_out(vty, "%24s", ""); + } + + /* Show path. */ + if (json) { + json_object_int_add(json_route, "cost", + or->cost); + json_object_string_addf(json_route, "area", + "%pI4", + &or->u.std.area_id); + if (or->path_type == OSPF_PATH_INTER_AREA) { + json_object_boolean_true_add(json_route, + "IA"); + json_object_boolean_true_add(json_route, + "ia"); + } + if (or->u.std.flags & ROUTER_LSA_BORDER) + json_object_string_add(json_route, + "routerType", + "abr"); + else if (or->u.std.flags & ROUTER_LSA_EXTERNAL) + json_object_string_add(json_route, + "routerType", + "asbr"); + } else { + vty_out(vty, "%s [%d] area: %pI4", + (or->path_type == OSPF_PATH_INTER_AREA + ? "IA" + : " "), + or->cost, &or->u.std.area_id); + /* Show flags. */ + vty_out(vty, "%s%s\n", + (or->u.std.flags & ROUTER_LSA_BORDER + ? ", ABR" + : ""), + (or->u.std.flags & ROUTER_LSA_EXTERNAL + ? ", ASBR" + : "")); + } + + if (json) { + json_nexthop_array = json_object_new_array(); + json_object_object_add(json_route, "nexthops", + json_nexthop_array); + } + + for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, path)) { + if (json) { + json_nexthop = json_object_new_object(); + json_object_array_add( + json_nexthop_array, + json_nexthop); + } + if (if_lookup_by_index(path->ifindex, + ospf->vrf_id)) { + if (path->nexthop.s_addr + == INADDR_ANY) { + if (json) { + json_object_string_add( + json_nexthop, + "ip", " "); + json_object_string_add( + json_nexthop, + "directlyAttachedTo", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } else { + vty_out(vty, + "%24s directly attached to %s\n", + "", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + } else { + if (json) { + json_object_string_addf( + json_nexthop, + "ip", "%pI4", + &path->nexthop); + json_object_string_add( + json_nexthop, + "via", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } else { + vty_out(vty, + "%24s via %pI4, %s\n", + "", + &path->nexthop, + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + } + } + } + } + } + if (!json) + vty_out(vty, "\n"); +} + +static void show_ip_ospf_route_external(struct vty *vty, struct ospf *ospf, + struct route_table *rt, + json_object *json, bool detail) +{ + struct route_node *rn; + struct ospf_route *er; + struct listnode *pnode, *pnnode; + struct ospf_path *path; + json_object *json_route = NULL, *json_nexthop_array = NULL, + *json_nexthop = NULL; + + if (!json) + vty_out(vty, + "============ OSPF external routing table ===========\n"); + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + if ((er = rn->info) == NULL) + continue; + + char buf1[19]; + + snprintfrr(buf1, sizeof(buf1), "%pFX", &rn->p); + if (json) { + json_route = json_object_new_object(); + json_object_object_add(json, buf1, json_route); + } + + switch (er->path_type) { + case OSPF_PATH_TYPE1_EXTERNAL: + if (json) { + json_object_string_add(json_route, "routeType", + "N E1"); + json_object_int_add(json_route, "cost", + er->cost); + json_object_int_add(json_route, "tag", + er->u.ext.tag); + } else { + vty_out(vty, + "N E1 %-18s [%d] tag: %" ROUTE_TAG_PRI + "\n", + buf1, er->cost, er->u.ext.tag); + } + break; + case OSPF_PATH_TYPE2_EXTERNAL: + if (json) { + json_object_string_add(json_route, "routeType", + "N E2"); + json_object_int_add(json_route, "cost", + er->cost); + json_object_int_add(json_route, "type2cost", + er->u.ext.type2_cost); + json_object_int_add(json_route, "tag", + er->u.ext.tag); + } else { + vty_out(vty, + "N E2 %-18s [%d/%d] tag: %" ROUTE_TAG_PRI + "\n", + buf1, er->cost, er->u.ext.type2_cost, + er->u.ext.tag); + } + break; + } + + if (json) { + json_nexthop_array = json_object_new_array(); + json_object_object_add(json_route, "nexthops", + json_nexthop_array); + } + + for (ALL_LIST_ELEMENTS(er->paths, pnode, pnnode, path)) { + if (json) { + json_nexthop = json_object_new_object(); + json_object_array_add(json_nexthop_array, + json_nexthop); + } + + if (if_lookup_by_index(path->ifindex, ospf->vrf_id)) { + if (path->nexthop.s_addr == INADDR_ANY) { + if (json) { + json_object_string_add( + json_nexthop, "ip", + " "); + json_object_string_add( + json_nexthop, + "directlyAttachedTo", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } else { + vty_out(vty, + "%24s directly attached to %s\n", + "", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + } else { + if (json) { + json_object_string_addf( + json_nexthop, "ip", + "%pI4", &path->nexthop); + json_object_string_add( + json_nexthop, "via", + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + json_object_string_addf( + json_nexthop, + "advertisedRouter", + "%pI4", + &path->adv_router); + } else { + vty_out(vty, + "%24s via %pI4, %s\n", + "", + &path->nexthop, + ifindex2ifname( + path->ifindex, + ospf->vrf_id)); + } + if (detail && !json) + vty_out(vty, + "%24s adv %pI4\n", "", + &path->adv_router); + } + } + } + } + if (!json) + vty_out(vty, "\n"); +} + +static int show_ip_ospf_reachable_routers_common(struct vty *vty, + struct ospf *ospf, + uint8_t use_vrf) +{ + if (ospf->instance) + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + + ospf_show_vrf_name(ospf, vty, NULL, use_vrf); + + if (ospf->all_rtrs == NULL) { + vty_out(vty, "No OSPF reachable router information exist\n"); + return CMD_SUCCESS; + } + + /* Show Router routes. */ + show_ip_ospf_route_router(vty, ospf, ospf->all_rtrs, NULL); + + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_reachable_routers, + show_ip_ospf_reachable_routers_cmd, + "show ip ospf [vrf ] reachable-routers", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Show all the reachable OSPF routers\n") +{ + struct ospf *ospf = NULL; + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (vrf_name) { + bool ospf_output = false; + + use_vrf = 1; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + ospf_output = true; + ret = show_ip_ospf_reachable_routers_common( + vty, ospf, use_vrf); + } + + if (!ospf_output) + vty_out(vty, "%% OSPF instance not found\n"); + } else { + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + vty_out(vty, "%% OSPF instance not found\n"); + return CMD_SUCCESS; + } + + ret = show_ip_ospf_reachable_routers_common(vty, ospf, + use_vrf); + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + vty_out(vty, "%% OSPF instance not found\n"); + return CMD_SUCCESS; + } + + ret = show_ip_ospf_reachable_routers_common(vty, ospf, use_vrf); + } + + return ret; +} + +DEFUN (show_ip_ospf_instance_reachable_routers, + show_ip_ospf_instance_reachable_routers_cmd, + "show ip ospf (1-65535) reachable-routers", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Show all the reachable OSPF routers\n") +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + return show_ip_ospf_reachable_routers_common(vty, ospf, 0); +} + +static int show_ip_ospf_border_routers_common(struct vty *vty, + struct ospf *ospf, + uint8_t use_vrf, + json_object *json) +{ + json_object *json_vrf = NULL; + json_object *json_router = NULL; + + if (json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + json_router = json_object_new_object(); + } + + if (ospf->instance) { + if (!json) + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + else + json_object_int_add(json_vrf, "ospfInstance", + ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (ospf->new_table == NULL) { + if (!json) + vty_out(vty, "No OSPF routing information exist\n"); + else { + json_object_free(json_router); + if (use_vrf) + json_object_free(json_vrf); + } + return CMD_SUCCESS; + } + + /* Show Network routes. + show_ip_ospf_route_network (vty, ospf->new_table); */ + + /* Show Router routes. */ + show_ip_ospf_route_router(vty, ospf, ospf->new_rtrs, json_router); + + if (json) { + json_object_object_add(json_vrf, "routers", json_router); + if (use_vrf) { + if (ospf->vrf_id == VRF_DEFAULT) + json_object_object_add(json, "default", + json_vrf); + else + json_object_object_add(json, ospf->name, + json_vrf); + } + } else { + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_ospf_border_routers, + show_ip_ospf_border_routers_cmd, + "show ip ospf [vrf ] border-routers [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Show all the ABR's and ASBR's\n" + JSON_STR) +{ + struct ospf *ospf = NULL; + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (vrf_name) { + bool ospf_output = false; + + use_vrf = 1; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + ospf_output = true; + ret = show_ip_ospf_border_routers_common( + vty, ospf, use_vrf, json); + } + + if (uj) + vty_json(vty, json); + else if (!ospf_output) + vty_out(vty, "%% OSPF is not enabled\n"); + + return ret; + } else { + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + } + + if (ospf) { + ret = show_ip_ospf_border_routers_common(vty, ospf, use_vrf, + json); + if (uj) + vty_json(vty, json); + } + + return ret; +} + +DEFUN (show_ip_ospf_instance_border_routers, + show_ip_ospf_instance_border_routers_cmd, + "show ip ospf (1-65535) border-routers", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Show all the ABR's and ASBR's\n") +{ + int idx_number = 3; + struct ospf *ospf; + unsigned short instance = 0; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + return show_ip_ospf_border_routers_common(vty, ospf, 0, NULL); +} + +static int show_ip_ospf_route_common(struct vty *vty, struct ospf *ospf, + json_object *json, uint8_t use_vrf, + bool detail) +{ + json_object *json_vrf = NULL; + + if (ospf->instance) + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + + + if (json) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (ospf->new_table == NULL) { + if (json) { + if (use_vrf) + json_object_free(json_vrf); + } else { + vty_out(vty, "No OSPF routing information exist\n"); + } + return CMD_SUCCESS; + } + + if (detail && json == NULL) { + vty_out(vty, "Codes: N - network T - transitive\n"); + vty_out(vty, " IA - inter-area E - external route\n"); + vty_out(vty, " D - destination R - router\n\n"); + } + + /* Show Network routes. */ + show_ip_ospf_route_network(vty, ospf, ospf->new_table, json_vrf, + detail); + + /* Show Router routes. */ + show_ip_ospf_route_router(vty, ospf, ospf->new_rtrs, json_vrf); + + /* Show Router routes. */ + if (ospf->all_rtrs) + show_ip_ospf_route_router(vty, ospf, ospf->all_rtrs, json_vrf); + + /* Show AS External routes. */ + show_ip_ospf_route_external(vty, ospf, ospf->old_external_route, + json_vrf, detail); + + if (json) { + if (use_vrf) { + // json_object_object_add(json_vrf, "areas", + // json_areas); + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } + } else { + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_route, + show_ip_ospf_route_cmd, + "show ip ospf [vrf ] route [detail] [json]", + SHOW_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "OSPF routing table\n" + "Detailed information\n" + JSON_STR) +{ + struct ospf *ospf = NULL; + struct listnode *node = NULL; + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int inst = 0; + int idx = 0; + int idx_vrf = 0; + uint8_t use_vrf = 0; + bool uj = use_json(argc, argv); + bool detail = false; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + /* vrf input is provided could be all or specific vrf*/ + if (vrf_name) { + bool ospf_output = false; + + use_vrf = 1; + + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ospf_output = true; + ret = show_ip_ospf_route_common( + vty, ospf, json, use_vrf, detail); + } + + if (uj) { + /* Keep Non-pretty format */ + vty_json(vty, json); + } else if (!ospf_output) + vty_out(vty, "%% OSPF is not enabled\n"); + + return ret; + } + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + } else { + /* Display default ospf (instance 0) info */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + } + + if (ospf) { + ret = show_ip_ospf_route_common(vty, ospf, json, use_vrf, + detail); + /* Keep Non-pretty format */ + if (uj) + vty_out(vty, "%s\n", + json_object_to_json_string_ext( + json, JSON_C_TO_STRING_NOSLASHESCAPE)); + } + + if (uj) + json_object_free(json); + + return ret; +} + +DEFUN (show_ip_ospf_instance_route, + show_ip_ospf_instance_route_cmd, + "show ip ospf (1-65535) route [detail]", + SHOW_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "OSPF routing table\n" + "Detailed information\n") +{ + int idx_number = 3; + int idx = 0; + struct ospf *ospf; + unsigned short instance = 0; + bool detail = false; + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + instance = strtoul(argv[idx_number]->arg, NULL, 10); + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + + ospf = ospf_lookup_instance(instance); + if (!ospf || !ospf->oi_running) + return CMD_SUCCESS; + + return show_ip_ospf_route_common(vty, ospf, NULL, 0, detail); +} + + +DEFUN (show_ip_ospf_vrfs, + show_ip_ospf_vrfs_cmd, + "show ip ospf vrfs [json]", + SHOW_STR + IP_STR + "OSPF information\n" + "Show OSPF VRFs \n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + json_object *json = NULL; + json_object *json_vrfs = NULL; + struct ospf *ospf = NULL; + struct listnode *node = NULL; + int count = 0; + static const char header[] = "Name Id RouterId "; + + if (uj) { + json = json_object_new_object(); + json_vrfs = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + json_object *json_vrf = NULL; + const char *name = NULL; + int64_t vrf_id_ui = 0; + + count++; + + if (!uj && count == 1) + vty_out(vty, "%s\n", header); + if (uj) + json_vrf = json_object_new_object(); + + name = ospf_get_name(ospf); + + vrf_id_ui = (ospf->vrf_id == VRF_UNKNOWN) + ? -1 + : (int64_t)ospf->vrf_id; + + if (uj) { + json_object_int_add(json_vrf, "vrfId", vrf_id_ui); + json_object_string_addf(json_vrf, "routerId", "%pI4", + &ospf->router_id); + + json_object_object_add(json_vrfs, name, json_vrf); + + } else { + vty_out(vty, "%-25s %-5d %-16pI4 \n", name, + ospf->vrf_id, &ospf->router_id); + } + } + + if (uj) { + json_object_object_add(json, "vrfs", json_vrfs); + json_object_int_add(json, "totalVrfs", count); + + vty_json(vty, json); + } else { + if (count) + vty_out(vty, "\nTotal number of OSPF VRFs: %d\n", + count); + } + + return CMD_SUCCESS; +} +DEFPY (clear_ip_ospf_neighbor, + clear_ip_ospf_neighbor_cmd, + "clear ip ospf [(1-65535)]$instance neighbor [A.B.C.D$nbr_id]", + CLEAR_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Reset OSPF Neighbor\n" + "Neighbor ID\n") +{ + struct listnode *node; + struct ospf *ospf = NULL; + + /* If user does not specify the arguments, + * instance = 0 and nbr_id = 0.0.0.0 + */ + if (instance != 0) { + /* This means clear only the particular ospf process */ + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + } + + /* Clear all the ospf processes */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + if (nbr_id_str && IPV4_ADDR_SAME(&ospf->router_id, &nbr_id)) { + vty_out(vty, "Self router-id is not allowed.\r\n "); + return CMD_SUCCESS; + } + + ospf_neighbor_reset(ospf, nbr_id, nbr_id_str); + } + + return CMD_SUCCESS; +} + +DEFPY (clear_ip_ospf_process, + clear_ip_ospf_process_cmd, + "clear ip ospf [(1-65535)]$instance process", + CLEAR_STR + IP_STR + "OSPF information\n" + "Instance ID\n" + "Reset OSPF Process\n") +{ + struct listnode *node; + struct ospf *ospf = NULL; + + /* Check if instance is not passed as an argument */ + if (instance != 0) { + /* This means clear only the particular ospf process */ + if (instance != ospf_instance) + return CMD_NOT_MY_INSTANCE; + } + + /* Clear all the ospf processes */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + + ospf_process_reset(ospf); + } + + return CMD_SUCCESS; +} + +static const char *const ospf_abr_type_str[] = { + "unknown", "standard", "ibm", "cisco", "shortcut" +}; + +static const char *const ospf_shortcut_mode_str[] = { + "default", "enable", "disable" +}; +static int ospf_vty_external_rt_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct external_info *ei = bucket->data; + struct vty *vty = (struct vty *)arg; + static unsigned int count; + + vty_out(vty, "%-4pI4/%d, ", &ei->p.prefix, ei->p.prefixlen); + count++; + + if (count % 5 == 0) + vty_out(vty, "\n"); + + if (OSPF_EXTERNAL_RT_COUNT(ei->aggr_route) == count) + count = 0; + + return HASHWALK_CONTINUE; +} + +static int ospf_json_external_rt_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct external_info *ei = bucket->data; + struct json_object *json = (struct json_object *)arg; + char buf[PREFIX2STR_BUFFER]; + char exnalbuf[20]; + static unsigned int count; + + prefix2str(&ei->p, buf, sizeof(buf)); + + snprintf(exnalbuf, 20, "Exnl Addr-%d", count); + + json_object_string_add(json, exnalbuf, buf); + + count++; + + if (OSPF_EXTERNAL_RT_COUNT(ei->aggr_route) == count) + count = 0; + + return HASHWALK_CONTINUE; +} + +static int ospf_show_summary_address(struct vty *vty, struct ospf *ospf, + uint8_t use_vrf, json_object *json, + bool uj, bool detail) +{ + struct route_node *rn; + json_object *json_vrf = NULL; + int mtype = 0; + int mval = 0; + static char header[] = + "Summary-address Metric-type Metric Tag External_Rt_count\n"; + + mtype = metric_type(ospf, 0, ospf->instance); + mval = metric_value(ospf, 0, ospf->instance); + + if (!uj) + vty_out(vty, "%s\n", header); + + if (uj) { + if (use_vrf) + json_vrf = json_object_new_object(); + else + json_vrf = json; + } + + if (ospf->instance) { + if (uj) + json_object_int_add(json, "ospfInstance", + ospf->instance); + else + vty_out(vty, "\nOSPF Instance: %d\n\n", ospf->instance); + } + + ospf_show_vrf_name(ospf, vty, json_vrf, use_vrf); + + if (!uj) { + vty_out(vty, "aggregation delay interval: %u(in seconds)\n\n", + ospf->aggr_delay_interval); + } else { + json_object_int_add(json_vrf, "aggregationDelayInterval", + ospf->aggr_delay_interval); + } + + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) + if (rn->info) { + struct ospf_external_aggr_rt *aggr = rn->info; + json_object *json_aggr = NULL; + char buf[PREFIX2STR_BUFFER]; + + prefix2str(&aggr->p, buf, sizeof(buf)); + + if (uj) { + + json_aggr = json_object_new_object(); + + json_object_object_add(json_vrf, buf, + json_aggr); + json_object_string_add(json_aggr, + "summaryAddress", buf); + json_object_string_add( + json_aggr, "metricType", + (mtype == EXTERNAL_METRIC_TYPE_1) + ? "E1" + : "E2"); + + json_object_int_add(json_aggr, "metric", mval); + json_object_int_add(json_aggr, "tag", + aggr->tag); + json_object_int_add( + json_aggr, "externalRouteCount", + OSPF_EXTERNAL_RT_COUNT(aggr)); + + if (OSPF_EXTERNAL_RT_COUNT(aggr) && detail) { + hash_walk( + aggr->match_extnl_hash, + ospf_json_external_rt_walkcb, + json_aggr); + } + + } else { + vty_out(vty, "%-20s", buf); + + (mtype == EXTERNAL_METRIC_TYPE_1) + ? vty_out(vty, "%-16s", "E1") + : vty_out(vty, "%-16s", "E2"); + vty_out(vty, "%-11d", mval); + + vty_out(vty, "%-12u", aggr->tag); + + vty_out(vty, "%-5ld\n", + OSPF_EXTERNAL_RT_COUNT(aggr)); + + if (OSPF_EXTERNAL_RT_COUNT(aggr) && detail) { + vty_out(vty, + "Matched External routes:\n"); + hash_walk( + aggr->match_extnl_hash, + ospf_vty_external_rt_walkcb, + vty); + vty_out(vty, "\n"); + } + + vty_out(vty, "\n"); + } + } + + if (uj) { + if (use_vrf) + json_object_object_add(json, ospf_get_name(ospf), + json_vrf); + } else + vty_out(vty, "\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_ospf_external_aggregator, + show_ip_ospf_external_aggregator_cmd, + "show ip ospf [vrf ] summary-address [detail] [json]", + SHOW_STR IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "All VRFs\n" + "Show external summary addresses\n" + "Detailed information\n" + JSON_STR) +{ + char *vrf_name = NULL; + bool all_vrf = false; + int ret = CMD_SUCCESS; + int idx_vrf = 0; + int idx = 0; + uint8_t use_vrf = 0; + bool uj = use_json(argc, argv); + struct ospf *ospf = NULL; + json_object *json = NULL; + struct listnode *node = NULL; + int inst = 0; + bool detail = false; + + OSPF_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf); + + if (argv_find(argv, argc, "detail", &idx)) + detail = true; + + if (uj) + json = json_object_new_object(); + + /* vrf input is provided */ + if (vrf_name) { + use_vrf = 1; + if (all_vrf) { + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->oi_running) + continue; + ret = ospf_show_summary_address( + vty, ospf, use_vrf, json, uj, detail); + } + + if (uj) + vty_json(vty, json); + + return ret; + } + + ospf = ospf_lookup_by_inst_name(inst, vrf_name); + + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf %s\n", + vrf_name); + + return CMD_SUCCESS; + } + ospf_show_summary_address(vty, ospf, use_vrf, json, uj, detail); + + } else { + /* Default Vrf */ + ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); + if (ospf == NULL || !ospf->oi_running) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "%% OSPF is not enabled in vrf default\n"); + + return CMD_SUCCESS; + } + + ospf_show_summary_address(vty, ospf, use_vrf, json, uj, detail); + } + + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; +} + +static const char *const ospf_int_type_str[] = { + "unknown", /* should never be used. */ + "point-to-point", + "broadcast", + "non-broadcast", + "point-to-multipoint", + "virtual-link", /* should never be used. */ + "loopback" +}; + +static int interface_config_auth_str(struct ospf_if_params *params, char *buf) +{ + if (!OSPF_IF_PARAM_CONFIGURED(params, auth_type) + || params->auth_type == OSPF_AUTH_NOTSET) + return 0; + + /* Translation tables are not that much help + * here due to syntax + * of the simple option */ + switch (params->auth_type) { + + case OSPF_AUTH_NULL: + snprintf(buf, BUFSIZ, " null"); + break; + + case OSPF_AUTH_SIMPLE: + snprintf(buf, BUFSIZ, " "); + break; + + case OSPF_AUTH_CRYPTOGRAPHIC: + if (OSPF_IF_PARAM_CONFIGURED(params, keychain_name)) + snprintf(buf, BUFSIZ, " key-chain %s", params->keychain_name); + else + snprintf(buf, BUFSIZ, " message-digest"); + break; + } + + return 1; +} + +static int config_write_interface_one(struct vty *vty, struct vrf *vrf) +{ + struct listnode *node; + struct interface *ifp; + struct crypt_key *ck; + struct route_node *rn = NULL; + struct ospf_if_params *params; + char buf[BUFSIZ]; + int ret = 0; + int write = 0; + + FOR_ALL_INTERFACES (vrf, ifp) { + + if (memcmp(ifp->name, "VLINK", 5) == 0) + continue; + + if_vty_config_start(vty, ifp); + + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + + write++; + + params = IF_DEF_PARAMS(ifp); + + do { + /* Interface Network print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, type) && + params->type != OSPF_IFTYPE_LOOPBACK && + params->type_cfg) { + vty_out(vty, " ip ospf network %s", + ospf_int_type_str[params->type]); + if (params->type == OSPF_IFTYPE_POINTOPOINT && + params->ptp_dmvpn) + vty_out(vty, " dmvpn"); + if (params->type == + OSPF_IFTYPE_POINTOMULTIPOINT && + params->p2mp_delay_reflood) + vty_out(vty, " delay-reflood"); + if (params->type == + OSPF_IFTYPE_POINTOMULTIPOINT && + params->p2mp_non_broadcast) + vty_out(vty, " non-broadcast"); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* OSPF interface authentication print */ + ret = interface_config_auth_str(params, buf); + if (ret) { + vty_out(vty, " ip ospf authentication%s", + buf); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Simple Authentication Password print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, auth_simple) + && params->auth_simple[0] != '\0') { + vty_out(vty, " ip ospf authentication-key %s", + params->auth_simple); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Cryptographic Authentication Key print. */ + if (params && params->auth_crypt) { + for (ALL_LIST_ELEMENTS_RO(params->auth_crypt, + node, ck)) { + vty_out(vty, + " ip ospf message-digest-key %d md5 %s", + ck->key_id, ck->auth_key); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + } + + /* Interface Output Cost print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, output_cost_cmd)) { + vty_out(vty, " ip ospf cost %u", + params->output_cost_cmd); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Hello Interval print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, v_hello) + && params->v_hello != OSPF_HELLO_INTERVAL_DEFAULT) { + vty_out(vty, " ip ospf hello-interval %u", + params->v_hello); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + + /* Router Dead Interval print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, v_wait) + && params->is_v_wait_set) { + vty_out(vty, " ip ospf dead-interval "); + + /* fast hello ? */ + if (OSPF_IF_PARAM_CONFIGURED(params, + fast_hello)) + vty_out(vty, + "minimal hello-multiplier %d", + params->fast_hello); + else + vty_out(vty, "%u", params->v_wait); + + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Hello Graceful-Restart Delay print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, + v_gr_hello_delay) && + params->v_gr_hello_delay != + OSPF_HELLO_DELAY_DEFAULT) + vty_out(vty, + " ip ospf graceful-restart hello-delay %u\n", + params->v_gr_hello_delay); + + /* Router Priority print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, priority) + && params->priority + != OSPF_ROUTER_PRIORITY_DEFAULT) { + vty_out(vty, " ip ospf priority %u", + params->priority); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Retransmit Interval print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, + retransmit_interval) + && params->retransmit_interval + != OSPF_RETRANSMIT_INTERVAL_DEFAULT) { + vty_out(vty, " ip ospf retransmit-interval %u", + params->retransmit_interval); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Transmit Delay print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, transmit_delay) + && params->transmit_delay + != OSPF_TRANSMIT_DELAY_DEFAULT) { + vty_out(vty, " ip ospf transmit-delay %u", + params->transmit_delay); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* Area print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) { + if (ospf_instance) + vty_out(vty, " ip ospf %d", + ospf_instance); + else + vty_out(vty, " ip ospf"); + + char buf[INET_ADDRSTRLEN]; + + area_id2str(buf, sizeof(buf), ¶ms->if_area, + params->if_area_id_fmt); + vty_out(vty, " area %s", buf); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* bfd print. */ + if (params && params->bfd_config) + ospf_bfd_write_config(vty, params); + + /* MTU ignore print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, mtu_ignore) + && params->mtu_ignore != OSPF_MTU_IGNORE_DEFAULT) { + if (params->mtu_ignore == 0) + vty_out(vty, " no ip ospf mtu-ignore"); + else + vty_out(vty, " ip ospf mtu-ignore"); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", + &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + if (OSPF_IF_PARAM_CONFIGURED(params, + passive_interface)) { + vty_out(vty, " %sip ospf passive", + params->passive_interface + == OSPF_IF_ACTIVE + ? "no " + : ""); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* LDP-Sync print */ + if (params && params->ldp_sync_info) + ospf_ldp_sync_if_write_config(vty, params); + + /* Capability opaque print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, opaque_capable) && + params->opaque_capable != + OSPF_OPAQUE_CAPABLE_DEFAULT) { + if (params->opaque_capable == false) + vty_out(vty, + " no ip ospf capability opaque"); + else + vty_out(vty, + " ip ospf capability opaque"); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* prefix-suppression print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, + prefix_suppression) && + params->prefix_suppression != + OSPF_PREFIX_SUPPRESSION_DEFAULT) { + if (params->prefix_suppression == false) + vty_out(vty, + " no ip ospf prefix-suppression"); + else + vty_out(vty, + " ip ospf prefix-suppression"); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + /* neighbor-filter print. */ + if (OSPF_IF_PARAM_CONFIGURED(params, nbr_filter_name)) { + vty_out(vty, " ip ospf neighbor-filter %s", + params->nbr_filter_name); + if (params != IF_DEF_PARAMS(ifp) && rn) + vty_out(vty, " %pI4", &rn->p.u.prefix4); + vty_out(vty, "\n"); + } + + while (1) { + if (rn == NULL) + rn = route_top(IF_OIFS_PARAMS(ifp)); + else + rn = route_next(rn); + + if (rn == NULL) + break; + params = rn->info; + if (params != NULL) + break; + } + } while (rn); + + ospf_opaque_config_write_if(vty, ifp); + + if_vty_config_end(vty); + } + + return write; +} + +/* Configuration write function for ospfd. */ +static int config_write_interface(struct vty *vty) +{ + int write = 0; + struct vrf *vrf = NULL; + + /* Display all VRF aware OSPF interface configuration */ + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + write += config_write_interface_one(vty, vrf); + } + + return write; +} + +static int config_write_network_area(struct vty *vty, struct ospf *ospf) +{ + struct route_node *rn; + char buf[INET_ADDRSTRLEN]; + + /* `network area' print. */ + for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) + if (rn->info) { + struct ospf_network *n = rn->info; + + /* Create Area ID string by specified Area ID format. */ + if (n->area_id_fmt == OSPF_AREA_ID_FMT_DOTTEDQUAD) + inet_ntop(AF_INET, &n->area_id, buf, + sizeof(buf)); + else + snprintf(buf, sizeof(buf), "%lu", + (unsigned long int)ntohl( + n->area_id.s_addr)); + + /* Network print. */ + vty_out(vty, " network %pFX area %s\n", &rn->p, buf); + } + + return 0; +} + +static int config_write_ospf_area(struct vty *vty, struct ospf *ospf) +{ + struct listnode *node; + struct ospf_area *area; + char buf[INET_ADDRSTRLEN]; + + /* Area configuration print. */ + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + struct route_node *rn1; + + area_id2str(buf, sizeof(buf), &area->area_id, + area->area_id_fmt); + + if (area->auth_type != OSPF_AUTH_NULL) { + if (area->auth_type == OSPF_AUTH_SIMPLE) + vty_out(vty, " area %s authentication\n", buf); + else + vty_out(vty, + " area %s authentication message-digest\n", + buf); + } + + if (area->shortcut_configured != OSPF_SHORTCUT_DEFAULT) + vty_out(vty, " area %s shortcut %s\n", buf, + ospf_shortcut_mode_str + [area->shortcut_configured]); + + if ((area->external_routing == OSPF_AREA_STUB) + || (area->external_routing == OSPF_AREA_NSSA)) { + if (area->external_routing == OSPF_AREA_STUB) { + vty_out(vty, " area %s stub", buf); + if (area->no_summary) + vty_out(vty, " no-summary\n"); + vty_out(vty, "\n"); + } else if (area->external_routing == OSPF_AREA_NSSA) { + vty_out(vty, " area %s nssa", buf); + + switch (area->NSSATranslatorRole) { + case OSPF_NSSA_ROLE_NEVER: + vty_out(vty, " translate-never"); + break; + case OSPF_NSSA_ROLE_ALWAYS: + vty_out(vty, " translate-always"); + break; + case OSPF_NSSA_ROLE_CANDIDATE: + break; + } + + if (area->nssa_default_originate.enabled) { + vty_out(vty, + " default-information-originate"); + if (area->nssa_default_originate + .metric_value != -1) + vty_out(vty, " metric %d", + area->nssa_default_originate + .metric_value); + if (area->nssa_default_originate + .metric_type != + DEFAULT_METRIC_TYPE) + vty_out(vty, " metric-type 1"); + } + + if (area->no_summary) + vty_out(vty, " no-summary"); + if (area->suppress_fa) + vty_out(vty, " suppress-fa"); + vty_out(vty, "\n"); + + for (rn1 = route_top(area->nssa_ranges); rn1; + rn1 = route_next(rn1)) { + struct ospf_area_range *range; + + range = rn1->info; + if (!range) + continue; + + vty_out(vty, " area %s nssa range %pFX", + buf, &rn1->p); + + if (range->cost_config != + OSPF_AREA_RANGE_COST_UNSPEC) + vty_out(vty, " cost %u", + range->cost_config); + + if (!CHECK_FLAG( + range->flags, + OSPF_AREA_RANGE_ADVERTISE)) + vty_out(vty, " not-advertise"); + + vty_out(vty, "\n"); + } + } + + if (area->default_cost != 1) + vty_out(vty, " area %s default-cost %d\n", buf, + area->default_cost); + } + + for (rn1 = route_top(area->ranges); rn1; rn1 = route_next(rn1)) + if (rn1->info) { + struct ospf_area_range *range = rn1->info; + + vty_out(vty, " area %s range %pFX", buf, + &rn1->p); + + if (range->cost_config + != OSPF_AREA_RANGE_COST_UNSPEC) + vty_out(vty, " cost %d", + range->cost_config); + + if (!CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_ADVERTISE)) + vty_out(vty, " not-advertise"); + + if (CHECK_FLAG(range->flags, + OSPF_AREA_RANGE_SUBSTITUTE)) + vty_out(vty, " substitute %pI4/%d", + &range->subst_addr, + range->subst_masklen); + + vty_out(vty, "\n"); + } + + if (EXPORT_NAME(area)) + vty_out(vty, " area %s export-list %s\n", buf, + EXPORT_NAME(area)); + + if (IMPORT_NAME(area)) + vty_out(vty, " area %s import-list %s\n", buf, + IMPORT_NAME(area)); + + if (PREFIX_NAME_IN(area)) + vty_out(vty, " area %s filter-list prefix %s in\n", buf, + PREFIX_NAME_IN(area)); + + if (PREFIX_NAME_OUT(area)) + vty_out(vty, " area %s filter-list prefix %s out\n", + buf, PREFIX_NAME_OUT(area)); + + if (area->fr_info.configured) + vty_out(vty, " area %s flood-reduction\n", buf); + } + + return 0; +} + +static int config_write_ospf_nbr_nbma(struct vty *vty, struct ospf *ospf) +{ + struct ospf_nbr_nbma *nbr_nbma; + struct route_node *rn; + + /* Static Neighbor configuration print. */ + for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) + if ((nbr_nbma = rn->info)) { + vty_out(vty, " neighbor %pI4", &nbr_nbma->addr); + + if (nbr_nbma->priority + != OSPF_NEIGHBOR_PRIORITY_DEFAULT) + vty_out(vty, " priority %d", + nbr_nbma->priority); + + if (nbr_nbma->v_poll != OSPF_POLL_INTERVAL_DEFAULT) + vty_out(vty, " poll-interval %d", + nbr_nbma->v_poll); + + vty_out(vty, "\n"); + } + + return 0; +} + +static int config_write_virtual_link(struct vty *vty, struct ospf *ospf) +{ + struct listnode *node; + struct ospf_vl_data *vl_data; + char buf[INET_ADDRSTRLEN]; + char buf2[BUFSIZ]; + int ret = 0; + + /* Virtual-Link print */ + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl_data)) { + struct listnode *n2; + struct crypt_key *ck; + struct ospf_interface *oi; + + if (vl_data != NULL) { + area_id2str(buf, sizeof(buf), &vl_data->vl_area_id, + vl_data->vl_area_id_fmt); + oi = vl_data->vl_oi; + + /* timers */ + if (OSPF_IF_PARAM(oi, v_hello) + != OSPF_HELLO_INTERVAL_DEFAULT + || OSPF_IF_PARAM(oi, v_wait) + != OSPF_ROUTER_DEAD_INTERVAL_DEFAULT + || OSPF_IF_PARAM(oi, retransmit_interval) + != OSPF_RETRANSMIT_INTERVAL_DEFAULT + || OSPF_IF_PARAM(oi, transmit_delay) + != OSPF_TRANSMIT_DELAY_DEFAULT) + vty_out(vty, + " area %s virtual-link %pI4 hello-interval %d retransmit-interval %d transmit-delay %d dead-interval %d\n", + buf, &vl_data->vl_peer, + OSPF_IF_PARAM(oi, v_hello), + OSPF_IF_PARAM(oi, retransmit_interval), + OSPF_IF_PARAM(oi, transmit_delay), + OSPF_IF_PARAM(oi, v_wait)); + else + vty_out(vty, " area %s virtual-link %pI4\n", buf, + &vl_data->vl_peer); + /* Auth type */ + ret = interface_config_auth_str( + IF_DEF_PARAMS(oi->ifp), buf2); + if (ret) + vty_out(vty, + " area %s virtual-link %pI4 authentication%s\n", + buf, &vl_data->vl_peer, buf2); + /* Auth key */ + if (IF_DEF_PARAMS(vl_data->vl_oi->ifp)->auth_simple[0] + != '\0') + vty_out(vty, + " area %s virtual-link %pI4 authentication-key %s\n", + buf, &vl_data->vl_peer, + IF_DEF_PARAMS(vl_data->vl_oi->ifp) + ->auth_simple); + /* md5 keys */ + for (ALL_LIST_ELEMENTS_RO( + IF_DEF_PARAMS(vl_data->vl_oi->ifp) + ->auth_crypt, + n2, ck)) + vty_out(vty, + " area %s virtual-link %pI4 message-digest-key %d md5 %s\n", + buf, &vl_data->vl_peer, + ck->key_id, ck->auth_key); + } + } + + return 0; +} + + +static int config_write_ospf_redistribute(struct vty *vty, struct ospf *ospf) +{ + int type; + + /* redistribute print. */ + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct listnode *node; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + vty_out(vty, " redistribute %s", + zebra_route_string(type)); + if (red->instance) + vty_out(vty, " %d", red->instance); + + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", red->dmetric.value); + + if (red->dmetric.type == EXTERNAL_METRIC_TYPE_1) + vty_out(vty, " metric-type 1"); + + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", + ROUTEMAP_NAME(red)); + + vty_out(vty, "\n"); + } + } + + return 0; +} + +static int ospf_cfg_write_helper_dis_rtr_walkcb(struct hash_bucket *bucket, + void *arg) +{ + struct advRtr *rtr = bucket->data; + struct vty *vty = (struct vty *)arg; + + vty_out(vty, " graceful-restart helper enable %pI4\n", + &rtr->advRtrAddr); + return HASHWALK_CONTINUE; +} + +static void config_write_ospf_gr(struct vty *vty, struct ospf *ospf) +{ + if (!ospf->gr_info.restart_support) + return; + + if (ospf->gr_info.grace_period == OSPF_DFLT_GRACE_INTERVAL) + vty_out(vty, " graceful-restart\n"); + else + vty_out(vty, " graceful-restart grace-period %u\n", + ospf->gr_info.grace_period); +} + +static int config_write_ospf_gr_helper(struct vty *vty, struct ospf *ospf) +{ + if (ospf->is_helper_supported) + vty_out(vty, " graceful-restart helper enable\n"); + + if (!ospf->strict_lsa_check) + vty_out(vty, + " no graceful-restart helper strict-lsa-checking\n"); + + if (ospf->only_planned_restart) + vty_out(vty, " graceful-restart helper planned-only\n"); + + if (ospf->supported_grace_time != OSPF_MAX_GRACE_INTERVAL) + vty_out(vty, + " graceful-restart helper supported-grace-time %d\n", + ospf->supported_grace_time); + + if (OSPF_HELPER_ENABLE_RTR_COUNT(ospf)) { + hash_walk(ospf->enable_rtr_list, + ospf_cfg_write_helper_dis_rtr_walkcb, vty); + } + return 0; +} + +static int config_write_ospf_external_aggregator(struct vty *vty, + struct ospf *ospf) +{ + struct route_node *rn; + + if (ospf->aggr_delay_interval != OSPF_EXTL_AGGR_DEFAULT_DELAY) + vty_out(vty, " aggregation timer %u\n", + ospf->aggr_delay_interval); + + /* print 'summary-address A.B.C.D/M' */ + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) + if (rn->info) { + struct ospf_external_aggr_rt *aggr = rn->info; + + vty_out(vty, " summary-address %pI4/%d", + &aggr->p.prefix, aggr->p.prefixlen); + if (aggr->tag) + vty_out(vty, " tag %u", aggr->tag); + + if (CHECK_FLAG(aggr->flags, + OSPF_EXTERNAL_AGGRT_NO_ADVERTISE)) + vty_out(vty, " no-advertise"); + + vty_out(vty, "\n"); + } + + return 0; +} + +static int config_write_ospf_default_metric(struct vty *vty, struct ospf *ospf) +{ + if (ospf->default_metric != -1) + vty_out(vty, " default-metric %d\n", ospf->default_metric); + return 0; +} + +static int config_write_ospf_distribute(struct vty *vty, struct ospf *ospf) +{ + int type; + struct ospf_redist *red; + + if (ospf) { + /* distribute-list print. */ + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) + if (DISTRIBUTE_NAME(ospf, type)) + vty_out(vty, " distribute-list %s out %s\n", + DISTRIBUTE_NAME(ospf, type), + zebra_route_string(type)); + + /* default-information print. */ + if (ospf->default_originate != DEFAULT_ORIGINATE_NONE) { + vty_out(vty, " default-information originate"); + if (ospf->default_originate == DEFAULT_ORIGINATE_ALWAYS) + vty_out(vty, " always"); + + red = ospf_redist_lookup(ospf, DEFAULT_ROUTE, 0); + if (red) { + if (red->dmetric.value >= 0) + vty_out(vty, " metric %d", + red->dmetric.value); + + if (red->dmetric.type == EXTERNAL_METRIC_TYPE_1) + vty_out(vty, " metric-type 1"); + + if (ROUTEMAP_NAME(red)) + vty_out(vty, " route-map %s", + ROUTEMAP_NAME(red)); + } + + vty_out(vty, "\n"); + } + } + + return 0; +} + +static int config_write_ospf_distance(struct vty *vty, struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_distance *odistance; + + if (ospf->distance_all) + vty_out(vty, " distance %d\n", ospf->distance_all); + + if (ospf->distance_intra || ospf->distance_inter + || ospf->distance_external) { + vty_out(vty, " distance ospf"); + + if (ospf->distance_intra) + vty_out(vty, " intra-area %d", ospf->distance_intra); + if (ospf->distance_inter) + vty_out(vty, " inter-area %d", ospf->distance_inter); + if (ospf->distance_external) + vty_out(vty, " external %d", ospf->distance_external); + + vty_out(vty, "\n"); + } + + for (rn = route_top(ospf->distance_table); rn; rn = route_next(rn)) + if ((odistance = rn->info) != NULL) { + vty_out(vty, " distance %d %pFX %s\n", + odistance->distance, &rn->p, + odistance->access_list ? odistance->access_list + : ""); + } + return 0; +} + +static int ospf_config_write_one(struct vty *vty, struct ospf *ospf) +{ + int write = 0; + + /* `router ospf' print. */ + if (ospf->instance && strcmp(ospf->name, VRF_DEFAULT_NAME)) { + vty_out(vty, "router ospf %d vrf %s\n", ospf->instance, + ospf->name); + } else if (ospf->instance) { + vty_out(vty, "router ospf %d\n", ospf->instance); + } else if (strcmp(ospf->name, VRF_DEFAULT_NAME)) { + vty_out(vty, "router ospf vrf %s\n", ospf->name); + } else + vty_out(vty, "router ospf\n"); + + if (!ospf->networks) { + write++; + return write; + } + + /* Router ID print. */ + if (ospf->router_id_static.s_addr != INADDR_ANY) + vty_out(vty, " ospf router-id %pI4\n", + &ospf->router_id_static); + + /* zebra opaque attributes configuration. */ + if (CHECK_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA)) + vty_out(vty, " ospf send-extra-data zebra\n"); + + /* ABR type print. */ + if (ospf->abr_type != OSPF_ABR_DEFAULT) + vty_out(vty, " ospf abr-type %s\n", + ospf_abr_type_str[ospf->abr_type]); + + /* log-adjacency-changes flag print. */ + if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_CHANGES)) { + if (CHECK_FLAG(ospf->config, OSPF_LOG_ADJACENCY_DETAIL)) + vty_out(vty, " log-adjacency-changes detail\n"); + else if (!SAVE_OSPF_LOG_ADJACENCY_CHANGES) + vty_out(vty, " log-adjacency-changes\n"); + } else if (SAVE_OSPF_LOG_ADJACENCY_CHANGES) { + vty_out(vty, " no log-adjacency-changes\n"); + } + + /* RFC1583 compatibility flag print -- Compatible with CISCO + * 12.1. */ + if (CHECK_FLAG(ospf->config, OSPF_RFC1583_COMPATIBLE)) + vty_out(vty, " compatible rfc1583\n"); + + /* auto-cost reference-bandwidth configuration. */ + if (ospf->ref_bandwidth != OSPF_DEFAULT_REF_BANDWIDTH) { + vty_out(vty, + "! Important: ensure reference bandwidth is consistent across all routers\n"); + vty_out(vty, " auto-cost reference-bandwidth %d\n", + ospf->ref_bandwidth); + } + + /* SPF timers print. */ + if (ospf->spf_delay != OSPF_SPF_DELAY_DEFAULT + || ospf->spf_holdtime != OSPF_SPF_HOLDTIME_DEFAULT + || ospf->spf_max_holdtime != OSPF_SPF_MAX_HOLDTIME_DEFAULT) + vty_out(vty, " timers throttle spf %d %d %d\n", ospf->spf_delay, + ospf->spf_holdtime, ospf->spf_max_holdtime); + + /* LSA timers print. */ + if (ospf->min_ls_interval != OSPF_MIN_LS_INTERVAL) + vty_out(vty, " timers throttle lsa all %d\n", + ospf->min_ls_interval); + if (ospf->min_ls_arrival != OSPF_MIN_LS_ARRIVAL) + vty_out(vty, " timers lsa min-arrival %d\n", + ospf->min_ls_arrival); + + /* Write multiplier print. */ + if (ospf->write_oi_count != OSPF_WRITE_INTERFACE_COUNT_DEFAULT) + vty_out(vty, " ospf write-multiplier %d\n", + ospf->write_oi_count); + + if (ospf->max_multipath != MULTIPATH_NUM) + vty_out(vty, " maximum-paths %d\n", ospf->max_multipath); + + /* Max-metric router-lsa print */ + config_write_stub_router(vty, ospf); + + /* SPF refresh parameters print. */ + if (ospf->lsa_refresh_interval != OSPF_LSA_REFRESH_INTERVAL_DEFAULT) + vty_out(vty, " refresh timer %d\n", ospf->lsa_refresh_interval); + + if (ospf->fr_configured) + vty_out(vty, " flood-reduction\n"); + + if (!ospf->intf_socket_enabled) + vty_out(vty, " no socket-per-interface\n"); + + /* Redistribute information print. */ + config_write_ospf_redistribute(vty, ospf); + + /* Graceful Restart print */ + config_write_ospf_gr(vty, ospf); + config_write_ospf_gr_helper(vty, ospf); + + /* Print external route aggregation. */ + config_write_ospf_external_aggregator(vty, ospf); + + /* passive-interface print. */ + if (ospf->passive_interface_default == OSPF_IF_PASSIVE) + vty_out(vty, " passive-interface default\n"); + + /* proactive-arp print. */ + if (ospf->proactive_arp != OSPF_PROACTIVE_ARP_DEFAULT) { + if (ospf->proactive_arp) + vty_out(vty, " proactive-arp\n"); + else + vty_out(vty, " no proactive-arp\n"); + } + + /* TI-LFA print. */ + if (ospf->ti_lfa_enabled) { + if (ospf->ti_lfa_protection_type == OSPF_TI_LFA_NODE_PROTECTION) + vty_out(vty, " fast-reroute ti-lfa node-protection\n"); + else + vty_out(vty, " fast-reroute ti-lfa\n"); + } + + /* Network area print. */ + config_write_network_area(vty, ospf); + + /* Area config print. */ + config_write_ospf_area(vty, ospf); + + /* static neighbor print. */ + config_write_ospf_nbr_nbma(vty, ospf); + + /* Virtual-Link print. */ + config_write_virtual_link(vty, ospf); + + /* Default metric configuration. */ + config_write_ospf_default_metric(vty, ospf); + + /* Distribute-list and default-information print. */ + config_write_ospf_distribute(vty, ospf); + + /* Distance configuration. */ + config_write_ospf_distance(vty, ospf); + + ospf_opaque_config_write_router(vty, ospf); + + /* LDP-Sync print */ + ospf_ldp_sync_write_config(vty, ospf); + + /* Socket buffer sizes */ + if (ospf->recv_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE) { + if (ospf->send_sock_bufsize == ospf->recv_sock_bufsize) + vty_out(vty, " socket buffer all %u\n", + ospf->recv_sock_bufsize); + else + vty_out(vty, " socket buffer recv %u\n", + ospf->recv_sock_bufsize); + } + + if (ospf->send_sock_bufsize != OSPF_DEFAULT_SOCK_BUFSIZE && + ospf->send_sock_bufsize != ospf->recv_sock_bufsize) + vty_out(vty, " socket buffer send %u\n", + ospf->send_sock_bufsize); + + + vty_out(vty, "exit\n"); + + write++; + return write; +} + +/* OSPF configuration write function. */ +static int ospf_config_write(struct vty *vty) +{ + struct ospf *ospf; + struct listnode *ospf_node = NULL; + int write = 0; + + if (listcount(om->ospf) == 0) + return write; + + for (ALL_LIST_ELEMENTS_RO(om->ospf, ospf_node, ospf)) { + /* VRF Default check if it is running. + * Upon daemon start, there could be default instance + * in absence of 'router ospf'/oi_running is disabled. */ + if (ospf->vrf_id == VRF_DEFAULT && ospf->oi_running) + write += ospf_config_write_one(vty, ospf); + /* For Non-Default VRF simply display the configuration, + * even if it is not oi_running. */ + else if (ospf->vrf_id != VRF_DEFAULT) + write += ospf_config_write_one(vty, ospf); + } + return write; +} + +void ospf_vty_show_init(void) +{ + /* "show ip ospf" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_cmd); + + install_element(VIEW_NODE, &show_ip_ospf_instance_cmd); + + /* "show ip ospf database" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_database_cmd); + + /* "show ip ospf interface" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_interface_cmd); + + install_element(VIEW_NODE, &show_ip_ospf_instance_interface_cmd); + /* "show ip ospf interface traffic */ + install_element(VIEW_NODE, &show_ip_ospf_interface_traffic_cmd); + + /* "show ip ospf neighbor" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_neighbor_int_detail_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_int_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_id_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_detail_all_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_detail_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_cmd); + install_element(VIEW_NODE, &show_ip_ospf_neighbor_all_cmd); + + install_element(VIEW_NODE, + &show_ip_ospf_instance_neighbor_int_detail_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_int_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_id_cmd); + install_element(VIEW_NODE, + &show_ip_ospf_instance_neighbor_detail_all_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_detail_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_neighbor_all_cmd); + + /* "show ip ospf route" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_route_cmd); + install_element(VIEW_NODE, &show_ip_ospf_border_routers_cmd); + install_element(VIEW_NODE, &show_ip_ospf_reachable_routers_cmd); + + install_element(VIEW_NODE, &show_ip_ospf_instance_route_cmd); + install_element(VIEW_NODE, &show_ip_ospf_instance_border_routers_cmd); + install_element(VIEW_NODE, + &show_ip_ospf_instance_reachable_routers_cmd); + + /* "show ip ospf vrfs" commands. */ + install_element(VIEW_NODE, &show_ip_ospf_vrfs_cmd); + + /* "show ip ospf gr-helper details" command */ + install_element(VIEW_NODE, &show_ip_ospf_gr_helper_cmd); + + /* "show ip ospf summary-address" command */ + install_element(VIEW_NODE, &show_ip_ospf_external_aggregator_cmd); +} + +/* Initialization of OSPF interface. */ +static void ospf_vty_if_init(void) +{ + /* Install interface node. */ + if_cmd_init(config_write_interface); + + /* "ip ospf authentication" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_authentication_args_addr_cmd); + install_element(INTERFACE_NODE, &ip_ospf_authentication_addr_cmd); + install_element(INTERFACE_NODE, + &no_ip_ospf_authentication_args_addr_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_authentication_addr_cmd); + install_element(INTERFACE_NODE, &ip_ospf_authentication_key_addr_cmd); + install_element(INTERFACE_NODE, + &no_ip_ospf_authentication_key_authkey_addr_cmd); + install_element(INTERFACE_NODE, + &no_ospf_authentication_key_authkey_addr_cmd); + + /* "ip ospf message-digest-key" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_message_digest_key_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_message_digest_key_cmd); + + /* "ip ospf cost" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_cost_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_cost_cmd); + + /* "ip ospf mtu-ignore" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_mtu_ignore_addr_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_mtu_ignore_addr_cmd); + + /* "ip ospf dead-interval" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_dead_interval_cmd); + install_element(INTERFACE_NODE, + &ip_ospf_dead_interval_minimal_addr_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_dead_interval_cmd); + + /* "ip ospf hello-interval" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_hello_interval_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_hello_interval_cmd); + + /* "ip ospf network" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_network_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_network_cmd); + + /* "ip ospf priority" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_priority_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_priority_cmd); + + /* "ip ospf retransmit-interval" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_retransmit_interval_addr_cmd); + install_element(INTERFACE_NODE, + &no_ip_ospf_retransmit_interval_addr_cmd); + + /* "ip ospf transmit-delay" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_transmit_delay_addr_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_transmit_delay_addr_cmd); + + /* "ip ospf area" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_area_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_area_cmd); + + /* "ip ospf passive" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_passive_cmd); + install_element(INTERFACE_NODE, &no_ip_ospf_passive_cmd); + + /* "ip ospf capability opaque" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_capability_opaque_addr_cmd); + + /* "ip ospf prefix-suppression" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_prefix_suppression_addr_cmd); + + /* "ip ospf neighbor-filter" commands. */ + install_element(INTERFACE_NODE, &ip_ospf_neighbor_filter_addr_cmd); + + /* These commands are compatibitliy for previous version. */ + install_element(INTERFACE_NODE, &ospf_authentication_key_cmd); + install_element(INTERFACE_NODE, &ospf_message_digest_key_cmd); + install_element(INTERFACE_NODE, &no_ospf_message_digest_key_cmd); + install_element(INTERFACE_NODE, &ospf_dead_interval_cmd); + install_element(INTERFACE_NODE, &no_ospf_dead_interval_cmd); + install_element(INTERFACE_NODE, &ospf_hello_interval_cmd); + install_element(INTERFACE_NODE, &no_ospf_hello_interval_cmd); + install_element(INTERFACE_NODE, &ospf_cost_cmd); + install_element(INTERFACE_NODE, &no_ospf_cost_cmd); + install_element(INTERFACE_NODE, &ospf_network_cmd); + install_element(INTERFACE_NODE, &no_ospf_network_cmd); + install_element(INTERFACE_NODE, &ospf_priority_cmd); + install_element(INTERFACE_NODE, &no_ospf_priority_cmd); + install_element(INTERFACE_NODE, &ospf_retransmit_interval_cmd); + install_element(INTERFACE_NODE, &no_ospf_retransmit_interval_cmd); + install_element(INTERFACE_NODE, &ospf_transmit_delay_cmd); + install_element(INTERFACE_NODE, &no_ospf_transmit_delay_cmd); +} + +static void ospf_vty_zebra_init(void) +{ + install_element(OSPF_NODE, &ospf_redistribute_source_cmd); + install_element(OSPF_NODE, &no_ospf_redistribute_source_cmd); + install_element(OSPF_NODE, &ospf_redistribute_instance_source_cmd); + install_element(OSPF_NODE, &no_ospf_redistribute_instance_source_cmd); + + install_element(OSPF_NODE, &ospf_distribute_list_out_cmd); + install_element(OSPF_NODE, &no_ospf_distribute_list_out_cmd); + + install_element(OSPF_NODE, &ospf_default_information_originate_cmd); + install_element(OSPF_NODE, &no_ospf_default_information_originate_cmd); + + install_element(OSPF_NODE, &ospf_default_metric_cmd); + install_element(OSPF_NODE, &no_ospf_default_metric_cmd); + + install_element(OSPF_NODE, &ospf_distance_cmd); + install_element(OSPF_NODE, &no_ospf_distance_cmd); + install_element(OSPF_NODE, &no_ospf_distance_ospf_cmd); + install_element(OSPF_NODE, &ospf_distance_ospf_cmd); + + /*Ospf garcefull restart helper configurations */ + install_element(OSPF_NODE, &ospf_gr_helper_enable_cmd); + install_element(OSPF_NODE, &no_ospf_gr_helper_enable_cmd); + install_element(OSPF_NODE, &ospf_gr_helper_enable_lsacheck_cmd); + install_element(OSPF_NODE, &no_ospf_gr_helper_enable_lsacheck_cmd); + install_element(OSPF_NODE, &ospf_gr_helper_supported_grace_time_cmd); + install_element(OSPF_NODE, &no_ospf_gr_helper_supported_grace_time_cmd); + install_element(OSPF_NODE, &ospf_gr_helper_planned_only_cmd); + install_element(OSPF_NODE, &no_ospf_gr_helper_planned_only_cmd); + + /* External LSA summarisation config commands.*/ + install_element(OSPF_NODE, &ospf_external_route_aggregation_cmd); + install_element(OSPF_NODE, &no_ospf_external_route_aggregation_cmd); + install_element(OSPF_NODE, + &ospf_external_route_aggregation_no_adrvertise_cmd); + install_element(OSPF_NODE, + &no_ospf_external_route_aggregation_no_adrvertise_cmd); + install_element(OSPF_NODE, &ospf_route_aggregation_timer_cmd); + install_element(OSPF_NODE, &no_ospf_route_aggregation_timer_cmd); +} + +static int ospf_config_write(struct vty *vty); +static struct cmd_node ospf_node = { + .name = "ospf", + .node = OSPF_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + .config_write = ospf_config_write, +}; + +static void ospf_interface_clear(struct interface *ifp) +{ + if (!if_is_operative(ifp)) + return; + + if (IS_DEBUG_OSPF(ism, ISM_EVENTS)) + zlog_debug("ISM[%s]: clear by reset", ifp->name); + + ospf_if_reset(ifp); +} + +DEFUN (clear_ip_ospf_interface, + clear_ip_ospf_interface_cmd, + "clear ip ospf [vrf NAME] interface [IFNAME]", + CLEAR_STR + IP_STR + "OSPF information\n" + VRF_CMD_HELP_STR + "Interface information\n" + "Interface name\n") +{ + int idx_ifname = 0; + int idx_vrf = 0; + struct interface *ifp; + struct listnode *node; + struct ospf *ospf = NULL; + char *vrf_name = NULL; + vrf_id_t vrf_id = VRF_DEFAULT; + struct vrf *vrf = NULL; + + if (argv_find(argv, argc, "vrf", &idx_vrf)) + vrf_name = argv[idx_vrf + 1]->arg; + if (vrf_name && strmatch(vrf_name, VRF_DEFAULT_NAME)) + vrf_name = NULL; + if (vrf_name) { + vrf = vrf_lookup_by_name(vrf_name); + if (vrf) + vrf_id = vrf->vrf_id; + } + if (!argv_find(argv, argc, "IFNAME", &idx_ifname)) { + /* Clear all the ospfv2 interfaces. */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (vrf_id != ospf->vrf_id) + continue; + if (!vrf) + vrf = vrf_lookup_by_id(ospf->vrf_id); + FOR_ALL_INTERFACES (vrf, ifp) + ospf_interface_clear(ifp); + } + } else { + /* Interface name is specified. */ + ifp = if_lookup_by_name(argv[idx_ifname]->arg, vrf_id); + if (ifp == NULL) + vty_out(vty, "No such interface name\n"); + else + ospf_interface_clear(ifp); + } + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN(ospf_lsa_refresh_timer, ospf_lsa_refresh_timer_cmd, + "[no$no] ospf lsa-refresh [(120-1800)]$value", + NO_STR OSPF_STR + "OSPF lsa refresh timer\n" + "timer value in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + + if (no) + ospf->lsa_refresh_timer = OSPF_LS_REFRESH_TIME; + else + ospf->lsa_refresh_timer = value; + + return CMD_SUCCESS; +} + +DEFPY_HIDDEN(ospf_maxage_delay_timer, ospf_maxage_delay_timer_cmd, + "[no$no] ospf maxage-delay [(0-60)]$value", + NO_STR OSPF_STR + "OSPF lsa maxage delay timer\n" + "timer value in seconds\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + + if (no) + ospf->maxage_delay = OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT; + else + ospf->maxage_delay = value; + + EVENT_OFF(ospf->t_maxage); + OSPF_TIMER_ON(ospf->t_maxage, ospf_maxage_lsa_remover, + ospf->maxage_delay); + + return CMD_SUCCESS; +} + +/* + * ------------------------------------------------------------------------* + * Following is (vty) configuration functions for flood-reduction handling. + * ------------------------------------------------------------------------ + */ + +DEFPY(flood_reduction, flood_reduction_cmd, "flood-reduction", + "flood reduction feature\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + struct ospf_area *area; + struct listnode *node; + + /* Turn on the Flood Reduction feature for the router. */ + if (!ospf->fr_configured) { + ospf->fr_configured = true; + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "Flood Reduction: OFF -> ON"); + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (area) { + ospf_area_update_fr_state(area); + ospf_refresh_area_self_lsas(area); + } + } + } + + return CMD_SUCCESS; +} + +DEFPY(flood_reduction_area, flood_reduction_area_cmd, + "area flood-reduction", + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Enable flood reduction for area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + struct ospf_area *oa; + int idx = 1; + int format; + int ret; + const char *areaid; + struct in_addr area_id; + + areaid = argv[idx]->arg; + + ret = str2area_id(areaid, &area_id, &format); + if (ret < 0) { + vty_out(vty, "Please specify area by A.B.C.D|<0-4294967295>\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + oa = ospf_area_lookup_by_area_id(ospf, area_id); + if (!oa) { + vty_out(vty, "OSPF area ID not present\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Turn on the Flood Reduction feature for the area. */ + if (!oa->fr_info.configured) { + oa->fr_info.configured = true; + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "Flood Reduction area %pI4 : OFF -> ON", + &oa->area_id); + ospf_area_update_fr_state(oa); + ospf_refresh_area_self_lsas(oa); + } + + return CMD_SUCCESS; +} + +DEFPY(no_flood_reduction, no_flood_reduction_cmd, "no flood-reduction", + NO_STR "flood reduction feature\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + struct listnode *node; + struct ospf_area *area; + + /* Turn off the Flood Reduction feature for the router. */ + if (ospf->fr_configured) { + ospf->fr_configured = false; + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "Flood Reduction: ON -> OFF"); + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (area) { + ospf_area_update_fr_state(area); + ospf_refresh_area_self_lsas(area); + } + } + } + + return CMD_SUCCESS; +} + +DEFPY(no_flood_reduction_area, no_flood_reduction_area_cmd, + "no area flood-reduction", + NO_STR + "OSPF area parameters\n" + "OSPF area ID in IP address format\n" + "OSPF area ID as a decimal value\n" + "Disable flood reduction for area\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf) + struct ospf_area *oa; + int idx = 2; + int format; + int ret; + const char *areaid; + struct in_addr area_id; + + areaid = argv[idx]->arg; + + ret = str2area_id(areaid, &area_id, &format); + if (ret < 0) { + vty_out(vty, "Please specify area by A.B.C.D|<0-4294967295>\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + oa = ospf_area_lookup_by_area_id(ospf, area_id); + if (!oa) { + vty_out(vty, "OSPF area ID not present\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Turn off the Flood Reduction feature for the area. */ + if (oa->fr_info.configured) { + oa->fr_info.configured = false; + OSPF_LOG_DEBUG(IS_DEBUG_OSPF_EVENT, + "Flood Reduction area %pI4 : ON -> OFF", + &oa->area_id); + ospf_area_update_fr_state(oa); + ospf_refresh_area_self_lsas(oa); + } + + return CMD_SUCCESS; +} + +DEFPY(ospf_socket_bufsizes, + ospf_socket_bufsizes_cmd, + "[no] socket buffer \ + ![(1-4000000000)$bufsize]", + NO_STR + "Socket parameters\n" + "Buffer size configuration\n" + "Send buffer size\n" + "Receive buffer size\n" + "Both send and receive buffer sizes\n" + "Buffer size, in bytes\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + uint32_t recvsz, sendsz; + + if (no) + bufsize = OSPF_DEFAULT_SOCK_BUFSIZE; + + if (all_val) { + recvsz = bufsize; + sendsz = bufsize; + } else if (send_val) { + sendsz = bufsize; + recvsz = ospf->recv_sock_bufsize; + } else if (recv_val) { + recvsz = bufsize; + sendsz = ospf->send_sock_bufsize; + } else + return CMD_SUCCESS; + + /* React to a change by modifying existing sockets */ + ospf_update_bufsize(ospf, recvsz, sendsz); + + return CMD_SUCCESS; +} + +DEFPY (per_intf_socket, + per_intf_socket_cmd, + "[no] socket-per-interface", + NO_STR + "Use write socket per interface\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + struct listnode *node; + struct ospf_interface *oi; + + if (no) { + if (ospf->intf_socket_enabled) { + ospf->intf_socket_enabled = false; + + /* Iterate and close any sockets */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + ospf_ifp_sock_close(oi->ifp); + } + } else if (!ospf->intf_socket_enabled) { + ospf->intf_socket_enabled = true; + + /* Iterate and open sockets */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + ospf_ifp_sock_init(oi->ifp); + } + + return CMD_SUCCESS; +} + +void ospf_vty_clear_init(void) +{ + install_element(ENABLE_NODE, &clear_ip_ospf_interface_cmd); + install_element(ENABLE_NODE, &clear_ip_ospf_process_cmd); + install_element(ENABLE_NODE, &clear_ip_ospf_neighbor_cmd); +} + + +/* Install OSPF related vty commands. */ +void ospf_vty_init(void) +{ + /* Install ospf top node. */ + install_node(&ospf_node); + + /* "router ospf" commands. */ + install_element(CONFIG_NODE, &router_ospf_cmd); + install_element(CONFIG_NODE, &no_router_ospf_cmd); + + + install_default(OSPF_NODE); + + /* "ospf router-id" commands. */ + install_element(OSPF_NODE, &ospf_router_id_cmd); + install_element(OSPF_NODE, &ospf_router_id_old_cmd); + install_element(OSPF_NODE, &no_ospf_router_id_cmd); + + /* "passive-interface" commands. */ + install_element(OSPF_NODE, &ospf_passive_interface_default_cmd); + install_element(OSPF_NODE, &ospf_passive_interface_addr_cmd); + install_element(OSPF_NODE, &no_ospf_passive_interface_default_cmd); + install_element(OSPF_NODE, &no_ospf_passive_interface_addr_cmd); + + /* "ospf abr-type" commands. */ + install_element(OSPF_NODE, &ospf_abr_type_cmd); + install_element(OSPF_NODE, &no_ospf_abr_type_cmd); + + /* "ospf log-adjacency-changes" commands. */ + install_element(OSPF_NODE, &ospf_log_adjacency_changes_cmd); + install_element(OSPF_NODE, &ospf_log_adjacency_changes_detail_cmd); + install_element(OSPF_NODE, &no_ospf_log_adjacency_changes_cmd); + install_element(OSPF_NODE, &no_ospf_log_adjacency_changes_detail_cmd); + + /* "ospf rfc1583-compatible" commands. */ + install_element(OSPF_NODE, &ospf_compatible_rfc1583_cmd); + install_element(OSPF_NODE, &no_ospf_compatible_rfc1583_cmd); + install_element(OSPF_NODE, &ospf_rfc1583_flag_cmd); + install_element(OSPF_NODE, &no_ospf_rfc1583_flag_cmd); + + /* "ospf send-extra-data zebra" commands. */ + install_element(OSPF_NODE, &ospf_send_extra_data_cmd); + + /* "network area" commands. */ + install_element(OSPF_NODE, &ospf_network_area_cmd); + install_element(OSPF_NODE, &no_ospf_network_area_cmd); + + /* "area authentication" commands. */ + install_element(OSPF_NODE, + &ospf_area_authentication_message_digest_cmd); + install_element(OSPF_NODE, &ospf_area_authentication_cmd); + install_element(OSPF_NODE, &no_ospf_area_authentication_cmd); + + /* "area range" commands. */ + install_element(OSPF_NODE, &ospf_area_range_cmd); + install_element(OSPF_NODE, &ospf_area_range_cost_cmd); + install_element(OSPF_NODE, &ospf_area_range_not_advertise_cmd); + install_element(OSPF_NODE, &no_ospf_area_range_cmd); + install_element(OSPF_NODE, &no_ospf_area_range_substitute_cmd); + + /* "area virtual-link" commands. */ + install_element(OSPF_NODE, &ospf_area_vlink_cmd); + install_element(OSPF_NODE, &ospf_area_vlink_intervals_cmd); + install_element(OSPF_NODE, &no_ospf_area_vlink_cmd); + install_element(OSPF_NODE, &no_ospf_area_vlink_intervals_cmd); + + + /* "area stub" commands. */ + install_element(OSPF_NODE, &ospf_area_stub_no_summary_cmd); + install_element(OSPF_NODE, &ospf_area_stub_cmd); + install_element(OSPF_NODE, &no_ospf_area_stub_no_summary_cmd); + install_element(OSPF_NODE, &no_ospf_area_stub_cmd); + + /* "area nssa" commands. */ + install_element(OSPF_NODE, &ospf_area_nssa_cmd); + install_element(OSPF_NODE, &no_ospf_area_nssa_cmd); + install_element(OSPF_NODE, &ospf_area_nssa_range_cmd); + install_element(OSPF_NODE, &no_ospf_area_nssa_range_cmd); + + install_element(OSPF_NODE, &ospf_area_default_cost_cmd); + install_element(OSPF_NODE, &no_ospf_area_default_cost_cmd); + + install_element(OSPF_NODE, &ospf_area_shortcut_cmd); + install_element(OSPF_NODE, &no_ospf_area_shortcut_cmd); + + install_element(OSPF_NODE, &ospf_area_export_list_cmd); + install_element(OSPF_NODE, &no_ospf_area_export_list_cmd); + + install_element(OSPF_NODE, &ospf_area_filter_list_cmd); + install_element(OSPF_NODE, &no_ospf_area_filter_list_cmd); + + install_element(OSPF_NODE, &ospf_area_import_list_cmd); + install_element(OSPF_NODE, &no_ospf_area_import_list_cmd); + + /* SPF timer commands */ + install_element(OSPF_NODE, &ospf_timers_throttle_spf_cmd); + install_element(OSPF_NODE, &no_ospf_timers_throttle_spf_cmd); + + /* LSA timers commands */ + install_element(OSPF_NODE, &ospf_timers_min_ls_interval_cmd); + install_element(OSPF_NODE, &no_ospf_timers_min_ls_interval_cmd); + install_element(OSPF_NODE, &ospf_timers_lsa_min_arrival_cmd); + install_element(OSPF_NODE, &no_ospf_timers_lsa_min_arrival_cmd); + + /* refresh timer commands */ + install_element(OSPF_NODE, &ospf_refresh_timer_cmd); + install_element(OSPF_NODE, &no_ospf_refresh_timer_val_cmd); + + /* max-metric commands */ + install_element(OSPF_NODE, &ospf_max_metric_router_lsa_admin_cmd); + install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_admin_cmd); + install_element(OSPF_NODE, &ospf_max_metric_router_lsa_startup_cmd); + install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_startup_cmd); + install_element(OSPF_NODE, &ospf_max_metric_router_lsa_shutdown_cmd); + install_element(OSPF_NODE, &no_ospf_max_metric_router_lsa_shutdown_cmd); + + /* reference bandwidth commands */ + install_element(OSPF_NODE, &ospf_auto_cost_reference_bandwidth_cmd); + install_element(OSPF_NODE, &no_ospf_auto_cost_reference_bandwidth_cmd); + + /* "neighbor" command. */ + install_element(OSPF_NODE, &ospf_neighbor_cmd); + + /* write multiplier commands */ + install_element(OSPF_NODE, &ospf_write_multiplier_cmd); + install_element(OSPF_NODE, &write_multiplier_cmd); + install_element(OSPF_NODE, &no_ospf_write_multiplier_cmd); + install_element(OSPF_NODE, &no_write_multiplier_cmd); + + /* "proactive-arp" commands. */ + install_element(OSPF_NODE, &ospf_proactive_arp_cmd); + install_element(OSPF_NODE, &no_ospf_proactive_arp_cmd); + + /* TI-LFA commands */ + install_element(OSPF_NODE, &ospf_ti_lfa_cmd); + install_element(OSPF_NODE, &no_ospf_ti_lfa_cmd); + + /* Max path configurations */ + install_element(OSPF_NODE, &ospf_max_multipath_cmd); + install_element(OSPF_NODE, &no_ospf_max_multipath_cmd); + + vrf_cmd_init(NULL); + + install_element(OSPF_NODE, &ospf_lsa_refresh_timer_cmd); + install_element(OSPF_NODE, &ospf_maxage_delay_timer_cmd); + + /* Flood Reduction commands */ + install_element(OSPF_NODE, &flood_reduction_cmd); + install_element(OSPF_NODE, &no_flood_reduction_cmd); + install_element(OSPF_NODE, &flood_reduction_area_cmd); + install_element(OSPF_NODE, &no_flood_reduction_area_cmd); + + install_element(OSPF_NODE, &ospf_socket_bufsizes_cmd); + install_element(OSPF_NODE, &per_intf_socket_cmd); + + /* Init interface related vty commands. */ + ospf_vty_if_init(); + + /* Init zebra related vty commands. */ + ospf_vty_zebra_init(); +} diff --git a/ospfd/ospf_vty.h b/ospfd/ospf_vty.h new file mode 100644 index 0000000..e940246 --- /dev/null +++ b/ospfd/ospf_vty.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPF VTY interface. + * Copyright (C) 2000 Toshiaki Takada + */ + +#ifndef _QUAGGA_OSPF_VTY_H +#define _QUAGGA_OSPF_VTY_H + +/* Macros. */ +#define VTY_GET_OSPF_AREA_ID(V, F, STR) \ + { \ + int retv; \ + retv = str2area_id((STR), &(V), &(F)); \ + if (retv < 0) { \ + vty_out(vty, "%% Invalid OSPF area ID\n"); \ + return CMD_WARNING; \ + } \ + } + +#define VTY_GET_OSPF_AREA_ID_NO_BB(NAME, V, F, STR) \ + { \ + int retv; \ + retv = str2area_id((STR), &(V), &(F)); \ + if (retv < 0) { \ + vty_out(vty, "%% Invalid OSPF area ID\n"); \ + return CMD_WARNING; \ + } \ + if (OSPF_IS_AREA_ID_BACKBONE((V))) { \ + vty_out(vty, \ + "%% You can't configure %s to backbone\n", \ + NAME); \ + return CMD_WARNING; \ + } \ + } + +/* Prototypes. */ +extern void ospf_vty_init(void); +extern void ospf_vty_show_init(void); +extern void ospf_vty_clear_init(void); +extern int str2area_id(const char *, struct in_addr *, int *); + +/* unit tests */ +void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self, + json_object *json); + +#endif /* _QUAGGA_OSPF_VTY_H */ diff --git a/ospfd/ospf_zebra.c b/ospfd/ospf_zebra.c new file mode 100644 index 0000000..2c518f2 --- /dev/null +++ b/ospfd/ospf_zebra.c @@ -0,0 +1,2226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for OSPFd + * Copyright (C) 1997, 98, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "routemap.h" +#include "table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "filter.h" +#include "plist.h" +#include "log.h" +#include "route_opaque.h" +#include "lib/bfd.h" +#include "lib/lib_errors.h" +#include "nexthop.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_te.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ldp_sync.h" + +DEFINE_MTYPE_STATIC(OSPFD, OSPF_EXTERNAL, "OSPF External route table"); +DEFINE_MTYPE_STATIC(OSPFD, OSPF_REDISTRIBUTE, "OSPF Redistriute"); + + +/* Zebra structure to hold current status. */ +struct zclient *zclient = NULL; +/* and for the Synchronous connection to the Label Manager */ +struct zclient *zclient_sync; + +/* For registering threads. */ +extern struct event_loop *master; + +/* Router-id update message from zebra. */ +static int ospf_router_id_update_zebra(ZAPI_CALLBACK_ARGS) +{ + struct ospf *ospf = NULL; + struct prefix router_id; + zebra_router_id_update_read(zclient->ibuf, &router_id); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra rcvd: router id update %pFX vrf %s id %u", + &router_id, ospf_vrf_id_to_name(vrf_id), vrf_id); + + ospf = ospf_lookup_by_vrf_id(vrf_id); + + if (ospf != NULL) { + ospf->router_id_zebra = router_id.u.prefix4; + ospf_router_id_update(ospf); + } else { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ospf instance not found for vrf %s id %u router_id %pFX", + __func__, ospf_vrf_id_to_name(vrf_id), vrf_id, + &router_id); + } + return 0; +} + +static int ospf_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct ospf *ospf = NULL; + + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: interface %s address add %pFX vrf %s id %u", + c->ifp->name, c->address, + ospf_vrf_id_to_name(vrf_id), vrf_id); + + ospf = ospf_lookup_by_vrf_id(vrf_id); + if (!ospf) + return 0; + + ospf_if_update(ospf, c->ifp); + + ospf_if_interface(c->ifp); + + return 0; +} + +static int ospf_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct interface *ifp; + struct ospf_interface *oi; + struct route_node *rn; + struct prefix p; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_INTERFACE)) + zlog_debug("Zebra: interface %s address delete %pFX", + c->ifp->name, c->address); + + ifp = c->ifp; + p = *c->address; + p.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_lookup(IF_OIFS(ifp), &p); + if (!rn) { + connected_free(&c); + return 0; + } + + assert(rn->info); + oi = rn->info; + route_unlock_node(rn); + + /* Call interface hook functions to clean up */ + ospf_if_free(oi); + + ospf_if_interface(c->ifp); + + connected_free(&c); + + return 0; +} + +static int ospf_interface_link_params(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + bool changed = false; + + ifp = zebra_interface_link_params_read(zclient->ibuf, vrf_id, &changed); + + if (ifp == NULL || !changed) + return 0; + + /* Update TE TLV */ + ospf_mpls_te_update_if(ifp); + + return 0; +} + +/* Nexthop, ifindex, distance and metric information. */ +static void ospf_zebra_add_nexthop(struct ospf *ospf, struct ospf_path *path, + struct zapi_route *api) +{ + struct zapi_nexthop *api_nh; + struct zapi_nexthop *api_nh_backup; + + /* TI-LFA backup path label stack comes first, if present */ + if (path->srni.backup_label_stack) { + api_nh_backup = &api->backup_nexthops[api->backup_nexthop_num]; + api_nh_backup->vrf_id = ospf->vrf_id; + + api_nh_backup->type = NEXTHOP_TYPE_IPV4; + api_nh_backup->gate.ipv4 = path->srni.backup_nexthop; + + api_nh_backup->label_num = + path->srni.backup_label_stack->num_labels; + memcpy(api_nh_backup->labels, + path->srni.backup_label_stack->label, + sizeof(mpls_label_t) * api_nh_backup->label_num); + + api->backup_nexthop_num++; + } + + /* And here comes the primary nexthop */ + api_nh = &api->nexthops[api->nexthop_num]; +#ifdef HAVE_NETLINK + if (path->unnumbered + || (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0)) { +#else /* HAVE_NETLINK */ + if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) { +#endif /* HAVE_NETLINK */ + api_nh->gate.ipv4 = path->nexthop; + api_nh->ifindex = path->ifindex; + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + } else if (path->nexthop.s_addr != INADDR_ANY) { + api_nh->gate.ipv4 = path->nexthop; + api_nh->type = NEXTHOP_TYPE_IPV4; + } else { + api_nh->ifindex = path->ifindex; + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + api_nh->vrf_id = ospf->vrf_id; + + /* Set TI-LFA backup nexthop info if present */ + if (path->srni.backup_label_stack) { + SET_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS); + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + + /* Just care about a single TI-LFA backup path for now */ + api_nh->backup_num = 1; + api_nh->backup_idx[0] = api->backup_nexthop_num - 1; + } + + api->nexthop_num++; +} + +static void ospf_zebra_append_opaque_attr(struct ospf_route *or, + struct zapi_route *api) +{ + struct ospf_zebra_opaque ospf_opaque = {}; + + /* OSPF path type */ + snprintf(ospf_opaque.path_type, sizeof(ospf_opaque.path_type), "%s", + ospf_path_type_name(or->path_type)); + + switch (or->path_type) { + case OSPF_PATH_INTRA_AREA: + case OSPF_PATH_INTER_AREA: + /* OSPF area ID */ + (void)inet_ntop(AF_INET, &or->u.std.area_id, + ospf_opaque.area_id, + sizeof(ospf_opaque.area_id)); + break; + case OSPF_PATH_TYPE1_EXTERNAL: + case OSPF_PATH_TYPE2_EXTERNAL: + /* OSPF route tag */ + snprintf(ospf_opaque.tag, sizeof(ospf_opaque.tag), "%u", + or->u.ext.tag); + break; + default: + break; + } + + SET_FLAG(api->message, ZAPI_MESSAGE_OPAQUE); + api->opaque.length = sizeof(struct ospf_zebra_opaque); + memcpy(api->opaque.data, &ospf_opaque, api->opaque.length); +} + +void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + struct zapi_route api; + uint8_t distance; + struct ospf_path *path; + struct listnode *node; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + p); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf->vrf_id; + api.type = ZEBRA_ROUTE_OSPF; + api.instance = ospf->instance; + api.safi = SAFI_UNICAST; + + memcpy(&api.prefix, p, sizeof(*p)); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + + /* Metric value. */ + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + if (or->path_type == OSPF_PATH_TYPE1_EXTERNAL) + api.metric = or->cost + or->u.ext.type2_cost; + else if (or->path_type == OSPF_PATH_TYPE2_EXTERNAL) + api.metric = or->u.ext.type2_cost; + else + api.metric = or->cost; + + /* Check if path type is ASE */ + if (((or->path_type == OSPF_PATH_TYPE1_EXTERNAL) + || (or->path_type == OSPF_PATH_TYPE2_EXTERNAL)) + && (or->u.ext.tag > 0) && (or->u.ext.tag <= ROUTE_TAG_MAX)) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = or->u.ext.tag; + } + + /* Distance value. */ + distance = ospf_distance_apply(ospf, p, or); + if (distance) { + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + api.distance = distance; + } + + for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) { + if (api.nexthop_num >= ospf->max_multipath) + break; + + ospf_zebra_add_nexthop(ospf, path, &api); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { + struct interface *ifp; + + ifp = if_lookup_by_index(path->ifindex, ospf->vrf_id); + + zlog_debug( + "Zebra: Route add %pFX nexthop %pI4, ifindex=%d %s", + p, &path->nexthop, path->ifindex, + ifp ? ifp->name : " "); + } + } + + if (CHECK_FLAG(ospf->config, OSPF_SEND_EXTRA_DATA_TO_ZEBRA)) + ospf_zebra_append_opaque_attr(or, &api); + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); +} + +void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + struct zapi_route api; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + p); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf->vrf_id; + api.type = ZEBRA_ROUTE_OSPF; + api.instance = ospf->instance; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, p, sizeof(*p)); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Zebra: Route delete %pFX", p); + + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); +} + +void ospf_zebra_add_discard(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct zapi_route api; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not installing %pFX", + p); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf->vrf_id; + api.type = ZEBRA_ROUTE_OSPF; + api.instance = ospf->instance; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, p, sizeof(*p)); + zapi_route_set_blackhole(&api, BLACKHOLE_NULL); + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Zebra: Route add discard %pFX", p); +} + +void ospf_zebra_delete_discard(struct ospf *ospf, struct prefix_ipv4 *p) +{ + struct zapi_route api; + + if (ospf->gr_info.restart_in_progress) { + if (IS_DEBUG_OSPF_GR) + zlog_debug( + "Zebra: Graceful Restart in progress -- not uninstalling %pFX", + p); + return; + } + + memset(&api, 0, sizeof(api)); + api.vrf_id = ospf->vrf_id; + api.type = ZEBRA_ROUTE_OSPF; + api.instance = ospf->instance; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, p, sizeof(*p)); + zapi_route_set_blackhole(&api, BLACKHOLE_NULL); + + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Zebra: Route delete discard %pFX", p); +} + +struct ospf_external *ospf_external_lookup(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct list *ext_list; + struct listnode *node; + struct ospf_external *ext; + + ext_list = ospf->external[type]; + if (!ext_list) + return (NULL); + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) + if (ext->instance == instance) + return ext; + + return NULL; +} + +struct ospf_external *ospf_external_add(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct list *ext_list; + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + if (ext) + return ext; + + if (!ospf->external[type]) + ospf->external[type] = list_new(); + + ext_list = ospf->external[type]; + ext = XCALLOC(MTYPE_OSPF_EXTERNAL, sizeof(struct ospf_external)); + ext->instance = instance; + EXTERNAL_INFO(ext) = route_table_init(); + + listnode_add(ext_list, ext); + + return ext; +} + +/* + * Walk all the ei received from zebra for a route type and apply + * default route-map. + */ +bool ospf_external_default_routemap_apply_walk(struct ospf *ospf, + struct list *ext_list, + struct external_info *default_ei) +{ + struct listnode *node; + struct ospf_external *ext; + struct route_node *rn; + struct external_info *ei = NULL; + int ret = 0; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + if (!ext->external_info) + continue; + + for (rn = route_top(ext->external_info); rn; + rn = route_next(rn)) { + ei = rn->info; + if (!ei) + continue; + ret = ospf_external_info_apply_default_routemap( + ospf, ei, default_ei); + if (ret) + break; + } + } + + if (ret && ei) { + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Default originate routemap permit ei: %pI4", + &ei->p.prefix); + return true; + } + + return false; +} + +/* + * Function to originate or flush default after applying + * route-map on all ei. + */ +static void ospf_external_lsa_default_routemap_timer(struct event *thread) +{ + struct list *ext_list; + struct ospf *ospf = EVENT_ARG(thread); + struct prefix_ipv4 p; + int type; + int ret = 0; + struct ospf_lsa *lsa; + struct external_info *default_ei; + + p.family = AF_INET; + p.prefixlen = 0; + p.prefix.s_addr = INADDR_ANY; + + /* Get the default extenal info. */ + default_ei = ospf_external_info_lookup(ospf, DEFAULT_ROUTE, + ospf->instance, &p); + if (!default_ei) { + /* Nothing to be done here. */ + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Default originate info not present"); + return; + } + + /* For all the ei apply route-map */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + ext_list = ospf->external[type]; + if (!ext_list || type == ZEBRA_ROUTE_OSPF) + continue; + + ret = ospf_external_default_routemap_apply_walk(ospf, ext_list, + default_ei); + if (ret) + break; + } + + /* Get the default LSA. */ + lsa = ospf_external_info_find_lsa(ospf, &p); + + /* If permit then originate default. */ + if (ret && !lsa) + ospf_external_lsa_originate(ospf, default_ei); + else if (ret && lsa && IS_LSA_MAXAGE(lsa)) + ospf_external_lsa_refresh(ospf, lsa, default_ei, true, false); + else if (!ret && lsa) + ospf_external_lsa_flush(ospf, DEFAULT_ROUTE, &default_ei->p, 0); +} + + +void ospf_external_del(struct ospf *ospf, uint8_t type, unsigned short instance) +{ + struct ospf_external *ext; + + ext = ospf_external_lookup(ospf, type, instance); + + if (ext) { + if (EXTERNAL_INFO(ext)) + route_table_finish(EXTERNAL_INFO(ext)); + + listnode_delete(ospf->external[type], ext); + + if (!ospf->external[type]->count) + list_delete(&ospf->external[type]); + + XFREE(MTYPE_OSPF_EXTERNAL, ext); + } + + /* + * Check if default needs to be flushed too. + */ + event_add_event(master, ospf_external_lsa_default_routemap_timer, ospf, + 0, &ospf->t_default_routemap_timer); +} + +/* Update NHLFE for Prefix SID */ +void ospf_zebra_update_prefix_sid(const struct sr_prefix *srp) +{ + struct zapi_labels zl; + struct zapi_nexthop *znh; + struct zapi_nexthop *znh_backup; + struct listnode *node; + struct ospf_path *path; + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_OSPF_SR; + zl.local_label = srp->label_in; + + switch (srp->type) { + case LOCAL_SID: + /* Set Label for local Prefix */ + znh = &zl.nexthops[zl.nexthop_num++]; + znh->type = NEXTHOP_TYPE_IFINDEX; + znh->ifindex = srp->nhlfe.ifindex; + znh->label_num = 1; + znh->labels[0] = srp->nhlfe.label_out; + + osr_debug("SR (%s): Configure Prefix %pFX with labels %u/%u", + __func__, (struct prefix *)&srp->prefv4, + srp->label_in, srp->nhlfe.label_out); + + break; + + case PREF_SID: + /* Update route in the RIB too. */ + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + zl.route.prefix.u.prefix4 = srp->prefv4.prefix; + zl.route.prefix.prefixlen = srp->prefv4.prefixlen; + zl.route.prefix.family = srp->prefv4.family; + zl.route.type = ZEBRA_ROUTE_OSPF; + zl.route.instance = 0; + + /* Check that SRP contains at least one valid path */ + if (srp->route == NULL) { + return; + } + + osr_debug("SR (%s): Configure Prefix %pFX with", + __func__, (struct prefix *)&srp->prefv4); + + for (ALL_LIST_ELEMENTS_RO(srp->route->paths, node, path)) { + if (path->srni.label_out == MPLS_INVALID_LABEL) + continue; + + if (zl.nexthop_num >= MULTIPATH_NUM) + break; + + /* + * TI-LFA backup path label stack comes first, if + * present. + */ + if (path->srni.backup_label_stack) { + znh_backup = &zl.backup_nexthops + [zl.backup_nexthop_num++]; + znh_backup->type = NEXTHOP_TYPE_IPV4; + znh_backup->gate.ipv4 = + path->srni.backup_nexthop; + + memcpy(znh_backup->labels, + path->srni.backup_label_stack->label, + sizeof(mpls_label_t) + * path->srni.backup_label_stack + ->num_labels); + + znh_backup->label_num = + path->srni.backup_label_stack + ->num_labels; + if (path->srni.label_out + != MPLS_LABEL_IPV4_EXPLICIT_NULL + && path->srni.label_out + != MPLS_LABEL_IMPLICIT_NULL) + znh_backup->labels + [znh_backup->label_num++] = + path->srni.label_out; + } + + znh = &zl.nexthops[zl.nexthop_num++]; + znh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + znh->gate.ipv4 = path->nexthop; + znh->ifindex = path->ifindex; + znh->label_num = 1; + znh->labels[0] = path->srni.label_out; + + osr_debug(" |- labels %u/%u", srp->label_in, + path->srni.label_out); + + /* Set TI-LFA backup nexthop info if present */ + if (path->srni.backup_label_stack) { + SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS); + SET_FLAG(znh->flags, + ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + + /* Just care about a single TI-LFA backup path + * for now */ + znh->backup_num = 1; + znh->backup_idx[0] = zl.backup_nexthop_num - 1; + } + } + break; + case ADJ_SID: + case LAN_ADJ_SID: + return; + } + + /* Finally, send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_REPLACE, &zl); +} + +/* Remove NHLFE for Prefix-SID */ +void ospf_zebra_delete_prefix_sid(const struct sr_prefix *srp) +{ + struct zapi_labels zl; + + osr_debug("SR (%s): Delete Labels %u for Prefix %pFX", __func__, + srp->label_in, (struct prefix *)&srp->prefv4); + + /* Prepare message. */ + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_OSPF_SR; + zl.local_label = srp->label_in; + + if (srp->type == PREF_SID) { + /* Update route in the RIB too */ + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + zl.route.prefix.u.prefix4 = srp->prefv4.prefix; + zl.route.prefix.prefixlen = srp->prefv4.prefixlen; + zl.route.prefix.family = srp->prefv4.family; + zl.route.type = ZEBRA_ROUTE_OSPF; + zl.route.instance = 0; + } + + /* Send message to zebra. */ + (void)zebra_send_mpls_labels(zclient, ZEBRA_MPLS_LABELS_DELETE, &zl); +} + +/* Send MPLS Label entry to Zebra for installation or deletion */ +void ospf_zebra_send_adjacency_sid(int cmd, struct sr_nhlfe nhlfe) +{ + struct zapi_labels zl; + struct zapi_nexthop *znh; + + osr_debug("SR (%s): %s Labels %u/%u for Adjacency via %u", __func__, + cmd == ZEBRA_MPLS_LABELS_ADD ? "Add" : "Delete", + nhlfe.label_in, nhlfe.label_out, nhlfe.ifindex); + + memset(&zl, 0, sizeof(zl)); + zl.type = ZEBRA_LSP_OSPF_SR; + zl.local_label = nhlfe.label_in; + zl.nexthop_num = 1; + znh = &zl.nexthops[0]; + znh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + znh->gate.ipv4 = nhlfe.nexthop; + znh->ifindex = nhlfe.ifindex; + znh->label_num = 1; + znh->labels[0] = nhlfe.label_out; + + (void)zebra_send_mpls_labels(zclient, cmd, &zl); +} + +struct ospf_redist *ospf_redist_lookup(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct list *red_list; + struct listnode *node; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + return (NULL); + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) + if (red->instance == instance) + return red; + + return NULL; +} + +struct ospf_redist *ospf_redist_add(struct ospf *ospf, uint8_t type, + unsigned short instance) +{ + struct list *red_list; + struct ospf_redist *red; + + red = ospf_redist_lookup(ospf, type, instance); + if (red) + return red; + + if (!ospf->redist[type]) + ospf->redist[type] = list_new(); + + red_list = ospf->redist[type]; + red = XCALLOC(MTYPE_OSPF_REDISTRIBUTE, sizeof(struct ospf_redist)); + red->instance = instance; + red->dmetric.type = -1; + red->dmetric.value = -1; + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; + + listnode_add(red_list, red); + + return red; +} + +void ospf_redist_del(struct ospf *ospf, uint8_t type, unsigned short instance) +{ + struct ospf_redist *red; + + red = ospf_redist_lookup(ospf, type, instance); + + if (red) { + listnode_delete(ospf->redist[type], red); + if (!ospf->redist[type]->count) { + list_delete(&ospf->redist[type]); + } + ospf_routemap_unset(red); + XFREE(MTYPE_OSPF_REDISTRIBUTE, red); + } +} + + +int ospf_is_type_redistributed(struct ospf *ospf, int type, + unsigned short instance) +{ + return (DEFAULT_ROUTE_TYPE(type) + ? vrf_bitmap_check( + &zclient->default_information[AFI_IP], + ospf->vrf_id) + : ((instance && + redist_check_instance( + &zclient->mi_redist[AFI_IP][type], + instance)) || + (!instance && + vrf_bitmap_check(&zclient->redist[AFI_IP][type], + ospf->vrf_id)))); +} + +int ospf_redistribute_update(struct ospf *ospf, struct ospf_redist *red, + int type, unsigned short instance, int mtype, + int mvalue) +{ + int force = 0; + + if (mtype != red->dmetric.type) { + red->dmetric.type = mtype; + force = LSA_REFRESH_FORCE; + } + if (mvalue != red->dmetric.value) { + red->dmetric.value = mvalue; + force = LSA_REFRESH_FORCE; + } + + ospf_external_lsa_refresh_type(ospf, type, instance, force); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "Redistribute[%s][%d]: Refresh Type[%d], Metric[%d]", + ospf_redist_string(type), instance, + metric_type(ospf, type, instance), + metric_value(ospf, type, instance)); + + return CMD_SUCCESS; +} + +int ospf_redistribute_set(struct ospf *ospf, struct ospf_redist *red, int type, + unsigned short instance, int mtype, int mvalue) +{ + red->dmetric.type = mtype; + red->dmetric.value = mvalue; + + ospf_external_add(ospf, type, instance); + + zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, type, + instance, ospf->vrf_id); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "Redistribute[%s][%d] vrf id %u: Start Type[%d], Metric[%d]", + ospf_redist_string(type), instance, ospf->vrf_id, + metric_type(ospf, type, instance), + metric_value(ospf, type, instance)); + + ospf_asbr_status_update(ospf, ++ospf->redistribute); + + return CMD_SUCCESS; +} + +int ospf_redistribute_unset(struct ospf *ospf, int type, + unsigned short instance) +{ + if (type == zclient->redist_default && instance == zclient->instance) + return CMD_SUCCESS; + + zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, AFI_IP, type, + instance, ospf->vrf_id); + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Redistribute[%s][%d] vrf id %u: Stop", + ospf_redist_string(type), instance, ospf->vrf_id); + + /* Remove the routes from OSPF table. */ + ospf_redistribute_withdraw(ospf, type, instance); + + ospf_external_del(ospf, type, instance); + + ospf_asbr_status_update(ospf, --ospf->redistribute); + + return CMD_SUCCESS; +} + +int ospf_redistribute_default_set(struct ospf *ospf, int originate, int mtype, + int mvalue) +{ + struct prefix_ipv4 p; + struct in_addr nexthop; + int cur_originate = ospf->default_originate; + const char *type_str = NULL; + + nexthop.s_addr = INADDR_ANY; + p.family = AF_INET; + p.prefix.s_addr = INADDR_ANY; + p.prefixlen = 0; + + ospf->default_originate = originate; + + if (cur_originate == originate) { + /* Refresh the lsa since metric might different */ + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "Redistribute[%s]: Refresh Type[%d], Metric[%d]", + ospf_redist_string(DEFAULT_ROUTE), + metric_type(ospf, DEFAULT_ROUTE, 0), + metric_value(ospf, DEFAULT_ROUTE, 0)); + + ospf_external_lsa_refresh_default(ospf); + return CMD_SUCCESS; + } + + switch (cur_originate) { + case DEFAULT_ORIGINATE_NONE: + break; + case DEFAULT_ORIGINATE_ZEBRA: + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, + zclient, AFI_IP, ospf->vrf_id); + ospf->redistribute--; + break; + case DEFAULT_ORIGINATE_ALWAYS: + ospf_external_info_delete(ospf, DEFAULT_ROUTE, 0, p); + ospf_external_del(ospf, DEFAULT_ROUTE, 0); + ospf->redistribute--; + break; + } + + switch (originate) { + case DEFAULT_ORIGINATE_NONE: + type_str = "none"; + break; + case DEFAULT_ORIGINATE_ZEBRA: + type_str = "normal"; + ospf->redistribute++; + zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + zclient, AFI_IP, ospf->vrf_id); + break; + case DEFAULT_ORIGINATE_ALWAYS: + type_str = "always"; + ospf->redistribute++; + ospf_external_add(ospf, DEFAULT_ROUTE, 0); + ospf_external_info_add(ospf, DEFAULT_ROUTE, 0, p, 0, nexthop, 0, + DEFAULT_DEFAULT_METRIC); + break; + } + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug("Redistribute[DEFAULT]: %s Type[%d], Metric[%d]", + type_str, + metric_type(ospf, DEFAULT_ROUTE, 0), + metric_value(ospf, DEFAULT_ROUTE, 0)); + + ospf_external_lsa_refresh_default(ospf); + ospf_asbr_status_update(ospf, ospf->redistribute); + return CMD_SUCCESS; +} + +static int ospf_external_lsa_originate_check(struct ospf *ospf, + struct external_info *ei) +{ + /* If prefix is multicast, then do not originate LSA. */ + if (IN_MULTICAST(htonl(ei->p.prefix.s_addr))) { + zlog_info( + "LSA[Type5:%pI4]: Not originate AS-external-LSA, Prefix belongs multicast", + &ei->p.prefix); + return 0; + } + + /* Take care of default-originate. */ + if (is_default_prefix4(&ei->p)) + if (ospf->default_originate == DEFAULT_ORIGINATE_NONE) { + zlog_info( + "LSA[Type5:0.0.0.0]: Not originate AS-external-LSA for default"); + return 0; + } + + return 1; +} + +/* If connected prefix is OSPF enable interface, then do not announce. */ +int ospf_distribute_check_connected(struct ospf *ospf, struct external_info *ei) +{ + struct listnode *node; + struct ospf_interface *oi; + + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) + if (prefix_match(oi->address, (struct prefix *)&ei->p)) + return 0; + return 1; +} + + +/* Apply default route-map on ei received. */ +int ospf_external_info_apply_default_routemap(struct ospf *ospf, + struct external_info *ei, + struct external_info *default_ei) +{ + struct ospf_redist *red; + int type = default_ei->type; + struct prefix_ipv4 *p = &ei->p; + struct route_map_set_values save_values; + + + if (!ospf_external_lsa_originate_check(ospf, default_ei)) + return 0; + + save_values = default_ei->route_map_set; + ospf_reset_route_map_set_values(&default_ei->route_map_set); + + /* apply route-map if needed */ + red = ospf_redist_lookup(ospf, type, ospf->instance); + if (red && ROUTEMAP_NAME(red)) { + route_map_result_t ret; + + ret = route_map_apply(ROUTEMAP(red), (struct prefix *)p, ei); + + if (ret == RMAP_DENYMATCH) { + ei->route_map_set = save_values; + return 0; + } + } + + return 1; +} + + +/* + * Default originated is based on route-map condition then + * apply route-map on received external info. Originate or + * flush based on route-map condition. + */ +static bool ospf_external_lsa_default_routemap_apply(struct ospf *ospf, + struct external_info *ei, + int cmd) +{ + struct external_info *default_ei; + struct prefix_ipv4 p; + struct ospf_lsa *lsa; + int ret; + + p.family = AF_INET; + p.prefixlen = 0; + p.prefix.s_addr = INADDR_ANY; + + + /* Get the default extenal info. */ + default_ei = ospf_external_info_lookup(ospf, DEFAULT_ROUTE, + ospf->instance, &p); + if (!default_ei) { + /* Nothing to be done here. */ + return false; + } + + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Apply default originate routemap on ei: %pI4 cmd: %d", + &ei->p.prefix, cmd); + + ret = ospf_external_info_apply_default_routemap(ospf, ei, default_ei); + + /* If deny then nothing to be done both in add and del case. */ + if (!ret) { + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Default originte routemap deny for ei: %pI4", + &ei->p.prefix); + return false; + } + + /* Get the default LSA. */ + lsa = ospf_external_info_find_lsa(ospf, &p); + + /* If this is add route and permit then ooriginate default. */ + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { + /* If permit and default already advertise then return. */ + if (lsa && !IS_LSA_MAXAGE(lsa)) { + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Default lsa already originated"); + return true; + } + + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug("Originating/Refreshing default lsa"); + + if (lsa && IS_LSA_MAXAGE(lsa)) + /* Refresh lsa.*/ + ospf_external_lsa_refresh(ospf, lsa, default_ei, true, + false); + else + /* If permit and default not advertised then advertise. + */ + ospf_external_lsa_originate(ospf, default_ei); + + } else if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) { + /* If deny and lsa is not originated then nothing to be done.*/ + if (!lsa) { + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug( + "Default lsa not originated, not flushing"); + return true; + } + + if (IS_DEBUG_OSPF_DEFAULT_INFO) + zlog_debug( + "Running default route-map again as ei: %pI4 deleted", + &ei->p.prefix); + /* + * if this route delete was permitted then we need to check + * there are any other external info which can still trigger + * default route origination else flush it. + */ + event_add_event(master, + ospf_external_lsa_default_routemap_timer, ospf, + 0, &ospf->t_default_routemap_timer); + } + + return true; +} + +/* return 1 if external LSA must be originated, 0 otherwise */ +int ospf_redistribute_check(struct ospf *ospf, struct external_info *ei, + int *changed) +{ + struct route_map_set_values save_values; + struct prefix_ipv4 *p = &ei->p; + struct ospf_redist *red; + uint8_t type = is_default_prefix4(&ei->p) ? DEFAULT_ROUTE : ei->type; + unsigned short instance = is_default_prefix4(&ei->p) ? 0 : ei->instance; + route_tag_t saved_tag = 0; + + /* Default is handled differently. */ + if (type == DEFAULT_ROUTE) + return 1; + + if (changed) + *changed = 0; + + if (!ospf_external_lsa_originate_check(ospf, ei)) + return 0; + + /* Take care connected route. */ + if (type == ZEBRA_ROUTE_CONNECT + && !ospf_distribute_check_connected(ospf, ei)) + return 0; + + if (!DEFAULT_ROUTE_TYPE(type) && DISTRIBUTE_NAME(ospf, type)) + /* distirbute-list exists, but access-list may not? */ + if (DISTRIBUTE_LIST(ospf, type)) + if (access_list_apply(DISTRIBUTE_LIST(ospf, type), p) + == FILTER_DENY) { + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "Redistribute[%s]: %pFX filtered by distribute-list.", + ospf_redist_string(type), p); + return 0; + } + + save_values = ei->route_map_set; + ospf_reset_route_map_set_values(&ei->route_map_set); + + saved_tag = ei->tag; + /* Resetting with original route tag */ + ei->tag = ei->orig_tag; + + /* apply route-map if needed */ + red = ospf_redist_lookup(ospf, type, instance); + if (red && ROUTEMAP_NAME(red)) { + route_map_result_t ret; + + ret = route_map_apply(ROUTEMAP(red), (struct prefix *)p, ei); + + if (ret == RMAP_DENYMATCH) { + ei->route_map_set = save_values; + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "Redistribute[%s]: %pFX filtered by route-map.", + ospf_redist_string(type), p); + return 0; + } + + /* check if 'route-map set' changed something */ + if (changed) { + *changed = !ospf_route_map_set_compare( + &ei->route_map_set, &save_values); + + /* check if tag is modified */ + *changed |= (saved_tag != ei->tag); + } + } + + return 1; +} + +/* OSPF route-map set for redistribution */ +void ospf_routemap_set(struct ospf_redist *red, const char *name) +{ + if (ROUTEMAP_NAME(red)) { + route_map_counter_decrement(ROUTEMAP(red)); + free(ROUTEMAP_NAME(red)); + } + + ROUTEMAP_NAME(red) = strdup(name); + ROUTEMAP(red) = route_map_lookup_by_name(name); + route_map_counter_increment(ROUTEMAP(red)); +} + +void ospf_routemap_unset(struct ospf_redist *red) +{ + if (ROUTEMAP_NAME(red)) { + route_map_counter_decrement(ROUTEMAP(red)); + free(ROUTEMAP_NAME(red)); + } + + ROUTEMAP_NAME(red) = NULL; + ROUTEMAP(red) = NULL; +} + +static int ospf_zebra_gr_update(struct ospf *ospf, int command, + uint32_t stale_time) +{ + struct zapi_cap api; + + if (!zclient || zclient->sock < 0 || !ospf) + return 1; + + memset(&api, 0, sizeof(api)); + api.cap = command; + api.stale_removal_time = stale_time; + api.vrf_id = ospf->vrf_id; + + (void)zclient_capabilities_send(ZEBRA_CLIENT_CAPABILITIES, zclient, + &api); + + return 0; +} + +int ospf_zebra_gr_enable(struct ospf *ospf, uint32_t stale_time) +{ + if (IS_DEBUG_OSPF_GR) + zlog_debug("Zebra enable GR [stale time %u]", stale_time); + + return ospf_zebra_gr_update(ospf, ZEBRA_CLIENT_GR_CAPABILITIES, + stale_time); +} + +int ospf_zebra_gr_disable(struct ospf *ospf) +{ + if (IS_DEBUG_OSPF_GR) + zlog_debug("Zebra disable GR"); + + return ospf_zebra_gr_update(ospf, ZEBRA_CLIENT_GR_DISABLE, 0); +} + +/* Zebra route add and delete treatment. */ +static int ospf_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + struct prefix_ipv4 p; + struct prefix pgen; + unsigned long ifindex; + struct in_addr nexthop; + struct external_info *ei; + struct ospf *ospf; + int i; + uint8_t rt_type; + + ospf = ospf_lookup_by_vrf_id(vrf_id); + if (ospf == NULL) + return 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + ifindex = api.nexthops[0].ifindex; + nexthop = api.nexthops[0].gate.ipv4; + rt_type = api.type; + + memcpy(&p, &api.prefix, sizeof(p)); + if (IPV4_NET127(ntohl(p.prefix.s_addr))) + return 0; + + pgen.family = p.family; + pgen.prefixlen = p.prefixlen; + pgen.u.prefix4 = p.prefix; + + /* Re-destributed route is default route. + * Here, route type is used as 'ZEBRA_ROUTE_KERNEL' for + * updating ex-info. But in resetting (no default-info + * originate)ZEBRA_ROUTE_MAX is used to delete the ex-info. + * Resolved this inconsistency by maintaining same route type. + */ + if ((is_default_prefix(&pgen)) && (api.type != ZEBRA_ROUTE_OSPF)) + rt_type = DEFAULT_ROUTE; + + if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) + zlog_debug( + "%s: cmd %s from client %s: vrf_id %d, p %pFX, metric %d", + __func__, zserv_command_string(cmd), + zebra_route_string(api.type), vrf_id, &api.prefix, + api.metric); + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) { + /* XXX|HACK|TODO|FIXME: + * Maybe we should ignore reject/blackhole routes? Testing + * shows that there is no problems though and this is only way + * to "summarize" routes in ASBR at the moment. Maybe we need + * just a better generalised solution for these types? + */ + + /* Protocol tag overwrites all other tag value sent by zebra */ + if (ospf->dtag[rt_type] > 0) + api.tag = ospf->dtag[rt_type]; + + /* + * Given zebra sends update for a prefix via ADD message, it + * should + * be considered as an implicit DEL for that prefix with other + * source + * types. + */ + for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) + if (i != rt_type) + ospf_external_info_delete(ospf, i, api.instance, + p); + + ei = ospf_external_info_add(ospf, rt_type, api.instance, p, + ifindex, nexthop, api.tag, + api.metric); + if (ei == NULL) { + /* Nothing has changed, so nothing to do; return */ + return 0; + } + if (ospf->router_id.s_addr != INADDR_ANY) { + if (is_default_prefix4(&p)) + ospf_external_lsa_refresh_default(ospf); + else { + struct ospf_external_aggr_rt *aggr; + struct as_external_lsa *al; + struct ospf_lsa *lsa = NULL; + struct in_addr mask; + + aggr = ospf_external_aggr_match(ospf, &ei->p); + + if (aggr) { + /* Check the AS-external-LSA + * should be originated. + */ + if (!ospf_redistribute_check(ospf, ei, + NULL)) + return 0; + + if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Send Aggreate LSA (%pI4/%d)", + __func__, + &aggr->p.prefix, + aggr->p.prefixlen); + + ospf_originate_summary_lsa(ospf, aggr, + ei); + + /* Handling the case where the + * external route prefix + * and aggegate prefix is same + * If same don't flush the + * originated + * external LSA. + */ + if (prefix_same( + (struct prefix *)&aggr->p, + (struct prefix *)&ei->p)) + return 0; + + lsa = ospf_external_info_find_lsa( + ospf, &ei->p); + + if (lsa) { + al = (struct as_external_lsa *) + lsa->data; + masklen2ip(ei->p.prefixlen, + &mask); + + if (mask.s_addr + != al->mask.s_addr) + return 0; + + ospf_external_lsa_flush( + ospf, ei->type, &ei->p, + 0); + } + } else { + struct ospf_lsa *current; + + current = ospf_external_info_find_lsa( + ospf, &ei->p); + if (!current) { + /* Check the + * AS-external-LSA + * should be + * originated. + */ + if (!ospf_redistribute_check( + ospf, ei, NULL)) + return 0; + + ospf_external_lsa_originate( + ospf, ei); + } else { + if (IS_DEBUG_OSPF( + zebra, + ZEBRA_REDISTRIBUTE)) + zlog_debug( + "%s: %pI4 refreshing LSA", + __func__, + &p.prefix); + ospf_external_lsa_refresh( + ospf, current, ei, + LSA_REFRESH_FORCE, + false); + } + } + } + } + + /* + * Check if default-information originate is + * with some routemap prefix/access list match. + */ + ospf_external_lsa_default_routemap_apply(ospf, ei, cmd); + + } else { /* if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) */ + struct ospf_external_aggr_rt *aggr; + + ei = ospf_external_info_lookup(ospf, rt_type, api.instance, &p); + if (ei == NULL) + return 0; + + /* + * Check if default-information originate i + * with some routemap prefix/access list match. + * Apply before ei is deleted. + */ + ospf_external_lsa_default_routemap_apply(ospf, ei, cmd); + + aggr = ospf_external_aggr_match(ospf, &ei->p); + + if (aggr && (ei->aggr_route == aggr)) { + ospf_unlink_ei_from_aggr(ospf, aggr, ei); + + ospf_external_info_delete(ospf, rt_type, api.instance, + p); + } else { + ospf_external_info_delete(ospf, rt_type, api.instance, + p); + + if (is_default_prefix4(&p)) + ospf_external_lsa_refresh_default(ospf); + else + ospf_external_lsa_flush(ospf, rt_type, &p, + ifindex /*, nexthop */); + } + } + + return 0; +} + +void ospf_zebra_import_default_route(struct ospf *ospf, bool unreg) +{ + struct prefix prefix = {}; + int command; + + if (zclient->sock < 0) { + if (IS_DEBUG_OSPF(zebra, ZEBRA)) + zlog_debug(" Not connected to Zebra"); + return; + } + + prefix.family = AF_INET; + prefix.prefixlen = 0; + + if (unreg) + command = ZEBRA_NEXTHOP_UNREGISTER; + else + command = ZEBRA_NEXTHOP_REGISTER; + + if (IS_DEBUG_OSPF(zebra, ZEBRA)) + zlog_debug("%s: sending cmd %s for %pFX (vrf %u)", __func__, + zserv_command_string(command), &prefix, + ospf->vrf_id); + + if (zclient_send_rnh(zclient, command, &prefix, SAFI_UNICAST, false, + true, ospf->vrf_id) == ZCLIENT_SEND_FAILURE) + flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient_send_rnh() failed", + __func__); +} + +static void ospf_zebra_import_check_update(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr) +{ + struct ospf *ospf = vrf->info; + + if (ospf == NULL || !IS_OSPF_ASBR(ospf)) + return; + + if (match->family != AF_INET || match->prefixlen != 0 || + nhr->type == ZEBRA_ROUTE_OSPF) + return; + + ospf->nssa_default_import_check.status = !!nhr->nexthop_num; + ospf_abr_nssa_type7_defaults(ospf); +} + +int ospf_distribute_list_out_set(struct ospf *ospf, int type, const char *name) +{ + /* Lookup access-list for distribute-list. */ + DISTRIBUTE_LIST(ospf, type) = access_list_lookup(AFI_IP, name); + + /* Clear previous distribute-name. */ + if (DISTRIBUTE_NAME(ospf, type)) + free(DISTRIBUTE_NAME(ospf, type)); + + /* Set distribute-name. */ + DISTRIBUTE_NAME(ospf, type) = strdup(name); + + /* If access-list have been set, schedule update timer. */ + if (DISTRIBUTE_LIST(ospf, type)) + ospf_distribute_list_update(ospf, type, 0); + + return CMD_SUCCESS; +} + +int ospf_distribute_list_out_unset(struct ospf *ospf, int type, + const char *name) +{ + /* Schedule update timer. */ + if (DISTRIBUTE_LIST(ospf, type)) + ospf_distribute_list_update(ospf, type, 0); + + /* Unset distribute-list. */ + DISTRIBUTE_LIST(ospf, type) = NULL; + + /* Clear distribute-name. */ + if (DISTRIBUTE_NAME(ospf, type)) + free(DISTRIBUTE_NAME(ospf, type)); + + DISTRIBUTE_NAME(ospf, type) = NULL; + + return CMD_SUCCESS; +} + +/* distribute-list update timer. */ +static void ospf_distribute_list_update_timer(struct event *thread) +{ + struct route_node *rn; + struct external_info *ei; + struct route_table *rt; + struct ospf_lsa *lsa; + int type, default_refresh = 0; + struct ospf *ospf = EVENT_ARG(thread); + + if (ospf == NULL) + return; + + ospf->t_distribute_update = NULL; + + zlog_info("Zebra[Redistribute]: distribute-list update timer fired!"); + + if (IS_DEBUG_OSPF_EVENT) { + zlog_debug("%s: ospf distribute-list update vrf %s id %d", + __func__, ospf_vrf_id_to_name(ospf->vrf_id), + ospf->vrf_id); + } + + /* foreach all external info. */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *ext_list; + struct listnode *node; + struct ospf_external *ext; + + ext_list = ospf->external[type]; + if (!ext_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(ext_list, node, ext)) { + rt = ext->external_info; + if (!rt) + continue; + for (rn = route_top(rt); rn; rn = route_next(rn)) { + ei = rn->info; + if (!ei) + continue; + + if (is_default_prefix4(&ei->p)) + default_refresh = 1; + else { + struct ospf_external_aggr_rt *aggr; + + aggr = ospf_external_aggr_match(ospf, + &ei->p); + if (aggr) { + /* Check the + * AS-external-LSA + * should be originated. + */ + if (!ospf_redistribute_check( + ospf, ei, NULL)) { + + ospf_unlink_ei_from_aggr( + ospf, aggr, ei); + continue; + } + + if (IS_DEBUG_OSPF( + lsa, + EXTNL_LSA_AGGR)) + zlog_debug( + "%s: Send Aggregate LSA (%pI4/%d)", + __func__, + &aggr->p.prefix, + aggr->p.prefixlen); + + /* Originate Aggregate + * LSA + */ + ospf_originate_summary_lsa( + ospf, aggr, ei); + } else if ( + (lsa = ospf_external_info_find_lsa( + ospf, &ei->p))) { + int force = + LSA_REFRESH_IF_CHANGED; + /* If this is a MaxAge + * LSA, we need to + * force refresh it + * because distribute + * settings might have + * changed and now, + * this LSA needs to be + * originated, not be + * removed. + * If we don't force + * refresh it, it will + * remain a MaxAge LSA + * because it will look + * like it hasn't + * changed. Neighbors + * will not receive + * updates for this LSA. + */ + if (IS_LSA_MAXAGE(lsa)) + force = LSA_REFRESH_FORCE; + + ospf_external_lsa_refresh( + ospf, lsa, ei, force, + false); + } else { + if (!ospf_redistribute_check( + ospf, ei, NULL)) + continue; + ospf_external_lsa_originate( + ospf, ei); + } + } + } + } + } + if (default_refresh) + ospf_external_lsa_refresh_default(ospf); +} + +/* Update distribute-list and set timer to apply access-list. */ +void ospf_distribute_list_update(struct ospf *ospf, int type, + unsigned short instance) +{ + struct ospf_external *ext; + + /* External info does not exist. */ + ext = ospf_external_lookup(ospf, type, instance); + if (!ext || !EXTERNAL_INFO(ext)) + return; + + /* Set timer. If timer is already started, this call does nothing. */ + event_add_timer_msec(master, ospf_distribute_list_update_timer, ospf, + ospf->min_ls_interval, &ospf->t_distribute_update); +} + +/* If access-list is updated, apply some check. */ +static void ospf_filter_update(struct access_list *access) +{ + struct ospf *ospf; + int type; + int abr_inv = 0; + struct ospf_area *area; + struct listnode *node, *n1; + + /* If OSPF instance does not exist, return right now. */ + if (listcount(om->ospf) == 0) + return; + + /* Iterate all ospf [VRF] instances */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { + /* Update distribute-list, and apply filter. */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (red_list) + for (ALL_LIST_ELEMENTS_RO(red_list, node, + red)) { + if (ROUTEMAP(red)) { + /* if route-map is not NULL it + * may be + * using this access list */ + ospf_distribute_list_update( + ospf, type, + red->instance); + } + } + + /* There is place for route-map for default-information + * (ZEBRA_ROUTE_MAX), + * but no distribute list. */ + if (type == ZEBRA_ROUTE_MAX) + break; + + if (DISTRIBUTE_NAME(ospf, type)) { + /* Keep old access-list for distribute-list. */ + struct access_list *old = + DISTRIBUTE_LIST(ospf, type); + + /* Update access-list for distribute-list. */ + DISTRIBUTE_LIST(ospf, type) = + access_list_lookup( + AFI_IP, + DISTRIBUTE_NAME(ospf, type)); + + /* No update for this distribute type. */ + if (old == NULL + && DISTRIBUTE_LIST(ospf, type) == NULL) + continue; + + /* Schedule distribute-list update timer. */ + if (DISTRIBUTE_LIST(ospf, type) == NULL + || strcmp(DISTRIBUTE_NAME(ospf, type), + access->name) + == 0) + ospf_distribute_list_update(ospf, type, + 0); + } + } + + /* Update Area access-list. */ + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + if (EXPORT_NAME(area)) { + EXPORT_LIST(area) = NULL; + abr_inv++; + } + + if (IMPORT_NAME(area)) { + IMPORT_LIST(area) = NULL; + abr_inv++; + } + } + + /* Schedule ABR tasks -- this will be changed -- takada. */ + if (IS_OSPF_ABR(ospf) && abr_inv) + ospf_schedule_abr_task(ospf); + } +} + +/* If prefix-list is updated, do some updates. */ +static void ospf_prefix_list_update(struct prefix_list *plist) +{ + struct ospf *ospf = NULL; + int type; + int abr_inv = 0; + struct ospf_area *area; + struct ospf_interface *oi; + struct listnode *node, *n1; + + /* If OSPF instatnce does not exist, return right now. */ + if (listcount(om->ospf) == 0) + return; + + /* Iterate all ospf [VRF] instances */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, n1, ospf)) { + + /* Update all route-maps which are used + * as redistribution filters. + * They might use prefix-list. + */ + for (type = 0; type <= ZEBRA_ROUTE_MAX; type++) { + struct list *red_list; + struct ospf_redist *red; + + red_list = ospf->redist[type]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(red_list, node, red)) { + if (ROUTEMAP(red)) { + /* if route-map is not NULL + * it may be using + * this prefix list */ + ospf_distribute_list_update( + ospf, type, red->instance); + } + } + } + + /* Update area filter-lists. */ + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) { + /* Update filter-list in. */ + if (PREFIX_NAME_IN(area) + && strcmp(PREFIX_NAME_IN(area), + prefix_list_name(plist)) + == 0) { + PREFIX_LIST_IN(area) = prefix_list_lookup( + AFI_IP, PREFIX_NAME_IN(area)); + abr_inv++; + } + + /* Update filter-list out. */ + if (PREFIX_NAME_OUT(area) + && strcmp(PREFIX_NAME_OUT(area), + prefix_list_name(plist)) + == 0) { + PREFIX_LIST_OUT(area) = prefix_list_lookup( + AFI_IP, PREFIX_NAME_OUT(area)); + abr_inv++; + } + } + + /* Update interface neighbor-filter lists. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + if (OSPF_IF_PARAM(oi, nbr_filter_name) && + strcmp(OSPF_IF_PARAM(oi, nbr_filter_name), + prefix_list_name(plist)) == 0) { + oi->nbr_filter = prefix_list_lookup( + AFI_IP, + OSPF_IF_PARAM(oi, nbr_filter_name)); + if (oi->nbr_filter) + ospf_intf_neighbor_filter_apply(oi); + } + } + + /* Schedule ABR task. */ + if (IS_OSPF_ABR(ospf) && abr_inv) + ospf_schedule_abr_task(ospf); + } +} + +static struct ospf_distance *ospf_distance_new(void) +{ + return XCALLOC(MTYPE_OSPF_DISTANCE, sizeof(struct ospf_distance)); +} + +static void ospf_distance_free(struct ospf_distance *odistance) +{ + XFREE(MTYPE_OSPF_DISTANCE, odistance); +} + +int ospf_distance_set(struct vty *vty, struct ospf *ospf, + const char *distance_str, const char *ip_str, + const char *access_list_str) +{ + int ret; + struct prefix_ipv4 p; + uint8_t distance; + struct route_node *rn; + struct ospf_distance *odistance; + + ret = str2prefix_ipv4(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + distance = atoi(distance_str); + + /* Get OSPF distance node. */ + rn = route_node_get(ospf->distance_table, (struct prefix *)&p); + if (rn->info) { + odistance = rn->info; + route_unlock_node(rn); + } else { + odistance = ospf_distance_new(); + rn->info = odistance; + } + + /* Set distance value. */ + odistance->distance = distance; + + /* Reset access-list configuration. */ + if (odistance->access_list) { + free(odistance->access_list); + odistance->access_list = NULL; + } + if (access_list_str) + odistance->access_list = strdup(access_list_str); + + return CMD_SUCCESS; +} + +int ospf_distance_unset(struct vty *vty, struct ospf *ospf, + const char *distance_str, const char *ip_str, + char const *access_list_str) +{ + int ret; + struct prefix_ipv4 p; + struct route_node *rn; + struct ospf_distance *odistance; + + ret = str2prefix_ipv4(ip_str, &p); + if (ret == 0) { + vty_out(vty, "Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + rn = route_node_lookup(ospf->distance_table, (struct prefix *)&p); + if (!rn) { + vty_out(vty, "Can't find specified prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + odistance = rn->info; + + if (odistance->access_list) + free(odistance->access_list); + ospf_distance_free(odistance); + + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + + return CMD_SUCCESS; +} + +void ospf_distance_reset(struct ospf *ospf) +{ + struct route_node *rn; + struct ospf_distance *odistance; + + for (rn = route_top(ospf->distance_table); rn; rn = route_next(rn)) { + odistance = rn->info; + if (!odistance) + continue; + + if (odistance->access_list) + free(odistance->access_list); + ospf_distance_free(odistance); + rn->info = NULL; + route_unlock_node(rn); + } +} + +uint8_t ospf_distance_apply(struct ospf *ospf, struct prefix_ipv4 *p, + struct ospf_route * or) +{ + + if (ospf == NULL) + return 0; + + if (ospf->distance_intra && or->path_type == OSPF_PATH_INTRA_AREA) + return ospf->distance_intra; + + if (ospf->distance_inter && or->path_type == OSPF_PATH_INTER_AREA) + return ospf->distance_inter; + + if (ospf->distance_external + && (or->path_type == OSPF_PATH_TYPE1_EXTERNAL || + or->path_type == OSPF_PATH_TYPE2_EXTERNAL)) + return ospf->distance_external; + + if (ospf->distance_all) + return ospf->distance_all; + + return 0; +} + +void ospf_zebra_vrf_register(struct ospf *ospf) +{ + if (!zclient || zclient->sock < 0 || !ospf) + return; + + if (ospf->vrf_id != VRF_UNKNOWN) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: Register VRF %s id %u", __func__, + ospf_vrf_id_to_name(ospf->vrf_id), + ospf->vrf_id); + zclient_send_reg_requests(zclient, ospf->vrf_id); + } +} + +void ospf_zebra_vrf_deregister(struct ospf *ospf) +{ + if (!zclient || zclient->sock < 0 || !ospf) + return; + + if (ospf->vrf_id != VRF_DEFAULT && ospf->vrf_id != VRF_UNKNOWN) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: De-Register VRF %s id %u to Zebra.", + __func__, ospf_vrf_id_to_name(ospf->vrf_id), + ospf->vrf_id); + /* Deregister for router-id, interfaces, + * redistributed routes. */ + zclient_send_dereg_requests(zclient, ospf->vrf_id); + } +} + +/* Label Manager Functions */ + +/** + * Check if Label Manager is Ready or not. + * + * @return True if Label Manager is ready, False otherwise + */ +bool ospf_zebra_label_manager_ready(void) +{ + return (zclient_sync->sock > 0); +} + +/** + * Request Label Range to the Label Manager. + * + * @param base base label of the label range to request + * @param chunk_size size of the label range to request + * + * @return 0 on success, -1 on failure + */ +int ospf_zebra_request_label_range(uint32_t base, uint32_t chunk_size) +{ + int ret; + uint32_t start, end; + + if (zclient_sync->sock < 0) + return -1; + + ret = lm_get_label_chunk(zclient_sync, 0, base, chunk_size, &start, + &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Release Label Range to the Label Manager. + * + * @param start start of label range to release + * @param end end of label range to release + * + * @return 0 on success, -1 otherwise + */ +int ospf_zebra_release_label_range(uint32_t start, uint32_t end) +{ + int ret; + + if (zclient_sync->sock < 0) + return -1; + + ret = lm_release_label_chunk(zclient_sync, start, end); + if (ret < 0) { + zlog_warn("%s: error releasing label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Connect to the Label Manager. + * + * @return 0 on success, -1 otherwise + */ +int ospf_zebra_label_manager_connect(void) +{ + /* Connect to label manager. */ + if (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: failed connecting synchronous zclient!", + __func__); + return -1; + } + /* make socket non-blocking */ + set_nonblocking(zclient_sync->sock); + + /* Send hello to notify zebra this is a synchronous client */ + if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: failed sending hello for synchronous zclient!", + __func__); + close(zclient_sync->sock); + zclient_sync->sock = -1; + return -1; + } + + /* Connect to label manager */ + if (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: failed connecting to label manager!", __func__); + if (zclient_sync->sock > 0) { + close(zclient_sync->sock); + zclient_sync->sock = -1; + } + return -1; + } + + osr_debug("SR (%s): Successfully connected to the Label Manager", + __func__); + + return 0; +} + +static void ospf_zebra_connected(struct zclient *zclient) +{ + struct ospf *ospf; + struct listnode *node; + + /* Send the client registration */ + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); + + zclient_send_reg_requests(zclient, VRF_DEFAULT); + + /* Activate graceful restart if configured. */ + for (ALL_LIST_ELEMENTS_RO(om->ospf, node, ospf)) { + if (!ospf->gr_info.restart_support) + continue; + (void)ospf_zebra_gr_enable(ospf, ospf->gr_info.grace_period); + } +} + +/* + * opaque messages between processes + */ +static int ospf_opaque_msg_handler(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct zapi_opaque_msg info; + struct ldp_igp_sync_if_state state; + struct ldp_igp_sync_announce announce; + struct zapi_opaque_reg_info dst; + int ret = 0; + + s = zclient->ibuf; + + if (zclient_opaque_decode(s, &info) != 0) + return -1; + + switch (info.type) { + case LINK_STATE_SYNC: + dst.proto = info.src_proto; + dst.instance = info.src_instance; + dst.session_id = info.src_session_id; + dst.type = LINK_STATE_SYNC; + ret = ospf_te_sync_ted(dst); + break; + case LDP_IGP_SYNC_IF_STATE_UPDATE: + STREAM_GET(&state, s, sizeof(state)); + ret = ospf_ldp_sync_state_update(state); + break; + case LDP_IGP_SYNC_ANNOUNCE_UPDATE: + STREAM_GET(&announce, s, sizeof(announce)); + ret = ospf_ldp_sync_announce_update(announce); + break; + default: + break; + } + +stream_failure: + + return ret; +} + +static int ospf_zebra_client_close_notify(ZAPI_CALLBACK_ARGS) +{ + int ret = 0; + + struct zapi_client_close_info info; + + if (zapi_client_close_notify_decode(zclient->ibuf, &info) < 0) + return -1; + + ospf_ldp_sync_handle_client_close(&info); + + return ret; +} + +static zclient_handler *const ospf_handlers[] = { + [ZEBRA_ROUTER_ID_UPDATE] = ospf_router_id_update_zebra, + [ZEBRA_INTERFACE_ADDRESS_ADD] = ospf_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = ospf_interface_address_delete, + [ZEBRA_INTERFACE_LINK_PARAMS] = ospf_interface_link_params, + + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = ospf_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = ospf_zebra_read_route, + + [ZEBRA_OPAQUE_MESSAGE] = ospf_opaque_msg_handler, + + [ZEBRA_CLIENT_CLOSE_NOTIFY] = ospf_zebra_client_close_notify, +}; + +void ospf_zebra_init(struct event_loop *master, unsigned short instance) +{ + /* Allocate zebra structure. */ + zclient = zclient_new(master, &zclient_options_default, ospf_handlers, + array_size(ospf_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_OSPF, instance, &ospfd_privs); + zclient->zebra_connected = ospf_zebra_connected; + zclient->nexthop_update = ospf_zebra_import_check_update; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_sync, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_OSPF; + zclient_sync->instance = instance; + /* + * session_id must be different from default value (0) to distinguish + * the asynchronous socket from the synchronous one + */ + zclient_sync->session_id = 1; + zclient_sync->privs = &ospfd_privs; + + access_list_add_hook(ospf_filter_update); + access_list_delete_hook(ospf_filter_update); + prefix_list_add_hook(ospf_prefix_list_update); + prefix_list_delete_hook(ospf_prefix_list_update); +} + +void ospf_zebra_send_arp(const struct interface *ifp, const struct prefix *p) +{ + zclient_send_neigh_discovery_req(zclient, ifp, p); +} diff --git a/ospfd/ospf_zebra.h b/ospfd/ospf_zebra.h new file mode 100644 index 0000000..86a5678 --- /dev/null +++ b/ospfd/ospf_zebra.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for OSPFd + * Copyright (C) 1997, 98, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPF_ZEBRA_H +#define _ZEBRA_OSPF_ZEBRA_H + +#include "vty.h" +#include "hook.h" + +#define EXTERNAL_METRIC_TYPE_1 0 +#define EXTERNAL_METRIC_TYPE_2 1 + +#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX +#define DEFAULT_ROUTE_TYPE(T) ((T) == DEFAULT_ROUTE) + +/* OSPF distance. */ +struct ospf_distance { + /* Distance value for the IP source prefix. */ + uint8_t distance; + + /* Name of the access-list to be matched. */ + char *access_list; +}; + +/* Prototypes */ +struct ospf_route; +extern void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *, + struct ospf_route *); +extern void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *, + struct ospf_route *); + +extern void ospf_zebra_add_discard(struct ospf *ospf, struct prefix_ipv4 *); +extern void ospf_zebra_delete_discard(struct ospf *ospf, struct prefix_ipv4 *); + +extern int ospf_redistribute_check(struct ospf *, struct external_info *, + int *); +extern int ospf_distribute_check_connected(struct ospf *, + struct external_info *); +extern void ospf_distribute_list_update(struct ospf *, int, unsigned short); + +extern int ospf_is_type_redistributed(struct ospf *, int, unsigned short); +extern void ospf_distance_reset(struct ospf *); +extern uint8_t ospf_distance_apply(struct ospf *ospf, struct prefix_ipv4 *, + struct ospf_route *); +extern struct ospf_external *ospf_external_lookup(struct ospf *, uint8_t, + unsigned short); +extern struct ospf_external *ospf_external_add(struct ospf *, uint8_t, + unsigned short); + +struct sr_prefix; +struct sr_nhlfe; +extern void ospf_zebra_update_prefix_sid(const struct sr_prefix *srp); +extern void ospf_zebra_delete_prefix_sid(const struct sr_prefix *srp); +extern void ospf_zebra_send_adjacency_sid(int cmd, struct sr_nhlfe nhlfe); + +extern void ospf_external_del(struct ospf *, uint8_t, unsigned short); +extern struct ospf_redist *ospf_redist_lookup(struct ospf *, uint8_t, + unsigned short); +extern struct ospf_redist *ospf_redist_add(struct ospf *, uint8_t, + unsigned short); +extern void ospf_redist_del(struct ospf *, uint8_t, unsigned short); + +extern int ospf_redistribute_update(struct ospf *, struct ospf_redist *, int, + unsigned short, int, int); +extern int ospf_redistribute_set(struct ospf *, struct ospf_redist *, int, + unsigned short, int, int); +extern int ospf_redistribute_unset(struct ospf *, int, unsigned short); +extern int ospf_redistribute_default_set(struct ospf *, int, int, int); +extern void ospf_zebra_import_default_route(struct ospf *ospf, bool unreg); +extern int ospf_distribute_list_out_set(struct ospf *, int, const char *); +extern int ospf_distribute_list_out_unset(struct ospf *, int, const char *); +extern void ospf_routemap_set(struct ospf_redist *, const char *); +extern void ospf_routemap_unset(struct ospf_redist *); +extern int ospf_zebra_gr_enable(struct ospf *ospf, uint32_t stale_time); +extern int ospf_zebra_gr_disable(struct ospf *ospf); +extern int ospf_distance_set(struct vty *, struct ospf *, const char *, + const char *, const char *); +extern int ospf_distance_unset(struct vty *, struct ospf *, const char *, + const char *, const char *); +extern void ospf_zebra_init(struct event_loop *m, unsigned short instance); +extern void ospf_zebra_vrf_register(struct ospf *ospf); +extern void ospf_zebra_vrf_deregister(struct ospf *ospf); +bool ospf_external_default_routemap_apply_walk( + struct ospf *ospf, struct list *ext_list, + struct external_info *default_ei); +int ospf_external_info_apply_default_routemap(struct ospf *ospf, + struct external_info *ei, + struct external_info *default_ei); + +extern void ospf_zebra_send_arp(const struct interface *ifp, + const struct prefix *p); +bool ospf_zebra_label_manager_ready(void); +int ospf_zebra_label_manager_connect(void); +int ospf_zebra_request_label_range(uint32_t base, uint32_t chunk_size); +int ospf_zebra_release_label_range(uint32_t start, uint32_t end); +#endif /* _ZEBRA_OSPF_ZEBRA_H */ diff --git a/ospfd/ospfd.c b/ospfd/ospfd.c new file mode 100644 index 0000000..1d013b2 --- /dev/null +++ b/ospfd/ospfd.c @@ -0,0 +1,2392 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* OSPF version 2 daemon program. + * Copyright (C) 1999, 2000 Toshiaki Takada + */ + +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "if.h" +#include "memory.h" +#include "stream.h" +#include "log.h" +#include "sockunion.h" /* for inet_aton () */ +#include "zclient.h" +#include "routemap.h" +#include "plist.h" +#include "sockopt.h" +#include "bfd.h" +#include "libfrr.h" +#include "defaults.h" +#include "lib_errors.h" +#include "ldp_sync.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_bfd.h" +#include "ospfd/ospf_network.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_ism.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_neighbor.h" +#include "ospfd/ospf_nsm.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_packet.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_zebra.h" +#include "ospfd/ospf_abr.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_ase.h" +#include "ospfd/ospf_ldp_sync.h" +#include "ospfd/ospf_gr.h" +#include "ospfd/ospf_apiserver.h" + + +DEFINE_QOBJ_TYPE(ospf); + +/* OSPF process wide configuration. */ +static struct ospf_master ospf_master; + +/* OSPF process wide configuration pointer to export. */ +struct ospf_master *om; + +unsigned short ospf_instance; + +extern struct zclient *zclient; +extern struct zclient *zclient_sync; + +/* OSPF config processing timer thread */ +struct event *t_ospf_cfg; + +static void ospf_remove_vls_through_area(struct ospf *, struct ospf_area *); +static void ospf_network_free(struct ospf *, struct ospf_network *); +static void ospf_area_free(struct ospf_area *); +static void ospf_network_run(struct prefix *, struct ospf_area *); +static void ospf_network_run_interface(struct ospf *, struct interface *, + struct prefix *, struct ospf_area *); +static void ospf_network_run_subnet(struct ospf *, struct connected *, + struct prefix *, struct ospf_area *); +static int ospf_network_match_iface(const struct connected *, + const struct prefix *); +static void ospf_finish_final(struct ospf *); + +/* API to clean refresh queues and LSAs */ +static void ospf_free_refresh_queue(struct ospf *ospf) +{ + for (int i = 0; i < OSPF_LSA_REFRESHER_SLOTS; i++) { + struct list *list = ospf->lsa_refresh_queue.qs[i]; + struct listnode *node, *nnode; + struct ospf_lsa *lsa; + + if (list) { + for (ALL_LIST_ELEMENTS(list, node, nnode, lsa)) { + listnode_delete(list, lsa); + lsa->refresh_list = -1; + ospf_lsa_unlock(&lsa); + } + list_delete(&list); + ospf->lsa_refresh_queue.qs[i] = NULL; + } + } +} +#define OSPF_EXTERNAL_LSA_ORIGINATE_DELAY 1 + +int p_spaces_compare_func(const struct p_space *a, const struct p_space *b) +{ + if (a->protected_resource->type == OSPF_TI_LFA_LINK_PROTECTION + && b->protected_resource->type == OSPF_TI_LFA_LINK_PROTECTION) + return (a->protected_resource->link->link_id.s_addr + - b->protected_resource->link->link_id.s_addr); + + if (a->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION + && b->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION) + return (a->protected_resource->router_id.s_addr + - b->protected_resource->router_id.s_addr); + + /* This should not happen */ + return 0; +} + +int q_spaces_compare_func(const struct q_space *a, const struct q_space *b) +{ + return (a->root->id.s_addr - b->root->id.s_addr); +} + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func); + +void ospf_process_refresh_data(struct ospf *ospf, bool reset) +{ + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct in_addr router_id, router_id_old; + struct ospf_interface *oi; + struct interface *ifp; + struct listnode *node, *nnode; + struct ospf_area *area; + bool rid_change = false; + + if (!ospf->oi_running) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "Router ospf not configured -- Router-ID update postponed"); + return; + } + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Router-ID[OLD:%pI4]: Update", + &ospf->router_id); + + router_id_old = ospf->router_id; + + /* Select the router ID based on these priorities: + 1. Statically assigned router ID is always the first choice. + 2. If there is no statically assigned router ID, then try to stick + with the most recent value, since changing router ID's is very + disruptive. + 3. Last choice: just go with whatever the zebra daemon recommends. + */ + if (ospf->router_id_static.s_addr != INADDR_ANY) + router_id = ospf->router_id_static; + else if (ospf->router_id.s_addr != INADDR_ANY) + router_id = ospf->router_id; + else + router_id = ospf->router_id_zebra; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Router-ID[OLD:%pI4]: Update to %pI4", + &ospf->router_id, &router_id); + + rid_change = !(IPV4_ADDR_SAME(&router_id_old, &router_id)); + if (rid_change || (reset)) { + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + /* Some nbrs are identified by router_id, these needs + * to be rebuilt. Possible optimization would be to do + * oi->nbr_self->router_id = router_id for + * !(virtual | ptop) links + */ + ospf_nbr_self_reset(oi, router_id); + + /* + * If the old router id was not set, but now it + * is and the interface is operative and the + * state is ISM_Down we should kick the state + * machine as that we processed the interfaces + * based upon the network statement( or intf config ) + * but could not start it at that time. + */ + if (if_is_operative(oi->ifp) && oi->state == ISM_Down + && router_id_old.s_addr == INADDR_ANY) + ospf_if_up(oi); + } + + /* Flush (inline) all the self originated LSAs */ + ospf_flush_self_originated_lsas_now(ospf); + + ospf->router_id = router_id; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Router-ID[NEW:%pI4]: Update", + &ospf->router_id); + + /* Flush (inline) all external LSAs which now match the new + router-id, + need to adjust the OSPF_LSA_SELF flag, so the flush doesn't + hit + asserts in ospf_refresher_unregister_lsa(). This step is + needed + because the current frr code does look-up for + self-originated LSAs + based on the self router-id alone but expects OSPF_LSA_SELF + to be + properly set */ + if (ospf->lsdb) { + struct route_node *rn; + struct ospf_lsa *lsa; + + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) { + /* AdvRouter and Router ID is the same. */ + if (IPV4_ADDR_SAME(&lsa->data->adv_router, + &ospf->router_id) && rid_change) { + SET_FLAG(lsa->flags, + OSPF_LSA_SELF_CHECKED); + SET_FLAG(lsa->flags, OSPF_LSA_SELF); + ospf_lsa_flush_schedule(ospf, lsa); + } + /* The above flush will send immediately + * So discard the LSA to originate new + */ + ospf_discard_from_db(ospf, ospf->lsdb, lsa); + } + + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + ospf_discard_from_db(ospf, ospf->lsdb, lsa); + + ospf_lsdb_delete_all(ospf->lsdb); + } + + /* Since the LSAs are deleted, need reset the aggr flag */ + ospf_unset_all_aggr_flag(ospf); + + /* Delete the LSDB */ + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) + ospf_area_lsdb_discard_delete(area); + + /* update router-lsa's for each area */ + ospf_router_lsa_update(ospf); + + /* update ospf_interface's */ + FOR_ALL_INTERFACES (vrf, ifp) { + if (reset) + ospf_if_reset(ifp); + else + ospf_if_update(ospf, ifp); + } + + ospf_external_lsa_rid_change(ospf); + +#ifdef SUPPORT_OSPF_API + ospf_apiserver_clients_notify_router_id_change(router_id); +#endif + } + + ospf->inst_shutdown = 0; +} + +void ospf_router_id_update(struct ospf *ospf) +{ + ospf_process_refresh_data(ospf, false); +} + +void ospf_process_reset(struct ospf *ospf) +{ + ospf_process_refresh_data(ospf, true); +} + +void ospf_neighbor_reset(struct ospf *ospf, struct in_addr nbr_id, + const char *nbr_str) +{ + struct route_node *rn; + struct ospf_neighbor *nbr; + struct ospf_interface *oi; + struct listnode *node; + + /* Clear only a particular nbr with nbr router id as nbr_id */ + if (nbr_str != NULL) { + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + nbr = ospf_nbr_lookup_by_routerid(oi->nbrs, &nbr_id); + if (nbr) + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr); + } + return; + } + + /* send Neighbor event KillNbr to all associated neighbors. */ + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + for (rn = route_top(oi->nbrs); rn; rn = route_next(rn)) { + nbr = rn->info; + if (nbr && (nbr != oi->nbr_self)) + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_KillNbr); + } + } +} + +/* For OSPF area sort by area id. */ +static int ospf_area_id_cmp(struct ospf_area *a1, struct ospf_area *a2) +{ + if (ntohl(a1->area_id.s_addr) > ntohl(a2->area_id.s_addr)) + return 1; + if (ntohl(a1->area_id.s_addr) < ntohl(a2->area_id.s_addr)) + return -1; + return 0; +} + +static void ospf_add(struct ospf *ospf) +{ + listnode_add(om->ospf, ospf); +} + +static void ospf_delete(struct ospf *ospf) +{ + listnode_delete(om->ospf, ospf); +} + +struct ospf *ospf_new_alloc(unsigned short instance, const char *name) +{ + int i; + struct vrf *vrf = NULL; + + struct ospf *new = XCALLOC(MTYPE_OSPF_TOP, sizeof(struct ospf)); + + new->instance = instance; + new->router_id.s_addr = htonl(0); + new->router_id_static.s_addr = htonl(0); + + vrf = vrf_lookup_by_name(name); + if (vrf) + new->vrf_id = vrf->vrf_id; + else + new->vrf_id = VRF_UNKNOWN; + + /* Freed in ospf_finish_final */ + new->name = XSTRDUP(MTYPE_OSPF_TOP, name); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: Create new ospf instance with vrf_name %s vrf_id %u", + __func__, name, new->vrf_id); + + if (vrf) + ospf_vrf_link(new, vrf); + + ospf_zebra_vrf_register(new); + + new->abr_type = OSPF_ABR_DEFAULT; + new->oiflist = list_new(); + new->vlinks = list_new(); + new->areas = list_new(); + new->areas->cmp = (int (*)(void *, void *))ospf_area_id_cmp; + new->networks = route_table_init(); + new->nbr_nbma = route_table_init(); + + new->lsdb = ospf_lsdb_new(); + + new->default_originate = DEFAULT_ORIGINATE_NONE; + + new->passive_interface_default = OSPF_IF_ACTIVE; + + new->new_external_route = route_table_init(); + new->old_external_route = route_table_init(); + new->external_lsas = route_table_init(); + + new->stub_router_startup_time = OSPF_STUB_ROUTER_UNCONFIGURED; + new->stub_router_shutdown_time = OSPF_STUB_ROUTER_UNCONFIGURED; + new->stub_router_admin_set = OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET; + + /* Distribute parameter init. */ + for (i = 0; i <= ZEBRA_ROUTE_MAX; i++) { + new->dtag[i] = 0; + } + new->default_metric = -1; + new->ref_bandwidth = OSPF_DEFAULT_REF_BANDWIDTH; + + /* LSA timers */ + new->min_ls_interval = OSPF_MIN_LS_INTERVAL; + new->min_ls_arrival = OSPF_MIN_LS_ARRIVAL; + + /* SPF timer value init. */ + new->spf_delay = OSPF_SPF_DELAY_DEFAULT; + new->spf_holdtime = OSPF_SPF_HOLDTIME_DEFAULT; + new->spf_max_holdtime = OSPF_SPF_MAX_HOLDTIME_DEFAULT; + new->spf_hold_multiplier = 1; + + /* MaxAge init. */ + new->maxage_delay = OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT; + new->maxage_lsa = route_table_init(); + new->t_maxage_walker = NULL; + event_add_timer(master, ospf_lsa_maxage_walker, new, + OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker); + + /* Max paths initialization */ + new->max_multipath = MULTIPATH_NUM; + + /* Distance table init. */ + new->distance_table = route_table_init(); + + new->lsa_refresh_queue.index = 0; + new->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT; + new->lsa_refresh_timer = OSPF_LS_REFRESH_TIME; + new->t_lsa_refresher = NULL; + event_add_timer(master, ospf_lsa_refresh_walker, new, + new->lsa_refresh_interval, &new->t_lsa_refresher); + new->lsa_refresher_started = monotime(NULL); + + new->ibuf = stream_new(OSPF_MAX_PACKET_SIZE + 1); + + new->t_read = NULL; + new->oi_write_q = list_new(); + new->write_oi_count = OSPF_WRITE_INTERFACE_COUNT_DEFAULT; + + new->proactive_arp = OSPF_PROACTIVE_ARP_DEFAULT; + + ospf_gr_helper_instance_init(new); + + ospf_asbr_external_aggregator_init(new); + + ospf_opaque_type11_lsa_init(new); + + QOBJ_REG(new, ospf); + + new->fd = -1; + new->intf_socket_enabled = true; + + new->recv_sock_bufsize = OSPF_DEFAULT_SOCK_BUFSIZE; + new->send_sock_bufsize = OSPF_DEFAULT_SOCK_BUFSIZE; + + return new; +} + +/* Allocate new ospf structure. */ +static struct ospf *ospf_new(unsigned short instance, const char *name) +{ + struct ospf *new; + + new = ospf_new_alloc(instance, name); + ospf_add(new); + + if (new->vrf_id == VRF_UNKNOWN) + return new; + + if ((ospf_sock_init(new)) < 0) { + flog_err(EC_LIB_SOCKET, + "%s: ospf_sock_init is unable to open a socket", + __func__); + return new; + } + + event_add_read(master, ospf_read, new, new->fd, &new->t_read); + + new->oi_running = 1; + ospf_router_id_update(new); + + /* + * Read from non-volatile memory whether this instance is performing a + * graceful restart or not. + */ + ospf_gr_nvm_read(new); + + new->fr_configured = false; + + return new; +} + +struct ospf *ospf_lookup_instance(unsigned short instance) +{ + struct ospf *ospf; + struct listnode *node, *nnode; + + if (listcount(om->ospf) == 0) + return NULL; + + for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) + if ((ospf->instance == 0 && instance == 0) + || (ospf->instance && instance + && ospf->instance == instance)) + return ospf; + + return NULL; +} + +static int ospf_is_ready(struct ospf *ospf) +{ + /* OSPF must be on and Router-ID must be configured. */ + if (!ospf || ospf->router_id.s_addr == INADDR_ANY) + return 0; + + return 1; +} + +struct ospf *ospf_lookup_by_inst_name(unsigned short instance, const char *name) +{ + struct ospf *ospf = NULL; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) { + if ((ospf->instance == instance) + && ((ospf->name == NULL && name == NULL) + || (ospf->name && name + && strcmp(ospf->name, name) == 0))) + return ospf; + } + return NULL; +} + +struct ospf *ospf_lookup(unsigned short instance, const char *name) +{ + struct ospf *ospf; + + if (ospf_instance) { + ospf = ospf_lookup_instance(instance); + } else { + ospf = ospf_lookup_by_inst_name(instance, name); + } + + return ospf; +} + +struct ospf *ospf_get(unsigned short instance, const char *name, bool *created) +{ + struct ospf *ospf; + + ospf = ospf_lookup(instance, name); + + *created = (ospf == NULL); + if (ospf == NULL) + ospf = ospf_new(instance, name); + + return ospf; +} + +struct ospf *ospf_lookup_by_vrf_id(vrf_id_t vrf_id) +{ + struct vrf *vrf = NULL; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + return (vrf->info) ? (struct ospf *)vrf->info : NULL; +} + +uint32_t ospf_count_area_params(struct ospf *ospf) +{ + struct vrf *vrf; + struct interface *ifp; + uint32_t count = 0; + + if (ospf->vrf_id != VRF_UNKNOWN) { + vrf = vrf_lookup_by_id(ospf->vrf_id); + + FOR_ALL_INTERFACES (vrf, ifp) { + count += ospf_if_count_area_params(ifp); + } + } + + return count; +} + +/* It should only be used when processing incoming info update from zebra. + * Other situations, it is not sufficient to lookup the ospf instance by + * vrf_name only without using the instance number. + */ +static struct ospf *ospf_lookup_by_name(const char *vrf_name) +{ + struct ospf *ospf = NULL; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) + if ((ospf->name == NULL && vrf_name == NULL) + || (ospf->name && vrf_name + && strcmp(ospf->name, vrf_name) == 0)) + return ospf; + return NULL; +} + +/* Timer thread for deferred shutdown */ +static void ospf_deferred_shutdown_timer(struct event *t) +{ + struct ospf *ospf = EVENT_ARG(t); + + ospf_finish_final(ospf); +} + +/* Check whether deferred-shutdown must be scheduled, otherwise call + * down directly into second-half of instance shutdown. + */ +static void ospf_deferred_shutdown_check(struct ospf *ospf) +{ + unsigned long timeout; + struct listnode *ln; + struct ospf_area *area; + + /* deferred shutdown already running? */ + if (ospf->t_deferred_shutdown) + return; + + /* Should we try push out max-metric LSAs? */ + if (ospf->stub_router_shutdown_time != OSPF_STUB_ROUTER_UNCONFIGURED) { + for (ALL_LIST_ELEMENTS_RO(ospf->areas, ln, area)) { + SET_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED); + + if (!CHECK_FLAG(area->stub_router_state, + OSPF_AREA_IS_STUB_ROUTED)) + ospf_router_lsa_update_area(area); + } + timeout = ospf->stub_router_shutdown_time; + OSPF_TIMER_ON(ospf->t_deferred_shutdown, + ospf_deferred_shutdown_timer, timeout); + } else { + /* No timer needed */ + ospf_finish_final(ospf); + } +} + +/* Shut down the entire process */ +void ospf_terminate(void) +{ + struct ospf *ospf; + struct listnode *node, *nnode; + + /* shutdown already in progress */ + if (CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN)) + return; + + SET_FLAG(om->options, OSPF_MASTER_SHUTDOWN); + + for (ALL_LIST_ELEMENTS(om->ospf, node, nnode, ospf)) + ospf_finish(ospf); + + /* Cleanup GR */ + ospf_gr_helper_stop(); + + /* Cleanup route maps */ + route_map_finish(); + + /* reverse prefix_list_init */ + prefix_list_add_hook(NULL); + prefix_list_delete_hook(NULL); + prefix_list_reset(); + + /* Cleanup vrf info */ + ospf_vrf_terminate(); + + keychain_terminate(); + + ospf_opaque_term(); + list_delete(&om->ospf); + + /* Deliberately go back up, hopefully to thread scheduler, as + * One or more ospf_finish()'s may have deferred shutdown to a timer + * thread + */ + zclient_stop(zclient); + zclient_free(zclient); + zclient_stop(zclient_sync); + zclient_free(zclient_sync); + + frr_fini(); +} + +void ospf_finish(struct ospf *ospf) +{ + if (CHECK_FLAG(om->options, OSPF_MASTER_SHUTDOWN)) + ospf_finish_final(ospf); + else { + /* let deferred shutdown decide */ + ospf_deferred_shutdown_check(ospf); + } +} + +/* Final cleanup of ospf instance */ +static void ospf_finish_final(struct ospf *ospf) +{ + struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); + struct route_node *rn; + struct ospf_nbr_nbma *nbr_nbma; + struct ospf_lsa *lsa; + struct ospf_interface *oi; + struct ospf_area *area; + struct ospf_vl_data *vl_data; + struct listnode *node, *nnode; + struct ospf_redist *red; + int i; + + QOBJ_UNREG(ospf); + + ospf_opaque_type11_lsa_term(ospf); + + ospf_opaque_finish(); + + if (!ospf->gr_info.prepare_in_progress) + ospf_flush_self_originated_lsas_now(ospf); + XFREE(MTYPE_TMP, ospf->gr_info.exit_reason); + + /* Unregister redistribution */ + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + struct list *red_list; + + red_list = ospf->redist[i]; + if (!red_list) + continue; + + for (ALL_LIST_ELEMENTS(red_list, node, nnode, red)) { + ospf_redistribute_unset(ospf, i, red->instance); + ospf_redist_del(ospf, i, red->instance); + } + } + red = ospf_redist_lookup(ospf, DEFAULT_ROUTE, 0); + if (red) { + ospf_routemap_unset(red); + ospf_redist_del(ospf, DEFAULT_ROUTE, 0); + ospf_redistribute_default_set(ospf, DEFAULT_ORIGINATE_NONE, 0, 0); + } + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) + ospf_remove_vls_through_area(ospf, area); + + for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) + ospf_vl_delete(ospf, vl_data); + + list_delete(&ospf->vlinks); + + /* shutdown LDP-Sync */ + if (ospf->vrf_id == VRF_DEFAULT) + ospf_ldp_sync_gbl_exit(ospf, true); + + /* Reset interface. */ + for (ALL_LIST_ELEMENTS(ospf->oiflist, node, nnode, oi)) + ospf_if_free(oi); + list_delete(&ospf->oiflist); + ospf->oi_running = 0; + + /* De-Register VRF */ + ospf_zebra_vrf_deregister(ospf); + + /* Clear static neighbors */ + for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) + if ((nbr_nbma = rn->info)) { + EVENT_OFF(nbr_nbma->t_poll); + + if (nbr_nbma->nbr) { + nbr_nbma->nbr->nbr_nbma = NULL; + nbr_nbma->nbr = NULL; + } + + if (nbr_nbma->oi) { + listnode_delete(nbr_nbma->oi->nbr_nbma, + nbr_nbma); + nbr_nbma->oi = NULL; + } + + XFREE(MTYPE_OSPF_NEIGHBOR_STATIC, nbr_nbma); + } + + route_table_finish(ospf->nbr_nbma); + + /* Clear networks and Areas. */ + for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) { + struct ospf_network *network; + + if ((network = rn->info) != NULL) { + ospf_network_free(ospf, network); + rn->info = NULL; + route_unlock_node(rn); + } + } + route_table_finish(ospf->networks); + + for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { + listnode_delete(ospf->areas, area); + ospf_area_free(area); + } + + LSDB_LOOP (OPAQUE_AS_LSDB(ospf), rn, lsa) + ospf_discard_from_db(ospf, ospf->lsdb, lsa); + LSDB_LOOP (EXTERNAL_LSDB(ospf), rn, lsa) + ospf_discard_from_db(ospf, ospf->lsdb, lsa); + + ospf_lsdb_delete_all(ospf->lsdb); + ospf_lsdb_free(ospf->lsdb); + + for (rn = route_top(ospf->maxage_lsa); rn; rn = route_next(rn)) { + if ((lsa = rn->info) != NULL) { + ospf_lsa_unlock(&lsa); + rn->info = NULL; + route_unlock_node(rn); + } + } + route_table_finish(ospf->maxage_lsa); + + if (ospf->old_table) + ospf_route_table_free(ospf->old_table); + if (ospf->new_table) { + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->new_table); + ospf_route_table_free(ospf->new_table); + } + if (ospf->oall_rtrs) + ospf_rtrs_free(ospf->oall_rtrs); + if (ospf->all_rtrs) + ospf_rtrs_free(ospf->all_rtrs); + if (ospf->old_rtrs) + ospf_rtrs_free(ospf->old_rtrs); + if (ospf->new_rtrs) + ospf_rtrs_free(ospf->new_rtrs); + if (ospf->new_external_route) { + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->new_external_route); + ospf_route_table_free(ospf->new_external_route); + } + if (ospf->old_external_route) { + if (!ospf->gr_info.prepare_in_progress) + ospf_route_delete(ospf, ospf->old_external_route); + ospf_route_table_free(ospf->old_external_route); + } + if (ospf->external_lsas) { + ospf_ase_external_lsas_finish(ospf->external_lsas); + } + + for (i = ZEBRA_ROUTE_SYSTEM; i <= ZEBRA_ROUTE_MAX; i++) { + struct list *ext_list; + struct ospf_external *ext; + + ext_list = ospf->external[i]; + if (!ext_list) + continue; + + for (ALL_LIST_ELEMENTS(ext_list, node, nnode, ext)) { + if (ext->external_info) + for (rn = route_top(ext->external_info); rn; + rn = route_next(rn)) { + if (rn->info == NULL) + continue; + + XFREE(MTYPE_OSPF_EXTERNAL_INFO, + rn->info); + rn->info = NULL; + route_unlock_node(rn); + } + + ospf_external_del(ospf, i, ext->instance); + } + } + + ospf_distance_reset(ospf); + route_table_finish(ospf->distance_table); + + /* Release extrenal Aggregator table */ + for (rn = route_top(ospf->rt_aggr_tbl); rn; rn = route_next(rn)) { + struct ospf_external_aggr_rt *aggr; + + aggr = rn->info; + + if (aggr) { + ospf_external_aggregator_free(aggr); + rn->info = NULL; + route_unlock_node(rn); + } + } + + /* Cancel all timers. */ + EVENT_OFF(ospf->t_read); + EVENT_OFF(ospf->t_write); + EVENT_OFF(ospf->t_spf_calc); + EVENT_OFF(ospf->t_ase_calc); + EVENT_OFF(ospf->t_maxage); + EVENT_OFF(ospf->t_maxage_walker); + EVENT_OFF(ospf->t_deferred_shutdown); + EVENT_OFF(ospf->t_abr_task); + EVENT_OFF(ospf->t_abr_fr); + EVENT_OFF(ospf->t_asbr_check); + EVENT_OFF(ospf->t_asbr_redist_update); + EVENT_OFF(ospf->t_distribute_update); + EVENT_OFF(ospf->t_lsa_refresher); + EVENT_OFF(ospf->t_opaque_lsa_self); + EVENT_OFF(ospf->t_sr_update); + EVENT_OFF(ospf->t_default_routemap_timer); + EVENT_OFF(ospf->t_external_aggr); + EVENT_OFF(ospf->gr_info.t_grace_period); + + route_table_finish(ospf->rt_aggr_tbl); + + ospf_free_refresh_queue(ospf); + + list_delete(&ospf->areas); + list_delete(&ospf->oi_write_q); + + /* Reset GR helper data structers */ + ospf_gr_helper_instance_stop(ospf); + + close(ospf->fd); + stream_free(ospf->ibuf); + ospf->fd = -1; + ospf->max_multipath = MULTIPATH_NUM; + ospf_delete(ospf); + + if (vrf) + ospf_vrf_unlink(ospf, vrf); + + XFREE(MTYPE_OSPF_TOP, ospf->name); + XFREE(MTYPE_OSPF_TOP, ospf); +} + +static void ospf_range_table_node_destroy(route_table_delegate_t *delegate, + struct route_table *table, struct route_node *node) +{ + XFREE(MTYPE_OSPF_AREA_RANGE, node->info); + XFREE(MTYPE_ROUTE_NODE, node); +} + +route_table_delegate_t ospf_range_table_delegate = {.create_node = route_node_create, + .destroy_node = ospf_range_table_node_destroy}; + +/* allocate new OSPF Area object */ +struct ospf_area *ospf_area_new(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *new; + + /* Allocate new config_network. */ + new = XCALLOC(MTYPE_OSPF_AREA, sizeof(struct ospf_area)); + + new->ospf = ospf; + + new->area_id = area_id; + new->area_id_fmt = OSPF_AREA_ID_FMT_DOTTEDQUAD; + + new->external_routing = OSPF_AREA_DEFAULT; + new->default_cost = 1; + new->auth_type = OSPF_AUTH_NULL; + + /* New LSDB init. */ + new->lsdb = ospf_lsdb_new(); + + /* Self-originated LSAs initialize. */ + new->router_lsa_self = NULL; + + /* Initialize FR field */ + new->fr_info.enabled = false; + new->fr_info.configured = false; + new->fr_info.state_changed = false; + new->fr_info.router_lsas_recv_dc_bit = 0; + new->fr_info.indication_lsa_self = NULL; + new->fr_info.area_ind_lsa_recvd = false; + new->fr_info.area_dc_clear = false; + + ospf_opaque_type10_lsa_init(new); + + new->oiflist = list_new(); + new->ranges = route_table_init_with_delegate(&ospf_range_table_delegate); + new->nssa_ranges = route_table_init_with_delegate(&ospf_range_table_delegate); + + if (area_id.s_addr == OSPF_AREA_BACKBONE) + ospf->backbone = new; + + return new; +} + +void ospf_area_lsdb_discard_delete(struct ospf_area *area) +{ + struct route_node *rn; + struct ospf_lsa *lsa; + + LSDB_LOOP (ROUTER_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + LSDB_LOOP (NETWORK_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + LSDB_LOOP (SUMMARY_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + LSDB_LOOP (ASBR_SUMMARY_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + + LSDB_LOOP (NSSA_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + LSDB_LOOP (OPAQUE_AREA_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + LSDB_LOOP (OPAQUE_LINK_LSDB(area), rn, lsa) + ospf_discard_from_db(area->ospf, area->lsdb, lsa); + + ospf_lsdb_delete_all(area->lsdb); +} + +static void ospf_area_free(struct ospf_area *area) +{ + ospf_opaque_type10_lsa_term(area); + + /* Free LSDBs. */ + ospf_area_lsdb_discard_delete(area); + + ospf_lsdb_free(area->lsdb); + + ospf_lsa_unlock(&area->router_lsa_self); + + route_table_finish(area->ranges); + route_table_finish(area->nssa_ranges); + list_delete(&area->oiflist); + + if (EXPORT_NAME(area)) + free(EXPORT_NAME(area)); + + if (IMPORT_NAME(area)) + free(IMPORT_NAME(area)); + + /* Cancel timer. */ + EVENT_OFF(area->t_stub_router); + EVENT_OFF(area->t_opaque_lsa_self); + + if (OSPF_IS_AREA_BACKBONE(area)) + area->ospf->backbone = NULL; + + XFREE(MTYPE_OSPF_AREA, area); +} + +void ospf_area_check_free(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area && listcount(area->oiflist) == 0 && + area->ranges->top == NULL && area->nssa_ranges->top == NULL && + !ospf_vl_count(ospf, area) && + area->shortcut_configured == OSPF_SHORTCUT_DEFAULT && + area->external_routing == OSPF_AREA_DEFAULT && + area->no_summary == 0 && area->default_cost == 1 && + EXPORT_NAME(area) == NULL && IMPORT_NAME(area) == NULL && + area->auth_type == OSPF_AUTH_NULL) { + listnode_delete(ospf->areas, area); + ospf_area_free(area); + } +} + +struct ospf_area *ospf_area_get(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (!area) { + area = ospf_area_new(ospf, area_id); + listnode_add_sort(ospf->areas, area); + ospf_check_abr_status(ospf); + if (ospf->stub_router_admin_set + == OSPF_STUB_ROUTER_ADMINISTRATIVE_SET) { + SET_FLAG(area->stub_router_state, + OSPF_AREA_ADMIN_STUB_ROUTED); + } + } + + return area; +} + +struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *ospf, + struct in_addr area_id) +{ + struct ospf_area *area; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(ospf->areas, node, area)) + if (IPV4_ADDR_SAME(&area->area_id, &area_id)) + return area; + + return NULL; +} + +void ospf_area_add_if(struct ospf_area *area, struct ospf_interface *oi) +{ + listnode_add(area->oiflist, oi); +} + +void ospf_area_del_if(struct ospf_area *area, struct ospf_interface *oi) +{ + listnode_delete(area->oiflist, oi); +} + + +struct ospf_interface *add_ospf_interface(struct connected *co, + struct ospf_area *area) +{ + struct ospf_interface *oi; + + oi = ospf_if_new(area->ospf, co->ifp, co->address); + oi->connected = co; + + oi->area = area; + + oi->params = ospf_lookup_if_params(co->ifp, oi->address->u.prefix4); + oi->output_cost = ospf_if_get_output_cost(oi); + + /* Relate ospf interface to ospf instance. */ + oi->ospf = area->ospf; + + /* update network type as interface flag */ + /* If network type is specified previously, + skip network type setting. */ + oi->type = IF_DEF_PARAMS(co->ifp)->type; + oi->ptp_dmvpn = IF_DEF_PARAMS(co->ifp)->ptp_dmvpn; + oi->p2mp_delay_reflood = IF_DEF_PARAMS(co->ifp)->p2mp_delay_reflood; + oi->p2mp_non_broadcast = IF_DEF_PARAMS(co->ifp)->p2mp_non_broadcast; + + /* Add pseudo neighbor. */ + ospf_nbr_self_reset(oi, oi->ospf->router_id); + + ospf_area_add_if(oi->area, oi); + + /* if LDP-IGP Sync is configured globally inherit config */ + ospf_ldp_sync_if_init(oi); + + /* + * if router_id is not configured, don't bring up + * interfaces. + * ospf_router_id_update() will call ospf_if_update + * whenever r-id is configured instead. + */ + if ((area->ospf->router_id.s_addr != INADDR_ANY) + && if_is_operative(co->ifp)) + ospf_if_up(oi); + + /* + * RFC 3623 - Section 5 ("Unplanned Outages"): + * "The grace-LSAs are encapsulated in Link State Update Packets + * and sent out to all interfaces, even though the restarted + * router has no adjacencies and no knowledge of previous + * adjacencies". + */ + if (oi->ospf->gr_info.restart_in_progress && + oi->ospf->gr_info.reason == OSPF_GR_UNKNOWN_RESTART) + ospf_gr_unplanned_start_interface(oi); + + return oi; +} + +static void update_redistributed(struct ospf *ospf, int add_to_ospf) +{ + struct route_node *rn; + struct external_info *ei; + struct ospf_external *ext; + + if (ospf_is_type_redistributed(ospf, ZEBRA_ROUTE_CONNECT, 0)) { + ext = ospf_external_lookup(ospf, ZEBRA_ROUTE_CONNECT, 0); + if ((ext) && EXTERNAL_INFO(ext)) { + for (rn = route_top(EXTERNAL_INFO(ext)); rn; + rn = route_next(rn)) { + ei = rn->info; + if (ei == NULL) + continue; + + if (add_to_ospf) { + if (ospf_external_info_find_lsa(ospf, + &ei->p)) + if (!ospf_redistribute_check( + ospf, ei, NULL)) + ospf_external_lsa_flush( + ospf, ei->type, + &ei->p, + ei->ifindex /*, ei->nexthop */); + } else { + if (!ospf_external_info_find_lsa( + ospf, &ei->p)) + if (ospf_redistribute_check( + ospf, ei, NULL)) + ospf_external_lsa_originate( + ospf, ei); + } + } + } + } +} + +/* Config network statement related functions. */ +static struct ospf_network *ospf_network_new(struct in_addr area_id) +{ + struct ospf_network *new; + new = XCALLOC(MTYPE_OSPF_NETWORK, sizeof(struct ospf_network)); + + new->area_id = area_id; + new->area_id_fmt = OSPF_AREA_ID_FMT_DOTTEDQUAD; + + return new; +} + +static void ospf_network_free(struct ospf *ospf, struct ospf_network *network) +{ + ospf_area_check_free(ospf, network->area_id); + ospf_schedule_abr_task(ospf); + XFREE(MTYPE_OSPF_NETWORK, network); +} + +int ospf_network_set(struct ospf *ospf, struct prefix_ipv4 *p, + struct in_addr area_id, int df) +{ + struct ospf_network *network; + struct ospf_area *area; + struct route_node *rn; + + rn = route_node_get(ospf->networks, (struct prefix *)p); + if (rn->info) { + network = rn->info; + route_unlock_node(rn); + + if (IPV4_ADDR_SAME(&area_id, &network->area_id)) { + return 1; + } else { + /* There is already same network statement. */ + return 0; + } + } + + rn->info = network = ospf_network_new(area_id); + network->area_id_fmt = df; + area = ospf_area_get(ospf, area_id); + ospf_area_display_format_set(ospf, area, df); + + /* Run network config now. */ + ospf_network_run((struct prefix *)p, area); + + /* Update connected redistribute. */ + update_redistributed(ospf, 1); /* interfaces possibly added */ + + ospf_area_check_free(ospf, area_id); + + return 1; +} + +int ospf_network_unset(struct ospf *ospf, struct prefix_ipv4 *p, + struct in_addr area_id) +{ + struct route_node *rn; + struct ospf_network *network; + struct listnode *node; + struct ospf_interface *oi; + struct list *ospf_oiflist = NULL; + + rn = route_node_lookup(ospf->networks, (struct prefix *)p); + if (rn == NULL) + return 0; + + network = rn->info; + route_unlock_node(rn); + if (!IPV4_ADDR_SAME(&area_id, &network->area_id)) + return 0; + + ospf_network_free(ospf, rn->info); + rn->info = NULL; + route_unlock_node(rn); /* initial reference */ + + ospf_oiflist = list_dup(ospf->oiflist); + /* Find interfaces that are not configured already. */ + for (ALL_LIST_ELEMENTS_RO(ospf_oiflist, node, oi)) { + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + ospf_network_run_subnet(ospf, oi->connected, NULL, NULL); + } + + list_delete(&ospf_oiflist); + + /* Update connected redistribute. */ + update_redistributed(ospf, 0); /* interfaces possibly removed */ + ospf_area_check_free(ospf, area_id); + + return 1; +} + + +/* Ensure there's an OSPF instance, as "ip ospf area" enabled OSPF means + * there might not be any 'router ospf' config. + * + * Otherwise, doesn't do anything different to ospf_if_update for now + */ +void ospf_interface_area_set(struct ospf *ospf, struct interface *ifp) +{ + if (!ospf) + return; + + ospf_if_update(ospf, ifp); + /* if_update does a update_redistributed */ + + return; +} + +void ospf_interface_area_unset(struct ospf *ospf, struct interface *ifp) +{ + struct route_node *rn_oi; + + if (!ospf) + return; /* Ospf not ready yet */ + + /* Find interfaces that may need to be removed. */ + for (rn_oi = route_top(IF_OIFS(ifp)); rn_oi; + rn_oi = route_next(rn_oi)) { + struct ospf_interface *oi = NULL; + + if ((oi = rn_oi->info) == NULL) + continue; + + if (oi->type == OSPF_IFTYPE_VIRTUALLINK) + continue; + + ospf_network_run_subnet(ospf, oi->connected, NULL, NULL); + } + + /* Update connected redistribute. */ + update_redistributed(ospf, 0); /* interfaces possibly removed */ +} + +/* Check whether interface matches given network + * returns: 1, true. 0, false + */ +static int ospf_network_match_iface(const struct connected *co, + const struct prefix *net) +{ + /* new approach: more elegant and conceptually clean */ + return prefix_match_network_statement(net, CONNECTED_PREFIX(co)); +} + +static void ospf_update_interface_area(struct connected *co, + struct ospf_area *area) +{ + struct ospf_interface *oi = ospf_if_table_lookup(co->ifp, co->address); + + /* nothing to be done case */ + if (oi && oi->area == area) { + return; + } + + if (oi) + ospf_if_free(oi); + + add_ospf_interface(co, area); +} + +/* Run OSPF for the given subnet, taking into account the following + * possible sources of area configuration, in the given order of preference: + * + * - Whether there is interface+address specific area configuration + * - Whether there is a default area for the interface + * - Whether there is an area given as a parameter. + * - If no specific network prefix/area is supplied, whether there's + * a matching network configured. + */ +static void ospf_network_run_subnet(struct ospf *ospf, struct connected *co, + struct prefix *p, + struct ospf_area *given_area) +{ + struct ospf_interface *oi; + struct ospf_if_params *params; + struct ospf_area *area = NULL; + struct route_node *rn; + int configed = 0; + + if (CHECK_FLAG(co->flags, ZEBRA_IFA_SECONDARY)) + return; + + if (co->address->family != AF_INET) + return; + + /* Try determine the appropriate area for this interface + address + * Start by checking interface config + */ + params = ospf_lookup_if_params(co->ifp, co->address->u.prefix4); + if (params && OSPF_IF_PARAM_CONFIGURED(params, if_area)) + area = ospf_area_get(ospf, params->if_area); + else { + params = IF_DEF_PARAMS(co->ifp); + if (OSPF_IF_PARAM_CONFIGURED(params, if_area)) + area = ospf_area_get(ospf, params->if_area); + } + + /* If we've found an interface and/or addr specific area, then we're + * done + */ + if (area) { + ospf_update_interface_area(co, area); + return; + } + + /* Otherwise, only remaining possibility is a matching network statement + */ + if (p) { + assert(given_area != NULL); + + /* Which either was supplied as a parameter.. (e.g. cause a new + * network/area was just added).. + */ + if (p->family == co->address->family + && ospf_network_match_iface(co, p)) + ospf_update_interface_area(co, given_area); + + return; + } + + /* Else we have to search the existing network/area config to see + * if any match.. + */ + for (rn = route_top(ospf->networks); rn; rn = route_next(rn)) + if (rn->info != NULL && ospf_network_match_iface(co, &rn->p)) { + struct ospf_network *network = + (struct ospf_network *)rn->info; + area = ospf_area_get(ospf, network->area_id); + ospf_update_interface_area(co, area); + configed = 1; + } + + /* If the subnet isn't in any area, deconfigure */ + if (!configed && (oi = ospf_if_table_lookup(co->ifp, co->address))) + ospf_if_free(oi); +} + +static void ospf_network_run_interface(struct ospf *ospf, struct interface *ifp, + struct prefix *p, + struct ospf_area *given_area) +{ + struct connected *co; + + if (memcmp(ifp->name, "VLINK", 5) == 0) + return; + + /* Network prefix without area is nonsensical */ + if (p) + assert(given_area != NULL); + + /* if interface prefix is match specified prefix, + then create socket and join multicast group. */ + frr_each (if_connected, ifp->connected, co) + ospf_network_run_subnet(ospf, co, p, given_area); +} + +static void ospf_network_run(struct prefix *p, struct ospf_area *area) +{ + struct vrf *vrf = vrf_lookup_by_id(area->ospf->vrf_id); + struct interface *ifp; + + /* Schedule Router ID Update. */ + if (area->ospf->router_id.s_addr == INADDR_ANY) + ospf_router_id_update(area->ospf); + + /* Get target interface. */ + FOR_ALL_INTERFACES (vrf, ifp) + ospf_network_run_interface(area->ospf, ifp, p, area); +} + +void ospf_ls_upd_queue_empty(struct ospf_interface *oi) +{ + struct route_node *rn; + struct listnode *node, *nnode; + struct list *lst; + struct ospf_lsa *lsa; + + /* empty ls update queue */ + for (rn = route_top(oi->ls_upd_queue); rn; rn = route_next(rn)) + if ((lst = (struct list *)rn->info)) { + for (ALL_LIST_ELEMENTS(lst, node, nnode, lsa)) + ospf_lsa_unlock(&lsa); /* oi->ls_upd_queue */ + list_delete(&lst); + rn->info = NULL; + } + + /* remove update event */ + EVENT_OFF(oi->t_ls_upd_event); +} + +void ospf_if_update(struct ospf *ospf, struct interface *ifp) +{ + + if (!ospf) + return; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: interface %s vrf %s(%u) ospf vrf %s vrf_id %u router_id %pI4", + __func__, ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ospf_vrf_id_to_name(ospf->vrf_id), ospf->vrf_id, + &ospf->router_id); + + /* OSPF must be ready. */ + if (!ospf_is_ready(ospf)) + return; + + ospf_network_run_interface(ospf, ifp, NULL, NULL); + + /* Update connected redistribute. */ + update_redistributed(ospf, 1); + +} + +void ospf_remove_vls_through_area(struct ospf *ospf, struct ospf_area *area) +{ + struct listnode *node, *nnode; + struct ospf_vl_data *vl_data; + + for (ALL_LIST_ELEMENTS(ospf->vlinks, node, nnode, vl_data)) + if (IPV4_ADDR_SAME(&vl_data->vl_area_id, &area->area_id)) + ospf_vl_delete(ospf, vl_data); +} + + +static const struct message ospf_area_type_msg[] = { + {OSPF_AREA_DEFAULT, "Default"}, + {OSPF_AREA_STUB, "Stub"}, + {OSPF_AREA_NSSA, "NSSA"}, + {0}}; + +static void ospf_area_type_set(struct ospf_area *area, int type) +{ + struct listnode *node; + struct ospf_interface *oi; + + if (area->external_routing == type) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Area[%pI4]: Types are the same, ignored.", + &area->area_id); + return; + } + + area->external_routing = type; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("Area[%pI4]: Configured as %s", + &area->area_id, + lookup_msg(ospf_area_type_msg, type, NULL)); + + switch (area->external_routing) { + case OSPF_AREA_DEFAULT: + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) + if (oi->nbr_self != NULL) { + UNSET_FLAG(oi->nbr_self->options, + OSPF_OPTION_NP); + SET_FLAG(oi->nbr_self->options, OSPF_OPTION_E); + } + break; + case OSPF_AREA_STUB: + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) + if (oi->nbr_self != NULL) { + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "setting options on %s accordingly", + IF_NAME(oi)); + UNSET_FLAG(oi->nbr_self->options, + OSPF_OPTION_NP); + UNSET_FLAG(oi->nbr_self->options, + OSPF_OPTION_E); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("options set on %s: %x", + IF_NAME(oi), OPTIONS(oi)); + } + break; + case OSPF_AREA_NSSA: + for (ALL_LIST_ELEMENTS_RO(area->oiflist, node, oi)) + if (oi->nbr_self != NULL) { + zlog_debug( + "setting nssa options on %s accordingly", + IF_NAME(oi)); + UNSET_FLAG(oi->nbr_self->options, + OSPF_OPTION_E); + SET_FLAG(oi->nbr_self->options, OSPF_OPTION_NP); + zlog_debug("options set on %s: %x", IF_NAME(oi), + OPTIONS(oi)); + } + break; + default: + break; + } + + ospf_router_lsa_update_area(area); + ospf_schedule_abr_task(area->ospf); +} + +int ospf_area_shortcut_set(struct ospf *ospf, struct ospf_area *area, int mode) +{ + if (area->shortcut_configured == mode) + return 0; + + area->shortcut_configured = mode; + ospf_router_lsa_update_area(area); + ospf_schedule_abr_task(ospf); + + ospf_area_check_free(ospf, area->area_id); + + return 1; +} + +int ospf_area_shortcut_unset(struct ospf *ospf, struct ospf_area *area) +{ + area->shortcut_configured = OSPF_SHORTCUT_DEFAULT; + ospf_router_lsa_update_area(area); + ospf_area_check_free(ospf, area->area_id); + ospf_schedule_abr_task(ospf); + + return 1; +} + +static int ospf_area_vlink_count(struct ospf *ospf, struct ospf_area *area) +{ + struct ospf_vl_data *vl; + struct listnode *node; + int count = 0; + + for (ALL_LIST_ELEMENTS_RO(ospf->vlinks, node, vl)) + if (IPV4_ADDR_SAME(&vl->vl_area_id, &area->area_id)) + count++; + + return count; +} + +int ospf_area_display_format_set(struct ospf *ospf, struct ospf_area *area, + int df) +{ + area->area_id_fmt = df; + + return 1; +} + +int ospf_area_stub_set(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_get(ospf, area_id); + if (ospf_area_vlink_count(ospf, area)) + return 0; + + if (area->external_routing != OSPF_AREA_STUB) + ospf_area_type_set(area, OSPF_AREA_STUB); + + return 1; +} + +int ospf_area_stub_unset(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 1; + + if (area->external_routing == OSPF_AREA_STUB) + ospf_area_type_set(area, OSPF_AREA_DEFAULT); + + ospf_area_check_free(ospf, area_id); + + return 1; +} + +int ospf_area_no_summary_set(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_get(ospf, area_id); + area->no_summary = 1; + + return 1; +} + +int ospf_area_no_summary_unset(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 0; + + area->no_summary = 0; + ospf_area_check_free(ospf, area_id); + + return 1; +} + +int ospf_area_nssa_no_summary_set(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_get(ospf, area_id); + if (ospf_area_vlink_count(ospf, area)) + return 0; + + if (area->external_routing != OSPF_AREA_NSSA) { + ospf_area_type_set(area, OSPF_AREA_NSSA); + ospf->anyNSSA++; + area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; + } + + ospf_area_no_summary_set(ospf, area_id); + + return 1; +} + +int ospf_area_nssa_set(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_get(ospf, area_id); + if (ospf_area_vlink_count(ospf, area)) + return 0; + + if (area->external_routing != OSPF_AREA_NSSA) { + ospf_area_type_set(area, OSPF_AREA_NSSA); + ospf->anyNSSA++; + + /* set NSSA area defaults */ + area->no_summary = 0; + area->suppress_fa = 0; + area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; + area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; + area->NSSATranslatorStabilityInterval = + OSPF_NSSA_TRANS_STABLE_DEFAULT; + } + return 1; +} + +int ospf_area_nssa_unset(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 0; + + ospf->anyNSSA--; + /* set NSSA area defaults */ + area->no_summary = 0; + area->suppress_fa = 0; + area->NSSATranslatorRole = OSPF_NSSA_ROLE_CANDIDATE; + area->NSSATranslatorState = OSPF_NSSA_TRANSLATE_DISABLED; + area->NSSATranslatorStabilityInterval = OSPF_NSSA_TRANS_STABLE_DEFAULT; + ospf_area_type_set(area, OSPF_AREA_DEFAULT); + ospf_area_check_free(ospf, area_id); + + return 1; +} + +int ospf_area_nssa_suppress_fa_set(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 0; + + area->suppress_fa = 1; + + return 1; +} + +int ospf_area_nssa_suppress_fa_unset(struct ospf *ospf, struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 0; + + area->suppress_fa = 0; + + return 1; +} + +int ospf_area_nssa_translator_role_set(struct ospf *ospf, + struct in_addr area_id, int role) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return 0; + + if (role != area->NSSATranslatorRole) { + if ((area->NSSATranslatorRole == OSPF_NSSA_ROLE_ALWAYS) + || (role == OSPF_NSSA_ROLE_ALWAYS)) { + /* RFC 3101 3.1 + * if new role is OSPF_NSSA_ROLE_ALWAYS we need to set + * Nt bit, if the role was OSPF_NSSA_ROLE_ALWAYS we need + * to clear Nt bit + */ + area->NSSATranslatorRole = role; + ospf_router_lsa_update_area(area); + } else + area->NSSATranslatorRole = role; + } + + return 1; +} + +void ospf_area_nssa_default_originate_set(struct ospf *ospf, + struct in_addr area_id, int metric, + int metric_type) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return; + + if (!area->nssa_default_originate.enabled) { + area->nssa_default_originate.enabled = true; + if (++ospf->nssa_default_import_check.refcnt == 1) { + ospf->nssa_default_import_check.status = false; + ospf_zebra_import_default_route(ospf, false); + } + } + + area->nssa_default_originate.metric_value = metric; + area->nssa_default_originate.metric_type = metric_type; +} + +void ospf_area_nssa_default_originate_unset(struct ospf *ospf, + struct in_addr area_id) +{ + struct ospf_area *area; + + area = ospf_area_lookup_by_area_id(ospf, area_id); + if (area == NULL) + return; + + if (area->nssa_default_originate.enabled) { + area->nssa_default_originate.enabled = false; + if (--ospf->nssa_default_import_check.refcnt == 0) { + ospf->nssa_default_import_check.status = false; + ospf_zebra_import_default_route(ospf, true); + } + area->nssa_default_originate.metric_value = -1; + area->nssa_default_originate.metric_type = -1; + + if (!IS_OSPF_ABR(ospf)) + ospf_abr_nssa_type7_defaults(ospf); + } +} + +int ospf_area_export_list_set(struct ospf *ospf, struct ospf_area *area, + const char *list_name) +{ + struct access_list *list; + list = access_list_lookup(AFI_IP, list_name); + + EXPORT_LIST(area) = list; + + if (EXPORT_NAME(area)) + free(EXPORT_NAME(area)); + + EXPORT_NAME(area) = strdup(list_name); + ospf_schedule_abr_task(ospf); + + return 1; +} + +int ospf_area_export_list_unset(struct ospf *ospf, struct ospf_area *area) +{ + + EXPORT_LIST(area) = 0; + + if (EXPORT_NAME(area)) + free(EXPORT_NAME(area)); + + EXPORT_NAME(area) = NULL; + + ospf_area_check_free(ospf, area->area_id); + + ospf_schedule_abr_task(ospf); + + return 1; +} + +int ospf_area_import_list_set(struct ospf *ospf, struct ospf_area *area, + const char *name) +{ + struct access_list *list; + list = access_list_lookup(AFI_IP, name); + + IMPORT_LIST(area) = list; + + if (IMPORT_NAME(area)) + free(IMPORT_NAME(area)); + + IMPORT_NAME(area) = strdup(name); + ospf_schedule_abr_task(ospf); + + return 1; +} + +int ospf_area_import_list_unset(struct ospf *ospf, struct ospf_area *area) +{ + IMPORT_LIST(area) = 0; + + if (IMPORT_NAME(area)) + free(IMPORT_NAME(area)); + + IMPORT_NAME(area) = NULL; + ospf_area_check_free(ospf, area->area_id); + + ospf_schedule_abr_task(ospf); + + return 1; +} + +int ospf_timers_refresh_set(struct ospf *ospf, int interval) +{ + int time_left; + + if (ospf->lsa_refresh_interval == interval) + return 1; + + time_left = ospf->lsa_refresh_interval + - (monotime(NULL) - ospf->lsa_refresher_started); + + if (time_left > interval) { + EVENT_OFF(ospf->t_lsa_refresher); + event_add_timer(master, ospf_lsa_refresh_walker, ospf, interval, + &ospf->t_lsa_refresher); + } + ospf->lsa_refresh_interval = interval; + + return 1; +} + +int ospf_timers_refresh_unset(struct ospf *ospf) +{ + int time_left; + + time_left = ospf->lsa_refresh_interval + - (monotime(NULL) - ospf->lsa_refresher_started); + + if (time_left > OSPF_LSA_REFRESH_INTERVAL_DEFAULT) { + EVENT_OFF(ospf->t_lsa_refresher); + ospf->t_lsa_refresher = NULL; + event_add_timer(master, ospf_lsa_refresh_walker, ospf, + OSPF_LSA_REFRESH_INTERVAL_DEFAULT, + &ospf->t_lsa_refresher); + } + + ospf->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT; + + return 1; +} + + +static struct ospf_nbr_nbma *ospf_nbr_nbma_new(void) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = XCALLOC(MTYPE_OSPF_NEIGHBOR_STATIC, + sizeof(struct ospf_nbr_nbma)); + + nbr_nbma->priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT; + nbr_nbma->v_poll = OSPF_POLL_INTERVAL_DEFAULT; + + return nbr_nbma; +} + +static void ospf_nbr_nbma_free(struct ospf_nbr_nbma *nbr_nbma) +{ + XFREE(MTYPE_OSPF_NEIGHBOR_STATIC, nbr_nbma); +} + +static void ospf_nbr_nbma_delete(struct ospf *ospf, + struct ospf_nbr_nbma *nbr_nbma) +{ + struct route_node *rn; + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefix = nbr_nbma->addr; + p.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_lookup(ospf->nbr_nbma, (struct prefix *)&p); + if (rn) { + ospf_nbr_nbma_free(rn->info); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } +} + +static void ospf_nbr_nbma_down(struct ospf_nbr_nbma *nbr_nbma) +{ + EVENT_OFF(nbr_nbma->t_poll); + + if (nbr_nbma->nbr) { + nbr_nbma->nbr->nbr_nbma = NULL; + OSPF_NSM_EVENT_EXECUTE(nbr_nbma->nbr, NSM_KillNbr); + } + + if (nbr_nbma->oi) + listnode_delete(nbr_nbma->oi->nbr_nbma, nbr_nbma); +} + +static void ospf_nbr_nbma_add(struct ospf_nbr_nbma *nbr_nbma, + struct ospf_interface *oi) +{ + struct ospf_neighbor *nbr; + struct route_node *rn; + struct prefix p; + + if (!OSPF_IF_NON_BROADCAST(oi)) + return; + + if (nbr_nbma->nbr != NULL) + return; + + if (IPV4_ADDR_SAME(&oi->nbr_self->address.u.prefix4, &nbr_nbma->addr)) + return; + + nbr_nbma->oi = oi; + listnode_add(oi->nbr_nbma, nbr_nbma); + + /* Get neighbor information from table. */ + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = nbr_nbma->addr; + + rn = route_node_get(oi->nbrs, &p); + if (rn->info) { + nbr = rn->info; + nbr->nbr_nbma = nbr_nbma; + nbr_nbma->nbr = nbr; + + route_unlock_node(rn); + } else { + nbr = rn->info = ospf_nbr_new(oi); + nbr->state = NSM_Down; + nbr->src = nbr_nbma->addr; + nbr->nbr_nbma = nbr_nbma; + nbr->priority = nbr_nbma->priority; + nbr->address = p; + + nbr_nbma->nbr = nbr; + + /* Configure BFD if interface has it. */ + ospf_neighbor_bfd_apply(nbr); + + OSPF_NSM_EVENT_EXECUTE(nbr, NSM_Start); + } +} + +void ospf_nbr_nbma_if_update(struct ospf *ospf, struct ospf_interface *oi) +{ + struct ospf_nbr_nbma *nbr_nbma; + struct route_node *rn; + struct prefix_ipv4 p; + + if (!OSPF_IF_NON_BROADCAST(oi)) + return; + + for (rn = route_top(ospf->nbr_nbma); rn; rn = route_next(rn)) + if ((nbr_nbma = rn->info)) + if (nbr_nbma->oi == NULL && nbr_nbma->nbr == NULL) { + p.family = AF_INET; + p.prefix = nbr_nbma->addr; + p.prefixlen = IPV4_MAX_BITLEN; + + if (prefix_match(oi->address, + (struct prefix *)&p)) + ospf_nbr_nbma_add(nbr_nbma, oi); + } +} + +struct ospf_nbr_nbma *ospf_nbr_nbma_lookup(struct ospf *ospf, + struct in_addr nbr_addr) +{ + struct route_node *rn; + struct prefix_ipv4 p; + + p.family = AF_INET; + p.prefix = nbr_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_lookup(ospf->nbr_nbma, (struct prefix *)&p); + if (rn) { + route_unlock_node(rn); + return rn->info; + } + return NULL; +} + +int ospf_nbr_nbma_set(struct ospf *ospf, struct in_addr nbr_addr) +{ + struct ospf_nbr_nbma *nbr_nbma; + struct ospf_interface *oi; + struct prefix_ipv4 p; + struct route_node *rn; + struct listnode *node; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); + if (nbr_nbma) + return 0; + + nbr_nbma = ospf_nbr_nbma_new(); + nbr_nbma->addr = nbr_addr; + + p.family = AF_INET; + p.prefix = nbr_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + rn = route_node_get(ospf->nbr_nbma, (struct prefix *)&p); + if (rn->info) + route_unlock_node(rn); + rn->info = nbr_nbma; + + for (ALL_LIST_ELEMENTS_RO(ospf->oiflist, node, oi)) { + if (OSPF_IF_NON_BROADCAST(oi)) + if (prefix_match(oi->address, (struct prefix *)&p)) { + ospf_nbr_nbma_add(nbr_nbma, oi); + break; + } + } + + return 1; +} + +int ospf_nbr_nbma_unset(struct ospf *ospf, struct in_addr nbr_addr) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); + if (nbr_nbma == NULL) + return 0; + + ospf_nbr_nbma_down(nbr_nbma); + ospf_nbr_nbma_delete(ospf, nbr_nbma); + + return 1; +} + +int ospf_nbr_nbma_priority_set(struct ospf *ospf, struct in_addr nbr_addr, + uint8_t priority) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); + if (nbr_nbma == NULL) + return 0; + + if (nbr_nbma->priority != priority) + nbr_nbma->priority = priority; + + return 1; +} + +int ospf_nbr_nbma_priority_unset(struct ospf *ospf, struct in_addr nbr_addr) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); + if (nbr_nbma == NULL) + return 0; + + if (nbr_nbma != OSPF_NEIGHBOR_PRIORITY_DEFAULT) + nbr_nbma->priority = OSPF_NEIGHBOR_PRIORITY_DEFAULT; + + return 1; +} + +int ospf_nbr_nbma_poll_interval_set(struct ospf *ospf, struct in_addr nbr_addr, + unsigned int interval) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, nbr_addr); + if (nbr_nbma == NULL) + return 0; + + if (nbr_nbma->v_poll != interval) { + nbr_nbma->v_poll = interval; + if (nbr_nbma->oi && ospf_if_is_up(nbr_nbma->oi)) { + EVENT_OFF(nbr_nbma->t_poll); + OSPF_POLL_TIMER_ON(nbr_nbma->t_poll, ospf_poll_timer, + nbr_nbma->v_poll); + } + } + + return 1; +} + +int ospf_nbr_nbma_poll_interval_unset(struct ospf *ospf, struct in_addr addr) +{ + struct ospf_nbr_nbma *nbr_nbma; + + nbr_nbma = ospf_nbr_nbma_lookup(ospf, addr); + if (nbr_nbma == NULL) + return 0; + + if (nbr_nbma->v_poll != OSPF_POLL_INTERVAL_DEFAULT) + nbr_nbma->v_poll = OSPF_POLL_INTERVAL_DEFAULT; + + return 1; +} + +/* + * Update socket bufsize(s), usually after config change + */ +void ospf_update_bufsize(struct ospf *ospf, uint32_t recvsize, + uint32_t sendsize) +{ + enum ospf_sock_type_e type = OSPF_SOCK_NONE; + + /* Figure out whether there's been a change */ + if (recvsize != ospf->recv_sock_bufsize) { + type = OSPF_SOCK_RECV; + ospf->recv_sock_bufsize = recvsize; + + if (sendsize != ospf->send_sock_bufsize) { + type = OSPF_SOCK_BOTH; + ospf->send_sock_bufsize = sendsize; + } + } else if (sendsize != ospf->send_sock_bufsize) { + type = OSPF_SOCK_SEND; + ospf->send_sock_bufsize = sendsize; + } + + if (type != OSPF_SOCK_NONE) + ospf_sock_bufsize_update(ospf, ospf->fd, type); +} + +void ospf_master_init(struct event_loop *master) +{ + memset(&ospf_master, 0, sizeof(ospf_master)); + + om = &ospf_master; + om->ospf = list_new(); + om->master = master; +} + +/* Link OSPF instance to VRF. */ +void ospf_vrf_link(struct ospf *ospf, struct vrf *vrf) +{ + ospf->vrf_id = vrf->vrf_id; + if (vrf->info != (void *)ospf) + vrf->info = (void *)ospf; +} + +/* Unlink OSPF instance from VRF. */ +void ospf_vrf_unlink(struct ospf *ospf, struct vrf *vrf) +{ + if (vrf->info == (void *)ospf) + vrf->info = NULL; + ospf->vrf_id = VRF_UNKNOWN; +} + +/* This is hook function for vrf create called as part of vrf_init */ +static int ospf_vrf_new(struct vrf *vrf) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: VRF Created: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +/* This is hook function for vrf delete call as part of vrf_init */ +static int ospf_vrf_delete(struct vrf *vrf) +{ + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: VRF Deletion: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static void ospf_set_redist_vrf_bitmaps(struct ospf *ospf, bool set) +{ + int type; + struct list *red_list; + + for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red_list = ospf->redist[type]; + if (!red_list) + continue; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: setting redist vrf %d bitmap for type %d", + __func__, ospf->vrf_id, type); + if (set) + vrf_bitmap_set(&zclient->redist[AFI_IP][type], + ospf->vrf_id); + else + vrf_bitmap_unset(&zclient->redist[AFI_IP][type], + ospf->vrf_id); + } + + red_list = ospf->redist[DEFAULT_ROUTE]; + if (red_list) { + if (set) + vrf_bitmap_set(&zclient->default_information[AFI_IP], + ospf->vrf_id); + else + vrf_bitmap_unset(&zclient->default_information[AFI_IP], + ospf->vrf_id); + } +} + +/* Enable OSPF VRF instance */ +static int ospf_vrf_enable(struct vrf *vrf) +{ + struct ospf *ospf = NULL; + vrf_id_t old_vrf_id; + int ret = 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: VRF %s id %u enabled", __func__, vrf->name, + vrf->vrf_id); + + ospf = ospf_lookup_by_name(vrf->name); + if (ospf) { + old_vrf_id = ospf->vrf_id; + /* We have instance configured, link to VRF and make it "up". */ + ospf_vrf_link(ospf, vrf); + if (IS_DEBUG_OSPF_EVENT) + zlog_debug( + "%s: ospf linked to vrf %s vrf_id %u (old id %u)", + __func__, vrf->name, ospf->vrf_id, old_vrf_id); + + if (old_vrf_id != ospf->vrf_id) { + ospf_set_redist_vrf_bitmaps(ospf, true); + + /* start zebra redist to us for new vrf */ + ospf_zebra_vrf_register(ospf); + + ret = ospf_sock_init(ospf); + if (ret < 0 || ospf->fd <= 0) + return 0; + event_add_read(master, ospf_read, ospf, ospf->fd, + &ospf->t_read); + ospf->oi_running = 1; + ospf_router_id_update(ospf); + } + } + + return 0; +} + +/* Disable OSPF VRF instance */ +static int ospf_vrf_disable(struct vrf *vrf) +{ + struct ospf *ospf = NULL; + vrf_id_t old_vrf_id = VRF_UNKNOWN; + + if (vrf->vrf_id == VRF_DEFAULT) + return 0; + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: VRF %s id %d disabled.", __func__, vrf->name, + vrf->vrf_id); + + ospf = ospf_lookup_by_name(vrf->name); + if (ospf) { + old_vrf_id = ospf->vrf_id; + + ospf_zebra_vrf_deregister(ospf); + + ospf_set_redist_vrf_bitmaps(ospf, false); + + /* We have instance configured, unlink + * from VRF and make it "down". + */ + ospf_vrf_unlink(ospf, vrf); + ospf->oi_running = 0; + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: ospf old_vrf_id %d unlinked", __func__, + old_vrf_id); + EVENT_OFF(ospf->t_read); + close(ospf->fd); + ospf->fd = -1; + } + + /* Note: This is a callback, the VRF will be deleted by the caller. */ + return 0; +} + +void ospf_vrf_init(void) +{ + vrf_init(ospf_vrf_new, ospf_vrf_enable, ospf_vrf_disable, + ospf_vrf_delete); +} + +void ospf_vrf_terminate(void) +{ + vrf_terminate(); +} + +const char *ospf_vrf_id_to_name(vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + return vrf ? vrf->name : "NIL"; +} + +const char *ospf_get_name(const struct ospf *ospf) +{ + if (ospf->name) + return ospf->name; + else + return VRF_DEFAULT_NAME; +} diff --git a/ospfd/ospfd.h b/ospfd/ospfd.h new file mode 100644 index 0000000..6051dff --- /dev/null +++ b/ospfd/ospfd.h @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSPFd main header. + * Copyright (C) 1998, 99, 2000 Kunihiro Ishiguro, Toshiaki Takada + */ + +#ifndef _ZEBRA_OSPFD_H +#define _ZEBRA_OSPFD_H + +#include +#include "typesafe.h" +#include "qobj.h" +#include "libospf.h" +#include "ldp_sync.h" + +#include "filter.h" +#include "log.h" +#include "vrf.h" + +#include "ospf_memory.h" +#include "ospf_dump_api.h" + +#define OSPF_VERSION 2 + +/* IP TTL for OSPF protocol. */ +#define OSPF_IP_TTL 1 +#define OSPF_VL_IP_TTL 100 + +/* Default configuration file name for ospfd. */ +#define OSPF_DEFAULT_CONFIG "ospfd.conf" + +#define OSPF_NSSA_TRANS_STABLE_DEFAULT 40 + +#define OSPF_ALLSPFROUTERS 0xe0000005 /* 224.0.0.5 */ +#define OSPF_ALLDROUTERS 0xe0000006 /* 224.0.0.6 */ + +/* OSPF Authentication Type. */ +#define OSPF_AUTH_NULL 0 +#define OSPF_AUTH_SIMPLE 1 +#define OSPF_AUTH_CRYPTOGRAPHIC 2 +/* For Interface authentication setting default */ +#define OSPF_AUTH_NOTSET -1 +/* For the consumption and sanity of the command handler */ +/* DO NIOT REMOVE!!! Need to detect whether a value has + been given or not in VLink command handlers */ +#define OSPF_AUTH_CMD_NOTSEEN -2 + +/* OSPF options. */ +#define OSPF_OPTION_MT 0x01 /* M/T */ +#define OSPF_OPTION_E 0x02 +#define OSPF_OPTION_MC 0x04 +#define OSPF_OPTION_NP 0x08 +#define OSPF_OPTION_EA 0x10 +#define OSPF_OPTION_DC 0x20 +#define OSPF_OPTION_O 0x40 +#define OSPF_OPTION_DN 0x80 + +/* OSPF Database Description flags. */ +#define OSPF_DD_FLAG_MS 0x01 +#define OSPF_DD_FLAG_M 0x02 +#define OSPF_DD_FLAG_I 0x04 +#define OSPF_DD_FLAG_ALL 0x07 + +#define OSPF_LS_REFRESH_SHIFT (60 * 15) +#define OSPF_LS_REFRESH_JITTER 60 + +/* Default socket buffer size */ +#define OSPF_DEFAULT_SOCK_BUFSIZE (8 * 1024 * 1024) + +/* OSPF config processing timer thread */ +extern struct event *t_ospf_cfg; + +struct ospf_external { + unsigned short instance; + struct route_table *external_info; +}; + +/* OSPF master for system wide configuration and variables. */ +struct ospf_master { + /* OSPF instance. */ + struct list *ospf; + + /* OSPF thread master. */ + struct event_loop *master; + + /* Various OSPF global configuration. */ + uint8_t options; +#define OSPF_MASTER_SHUTDOWN (1 << 0) /* deferred-shutdown */ +}; + +struct ospf_redist { + unsigned short instance; + + /* Redistribute metric info. */ + struct { + int type; /* External metric type (E1 or E2). */ + int value; /* Value for static metric (24-bit). + -1 means metric value is not set. */ + } dmetric; + + /* For redistribute route map. */ + struct { + char *name; + struct route_map *map; + } route_map; /* +1 is for default-information */ +#define ROUTEMAP_NAME(R) (R->route_map.name) +#define ROUTEMAP(R) (R->route_map.map) +}; + +/* OSPF area flood reduction info */ +struct ospf_area_fr_info { + bool enabled; /* Area support for Flood Reduction */ + bool configured; /* Flood Reduction configured per area knob */ + bool state_changed; /* flood reduction state change info */ + int router_lsas_recv_dc_bit; /* Number of unique router lsas + * received with DC bit set. + * (excluding self) + */ + bool area_ind_lsa_recvd; /* Indication lsa received in this area */ + bool area_dc_clear; /* Area has atleast one lsa with dc bit 0( + * excluding indication lsa) + */ + struct ospf_lsa *indication_lsa_self; /* Indication LSA generated + * in the area. + */ +}; + +/* ospf->config */ +enum { + OSPF_RFC1583_COMPATIBLE = (1 << 0), + OSPF_OPAQUE_CAPABLE = (1 << 2), + OSPF_LOG_ADJACENCY_CHANGES = (1 << 3), + OSPF_LOG_ADJACENCY_DETAIL = (1 << 4), + OSPF_SEND_EXTRA_DATA_TO_ZEBRA = (1 << 5), +}; + +/* TI-LFA */ +enum protection_type { + OSPF_TI_LFA_UNDEFINED_PROTECTION, + OSPF_TI_LFA_LINK_PROTECTION, + OSPF_TI_LFA_NODE_PROTECTION, +}; + +/* OSPF nonstop forwarding aka Graceful Restart */ +struct ospf_gr_info { + bool restart_support; + bool restart_in_progress; + bool prepare_in_progress; + bool finishing_restart; + uint32_t grace_period; + int reason; + char *exit_reason; + struct event *t_grace_period; +}; + +/* OSPF instance structure. */ +struct ospf { + /* OSPF's running state based on the '[no] router ospf []' + * config. */ + uint8_t oi_running; + + /* OSPF instance ID */ + unsigned short instance; + + /* OSPF Router ID. */ + struct in_addr router_id; /* Configured automatically. */ + struct in_addr router_id_static; /* Configured manually. */ + struct in_addr router_id_zebra; + + vrf_id_t vrf_id; /* VRF Id */ + char *name; /* VRF name */ + + /* ABR/ASBR internal flags. */ + uint8_t flags; +#define OSPF_FLAG_ABR 0x0001 +#define OSPF_FLAG_ASBR 0x0002 + + /* ABR type. */ + uint8_t abr_type; +#define OSPF_ABR_UNKNOWN 0 +#define OSPF_ABR_STAND 1 +#define OSPF_ABR_IBM 2 +#define OSPF_ABR_CISCO 3 +#define OSPF_ABR_SHORTCUT 4 +#define OSPF_ABR_DEFAULT OSPF_ABR_CISCO + + /* NSSA ABR */ + uint8_t anyNSSA; /* Bump for every NSSA attached. */ + + /* Configuration bitmask, refer to enum above */ + uint8_t config; + + /* Opaque-LSA administrative flags. */ + uint8_t opaque; +#define OPAQUE_OPERATION_READY_BIT (1 << 0) + + /* RFC3137 stub router. Configured time to stay stub / max-metric */ + unsigned int stub_router_startup_time; /* seconds */ + unsigned int stub_router_shutdown_time; /* seconds */ +#define OSPF_STUB_ROUTER_UNCONFIGURED 0 + uint8_t stub_router_admin_set; +#define OSPF_STUB_ROUTER_ADMINISTRATIVE_SET 1 +#define OSPF_STUB_ROUTER_ADMINISTRATIVE_UNSET 0 + +#define OSPF_STUB_MAX_METRIC_SUMMARY_COST 0x00ff0000 + + /* LSA timers */ + unsigned int min_ls_interval; /* minimum delay between LSAs (in msec) */ + unsigned int min_ls_arrival; /* minimum interarrival time between LSAs + (in msec) */ + + /* SPF parameters */ + unsigned int spf_delay; /* SPF delay time. */ + unsigned int spf_holdtime; /* SPF hold time. */ + unsigned int spf_max_holdtime; /* SPF maximum-holdtime */ + unsigned int + spf_hold_multiplier; /* Adaptive multiplier for hold time */ + + int default_originate; /* Default information originate. */ +#define DEFAULT_ORIGINATE_NONE 0 +#define DEFAULT_ORIGINATE_ZEBRA 1 +#define DEFAULT_ORIGINATE_ALWAYS 2 + uint32_t ref_bandwidth; /* Reference Bandwidth (Kbps). */ + struct route_table *networks; /* OSPF config networks. */ + struct list *vlinks; /* Configured Virtual-Links. */ + struct list *areas; /* OSPF areas. */ + struct route_table *nbr_nbma; + struct ospf_area *backbone; /* Pointer to the Backbone Area. */ + + struct list *oiflist; /* ospf interfaces */ + uint8_t passive_interface_default; /* passive-interface default */ + + /* LSDB of AS-external-LSAs. */ + struct ospf_lsdb *lsdb; + + /* Flags. */ + int ase_calc; /* ASE calculation flag. */ + + struct list *opaque_lsa_self; /* Type-11 Opaque-LSAs */ + + /* Routing tables. */ + struct route_table *old_table; /* Old routing table. */ + struct route_table *new_table; /* Current routing table. */ + + struct route_table *oall_rtrs; /* Old router RT. */ + struct route_table *all_rtrs; /* New routers RT. */ + + struct route_table *old_rtrs; /* Old ABR/ASBR RT. */ + struct route_table *new_rtrs; /* New ABR/ASBR RT. */ + + struct route_table *new_external_route; /* New External Route. */ + struct route_table *old_external_route; /* Old External Route. */ + + struct route_table *external_lsas; /* Database of external LSAs, + prefix is LSA's adv. network*/ + + /* Time stamps */ + struct timeval ts_spf; /* SPF calculation time stamp. */ + struct timeval ts_spf_duration; /* Execution time of last SPF */ + + struct route_table *maxage_lsa; /* List of MaxAge LSA for deletion. */ + int redistribute; /* Num of redistributed protocols. */ + + /* Threads. */ + struct event *t_abr_task; /* ABR task timer. */ + struct event *t_abr_fr; /* ABR FR timer. */ + struct event *t_asbr_check; /* ASBR check timer. */ + struct event *t_asbr_redist_update; /* ASBR redistribution update + timer. */ + struct event *t_distribute_update; /* Distirbute list update timer. */ + struct event *t_spf_calc; /* SPF calculation timer. */ + struct event *t_ase_calc; /* ASE calculation timer. */ + struct event *t_opaque_lsa_self; /* Type-11 Opaque-LSAs origin event. */ + struct event *t_sr_update; /* Segment Routing update timer */ + + unsigned int maxage_delay; /* Delay on Maxage remover timer, sec */ + struct event *t_maxage; /* MaxAge LSA remover timer. */ + struct event *t_maxage_walker; /* MaxAge LSA checking timer. */ + + struct event + *t_deferred_shutdown; /* deferred/stub-router shutdown timer*/ + + struct event *t_write; +#define OSPF_WRITE_INTERFACE_COUNT_DEFAULT 20 + struct event *t_default_routemap_timer; + + int write_oi_count; /* Num of packets sent per thread invocation */ + struct event *t_read; + int fd; + struct stream *ibuf; + struct list *oi_write_q; + + /* Distribute lists out of other route sources. */ + struct { + char *name; + struct access_list *list; + } dlist[ZEBRA_ROUTE_MAX]; +#define DISTRIBUTE_NAME(O,T) (O)->dlist[T].name +#define DISTRIBUTE_LIST(O,T) (O)->dlist[T].list + + /* OSPF redistribute configuration */ + struct list *redist[ZEBRA_ROUTE_MAX + 1]; + + /* Redistribute tag info. */ + route_tag_t + dtag[ZEBRA_ROUTE_MAX + 1]; // Pending: cant configure as of now + + int default_metric; /* Default metric for redistribute. */ + + /* NSSA default-information-originate */ + struct { + /* # of NSSA areas requesting default information */ + uint16_t refcnt; + + /* + * Whether a default route known through non-OSPF protocol is + * present in the RIB. + */ + bool status; + } nssa_default_import_check; + +#define OSPF_LSA_REFRESHER_GRANULARITY 10 +#define OSPF_LSA_REFRESHER_SLOTS \ + ((OSPF_LS_REFRESH_TIME + OSPF_LS_REFRESH_SHIFT) \ + / OSPF_LSA_REFRESHER_GRANULARITY \ + + 1) + struct { + uint16_t index; + struct list *qs[OSPF_LSA_REFRESHER_SLOTS]; + } lsa_refresh_queue; + + struct event *t_lsa_refresher; + time_t lsa_refresher_started; +#define OSPF_LSA_REFRESH_INTERVAL_DEFAULT 10 + uint16_t lsa_refresh_interval; + uint16_t lsa_refresh_timer; + + /* Distance parameter. */ + uint8_t distance_all; + uint8_t distance_intra; + uint8_t distance_inter; + uint8_t distance_external; + + /* Statistics for LSA origination. */ + uint32_t lsa_originate_count; + + /* Statistics for LSA used for new instantiation. */ + uint32_t rx_lsa_count; + + struct route_table *distance_table; + + /* Used during ospf instance going down send LSDB + * update to neighbors immediatly */ + uint8_t inst_shutdown; + + /* Enable or disable sending proactive ARP requests. */ + bool proactive_arp; +#define OSPF_PROACTIVE_ARP_DEFAULT true + + /* Redistributed external information. */ + struct list *external[ZEBRA_ROUTE_MAX + 1]; +#define EXTERNAL_INFO(E) (E->external_info) + + /* Graceful restart Helper supported configs*/ + /* Supported grace interval*/ + uint32_t supported_grace_time; + + /* Helper support + * Supported : True + * Not Supported : False. + */ + bool is_helper_supported; + + /* Support for strict LSA check. + * if it is set,Helper aborted + * upon a TOPO change. + */ + bool strict_lsa_check; + + /* Support as HELPER only for + * planned restarts. + */ + bool only_planned_restart; + + /* This list contains the advertisement + * routerids which are not support for HELPERs. + */ + struct hash *enable_rtr_list; + + /* HELPER for number of active + * RESTARTERs. + */ + uint16_t active_restarter_cnt; + + /* last HELPER exit reason */ + uint32_t last_exit_reason; + + /* delay timer to process external routes + * with summary address. + */ + struct event *t_external_aggr; + + /* delay interval in seconds */ + uint16_t aggr_delay_interval; + + /* Table of configured Aggregate addresses */ + struct route_table *rt_aggr_tbl; + + /* used as argument for aggr delay + * timer thread. + */ + int aggr_action; + + /* Max number of multiple paths + * to support ECMP. + */ + uint16_t max_multipath; + + /* MPLS LDP-IGP Sync */ + struct ldp_sync_info_cmd ldp_sync_cmd; + + /* OSPF Graceful Restart info */ + struct ospf_gr_info gr_info; + + /* TI-LFA support for all interfaces. */ + bool ti_lfa_enabled; + enum protection_type ti_lfa_protection_type; + + /* Flood Reduction configuration state */ + bool fr_configured; + + /* Socket buffer sizes */ + uint32_t recv_sock_bufsize; + uint32_t send_sock_bufsize; + + /* Per-interface write socket */ + bool intf_socket_enabled; + + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(ospf); + +enum ospf_ti_lfa_p_q_space_adjacency { + OSPF_TI_LFA_P_Q_SPACE_ADJACENT, + OSPF_TI_LFA_P_Q_SPACE_NON_ADJACENT, +}; + +enum ospf_ti_lfa_node_type { + OSPF_TI_LFA_UNDEFINED_NODE, + OSPF_TI_LFA_PQ_NODE, + OSPF_TI_LFA_P_NODE, + OSPF_TI_LFA_Q_NODE, +}; + +struct ospf_ti_lfa_node_info { + struct vertex *node; + enum ospf_ti_lfa_node_type type; + struct in_addr nexthop; +}; + +struct ospf_ti_lfa_inner_backup_path_info { + struct ospf_ti_lfa_node_info p_node_info; + struct ospf_ti_lfa_node_info q_node_info; + struct mpls_label_stack *label_stack; +}; + +struct protected_resource { + enum protection_type type; + + /* Link Protection */ + struct router_lsa_link *link; + + /* Node Protection */ + struct in_addr router_id; +}; + +PREDECL_RBTREE_UNIQ(q_spaces); +struct q_space { + struct vertex *root; + struct list *vertex_list; + struct mpls_label_stack *label_stack; + struct in_addr nexthop; + struct list *pc_path; + struct ospf_ti_lfa_node_info *p_node_info; + struct ospf_ti_lfa_node_info *q_node_info; + struct q_spaces_item q_spaces_item; +}; + +PREDECL_RBTREE_UNIQ(p_spaces); +struct p_space { + struct vertex *root; + struct protected_resource *protected_resource; + struct q_spaces_head *q_spaces; + struct list *vertex_list; + struct vertex *pc_spf; + struct list *pc_vertex_list; + struct p_spaces_item p_spaces_item; +}; + +/* OSPF area structure. */ +struct ospf_area { + /* OSPF instance. */ + struct ospf *ospf; + + /* Zebra interface list belonging to the area. */ + struct list *oiflist; + + /* Area ID. */ + struct in_addr area_id; + + /* Area ID format. */ + int area_id_fmt; +#define OSPF_AREA_ID_FMT_DOTTEDQUAD 1 +#define OSPF_AREA_ID_FMT_DECIMAL 2 + + /* Address range. */ + struct list *address_range; + + /* Configured variables. */ + int external_routing; /* ExternalRoutingCapability. */ + int no_summary; /* Don't inject summaries into stub.*/ + int shortcut_configured; /* Area configured as shortcut. */ +#define OSPF_SHORTCUT_DEFAULT 0 +#define OSPF_SHORTCUT_ENABLE 1 +#define OSPF_SHORTCUT_DISABLE 2 + int shortcut_capability; /* Other ABRs agree on S-bit */ + uint32_t default_cost; /* StubDefaultCost. */ + int auth_type; /* Authentication type. */ + int suppress_fa; /* Suppress forwarding address in NSSA ABR */ + + uint8_t NSSATranslatorRole; /* NSSA configured role */ +#define OSPF_NSSA_ROLE_NEVER 0 +#define OSPF_NSSA_ROLE_CANDIDATE 1 +#define OSPF_NSSA_ROLE_ALWAYS 2 + uint8_t NSSATranslatorState; /* NSSA operational role */ +#define OSPF_NSSA_TRANSLATE_DISABLED 0 +#define OSPF_NSSA_TRANSLATE_ENABLED 1 + int NSSATranslatorStabilityInterval; + + uint8_t transit; /* TransitCapability. */ +#define OSPF_TRANSIT_FALSE 0 +#define OSPF_TRANSIT_TRUE 1 + struct route_table *ranges; /* Configured Area Ranges. */ + struct route_table *nssa_ranges; /* Configured NSSA Area Ranges. */ + + /* RFC3137 stub router state flags for area */ + uint8_t stub_router_state; +#define OSPF_AREA_ADMIN_STUB_ROUTED (1 << 0) /* admin stub-router set */ +#define OSPF_AREA_IS_STUB_ROUTED (1 << 1) /* stub-router active */ +#define OSPF_AREA_WAS_START_STUB_ROUTED (1 << 2) /* startup SR was done */ + /* Area related LSDBs[Type1-4]. */ + struct ospf_lsdb *lsdb; + + /* Self-originated LSAs. */ + struct ospf_lsa *router_lsa_self; + struct list *opaque_lsa_self; /* Type-10 Opaque-LSAs */ + + /* Area announce list. */ + struct { + char *name; + struct access_list *list; + } _export; +#define EXPORT_NAME(A) (A)->_export.name +#define EXPORT_LIST(A) (A)->_export.list + + /* Area acceptance list. */ + struct { + char *name; + struct access_list *list; + } import; +#define IMPORT_NAME(A) (A)->import.name +#define IMPORT_LIST(A) (A)->import.list + + /* Type 3 LSA Area prefix-list. */ + struct { + char *name; + struct prefix_list *list; + } plist_in; +#define PREFIX_LIST_IN(A) (A)->plist_in.list +#define PREFIX_NAME_IN(A) (A)->plist_in.name + + struct { + char *name; + struct prefix_list *list; + } plist_out; +#define PREFIX_LIST_OUT(A) (A)->plist_out.list +#define PREFIX_NAME_OUT(A) (A)->plist_out.name + + /* NSSA default-information-originate */ + struct { + bool enabled; + int metric_type; + int metric_value; + } nssa_default_originate; + + /* Shortest Path Tree. */ + struct vertex *spf; + struct list *spf_vertex_list; + + bool spf_dry_run; /* flag for checking if the SPF calculation is + intended for the local RIB */ + bool spf_root_node; /* flag for checking if the calculating node is the + root node of the SPF tree */ + + /* TI-LFA protected link for SPF calculations */ + struct protected_resource *spf_protected_resource; + + /* P/Q spaces for TI-LFA */ + struct p_spaces_head *p_spaces; + + /* Threads. */ + struct event *t_stub_router; /* Stub-router timer */ + struct event *t_opaque_lsa_self; /* Type-10 Opaque-LSAs origin. */ + + /* Statistics field. */ + uint32_t spf_calculation; /* SPF Calculation Count. */ + + /* reverse SPF (used for TI-LFA Q spaces) */ + bool spf_reversed; + + /* Time stamps. */ + struct timeval ts_spf; /* SPF calculation time stamp. */ + + /* Router count. */ + uint32_t abr_count; /* ABR router in this area. */ + uint32_t asbr_count; /* ASBR router in this area. */ + + /* Counters. */ + uint32_t act_ints; /* Active interfaces. */ + uint32_t full_nbrs; /* Fully adjacent neighbors. */ + uint32_t full_vls; /* Fully adjacent virtual neighbors. */ + + struct ospf_area_fr_info fr_info; /* Flood reduction info. */ +}; + +/* OSPF config network structure. */ +struct ospf_network { + /* Area ID. */ + struct in_addr area_id; + int area_id_fmt; +}; + +/* OSPF NBMA neighbor structure. */ +struct ospf_nbr_nbma { + /* Neighbor IP address. */ + struct in_addr addr; + + /* OSPF interface. */ + struct ospf_interface *oi; + + /* OSPF neighbor structure. */ + struct ospf_neighbor *nbr; + + /* Neighbor priority. */ + uint8_t priority; + + /* Poll timer value. */ + uint32_t v_poll; + + /* Poll timer thread. */ + struct event *t_poll; + + /* State change. */ + uint32_t state_change; +}; + +/* Macro. */ +#define OSPF_AREA_SAME(X, Y) \ + (memcmp((X->area_id), (Y->area_id), IPV4_MAX_BYTELEN) == 0) + +#define IS_OSPF_ABR(O) ((O)->flags & OSPF_FLAG_ABR) +#define IS_OSPF_ASBR(O) ((O)->flags & OSPF_FLAG_ASBR) + +#define OSPF_IS_AREA_ID_BACKBONE(I) ((I).s_addr == OSPF_AREA_BACKBONE) +#define OSPF_IS_AREA_BACKBONE(A) OSPF_IS_AREA_ID_BACKBONE ((A)->area_id) + +#ifdef roundup +# define ROUNDUP(val, gran) roundup(val, gran) +#else /* roundup */ +# define ROUNDUP(val, gran) (((val) - 1 | (gran) - 1) + 1) +#endif /* roundup */ + +#define LSA_OPTIONS_GET(area) \ + (((area)->external_routing == OSPF_AREA_DEFAULT) ? OSPF_OPTION_E : 0) +#define LSA_OPTIONS_NSSA_GET(area) \ + (((area)->external_routing == OSPF_AREA_NSSA) ? OSPF_OPTION_NP : 0) + +#define OSPF_TIMER_ON(T, F, V) event_add_timer(master, (F), ospf, (V), &(T)) +#define OSPF_AREA_TIMER_ON(T, F, V) \ + event_add_timer(master, (F), area, (V), &(T)) +#define OSPF_POLL_TIMER_ON(T, F, V) \ + event_add_timer(master, (F), nbr_nbma, (V), &(T)) + +/* Extern variables. */ +extern struct ospf_master *om; +extern unsigned short ospf_instance; +extern const int ospf_redistributed_proto_max; +extern struct zclient *zclient; +extern struct event_loop *master; +extern int ospf_zlog; +extern struct zebra_privs_t ospfd_privs; + +/* Prototypes. */ +extern const char *ospf_redist_string(unsigned int route_type); +extern struct ospf *ospf_lookup_instance(unsigned short instance); +extern struct ospf *ospf_lookup(unsigned short instance, const char *name); +extern struct ospf *ospf_get(unsigned short instance, const char *name, + bool *created); +extern struct ospf *ospf_new_alloc(unsigned short instance, const char *name); +extern struct ospf *ospf_lookup_by_inst_name(unsigned short instance, + const char *name); +extern struct ospf *ospf_lookup_by_vrf_id(vrf_id_t vrf_id); +extern uint32_t ospf_count_area_params(struct ospf *ospf); +extern void ospf_finish(struct ospf *ospf); +extern void ospf_process_refresh_data(struct ospf *ospf, bool reset); +extern void ospf_router_id_update(struct ospf *ospf); +extern void ospf_process_reset(struct ospf *ospf); +extern void ospf_neighbor_reset(struct ospf *ospf, struct in_addr nbr_id, + const char *nbr_str); +extern int ospf_network_set(struct ospf *ospf, struct prefix_ipv4 *p, + struct in_addr area_id, int df); +extern int ospf_network_unset(struct ospf *ospf, struct prefix_ipv4 *p, + struct in_addr aread_id); +extern int ospf_area_display_format_set(struct ospf *ospf, + struct ospf_area *area, int df); +extern int ospf_area_stub_set(struct ospf *ospf, struct in_addr area_id); +extern int ospf_area_stub_unset(struct ospf *ospf, struct in_addr area_id); +extern int ospf_area_no_summary_set(struct ospf *ospf, struct in_addr area_id); +extern int ospf_area_no_summary_unset(struct ospf *ospf, + struct in_addr area_id); +extern int ospf_area_nssa_set(struct ospf *ospf, struct in_addr area_id); +extern int ospf_area_nssa_unset(struct ospf *ospf, struct in_addr area_id); +extern int ospf_area_nssa_suppress_fa_set(struct ospf *ospf, + struct in_addr area_id); +extern int ospf_area_nssa_suppress_fa_unset(struct ospf *ospf, + struct in_addr area_id); +extern int ospf_area_nssa_translator_role_set(struct ospf *ospf, + struct in_addr area_id, int role); +extern void ospf_area_nssa_default_originate_set(struct ospf *ospf, + struct in_addr area_id, + int metric, int metric_type); +extern void ospf_area_nssa_default_originate_unset(struct ospf *ospf, + struct in_addr area_id); +extern int ospf_area_export_list_set(struct ospf *ospf, + struct ospf_area *area_id, + const char *list_name); +extern int ospf_area_export_list_unset(struct ospf *ospf, + struct ospf_area *area_id); +extern int ospf_area_import_list_set(struct ospf *ospf, + struct ospf_area *area_id, + const char *name); +extern int ospf_area_import_list_unset(struct ospf *ospf, + struct ospf_area *area_id); +extern int ospf_area_shortcut_set(struct ospf *ospf, struct ospf_area *area_id, + int mode); +extern int ospf_area_shortcut_unset(struct ospf *ospf, + struct ospf_area *area_id); +extern int ospf_timers_refresh_set(struct ospf *ospf, int interval); +extern int ospf_timers_refresh_unset(struct ospf *ospf); +void ospf_area_lsdb_discard_delete(struct ospf_area *area); +extern int ospf_nbr_nbma_set(struct ospf *ospf, struct in_addr nbr_addr); +extern int ospf_nbr_nbma_unset(struct ospf *ospf, struct in_addr nbr_addr); +extern int ospf_nbr_nbma_priority_set(struct ospf *ospf, + struct in_addr nbr_addr, + uint8_t priority); +extern int ospf_nbr_nbma_priority_unset(struct ospf *ospf, + struct in_addr nbr_addr); +extern int ospf_nbr_nbma_poll_interval_set(struct ospf *ospf, + struct in_addr nbr_addr, + unsigned int interval); +extern int ospf_nbr_nbma_poll_interval_unset(struct ospf *ospf, + struct in_addr addr); +extern void ospf_if_update(struct ospf *ospf, struct interface *ifp); +extern void ospf_ls_upd_queue_empty(struct ospf_interface *oi); +extern void ospf_terminate(void); +extern void ospf_nbr_nbma_if_update(struct ospf *ospf, + struct ospf_interface *oi); +extern struct ospf_nbr_nbma *ospf_nbr_nbma_lookup(struct ospf *ospf, + struct in_addr nbr_addr); +extern int ospf_oi_count(struct interface *ifp); + +extern struct ospf_area *ospf_area_new(struct ospf *ospf, + struct in_addr area_id); +extern struct ospf_area *ospf_area_get(struct ospf *ospf, + struct in_addr area_id); +extern void ospf_area_check_free(struct ospf *ospf, struct in_addr area_id); +extern struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *ospf, + struct in_addr area_id); +extern void ospf_area_add_if(struct ospf_area *oa, struct ospf_interface *oi); +extern void ospf_area_del_if(struct ospf_area *oa, struct ospf_interface *oi); + +extern void ospf_interface_area_set(struct ospf *ospf, struct interface *ifp); +extern void ospf_interface_area_unset(struct ospf *ospf, struct interface *ifp); + +extern void ospf_route_map_init(void); + +extern void ospf_master_init(struct event_loop *master); +extern void ospf_vrf_init(void); +extern void ospf_vrf_terminate(void); +extern void ospf_vrf_link(struct ospf *ospf, struct vrf *vrf); +extern void ospf_vrf_unlink(struct ospf *ospf, struct vrf *vrf); +const char *ospf_vrf_id_to_name(vrf_id_t vrf_id); +int ospf_area_nssa_no_summary_set(struct ospf *ospf, struct in_addr area_id); + +const char *ospf_get_name(const struct ospf *ospf); +extern struct ospf_interface *add_ospf_interface(struct connected *co, + struct ospf_area *area); +/* Update socket bufsize(s), after config change */ +void ospf_update_bufsize(struct ospf *ospf, uint32_t recvsize, + uint32_t sendsize); + +extern int p_spaces_compare_func(const struct p_space *a, + const struct p_space *b); +extern int q_spaces_compare_func(const struct q_space *a, + const struct q_space *b); + +#endif /* _ZEBRA_OSPFD_H */ diff --git a/ospfd/subdir.am b/ospfd/subdir.am new file mode 100644 index 0000000..4803aae --- /dev/null +++ b/ospfd/subdir.am @@ -0,0 +1,127 @@ +# +# ospfd +# + +if OSPFD +noinst_LIBRARIES += ospfd/libfrrospf.a +noinst_LIBRARIES += ospfd/libfrrospfclient.a +sbin_PROGRAMS += ospfd/ospfd +vtysh_daemons += ospfd +if SNMP +module_LTLIBRARIES += ospfd/ospfd_snmp.la +endif +man8 += $(MANBUILD)/frr-ospfd.8 +endif + +ospfd_libfrrospfclient_a_SOURCES = \ + ospfd/ospf_api.c \ + ospfd/ospf_dump_api.c \ + #end + +ospfd_libfrrospf_a_SOURCES = \ + ospfd/ospf_abr.c \ + ospfd/ospf_api.c \ + ospfd/ospf_apiserver.c \ + ospfd/ospf_asbr.c \ + ospfd/ospf_ase.c \ + ospfd/ospf_bfd.c \ + ospfd/ospf_dump.c \ + ospfd/ospf_dump_api.c \ + ospfd/ospf_errors.c \ + ospfd/ospf_ext.c \ + ospfd/ospf_flood.c \ + ospfd/ospf_gr.c \ + ospfd/ospf_ia.c \ + ospfd/ospf_interface.c \ + ospfd/ospf_ism.c \ + ospfd/ospf_ldp_sync.c \ + ospfd/ospf_lsa.c \ + ospfd/ospf_lsdb.c \ + ospfd/ospf_memory.c \ + ospfd/ospf_neighbor.c \ + ospfd/ospf_network.c \ + ospfd/ospf_nsm.c \ + ospfd/ospf_opaque.c \ + ospfd/ospf_packet.c \ + ospfd/ospf_ri.c \ + ospfd/ospf_route.c \ + ospfd/ospf_routemap.c \ + ospfd/ospf_routemap_nb.c \ + ospfd/ospf_routemap_nb_config.c \ + ospfd/ospf_spf.c \ + ospfd/ospf_ti_lfa.c \ + ospfd/ospf_sr.c \ + ospfd/ospf_te.c \ + ospfd/ospf_vty.c \ + ospfd/ospf_zebra.c \ + ospfd/ospfd.c \ + ospfd/ospf_gr_helper.c \ + ospfd/ospf_auth.c \ + # end + +if OSPFD +ospfdheaderdir = $(pkgincludedir)/ospfd +ospfdheader_HEADERS = \ + ospfd/ospf_api.h \ + ospfd/ospf_asbr.h \ + ospfd/ospf_dump.h \ + ospfd/ospf_dump_api.h \ + ospfd/ospf_ism.h \ + ospfd/ospf_lsa.h \ + ospfd/ospf_lsdb.h \ + ospfd/ospf_nsm.h \ + ospfd/ospf_opaque.h \ + ospfd/ospfd.h \ + # end +endif + +clippy_scan += \ + ospfd/ospf_vty.c \ + ospfd/ospf_ldp_sync.c \ + ospfd/ospf_dump.c \ + ospfd/ospf_gr.c \ + # end + +noinst_HEADERS += \ + ospfd/ospf_abr.h \ + ospfd/ospf_apiserver.h \ + ospfd/ospf_ase.h \ + ospfd/ospf_bfd.h \ + ospfd/ospf_errors.h \ + ospfd/ospf_ext.h \ + ospfd/ospf_flood.h \ + ospfd/ospf_ia.h \ + ospfd/ospf_interface.h \ + ospfd/ospf_ldp_sync.h \ + ospfd/ospf_memory.h \ + ospfd/ospf_neighbor.h \ + ospfd/ospf_network.h \ + ospfd/ospf_packet.h \ + ospfd/ospf_ri.h \ + ospfd/ospf_gr.h \ + ospfd/ospf_route.h \ + ospfd/ospf_routemap_nb.h \ + ospfd/ospf_spf.h \ + ospfd/ospf_ti_lfa.h \ + ospfd/ospf_sr.h \ + ospfd/ospf_te.h \ + ospfd/ospf_vty.h \ + ospfd/ospf_zebra.h \ + ospfd/ospf_auth.h \ + # end + +ospfd_ospfd_LDADD = ospfd/libfrrospf.a ospfd/libfrrospfclient.a lib/libfrr.la $(LIBCAP) $(LIBM) +ospfd_ospfd_SOURCES = ospfd/ospf_main.c + +ospfd_ospfd_snmp_la_SOURCES = ospfd/ospf_snmp.c +ospfd_ospfd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +ospfd_ospfd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +ospfd_ospfd_snmp_la_LIBADD = lib/libfrrsnmp.la + +EXTRA_DIST += \ + ospfd/ChangeLog.opaque.txt \ + # end + +nodist_ospfd_ospfd_SOURCES = \ + yang/frr-ospf-route-map.yang.c \ + # end diff --git a/pathd/.gitignore b/pathd/.gitignore new file mode 100644 index 0000000..95f4a99 --- /dev/null +++ b/pathd/.gitignore @@ -0,0 +1,2 @@ +libpath.a +pathd diff --git a/pathd/Makefile b/pathd/Makefile new file mode 100644 index 0000000..b681a9a --- /dev/null +++ b/pathd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pathd/pathd +%: ALWAYS + @$(MAKE) -s -C .. pathd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pathd/path_cli.c b/pathd/path_cli.c new file mode 100644 index 0000000..e22931c --- /dev/null +++ b/pathd/path_cli.c @@ -0,0 +1,1389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include +#include +#include + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "mpls.h" +#include "northbound_cli.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" +#include "pathd/path_cli_clippy.c" +#include "pathd/path_ted.h" + +#define XPATH_MAXATTRSIZE 64 +#define XPATH_MAXKEYSIZE 42 +#define XPATH_POLICY_BASELEN 100 +#define XPATH_POLICY_MAXLEN (XPATH_POLICY_BASELEN + XPATH_MAXATTRSIZE) +#define XPATH_CANDIDATE_BASELEN (XPATH_POLICY_BASELEN + XPATH_MAXKEYSIZE) +#define XPATH_CANDIDATE_MAXLEN (XPATH_CANDIDATE_BASELEN + XPATH_MAXATTRSIZE) + + +static int config_write_segment_routing(struct vty *vty); +static int segment_list_has_src_dst( + struct vty *vty, char *xpath, long index, const char *index_str, + struct in_addr adj_src_ipv4, struct in_addr adj_dst_ipv4, + struct in6_addr adj_src_ipv6, struct in6_addr adj_dst_ipv6, + const char *adj_src_ipv4_str, const char *adj_dst_ipv4_str, + const char *adj_src_ipv6_str, const char *adj_dst_ipv6_str); +static int segment_list_has_prefix( + struct vty *vty, char *xpath, long index, const char *index_str, + const struct prefix_ipv4 *prefix_ipv4, const char *prefix_ipv4_str, + const struct prefix_ipv6 *prefix_ipv6, const char *prefix_ipv6_str, + const char *has_algo, long algo, const char *algo_str, + const char *has_iface_id, long iface_id, const char *iface_id_str); + +DEFINE_MTYPE_STATIC(PATHD, PATH_CLI, "Client"); + +DEFINE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty)); + +/* Vty node structures. */ +static struct cmd_node segment_routing_node = { + .name = "segment-routing", + .node = SEGMENT_ROUTING_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-sr)# ", + .config_write = config_write_segment_routing, +}; + +static struct cmd_node sr_traffic_eng_node = { + .name = "sr traffic-eng", + .node = SR_TRAFFIC_ENG_NODE, + .parent_node = SEGMENT_ROUTING_NODE, + .prompt = "%s(config-sr-te)# ", +}; + +static struct cmd_node srte_segment_list_node = { + .name = "srte segment-list", + .node = SR_SEGMENT_LIST_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-segment-list)# ", +}; + +static struct cmd_node srte_policy_node = { + .name = "srte policy", + .node = SR_POLICY_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-policy)# ", +}; + +static struct cmd_node srte_candidate_dyn_node = { + .name = "srte candidate-dyn", + .node = SR_CANDIDATE_DYN_NODE, + .parent_node = SR_POLICY_NODE, + .prompt = "%s(config-sr-te-candidate)# ", +}; + + +/* + * Show SR-TE info + */ +DEFPY(show_srte_policy, + show_srte_policy_cmd, + "show sr-te policy", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n") +{ + struct ttable *tt; + struct srte_policy *policy; + char *table; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Endpoint|Color|Name|BSID|Status"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + char endpoint[ENDPOINT_STR_LENGTH]; + char binding_sid[16] = "-"; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + + ttable_add_row(tt, "%s|%u|%s|%s|%s", endpoint, policy->color, + policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP + ? "Active" + : "Inactive"); + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + + return CMD_SUCCESS; +} + + +/* + * Show detailed SR-TE info + */ +DEFPY(show_srte_policy_detail, + show_srte_policy_detail_cmd, + "show sr-te policy detail", + SHOW_STR + "SR-TE info\n" + "SR-TE Policy\n" + "Show a detailed summary\n") +{ + struct srte_policy *policy; + + if (RB_EMPTY(srte_policy_head, &srte_policies)) { + vty_out(vty, "No SR Policies to display.\n\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "\n"); + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + char endpoint[ENDPOINT_STR_LENGTH]; + char binding_sid[16] = "-"; + char *segment_list_info; + static char undefined_info[] = "(undefined)"; + static char created_by_pce_info[] = "(created by PCE)"; + + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->binding_sid != MPLS_LABEL_NONE) + snprintf(binding_sid, sizeof(binding_sid), "%u", + policy->binding_sid); + vty_out(vty, + "Endpoint: %s Color: %u Name: %s BSID: %s Status: %s\n", + endpoint, policy->color, policy->name, binding_sid, + policy->status == SRTE_POLICY_STATUS_UP ? "Active" + : "Inactive"); + + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + struct srte_segment_list *segment_list; + + segment_list = candidate->lsp->segment_list; + if (segment_list == NULL) + segment_list_info = undefined_info; + else if (segment_list->protocol_origin + == SRTE_ORIGIN_PCEP) + segment_list_info = created_by_pce_info; + else + segment_list_info = + candidate->lsp->segment_list->name; + + vty_out(vty, + " %s Preference: %d Name: %s Type: %s Segment-List: %s Protocol-Origin: %s\n", + CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST) + ? "*" + : " ", + candidate->preference, candidate->name, + candidate->type == SRTE_CANDIDATE_TYPE_EXPLICIT + ? "explicit" + : "dynamic", + segment_list_info, + srte_origin2str( + candidate->lsp->protocol_origin)); + } + + vty_out(vty, "\n"); + } + + return CMD_SUCCESS; +} + +DEFPY_NOSH( + segment_routing_list, + segment_routing_cmd, + "segment-routing", + "Configure segment routing\n") +{ + VTY_PUSH_CONTEXT_NULL(SEGMENT_ROUTING_NODE); + return CMD_SUCCESS; +} + +DEFPY_NOSH( + sr_traffic_eng_list, + sr_traffic_eng_cmd, + "traffic-eng", + "Configure SR traffic engineering\n") +{ + VTY_PUSH_CONTEXT_NULL(SR_TRAFFIC_ENG_NODE); + return CMD_SUCCESS; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +DEFPY_NOSH( + srte_segment_list, + srte_segment_list_cmd, + "segment-list WORD$name", + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/protocol-origin", + name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "local"); + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']/originator", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "config"); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) { + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + VTY_PUSH_XPATH(SR_SEGMENT_LIST_NODE, xpath); + } + + return ret; +} + +DEFPY(srte_no_segment_list, + srte_no_segment_list_cmd, + "no segment-list WORD$name", + NO_STR + "Segment List\n" + "Segment List Name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/segment-list[name='%s']", name); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " segment-list %s\n", + yang_dnode_get_string(dnode, "name")); +} + +void cli_show_srte_segment_list_end(struct vty *vty, + const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +static int segment_list_has_src_dst( + struct vty *vty, char *xpath, long index, const char *index_str, + struct in_addr adj_src_ipv4, struct in_addr adj_dst_ipv4, + struct in6_addr adj_src_ipv6, struct in6_addr adj_dst_ipv6, + const char *adj_src_ipv4_str, const char *adj_dst_ipv4_str, + const char *adj_src_ipv6_str, const char *adj_dst_ipv6_str) +{ + const char *node_src_id; + uint32_t ted_sid = MPLS_LABEL_NONE; + + struct ipaddr ip_src = {}; + struct ipaddr ip_dst = {}; + if (adj_src_ipv4_str != NULL) { + ip_src.ipa_type = IPADDR_V4; + ip_src.ip._v4_addr = adj_src_ipv4; + ip_dst.ipa_type = IPADDR_V4; + ip_dst.ip._v4_addr = adj_dst_ipv4; + } else if (adj_src_ipv6_str != NULL) { + ip_src.ipa_type = IPADDR_V6; + ip_src.ip._v6_addr = adj_src_ipv6; + ip_dst.ipa_type = IPADDR_V6; + ip_dst.ip._v6_addr = adj_dst_ipv6; + } else { + return CMD_ERR_NO_MATCH; + } + ted_sid = path_ted_query_type_f(&ip_src, &ip_dst); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_f SRC (%pIA) DST (%pIA)!", + __func__, &ip_src, &ip_dst); + } + /* type */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/type", + index_str); + if (adj_src_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_adjacency"); + node_src_id = adj_src_ipv4_str; + } else if (adj_src_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_adjacency"); + node_src_id = adj_src_ipv6_str; + } else { + /* + * This is just to make the compiler happy about + * node_src_id not being initialized. This + * should never happen unless we change the cli + * function. + */ + assert(!"We must have a adj_src_ipv4_str or a adj_src_ipv6_str"); + } + + /* addresses */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/local-address", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, node_src_id); + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/remote-address", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + adj_dst_ipv4_str ? adj_dst_ipv4_str + : adj_dst_ipv6_str); + return CMD_SUCCESS; +} +int segment_list_has_prefix( + struct vty *vty, char *xpath, long index, const char *index_str, + const struct prefix_ipv4 *prefix_ipv4, const char *prefix_ipv4_str, + const struct prefix_ipv6 *prefix_ipv6, const char *prefix_ipv6_str, + const char *has_algo, long algo, const char *algo_str, + const char *has_iface_id, long iface_id, const char *iface_id_str) +{ + char buf_prefix[INET6_ADDRSTRLEN]; + + uint32_t ted_sid = MPLS_LABEL_NONE; + struct prefix prefix_cli = {}; + struct ipaddr pre_ipaddr = {}; + /* prefix with algorithm or local interface id */ + /* Type */ + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/type", + index_str); + if (has_iface_id != NULL) { + if (prefix_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_local_iface"); + } else if (prefix_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_local_iface"); + } else { + return CMD_ERR_NO_MATCH; + } + } else { + if (prefix_ipv4_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv4_algo"); + } else if (prefix_ipv6_str != NULL) { + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + "ipv6_algo"); + } else { + return CMD_ERR_NO_MATCH; + } + } + /* Prefix */ + if (prefix_ipv4_str != NULL) { + if (!str2prefix(prefix_ipv4_str, &prefix_cli)) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + inet_ntop(AF_INET, &prefix_cli.u.prefix4, buf_prefix, + sizeof(buf_prefix)); + pre_ipaddr.ipa_type = IPADDR_V4; + pre_ipaddr.ip._v4_addr = prefix_cli.u.prefix4; + } else if (prefix_ipv6_str != NULL) { + if (!str2prefix(prefix_ipv6_str, &prefix_cli)) { + vty_out(vty, "%% Malformed prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + inet_ntop(AF_INET6, &prefix_cli.u.prefix6, buf_prefix, + sizeof(buf_prefix)); + pre_ipaddr.ipa_type = IPADDR_V6; + pre_ipaddr.ip._v6_addr = prefix_cli.u.prefix6; + } + snprintf(xpath, XPATH_MAXLEN, "./segment[index='%s']/nai/local-address", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, buf_prefix); + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/local-prefix-len", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + prefix_ipv4_str + ? strchr(prefix_ipv4_str, '/') + 1 + : strchr(prefix_ipv6_str, '/') + 1); + /* Alg / Iface */ + if (has_algo != NULL) { + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/algorithm", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, algo_str); + } else { + if (has_iface_id != NULL) { + snprintf(xpath, XPATH_MAXLEN, + "./segment[index='%s']/nai/local-interface", + index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + iface_id_str); + } + } + if (has_algo != NULL) { + ted_sid = path_ted_query_type_c(&prefix_cli, algo); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_err( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_c PREFIX (%pIA/%d) ALGO (%ld) sid:(%d)!", + __func__, &pre_ipaddr, prefix_cli.prefixlen, + algo, ted_sid); + } + } + if (has_iface_id != NULL) { + ted_sid = path_ted_query_type_e(&prefix_cli, iface_id); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_err( + "%s: [rcv ted] CLI NOT FOUND Continue query_type_e PREFIX (%pIA/%d) IFACE (%ld) sid:(%d)!", + __func__, &pre_ipaddr, prefix_cli.prefixlen, + iface_id, ted_sid); + } + } + return CMD_SUCCESS; +} +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +/* clang-format off */ +DEFPY(srte_segment_list_segment, srte_segment_list_segment_cmd, + "index (0-4294967295)$index <[mpls$has_mpls_label label (16-1048575)$label] " + "|" + "[nai$has_nai <" + "prefix " + "" + "| adjacency$has_adj " + "" + ">]" + ">", + "Index\n" + "Index Value\n" + "MPLS or IP Label\n" + "Label\n" + "Label Value\n" + "Segment NAI\n" + "NAI prefix identifier\n" + "NAI IPv4 prefix identifier\n" + "NAI IPv6 prefix identifier\n" + "IGP Algorithm\n" + "Algorithm Value SPF or Strict-SPF\n" + "Interface Id\n" + "Interface Value\n" + "ADJ identifier\n" + "ADJ IPv4 src identifier\n" + "ADJ IPv4 dst identifier\n" + "ADJ IPv6 src identifier\n" + "ADJ IPv6 dst identifier\n") +/* clang-format on */ +{ + char xpath[XPATH_MAXLEN]; + int status = CMD_SUCCESS; + + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + if (has_mpls_label != NULL) { + snprintf(xpath, sizeof(xpath), + "./segment[index='%s']/sid-value", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, label_str); + return nb_cli_apply_changes(vty, NULL); + } + + if (has_adj != NULL) { + status = segment_list_has_src_dst(vty, xpath, index, index_str, + adj_src_ipv4, adj_dst_ipv4, + adj_src_ipv6, adj_dst_ipv6, + adj_src_ipv4_str, adj_dst_ipv4_str, + adj_src_ipv6_str, adj_dst_ipv6_str); + if (status != CMD_SUCCESS) + return status; + } else { + status = segment_list_has_prefix( + vty, xpath, index, index_str, prefix_ipv4, + prefix_ipv4_str, prefix_ipv6, prefix_ipv6_str, has_algo, + algo, algo_str, has_iface_id, iface_id, iface_id_str); + if (status != CMD_SUCCESS) + return status; + } + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_segment_list_no_segment, + srte_segment_list_no_segment_cmd, + "no index (0-4294967295)$index", + NO_STR + "Index\n" + "Index Value\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./segment[index='%s']", index_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_segment_list_segment(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " index %s", yang_dnode_get_string(dnode, "index")); + if (yang_dnode_exists(dnode, "sid-value")) { + vty_out(vty, " mpls label %s", + yang_dnode_get_string(dnode, "sid-value")); + } + if (yang_dnode_exists(dnode, "nai")) { + struct ipaddr addr; + struct ipaddr addr_rmt; + + switch (yang_dnode_get_enum(dnode, "nai/type")) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + yang_dnode_get_ip(&addr, dnode, "nai/local-address"); + vty_out(vty, " nai prefix %pI4", &addr.ipaddr_v4); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + yang_dnode_get_ip(&addr, dnode, "nai/local-address"); + vty_out(vty, " nai prefix %pI6", &addr.ipaddr_v6); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + yang_dnode_get_ip(&addr, dnode, "nai/local-address"); + yang_dnode_get_ip(&addr_rmt, dnode, + "./nai/remote-address"); + vty_out(vty, " nai adjacency %pI4", &addr.ipaddr_v4); + vty_out(vty, " %pI4", &addr_rmt.ipaddr_v4); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + yang_dnode_get_ip(&addr, dnode, "nai/local-address"); + yang_dnode_get_ip(&addr_rmt, dnode, + "./nai/remote-address"); + vty_out(vty, " nai adjacency %pI6", &addr.ipaddr_v6); + vty_out(vty, " %pI6", &addr_rmt.ipaddr_v6); + break; + default: + break; + } + if (yang_dnode_exists(dnode, "nai/local-prefix-len")) { + vty_out(vty, "/%s", + yang_dnode_get_string( + dnode, "./nai/local-prefix-len")); + } + if (yang_dnode_exists(dnode, "nai/local-interface")) { + vty_out(vty, " iface %s", + yang_dnode_get_string(dnode, + "./nai/local-interface")); + } + if (yang_dnode_exists(dnode, "nai/algorithm")) { + vty_out(vty, " algorithm %s", + yang_dnode_get_string(dnode, + "./nai/algorithm")); + } + } + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-pathd:pathd/policy + */ +DEFPY_NOSH( + srte_policy, + srte_policy_cmd, + "policy color (0-4294967295)$num endpoint $endpoint", + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_POLICY_NODE, xpath); + + return ret; +} + +DEFPY(srte_no_policy, + srte_no_policy_cmd, + "no policy color (0-4294967295)$num endpoint $endpoint", + NO_STR + "Segment Routing Policy\n" + "SR Policy color\n" + "SR Policy color value\n" + "SR Policy endpoint\n" + "SR Policy endpoint IPv4 address\n" + "SR Policy endpoint IPv6 address\n") +{ + char xpath[XPATH_POLICY_BASELEN]; + + snprintf(xpath, sizeof(xpath), + "/frr-pathd:pathd/srte/policy[color='%s'][endpoint='%s']", + num_str, endpoint_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " policy color %s endpoint %s\n", + yang_dnode_get_string(dnode, "color"), + yang_dnode_get_string(dnode, "endpoint")); +} + +void cli_show_srte_policy_end(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, " exit\n"); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +DEFPY(srte_policy_name, + srte_policy_name_cmd, + "name WORD$name", + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_CREATE, name); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_name, + srte_policy_no_name_cmd, + "no name [WORD]", + NO_STR + "Segment Routing Policy name\n" + "SR Policy name value\n") +{ + nb_cli_enqueue_change(vty, "./name", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + + +void cli_show_srte_policy_name(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " name %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +DEFPY(srte_policy_binding_sid, + srte_policy_binding_sid_cmd, + "binding-sid (16-1048575)$label", + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_CREATE, label_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_binding_sid, + srte_policy_no_binding_sid_cmd, + "no binding-sid [(16-1048575)]", + NO_STR + "Segment Routing Policy Binding-SID\n" + "SR Policy Binding-SID label\n") +{ + nb_cli_enqueue_change(vty, "./binding-sid", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_srte_policy_binding_sid(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " binding-sid %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +DEFPY(srte_policy_candidate_exp, + srte_policy_candidate_exp_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name \ + explicit segment-list WORD$list_name", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "explicit"); + nb_cli_enqueue_change(vty, "./segment-list-name", NB_OP_MODIFY, + list_name); + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY_NOSH( + srte_policy_candidate_dyn, + srte_policy_candidate_dyn_cmd, + "candidate-path preference (0-4294967295)$preference name WORD$name dynamic", + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Dynamic Path\n") +{ + char xpath[XPATH_MAXLEN + XPATH_CANDIDATE_BASELEN]; + int ret; + + snprintf(xpath, sizeof(xpath), "%s/candidate-path[preference='%s']", + VTY_CURR_XPATH, preference_str); + + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, preference_str); + nb_cli_enqueue_change(vty, "./name", NB_OP_MODIFY, name); + nb_cli_enqueue_change(vty, "./protocol-origin", NB_OP_MODIFY, "local"); + nb_cli_enqueue_change(vty, "./originator", NB_OP_MODIFY, "config"); + nb_cli_enqueue_change(vty, "./type", NB_OP_MODIFY, "dynamic"); + ret = nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); + + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(SR_CANDIDATE_DYN_NODE, xpath); + + return ret; +} + +DEFPY(srte_candidate_bandwidth, + srte_candidate_bandwidth_cmd, + "bandwidth BANDWIDTH$value [required$required]", + "Define a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth/required", + NB_OP_MODIFY, required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/bandwidth/value", + NB_OP_MODIFY, value); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_bandwidth, + srte_candidate_no_bandwidth_cmd, + "no bandwidth [BANDWIDTH$value] [required$required]", + NO_STR + "Remove a bandwidth constraint\n" + "Bandwidth value\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/bandwidth", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_affinity_filter, srte_candidate_affinity_filter_cmd, + "affinity $type BITPATTERN$value", + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + uint32_t filter; + char xpath[XPATH_CANDIDATE_MAXLEN]; + char decimal_value[11]; + + if (sscanf(value, "0x%x", &filter) != 1) { + vty_out(vty, "affinity type: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + snprintf(decimal_value, sizeof(decimal_value), "%u", filter); + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, decimal_value); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_affinity_filter, srte_candidate_no_affinity_filter_cmd, + "no affinity $type [BITPATTERN$value]", + NO_STR + "Affinity constraint\n" + "Exclude any matching link\n" + "Include any matching link\n" + "Include all matching links\n" + "Attribute filter bit pattern as an hexadecimal value from 0x00000000 to 0xFFFFFFFF\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./constraints/affinity/%s", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_metric, + srte_candidate_metric_cmd, + "metric [bound$bound] $type METRIC$value [required$required] [computed$computed]", + "Define a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n" + "Force the PCE to provide the computed path metric\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']/value", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, value); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/is-bound", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + (bound != NULL) ? "true" : "false"); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/required", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + snprintf(xpath, sizeof(xpath), + "./constraints/metrics[type='%s']/is-computed", type); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + computed ? "true" : "false"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_metric, + srte_candidate_no_metric_cmd, + "no metric [bound] $type [METRIC$value] [required$required] [computed$computed]", + NO_STR + "Remove a metric constraint\n" + "If the metric is bounded\n" + "IGP metric\n" + "TE metric\n" + "Hop Counts\n" + "Aggregate bandwidth consumption\n" + "Load of the most loaded link\n" + "Cumulative IGP cost\n" + "Cumulative TE cost\n" + "P2MP IGP metric\n" + "P2MP TE metric\n" + "P2MP hop count metric\n" + "Segment-ID (SID) Depth.\n" + "Path Delay metric\n" + "Path Delay Variation metric\n" + "Path Loss metric\n" + "P2MP Path Delay metric\n" + "P2MP Path Delay variation metric\n" + "P2MP Path Loss metric\n" + "Number of adaptations on a path\n" + "Number of layers on a path\n" + "Domain Count metric\n" + "Border Node Count metric\n" + "Metric value\n" + "Required constraint\n" + "Force the PCE to provide the computed path metric\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + snprintf(xpath, sizeof(xpath), "./constraints/metrics[type='%s']", + type); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_policy_no_candidate, + srte_policy_no_candidate_cmd, + "no candidate-path\ + preference (0-4294967295)$preference\ + [name WORD\ + <\ + explicit segment-list WORD\ + |dynamic\ + >]", + NO_STR + "Segment Routing Policy Candidate Path\n" + "Segment Routing Policy Candidate Path Preference\n" + "Administrative Preference\n" + "Segment Routing Policy Candidate Path Name\n" + "Symbolic Name\n" + "Explicit Path\n" + "List of SIDs\n" + "Name of the Segment List\n" + "Dynamic Path\n") +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./candidate-path[preference='%s']", + preference_str); +} + +DEFPY(srte_candidate_objfun, + srte_candidate_objfun_cmd, + "objective-function $type [required$required]", + "Define an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + char xpath[XPATH_CANDIDATE_MAXLEN]; + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + snprintf(xpath, sizeof(xpath), + "./constraints/objective-function/required"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + required ? "true" : "false"); + nb_cli_enqueue_change(vty, "./constraints/objective-function/type", + NB_OP_MODIFY, type); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(srte_candidate_no_objfun, + srte_candidate_no_objfun_cmd, + "no objective-function [] [required$required]", + NO_STR + "Remove an objective function constraint\n" + "Minimum Cost Path\n" + "Minimum Load Path\n" + "Maximum residual Bandwidth Path\n" + "Minimize aggregate Bandwidth Consumption\n" + "Minimize the Load of the most loaded Link\n" + "Minimize the Cumulative Cost of a set of paths\n" + "Shortest Path Tree\n" + "Minimum Cost Tree\n" + "Minimum Packet Loss Path\n" + "Maximum Under-Utilized Path\n" + "Maximum Reserved Under-Utilized Path\n" + "Minimize the number of Transit Domains\n" + "Minimize the number of Border Nodes\n" + "Minimize the number of Common Transit Domains\n" + "Minimize the number of Shared Links\n" + "Minimize the number of Shared SRLGs\n" + "Minimize the number of Shared Nodes\n" + "Required constraint\n") +{ + nb_cli_enqueue_change(vty, "./constraints/objective-function", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_MCP: + return "mcp"; + case OBJFUN_MLP: + return "mlp"; + case OBJFUN_MBP: + return "mbp"; + case OBJFUN_MBC: + return "mbc"; + case OBJFUN_MLL: + return "mll"; + case OBJFUN_MCC: + return "mcc"; + case OBJFUN_SPT: + return "spt"; + case OBJFUN_MCT: + return "mct"; + case OBJFUN_MPLP: + return "mplp"; + case OBJFUN_MUP: + return "mup"; + case OBJFUN_MRUP: + return "mrup"; + case OBJFUN_MTD: + return "mtd"; + case OBJFUN_MBN: + return "mbn"; + case OBJFUN_MCTD: + return "mctd"; + case OBJFUN_MSL: + return "msl"; + case OBJFUN_MSS: + return "mss"; + case OBJFUN_MSN: + return "msn"; + case OBJFUN_UNDEFINED: + return NULL; + } + + assert(!"Reached end of function we should never hit"); +} + +DEFPY_NOSH(show_debugging_pathd, show_debugging_pathd_cmd, + "show debugging [pathd]", + SHOW_STR + "State of each debugging option\n" + "pathd module debugging\n") +{ + + vty_out(vty, "Path debugging status:\n"); + + cmd_show_lib_debugs(vty); + /* nothing to do here */ + path_ted_show_debugging(vty); + path_policy_show_debugging(vty); + return CMD_SUCCESS; +} + +DEFPY(debug_path_policy, debug_path_policy_cmd, "[no] debug pathd policy", + NO_STR DEBUG_STR + "path debugging\n" + "policy debugging\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + bool no_debug = no; + + DEBUG_MODE_SET(&path_policy_debug, mode, !no); + DEBUG_FLAGS_SET(&path_policy_debug, PATH_POLICY_DEBUG_BASIC, !no_debug); + return CMD_SUCCESS; +} + +static const char *metric_type_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "igp"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "te"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "hc"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "abc"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "lmll"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "cigp"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "cte"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "pigp"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "pte"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "phc"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "msd"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "pd"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "pdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "pl"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "ppd"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "ppdv"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "ppl"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "nap"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "nlp"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "dc"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "bnc"; + default: + return NULL; + } +} + +static void config_write_float(struct vty *vty, float value) +{ + if (fabs(truncf(value) - value) < FLT_EPSILON) { + vty_out(vty, " %d", (int)value); + return; + } else { + vty_out(vty, " %f", value); + } +} + +static void config_write_metric(struct vty *vty, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound, + bool is_computed) +{ + const char *name = metric_type_name(type); + if (name == NULL) + return; + vty_out(vty, " metric %s%s", is_bound ? "bound " : "", + metric_type_name(type)); + config_write_float(vty, value); + vty_out(vty, required ? " required" : ""); + vty_out(vty, is_computed ? " computed" : ""); + vty_out(vty, "\n"); +} + +static int config_write_metric_cb(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + enum srte_candidate_metric_type type; + bool required, is_bound = false, is_computed = false; + float value; + + type = yang_dnode_get_enum(dnode, "type"); + value = (float)yang_dnode_get_dec64(dnode, "value"); + required = yang_dnode_get_bool(dnode, "required"); + if (yang_dnode_exists(dnode, "is-bound")) + is_bound = yang_dnode_get_bool(dnode, "is-bound"); + if (yang_dnode_exists(dnode, "is-computed")) + is_computed = yang_dnode_get_bool(dnode, "is-computed"); + + config_write_metric(vty, type, value, required, is_bound, is_computed); + return YANG_ITER_CONTINUE; +} + +void cli_show_srte_policy_candidate_path(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + float bandwidth; + uint32_t affinity; + bool required; + enum objfun_type objfun_type; + const char *type = yang_dnode_get_string(dnode, "type"); + + vty_out(vty, " candidate-path preference %s name %s %s", + yang_dnode_get_string(dnode, "preference"), + yang_dnode_get_string(dnode, "name"), type); + if (strmatch(type, "explicit")) + vty_out(vty, " segment-list %s", + yang_dnode_get_string(dnode, "segment-list-name")); + vty_out(vty, "\n"); + + if (strmatch(type, "dynamic")) { + if (yang_dnode_exists(dnode, "constraints/bandwidth")) { + bandwidth = (float)yang_dnode_get_dec64( + dnode, "./constraints/bandwidth/value"); + required = yang_dnode_get_bool( + dnode, "./constraints/bandwidth/required"); + vty_out(vty, " bandwidth"); + config_write_float(vty, bandwidth); + if (required) + vty_out(vty, " required"); + vty_out(vty, "\n"); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/exclude-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/exclude-any"); + vty_out(vty, " affinity exclude-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-any")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-any"); + vty_out(vty, " affinity include-any 0x%08x\n", + affinity); + } + if (yang_dnode_exists(dnode, + "./constraints/affinity/include-all")) { + affinity = yang_dnode_get_uint32( + dnode, "./constraints/affinity/include-all"); + vty_out(vty, " affinity include-all 0x%08x\n", + affinity); + } + yang_dnode_iterate(config_write_metric_cb, vty, dnode, + "./constraints/metrics"); + if (yang_dnode_exists(dnode, + "./constraints/objective-function")) { + objfun_type = yang_dnode_get_enum(dnode, + "./constraints/objective-function/type"); + required = yang_dnode_get_bool(dnode, + "./constraints/objective-function/required"); + vty_out(vty, " objective-function %s%s\n", + objfun_type_name(objfun_type), + required ? " required" : ""); + } + } +} + +void cli_show_srte_policy_candidate_path_end(struct vty *vty, + const struct lyd_node *dnode) +{ + const char *type = yang_dnode_get_string(dnode, "type"); + + if (strmatch(type, "dynamic")) + vty_out(vty, " exit\n"); +} + +static int config_write_dnode(const struct lyd_node *dnode, void *arg) +{ + struct vty *vty = arg; + + nb_cli_show_dnode_cmds(vty, dnode, false); + + return YANG_ITER_CONTINUE; +} + +int config_write_segment_routing(struct vty *vty) +{ + vty_out(vty, "segment-routing\n"); + vty_out(vty, " traffic-eng\n"); + + path_ted_config_write(vty); + + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/segment-list"); + yang_dnode_iterate(config_write_dnode, vty, running_config->dnode, + "/frr-pathd:pathd/srte/policy"); + + hook_call(pathd_srte_config_write, vty); + + vty_out(vty, " exit\n"); + vty_out(vty, "exit\n"); + + return 1; +} + +static int path_policy_cli_debug_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&path_policy_debug, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&path_policy_debug, + PATH_POLICY_DEBUG_BASIC)) + vty_out(vty, "debug pathd policy\n"); + return 1; + } + return 0; +} + +static int path_policy_cli_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&path_policy_debug, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&path_policy_debug, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&path_policy_debug); + + return 0; +} + +void path_cli_init(void) +{ + hook_register(nb_client_debug_config_write, + path_policy_cli_debug_config_write); + hook_register(nb_client_debug_set_all, path_policy_cli_debug_set_all); + + install_node(&segment_routing_node); + install_node(&sr_traffic_eng_node); + install_node(&srte_segment_list_node); + install_node(&srte_policy_node); + install_node(&srte_candidate_dyn_node); + install_default(SEGMENT_ROUTING_NODE); + install_default(SR_TRAFFIC_ENG_NODE); + install_default(SR_SEGMENT_LIST_NODE); + install_default(SR_POLICY_NODE); + install_default(SR_CANDIDATE_DYN_NODE); + + install_element(ENABLE_NODE, &show_debugging_pathd_cmd); + install_element(ENABLE_NODE, &show_srte_policy_cmd); + install_element(ENABLE_NODE, &show_srte_policy_detail_cmd); + + install_element(ENABLE_NODE, &debug_path_policy_cmd); + install_element(CONFIG_NODE, &debug_path_policy_cmd); + + install_element(CONFIG_NODE, &segment_routing_cmd); + install_element(SEGMENT_ROUTING_NODE, &sr_traffic_eng_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_segment_list_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_segment_list_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_segment_cmd); + install_element(SR_SEGMENT_LIST_NODE, + &srte_segment_list_no_segment_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_policy_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &srte_no_policy_cmd); + install_element(SR_POLICY_NODE, &srte_policy_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_name_cmd); + install_element(SR_POLICY_NODE, &srte_policy_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_binding_sid_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_exp_cmd); + install_element(SR_POLICY_NODE, &srte_policy_candidate_dyn_cmd); + install_element(SR_POLICY_NODE, &srte_policy_no_candidate_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_bandwidth_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_affinity_filter_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_metric_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_objfun_cmd); + install_element(SR_CANDIDATE_DYN_NODE, + &srte_candidate_no_objfun_cmd); +} diff --git a/pathd/path_debug.c b/pathd/path_debug.c new file mode 100644 index 0000000..cae212d --- /dev/null +++ b/pathd/path_debug.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "printfrr.h" +#include "ipaddr.h" + +#include "pathd/path_debug.h" + +THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +/** + * Gives the string representation of an srte_protocol_origin enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_protocol_origin_name(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_UNDEFINED: + return "UNDEFINED"; + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "LOCAL"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an srte_candidate_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *srte_candidate_type_name(enum srte_candidate_type type) +{ + switch (type) { + case SRTE_CANDIDATE_TYPE_EXPLICIT: + return "EXPLICIT"; + case SRTE_CANDIDATE_TYPE_DYNAMIC: + return "DYNAMIC"; + case SRTE_CANDIDATE_TYPE_UNDEFINED: + return "UNDEFINED"; + default: + return "UNKNOWN"; + } +} + +/** + * Gives the string representation of an objfun_type enum value. + * + * @param origin The enum value to convert to string + * @return a constant string representation of the enum value + */ +const char *objfun_type_name(enum objfun_type type) +{ + switch (type) { + case OBJFUN_UNDEFINED: + return "UNDEFINED"; + case OBJFUN_MCP: + return "MCP"; + case OBJFUN_MLP: + return "MLP"; + case OBJFUN_MBP: + return "MBP"; + case OBJFUN_MBC: + return "MBC"; + case OBJFUN_MLL: + return "MLL"; + case OBJFUN_MCC: + return "MCC"; + case OBJFUN_SPT: + return "SPT"; + case OBJFUN_MCT: + return "MCT"; + case OBJFUN_MPLP: + return "MPLP"; + case OBJFUN_MUP: + return "MUP"; + case OBJFUN_MRUP: + return "MRUP"; + case OBJFUN_MTD: + return "MTD"; + case OBJFUN_MBN: + return "MBN"; + case OBJFUN_MCTD: + return "MCTD"; + case OBJFUN_MSL: + return "MSL"; + case OBJFUN_MSS: + return "MSS"; + case OBJFUN_MSN: + return "MSN"; + default: + return "UNKNOWN"; + } +} diff --git a/pathd/path_debug.h b/pathd/path_debug.h new file mode 100644 index 0000000..12a21cb --- /dev/null +++ b/pathd/path_debug.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_DEBUG_H_ +#define _PATH_DEBUG_H_ + +#include "pathd/pathd.h" + +#ifdef __GNUC__ +#define THREAD_DATA __thread +#else +#define THREAD_DATA +#endif + +#define DEBUG_IDENT_SIZE 4 +#define DEBUG_BUFF_SIZE 4096 +#define TUP(A, B) ((((uint32_t)(A)) << 16) | ((uint32_t)(B))) +#define PATHD_FORMAT_INIT() _debug_buff[0] = 0 +#define PATHD_FORMAT(fmt, ...) \ + csnprintfrr(_debug_buff, DEBUG_BUFF_SIZE, fmt, ##__VA_ARGS__) +#define PATHD_FORMAT_FINI() _debug_buff + +extern THREAD_DATA char _debug_buff[DEBUG_BUFF_SIZE]; + +const char *srte_protocol_origin_name(enum srte_protocol_origin origin); +const char *srte_candidate_type_name(enum srte_candidate_type type); +const char *objfun_type_name(enum objfun_type type); + +#endif // _PATH_DEBUG_H_ \ No newline at end of file diff --git a/pathd/path_errors.c b/pathd/path_errors.c new file mode 100644 index 0000000..53187fd --- /dev/null +++ b/pathd/path_errors.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pathd-specific error messages. + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "lib/ferr.h" +#include "path_errors.h" + +/* clang-format off */ +static struct log_ref ferr_path_err[] = { + { + .code = EC_PATH_SYSTEM_CALL, + .title = "Thread setup error", + .description = "A system call for creating, or setting up PCEP module's pthread failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_INIT, + .title = "PCC initialization error", + .description = "pceplib PCC initialization call failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_FINI, + .title = "PCC finalization error", + .description = "pceplib PCC finalization call failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = EC_PATH_PCEP_PCC_CONF_UPDATE, + .title = "PCC configuration update error", + .description = "The update of the PCC configuration failed", + .suggestion = "Open an Issue with all relevant log files and restart FRR" + }, + { + .code = END_FERR, + } +}; + +static struct log_ref ferr_path_warn[] = { + { + .code = EC_PATH_PCEP_LIB_CONNECT, + .title = "PCC connection error", + .description = "The PCEP module failed to connected to configured PCE", + .suggestion = "Check the connectivity between the PCC and the PCE" + }, + { + .code = EC_PATH_PCEP_PROTOCOL_ERROR, + .title = "PCEP protocol error", + .description = "The PCE did not respect the PCEP protocol", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + .title = "PCC connection error", + .description = "The PCEP module did not try to connect because it is missing a source address", + .suggestion = "Wait for the router ID to be defined or set the PCC source address in the configuration" + }, + { + .code = EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + .title = "Recoverable internal error", + .description = "Some recoverable internal error", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + .title = "Unsupported PCEP feature", + .description = "Received an unsupported PCEP message", + .suggestion = "The PCC and PCE are probably not compatible. Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + .title = "Unexpected PCEP message", + .description = "The PCEP module received an unexpected PCEP message", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + .title = "Unexpected pceplib event", + .description = "The PCEP module received an unexpected event from pceplib", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + .title = "Unexpected PCEP object", + .description = "The PCEP module received an unexpected PCEP object from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + .title = "Unexpected PCEP TLV", + .description = "The PCEP module received an unexpected PCEP TLV from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + .title = "Unexpected PCEP ERO sub-object", + .description = "The PCEP module received an unexpected PCEP ERO sub-object from a PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_UNEXPECTED_SR_NAI, + .title = "Unexpected PCEP SR segment NAI", + .description = "The PCEP module received an SR segment with an unsupported NAI specification from the PCE", + .suggestion = "Open an Issue with all relevant log files" + }, + { + .code = EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT, + .title = "Computation request timeout", + .description = "The PCE did not respond in time to the PCC computation request", + .suggestion = "The PCE is overloaded or incompatible with the PCC, try with a different PCE" + }, + { + .code = END_FERR, + } + +}; +/* clang-format on */ + +void path_error_init(void) +{ + log_ref_add(ferr_path_err); + log_ref_add(ferr_path_warn); +} diff --git a/pathd/path_errors.h b/pathd/path_errors.h new file mode 100644 index 0000000..69dd023 --- /dev/null +++ b/pathd/path_errors.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef __PATH_ERRORS_H__ +#define __PATH_ERRORS_H__ + +#include "lib/ferr.h" + +enum path_log_refs { + EC_PATH_PCEP_INIT = PATH_FERR_START, + EC_PATH_SYSTEM_CALL, + EC_PATH_PCEP_PCC_INIT, + EC_PATH_PCEP_PCC_FINI, + EC_PATH_PCEP_PCC_CONF_UPDATE, + EC_PATH_PCEP_LIB_CONNECT, + EC_PATH_PCEP_PROTOCOL_ERROR, + EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + EC_PATH_PCEP_UNEXPECTED_SR_NAI, + EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT +}; + +extern void path_error_init(void); + +#endif diff --git a/pathd/path_main.c b/pathd/path_main.c new file mode 100644 index 0000000..23cbb9c --- /dev/null +++ b/pathd/path_main.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "libfrr.h" +#include "vrf.h" +#include "filter.h" + +#include "pathd.h" +#include "path_nb.h" +#include "path_zebra.h" +#include "path_errors.h" +#include "path_ted.h" + +char backup_config_file[256]; + +zebra_capabilities_t _caps_p[] = {}; + +struct zebra_privs_t pathd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct event_loop *master; + +static struct frr_daemon_info pathd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, pathd_di.config_file, config_default); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + zlog_notice("Unregister from opaque,etc "); + pathd_shutdown(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t path_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *pathd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_pathd_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(pathd, PATH, + .vty_port = PATH_VTY_PORT, + .proghelp = "Implementation of PATH.", + + .signals = path_signals, + .n_signals = array_size(path_signals), + + .privs = &pathd_privs, + + .yang_modules = pathd_yang_modules, + .n_yang_modules = array_size(pathd_yang_modules), +); +/* clang-format on */ + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pathd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + access_list_init(); + + path_error_init(); + path_zebra_init(master); + path_cli_init(); + path_ted_init(master); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/pathd/path_nb.c b/pathd/path_nb.c new file mode 100644 index 0000000..e1c0cc3 --- /dev/null +++ b/pathd/path_nb.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/path_nb.h" + +static int iter_objfun_cb(const struct lyd_node *dnode, void *arg); +static int dummy_create(struct nb_cb_create_args *args); +static int dummy_modify(struct nb_cb_modify_args *args); +static int dummy_destroy(struct nb_cb_destroy_args *args); + +struct of_cb_pref { + uint32_t index; + enum objfun_type type; + struct of_cb_pref *next; +}; + +struct of_cb_args { + struct of_cb_pref *first; + uint32_t free_slot; + struct of_cb_pref prefs[MAX_OBJFUN_TYPE]; +}; + +/* clang-format off */ +const struct frr_yang_module_info frr_pathd_info = { + .name = "frr-pathd", + .nodes = { + { + .xpath = "/frr-pathd:pathd", + .cbs = { + .apply_finish = pathd_apply_finish, + }, + .priority = NB_DFLT_PRIORITY + 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list", + .cbs = { + .create = pathd_srte_segment_list_create, + .cli_show = cli_show_srte_segment_list, + .cli_show_end = cli_show_srte_segment_list_end, + .destroy = pathd_srte_segment_list_destroy, + .get_next = pathd_srte_segment_list_get_next, + .get_keys = pathd_srte_segment_list_get_keys, + .lookup_entry = pathd_srte_segment_list_lookup_entry, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/protocol-origin", + .cbs = { + .modify = pathd_srte_segment_list_protocol_origin_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/originator", + .cbs = { + .modify = pathd_srte_segment_list_originator_modify, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment", + .cbs = { + .create = pathd_srte_segment_list_segment_create, + .cli_show = cli_show_srte_segment_list_segment, + .destroy = pathd_srte_segment_list_segment_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/sid-value", + .cbs = { + .modify = pathd_srte_segment_list_segment_sid_value_modify, + .destroy = pathd_srte_segment_list_segment_sid_value_destroy, + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_segment_list_segment_nai_destroy, + .apply_finish = pathd_srte_segment_list_segment_nai_apply_finish + }, + .priority = NB_DFLT_PRIORITY - 1 + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-address", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/local-prefix-len", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-address", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/remote-interface", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/segment-list/segment/nai/algorithm", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy", + .cbs = { + .create = pathd_srte_policy_create, + .cli_show = cli_show_srte_policy, + .cli_show_end = cli_show_srte_policy_end, + .destroy = pathd_srte_policy_destroy, + .get_next = pathd_srte_policy_get_next, + .get_keys = pathd_srte_policy_get_keys, + .lookup_entry = pathd_srte_policy_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/name", + .cbs = { + .modify = pathd_srte_policy_name_modify, + .cli_show = cli_show_srte_policy_name, + .destroy = pathd_srte_policy_name_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/binding-sid", + .cbs = { + .modify = pathd_srte_policy_binding_sid_modify, + .cli_show = cli_show_srte_policy_binding_sid, + .destroy = pathd_srte_policy_binding_sid_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/is-operational", + .cbs = { + .get_elem = pathd_srte_policy_is_operational_get_elem + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path", + .cbs = { + .create = pathd_srte_policy_candidate_path_create, + .cli_show = cli_show_srte_policy_candidate_path, + .cli_show_end = cli_show_srte_policy_candidate_path_end, + .destroy = pathd_srte_policy_candidate_path_destroy, + .get_next = pathd_srte_policy_candidate_path_get_next, + .get_keys = pathd_srte_policy_candidate_path_get_keys, + .lookup_entry = pathd_srte_policy_candidate_path_lookup_entry, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/name", + .cbs = { + .modify = pathd_srte_policy_candidate_path_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/is-best-candidate-path", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/protocol-origin", + .cbs = { + .modify = pathd_srte_policy_candidate_path_protocol_origin_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/originator", + .cbs = { + .modify = pathd_srte_policy_candidate_path_originator_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/discriminator", + .cbs = { + .get_elem = pathd_srte_policy_candidate_path_discriminator_get_elem, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/type", + .cbs = { + .modify = pathd_srte_policy_candidate_path_type_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/segment-list-name", + .cbs = { + .destroy = pathd_srte_policy_candidate_path_segment_list_name_destroy, + .modify = pathd_srte_policy_candidate_path_segment_list_name_modify, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_bandwidth_destroy, + .apply_finish = pathd_srte_policy_candidate_path_bandwidth_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_exclude_any_modify, + .destroy = pathd_srte_policy_candidate_path_exclude_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_any_modify, + .destroy = pathd_srte_policy_candidate_path_include_any_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all", + .cbs = { + .modify = pathd_srte_policy_candidate_path_include_all_modify, + .destroy = pathd_srte_policy_candidate_path_include_all_destroy, + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_metrics_destroy, + .apply_finish = pathd_srte_policy_candidate_path_metrics_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/value", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-bound", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics/is-computed", + .cbs = {.modify = dummy_modify, .destroy = dummy_destroy} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function", + .cbs = { + .create = dummy_create, + .destroy = pathd_srte_policy_candidate_path_objfun_destroy, + .apply_finish = pathd_srte_policy_candidate_path_objfun_apply_finish + } + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/required", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = "/frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function/type", + .cbs = {.modify = dummy_modify} + }, + { + .xpath = NULL, + }, + } +}; + +void iter_objfun_prefs(const struct lyd_node *dnode, const char* path, + of_pref_cp_t fun, void *arg) +{ + struct of_cb_args args = {0}; + struct of_cb_pref *p; + + yang_dnode_iterate(iter_objfun_cb, &args, dnode, "%s", path); + for (p = args.first; p != NULL; p = p->next) + fun(p->type, arg); +} + +int iter_objfun_cb(const struct lyd_node *dnode, void *arg) +{ + struct of_cb_args *of_arg = arg; + struct of_cb_pref *pref; + struct of_cb_pref **p; + + if (of_arg->free_slot >= MAX_OBJFUN_TYPE) + return YANG_ITER_STOP; + + pref = &of_arg->prefs[of_arg->free_slot++]; + + pref->index = yang_dnode_get_uint32(dnode, "index"); + pref->type = yang_dnode_get_enum(dnode, "type"); + + /* Simplistic insertion sort */ + p = &of_arg->first; + while (true) { + if (*p == NULL) { + *p = pref; + break; + } + if ((*p)->index >= pref->index) { + pref->next = *p; + *p = pref; + break; + } + p = &(*p)->next; + } + + return YANG_ITER_CONTINUE; +} + +int dummy_create(struct nb_cb_create_args *args) +{ + return NB_OK; +} + +int dummy_modify(struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +int dummy_destroy(struct nb_cb_destroy_args *args) +{ + return NB_OK; +} diff --git a/pathd/path_nb.h b/pathd/path_nb.h new file mode 100644 index 0000000..21876d7 --- /dev/null +++ b/pathd/path_nb.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _FRR_PATH_NB_H_ +#define _FRR_PATH_NB_H_ + +#include "pathd/pathd.h" + +extern const struct frr_yang_module_info frr_pathd_info; + +/* Mandatory callbacks. */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args); + +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args); + +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args); +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args); +int pathd_srte_segment_list_segment_nai_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_segment_list_segment_sid_value_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_create(struct nb_cb_create_args *args); +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args); +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args); +const void * +pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args); +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args); +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args); +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args); +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_name_modify( + struct nb_cb_modify_args *args); +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args); +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args); +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args); +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args); +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args); +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args); +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args); +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args); +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args); +int pathd_srte_policy_candidate_path_type_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args); +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args); + +/* Optional 'apply_finish' callbacks. */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_srte_segment_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_segment_list_end(struct vty *vty, + const struct lyd_node *dnode); +void cli_show_srte_segment_list_segment(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_end(struct vty *vty, const struct lyd_node *dnode); +void cli_show_srte_policy_name(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_binding_sid(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_candidate_path(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_srte_policy_candidate_path_end(struct vty *vty, + const struct lyd_node *dnode); + +/* Utility functions */ +typedef void (*of_pref_cp_t)(enum objfun_type type, void *arg); +void iter_objfun_prefs(const struct lyd_node *dnode, const char *path, + of_pref_cp_t fun, void *arg); + +#endif /* _FRR_PATH_NB_H_ */ diff --git a/pathd/path_nb_config.c b/pathd/path_nb_config.c new file mode 100644 index 0000000..48531ba --- /dev/null +++ b/pathd/path_nb_config.c @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include +#include + +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/path_zebra.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd + */ +void pathd_apply_finish(struct nb_cb_apply_finish_args *args) +{ + srte_apply_changes(); +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +int pathd_srte_segment_list_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + const char *name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + name = yang_dnode_get_string(args->dnode, "name"); + segment_list = srte_segment_list_add(name); + nb_running_set_entry(args->dnode, segment_list); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + + return NB_OK; +} + +int pathd_srte_segment_list_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_unset_entry(args->dnode); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/protocol-origin + */ +int pathd_srte_segment_list_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + segment_list->protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/originator + */ +int pathd_srte_segment_list_originator_modify(struct nb_cb_modify_args *args) +{ + struct srte_segment_list *segment_list; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(segment_list->originator, originator, + sizeof(segment_list->originator)); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment + */ +int pathd_srte_segment_list_segment_create(struct nb_cb_create_args *args) +{ + struct srte_segment_list *segment_list; + struct srte_segment_entry *segment; + uint32_t index; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment_list = nb_running_get_entry(args->dnode, NULL, true); + index = yang_dnode_get_uint32(args->dnode, "index"); + segment = srte_segment_entry_add(segment_list, index); + nb_running_set_entry(args->dnode, segment); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_unset_entry(args->dnode); + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + srte_segment_entry_del(segment); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/segment-list/segment/sid-value + */ +int pathd_srte_segment_list_segment_sid_value_modify( + struct nb_cb_modify_args *args) +{ + mpls_label_t sid_value; + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + sid_value = yang_dnode_get_uint32(args->dnode, NULL); + segment->sid_value = sid_value; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + +int pathd_srte_segment_list_segment_sid_value_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + segment->sid_value = MPLS_LABEL_NONE; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + return NB_OK; +} + + +int pathd_srte_segment_list_segment_nai_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_segment_entry *segment; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + segment = nb_running_get_entry(args->dnode, NULL, true); + segment->nai_type = SRTE_SEGMENT_NAI_TYPE_NONE; + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + + return NB_OK; +} + +void pathd_srte_segment_list_segment_nai_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_segment_entry *segment; + enum srte_segment_nai_type type; + struct ipaddr local_addr, remote_addr; + uint32_t local_iface = 0, remote_iface = 0; + uint8_t algo = 0, local_prefix_len = 0; + const char *algo_buf, *local_prefix_len_buf; + + segment = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "type"); + + yang_dnode_get_ip(&local_addr, args->dnode, "local-address"); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + yang_dnode_get_ip(&remote_addr, args->dnode, + "./remote-address"); + local_iface = + yang_dnode_get_uint32(args->dnode, "local-interface"); + remote_iface = yang_dnode_get_uint32(args->dnode, + "./remote-interface"); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + algo_buf = yang_dnode_get_string(args->dnode, "algorithm"); + algo = atoi(algo_buf); + local_prefix_len_buf = yang_dnode_get_string( + args->dnode, "./local-prefix-len"); + local_prefix_len = atoi(local_prefix_len_buf); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + local_iface = + yang_dnode_get_uint32(args->dnode, "local-interface"); + local_prefix_len_buf = yang_dnode_get_string( + args->dnode, "./local-prefix-len"); + local_prefix_len = atoi(local_prefix_len_buf); + break; + case SRTE_SEGMENT_NAI_TYPE_NONE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + break; + } + + zlog_debug(" Segment list name (%d) index (%s) ", segment->index, + segment->segment_list->name); + if (srte_segment_entry_set_nai(segment, type, &local_addr, local_iface, + &remote_addr, remote_iface, algo, + local_prefix_len)) + SET_FLAG(segment->segment_list->flags, + F_SEGMENT_LIST_SID_CONFLICT); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +int pathd_srte_policy_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + uint32_t color; + struct ipaddr endpoint; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + color = yang_dnode_get_uint32(args->dnode, "color"); + yang_dnode_get_ip(&endpoint, args->dnode, "endpoint"); + policy = srte_policy_add(color, &endpoint, SRTE_ORIGIN_LOCAL, NULL); + + nb_running_set_entry(args->dnode, policy); + SET_FLAG(policy->flags, F_POLICY_NEW); + + return NB_OK; +} + +int pathd_srte_policy_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_unset_entry(args->dnode); + SET_FLAG(policy->flags, F_POLICY_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/name + */ +int pathd_srte_policy_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + const char *name; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + + if (args->event == NB_EV_VALIDATE) { + /* the policy name is fixed after setting it once */ + if (strlen(policy->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The SR Policy name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(policy->name, name, sizeof(policy->name)); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_name_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + policy->name[0] = '\0'; + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/binding-sid + */ +int pathd_srte_policy_binding_sid_modify(struct nb_cb_modify_args *args) +{ + struct srte_policy *policy; + mpls_label_t binding_sid; + + binding_sid = yang_dnode_get_uint32(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + if (path_zebra_request_label(binding_sid) < 0) + return NB_ERR_RESOURCE; + break; + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + policy = nb_running_get_entry(args->dnode, NULL, true); + srte_policy_update_binding_sid(policy, binding_sid); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + break; + } + + return NB_OK; +} + +int pathd_srte_policy_binding_sid_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_policy *policy; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + srte_policy_update_binding_sid(policy, MPLS_LABEL_NONE); + SET_FLAG(policy->flags, F_POLICY_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +int pathd_srte_policy_candidate_path_create(struct nb_cb_create_args *args) +{ + struct srte_policy *policy; + struct srte_candidate *candidate; + uint32_t preference; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + policy = nb_running_get_entry(args->dnode, NULL, true); + preference = yang_dnode_get_uint32(args->dnode, "preference"); + candidate = + srte_candidate_add(policy, preference, SRTE_ORIGIN_LOCAL, NULL); + nb_running_set_entry(args->dnode, candidate); + SET_FLAG(candidate->flags, F_CANDIDATE_NEW); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_destroy(struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_unset_entry(args->dnode); + SET_FLAG(candidate->flags, F_CANDIDATE_DELETED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/name + */ +int pathd_srte_policy_candidate_path_name_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *name; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate name is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the name was provided */ + if (candidate && strlen(candidate->name) > 0) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate name is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + name = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->name, name, sizeof(candidate->name)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + + +static int affinity_filter_modify(struct nb_cb_modify_args *args, + enum affinity_filter_type type) +{ + uint32_t filter; + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + filter = yang_dnode_get_uint32(args->dnode, NULL); + srte_candidate_set_affinity_filter(candidate, type, filter); + + return NB_OK; +} + +static int affinity_filter_destroy(struct nb_cb_destroy_args *args, + enum affinity_filter_type type) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_affinity_filter(candidate, type); + + return NB_OK; +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/exclude-any + */ + +int pathd_srte_policy_candidate_path_exclude_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_exclude_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_EXCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-any + */ +int pathd_srte_policy_candidate_path_include_any_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ANY); +} + +int pathd_srte_policy_candidate_path_include_any_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ANY); +} + + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/affinity/include-all + */ +int pathd_srte_policy_candidate_path_include_all_modify( + struct nb_cb_modify_args *args) +{ + return affinity_filter_modify(args, AFFINITY_FILTER_INCLUDE_ALL); +} + +int pathd_srte_policy_candidate_path_include_all_destroy( + struct nb_cb_destroy_args *args) +{ + return affinity_filter_destroy(args, AFFINITY_FILTER_INCLUDE_ALL); +} + + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/metrics + */ +int pathd_srte_policy_candidate_path_metrics_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "type"); + srte_candidate_unset_metric(candidate, type); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_metrics_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_metric_type type; + float value; + bool required, is_bound = false, is_computed = false; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, "type"); + value = (float)yang_dnode_get_dec64(args->dnode, "value"); + required = yang_dnode_get_bool(args->dnode, "required"); + if (yang_dnode_exists(args->dnode, "is-bound")) + is_bound = yang_dnode_get_bool(args->dnode, "is-bound"); + if (yang_dnode_exists(args->dnode, "is-computed")) + is_computed = yang_dnode_get_bool(args->dnode, "is-computed"); + + srte_candidate_set_metric(candidate, type, value, required, is_bound, + is_computed); +} + +/* + * XPath: + * /frr-pathd:pathd/srte/policy/candidate-path/constraints/objective-function + */ +int pathd_srte_policy_candidate_path_objfun_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_objfun(candidate); + + return NB_OK; +} + +void pathd_srte_policy_candidate_path_objfun_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + enum objfun_type type; + bool required; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + required = yang_dnode_get_bool(args->dnode, "required"); + type = yang_dnode_get_enum(args->dnode, "type"); + srte_candidate_set_objfun(candidate, required, type); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/protocol-origin + */ +int pathd_srte_policy_candidate_path_protocol_origin_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_protocol_origin protocol_origin; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + protocol_origin = yang_dnode_get_enum(args->dnode, NULL); + candidate->protocol_origin = protocol_origin; + candidate->lsp->protocol_origin = protocol_origin; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/originator + */ +int pathd_srte_policy_candidate_path_originator_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *originator; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + originator = yang_dnode_get_string(args->dnode, NULL); + strlcpy(candidate->originator, originator, + sizeof(candidate->originator)); + strlcpy(candidate->lsp->originator, originator, + sizeof(candidate->lsp->originator)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/type + */ +int pathd_srte_policy_candidate_path_type_modify(struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + enum srte_candidate_type type; + char xpath[XPATH_MAXLEN]; + char xpath_buf[XPATH_MAXLEN - 3]; + + if (args->event != NB_EV_APPLY && args->event != NB_EV_VALIDATE) + return NB_OK; + + /* the candidate type is fixed after setting it once, this is checked + * here */ + if (args->event == NB_EV_VALIDATE) { + /* first get the precise path to the candidate path */ + yang_dnode_get_path(args->dnode, xpath_buf, sizeof(xpath_buf)); + snprintf(xpath, sizeof(xpath), "%s%s", xpath_buf, "/.."); + + candidate = nb_running_get_entry_non_rec(NULL, xpath, false); + + /* then check if it exists and if the type was provided */ + if (candidate + && candidate->type != SRTE_CANDIDATE_TYPE_UNDEFINED) { + flog_warn(EC_LIB_NB_CB_CONFIG_VALIDATE, + "The candidate type is fixed!"); + return NB_ERR_RESOURCE; + } else + return NB_OK; + } + + candidate = nb_running_get_entry(args->dnode, NULL, true); + + type = yang_dnode_get_enum(args->dnode, NULL); + candidate->type = type; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/segment-list-name + */ +int pathd_srte_policy_candidate_path_segment_list_name_modify( + struct nb_cb_modify_args *args) +{ + struct srte_candidate *candidate; + const char *segment_list_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + segment_list_name = yang_dnode_get_string(args->dnode, NULL); + + candidate->segment_list = srte_segment_list_find(segment_list_name); + candidate->lsp->segment_list = candidate->segment_list; + assert(candidate->segment_list); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +int pathd_srte_policy_candidate_path_segment_list_name_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + candidate = nb_running_get_entry(args->dnode, NULL, true); + candidate->segment_list = NULL; + candidate->lsp->segment_list = NULL; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + return NB_OK; +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/constraints/bandwidth + */ +void pathd_srte_policy_candidate_path_bandwidth_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct srte_candidate *candidate; + float value; + bool required; + + assert(args->context != NULL); + + candidate = nb_running_get_entry(args->dnode, NULL, true); + value = (float)yang_dnode_get_dec64(args->dnode, "value"); + required = yang_dnode_get_bool(args->dnode, "required"); + srte_candidate_set_bandwidth(candidate, value, required); +} + +int pathd_srte_policy_candidate_path_bandwidth_destroy( + struct nb_cb_destroy_args *args) +{ + struct srte_candidate *candidate; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + assert(args->context != NULL); + candidate = nb_running_get_entry(args->dnode, NULL, true); + srte_candidate_unset_bandwidth(candidate); + return NB_OK; +} diff --git a/pathd/path_nb_state.c b/pathd/path_nb_state.c new file mode 100644 index 0000000..35b9e37 --- /dev/null +++ b/pathd/path_nb_state.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "northbound.h" +#include "libfrr.h" + +#include "pathd/pathd.h" +#include "pathd/path_nb.h" + +/* + * XPath: /frr-pathd:pathd/srte/segment-list + */ +const void *pathd_srte_segment_list_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + if (args->list_entry == NULL) + segment_list = + RB_MIN(srte_segment_list_head, &srte_segment_lists); + else + segment_list = RB_NEXT(srte_segment_list_head, segment_list); + + return segment_list; +} + +int pathd_srte_segment_list_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_segment_list *segment_list = + (struct srte_segment_list *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%s", + segment_list->name); + + return NB_OK; +} + +const void * +pathd_srte_segment_list_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + return srte_segment_list_find(args->keys->key[0]); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy + */ +const void *pathd_srte_policy_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + + if (args->list_entry == NULL) + policy = RB_MIN(srte_policy_head, &srte_policies); + else + policy = RB_NEXT(srte_policy_head, policy); + + return policy; +} + +int pathd_srte_policy_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_policy *policy = + (struct srte_policy *)args->list_entry; + + args->keys->num = 2; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + policy->color); + ipaddr2str(&policy->endpoint, args->keys->key[1], + sizeof(args->keys->key[1])); + + return NB_OK; +} + +const void *pathd_srte_policy_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + uint32_t color; + struct ipaddr endpoint; + + color = yang_str2uint32(args->keys->key[0]); + yang_str2ip(args->keys->key[1], &endpoint); + + return srte_policy_find(color, &endpoint); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/is-operational + */ +struct yang_data * +pathd_srte_policy_is_operational_get_elem(struct nb_cb_get_elem_args *args) +{ + struct srte_policy *policy = (struct srte_policy *)args->list_entry; + bool is_operational = false; + + if (policy->status == SRTE_POLICY_STATUS_UP) + is_operational = true; + + return yang_data_new_bool(args->xpath, is_operational); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path + */ +const void * +pathd_srte_policy_candidate_path_get_next(struct nb_cb_get_next_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + if (args->list_entry == NULL) + candidate = + RB_MIN(srte_candidate_head, &policy->candidate_paths); + else + candidate = RB_NEXT(srte_candidate_head, candidate); + + return candidate; +} + +int pathd_srte_policy_candidate_path_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + args->keys->num = 1; + snprintf(args->keys->key[0], sizeof(args->keys->key[0]), "%u", + candidate->preference); + + return NB_OK; +} + +const void *pathd_srte_policy_candidate_path_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + struct srte_policy *policy = + (struct srte_policy *)args->parent_list_entry; + uint32_t preference; + + preference = yang_str2uint32(args->keys->key[0]); + + return srte_candidate_find(policy, preference); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate_path/is-best-candidate-path + */ +struct yang_data * +pathd_srte_policy_candidate_path_is_best_candidate_path_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_bool( + args->xpath, CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)); +} + +/* + * XPath: /frr-pathd:pathd/srte/policy/candidate-path/discriminator + */ +struct yang_data *pathd_srte_policy_candidate_path_discriminator_get_elem( + struct nb_cb_get_elem_args *args) +{ + struct srte_candidate *candidate = + (struct srte_candidate *)args->list_entry; + + return yang_data_new_uint32(args->xpath, candidate->discriminator); +} diff --git a/pathd/path_pcep.c b/pathd/path_pcep.c new file mode 100644 index 0000000..ec9d8ad --- /dev/null +++ b/pathd/path_pcep.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include +#include "pceplib/pcep_utils_counters.h" + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "lib/version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_cli.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" + +DEFINE_MTYPE(PATHD, PCEP, "PCEP module"); + +/* + * Globals. + */ +static struct pcep_glob pcep_glob_space = {.dbg = {0, "pathd module: pcep"}}; +struct pcep_glob *pcep_g = &pcep_glob_space; + +/* Main Thread Even Handler */ +static int pcep_main_event_handler(enum pcep_main_event_type type, int pcc_id, + void *payload); +static int pcep_main_event_start_sync(int pcc_id); +static int pcep_main_event_start_sync_cb(struct path *path, void *arg); +static int pcep_main_event_initiate_candidate(struct path *path); +static int pcep_main_event_update_candidate(struct path *path); +static int pcep_main_event_remove_candidate_segments(const char *originator, + bool force); + +/* Hook Handlers called from the Main Thread */ +static int pathd_candidate_created_handler(struct srte_candidate *candidate); +static int pathd_candidate_updated_handler(struct srte_candidate *candidate); +static int pathd_candidate_removed_handler(struct srte_candidate *candidate); + +/* Path manipulation functions */ +static struct path_metric *pcep_copy_metrics(struct path_metric *metric); +static struct path_hop *pcep_copy_hops(struct path_hop *hop); + +/* Other static functions */ +static void notify_status(struct path *path, bool not_changed); + +/* Module Functions */ +static int pcep_module_finish(void); +static int pcep_module_late_init(struct event_loop *tm); +static int pcep_module_init(void); + +/* ------------ Path Helper Functions ------------ */ + +struct path *pcep_new_path(void) +{ + struct path *path; + path = XCALLOC(MTYPE_PCEP, sizeof(*path)); + path->binding_sid = MPLS_LABEL_NONE; + path->enforce_bandwidth = true; + return path; +} + +struct path_hop *pcep_new_hop(void) +{ + struct path_hop *hop; + hop = XCALLOC(MTYPE_PCEP, sizeof(*hop)); + return hop; +} + +struct path_metric *pcep_new_metric(void) +{ + struct path_metric *metric; + metric = XCALLOC(MTYPE_PCEP, sizeof(*metric)); + return metric; +} + +struct path_metric *pcep_copy_metrics(struct path_metric *metric) +{ + if (metric == NULL) + return NULL; + struct path_metric *new_metric = pcep_new_metric(); + *new_metric = *metric; + new_metric->next = pcep_copy_metrics(metric->next); + return new_metric; +} + +struct path_hop *pcep_copy_hops(struct path_hop *hop) +{ + if (hop == NULL) + return NULL; + struct path_hop *new_hop = pcep_new_hop(); + *new_hop = *hop; + new_hop->next = pcep_copy_hops(hop->next); + return new_hop; +} + +struct path *pcep_copy_path(struct path *path) +{ + struct path *new_path = pcep_new_path(); + + *new_path = *path; + new_path->first_metric = pcep_copy_metrics(path->first_metric); + new_path->first_hop = pcep_copy_hops(path->first_hop); + if (path->name != NULL) + new_path->name = XSTRDUP(MTYPE_PCEP, path->name); + if (path->originator != NULL) + new_path->originator = XSTRDUP(MTYPE_PCEP, path->originator); + return new_path; +} + +void pcep_free_path(struct path *path) +{ + struct path_hop *hop; + struct path_metric *metric; + char *tmp; + + metric = path->first_metric; + while (metric != NULL) { + struct path_metric *next = metric->next; + XFREE(MTYPE_PCEP, metric); + metric = next; + } + hop = path->first_hop; + while (hop != NULL) { + struct path_hop *next = hop->next; + XFREE(MTYPE_PCEP, hop); + hop = next; + } + if (path->originator != NULL) { + /* The path own the memory, it is const so it is clear it + shouldn't be modified. XFREE macro do not support type casting + so we need a temporary variable */ + tmp = (char *)path->originator; + XFREE(MTYPE_PCEP, tmp); + path->originator = NULL; + } + if (path->name != NULL) { + /* The path own the memory, it is const so it is clear it + shouldn't be modified. XFREE macro do not support type casting + so we need a temporary variable */ + tmp = (char *)path->name; + XFREE(MTYPE_PCEP, tmp); + path->name = NULL; + } + XFREE(MTYPE_PCEP, path); +} + +/* ------------ Other Static Functions ------------ */ + +void notify_status(struct path *path, bool not_changed) +{ + struct path *resp = NULL; + + if ((resp = path_pcep_config_get_path(&path->nbkey))) { + resp->srp_id = path->srp_id; + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "(%s) Send report for candidate path %s", __func__, + path->name); + pcep_ctrl_send_report(pcep_g->fpt, path->pcc_id, resp, + not_changed); + } +} + +/* ------------ Main Thread Even Handler ------------ */ + +int pcep_main_event_handler(enum pcep_main_event_type type, int pcc_id, + void *payload) +{ + int ret = 0; + + switch (type) { + case PCEP_MAIN_EVENT_START_SYNC: + ret = pcep_main_event_start_sync(pcc_id); + break; + case PCEP_MAIN_EVENT_INITIATE_CANDIDATE: + assert(payload != NULL); + ret = pcep_main_event_initiate_candidate( + (struct path *)payload); + break; + case PCEP_MAIN_EVENT_UPDATE_CANDIDATE: + assert(payload != NULL); + ret = pcep_main_event_update_candidate((struct path *)payload); + break; + case PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP: + ret = pcep_main_event_remove_candidate_segments( + (const char *)payload, true); + break; + case PCEP_MAIN_EVENT_UNDEFINED: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected event received in the main thread: %u", + type); + break; + } + + return ret; +} + +int pcep_main_event_start_sync(int pcc_id) +{ + path_pcep_config_list_path(pcep_main_event_start_sync_cb, &pcc_id); + pcep_ctrl_sync_done(pcep_g->fpt, pcc_id); + return 0; +} + +int pcep_main_event_start_sync_cb(struct path *path, void *arg) +{ + int *pcc_id = (int *)arg; + pcep_ctrl_sync_path(pcep_g->fpt, *pcc_id, path); + return 1; +} + +int pcep_main_event_initiate_candidate(struct path *path) +{ + int ret = 0; + + ret = path_pcep_config_initiate_path(path); + if (path->do_remove) { + struct pcep_error *error; + error = XCALLOC(MTYPE_PCEP, sizeof(*error)); + error->path = path; + error->error_type = PCEP_ERRT_INVALID_OPERATION; + switch (ret) { + case ERROR_19_1: + error->error_value = + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP; + break; + case ERROR_19_3: + error->error_value = + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID; + break; + case ERROR_19_9: + error->error_value = PCEP_ERRV_LSP_NOT_PCE_INITIATED; + break; + default: + zlog_warn("(%s)PCE tried to REMOVE unknown error!", + __func__); + XFREE(MTYPE_PCEP, error); + pcep_free_path(path); + return ret; + break; + } + pcep_ctrl_send_error(pcep_g->fpt, path->pcc_id, error); + } else if (ret != PATH_NB_ERR && path->srp_id != 0) + notify_status(path, ret == PATH_NB_NO_CHANGE); + return ret; +} + +int pcep_main_event_update_candidate(struct path *path) +{ + int ret = 0; + + ret = path_pcep_config_update_path(path); + if (ret != PATH_NB_ERR && path->srp_id != 0) + notify_status(path, ret == PATH_NB_NO_CHANGE); + return ret; +} + +int pcep_main_event_remove_candidate_segments(const char *originator, + bool force) +{ + srte_candidate_unset_segment_list(originator, force); + /* Avoid compiler warnings about const char* */ + void *free_ptr = (void *)originator; + XFREE(MTYPE_PCEP, free_ptr); + + srte_apply_changes(); + + return 0; +} + +/* ------------ Hook Handlers Functions Called From Main Thread ------------ */ + +int pathd_candidate_created_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_CREATED, path); + return ret; +} + +int pathd_candidate_updated_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_UPDATED, path); + return ret; +} + +int pathd_candidate_removed_handler(struct srte_candidate *candidate) +{ + struct path *path = candidate_to_path(candidate); + int ret = pcep_ctrl_pathd_event(pcep_g->fpt, PCEP_PATH_REMOVED, path); + return ret; +} + + +/* ------------ Module Functions ------------ */ + +/* this creates threads, therefore must run after fork(). but it must also + * run before config load, so the CLI commands don't try to touch things that + * aren't set up yet... + */ +static int pcep_module_config_pre(struct event_loop *tm) +{ + assert(pcep_g->fpt == NULL); + assert(pcep_g->master == NULL); + + struct frr_pthread *fpt; + + if (pcep_ctrl_initialize(tm, &fpt, pcep_main_event_handler)) + return 1; + + if (pcep_lib_initialize(fpt)) + return 1; + + pcep_g->master = tm; + pcep_g->fpt = fpt; + + return 0; +} + +static int pcep_module_late_init(struct event_loop *tm) +{ + hook_register(pathd_candidate_created, pathd_candidate_created_handler); + hook_register(pathd_candidate_updated, pathd_candidate_updated_handler); + hook_register(pathd_candidate_removed, pathd_candidate_removed_handler); + + hook_register(frr_config_pre, pcep_module_config_pre); + hook_register(frr_fini, pcep_module_finish); + + pcep_cli_init(); + + return 0; +} + +int pcep_module_finish(void) +{ + pcep_ctrl_finalize(&pcep_g->fpt); + pcep_lib_finalize(); + + for (int i = 0; i < MAX_PCC; i++) + if (pcep_g->pce_opts_cli[i] != NULL) + XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]); + + return 0; +} + +int pcep_module_init(void) +{ + pcep_g->num_pce_opts_cli = 0; + for (int i = 0; i < MAX_PCE; i++) + pcep_g->pce_opts_cli[i] = NULL; + pcep_g->num_config_group_opts = 0; + for (int i = 0; i < MAX_PCE; i++) + pcep_g->config_group_opts[i] = NULL; + + hook_register(frr_late_init, pcep_module_late_init); + return 0; +} + +FRR_MODULE_SETUP(.name = "frr_pathd_pcep", .version = FRR_VERSION, + .description = "FRR pathd PCEP module", + .init = pcep_module_init, +); diff --git a/pathd/path_pcep.h b/pathd/path_pcep.h new file mode 100644 index 0000000..d6dbcb5 --- /dev/null +++ b/pathd/path_pcep.h @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_H_ +#define _PATH_PCEP_H_ + +#include +#include +#include +#include "memory.h" +#include "pceplib/pcep_utils_logging.h" +#include "pceplib/pcep_pcc_api.h" +#include "mpls.h" +#include "pathd/pathd.h" + +DECLARE_MTYPE(PCEP); + +#define PCEP_DEFAULT_PORT 4189 +#define MAX_PCC 32 +#define MAX_PCE 32 +#define MAX_TAG_SIZE 50 +#define PCEP_DEBUG_MODE_BASIC 0x01 +#define PCEP_DEBUG_MODE_PATH 0x02 +#define PCEP_DEBUG_MODE_PCEP 0x04 +#define PCEP_DEBUG_MODE_PCEPLIB 0x08 +#define PCEP_DEBUG_MODE_ALL 0x0F +#define PCEP_DEBUG(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PATH(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PCEP(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PCEP_DEBUG_PCEPLIB(priority, fmt, ...) \ + do { \ + switch (priority) { \ + case LOG_DEBUG: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGD(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_INFO: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGI(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_NOTICE: \ + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, \ + PCEP_DEBUG_MODE_PCEPLIB)) \ + DEBUGN(&pcep_g->dbg, "pcep: " fmt, \ + ##__VA_ARGS__); \ + break; \ + case LOG_WARNING: \ + case LOG_ERR: \ + default: \ + zlog(priority, "pcep: " fmt, ##__VA_ARGS__); \ + break; \ + } \ + } while (0) + +struct pcep_config_group_opts { + char name[64]; + char tcp_md5_auth[PCEP_MD5SIG_MAXKEYLEN]; + struct ipaddr source_ip; + short source_port; + bool draft07; + bool pce_initiated; + int keep_alive_seconds; + int min_keep_alive_seconds; + int max_keep_alive_seconds; + int dead_timer_seconds; + int min_dead_timer_seconds; + int max_dead_timer_seconds; + int pcep_request_time_seconds; + int session_timeout_inteval_seconds; + int delegation_timeout_seconds; +}; + +struct pce_opts { + struct ipaddr addr; + short port; + char pce_name[64]; + struct pcep_config_group_opts config_opts; + uint8_t precedence; /* Multi-PCE precedence */ +}; + +struct pcc_opts { + struct ipaddr addr; + short port; + short msd; +}; + +/* Encapsulate the pce_opts with needed CLI information */ +struct pce_opts_cli { + struct pce_opts pce_opts; + char config_group_name[64]; + /* These are the values configured in the pcc-peer sub-commands. + * These need to be stored for later merging. Notice, it could + * be that not all of them are set. */ + struct pcep_config_group_opts pce_config_group_opts; + /* The pce_opts->config_opts will be a merge of the default values, + * optional config_group values (which overwrite default values), + * and any values configured in the pce sub-commands (which overwrite + * both default and config_group values). This flag indicates of the + * values need to be merged or not. */ + bool merged; +}; + +struct lsp_nb_key { + uint32_t color; + struct ipaddr endpoint; + uint32_t preference; +}; + +struct sid_mpls { + mpls_label_t label; + uint8_t traffic_class; + bool is_bottom; + uint8_t ttl; +}; + +struct pcep_caps { + bool is_stateful; + /* If we know the objective functions supported by the PCE. + * If we don't know, it doesn't mean the PCE doesn't support any */ + bool supported_ofs_are_known; + /* Defined if we know which objective funtions are supported by the PCE. + * One bit per objective function, the bit index being equal to + * enum pcep_objfun_type values: bit 0 is not used, bit 1 is + * PCEP_OBJFUN_MCP, up to bit 17 that is PCEP_OBJFUN_MSN */ + uint32_t supported_ofs; +}; + +union sid { + uint32_t value; + struct sid_mpls mpls; +}; + +struct nai { + /* NAI type */ + enum pcep_sr_subobj_nai type; + /* Local IP address*/ + struct ipaddr local_addr; + /* Local interface identifier if the NAI is an unnumbered adjacency */ + uint32_t local_iface; + /* Remote address if the NAI is an adjacency */ + struct ipaddr remote_addr; + /* Remote interface identifier if the NAI is an unnumbered adjacency */ + uint32_t remote_iface; +}; + +struct path_hop { + /* Pointer to the next hop in the path */ + struct path_hop *next; + /* Indicateif this ia a loose or strict hop */ + bool is_loose; + /* Indicate if there is an SID for the hop */ + bool has_sid; + /* Indicate if the hop as a MPLS label */ + bool is_mpls; + /* Indicate if the MPLS label has extra attributes (TTL, class..)*/ + bool has_attribs; + /* Hop's SID if available */ + union sid sid; + /* Indicate if there is a NAI for this hop */ + bool has_nai; + /* NAI if available */ + struct nai nai; +}; + +struct path_metric { + /* Pointer to the next metric */ + struct path_metric *next; + /* The metric type */ + enum pcep_metric_types type; + /* If the metric should be enforced */ + bool enforce; + /* If the metric value is bound (a maximum) */ + bool is_bound; + /* If the metric value is computed */ + bool is_computed; + /* The metric value */ + float value; +}; + +struct path { + /* Both the nbkey and the plspid are keys comming from the PCC, + but the PCE is only using the plspid. The missing key is looked up by + the PCC so we always have both */ + + /* The northbound key identifying this path */ + struct lsp_nb_key nbkey; + /* The generated unique PLSP identifier for this path. + See draft-ietf-pce-stateful-pce */ + uint32_t plsp_id; + + /* The transport address the path is comming from, PCE or PCC*/ + struct ipaddr sender; + /* The pcc protocol address, must be the same family as the endpoint */ + struct ipaddr pcc_addr; + + /* The identifier of the PCC the path is for/from. If 0 it is undefined, + meaning it hasn't be set yet or is for all the PCC */ + int pcc_id; + + /* The origin of the path creation */ + enum srte_protocol_origin create_origin; + /* The origin of the path modification */ + enum srte_protocol_origin update_origin; + /* The identifier of the entity that originated the path */ + const char *originator; + /* The type of the path, for PCE initiated or updated path it is always + SRTE_CANDIDATE_TYPE_DYNAMIC */ + enum srte_candidate_type type; + + /* The following data comes from either the PCC or the PCE if available + */ + + /* Path's binding SID */ + mpls_label_t binding_sid; + /* The name of the path */ + const char *name; + /* The request identifier from the PCE, when getting a path from the + PCE. See draft-ietf-pce-stateful-pce */ + uint32_t srp_id; + /* The request identifier from the PCC , when getting a path from the + PCE after a computation request. See rfc5440, section-7.4 */ + uint32_t req_id; + /* The operational status of the path */ + enum pcep_lsp_operational_status status; + /* If true, the receiver (PCC) must remove the path. + See draft-ietf-pce-pce-initiated-lsp */ + bool do_remove; + /* Indicate the given path was removed by the PCC. + See draft-ietf-pce-stateful-pce, section-7.3, flag R */ + bool was_removed; + /* Indicate the path is part of the synchronization process. + See draft-ietf-pce-stateful-pce, section-7.3, flag S */ + bool is_synching; + /* Indicate if the path bandwidth requirment is defined */ + bool has_bandwidth; + /* Indicate if the bandwidth requirment should be enforced */ + bool enforce_bandwidth; + /* Path required bandwidth if defined */ + float bandwidth; + /* Specify the list of hop defining the path */ + struct path_hop *first_hop; + /* Specify the list of metrics */ + struct path_metric *first_metric; + /* Indicate if the path has a PCC-defined objective function */ + bool has_pcc_objfun; + /* Indicate the PCC-defined objective function is required */ + bool enforce_pcc_objfun; + /* PCC-defined Objective Function */ + enum objfun_type pcc_objfun; + /* Indicate if the path has a PCE-defined objective function */ + bool has_pce_objfun; + /* PCE-defined Objective Function */ + enum objfun_type pce_objfun; + /* Indicate if some affinity filters are defined */ + bool has_affinity_filters; + /* Affinity attribute filters indexed by enum affinity_filter_type - 1 + */ + uint32_t affinity_filters[MAX_AFFINITY_FILTER_TYPE]; + + /* The following data need to be specialized for a given PCE */ + + /* Indicate the path is delegated to the PCE. + See draft-ietf-pce-stateful-pce, section-7.3, flag D */ + bool is_delegated; + /* Indicate if the PCE wants the path to get active. + See draft-ietf-pce-stateful-pce, section-7.3, flag A */ + bool go_active; + /* Indicate the given path was created by the PCE, + See draft-ietf-pce-pce-initiated-lsp, section-5.3.1, flag C */ + bool was_created; + + /* The following data is defined for comnputation replies */ + + /* Indicate that no path could be computed */ + bool no_path; +}; + +struct pcep_glob { + struct debug dbg; + struct event_loop *master; + struct frr_pthread *fpt; + uint8_t num_pce_opts_cli; + struct pce_opts_cli *pce_opts_cli[MAX_PCE]; + uint8_t num_config_group_opts; + struct pcep_config_group_opts *config_group_opts[MAX_PCE]; +}; + +extern struct pcep_glob *pcep_g; + +struct pcep_error { + struct path *path; + int error_type; + int error_value; + /* Rfc 8281 PcInitiated error on bad values */ +#define ERROR_19_1 1 +#define ERROR_19_3 2 +#define ERROR_19_9 3 +}; + +/* Path Helper Functions */ +struct path *pcep_new_path(void); +struct path_hop *pcep_new_hop(void); +struct path_metric *pcep_new_metric(void); +struct path *pcep_copy_path(struct path *path); +void pcep_free_path(struct path *path); + + +#endif // _PATH_PCEP_H_ diff --git a/pathd/path_pcep_cli.c b/pathd/path_pcep_cli.c new file mode 100644 index 0000000..47a811d --- /dev/null +++ b/pathd/path_pcep_cli.c @@ -0,0 +1,2433 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Volta Networks, Inc + * Brady Johnson + */ + +#include +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_session_logic.h" + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "lib/version.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "termtable.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_cli.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_debug.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_pcc.h" + +#include "pathd/path_pcep_cli_clippy.c" + +#define DEFAULT_PCE_PRECEDENCE 255 +#define DEFAULT_PCC_MSD 4 +#define DEFAULT_SR_DRAFT07 false +#define DEFAULT_PCE_INITIATED false +#define DEFAULT_TIMER_KEEP_ALIVE 30 +#define DEFAULT_TIMER_KEEP_ALIVE_MIN 1 +#define DEFAULT_TIMER_KEEP_ALIVE_MAX 255 +#define DEFAULT_TIMER_DEADTIMER 120 +#define DEFAULT_TIMER_DEADTIMER_MIN 4 +#define DEFAULT_TIMER_DEADTIMER_MAX 255 +#define DEFAULT_TIMER_PCEP_REQUEST 30 +#define DEFAULT_TIMER_SESSION_TIMEOUT_INTERVAL 30 +#define DEFAULT_DELEGATION_TIMEOUT_INTERVAL 10 + +#define BUFFER_PCC_PCE_SIZE 1024 + +/* CLI Function declarations */ +static int pcep_cli_debug_config_write(struct vty *vty); +static int pcep_cli_debug_set_all(uint32_t flags, bool set); +static int pcep_cli_pcep_config_write(struct vty *vty); +static int pcep_cli_pcc_config_write(struct vty *vty); +static int pcep_cli_pce_config_write(struct vty *vty); +static int pcep_cli_pcep_pce_config_write(struct vty *vty); + +/* Internal Util Function declarations */ +static void reset_pcc_peer(const char *peer_name); +static struct pce_opts_cli *pcep_cli_find_pce(const char *pce_name); +static bool pcep_cli_add_pce(struct pce_opts_cli *pce_opts_cli); +static struct pce_opts_cli *pcep_cli_create_pce_opts(const char *name); +static void pcep_cli_delete_pce(const char *pce_name); +static void +pcep_cli_merge_pcep_pce_config_options(struct pce_opts_cli *pce_opts_cli); +static struct pcep_config_group_opts * +pcep_cli_find_pcep_pce_config(const char *group_name); +static bool +pcep_cli_add_pcep_pce_config(struct pcep_config_group_opts *config_group_opts); +static struct pcep_config_group_opts * +pcep_cli_create_pcep_pce_config(const char *group_name); +static bool pcep_cli_is_pcep_pce_config_used(const char *group_name); +static void pcep_cli_delete_pcep_pce_config(const char *group_name); +static int pcep_cli_print_pce_config(struct pcep_config_group_opts *group_opts, + char *buf, size_t buf_len); +static void print_pcep_capabilities(char *buf, size_t buf_len, + pcep_configuration *config); +static void print_pcep_session(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info); +static void print_pcep_session_json(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info, + json_object *json); +static bool pcep_cli_pcc_has_pce(const char *pce_name); +static void pcep_cli_add_pce_connection(struct pce_opts *pce_opts); +static void pcep_cli_remove_pce_connection(struct pce_opts *pce_opts); +static int path_pcep_cli_pcc_pcc_peer_delete(struct vty *vty, + const char *peer_name, + const char *precedence_str, + long precedence); + +/* + * Globals. + */ + +static const char PCEP_VTYSH_ARG_ADDRESS[] = "address"; +static const char PCEP_VTYSH_ARG_SOURCE_ADDRESS[] = "source-address"; +static const char PCEP_VTYSH_ARG_IP[] = "ip"; +static const char PCEP_VTYSH_ARG_IPV6[] = "ipv6"; +static const char PCEP_VTYSH_ARG_PORT[] = "port"; +static const char PCEP_VTYSH_ARG_PRECEDENCE[] = "precedence"; +static const char PCEP_VTYSH_ARG_MSD[] = "msd"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE[] = "keep-alive"; +static const char PCEP_VTYSH_ARG_TIMER[] = "timer"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE_MIN[] = "min-peer-keep-alive"; +static const char PCEP_VTYSH_ARG_KEEP_ALIVE_MAX[] = "max-peer-keep-alive"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER[] = "dead-timer"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER_MIN[] = "min-peer-dead-timer"; +static const char PCEP_VTYSH_ARG_DEAD_TIMER_MAX[] = "max-peer-dead-timer"; +static const char PCEP_VTYSH_ARG_PCEP_REQUEST[] = "pcep-request"; +static const char PCEP_VTYSH_ARG_SESSION_TIMEOUT[] = "session-timeout-interval"; +static const char PCEP_VTYSH_ARG_DELEGATION_TIMEOUT[] = "delegation-timeout"; +static const char PCEP_VTYSH_ARG_SR_DRAFT07[] = "sr-draft07"; +static const char PCEP_VTYSH_ARG_PCE_INIT[] = "pce-initiated"; +static const char PCEP_VTYSH_ARG_TCP_MD5[] = "tcp-md5-auth"; +static const char PCEP_VTYSH_ARG_BASIC[] = "basic"; +static const char PCEP_VTYSH_ARG_PATH[] = "path"; +static const char PCEP_VTYSH_ARG_MESSAGE[] = "message"; +static const char PCEP_VTYSH_ARG_PCEPLIB[] = "pceplib"; +static const char PCEP_CLI_CAP_STATEFUL[] = " [Stateful PCE]"; +static const char PCEP_CLI_CAP_INCL_DB_VER[] = " [Include DB version]"; +static const char PCEP_CLI_CAP_LSP_TRIGGERED[] = " [LSP Triggered Resync]"; +static const char PCEP_CLI_CAP_LSP_DELTA[] = " [LSP Delta Sync]"; +static const char PCEP_CLI_CAP_PCE_TRIGGERED[] = + " [PCE triggered Initial Sync]"; +static const char PCEP_CLI_CAP_SR_TE_PST[] = " [SR TE PST]"; +static const char PCEP_CLI_CAP_PCC_RESOLVE_NAI[] = + " [PCC can resolve NAI to SID]"; +static const char PCEP_CLI_CAP_PCC_INITIATED[] = " [PCC Initiated LSPs]"; +static const char PCEP_CLI_CAP_PCC_PCE_INITIATED[] = + " [PCC and PCE Initiated LSPs]"; + +struct pce_connections { + int num_connections; + struct pce_opts *connections[MAX_PCC]; +}; + +struct pce_connections pce_connections_g = {.num_connections = 0}; + +/* Default PCE group that all PCE-Groups and PCEs will inherit from */ +struct pcep_config_group_opts default_pcep_config_group_opts_g = { + .name = "default", + .tcp_md5_auth = "\0", + .draft07 = DEFAULT_SR_DRAFT07, + .pce_initiated = DEFAULT_PCE_INITIATED, + .keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE, + .min_keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE_MIN, + .max_keep_alive_seconds = DEFAULT_TIMER_KEEP_ALIVE_MAX, + .dead_timer_seconds = DEFAULT_TIMER_DEADTIMER, + .min_dead_timer_seconds = DEFAULT_TIMER_DEADTIMER_MIN, + .max_dead_timer_seconds = DEFAULT_TIMER_DEADTIMER_MAX, + .pcep_request_time_seconds = DEFAULT_TIMER_PCEP_REQUEST, + .session_timeout_inteval_seconds = + DEFAULT_TIMER_SESSION_TIMEOUT_INTERVAL, + .delegation_timeout_seconds = DEFAULT_DELEGATION_TIMEOUT_INTERVAL, + .source_port = DEFAULT_PCEP_TCP_PORT, + .source_ip.ipa_type = IPADDR_NONE, +}; + +/* Used by PCEP_PCE_CONFIG_NODE sub-commands to operate on the current pce group + */ +struct pcep_config_group_opts *current_pcep_config_group_opts_g = NULL; +/* Used by PCEP_PCE_NODE sub-commands to operate on the current pce opts */ +struct pce_opts_cli *current_pce_opts_g = NULL; +short pcc_msd_g = DEFAULT_PCC_MSD; +bool pcc_msd_configured_g = false; + +static struct cmd_node pcep_node = { + .name = "srte pcep", + .node = PCEP_NODE, + .parent_node = SR_TRAFFIC_ENG_NODE, + .prompt = "%s(config-sr-te-pcep)# " +}; + +static struct cmd_node pcep_pcc_node = { + .name = "srte pcep pcc", + .node = PCEP_PCC_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(config-sr-te-pcep-pcc)# " +}; + +static struct cmd_node pcep_pce_node = { + .name = "srte pcep pce", + .node = PCEP_PCE_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(config-sr-te-pcep-pce)# " +}; + +static struct cmd_node pcep_pce_config_node = { + .name = "srte pcep pce-config", + .node = PCEP_PCE_CONFIG_NODE, + .parent_node = PCEP_NODE, + .prompt = "%s(pce-sr-te-pcep-pce-config)# " +}; + +/* Common code used in VTYSH processing for int values */ +#define PCEP_VTYSH_INT_ARG_CHECK(arg_str, arg_val, arg_store, min_value, \ + max_value) \ + if (arg_str != NULL) { \ + if (arg_val <= min_value || arg_val >= max_value) { \ + vty_out(vty, \ + "%% Invalid value %ld in range [%d - %d]", \ + arg_val, min_value, max_value); \ + return CMD_WARNING; \ + } \ + arg_store = arg_val; \ + } + +#define MERGE_COMPARE_CONFIG_GROUP_VALUE(config_param, not_set_value) \ + pce_opts_cli->pce_opts.config_opts.config_param = \ + pce_opts_cli->pce_config_group_opts.config_param; \ + if (pce_opts_cli->pce_config_group_opts.config_param \ + == not_set_value) { \ + pce_opts_cli->pce_opts.config_opts.config_param = \ + ((pce_config != NULL \ + && pce_config->config_param != not_set_value) \ + ? pce_config->config_param \ + : default_pcep_config_group_opts_g \ + .config_param); \ + } + +/* + * Internal Util functions + */ + +/* Check if a pce_opts_cli already exists based on its name and return it, + * return NULL otherwise */ +static struct pce_opts_cli *pcep_cli_find_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + struct pce_opts_cli *pce_rhs_cli = pcep_g->pce_opts_cli[i]; + if (pce_rhs_cli != NULL) { + if (strcmp(pce_name, pce_rhs_cli->pce_opts.pce_name) + == 0) { + return pce_rhs_cli; + } + } + } + + return NULL; +} + +/* Add a new pce_opts_cli to pcep_g, return false if MAX_PCES, true otherwise */ +static bool pcep_cli_add_pce(struct pce_opts_cli *pce_opts_cli) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] == NULL) { + pcep_g->pce_opts_cli[i] = pce_opts_cli; + pcep_g->num_pce_opts_cli++; + return true; + } + } + + return false; +} + +/* Create a new pce opts_cli */ +static struct pce_opts_cli *pcep_cli_create_pce_opts(const char *name) +{ + struct pce_opts_cli *pce_opts_cli = + XCALLOC(MTYPE_PCEP, sizeof(struct pce_opts_cli)); + strlcpy(pce_opts_cli->pce_opts.pce_name, name, + sizeof(pce_opts_cli->pce_opts.pce_name)); + pce_opts_cli->pce_opts.port = PCEP_DEFAULT_PORT; + + return pce_opts_cli; +} + +static void pcep_cli_delete_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] != NULL) { + if (strcmp(pcep_g->pce_opts_cli[i]->pce_opts.pce_name, + pce_name) + == 0) { + XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]); + pcep_g->pce_opts_cli[i] = NULL; + pcep_g->num_pce_opts_cli--; + return; + } + } + } +} + +static void +pcep_cli_merge_pcep_pce_config_options(struct pce_opts_cli *pce_opts_cli) +{ + if (pce_opts_cli->merged == true) { + return; + } + + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pce_opts_cli->config_group_name); + + /* Configuration priorities: + * 1) pce_opts->config_opts, if present, overwrite pce_config + * config_opts 2) pce_config config_opts, if present, overwrite + * default config_opts 3) If neither pce_opts->config_opts nor + * pce_config config_opts are set, then the default config_opts value + * will be used. + */ + + const char *tcp_md5_auth_str = + pce_opts_cli->pce_config_group_opts.tcp_md5_auth; + if (pce_opts_cli->pce_config_group_opts.tcp_md5_auth[0] == '\0') { + if (pce_config != NULL && pce_config->tcp_md5_auth[0] != '\0') { + tcp_md5_auth_str = pce_config->tcp_md5_auth; + } else { + tcp_md5_auth_str = + default_pcep_config_group_opts_g.tcp_md5_auth; + } + } + strlcpy(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth, + tcp_md5_auth_str, + sizeof(pce_opts_cli->pce_opts.config_opts.tcp_md5_auth)); + + struct ipaddr *source_ip = + &pce_opts_cli->pce_config_group_opts.source_ip; + if (pce_opts_cli->pce_config_group_opts.source_ip.ipa_type + == IPADDR_NONE) { + if (pce_config != NULL + && pce_config->source_ip.ipa_type != IPADDR_NONE) { + source_ip = &pce_config->source_ip; + } else { + source_ip = &default_pcep_config_group_opts_g.source_ip; + } + } + memcpy(&pce_opts_cli->pce_opts.config_opts.source_ip, source_ip, + sizeof(struct ipaddr)); + + MERGE_COMPARE_CONFIG_GROUP_VALUE(draft07, false); + MERGE_COMPARE_CONFIG_GROUP_VALUE(pce_initiated, false); + MERGE_COMPARE_CONFIG_GROUP_VALUE(keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(min_keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(max_keep_alive_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(min_dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(max_dead_timer_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(pcep_request_time_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(session_timeout_inteval_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(delegation_timeout_seconds, 0); + MERGE_COMPARE_CONFIG_GROUP_VALUE(source_port, 0); + + pce_opts_cli->merged = true; +} + +/* Check if a pcep_config_group_opts already exists based on its name and return + * it, return NULL otherwise */ +static struct pcep_config_group_opts * +pcep_cli_find_pcep_pce_config(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + struct pcep_config_group_opts *pcep_pce_config_rhs = + pcep_g->config_group_opts[i]; + if (pcep_pce_config_rhs != NULL) { + if (strcmp(group_name, pcep_pce_config_rhs->name) + == 0) { + return pcep_pce_config_rhs; + } + } + } + + return NULL; +} + +/* Add a new pcep_config_group_opts to pcep_g, return false if MAX_PCE, + * true otherwise */ +static bool pcep_cli_add_pcep_pce_config( + struct pcep_config_group_opts *pcep_config_group_opts) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->config_group_opts[i] == NULL) { + pcep_g->config_group_opts[i] = pcep_config_group_opts; + pcep_g->num_config_group_opts++; + return true; + } + } + + return false; +} + +/* Create a new pce group, inheriting its values from the default pce group */ +static struct pcep_config_group_opts * +pcep_cli_create_pcep_pce_config(const char *group_name) +{ + struct pcep_config_group_opts *pcep_config_group_opts = + XCALLOC(MTYPE_PCEP, sizeof(struct pcep_config_group_opts)); + strlcpy(pcep_config_group_opts->name, group_name, + sizeof(pcep_config_group_opts->name)); + + return pcep_config_group_opts; +} + +/* Iterate the pce_opts and return true if the pce-group-name is referenced, + * false otherwise. */ +static bool pcep_cli_is_pcep_pce_config_used(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->pce_opts_cli[i] != NULL) { + if (strcmp(pcep_g->pce_opts_cli[i]->config_group_name, + group_name) + == 0) { + return true; + } + } + } + + return false; +} + +static void pcep_cli_delete_pcep_pce_config(const char *group_name) +{ + for (int i = 0; i < MAX_PCE; i++) { + if (pcep_g->config_group_opts[i] != NULL) { + if (strcmp(pcep_g->config_group_opts[i]->name, + group_name) + == 0) { + XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]); + pcep_g->config_group_opts[i] = NULL; + pcep_g->num_config_group_opts--; + return; + } + } + } +} + +static bool pcep_cli_pcc_has_pce(const char *pce_name) +{ + for (int i = 0; i < MAX_PCC; i++) { + struct pce_opts *pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + if (strcmp(pce_opts->pce_name, pce_name) == 0) { + return true; + } + } + + return false; +} + +static void pcep_cli_add_pce_connection(struct pce_opts *pce_opts) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pce_connections_g.connections[i] == NULL) { + pce_connections_g.num_connections++; + pce_connections_g.connections[i] = pce_opts; + return; + } + } +} + +static void pcep_cli_remove_pce_connection(struct pce_opts *pce_opts) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pce_connections_g.connections[i] == pce_opts) { + pce_connections_g.num_connections--; + pce_connections_g.connections[i] = NULL; + return; + } + } +} + +/* + * VTY command implementations + */ + +static int path_pcep_cli_debug(struct vty *vty, const char *debug_type, bool set) +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + /* Global Set */ + if (debug_type == NULL) { + DEBUG_MODE_SET(&pcep_g->dbg, mode, set); + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_ALL, set); + return CMD_SUCCESS; + } + + DEBUG_MODE_SET(&pcep_g->dbg, mode, true); + + if (strcmp(debug_type, "basic") == 0) + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC, set); + else if (strcmp(debug_type, "path") == 0) + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH, set); + else if (strcmp(debug_type, "message") == 0) + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP, set); + else if (strcmp(debug_type, "pceplib") == 0) + DEBUG_FLAGS_SET(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB, set); + + /* Unset the pcep debug mode if there is no flag at least set*/ + if (!DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_ALL)) + DEBUG_MODE_SET(&pcep_g->dbg, mode, false); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_counters(struct vty *vty) +{ + int i, j, row; + time_t diff_time; + struct tm tm_info; + char tm_buffer[26]; + struct counters_group *group; + struct counters_subgroup *subgroup; + struct counter *counter; + const char *group_name, *empty_string = ""; + struct ttable *tt; + char *table; + + group = pcep_ctrl_get_counters(pcep_g->fpt, 1); + + if (group == NULL) { + vty_out(vty, "No counters to display.\n\n"); + return CMD_SUCCESS; + } + + diff_time = time(NULL) - group->start_time; + localtime_r(&group->start_time, &tm_info); + strftime(tm_buffer, sizeof(tm_buffer), "%Y-%m-%d %H:%M:%S", &tm_info); + + vty_out(vty, "PCEP counters since %s (%uh %um %us):\n", tm_buffer, + (uint32_t)(diff_time / 3600), (uint32_t)((diff_time / 60) % 60), + (uint32_t)(diff_time % 60)); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Group|Name|Value"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + + for (row = 0, i = 0; i <= group->num_subgroups; i++) { + subgroup = group->subgroups[i]; + if (subgroup != NULL) { + group_name = subgroup->counters_subgroup_name; + for (j = 0; j <= subgroup->num_counters; j++) { + counter = subgroup->counters[j]; + if (counter != NULL) { + ttable_add_row(tt, "%s|%s|%u", + group_name, + counter->counter_name, + counter->counter_value); + row++; + group_name = empty_string; + } + } + ttable_rowseps(tt, row, BOTTOM, true, '-'); + } + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + + pcep_lib_free_counters(group); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcep_pce_config(struct vty *vty, + const char *pcep_pce_config) +{ + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + if (pce_config == NULL) { + pce_config = pcep_cli_create_pcep_pce_config(pcep_pce_config); + if (pcep_cli_add_pcep_pce_config(pce_config) == false) { + vty_out(vty, + "%% Cannot create pce-config, as the Maximum limit of %d pce-config has been reached.\n", + MAX_PCE); + XFREE(MTYPE_PCEP, pce_config); + return CMD_WARNING; + } + } else { + vty_out(vty, + "Notice: changes to this pce-config will not affect PCEs already configured with this group\n"); + } + + current_pcep_config_group_opts_g = pce_config; + vty->node = PCEP_PCE_CONFIG_NODE; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcep_pce_config_delete(struct vty *vty, + const char *pcep_pce_config) +{ + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + if (pce_config == NULL) { + vty_out(vty, + "%% Cannot delete pce-config, since it does not exist.\n"); + return CMD_WARNING; + } + + if (pcep_cli_is_pcep_pce_config_used(pce_config->name)) { + vty_out(vty, + "%% Cannot delete pce-config, since it is in use by a peer.\n"); + return CMD_WARNING; + } + + pcep_cli_delete_pcep_pce_config(pce_config->name); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_pce_config(struct vty *vty, + const char *pcep_pce_config) +{ + char buf[1024] = ""; + + /* Only show 1 Peer config group */ + struct pcep_config_group_opts *group_opts; + if (pcep_pce_config != NULL) { + if (strcmp(pcep_pce_config, "default") == 0) { + group_opts = &default_pcep_config_group_opts_g; + } else { + group_opts = + pcep_cli_find_pcep_pce_config(pcep_pce_config); + } + if (group_opts == NULL) { + vty_out(vty, "%% pce-config [%s] does not exist.\n", + pcep_pce_config); + return CMD_WARNING; + } + + vty_out(vty, "pce-config: %s\n", group_opts->name); + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + return CMD_SUCCESS; + } + + /* Show all Peer config groups */ + for (int i = 0; i < MAX_PCE; i++) { + group_opts = pcep_g->config_group_opts[i]; + if (group_opts == NULL) { + continue; + } + + vty_out(vty, "pce-config: %s\n", group_opts->name); + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + buf[0] = 0; + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pce(struct vty *vty, const char *pce_peer_name) +{ + /* If it already exists, it will be updated in the sub-commands */ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(pce_peer_name); + if (pce_opts_cli == NULL) { + pce_opts_cli = pcep_cli_create_pce_opts(pce_peer_name); + + if (!pcep_cli_add_pce(pce_opts_cli)) { + vty_out(vty, + "%% Cannot create PCE, as the Maximum limit of %d PCEs has been reached.\n", + MAX_PCE); + XFREE(MTYPE_PCEP, pce_opts_cli); + return CMD_WARNING; + } + } + + current_pce_opts_g = pce_opts_cli; + vty->node = PCEP_PCE_NODE; + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pce_delete(struct vty *vty, const char *pce_peer_name) +{ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(pce_peer_name); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCC peer does not exist.\n"); + return CMD_WARNING; + } + + /* To better work with frr-reload, go ahead and delete it if its in use + */ + if (pcep_cli_pcc_has_pce(pce_peer_name)) { + vty_out(vty, + "%% Notice: the pce is in use by a PCC, also disconnecting.\n"); + path_pcep_cli_pcc_pcc_peer_delete(vty, pce_peer_name, NULL, 0); + } + + pcep_cli_delete_pce(pce_peer_name); + + return CMD_SUCCESS; +} + +/* Internal Util func to show an individual PCE, + * only used by path_pcep_cli_show_srte_pcep_pce() */ +static void show_pce_peer(struct vty *vty, struct pce_opts_cli *pce_opts_cli) +{ + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + vty_out(vty, "PCE: %s\n", pce_opts->pce_name); + + /* Remote PCE IP address */ + if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " %s %s %pI6 %s %d\n", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IPV6, &pce_opts->addr.ipaddr_v6, + PCEP_VTYSH_ARG_PORT, pce_opts->port); + } else { + vty_out(vty, " %s %s %pI4 %s %d\n", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IP, &pce_opts->addr.ipaddr_v4, + PCEP_VTYSH_ARG_PORT, pce_opts->port); + } + + if (pce_opts_cli->config_group_name[0] != '\0') { + vty_out(vty, " pce-config: %s\n", + pce_opts_cli->config_group_name); + } + + char buf[1024] = ""; + pcep_cli_print_pce_config(&pce_opts->config_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); +} + +static int path_pcep_cli_show_srte_pcep_pce(struct vty *vty, + const char *pce_peer) +{ + /* Only show 1 PCE */ + struct pce_opts_cli *pce_opts_cli; + if (pce_peer != NULL) { + pce_opts_cli = pcep_cli_find_pce(pce_peer); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", pce_peer); + return CMD_WARNING; + } + + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + show_pce_peer(vty, pce_opts_cli); + + return CMD_SUCCESS; + } + + /* Show all PCEs */ + for (int i = 0; i < MAX_PCE; i++) { + pce_opts_cli = pcep_g->pce_opts_cli[i]; + if (pce_opts_cli == NULL) { + continue; + } + + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + show_pce_peer(vty, pce_opts_cli); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_sr_draft07(struct vty *vty, bool reset) +{ + struct pcep_config_group_opts *pce_config = NULL; + struct pce_opts *pce_opts = ¤t_pce_opts_g->pce_opts; + bool pce_in_use = false; + + if (vty->node == PCEP_PCE_NODE) { + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + pce_in_use = pcep_cli_pcc_has_pce(pce_opts->pce_name); + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + pce_config->draft07 = reset ? DEFAULT_SR_DRAFT07 : true; + + if (pce_in_use) { + vty_out(vty, "%% PCE in use, resetting pcc peer session...\n"); + reset_pcc_peer(pce_opts->pce_name); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_pce_initiated(struct vty *vty, bool reset) +{ + struct pcep_config_group_opts *pce_config = NULL; + struct pce_opts *pce_opts = ¤t_pce_opts_g->pce_opts; + bool pce_in_use = false; + + if (vty->node == PCEP_PCE_NODE) { + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + pce_in_use = pcep_cli_pcc_has_pce(pce_opts->pce_name); + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + pce_config->pce_initiated = reset ? DEFAULT_PCE_INITIATED : true; + + if (pce_in_use) { + vty_out(vty, "%% PCE in use, resetting pcc peer session...\n"); + reset_pcc_peer(pce_opts->pce_name); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_tcp_md5_auth(struct vty *vty, + const char *tcp_md5_auth, + bool reset) +{ + struct pcep_config_group_opts *pce_config = NULL; + struct pce_opts *pce_opts = ¤t_pce_opts_g->pce_opts; + bool pce_in_use = false; + + if (vty->node == PCEP_PCE_NODE) { + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + pce_in_use = pcep_cli_pcc_has_pce(pce_opts->pce_name); + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + if (reset) + pce_config->tcp_md5_auth[0] = '\0'; + else + strlcpy(pce_config->tcp_md5_auth, tcp_md5_auth, + sizeof(pce_config->tcp_md5_auth)); + + if (pce_in_use) { + vty_out(vty, "%% PCE in use, resetting pcc peer session...\n"); + reset_pcc_peer(pce_opts->pce_name); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_address(struct vty *vty, const char *ip_str, + struct in_addr *ip, const char *ipv6_str, + struct in6_addr *ipv6, + const char *port_str, long port) +{ + struct pce_opts *pce_opts = NULL; + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + pce_opts = ¤t_pce_opts_g->pce_opts; + current_pce_opts_g->merged = false; + } else { + return CMD_ERR_NO_MATCH; + } + + if (ipv6_str != NULL) { + pce_opts->addr.ipa_type = IPADDR_V6; + memcpy(&pce_opts->addr.ipaddr_v6, ipv6, + sizeof(struct in6_addr)); + } else if (ip_str != NULL) { + pce_opts->addr.ipa_type = IPADDR_V4; + memcpy(&pce_opts->addr.ipaddr_v4, ip, sizeof(struct in_addr)); + } else { + return CMD_ERR_NO_MATCH; + } + + /* Handle the optional port */ + pce_opts->port = PCEP_DEFAULT_PORT; + PCEP_VTYSH_INT_ARG_CHECK(port_str, port, pce_opts->port, 0, 65535); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_source_address(struct vty *vty, + const char *ip_str, + struct in_addr *ip, + const char *ipv6_str, + struct in6_addr *ipv6, + const char *port_str, long port, + bool reset) +{ + struct pcep_config_group_opts *pce_config = NULL; + struct pce_opts *pce_opts = ¤t_pce_opts_g->pce_opts; + bool pce_in_use = false; + + if (vty->node == PCEP_PCE_NODE) { + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + pce_in_use = pcep_cli_pcc_has_pce(pce_opts->pce_name); + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + if (reset) { + pce_config->source_ip.ipa_type = IPADDR_NONE; + pce_config->source_port = 0; + return CMD_SUCCESS; + } + + /* Handle the optional source IP */ + if (ipv6_str != NULL) { + pce_config->source_ip.ipa_type = IPADDR_V6; + memcpy(&pce_config->source_ip.ipaddr_v6, ipv6, + sizeof(struct in6_addr)); + } else if (ip_str != NULL) { + pce_config->source_ip.ipa_type = IPADDR_V4; + memcpy(&pce_config->source_ip.ipaddr_v4, ip, + sizeof(struct in_addr)); + } + + /* Handle the optional port */ + PCEP_VTYSH_INT_ARG_CHECK(port_str, port, pce_config->source_port, 0, + 65535); + + if (pce_in_use) { + vty_out(vty, "%% PCE in use, resetting pcc peer session...\n"); + reset_pcc_peer(pce_opts->pce_name); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_pcep_pce_config_ref(struct vty *vty, + const char *config_group_name) +{ + if (vty->node == PCEP_PCE_NODE) { + /* TODO need to see if the pce is in use, and reset the + * connection */ + current_pce_opts_g->merged = false; + } else { + return CMD_ERR_NO_MATCH; + } + + struct pcep_config_group_opts *pce_config = + pcep_cli_find_pcep_pce_config(config_group_name); + if (pce_config == NULL) { + vty_out(vty, "%% pce-config [%s] does not exist.\n", + config_group_name); + return CMD_WARNING; + } + + strlcpy(current_pce_opts_g->config_group_name, config_group_name, + sizeof(current_pce_opts_g->config_group_name)); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_peer_timers( + struct vty *vty, const char *keep_alive_str, long keep_alive, + const char *min_peer_keep_alive_str, long min_peer_keep_alive, + const char *max_peer_keep_alive_str, long max_peer_keep_alive, + const char *dead_timer_str, long dead_timer, + const char *min_peer_dead_timer_str, long min_peer_dead_timer, + const char *max_peer_dead_timer_str, long max_peer_dead_timer, + const char *pcep_request_str, long pcep_request, + const char *session_timeout_interval_str, long session_timeout_interval, + const char *delegation_timeout_str, long delegation_timeout) +{ + struct pcep_config_group_opts *pce_config = NULL; + struct pce_opts *pce_opts = ¤t_pce_opts_g->pce_opts; + bool pce_in_use = false; + + if (vty->node == PCEP_PCE_NODE) { + pce_config = ¤t_pce_opts_g->pce_config_group_opts; + current_pce_opts_g->merged = false; + pce_in_use = pcep_cli_pcc_has_pce(pce_opts->pce_name); + } else if (vty->node == PCEP_PCE_CONFIG_NODE) { + pce_config = current_pcep_config_group_opts_g; + } else { + return CMD_ERR_NO_MATCH; + } + + if (min_peer_keep_alive && max_peer_keep_alive) + if (min_peer_keep_alive >= max_peer_keep_alive) { + return CMD_ERR_NO_MATCH; + } + + if (min_peer_dead_timer && max_peer_dead_timer) + if (min_peer_dead_timer >= max_peer_dead_timer) { + return CMD_ERR_NO_MATCH; + } + + /* Handle the arguments */ + PCEP_VTYSH_INT_ARG_CHECK(keep_alive_str, keep_alive, + pce_config->keep_alive_seconds, 0, 64); + PCEP_VTYSH_INT_ARG_CHECK(min_peer_keep_alive_str, min_peer_keep_alive, + pce_config->min_keep_alive_seconds, 0, 256); + PCEP_VTYSH_INT_ARG_CHECK(max_peer_keep_alive_str, max_peer_keep_alive, + pce_config->max_keep_alive_seconds, 0, 256); + PCEP_VTYSH_INT_ARG_CHECK(dead_timer_str, dead_timer, + pce_config->dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(min_peer_dead_timer_str, min_peer_dead_timer, + pce_config->min_dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(max_peer_dead_timer_str, max_peer_dead_timer, + pce_config->max_dead_timer_seconds, 3, 256); + PCEP_VTYSH_INT_ARG_CHECK(pcep_request_str, pcep_request, + pce_config->pcep_request_time_seconds, 0, 121); + PCEP_VTYSH_INT_ARG_CHECK( + session_timeout_interval_str, session_timeout_interval, + pce_config->session_timeout_inteval_seconds, 0, 121); + PCEP_VTYSH_INT_ARG_CHECK(delegation_timeout_str, delegation_timeout, + pce_config->delegation_timeout_seconds, 0, 61); + + if (pce_in_use) { + vty_out(vty, "%% PCE in use, resetting pcc peer session...\n"); + reset_pcc_peer(pce_opts->pce_name); + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc(struct vty *vty) +{ + VTY_PUSH_CONTEXT_NULL(PCEP_PCC_NODE); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_delete(struct vty *vty) +{ + /* Clear the pce_connections */ + memset(&pce_connections_g, 0, sizeof(pce_connections_g)); + pcc_msd_configured_g = false; + + pcep_ctrl_remove_pcc(pcep_g->fpt, NULL); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_pcc_msd(struct vty *vty, const char *msd_str, + long msd, bool reset) +{ + if (reset) + pcc_msd_configured_g = false; + else if (msd_str) { + pcc_msd_configured_g = true; + PCEP_VTYSH_INT_ARG_CHECK(msd_str, msd, pcc_msd_g, 0, 33); + } + + return CMD_SUCCESS; +} + +void reset_pcc_peer(const char *peer_name) +{ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(peer_name); + + /* Remove the pcc peer */ + pcep_cli_remove_pce_connection(&pce_opts_cli->pce_opts); + struct pce_opts *pce_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, &pce_opts_cli->pce_opts, sizeof(struct pce_opts)); + pcep_ctrl_remove_pcc(pcep_g->fpt, pce_opts_copy); + + /* Re-add the pcc peer */ + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + pcep_cli_add_pce_connection(&pce_opts_cli->pce_opts); + + /* Update the pcc_opts */ + struct pcc_opts *pcc_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pcc_opts)); + memcpy(&pcc_opts_copy->addr, + &pce_opts_cli->pce_opts.config_opts.source_ip, + sizeof(pcc_opts_copy->addr)); + pcc_opts_copy->msd = pcc_msd_g; + pcc_opts_copy->port = pce_opts_cli->pce_opts.config_opts.source_port; + pcep_ctrl_update_pcc_options(pcep_g->fpt, pcc_opts_copy); + + /* Update the pce_opts */ + pce_opts_copy = XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, &pce_opts_cli->pce_opts, sizeof(struct pce_opts)); + pcep_ctrl_update_pce_options(pcep_g->fpt, pce_opts_copy); +} + +static int path_pcep_cli_pcc_pcc_peer(struct vty *vty, const char *peer_name, + const char *precedence_str, + long precedence) +{ + /* Check if the pcc-peer exists */ + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(peer_name); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", peer_name); + return CMD_WARNING; + } + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + + /* Check if the pcc-peer is duplicated */ + if (pcep_cli_pcc_has_pce(peer_name)) { + vty_out(vty, "%% The peer [%s] has already been configured.\n", + peer_name); + return CMD_WARNING; + } + + /* Get the optional precedence argument */ + pce_opts->precedence = DEFAULT_PCE_PRECEDENCE; + PCEP_VTYSH_INT_ARG_CHECK(precedence_str, precedence, + pce_opts->precedence, 0, 256); + + /* Finalize the pce_opts config values */ + pcep_cli_merge_pcep_pce_config_options(pce_opts_cli); + pcep_cli_add_pce_connection(&pce_opts_cli->pce_opts); + + /* Verify the PCE has the IP set */ + struct in6_addr zero_v6_addr; + memset(&zero_v6_addr, 0, sizeof(zero_v6_addr)); + if (memcmp(&pce_opts->addr.ip, &zero_v6_addr, IPADDRSZ(&pce_opts->addr)) + == 0) { + vty_out(vty, + "%% The peer [%s] does not have an IP set and cannot be used until it does.\n", + peer_name); + return CMD_WARNING; + } + + /* Update the pcc_opts with the source ip, port, and msd */ + struct pcc_opts *pcc_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pcc_opts)); + memcpy(&pcc_opts_copy->addr, + &pce_opts_cli->pce_opts.config_opts.source_ip, + sizeof(pcc_opts_copy->addr)); + pcc_opts_copy->msd = pcc_msd_g; + pcc_opts_copy->port = pce_opts_cli->pce_opts.config_opts.source_port; + if (pcep_ctrl_update_pcc_options(pcep_g->fpt, pcc_opts_copy)) { + return CMD_WARNING; + } + + /* Send a copy of the pce_opts, this one is only used for the CLI */ + struct pce_opts *pce_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, pce_opts, sizeof(struct pce_opts)); + if (pcep_ctrl_update_pce_options(pcep_g->fpt, pce_opts_copy)) { + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +static int path_pcep_cli_pcc_pcc_peer_delete(struct vty *vty, + const char *peer_name, + const char *precedence_str, + long precedence) +{ + /* Check if the pcc-peer is connected to the PCC */ + if (!pcep_cli_pcc_has_pce(peer_name)) { + vty_out(vty, + "%% WARN: The peer [%s] is not connected to the PCC.\n", + peer_name); + return CMD_WARNING; + } + + struct pce_opts_cli *pce_opts_cli = pcep_cli_find_pce(peer_name); + pcep_cli_remove_pce_connection(&pce_opts_cli->pce_opts); + + /* Send a copy of the pce_opts, this one is used for CLI only */ + struct pce_opts *pce_opts_copy = + XMALLOC(MTYPE_PCEP, sizeof(struct pce_opts)); + memcpy(pce_opts_copy, &pce_opts_cli->pce_opts, sizeof(struct pce_opts)); + pcep_ctrl_remove_pcc(pcep_g->fpt, pce_opts_copy); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_show_srte_pcep_pcc(struct vty *vty) +{ + vty_out(vty, "pcc msd %d\n", pcc_msd_g); + + return CMD_SUCCESS; +} + +/* Internal util function to print pcep capabilities to a buffer */ +static void print_pcep_capabilities(char *buf, size_t buf_len, + pcep_configuration *config) +{ + if (config->support_stateful_pce_lsp_update) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_STATEFUL); + } + if (config->support_include_db_version) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_INCL_DB_VER); + } + if (config->support_lsp_triggered_resync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_LSP_TRIGGERED); + } + if (config->support_lsp_delta_sync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_LSP_DELTA); + } + if (config->support_pce_triggered_initial_sync) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_PCE_TRIGGERED); + } + if (config->support_sr_te_pst) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_SR_TE_PST); + } + if (config->pcc_can_resolve_nai_to_sid) { + csnprintfrr(buf, buf_len, "%s", PCEP_CLI_CAP_PCC_RESOLVE_NAI); + } +} + +/* Internal util function to print a pcep session */ +static void print_pcep_session_json(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info, + json_object *json) +{ + char buf[BUFFER_PCC_PCE_SIZE] = {}; + int index = 0; + pcep_session *session; + struct pcep_config_group_opts *config_opts; + struct counters_group *group; + + /* PCE IP */ + if (IS_IPADDR_V4(&pce_opts->addr)) + json_object_string_addf(json, "pceAddress", "%pI4", + &pce_opts->addr.ipaddr_v4); + else if (IS_IPADDR_V6(&pce_opts->addr)) + json_object_string_addf(json, "pceAddress", "%pI6", + &pce_opts->addr.ipaddr_v6); + json_object_int_add(json, "pcePort", pce_opts->port); + + /* PCC IP */ + if (IS_IPADDR_V4(&pcc_info->pcc_addr)) + json_object_string_addf(json, "pccAddress", "%pI4", + &pcc_info->pcc_addr.ipaddr_v4); + else if (IS_IPADDR_V6(&pcc_info->pcc_addr)) + json_object_string_addf(json, "pccAddress", "%pI6", + &pcc_info->pcc_addr.ipaddr_v6); + + json_object_int_add(json, "pccPort", pcc_info->pcc_port); + json_object_int_add(json, "pccMsd", pcc_info->msd); + + if (pcc_info->status == PCEP_PCC_OPERATING) + json_object_string_add(json, "sessionStatus", "UP"); + else + json_object_string_add(json, "sessionStatus", + pcc_status_name(pcc_info->status)); + + json_object_boolean_add(json, "bestMultiPce", + pcc_info->is_best_multi_pce); + json_object_int_add(json, "precedence", + pcc_info->precedence > 0 ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE); + json_object_string_add(json, "confidence", + pcc_info->previous_best ? "low" : "normal"); + + /* PCEPlib pcep session values, get a thread safe copy of the counters + */ + session = pcep_ctrl_get_pcep_session(pcep_g->fpt, pcc_info->pcc_id); + + /* Config Options values */ + config_opts = &pce_opts->config_opts; + json_object_int_add(json, "keepaliveConfig", + config_opts->keep_alive_seconds); + json_object_int_add(json, "deadTimerConfig", + config_opts->dead_timer_seconds); + json_object_int_add(json, "pccPcepRequestTimerConfig", + config_opts->pcep_request_time_seconds); + json_object_int_add(json, "sessionTimeoutIntervalSec", + config_opts->session_timeout_inteval_seconds); + json_object_int_add(json, "delegationTimeout", + config_opts->delegation_timeout_seconds); + json_object_boolean_add(json, "tcpMd5Authentication", + (strlen(config_opts->tcp_md5_auth) > 0)); + if (strlen(config_opts->tcp_md5_auth) > 0) + json_object_string_add(json, "tcpMd5AuthenticationString", + config_opts->tcp_md5_auth); + json_object_boolean_add(json, "draft07", !!config_opts->draft07); + json_object_boolean_add(json, "draft16AndRfc8408", + !config_opts->draft07); + + json_object_int_add(json, "nextPcRequestId", pcc_info->next_reqid); + /* original identifier used by the PCC for LSP instantiation */ + json_object_int_add(json, "nextPLspId", pcc_info->next_plspid); + + if (session != NULL) { + json_object_int_add(json, "sessionKeepalivePceNegotiatedSec", + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds); + json_object_int_add(json, "sessionDeadTimerPceNegotiatedSec", + session->pcc_config + .dead_timer_pce_negotiated_seconds); + if (pcc_info->status == PCEP_PCC_SYNCHRONIZING || + pcc_info->status == PCEP_PCC_OPERATING) { + time_t current_time = time(NULL); + struct tm lt = { 0 }; + /* Just for the timezone */ + localtime_r(¤t_time, <); + gmtime_r(&session->time_connected, <); + json_object_int_add(json, "sessionConnectionDurationSec", + (uint32_t)(current_time - + session->time_connected)); + json_object_string_addf(json, + "sessionConnectionStartTimeUTC", + "%d-%02d-%02d %02d:%02d:%02d", + lt.tm_year + 1900, lt.tm_mon + 1, + lt.tm_mday, lt.tm_hour, + lt.tm_min, lt.tm_sec); + } + + /* PCC capabilities */ + buf[0] = '\0'; + + if (config_opts->pce_initiated) + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_PCE_INITIATED); + else + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_INITIATED); + print_pcep_capabilities(buf, sizeof(buf) - index, + &session->pcc_config); + json_object_string_add(json, "pccCapabilities", buf); + + /* PCE capabilities */ + buf[0] = '\0'; + print_pcep_capabilities(buf, sizeof(buf), &session->pce_config); + if (buf[0] != '\0') + json_object_string_add(json, "pceCapabilities", buf); + XFREE(MTYPE_PCEP, session); + } else { + json_object_string_add(json, "warningSession", + "Detailed session information not available."); + } + + /* Message Counters, get a thread safe copy of the counters */ + group = pcep_ctrl_get_counters(pcep_g->fpt, pcc_info->pcc_id); + + if (group != NULL) { + struct counters_subgroup *rx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_RX_MSG); + struct counters_subgroup *tx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_TX_MSG); + json_object *json_counter; + struct counter *tx_counter, *rx_counter; + + if (rx_msgs != NULL) { + json_counter = json_object_new_object(); + for (int i = 0; i < rx_msgs->max_counters; i++) { + rx_counter = rx_msgs->counters[i]; + + if (rx_counter && + rx_counter->counter_name_json[0] != '\0') + json_object_int_add( + json_counter, + rx_counter->counter_name_json, + rx_counter->counter_value); + } + json_object_int_add(json_counter, "total", + subgroup_counters_total(rx_msgs)); + json_object_object_add(json, "messageStatisticsReceived", + json_counter); + } + if (tx_msgs != NULL) { + json_counter = json_object_new_object(); + for (int i = 0; i < tx_msgs->max_counters; i++) { + tx_counter = tx_msgs->counters[i]; + + if (tx_counter && + tx_counter->counter_name_json[0] != '\0') + json_object_int_add( + json_counter, + tx_counter->counter_name_json, + tx_counter->counter_value); + } + json_object_int_add(json_counter, "total", + subgroup_counters_total(tx_msgs)); + json_object_object_add(json, "messageStatisticsSent", + json_counter); + } + pcep_lib_free_counters(group); + } else { + json_object_string_add(json, "messageStatisticsWarning", + "Counters not available."); + } + + XFREE(MTYPE_PCEP, pcc_info); +} + +/* Internal util function to print a pcep session */ +static void print_pcep_session(struct vty *vty, struct pce_opts *pce_opts, + struct pcep_pcc_info *pcc_info) +{ + char buf[1024]; + + buf[0] = '\0'; + + vty_out(vty, "\nPCE %s\n", pce_opts->pce_name); + + /* PCE IP */ + if (IS_IPADDR_V4(&pce_opts->addr)) { + vty_out(vty, " PCE IP %pI4 port %d\n", + &pce_opts->addr.ipaddr_v4, pce_opts->port); + } else if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " PCE IPv6 %pI6 port %d\n", + &pce_opts->addr.ipaddr_v6, pce_opts->port); + } + + /* PCC IP */ + if (IS_IPADDR_V4(&pcc_info->pcc_addr)) { + vty_out(vty, " PCC IP %pI4 port %d\n", + &pcc_info->pcc_addr.ipaddr_v4, pcc_info->pcc_port); + } else if (IS_IPADDR_V6(&pcc_info->pcc_addr)) { + vty_out(vty, " PCC IPv6 %pI6 port %d\n", + &pcc_info->pcc_addr.ipaddr_v6, pcc_info->pcc_port); + } + vty_out(vty, " PCC MSD %d\n", pcc_info->msd); + + if (pcc_info->status == PCEP_PCC_OPERATING) { + vty_out(vty, " Session Status UP\n"); + } else { + vty_out(vty, " Session Status %s\n", + pcc_status_name(pcc_info->status)); + } + + if (pcc_info->is_best_multi_pce) { + vty_out(vty, " Precedence %d, best candidate\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } else { + vty_out(vty, " Precedence %d\n", + ((pcc_info->precedence > 0) ? pcc_info->precedence + : DEFAULT_PCE_PRECEDENCE)); + } + vty_out(vty, " Confidence %s\n", + ((pcc_info->previous_best) ? "low" + : "normal")); + + /* PCEPlib pcep session values, get a thread safe copy of the counters + */ + pcep_session *session = + pcep_ctrl_get_pcep_session(pcep_g->fpt, pcc_info->pcc_id); + + /* Config Options values */ + struct pcep_config_group_opts *config_opts = &pce_opts->config_opts; + + if (session != NULL) { + vty_out(vty, " Timer: KeepAlive config %d, pce-negotiated %d\n", + config_opts->keep_alive_seconds, + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds); + vty_out(vty, " Timer: DeadTimer config %d, pce-negotiated %d\n", + config_opts->dead_timer_seconds, + session->pcc_config.dead_timer_pce_negotiated_seconds); + } else { + vty_out(vty, " Timer: KeepAlive %d\n", + config_opts->keep_alive_seconds); + vty_out(vty, " Timer: DeadTimer %d\n", + config_opts->dead_timer_seconds); + } + vty_out(vty, " Timer: PcRequest %d\n", + config_opts->pcep_request_time_seconds); + vty_out(vty, " Timer: SessionTimeout Interval %d\n", + config_opts->session_timeout_inteval_seconds); + vty_out(vty, " Timer: Delegation Timeout %d\n", + config_opts->delegation_timeout_seconds); + if (strlen(config_opts->tcp_md5_auth) > 0) { + vty_out(vty, " TCP MD5 Auth Str: %s\n", + config_opts->tcp_md5_auth); + } else { + vty_out(vty, " No TCP MD5 Auth\n"); + } + + if (config_opts->draft07) { + vty_out(vty, " PCE SR Version draft07\n"); + } else { + vty_out(vty, " PCE SR Version draft16 and RFC8408\n"); + } + + vty_out(vty, " Next PcReq ID %d\n", pcc_info->next_reqid); + vty_out(vty, " Next PLSP ID %d\n", pcc_info->next_plspid); + + if (session != NULL) { + if (pcc_info->status == PCEP_PCC_SYNCHRONIZING + || pcc_info->status == PCEP_PCC_OPERATING) { + time_t current_time = time(NULL); + struct tm lt = {0}; + /* Just for the timezone */ + localtime_r(¤t_time, <); + gmtime_r(&session->time_connected, <); + vty_out(vty, + " Connected for %u seconds, since %d-%02d-%02d %02d:%02d:%02d UTC\n", + (uint32_t)(current_time + - session->time_connected), + lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, + lt.tm_hour, lt.tm_min, lt.tm_sec); + } + + /* PCC capabilities */ + buf[0] = '\0'; + int index = 0; + if (config_opts->pce_initiated) { + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_PCE_INITIATED); + } else { + index += csnprintfrr(buf, sizeof(buf), "%s", + PCEP_CLI_CAP_PCC_INITIATED); + } + print_pcep_capabilities(buf, sizeof(buf) - index, + &session->pcc_config); + vty_out(vty, " PCC Capabilities:%s\n", buf); + + /* PCE capabilities */ + buf[0] = '\0'; + print_pcep_capabilities(buf, sizeof(buf), &session->pce_config); + if (buf[0] != '\0') { + vty_out(vty, " PCE Capabilities:%s\n", buf); + } + XFREE(MTYPE_PCEP, session); + } else { + vty_out(vty, " Detailed session information not available\n"); + } + + /* Message Counters, get a thread safe copy of the counters */ + struct counters_group *group = + pcep_ctrl_get_counters(pcep_g->fpt, pcc_info->pcc_id); + + if (group != NULL) { + struct counters_subgroup *rx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_RX_MSG); + struct counters_subgroup *tx_msgs = + find_subgroup(group, COUNTER_SUBGROUP_ID_TX_MSG); + + if (rx_msgs != NULL && tx_msgs != NULL) { + vty_out(vty, " PCEP Message Statistics\n"); + vty_out(vty, " %27s %6s\n", "Sent", "Rcvd"); + for (int i = 0; i < rx_msgs->max_counters; i++) { + struct counter *rx_counter = + rx_msgs->counters[i]; + struct counter *tx_counter = + tx_msgs->counters[i]; + if (rx_counter != NULL && tx_counter != NULL) { + vty_out(vty, " %20s: %5d %5d\n", + tx_counter->counter_name, + tx_counter->counter_value, + rx_counter->counter_value); + } + } + vty_out(vty, " %20s: %5d %5d\n", "Total", + subgroup_counters_total(tx_msgs), + subgroup_counters_total(rx_msgs)); + } + pcep_lib_free_counters(group); + } else { + vty_out(vty, " Counters not available\n"); + } + + XFREE(MTYPE_PCEP, pcc_info); +} + +static int path_pcep_cli_show_srte_pcep_session(struct vty *vty, + const char *pcc_peer, bool uj) +{ + struct pce_opts_cli *pce_opts_cli; + struct pcep_pcc_info *pcc_info; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + + /* Only show 1 PCEP session */ + if (pcc_peer != NULL) { + if (json) + json_object_string_add(json, "pceName", pcc_peer); + pce_opts_cli = pcep_cli_find_pce(pcc_peer); + if (pce_opts_cli == NULL) { + if (json) { + json_object_string_addf(json, "warning", + "PCE [%s] does not exist.", + pcc_peer); + vty_json(vty, json); + } else + vty_out(vty, "%% PCE [%s] does not exist.\n", + pcc_peer); + return CMD_WARNING; + } + + if (!pcep_cli_pcc_has_pce(pcc_peer)) { + if (json) { + json_object_string_addf(json, "warning", + "PCC is not connected to PCE [%s].", + pcc_peer); + vty_json(vty, json); + } else + vty_out(vty, + "%% PCC is not connected to PCE [%s].\n", + pcc_peer); + return CMD_WARNING; + } + + pcc_info = pcep_ctrl_get_pcc_info(pcep_g->fpt, pcc_peer); + if (pcc_info == NULL) { + if (json) { + json_object_string_addf(json, "warning", + "Cannot retrieve PCEP session info for PCE [%s].", + pcc_peer); + vty_json(vty, json); + } else + vty_out(vty, + "%% Cannot retrieve PCEP session info for PCE [%s]\n", + pcc_peer); + return CMD_WARNING; + } + + if (json) { + print_pcep_session_json(vty, &pce_opts_cli->pce_opts, + pcc_info, json); + vty_json(vty, json); + } else + print_pcep_session(vty, &pce_opts_cli->pce_opts, + pcc_info); + + return CMD_SUCCESS; + } + + /* Show all PCEP sessions */ + struct pce_opts *pce_opts; + int num_pcep_sessions_conf = 0; + int num_pcep_sessions_conn = 0; + json_object *json_array = NULL, *json_entry = NULL; + + if (json) + json_array = json_object_new_array(); + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + if (json) { + json_entry = json_object_new_object(); + json_object_string_add(json_entry, "pceName", + pce_opts->pce_name); + } + pcc_info = + pcep_ctrl_get_pcc_info(pcep_g->fpt, pce_opts->pce_name); + if (pcc_info == NULL) { + if (json_entry) { + json_object_string_addf(json_entry, "warning", + "Cannot retrieve PCEP session info for PCE [%s].", + pce_opts->pce_name); + json_object_array_add(json_array, json_entry); + } else + vty_out(vty, + "%% Cannot retrieve PCEP session info for PCE [%s]\n", + pce_opts->pce_name); + continue; + } + + num_pcep_sessions_conn += + pcc_info->status == PCEP_PCC_OPERATING ? 1 : 0; + num_pcep_sessions_conf++; + if (json_entry) { + print_pcep_session_json(vty, pce_opts, pcc_info, + json_entry); + json_object_array_add(json_array, json_entry); + } else + print_pcep_session(vty, pce_opts, pcc_info); + } + if (json) { + json_object_object_add(json, "pcepSessions", json_array); + json_object_int_add(json, "pcepSessionsConfigured", + num_pcep_sessions_conf); + json_object_int_add(json, "pcepSessionsConnected", + num_pcep_sessions_conn); + vty_json(vty, json); + } else + vty_out(vty, "PCEP Sessions => Configured %d ; Connected %d\n", + num_pcep_sessions_conf, num_pcep_sessions_conn); + + return CMD_SUCCESS; +} + +static int path_pcep_cli_clear_srte_pcep_session(struct vty *vty, + const char *pcc_peer) +{ + struct pce_opts_cli *pce_opts_cli; + + /* Only clear 1 PCEP session */ + if (pcc_peer != NULL) { + pce_opts_cli = pcep_cli_find_pce(pcc_peer); + if (pce_opts_cli == NULL) { + vty_out(vty, "%% PCE [%s] does not exist.\n", pcc_peer); + return CMD_WARNING; + } + + if (!pcep_cli_pcc_has_pce(pcc_peer)) { + vty_out(vty, "%% PCC is not connected to PCE [%s].\n", + pcc_peer); + return CMD_WARNING; + } + + pcep_ctrl_reset_pcc_session(pcep_g->fpt, + pce_opts_cli->pce_opts.pce_name); + vty_out(vty, "PCEP session cleared for peer %s\n", pcc_peer); + + return CMD_SUCCESS; + } + + /* Clear all PCEP sessions */ + struct pce_opts *pce_opts; + int num_pcep_sessions = 0; + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + num_pcep_sessions++; + pcep_ctrl_reset_pcc_session(pcep_g->fpt, pce_opts->pce_name); + vty_out(vty, "PCEP session cleared for peer %s\n", + pce_opts->pce_name); + } + + vty_out(vty, "Cleared [%d] PCEP sessions\n", num_pcep_sessions); + + return CMD_SUCCESS; +} + +/* + * Config Write functions + */ + +int pcep_cli_debug_config_write(struct vty *vty) +{ + char buff[128] = ""; + + if (DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_BASIC); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_PATH); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_MESSAGE); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB)) + csnprintfrr(buff, sizeof(buff), " %s", + PCEP_VTYSH_ARG_PCEPLIB); + vty_out(vty, "debug pathd pcep%s\n", buff); + buff[0] = 0; + return 1; + } + + return 0; +} + +int pcep_cli_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&pcep_g->dbg, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&pcep_g->dbg); + + return 0; +} + +int pcep_cli_pcep_config_write(struct vty *vty) +{ + vty_out(vty, " pcep\n"); + pcep_cli_pcep_pce_config_write(vty); + pcep_cli_pce_config_write(vty); + pcep_cli_pcc_config_write(vty); + vty_out(vty, " exit\n"); + return 1; +} + +int pcep_cli_pcc_config_write(struct vty *vty) +{ + struct pce_opts *pce_opts; + char buf[128] = ""; + int lines = 0; + + /* The MSD, nor any PCE peers have been configured on the PCC */ + if (!pcc_msd_configured_g && pce_connections_g.num_connections == 0) { + return lines; + } + + vty_out(vty, " pcc\n"); + lines++; + + /* Prepare the MSD, if present */ + if (pcc_msd_configured_g) { + vty_out(vty, " %s %d\n", PCEP_VTYSH_ARG_MSD, pcc_msd_g); + lines++; + } + + if (pce_connections_g.num_connections == 0) { + goto exit; + } + + buf[0] = 0; + for (int i = 0; i < MAX_PCC; i++) { + pce_opts = pce_connections_g.connections[i]; + if (pce_opts == NULL) { + continue; + } + + /* Only show the PCEs configured in the pcc sub-command */ + if (!pcep_cli_pcc_has_pce(pce_opts->pce_name)) { + continue; + } + + csnprintfrr(buf, sizeof(buf), " peer %s", + pce_opts->pce_name); + if (pce_opts->precedence > 0 + && pce_opts->precedence != DEFAULT_PCE_PRECEDENCE) { + csnprintfrr(buf, sizeof(buf), " %s %d", + PCEP_VTYSH_ARG_PRECEDENCE, + pce_opts->precedence); + } + vty_out(vty, "%s\n", buf); + lines++; + buf[0] = 0; + } +exit: + vty_out(vty, " exit\n"); + + return lines; +} + +/* Internal function used by pcep_cli_pce_config_write() + * and pcep_cli_pcep_pce_config_write() */ +static int pcep_cli_print_pce_config(struct pcep_config_group_opts *group_opts, + char *buf, size_t buf_len) +{ + int lines = 0; + + if (group_opts->source_ip.ipa_type != IPADDR_NONE + || group_opts->source_port != 0) { + csnprintfrr(buf, buf_len, " "); + if (IS_IPADDR_V4(&group_opts->source_ip)) { + csnprintfrr(buf, buf_len, " %s %s %pI4", + PCEP_VTYSH_ARG_SOURCE_ADDRESS, + PCEP_VTYSH_ARG_IP, + &group_opts->source_ip.ipaddr_v4); + } else if (IS_IPADDR_V6(&group_opts->source_ip)) { + csnprintfrr(buf, buf_len, " %s %s %pI6", + PCEP_VTYSH_ARG_SOURCE_ADDRESS, + PCEP_VTYSH_ARG_IPV6, + &group_opts->source_ip.ipaddr_v6); + } + if (group_opts->source_port > 0) { + csnprintfrr(buf, buf_len, " %s %d", PCEP_VTYSH_ARG_PORT, + group_opts->source_port); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + /* Group the keep-alive together for devman */ + if ((group_opts->keep_alive_seconds > 0) + || (group_opts->min_keep_alive_seconds > 0) + || (group_opts->max_keep_alive_seconds > 0)) { + csnprintfrr(buf, buf_len, " %s", PCEP_VTYSH_ARG_TIMER); + + if (group_opts->keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE, + group_opts->keep_alive_seconds); + } + if (group_opts->min_keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE_MIN, + group_opts->min_keep_alive_seconds); + } + if (group_opts->max_keep_alive_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_KEEP_ALIVE_MAX, + group_opts->max_keep_alive_seconds); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + + /* Group the dead-timer together for devman */ + if ((group_opts->dead_timer_seconds > 0) + || (group_opts->min_dead_timer_seconds > 0) + || (group_opts->max_dead_timer_seconds > 0)) { + csnprintfrr(buf, buf_len, " %s", PCEP_VTYSH_ARG_TIMER); + + if (group_opts->dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER, + group_opts->dead_timer_seconds); + } + if (group_opts->min_dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER_MIN, + group_opts->min_dead_timer_seconds); + } + if (group_opts->max_dead_timer_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %d", + PCEP_VTYSH_ARG_DEAD_TIMER_MAX, + group_opts->max_dead_timer_seconds); + } + csnprintfrr(buf, buf_len, "\n"); + lines++; + } + + if (group_opts->pcep_request_time_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, PCEP_VTYSH_ARG_PCEP_REQUEST, + group_opts->pcep_request_time_seconds); + lines++; + } + if (group_opts->delegation_timeout_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, + PCEP_VTYSH_ARG_DELEGATION_TIMEOUT, + group_opts->delegation_timeout_seconds); + lines++; + } + if (group_opts->session_timeout_inteval_seconds > 0) { + csnprintfrr(buf, buf_len, " %s %s %d\n", + PCEP_VTYSH_ARG_TIMER, + PCEP_VTYSH_ARG_SESSION_TIMEOUT, + group_opts->session_timeout_inteval_seconds); + lines++; + } + if (group_opts->tcp_md5_auth[0] != '\0') { + csnprintfrr(buf, buf_len, " %s %s\n", PCEP_VTYSH_ARG_TCP_MD5, + group_opts->tcp_md5_auth); + lines++; + } + if (group_opts->draft07) { + csnprintfrr(buf, buf_len, " %s\n", + PCEP_VTYSH_ARG_SR_DRAFT07); + lines++; + } + if (group_opts->pce_initiated) { + csnprintfrr(buf, buf_len, " %s\n", PCEP_VTYSH_ARG_PCE_INIT); + lines++; + } + + return lines; +} + +int pcep_cli_pce_config_write(struct vty *vty) +{ + int lines = 0; + char buf[1024] = ""; + + for (int i = 0; i < MAX_PCE; i++) { + struct pce_opts_cli *pce_opts_cli = pcep_g->pce_opts_cli[i]; + if (pce_opts_cli == NULL) { + continue; + } + struct pce_opts *pce_opts = &pce_opts_cli->pce_opts; + + vty_out(vty, " pce %s\n", pce_opts->pce_name); + if (IS_IPADDR_V6(&pce_opts->addr)) { + vty_out(vty, " %s %s %pI6", PCEP_VTYSH_ARG_ADDRESS, + PCEP_VTYSH_ARG_IPV6, &pce_opts->addr.ipaddr_v6); + } else if (IS_IPADDR_V4(&pce_opts->addr)) { + vty_out(vty, " address %s %pI4", PCEP_VTYSH_ARG_IP, + &pce_opts->addr.ipaddr_v4); + } + if (pce_opts->port != PCEP_DEFAULT_PORT) { + vty_out(vty, " %s %d", PCEP_VTYSH_ARG_PORT, + pce_opts->port); + } + vty_out(vty, "%s\n", buf); + lines += 2; + + if (pce_opts_cli->config_group_name[0] != '\0') { + vty_out(vty, " config %s\n", + pce_opts_cli->config_group_name); + lines++; + } + + /* Only display the values configured on the PCE, not the values + * from its optional pce-config-group, nor the default values */ + lines += pcep_cli_print_pce_config( + &pce_opts_cli->pce_config_group_opts, buf, sizeof(buf)); + + vty_out(vty, "%s", buf); + buf[0] = '\0'; + + vty_out(vty, " exit\n"); + } + + return lines; +} + +int pcep_cli_pcep_pce_config_write(struct vty *vty) +{ + int lines = 0; + char buf[1024] = ""; + + for (int i = 0; i < MAX_PCE; i++) { + struct pcep_config_group_opts *group_opts = + pcep_g->config_group_opts[i]; + if (group_opts == NULL) { + continue; + } + + vty_out(vty, " pce-config %s\n", group_opts->name); + lines += 1; + + lines += + pcep_cli_print_pce_config(group_opts, buf, sizeof(buf)); + vty_out(vty, "%s", buf); + buf[0] = 0; + + vty_out(vty, " exit\n"); + } + + return lines; +} + +/* + * VTYSH command syntax definitions + * The param names are taken from the path_pcep_cli_clippy.c generated file. + */ + +DEFPY(show_debugging_pathd_pcep, + show_debugging_pathd_pcep_cmd, + "show debugging pathd-pcep", + SHOW_STR + "State of each debugging option\n" + "pathd pcep module debugging\n") +{ + vty_out(vty, "Pathd pcep debugging status:\n"); + + if (DEBUG_MODE_CHECK(&pcep_g->dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_BASIC)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_BASIC); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PATH)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_PATH); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEP)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_MESSAGE); + if (DEBUG_FLAGS_CHECK(&pcep_g->dbg, PCEP_DEBUG_MODE_PCEPLIB)) + vty_out(vty, " Pathd pcep %s debugging is on\n", + PCEP_VTYSH_ARG_PCEPLIB); + } + + return CMD_SUCCESS; +} + +DEFPY(pcep_cli_debug, + pcep_cli_debug_cmd, + "[no] debug pathd pcep [$debug_type]", + NO_STR DEBUG_STR + "pathd debugging\n" + "pcep module debugging\n" + "module basic debugging\n" + "path structures debugging\n" + "pcep message debugging\n" + "pceplib debugging\n") +{ + return path_pcep_cli_debug(vty, debug_type, !no); +} + +DEFPY(pcep_cli_show_srte_pcep_counters, + pcep_cli_show_srte_pcep_counters_cmd, + "show sr-te pcep counters", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "PCEP counters\n") +{ + return path_pcep_cli_show_srte_pcep_counters(vty); +} + +DEFPY_NOSH( + pcep_cli_pcep, + pcep_cli_pcep_cmd, + "pcep", + "PCEP configuration\n") +{ + vty->node = PCEP_NODE; + return CMD_SUCCESS; +} + +DEFPY( + pcep_cli_no_pcep, + pcep_cli_no_pcep_cmd, + "no pcep", + NO_STR + "PCEP configuration\n") +{ + /* Delete PCCs */ + path_pcep_cli_pcc_delete(vty); + + for (int i = 0; i < MAX_PCE; i++) { + /* Delete PCEs */ + if (pcep_g->pce_opts_cli[i] != NULL) { + XFREE(MTYPE_PCEP, pcep_g->pce_opts_cli[i]); + pcep_g->pce_opts_cli[i] = NULL; + pcep_g->num_pce_opts_cli--; + } + + /* Delete PCE-CONFIGs */ + if (pcep_g->config_group_opts[i] != NULL) { + XFREE(MTYPE_PCEP, pcep_g->config_group_opts[i]); + pcep_g->config_group_opts[i] = NULL; + pcep_g->num_config_group_opts--; + } + } + + return CMD_SUCCESS; +} + +DEFPY_NOSH( + pcep_cli_pcep_pce_config, + pcep_cli_pcep_pce_config_cmd, + "pce-config WORD$name", + "Shared configuration\n" + "Shared configuration name\n") +{ + return path_pcep_cli_pcep_pce_config(vty, name); +} + +DEFPY(pcep_cli_pcep_no_pce_config, + pcep_cli_pcep_no_pce_config_cmd, + "no pce-config WORD$name", + NO_STR + "Shared configuration\n" + "Shared configuration name\n") +{ + return path_pcep_cli_pcep_pce_config_delete(vty, name); +} + +DEFPY(pcep_cli_show_srte_pcep_pce_config, + pcep_cli_show_srte_pcep_pce_config_cmd, + "show sr-te pcep pce-config [$name]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show shared PCE configuration\n" + "Show default hard-coded values\n" + "Shared configuration name\n") +{ + return path_pcep_cli_show_srte_pcep_pce_config(vty, name); +} + +DEFPY_NOSH( + pcep_cli_pce, + pcep_cli_pce_cmd, + "pce WORD$name", + "PCE configuration, address sub-config is mandatory\n" + "PCE name\n") +{ + return path_pcep_cli_pce(vty, name); +} + +DEFPY(pcep_cli_no_pce, + pcep_cli_no_pce_cmd, + "no pce WORD$name", + NO_STR + "PCE configuration, address sub-config is mandatory\n" + "PCE name\n") +{ + return path_pcep_cli_pce_delete(vty, name); +} + +DEFPY(pcep_cli_show_srte_pcep_pce, + pcep_cli_show_srte_pcep_pce_cmd, + "show sr-te pcep pce [WORD$name]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show detailed pce values\n" + "pce name\n") +{ + return path_pcep_cli_show_srte_pcep_pce(vty, name); +} + +DEFPY(pcep_cli_peer_sr_draft07, + pcep_cli_peer_sr_draft07_cmd, + "[no] sr-draft07", + NO_STR + "Configure PCC to send PCEP Open with SR draft07\n") +{ + return path_pcep_cli_peer_sr_draft07(vty, no); +} + +DEFPY(pcep_cli_peer_pce_initiated, + pcep_cli_peer_pce_initiated_cmd, + "[no] pce-initiated", + NO_STR + "Configure PCC to accept PCE initiated LSPs\n") +{ + return path_pcep_cli_peer_pce_initiated(vty, no); +} + +DEFPY(pcep_cli_peer_tcp_md5_auth, + pcep_cli_peer_tcp_md5_auth_cmd, + "[no] tcp-md5-auth WORD", + NO_STR + "Configure PCC TCP-MD5 RFC2385 Authentication\n" + "TCP-MD5 Authentication string\n") +{ + return path_pcep_cli_peer_tcp_md5_auth(vty, tcp_md5_auth, no); +} + +DEFPY(pcep_cli_peer_address, + pcep_cli_peer_address_cmd, + "address [port (1024-65535)]", + "PCE IP Address configuration, mandatory configuration\n" + "PCE IPv4 address\n" + "Remote PCE server IPv4 address\n" + "PCE IPv6 address\n" + "Remote PCE server IPv6 address\n" + "Remote PCE server port\n" + "Remote PCE server port value\n") +{ + return path_pcep_cli_peer_address(vty, ip_str, &ip, ipv6_str, &ipv6, + port_str, port); +} + +DEFPY(pcep_cli_peer_source_address, + pcep_cli_peer_source_address_cmd, + "[no] source-address [ip A.B.C.D | ipv6 X:X::X:X] [port (1024-65535)]", + NO_STR + "PCE source IP Address configuration\n" + "PCE source IPv4 address\n" + "PCE source IPv4 address value\n" + "PCE source IPv6 address\n" + "PCE source IPv6 address value\n" + "Source PCE server port\n" + "Source PCE server port value\n") +{ + return path_pcep_cli_peer_source_address(vty, ip_str, &ip, ipv6_str, + &ipv6, port_str, port, no); +} + +DEFPY(pcep_cli_peer_pcep_pce_config_ref, + pcep_cli_peer_pcep_pce_config_ref_cmd, + "config WORD$name", + "PCE shared configuration to use\n" + "Shared configuration name\n") +{ + return path_pcep_cli_peer_pcep_pce_config_ref(vty, name); +} + +DEFPY(pcep_cli_peer_timers, + pcep_cli_peer_timers_cmd, + "timer [keep-alive (1-63)] [min-peer-keep-alive (1-255)] [max-peer-keep-alive (1-255)] " + "[dead-timer (4-255)] [min-peer-dead-timer (4-255)] [max-peer-dead-timer (4-255)] " + "[pcep-request (1-120)] [session-timeout-interval (1-120)] [delegation-timeout (1-60)]", + "PCE PCEP Session Timers configuration\n" + "PCC Keep Alive Timer\n" + "PCC Keep Alive Timer value in seconds\n" + "Min Acceptable PCE Keep Alive Timer\n" + "Min Acceptable PCE Keep Alive Timer value in seconds\n" + "Max Acceptable PCE Keep Alive Timer\n" + "Max Acceptable PCE Keep Alive Timer value in seconds\n" + "PCC Dead Timer\n" + "PCC Dead Timer value in seconds\n" + "Min Acceptable PCE Dead Timer\n" + "Min Acceptable PCE Dead Timer value in seconds\n" + "Max Acceptable PCE Dead Timer\n" + "Max Acceptable PCE Dead Timer value in seconds\n" + "PCC PCEP Request Timer\n" + "PCC PCEP Request Timer value in seconds\n" + "PCC Session Timeout Interval\n" + "PCC Session Timeout Interval value in seconds\n" + "Multi-PCE delegation timeout\n" + "Multi-PCE delegation timeout value in seconds\n") +{ + return path_pcep_cli_peer_timers( + vty, keep_alive_str, keep_alive, min_peer_keep_alive_str, + min_peer_keep_alive, max_peer_keep_alive_str, + max_peer_keep_alive, dead_timer_str, dead_timer, + min_peer_dead_timer_str, min_peer_dead_timer, + max_peer_dead_timer_str, max_peer_dead_timer, pcep_request_str, + pcep_request, session_timeout_interval_str, + session_timeout_interval, delegation_timeout_str, + delegation_timeout); +} + +DEFPY_NOSH( + pcep_cli_pcc, + pcep_cli_pcc_cmd, + "pcc", + "PCC configuration\n") +{ + return path_pcep_cli_pcc(vty); +} + +DEFPY(pcep_cli_no_pcc, + pcep_cli_no_pcc_cmd, + "no pcc", + NO_STR + "PCC configuration\n") +{ + return path_pcep_cli_pcc_delete(vty); +} + +DEFPY(pcep_cli_pcc_pcc_msd, + pcep_cli_pcc_pcc_msd_cmd, + "msd (1-32)", + "PCC maximum SID depth \n" + "PCC maximum SID depth value\n") +{ + return path_pcep_cli_pcc_pcc_msd(vty, msd_str, msd, false); +} + +DEFPY(no_pcep_cli_pcc_pcc_msd, + no_pcep_cli_pcc_pcc_msd_cmd, + "no msd [(1-32)]", + NO_STR + "PCC maximum SID depth \n" + "PCC maximum SID depth value\n") +{ + return path_pcep_cli_pcc_pcc_msd(vty, msd_str, msd, true); +} + +DEFPY(pcep_cli_pcc_pcc_peer, + pcep_cli_pcc_pcc_peer_cmd, + "[no] peer WORD [precedence (1-255)]", + NO_STR + "PCC PCE peer\n" + "PCC PCE name\n" + "PCC Multi-PCE precedence\n" + "PCE precedence\n") +{ + if (no != NULL) { + return path_pcep_cli_pcc_pcc_peer_delete( + vty, peer, precedence_str, precedence); + } else { + return path_pcep_cli_pcc_pcc_peer(vty, peer, precedence_str, + precedence); + } +} + +DEFPY(pcep_cli_show_srte_pcc, + pcep_cli_show_srte_pcc_cmd, + "show sr-te pcep pcc", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show current PCC configuration\n") +{ + return path_pcep_cli_show_srte_pcep_pcc(vty); +} + +DEFPY(pcep_cli_show_srte_pcep_session, + pcep_cli_show_srte_pcep_session_cmd, + "show sr-te pcep session WORD$pce [json$uj]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show PCEP Session information\n" + "PCE name\n" + JSON_STR) +{ + return path_pcep_cli_show_srte_pcep_session(vty, pce, !!uj); +} + +DEFPY(pcep_cli_show_srte_pcep_sessions, + pcep_cli_show_srte_pcep_sessions_cmd, + "show sr-te pcep session [json$uj]", + SHOW_STR + "SR-TE info\n" + "PCEP info\n" + "Show PCEP Session information\n" + JSON_STR) +{ + return path_pcep_cli_show_srte_pcep_session(vty, NULL, !!uj); +} + +DEFPY(pcep_cli_clear_srte_pcep_session, + pcep_cli_clear_srte_pcep_session_cmd, + "clear sr-te pcep session [WORD]$pce", + CLEAR_STR + "SR-TE\n" + "PCEP\n" + "Reset PCEP connection\n" + "PCE name\n") +{ + return path_pcep_cli_clear_srte_pcep_session(vty, pce); +} + +void pcep_cli_init(void) +{ + hook_register(pathd_srte_config_write, pcep_cli_pcep_config_write); + hook_register(nb_client_debug_config_write, + pcep_cli_debug_config_write); + hook_register(nb_client_debug_set_all, pcep_cli_debug_set_all); + + memset(&pce_connections_g, 0, sizeof(pce_connections_g)); + + install_node(&pcep_node); + install_node(&pcep_pcc_node); + install_node(&pcep_pce_node); + install_node(&pcep_pce_config_node); + + install_default(PCEP_PCE_CONFIG_NODE); + install_default(PCEP_PCE_NODE); + install_default(PCEP_PCC_NODE); + install_default(PCEP_NODE); + + install_element(SR_TRAFFIC_ENG_NODE, &pcep_cli_pcep_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &pcep_cli_no_pcep_cmd); + + /* PCEP configuration group related configuration commands */ + install_element(PCEP_NODE, &pcep_cli_pcep_pce_config_cmd); + install_element(PCEP_NODE, &pcep_cli_pcep_no_pce_config_cmd); + install_element(PCEP_PCE_CONFIG_NODE, + &pcep_cli_peer_source_address_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_timers_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_sr_draft07_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_pce_initiated_cmd); + install_element(PCEP_PCE_CONFIG_NODE, &pcep_cli_peer_tcp_md5_auth_cmd); + + /* PCE peer related configuration commands */ + install_element(PCEP_NODE, &pcep_cli_pce_cmd); + install_element(PCEP_NODE, &pcep_cli_no_pce_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_address_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_source_address_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_pcep_pce_config_ref_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_timers_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_sr_draft07_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_pce_initiated_cmd); + install_element(PCEP_PCE_NODE, &pcep_cli_peer_tcp_md5_auth_cmd); + + /* PCC related configuration commands */ + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcc_cmd); + install_element(PCEP_NODE, &pcep_cli_pcc_cmd); + install_element(PCEP_NODE, &pcep_cli_no_pcc_cmd); + install_element(PCEP_PCC_NODE, &pcep_cli_pcc_pcc_peer_cmd); + install_element(PCEP_PCC_NODE, &pcep_cli_pcc_pcc_msd_cmd); + install_element(PCEP_PCC_NODE, &no_pcep_cli_pcc_pcc_msd_cmd); + + /* Top commands */ + install_element(CONFIG_NODE, &pcep_cli_debug_cmd); + install_element(ENABLE_NODE, &pcep_cli_debug_cmd); + install_element(ENABLE_NODE, &show_debugging_pathd_pcep_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_counters_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_pce_config_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_pce_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_session_cmd); + install_element(ENABLE_NODE, &pcep_cli_show_srte_pcep_sessions_cmd); + install_element(ENABLE_NODE, &pcep_cli_clear_srte_pcep_session_cmd); +} diff --git a/pathd/path_pcep_cli.h b/pathd/path_pcep_cli.h new file mode 100644 index 0000000..a6fd858 --- /dev/null +++ b/pathd/path_pcep_cli.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Volta Networks, Inc + * Brady Johnson + */ + +#ifndef _PATH_PCEP_CLI_H_ +#define _PATH_PCEP_CLI_H_ + + +/* PCEP CLI Functions */ +void pcep_cli_init(void); + +#endif // _PATH_PCEP_CLI_H_ diff --git a/pathd/path_pcep_config.c b/pathd/path_pcep_config.c new file mode 100644 index 0000000..da7ee89 --- /dev/null +++ b/pathd/path_pcep_config.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include +#include +#include +#include "pceplib/pcep_msg_objects.h" +#include "pathd/pathd.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" +#include "frrevent.h" + +#define MAX_XPATH 256 +#define MAX_FLOAT_LEN 22 +#define INETADDR4_MAXLEN 16 +#define INETADDR6_MAXLEN 40 +#define INITIATED_CANDIDATE_PREFERENCE 255 +#define INITIATED_POLICY_COLOR 1 + + +static void copy_candidate_objfun_info(struct srte_candidate *candidate, + struct path *path); +static void copy_candidate_affinity_filters(struct srte_candidate *candidate, + struct path *path); +static struct path_hop * +path_pcep_config_list_path_hops(struct srte_segment_list *segment_list); +static struct srte_candidate *lookup_candidate(struct lsp_nb_key *key); +static char *candidate_name(struct srte_candidate *candidate); +static enum pcep_lsp_operational_status +status_int_to_ext(enum srte_policy_status status); +static enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type); +static enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type); + +void path_pcep_refine_path(struct path *path) +{ + struct srte_candidate *candidate = lookup_candidate(&path->nbkey); + struct srte_lsp *lsp; + + if (candidate == NULL) + return; + + lsp = candidate->lsp; + + if (path->name == NULL) + path->name = candidate_name(candidate); + if (path->type == SRTE_CANDIDATE_TYPE_UNDEFINED) + path->type = candidate->type; + if (path->create_origin == SRTE_ORIGIN_UNDEFINED) + path->create_origin = candidate->protocol_origin; + if ((path->update_origin == SRTE_ORIGIN_UNDEFINED) + && (lsp->segment_list != NULL)) + path->update_origin = lsp->segment_list->protocol_origin; +} + +struct path *path_pcep_config_get_path(struct lsp_nb_key *key) +{ + struct srte_candidate *candidate = lookup_candidate(key); + if (candidate == NULL) + return NULL; + return candidate_to_path(candidate); +} + +void path_pcep_config_list_path(path_list_cb_t cb, void *arg) +{ + struct path *path; + struct srte_policy *policy; + struct srte_candidate *candidate; + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + path = candidate_to_path(candidate); + if (!cb(path, arg)) + return; + } + } +} + +struct path *candidate_to_path(struct srte_candidate *candidate) +{ + char *name; + struct path *path; + struct path_hop *hop = NULL; + struct path_metric *metric = NULL; + struct srte_policy *policy; + struct srte_lsp *lsp; + enum pcep_lsp_operational_status status; + enum srte_protocol_origin update_origin = 0; + char *originator = NULL; + + policy = candidate->policy; + lsp = candidate->lsp; + + if (lsp->segment_list != NULL) { + hop = path_pcep_config_list_path_hops(lsp->segment_list); + update_origin = lsp->segment_list->protocol_origin; + originator = XSTRDUP(MTYPE_PCEP, lsp->segment_list->originator); + } + path = pcep_new_path(); + name = candidate_name(candidate); + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_BEST)) { + status = status_int_to_ext(policy->status); + } else { + status = PCEP_LSP_OPERATIONAL_DOWN; + } + for (uint32_t i = 0; i < MAX_METRIC_TYPE; i++) { + struct path_metric *path_metric; + struct srte_metric *srte_metric = &lsp->metrics[i]; + if (CHECK_FLAG(srte_metric->flags, F_METRIC_IS_DEFINED)) { + path_metric = pcep_new_metric(); + path_metric->next = metric; + metric = path_metric; + metric->type = i + 1; + metric->value = srte_metric->value; + metric->enforce = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_REQUIRED); + metric->is_bound = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_BOUND); + metric->is_computed = CHECK_FLAG(srte_metric->flags, + F_METRIC_IS_COMPUTED); + } + } + *path = (struct path){ + .nbkey = (struct lsp_nb_key){.color = policy->color, + .endpoint = policy->endpoint, + .preference = + candidate->preference}, + .create_origin = lsp->protocol_origin, + .update_origin = update_origin, + .originator = originator, + .plsp_id = 0, + .name = name, + .type = candidate->type, + .srp_id = policy->srp_id, + .req_id = 0, + .binding_sid = policy->binding_sid, + .status = status, + .do_remove = false, + .go_active = false, + .was_created = false, + .was_removed = false, + .is_synching = false, + .is_delegated = false, + .first_hop = hop, + .first_metric = metric}; + + path->has_bandwidth = CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + if (path->has_bandwidth) { + path->enforce_bandwidth = + CHECK_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + path->bandwidth = lsp->bandwidth; + } else { + path->enforce_bandwidth = true; + path->bandwidth = 0; + } + + copy_candidate_objfun_info(candidate, path); + copy_candidate_affinity_filters(candidate, path); + + return path; +} + +void copy_candidate_objfun_info(struct srte_candidate *candidate, + struct path *path) +{ + struct srte_lsp *lsp = candidate->lsp; + + if (lsp != NULL) { + if (CHECK_FLAG(lsp->flags, F_CANDIDATE_HAS_OBJFUN)) { + path->has_pce_objfun = true; + path->pce_objfun = lsp->objfun; + } else { + path->has_pce_objfun = false; + path->pce_objfun = OBJFUN_UNDEFINED; + } + } + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN)) { + path->has_pcc_objfun = true; + path->pcc_objfun = candidate->objfun; + path->enforce_pcc_objfun = CHECK_FLAG( + candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + + } else { + path->has_pcc_objfun = false; + path->pcc_objfun = OBJFUN_UNDEFINED; + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + } +} + +void copy_candidate_affinity_filters(struct srte_candidate *candidate, + struct path *path) +{ + bool eany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_EXCLUDE_ANY); + bool iany = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ANY); + bool iall = CHECK_FLAG(candidate->flags, F_CANDIDATE_HAS_INCLUDE_ALL); + path->has_affinity_filters = eany || iany || iall; + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1] = + eany ? candidate->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY + - 1] + : 0; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1] = + iany ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY + - 1] + : 0; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1] = + iall ? candidate->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL + - 1] + : 0; +} + +struct path_hop * +path_pcep_config_list_path_hops(struct srte_segment_list *segment_list) +{ + struct srte_segment_entry *segment; + struct path_hop *hop = NULL, *last_hop = NULL; + + RB_FOREACH_REVERSE (segment, srte_segment_entry_head, + &segment_list->segments) { + hop = pcep_new_hop(); + *hop = (struct path_hop){ + .next = last_hop, + .is_loose = false, + .has_sid = true, + .is_mpls = true, + .has_attribs = false, + .sid = {.mpls = {.label = segment->sid_value}}, + .has_nai = + segment->nai_type != SRTE_SEGMENT_NAI_TYPE_NONE, + .nai = {.type = pcep_nai_type(segment->nai_type)}}; + switch (segment->nai_type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr, + sizeof(struct ipaddr)); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + memcpy(&hop->nai.local_addr, &segment->nai_local_addr, + sizeof(struct ipaddr)); + hop->nai.local_iface = segment->nai_local_iface; + memcpy(&hop->nai.remote_addr, &segment->nai_remote_addr, + sizeof(struct ipaddr)); + hop->nai.remote_iface = segment->nai_remote_iface; + break; + case SRTE_SEGMENT_NAI_TYPE_NONE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + break; + } + last_hop = hop; + } + return hop; +} + +int path_pcep_config_initiate_path(struct path *path) +{ + struct srte_policy *policy; + struct srte_candidate *candidate; + + if (path->do_remove) { + zlog_warn("PCE %s tried to REMOVE pce-initiate a path ", + path->originator); + candidate = lookup_candidate(&path->nbkey); + if (candidate) { + if (!path->is_delegated) { + zlog_warn( + "(%s)PCE tried to REMOVE but it's not Delegated!", + __func__); + return ERROR_19_1; + } + if (candidate->type != SRTE_CANDIDATE_TYPE_DYNAMIC) { + zlog_warn( + "(%s)PCE tried to REMOVE but it's not PCE origin!", + __func__); + return ERROR_19_9; + } + zlog_warn( + "(%s)PCE tried to REMOVE found candidate!, let's remove", + __func__); + candidate->policy->srp_id = path->srp_id; + SET_FLAG(candidate->policy->flags, F_POLICY_DELETED); + SET_FLAG(candidate->flags, F_CANDIDATE_DELETED); + } else { + zlog_warn("(%s)PCE tried to REMOVE not existing LSP!", + __func__); + return ERROR_19_3; + } + srte_apply_changes(); + } else { + assert(!IS_IPADDR_NONE(&path->nbkey.endpoint)); + + if (path->nbkey.preference == 0) + path->nbkey.preference = INITIATED_CANDIDATE_PREFERENCE; + + if (path->nbkey.color == 0) + path->nbkey.color = INITIATED_POLICY_COLOR; + + candidate = lookup_candidate(&path->nbkey); + if (!candidate) { + policy = srte_policy_add( + path->nbkey.color, &path->nbkey.endpoint, + SRTE_ORIGIN_PCEP, path->originator); + strlcpy(policy->name, path->name, sizeof(policy->name)); + policy->binding_sid = path->binding_sid; + SET_FLAG(policy->flags, F_POLICY_NEW); + candidate = srte_candidate_add( + policy, path->nbkey.preference, + SRTE_ORIGIN_PCEP, path->originator); + candidate->policy->srp_id = path->srp_id; + strlcpy(candidate->name, path->name, + sizeof(candidate->name)); + SET_FLAG(candidate->flags, F_CANDIDATE_NEW); + } else { + policy = candidate->policy; + if ((path->originator != candidate->originator) + || (path->originator != policy->originator)) { + /* There is already an initiated path from + * another PCE, show a warning and regect the + * initiated path */ + zlog_warn( + "PCE %s tried to initiate a path already initiated by PCE %s", + path->originator, + candidate->originator); + return 1; + } + if ((policy->protocol_origin != SRTE_ORIGIN_PCEP) + || (candidate->protocol_origin + != SRTE_ORIGIN_PCEP)) { + /* There is already an initiated path from + * another PCE, show a warning and regect the + * initiated path */ + zlog_warn( + "PCE %s tried to initiate a path created localy", + path->originator); + return 1; + } + } + return path_pcep_config_update_path(path); + } + return 0; +} + +int path_pcep_config_update_path(struct path *path) +{ + assert(path != NULL); + assert(path->nbkey.preference != 0); + assert(path->nbkey.endpoint.ipa_type == IPADDR_V4); + + int number_of_sid_clashed = 0; + struct path_hop *hop; + struct path_metric *metric; + int index; + char segment_list_name_buff[64 + 1 + 64 + 1 + 11 + 1]; + char *segment_list_name = NULL; + struct srte_candidate *candidate; + struct srte_segment_list *segment_list = NULL; + struct srte_segment_entry *segment; + + candidate = lookup_candidate(&path->nbkey); + + // if there is no candidate to update we are done + if (!candidate) + return 0; + + candidate->policy->srp_id = path->srp_id; + // first clean up old segment list if present + if (candidate->lsp->segment_list) { + SET_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_DELETED); + srte_segment_list_del(candidate->lsp->segment_list); + candidate->lsp->segment_list = NULL; + } + + if (path->first_hop == NULL) + return PATH_NB_ERR; + + snprintf(segment_list_name_buff, sizeof(segment_list_name_buff), + "%s-%u", path->name, path->plsp_id); + segment_list_name = segment_list_name_buff; + + segment_list = srte_segment_list_add(segment_list_name); + segment_list->protocol_origin = path->update_origin; + strlcpy(segment_list->originator, path->originator, + sizeof(segment_list->originator)); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + for (hop = path->first_hop, index = 10; hop != NULL; + hop = hop->next, index += 10) { + assert(hop->has_sid); + assert(hop->is_mpls); + + segment = srte_segment_entry_add(segment_list, index); + + segment->sid_value = (mpls_label_t)hop->sid.mpls.label; + SET_FLAG(segment->segment_list->flags, F_SEGMENT_LIST_MODIFIED); + + if (!hop->has_nai) + continue; + if (srte_segment_entry_set_nai( + segment, srte_nai_type(hop->nai.type), + &hop->nai.local_addr, hop->nai.local_iface, + &hop->nai.remote_addr, hop->nai.remote_iface, 0, 0) + == PATH_SID_ERROR) + /* TED queries don't match PCE */ + /* Don't apply srte,zebra changes */ + number_of_sid_clashed++; + } + + candidate->lsp->segment_list = segment_list; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + + for (metric = path->first_metric; metric != NULL; metric = metric->next) + srte_lsp_set_metric( + candidate->lsp, + (enum srte_candidate_metric_type)metric->type, + metric->value, metric->enforce, metric->is_bound, + metric->is_computed); + + if (path->has_bandwidth) + srte_lsp_set_bandwidth(candidate->lsp, path->bandwidth, + path->enforce_bandwidth); + + if (path->has_pce_objfun) { + SET_FLAG(candidate->lsp->flags, F_CANDIDATE_HAS_OBJFUN); + candidate->lsp->objfun = path->pce_objfun; + } + + if (number_of_sid_clashed) + SET_FLAG(segment_list->flags, F_SEGMENT_LIST_SID_CONFLICT); + else + srte_apply_changes(); + + return 0; +} + +struct srte_candidate *lookup_candidate(struct lsp_nb_key *key) +{ + struct srte_policy *policy = NULL; + policy = srte_policy_find(key->color, &key->endpoint); + if (policy == NULL) + return NULL; + return srte_candidate_find(policy, key->preference); +} + +char *candidate_name(struct srte_candidate *candidate) +{ + if (candidate->protocol_origin == SRTE_ORIGIN_PCEP + || candidate->protocol_origin == SRTE_ORIGIN_BGP) + return asprintfrr(MTYPE_PCEP, "%s", candidate->policy->name); + else + return asprintfrr(MTYPE_PCEP, "%s-%s", candidate->policy->name, + candidate->name); +} + +enum pcep_lsp_operational_status +status_int_to_ext(enum srte_policy_status status) +{ + switch (status) { + case SRTE_POLICY_STATUS_UP: + return PCEP_LSP_OPERATIONAL_ACTIVE; + case SRTE_POLICY_STATUS_GOING_UP: + return PCEP_LSP_OPERATIONAL_GOING_UP; + case SRTE_POLICY_STATUS_GOING_DOWN: + return PCEP_LSP_OPERATIONAL_GOING_DOWN; + case SRTE_POLICY_STATUS_DOWN: + case SRTE_POLICY_STATUS_UNKNOWN: + return PCEP_LSP_OPERATIONAL_DOWN; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +enum pcep_sr_subobj_nai pcep_nai_type(enum srte_segment_nai_type type) +{ + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_NONE: + return PCEP_SR_SUBOBJ_NAI_ABSENT; + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + return PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + return PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + return PCEP_SR_SUBOBJ_NAI_IPV4_NODE; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + return PCEP_SR_SUBOBJ_NAI_IPV6_NODE; + default: + return PCEP_SR_SUBOBJ_NAI_UNKNOWN; + } +} + +enum srte_segment_nai_type srte_nai_type(enum pcep_sr_subobj_nai type) +{ + switch (type) { + case PCEP_SR_SUBOBJ_NAI_ABSENT: + return SRTE_SEGMENT_NAI_TYPE_NONE; + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + return SRTE_SEGMENT_NAI_TYPE_IPV4_NODE; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + return SRTE_SEGMENT_NAI_TYPE_IPV6_NODE; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + return SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY; + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + return SRTE_SEGMENT_NAI_TYPE_NONE; + } + + assert(!"Reached end of function where we were not expecting to"); +} diff --git a/pathd/path_pcep_config.h b/pathd/path_pcep_config.h new file mode 100644 index 0000000..e933728 --- /dev/null +++ b/pathd/path_pcep_config.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_CONFIG_H_ +#define _PATH_PCEP_CONFIG_H_ + +#include +#include + +#include "pathd/path_pcep.h" + +#define PATH_NB_NO_CHANGE 0 +#define PATH_NB_OK 1 +#define PATH_NB_ERR -1 + +typedef int (*path_list_cb_t)(struct path *path, void *arg); + +/* Lookup the candidate path and fill up the missing path attributes like name + * and type. Used for path generated from PCEP message received from the PCE + * so they contains more information about the candidate path. If no matching + * policy or candidate path is found, nothing is changed. + * MUST BE CALLED FROM THE MAIN THREAD */ +void path_pcep_refine_path(struct path *path); +struct path *path_pcep_config_get_path(struct lsp_nb_key *key); +void path_pcep_config_list_path(path_list_cb_t cb, void *arg); +int path_pcep_config_initiate_path(struct path *path); +int path_pcep_config_update_path(struct path *path); +struct path *candidate_to_path(struct srte_candidate *candidate); + + +#endif // _PATH_PCEP_CONFIG_H_ diff --git a/pathd/path_pcep_controller.c b/pathd/path_pcep_controller.c new file mode 100644 index 0000000..a00a114 --- /dev/null +++ b/pathd/path_pcep_controller.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" +#include "network.h" + +#include "pathd/pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_pcc.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" + +#define MAX_RECONNECT_DELAY 120 + +/* Event handling data structures */ +enum pcep_ctrl_event_type { + EV_UPDATE_PCC_OPTS = 1, + EV_UPDATE_PCE_OPTS, + EV_REMOVE_PCC, + EV_PATHD_EVENT, + EV_SYNC_PATH, + EV_SYNC_DONE, + EV_PCEPLIB_EVENT, + EV_RESET_PCC_SESSION, + EV_SEND_REPORT, + EV_SEND_ERROR, + EV_PATH_REFINED +}; + +struct pcep_ctrl_event_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_event_type type; + uint32_t sub_type; + int pcc_id; + void *payload; +}; + +struct pcep_main_event_data { + pcep_main_event_handler_t handler; + int pcc_id; + enum pcep_main_event_type type; + void *payload; +}; + +struct pcep_refine_path_event_data { + struct ctrl_state *ctrl_state; + int pcc_id; + pcep_refine_callback_t continue_lsp_update_handler; + struct path *path; + void *payload; +}; + +/* Synchronous call arguments */ + +struct get_counters_args { + struct ctrl_state *ctrl_state; + int pcc_id; + struct counters_group *counters; +}; + +struct get_pcep_session_args { + struct ctrl_state *ctrl_state; + int pcc_id; + pcep_session *pcep_session; +}; + +/* Internal Functions Called From Main Thread */ +static int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res); +static void pcep_refine_path_event_cb(struct event *thread); + +/* Internal Functions Called From Controller Thread */ +static void pcep_thread_finish_event_handler(struct event *thread); + +/* Controller Thread Timer Handler */ +static int schedule_thread_timer(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, + struct event **thread); +static int schedule_thread_timer_with_cb( + struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, uint32_t delay, void *payload, + struct event **thread, pcep_ctrl_thread_callback timer_cb); +static void pcep_thread_timer_handler(struct event *thread); + +/* Controller Thread Socket read/write Handler */ +static int schedule_thread_socket(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_socket_type type, bool is_read, + void *payload, int fd, struct event **thread, + pcep_ctrl_thread_callback cb); + +/* Controller Thread Event Handler */ +static int send_to_thread(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload); +static int send_to_thread_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, + uint32_t sub_type, void *payload, + pcep_ctrl_thread_callback event_cb); +static void pcep_thread_event_handler(struct event *thread); +static int pcep_thread_event_update_pcc_options(struct ctrl_state *ctrl_state, + struct pcc_opts *opts); +static int pcep_thread_event_update_pce_options(struct ctrl_state *ctrl_state, + int pcc_id, + struct pce_opts *opts); +static int pcep_thread_event_remove_pcc_by_id(struct ctrl_state *ctrl_state, + int pcc_id); +static int pcep_thread_event_remove_pcc_all(struct ctrl_state *ctrl_state); +static int pcep_thread_event_remove_pcc(struct ctrl_state *ctrl_state, + struct pce_opts *pce_opts); +static int pcep_thread_event_sync_path(struct ctrl_state *ctrl_state, + int pcc_id, struct path *path); +static int pcep_thread_event_sync_done(struct ctrl_state *ctrl_state, + int pcc_id); +static int pcep_thread_event_pathd_event(struct ctrl_state *ctrl_state, + enum pcep_pathd_event_type type, + struct path *path); +static void +pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data); + +/* Main Thread Event Handler */ +static int send_to_main(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_main_event_type type, void *payload); +static void pcep_main_event_handler(struct event *thread); + +/* Helper functions */ +static void set_ctrl_state(struct frr_pthread *fpt, + struct ctrl_state *ctrl_state); +static struct ctrl_state *get_ctrl_state(struct frr_pthread *fpt); +int get_next_id(struct ctrl_state *ctrl_state); +int set_pcc_state(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state); +void remove_pcc_state(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static uint32_t backoff_delay(uint32_t max, uint32_t base, uint32_t attempt); +static const char *timer_type_name(enum pcep_ctrl_timer_type type); +static const char *timeout_type_name(enum pcep_ctrl_timeout_type type); + + +/* ------------ API Functions Called from Main Thread ------------ */ + +int pcep_ctrl_initialize(struct event_loop *main_thread, + struct frr_pthread **fpt, + pcep_main_event_handler_t event_handler) +{ + assert(fpt != NULL); + + int ret = 0; + struct ctrl_state *ctrl_state; + struct frr_pthread_attr attr = { + .start = frr_pthread_attr_default.start, + .stop = pcep_ctrl_halt_cb, + }; + + PCEP_DEBUG("Initializing pcep module controller"); + + /* Create and start the FRR pthread */ + *fpt = frr_pthread_new(&attr, "PCEP thread", "pcep_controller"); + if (*fpt == NULL) { + flog_err(EC_PATH_SYSTEM_CALL, + "failed to initialize PCEP thread"); + return 1; + } + ret = frr_pthread_run(*fpt, NULL); + if (ret < 0) { + flog_err(EC_PATH_SYSTEM_CALL, "failed to create PCEP thread"); + return ret; + } + frr_pthread_wait_running(*fpt); + + /* Initialize the thread state */ + ctrl_state = XCALLOC(MTYPE_PCEP, sizeof(*ctrl_state)); + ctrl_state->main = main_thread; + ctrl_state->self = (*fpt)->master; + ctrl_state->main_event_handler = event_handler; + ctrl_state->pcc_count = 0; + ctrl_state->pcc_last_id = 0; + ctrl_state->pcc_opts = + XCALLOC(MTYPE_PCEP, sizeof(*ctrl_state->pcc_opts)); + /* Default to no PCC address defined */ + ctrl_state->pcc_opts->addr.ipa_type = IPADDR_NONE; + ctrl_state->pcc_opts->port = PCEP_DEFAULT_PORT; + + /* Keep the state reference for events */ + set_ctrl_state(*fpt, ctrl_state); + + return ret; +} + +int pcep_ctrl_finalize(struct frr_pthread **fpt) +{ + assert(fpt != NULL); + + int ret = 0; + + PCEP_DEBUG("Finalizing pcep module controller"); + + if (*fpt != NULL) { + frr_pthread_stop(*fpt, NULL); + *fpt = NULL; + } + + return ret; +} + +int pcep_ctrl_update_pcc_options(struct frr_pthread *fpt, struct pcc_opts *opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_UPDATE_PCC_OPTS, 0, opts); +} + +int pcep_ctrl_update_pce_options(struct frr_pthread *fpt, struct pce_opts *opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_UPDATE_PCE_OPTS, 0, opts); +} + +int pcep_ctrl_remove_pcc(struct frr_pthread *fpt, struct pce_opts *pce_opts) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_REMOVE_PCC, 0, pce_opts); +} + +int pcep_ctrl_reset_pcc_session(struct frr_pthread *fpt, char *pce_name) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_RESET_PCC_SESSION, 0, pce_name); +} + +int pcep_ctrl_pathd_event(struct frr_pthread *fpt, + enum pcep_pathd_event_type type, struct path *path) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, 0, EV_PATHD_EVENT, type, path); +} + +int pcep_ctrl_sync_path(struct frr_pthread *fpt, int pcc_id, struct path *path) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SYNC_PATH, 0, path); +} + +int pcep_ctrl_sync_done(struct frr_pthread *fpt, int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SYNC_DONE, 0, NULL); +} + +struct counters_group *pcep_ctrl_get_counters(struct frr_pthread *fpt, + int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct counters_group *counters = NULL; + struct pcc_state *pcc_state; + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + counters = pcep_lib_copy_counters(pcc_state->sess); + } + return counters; +} + +pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct pcc_state *pcc_state; + pcep_session *session = NULL; + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + session = pcep_lib_copy_pcep_session(pcc_state->sess); + } + return session; +} + +struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, + const char *pce_name) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + struct pcep_pcc_info *pcc_info = XCALLOC(MTYPE_PCEP, sizeof(*pcc_info)); + if( pcc_info && ctrl_state){ + strlcpy(pcc_info->pce_name, pce_name, sizeof(pcc_info->pce_name)); + pcep_pcc_copy_pcc_info(ctrl_state->pcc, pcc_info); + } + + return pcc_info; +} + +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SEND_REPORT, is_stable, + path); +} + + +int pcep_ctrl_send_error(struct frr_pthread *fpt, int pcc_id, + struct pcep_error *error) +{ + struct ctrl_state *ctrl_state = get_ctrl_state(fpt); + return send_to_thread(ctrl_state, pcc_id, EV_SEND_ERROR, 0, error); +} + + +/* ------------ Internal Functions Called from Main Thread ------------ */ + +int pcep_ctrl_halt_cb(struct frr_pthread *fpt, void **res) +{ + event_add_event(fpt->master, pcep_thread_finish_event_handler, + (void *)fpt, 0, NULL); + pthread_join(fpt->thread, res); + + return 0; +} + +void pcep_refine_path_event_cb(struct event *thread) +{ + struct pcep_refine_path_event_data *data = EVENT_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + struct path *path = data->path; + assert(path != NULL); + int pcc_id = data->pcc_id; + + + path_pcep_refine_path(path); + send_to_thread(ctrl_state, pcc_id, EV_PATH_REFINED, 0, data); +} + + +/* ------------ API Functions Called From Controller Thread ------------ */ + +void pcep_thread_start_sync(struct ctrl_state *ctrl_state, int pcc_id) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_START_SYNC, NULL); +} + +void pcep_thread_update_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_UPDATE_CANDIDATE, + path); +} + +void pcep_thread_initiate_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + send_to_main(ctrl_state, pcc_id, PCEP_MAIN_EVENT_INITIATE_CANDIDATE, + path); +} + +void pcep_thread_remove_candidate_path_segments(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + if (!pcc_state) + return; + /* Will be deleted when the event is handled */ + char *originator = XSTRDUP(MTYPE_PCEP, pcc_state->originator); + PCEP_DEBUG("schedule candidate path segments removal for originator %s", + originator); + send_to_main(ctrl_state, pcep_pcc_get_pcc_id(pcc_state), + PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP, originator); +} + +void pcep_thread_schedule_sync_best_pce(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct event **thread) +{ + + schedule_thread_timer(ctrl_state, pcc_id, TM_CALCULATE_BEST_PCE, + TO_UNDEFINED, delay, NULL, thread); +} + +void pcep_thread_cancel_timer(struct event **thread) +{ + if (thread == NULL || *thread == NULL) { + return; + } + + struct pcep_ctrl_timer_data *data = EVENT_ARG(*thread); + PCEP_DEBUG("Timer %s / %s canceled", timer_type_name(data->timer_type), + timeout_type_name(data->timeout_type)); + if (data != NULL) { + XFREE(MTYPE_PCEP, data); + } + + if ((*thread)->master->owner == pthread_self()) { + event_cancel(thread); + } else { + event_cancel_async((*thread)->master, thread, NULL); + } +} + +void pcep_thread_schedule_reconnect(struct ctrl_state *ctrl_state, int pcc_id, + int retry_count, struct event **thread) +{ + uint32_t delay = backoff_delay(MAX_RECONNECT_DELAY, 1, retry_count); + PCEP_DEBUG("Schedule RECONNECT_PCC for %us (retry %u)", delay, + retry_count); + schedule_thread_timer(ctrl_state, pcc_id, TM_RECONNECT_PCC, + TO_UNDEFINED, delay, NULL, thread); +} + +void pcep_thread_schedule_timeout(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *param, + struct event **thread) +{ + assert(timeout_type > TO_UNDEFINED); + assert(timeout_type < TO_MAX); + PCEP_DEBUG("Schedule timeout %s for %us", + timeout_type_name(timeout_type), delay); + schedule_thread_timer(ctrl_state, pcc_id, TM_TIMEOUT, timeout_type, + delay, param, thread); +} + +void pcep_thread_schedule_pceplib_timer(struct ctrl_state *ctrl_state, + int delay, void *payload, + struct event **thread, + pcep_ctrl_thread_callback timer_cb) +{ + PCEP_DEBUG("Schedule PCEPLIB_TIMER for %us", delay); + schedule_thread_timer_with_cb(ctrl_state, 0, TM_PCEPLIB_TIMER, + TO_UNDEFINED, delay, payload, thread, + timer_cb); +} + +void pcep_thread_schedule_session_timeout(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct event **thread) +{ + PCEP_DEBUG("Schedule session_timeout interval for %us", delay); + schedule_thread_timer(ctrl_state, pcc_id, TM_SESSION_TIMEOUT_PCC, + TO_UNDEFINED, delay, NULL, thread); +} + +int pcep_thread_pcc_count(struct ctrl_state *ctrl_state) +{ + if (ctrl_state == NULL) { + return 0; + } + + return ctrl_state->pcc_count; +} + +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload) +{ + struct pcep_refine_path_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->path = path; + data->pcc_id = pcc_id; + data->continue_lsp_update_handler = cb; + data->payload = payload; + + event_add_event(ctrl_state->main, pcep_refine_path_event_cb, + (void *)data, 0, NULL); + return 0; +} + +void pcep_thread_path_refined_event(struct ctrl_state *ctrl_state, + struct pcep_refine_path_event_data *data) +{ + assert(data != NULL); + int pcc_id = data->pcc_id; + pcep_refine_callback_t continue_lsp_update_handler = data->continue_lsp_update_handler; + assert(continue_lsp_update_handler != NULL); + struct path *path = data->path; + void *payload = data->payload; + struct pcc_state *pcc_state = NULL; + XFREE(MTYPE_PCEP, data); + + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + continue_lsp_update_handler(ctrl_state, pcc_state, path, payload); +} + + +/* ------------ Internal Functions Called From Controller Thread ------------ */ + +void pcep_thread_finish_event_handler(struct event *thread) +{ + int i; + struct frr_pthread *fpt = EVENT_ARG(thread); + struct ctrl_state *ctrl_state = fpt->data; + + assert(ctrl_state != NULL); + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + pcep_pcc_finalize(ctrl_state, ctrl_state->pcc[i]); + ctrl_state->pcc[i] = NULL; + } + } + + XFREE(MTYPE_PCEP, ctrl_state->pcc_opts); + XFREE(MTYPE_PCEP, ctrl_state); + fpt->data = NULL; + + atomic_store_explicit(&fpt->running, false, memory_order_relaxed); +} + +/* ------------ Controller Thread Timer Handler ------------ */ + +int schedule_thread_timer_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, + struct event **thread, + pcep_ctrl_thread_callback timer_cb) +{ + assert(thread != NULL); + + struct pcep_ctrl_timer_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->timer_type = timer_type; + data->timeout_type = timeout_type; + data->pcc_id = pcc_id; + data->payload = payload; + + event_add_timer(ctrl_state->self, timer_cb, (void *)data, delay, + thread); + + return 0; +} + +int schedule_thread_timer(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timer_type timer_type, + enum pcep_ctrl_timeout_type timeout_type, + uint32_t delay, void *payload, struct event **thread) +{ + return schedule_thread_timer_with_cb(ctrl_state, pcc_id, timer_type, + timeout_type, delay, payload, + thread, pcep_thread_timer_handler); +} + +void pcep_thread_timer_handler(struct event *thread) +{ + /* data unpacking */ + struct pcep_ctrl_timer_data *data = EVENT_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + assert(ctrl_state != NULL); + enum pcep_ctrl_timer_type timer_type = data->timer_type; + enum pcep_ctrl_timeout_type timeout_type = data->timeout_type; + int pcc_id = data->pcc_id; + void *param = data->payload; + XFREE(MTYPE_PCEP, data); + + struct pcc_state *pcc_state = NULL; + + switch (timer_type) { + case TM_RECONNECT_PCC: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (!pcc_state) + return; + pcep_pcc_reconnect(ctrl_state, pcc_state); + break; + case TM_TIMEOUT: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (!pcc_state) + return; + pcep_pcc_timeout_handler(ctrl_state, pcc_state, timeout_type, + param); + break; + case TM_CALCULATE_BEST_PCE: + /* Previous best disconnect so new best should be synced */ + pcep_pcc_timer_update_best_pce(ctrl_state, pcc_id); + break; + case TM_SESSION_TIMEOUT_PCC: + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_thread_remove_candidate_path_segments(ctrl_state, + pcc_state); + break; + case TM_PCEPLIB_TIMER: + case TM_UNDEFINED: + case TM_MAX: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unknown controller timer triggered: %u", timer_type); + break; + } +} + +void pcep_thread_pcep_event(struct event *thread) +{ + struct pcep_ctrl_event_data *data = EVENT_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + pcep_event *event = data->payload; + XFREE(MTYPE_PCEP, data); + int i; + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + struct pcc_state *pcc_state = ctrl_state->pcc[i]; + if (pcc_state->sess != event->session) + continue; + pcep_pcc_pcep_event_handler(ctrl_state, pcc_state, + event); + break; + } + } + destroy_pcep_event(event); +} + +/* ------------ Controller Thread Socket Functions ------------ */ + +int schedule_thread_socket(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_socket_type type, bool is_read, + void *payload, int fd, struct event **thread, + pcep_ctrl_thread_callback socket_cb) +{ + assert(thread != NULL); + + struct pcep_ctrl_socket_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->type = type; + data->is_read = is_read; + data->fd = fd; + data->pcc_id = pcc_id; + data->payload = payload; + + if (is_read) { + event_add_read(ctrl_state->self, socket_cb, (void *)data, fd, + thread); + } else { + event_add_write(ctrl_state->self, socket_cb, (void *)data, fd, + thread); + } + + return 0; +} + +int pcep_thread_socket_write(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback socket_cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return schedule_thread_socket(ctrl_state, 0, SOCK_PCEPLIB, false, + payload, fd, (struct event **)thread, + socket_cb); +} + +int pcep_thread_socket_read(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback socket_cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return schedule_thread_socket(ctrl_state, 0, SOCK_PCEPLIB, true, + payload, fd, (struct event **)thread, + socket_cb); +} + +int pcep_thread_send_ctrl_event(void *fpt, void *payload, + pcep_ctrl_thread_callback cb) +{ + struct ctrl_state *ctrl_state = ((struct frr_pthread *)fpt)->data; + + return send_to_thread_with_cb(ctrl_state, 0, EV_PCEPLIB_EVENT, 0, + payload, cb); +} + +/* ------------ Controller Thread Event Handler ------------ */ + +int send_to_thread(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload) +{ + return send_to_thread_with_cb(ctrl_state, pcc_id, type, sub_type, + payload, pcep_thread_event_handler); +} + +int send_to_thread_with_cb(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_event_type type, uint32_t sub_type, + void *payload, pcep_ctrl_thread_callback event_cb) +{ + struct pcep_ctrl_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->ctrl_state = ctrl_state; + data->type = type; + data->sub_type = sub_type; + data->pcc_id = pcc_id; + data->payload = payload; + + event_add_event(ctrl_state->self, event_cb, (void *)data, 0, NULL); + + return 0; +} + +void pcep_thread_event_handler(struct event *thread) +{ + /* data unpacking */ + struct pcep_ctrl_event_data *data = EVENT_ARG(thread); + assert(data != NULL); + struct ctrl_state *ctrl_state = data->ctrl_state; + assert(ctrl_state != NULL); + enum pcep_ctrl_event_type type = data->type; + uint32_t sub_type = data->sub_type; + int pcc_id = data->pcc_id; + void *payload = data->payload; + XFREE(MTYPE_PCEP, data); + + /* Possible sub-type values */ + enum pcep_pathd_event_type path_event_type = PCEP_PATH_UNDEFINED; + + /* Possible payload values, maybe an union would be better... */ + struct path *path = NULL; + struct pcc_opts *pcc_opts = NULL; + struct pce_opts *pce_opts = NULL; + struct pcc_state *pcc_state = NULL; + struct pcep_refine_path_event_data *refine_data = NULL; + + struct path *path_copy = NULL; + struct pcep_error *error = NULL; + + switch (type) { + case EV_UPDATE_PCC_OPTS: + assert(payload != NULL); + pcc_opts = (struct pcc_opts *)payload; + pcep_thread_event_update_pcc_options(ctrl_state, pcc_opts); + break; + case EV_UPDATE_PCE_OPTS: + assert(payload != NULL); + pce_opts = (struct pce_opts *)payload; + pcep_thread_event_update_pce_options(ctrl_state, pcc_id, + pce_opts); + break; + case EV_REMOVE_PCC: + pce_opts = (struct pce_opts *)payload; + if (pcep_thread_event_remove_pcc(ctrl_state, pce_opts) == 0) + pcep_pcc_multi_pce_remove_pcc(ctrl_state, + ctrl_state->pcc); + break; + case EV_PATHD_EVENT: + assert(payload != NULL); + path_event_type = (enum pcep_pathd_event_type)sub_type; + path = (struct path *)payload; + pcep_thread_event_pathd_event(ctrl_state, path_event_type, + path); + break; + case EV_SYNC_PATH: + assert(payload != NULL); + path = (struct path *)payload; + pcep_pcc_multi_pce_sync_path(ctrl_state, pcc_id, + ctrl_state->pcc); + pcep_thread_event_sync_path(ctrl_state, pcc_id, path); + break; + case EV_SYNC_DONE: + pcep_thread_event_sync_done(ctrl_state, pcc_id); + break; + case EV_RESET_PCC_SESSION: + pcc_state = pcep_pcc_get_pcc_by_name(ctrl_state->pcc, + (const char *)payload); + if (pcc_state) { + pcep_pcc_disable(ctrl_state, pcc_state); + pcep_pcc_enable(ctrl_state, pcc_state); + } else { + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Cannot reset state for PCE: %s", + (const char *)payload); + } + break; + case EV_SEND_REPORT: + assert(payload != NULL); + path = (struct path *)payload; + if (pcc_id == 0) { + for (int i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + path_copy = pcep_copy_path(path); + pcep_pcc_send_report( + ctrl_state, ctrl_state->pcc[i], + path_copy, (bool)sub_type); + } + } + } else { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_send_report(ctrl_state, pcc_state, path, + (bool)sub_type); + } + break; + case EV_PATH_REFINED: + assert(payload != NULL); + refine_data = (struct pcep_refine_path_event_data *)payload; + pcep_thread_path_refined_event(ctrl_state, refine_data); + break; + case EV_SEND_ERROR: + assert(payload != NULL); + error = (struct pcep_error *)payload; + pcc_state = pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_send_error(ctrl_state, pcc_state, error, + (bool)sub_type); + break; + case EV_PCEPLIB_EVENT: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected event received in controller thread: %u", + type); + break; + } +} + +int pcep_thread_event_update_pcc_options(struct ctrl_state *ctrl_state, + struct pcc_opts *opts) +{ + assert(opts != NULL); + if (ctrl_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, ctrl_state->pcc_opts); + } + ctrl_state->pcc_opts = opts; + return 0; +} + +int pcep_thread_event_update_pce_options(struct ctrl_state *ctrl_state, + int pcc_id, struct pce_opts *pce_opts) +{ + if (!pce_opts || !ctrl_state) { + return 0; + } + struct pcc_state *pcc_state; + struct pcc_opts *pcc_opts; + + int current_pcc_id = + pcep_pcc_get_pcc_id_by_ip_port(ctrl_state->pcc, pce_opts); + if (current_pcc_id) { + pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, current_pcc_id); + } else { + pcc_state = pcep_pcc_initialize(ctrl_state, + get_next_id(ctrl_state)); + if (set_pcc_state(ctrl_state, pcc_state)) { + XFREE(MTYPE_PCEP, pcc_state); + return 0; + } + } + + /* Copy the pcc options to delegate it to the update function */ + pcc_opts = XCALLOC(MTYPE_PCEP, sizeof(*pcc_opts)); + memcpy(pcc_opts, ctrl_state->pcc_opts, sizeof(*pcc_opts)); + + if (pcep_pcc_update(ctrl_state, pcc_state, pcc_opts, pce_opts)) { + flog_err(EC_PATH_PCEP_PCC_CONF_UPDATE, + "failed to update PCC configuration"); + } + + + return 0; +} + +int pcep_thread_event_remove_pcc_by_id(struct ctrl_state *ctrl_state, + int pcc_id) +{ + if (pcc_id) { + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + if (pcc_state) { + remove_pcc_state(ctrl_state, pcc_state); + pcep_pcc_finalize(ctrl_state, pcc_state); + } + } + return 0; +} + +int pcep_thread_event_remove_pcc_all(struct ctrl_state *ctrl_state) +{ + assert(ctrl_state != NULL); + + for (int i = 0; i < MAX_PCC; i++) { + pcep_thread_event_remove_pcc_by_id( + ctrl_state, + pcep_pcc_get_pcc_id_by_idx(ctrl_state->pcc, i)); + } + return 0; +} + +int pcep_thread_event_remove_pcc(struct ctrl_state *ctrl_state, + struct pce_opts *pce_opts) +{ + assert(ctrl_state != NULL); + + if (pce_opts) { + int pcc_id = pcep_pcc_get_pcc_id_by_ip_port(ctrl_state->pcc, + pce_opts); + if (pcc_id) { + pcep_thread_event_remove_pcc_by_id(ctrl_state, pcc_id); + } else { + return -1; + } + XFREE(MTYPE_PCEP, pce_opts); + } else { + pcep_thread_event_remove_pcc_all(ctrl_state); + } + + return 0; +} + +int pcep_thread_event_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_sync_path(ctrl_state, pcc_state, path); + pcep_free_path(path); + return 0; +} + +int pcep_thread_event_sync_done(struct ctrl_state *ctrl_state, int pcc_id) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, pcc_id); + pcep_pcc_sync_done(ctrl_state, pcc_state); + return 0; +} + +int pcep_thread_event_pathd_event(struct ctrl_state *ctrl_state, + enum pcep_pathd_event_type type, + struct path *path) +{ + int i; + + for (i = 0; i < MAX_PCC; i++) { + if (ctrl_state->pcc[i]) { + struct pcc_state *pcc_state = ctrl_state->pcc[i]; + pcep_pcc_pathd_event_handler(ctrl_state, pcc_state, + type, path); + } + } + + pcep_free_path(path); + + return 0; +} + + +/* ------------ Main Thread Event Handler ------------ */ + +int send_to_main(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_main_event_type type, void *payload) +{ + struct pcep_main_event_data *data; + + data = XCALLOC(MTYPE_PCEP, sizeof(*data)); + data->handler = ctrl_state->main_event_handler; + data->type = type; + data->pcc_id = pcc_id; + data->payload = payload; + + event_add_event(ctrl_state->main, pcep_main_event_handler, (void *)data, + 0, NULL); + return 0; +} + +void pcep_main_event_handler(struct event *thread) +{ + /* data unpacking */ + struct pcep_main_event_data *data = EVENT_ARG(thread); + assert(data != NULL); + pcep_main_event_handler_t handler = data->handler; + enum pcep_main_event_type type = data->type; + int pcc_id = data->pcc_id; + void *payload = data->payload; + XFREE(MTYPE_PCEP, data); + + handler(type, pcc_id, payload); +} + + +/* ------------ Helper functions ------------ */ + +void set_ctrl_state(struct frr_pthread *fpt, struct ctrl_state *ctrl_state) +{ + assert(fpt != NULL); + fpt->data = ctrl_state; +} + +struct ctrl_state *get_ctrl_state(struct frr_pthread *fpt) +{ + assert(fpt != NULL); + assert(fpt->data != NULL); + + struct ctrl_state *ctrl_state; + ctrl_state = (struct ctrl_state *)fpt->data; + assert(ctrl_state != NULL); + return ctrl_state; +} + +int get_next_id(struct ctrl_state *ctrl_state) +{ + return ++ctrl_state->pcc_last_id; +} + +int set_pcc_state(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + assert(ctrl_state != NULL); + assert(pcep_pcc_get_pcc_id(pcc_state) != 0); + + int current_pcc_idx = pcep_pcc_get_free_pcc_idx(ctrl_state->pcc); + if (current_pcc_idx >= 0) { + ctrl_state->pcc[current_pcc_idx] = pcc_state; + ctrl_state->pcc_count++; + PCEP_DEBUG("added pce pcc_id (%d) idx (%d)", + pcep_pcc_get_pcc_id(pcc_state), current_pcc_idx); + return 0; + } else { + PCEP_DEBUG("Max number of pce "); + return 1; + } +} + +void remove_pcc_state(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + assert(ctrl_state != NULL); + assert(pcep_pcc_get_pcc_id(pcc_state) != 0); + + int idx = 0; + idx = pcep_pcc_get_pcc_idx_by_id(ctrl_state->pcc, + pcep_pcc_get_pcc_id(pcc_state)); + if (idx != -1) { + ctrl_state->pcc[idx] = NULL; + ctrl_state->pcc_count--; + PCEP_DEBUG("removed pce pcc_id (%d)", + pcep_pcc_get_pcc_id(pcc_state)); + } +} + +uint32_t backoff_delay(uint32_t max, uint32_t base, uint32_t retry_count) +{ + uint32_t a = MIN(max, base * (1 << retry_count)); + uint64_t r = frr_weak_random(), m = RAND_MAX; + uint32_t b = (a / 2) + (r * (a / 2)) / m; + return b; +} + +const char *timer_type_name(enum pcep_ctrl_timer_type type) +{ + switch (type) { + case TM_UNDEFINED: + return "UNDEFINED"; + case TM_RECONNECT_PCC: + return "RECONNECT_PCC"; + case TM_PCEPLIB_TIMER: + return "PCEPLIB_TIMER"; + case TM_TIMEOUT: + return "TIMEOUT"; + case TM_CALCULATE_BEST_PCE: + return "BEST_PCE"; + case TM_SESSION_TIMEOUT_PCC: + return "TIMEOUT_PCC"; + case TM_MAX: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we did not expect to"); +} + +const char *timeout_type_name(enum pcep_ctrl_timeout_type type) +{ + switch (type) { + case TO_UNDEFINED: + return "UNDEFINED"; + case TO_COMPUTATION_REQUEST: + return "COMPUTATION_REQUEST"; + case TO_MAX: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we did not expect to"); +} diff --git a/pathd/path_pcep_controller.h b/pathd/path_pcep_controller.h new file mode 100644 index 0000000..236b267 --- /dev/null +++ b/pathd/path_pcep_controller.h @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_CONTROLLER_H_ +#define _PATH_PCEP_CONTROLLER_H_ + +#include "pathd/path_pcep.h" + +struct ctrl_state; +struct pcc_state; + +enum pcep_main_event_type { + PCEP_MAIN_EVENT_UNDEFINED = 0, + PCEP_MAIN_EVENT_START_SYNC, + PCEP_MAIN_EVENT_INITIATE_CANDIDATE, + PCEP_MAIN_EVENT_UPDATE_CANDIDATE, + PCEP_MAIN_EVENT_REMOVE_CANDIDATE_LSP, +}; + +typedef int (*pcep_main_event_handler_t)(enum pcep_main_event_type type, + int pcc_id, void *payload); +typedef void (*pcep_refine_callback_t)(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct path *path, void *payload); + +enum pcep_pathd_event_type { + PCEP_PATH_UNDEFINED = 0, + PCEP_PATH_CREATED, + PCEP_PATH_UPDATED, + PCEP_PATH_REMOVED +}; + +struct ctrl_state { + struct event_loop *main; + struct event_loop *self; + pcep_main_event_handler_t main_event_handler; + struct pcc_opts *pcc_opts; + int pcc_count; + int pcc_last_id; + struct pcc_state *pcc[MAX_PCC]; +}; + +/* Timer handling data structures */ + +enum pcep_ctrl_timeout_type { TO_UNDEFINED, TO_COMPUTATION_REQUEST, TO_MAX }; + +enum pcep_ctrl_timer_type { + TM_UNDEFINED, + TM_RECONNECT_PCC, + TM_PCEPLIB_TIMER, + TM_TIMEOUT, + TM_CALCULATE_BEST_PCE, + TM_SESSION_TIMEOUT_PCC, + TM_MAX +}; + +struct pcep_ctrl_timer_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_timer_type timer_type; + enum pcep_ctrl_timeout_type timeout_type; + int pcc_id; + void *payload; +}; + +/* Socket handling data structures */ + +enum pcep_ctrl_socket_type { SOCK_PCEPLIB = 1 }; + +struct pcep_ctrl_socket_data { + struct ctrl_state *ctrl_state; + enum pcep_ctrl_socket_type type; + bool is_read; + int fd; + int pcc_id; + void *payload; +}; + +typedef void (*pcep_ctrl_thread_callback)(struct event *); + +/* PCC connection information, populated in a thread-safe + * manner with pcep_ctrl_get_pcc_info() */ +struct pcep_pcc_info { + struct ctrl_state *ctrl_state; /* will be NULL when returned */ + char pce_name[64]; + int pcc_id; + struct ipaddr pcc_addr; + uint16_t pcc_port; + int status; + short msd; + uint32_t next_reqid; + uint32_t next_plspid; + bool is_best_multi_pce; + bool previous_best; + uint8_t precedence; +}; + +/* Functions called from the main thread */ +int pcep_ctrl_initialize(struct event_loop *main_thread, + struct frr_pthread **fpt, + pcep_main_event_handler_t event_handler); +int pcep_ctrl_finalize(struct frr_pthread **fpt); +int pcep_ctrl_update_pcc_options(struct frr_pthread *fpt, + struct pcc_opts *opts); +int pcep_ctrl_update_pce_options(struct frr_pthread *fpt, + struct pce_opts *opts); +int pcep_ctrl_remove_pcc(struct frr_pthread *fpt, struct pce_opts *pce_opts); +int pcep_ctrl_reset_pcc_session(struct frr_pthread *fpt, char *pce_name); +int pcep_ctrl_pathd_event(struct frr_pthread *fpt, + enum pcep_pathd_event_type type, struct path *path); +int pcep_ctrl_sync_path(struct frr_pthread *fpt, int pcc_id, struct path *path); +int pcep_ctrl_sync_done(struct frr_pthread *fpt, int pcc_id); +struct counters_group *pcep_ctrl_get_counters(struct frr_pthread *fpt, + int pcc_id); +pcep_session *pcep_ctrl_get_pcep_session(struct frr_pthread *fpt, int pcc_id); +struct pcep_pcc_info *pcep_ctrl_get_pcc_info(struct frr_pthread *fpt, + const char *pce_name); + +/* Asynchronously send a report. The caller is giving away the path structure, + * it shouldn't be allocated on the stack. If `pcc_id` is `0` the report is + * sent by all PCCs. The parameter is_stable is used to hint whether the status + * will soon change, this is used to ensure all report updates are sent even + * when missing status update events */ +int pcep_ctrl_send_report(struct frr_pthread *fpt, int pcc_id, + struct path *path, bool is_stable); + +int pcep_ctrl_send_error(struct frr_pthread *fpt, int pcc_id, + struct pcep_error *error); + +/* Functions called from the controller thread */ +void pcep_thread_start_sync(struct ctrl_state *ctrl_state, int pcc_id); +void pcep_thread_update_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path); +void pcep_thread_initiate_path(struct ctrl_state *ctrl_state, int pcc_id, + struct path *path); +void pcep_thread_cancel_timer(struct event **thread); +void pcep_thread_schedule_reconnect(struct ctrl_state *ctrl_state, int pcc_id, + int retry_count, struct event **thread); +void pcep_thread_schedule_timeout(struct ctrl_state *ctrl_state, int pcc_id, + enum pcep_ctrl_timeout_type type, + uint32_t delay, void *param, + struct event **thread); +void pcep_thread_schedule_session_timeout(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct event **thread); +void pcep_thread_remove_candidate_path_segments(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); + +void pcep_thread_schedule_sync_best_pce(struct ctrl_state *ctrl_state, + int pcc_id, int delay, + struct event **thread); +void pcep_thread_schedule_pceplib_timer(struct ctrl_state *ctrl_state, + int delay, void *payload, + struct event **thread, + pcep_ctrl_thread_callback cb); +int pcep_thread_socket_read(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback cb); +int pcep_thread_socket_write(void *fpt, void **thread, int fd, void *payload, + pcep_ctrl_thread_callback cb); + +int pcep_thread_send_ctrl_event(void *fpt, void *payload, + pcep_ctrl_thread_callback cb); +void pcep_thread_pcep_event(struct event *thread); +int pcep_thread_pcc_count(struct ctrl_state *ctrl_state); +/* Called by the PCC to refine a path in the main thread */ +int pcep_thread_refine_path(struct ctrl_state *ctrl_state, int pcc_id, + pcep_refine_callback_t cb, struct path *path, + void *payload); + +#endif // _PATH_PCEP_CONTROLLER_H_ diff --git a/pathd/path_pcep_debug.c b/pathd/path_pcep_debug.c new file mode 100644 index 0000000..7bff9c7 --- /dev/null +++ b/pathd/path_pcep_debug.c @@ -0,0 +1,1804 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include +#include +#include +#include + +#include "printfrr.h" +#include "ipaddr.h" + +#include "pathd/path_pcep_debug.h" + +static void _format_pcc_opts(int ps, struct pcc_opts *ops); +static void _format_pce_opts(int ps, struct pce_opts *ops); +static void _format_pcc_caps(int ps, struct pcep_caps *caps); +static void _format_pcc_state(int ps, struct pcc_state *state); +static void _format_ctrl_state(int ps, struct ctrl_state *state); +static void _format_path(int ps, struct path *path); +static void _format_path_hop(int ps, struct path_hop *hop); +static void _format_path_metric(int ps, struct path_metric *metric); +static void _format_pcep_event(int ps, pcep_event *event); +static void _format_pcep_message(int ps, struct pcep_message *msg); +static void _format_pcep_objects(int ps, double_linked_list *objs); +static void _format_pcep_object(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_details(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_error(int ps, struct pcep_object_error *obj); +static void _format_pcep_object_open(int ps, struct pcep_object_open *obj); +static void _format_pcep_object_rp(int ps, struct pcep_object_rp *obj); +static void _format_pcep_object_srp(int ps, struct pcep_object_srp *obj); +static void _format_pcep_object_lsp(int psps, struct pcep_object_lsp *obj); +static void _format_pcep_object_lspa(int psps, struct pcep_object_lspa *obj); +static void +_format_pcep_object_ipv4_endpoint(int ps, + struct pcep_object_endpoints_ipv4 *obj); +static void _format_pcep_object_metric(int ps, struct pcep_object_metric *obj); +static void _format_pcep_object_bandwidth(int ps, + struct pcep_object_bandwidth *obj); +static void _format_pcep_object_nopath(int ps, struct pcep_object_nopath *obj); +static void +_format_pcep_object_objfun(int ps, struct pcep_object_objective_function *obj); +static void _format_pcep_object_ro(int ps, struct pcep_object_ro *obj); +static void _format_pcep_object_ro_details(int ps, + struct pcep_object_ro_subobj *ro); +static void _format_pcep_object_ro_ipv4(int ps, + struct pcep_ro_subobj_ipv4 *obj); +static void _format_pcep_object_ro_sr(int ps, struct pcep_ro_subobj_sr *obj); +static void _format_pcep_object_tlvs(int ps, struct pcep_object_header *obj); +static void _format_pcep_object_tlv(int ps, + struct pcep_object_tlv_header *tlv_header); +static void +_format_pcep_object_tlv_details(int ps, + struct pcep_object_tlv_header *tlv_header); +static void _format_pcep_object_tlv_symbolic_path_name( + int ps, struct pcep_object_tlv_symbolic_path_name *tlv); +static void _format_pcep_object_tlv_stateful_pce_capability( + int ps, struct pcep_object_tlv_stateful_pce_capability *tlv); +static void _format_pcep_object_tlv_sr_pce_capability( + int ps, struct pcep_object_tlv_sr_pce_capability *tlv); +static void _format_pcep_object_tlv_path_setup_type( + int ps, struct pcep_object_tlv_path_setup_type *tlv); + +const char *pcc_status_name(enum pcc_status status) +{ + switch (status) { + case PCEP_PCC_INITIALIZED: + return "INITIALIZED"; + case PCEP_PCC_DISCONNECTED: + return "DISCONNECTED"; + case PCEP_PCC_CONNECTING: + return "CONNECTING"; + case PCEP_PCC_SYNCHRONIZING: + return "SYNCHRONIZING"; + case PCEP_PCC_OPERATING: + return "OPERATING"; + } + + assert(!"Reached end of function where we do not expect to"); +} + +const char *pcep_event_type_name(pcep_event_type event_type) +{ + switch (event_type) { + case MESSAGE_RECEIVED: + return "MESSAGE_RECEIVED"; + case PCE_CLOSED_SOCKET: + return "PCE_CLOSED_SOCKET"; + case PCE_SENT_PCEP_CLOSE: + return "PCE_SENT_PCEP_CLOSE"; + case PCE_DEAD_TIMER_EXPIRED: + return "PCE_DEAD_TIMER_EXPIRED"; + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + return "PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED"; + case PCC_CONNECTED_TO_PCE: + return "PCC_CONNECTED_TO_PCE"; + case PCC_PCEP_SESSION_CLOSED: + return "PCC_PCEP_SESSION_CLOSED"; + case PCC_RCVD_INVALID_OPEN: + return "PCC_RCVD_INVALID_OPEN"; + case PCC_RCVD_MAX_INVALID_MSGS: + return "PCC_RCVD_MAX_INVALID_MSGS"; + case PCC_RCVD_MAX_UNKOWN_MSGS: + return "PCC_RCVD_MAX_UNKOWN_MSGS"; + case PCC_CONNECTION_FAILURE: + return "PCC_CONNECTION_FAILURE"; + case PCC_SENT_INVALID_OPEN: + return "PCC_SENT_INVALID_OPEN"; + } + + assert(!"Reached end of function where we do not expect to"); +} + +const char *pcep_error_type_name(enum pcep_error_type error_type) +{ + switch (error_type) { + + case PCEP_ERRT_SESSION_FAILURE: + return "SESSION_FAILURE"; + case PCEP_ERRT_CAPABILITY_NOT_SUPPORTED: + return "CAPABILITY_NOT_SUPPORTED"; + case PCEP_ERRT_UNKNOW_OBJECT: + return "UNKNOW_OBJECT"; + case PCEP_ERRT_NOT_SUPPORTED_OBJECT: + return "NOT_SUPPORTED_OBJECT"; + case PCEP_ERRT_POLICY_VIOLATION: + return "POLICY_VIOLATION"; + case PCEP_ERRT_MANDATORY_OBJECT_MISSING: + return "MANDATORY_OBJECT_MISSING"; + case PCEP_ERRT_SYNC_PC_REQ_MISSING: + return "SYNC_PC_REQ_MISSING"; + case PCEP_ERRT_UNKNOWN_REQ_REF: + return "UNKNOWN_REQ_REF"; + case PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION: + return "ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION"; + case PCEP_ERRT_RECEPTION_OF_INV_OBJECT: + return "RECEPTION_OF_INV_OBJECT"; + case PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ: + return "UNRECOGNIZED_EXRS_SUBOBJ"; + case PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR: + return "DIFFSERV_AWARE_TE_ERROR"; + case PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR: + return "BRPC_PROC_COMPLETION_ERROR"; + case PCEP_ERRT_UNASSIGNED14: + return "UNASSIGNED14"; + case PCEP_ERRT_GLOBAL_CONCURRENT_ERROR: + return "GLOBAL_CONCURRENT_ERROR"; + case PCEP_ERRT_P2PMP_CAP_ERROR: + return "P2PMP_CAP_ERROR"; + case PCEP_ERRT_P2P_ENDPOINTS_ERROR: + return "P2P_ENDPOINTS_ERROR"; + case PCEP_ERRT_P2P_FRAGMENTATION_ERROR: + return "P2P_FRAGMENTATION_ERROR"; + case PCEP_ERRT_INVALID_OPERATION: + return "INVALID_OPERATION"; + case PCEP_ERRT_LSP_STATE_SYNC_ERROR: + return "LSP_STATE_SYNC_ERROR"; + case PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE: + return "INVALID_TE_PATH_SETUP_TYPE"; + case PCEP_ERRT_UNASSIGNED22: + return "UNASSIGNED22"; + case PCEP_ERRT_BAD_PARAMETER_VALUE: + return "BAD_PARAMETER_VALUE"; + case PCEP_ERRT_LSP_INSTANTIATE_ERROR: + return "LSP_INSTANTIATE_ERROR"; + case PCEP_ERRT_START_TLS_FAILURE: + return "START_TLS_FAILURE"; + case PCEP_ERRT_ASSOCIATION_ERROR: + return "ASSOCIATION_ERROR"; + case PCEP_ERRT_WSON_RWA_ERROR: + return "WSON_RWA_ERROR"; + case PCEP_ERRT_H_PCE_ERROR: + return "H_PCE_ERROR"; + case PCEP_ERRT_PATH_COMP_FAILURE: + return "PATH_COMP_FAILURE"; + case PCEP_ERRT_UNASSIGNED30: + return "UNASSIGNED30"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_error_value_name(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + switch (TUP(error_type, error_value)) { + + case TUP(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_SYNC_PC_REQ_MISSING, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_UNKNOWN_REQ_REF, PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_UNASSIGNED): + case TUP(PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ, PCEP_ERRV_UNASSIGNED): + return "UNASSIGNED"; + + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_RECVD_INVALID_OPEN_MSG): + return "RECVD_INVALID_OPEN_MSG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_OPENWAIT_TIMED_OUT): + return "OPENWAIT_TIMED_OUT"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NO_NEG): + return "UNACCEPTABLE_OPEN_MSG_NO_NEG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG): + return "UNACCEPTABLE_OPEN_MSG_NEG"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE): + return "RECVD_SECOND_OPEN_MSG_UNACCEPTABLE"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_RECVD_PCERR): + return "RECVD_PCERR"; + case TUP(PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT): + return "KEEPALIVEWAIT_TIMED_OUT"; + case TUP(PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_PCEP_VERSION_NOT_SUPPORTED): + return "PCEP_VERSION_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_UNKNOW_OBJECT, PCEP_ERRV_UNREC_OBJECT_CLASS): + return "UNREC_OBJECT_CLASS"; + case TUP(PCEP_ERRT_UNKNOW_OBJECT, PCEP_ERRV_UNREC_OBJECT_TYPE): + return "UNREC_OBJECT_TYPE"; + + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_CLASS): + return "NOT_SUPPORTED_OBJECT_CLASS"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_TYPE): + return "NOT_SUPPORTED_OBJECT_TYPE"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, PCEP_ERRV_UNSUPPORTED_PARAM): + return "UNSUPPORTED_PARAM"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_NW_PERF_CONSTRAINT): + return "UNSUPPORTED_NW_PERF_CONSTRAINT"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_NOT_SUPPORTED_BW_OBJECT_3_4): + return "NOT_SUPPORTED_BW_OBJECT_3_4"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TYPE): + return "UNSUPPORTED_ENDPOINT_TYPE"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TLV): + return "UNSUPPORTED_ENDPOINT_TLV"; + case TUP(PCEP_ERRT_NOT_SUPPORTED_OBJECT, + PCEP_ERRV_UNSUPPORTED_RP_FLAG_GRANULARITY): + return "UNSUPPORTED_RP_FLAG_GRANULARITY"; + + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_C_BIT_SET_IN_METRIC_OBJECT): + return "C_BIT_SET_IN_METRIC_OBJECT"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_O_BIT_CLEARD_IN_RP_OBJECT): + return "O_BIT_CLEARD_IN_RP_OBJECT"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_OBJECTIVE_FUNC_NOT_ALLOWED): + return "OBJECTIVE_FUNC_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, PCEP_ERRV_RP_OF_BIT_SET): + return "RP_OF_BIT_SET"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_GLOBAL_CONCURRENCY_NOT_ALLOWED): + return "GLOBAL_CONCURRENCY_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, PCEP_ERRV_MONITORING_MSG_REJECTED): + return "MONITORING_MSG_REJECTED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_P2MP_PATH_COMP_NOT_ALLOWED): + return "P2MP_PATH_COMP_NOT_ALLOWED"; + case TUP(PCEP_ERRT_POLICY_VIOLATION, + PCEP_ERRV_UNALLOWED_NW_PERF_CONSTRAINT): + return "UNALLOWED_NW_PERF_CONSTRAINT"; + + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_RP_OBJECT_MISSING): + return "RP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_RRO_OBJECT_MISSING_FOR_REOP): + return "RRO_OBJECT_MISSING_FOR_REOP"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_EP_OBJECT_MISSING): + return "EP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_MONITOR_OBJECT_MISSING): + return "MONITOR_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING): + return "LSP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING): + return "ERO_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING): + return "SRP_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_ID_TLV_MISSING): + return "LSP_ID_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_DB_TLV_MISSING): + return "LSP_DB_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_S2LS_OBJECT_MISSING): + return "S2LS_OBJECT_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_P2MP_LSP_ID_TLV_MISSING): + return "P2MP_LSP_ID_TLV_MISSING"; + case TUP(PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING): + return "DISJOINTED_CONF_TLV_MISSING"; + + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_P_FLAG_NOT_CORRECT_IN_OBJECT): + return "P_FLAG_NOT_CORRECT_IN_OBJECT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_BAD_LABEL_VALUE): + return "BAD_LABEL_VALUE"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS): + return "UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_BAD_LABEL_FORMAT): + return "BAD_LABEL_FORMAT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_ERO_SR_ERO_MIX): + return "ERO_SR_ERO_MIX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SR_ERO_SID_NAI_ABSENT): + return "SR_ERO_SID_NAI_ABSENT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SR_RRO_SID_NAI_ABSENT): + return "SR_RRO_SID_NAI_ABSENT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING): + return "SYMBOLIC_PATH_NAME_TLV_MISSING"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MSD_EXCEEDS_PCEP_SESSION_MAX): + return "MSD_EXCEEDS_PCEP_SESSION_MAX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_RRO_SR_RRO_MIX): + return "RRO_SR_RRO_MIX"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_MALFORMED_OBJECT): + return "MALFORMED_OBJECT"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISSING_PCE_SR_CAP_TLV): + return "MISSING_PCE_SR_CAP_TLV"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_UNSUPPORTED_NAI): + return "UNSUPPORTED_NAI"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_UNKNOWN_SID): + return "UNKNOWN_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_CANNOT_RESOLVE_NAI_TO_SID): + return "CANNOT_RESOLVE_NAI_TO_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_COULD_NOT_FIND_SRGB): + return "COULD_NOT_FIND_SRGB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_SID_EXCEEDS_SRGB): + return "SID_EXCEEDS_SRGB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_COULD_NOT_FIND_SRLB): + return "COULD_NOT_FIND_SRLB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_SID_EXCEEDS_SRLB): + return "SID_EXCEEDS_SRLB"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, PCEP_ERRV_INCONSISTENT_SID): + return "INCONSISTENT_SID"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MSD_MUST_BE_NONZERO): + return "MSD_MUST_BE_NONZERO"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISMATCH_O_S2LS_LSP): + return "MISMATCH_O_S2LS_LSP"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_INCOMPATIBLE_H_PCE_OF): + return "INCOMPATIBLE_H_PCE_OF"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_BAD_BANDWIDTH_TYPE_3_4): + return "BAD_BANDWIDTH_TYPE_3_4"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_LSP_PROT_FLAGS): + return "UNSUPPORTED_LSP_PROT_FLAGS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_2ND_LSP_PROT_FLAGS): + return "UNSUPPORTED_2ND_LSP_PROT_FLAGS"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_UNSUPPORTED_LINK_PROT_TYPE): + return "UNSUPPORTED_LINK_PROT_TYPE"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_LABEL_SET_TLV_NO_RP_R): + return "LABEL_SET_TLV_NO_RP_R"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_WRONG_LABEL_SET_TLV_O_L_SET): + return "WRONG_LABEL_SET_TLV_O_L_SET"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_WRONG_LABEL_SET_O_SET): + return "WRONG_LABEL_SET_O_SET"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_MISSING_GMPLS_CAP_TLV): + return "MISSING_GMPLS_CAP_TLV"; + case TUP(PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_INCOMPATIBLE_OF_CODE): + return "INCOMPATIBLE_OF_CODE"; + + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_UNSUPPORTED_CLASS_TYPE): + return "UNSUPPORTED_CLASS_TYPE"; + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_INVALID_CLASS_TYPE): + return "INVALID_CLASS_TYPE"; + case TUP(PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR, + PCEP_ERRV_CLASS_SETUP_TYPE_NOT_TE_CLASS): + return "CLASS_SETUP_TYPE_NOT_TE_CLASS"; + + case TUP(PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR, + PCEP_ERRV_BRPC_PROC_NOT_SUPPORTED): + return "BRPC_PROC_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_GLOBAL_CONCURRENT_ERROR, + PCEP_ERRV_INSUFFICIENT_MEMORY): + return "INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_GLOBAL_CONCURRENT_ERROR, + PCEP_ERRV_GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED): + return "GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_P2PMP_CAP_ERROR, PCEP_ERRV_PCE_INSUFFICIENT_MEMORY): + return "PCE_INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_P2PMP_CAP_ERROR, + PCEP_ERRV_PCE_NOT_CAPABLE_P2MP_COMP): + return "PCE_NOT_CAPABLE_P2MP_COMP"; + + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE2): + return "NO_EP_WITH_LEAF_TYPE2"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE3): + return "NO_EP_WITH_LEAF_TYPE3"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE4): + return "NO_EP_WITH_LEAF_TYPE4"; + case TUP(PCEP_ERRT_P2P_ENDPOINTS_ERROR, PCEP_ERRV_INCONSITENT_EP): + return "INCONSITENT_EP"; + + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_REQUEST_FAILURE): + return "FRAG_REQUEST_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_REPORT_FAILURE): + return "FRAG_REPORT_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_UPDATE_FAILURE): + return "FRAG_UPDATE_FAILURE"; + case TUP(PCEP_ERRT_P2P_FRAGMENTATION_ERROR, + PCEP_ERRV_FRAG_INSTANTIATION_FAILURE): + return "FRAG_INSTANTIATION_FAILURE"; + + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP): + return "LSP_UPDATE_FOR_NON_DELEGATED_LS"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_NON_ADVERTISED_PCE): + return "LSP_UPDATE_NON_ADVERTISED_PC"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID): + return "LSP_UPDATE_UNKNOWN_PLSP_I"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_REPORT_NON_ADVERTISED_PCE): + return "LSP_REPORT_NON_ADVERTISED_PC"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_LSP_LIMIT_REACHED): + return "PCE_INIT_LSP_LIMIT_REACHE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_LSP_DELEGATION_CANT_REVOKE): + return "PCE_INIT_LSP_DELEGATION_CANT_REVOK"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID): + return "LSP_INIT_NON_ZERO_PLSP_I"; + case TUP(PCEP_ERRT_INVALID_OPERATION, PCEP_ERRV_LSP_NOT_PCE_INITIATED): + return "LSP_NOT_PCE_INITIATE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_PCE_INIT_OP_FREQ_LIMIT_REACHED): + return "PCE_INIT_OP_FREQ_LIMIT_REACHE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_REPORT_P2MP_NOT_ADVERTISED): + return "LSP_REPORT_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_UPDATE_P2MP_NOT_ADVERTISED): + return "LSP_UPDATE_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INSTANTIATION_P2MP_NOT_ADVERTISED): + return "LSP_INSTANTIATION_P2MP_NOT_ADVERTISE"; + case TUP(PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_AUTO_BW_CAP_NOT_ADVERTISED): + return "AUTO_BW_CAP_NOT_ADVERTISE"; + + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_PCE_CANT_PROCESS_LSP_REPORT): + return "PCE_CANT_PROCESS_LSP_REPORT"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_LSP_DB_VERSION_MISMATCH): + return "LSP_DB_VERSION_MISMATCH"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER): + return "TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP): + return "TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_PCC_CANT_COMPLETE_STATE_SYNC): + return "PCC_CANT_COMPLETE_STATE_SYNC"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_INVALID_LSP_DB_VERSION_NUMBER): + return "INVALID_LSP_DB_VERSION_NUMBER"; + case TUP(PCEP_ERRT_LSP_STATE_SYNC_ERROR, + PCEP_ERRV_INVALID_SPEAKER_ENTITY_ID): + return "INVALID_SPEAKER_ENTITY_ID"; + + case TUP(PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE, + PCEP_ERRV_UNSUPPORTED_PATH_SETUP_TYPE): + return "UNSUPPORTED_PATH_SETUP_TYPE"; + case TUP(PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE, + PCEP_ERRV_MISMATCHED_PATH_SETUP_TYPE): + return "MISMATCHED_PATH_SETUP_TYPE"; + + case TUP(PCEP_ERRT_BAD_PARAMETER_VALUE, + PCEP_ERRV_SYMBOLIC_PATH_NAME_IN_USE): + return "SYMBOLIC_PATH_NAME_IN_USE"; + case TUP(PCEP_ERRT_BAD_PARAMETER_VALUE, + PCEP_ERRV_LSP_SPEAKER_ID_NOT_PCE_INITIATED): + return "LSP_SPEAKER_ID_NOT_PCE_INITIATED"; + + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR): + return "UNACCEPTABLE_INSTANTIATE_ERROR"; + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, PCEP_ERRV_INTERNAL_ERROR): + return "INTERNAL_ERROR"; + case TUP(PCEP_ERRT_LSP_INSTANTIATE_ERROR, PCEP_ERRV_SIGNALLING_ERROR): + return "SIGNALLING_ERROR"; + + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_START_TLS_AFTER_PCEP_EXCHANGE): + return "START_TLS_AFTER_PCEP_EXCHANGE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_MSG_NOT_START_TLS_OPEN_ERROR): + return "MSG_NOT_START_TLS_OPEN_ERROR"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_CONNECTION_WO_TLS_NOT_POSSIBLE): + return "CONNECTION_WO_TLS_NOT_POSSIBLE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_CONNECTION_WO_TLS_IS_POSSIBLE): + return "CONNECTION_WO_TLS_IS_POSSIBLE"; + case TUP(PCEP_ERRT_START_TLS_FAILURE, + PCEP_ERRV_NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER): + return "NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER"; + + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_ASSOC_TYPE_NOT_SUPPORTED): + return "ASSOC_TYPE_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_TOO_MANY_LSPS_IN_ASSOC_GRP): + return "TOO_MANY_LSPS_IN_ASSOC_GRP"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_TOO_MANY_ASSOC_GROUPS): + return "TOO_MANY_ASSOC_GROUPS"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOCIATION_UNKNOWN): + return "ASSOCIATION_UNKNOWN"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_OP_CONF_ASSOC_INFO_MISMATCH): + return "OP_CONF_ASSOC_INFO_MISMATCH"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOC_INFO_MISMATCH): + return "ASSOC_INFO_MISMATCH"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_CANNOT_JOIN_ASSOC_GROUP): + return "CANNOT_JOIN_ASSOC_GROUP"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, PCEP_ERRV_ASSOC_ID_NOT_IN_RANGE): + return "ASSOC_ID_NOT_IN_RANGE"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC): + return "TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC): + return "ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC"; + case TUP(PCEP_ERRT_ASSOCIATION_ERROR, + PCEP_ERRV_PROTECTION_TYPE_NOT_SUPPORTED): + return "PROTECTION_TYPE_NOT_SUPPORTED"; + + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_RWA_INSUFFICIENT_MEMORY): + return "RWA_INSUFFICIENT_MEMORY"; + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_RWA_COMP_NOT_SUPPORTED): + return "RWA_COMP_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_WSON_RWA_ERROR, PCEP_ERRV_SYNTAX_ENC_ERROR): + return "SYNTAX_ENC_ERROR"; + + case TUP(PCEP_ERRT_H_PCE_ERROR, PCEP_ERRV_H_PCE_CAP_NOT_ADVERTISED): + return "H_PCE_CAP_NOT_ADVERTISED"; + case TUP(PCEP_ERRT_H_PCE_ERROR, + PCEP_ERRV_PARENT_PCE_CAP_CANT_BE_PROVIDED): + return "PARENT_PCE_CAP_CANT_BE_PROVIDED"; + + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_UNACCEPTABLE_REQUEST_MSG): + return "UNACCEPTABLE_REQUEST_MSG"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_GENERALIZED_BW_VAL_NOT_SUPPORTED): + return "GENERALIZED_BW_VAL_NOT_SUPPORTED"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET): + return "LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET"; + case TUP(PCEP_ERRT_PATH_COMP_FAILURE, + PCEP_ERRV_LABEL_CONSTRAINT_COULD_NOT_BE_MET): + return "LABEL_CONSTRAINT_COULD_NOT_BE_MET"; + + default: + return "UNKNOWN"; + } +} + +const char *pcep_message_type_name(enum pcep_message_types pcep_message_type) +{ + switch (pcep_message_type) { + + case PCEP_TYPE_OPEN: + return "OPEN"; + case PCEP_TYPE_KEEPALIVE: + return "KEEPALIVE"; + case PCEP_TYPE_PCREQ: + return "PCREQ"; + case PCEP_TYPE_PCREP: + return "PCREP"; + case PCEP_TYPE_PCNOTF: + return "PCNOTF"; + case PCEP_TYPE_ERROR: + return "ERROR"; + case PCEP_TYPE_CLOSE: + return "CLOSE"; + case PCEP_TYPE_REPORT: + return "REPORT"; + case PCEP_TYPE_UPDATE: + return "UPDATE"; + case PCEP_TYPE_INITIATE: + return "INITIATE"; + case PCEP_TYPE_START_TLS: + return "START_TLS"; + case PCEP_TYPE_MAX: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +const char *pcep_object_class_name(enum pcep_object_classes obj_class) +{ + switch (obj_class) { + case PCEP_OBJ_CLASS_OPEN: + return "OPEN"; + case PCEP_OBJ_CLASS_RP: + return "RP"; + case PCEP_OBJ_CLASS_NOPATH: + return "NOPATH"; + case PCEP_OBJ_CLASS_ENDPOINTS: + return "ENDPOINTS"; + case PCEP_OBJ_CLASS_BANDWIDTH: + return "BANDWIDTH"; + case PCEP_OBJ_CLASS_METRIC: + return "METRIC"; + case PCEP_OBJ_CLASS_ERO: + return "ERO"; + case PCEP_OBJ_CLASS_RRO: + return "RRO"; + case PCEP_OBJ_CLASS_LSPA: + return "LSPA"; + case PCEP_OBJ_CLASS_IRO: + return "IRO"; + case PCEP_OBJ_CLASS_SVEC: + return "SVEC"; + case PCEP_OBJ_CLASS_NOTF: + return "NOTF"; + case PCEP_OBJ_CLASS_ERROR: + return "ERROR"; + case PCEP_OBJ_CLASS_CLOSE: + return "CLOSE"; + case PCEP_OBJ_CLASS_OF: + return "OF"; + case PCEP_OBJ_CLASS_LSP: + return "LSP"; + case PCEP_OBJ_CLASS_SRP: + return "SRP"; + case PCEP_OBJ_CLASS_VENDOR_INFO: + return "VENDOR_INFO"; + case PCEP_OBJ_CLASS_INTER_LAYER: + return "INTER_LAYER"; + case PCEP_OBJ_CLASS_SWITCH_LAYER: + return "SWITCH_LAYER"; + case PCEP_OBJ_CLASS_REQ_ADAP_CAP: + return "REQ_ADAP_CAP"; + case PCEP_OBJ_CLASS_SERVER_IND: + return "SERVER_IND"; + case PCEP_OBJ_CLASS_ASSOCIATION: + return "ASSOCIATION"; + case PCEP_OBJ_CLASS_MAX: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +const char *pcep_object_type_name(enum pcep_object_classes obj_class, + enum pcep_object_types obj_type) +{ + switch (TUP(obj_class, obj_type)) { + case TUP(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + return "OPEN"; + case TUP(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + return "RP"; + case TUP(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + return "NOPATH"; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4): + return "ENDPOINT_IPV4"; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV6): + return "ENDPOINT_IPV6"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ): + return "BANDWIDTH_REQ"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_TELSP): + return "BANDWIDTH_TELSP"; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + return "BANDWIDTH_CISCO"; + case TUP(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + return "METRIC"; + case TUP(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + return "ERO"; + case TUP(PCEP_OBJ_CLASS_RRO, PCEP_OBJ_TYPE_RRO): + return "RRO"; + case TUP(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + return "LSPA"; + case TUP(PCEP_OBJ_CLASS_IRO, PCEP_OBJ_TYPE_IRO): + return "IRO"; + case TUP(PCEP_OBJ_CLASS_SVEC, PCEP_OBJ_TYPE_SVEC): + return "SVEC"; + case TUP(PCEP_OBJ_CLASS_NOTF, PCEP_OBJ_TYPE_NOTF): + return "NOTF"; + case TUP(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR): + return "ERROR"; + case TUP(PCEP_OBJ_CLASS_CLOSE, PCEP_OBJ_TYPE_CLOSE): + return "CLOSE"; + case TUP(PCEP_OBJ_CLASS_INTER_LAYER, PCEP_OBJ_TYPE_INTER_LAYER): + return "INTER_LAYER"; + case TUP(PCEP_OBJ_CLASS_SWITCH_LAYER, PCEP_OBJ_TYPE_SWITCH_LAYER): + return "SWITCH_LAYER"; + case TUP(PCEP_OBJ_CLASS_REQ_ADAP_CAP, PCEP_OBJ_TYPE_REQ_ADAP_CAP): + return "REQ_ADAP_CAP"; + case TUP(PCEP_OBJ_CLASS_SERVER_IND, PCEP_OBJ_TYPE_SERVER_IND): + return "SERVER_IND"; + case TUP(PCEP_OBJ_CLASS_ASSOCIATION, PCEP_OBJ_TYPE_ASSOCIATION_IPV4): + return "ASSOCIATION_IPV4"; + case TUP(PCEP_OBJ_CLASS_ASSOCIATION, PCEP_OBJ_TYPE_ASSOCIATION_IPV6): + return "ASSOCIATION_IPV6"; + case TUP(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + return "OF"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_lsp_status_name(enum pcep_lsp_operational_status status) +{ + switch (status) { + case PCEP_LSP_OPERATIONAL_DOWN: + return "DOWN"; + case PCEP_LSP_OPERATIONAL_UP: + return "UP"; + case PCEP_LSP_OPERATIONAL_ACTIVE: + return "ACTIVE"; + case PCEP_LSP_OPERATIONAL_GOING_DOWN: + return "GOING_DOWN"; + case PCEP_LSP_OPERATIONAL_GOING_UP: + return "GOING_UP"; + } + + assert(!"Reached end of function where we do not expect to"); +} + + +const char *pcep_tlv_type_name(enum pcep_object_tlv_types tlv_type) +{ + switch (tlv_type) { + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + return "NO_PATH_VECTOR"; + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + return "OBJECTIVE_FUNCTION_LIST"; + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + return "VENDOR_INFO"; + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + return "STATEFUL_PCE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + return "SYMBOLIC_PATH_NAME"; + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + return "IPV4_LSP_IDENTIFIERS"; + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + return "IPV6_LSP_IDENTIFIERS"; + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + return "LSP_ERROR_CODE"; + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + return "RSVP_ERROR_SPEC"; + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + return "LSP_DB_VERSION"; + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + return "SPEAKER_ENTITY_ID"; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + return "SR_PCE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + return "PATH_SETUP_TYPE"; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + return "PATH_SETUP_TYPE_CAPABILITY"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + return "SRPOLICY_POL_ID"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + return "SRPOLICY_POL_NAME"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + return "SRPOLICY_CPATH_ID"; + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + return "SRPOLICY_CPATH_PREFERENCE"; + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + return "UNKNOWN"; + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + return "ARBITRARY"; + case PCEP_OBJ_TYPE_CISCO_BSID: + return "CISCO_BSID"; + } + + assert(!"Reached end of function where we do not expect to"); +} + +const char *pcep_ro_type_name(enum pcep_ro_subobj_types ro_type) +{ + switch (ro_type) { + + case RO_SUBOBJ_TYPE_IPV4: + return "IPV4"; + case RO_SUBOBJ_TYPE_IPV6: + return "IPV6"; + case RO_SUBOBJ_TYPE_LABEL: + return "LABEL"; + case RO_SUBOBJ_TYPE_UNNUM: + return "UNNUM"; + case RO_SUBOBJ_TYPE_ASN: + return "ASN"; + case RO_SUBOBJ_TYPE_SR: + return "SR"; + case RO_SUBOBJ_UNKNOWN: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we do not expect to"); +} + +const char *pcep_nai_type_name(enum pcep_sr_subobj_nai nai_type) +{ + switch (nai_type) { + case PCEP_SR_SUBOBJ_NAI_ABSENT: + return "ABSENT"; + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + return "IPV4_NODE"; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + return "IPV6_NODE"; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + return "IPV4_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + return "IPV6_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + return "UNNUMBERED_IPV4_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + return "LINK_LOCAL_IPV6_ADJACENCY"; + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + return "UNKNOWN"; + } + + assert(!"Reached end of function where we do not expect to"); +} + +const char *pcep_metric_type_name(enum pcep_metric_types type) +{ + switch (type) { + case PCEP_METRIC_IGP: + return "IGP"; + case PCEP_METRIC_TE: + return "TE"; + case PCEP_METRIC_HOP_COUNT: + return "HOP_COUNT"; + case PCEP_METRIC_AGGREGATE_BW: + return "AGGREGATE_BW"; + case PCEP_METRIC_MOST_LOADED_LINK: + return "MOST_LOADED_LINK"; + case PCEP_METRIC_CUMULATIVE_IGP: + return "CUMULATIVE_IGP"; + case PCEP_METRIC_CUMULATIVE_TE: + return "CUMULATIVE_TE"; + case PCEP_METRIC_P2MP_IGP: + return "P2MP_IGP"; + case PCEP_METRIC_P2MP_TE: + return "P2MP_TE"; + case PCEP_METRIC_P2MP_HOP_COUNT: + return "P2MP_HOP_COUNT"; + case PCEP_METRIC_SEGMENT_ID_DEPTH: + return "SEGMENT_ID_DEPTH"; + case PCEP_METRIC_PATH_DELAY: + return "PATH_DELAY"; + case PCEP_METRIC_PATH_DELAY_VARIATION: + return "PATH_DELAY_VARIATION"; + case PCEP_METRIC_PATH_LOSS: + return "PATH_LOSS"; + case PCEP_METRIC_P2MP_PATH_DELAY: + return "P2MP_PATH_DELAY"; + case PCEP_METRIC_P2MP_PATH_DELAY_VARIATION: + return "P2MP_PATH_DELAY_VARIATION"; + case PCEP_METRIC_P2MP_PATH_LOSS: + return "P2MP_PATH_LOSS"; + case PCEP_METRIC_NUM_PATH_ADAPTATIONS: + return "NUM_PATH_ADAPTATIONS"; + case PCEP_METRIC_NUM_PATH_LAYERS: + return "NUM_PATH_LAYERS"; + case PCEP_METRIC_DOMAIN_COUNT: + return "DOMAIN_COUNT"; + case PCEP_METRIC_BORDER_NODE_COUNT: + return "BORDER_NODE_COUNT"; + default: + return "UNKNOWN"; + } +} + +const char *pcep_nopath_tlv_err_code_name(enum pcep_nopath_tlv_err_codes type) +{ + switch (type) { + case PCEP_NOPATH_TLV_ERR_NO_TLV: + return "NO_TLV"; + case PCEP_NOPATH_TLV_ERR_PCE_UNAVAILABLE: + return "PCE_UNAVAILABLE"; + case PCEP_NOPATH_TLV_ERR_UNKNOWN_DST: + return "UNKNOWN_DST"; + case PCEP_NOPATH_TLV_ERR_UNKNOWN_SRC: + return "UNKNOWN_SRC"; + default: + return "UNKNOWN"; + } +} + +const char *format_objfun_set(uint32_t flags) +{ + int i, c; + PATHD_FORMAT_INIT(); + for (i = 1, c = 0; i <= MAX_OBJFUN_TYPE; i++) { + if (CHECK_FLAG(flags, i)) { + if (c > 0) + PATHD_FORMAT(", %s", objfun_type_name(i)); + else + PATHD_FORMAT("%s", objfun_type_name(i)); + c++; + } + } + return PATHD_FORMAT_FINI(); +} + + +const char *format_pcc_opts(struct pcc_opts *opts) +{ + PATHD_FORMAT_INIT(); + _format_pcc_opts(0, opts); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcc_state(struct pcc_state *state) +{ + PATHD_FORMAT_INIT(); + _format_pcc_state(0, state); + return PATHD_FORMAT_FINI(); +} + +const char *format_ctrl_state(struct ctrl_state *state) +{ + PATHD_FORMAT_INIT(); + _format_ctrl_state(0, state); + return PATHD_FORMAT_FINI(); +} + +const char *format_path(struct path *path) +{ + PATHD_FORMAT_INIT(); + _format_path(0, path); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcep_event(pcep_event *event) +{ + PATHD_FORMAT_INIT(); + _format_pcep_event(0, event); + return PATHD_FORMAT_FINI(); +} + +const char *format_pcep_message(struct pcep_message *msg) +{ + PATHD_FORMAT_INIT(); + _format_pcep_message(0, msg); + return PATHD_FORMAT_FINI(); +} + +void _format_pcc_opts(int ps, struct pcc_opts *opts) +{ + if (opts == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (IS_IPADDR_V4(&opts->addr)) { + PATHD_FORMAT("%*saddr_v4: %pI4\n", ps2, "", + &opts->addr.ipaddr_v4); + } else { + PATHD_FORMAT("%*saddr_v4: undefined", ps2, ""); + } + if (IS_IPADDR_V6(&opts->addr)) { + PATHD_FORMAT("%*saddr_v6: %pI6\n", ps2, "", + &opts->addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*saddr_v6: undefined", ps2, ""); + } + PATHD_FORMAT("%*sport: %i\n", ps2, "", opts->port); + PATHD_FORMAT("%*smsd: %i\n", ps2, "", opts->msd); + } +} + +void _format_pce_opts(int ps, struct pce_opts *opts) +{ + if (opts == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (IS_IPADDR_V6(&opts->addr)) { + PATHD_FORMAT("%*saddr: %pI6\n", ps2, "", + &opts->addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*saddr: %pI4\n", ps2, "", + &opts->addr.ipaddr_v4); + } + PATHD_FORMAT("%*sport: %i\n", ps2, "", opts->port); + } +} + +void _format_pcc_caps(int ps, struct pcep_caps *caps) +{ + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sis_stateful: %d\n", ps2, "", caps->is_stateful); +} + +void _format_pcc_state(int ps, struct pcc_state *state) +{ + if (state == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sstatus: %s\n", ps2, "", + pcc_status_name(state->status)); + PATHD_FORMAT("%*spcc_opts: ", ps2, ""); + _format_pcc_opts(ps2, state->pcc_opts); + PATHD_FORMAT("%*spce_opts: ", ps2, ""); + _format_pce_opts(ps2, state->pce_opts); + if (state->sess == NULL) { + PATHD_FORMAT("%*ssess: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*ssess: \n", ps2, "", + state->sess); + } + PATHD_FORMAT("%*scaps: ", ps2, ""); + _format_pcc_caps(ps2, &state->caps); + } +} + +void _format_ctrl_state(int ps, struct ctrl_state *state) +{ + if (state == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int i; + int ps2 = ps + DEBUG_IDENT_SIZE; + int ps3 = ps2 + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + if (state->main == NULL) { + PATHD_FORMAT("%*smain: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*smain: \n", ps2, "", + state->main); + } + if (state->self == NULL) { + PATHD_FORMAT("%*sself: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*sself: \n", ps2, "", + state->self); + } + PATHD_FORMAT("%*spcc_count: %d\n", ps2, "", state->pcc_count); + PATHD_FORMAT("%*spcc:\n", ps2, ""); + for (i = 0; i < MAX_PCC; i++) { + if (state->pcc[i]) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_pcc_state(ps3, state->pcc[i]); + } + } + } +} + +void _format_path(int ps, struct path *path) +{ + if (path == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + int ps3 = ps2 + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*snbkey: \n", ps2, ""); + PATHD_FORMAT("%*scolor: %u\n", ps3, "", path->nbkey.color); + switch (path->nbkey.endpoint.ipa_type) { + case IPADDR_V4: + PATHD_FORMAT("%*sendpoint: %pI4\n", ps3, "", + &path->nbkey.endpoint.ipaddr_v4); + break; + case IPADDR_V6: + PATHD_FORMAT("%*sendpoint: %pI6\n", ps3, "", + &path->nbkey.endpoint.ipaddr_v6); + break; + case IPADDR_NONE: + PATHD_FORMAT("%*sendpoint: NONE\n", ps3, ""); + break; + } + PATHD_FORMAT("%*spreference: %u\n", ps3, "", + path->nbkey.preference); + + if (path->sender.ipa_type == IPADDR_V4) { + PATHD_FORMAT("%*ssender: %pI4\n", ps2, "", + &path->sender.ipaddr_v4); + } else if (path->sender.ipa_type == IPADDR_V6) { + PATHD_FORMAT("%*ssender: %pI6\n", ps2, "", + &path->sender.ipaddr_v6); + } else { + PATHD_FORMAT("%*ssender: UNDEFINED\n", ps2, ""); + } + if (path->pcc_addr.ipa_type == IPADDR_V4) { + PATHD_FORMAT("%*spcc_addr: %pI4\n", ps2, "", + &path->pcc_addr.ipaddr_v4); + } else if (path->pcc_addr.ipa_type == IPADDR_V6) { + PATHD_FORMAT("%*spcc_addr: %pI6\n", ps2, "", + &path->pcc_addr.ipaddr_v6); + } else { + PATHD_FORMAT("%*spcc_addr: UNDEFINED\n", ps2, ""); + } + PATHD_FORMAT("%*spcc_id: %u\n", ps2, "", path->pcc_id); + PATHD_FORMAT("%*screate_origin: %s (%u)\n", ps2, "", + srte_protocol_origin_name(path->create_origin), + path->create_origin); + PATHD_FORMAT("%*supdate_origin: %s (%u)\n", ps2, "", + srte_protocol_origin_name(path->update_origin), + path->update_origin); + if (path->originator != NULL) { + PATHD_FORMAT("%*soriginator: %s\n", ps2, "", + path->originator); + } else { + PATHD_FORMAT("%*soriginator: UNDEFINED\n", ps2, ""); + } + PATHD_FORMAT("%*stype: %s (%u)\n", ps2, "", + srte_candidate_type_name(path->type), path->type); + PATHD_FORMAT("%*splsp_id: %u\n", ps2, "", path->plsp_id); + if (path->name == NULL) { + PATHD_FORMAT("%*sname: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*sname: %s\n", ps2, "", path->name); + } + PATHD_FORMAT("%*ssrp_id: %u\n", ps2, "", path->srp_id); + PATHD_FORMAT("%*sreq_id: %u\n", ps2, "", path->req_id); + PATHD_FORMAT("%*sstatus: %s (%u)\n", ps2, "", + pcep_lsp_status_name(path->status), path->status); + PATHD_FORMAT("%*sdo_remove: %u\n", ps2, "", path->do_remove); + PATHD_FORMAT("%*sgo_active: %u\n", ps2, "", path->go_active); + PATHD_FORMAT("%*swas_created: %u\n", ps2, "", + path->was_created); + PATHD_FORMAT("%*swas_removed: %u\n", ps2, "", + path->was_removed); + PATHD_FORMAT("%*sis_synching: %u\n", ps2, "", + path->is_synching); + PATHD_FORMAT("%*sis_delegated: %u\n", ps2, "", + path->is_delegated); + PATHD_FORMAT("%*shas_bandwidth: %u\n", ps2, "", + path->has_bandwidth); + if (path->has_bandwidth) { + PATHD_FORMAT("%*senforce_bandwidth: %u\n", ps2, "", + path->enforce_bandwidth); + PATHD_FORMAT("%*sbandwidth: %f\n", ps2, "", + path->bandwidth); + } + PATHD_FORMAT("%*shas_pcc_objfun: %u\n", ps2, "", + path->has_pcc_objfun); + if (path->has_pcc_objfun) { + PATHD_FORMAT("%*senforce_pcc_objfun: %d\n", ps2, "", + path->enforce_pcc_objfun); + PATHD_FORMAT("%*spcc_objfun: %s (%u)\n", ps2, "", + objfun_type_name(path->pcc_objfun), + path->pcc_objfun); + } + PATHD_FORMAT("%*shas_pce_objfun: %u\n", ps2, "", + path->has_pce_objfun); + if (path->has_pce_objfun) + PATHD_FORMAT("%*spce_objfun: %s (%u)\n", ps2, "", + objfun_type_name(path->pce_objfun), + path->pce_objfun); + PATHD_FORMAT("%*shas_affinity_filters: %u\n", ps2, "", + path->has_affinity_filters); + if (path->has_affinity_filters) { + PATHD_FORMAT("%*sexclude_any: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_EXCLUDE_ANY - 1]); + PATHD_FORMAT("%*sinclude_any: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_INCLUDE_ANY - 1]); + PATHD_FORMAT("%*sinclude_all: 0x%08x\n", ps2, "", + path->affinity_filters + [AFFINITY_FILTER_INCLUDE_ALL - 1]); + } + + if (path->first_hop == NULL) { + PATHD_FORMAT("%*shops: []\n", ps2, ""); + } else { + PATHD_FORMAT("%*shops: \n", ps2, ""); + for (struct path_hop *hop = path->first_hop; + hop != NULL; hop = hop->next) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_path_hop(ps3, hop); + } + } + if (path->first_metric == NULL) { + PATHD_FORMAT("%*smetrics: []\n", ps2, ""); + } else { + PATHD_FORMAT("%*smetrics: \n", ps2, ""); + for (struct path_metric *metric = path->first_metric; + NULL != metric; metric = metric->next) { + PATHD_FORMAT("%*s- ", ps3 - 2, ""); + _format_path_metric(ps3, metric); + } + } + } +} + +void _format_path_metric(int ps, struct path_metric *metric) +{ + PATHD_FORMAT("type: %s (%u)\n", pcep_metric_type_name(metric->type), + metric->type); + PATHD_FORMAT("%*senforce: %u\n", ps, "", metric->enforce); + PATHD_FORMAT("%*sis_bound: %u\n", ps, "", metric->is_bound); + PATHD_FORMAT("%*sis_computed: %u\n", ps, "", metric->is_computed); + PATHD_FORMAT("%*svalue: %f\n", ps, "", metric->value); +} + +void _format_path_hop(int ps, struct path_hop *hop) +{ + PATHD_FORMAT("is_loose: %u\n", hop->is_loose); + PATHD_FORMAT("%*shas_sid: %u\n", ps, "", hop->has_sid); + + if (hop->has_sid) { + PATHD_FORMAT("%*sis_mpls: %u\n", ps, "", hop->is_mpls); + if (hop->is_mpls) { + PATHD_FORMAT("%*shas_attribs: %u\n", ps, "", + hop->has_attribs); + PATHD_FORMAT("%*slabel: %u\n", ps, "", + hop->sid.mpls.label); + if (hop->has_attribs) { + PATHD_FORMAT("%*straffic_class: %u\n", ps, "", + hop->sid.mpls.traffic_class); + PATHD_FORMAT("%*sis_bottom: %u\n", ps, "", + hop->sid.mpls.is_bottom); + PATHD_FORMAT("%*sttl: %u\n", ps, "", + hop->sid.mpls.ttl); + } + } else { + PATHD_FORMAT("%*sSID: %u\n", ps, "", hop->sid.value); + } + } + + PATHD_FORMAT("%*shas_nai: %u\n", ps, "", hop->has_nai); + if (hop->has_nai) { + PATHD_FORMAT("%*snai_type: %s (%u)\n", ps, "", + pcep_nai_type_name(hop->nai.type), hop->nai.type); + switch (hop->nai.type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + PATHD_FORMAT("%*sNAI: %pI4\n", ps, "", + &hop->nai.local_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + PATHD_FORMAT("%*sNAI: %pI6\n", ps, "", + &hop->nai.local_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI4/%pI4\n", ps, "", + &hop->nai.local_addr.ipaddr_v4, + &hop->nai.remote_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI6/%pI6\n", ps, "", + &hop->nai.local_addr.ipaddr_v6, + &hop->nai.remote_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + PATHD_FORMAT("%*sNAI: %pI6(%u)/%pI6(%u)\n", ps, "", + &hop->nai.local_addr.ipaddr_v6, + hop->nai.local_iface, + &hop->nai.remote_addr.ipaddr_v6, + hop->nai.remote_iface); + break; + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + PATHD_FORMAT("%*sNAI: UNSUPPORTED\n", ps, ""); + break; + } + } +} + +void _format_pcep_event(int ps, pcep_event *event) +{ + char buf[32]; + + if (event == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*sevent_type: %s\n", ps2, "", + pcep_event_type_name(event->event_type)); + PATHD_FORMAT("%*sevent_time: %s", ps2, "", + ctime_r(&event->event_time, buf)); + if (event->session == NULL) { + PATHD_FORMAT("%*ssession: NULL\n", ps2, ""); + } else { + PATHD_FORMAT("%*ssession: \n", ps2, "", + event->session); + } + PATHD_FORMAT("%*smessage: ", ps2, ""); + _format_pcep_message(ps2, event->message); + } +} + +void _format_pcep_message(int ps, struct pcep_message *msg) +{ + if (msg == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + int ps2 = ps + DEBUG_IDENT_SIZE; + PATHD_FORMAT("\n"); + PATHD_FORMAT("%*spcep_version: %u\n", ps2, "", + msg->msg_header->pcep_version); + PATHD_FORMAT("%*stype: %s (%u)\n", ps2, "", + pcep_message_type_name(msg->msg_header->type), + msg->msg_header->type); + PATHD_FORMAT("%*sobjects: ", ps2, ""); + _format_pcep_objects(ps2, msg->obj_list); + } +} + +void _format_pcep_objects(int ps, double_linked_list *objs) +{ + if (objs == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + double_linked_list_node *node; + int ps2 = ps + DEBUG_IDENT_SIZE; + int i; + + if (objs->num_entries == 0) { + PATHD_FORMAT("[]\n"); + return; + } + + PATHD_FORMAT("\n"); + for (node = objs->head, i = 0; node != NULL; + node = node->next_node, i++) { + struct pcep_object_header *obj = + (struct pcep_object_header *)node->data; + PATHD_FORMAT("%*s- ", ps2 - 2, ""); + _format_pcep_object(ps2, obj); + } + } +} + +void _format_pcep_object(int ps, struct pcep_object_header *obj) +{ + if (obj == NULL) { + PATHD_FORMAT("NULL\n"); + } else { + PATHD_FORMAT("object_class: %s (%u)\n", + pcep_object_class_name(obj->object_class), + obj->object_class); + PATHD_FORMAT("%*sobject_type: %s (%u)\n", ps, "", + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + PATHD_FORMAT("%*sflag_p: %u\n", ps, "", obj->flag_p); + PATHD_FORMAT("%*sflag_i: %u\n", ps, "", obj->flag_i); + _format_pcep_object_details(ps, obj); + _format_pcep_object_tlvs(ps, obj); + } +} + +void _format_pcep_object_details(int ps, struct pcep_object_header *obj) +{ + switch (TUP(obj->object_class, obj->object_type)) { + case TUP(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR): + _format_pcep_object_error(ps, (struct pcep_object_error *)obj); + break; + case TUP(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + _format_pcep_object_open(ps, (struct pcep_object_open *)obj); + break; + case TUP(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + _format_pcep_object_rp(ps, (struct pcep_object_rp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP): + _format_pcep_object_srp(ps, (struct pcep_object_srp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP): + _format_pcep_object_lsp(ps, (struct pcep_object_lsp *)obj); + break; + case TUP(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + _format_pcep_object_lspa(ps, (struct pcep_object_lspa *)obj); + break; + case TUP(PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4): + _format_pcep_object_ipv4_endpoint( + ps, (struct pcep_object_endpoints_ipv4 *)obj); + break; + case TUP(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + _format_pcep_object_ro(ps, (struct pcep_object_ro *)obj); + break; + case TUP(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + _format_pcep_object_metric(ps, + (struct pcep_object_metric *)obj); + break; + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ): + case TUP(PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + _format_pcep_object_bandwidth( + ps, (struct pcep_object_bandwidth *)obj); + break; + case TUP(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + _format_pcep_object_nopath(ps, + (struct pcep_object_nopath *)obj); + break; + case TUP(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + _format_pcep_object_objfun( + ps, (struct pcep_object_objective_function *)obj); + break; + default: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_error(int ps, struct pcep_object_error *obj) +{ + PATHD_FORMAT("%*serror_type: %s (%u)\n", ps, "", + pcep_error_type_name(obj->error_type), obj->error_type); + PATHD_FORMAT("%*serror_value: %s (%u)\n", ps, "", + pcep_error_value_name(obj->error_type, obj->error_value), + obj->error_value); +} + + +void _format_pcep_object_open(int ps, struct pcep_object_open *obj) +{ + PATHD_FORMAT("%*sopen_version: %u\n", ps, "", obj->open_version); + PATHD_FORMAT("%*sopen_keepalive: %u\n", ps, "", obj->open_keepalive); + PATHD_FORMAT("%*sopen_deadtimer: %u\n", ps, "", obj->open_deadtimer); + PATHD_FORMAT("%*sopen_sid: %u\n", ps, "", obj->open_sid); +} + +void _format_pcep_object_rp(int ps, struct pcep_object_rp *obj) +{ + PATHD_FORMAT("%*spriority: %u\n", ps, "", obj->priority); + PATHD_FORMAT("%*sflag_reoptimization: %u\n", ps, "", + obj->flag_reoptimization); + PATHD_FORMAT("%*sflag_bidirectional: %u\n", ps, "", + obj->flag_bidirectional); + PATHD_FORMAT("%*sflag_strict: %u\n", ps, "", obj->flag_strict); + PATHD_FORMAT("%*sflag_of: %u\n", ps, "", obj->flag_of); + PATHD_FORMAT("%*srequest_id: %u\n", ps, "", obj->request_id); +} + + +void _format_pcep_object_srp(int ps, struct pcep_object_srp *obj) +{ + PATHD_FORMAT("%*sflag_lsp_remove: %u\n", ps, "", obj->flag_lsp_remove); + PATHD_FORMAT("%*ssrp_id_number: %u\n", ps, "", obj->srp_id_number); +} + +void _format_pcep_object_lsp(int ps, struct pcep_object_lsp *obj) +{ + PATHD_FORMAT("%*splsp_id: %u\n", ps, "", obj->plsp_id); + PATHD_FORMAT("%*sstatus: %s\n", ps, "", + pcep_lsp_status_name(obj->operational_status)); + PATHD_FORMAT("%*sflag_d: %u\n", ps, "", obj->flag_d); + PATHD_FORMAT("%*sflag_s: %u\n", ps, "", obj->flag_s); + PATHD_FORMAT("%*sflag_r: %u\n", ps, "", obj->flag_r); + PATHD_FORMAT("%*sflag_a: %u\n", ps, "", obj->flag_a); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); +} + +void _format_pcep_object_lspa(int ps, struct pcep_object_lspa *obj) +{ + PATHD_FORMAT("%*slspa_exclude_any: 0x%08x\n", ps, "", + obj->lspa_exclude_any); + PATHD_FORMAT("%*slspa_include_any: 0x%08x\n", ps, "", + obj->lspa_include_any); + PATHD_FORMAT("%*slspa_include_all: 0x%08x\n", ps, "", + obj->lspa_include_all); + PATHD_FORMAT("%*ssetup_priority: %u\n", ps, "", obj->setup_priority); + PATHD_FORMAT("%*sholding_priority: %u\n", ps, "", + obj->holding_priority); + PATHD_FORMAT("%*sflag_local_protection: %u\n", ps, "", + obj->flag_local_protection); +} + +void _format_pcep_object_ipv4_endpoint(int ps, + struct pcep_object_endpoints_ipv4 *obj) +{ + PATHD_FORMAT("%*ssrc_ipv4: %pI4\n", ps, "", &obj->src_ipv4); + PATHD_FORMAT("%*sdst_ipv4: %pI4\n", ps, "", &obj->dst_ipv4); +} + +void _format_pcep_object_metric(int ps, struct pcep_object_metric *obj) +{ + PATHD_FORMAT("%*stype: %s (%u)\n", ps, "", + pcep_metric_type_name(obj->type), obj->type); + PATHD_FORMAT("%*sflag_b: %u\n", ps, "", obj->flag_b); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*svalue: %f\n", ps, "", obj->value); +} + +void _format_pcep_object_bandwidth(int ps, struct pcep_object_bandwidth *obj) +{ + PATHD_FORMAT("%*sbandwidth: %f\n", ps, "", obj->bandwidth); +} + +void _format_pcep_object_nopath(int ps, struct pcep_object_nopath *obj) +{ + PATHD_FORMAT("%*sni: %u\n", ps, "", obj->ni); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*serr_code: %s (%u)\n", ps, "", + pcep_nopath_tlv_err_code_name(obj->err_code), + obj->err_code); +} + +void _format_pcep_object_objfun(int ps, + struct pcep_object_objective_function *obj) +{ + PATHD_FORMAT("%*sof_code: %s (%u)\n", ps, "", + objfun_type_name(obj->of_code), obj->of_code); +} + +void _format_pcep_object_ro(int ps, struct pcep_object_ro *obj) +{ + double_linked_list *obj_list = obj->sub_objects; + double_linked_list_node *node; + struct pcep_object_ro_subobj *sub_obj; + + int ps2 = ps + DEBUG_IDENT_SIZE; + int i; + + if ((obj_list == NULL) || (obj_list->num_entries == 0)) { + PATHD_FORMAT("%*ssub_objects: []\n", ps, ""); + return; + } + + PATHD_FORMAT("%*ssub_objects:\n", ps, ""); + + for (node = obj_list->head, i = 0; node != NULL; + node = node->next_node, i++) { + sub_obj = (struct pcep_object_ro_subobj *)node->data; + PATHD_FORMAT("%*s- flag_subobj_loose_hop: %u\n", ps2 - 2, "", + sub_obj->flag_subobj_loose_hop); + PATHD_FORMAT("%*sro_subobj_type: %s (%u)\n", ps2, "", + pcep_ro_type_name(sub_obj->ro_subobj_type), + sub_obj->ro_subobj_type); + _format_pcep_object_ro_details(ps2, sub_obj); + } +} + +void _format_pcep_object_ro_details(int ps, struct pcep_object_ro_subobj *ro) +{ + switch (ro->ro_subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: + _format_pcep_object_ro_ipv4(ps, + (struct pcep_ro_subobj_ipv4 *)ro); + break; + case RO_SUBOBJ_TYPE_SR: + _format_pcep_object_ro_sr(ps, (struct pcep_ro_subobj_sr *)ro); + break; + case RO_SUBOBJ_TYPE_IPV6: + case RO_SUBOBJ_TYPE_LABEL: + case RO_SUBOBJ_TYPE_UNNUM: + case RO_SUBOBJ_TYPE_ASN: + case RO_SUBOBJ_UNKNOWN: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_ro_ipv4(int ps, struct pcep_ro_subobj_ipv4 *obj) +{ + PATHD_FORMAT("%*sip_addr: %pI4\n", ps, "", &obj->ip_addr); + PATHD_FORMAT("%*sprefix_length: %u\n", ps, "", obj->prefix_length); + PATHD_FORMAT("%*sflag_local_protection: %u\n", ps, "", + obj->flag_local_protection); +} + +void _format_pcep_object_ro_sr(int ps, struct pcep_ro_subobj_sr *obj) +{ + PATHD_FORMAT("%*snai_type = %s (%u)\n", ps, "", + pcep_nai_type_name(obj->nai_type), obj->nai_type); + PATHD_FORMAT("%*sflag_f: %u\n", ps, "", obj->flag_f); + PATHD_FORMAT("%*sflag_s: %u\n", ps, "", obj->flag_s); + PATHD_FORMAT("%*sflag_c: %u\n", ps, "", obj->flag_c); + PATHD_FORMAT("%*sflag_m: %u\n", ps, "", obj->flag_m); + + if (!obj->flag_s) { + PATHD_FORMAT("%*sSID: %u\n", ps, "", obj->sid); + if (obj->flag_m) { + PATHD_FORMAT("%*slabel: %u\n", ps, "", + GET_SR_ERO_SID_LABEL(obj->sid)); + if (obj->flag_c) { + PATHD_FORMAT("%*sTC: %u\n", ps, "", + GET_SR_ERO_SID_TC(obj->sid)); + PATHD_FORMAT("%*sS: %u\n", ps, "", + GET_SR_ERO_SID_S(obj->sid)); + PATHD_FORMAT("%*sTTL: %u\n", ps, "", + GET_SR_ERO_SID_TTL(obj->sid)); + } + } + } + + if (!obj->flag_f) { + struct in_addr *laddr4, *raddr4; + struct in6_addr *laddr6, *raddr6; + uint32_t *liface, *riface; + assert(obj->nai_list != NULL); + double_linked_list_node *n = obj->nai_list->head; + assert(n != NULL); + assert(n->data != NULL); + switch (obj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + laddr4 = (struct in_addr *)n->data; + PATHD_FORMAT("%*sNAI: %pI4\n", ps, "", laddr4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + laddr6 = (struct in6_addr *)n->data; + PATHD_FORMAT("%*sNAI: %pI6\n", ps, "", laddr6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + assert(n->next_node != NULL); + assert(n->next_node->data != NULL); + laddr4 = (struct in_addr *)n->data; + raddr4 = (struct in_addr *)n->next_node->data; + PATHD_FORMAT("%*sNAI: %pI4/%pI4\n", ps, "", laddr4, + raddr4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + assert(n->next_node != NULL); + assert(n->next_node->data != NULL); + laddr6 = (struct in6_addr *)n->data; + raddr6 = (struct in6_addr *)n->next_node->data; + PATHD_FORMAT("%*sNAI: %pI6/%pI6\n", ps, "", laddr6, + raddr6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + laddr4 = (struct in_addr *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + liface = (uint32_t *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + raddr4 = (struct in_addr *)n->data; + assert(n != NULL); + assert(n->data != NULL); + riface = (uint32_t *)n->data; + PATHD_FORMAT("%*sNAI: %pI4(%u)/%pI4(%u)\n", ps, "", + laddr4, *liface, raddr4, *riface); + break; + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + PATHD_FORMAT("%*sNAI: UNSUPPORTED\n", ps, ""); + break; + } + } +} + +void _format_pcep_object_tlvs(int ps, struct pcep_object_header *obj) +{ + double_linked_list *tlv_list = obj->tlv_list; + struct pcep_object_tlv_header *tlv; + double_linked_list_node *node; + int ps2 = ps + DEBUG_IDENT_SIZE; + int i = 0; + + if (tlv_list == NULL) + return; + if (tlv_list->num_entries == 0) { + PATHD_FORMAT("%*stlvs: []\n", ps, ""); + return; + } + + PATHD_FORMAT("%*stlvs:\n", ps, ""); + + for (node = tlv_list->head, i = 0; node != NULL; + node = node->next_node, i++) { + tlv = (struct pcep_object_tlv_header *)node->data; + PATHD_FORMAT("%*s- ", ps2 - 2, ""); + _format_pcep_object_tlv(ps2, tlv); + } +} + +void _format_pcep_object_tlv(int ps, struct pcep_object_tlv_header *tlv_header) +{ + PATHD_FORMAT("type: %s (%u)\n", pcep_tlv_type_name(tlv_header->type), + tlv_header->type); + _format_pcep_object_tlv_details(ps, tlv_header); +} + +void _format_pcep_object_tlv_details(int ps, + struct pcep_object_tlv_header *tlv_header) +{ + switch (tlv_header->type) { + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + _format_pcep_object_tlv_symbolic_path_name( + ps, (struct pcep_object_tlv_symbolic_path_name *) + tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + _format_pcep_object_tlv_stateful_pce_capability( + ps, (struct pcep_object_tlv_stateful_pce_capability *) + tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + _format_pcep_object_tlv_sr_pce_capability( + ps, + (struct pcep_object_tlv_sr_pce_capability *)tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + _format_pcep_object_tlv_path_setup_type( + ps, + (struct pcep_object_tlv_path_setup_type *)tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TYPE_CISCO_BSID: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + PATHD_FORMAT("%*s...\n", ps, ""); + break; + } +} + +void _format_pcep_object_tlv_symbolic_path_name( + int ps, struct pcep_object_tlv_symbolic_path_name *tlv) +{ + PATHD_FORMAT("%*ssymbolic_path_name: %.*s\n", ps, "", + tlv->symbolic_path_name_length, tlv->symbolic_path_name); +} + +void _format_pcep_object_tlv_stateful_pce_capability( + int ps, struct pcep_object_tlv_stateful_pce_capability *tlv) +{ + PATHD_FORMAT("%*sflag_u_lsp_update_capability: %u\n", ps, "", + tlv->flag_u_lsp_update_capability); + PATHD_FORMAT("%*sflag_s_include_db_version: %u\n", ps, "", + tlv->flag_s_include_db_version); + PATHD_FORMAT("%*sflag_i_lsp_instantiation_capability: %u\n", ps, "", + tlv->flag_i_lsp_instantiation_capability); + PATHD_FORMAT("%*sflag_t_triggered_resync: %u\n", ps, "", + tlv->flag_t_triggered_resync); + PATHD_FORMAT("%*sflag_d_delta_lsp_sync: %u\n", ps, "", + tlv->flag_d_delta_lsp_sync); + PATHD_FORMAT("%*sflag_f_triggered_initial_sync: %u\n", ps, "", + tlv->flag_f_triggered_initial_sync); +} + +void _format_pcep_object_tlv_sr_pce_capability( + int ps, struct pcep_object_tlv_sr_pce_capability *tlv) +{ + + PATHD_FORMAT("%*sflag_n: %u\n", ps, "", tlv->flag_n); + PATHD_FORMAT("%*sflag_x: %u\n", ps, "", tlv->flag_x); + PATHD_FORMAT("%*smax_sid_depth: %u\n", ps, "", tlv->max_sid_depth); +} + +void _format_pcep_object_tlv_path_setup_type( + int ps, struct pcep_object_tlv_path_setup_type *tlv) +{ + PATHD_FORMAT("%*spath_setup_type: %u\n", ps, "", tlv->path_setup_type); +} diff --git a/pathd/path_pcep_debug.h b/pathd/path_pcep_debug.h new file mode 100644 index 0000000..cc63ca0 --- /dev/null +++ b/pathd/path_pcep_debug.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_DEBUG_H_ +#define _PATH_PCEP_DEBUG_H_ + +#include "pathd/path_debug.h" +#include "pceplib/pcep_pcc_api.h" +#include "pceplib/pcep_msg_objects.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_pcc.h" +#include "pathd/path_pcep_lib.h" + +const char *pcc_status_name(enum pcc_status status); + +const char *pcep_error_type_name(enum pcep_error_type error_type); +const char *pcep_error_value_name(enum pcep_error_type error_type, + enum pcep_error_value error_value); +const char *pcep_event_type_name(pcep_event_type event_type); +const char *pcep_message_type_name(enum pcep_message_types pcep_message_type); +const char *pcep_object_class_name(enum pcep_object_classes obj_class); +const char *pcep_object_type_name(enum pcep_object_classes obj_class, + enum pcep_object_types obj_type); +const char *pcep_lsp_status_name(enum pcep_lsp_operational_status status); +const char *pcep_tlv_type_name(enum pcep_object_tlv_types tlv_type); +const char *pcep_ro_type_name(enum pcep_ro_subobj_types ro_type); +const char *pcep_nai_type_name(enum pcep_sr_subobj_nai nai_type); +const char *pcep_metric_type_name(enum pcep_metric_types type); +const char *pcep_nopath_tlv_err_code_name(enum pcep_nopath_tlv_err_codes code); + +const char *format_objfun_set(uint32_t flags); +const char *format_pcc_opts(struct pcc_opts *ops); +const char *format_pcc_state(struct pcc_state *state); +const char *format_ctrl_state(struct ctrl_state *state); +const char *format_path(struct path *path); +const char *format_pcep_event(pcep_event *event); +const char *format_pcep_message(struct pcep_message *msg); + +#endif // _PATH_PCEP_DEBUG_H_ diff --git a/pathd/path_pcep_lib.c b/pathd/path_pcep_lib.c new file mode 100644 index 0000000..d43fdb0 --- /dev/null +++ b/pathd/path_pcep_lib.c @@ -0,0 +1,1336 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "memory.h" + +#include +#include "pceplib/pcep_utils_counters.h" +#include "pceplib/pcep_timers.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_debug.h" + +DEFINE_MTYPE_STATIC(PATHD, PCEPLIB_INFRA, "PCEPlib Infrastructure"); +DEFINE_MTYPE_STATIC(PATHD, PCEPLIB_MESSAGES, "PCEPlib PCEP Messages"); + +#define CLASS_TYPE(CLASS, TYPE) (((CLASS) << 16) | (TYPE)) +#define DEFAULT_LSAP_SETUP_PRIO 4 +#define DEFAULT_LSAP_HOLDING_PRIO 4 +#define DEFAULT_LSAP_LOCAL_PRETECTION false +#define MAX_PATH_NAME_SIZE 255 + +/* pceplib logging callback */ +static int pceplib_logging_cb(int level, const char *fmt, va_list args) + PRINTFRR(2, 0); + +/* Socket callbacks */ +static int pcep_lib_pceplib_socket_read_cb(void *fpt, void **thread, int fd, + void *payload); +static int pcep_lib_pceplib_socket_write_cb(void *fpt, void **thread, int fd, + void *payload); +static void pcep_lib_socket_read_ready(struct event *thread); +static void pcep_lib_socket_write_ready(struct event *thread); + +/* pceplib pcep_event callbacks */ +static void pcep_lib_pceplib_event_cb(void *fpt, pcep_event *event); + +/* pceplib pthread creation callback */ +static int pcep_lib_pthread_create_cb(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); +void *pcep_lib_pthread_start_passthrough(void *data); +int pcep_lib_pthread_stop_cb(struct frr_pthread *, void **); + +/* Internal functions */ +static double_linked_list *pcep_lib_format_path(struct pcep_caps *caps, + struct path *path); +static void pcep_lib_format_constraints(struct path *path, + double_linked_list *objs); +static void pcep_lib_parse_open(struct pcep_caps *caps, + struct pcep_object_open *open); +static void +pcep_lib_parse_open_pce_capability(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header); +static void +pcep_lib_parse_open_objfun_list(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header); +static void pcep_lib_parse_rp(struct path *path, struct pcep_object_rp *rp); +static void pcep_lib_parse_srp(struct path *path, struct pcep_object_srp *srp); +static void pcep_lib_parse_lsp(struct path *path, struct pcep_object_lsp *lsp); +static void pcep_lib_parse_lspa(struct path *path, + struct pcep_object_lspa *lspa); +static void pcep_lib_parse_lsp_symbolic_name( + struct path *path, struct pcep_object_tlv_symbolic_path_name *tlv); +static void pcep_lib_parse_metric(struct path *path, + struct pcep_object_metric *obj); +static void +pcep_lib_parse_endpoints_ipv4(struct path *path, + struct pcep_object_endpoints_ipv4 *obj); +static void +pcep_lib_parse_endpoints_ipv6(struct path *path, + struct pcep_object_endpoints_ipv6 *obj); +static void pcep_lib_parse_vendor_info(struct path *path, + struct pcep_object_vendor_info *obj); +static void pcep_lib_parse_ero(struct path *path, struct pcep_object_ro *ero); +static struct path_hop *pcep_lib_parse_ero_sr(struct path_hop *next, + struct pcep_ro_subobj_sr *sr); +static struct counters_group *copy_counter_group(struct counters_group *from); +static struct counters_subgroup * +copy_counter_subgroup(struct counters_subgroup *from); +static struct counter *copy_counter(struct counter *from); +static void free_counter_group(struct counters_group *group); +static void free_counter_subgroup(struct counters_subgroup *subgroup); +static void free_counter(struct counter *counter); + +struct pcep_lib_pthread_passthrough_data { + void *(*start_routine)(void *data); + void *data; +}; + +/* ------------ API Functions ------------ */ + +int pcep_lib_initialize(struct frr_pthread *fpt) +{ + PCEP_DEBUG("Initializing pceplib"); + + /* Register pceplib logging callback */ + register_logger(pceplib_logging_cb); + + /* Its ok that this object goes out of scope, as it + * wont be stored, and its values will be copied */ + struct pceplib_infra_config infra = { + /* Memory infrastructure */ + .pceplib_infra_mt = MTYPE_PCEPLIB_INFRA, + .pceplib_messages_mt = MTYPE_PCEPLIB_MESSAGES, + .malloc_func = (pceplib_malloc_func)qmalloc, + .calloc_func = (pceplib_calloc_func)qcalloc, + .realloc_func = (pceplib_realloc_func)qrealloc, + .strdup_func = (pceplib_strdup_func)qstrdup, + .free_func = (pceplib_free_func)qfree, + /* Timers infrastructure */ + .external_infra_data = fpt, + .socket_read_func = pcep_lib_pceplib_socket_read_cb, + .socket_write_func = pcep_lib_pceplib_socket_write_cb, + /* PCEP events */ + .pcep_event_func = pcep_lib_pceplib_event_cb, + /* PCEPlib pthread creation callback */ + .pthread_create_func = pcep_lib_pthread_create_cb}; + if (!initialize_pcc_infra(&infra)) { + flog_err(EC_PATH_PCEP_PCC_INIT, "failed to initialize pceplib"); + return 1; + } + + return 0; +} + +void pcep_lib_finalize(void) +{ + PCEP_DEBUG("Finalizing pceplib"); + if (!destroy_pcc()) { + flog_err(EC_PATH_PCEP_PCC_FINI, "failed to finalize pceplib"); + } +} + + +pcep_session * +pcep_lib_connect(struct ipaddr *src_addr, int src_port, struct ipaddr *dst_addr, + int dst_port, short msd, + const struct pcep_config_group_opts *pcep_options) +{ + pcep_configuration *config; + pcep_session *sess; + + config = create_default_pcep_configuration(); + config->dst_pcep_port = dst_port; + config->src_pcep_port = src_port; + if (IS_IPADDR_V6(src_addr)) { + config->is_src_ipv6 = true; + memcpy(&config->src_ip.src_ipv6, &src_addr->ipaddr_v6, + sizeof(struct in6_addr)); + } else { + config->is_src_ipv6 = false; + config->src_ip.src_ipv4 = src_addr->ipaddr_v4; + } + + config->support_stateful_pce_lsp_update = true; + config->support_pce_lsp_instantiation = pcep_options->pce_initiated; + config->support_include_db_version = false; + config->support_lsp_triggered_resync = false; + config->support_lsp_delta_sync = false; + config->support_pce_triggered_initial_sync = false; + config->support_sr_te_pst = true; + config->pcc_can_resolve_nai_to_sid = false; + + config->max_sid_depth = msd; + config->pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = + pcep_options->draft07; + config->keep_alive_seconds = pcep_options->keep_alive_seconds; + config->min_keep_alive_seconds = pcep_options->min_keep_alive_seconds; + config->max_keep_alive_seconds = pcep_options->max_keep_alive_seconds; + config->dead_timer_seconds = pcep_options->dead_timer_seconds; + config->min_dead_timer_seconds = pcep_options->min_dead_timer_seconds; + config->max_dead_timer_seconds = pcep_options->max_dead_timer_seconds; + config->request_time_seconds = pcep_options->pcep_request_time_seconds; + /* TODO when available in the pceplib, set it here + pcep_options->state_timeout_inteval_seconds;*/ + + if (pcep_options->tcp_md5_auth[0] != '\0') { + config->is_tcp_auth_md5 = true; + strlcpy(config->tcp_authentication_str, + pcep_options->tcp_md5_auth, + sizeof(config->tcp_authentication_str)); + } else { + config->is_tcp_auth_md5 = false; + } + + if (IS_IPADDR_V6(dst_addr)) { + sess = connect_pce_ipv6(config, &dst_addr->ipaddr_v6); + } else { + sess = connect_pce(config, &dst_addr->ipaddr_v4); + } + destroy_pcep_configuration(config); + return sess; +} + +void pcep_lib_disconnect(pcep_session *sess) +{ + assert(sess != NULL); + disconnect_pce(sess); +} + +/* Callback passed to pceplib to write to a socket. + * When the socket is ready to be written to, + * pcep_lib_socket_write_ready() will be called */ + +int pcep_lib_pceplib_socket_write_cb(void *fpt, void **thread, int fd, + void *payload) +{ + return pcep_thread_socket_write(fpt, thread, fd, payload, + pcep_lib_socket_write_ready); +} + +/* Callback passed to pceplib to read from a socket. + * When the socket is ready to be read from, + * pcep_lib_socket_read_ready() will be called */ + +int pcep_lib_pceplib_socket_read_cb(void *fpt, void **thread, int fd, + void *payload) +{ + return pcep_thread_socket_read(fpt, thread, fd, payload, + pcep_lib_socket_read_ready); +} + +/* Callbacks called by path_pcep_controller when a socket is ready to read/write + */ + +void pcep_lib_socket_write_ready(struct event *thread) +{ + struct pcep_ctrl_socket_data *data = EVENT_ARG(thread); + assert(data != NULL); + + pceplib_external_socket_write(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); +} + +void pcep_lib_socket_read_ready(struct event *thread) +{ + struct pcep_ctrl_socket_data *data = EVENT_ARG(thread); + assert(data != NULL); + + pceplib_external_socket_read(data->fd, data->payload); + XFREE(MTYPE_PCEP, data); +} + +/* Callback passed to pceplib when a pcep_event is ready */ +void pcep_lib_pceplib_event_cb(void *fpt, pcep_event *event) +{ + pcep_thread_send_ctrl_event(fpt, event, pcep_thread_pcep_event); +} + +/* Wrapper function around the actual pceplib thread start function */ +void *pcep_lib_pthread_start_passthrough(void *data) +{ + struct frr_pthread *fpt = data; + struct pcep_lib_pthread_passthrough_data *passthrough_data = fpt->data; + void *start_routine_data = passthrough_data->data; + void *(*start_routine)(void *) = passthrough_data->start_routine; + XFREE(MTYPE_PCEP, passthrough_data); + + if (start_routine != NULL) { + return start_routine(start_routine_data); + } + + return NULL; +} + +int pcep_lib_pthread_create_cb(pthread_t *thread_id, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *data, + const char *thread_name) +{ + /* Since FRR calls the start_routine with a struct frr_pthread, + * we have to store the real data and callback in a passthrough + * and pass the actual data the start_routine needs */ + struct pcep_lib_pthread_passthrough_data *passthrough_data = XMALLOC( + MTYPE_PCEP, sizeof(struct pcep_lib_pthread_passthrough_data)); + passthrough_data->data = data; + passthrough_data->start_routine = start_routine; + + struct frr_pthread_attr fpt_attr = { + .start = pcep_lib_pthread_start_passthrough, + .stop = pcep_lib_pthread_stop_cb}; + struct frr_pthread *fpt = + frr_pthread_new(&fpt_attr, thread_name, "pcep_lib"); + if (fpt == NULL) { + return 1; + } + + fpt->data = passthrough_data; + int retval = frr_pthread_run(fpt, attr); + if (retval) { + return retval; + } + + *thread_id = fpt->thread; + + return 0; +} + +int pcep_lib_pthread_stop_cb(struct frr_pthread *fpt, void **res) +{ + pcep_lib_finalize(); + frr_pthread_destroy(fpt); + + return 0; +} + +struct pcep_message *pcep_lib_format_report(struct pcep_caps *caps, + struct path *path) +{ + double_linked_list *objs = pcep_lib_format_path(caps, path); + return pcep_msg_create_report(objs); +} + +static struct pcep_object_rp *create_rp(uint32_t reqid) +{ + double_linked_list *rp_tlvs; + struct pcep_object_tlv_path_setup_type *setup_type_tlv; + struct pcep_object_rp *rp; + + rp_tlvs = dll_initialize(); + setup_type_tlv = pcep_tlv_create_path_setup_type(SR_TE_PST); + dll_append(rp_tlvs, setup_type_tlv); + + rp = pcep_obj_create_rp(0, false, false, false, true, reqid, rp_tlvs); + + return rp; +} + +struct pcep_message *pcep_lib_format_request(struct pcep_caps *caps, + struct path *path) +{ + struct ipaddr *src = &path->pcc_addr; + struct ipaddr *dst = &path->nbkey.endpoint; + double_linked_list *objs; + struct pcep_object_rp *rp; + struct pcep_object_endpoints_ipv4 *endpoints_ipv4; + struct pcep_object_endpoints_ipv6 *endpoints_ipv6; + struct pcep_object_objective_function *of = NULL; + enum objfun_type objfun = OBJFUN_UNDEFINED; + + assert(src->ipa_type == dst->ipa_type); + + objs = dll_initialize(); + rp = create_rp(path->req_id); + rp->header.flag_p = true; + + pcep_lib_format_constraints(path, objs); + + /* Objective Function */ + if (path->has_pcc_objfun) { + objfun = path->pcc_objfun; + } + + if (objfun != OBJFUN_UNDEFINED) { + of = pcep_obj_create_objective_function(objfun, NULL); + assert(of != NULL); + of->header.flag_p = path->enforce_pcc_objfun; + dll_append(objs, of); + } + + if (IS_IPADDR_V6(src)) { + endpoints_ipv6 = pcep_obj_create_endpoint_ipv6(&src->ipaddr_v6, + &dst->ipaddr_v6); + endpoints_ipv6->header.flag_p = true; + return pcep_msg_create_request_ipv6(rp, endpoints_ipv6, objs); + } else { + endpoints_ipv4 = pcep_obj_create_endpoint_ipv4(&src->ipaddr_v4, + &dst->ipaddr_v4); + endpoints_ipv4->header.flag_p = true; + return pcep_msg_create_request(rp, endpoints_ipv4, objs); + } +} + +struct pcep_message *pcep_lib_format_error(int error_type, int error_value, + struct path *path) +{ + double_linked_list *objs, *srp_tlvs; + struct pcep_object_srp *srp; + struct pcep_object_tlv_header *tlv; + + if ((path == NULL) || (path->srp_id == 0)) + return pcep_msg_create_error(error_type, error_value); + + objs = dll_initialize(); + srp_tlvs = dll_initialize(); + tlv = (struct pcep_object_tlv_header *)pcep_tlv_create_path_setup_type( + SR_TE_PST); + dll_append(srp_tlvs, tlv); + srp = pcep_obj_create_srp(path->do_remove, path->srp_id, srp_tlvs); + dll_append(objs, srp); + return pcep_msg_create_error_with_objects(error_type, error_value, + objs); +} + +struct pcep_message *pcep_lib_format_request_cancelled(uint32_t reqid) +{ + struct pcep_object_notify *notify; + double_linked_list *objs; + struct pcep_object_rp *rp; + + notify = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + objs = dll_initialize(); + rp = create_rp(reqid); + dll_append(objs, rp); + + return pcep_msg_create_notify(notify, objs); +} + +struct path *pcep_lib_parse_path(struct pcep_message *msg) +{ + struct path *path; + double_linked_list *objs = msg->obj_list; + double_linked_list_node *node; + + struct pcep_object_header *obj; + struct pcep_object_rp *rp = NULL; + struct pcep_object_srp *srp = NULL; + struct pcep_object_lsp *lsp = NULL; + struct pcep_object_lspa *lspa = NULL; + struct pcep_object_ro *ero = NULL; + struct pcep_object_metric *metric = NULL; + struct pcep_object_bandwidth *bandwidth = NULL; + struct pcep_object_objective_function *of = NULL; + struct pcep_object_endpoints_ipv4 *epv4 = NULL; + struct pcep_object_endpoints_ipv6 *epv6 = NULL; + struct pcep_object_vendor_info *vendor_info = NULL; + + path = pcep_new_path(); + + for (node = objs->head; node != NULL; node = node->next_node) { + obj = (struct pcep_object_header *)node->data; + switch (CLASS_TYPE(obj->object_class, obj->object_type)) { + case CLASS_TYPE(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP): + assert(rp == NULL); + rp = (struct pcep_object_rp *)obj; + pcep_lib_parse_rp(path, rp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP): + assert(srp == NULL); + srp = (struct pcep_object_srp *)obj; + pcep_lib_parse_srp(path, srp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP): + /* Only support single LSP per message */ + assert(lsp == NULL); + lsp = (struct pcep_object_lsp *)obj; + pcep_lib_parse_lsp(path, lsp); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA): + assert(lspa == NULL); + lspa = (struct pcep_object_lspa *)obj; + pcep_lib_parse_lspa(path, lspa); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO): + /* Only support single ERO per message */ + assert(ero == NULL); + ero = (struct pcep_object_ro *)obj; + pcep_lib_parse_ero(path, ero); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC): + metric = (struct pcep_object_metric *)obj; + pcep_lib_parse_metric(path, metric); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_REQ): + case CLASS_TYPE(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO): + bandwidth = (struct pcep_object_bandwidth *)obj; + path->has_bandwidth = true; + path->bandwidth = bandwidth->bandwidth; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH): + path->no_path = true; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF): + of = (struct pcep_object_objective_function *)obj; + path->has_pce_objfun = true; + path->pce_objfun = of->of_code; + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV4): + epv4 = (struct pcep_object_endpoints_ipv4 *)obj; + pcep_lib_parse_endpoints_ipv4(path, epv4); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV6): + epv6 = (struct pcep_object_endpoints_ipv6 *)obj; + pcep_lib_parse_endpoints_ipv6(path, epv6); + break; + case CLASS_TYPE(PCEP_OBJ_CLASS_VENDOR_INFO, + PCEP_OBJ_TYPE_VENDOR_INFO): + vendor_info = (struct pcep_object_vendor_info *)obj; + pcep_lib_parse_vendor_info(path, vendor_info); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + "Unexpected PCEP object %s (%u) / %s (%u)", + pcep_object_class_name(obj->object_class), + obj->object_class, + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + break; + } + } + + return path; +} + +void pcep_lib_parse_capabilities(struct pcep_message *msg, + struct pcep_caps *caps) +{ + double_linked_list *objs = msg->obj_list; + double_linked_list_node *node; + + struct pcep_object_header *obj; + struct pcep_object_open *open = NULL; + + for (node = objs->head; node != NULL; node = node->next_node) { + obj = (struct pcep_object_header *)node->data; + switch (CLASS_TYPE(obj->object_class, obj->object_type)) { + case CLASS_TYPE(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN): + assert(open == NULL); + open = (struct pcep_object_open *)obj; + pcep_lib_parse_open(caps, open); + break; + default: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_OBJECT, + "Unexpected PCEP object %s (%u) / %s (%u)", + pcep_object_class_name(obj->object_class), + obj->object_class, + pcep_object_type_name(obj->object_class, + obj->object_type), + obj->object_type); + break; + } + } +} + +struct counters_group *pcep_lib_copy_counters(pcep_session *sess) +{ + if (!sess || !sess->pcep_session_counters) { + return NULL; + } + + return copy_counter_group(sess->pcep_session_counters); +} + +void pcep_lib_free_counters(struct counters_group *counters) +{ + free_counter_group(counters); +} + +pcep_session *pcep_lib_copy_pcep_session(pcep_session *sess) +{ + if (!sess) { + return NULL; + } + + pcep_session *copy; + copy = XCALLOC(MTYPE_PCEP, sizeof(*copy)); + memcpy(copy, sess, sizeof(*copy)); + /* These fields should not be accessed */ + copy->num_unknown_messages_time_queue = NULL; + copy->socket_comm_session = NULL; + copy->pcep_session_counters = NULL; + + return copy; +} + +/* ------------ pceplib logging callback ------------ */ + +int pceplib_logging_cb(int priority, const char *fmt, va_list args) +{ + char buffer[1024]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + PCEP_DEBUG_PCEPLIB(priority, "pceplib: %s", buffer); + return 0; +} + +/* ------------ Internal Functions ------------ */ + +double_linked_list *pcep_lib_format_path(struct pcep_caps *caps, + struct path *path) +{ + struct in_addr addr_null; + double_linked_list *objs, *srp_tlvs, *lsp_tlvs, *ero_objs; + struct pcep_object_tlv_header *tlv; + struct pcep_object_ro_subobj *ero_obj; + struct pcep_object_srp *srp; + struct pcep_object_lsp *lsp; + struct pcep_object_ro *ero; + uint32_t encoded_binding_sid; + char binding_sid_lsp_tlv_data[6]; + + memset(&addr_null, 0, sizeof(addr_null)); + + objs = dll_initialize(); + + if (path->plsp_id != 0) { + /* SRP object */ + srp_tlvs = dll_initialize(); + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_path_setup_type(SR_TE_PST); + assert(tlv != NULL); + dll_append(srp_tlvs, tlv); + srp = pcep_obj_create_srp(path->do_remove, path->srp_id, + srp_tlvs); + assert(srp != NULL); + srp->header.flag_p = true; + dll_append(objs, srp); + } + + /* LSP object */ + lsp_tlvs = dll_initialize(); + + if (path->plsp_id == 0 || IS_IPADDR_NONE(&path->nbkey.endpoint) + || IS_IPADDR_NONE(&path->pcc_addr)) { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv4_lsp_identifiers( + &addr_null, &addr_null, 0, 0, &addr_null); + } else { + assert(path->pcc_addr.ipa_type + == path->nbkey.endpoint.ipa_type); + if (IS_IPADDR_V4(&path->pcc_addr)) { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv4_lsp_identifiers( + &path->pcc_addr.ipaddr_v4, + &path->nbkey.endpoint.ipaddr_v4, 0, 0, + &path->pcc_addr.ipaddr_v4); + } else { + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_ipv6_lsp_identifiers( + &path->pcc_addr.ipaddr_v6, + &path->nbkey.endpoint.ipaddr_v6, 0, 0, + &path->pcc_addr.ipaddr_v6); + } + } + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + if (path->name != NULL) { + tlv = (struct pcep_object_tlv_header *) + /*FIXME: Remove the typecasty when pceplib is changed + to take a const char* */ + pcep_tlv_create_symbolic_path_name((char *)path->name, + strlen(path->name)); + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + } + if ((path->plsp_id != 0) && (path->binding_sid != MPLS_LABEL_NONE)) { + memset(binding_sid_lsp_tlv_data, 0, 2); + encoded_binding_sid = htonl(path->binding_sid << 12); + memcpy(binding_sid_lsp_tlv_data + 2, &encoded_binding_sid, 4); + tlv = (struct pcep_object_tlv_header *) + pcep_tlv_create_tlv_arbitrary( + binding_sid_lsp_tlv_data, + sizeof(binding_sid_lsp_tlv_data), + PCEP_OBJ_TYPE_CISCO_BSID); + assert(tlv != NULL); + dll_append(lsp_tlvs, tlv); + } + lsp = pcep_obj_create_lsp( + path->plsp_id, path->status, path->was_created /* C Flag */, + path->go_active /* A Flag */, path->was_removed /* R Flag */, + path->is_synching /* S Flag */, path->is_delegated /* D Flag */, + lsp_tlvs); + assert(lsp != NULL); + lsp->header.flag_p = true; + dll_append(objs, lsp); + /* ERO object */ + ero_objs = dll_initialize(); + for (struct path_hop *hop = path->first_hop; hop != NULL; + hop = hop->next) { + uint32_t sid; + + /* Only supporting MPLS hops with both sid and nai */ + assert(hop->is_mpls); + assert(hop->has_sid); + + if (hop->has_attribs) { + sid = ENCODE_SR_ERO_SID(hop->sid.mpls.label, + hop->sid.mpls.traffic_class, + hop->sid.mpls.is_bottom, + hop->sid.mpls.ttl); + } else { + sid = ENCODE_SR_ERO_SID(hop->sid.mpls.label, 0, 0, 0); + } + + ero_obj = NULL; + if (hop->has_nai) { + assert(hop->nai.type != PCEP_SR_SUBOBJ_NAI_ABSENT); + assert(hop->nai.type + != PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY); + assert(hop->nai.type != PCEP_SR_SUBOBJ_NAI_UNKNOWN); + switch (hop->nai.type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv4_node( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv6_node( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv4_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v4, + &hop->nai.remote_addr + .ipaddr_v4); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_ipv6_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + &hop->nai.local_addr.ipaddr_v6, + &hop->nai.remote_addr + .ipaddr_v6); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + hop->is_loose, !hop->has_sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls, /* M Flag */ + sid, + hop->nai.local_addr.ipaddr_v4 + .s_addr, + hop->nai.local_iface, + hop->nai.remote_addr.ipaddr_v4 + .s_addr, + hop->nai.remote_iface); + break; + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + break; + } + } + if (ero_obj == NULL) { + ero_obj = (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_sr_nonai( + hop->is_loose, sid, + hop->has_attribs, /* C Flag */ + hop->is_mpls); /* M Flag */ + } + dll_append(ero_objs, ero_obj); + } + ero = pcep_obj_create_ero(ero_objs); + assert(ero != NULL); + ero->header.flag_p = true; + dll_append(objs, ero); + + if (path->plsp_id == 0) { + return objs; + } + + pcep_lib_format_constraints(path, objs); + + return objs; +} + +void pcep_lib_format_constraints(struct path *path, double_linked_list *objs) +{ + struct pcep_object_metric *metric; + struct pcep_object_bandwidth *bandwidth; + struct pcep_object_lspa *lspa; + + /* LSPA object */ + if (path->has_affinity_filters) { + lspa = pcep_obj_create_lspa( + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1], + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1], + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1], + DEFAULT_LSAP_SETUP_PRIO, DEFAULT_LSAP_HOLDING_PRIO, + DEFAULT_LSAP_LOCAL_PRETECTION); + assert(lspa != NULL); + lspa->header.flag_p = true; + dll_append(objs, lspa); + } + + /* Bandwidth Objects */ + if (path->has_bandwidth) { + /* Requested Bandwidth */ + bandwidth = pcep_obj_create_bandwidth(path->bandwidth); + assert(bandwidth != NULL); + bandwidth->header.flag_p = path->enforce_bandwidth; + dll_append(objs, bandwidth); + } + + /* Metric Objects */ + for (struct path_metric *m = path->first_metric; m != NULL; + m = m->next) { + metric = pcep_obj_create_metric(m->type, m->is_bound, + m->is_computed, m->value); + assert(metric != NULL); + metric->header.flag_p = m->enforce; + dll_append(objs, metric); + } +} + +void pcep_lib_parse_open(struct pcep_caps *caps, struct pcep_object_open *open) +{ + double_linked_list *tlvs = open->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv_header; + + caps->is_stateful = false; + caps->supported_ofs_are_known = false; + caps->supported_ofs = 0; + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv_header = (struct pcep_object_tlv_header *)node->data; + switch (tlv_header->type) { + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + pcep_lib_parse_open_pce_capability(caps, tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + break; + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + pcep_lib_parse_open_objfun_list(caps, tlv_header); + break; + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + case PCEP_OBJ_TYPE_CISCO_BSID: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected OPEN's TLV %s (%u)", + pcep_tlv_type_name(tlv_header->type), + tlv_header->type); + break; + } + } +} + +void pcep_lib_parse_open_pce_capability( + struct pcep_caps *caps, struct pcep_object_tlv_header *tlv_header) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv; + tlv = (struct pcep_object_tlv_stateful_pce_capability *)tlv_header; + caps->is_stateful = tlv->flag_u_lsp_update_capability; +} + +void pcep_lib_parse_open_objfun_list(struct pcep_caps *caps, + struct pcep_object_tlv_header *tlv_header) +{ + double_linked_list_node *node; + struct pcep_object_tlv_of_list *tlv; + tlv = (struct pcep_object_tlv_of_list *)tlv_header; + uint16_t of_code; + caps->supported_ofs_are_known = true; + for (node = tlv->of_list->head; node != NULL; node = node->next_node) { + of_code = *(uint16_t *)node->data; + if (of_code >= 32) { + zlog_warn( + "Ignoring unexpected objective function with code %u", + of_code); + continue; + } + SET_FLAG(caps->supported_ofs, of_code); + } +} + +void pcep_lib_parse_rp(struct path *path, struct pcep_object_rp *rp) +{ + double_linked_list *tlvs = rp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty RP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + /* We ignore the other flags and priority for now */ + path->req_id = rp->request_id; + path->has_pce_objfun = false; + path->pce_objfun = OBJFUN_UNDEFINED; + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + // TODO: enforce the path setup type is SR_TE_PST + break; + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + case PCEP_OBJ_TYPE_CISCO_BSID: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected RP's TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_srp(struct path *path, struct pcep_object_srp *srp) +{ + double_linked_list *tlvs = srp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + + path->do_remove = srp->flag_lsp_remove; + path->srp_id = srp->srp_id_number; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty SRP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + // TODO: enforce the path setup type is SR_TE_PST + break; + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TYPE_CISCO_BSID: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected SRP's TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_lsp(struct path *path, struct pcep_object_lsp *lsp) +{ + double_linked_list *tlvs = lsp->header.tlv_list; + double_linked_list_node *node; + struct pcep_object_tlv_header *tlv; + struct pcep_object_tlv_symbolic_path_name *name; + struct pcep_object_tlv_arbitrary *arb_tlv; + + path->plsp_id = lsp->plsp_id; + path->status = lsp->operational_status; + path->go_active = lsp->flag_a; + path->was_created = lsp->flag_c; + path->was_removed = lsp->flag_r; + path->is_synching = lsp->flag_s; + path->is_delegated = lsp->flag_d; + + if (tlvs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty LSP's TLV plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + + for (node = tlvs->head; node != NULL; node = node->next_node) { + tlv = (struct pcep_object_tlv_header *)node->data; + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + name = (struct pcep_object_tlv_symbolic_path_name *)tlv; + pcep_lib_parse_lsp_symbolic_name(path, name); + break; + case PCEP_OBJ_TYPE_CISCO_BSID: + arb_tlv = (struct pcep_object_tlv_arbitrary *)tlv; + memcpy(&path->binding_sid, arb_tlv->data + 2, + sizeof(path->binding_sid)); + path->binding_sid = ntohl(path->binding_sid); + path->binding_sid = (path->binding_sid >> 12); + break; + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected LSP TLV %s (%u)", + pcep_tlv_type_name(tlv->type), tlv->type); + break; + } + } +} + +void pcep_lib_parse_lsp_symbolic_name( + struct path *path, struct pcep_object_tlv_symbolic_path_name *tlv) +{ + uint16_t size = tlv->symbolic_path_name_length; + assert(path->name == NULL); + size = size > MAX_PATH_NAME_SIZE ? MAX_PATH_NAME_SIZE : size; + path->name = XCALLOC(MTYPE_PCEP, size); + strlcpy((char *)path->name, tlv->symbolic_path_name, size + 1); +} + +void pcep_lib_parse_lspa(struct path *path, struct pcep_object_lspa *lspa) +{ + path->has_affinity_filters = true; + path->affinity_filters[AFFINITY_FILTER_EXCLUDE_ANY - 1] = + lspa->lspa_exclude_any; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ANY - 1] = + lspa->lspa_include_any; + path->affinity_filters[AFFINITY_FILTER_INCLUDE_ALL - 1] = + lspa->lspa_include_all; +} + +void pcep_lib_parse_metric(struct path *path, struct pcep_object_metric *obj) +{ + struct path_metric *metric; + + metric = pcep_new_metric(); + metric->type = obj->type; + metric->is_bound = obj->flag_b; + metric->is_computed = obj->flag_c; + metric->value = obj->value; + metric->next = path->first_metric; + path->first_metric = metric; +} + +void pcep_lib_parse_endpoints_ipv4(struct path *path, + struct pcep_object_endpoints_ipv4 *obj) +{ + SET_IPADDR_V4(&path->pcc_addr); + path->pcc_addr.ipaddr_v4 = obj->src_ipv4; + SET_IPADDR_V4(&path->nbkey.endpoint); + path->nbkey.endpoint.ipaddr_v4 = obj->dst_ipv4; +} + +void pcep_lib_parse_endpoints_ipv6(struct path *path, + struct pcep_object_endpoints_ipv6 *obj) +{ + SET_IPADDR_V6(&path->pcc_addr); + path->pcc_addr.ipaddr_v6 = obj->src_ipv6; + SET_IPADDR_V6(&path->nbkey.endpoint); + path->nbkey.endpoint.ipaddr_v6 = obj->dst_ipv6; +} + +void pcep_lib_parse_vendor_info(struct path *path, + struct pcep_object_vendor_info *obj) +{ + if (obj->enterprise_number == ENTERPRISE_NUMBER_CISCO + && obj->enterprise_specific_info == ENTERPRISE_COLOR_CISCO) + path->nbkey.color = obj->enterprise_specific_info1; + else + path->nbkey.color = 0; +} + +void pcep_lib_parse_ero(struct path *path, struct pcep_object_ro *ero) +{ + struct path_hop *hop = NULL; + double_linked_list *objs = ero->sub_objects; + double_linked_list_node *node; + struct pcep_object_ro_subobj *obj; + + if (objs == NULL) { + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_TLV, + "Unexpected Empty ERO's sub_obj plsp-id:(%d)", + path ? (int32_t)path->plsp_id : -1); + return; + } + for (node = objs->tail; node != NULL; node = node->prev_node) { + obj = (struct pcep_object_ro_subobj *)node->data; + switch (obj->ro_subobj_type) { + case RO_SUBOBJ_TYPE_SR: + hop = pcep_lib_parse_ero_sr( + hop, (struct pcep_ro_subobj_sr *)obj); + break; + case RO_SUBOBJ_TYPE_IPV4: + case RO_SUBOBJ_TYPE_IPV6: + case RO_SUBOBJ_TYPE_LABEL: + case RO_SUBOBJ_TYPE_UNNUM: + case RO_SUBOBJ_TYPE_ASN: + case RO_SUBOBJ_UNKNOWN: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_ERO_SUBOBJ, + "Unexpected ERO sub-object %s (%u)", + pcep_ro_type_name(obj->ro_subobj_type), + obj->ro_subobj_type); + break; + } + } + + path->first_hop = hop; +} + +struct path_hop *pcep_lib_parse_ero_sr(struct path_hop *next, + struct pcep_ro_subobj_sr *sr) +{ + struct path_hop *hop = NULL; + union sid sid; + + /* Only support IPv4 node with SID */ + assert(!sr->flag_s); + + if (sr->flag_m) { + sid.mpls = (struct sid_mpls){ + .label = GET_SR_ERO_SID_LABEL(sr->sid), + .traffic_class = GET_SR_ERO_SID_TC(sr->sid), + .is_bottom = GET_SR_ERO_SID_S(sr->sid), + .ttl = GET_SR_ERO_SID_TTL(sr->sid)}; + } else { + sid.value = sr->sid; + } + + hop = pcep_new_hop(); + *hop = (struct path_hop){.next = next, + .is_loose = + sr->ro_subobj.flag_subobj_loose_hop, + .has_sid = !sr->flag_s, + .is_mpls = sr->flag_m, + .has_attribs = sr->flag_c, + .sid = sid, + .has_nai = !sr->flag_f, + .nai = {.type = sr->nai_type}}; + + if (!sr->flag_f) { + assert(sr->nai_list != NULL); + double_linked_list_node *n = sr->nai_list->head; + assert(n != NULL); + assert(n->data != NULL); + switch (sr->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + hop->nai.local_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.local_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.remote_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.local_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V6; + memcpy(&hop->nai.remote_addr.ipaddr_v6, n->data, + sizeof(struct in6_addr)); + break; + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + hop->nai.local_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.local_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.local_iface = *(uint32_t *)n->data; + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_addr.ipa_type = IPADDR_V4; + memcpy(&hop->nai.remote_addr.ipaddr_v4, n->data, + sizeof(struct in_addr)); + n = n->next_node; + assert(n != NULL); + assert(n->data != NULL); + hop->nai.remote_iface = *(uint32_t *)n->data; + break; + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + hop->has_nai = false; + flog_warn(EC_PATH_PCEP_UNEXPECTED_SR_NAI, + "Unexpected SR segment NAI type %s (%u)", + pcep_nai_type_name(sr->nai_type), + sr->nai_type); + break; + } + } + + return hop; +} + +struct counters_group *copy_counter_group(struct counters_group *from) +{ + int size, i; + struct counters_group *result; + if (from == NULL) + return NULL; + assert(from->max_subgroups >= from->num_subgroups); + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + size = sizeof(struct counters_subgroup *) * (from->max_subgroups + 1); + result->subgroups = XCALLOC(MTYPE_PCEP, size); + for (i = 0; i <= from->max_subgroups; i++) + result->subgroups[i] = + copy_counter_subgroup(from->subgroups[i]); + return result; +} + +struct counters_subgroup *copy_counter_subgroup(struct counters_subgroup *from) +{ + int size, i; + struct counters_subgroup *result; + if (from == NULL) + return NULL; + assert(from->max_counters >= from->num_counters); + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + size = sizeof(struct counter *) * (from->max_counters + 1); + result->counters = XCALLOC(MTYPE_PCEP, size); + for (i = 0; i <= from->max_counters; i++) + result->counters[i] = copy_counter(from->counters[i]); + return result; +} + +struct counter *copy_counter(struct counter *from) +{ + struct counter *result; + if (from == NULL) + return NULL; + result = XCALLOC(MTYPE_PCEP, sizeof(*result)); + memcpy(result, from, sizeof(*result)); + return result; +} + +void free_counter_group(struct counters_group *group) +{ + int i; + if (group == NULL) + return; + for (i = 0; i <= group->max_subgroups; i++) + free_counter_subgroup(group->subgroups[i]); + XFREE(MTYPE_PCEP, group->subgroups); + XFREE(MTYPE_PCEP, group); +} + +void free_counter_subgroup(struct counters_subgroup *subgroup) +{ + int i; + if (subgroup == NULL) + return; + for (i = 0; i <= subgroup->max_counters; i++) + free_counter(subgroup->counters[i]); + XFREE(MTYPE_PCEP, subgroup->counters); + XFREE(MTYPE_PCEP, subgroup); +} + +void free_counter(struct counter *counter) +{ + if (counter == NULL) + return; + XFREE(MTYPE_PCEP, counter); +} diff --git a/pathd/path_pcep_lib.h b/pathd/path_pcep_lib.h new file mode 100644 index 0000000..6bba344 --- /dev/null +++ b/pathd/path_pcep_lib.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_LIB_H_ +#define _PATH_PCEP_LIB_H_ + +#include +#include "pceplib/pcep_pcc_api.h" +#include "frr_pthread.h" +#include "pathd/path_pcep.h" + +int pcep_lib_initialize(struct frr_pthread *fpt); +void pcep_lib_finalize(void); +pcep_session * +pcep_lib_connect(struct ipaddr *src_addr, int src_port, struct ipaddr *dst_addr, + int dst_port, short msd, + const struct pcep_config_group_opts *pcep_options); +void pcep_lib_disconnect(pcep_session *sess); +struct pcep_message *pcep_lib_format_report(struct pcep_caps *caps, + struct path *path); +struct pcep_message *pcep_lib_format_request(struct pcep_caps *caps, + struct path *path); +struct pcep_message *pcep_lib_format_request_cancelled(uint32_t reqid); + +struct pcep_message *pcep_lib_format_error(int error_type, int error_value, + struct path *path); +struct path *pcep_lib_parse_path(struct pcep_message *msg); +void pcep_lib_parse_capabilities(struct pcep_message *msg, + struct pcep_caps *caps); +struct counters_group *pcep_lib_copy_counters(pcep_session *sess); +void pcep_lib_free_counters(struct counters_group *counters); +pcep_session *pcep_lib_copy_pcep_session(pcep_session *sess); + +#endif // _PATH_PCEP_LIB_H_ diff --git a/pathd/path_pcep_pcc.c b/pathd/path_pcep_pcc.c new file mode 100644 index 0000000..f18eff2 --- /dev/null +++ b/pathd/path_pcep_pcc.c @@ -0,0 +1,2001 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +/* TODOS AND KNOWN ISSUES: + - Delete mapping from NB keys to PLSPID when an LSP is deleted either + by the PCE or by NB. + - Revert the hacks to work around ODL requiring a report with + operational status DOWN when an LSP is activated. + - Enforce only the PCE a policy has been delegated to can update it. + - If the router-id is used because the PCC IP is not specified + (either IPv4 or IPv6), the connection to the PCE is not reset + when the router-id changes. +*/ + +#include + +#include "log.h" +#include "command.h" +#include "libfrr.h" +#include "printfrr.h" +#include "northbound.h" +#include "frr_pthread.h" +#include "jhash.h" + +#include "pathd/pathd.h" +#include "pathd/path_zebra.h" +#include "pathd/path_errors.h" +#include "pathd/path_pcep.h" +#include "pathd/path_pcep_controller.h" +#include "pathd/path_pcep_lib.h" +#include "pathd/path_pcep_config.h" +#include "pathd/path_pcep_debug.h" + + +/* The number of time we will skip connecting if we are missing the PCC + * address for an inet family different from the selected transport one*/ +#define OTHER_FAMILY_MAX_RETRIES 4 +#define MAX_ERROR_MSG_SIZE 256 +#define MAX_COMPREQ_TRIES 3 + +pthread_mutex_t g_pcc_info_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* PCEP Event Handler */ +static void handle_pcep_open(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_message(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); +static void continue_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct path *path, void *payload); +static void handle_pcep_comp_reply(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg); + +/* Internal Functions */ +static const char *ipaddr_type_name(struct ipaddr *addr); +static bool filter_path(struct pcc_state *pcc_state, struct path *path); +static void select_pcc_addresses(struct pcc_state *pcc_state); +static void select_transport_address(struct pcc_state *pcc_state); +static void update_tag(struct pcc_state *pcc_state); +static void update_originator(struct pcc_state *pcc_state); +static void schedule_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void schedule_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void cancel_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void send_pcep_message(struct pcc_state *pcc_state, + struct pcep_message *msg); +static void send_pcep_error(struct pcc_state *pcc_state, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct path *trigger_path); +static void send_report(struct pcc_state *pcc_state, struct path *path); +static void send_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct req_entry *req); +static void cancel_comp_requests(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +static void cancel_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct req_entry *req); +static void specialize_outgoing_path(struct pcc_state *pcc_state, + struct path *path); +static void specialize_incoming_path(struct pcc_state *pcc_state, + struct path *path); +static bool validate_incoming_path(struct pcc_state *pcc_state, + struct path *path, char *errbuff, + size_t buffsize); +static void set_pcc_address(struct pcc_state *pcc_state, + struct lsp_nb_key *nbkey, struct ipaddr *addr); +static int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs); +static int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs); +static int get_previous_best_pce(struct pcc_state **pcc); +static int get_best_pce(struct pcc_state **pcc); +static int get_pce_count_connected(struct pcc_state **pcc); +static bool update_best_pce(struct pcc_state **pcc, int best); + +/* Data Structure Helper Functions */ +static void lookup_plspid(struct pcc_state *pcc_state, struct path *path); +static void lookup_nbkey(struct pcc_state *pcc_state, struct path *path); +static void free_req_entry(struct req_entry *req); +static struct req_entry *push_new_req(struct pcc_state *pcc_state, + struct path *path); +static void repush_req(struct pcc_state *pcc_state, struct req_entry *req); +static struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid); +static struct req_entry *pop_req_no_reqid(struct pcc_state *pcc_state, + uint32_t reqid); +static bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path); +static void remove_reqid_mapping(struct pcc_state *pcc_state, + struct path *path); +static uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path); +static bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path); + +/* Data Structure Callbacks */ +static int plspid_map_cmp(const struct plspid_map_data *a, + const struct plspid_map_data *b); +static uint32_t plspid_map_hash(const struct plspid_map_data *e); +static int nbkey_map_cmp(const struct nbkey_map_data *a, + const struct nbkey_map_data *b); +static uint32_t nbkey_map_hash(const struct nbkey_map_data *e); +static int req_map_cmp(const struct req_map_data *a, + const struct req_map_data *b); +static uint32_t req_map_hash(const struct req_map_data *e); + +/* Data Structure Declarations */ +DECLARE_HASH(plspid_map, struct plspid_map_data, mi, plspid_map_cmp, + plspid_map_hash); +DECLARE_HASH(nbkey_map, struct nbkey_map_data, mi, nbkey_map_cmp, + nbkey_map_hash); +DECLARE_HASH(req_map, struct req_map_data, mi, req_map_cmp, req_map_hash); + +static inline int req_entry_compare(const struct req_entry *a, + const struct req_entry *b) +{ + return a->path->req_id - b->path->req_id; +} +RB_GENERATE(req_entry_head, req_entry, entry, req_entry_compare) + + +/* ------------ API Functions ------------ */ + +struct pcc_state *pcep_pcc_initialize(struct ctrl_state *ctrl_state, int index) +{ + struct pcc_state *pcc_state = XCALLOC(MTYPE_PCEP, sizeof(*pcc_state)); + + pcc_state->id = index; + pcc_state->status = PCEP_PCC_DISCONNECTED; + pcc_state->next_reqid = 1; + pcc_state->next_plspid = 1; + + RB_INIT(req_entry_head, &pcc_state->requests); + + update_tag(pcc_state); + update_originator(pcc_state); + + PCEP_DEBUG("%s PCC initialized", pcc_state->tag); + + return pcc_state; +} + +void pcep_pcc_finalize(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + PCEP_DEBUG("%s PCC finalizing...", pcc_state->tag); + + pcep_pcc_disable(ctrl_state, pcc_state); + + if (pcc_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pcc_opts); + pcc_state->pcc_opts = NULL; + } + if (pcc_state->pce_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pce_opts); + pcc_state->pce_opts = NULL; + } + if (pcc_state->originator != NULL) { + XFREE(MTYPE_PCEP, pcc_state->originator); + pcc_state->originator = NULL; + } + + if (pcc_state->t_reconnect != NULL) { + event_cancel(&pcc_state->t_reconnect); + pcc_state->t_reconnect = NULL; + } + + if (pcc_state->t_update_best != NULL) { + event_cancel(&pcc_state->t_update_best); + pcc_state->t_update_best = NULL; + } + + if (pcc_state->t_session_timeout != NULL) { + event_cancel(&pcc_state->t_session_timeout); + pcc_state->t_session_timeout = NULL; + } + + XFREE(MTYPE_PCEP, pcc_state); +} + +int compare_pcc_opts(struct pcc_opts *lhs, struct pcc_opts *rhs) +{ + int retval; + + if (lhs == NULL) { + return 1; + } + + if (rhs == NULL) { + return -1; + } + + retval = lhs->port - rhs->port; + if (retval != 0) { + return retval; + } + + retval = lhs->msd - rhs->msd; + if (retval != 0) { + return retval; + } + + if (IS_IPADDR_V4(&lhs->addr)) { + retval = memcmp(&lhs->addr.ipaddr_v4, &rhs->addr.ipaddr_v4, + sizeof(lhs->addr.ipaddr_v4)); + if (retval != 0) { + return retval; + } + } else if (IS_IPADDR_V6(&lhs->addr)) { + retval = memcmp(&lhs->addr.ipaddr_v6, &rhs->addr.ipaddr_v6, + sizeof(lhs->addr.ipaddr_v6)); + if (retval != 0) { + return retval; + } + } + + return 0; +} + +int compare_pce_opts(struct pce_opts *lhs, struct pce_opts *rhs) +{ + if (lhs == NULL) { + return 1; + } + + if (rhs == NULL) { + return -1; + } + + int retval = lhs->port - rhs->port; + if (retval != 0) { + return retval; + } + + retval = strcmp(lhs->pce_name, rhs->pce_name); + if (retval != 0) { + return retval; + } + + retval = lhs->precedence - rhs->precedence; + if (retval != 0) { + return retval; + } + + retval = memcmp(&lhs->addr, &rhs->addr, sizeof(lhs->addr)); + if (retval != 0) { + return retval; + } + + return 0; +} + +int pcep_pcc_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, + struct pcc_opts *pcc_opts, struct pce_opts *pce_opts) +{ + int ret = 0; + + // If the options did not change, then there is nothing to do + if ((compare_pce_opts(pce_opts, pcc_state->pce_opts) == 0) + && (compare_pcc_opts(pcc_opts, pcc_state->pcc_opts) == 0)) { + return ret; + } + + if ((ret = pcep_pcc_disable(ctrl_state, pcc_state))) { + XFREE(MTYPE_PCEP, pcc_opts); + XFREE(MTYPE_PCEP, pce_opts); + return ret; + } + + if (pcc_state->pcc_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pcc_opts); + } + if (pcc_state->pce_opts != NULL) { + XFREE(MTYPE_PCEP, pcc_state->pce_opts); + } + + pcc_state->pcc_opts = pcc_opts; + pcc_state->pce_opts = pce_opts; + + if (IS_IPADDR_V4(&pcc_opts->addr)) { + pcc_state->pcc_addr_v4 = pcc_opts->addr.ipaddr_v4; + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } else { + UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } + + if (IS_IPADDR_V6(&pcc_opts->addr)) { + memcpy(&pcc_state->pcc_addr_v6, &pcc_opts->addr.ipaddr_v6, + sizeof(struct in6_addr)); + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } else { + UNSET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } + + update_tag(pcc_state); + update_originator(pcc_state); + + return pcep_pcc_enable(ctrl_state, pcc_state); +} + +void pcep_pcc_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + if (pcc_state->status == PCEP_PCC_DISCONNECTED) + pcep_pcc_enable(ctrl_state, pcc_state); +} + +int pcep_pcc_enable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + assert(pcc_state->status == PCEP_PCC_DISCONNECTED); + assert(pcc_state->sess == NULL); + + if (pcc_state->t_reconnect != NULL) { + event_cancel(&pcc_state->t_reconnect); + pcc_state->t_reconnect = NULL; + } + + select_transport_address(pcc_state); + + /* Even though we are connecting using IPv6. we want to have an IPv4 + * address so we can handle candidate path with IPv4 endpoints */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %pIA:%d due to missing PCC IPv4 address", + &pcc_state->pce_opts->addr, + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } else { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "missing IPv4 PCC address, IPv4 candidate paths will be ignored"); + } + } + + /* Even though we are connecting using IPv4. we want to have an IPv6 + * address so we can handle candidate path with IPv6 endpoints */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + if (pcc_state->retry_count < OTHER_FAMILY_MAX_RETRIES) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %pIA:%d due to missing PCC IPv6 address", + &pcc_state->pce_opts->addr, + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } else { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "missing IPv6 PCC address, IPv6 candidate paths will be ignored"); + } + } + + /* Even if the maximum retries to try to have all the familly addresses + * have been spent, we still need the one for the transport familly */ + if (pcc_state->pcc_addr_tr.ipa_type == IPADDR_NONE) { + flog_warn(EC_PATH_PCEP_MISSING_SOURCE_ADDRESS, + "skipping connection to PCE %pIA:%d due to missing PCC address", + &pcc_state->pce_opts->addr, + pcc_state->pce_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } + + PCEP_DEBUG("%s PCC connecting", pcc_state->tag); + pcc_state->sess = pcep_lib_connect( + &pcc_state->pcc_addr_tr, pcc_state->pcc_opts->port, + &pcc_state->pce_opts->addr, pcc_state->pce_opts->port, + pcc_state->pcc_opts->msd, &pcc_state->pce_opts->config_opts); + + if (pcc_state->sess == NULL) { + flog_warn(EC_PATH_PCEP_LIB_CONNECT, + "failed to connect to PCE %pIA:%d from %pIA:%d", + &pcc_state->pce_opts->addr, + pcc_state->pce_opts->port, + &pcc_state->pcc_addr_tr, + pcc_state->pcc_opts->port); + schedule_reconnect(ctrl_state, pcc_state); + return 0; + } + + // In case some best pce alternative were waiting to activate + if (pcc_state->t_update_best != NULL) { + event_cancel(&pcc_state->t_update_best); + pcc_state->t_update_best = NULL; + } + + pcc_state->status = PCEP_PCC_CONNECTING; + + return 0; +} + +int pcep_pcc_disable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state) +{ + switch (pcc_state->status) { + case PCEP_PCC_DISCONNECTED: + return 0; + case PCEP_PCC_CONNECTING: + case PCEP_PCC_SYNCHRONIZING: + case PCEP_PCC_OPERATING: + PCEP_DEBUG("%s Disconnecting PCC...", pcc_state->tag); + cancel_comp_requests(ctrl_state, pcc_state); + pcep_lib_disconnect(pcc_state->sess); + /* No need to remove if any PCEs is connected */ + if (get_pce_count_connected(ctrl_state->pcc) == 0) { + pcep_thread_remove_candidate_path_segments(ctrl_state, + pcc_state); + } + pcc_state->sess = NULL; + pcc_state->status = PCEP_PCC_DISCONNECTED; + return 0; + case PCEP_PCC_INITIALIZED: + return 1; + } + + assert(!"Reached end of function where we are not expecting to"); +} + +void pcep_pcc_sync_path(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path) +{ + if (pcc_state->status == PCEP_PCC_SYNCHRONIZING) { + path->is_synching = true; + } else if (pcc_state->status == PCEP_PCC_OPERATING) + path->is_synching = false; + else + return; + + path->go_active = true; + + /* Accumulate the dynamic paths without any LSP so computation + * requests can be performed after synchronization */ + if ((path->type == SRTE_CANDIDATE_TYPE_DYNAMIC) + && (path->first_hop == NULL) + && !has_pending_req_for(pcc_state, path)) { + PCEP_DEBUG("%s Scheduling computation request for path %s", + pcc_state->tag, path->name); + push_new_req(pcc_state, path); + return; + } + + /* Synchronize the path if the PCE supports LSP updates and the + * endpoint address familly is supported */ + if (pcc_state->caps.is_stateful) { + if (filter_path(pcc_state, path)) { + PCEP_DEBUG("%s Synchronizing path %s", pcc_state->tag, + path->name); + send_report(pcc_state, path); + } else { + PCEP_DEBUG( + "%s Skipping %s candidate path %s synchronization", + pcc_state->tag, + ipaddr_type_name(&path->nbkey.endpoint), + path->name); + } + } +} + +void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + struct req_entry *req; + + if (pcc_state->status != PCEP_PCC_SYNCHRONIZING + && pcc_state->status != PCEP_PCC_OPERATING) + return; + + if (pcc_state->caps.is_stateful + && pcc_state->status == PCEP_PCC_SYNCHRONIZING) { + struct path *path = pcep_new_path(); + *path = (struct path){.name = NULL, + .srp_id = 0, + .plsp_id = 0, + .status = PCEP_LSP_OPERATIONAL_DOWN, + .do_remove = false, + .go_active = false, + .was_created = false, + .was_removed = false, + .is_synching = false, + .is_delegated = false, + .first_hop = NULL, + .first_metric = NULL}; + send_report(pcc_state, path); + pcep_free_path(path); + } + + pcc_state->synchronized = true; + pcc_state->status = PCEP_PCC_OPERATING; + + PCEP_DEBUG("%s Synchronization done", pcc_state->tag); + + /* Start the computation request accumulated during synchronization */ + RB_FOREACH (req, req_entry_head, &pcc_state->requests) { + send_comp_request(ctrl_state, pcc_state, req); + } +} + +void pcep_pcc_send_report(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + bool is_stable) +{ + if ((pcc_state->status != PCEP_PCC_OPERATING) + || (!pcc_state->caps.is_stateful)) { + pcep_free_path(path); + return; + } + + PCEP_DEBUG("(%s)%s Send report for candidate path %s", __func__, + pcc_state->tag, path->name); + + /* ODL and Cisco requires the first reported + * LSP to have a DOWN status, the later status changes + * will be comunicated through hook calls. + */ + enum pcep_lsp_operational_status real_status = path->status; + path->status = PCEP_LSP_OPERATIONAL_DOWN; + send_report(pcc_state, path); + + /* If no update is expected and the real status wasn't down, we need to + * send a second report with the real status */ + if (is_stable && (real_status != PCEP_LSP_OPERATIONAL_DOWN)) { + PCEP_DEBUG("(%s)%s Send report for candidate path (!DOWN) %s", + __func__, pcc_state->tag, path->name); + path->status = real_status; + send_report(pcc_state, path); + } + + pcep_free_path(path); +} + + +void pcep_pcc_send_error(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_error *error, + bool sub_type) +{ + + PCEP_DEBUG("(%s) Send error after PcInitiated ", __func__); + + + send_pcep_error(pcc_state, error->error_type, error->error_value, + error->path); + pcep_free_path(error->path); + XFREE(MTYPE_PCEP, error); +} +/* ------------ Timeout handler ------------ */ + +void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_ctrl_timeout_type type, void *param) +{ + struct req_entry *req; + + switch (type) { + case TO_COMPUTATION_REQUEST: + assert(param != NULL); + req = (struct req_entry *)param; + pop_req(pcc_state, req->path->req_id); + flog_warn(EC_PATH_PCEP_COMPUTATION_REQUEST_TIMEOUT, + "Computation request %d timeout", req->path->req_id); + cancel_comp_request(ctrl_state, pcc_state, req); + if (req->retry_count++ < MAX_COMPREQ_TRIES) { + repush_req(pcc_state, req); + send_comp_request(ctrl_state, pcc_state, req); + return; + } + if (pcc_state->caps.is_stateful) { + struct path *path; + PCEP_DEBUG( + "%s Delegating undefined dynamic path %s to PCE %s", + pcc_state->tag, req->path->name, + pcc_state->originator); + path = pcep_copy_path(req->path); + path->is_delegated = true; + send_report(pcc_state, path); + free_req_entry(req); + } + break; + case TO_UNDEFINED: + case TO_MAX: + break; + } +} + + +/* ------------ Pathd event handler ------------ */ + +void pcep_pcc_pathd_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_pathd_event_type type, + struct path *path) +{ + struct req_entry *req; + + if (pcc_state->status != PCEP_PCC_OPERATING) + return; + + /* Skipping candidate path with endpoint that do not match the + * configured or deduced PCC IP version */ + if (!filter_path(pcc_state, path)) { + PCEP_DEBUG("%s Skipping %s candidate path %s event", + pcc_state->tag, + ipaddr_type_name(&path->nbkey.endpoint), path->name); + return; + } + + switch (type) { + case PCEP_PATH_CREATED: + if (has_pending_req_for(pcc_state, path)) { + PCEP_DEBUG( + "%s Candidate path %s created, computation request already sent", + pcc_state->tag, path->name); + return; + } + PCEP_DEBUG("%s Candidate path %s created", pcc_state->tag, + path->name); + if ((path->first_hop == NULL) + && (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC)) { + req = push_new_req(pcc_state, path); + send_comp_request(ctrl_state, pcc_state, req); + } else if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + case PCEP_PATH_UPDATED: + PCEP_DEBUG("%s Candidate path %s updated", pcc_state->tag, + path->name); + if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + case PCEP_PATH_REMOVED: + PCEP_DEBUG("%s Candidate path %s removed", pcc_state->tag, + path->name); + path->was_removed = true; + /* Removed as response to a PcInitiated 'R'emove*/ + /* RFC 8281 #5.4 LSP Deletion*/ + path->do_remove = path->was_removed; + if (pcc_state->caps.is_stateful) + send_report(pcc_state, path); + return; + case PCEP_PATH_UNDEFINED: + flog_warn(EC_PATH_PCEP_RECOVERABLE_INTERNAL_ERROR, + "Unexpected pathd event received by pcc %s: %u", + pcc_state->tag, type); + return; + } +} + + +/* ------------ PCEP event handler ------------ */ + +void pcep_pcc_pcep_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, pcep_event *event) +{ + PCEP_DEBUG("%s Received PCEP event: %s", pcc_state->tag, + pcep_event_type_name(event->event_type)); + switch (event->event_type) { + case PCC_CONNECTED_TO_PCE: + assert(PCEP_PCC_CONNECTING == pcc_state->status); + PCEP_DEBUG("%s Connection established", pcc_state->tag); + pcc_state->status = PCEP_PCC_SYNCHRONIZING; + pcc_state->retry_count = 0; + pcc_state->synchronized = false; + PCEP_DEBUG("%s Starting PCE synchronization", pcc_state->tag); + cancel_session_timeout(ctrl_state, pcc_state); + pcep_pcc_calculate_best_pce(ctrl_state->pcc); + pcep_thread_start_sync(ctrl_state, pcc_state->id); + break; + case PCC_SENT_INVALID_OPEN: + PCEP_DEBUG("%s Sent invalid OPEN message", pcc_state->tag); + PCEP_DEBUG( + "%s Reconciling values: keep alive (%d) dead timer (%d) seconds ", + pcc_state->tag, + pcc_state->sess->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + pcc_state->sess->pcc_config + .dead_timer_pce_negotiated_seconds); + pcc_state->pce_opts->config_opts.keep_alive_seconds = + pcc_state->sess->pcc_config + .keep_alive_pce_negotiated_timer_seconds; + pcc_state->pce_opts->config_opts.dead_timer_seconds = + pcc_state->sess->pcc_config + .dead_timer_pce_negotiated_seconds; + break; + + case PCC_RCVD_INVALID_OPEN: + PCEP_DEBUG("%s Received invalid OPEN message", pcc_state->tag); + PCEP_DEBUG_PCEP("%s PCEP message: %s", pcc_state->tag, + format_pcep_message(event->message)); + break; + case PCE_DEAD_TIMER_EXPIRED: + case PCE_CLOSED_SOCKET: + case PCE_SENT_PCEP_CLOSE: + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + case PCC_PCEP_SESSION_CLOSED: + case PCC_RCVD_MAX_INVALID_MSGS: + case PCC_RCVD_MAX_UNKOWN_MSGS: + pcep_pcc_disable(ctrl_state, pcc_state); + schedule_reconnect(ctrl_state, pcc_state); + schedule_session_timeout(ctrl_state, pcc_state); + break; + case MESSAGE_RECEIVED: + PCEP_DEBUG_PCEP("%s Received PCEP message: %s", pcc_state->tag, + format_pcep_message(event->message)); + if (pcc_state->status == PCEP_PCC_CONNECTING) { + if (event->message->msg_header->type == PCEP_TYPE_OPEN) + handle_pcep_open(ctrl_state, pcc_state, + event->message); + break; + } + assert(pcc_state->status == PCEP_PCC_SYNCHRONIZING + || pcc_state->status == PCEP_PCC_OPERATING); + handle_pcep_message(ctrl_state, pcc_state, event->message); + break; + case PCC_CONNECTION_FAILURE: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEPLIB_EVENT, + "Unexpected event from pceplib: %s", + format_pcep_event(event)); + break; + } +} + + +/*------------------ Multi-PCE --------------------- */ + +/* Internal util function, returns true if sync is necessary, false otherwise */ +bool update_best_pce(struct pcc_state **pcc, int best) +{ + PCEP_DEBUG(" recalculating pce precedence "); + if (best) { + struct pcc_state *best_pcc_state = + pcep_pcc_get_pcc_by_id(pcc, best); + if (best_pcc_state->previous_best != best_pcc_state->is_best) { + PCEP_DEBUG(" %s Resynch best (%i) previous best (%i)", + best_pcc_state->tag, best_pcc_state->id, + best_pcc_state->previous_best); + return true; + } else { + PCEP_DEBUG( + " %s No Resynch best (%i) previous best (%i)", + best_pcc_state->tag, best_pcc_state->id, + best_pcc_state->previous_best); + } + } else { + PCEP_DEBUG(" No best pce available, all pce seem disconnected"); + } + + return false; +} + +int get_best_pce(struct pcc_state **pcc) +{ + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + if (pcc[i]->is_best == true) { + return pcc[i]->id; + } + } + } + return 0; +} + +int get_pce_count_connected(struct pcc_state **pcc) +{ + int count = 0; + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + count++; + } + } + return count; +} + +int get_previous_best_pce(struct pcc_state **pcc) +{ + int previous_best_pce = -1; + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts && pcc[i]->previous_best == true + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + previous_best_pce = i; + break; + } + } + return previous_best_pce != -1 ? pcc[previous_best_pce]->id : 0; +} + +/* Called by path_pcep_controller EV_REMOVE_PCC + * Event handler when a PCC is removed. */ +int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state, + struct pcc_state **pcc) +{ + int new_best_pcc_id = -1; + new_best_pcc_id = pcep_pcc_calculate_best_pce(pcc); + if (new_best_pcc_id) { + if (update_best_pce(ctrl_state->pcc, new_best_pcc_id) == true) { + pcep_thread_start_sync(ctrl_state, new_best_pcc_id); + } + } + + return 0; +} + +/* Called by path_pcep_controller EV_SYNC_PATH + * Event handler when a path is sync'd. */ +int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct pcc_state **pcc) +{ + int previous_best_pcc_id = -1; + + if (pcc_id == get_best_pce(pcc)) { + previous_best_pcc_id = get_previous_best_pce(pcc); + if (previous_best_pcc_id != 0) { + /* while adding new pce, path has to resync to the + * previous best. pcep_thread_start_sync() will be + * called by the calling function */ + if (update_best_pce(ctrl_state->pcc, + previous_best_pcc_id) + == true) { + cancel_comp_requests( + ctrl_state, + pcep_pcc_get_pcc_by_id( + pcc, previous_best_pcc_id)); + pcep_thread_start_sync(ctrl_state, + previous_best_pcc_id); + } + } + } + + return 0; +} + +/* Called by path_pcep_controller when the TM_CALCULATE_BEST_PCE + * timer expires */ +int pcep_pcc_timer_update_best_pce(struct ctrl_state *ctrl_state, int pcc_id) +{ + int ret = 0; + /* resync whatever was the new best */ + int prev_best = get_best_pce(ctrl_state->pcc); + int best_id = pcep_pcc_calculate_best_pce(ctrl_state->pcc); + if (best_id && prev_best != best_id) { // Avoid Multiple call + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_id(ctrl_state->pcc, best_id); + if (update_best_pce(ctrl_state->pcc, pcc_state->id) == true) { + pcep_thread_start_sync(ctrl_state, pcc_state->id); + } + } + + return ret; +} + +/* Called by path_pcep_controller::pcep_thread_event_update_pce_options() + * Returns the best PCE id */ +int pcep_pcc_calculate_best_pce(struct pcc_state **pcc) +{ + int best_precedence = 255; // DEFAULT_PCE_PRECEDENCE; + int best_pce = -1; + int one_connected_pce = -1; + int previous_best_pce = -1; + int step_0_best = -1; + int step_0_previous = -1; + int pcc_count = 0; + + // Get state + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + zlog_debug( + "multi-pce: calculate all : i (%i) is_best (%i) previous_best (%i) ", + i, pcc[i]->is_best, pcc[i]->previous_best); + pcc_count++; + + if (pcc[i]->is_best == true) { + step_0_best = i; + } + if (pcc[i]->previous_best == true) { + step_0_previous = i; + } + } + } + + if (!pcc_count) { + return 0; + } + + // Calculate best + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts + && pcc[i]->status != PCEP_PCC_DISCONNECTED) { + one_connected_pce = i; // In case none better + if (pcc[i]->pce_opts->precedence <= best_precedence) { + if (best_pce != -1 + && pcc[best_pce]->pce_opts->precedence + == pcc[i]->pce_opts + ->precedence) { + if (ipaddr_cmp( + &pcc[i]->pce_opts->addr, + &pcc[best_pce] + ->pce_opts->addr) + > 0) + // collide of precedences so + // compare ip + best_pce = i; + } else { + if (!pcc[i]->previous_best) { + best_precedence = + pcc[i]->pce_opts + ->precedence; + best_pce = i; + } + } + } + } + } + + zlog_debug( + "multi-pce: calculate data : sb (%i) sp (%i) oc (%i) b (%i) ", + step_0_best, step_0_previous, one_connected_pce, best_pce); + + // Changed of state so ... + if (step_0_best != best_pce) { + pthread_mutex_lock(&g_pcc_info_mtx); + // Calculate previous + previous_best_pce = step_0_best; + // Clean state + if (step_0_best != -1) { + pcc[step_0_best]->is_best = false; + } + if (step_0_previous != -1) { + pcc[step_0_previous]->previous_best = false; + } + + // Set previous + if (previous_best_pce != -1 + && pcc[previous_best_pce]->status + == PCEP_PCC_DISCONNECTED) { + pcc[previous_best_pce]->previous_best = true; + zlog_debug("multi-pce: previous best pce (%i) ", + previous_best_pce + 1); + } + + + // Set best + if (best_pce != -1) { + pcc[best_pce]->is_best = true; + zlog_debug("multi-pce: best pce (%i) ", best_pce + 1); + } else { + if (one_connected_pce != -1) { + best_pce = one_connected_pce; + pcc[one_connected_pce]->is_best = true; + zlog_debug( + "multi-pce: one connected best pce (default) (%i) ", + one_connected_pce + 1); + } else { + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] && pcc[i]->pce_opts) { + best_pce = i; + pcc[i]->is_best = true; + zlog_debug( + "(disconnected) best pce (default) (%i) ", + i + 1); + break; + } + } + } + } + pthread_mutex_unlock(&g_pcc_info_mtx); + } + + return ((best_pce == -1) ? 0 : pcc[best_pce]->id); +} + +int pcep_pcc_get_pcc_id_by_ip_port(struct pcc_state **pcc, + struct pce_opts *pce_opts) +{ + if (pcc == NULL) { + return 0; + } + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx]) { + if ((ipaddr_cmp((const struct ipaddr *)&pcc[idx] + ->pce_opts->addr, + (const struct ipaddr *)&pce_opts->addr) + == 0) + && pcc[idx]->pce_opts->port == pce_opts->port) { + zlog_debug("found pcc_id (%d) idx (%d)", + pcc[idx]->id, idx); + return pcc[idx]->id; + } + } + } + return 0; +} + +int pcep_pcc_get_pcc_id_by_idx(struct pcc_state **pcc, int idx) +{ + if (pcc == NULL || idx < 0) { + return 0; + } + + return pcc[idx] ? pcc[idx]->id : 0; +} + +struct pcc_state *pcep_pcc_get_pcc_by_id(struct pcc_state **pcc, int id) +{ + if (pcc == NULL || id < 0) { + return NULL; + } + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i]) { + if (pcc[i]->id == id) { + zlog_debug("found id (%d) pcc_idx (%d)", + pcc[i]->id, i); + return pcc[i]; + } + } + } + + return NULL; +} + +struct pcc_state *pcep_pcc_get_pcc_by_name(struct pcc_state **pcc, + const char *pce_name) +{ + if (pcc == NULL || pce_name == NULL) { + return NULL; + } + + for (int i = 0; i < MAX_PCC; i++) { + if (pcc[i] == NULL) { + continue; + } + + if (strcmp(pcc[i]->pce_opts->pce_name, pce_name) == 0) { + return pcc[i]; + } + } + + return NULL; +} + +int pcep_pcc_get_pcc_idx_by_id(struct pcc_state **pcc, int id) +{ + if (pcc == NULL) { + return -1; + } + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx]) { + if (pcc[idx]->id == id) { + zlog_debug("found pcc_id (%d) array_idx (%d)", + pcc[idx]->id, idx); + return idx; + } + } + } + + return -1; +} + +int pcep_pcc_get_free_pcc_idx(struct pcc_state **pcc) +{ + assert(pcc != NULL); + + for (int idx = 0; idx < MAX_PCC; idx++) { + if (pcc[idx] == NULL) { + zlog_debug("new pcc_idx (%d)", idx); + return idx; + } + } + + return -1; +} + +int pcep_pcc_get_pcc_id(struct pcc_state *pcc) +{ + return ((pcc == NULL) ? 0 : pcc->id); +} + +void pcep_pcc_copy_pcc_info(struct pcc_state **pcc, + struct pcep_pcc_info *pcc_info) +{ + struct pcc_state *pcc_state = + pcep_pcc_get_pcc_by_name(pcc, pcc_info->pce_name); + if (!pcc_state) { + return; + } + + pcc_info->ctrl_state = NULL; + if(pcc_state->pcc_opts){ + pcc_info->msd = pcc_state->pcc_opts->msd; + pcc_info->pcc_port = pcc_state->pcc_opts->port; + } + pcc_info->next_plspid = pcc_state->next_plspid; + pcc_info->next_reqid = pcc_state->next_reqid; + pcc_info->status = pcc_state->status; + pcc_info->pcc_id = pcc_state->id; + pthread_mutex_lock(&g_pcc_info_mtx); + pcc_info->is_best_multi_pce = pcc_state->is_best; + pcc_info->previous_best = pcc_state->previous_best; + pthread_mutex_unlock(&g_pcc_info_mtx); + pcc_info->precedence = + pcc_state->pce_opts ? pcc_state->pce_opts->precedence : 0; + if(pcc_state->pcc_addr_tr.ipa_type != IPADDR_NONE){ + memcpy(&pcc_info->pcc_addr, &pcc_state->pcc_addr_tr, + sizeof(struct ipaddr)); + } +} + + +/*------------------ PCEP Message handlers --------------------- */ + +void handle_pcep_open(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_message *msg) +{ + assert(msg->msg_header->type == PCEP_TYPE_OPEN); + pcep_lib_parse_capabilities(msg, &pcc_state->caps); + PCEP_DEBUG("PCE capabilities: %s, %s%s", + pcc_state->caps.is_stateful ? "stateful" : "stateless", + pcc_state->caps.supported_ofs_are_known + ? (pcc_state->caps.supported_ofs == 0 + ? "no objective functions supported" + : "supported objective functions are ") + : "supported objective functions are unknown", + format_objfun_set(pcc_state->caps.supported_ofs)); +} + +void handle_pcep_message(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_message *msg) +{ + if (pcc_state->status != PCEP_PCC_OPERATING) + return; + + switch (msg->msg_header->type) { + case PCEP_TYPE_INITIATE: + handle_pcep_lsp_initiate(ctrl_state, pcc_state, msg); + break; + case PCEP_TYPE_UPDATE: + handle_pcep_lsp_update(ctrl_state, pcc_state, msg); + break; + case PCEP_TYPE_PCREP: + handle_pcep_comp_reply(ctrl_state, pcc_state, msg); + break; + case PCEP_TYPE_OPEN: + case PCEP_TYPE_KEEPALIVE: + case PCEP_TYPE_PCREQ: + case PCEP_TYPE_PCNOTF: + case PCEP_TYPE_ERROR: + case PCEP_TYPE_CLOSE: + case PCEP_TYPE_REPORT: + case PCEP_TYPE_START_TLS: + case PCEP_TYPE_MAX: + flog_warn(EC_PATH_PCEP_UNEXPECTED_PCEP_MESSAGE, + "Unexpected pcep message from pceplib: %s", + format_pcep_message(msg)); + break; + } +} + +void handle_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + struct path *path; + path = pcep_lib_parse_path(msg); + lookup_nbkey(pcc_state, path); + pcep_thread_refine_path(ctrl_state, pcc_state->id, + &continue_pcep_lsp_update, path, NULL); +} + +void continue_pcep_lsp_update(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + void *payload) +{ + char err[MAX_ERROR_MSG_SIZE] = {0}; + + specialize_incoming_path(pcc_state, path); + PCEP_DEBUG("%s Received LSP update", pcc_state->tag); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (validate_incoming_path(pcc_state, path, err, sizeof(err))) + pcep_thread_update_path(ctrl_state, pcc_state->id, path); + else { + /* FIXME: Monitor the amount of errors from the PCE and + * possibly disconnect and blacklist */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Unsupported PCEP protocol feature: %s", err); + pcep_free_path(path); + } +} + +void handle_pcep_lsp_initiate(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + char err[MAX_ERROR_MSG_SIZE] = ""; + struct path *path; + + path = pcep_lib_parse_path(msg); + + if (!pcc_state->pce_opts->config_opts.pce_initiated) { + /* PCE Initiated is not enabled */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Not allowed PCE initiated path received: %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR, path); + return; + } + + if (path->do_remove) { + // lookup in nbkey sequential as no endpoint + struct nbkey_map_data *key; + char endpoint[46]; + + frr_each (nbkey_map, &pcc_state->nbkey_map, key) { + ipaddr2str(&key->nbkey.endpoint, endpoint, + sizeof(endpoint)); + flog_warn( + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "FOR_EACH nbkey [color (%d) endpoint (%s)] path [plsp_id (%d)] ", + key->nbkey.color, endpoint, path->plsp_id); + if (path->plsp_id == key->plspid) { + flog_warn( + EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "FOR_EACH MATCH nbkey [color (%d) endpoint (%s)] path [plsp_id (%d)] ", + key->nbkey.color, endpoint, + path->plsp_id); + path->nbkey = key->nbkey; + break; + } + } + } else { + if (path->first_hop == NULL /*ero sets first_hop*/) { + /* If the PCC receives a PCInitiate message without an + * ERO and the R flag in the SRP object != zero, then it + * MUST send a PCErr message with Error-type=6 + * (Mandatory Object missing) and Error-value=9 (ERO + * object missing). */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "ERO object missing or incomplete : %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, + PCEP_ERRT_LSP_INSTANTIATE_ERROR, + PCEP_ERRV_INTERNAL_ERROR, path); + return; + } + + if (path->plsp_id != 0) { + /* If the PCC receives a PCInitiate message with a + * non-zero PLSP-ID and the R flag in the SRP object set + * to zero, then it MUST send a PCErr message with + * Error-type=19 (Invalid Operation) and Error-value=8 + * (Non-zero PLSP-ID in the LSP Initiate Request) */ + flog_warn( + EC_PATH_PCEP_PROTOCOL_ERROR, + "PCE initiated path with non-zero PLSP ID: %s", + format_pcep_message(msg)); + send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID, + path); + return; + } + + if (path->name == NULL) { + /* If the PCC receives a PCInitiate message without a + * SYMBOLIC-PATH-NAME TLV, then it MUST send a PCErr + * message with Error-type=10 (Reception of an invalid + * object) and Error-value=8 (SYMBOLIC-PATH-NAME TLV + * missing) */ + flog_warn( + EC_PATH_PCEP_PROTOCOL_ERROR, + "PCE initiated path without symbolic name: %s", + format_pcep_message(msg)); + send_pcep_error( + pcc_state, PCEP_ERRT_RECEPTION_OF_INV_OBJECT, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING, path); + return; + } + } + + /* TODO: If there is a conflict with the symbolic path name of an + * existing LSP, the PCC MUST send a PCErr message with Error-type=23 + * (Bad Parameter value) and Error-value=1 (SYMBOLIC-PATH-NAME in + * use) */ + + specialize_incoming_path(pcc_state, path); + /* TODO: Validate the PCC address received from the PCE is valid */ + PCEP_DEBUG("%s Received LSP initiate", pcc_state->tag); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (validate_incoming_path(pcc_state, path, err, sizeof(err))) { + pcep_thread_initiate_path(ctrl_state, pcc_state->id, path); + } else { + /* FIXME: Monitor the amount of errors from the PCE and + * possibly disconnect and blacklist */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Unsupported PCEP protocol feature: %s", err); + send_pcep_error(pcc_state, PCEP_ERRT_INVALID_OPERATION, + PCEP_ERRV_LSP_NOT_PCE_INITIATED, path); + pcep_free_path(path); + } +} + +void handle_pcep_comp_reply(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + struct pcep_message *msg) +{ + char err[MAX_ERROR_MSG_SIZE] = ""; + struct req_entry *req; + struct path *path; + + path = pcep_lib_parse_path(msg); + if (path->no_path) { + req = pop_req_no_reqid(pcc_state, path->req_id); + } else { + req = pop_req(pcc_state, path->req_id); + } + if (req == NULL) { + /* TODO: check the rate of bad computation reply and close + * the connection if more that a given rate. + */ + PCEP_DEBUG( + "%s Received computation reply for unknown request %d", + pcc_state->tag, path->req_id); + PCEP_DEBUG_PATH("%s", format_path(path)); + send_pcep_error(pcc_state, PCEP_ERRT_UNKNOWN_REQ_REF, + PCEP_ERRV_UNASSIGNED, NULL); + return; + } + + /* Cancel the computation request timeout */ + pcep_thread_cancel_timer(&req->t_retry); + + /* Transfer relevent metadata from the request to the response */ + path->nbkey = req->path->nbkey; + path->plsp_id = req->path->plsp_id; + path->type = req->path->type; + path->name = XSTRDUP(MTYPE_PCEP, req->path->name); + specialize_incoming_path(pcc_state, path); + + PCEP_DEBUG("%s Received computation reply %d (no-path: %s)", + pcc_state->tag, path->req_id, + path->no_path ? "true" : "false"); + PCEP_DEBUG_PATH("%s", format_path(path)); + + if (path->no_path) { + PCEP_DEBUG("%s Computation for path %s did not find any result", + pcc_state->tag, path->name); + free_req_entry(req); + pcep_free_path(path); + return; + } else if (validate_incoming_path(pcc_state, path, err, sizeof(err))) { + /* Updating a dynamic path will automatically delegate it */ + pcep_thread_update_path(ctrl_state, pcc_state->id, path); + free_req_entry(req); + return; + } else { + /* FIXME: Monitor the amount of errors from the PCE and + * possibly disconnect and blacklist */ + flog_warn(EC_PATH_PCEP_UNSUPPORTED_PCEP_FEATURE, + "Unsupported PCEP protocol feature: %s", err); + } + + pcep_free_path(path); + + /* Delegate the path regardless of the outcome */ + /* TODO: For now we are using the path from the request, when + * pathd API is thread safe, we could get a new path */ + if (pcc_state->caps.is_stateful) { + PCEP_DEBUG("%s Delegating undefined dynamic path %s to PCE %s", + pcc_state->tag, req->path->name, + pcc_state->originator); + path = pcep_copy_path(req->path); + path->is_delegated = true; + send_report(pcc_state, path); + pcep_free_path(path); + } + + free_req_entry(req); +} + + +/* ------------ Internal Functions ------------ */ + +const char *ipaddr_type_name(struct ipaddr *addr) +{ + if (IS_IPADDR_V4(addr)) + return "IPv4"; + if (IS_IPADDR_V6(addr)) + return "IPv6"; + return "undefined"; +} + +bool filter_path(struct pcc_state *pcc_state, struct path *path) +{ + return (IS_IPADDR_V4(&path->nbkey.endpoint) + && CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) + || (IS_IPADDR_V6(&path->nbkey.endpoint) + && CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)); +} + +void select_pcc_addresses(struct pcc_state *pcc_state) +{ + /* If no IPv4 address was specified, try to get one from zebra */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + if (get_ipv4_router_id(&pcc_state->pcc_addr_v4)) { + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4); + } + } + + /* If no IPv6 address was specified, try to get one from zebra */ + if (!CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + if (get_ipv6_router_id(&pcc_state->pcc_addr_v6)) { + SET_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6); + } + } +} + +void select_transport_address(struct pcc_state *pcc_state) +{ + struct ipaddr *taddr = &pcc_state->pcc_addr_tr; + + select_pcc_addresses(pcc_state); + + taddr->ipa_type = IPADDR_NONE; + + /* Select a transport source address in function of the configured PCE + * address */ + if (IS_IPADDR_V4(&pcc_state->pce_opts->addr)) { + if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)) { + taddr->ipaddr_v4 = pcc_state->pcc_addr_v4; + taddr->ipa_type = IPADDR_V4; + } + } else { + if (CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)) { + taddr->ipaddr_v6 = pcc_state->pcc_addr_v6; + taddr->ipa_type = IPADDR_V6; + } + } +} + +void update_tag(struct pcc_state *pcc_state) +{ + if (pcc_state->pce_opts != NULL) { + assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr)); + if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), + "%pI6:%i (%u)", + &pcc_state->pce_opts->addr.ipaddr_v6, + pcc_state->pce_opts->port, pcc_state->id); + } else { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), + "%pI4:%i (%u)", + &pcc_state->pce_opts->addr.ipaddr_v4, + pcc_state->pce_opts->port, pcc_state->id); + } + } else { + snprintfrr(pcc_state->tag, sizeof(pcc_state->tag), "(%u)", + pcc_state->id); + } +} + +void update_originator(struct pcc_state *pcc_state) +{ + char *originator; + if (pcc_state->originator != NULL) { + XFREE(MTYPE_PCEP, pcc_state->originator); + pcc_state->originator = NULL; + } + if (pcc_state->pce_opts == NULL) + return; + originator = XCALLOC(MTYPE_PCEP, 52); + assert(!IS_IPADDR_NONE(&pcc_state->pce_opts->addr)); + if (IS_IPADDR_V6(&pcc_state->pce_opts->addr)) { + snprintfrr(originator, 52, "%pI6:%i", + &pcc_state->pce_opts->addr.ipaddr_v6, + pcc_state->pce_opts->port); + } else { + snprintfrr(originator, 52, "%pI4:%i", + &pcc_state->pce_opts->addr.ipaddr_v4, + pcc_state->pce_opts->port); + } + pcc_state->originator = originator; +} + +void schedule_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + pcc_state->retry_count++; + pcep_thread_schedule_reconnect(ctrl_state, pcc_state->id, + pcc_state->retry_count, + &pcc_state->t_reconnect); + if (pcc_state->retry_count == 1) { + pcep_thread_schedule_sync_best_pce( + ctrl_state, pcc_state->id, + pcc_state->pce_opts->config_opts + .delegation_timeout_seconds, + &pcc_state->t_update_best); + } +} + +void schedule_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + /* No need to schedule timeout if multiple PCEs are connected */ + if (get_pce_count_connected(ctrl_state->pcc)) { + PCEP_DEBUG_PCEP( + "schedule_session_timeout not setting timer for multi-pce mode"); + + return; + } + + pcep_thread_schedule_session_timeout( + ctrl_state, pcep_pcc_get_pcc_id(pcc_state), + pcc_state->pce_opts->config_opts + .session_timeout_inteval_seconds, + &pcc_state->t_session_timeout); +} + +void cancel_session_timeout(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + /* No need to schedule timeout if multiple PCEs are connected */ + if (pcc_state->t_session_timeout == NULL) { + PCEP_DEBUG_PCEP("cancel_session_timeout timer thread NULL"); + return; + } + + PCEP_DEBUG_PCEP("Cancel session_timeout timer"); + pcep_thread_cancel_timer(&pcc_state->t_session_timeout); + pcc_state->t_session_timeout = NULL; +} + +void send_pcep_message(struct pcc_state *pcc_state, struct pcep_message *msg) +{ + if (pcc_state->sess != NULL) { + PCEP_DEBUG_PCEP("%s Sending PCEP message: %s", pcc_state->tag, + format_pcep_message(msg)); + send_message(pcc_state->sess, msg, true); + } +} + +void send_pcep_error(struct pcc_state *pcc_state, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct path *trigger_path) +{ + struct pcep_message *msg; + PCEP_DEBUG("%s Sending PCEP error type %s (%d) value %s (%d)", + pcc_state->tag, pcep_error_type_name(error_type), error_type, + pcep_error_value_name(error_type, error_value), error_value); + msg = pcep_lib_format_error(error_type, error_value, trigger_path); + send_pcep_message(pcc_state, msg); +} + +void send_report(struct pcc_state *pcc_state, struct path *path) +{ + struct pcep_message *report; + + path->req_id = 0; + specialize_outgoing_path(pcc_state, path); + PCEP_DEBUG_PATH("%s Sending path %s: %s", pcc_state->tag, path->name, + format_path(path)); + report = pcep_lib_format_report(&pcc_state->caps, path); + send_pcep_message(pcc_state, report); +} + +/* Updates the path for the PCE, updating the delegation and creation flags */ +void specialize_outgoing_path(struct pcc_state *pcc_state, struct path *path) +{ + bool is_delegated = false; + bool was_created = false; + + lookup_plspid(pcc_state, path); + + set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr); + path->sender = pcc_state->pcc_addr_tr; + + /* TODO: When the pathd API have a way to mark a path as + * delegated, use it instead of considering all dynamic path + * delegated. We need to disable the originator check for now, + * because path could be delegated without having any originator yet */ + // if ((path->originator == NULL) + // || (strcmp(path->originator, pcc_state->originator) == 0)) { + // is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC) + // && (path->first_hop != NULL); + // /* it seems the PCE consider updating an LSP a creation ?!? + // at least Cisco does... */ + // was_created = path->update_origin == SRTE_ORIGIN_PCEP; + // } + is_delegated = (path->type == SRTE_CANDIDATE_TYPE_DYNAMIC); + was_created = path->update_origin == SRTE_ORIGIN_PCEP; + + path->pcc_id = pcc_state->id; + path->go_active = is_delegated && pcc_state->is_best; + path->is_delegated = is_delegated && pcc_state->is_best; + path->was_created = was_created; +} + +/* Updates the path for the PCC */ +void specialize_incoming_path(struct pcc_state *pcc_state, struct path *path) +{ + if (IS_IPADDR_NONE(&path->pcc_addr)) + set_pcc_address(pcc_state, &path->nbkey, &path->pcc_addr); + path->sender = pcc_state->pce_opts->addr; + path->pcc_id = pcc_state->id; + path->update_origin = SRTE_ORIGIN_PCEP; + path->originator = XSTRDUP(MTYPE_PCEP, pcc_state->originator); +} + +/* Ensure the path can be handled by the PCC and if not, sends an error */ +bool validate_incoming_path(struct pcc_state *pcc_state, struct path *path, + char *errbuff, size_t buffsize) +{ + struct path_hop *hop; + enum pcep_error_type err_type = 0; + enum pcep_error_value err_value = PCEP_ERRV_UNASSIGNED; + + for (hop = path->first_hop; hop != NULL; hop = hop->next) { + /* Hops without SID are not supported */ + if (!hop->has_sid) { + snprintfrr(errbuff, buffsize, "SR segment without SID"); + err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + err_value = PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING; + break; + } + /* Hops with non-MPLS SID are not supported */ + if (!hop->is_mpls) { + snprintfrr(errbuff, buffsize, + "SR segment with non-MPLS SID"); + err_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + err_value = PCEP_ERRV_UNSUPPORTED_NAI; + break; + } + } + + if (err_type != 0) { + send_pcep_error(pcc_state, err_type, err_value, NULL); + return false; + } + + return true; +} + +void send_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct req_entry *req) +{ + assert(req != NULL); + + if (req->t_retry) + return; + + assert(req->path != NULL); + assert(req->path->req_id > 0); + assert(RB_FIND(req_entry_head, &pcc_state->requests, req) == req); + assert(lookup_reqid(pcc_state, req->path) == req->path->req_id); + + int timeout; + struct pcep_message *msg; + + if (!pcc_state->is_best) { + return; + } + + specialize_outgoing_path(pcc_state, req->path); + + PCEP_DEBUG( + "%s Sending computation request %d for path %s to %pIA (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + &req->path->nbkey.endpoint, req->retry_count); + PCEP_DEBUG_PATH("%s Computation request path %s: %s", pcc_state->tag, + req->path->name, format_path(req->path)); + + msg = pcep_lib_format_request(&pcc_state->caps, req->path); + send_pcep_message(pcc_state, msg); + req->was_sent = true; + + timeout = pcc_state->pce_opts->config_opts.pcep_request_time_seconds; + pcep_thread_schedule_timeout(ctrl_state, pcc_state->id, + TO_COMPUTATION_REQUEST, timeout, + (void *)req, &req->t_retry); +} + +void cancel_comp_requests(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state) +{ + struct req_entry *req, *safe_req; + + RB_FOREACH_SAFE (req, req_entry_head, &pcc_state->requests, safe_req) { + cancel_comp_request(ctrl_state, pcc_state, req); + RB_REMOVE(req_entry_head, &pcc_state->requests, req); + remove_reqid_mapping(pcc_state, req->path); + free_req_entry(req); + } +} + +void cancel_comp_request(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct req_entry *req) +{ + struct pcep_message *msg; + + if (req->was_sent) { + /* TODO: Send a computation request cancelation + * notification to the PCE */ + pcep_thread_cancel_timer(&req->t_retry); + } + + PCEP_DEBUG( + "%s Canceling computation request %d for path %s to %pIA (retry %d)", + pcc_state->tag, req->path->req_id, req->path->name, + &req->path->nbkey.endpoint, req->retry_count); + PCEP_DEBUG_PATH("%s Canceled computation request path %s: %s", + pcc_state->tag, req->path->name, + format_path(req->path)); + + msg = pcep_lib_format_request_cancelled(req->path->req_id); + send_pcep_message(pcc_state, msg); +} + +void set_pcc_address(struct pcc_state *pcc_state, struct lsp_nb_key *nbkey, + struct ipaddr *addr) +{ + select_pcc_addresses(pcc_state); + if (IS_IPADDR_V6(&nbkey->endpoint)) { + assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV6)); + addr->ipa_type = IPADDR_V6; + addr->ipaddr_v6 = pcc_state->pcc_addr_v6; + } else if (IS_IPADDR_V4(&nbkey->endpoint)) { + assert(CHECK_FLAG(pcc_state->flags, F_PCC_STATE_HAS_IPV4)); + addr->ipa_type = IPADDR_V4; + addr->ipaddr_v4 = pcc_state->pcc_addr_v4; + } else { + addr->ipa_type = IPADDR_NONE; + } +} + +/* ------------ Data Structure Helper Functions ------------ */ + +void lookup_plspid(struct pcc_state *pcc_state, struct path *path) +{ + struct plspid_map_data key, *plspid_mapping; + struct nbkey_map_data *nbkey_mapping; + + if (path->nbkey.color != 0) { + key.nbkey = path->nbkey; + plspid_mapping = plspid_map_find(&pcc_state->plspid_map, &key); + if (plspid_mapping == NULL) { + plspid_mapping = + XCALLOC(MTYPE_PCEP, sizeof(*plspid_mapping)); + plspid_mapping->nbkey = key.nbkey; + plspid_mapping->plspid = pcc_state->next_plspid; + plspid_map_add(&pcc_state->plspid_map, plspid_mapping); + nbkey_mapping = + XCALLOC(MTYPE_PCEP, sizeof(*nbkey_mapping)); + nbkey_mapping->nbkey = key.nbkey; + nbkey_mapping->plspid = pcc_state->next_plspid; + nbkey_map_add(&pcc_state->nbkey_map, nbkey_mapping); + pcc_state->next_plspid++; + // FIXME: Send some error to the PCE isntead of crashing + assert(pcc_state->next_plspid <= 1048576); + } + path->plsp_id = plspid_mapping->plspid; + } +} + +void lookup_nbkey(struct pcc_state *pcc_state, struct path *path) +{ + struct nbkey_map_data key, *mapping; + // TODO: Should give an error to the PCE instead of crashing + assert(path->plsp_id != 0); + key.plspid = path->plsp_id; + mapping = nbkey_map_find(&pcc_state->nbkey_map, &key); + assert(mapping != NULL); + path->nbkey = mapping->nbkey; +} + +void free_req_entry(struct req_entry *req) +{ + pcep_free_path(req->path); + XFREE(MTYPE_PCEP, req); +} + +struct req_entry *push_new_req(struct pcc_state *pcc_state, struct path *path) +{ + struct req_entry *req; + + req = XCALLOC(MTYPE_PCEP, sizeof(*req)); + req->retry_count = 0; + req->path = pcep_copy_path(path); + repush_req(pcc_state, req); + + return req; +} + +void repush_req(struct pcc_state *pcc_state, struct req_entry *req) +{ + uint32_t reqid = pcc_state->next_reqid; + void *res; + + req->was_sent = false; + req->path->req_id = reqid; + res = RB_INSERT(req_entry_head, &pcc_state->requests, req); + assert(res == NULL); + assert(add_reqid_mapping(pcc_state, req->path) == true); + + pcc_state->next_reqid += 1; + /* Wrapping is allowed, but 0 is not a valid id */ + if (pcc_state->next_reqid == 0) + pcc_state->next_reqid = 1; +} + +struct req_entry *pop_req(struct pcc_state *pcc_state, uint32_t reqid) +{ + struct path path = {.req_id = reqid}; + struct req_entry key = {.path = &path}; + struct req_entry *req; + + req = RB_FIND(req_entry_head, &pcc_state->requests, &key); + if (req == NULL) + return NULL; + RB_REMOVE(req_entry_head, &pcc_state->requests, req); + remove_reqid_mapping(pcc_state, req->path); + + return req; +} + +struct req_entry *pop_req_no_reqid(struct pcc_state *pcc_state, uint32_t reqid) +{ + struct path path = {.req_id = reqid}; + struct req_entry key = {.path = &path}; + struct req_entry *req; + + req = RB_FIND(req_entry_head, &pcc_state->requests, &key); + if (req == NULL) + return NULL; + RB_REMOVE(req_entry_head, &pcc_state->requests, req); + + return req; +} + +bool add_reqid_mapping(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data *mapping; + mapping = XCALLOC(MTYPE_PCEP, sizeof(*mapping)); + mapping->nbkey = path->nbkey; + mapping->reqid = path->req_id; + if (req_map_add(&pcc_state->req_map, mapping) != NULL) { + XFREE(MTYPE_PCEP, mapping); + return false; + } + return true; +} + +void remove_reqid_mapping(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data key, *mapping; + key.nbkey = path->nbkey; + mapping = req_map_find(&pcc_state->req_map, &key); + if (mapping != NULL) { + req_map_del(&pcc_state->req_map, mapping); + XFREE(MTYPE_PCEP, mapping); + } +} + +uint32_t lookup_reqid(struct pcc_state *pcc_state, struct path *path) +{ + struct req_map_data key, *mapping; + key.nbkey = path->nbkey; + mapping = req_map_find(&pcc_state->req_map, &key); + if (mapping != NULL) + return mapping->reqid; + return 0; +} + +bool has_pending_req_for(struct pcc_state *pcc_state, struct path *path) +{ + struct req_entry key = {.path = path}; + struct req_entry *req; + + + PCEP_DEBUG_PATH("(%s) %s", format_path(path), __func__); + /* Looking for request without result */ + if (path->no_path || !path->first_hop) { + PCEP_DEBUG_PATH("%s Path : no_path|!first_hop", __func__); + /* ...and already was handle */ + req = RB_FIND(req_entry_head, &pcc_state->requests, &key); + if (!req) { + /* we must purge remaining reqid */ + PCEP_DEBUG_PATH("%s Purge pending reqid: no_path(%s)", + __func__, + path->no_path ? "TRUE" : "FALSE"); + if (lookup_reqid(pcc_state, path) != 0) { + PCEP_DEBUG_PATH("%s Purge pending reqid: DONE ", + __func__); + remove_reqid_mapping(pcc_state, path); + return true; + } else { + return false; + } + } + } + + + return lookup_reqid(pcc_state, path) != 0; +} + + +/* ------------ Data Structure Callbacks ------------ */ + +#define CMP_RETURN(A, B) \ + if (A != B) \ + return (A < B) ? -1 : 1 + +static uint32_t hash_nbkey(const struct lsp_nb_key *nbkey) +{ + uint32_t hash; + hash = jhash_2words(nbkey->color, nbkey->preference, 0x55aa5a5a); + switch (nbkey->endpoint.ipa_type) { + case IPADDR_V4: + return jhash(&nbkey->endpoint.ipaddr_v4, + sizeof(nbkey->endpoint.ipaddr_v4), hash); + case IPADDR_V6: + return jhash(&nbkey->endpoint.ipaddr_v6, + sizeof(nbkey->endpoint.ipaddr_v6), hash); + case IPADDR_NONE: + return hash; + } + + assert(!"Reached end of function where we were not expecting to"); +} + +static int cmp_nbkey(const struct lsp_nb_key *a, const struct lsp_nb_key *b) +{ + CMP_RETURN(a->color, b->color); + int cmp = ipaddr_cmp(&a->endpoint, &b->endpoint); + if (cmp != 0) + return cmp; + CMP_RETURN(a->preference, b->preference); + return 0; +} + +int plspid_map_cmp(const struct plspid_map_data *a, + const struct plspid_map_data *b) +{ + return cmp_nbkey(&a->nbkey, &b->nbkey); +} + +uint32_t plspid_map_hash(const struct plspid_map_data *e) +{ + return hash_nbkey(&e->nbkey); +} + +int nbkey_map_cmp(const struct nbkey_map_data *a, + const struct nbkey_map_data *b) +{ + CMP_RETURN(a->plspid, b->plspid); + return 0; +} + +uint32_t nbkey_map_hash(const struct nbkey_map_data *e) +{ + return e->plspid; +} + +int req_map_cmp(const struct req_map_data *a, const struct req_map_data *b) +{ + return cmp_nbkey(&a->nbkey, &b->nbkey); +} + +uint32_t req_map_hash(const struct req_map_data *e) +{ + return hash_nbkey(&e->nbkey); +} diff --git a/pathd/path_pcep_pcc.h b/pathd/path_pcep_pcc.h new file mode 100644 index 0000000..f3d0f29 --- /dev/null +++ b/pathd/path_pcep_pcc.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _PATH_PCEP_PCC_H_ +#define _PATH_PCEP_PCC_H_ + +#include "typesafe.h" +#include "pathd/path_pcep.h" + +enum pcc_status { + PCEP_PCC_INITIALIZED = 0, + PCEP_PCC_DISCONNECTED, + PCEP_PCC_CONNECTING, + PCEP_PCC_SYNCHRONIZING, + PCEP_PCC_OPERATING +}; + +PREDECL_HASH(plspid_map); +PREDECL_HASH(nbkey_map); +PREDECL_HASH(req_map); + +struct plspid_map_data { + struct plspid_map_item mi; + struct lsp_nb_key nbkey; + uint32_t plspid; +}; + +struct nbkey_map_data { + struct nbkey_map_item mi; + struct lsp_nb_key nbkey; + uint32_t plspid; +}; + +struct req_map_data { + struct req_map_item mi; + struct lsp_nb_key nbkey; + uint32_t reqid; +}; + +struct req_entry { + RB_ENTRY(req_entry) entry; + struct event *t_retry; + int retry_count; + bool was_sent; + struct path *path; +}; +RB_HEAD(req_entry_head, req_entry); +RB_PROTOTYPE(req_entry_head, req_entry, entry, req_entry_compare); + +struct pcc_state { + int id; + char tag[MAX_TAG_SIZE]; + enum pcc_status status; + uint16_t flags; +#define F_PCC_STATE_HAS_IPV4 0x0002 +#define F_PCC_STATE_HAS_IPV6 0x0004 + struct pcc_opts *pcc_opts; + struct pce_opts *pce_opts; + struct in_addr pcc_addr_v4; + struct in6_addr pcc_addr_v6; + /* PCC transport source address */ + struct ipaddr pcc_addr_tr; + char *originator; + pcep_session *sess; + uint32_t retry_count; + bool synchronized; + struct event *t_reconnect; + struct event *t_update_best; + struct event *t_session_timeout; + uint32_t next_reqid; + uint32_t next_plspid; + struct plspid_map_head plspid_map; + struct nbkey_map_head nbkey_map; + struct req_map_head req_map; + struct req_entry_head requests; + struct pcep_caps caps; + bool is_best; + bool previous_best; +}; + +struct pcc_state *pcep_pcc_initialize(struct ctrl_state *ctrl_state, + int pcc_id); +void pcep_pcc_finalize(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +int pcep_pcc_enable(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state); +int pcep_pcc_disable(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +int pcep_pcc_update(struct ctrl_state *ctrl_state, struct pcc_state *pcc_state, + struct pcc_opts *pcc_opts, struct pce_opts *pce_opts); +void pcep_pcc_reconnect(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +void pcep_pcc_pcep_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + pcep_event *event); +void pcep_pcc_pathd_event_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_pathd_event_type type, + struct path *path); +void pcep_pcc_timeout_handler(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, + enum pcep_ctrl_timeout_type type, void *param); +void pcep_pcc_sync_path(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path); +void pcep_pcc_sync_done(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state); +/* Send a report explicitly. When doing so the PCC may send multiple reports + * due to expectations from vendors for the first report to be with a DOWN + * status. The parameter is_stable is used for that purpose as a hint wheter + * to expect an update for the report */ +void pcep_pcc_send_report(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct path *path, + bool is_stable); +void pcep_pcc_send_error(struct ctrl_state *ctrl_state, + struct pcc_state *pcc_state, struct pcep_error *path, + bool is_stable); +int pcep_pcc_multi_pce_sync_path(struct ctrl_state *ctrl_state, int pcc_id, + struct pcc_state **pcc_state_list); +int pcep_pcc_multi_pce_remove_pcc(struct ctrl_state *ctrl_state, + struct pcc_state **pcc_state_list); +int pcep_pcc_timer_update_best_pce(struct ctrl_state *ctrl_state, int pcc_id); +int pcep_pcc_calculate_best_pce(struct pcc_state **pcc); +int pcep_pcc_get_pcc_id_by_ip_port(struct pcc_state **pcc, + struct pce_opts *pce_opts); +int pcep_pcc_get_pcc_id_by_idx(struct pcc_state **pcc, int idx); +struct pcc_state *pcep_pcc_get_pcc_by_id(struct pcc_state **pcc, int id); +struct pcc_state *pcep_pcc_get_pcc_by_name(struct pcc_state **pcc, + const char *pce_name); +int pcep_pcc_get_pcc_idx_by_id(struct pcc_state **pcc, int id); +int pcep_pcc_get_free_pcc_idx(struct pcc_state **pcc); +int pcep_pcc_get_pcc_id(struct pcc_state *pcc); +void pcep_pcc_copy_pcc_info(struct pcc_state **pcc, + struct pcep_pcc_info *pcc_info); + +#endif // _PATH_PCEP_PCC_H_ diff --git a/pathd/path_ted.c b/pathd/path_ted.c new file mode 100644 index 0000000..df23f93 --- /dev/null +++ b/pathd/path_ted.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Volta Networks, Inc + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include "memory.h" +#include "log.h" +#include "command.h" +#include "prefix.h" +#include + +#include "pathd.h" +#include "pathd/path_errors.h" +#include "pathd/path_ted.h" + +#include "pathd/path_ted_clippy.c" + +static struct ls_ted *path_ted_create_ted(void); +static void path_ted_register_vty(void); +static void path_ted_unregister_vty(void); +static uint32_t path_ted_start_importing_igp(const char *daemon_str); +static uint32_t path_ted_stop_importing_igp(void); +static enum zclient_send_status path_ted_link_state_sync(void); +static void path_ted_timer_handler_sync(struct event *thread); +static void path_ted_timer_handler_refresh(struct event *thread); +static int path_ted_cli_debug_config_write(struct vty *vty); +static int path_ted_cli_debug_set_all(uint32_t flags, bool set); + +extern struct zclient *zclient; + +struct ted_state ted_state_g = {}; + +/* + * path_path_ted public API function implementations + */ + +void path_ted_init(struct event_loop *master) +{ + ted_state_g.main = master; + ted_state_g.link_state_delay_interval = TIMER_RETRY_DELAY; + ted_state_g.segment_list_refresh_interval = TIMER_RETRY_DELAY; + path_ted_register_vty(); + path_ted_segment_list_refresh(); +} + +uint32_t path_ted_teardown(void) +{ + PATH_TED_DEBUG("%s : TED [%p]", __func__, ted_state_g.ted); + path_ted_unregister_vty(); + path_ted_stop_importing_igp(); + ls_ted_del_all(&ted_state_g.ted); + path_ted_timer_sync_cancel(); + path_ted_timer_refresh_cancel(); + return 0; +} + +/** + * Set all needed to receive igp data. + * + * @return true if ok + * + */ +uint32_t path_ted_start_importing_igp(const char *daemon_str) +{ + uint32_t status = 0; + + if (strcmp(daemon_str, "ospfv2") == 0) + ted_state_g.import = IMPORT_OSPFv2; + else if (strcmp(daemon_str, "ospfv3") == 0) { + ted_state_g.import = IMPORT_UNKNOWN; + return 1; + } else if (strcmp(daemon_str, "isis") == 0) + ted_state_g.import = IMPORT_ISIS; + else { + ted_state_g.import = IMPORT_UNKNOWN; + return 1; + } + + if (ls_register(zclient, false /*client*/) != 0) { + PATH_TED_ERROR("%s: PATHD-TED: Unable to register Link State", + __func__); + ted_state_g.import = IMPORT_UNKNOWN; + status = 1; + } else { + if (path_ted_link_state_sync() != -1) { + PATH_TED_DEBUG("%s: PATHD-TED: Importing %s data ON", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import)); + } else { + PATH_TED_WARN("%s: PATHD-TED: Importing %s data OFF", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import)); + ted_state_g.import = IMPORT_UNKNOWN; + } + } + return status; +} + +/** + * Unset all needed to receive igp data. + * + * @return true if ok + * + */ +uint32_t path_ted_stop_importing_igp(void) +{ + uint32_t status = 0; + + if (ted_state_g.import != IMPORT_UNKNOWN) { + if (ls_unregister(zclient, false /*client*/) != 0) { + PATH_TED_ERROR( + "%s: PATHD-TED: Unable to unregister Link State", + __func__); + status = 1; + } else { + ted_state_g.import = IMPORT_UNKNOWN; + PATH_TED_DEBUG("%s: PATHD-TED: Importing igp data OFF", + __func__); + } + path_ted_timer_sync_cancel(); + } + return status; +} +/** + * Check for ted status + * + * @return true if ok + * + */ +bool path_ted_is_initialized(void) +{ + if (ted_state_g.ted == NULL) { + PATH_TED_WARN("PATHD TED ls_ted not initialized"); + return false; + } + + return true; +} + +/** + * Creates an empty ted + * + * @param void + * + * @return Ptr to ted or NULL + */ +struct ls_ted *path_ted_create_ted(void) +{ + struct ls_ted *ted = ls_ted_new(TED_KEY, TED_NAME, TED_ASN); + + if (ted == NULL) { + PATH_TED_ERROR("%s Unable to initialize TED Key [%d] ASN [%d] Name [%s]", + __func__, TED_KEY, TED_ASN, TED_NAME); + } else { + PATH_TED_INFO("%s Initialize TED Key [%d] ASN [%d] Name [%s]", + __func__, TED_KEY, TED_ASN, TED_NAME); + } + + return ted; +} + +uint32_t path_ted_rcvd_message(struct ls_message *msg) +{ + if (!path_ted_is_initialized()) + return 1; + + if (msg == NULL) { + PATH_TED_ERROR("%s: [rcv ted] TED received NULL message ", + __func__); + return 1; + } + + if (path_ted_get_current_igp(msg->data.node->adv.origin)) + return 1; + + switch (msg->type) { + case LS_MSG_TYPE_NODE: + ls_msg2vertex(ted_state_g.ted, msg, true /*hard delete*/); + break; + + case LS_MSG_TYPE_ATTRIBUTES: + ls_msg2edge(ted_state_g.ted, msg, true /*ĥard delete*/); + break; + + case LS_MSG_TYPE_PREFIX: + ls_msg2subnet(ted_state_g.ted, msg, true /*hard delete*/); + break; + + default: + PATH_TED_DEBUG( + "%s: [rcv ted] TED received unknown message type [%d]", + __func__, msg->type); + break; + } + return 0; +} + +uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_edge *edge; + struct ls_edge_key key; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!local || !remote) + return MPLS_LABEL_NONE; + + switch (local->ipa_type) { + case IPADDR_V4: + /* We have local and remote ip */ + /* so check all attributes in ted */ + key.family = AF_INET; + IPV4_ADDR_COPY(&key.k.addr, &local->ip._v4_addr); + edge = ls_find_edge_by_key(ted_state_g.ted, key); + if (edge) { + if (edge->attributes->standard.remote.s_addr + == remote->ip._v4_addr.s_addr + && CHECK_FLAG(edge->attributes->flags, + LS_ATTR_ADJ_SID)) { + sid = edge->attributes->adj_sid[0] + .sid; /* from primary */ + break; + } + } + break; + case IPADDR_V6: + key.family = AF_INET6; + IPV6_ADDR_COPY(&key.k.addr6, &local->ip._v6_addr); + edge = ls_find_edge_by_key(ted_state_g.ted, key); + if (edge) { + if ((0 == memcmp(&edge->attributes->standard.remote6, + &remote->ip._v6_addr, + sizeof(remote->ip._v6_addr)) && + CHECK_FLAG(edge->attributes->flags, + LS_ATTR_ADJ_SID6))) { + sid = edge->attributes->adj_sid[ADJ_PRI_IPV6] + .sid; /* from primary */ + break; + } + } + break; + case IPADDR_NONE: + break; + } + + return sid; +} + +uint32_t path_ted_query_type_c(struct prefix *prefix, uint8_t algo) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_subnet *subnet; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!prefix) + return MPLS_LABEL_NONE; + + switch (prefix->family) { + case AF_INET: + case AF_INET6: + subnet = ls_find_subnet(ted_state_g.ted, prefix); + if (subnet) { + if ((CHECK_FLAG(subnet->ls_pref->flags, LS_PREF_SR)) + && (subnet->ls_pref->sr.algo == algo)) + sid = subnet->ls_pref->sr.sid; + } + break; + default: + break; + } + + return sid; +} + +uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id) +{ + uint32_t sid = MPLS_LABEL_NONE; + struct ls_subnet *subnet; + struct listnode *lst_node; + struct ls_edge *edge; + + if (!path_ted_is_initialized()) + return MPLS_LABEL_NONE; + + if (!prefix) + return MPLS_LABEL_NONE; + + switch (prefix->family) { + case AF_INET: + case AF_INET6: + subnet = ls_find_subnet(ted_state_g.ted, prefix); + if (subnet && subnet->vertex + && subnet->vertex->outgoing_edges) { + /* from the vertex linked in subnet */ + /* loop over outgoing edges */ + for (ALL_LIST_ELEMENTS_RO( + subnet->vertex->outgoing_edges, lst_node, + edge)) { + /* and look for ifaceid */ + /* so get sid of attribute */ + if (CHECK_FLAG(edge->attributes->flags, + LS_ATTR_LOCAL_ID) + && edge->attributes->standard.local_id + == iface_id) { + sid = subnet->ls_pref->sr.sid; + break; + } + } + } + break; + default: + break; + } + + return sid; +} + +DEFPY (debug_path_ted, + debug_path_ted_cmd, + "[no] debug pathd mpls-te", + NO_STR + DEBUG_STR + "path debugging\n" + "ted debugging\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + bool no_debug = (no != NULL); + + DEBUG_MODE_SET(&ted_state_g.dbg, mode, !no); + DEBUG_FLAGS_SET(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC, !no_debug); + return CMD_SUCCESS; +} + +/* + * Following are vty command functions. + */ +/* clang-format off */ +DEFUN (path_ted_on, + path_ted_on_cmd, + "mpls-te on", + NO_STR + "Enable the TE database (TED) functionality\n") +/* clang-format on */ +{ + + if (ted_state_g.enabled) { + PATH_TED_DEBUG("%s: PATHD-TED: Enabled ON -> ON.", __func__); + return CMD_SUCCESS; + } + + ted_state_g.ted = path_ted_create_ted(); + ted_state_g.enabled = true; + PATH_TED_DEBUG("%s: PATHD-TED: Enabled OFF -> ON.", __func__); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN (no_path_ted, + no_path_ted_cmd, + "no mpls-te [on]", + NO_STR + NO_STR + "Disable the TE Database functionality\n") +/* clang-format on */ +{ + if (!ted_state_g.enabled) { + PATH_TED_DEBUG("%s: PATHD-TED: OFF -> OFF", __func__); + return CMD_SUCCESS; + } + + /* Remove TED */ + ls_ted_del_all(&ted_state_g.ted); + ted_state_g.enabled = false; + PATH_TED_DEBUG("%s: PATHD-TED: ON -> OFF", __func__); + ted_state_g.import = IMPORT_UNKNOWN; + if (ls_unregister(zclient, false /*client*/) != 0) { + vty_out(vty, "Unable to unregister Link State\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY(path_ted_import, + path_ted_import_cmd, + "mpls-te import $import_daemon", + "Enable the TE database (TED) fill with remote igp data\n" + "import\n" + "Origin ospfv2\n" + "Origin ospfv3\n" + "Origin isis\n") +/* clang-format on */ +{ + + if (ted_state_g.enabled) + if (path_ted_start_importing_igp(import_daemon)) { + vty_out(vty, "Unable to start importing\n"); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN (no_path_ted_import, + no_path_ted_import_cmd, + "no mpls-te import", + NO_STR + NO_STR + "Disable the TE Database fill with remote igp data\n") +/* clang-format on */ +{ + + if (ted_state_g.import) { + if (path_ted_stop_importing_igp()) { + vty_out(vty, "Unable to stop importing\n"); + return CMD_WARNING; + } else { + PATH_TED_DEBUG( + "%s: PATHD-TED: Importing igp data already OFF", + __func__); + } + } + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (show_pathd_ted_db, + show_pathd_ted_db_cmd, + "show pathd ted database $ver_json ", + "show command\n" + "pathd daemon\n" + "traffic eng\n" + "database\n" + "verbose output\n" + "Show complete received TED database\n") +/* clang-format on */ +{ + bool st_json = false; + json_object *json = NULL; + + if (!ted_state_g.enabled) { + vty_out(vty, "Traffic Engineering database is not enabled\n"); + return CMD_WARNING; + } + if (strcmp(ver_json, "json") == 0) { + st_json = true; + json = json_object_new_object(); + } + /* Show the complete TED */ + ls_show_ted(ted_state_g.ted, vty, json, !st_json); + if (st_json) + vty_json(vty, json); + return CMD_SUCCESS; +} + +/* + * Config Write functions + */ + +int path_ted_cli_debug_config_write(struct vty *vty) +{ + if (DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_CONF)) { + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) + vty_out(vty, "debug pathd mpls-te\n"); + return 1; + } + return 0; +} + +void path_ted_show_debugging(struct vty *vty) +{ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) + vty_out(vty, " Path TED debugging is on\n"); +} + +int path_ted_cli_debug_set_all(uint32_t flags, bool set) +{ + DEBUG_FLAGS_SET(&ted_state_g.dbg, flags, set); + + /* If all modes have been turned off, don't preserve options. */ + if (!DEBUG_MODE_CHECK(&ted_state_g.dbg, DEBUG_MODE_ALL)) + DEBUG_CLEAR(&ted_state_g.dbg); + + return 0; +} + +/** + * Help fn to show ted related configuration + * + * @param vty + * + * @return Status + */ +uint32_t path_ted_config_write(struct vty *vty) +{ + + if (ted_state_g.enabled) { + vty_out(vty, " mpls-te on\n"); + switch (ted_state_g.import) { + case IMPORT_ISIS: + vty_out(vty, " mpls-te import isis\n"); + break; + case IMPORT_OSPFv2: + vty_out(vty, " mpls-te import ospfv2\n"); + break; + case IMPORT_OSPFv3: + vty_out(vty, " mpls-te import ospfv3\n"); + break; + case IMPORT_UNKNOWN: + break; + } + } + return 0; +} + +/** + * Register the fn's for CLI and hook for config show + * + * @param void + * + */ +static void path_ted_register_vty(void) +{ + install_element(VIEW_NODE, &show_pathd_ted_db_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd); + install_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd); + + install_element(CONFIG_NODE, &debug_path_ted_cmd); + install_element(ENABLE_NODE, &debug_path_ted_cmd); + + hook_register(nb_client_debug_config_write, + path_ted_cli_debug_config_write); + hook_register(nb_client_debug_set_all, path_ted_cli_debug_set_all); +} + +/** + * UnRegister the fn's for CLI and hook for config show + * + * @param void + * + */ +static void path_ted_unregister_vty(void) +{ + uninstall_element(VIEW_NODE, &show_pathd_ted_db_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_on_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &path_ted_import_cmd); + uninstall_element(SR_TRAFFIC_ENG_NODE, &no_path_ted_import_cmd); +} + +/** + * Ask igp for a complete TED so far + * + * @param void + * + * @return zclient status + */ +enum zclient_send_status path_ted_link_state_sync(void) +{ + enum zclient_send_status status; + + status = ls_request_sync(zclient); + if (status == -1) { + PATH_TED_ERROR( + "%s: PATHD-TED: Opaque error asking for TED sync ", + __func__); + return status; + } else { + PATH_TED_DEBUG("%s: PATHD-TED: Opaque asked for TED sync ", + __func__); + } + event_add_timer(ted_state_g.main, path_ted_timer_handler_sync, + &ted_state_g, ted_state_g.link_state_delay_interval, + &ted_state_g.t_link_state_sync); + + return status; +} + +/** + * Timer cb for check link state sync + * + * @param thread Current thread + * + * @return status + */ +void path_ted_timer_handler_sync(struct event *thread) +{ + /* data unpacking */ + struct ted_state *data = EVENT_ARG(thread); + + assert(data != NULL); + /* Retry the sync */ + path_ted_link_state_sync(); +} + +/** + * refresg segment list and create timer to keep up updated + * + * @param void + * + * @return status + */ +int path_ted_segment_list_refresh(void) +{ + int status = 0; + + path_ted_timer_refresh_cancel(); + event_add_timer(ted_state_g.main, path_ted_timer_handler_refresh, + &ted_state_g, ted_state_g.segment_list_refresh_interval, + &ted_state_g.t_segment_list_refresh); + + return status; +} + +/** + * Timer cb for refreshing sid in segment lists + * + * @param void + * + * @return status + */ +void path_ted_timer_handler_refresh(struct event *thread) +{ + if (!path_ted_is_initialized()) + return; + + PATH_TED_DEBUG("%s: PATHD-TED: Refresh sid from current TED", __func__); + /* data unpacking */ + struct ted_state *data = EVENT_ARG(thread); + + assert(data != NULL); + + srte_policy_update_ted_sid(); +} + +/** + * Cancel sync timer + * + * @param void + * + * @return void status + */ +void path_ted_timer_sync_cancel(void) +{ + if (ted_state_g.t_link_state_sync != NULL) { + event_cancel(&ted_state_g.t_link_state_sync); + ted_state_g.t_link_state_sync = NULL; + } +} + +/** + * Cancel refresh timer + * + * @param void + * + * @return void status + */ +void path_ted_timer_refresh_cancel(void) +{ + if (ted_state_g.t_segment_list_refresh != NULL) { + event_cancel(&ted_state_g.t_segment_list_refresh); + ted_state_g.t_segment_list_refresh = NULL; + } +} + +/** + * Check which igp is configured + * + * @param igp who want to check against config- + * + * @return status + */ +uint32_t path_ted_get_current_igp(uint32_t igp) +{ + switch (igp) { + case ISIS_L1: + case ISIS_L2: + if (ted_state_g.import != IMPORT_ISIS) { + PATH_TED_ERROR( + "%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import), + LS_IGP_PRINT(igp)); + return 1; + } + break; + case OSPFv2: + if (ted_state_g.import != IMPORT_OSPFv2) { + PATH_TED_ERROR( + "%s: [rcv ted] Incorrect igp origin wait (%s) got (%s) ", + __func__, + PATH_TED_IGP_PRINT(ted_state_g.import), + LS_IGP_PRINT(igp)); + return 1; + } + break; + case STATIC: + break; + } + return 0; +} diff --git a/pathd/path_ted.h b/pathd/path_ted.h new file mode 100644 index 0000000..a1bc784 --- /dev/null +++ b/pathd/path_ted.h @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Volta Networks, Inc + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + */ + +#ifndef _PATH_TED_H +#define _PATH_TED_H + +#ifdef __cplusplus + +extern "C" { +#endif + +#include + +#include + +#include +#include "linklist.h" +#include "log.h" +#include "command.h" +#include "stream.h" +#include "prefix.h" +#include "zclient.h" +#include "link_state.h" + +extern struct ted_state ted_state_g; +#define TIMER_RETRY_DELAY 5 /* Timeout in seconds between ls sync request */ +#define TED_KEY 1 +#define TED_ASN 1 +#define TED_NAME "PATHD TED" + +enum igp_import { + IMPORT_UNKNOWN = 0, + IMPORT_ISIS, + IMPORT_OSPFv2, + IMPORT_OSPFv3 +}; +struct ted_state { + struct event_loop *main; + /* Status of TED: enable or disable */ + bool enabled; + /* From which igp is going to receive data */ + enum igp_import import; + /* The TED itself as in link_state.h */ + struct ls_ted *ted; + /* Timer for ted sync */ + struct event *t_link_state_sync; + /* Timer for refresh sid in segment list */ + struct event *t_segment_list_refresh; + /* delay interval in seconds */ + uint32_t link_state_delay_interval; + /* delay interval refresh in seconds */ + uint32_t segment_list_refresh_interval; + struct debug dbg; +}; +/* Debug flags. */ +#define PATH_TED_DEBUG_BASIC 0x01 +#define PATH_TED_DEBUG(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGD(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) + +#define PATH_TED_ERROR(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGE(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PATH_TED_WARN(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGW(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) +#define PATH_TED_INFO(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&ted_state_g.dbg, PATH_TED_DEBUG_BASIC)) \ + DEBUGI(&ted_state_g.dbg, "mpls-te: " fmt, ##__VA_ARGS__); \ + } while (0) + +/* TED management functions */ +bool path_ted_is_initialized(void); +void path_ted_init(struct event_loop *master); +uint32_t path_ted_teardown(void); +void path_ted_timer_sync_cancel(void); +void path_ted_timer_refresh_cancel(void); +int path_ted_segment_list_refresh(void); + +/* TED configuration functions */ +uint32_t path_ted_config_write(struct vty *vty); +void path_ted_show_debugging(struct vty *vty); + +/* TED util functions */ +/* clang-format off */ +#define LS_MSG_EVENT_PRINT(event) event == LS_MSG_EVENT_ADD?"add"\ + : event == LS_MSG_EVENT_DELETE?"del"\ + : event == LS_MSG_EVENT_UPDATE?"upd"\ + : event == LS_MSG_EVENT_SYNC?"syn"\ + : event == LS_MSG_EVENT_SYNC?"und" : "none" +#define LS_MSG_TYPE_PRINT(type) type == LS_MSG_TYPE_NODE?"node"\ + : type == LS_MSG_TYPE_ATTRIBUTES?"att"\ + : type == LS_MSG_TYPE_PREFIX?"pre" : "none" +#define LS_IGP_PRINT(type) type == ISIS_L1?"ISIS_L1"\ + : type == ISIS_L2?"ISIS_L2"\ + : type == DIRECT?"DIRECT"\ + : type == STATIC?"STATIC"\ + : type == OSPFv2?"OSPFv2" : "none" +#define PATH_TED_IGP_PRINT(type) type == IMPORT_OSPFv2?"OSPFv2"\ + : type == IMPORT_OSPFv3?"OSPFv3"\ + : type == IMPORT_ISIS?"ISIS" : "none" +/* clang-format on */ + + +uint32_t path_ted_get_current_igp(uint32_t); +/* TED Query functions */ + +/* + * Type of queries from draft-ietf-spring-segment-routing-policy-07 for types + * f,c,e + */ + +/** + * Search for sid based in prefix and optional algo + * + * @param prefix Net prefix to resolv + * @param algo Algorithm for link state + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_c(struct prefix *prefix, uint8_t algo); + +/** + * Search for sid based in prefix and interface id + * + * @param prefix Net prefix to resolv + * @param iface_id The interface id + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_e(struct prefix *prefix, uint32_t iface_id); + +/** + * Search for sid based in local, remote pair + * + * @param local local ip of attribute + * @param remote remote ip of attribute + * + * @return sid of attribute + */ +uint32_t path_ted_query_type_f(struct ipaddr *local, struct ipaddr *remote); + + +/** + * Handle the received opaque msg + * + * @param msg Holds the ted data + * + * @return sid of attribute + */ +uint32_t path_ted_rcvd_message(struct ls_message *msg); + +#ifdef __cplusplus +} +#endif + +#endif /* _PATH_TED_H */ diff --git a/pathd/path_zebra.c b/pathd/path_zebra.c new file mode 100644 index 0000000..ba03315 --- /dev/null +++ b/pathd/path_zebra.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "frrevent.h" +#include "log.h" +#include "lib_errors.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "network.h" +#include "stream.h" +#include "linklist.h" +#include "nexthop.h" +#include "vrf.h" +#include "typesafe.h" + +#include "pathd/pathd.h" +#include "pathd/path_ted.h" +#include "pathd/path_zebra.h" +#include "lib/command.h" +#include "lib/link_state.h" + +static int path_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS); + +struct zclient *zclient; +static struct zclient *zclient_sync; + +/* Event to retry synch zapi setup for label-manager */ +static struct event *t_sync_connect; + +enum path_sync_level { + PATH_SYNC_NONE = 0, + PATH_SYNC_CONN, + PATH_SYNC_HELLO, + PATH_SYNC_DONE +}; +static enum path_sync_level path_sync_client_level; + +/* Global Variables */ +bool g_has_router_id_v4 = false; +bool g_has_router_id_v6 = false; +struct in_addr g_router_id_v4; +struct in6_addr g_router_id_v6; +pthread_mutex_t g_router_id_v4_mtx = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t g_router_id_v6_mtx = PTHREAD_MUTEX_INITIALIZER; + +/** + * Gives the IPv4 router ID received from Zebra. + * + * @param router_id The in_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv4_router_id(struct in_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v4_mtx); + if (g_has_router_id_v4) { + memcpy(router_id, &g_router_id_v4, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v4_mtx); + return retval; +} + +/** + * Gives the IPv6 router ID received from Zebra. + * + * @param router_id The in6_addr strucure where to store the router id + * @return true if the router ID was available, false otherwise + */ +bool get_ipv6_router_id(struct in6_addr *router_id) +{ + bool retval = false; + assert(router_id != NULL); + pthread_mutex_lock(&g_router_id_v6_mtx); + if (g_has_router_id_v6) { + memcpy(router_id, &g_router_id_v6, sizeof(*router_id)); + retval = true; + } + pthread_mutex_unlock(&g_router_id_v6_mtx); + return retval; +} + +static void path_zebra_connected(struct zclient *zclient) +{ + struct srte_policy *policy; + + zclient_send_reg_requests(zclient, VRF_DEFAULT); + zclient_send_router_id_update(zclient, ZEBRA_ROUTER_ID_ADD, AFI_IP6, + VRF_DEFAULT); + + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + struct srte_candidate *candidate; + struct srte_segment_list *segment_list; + + candidate = policy->best_candidate; + if (!candidate) + continue; + + segment_list = candidate->lsp->segment_list; + if (!segment_list) + continue; + + path_zebra_add_sr_policy(policy, segment_list); + } +} + +static int path_zebra_sr_policy_notify_status(ZAPI_CALLBACK_ARGS) +{ + struct zapi_sr_policy zapi_sr_policy; + struct srte_policy *policy; + struct srte_candidate *best_candidate_path; + + if (zapi_sr_policy_notify_status_decode(zclient->ibuf, &zapi_sr_policy)) + return -1; + + policy = srte_policy_find(zapi_sr_policy.color, + &zapi_sr_policy.endpoint); + if (!policy) + return -1; + + best_candidate_path = policy->best_candidate; + if (!best_candidate_path) + return -1; + + srte_candidate_status_update(best_candidate_path, + zapi_sr_policy.status); + + return 0; +} + +/* Router-id update message from zebra. */ +static int path_zebra_router_id_update(ZAPI_CALLBACK_ARGS) +{ + struct prefix pref; + const char *family; + char buf[PREFIX2STR_BUFFER]; + zebra_router_id_update_read(zclient->ibuf, &pref); + if (pref.family == AF_INET) { + pthread_mutex_lock(&g_router_id_v4_mtx); + memcpy(&g_router_id_v4, &pref.u.prefix4, + sizeof(g_router_id_v4)); + g_has_router_id_v4 = true; + inet_ntop(AF_INET, &g_router_id_v4, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v4_mtx); + family = "IPv4"; + } else if (pref.family == AF_INET6) { + pthread_mutex_lock(&g_router_id_v6_mtx); + memcpy(&g_router_id_v6, &pref.u.prefix6, + sizeof(g_router_id_v6)); + g_has_router_id_v6 = true; + inet_ntop(AF_INET6, &g_router_id_v6, buf, sizeof(buf)); + pthread_mutex_unlock(&g_router_id_v6_mtx); + family = "IPv6"; + } else { + zlog_warn("Unexpected router ID address family for vrf %u: %u", + vrf_id, pref.family); + return 0; + } + zlog_info("%s Router Id updated for VRF %u: %s", family, vrf_id, buf); + return 0; +} + +/** + * Adds a segment routing policy to Zebra. + * + * @param policy The policy to add + * @param segment_list The segment list for the policy + */ +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list) +{ + struct zapi_sr_policy zp = {}; + struct srte_segment_entry *segment; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + RB_FOREACH (segment, srte_segment_entry_head, &segment_list->segments) + zp.segment_list.labels[zp.segment_list.label_num++] = + segment->sid_value; + policy->status = SRTE_POLICY_STATUS_GOING_UP; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_SET, &zp); +} + +/** + * Deletes a segment policy from Zebra. + * + * @param policy The policy to remove + */ +void path_zebra_delete_sr_policy(struct srte_policy *policy) +{ + struct zapi_sr_policy zp = {}; + + zp.color = policy->color; + zp.endpoint = policy->endpoint; + strlcpy(zp.name, policy->name, sizeof(zp.name)); + zp.segment_list.type = ZEBRA_LSP_SRTE; + zp.segment_list.local_label = policy->binding_sid; + zp.segment_list.label_num = 0; + policy->status = SRTE_POLICY_STATUS_DOWN; + + (void)zebra_send_sr_policy(zclient, ZEBRA_SR_POLICY_DELETE, &zp); +} + +/** + * Allocates a label from Zebra's label manager. + * + * @param label the label to be allocated + * @return 0 if the label has been allocated, -1 otherwise + */ +int path_zebra_request_label(mpls_label_t label) +{ + int ret; + uint32_t start, end; + + ret = lm_get_label_chunk(zclient_sync, 0, label, 1, &start, &end); + if (ret < 0) { + zlog_warn("%s: error getting label range!", __func__); + return -1; + } + + return 0; +} + +/** + * Releases a previously allocated label from Zebra's label manager. + * + * @param label The label to release + * @return 0 ifthe label has beel released, -1 otherwise + */ +void path_zebra_release_label(mpls_label_t label) +{ + int ret; + + ret = lm_release_label_chunk(zclient_sync, label, label); + if (ret < 0) + zlog_warn("%s: error releasing label range!", __func__); +} + +/* + * Initialize and connect the synchronous zclient session for the + * label-manager. This is prepared to retry on error. + */ +static void path_zebra_label_manager_connect(struct event *event) +{ + if (path_sync_client_level == PATH_SYNC_NONE) { + /* Connect to label manager. */ + if (zclient_socket_connect(zclient_sync) < 0) { + zlog_warn("%s: error connecting synchronous zclient!", + __func__); + event_add_timer(master, path_zebra_label_manager_connect, + NULL, 1, &t_sync_connect); + return; + } + set_nonblocking(zclient_sync->sock); + + path_sync_client_level = PATH_SYNC_CONN; + } + + /* Send hello to notify zebra this is a synchronous client */ + if (path_sync_client_level == PATH_SYNC_CONN) { + if (zclient_send_hello(zclient_sync) == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: Error sending hello for synchronous zclient!", + __func__); + event_add_timer(master, path_zebra_label_manager_connect, + NULL, 1, &t_sync_connect); + return; + } + + path_sync_client_level = PATH_SYNC_HELLO; + } + + if (path_sync_client_level == PATH_SYNC_HELLO) { + if (lm_label_manager_connect(zclient_sync, 0) != 0) { + zlog_warn("%s: error connecting to label manager!", + __func__); + event_add_timer(master, path_zebra_label_manager_connect, + NULL, 1, &t_sync_connect); + return; + } + path_sync_client_level = PATH_SYNC_DONE; + } +} + +static int path_zebra_opaque_msg_handler(ZAPI_CALLBACK_ARGS) +{ + int ret = 0; + struct stream *s; + struct zapi_opaque_msg info; + + s = zclient->ibuf; + + if (zclient_opaque_decode(s, &info) != 0) + return -1; + + switch (info.type) { + case LINK_STATE_UPDATE: + case LINK_STATE_SYNC: + /* Start receiving ls data so cancel request sync timer */ + path_ted_timer_sync_cancel(); + + struct ls_message *msg = ls_parse_msg(s); + + if (msg) { + zlog_debug("%s: [rcv ted] ls (%s) msg (%s)-(%s) !", + __func__, + info.type == LINK_STATE_UPDATE + ? "LINK_STATE_UPDATE" + : "LINK_STATE_SYNC", + LS_MSG_TYPE_PRINT(msg->type), + LS_MSG_EVENT_PRINT(msg->event)); + } else { + zlog_err( + "%s: [rcv ted] Could not parse LinkState stream message.", + __func__); + return -1; + } + + ret = path_ted_rcvd_message(msg); + ls_delete_msg(msg); + /* Update local configuration after process update. */ + path_ted_segment_list_refresh(); + break; + default: + zlog_debug("%s: [rcv ted] unknown opaque event (%d) !", + __func__, info.type); + break; + } + + return ret; +} + +static zclient_handler *const path_handlers[] = { + [ZEBRA_SR_POLICY_NOTIFY_STATUS] = path_zebra_sr_policy_notify_status, + [ZEBRA_ROUTER_ID_UPDATE] = path_zebra_router_id_update, + [ZEBRA_OPAQUE_MESSAGE] = path_zebra_opaque_msg_handler, +}; + +/** + * Initializes Zebra asynchronous connection. + * + * @param master The master thread + */ +void path_zebra_init(struct event_loop *master) +{ + /* Initialize asynchronous zclient. */ + zclient = zclient_new(master, &zclient_options_default, path_handlers, + array_size(path_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_SRTE, 0, &pathd_privs); + zclient->zebra_connected = path_zebra_connected; + + /* Initialize special zclient for synchronous message exchanges. */ + zclient_sync = zclient_new(master, &zclient_options_sync, NULL, 0); + zclient_sync->sock = -1; + zclient_sync->redist_default = ZEBRA_ROUTE_SRTE; + zclient_sync->instance = 1; + zclient_sync->privs = &pathd_privs; + + /* Connect to the LM. */ + t_sync_connect = NULL; + path_zebra_label_manager_connect(NULL); +} + +void path_zebra_stop(void) +{ + zclient_stop(zclient); + zclient_free(zclient); + event_cancel(&t_sync_connect); + zclient_stop(zclient_sync); + zclient_free(zclient_sync); +} diff --git a/pathd/path_zebra.h b/pathd/path_zebra.h new file mode 100644 index 0000000..74a62e3 --- /dev/null +++ b/pathd/path_zebra.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _FRR_PATH_MPLS_H_ +#define _FRR_PATH_MPLS_H_ + +#include +#include "pathd/pathd.h" + +bool get_ipv4_router_id(struct in_addr *router_id); +bool get_ipv6_router_id(struct in6_addr *router_id); +void path_zebra_add_sr_policy(struct srte_policy *policy, + struct srte_segment_list *segment_list); +void path_zebra_delete_sr_policy(struct srte_policy *policy); +int path_zebra_request_label(mpls_label_t label); +void path_zebra_release_label(mpls_label_t label); +void path_zebra_init(struct event_loop *master); +void path_zebra_stop(void); + +#endif /* _FRR_PATH_MPLS_H_ */ diff --git a/pathd/pathd.c b/pathd/pathd.c new file mode 100644 index 0000000..6c13503 --- /dev/null +++ b/pathd/pathd.c @@ -0,0 +1,1476 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#include + +#include "memory.h" +#include "log.h" +#include "lib_errors.h" +#include "network.h" +#include "libfrr.h" +#include +#include + +#include "pathd/pathd.h" +#include "pathd/path_zebra.h" +#include "pathd/path_debug.h" +#include "pathd/path_ted.h" + +#define HOOK_DELAY 3 + +DEFINE_MGROUP(PATHD, "pathd"); + +DEFINE_MTYPE_STATIC(PATHD, PATH_SEGMENT_LIST, "Segment List"); +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_POLICY, "SR Policy"); +DEFINE_MTYPE_STATIC(PATHD, PATH_SR_CANDIDATE, "SR Policy candidate path"); + +DEFINE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)); +DEFINE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)); +DEFINE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)); + +struct debug path_policy_debug; + +#define PATH_POLICY_DEBUG(fmt, ...) \ + do { \ + if (DEBUG_FLAGS_CHECK(&path_policy_debug, \ + PATH_POLICY_DEBUG_BASIC)) \ + DEBUGD(&path_policy_debug, "policy: " fmt, \ + ##__VA_ARGS__); \ + } while (0) + + +static void trigger_pathd_candidate_created(struct srte_candidate *candidate); +static void trigger_pathd_candidate_created_timer(struct event *thread); +static void trigger_pathd_candidate_updated(struct srte_candidate *candidate); +static void trigger_pathd_candidate_updated_timer(struct event *thread); +static void trigger_pathd_candidate_removed(struct srte_candidate *candidate); +static const char * +srte_candidate_metric_name(enum srte_candidate_metric_type type); + +static void srte_set_metric(struct srte_metric *metric, float value, + bool required, bool is_bound, bool is_computed); +static void srte_unset_metric(struct srte_metric *metric); + + +/* Generate rb-tree of Segment List Segment instances. */ +static inline int srte_segment_entry_compare(const struct srte_segment_entry *a, + const struct srte_segment_entry *b) +{ + return a->index - b->index; +} +RB_GENERATE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +/* Generate rb-tree of Segment List instances. */ +static inline int srte_segment_list_compare(const struct srte_segment_list *a, + const struct srte_segment_list *b) +{ + return strcmp(a->name, b->name); +} +RB_GENERATE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_segment_list_head srte_segment_lists = + RB_INITIALIZER(&srte_segment_lists); + +/* Generate rb-tree of Candidate Path instances. */ +static inline int srte_candidate_compare(const struct srte_candidate *a, + const struct srte_candidate *b) +{ + return a->preference - b->preference; +} +RB_GENERATE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +/* Generate rb-tree of SR Policy instances. */ +static inline int srte_policy_compare(const struct srte_policy *a, + const struct srte_policy *b) +{ + return sr_policy_compare(&a->endpoint, &b->endpoint, a->color, + b->color); +} +RB_GENERATE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +struct srte_policy_head srte_policies = RB_INITIALIZER(&srte_policies); + +static void srte_policy_status_log(struct srte_policy *policy) +{ + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + if (policy->status == SRTE_POLICY_STATUS_DOWN) { + PATH_POLICY_DEBUG("SR-TE(%s, %u): policy is DOWN", endpoint, + policy->color); + } else if (policy->status == SRTE_POLICY_STATUS_UP) { + PATH_POLICY_DEBUG("SR-TE(%s, %u): policy is UP", endpoint, + policy->color); + } +} + +/** + * Adds a segment list to pathd. + * + * @param name The name of the segment list to add + * @return The added segment list + */ +struct srte_segment_list *srte_segment_list_add(const char *name) +{ + struct srte_segment_list *segment_list; + + segment_list = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment_list)); + strlcpy(segment_list->name, name, sizeof(segment_list->name)); + RB_INIT(srte_segment_entry_head, &segment_list->segments); + RB_INSERT(srte_segment_list_head, &srte_segment_lists, segment_list); + + return segment_list; +} + +/** + * Deletes a segment list from pathd. + * + * The given segment list structure will be freed and should not be used anymore + * after calling this function. + * + * @param segment_list the segment list to remove from pathd. + */ +void srte_segment_list_del(struct srte_segment_list *segment_list) +{ + struct srte_segment_entry *segment, *safe_seg; + RB_FOREACH_SAFE (segment, srte_segment_entry_head, + &segment_list->segments, safe_seg) { + srte_segment_entry_del(segment); + } + RB_REMOVE(srte_segment_list_head, &srte_segment_lists, segment_list); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment_list); +} + +/** + * Search for a segment list by name. + * + * @param name The name of the segment list to look for + * @return The segment list if found, NULL otherwise + */ +struct srte_segment_list *srte_segment_list_find(const char *name) +{ + struct srte_segment_list search; + + strlcpy(search.name, name, sizeof(search.name)); + return RB_FIND(srte_segment_list_head, &srte_segment_lists, &search); +} + +/** + * Adds a segment to a segment list. + * + * @param segment_list The segment list the segment should be added to + * @param index The index of the added segment in the segment list + * @return The added segment + */ +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry *segment; + + segment = XCALLOC(MTYPE_PATH_SEGMENT_LIST, sizeof(*segment)); + segment->segment_list = segment_list; + segment->index = index; + RB_INSERT(srte_segment_entry_head, &segment_list->segments, segment); + + return segment; +} + +/** + * Deletes a segment from a segment list. + * + * @param segment The segment to be removed + */ +void srte_segment_entry_del(struct srte_segment_entry *segment) +{ + RB_REMOVE(srte_segment_entry_head, &segment->segment_list->segments, + segment); + XFREE(MTYPE_PATH_SEGMENT_LIST, segment); +} + +/** + * Set the node or adjacency identifier of a segment. + * + * @param segment The segment for which the NAI should be set + * @param type The type of the NAI + * @param type The address of the node or the local address of the adjacency + * @param type The local interface index of the unumbered adjacency + * @param type The remote address of the adjacency + * @param type The remote interface index of the unumbered adjacency + */ +int srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, uint32_t remote_iface, + uint8_t algo, uint8_t pref_len) +{ + + int32_t status = 0; + struct prefix pre = {0}; + + if (!segment || !local_ip || !remote_ip) + return 1; + + segment->nai_type = type; + memcpy(&segment->nai_local_addr, local_ip, sizeof(struct ipaddr)); + + switch (type) { + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + status = srte_ted_do_query_type_f(segment, local_ip, remote_ip); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + memcpy(&segment->nai_remote_addr, remote_ip, + sizeof(struct ipaddr)); + segment->nai_local_iface = local_iface; + segment->nai_remote_iface = remote_iface; + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + pre.family = AF_INET6; + pre.prefixlen = pref_len; + pre.u.prefix6 = local_ip->ip._v6_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_algorithm = algo; + status = srte_ted_do_query_type_c(segment, &pre, algo); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + pre.family = AF_INET; + pre.prefixlen = pref_len; + pre.u.prefix4 = local_ip->ip._v4_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_algorithm = algo; + status = srte_ted_do_query_type_c(segment, &pre, algo); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + pre.family = AF_INET6; + pre.prefixlen = pref_len; + pre.u.prefix6 = local_ip->ip._v6_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_local_iface = local_iface; + status = srte_ted_do_query_type_e(segment, &pre, local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + pre.family = AF_INET; + pre.prefixlen = pref_len; + pre.u.prefix4 = local_ip->ip._v4_addr; + segment->nai_local_prefix_len = pref_len; + segment->nai_local_iface = local_iface; + status = srte_ted_do_query_type_e(segment, &pre, local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_NONE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + segment->nai_local_addr.ipa_type = IPADDR_NONE; + segment->nai_local_iface = 0; + segment->nai_remote_addr.ipa_type = IPADDR_NONE; + segment->nai_remote_iface = 0; + } + return status; +} + +/** + * Mark segment as modified depending in protocol and sid conditions + * + * @param protocol_origin Origin of the segment list + * @param s_list Ptr to segment list with flags,sid to modidy + * @param s_entry Ptr to segment entry with sid to modidy + * @param ted_sid The sid from ted query + * @return void + */ +void srte_segment_set_local_modification(struct srte_segment_list *s_list, + struct srte_segment_entry *s_entry, + uint32_t ted_sid) +{ + if (!s_list || !s_entry) + return; + + if (s_list->protocol_origin == SRTE_ORIGIN_LOCAL + && s_entry->sid_value != ted_sid) { + s_entry->sid_value = ted_sid; + SET_FLAG(s_list->flags, F_SEGMENT_LIST_MODIFIED); + } +} + +/** + * Add a policy to pathd. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy. + * @param endpoint The IP address of the policy endpoint + * @return The created policy + */ +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint, + enum srte_protocol_origin origin, + const char *originator) +{ + struct srte_policy *policy; + + policy = XCALLOC(MTYPE_PATH_SR_POLICY, sizeof(*policy)); + policy->color = color; + policy->endpoint = *endpoint; + policy->binding_sid = MPLS_LABEL_NONE; + policy->protocol_origin = origin; + if (originator != NULL) + strlcpy(policy->originator, originator, + sizeof(policy->originator)); + + RB_INIT(srte_candidate_head, &policy->candidate_paths); + RB_INSERT(srte_policy_head, &srte_policies, policy); + + return policy; +} + +/** + * Delete a policy from pathd. + * + * The given policy structure will be freed and should never be used again + * after calling this function. + * + * @param policy The policy to be removed + */ +void srte_policy_del(struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + path_zebra_delete_sr_policy(policy); + + path_zebra_release_label(policy->binding_sid); + + while (!RB_EMPTY(srte_candidate_head, &policy->candidate_paths)) { + candidate = + RB_ROOT(srte_candidate_head, &policy->candidate_paths); + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + } + + RB_REMOVE(srte_policy_head, &srte_policies, policy); + XFREE(MTYPE_PATH_SR_POLICY, policy); +} + +/** + * Search for a policy by color and endpoint. + * + * WARNING: The color 0 is a special case as it is the no-color. + * + * @param color The color of the policy to look for + * @param endpoint The endpoint of the policy to look for + * @return The policy if found, NULL otherwise + */ +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint) +{ + struct srte_policy search; + + search.color = color; + search.endpoint = *endpoint; + return RB_FIND(srte_policy_head, &srte_policies, &search); +} + +/* + * After new data from igp,local and pce the segment list : + * Mark as invalid for origin pce if cannot be validated + * Updated for origin local + */ +int srte_policy_update_ted_sid(void) +{ + + int number_of_sid_clashed = 0; + struct srte_segment_list *s_list; + struct srte_segment_entry *s_entry; + + if (!path_ted_is_initialized()) + return 0; + if (RB_EMPTY(srte_segment_list_head, &srte_segment_lists)) + return 0; + + RB_FOREACH (s_list, srte_segment_list_head, &srte_segment_lists) { + if (CHECK_FLAG(s_list->flags, F_SEGMENT_LIST_DELETED)) + continue; + RB_FOREACH (s_entry, srte_segment_entry_head, + &s_list->segments) { + PATH_TED_DEBUG( + "%s:PATHD-TED: SL: Name: %s index:(%d) sid:(%d) prefix_len:(%d) local iface:(%d) algorithm:(%d)", + __func__, s_list->name, s_entry->index, + s_entry->sid_value, + s_entry->nai_local_prefix_len, + s_entry->nai_local_iface, + s_entry->nai_algorithm); + struct prefix prefix_cli = {0}; + + switch (s_entry->nai_type) { + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY: + number_of_sid_clashed += + srte_ted_do_query_type_f( + s_entry, + &s_entry->nai_local_addr, + &s_entry->nai_remote_addr); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE: + prefix_cli.family = AF_INET6; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix6 = + s_entry->nai_local_addr.ip._v6_addr; + number_of_sid_clashed += + srte_ted_do_query_type_e( + s_entry, &prefix_cli, + s_entry->nai_local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE: + prefix_cli.family = AF_INET; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix4 = + s_entry->nai_local_addr.ip._v4_addr; + number_of_sid_clashed += + srte_ted_do_query_type_e( + s_entry, &prefix_cli, + s_entry->nai_local_iface); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM: + prefix_cli.family = AF_INET6; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix6 = + s_entry->nai_local_addr.ip._v6_addr; + number_of_sid_clashed += + srte_ted_do_query_type_c( + s_entry, &prefix_cli, + s_entry->nai_algorithm); + break; + case SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM: + prefix_cli.family = AF_INET; + prefix_cli.prefixlen = + s_entry->nai_local_prefix_len; + prefix_cli.u.prefix4 = + s_entry->nai_local_addr.ip._v4_addr; + number_of_sid_clashed += + srte_ted_do_query_type_c( + s_entry, &prefix_cli, + s_entry->nai_algorithm); + break; + case SRTE_SEGMENT_NAI_TYPE_NONE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV6_NODE: + case SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY: + case SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES: + break; + } + } + if (number_of_sid_clashed) { + SET_FLAG(s_list->flags, F_SEGMENT_LIST_SID_CONFLICT); + number_of_sid_clashed = 0; + } else + UNSET_FLAG(s_list->flags, F_SEGMENT_LIST_SID_CONFLICT); + } + srte_apply_changes(); + + return 0; +} + +/** + * Update a policy binding SID. + * + * @param policy The policy for which the SID should be updated + * @param binding_sid The new binding SID for the given policy + */ +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid) +{ + if (policy->binding_sid != MPLS_LABEL_NONE) + path_zebra_release_label(policy->binding_sid); + + policy->binding_sid = binding_sid; + + /* Reinstall the Binding-SID if necessary. */ + if (policy->best_candidate) + path_zebra_add_sr_policy( + policy, policy->best_candidate->lsp->segment_list); +} + +/** + * Gives the policy best candidate path. + * + * @param policy The policy we want the best candidate path from + * @return The best candidate path + */ +static struct srte_candidate * +srte_policy_best_candidate(const struct srte_policy *policy) +{ + struct srte_candidate *candidate; + + RB_FOREACH_REVERSE (candidate, srte_candidate_head, + &policy->candidate_paths) { + /* search for highest preference with existing segment list */ + if (!CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED) + && candidate->lsp->segment_list + && (!CHECK_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_SID_CONFLICT))) + return candidate; + } + + return NULL; +} + +void srte_clean_zebra(void) +{ + struct srte_policy *policy, *safe_pol; + + RB_FOREACH_SAFE (policy, srte_policy_head, &srte_policies, safe_pol) + srte_policy_del(policy); + + path_zebra_stop(); +} + +/** + * Apply changes defined by setting the policies, candidate paths + * and segment lists modification flags NEW, MODIFIED and DELETED. + * + * This allows the northbound code to delay all the side effects of adding + * modifying and deleting them to the end. + * + * Example of marking an object as modified: + * `SET_FLAG(obj->flags, F_XXX_MODIFIED)` + */ +void srte_apply_changes(void) +{ + struct srte_policy *policy, *safe_pol; + struct srte_segment_list *segment_list, *safe_sl; + + RB_FOREACH_SAFE (policy, srte_policy_head, &srte_policies, safe_pol) { + if (CHECK_FLAG(policy->flags, F_POLICY_DELETED)) { + if (policy->status != SRTE_POLICY_STATUS_DOWN) { + policy->status = SRTE_POLICY_STATUS_DOWN; + srte_policy_status_log(policy); + } + srte_policy_del(policy); + continue; + } + srte_policy_apply_changes(policy); + UNSET_FLAG(policy->flags, F_POLICY_NEW); + UNSET_FLAG(policy->flags, F_POLICY_MODIFIED); + } + + RB_FOREACH_SAFE (segment_list, srte_segment_list_head, + &srte_segment_lists, safe_sl) { + if (CHECK_FLAG(segment_list->flags, F_SEGMENT_LIST_DELETED)) { + srte_segment_list_del(segment_list); + continue; + } + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_NEW); + UNSET_FLAG(segment_list->flags, F_SEGMENT_LIST_MODIFIED); + } +} + +/** + * Apply changes defined by setting the given policy and its candidate paths + * modification flags NEW, MODIFIED and DELETED. + * + * In moste cases `void srte_apply_changes(void)` should be used instead, + * this function will not handle the changes of segment lists used by the + * policy. + * + * @param policy The policy changes has to be applied to. + */ +void srte_policy_apply_changes(struct srte_policy *policy) +{ + struct srte_candidate *candidate, *safe; + struct srte_candidate *old_best_candidate; + struct srte_candidate *new_best_candidate; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + /* Get old and new best candidate path. */ + old_best_candidate = policy->best_candidate; + new_best_candidate = srte_policy_best_candidate(policy); + + if (new_best_candidate != old_best_candidate) { + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): best candidate changed from %s to %s", + endpoint, policy->color, + old_best_candidate ? old_best_candidate->name : "none", + new_best_candidate ? new_best_candidate->name : "none"); + + if (old_best_candidate) { + policy->best_candidate = NULL; + UNSET_FLAG(old_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(old_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + /* + * Rely on replace semantics if there's a new best + * candidate. + */ + if (!new_best_candidate) + path_zebra_delete_sr_policy(policy); + } + if (new_best_candidate) { + policy->best_candidate = new_best_candidate; + SET_FLAG(new_best_candidate->flags, F_CANDIDATE_BEST); + SET_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } else if (new_best_candidate) { + /* The best candidate path did not change, but some of its + * attributes or its segment list may have changed. + */ + + bool candidate_changed = CHECK_FLAG(new_best_candidate->flags, + F_CANDIDATE_MODIFIED); + bool segment_list_changed = + new_best_candidate->lsp->segment_list + && CHECK_FLAG( + new_best_candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED); + + if (candidate_changed || segment_list_changed) { + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): best candidate %s changed", + endpoint, policy->color, + new_best_candidate->name); + + path_zebra_add_sr_policy( + policy, new_best_candidate->lsp->segment_list); + } + } + + RB_FOREACH_SAFE (candidate, srte_candidate_head, + &policy->candidate_paths, safe) { + if (CHECK_FLAG(candidate->flags, F_CANDIDATE_DELETED)) { + trigger_pathd_candidate_removed(candidate); + srte_candidate_del(candidate); + continue; + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_NEW)) { + trigger_pathd_candidate_created(candidate); + } else if (CHECK_FLAG(candidate->flags, F_CANDIDATE_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } else if (candidate->lsp->segment_list + && CHECK_FLAG(candidate->lsp->segment_list->flags, + F_SEGMENT_LIST_MODIFIED)) { + trigger_pathd_candidate_updated(candidate); + } + + UNSET_FLAG(candidate->flags, F_CANDIDATE_NEW); + UNSET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + } +} + +/** + * Adds a candidate path to a policy. + * + * @param policy The policy the candidate path should be added to + * @param preference The preference of the candidate path to be added + * @return The added candidate path + */ +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference, + enum srte_protocol_origin origin, + const char *originator) +{ + struct srte_candidate *candidate; + struct srte_lsp *lsp; + + candidate = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*candidate)); + lsp = XCALLOC(MTYPE_PATH_SR_CANDIDATE, sizeof(*lsp)); + + candidate->preference = preference; + candidate->policy = policy; + candidate->type = SRTE_CANDIDATE_TYPE_UNDEFINED; + candidate->discriminator = frr_weak_random(); + candidate->protocol_origin = origin; + if (originator != NULL) { + strlcpy(candidate->originator, originator, + sizeof(candidate->originator)); + lsp->protocol_origin = origin; + } + + if (candidate->protocol_origin == SRTE_ORIGIN_PCEP + || candidate->protocol_origin == SRTE_ORIGIN_BGP) { + candidate->type = SRTE_CANDIDATE_TYPE_DYNAMIC; + } + lsp->candidate = candidate; + candidate->lsp = lsp; + + RB_INSERT(srte_candidate_head, &policy->candidate_paths, candidate); + + return candidate; +} + +/** + * Deletes a candidate. + * + * The corresponding LSP will be removed alongside the candidate path. + * The given candidate will be freed and shouldn't be used anymore after the + * calling this function. + * + * @param candidate The candidate path to delete + */ +void srte_candidate_del(struct srte_candidate *candidate) +{ + struct srte_policy *srte_policy = candidate->policy; + + RB_REMOVE(srte_candidate_head, &srte_policy->candidate_paths, + candidate); + + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate->lsp); + XFREE(MTYPE_PATH_SR_CANDIDATE, candidate); +} + +/** + * Sets the bandwidth constraint of given candidate path. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s %sconfig bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required " : "", bandwidth); + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + candidate->bandwidth = bandwidth; + + srte_lsp_set_bandwidth(candidate->lsp, bandwidth, required); +} + +/** + * Sets the bandwidth constraint of the given LSP. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + * @param bandwidth The Bandwidth constraint to set to the candidate path + * @param required If the constraint is required (true) or only desired (false) + */ +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s %slsp bandwidth set to %f B/s", + endpoint, policy->color, candidate->name, + required ? "required" : "", bandwidth); + SET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + COND_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH, required); + lsp->bandwidth = bandwidth; +} + +/** + * Remove a candidate path bandwidth constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the bandwidth should be removed + */ +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG("SR-TE(%s, %u): candidate %s config bandwidth unset", + endpoint, policy->color, candidate->name); + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + candidate->bandwidth = 0; + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + srte_lsp_unset_bandwidth(candidate->lsp); +} + +/** + * Remove an LSP bandwidth constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The lsp of which the bandwidth should be changed + */ +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG("SR-TE(%s, %u): candidate %s lsp bandwidth unset", + endpoint, policy->color, candidate->name); + UNSET_FLAG(lsp->flags, F_CANDIDATE_HAS_BANDWIDTH); + UNSET_FLAG(lsp->flags, F_CANDIDATE_REQUIRED_BANDWIDTH); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + lsp->bandwidth = 0; +} + +/** + * Sets a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_bound, + bool is_computed) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s %sconfig metric %s (%u) set to %f (is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&candidate->metrics[type - 1], value, required, + is_bound, is_computed); + srte_lsp_set_metric(candidate->lsp, type, value, required, is_bound, + is_computed); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Sets an LSP metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP of which the metric should be changed + * @param type The metric type + * @param value The metric value + * @param required If the constraint is required (true) or only desired (false) + * @param is_bound If the metric is an indicative value or a strict upper bound + * @param is_computed If the metric was computed or configured + */ +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_bound, bool is_computed) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s %slsp metric %s (%u) set to %f (is-bound: %s; is_computed: %s)", + endpoint, policy->color, candidate->name, + required ? "required " : "", srte_candidate_metric_name(type), + type, value, is_bound ? "true" : "false", + is_computed ? "true" : "false"); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_set_metric(&lsp->metrics[type - 1], value, required, is_bound, + is_computed); +} + +void srte_set_metric(struct srte_metric *metric, float value, bool required, + bool is_bound, bool is_computed) +{ + SET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + COND_FLAG(metric->flags, F_METRIC_IS_REQUIRED, required); + COND_FLAG(metric->flags, F_METRIC_IS_BOUND, is_bound); + COND_FLAG(metric->flags, F_METRIC_IS_COMPUTED, is_computed); + metric->value = value; +} + +/** + * Removes a candidate path metric constraint. + * + * The corresponding LSP will be changed too. + * + * @param candidate The candidate path from which the metric should be removed + * @param type The metric type + */ +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s config metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&candidate->metrics[type - 1]); + srte_lsp_unset_metric(candidate->lsp, type); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); +} + +/** + * Removes a candidate path metric constraint. + * + * The changes will not be shown as part of the running configuration. + * + * @param lsp The LSP from which the metric should be removed + * @param type The metric type + */ +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type) +{ + struct srte_candidate *candidate = lsp->candidate; + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s lsp metric %s (%u) unset", + endpoint, policy->color, candidate->name, + srte_candidate_metric_name(type), type); + assert((type > 0) && (type <= MAX_METRIC_TYPE)); + srte_unset_metric(&lsp->metrics[type - 1]); +} + +void srte_unset_metric(struct srte_metric *metric) +{ + UNSET_FLAG(metric->flags, F_METRIC_IS_DEFINED); + UNSET_FLAG(metric->flags, F_METRIC_IS_BOUND); + UNSET_FLAG(metric->flags, F_METRIC_IS_COMPUTED); + metric->value = 0; +} + +/** + * Sets a candidate path objective function. + * + * @param candidate The candidate path of which the OF should be changed + * @param required If the constraint is required (true) or only desired (false) + * @param type The objective function type + */ +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + candidate->objfun = type; + SET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + COND_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN, required); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s %sobjective function set to %s", + endpoint, policy->color, candidate->name, + required ? "required " : "", objfun_type_name(type)); +} + +/** + * Removed the objective function constraint from a candidate path. + * + * @param candidate The candidate path from which the OF should be removed + */ +void srte_candidate_unset_objfun(struct srte_candidate *candidate) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + UNSET_FLAG(candidate->flags, F_CANDIDATE_HAS_OBJFUN); + UNSET_FLAG(candidate->flags, F_CANDIDATE_REQUIRED_OBJFUN); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->objfun = OBJFUN_UNDEFINED; + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s objective functions preferences unset", + endpoint, policy->color, candidate->name); +} + +static uint32_t filter_type_to_flag(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return F_CANDIDATE_HAS_EXCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ANY: + return F_CANDIDATE_HAS_INCLUDE_ANY; + case AFFINITY_FILTER_INCLUDE_ALL: + return F_CANDIDATE_HAS_INCLUDE_ALL; + case AFFINITY_FILTER_UNDEFINED: + return 0; + } + + assert(!"Reached end of function we should never hit"); +} + +static const char *filter_type_name(enum affinity_filter_type type) +{ + switch (type) { + case AFFINITY_FILTER_EXCLUDE_ANY: + return "exclude-any"; + case AFFINITY_FILTER_INCLUDE_ANY: + return "include-any"; + case AFFINITY_FILTER_INCLUDE_ALL: + return "include-all"; + case AFFINITY_FILTER_UNDEFINED: + return "unknown"; + } + + assert(!"Reached end of function we should never hit"); +} + +/** + * Sets a candidate path affinity filter constraint. + * + * @param candidate The candidate path of which the constraint should be changed + * @param type The affinity constraint type to set + * @param filter The bitmask filter of the constraint + */ +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + SET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = filter; + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s affinity filter %s set to 0x%08x", + endpoint, policy->color, candidate->name, + filter_type_name(type), filter); +} + +/** + * Removes a candidate path affinity filter constraint. + * + * @param candidate The candidate path from which the constraint should be + * removed + * @param type The affinity constraint type to remove + */ +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + + assert(type > AFFINITY_FILTER_UNDEFINED); + assert(type <= MAX_AFFINITY_FILTER_TYPE); + UNSET_FLAG(candidate->flags, filter_type_to_flag(type)); + SET_FLAG(candidate->flags, F_CANDIDATE_MODIFIED); + candidate->affinity_filters[type - 1] = 0; + PATH_POLICY_DEBUG( + "SR-TE(%s, %u): candidate %s affinity filter %s unset", + endpoint, policy->color, candidate->name, + filter_type_name(type)); +} + +/** + * Searches for a candidate path of the given policy. + * + * @param policy The policy to search for candidate path + * @param preference The preference of the candidate path you are looking for + * @return The candidate path if found, NULL otherwise + */ +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference) +{ + struct srte_candidate search; + + search.preference = preference; + return RB_FIND(srte_candidate_head, &policy->candidate_paths, &search); +} + +/** + * Searches for a an entry of a given segment list. + * + * @param segment_list The segment list to search for the entry + * @param index The index of the entry you are looking for + * @return The segment list entry if found, NULL otherwise. + */ +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index) +{ + struct srte_segment_entry search; + + search.index = index; + return RB_FIND(srte_segment_entry_head, &segment_list->segments, + &search); +} + +/** + * Updates a candidate status. + * + * @param candidate The candidate of which the status should be updated + * @param status The new candidate path status + */ +void srte_candidate_status_update(struct srte_candidate *candidate, int status) +{ + struct srte_policy *policy = candidate->policy; + char endpoint[ENDPOINT_STR_LENGTH]; + + ipaddr2str(&policy->endpoint, endpoint, sizeof(endpoint)); + PATH_POLICY_DEBUG("SR-TE(%s, %u): zebra updated status to %d", endpoint, + policy->color, status); + switch (status) { + case ZEBRA_SR_POLICY_DOWN: + switch (policy->status) { + /* If the policy is GOING_UP, and zebra faild + to install it, we wait for zebra to retry */ + /* TODO: Add some timeout after which we would + get is back to DOWN and remove the + policy */ + case SRTE_POLICY_STATUS_GOING_UP: + case SRTE_POLICY_STATUS_DOWN: + return; + case SRTE_POLICY_STATUS_UNKNOWN: + case SRTE_POLICY_STATUS_UP: + case SRTE_POLICY_STATUS_GOING_DOWN: + policy->status = SRTE_POLICY_STATUS_DOWN; + srte_policy_status_log(policy); + break; + } + break; + case ZEBRA_SR_POLICY_UP: + switch (policy->status) { + case SRTE_POLICY_STATUS_UP: + return; + case SRTE_POLICY_STATUS_UNKNOWN: + case SRTE_POLICY_STATUS_DOWN: + case SRTE_POLICY_STATUS_GOING_DOWN: + case SRTE_POLICY_STATUS_GOING_UP: + policy->status = SRTE_POLICY_STATUS_UP; + srte_policy_status_log(policy); + break; + } + break; + } + + trigger_pathd_candidate_updated(candidate); +} + +/** + * Flags the segment lists from give originator for removal. + * + * The function srte_apply_changes must be called afterward for + * the segment list to be removed. + * + * @param originator The originator tag of the segment list to be marked + * @param force If the unset should be forced regardless of the originator + */ +void srte_candidate_unset_segment_list(const char *originator, bool force) +{ + if (originator == NULL) { + zlog_warn( + "Cannot unset segment list because originator is NULL"); + return; + } + + PATH_POLICY_DEBUG("Unset segment lists for originator %s", originator); + + /* Iterate the policies, then iterate each policy's candidate path + * to check the candidate path's segment list originator */ + struct srte_policy *policy; + RB_FOREACH (policy, srte_policy_head, &srte_policies) { + PATH_POLICY_DEBUG("Unset segment lists checking policy %s", + policy->name); + struct srte_candidate *candidate; + RB_FOREACH (candidate, srte_candidate_head, + &policy->candidate_paths) { + PATH_POLICY_DEBUG( + "Unset segment lists checking candidate %s", + candidate->name); + if (candidate->lsp == NULL) { + continue; + } + + /* The candidate->lsp->segment_list is operational data, + * configured by the PCE. We dont want to modify the + * candidate->segment_list, + * which is configuration data. */ + struct srte_segment_list *segment_list = + candidate->lsp->segment_list; + if (segment_list == NULL) { + continue; + } + + if (segment_list->protocol_origin + == SRTE_ORIGIN_LOCAL) { + zlog_warn( + "Cannot unset segment list %s because it was created locally", + segment_list->name); + continue; + } + + /* In the case of last pce,we force the unset + * because we don't have pce by prefix (TODO) is all + * 'global' */ + if (strncmp(segment_list->originator, originator, + sizeof(segment_list->originator)) + == 0 + || force) { + PATH_POLICY_DEBUG("Unset segment list %s", + segment_list->name); + SET_FLAG(segment_list->flags, + F_SEGMENT_LIST_DELETED); + SET_FLAG(candidate->flags, + F_CANDIDATE_MODIFIED); + candidate->lsp->segment_list = NULL; + } + } + } +} + +/** + * Gives a string representation of given protocol origin enum. + * + * @param origin The enum you want a string representation of + * @return The string representation of given enum + */ +const char *srte_origin2str(enum srte_protocol_origin origin) +{ + switch (origin) { + case SRTE_ORIGIN_PCEP: + return "PCEP"; + case SRTE_ORIGIN_BGP: + return "BGP"; + case SRTE_ORIGIN_LOCAL: + return "Local"; + case SRTE_ORIGIN_UNDEFINED: + return "Unknown"; + } + + assert(!"Reached end of function we should never hit"); +} + +void path_policy_show_debugging(struct vty *vty) +{ + if (DEBUG_FLAGS_CHECK(&path_policy_debug, PATH_POLICY_DEBUG_BASIC)) + vty_out(vty, " Path policy debugging is on\n"); +} + +void pathd_shutdown(void) +{ + path_ted_teardown(); + srte_clean_zebra(); + frr_fini(); +} + +void trigger_pathd_candidate_created(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + event_add_timer(master, trigger_pathd_candidate_created_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +void trigger_pathd_candidate_created_timer(struct event *thread) +{ + struct srte_candidate *candidate = EVENT_ARG(thread); + candidate->hook_timer = NULL; + hook_call(pathd_candidate_created, candidate); +} + +void trigger_pathd_candidate_updated(struct srte_candidate *candidate) +{ + /* The hook is called asynchronously to let the PCEP module + time to send a response to the PCE before receiving any updates from + pathd. In addition, a minimum amount of time need to pass before + the hook is called to prevent the hook to be called multiple times + from changing the candidate by hand with the console */ + if (candidate->hook_timer != NULL) + return; + event_add_timer(master, trigger_pathd_candidate_updated_timer, + (void *)candidate, HOOK_DELAY, &candidate->hook_timer); +} + +void trigger_pathd_candidate_updated_timer(struct event *thread) +{ + struct srte_candidate *candidate = EVENT_ARG(thread); + candidate->hook_timer = NULL; + hook_call(pathd_candidate_updated, candidate); +} + +void trigger_pathd_candidate_removed(struct srte_candidate *candidate) +{ + /* The hook needs to be call synchronously, otherwise the candidate + path will be already deleted when the handler is called */ + if (candidate->hook_timer != NULL) { + event_cancel(&candidate->hook_timer); + candidate->hook_timer = NULL; + } + hook_call(pathd_candidate_removed, candidate); +} + +const char *srte_candidate_metric_name(enum srte_candidate_metric_type type) +{ + switch (type) { + case SRTE_CANDIDATE_METRIC_TYPE_IGP: + return "IGP"; + case SRTE_CANDIDATE_METRIC_TYPE_TE: + return "TE"; + case SRTE_CANDIDATE_METRIC_TYPE_HC: + return "HC"; + case SRTE_CANDIDATE_METRIC_TYPE_ABC: + return "ABC"; + case SRTE_CANDIDATE_METRIC_TYPE_LMLL: + return "LMLL"; + case SRTE_CANDIDATE_METRIC_TYPE_CIGP: + return "CIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_CTE: + return "CTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PIGP: + return "PIGP"; + case SRTE_CANDIDATE_METRIC_TYPE_PTE: + return "PTE"; + case SRTE_CANDIDATE_METRIC_TYPE_PHC: + return "PHC"; + case SRTE_CANDIDATE_METRIC_TYPE_MSD: + return "MSD"; + case SRTE_CANDIDATE_METRIC_TYPE_PD: + return "PD"; + case SRTE_CANDIDATE_METRIC_TYPE_PDV: + return "PDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PL: + return "PL"; + case SRTE_CANDIDATE_METRIC_TYPE_PPD: + return "PPD"; + case SRTE_CANDIDATE_METRIC_TYPE_PPDV: + return "PPDV"; + case SRTE_CANDIDATE_METRIC_TYPE_PPL: + return "PPL"; + case SRTE_CANDIDATE_METRIC_TYPE_NAP: + return "NAP"; + case SRTE_CANDIDATE_METRIC_TYPE_NLP: + return "NLP"; + case SRTE_CANDIDATE_METRIC_TYPE_DC: + return "DC"; + case SRTE_CANDIDATE_METRIC_TYPE_BNC: + return "BNC"; + default: + return "UNKNOWN"; + } +} + +int32_t srte_ted_do_query_type_c(struct srte_segment_entry *entry, + struct prefix *prefix_cli, uint32_t algo) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !prefix_cli) + return 0; + + if (!path_ted_is_initialized()) + return 0; + + ted_sid = path_ted_query_type_c(prefix_cli, algo); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn(" %s: PATHD-TED: SL: ERROR query C : ted-sid (%d)", + __func__, ted_sid); + } else { + PATH_TED_DEBUG( + "%s: PATHD-TED: SL: Success query C : ted-sid (%d)", + __func__, ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} + +int32_t srte_ted_do_query_type_e(struct srte_segment_entry *entry, + struct prefix *prefix_cli, + uint32_t local_iface) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !prefix_cli) + return 0; + + if (!path_ted_is_initialized()) + return 0; + + ted_sid = path_ted_query_type_e(prefix_cli, local_iface); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn(" %s: PATHD-TED: SL: ERROR query E : ted-sid (%d)", + __func__, ted_sid); + } else { + PATH_TED_DEBUG( + "%s: PATHD-TED: SL: Success query E : ted-sid (%d)", + __func__, ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} + +int32_t srte_ted_do_query_type_f(struct srte_segment_entry *entry, + struct ipaddr *local, struct ipaddr *remote) +{ + int32_t status = 0; + uint32_t ted_sid = MPLS_LABEL_NONE; + + if (!entry || !local || !remote) + return 0; + + if (!path_ted_is_initialized()) + return status; + + ted_sid = path_ted_query_type_f(local, remote); + if (ted_sid == MPLS_LABEL_NONE) { + zlog_warn("%s:SL: ERROR query F : ted-sid (%d)", __func__, + ted_sid); + } else { + PATH_TED_DEBUG("%s:SL: Success query F : ted-sid (%d)", + __func__, ted_sid); + } + if (CHECK_SID(entry->segment_list->protocol_origin, ted_sid, + entry->sid_value)) { + status = PATH_SID_ERROR; + } else + srte_segment_set_local_modification(entry->segment_list, entry, + ted_sid); + return status; +} diff --git a/pathd/pathd.h b/pathd/pathd.h new file mode 100644 index 0000000..73ad492 --- /dev/null +++ b/pathd/pathd.h @@ -0,0 +1,481 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + */ + +#ifndef _FRR_PATHD_H_ +#define _FRR_PATHD_H_ + +#include "lib/memory.h" +#include "lib/mpls.h" +#include "lib/ipaddr.h" +#include "lib/srte.h" +#include "lib/hook.h" +#include "lib/prefix.h" + +#define PATH_SID_ERROR 1 +#define PATH_SID_NO_ERROR 0 +#define CHECK_SID(or, ts, es) \ + ((or == SRTE_ORIGIN_PCEP && (ts == MPLS_LABEL_NONE || es != ts)) \ + || (or == SRTE_ORIGIN_LOCAL && ts == MPLS_LABEL_NONE)) + +DECLARE_MGROUP(PATHD); + +DECLARE_HOOK(pathd_srte_config_write, (struct vty *vty), (vty)); + +enum srte_protocol_origin { + SRTE_ORIGIN_UNDEFINED = 0, + SRTE_ORIGIN_PCEP = 1, + SRTE_ORIGIN_BGP = 2, + SRTE_ORIGIN_LOCAL = 3, +}; + +extern struct debug path_policy_debug; + +#define PATH_POLICY_DEBUG_BASIC 0x01 + +enum srte_policy_status { + SRTE_POLICY_STATUS_UNKNOWN = 0, + SRTE_POLICY_STATUS_DOWN = 1, + SRTE_POLICY_STATUS_UP = 2, + SRTE_POLICY_STATUS_GOING_DOWN = 3, + SRTE_POLICY_STATUS_GOING_UP = 4 +}; + +enum srte_candidate_type { + SRTE_CANDIDATE_TYPE_UNDEFINED = 0, + SRTE_CANDIDATE_TYPE_EXPLICIT = 1, + SRTE_CANDIDATE_TYPE_DYNAMIC = 2, +}; + +enum srte_candidate_metric_type { + /* IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_IGP = 1, + /* TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_TE = 2, + /* Hop Counts */ + SRTE_CANDIDATE_METRIC_TYPE_HC = 3, + /* Aggregate bandwidth consumption */ + SRTE_CANDIDATE_METRIC_TYPE_ABC = 4, + /* Load of the most loaded link */ + SRTE_CANDIDATE_METRIC_TYPE_LMLL = 5, + /* Cumulative IGP cost */ + SRTE_CANDIDATE_METRIC_TYPE_CIGP = 6, + /* Cumulative TE cost */ + SRTE_CANDIDATE_METRIC_TYPE_CTE = 7, + /* P2MP IGP metric */ + SRTE_CANDIDATE_METRIC_TYPE_PIGP = 8, + /* P2MP TE metric */ + SRTE_CANDIDATE_METRIC_TYPE_PTE = 9, + /* P2MP hop count metric */ + SRTE_CANDIDATE_METRIC_TYPE_PHC = 10, + /* Segment-ID (SID) Depth */ + SRTE_CANDIDATE_METRIC_TYPE_MSD = 11, + /* Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PD = 12, + /* Path Delay Variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PDV = 13, + /* Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PL = 14, + /* P2MP Path Delay metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPD = 15, + /* P2MP Path Delay variation metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPDV = 16, + /* P2MP Path Loss metric */ + SRTE_CANDIDATE_METRIC_TYPE_PPL = 17, + /* Number of adaptations on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NAP = 18, + /* Number of layers on a path */ + SRTE_CANDIDATE_METRIC_TYPE_NLP = 19, + /* Domain Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_DC = 20, + /* Border Node Count metric */ + SRTE_CANDIDATE_METRIC_TYPE_BNC = 21, +}; +#define MAX_METRIC_TYPE 21 + +enum srte_segment_nai_type { + SRTE_SEGMENT_NAI_TYPE_NONE = 0, + SRTE_SEGMENT_NAI_TYPE_IPV4_NODE = 1, + SRTE_SEGMENT_NAI_TYPE_IPV6_NODE = 2, + SRTE_SEGMENT_NAI_TYPE_IPV4_ADJACENCY = 3, + SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY = 4, + SRTE_SEGMENT_NAI_TYPE_IPV4_UNNUMBERED_ADJACENCY = 5, + SRTE_SEGMENT_NAI_TYPE_IPV6_ADJACENCY_LINK_LOCAL_ADDRESSES = 6, + SRTE_SEGMENT_NAI_TYPE_IPV4_LOCAL_IFACE = 7, + SRTE_SEGMENT_NAI_TYPE_IPV6_LOCAL_IFACE = 8, + SRTE_SEGMENT_NAI_TYPE_IPV4_ALGORITHM = 9, + SRTE_SEGMENT_NAI_TYPE_IPV6_ALGORITHM = 10 +}; + +enum objfun_type { + OBJFUN_UNDEFINED = 0, + /* Minimum Cost Path [RFC5541] */ + OBJFUN_MCP = 1, + /* Minimum Load Path [RFC5541] */ + OBJFUN_MLP = 2, + /* Maximum residual Bandwidth Path [RFC5541] */ + OBJFUN_MBP = 3, + /* Minimize aggregate Bandwidth Consumption [RFC5541] */ + OBJFUN_MBC = 4, + /* Minimize the Load of the most loaded Link [RFC5541] */ + OBJFUN_MLL = 5, + /* Minimize the Cumulative Cost of a set of paths [RFC5541] */ + OBJFUN_MCC = 6, + /* Shortest Path Tree [RFC8306] */ + OBJFUN_SPT = 7, + /* Minimum Cost Tree [RFC8306] */ + OBJFUN_MCT = 8, + /* Minimum Packet Loss Path [RFC8233] */ + OBJFUN_MPLP = 9, + /* Maximum Under-Utilized Path [RFC8233] */ + OBJFUN_MUP = 10, + /* Maximum Reserved Under-Utilized Path [RFC8233] */ + OBJFUN_MRUP = 11, + /* Minimize the number of Transit Domains [RFC8685] */ + OBJFUN_MTD = 12, + /* Minimize the number of Border Nodes [RFC8685] */ + OBJFUN_MBN = 13, + /* Minimize the number of Common Transit Domains [RFC8685] */ + OBJFUN_MCTD = 14, + /* Minimize the number of Shared Links [RFC8800] */ + OBJFUN_MSL = 15, + /* Minimize the number of Shared SRLGs [RFC8800] */ + OBJFUN_MSS = 16, + /* Minimize the number of Shared Nodes [RFC8800] */ + OBJFUN_MSN = 17, +}; +#define MAX_OBJFUN_TYPE 17 + +enum affinity_filter_type { + AFFINITY_FILTER_UNDEFINED = 0, + AFFINITY_FILTER_EXCLUDE_ANY = 1, + AFFINITY_FILTER_INCLUDE_ANY = 2, + AFFINITY_FILTER_INCLUDE_ALL = 3, +}; +#define MAX_AFFINITY_FILTER_TYPE 3 + +struct srte_segment_list; + +struct srte_segment_entry { + RB_ENTRY(srte_segment_entry) entry; + + /* The segment list the entry belong to */ + struct srte_segment_list *segment_list; + + /* Index of the Label. */ + uint32_t index; + + /* Label Value. */ + mpls_label_t sid_value; + + /* NAI Type */ + enum srte_segment_nai_type nai_type; + /* NAI local address when nai type is not NONE */ + struct ipaddr nai_local_addr; + /* NAI local interface when nai type is not IPv4 unnumbered adjacency */ + uint32_t nai_local_iface; + /* NAI local interface when nai type is IPv4 or IPv6 adjacency */ + struct ipaddr nai_remote_addr; + /* NAI remote interface when nai type is not IPv4 unnumbered adjacency + */ + uint32_t nai_remote_iface; + /* Support draft-ietf-spring-segment-routing-policy sl types queries*/ + uint8_t nai_local_prefix_len; + uint8_t nai_algorithm; +}; +RB_HEAD(srte_segment_entry_head, srte_segment_entry); +RB_PROTOTYPE(srte_segment_entry_head, srte_segment_entry, entry, + srte_segment_entry_compare) + +struct srte_segment_list { + RB_ENTRY(srte_segment_list) entry; + + /* Name of the Segment List. */ + char name[64]; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* Nexthops. */ + struct srte_segment_entry_head segments; + + /* Status flags. */ + uint16_t flags; +#define F_SEGMENT_LIST_NEW 0x0002 +#define F_SEGMENT_LIST_MODIFIED 0x0004 +#define F_SEGMENT_LIST_DELETED 0x0008 +#define F_SEGMENT_LIST_SID_CONFLICT 0x0010 +}; +RB_HEAD(srte_segment_list_head, srte_segment_list); +RB_PROTOTYPE(srte_segment_list_head, srte_segment_list, entry, + srte_segment_list_compare) + +struct srte_metric { + uint16_t flags; +#define F_METRIC_IS_DEFINED 0x0001 +#define F_METRIC_IS_REQUIRED 0x0002 +#define F_METRIC_IS_BOUND 0x0004 +#define F_METRIC_IS_COMPUTED 0x0008 + float value; +}; + +/* Runtime information about the candidate path */ +struct srte_lsp { + /* Backpointer to the Candidate Path. */ + struct srte_candidate *candidate; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* Flags. */ + uint32_t flags; + + /* Metrics LSP Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* The objective function in used */ + enum objfun_type objfun; +}; + +/* Configured candidate path */ +struct srte_candidate { + RB_ENTRY(srte_candidate) entry; + + /* Backpointer to SR Policy */ + struct srte_policy *policy; + + /* The LSP associated with this candidate path. */ + struct srte_lsp *lsp; + + /* Administrative preference. */ + uint32_t preference; + + /* Symbolic Name. */ + char name[64]; + + /* The associated Segment List. */ + struct srte_segment_list *segment_list; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* The Discriminator */ + uint32_t discriminator; + + /* The Type (explicit or dynamic) */ + enum srte_candidate_type type; + + /* Flags. */ + uint32_t flags; +#define F_CANDIDATE_BEST 0x0001 +#define F_CANDIDATE_NEW 0x0002 +#define F_CANDIDATE_MODIFIED 0x0004 +#define F_CANDIDATE_DELETED 0x0008 +#define F_CANDIDATE_HAS_BANDWIDTH 0x0100 +#define F_CANDIDATE_REQUIRED_BANDWIDTH 0x0200 +#define F_CANDIDATE_HAS_OBJFUN 0x0400 +#define F_CANDIDATE_REQUIRED_OBJFUN 0x0800 +#define F_CANDIDATE_HAS_EXCLUDE_ANY 0x1000 +#define F_CANDIDATE_HAS_INCLUDE_ANY 0x2000 +#define F_CANDIDATE_HAS_INCLUDE_ALL 0x4000 + + /* Metrics Configured Values */ + struct srte_metric metrics[MAX_METRIC_TYPE]; + + /* Bandwidth Configured Value */ + float bandwidth; + + /* Configured objective functions */ + enum objfun_type objfun; + + /* Path constraints attribute filters */ + uint32_t affinity_filters[MAX_AFFINITY_FILTER_TYPE]; + + /* Hooks delaying timer */ + struct event *hook_timer; +}; + +RB_HEAD(srte_candidate_head, srte_candidate); +RB_PROTOTYPE(srte_candidate_head, srte_candidate, entry, srte_candidate_compare) + +#define ENDPOINT_STR_LENGTH IPADDR_STRING_SIZE + +struct srte_policy { + RB_ENTRY(srte_policy) entry; + + /* Color */ + uint32_t color; + + /* Endpoint */ + struct ipaddr endpoint; + + /* Name */ + char name[64]; + + /* Binding SID */ + mpls_label_t binding_sid; + + /* The Protocol-Origin. */ + enum srte_protocol_origin protocol_origin; + + /* The Originator */ + char originator[64]; + + /* Operational Status of the policy */ + enum srte_policy_status status; + + /* Best candidate path. */ + struct srte_candidate *best_candidate; + + /* Candidate Paths */ + struct srte_candidate_head candidate_paths; + /* Status flags. */ + uint16_t flags; +#define F_POLICY_NEW 0x0002 +#define F_POLICY_MODIFIED 0x0004 +#define F_POLICY_DELETED 0x0008 + /* SRP id for PcInitiated support */ + int srp_id; +}; +RB_HEAD(srte_policy_head, srte_policy); +RB_PROTOTYPE(srte_policy_head, srte_policy, entry, srte_policy_compare) + +DECLARE_HOOK(pathd_candidate_created, (struct srte_candidate * candidate), + (candidate)); +DECLARE_HOOK(pathd_candidate_updated, (struct srte_candidate * candidate), + (candidate)); +DECLARE_HOOK(pathd_candidate_removed, (struct srte_candidate * candidate), + (candidate)); + +extern struct srte_segment_list_head srte_segment_lists; +extern struct srte_policy_head srte_policies; +extern struct zebra_privs_t pathd_privs; + +/* master thread, defined in path_main.c */ +extern struct event_loop *master; + +/* pathd.c */ +struct srte_segment_list *srte_segment_list_add(const char *name); +void srte_segment_list_del(struct srte_segment_list *segment_list); +struct srte_segment_list *srte_segment_list_find(const char *name); +struct srte_segment_entry * +srte_segment_entry_add(struct srte_segment_list *segment_list, uint32_t index); +void srte_segment_entry_del(struct srte_segment_entry *segment); +int srte_segment_entry_set_nai(struct srte_segment_entry *segment, + enum srte_segment_nai_type type, + struct ipaddr *local_ip, uint32_t local_iface, + struct ipaddr *remote_ip, uint32_t remote_iface, + uint8_t algo, uint8_t pref_len); +void srte_segment_set_local_modification(struct srte_segment_list *s_list, + struct srte_segment_entry *s_entry, + uint32_t ted_sid); +struct srte_policy *srte_policy_add(uint32_t color, struct ipaddr *endpoint, + enum srte_protocol_origin origin, + const char *originator); +void srte_policy_del(struct srte_policy *policy); +struct srte_policy *srte_policy_find(uint32_t color, struct ipaddr *endpoint); +int srte_policy_update_ted_sid(void); +void srte_policy_update_binding_sid(struct srte_policy *policy, + uint32_t binding_sid); +void srte_apply_changes(void); +void srte_clean_zebra(void); +void srte_policy_apply_changes(struct srte_policy *policy); +struct srte_candidate *srte_candidate_add(struct srte_policy *policy, + uint32_t preference, + enum srte_protocol_origin origin, + const char *originator); +void srte_candidate_del(struct srte_candidate *candidate); +void srte_candidate_set_bandwidth(struct srte_candidate *candidate, + float bandwidth, bool required); +void srte_candidate_unset_bandwidth(struct srte_candidate *candidate); +void srte_candidate_set_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type, + float value, bool required, bool is_cound, + bool is_computed); +void srte_candidate_unset_metric(struct srte_candidate *candidate, + enum srte_candidate_metric_type type); +void srte_candidate_set_objfun(struct srte_candidate *candidate, bool required, + enum objfun_type type); +void srte_candidate_unset_objfun(struct srte_candidate *candidate); +void srte_candidate_set_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type, + uint32_t filter); +void srte_candidate_unset_affinity_filter(struct srte_candidate *candidate, + enum affinity_filter_type type); +void srte_lsp_set_bandwidth(struct srte_lsp *lsp, float bandwidth, + bool required); +void srte_lsp_unset_bandwidth(struct srte_lsp *lsp); +void srte_lsp_set_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type, float value, + bool required, bool is_cound, bool is_computed); +void srte_lsp_unset_metric(struct srte_lsp *lsp, + enum srte_candidate_metric_type type); +struct srte_candidate *srte_candidate_find(struct srte_policy *policy, + uint32_t preference); +struct srte_segment_entry * +srte_segment_entry_find(struct srte_segment_list *segment_list, uint32_t index); +void srte_candidate_status_update(struct srte_candidate *candidate, int status); +void srte_candidate_unset_segment_list(const char *originator, bool force); +const char *srte_origin2str(enum srte_protocol_origin origin); +void pathd_shutdown(void); +void path_policy_show_debugging(struct vty *vty); + +/* path_cli.c */ +void path_cli_init(void); + + +/** + * Search for sid based in prefix and algorithm + * + * @param Prefix The prefix to use + * @param algo Algorithm we want to query for + * @param ted_sid Sid to query + * + * @return void + */ +int32_t srte_ted_do_query_type_c(struct srte_segment_entry *entry, + struct prefix *prefix_cli, uint32_t algo); + +/** + * Search for sid based in prefix and interface id + * + * @param Prefix The prefix to use + * @param local_iface The id of interface + * @param ted_sid Sid to query + * + * @return void + */ +int32_t srte_ted_do_query_type_e(struct srte_segment_entry *entry, + struct prefix *prefix_cli, + uint32_t local_iface); +/** + * Search for sid based in local and remote ip + * + * @param entry entry to update + * @param local Local addr for query + * @param remote Local addr for query + * + * @return void + */ +int32_t srte_ted_do_query_type_f(struct srte_segment_entry *entry, + struct ipaddr *local, struct ipaddr *remote); +#endif /* _FRR_PATHD_H_ */ diff --git a/pathd/subdir.am b/pathd/subdir.am new file mode 100644 index 0000000..29be8f4 --- /dev/null +++ b/pathd/subdir.am @@ -0,0 +1,80 @@ +# +# pathd +# + +if PATHD +noinst_LIBRARIES += pathd/libpath.a +sbin_PROGRAMS += pathd/pathd +vtysh_daemons += pathd +# TODO add man page +#man8 += $(MANBUILD)/pathd.8 + +if PATHD_PCEP +module_LTLIBRARIES += pathd/pathd_pcep.la +endif + +endif + +pathd_libpath_a_SOURCES = \ + pathd/path_cli.c \ + pathd/path_debug.c \ + pathd/path_errors.c \ + pathd/path_nb.c \ + pathd/path_nb_config.c \ + pathd/path_nb_state.c \ + pathd/path_ted.c \ + pathd/path_zebra.c \ + pathd/pathd.c \ + # end + +clippy_scan += \ + pathd/path_cli.c \ + pathd/path_pcep_cli.c \ + pathd/path_ted.c \ + # end + +noinst_HEADERS += \ + pathd/path_debug.h \ + pathd/path_errors.h \ + pathd/path_nb.h \ + pathd/path_pcep.h \ + pathd/path_pcep_cli.h \ + pathd/path_pcep_controller.h \ + pathd/path_pcep_debug.h \ + pathd/path_pcep_lib.h \ + pathd/path_pcep_config.h \ + pathd/path_pcep_pcc.h \ + pathd/path_ted.h \ + pathd/path_zebra.h \ + pathd/pathd.h \ + # end + +pathd_pathd_SOURCES = \ + pathd/path_main.c \ + # end +nodist_pathd_pathd_SOURCES = \ + yang/frr-pathd.yang.c \ + # end +pathd_pathd_LDADD = pathd/libpath.a lib/libfrr.la -lm $(LIBCAP) + +pathd_pathd_pcep_la_SOURCES = \ + pathd/path_pcep.c \ + pathd/path_pcep_cli.c \ + pathd/path_pcep_controller.c \ + pathd/path_pcep_debug.c \ + pathd/path_pcep_lib.c \ + pathd/path_pcep_config.c \ + pathd/path_pcep_pcc.c \ + # end + +if PATHD_PCEP +pathd_pathd_pcep_la_CPPFLAGS = -I./pceplib $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = pceplib/libpcep_pcc.la +else +pathd_pathd_pcep_la_CPPFLAGS = $(AM_CPPFLAGS) +pathd_pathd_pcep_la_LIBADD = +endif + + +#pathd_pathd_pcep_la_CFLAGS = $(AM_CFLAGS) +pathd_pathd_pcep_la_LDFLAGS = $(MODULE_LDFLAGS) diff --git a/pbrd/.gitignore b/pbrd/.gitignore new file mode 100644 index 0000000..86622ea --- /dev/null +++ b/pbrd/.gitignore @@ -0,0 +1 @@ +pbrd diff --git a/pbrd/Makefile b/pbrd/Makefile new file mode 100644 index 0000000..e8999c3 --- /dev/null +++ b/pbrd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pbrd/pbrd +%: ALWAYS + @$(MAKE) -s -C .. pbrd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pbrd/pbr_debug.c b/pbrd/pbr_debug.c new file mode 100644 index 0000000..b30b54b --- /dev/null +++ b/pbrd/pbr_debug.c @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR - debugging + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#include + +#include "debug.h" +#include "command.h" +#include "vector.h" + +#include "pbrd/pbr_debug_clippy.c" +#include "pbrd/pbr_debug.h" + +struct debug pbr_dbg_map = {0, "PBR map"}; +struct debug pbr_dbg_zebra = {0, "PBR Zebra communications"}; +struct debug pbr_dbg_nht = {0, "PBR nexthop tracking"}; +struct debug pbr_dbg_event = {0, "PBR events"}; + +struct debug *pbr_debugs[] = {&pbr_dbg_map, &pbr_dbg_zebra, &pbr_dbg_nht, + &pbr_dbg_event}; + +const char *pbr_debugs_conflines[] = { + "debug pbr map", + "debug pbr zebra", + "debug pbr nht", + "debug pbr events", +}; + +void pbr_debug_set_all(uint32_t flags, bool set) +{ + for (unsigned int i = 0; i < array_size(pbr_debugs); i++) { + DEBUG_FLAGS_SET(pbr_debugs[i], flags, set); + + /* if all modes have been turned off, don't preserve options */ + if (!DEBUG_MODE_CHECK(pbr_debugs[i], DEBUG_MODE_ALL)) + DEBUG_CLEAR(pbr_debugs[i]); + } +} + +int pbr_debug_config_write_helper(struct vty *vty, bool config) +{ + uint32_t mode = DEBUG_MODE_ALL; + + if (config) + mode = DEBUG_MODE_CONF; + + for (unsigned int i = 0; i < array_size(pbr_debugs); i++) + if (DEBUG_MODE_CHECK(pbr_debugs[i], mode)) + vty_out(vty, "%s\n", pbr_debugs_conflines[i]); + return 0; +} + +int pbr_debug_config_write(struct vty *vty) +{ + return pbr_debug_config_write_helper(vty, true); +} + +struct debug_callbacks pbr_dbg_cbs = {.debug_set_all = pbr_debug_set_all}; + +void pbr_debug_init(void) +{ + debug_init(&pbr_dbg_cbs); +} diff --git a/pbrd/pbr_debug.h b/pbrd/pbr_debug.h new file mode 100644 index 0000000..0910997 --- /dev/null +++ b/pbrd/pbr_debug.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR - debugging + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#ifndef __PBR_DEBUG_H__ +#define __PBR_DEBUG_H__ + +#include + +#include "debug.h" + +/* PBR debugging records */ +extern struct debug pbr_dbg_map; +extern struct debug pbr_dbg_zebra; +extern struct debug pbr_dbg_nht; +extern struct debug pbr_dbg_event; + +/* + * Initialize PBR debugging. + * + * Installs VTY commands and registers callbacks. + */ +void pbr_debug_init(void); + +/* + * Set or unset flags on all debugs for pbrd. + * + * flags + * The flags to set + * + * set + * Whether to set or unset the specified flags + */ +void pbr_debug_set_all(uint32_t flags, bool set); + +/* + * Config write helper. + * + * vty + * Vty to write to + * + * config + * Whether we are writing to show run or saving config file + * + * Returns: + * 0 for convenience + */ +int pbr_debug_config_write_helper(struct vty *vty, bool config); + +/* + * Print PBR debugging configuration. + * + * vty + * VTY to print debugging configuration to. + */ +int pbr_debug_config_write(struct vty *vty); + +#endif /* __PBR_DEBUG_H__ */ diff --git a/pbrd/pbr_main.c b/pbrd/pbr_main.c new file mode 100644 index 0000000..6695b53 --- /dev/null +++ b/pbrd/pbr_main.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR - main code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "keychain.h" +#include "distribute.h" +#include "libfrr.h" +#include "routemap.h" +#include "nexthop.h" +#include "nexthop_group.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_zebra.h" +#include "pbr_vty.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, +}; + +struct zebra_privs_t pbr_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = { { 0 } }; + +/* Master of threads. */ +struct event_loop *master; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + pbr_vrf_terminate(); + + pbr_zebra_destroy(); + + frr_fini(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t pbr_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const pbrd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(pbrd, PBR, + .vty_port = PBR_VTY_PORT, + .proghelp = "Implementation of PBR.", + + .signals = pbr_signals, + .n_signals = array_size(pbr_signals), + + .privs = &pbr_privs, + + .yang_modules = pbrd_yang_modules, + .n_yang_modules = array_size(pbrd_yang_modules), +); +/* clang-format on */ + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pbrd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + pbr_debug_init(); + + nexthop_group_init(pbr_nhgroup_add_cb, pbr_nhgroup_modify_cb, + pbr_nhgroup_add_nexthop_cb, + pbr_nhgroup_del_nexthop_cb, pbr_nhgroup_delete_cb); + + /* + * So we safely ignore these commands since + * we are getting them at this point in time + */ + access_list_init(); + pbr_nht_init(); + pbr_map_init(); + hook_register_prio(if_real, 0, pbr_ifp_create); + hook_register_prio(if_up, 0, pbr_ifp_up); + hook_register_prio(if_down, 0, pbr_ifp_down); + hook_register_prio(if_unreal, 0, pbr_ifp_destroy); + pbr_zebra_init(); + pbr_vrf_init(); + pbr_vty_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/pbrd/pbr_map.c b/pbrd/pbr_map.c new file mode 100644 index 0000000..8f7a463 --- /dev/null +++ b/pbrd/pbr_map.c @@ -0,0 +1,947 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR-map Code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + * Portions: + * Copyright (c) 2021 The MITRE Corporation. + * Copyright (c) 2023 LabN Consulting, L.L.C. + */ +#include + +#include "frrevent.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "vrf.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "memory.h" +#include "log.h" +#include "vty.h" +#include "pbr.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_zebra.h" +#include "pbr_memory.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP, "PBR Map"); +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_SEQNO, "PBR Map Sequence"); +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_INTERFACE, "PBR Map Interface"); + +static uint32_t pbr_map_sequence_unique; + +static bool pbr_map_check_valid_internal(struct pbr_map *pbrm); +static inline int pbr_map_compare(const struct pbr_map *pbrmap1, + const struct pbr_map *pbrmap2); + +RB_GENERATE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) + +struct pbr_map_entry_head pbr_maps = RB_INITIALIZER(&pbr_maps); + +DEFINE_QOBJ_TYPE(pbr_map_sequence); + +static inline int pbr_map_compare(const struct pbr_map *pbrmap1, + const struct pbr_map *pbrmap2) +{ + return strcmp(pbrmap1->name, pbrmap2->name); +} + +static int pbr_map_sequence_compare(const struct pbr_map_sequence *pbrms1, + const struct pbr_map_sequence *pbrms2) +{ + if (pbrms1->seqno == pbrms2->seqno) + return 0; + + if (pbrms1->seqno < pbrms2->seqno) + return -1; + + return 1; +} + +void pbr_map_sequence_delete(struct pbr_map_sequence *pbrms) +{ + XFREE(MTYPE_TMP, pbrms->internal_nhg_name); + + QOBJ_UNREG(pbrms); + XFREE(MTYPE_PBR_MAP_SEQNO, pbrms); +} + +static int pbr_map_interface_compare(const struct pbr_map_interface *pmi1, + const struct pbr_map_interface *pmi2) +{ + return strcmp(pmi1->ifp->name, pmi2->ifp->name); +} + +static void pbr_map_interface_list_delete(struct pbr_map_interface *pmi) +{ + struct pbr_map_interface *pmi_int; + struct listnode *node, *nnode; + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS(pbrm->incoming, node, nnode, pmi_int)) { + if (pmi == pmi_int) { + pbr_map_policy_delete(pbrm, pmi); + return; + } + } + } +} + +static bool pbrms_is_installed(const struct pbr_map_sequence *pbrms, + const struct pbr_map_interface *pmi) +{ + uint64_t is_installed = (uint64_t)1 << pmi->install_bit; + + is_installed &= pbrms->installed; + + if (is_installed) + return true; + + return false; +} + +/* If any sequence is installed on the interface, assume installed */ +static bool +pbr_map_interface_is_installed(const struct pbr_map *pbrm, + const struct pbr_map_interface *check_pmi) +{ + + struct pbr_map_sequence *pbrms; + struct pbr_map_interface *pmi; + struct listnode *node, *inode; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pmi == check_pmi && pbrms_is_installed(pbrms, pmi)) + return true; + + return false; +} + +static bool pbr_map_interface_is_valid(const struct pbr_map_interface *pmi) +{ + /* Don't install rules without a real ifindex on the incoming interface. + * + * This can happen when we have config for an interface that does not + * exist or when an interface is changing vrfs. + */ + if (pmi->ifp && pmi->ifp->ifindex != IFINDEX_INTERNAL) + return true; + + return false; +} + +static void pbr_map_pbrms_update_common(struct pbr_map_sequence *pbrms, + bool install, bool changed) +{ + struct pbr_map *pbrm; + struct listnode *node; + struct pbr_map_interface *pmi; + + pbrm = pbrms->parent; + + if (pbrms->nhs_installed && pbrm->incoming->count) { + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (!pmi->ifp) + continue; + + if (install && !pbr_map_interface_is_valid(pmi)) + continue; + + pbr_send_pbr_map(pbrms, pmi, install, changed); + } + } +} + +static void pbr_map_pbrms_install(struct pbr_map_sequence *pbrms, bool changed) +{ + pbr_map_pbrms_update_common(pbrms, true, changed); +} + +static void pbr_map_pbrms_uninstall(struct pbr_map_sequence *pbrms) +{ + pbr_map_pbrms_update_common(pbrms, false, false); +} + +static const char *const pbr_map_reason_str[] = { + "Invalid NH-group", "Invalid NH", "No Nexthops", + "Both NH and NH-Group", "Invalid Src or Dst", "Invalid VRF", + "Both VLAN Set and Strip", "Deleting Sequence", +}; + +void pbr_map_reason_string(unsigned int reason, char *buf, int size) +{ + unsigned int bit; + int len = 0; + + if (!buf) + return; + + for (bit = 0; bit < array_size(pbr_map_reason_str); bit++) { + if ((reason & (1 << bit)) && (len < size)) { + len += snprintf((buf + len), (size - len), "%s%s", + (len > 0) ? ", " : "", + pbr_map_reason_str[bit]); + } + } +} + +void pbr_map_final_interface_deletion(struct pbr_map *pbrm, + struct pbr_map_interface *pmi) +{ + if (pmi->delete && !pbr_map_interface_is_installed(pbrm, pmi)) { + listnode_delete(pbrm->incoming, pmi); + pmi->pbrm = NULL; + + bf_release_index(pbrm->ifi_bitfield, pmi->install_bit); + XFREE(MTYPE_PBR_MAP_INTERFACE, pmi); + } +} + +void pbr_map_interface_delete(struct pbr_map *pbrm, struct interface *ifp_del) +{ + + struct listnode *node; + struct pbr_map_interface *pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (ifp_del == pmi->ifp) + break; + } + + if (pmi) + pbr_map_policy_delete(pbrm, pmi); +} + +void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp_add) +{ + struct listnode *node; + struct pbr_map_interface *pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (ifp_add == pmi->ifp) + return; + } + + pmi = XCALLOC(MTYPE_PBR_MAP_INTERFACE, sizeof(*pmi)); + pmi->ifp = ifp_add; + pmi->pbrm = pbrm; + listnode_add_sort(pbrm->incoming, pmi); + + bf_assign_index(pbrm->ifi_bitfield, pmi->install_bit); + pbr_map_check_valid(pbrm->name); + if (pbrm->valid) + pbr_map_install(pbrm); +} + +static int +pbr_map_policy_interface_update_common(const struct interface *ifp, + struct pbr_interface **pbr_ifp, + struct pbr_map **pbrm) +{ + if (!ifp->info) { + DEBUGD(&pbr_dbg_map, "%s: %s has no pbr_interface info", + __func__, ifp->name); + return -1; + } + + *pbr_ifp = ifp->info; + + *pbrm = pbrm_find((*pbr_ifp)->mapname); + + if (!*pbrm) { + DEBUGD(&pbr_dbg_map, "%s: applied PBR-MAP(%s) does not exist?", + __func__, (*pbr_ifp)->mapname); + return -1; + } + + return 0; +} + +void pbr_map_policy_interface_update(const struct interface *ifp, bool state_up) +{ + struct pbr_interface *pbr_ifp; + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + + if (pbr_map_policy_interface_update_common(ifp, &pbr_ifp, &pbrm)) + return; + + DEBUGD(&pbr_dbg_map, "%s: %s %s rules on interface %s", __func__, + pbr_ifp->mapname, (state_up ? "installing" : "removing"), + ifp->name); + + /* + * Walk the list and install/remove maps on the interface. + */ + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pmi->ifp == ifp && pbr_map_interface_is_valid(pmi)) + pbr_send_pbr_map(pbrms, pmi, state_up, true); +} + +static void pbrms_vrf_update(struct pbr_map_sequence *pbrms, + const struct pbr_vrf *pbr_vrf) +{ + const char *vrf_name = pbr_vrf_name(pbr_vrf); + + if (pbrms->vrf_lookup + && (strncmp(vrf_name, pbrms->vrf_name, sizeof(pbrms->vrf_name)) + == 0)) { + DEBUGD(&pbr_dbg_map, " Seq %u uses vrf %s (%u), updating map", + pbrms->seqno, vrf_name, pbr_vrf_id(pbr_vrf)); + + pbr_map_check(pbrms, false); + } +} + +/* Vrf enabled/disabled */ +void pbr_map_vrf_update(const struct pbr_vrf *pbr_vrf) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + if (!pbr_vrf) + return; + + bool enabled = pbr_vrf_is_enabled(pbr_vrf); + + DEBUGD(&pbr_dbg_map, "%s: %s (%u) %s, updating pbr maps", __func__, + pbr_vrf_name(pbr_vrf), pbr_vrf_id(pbr_vrf), + enabled ? "enabled" : "disabled"); + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + DEBUGD(&pbr_dbg_map, "%s: Looking at %s", __func__, pbrm->name); + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbrms_vrf_update(pbrms, pbr_vrf); + } +} + +void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp) +{ + struct pbr_interface *pbr_ifp = ifp->info; + + if (pbr_ifp + && strncmp(pbr_ifp->mapname, "", sizeof(pbr_ifp->mapname)) != 0) + vty_out(vty, " pbr-policy %s\n", pbr_ifp->mapname); +} + +struct pbr_map *pbrm_find(const char *name) +{ + struct pbr_map pbrm; + + strlcpy(pbrm.name, name, sizeof(pbrm.name)); + + return RB_FIND(pbr_map_entry_head, &pbr_maps, &pbrm); +} + +extern void pbr_map_delete(struct pbr_map_sequence *pbrms) +{ + struct pbr_map *pbrm; + struct listnode *inode; + struct pbr_map_interface *pmi; + + pbrm = pbrms->parent; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + pbr_send_pbr_map(pbrms, pmi, false, false); + + if (pbrms->nhg) + pbr_nht_delete_individual_nexthop(pbrms); + + if (pbrms->nhgrp_name) + XFREE(MTYPE_TMP, pbrms->nhgrp_name); + + prefix_free(&pbrms->dst); + + listnode_delete(pbrm->seqnumbers, pbrms); + + if (pbrm->seqnumbers->count == 0) { + RB_REMOVE(pbr_map_entry_head, &pbr_maps, pbrm); + + bf_free(pbrm->ifi_bitfield); + XFREE(MTYPE_PBR_MAP, pbrm); + } +} + +static void pbr_map_delete_common(struct pbr_map_sequence *pbrms) +{ + struct pbr_map *pbrm = pbrms->parent; + + pbr_map_pbrms_uninstall(pbrms); + + pbrm->valid = false; + pbrms->nhs_installed = false; + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + XFREE(MTYPE_TMP, pbrms->nhgrp_name); +} + +void pbr_map_delete_nexthops(struct pbr_map_sequence *pbrms) +{ + pbr_map_delete_common(pbrms); +} + +void pbr_map_delete_vrf(struct pbr_map_sequence *pbrms) +{ + pbr_map_delete_common(pbrms); +} + +struct pbr_map_sequence *pbrms_lookup_unique(uint32_t unique, char *ifname, + struct pbr_map_interface **ppmi) +{ + struct pbr_map_sequence *pbrms; + struct listnode *snode, *inode; + struct pbr_map_interface *pmi; + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) { + if (strcmp(pmi->ifp->name, ifname) != 0) + continue; + + if (ppmi) + *ppmi = pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, + pbrms)) { + DEBUGD(&pbr_dbg_map, "%s: Comparing %u to %u", + __func__, pbrms->unique, unique); + if (pbrms->unique == unique) + return pbrms; + } + } + } + + return NULL; +} + +static void pbr_map_add_interfaces(struct pbr_map *pbrm) +{ + struct interface *ifp; + struct pbr_interface *pbr_ifp; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->info) { + pbr_ifp = ifp->info; + if (strcmp(pbrm->name, pbr_ifp->mapname) == 0) + pbr_map_add_interface(pbrm, ifp); + } + } + } +} + +/* Decodes a standardized DSCP into its representative value */ +uint8_t pbr_map_decode_dscp_enum(const char *name) +{ + /* Standard Differentiated Services Field Codepoints */ + if (!strcmp(name, "cs0")) + return 0; + if (!strcmp(name, "cs1")) + return 8; + if (!strcmp(name, "cs2")) + return 16; + if (!strcmp(name, "cs3")) + return 24; + if (!strcmp(name, "cs4")) + return 32; + if (!strcmp(name, "cs5")) + return 40; + if (!strcmp(name, "cs6")) + return 48; + if (!strcmp(name, "cs7")) + return 56; + if (!strcmp(name, "af11")) + return 10; + if (!strcmp(name, "af12")) + return 12; + if (!strcmp(name, "af13")) + return 14; + if (!strcmp(name, "af21")) + return 18; + if (!strcmp(name, "af22")) + return 20; + if (!strcmp(name, "af23")) + return 22; + if (!strcmp(name, "af31")) + return 26; + if (!strcmp(name, "af32")) + return 28; + if (!strcmp(name, "af33")) + return 30; + if (!strcmp(name, "af41")) + return 34; + if (!strcmp(name, "af42")) + return 36; + if (!strcmp(name, "af43")) + return 38; + if (!strcmp(name, "ef")) + return 46; + if (!strcmp(name, "voice-admit")) + return 44; + + /* No match? Error out */ + return -1; +} + +struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno) +{ + struct pbr_map *pbrm = NULL; + struct pbr_map_sequence *pbrms = NULL; + struct listnode *node = NULL; + + pbrm = pbrm_find(name); + if (!pbrm) { + pbrm = XCALLOC(MTYPE_PBR_MAP, sizeof(*pbrm)); + snprintf(pbrm->name, sizeof(pbrm->name), "%s", name); + + pbrm->seqnumbers = list_new(); + pbrm->seqnumbers->cmp = + (int (*)(void *, void *))pbr_map_sequence_compare; + pbrm->seqnumbers->del = + (void (*)(void *))pbr_map_sequence_delete; + + pbrm->incoming = list_new(); + pbrm->incoming->cmp = + (int (*)(void *, void *))pbr_map_interface_compare; + pbrm->incoming->del = + (void (*)(void *))pbr_map_interface_list_delete; + + RB_INSERT(pbr_map_entry_head, &pbr_maps, pbrm); + + bf_init(pbrm->ifi_bitfield, 64); + pbr_map_add_interfaces(pbrm); + } + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->seqno == seqno) + break; + + } + + if (!pbrms) { + pbrms = XCALLOC(MTYPE_PBR_MAP_SEQNO, sizeof(*pbrms)); + pbrms->unique = pbr_map_sequence_unique++; + pbrms->seqno = seqno; + pbrms->ruleno = pbr_nht_get_next_rule(seqno); + pbrms->parent = pbrm; + + pbrms->action_queue_id = PBR_MAP_UNDEFINED_QUEUE_ID; + + pbrms->reason = + PBR_MAP_INVALID_EMPTY | + PBR_MAP_INVALID_NO_NEXTHOPS; + pbrms->vrf_name[0] = '\0'; + + QOBJ_REG(pbrms, pbr_map_sequence); + listnode_add_sort(pbrm->seqnumbers, pbrms); + } + + return pbrms; +} + +static void +pbr_map_sequence_check_nexthops_valid(struct pbr_map_sequence *pbrms) +{ + /* Check if any are present first */ + if (!pbrms->vrf_unchanged && !pbrms->vrf_lookup && !pbrms->nhg + && !pbrms->nhgrp_name) { + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + return; + } + + /* + * Check validness of vrf. + */ + + /* This one can be considered always valid */ + if (pbrms->vrf_unchanged) + pbrms->nhs_installed = true; + + if (pbrms->vrf_lookup) { + struct pbr_vrf *pbr_vrf = + pbr_vrf_lookup_by_name(pbrms->vrf_name); + + if (pbr_vrf && pbr_vrf_is_valid(pbr_vrf)) + pbrms->nhs_installed = true; + else + pbrms->reason |= PBR_MAP_INVALID_VRF; + } + + /* + * Check validness of the nexthop or nexthop-group + */ + + /* Only nexthop or nexthop group allowed */ + if (pbrms->nhg && pbrms->nhgrp_name) + pbrms->reason |= PBR_MAP_INVALID_BOTH_NHANDGRP; + + if (pbrms->nhg && + !pbr_nht_nexthop_group_valid(pbrms->internal_nhg_name)) + pbrms->reason |= PBR_MAP_INVALID_NEXTHOP; + + if (pbrms->nhgrp_name) { + if (!pbr_nht_nexthop_group_valid(pbrms->nhgrp_name)) + pbrms->reason |= PBR_MAP_INVALID_NEXTHOP_GROUP; + else + pbrms->nhs_installed = true; + } +} + +static void pbr_map_sequence_check_not_empty(struct pbr_map_sequence *pbrms) +{ + /* clang-format off */ + if ( + !CHECK_FLAG(pbrms->filter_bm, ( + PBR_FILTER_SRC_IP | + PBR_FILTER_DST_IP | + PBR_FILTER_SRC_PORT | + PBR_FILTER_DST_PORT | + + PBR_FILTER_IP_PROTOCOL | + PBR_FILTER_DSCP | + PBR_FILTER_ECN | + + PBR_FILTER_FWMARK | + PBR_FILTER_PCP | + PBR_FILTER_VLAN_ID | + PBR_FILTER_VLAN_FLAGS + )) && + !CHECK_FLAG(pbrms->action_bm, ( + PBR_ACTION_SRC_IP | + PBR_ACTION_DST_IP | + PBR_ACTION_SRC_PORT | + PBR_ACTION_DST_PORT | + + PBR_ACTION_DSCP | + PBR_ACTION_ECN | + + PBR_ACTION_PCP | + PBR_ACTION_VLAN_ID | + PBR_ACTION_VLAN_STRIP_INNER_ANY | + + PBR_ACTION_QUEUE_ID + )) + ) { + pbrms->reason |= PBR_MAP_INVALID_EMPTY; + } + /* clang-format on */ +} + +static void pbr_map_sequence_check_vlan_actions(struct pbr_map_sequence *pbrms) +{ + /* The set vlan tag action does the following: + * 1. If the frame is untagged, it tags the frame with the + * configured VLAN ID. + * 2. If the frame is tagged, if replaces the tag. + * + * The strip vlan action removes any inner tag, so it is invalid to + * specify both a set and strip action. + */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID) && + (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY))) + pbrms->reason |= PBR_MAP_INVALID_SET_STRIP_VLAN; +} + + +/* + * Checks to see if we think that the pbmrs is valid. If we think + * the config is valid return true. + */ +static void pbr_map_sequence_check_valid(struct pbr_map_sequence *pbrms) +{ + pbr_map_sequence_check_nexthops_valid(pbrms); + pbr_map_sequence_check_vlan_actions(pbrms); + pbr_map_sequence_check_not_empty(pbrms); +} + +static bool pbr_map_check_valid_internal(struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + pbrm->valid = true; + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + pbrms->reason = 0; + pbr_map_sequence_check_valid(pbrms); + /* + * A pbr_map_sequence that is invalid causes + * the whole shebang to be invalid + */ + if (pbrms->reason != 0) + pbrm->valid = false; + } + + return pbrm->valid; +} + +/* + * For a given PBR-MAP check to see if we think it is a + * valid config or not. If so note that it is and return + * that we are valid. + */ +bool pbr_map_check_valid(const char *name) +{ + struct pbr_map *pbrm; + + pbrm = pbrm_find(name); + if (!pbrm) { + DEBUGD(&pbr_dbg_map, + "%s: Specified PBR-MAP(%s) does not exist?", __func__, + name); + return false; + } + + pbr_map_check_valid_internal(pbrm); + return pbrm->valid; +} + +void pbr_map_schedule_policy_from_nhg(const char *nh_group, bool installed) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + DEBUGD(&pbr_dbg_map, "%s: Looking at %s", __func__, pbrm->name); + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + DEBUGD(&pbr_dbg_map, " NH Grp name: %s", + pbrms->nhgrp_name ? + pbrms->nhgrp_name : pbrms->internal_nhg_name); + + if (pbrms->nhgrp_name + && (strcmp(nh_group, pbrms->nhgrp_name) == 0)) { + pbrms->nhs_installed = installed; + + pbr_map_check(pbrms, false); + } + + if (pbrms->nhg + && (strcmp(nh_group, pbrms->internal_nhg_name) + == 0)) { + pbrms->nhs_installed = installed; + + pbr_map_check(pbrms, false); + } + } + } +} + +void pbr_map_policy_install(const char *name) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + + DEBUGD(&pbr_dbg_map, "%s: for %s", __func__, name); + pbrm = pbrm_find(name); + if (!pbrm) + return; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + DEBUGD(&pbr_dbg_map, + "%s: Looking at what to install %s(%u) %d %d", __func__, + name, pbrms->seqno, pbrm->valid, pbrms->nhs_installed); + + if (pbrm->valid && pbrms->nhs_installed + && pbrm->incoming->count) { + DEBUGD(&pbr_dbg_map, " Installing %s %u", pbrm->name, + pbrms->seqno); + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pbr_map_interface_is_valid(pmi)) + pbr_send_pbr_map(pbrms, pmi, true, + false); + } + } +} + +void pbr_map_policy_delete(struct pbr_map *pbrm, struct pbr_map_interface *pmi) +{ + struct listnode *node; + struct pbr_map_sequence *pbrms; + bool sent = false; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + if (pbr_send_pbr_map(pbrms, pmi, false, true)) + sent = true; /* rule removal sent to zebra */ + + pmi->delete = true; + + /* + * If we actually sent something for deletion, wait on zapi callback + * before clearing data. + */ + if (sent) + return; + + pbr_map_final_interface_deletion(pbrm, pmi); +} + +/* + * For a nexthop group specified, see if any of the pbr-maps + * are using it and if so, check to see that we are still + * valid for usage. If we are valid then schedule the installation/deletion + * of the pbr-policy. + */ +void pbr_map_check_nh_group_change(const char *nh_group) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + bool found_name; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + found_name = false; + if (pbrms->nhgrp_name) + found_name = + !strcmp(nh_group, pbrms->nhgrp_name); + else if (pbrms->nhg) + found_name = !strcmp(nh_group, + pbrms->internal_nhg_name); + + if (found_name) { + bool original = pbrm->valid; + + /* Set data we were waiting on */ + if (pbrms->nhgrp_name) + pbr_nht_set_seq_nhg_data( + pbrms, + nhgc_find(pbrms->nhgrp_name)); + + pbr_map_check_valid_internal(pbrm); + + if (pbrm->valid && (original != pbrm->valid)) + pbr_map_install(pbrm); + + if (pbrm->valid == false) + for (ALL_LIST_ELEMENTS_RO( + pbrm->incoming, inode, + pmi)) + pbr_send_pbr_map(pbrms, pmi, + false, false); + } + } + } +} + +void pbr_map_check_vrf_nh_group_change(const char *nh_group, + struct pbr_vrf *pbr_vrf, + uint32_t old_vrf_id) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->nhgrp_name) + continue; + + if (pbrms->nhg == NULL) + continue; + + if (strcmp(nh_group, pbrms->internal_nhg_name)) + continue; + + if (pbrms->nhg->nexthop == NULL) + continue; + + if (pbrms->nhg->nexthop->vrf_id != old_vrf_id) + continue; + + pbrms->nhg->nexthop->vrf_id = pbr_vrf_id(pbr_vrf); + } + } +} + +void pbr_map_check_interface_nh_group_change(const char *nh_group, + struct interface *ifp, + ifindex_t oldifindex) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->nhgrp_name) + continue; + + if (pbrms->nhg == NULL) + continue; + + if (strcmp(nh_group, pbrms->internal_nhg_name)) + continue; + + if (pbrms->nhg->nexthop == NULL) + continue; + + if (pbrms->nhg->nexthop->ifindex != oldifindex) + continue; + + pbrms->nhg->nexthop->ifindex = ifp->ifindex; + } + } +} + +void pbr_map_check(struct pbr_map_sequence *pbrms, bool changed) +{ + struct pbr_map *pbrm; + bool install; + + pbrm = pbrms->parent; + DEBUGD(&pbr_dbg_map, "%s: for %s(%u)", __func__, pbrm->name, + pbrms->seqno); + if (pbr_map_check_valid(pbrm->name)) + DEBUGD(&pbr_dbg_map, "We are totally valid %s", + pbrm->name); + + if (pbrms->reason == PBR_MAP_VALID_SEQUENCE_NUMBER) { + install = true; + DEBUGD(&pbr_dbg_map, "%s: Installing %s(%u) reason: %" PRIu64, + __func__, pbrm->name, pbrms->seqno, pbrms->reason); + DEBUGD(&pbr_dbg_map, + " Sending PBR_MAP_POLICY_INSTALL event"); + } else { + install = false; + DEBUGD(&pbr_dbg_map, "%s: Removing %s(%u) reason: %" PRIu64, + __func__, pbrm->name, pbrms->seqno, pbrms->reason); + } + + if (install) + pbr_map_pbrms_install(pbrms, changed); + else + pbr_map_pbrms_uninstall(pbrms); +} + +void pbr_map_install(struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + if (!pbrm->incoming->count) + return; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbr_map_pbrms_install(pbrms, false); +} + +void pbr_map_init(void) +{ + RB_INIT(pbr_map_entry_head, &pbr_maps); + + pbr_map_sequence_unique = 1; +} diff --git a/pbrd/pbr_map.h b/pbrd/pbr_map.h new file mode 100644 index 0000000..9fb674b --- /dev/null +++ b/pbrd/pbr_map.h @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR-map Header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + * Portions: + * Copyright (c) 2023 LabN Consulting, L.L.C. + * Copyright (c) 2021 The MITRE Corporation + */ +#ifndef __PBR_MAP_H__ +#define __PBR_MAP_H__ + +#include + +#include "pbr_vrf.h" + +struct pbr_map { + /* + * RB Tree of the pbr_maps + */ + RB_ENTRY(pbr_map) pbr_map_entry; + + /* + * The name of the PBR_MAP + */ +#define PBR_MAP_NAMELEN 100 + char name[PBR_MAP_NAMELEN]; + + struct list *seqnumbers; + + /* + * The list of incoming interfaces that + * we will apply this policy map onto + */ + struct list *incoming; + + bitfield_t ifi_bitfield; + /* + * If valid is true we think the pbr_map is valid, + * If false, look in individual pbrms to see + * what we think is the invalid reason + */ + bool valid; +}; + +RB_HEAD(pbr_map_entry_head, pbr_map); +RB_PROTOTYPE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) + +struct pbr_map_interface { + uint32_t install_bit; + + struct interface *ifp; + + struct pbr_map *pbrm; + + bool delete; +}; + +enum pbr_forwarding_type { + PBR_FT_UNSPEC = 0, + PBR_FT_VRF_UNCHANGED, + PBR_FT_SETVRF, + PBR_FT_NEXTHOP_GROUP, + PBR_FT_NEXTHOP_SINGLE, +}; + +struct pbr_map_sequence { + struct pbr_map *parent; + + /* + * The Unique identifier of this specific pbrms + */ + uint32_t unique; + + /* + * The sequence of where we are for display + */ + uint32_t seqno; + + /* + * The rule number to install into + */ + uint32_t ruleno; + + + /***************************************************************** + * Filter fields + * gpz 230716: I hope to replace all of the filter fields with + * 'struct pbr_filter' from lib/pbr.h. + *****************************************************************/ + + /* + * same bit definitions as in lib/pbr.h + */ + uint32_t filter_bm; + + /* Family of the src/dst. Needed when deleting since we clear them */ + unsigned char family; + + /* src and dst IP addresses */ + struct prefix *src; + struct prefix *dst; + + /* src and dst UDP/TCP ports */ + uint16_t src_prt; + uint16_t dst_prt; + + uint8_t ip_proto; + + uint8_t match_pcp; + uint16_t match_vlan_id; /* bits defined in lib/pbr.h */ + + uint16_t match_vlan_flags; + + uint8_t dsfield; + uint32_t mark; + + /***************************************************************** + * Action fields + *****************************************************************/ + + /* + * same bit definitions as in lib/pbr.h + */ + uint32_t action_bm; + + union sockunion action_src; + union sockunion action_dst; + + uint16_t action_src_port; + uint16_t action_dst_port; + + uint8_t action_dscp; + uint8_t action_ecn; + + uint8_t action_pcp; + uint8_t action_vlan_id; + +#define PBR_MAP_UNDEFINED_QUEUE_ID 0 + uint32_t action_queue_id; + + enum pbr_forwarding_type forwarding_type; + + /* + * Use interface's vrf. + */ + bool vrf_unchanged; + + /* + * The vrf to lookup in was directly configured. + */ + bool vrf_lookup; + + /* + * VRF to lookup. + */ + char vrf_name[VRF_NAMSIZ + 1]; + + /* + * The nexthop group we auto create + * for when the user specifies a individual + * nexthop + */ + struct nexthop_group *nhg; + char *internal_nhg_name; + + /* + * The name of the nexthop group + * configured in the pbr-map + */ + char *nhgrp_name; + + /* + * Do we think are nexthops are installed + */ + bool nhs_installed; + + /* + * Are we installed + */ + uint64_t installed; + + /* + * A reason of 0 means we think the pbr_map_sequence is good to go + * We can accumuluate multiple failure states + */ +#define PBR_MAP_VALID_SEQUENCE_NUMBER 0 +#define PBR_MAP_INVALID_NEXTHOP_GROUP (1 << 0) +#define PBR_MAP_INVALID_NEXTHOP (1 << 1) +#define PBR_MAP_INVALID_NO_NEXTHOPS (1 << 2) +#define PBR_MAP_INVALID_BOTH_NHANDGRP (1 << 3) +#define PBR_MAP_INVALID_EMPTY (1 << 4) +#define PBR_MAP_INVALID_VRF (1 << 5) +#define PBR_MAP_INVALID_SET_STRIP_VLAN (1 << 6) + uint64_t reason; + + QOBJ_FIELDS; +}; + +DECLARE_QOBJ_TYPE(pbr_map_sequence); + +extern struct pbr_map_entry_head pbr_maps; + +extern struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno); +extern struct pbr_map_sequence * +pbrms_lookup_unique(uint32_t unique, char *ifname, + struct pbr_map_interface **ppmi); + +extern struct pbr_map *pbrm_find(const char *name); +extern void pbr_map_delete(struct pbr_map_sequence *pbrms); +extern void pbr_map_delete_nexthops(struct pbr_map_sequence *pbrms); +extern void pbr_map_delete_vrf(struct pbr_map_sequence *pbrms); +extern void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp); +extern void pbr_map_interface_delete(struct pbr_map *pbrm, + struct interface *ifp); + +extern uint8_t pbr_map_decode_dscp_enum(const char *name); + +/* Update maps installed on interface */ +extern void pbr_map_policy_interface_update(const struct interface *ifp, + bool state_up); + +extern void pbr_map_final_interface_deletion(struct pbr_map *pbrm, + struct pbr_map_interface *pmi); + +extern void pbr_map_vrf_update(const struct pbr_vrf *pbr_vrf); + +extern void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp); +extern void pbr_map_init(void); + +extern bool pbr_map_check_valid(const char *name); + +/** + * Re-check the pbr map for validity. + * + * Install if valid, remove if not. + * + * If changed is set, the config on the on the map has changed somewhere + * and the rules need to be replaced if valid. + */ +extern void pbr_map_check(struct pbr_map_sequence *pbrms, bool changed); +extern void pbr_map_check_nh_group_change(const char *nh_group); +extern void pbr_map_reason_string(unsigned int reason, char *buf, int size); + +extern void pbr_map_schedule_policy_from_nhg(const char *nh_group, + bool installed); + +extern void pbr_map_install(struct pbr_map *pbrm); + +extern void pbr_map_policy_install(const char *name); +extern void pbr_map_policy_delete(struct pbr_map *pbrm, + struct pbr_map_interface *pmi); + +extern void pbr_map_sequence_delete(struct pbr_map_sequence *pbrms); + +extern void pbr_map_check_vrf_nh_group_change(const char *nh_group, + struct pbr_vrf *pbr_vrf, + uint32_t old_vrf_id); +extern void pbr_map_check_interface_nh_group_change(const char *nh_group, + struct interface *ifp, + ifindex_t oldifindex); +#endif diff --git a/pbrd/pbr_memory.c b/pbrd/pbr_memory.c new file mode 100644 index 0000000..0e93ecc --- /dev/null +++ b/pbrd/pbr_memory.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR memory code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include + +#include "pbrd/pbr_memory.h" + + +DEFINE_MGROUP(PBRD, "pbrd"); diff --git a/pbrd/pbr_memory.h b/pbrd/pbr_memory.h new file mode 100644 index 0000000..29a09db --- /dev/null +++ b/pbrd/pbr_memory.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pbr memory code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PBR_MEMORY_H__ + +DECLARE_MGROUP(PBRD); + +#endif diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c new file mode 100644 index 0000000..ff252f8 --- /dev/null +++ b/pbrd/pbr_nht.c @@ -0,0 +1,1491 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR-nht Code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include +#include "nexthop_group.h" +#include "nexthop_group_private.h" +#include +#include +#include +#include +#include + +#include "pbrd/pbr_nht.h" +#include "pbrd/pbr_map.h" +#include "pbrd/pbr_zebra.h" +#include "pbrd/pbr_memory.h" +#include "pbrd/pbr_debug.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_NHG, "PBR Nexthop Groups"); + +struct hash *pbr_nhg_hash; +static struct hash *pbr_nhrc_hash; +static struct hash *pbr_nhg_allocated_id_hash; + +static uint32_t pbr_nhg_low_table; +static uint32_t pbr_nhg_high_table; +static uint32_t pbr_next_unallocated_table_id; +static uint32_t pbr_nhg_low_rule; +static uint32_t pbr_nhg_high_rule; + +static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg); +static void +pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, + enum nexthop_types_t nh_type); + +/* + * Nexthop refcount. + */ +struct nhrc { + struct nexthop nexthop; + unsigned int refcount; +}; + +/* Hash functions for pbr_nhrc_hash ---------------------------------------- */ + +static void *pbr_nhrc_hash_alloc(void *p) +{ + struct nhrc *nhrc = XCALLOC(MTYPE_PBR_NHG, sizeof(struct nhrc)); + nhrc->nexthop = *(struct nexthop *)p; + nhrc->nexthop.next = NULL; + nhrc->nexthop.prev = NULL; + return nhrc; +} + +static bool pbr_nhrc_hash_equal(const void *arg1, const void *arg2) +{ + const struct nexthop *nh1, *nh2; + + nh1 = arg1; + nh2 = arg2; + + return nexthop_same(nh1, nh2); +} + +/* ------------------------------------------------------------------------- */ + +static void *pbr_nh_alloc(void *p) +{ + struct pbr_nexthop_cache *new; + struct pbr_nexthop_cache *pnhc = (struct pbr_nexthop_cache *)p; + struct nhrc *nhrc; + + new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); + nhrc = hash_get(pbr_nhrc_hash, &pnhc->nexthop, pbr_nhrc_hash_alloc); + new->nexthop = nhrc->nexthop; + + /* Decremented again in pbr_nh_delete */ + ++nhrc->refcount; + + DEBUGD(&pbr_dbg_nht, "%s: Sending nexthop to Zebra", __func__); + + pbr_send_rnh(&new->nexthop, true); + + new->valid = false; + return new; +} + +static void pbr_nh_delete(struct pbr_nexthop_cache **pnhc) +{ + struct nhrc *nhrc; + + nhrc = hash_lookup(pbr_nhrc_hash, &((*pnhc)->nexthop)); + + if (nhrc) + --nhrc->refcount; + if (!nhrc || nhrc->refcount == 0) { + DEBUGD(&pbr_dbg_nht, "%s: Removing nexthop from Zebra", + __func__); + pbr_send_rnh(&((*pnhc)->nexthop), false); + } + if (nhrc && nhrc->refcount == 0) { + hash_release(pbr_nhrc_hash, nhrc); + XFREE(MTYPE_PBR_NHG, nhrc); + } + + XFREE(MTYPE_PBR_NHG, *pnhc); +} + +static void pbr_nh_delete_iterate(struct hash_bucket *b, void *p) +{ + pbr_nh_delete((struct pbr_nexthop_cache **)&b->data); +} + +static uint32_t pbr_nh_hash_key(const void *arg) +{ + uint32_t key; + const struct pbr_nexthop_cache *pbrnc = arg; + + key = nexthop_hash(&pbrnc->nexthop); + + return key; +} + +static bool pbr_nh_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_cache *pbrnc1 = + (const struct pbr_nexthop_cache *)arg1; + const struct pbr_nexthop_cache *pbrnc2 = + (const struct pbr_nexthop_cache *)arg2; + + if (pbrnc1->nexthop.vrf_id != pbrnc2->nexthop.vrf_id) + return false; + + if (pbrnc1->nexthop.ifindex != pbrnc2->nexthop.ifindex) + return false; + + if (pbrnc1->nexthop.type != pbrnc2->nexthop.type) + return false; + + switch (pbrnc1->nexthop.type) { + case NEXTHOP_TYPE_IFINDEX: + return pbrnc1->nexthop.ifindex == pbrnc2->nexthop.ifindex; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + return pbrnc1->nexthop.gate.ipv4.s_addr + == pbrnc2->nexthop.gate.ipv4.s_addr; + case NEXTHOP_TYPE_IPV6_IFINDEX: + case NEXTHOP_TYPE_IPV6: + return !memcmp(&pbrnc1->nexthop.gate.ipv6, + &pbrnc2->nexthop.gate.ipv6, 16); + case NEXTHOP_TYPE_BLACKHOLE: + return pbrnc1->nexthop.bh_type == pbrnc2->nexthop.bh_type; + } + + /* + * We should not get here + */ + return false; +} + +static void pbr_nhgc_delete(struct pbr_nexthop_group_cache *p) +{ + hash_iterate(p->nhh, pbr_nh_delete_iterate, NULL); + hash_free(p->nhh); + XFREE(MTYPE_PBR_NHG, p); +} + +static void *pbr_nhgc_alloc(void *p) +{ + struct pbr_nexthop_group_cache *new; + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)p; + + new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); + + strlcpy(new->name, pnhgc->name, sizeof(pnhgc->name)); + pbr_nht_reserve_next_table_id(new); + + DEBUGD(&pbr_dbg_nht, "%s: NHT: %s assigned Table ID: %u", __func__, + new->name, new->table_id); + + new->nhh = hash_create_size(8, pbr_nh_hash_key, pbr_nh_hash_equal, + "PBR NH Cache Hash"); + return new; +} + + +void pbr_nhgroup_add_cb(const char *name) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct nexthop_group_cmd *nhgc; + + nhgc = nhgc_find(name); + + if (!nhgc) { + DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s", + __func__, name); + return; + } + + pnhgc = pbr_nht_add_group(name); + + if (!pnhgc) + return; + + DEBUGD(&pbr_dbg_nht, "%s: Added nexthop-group %s", __func__, name); + + pbr_map_check_nh_group_change(name); +} + +void pbr_nhgroup_modify_cb(const struct nexthop_group_cmd *nhgc) +{ +} + +void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + char debugstr[256]; + struct pbr_nexthop_group_cache pnhgc_find = {}; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache pnhc_find = {}; + struct pbr_nexthop_cache *pnhc; + + /* find pnhgc by name */ + strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); + pnhgc = hash_lookup(pbr_nhg_hash, &pnhgc_find); + + if (!pnhgc) { + /* Check if configured table range is exhausted */ + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, nhgc->name); + return; + } + + /* No nhgc but range not exhausted? Then alloc it */ + pnhgc = hash_get(pbr_nhg_hash, &pnhgc_find, pbr_nhgc_alloc); + } + + /* create & insert new pnhc into pnhgc->nhh */ + pnhc_find.nexthop = *nhop; + pnhc = hash_get(pnhgc->nhh, &pnhc_find, pbr_nh_alloc); + + /* set parent pnhgc */ + pnhc->parent = pnhgc; + + if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { + nexthop2str(nhop, debugstr, sizeof(debugstr)); + DEBUGD(&pbr_dbg_nht, "%s: Added %s to nexthop-group %s", + __func__, debugstr, nhgc->name); + } + + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); + pbr_map_check_nh_group_change(nhgc->name); + + if (nhop->type == NEXTHOP_TYPE_IFINDEX + || (nhop->type == NEXTHOP_TYPE_IPV6_IFINDEX + && IN6_IS_ADDR_LINKLOCAL(&nhop->gate.ipv6))) { + struct interface *ifp; + + ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + if (ifp) + pbr_nht_nexthop_interface_update(ifp); + } +} + +void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + char debugstr[256]; + struct pbr_nexthop_group_cache pnhgc_find = {}; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache pnhc_find = {}; + struct pbr_nexthop_cache *pnhc; + enum nexthop_types_t nh_type = nhop->type; + + /* find pnhgc by name */ + strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); + pnhgc = hash_lookup(pbr_nhg_hash, &pnhgc_find); + + /* + * Ignore deletions of nhg we did not / could not allocate nhgc for + * Occurs when PBR table range is full but new nhg keep coming in + */ + if (!pnhgc) + return; + + /* delete pnhc from pnhgc->nhh */ + pnhc_find.nexthop = *nhop; + pnhc = hash_release(pnhgc->nhh, &pnhc_find); + + /* delete pnhc */ + pbr_nh_delete(&pnhc); + + if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { + nexthop2str(nhop, debugstr, sizeof(debugstr)); + DEBUGD(&pbr_dbg_nht, "%s: Removed %s from nexthop-group %s", + __func__, debugstr, nhgc->name); + } + + if (pnhgc->nhh->count) + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhgc->nhg, nh_type); + + pbr_map_check_nh_group_change(nhgc->name); +} + +void pbr_nhgroup_delete_cb(const char *name) +{ + DEBUGD(&pbr_dbg_nht, "%s: Removed nexthop-group %s", __func__, name); + + /* delete group from all pbrms's */ + pbr_nht_delete_group(name); + + pbr_map_check_nh_group_change(name); +} + +static void +pbr_nht_find_nhg_from_table_update(struct pbr_nexthop_group_cache *pnhgc, + uint32_t table_id, bool installed) +{ + if (pnhgc->table_id == table_id) { + DEBUGD(&pbr_dbg_nht, "%s: %s: Table ID (%u) matches %s", + __func__, (installed ? "install" : "remove"), table_id, + pnhgc->name); + + pnhgc->installed = installed; + pnhgc->valid = installed; + pbr_map_schedule_policy_from_nhg(pnhgc->name, pnhgc->installed); + } +} + +static void pbr_nht_find_nhg_from_table_install(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)b->data; + uint32_t table_id = *(uint32_t *)data; + + pbr_nht_find_nhg_from_table_update(pnhgc, table_id, true); +} + +void pbr_nht_route_installed_for_table(uint32_t table_id) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_install, + &table_id); +} + +static void pbr_nht_find_nhg_from_table_remove(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)b->data; + uint32_t table_id = *(uint32_t *)data; + + pbr_nht_find_nhg_from_table_update(pnhgc, table_id, false); +} + +void pbr_nht_route_removed_for_table(uint32_t table_id) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_remove, + &table_id); +} + +/* + * Loop through all nexthops in a nexthop group to check that they are all the + * same. If they are not all the same, log this peculiarity. + * + * nhg + * The nexthop group to check + * + * Returns: + * - AFI of last nexthop in the group + * - AFI_MAX on error + */ +static afi_t pbr_nht_which_afi(struct nexthop_group nhg, + enum nexthop_types_t nh_type) +{ + struct nexthop *nexthop; + afi_t install_afi = AFI_MAX; + bool v6, v4, bh; + + if (nh_type) { + switch (nh_type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + return AFI_IP; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + return AFI_IP6; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return AFI_MAX; + } + } + + v6 = v4 = bh = false; + + for (ALL_NEXTHOPS(nhg, nexthop)) { + nh_type = nexthop->type; + + switch (nh_type) { + case NEXTHOP_TYPE_IFINDEX: + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + v6 = true; + install_afi = AFI_IP; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + v4 = true; + install_afi = AFI_IP6; + break; + case NEXTHOP_TYPE_BLACKHOLE: + bh = true; + break; + } + } + + /* Interface and/or blackhole nexthops only. */ + if (!v4 && !v6) + install_afi = AFI_MAX; + + if (!bh && v6 && v4) + DEBUGD(&pbr_dbg_nht, + "%s: Saw both V6 and V4 nexthops...using %s", __func__, + afi2str(install_afi)); + if (bh && (v6 || v4)) + DEBUGD(&pbr_dbg_nht, + "%s: Saw blackhole nexthop(s) with %s%s%s nexthop(s), using AFI_MAX.", + __func__, v4 ? "v4" : "", (v4 && v6) ? " and " : "", + v6 ? "v6" : ""); + + return install_afi; +} + +static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg) +{ + afi_t install_afi; + enum nexthop_types_t nh_type = 0; + + install_afi = pbr_nht_which_afi(nhg, nh_type); + + route_add(pnhgc, nhg, install_afi); +} + +static void +pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, + enum nexthop_types_t nh_type) +{ + afi_t install_afi; + + install_afi = pbr_nht_which_afi(nhg, nh_type); + + pnhgc->installed = false; + pnhgc->valid = false; + route_delete(pnhgc, install_afi); +} + +void pbr_nht_change_group(const char *name) +{ + struct nexthop_group_cmd *nhgc; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct nexthop *nhop; + + nhgc = nhgc_find(name); + if (!nhgc) + return; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) { + DEBUGD(&pbr_dbg_nht, + "%s: Could not find nexthop-group cache w/ name '%s'", + __func__, name); + return; + } + + for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { + struct pbr_nexthop_cache lookup; + struct pbr_nexthop_cache *pnhc; + + lookup.nexthop = *nhop; + pnhc = hash_lookup(pnhgc->nhh, &lookup); + if (!pnhc) { + pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); + pnhc->parent = pnhgc; + } + } + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); +} + +char *pbr_nht_nexthop_make_name(char *name, size_t l, + uint32_t seqno, char *buffer) +{ + snprintf(buffer, l, "%s%u", name, seqno); + return buffer; +} + +/* Set data derived from nhg in pbrms */ +void pbr_nht_set_seq_nhg_data(struct pbr_map_sequence *pbrms, + const struct nexthop_group_cmd *nhgc) +{ + const struct nexthop_group *nhg; + + if (!nhgc) + return; + + nhg = &nhgc->nhg; + if (!nhg->nexthop) + return; + + switch (nhg->nexthop->type) { + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + pbrms->family = AF_INET6; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + pbrms->family = AF_INET; + break; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + break; + } +} + +/* Configure a routemap sequence to use a given nexthop group */ +void pbr_nht_set_seq_nhg(struct pbr_map_sequence *pbrms, const char *name) +{ + struct nexthop_group_cmd *nhgc; + + if (!name) + return; + + pbrms->nhgrp_name = XSTRDUP(MTYPE_TMP, name); + pbrms->forwarding_type = PBR_FT_NEXTHOP_GROUP; + + nhgc = nhgc_find(name); + if (!nhgc) + return; + + pbr_nht_set_seq_nhg_data(pbrms, nhgc); +} + +void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms, + const struct nexthop *nhop) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_cache *pnhc; + struct pbr_nexthop_cache lookup; + struct nexthop *nh; + char buf[PBR_NHC_NAMELEN]; + + pbrms->nhg = nexthop_group_new(); + pbrms->internal_nhg_name = XSTRDUP( + MTYPE_TMP, + pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, + pbrms->seqno, buf)); + pbrms->forwarding_type = PBR_FT_NEXTHOP_SINGLE; + + nh = nexthop_new(); + memcpy(nh, nhop, sizeof(*nh)); + + nexthop_group_add_sorted(pbrms->nhg, nh); + + memset(&find, 0, sizeof(find)); + pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, + pbrms->seqno, find.name); + + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, find.name); + return; + } + + if (!pbrms->internal_nhg_name) + pbrms->internal_nhg_name = XSTRDUP(MTYPE_TMP, find.name); + + pnhgc = hash_get(pbr_nhg_hash, &find, pbr_nhgc_alloc); + + lookup.nexthop = *pbrms->nhg->nexthop; + pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); + pnhc->parent = pnhgc; + if (nhop->vrf_id != VRF_DEFAULT) { + struct vrf *vrf = vrf_lookup_by_id(nhop->vrf_id); + + if (vrf) + strlcpy(pnhc->vrf_name, vrf->name, + sizeof(pnhc->vrf_name)); + } + + if (nhop->ifindex != 0) { + struct interface *ifp = + if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + + if (ifp) + strlcpy(pnhc->intf_name, ifp->name, + sizeof(pnhc->intf_name)); + } + pbr_nht_install_nexthop_group(pnhgc, *pbrms->nhg); +} + +static void pbr_nht_release_individual_nexthop(struct pbr_map_sequence *pbrms) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_cache *pnhc; + struct pbr_nexthop_cache lup; + struct nexthop *nh; + enum nexthop_types_t nh_type = 0; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", pbrms->internal_nhg_name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + nh = pbrms->nhg->nexthop; + nh_type = nh->type; + lup.nexthop = *nh; + pnhc = hash_lookup(pnhgc->nhh, &lup); + pnhc->parent = NULL; + hash_release(pnhgc->nhh, pnhc); + pbr_nh_delete(&pnhc); + pbr_nht_uninstall_nexthop_group(pnhgc, *pbrms->nhg, nh_type); + + hash_release(pbr_nhg_hash, pnhgc); + pbr_nhgc_delete(pnhgc); + + nexthop_group_delete(&pbrms->nhg); + XFREE(MTYPE_TMP, pbrms->internal_nhg_name); +} + +void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms) +{ + struct pbr_map *pbrm = pbrms->parent; + + /* The idea here is to send a delete command to zebra only once, + * and set 'valid' and 'installed' to false only when the last + * rule is being deleted. In other words, the pbr common should be + * updated only when the last rule is being updated or deleted. + */ + if (pbrm->seqnumbers->count == 1) + pbr_map_delete_nexthops(pbrms); + + pbr_nht_release_individual_nexthop(pbrms); +} + +struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name) +{ + struct nexthop *nhop; + struct nexthop_group_cmd *nhgc; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache lookup; + + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, name); + return NULL; + } + + nhgc = nhgc_find(name); + + if (!nhgc) { + DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s", + __func__, name); + return NULL; + } + + snprintf(lookup.name, sizeof(lookup.name), "%s", name); + pnhgc = hash_get(pbr_nhg_hash, &lookup, pbr_nhgc_alloc); + DEBUGD(&pbr_dbg_nht, "%s: Retrieved NHGC @ %p", __func__, pnhgc); + + for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { + struct pbr_nexthop_cache lookupc; + struct pbr_nexthop_cache *pnhc; + + lookupc.nexthop = *nhop; + pnhc = hash_lookup(pnhgc->nhh, &lookupc); + if (!pnhc) { + pnhc = hash_get(pnhgc->nhh, &lookupc, pbr_nh_alloc); + pnhc->parent = pnhgc; + } + } + + return pnhgc; +} + +void pbr_nht_delete_group(const char *name) +{ + struct pbr_map_sequence *pbrms; + struct listnode *snode; + struct pbr_map *pbrm; + struct pbr_nexthop_group_cache pnhgc_find; + struct pbr_nexthop_group_cache *pnhgc; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, pbrms)) { + if (pbrms->nhgrp_name + && strmatch(pbrms->nhgrp_name, name)) { + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + pbrms->nhg = NULL; + pbrms->internal_nhg_name = NULL; + pbrm->valid = false; + } + } + } + + strlcpy(pnhgc_find.name, name, sizeof(pnhgc_find.name)); + pnhgc = hash_release(pbr_nhg_hash, &pnhgc_find); + + /* + * Ignore deletions of nh we did not / could not allocate nhgc for + * Occurs when PBR table range is full but new nhg keep coming in + */ + if (!pnhgc) + return; + + /* Remove and recalculate the next table id */ + hash_release(pbr_nhg_allocated_id_hash, pnhgc); + pbr_nht_update_next_unallocated_table_id(); + + pbr_nhgc_delete(pnhgc); +} + +bool pbr_nht_nexthop_valid(struct nexthop_group *nhg) +{ + DEBUGD(&pbr_dbg_nht, "%s: %p", __func__, nhg); + return true; +} + +bool pbr_nht_nexthop_group_valid(const char *name) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache lookup; + + DEBUGD(&pbr_dbg_nht, "%s: %s", __func__, name); + + snprintf(lookup.name, sizeof(lookup.name), "%s", name); + pnhgc = hash_get(pbr_nhg_hash, &lookup, NULL); + if (!pnhgc) + return false; + DEBUGD(&pbr_dbg_nht, "%s: %d %d", __func__, pnhgc->valid, + pnhgc->installed); + if (pnhgc->valid && pnhgc->installed) + return true; + + return false; +} + +struct pbr_nht_individual { + struct zapi_route *nhr; + struct interface *ifp; + struct pbr_vrf *pbr_vrf; + struct pbr_nexthop_cache *pnhc; + vrf_id_t old_vrf_id; + + bool valid; + + bool nhr_matched; +}; + +static bool +pbr_nht_individual_nexthop_gw_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + bool is_valid = pnhc->valid; + + /* + * If we have an interface down event, let's note that + * it is happening and find all the nexthops that depend + * on that interface. As that if we have an interface + * flapping fast enough it means that zebra might turn + * those nexthop tracking events into a no-update + * So let's search and do the right thing on the + * interface event. + */ + if (!pnhi->nhr) { + switch (pnhc->nexthop.type) { + case NEXTHOP_TYPE_BLACKHOLE: + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + goto done; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (pnhc->nexthop.ifindex == pnhi->ifp->ifindex) + is_valid = if_is_up(pnhi->ifp); + goto done; + } + + goto done; + } + + switch (pnhi->nhr->prefix.family) { + case AF_INET: + if (pnhc->nexthop.gate.ipv4.s_addr + != pnhi->nhr->prefix.u.prefix4.s_addr) + goto done; /* Unrelated change */ + break; + case AF_INET6: + if (memcmp(&pnhc->nexthop.gate.ipv6, + &pnhi->nhr->prefix.u.prefix6, 16) + != 0) + goto done; /* Unrelated change */ + break; + } + + pnhi->nhr_matched = true; + if (!pnhi->nhr->nexthop_num) { + is_valid = false; + goto done; + } + + if (pnhc->nexthop.type == NEXTHOP_TYPE_IPV4_IFINDEX + || pnhc->nexthop.type == NEXTHOP_TYPE_IPV6_IFINDEX) { + + /* GATEWAY_IFINDEX type shouldn't resolve to group */ + if (pnhi->nhr->nexthop_num > 1) { + is_valid = false; + goto done; + } + + /* If whatever we resolved to wasn't on the interface we + * specified. (i.e. not a connected route), its invalid. + */ + if (pnhi->nhr->nexthops[0].ifindex != pnhc->nexthop.ifindex) { + is_valid = false; + goto done; + } + } + + is_valid = true; + +done: + pnhc->valid = is_valid; + + return pnhc->valid; +} + +static bool +pbr_nht_individual_nexthop_interface_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + bool is_valid = pnhc->valid; + + if (!pnhi->ifp) /* It doesn't care about non-interface updates */ + goto done; + + if (pnhc->nexthop.ifindex + != pnhi->ifp->ifindex) /* Un-related interface */ + goto done; + + pnhi->nhr_matched = true; + is_valid = !!if_is_up(pnhi->ifp); + +done: + pnhc->valid = is_valid; + + return pnhc->valid; +} + +/* Given this update either from interface or nexthop tracking, re-validate this + * nexthop. + * + * If the update is un-related, the subroutines shoud just return their cached + * valid state. + */ +static void pbr_nht_individual_nexthop_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + assert(pnhi->nhr || pnhi->ifp); /* Either nexthop or interface update */ + + switch (pnhc->nexthop.type) { + case NEXTHOP_TYPE_IFINDEX: + pbr_nht_individual_nexthop_interface_update(pnhc, pnhi); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (IN6_IS_ADDR_LINKLOCAL(&pnhc->nexthop.gate.ipv6)) { + pbr_nht_individual_nexthop_interface_update(pnhc, pnhi); + break; + } + fallthrough; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + pbr_nht_individual_nexthop_gw_update(pnhc, pnhi); + break; + case NEXTHOP_TYPE_BLACKHOLE: + pnhc->valid = true; + break; + } +} + +static void pbr_nht_individual_nexthop_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + bool old_valid; + + old_valid = pnhc->valid; + + pbr_nht_individual_nexthop_update(pnhc, pnhi); + + DEBUGD(&pbr_dbg_nht, " Found %pFX: old: %d new: %d", + &pnhi->nhr->prefix, old_valid, pnhc->valid); + + if (pnhc->valid) + pnhi->valid = true; +} + +static void pbr_nexthop_group_cache_iterate_to_group(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct nexthop_group *nhg = data; + struct nexthop *nh = NULL; + + copy_nexthops(&nh, &pnhc->nexthop, NULL); + + _nexthop_add(&nhg->nexthop, nh); +} + +static void +pbr_nexthop_group_cache_to_nexthop_group(struct nexthop_group *nhg, + struct pbr_nexthop_group_cache *pnhgc) +{ + hash_iterate(pnhgc->nhh, pbr_nexthop_group_cache_iterate_to_group, nhg); +} + +static void pbr_nht_nexthop_update_lookup(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_individual pnhi = {}; + struct nexthop_group nhg = {}; + bool old_valid; + + old_valid = pnhgc->valid; + + pnhi.nhr = (struct zapi_route *)data; + pnhi.valid = false; + pnhi.nhr_matched = false; + hash_iterate(pnhgc->nhh, pbr_nht_individual_nexthop_update_lookup, + &pnhi); + + if (!pnhi.nhr_matched) + return; + + /* + * If any of the specified nexthops are valid we are valid + */ + pnhgc->valid = !!pnhi.valid; + + pbr_nexthop_group_cache_to_nexthop_group(&nhg, pnhgc); + + if (pnhgc->valid) + pbr_nht_install_nexthop_group(pnhgc, nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhg, 0); + + /* Don't need copied nexthops anymore */ + nexthops_free(nhg.nexthop); + + if (old_valid != pnhgc->valid) + pbr_map_check_nh_group_change(pnhgc->name); +} + +void pbr_nht_nexthop_update(struct zapi_route *nhr) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_update_lookup, nhr); +} + +struct nhrc_vrf_info { + struct pbr_vrf *pbr_vrf; + uint32_t old_vrf_id; + struct nhrc *nhrc; +}; + +static int pbr_nht_nhrc_vrf_change(struct hash_bucket *b, void *data) +{ + struct nhrc *nhrc = b->data; + struct nhrc_vrf_info *nhrcvi = data; + + if (nhrc->nexthop.vrf_id == nhrcvi->old_vrf_id) { + nhrcvi->nhrc = nhrc; + return HASHWALK_ABORT; + } + + return HASHWALK_CONTINUE; +} + +static int pbr_nht_individual_nexthop_vrf_handle(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + + if (pnhc->looked_at == true) + return HASHWALK_CONTINUE; + + if (pnhc->nexthop.vrf_id == VRF_DEFAULT) + return HASHWALK_CONTINUE; + + if (strncmp(pnhc->vrf_name, pbr_vrf_name(pnhi->pbr_vrf), + sizeof(pnhc->vrf_name)) + == 0) { + pnhi->pnhc = pnhc; + + if (pnhc->nexthop.vrf_id != pbr_vrf_id(pnhi->pbr_vrf)) { + struct nhrc_vrf_info nhrcvi; + + memset(&nhrcvi, 0, sizeof(nhrcvi)); + nhrcvi.pbr_vrf = pnhi->pbr_vrf; + nhrcvi.old_vrf_id = pnhc->nexthop.vrf_id; + + pnhi->nhr_matched = true; + pnhi->old_vrf_id = pnhc->nexthop.vrf_id; + + do { + nhrcvi.nhrc = NULL; + hash_walk(pbr_nhrc_hash, + pbr_nht_nhrc_vrf_change, &nhrcvi); + if (nhrcvi.nhrc) { + hash_release(pbr_nhrc_hash, + nhrcvi.nhrc); + nhrcvi.nhrc->nexthop.vrf_id = + pbr_vrf_id(pnhi->pbr_vrf); + (void)hash_get(pbr_nhrc_hash, + nhrcvi.nhrc, + hash_alloc_intern); + pbr_send_rnh(&nhrcvi.nhrc->nexthop, true); + } + } while (nhrcvi.nhrc); + } + + pnhc->looked_at = true; + return HASHWALK_ABORT; + } + + return HASHWALK_CONTINUE; +} + +static void pbr_nht_clear_looked_at(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + + pnhc->looked_at = false; +} + +static void pbr_nht_nexthop_vrf_handle(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_vrf *pbr_vrf = data; + struct pbr_nht_individual pnhi = {}; + + hash_iterate(pnhgc->nhh, pbr_nht_clear_looked_at, NULL); + memset(&pnhi, 0, sizeof(pnhi)); + pnhi.pbr_vrf = pbr_vrf; + do { + struct pbr_nexthop_cache *pnhc; + + pnhi.pnhc = NULL; + hash_walk(pnhgc->nhh, pbr_nht_individual_nexthop_vrf_handle, + &pnhi); + + if (!pnhi.pnhc) + continue; + + pnhc = pnhi.pnhc; + pnhc->nexthop.vrf_id = pnhi.old_vrf_id; + pnhi.pnhc = hash_release(pnhgc->nhh, pnhi.pnhc); + if (pnhi.pnhc) { + pnhi.pnhc->nexthop.vrf_id = pbr_vrf_id(pbr_vrf); + + (void)hash_get(pnhgc->nhh, pnhi.pnhc, + hash_alloc_intern); + } else + pnhc->nexthop.vrf_id = pbr_vrf_id(pbr_vrf); + + pbr_map_check_vrf_nh_group_change(pnhgc->name, pbr_vrf, + pnhi.old_vrf_id); + } while (pnhi.pnhc); +} + +void pbr_nht_vrf_update(struct pbr_vrf *pbr_vrf) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_vrf_handle, pbr_vrf); +} + +static void pbr_nht_individual_nexthop_interface_handle(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + + if (pnhc->nexthop.ifindex == 0) + return; + + if ((strncmp(pnhc->intf_name, pnhi->ifp->name, sizeof(pnhc->intf_name)) + == 0) + && pnhc->nexthop.ifindex != pnhi->ifp->ifindex) + pnhi->pnhc = pnhc; +} + +static void pbr_nht_nexthop_interface_handle(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct interface *ifp = data; + struct pbr_nht_individual pnhi = {}; + struct nhrc *nhrc; + uint32_t old_ifindex; + + do { + memset(&pnhi, 0, sizeof(pnhi)); + pnhi.ifp = ifp; + hash_iterate(pnhgc->nhh, + pbr_nht_individual_nexthop_interface_handle, + &pnhi); + + if (!pnhi.pnhc) + continue; + + pnhi.pnhc = hash_release(pnhgc->nhh, pnhi.pnhc); + old_ifindex = pnhi.pnhc->nexthop.ifindex; + + nhrc = hash_lookup(pbr_nhrc_hash, &pnhi.pnhc->nexthop); + if (nhrc) { + hash_release(pbr_nhrc_hash, nhrc); + nhrc->nexthop.ifindex = ifp->ifindex; + (void)hash_get(pbr_nhrc_hash, nhrc, hash_alloc_intern); + } + pnhi.pnhc->nexthop.ifindex = ifp->ifindex; + + (void)hash_get(pnhgc->nhh, pnhi.pnhc, hash_alloc_intern); + + pbr_map_check_interface_nh_group_change(pnhgc->name, ifp, + old_ifindex); + } while (pnhi.pnhc); +} + +void pbr_nht_interface_update(struct interface *ifp) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_interface_handle, ifp); +} + +static void +pbr_nht_individual_nexthop_interface_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + bool old_valid; + + old_valid = pnhc->valid; + + pbr_nht_individual_nexthop_update(pnhc, pnhi); + + DEBUGD(&pbr_dbg_nht, " Found %s: old: %d new: %d", pnhi->ifp->name, + old_valid, pnhc->valid); + + if (pnhc->valid) + pnhi->valid = true; +} + +static void pbr_nht_nexthop_interface_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_individual pnhi = {}; + struct nexthop_group nhg = {}; + bool old_valid; + + old_valid = pnhgc->valid; + + pnhi.ifp = data; + pnhi.valid = false; + hash_iterate(pnhgc->nhh, + pbr_nht_individual_nexthop_interface_update_lookup, &pnhi); + + /* + * If any of the specified nexthops are valid we are valid + */ + pnhgc->valid = pnhi.valid; + + pbr_nexthop_group_cache_to_nexthop_group(&nhg, pnhgc); + + if (pnhgc->valid) + pbr_nht_install_nexthop_group(pnhgc, nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhg, 0); + + nexthops_free(nhg.nexthop); + + if (old_valid != pnhgc->valid) + pbr_map_check_nh_group_change(pnhgc->name); +} + +void pbr_nht_nexthop_interface_update(struct interface *ifp) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_interface_update_lookup, + ifp); +} + +static bool pbr_nhg_allocated_id_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_group_cache *left, *right; + + left = (const struct pbr_nexthop_group_cache *)arg1; + right = (const struct pbr_nexthop_group_cache *)arg2; + + return left->table_id == right->table_id; +} + +static uint32_t pbr_nhg_allocated_id_hash_key(const void *arg) +{ + const struct pbr_nexthop_group_cache *nhgc = arg; + + /* table_id makes elements in this hash unique */ + return nhgc->table_id; +} + +static uint32_t pbr_nhg_hash_key(const void *arg) +{ + const struct pbr_nexthop_group_cache *nhgc = arg; + + return jhash(&nhgc->name, strlen(nhgc->name), 0x52c34a96); +} + +static bool pbr_nhg_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_group_cache *nhgc1 = + (const struct pbr_nexthop_group_cache *)arg1; + const struct pbr_nexthop_group_cache *nhgc2 = + (const struct pbr_nexthop_group_cache *)arg2; + + return !strcmp(nhgc1->name, nhgc2->name); +} + +uint32_t pbr_nht_find_next_unallocated_table_id(void) +{ + struct pbr_nexthop_group_cache iter; + + /* + * Find the smallest unallocated table id + * This can be non-trivial considering nhg removals / shifting upper & + * lower bounds, so start at the lowest in the range and continue until + * an unallocated space is found + */ + for (iter.table_id = pbr_nhg_low_table; + iter.table_id < pbr_nhg_high_table; ++iter.table_id) + if (!hash_lookup(pbr_nhg_allocated_id_hash, &iter)) + return iter.table_id; + + /* Configured range is full, cannot install anywhere */ + return 0; +} + +bool pbr_nht_has_unallocated_table(void) +{ + return !!pbr_next_unallocated_table_id; +} + +void pbr_nht_update_next_unallocated_table_id(void) +{ + pbr_next_unallocated_table_id = + pbr_nht_find_next_unallocated_table_id(); +} + +uint32_t pbr_nht_reserve_next_table_id(struct pbr_nexthop_group_cache *nhgc) +{ + /* Nothing to reserve if all tables in range already used */ + if (!pbr_next_unallocated_table_id) + return 0; + + /* Reserve this table id */ + nhgc->table_id = pbr_next_unallocated_table_id; + + /* Mark table id as allocated in id-indexed hash */ + (void)hash_get(pbr_nhg_allocated_id_hash, nhgc, hash_alloc_intern); + + /* Pre-compute the next unallocated table id */ + pbr_nht_update_next_unallocated_table_id(); + + /* Present caller with reserved table id */ + return nhgc->table_id; +} + +void pbr_nht_set_tableid_range(uint32_t low, uint32_t high) +{ + pbr_nhg_low_table = low; + pbr_nhg_high_table = high; + + /* Re-compute next unallocated id within new range */ + pbr_nht_update_next_unallocated_table_id(); +} + +void pbr_nht_write_table_range(struct vty *vty) +{ + if (pbr_nhg_low_table != PBR_NHT_DEFAULT_LOW_TABLEID + || pbr_nhg_high_table != PBR_NHT_DEFAULT_HIGH_TABLEID) { + vty_out(vty, "pbr table range %u %u\n", pbr_nhg_low_table, + pbr_nhg_high_table); + } +} + +uint32_t pbr_nht_get_next_rule(uint32_t seqno) +{ + return seqno + pbr_nhg_low_rule - 1; +} +void pbr_nht_set_rule_range(uint32_t low, uint32_t high) +{ + pbr_nhg_low_rule = low; + pbr_nhg_high_rule = high; +} + +void pbr_nht_write_rule_range(struct vty *vty) +{ + if (pbr_nhg_low_rule != PBR_NHT_DEFAULT_LOW_RULE + || pbr_nhg_high_rule != PBR_NHT_DEFAULT_HIGH_RULE) { + vty_out(vty, "pbr rule range %u %u\n", pbr_nhg_low_rule, + pbr_nhg_high_rule); + } +} + +uint32_t pbr_nht_get_table(const char *name) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) { + DEBUGD(&pbr_dbg_nht, + "%s: Could not find nexthop-group cache w/ name '%s'", + __func__, name); + return 5000; + } + + return pnhgc->table_id; +} + +bool pbr_nht_get_installed(const char *name) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) + return false; + + return pnhgc->installed; +} + +static void pbr_nht_show_nhg_nexthops(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct vty *vty = data; + + vty_out(vty, "\tValid: %d ", pnhc->valid); + nexthop_group_write_nexthop(vty, &pnhc->nexthop); +} + +static void pbr_nht_json_nhg_nexthops(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + json_object *all_hops = data; + json_object *this_hop; + + this_hop = json_object_new_object(); + nexthop_group_json_nexthop(this_hop, &pnhc->nexthop); + json_object_boolean_add(this_hop, "valid", pnhc->valid); + + json_object_array_add(all_hops, this_hop); +} + +struct pbr_nht_show { + struct vty *vty; + json_object *json; + const char *name; +}; + +static void pbr_nht_show_nhg(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_show *pns = data; + struct vty *vty; + + if (pns->name && strcmp(pns->name, pnhgc->name) != 0) + return; + + vty = pns->vty; + vty_out(vty, "Nexthop-Group: %s Table: %u Valid: %d Installed: %d\n", + pnhgc->name, pnhgc->table_id, pnhgc->valid, pnhgc->installed); + + hash_iterate(pnhgc->nhh, pbr_nht_show_nhg_nexthops, vty); +} + +static void pbr_nht_json_nhg(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_show *pns = data; + json_object *j, *this_group, *group_hops; + + if (pns->name && strcmp(pns->name, pnhgc->name) != 0) + return; + + j = pns->json; + this_group = json_object_new_object(); + + if (!j || !this_group) + return; + + json_object_int_add(this_group, "id", pnhgc->table_id); + json_object_string_add(this_group, "name", pnhgc->name); + json_object_boolean_add(this_group, "valid", pnhgc->valid); + json_object_boolean_add(this_group, "installed", pnhgc->installed); + + group_hops = json_object_new_array(); + + if (group_hops) { + hash_iterate(pnhgc->nhh, pbr_nht_json_nhg_nexthops, group_hops); + json_object_object_add(this_group, "nexthops", group_hops); + } + + json_object_array_add(j, this_group); +} + +void pbr_nht_show_nexthop_group(struct vty *vty, const char *name) +{ + struct pbr_nht_show pns; + + pns.vty = vty; + pns.name = name; + + hash_iterate(pbr_nhg_hash, pbr_nht_show_nhg, &pns); +} + +void pbr_nht_json_nexthop_group(json_object *j, const char *name) +{ + struct pbr_nht_show pns; + + pns.name = name; + pns.json = j; + + hash_iterate(pbr_nhg_hash, pbr_nht_json_nhg, &pns); +} + +void pbr_nht_init(void) +{ + pbr_nhg_hash = hash_create_size( + 16, pbr_nhg_hash_key, pbr_nhg_hash_equal, "PBR NHG Cache Hash"); + pbr_nhrc_hash = + hash_create_size(16, (unsigned int (*)(const void *))nexthop_hash, + pbr_nhrc_hash_equal, "PBR NH Hash"); + pbr_nhg_allocated_id_hash = hash_create_size( + 16, pbr_nhg_allocated_id_hash_key, + pbr_nhg_allocated_id_hash_equal, "PBR Allocated Table Hash"); + + pbr_nhg_low_table = PBR_NHT_DEFAULT_LOW_TABLEID; + pbr_nhg_high_table = PBR_NHT_DEFAULT_HIGH_TABLEID; + pbr_nhg_low_rule = PBR_NHT_DEFAULT_LOW_RULE; + pbr_nhg_high_rule = PBR_NHT_DEFAULT_HIGH_RULE; + + /* First unallocated table is lowest in range on init */ + pbr_next_unallocated_table_id = PBR_NHT_DEFAULT_LOW_TABLEID; +} diff --git a/pbrd/pbr_nht.h b/pbrd/pbr_nht.h new file mode 100644 index 0000000..a702a57 --- /dev/null +++ b/pbrd/pbr_nht.h @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR-nht Header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PBR_NHT_H__ +#define __PBR_NHT_H__ + +#include +#include + +#include "pbr_map.h" +#include "json.h" + +#define PBR_NHC_NAMELEN PBR_MAP_NAMELEN + 10 + +extern struct hash *pbr_nhg_hash; + +struct pbr_nexthop_group_cache { + char name[PBR_NHC_NAMELEN]; + + uint32_t table_id; + + struct hash *nhh; + + /* + * If all nexthops are considered valid + */ + bool valid; + + bool installed; +}; + +struct pbr_nexthop_cache { + struct pbr_nexthop_group_cache *parent; + + char vrf_name[VRF_NAMSIZ + 1]; + char intf_name[IFNAMSIZ + 1]; + + struct nexthop nexthop; + + bool looked_at; + bool valid; + bool nhr_matched; +}; + +extern void pbr_nht_write_table_range(struct vty *vty); +#define PBR_NHT_DEFAULT_LOW_TABLEID 10000 +#define PBR_NHT_DEFAULT_HIGH_TABLEID 11000 +extern void pbr_nht_set_tableid_range(uint32_t low, uint32_t high); + +/* + * Find and reserve the next available table for installation; + * Sequential calls to this function will reserve sequential table numbers + * until the configured range is exhausted; calls made after exhaustion always + * return 0 + */ +extern uint32_t +pbr_nht_reserve_next_table_id(struct pbr_nexthop_group_cache *nhgc); +/* + * Get the next tableid to use for installation to kernel + */ +extern uint32_t pbr_nht_find_next_unallocated_table_id(void); +/* + * Calculate where the next table representing a nhg will go in kernel + */ +extern void pbr_nht_update_next_unallocated_table_id(void); +/* + * Indicate if there are free spots to install a table to kernel within the + * configured PBR table range + */ +extern bool pbr_nht_has_unallocated_table(void); +/* + * Get the next rule number to use for installation + */ +extern void pbr_nht_write_rule_range(struct vty *vty); + +#define PBR_NHT_DEFAULT_LOW_RULE 300 +#define PBR_NHT_DEFAULT_HIGH_RULE 1300 +extern void pbr_nht_set_rule_range(uint32_t low, uint32_t high); + +extern uint32_t pbr_nht_get_next_rule(uint32_t seqno); + +extern void pbr_nhgroup_add_cb(const char *name); +extern void pbr_nhgroup_modify_cb(const struct nexthop_group_cmd *nhgc); +extern void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); +extern void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); +extern void pbr_nhgroup_delete_cb(const char *name); + +extern bool pbr_nht_nexthop_valid(struct nexthop_group *nhg); +extern bool pbr_nht_nexthop_group_valid(const char *name); + +extern struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name); +extern void pbr_nht_change_group(const char *name); +extern void pbr_nht_delete_group(const char *name); + +extern void pbr_nht_set_seq_nhg_data(struct pbr_map_sequence *pbrms, + const struct nexthop_group_cmd *nhgc); +extern void pbr_nht_set_seq_nhg(struct pbr_map_sequence *pbrms, + const char *name); + +extern void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms, + const struct nexthop *nhop); +extern void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms); +/* + * Given the tableid of the installed default + * route, find the nexthop-group associated with + * it, then find all pbr-maps that use it and + * install/delete them as well. + */ +extern void pbr_nht_route_installed_for_table(uint32_t table_id); +extern void pbr_nht_route_removed_for_table(uint32_t table_id); + +/* + * Given the nexthop group name, lookup the associated + * tableid with it + */ +extern uint32_t pbr_nht_get_table(const char *name); + +extern bool pbr_nht_get_installed(const char *name); + +extern char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno, + char *buffer); + +extern void pbr_nht_show_nexthop_group(struct vty *vty, const char *name); +extern void pbr_nht_json_nexthop_group(json_object *j, const char *name); + +/* + * When we get a callback from zebra about a nexthop changing + */ +extern void pbr_nht_nexthop_update(struct zapi_route *nhr); + +/* + * When we get a callback from zebra about an interface status update. + */ +extern void pbr_nht_nexthop_interface_update(struct interface *ifp); + +extern void pbr_nht_init(void); + +extern void pbr_nht_vrf_update(struct pbr_vrf *pbr_vrf); +extern void pbr_nht_interface_update(struct interface *ifp); +#endif diff --git a/pbrd/pbr_vrf.c b/pbrd/pbr_vrf.c new file mode 100644 index 0000000..ef4a4c2 --- /dev/null +++ b/pbrd/pbr_vrf.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR - vrf code + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ +#include + +#include "vrf.h" + +#include "pbr_vrf.h" +#include "pbr_memory.h" +#include "pbr_map.h" +#include "pbr_debug.h" +#include "pbr_nht.h" +#include "pbr_zebra.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_VRF, "PBR Map VRF"); + +static struct pbr_vrf *pbr_vrf_alloc(void) +{ + struct pbr_vrf *pbr_vrf; + + pbr_vrf = XCALLOC(MTYPE_PBR_MAP_VRF, sizeof(struct pbr_vrf)); + + return pbr_vrf; +} + +static void pbr_vrf_free(struct pbr_vrf *pbr_vrf) +{ + XFREE(MTYPE_PBR_MAP_VRF, pbr_vrf); +} + +static int pbr_vrf_new(struct vrf *vrf) +{ + struct pbr_vrf *pbr_vrf; + + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_vrf = pbr_vrf_alloc(); + vrf->info = pbr_vrf; + pbr_vrf->vrf = vrf; + + return 0; +} + +static int pbr_vrf_enable(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_nht_vrf_update(vrf->info); + pbr_map_vrf_update(vrf->info); + + return 0; +} + +static int pbr_vrf_disable(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_map_vrf_update(vrf->info); + + return 0; +} + +static int pbr_vrf_delete(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + /* + * Make sure vrf is always marked disabled first so we handle + * pbr rules using it. + */ + assert(!vrf_is_enabled(vrf)); + + pbr_vrf_free(vrf->info); + vrf->info = NULL; + + return 0; +} + +struct pbr_vrf *pbr_vrf_lookup_by_name(const char *name) +{ + struct vrf *vrf; + + if (!name) + name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(name); + if (vrf) + return ((struct pbr_vrf *)vrf->info); + + return NULL; +} + +bool pbr_vrf_is_enabled(const struct pbr_vrf *pbr_vrf) +{ + return vrf_is_enabled(pbr_vrf->vrf) ? true : false; +} + +bool pbr_vrf_is_valid(const struct pbr_vrf *pbr_vrf) +{ + if (vrf_is_backend_netns()) + return false; + + if (!pbr_vrf->vrf) + return false; + + return pbr_vrf_is_enabled(pbr_vrf); +} + +void pbr_vrf_init(void) +{ + vrf_init(pbr_vrf_new, pbr_vrf_enable, pbr_vrf_disable, pbr_vrf_delete); +} + +void pbr_vrf_terminate(void) +{ + struct vrf *vrf; + struct interface *ifp; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) + pbr_if_del(ifp); + } +} diff --git a/pbrd/pbr_vrf.h b/pbrd/pbr_vrf.h new file mode 100644 index 0000000..c8c7c57 --- /dev/null +++ b/pbrd/pbr_vrf.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VRF library for PBR + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + */ +#ifndef __PBR_VRF_H__ +#define __PBR_VRF_H__ + +struct pbr_vrf { + struct vrf *vrf; +}; + +static inline const char *pbr_vrf_name(const struct pbr_vrf *pbr_vrf) +{ + return pbr_vrf->vrf->name; +} + +static inline vrf_id_t pbr_vrf_id(const struct pbr_vrf *pbr_vrf) +{ + return pbr_vrf->vrf->vrf_id; +} + +extern struct pbr_vrf *pbr_vrf_lookup_by_name(const char *name); +extern bool pbr_vrf_is_valid(const struct pbr_vrf *pbr_vrf); +extern bool pbr_vrf_is_enabled(const struct pbr_vrf *pbr_vrf); + +extern void pbr_vrf_init(void); +extern void pbr_vrf_terminate(void); +#endif diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c new file mode 100644 index 0000000..64d8884 --- /dev/null +++ b/pbrd/pbr_vty.c @@ -0,0 +1,2243 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PBR - vty code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + * Portions: + * Copyright (c) 2021 The MITRE Corporation. + * Copyright (c) 2023 LabN Consulting, L.L.C. + */ +#include + +#include "vty.h" +#include "command.h" +#include "prefix.h" +#include "vrf.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "nexthop_group_private.h" +#include "log.h" +#include "json.h" +#include "debug.h" +#include "pbr.h" + +#include "pbrd/pbr_nht.h" +#include "pbrd/pbr_map.h" +#include "pbrd/pbr_zebra.h" +#include "pbrd/pbr_vty.h" +#include "pbrd/pbr_debug.h" +#include "pbrd/pbr_vty_clippy.c" + +/* clang-format off */ +DEFPY (pbr_set_table_range, + pbr_set_table_range_cmd, + "pbr table range (10000-4294966272)$lb (10000-4294966272)$ub", + PBR_STR + "Set table ID range\n" + "Set table ID range\n" + "Lower bound for table ID range\n" + "Upper bound for table ID range\n") +{ + /* clang-format on */ + /* upper bound is 2^32 - 2^10 */ + int ret = CMD_WARNING; + const int minrange = 1000; + + /* validate given bounds */ + if (lb > ub) + vty_out(vty, "%% Lower bound must be less than upper bound\n"); + else if (ub - lb < minrange) + vty_out(vty, "%% Range breadth must be at least %d\n", minrange); + else { + ret = CMD_SUCCESS; + pbr_nht_set_tableid_range((uint32_t)lb, (uint32_t)ub); + } + + return ret; +} + +/* clang-format off */ +DEFPY (no_pbr_set_table_range, + no_pbr_set_table_range_cmd, + "no pbr table range [(10000-4294966272)$lb (10000-4294966272)$ub]", + NO_STR + PBR_STR + "Set table ID range\n" + "Set table ID range\n" + "Lower bound for table ID range\n" + "Upper bound for table ID range\n") +{ + /* clang-format on */ + pbr_nht_set_tableid_range(PBR_NHT_DEFAULT_LOW_TABLEID, + PBR_NHT_DEFAULT_HIGH_TABLEID); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN_NOSH(pbr_map, + pbr_map_cmd, + "pbr-map PBRMAP seq (1-700)", + "Create pbr-map or enter pbr-map command mode\n" + "The name of the PBR MAP\n" + "Sequence to insert in existing pbr-map entry\n" + "Sequence number\n") +{ + /* clang-format on */ + const char *pbrm_name = argv[1]->arg; + uint32_t seqno = atoi(argv[3]->arg); + struct pbr_map_sequence *pbrms; + + pbrms = pbrms_get(pbrm_name, seqno); + VTY_PUSH_CONTEXT(PBRMAP_NODE, pbrms); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFUN_NOSH(no_pbr_map, + no_pbr_map_cmd, + "no pbr-map PBRMAP [seq (1-700)]", + NO_STR + "Delete pbr-map\n" + "The name of the PBR MAP\n" + "Sequence to delete from existing pbr-map entry\n" + "Sequence number\n") +{ + /* clang-format on */ + const char *pbrm_name = argv[2]->arg; + uint32_t seqno = 0; + struct pbr_map *pbrm = pbrm_find(pbrm_name); + struct pbr_map_sequence *pbrms; + struct listnode *node, *next_node; + + if (argc > 3) + seqno = atoi(argv[4]->arg); + + if (!pbrm) { + vty_out(vty, "pbr-map %s not found\n", pbrm_name); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS(pbrm->seqnumbers, node, next_node, pbrms)) { + if (seqno && pbrms->seqno != seqno) + continue; + + pbr_map_delete(pbrms); + pbr_map_sequence_delete(pbrms); + } + + return CMD_SUCCESS; +} + +/*********************************************************************** + * pbrms/rule Match L3 Fields + ***********************************************************************/ + +/* + * Address Family Matters + * + * Linux Kernel constraints + * ------------------------ + * The underlying linux kernel dataplane requires that rules be + * installed into an IPv4-specific or an IPv6-specific database. + * + * Not only do we need to designate an address-family for rule + * installation, but we ALSO must have the same address-family + * available to be able to delete the rule from the correct kernel + * database. + * + * Determining the address-family + * ------------------------------ + * In the current code, we do our best to infer the correct family + * from any configured IP-address match or set clauses in a rule. + * Absent any of those fields, the NHT code also tries to glean the + * address family from resolved nexthops or nexthop-groups. All of + * those opportunistic address-family determinations are stored in + * the "family" field of struct pbr_map_sequence. + * + * This "family" field value is needed particularly when deleting + * a rule piece-by-piece because at the end, the match/set fields + * will be empty. Maybe it would be possible to handle this issue + * as an internal zebra matter in the future. + * + * We also attempt to maintain address-family consistency among the + * various configured fields in a rule. So far, these fields are + * src/dst IP-address match/set values. + * + * It is probably possible to perform the same address-family check in + * the CLI for single nexthops (set nexthop A.B.C.D|X:X::X:X) but the + * address-family is not immediately available for nexthop-groups. + * In both the single-nexthop and nexthop-group, the NHT resolution code + * sets the "family" field of struct pbr_map_sequence asynchronously. + * + * There isn't currently any flagging of rules that have a consistent + * set of src/dst IP-address match/set values but an asynchronously-resolved + * nexthop-group that has a different address-family. + * + * The match/set IP-address handlers below blindly set "family"; it's + * probably possible to wrongly set "family" to, e.g., IPv4 this way after + * a v6 NHG has been resolved and break rule removal. It's not clear + * how to best address this potential issue. + */ +static bool pbr_family_consistent(struct pbr_map_sequence *pbrms, + uint8_t family, uint32_t skip_filter_bm, + uint32_t skip_action_bm, const char **msg) +{ + uint32_t filter_bm = pbrms->filter_bm & ~skip_filter_bm; + uint32_t action_bm = pbrms->action_bm & ~skip_action_bm; + + if (CHECK_FLAG(filter_bm, PBR_FILTER_SRC_IP) && + (family != pbrms->src->family)) { + if (msg) + *msg = "match src-ip"; + return false; + } + if (CHECK_FLAG(filter_bm, PBR_FILTER_DST_IP) && + (family != pbrms->dst->family)) { + if (msg) + *msg = "match dst-ip"; + return false; + } + if (CHECK_FLAG(action_bm, PBR_ACTION_SRC_IP) && + (family != sockunion_family(&pbrms->action_src))) { + if (msg) + *msg = "set src-ip"; + return false; + } + if (CHECK_FLAG(filter_bm, PBR_ACTION_DST_IP) && + (family != sockunion_family(&pbrms->action_dst))) { + if (msg) + *msg = "set dst-ip"; + return false; + } + return true; +} + + +/* clang-format off */ +DEFPY (pbr_map_match_src, + pbr_map_match_src_cmd, + "[no] match src-ip ![$prefix]", + NO_STR + "Match the rest of the command\n" + "Choose the src ipv4 or ipv6 prefix to use\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + const char *fmsg = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) + return CMD_SUCCESS; + prefix_free(&pbrms->src); + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP); + goto check; + } + + assert(prefix); + if (!pbr_family_consistent(pbrms, prefix->family, PBR_FILTER_SRC_IP, 0, + &fmsg)) { + vty_out(vty, "Address family mismatch (%s)\n", fmsg); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = prefix->family; + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) { + if (prefix_same(pbrms->src, prefix)) + return CMD_SUCCESS; + } else + pbrms->src = prefix_new(); + + prefix_copy(pbrms->src, prefix); + SET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP); + +check: + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_dst, + pbr_map_match_dst_cmd, + "[no] match dst-ip ![$prefix]", + NO_STR + "Match the rest of the command\n" + "Choose the dst ipv4 or ipv6 prefix to use\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + const char *fmsg = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) + return CMD_SUCCESS; + prefix_free(&pbrms->dst); + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP); + goto check; + } + + assert(prefix); + if (!pbr_family_consistent(pbrms, prefix->family, PBR_FILTER_DST_IP, 0, + &fmsg)) { + vty_out(vty, "Address family mismatch (%s)\n", fmsg); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = prefix->family; + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) { + if (prefix_same(pbrms->dst, prefix)) + return CMD_SUCCESS; + } else + pbrms->dst = prefix_new(); + + prefix_copy(pbrms->dst, prefix); + SET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP); + +check: + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_ip_proto, + pbr_map_match_ip_proto_cmd, + "[no] match ip-protocol ![PROTO$ip_proto]", + NO_STR + "Match the rest of the command\n" + "Choose an ip-protocol\n" + "Protocol name\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct protoent *p = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL); + goto check; + } + + if (ip_proto) + p = getprotobyname(ip_proto); + + if (!ip_proto || !p) { + vty_out(vty, "Unable to convert %s to proto id\n", + (ip_proto ? ip_proto : "(null)")); + return CMD_WARNING_CONFIG_FAILED; + } + + pbrms->ip_proto = p->p_proto; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_src_port, + pbr_map_match_src_port_cmd, + "[no] match src-port ![(1-65535)$port]", + NO_STR + "Match the rest of the command\n" + "Choose the source port to use\n" + "The Source Port\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT); + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT) && + (pbrms->src_prt == port)) { + return CMD_SUCCESS; + } + pbrms->src_prt = port; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_dst_port, + pbr_map_match_dst_port_cmd, + "[no] match dst-port ![(1-65535)$port]", + NO_STR + "Match the rest of the command\n" + "Choose the destination port to use\n" + "The Destination Port\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT); + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT) && + (pbrms->dst_prt == port)) { + return CMD_SUCCESS; + } + pbrms->dst_prt = port; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_dscp, + pbr_map_match_dscp_cmd, + "[no] match dscp ![DSCP$dscp]", + NO_STR + "Match the rest of the command\n" + "Match based on IP DSCP field\n" + "DSCP value (below 64) or standard codepoint name\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); + pbrms->dsfield &= ~PBR_DSFIELD_DSCP; + goto check; + } + + unsigned long ul_dscp; + char *pend = NULL; + uint8_t shifted_dscp; + + assert(dscp); + ul_dscp = strtoul(dscp, &pend, 0); + if (pend && *pend) + ul_dscp = pbr_map_decode_dscp_enum(dscp); + + if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { + vty_out(vty, "Invalid dscp value: %s%s\n", dscp, + ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); + return CMD_WARNING_CONFIG_FAILED; + } + + shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP) && + ((pbrms->dsfield & PBR_DSFIELD_DSCP) == shifted_dscp)) { + return CMD_SUCCESS; + } + + /* Set the DSCP bits of the DSField */ + pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | shifted_dscp; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_ecn, + pbr_map_match_ecn_cmd, + "[no] match ecn ![(0-3)$ecn]", + NO_STR + "Match the rest of the command\n" + "Match based on IP ECN field\n" + "Explicit Congestion Notification\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); + pbrms->dsfield &= ~PBR_DSFIELD_ECN; + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN) && + ((pbrms->dsfield & PBR_DSFIELD_ECN) == ecn)) { + return CMD_SUCCESS; + } + + /* Set the ECN bits of the DSField */ + pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_ECN) | ecn; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/*********************************************************************** + * pbrms/rule Match L2 fields + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_map_match_pcp, + pbr_map_match_pcp_cmd, + "[no] match pcp ![(0-7)$pcp]", + NO_STR + "Match spec follows\n" + "Match based on 802.1p Priority Code Point (PCP) value\n" + "PCP value to match\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_PCP); + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP) && + (pbrms->match_pcp == pcp)) { + return CMD_SUCCESS; + } + + pbrms->match_pcp = pcp; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_PCP); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_vlan_id, + pbr_map_match_vlan_id_cmd, + "[no] match vlan ![(1-4094)$vlan_id]", + NO_STR + "Match spec follows\n" + "Match based on VLAN ID\n" + "VLAN ID to match\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID) && + (pbrms->match_vlan_id == vlan_id)) { + return CMD_SUCCESS; + } + + /* + * Maintaining previous behavior: setting a vlan_id match + * automatically clears any vlan_flags matching. + */ + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); + SET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); + pbrms->match_vlan_id = vlan_id; + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_match_vlan_tag, + pbr_map_match_vlan_tag_cmd, + "[no] match vlan ![$tag_type]", + NO_STR + "Match the rest of the command\n" + "Match based on VLAN tagging\n" + "Match all tagged frames\n" + "Match all untagged frames\n" + "Match untagged frames, or tagged frames with id zero\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + uint16_t vlan_flags; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); + goto check; + } + + assert(tag_type); + if (strmatch(tag_type, "tagged")) + vlan_flags = PBR_VLAN_FLAGS_TAGGED; + else if (strmatch(tag_type, "untagged")) + vlan_flags = PBR_VLAN_FLAGS_UNTAGGED; + else if (strmatch(tag_type, "untagged-or-zero")) + vlan_flags = PBR_VLAN_FLAGS_UNTAGGED_0; + else { + vty_out(vty, "unknown vlan flag\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS) && + (pbrms->match_vlan_flags == vlan_flags)) { + return CMD_SUCCESS; + } + + /* + * Maintaining previous behavior: setting a vlan_flags match + * automatically clears any vlan_id matching. + */ + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); + SET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); + pbrms->match_vlan_flags = vlan_flags; + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/*********************************************************************** + * pbrms/rule Match meta + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_map_match_mark, + pbr_map_match_mark_cmd, + "[no] match mark ![(1-4294967295)$mark]", + NO_STR + "Match the rest of the command\n" + "Choose the mark value to use\n" + "mark\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + +#ifndef GNU_LINUX + vty_out(vty, "pbr marks are not supported on this platform\n"); + return CMD_WARNING_CONFIG_FAILED; +#endif + + if (no) { + if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK); + goto check; + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK) && + (pbrms->mark == (uint32_t)mark)) { + return CMD_SUCCESS; + } + + pbrms->mark = (uint32_t)mark; + SET_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK); + +check: + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +/*********************************************************************** + * pbrms/rule Action Set L3 Fields + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_map_action_src, + pbr_map_action_src_cmd, + "[no] set src-ip ![$su]", + NO_STR + "Set command\n" + "Set the src ipv4 or ipv6 prefix\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + const char *fmsg = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP); + goto check; + } + + assert(su); + if (!pbr_family_consistent(pbrms, sockunion_family(su), + PBR_ACTION_SRC_IP, 0, &fmsg)) { + vty_out(vty, "Address family mismatch (%s)\n", fmsg); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = sockunion_family(su); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP) && + (sockunion_same(&pbrms->action_src, su))) { + return CMD_SUCCESS; + } + pbrms->action_src = *su; + SET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_dst, + pbr_map_action_dst_cmd, + "[no] set dst-ip ![$su]", + NO_STR + "Set command\n" + "Set the dst ipv4 or ipv6 prefix\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + const char *fmsg = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP); + goto check; + } + + assert(su); + if (!pbr_family_consistent(pbrms, sockunion_family(su), + PBR_ACTION_DST_IP, 0, &fmsg)) { + vty_out(vty, "Address family mismatch (%s)\n", fmsg); + return CMD_WARNING_CONFIG_FAILED; + } + pbrms->family = sockunion_family(su); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP) && + (sockunion_same(&pbrms->action_dst, su))) { + return CMD_SUCCESS; + } + pbrms->action_dst = *su; + SET_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_src_port, + pbr_map_action_src_port_cmd, + "[no] set src-port ![(1-65535)$port]", + NO_STR + "Set command\n" + "Set Source Port\n" + "The Source Port\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT); + goto check; + } + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT) && + (pbrms->action_src_port == port)) + return CMD_SUCCESS; + + pbrms->action_src_port = port; + SET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_dst_port, + pbr_map_action_dst_port_cmd, + "[no] set dst-port ![(1-65535)$port]", + NO_STR + "Set command\n" + "Set Destination Port\n" + "The Destination Port\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT); + goto check; + } + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT) && + (pbrms->action_dst_port == port)) + return CMD_SUCCESS; + + SET_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT); + pbrms->action_dst_port = port; + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_dscp, + pbr_map_action_dscp_cmd, + "[no] set dscp ![DSCP$dscp]", + NO_STR + "Set command\n" + "Set IP DSCP field\n" + "DSCP numeric value (0-63) or standard codepoint name\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DSCP); + goto check; + } + + unsigned long ul_dscp; + char *pend = NULL; + uint8_t shifted_dscp; + + assert(dscp); + ul_dscp = strtoul(dscp, &pend, 0); + if (pend && *pend) + ul_dscp = pbr_map_decode_dscp_enum(dscp); + + if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { + vty_out(vty, "Invalid dscp value: %s%s\n", dscp, + ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); + return CMD_WARNING_CONFIG_FAILED; + } + + shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP) && + (pbrms->action_dscp == shifted_dscp)) { + return CMD_SUCCESS; + } + SET_FLAG(pbrms->action_bm, PBR_ACTION_DSCP); + pbrms->action_dscp = shifted_dscp; + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_ecn, + pbr_map_action_ecn_cmd, + "[no] set ecn ![(0-3)$ecn]", + NO_STR + "Set command\n" + "Set IP ECN field\n" + "Explicit Congestion Notification value\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_ECN); + goto check; + } + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN) && + (pbrms->action_ecn == ecn)) { + return CMD_SUCCESS; + } + SET_FLAG(pbrms->action_bm, PBR_ACTION_ECN); + pbrms->action_ecn = ecn; + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + + +/*********************************************************************** + * pbrms/rule Action Set Meta + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_map_action_queue_id, + pbr_map_action_queue_id_cmd, + "[no] set queue-id ![(1-65535)$queue_id]", + NO_STR + "Set the rest of the command\n" + "Set based on egress port queue id\n" + "A valid value in range 1..65535 \n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID); + goto check; + } + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID) && + (pbrms->action_queue_id == (uint32_t)queue_id)) { + return CMD_SUCCESS; + } + pbrms->action_queue_id = (uint32_t)queue_id; + SET_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + + +/*********************************************************************** + * pbrms/rule Action Set L2 Fields + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_map_action_pcp, + pbr_map_action_pcp_cmd, + "[no] set pcp ![(0-7)$pcp]", + NO_STR + "Set the rest of the command\n" + "Set based on 802.1p Priority Code Point (PCP) value\n" + "A valid value in range 0..7\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_PCP); + goto check; + } + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP) && + pbrms->action_pcp == pcp) { + return CMD_SUCCESS; + } + + pbrms->action_pcp = pcp; + SET_FLAG(pbrms->action_bm, PBR_ACTION_PCP); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_vlan_id, + pbr_map_action_vlan_id_cmd, + "[no] set vlan ![(1-4094)$vlan_id]", + NO_STR + "Set the rest of the command\n" + "Set action for VLAN tagging\n" + "A valid value in range 1..4094\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); + goto check; + } + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID) && + (pbrms->action_vlan_id == vlan_id)) { + return CMD_SUCCESS; + } + + /* + * Setting a vlan_id action automatically clears any strip-inner action + */ + pbrms->action_vlan_id = vlan_id; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); + SET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_action_strip_vlan, + pbr_map_action_strip_vlan_cmd, + "[no] strip vlan", + NO_STR + "Strip the vlan tags from frame\n" + "Strip any inner vlan tag\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (no) { + if (!CHECK_FLAG(pbrms->action_bm, + PBR_ACTION_VLAN_STRIP_INNER_ANY)) + return CMD_SUCCESS; + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); + goto check; + } + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) + return CMD_SUCCESS; + + /* + * Setting a strip-inner action automatically clears any vlan_id action + */ + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); + SET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); + +check: + pbr_map_check(pbrms, true); + return CMD_SUCCESS; +} + + +/*********************************************************************** + * pbrms/rule Action Forwarding + ***********************************************************************/ + +static void pbrms_clear_set_vrf_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->vrf_lookup || pbrms->vrf_unchanged) { + pbr_map_delete_vrf(pbrms); + pbrms->vrf_name[0] = '\0'; + pbrms->vrf_lookup = false; + pbrms->vrf_unchanged = false; + } +} + +static void pbrms_clear_set_nhg_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->nhgrp_name) + pbr_map_delete_nexthops(pbrms); +} + +static void pbrms_clear_set_nexthop_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->nhg) + pbr_nht_delete_individual_nexthop(pbrms); +} + +static void pbrms_clear_set_config(struct pbr_map_sequence *pbrms) +{ + pbrms_clear_set_vrf_config(pbrms); + pbrms_clear_set_nhg_config(pbrms); + pbrms_clear_set_nexthop_config(pbrms); + + pbrms->nhs_installed = false; + + pbrms->forwarding_type = PBR_FT_UNSPEC; + + /* clear advisory flag indicating nexthop == blackhole */ + UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DROP); +} + + + +DEFPY(pbr_map_nexthop_group, pbr_map_nexthop_group_cmd, + "set nexthop-group NHGNAME$name", + "Set for the PBR-MAP\n" + "nexthop-group to use\n" + "The name of the nexthop-group\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct nexthop_group_cmd *nhgc; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + nhgc = nhgc_find(name); + if (!nhgc) { + vty_out(vty, "Specified nexthop-group %s does not exist\n", + name); + vty_out(vty, + "PBR-MAP will not be applied until it is created\n"); + } + + if (pbrms->nhgrp_name && strcmp(name, pbrms->nhgrp_name) == 0) + return CMD_SUCCESS; + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + pbr_nht_set_seq_nhg(pbrms, name); + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(no_pbr_map_nexthop_group, no_pbr_map_nexthop_group_cmd, + "no set nexthop-group [NHGNAME$name]", + NO_STR + "Set for the PBR-MAP\n" + "nexthop-group to use\n" + "The name of the nexthop-group\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (pbr_map_nexthop, + pbr_map_nexthop_cmd, + "set nexthop\ + <\ + $addr [INTERFACE$intf]\ + |INTERFACE$intf\ + |blackhole$bh\ + >\ + [nexthop-vrf NAME$vrf_name]", + "Set for the PBR-MAP\n" + "Specify one of the nexthops in this map\n" + "v4 Address\n" + "v6 Address\n" + "Interface to use\n" + "Interface to use\n" + "Blackhole route\n" + "If the nexthop is in a different vrf tell us\n" + "The nexthop-vrf Name\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct vrf *vrf; + struct nexthop nhop; + struct nexthop *nh = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (vrf_name) + vrf = vrf_lookup_by_name(vrf_name); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) { + vty_out(vty, "Specified VRF: %s is non-existent\n", vrf_name); + return CMD_WARNING_CONFIG_FAILED; + } + + memset(&nhop, 0, sizeof(nhop)); + nhop.vrf_id = vrf->vrf_id; + + if (intf) { + struct interface *ifp = NULL; + struct interface *ifptmp; + struct vrf *vrftmp; + int count = 0; + + if (vrf_is_backend_netns() && vrf_name) { + ifp = if_lookup_by_name_vrf(intf, vrf); + } else { + RB_FOREACH (vrftmp, vrf_name_head, &vrfs_by_name) { + ifptmp = if_lookup_by_name_vrf(intf, vrftmp); + if (ifptmp) { + ifp = ifptmp; + count++; + if (!vrf_is_backend_netns()) + break; + } + } + } + + if (!ifp) { + vty_out(vty, "Specified Intf %s does not exist\n", + intf); + return CMD_WARNING_CONFIG_FAILED; + } + if (count > 1) { + vty_out(vty, + "Specified Intf %s exists in multiple VRFs\n", + intf); + vty_out(vty, "You must specify the nexthop-vrf\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (ifp->vrf->vrf_id != vrf->vrf_id) + vty_out(vty, + "Specified Intf %s is not in vrf %s but is in vrf %s, using actual vrf\n", + ifp->name, vrf->name, ifp->vrf->name); + nhop.ifindex = ifp->ifindex; + nhop.vrf_id = ifp->vrf->vrf_id; + } + + if (addr) { + if (addr->sa.sa_family == AF_INET) { + nhop.gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; + if (intf) + nhop.type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + nhop.type = NEXTHOP_TYPE_IPV4; + } else { + nhop.gate.ipv6 = addr->sin6.sin6_addr; + if (intf) + nhop.type = NEXTHOP_TYPE_IPV6_IFINDEX; + else { + if (IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { + vty_out(vty, + "Specified a v6 LL with no interface, rejecting\n"); + return CMD_WARNING_CONFIG_FAILED; + } + nhop.type = NEXTHOP_TYPE_IPV6; + } + } + } else if (bh) { + nhop.type = NEXTHOP_TYPE_BLACKHOLE; + /* advisory flag for non-linux dataplanes */ + SET_FLAG(pbrms->action_bm, PBR_ACTION_DROP); + } else { + nhop.type = NEXTHOP_TYPE_IFINDEX; + } + + if (pbrms->nhg) + nh = nexthop_exists(pbrms->nhg, &nhop); + + if (nh) /* Same config re-entered */ + goto done; + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + pbr_nht_add_individual_nexthop(pbrms, &nhop); + + pbr_map_check(pbrms, true); + +done: + if (nhop.type == NEXTHOP_TYPE_IFINDEX + || (nhop.type == NEXTHOP_TYPE_IPV6_IFINDEX + && IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6))) { + struct interface *ifp; + + ifp = if_lookup_by_index(nhop.ifindex, nhop.vrf_id); + if (ifp) + pbr_nht_nexthop_interface_update(ifp); + } + + return CMD_SUCCESS; +} + +/* clang-format off */ +DEFPY (no_pbr_map_nexthop, + no_pbr_map_nexthop_cmd, + "no set nexthop\ + [<\ + $addr [INTERFACE$intf]\ + |INTERFACE$intf\ + |blackhole$bh\ + >\ + [nexthop-vrf NAME$vrf_name]]", + NO_STR + "Set for the PBR-MAP\n" + "Specify one of the nexthops in this map\n" + "v4 Address\n" + "v6 Address\n" + "Interface to use\n" + "Interface to use\n" + "Blackhole route\n" + "If the nexthop is in a different vrf tell us\n" + "The nexthop-vrf Name\n") +{ + /* clang-format on */ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_vrf, pbr_map_vrf_cmd, + "set vrf ", + "Set for the PBR-MAP\n" + "Specify the VRF for this map\n" + "The VRF Name\n" + "Use the interface's VRF for lookup\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + /* + * If an equivalent set vrf * exists, just return success. + */ + if ((pbrms->forwarding_type == PBR_FT_SETVRF) && + vrf_name && + strncmp(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)) == 0) + return CMD_SUCCESS; + else if (!vrf_name && (pbrms->forwarding_type == PBR_FT_VRF_UNCHANGED)) + /* Unchanged already set */ + return CMD_SUCCESS; + + if (vrf_name && !pbr_vrf_lookup_by_name(vrf_name)) { + vty_out(vty, "Specified: %s is non-existent\n", vrf_name); + return CMD_WARNING_CONFIG_FAILED; + } + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + if (vrf_name) { + pbrms->vrf_lookup = true; + pbrms->forwarding_type = PBR_FT_SETVRF; + strlcpy(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)); + } else { + pbrms->forwarding_type = PBR_FT_VRF_UNCHANGED; + pbrms->vrf_unchanged = true; + } + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(no_pbr_map_vrf, no_pbr_map_vrf_cmd, + "no set vrf []", + NO_STR + "Set for the PBR-MAP\n" + "Specify the VRF for this map\n" + "The VRF Name\n" + "Use the interface's VRF for lookup\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +/*********************************************************************** + * Policy + ***********************************************************************/ + +/* clang-format off */ +DEFPY (pbr_policy, + pbr_policy_cmd, + "[no] pbr-policy PBRMAP$mapname", + NO_STR + "Policy to use\n" + "Name of the pbr-map to apply\n") +{ + /* clang-format on */ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct pbr_map *pbrm, *old_pbrm; + struct pbr_interface *pbr_ifp = ifp->info; + + old_pbrm = NULL; + pbrm = pbrm_find(mapname); + + if (!pbr_ifp) { + /* we don't want one and we don't have one, so... */ + if (no) + return CMD_SUCCESS; + + /* Some one could have fat fingered the interface name */ + pbr_ifp = pbr_if_new(ifp); + } + + if (no) { + if (strcmp(pbr_ifp->mapname, mapname) == 0) { + pbr_ifp->mapname[0] = '\0'; + if (pbrm) + pbr_map_interface_delete(pbrm, ifp); + } + } else { + if (strcmp(pbr_ifp->mapname, "") != 0) { + old_pbrm = pbrm_find(pbr_ifp->mapname); + + /* + * So if we have an old pbrm we should only + * delete it if we are actually deleting and + * moving to a new pbrm + */ + if (old_pbrm && old_pbrm != pbrm) + pbr_map_interface_delete(old_pbrm, ifp); + } + snprintf(pbr_ifp->mapname, sizeof(pbr_ifp->mapname), + "%s", mapname); + + /* + * So only reinstall if the old_pbrm and this pbrm are + * different. + */ + if (pbrm && pbrm != old_pbrm) + pbr_map_add_interface(pbrm, ifp); + } + + return CMD_SUCCESS; +} + +DEFPY (show_pbr, + show_pbr_cmd, + "show pbr", + SHOW_STR + PBR_STR) +{ + pbr_nht_write_table_range(vty); + pbr_nht_write_rule_range(vty); + + return CMD_SUCCESS; +} + +static void +pbrms_nexthop_group_write_individual_nexthop( + struct vty *vty, const struct pbr_map_sequence *pbrms) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache lookup; + struct pbr_nexthop_cache *pnhc; + + memset(&find, 0, sizeof(find)); + strlcpy(find.name, pbrms->internal_nhg_name, sizeof(find.name)); + + pnhgc = hash_lookup(pbr_nhg_hash, &find); + assert(pnhgc); + + lookup.nexthop = *pbrms->nhg->nexthop; + pnhc = hash_lookup(pnhgc->nhh, &lookup); + + nexthop_group_write_nexthop_simple( + vty, pbrms->nhg->nexthop, + pnhc->nexthop.ifindex != 0 ? pnhc->intf_name : NULL); + if (pnhc->nexthop.vrf_id != VRF_DEFAULT) + vty_out(vty, " nexthop-vrf %s", pnhc->vrf_name); + + vty_out(vty, "\n"); +} + +static void vty_show_pbrms(struct vty *vty, + const struct pbr_map_sequence *pbrms, bool detail) +{ + char rbuf[64]; + + if (pbrms->reason) + pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); + + vty_out(vty, " Seq: %u rule: %u\n", pbrms->seqno, pbrms->ruleno); + + if (detail) + vty_out(vty, " Installed: %" PRIu64 "(%u) Reason: %s\n", + pbrms->installed, pbrms->unique, + pbrms->reason ? rbuf : "Valid"); + else + vty_out(vty, " Installed: %s Reason: %s\n", + pbrms->installed ? "yes" : "no", + pbrms->reason ? rbuf : "Valid"); + + /* match clauses first */ + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) { + struct protoent *p; + + p = getprotobynumber(pbrms->ip_proto); + vty_out(vty, " IP Protocol Match: %s\n", p->p_name); + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) + vty_out(vty, " SRC IP Match: %pFX\n", pbrms->src); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) + vty_out(vty, " DST IP Match: %pFX\n", pbrms->dst); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) + vty_out(vty, " SRC Port Match: %u\n", pbrms->src_prt); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) + vty_out(vty, " DST Port Match: %u\n", pbrms->dst_prt); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) + vty_out(vty, " DSCP Match: %u\n", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) + vty_out(vty, " ECN Match: %u\n", + pbrms->dsfield & PBR_DSFIELD_ECN); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) + vty_out(vty, " MARK Match: %u\n", pbrms->mark); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) + vty_out(vty, " PCP Match: %d\n", pbrms->match_pcp); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) + vty_out(vty, " Match VLAN ID: %u\n", + pbrms->match_vlan_id); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) + vty_out(vty, " Match VLAN tagged frames\n"); + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) + vty_out(vty, " Match VLAN untagged frames\n"); + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) + vty_out(vty, " Match VLAN untagged or ID 0\n"); + } + + /* set actions */ + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) + vty_out(vty, " Set SRC IP: %pSU\n", &pbrms->action_src); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) + vty_out(vty, " Set DST IP: %pSU\n", &pbrms->action_dst); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) + vty_out(vty, " Set Src port: %u\n", + pbrms->action_src_port); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) + vty_out(vty, " Set Dst port: %u\n", + pbrms->action_dst_port); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) + vty_out(vty, " Set DSCP: %u\n", (pbrms->action_dscp) >> 2); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) + vty_out(vty, " Set ECN: %u\n", pbrms->action_ecn); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) + vty_out(vty, " Set VLAN ID %u\n", pbrms->action_vlan_id); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) + vty_out(vty, " Strip VLAN ID\n"); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) + vty_out(vty, " Set PCP %u\n", pbrms->action_pcp); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) + vty_out(vty, " Set Queue ID: %u\n", + pbrms->action_queue_id); + + + switch (pbrms->forwarding_type) { + case PBR_FT_UNSPEC: + vty_out(vty, " Nexthop-Group: Unknown Installed: no\n"); + break; + case PBR_FT_VRF_UNCHANGED: + vty_out(vty, " VRF Unchanged (use interface vrf)\n"); + break; + case PBR_FT_SETVRF: + assert(pbrms->vrf_name); + vty_out(vty, " VRF Lookup: %s\n", pbrms->vrf_name); + break; + case PBR_FT_NEXTHOP_GROUP: + assert(pbrms->nhgrp_name); + vty_out(vty, " Nexthop-Group: %s\n", pbrms->nhgrp_name); + + if (detail) + vty_out(vty, + " Installed: %u(%d) Tableid: %u\n", + pbrms->nhs_installed, + pbr_nht_get_installed(pbrms->nhgrp_name), + pbr_nht_get_table(pbrms->nhgrp_name)); + else + vty_out(vty, " Installed: %s Tableid: %u\n", + pbr_nht_get_installed(pbrms->nhgrp_name) ? "yes" + : "no", + pbr_nht_get_table(pbrms->nhgrp_name)); + break; + case PBR_FT_NEXTHOP_SINGLE: + assert(pbrms->internal_nhg_name); + vty_out(vty, " "); + pbrms_nexthop_group_write_individual_nexthop(vty, pbrms); + if (detail) + vty_out(vty, + " Installed: %u(%d) Tableid: %u\n", + pbrms->nhs_installed, + pbr_nht_get_installed(pbrms->internal_nhg_name), + pbr_nht_get_table(pbrms->internal_nhg_name)); + else + vty_out(vty, " Installed: %s Tableid: %u\n", + pbr_nht_get_installed(pbrms->internal_nhg_name) + ? "yes" + : "no", + pbr_nht_get_table(pbrms->internal_nhg_name)); + break; + } +} + +static void vty_json_pbrms(json_object *j, struct vty *vty, + const struct pbr_map_sequence *pbrms) +{ + json_object *jpbrm, *nexthop_group; + char *nhg_name = pbrms->nhgrp_name ? pbrms->nhgrp_name + : pbrms->internal_nhg_name; + char rbuf[64]; + + jpbrm = json_object_new_object(); + + json_object_int_add(jpbrm, "id", pbrms->unique); + + if (pbrms->reason) + pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); + + json_object_int_add(jpbrm, "sequenceNumber", pbrms->seqno); + json_object_int_add(jpbrm, "ruleNumber", pbrms->ruleno); + json_object_boolean_add(jpbrm, "vrfUnchanged", pbrms->vrf_unchanged); + json_object_boolean_add(jpbrm, "installed", pbrms->installed); + json_object_string_add(jpbrm, "installedReason", + pbrms->reason ? rbuf : "Valid"); + + switch (pbrms->forwarding_type) { + case PBR_FT_UNSPEC: + break; + case PBR_FT_VRF_UNCHANGED: + break; + case PBR_FT_SETVRF: + assert(pbrms->vrf_name); + json_object_string_add(jpbrm, "vrfName", pbrms->vrf_name); + break; + case PBR_FT_NEXTHOP_GROUP: + case PBR_FT_NEXTHOP_SINGLE: + assert(nhg_name); + nexthop_group = json_object_new_object(); + + json_object_int_add(nexthop_group, "tableId", + pbr_nht_get_table(nhg_name)); + json_object_string_add(nexthop_group, "name", nhg_name); + json_object_boolean_add(nexthop_group, "installed", + pbr_nht_get_installed(nhg_name)); + json_object_int_add(nexthop_group, "installedInternally", + pbrms->nhs_installed); + + json_object_object_add(jpbrm, "nexthopGroup", nexthop_group); + break; + } + + + /* + * Match clauses + */ + + /* IP Header */ + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) + json_object_int_add(jpbrm, "matchIpProtocol", pbrms->ip_proto); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) + json_object_string_addf(jpbrm, "matchSrc", "%pFX", pbrms->src); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) + json_object_string_addf(jpbrm, "matchDst", "%pFX", pbrms->dst); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) + json_object_int_add(jpbrm, "matchSrcPort", pbrms->src_prt); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) + json_object_int_add(jpbrm, "matchDstPort", pbrms->dst_prt); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) + json_object_int_add(jpbrm, "matchDscp", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) + json_object_int_add(jpbrm, "matchEcn", + pbrms->dsfield & PBR_DSFIELD_ECN); + + /* L2 headers */ + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) + json_object_int_add(jpbrm, "matchPcp", pbrms->match_pcp); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) + json_object_int_add(jpbrm, "matchVlanId", pbrms->match_vlan_id); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { + const char *p = "?"; + + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) + p = "tagged"; + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) + p = "untagged"; + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) + p = "untagged-or-0"; + + json_object_string_addf(jpbrm, "matchVlanFlags", "%s", p); + } + + /* meta */ + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) + json_object_int_add(jpbrm, "matchMark", pbrms->mark); + + /* + * action clauses + */ + + /* IP header fields */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) + json_object_string_addf(jpbrm, "actionSetSrcIpAddr", "%pSU", + &pbrms->action_src); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) + json_object_string_addf(jpbrm, "actionSetDstIpAddr", "%pSU", + &pbrms->action_dst); + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) + json_object_int_add(jpbrm, "actionSetSrcPort", + pbrms->action_src_port); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) + json_object_int_add(jpbrm, "actionSetDstPort", + pbrms->action_dst_port); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) + json_object_int_add(jpbrm, "actionSetDscp", + pbrms->action_dscp >> 2); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) + json_object_int_add(jpbrm, "actionSetEcn", pbrms->action_ecn); + + /* L2 header fields */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) + json_object_boolean_true_add(jpbrm, "actionVlanStripInnerAny"); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) + json_object_int_add(jpbrm, "actionSetVlanId", + pbrms->action_vlan_id); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) + json_object_int_add(jpbrm, "actionSetPcp", pbrms->action_pcp); + + /* meta */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) + json_object_int_add(jpbrm, "actionSetQueueId", + pbrms->action_queue_id); + + json_object_array_add(j, jpbrm); +} + +static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm, + bool detail) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + vty_out(vty, " pbr-map %s valid: %s\n", pbrm->name, + pbrm->valid ? "yes" : "no"); + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + vty_show_pbrms(vty, pbrms, detail); +} + +static void vty_json_pbr_map(json_object *j, struct vty *vty, + const struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + json_object *jpbrms; + + json_object_string_add(j, "name", pbrm->name); + json_object_boolean_add(j, "valid", pbrm->valid); + + jpbrms = json_object_new_array(); + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + vty_json_pbrms(jpbrms, vty, pbrms); + + json_object_object_add(j, "policies", jpbrms); +} + +DEFPY (show_pbr_map, + show_pbr_map_cmd, + "show pbr map [NAME$name] [detail$detail] [json$json]", + SHOW_STR + PBR_STR + "PBR Map\n" + "PBR Map Name\n" + "Detailed information\n" + JSON_STR) +{ + struct pbr_map *pbrm; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + json_object *this_map = NULL; + if (name && strcmp(name, pbrm->name) != 0) + continue; + + if (j) + this_map = json_object_new_object(); + + if (this_map) { + vty_json_pbr_map(this_map, vty, pbrm); + + json_object_array_add(j, this_map); + continue; + } + + vty_show_pbr_map(vty, pbrm, detail); + } + + if (j) + vty_json(vty, j); + + return CMD_SUCCESS; +} + +DEFPY(show_pbr_nexthop_group, + show_pbr_nexthop_group_cmd, + "show pbr nexthop-groups [WORD$word] [json$json]", + SHOW_STR + PBR_STR + "Nexthop Groups\n" + "Optional Name of the nexthop group\n" + JSON_STR) +{ + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + if (j) { + pbr_nht_json_nexthop_group(j, word); + + vty_json(vty, j); + } else + pbr_nht_show_nexthop_group(vty, word); + + + return CMD_SUCCESS; +} + +DEFPY (show_pbr_interface, + show_pbr_interface_cmd, + "show pbr interface [NAME$name] [json$json]", + SHOW_STR + PBR_STR + "PBR Interface\n" + "PBR Interface Name\n" + JSON_STR) +{ + struct interface *ifp; + struct vrf *vrf; + struct pbr_interface *pbr_ifp; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES(vrf, ifp) { + struct pbr_map *pbrm; + json_object *this_iface = NULL; + + if (j) + this_iface = json_object_new_object(); + + if (!ifp->info) { + json_object_free(this_iface); + continue; + } + + if (name && strcmp(ifp->name, name) != 0) { + json_object_free(this_iface); + continue; + } + + pbr_ifp = ifp->info; + + if (strcmp(pbr_ifp->mapname, "") == 0) { + json_object_free(this_iface); + continue; + } + + pbrm = pbrm_find(pbr_ifp->mapname); + + if (this_iface) { + json_object_string_add(this_iface, "name", + ifp->name); + json_object_int_add(this_iface, "index", + ifp->ifindex); + json_object_string_add(this_iface, "policy", + pbr_ifp->mapname); + json_object_boolean_add(this_iface, "valid", + pbrm); + + json_object_array_add(j, this_iface); + continue; + } + + vty_out(vty, " %s(%d) with pbr-policy %s", ifp->name, + ifp->ifindex, pbr_ifp->mapname); + if (!pbrm) + vty_out(vty, " (map doesn't exist)"); + vty_out(vty, "\n"); + } + } + + if (j) + vty_json(vty, j); + + return CMD_SUCCESS; +} + +/* PBR debugging CLI ------------------------------------------------------- */ + +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = pbr_debug_config_write, +}; + +DEFPY(debug_pbr, + debug_pbr_cmd, + "[no] debug pbr [{map$map|zebra$zebra|nht$nht|events$events}]", + NO_STR + DEBUG_STR + PBR_STR + "Policy maps\n" + "PBRD <-> Zebra communications\n" + "Nexthop tracking\n" + "Events\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + if (map) + DEBUG_MODE_SET(&pbr_dbg_map, mode, !no); + if (zebra) + DEBUG_MODE_SET(&pbr_dbg_zebra, mode, !no); + if (nht) + DEBUG_MODE_SET(&pbr_dbg_nht, mode, !no); + if (events) + DEBUG_MODE_SET(&pbr_dbg_event, mode, !no); + + /* no specific debug --> act on all of them */ + if (strmatch(argv[argc - 1]->text, "pbr")) + pbr_debug_set_all(mode, !no); + + return CMD_SUCCESS; +} + +DEFUN_NOSH(show_debugging_pbr, + show_debugging_pbr_cmd, + "show debugging [pbr]", + SHOW_STR + DEBUG_STR + PBR_STR) +{ + vty_out(vty, "PBR debugging status:\n"); + + pbr_debug_config_write_helper(vty, false); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +/* ------------------------------------------------------------------------- */ + + +static int pbr_interface_config_write(struct vty *vty) +{ + struct interface *ifp; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + if_vty_config_start(vty, ifp); + + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + + pbr_map_write_interfaces(vty, ifp); + + if_vty_config_end(vty); + } + } + + return 1; +} + +static int pbr_vty_map_config_write(struct vty *vty); +/* PBR map node structure. */ +static struct cmd_node pbr_map_node = { + .name = "pbr-map", + .node = PBRMAP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-pbr-map)# ", + .config_write = pbr_vty_map_config_write, +}; + +static int pbr_vty_map_config_write_sequence(struct vty *vty, + struct pbr_map *pbrm, + struct pbr_map_sequence *pbrms) +{ + vty_out(vty, "pbr-map %s seq %u\n", pbrm->name, pbrms->seqno); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) { + struct protoent *p; + + p = getprotobynumber(pbrms->ip_proto); + vty_out(vty, " match ip-protocol %s\n", p->p_name); + } + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) + vty_out(vty, " match src-ip %pFX\n", pbrms->src); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) + vty_out(vty, " match dst-ip %pFX\n", pbrms->dst); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) + vty_out(vty, " match src-port %u\n", pbrms->src_prt); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) + vty_out(vty, " match dst-port %u\n", pbrms->dst_prt); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) + vty_out(vty, " match dscp %u\n", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) + vty_out(vty, " match ecn %u\n", + pbrms->dsfield & PBR_DSFIELD_ECN); + + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) + vty_out(vty, " match pcp %d\n", pbrms->match_pcp); + + /* L2 headers */ + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) + vty_out(vty, " match vlan %u\n", pbrms->match_vlan_id); + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) + vty_out(vty, " match vlan tagged\n"); + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) + vty_out(vty, " match vlan untagged\n"); + if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) + vty_out(vty, " match vlan untagged-or-zero\n"); + } + + /* meta */ + if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) + vty_out(vty, " match mark %u\n", pbrms->mark); + + /* + * action clauses + */ + + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) + vty_out(vty, " set src-ip %pSU\n", &pbrms->action_src); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) + vty_out(vty, " set dst-ip %pSU\n", &pbrms->action_dst); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) + vty_out(vty, " set src-port %d\n", pbrms->action_src_port); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) + vty_out(vty, " set dst-port %d\n", pbrms->action_dst_port); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) + vty_out(vty, " set dscp %u\n", (pbrms->action_dscp) >> 2); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) + vty_out(vty, " set ecn %u\n", pbrms->action_ecn); + + /* L2 header fields */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) + vty_out(vty, " strip vlan any\n"); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) + vty_out(vty, " set vlan %u\n", pbrms->action_vlan_id); + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) + vty_out(vty, " set pcp %d\n", pbrms->action_pcp); + + /* meta */ + if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) + vty_out(vty, " set queue-id %d\n", pbrms->action_queue_id); + + switch (pbrms->forwarding_type) { + case PBR_FT_UNSPEC: + break; + case PBR_FT_VRF_UNCHANGED: + vty_out(vty, " set vrf unchanged\n"); + break; + case PBR_FT_SETVRF: + assert(pbrms->vrf_name); + vty_out(vty, " set vrf %s\n", pbrms->vrf_name); + break; + case PBR_FT_NEXTHOP_GROUP: + assert(pbrms->nhgrp_name); + vty_out(vty, " set nexthop-group %s\n", pbrms->nhgrp_name); + break; + case PBR_FT_NEXTHOP_SINGLE: + assert(pbrms->nhg); + vty_out(vty, " set "); + pbrms_nexthop_group_write_individual_nexthop(vty, pbrms); + break; + } + + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); + return 1; +} + +static int pbr_vty_map_config_write(struct vty *vty) +{ + struct pbr_map *pbrm; + + pbr_nht_write_table_range(vty); + pbr_nht_write_rule_range(vty); + + RB_FOREACH(pbrm, pbr_map_entry_head, &pbr_maps) { + struct pbr_map_sequence *pbrms; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbr_vty_map_config_write_sequence(vty, pbrm, pbrms); + } + + return 1; +} + +static void pbr_map_completer(vector comps, struct cmd_token *token) +{ + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, pbrm->name)); +} + +static const struct cmd_variable_handler pbr_map_name[] = { + { + .tokenname = "PBRMAP", .completions = pbr_map_completer, + }, + { + .completions = NULL + } +}; + +extern struct zebra_privs_t pbr_privs; + +void pbr_vty_init(void) +{ + cmd_variable_handler_register(pbr_map_name); + + vrf_cmd_init(NULL); + + if_cmd_init(pbr_interface_config_write); + + install_node(&pbr_map_node); + + /* debug */ + install_node(&debug_node); + install_element(ENABLE_NODE, &debug_pbr_cmd); + install_element(CONFIG_NODE, &debug_pbr_cmd); + install_element(ENABLE_NODE, &show_debugging_pbr_cmd); + + install_default(PBRMAP_NODE); + + install_element(CONFIG_NODE, &pbr_map_cmd); + install_element(CONFIG_NODE, &no_pbr_map_cmd); + install_element(CONFIG_NODE, &pbr_set_table_range_cmd); + install_element(CONFIG_NODE, &no_pbr_set_table_range_cmd); + install_element(INTERFACE_NODE, &pbr_policy_cmd); + + install_element(PBRMAP_NODE, &pbr_map_match_ip_proto_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_src_port_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dst_port_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_src_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dst_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dscp_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_ecn_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_vlan_id_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_vlan_tag_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_pcp_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_mark_cmd); + + install_element(PBRMAP_NODE, &pbr_map_action_queue_id_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_strip_vlan_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_vlan_id_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_pcp_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_src_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_dst_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_dscp_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_ecn_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_src_port_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_dst_port_cmd); + + install_element(PBRMAP_NODE, &pbr_map_nexthop_group_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_nexthop_group_cmd); + install_element(PBRMAP_NODE, &pbr_map_nexthop_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_nexthop_cmd); + install_element(PBRMAP_NODE, &pbr_map_vrf_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_vrf_cmd); + install_element(VIEW_NODE, &show_pbr_cmd); + install_element(VIEW_NODE, &show_pbr_map_cmd); + install_element(VIEW_NODE, &show_pbr_interface_cmd); + install_element(VIEW_NODE, &show_pbr_nexthop_group_cmd); +} diff --git a/pbrd/pbr_vty.h b/pbrd/pbr_vty.h new file mode 100644 index 0000000..9158d8d --- /dev/null +++ b/pbrd/pbr_vty.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VTY library for PBR + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PBR_VTY_H__ +#define __PBR_VTY_H__ + +extern void pbr_vty_init(void); +#endif diff --git a/pbrd/pbr_zebra.c b/pbrd/pbr_zebra.c new file mode 100644 index 0000000..dd15bea --- /dev/null +++ b/pbrd/pbr_zebra.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + * Portions: + * Copyright (c) 2021 The MITRE Corporation. + * Copyright (c) 2023 LabN Consulting, L.L.C. + */ +#include + +#include "frrevent.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "routemap.h" +#include "table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "filter.h" +#include "plist.h" +#include "log.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "pbr.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_memory.h" +#include "pbr_zebra.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_INTERFACE, "PBR Interface"); + +/* Zebra structure to hold current status. */ +struct zclient *zclient; + +struct pbr_interface *pbr_if_new(struct interface *ifp) +{ + struct pbr_interface *pbr_ifp; + + assert(ifp); + assert(!ifp->info); + + pbr_ifp = XCALLOC(MTYPE_PBR_INTERFACE, sizeof(*pbr_ifp)); + + ifp->info = pbr_ifp; + return pbr_ifp; +} + +void pbr_if_del(struct interface *ifp) +{ + XFREE(MTYPE_PBR_INTERFACE, ifp->info); +} + +/* Interface addition message from zebra. */ +int pbr_ifp_create(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s", __func__, ifp->name); + + if (!ifp->info) + pbr_if_new(ifp); + + pbr_nht_interface_update(ifp); + /* Update nexthops tracked from a `set nexthop` command */ + pbr_nht_nexthop_interface_update(ifp); + + pbr_map_policy_interface_update(ifp, true); + + return 0; +} + +int pbr_ifp_destroy(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s", __func__, ifp->name); + + pbr_map_policy_interface_update(ifp, false); + + return 0; +} + +static int interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + char buf[PREFIX_STRLEN]; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + DEBUGD(&pbr_dbg_zebra, "%s: %s added %s", __func__, + c ? c->ifp->name : "Unknown", + c ? prefix2str(c->address, buf, sizeof(buf)) : "Unknown"); + + return 0; +} + +static int interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (!c) + return 0; + + DEBUGD(&pbr_dbg_zebra, "%s: %s deleted %pFX", __func__, c->ifp->name, + c->address); + + connected_free(&c); + return 0; +} + +int pbr_ifp_up(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s is up", __func__, ifp->name); + + pbr_nht_nexthop_interface_update(ifp); + + return 0; +} + +int pbr_ifp_down(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s is down", __func__, ifp->name); + + pbr_nht_nexthop_interface_update(ifp); + + return 0; +} + +static int route_notify_owner(ZAPI_CALLBACK_ARGS) +{ + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table_id; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e, + NULL, NULL)) + return -1; + + switch (note) { + case ZAPI_ROUTE_FAIL_INSTALL: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route install failure for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_BETTER_ADMIN_WON: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route better admin distance won for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_INSTALLED: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route installed succeeded for table: %u", + __func__, &p, table_id); + pbr_nht_route_installed_for_table(table_id); + break; + case ZAPI_ROUTE_REMOVED: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route Removed succeeded for table: %u", + __func__, &p, table_id); + pbr_nht_route_removed_for_table(table_id); + break; + case ZAPI_ROUTE_REMOVE_FAIL: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route remove fail for table: %u", __func__, + &p, table_id); + break; + } + + return 0; +} + +static int rule_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t seqno, priority, unique; + enum zapi_rule_notify_owner note; + struct pbr_map_sequence *pbrms; + struct pbr_map_interface *pmi; + char ifname[IFNAMSIZ + 1]; + uint64_t installed; + + if (!zapi_rule_notify_decode(zclient->ibuf, &seqno, &priority, &unique, + ifname, ¬e)) + return -1; + + pmi = NULL; + pbrms = pbrms_lookup_unique(unique, ifname, &pmi); + if (!pbrms) { + DEBUGD(&pbr_dbg_zebra, + "%s: Failure to lookup pbrms based upon %u", __func__, + unique); + return 0; + } + + installed = 1 << pmi->install_bit; + + switch (note) { + case ZAPI_RULE_FAIL_INSTALL: + pbrms->installed &= ~installed; + break; + case ZAPI_RULE_INSTALLED: + pbrms->installed |= installed; + break; + case ZAPI_RULE_FAIL_REMOVE: + /* Don't change state on rule removal failure */ + break; + case ZAPI_RULE_REMOVED: + pbrms->installed &= ~installed; + break; + } + + DEBUGD(&pbr_dbg_zebra, "%s: Received %s: %" PRIu64, __func__, + zapi_rule_notify_owner2str(note), pbrms->installed); + + pbr_map_final_interface_deletion(pbrms->parent, pmi); + + return 0; +} + +static void zebra_connected(struct zclient *zclient) +{ + DEBUGD(&pbr_dbg_zebra, "%s: Registering for fun and profit", __func__); + + zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, zclient, true); + zclient_send_reg_requests(zclient, VRF_DEFAULT); +} + +static void route_add_helper(struct zapi_route *api, struct nexthop_group nhg, + uint8_t install_afi) +{ + struct zapi_nexthop *api_nh; + struct nexthop *nhop; + int i; + + api->prefix.family = install_afi; + + DEBUGD(&pbr_dbg_zebra, " Encoding %pFX", &api->prefix); + + i = 0; + for (ALL_NEXTHOPS(nhg, nhop)) { + api_nh = &api->nexthops[i]; + api_nh->vrf_id = nhop->vrf_id; + api_nh->type = nhop->type; + api_nh->weight = nhop->weight; + switch (nhop->type) { + case NEXTHOP_TYPE_IPV4: + api_nh->gate.ipv4 = nhop->gate.ipv4; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + api_nh->gate.ipv4 = nhop->gate.ipv4; + api_nh->ifindex = nhop->ifindex; + break; + case NEXTHOP_TYPE_IFINDEX: + api_nh->ifindex = nhop->ifindex; + break; + case NEXTHOP_TYPE_IPV6: + memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, + IPV6_MAX_BYTELEN); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + api_nh->ifindex = nhop->ifindex; + memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, + IPV6_MAX_BYTELEN); + break; + case NEXTHOP_TYPE_BLACKHOLE: + api_nh->bh_type = nhop->bh_type; + break; + } + i++; + } + api->nexthop_num = i; + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, api); +} + +/* + * This function assumes a default route is being + * installed into the appropriate tableid + */ +void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, + afi_t install_afi) +{ + struct zapi_route api; + + DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __func__, pnhgc->table_id); + + memset(&api, 0, sizeof(api)); + + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_PBR; + api.safi = SAFI_UNICAST; + /* + * Sending a default route + */ + api.tableid = pnhgc->table_id; + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + switch (install_afi) { + case AFI_MAX: + route_add_helper(&api, nhg, AF_INET); + route_add_helper(&api, nhg, AF_INET6); + break; + case AFI_IP: + route_add_helper(&api, nhg, AF_INET); + break; + case AFI_IP6: + route_add_helper(&api, nhg, AF_INET6); + break; + case AFI_L2VPN: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to install unsupported route type: L2VPN", + __func__); + break; + case AFI_UNSPEC: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to install unspecified route type", __func__); + break; + } +} + +/* + * This function assumes a default route is being + * removed from the appropriate tableid + */ +void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t afi) +{ + struct zapi_route api; + + DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __func__, pnhgc->table_id); + + memset(&api, 0, sizeof(api)); + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_PBR; + api.safi = SAFI_UNICAST; + + api.tableid = pnhgc->table_id; + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + + switch (afi) { + case AFI_IP: + api.prefix.family = AF_INET; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_IP6: + api.prefix.family = AF_INET6; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_MAX: + api.prefix.family = AF_INET; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + api.prefix.family = AF_INET6; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_L2VPN: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to delete unsupported route type: L2VPN", + __func__); + break; + case AFI_UNSPEC: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to delete unspecified route type", __func__); + break; + } +} + +static void pbr_zebra_nexthop_update(struct vrf *vrf, struct prefix *matched, + struct zapi_route *nhr) +{ + uint32_t i; + + if (DEBUG_MODE_CHECK(&pbr_dbg_zebra, DEBUG_MODE_ALL)) { + DEBUGD(&pbr_dbg_zebra, + "%s: Received Nexthop update: %pFX against %pFX", + __func__, matched, &nhr->prefix); + + DEBUGD(&pbr_dbg_zebra, "%s: (Nexthops(%u)", __func__, + nhr->nexthop_num); + + for (i = 0; i < nhr->nexthop_num; i++) { + DEBUGD(&pbr_dbg_zebra, + "%s: Type: %d: vrf: %d, ifindex: %d gate: %pI4", + __func__, nhr->nexthops[i].type, + nhr->nexthops[i].vrf_id, nhr->nexthops[i].ifindex, + &nhr->nexthops[i].gate.ipv4); + } + } + + nhr->prefix = *matched; + pbr_nht_nexthop_update(nhr); +} + +extern struct zebra_privs_t pbr_privs; + +static zclient_handler *const pbr_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = interface_address_delete, + [ZEBRA_ROUTE_NOTIFY_OWNER] = route_notify_owner, + [ZEBRA_RULE_NOTIFY_OWNER] = rule_notify_owner, +}; + +void pbr_zebra_init(void) +{ + zclient = zclient_new(master, &zclient_options_default, pbr_handlers, + array_size(pbr_handlers)); + + zclient_init(zclient, ZEBRA_ROUTE_PBR, 0, &pbr_privs); + zclient->zebra_connected = zebra_connected; + zclient->nexthop_update = pbr_zebra_nexthop_update; +} + +void pbr_zebra_destroy(void) +{ + if (zclient == NULL) + return; + + zclient_stop(zclient); + zclient_free(zclient); + zclient = NULL; +} + +void pbr_send_rnh(struct nexthop *nhop, bool reg) +{ + uint32_t command; + struct prefix p; + + command = (reg) ? + ZEBRA_NEXTHOP_REGISTER : ZEBRA_NEXTHOP_UNREGISTER; + + memset(&p, 0, sizeof(p)); + switch (nhop->type) { + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + p.family = AF_INET; + p.u.prefix4.s_addr = nhop->gate.ipv4.s_addr; + p.prefixlen = IPV4_MAX_BITLEN; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + p.family = AF_INET6; + memcpy(&p.u.prefix6, &nhop->gate.ipv6, IPV6_MAX_BYTELEN); + p.prefixlen = IPV6_MAX_BITLEN; + if (IN6_IS_ADDR_LINKLOCAL(&nhop->gate.ipv6)) + /* + * Don't bother tracking link locals, just track their + * interface state. + */ + return; + break; + } + + if (zclient_send_rnh(zclient, command, &p, SAFI_UNICAST, false, false, + nhop->vrf_id) + == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: Failure to send nexthop to zebra", __func__); + } +} + + +static uint32_t pbr_map_sequence_vrf(const struct pbr_map_sequence *pbrms, + const struct interface *ifp) +{ + struct pbr_vrf *pbr_vrf; + + if (pbrms->vrf_unchanged) + pbr_vrf = ifp->vrf->info; + else + pbr_vrf = pbr_vrf_lookup_by_name(pbrms->vrf_name); + + if (!pbr_vrf) { + DEBUGD(&pbr_dbg_zebra, "%s: VRF not found", __func__); + return 0; + } + + return pbr_vrf->vrf->data.l.table_id; + +} + +/* + * 230716 gpz note: it would be worthwhile for pbrd to represent + * its rules internally using the lib/pbr.h structures to help + * move toward a more common structure across pbrd, bgpd, and zebra. + */ +static bool pbr_encode_pbr_map_sequence(struct stream *s, + struct pbr_map_sequence *pbrms, + struct interface *ifp) +{ + + struct pbr_rule r; + uint8_t family; + + /* + * Opportunistic address family field is set when any of the IP + * address match/set fields is set, or when a NH/NHG is resolved. + * The value is needed by zebra for the underlying netlink + * messaging, particularly in delete operations, because it + * selects the rule database (IPv4 vs. IPv6). + * + * Historically the value has been encoded into any unused + * "match src/dst address" fields and picked off in zebra. + */ + family = AF_INET; + if (pbrms->family) + family = pbrms->family; + + if (pbrms->src) + assert(family == pbrms->src->family); + if (pbrms->dst) + assert(family == pbrms->dst->family); + + /* + * Convert struct pbr_map_sequence to canonical form + */ + memset(&r, 0, sizeof(r)); + r.seq = pbrms->seqno; + r.priority = pbrms->ruleno; + r.unique = pbrms->unique; + + r.family = pbrms->family; + + /* filter */ + r.filter.filter_bm = pbrms->filter_bm; + if (pbrms->src) + r.filter.src_ip = *pbrms->src; + else + r.filter.src_ip.family = family; + if (pbrms->dst) + r.filter.dst_ip = *pbrms->dst; + else + r.filter.dst_ip.family = family; + r.filter.src_port = pbrms->src_prt; + r.filter.dst_port = pbrms->dst_prt; + r.filter.pcp = pbrms->match_pcp; + r.filter.vlan_id = pbrms->match_vlan_id; + r.filter.vlan_flags = pbrms->match_vlan_flags; + r.filter.dsfield = pbrms->dsfield; + r.filter.fwmark = pbrms->mark; + r.filter.ip_proto = pbrms->ip_proto; + + r.filter.filter_bm = pbrms->filter_bm; + + /* actions */ + + r.action.flags = pbrms->action_bm; + + SET_FLAG(r.action.flags, PBR_ACTION_TABLE); /* always valid */ + + /* + * if the user does not use the command "set vrf name unchanged" + * then pbr_encode_pbr_map_sequence_vrf will not be called + */ + if (pbrms->vrf_unchanged || pbrms->vrf_lookup) + r.action.table = pbr_map_sequence_vrf(pbrms, ifp); + else if (pbrms->nhgrp_name) + r.action.table = pbr_nht_get_table(pbrms->nhgrp_name); + else if (pbrms->nhg) + r.action.table = pbr_nht_get_table(pbrms->internal_nhg_name); + else { + /* Not valid for install without table */ + return false; + } + + r.action.queue_id = pbrms->action_queue_id; + + r.action.src_ip = pbrms->action_src; + r.action.dst_ip = pbrms->action_dst; + + r.action.src_port = pbrms->action_src_port; + r.action.dst_port = pbrms->action_dst_port; + + r.action.dscp = pbrms->action_dscp; + r.action.ecn = pbrms->action_ecn; + + r.action.pcp = pbrms->action_pcp; + r.action.vlan_id = pbrms->action_vlan_id; + + strlcpy(r.ifname, ifp->name, sizeof(r.ifname)); + + zapi_pbr_rule_encode(s, &r); + + return true; +} + +bool pbr_send_pbr_map(struct pbr_map_sequence *pbrms, + struct pbr_map_interface *pmi, bool install, bool changed) +{ + struct pbr_map *pbrm = pbrms->parent; + struct stream *s; + uint64_t is_installed = (uint64_t)1 << pmi->install_bit; + + is_installed &= pbrms->installed; + + /* + * If we are installed and asked to do so again and the config + * has not changed, just return. + * + * If we are not installed and asked + * to delete just return. + */ + if (install && is_installed && !changed) + return false; + + if (!install && !is_installed) + return false; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_RULE_ADD : ZEBRA_RULE_DELETE, + VRF_DEFAULT); + + DEBUGD(&pbr_dbg_zebra, "%s: %s %s seq %u %d %s %u", __func__, + install ? "Installing" : "Deleting", pbrm->name, pbrms->seqno, + install, pmi->ifp->name, pmi->delete); + + if (pbr_encode_pbr_map_sequence(s, pbrms, pmi->ifp)) { + stream_putw_at(s, 0, stream_get_endp(s)); + zclient_send_message(zclient); + } else { + DEBUGD(&pbr_dbg_zebra, "%s: %s seq %u encode failed, skipped", + __func__, pbrm->name, pbrms->seqno); + } + + return true; +} diff --git a/pbrd/pbr_zebra.h b/pbrd/pbr_zebra.h new file mode 100644 index 0000000..5cbb1fd --- /dev/null +++ b/pbrd/pbr_zebra.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for PBR + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PBR_ZEBRA_H__ +#define __PBR_ZEBRA_H__ + +struct pbr_interface { + char mapname[100]; +}; + +extern struct event_loop *master; + +extern void pbr_zebra_init(void); +extern void pbr_zebra_destroy(void); + +extern void route_add(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, afi_t install_afi); +extern void route_delete(struct pbr_nexthop_group_cache *pnhgc, + afi_t install_afi); + +extern void pbr_send_rnh(struct nexthop *nhop, bool reg); + +extern bool pbr_send_pbr_map(struct pbr_map_sequence *pbrms, + struct pbr_map_interface *pmi, bool install, + bool changed); + +extern struct pbr_interface *pbr_if_new(struct interface *ifp); + +extern int pbr_ifp_create(struct interface *ifp); +extern int pbr_ifp_up(struct interface *ifp); +extern int pbr_ifp_down(struct interface *ifp); +extern int pbr_ifp_destroy(struct interface *ifp); + +/* Free the ifp->info pointer */ +extern void pbr_if_del(struct interface *ifp); + +#endif diff --git a/pbrd/subdir.am b/pbrd/subdir.am new file mode 100644 index 0000000..8a3bf31 --- /dev/null +++ b/pbrd/subdir.am @@ -0,0 +1,39 @@ +# +# pbrd +# + +if PBRD +noinst_LIBRARIES += pbrd/libpbr.a +sbin_PROGRAMS += pbrd/pbrd +vtysh_daemons += pbrd +man8 += $(MANBUILD)/frr-pbrd.8 +endif + +pbrd_libpbr_a_SOURCES = \ + pbrd/pbr_zebra.c \ + pbrd/pbr_vty.c \ + pbrd/pbr_map.c \ + pbrd/pbr_memory.c \ + pbrd/pbr_nht.c \ + pbrd/pbr_debug.c \ + pbrd/pbr_vrf.c \ + # end + +noinst_HEADERS += \ + pbrd/pbr_map.h \ + pbrd/pbr_memory.h \ + pbrd/pbr_nht.h \ + pbrd/pbr_vty.h \ + pbrd/pbr_zebra.h \ + pbrd/pbr_debug.h \ + pbrd/pbr_vrf.h \ + # end + +clippy_scan += \ + pbrd/pbr_debug.c \ + pbrd/pbr_vty.c \ + # end + +pbrd_pbrd_SOURCES = pbrd/pbr_main.c +pbrd_pbrd_LDADD = pbrd/libpbr.a lib/libfrr.la $(LIBCAP) + diff --git a/pceplib/.gitignore b/pceplib/.gitignore new file mode 100644 index 0000000..a82fe5a --- /dev/null +++ b/pceplib/.gitignore @@ -0,0 +1,26 @@ +pcep_pcc +test/pcep_msg_tests +test/pcep_msg_tests.log +test/pcep_msg_tests.trs +test/pcep_pcc_api_tests +test/pcep_pcc_api_tests.log +test/pcep_pcc_api_tests.trs +test/pcep_session_logic_tests +test/pcep_session_logic_tests.log +test/pcep_session_logic_tests.trs +test/pcep_socket_comm_tests +test/pcep_socket_comm_tests.log +test/pcep_socket_comm_tests.trs +test/pcep_timers_tests +test/pcep_timers_tests.log +test/pcep_timers_tests.trs +test/pcep_utils_tests +test/pcep_utils_tests.log +test/pcep_utils_tests.trs +test/valgrind.pcep_msg_tests.log +test/valgrind.pcep_pcc_api_tests.log +test/valgrind.pcep_session_logic_tests.log +test/valgrind.pcep_socket_comm_tests.log +test/valgrind.pcep_timers_tests.log +test/valgrind.pcep_utils_tests.log +../test-driver diff --git a/pceplib/pcep.h b/pceplib/pcep.h new file mode 100644 index 0000000..cb7019d --- /dev/null +++ b/pceplib/pcep.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + + +#ifndef PCEP_H_ +#define PCEP_H_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(linux) || defined(GNU_LINUX) + +#define ipv6_u __in6_u +#else +/* bsd family */ +#define ipv6_u __u6_addr +#ifdef __FreeBSD__ +#include +#else +#include +#endif /* __FreeBSD__ */ +#endif + +#include +#include +#include + +/* Cross-compilation seems to have trouble finding this */ +#if defined(TCP_MD5SIG_MAXKEYLEN) +#define PCEP_MD5SIG_MAXKEYLEN TCP_MD5SIG_MAXKEYLEN +#else +#define PCEP_MD5SIG_MAXKEYLEN 80 +#endif + +#endif diff --git a/pceplib/pcep_msg_encoding.h b/pceplib/pcep_msg_encoding.h new file mode 100644 index 0000000..9a1a660 --- /dev/null +++ b/pceplib/pcep_msg_encoding.h @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Definitions for encoding and decoding PCEP messages, objects, and TLVs. + */ + +#ifndef PCEP_ENCODING_H +#define PCEP_ENCODING_H + +#include + +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct pcep_versioning { + bool draft_ietf_pce_segment_routing_07; /* If false, use draft16 */ + /* As more draft versions are incorporated, add appropriate attributes + */ +}; + +#define MESSAGE_HEADER_LENGTH 4 +#define PCEP_MESSAGE_LENGTH 65535 +#define OBJECT_HEADER_LENGTH 4 +#define OBJECT_RO_SUBOBJ_HEADER_LENGTH 2 +#define TLV_HEADER_LENGTH 4 +#define LENGTH_1WORD sizeof(uint32_t) +#define LENGTH_2WORDS sizeof(uint32_t) * 2 +#define LENGTH_3WORDS sizeof(uint32_t) * 3 +#define LENGTH_4WORDS sizeof(uint32_t) * 4 +#define LENGTH_5WORDS sizeof(uint32_t) * 5 +#define LENGTH_6WORDS sizeof(uint32_t) * 6 +#define LENGTH_7WORDS sizeof(uint32_t) * 7 +#define LENGTH_8WORDS sizeof(uint32_t) * 8 +#define LENGTH_9WORDS sizeof(uint32_t) * 9 +#define LENGTH_10WORDS sizeof(uint32_t) * 10 +#define LENGTH_11WORDS sizeof(uint32_t) * 11 +#define LENGTH_12WORDS sizeof(uint32_t) * 12 +#define LENGTH_13WORDS sizeof(uint32_t) * 13 + +/* When iterating sub-objects or TLVs, limit to 10 in case corrupt data is + * received */ +#define MAX_ITERATIONS 10 + +struct pcep_versioning *create_default_pcep_versioning(void); +void destroy_pcep_versioning(struct pcep_versioning *versioning); + +/* + * Message encoding / decoding functions + */ + +/* Called before sending messages to encode the message to a byte buffer in + * Network byte order. This function will also encode all the objects and their + * TLVs in the message. The result will be stored in the encoded_message field + * in the pcep_message. Implemented in pcep-messages-encoding.c */ +void pcep_encode_message(struct pcep_message *message, + struct pcep_versioning *versioning); + +/* Decode the message header and return the message length. + * Returns < 0 for invalid message headers. */ +int32_t pcep_decode_validate_msg_header(const uint8_t *msg_buf); + +/* Decode the entire message */ +struct pcep_message *pcep_decode_message(const uint8_t *message_buffer); + + +/* + * Object encoding / decoding functions + */ + +/* Implemented in pcep-objects-encoding.c + * Encode the object in struct pcep_object_header* into the uint8_t *buf, + * and return the encoded object_length. */ +uint16_t pcep_encode_object(struct pcep_object_header *object_hdr, + struct pcep_versioning *versioning, uint8_t *buf); + +/* Implemented in pcep-objects-encoding.c + * Decode the object, including the TLVs (if any) and return the object. + * Returns object on success, NULL otherwise. */ +struct pcep_object_header *pcep_decode_object(const uint8_t *msg_buf); + +/* Internal util functions implemented in pcep-objects-encoding.c */ +void encode_ipv6(struct in6_addr *src_ipv6, uint32_t *dst); +void decode_ipv6(const uint32_t *src, struct in6_addr *dst_ipv6); +uint16_t normalize_pcep_tlv_length(uint16_t length); +bool pcep_object_has_tlvs(struct pcep_object_header *object_hdr); +uint16_t pcep_object_get_length_by_hdr(struct pcep_object_header *object_hdr); +uint16_t pcep_object_get_length(enum pcep_object_classes object_class, + enum pcep_object_types object_type); + + +/* + * TLV encoding / decoding functions + */ + +/* Implemented in pcep-tlv-encoding.c + * Encode the tlv in struct pcep_tlv_header* into the uint8_t *buf, + * and return the encoded tlv_length. */ +uint16_t pcep_encode_tlv(struct pcep_object_tlv_header *tlv_hdr, + struct pcep_versioning *versioning, uint8_t *buf); + +/* Decode the TLV in tlv_buf and return a pointer to the object */ +struct pcep_object_tlv_header *pcep_decode_tlv(const uint8_t *tlv_buf); + + +/* + * utils mainly for testing purposes + */ +bool validate_message_objects(struct pcep_message *msg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_messages.c b/pceplib/pcep_msg_messages.c new file mode 100644 index 0000000..eb0f679 --- /dev/null +++ b/pceplib/pcep_msg_messages.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message API. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +static struct pcep_message * +pcep_msg_create_common_with_obj_list(enum pcep_message_types msg_type, + double_linked_list *obj_list) +{ + struct pcep_message *message = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + memset(message, 0, sizeof(struct pcep_message)); + message->msg_header = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_message_header)); + memset(message->msg_header, 0, sizeof(struct pcep_message_header)); + message->msg_header->type = msg_type; + message->msg_header->pcep_version = PCEP_MESSAGE_HEADER_VERSION; + message->obj_list = ((obj_list == NULL) ? dll_initialize() : obj_list); + + return message; +} + +static struct pcep_message * +pcep_msg_create_common(enum pcep_message_types msg_type) +{ + return pcep_msg_create_common_with_obj_list(msg_type, NULL); +} + +struct pcep_message *pcep_msg_create_open(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_OPEN); + dll_append(message->obj_list, + pcep_obj_create_open(keepalive, deadtimer, sid, NULL)); + + return message; +} + +struct pcep_message * +pcep_msg_create_open_with_tlvs(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid, double_linked_list *tlv_list) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_OPEN); + dll_append(message->obj_list, + pcep_obj_create_open(keepalive, deadtimer, sid, tlv_list)); + + return message; +} + + +struct pcep_message * +pcep_msg_create_request(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv4 *endpoints, + double_linked_list *object_list) +{ + if ((rp == NULL) || (endpoints == NULL)) { + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREQ, object_list); + dll_prepend(message->obj_list, endpoints); + dll_prepend(message->obj_list, rp); + + return message; +} + +struct pcep_message * +pcep_msg_create_request_ipv6(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv6 *endpoints, + double_linked_list *object_list) +{ + if ((rp == NULL) || (endpoints == NULL)) { + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREQ, object_list); + dll_prepend(message->obj_list, endpoints); + dll_prepend(message->obj_list, rp); + + return message; +} + +struct pcep_message *pcep_msg_create_reply(struct pcep_object_rp *rp, + double_linked_list *object_list) +{ + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCREP, object_list); + + if (rp != NULL) { + dll_prepend(message->obj_list, rp); + } + + return message; +} + +struct pcep_message *pcep_msg_create_close(uint8_t reason) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_CLOSE); + dll_append(message->obj_list, pcep_obj_create_close(reason)); + + return message; +} + +struct pcep_message *pcep_msg_create_error(uint8_t error_type, + uint8_t error_value) +{ + struct pcep_message *message = pcep_msg_create_common(PCEP_TYPE_ERROR); + dll_append(message->obj_list, + pcep_obj_create_error(error_type, error_value)); + + return message; +} + +struct pcep_message * +pcep_msg_create_error_with_objects(uint8_t error_type, uint8_t error_value, + double_linked_list *object_list) +{ + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_ERROR, object_list); + dll_prepend(message->obj_list, + pcep_obj_create_error(error_type, error_value)); + + return message; +} + +struct pcep_message *pcep_msg_create_keepalive(void) +{ + return (pcep_msg_create_common(PCEP_TYPE_KEEPALIVE)); +} + +struct pcep_message * +pcep_msg_create_report(double_linked_list *state_report_object_list) +{ + return (state_report_object_list == NULL + ? NULL + : pcep_msg_create_common_with_obj_list( + PCEP_TYPE_REPORT, state_report_object_list)); +} + +struct pcep_message * +pcep_msg_create_update(double_linked_list *update_request_object_list) +{ + if (update_request_object_list == NULL) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update NULL update_request_object_list", + __func__); + return NULL; + } + + /* There must be at least 3 objects: + * These 3 are mandatory: SRP, LSP, and ERO. The ERO may be empty */ + if (update_request_object_list->num_entries < 3) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update there must be at least 3 update objects", + __func__); + return NULL; + } + + double_linked_list_node *node = update_request_object_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + + /* Check for the mandatory first SRP object */ + if (obj_hdr->object_class != PCEP_OBJ_CLASS_SRP) { + /* If the SRP object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=10 (SRP object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory first SRP object", + __func__); + return NULL; + } + + /* Check for the mandatory 2nd LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_LSP) { + /* If the LSP object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=8 (LSP object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory second LSP object", + __func__); + return NULL; + } + + /* Check for the mandatory 3rd ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_ERO) { + /* If the ERO object is missing, the receiving PCC MUST send a + * PCErr message with Error-type=6 (Mandatory Object missing) + * and Error-value=9 (ERO object missing). */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_update missing mandatory third ERO object", + __func__); + return NULL; + } + + return (pcep_msg_create_common_with_obj_list( + PCEP_TYPE_UPDATE, update_request_object_list)); +} + +struct pcep_message * +pcep_msg_create_initiate(double_linked_list *lsp_object_list) +{ + if (lsp_object_list == NULL) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate NULL update_request_object_list", + __func__); + return NULL; + } + + /* There must be at least 2 objects: SRP and LSP. */ + if (lsp_object_list->num_entries < 2) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate there must be at least 2 objects", + __func__); + return NULL; + } + + double_linked_list_node *node = lsp_object_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + + /* Check for the mandatory first SRP object */ + if (obj_hdr->object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate missing mandatory first SRP object", + __func__); + return NULL; + } + + /* Check for the mandatory 2nd LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + if (obj_hdr->object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_create_initiate missing mandatory second LSP object", + __func__); + return NULL; + } + + return (pcep_msg_create_common_with_obj_list(PCEP_TYPE_INITIATE, + lsp_object_list)); +} + +struct pcep_message *pcep_msg_create_notify(struct pcep_object_notify *notify, + double_linked_list *object_list) +{ + if (notify == NULL) { + pcep_log(LOG_INFO, + "%s: pcep_msg_create_notify NULL notify object", + __func__); + return NULL; + } + + struct pcep_message *message = pcep_msg_create_common_with_obj_list( + PCEP_TYPE_PCNOTF, object_list); + dll_prepend(message->obj_list, notify); + + return message; +} diff --git a/pceplib/pcep_msg_messages.h b/pceplib/pcep_msg_messages.h new file mode 100644 index 0000000..8876e0d --- /dev/null +++ b/pceplib/pcep_msg_messages.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message API. + */ + +#ifndef PCEP_MESSAGES_H +#define PCEP_MESSAGES_H + +#include +#include /* struct in_addr */ + +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum pcep_message_types { + PCEP_TYPE_OPEN = 1, + PCEP_TYPE_KEEPALIVE = 2, + PCEP_TYPE_PCREQ = 3, + PCEP_TYPE_PCREP = 4, + PCEP_TYPE_PCNOTF = 5, + PCEP_TYPE_ERROR = 6, + PCEP_TYPE_CLOSE = 7, + PCEP_TYPE_REPORT = 10, + PCEP_TYPE_UPDATE = 11, + PCEP_TYPE_INITIATE = 12, + PCEP_TYPE_START_TLS = 13, + PCEP_TYPE_MAX, +}; + +#define PCEP_MESSAGE_HEADER_VERSION 1 + +struct pcep_message_header { + uint8_t pcep_version; /* Current version is 1. */ + enum pcep_message_types + type; /* Defines message type: + OPEN/KEEPALIVE/PCREQ/PCREP/PCNOTF/ERROR/CLOSE */ +}; + +/* The obj_list is a double_linked_list of struct pcep_object_header pointers. + */ +struct pcep_message { + struct pcep_message_header *msg_header; + double_linked_list *obj_list; + uint8_t *encoded_message; + uint16_t encoded_message_length; +}; + + +/* + * Regarding memory usage: + * When creating messages, any objects and tlvs passed into these APIs will be + * free'd when the pcep_message is free'd. That includes the + * double_linked_list's. So, just create the objects and TLVs, put them in their + * double_linked_list's, and everything will be managed internally. The message + * will be deleted by pcep_msg_free_message() or pcep_msg_free_message_list() + * which, in turn will call one of: pcep_obj_free_object() and + * pcep_obj_free_tlv(). For received messages, call pcep_msg_free_message() to + * free them. + */ + +struct pcep_message *pcep_msg_create_open(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid); +struct pcep_message * +pcep_msg_create_open_with_tlvs(uint8_t keepalive, uint8_t deadtimer, + uint8_t sid, double_linked_list *tlv_list); +struct pcep_message * +pcep_msg_create_request(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv4 *endpoints, + double_linked_list *object_list); +struct pcep_message * +pcep_msg_create_request_ipv6(struct pcep_object_rp *rp, + struct pcep_object_endpoints_ipv6 *endpoints, + double_linked_list *object_list); +struct pcep_message *pcep_msg_create_reply(struct pcep_object_rp *rp, + double_linked_list *object_list); +struct pcep_message *pcep_msg_create_close(uint8_t reason); +struct pcep_message *pcep_msg_create_error(uint8_t error_type, + uint8_t error_value); +struct pcep_message *pcep_msg_create_error_with_objects( + uint8_t error_type, uint8_t error_value, + double_linked_list *object_list); /* include the offending objects */ +struct pcep_message *pcep_msg_create_keepalive(void); +struct pcep_message *pcep_msg_create_notify(struct pcep_object_notify *notify, + double_linked_list *object_list); + +/* Message defined in RFC 8231 section 6.1. Expecting double_linked_list of + * struct pcep_object_header* objects of type SRP, LSP, or path (ERO, Bandwidth, + * metrics, and RRO objects). */ +struct pcep_message * +pcep_msg_create_report(double_linked_list *state_report_object_list); +/* Message defined in RFC 8231. Expecting double_linked_list of at least 3 + * struct pcep_object_header* objects of type SRP, LSP, and path (ERO and + * intended-attribute-list). The ERO must be present, but may be empty if + * the PCE cannot find a valid path for a delegated LSP. */ +struct pcep_message * +pcep_msg_create_update(double_linked_list *update_request_object_list); +/* Message defined in RFC 8281. Expecting double_linked_list of at least 2 + * struct pcep_object_header* objects of type SRP and LSP for LSP deletion, and + * may also contain Endpoints, ERO and an attribute list for LSP creation. */ +struct pcep_message * +pcep_msg_create_initiate(double_linked_list *lsp_object_list); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_messages_encoding.c b/pceplib/pcep_msg_messages_encoding.c new file mode 100644 index 0000000..1b9f1c4 --- /dev/null +++ b/pceplib/pcep_msg_messages_encoding.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP messages. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +#define ANY_OBJECT 0 +#define NO_OBJECT -1 +#define NUM_CHECKED_OBJECTS 4 +/* It wont compile with this definition: + static const int + MANDATORY_MESSAGE_OBJECT_CLASSES[PCEP_TYPE_INITIATE+1][NUM_CHECKED_OBJECTS] + */ +static const enum pcep_object_classes MANDATORY_MESSAGE_OBJECT_CLASSES[13][4] = + { + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 0 */ + {PCEP_OBJ_CLASS_OPEN, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_OPEN = 1 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_KEEPALIVE = 2 */ + {PCEP_OBJ_CLASS_RP, PCEP_OBJ_CLASS_ENDPOINTS, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCREQ = 3 */ + {PCEP_OBJ_CLASS_RP, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCREP = 4 */ + {PCEP_OBJ_CLASS_NOTF, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_PCNOTF = 5 */ + {PCEP_OBJ_CLASS_ERROR, ANY_OBJECT, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_ERROR = 6 */ + {PCEP_OBJ_CLASS_CLOSE, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* PCEP_TYPE_CLOSE = 7 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 8 */ + {NO_OBJECT, NO_OBJECT, NO_OBJECT, + NO_OBJECT}, /* unsupported message ID = 9 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_REPORT = 10 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_UPDATE = 11 */ + {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, + ANY_OBJECT}, /* PCEP_TYPE_INITIATE = 12 */ +}; + +/* PCEP Message Common Header, According to RFC 5440 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Ver | Flags | Message-Type | Message-Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Ver (Version - 3 bits): PCEP version number. Current version is version 1. + * + * Flags (5 bits): No flags are currently defined. Unassigned bits are + * considered as reserved. They MUST be set to zero on transmission + * and MUST be ignored on receipt. + */ +void pcep_encode_message(struct pcep_message *message, + struct pcep_versioning *versioning) +{ + if (message == NULL) { + return; + } + + if (message->msg_header == NULL) { + return; + } + + /* Internal buffer used for the entire message. Later, once the entire + * length is known, memory will be allocated and this buffer will be + * copied. */ + uint8_t message_buffer[PCEP_MESSAGE_LENGTH] = {0}; + + /* Write the message header. The message header length will be + * written when the entire length is known. */ + uint32_t message_length = MESSAGE_HEADER_LENGTH; + uint16_t net_order_length = 0; + message_buffer[0] = (message->msg_header->pcep_version << 5) & 0xf0; + message_buffer[1] = message->msg_header->type; + + if (message->obj_list == NULL) { + net_order_length = htons(message_length); + memcpy(message_buffer + 2, &net_order_length, + sizeof(net_order_length)); + message->encoded_message = + pceplib_malloc(PCEPLIB_MESSAGES, message_length); + memcpy(message->encoded_message, message_buffer, + message_length); + message->encoded_message_length = message_length; + + return; + } + + /* Encode each of the objects */ + double_linked_list_node *node = message->obj_list->head; + for (; node != NULL; node = node->next_node) { + message_length += + pcep_encode_object(node->data, versioning, + message_buffer + message_length); + if (message_length >= PCEP_MESSAGE_LENGTH) { + message->encoded_message = NULL; + message->encoded_message_length = 0; + return; + } + } + + net_order_length = htons(message_length); + memcpy(message_buffer + 2, &net_order_length, sizeof(net_order_length)); + message->encoded_message = + pceplib_malloc(PCEPLIB_MESSAGES, message_length); + memcpy(message->encoded_message, message_buffer, message_length); + message->encoded_message_length = message_length; +} + +/* + * Decoding functions + */ + +/* Expecting Host byte ordered header */ +static bool validate_msg_header(uint8_t msg_version, uint8_t msg_flags, + uint8_t msg_type, uint16_t msg_length) +{ + /* Invalid message if the length is less than the header + * size or if its not a multiple of 4 */ + if (msg_length < MESSAGE_HEADER_LENGTH || (msg_length % 4) != 0) { + pcep_log(LOG_INFO, + "%s: Invalid PCEP message header length [%d]", + __func__, msg_length); + return false; + } + + if (msg_version != PCEP_MESSAGE_HEADER_VERSION) { + pcep_log( + LOG_INFO, + "%s: Invalid PCEP message header version [0x%x] expected version [0x%x]", + __func__, msg_version, PCEP_MESSAGE_HEADER_VERSION); + return false; + } + + if (msg_flags != 0) { + pcep_log(LOG_INFO, + "%s: Invalid PCEP message header flags [0x%x]", + __func__, msg_flags); + return false; + } + + switch (msg_type) { + /* Supported message types */ + case PCEP_TYPE_OPEN: + case PCEP_TYPE_KEEPALIVE: + case PCEP_TYPE_PCREQ: + case PCEP_TYPE_PCREP: + case PCEP_TYPE_PCNOTF: + case PCEP_TYPE_ERROR: + case PCEP_TYPE_CLOSE: + case PCEP_TYPE_REPORT: + case PCEP_TYPE_UPDATE: + case PCEP_TYPE_INITIATE: + break; + default: + pcep_log(LOG_INFO, "%s: Invalid PCEP message header type [%d]", + __func__, msg_type); + return false; + break; + } + + return true; +} + +/* Internal util function */ +static uint16_t pcep_decode_msg_header(const uint8_t *msg_buf, + uint8_t *msg_version, uint8_t *msg_flags, + uint8_t *msg_type) +{ + // Check RFC 5440 for version and flags position. + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + //| Ver | Flags | Message-Type | Message-Length | + //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + *msg_version = (msg_buf[0] >> 5) & 0x07; + *msg_flags = (msg_buf[0] & 0x1f); + *msg_type = msg_buf[1]; + uint16_t host_order_length; + memcpy(&host_order_length, msg_buf + 2, sizeof(host_order_length)); + return ntohs(host_order_length); +} + +/* Decode the message header and return the message length */ +int32_t pcep_decode_validate_msg_header(const uint8_t *msg_buf) +{ + uint8_t msg_version; + uint8_t msg_flags; + uint8_t msg_type; + uint32_t msg_length; + + msg_length = pcep_decode_msg_header(msg_buf, &msg_version, &msg_flags, + &msg_type); + + return ((validate_msg_header(msg_version, msg_flags, msg_type, + msg_length) + == false) + ? -1 + : (int32_t)msg_length); +} + +bool validate_message_objects(struct pcep_message *msg) +{ + if (msg->msg_header->type >= PCEP_TYPE_START_TLS) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unknown message type [%d]", + __func__, msg->msg_header->type); + return false; + } + + const enum pcep_object_classes *object_classes = + MANDATORY_MESSAGE_OBJECT_CLASSES[msg->msg_header->type]; + double_linked_list_node *node; + int index; + for (node = (msg->obj_list == NULL ? NULL : msg->obj_list->head), + index = 0; + index < NUM_CHECKED_OBJECTS; + index++, (node = (node == NULL ? NULL : node->next_node))) { + struct pcep_object_header *obj = + ((node == NULL) + ? NULL + : (struct pcep_object_header *)node->data); + + if ((int)object_classes[index] == NO_OBJECT) { + if (node != NULL) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unexpected object [%d] present", + __func__, obj->object_class); + return false; + } + } else if (object_classes[index] != ANY_OBJECT) { + if (node == NULL) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Expecting object in position [%d], but none received", + __func__, index); + return false; + } else if (object_classes[index] != obj->object_class) { + pcep_log( + LOG_INFO, + "%s: Rejecting received message: Unexpected Object Class received [%d]", + __func__, object_classes[index]); + return false; + } + } + } + + return true; +} + +struct pcep_message *pcep_decode_message(const uint8_t *msg_buf) +{ + uint8_t msg_version; + uint8_t msg_flags; + uint8_t msg_type; + uint16_t msg_length; + + msg_length = pcep_decode_msg_header(msg_buf, &msg_version, &msg_flags, + &msg_type); + if (msg_length == 0) { + pcep_log(LOG_INFO, "%s: Discarding empty message", __func__); + return NULL; + } + if (msg_length >= PCEP_MESSAGE_LENGTH) { + pcep_log(LOG_INFO, "%s: Discarding message too big", __func__); + return NULL; + } + + struct pcep_message *msg = + pceplib_calloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + + msg->msg_header = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_message_header)); + msg->msg_header->pcep_version = msg_version; + msg->msg_header->type = msg_type; + + msg->obj_list = dll_initialize(); + msg->encoded_message = pceplib_malloc(PCEPLIB_MESSAGES, msg_length); + memcpy(msg->encoded_message, msg_buf, msg_length); + msg->encoded_message_length = msg_length; + + uint16_t bytes_read = MESSAGE_HEADER_LENGTH; + while ((msg_length - bytes_read) >= OBJECT_HEADER_LENGTH) { + struct pcep_object_header *obj_hdr = + pcep_decode_object(msg_buf + bytes_read); + + if (obj_hdr == NULL) { + pcep_log(LOG_INFO, "%s: Discarding invalid message", + __func__); + pcep_msg_free_message(msg); + + return NULL; + } + + dll_append(msg->obj_list, obj_hdr); + bytes_read += obj_hdr->encoded_object_length; + } + + if (validate_message_objects(msg) == false) { + pcep_log(LOG_INFO, "%s: Discarding invalid message", __func__); + pcep_msg_free_message(msg); + + return NULL; + } + + return msg; +} + +struct pcep_versioning *create_default_pcep_versioning(void) +{ + struct pcep_versioning *versioning = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memset(versioning, 0, sizeof(struct pcep_versioning)); + + return versioning; +} + +void destroy_pcep_versioning(struct pcep_versioning *versioning) +{ + pceplib_free(PCEPLIB_INFRA, versioning); +} diff --git a/pceplib/pcep_msg_object_error_types.c b/pceplib/pcep_msg_object_error_types.c new file mode 100644 index 0000000..f42eaa4 --- /dev/null +++ b/pceplib/pcep_msg_object_error_types.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "pcep_msg_object_error_types.h" +#include "pcep_utils_logging.h" + +/* All of these values were copied from: + * https://www.iana.org/assignments/pcep/pcep.xhtml#pcep-error-object + * Which was last updated 2020-06-02 */ + +static const char *error_type_strings[] = { + "Reserved", + "PCEP session establishment failure", + "Capability not supported", + "Unknown Object", + "Not supported object", + "Policy violation", + "Mandatory Object missing", + "Synchronized path computation request missing", + "Unknown request reference", + "Attempt to establish a second PCEP session", + + "Reception of an invalid object", /* 10 */ + "Unrecognized EXRS subobject", + "Diffserv-aware TE error", + "BRPC procedure completion failure", + "Unassigned 14", + "Global Concurrent Optimization Error", + "P2MP Capability Error", + "P2MP END-POINTS Error", + "P2MP Fragmentation Error", + "Invalid Operation", + + "LSP State Synchronization Error", /* 20 */ + "Invalid traffic engineering path setup type", + "Unassigned 22", + "Bad parameter value", + "LSP instantiation error", + "PCEP StartTLS failure", + "Association Error", + "WSON RWA Error", + "H-PCE Error", + "Path computation failure", + "Unassigned 30"}; + +static const char *error_value_strings[MAX_ERROR_TYPE][MAX_ERROR_VALUE] = { + + /* 0 Reserved */ + {"Unassigned"}, + + /* 1 PCEP session establishment failure */ + { + "Unassigned", + "reception of an invalid Open message or a non Open message.", + "no Open message received before the expiration of the OpenWait timer", + "unacceptable and non negotiable session characteristics", + "unacceptable but negotiable session characteristics", + "reception of a second Open message with still unacceptable session characteristics", + "reception of a PCErr message proposing unacceptable session characteristics", + "No Keepalive or PCErr message received before the expiration of the KeepWait timer", + "PCEP version not supported", + }, + + /* 2 Capability not supported */ + {"Unassigned"}, + + /* 3 Unknown Object */ + { + "Unassigned", + "Unrecognized object class", + "Unrecognized object Type", + }, + + /* 4 Not supported object */ + { + "Unassigned", + "Not supported object class", + "Not supported object Type", + "Unassigned", + "Unsupported parameter", + "Unsupported network performance constraint", + "Bandwidth Object type 3 or 4 not supported", + "Unsupported endpoint type in END-POINTS Generalized Endpoint object type", + "Unsupported TLV present in END-POINTS Generalized Endpoint object type", + "Unsupported granularity in the RP object flags", + }, + + /* 5 Policy violation */ + { + "Unassigned", + "C bit of the METRIC object set (request rejected)", + "O bit of the RP object cleared (request rejected)", + "objective function not allowed (request rejected)", + "OF bit of the RP object set (request rejected)", + "Global concurrent optimization not allowed", + "Monitoring message supported but rejected due to policy violation", + "P2MP Path computation is not allowed", + "Not allowed network performance constraint", + }, + + /* 6 Mandatory Object missing */ + { + "Unassigned", + "RP object missing", + "RRO missing for a reoptimization request (R bit of the RP object set)", + "END-POINTS object missing", + "MONITORING object missing", + "Unassigned", + "Unassigned", + "Unassigned", + "LSP object missing", + "ERO object missing", + "SRP object missing", + "LSP-IDENTIFIERS TLV missing", + "LSP-DB-VERSION TLV missing", + "S2LS object missing", + "P2MP-LSP-IDENTIFIERS TLV missing", + "DISJOINTNESS-CONFIGURATION TLV missing", + }, + + /* 7 Synchronized path computation request missing */ + {"Unassigned"}, + + /* 8 Unknown request reference */ + {"Unassigned"}, + + /* 9 Attempt to establish a second PCEP session */ + {"Unassigned"}, + + /* 10 Reception of an invalid object */ + { + "Unassigned", + "reception of an object with P flag not set although the P-flag must be set according to this specification.", + "Bad label value", + "Unsupported number of SR-ERO subobjects", + "Bad label format", + "ERO mixes SR-ERO subobjects with other subobject types", + "Both SID and NAI are absent in the SR-ERO subobject", + "Both SID and NAI are absent in the SR-RRO subobject", + "SYMBOLIC-PATH-NAME TLV missing", + "MSD exceeds the default for the PCEP session", + "RRO mixes SR-RRO subobjects with other subobject types", + "Malformed object", + "Missing PCE-SR-CAPABILITY sub-TLV", + "Unsupported NAI Type in the SR-ERO/SR-RRO subobject", + "Unknown SID", + "NAI cannot be resolved to a SID", + "Could not find SRGB", + "SID index exceeds SRGB size", + "Could not find SRLB", + "SID index exceeds SRLB size", + "Inconsistent SIDs in SR-ERO / SR-RRO subobjects", + "MSD must be nonzero", + "Mismatch of O field in S2LS and LSP object", + "Incompatible OF codes in H-PCE", + "Bad Bandwidth Object type 3 (Generalized bandwidth) or 4 (Generalized bandwidth of existing TE-LSP for which a reoptimization is requested)", + "Unsupported LSP Protection Flags in PROTECTION-ATTRIBUTE TLV", + "Unsupported Secondary LSP Protection Flags in PROTECTION-ATTRIBUTE TLV", + "Unsupported Link Protection Type in PROTECTION-ATTRIBUTE TLV", + "LABEL-SET TLV present with 0 bit set but without R bit set in RP", + "Wrong LABEL-SET TLV present with 0 and L bit set", + "Wrong LABEL-SET with O bit set and wrong format", + "Missing GMPLS-CAPABILITY TLV", + "Incompatible OF code", + }, + + /* 11 Unrecognized EXRS subobject */ + {"Unassigned"}, + + /* 12 Diffserv-aware TE error */ + { + "Unassigned", + "Unsupported class-type", + "Invalid class-type", + "Class-Type and setup priority do not form a configured TE-class", + }, + + /* 13 BRPC procedure completion failure */ + { + "Unassigned", + "BRPC procedure not supported by one or more PCEs along the domain path", + }, + + /* 14 Unassigned */ + {"Unassigned"}, + + /* 15 Global Concurrent Optimization Error */ + { + "Unassigned", + "Insufficient memory", + "Global concurrent optimization not supported", + }, + + /* 16 P2MP Capability Error */ + { + "Unassigned", + "The PCE cannot satisfy the request due to insufficient memory", + "The PCE is not capable of P2MP computation", + }, + + /* 17 P2MP END-POINTS Error */ + { + "Unassigned", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 2", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 3", + "The PCE cannot satisfy the request due to no END-POINTS with leaf type 4", + "The PCE cannot satisfy the request due to inconsistent END-POINTS", + }, + + /* 18 P2MP Fragmentation Error */ + { + "Unassigned", + "Fragmented request failure", + "Fragmented Report failure", + "Fragmented Update failure", + "Fragmented Instantiation failure", + }, + + /* 19 Invalid Operation */ + { + "Unassigned", + "Attempted LSP Update Request for a non-delegated LSP. The PCEP-ERROR object is followed by the LSP object that identifies the LSP.", + "Attempted LSP Update Request if the stateful PCE capability was not advertised.", + "Attempted LSP Update Request for an LSP identified by an unknown PLSP-ID.", + "Unassigned", + "Attempted LSP State Report if active stateful PCE capability was not advertised.", + "PCE-initiated LSP limit reached", + "Delegation for PCE-initiated LSP cannot be revoked", + "Non-zero PLSP-ID in LSP Initiate Request", + "LSP is not PCE initiated", + "PCE-initiated operation-frequency limit reached", + "Attempted LSP State Report for P2MP if stateful PCE capability for P2MP was not advertised", + "Attempted LSP Update Request for P2MP if active stateful PCE capability for P2MP was not advertised", + "Attempted LSP Instantiation Request for P2MP if stateful PCE instantiation capability for P2MP was not advertised", + "Auto-Bandwidth capability was not advertised", + }, + + /* 20 LSP State Synchronization Error */ + { + "Unassigned", + "A PCE indicates to a PCC that it cannot process (an otherwise valid) LSP State Report. The PCEP- ERROR object is followed by the LSP object that identifies the LSP.", + "LSP-DB version mismatch.", + "Attempt to trigger synchronization before PCE trigger.", + "Attempt to trigger a synchronization when the PCE triggered synchronization capability has not been advertised.", + "A PCC indicates to a PCE that it cannot complete the State Synchronization.", + "Received an invalid LSP-DB Version Number.", + "Received an invalid Speaker Entity Identifier.", + }, + + /* 21 Invalid traffic engineering path setup type */ + { + "Unassigned", + "Unsupported path setup type", + "Mismatched path setup type", + }, + + /* 22 Unassigned */ + {"Unassigned"}, + + /* 23 Bad parameter value */ + { + "Unassigned", + "SYMBOLIC-PATH-NAME in use", + "Speaker identity included for an LSP that is not PCE initiated", + }, + + /* 24 LSP instantiation error */ + { + "Unassigned", + "Unacceptable instantiation parameters", + "Internal error", + "Signaling error", + }, + + /* 25 PCEP StartTLS failure */ + { + "Unassigned", + "Reception of StartTLS after any PCEP exchange", + "Reception of any other message apart from StartTLS, Open, or PCErr", + "Failure, connection without TLS is not possible", + "Failure, connection without TLS is possible", + "No StartTLS message (nor PCErr/Open) before StartTLSWait timer expiry", + }, + + /* 26 Association Error */ + { + "Unassigned", + "Association Type is not supported", + "Too many LSPs in the association group", + "Too many association groups", + "Association unknown", + "Operator-configured association information mismatch", + "Association information mismatch", + "Cannot join the association group", + "Association ID not in range", + "Tunnel ID or End points mismatch for Path Protection Association", + "Attempt to add another working/protection LSP for Path Protection Association", + "Protection type is not supported", + }, + + /* 27 WSON RWA Error */ + { + "Unassigned", + "Insufficient Memory", + "RWA computation Not supported", + "Syntactical Encoding error", + }, + + /* 28 H-PCE Error */ + { + "Unassigned", + "H-PCE Capability not advertised", + "Parent PCE Capability cannot be provided", + }, + + /* 29 Path computation failure */ + { + "Unassigned", + "Unacceptable request message", + "Generalized bandwidth value not supported", + "Label Set constraint could not be met", + "Label constraint could not be met", + } + + /* 30-255 Unassigned */ +}; + + +const char *get_error_type_str(enum pcep_error_type error_type) +{ + if (error_type < 0 || error_type >= MAX_ERROR_TYPE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_type_str: error_type [%d] out of range [0..%d]", + __func__, error_type, MAX_ERROR_TYPE); + + return NULL; + } + + return error_type_strings[error_type]; +} + +const char *get_error_value_str(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + if (error_type < 0 || error_type >= MAX_ERROR_TYPE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_value_str: error_type [%d] out of range [0..%d]", + __func__, error_type, MAX_ERROR_TYPE); + + return NULL; + } + + if (error_value < 0 || error_value >= MAX_ERROR_VALUE) { + pcep_log( + LOG_DEBUG, + "%s: get_error_value_str: error_value [%d] out of range [0..%d]", + __func__, error_value, MAX_ERROR_VALUE); + + return NULL; + } + + if (error_value_strings[error_type][error_value] == NULL) { + return "Unassigned"; + } + + return error_value_strings[error_type][error_value]; +} diff --git a/pceplib/pcep_msg_object_error_types.h b/pceplib/pcep_msg_object_error_types.h new file mode 100644 index 0000000..68e8b1a --- /dev/null +++ b/pceplib/pcep_msg_object_error_types.h @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + + +/* + * Error Object Type and Value definitions + */ + +#ifndef PCEP_OBJECT_ERROR_TYPES_H +#define PCEP_OBJECT_ERROR_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_ERROR_TYPE 30 +#define MAX_ERROR_VALUE 255 + +enum pcep_error_type { + PCEP_ERRT_SESSION_FAILURE = 1, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED = 2, + PCEP_ERRT_UNKNOW_OBJECT = 3, + PCEP_ERRT_NOT_SUPPORTED_OBJECT = 4, + PCEP_ERRT_POLICY_VIOLATION = 5, + PCEP_ERRT_MANDATORY_OBJECT_MISSING = 6, + PCEP_ERRT_SYNC_PC_REQ_MISSING = 7, + PCEP_ERRT_UNKNOWN_REQ_REF = 8, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION = 9, + PCEP_ERRT_RECEPTION_OF_INV_OBJECT = 10, + + PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ = 11, + PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR = 12, + PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR = 13, + PCEP_ERRT_UNASSIGNED14 = 14, + PCEP_ERRT_GLOBAL_CONCURRENT_ERROR = 15, + PCEP_ERRT_P2PMP_CAP_ERROR = 16, + PCEP_ERRT_P2P_ENDPOINTS_ERROR = 17, + PCEP_ERRT_P2P_FRAGMENTATION_ERROR = 18, + PCEP_ERRT_INVALID_OPERATION = 19, + PCEP_ERRT_LSP_STATE_SYNC_ERROR = 20, + + PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE = 21, + PCEP_ERRT_UNASSIGNED22 = 22, + PCEP_ERRT_BAD_PARAMETER_VALUE = 23, + PCEP_ERRT_LSP_INSTANTIATE_ERROR = 24, + PCEP_ERRT_START_TLS_FAILURE = 25, + PCEP_ERRT_ASSOCIATION_ERROR = 26, + PCEP_ERRT_WSON_RWA_ERROR = 27, + PCEP_ERRT_H_PCE_ERROR = 28, + PCEP_ERRT_PATH_COMP_FAILURE = 29, + PCEP_ERRT_UNASSIGNED30 = 30 /* 30 - 255 Unassigned */ +}; + +enum pcep_error_value { + /* Error Value for Error Types that do not use an Error Value: + * PCEP_ERRT_CAPABILITY_NOT_SUPPORTED=2 + * PCEP_ERRT_SYNC_PC_REQ_MISSING=7 + * PCEP_ERRT_UNKNOWN_REQ_REF=8 + * PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION=9 + * PCEP_ERRT_UNRECOGNIZED_EXRS_SUBOBJ=11 */ + PCEP_ERRV_UNASSIGNED = 0, + + /* Error Values for PCEP_ERRT_SESSION_FAILURE=1 */ + PCEP_ERRV_RECVD_INVALID_OPEN_MSG = 1, + PCEP_ERRV_OPENWAIT_TIMED_OUT = 2, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NO_NEG = 3, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG = 4, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE = 5, + PCEP_ERRV_RECVD_PCERR = 6, + PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT = 7, + PCEP_ERRV_PCEP_VERSION_NOT_SUPPORTED = 8, + + /* Error Values for PCEP_ERRT_UNKNOW_OBJECT=3 */ + PCEP_ERRV_UNREC_OBJECT_CLASS = 1, + PCEP_ERRV_UNREC_OBJECT_TYPE = 2, + + /* Error Values for PCEP_ERRT_NOT_SUPPORTED_OBJECT=4 */ + PCEP_ERRV_NOT_SUPPORTED_OBJECT_CLASS = 1, + PCEP_ERRV_NOT_SUPPORTED_OBJECT_TYPE = 2, + /* 3: Unassigned */ + PCEP_ERRV_UNSUPPORTED_PARAM = 4, + PCEP_ERRV_UNSUPPORTED_NW_PERF_CONSTRAINT = 5, + PCEP_ERRV_NOT_SUPPORTED_BW_OBJECT_3_4 = 6, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TYPE = 7, + PCEP_ERRV_UNSUPPORTED_ENDPOINT_TLV = 8, + PCEP_ERRV_UNSUPPORTED_RP_FLAG_GRANULARITY = 9, + + /* Error Values for PCEP_ERRT_POLICY_VIOLATION=5 */ + PCEP_ERRV_C_BIT_SET_IN_METRIC_OBJECT = 1, + PCEP_ERRV_O_BIT_CLEARD_IN_RP_OBJECT = 2, + PCEP_ERRV_OBJECTIVE_FUNC_NOT_ALLOWED = 3, + PCEP_ERRV_RP_OF_BIT_SET = 4, + PCEP_ERRV_GLOBAL_CONCURRENCY_NOT_ALLOWED = 5, + PCEP_ERRV_MONITORING_MSG_REJECTED = 6, + PCEP_ERRV_P2MP_PATH_COMP_NOT_ALLOWED = 7, + PCEP_ERRV_UNALLOWED_NW_PERF_CONSTRAINT = 8, + + /* Error Values for PCEP_ERRT_MANDATORY_OBJECT_MISSING=6 */ + PCEP_ERRV_RP_OBJECT_MISSING = 1, + PCEP_ERRV_RRO_OBJECT_MISSING_FOR_REOP = 2, + PCEP_ERRV_EP_OBJECT_MISSING = 3, + PCEP_ERRV_MONITOR_OBJECT_MISSING = 4, + /* 5 - 7 Unassigned */ + PCEP_ERRV_LSP_OBJECT_MISSING = 8, + PCEP_ERRV_ERO_OBJECT_MISSING = 9, + PCEP_ERRV_SRP_OBJECT_MISSING = 10, + PCEP_ERRV_LSP_ID_TLV_MISSING = 11, + PCEP_ERRV_LSP_DB_TLV_MISSING = 12, + PCEP_ERRV_S2LS_OBJECT_MISSING = 13, + PCEP_ERRV_P2MP_LSP_ID_TLV_MISSING = 14, + PCEP_ERRV_DISJOINTED_CONF_TLV_MISSING = 15, + + /* Error Values for PCEP_ERRT_RECEPTION_OF_INV_OBJECT=10 */ + PCEP_ERRV_P_FLAG_NOT_CORRECT_IN_OBJECT = 1, + PCEP_ERRV_BAD_LABEL_VALUE = 2, + PCEP_ERRV_UNSUPPORTED_NUM_SR_ERO_SUBOBJECTS = 3, + PCEP_ERRV_BAD_LABEL_FORMAT = 4, + PCEP_ERRV_ERO_SR_ERO_MIX = 5, + PCEP_ERRV_SR_ERO_SID_NAI_ABSENT = 6, + PCEP_ERRV_SR_RRO_SID_NAI_ABSENT = 7, + PCEP_ERRV_SYMBOLIC_PATH_NAME_TLV_MISSING = 8, + PCEP_ERRV_MSD_EXCEEDS_PCEP_SESSION_MAX = 9, + + PCEP_ERRV_RRO_SR_RRO_MIX = 10, + PCEP_ERRV_MALFORMED_OBJECT = 11, + PCEP_ERRV_MISSING_PCE_SR_CAP_TLV = 12, + PCEP_ERRV_UNSUPPORTED_NAI = 13, + PCEP_ERRV_UNKNOWN_SID = 14, + PCEP_ERRV_CANNOT_RESOLVE_NAI_TO_SID = 15, + PCEP_ERRV_COULD_NOT_FIND_SRGB = 16, + PCEP_ERRV_SID_EXCEEDS_SRGB = 17, + PCEP_ERRV_COULD_NOT_FIND_SRLB = 18, + PCEP_ERRV_SID_EXCEEDS_SRLB = 19, + + PCEP_ERRV_INCONSISTENT_SID = 20, + PCEP_ERRV_MSD_MUST_BE_NONZERO = 21, + PCEP_ERRV_MISMATCH_O_S2LS_LSP = 22, + PCEP_ERRV_INCOMPATIBLE_H_PCE_OF = 23, + PCEP_ERRV_BAD_BANDWIDTH_TYPE_3_4 = 24, + PCEP_ERRV_UNSUPPORTED_LSP_PROT_FLAGS = 25, + PCEP_ERRV_UNSUPPORTED_2ND_LSP_PROT_FLAGS = 26, + PCEP_ERRV_UNSUPPORTED_LINK_PROT_TYPE = 27, + PCEP_ERRV_LABEL_SET_TLV_NO_RP_R = 28, + PCEP_ERRV_WRONG_LABEL_SET_TLV_O_L_SET = 29, + + PCEP_ERRV_WRONG_LABEL_SET_O_SET = 30, + PCEP_ERRV_MISSING_GMPLS_CAP_TLV = 31, + PCEP_ERRV_INCOMPATIBLE_OF_CODE = 32, + + /* PCEP_ERRT_DIFFSERV_AWARE_TE_ERROR = 12 */ + PCEP_ERRV_UNSUPPORTED_CLASS_TYPE = 1, + PCEP_ERRV_INVALID_CLASS_TYPE = 2, + PCEP_ERRV_CLASS_SETUP_TYPE_NOT_TE_CLASS = 3, + + /* PCEP_ERRT_BRPC_PROC_COMPLETION_ERROR = 13 */ + PCEP_ERRV_BRPC_PROC_NOT_SUPPORTED = 1, + + /* PCEP_ERRT_UNASSIGNED14 = 14 */ + + /* PCEP_ERRT_GLOBAL_CONCURRENT_ERROR = 15 */ + PCEP_ERRV_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_GLOBAL_CONCURRENT_OPT_NOT_SUPPORTED = 2, + + /* PCEP_ERRT_P2PMP_CAP_ERROR = 16 */ + PCEP_ERRV_PCE_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_PCE_NOT_CAPABLE_P2MP_COMP = 2, + + /* PCEP_ERRT_P2P_ENDPOINTS_ERROR = 17 */ + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE2 = 1, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE3 = 2, + PCEP_ERRV_NO_EP_WITH_LEAF_TYPE4 = 3, + PCEP_ERRV_INCONSITENT_EP = 4, + + /* PCEP_ERRT_P2P_FRAGMENTATION_ERROR = 18 */ + PCEP_ERRV_FRAG_REQUEST_FAILURE = 1, + PCEP_ERRV_FRAG_REPORT_FAILURE = 2, + PCEP_ERRV_FRAG_UPDATE_FAILURE = 3, + PCEP_ERRV_FRAG_INSTANTIATION_FAILURE = 4, + + /* Error Values for PCEP_ERRT_INVALID_OPERATION=19 */ + PCEP_ERRV_LSP_UPDATE_FOR_NON_DELEGATED_LSP = 1, + PCEP_ERRV_LSP_UPDATE_NON_ADVERTISED_PCE = 2, + PCEP_ERRV_LSP_UPDATE_UNKNOWN_PLSP_ID = 3, + /* 4: unassigned */ + PCEP_ERRV_LSP_REPORT_NON_ADVERTISED_PCE = 5, + PCEP_ERRV_PCE_INIT_LSP_LIMIT_REACHED = 6, + PCEP_ERRV_PCE_INIT_LSP_DELEGATION_CANT_REVOKE = 7, + PCEP_ERRV_LSP_INIT_NON_ZERO_PLSP_ID = 8, + PCEP_ERRV_LSP_NOT_PCE_INITIATED = 9, + PCEP_ERRV_PCE_INIT_OP_FREQ_LIMIT_REACHED = 10, + PCEP_ERRV_LSP_REPORT_P2MP_NOT_ADVERTISED = 11, + PCEP_ERRV_LSP_UPDATE_P2MP_NOT_ADVERTISED = 12, + PCEP_ERRV_LSP_INSTANTIATION_P2MP_NOT_ADVERTISED = 13, + PCEP_ERRV_AUTO_BW_CAP_NOT_ADVERTISED = 14, + + /* Error Values for PCEP_ERRT_LSP_STATE_SYNC_ERROR=20 */ + PCEP_ERRV_PCE_CANT_PROCESS_LSP_REPORT = 1, + PCEP_ERRV_LSP_DB_VERSION_MISMATCH = 2, + PCEP_ERRV_TRIGGER_ATTEMPT_BEFORE_PCE_TRIGGER = 3, + PCEP_ERRV_TRIGGER_ATTEMPT_NO_PCE_TRIGGER_CAP = 4, + PCEP_ERRV_PCC_CANT_COMPLETE_STATE_SYNC = 5, + PCEP_ERRV_INVALID_LSP_DB_VERSION_NUMBER = 6, + PCEP_ERRV_INVALID_SPEAKER_ENTITY_ID = 7, + + /* PCEP_ERRT_INVALID_TE_PATH_SETUP_TYPE = 21 */ + PCEP_ERRV_UNSUPPORTED_PATH_SETUP_TYPE = 1, + PCEP_ERRV_MISMATCHED_PATH_SETUP_TYPE = 2, + + /* PCEP_ERRT_UNASSIGNED22 = 22 */ + + /* Error Values for PCEP_ERRT_BAD_PARAMETER_VALUE=23 */ + PCEP_ERRV_SYMBOLIC_PATH_NAME_IN_USE = 1, + PCEP_ERRV_LSP_SPEAKER_ID_NOT_PCE_INITIATED = 2, + + /* Error Values for PCEP_ERRT_LSP_INSTANTIATE_ERROR=24 */ + PCEP_ERRV_UNACCEPTABLE_INSTANTIATE_ERROR = 1, + PCEP_ERRV_INTERNAL_ERROR = 2, + PCEP_ERRV_SIGNALLING_ERROR = 3, + + /* PCEP_ERRT_START_TLS_FAILURE = 25 */ + PCEP_ERRV_START_TLS_AFTER_PCEP_EXCHANGE = 1, + PCEP_ERRV_MSG_NOT_START_TLS_OPEN_ERROR = 2, + PCEP_ERRV_CONNECTION_WO_TLS_NOT_POSSIBLE = 3, + PCEP_ERRV_CONNECTION_WO_TLS_IS_POSSIBLE = 4, + PCEP_ERRV_NO_START_TLS_BEFORE_START_TLS_WAIT_TIMER = 5, + + /* PCEP_ERRT_ASSOCIATION_ERROR = 26 */ + PCEP_ERRV_ASSOC_TYPE_NOT_SUPPORTED = 1, + PCEP_ERRV_TOO_MANY_LSPS_IN_ASSOC_GRP = 2, + PCEP_ERRV_TOO_MANY_ASSOC_GROUPS = 3, + PCEP_ERRV_ASSOCIATION_UNKNOWN = 4, + PCEP_ERRV_OP_CONF_ASSOC_INFO_MISMATCH = 5, + PCEP_ERRV_ASSOC_INFO_MISMATCH = 6, + PCEP_ERRV_CANNOT_JOIN_ASSOC_GROUP = 7, + PCEP_ERRV_ASSOC_ID_NOT_IN_RANGE = 8, + PCEP_ERRV_TUNNEL_EP_MISMATCH_PATH_PROT_ASSOC = 9, + PCEP_ERRV_ATTEMPTED_ADD_LSP_PATH_PROT_ASSOC = 10, + PCEP_ERRV_PROTECTION_TYPE_NOT_SUPPORTED = 11, + + /* PCEP_ERRT_WSON_RWA_ERROR = 27 */ + PCEP_ERRV_RWA_INSUFFICIENT_MEMORY = 1, + PCEP_ERRV_RWA_COMP_NOT_SUPPORTED = 2, + PCEP_ERRV_SYNTAX_ENC_ERROR = 3, + + /* PCEP_ERRT_H_PCE_ERROR = 28 */ + PCEP_ERRV_H_PCE_CAP_NOT_ADVERTISED = 1, + PCEP_ERRV_PARENT_PCE_CAP_CANT_BE_PROVIDED = 2, + + /* PCEP_ERRT_PATH_COMP_FAILURE = 29 */ + PCEP_ERRV_UNACCEPTABLE_REQUEST_MSG = 1, + PCEP_ERRV_GENERALIZED_BW_VAL_NOT_SUPPORTED = 2, + PCEP_ERRV_LABEL_SET_CONSTRAINT_COULD_NOT_BE_MET = 3, + PCEP_ERRV_LABEL_CONSTRAINT_COULD_NOT_BE_MET = 4, + +}; + +const char *get_error_type_str(enum pcep_error_type error_type); +const char *get_error_value_str(enum pcep_error_type error_type, + enum pcep_error_value error_value); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_objects.c b/pceplib/pcep_msg_objects.c new file mode 100644 index 0000000..e7af6b3 --- /dev/null +++ b/pceplib/pcep_msg_objects.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message object API. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* Internal common function used to create a pcep_object and populate the header + */ +static struct pcep_object_header *pcep_obj_create_common_with_tlvs( + uint8_t obj_length, enum pcep_object_classes object_class, + enum pcep_object_types object_type, double_linked_list *tlv_list) +{ + uint8_t *buffer = pceplib_malloc(PCEPLIB_MESSAGES, obj_length); + memset(buffer, 0, obj_length); + + /* The flag_p and flag_i flags will be set externally */ + struct pcep_object_header *hdr = (struct pcep_object_header *)buffer; + hdr->object_class = object_class; + hdr->object_type = object_type; + hdr->tlv_list = tlv_list; + + return hdr; +} + +static struct pcep_object_header * +pcep_obj_create_common(uint8_t obj_length, + enum pcep_object_classes object_class, + enum pcep_object_types object_type) +{ + return pcep_obj_create_common_with_tlvs(obj_length, object_class, + object_type, NULL); +} + +struct pcep_object_open *pcep_obj_create_open(uint8_t keepalive, + uint8_t deadtimer, uint8_t sid, + double_linked_list *tlv_list) +{ + struct pcep_object_open *open = + (struct pcep_object_open *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_open), PCEP_OBJ_CLASS_OPEN, + PCEP_OBJ_TYPE_OPEN, tlv_list); + + open->open_version = + PCEP_OBJECT_OPEN_VERSION; /* PCEP version. Current version is 1 + /No flags are currently defined. */ + open->open_keepalive = + keepalive; /* Maximum period of time between two consecutive + PCEP messages sent by the sender. */ + open->open_deadtimer = deadtimer; /* Specifies the amount of time before + closing the session down. */ + open->open_sid = sid; /* PCEP session number that identifies the current + session. */ + + return open; +} + +struct pcep_object_rp *pcep_obj_create_rp(uint8_t priority, bool flag_r, + bool flag_b, bool flag_s, + bool flag_of, uint32_t reqid, + double_linked_list *tlv_list) +{ + if (priority > OBJECT_RP_MAX_PRIORITY) { + pcep_log( + LOG_INFO, + "%s: Error creating RP object, invalid priority [%d], max priority [%d].", + __func__, priority, OBJECT_RP_MAX_PRIORITY); + return NULL; + } + + struct pcep_object_rp *obj = + (struct pcep_object_rp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_rp), PCEP_OBJ_CLASS_RP, + PCEP_OBJ_TYPE_RP, tlv_list); + + obj->priority = priority; + obj->flag_reoptimization = flag_r; + obj->flag_bidirectional = flag_b; + obj->flag_strict = flag_s; + obj->flag_of = flag_of; + obj->request_id = reqid; + + return obj; +} + +struct pcep_object_notify * +pcep_obj_create_notify(enum pcep_notification_types notification_type, + enum pcep_notification_values notification_value) +{ + struct pcep_object_notify *obj = + (struct pcep_object_notify *)pcep_obj_create_common( + sizeof(struct pcep_object_notify), PCEP_OBJ_CLASS_NOTF, + PCEP_OBJ_TYPE_NOTF); + + obj->notification_type = notification_type; + obj->notification_value = notification_value; + + return obj; +} + +struct pcep_object_nopath * +pcep_obj_create_nopath(uint8_t ni, bool flag_c, + enum pcep_nopath_tlv_err_codes error_code) +{ + struct pcep_object_tlv_nopath_vector *tlv = + pcep_tlv_create_nopath_vector(error_code); + double_linked_list *tlv_list = dll_initialize(); + dll_append(tlv_list, tlv); + + struct pcep_object_nopath *obj = + (struct pcep_object_nopath *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_nopath), + PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH, tlv_list); + + obj->ni = ni; + obj->flag_c = flag_c; + obj->err_code = error_code; + + return obj; +} + +struct pcep_object_association_ipv4 * +pcep_obj_create_association_ipv4(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in_addr src) +{ + struct pcep_object_association_ipv4 *obj = + (struct pcep_object_association_ipv4 *)pcep_obj_create_common( + sizeof(struct pcep_object_association_ipv4), + PCEP_OBJ_CLASS_ASSOCIATION, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4); + + obj->R_flag = r_flag; + obj->association_type = association_type; + obj->association_id = association_id; + obj->src = src; + + return obj; +} +struct pcep_object_association_ipv6 * +pcep_obj_create_association_ipv6(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in6_addr src) +{ + struct pcep_object_association_ipv6 *obj = + (struct pcep_object_association_ipv6 *)pcep_obj_create_common( + sizeof(struct pcep_object_association_ipv6), + PCEP_OBJ_CLASS_ASSOCIATION, + PCEP_OBJ_TYPE_ASSOCIATION_IPV6); + + obj->R_flag = r_flag; + obj->association_type = association_type; + obj->association_id = association_id; + obj->src = src; + + return obj; +} +struct pcep_object_endpoints_ipv4 * +pcep_obj_create_endpoint_ipv4(const struct in_addr *src_ipv4, + const struct in_addr *dst_ipv4) +{ + if (src_ipv4 == NULL || dst_ipv4 == NULL) { + return NULL; + } + + struct pcep_object_endpoints_ipv4 *obj = + (struct pcep_object_endpoints_ipv4 *)pcep_obj_create_common( + sizeof(struct pcep_object_endpoints_ipv4), + PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + + obj->src_ipv4.s_addr = src_ipv4->s_addr; + obj->dst_ipv4.s_addr = dst_ipv4->s_addr; + + return obj; +} + +struct pcep_object_endpoints_ipv6 * +pcep_obj_create_endpoint_ipv6(const struct in6_addr *src_ipv6, + const struct in6_addr *dst_ipv6) +{ + if (src_ipv6 == NULL || dst_ipv6 == NULL) { + return NULL; + } + + struct pcep_object_endpoints_ipv6 *obj = + (struct pcep_object_endpoints_ipv6 *)pcep_obj_create_common( + sizeof(struct pcep_object_endpoints_ipv6), + PCEP_OBJ_CLASS_ENDPOINTS, PCEP_OBJ_TYPE_ENDPOINT_IPV6); + + memcpy(&obj->src_ipv6, src_ipv6, sizeof(struct in6_addr)); + memcpy(&obj->dst_ipv6, dst_ipv6, sizeof(struct in6_addr)); + + return obj; +} + +struct pcep_object_bandwidth *pcep_obj_create_bandwidth(float bandwidth) +{ + struct pcep_object_bandwidth *obj = + (struct pcep_object_bandwidth *)pcep_obj_create_common( + sizeof(struct pcep_object_bandwidth), + PCEP_OBJ_CLASS_BANDWIDTH, PCEP_OBJ_TYPE_BANDWIDTH_REQ); + + obj->bandwidth = bandwidth; + + return obj; +} + +struct pcep_object_metric *pcep_obj_create_metric(enum pcep_metric_types type, + bool flag_b, bool flag_c, + float value) +{ + struct pcep_object_metric *obj = + (struct pcep_object_metric *)pcep_obj_create_common( + sizeof(struct pcep_object_metric), + PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC); + + obj->flag_b = flag_b; + obj->flag_c = flag_c; + obj->type = type; + obj->value = value; + + return obj; +} + +struct pcep_object_lspa * +pcep_obj_create_lspa(uint32_t exclude_any, uint32_t include_any, + uint32_t include_all, uint8_t setup_priority, + uint8_t holding_priority, bool flag_local_protection) +{ + struct pcep_object_lspa *obj = + (struct pcep_object_lspa *)pcep_obj_create_common( + sizeof(struct pcep_object_lspa), PCEP_OBJ_CLASS_LSPA, + PCEP_OBJ_TYPE_LSPA); + + obj->lspa_exclude_any = exclude_any; + obj->lspa_include_any = include_any; + obj->lspa_include_all = include_all; + obj->setup_priority = setup_priority; + obj->holding_priority = holding_priority; + obj->flag_local_protection = flag_local_protection; + + return obj; +} + +struct pcep_object_svec * +pcep_obj_create_svec(bool srlg, bool node, bool link, + double_linked_list *request_id_list) +{ + if (request_id_list == NULL) { + return NULL; + } + + struct pcep_object_svec *obj = + (struct pcep_object_svec *)pcep_obj_create_common( + sizeof(struct pcep_object_svec), PCEP_OBJ_CLASS_SVEC, + PCEP_OBJ_TYPE_SVEC); + + obj->flag_srlg_diverse = srlg; + obj->flag_node_diverse = node; + obj->flag_link_diverse = link; + obj->request_id_list = request_id_list; + + return obj; +} + +struct pcep_object_error * +pcep_obj_create_error(enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + struct pcep_object_error *obj = + (struct pcep_object_error *)pcep_obj_create_common( + sizeof(struct pcep_object_error), PCEP_OBJ_CLASS_ERROR, + PCEP_OBJ_TYPE_ERROR); + + obj->error_type = error_type; + obj->error_value = error_value; + + return obj; +} + +struct pcep_object_close *pcep_obj_create_close(enum pcep_close_reason reason) +{ + struct pcep_object_close *obj = + (struct pcep_object_close *)pcep_obj_create_common( + sizeof(struct pcep_object_close), PCEP_OBJ_CLASS_CLOSE, + PCEP_OBJ_TYPE_CLOSE); + + obj->reason = reason; + + return obj; +} + +struct pcep_object_srp *pcep_obj_create_srp(bool lsp_remove, + uint32_t srp_id_number, + double_linked_list *tlv_list) +{ + struct pcep_object_srp *obj = + (struct pcep_object_srp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_srp), PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_TYPE_SRP, tlv_list); + + obj->flag_lsp_remove = lsp_remove; + obj->srp_id_number = srp_id_number; + + return obj; +} + +struct pcep_object_lsp * +pcep_obj_create_lsp(uint32_t plsp_id, enum pcep_lsp_operational_status status, + bool c_flag, bool a_flag, bool r_flag, bool s_flag, + bool d_flag, double_linked_list *tlv_list) +{ + /* The plsp_id is only 20 bits */ + if (plsp_id > MAX_PLSP_ID) { + pcep_log( + LOG_INFO, + "%s: pcep_obj_create_lsp invalid plsp_id [%d] max value [%d]", + __func__, plsp_id, MAX_PLSP_ID); + return NULL; + } + + /* The status is only 3 bits */ + if (status > MAX_LSP_STATUS) { + pcep_log( + LOG_INFO, + "%s: pcep_obj_create_lsp invalid status [%d] max value [%d]", + __func__, plsp_id, MAX_PLSP_ID); + return NULL; + } + + struct pcep_object_lsp *obj = + (struct pcep_object_lsp *)pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_lsp), PCEP_OBJ_CLASS_LSP, + PCEP_OBJ_TYPE_LSP, tlv_list); + + obj->plsp_id = plsp_id; + obj->operational_status = status; + obj->flag_c = c_flag; + obj->flag_a = a_flag; + obj->flag_r = r_flag; + obj->flag_s = s_flag; + obj->flag_d = d_flag; + + return obj; +} + +struct pcep_object_vendor_info * +pcep_obj_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_spec_info) +{ + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)pcep_obj_create_common( + sizeof(struct pcep_object_vendor_info), + PCEP_OBJ_CLASS_VENDOR_INFO, PCEP_OBJ_TYPE_VENDOR_INFO); + + obj->enterprise_number = enterprise_number; + obj->enterprise_specific_info = enterprise_spec_info; + + return obj; +} + +struct pcep_object_inter_layer * +pcep_obj_create_inter_layer(bool flag_i, bool flag_m, bool flag_t) +{ + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)pcep_obj_create_common( + sizeof(struct pcep_object_inter_layer), + PCEP_OBJ_CLASS_INTER_LAYER, PCEP_OBJ_TYPE_INTER_LAYER); + + obj->flag_i = flag_i; + obj->flag_m = flag_m; + obj->flag_t = flag_t; + + return obj; +} + +struct pcep_object_switch_layer * +pcep_obj_create_switch_layer(double_linked_list *switch_layer_rows) +{ + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)pcep_obj_create_common( + sizeof(struct pcep_object_switch_layer), + PCEP_OBJ_CLASS_SWITCH_LAYER, + PCEP_OBJ_TYPE_SWITCH_LAYER); + + obj->switch_layer_rows = switch_layer_rows; + + return obj; +} + +struct pcep_object_req_adap_cap * +pcep_obj_create_req_adap_cap(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding) +{ + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)pcep_obj_create_common( + sizeof(struct pcep_object_req_adap_cap), + PCEP_OBJ_CLASS_REQ_ADAP_CAP, + PCEP_OBJ_TYPE_REQ_ADAP_CAP); + + obj->switching_capability = sw_cap; + obj->encoding = encoding; + + return obj; +} + +struct pcep_object_server_indication * +pcep_obj_create_server_indication(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding, + double_linked_list *tlv_list) +{ + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *) + pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_server_indication), + PCEP_OBJ_CLASS_SERVER_IND, + PCEP_OBJ_TYPE_SERVER_IND, tlv_list); + + obj->switching_capability = sw_cap; + obj->encoding = encoding; + + return obj; +} + +struct pcep_object_objective_function * +pcep_obj_create_objective_function(uint16_t of_code, + double_linked_list *tlv_list) +{ + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *) + pcep_obj_create_common_with_tlvs( + sizeof(struct pcep_object_objective_function), + PCEP_OBJ_CLASS_OF, PCEP_OBJ_TYPE_OF, tlv_list); + + obj->of_code = of_code; + + return obj; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_ero(double_linked_list *ero_list) +{ + struct pcep_object_ro *ero = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_ERO, + PCEP_OBJ_TYPE_ERO); + ero->sub_objects = ero_list; + + return ero; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_iro(double_linked_list *iro_list) +{ + struct pcep_object_ro *iro = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_IRO, + PCEP_OBJ_TYPE_IRO); + iro->sub_objects = iro_list; + + return iro; +} + +/* Wrap a list of ro subobjects in a structure with an object header */ +struct pcep_object_ro *pcep_obj_create_rro(double_linked_list *rro_list) +{ + struct pcep_object_ro *rro = + (struct pcep_object_ro *)pcep_obj_create_common( + sizeof(struct pcep_object_ro), PCEP_OBJ_CLASS_RRO, + PCEP_OBJ_TYPE_RRO); + rro->sub_objects = rro_list; + + return rro; +} + +/* + * Route Object Sub-object creation functions + */ + +static struct pcep_object_ro_subobj * +pcep_obj_create_ro_subobj_common(uint8_t subobj_size, + enum pcep_ro_subobj_types ro_subobj_type, + bool flag_subobj_loose_hop) +{ + struct pcep_object_ro_subobj *ro_subobj = + pceplib_malloc(PCEPLIB_MESSAGES, subobj_size); + memset(ro_subobj, 0, subobj_size); + ro_subobj->flag_subobj_loose_hop = flag_subobj_loose_hop; + ro_subobj->ro_subobj_type = ro_subobj_type; + + return ro_subobj; +} + +struct pcep_ro_subobj_ipv4 * +pcep_obj_create_ro_subobj_ipv4(bool loose_hop, const struct in_addr *rro_ipv4, + uint8_t prefix_length, bool flag_local_prot) +{ + if (rro_ipv4 == NULL) { + return NULL; + } + + struct pcep_ro_subobj_ipv4 *obj = + (struct pcep_ro_subobj_ipv4 *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_ipv4), RO_SUBOBJ_TYPE_IPV4, + loose_hop); + obj->ip_addr.s_addr = rro_ipv4->s_addr; + obj->prefix_length = prefix_length; + obj->flag_local_protection = flag_local_prot; + + return obj; +} + +struct pcep_ro_subobj_ipv6 * +pcep_obj_create_ro_subobj_ipv6(bool loose_hop, const struct in6_addr *rro_ipv6, + uint8_t prefix_length, bool flag_local_prot) +{ + if (rro_ipv6 == NULL) { + return NULL; + } + + struct pcep_ro_subobj_ipv6 *obj = + (struct pcep_ro_subobj_ipv6 *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_ipv6), RO_SUBOBJ_TYPE_IPV6, + loose_hop); + obj->prefix_length = prefix_length; + obj->flag_local_protection = flag_local_prot; + memcpy(&obj->ip_addr, rro_ipv6, sizeof(struct in6_addr)); + + return obj; +} + +struct pcep_ro_subobj_unnum * +pcep_obj_create_ro_subobj_unnum(struct in_addr *router_id, uint32_t if_id) +{ + if (router_id == NULL) { + return NULL; + } + + struct pcep_ro_subobj_unnum *obj = + (struct pcep_ro_subobj_unnum *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_unnum), + RO_SUBOBJ_TYPE_UNNUM, false); + obj->interface_id = if_id; + obj->router_id.s_addr = router_id->s_addr; + + return obj; +} + +struct pcep_ro_subobj_32label * +pcep_obj_create_ro_subobj_32label(bool flag_global_label, uint8_t class_type, + uint32_t label) +{ + struct pcep_ro_subobj_32label *obj = (struct pcep_ro_subobj_32label *) + pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_32label), + RO_SUBOBJ_TYPE_LABEL, false); + obj->class_type = class_type; + obj->flag_global_label = flag_global_label; + obj->label = label; + + return obj; +} + +struct pcep_ro_subobj_asn *pcep_obj_create_ro_subobj_asn(uint16_t asn) +{ + struct pcep_ro_subobj_asn *obj = + (struct pcep_ro_subobj_asn *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_asn), RO_SUBOBJ_TYPE_ASN, + false); + obj->asn = asn; + + return obj; +} + +/* Internal util function to create pcep_ro_subobj_sr sub-objects */ +static struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_common(enum pcep_sr_subobj_nai nai_type, + bool loose_hop, bool f_flag, bool s_flag, + bool c_flag_in, bool m_flag_in) +{ + struct pcep_ro_subobj_sr *obj = + (struct pcep_ro_subobj_sr *)pcep_obj_create_ro_subobj_common( + sizeof(struct pcep_ro_subobj_sr), RO_SUBOBJ_TYPE_SR, + loose_hop); + + /* Flag logic according to draft-ietf-pce-segment-routing-16 */ + bool c_flag = c_flag_in; + bool m_flag = m_flag_in; + if (s_flag) { + c_flag = false; + m_flag = false; + } + + if (m_flag == false) { + c_flag = false; + } + + obj->nai_type = nai_type; + obj->flag_f = f_flag; + obj->flag_s = s_flag; + obj->flag_c = c_flag; + obj->flag_m = m_flag; + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_nonai(bool loose_hop, + uint32_t sid, + bool c_flag, + bool m_flag) +{ + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=0, the F bit MUST be 1, the S bit MUST be zero and the + * Length MUST be 8. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_ABSENT, loose_hop, true, false, c_flag, + m_flag); + obj->sid = sid; + + return obj; +} + +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv4_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *ipv4_node_id) +{ + if (ipv4_node_id == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=1, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 8, otherwise the Length MUST be 12 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + /* Since the IP has to be stored in the list, copy it so the caller + * doesn't have any restrictions about the type of memory used + * externally for the IP. This memory will be freed with the object is + * freed. */ + struct in_addr *ipv4_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + ipv4_node_id_copy->s_addr = ipv4_node_id->s_addr; + dll_append(obj->nai_list, ipv4_node_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv6_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *ipv6_node_id) +{ + if (ipv6_node_id == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=2, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 20, otherwise the Length MUST be 24. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *ipv6_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(ipv6_node_id_copy, ipv6_node_id, sizeof(struct in6_addr)); + dll_append(obj->nai_list, ipv6_node_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *local_ipv4, struct in_addr *remote_ipv4) +{ + if (local_ipv4 == NULL || remote_ipv4 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=3, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 12, otherwise the Length MUST be 16 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in_addr *local_ipv4_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + struct in_addr *remote_ipv4_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in_addr)); + local_ipv4_copy->s_addr = local_ipv4->s_addr; + remote_ipv4_copy->s_addr = remote_ipv4->s_addr; + dll_append(obj->nai_list, local_ipv4_copy); + dll_append(obj->nai_list, remote_ipv4_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, struct in6_addr *remote_ipv6) +{ + if (local_ipv6 == NULL || remote_ipv6 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=4, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 36, otherwise the Length MUST be 40 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, loose_hop, false, sid_absent, + c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *local_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + struct in6_addr *remote_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(local_ipv6_copy, local_ipv6, sizeof(struct in6_addr)); + memcpy(remote_ipv6_copy, remote_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, local_ipv6_copy); + dll_append(obj->nai_list, remote_ipv6_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + uint32_t local_node_id, uint32_t local_if_id, uint32_t remote_node_id, + uint32_t remote_if_id) +{ + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=5, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 20, otherwise the Length MUST be 24. */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, loose_hop, false, + sid_absent, c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + + obj->nai_list = dll_initialize(); + uint32_t *local_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_node_id_copy = local_node_id; + dll_append(obj->nai_list, local_node_id_copy); + + uint32_t *local_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_if_id_copy = local_if_id; + dll_append(obj->nai_list, local_if_id_copy); + + uint32_t *remote_node_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_node_id_copy = remote_node_id; + dll_append(obj->nai_list, remote_node_id_copy); + + uint32_t *remote_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_if_id_copy = remote_if_id; + dll_append(obj->nai_list, remote_if_id_copy); + + return obj; +} + +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, uint32_t local_if_id, + struct in6_addr *remote_ipv6, uint32_t remote_if_id) +{ + if (local_ipv6 == NULL || remote_ipv6 == NULL) { + return NULL; + } + + /* According to draft-ietf-pce-segment-routing-16#section-5.2.1 + * If NT=6, the F bit MUST be zero. If the S bit is 1, the Length + * MUST be 44, otherwise the Length MUST be 48 */ + struct pcep_ro_subobj_sr *obj = pcep_obj_create_ro_subobj_sr_common( + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, loose_hop, false, + sid_absent, c_flag, m_flag); + + if (!sid_absent) { + obj->sid = sid; + } + obj->nai_list = dll_initialize(); + struct in6_addr *local_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(local_ipv6_copy, local_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, local_ipv6_copy); + + uint32_t *local_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *local_if_id_copy = local_if_id; + dll_append(obj->nai_list, local_if_id_copy); + + struct in6_addr *remote_ipv6_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct in6_addr)); + memcpy(remote_ipv6_copy, remote_ipv6, sizeof(struct in6_addr)); + dll_append(obj->nai_list, remote_ipv6_copy); + + uint32_t *remote_if_id_copy = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *remote_if_id_copy = remote_if_id; + dll_append(obj->nai_list, remote_if_id_copy); + + return obj; +} diff --git a/pceplib/pcep_msg_objects.h b/pceplib/pcep_msg_objects.h new file mode 100644 index 0000000..42a8bbf --- /dev/null +++ b/pceplib/pcep_msg_objects.h @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message object API. + */ + +#ifndef PCEP_OBJECTS_H +#define PCEP_OBJECTS_H + +#include +#include + +#include "pcep.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_tlvs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Regarding memory usage: + * When creating objects, any objects passed into these APIs will be free'd when + * the enclosing pcep_message is free'd. That includes the double_linked_list's. + * So, just create the objects and TLVs, put them in their double_linked_list's, + * and everything will be managed internally. The enclosing message will be + * deleted by pcep_msg_free_message() or pcep_msg_free_message_list() which, + * in turn will call one of: pcep_obj_free_object() and pcep_obj_free_tlv(). + * For received messages with objects, call pcep_msg_free_message() to free + * them. + */ + +enum pcep_object_classes { + PCEP_OBJ_CLASS_OPEN = 1, + PCEP_OBJ_CLASS_RP = 2, + PCEP_OBJ_CLASS_NOPATH = 3, + PCEP_OBJ_CLASS_ENDPOINTS = 4, + PCEP_OBJ_CLASS_BANDWIDTH = 5, + PCEP_OBJ_CLASS_METRIC = 6, + PCEP_OBJ_CLASS_ERO = 7, + PCEP_OBJ_CLASS_RRO = 8, + PCEP_OBJ_CLASS_LSPA = 9, + PCEP_OBJ_CLASS_IRO = 10, + PCEP_OBJ_CLASS_SVEC = 11, + PCEP_OBJ_CLASS_NOTF = 12, + PCEP_OBJ_CLASS_ERROR = 13, + PCEP_OBJ_CLASS_CLOSE = 15, + PCEP_OBJ_CLASS_OF = 21, + PCEP_OBJ_CLASS_LSP = 32, + PCEP_OBJ_CLASS_SRP = 33, + PCEP_OBJ_CLASS_VENDOR_INFO = 34, + PCEP_OBJ_CLASS_INTER_LAYER = 36, /* RFC 8282 */ + PCEP_OBJ_CLASS_SWITCH_LAYER = 37, /* RFC 8282 */ + PCEP_OBJ_CLASS_REQ_ADAP_CAP = 38, /* RFC 8282 */ + PCEP_OBJ_CLASS_SERVER_IND = 39, /* RFC 8282 */ + PCEP_OBJ_CLASS_ASSOCIATION = 40, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_CLASS_MAX, +}; + +enum pcep_object_types { + PCEP_OBJ_TYPE_OPEN = 1, + PCEP_OBJ_TYPE_RP = 1, + PCEP_OBJ_TYPE_NOPATH = 1, + PCEP_OBJ_TYPE_ENDPOINT_IPV4 = 1, + PCEP_OBJ_TYPE_ENDPOINT_IPV6 = 2, + PCEP_OBJ_TYPE_BANDWIDTH_REQ = 1, + PCEP_OBJ_TYPE_BANDWIDTH_TELSP = 2, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO = + 5, /* IANA unassigned, but rcvd from Cisco PCE */ + PCEP_OBJ_TYPE_SRP = 1, + PCEP_OBJ_TYPE_VENDOR_INFO = 1, + PCEP_OBJ_TYPE_LSP = 1, + PCEP_OBJ_TYPE_METRIC = 1, + PCEP_OBJ_TYPE_ERO = 1, + PCEP_OBJ_TYPE_RRO = 1, + PCEP_OBJ_TYPE_LSPA = 1, + PCEP_OBJ_TYPE_IRO = 1, + PCEP_OBJ_TYPE_SVEC = 1, + PCEP_OBJ_TYPE_NOTF = 1, + PCEP_OBJ_TYPE_ERROR = 1, + PCEP_OBJ_TYPE_CLOSE = 1, + PCEP_OBJ_TYPE_INTER_LAYER = 1, + PCEP_OBJ_TYPE_SWITCH_LAYER = 1, + PCEP_OBJ_TYPE_REQ_ADAP_CAP = 1, + PCEP_OBJ_TYPE_SERVER_IND = 1, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4 = + 1, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_TYPE_ASSOCIATION_IPV6 = + 2, /*draft-ietf-pce-association-group-10*/ + PCEP_OBJ_TYPE_OF = 1, + PCEP_OBJ_TYPE_MAX = 2, +}; + +#define OBJECT_HEADER_FLAG_I 0x01 +#define OBJECT_HEADER_FLAG_P 0x02 + +/* The flag_p and flag_i arent set via the APIs, if they need to be set, just + * set them on the returned object once it has been created. */ +struct pcep_object_header { + enum pcep_object_classes object_class; + enum pcep_object_types object_type; + bool flag_p; /* PCC Processing rule bit: When set, the object MUST be + taken into account, when cleared the object is optional. + */ + bool flag_i; /* PCE Ignore bit: indicates to a PCC whether or not an + optional object was processed */ + double_linked_list *tlv_list; + /* Pointer into encoded_message field from the pcep_message */ + const uint8_t *encoded_object; + uint16_t encoded_object_length; +}; + +#define PCEP_OBJECT_OPEN_VERSION 1 + +struct pcep_object_open { + struct pcep_object_header header; + uint8_t open_version; /* PCEP version. Current version is 1 */ + uint8_t open_keepalive; /* Maximum period of time between two + consecutive PCEP messages sent by the sender. + */ + uint8_t open_deadtimer; /* Specifies the amount of time before closing + the session down. */ + uint8_t open_sid; /* PCEP session number that identifies the current + session. */ +}; + +#define OBJECT_RP_FLAG_R 0x08 +#define OBJECT_RP_FLAG_B 0x10 +#define OBJECT_RP_FLAG_O 0x20 +#define OBJECT_RP_FLAG_OF 0x80 +#define OBJECT_RP_MAX_PRIORITY 0x07 + +struct pcep_object_rp { + struct pcep_object_header header; + uint8_t priority; /* 3 bit priority, max priority is 7 */ + bool flag_reoptimization; + bool flag_bidirectional; + bool flag_strict; /* when set, a loose path is acceptable */ + bool flag_of; /* Supply Objective Function on Response */ + uint32_t request_id; /* The Request-id-number value combined with the + source for PCC & PCE creates a uniquely number. + */ +}; + +enum pcep_notification_types { + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED = 1, + PCEP_NOTIFY_TYPE_PCE_OVERLOADED = 2 +}; + +enum pcep_notification_values { + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST = 1, + PCEP_NOTIFY_VALUE_PCE_CANCELLED_REQUEST = 2, + PCEP_NOTIFY_VALUE_PCE_CURRENTLY_OVERLOADED = 1, + PCEP_NOTIFY_VALUE_PCE_NO_LONGER_OVERLOADED = 2 +}; + +struct pcep_object_notify { + struct pcep_object_header header; + enum pcep_notification_types notification_type; + enum pcep_notification_values notification_value; +}; + +enum pcep_association_type { + PCEP_ASSOCIATION_TYPE_PATH_PROTECTION_ASSOCIATION = + 1, // iana unique value define as 2020-01-08! + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE = + 65535 // TBD1 draft-barth-pce-segment-routing-policy-cp-04 +}; +#define OBJECT_ASSOCIATION_FLAG_R 0x01 +struct pcep_object_association_ipv4 { // draft-ietf-pce-association-group-10 + struct pcep_object_header header; + bool R_flag; + uint16_t association_type; + uint16_t association_id; + struct in_addr src; +}; + +struct pcep_object_association_ipv6 { // draft-ietf-pce-association-group-10 + struct pcep_object_header header; + bool R_flag; + uint16_t association_type; + uint16_t association_id; + struct in6_addr src; +}; + + +enum pcep_nopath_nature_of_issue { + PCEP_NOPATH_NI_NO_PATH_FOUND = 0, + PCEP_NOPATH_NI_PCE_CHAIN_BROKEN = 1, +}; + +enum pcep_nopath_tlv_err_codes { + PCEP_NOPATH_TLV_ERR_NO_TLV = 0, + PCEP_NOPATH_TLV_ERR_PCE_UNAVAILABLE = 1, + PCEP_NOPATH_TLV_ERR_UNKNOWN_DST = 2, + PCEP_NOPATH_TLV_ERR_UNKNOWN_SRC = 3 +}; + +#define OBJECT_NOPATH_FLAG_C 0x80 + +struct pcep_object_nopath { + struct pcep_object_header header; + uint8_t ni; /* Nature of Issue, reports the nature of the issue that led + to a negative reply */ + bool flag_c; /* when set, indicates the unsatisfied constraints by + including relevant PCEP objects. */ + enum pcep_nopath_tlv_err_codes + err_code; /* When set other than 0, an appropriate TLV will be + included */ +}; + +struct pcep_object_endpoints_ipv4 { + struct pcep_object_header header; + struct in_addr src_ipv4; + struct in_addr dst_ipv4; +}; + +struct pcep_object_endpoints_ipv6 { + struct pcep_object_header header; + struct in6_addr src_ipv6; + struct in6_addr dst_ipv6; +}; + +/* PCEP floats are encoded according to: + * https://en.wikipedia.org/wiki/IEEE_754-1985 + * Luckily, this is the same encoding used by C */ +struct pcep_object_bandwidth { + struct pcep_object_header header; + float bandwidth; +}; + +enum pcep_metric_types { + /* RFC 5440 */ + PCEP_METRIC_IGP = 1, + PCEP_METRIC_TE = 2, + PCEP_METRIC_HOP_COUNT = 3, + /* RFC 5541 */ + PCEP_METRIC_AGGREGATE_BW = 4, + PCEP_METRIC_MOST_LOADED_LINK = 5, + PCEP_METRIC_CUMULATIVE_IGP = 6, + PCEP_METRIC_CUMULATIVE_TE = 7, + /* RFC 8306 */ + PCEP_METRIC_P2MP_IGP = 8, + PCEP_METRIC_P2MP_TE = 9, + PCEP_METRIC_P2MP_HOP_COUNT = 10, + /* RFC 8864 */ + PCEP_METRIC_SEGMENT_ID_DEPTH = 11, + /* RFC 8233 */ + PCEP_METRIC_PATH_DELAY = 12, + PCEP_METRIC_PATH_DELAY_VARIATION = 13, + PCEP_METRIC_PATH_LOSS = 14, + PCEP_METRIC_P2MP_PATH_DELAY = 15, + PCEP_METRIC_P2MP_PATH_DELAY_VARIATION = 16, + PCEP_METRIC_P2MP_PATH_LOSS = 17, + /* RFC 8282 */ + PCEP_METRIC_NUM_PATH_ADAPTATIONS = 18, + PCEP_METRIC_NUM_PATH_LAYERS = 19, + /* RFC 8685 */ + PCEP_METRIC_DOMAIN_COUNT = 20, + PCEP_METRIC_BORDER_NODE_COUNT = 21, +}; + +#define OBJECT_METRIC_FLAC_B 0x01 +#define OBJECT_METRIC_FLAC_C 0x02 + +/* PCEP floats are encoded according to: + * https://en.wikipedia.org/wiki/IEEE_754-1985 + * Luckily, this is the same encoding used by C */ +struct pcep_object_metric { + struct pcep_object_header header; + enum pcep_metric_types type; + bool flag_b; /* Bound flag */ + bool flag_c; /* Computed metric */ + float value; /* Metric value in 32 bits */ +}; + +#define OBJECT_LSPA_FLAG_L 0x01 + +struct pcep_object_lspa { + struct pcep_object_header header; + uint32_t lspa_exclude_any; + uint32_t lspa_include_any; + uint32_t lspa_include_all; + uint8_t setup_priority; + uint8_t holding_priority; + bool flag_local_protection; /* Local protection desired bit */ +}; + +/* The SVEC object with some custom extensions. */ +#define OBJECT_SVEC_FLAG_L 0x01 +#define OBJECT_SVEC_FLAG_N 0x02 +#define OBJECT_SVEC_FLAG_S 0x04 + +struct pcep_object_svec { + struct pcep_object_header header; + bool flag_link_diverse; + bool flag_node_diverse; + bool flag_srlg_diverse; + double_linked_list + *request_id_list; /* list of 32-bit request ID pointers */ +}; + +struct pcep_object_error { + struct pcep_object_header header; + enum pcep_error_type error_type; + enum pcep_error_value error_value; +}; + +struct pcep_object_load_balancing { + struct pcep_object_header header; + uint8_t load_maxlsp; /* Maximum number of TE LSPs in the set */ + uint32_t load_minband; /* Specifies the minimum bandwidth of each + element */ +}; + +enum pcep_close_reason { + PCEP_CLOSE_REASON_NO = 1, + PCEP_CLOSE_REASON_DEADTIMER = 2, + PCEP_CLOSE_REASON_FORMAT = 3, + PCEP_CLOSE_REASON_UNKNOWN_REQ = 4, + PCEP_CLOSE_REASON_UNREC_MSG = 5 +}; + +struct pcep_object_close { + struct pcep_object_header header; + enum pcep_close_reason reason; +}; + +/* Stateful PCE Request Parameters RFC 8231, 8281 */ + +#define OBJECT_SRP_FLAG_R 0x01 + +struct pcep_object_srp { + struct pcep_object_header header; + bool flag_lsp_remove; /* RFC 8281 */ + uint32_t srp_id_number; +}; + +/* Label Switched Path Object RFC 8231 */ +enum pcep_lsp_operational_status { + PCEP_LSP_OPERATIONAL_DOWN = 0, + PCEP_LSP_OPERATIONAL_UP = 1, + PCEP_LSP_OPERATIONAL_ACTIVE = 2, + PCEP_LSP_OPERATIONAL_GOING_DOWN = 3, + PCEP_LSP_OPERATIONAL_GOING_UP = 4, +}; + +#define MAX_PLSP_ID 0x000fffff /* The plsp_id is only 20 bits */ +#define MAX_LSP_STATUS 0x0007 /* The status is only 3 bits */ +#define OBJECT_LSP_FLAG_D 0x01 +#define OBJECT_LSP_FLAG_S 0x02 +#define OBJECT_LSP_FLAG_R 0x04 +#define OBJECT_LSP_FLAG_A 0x08 +#define OBJECT_LSP_FLAG_C 0x80 + +struct pcep_object_lsp { + struct pcep_object_header header; + uint32_t plsp_id; /* plsp_id is 20 bits, must be <= MAX_PLSP_ID*/ + enum pcep_lsp_operational_status operational_status; /* max 3 bits */ + bool flag_d; + bool flag_s; + bool flag_r; + bool flag_a; + bool flag_c; +}; + +#define ENTERPRISE_NUMBER_CISCO 9 +#define ENTERPRISE_COLOR_CISCO 65540 +/* RFC 7470 */ +struct pcep_object_vendor_info { + struct pcep_object_header header; + uint32_t enterprise_number; + uint32_t enterprise_specific_info; + uint32_t enterprise_specific_info1; /* cisco sends color for PcInit */ + uint32_t enterprise_specific_info2; + uint32_t enterprise_specific_info3; +}; + +/* RFC 8282 */ +#define OBJECT_INTER_LAYER_FLAG_I 0x01 +#define OBJECT_INTER_LAYER_FLAG_M 0x02 +#define OBJECT_INTER_LAYER_FLAG_T 0x04 + +struct pcep_object_inter_layer { + struct pcep_object_header header; + bool flag_i; + bool flag_m; + bool flag_t; +}; + +/* RFC 8282 */ +#define OBJECT_SWITCH_LAYER_FLAG_I 0x01 +enum pcep_lsp_encoding_type { + /* Values taken from RFC 3471 as suggested by RFC 8282 */ + PCEP_LSP_ENC_PACKET = 1, + PCEP_LSP_ENC_ETHERNET = 2, + PCEP_LSP_ENC_PDH = 3, + PCEP_LSP_ENC_RESERVED4 = 4, + PCEP_LSP_ENC_SDH_SONET = 5, + PCEP_LSP_ENC_RESERVED6 = 6, + PCEP_LSP_ENC_DIG_WRAPPER = 7, + PCEP_LSP_ENC_LAMBDA = 8, + PCEP_LSP_ENC_FIBER = 9, + PCEP_LSP_ENC_RESERVED10 = 10, + PCEP_LSP_ENC_FIBER_CHAN = 11 +}; + +enum pcep_switching_capability { + /* Switching capability values taken from RFC 4203/3471 as suggested by + RFC 8282 */ + PCEP_SW_CAP_PSC1 = 1, /* Packet-Switch Capable-1 (PSC-1) */ + PCEP_SW_CAP_PSC2 = 2, + PCEP_SW_CAP_PSC3 = 3, + PCEP_SW_CAP_PSC4 = 4, + PCEP_SW_CAP_L2SC = 51, /* Layer-2 Switch Capable */ + PCEP_SW_CAP_TDM = 100, /* Time-Division-Multiplex Capable */ + PCEP_SW_CAP_LSC = 150, /* Lambda-Switch Capable */ + PCEP_SW_CAP_FSC = 200 /* Fiber-Switch Capable */ +}; + +struct pcep_object_switch_layer_row { + enum pcep_lsp_encoding_type lsp_encoding_type; + enum pcep_switching_capability switching_type; + bool flag_i; +}; + +struct pcep_object_switch_layer { + struct pcep_object_header header; + double_linked_list + *switch_layer_rows; /* list of struct + pcep_object_switch_layer_row */ +}; + +/* RFC 8282 + * Requested Adaptation capability */ + +struct pcep_object_req_adap_cap { + struct pcep_object_header header; + enum pcep_switching_capability switching_capability; + enum pcep_lsp_encoding_type encoding; +}; + +/* RFC 8282 */ + +struct pcep_object_server_indication { + struct pcep_object_header header; + enum pcep_switching_capability switching_capability; + enum pcep_lsp_encoding_type encoding; + /* This object is identical to req_adap_cap, except it allows TLVs */ +}; + +/* Objective Function Object: RFC 5541 */ + +struct pcep_object_objective_function { + struct pcep_object_header header; + uint16_t of_code; +}; + +/* + * Common Route Object sub-object definitions + * used by ERO, IRO, and RRO + */ + +/* Common Route Object sub-object types + * used by ERO, IRO, and RRO */ +enum pcep_ro_subobj_types { + RO_SUBOBJ_TYPE_IPV4 = 1, /* RFC 3209 */ + RO_SUBOBJ_TYPE_IPV6 = 2, /* RFC 3209 */ + RO_SUBOBJ_TYPE_LABEL = 3, /* RFC 3209 */ + RO_SUBOBJ_TYPE_UNNUM = 4, /* RFC 3477 */ + RO_SUBOBJ_TYPE_ASN = 32, /* RFC 3209, Section 4.3.3.4 */ + RO_SUBOBJ_TYPE_SR = 36, /* RFC 8408, draft-ietf-pce-segment-routing-16. + Type 5 for draft07 has been assigned to + something else. */ + RO_SUBOBJ_UNKNOWN +}; + +struct pcep_object_ro { + struct pcep_object_header header; + double_linked_list + *sub_objects; /* list of struct pcep_object_ro_subobj */ +}; + +struct pcep_object_ro_subobj { + bool flag_subobj_loose_hop; /* L subobj flag */ + enum pcep_ro_subobj_types ro_subobj_type; +}; + +#define OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT 0x01 + +struct pcep_ro_subobj_ipv4 { + struct pcep_object_ro_subobj ro_subobj; + struct in_addr ip_addr; + uint8_t prefix_length; + bool flag_local_protection; +}; + +struct pcep_ro_subobj_ipv6 { + struct pcep_object_ro_subobj ro_subobj; + struct in6_addr ip_addr; + uint8_t prefix_length; + bool flag_local_protection; +}; + +struct pcep_ro_subobj_unnum { + struct pcep_object_ro_subobj ro_subobj; + struct in_addr router_id; + uint32_t interface_id; +}; + +#define OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL 0x01 +struct pcep_ro_subobj_32label { + struct pcep_object_ro_subobj ro_subobj; + bool flag_global_label; + uint8_t class_type; /* label class-type (generalized label = 2) */ + uint32_t label; /* label supported */ +}; + +struct pcep_ro_subobj_asn { + struct pcep_object_ro_subobj ro_subobj; + uint16_t asn; /* Autonomous system number */ +}; + +/* The SR ERO and SR RRO subobjects are the same, except + * the SR-RRO does not have the L flag in the Type field. + * Defined in draft-ietf-pce-segment-routing-16 */ +enum pcep_sr_subobj_nai { + PCEP_SR_SUBOBJ_NAI_ABSENT = 0, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE = 1, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE = 2, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY = 3, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY = 4, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY = 5, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY = 6, + PCEP_SR_SUBOBJ_NAI_UNKNOWN +}; + +#define OBJECT_SUBOBJ_SR_FLAG_M 0x01 +#define OBJECT_SUBOBJ_SR_FLAG_C 0x02 +#define OBJECT_SUBOBJ_SR_FLAG_S 0x04 +#define OBJECT_SUBOBJ_SR_FLAG_F 0x08 + +struct pcep_ro_subobj_sr { + struct pcep_object_ro_subobj ro_subobj; + enum pcep_sr_subobj_nai nai_type; + bool flag_f; + bool flag_s; + bool flag_c; + bool flag_m; + + /* The SID and NAI are optional depending on the flags, + * and the NAI can be variable length */ + uint32_t sid; + double_linked_list + *nai_list; /* double linked list of in_addr or in6_addr */ +}; + +/* Macros to make a SID Label + * + * 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Label + | Label | TC |S| TTL | Stack + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Entry + */ +#define ENCODE_SR_ERO_SID(label_20bits, tc_3bits, stack_bottom_bit, ttl_8bits) \ + ((((label_20bits) << 12) & 0xfffff000) \ + | (((tc_3bits) << 9) & 0x00000e00) \ + | (((stack_bottom_bit) << 8) & 0x00000100) | ((ttl_8bits)&0xff)) +#define GET_SR_ERO_SID_LABEL(SID) ((SID & 0xfffff000) >> 12) +#define GET_SR_ERO_SID_TC(SID) ((SID & 0x00000e00) >> 9) +#define GET_SR_ERO_SID_S(SID) ((SID & 0x00000100) >> 8) +#define GET_SR_ERO_SID_TTL(SID) ((SID & 0x000000ff)) + +/* + * All created objects will be in Host byte order, except for IPs. + * All IP addresses are expected to be passed-in in Network byte order, + * and any objects received will have their IPs in Network byte order. + * The message containing the objects should be converted to Network byte order + * with pcep_encode_msg_header() before sending, which will also convert the + * Objects, TLVs, and sub-objects. + */ + +struct pcep_object_open *pcep_obj_create_open(uint8_t keepalive, + uint8_t deadtimer, uint8_t sid, + double_linked_list *tlv_list); +struct pcep_object_rp *pcep_obj_create_rp(uint8_t priority, bool flag_r, + bool flag_b, bool flag_s, + bool flag_of, uint32_t reqid, + double_linked_list *tlv_list); +struct pcep_object_notify * +pcep_obj_create_notify(enum pcep_notification_types notification_type, + enum pcep_notification_values notification_value); +struct pcep_object_nopath * +pcep_obj_create_nopath(uint8_t ni, bool flag_c, + enum pcep_nopath_tlv_err_codes error_code); +struct pcep_object_association_ipv4 * +pcep_obj_create_association_ipv4(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in_addr src); +struct pcep_object_association_ipv6 * +pcep_obj_create_association_ipv6(bool r_flag, uint16_t association_type, + uint16_t association_id, struct in6_addr src); +struct pcep_object_endpoints_ipv4 * +pcep_obj_create_endpoint_ipv4(const struct in_addr *src_ipv4, + const struct in_addr *dst_ipv4); +struct pcep_object_endpoints_ipv6 * +pcep_obj_create_endpoint_ipv6(const struct in6_addr *src_ipv6, + const struct in6_addr *dst_ipv6); +struct pcep_object_bandwidth *pcep_obj_create_bandwidth(float bandwidth); +struct pcep_object_metric *pcep_obj_create_metric(enum pcep_metric_types type, + bool flag_b, bool flag_c, + float value); +struct pcep_object_lspa * +pcep_obj_create_lspa(uint32_t exclude_any, uint32_t include_any, + uint32_t include_all, uint8_t setup_priority, + uint8_t holding_priority, bool flag_local_protection); +struct pcep_object_svec * +pcep_obj_create_svec(bool srlg, bool node, bool link, + double_linked_list *request_id_list); +struct pcep_object_error * +pcep_obj_create_error(enum pcep_error_type error_type, + enum pcep_error_value error_value); +struct pcep_object_close *pcep_obj_create_close(enum pcep_close_reason reason); +struct pcep_object_srp *pcep_obj_create_srp(bool lsp_remove, + uint32_t srp_id_number, + double_linked_list *tlv_list); +struct pcep_object_lsp * +pcep_obj_create_lsp(uint32_t plsp_id, enum pcep_lsp_operational_status status, + bool c_flag, bool a_flag, bool r_flag, bool s_flag, + bool d_flag, double_linked_list *tlv_list); +struct pcep_object_vendor_info * +pcep_obj_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_spec_info); +struct pcep_object_inter_layer * +pcep_obj_create_inter_layer(bool flag_i, bool flag_m, bool flag_t); +struct pcep_object_switch_layer * +pcep_obj_create_switch_layer(double_linked_list *switch_layer_rows); +struct pcep_object_req_adap_cap * +pcep_obj_create_req_adap_cap(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding); +struct pcep_object_server_indication * +pcep_obj_create_server_indication(enum pcep_switching_capability sw_cap, + enum pcep_lsp_encoding_type encoding, + double_linked_list *tlv_list); +struct pcep_object_objective_function * +pcep_obj_create_objective_function(uint16_t of_code, + double_linked_list *tlv_list); + +/* Route Object (Explicit ero, Reported rro, and Include iro) functions + * First, the sub-objects should be created and appended to a + * double_linked_list, then call one of these Route Object creation functions + * with the subobj list */ +struct pcep_object_ro *pcep_obj_create_ero(double_linked_list *ero_list); +struct pcep_object_ro *pcep_obj_create_rro(double_linked_list *rro_list); +struct pcep_object_ro *pcep_obj_create_iro(double_linked_list *iro_list); +/* Route Object sub-object creation functions */ +struct pcep_ro_subobj_ipv4 * +pcep_obj_create_ro_subobj_ipv4(bool loose_hop, const struct in_addr *ro_ipv4, + uint8_t prefix_len, bool flag_local_prot); +struct pcep_ro_subobj_ipv6 * +pcep_obj_create_ro_subobj_ipv6(bool loose_hop, const struct in6_addr *ro_ipv6, + uint8_t prefix_len, bool flag_local_prot); +struct pcep_ro_subobj_unnum * +pcep_obj_create_ro_subobj_unnum(struct in_addr *router_id, uint32_t if_id); +struct pcep_ro_subobj_32label * +pcep_obj_create_ro_subobj_32label(bool flag_global_label, uint8_t class_type, + uint32_t label); +struct pcep_ro_subobj_asn *pcep_obj_create_ro_subobj_asn(uint16_t asn); + +/* SR ERO and SR RRO creation functions for different NAI (Node/Adj ID) types. + * - The loose_hop is only used for sr ero and must always be false for sr rro. + * - The NAI value will be set internally, depending on which function is used. + * m_flag: + * - If this flag is true, the SID value represents an MPLS label stack + * entry as specified in [RFC3032]. Otherwise, the SID value is an + * administratively configured value which represents an index into + * an MPLS label space (either SRGB or SRLB) per [RFC8402]. + * c_flag: + * - If the M flag and the C flag are both true, then the TC, S, and TTL + * fields in the MPLS label stack entry are specified by the PCE. However, + * a PCC MAY choose to override these values according to its local policy + * and MPLS forwarding rules. + * - If the M flag is true but the C flag is false, then the TC, S, and TTL + * fields MUST be ignored by the PCC. + * - The PCC MUST set these fields according to its local policy and MPLS + * forwarding rules. + * - If the M flag is false then the C bit MUST be false. */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_nonai(bool loose_hop, + uint32_t sid, + bool c_flag, + bool m_flag); + +/* The ipv4_node_id will be copied internally */ +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv4_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *ipv4_node_id); +/* The ipv6_node_id will be copied internally */ +struct pcep_ro_subobj_sr * +pcep_obj_create_ro_subobj_sr_ipv6_node(bool loose_hop, bool sid_absent, + bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *ipv6_node_id); +/* The local_ipv4 and remote_ipv4 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in_addr *local_ipv4, struct in_addr *remote_ipv4); +/* The local_ipv6 and remote_ipv6 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, struct in6_addr *remote_ipv6); +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + uint32_t local_node_id, uint32_t local_if_id, uint32_t remote_node_id, + uint32_t remote_if_id); +/* The local_ipv6 and remote_ipv6 will be copied internally */ +struct pcep_ro_subobj_sr *pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + bool loose_hop, bool sid_absent, bool c_flag, bool m_flag, uint32_t sid, + struct in6_addr *local_ipv6, uint32_t local_if_id, + struct in6_addr *remote_ipv6, uint32_t remote_if_id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_msg_objects_encoding.c b/pceplib/pcep_msg_objects_encoding.c new file mode 100644 index 0000000..2747678 --- /dev/null +++ b/pceplib/pcep_msg_objects_encoding.c @@ -0,0 +1,1720 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP Objects. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_msg_objects.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_object_header(struct pcep_object_header *object_hdr, + uint16_t object_length, uint8_t *buf); +void pcep_decode_object_hdr(const uint8_t *obj_buf, + struct pcep_object_header *obj_hdr); +void set_ro_subobj_fields(struct pcep_object_ro_subobj *subobj, bool flag_l, + uint8_t subobj_type); + +/* + * forward declarations for initialize_object_encoders() + */ +uint16_t pcep_encode_obj_open(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_rp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_nopath(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_endpoints(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_association(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_bandwidth(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_metric(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_ro(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_lspa(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_svec(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_notify(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_error(struct pcep_object_header *error, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_close(struct pcep_object_header *close, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_srp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_lsp(struct pcep_object_header *obj, + struct pcep_versioning *versioning, uint8_t *buf); +uint16_t pcep_encode_obj_vendor_info(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_inter_layer(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_switch_layer(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_req_adap_cap(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_server_ind(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +uint16_t pcep_encode_obj_objective_function(struct pcep_object_header *obj, + struct pcep_versioning *versioning, + uint8_t *buf); +typedef uint16_t (*object_encoder_funcptr)(struct pcep_object_header *, + struct pcep_versioning *versioning, + uint8_t *buf); + +#define MAX_OBJECT_ENCODER_INDEX 64 + +#define PCEP_ENCODERS_ARGS \ + struct pcep_object_header *, struct pcep_versioning *versioning, \ + uint8_t *buf +uint16_t (*const object_encoders[MAX_OBJECT_ENCODER_INDEX])( + PCEP_ENCODERS_ARGS) = { + [PCEP_OBJ_CLASS_OPEN] = pcep_encode_obj_open, + [PCEP_OBJ_CLASS_RP] = pcep_encode_obj_rp, + [PCEP_OBJ_CLASS_NOPATH] = pcep_encode_obj_nopath, + [PCEP_OBJ_CLASS_ENDPOINTS] = pcep_encode_obj_endpoints, + [PCEP_OBJ_CLASS_BANDWIDTH] = pcep_encode_obj_bandwidth, + [PCEP_OBJ_CLASS_METRIC] = pcep_encode_obj_metric, + [PCEP_OBJ_CLASS_ERO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_RRO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_LSPA] = pcep_encode_obj_lspa, + [PCEP_OBJ_CLASS_IRO] = pcep_encode_obj_ro, + [PCEP_OBJ_CLASS_SVEC] = pcep_encode_obj_svec, + [PCEP_OBJ_CLASS_NOTF] = pcep_encode_obj_notify, + [PCEP_OBJ_CLASS_ERROR] = pcep_encode_obj_error, + [PCEP_OBJ_CLASS_CLOSE] = pcep_encode_obj_close, + [PCEP_OBJ_CLASS_LSP] = pcep_encode_obj_lsp, + [PCEP_OBJ_CLASS_SRP] = pcep_encode_obj_srp, + [PCEP_OBJ_CLASS_ASSOCIATION] = pcep_encode_obj_association, + [PCEP_OBJ_CLASS_INTER_LAYER] = pcep_encode_obj_inter_layer, + [PCEP_OBJ_CLASS_SWITCH_LAYER] = pcep_encode_obj_switch_layer, + [PCEP_OBJ_CLASS_REQ_ADAP_CAP] = pcep_encode_obj_req_adap_cap, + [PCEP_OBJ_CLASS_SERVER_IND] = pcep_encode_obj_server_ind, + [PCEP_OBJ_CLASS_VENDOR_INFO] = pcep_encode_obj_vendor_info, + [PCEP_OBJ_CLASS_OF] = pcep_encode_obj_objective_function, +}; +/* + * forward declarations for initialize_object_decoders() + */ +struct pcep_object_header *pcep_decode_obj_open(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_rp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_nopath(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_endpoints(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_association(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_bandwidth(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_metric(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_ro(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_lspa(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_svec(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_notify(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_error(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_close(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_srp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header *pcep_decode_obj_lsp(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_vendor_info(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_inter_layer(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_switch_layer(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_req_adap_cap(struct pcep_object_header *hdr, + const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_server_ind(struct pcep_object_header *hdr, const uint8_t *buf); +struct pcep_object_header * +pcep_decode_obj_objective_function(struct pcep_object_header *hdr, + const uint8_t *buf); +typedef struct pcep_object_header *(*object_decoder_funcptr)( + struct pcep_object_header *, const uint8_t *buf); + +#define PCEP_DECODERS_ARGS struct pcep_object_header *, const uint8_t *buf + +struct pcep_object_header *(*const object_decoders[MAX_OBJECT_ENCODER_INDEX])( + PCEP_DECODERS_ARGS) = { + [PCEP_OBJ_CLASS_OPEN] = pcep_decode_obj_open, + [PCEP_OBJ_CLASS_RP] = pcep_decode_obj_rp, + [PCEP_OBJ_CLASS_NOPATH] = pcep_decode_obj_nopath, + [PCEP_OBJ_CLASS_ENDPOINTS] = pcep_decode_obj_endpoints, + [PCEP_OBJ_CLASS_BANDWIDTH] = pcep_decode_obj_bandwidth, + [PCEP_OBJ_CLASS_METRIC] = pcep_decode_obj_metric, + [PCEP_OBJ_CLASS_ERO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_RRO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_LSPA] = pcep_decode_obj_lspa, + [PCEP_OBJ_CLASS_IRO] = pcep_decode_obj_ro, + [PCEP_OBJ_CLASS_SVEC] = pcep_decode_obj_svec, + [PCEP_OBJ_CLASS_NOTF] = pcep_decode_obj_notify, + [PCEP_OBJ_CLASS_ERROR] = pcep_decode_obj_error, + [PCEP_OBJ_CLASS_CLOSE] = pcep_decode_obj_close, + [PCEP_OBJ_CLASS_LSP] = pcep_decode_obj_lsp, + [PCEP_OBJ_CLASS_SRP] = pcep_decode_obj_srp, + [PCEP_OBJ_CLASS_ASSOCIATION] = pcep_decode_obj_association, + [PCEP_OBJ_CLASS_INTER_LAYER] = pcep_decode_obj_inter_layer, + [PCEP_OBJ_CLASS_SWITCH_LAYER] = pcep_decode_obj_switch_layer, + [PCEP_OBJ_CLASS_REQ_ADAP_CAP] = pcep_decode_obj_req_adap_cap, + [PCEP_OBJ_CLASS_SERVER_IND] = pcep_decode_obj_server_ind, + [PCEP_OBJ_CLASS_VENDOR_INFO] = pcep_decode_obj_vendor_info, + [PCEP_OBJ_CLASS_OF] = pcep_decode_obj_objective_function, +}; + +/* Object lengths, including the Object Header. + * Used by pcep_object_get_length() and pcep_object_has_tlvs() */ +static uint8_t pcep_object_class_lengths[] = { + 0, /* Object class 0 unused */ + 8, /* PCEP_OBJ_CLASS_OPEN = 1 */ + 12, /* PCEP_OBJ_CLASS_RP = 2 */ + 16, /* PCEP_OBJ_CLASS_NOPATH = 3, includes 8 for mandatory TLV */ + 0, /* PCEP_OBJ_CLASS_ENDPOINTS = 4, could be ipv4 or ipv6, setting to 0 + */ + 8, /* PCEP_OBJ_CLASS_BANDWIDTH = 5 */ + 12, /* PCEP_OBJ_CLASS_METRIC = 6 */ + 0, /* PCEP_OBJ_CLASS_ERO = 7, setting 0, ROs cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_RRO = 8, setting 0, ROs cannot have TLVs */ + 20, /* PCEP_OBJ_CLASS_LSPA = 9 */ + 0, /* PCEP_OBJ_CLASS_IRO = 10, setting 0, ROs cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_SVEC = 11, SVECs cannot have TLVs */ + 8, /* PCEP_OBJ_CLASS_NOTF = 12 */ + 8, /* PCEP_OBJ_CLASS_ERROR = 13 */ + 0, /* Object class 14 unused */ + 8, /* PCEP_OBJ_CLASS_CLOSE = 15 */ + 0, 0, 0, 0, 0, /* Object classes 16 - 20 are not used */ + 8, /* PCEP_OBJ_CLASS_OF = 21 */ + 0, 0, 0, 0, 0, /* Object classes 22 - 26 are not used */ + 0, 0, 0, 0, 0, /* Object classes 27 - 31 are not used */ + 8, /* PCEP_OBJ_CLASS_LSP = 32 */ + 12, /* PCEP_OBJ_CLASS_SRP = 33 */ + 12, /* PCEP_OBJ_CLASS_VENDOR_INFO = 34 */ + 0, /* Object class 35 unused */ + 0, /* PCEP_OBJ_CLASS_INTER_LAYER = 36, cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_SWITCH_LAYER = 37, cannot have TLVs */ + 0, /* PCEP_OBJ_CLASS_REQ_ADAP_CAP = 38, cannot have TLVs*/ + 8, /* PCEP_OBJ_CLASS_SERVER_IND = 39 */ + 0, /* PCEP_OBJ_CLASS_ASSOCIATION = 40, cannot have TLVs */ +}; + +/* + * The TLVs can have strange length values, since they do not include padding in + * the TLV header length, but that extra padding must be taken into account by + * the enclosing object by rounding up to the next 4 byte boundary. + * Example returned lengths: + * normalize_length(4) = 4, normalize_length(5) = 8, normalize_length(6) + * = 8, normalize_length(7) = 8, normalize_length(8) = 8 + * normalize_length(9) = 12, normalize_length(10) = 12, normalize_length(11) = + * 12, normalize_length(12) = 12, normalize_length(13) = 13... + */ +uint16_t normalize_pcep_tlv_length(uint16_t length) +{ + return (length % 4 == 0) ? length : (length + (4 - (length % 4))); +} + +/* + * Encoding functions + */ +uint16_t pcep_encode_object(struct pcep_object_header *object_hdr, + struct pcep_versioning *versioning, uint8_t *buf) +{ + + if (object_hdr->object_class >= MAX_OBJECT_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot encode unknown Object class [%d]", + __func__, object_hdr->object_class); + return 0; + } + + object_encoder_funcptr obj_encoder = + object_encoders[object_hdr->object_class]; + if (obj_encoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object encoder found for Object class [%d]", + __func__, object_hdr->object_class); + return 0; + } + + uint16_t object_length = OBJECT_HEADER_LENGTH + + obj_encoder(object_hdr, versioning, + buf + OBJECT_HEADER_LENGTH); + double_linked_list_node *node = + (object_hdr->tlv_list == NULL ? NULL + : object_hdr->tlv_list->head); + for (; node != NULL; node = node->next_node) { + /* Returns the length of the TLV, including the TLV header */ + object_length += pcep_encode_tlv( + (struct pcep_object_tlv_header *)node->data, versioning, + buf + object_length); + } + object_length = normalize_pcep_tlv_length(object_length); + write_object_header(object_hdr, object_length, buf); + object_hdr->encoded_object = buf; + object_hdr->encoded_object_length = object_length; + + return object_length; +} + + +/* Object Header + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Object-Class | OT |Res|P|I| Object Length (bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * // (Object body) // + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +void write_object_header(struct pcep_object_header *object_hdr, + uint16_t object_length, uint8_t *buf) +{ + buf[0] = object_hdr->object_class; + buf[1] = ((object_hdr->object_type << 4) + | (object_hdr->flag_p ? OBJECT_HEADER_FLAG_P : 0x00) + | (object_hdr->flag_i ? OBJECT_HEADER_FLAG_I : 0x00)); + uint16_t net_order_length = htons(object_length); + memcpy(buf + 2, &net_order_length, sizeof(net_order_length)); +} + + +/* + * Functions to encode objects + * - they will be passed a pointer to a buffer to write the object body, + * which is past the object header. + * - they should return the object body length, not including the object header + * length. + */ + +uint16_t pcep_encode_obj_open(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_open *open = (struct pcep_object_open *)hdr; + obj_body_buf[0] = (open->open_version << 5) & 0xe0; + obj_body_buf[1] = open->open_keepalive; + obj_body_buf[2] = open->open_deadtimer; + obj_body_buf[3] = open->open_sid; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_rp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_rp *rp = (struct pcep_object_rp *)hdr; + obj_body_buf[3] = ((rp->flag_strict ? OBJECT_RP_FLAG_O : 0x00) + | (rp->flag_bidirectional ? OBJECT_RP_FLAG_B : 0x00) + | (rp->flag_reoptimization ? OBJECT_RP_FLAG_R : 0x00) + | (rp->flag_of ? OBJECT_RP_FLAG_OF : 0x00) + | (rp->priority & 0x07)); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + *uint32_ptr = htonl(rp->request_id); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_notify(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_notify *notify = (struct pcep_object_notify *)hdr; + obj_body_buf[2] = notify->notification_type; + obj_body_buf[3] = notify->notification_value; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_nopath(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_nopath *nopath = (struct pcep_object_nopath *)hdr; + obj_body_buf[0] = nopath->ni; + obj_body_buf[1] = ((nopath->flag_c) ? OBJECT_NOPATH_FLAG_C : 0x00); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_association(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + uint16_t *uint16_ptr = (uint16_t *)obj_body_buf; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + if (hdr->object_type == PCEP_OBJ_TYPE_ASSOCIATION_IPV4) { + struct pcep_object_association_ipv4 *ipv4 = + (struct pcep_object_association_ipv4 *)hdr; + obj_body_buf[3] = + (ipv4->R_flag ? OBJECT_ASSOCIATION_FLAG_R : 0x00); + uint16_ptr[2] = htons(ipv4->association_type); + uint16_ptr[3] = htons(ipv4->association_id); + uint32_ptr[2] = ipv4->src.s_addr; + + return LENGTH_3WORDS; + } else { + struct pcep_object_association_ipv6 *ipv6 = + (struct pcep_object_association_ipv6 *)hdr; + obj_body_buf[3] = + (ipv6->R_flag ? OBJECT_ASSOCIATION_FLAG_R : 0x00); + uint16_ptr[2] = htons(ipv6->association_type); + uint16_ptr[3] = htons(ipv6->association_id); + memcpy(uint32_ptr, &ipv6->src, sizeof(struct in6_addr)); + + return LENGTH_6WORDS; + } +} + +uint16_t pcep_encode_obj_endpoints(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + struct pcep_object_endpoints_ipv4 *ipv4 = + (struct pcep_object_endpoints_ipv4 *)hdr; + uint32_ptr[0] = ipv4->src_ipv4.s_addr; + uint32_ptr[1] = ipv4->dst_ipv4.s_addr; + + return LENGTH_2WORDS; + } else { + struct pcep_object_endpoints_ipv6 *ipv6 = + (struct pcep_object_endpoints_ipv6 *)hdr; + memcpy(uint32_ptr, &ipv6->src_ipv6, sizeof(struct in6_addr)); + memcpy(&uint32_ptr[4], &ipv6->dst_ipv6, + sizeof(struct in6_addr)); + + return LENGTH_8WORDS; + } +} + +uint16_t pcep_encode_obj_bandwidth(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_bandwidth *bandwidth = + (struct pcep_object_bandwidth *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + /* Seems like the compiler doesn't correctly copy the float, so memcpy() + * it */ + memcpy(uint32_ptr, &(bandwidth->bandwidth), sizeof(uint32_t)); + *uint32_ptr = htonl(*uint32_ptr); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_metric(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_metric *metric = (struct pcep_object_metric *)hdr; + obj_body_buf[2] = ((metric->flag_c ? OBJECT_METRIC_FLAC_C : 0x00) + | (metric->flag_b ? OBJECT_METRIC_FLAC_B : 0x00)); + obj_body_buf[3] = metric->type; + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + /* Seems like the compiler doesn't correctly copy the float, so memcpy() + * it */ + memcpy(uint32_ptr, &(metric->value), sizeof(uint32_t)); + *uint32_ptr = htonl(*uint32_ptr); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_lspa(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_lspa *lspa = (struct pcep_object_lspa *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl(lspa->lspa_exclude_any); + uint32_ptr[1] = htonl(lspa->lspa_include_any); + uint32_ptr[2] = htonl(lspa->lspa_include_all); + obj_body_buf[12] = lspa->setup_priority; + obj_body_buf[13] = lspa->holding_priority; + obj_body_buf[14] = + (lspa->flag_local_protection ? OBJECT_LSPA_FLAG_L : 0x00); + + return LENGTH_4WORDS; +} + +uint16_t pcep_encode_obj_svec(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_svec *svec = (struct pcep_object_svec *)hdr; + obj_body_buf[3] = + ((svec->flag_srlg_diverse ? OBJECT_SVEC_FLAG_S : 0x00) + | (svec->flag_node_diverse ? OBJECT_SVEC_FLAG_N : 0x00) + | (svec->flag_link_diverse ? OBJECT_SVEC_FLAG_L : 0x00)); + + if (svec->request_id_list == NULL) { + return LENGTH_1WORD; + } + + int index = 1; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + double_linked_list_node *node = svec->request_id_list->head; + for (; node != NULL; node = node->next_node) { + uint32_ptr[index++] = htonl(*((uint32_t *)(node->data))); + } + + return LENGTH_1WORD + + (svec->request_id_list->num_entries * sizeof(uint32_t)); +} + +uint16_t pcep_encode_obj_error(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_error *error = (struct pcep_object_error *)hdr; + obj_body_buf[2] = error->error_type; + obj_body_buf[3] = error->error_value; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_close(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_close *close = (struct pcep_object_close *)hdr; + obj_body_buf[3] = close->reason; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_srp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_srp *srp = (struct pcep_object_srp *)hdr; + obj_body_buf[3] = (srp->flag_lsp_remove ? OBJECT_SRP_FLAG_R : 0x00); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + 4); + *uint32_ptr = htonl(srp->srp_id_number); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_lsp(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl((lsp->plsp_id << 12) & 0xfffff000); + obj_body_buf[3] = ((lsp->flag_c ? OBJECT_LSP_FLAG_C : 0x00) + | ((lsp->operational_status << 4) & 0x70) + | (lsp->flag_a ? OBJECT_LSP_FLAG_A : 0x00) + | (lsp->flag_r ? OBJECT_LSP_FLAG_R : 0x00) + | (lsp->flag_s ? OBJECT_LSP_FLAG_S : 0x00) + | (lsp->flag_d ? OBJECT_LSP_FLAG_D : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_vendor_info(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)hdr; + uint32_t *uint32_ptr = (uint32_t *)obj_body_buf; + uint32_ptr[0] = htonl(obj->enterprise_number); + uint32_ptr[1] = htonl(obj->enterprise_specific_info); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_obj_inter_layer(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)hdr; + obj_body_buf[3] = ((obj->flag_i ? OBJECT_INTER_LAYER_FLAG_I : 0x00) + | (obj->flag_m ? OBJECT_INTER_LAYER_FLAG_M : 0x00) + | (obj->flag_t ? OBJECT_INTER_LAYER_FLAG_T : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_switch_layer(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)hdr; + uint8_t buf_index = 0; + + double_linked_list_node *node = obj->switch_layer_rows->head; + while (node != NULL) { + struct pcep_object_switch_layer_row *row = node->data; + if (row == NULL) { + break; + } + + obj_body_buf[buf_index] = row->lsp_encoding_type; + obj_body_buf[buf_index + 1] = row->switching_type; + obj_body_buf[buf_index + 3] = + (row->flag_i ? OBJECT_SWITCH_LAYER_FLAG_I : 0x00); + + buf_index += LENGTH_1WORD; + } + + return buf_index; +} + +uint16_t pcep_encode_obj_req_adap_cap(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)hdr; + + obj_body_buf[0] = obj->switching_capability; + obj_body_buf[1] = obj->encoding; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_server_ind(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *)hdr; + + obj_body_buf[0] = obj->switching_capability; + obj_body_buf[1] = obj->encoding; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_objective_function(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *)hdr; + + uint16_t *uint16_ptr = (uint16_t *)obj_body_buf; + *uint16_ptr = htons(obj->of_code); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_obj_ro(struct pcep_object_header *hdr, + struct pcep_versioning *versioning, + uint8_t *obj_body_buf) +{ + (void)versioning; + struct pcep_object_ro *ro = (struct pcep_object_ro *)hdr; + if (ro == NULL || ro->sub_objects == NULL) { + return 0; + } + + /* RO Subobject format + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + * |L| Type | Length | (Subobject contents) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + */ + + uint16_t index = 0; + double_linked_list_node *node = ro->sub_objects->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = node->data; + obj_body_buf[index++] = + ((ro_subobj->flag_subobj_loose_hop ? 0x80 : 0x00) + | (ro_subobj->ro_subobj_type)); + /* The length will be written below, depending on the subobj + * type */ + uint8_t *length_ptr = &(obj_body_buf[index++]); + uint32_t *uint32_ptr = (uint32_t *)(obj_body_buf + index); + + /* - The index has already been incremented past the header, + * and now points to the ro_subobj body. Below it just needs + * to be incremented past the body. + * + * - Each section below needs to write the total length, + * including the 2 byte subobj header. */ + + switch (ro_subobj->ro_subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: { + struct pcep_ro_subobj_ipv4 *ipv4 = + (struct pcep_ro_subobj_ipv4 *)ro_subobj; + uint32_ptr[0] = ipv4->ip_addr.s_addr; + index += LENGTH_1WORD; + obj_body_buf[index++] = ipv4->prefix_length; + obj_body_buf[index++] = + (ipv4->flag_local_protection + ? OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT + : 0x00); + *length_ptr = LENGTH_2WORDS; + } break; + + case RO_SUBOBJ_TYPE_IPV6: { + struct pcep_ro_subobj_ipv6 *ipv6 = + (struct pcep_ro_subobj_ipv6 *)ro_subobj; + encode_ipv6(&ipv6->ip_addr, uint32_ptr); + index += LENGTH_4WORDS; + obj_body_buf[index++] = ipv6->prefix_length; + obj_body_buf[index++] = + (ipv6->flag_local_protection + ? OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT + : 0x00); + *length_ptr = LENGTH_5WORDS; + } break; + + case RO_SUBOBJ_TYPE_LABEL: { + struct pcep_ro_subobj_32label *label = + (struct pcep_ro_subobj_32label *)ro_subobj; + obj_body_buf[index++] = + (label->flag_global_label + ? OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL + : 0x00); + obj_body_buf[index++] = label->class_type; + uint32_ptr = (uint32_t *)(obj_body_buf + index); + *uint32_ptr = htonl(label->label); + *length_ptr = LENGTH_2WORDS; + index += LENGTH_1WORD; + } break; + + case RO_SUBOBJ_TYPE_UNNUM: { + struct pcep_ro_subobj_unnum *unum = + (struct pcep_ro_subobj_unnum *)ro_subobj; + index += 2; /* increment past 2 reserved bytes */ + uint32_ptr = (uint32_t *)(obj_body_buf + index); + uint32_ptr[0] = unum->router_id.s_addr; + uint32_ptr[1] = htonl(unum->interface_id); + *length_ptr = LENGTH_3WORDS; + index += LENGTH_2WORDS; + } break; + + case RO_SUBOBJ_TYPE_ASN: { + struct pcep_ro_subobj_asn *asn = + (struct pcep_ro_subobj_asn *)ro_subobj; + uint16_t *uint16_ptr = + (uint16_t *)(obj_body_buf + index); + *uint16_ptr = htons(asn->asn); + *length_ptr = LENGTH_1WORD; + index += 2; + } break; + + case RO_SUBOBJ_TYPE_SR: { + /* SR-ERO subobject format + * + * 0 1 2 3 0 1 2 3 4 + * 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |L| Type=36 | Length | NT | Flags + * |F|S|C|M| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SID (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * // NAI (variable, optional) // + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + struct pcep_ro_subobj_sr *sr_subobj = + (struct pcep_ro_subobj_sr *)ro_subobj; + obj_body_buf[index++] = + ((sr_subobj->nai_type << 4) & 0xf0); + obj_body_buf[index++] = + ((sr_subobj->flag_f ? OBJECT_SUBOBJ_SR_FLAG_F + : 0x00) + | (sr_subobj->flag_s ? OBJECT_SUBOBJ_SR_FLAG_S + : 0x00) + | (sr_subobj->flag_c ? OBJECT_SUBOBJ_SR_FLAG_C + : 0x00) + | (sr_subobj->flag_m ? OBJECT_SUBOBJ_SR_FLAG_M + : 0x00)); + uint32_ptr = (uint32_t *)(obj_body_buf + index); + /* Start with LENGTH_1WORD for the SubObj HDR + NT + + * Flags */ + uint8_t sr_base_length = LENGTH_1WORD; + /* If the sid_absent flag is true, then dont convert the + * sid */ + if (sr_subobj->flag_s == false) { + uint32_ptr[0] = htonl(sr_subobj->sid); + index += LENGTH_1WORD; + uint32_ptr = (uint32_t *)(obj_body_buf + index); + sr_base_length += LENGTH_1WORD; + } + + /* The lengths below need to include: + * - sr_base_length: set above to include SR SubObj Hdr + * and the SID if present + * - Number of bytes written to the NAI + * The index will only be incremented below by the + * number of bytes written to the NAI, since the RO SR + * subobj header and the SID have already been written. + */ + + double_linked_list_node *nai_node = + (sr_subobj->nai_list == NULL + ? NULL + : sr_subobj->nai_list->head); + if (nai_node == NULL) { + if (sr_subobj->nai_type + == PCEP_SR_SUBOBJ_NAI_ABSENT) { + *length_ptr = sr_base_length; + continue; + } else { + return 0; + } + } + switch (sr_subobj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_1WORD; + index += LENGTH_1WORD; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + *length_ptr = sr_base_length + LENGTH_4WORDS; + index += LENGTH_4WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[1] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[2] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[3] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_4WORDS; + index += LENGTH_4WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: + uint32_ptr[0] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + uint32_ptr[1] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_2WORDS; + index += LENGTH_2WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + nai_node = nai_node->next_node; + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr + 4); + *length_ptr = sr_base_length + LENGTH_8WORDS; + index += LENGTH_8WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr); + nai_node = nai_node->next_node; + uint32_ptr[4] = + ((struct in_addr *)nai_node->data) + ->s_addr; + nai_node = nai_node->next_node; + encode_ipv6((struct in6_addr *)nai_node->data, + uint32_ptr + 5); + nai_node = nai_node->next_node; + uint32_ptr[9] = + ((struct in_addr *)nai_node->data) + ->s_addr; + *length_ptr = sr_base_length + LENGTH_10WORDS; + index += LENGTH_10WORDS; + break; + + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + break; + } + } break; + + case RO_SUBOBJ_UNKNOWN: + break; + } + } + + return index; +} + +void encode_ipv6(struct in6_addr *src_ipv6, uint32_t *dst) +{ + memcpy(dst, src_ipv6, sizeof(struct in6_addr)); +} + +/* + * Decoding functions. + */ + +void pcep_decode_object_hdr(const uint8_t *obj_buf, + struct pcep_object_header *obj_hdr) +{ + memset(obj_hdr, 0, sizeof(struct pcep_object_header)); + + obj_hdr->object_class = obj_buf[0]; + obj_hdr->object_type = (obj_buf[1] >> 4) & 0x0f; + obj_hdr->flag_p = (obj_buf[1] & OBJECT_HEADER_FLAG_P); + obj_hdr->flag_i = (obj_buf[1] & OBJECT_HEADER_FLAG_I); + uint16_t net_order_length; + memcpy(&net_order_length, obj_buf + 2, sizeof(net_order_length)); + obj_hdr->encoded_object_length = ntohs(net_order_length); + obj_hdr->encoded_object = obj_buf; +} + +uint16_t pcep_object_get_length(enum pcep_object_classes object_class, + enum pcep_object_types object_type) +{ + uint8_t object_length = pcep_object_class_lengths[object_class]; + if (object_length == 0) { + if (object_class == PCEP_OBJ_CLASS_ENDPOINTS) { + if (object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + return 12; + } else if (object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + return 36; + } + } + + return 0; + } + + return object_length; +} + +uint16_t pcep_object_get_length_by_hdr(struct pcep_object_header *object_hdr) +{ + return (pcep_object_get_length(object_hdr->object_class, + object_hdr->object_type)); +} + +bool pcep_object_has_tlvs(struct pcep_object_header *object_hdr) +{ + uint8_t object_length = pcep_object_get_length_by_hdr(object_hdr); + if (object_length == 0) { + return false; + } + + return (object_hdr->encoded_object_length - object_length) > 0; +} + +struct pcep_object_header *pcep_decode_object(const uint8_t *obj_buf) +{ + + struct pcep_object_header object_hdr; + /* Only initializes and decodes the Object Header: class, type, flags, + * and length */ + pcep_decode_object_hdr(obj_buf, &object_hdr); + + if (object_hdr.object_class >= MAX_OBJECT_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot decode unknown Object class [%d]", + __func__, object_hdr.object_class); + return NULL; + } + + object_decoder_funcptr obj_decoder = + object_decoders[object_hdr.object_class]; + if (obj_decoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object decoder found for Object class [%d]", + __func__, object_hdr.object_class); + return NULL; + } + + /* The object decoders will start decoding the object body, if + * anything from the header is needed, they have the object_hdr */ + struct pcep_object_header *object = + obj_decoder(&object_hdr, obj_buf + OBJECT_HEADER_LENGTH); + if (object == NULL) { + pcep_log(LOG_INFO, "%s: Unable to decode Object class [%d].", + __func__, object_hdr.object_class); + return NULL; + } + + if (pcep_object_has_tlvs(&object_hdr)) { + object->tlv_list = dll_initialize(); + int num_iterations = 0; + uint16_t tlv_index = pcep_object_get_length_by_hdr(&object_hdr); + while ((object->encoded_object_length - tlv_index) > 0 + && num_iterations++ < MAX_ITERATIONS) { + struct pcep_object_tlv_header *tlv = + pcep_decode_tlv(obj_buf + tlv_index); + if (tlv == NULL) { + /* TODO should we do anything else here ? */ + return object; + } + + /* The TLV length does not include the TLV header */ + tlv_index += normalize_pcep_tlv_length( + tlv->encoded_tlv_length + TLV_HEADER_LENGTH); + dll_append(object->tlv_list, tlv); + } + } + + return object; +} + +static struct pcep_object_header * +common_object_create(struct pcep_object_header *hdr, uint16_t new_obj_length) +{ + struct pcep_object_header *new_object = + pceplib_malloc(PCEPLIB_MESSAGES, new_obj_length); + memset(new_object, 0, new_obj_length); + memcpy(new_object, hdr, sizeof(struct pcep_object_header)); + + return new_object; +} + +/* + * Decoders + */ + +struct pcep_object_header *pcep_decode_obj_open(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_open *obj = + (struct pcep_object_open *)common_object_create( + hdr, sizeof(struct pcep_object_open)); + + obj->open_version = (obj_buf[0] >> 5) & 0x07; + obj->open_keepalive = obj_buf[1]; + obj->open_deadtimer = obj_buf[2]; + obj->open_sid = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_rp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_rp *obj = + (struct pcep_object_rp *)common_object_create( + hdr, sizeof(struct pcep_object_rp)); + + obj->flag_reoptimization = (obj_buf[3] & OBJECT_RP_FLAG_R); + obj->flag_bidirectional = (obj_buf[3] & OBJECT_RP_FLAG_B); + obj->flag_strict = (obj_buf[3] & OBJECT_RP_FLAG_O); + obj->flag_of = (obj_buf[3] & OBJECT_RP_FLAG_OF); + obj->priority = (obj_buf[3] & 0x07); + obj->request_id = ntohl(*((uint32_t *)(obj_buf + 4))); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_notify(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_notify *obj = + (struct pcep_object_notify *)common_object_create( + hdr, sizeof(struct pcep_object_notify)); + + obj->notification_type = obj_buf[2]; + obj->notification_value = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_nopath(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_nopath *obj = + (struct pcep_object_nopath *)common_object_create( + hdr, sizeof(struct pcep_object_nopath)); + + obj->ni = (obj_buf[0] >> 1); + obj->flag_c = (obj_buf[0] & OBJECT_NOPATH_FLAG_C); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_association(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + uint16_t *uint16_ptr = (uint16_t *)obj_buf; + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + if (hdr->object_type == PCEP_OBJ_TYPE_ASSOCIATION_IPV4) { + struct pcep_object_association_ipv4 *obj = + (struct pcep_object_association_ipv4 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_association_ipv4)); + obj->R_flag = (obj_buf[3] & OBJECT_ASSOCIATION_FLAG_R); + obj->association_type = ntohs(uint16_ptr[2]); + obj->association_id = ntohs(uint16_ptr[3]); + obj->src.s_addr = uint32_ptr[2]; + + return (struct pcep_object_header *)obj; + } else if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + struct pcep_object_association_ipv6 *obj = + (struct pcep_object_association_ipv6 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_association_ipv6)); + + obj->R_flag = (obj_buf[3] & OBJECT_ASSOCIATION_FLAG_R); + obj->association_type = ntohs(uint16_ptr[2]); + obj->association_id = ntohs(uint16_ptr[3]); + memcpy(&obj->src, &uint32_ptr[2], sizeof(struct in6_addr)); + + return (struct pcep_object_header *)obj; + } + + return NULL; +} +struct pcep_object_header * +pcep_decode_obj_endpoints(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV4) { + struct pcep_object_endpoints_ipv4 *obj = + (struct pcep_object_endpoints_ipv4 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_endpoints_ipv4)); + obj->src_ipv4.s_addr = uint32_ptr[0]; + obj->dst_ipv4.s_addr = uint32_ptr[1]; + + return (struct pcep_object_header *)obj; + } else if (hdr->object_type == PCEP_OBJ_TYPE_ENDPOINT_IPV6) { + struct pcep_object_endpoints_ipv6 *obj = + (struct pcep_object_endpoints_ipv6 *) + common_object_create( + hdr, + sizeof(struct + pcep_object_endpoints_ipv6)); + + memcpy(&obj->src_ipv6, &uint32_ptr[0], sizeof(struct in6_addr)); + memcpy(&obj->dst_ipv6, &uint32_ptr[4], sizeof(struct in6_addr)); + + return (struct pcep_object_header *)obj; + } + + return NULL; +} + +struct pcep_object_header * +pcep_decode_obj_bandwidth(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_bandwidth *obj = + (struct pcep_object_bandwidth *)common_object_create( + hdr, sizeof(struct pcep_object_bandwidth)); + + uint32_t value = ntohl(*((uint32_t *)obj_buf)); + /* Seems like the compiler doesn't correctly copy to the float, so + * memcpy() it */ + memcpy(&obj->bandwidth, &value, sizeof(uint32_t)); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_metric(struct pcep_object_header *hdr, const uint8_t *obj_buf) +{ + struct pcep_object_metric *obj = + (struct pcep_object_metric *)common_object_create( + hdr, sizeof(struct pcep_object_metric)); + obj->flag_b = (obj_buf[2] & OBJECT_METRIC_FLAC_B); + obj->flag_c = (obj_buf[2] & OBJECT_METRIC_FLAC_C); + obj->type = obj_buf[3]; + uint32_t value = ntohl(*((uint32_t *)(obj_buf + 4))); + /* Seems like the compiler doesn't correctly copy to the float, so + * memcpy() it */ + memcpy(&obj->value, &value, sizeof(uint32_t)); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_lspa(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_lspa *obj = + (struct pcep_object_lspa *)common_object_create( + hdr, sizeof(struct pcep_object_lspa)); + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + + obj->lspa_exclude_any = ntohl(uint32_ptr[0]); + obj->lspa_include_any = ntohl(uint32_ptr[1]); + obj->lspa_include_all = ntohl(uint32_ptr[2]); + obj->setup_priority = obj_buf[12]; + obj->holding_priority = obj_buf[13]; + obj->flag_local_protection = (obj_buf[14] & OBJECT_LSPA_FLAG_L); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_svec(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_svec *obj = + (struct pcep_object_svec *)common_object_create( + hdr, sizeof(struct pcep_object_svec)); + + obj->flag_link_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_L); + obj->flag_node_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_N); + obj->flag_srlg_diverse = (obj_buf[3] & OBJECT_SVEC_FLAG_S); + + if (hdr->encoded_object_length > LENGTH_2WORDS) { + obj->request_id_list = dll_initialize(); + uint16_t index = 1; + uint32_t *uint32_ptr = (uint32_t *)obj_buf; + for (; + index < ((hdr->encoded_object_length - LENGTH_2WORDS) / 4); + index++) { + uint32_t *req_id_ptr = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(uint32_t)); + *req_id_ptr = uint32_ptr[index]; + dll_append(obj->request_id_list, req_id_ptr); + } + } + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_error(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_error *obj = + (struct pcep_object_error *)common_object_create( + hdr, sizeof(struct pcep_object_error)); + + obj->error_type = obj_buf[2]; + obj->error_value = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_close(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_close *obj = + (struct pcep_object_close *)common_object_create( + hdr, sizeof(struct pcep_object_close)); + + obj->reason = obj_buf[3]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_srp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_srp *obj = + (struct pcep_object_srp *)common_object_create( + hdr, sizeof(struct pcep_object_srp)); + + obj->flag_lsp_remove = (obj_buf[3] & OBJECT_SRP_FLAG_R); + obj->srp_id_number = ntohl(*((uint32_t *)(obj_buf + 4))); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header *pcep_decode_obj_lsp(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_lsp *obj = + (struct pcep_object_lsp *)common_object_create( + hdr, sizeof(struct pcep_object_lsp)); + + obj->flag_d = (obj_buf[3] & OBJECT_LSP_FLAG_D); + obj->flag_s = (obj_buf[3] & OBJECT_LSP_FLAG_S); + obj->flag_r = (obj_buf[3] & OBJECT_LSP_FLAG_R); + obj->flag_a = (obj_buf[3] & OBJECT_LSP_FLAG_A); + obj->flag_c = (obj_buf[3] & OBJECT_LSP_FLAG_C); + obj->operational_status = ((obj_buf[3] >> 4) & 0x07); + obj->plsp_id = ((ntohl(*((uint32_t *)obj_buf)) >> 12) & 0x000fffff); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_vendor_info(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_vendor_info *obj = + (struct pcep_object_vendor_info *)common_object_create( + hdr, sizeof(struct pcep_object_vendor_info)); + + obj->enterprise_number = ntohl(*((uint32_t *)(obj_buf))); + obj->enterprise_specific_info = ntohl(*((uint32_t *)(obj_buf + 4))); + if (obj->enterprise_number == ENTERPRISE_NUMBER_CISCO + && obj->enterprise_specific_info == ENTERPRISE_COLOR_CISCO) + obj->enterprise_specific_info1 = + ntohl(*((uint32_t *)(obj_buf + 8))); + else + obj->enterprise_specific_info1 = 0; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_inter_layer(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_inter_layer *obj = + (struct pcep_object_inter_layer *)common_object_create( + hdr, sizeof(struct pcep_object_inter_layer)); + obj->flag_t = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_T); + obj->flag_m = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_M); + obj->flag_i = (obj_buf[3] & OBJECT_INTER_LAYER_FLAG_I); + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_switch_layer(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_switch_layer *obj = + (struct pcep_object_switch_layer *)common_object_create( + hdr, sizeof(struct pcep_object_switch_layer)); + obj->switch_layer_rows = dll_initialize(); + int num_rows = ((hdr->encoded_object_length - 4) / 4); + uint8_t buf_index = 0; + + int i = 0; + for (; i < num_rows; i++) { + struct pcep_object_switch_layer_row *row = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_object_switch_layer_row)); + row->lsp_encoding_type = obj_buf[buf_index]; + row->switching_type = obj_buf[buf_index + 1]; + row->flag_i = + (obj_buf[buf_index + 3] & OBJECT_SWITCH_LAYER_FLAG_I); + dll_append(obj->switch_layer_rows, row); + + buf_index += LENGTH_1WORD; + } + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_req_adap_cap(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_req_adap_cap *obj = + (struct pcep_object_req_adap_cap *)common_object_create( + hdr, sizeof(struct pcep_object_req_adap_cap)); + + obj->switching_capability = obj_buf[0]; + obj->encoding = obj_buf[1]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_server_ind(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_server_indication *obj = + (struct pcep_object_server_indication *)common_object_create( + hdr, sizeof(struct pcep_object_server_indication)); + + obj->switching_capability = obj_buf[0]; + obj->encoding = obj_buf[1]; + + return (struct pcep_object_header *)obj; +} + +struct pcep_object_header * +pcep_decode_obj_objective_function(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_objective_function *obj = + (struct pcep_object_objective_function *)common_object_create( + hdr, sizeof(struct pcep_object_objective_function)); + + uint16_t *uint16_ptr = (uint16_t *)obj_buf; + obj->of_code = ntohs(*uint16_ptr); + + return (struct pcep_object_header *)obj; +} + +void set_ro_subobj_fields(struct pcep_object_ro_subobj *subobj, bool flag_l, + uint8_t subobj_type) +{ + subobj->flag_subobj_loose_hop = flag_l; + subobj->ro_subobj_type = subobj_type; +} + +void decode_ipv6(const uint32_t *src, struct in6_addr *dst_ipv6) +{ + memcpy(dst_ipv6, src, sizeof(struct in6_addr)); +} +struct pcep_object_header *pcep_decode_obj_ro(struct pcep_object_header *hdr, + const uint8_t *obj_buf) +{ + struct pcep_object_ro *obj = + (struct pcep_object_ro *)common_object_create( + hdr, sizeof(struct pcep_object_ro)); + obj->sub_objects = dll_initialize(); + + /* RO Subobject format + * + * 0 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + * |L| Type | Length | (Subobject contents) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------//----------------+ + */ + + uint16_t read_count = 0; + int num_sub_objects = 1; + uint32_t *uint32_ptr; + uint16_t obj_body_length = + hdr->encoded_object_length - OBJECT_HEADER_LENGTH; + + while ((obj_body_length - read_count) > OBJECT_RO_SUBOBJ_HEADER_LENGTH + && num_sub_objects < MAX_ITERATIONS) { + num_sub_objects++; + /* Read the Sub-Object Header */ + bool flag_l = (obj_buf[read_count] & 0x80); + uint8_t subobj_type = (obj_buf[read_count++] & 0x7f); + uint8_t subobj_length = obj_buf[read_count++]; + + if (subobj_length <= OBJECT_RO_SUBOBJ_HEADER_LENGTH) { + pcep_log(LOG_INFO, + "%s: Invalid ro subobj type [%d] length [%d]", + __func__, subobj_type, subobj_length); + pceplib_free(PCEPLIB_MESSAGES, obj); + return NULL; + } + + switch (subobj_type) { + case RO_SUBOBJ_TYPE_IPV4: { + struct pcep_ro_subobj_ipv4 *ipv4 = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_ipv4)); + ipv4->ro_subobj.flag_subobj_loose_hop = flag_l; + ipv4->ro_subobj.ro_subobj_type = subobj_type; + uint32_ptr = (uint32_t *)(obj_buf + read_count); + ipv4->ip_addr.s_addr = *uint32_ptr; + read_count += LENGTH_1WORD; + ipv4->prefix_length = obj_buf[read_count++]; + ipv4->flag_local_protection = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + + dll_append(obj->sub_objects, ipv4); + } break; + + case RO_SUBOBJ_TYPE_IPV6: { + struct pcep_ro_subobj_ipv6 *ipv6 = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_ipv6)); + ipv6->ro_subobj.flag_subobj_loose_hop = flag_l; + ipv6->ro_subobj.ro_subobj_type = subobj_type; + decode_ipv6((uint32_t *)obj_buf, &ipv6->ip_addr); + read_count += LENGTH_4WORDS; + ipv6->prefix_length = obj_buf[read_count++]; + ipv6->flag_local_protection = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + + dll_append(obj->sub_objects, ipv6); + } break; + + case RO_SUBOBJ_TYPE_LABEL: { + struct pcep_ro_subobj_32label *label = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_32label)); + label->ro_subobj.flag_subobj_loose_hop = flag_l; + label->ro_subobj.ro_subobj_type = subobj_type; + label->flag_global_label = + (obj_buf[read_count++] + & OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL); + label->class_type = obj_buf[read_count++]; + label->label = ntohl(obj_buf[read_count]); + read_count += LENGTH_1WORD; + + dll_append(obj->sub_objects, label); + } break; + + case RO_SUBOBJ_TYPE_UNNUM: { + struct pcep_ro_subobj_unnum *unum = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_unnum)); + unum->ro_subobj.flag_subobj_loose_hop = flag_l; + unum->ro_subobj.ro_subobj_type = subobj_type; + set_ro_subobj_fields( + (struct pcep_object_ro_subobj *)unum, flag_l, + subobj_type); + uint32_ptr = (uint32_t *)(obj_buf + read_count); + unum->interface_id = ntohl(uint32_ptr[0]); + unum->router_id.s_addr = uint32_ptr[1]; + read_count += 2; + + dll_append(obj->sub_objects, unum); + } break; + + case RO_SUBOBJ_TYPE_ASN: { + struct pcep_ro_subobj_asn *asn = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_asn)); + asn->ro_subobj.flag_subobj_loose_hop = flag_l; + asn->ro_subobj.ro_subobj_type = subobj_type; + uint16_t *uint16_ptr = + (uint16_t *)(obj_buf + read_count); + asn->asn = ntohs(*uint16_ptr); + read_count += 2; + + dll_append(obj->sub_objects, asn); + } break; + + case RO_SUBOBJ_TYPE_SR: { + /* SR-ERO subobject format + * + * 0 1 2 3 0 1 2 3 4 + * 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |L| Type=36 | Length | NT | Flags + * |F|S|C|M| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SID (optional) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * // NAI (variable, optional) // + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + struct pcep_ro_subobj_sr *sr_subobj = pceplib_malloc( + PCEPLIB_MESSAGES, + sizeof(struct pcep_ro_subobj_sr)); + sr_subobj->ro_subobj.flag_subobj_loose_hop = flag_l; + sr_subobj->ro_subobj.ro_subobj_type = subobj_type; + dll_append(obj->sub_objects, sr_subobj); + + sr_subobj->nai_list = dll_initialize(); + sr_subobj->nai_type = + ((obj_buf[read_count++] >> 4) & 0x0f); + sr_subobj->flag_f = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_F); + sr_subobj->flag_s = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_S); + sr_subobj->flag_c = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_C); + sr_subobj->flag_m = + (obj_buf[read_count] & OBJECT_SUBOBJ_SR_FLAG_M); + read_count++; + + /* If the sid_absent flag is true, then dont decode the + * sid */ + uint32_ptr = (uint32_t *)(obj_buf + read_count); + if (sr_subobj->flag_s == false) { + sr_subobj->sid = ntohl(*uint32_ptr); + read_count += LENGTH_1WORD; + uint32_ptr += 1; + } + + switch (sr_subobj->nai_type) { + case PCEP_SR_SUBOBJ_NAI_IPV4_NODE: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = *uint32_ptr; + dll_append(sr_subobj->nai_list, ipv4); + read_count += LENGTH_1WORD; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_NODE: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + read_count += LENGTH_4WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[0]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[1]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[2]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[3]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_4WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY: { + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[0]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[1]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_2WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + ipv6 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr + 4, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + read_count += LENGTH_8WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY: { + struct in6_addr *ipv6 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + struct in_addr *ipv4 = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[4]; + dll_append(sr_subobj->nai_list, ipv4); + + ipv6 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in6_addr)); + decode_ipv6(uint32_ptr + 5, ipv6); + dll_append(sr_subobj->nai_list, ipv6); + + ipv4 = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct in_addr)); + ipv4->s_addr = uint32_ptr[9]; + dll_append(sr_subobj->nai_list, ipv4); + + read_count += LENGTH_10WORDS; + } break; + + case PCEP_SR_SUBOBJ_NAI_ABSENT: + case PCEP_SR_SUBOBJ_NAI_UNKNOWN: + break; + } + } break; + + default: + pcep_log( + LOG_INFO, + "%s: pcep_decode_obj_ro skipping unrecognized sub-object type [%d]", + __func__, subobj_type); + read_count += subobj_length; + break; + } + } + + return (struct pcep_object_header *)obj; +} diff --git a/pceplib/pcep_msg_tlvs.c b/pceplib/pcep_msg_tlvs.c new file mode 100644 index 0000000..86a7db6 --- /dev/null +++ b/pceplib/pcep_msg_tlvs.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * This is the implementation of a High Level PCEP message object TLV API. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "pcep_msg_tlvs.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_memory.h" + +static struct pcep_object_tlv_header * +pcep_tlv_common_create(enum pcep_object_tlv_types type, uint16_t size) +{ + struct pcep_object_tlv_header *tlv = + pceplib_malloc(PCEPLIB_MESSAGES, size); + memset(tlv, 0, size); + tlv->type = type; + + return tlv; +} + +/* + * Open Object TLVs + */ + +struct pcep_object_tlv_stateful_pce_capability * +pcep_tlv_create_stateful_pce_capability( + bool flag_u_lsp_update_capability, bool flag_s_include_db_version, + bool flag_i_lsp_instantiation_capability, bool flag_t_triggered_resync, + bool flag_d_delta_lsp_sync, bool flag_f_triggered_initial_sync) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY, + sizeof(struct + pcep_object_tlv_stateful_pce_capability)); + tlv->flag_u_lsp_update_capability = flag_u_lsp_update_capability; + tlv->flag_s_include_db_version = flag_s_include_db_version; + tlv->flag_i_lsp_instantiation_capability = + flag_i_lsp_instantiation_capability; + tlv->flag_t_triggered_resync = flag_t_triggered_resync; + tlv->flag_d_delta_lsp_sync = flag_d_delta_lsp_sync; + tlv->flag_f_triggered_initial_sync = flag_f_triggered_initial_sync; + + return tlv; +} + +struct pcep_object_tlv_lsp_db_version * +pcep_tlv_create_lsp_db_version(uint64_t lsp_db_version) +{ + struct pcep_object_tlv_lsp_db_version *tlv = + (struct pcep_object_tlv_lsp_db_version *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION, + sizeof(struct pcep_object_tlv_lsp_db_version)); + tlv->lsp_db_version = lsp_db_version; + + return tlv; +} + +struct pcep_object_tlv_speaker_entity_identifier * +pcep_tlv_create_speaker_entity_id(double_linked_list *speaker_entity_id_list) +{ + if (speaker_entity_id_list == NULL) { + return NULL; + } + + if (speaker_entity_id_list->num_entries == 0) { + return NULL; + } + + struct pcep_object_tlv_speaker_entity_identifier *tlv = + (struct pcep_object_tlv_speaker_entity_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID, + sizeof(struct + pcep_object_tlv_speaker_entity_identifier)); + tlv->speaker_entity_id_list = speaker_entity_id_list; + + return tlv; +} + +struct pcep_object_tlv_path_setup_type * +pcep_tlv_create_path_setup_type(uint8_t pst) +{ + struct pcep_object_tlv_path_setup_type *tlv = + (struct pcep_object_tlv_path_setup_type *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE, + sizeof(struct pcep_object_tlv_path_setup_type)); + tlv->path_setup_type = pst; + + return tlv; +} + +struct pcep_object_tlv_path_setup_type_capability * +pcep_tlv_create_path_setup_type_capability(double_linked_list *pst_list, + double_linked_list *sub_tlv_list) +{ + if (pst_list == NULL) { + return NULL; + } + + if (pst_list->num_entries == 0) { + return NULL; + } + + struct pcep_object_tlv_path_setup_type_capability *tlv = + (struct pcep_object_tlv_path_setup_type_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY, + sizeof(struct + pcep_object_tlv_path_setup_type_capability)); + + tlv->pst_list = pst_list; + tlv->sub_tlv_list = sub_tlv_list; + + return tlv; +} + +struct pcep_object_tlv_sr_pce_capability * +pcep_tlv_create_sr_pce_capability(bool flag_n, bool flag_x, + uint8_t max_sid_depth) +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + (struct pcep_object_tlv_sr_pce_capability *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY, + sizeof(struct + pcep_object_tlv_sr_pce_capability)); + tlv->flag_n = flag_n; + tlv->flag_x = flag_x; + tlv->max_sid_depth = max_sid_depth; + + return tlv; +} + +struct pcep_object_tlv_of_list * +pcep_tlv_create_of_list(double_linked_list *of_list) +{ + if (of_list == NULL) { + return NULL; + } + + struct pcep_object_tlv_of_list *tlv = + (struct pcep_object_tlv_of_list *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST, + sizeof(struct pcep_object_tlv_of_list)); + + tlv->of_list = of_list; + + return tlv; +} + +/* + * LSP Object TLVs + */ + +struct pcep_object_tlv_ipv4_lsp_identifier * +pcep_tlv_create_ipv4_lsp_identifiers(struct in_addr *ipv4_tunnel_sender, + struct in_addr *ipv4_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in_addr *extended_tunnel_id) +{ + if (ipv4_tunnel_sender == NULL || ipv4_tunnel_endpoint == NULL) { + return NULL; + } + + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv4_lsp_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS, + sizeof(struct + pcep_object_tlv_ipv4_lsp_identifier)); + tlv->ipv4_tunnel_sender.s_addr = ipv4_tunnel_sender->s_addr; + tlv->ipv4_tunnel_endpoint.s_addr = ipv4_tunnel_endpoint->s_addr; + tlv->lsp_id = lsp_id; + tlv->tunnel_id = tunnel_id; + tlv->extended_tunnel_id.s_addr = + (extended_tunnel_id == NULL ? INADDR_ANY + : extended_tunnel_id->s_addr); + + return tlv; +} + +struct pcep_object_tlv_ipv6_lsp_identifier * +pcep_tlv_create_ipv6_lsp_identifiers(struct in6_addr *ipv6_tunnel_sender, + struct in6_addr *ipv6_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in6_addr *extended_tunnel_id) +{ + if (ipv6_tunnel_sender == NULL || ipv6_tunnel_endpoint == NULL) { + return NULL; + } + + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv6_lsp_identifier *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS, + sizeof(struct + pcep_object_tlv_ipv6_lsp_identifier)); + + memcpy(&tlv->ipv6_tunnel_sender, ipv6_tunnel_sender, + sizeof(struct in6_addr)); + + tlv->tunnel_id = tunnel_id; + tlv->lsp_id = lsp_id; + + memcpy(&tlv->extended_tunnel_id, extended_tunnel_id, + sizeof(struct in6_addr)); + + memcpy(&tlv->ipv6_tunnel_endpoint, ipv6_tunnel_endpoint, + sizeof(struct in6_addr)); + + return tlv; +} + +struct pcep_object_tlv_symbolic_path_name * +pcep_tlv_create_symbolic_path_name(const char *symbolic_path_name, + uint16_t symbolic_path_name_length) +{ + /* symbolic_path_name_length should NOT include the null terminator and + * cannot be zero */ + if (symbolic_path_name == NULL || symbolic_path_name_length == 0) { + return NULL; + } + + struct pcep_object_tlv_symbolic_path_name *tlv = + (struct pcep_object_tlv_symbolic_path_name *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME, + sizeof(struct + pcep_object_tlv_symbolic_path_name)); + + uint16_t length = (symbolic_path_name_length > MAX_SYMBOLIC_PATH_NAME) + ? MAX_SYMBOLIC_PATH_NAME + : symbolic_path_name_length; + memcpy(tlv->symbolic_path_name, symbolic_path_name, length); + tlv->symbolic_path_name_length = length; + + return tlv; +} + +struct pcep_object_tlv_lsp_error_code * +pcep_tlv_create_lsp_error_code(enum pcep_tlv_lsp_error_codes lsp_error_code) +{ + struct pcep_object_tlv_lsp_error_code *tlv = + (struct pcep_object_tlv_lsp_error_code *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE, + sizeof(struct pcep_object_tlv_lsp_error_code)); + tlv->lsp_error_code = lsp_error_code; + + return tlv; +} + +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv4_error_spec(struct in_addr *error_node_ip, + uint8_t error_code, uint16_t error_value) +{ + if (error_node_ip == NULL) { + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->c_type = RSVP_ERROR_SPEC_IPV4_CTYPE; + tlv->class_num = RSVP_ERROR_SPEC_CLASS_NUM; + tlv->error_code = error_code; + tlv->error_value = error_value; + tlv->error_spec_ip.ipv4_error_node_address.s_addr = + error_node_ip->s_addr; + + return tlv; +} + +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv6_error_spec(struct in6_addr *error_node_ip, + uint8_t error_code, uint16_t error_value) +{ + if (error_node_ip == NULL) { + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *) + pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->c_type = RSVP_ERROR_SPEC_IPV6_CTYPE; + tlv->class_num = RSVP_ERROR_SPEC_CLASS_NUM; + tlv->error_code = error_code; + tlv->error_value = error_value; + memcpy(&tlv->error_spec_ip, error_node_ip, sizeof(struct in6_addr)); + + return tlv; +} + +struct pcep_object_tlv_nopath_vector * +pcep_tlv_create_nopath_vector(uint32_t error_code) +{ + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR, + sizeof(struct pcep_object_tlv_nopath_vector)); + + tlv->error_code = error_code; + + return tlv; +} + +struct pcep_object_tlv_vendor_info * +pcep_tlv_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_specific_info) +{ + struct pcep_object_tlv_vendor_info *tlv = + (struct pcep_object_tlv_vendor_info *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_VENDOR_INFO, + sizeof(struct pcep_object_tlv_vendor_info)); + + tlv->enterprise_number = enterprise_number; + tlv->enterprise_specific_info = enterprise_specific_info; + + return tlv; +} + +/* + * SRPAG (SR Association Group) TLVs + */ + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv4(uint32_t color, struct in_addr *ipv4) +{ + struct pcep_object_tlv_srpag_pol_id *tlv = + (struct pcep_object_tlv_srpag_pol_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + sizeof(struct pcep_object_tlv_srpag_pol_id)); + tlv->color = color; + tlv->is_ipv4 = true; + memcpy(&tlv->end_point.ipv4.s_addr, ipv4, sizeof(struct in_addr)); + + return tlv; +} + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv6(uint32_t color, struct in6_addr *ipv6) +{ + struct pcep_object_tlv_srpag_pol_id *tlv = + (struct pcep_object_tlv_srpag_pol_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + sizeof(struct pcep_object_tlv_srpag_pol_id)); + tlv->color = color; + tlv->is_ipv4 = false; + memcpy(&tlv->end_point.ipv6, ipv6, sizeof(struct in6_addr)); + + return tlv; +} + + +struct pcep_object_tlv_srpag_pol_name * +pcep_tlv_create_srpag_pol_name(const char *pol_name, uint16_t pol_name_length) +{ + if (pol_name == NULL) { + return NULL; + } + struct pcep_object_tlv_srpag_pol_name *tlv = + (struct pcep_object_tlv_srpag_pol_name *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME, + sizeof(struct pcep_object_tlv_srpag_pol_name)); + uint16_t length = + (normalize_pcep_tlv_length(pol_name_length) > MAX_POLICY_NAME) + ? MAX_POLICY_NAME + : pol_name_length; + memcpy(tlv->name, pol_name, length); + tlv->name_length = length; + + return tlv; +} +struct pcep_object_tlv_srpag_cp_id * +pcep_tlv_create_srpag_cp_id(uint8_t proto_origin, uint32_t asn, + struct in6_addr *in6_addr_with_mapped_ipv4, + uint32_t discriminator) +{ + if (!in6_addr_with_mapped_ipv4) { + return NULL; + } + + struct pcep_object_tlv_srpag_cp_id *tlv = + (struct pcep_object_tlv_srpag_cp_id *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID, + sizeof(struct pcep_object_tlv_srpag_cp_id)); + tlv->proto = proto_origin; + tlv->orig_asn = asn; + memcpy(&(tlv->orig_addres), in6_addr_with_mapped_ipv4, + sizeof(*in6_addr_with_mapped_ipv4)); + tlv->discriminator = discriminator; + + return tlv; +} +struct pcep_object_tlv_srpag_cp_pref * +pcep_tlv_create_srpag_cp_pref(uint32_t pref) +{ + + struct pcep_object_tlv_srpag_cp_pref *tlv = + (struct pcep_object_tlv_srpag_cp_pref *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE, + sizeof(struct pcep_object_tlv_srpag_cp_pref)); + tlv->preference = pref; + + return tlv; +} + +struct pcep_object_tlv_arbitrary * +pcep_tlv_create_tlv_arbitrary(const char *data, uint16_t data_length, + int tlv_id) +{ + if (data == NULL || data_length == 0) { + return NULL; + } + + struct pcep_object_tlv_arbitrary *tlv = + (struct pcep_object_tlv_arbitrary *)pcep_tlv_common_create( + PCEP_OBJ_TLV_TYPE_ARBITRARY, + sizeof(struct pcep_object_tlv_arbitrary)); + + uint16_t length = (data_length > MAX_ARBITRARY_SIZE) + ? MAX_ARBITRARY_SIZE + : data_length; + memcpy(tlv->data, data, length); + tlv->data_length = length; + tlv->arbitraty_type = tlv_id; + + return tlv; +} diff --git a/pceplib/pcep_msg_tlvs.h b/pceplib/pcep_msg_tlvs.h new file mode 100644 index 0000000..61f446f --- /dev/null +++ b/pceplib/pcep_msg_tlvs.h @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + + +/* + * This is a High Level PCEP message object TLV API. + */ + +#ifndef PCEP_TLVS_H_ +#define PCEP_TLVS_H_ + +#include +#include + +#include "pcep.h" +#include "pcep_utils_double_linked_list.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Regarding memory usage: + * When creating TLVs, any TLVs passed into messages or objects with these APIs + * will be free'd when the the enclosing pcep_message is free'd. That includes + * the double_linked_list's. So, just create the objects and TLVs, put them in + * their double_linked_list's, and everything will be managed internally. The + * enclosing message will be deleted by pcep_msg_free_message() or + * pcep_msg_free_message_list() which, * in turn will call one of: + * pcep_obj_free_object() and pcep_obj_free_tlv(). + * For received messages, call pcep_msg_free_message() to free them. + */ + +/* These numbers can be found here: + * https://www.iana.org/assignments/pcep/pcep.xhtml */ +enum pcep_object_tlv_types { + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR = 1, + PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST = 4, /* RFC 5541 */ + PCEP_OBJ_TLV_TYPE_VENDOR_INFO = 7, /* RFC 7470 */ + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY = 16, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME = 17, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS = 18, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS = 19, /* RFC 8231 */ + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE = 20, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC = 21, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION = 23, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID = 24, /* RFC 8232 */ + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY = + 26, /* draft-ietf-pce-segment-routing-16 */ + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE = 28, /* RFC 8408 */ + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY = + 34, /* RFC 8408, draft-ietf-pce-segment-routing-16 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID = + 60, /*TDB2 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME = + 61, /*TDB3 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID = + 62, /*TDB4 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE = + 63, /*TDB5 draft-barth-pce-segment-routing-policy-cp-04 */ + PCEP_OBJ_TLV_TYPE_UNKNOWN = 128, + PCEP_OBJ_TYPE_CISCO_BSID = 65505, + /* Max IANA To write arbitrary data */ + PCEP_OBJ_TLV_TYPE_ARBITRARY = 65533 +}; + + +struct pcep_object_tlv_header { + enum pcep_object_tlv_types type; + /* Pointer into encoded_message field from the pcep_message */ + const uint8_t *encoded_tlv; + uint16_t encoded_tlv_length; +}; + +/* STATEFUL-PCE-CAPABILITY TLV, Used in Open Object. RFCs: 8231, 8232, 8281 */ +#define TLV_STATEFUL_PCE_CAP_FLAG_U 0x01 +#define TLV_STATEFUL_PCE_CAP_FLAG_S 0x02 +#define TLV_STATEFUL_PCE_CAP_FLAG_I 0x04 +#define TLV_STATEFUL_PCE_CAP_FLAG_T 0x08 +#define TLV_STATEFUL_PCE_CAP_FLAG_D 0x10 +#define TLV_STATEFUL_PCE_CAP_FLAG_F 0x20 + +struct pcep_object_tlv_stateful_pce_capability { + struct pcep_object_tlv_header header; + bool flag_u_lsp_update_capability; /* RFC 8231 */ + bool flag_s_include_db_version; /* RFC 8232 */ + bool flag_i_lsp_instantiation_capability; /* RFC 8281 */ + bool flag_t_triggered_resync; /* RFC 8232 */ + bool flag_d_delta_lsp_sync; /* RFC 8232 */ + bool flag_f_triggered_initial_sync; /* RFC 8232 */ +}; + +/* NOPATH-VECTOR TLV, Used in the Reply NoPath Object. */ +struct pcep_object_tlv_nopath_vector { + struct pcep_object_tlv_header header; + uint32_t error_code; +}; + +/* STATEFUL-PCE-CAPABILITY TLV, Used in Open Object. RFCs: 8232 */ +struct pcep_object_tlv_lsp_db_version { + struct pcep_object_tlv_header header; + uint64_t lsp_db_version; +}; + +/* Speaker Entity Identifier TLV, Used in Open Object. RFCs: 8232 */ +struct pcep_object_tlv_speaker_entity_identifier { + struct pcep_object_tlv_header header; + double_linked_list *speaker_entity_id_list; /* list of uint32_t speaker + entity ids */ +}; + +/* Ipv4 LSP Identifier TLV, Used in LSP Object. RFCs: 8231 */ +struct pcep_object_tlv_ipv4_lsp_identifier { + struct pcep_object_tlv_header header; + struct in_addr ipv4_tunnel_sender; + uint16_t lsp_id; + uint16_t tunnel_id; + struct in_addr extended_tunnel_id; + struct in_addr ipv4_tunnel_endpoint; +}; + +/* Ipv6 LSP Identifier TLV, Used in LSP Object. RFCs: 8231 */ +struct pcep_object_tlv_ipv6_lsp_identifier { + struct pcep_object_tlv_header header; + struct in6_addr ipv6_tunnel_sender; + uint16_t lsp_id; + uint16_t tunnel_id; + struct in6_addr extended_tunnel_id; + struct in6_addr ipv6_tunnel_endpoint; +}; + +/* Symbolic Path Name TLV, Used in LSP Object. RFCs: 8231 */ +#define MAX_SYMBOLIC_PATH_NAME 256 + +struct pcep_object_tlv_symbolic_path_name { + struct pcep_object_tlv_header header; + uint16_t symbolic_path_name_length; + char symbolic_path_name[MAX_SYMBOLIC_PATH_NAME]; +}; + +/* LSP Error Code TLV, Used in LSP Object. RFCs: 8231 */ +enum pcep_tlv_lsp_error_codes { + PCEP_TLV_LSP_ERROR_CODE_UNKNOWN = 1, + PCEP_TLV_LSP_ERROR_CODE_LSP_LIMIT_REACHED = 2, + PCEP_TLV_LSP_ERROR_CODE_TOO_MANY_PENDING_LSP_UPDATES = 3, + PCEP_TLV_LSP_ERROR_CODE_UNACCEPTABLE_PARAMS = 4, + PCEP_TLV_LSP_ERROR_CODE_INTERNAL_ERROR = 5, + PCEP_TLV_LSP_ERROR_CODE_LSP_BROUGHT_DOWN = 6, + PCEP_TLV_LSP_ERROR_CODE_LSP_PREEMPTED = 7, + PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR = 8, +}; + +struct pcep_object_tlv_lsp_error_code { + struct pcep_object_tlv_header header; + enum pcep_tlv_lsp_error_codes lsp_error_code; +}; + +/* Path Setup Type TLV, Used in RP and SRP Object. RFCs: 8408, + * draft-ietf-pce-segment-routing-16 */ +#define SR_TE_PST 1 + +struct pcep_object_tlv_path_setup_type { + struct pcep_object_tlv_header header; + uint8_t path_setup_type; +}; + +/* Path Setup Type Capability TLV, Used in Open Object. RFCs: 8408, + * draft-ietf-pce-segment-routing-16 */ +struct pcep_object_tlv_path_setup_type_capability { + struct pcep_object_tlv_header header; + double_linked_list *pst_list; /* list of uint8_t PSTs */ + double_linked_list *sub_tlv_list; /* list of sub_tlvs */ +}; + +/* SR PCE Capability sub-TLV, Used in Open Object. RFCs: + * draft-ietf-pce-segment-routing-16 */ +#define TLV_SR_PCE_CAP_FLAG_X 0x01 +#define TLV_SR_PCE_CAP_FLAG_N 0x02 + +struct pcep_object_tlv_sr_pce_capability { + struct pcep_object_tlv_header header; + bool flag_n; + bool flag_x; + uint8_t max_sid_depth; +}; + + +/* RSVP Error Spec TLV, Used in LSP Object. RFCs: 8231, 2205 */ +#define RSVP_ERROR_SPEC_IPV4_CTYPE 1 +#define RSVP_ERROR_SPEC_IPV6_CTYPE 2 +#define RSVP_ERROR_SPEC_CLASS_NUM 6 + +struct pcep_object_tlv_rsvp_error_spec { + struct pcep_object_tlv_header header; + uint8_t class_num; + uint8_t c_type; + uint8_t error_code; + uint16_t error_value; + /* Use the c_type to determine which union entry to use */ + union error_spec_ip { + struct in_addr ipv4_error_node_address; + struct in6_addr ipv6_error_node_address; + } error_spec_ip; +}; + +/* SR Policy Identifier TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_pol_id { + struct pcep_object_tlv_header header; + uint32_t color; + bool is_ipv4; + union end_point_ { + struct in_addr ipv4; + struct in6_addr ipv6; + } end_point; +}; + +/*draft-ietf-spring-segment-routing-policy-06*/ +#define MAX_POLICY_NAME 256 + +/* SR Policy Name TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_pol_name { + struct pcep_object_tlv_header header; + uint16_t name_length; + char name[MAX_POLICY_NAME]; +}; + +/* SR Candidate Path Id TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_cp_id { + struct pcep_object_tlv_header header; + uint8_t proto; + uint32_t orig_asn; + struct in6_addr orig_addres; /*With ipv4 embedded*/ + uint32_t discriminator; +}; + +/* SR Candidate Preference TLV Used in Association Object. + * draft-barth-pce-segment-routing-policy-cp-04*/ +struct pcep_object_tlv_srpag_cp_pref { + struct pcep_object_tlv_header header; + uint32_t preference; +}; + +struct pcep_object_tlv_vendor_info { + struct pcep_object_tlv_header header; + uint32_t enterprise_number; + uint32_t enterprise_specific_info; +}; + +/* arbitrary TLV 65535 */ +#define MAX_ARBITRARY_SIZE 256 +struct pcep_object_tlv_arbitrary { + struct pcep_object_tlv_header header; + enum pcep_object_tlv_types arbitraty_type; + uint16_t data_length; + char data[MAX_ARBITRARY_SIZE]; +}; + +/* Objective Functions List RFC 5541 + * At least the following 6 OF codes must be supported */ +enum objective_function_codes { + PCEP_OF_CODE_MINIMUM_COST_PATH = 1, /* MCP */ + PCEP_OF_CODE_MINIMUM_LOAD_PATH = 2, /* MLP */ + PCEP_OF_CODE_MAXIMUM_BW_PATH = 3, /* MBP */ + PCEP_OF_CODE_MINIMIZE_AGGR_BW_CONSUMPTION = 4, /* MBC */ + PCEP_OF_CODE_MINIMIZE_MOST_LOADED_LINK = 5, /* MLL */ + PCEP_OF_CODE_MINIMIZE_CUMULATIVE_COST_PATHS = 6, /* MCC */ +}; + +struct pcep_object_tlv_of_list { + struct pcep_object_tlv_header header; + double_linked_list *of_list; /* list of uint16_t OF code points */ +}; + +/* + * TLV creation functions + */ + +/* + * Open Object TLVs + */ + +struct pcep_object_tlv_stateful_pce_capability * +pcep_tlv_create_stateful_pce_capability( + bool flag_u_lsp_update_capability, bool flag_s_include_db_version, + bool flag_i_lsp_instantiation_capability, bool flag_t_triggered_resync, + bool flag_d_delta_lsp_sync, bool flag_f_triggered_initial_sync); +struct pcep_object_tlv_lsp_db_version * +pcep_tlv_create_lsp_db_version(uint64_t lsp_db_version); +struct pcep_object_tlv_speaker_entity_identifier * +pcep_tlv_create_speaker_entity_id(double_linked_list *speaker_entity_id_list); +struct pcep_object_tlv_path_setup_type * +pcep_tlv_create_path_setup_type(uint8_t pst); +struct pcep_object_tlv_path_setup_type_capability * +pcep_tlv_create_path_setup_type_capability(double_linked_list *pst_list, + double_linked_list *sub_tlv_list); +struct pcep_object_tlv_sr_pce_capability * +pcep_tlv_create_sr_pce_capability(bool flag_n, bool flag_x, + uint8_t max_sid_depth); +struct pcep_object_tlv_of_list * +pcep_tlv_create_of_list(double_linked_list *of_list); + +/* + * LSP Object TLVs + */ + +struct pcep_object_tlv_ipv4_lsp_identifier * +pcep_tlv_create_ipv4_lsp_identifiers(struct in_addr *ipv4_tunnel_sender, + struct in_addr *ipv4_tunnel_endpoint, + uint16_t lsp_id, uint16_t tunnel_id, + struct in_addr *extended_tunnel_id); +struct pcep_object_tlv_ipv6_lsp_identifier * +pcep_tlv_create_ipv6_lsp_identifiers(struct in6_addr *ipv6_tunnel_sender, + struct in6_addr *extended_tunnel_id, + uint16_t lsp_id, uint16_t tunnel_id, + struct in6_addr *ipv6_tunnel_endpoint); +/* symbolic_path_name_length should NOT include the null terminator and cannot + * be zero */ +struct pcep_object_tlv_symbolic_path_name * +pcep_tlv_create_symbolic_path_name(const char *symbolic_path_name, + uint16_t symbolic_path_name_length); +struct pcep_object_tlv_lsp_error_code * +pcep_tlv_create_lsp_error_code(enum pcep_tlv_lsp_error_codes lsp_error_code); +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv4_error_spec(struct in_addr *error_node_ip, + uint8_t error_code, uint16_t error_value); +struct pcep_object_tlv_rsvp_error_spec * +pcep_tlv_create_rsvp_ipv6_error_spec(struct in6_addr *error_node_ip, + uint8_t error_code, uint16_t error_value); + +struct pcep_object_tlv_nopath_vector * +pcep_tlv_create_nopath_vector(uint32_t error_code); +struct pcep_object_tlv_vendor_info * +pcep_tlv_create_vendor_info(uint32_t enterprise_number, + uint32_t enterprise_specific_info); + +struct pcep_object_tlv_arbitrary * +pcep_tlv_create_tlv_arbitrary(const char *data, uint16_t data_length, + int tlv_id); +/* + * SRPAG (SR Association Group) TLVs + */ + +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv4(uint32_t color, struct in_addr *ipv4); +struct pcep_object_tlv_srpag_pol_id * +pcep_tlv_create_srpag_pol_id_ipv6(uint32_t color, struct in6_addr *ipv6); +struct pcep_object_tlv_srpag_pol_name * +pcep_tlv_create_srpag_pol_name(const char *pol_name, uint16_t pol_name_length); +struct pcep_object_tlv_srpag_cp_id * +pcep_tlv_create_srpag_cp_id(uint8_t proto_origin, uint32_t asn, + struct in6_addr *in6_addr_with_mapped_ipv4, + uint32_t discriminator); +struct pcep_object_tlv_srpag_cp_pref * +pcep_tlv_create_srpag_cp_pref(uint32_t pref); + + +#ifdef __cplusplus +} +#endif + +#endif /* PCEP_TLVS_H_ */ diff --git a/pceplib/pcep_msg_tlvs_encoding.c b/pceplib/pcep_msg_tlvs_encoding.c new file mode 100644 index 0000000..08a4302 --- /dev/null +++ b/pceplib/pcep_msg_tlvs_encoding.c @@ -0,0 +1,1286 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Encoding and decoding for PCEP Object TLVs. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __FreeBSD__ +#include +#else +#include +#endif /* __FreeBSD__ */ +#include +#include + +#include "pcep.h" +#include "pcep_msg_encoding.h" +#include "pcep_msg_tlvs.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_tlv_header(struct pcep_object_tlv_header *tlv_hdr, + uint16_t tlv_length, struct pcep_versioning *versioning, + uint8_t *buf); +void pcep_decode_tlv_hdr(const uint8_t *tlv_buf, + struct pcep_object_tlv_header *tlv_hdr); + +/* + * forward declarations for initialize_tlv_encoders() + */ +uint16_t pcep_encode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t +pcep_encode_tlv_path_setup_type_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_pol_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_pol_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_cpath_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_vendor_info(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_arbitrary(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +uint16_t pcep_encode_tlv_of_list(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); +typedef uint16_t (*tlv_encoder_funcptr)(struct pcep_object_tlv_header *, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf); + +#define MAX_TLV_ENCODER_INDEX 65533 + 1 // 65 + +#define PCEP_TLV_ENCODERS_ARGS \ + struct pcep_object_tlv_header *, struct pcep_versioning *versioning, \ + uint8_t *tlv_body_buf +uint16_t (*const tlv_encoders[MAX_TLV_ENCODER_INDEX])( + PCEP_TLV_ENCODERS_ARGS) = { + [PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = pcep_encode_tlv_no_path_vector, + [PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_encode_tlv_stateful_pce_capability, + [PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_encode_tlv_symbolic_path_name, + [PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv4_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv6_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = pcep_encode_tlv_lsp_error_code, + [PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = pcep_encode_tlv_rsvp_error_spec, + [PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = pcep_encode_tlv_lsp_db_version, + [PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_encode_tlv_speaker_entity_id, + [PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_encode_tlv_sr_pce_capability, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = pcep_encode_tlv_path_setup_type, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_encode_tlv_path_setup_type_capability, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = pcep_encode_tlv_pol_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = pcep_encode_tlv_pol_name, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = pcep_encode_tlv_cpath_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_encode_tlv_cpath_preference, + [PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = pcep_encode_tlv_vendor_info, + [PCEP_OBJ_TLV_TYPE_ARBITRARY] = pcep_encode_tlv_arbitrary, + [PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = pcep_encode_tlv_of_list, +}; +/* + * forward declarations for initialize_tlv_decoders() + */ +struct pcep_object_tlv_header * +pcep_decode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header *pcep_decode_tlv_path_setup_type_capability( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_vendor_info(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_arbitrary(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +struct pcep_object_tlv_header * +pcep_decode_tlv_of_list(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf); +typedef struct pcep_object_tlv_header *(*tlv_decoder_funcptr)( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf); + +// tlv_decoder_funcptr tlv_decoders[MAX_TLV_ENCODER_INDEX]; + +#define PCEP_TLV_DECODERS_ARGS \ + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf + +struct pcep_object_tlv_header *(*const tlv_decoders[MAX_TLV_ENCODER_INDEX])( + PCEP_TLV_DECODERS_ARGS) = { + [PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = pcep_decode_tlv_no_path_vector, + [PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_decode_tlv_stateful_pce_capability, + [PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_decode_tlv_symbolic_path_name, + [PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv4_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv6_lsp_identifiers, + [PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = pcep_decode_tlv_lsp_error_code, + [PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = pcep_decode_tlv_rsvp_error_spec, + [PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = pcep_decode_tlv_lsp_db_version, + [PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_decode_tlv_speaker_entity_id, + [PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_decode_tlv_sr_pce_capability, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = pcep_decode_tlv_path_setup_type, + [PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_decode_tlv_path_setup_type_capability, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = pcep_decode_tlv_pol_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = pcep_decode_tlv_pol_name, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = pcep_decode_tlv_cpath_id, + [PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_decode_tlv_cpath_preference, + [PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = pcep_decode_tlv_vendor_info, + [PCEP_OBJ_TLV_TYPE_ARBITRARY] = pcep_decode_tlv_arbitrary, + [PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = pcep_decode_tlv_of_list, +}; + +static void initialize_tlv_coders(void) +{ + static bool initialized = false; + + if (initialized == true) { + return; + } + + initialized = true; + + /* Encoders */ + /* + memset(tlv_encoders, 0, sizeof(tlv_encoder_funcptr) * + MAX_TLV_ENCODER_INDEX); tlv_encoders[PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = + pcep_encode_tlv_no_path_vector; + tlv_encoders[PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_encode_tlv_stateful_pce_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_encode_tlv_symbolic_path_name; + tlv_encoders[PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv4_lsp_identifiers; + tlv_encoders[PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_encode_tlv_ipv6_lsp_identifiers; + tlv_encoders[PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = + pcep_encode_tlv_lsp_error_code; + tlv_encoders[PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = + pcep_encode_tlv_rsvp_error_spec; + tlv_encoders[PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = + pcep_encode_tlv_lsp_db_version; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_encode_tlv_speaker_entity_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_encode_tlv_sr_pce_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = + pcep_encode_tlv_path_setup_type; + tlv_encoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_encode_tlv_path_setup_type_capability; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = + pcep_encode_tlv_pol_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = + pcep_encode_tlv_pol_name; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = + pcep_encode_tlv_cpath_id; + tlv_encoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_encode_tlv_cpath_preference; + tlv_encoders[PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = + pcep_encode_tlv_vendor_info; tlv_encoders[PCEP_OBJ_TLV_TYPE_ARBITRARY] = + pcep_encode_tlv_arbitrary; + tlv_encoders[PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = + pcep_encode_tlv_of_list; + */ + + /* Decoders */ + /* + memset(tlv_decoders, 0, sizeof(tlv_decoder_funcptr) * + MAX_TLV_ENCODER_INDEX); tlv_decoders[PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR] = + pcep_decode_tlv_no_path_vector; + tlv_decoders[PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY] = + pcep_decode_tlv_stateful_pce_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME] = + pcep_decode_tlv_symbolic_path_name; + tlv_decoders[PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv4_lsp_identifiers; + tlv_decoders[PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS] = + pcep_decode_tlv_ipv6_lsp_identifiers; + tlv_decoders[PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE] = + pcep_decode_tlv_lsp_error_code; + tlv_decoders[PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC] = + pcep_decode_tlv_rsvp_error_spec; + tlv_decoders[PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION] = + pcep_decode_tlv_lsp_db_version; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID] = + pcep_decode_tlv_speaker_entity_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY] = + pcep_decode_tlv_sr_pce_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE] = + pcep_decode_tlv_path_setup_type; + tlv_decoders[PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY] = + pcep_decode_tlv_path_setup_type_capability; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID] = + pcep_decode_tlv_pol_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME] = + pcep_decode_tlv_pol_name; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID] = + pcep_decode_tlv_cpath_id; + tlv_decoders[PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE] = + pcep_decode_tlv_cpath_preference; + tlv_decoders[PCEP_OBJ_TLV_TYPE_VENDOR_INFO] = + pcep_decode_tlv_vendor_info; tlv_decoders[PCEP_OBJ_TLV_TYPE_ARBITRARY] = + pcep_decode_tlv_arbitrary; + tlv_decoders[PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST] = + pcep_decode_tlv_of_list; + */ +} + +uint16_t pcep_encode_tlv(struct pcep_object_tlv_header *tlv_hdr, + struct pcep_versioning *versioning, uint8_t *buf) +{ + initialize_tlv_coders(); + + if (tlv_hdr->type >= MAX_TLV_ENCODER_INDEX) { + pcep_log(LOG_INFO, + "%s: Cannot encode unknown Object class [%d]", + __func__, tlv_hdr->type); + return 0; + } + + tlv_encoder_funcptr tlv_encoder = tlv_encoders[tlv_hdr->type]; + if (tlv_encoder == NULL) { + pcep_log(LOG_INFO, + "%s: No object encoder found for Object class [%d]", + __func__, tlv_hdr->type); + return 0; + } + + /* Notice: The length in the TLV header does not include the TLV header, + * so the length returned from the tlv_encoder() is only the TLV body. + */ + uint16_t tlv_length = + tlv_encoder(tlv_hdr, versioning, buf + TLV_HEADER_LENGTH); + write_tlv_header(tlv_hdr, tlv_length, versioning, buf); + tlv_hdr->encoded_tlv = buf; + tlv_hdr->encoded_tlv_length = tlv_length; + + return normalize_pcep_tlv_length(tlv_length + TLV_HEADER_LENGTH); +} + +/* TLV Header format + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type (2 bytes) | Length (2 bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Value (Variable) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + +void write_tlv_header(struct pcep_object_tlv_header *tlv_hdr, + uint16_t tlv_length, struct pcep_versioning *versioning, + uint8_t *buf) +{ + (void)versioning; + uint16_t *uint16_ptr = (uint16_t *)buf; + uint16_ptr[0] = htons(tlv_hdr->type); + uint16_ptr[1] = htons(tlv_length); +} + +/* + * Functions to encode TLVs + */ + +uint16_t pcep_encode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_nopath_vector *nopath_tlv = + (struct pcep_object_tlv_nopath_vector *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + *uint32_ptr = htonl(nopath_tlv->error_code); + + return LENGTH_1WORD; +} + +uint16_t +pcep_encode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_stateful_pce_capability *spc_tlv = + (struct pcep_object_tlv_stateful_pce_capability *)tlv; + tlv_body_buf[3] = + ((spc_tlv->flag_f_triggered_initial_sync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_F + : 0x00) + | (spc_tlv->flag_d_delta_lsp_sync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_D + : 0x00) + | (spc_tlv->flag_t_triggered_resync == true + ? TLV_STATEFUL_PCE_CAP_FLAG_T + : 0x00) + | (spc_tlv->flag_i_lsp_instantiation_capability == true + ? TLV_STATEFUL_PCE_CAP_FLAG_I + : 0x00) + | (spc_tlv->flag_s_include_db_version == true + ? TLV_STATEFUL_PCE_CAP_FLAG_S + : 0x00) + | (spc_tlv->flag_u_lsp_update_capability == true + ? TLV_STATEFUL_PCE_CAP_FLAG_U + : 0x00)); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_symbolic_path_name *spn_tlv = + (struct pcep_object_tlv_symbolic_path_name *)tlv; + memcpy(tlv_body_buf, spn_tlv->symbolic_path_name, + spn_tlv->symbolic_path_name_length); + + return spn_tlv->symbolic_path_name_length; +} + +uint16_t +pcep_encode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp = + (struct pcep_object_tlv_ipv4_lsp_identifier *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = ipv4_lsp->ipv4_tunnel_sender.s_addr; + /* uint32_t[1] is lsp_id and tunnel_id, below */ + uint32_ptr[2] = ipv4_lsp->extended_tunnel_id.s_addr; + uint32_ptr[3] = ipv4_lsp->ipv4_tunnel_endpoint.s_addr; + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_1WORD); + uint16_ptr[0] = htons(ipv4_lsp->lsp_id); + uint16_ptr[1] = htons(ipv4_lsp->tunnel_id); + + return LENGTH_4WORDS; +} + +uint16_t +pcep_encode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_ipv6_lsp_identifier *ipv6_lsp = + (struct pcep_object_tlv_ipv6_lsp_identifier *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + encode_ipv6(&ipv6_lsp->ipv6_tunnel_sender, uint32_ptr); + encode_ipv6(&ipv6_lsp->extended_tunnel_id, uint32_ptr + 5); + encode_ipv6(&ipv6_lsp->ipv6_tunnel_endpoint, uint32_ptr + 9); + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_4WORDS); + uint16_ptr[0] = htons(ipv6_lsp->lsp_id); + uint16_ptr[1] = htons(ipv6_lsp->tunnel_id); + + return LENGTH_13WORDS; +} + +uint16_t pcep_encode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_lsp_error_code *lsp_error_tlv = + (struct pcep_object_tlv_lsp_error_code *)tlv; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + *uint32_ptr = htonl(lsp_error_tlv->lsp_error_code); + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + /* Same decode tlv function for both types: + pcep_create_tlv_rsvp_ipv4_error_spec(tlv); + pcep_create_tlv_rsvp_ipv6_error_spec(tlv); */ + + /* RSVP Object Header + * + * 0 1 2 3 + * +-------------+-------------+-------------+-------------+ + * | Length (bytes) | Class-Num | C-Type | + * +-------------+-------------+-------------+-------------+ + * | | + * // (Object contents) // + * | | + * +-------------+-------------+-------------+-------------+ + * + * IPv4 ERROR_SPEC object: Class = 6, C-Type = 1 + * +-------------+-------------+-------------+-------------+ + * | IPv4 Error Node Address (4 bytes) | + * +-------------+-------------+-------------+-------------+ + * | Flags | Error Code | Error Value | + * +-------------+-------------+-------------+-------------+ + * + * IPv6 ERROR_SPEC object: Class = 6, C-Type = 2 + * +-------------+-------------+-------------+-------------+ + * | IPv6 Error Node Address (16 bytes) | + * +-------------+-------------+-------------+-------------+ + * | Flags | Error Code | Error Value | + * +-------------+-------------+-------------+-------------+ + */ + + (void)versioning; + struct pcep_object_tlv_rsvp_error_spec *rsvp_hdr = + (struct pcep_object_tlv_rsvp_error_spec *)tlv; + tlv_body_buf[2] = rsvp_hdr->class_num; + tlv_body_buf[3] = rsvp_hdr->c_type; + + uint16_t *length_ptr = (uint16_t *)tlv_body_buf; + uint32_t *uint32_ptr = (uint32_t *)(tlv_body_buf + LENGTH_1WORD); + if (rsvp_hdr->c_type == RSVP_ERROR_SPEC_IPV4_CTYPE) { + *length_ptr = htons(LENGTH_3WORDS); + *uint32_ptr = + rsvp_hdr->error_spec_ip.ipv4_error_node_address.s_addr; + tlv_body_buf[LENGTH_2WORDS + 1] = rsvp_hdr->error_code; + uint16_t *uint16_ptr = + (uint16_t *)(tlv_body_buf + LENGTH_2WORDS + 2); + *uint16_ptr = htons(rsvp_hdr->error_value); + + return LENGTH_3WORDS; + } else if (rsvp_hdr->c_type == RSVP_ERROR_SPEC_IPV6_CTYPE) { + *length_ptr = htons(LENGTH_6WORDS); + encode_ipv6(&rsvp_hdr->error_spec_ip.ipv6_error_node_address, + uint32_ptr); + tlv_body_buf[LENGTH_5WORDS + 1] = rsvp_hdr->error_code; + uint16_t *uint16_ptr = + (uint16_t *)(tlv_body_buf + LENGTH_5WORDS + 2); + *uint16_ptr = htons(rsvp_hdr->error_value); + + return LENGTH_6WORDS; + } + + return 0; +} + +uint16_t pcep_encode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_lsp_db_version *lsp_db_ver = + (struct pcep_object_tlv_lsp_db_version *)tlv; + *((uint64_t *)tlv_body_buf) = htobe64(lsp_db_ver->lsp_db_version); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_speaker_entity_identifier *speaker_id = + (struct pcep_object_tlv_speaker_entity_identifier *)tlv; + if (speaker_id->speaker_entity_id_list == NULL) { + return 0; + } + + int index = 0; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + double_linked_list_node *node = + speaker_id->speaker_entity_id_list->head; + for (; node != NULL; node = node->next_node) { + uint32_ptr[index++] = htonl(*((uint32_t *)node->data)); + } + + return speaker_id->speaker_entity_id_list->num_entries * LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap = + (struct pcep_object_tlv_sr_pce_capability *)tlv; + tlv_body_buf[2] = + ((sr_pce_cap->flag_n == true ? TLV_SR_PCE_CAP_FLAG_N : 0x00) + | (sr_pce_cap->flag_x == true ? TLV_SR_PCE_CAP_FLAG_X : 0x00)); + tlv_body_buf[3] = sr_pce_cap->max_sid_depth; + + return LENGTH_1WORD; +} + +uint16_t pcep_encode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_path_setup_type *pst = + (struct pcep_object_tlv_path_setup_type *)tlv; + tlv_body_buf[3] = pst->path_setup_type; + + return LENGTH_1WORD; +} + +uint16_t +pcep_encode_tlv_path_setup_type_capability(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_path_setup_type_capability *pst_cap = + (struct pcep_object_tlv_path_setup_type_capability *)tlv; + if (pst_cap->pst_list == NULL) { + return 0; + } + + tlv_body_buf[3] = pst_cap->pst_list->num_entries; + + /* Index past the reserved and NumPSTs fields */ + int index = 4; + double_linked_list_node *node = pst_cap->pst_list->head; + for (; node != NULL; node = node->next_node) { + tlv_body_buf[index++] = *((uint8_t *)node->data); + } + + uint16_t pst_length = normalize_pcep_tlv_length( + LENGTH_1WORD + pst_cap->pst_list->num_entries); + if (pst_cap->sub_tlv_list == NULL) { + return pst_length; + } + + /* Any padding used for the PSTs should not be included in the tlv + * header length */ + index = normalize_pcep_tlv_length(index); + uint16_t sub_tlvs_length = 0; + node = pst_cap->sub_tlv_list->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_tlv_header *sub_tlv = + (struct pcep_object_tlv_header *)node->data; + uint16_t sub_tlv_length = pcep_encode_tlv(sub_tlv, versioning, + tlv_body_buf + index); + index += sub_tlv_length; + sub_tlvs_length += sub_tlv_length; + } + + return sub_tlvs_length + pst_length; +} +uint16_t pcep_encode_tlv_pol_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_pol_id *ipv4 = + (struct pcep_object_tlv_srpag_pol_id *)tlv; + if (ipv4->is_ipv4) { + uint32_ptr[0] = htonl(ipv4->color); + uint32_ptr[1] = ipv4->end_point.ipv4.s_addr; + return LENGTH_2WORDS; + } else { + struct pcep_object_tlv_srpag_pol_id *ipv6 = + (struct pcep_object_tlv_srpag_pol_id *)tlv; + uint32_ptr[0] = htonl(ipv6->color); + encode_ipv6(&ipv6->end_point.ipv6, &uint32_ptr[1]); + return LENGTH_5WORDS; + } +} + +uint16_t pcep_encode_tlv_pol_name(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_pol_name *pol_name_tlv = + (struct pcep_object_tlv_srpag_pol_name *)tlv; + memcpy(tlv_body_buf, pol_name_tlv->name, pol_name_tlv->name_length); + + return normalize_pcep_tlv_length(pol_name_tlv->name_length); +} + +uint16_t pcep_encode_tlv_cpath_id(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_cp_id *cpath_id_tlv = + (struct pcep_object_tlv_srpag_cp_id *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv_body_buf[0] = cpath_id_tlv->proto; + uint32_ptr[1] = htonl(cpath_id_tlv->orig_asn); + encode_ipv6(&cpath_id_tlv->orig_addres, &uint32_ptr[2]); + uint32_ptr[6] = htonl(cpath_id_tlv->discriminator); + + return sizeof(cpath_id_tlv->proto) + sizeof(cpath_id_tlv->orig_asn) + + sizeof(cpath_id_tlv->orig_addres) + + sizeof(cpath_id_tlv->discriminator); +} + +uint16_t pcep_encode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_srpag_cp_pref *cpath_pref_tlv = + (struct pcep_object_tlv_srpag_cp_pref *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = htonl(cpath_pref_tlv->preference); + + return sizeof(cpath_pref_tlv->preference); +} + +uint16_t pcep_encode_tlv_vendor_info(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_vendor_info *vendor_info = + (struct pcep_object_tlv_vendor_info *)tlv; + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + uint32_ptr[0] = htonl(vendor_info->enterprise_number); + uint32_ptr[1] = htonl(vendor_info->enterprise_specific_info); + + return LENGTH_2WORDS; +} + +uint16_t pcep_encode_tlv_arbitrary(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_arbitrary *tlv_arbitrary = + (struct pcep_object_tlv_arbitrary *)tlv; + memcpy(tlv_body_buf, tlv_arbitrary->data, tlv_arbitrary->data_length); + tlv->type = tlv_arbitrary->arbitraty_type; + + return tlv_arbitrary->data_length; +} + +uint16_t pcep_encode_tlv_of_list(struct pcep_object_tlv_header *tlv, + struct pcep_versioning *versioning, + uint8_t *tlv_body_buf) +{ + (void)versioning; + struct pcep_object_tlv_of_list *of_list = + (struct pcep_object_tlv_of_list *)tlv; + + if (of_list->of_list == NULL) { + return 0; + } + + int index = 0; + double_linked_list_node *node = of_list->of_list->head; + while (node != NULL) { + uint16_t *of_code = (uint16_t *)node->data; + if (of_code == NULL) { + return 0; + } + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + index); + *uint16_ptr = *of_code; + index += 2; + + node = node->next_node; + } + + return of_list->of_list->num_entries * 2; +} + +/* + * Decoding functions + */ + +void pcep_decode_tlv_hdr(const uint8_t *tlv_buf, + struct pcep_object_tlv_header *tlv_hdr) +{ + memset(tlv_hdr, 0, sizeof(struct pcep_object_tlv_header)); + + uint16_t *uint16_ptr = (uint16_t *)tlv_buf; + tlv_hdr->type = ntohs(uint16_ptr[0]); + tlv_hdr->encoded_tlv_length = ntohs(uint16_ptr[1]); + tlv_hdr->encoded_tlv = tlv_buf; +} + +struct pcep_object_tlv_header *pcep_decode_tlv(const uint8_t *tlv_buf) +{ + initialize_tlv_coders(); + + struct pcep_object_tlv_header tlv_hdr; + /* Only initializes and decodes the Object Header: class, type, flags, + * and length */ + pcep_decode_tlv_hdr(tlv_buf, &tlv_hdr); + + if (tlv_hdr.type >= MAX_TLV_ENCODER_INDEX) { + pcep_log(LOG_INFO, "%s: Cannot decode unknown TLV type [%d]", + __func__, tlv_hdr.type); + return NULL; + } + + tlv_decoder_funcptr tlv_decoder = NULL; + if (tlv_hdr.type == PCEP_OBJ_TYPE_CISCO_BSID) { + pcep_log(LOG_INFO, + "%s: Cisco BSID TLV decoder found for TLV type [%d]", + __func__, tlv_hdr.type); + tlv_decoder = tlv_decoders[PCEP_OBJ_TLV_TYPE_ARBITRARY]; + } else { + tlv_decoder = tlv_decoders[tlv_hdr.type]; + } + if (tlv_decoder == NULL) { + pcep_log(LOG_INFO, "%s: No TLV decoder found for TLV type [%d]", + __func__, tlv_hdr.type); + return NULL; + } + + return tlv_decoder(&tlv_hdr, tlv_buf + LENGTH_1WORD); +} + +static struct pcep_object_tlv_header * +common_tlv_create(struct pcep_object_tlv_header *hdr, uint16_t new_tlv_length) +{ + struct pcep_object_tlv_header *new_tlv = + pceplib_malloc(PCEPLIB_MESSAGES, new_tlv_length); + memset(new_tlv, 0, new_tlv_length); + memcpy(new_tlv, hdr, sizeof(struct pcep_object_tlv_header)); + + return new_tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_no_path_vector(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_nopath_vector)); + + tlv->error_code = ntohl(*((uint32_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_stateful_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_stateful_pce_capability)); + + tlv->flag_f_triggered_initial_sync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_F); + tlv->flag_d_delta_lsp_sync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_D); + tlv->flag_t_triggered_resync = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_T); + tlv->flag_i_lsp_instantiation_capability = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_I); + tlv->flag_s_include_db_version = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_S); + tlv->flag_u_lsp_update_capability = + (tlv_body_buf[3] & TLV_STATEFUL_PCE_CAP_FLAG_U); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_symbolic_path_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_symbolic_path_name *tlv = + (struct pcep_object_tlv_symbolic_path_name *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_symbolic_path_name)); + + uint16_t length = tlv_hdr->encoded_tlv_length; + if (length > MAX_SYMBOLIC_PATH_NAME) { + /* TODO should we also reset the tlv_hdr->encoded_tlv_length ? + */ + length = MAX_SYMBOLIC_PATH_NAME; + pcep_log( + LOG_INFO, + "%s: Decoding Symbolic Path Name TLV, truncate path name from [%d] to [%d].\",", + __func__, tlv_hdr->encoded_tlv_length, + MAX_SYMBOLIC_PATH_NAME); + } + + tlv->symbolic_path_name_length = length; + memcpy(tlv->symbolic_path_name, tlv_body_buf, length); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv4_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv4_lsp_identifier *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_ipv4_lsp_identifier)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->ipv4_tunnel_sender.s_addr = uint32_ptr[0]; + /* uint32_t[1] is lsp_id and tunnel_id, below */ + tlv->extended_tunnel_id.s_addr = uint32_ptr[2]; + tlv->ipv4_tunnel_endpoint.s_addr = uint32_ptr[3]; + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_1WORD); + tlv->lsp_id = ntohs(uint16_ptr[0]); + tlv->tunnel_id = ntohs(uint16_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_ipv6_lsp_identifiers(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + (struct pcep_object_tlv_ipv6_lsp_identifier *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_ipv6_lsp_identifier)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + decode_ipv6(uint32_ptr, &tlv->ipv6_tunnel_sender); + decode_ipv6(uint32_ptr + 5, &tlv->extended_tunnel_id); + decode_ipv6(uint32_ptr + 9, &tlv->ipv6_tunnel_endpoint); + + uint16_t *uint16_ptr = (uint16_t *)(tlv_body_buf + LENGTH_4WORDS); + tlv->lsp_id = htons(uint16_ptr[0]); + tlv->tunnel_id = htons(uint16_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_error_code(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_lsp_error_code *tlv = + (struct pcep_object_tlv_lsp_error_code *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_lsp_error_code)); + + tlv->lsp_error_code = ntohl(*((uint32_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_rsvp_error_spec(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint8_t class_num = tlv_body_buf[2]; + uint8_t ctype = tlv_body_buf[3]; + + if (class_num != RSVP_ERROR_SPEC_CLASS_NUM) { + pcep_log( + LOG_INFO, + "%s: Decoding RSVP Error Spec TLV, unknown class num [%d]", + __func__, class_num); + return NULL; + } + + if (ctype != RSVP_ERROR_SPEC_IPV4_CTYPE + && ctype != RSVP_ERROR_SPEC_IPV6_CTYPE) { + pcep_log(LOG_INFO, + "%s: Decoding RSVP Error Spec TLV, unknown ctype [%d]", + __func__, ctype); + return NULL; + } + + struct pcep_object_tlv_rsvp_error_spec *tlv = + (struct pcep_object_tlv_rsvp_error_spec *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_rsvp_error_spec)); + + tlv->class_num = class_num; + tlv->c_type = ctype; + + uint32_t *uint32_ptr = (uint32_t *)(tlv_body_buf + LENGTH_1WORD); + if (ctype == RSVP_ERROR_SPEC_IPV4_CTYPE) { + tlv->error_spec_ip.ipv4_error_node_address.s_addr = *uint32_ptr; + tlv->error_code = tlv_body_buf[LENGTH_2WORDS + 1]; + tlv->error_value = ntohs( + *((uint16_t *)(tlv_body_buf + LENGTH_2WORDS + 2))); + } else /* RSVP_ERROR_SPEC_IPV6_CTYPE */ + { + decode_ipv6(uint32_ptr, + &tlv->error_spec_ip.ipv6_error_node_address); + tlv->error_code = tlv_body_buf[LENGTH_5WORDS + 1]; + tlv->error_value = ntohs( + *((uint16_t *)(tlv_body_buf + LENGTH_5WORDS + 2))); + } + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_lsp_db_version(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_lsp_db_version *tlv = + (struct pcep_object_tlv_lsp_db_version *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_lsp_db_version)); + + tlv->lsp_db_version = be64toh(*((uint64_t *)tlv_body_buf)); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_speaker_entity_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_speaker_entity_identifier *tlv = + (struct pcep_object_tlv_speaker_entity_identifier *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_speaker_entity_identifier)); + + uint8_t num_entity_ids = tlv_hdr->encoded_tlv_length / LENGTH_1WORD; + if (num_entity_ids > MAX_ITERATIONS) { + num_entity_ids = MAX_ITERATIONS; + pcep_log( + LOG_INFO, + "%s: Decode Speaker Entity ID, truncating num entities from [%d] to [%d].", + __func__, num_entity_ids, MAX_ITERATIONS); + } + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->speaker_entity_id_list = dll_initialize(); + int i; + for (i = 0; i < num_entity_ids; i++) { + uint32_t *entity_id = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *entity_id = ntohl(uint32_ptr[i]); + dll_append(tlv->speaker_entity_id_list, entity_id); + } + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_sr_pce_capability(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + (struct pcep_object_tlv_sr_pce_capability *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_sr_pce_capability)); + + tlv->flag_n = (tlv_body_buf[2] & TLV_SR_PCE_CAP_FLAG_N); + tlv->flag_x = (tlv_body_buf[2] & TLV_SR_PCE_CAP_FLAG_X); + tlv->max_sid_depth = tlv_body_buf[3]; + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_path_setup_type(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_path_setup_type *tlv = + (struct pcep_object_tlv_path_setup_type *)common_tlv_create( + tlv_hdr, + sizeof(struct pcep_object_tlv_path_setup_type)); + + tlv->path_setup_type = tlv_body_buf[3]; + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header *pcep_decode_tlv_path_setup_type_capability( + struct pcep_object_tlv_header *tlv_hdr, const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_path_setup_type_capability *tlv = + (struct pcep_object_tlv_path_setup_type_capability *) + common_tlv_create( + tlv_hdr, + sizeof(struct + pcep_object_tlv_path_setup_type_capability)); + + uint8_t num_psts = tlv_body_buf[3]; + if (num_psts > MAX_ITERATIONS) { + pcep_log( + LOG_INFO, + "%s: Decode Path Setup Type Capability num PSTs [%d] exceeds MAX [%d] continuing anyways", + __func__, num_psts, MAX_ITERATIONS); + } + + int i; + tlv->pst_list = dll_initialize(); + for (i = 0; i < num_psts; i++) { + uint8_t *pst = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t)); + *pst = tlv_body_buf[i + LENGTH_1WORD]; + dll_append(tlv->pst_list, pst); + } + + if (tlv->header.encoded_tlv_length + == (TLV_HEADER_LENGTH + LENGTH_1WORD + num_psts)) { + return (struct pcep_object_tlv_header *)tlv; + } + + uint8_t num_iterations = 0; + tlv->sub_tlv_list = dll_initialize(); + uint16_t buf_index = normalize_pcep_tlv_length( + TLV_HEADER_LENGTH + LENGTH_1WORD + num_psts); + while ((tlv->header.encoded_tlv_length - buf_index) > TLV_HEADER_LENGTH + && num_iterations++ < MAX_ITERATIONS) { + struct pcep_object_tlv_header *sub_tlv = + pcep_decode_tlv(tlv_body_buf + buf_index); + if (sub_tlv == NULL) { + pcep_log( + LOG_INFO, + "%s: Decode PathSetupType Capability sub-TLV decode returned NULL", + __func__); + return (struct pcep_object_tlv_header *)tlv; + } + + buf_index += + normalize_pcep_tlv_length(sub_tlv->encoded_tlv_length); + dll_append(tlv->sub_tlv_list, sub_tlv); + } + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_pol_id *ipv4 = + (struct pcep_object_tlv_srpag_pol_id *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_pol_id)); + if (tlv_hdr->encoded_tlv_length == 8) { + ipv4->is_ipv4 = true; + ipv4->color = ntohl(uint32_ptr[0]); + ipv4->end_point.ipv4.s_addr = uint32_ptr[1]; + return (struct pcep_object_tlv_header *)ipv4; + } else { + ipv4->is_ipv4 = false; + struct pcep_object_tlv_srpag_pol_id *ipv6 = ipv4; + ipv6->color = ntohl(uint32_ptr[0]); + decode_ipv6(&uint32_ptr[1], &ipv6->end_point.ipv6); + return (struct pcep_object_tlv_header *)ipv6; + } +} +struct pcep_object_tlv_header * +pcep_decode_tlv_pol_name(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_srpag_pol_name *tlv = + (struct pcep_object_tlv_srpag_pol_name *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_pol_name)); + + memcpy(tlv->name, tlv_body_buf, tlv->header.encoded_tlv_length); + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_id(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_cp_id *tlv = + (struct pcep_object_tlv_srpag_cp_id *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_cp_id)); + + tlv->proto = tlv_body_buf[0]; + tlv->orig_asn = ntohl(uint32_ptr[1]); + decode_ipv6(&uint32_ptr[2], &tlv->orig_addres); + tlv->discriminator = ntohl(uint32_ptr[6]); + + return (struct pcep_object_tlv_header *)tlv; +} +struct pcep_object_tlv_header * +pcep_decode_tlv_cpath_preference(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + struct pcep_object_tlv_srpag_cp_pref *tlv = + (struct pcep_object_tlv_srpag_cp_pref *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_srpag_cp_pref)); + + tlv->preference = ntohl(uint32_ptr[0]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_vendor_info(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_vendor_info *tlv = + (struct pcep_object_tlv_vendor_info *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_vendor_info)); + + uint32_t *uint32_ptr = (uint32_t *)tlv_body_buf; + tlv->enterprise_number = ntohl(uint32_ptr[0]); + tlv->enterprise_specific_info = ntohl(uint32_ptr[1]); + + return (struct pcep_object_tlv_header *)tlv; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_arbitrary(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_arbitrary *tlv_arbitrary = + (struct pcep_object_tlv_arbitrary *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_arbitrary)); + + uint16_t length = tlv_hdr->encoded_tlv_length; + if (length > MAX_ARBITRARY_SIZE) { + /* TODO should we also reset the tlv_hdr->encoded_tlv_length ? + */ + length = MAX_ARBITRARY_SIZE; + pcep_log( + LOG_INFO, + "%s: Decoding Arbitrary TLV , truncate path name from [%d] to [%d].\",", + __func__, tlv_hdr->encoded_tlv_length, + MAX_ARBITRARY_SIZE); + } + + tlv_arbitrary->data_length = length; + tlv_arbitrary->arbitraty_type = tlv_hdr->type; + tlv_hdr->type = PCEP_OBJ_TLV_TYPE_ARBITRARY; + memcpy(tlv_arbitrary->data, tlv_body_buf, length); + + return (struct pcep_object_tlv_header *)tlv_arbitrary; +} + +struct pcep_object_tlv_header * +pcep_decode_tlv_of_list(struct pcep_object_tlv_header *tlv_hdr, + const uint8_t *tlv_body_buf) +{ + struct pcep_object_tlv_of_list *of_tlv = + (struct pcep_object_tlv_of_list *)common_tlv_create( + tlv_hdr, sizeof(struct pcep_object_tlv_of_list)); + + of_tlv->of_list = dll_initialize(); + uint16_t *uint16_ptr = (uint16_t *)tlv_body_buf; + int i = 0; + for (; i < tlv_hdr->encoded_tlv_length && i < MAX_ITERATIONS; i++) { + uint16_t *of_code_ptr = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint16_t)); + *of_code_ptr = ntohs(uint16_ptr[i]); + dll_append(of_tlv->of_list, of_code_ptr); + } + + return (struct pcep_object_tlv_header *)of_tlv; +} diff --git a/pceplib/pcep_msg_tools.c b/pceplib/pcep_msg_tools.c new file mode 100644 index 0000000..fe161f0 --- /dev/null +++ b/pceplib/pcep_msg_tools.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include "pcep_msg_tools.h" +#include "pcep_msg_encoding.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +static const char *message_type_strs[] = {"NOT_IMPLEMENTED0", + "OPEN", + "KEEPALIVE", + "PCREQ", + "PCREP", + "PCNOTF", + "ERROR", + "CLOSE", + "NOT_IMPLEMENTED8", + "NOT_IMPLEMENTED9", + "REPORT", + "UPDATE", + "INITIATE", + "UNKOWN_MESSAGE_TYPE"}; + +static const char *object_class_strs[] = {"NOT_IMPLEMENTED0", + "OPEN", + "RP", + "NOPATH", + "ENDPOINTS", + "BANDWIDTH", + "METRIC", + "ERO", + "RRO", + "LSPA", + "IRO", + "SVEC", + "NOTF", + "ERROR", + "NOT_IMPLEMENTED14", + "CLOSE", + "NOT_IMPLEMENTED16", + "NOT_IMPLEMENTED17", + "NOT_IMPLEMENTED18", + "NOT_IMPLEMENTED19", + "NOT_IMPLEMENTED20", + "OBJECTIVE_FUNCTION", + "NOT_IMPLEMENTED22", + "NOT_IMPLEMENTED23", + "NOT_IMPLEMENTED24", + "NOT_IMPLEMENTED25", + "NOT_IMPLEMENTED26", + "NOT_IMPLEMENTED27", + "NOT_IMPLEMENTED28", + "NOT_IMPLEMENTED29", + "NOT_IMPLEMENTED30", + "NOT_IMPLEMENTED31", + "LSP", + "SRP", + "VENDOR_INFO", + "NOT_IMPLEMENTED35", + "INTER_LAYER", + "SWITCH_LAYER", + "REQ_ADAP_CAP", + "SERVER_IND", + "ASSOCIATION", /* 40 */ + "UNKNOWN_MESSAGE_TYPE"}; + + +double_linked_list *pcep_msg_read(int sock_fd) +{ + int ret; + uint8_t buffer[PCEP_MESSAGE_LENGTH] = {0}; + uint16_t buffer_read = 0; + + + ret = read(sock_fd, &buffer, PCEP_MESSAGE_LENGTH); + + if (ret < 0) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Failed to read from socket fd [%d] errno [%d %s]", + __func__, sock_fd, errno, strerror(errno)); + return NULL; + } else if (ret == 0) { + pcep_log(LOG_INFO, "%s: pcep_msg_read: Remote shutdown fd [%d]", + __func__, sock_fd); + return NULL; + } + + double_linked_list *msg_list = dll_initialize(); + struct pcep_message *msg = NULL; + + while (((uint16_t)ret - buffer_read) >= MESSAGE_HEADER_LENGTH) { + + /* Get the Message header, validate it, and return the msg + * length */ + int32_t msg_length = + pcep_decode_validate_msg_header(buffer + buffer_read); + if (msg_length < 0 || msg_length > PCEP_MESSAGE_LENGTH) { + /* If the message header is invalid, we cant keep + * reading since the length may be invalid */ + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Received an invalid message fd [%d]", + __func__, sock_fd); + return msg_list; + } + + /* Check if the msg_length is longer than what was read, + * in which case, we need to read the rest of the message. */ + if ((ret - buffer_read) < msg_length) { + int read_len = (msg_length - (ret - buffer_read)); + int read_ret = 0; + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Message not fully read! Trying to read %d bytes more, fd [%d]", + __func__, read_len, sock_fd); + + if (PCEP_MESSAGE_LENGTH - ret - buffer_read >= read_len) + read_ret = + read(sock_fd, &buffer[ret], read_len); + else { + pcep_log( + LOG_ERR, + "%s: Trying to read size (%d) offset (%d) in a buff of size (%d)", + __func__, read_len, ret, + PCEP_MESSAGE_LENGTH); + return msg_list; + } + + if (read_ret != read_len) { + pcep_log( + LOG_INFO, + "%s: pcep_msg_read: Did not manage to read enough data (%d != %d) fd [%d]", + __func__, read_ret, read_len, sock_fd); + return msg_list; + } + } + + msg = pcep_decode_message(buffer + buffer_read); + buffer_read += msg_length; + + if (msg == NULL) { + return msg_list; + } else { + dll_append(msg_list, msg); + } + } + + return msg_list; +} + +struct pcep_message *pcep_msg_get(double_linked_list *msg_list, uint8_t type) +{ + if (msg_list == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = msg_list->head; node != NULL; node = node->next_node) { + if (((struct pcep_message *)node->data)->msg_header->type + == type) { + return (struct pcep_message *)node->data; + } + } + + return NULL; +} + +struct pcep_message *pcep_msg_get_next(double_linked_list *list, + struct pcep_message *current, + uint8_t type) +{ + if (list == NULL || current == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = list->head; node != NULL; node = node->next_node) { + if (node->data == current) { + continue; + } + + if (((struct pcep_message *)node->data)->msg_header->type + == type) { + return (struct pcep_message *)node->data; + } + } + + return NULL; +} + +struct pcep_object_header *pcep_obj_get(double_linked_list *list, + uint8_t object_class) +{ + if (list == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *obj_item; + for (obj_item = list->head; obj_item != NULL; + obj_item = obj_item->next_node) { + if (((struct pcep_object_header *)obj_item->data)->object_class + == object_class) { + return (struct pcep_object_header *)obj_item->data; + } + } + + return NULL; +} + +struct pcep_object_header *pcep_obj_get_next(double_linked_list *list, + struct pcep_object_header *current, + uint8_t object_class) +{ + if (list == NULL || current == NULL) { + return NULL; + } + + if (list->head == NULL) { + return NULL; + } + + double_linked_list_node *node; + for (node = list->head; node != NULL; node = node->next_node) { + if (node->data == current) { + continue; + } + + if (((struct pcep_object_header *)node->data)->object_class + == object_class) { + return (struct pcep_object_header *)node->data; + } + } + + return NULL; +} + +void pcep_obj_free_tlv(struct pcep_object_tlv_header *tlv) +{ + /* Specific TLV freeing */ + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + if (((struct pcep_object_tlv_speaker_entity_identifier *)tlv) + ->speaker_entity_id_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_speaker_entity_identifier *) + tlv) + ->speaker_entity_id_list, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + if (((struct pcep_object_tlv_path_setup_type_capability *)tlv) + ->pst_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_path_setup_type_capability *) + tlv) + ->pst_list, + PCEPLIB_MESSAGES); + } + + if (((struct pcep_object_tlv_path_setup_type_capability *)tlv) + ->sub_tlv_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_object_tlv_path_setup_type_capability *) + tlv) + ->sub_tlv_list, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TYPE_CISCO_BSID: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + break; + } + + pceplib_free(PCEPLIB_MESSAGES, tlv); +} + +void pcep_obj_free_object(struct pcep_object_header *obj) +{ + /* Iterate the TLVs and free each one */ + if (obj->tlv_list != NULL) { + struct pcep_object_tlv_header *tlv; + while ((tlv = (struct pcep_object_tlv_header *) + dll_delete_first_node(obj->tlv_list)) + != NULL) { + pcep_obj_free_tlv(tlv); + } + + dll_destroy(obj->tlv_list); + } + + /* Specific object freeing */ + switch (obj->object_class) { + case PCEP_OBJ_CLASS_ERO: + case PCEP_OBJ_CLASS_IRO: + case PCEP_OBJ_CLASS_RRO: { + if (((struct pcep_object_ro *)obj)->sub_objects != NULL) { + double_linked_list_node *node = + ((struct pcep_object_ro *)obj) + ->sub_objects->head; + for (; node != NULL; node = node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = + (struct pcep_object_ro_subobj *) + node->data; + if (ro_subobj->ro_subobj_type + == RO_SUBOBJ_TYPE_SR) { + if (((struct pcep_ro_subobj_sr *) + ro_subobj) + ->nai_list + != NULL) { + dll_destroy_with_data_memtype( + ((struct + pcep_ro_subobj_sr *) + ro_subobj) + ->nai_list, + PCEPLIB_MESSAGES); + } + } + } + dll_destroy_with_data_memtype( + ((struct pcep_object_ro *)obj)->sub_objects, + PCEPLIB_MESSAGES); + } + } break; + + case PCEP_OBJ_CLASS_SVEC: + if (((struct pcep_object_svec *)obj)->request_id_list != NULL) { + dll_destroy_with_data_memtype( + ((struct pcep_object_svec *)obj) + ->request_id_list, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_CLASS_SWITCH_LAYER: + if (((struct pcep_object_switch_layer *)obj)->switch_layer_rows + != NULL) { + dll_destroy_with_data_memtype( + ((struct pcep_object_switch_layer *)obj) + ->switch_layer_rows, + PCEPLIB_MESSAGES); + } + break; + + case PCEP_OBJ_CLASS_OPEN: + case PCEP_OBJ_CLASS_RP: + case PCEP_OBJ_CLASS_NOPATH: + case PCEP_OBJ_CLASS_ENDPOINTS: + case PCEP_OBJ_CLASS_BANDWIDTH: + case PCEP_OBJ_CLASS_METRIC: + case PCEP_OBJ_CLASS_LSPA: + case PCEP_OBJ_CLASS_NOTF: + case PCEP_OBJ_CLASS_ERROR: + case PCEP_OBJ_CLASS_CLOSE: + case PCEP_OBJ_CLASS_OF: + case PCEP_OBJ_CLASS_LSP: + case PCEP_OBJ_CLASS_SRP: + case PCEP_OBJ_CLASS_VENDOR_INFO: + case PCEP_OBJ_CLASS_INTER_LAYER: + case PCEP_OBJ_CLASS_REQ_ADAP_CAP: + case PCEP_OBJ_CLASS_SERVER_IND: + case PCEP_OBJ_CLASS_ASSOCIATION: + case PCEP_OBJ_CLASS_MAX: + break; + } + + pceplib_free(PCEPLIB_MESSAGES, obj); +} + +void pcep_msg_free_message(struct pcep_message *message) +{ + /* Iterate the objects and free each one */ + if (message->obj_list != NULL) { + struct pcep_object_header *obj; + while ((obj = (struct pcep_object_header *) + dll_delete_first_node(message->obj_list)) + != NULL) { + pcep_obj_free_object(obj); + } + + dll_destroy(message->obj_list); + } + + if (message->msg_header != NULL) { + pceplib_free(PCEPLIB_MESSAGES, message->msg_header); + } + + if (message->encoded_message != NULL) { + pceplib_free(PCEPLIB_MESSAGES, message->encoded_message); + } + + pceplib_free(PCEPLIB_MESSAGES, message); +} + +void pcep_msg_free_message_list(double_linked_list *list) +{ + /* Iterate the messages and free each one */ + struct pcep_message *msg; + while ((msg = (struct pcep_message *)dll_delete_first_node(list)) + != NULL) { + pcep_msg_free_message(msg); + } + + dll_destroy(list); +} + +const char *get_message_type_str(uint8_t type) +{ + uint8_t msg_type = + (type > PCEP_TYPE_INITIATE) ? PCEP_TYPE_INITIATE + 1 : type; + + return message_type_strs[msg_type]; +} + +const char *get_object_class_str(uint8_t class) +{ + uint8_t object_class = + (class > PCEP_OBJ_CLASS_SRP) ? PCEP_OBJ_CLASS_SRP + 1 : class; + + return object_class_strs[object_class]; +} + +/* Expecting a list of struct pcep_message pointers */ +void pcep_msg_print(double_linked_list *msg_list) +{ + double_linked_list_node *node; + for (node = msg_list->head; node != NULL; node = node->next_node) { + struct pcep_message *msg = (struct pcep_message *)node->data; + pcep_log(LOG_INFO, "%s: PCEP_MSG %s", __func__, + get_message_type_str(msg->msg_header->type)); + + double_linked_list_node *obj_node = + (msg->obj_list == NULL ? NULL : msg->obj_list->head); + for (; obj_node != NULL; obj_node = obj_node->next_node) { + struct pcep_object_header *obj_header = + ((struct pcep_object_header *)obj_node->data); + pcep_log( + LOG_INFO, "%s: PCEP_OBJ %s", __func__, + get_object_class_str(obj_header->object_class)); + } + } +} + +int pcep_msg_send(int sock_fd, struct pcep_message *msg) +{ + if (msg == NULL) { + return 0; + } + int msg_length = ntohs(msg->encoded_message_length); + if (msg_length > PCEP_MESSAGE_LENGTH) { + pcep_log(LOG_ERR, "%s: Not sended, size(% d) exceed max(% d) ", + __func__, msg_length, PCEP_MESSAGE_LENGTH); + return 0; + } + + return write(sock_fd, msg->encoded_message, msg_length); +} diff --git a/pceplib/pcep_msg_tools.h b/pceplib/pcep_msg_tools.h new file mode 100644 index 0000000..e756b46 --- /dev/null +++ b/pceplib/pcep_msg_tools.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + */ + +#ifndef PCEP_TOOLS_H +#define PCEP_TOOLS_H + +#include +#include // struct in_addr + +#include "pcep_utils_double_linked_list.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCEP_MAX_SIZE 6000 + +/* Returns a double linked list of PCEP messages */ +double_linked_list *pcep_msg_read(int sock_fd); +/* Given a double linked list of PCEP messages, return the first node that has + * the same message type */ +struct pcep_message *pcep_msg_get(double_linked_list *msg_list, uint8_t type); +/* Given a double linked list of PCEP messages, return the next node after + * current node that has the same message type */ +struct pcep_message *pcep_msg_get_next(double_linked_list *msg_list, + struct pcep_message *current, + uint8_t type); +struct pcep_object_header *pcep_obj_get(double_linked_list *list, + uint8_t object_class); +struct pcep_object_header *pcep_obj_get_next(double_linked_list *list, + struct pcep_object_header *current, + uint8_t object_class); +struct pcep_object_tlv_header *pcep_tlv_get(double_linked_list *list, + uint16_t type); +struct pcep_object_tlv_header * +pcep_tlv_get_next(double_linked_list *list, + struct pcep_object_tlv_header *current, uint16_t type); +void pcep_obj_free_tlv(struct pcep_object_tlv_header *tlv); +void pcep_obj_free_object(struct pcep_object_header *obj); +void pcep_msg_free_message(struct pcep_message *message); +void pcep_msg_free_message_list(double_linked_list *list); +void pcep_msg_print(double_linked_list *list); +const char *get_message_type_str(uint8_t type); +const char *get_object_class_str(uint8_t class); +int pcep_msg_send(int sock_fd, struct pcep_message *hdr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pceplib/pcep_pcc.c b/pceplib/pcep_pcc.c new file mode 100644 index 0000000..92a968e --- /dev/null +++ b/pceplib/pcep_pcc.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Sample PCC implementation + */ + +#include + +#include // gethostbyname +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcep_pcc_api.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* + * PCEP PCC design spec: + * https://docs.google.com/presentation/d/1DYc3ZhYA1c_qg9A552HjhneJXQKdh_yrKW6v3NRYPtnbw/edit?usp=sharing + */ +#define MAX_SRC_IP_STR 40 +#define MAX_DST_IP_STR 40 +struct cmd_line_args { + char src_ip_str[MAX_SRC_IP_STR]; + char dest_ip_str[MAX_DST_IP_STR]; + short src_tcp_port; + short dest_tcp_port; + char tcp_md5_str[PCEP_MD5SIG_MAXKEYLEN]; /* RFC 2385 */ + bool is_ipv6; + bool eventpoll; /* poll for pcep_event's, or use callback (default) */ +}; + +bool pcc_active_ = true; +pcep_session *session = NULL; +struct cmd_line_args *cmd_line_args = NULL; +/* pcep_event callback variables */ +bool pcep_event_condition = false; +struct pcep_event *event = NULL; +pthread_mutex_t pcep_event_mutex; +pthread_cond_t pcep_event_cond_var; + +static const char DEFAULT_DEST_HOSTNAME[] = "localhost"; +static const char DEFAULT_DEST_HOSTNAME_IPV6[] = "ip6-localhost"; +static const short DEFAULT_SRC_TCP_PORT = 4999; + +// Private fn's +struct cmd_line_args *get_cmdline_args(int argc, char *argv[]); +void handle_signal_action(int sig_number); +void send_pce_path_request_message(pcep_session *session); +void send_pce_report_message(pcep_session *session); +void print_queue_event(struct pcep_event *event); +void pcep_event_callback(void *cb_data, pcep_event *e); + +struct cmd_line_args *get_cmdline_args(int argc, char *argv[]) +{ + /* Allocate and set default values */ + struct cmd_line_args *cmd_line_args = + malloc(sizeof(struct cmd_line_args)); + memset(cmd_line_args, 0, sizeof(struct cmd_line_args)); + strlcpy(cmd_line_args->dest_ip_str, DEFAULT_DEST_HOSTNAME, + MAX_DST_IP_STR); + cmd_line_args->src_tcp_port = DEFAULT_SRC_TCP_PORT; + cmd_line_args->is_ipv6 = false; + + /* Parse the cmd_line args: + * -ipv6 + * -srcip localhost + * -destip 192.168.0.2 + * -srcport 4999 + * -dstport 4189 + * -tcpmd5 hello + * -event_poll */ + int i = 1; + for (; i < argc; ++i) { + if (strcmp(argv[i], "-help") == 0 + || strcmp(argv[i], "--help") == 0 + || strcmp(argv[i], "-h") == 0) { + pcep_log( + LOG_INFO, + "%s: pcep_pcc [-ipv6] [-srcip localhost] [-destip 192.168.0.1] [-srcport 4999] [-dstport 4189] [-tcpmd5 authstr] [-eventpoll]", + __func__); + free(cmd_line_args); + return NULL; + } else if (strcmp(argv[i], "-ipv6") == 0) { + cmd_line_args->is_ipv6 = true; + if (argc == 2) { + strlcpy(cmd_line_args->dest_ip_str, + DEFAULT_DEST_HOSTNAME_IPV6, + MAX_DST_IP_STR); + } + } else if (strcmp(argv[i], "-eventpoll") == 0) { + cmd_line_args->eventpoll = true; + } else if (strcmp(argv[i], "-srcip") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->src_ip_str, argv[++i], + MAX_SRC_IP_STR); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-srcip\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-destip") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->dest_ip_str, argv[++i], + MAX_DST_IP_STR); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-destip\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-srcport") == 0) { + if (argc >= i + 2) { + cmd_line_args->src_tcp_port = atoi(argv[++i]); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-srcport\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-destport") == 0) { + if (argc >= i + 2) { + cmd_line_args->dest_tcp_port = atoi(argv[++i]); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-destport\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else if (strcmp(argv[i], "-tcpmd5") == 0) { + if (argc >= i + 2) { + strlcpy(cmd_line_args->tcp_md5_str, argv[++i], + sizeof(cmd_line_args->tcp_md5_str)); + } else { + pcep_log( + LOG_ERR, + "%s: Invalid number of cmd_line_args for \"-tcpmd5\"", + __func__); + free(cmd_line_args); + return NULL; + } + } else { + pcep_log(LOG_ERR, "%s: Invalid cmd_line_arg[%d] = %s", + __func__, i, argv[i]); + free(cmd_line_args); + return NULL; + } + } + + return cmd_line_args; +} + +void handle_signal_action(int sig_number) +{ + if (sig_number == SIGINT) { + pcep_log(LOG_INFO, "%s: SIGINT was caught!", __func__); + pcc_active_ = false; + if (cmd_line_args->eventpoll == false) { + pthread_mutex_lock(&pcep_event_mutex); + pcep_event_condition = true; + pthread_cond_signal(&pcep_event_cond_var); + pthread_mutex_unlock(&pcep_event_mutex); + } + } else if (sig_number == SIGUSR1) { + pcep_log(LOG_INFO, "%s: SIGUSR1 was caught, dumping counters", + __func__); + dump_pcep_session_counters(session); + pceplib_memory_dump(); + } else if (sig_number == SIGUSR2) { + pcep_log(LOG_INFO, "%s: SIGUSR2 was caught, reseting counters", + __func__); + reset_pcep_session_counters(session); + } +} + +static int setup_signals(void) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handle_signal_action; + if (sigaction(SIGINT, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + if (sigaction(SIGUSR1, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + if (sigaction(SIGUSR2, &sa, 0) != 0) { + perror("sigaction()"); + return -1; + } + + return 0; +} + +void send_pce_path_request_message(pcep_session *session) +{ + struct in_addr src_ipv4; + struct in_addr dst_ipv4; + inet_pton(AF_INET, "1.2.3.4", &src_ipv4); + inet_pton(AF_INET, "10.20.30.40", &dst_ipv4); + + struct pcep_object_rp *rp_object = + pcep_obj_create_rp(1, false, false, false, false, 42, NULL); + struct pcep_object_endpoints_ipv4 *ep_object = + pcep_obj_create_endpoint_ipv4(&src_ipv4, &dst_ipv4); + + struct pcep_message *path_request = + pcep_msg_create_request(rp_object, ep_object, NULL); + send_message(session, path_request, true); +} + +void send_pce_report_message(pcep_session *session) +{ + double_linked_list *report_list = dll_initialize(); + + /* SRP Path Setup Type TLV */ + struct pcep_object_tlv_path_setup_type *pst_tlv = + pcep_tlv_create_path_setup_type(SR_TE_PST); + double_linked_list *srp_tlv_list = dll_initialize(); + dll_append(srp_tlv_list, pst_tlv); + + /* + * Create the SRP object + */ + uint32_t srp_id_number = 0x10203040; + struct pcep_object_header *obj = + (struct pcep_object_header *)pcep_obj_create_srp( + false, srp_id_number, srp_tlv_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message SRP object was NULL", + __func__); + dll_destroy_with_data(report_list); + return; + } + dll_append(report_list, obj); + + /* LSP Symbolic path name TLV */ + char symbolic_path_name[] = "second-default"; + struct pcep_object_tlv_symbolic_path_name *spn_tlv = + pcep_tlv_create_symbolic_path_name(symbolic_path_name, 14); + double_linked_list *lsp_tlv_list = dll_initialize(); + dll_append(lsp_tlv_list, spn_tlv); + + /* LSP IPv4 LSP ID TLV */ + struct in_addr ipv4_tunnel_sender; + struct in_addr ipv4_tunnel_endpoint; + inet_pton(AF_INET, "9.9.1.1", &ipv4_tunnel_sender); + inet_pton(AF_INET, "9.9.2.1", &ipv4_tunnel_endpoint); + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp_id_tlv = + pcep_tlv_create_ipv4_lsp_identifiers(&ipv4_tunnel_sender, + &ipv4_tunnel_endpoint, 42, + 1, NULL); + dll_append(lsp_tlv_list, ipv4_lsp_id_tlv); + + /* + * Create the LSP object + */ + uint32_t plsp_id = 42; + enum pcep_lsp_operational_status lsp_status = + PCEP_LSP_OPERATIONAL_ACTIVE; + bool c_flag = false; /* Lsp was created by PcInitiate msg */ + bool a_flag = false; /* Admin state, active / inactive */ + bool r_flag = false; /* true if LSP has been removed */ + bool s_flag = true; /* Synchronization */ + bool d_flag = false; /* Delegate LSP to PCE */ + obj = (struct pcep_object_header *)pcep_obj_create_lsp( + plsp_id, lsp_status, c_flag, a_flag, r_flag, s_flag, d_flag, + lsp_tlv_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message LSP object was NULL", + __func__); + dll_destroy_with_data(report_list); + return; + } + dll_append(report_list, obj); + + /* Create 2 ERO NONAI sub-objects */ + double_linked_list *ero_subobj_list = dll_initialize(); + struct pcep_ro_subobj_sr *sr_subobj_nonai1 = + pcep_obj_create_ro_subobj_sr_nonai(false, 503808, true, true); + dll_append(ero_subobj_list, sr_subobj_nonai1); + + struct pcep_ro_subobj_sr *sr_subobj_nonai2 = + pcep_obj_create_ro_subobj_sr_nonai(false, 1867776, true, true); + dll_append(ero_subobj_list, sr_subobj_nonai2); + + /* Create ERO IPv4 node sub-object */ + struct in_addr sr_subobj_ipv4; + inet_pton(AF_INET, "9.9.9.1", &sr_subobj_ipv4); + struct pcep_ro_subobj_sr *sr_subobj_ipv4node = + pcep_obj_create_ro_subobj_sr_ipv4_node( + false, false, false, true, 16060, &sr_subobj_ipv4); + if (sr_subobj_ipv4node == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message ERO sub-object was NULL", + __func__); + return; + } + dll_append(ero_subobj_list, sr_subobj_ipv4node); + + /* + * Create the ERO object + */ + obj = (struct pcep_object_header *)pcep_obj_create_ero(ero_subobj_list); + if (obj == NULL) { + pcep_log(LOG_WARNING, + "%s: send_pce_report_message ERO object was NULL", + __func__); + dll_destroy_with_data(report_list); + return; + } + dll_append(report_list, obj); + + /* + * Create the Metric object + */ + obj = (struct pcep_object_header *)pcep_obj_create_metric( + PCEP_METRIC_TE, false, true, 16.0); + dll_append(report_list, obj); + + /* Create and send the report message */ + struct pcep_message *report_msg = pcep_msg_create_report(report_list); + send_message(session, report_msg, true); +} + +void print_queue_event(struct pcep_event *event) +{ + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Received Event: type [%s] on session [%d] occurred at [%ld]", + __func__, time(NULL), pthread_self(), + get_event_type_str(event->event_type), + event->session->session_id, event->event_time); + + if (event->event_type == MESSAGE_RECEIVED) { + pcep_log( + LOG_INFO, "%s: \t Event message type [%s]", __func__, + get_message_type_str(event->message->msg_header->type)); + } +} + +/* Called by pcep_session_logic when pcep_event's are ready */ +void pcep_event_callback(void *cb_data, pcep_event *e) +{ + (void)cb_data; + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] pcep_event_callback", __func__, + time(NULL), pthread_self()); + pthread_mutex_lock(&pcep_event_mutex); + event = e; + pcep_event_condition = true; + pthread_cond_signal(&pcep_event_cond_var); + pthread_mutex_unlock(&pcep_event_mutex); +} + +int main(int argc, char **argv) +{ + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] starting pcc_pcep example client", + __func__, time(NULL), pthread_self()); + + cmd_line_args = get_cmdline_args(argc, argv); + if (cmd_line_args == NULL) { + return -1; + } + + setup_signals(); + + if (cmd_line_args->eventpoll == false) { + struct pceplib_infra_config infra_config; + memset(&infra_config, 0, sizeof(infra_config)); + infra_config.pcep_event_func = pcep_event_callback; + if (!initialize_pcc_infra(&infra_config)) { + pcep_log(LOG_ERR, + "%s: Error initializing PCC with infra.", + __func__); + return -1; + } + } else { + if (!initialize_pcc()) { + pcep_log(LOG_ERR, "%s: Error initializing PCC.", + __func__); + return -1; + } + } + + pcep_configuration *config = create_default_pcep_configuration(); + config->pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = true; + config->src_pcep_port = cmd_line_args->src_tcp_port; + config->is_tcp_auth_md5 = true; + + strlcpy(config->tcp_authentication_str, cmd_line_args->tcp_md5_str, + sizeof(config->tcp_authentication_str)); + + int af = (cmd_line_args->is_ipv6 ? AF_INET6 : AF_INET); + struct hostent *host_info = + gethostbyname2(cmd_line_args->dest_ip_str, af); + if (host_info == NULL) { + pcep_log(LOG_ERR, "%s: Error getting IP address.", __func__); + return -1; + } + + if (cmd_line_args->is_ipv6) { + struct in6_addr host_address; + memcpy(&host_address, host_info->h_addr, host_info->h_length); + session = connect_pce_ipv6(config, &host_address); + } else { + struct in_addr host_address; + memcpy(&host_address, host_info->h_addr, host_info->h_length); + session = connect_pce(config, &host_address); + } + + if (session == NULL) { + pcep_log(LOG_WARNING, "%s: Error in connect_pce.", __func__); + destroy_pcep_configuration(config); + return -1; + } + + sleep(2); + + send_pce_report_message(session); + /*send_pce_path_request_message(session);*/ + + /* Wait for pcep_event's either by polling the event queue or by + * callback */ + if (cmd_line_args->eventpoll == true) { + /* Poll the pcep_event queue*/ + while (pcc_active_) { + if (event_queue_is_empty() == false) { + struct pcep_event *event = + event_queue_get_event(); + print_queue_event(event); + destroy_pcep_event(event); + } + + sleep(5); + } + } else { + /* Get events via callback and conditional variable */ + pthread_mutex_init(&pcep_event_mutex, NULL); + pthread_cond_init(&pcep_event_cond_var, NULL); + while (pcc_active_) { + pthread_mutex_lock(&pcep_event_mutex); + + /* this internal loop helps avoid spurious interrupts */ + while (!pcep_event_condition) { + pthread_cond_wait(&pcep_event_cond_var, + &pcep_event_mutex); + } + + /* Check if we have been interrupted by SIGINT */ + if (pcc_active_) { + print_queue_event(event); + destroy_pcep_event(event); + } + + pcep_event_condition = false; + pthread_mutex_unlock(&pcep_event_mutex); + } + + pthread_mutex_destroy(&pcep_event_mutex); + pthread_cond_destroy(&pcep_event_cond_var); + } + + pcep_log(LOG_NOTICE, "%s: Disconnecting from PCE", __func__); + disconnect_pce(session); + destroy_pcep_configuration(config); + free(cmd_line_args); + + if (!destroy_pcc()) { + pcep_log(LOG_NOTICE, "%s: Error stopping PCC.", __func__); + } + + pceplib_memory_dump(); + + return 0; +} diff --git a/pceplib/pcep_pcc_api.c b/pceplib/pcep_pcc_api.c new file mode 100644 index 0000000..a807642 --- /dev/null +++ b/pceplib/pcep_pcc_api.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Public PCEPlib PCC API implementation + */ + +#include + +#include +#include +#include +#include + +#include "pcep_msg_messages.h" +#include "pcep_pcc_api.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" + +/* Not using an array here since the enum pcep_event_type indeces go into the + * 100's */ +const char MESSAGE_RECEIVED_STR[] = "MESSAGE_RECEIVED"; +const char PCE_CLOSED_SOCKET_STR[] = "PCE_CLOSED_SOCKET"; +const char PCE_SENT_PCEP_CLOSE_STR[] = "PCE_SENT_PCEP_CLOSE"; +const char PCE_DEAD_TIMER_EXPIRED_STR[] = "PCE_DEAD_TIMER_EXPIRED"; +const char PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED_STR[] = + "PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED"; +const char PCC_CONNECTED_TO_PCE_STR[] = "PCC_CONNECTED_TO_PCE"; +const char PCC_PCEP_SESSION_CLOSED_STR[] = "PCC_PCEP_SESSION_CLOSED"; +const char PCC_RCVD_INVALID_OPEN_STR[] = "PCC_RCVD_INVALID_OPEN"; +const char PCC_RCVD_MAX_INVALID_MSGS_STR[] = "PCC_RCVD_MAX_INVALID_MSGS"; +const char PCC_RCVD_MAX_UNKOWN_MSGS_STR[] = "PCC_RCVD_MAX_UNKOWN_MSGS"; +const char UNKNOWN_EVENT_STR[] = "UNKNOWN Event Type"; + +/* Session Logic Handle managed in pcep_session_logic.c */ +extern pcep_event_queue *session_logic_event_queue_; + +bool initialize_pcc(void) +{ + if (!run_session_logic()) { + pcep_log(LOG_ERR, "%s: Error initializing PCC session logic.", + __func__); + return false; + } + + return true; +} + + +bool initialize_pcc_infra(struct pceplib_infra_config *infra_config) +{ + if (infra_config == NULL) { + return initialize_pcc(); + } + + if (!run_session_logic_with_infra(infra_config)) { + pcep_log(LOG_ERR, + "%s: Error initializing PCC session logic with infra.", + __func__); + return false; + } + + return true; +} + + +/* this function is blocking */ +bool initialize_pcc_wait_for_completion(void) +{ + return run_session_logic_wait_for_completion(); +} + + +bool destroy_pcc(void) +{ + if (!stop_session_logic()) { + pcep_log(LOG_WARNING, "%s: Error stopping PCC session logic.", + __func__); + return false; + } + + return true; +} + + +pcep_configuration *create_default_pcep_configuration(void) +{ + pcep_configuration *config = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_configuration)); + memset(config, 0, sizeof(pcep_configuration)); + + config->keep_alive_seconds = DEFAULT_CONFIG_KEEP_ALIVE; + /* This value will possibly be overwritten later with PCE config data */ + config->keep_alive_pce_negotiated_timer_seconds = + DEFAULT_CONFIG_KEEP_ALIVE; + config->min_keep_alive_seconds = DEFAULT_MIN_CONFIG_KEEP_ALIVE; + config->max_keep_alive_seconds = DEFAULT_MAX_CONFIG_KEEP_ALIVE; + + config->dead_timer_seconds = DEFAULT_CONFIG_DEAD_TIMER; + /* This value will be overwritten later with PCE config data */ + config->dead_timer_pce_negotiated_seconds = DEFAULT_CONFIG_DEAD_TIMER; + config->min_dead_timer_seconds = DEFAULT_MIN_CONFIG_DEAD_TIMER; + config->max_dead_timer_seconds = DEFAULT_MAX_CONFIG_DEAD_TIMER; + + config->request_time_seconds = DEFAULT_CONFIG_REQUEST_TIME; + config->max_unknown_messages = DEFAULT_CONFIG_MAX_UNKNOWN_MESSAGES; + config->max_unknown_requests = DEFAULT_CONFIG_MAX_UNKNOWN_REQUESTS; + + config->socket_connect_timeout_millis = + DEFAULT_TCP_CONNECT_TIMEOUT_MILLIS; + config->support_stateful_pce_lsp_update = true; + config->support_pce_lsp_instantiation = true; + config->support_include_db_version = true; + config->lsp_db_version = 0; + config->support_lsp_triggered_resync = true; + config->support_lsp_delta_sync = true; + config->support_pce_triggered_initial_sync = true; + config->support_sr_te_pst = true; + config->pcc_can_resolve_nai_to_sid = true; + config->max_sid_depth = 0; + config->dst_pcep_port = 0; + config->src_pcep_port = 0; + config->src_ip.src_ipv4.s_addr = INADDR_ANY; + config->is_src_ipv6 = false; + config->pcep_msg_versioning = create_default_pcep_versioning(); + config->tcp_authentication_str[0] = '\0'; + config->is_tcp_auth_md5 = true; + + return config; +} + +void destroy_pcep_configuration(pcep_configuration *config) +{ + destroy_pcep_versioning(config->pcep_msg_versioning); + pceplib_free(PCEPLIB_INFRA, config); +} + +pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip) +{ + return create_pcep_session(config, pce_ip); +} + +pcep_session *connect_pce_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip) +{ + return create_pcep_session_ipv6(config, pce_ip); +} + +void disconnect_pce(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: disconnect_pce session [%p] has already been deleted", + __func__, session); + return; + } + + if (session->socket_comm_session == NULL + || session->socket_comm_session->socket_fd < 0) { + /* If the socket has already been closed, just destroy the + * session */ + destroy_pcep_session(session); + } else { + /* This will cause the session to be destroyed AFTER the close + * message is sent */ + session->destroy_session_after_write = true; + + /* Send a PCEP close message */ + close_pcep_session(session); + } +} + +void send_message(pcep_session *session, struct pcep_message *msg, + bool free_after_send) +{ + if (session == NULL || msg == NULL) { + pcep_log(LOG_DEBUG, + "%s: send_message NULL params session [%p] msg [%p]", + __func__, session, msg); + + return; + } + + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: send_message session [%p] has already been deleted", + __func__, session); + return; + } + + pcep_encode_message(msg, session->pcc_config.pcep_msg_versioning); + socket_comm_session_send_message( + session->socket_comm_session, (char *)msg->encoded_message, + msg->encoded_message_length, free_after_send); + + increment_message_tx_counters(session, msg); + + if (free_after_send == true) { + /* The encoded_message will be deleted once sent, so everything + * else in the message will be freed */ + msg->encoded_message = NULL; + pcep_msg_free_message(msg); + } +} + +/* Returns true if the queue is empty, false otherwise */ +bool event_queue_is_empty(void) +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_is_empty Session Logic is not initialized yet", + __func__); + return false; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + bool is_empty = + (session_logic_event_queue_->event_queue->num_entries == 0); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return is_empty; +} + + +/* Return the number of events on the queue, 0 if empty */ +uint32_t event_queue_num_events_available(void) +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_num_events_available Session Logic is not initialized yet", + __func__); + return 0; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + uint32_t num_events = + session_logic_event_queue_->event_queue->num_entries; + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return num_events; +} + + +/* Return the next event on the queue, NULL if empty */ +struct pcep_event *event_queue_get_event(void) +{ + if (session_logic_event_queue_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: event_queue_get_event Session Logic is not initialized yet", + __func__); + return NULL; + } + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + struct pcep_event *event = (struct pcep_event *)queue_dequeue( + session_logic_event_queue_->event_queue); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + return event; +} + + +/* Free the PCEP Event resources, including the PCEP message */ +void destroy_pcep_event(struct pcep_event *event) +{ + if (event == NULL) { + pcep_log(LOG_WARNING, + "%s: destroy_pcep_event cannot destroy NULL event", + __func__); + return; + } + + if (event->event_type == MESSAGE_RECEIVED && event->message != NULL) { + pcep_msg_free_message(event->message); + } + + pceplib_free(PCEPLIB_INFRA, event); +} + +const char *get_event_type_str(int event_type) +{ + switch (event_type) { + case MESSAGE_RECEIVED: + return MESSAGE_RECEIVED_STR; + break; + case PCE_CLOSED_SOCKET: + return PCE_CLOSED_SOCKET_STR; + break; + case PCE_SENT_PCEP_CLOSE: + return PCE_SENT_PCEP_CLOSE_STR; + break; + case PCE_DEAD_TIMER_EXPIRED: + return PCE_DEAD_TIMER_EXPIRED_STR; + break; + case PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED: + return PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED_STR; + break; + case PCC_CONNECTED_TO_PCE: + return PCC_CONNECTED_TO_PCE_STR; + break; + case PCC_PCEP_SESSION_CLOSED: + return PCC_PCEP_SESSION_CLOSED_STR; + break; + case PCC_RCVD_INVALID_OPEN: + return PCC_RCVD_INVALID_OPEN_STR; + break; + case PCC_RCVD_MAX_INVALID_MSGS: + return PCC_RCVD_MAX_INVALID_MSGS_STR; + break; + case PCC_RCVD_MAX_UNKOWN_MSGS: + return PCC_RCVD_MAX_UNKOWN_MSGS_STR; + break; + default: + return UNKNOWN_EVENT_STR; + break; + } +} + +void dump_pcep_session_counters(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: dump_pcep_session_counters session [%p] has already been deleted", + __func__, session); + return; + } + + /* Update the counters group name so that the PCE session connected time + * is accurate */ + time_t now = time(NULL); + char counters_name[MAX_COUNTER_STR_LENGTH] = {0}; + char ip_str[40] = {0}; + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + ip_str, 40); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + ip_str, 40); + } + snprintf(counters_name, MAX_COUNTER_STR_LENGTH, + "PCEP Session [%d], connected to [%s] for [%u seconds]", + session->session_id, ip_str, + (uint32_t)(now - session->time_connected)); + strlcpy(session->pcep_session_counters->counters_group_name, + counters_name, + sizeof(session->pcep_session_counters->counters_group_name)); + + dump_counters_group_to_log(session->pcep_session_counters); +} + +void reset_pcep_session_counters(pcep_session *session) +{ + if (session_exists(session) == false) { + pcep_log( + LOG_WARNING, + "%s: reset_pcep_session_counters session [%p] has already been deleted", + session); + return; + } + + reset_group_counters(session->pcep_session_counters); +} diff --git a/pceplib/pcep_pcc_api.h b/pceplib/pcep_pcc_api.h new file mode 100644 index 0000000..f29de68 --- /dev/null +++ b/pceplib/pcep_pcc_api.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Public PCEPlib PCC API + */ + +#ifndef PCEPPCC_INCLUDE_PCEPPCCAPI_H_ +#define PCEPPCC_INCLUDE_PCEPPCCAPI_H_ + +#include + +#include "pcep_session_logic.h" +#include "pcep_timers.h" + +#define DEFAULT_PCEP_TCP_PORT 4189 +#define DEFAULT_CONFIG_KEEP_ALIVE 30 +#define DEFAULT_CONFIG_DEAD_TIMER DEFAULT_CONFIG_KEEP_ALIVE * 4 +#define DEFAULT_CONFIG_REQUEST_TIME 30 +#define DEFAULT_CONFIG_MAX_UNKNOWN_REQUESTS 5 +#define DEFAULT_CONFIG_MAX_UNKNOWN_MESSAGES 5 +#define DEFAULT_TCP_CONNECT_TIMEOUT_MILLIS 250 + +/* Acceptable MIN and MAX values used in deciding if the PCEP + * Open received from a PCE should be accepted or rejected. */ +#define DEFAULT_MIN_CONFIG_KEEP_ALIVE 5 +#define DEFAULT_MAX_CONFIG_KEEP_ALIVE 120 +#define DEFAULT_MIN_CONFIG_DEAD_TIMER DEFAULT_MIN_CONFIG_KEEP_ALIVE * 4 +#define DEFAULT_MAX_CONFIG_DEAD_TIMER DEFAULT_MAX_CONFIG_KEEP_ALIVE * 4 + +/* + * PCEP PCC library initialization/teardown functions + */ + +/* Later when this is integrated with FRR pathd, it will be changed + * to just initialize_pcc(struct pceplib_infra_config *infra_config) */ +bool initialize_pcc(void); +bool initialize_pcc_infra(struct pceplib_infra_config *infra_config); +/* this function is blocking */ +bool initialize_pcc_wait_for_completion(void); +bool destroy_pcc(void); + + +/* + * PCEP session functions + */ + +pcep_configuration *create_default_pcep_configuration(void); +void destroy_pcep_configuration(pcep_configuration *config); + +/* Uses the standard PCEP TCP src and dest port = 4189. + * To use a specific dest or src port, set them other than 0 in the + * pcep_configuration. If src_ip is not set, INADDR_ANY will be used. */ +pcep_session *connect_pce(pcep_configuration *config, struct in_addr *pce_ip); +pcep_session *connect_pce_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip); +void disconnect_pce(pcep_session *session); +void send_message(pcep_session *session, struct pcep_message *msg, + bool free_after_send); + +void dump_pcep_session_counters(pcep_session *session); +void reset_pcep_session_counters(pcep_session *session); + +/* + * Event Queue functions + */ + +/* Returns true if the queue is empty, false otherwise */ +bool event_queue_is_empty(void); + +/* Return the number of events on the queue, 0 if empty */ +uint32_t event_queue_num_events_available(void); + +/* Return the next event on the queue, NULL if empty */ +struct pcep_event *event_queue_get_event(void); + +/* Free the PCEP Event resources, including the PCEP message */ +void destroy_pcep_event(struct pcep_event *event); + +const char *get_event_type_str(int event_type); + + +#endif /* PCEPPCC_INCLUDE_PCEPPCCAPI_H_ */ diff --git a/pceplib/pcep_session_logic.c b/pceplib/pcep_session_logic.c new file mode 100644 index 0000000..f71c93a --- /dev/null +++ b/pceplib/pcep_session_logic.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* + * public API function implementations for the session_logic + */ + +pcep_session_logic_handle *session_logic_handle_ = NULL; +pcep_event_queue *session_logic_event_queue_ = NULL; +int session_id_ = 0; + +void send_pcep_open(pcep_session *session); /* forward decl */ + +static bool run_session_logic_common(void) +{ + if (session_logic_handle_ != NULL) { + pcep_log(LOG_WARNING, + "%s: Session Logic is already initialized.", __func__); + return false; + } + + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + + session_logic_handle_->active = true; + session_logic_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + session_logic_handle_->session_event_queue = queue_initialize(); + + /* Initialize the event queue */ + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); + if (pthread_mutex_init(&(session_logic_event_queue_->event_queue_mutex), + NULL) + != 0) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic event queue mutex.", + __func__); + return false; + } + + pthread_cond_init(&(session_logic_handle_->session_logic_cond_var), + NULL); + + if (pthread_mutex_init(&(session_logic_handle_->session_logic_mutex), + NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic mutex.", + __func__); + return false; + } + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + + if (pthread_mutex_init(&(session_logic_handle_->session_list_mutex), + NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_list mutex.", + __func__); + return false; + } + + return true; +} + + +bool run_session_logic(void) +{ + if (!run_session_logic_common()) { + return false; + } + + if (pthread_create(&(session_logic_handle_->session_logic_thread), NULL, + session_logic_loop, session_logic_handle_)) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic thread.", + __func__); + return false; + } + + if (!initialize_timers(session_logic_timer_expire_handler)) { + pcep_log(LOG_ERR, "%s: Cannot initialize session_logic timers.", + __func__); + return false; + } + + /* No need to call initialize_socket_comm_loop() since it will be + * called internally when the first socket_comm_session is created. */ + + return true; +} + + +bool run_session_logic_with_infra(pceplib_infra_config *infra_config) +{ + if (infra_config == NULL) { + return run_session_logic(); + } + + /* Initialize the memory infrastructure before anything gets allocated + */ + if (infra_config->pceplib_infra_mt != NULL + && infra_config->pceplib_messages_mt != NULL) { + pceplib_memory_initialize( + infra_config->pceplib_infra_mt, + infra_config->pceplib_messages_mt, + infra_config->malloc_func, infra_config->calloc_func, + infra_config->realloc_func, infra_config->strdup_func, + infra_config->free_func); + } + + if (!run_session_logic_common()) { + return false; + } + + /* Create the pcep_session_logic pthread so it can be managed externally + */ + if (infra_config->pthread_create_func != NULL) { + if (infra_config->pthread_create_func( + &(session_logic_handle_->session_logic_thread), + NULL, session_logic_loop, session_logic_handle_, + "pcep_session_logic")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external session_logic thread.", + __func__); + return false; + } + } else { + if (pthread_create( + &(session_logic_handle_->session_logic_thread), + NULL, session_logic_loop, session_logic_handle_)) { + pcep_log(LOG_ERR, + "%s: Cannot initialize session_logic thread.", + __func__); + return false; + } + } + + session_logic_event_queue_->event_callback = + infra_config->pcep_event_func; + session_logic_event_queue_->event_callback_data = + infra_config->external_infra_data; + + if (!initialize_timers_external_infra( + session_logic_timer_expire_handler, + infra_config->external_infra_data, + infra_config->timer_create_func, + infra_config->timer_cancel_func, + infra_config->pthread_create_func)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic timers with infra.", + __func__); + return false; + } + + /* We found a problem with the FRR sockets, where not all the KeepAlive + * messages were received, so if the pthread_create_func is set, the + * internal PCEPlib socket infrastructure will be used. */ + + /* For the SocketComm, the socket_read/write_func and the + * pthread_create_func are mutually exclusive. */ + if (infra_config->pthread_create_func != NULL) { + if (!initialize_socket_comm_external_infra( + infra_config->external_infra_data, NULL, NULL, + infra_config->pthread_create_func)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic socket comm with infra.", + __func__); + return false; + } + } else if (infra_config->socket_read_func != NULL + && infra_config->socket_write_func != NULL) { + if (!initialize_socket_comm_external_infra( + infra_config->external_infra_data, + infra_config->socket_read_func, + infra_config->socket_write_func, NULL)) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize session_logic socket comm with infra.", + __func__); + return false; + } + } + + return true; +} + +bool run_session_logic_wait_for_completion(void) +{ + if (!run_session_logic()) { + return false; + } + + /* Blocking call, waits for session logic thread to complete */ + pthread_join(session_logic_handle_->session_logic_thread, NULL); + + return true; +} + + +bool stop_session_logic(void) +{ + if (session_logic_handle_ == NULL) { + pcep_log(LOG_WARNING, "%s: Session logic already stopped", + __func__); + return false; + } + + session_logic_handle_->active = false; + teardown_timers(); + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + pthread_join(session_logic_handle_->session_logic_thread, NULL); + + pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex)); + ordered_list_destroy(session_logic_handle_->session_list); + queue_destroy(session_logic_handle_->session_event_queue); + + /* destroy the event_queue */ + pthread_mutex_destroy(&(session_logic_event_queue_->event_queue_mutex)); + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + + /* Explicitly stop the socket comm loop started by the pcep_sessions */ + destroy_socket_comm_loop(); + + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + session_logic_handle_ = NULL; + + return true; +} + + +void close_pcep_session(pcep_session *session) +{ + close_pcep_session_with_reason(session, PCEP_CLOSE_REASON_NO); +} + +void close_pcep_session_with_reason(pcep_session *session, + enum pcep_close_reason reason) +{ + struct pcep_message *close_msg = pcep_msg_create_close(reason); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send pcep_close message for session [%d]", + __func__, time(NULL), pthread_self(), session->session_id); + + session_send_message(session, close_msg); + socket_comm_session_close_tcp_after_write(session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; +} + + +void destroy_pcep_session(pcep_session *session) +{ + if (session == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot destroy NULL session", + __func__); + return; + } + + /* Remove the session from the session_list and synchronize session + * destroy with the session_logic_loop, so that no in-flight events + * will be handled now that the session is destroyed. */ + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + ordered_list_remove_first_node_equals( + session_logic_handle_->session_list, session); + pcep_log(LOG_DEBUG, + "%s: destroy_pcep_session delete session_list sessionPtr %p", + __func__, session); + + pcep_session_cancel_timers(session); + delete_counters_group(session->pcep_session_counters); + queue_destroy_with_data(session->num_unknown_messages_time_queue); + socket_comm_session_teardown(session->socket_comm_session); + + if (session->pcc_config.pcep_msg_versioning != NULL) { + pceplib_free(PCEPLIB_INFRA, + session->pcc_config.pcep_msg_versioning); + } + + if (session->pce_config.pcep_msg_versioning != NULL) { + pceplib_free(PCEPLIB_INFRA, + session->pce_config.pcep_msg_versioning); + } + + int session_id = session->session_id; + pceplib_free(PCEPLIB_INFRA, session); + pcep_log(LOG_INFO, "%s: [%ld-%ld] session [%d] destroyed", __func__, + time(NULL), pthread_self(), session_id); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); +} + +void pcep_session_cancel_timers(pcep_session *session) +{ + if (session == NULL) { + return; + } + + if (session->timer_id_dead_timer != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_dead_timer); + } + + if (session->timer_id_keep_alive != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_keep_alive); + } + + if (session->timer_id_open_keep_wait != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_open_keep_wait); + } + + if (session->timer_id_open_keep_alive != TIMER_ID_NOT_SET) { + cancel_timer(session->timer_id_open_keep_alive); + } +} + +/* Internal util function */ +static int get_next_session_id(void) +{ + if (session_id_ == INT_MAX) { + session_id_ = 0; + } + + return session_id_++; +} + +/* Internal util function */ +static pcep_session *create_pcep_session_pre_setup(pcep_configuration *config) +{ + if (config == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL config", + __func__); + return NULL; + } + + pcep_session *session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session)); + memset(session, 0, sizeof(pcep_session)); + session->session_id = get_next_session_id(); + session->session_state = SESSION_STATE_INITIALIZED; + session->timer_id_open_keep_wait = TIMER_ID_NOT_SET; + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + session->stateful_pce = false; + session->num_unknown_messages_time_queue = queue_initialize(); + session->pce_open_received = false; + session->pce_open_rejected = false; + session->pce_open_keep_alive_sent = false; + session->pcc_open_rejected = false; + session->pce_open_accepted = false; + session->pcc_open_accepted = false; + session->destroy_session_after_write = false; + session->lsp_db_version = config->lsp_db_version; + memcpy(&(session->pcc_config), config, sizeof(pcep_configuration)); + /* copy the pcc_config to the pce_config until we receive the open + * keep_alive response */ + memcpy(&(session->pce_config), config, sizeof(pcep_configuration)); + if (config->pcep_msg_versioning != NULL) { + session->pcc_config.pcep_msg_versioning = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memcpy(session->pcc_config.pcep_msg_versioning, + config->pcep_msg_versioning, + sizeof(struct pcep_versioning)); + session->pce_config.pcep_msg_versioning = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct pcep_versioning)); + memcpy(session->pce_config.pcep_msg_versioning, + config->pcep_msg_versioning, + sizeof(struct pcep_versioning)); + } + + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + ordered_list_add_node(session_logic_handle_->session_list, session); + pcep_log( + LOG_DEBUG, + "%s: create_pcep_session_pre_setup add session_list sessionPtr %p", + __func__, session); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); + + return session; +} + +/* Internal util function */ +static bool create_pcep_session_post_setup(pcep_session *session) +{ + if (!socket_comm_session_connect_tcp(session->socket_comm_session)) { + pcep_log(LOG_WARNING, "%s: Cannot establish TCP socket.", + __func__); + destroy_pcep_session(session); + + return false; + } + + session->time_connected = time(NULL); + create_session_counters(session); + + send_pcep_open(session); + + session->session_state = SESSION_STATE_PCEP_CONNECTING; + session->timer_id_open_keep_wait = + create_timer(session->pcc_config.keep_alive_seconds, session); + // session->session_state = SESSION_STATE_OPENED; + + return true; +} + +pcep_session *create_pcep_session(pcep_configuration *config, + struct in_addr *pce_ip) +{ + if (pce_ip == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL pce_ip", + __func__); + return NULL; + } + + pcep_session *session = create_pcep_session_pre_setup(config); + if (session == NULL) { + return NULL; + } + + session->socket_comm_session = socket_comm_session_initialize_with_src( + NULL, session_logic_msg_ready_handler, + session_logic_message_sent_handler, + session_logic_conn_except_notifier, &(config->src_ip.src_ipv4), + ((config->src_pcep_port == 0) ? PCEP_TCP_PORT + : config->src_pcep_port), + pce_ip, + ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT + : config->dst_pcep_port), + config->socket_connect_timeout_millis, + config->tcp_authentication_str, config->is_tcp_auth_md5, + session); + if (session->socket_comm_session == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot establish socket_comm_session.", __func__); + destroy_pcep_session(session); + + return NULL; + } + + if (create_pcep_session_post_setup(session) == false) { + return NULL; + } + + return session; +} + +pcep_session *create_pcep_session_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip) +{ + if (pce_ip == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot create pcep session with NULL pce_ip", + __func__); + return NULL; + } + + pcep_session *session = create_pcep_session_pre_setup(config); + if (session == NULL) { + return NULL; + } + + session->socket_comm_session = + socket_comm_session_initialize_with_src_ipv6( + NULL, session_logic_msg_ready_handler, + session_logic_message_sent_handler, + session_logic_conn_except_notifier, + &(config->src_ip.src_ipv6), + ((config->src_pcep_port == 0) ? PCEP_TCP_PORT + : config->src_pcep_port), + pce_ip, + ((config->dst_pcep_port == 0) ? PCEP_TCP_PORT + : config->dst_pcep_port), + config->socket_connect_timeout_millis, + config->tcp_authentication_str, config->is_tcp_auth_md5, + session); + if (session->socket_comm_session == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot establish ipv6 socket_comm_session.", + __func__); + destroy_pcep_session(session); + + return NULL; + } + + if (create_pcep_session_post_setup(session) == false) { + return NULL; + } + + return session; +} + + +void session_send_message(pcep_session *session, struct pcep_message *message) +{ + pcep_encode_message(message, session->pcc_config.pcep_msg_versioning); + socket_comm_session_send_message(session->socket_comm_session, + (char *)message->encoded_message, + message->encoded_message_length, true); + + increment_message_tx_counters(session, message); + + /* The message->encoded_message will be freed in + * socket_comm_session_send_message() once sent. + * Setting to NULL here so pcep_msg_free_message() does not free it */ + message->encoded_message = NULL; + pcep_msg_free_message(message); +} + + +/* This function is also used in pcep_session_logic_states.c */ +struct pcep_message *create_pcep_open(pcep_session *session) +{ + /* create and send PCEP open + * with PCEP, the PCC sends the config the PCE should use in the open + * message, + * and the PCE will send an open with the config the PCC should use. */ + double_linked_list *tlv_list = dll_initialize(); + if (session->pcc_config.support_stateful_pce_lsp_update + || session->pcc_config.support_pce_lsp_instantiation + || session->pcc_config.support_include_db_version + || session->pcc_config.support_lsp_triggered_resync + || session->pcc_config.support_lsp_delta_sync + || session->pcc_config.support_pce_triggered_initial_sync) { + /* Prepend this TLV as the first in the list */ + dll_append( + tlv_list, + pcep_tlv_create_stateful_pce_capability( + /* U flag */ + session->pcc_config + .support_stateful_pce_lsp_update, + /* S flag */ + session->pcc_config.support_include_db_version, + /* I flag */ + session->pcc_config + .support_pce_lsp_instantiation, + /* T flag */ + session->pcc_config + .support_lsp_triggered_resync, + /* D flag */ + session->pcc_config.support_lsp_delta_sync, + /* F flag */ + session->pcc_config + .support_pce_triggered_initial_sync)); + } + + if (session->pcc_config.support_include_db_version) { + if (session->pcc_config.lsp_db_version != 0) { + dll_append(tlv_list, + pcep_tlv_create_lsp_db_version( + session->pcc_config.lsp_db_version)); + } + } + + if (session->pcc_config.support_sr_te_pst) { + bool flag_n = false; + bool flag_x = false; + if (session->pcc_config.pcep_msg_versioning + ->draft_ietf_pce_segment_routing_07 + == false) { + flag_n = session->pcc_config.pcc_can_resolve_nai_to_sid; + flag_x = (session->pcc_config.max_sid_depth == 0); + } + + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv = + pcep_tlv_create_sr_pce_capability( + flag_n, flag_x, + session->pcc_config.max_sid_depth); + + double_linked_list *sub_tlv_list = NULL; + if (session->pcc_config.pcep_msg_versioning + ->draft_ietf_pce_segment_routing_07 + == true) { + /* With draft07, send the sr_pce_cap_tlv as a normal TLV + */ + dll_append(tlv_list, sr_pce_cap_tlv); + } else { + /* With draft16, send the sr_pce_cap_tlv as a sub-TLV in + * the path_setup_type_capability TLV */ + sub_tlv_list = dll_initialize(); + dll_append(sub_tlv_list, sr_pce_cap_tlv); + } + + uint8_t *pst = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint8_t)); + *pst = SR_TE_PST; + double_linked_list *pst_list = dll_initialize(); + dll_append(pst_list, pst); + dll_append(tlv_list, pcep_tlv_create_path_setup_type_capability( + pst_list, sub_tlv_list)); + } + + struct pcep_message *open_msg = pcep_msg_create_open_with_tlvs( + session->pcc_config.keep_alive_seconds, + session->pcc_config.dead_timer_seconds, session->session_id, + tlv_list); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic create open message: TLVs [%d] for session [%d]", + __func__, time(NULL), pthread_self(), tlv_list->num_entries, + session->session_id); + + return (open_msg); +} + + +void send_pcep_open(pcep_session *session) +{ + session_send_message(session, create_pcep_open(session)); +} + +/* This is a blocking call, since it is synchronized with destroy_pcep_session() + * and session_logic_loop(). It may be possible that the session has been + * deleted but API users havent been informed yet. + */ +bool session_exists(pcep_session *session) +{ + if (session_logic_handle_ == NULL) { + pcep_log(LOG_DEBUG, + "%s: session_exists session_logic_handle_ is NULL", + __func__); + return false; + } + + pthread_mutex_lock(&(session_logic_handle_->session_list_mutex)); + bool retval = + (ordered_list_find(session_logic_handle_->session_list, session) + != NULL); + pthread_mutex_unlock(&(session_logic_handle_->session_list_mutex)); + + return retval; +} diff --git a/pceplib/pcep_session_logic.h b/pceplib/pcep_session_logic.h new file mode 100644 index 0000000..d7ef7c3 --- /dev/null +++ b/pceplib/pcep_session_logic.h @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPSESSIONLOGIC_H_ +#define INCLUDE_PCEPSESSIONLOGIC_H_ + +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_socket_comm.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_timers.h" +#include "pcep_utils_queue.h" +#include "pcep_utils_memory.h" + +#define PCEP_TCP_PORT 4189 + +typedef struct pcep_configuration_ { + /* These are the configuration values that will + * be sent to the PCE in the PCEP Open message */ + int keep_alive_seconds; + int dead_timer_seconds; + int dead_timer_pce_negotiated_seconds; /* Config data negotiated with + PCE */ + int keep_alive_pce_negotiated_timer_seconds; /* Config data negotiated + with PCE */ + int request_time_seconds; + + /* These are the acceptable ranges of values received by + * the PCE in the initial PCEP Open Message. If a value is + * received outside of these ranges, then the Open message + * will be rejected. */ + int min_keep_alive_seconds; + int max_keep_alive_seconds; + int min_dead_timer_seconds; + int max_dead_timer_seconds; + + /* If more than this many unknown messages/requests are received + * per minute, then the session will be closed. */ + int max_unknown_messages; + int max_unknown_requests; + + /* Maximum amount of time to wait to connect to the + * PCE TCP socket before failing, in milliseconds. */ + uint32_t socket_connect_timeout_millis; + + /* Set if the PCE/PCC will support stateful PCE LSP Updates + * according to RCF8231, section 7.1.1, defaults to true. + * Will cause an additional TLV to be sent from the PCC in + * the PCEP Open */ + bool support_stateful_pce_lsp_update; + + /* RFC 8281: I-bit, the PCC allows instantiation of an LSP by a PCE */ + bool support_pce_lsp_instantiation; + + /* RFC 8232: S-bit, the PCC will include the LSP-DB-VERSION + * TLV in each LSP object */ + bool support_include_db_version; + + /* Only set if support_include_db_version is true and if the LSP-DB + * survived a restart and is available. If this has a value other than + * 0, then a LSP-DB-VERSION TLV will be sent in the OPEN object. This + * value will be copied over to the pcep_session upon init. */ + uint64_t lsp_db_version; + + /* RFC 8232: T-bit, the PCE can trigger resynchronization of + * LSPs at any point in the life of the session */ + bool support_lsp_triggered_resync; + + /* RFC 8232: D-bit, the PCEP speaker allows incremental (delta) + * State Synchronization */ + bool support_lsp_delta_sync; + + /* RFC 8232: F-bit, the PCE SHOULD trigger initial (first) + * State Synchronization */ + bool support_pce_triggered_initial_sync; + + /* draft-ietf-pce-segment-routing-16: Send a SR PCE Capability + * sub-TLV in a Path Setup Type Capability TLV with a PST = 1, + * Path is setup using SR TE. */ + bool support_sr_te_pst; + /* Used in the SR PCE Capability sub-TLV */ + bool pcc_can_resolve_nai_to_sid; + /* Used in the SR TE Capability sub-TLV, 0 means there are no max sid + * limits */ + uint8_t max_sid_depth; + + /* If set to 0, then the default 4189 PCEP port will be used */ + uint16_t dst_pcep_port; + + /* If set to 0, then the default 4189 PCEP port will be used. + * This is according to the RFC5440, Section 5 */ + uint16_t src_pcep_port; + + union src_ip { + struct in_addr src_ipv4; + struct in6_addr src_ipv6; + } src_ip; + bool is_src_ipv6; + + struct pcep_versioning *pcep_msg_versioning; + + char tcp_authentication_str[PCEP_MD5SIG_MAXKEYLEN]; + bool is_tcp_auth_md5; /* true: RFC 2385, false: RFC 5925 */ + +} pcep_configuration; + + +typedef enum pcep_session_state_ { + SESSION_STATE_UNKNOWN = 0, + SESSION_STATE_INITIALIZED = 1, + SESSION_STATE_PCEP_CONNECTING = 2, + SESSION_STATE_PCEP_CONNECTED = 3 + +} pcep_session_state; + + +typedef struct pcep_session_ { + int session_id; + pcep_session_state session_state; + int timer_id_open_keep_wait; + int timer_id_open_keep_alive; + int timer_id_dead_timer; + int timer_id_keep_alive; + bool pce_open_received; + bool pce_open_rejected; + bool pce_open_accepted; + bool pce_open_keep_alive_sent; + bool pcc_open_rejected; + bool pcc_open_accepted; + bool stateful_pce; + time_t time_connected; + uint64_t lsp_db_version; + queue_handle *num_unknown_messages_time_queue; + /* set this flag when finalizing the session */ + bool destroy_session_after_write; + pcep_socket_comm_session *socket_comm_session; + /* Configuration sent from the PCC to the PCE */ + pcep_configuration pcc_config; + /* Configuration received from the PCE, to be used in the PCC */ + pcep_configuration pce_config; + struct counters_group *pcep_session_counters; + +} pcep_session; + + +typedef enum pcep_event_type { + MESSAGE_RECEIVED = 0, + PCE_CLOSED_SOCKET = 1, + PCE_SENT_PCEP_CLOSE = 2, + PCE_DEAD_TIMER_EXPIRED = 3, + PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED = 4, + PCC_CONNECTED_TO_PCE = 100, + PCC_CONNECTION_FAILURE = 101, + PCC_PCEP_SESSION_CLOSED = 102, + PCC_RCVD_INVALID_OPEN = 103, + PCC_SENT_INVALID_OPEN = 104, + PCC_RCVD_MAX_INVALID_MSGS = 105, + PCC_RCVD_MAX_UNKOWN_MSGS = 106 + +} pcep_event_type; + + +typedef struct pcep_event { + enum pcep_event_type event_type; + time_t event_time; + struct pcep_message *message; + pcep_session *session; + +} pcep_event; + +typedef void (*pceplib_pcep_event_callback)(void *cb_data, pcep_event *); +typedef int (*pthread_create_callback)(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); + + +typedef struct pcep_event_queue { + /* The event_queue and event_callback are mutually exclusive. + * If the event_callback is configured, then the event_queue + * will not be used. */ + queue_handle *event_queue; + pthread_mutex_t event_queue_mutex; + pceplib_pcep_event_callback event_callback; + void *event_callback_data; + +} pcep_event_queue; + + +typedef struct pceplib_infra_config { + /* Memory infrastructure */ + void *pceplib_infra_mt; + void *pceplib_messages_mt; + pceplib_malloc_func malloc_func; + pceplib_calloc_func calloc_func; + pceplib_realloc_func realloc_func; + pceplib_strdup_func strdup_func; + pceplib_free_func free_func; + + /* External Timer and Socket infrastructure */ + void *external_infra_data; + ext_timer_create timer_create_func; + ext_timer_cancel timer_cancel_func; + ext_socket_write socket_write_func; + ext_socket_read socket_read_func; + + /* External pcep_event infrastructure */ + pceplib_pcep_event_callback pcep_event_func; + + /* Callback to create pthreads */ + pthread_create_callback pthread_create_func; + +} pceplib_infra_config; + +/* + * Counters Sub-groups definitions + */ +typedef enum pcep_session_counters_subgroup_ids { + COUNTER_SUBGROUP_ID_RX_MSG = 0, + COUNTER_SUBGROUP_ID_TX_MSG = 1, + COUNTER_SUBGROUP_ID_RX_OBJ = 2, + COUNTER_SUBGROUP_ID_TX_OBJ = 3, + COUNTER_SUBGROUP_ID_RX_SUBOBJ = 4, + COUNTER_SUBGROUP_ID_TX_SUBOBJ = 5, + COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ = 6, + COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ = 7, + COUNTER_SUBGROUP_ID_RX_TLV = 8, + COUNTER_SUBGROUP_ID_TX_TLV = 9, + COUNTER_SUBGROUP_ID_EVENT = 10 + +} pcep_session_counters_subgroup_ids; + +bool run_session_logic(void); +bool run_session_logic_with_infra(pceplib_infra_config *infra_config); + +bool run_session_logic_wait_for_completion(void); + +bool stop_session_logic(void); + +/* Uses the standard PCEP TCP dest port = 4189 and an ephemeral src port. + * To use a specific dest or src port, set them other than 0 in the + * pcep_configuration. */ +pcep_session *create_pcep_session(pcep_configuration *config, + struct in_addr *pce_ip); +pcep_session *create_pcep_session_ipv6(pcep_configuration *config, + struct in6_addr *pce_ip); + +/* Send a PCEP close for this pcep_session */ +void close_pcep_session(pcep_session *session); +void close_pcep_session_with_reason(pcep_session *session, + enum pcep_close_reason); + +/* Destroy the PCEP session, a PCEP close should have + * already been sent with close_pcep_session() */ +void destroy_pcep_session(pcep_session *session); + +void pcep_session_cancel_timers(pcep_session *session); + +/* Increments transmitted message counters, additionally counters for the + * objects, sub-objects, and TLVs in the message will be incremented. Received + * counters are incremented internally. */ +void increment_message_tx_counters(pcep_session *session, + struct pcep_message *message); + +bool session_exists(pcep_session *session); + +#endif /* INCLUDE_PCEPSESSIONLOGIC_H_ */ diff --git a/pceplib/pcep_session_logic_counters.c b/pceplib/pcep_session_logic_counters.c new file mode 100644 index 0000000..b12b8ab --- /dev/null +++ b/pceplib/pcep_session_logic_counters.c @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * PCEP session logic counters configuration. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" + +void increment_message_counters(pcep_session *session, + struct pcep_message *message, bool is_rx); + +void create_session_counters(pcep_session *session) +{ + /* + * Message RX and TX counters + */ + struct counters_subgroup *rx_msg_subgroup = create_counters_subgroup( + "RX Message counters", COUNTER_SUBGROUP_ID_RX_MSG, + PCEP_TYPE_MAX + 1); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_OPEN, "Message Open", + "messageOpen"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_KEEPALIVE, + "Message KeepAlive", "messageKeepalive"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCREQ, + "Message PcReq", "messagePcReq"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCREP, + "Message PcRep", "messagePcRep"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_PCNOTF, + "Message Notify", "messageNotify"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_ERROR, + "Message Error", "messageError"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_CLOSE, + "Message Close", "messageClose"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_REPORT, + "Message Report", "messageReport"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_UPDATE, + "Message Update", "messageUpdate"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_INITIATE, + "Message Initiate", "messageInitiate"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_START_TLS, + "Message StartTls", "messageStartTls"); + create_subgroup_counter(rx_msg_subgroup, PCEP_TYPE_MAX, + "Message Erroneous", "messageErroneous"); + + struct counters_subgroup *tx_msg_subgroup = + clone_counters_subgroup(rx_msg_subgroup, "TX Message counters", + COUNTER_SUBGROUP_ID_TX_MSG); + + /* + * Object RX and TX counters + */ + + /* For the Endpoints, the ID will be either 64 or 65, so setting + * num_counters to 100 */ + struct counters_subgroup *rx_obj_subgroup = create_counters_subgroup( + "RX Object counters", COUNTER_SUBGROUP_ID_RX_OBJ, 100); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_OPEN, + "Object Open", "objectOpen"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_RP, "Object RP", + "objectRP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_NOPATH, + "Object Nopath", "objectNopath"); + create_subgroup_counter(rx_obj_subgroup, + ((PCEP_OBJ_CLASS_ENDPOINTS << 4) | + PCEP_OBJ_TYPE_ENDPOINT_IPV4), + "Object Endpoint IPv4", "objectEndpointIPv4"); + create_subgroup_counter(rx_obj_subgroup, + ((PCEP_OBJ_CLASS_ENDPOINTS << 4) | + PCEP_OBJ_TYPE_ENDPOINT_IPV6), + "Object Endpoint IPv6", "objectEndpointIPv6"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_BANDWIDTH, + "Object Bandwidth", "objectBandwidth"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_METRIC, + "Object Metric", "objectMetric"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ERO, + "Object ERO", "objectERO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_RRO, + "Object RRO", "objectRRO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_LSPA, + "Object LSPA", "objectLSPA"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_IRO, + "Object IRO", "objectIRO"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SVEC, + "Object SVEC", "objectSVEC"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_NOTF, + "Object Notify", "objectNotify"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ERROR, + "Object Error", "objectError"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_CLOSE, + "Object Close", "objectClose"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_LSP, + "Object LSP", "objectLSP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SRP, + "Object SRP", "objectSRP"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_VENDOR_INFO, + "Object Vendor Info", "objectVendorInfo"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_INTER_LAYER, + "Object Inter-Layer", "objectInterLayer"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SWITCH_LAYER, + "Object Switch-Layer", "objectSwitchLayer"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_REQ_ADAP_CAP, + "Object Requested Adap-Cap", + "objectRequestedAdapCap"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_SERVER_IND, + "Object Server-Indication", + "objectServerIndication"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_ASSOCIATION, + "Object Association", "objectAssociation"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_MAX, + "Object Unknown", "objectUnknown"); + create_subgroup_counter(rx_obj_subgroup, PCEP_OBJ_CLASS_MAX + 1, + "Object Erroneous", "objectErroneous"); + + struct counters_subgroup *tx_obj_subgroup = + clone_counters_subgroup(rx_obj_subgroup, "TX Object counters", + COUNTER_SUBGROUP_ID_TX_OBJ); + + /* + * Sub-Object RX and TX counters + */ + struct counters_subgroup *rx_subobj_subgroup = create_counters_subgroup( + "RX RO Sub-Object counters", COUNTER_SUBGROUP_ID_RX_SUBOBJ, + RO_SUBOBJ_UNKNOWN + 2); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_IPV4, + "RO Sub-Object IPv4", "ROSubObjectIPv4"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_IPV6, + "RO Sub-Object IPv6", "ROSubObjectIPv6"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_LABEL, + "RO Sub-Object Label", "ROSubObjectLabel"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_UNNUM, + "RO Sub-Object Unnum", "ROSubObjectUnnum"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_ASN, + "RO Sub-Object ASN", "ROSubObjectASN"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_TYPE_SR, + "RO Sub-Object SR", "ROSubObjectSR"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_UNKNOWN, + "RO Sub-Object Unknown", "ROSubObjectUnknown"); + create_subgroup_counter(rx_subobj_subgroup, RO_SUBOBJ_UNKNOWN + 1, + "RO Sub-Object Erroneous", + "ROSubObjectErroneous"); + + struct counters_subgroup *tx_subobj_subgroup = clone_counters_subgroup( + rx_subobj_subgroup, "TX RO Sub-Object counters", + COUNTER_SUBGROUP_ID_TX_SUBOBJ); + + /* + * RO SR Sub-Object RX and TX counters + */ + struct counters_subgroup *rx_subobj_sr_nai_subgroup = + create_counters_subgroup("RX RO SR NAI Sub-Object counters", + COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ, + PCEP_SR_SUBOBJ_NAI_UNKNOWN + 1); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_ABSENT, + "RO Sub-Object SR NAI absent", + "ROSubObjectSRNAIAbsent"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, + "RO Sub-Object SR NAI IPv4 Node", + "ROSubObjectSRNAIIPv4Node"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, + "RO Sub-Object SR NAI IPv6 Node", + "ROSubObjectSRNAIIPv6Node"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, + "RO Sub-Object SR NAI IPv4 Adj", + "ROSubObjectSRNAIIPv4Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, + "RO Sub-Object SR NAI IPv6 Adj", + "ROSubObjectSRNAIIPv6Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, + "RO Sub-Object SR NAI Unnumbered IPv4 Adj", + "ROSubObjectSRNAIUnnumberedIPv4Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, + "RO Sub-Object SR NAI Link Local IPv6 Adj", + "ROSubObjectSRNAILinkLocalIPv6Adj"); + create_subgroup_counter(rx_subobj_sr_nai_subgroup, + PCEP_SR_SUBOBJ_NAI_UNKNOWN, + "RO Sub-Object SR NAI Unknown", + "ROSubObjectSRNAIUnknown"); + + struct counters_subgroup *tx_subobj_sr_nai_subgroup = + clone_counters_subgroup(rx_subobj_sr_nai_subgroup, + "TX RO SR NAI Sub-Object counters", + COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ); + + /* + * TLV RX and TX counters + */ + struct counters_subgroup *rx_tlv_subgroup = create_counters_subgroup( + "RX TLV counters", COUNTER_SUBGROUP_ID_RX_TLV, + PCEP_OBJ_TLV_TYPE_UNKNOWN + 1); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR, + "TLV No Path Vector", "TLVNoPathVector"); + create_subgroup_counter(rx_tlv_subgroup, PCEP_OBJ_TLV_TYPE_VENDOR_INFO, + "TLV Vendor Info", "TLVVendorInfo"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY, + "TLV Stateful PCE Capability", + "TLVStatefulPCCapability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME, + "TLV Symbolic Path Name", "TLVSymbolicPathName"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS, + "TLV IPv4 LSP Identifier", + "TLVIPv4LSPIdentifier"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS, + "TLV IPv6 LSP Identifier", + "TLVIPv6LSPIdentifier"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE, + "TLV LSP Error Code", "TLVLSPErrorCode"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC, + "TLV RSVP Error Spec", "TLVRSVPErrorSpec"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION, + "TLV LSP DB Version", "TLVLSPDBVersion"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID, + "TLV Speaker Entity ID", "TLVSpeakerEntityId"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY, + "TLV SR PCE Capability", "TLVSRPCECapability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE, + "TLV Path Setup Type", "TLVPathSetupType"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY, + "TLV Path Setup Type Capability", + "TLVPathSetupTypeCapability"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID, + "TLV SR Policy PolId", "TLVSRPolicyPolId"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME, + "TLV SR Policy PolName", "TLVSRPolicyPolName"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID, + "TLV SR Policy CpathId", "TLVSRPolicyCpathId"); + create_subgroup_counter(rx_tlv_subgroup, + PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE, + "TLV SR Policy CpathRef", "TLVSRPolicyCpathRef"); + create_subgroup_counter(rx_tlv_subgroup, PCEP_OBJ_TLV_TYPE_UNKNOWN, + "TLV Unknown", "TLVUnknown"); + + struct counters_subgroup *tx_tlv_subgroup = clone_counters_subgroup( + rx_tlv_subgroup, "TX TLV counters", COUNTER_SUBGROUP_ID_TX_TLV); + + /* + * PCEP Event counters + */ + struct counters_subgroup *events_subgroup = create_counters_subgroup( + "Events counters", COUNTER_SUBGROUP_ID_EVENT, MAX_COUNTERS); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCC_CONNECT, + "PCC connect", "PCCConnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT, + "PCE connect", "PCEConnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCC_DISCONNECT, + "PCC disconnect", "PCCDisconnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT, + "PCE disconnect", "PCEDisconnect"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE, + "Timer KeepAlive expired", + "timerKeepAliveExpired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER, + "Timer DeadTimer expired", + "timerDeadTimerExpired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT, + "Timer OpenKeepWait expired", + "timerOpenKeepWaitExpired"); + create_subgroup_counter(events_subgroup, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE, + "Timer OpenKeepAlive expired", + "timerOpenKeepAliveExpired"); + + /* + * Create the parent counters group + */ + time_t now = time(NULL); + char counters_name[MAX_COUNTER_STR_LENGTH] = {0}; + char ip_str[40] = {0}; + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + ip_str, 40); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + ip_str, 40); + } + snprintf(counters_name, MAX_COUNTER_STR_LENGTH, + "PCEP Session [%d], connected to [%s] for [%u seconds]", + session->session_id, ip_str, + (uint32_t)(now - session->time_connected)); + /* The (time(NULL) - session->time_connected) will probably be 0, + * so the group name will be updated when the counters are dumped */ + session->pcep_session_counters = + create_counters_group(counters_name, MAX_COUNTER_GROUPS); + + /* + * Add all the subgroups to the parent counters group + */ + add_counters_subgroup(session->pcep_session_counters, rx_msg_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_msg_subgroup); + add_counters_subgroup(session->pcep_session_counters, rx_obj_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_obj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + rx_subobj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + tx_subobj_subgroup); + add_counters_subgroup(session->pcep_session_counters, + rx_subobj_sr_nai_subgroup); + add_counters_subgroup(session->pcep_session_counters, + tx_subobj_sr_nai_subgroup); + add_counters_subgroup(session->pcep_session_counters, rx_tlv_subgroup); + add_counters_subgroup(session->pcep_session_counters, tx_tlv_subgroup); + add_counters_subgroup(session->pcep_session_counters, events_subgroup); +} + +/* Internal util function used by increment_message_rx_counters or + * increment_message_tx_counters */ +void increment_message_counters(pcep_session *session, + struct pcep_message *message, bool is_rx) +{ + uint16_t counter_subgroup_id_msg = (is_rx ? COUNTER_SUBGROUP_ID_RX_MSG + : COUNTER_SUBGROUP_ID_TX_MSG); + uint16_t counter_subgroup_id_obj = (is_rx ? COUNTER_SUBGROUP_ID_RX_OBJ + : COUNTER_SUBGROUP_ID_TX_OBJ); + uint16_t counter_subgroup_id_subobj = + (is_rx ? COUNTER_SUBGROUP_ID_RX_SUBOBJ + : COUNTER_SUBGROUP_ID_TX_SUBOBJ); + uint16_t counter_subgroup_id_ro_sr_subobj = + (is_rx ? COUNTER_SUBGROUP_ID_RX_RO_SR_SUBOBJ + : COUNTER_SUBGROUP_ID_TX_RO_SR_SUBOBJ); + uint16_t counter_subgroup_id_tlv = (is_rx ? COUNTER_SUBGROUP_ID_RX_TLV + : COUNTER_SUBGROUP_ID_TX_TLV); + + increment_counter(session->pcep_session_counters, + counter_subgroup_id_msg, message->msg_header->type); + + /* Iterate the objects */ + double_linked_list_node *obj_node = + (message->obj_list == NULL ? NULL : message->obj_list->head); + for (; obj_node != NULL; obj_node = obj_node->next_node) { + struct pcep_object_header *obj = + (struct pcep_object_header *)obj_node->data; + + /* Handle class: PCEP_OBJ_CLASS_ENDPOINTS, + * type: PCEP_OBJ_TYPE_ENDPOINT_IPV4 or + * PCEP_OBJ_TYPE_ENDPOINT_IPV6 */ + uint16_t obj_counter_id = + (obj->object_class == PCEP_OBJ_CLASS_ENDPOINTS + ? (obj->object_class << 4) | obj->object_type + : obj->object_class); + + increment_counter(session->pcep_session_counters, + counter_subgroup_id_obj, obj_counter_id); + + /* Iterate the RO Sub-objects */ + if (obj->object_class == PCEP_OBJ_CLASS_ERO + || obj->object_class == PCEP_OBJ_CLASS_IRO + || obj->object_class == PCEP_OBJ_CLASS_RRO) { + struct pcep_object_ro *ro_obj = + (struct pcep_object_ro *)obj; + + double_linked_list_node *ro_subobj_node = + (ro_obj->sub_objects == NULL + ? NULL + : ro_obj->sub_objects->head); + for (; ro_subobj_node != NULL; + ro_subobj_node = ro_subobj_node->next_node) { + struct pcep_object_ro_subobj *ro_subobj = + (struct pcep_object_ro_subobj *) + ro_subobj_node->data; + increment_counter( + session->pcep_session_counters, + counter_subgroup_id_subobj, + ro_subobj->ro_subobj_type); + + /* Handle the ro subobj type RO_SUBOBJ_TYPE_SR + * different NAI types */ + if (ro_subobj->ro_subobj_type + == RO_SUBOBJ_TYPE_SR) { + struct pcep_ro_subobj_sr *ro_sr_subobj = + (struct pcep_ro_subobj_sr *) + ro_subobj; + increment_counter( + session->pcep_session_counters, + counter_subgroup_id_ro_sr_subobj, + ro_sr_subobj->nai_type); + } + } + } + + /* Iterate the TLVs */ + double_linked_list_node *tlv_node = + (obj->tlv_list == NULL ? NULL : obj->tlv_list->head); + for (; tlv_node != NULL; tlv_node = tlv_node->next_node) { + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)tlv_node->data; + increment_counter(session->pcep_session_counters, + counter_subgroup_id_tlv, tlv->type); + } + } +} + +void increment_message_rx_counters(pcep_session *session, + struct pcep_message *message) +{ + increment_message_counters(session, message, true); +} + +void increment_message_tx_counters(pcep_session *session, + struct pcep_message *message) +{ + increment_message_counters(session, message, false); +} + +void increment_event_counters( + pcep_session *session, + pcep_session_counters_event_counter_ids counter_id) +{ + increment_counter(session->pcep_session_counters, + COUNTER_SUBGROUP_ID_EVENT, counter_id); +} diff --git a/pceplib/pcep_session_logic_internals.h b/pceplib/pcep_session_logic_internals.h new file mode 100644 index 0000000..9c29c17 --- /dev/null +++ b/pceplib/pcep_session_logic_internals.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Internal Session Logic declarations, not intended to be in the public API. + */ + +#ifndef SRC_PCEPSESSIONLOGICINTERNALS_H_ +#define SRC_PCEPSESSIONLOGICINTERNALS_H_ + + +#include +#include + +#include "pcep_msg_tools.h" + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_queue.h" + + +typedef struct pcep_session_logic_handle_ { + pthread_t session_logic_thread; + pthread_mutex_t session_logic_mutex; + pthread_cond_t session_logic_cond_var; + bool session_logic_condition; + bool active; + + ordered_list_handle *session_list; + pthread_mutex_t session_list_mutex; + /* Internal timers and socket events */ + queue_handle *session_event_queue; + +} pcep_session_logic_handle; + + +/* Used internally for Session events: message received, timer expired, + * or socket closed */ +typedef struct pcep_session_event_ { + pcep_session *session; + int expired_timer_id; + double_linked_list *received_msg_list; + bool socket_closed; + +} pcep_session_event; + +/* Event Counters counter-id definitions */ +typedef enum pcep_session_counters_event_counter_ids { + PCEP_EVENT_COUNTER_ID_PCC_CONNECT = 0, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT = 1, + PCEP_EVENT_COUNTER_ID_PCC_DISCONNECT = 2, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT = 3, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE = 4, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER = 5, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT = 6, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE = 7 + +} pcep_session_counters_event_counter_ids; + +/* functions implemented in pcep_session_logic_loop.c */ +void *session_logic_loop(void *data); +int session_logic_msg_ready_handler(void *data, int socket_fd); +void session_logic_message_sent_handler(void *data, int socket_fd); +void session_logic_conn_except_notifier(void *data, int socket_fd); +void session_logic_timer_expire_handler(void *data, int timer_id); + +void handle_timer_event(pcep_session_event *event); +void handle_socket_comm_event(pcep_session_event *event); +void session_send_message(pcep_session *session, struct pcep_message *message); + +/* defined in pcep_session_logic_states.c */ +void send_pcep_error(pcep_session *session, enum pcep_error_type error_type, + enum pcep_error_value error_value); +void enqueue_event(pcep_session *session, pcep_event_type event_type, + struct pcep_message *message); +void increment_unknown_message(pcep_session *session); + +/* defined in pcep_session_logic_counters.c */ +void create_session_counters(pcep_session *session); +void increment_event_counters( + pcep_session *session, + pcep_session_counters_event_counter_ids counter_id); +void increment_message_rx_counters(pcep_session *session, + struct pcep_message *message); + +/* defined in pcep_session_logic.c, also used in pcep_session_logic_states.c */ +struct pcep_message *create_pcep_open(pcep_session *session); + +#endif /* SRC_PCEPSESSIONLOGICINTERNALS_H_ */ diff --git a/pceplib/pcep_session_logic_loop.c b/pceplib/pcep_session_logic_loop.c new file mode 100644 index 0000000..6717f46 --- /dev/null +++ b/pceplib/pcep_session_logic_loop.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* global var needed for callback handlers */ +extern pcep_session_logic_handle *session_logic_handle_; + +/* internal util function to create session_event's */ +static pcep_session_event *create_session_event(pcep_session *session) +{ + pcep_session_event *event = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_session_event)); + event->session = session; + event->expired_timer_id = TIMER_ID_NOT_SET; + event->received_msg_list = NULL; + event->socket_closed = false; + + return event; +} + + +/* A function pointer to this function is passed to pcep_socket_comm + * for each pcep_session creation, so it will be called whenever + * messages are ready to be read. This function will be called + * by the socket_comm thread. + * This function will decode the read PCEP message and give it + * to the session_logic_loop so it can be handled by the session_logic + * state machine. */ +int session_logic_msg_ready_handler(void *data, int socket_fd) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle msg_ready with NULL data", + __func__); + return -1; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a message ready notification while the session logic is not active", + __func__); + return -1; + } + + pcep_session *session = (pcep_session *)data; + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + + /* This event will ultimately be handled by handle_socket_comm_event() + * in pcep_session_logic_states.c */ + pcep_session_event *rcvd_msg_event = create_session_event(session); + + int msg_length = 0; + double_linked_list *msg_list = pcep_msg_read(socket_fd); + + if (msg_list == NULL) { + /* The socket was closed, or there was a socket read error */ + pcep_log(LOG_INFO, + "%s: PCEP connection closed for session [%d]", + __func__, session->session_id); + dll_destroy(msg_list); + rcvd_msg_event->socket_closed = true; + socket_comm_session_teardown(session->socket_comm_session); + pcep_session_cancel_timers(session); + session->socket_comm_session = NULL; + session->session_state = SESSION_STATE_INITIALIZED; + enqueue_event(session, PCE_CLOSED_SOCKET, NULL); + } else if (msg_list->num_entries == 0) { + /* Invalid message received */ + increment_unknown_message(session); + dll_destroy_with_data(msg_list); + } else { + /* Just logging the first of potentially several messages + * received */ + struct pcep_message *msg = + ((struct pcep_message *)msg_list->head->data); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] session_logic_msg_ready_handler received message of type [%d] len [%d] on session [%d]", + __func__, time(NULL), pthread_self(), + msg->msg_header->type, msg->encoded_message_length, + session->session_id); + + rcvd_msg_event->received_msg_list = msg_list; + msg_length = msg->encoded_message_length; + } + + queue_enqueue(session_logic_handle_->session_event_queue, + rcvd_msg_event); + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + + return msg_length; +} + + +/* A function pointer to this function was passed to pcep_socket_comm, + * so it will be called when a message is sent. This is useful since + * message sending is asynchronous, and there are times that actions + * need to be performed only after a message has been sent. */ +void session_logic_message_sent_handler(void *data, int socket_fd) +{ + (void)socket_fd; + + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle msg_sent with NULL data", __func__); + return; + } + + pcep_session *session = (pcep_session *)data; + if (session->destroy_session_after_write == true) { + /* Do not call destroy until all of the queued messages are + * written */ + if (session->socket_comm_session != NULL + && session->socket_comm_session->message_queue->num_entries + == 0) { + destroy_pcep_session(session); + } + } else { + /* Reset the keep alive timer for every message sent on + * the session, only if the session is not destroyed */ + if (session->timer_id_keep_alive == TIMER_ID_NOT_SET) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic set keep alive timer [%d secs] for session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session->session_id); + session->timer_id_keep_alive = create_timer( + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session); + } else { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic reset keep alive timer [%d secs] for session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds, + session->session_id); + reset_timer(session->timer_id_keep_alive); + } + } +} + + +/* A function pointer to this function was passed to pcep_socket_comm, + * so it will be called whenever the socket is closed. this function + * will be called by the socket_comm thread. */ +void session_logic_conn_except_notifier(void *data, int socket_fd) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot handle conn_except with NULL data", + __func__); + return; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a connection exception notification while the session logic is not active", + __func__); + return; + } + + pcep_session *session = (pcep_session *)data; + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic session_logic_conn_except_notifier socket closed [%d], session [%d]", + __func__, time(NULL), pthread_self(), socket_fd, + session->session_id); + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + pcep_session_event *socket_event = create_session_event(session); + socket_event->socket_closed = true; + queue_enqueue(session_logic_handle_->session_event_queue, socket_event); + session_logic_handle_->session_logic_condition = true; + + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); +} + + +/* + * this method is the timer expire handler, and will only + * pass the event to the session_logic loop and notify it + * that there is a timer available. this function will be + * called by the timers thread. + */ +void session_logic_timer_expire_handler(void *data, int timer_id) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot handle timer with NULL data", + __func__); + return; + } + + if (session_logic_handle_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Received a timer expiration while the session logic is not active", + __func__); + return; + } + + pcep_log(LOG_INFO, "%s: [%ld-%ld] timer expired handler timer_id [%d]", + __func__, time(NULL), pthread_self(), timer_id); + pcep_session_event *expired_timer_event = + create_session_event((pcep_session *)data); + expired_timer_event->expired_timer_id = timer_id; + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + queue_enqueue(session_logic_handle_->session_event_queue, + expired_timer_event); + + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); +} + + +/* + * session_logic event loop + * this function is called upon thread creation from pcep_session_logic.c + */ +void *session_logic_loop(void *data) +{ + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot start session_logic_loop with NULL data", + __func__); + + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting session_logic_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_session_logic_handle *session_logic_handle = + (pcep_session_logic_handle *)data; + + while (session_logic_handle->active) { + /* Mutex locking for session_logic_loop condition variable */ + pthread_mutex_lock( + &(session_logic_handle->session_logic_mutex)); + + /* this internal loop helps avoid spurious interrupts */ + while (!session_logic_handle->session_logic_condition) { + pthread_cond_wait( + &(session_logic_handle->session_logic_cond_var), + &(session_logic_handle->session_logic_mutex)); + } + + pcep_session_event *event = queue_dequeue( + session_logic_handle->session_event_queue); + while (event != NULL) { + if (event->session == NULL) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Invalid session_logic_loop event [%s] with NULL session", + __func__, time(NULL), pthread_self(), + (event->expired_timer_id + != TIMER_ID_NOT_SET) + ? "timer" + : "message"); + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle + ->session_event_queue); + continue; + } + + /* Check if the session still exists, and synchronize + * possible session destroy */ + pcep_log( + LOG_DEBUG, + "%s: session_logic_loop checking session_list sessionPtr %p", + __func__, event->session); + pthread_mutex_lock( + &(session_logic_handle->session_list_mutex)); + if (ordered_list_find( + session_logic_handle->session_list, + event->session) + == NULL) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] In-flight event [%s] for destroyed session being discarded", + __func__, time(NULL), pthread_self(), + (event->expired_timer_id + != TIMER_ID_NOT_SET) + ? "timer" + : "message"); + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle + ->session_event_queue); + pthread_mutex_unlock( + &(session_logic_handle + ->session_list_mutex)); + continue; + } + + if (event->expired_timer_id != TIMER_ID_NOT_SET) { + handle_timer_event(event); + } + + if (event->received_msg_list != NULL) { + handle_socket_comm_event(event); + } + + pceplib_free(PCEPLIB_INFRA, event); + event = queue_dequeue( + session_logic_handle->session_event_queue); + + pthread_mutex_unlock( + &(session_logic_handle->session_list_mutex)); + } + + session_logic_handle->session_logic_condition = false; + pthread_mutex_unlock( + &(session_logic_handle->session_logic_mutex)); + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Finished session_logic_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} diff --git a/pceplib/pcep_session_logic_states.c b/pceplib/pcep_session_logic_states.c new file mode 100644 index 0000000..2691452 --- /dev/null +++ b/pceplib/pcep_session_logic_states.c @@ -0,0 +1,1143 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +#define TIMER_OPEN_KEEP_ALIVE_SECONDS 1 + +/* Session Logic Handle managed in pcep_session_logic.c */ +extern pcep_event_queue *session_logic_event_queue_; +void send_keep_alive(pcep_session *session); +void send_pcep_error_with_object(pcep_session *session, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct pcep_object_header *object); +void reset_dead_timer(pcep_session *session); +bool verify_pcep_open_object(pcep_session *session, + struct pcep_object_open *open_object); +void send_reconciled_pcep_open(pcep_session *session, + struct pcep_message *error_msg); +bool handle_pcep_update(pcep_session *session, struct pcep_message *upd_msg); +bool handle_pcep_initiate(pcep_session *session, struct pcep_message *init_msg); +bool check_and_send_open_keep_alive(pcep_session *session); +void log_pcc_pce_connection(pcep_session *session); +bool handle_pcep_open(pcep_session *session, struct pcep_message *open_msg); + +/* + * util functions called by the state handling below + */ + +void send_keep_alive(pcep_session *session) +{ + struct pcep_message *keep_alive_msg = pcep_msg_create_keepalive(); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send keep_alive message for session [%d]", + __func__, time(NULL), pthread_self(), session->session_id); + + session_send_message(session, keep_alive_msg); + + /* The keep alive timer will be (re)set once the message + * is sent in session_logic_message_sent_handler() */ +} + + +/* Send an error message with the corrected or offending object */ +void send_pcep_error_with_object(pcep_session *session, + enum pcep_error_type error_type, + enum pcep_error_value error_value, + struct pcep_object_header *object) +{ + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, object); + struct pcep_message *error_msg = pcep_msg_create_error_with_objects( + error_type, error_value, obj_list); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send error message with object [%d][%d] for session [%d]", + __func__, time(NULL), pthread_self(), error_type, error_value, + session->session_id); + + session_send_message(session, error_msg); +} + + +void send_pcep_error(pcep_session *session, enum pcep_error_type error_type, + enum pcep_error_value error_value) +{ + struct pcep_message *error_msg = + pcep_msg_create_error(error_type, error_value); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic send error message [%d][%d] for session [%d]", + __func__, time(NULL), pthread_self(), error_type, error_value, + session->session_id); + + session_send_message(session, error_msg); +} + + +void reset_dead_timer(pcep_session *session) +{ + /* Default to configured dead_timer if its not set yet or set to 0 by + * the PCE */ + int dead_timer_seconds = + (session->pcc_config.dead_timer_pce_negotiated_seconds == 0) + ? session->pcc_config.dead_timer_seconds + : session->pcc_config.dead_timer_pce_negotiated_seconds; + + if (session->timer_id_dead_timer == TIMER_ID_NOT_SET) { + session->timer_id_dead_timer = + create_timer(dead_timer_seconds, session); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic set dead timer [%d secs] id [%d] for session [%d]", + __func__, time(NULL), pthread_self(), + dead_timer_seconds, session->timer_id_dead_timer, + session->session_id); + } else { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic reset dead timer [%d secs] id [%d] for session [%d]", + __func__, time(NULL), pthread_self(), + dead_timer_seconds, session->timer_id_dead_timer, + session->session_id); + reset_timer(session->timer_id_dead_timer); + } +} + + +void enqueue_event(pcep_session *session, pcep_event_type event_type, + struct pcep_message *message) +{ + if (event_type == MESSAGE_RECEIVED && message == NULL) { + pcep_log( + LOG_WARNING, + "%s: enqueue_event cannot enqueue a NULL message session [%d]", + __func__, session->session_id); + return; + } + + pcep_event *event = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event)); + memset(event, 0, sizeof(pcep_event)); + + event->session = session; + event->event_type = event_type; + event->event_time = time(NULL); + event->message = message; + + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + if (session_logic_event_queue_->event_callback != NULL) { + session_logic_event_queue_->event_callback( + session_logic_event_queue_->event_callback_data, event); + } else { + queue_enqueue(session_logic_event_queue_->event_queue, event); + } + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); +} + +/* Verify the received PCEP Open object parameters are acceptable. If not, + * update the unacceptable value(s) with an acceptable value so it can be sent + * back to the sender. */ +bool verify_pcep_open_object(pcep_session *session, + struct pcep_object_open *open_object) +{ + int retval = true; + + if (open_object->open_keepalive + < session->pcc_config.min_keep_alive_seconds) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open Keep Alive value [%d] min [%d]", + __func__, open_object->open_keepalive, + session->pcc_config.min_keep_alive_seconds); + open_object->open_keepalive = + session->pcc_config.min_keep_alive_seconds; + retval = false; + } else if (open_object->open_keepalive + > session->pcc_config.max_keep_alive_seconds) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open Keep Alive value [%d] max [%d]", + __func__, open_object->open_keepalive, + session->pcc_config.max_keep_alive_seconds); + open_object->open_keepalive = + session->pcc_config.max_keep_alive_seconds; + retval = false; + } + + if (open_object->open_deadtimer + < session->pcc_config.min_dead_timer_seconds) { + pcep_log(LOG_INFO, + "%s: Rejecting unsupported Open Dead Timer value [%d]", + __func__, open_object->open_deadtimer); + open_object->open_deadtimer = + session->pcc_config.min_dead_timer_seconds; + retval = false; + } else if (open_object->open_deadtimer + > session->pcc_config.max_dead_timer_seconds) { + pcep_log(LOG_INFO, + "%s: Rejecting unsupported Open Dead Timer value [%d]", + __func__, open_object->open_deadtimer); + open_object->open_deadtimer = + session->pcc_config.max_dead_timer_seconds; + retval = false; + } + + /* Check for Open Object TLVs */ + if (pcep_object_has_tlvs((struct pcep_object_header *)open_object) + == false) { + /* There are no TLVs, all done */ + return retval; + } + + double_linked_list_node *tlv_node = open_object->header.tlv_list->head; + while (tlv_node != NULL) { + struct pcep_object_tlv_header *tlv = tlv_node->data; + tlv_node = tlv_node->next_node; + + /* Supported Open Object TLVs */ + switch (tlv->type) { + case PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID: + case PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY: + case PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY: + break; + + case PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR: + case PCEP_OBJ_TLV_TYPE_OBJECTIVE_FUNCTION_LIST: + case PCEP_OBJ_TLV_TYPE_VENDOR_INFO: + case PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME: + case PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS: + case PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE: + case PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC: + case PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID: + case PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE: + case PCEP_OBJ_TLV_TYPE_UNKNOWN: + case PCEP_OBJ_TYPE_CISCO_BSID: + case PCEP_OBJ_TLV_TYPE_ARBITRARY: + /* TODO how to handle unrecognized TLV ?? */ + pcep_log( + LOG_INFO, + "%s: Unhandled OPEN Object TLV type: %d, length %d", + __func__, tlv->type, tlv->encoded_tlv_length); + break; + } + + /* Verify the STATEFUL-PCE-CAPABILITY TLV */ + if (tlv->type == PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY) { + struct pcep_object_tlv_stateful_pce_capability + *pce_cap_tlv = + (struct + pcep_object_tlv_stateful_pce_capability + *)tlv; + + /* If the U flag is set, then the PCE is + * capable of updating LSP parameters */ + if (pce_cap_tlv->flag_u_lsp_update_capability) { + if (session->pce_config + .support_stateful_pce_lsp_update + == false) { + /* Turn off the U bit, as it is not + * supported */ + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open STATEFUL-PCE-CAPABILITY TLV U flag", + __func__); + pce_cap_tlv + ->flag_u_lsp_update_capability = + false; + retval = false; + } else { + session->stateful_pce = true; + pcep_log( + LOG_INFO, + "%s: Setting PCEP session [%d] STATEFUL to support LSP updates", + __func__, session->session_id); + } + } + /* TODO the rest of the flags are not implemented yet */ + else if (pce_cap_tlv->flag_s_include_db_version) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV S Include DB Version flag", + __func__); + } else if ( + pce_cap_tlv + ->flag_i_lsp_instantiation_capability) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV I LSP Instantiation Capability flag", + __func__); + } else if (pce_cap_tlv->flag_t_triggered_resync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV T Triggered Resync flag", + __func__); + } else if (pce_cap_tlv->flag_d_delta_lsp_sync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV D Delta LSP Sync flag", + __func__); + } else if (pce_cap_tlv->flag_f_triggered_initial_sync) { + pcep_log( + LOG_INFO, + "%s: Ignoring Open STATEFUL-PCE-CAPABILITY TLV F Triggered Initial Sync flag", + __func__); + } + } else if (tlv->type == PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION) { + if (session->pce_config.support_include_db_version + == false) { + pcep_log( + LOG_INFO, + "%s: Rejecting unsupported Open LSP DB VERSION TLV", + __func__); + /* Remove this TLV from the list */ + dll_delete_node(open_object->header.tlv_list, + tlv_node); + retval = false; + } + } + } + + return retval; +} + + +bool handle_pcep_open(pcep_session *session, struct pcep_message *open_msg) +{ + /* Open Message validation and errors according to: + * https://tools.ietf.org/html/rfc5440#section-7.15 */ + + if (session->session_state != SESSION_STATE_PCEP_CONNECTING + && session->session_state != SESSION_STATE_INITIALIZED) { + pcep_log( + LOG_INFO, + "%s: Received unexpected OPEN, current session state [%d, replying with error]", + __func__, session->session_state); + send_pcep_error(session, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + if (session->pce_open_received == true + && session->pce_open_rejected == false) { + pcep_log(LOG_INFO, + "%s: Received duplicate OPEN, replying with error", + __func__); + send_pcep_error(session, + PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + struct pcep_object_open *open_object = + (struct pcep_object_open *)pcep_obj_get(open_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + if (open_object == NULL) { + pcep_log( + LOG_INFO, + "%s: Received OPEN message with no OPEN object, replying with error", + __func__); + send_pcep_error(session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + /* Check for additional Open Msg objects */ + if (open_msg->obj_list->num_entries > 1) { + pcep_log( + LOG_INFO, + "%s: Found additional unsupported objects in the Open message, replying with error", + __func__); + send_pcep_error(session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_INVALID_OPEN_MSG); + return false; + } + + session->pce_open_received = true; + + /* Verify the open object parameters and TLVs */ + if (verify_pcep_open_object(session, open_object) == false) { + enqueue_event(session, PCC_RCVD_INVALID_OPEN, NULL); + if (session->pce_open_rejected) { + /* The Open message was already rejected once, so + * according to the spec, send an error message and + * close the TCP connection. */ + pcep_log( + LOG_INFO, + "%s: Received 2 consecutive unsupported Open messages, closing the connection.", + __func__); + send_pcep_error( + session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_RECVD_SECOND_OPEN_MSG_UNACCEPTABLE); + socket_comm_session_close_tcp_after_write( + session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; + enqueue_event(session, PCC_CONNECTION_FAILURE, NULL); + } else { + session->pce_open_rejected = true; + /* Clone the object here, since the encapsulating + * message will be deleted in handle_socket_comm_event() + * most likely before this error message is sent */ + struct pcep_object_open *cloned_open_object = + pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_object_open)); + memcpy(cloned_open_object, open_object, + sizeof(struct pcep_object_open)); + open_object->header.tlv_list = NULL; + cloned_open_object->header.encoded_object = NULL; + cloned_open_object->header.encoded_object_length = 0; + send_pcep_error_with_object( + session, PCEP_ERRT_SESSION_FAILURE, + PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG, + &cloned_open_object->header); + } + + return false; + } + + /* + * Open Message accepted + * Sending the keep-alive response will be managed the function caller + */ + + session->timer_id_open_keep_alive = + create_timer(TIMER_OPEN_KEEP_ALIVE_SECONDS, session); + session->pcc_config.dead_timer_pce_negotiated_seconds = + (int)open_object->open_deadtimer; + /* Cancel the timer so we can change the dead_timer value */ + cancel_timer(session->timer_id_dead_timer); + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + reset_dead_timer(session); + + return true; +} + + +/* The original PCEP Open message sent to the PCE was rejected, + * try to reconcile the differences and re-send a new Open. */ +void send_reconciled_pcep_open(pcep_session *session, + struct pcep_message *error_msg) +{ + struct pcep_message *open_msg = create_pcep_open(session); + + struct pcep_object_open *error_open_obj = + (struct pcep_object_open *)pcep_obj_get(error_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + if (error_open_obj == NULL) { + /* Nothing to reconcile, send the same Open message again */ + pcep_log( + LOG_INFO, + "%s: No Open object received in Error, sending the same Open message", + __func__); + session_send_message(session, open_msg); + return; + } + + struct pcep_object_open *open_obj = + (struct pcep_object_open *)pcep_obj_get(open_msg->obj_list, + PCEP_OBJ_CLASS_OPEN); + // open_msg can not have empty obj_list + assert(open_obj != NULL); + + if (error_open_obj->open_deadtimer + != session->pce_config.dead_timer_seconds) { + if (error_open_obj->open_deadtimer + >= session->pce_config.min_dead_timer_seconds + && error_open_obj->open_deadtimer + <= session->pce_config.max_dead_timer_seconds) { + open_obj->open_deadtimer = + error_open_obj->open_deadtimer; + session->pcc_config.dead_timer_pce_negotiated_seconds = + error_open_obj->open_deadtimer; + pcep_log( + LOG_INFO, + "%s: Open deadtimer value [%d] rejected, using PCE value [%d]", + __func__, + session->pcc_config.dead_timer_seconds, + session->pcc_config + .dead_timer_pce_negotiated_seconds); + /* Reset the timer with the new value */ + cancel_timer(session->timer_id_dead_timer); + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + reset_dead_timer(session); + } else { + pcep_log( + LOG_INFO, + "%s: Can not reconcile Open with suggested deadtimer [%d]", + __func__, error_open_obj->open_deadtimer); + } + } + + if (error_open_obj->open_keepalive + != session->pce_config.keep_alive_seconds) { + if (error_open_obj->open_keepalive + >= session->pce_config.min_keep_alive_seconds + && error_open_obj->open_keepalive + <= session->pce_config.max_keep_alive_seconds) { + open_obj->open_keepalive = + error_open_obj->open_keepalive; + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds = + error_open_obj->open_keepalive; + pcep_log( + LOG_INFO, + "%s: Open keep alive value [%d] rejected, using PCE value [%d]", + __func__, + session->pcc_config.keep_alive_seconds, + session->pcc_config + .keep_alive_pce_negotiated_timer_seconds); + /* Cancel the timer, the timer will be set again with + * the new value when this open message is sent */ + cancel_timer(session->timer_id_keep_alive); + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + } else { + pcep_log( + LOG_INFO, + "%s: Can not reconcile Open with suggested keepalive [%d]", + __func__, error_open_obj->open_keepalive); + } + } + + /* TODO reconcile the TLVs */ + + session_send_message(session, open_msg); + reset_timer(session->timer_id_open_keep_alive); +} + + +bool handle_pcep_update(pcep_session *session, struct pcep_message *upd_msg) +{ + /* Update Message validation and errors according to: + * https://tools.ietf.org/html/rfc8231#section-6.2 */ + + if (upd_msg->obj_list == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Message has no objects", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + /* Verify the mandatory objects are present */ + struct pcep_object_header *obj = + pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_SRP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing SRP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_LSP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing LSP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(upd_msg->obj_list, PCEP_OBJ_CLASS_ERO); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcUpd message: Missing ERO object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING); + return false; + } + + /* Verify the objects are are in the correct order */ + double_linked_list_node *node = upd_msg->obj_list->head; + struct pcep_object_srp *srp_object = + (struct pcep_object_srp *)node->data; + if (srp_object->header.object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: First object must be an SRP, found [%d]", + __func__, srp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_lsp *lsp_object = + (struct pcep_object_lsp *)node->data; + if (lsp_object->header.object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: Second object must be an LSP, found [%d]", + __func__, lsp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_ro *ero_object = node->data; + if (ero_object->header.object_class != PCEP_OBJ_CLASS_ERO) { + pcep_log( + LOG_INFO, + "%s: Invalid PcUpd message: Third object must be an ERO, found [%d]", + __func__, ero_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_ERO_OBJECT_MISSING); + return false; + } + + return true; +} + +bool handle_pcep_initiate(pcep_session *session, struct pcep_message *init_msg) +{ + /* Instantiate Message validation and errors according to: + * https://tools.ietf.org/html/rfc8281#section-5 */ + + if (init_msg->obj_list == NULL) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: Message has no objects", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + /* Verify the mandatory objects are present */ + struct pcep_object_header *obj = + pcep_obj_get(init_msg->obj_list, PCEP_OBJ_CLASS_SRP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcInitiate message: Missing SRP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + obj = pcep_obj_get(init_msg->obj_list, PCEP_OBJ_CLASS_LSP); + if (obj == NULL) { + pcep_log(LOG_INFO, + "%s: Invalid PcInitiate message: Missing LSP object", + __func__); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + /* Verify the objects are are in the correct order */ + double_linked_list_node *node = init_msg->obj_list->head; + struct pcep_object_srp *srp_object = + (struct pcep_object_srp *)node->data; + if (srp_object->header.object_class != PCEP_OBJ_CLASS_SRP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: First object must be an SRP, found [%d]", + __func__, srp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_SRP_OBJECT_MISSING); + return false; + } + + node = node->next_node; + struct pcep_object_lsp *lsp_object = + (struct pcep_object_lsp *)node->data; + if (lsp_object->header.object_class != PCEP_OBJ_CLASS_LSP) { + pcep_log( + LOG_INFO, + "%s: Invalid PcInitiate message: Second object must be an LSP, found [%d]", + __func__, lsp_object->header.object_class); + send_pcep_error(session, PCEP_ERRT_MANDATORY_OBJECT_MISSING, + PCEP_ERRV_LSP_OBJECT_MISSING); + return false; + } + + /* There may be more optional objects */ + return true; +} + +void increment_unknown_message(pcep_session *session) +{ + /* https://tools.ietf.org/html/rfc5440#section-6.9 + * If a PCC/PCE receives unrecognized messages at a rate equal or + * greater than MAX-UNKNOWN-MESSAGES unknown message requests per + * minute, the PCC/PCE MUST send a PCEP CLOSE message */ + + time_t *unknown_message_time = + pceplib_malloc(PCEPLIB_INFRA, sizeof(time_t)); + *unknown_message_time = time(NULL); + time_t expire_time = *unknown_message_time + 60; + queue_enqueue(session->num_unknown_messages_time_queue, + unknown_message_time); + + /* Purge any entries older than 1 minute. The oldest entries are at the + * queue head */ + queue_node *time_node = session->num_unknown_messages_time_queue->head; + while (time_node != NULL) { + if (*((time_t *)time_node->data) > expire_time) { + pceplib_free( + PCEPLIB_INFRA, + queue_dequeue( + session->num_unknown_messages_time_queue)); + time_node = + session->num_unknown_messages_time_queue->head; + } else { + time_node = NULL; + } + } + + if ((int)session->num_unknown_messages_time_queue->num_entries + >= session->pcc_config.max_unknown_messages) { + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Max unknown messages reached [%d] closing session [%d]", + __func__, time(NULL), pthread_self(), + session->pcc_config.max_unknown_messages, + session->session_id); + + close_pcep_session_with_reason(session, + PCEP_CLOSE_REASON_UNREC_MSG); + enqueue_event(session, PCC_RCVD_MAX_UNKOWN_MSGS, NULL); + } +} + +bool check_and_send_open_keep_alive(pcep_session *session) +{ + if (session->pce_open_received == true + && session->pce_open_rejected == false + && session->pce_open_keep_alive_sent == false) { + /* Send the PCE Open keep-alive response if it hasnt been sent + * yet */ + cancel_timer(session->timer_id_open_keep_alive); + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + send_keep_alive(session); + session->pce_open_keep_alive_sent = true; + + return true; + } + + return false; +} + +void log_pcc_pce_connection(pcep_session *session) +{ + if (session->socket_comm_session == NULL) { + /* This only happens in UT */ + return; + } + + char src_ip_buf[40] = {0}, dst_ip_buf[40] = {0}; + uint16_t src_port, dst_port; + + if (session->socket_comm_session->is_ipv6) { + inet_ntop(AF_INET6, + &session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv6.sin6_addr, + src_ip_buf, sizeof(src_ip_buf)); + inet_ntop(AF_INET6, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_addr, + dst_ip_buf, sizeof(dst_ip_buf)); + src_port = htons(session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv6.sin6_port); + dst_port = htons(session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6.sin6_port); + } else { + inet_ntop(AF_INET, + &session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv4.sin_addr, + src_ip_buf, sizeof(src_ip_buf)); + inet_ntop(AF_INET, + &session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_addr, + dst_ip_buf, sizeof(dst_ip_buf)); + src_port = htons(session->socket_comm_session->src_sock_addr + .src_sock_addr_ipv4.sin_port); + dst_port = htons(session->socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4.sin_port); + } + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] Successful PCC [%s:%d] connection to PCE [%s:%d] session [%d] fd [%d]", + __func__, time(NULL), pthread_self(), src_ip_buf, src_port, + dst_ip_buf, dst_port, session->session_id, + session->socket_comm_session->socket_fd); +} + +/* + * these functions are called by session_logic_loop() from + * pcep_session_logic_loop.c these functions are executed in the + * session_logic_loop thread, and the mutex is locked before calling these + * functions, so they are thread safe. + */ + +/* state machine handling for expired timers */ +void handle_timer_event(pcep_session_event *event) +{ + if (event == NULL) { + pcep_log(LOG_INFO, "%s: handle_timer_event NULL event", + __func__); + return; + } + + pcep_session *session = event->session; + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic handle_timer_event: session [%d] event timer_id [%d] session timers [OKW, OKA, DT, KA] [%d, %d, %d, %d]", + __func__, time(NULL), pthread_self(), session->session_id, + event->expired_timer_id, session->timer_id_open_keep_wait, + session->timer_id_open_keep_alive, session->timer_id_dead_timer, + session->timer_id_keep_alive); + + /* + * these timer expirations are independent of the session state + */ + if (event->expired_timer_id == session->timer_id_dead_timer) { + session->timer_id_dead_timer = TIMER_ID_NOT_SET; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_TIMER_DEADTIMER); + close_pcep_session_with_reason(session, + PCEP_CLOSE_REASON_DEADTIMER); + enqueue_event(session, PCE_DEAD_TIMER_EXPIRED, NULL); + return; + } else if (event->expired_timer_id == session->timer_id_keep_alive) { + session->timer_id_keep_alive = TIMER_ID_NOT_SET; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_TIMER_KEEPALIVE); + send_keep_alive(session); + return; + } + + /* + * handle timers that depend on the session state + */ + switch (session->session_state) { + case SESSION_STATE_PCEP_CONNECTING: + if (event->expired_timer_id + == session->timer_id_open_keep_wait) { + /* close the TCP session */ + pcep_log( + LOG_INFO, + "%s: handle_timer_event open_keep_wait timer expired for session [%d]", + __func__, session->session_id); + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPWAIT); + socket_comm_session_close_tcp_after_write( + session->socket_comm_session); + session->session_state = SESSION_STATE_INITIALIZED; + session->timer_id_open_keep_wait = TIMER_ID_NOT_SET; + enqueue_event(session, PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED, + NULL); + } + + if (event->expired_timer_id + == session->timer_id_open_keep_alive) { + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_TIMER_OPENKEEPALIVE); + session->timer_id_open_keep_alive = TIMER_ID_NOT_SET; + if (check_and_send_open_keep_alive(session) == true) { + if (session->pcc_open_accepted == true + && session->session_state + != SESSION_STATE_PCEP_CONNECTED) { + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + return; + } + break; + + case SESSION_STATE_INITIALIZED: + case SESSION_STATE_PCEP_CONNECTED: + case SESSION_STATE_UNKNOWN: + pcep_log( + LOG_INFO, + "%s: handle_timer_event unrecognized state transition, timer_id [%d] state [%d] session [%d]", + __func__, event->expired_timer_id, + session->session_state, session->session_id); + break; + } +} + +/* State machine handling for received messages. + * This event was created in session_logic_msg_ready_handler() in + * pcep_session_logic_loop.c */ +void handle_socket_comm_event(pcep_session_event *event) +{ + if (event == NULL) { + pcep_log(LOG_INFO, "%s: handle_socket_comm_event NULL event", + __func__); + return; + } + + pcep_session *session = event->session; + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] pcep_session_logic handle_socket_comm_event: session [%d] num messages [%d] socket_closed [%d]", + __func__, time(NULL), pthread_self(), session->session_id, + (event->received_msg_list == NULL + ? -1 + : (int)event->received_msg_list->num_entries), + event->socket_closed); + + /* + * independent of the session state + */ + if (event->socket_closed) { + pcep_log( + LOG_INFO, + "%s: handle_socket_comm_event socket closed for session [%d]", + __func__, session->session_id); + socket_comm_session_close_tcp(session->socket_comm_session); + enqueue_event(session, PCE_CLOSED_SOCKET, NULL); + if (session->session_state == SESSION_STATE_PCEP_CONNECTING) { + enqueue_event(session, PCC_CONNECTION_FAILURE, NULL); + } + session->session_state = SESSION_STATE_INITIALIZED; + increment_event_counters(session, + PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT); + return; + } + + reset_dead_timer(session); + + if (event->received_msg_list == NULL) { + return; + } + + /* Message received on socket */ + double_linked_list_node *msg_node; + for (msg_node = event->received_msg_list->head; msg_node != NULL; + msg_node = msg_node->next_node) { + bool message_enqueued = false; + struct pcep_message *msg = + (struct pcep_message *)msg_node->data; + pcep_log(LOG_INFO, "%s: \t %s message", __func__, + get_message_type_str(msg->msg_header->type)); + + increment_message_rx_counters(session, msg); + + switch (msg->msg_header->type) { + case PCEP_TYPE_OPEN: + /* handle_pcep_open() checks session state, and for + * duplicate erroneous open messages, and replies with + * error messages as needed. It also sets + * pce_open_received. */ + if (handle_pcep_open(session, msg) == true) { + /* PCE Open Message Accepted */ + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + session->pce_open_accepted = true; + session->pce_open_rejected = false; + if (session->pcc_open_accepted) { + /* If both the PCC and PCE Opens are + * accepted, then the session is + * connected */ + + check_and_send_open_keep_alive(session); + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCE_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + break; + + case PCEP_TYPE_KEEPALIVE: + if (session->session_state + == SESSION_STATE_PCEP_CONNECTING) { + /* PCC Open Message Accepted */ + cancel_timer(session->timer_id_open_keep_wait); + session->timer_id_open_keep_wait = + TIMER_ID_NOT_SET; + session->pcc_open_accepted = true; + session->pcc_open_rejected = false; + check_and_send_open_keep_alive(session); + + if (session->pce_open_accepted) { + /* If both the PCC and PCE Opens are + * accepted, then the session is + * connected */ + log_pcc_pce_connection(session); + session->session_state = + SESSION_STATE_PCEP_CONNECTED; + increment_event_counters( + session, + PCEP_EVENT_COUNTER_ID_PCC_CONNECT); + enqueue_event(session, + PCC_CONNECTED_TO_PCE, + NULL); + } + } + /* The dead_timer was already reset above, so nothing + * extra to do here */ + break; + + case PCEP_TYPE_PCREP: + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + case PCEP_TYPE_CLOSE: + session->session_state = SESSION_STATE_INITIALIZED; + socket_comm_session_close_tcp( + session->socket_comm_session); + /* TODO should we also enqueue the message, so they can + * see the reasons?? */ + enqueue_event(session, PCE_SENT_PCEP_CLOSE, NULL); + /* TODO could this duplicate the disconnect counter with + * socket close ?? */ + increment_event_counters( + session, PCEP_EVENT_COUNTER_ID_PCE_DISCONNECT); + break; + + case PCEP_TYPE_PCREQ: + /* The PCC does not support receiving PcReq messages */ + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + break; + + case PCEP_TYPE_REPORT: + /* The PCC does not support receiving Report messages */ + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + break; + + case PCEP_TYPE_UPDATE: + /* Should reply with a PcRpt */ + if (handle_pcep_update(session, msg) == true) { + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + } + break; + + case PCEP_TYPE_INITIATE: + /* Should reply with a PcRpt */ + if (handle_pcep_initiate(session, msg) == true) { + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + } + break; + + case PCEP_TYPE_PCNOTF: + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + case PCEP_TYPE_ERROR: + if (msg->obj_list != NULL + && msg->obj_list->num_entries > 0) { + struct pcep_object_header *obj_hdr = + pcep_obj_get(msg->obj_list, + PCEP_OBJ_CLASS_ERROR); + if (obj_hdr != NULL) { + struct pcep_object_error *error_obj = + (struct pcep_object_error *) + obj_hdr; + pcep_log( + LOG_DEBUG, + "%s: Error object [type, value] = [%s, %s]", + __func__, + get_error_type_str( + error_obj->error_type), + get_error_value_str( + error_obj->error_type, + error_obj + ->error_value)); + } + } + + if (session->session_state + == SESSION_STATE_PCEP_CONNECTING) { + /* A PCC_CONNECTION_FAILURE event will be sent + * when the socket is closed, if the state is + * SESSION_STATE_PCEP_CONNECTING, in case the + * PCE allows more than 2 failed open messages. + */ + pcep_log(LOG_INFO, + "%s: PCC Open message rejected by PCE", + __func__); + session->pcc_open_rejected = true; + send_reconciled_pcep_open(session, msg); + enqueue_event(session, PCC_SENT_INVALID_OPEN, + NULL); + } + enqueue_event(session, MESSAGE_RECEIVED, msg); + message_enqueued = true; + break; + + case PCEP_TYPE_START_TLS: + case PCEP_TYPE_MAX: + pcep_log(LOG_INFO, "%s: \t UnSupported message", + __func__); + send_pcep_error(session, + PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + PCEP_ERRV_UNASSIGNED); + increment_unknown_message(session); + break; + } + + /* if the message was enqueued, dont free it yet */ + if (message_enqueued == false) { + pcep_msg_free_message(msg); + } + } + dll_destroy(event->received_msg_list); +} diff --git a/pceplib/pcep_socket_comm.c b/pceplib/pcep_socket_comm.c new file mode 100644 index 0000000..30fbc82 --- /dev/null +++ b/pceplib/pcep_socket_comm.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of public API functions. + */ + +#include + +#include +#include +#include // gethostbyname +#include +#include +#include // close + +#include // sockets etc. +#include // sockets etc. +#include // sockets etc. + +#include "pcep.h" +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_internals.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_queue.h" + +bool initialize_socket_comm_pre(void); +bool socket_comm_session_initialize_post( + pcep_socket_comm_session *socket_comm_session); + +pcep_socket_comm_handle *socket_comm_handle_ = NULL; + + +/* simple compare method callback used by pcep_utils_ordered_list + * for ordered list insertion. */ +int socket_fd_node_compare(void *list_entry, void *new_entry) +{ + return ((pcep_socket_comm_session *)new_entry)->socket_fd + - ((pcep_socket_comm_session *)list_entry)->socket_fd; +} + + +bool initialize_socket_comm_pre(void) +{ + socket_comm_handle_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_handle)); + memset(socket_comm_handle_, 0, sizeof(pcep_socket_comm_handle)); + + socket_comm_handle_->active = true; + socket_comm_handle_->num_active_sessions = 0; + socket_comm_handle_->read_list = + ordered_list_initialize(socket_fd_node_compare); + socket_comm_handle_->write_list = + ordered_list_initialize(socket_fd_node_compare); + socket_comm_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + FD_ZERO(&socket_comm_handle_->except_master_set); + FD_ZERO(&socket_comm_handle_->read_master_set); + FD_ZERO(&socket_comm_handle_->write_master_set); + + if (pthread_mutex_init(&(socket_comm_handle_->socket_comm_mutex), NULL) + != 0) { + pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm mutex.", + __func__); + pceplib_free(PCEPLIB_INFRA, socket_comm_handle_); + socket_comm_handle_ = NULL; + + return false; + } + + return true; +} + +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func) +{ + if (socket_comm_handle_ != NULL) { + /* already initialized */ + return true; + } + + if (initialize_socket_comm_pre() == false) { + return false; + } + + /* Notice: If the thread_create_func is set, then both the + * socket_read_cb and the socket_write_cb SHOULD be NULL. */ + if (thread_create_func != NULL) { + if (thread_create_func( + &(socket_comm_handle_->socket_comm_thread), NULL, + socket_comm_loop, socket_comm_handle_, + "pceplib_timers")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external socket_comm thread.", + __func__); + return false; + } + } + + socket_comm_handle_->external_infra_data = external_infra_data; + socket_comm_handle_->socket_write_func = socket_write_cb; + socket_comm_handle_->socket_read_func = socket_read_cb; + + return true; +} + +bool initialize_socket_comm_loop(void) +{ + if (socket_comm_handle_ != NULL) { + /* already initialized */ + return true; + } + + if (initialize_socket_comm_pre() == false) { + return false; + } + + /* Launch socket comm loop pthread */ + if (pthread_create(&(socket_comm_handle_->socket_comm_thread), NULL, + socket_comm_loop, socket_comm_handle_)) { + pcep_log(LOG_ERR, "%s: Cannot initialize socket_comm thread.", + __func__); + return false; + } + + return true; +} + + +bool destroy_socket_comm_loop(void) +{ + socket_comm_handle_->active = false; + + pthread_join(socket_comm_handle_->socket_comm_thread, NULL); + ordered_list_destroy(socket_comm_handle_->read_list); + ordered_list_destroy(socket_comm_handle_->write_list); + ordered_list_destroy(socket_comm_handle_->session_list); + pthread_mutex_destroy(&(socket_comm_handle_->socket_comm_mutex)); + + pceplib_free(PCEPLIB_INFRA, socket_comm_handle_); + socket_comm_handle_ = NULL; + + return true; +} + +/* Internal common init function */ +static pcep_socket_comm_session *socket_comm_session_initialize_pre( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + /* check that not both message handlers were set */ + if (message_handler != NULL && message_ready_handler != NULL) { + pcep_log( + LOG_WARNING, + "%s: Only one of can be set.", + __func__); + return NULL; + } + + /* check that at least one message handler was set */ + if (message_handler == NULL && message_ready_handler == NULL) { + pcep_log( + LOG_WARNING, + "%s: At least one of must be set.", + __func__); + return NULL; + } + + if (!initialize_socket_comm_loop()) { + pcep_log(LOG_WARNING, + "%s: ERROR: cannot initialize socket_comm_loop.", + __func__); + + return NULL; + } + + /* initialize everything for a pcep_session socket_comm */ + + pcep_socket_comm_session *socket_comm_session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_session)); + memset(socket_comm_session, 0, sizeof(pcep_socket_comm_session)); + + socket_comm_handle_->num_active_sessions++; + socket_comm_session->close_after_write = false; + socket_comm_session->session_data = session_data; + socket_comm_session->message_handler = message_handler; + socket_comm_session->message_ready_to_read_handler = + message_ready_handler; + socket_comm_session->message_sent_handler = msg_sent_notifier; + socket_comm_session->conn_except_notifier = notifier; + socket_comm_session->message_queue = queue_initialize(); + socket_comm_session->connect_timeout_millis = connect_timeout_millis; + socket_comm_session->external_socket_data = NULL; + if (tcp_authentication_str != NULL) { + socket_comm_session->is_tcp_auth_md5 = is_tcp_auth_md5; + strlcpy(socket_comm_session->tcp_authentication_str, + tcp_authentication_str, + sizeof(socket_comm_session->tcp_authentication_str)); + } + + return socket_comm_session; +} + +/* Internal common init function */ +bool socket_comm_session_initialize_post( + pcep_socket_comm_session *socket_comm_session) +{ + /* If we dont use SO_REUSEADDR, the socket will take 2 TIME_WAIT + * periods before being closed in the kernel if bind() was called */ + int reuse_addr = 1; + if (setsockopt(socket_comm_session->socket_fd, SOL_SOCKET, SO_REUSEADDR, + &reuse_addr, sizeof(int)) + < 0) { + pcep_log( + LOG_WARNING, + "%s: Error in setsockopt() SO_REUSEADDR errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown(socket_comm_session); + + return false; + } + + struct sockaddr *src_sock_addr = + (socket_comm_session->is_ipv6 + ? (struct sockaddr *)&( + socket_comm_session->src_sock_addr + .src_sock_addr_ipv6) + : (struct sockaddr *)&( + socket_comm_session->src_sock_addr + .src_sock_addr_ipv4)); + int addr_len = (socket_comm_session->is_ipv6 + ? sizeof(socket_comm_session->src_sock_addr + .src_sock_addr_ipv6) + : sizeof(socket_comm_session->src_sock_addr + .src_sock_addr_ipv4)); + if (bind(socket_comm_session->socket_fd, src_sock_addr, addr_len) + == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot bind address to socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown(socket_comm_session); + + return false; + } + + /* Register the session as active with the Socket Comm Loop */ + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + ordered_list_add_node(socket_comm_handle_->session_list, + socket_comm_session); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + /* dont connect to the destination yet, since the PCE will have a timer + * for max time between TCP connect and PCEP open. we'll connect later + * when we send the PCEP open. */ + + return true; +} + + +pcep_socket_comm_session *socket_comm_session_initialize( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *dest_ip, + short dest_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + return socket_comm_session_initialize_with_src( + message_handler, message_ready_handler, msg_sent_notifier, + notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); +} + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dest_ip, + short dest_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + return socket_comm_session_initialize_with_src_ipv6( + message_handler, message_ready_handler, msg_sent_notifier, + notifier, NULL, 0, dest_ip, dest_port, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); +} + + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dest_ip, short dest_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + if (dest_ip == NULL) { + pcep_log(LOG_WARNING, "%s: dest_ipv4 is NULL", __func__); + return NULL; + } + + pcep_socket_comm_session *socket_comm_session = + socket_comm_session_initialize_pre( + message_handler, message_ready_handler, + msg_sent_notifier, notifier, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); + if (socket_comm_session == NULL) { + return NULL; + } + + socket_comm_session->socket_fd = + socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket_comm_session->socket_fd == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot create ipv4 socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown( + socket_comm_session); // socket_comm_session freed + // inside fn so NOLINT next. + + return NULL; // NOLINT(clang-analyzer-unix.Malloc) + } + + socket_comm_session->is_ipv6 = false; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = + AF_INET; + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_family = + AF_INET; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dest_port); + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_port = + htons(src_port); + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr + .s_addr = dest_ip->s_addr; + if (src_ip != NULL) { + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr + .s_addr = src_ip->s_addr; + } else { + socket_comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr + .s_addr = INADDR_ANY; + } + + if (socket_comm_session_initialize_post(socket_comm_session) == false) { + return NULL; + } + + return socket_comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler message_handler, + message_ready_to_read_handler message_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dest_ip, short dest_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + if (dest_ip == NULL) { + pcep_log(LOG_WARNING, "%s: dest_ipv6 is NULL", __func__); + return NULL; + } + + pcep_socket_comm_session *socket_comm_session = + socket_comm_session_initialize_pre( + message_handler, message_ready_handler, + msg_sent_notifier, notifier, connect_timeout_millis, + tcp_authentication_str, is_tcp_auth_md5, session_data); + if (socket_comm_session == NULL) { + return NULL; + } + + socket_comm_session->socket_fd = + socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (socket_comm_session->socket_fd == -1) { + pcep_log(LOG_WARNING, + "%s: Cannot create ipv6 socket errno [%d %s].", + __func__, errno, strerror(errno)); + socket_comm_session_teardown( + socket_comm_session); // socket_comm_session freed + // inside fn so NOLINT next. + + return NULL; // NOLINT(clang-analyzer-unix.Malloc) + } + + socket_comm_session->is_ipv6 = true; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = + AF_INET6; + socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_family = + AF_INET6; + socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dest_port); + socket_comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_port = + htons(src_port); + memcpy(&socket_comm_session->dest_sock_addr.dest_sock_addr_ipv6 + .sin6_addr, + dest_ip, sizeof(struct in6_addr)); + if (src_ip != NULL) { + memcpy(&socket_comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + src_ip, sizeof(struct in6_addr)); + } else { + socket_comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr = in6addr_any; + } + + if (socket_comm_session_initialize_post(socket_comm_session) == false) { + return NULL; + } + + return socket_comm_session; +} + + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_connect_tcp NULL socket_comm_session.", + __func__); + return NULL; + } + + /* Set the socket to non-blocking, so connect() does not block */ + int fcntl_arg; + if ((fcntl_arg = fcntl(socket_comm_session->socket_fd, F_GETFL, NULL)) + < 0) { + pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_GETFL) [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + + fcntl_arg |= O_NONBLOCK; + if (fcntl(socket_comm_session->socket_fd, F_SETFL, fcntl_arg) < 0) { + pcep_log(LOG_WARNING, "%s: Error fcntl(..., F_SETFL) [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + +#if HAVE_DECL_TCP_MD5SIG + /* TCP authentication, currently only TCP MD5 RFC2385 is supported */ + if (socket_comm_session->tcp_authentication_str[0] != '\0') { +#if defined(linux) || defined(GNU_LINUX) + struct tcp_md5sig sig; + memset(&sig, 0, sizeof(sig)); + if (socket_comm_session->is_ipv6) { + memcpy(&sig.tcpm_addr, + &socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6, + sizeof(struct sockaddr_in6)); + } else { + memcpy(&sig.tcpm_addr, + &socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4, + sizeof(struct sockaddr_in)); + } + sig.tcpm_keylen = + strlen(socket_comm_session->tcp_authentication_str); + memcpy(sig.tcpm_key, + socket_comm_session->tcp_authentication_str, + sig.tcpm_keylen); +#else + int sig = 1; +#endif + if (setsockopt(socket_comm_session->socket_fd, IPPROTO_TCP, + TCP_MD5SIG, &sig, sizeof(sig)) + == -1) { + pcep_log(LOG_ERR, "%s: Failed to setsockopt(): [%d %s]", + __func__, errno, strerror(errno)); + return false; + } + } +#endif + + int connect_result = 0; + if (socket_comm_session->is_ipv6) { + connect_result = connect( + socket_comm_session->socket_fd, + (struct sockaddr *)&(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6), + sizeof(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv6)); + } else { + connect_result = connect( + socket_comm_session->socket_fd, + (struct sockaddr *)&(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4), + sizeof(socket_comm_session->dest_sock_addr + .dest_sock_addr_ipv4)); + } + + if (connect_result < 0) { + if (errno == EINPROGRESS) { + /* Calculate the configured timeout in seconds and + * microseconds */ + struct timeval tv; + if (socket_comm_session->connect_timeout_millis + > 1000) { + tv.tv_sec = socket_comm_session + ->connect_timeout_millis + / 1000; + tv.tv_usec = (socket_comm_session + ->connect_timeout_millis + - (tv.tv_sec * 1000)) + * 1000; + } else { + tv.tv_sec = 0; + tv.tv_usec = socket_comm_session + ->connect_timeout_millis + * 1000; + } + + /* Use select to wait a max timeout for connect + * https://stackoverflow.com/questions/2597608/c-socket-connection-timeout + */ + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(socket_comm_session->socket_fd, &fdset); + if (select(socket_comm_session->socket_fd + 1, NULL, + &fdset, NULL, &tv) + > 0) { + int so_error; + socklen_t len = sizeof(so_error); + getsockopt(socket_comm_session->socket_fd, + SOL_SOCKET, SO_ERROR, &so_error, + &len); + if (so_error) { + pcep_log( + LOG_WARNING, + "%s: TCP connect failed on socket_fd [%d].", + __func__, + socket_comm_session->socket_fd); + return false; + } + } else { + pcep_log( + LOG_WARNING, + "%s: TCP connect timed-out on socket_fd [%d].", + __func__, + socket_comm_session->socket_fd); + return false; + } + } else { + pcep_log( + LOG_WARNING, + "%s: TCP connect, error connecting on socket_fd [%d] errno [%d %s]", + __func__, socket_comm_session->socket_fd, errno, + strerror(errno)); + return false; + } + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + /* once the TCP connection is open, we should be ready to read at any + * time */ + ordered_list_add_node(socket_comm_handle_->read_list, + socket_comm_session); + + if (socket_comm_handle_->socket_read_func != NULL) { + socket_comm_handle_->socket_read_func( + socket_comm_handle_->external_infra_data, + &socket_comm_session->external_socket_data, + socket_comm_session->socket_fd, socket_comm_handle_); + } + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_close_tcp NULL socket_comm_session.", + __func__); + return false; + } + + pcep_log(LOG_DEBUG, + "%s: socket_comm_session_close_tcp close() socket fd [%d]", + __func__, socket_comm_session->socket_fd); + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + ordered_list_remove_first_node_equals(socket_comm_handle_->read_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->write_list, + socket_comm_session); + // TODO should it be close() or shutdown()?? + close(socket_comm_session->socket_fd); + socket_comm_session->socket_fd = -1; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_close_tcp_after_write NULL socket_comm_session.", + __func__); + return false; + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + socket_comm_session->close_after_write = true; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + return true; +} + + +bool socket_comm_session_teardown(pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle_ == NULL) { + pcep_log(LOG_WARNING, + "%s: Cannot teardown NULL socket_comm_handle", + __func__); + return false; + } + + if (socket_comm_session == NULL) { + pcep_log(LOG_WARNING, "%s: Cannot teardown NULL session", + __func__); + return false; + } + + if (comm_session_exists_locking(socket_comm_handle_, + socket_comm_session) + == false) { + pcep_log(LOG_WARNING, + "%s: Cannot teardown session that does not exist", + __func__); + return false; + } + + if (socket_comm_session->socket_fd >= 0) { + shutdown(socket_comm_session->socket_fd, SHUT_RDWR); + close(socket_comm_session->socket_fd); + } + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + queue_destroy(socket_comm_session->message_queue); + ordered_list_remove_first_node_equals(socket_comm_handle_->session_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->read_list, + socket_comm_session); + ordered_list_remove_first_node_equals(socket_comm_handle_->write_list, + socket_comm_session); + socket_comm_handle_->num_active_sessions--; + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm_session fd [%d] destroyed, [%d] sessions remaining", + __func__, time(NULL), pthread_self(), + socket_comm_session->socket_fd, + socket_comm_handle_->num_active_sessions); + + pceplib_free(PCEPLIB_INFRA, socket_comm_session); + + /* It would be nice to call destroy_socket_comm_loop() here if + * socket_comm_handle_->num_active_sessions == 0, but this function + * will usually be called from the message_sent_notifier callback, + * which gets called in the middle of the socket_comm_loop, and that + * is dangerous, so destroy_socket_comm_loop() must be called upon + * application exit. */ + + return true; +} + + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool free_after_send) +{ + if (socket_comm_session == NULL) { + pcep_log( + LOG_WARNING, + "%s: socket_comm_session_send_message NULL socket_comm_session.", + __func__); + return; + } + + pcep_socket_comm_queued_message *queued_message = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(pcep_socket_comm_queued_message)); + queued_message->encoded_message = encoded_message; + queued_message->msg_length = msg_length; + queued_message->free_after_send = free_after_send; + + pthread_mutex_lock(&(socket_comm_handle_->socket_comm_mutex)); + + /* Do not proceed if the socket_comm_session has been deleted */ + if (ordered_list_find(socket_comm_handle_->session_list, + socket_comm_session) + == NULL) { + /* Should never get here, only if the session was deleted and + * someone still tries to write on it */ + pcep_log( + LOG_WARNING, + "%s: Cannot write a message on a deleted socket comm session, discarding message", + __func__); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + pceplib_free(PCEPLIB_MESSAGES, queued_message); + + return; + } + + /* Do not proceed if the socket has been closed */ + if (socket_comm_session->socket_fd < 0) { + /* Should never get here, only if the session was deleted and + * someone still tries to write on it */ + pcep_log( + LOG_WARNING, + "%s: Cannot write a message on a closed socket, discarding message", + __func__); + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); + pceplib_free(PCEPLIB_MESSAGES, queued_message); + + return; + } + + queue_enqueue(socket_comm_session->message_queue, queued_message); + + /* Add it to the write list only if its not already there */ + if (ordered_list_find(socket_comm_handle_->write_list, + socket_comm_session) + == NULL) { + ordered_list_add_node(socket_comm_handle_->write_list, + socket_comm_session); + } + + if (socket_comm_handle_->socket_write_func != NULL) { + socket_comm_handle_->socket_write_func( + socket_comm_handle_->external_infra_data, + &socket_comm_session->external_socket_data, + socket_comm_session->socket_fd, socket_comm_handle_); + } + pthread_mutex_unlock(&(socket_comm_handle_->socket_comm_mutex)); +} diff --git a/pceplib/pcep_socket_comm.h b/pceplib/pcep_socket_comm.h new file mode 100644 index 0000000..30edee4 --- /dev/null +++ b/pceplib/pcep_socket_comm.h @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Declaration of public API functions. + */ + +#ifndef INCLUDE_PCEPSOCKETCOMM_H_ +#define INCLUDE_PCEPSOCKETCOMM_H_ + +#include "pcep.h" +#include // sockaddr_in +#include +#include + +#include "pcep_utils_queue.h" + +#define MAX_RECVD_MSG_SIZE 2048 + +/* + * A socket_comm_session can be initialized with 1 of 2 types of mutually + * exclusive message callbacks: + * - message_received_handler : the socket_comm library reads the message and + * calls the callback with the message_data and message_length. this callback + * should be used for smaller/simpler messages. + * - message_ready_to_read_handler : the socket_comm library will call this + * callback when a message is ready to be read on a socket_fd. this callback + * should be used if the + */ + +/* message received handler that receives the message data and message length */ +typedef void (*message_received_handler)(void *session_data, + const char *message_data, + unsigned int message_length); +/* message ready received handler that should read the message on socket_fd + * and return the number of bytes read */ +typedef int (*message_ready_to_read_handler)(void *session_data, int socket_fd); +/* callback handler called when a messages is sent */ +typedef void (*message_sent_notifier)(void *session_data, int socket_fd); +/* callback handler called when the socket is closed */ +typedef void (*connection_except_notifier)(void *session_data, int socket_fd); + +/* Function pointers when an external socket infrastructure is used */ +typedef int (*ext_socket_write)(void *infra_data, void **infra_socket_data, + int fd, void *data); +typedef int (*ext_socket_read)(void *infra_data, void **infra_socket_data, + int fd, void *data); +typedef int (*ext_socket_pthread_create_callback)( + pthread_t *pthread_id, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *data, const char *thread_name); + +typedef struct pcep_socket_comm_session_ { + message_received_handler message_handler; + message_ready_to_read_handler message_ready_to_read_handler; + message_sent_notifier message_sent_handler; + connection_except_notifier conn_except_notifier; + union src_sock_addr { + struct sockaddr_in src_sock_addr_ipv4; + struct sockaddr_in6 src_sock_addr_ipv6; + } src_sock_addr; + union dest_sock_addr { + struct sockaddr_in dest_sock_addr_ipv4; + struct sockaddr_in6 dest_sock_addr_ipv6; + } dest_sock_addr; + bool is_ipv6; + uint32_t connect_timeout_millis; + int socket_fd; + void *session_data; + queue_handle *message_queue; + char received_message[MAX_RECVD_MSG_SIZE]; + int received_bytes; + bool close_after_write; + void *external_socket_data; /* used for external socket infra */ + /* should be used with is_tcp_auth_md5 flag */ + char tcp_authentication_str[PCEP_MD5SIG_MAXKEYLEN + 1]; + + bool is_tcp_auth_md5; /* flag to distinguish between rfc 2385 (md5) and + rfc 5925 (tcp-ao) */ + +} pcep_socket_comm_session; + + +/* Need to document that when the msg_rcv_handler is called, the data needs + * to be handled in the same function call, else it may be overwritten by + * the next read from this socket */ + + +/* Initialize the Socket Comm infrastructure, with either an internal pthread + * or with an external infrastructure. + * If an internal pthread infrastructure is to be used, then it is not necessary + * to explicitly call initialize_socket_comm_loop() as it will be called + * internally when a socket comm session is initialized. */ + +/* Initialize the Socket Comm infrastructure with an internal pthread */ +bool initialize_socket_comm_loop(void); +/* Initialize the Socket Comm infrastructure with an external infrastructure. + * Notice: If the thread_create_func is set, then both the socket_read_cb + * and the socket_write_cb SHOULD be NULL. */ +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func); + +/* The msg_rcv_handler and msg_ready_handler are mutually exclusive, and only + * one can be set (as explained above), else NULL will be returned. */ +pcep_socket_comm_session * +socket_comm_session_initialize(message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, + struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, + const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dst_ip, + short dst_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data); + +bool socket_comm_session_teardown( + pcep_socket_comm_session *socket_comm_session); + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session); + +/* Immediately close the TCP connection, irregardless if there are pending + * messages to be sent. */ +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session); + +/* Sets a flag to close the TCP connection either after all the pending messages + * are written, or if there are no pending messages, the next time the socket is + * checked to be writeable. */ +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session); + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool free_after_send); + +/* If an external Socket infra like FRR is used, then these functions will + * be called when a socket is ready to read/write in the external infra. + * Implemented in pcep_socket_comm_loop.c */ +int pceplib_external_socket_read(int fd, void *payload); +int pceplib_external_socket_write(int fd, void *payload); + +/* the socket comm loop is started internally by + * socket_comm_session_initialize() + * but needs to be explicitly stopped with this call. */ +bool destroy_socket_comm_loop(void); + +int socket_fd_node_compare(void *list_entry, void *new_entry); + +#endif /* INCLUDE_PCEPSOCKETCOMM_H_ */ diff --git a/pceplib/pcep_socket_comm_internals.h b/pceplib/pcep_socket_comm_internals.h new file mode 100644 index 0000000..f564847 --- /dev/null +++ b/pceplib/pcep_socket_comm_internals.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef SRC_PCEPSOCKETCOMMINTERNALS_H_ +#define SRC_PCEPSOCKETCOMMINTERNALS_H_ + +#include +#include + +#include "pcep_utils_ordered_list.h" +#include "pcep_socket_comm.h" + + +typedef struct pcep_socket_comm_handle_ { + bool active; + pthread_t socket_comm_thread; + pthread_mutex_t socket_comm_mutex; + fd_set read_master_set; + fd_set write_master_set; + fd_set except_master_set; + /* ordered_list of socket_descriptors to read from */ + ordered_list_handle *read_list; + /* ordered_list of socket_descriptors to write to */ + ordered_list_handle *write_list; + ordered_list_handle *session_list; + int num_active_sessions; + void *external_infra_data; + ext_socket_write socket_write_func; + ext_socket_read socket_read_func; + +} pcep_socket_comm_handle; + + +typedef struct pcep_socket_comm_queued_message_ { + const char *encoded_message; + int msg_length; + bool free_after_send; + +} pcep_socket_comm_queued_message; + + +/* Functions implemented in pcep_socket_comm_loop.c */ +void *socket_comm_loop(void *data); +bool comm_session_exists(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session); +bool comm_session_exists_locking(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session); + +#endif /* SRC_PCEPSOCKETCOMMINTERNALS_H_ */ diff --git a/pceplib/pcep_socket_comm_loop.c b/pceplib/pcep_socket_comm_loop.c new file mode 100644 index 0000000..87c45d9 --- /dev/null +++ b/pceplib/pcep_socket_comm_loop.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_loop.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +void write_message(int socket_fd, const char *message, unsigned int msg_length); +unsigned int read_message(int socket_fd, char *received_message, + unsigned int max_message_size); +int build_fd_sets(pcep_socket_comm_handle *socket_comm_handle); +void handle_writes(pcep_socket_comm_handle *socket_comm_handle); +void handle_excepts(pcep_socket_comm_handle *socket_comm_handle); + +bool comm_session_exists(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle == NULL) { + return false; + } + + return (ordered_list_find(socket_comm_handle->session_list, + socket_comm_session) + != NULL); +} + + +bool comm_session_exists_locking(pcep_socket_comm_handle *socket_comm_handle, + pcep_socket_comm_session *socket_comm_session) +{ + if (socket_comm_handle == NULL) { + return false; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + bool exists = + comm_session_exists(socket_comm_handle, socket_comm_session); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return exists; +} + + +void write_message(int socket_fd, const char *message, unsigned int msg_length) +{ + ssize_t bytes_sent = 0; + unsigned int total_bytes_sent = 0; + + while ((uint32_t)bytes_sent < msg_length) { + bytes_sent = write(socket_fd, message + total_bytes_sent, + msg_length); + + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm writing on socket fd [%d] msg_lenth [%u] bytes sent [%d]", + __func__, time(NULL), pthread_self(), socket_fd, + msg_length, bytes_sent); + + if (bytes_sent < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + pcep_log(LOG_WARNING, "%s: send() failure", + __func__); + + return; + } + } else { + total_bytes_sent += bytes_sent; + } + } +} + + +unsigned int read_message(int socket_fd, char *received_message, + unsigned int max_message_size) +{ + /* TODO what if bytes_read == max_message_size? there could be more to + * read */ + unsigned int bytes_read = + read(socket_fd, received_message, max_message_size); + pcep_log( + LOG_INFO, + "%s: [%ld-%ld] socket_comm read message bytes_read [%u] on socket fd [%d]", + __func__, time(NULL), pthread_self(), bytes_read, socket_fd); + + return bytes_read; +} + + +int build_fd_sets(pcep_socket_comm_handle *socket_comm_handle) +{ + int max_fd = 0; + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + + FD_ZERO(&socket_comm_handle->except_master_set); + FD_ZERO(&socket_comm_handle->read_master_set); + ordered_list_node *node = socket_comm_handle->read_list->head; + pcep_socket_comm_session *comm_session; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + if (comm_session->socket_fd > max_fd) { + max_fd = comm_session->socket_fd; + } else if (comm_session->socket_fd < 0) { + pcep_log(LOG_ERR, "%s: Negative fd", __func__); + assert(comm_session->socket_fd > 0); + } + + /*pcep_log(LOG_DEBUG, ld] socket_comm::build_fdSets set + ready_toRead + [%d]", __func__, time(NULL), comm_session->socket_fd);*/ + FD_SET(comm_session->socket_fd, + &socket_comm_handle->read_master_set); + FD_SET(comm_session->socket_fd, + &socket_comm_handle->except_master_set); + node = node->next_node; + } + + FD_ZERO(&socket_comm_handle->write_master_set); + node = socket_comm_handle->write_list->head; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + if (comm_session->socket_fd > max_fd) { + max_fd = comm_session->socket_fd; + } else if (comm_session->socket_fd < 0) { + pcep_log(LOG_ERR, "%s: Negative fd", __func__); + assert(comm_session->socket_fd > 0); + } + + /*pcep_log(LOG_DEBUG, "%s: [%ld] socket_comm::build_fdSets set + ready_toWrite [%d]", __func__, time(NULL), + comm_session->socket_fd);*/ + FD_SET(comm_session->socket_fd, + &socket_comm_handle->write_master_set); + FD_SET(comm_session->socket_fd, + &socket_comm_handle->except_master_set); + node = node->next_node; + } + + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return max_fd + 1; +} + + +void handle_reads(pcep_socket_comm_handle *socket_comm_handle) +{ + + /* + * iterate all the socket_fd's in the read_list. it may be that not + * all of them have something to read. dont remove the socket_fd + * from the read_list since messages could come at any time. + */ + + /* Notice: Only locking the mutex when accessing the read_list, + * since the read callbacks may end up calling back into the socket + * comm module to write messages which could be a deadlock. */ + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + ordered_list_node *node = socket_comm_handle->read_list->head; + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + while (node != NULL) { + pcep_socket_comm_session *comm_session = + (pcep_socket_comm_session *)node->data; + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + node = node->next_node; + if (!comm_session_exists(socket_comm_handle, comm_session)) { + /* This comm_session has been deleted, move on to the + * next one */ + pthread_mutex_unlock( + &(socket_comm_handle->socket_comm_mutex)); + continue; + } + + int is_set = FD_ISSET(comm_session->socket_fd, + &(socket_comm_handle->read_master_set)); + /* Upon read failure, the comm_session might be free'd, so we + * cant store the received_bytes in the comm_session, until we + * know the read was successful. */ + int received_bytes = 0; + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + if (is_set) { + FD_CLR(comm_session->socket_fd, + &(socket_comm_handle->read_master_set)); + + /* either read the message locally, or call the + * message_ready_handler to read it */ + if (comm_session->message_handler != NULL) { + received_bytes = read_message( + comm_session->socket_fd, + comm_session->received_message, + MAX_RECVD_MSG_SIZE); + if (received_bytes > 0) { + /* Send the received message to the + * handler */ + comm_session->received_bytes = + received_bytes; + comm_session->message_handler( + comm_session->session_data, + comm_session->received_message, + comm_session->received_bytes); + } + } else { + /* Tell the handler a message is ready to be + * read. The comm_session may be destroyed in + * this call, if + * there is an error reading or if the socket is + * closed. */ + received_bytes = + comm_session + ->message_ready_to_read_handler( + comm_session + ->session_data, + comm_session + ->socket_fd); + } + + /* handle the read results */ + if (received_bytes == 0) { + if (comm_session_exists_locking( + socket_comm_handle, comm_session)) { + comm_session->received_bytes = 0; + /* the socket was closed */ + /* TODO should we define a socket except + * enum? or will the only time we call + * this is when the socket is closed?? + */ + if (comm_session->conn_except_notifier + != NULL) { + comm_session->conn_except_notifier( + comm_session + ->session_data, + comm_session + ->socket_fd); + } + + /* stop reading from the socket if its + * closed */ + pthread_mutex_lock( + &(socket_comm_handle + ->socket_comm_mutex)); + ordered_list_remove_first_node_equals( + socket_comm_handle->read_list, + comm_session); + pthread_mutex_unlock( + &(socket_comm_handle + ->socket_comm_mutex)); + } + } else if (received_bytes < 0) { + /* TODO should we call conn_except_notifier() + * here ? */ + pcep_log( + LOG_WARNING, + "%s: Error on socket fd [%d] : errno [%d][%s]", + __func__, comm_session->socket_fd, + errno, strerror(errno)); + } else { + comm_session->received_bytes = received_bytes; + } + } + } +} + + +void handle_writes(pcep_socket_comm_handle *socket_comm_handle) +{ + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + + /* + * iterate all the socket_fd's in the write_list. it may be that not + * all of them are ready to be written to. only remove the socket_fd + * from the list if it is ready to be written to. + */ + + ordered_list_node *node = socket_comm_handle->write_list->head; + pcep_socket_comm_session *comm_session; + bool msg_written; + while (node != NULL) { + comm_session = (pcep_socket_comm_session *)node->data; + node = node->next_node; + msg_written = false; + + if (!comm_session_exists(socket_comm_handle, comm_session)) { + /* This comm_session has been deleted, move on to the + * next one */ + continue; + } + + if (FD_ISSET(comm_session->socket_fd, + &(socket_comm_handle->write_master_set))) { + /* only remove the entry from the list, if it is written + * to */ + ordered_list_remove_first_node_equals( + socket_comm_handle->write_list, comm_session); + FD_CLR(comm_session->socket_fd, + &(socket_comm_handle->write_master_set)); + + /* dequeue all the comm_session messages and send them + */ + pcep_socket_comm_queued_message *queued_message = + queue_dequeue(comm_session->message_queue); + while (queued_message != NULL) { + msg_written = true; + write_message(comm_session->socket_fd, + queued_message->encoded_message, + queued_message->msg_length); + if (queued_message->free_after_send) { + pceplib_free(PCEPLIB_MESSAGES, + (void *)queued_message + ->encoded_message); + } + pceplib_free(PCEPLIB_MESSAGES, queued_message); + queued_message = queue_dequeue( + comm_session->message_queue); + } + } + + /* check if the socket should be closed after writing */ + if (comm_session->close_after_write == true) { + if (comm_session->message_queue->num_entries == 0) { + /* TODO check to make sure modifying the + * write_list while iterating it doesn't cause + * problems. */ + pcep_log( + LOG_DEBUG, + "%s: handle_writes close() socket fd [%d]", + __func__, comm_session->socket_fd); + ordered_list_remove_first_node_equals( + socket_comm_handle->read_list, + comm_session); + ordered_list_remove_first_node_equals( + socket_comm_handle->write_list, + comm_session); + close(comm_session->socket_fd); + comm_session->socket_fd = -1; + } + } + + if (comm_session->message_sent_handler != NULL + && msg_written == true) { + /* Unlocking to allow the message_sent_handler to + * make calls like destroy_socket_comm_session */ + pthread_mutex_unlock( + &(socket_comm_handle->socket_comm_mutex)); + comm_session->message_sent_handler( + comm_session->session_data, + comm_session->socket_fd); + pthread_mutex_lock( + &(socket_comm_handle->socket_comm_mutex)); + } + } + + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); +} + + +void handle_excepts(pcep_socket_comm_handle *socket_comm_handle) +{ + /* TODO finish this */ + (void)socket_comm_handle; +} + + +/* pcep_socket_comm::initialize_socket_comm_loop() will create a thread and + * invoke this method */ +void *socket_comm_loop(void *data) +{ + if (data == NULL) { + pcep_log( + LOG_WARNING, + "%s: Cannot start socket_comm_loop with NULL pcep_socketcomm_handle", + __func__); + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting socket_comm_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)data; + struct timeval timer; + int max_fd; + + while (socket_comm_handle->active) { + /* check the FD's every 1/4 sec, 250 milliseconds */ + timer.tv_sec = 0; + timer.tv_usec = 250000; + max_fd = build_fd_sets(socket_comm_handle); + + if (select(max_fd, &(socket_comm_handle->read_master_set), + &(socket_comm_handle->write_master_set), + &(socket_comm_handle->except_master_set), &timer) + < 0) { + /* TODO handle the error */ + pcep_log( + LOG_WARNING, + "%s: ERROR socket_comm_loop on select : errno [%d][%s]", + __func__, errno, strerror(errno)); + } + + handle_reads(socket_comm_handle); + handle_writes(socket_comm_handle); + handle_excepts(socket_comm_handle); + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Finished socket_comm_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} + +int pceplib_external_socket_read(int fd, void *payload) +{ + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)payload; + if (socket_comm_handle == NULL) { + return -1; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + FD_SET(fd, &(socket_comm_handle->read_master_set)); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + handle_reads(socket_comm_handle); + + /* Get the socket_comm_session */ + pcep_socket_comm_session find_session = {.socket_fd = fd}; + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + ordered_list_node *node = + ordered_list_find(socket_comm_handle->read_list, &find_session); + + /* read again */ + if (node != NULL) { + socket_comm_handle->socket_read_func( + socket_comm_handle->external_infra_data, + &((pcep_socket_comm_session *)node) + ->external_socket_data, + fd, socket_comm_handle); + } + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + return 0; +} + +int pceplib_external_socket_write(int fd, void *payload) +{ + pcep_socket_comm_handle *socket_comm_handle = + (pcep_socket_comm_handle *)payload; + if (socket_comm_handle == NULL) { + return -1; + } + + pthread_mutex_lock(&(socket_comm_handle->socket_comm_mutex)); + FD_SET(fd, &(socket_comm_handle->write_master_set)); + pthread_mutex_unlock(&(socket_comm_handle->socket_comm_mutex)); + + handle_writes(socket_comm_handle); + + /* TODO do we need to cancel this FD from writing?? */ + + return 0; +} diff --git a/pceplib/pcep_socket_comm_loop.h b/pceplib/pcep_socket_comm_loop.h new file mode 100644 index 0000000..3c68efc --- /dev/null +++ b/pceplib/pcep_socket_comm_loop.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEPSOCKETCOMMLOOP_H_ +#define PCEPSOCKETCOMMLOOP_H_ + +void handle_reads(pcep_socket_comm_handle *socket_comm_handle); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/pcep_socket_comm_mock.c b/pceplib/pcep_socket_comm_mock.c new file mode 100644 index 0000000..bda9b1f --- /dev/null +++ b/pceplib/pcep_socket_comm_mock.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * This module is built into a separate library, and is used by several + * other modules for unit testing, so that real sockets dont have to be + * created. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_mock.h" +#include "pcep_utils_queue.h" + +/* reset_mock_socket_comm_info() should be used before each test */ +mock_socket_comm_info mock_socket_metadata; + +void setup_mock_socket_comm_info(void) +{ + mock_socket_metadata.socket_comm_session_initialize_times_called = 0; + mock_socket_metadata.socket_comm_session_initialize_src_times_called = + 0; + mock_socket_metadata.socket_comm_session_teardown_times_called = 0; + mock_socket_metadata.socket_comm_session_connect_tcp_times_called = 0; + mock_socket_metadata.socket_comm_session_send_message_times_called = 0; + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called = 0; + mock_socket_metadata.socket_comm_session_close_tcp_times_called = 0; + mock_socket_metadata.destroy_socket_comm_loop_times_called = 0; + mock_socket_metadata.send_message_save_message = false; + mock_socket_metadata.sent_message_list = dll_initialize(); +} + +void teardown_mock_socket_comm_info(void) +{ + dll_destroy(mock_socket_metadata.sent_message_list); +} + +void reset_mock_socket_comm_info(void) +{ + teardown_mock_socket_comm_info(); + setup_mock_socket_comm_info(); +} + +mock_socket_comm_info *get_mock_socket_comm_info(void) +{ + return &mock_socket_metadata; +} + +void verify_socket_comm_times_called(int initialized, int teardown, int connect, + int send_message, + int close_tcp_after_write, int close_tcp, + int destroy) +{ + CU_ASSERT_EQUAL(initialized, + mock_socket_metadata + .socket_comm_session_initialize_times_called); + CU_ASSERT_EQUAL( + teardown, + mock_socket_metadata.socket_comm_session_teardown_times_called); + CU_ASSERT_EQUAL(connect, + mock_socket_metadata + .socket_comm_session_connect_tcp_times_called); + CU_ASSERT_EQUAL(send_message, + mock_socket_metadata + .socket_comm_session_send_message_times_called); + CU_ASSERT_EQUAL( + close_tcp_after_write, + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called); + CU_ASSERT_EQUAL(close_tcp, + mock_socket_metadata + .socket_comm_session_close_tcp_times_called); + CU_ASSERT_EQUAL( + destroy, + mock_socket_metadata.destroy_socket_comm_loop_times_called); +} + + +/* + * Mock the socket_comm functions used by session_logic for Unit Testing + */ + +bool initialize_socket_comm_external_infra( + void *external_infra_data, ext_socket_read socket_read_cb, + ext_socket_write socket_write_cb, + ext_socket_pthread_create_callback thread_create_func) +{ + (void)external_infra_data; + (void)socket_read_cb; + (void)socket_write_cb; + (void)thread_create_func; + + mock_socket_metadata + .socket_comm_initialize_external_infra_times_called++; + + return true; +} + +bool destroy_socket_comm_loop(void) +{ + mock_socket_metadata.destroy_socket_comm_loop_times_called++; + + return false; +} + +pcep_socket_comm_session * +socket_comm_session_initialize(message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, + struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, + const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = false; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = AF_INET; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dst_port); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr.s_addr = + dst_ip->s_addr; + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *dst_ip, + short dst_port, uint32_t connect_timeout_millis, + const char *tcp_authentication_str, bool is_tcp_auth_md5, + void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = true; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dst_port); + memcpy(&comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_addr, + dst_ip, sizeof(struct in6_addr)); + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in_addr *src_ip, + short src_port, struct in_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_src_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = false; + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_family = AF_INET; + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_port = + htons(src_port); + comm_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr = + ((src_ip == NULL) ? INADDR_ANY : src_ip->s_addr); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_family = AF_INET; + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_port = + htons(dst_port); + comm_session->dest_sock_addr.dest_sock_addr_ipv4.sin_addr.s_addr = + dst_ip->s_addr; + + return comm_session; +} + +pcep_socket_comm_session *socket_comm_session_initialize_with_src_ipv6( + message_received_handler msg_rcv_handler, + message_ready_to_read_handler msg_ready_handler, + message_sent_notifier msg_sent_notifier, + connection_except_notifier notifier, struct in6_addr *src_ip, + short src_port, struct in6_addr *dst_ip, short dst_port, + uint32_t connect_timeout_millis, const char *tcp_authentication_str, + bool is_tcp_auth_md5, void *session_data) +{ + (void)msg_sent_notifier; + (void)tcp_authentication_str; + (void)is_tcp_auth_md5; + + mock_socket_metadata.socket_comm_session_initialize_src_times_called++; + + pcep_socket_comm_session *comm_session = + malloc(sizeof(pcep_socket_comm_session)); + memset(comm_session, 0, sizeof(pcep_socket_comm_session)); + + comm_session->message_handler = msg_rcv_handler; + comm_session->message_ready_to_read_handler = msg_ready_handler; + comm_session->conn_except_notifier = notifier; + comm_session->message_queue = queue_initialize(); + comm_session->session_data = session_data; + comm_session->close_after_write = false; + comm_session->connect_timeout_millis = connect_timeout_millis; + comm_session->is_ipv6 = true; + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_port = + htons(src_port); + if (src_ip == NULL) { + comm_session->src_sock_addr.src_sock_addr_ipv6.sin6_addr = + in6addr_any; + } else { + memcpy(&comm_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + src_ip, sizeof(struct in6_addr)); + } + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_family = AF_INET6; + comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_port = + htons(dst_port); + memcpy(&comm_session->dest_sock_addr.dest_sock_addr_ipv6.sin6_addr, + dst_ip, sizeof(struct in6_addr)); + + return comm_session; +} + +bool socket_comm_session_teardown(pcep_socket_comm_session *socket_comm_session) +{ + mock_socket_metadata.socket_comm_session_teardown_times_called++; + + if (socket_comm_session != NULL) { + queue_destroy(socket_comm_session->message_queue); + free(socket_comm_session); + } + + return true; +} + + +bool socket_comm_session_connect_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata.socket_comm_session_connect_tcp_times_called++; + + return true; +} + + +void socket_comm_session_send_message( + pcep_socket_comm_session *socket_comm_session, + const char *encoded_message, unsigned int msg_length, + bool delete_after_send) +{ + (void)socket_comm_session; + (void)msg_length; + + mock_socket_metadata.socket_comm_session_send_message_times_called++; + + if (mock_socket_metadata.send_message_save_message == true) { + /* the caller/test case is responsible for freeing the message + */ + dll_append(mock_socket_metadata.sent_message_list, + (char *)encoded_message); + } else { + if (delete_after_send == true) { + free((void *)encoded_message); + } + } + + return; +} + + +bool socket_comm_session_close_tcp_after_write( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata + .socket_comm_session_close_tcp_after_write_times_called++; + + return true; +} + + +bool socket_comm_session_close_tcp( + pcep_socket_comm_session *socket_comm_session) +{ + (void)socket_comm_session; + + mock_socket_metadata.socket_comm_session_close_tcp_times_called++; + + return true; +} diff --git a/pceplib/pcep_socket_comm_mock.h b/pceplib/pcep_socket_comm_mock.h new file mode 100644 index 0000000..1290b8b --- /dev/null +++ b/pceplib/pcep_socket_comm_mock.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +/* + * This module is built into a separate library, and is used by several + * other modules for unit testing, so that real sockets dont have to be + * created. + */ + +#ifndef PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ +#define PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ + +#include + +#include "pcep_utils_double_linked_list.h" + +typedef struct mock_socket_comm_info_ { + int socket_comm_initialize_external_infra_times_called; + int socket_comm_session_initialize_times_called; + int socket_comm_session_initialize_src_times_called; + int socket_comm_session_teardown_times_called; + int socket_comm_session_connect_tcp_times_called; + int socket_comm_session_send_message_times_called; + int socket_comm_session_close_tcp_after_write_times_called; + int socket_comm_session_close_tcp_times_called; + int destroy_socket_comm_loop_times_called; + + /* TODO later if necessary, we can add return values for + * those functions that return something */ + + /* Used to access messages sent with socket_comm_session_send_message() + */ + bool send_message_save_message; + double_linked_list *sent_message_list; + +} mock_socket_comm_info; + +void setup_mock_socket_comm_info(void); +void teardown_mock_socket_comm_info(void); +void reset_mock_socket_comm_info(void); +bool destroy_socket_comm_loop(void); + +mock_socket_comm_info *get_mock_socket_comm_info(void); +void verify_socket_comm_times_called(int initialized, int teardown, int connect, + int send_message, int close_after_write, + int close, int destroy); + +#endif /* PCEP_SOCKET_COMM_MOCK_SOCKET_COMM_H_ */ diff --git a/pceplib/pcep_timer_internals.h b/pceplib/pcep_timer_internals.h new file mode 100644 index 0000000..a10243c --- /dev/null +++ b/pceplib/pcep_timer_internals.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEPTIMERINTERNALS_H_ +#define PCEPTIMERINTERNALS_H_ + +#include +#include + +#include "pcep_utils_ordered_list.h" + +/* Function pointer to be called when timers expire. + * Parameters: + * void *data - passed into create_timer + * int timer_id - the timer_id returned by create_timer + */ +typedef void (*timer_expire_handler)(void *, int); + +/* Function pointer when an external timer infrastructure is used */ +typedef void (*ext_timer_create)(void *infra_data, void **timer, int seconds, + void *data); +typedef void (*ext_timer_cancel)(void **timer); +typedef int (*ext_pthread_create_callback)(pthread_t *pthread_id, + const pthread_attr_t *attr, + void *(*start_routine)(void *), + void *data, const char *thread_name); + +typedef struct pcep_timer_ { + time_t expire_time; + uint16_t sleep_seconds; + int timer_id; + void *data; + void *external_timer; + +} pcep_timer; + +typedef struct pcep_timers_context_ { + ordered_list_handle *timer_list; + bool active; + timer_expire_handler expire_handler; + pthread_t event_loop_thread; + pthread_mutex_t timer_list_lock; + void *external_timer_infra_data; + ext_timer_create timer_create_func; + ext_timer_cancel timer_cancel_func; + +} pcep_timers_context; + +/* functions implemented in pcep_timers_loop.c */ +void *event_loop(void *context); + + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/pcep_timers.c b/pceplib/pcep_timers.c new file mode 100644 index 0000000..79ae563 --- /dev/null +++ b/pceplib/pcep_timers.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of public API timer functions. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" + +static pcep_timers_context *timers_context_ = NULL; +static int timer_id_ = 0; + + +/* simple compare method callback used by pcep_utils_ordered_list + * for ordered list insertion. */ +int timer_list_node_compare(void *list_entry, void *new_entry) +{ + /* return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry */ + return ((pcep_timer *)new_entry)->expire_time + - ((pcep_timer *)list_entry)->expire_time; +} + + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its timer_id. */ +int timer_list_node_timer_id_compare(void *list_entry, void *new_entry) +{ + return ((pcep_timer *)new_entry)->timer_id + - ((pcep_timer *)list_entry)->timer_id; +} + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its address. */ +int timer_list_node_timer_ptr_compare(void *list_entry, void *new_entry) +{ + return ((char *)new_entry - (char *)list_entry); +} + +/* internal util method */ +static pcep_timers_context *create_timers_context_(void) +{ + if (timers_context_ == NULL) { + timers_context_ = pceplib_malloc(PCEPLIB_INFRA, + sizeof(pcep_timers_context)); + memset(timers_context_, 0, sizeof(pcep_timers_context)); + timers_context_->active = false; + } + + return timers_context_; +} + + +/* Internal util function */ +static bool initialize_timers_common(timer_expire_handler expire_handler) +{ + if (expire_handler == NULL) { + /* Cannot have a NULL handler function */ + return false; + } + + timers_context_ = create_timers_context_(); + + if (timers_context_->active == true) { + /* already initialized */ + return false; + } + + timers_context_->active = true; + timers_context_->timer_list = + ordered_list_initialize(timer_list_node_compare); + timers_context_->expire_handler = expire_handler; + + if (pthread_mutex_init(&(timers_context_->timer_list_lock), NULL) + != 0) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the mutex", + __func__); + return false; + } + + return true; +} + +bool initialize_timers(timer_expire_handler expire_handler) +{ + if (initialize_timers_common(expire_handler) == false) { + return false; + } + + if (pthread_create(&(timers_context_->event_loop_thread), NULL, + event_loop, timers_context_)) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the thread", + __func__); + return false; + } + + return true; +} + +bool initialize_timers_external_infra( + timer_expire_handler expire_handler, void *external_timer_infra_data, + ext_timer_create timer_create_func, ext_timer_cancel timer_cancel_func, + ext_pthread_create_callback thread_create_func) +{ + if (initialize_timers_common(expire_handler) == false) { + return false; + } + + if (thread_create_func != NULL) { + if (thread_create_func(&(timers_context_->event_loop_thread), + NULL, event_loop, timers_context_, + "pceplib_timers")) { + pcep_log( + LOG_ERR, + "%s: Cannot initialize external timers thread.", + __func__); + return false; + } + } else { + if (pthread_create(&(timers_context_->event_loop_thread), NULL, + event_loop, timers_context_)) { + pcep_log( + LOG_ERR, + "%s: ERROR initializing timers, cannot initialize the thread", + __func__); + return false; + } + } + + timers_context_->external_timer_infra_data = external_timer_infra_data; + timers_context_->timer_create_func = timer_create_func; + timers_context_->timer_cancel_func = timer_cancel_func; + + return true; +} + +/* + * This function is only used to tear_down the timer data. + * Only the timer data is deleted, not the list itself, + * which is deleted by ordered_list_destroy(). + */ +void free_all_timers(pcep_timers_context *timers_context) +{ + pthread_mutex_lock(&timers_context->timer_list_lock); + + ordered_list_node *timer_node = timers_context->timer_list->head; + + while (timer_node != NULL) { + if (timer_node->data != NULL) { + pceplib_free(PCEPLIB_INFRA, timer_node->data); + } + timer_node = timer_node->next_node; + } + + pthread_mutex_unlock(&timers_context->timer_list_lock); +} + + +bool teardown_timers(void) +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, but they are not initialized", + __func__); + return false; + } + + if (timers_context_->active == false) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, but they are not active", + __func__); + return false; + } + + timers_context_->active = false; + if (timers_context_->event_loop_thread != 0) { + /* TODO this does not build + * Instead of calling pthread_join() which could block if the + thread + * is blocked, try joining for at most 1 second. + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + int retval = + pthread_timedjoin_np(timers_context_->event_loop_thread, NULL, + &ts); if (retval != 0) + { + pcep_log(LOG_WARNING, "%s: thread did not stop after 1 + second waiting on it.", __func__); + } + */ + pthread_join(timers_context_->event_loop_thread, NULL); + } + + free_all_timers(timers_context_); + ordered_list_destroy(timers_context_->timer_list); + + if (pthread_mutex_destroy(&(timers_context_->timer_list_lock)) != 0) { + pcep_log( + LOG_WARNING, + "%s: Trying to teardown the timers, cannot destroy the mutex", + __func__); + } + + pceplib_free(PCEPLIB_INFRA, timers_context_); + timers_context_ = NULL; + + return true; +} + + +int get_next_timer_id(void) +{ + if (timer_id_ == INT_MAX) { + timer_id_ = 0; + } + + return timer_id_++; +} + +int create_timer(uint16_t sleep_seconds, void *data) +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to create a timer: the timers have not been initialized", + __func__); + return -1; + } + + pcep_timer *timer = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timer)); + memset(timer, 0, sizeof(pcep_timer)); + timer->data = data; + timer->sleep_seconds = sleep_seconds; + timer->expire_time = time(NULL) + sleep_seconds; + + pthread_mutex_lock(&timers_context_->timer_list_lock); + timer->timer_id = get_next_timer_id(); + + /* implemented in pcep_utils_ordered_list.c */ + if (ordered_list_add_node(timers_context_->timer_list, timer) == NULL) { + pceplib_free(PCEPLIB_INFRA, timer); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to create a timer, cannot add the timer to the timer list", + __func__); + + return -1; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_create_func) { + timers_context_->timer_create_func( + timers_context_->external_timer_infra_data, + &timer->external_timer, sleep_seconds, timer); + } + + return timer->timer_id; +} + + +bool cancel_timer(int timer_id) +{ + static pcep_timer compare_timer; + + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to cancel a timer: the timers have not been initialized", + __func__); + return false; + } + + pthread_mutex_lock(&timers_context_->timer_list_lock); + + compare_timer.timer_id = timer_id; + pcep_timer *timer_toRemove = ordered_list_remove_first_node_equals2( + timers_context_->timer_list, &compare_timer, + timer_list_node_timer_id_compare); + if (timer_toRemove == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to cancel a timer [%d] that does not exist", + __func__, timer_id); + return false; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_cancel_func) { + timers_context_->timer_cancel_func( + &timer_toRemove->external_timer); + } + + pceplib_free(PCEPLIB_INFRA, timer_toRemove); + + return true; +} + + +bool reset_timer(int timer_id) +{ + static pcep_timer compare_timer; + + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: Trying to reset a timer: the timers have not been initialized", + __func__); + + return false; + } + + pthread_mutex_lock(&timers_context_->timer_list_lock); + + compare_timer.timer_id = timer_id; + ordered_list_node *timer_to_reset_node = + ordered_list_find2(timers_context_->timer_list, &compare_timer, + timer_list_node_timer_id_compare); + if (timer_to_reset_node == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log(LOG_WARNING, + "%s: Trying to reset a timer node that does not exist", + __func__); + + return false; + } + + pcep_timer *timer_to_reset = timer_to_reset_node->data; + if (timer_to_reset == NULL) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log(LOG_WARNING, + "%s: Trying to reset a timer that does not exist", + __func__); + + return false; + } + + /* First check if the timer to reset already has the same expire time, + * which means multiple reset_timer() calls were made on the same timer + * in the same second */ + time_t expire_time = time(NULL) + timer_to_reset->sleep_seconds; + if (timer_to_reset->expire_time == expire_time) { + pthread_mutex_unlock(&timers_context_->timer_list_lock); + return true; + } + + ordered_list_remove_node2(timers_context_->timer_list, + timer_to_reset_node); + + timer_to_reset->expire_time = expire_time; + if (ordered_list_add_node(timers_context_->timer_list, timer_to_reset) + == NULL) { + pceplib_free(PCEPLIB_INFRA, timer_to_reset); + pthread_mutex_unlock(&timers_context_->timer_list_lock); + pcep_log( + LOG_WARNING, + "%s: Trying to reset a timer, cannot add the timer to the timer list", + __func__); + + return false; + } + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + if (timers_context_->timer_cancel_func) { + /* Keeping this log for now, since in older versions of FRR the + * timer cancellation was blocking. This allows us to see how + * long the it takes.*/ + pcep_log(LOG_DEBUG, "%s: Resetting timer [%d] with callback", + __func__, timer_to_reset->timer_id); + timers_context_->timer_cancel_func( + &timer_to_reset->external_timer); + timer_to_reset->external_timer = NULL; + } + + if (timers_context_->timer_create_func) { + timers_context_->timer_create_func( + timers_context_->external_timer_infra_data, + &timer_to_reset->external_timer, + timer_to_reset->sleep_seconds, timer_to_reset); + /* Keeping this log for now, since in older versions of FRR the + * timer cancellation was blocking. This allows us to see how + * long the it takes.*/ + pcep_log(LOG_DEBUG, "%s: Reset timer [%d] with callback", + __func__, timer_to_reset->timer_id); + } + + return true; +} + + +void pceplib_external_timer_expire_handler(void *data) +{ + if (timers_context_ == NULL) { + pcep_log( + LOG_WARNING, + "%s: External timer expired but timers_context is not initialized", + __func__); + return; + } + + if (timers_context_->expire_handler == NULL) { + pcep_log( + LOG_WARNING, + "%s: External timer expired but expire_handler is not initialized", + __func__); + return; + } + + if (data == NULL) { + pcep_log(LOG_WARNING, + "%s: External timer expired with NULL data", __func__); + return; + } + + pcep_timer *timer = (pcep_timer *)data; + + pthread_mutex_lock(&timers_context_->timer_list_lock); + ordered_list_node *timer_node = + ordered_list_find2(timers_context_->timer_list, timer, + timer_list_node_timer_ptr_compare); + + /* Remove timer from list */ + if (timer_node) + ordered_list_remove_node2(timers_context_->timer_list, + timer_node); + + pthread_mutex_unlock(&timers_context_->timer_list_lock); + + /* Cannot continue if the timer does not exist */ + if (timer_node == NULL) { + pcep_log( + LOG_WARNING, + "%s: pceplib_external_timer_expire_handler timer [%p] id [%d] does not exist", + __func__, timer, timer->timer_id); + return; + } + + timers_context_->expire_handler(timer->data, timer->timer_id); + + pceplib_free(PCEPLIB_INFRA, timer); +} diff --git a/pceplib/pcep_timers.h b/pceplib/pcep_timers.h new file mode 100644 index 0000000..bbd21bd --- /dev/null +++ b/pceplib/pcep_timers.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Public API for pcep_timers + */ + +#ifndef PCEPTIMERS_H_ +#define PCEPTIMERS_H_ + +#include +#include + +#include "pcep_timer_internals.h" + +#define TIMER_ID_NOT_SET -1 + +/* + * Initialize the timers module. + * The timer_expire_handler function pointer will be called each time a timer + * expires. Return true for successful initialization, false otherwise. + */ +bool initialize_timers(timer_expire_handler expire_handler); + +/* + * Initialize the timers module with an external back-end infrastructure, like + * FRR. + */ +bool initialize_timers_external_infra( + timer_expire_handler expire_handler, void *external_timer_infra_data, + ext_timer_create timer_create_func, ext_timer_cancel timer_cancel_func, + ext_pthread_create_callback thread_create_func); + +/* + * Teardown the timers module. + */ +bool teardown_timers(void); + +/* + * Create a new timer for "sleep_seconds" seconds. + * If the timer expires before being cancelled, the timer_expire_handler + * passed to initialize_timers() will be called with the pointer to "data". + * Returns a timer_id <= 0 that can be used to cancel_timer. + * Returns < 0 on error. + */ +int create_timer(uint16_t sleep_seconds, void *data); + +/* + * Cancel a timer created with create_timer(). + * Returns true if the timer was found and cancelled, false otherwise. + */ +bool cancel_timer(int timer_id); + +/* + * Reset an previously created timer, maintaining the same timer_id. + * Returns true if the timer was found and reset, false otherwise. + */ +bool reset_timer(int timer_id); + +/* + * If an external timer infra like FRR is used, then this function + * will be called when the timers expire in the external infra. + */ +void pceplib_external_timer_expire_handler(void *data); + +int timer_list_node_compare(void *list_entry, void *new_entry); +int timer_list_node_timer_id_compare(void *list_entry, void *new_entry); +int timer_list_node_timer_ptr_compare(void *list_entry, void *new_entry); +void free_all_timers(pcep_timers_context *timers_context); +int get_next_timer_id(void); + +#endif /* PCEPTIMERS_H_ */ diff --git a/pceplib/pcep_timers_event_loop.c b/pceplib/pcep_timers_event_loop.c new file mode 100644 index 0000000..ce75dde --- /dev/null +++ b/pceplib/pcep_timers_event_loop.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "pcep_timers_event_loop.h" +#include "pcep_timer_internals.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* For each expired timer: remove the timer from the list, call the + * expire_handler, and free the timer. */ +void walk_and_process_timers(pcep_timers_context *timers_context) +{ + pthread_mutex_lock(&timers_context->timer_list_lock); + + bool keep_walking = true; + ordered_list_node *timer_node = timers_context->timer_list->head; + time_t now = time(NULL); + pcep_timer *timer_data; + + /* the timers are sorted by expire_time, so we will only + * remove the top node each time through the loop */ + while (timer_node != NULL && keep_walking) { + timer_data = (pcep_timer *)timer_node->data; + if (timer_data->expire_time <= now) { + timer_node = timer_node->next_node; + ordered_list_remove_first_node( + timers_context->timer_list); + /* call the timer expired handler */ + timers_context->expire_handler(timer_data->data, + timer_data->timer_id); + pceplib_free(PCEPLIB_INFRA, timer_data); + } else { + keep_walking = false; + } + } + + pthread_mutex_unlock(&timers_context->timer_list_lock); +} + + +/* pcep_timers::initialize() will create a thread and invoke this method */ +void *event_loop(void *context) +{ + if (context == NULL) { + pcep_log( + LOG_WARNING, + "%s: pcep_timers_event_loop cannot start event_loop with NULL data", + __func__); + return NULL; + } + + pcep_log(LOG_NOTICE, "%s: [%ld-%ld] Starting timers_event_loop thread", + __func__, time(NULL), pthread_self()); + + pcep_timers_context *timers_context = (pcep_timers_context *)context; + struct timeval timer; + int retval; + + while (timers_context->active) { + /* check the timers every half second */ + timer.tv_sec = 0; + timer.tv_usec = 500000; + + do { + /* if the select() call gets interrupted, select() will + * set the remaining time in timer, so we need to call + * it again. + */ + retval = select(0, NULL, NULL, NULL, &timer); + } while (retval != 0 && errno == EINTR); + + walk_and_process_timers(timers_context); + } + + pcep_log(LOG_WARNING, "%s: [%ld-%ld] Finished timers_event_loop thread", + __func__, time(NULL), pthread_self()); + + return NULL; +} diff --git a/pceplib/pcep_timers_event_loop.h b/pceplib/pcep_timers_event_loop.h new file mode 100644 index 0000000..88d8d14 --- /dev/null +++ b/pceplib/pcep_timers_event_loop.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_EVENT_LOOP_H_ +#define PCEP_TIMERS_EVENT_LOOP_H_ + +#include "pcep_timer_internals.h" + +void walk_and_process_timers(pcep_timers_context *timers_context); + +#endif diff --git a/pceplib/pcep_utils_counters.c b/pceplib/pcep_utils_counters.c new file mode 100644 index 0000000..1ab341c --- /dev/null +++ b/pceplib/pcep_utils_counters.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Implementation of PCEP Counters. + */ + +#include + +#include +#include +#include + +#include "pcep_utils_counters.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +struct counters_group *create_counters_group(const char *group_name, + uint16_t max_subgroups) +{ + if (group_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters group: group_name is NULL.", + __func__); + return NULL; + } + + if (max_subgroups > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters group: max_subgroups [%d] is larger than max the [%d].", + __func__, max_subgroups, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_group *group = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_group)); + memset(group, 0, sizeof(struct counters_group)); + group->subgroups = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_subgroup *) + * (max_subgroups + 1)); + memset(group->subgroups, 0, + sizeof(struct counters_subgroup *) * (max_subgroups + 1)); + + strlcpy(group->counters_group_name, group_name, + sizeof(group->counters_group_name)); + group->max_subgroups = max_subgroups; + group->start_time = time(NULL); + + return group; +} + +struct counters_subgroup *create_counters_subgroup(const char *subgroup_name, + uint16_t subgroup_id, + uint16_t max_counters) +{ + if (subgroup_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: subgroup_name is NULL.", + __func__); + return NULL; + } + + if (max_counters > MAX_COUNTERS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: max_counters [%d] is larger than the max [%d].", + __func__, max_counters, MAX_COUNTERS); + return NULL; + } + + if (subgroup_id > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot create counters subgroup: subgroup_id [%d] is larger than max the [%d].", + __func__, subgroup_id, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_subgroup *subgroup = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counters_subgroup)); + memset(subgroup, 0, sizeof(struct counters_subgroup)); + subgroup->counters = pceplib_malloc( + PCEPLIB_INFRA, sizeof(struct counter *) * (max_counters + 1)); + memset(subgroup->counters, 0, + sizeof(struct counter *) * (max_counters + 1)); + + strlcpy(subgroup->counters_subgroup_name, subgroup_name, + sizeof(subgroup->counters_subgroup_name)); + subgroup->subgroup_id = subgroup_id; + subgroup->max_counters = max_counters; + + return subgroup; +} + +struct counters_subgroup * +clone_counters_subgroup(struct counters_subgroup *subgroup, + const char *subgroup_name, uint16_t subgroup_id) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: input counters_subgroup is NULL.", + __func__); + return NULL; + } + + if (subgroup_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: subgroup_name is NULL.", + __func__); + return NULL; + } + + if (subgroup_id > MAX_COUNTER_GROUPS) { + pcep_log( + LOG_INFO, + "%s: Cannot clone counters subgroup: subgroup_id [%d] is larger than max the [%d].", + __func__, subgroup_id, MAX_COUNTER_GROUPS); + return NULL; + } + + struct counters_subgroup *cloned_subgroup = create_counters_subgroup( + subgroup_name, subgroup_id, subgroup->max_counters); + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + create_subgroup_counter(cloned_subgroup, + counter->counter_id, + counter->counter_name, + counter->counter_name_json); + } + } + + return cloned_subgroup; +} + +bool add_counters_subgroup(struct counters_group *group, + struct counters_subgroup *subgroup) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_group is NULL.", + __func__); + return false; + } + + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_subgroup is NULL.", + __func__); + return false; + } + + if (subgroup->subgroup_id >= group->max_subgroups) { + pcep_log( + LOG_INFO, + "%s: Cannot add counters subgroup: counters_subgroup id [%d] is larger than the group max_subgroups [%d].", + __func__, subgroup->subgroup_id, group->max_subgroups); + return false; + } + + group->num_subgroups++; + group->subgroups[subgroup->subgroup_id] = subgroup; + + return true; +} + +bool create_subgroup_counter(struct counters_subgroup *subgroup, + uint32_t counter_id, const char *counter_name, + const char *counter_name_json) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counters_subgroup is NULL.", + __func__); + return false; + } + + if (counter_id >= subgroup->max_counters) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counter_id [%d] is larger than the subgroup max_counters [%d].", + __func__, counter_id, subgroup->max_counters); + return false; + } + + if (counter_name == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot create subgroup counter: counter_name is NULL.", + __func__); + return NULL; + } + + struct counter *counter = + pceplib_malloc(PCEPLIB_INFRA, sizeof(struct counter)); + memset(counter, 0, sizeof(struct counter)); + counter->counter_id = counter_id; + strlcpy(counter->counter_name, counter_name, + sizeof(counter->counter_name)); + if (counter_name_json) + strlcpy(counter->counter_name_json, counter_name_json, + sizeof(counter->counter_name_json)); + subgroup->num_counters++; + subgroup->counters[counter->counter_id] = counter; + + return true; +} + +bool delete_counters_group(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot delete group counters: counters_group is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + delete_counters_subgroup(subgroup); + } + } + + pceplib_free(PCEPLIB_INFRA, group->subgroups); + pceplib_free(PCEPLIB_INFRA, group); + + return true; +} + +bool delete_counters_subgroup(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL || subgroup->counters == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot delete subgroup counters: counters_subgroup is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + pceplib_free(PCEPLIB_INFRA, counter); + } + } + + pceplib_free(PCEPLIB_INFRA, subgroup->counters); + pceplib_free(PCEPLIB_INFRA, subgroup); + + return true; +} + +bool reset_group_counters(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot reset group counters: counters_group is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + reset_subgroup_counters(subgroup); + } + } + + group->start_time = time(NULL); + + return true; +} + +bool reset_subgroup_counters(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot reset subgroup counters: counters_subgroup is NULL.", + __func__); + return false; + } + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + counter->counter_value = 0; + } + } + + return true; +} + +bool increment_counter(struct counters_group *group, uint16_t subgroup_id, + uint16_t counter_id) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_group is NULL.", + __func__); + return false; + } + + if (subgroup_id >= group->max_subgroups) { + pcep_log( + LOG_DEBUG, + "%s: Cannot increment counter: subgroup_id [%d] is larger than the group max_subgroups [%d].", + __func__, subgroup_id, group->max_subgroups); + return false; + } + + struct counters_subgroup *subgroup = group->subgroups[subgroup_id]; + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_subgroup in counters_group is NULL.", + __func__); + return false; + } + + return increment_subgroup_counter(subgroup, counter_id); +} + +bool increment_subgroup_counter(struct counters_subgroup *subgroup, + uint16_t counter_id) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: counters_subgroup is NULL.", + __func__); + return false; + } + + if (counter_id >= subgroup->max_counters) { + pcep_log( + LOG_DEBUG, + "%s: Cannot increment counter: counter_id [%d] is larger than the subgroup max_counters [%d].", + __func__, counter_id, subgroup->max_counters); + return false; + } + + if (subgroup->counters[counter_id] == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot increment counter: No counter exists for counter_id [%d].", + __func__, counter_id); + return false; + } + + subgroup->counters[counter_id]->counter_value++; + + return true; +} + +bool dump_counters_group_to_log(struct counters_group *group) +{ + if (group == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot dump group counters to log: counters_group is NULL.", + __func__); + return false; + } + + time_t now = time(NULL); + pcep_log( + LOG_INFO, + "%s: PCEP Counters group:\n %s \n Sub-Groups [%d] \n Active for [%d seconds]", + __func__, group->counters_group_name, group->num_subgroups, + (now - group->start_time)); + + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + dump_counters_subgroup_to_log(subgroup); + } + } + + return true; +} + +bool dump_counters_subgroup_to_log(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + pcep_log( + LOG_INFO, + "%s: Cannot dump subgroup counters to log: counters_subgroup is NULL.", + __func__); + return false; + } + + pcep_log(LOG_INFO, + "%s: \tPCEP Counters sub-group [%s] with [%d] counters", + __func__, subgroup->counters_subgroup_name, + subgroup->num_counters); + + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + pcep_log(LOG_INFO, "%s: \t\t%s %d", __func__, + counter->counter_name, counter->counter_value); + } + } + + return true; +} + +struct counters_subgroup *find_subgroup(const struct counters_group *group, + uint16_t subgroup_id) +{ + int i = 0; + for (; i <= group->max_subgroups; i++) { + struct counters_subgroup *subgroup = group->subgroups[i]; + if (subgroup != NULL) { + if (subgroup->subgroup_id == subgroup_id) { + return subgroup; + } + } + } + + return NULL; +} + +uint32_t subgroup_counters_total(struct counters_subgroup *subgroup) +{ + if (subgroup == NULL) { + return 0; + } + uint32_t counter_total = 0; + int i = 0; + for (; i <= subgroup->max_counters; i++) { + struct counter *counter = subgroup->counters[i]; + if (counter != NULL) { + counter_total += counter->counter_value; + } + } + + return counter_total; +} diff --git a/pceplib/pcep_utils_counters.h b/pceplib/pcep_utils_counters.h new file mode 100644 index 0000000..dfae02f --- /dev/null +++ b/pceplib/pcep_utils_counters.h @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +/* + * Definitions of PCEP Counters. + */ + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Example Counter group with sub-groups and counters + * + * pcep_counters { + * counters_group_rx { + * message_open; + * message_keepalive; + * message_pcreq; + * } + * counters_group_tx { + * message_open; + * message_keepalive; + * message_pcreq; + * } + * counters_group_events { + * pcc_connect; + * pce_connect; + * pcc_disconnect; + * pce_disconnect; + * } + * } + * + * To create the above structure of groups, sub-groups, and counters, do the + * following: + * + * struct counters_subgroup *rx_subgroup = create_counters_subgroup("rx + * counters", 3); struct counters_subgroup *tx_subgroup = + * create_counters_subgroup("tx counters", 3); struct counters_subgroup + * *events_subgroup = create_counters_subgroup("events counters", 4); + * + * Use message_id: PCEP_TYPE_OPEN=1 + * create_subgroup_counter(rx_subgroup, 1, "Message Open", "messageOpen"); + * create_subgroup_counter(rx_subgroup, 2, "Message KeepAlive", "messageKeepAlive"); + * create_subgroup_counter(rx_subgroup, 3, "Message PcReq", "messagePcReq"); + * + * create_subgroup_counter(tx_subgroup, 1, "Message Open", "messageOpen"); + * create_subgroup_counter(tx_subgroup, 2, "Message KeepAlive", "messageKeepAlive"); + * create_subgroup_counter(tx_subgroup, 3, "Message PcReq", "messagePcReq"); + * + * create_subgroup_counter(events_subgroup, 1, "PCC Connect", "PCConnect"); + * create_subgroup_counter(events_subgroup, 2, "PCE Connect", "PCEConnect"); + * create_subgroup_counter(events_subgroup, 3, "PCC Disconnect", "PCCDisconnect"); + * create_subgroup_counter(events_subgroup, 4, "PCE Disconnect", "PCEDisconnect"); + * + * struct counters_group *cntrs_group = create_counters_group("PCEP Counters", + * 3); add_counters_subgroup(cntrs_group, rx_subgroup); + * add_counters_subgroup(cntrs_group, tx_subgroup); + * add_counters_subgroup(cntrs_group, events_subgroup); + */ + +#define MAX_COUNTER_STR_LENGTH 128 +#define MAX_COUNTER_GROUPS 500 +#define MAX_COUNTERS 500 + +struct counter { + uint16_t counter_id; + char counter_name[MAX_COUNTER_STR_LENGTH]; + char counter_name_json[MAX_COUNTER_STR_LENGTH]; + uint32_t counter_value; +}; + +struct counters_subgroup { + char counters_subgroup_name[MAX_COUNTER_STR_LENGTH]; + uint16_t subgroup_id; + uint16_t num_counters; + uint16_t max_counters; + /* Array of (struct counter *) allocated when the subgroup is created. + * The array is indexed by the struct counter->counter_id */ + struct counter **counters; +}; + +struct counters_group { + char counters_group_name[MAX_COUNTER_STR_LENGTH]; + uint16_t num_subgroups; + uint16_t max_subgroups; + time_t start_time; + /* Array of (struct counters_subgroup *) allocated when the group is + * created. The subgroup is indexed by the (struct counters_subgroup + * *)->subgroup_id */ + struct counters_subgroup **subgroups; +}; + +/* + * Create a counters group with the given group_name and number of subgroups. + * Subgroup_ids are 0-based, so take that into account when setting + * max_subgroups. Return true on success or false if group_name is NULL or + * max_subgroups >= MAX_COUNTER_GROUPS. + */ +struct counters_group *create_counters_group(const char *group_name, + uint16_t max_subgroups); + +/* + * Create a counters subgroup with the given subgroup_name, subgroup_id and + * number of counters. The subgroup_id is 0-based. counter_ids are 0-based, so + * take that into account when setting max_counters. Return true on success or + * false if subgroup_name is NULL, subgroup_id >= MAX_COUNTER_GROUPS, or + * max_counters >= MAX_COUNTERS. + */ +struct counters_subgroup *create_counters_subgroup(const char *subgroup_name, + uint16_t subgroup_id, + uint16_t max_counters); + +/* + * Add a counter_subgroup to a counter_group. + * Return true on success or false if group is NULL or if subgroup is NULL. + */ +bool add_counters_subgroup(struct counters_group *group, + struct counters_subgroup *subgroup); + +/* + * Clone a subgroup and set a new name and subgroup_id for the new subgroup. + * This is useful for RX and TX counters: just create the RX counters and clone + * it for the TX counters. + */ +struct counters_subgroup * +clone_counters_subgroup(struct counters_subgroup *subgroup, + const char *subgroup_name, uint16_t subgroup_id); + +/* + * Create a counter in a subgroup with the given counter_id and counter_name + * and counter_name_json. + * The counter_id is 0-based. + * Return true on success or false if subgroup is NULL, counter_id >= + * MAX_COUNTERS, or if counter_name is NULL. + */ +bool create_subgroup_counter(struct counters_subgroup *subgroup, + uint32_t counter_id, const char *counter_name, + const char *couter_name_json); + +/* + * Delete the counters_group and recursively delete all subgroups and their + * counters. Return true on success or false if group is NULL. + */ +bool delete_counters_group(struct counters_group *group); + +/* + * Delete the counters_subgroup and all its counters counters. + * Return true on success or false if subgroup is NULL. + */ +bool delete_counters_subgroup(struct counters_subgroup *subgroup); + +/* + * Reset all the counters in all sub-groups contained in this group. + * Return true on success or false if group is NULL. + */ +bool reset_group_counters(struct counters_group *group); + +/* + * Reset all the counters in this subgroup. + * Return true on success or false if subgroup is NULL. + */ +bool reset_subgroup_counters(struct counters_subgroup *subgroup); + +/* + * Increment a counter given a counter_group, subgroup_id, and counter_id. + * Return true on success or false if group is NULL, subgroup_id >= + * MAX_COUNTER_GROUPS, or counter_id >= MAX_COUNTERS. + */ +bool increment_counter(struct counters_group *group, uint16_t subgroup_id, + uint16_t counter_id); + +/* + * Increment a counter given the counter_subgroup and counter_id. + * Return true on success or false if subgroup is NULL or counter_id >= + * MAX_COUNTERS. + */ +bool increment_subgroup_counter(struct counters_subgroup *subgroup, + uint16_t counter_id); + +/* + * Dump the counter_group info and all its counter_subgroups. + * Return true on success or false if group is NULL. + */ +bool dump_counters_group_to_log(struct counters_group *group); + +/* + * Dump all the counters in a counter_subgroup. + * Return true on success or false if subgroup is NULL. + */ +bool dump_counters_subgroup_to_log(struct counters_subgroup *subgroup); + +/* + * Search for a counters_subgroup by subgroup_id in a counters_group + * and return it, if found, else return NULL. + */ +struct counters_subgroup *find_subgroup(const struct counters_group *group, + uint16_t subgroup_id); + +/* + * Given a counters_subgroup, return the sum of all the counters. + */ +uint32_t subgroup_counters_total(struct counters_subgroup *subgroup); + +#ifdef __cplusplus +} +#endif + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_COUNTERS_H_ */ diff --git a/pceplib/pcep_utils_double_linked_list.c b/pceplib/pcep_utils_double_linked_list.c new file mode 100644 index 0000000..2eeffb2 --- /dev/null +++ b/pceplib/pcep_utils_double_linked_list.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +double_linked_list *dll_initialize(void) +{ + double_linked_list *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list)); + if (handle != NULL) { + memset(handle, 0, sizeof(double_linked_list)); + handle->num_entries = 0; + handle->head = NULL; + handle->tail = NULL; + } else { + pcep_log(LOG_WARNING, + "%s: dll_initialize cannot allocate memory for handle", + __func__); + return NULL; + } + + return handle; +} + + +void dll_destroy(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, + "%s: dll_destroy cannot destroy NULL handle", + __func__); + return; + } + + double_linked_list_node *node = handle->head; + while (node != NULL) { + double_linked_list_node *node_to_delete = node; + node = node->next_node; + pceplib_free(PCEPLIB_INFRA, node_to_delete); + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void dll_destroy_with_data_memtype(double_linked_list *handle, + void *data_memory_type) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, + "%s: dll_destroy_with_data cannot destroy NULL handle", + __func__); + return; + } + + double_linked_list_node *node = handle->head; + while (node != NULL) { + double_linked_list_node *node_to_delete = node; + pceplib_free(data_memory_type, node->data); + node = node->next_node; + pceplib_free(PCEPLIB_INFRA, node_to_delete); + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void dll_destroy_with_data(double_linked_list *handle) +{ + /* Default to destroying the data with the INFRA mem type */ + dll_destroy_with_data_memtype(handle, PCEPLIB_INFRA); +} + + +/* Creates a node and adds it as the first item in the list */ +double_linked_list_node *dll_prepend(double_linked_list *handle, void *data) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_prepend_data NULL handle", + __func__); + return NULL; + } + + /* Create the new node */ + double_linked_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list_node)); + memset(new_node, 0, sizeof(double_linked_list_node)); + new_node->data = data; + + if (handle->head == NULL) { + handle->head = new_node; + handle->tail = new_node; + } else { + new_node->next_node = handle->head; + handle->head->prev_node = new_node; + handle->head = new_node; + } + + (handle->num_entries)++; + + return new_node; +} + + +/* Creates a node and adds it as the last item in the list */ +double_linked_list_node *dll_append(double_linked_list *handle, void *data) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_append_data NULL handle", + __func__); + return NULL; + } + + /* Create the new node */ + double_linked_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(double_linked_list_node)); + memset(new_node, 0, sizeof(double_linked_list_node)); + new_node->data = data; + + if (handle->head == NULL) { + handle->head = new_node; + handle->tail = new_node; + } else { + new_node->prev_node = handle->tail; + handle->tail->next_node = new_node; + handle->tail = new_node; + } + + (handle->num_entries)++; + + return new_node; +} + + +/* Delete the first node in the list, and return the data */ +void *dll_delete_first_node(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_first_node NULL handle", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + double_linked_list_node *delete_node = handle->head; + void *data = delete_node->data; + + if (delete_node->next_node == NULL) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else { + handle->head = delete_node->next_node; + handle->head->prev_node = NULL; + } + + pceplib_free(PCEPLIB_INFRA, delete_node); + (handle->num_entries)--; + + return data; +} + + +/* Delete the last node in the list, and return the data */ +void *dll_delete_last_node(double_linked_list *handle) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_last_node NULL handle", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + double_linked_list_node *delete_node = handle->tail; + void *data = delete_node->data; + + if (delete_node->prev_node == NULL) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else { + handle->tail = delete_node->prev_node; + handle->tail->next_node = NULL; + } + + pceplib_free(PCEPLIB_INFRA, delete_node); + (handle->num_entries)--; + + return data; +} + + +/* Delete the designated node in the list, and return the data */ +void *dll_delete_node(double_linked_list *handle, double_linked_list_node *node) +{ + if (handle == NULL) { + pcep_log(LOG_WARNING, "%s: dll_delete_node NULL handle", + __func__); + return NULL; + } + + if (node == NULL) { + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *data = node->data; + + if (handle->head == handle->tail) { + /* Its the last node in the list */ + handle->head = NULL; + handle->tail = NULL; + } else if (handle->head == node) { + handle->head = node->next_node; + handle->head->prev_node = NULL; + } else if (handle->tail == node) { + handle->tail = node->prev_node; + handle->tail->next_node = NULL; + } else { + /* Its somewhere in the middle of the list */ + node->next_node->prev_node = node->prev_node; + node->prev_node->next_node = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + (handle->num_entries)--; + + return data; +} diff --git a/pceplib/pcep_utils_double_linked_list.h b/pceplib/pcep_utils_double_linked_list.h new file mode 100644 index 0000000..4eab428 --- /dev/null +++ b/pceplib/pcep_utils_double_linked_list.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ + +typedef struct double_linked_list_node_ { + struct double_linked_list_node_ *prev_node; + struct double_linked_list_node_ *next_node; + void *data; + +} double_linked_list_node; + + +typedef struct double_linked_list_ { + double_linked_list_node *head; + double_linked_list_node *tail; + unsigned int num_entries; + +} double_linked_list; + + +/* Initialize a double linked list */ +double_linked_list *dll_initialize(void); + +/* Destroy a double linked list, by freeing the handle and nodes, + * user data will not be freed, and may be leaked if not handled + * externally. */ +void dll_destroy(double_linked_list *handle); +/* Destroy a double linked list, by freeing the handle and nodes, + * and the user data. */ +void dll_destroy_with_data(double_linked_list *handle); +void dll_destroy_with_data_memtype(double_linked_list *handle, + void *data_memory_type); + +/* Creates a node and adds it as the first item in the list */ +double_linked_list_node *dll_prepend(double_linked_list *handle, void *data); + +/* Creates a node and adds it as the last item in the list */ +double_linked_list_node *dll_append(double_linked_list *handle, void *data); + +/* Delete the first node in the list, and return the data */ +void *dll_delete_first_node(double_linked_list *handle); + +/* Delete the last node in the list, and return the data */ +void *dll_delete_last_node(double_linked_list *handle); + +/* Delete the designated node in the list, and return the data */ +void *dll_delete_node(double_linked_list *handle, + double_linked_list_node *node); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_DOUBLE_LINKED_LIST_H_ */ diff --git a/pceplib/pcep_utils_logging.c b/pceplib/pcep_utils_logging.c new file mode 100644 index 0000000..926b662 --- /dev/null +++ b/pceplib/pcep_utils_logging.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "compiler.h" +#include "pcep_utils_logging.h" + +/* Forward declaration */ +int pcep_stdout_logger(int priority, const char *format, va_list args) + PRINTFRR(2, 0); + +static pcep_logger_func logger_func = pcep_stdout_logger; +static int logging_level_ = LOG_INFO; + +void register_logger(pcep_logger_func logger) +{ + logger_func = logger; +} + +void set_logging_level(int level) +{ + logging_level_ = level; +} + +int get_logging_level(void) +{ + return logging_level_; +} + +void pcep_log(int priority, const char *format, ...) +{ + va_list va; + va_start(va, format); + logger_func(priority, format, va); + va_end(va); +} + +void pcep_log_hexbytes(int priority, const char *message, const uint8_t *bytes, + uint8_t bytes_len) +{ + char byte_str[2048] = {0}; + int i = 0; + + snprintf(byte_str, 2048, "%s ", message); + for (; i < bytes_len; i++) { + snprintf(byte_str, 2048, "%02x ", bytes[i]); + } + snprintf(byte_str, 2048, "\n"); + + pcep_log(priority, "%s", byte_str); +} + +/* Defined with a return type to match the FRR logging signature. + * Assuming glibc printf() is thread-safe. */ +int pcep_stdout_logger(int priority, const char *format, va_list args) +{ + if (priority <= logging_level_) { + vprintf(format, args); + printf("\n"); + } + + return 0; +} diff --git a/pceplib/pcep_utils_logging.h b/pceplib/pcep_utils_logging.h new file mode 100644 index 0000000..2336114 --- /dev/null +++ b/pceplib/pcep_utils_logging.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ + +#include /* Logging levels */ +#include /* va_list */ +#include /* uint8_t */ + +/* + * The logging defined here i intended to provide the infrastructure to + * be able to plug-in an external logger, primarily the FRR logger. There + * will be a default internal logger implemented that will write to stdout, + * but any other advanced logging features should be implemented externally. + */ + +/* Only the following logging levels from syslog.h should be used: + * + * LOG_DEBUG - For all messages that are enabled by optional debugging + * features, typically preceded by "if (IS...DEBUG...)" + * LOG_INFO - Information that may be of interest, but + * everything seems to be working properly. + * LOG_NOTICE - Only for message pertaining to daemon startup or shutdown. + * LOG_WARNING - Warning conditions: unexpected events, but the daemon + * believes it can continue to operate correctly. + * LOG_ERR - Error situations indicating malfunctions. + * Probably requires attention. + */ + + +/* The signature of this logger function is the same as the FRR logger */ +typedef int (*pcep_logger_func)(int, const char *, va_list); +void register_logger(pcep_logger_func logger); + +/* These functions only take affect when using the internal stdout logger */ +void set_logging_level(int level); +int get_logging_level(void); + +/* Log messages either to a previously registered + * logger or to the internal default stdout logger. */ +void pcep_log(int priority, const char *format, ...); +void pcep_log_hexbytes(int priority, const char *message, const uint8_t *bytes, + uint8_t bytes_len); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_LOGGING_H_ */ diff --git a/pceplib/pcep_utils_memory.c b/pceplib/pcep_utils_memory.c new file mode 100644 index 0000000..c3a2ab9 --- /dev/null +++ b/pceplib/pcep_utils_memory.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +/* Set default values for memory function pointers */ +static pceplib_malloc_func mfunc = NULL; +static pceplib_calloc_func cfunc = NULL; +static pceplib_realloc_func rfunc = NULL; +static pceplib_strdup_func sfunc = NULL; +static pceplib_free_func ffunc = NULL; + +/* Internal memory types */ +struct pceplib_memory_type pceplib_infra_mt = { + .memory_type_name = "PCEPlib Infrastructure memory", + .total_bytes_allocated = 0, + .num_allocates = 0, + .total_bytes_freed = 0, + .num_frees = 0}; +struct pceplib_memory_type pceplib_messages_mt = { + .memory_type_name = "PCEPlib Messages memory", + .total_bytes_allocated = 0, + .num_allocates = 0, + .total_bytes_freed = 0, + .num_frees = 0}; + +/* The memory type pointers default to the internal memory types */ +void *PCEPLIB_INFRA = &pceplib_infra_mt; +void *PCEPLIB_MESSAGES = &pceplib_messages_mt; + +/* Initialize memory function pointers and memory type pointers */ +bool pceplib_memory_initialize(void *pceplib_infra_mt, + void *pceplib_messages_mt, + pceplib_malloc_func mf, pceplib_calloc_func cf, + pceplib_realloc_func rf, pceplib_strdup_func sf, + pceplib_free_func ff) +{ + PCEPLIB_INFRA = (pceplib_infra_mt ? pceplib_infra_mt : PCEPLIB_INFRA); + PCEPLIB_MESSAGES = + (pceplib_messages_mt ? pceplib_messages_mt : PCEPLIB_MESSAGES); + + mfunc = (mf ? mf : mfunc); + cfunc = (cf ? cf : cfunc); + rfunc = (rf ? rf : rfunc); + sfunc = (sf ? sf : sfunc); + ffunc = (ff ? ff : ffunc); + + return true; +} + +void pceplib_memory_reset(void) +{ + pceplib_infra_mt.total_bytes_allocated = 0; + pceplib_infra_mt.num_allocates = 0; + pceplib_infra_mt.total_bytes_freed = 0; + pceplib_infra_mt.num_frees = 0; + + pceplib_messages_mt.total_bytes_allocated = 0; + pceplib_messages_mt.num_allocates = 0; + pceplib_messages_mt.total_bytes_freed = 0; + pceplib_messages_mt.num_frees = 0; +} + +void pceplib_memory_dump(void) +{ + if (PCEPLIB_INFRA) { + pcep_log( + LOG_INFO, + "%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]", + __func__, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->memory_type_name, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->num_allocates, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->total_bytes_allocated, + ((struct pceplib_memory_type *)PCEPLIB_INFRA) + ->num_frees); + } + + if (PCEPLIB_MESSAGES) { + pcep_log( + LOG_INFO, + "%s: Memory Type [%s] Total [allocs, alloc bytes, frees] [%d, %d, %d]", + __func__, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->memory_type_name, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->num_allocates, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->total_bytes_allocated, + ((struct pceplib_memory_type *)PCEPLIB_MESSAGES) + ->num_frees); + } +} + +/* PCEPlib memory functions: + * They either call the supplied function pointers, or use the internal + * implementations, which just increment simple counters and call the + * C stdlib memory implementations. */ + +void *pceplib_malloc(void *mem_type, size_t size) +{ + if (mfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return malloc(size); + } else { + return mfunc(mem_type, size); + } +} + +void *pceplib_calloc(void *mem_type, size_t size) +{ + if (cfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return calloc(1, size); + } else { + return cfunc(mem_type, size); + } +} + +void *pceplib_realloc(void *mem_type, void *ptr, size_t size) +{ + if (rfunc == NULL) { + if (mem_type != NULL) { + /* TODO should add previous allocated bytes to + * total_bytes_freed */ + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += size; + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return realloc(ptr, size); + } else { + return rfunc(mem_type, ptr, size); + } +} + +void *pceplib_strdup(void *mem_type, const char *str) +{ + if (sfunc == NULL) { + if (mem_type != NULL) { + ((struct pceplib_memory_type *)mem_type) + ->total_bytes_allocated += strlen(str); + ((struct pceplib_memory_type *)mem_type) + ->num_allocates++; + } + + return strdup(str); + } else { + return sfunc(mem_type, str); + } +} + +void pceplib_free(void *mem_type, void *ptr) +{ + if (ffunc == NULL) { + if (mem_type != NULL) { + /* TODO in order to increment total_bytes_freed, we need + * to keep track of the bytes allocated per pointer. + * Currently not implemented. */ + ((struct pceplib_memory_type *)mem_type)->num_frees++; + if (((struct pceplib_memory_type *)mem_type) + ->num_allocates + < ((struct pceplib_memory_type *)mem_type) + ->num_frees) { + pcep_log( + LOG_ERR, + "%s: pceplib_free MT N_Alloc < N_Free: MemType [%s] NumAllocates [%d] NumFrees [%d]", + __func__, + ((struct pceplib_memory_type *)mem_type) + ->memory_type_name, + ((struct pceplib_memory_type *)mem_type) + ->num_allocates, + ((struct pceplib_memory_type *)mem_type) + ->num_frees); + } + } + + return free(ptr); + } else { + return ffunc(mem_type, ptr); + } +} diff --git a/pceplib/pcep_utils_memory.h b/pceplib/pcep_utils_memory.h new file mode 100644 index 0000000..a969143 --- /dev/null +++ b/pceplib/pcep_utils_memory.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + +#ifndef PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ +#define PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ + +#include +#include +#include + +/* This module is intended to be used primarily with FRR's memory module, + * which has memory groups and memory types, although any memory infrastructure + * can be used that has memory types or the memory types in this module can be + * set to NULL. The PCEPlib can be used stand-alone, in which case the simple + * internal memory type system will be used. + */ + +/* These memory function pointers are modeled after the memory functions + * in frr/lib/memory.h */ +typedef void *(*pceplib_malloc_func)(void *mem_type, size_t size); +typedef void *(*pceplib_calloc_func)(void *mem_type, size_t size); +typedef void *(*pceplib_realloc_func)(void *mem_type, void *ptr, size_t size); +typedef void *(*pceplib_strdup_func)(void *mem_type, const char *str); +typedef void (*pceplib_free_func)(void *mem_type, void *ptr); + +/* Either an internal pceplib_memory_type pointer + * or could be an FRR memory type pointer */ +extern void *PCEPLIB_INFRA; +extern void *PCEPLIB_MESSAGES; + +/* Internal PCEPlib memory type */ +struct pceplib_memory_type { + char memory_type_name[64]; + uint32_t total_bytes_allocated; + uint32_t num_allocates; + uint32_t total_bytes_freed; /* currently not used */ + uint32_t num_frees; +}; + +/* Initialize this module by passing in the 2 memory types used in the PCEPlib + * and by passing in the different memory allocation/free function pointers. + * Any of these parameters can be NULL, in which case an internal implementation + * will be used. + */ +bool pceplib_memory_initialize(void *pceplib_infra_mt, + void *pceplib_messages_mt, + pceplib_malloc_func mfunc, + pceplib_calloc_func cfunc, + pceplib_realloc_func rfunc, + pceplib_strdup_func sfunc, + pceplib_free_func ffunc); + +/* Reset the internal allocation/free counters. Used mainly for internal + * testing. */ +void pceplib_memory_reset(void); +void pceplib_memory_dump(void); + +/* Memory functions to be used throughout the PCEPlib. Internally, these + * functions will either used the function pointers passed in via + * pceplib_memory_initialize() or a simple internal implementation. The + * internal implementations just increment the internal memory type + * counters and call the C stdlib memory functions. + */ +void *pceplib_malloc(void *mem_type, size_t size); +void *pceplib_calloc(void *mem_type, size_t size); +void *pceplib_realloc(void *mem_type, void *ptr, size_t size); +void *pceplib_strdup(void *mem_type, const char *str); +void pceplib_free(void *mem_type, void *ptr); + +#endif /* PCEP_UTILS_INCLUDE_PCEP_UTILS_MEMORY_H_ */ diff --git a/pceplib/pcep_utils_ordered_list.c b/pceplib/pcep_utils_ordered_list.c new file mode 100644 index 0000000..43b4863 --- /dev/null +++ b/pceplib/pcep_utils_ordered_list.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" + +/* Compare function that simply compares pointers. + * return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ +int pointer_compare_function(void *list_entry, void *new_entry) +{ + return (char *)new_entry - (char *)list_entry; +} + +ordered_list_handle *ordered_list_initialize(ordered_compare_function func_ptr) +{ + ordered_list_handle *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_handle)); + memset(handle, 0, sizeof(ordered_list_handle)); + handle->head = NULL; + handle->num_entries = 0; + handle->compare_function = func_ptr; + + return handle; +} + + +/* free all the ordered_list_node resources and the ordered_list_handle. + * it is assumed that the user is responsible fore freeing the data + * pointed to by the nodes. + */ +void ordered_list_destroy(ordered_list_handle *handle) +{ + if (handle == NULL) { + return; + } + + ordered_list_node *node = handle->head; + ordered_list_node *next; + + while (node != NULL) { + next = node->next_node; + pceplib_free(PCEPLIB_INFRA, node); + node = next; + } + + pceplib_free(PCEPLIB_INFRA, handle); +} + + +ordered_list_node *ordered_list_add_node(ordered_list_handle *handle, + void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_add_node, the list has not been initialized", + __func__); + return NULL; + } + handle->num_entries++; + + ordered_list_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_node)); + memset(new_node, 0, sizeof(ordered_list_node)); + new_node->data = data; + new_node->next_node = NULL; + + /* check if its an empty list */ + if (handle->head == NULL) { + handle->head = new_node; + + return new_node; + } + + ordered_list_node *prev_node = handle->head; + ordered_list_node *node = prev_node; + int compare_result; + + while (node != NULL) { + compare_result = handle->compare_function(node->data, data); + if (compare_result < 0) { + /* insert the node */ + new_node->next_node = node; + if (handle->head == node) { + /* add it at the beginning of the list */ + handle->head = new_node; + } else { + prev_node->next_node = new_node; + } + + return new_node; + } + + /* keep searching with the next node in the list */ + prev_node = node; + node = node->next_node; + } + + /* at the end of the list, add it here */ + prev_node->next_node = new_node; + + return new_node; +} + + +ordered_list_node *ordered_list_find2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_find2, the list has not been initialized", + __func__); + return NULL; + } + + ordered_list_node *node = handle->head; + int compare_result; + + while (node != NULL) { + compare_result = compare_func(node->data, data); + if (compare_result == 0) { + return node; + } else { + node = node->next_node; + } + } + + return NULL; +} + + +ordered_list_node *ordered_list_find(ordered_list_handle *handle, void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_find, the list has not been initialized", + __func__); + return NULL; + } + + return ordered_list_find2(handle, data, handle->compare_function); +} + + +void *ordered_list_remove_first_node(ordered_list_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + handle->num_entries--; + + void *data = handle->head->data; + ordered_list_node *next_node = handle->head->next_node; + pceplib_free(PCEPLIB_INFRA, handle->head); + handle->head = next_node; + + return data; +} + + +void * +ordered_list_remove_first_node_equals2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node_equals2, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + ordered_list_node *prev_node = handle->head; + ordered_list_node *node = prev_node; + bool keep_walking = true; + void *return_data = NULL; + int compare_result; + + while (node != NULL && keep_walking) { + compare_result = compare_func(node->data, data); + if (compare_result == 0) { + return_data = node->data; + keep_walking = false; + handle->num_entries--; + + /* adjust the corresponding pointers accordingly */ + if (handle->head == node) { + /* its the first node in the list */ + handle->head = node->next_node; + } else { + prev_node->next_node = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + } else { + prev_node = node; + node = node->next_node; + } + } + + return return_data; +} + + +void *ordered_list_remove_first_node_equals(ordered_list_handle *handle, + void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_first_node_equals, the list has not been initialized", + __func__); + return NULL; + } + + return ordered_list_remove_first_node_equals2(handle, data, + handle->compare_function); +} + + +void *ordered_list_remove_node(ordered_list_handle *handle, + ordered_list_node *prev_node, + ordered_list_node *node_toRemove) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_node, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *return_data = node_toRemove->data; + handle->num_entries--; + + if (node_toRemove == handle->head) { + handle->head = node_toRemove->next_node; + } else { + prev_node->next_node = node_toRemove->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node_toRemove); + + return return_data; +} + +void *ordered_list_remove_node2(ordered_list_handle *handle, + ordered_list_node *node_to_remove) +{ + if (handle == NULL) { + pcep_log( + LOG_WARNING, + "%s: ordered_list_remove_node2, the list has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + ordered_list_node *node = handle->head; + ordered_list_node *prev_node = handle->head; + + while (node != NULL) { + if (node == node_to_remove) { + return (ordered_list_remove_node(handle, prev_node, + node)); + } else { + prev_node = node; + node = node->next_node; + } + } + + return NULL; +} diff --git a/pceplib/pcep_utils_ordered_list.h b/pceplib/pcep_utils_ordered_list.h new file mode 100644 index 0000000..2d4e5c2 --- /dev/null +++ b/pceplib/pcep_utils_ordered_list.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPUTILSORDEREDLIST_H_ +#define INCLUDE_PCEPUTILSORDEREDLIST_H_ + +#include + +typedef struct ordered_list_node_ { + struct ordered_list_node_ *next_node; + void *data; + +} ordered_list_node; + +/* The implementation of this function will receive a pointer to the + * new data to be inserted and a pointer to the list_entry, and should + * return: + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ +typedef int (*ordered_compare_function)(void *list_entry, void *new_entry); + +/* Compare function that compares pointers */ +int pointer_compare_function(void *list_entry, void *new_entry); + +typedef struct ordered_list_handle_ { + ordered_list_node *head; + unsigned int num_entries; + ordered_compare_function compare_function; + +} ordered_list_handle; + +ordered_list_handle *ordered_list_initialize(ordered_compare_function func_ptr); +void ordered_list_destroy(ordered_list_handle *handle); + +/* Add a new ordered_list_node to the list, using the ordered_compare_function + * to determine where in the list to add it. The newly created ordered_list_node + * will be returned. + */ +ordered_list_node *ordered_list_add_node(ordered_list_handle *handle, + void *data); + +/* Find an entry in the ordered_list using the ordered_compare_function to + * compare the data passed in. + * Return the node if found, NULL otherwise. + */ +ordered_list_node *ordered_list_find(ordered_list_handle *handle, void *data); + +/* The same as the previous function, but with a specific orderedComparefunction + */ +ordered_list_node *ordered_list_find2(ordered_list_handle *handle, void *data, + ordered_compare_function compare_func); + +/* Remove the first entry in the list and return the data it points to. + * Will return NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_first_node(ordered_list_handle *handle); + +/* Remove the first entry in the list that has the same data, using the + * ordered_compare_function, and return the data it points to. + * Will return NULL if the handle is NULL or if the list is empty or + * if no entry is found that equals data. + */ +void *ordered_list_remove_first_node_equals(ordered_list_handle *handle, + void *data); + +/* The same as the previous function, but with a specific orderedComparefunction + */ +void *ordered_list_remove_first_node_equals2(ordered_list_handle *handle, + void *data, + ordered_compare_function func_ptr); + +/* Remove the node "node_to_remove" and adjust the "prev_node" pointers + * accordingly, returning the data pointed to by "node_to_remove". Will return + * NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_node(ordered_list_handle *handle, + ordered_list_node *prev_node, + ordered_list_node *node_to_remove); + +/* Remove the node "node_to_remove" by searching for it in the entire list, + * returning the data pointed to by "node_to_remove". + * Will return NULL if the handle is NULL or if the list is empty. + */ +void *ordered_list_remove_node2(ordered_list_handle *handle, + ordered_list_node *node_to_remove); + +#endif /* INCLUDE_PCEPUTILSORDEREDLIST_H_ */ diff --git a/pceplib/pcep_utils_queue.c b/pceplib/pcep_utils_queue.c new file mode 100644 index 0000000..27a64b7 --- /dev/null +++ b/pceplib/pcep_utils_queue.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_queue.h" + +queue_handle *queue_initialize(void) +{ + /* Set the max_entries to 0 to disable it */ + return queue_initialize_with_size(0); +} + + +queue_handle *queue_initialize_with_size(unsigned int max_entries) +{ + queue_handle *handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(queue_handle)); + memset(handle, 0, sizeof(queue_handle)); + handle->max_entries = max_entries; + + return handle; +} + + +void queue_destroy(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_destroy, the queue has not been initialized", + __func__); + return; + } + + while (queue_dequeue(handle) != NULL) { + } + pceplib_free(PCEPLIB_INFRA, handle); +} + + +void queue_destroy_with_data(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_destroy_with_data, the queue has not been initialized", + __func__); + return; + } + + void *data = queue_dequeue(handle); + while (data != NULL) { + pceplib_free(PCEPLIB_INFRA, data); + data = queue_dequeue(handle); + } + pceplib_free(PCEPLIB_INFRA, handle); +} + + +queue_node *queue_enqueue(queue_handle *handle, void *data) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_enqueue, the queue has not been initialized", + __func__); + return NULL; + } + + if (handle->max_entries > 0 + && handle->num_entries >= handle->max_entries) { + pcep_log( + LOG_DEBUG, + "%s: queue_enqueue, cannot enqueue: max entries hit [%u]", + handle->num_entries); + return NULL; + } + + queue_node *new_node = + pceplib_malloc(PCEPLIB_INFRA, sizeof(queue_node)); + memset(new_node, 0, sizeof(queue_node)); + new_node->data = data; + new_node->next_node = NULL; + + (handle->num_entries)++; + if (handle->head == NULL) { + /* its the first entry in the queue */ + handle->head = handle->tail = new_node; + } else { + handle->tail->next_node = new_node; + handle->tail = new_node; + } + + return new_node; +} + + +void *queue_dequeue(queue_handle *handle) +{ + if (handle == NULL) { + pcep_log( + LOG_DEBUG, + "%s: queue_dequeue, the queue has not been initialized", + __func__); + return NULL; + } + + if (handle->head == NULL) { + return NULL; + } + + void *node_data = handle->head->data; + queue_node *node = handle->head; + (handle->num_entries)--; + if (handle->head == handle->tail) { + /* its the last entry in the queue */ + handle->head = handle->tail = NULL; + } else { + handle->head = node->next_node; + } + + pceplib_free(PCEPLIB_INFRA, node); + + return node_data; +} diff --git a/pceplib/pcep_utils_queue.h b/pceplib/pcep_utils_queue.h new file mode 100644 index 0000000..d3f8c20 --- /dev/null +++ b/pceplib/pcep_utils_queue.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifndef INCLUDE_PCEPUTILSQUEUE_H_ +#define INCLUDE_PCEPUTILSQUEUE_H_ + +typedef struct queue_node_ { + struct queue_node_ *next_node; + void *data; + +} queue_node; + +typedef struct queue_handle_ { + queue_node *head; + queue_node *tail; + unsigned int num_entries; + /* Set to 0 to disable */ + unsigned int max_entries; + +} queue_handle; + +queue_handle *queue_initialize(void); +queue_handle *queue_initialize_with_size(unsigned int max_entries); +void queue_destroy(queue_handle *handle); +void queue_destroy_with_data(queue_handle *handle); +queue_node *queue_enqueue(queue_handle *handle, void *data); +void *queue_dequeue(queue_handle *handle); + +#endif /* INCLUDE_PCEPUTILSQUEUE_H_ */ diff --git a/pceplib/subdir.am b/pceplib/subdir.am new file mode 100644 index 0000000..2633f67 --- /dev/null +++ b/pceplib/subdir.am @@ -0,0 +1,62 @@ +if PATHD_PCEP + +noinst_LTLIBRARIES = pceplib/libpcep_pcc.la pceplib/libsocket_comm_mock.la +pceplib_libpcep_pcc_la_CFLAGS = $(AM_CFLAGS) -fPIC +pceplib_libpcep_pcc_la_SOURCES = pceplib/pcep_msg_messages.c \ + pceplib/pcep_msg_objects.c \ + pceplib/pcep_msg_tlvs.c \ + pceplib/pcep_msg_tools.c \ + pceplib/pcep_msg_messages_encoding.c \ + pceplib/pcep_msg_objects_encoding.c \ + pceplib/pcep_msg_tlvs_encoding.c \ + pceplib/pcep_msg_object_error_types.c \ + pceplib/pcep_pcc_api.c \ + pceplib/pcep_session_logic.c \ + pceplib/pcep_session_logic_loop.c \ + pceplib/pcep_session_logic_states.c \ + pceplib/pcep_session_logic_counters.c \ + pceplib/pcep_socket_comm_loop.c \ + pceplib/pcep_socket_comm.c \ + pceplib/pcep_timers_event_loop.c \ + pceplib/pcep_timers.c \ + pceplib/pcep_utils_counters.c \ + pceplib/pcep_utils_double_linked_list.c \ + pceplib/pcep_utils_logging.c \ + pceplib/pcep_utils_memory.c \ + pceplib/pcep_utils_ordered_list.c \ + pceplib/pcep_utils_queue.c + +if PATHD_PCEP_TEST +# SocketComm Mock library used for Unit Testing +pceplib_libsocket_comm_mock_la_SOURCES = pceplib/pcep_socket_comm_mock.c +endif + +noinst_HEADERS += pceplib/pcep.h \ + pceplib/pcep_msg_encoding.h \ + pceplib/pcep_msg_messages.h \ + pceplib/pcep_msg_object_error_types.h \ + pceplib/pcep_msg_objects.h \ + pceplib/pcep_msg_tlvs.h \ + pceplib/pcep_msg_tools.h \ + pceplib/pcep_pcc_api.h \ + pceplib/pcep_session_logic.h \ + pceplib/pcep_session_logic_internals.h \ + pceplib/pcep_socket_comm.h \ + pceplib/pcep_socket_comm_internals.h \ + pceplib/pcep_socket_comm_loop.h \ + pceplib/pcep_socket_comm_mock.h \ + pceplib/pcep_timer_internals.h \ + pceplib/pcep_timers.h \ + pceplib/pcep_timers_event_loop.h \ + pceplib/pcep_utils_counters.h \ + pceplib/pcep_utils_double_linked_list.h \ + pceplib/pcep_utils_logging.h \ + pceplib/pcep_utils_memory.h \ + pceplib/pcep_utils_ordered_list.h \ + pceplib/pcep_utils_queue.h + +noinst_PROGRAMS += pceplib/pcep_pcc +pceplib_pcep_pcc_SOURCES = pceplib/pcep_pcc.c +pceplib_pcep_pcc_LDADD = pceplib/libpcep_pcc.la lib/libfrr.la -lpthread + +endif diff --git a/pceplib/test/pcep_msg_messages_test.c b/pceplib/test/pcep_msg_messages_test.c new file mode 100644 index 0000000..e1f5249 --- /dev/null +++ b/pceplib/test/pcep_msg_messages_test.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_messages_test.h" + +/* + * Notice: + * All of these message Unit Tests encode the created messages by explicitly + * calling pcep_encode_message() thus testing the message creation and the + * message encoding. + */ + +static struct pcep_versioning *versioning = NULL; + +int pcep_messages_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_messages_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_messages_test_setup(void) +{ + versioning = create_default_pcep_versioning(); +} + +void pcep_messages_test_teardown(void) +{ + destroy_pcep_versioning(versioning); +} + +void test_pcep_msg_create_open(void) +{ + uint8_t keepalive = 30; + uint8_t deadtimer = 60; + uint8_t sid = 255; + + struct pcep_message *message = + pcep_msg_create_open(keepalive, deadtimer, sid); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_OPEN, + PCEP_OBJ_TYPE_OPEN)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_open *open_obj = + (struct pcep_object_open *)message->obj_list->head->data; + CU_ASSERT_EQUAL(open_obj->header.object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(open_obj->header.object_type, PCEP_OBJ_TYPE_OPEN); + + CU_ASSERT_EQUAL(open_obj->open_deadtimer, deadtimer); + CU_ASSERT_EQUAL(open_obj->open_keepalive, keepalive); + CU_ASSERT_EQUAL(open_obj->open_sid, sid); + CU_ASSERT_EQUAL(open_obj->open_version, PCEP_OBJECT_OPEN_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_request(void) +{ + /* First test with NULL objects */ + struct pcep_message *message = + pcep_msg_create_request(NULL, NULL, NULL); + CU_ASSERT_PTR_NULL(message); + + /* Test IPv4 */ + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct in_addr src_addr = {}, dst_addr = {}; + struct pcep_object_endpoints_ipv4 *ipv4_obj = + pcep_obj_create_endpoint_ipv4(&src_addr, &dst_addr); + message = pcep_msg_create_request(rp_obj, ipv4_obj, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv4_obj->header)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + /* Test IPv6 */ + rp_obj = pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct in6_addr src_addr_ipv6 = {}, dst_addr_ipv6 = {}; + struct pcep_object_endpoints_ipv6 *ipv6_obj = + pcep_obj_create_endpoint_ipv6(&src_addr_ipv6, &dst_addr_ipv6); + message = pcep_msg_create_request_ipv6(rp_obj, ipv6_obj, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv6_obj->header)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + /* The objects get deleted with the message, so they need to be created + * again */ + rp_obj = pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + ipv4_obj = pcep_obj_create_endpoint_ipv4(&src_addr, &dst_addr); + struct pcep_object_bandwidth *bandwidth_obj = + pcep_obj_create_bandwidth(4.2); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, bandwidth_obj); + message = pcep_msg_create_request(rp_obj, ipv4_obj, obj_list); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL( + message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&ipv4_obj->header) + + pcep_object_get_length_by_hdr( + &bandwidth_obj->header)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREQ); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_request_svec(void) +{ +} + +void test_pcep_msg_create_reply_nopath(void) +{ + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + struct pcep_object_nopath *nopath_obj = pcep_obj_create_nopath( + false, false, PCEP_NOPATH_TLV_ERR_NO_TLV); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, nopath_obj); + + struct pcep_message *message = pcep_msg_create_reply(rp_obj, obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + (MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + pcep_object_get_length_by_hdr(&nopath_obj->header))); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_reply(void) +{ + /* First test with NULL ero and rp objects */ + struct pcep_message *message = pcep_msg_create_reply(NULL, NULL); + + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 0); + CU_ASSERT_EQUAL(message->encoded_message_length, MESSAGE_HEADER_LENGTH); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); + + double_linked_list *ero_subobj_list = dll_initialize(); + struct pcep_object_ro_subobj *ero_subobj = + (struct pcep_object_ro_subobj *) + pcep_obj_create_ro_subobj_32label(true, 1, 10); + dll_append(ero_subobj_list, ero_subobj); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + double_linked_list *object_list = dll_initialize(); + dll_append(object_list, ero); + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + message = pcep_msg_create_reply(rp_obj, object_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length_by_hdr(&rp_obj->header) + + OBJECT_HEADER_LENGTH + + OBJECT_RO_SUBOBJ_HEADER_LENGTH + + 6 /* size of the 32label */); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCREP); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_close(void) +{ + uint8_t reason = PCEP_CLOSE_REASON_UNREC_MSG; + + struct pcep_message *message = pcep_msg_create_close(reason); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_CLOSE, + PCEP_OBJ_TYPE_CLOSE)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_CLOSE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_close *close_obj = + (struct pcep_object_close *)message->obj_list->head->data; + assert(close_obj != NULL); + CU_ASSERT_EQUAL(close_obj->header.object_class, PCEP_OBJ_CLASS_CLOSE); + CU_ASSERT_EQUAL(close_obj->header.object_type, PCEP_OBJ_TYPE_CLOSE); + CU_ASSERT_EQUAL(close_obj->reason, reason); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_error(void) +{ + uint8_t error_type = PCEP_ERRT_RECEPTION_OF_INV_OBJECT; + uint8_t error_value = PCEP_ERRV_KEEPALIVEWAIT_TIMED_OUT; + + struct pcep_message *message = + pcep_msg_create_error(error_type, error_value); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + pcep_object_get_length(PCEP_OBJ_CLASS_ERROR, + PCEP_OBJ_TYPE_ERROR)); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_ERROR); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + /* Just check the class and type, the rest of the hdr fields + * are verified in pcep-objects-test.c */ + struct pcep_object_error *error_obj = + (struct pcep_object_error *)message->obj_list->head->data; + CU_ASSERT_EQUAL(error_obj->header.object_class, PCEP_OBJ_CLASS_ERROR); + CU_ASSERT_EQUAL(error_obj->header.object_type, PCEP_OBJ_TYPE_ERROR); + + CU_ASSERT_EQUAL(error_obj->error_type, error_type); + CU_ASSERT_EQUAL(error_obj->error_value, error_value); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_keepalive(void) +{ + struct pcep_message *message = pcep_msg_create_keepalive(); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 0); + CU_ASSERT_EQUAL(message->encoded_message_length, MESSAGE_HEADER_LENGTH); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_KEEPALIVE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_report(void) +{ + double_linked_list *obj_list = dll_initialize(); + + /* Should return NULL if obj_list is empty */ + struct pcep_message *message = pcep_msg_create_report(NULL); + CU_ASSERT_PTR_NULL(message); + + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(obj_list, lsp); + message = pcep_msg_create_report(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + lsp->header.encoded_object_length); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_REPORT); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_update(void) +{ + double_linked_list *obj_list = dll_initialize(); + double_linked_list *ero_subobj_list = dll_initialize(); + + struct pcep_message *message = pcep_msg_create_update(NULL); + CU_ASSERT_PTR_NULL(message); + + /* Should return NULL if obj_list is empty */ + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NULL(message); + if (message != NULL) + pcep_msg_free_message(message); + + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + /* Should return NULL if obj_list does not have 3 entries */ + dll_append(obj_list, srp); + dll_append(obj_list, lsp); + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NULL(message); + + dll_append(obj_list, ero); + if (message != NULL) + pcep_msg_free_message(message); + + message = pcep_msg_create_update(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + srp->header.encoded_object_length + + lsp->header.encoded_object_length + + ero->header.encoded_object_length); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_initiate(void) +{ + double_linked_list *obj_list = dll_initialize(); + double_linked_list *ero_subobj_list = dll_initialize(); + + /* Should return NULL if obj_list is empty */ + struct pcep_message *message = pcep_msg_create_initiate(NULL); + CU_ASSERT_PTR_NULL(message); + if (message != NULL) + pcep_msg_free_message(message); + + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + + /* Should return NULL if obj_list does not have 2 entries */ + dll_append(obj_list, srp); + message = pcep_msg_create_initiate(obj_list); + CU_ASSERT_PTR_NULL(message); + if (message != NULL) + pcep_msg_free_message(message); + + dll_append(obj_list, lsp); + dll_append(obj_list, ero); + message = pcep_msg_create_initiate(obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->msg_header); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 3); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + srp->header.encoded_object_length + + lsp->header.encoded_object_length + + ero->header.encoded_object_length); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} + +void test_pcep_msg_create_notify(void) +{ + struct pcep_object_notify *notify_obj = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + + /* Should return NULL if the notify obj is empty */ + struct pcep_message *message = pcep_msg_create_notify(NULL, NULL); + CU_ASSERT_PTR_NULL(message); + + message = pcep_msg_create_notify(notify_obj, NULL); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + notify_obj->header.encoded_object_length); + assert(message->msg_header != NULL); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCNOTF); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); + + struct pcep_object_rp *rp_obj = + pcep_obj_create_rp(0, false, false, false, false, 10, NULL); + double_linked_list *obj_list = dll_initialize(); + dll_append(obj_list, rp_obj); + notify_obj = pcep_obj_create_notify( + PCEP_NOTIFY_TYPE_PENDING_REQUEST_CANCELLED, + PCEP_NOTIFY_VALUE_PCC_CANCELLED_REQUEST); + + message = pcep_msg_create_notify(notify_obj, obj_list); + CU_ASSERT_PTR_NOT_NULL(message); + pcep_encode_message(message, versioning); + assert(message != NULL); + CU_ASSERT_PTR_NOT_NULL(message->obj_list); + assert(message->obj_list != NULL); + CU_ASSERT_EQUAL(message->obj_list->num_entries, 2); + CU_ASSERT_EQUAL(message->encoded_message_length, + MESSAGE_HEADER_LENGTH + + notify_obj->header.encoded_object_length + + rp_obj->header.encoded_object_length); + CU_ASSERT_EQUAL(message->msg_header->type, PCEP_TYPE_PCNOTF); + CU_ASSERT_EQUAL(message->msg_header->pcep_version, + PCEP_MESSAGE_HEADER_VERSION); + + pcep_msg_free_message(message); +} diff --git a/pceplib/test/pcep_msg_messages_test.h b/pceplib/test/pcep_msg_messages_test.h new file mode 100644 index 0000000..b9cfa93 --- /dev/null +++ b/pceplib/test/pcep_msg_messages_test.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_MSG_TEST_H_ +#define PCEP_MSG_MSG_TEST_H_ + +/* functions to be tested from pcep-messages.c */ +int pcep_messages_test_suite_setup(void); +int pcep_messages_test_suite_teardown(void); +void pcep_messages_test_setup(void); +void pcep_messages_test_teardown(void); +void test_pcep_msg_create_open(void); +void test_pcep_msg_create_request(void); +void test_pcep_msg_create_request_svec(void); +void test_pcep_msg_create_reply_nopath(void); +void test_pcep_msg_create_reply(void); +void test_pcep_msg_create_close(void); +void test_pcep_msg_create_error(void); +void test_pcep_msg_create_keepalive(void); +void test_pcep_msg_create_report(void); +void test_pcep_msg_create_update(void); +void test_pcep_msg_create_initiate(void); +void test_pcep_msg_create_notify(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_msg_messages_tests.c b/pceplib/test/pcep_msg_messages_tests.c new file mode 100644 index 0000000..8e8e7cd --- /dev/null +++ b/pceplib/test/pcep_msg_messages_tests.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_msg_messages_test.h" +#include "pcep_msg_tools_test.h" +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_object_error_types_test.h" +#include "pcep_msg_tlvs_test.h" +#include "pcep_msg_objects_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + CU_pSuite messages_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Messages Test Suite", pcep_messages_test_suite_setup, + pcep_messages_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_messages_test_setup, pcep_messages_test_teardown); + CU_add_test(messages_suite, "test_pcep_msg_create_open", + test_pcep_msg_create_open); + CU_add_test(messages_suite, "test_pcep_msg_create_request", + test_pcep_msg_create_request); + CU_add_test(messages_suite, "test_pcep_msg_create_request_svec", + test_pcep_msg_create_request_svec); + CU_add_test(messages_suite, "test_pcep_msg_create_reply_nopath", + test_pcep_msg_create_reply_nopath); + CU_add_test(messages_suite, "test_pcep_msg_create_reply", + test_pcep_msg_create_reply); + CU_add_test(messages_suite, "test_pcep_msg_create_close", + test_pcep_msg_create_close); + CU_add_test(messages_suite, "test_pcep_msg_create_error", + test_pcep_msg_create_error); + CU_add_test(messages_suite, "test_pcep_msg_create_keepalive", + test_pcep_msg_create_keepalive); + CU_add_test(messages_suite, "test_pcep_msg_create_report", + test_pcep_msg_create_report); + CU_add_test(messages_suite, "test_pcep_msg_create_update", + test_pcep_msg_create_update); + CU_add_test(messages_suite, "test_pcep_msg_create_initiate", + test_pcep_msg_create_initiate); + CU_add_test(messages_suite, "test_pcep_msg_create_notify", + test_pcep_msg_create_notify); + + CU_pSuite tlvs_suite = CU_add_suite_with_setup_and_teardown( + "PCEP TLVs Test Suite", pcep_tlvs_test_suite_setup, + pcep_tlvs_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_tlvs_test_setup, pcep_tlvs_test_teardown); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_stateful_pce_capability", + test_pcep_tlv_create_stateful_pce_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_speaker_entity_id", + test_pcep_tlv_create_speaker_entity_id); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_lsp_db_version", + test_pcep_tlv_create_lsp_db_version); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_path_setup_type", + test_pcep_tlv_create_path_setup_type); + CU_add_test(tlvs_suite, + "test_pcep_tlv_create_path_setup_type_capability", + test_pcep_tlv_create_path_setup_type_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_sr_pce_capability", + test_pcep_tlv_create_sr_pce_capability); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_symbolic_path_name", + test_pcep_tlv_create_symbolic_path_name); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_ipv4_lsp_identifiers", + test_pcep_tlv_create_ipv4_lsp_identifiers); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_ipv6_lsp_identifiers", + test_pcep_tlv_create_ipv6_lsp_identifiers); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_id_ipv4", + test_pcep_tlv_create_srpag_pol_id_ipv4); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_id_ipv6", + test_pcep_tlv_create_srpag_pol_id_ipv6); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_pol_name", + test_pcep_tlv_create_srpag_pol_name); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_cp_id", + test_pcep_tlv_create_srpag_cp_id); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_srpag_cp_pref", + test_pcep_tlv_create_srpag_cp_pref); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_lsp_error_code", + test_pcep_tlv_create_lsp_error_code); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_rsvp_ipv4_error_spec", + test_pcep_tlv_create_rsvp_ipv4_error_spec); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_rsvp_ipv6_error_spec", + test_pcep_tlv_create_rsvp_ipv6_error_spec); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_nopath_vector", + test_pcep_tlv_create_nopath_vector); + CU_add_test(tlvs_suite, "test_pcep_tlv_create_arbitrary", + test_pcep_tlv_create_arbitrary); + + CU_pSuite objects_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Objects Test Suite", pcep_objects_test_suite_setup, + pcep_objects_test_suite_teardown, /* suite setup and cleanup + function pointers */ + pcep_objects_test_setup, pcep_objects_test_teardown); + CU_add_test(objects_suite, "test_pcep_obj_create_open", + test_pcep_obj_create_open); + CU_add_test(objects_suite, "test_pcep_obj_create_open", + test_pcep_obj_create_open_with_tlvs); + CU_add_test(objects_suite, "test_pcep_obj_create_rp", + test_pcep_obj_create_rp); + CU_add_test(objects_suite, "test_pcep_obj_create_nopath", + test_pcep_obj_create_nopath); + CU_add_test(objects_suite, "test_pcep_obj_create_enpoint_ipv4", + test_pcep_obj_create_endpoint_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_enpoint_ipv6", + test_pcep_obj_create_endpoint_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_association_ipv4", + test_pcep_obj_create_association_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_association_ipv6", + test_pcep_obj_create_association_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_bandwidth", + test_pcep_obj_create_bandwidth); + CU_add_test(objects_suite, "test_pcep_obj_create_metric", + test_pcep_obj_create_metric); + CU_add_test(objects_suite, "test_pcep_obj_create_lspa", + test_pcep_obj_create_lspa); + CU_add_test(objects_suite, "test_pcep_obj_create_svec", + test_pcep_obj_create_svec); + CU_add_test(objects_suite, "test_pcep_obj_create_error", + test_pcep_obj_create_error); + CU_add_test(objects_suite, "test_pcep_obj_create_close", + test_pcep_obj_create_close); + CU_add_test(objects_suite, "test_pcep_obj_create_srp", + test_pcep_obj_create_srp); + CU_add_test(objects_suite, "test_pcep_obj_create_lsp", + test_pcep_obj_create_lsp); + CU_add_test(objects_suite, "test_pcep_obj_create_vendor_info", + test_pcep_obj_create_vendor_info); + + CU_add_test(objects_suite, "test_pcep_obj_create_ero", + test_pcep_obj_create_ero); + CU_add_test(objects_suite, "test_pcep_obj_create_rro", + test_pcep_obj_create_rro); + CU_add_test(objects_suite, "test_pcep_obj_create_iro", + test_pcep_obj_create_iro); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_ipv4", + test_pcep_obj_create_ro_subobj_ipv4); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_ipv6", + test_pcep_obj_create_ro_subobj_ipv6); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_unnum", + test_pcep_obj_create_ro_subobj_unnum); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_32label", + test_pcep_obj_create_ro_subobj_32label); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_asn", + test_pcep_obj_create_ro_subobj_asn); + + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_nonai", + test_pcep_obj_create_ro_subobj_sr_nonai); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_ipv4_node", + test_pcep_obj_create_ro_subobj_sr_ipv4_node); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_ipv6_node", + test_pcep_obj_create_ro_subobj_sr_ipv6_node); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_ipv4_adj", + test_pcep_obj_create_ro_subobj_sr_ipv4_adj); + CU_add_test(objects_suite, "test_pcep_obj_create_ro_subobj_sr_ipv6_adj", + test_pcep_obj_create_ro_subobj_sr_ipv6_adj); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj", + test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj); + CU_add_test(objects_suite, + "test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj", + test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj); + + CU_pSuite tools_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Tools Test Suite", pcep_tools_test_suite_setup, + pcep_tools_test_suite_teardown, pcep_tools_test_setup, + pcep_tools_test_teardown); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate", + test_pcep_msg_read_pcep_initiate); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate2", + test_pcep_msg_read_pcep_initiate2); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_update", + test_pcep_msg_read_pcep_update); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open", + test_pcep_msg_read_pcep_open); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open_initiate", + test_pcep_msg_read_pcep_open_initiate); + CU_add_test(tools_suite, "test_validate_message_header", + test_validate_message_header); + CU_add_test(tools_suite, "test_validate_message_objects", + test_validate_message_objects); + CU_add_test(tools_suite, "test_validate_message_objects_invalid", + test_validate_message_objects_invalid); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_open_cisco_pce", + test_pcep_msg_read_pcep_open_cisco_pce); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_update_cisco_pce", + test_pcep_msg_read_pcep_update_cisco_pce); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_report_cisco_pcc", + test_pcep_msg_read_pcep_report_cisco_pcc); + CU_add_test(tools_suite, "test_pcep_msg_read_pcep_initiate_cisco_pcc", + test_pcep_msg_read_pcep_initiate_cisco_pcc); + + CU_pSuite obj_errors_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Object Error Types Test Suite", + pcep_object_error_types_test_suite_setup, + pcep_object_error_types_test_suite_teardown, + pcep_object_error_types_test_setup, + pcep_object_error_types_test_teardown); + CU_add_test(obj_errors_suite, "test_get_error_type_str", + test_get_error_type_str); + CU_add_test(obj_errors_suite, "test_get_error_value_str", + test_get_error_value_str); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_msg_object_error_types_test.c b/pceplib/test/pcep_msg_object_error_types_test.c new file mode 100644 index 0000000..b7198fb --- /dev/null +++ b/pceplib/test/pcep_msg_object_error_types_test.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "pcep_msg_object_error_types.h" +#include "pcep_msg_object_error_types_test.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +int pcep_object_error_types_test_suite_setup(void) +{ + pceplib_memory_reset(); + set_logging_level(LOG_DEBUG); + return 0; +} + +int pcep_object_error_types_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_object_error_types_test_setup(void) +{ +} + +void pcep_object_error_types_test_teardown(void) +{ +} + +void test_get_error_type_str(void) +{ + const char *error_type_str; + int i = 0; + for (; i < MAX_ERROR_TYPE; i++) { + error_type_str = get_error_type_str(i); + CU_ASSERT_PTR_NOT_NULL(error_type_str); + } + + CU_ASSERT_PTR_NULL(get_error_type_str(-1)); + CU_ASSERT_PTR_NULL(get_error_type_str(MAX_ERROR_TYPE)); +} + +void test_get_error_value_str(void) +{ + const char *error_value_str; + int i = 0, j = 0; + + for (; i < MAX_ERROR_TYPE; i++) { + for (; j < MAX_ERROR_VALUE; j++) { + error_value_str = get_error_value_str(i, j); + CU_ASSERT_PTR_NOT_NULL(error_value_str); + } + } + + CU_ASSERT_PTR_NULL(get_error_value_str(-1, 0)); + CU_ASSERT_PTR_NULL(get_error_value_str(MAX_ERROR_TYPE, 0)); + CU_ASSERT_PTR_NULL(get_error_value_str(1, -1)); + CU_ASSERT_PTR_NULL(get_error_value_str(1, MAX_ERROR_VALUE)); +} diff --git a/pceplib/test/pcep_msg_object_error_types_test.h b/pceplib/test/pcep_msg_object_error_types_test.h new file mode 100644 index 0000000..e395520 --- /dev/null +++ b/pceplib/test/pcep_msg_object_error_types_test.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_OBJECT_ERROR_TYPES_TEST_ +#define PCEP_MSG_OBJECT_ERROR_TYPES_TEST_ + +int pcep_object_error_types_test_suite_setup(void); +int pcep_object_error_types_test_suite_teardown(void); +void pcep_object_error_types_test_setup(void); +void pcep_object_error_types_test_teardown(void); +void test_get_error_type_str(void); +void test_get_error_value_str(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_msg_objects_test.c b/pceplib/test/pcep_msg_objects_test.c new file mode 100644 index 0000000..0a1d34d --- /dev/null +++ b/pceplib/test/pcep_msg_objects_test.c @@ -0,0 +1,1301 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_objects_test.h" + +/* + * Notice: + * All of these object Unit Tests encode the created objects by explicitly + * calling pcep_encode_object() thus testing the object creation and the object + * encoding. All APIs expect IPs to be in network byte order. + */ + +static struct pcep_versioning *versioning = NULL; +static uint8_t object_buf[2000]; + +void reset_objects_buffer(void); + +int pcep_objects_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_objects_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void reset_objects_buffer(void) +{ + memset(object_buf, 0, 2000); +} + +void pcep_objects_test_setup(void) +{ + versioning = create_default_pcep_versioning(); + reset_objects_buffer(); +} + +void pcep_objects_test_teardown(void) +{ + destroy_pcep_versioning(versioning); +} + +/* Internal util verification function */ +static void verify_pcep_obj_header2(uint8_t obj_class, uint8_t obj_type, + uint16_t obj_length, const uint8_t *obj_buf) +{ + /* Object Header + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Object-Class | OT |Res|P|I| Object Length (bytes) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + /* Not using CU_ASSERT_EQUAL here, so that in case of failure, + * we can provide more info in the error message. */ + if (obj_buf[0] != obj_class) { + fprintf(stderr, + "Test failure obj_class expected [%d] found [%d]\n", + obj_class, obj_buf[0]); + CU_FAIL("Object Header Class"); + } + + uint8_t found8 = (obj_buf[1] >> 4) & 0x0f; + if (obj_type != found8) { + fprintf(stderr, + "Test failure obj_class [%d] obj_type expected [%d] found [%d]\n", + obj_class, obj_type, found8); + CU_FAIL("Object Header Type"); + } + + uint8_t exp8 = 0; + found8 = obj_buf[1] & 0x0f; + if (exp8 != found8) { + fprintf(stderr, + "Test failure obj_class [%d] flags expected [%d] found [%d]\n", + obj_class, exp8, found8); + CU_FAIL("Object Header Flags"); + } + + uint16_t found16 = ntohs(*((uint16_t *)(obj_buf + 2))); + if (obj_length != found16) { + fprintf(stderr, + "Test failure obj_class [%d] obj_length expected [%d] found [%d]\n", + obj_class, obj_length, found16); + CU_FAIL("Object Header Length"); + } +} + +/* Internal util verification function */ +static void verify_pcep_obj_header(uint8_t obj_class, uint8_t obj_type, + struct pcep_object_header *obj_hdr) +{ + assert(obj_hdr != NULL); + verify_pcep_obj_header2(obj_class, obj_type, + pcep_object_get_length_by_hdr(obj_hdr), + obj_hdr->encoded_object); +} + +void test_pcep_obj_create_open(void) +{ + uint8_t deadtimer = 60; + uint8_t keepalive = 30; + uint8_t sid = 1; + + struct pcep_object_open *open = + pcep_obj_create_open(keepalive, deadtimer, sid, NULL); + + CU_ASSERT_PTR_NOT_NULL(open); + pcep_encode_object(&open->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN, + &open->header); + + CU_ASSERT_EQUAL(open->header.encoded_object[4], + (PCEP_OBJECT_OPEN_VERSION << 5) & 0xe0); + CU_ASSERT_EQUAL(open->header.encoded_object[4] & 0x1f, 0); + CU_ASSERT_EQUAL(open->header.encoded_object[5], keepalive); + CU_ASSERT_EQUAL(open->header.encoded_object[6], deadtimer); + CU_ASSERT_EQUAL(open->header.encoded_object[7], sid); + + pcep_obj_free_object((struct pcep_object_header *)open); +} + +void test_pcep_obj_create_open_with_tlvs(void) +{ + uint8_t deadtimer = 60; + uint8_t keepalive = 30; + uint8_t sid = 1; + double_linked_list *tlv_list = dll_initialize(); + + struct pcep_object_tlv_stateful_pce_capability *tlv = + pcep_tlv_create_stateful_pce_capability(true, true, true, true, + true, true); + dll_append(tlv_list, tlv); + struct pcep_object_open *open = + pcep_obj_create_open(keepalive, deadtimer, sid, tlv_list); + + CU_ASSERT_PTR_NOT_NULL(open); + assert(open != NULL); + pcep_encode_object(&open->header, versioning, object_buf); + verify_pcep_obj_header2(PCEP_OBJ_CLASS_OPEN, PCEP_OBJ_TYPE_OPEN, + pcep_object_get_length_by_hdr(&open->header) + + sizeof(uint32_t) * 2, + open->header.encoded_object); + CU_ASSERT_PTR_NOT_NULL(open->header.tlv_list); + assert(open->header.tlv_list != NULL); + CU_ASSERT_EQUAL(open->header.tlv_list->num_entries, 1); + + CU_ASSERT_EQUAL(open->header.encoded_object[4], + (PCEP_OBJECT_OPEN_VERSION << 5) & 0xe0); + CU_ASSERT_EQUAL(open->header.encoded_object[4] & 0x1f, 0); + CU_ASSERT_EQUAL(open->header.encoded_object[5], keepalive); + CU_ASSERT_EQUAL(open->header.encoded_object[6], deadtimer); + CU_ASSERT_EQUAL(open->header.encoded_object[7], sid); + + pcep_obj_free_object((struct pcep_object_header *)open); +} + +void test_pcep_obj_create_rp(void) +{ + uint32_t reqid = 15; + uint8_t invalid_priority = 100; + uint8_t priority = 7; + + struct pcep_object_rp *rp = pcep_obj_create_rp( + invalid_priority, true, false, false, true, reqid, NULL); + CU_ASSERT_PTR_NULL(rp); + + rp = pcep_obj_create_rp(priority, true, false, false, true, reqid, + NULL); + CU_ASSERT_PTR_NOT_NULL(rp); + pcep_encode_object(&rp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_RP, PCEP_OBJ_TYPE_RP, + &rp->header); + + CU_ASSERT_EQUAL(rp->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(rp->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(rp->header.encoded_object[6], 0); + CU_ASSERT_EQUAL((rp->header.encoded_object[7] & 0x07), priority); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & OBJECT_RP_FLAG_R); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & OBJECT_RP_FLAG_OF); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & ~OBJECT_RP_FLAG_B); + CU_ASSERT_TRUE(rp->header.encoded_object[7] & ~OBJECT_RP_FLAG_O); + CU_ASSERT_EQUAL(*((uint32_t *)(rp->header.encoded_object + 8)), + htonl(reqid)); + + pcep_obj_free_object((struct pcep_object_header *)rp); +} + +void test_pcep_obj_create_nopath(void) +{ + uint8_t ni = 8; + uint32_t errorcode = 42; + + struct pcep_object_nopath *nopath = + pcep_obj_create_nopath(ni, true, errorcode); + + CU_ASSERT_PTR_NOT_NULL(nopath); + pcep_encode_object(&nopath->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_NOPATH, PCEP_OBJ_TYPE_NOPATH, + &nopath->header); + + CU_ASSERT_EQUAL(nopath->header.encoded_object[4], ni); + CU_ASSERT_TRUE(nopath->header.encoded_object[5] & OBJECT_NOPATH_FLAG_C); + CU_ASSERT_EQUAL(nopath->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(nopath->header.encoded_object[7], 0); + + /* Verify the TLV */ + assert(nopath != NULL); + assert(nopath->header.tlv_list != NULL); + CU_ASSERT_PTR_NOT_NULL(nopath->header.tlv_list); + struct pcep_object_tlv_nopath_vector *tlv = + (struct pcep_object_tlv_nopath_vector *) + nopath->header.tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(tlv->header.type, 1); + CU_ASSERT_EQUAL(tlv->error_code, errorcode); + + CU_ASSERT_EQUAL(*((uint16_t *)(nopath->header.encoded_object + 8)), + htons(PCEP_OBJ_TLV_TYPE_NO_PATH_VECTOR)); + CU_ASSERT_EQUAL(*((uint16_t *)(nopath->header.encoded_object + 10)), + htons(4)); + CU_ASSERT_EQUAL(*((uint32_t *)(nopath->header.encoded_object + 12)), + htonl(errorcode)); + + pcep_obj_free_object((struct pcep_object_header *)nopath); +} + +void test_pcep_obj_create_association_ipv4(void) +{ + + uint16_t all_assoc_groups = 0xffff; + struct in_addr src; + inet_pton(AF_INET, "192.168.1.2", &src); + + struct pcep_object_association_ipv4 *assoc = + pcep_obj_create_association_ipv4( + false, PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE, + all_assoc_groups, src); + CU_ASSERT_PTR_NOT_NULL(assoc); + assert(assoc != NULL); + CU_ASSERT_EQUAL(assoc->association_type, + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE); + CU_ASSERT_EQUAL(assoc->association_id, all_assoc_groups); + CU_ASSERT_EQUAL(assoc->header.object_class, PCEP_OBJ_CLASS_ASSOCIATION); + CU_ASSERT_EQUAL(assoc->header.object_type, + PCEP_OBJ_TYPE_ASSOCIATION_IPV4); + CU_ASSERT_EQUAL(assoc->src.s_addr, src.s_addr); + + pcep_obj_free_object((struct pcep_object_header *)assoc); +} + +void test_pcep_obj_create_association_ipv6(void) +{ + uint32_t all_assoc_groups = 0xffff; + struct in6_addr src; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src); + + struct pcep_object_association_ipv6 *assoc = + pcep_obj_create_association_ipv6( + false, PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE, + all_assoc_groups, src); + CU_ASSERT_PTR_NOT_NULL(assoc); + assert(assoc != NULL); + CU_ASSERT_EQUAL(assoc->association_type, + PCEP_ASSOCIATION_TYPE_SR_POLICY_ASSOCIATION_TYPE); + CU_ASSERT_EQUAL(assoc->association_id, all_assoc_groups); + CU_ASSERT_EQUAL(assoc->header.object_class, PCEP_OBJ_CLASS_ASSOCIATION); + CU_ASSERT_EQUAL(assoc->header.object_type, + PCEP_OBJ_TYPE_ASSOCIATION_IPV6); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[0], + (src.__in6_u.__u6_addr32[0])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[1], + (src.__in6_u.__u6_addr32[1])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[2], + (src.__in6_u.__u6_addr32[2])); + CU_ASSERT_EQUAL(assoc->src.__in6_u.__u6_addr32[3], + (src.__in6_u.__u6_addr32[3])); + + pcep_obj_free_object((struct pcep_object_header *)assoc); +} + +void test_pcep_obj_create_endpoint_ipv4(void) +{ + struct in_addr src_ipv4, dst_ipv4; + inet_pton(AF_INET, "192.168.1.2", &src_ipv4); + inet_pton(AF_INET, "172.168.1.2", &dst_ipv4); + + struct pcep_object_endpoints_ipv4 *ipv4 = + pcep_obj_create_endpoint_ipv4(NULL, NULL); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(&src_ipv4, NULL); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(NULL, &dst_ipv4); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_endpoint_ipv4(&src_ipv4, &dst_ipv4); + CU_ASSERT_PTR_NOT_NULL(ipv4); + pcep_encode_object(&ipv4->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV4, &ipv4->header); + CU_ASSERT_EQUAL(*((uint32_t *)(ipv4->header.encoded_object + 4)), + src_ipv4.s_addr); + CU_ASSERT_EQUAL(*((uint32_t *)(ipv4->header.encoded_object + 8)), + dst_ipv4.s_addr); + + pcep_obj_free_object((struct pcep_object_header *)ipv4); +} + +void test_pcep_obj_create_endpoint_ipv6(void) +{ + struct in6_addr src_ipv6, dst_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8446", &dst_ipv6); + + struct pcep_object_endpoints_ipv6 *ipv6 = + pcep_obj_create_endpoint_ipv6(NULL, NULL); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(&src_ipv6, NULL); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(NULL, &dst_ipv6); + CU_ASSERT_PTR_NULL(ipv6); + + ipv6 = pcep_obj_create_endpoint_ipv6(&src_ipv6, &dst_ipv6); + CU_ASSERT_PTR_NOT_NULL(ipv6); + pcep_encode_object(&ipv6->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ENDPOINTS, + PCEP_OBJ_TYPE_ENDPOINT_IPV6, &ipv6->header); + uint32_t *uint32_ptr = (uint32_t *)(ipv6->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], src_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], src_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], src_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], src_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[4], dst_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[5], dst_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[6], dst_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[7], dst_ipv6.__in6_u.__u6_addr32[3]); + + pcep_obj_free_object((struct pcep_object_header *)ipv6); +} + +void test_pcep_obj_create_bandwidth(void) +{ + /* 1.8 => binary 1.11001101 + * exponent = 127 => 0111 1111 + * fraction = 1100 1101 0000 0000 0000 000 */ + float bandwidth = 1.8; + + struct pcep_object_bandwidth *bw = pcep_obj_create_bandwidth(bandwidth); + + CU_ASSERT_PTR_NOT_NULL(bw); + pcep_encode_object(&bw->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_BANDWIDTH, + PCEP_OBJ_TYPE_BANDWIDTH_REQ, &bw->header); + CU_ASSERT_EQUAL(bw->header.encoded_object[4], 0x3f); + CU_ASSERT_EQUAL(bw->header.encoded_object[5], 0xe6); + CU_ASSERT_EQUAL(bw->header.encoded_object[6], 0x66); + CU_ASSERT_EQUAL(bw->header.encoded_object[7], 0x66); + + pcep_obj_free_object((struct pcep_object_header *)bw); +} + +void test_pcep_obj_create_metric(void) +{ + uint8_t type = PCEP_METRIC_BORDER_NODE_COUNT; + /* https://en.wikipedia.org/wiki/IEEE_754-1985 + * 0.15625 = 1/8 + 1/32 = binary 0.00101 = 1.01 x 10^-3 + * Exponent bias = 127, so exponent = (127-3) = 124 = 0111 1100 + * Sign Exponent Fraction + * (8 bits) (23 bits) + * 0.15625 => 0 0111 1100 010 0000 ... 0000 */ + float value = 0.15625; + + struct pcep_object_metric *metric = + pcep_obj_create_metric(type, true, true, value); + + CU_ASSERT_PTR_NOT_NULL(metric); + pcep_encode_object(&metric->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_METRIC, PCEP_OBJ_TYPE_METRIC, + &metric->header); + CU_ASSERT_EQUAL(metric->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(metric->header.encoded_object[5], 0); + CU_ASSERT_TRUE(metric->header.encoded_object[6] & OBJECT_METRIC_FLAC_B); + CU_ASSERT_TRUE(metric->header.encoded_object[6] & OBJECT_METRIC_FLAC_C); + CU_ASSERT_EQUAL(metric->header.encoded_object[7], type); + /* See comments above for explanation of these values */ + CU_ASSERT_EQUAL(metric->header.encoded_object[8], 0x3e); + CU_ASSERT_EQUAL(metric->header.encoded_object[9], 0x20); + CU_ASSERT_EQUAL(metric->header.encoded_object[10], 0x00); + CU_ASSERT_EQUAL(metric->header.encoded_object[11], 0x00); + + pcep_obj_free_object((struct pcep_object_header *)metric); +} + +void test_pcep_obj_create_lspa(void) +{ + uint32_t exclude_any = 10; + uint32_t include_any = 20; + uint32_t include_all = 30; + uint8_t prio = 0; + uint8_t hold_prio = 10; + + struct pcep_object_lspa *lspa = pcep_obj_create_lspa( + exclude_any, include_any, include_all, prio, hold_prio, true); + + CU_ASSERT_PTR_NOT_NULL(lspa); + pcep_encode_object(&lspa->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_LSPA, PCEP_OBJ_TYPE_LSPA, + &lspa->header); + uint32_t *uint32_ptr = (uint32_t *)(lspa->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(exclude_any)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(include_any)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(include_all)); + CU_ASSERT_EQUAL(lspa->header.encoded_object[16], prio); + CU_ASSERT_EQUAL(lspa->header.encoded_object[17], hold_prio); + CU_ASSERT_TRUE(lspa->header.encoded_object[18] & OBJECT_LSPA_FLAG_L); + CU_ASSERT_EQUAL(lspa->header.encoded_object[19], 0); + + pcep_obj_free_object((struct pcep_object_header *)lspa); +} + +void test_pcep_obj_create_svec(void) +{ + struct pcep_object_svec *svec = + pcep_obj_create_svec(true, true, true, NULL); + CU_ASSERT_PTR_NULL(svec); + + double_linked_list *id_list = dll_initialize(); + uint32_t *uint32_ptr = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *uint32_ptr = 10; + dll_append(id_list, uint32_ptr); + + svec = pcep_obj_create_svec(true, true, true, id_list); + CU_ASSERT_PTR_NOT_NULL(svec); + assert(svec != NULL); + pcep_encode_object(&svec->header, versioning, object_buf); + verify_pcep_obj_header2(PCEP_OBJ_CLASS_SVEC, PCEP_OBJ_TYPE_SVEC, + (OBJECT_HEADER_LENGTH + sizeof(uint32_t) * 2), + svec->header.encoded_object); + CU_ASSERT_EQUAL(svec->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(svec->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(svec->header.encoded_object[6], 0); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_S); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_N); + CU_ASSERT_TRUE(svec->header.encoded_object[7] & OBJECT_SVEC_FLAG_L); + CU_ASSERT_EQUAL(*((uint32_t *)(svec->header.encoded_object + 8)), + htonl(*uint32_ptr)); + + pcep_obj_free_object((struct pcep_object_header *)svec); +} + +void test_pcep_obj_create_error(void) +{ + uint8_t error_type = PCEP_ERRT_SESSION_FAILURE; + uint8_t error_value = PCEP_ERRV_RECVD_INVALID_OPEN_MSG; + + struct pcep_object_error *error = + pcep_obj_create_error(error_type, error_value); + + CU_ASSERT_PTR_NOT_NULL(error); + pcep_encode_object(&error->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_ERROR, PCEP_OBJ_TYPE_ERROR, + &error->header); + CU_ASSERT_EQUAL(error->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(error->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(error->header.encoded_object[6], error_type); + CU_ASSERT_EQUAL(error->header.encoded_object[7], error_value); + + pcep_obj_free_object((struct pcep_object_header *)error); +} + +void test_pcep_obj_create_close(void) +{ + uint8_t reason = PCEP_CLOSE_REASON_DEADTIMER; + + struct pcep_object_close *close = pcep_obj_create_close(reason); + + CU_ASSERT_PTR_NOT_NULL(close); + pcep_encode_object(&close->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_CLOSE, PCEP_OBJ_TYPE_CLOSE, + &close->header); + CU_ASSERT_EQUAL(close->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(close->header.encoded_object[7], reason); + + pcep_obj_free_object((struct pcep_object_header *)close); +} + +void test_pcep_obj_create_srp(void) +{ + bool lsp_remove = true; + uint32_t srp_id_number = 0x89674523; + struct pcep_object_srp *srp = + pcep_obj_create_srp(lsp_remove, srp_id_number, NULL); + + CU_ASSERT_PTR_NOT_NULL(srp); + pcep_encode_object(&srp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_SRP, PCEP_OBJ_TYPE_SRP, + &srp->header); + CU_ASSERT_EQUAL(srp->header.encoded_object[4], 0); + CU_ASSERT_EQUAL(srp->header.encoded_object[5], 0); + CU_ASSERT_EQUAL(srp->header.encoded_object[6], 0); + CU_ASSERT_TRUE(srp->header.encoded_object[7] & OBJECT_SRP_FLAG_R); + CU_ASSERT_EQUAL(*((uint32_t *)(srp->header.encoded_object + 8)), + htonl(srp_id_number)); + + pcep_obj_free_object((struct pcep_object_header *)srp); +} + +void test_pcep_obj_create_lsp(void) +{ + uint32_t plsp_id = 0x000fffff; + enum pcep_lsp_operational_status status = PCEP_LSP_OPERATIONAL_ACTIVE; + bool c_flag = true; + bool a_flag = true; + bool r_flag = true; + bool s_flag = true; + bool d_flag = true; + + /* Should return for invalid plsp_id */ + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(0x001fffff, status, c_flag, a_flag, r_flag, + s_flag, d_flag, NULL); + CU_ASSERT_PTR_NULL(lsp); + + /* Should return for invalid status */ + lsp = pcep_obj_create_lsp(plsp_id, 8, c_flag, a_flag, r_flag, s_flag, + d_flag, NULL); + CU_ASSERT_PTR_NULL(lsp); + + lsp = pcep_obj_create_lsp(plsp_id, status, c_flag, a_flag, r_flag, + s_flag, d_flag, NULL); + + CU_ASSERT_PTR_NOT_NULL(lsp); + pcep_encode_object(&lsp->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_LSP, PCEP_OBJ_TYPE_LSP, + &lsp->header); + CU_ASSERT_EQUAL((ntohl(*((uint32_t *)(lsp->header.encoded_object + 4))) + >> 12) & 0x000fffff, + plsp_id); + CU_ASSERT_EQUAL((lsp->header.encoded_object[7] >> 4) & 0x07, status); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_A); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_C); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_D); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_R); + CU_ASSERT_TRUE(lsp->header.encoded_object[7] & OBJECT_LSP_FLAG_S); + + pcep_obj_free_object((struct pcep_object_header *)lsp); +} + +void test_pcep_obj_create_vendor_info(void) +{ + uint32_t enterprise_number = 0x01020304; + uint32_t enterprise_specific_info = 0x05060708; + + struct pcep_object_vendor_info *obj = pcep_obj_create_vendor_info( + enterprise_number, enterprise_specific_info); + + CU_ASSERT_PTR_NOT_NULL(obj); + pcep_encode_object(&obj->header, versioning, object_buf); + verify_pcep_obj_header(PCEP_OBJ_CLASS_VENDOR_INFO, + PCEP_OBJ_TYPE_VENDOR_INFO, &obj->header); + uint32_t *uint32_ptr = (uint32_t *)(obj->header.encoded_object + 4); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(enterprise_number)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(enterprise_specific_info)); + + pcep_obj_free_object((struct pcep_object_header *)obj); +} + +/* Internal test function. The only difference between pcep_obj_create_ero(), + * pcep_obj_create_iro(), and pcep_obj_create_rro() is the object_class + * and the object_type. + */ +typedef struct pcep_object_ro *(*ro_func)(double_linked_list *); +static void test_pcep_obj_create_object_common(ro_func func_to_test, + uint8_t object_class, + uint8_t object_type) +{ + double_linked_list *ero_list = dll_initialize(); + + struct pcep_object_ro *ero = func_to_test(NULL); + CU_ASSERT_PTR_NOT_NULL(ero); + assert(ero != NULL); + pcep_encode_object(&ero->header, versioning, object_buf); + verify_pcep_obj_header2(object_class, object_type, OBJECT_HEADER_LENGTH, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); + + reset_objects_buffer(); + ero = func_to_test(ero_list); + CU_ASSERT_PTR_NOT_NULL(ero); + assert(ero != NULL); + pcep_encode_object(&ero->header, versioning, object_buf); + verify_pcep_obj_header2(object_class, object_type, OBJECT_HEADER_LENGTH, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); + + reset_objects_buffer(); + struct pcep_ro_subobj_32label *ro_subobj = + pcep_obj_create_ro_subobj_32label(false, 0, 101); + ero_list = dll_initialize(); + dll_append(ero_list, ro_subobj); + ero = func_to_test(ero_list); + CU_ASSERT_PTR_NOT_NULL(ero); + assert(ero != NULL); + pcep_encode_object(&ero->header, versioning, object_buf); + /* 4 bytes for obj header + + * 2 bytes for ro_subobj header + + * 2 bytes for lable c-type and flags + + * 4 bytes for label */ + verify_pcep_obj_header2(object_class, object_type, + OBJECT_HEADER_LENGTH + sizeof(uint32_t) * 2, + ero->header.encoded_object); + pcep_obj_free_object((struct pcep_object_header *)ero); +} + +void test_pcep_obj_create_ero(void) +{ + test_pcep_obj_create_object_common( + pcep_obj_create_ero, PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO); +} + +void test_pcep_obj_create_rro(void) +{ + test_pcep_obj_create_object_common( + pcep_obj_create_rro, PCEP_OBJ_CLASS_RRO, PCEP_OBJ_TYPE_RRO); +} + +void test_pcep_obj_create_iro(void) +{ + test_pcep_obj_create_object_common( + pcep_obj_create_iro, PCEP_OBJ_CLASS_IRO, PCEP_OBJ_TYPE_IRO); +} + +/* Internal util function to wrap an RO Subobj in a RO and encode it */ +static struct pcep_object_ro *encode_ro_subobj(struct pcep_object_ro_subobj *sr) +{ + double_linked_list *sr_subobj_list = dll_initialize(); + dll_append(sr_subobj_list, sr); + struct pcep_object_ro *ro = pcep_obj_create_ero(sr_subobj_list); + pcep_encode_object(&ro->header, versioning, object_buf); + + return ro; +} + +static void verify_pcep_obj_ro_header(struct pcep_object_ro *ro, + struct pcep_object_ro_subobj *ro_subobj, + uint8_t ro_subobj_type, bool loose_hop, + uint16_t length) +{ + (void)ro_subobj; + + verify_pcep_obj_header2(PCEP_OBJ_CLASS_ERO, PCEP_OBJ_TYPE_ERO, length, + ro->header.encoded_object); + + /* TODO consider printing the stack trace: + * https://stackoverflow.com/questions/105659/how-can-one-grab-a-stack-trace-in-c + */ + + /* Not using CU_ASSERT_EQUAL here, so that in case of failure, + * we can provide more info in the error message. */ + uint8_t found_type = (ro->header.encoded_object[4] + & 0x7f); /* remove the Loose hop bit */ + if (found_type != ro_subobj_type) { + fprintf(stderr, + "Test failure ro_sub_obj_type expected [%d] found [%d]\n", + ro_subobj_type, found_type); + CU_FAIL("Sub Object Header Type"); + } + + bool loose_hop_found = (ro->header.encoded_object[4] & 0x80); + if (loose_hop != loose_hop_found) { + fprintf(stderr, + "Test failure ro_sub_obj Loose Hop bit expected [%d] found [%d]\n", + loose_hop, loose_hop_found); + CU_FAIL("Sub Object Header Loose Hop bit"); + } + + if (length - 4 != ro->header.encoded_object[5]) { + fprintf(stderr, + "Test failure ro_sub_obj length expected [%d] found [%d]\n", + length - 4, ro->header.encoded_object[5]); + CU_FAIL("Sub Object Length"); + } +} + +static void +verify_pcep_obj_ro_sr_header(struct pcep_object_ro *ro, + struct pcep_object_ro_subobj *ro_subobj, + uint8_t nai_type, bool loose_hop, uint16_t length) +{ + verify_pcep_obj_ro_header(ro, ro_subobj, RO_SUBOBJ_TYPE_SR, loose_hop, + length); + uint8_t found_nai_type = ((ro->header.encoded_object[6] >> 4) & 0x0f); + if (nai_type != found_nai_type) { + fprintf(stderr, + "Test failure ro_sr_sub_obj nai_type expected [%d] found [%d]\n", + nai_type, found_nai_type); + CU_FAIL("Sub Object SR NAI Type"); + } +} + +void test_pcep_obj_create_ro_subobj_ipv4(void) +{ + struct in_addr ro_ipv4; + inet_pton(AF_INET, "192.168.1.2", &ro_ipv4); + uint8_t prefix_len = 8; + + struct pcep_ro_subobj_ipv4 *ipv4 = + pcep_obj_create_ro_subobj_ipv4(true, NULL, prefix_len, false); + CU_ASSERT_PTR_NULL(ipv4); + + ipv4 = pcep_obj_create_ro_subobj_ipv4(false, &ro_ipv4, prefix_len, + true); + CU_ASSERT_PTR_NOT_NULL(ipv4); + struct pcep_object_ro *ro = encode_ro_subobj(&ipv4->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv4->ro_subobj, RO_SUBOBJ_TYPE_IPV4, + false, sizeof(uint32_t) * 3); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 6)), + ro_ipv4.s_addr); + CU_ASSERT_EQUAL(ro->header.encoded_object[10], prefix_len); + CU_ASSERT_TRUE(ro->header.encoded_object[11] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + ipv4 = pcep_obj_create_ro_subobj_ipv4(true, &ro_ipv4, prefix_len, + false); + CU_ASSERT_PTR_NOT_NULL(ipv4); + ro = encode_ro_subobj(&ipv4->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv4->ro_subobj, RO_SUBOBJ_TYPE_IPV4, + true, sizeof(uint32_t) * 3); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 6)), + ro_ipv4.s_addr); + CU_ASSERT_EQUAL(ro->header.encoded_object[10], prefix_len); + CU_ASSERT_EQUAL(ro->header.encoded_object[11], 0); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_ipv6(void) +{ + struct in6_addr ro_ipv6; + uint8_t prefix_len = 16; + + struct pcep_ro_subobj_ipv6 *ipv6 = + pcep_obj_create_ro_subobj_ipv6(true, NULL, prefix_len, true); + CU_ASSERT_PTR_NULL(ipv6); + + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ro_ipv6); + ipv6 = pcep_obj_create_ro_subobj_ipv6(false, &ro_ipv6, prefix_len, + true); + CU_ASSERT_PTR_NOT_NULL(ipv6); + struct pcep_object_ro *ro = encode_ro_subobj(&ipv6->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv6->ro_subobj, RO_SUBOBJ_TYPE_IPV6, + false, sizeof(uint32_t) * 6); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 6); + CU_ASSERT_EQUAL(uint32_ptr[0], ro_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ro_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ro_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ro_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(ro->header.encoded_object[22], prefix_len); + CU_ASSERT_TRUE(ro->header.encoded_object[23] + & OBJECT_SUBOBJ_IP_FLAG_LOCAL_PROT); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + ipv6 = pcep_obj_create_ro_subobj_ipv6(true, &ro_ipv6, prefix_len, + false); + CU_ASSERT_PTR_NOT_NULL(ipv6); + ro = encode_ro_subobj(&ipv6->ro_subobj); + verify_pcep_obj_ro_header(ro, &ipv6->ro_subobj, RO_SUBOBJ_TYPE_IPV6, + true, sizeof(uint32_t) * 6); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 6); + CU_ASSERT_EQUAL(uint32_ptr[0], ro_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ro_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ro_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ro_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(ro->header.encoded_object[22], prefix_len); + CU_ASSERT_EQUAL(ro->header.encoded_object[23], 0); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_unnum(void) +{ + struct in_addr router_id; + uint32_t if_id = 123; + + struct pcep_ro_subobj_unnum *unnum = + pcep_obj_create_ro_subobj_unnum(NULL, if_id); + CU_ASSERT_PTR_NULL(unnum); + + inet_pton(AF_INET, "192.168.1.2", &router_id); + unnum = pcep_obj_create_ro_subobj_unnum(&router_id, if_id); + CU_ASSERT_PTR_NOT_NULL(unnum); + struct pcep_object_ro *ro = encode_ro_subobj(&unnum->ro_subobj); + verify_pcep_obj_ro_header(ro, &unnum->ro_subobj, RO_SUBOBJ_TYPE_UNNUM, + false, sizeof(uint32_t) * 4); + CU_ASSERT_EQUAL(ro->header.encoded_object[6], 0); + CU_ASSERT_EQUAL(ro->header.encoded_object[7], 0); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + router_id.s_addr); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 12)), + htonl(if_id)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_32label(void) +{ + uint8_t class_type = 1; + uint32_t label = 0xeeffaabb; + + struct pcep_ro_subobj_32label *label32 = + pcep_obj_create_ro_subobj_32label(true, class_type, label); + CU_ASSERT_PTR_NOT_NULL(label32); + struct pcep_object_ro *ro = encode_ro_subobj(&label32->ro_subobj); + verify_pcep_obj_ro_header(ro, &label32->ro_subobj, RO_SUBOBJ_TYPE_LABEL, + false, sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[6] + & OBJECT_SUBOBJ_LABEL_FLAG_GLOGAL); + CU_ASSERT_EQUAL(ro->header.encoded_object[7], class_type); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + htonl(label)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_asn(void) +{ + uint16_t asn = 0x0102; + + struct pcep_ro_subobj_asn *asn_obj = pcep_obj_create_ro_subobj_asn(asn); + CU_ASSERT_PTR_NOT_NULL(asn_obj); + struct pcep_object_ro *ro = encode_ro_subobj(&asn_obj->ro_subobj); + verify_pcep_obj_ro_header(ro, &asn_obj->ro_subobj, RO_SUBOBJ_TYPE_ASN, + false, sizeof(uint32_t) * 2); + CU_ASSERT_EQUAL(*((uint16_t *)(ro->header.encoded_object + 6)), + htons(asn)); + + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_nonai(void) +{ + uint32_t sid = 0x01020304; + + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_nonai(false, sid, false, false); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_ABSENT, false, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + pcep_obj_free_object((struct pcep_object_header *)ro); + + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_nonai(true, sid, true, true); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_ABSENT, true, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv4_node(void) +{ + uint32_t sid = 0x01020304; + struct in_addr ipv4_node_id; + inet_pton(AF_INET, "192.168.1.2", &ipv4_node_id); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, ipv4_node_id) */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv4_node( + true, false, true, true, sid, NULL); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv4_node(true, true, false, false, + sid, &ipv4_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + assert(sr != NULL); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, true, + sizeof(uint32_t) * 3); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + ipv4_node_id.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET, "192.168.1.2", &ipv4_node_id); + sr = pcep_obj_create_ro_subobj_sr_ipv4_node(false, false, true, true, + sid, &ipv4_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + assert(sr != NULL); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE, false, + sizeof(uint32_t) * 4); + assert(ro != NULL); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 8)), + htonl(sid)); + CU_ASSERT_EQUAL(*((uint32_t *)(ro->header.encoded_object + 12)), + ipv4_node_id.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv6_node(void) +{ + uint32_t sid = 0x01020304; + struct in6_addr ipv6_node_id; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ipv6_node_id); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, ipv6_node_id) */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv6_node( + false, true, true, true, sid, NULL); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv6_node(true, true, true, true, sid, + &ipv6_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, true, + sizeof(uint32_t) * 6); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], ipv6_node_id.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], ipv6_node_id.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], ipv6_node_id.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], ipv6_node_id.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &ipv6_node_id); + sr = pcep_obj_create_ro_subobj_sr_ipv6_node(false, false, true, true, + sid, &ipv6_node_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_NODE, false, + sizeof(uint32_t) * 7); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], ipv6_node_id.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], ipv6_node_id.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], ipv6_node_id.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], ipv6_node_id.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv4_adj(void) +{ + struct in_addr local_ipv4; + struct in_addr remote_ipv4; + inet_pton(AF_INET, "192.168.1.2", &local_ipv4); + inet_pton(AF_INET, "172.168.1.2", &remote_ipv4); + + uint32_t sid = ENCODE_SR_ERO_SID(3, 7, 0, 188); + CU_ASSERT_EQUAL(sid, 16060); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv4, remote_ipv4) + */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv4_adj( + false, true, true, true, sid, NULL, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(false, true, true, true, sid, + &local_ipv4, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(false, true, true, true, sid, + NULL, &remote_ipv4); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj(true, true, true, true, sid, + &local_ipv4, &remote_ipv4); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, true, + sizeof(uint32_t) * 4); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + assert(sr != NULL); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv4.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[1], remote_ipv4.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + inet_pton(AF_INET, "192.168.1.2", &local_ipv4); + inet_pton(AF_INET, "172.168.1.2", &remote_ipv4); + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_ipv4_adj( + false, false, true, true, sid, &local_ipv4, &remote_ipv4); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV4_ADJACENCY, false, + sizeof(uint32_t) * 5); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv4.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], remote_ipv4.s_addr); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_ipv6_adj(void) +{ + uint32_t sid = 0x01020304; + struct in6_addr local_ipv6; + struct in6_addr remote_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv6, remote_ipv6) + */ + struct pcep_ro_subobj_sr *sr = pcep_obj_create_ro_subobj_sr_ipv6_adj( + false, true, true, true, sid, NULL, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(false, true, true, true, sid, + &local_ipv6, NULL); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(false, true, true, true, sid, + NULL, &remote_ipv6); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj(true, true, true, true, sid, + &local_ipv6, &remote_ipv6); + CU_ASSERT_PTR_NOT_NULL(sr); + assert(sr != NULL); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, true, + sizeof(uint32_t) * 10); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[3]); + + CU_ASSERT_EQUAL(uint32_ptr[4], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + sr = pcep_obj_create_ro_subobj_sr_ipv6_adj( + false, false, true, false, sid, &local_ipv6, &remote_ipv6); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header(ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_IPV6_ADJACENCY, false, + sizeof(uint32_t) * 11); + /* All flags are false */ + CU_ASSERT_EQUAL(ro->header.encoded_object[7], 0); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_ipv6.__in6_u.__u6_addr32[3]); + + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[3]); + pcep_obj_free_object((struct pcep_object_header *)ro); +} + +void test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj(void) +{ + uint32_t sid = 0x01020304; + uint32_t local_node_id = 0x11223344; + uint32_t local_if_id = 0x55667788; + uint32_t remote_node_id = 0x99aabbcc; + uint32_t remote_if_id = 0xddeeff11; + + /* (loose_hop, sid_absent, c_flag, m_flag, + sid, local_node_id, local_if_id, remote_node_id, remote_if_id) */ + + /* Test the sid is absent */ + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + true, true, true, true, sid, local_node_id, local_if_id, + remote_node_id, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, true, + sizeof(uint32_t) * 6); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + assert(sr != NULL); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_node_id); + CU_ASSERT_EQUAL(uint32_ptr[1], local_if_id); + CU_ASSERT_EQUAL(uint32_ptr[2], remote_node_id); + CU_ASSERT_EQUAL(uint32_ptr[3], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj( + false, false, true, true, sid, local_node_id, local_if_id, + remote_node_id, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_UNNUMBERED_IPV4_ADJACENCY, false, + sizeof(uint32_t) * 7); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_node_id); + CU_ASSERT_EQUAL(uint32_ptr[2], local_if_id); + CU_ASSERT_EQUAL(uint32_ptr[3], remote_node_id); + CU_ASSERT_EQUAL(uint32_ptr[4], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* TODO Test draft07 types */ +} + +void test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj(void) +{ + uint32_t sid = 0x01020304; + uint32_t local_if_id = 0x11002200; + uint32_t remote_if_id = 0x00110022; + struct in6_addr local_ipv6; + struct in6_addr remote_ipv6; + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + + /* (loose_hop, sid_absent, c_flag, m_flag, sid, local_ipv6, local_if_id, + * remote_ipv6, remote_if_id */ + struct pcep_ro_subobj_sr *sr = + pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, NULL, local_if_id, NULL, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, &local_ipv6, local_if_id, NULL, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, true, true, true, sid, NULL, local_if_id, &remote_ipv6, + remote_if_id); + CU_ASSERT_PTR_NULL(sr); + + /* Test the sid is absent */ + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + true, true, true, true, sid, &local_ipv6, local_if_id, + &remote_ipv6, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + struct pcep_object_ro *ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, true, + sizeof(uint32_t) * 12); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_M); + assert(sr != NULL); + CU_ASSERT_EQUAL(sr->sid, 0); + uint32_t *uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_if_id); + + CU_ASSERT_EQUAL(uint32_ptr[5], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[9], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); + + /* Test the sid is present */ + inet_pton(AF_INET6, "2001:db8::8a2e:370:8221", &local_ipv6); + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &remote_ipv6); + reset_objects_buffer(); + sr = pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj( + false, false, true, true, sid, &local_ipv6, local_if_id, + &remote_ipv6, remote_if_id); + CU_ASSERT_PTR_NOT_NULL(sr); + ro = encode_ro_subobj(&sr->ro_subobj); + verify_pcep_obj_ro_sr_header( + ro, &sr->ro_subobj, + PCEP_SR_SUBOBJ_NAI_LINK_LOCAL_IPV6_ADJACENCY, false, + sizeof(uint32_t) * 13); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_C); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & OBJECT_SUBOBJ_SR_FLAG_M); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_S); + CU_ASSERT_TRUE(ro->header.encoded_object[7] & ~OBJECT_SUBOBJ_SR_FLAG_F); + uint32_ptr = (uint32_t *)(ro->header.encoded_object + 8); + CU_ASSERT_EQUAL(uint32_ptr[0], htonl(sid)); + CU_ASSERT_EQUAL(uint32_ptr[1], local_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[2], local_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[3], local_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[4], local_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[5], local_if_id); + + CU_ASSERT_EQUAL(uint32_ptr[6], remote_ipv6.__in6_u.__u6_addr32[0]); + CU_ASSERT_EQUAL(uint32_ptr[7], remote_ipv6.__in6_u.__u6_addr32[1]); + CU_ASSERT_EQUAL(uint32_ptr[8], remote_ipv6.__in6_u.__u6_addr32[2]); + CU_ASSERT_EQUAL(uint32_ptr[9], remote_ipv6.__in6_u.__u6_addr32[3]); + CU_ASSERT_EQUAL(uint32_ptr[10], remote_if_id); + pcep_obj_free_object((struct pcep_object_header *)ro); +} diff --git a/pceplib/test/pcep_msg_objects_test.h b/pceplib/test/pcep_msg_objects_test.h new file mode 100644 index 0000000..3a3577b --- /dev/null +++ b/pceplib/test/pcep_msg_objects_test.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + + +#ifndef PCEP_MSG_OBJECTS_TEST_H_ +#define PCEP_MSG_OBJECTS_TEST_H_ + +int pcep_objects_test_suite_setup(void); +int pcep_objects_test_suite_teardown(void); +void pcep_objects_test_setup(void); +void pcep_objects_test_teardown(void); +void test_pcep_obj_create_open(void); +void test_pcep_obj_create_open_with_tlvs(void); +void test_pcep_obj_create_rp(void); +void test_pcep_obj_create_nopath(void); +void test_pcep_obj_create_endpoint_ipv4(void); +void test_pcep_obj_create_endpoint_ipv6(void); +void test_pcep_obj_create_association_ipv4(void); +void test_pcep_obj_create_association_ipv6(void); +void test_pcep_obj_create_bandwidth(void); +void test_pcep_obj_create_metric(void); +void test_pcep_obj_create_lspa(void); +void test_pcep_obj_create_svec(void); +void test_pcep_obj_create_error(void); +void test_pcep_obj_create_close(void); +void test_pcep_obj_create_srp(void); +void test_pcep_obj_create_lsp(void); +void test_pcep_obj_create_vendor_info(void); +void test_pcep_obj_create_ero(void); +void test_pcep_obj_create_rro(void); +void test_pcep_obj_create_iro(void); +void test_pcep_obj_create_ro_subobj_ipv4(void); +void test_pcep_obj_create_ro_subobj_ipv6(void); +void test_pcep_obj_create_ro_subobj_unnum(void); +void test_pcep_obj_create_ro_subobj_32label(void); +void test_pcep_obj_create_ro_subobj_asn(void); +void test_pcep_obj_create_ro_subobj_sr_nonai(void); +void test_pcep_obj_create_ro_subobj_sr_ipv4_node(void); +void test_pcep_obj_create_ro_subobj_sr_ipv6_node(void); +void test_pcep_obj_create_ro_subobj_sr_ipv4_adj(void); +void test_pcep_obj_create_ro_subobj_sr_ipv6_adj(void); +void test_pcep_obj_create_ro_subobj_sr_unnumbered_ipv4_adj(void); +void test_pcep_obj_create_ro_subobj_sr_linklocal_ipv6_adj(void); + +#endif diff --git a/pceplib/test/pcep_msg_tests_valgrind.sh b/pceplib/test/pcep_msg_tests_valgrind.sh new file mode 100755 index 0000000..4a9a999 --- /dev/null +++ b/pceplib/test/pcep_msg_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_msg_tests diff --git a/pceplib/test/pcep_msg_tlvs_test.c b/pceplib/test/pcep_msg_tlvs_test.c new file mode 100644 index 0000000..fc11205 --- /dev/null +++ b/pceplib/test/pcep_msg_tlvs_test.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __FreeBSD__ +#include +#else +#include +#endif /* __FreeBSD__ */ +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tlvs.h" +#include "pcep_msg_tools.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_tlvs_test.h" + +/* + * Notice: + * All of these TLV Unit Tests encode the created TLVs by explicitly calling + * pcep_encode_tlv() thus testing the TLV creation and the TLV encoding. + * All APIs expect IPs to be in network byte order. + */ + +static struct pcep_versioning *versioning = NULL; +static uint8_t tlv_buf[2000]; + +void reset_tlv_buffer(void); + +int pcep_tlvs_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_tlvs_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void reset_tlv_buffer(void) +{ + memset(tlv_buf, 0, 2000); +} + +void pcep_tlvs_test_setup(void) +{ + versioning = create_default_pcep_versioning(); + reset_tlv_buffer(); +} + +void pcep_tlvs_test_teardown(void) +{ + destroy_pcep_versioning(versioning); +} + +void test_pcep_tlv_create_stateful_pce_capability(void) +{ + struct pcep_object_tlv_stateful_pce_capability *tlv = + pcep_tlv_create_stateful_pce_capability(true, true, true, true, + true, true); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_TRUE(tlv->flag_u_lsp_update_capability); + CU_ASSERT_TRUE(tlv->flag_s_include_db_version); + CU_ASSERT_TRUE(tlv->flag_i_lsp_instantiation_capability); + CU_ASSERT_TRUE(tlv->flag_t_triggered_resync); + CU_ASSERT_TRUE(tlv->flag_d_delta_lsp_sync); + CU_ASSERT_TRUE(tlv->flag_f_triggered_initial_sync); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0x3f); + /* TODO add a new function: verify_tlv_header(tlv->header.encoded_tlv) + * to all tests */ + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_speaker_entity_id(void) +{ + struct pcep_object_tlv_speaker_entity_identifier *tlv = + pcep_tlv_create_speaker_entity_id(NULL); + CU_ASSERT_PTR_NULL(tlv); + + double_linked_list *list = dll_initialize(); + tlv = pcep_tlv_create_speaker_entity_id(list); + CU_ASSERT_PTR_NULL(tlv); + if (tlv != NULL) + pceplib_free(PCEPLIB_INFRA, tlv); + + uint32_t *speaker_entity = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(uint32_t)); + *speaker_entity = 42; + dll_append(list, speaker_entity); + tlv = pcep_tlv_create_speaker_entity_id(list); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SPEAKER_ENTITY_ID); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_PTR_NOT_NULL(tlv->speaker_entity_id_list); + assert(tlv->speaker_entity_id_list != NULL); + CU_ASSERT_EQUAL(tlv->speaker_entity_id_list->num_entries, 1); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(*speaker_entity)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_lsp_db_version(void) +{ + uint64_t lsp_db_version = 0xf005ba11ba5eba11; + struct pcep_object_tlv_lsp_db_version *tlv = + pcep_tlv_create_lsp_db_version(lsp_db_version); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint64_t)); + CU_ASSERT_EQUAL(tlv->lsp_db_version, lsp_db_version); + CU_ASSERT_EQUAL(*((uint64_t *)(tlv->header.encoded_tlv + 4)), + be64toh(lsp_db_version)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_path_setup_type(void) +{ + uint8_t pst = 0x89; + + struct pcep_object_tlv_path_setup_type *tlv = + pcep_tlv_create_path_setup_type(pst); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + CU_ASSERT_EQUAL(tlv->path_setup_type, pst); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x000000FF & pst)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_path_setup_type_capability(void) +{ + /* The sub_tlv list is optional */ + + /* Should return NULL if pst_list is NULL */ + struct pcep_object_tlv_path_setup_type_capability *tlv = + pcep_tlv_create_path_setup_type_capability(NULL, NULL); + CU_ASSERT_PTR_NULL(tlv); + + /* Should return NULL if pst_list is empty */ + double_linked_list *pst_list = dll_initialize(); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, NULL); + CU_ASSERT_PTR_NULL(tlv); + if (tlv != NULL) + pcep_obj_free_tlv(&tlv->header); + + /* Should still return NULL if pst_list is NULL */ + double_linked_list *sub_tlv_list = dll_initialize(); + tlv = pcep_tlv_create_path_setup_type_capability(NULL, sub_tlv_list); + CU_ASSERT_PTR_NULL(tlv); + if (tlv != NULL) + pcep_obj_free_tlv(&tlv->header); + + /* Should still return NULL if pst_list is empty */ + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NULL(tlv); + if (tlv != NULL) + pcep_obj_free_tlv(&tlv->header); + + /* Test only populating the pst list */ + uint8_t *pst1 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + uint8_t *pst2 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + uint8_t *pst3 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + *pst1 = 1; + *pst2 = 2; + *pst3 = 3; + dll_append(pst_list, pst1); + dll_append(pst_list, pst2); + dll_append(pst_list, pst3); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NOT_NULL(tlv); + if (tlv == NULL) { + CU_ASSERT_TRUE(tlv != NULL); + return; + } + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 2); + CU_ASSERT_PTR_NOT_NULL(tlv->pst_list); + assert(tlv != NULL); + CU_ASSERT_EQUAL(tlv->pst_list->num_entries, 3); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000003)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(0x01020300)); + pcep_obj_free_tlv(&tlv->header); + + /* Now test populating both the pst_list and the sub_tlv_list */ + reset_tlv_buffer(); + struct pcep_object_tlv_header *sub_tlv = + (struct pcep_object_tlv_header *) + pcep_tlv_create_sr_pce_capability(true, true, 0); + pst_list = dll_initialize(); + sub_tlv_list = dll_initialize(); + pst1 = pceplib_malloc(PCEPLIB_MESSAGES, 1); + *pst1 = 1; + dll_append(pst_list, pst1); + dll_append(sub_tlv_list, sub_tlv); + tlv = pcep_tlv_create_path_setup_type_capability(pst_list, + sub_tlv_list); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + sizeof(uint32_t) * 2 + TLV_HEADER_LENGTH + + sub_tlv->encoded_tlv_length); + CU_ASSERT_PTR_NOT_NULL(tlv->pst_list); + CU_ASSERT_PTR_NOT_NULL(tlv->sub_tlv_list); + uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + uint16_t *uint16_ptr = (uint16_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(tlv->header.encoded_tlv_length)); + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000001)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(0x01000000)); + /* Verify the Sub-TLV */ + uint16_ptr = (uint16_t *)(tlv->header.encoded_tlv + 12); + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(4)); + CU_ASSERT_EQUAL(uint16_ptr[2], 0); + CU_ASSERT_EQUAL(uint16_ptr[3], htons(0x0300)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_sr_pce_capability(void) +{ + struct pcep_object_tlv_sr_pce_capability *tlv = + pcep_tlv_create_sr_pce_capability(true, true, 8); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + uint16_t *uint16_ptr = (uint16_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint16_ptr[0], + htons(PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY)); + CU_ASSERT_EQUAL(uint16_ptr[1], htons(tlv->header.encoded_tlv_length)); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(0x00000308)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_symbolic_path_name(void) +{ + /* char *symbolic_path_name, uint16_t symbolic_path_name_length); */ + char path_name[16] = "Some Path Name"; + uint16_t path_name_length = 14; + struct pcep_object_tlv_symbolic_path_name *tlv = + pcep_tlv_create_symbolic_path_name(path_name, path_name_length); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, path_name_length); + /* Test the padding is correct */ + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[4]), + &path_name[0], 4)); + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[8]), + &path_name[4], 4)); + CU_ASSERT_EQUAL(0, strncmp((char *)&(tlv->header.encoded_tlv[12]), + &path_name[8], 4)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[16], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[17], 'e'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[18], 0); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[19], 0); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_symbolic_path_name(path_name, 3); + CU_ASSERT_PTR_NOT_NULL(tlv); + printf("El tlv es %p", tlv); + assert(tlv != NULL); // crash si FALSE + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 3); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[4], 'S'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[5], 'o'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[6], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_ipv4_lsp_identifiers(void) +{ + struct in_addr sender_ip, endpoint_ip; + uint16_t lsp_id = 7; + uint16_t tunnel_id = 16; + struct in_addr extended_tunnel_id; + extended_tunnel_id.s_addr = 256; + inet_pton(AF_INET, "192.168.1.1", &sender_ip); + inet_pton(AF_INET, "192.168.1.2", &endpoint_ip); + + struct pcep_object_tlv_ipv4_lsp_identifier *tlv = + pcep_tlv_create_ipv4_lsp_identifiers(NULL, &endpoint_ip, lsp_id, + tunnel_id, + &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers( + &sender_ip, NULL, lsp_id, tunnel_id, &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers( + NULL, NULL, lsp_id, tunnel_id, &extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + assert(tlv == NULL); + + tlv = pcep_tlv_create_ipv4_lsp_identifiers(&sender_ip, &endpoint_ip, + lsp_id, tunnel_id, + &extended_tunnel_id); + CU_ASSERT_PTR_NOT_NULL(tlv); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 4); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], sender_ip.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + CU_ASSERT_EQUAL(uint32_ptr[3], extended_tunnel_id.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[4], endpoint_ip.s_addr); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_ipv4_lsp_identifiers(&sender_ip, &endpoint_ip, + lsp_id, tunnel_id, NULL); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t) * 4); + uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], sender_ip.s_addr); + CU_ASSERT_EQUAL(uint32_ptr[2], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + CU_ASSERT_EQUAL(uint32_ptr[3], INADDR_ANY); + CU_ASSERT_EQUAL(uint32_ptr[4], endpoint_ip.s_addr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_ipv6_lsp_identifiers(void) +{ + struct in6_addr sender_ip, endpoint_ip; + uint16_t lsp_id = 3; + uint16_t tunnel_id = 16; + uint32_t extended_tunnel_id[4]; + + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &sender_ip); + inet_pton(AF_INET6, "2001:db8::8a2e:370:8446", &endpoint_ip); + extended_tunnel_id[0] = 1; + extended_tunnel_id[1] = 2; + extended_tunnel_id[2] = 3; + extended_tunnel_id[3] = 4; + + struct pcep_object_tlv_ipv6_lsp_identifier *tlv = + pcep_tlv_create_ipv6_lsp_identifiers( + NULL, &endpoint_ip, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + &sender_ip, NULL, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + NULL, NULL, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NULL(tlv); + assert(tlv == NULL); + + tlv = pcep_tlv_create_ipv6_lsp_identifiers( + &sender_ip, &endpoint_ip, lsp_id, tunnel_id, + (struct in6_addr *)&extended_tunnel_id); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + PCEP_OBJ_TLV_TYPE_IPV6_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 52); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[5], + (uint32_t)(htons(tunnel_id) << 16) | htons(lsp_id)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_pol_id_ipv4(void) +{ + uint32_t color = 1; + struct in_addr src; + inet_pton(AF_INET, "192.168.1.2", &src); + + struct pcep_object_tlv_srpag_pol_id *tlv = + pcep_tlv_create_srpag_pol_id_ipv4(color, (void *)&src); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID)); + CU_ASSERT_EQUAL( + tlv->header.encoded_tlv_length, + (8 /*draft-barth-pce-segment-routing-policy-cp-04#5.1*/)); + CU_ASSERT_EQUAL(tlv->color, (color)); + uint32_t aux_color = htonl(color); // Is color right encoded + CU_ASSERT_EQUAL(0, memcmp(&tlv_buf[0] + TLV_HEADER_LENGTH, &aux_color, + sizeof(color))); + CU_ASSERT_EQUAL(tlv->end_point.ipv4.s_addr, (src.s_addr)); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_pol_id *dec_tlv = + (struct pcep_object_tlv_srpag_pol_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->color, dec_tlv->color); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_pol_id_ipv6(void) +{ + + uint32_t color = 1; + struct in6_addr src; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &src); + + struct pcep_object_tlv_srpag_pol_id *tlv = + pcep_tlv_create_srpag_pol_id_ipv6(color, &src); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_ID)); + CU_ASSERT_EQUAL( + tlv->header.encoded_tlv_length, + (20 /*draft-barth-pce-segment-routing-policy-cp-04#5.1*/)); + CU_ASSERT_EQUAL(tlv->color, (color)); + CU_ASSERT_EQUAL(0, memcmp(&tlv->end_point.ipv6, &src, sizeof(src))); + + uint32_t aux_color = htonl(color); + CU_ASSERT_EQUAL(0, memcmp(&aux_color, tlv_buf + TLV_HEADER_LENGTH, + sizeof(tlv->color))); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_pol_id *dec_tlv = + (struct pcep_object_tlv_srpag_pol_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->color, dec_tlv->color); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} +void test_pcep_tlv_create_srpag_pol_name(void) +{ + const char *pol_name = "Some Pol Name"; + + struct pcep_object_tlv_srpag_pol_name *tlv = + pcep_tlv_create_srpag_pol_name(pol_name, strlen(pol_name)); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_POL_NAME)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + (normalize_pcep_tlv_length(strlen(pol_name)))); + CU_ASSERT_EQUAL(0, strcmp(pol_name, (char *)tlv->name)); + + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_cp_id(void) +{ + // draft-ietf-spring-segment-routing-policy-06.pdf#2.3 + // 10 PCEP, 20 BGP SR Policy, 30 Via Configuration + uint8_t proto_origin = 10; + uint32_t ASN = 0; + struct in6_addr with_mapped_ipv4; + inet_pton(AF_INET6, "::ffff:192.0.2.128", &with_mapped_ipv4); + uint32_t discriminator = 0; + + struct pcep_object_tlv_srpag_cp_id *tlv = pcep_tlv_create_srpag_cp_id( + proto_origin, ASN, &with_mapped_ipv4, discriminator); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_ID)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + (sizeof(proto_origin) + sizeof(ASN) + + sizeof(with_mapped_ipv4) + sizeof(discriminator))); + CU_ASSERT_EQUAL(tlv->proto, (proto_origin)); + CU_ASSERT_EQUAL(tlv->orig_asn, (ASN)); + CU_ASSERT_EQUAL(0, memcmp(&tlv->orig_addres, &with_mapped_ipv4, + sizeof(with_mapped_ipv4))); + CU_ASSERT_EQUAL(tlv->discriminator, (discriminator)); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_cp_id *dec_tlv = + (struct pcep_object_tlv_srpag_cp_id *)dec_hdr; + CU_ASSERT_EQUAL(tlv->proto, dec_tlv->proto); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_srpag_cp_pref(void) +{ + uint32_t preference_default = 100; + + struct pcep_object_tlv_srpag_cp_pref *tlv = + pcep_tlv_create_srpag_cp_pref(preference_default); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, + (PCEP_OBJ_TLV_TYPE_SRPOLICY_CPATH_PREFERENCE)); + printf(" encoded length vs sizeof pref (%d) vs (%ld)\n", + tlv->header.encoded_tlv_length, sizeof(preference_default)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, + sizeof(preference_default)); + CU_ASSERT_EQUAL(tlv->preference, (preference_default)); + uint32_t aux_pref = htonl(preference_default); // Is pref right encoded + CU_ASSERT_EQUAL(0, memcmp(tlv_buf + TLV_HEADER_LENGTH, &aux_pref, + sizeof(preference_default))); + // Are simetrical? + struct pcep_object_tlv_header *dec_hdr = pcep_decode_tlv(tlv_buf); + struct pcep_object_tlv_srpag_cp_pref *dec_tlv = + (struct pcep_object_tlv_srpag_cp_pref *)dec_hdr; + CU_ASSERT_EQUAL(tlv->preference, dec_tlv->preference); + + pceplib_free(PCEPLIB_MESSAGES, dec_hdr); + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_lsp_error_code(void) +{ + struct pcep_object_tlv_lsp_error_code *tlv = + pcep_tlv_create_lsp_error_code( + PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_LSP_ERROR_CODE); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, sizeof(uint32_t)); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], + htonl(PCEP_TLV_LSP_ERROR_CODE_RSVP_SIGNALING_ERROR)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_rsvp_ipv4_error_spec(void) +{ + struct in_addr error_node_ip; + inet_pton(AF_INET, "192.168.1.1", &error_node_ip); + uint8_t error_code = 8; + uint16_t error_value = 0xaabb; + + struct pcep_object_tlv_rsvp_error_spec *tlv = + pcep_tlv_create_rsvp_ipv4_error_spec(NULL, error_code, + error_value); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_rsvp_ipv4_error_spec(&error_node_ip, error_code, + error_value); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 12); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_rsvp_ipv6_error_spec(void) +{ + struct in6_addr error_node_ip; + inet_pton(AF_INET6, "2001:db8::8a2e:370:7334", &error_node_ip); + uint8_t error_code = 8; + uint16_t error_value = 0xaabb; + + struct pcep_object_tlv_rsvp_error_spec *tlv = + pcep_tlv_create_rsvp_ipv6_error_spec(NULL, error_code, + error_value); + CU_ASSERT_PTR_NULL(tlv); + + tlv = pcep_tlv_create_rsvp_ipv6_error_spec(&error_node_ip, error_code, + error_value); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_RSVP_ERROR_SPEC); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 24); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_nopath_vector(void) +{ + uint32_t enterprise_number = 0x01020304; + uint32_t enterprise_specific_info = 0x05060708; + + struct pcep_object_tlv_vendor_info *tlv = pcep_tlv_create_vendor_info( + enterprise_number, enterprise_specific_info); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, PCEP_OBJ_TLV_TYPE_VENDOR_INFO); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 8); + uint32_t *uint32_ptr = (uint32_t *)tlv->header.encoded_tlv; + CU_ASSERT_EQUAL(uint32_ptr[1], htonl(enterprise_number)); + CU_ASSERT_EQUAL(uint32_ptr[2], htonl(enterprise_specific_info)); + + pcep_obj_free_tlv(&tlv->header); +} + +void test_pcep_tlv_create_arbitrary(void) +{ + char data[16] = "Some Data"; + uint16_t data_length = 9; + uint16_t tlv_id_unknown = 1; // 65505; // Whatever id to be created + struct pcep_object_tlv_arbitrary *tlv = pcep_tlv_create_tlv_arbitrary( + data, data_length, tlv_id_unknown); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, tlv_id_unknown); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, data_length); + /* Test the padding is correct */ + CU_ASSERT_EQUAL( + 0, strncmp((char *)&(tlv->header.encoded_tlv[4]), &data[0], 4)); + CU_ASSERT_EQUAL( + 0, strncmp((char *)&(tlv->header.encoded_tlv[8]), &data[4], 4)); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[11], 't'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[12], 'a'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[13], 0); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[14], 0); + pcep_obj_free_tlv(&tlv->header); + + reset_tlv_buffer(); + tlv = pcep_tlv_create_tlv_arbitrary(data, 3, tlv_id_unknown); + CU_ASSERT_PTR_NOT_NULL(tlv); + assert(tlv != NULL); + pcep_encode_tlv(&tlv->header, versioning, tlv_buf); + CU_ASSERT_EQUAL(tlv->header.type, tlv_id_unknown); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv_length, 3); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[4], 'S'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[5], 'o'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[6], 'm'); + CU_ASSERT_EQUAL(tlv->header.encoded_tlv[7], 0); + + pcep_obj_free_tlv(&tlv->header); +} diff --git a/pceplib/test/pcep_msg_tlvs_test.h b/pceplib/test/pcep_msg_tlvs_test.h new file mode 100644 index 0000000..78ef5ac --- /dev/null +++ b/pceplib/test/pcep_msg_tlvs_test.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +#ifndef PCEP_MSG_TLVS_TEST_H_ +#define PCEP_MSG_TLVS_TEST_H_ + +int pcep_tlvs_test_suite_setup(void); +int pcep_tlvs_test_suite_teardown(void); +void pcep_tlvs_test_setup(void); +void pcep_tlvs_test_teardown(void); +void test_pcep_tlv_create_stateful_pce_capability(void); +void test_pcep_tlv_create_speaker_entity_id(void); +void test_pcep_tlv_create_lsp_db_version(void); +void test_pcep_tlv_create_path_setup_type(void); +void test_pcep_tlv_create_path_setup_type_capability(void); +void test_pcep_tlv_create_sr_pce_capability(void); +void test_pcep_tlv_create_symbolic_path_name(void); +void test_pcep_tlv_create_ipv4_lsp_identifiers(void); +void test_pcep_tlv_create_ipv6_lsp_identifiers(void); +void test_pcep_tlv_create_lsp_error_code(void); +void test_pcep_tlv_create_rsvp_ipv4_error_spec(void); +void test_pcep_tlv_create_rsvp_ipv6_error_spec(void); +void test_pcep_tlv_create_srpag_pol_id_ipv4(void); +void test_pcep_tlv_create_srpag_pol_id_ipv6(void); +void test_pcep_tlv_create_srpag_pol_name(void); +void test_pcep_tlv_create_srpag_cp_id(void); +void test_pcep_tlv_create_srpag_cp_pref(void); +void test_pcep_tlv_create_nopath_vector(void); +void test_pcep_tlv_create_arbitrary(void); + + +#endif diff --git a/pceplib/test/pcep_msg_tools_test.c b/pceplib/test/pcep_msg_tools_test.c new file mode 100644 index 0000000..440cccd --- /dev/null +++ b/pceplib/test/pcep_msg_tools_test.c @@ -0,0 +1,1355 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + + +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_msg_messages.h" +#include "pcep_msg_tools.h" +#include "pcep_msg_tools_test.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" + +const uint8_t any_obj_class = 255; + +uint16_t pcep_open_hexbyte_strs_length = 28; +const char *pcep_open_odl_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "1e", + "78", "55", "00", "10", "00", "04", "00", "00", "00", "3f", + "00", "1a", "00", "04", "00", "00", "00", "00"}; + +/* PCEP INITIATE str received from ODL with 4 objects: [SRP, LSP, Endpoints, + * ERO] The LSP has a SYMBOLIC_PATH_NAME TLV. The ERO has 2 IPV4 Endpoints. */ +uint16_t pcep_initiate_hexbyte_strs_length = 68; +const char *pcep_initiate_hexbyte_strs[] = { + "20", "0c", "00", "44", "21", "12", "00", "0c", "00", "00", "00", "00", + "00", "00", "00", "01", "20", "10", "00", "14", "00", "00", "00", "09", + "00", "11", "00", "08", "66", "61", "39", "33", "33", "39", "32", "39", + "04", "10", "00", "0c", "7f", "00", "00", "01", "28", "28", "28", "28", + "07", "10", "00", "14", "01", "08", "0a", "00", "01", "01", "18", "00", + "01", "08", "0a", "00", "07", "04", "18", "00"}; + +uint16_t pcep_initiate2_hexbyte_strs_length = 72; +const char *pcep_initiate2_hexbyte_strs[] = { + "20", "0c", "00", "48", "21", "12", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "14", "00", "00", "00", "09", "00", "11", "00", "08", + "36", "65", "31", "31", "38", "39", "32", "31", "04", "10", "00", "0c", + "c0", "a8", "14", "05", "01", "01", "01", "01", "07", "10", "00", "10", + "05", "0c", "10", "01", "03", "e8", "a0", "00", "01", "01", "01", "01"}; + +uint16_t pcep_update_hexbyte_strs_length = 48; +const char *pcep_update_hexbyte_strs[] = { + "20", "0b", "00", "30", "21", "12", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "08", "00", "02", "a0", "09", "07", "10", "00", "10", + "05", "0c", "10", "01", "03", "e8", "a0", "00", "01", "01", "01", "01"}; + +/* Test that pcep_msg_read() can read multiple messages in 1 call */ +uint16_t pcep_open_initiate_hexbyte_strs_length = 100; +const char *pcep_open_initiate_odl_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "1e", "78", "55", + "00", "10", "00", "04", "00", "00", "00", "3f", "00", "1a", "00", "04", + "00", "00", "00", "00", "20", "0c", "00", "48", "21", "12", "00", "14", + "00", "00", "00", "00", "00", "00", "00", "01", "00", "1c", "00", "04", + "00", "00", "00", "01", "20", "10", "00", "14", "00", "00", "00", "09", + "00", "11", "00", "08", "36", "65", "31", "31", "38", "39", "32", "31", + "04", "10", "00", "0c", "c0", "a8", "14", "05", "01", "01", "01", "01", + "07", "10", "00", "10", "05", "0c", "10", "01", "03", "e8", "a0", "00", + "01", "01", "01", "01"}; + +uint16_t pcep_open_cisco_pce_hexbyte_strs_length = 28; +const char *pcep_open_cisco_pce_hexbyte_strs[] = { + "20", "01", "00", "1c", "01", "10", "00", "18", "20", "3c", + "78", "00", "00", "10", "00", "04", "00", "00", "00", "05", + "00", "1a", "00", "04", "00", "00", "00", "0a"}; + +uint16_t pcep_update_cisco_pce_hexbyte_strs_length = 100; +const char *pcep_update_cisco_pce_hexbyte_strs[] = { + "20", "0b", "00", "64", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "18", "80", "00", "f0", "89", "00", "07", "00", "0c", + "00", "00", "00", "09", "00", "03", "00", "04", "00", "00", "00", "01", + "07", "10", "00", "28", "24", "0c", "10", "01", "04", "65", "50", "00", + "0a", "0a", "0a", "05", "24", "0c", "10", "01", "04", "65", "20", "00", + "0a", "0a", "0a", "02", "24", "0c", "10", "01", "04", "65", "10", "00", + "0a", "0a", "0a", "01", "06", "10", "00", "0c", "00", "00", "00", "02", + "41", "f0", "00", "00"}; + +uint16_t pcep_report_cisco_pcc_hexbyte_strs_length = 148; +const char *pcep_report_cisco_pcc_hexbyte_strs[] = { + "20", "0a", "00", "94", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "3c", "80", "00", "f0", "09", "00", "12", "00", "10", + "0a", "0a", "0a", "06", "00", "02", "00", "0f", "0a", "0a", "0a", "06", + "0a", "0a", "0a", "01", "00", "11", "00", "0d", "63", "66", "67", "5f", + "52", "36", "2d", "74", "6f", "2d", "52", "31", "00", "00", "00", "00", + "ff", "e1", "00", "06", "00", "00", "05", "dd", "70", "00", "00", "00", + "07", "10", "00", "04", "09", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "00", "00", "00", "07", "07", "01", "00", + "05", "12", "00", "08", "00", "00", "00", "00", "05", "52", "00", "08", + "00", "00", "00", "00", "06", "10", "00", "0c", "00", "00", "00", "02", + "00", "00", "00", "00", "06", "10", "00", "0c", "00", "00", "01", "04", + "41", "80", "00", "00"}; + +/* Cisco PcInitiate with the following objects: + * SRP, LSP, Endpoint, Inter-layer, Switch-layer, ERO + */ +uint16_t pcep_initiate_cisco_pcc_hexbyte_strs_length = 104; +const char *pcep_initiate_cisco_pcc_hexbyte_strs[] = { + "20", "0c", "00", "68", "21", "10", "00", "14", "00", "00", "00", "00", + "00", "00", "00", "01", "00", "1c", "00", "04", "00", "00", "00", "01", + "20", "10", "00", "30", "00", "00", "00", "89", "00", "11", "00", "13", + "50", "4f", "4c", "31", "5f", "50", "43", "49", "4e", "49", "54", "41", + "54", "45", "5f", "54", "45", "53", "54", "00", "00", "07", "00", "0c", + "00", "00", "00", "09", "00", "03", "00", "04", "00", "00", "00", "01", + "04", "10", "00", "0c", "0a", "0a", "0a", "0a", "0a", "0a", "0a", "04", + "24", "10", "00", "08", "00", "00", "01", "4d", "25", "10", "00", "08", + "00", "00", "00", "64", "07", "10", "00", "04"}; + +struct pcep_message *create_message(uint8_t msg_type, uint8_t obj1_class, + uint8_t obj2_class, uint8_t obj3_class, + uint8_t obj4_class); +int convert_hexstrs_to_binary(char *filename, const char *hexbyte_strs[], + uint16_t hexbyte_strs_length); + +int pcep_tools_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_tools_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +void pcep_tools_test_setup(void) +{ +} + +void pcep_tools_test_teardown(void) +{ +} + +static const char BASE_TMPFILE[] = "/tmp/pceplib_XXXXXX"; +static int BASE_TMPFILE_SIZE = sizeof(BASE_TMPFILE); + +/* Reads an array of hexbyte strs, and writes them to a temporary file. + * The caller should close the returned file. */ +int convert_hexstrs_to_binary(char *filename, const char *hexbyte_strs[], + uint16_t hexbyte_strs_length) +{ + mode_t oldumask; + oldumask = umask(S_IXUSR | S_IXGRP | S_IWOTH | S_IROTH | S_IXOTH); + /* Set umask before anything for security */ + umask(0027); + + strlcpy(filename, BASE_TMPFILE, BASE_TMPFILE_SIZE); + int fd = mkstemp(filename); + umask(oldumask); + + if (fd == -1) + return -1; + + int i = 0; + for (; i < hexbyte_strs_length; i++) { + uint8_t byte = (uint8_t)strtol(hexbyte_strs[i], 0, 16); + if (write(fd, (char *)&byte, 1) < 0) { + return -1; + } + } + + /* Go back to the beginning of the file */ + lseek(fd, 0, SEEK_SET); + return fd; +} + +static bool pcep_obj_has_tlv(struct pcep_object_header *obj_hdr) +{ + if (obj_hdr->tlv_list == NULL) { + return false; + } + + return (obj_hdr->tlv_list->num_entries > 0); +} + +void test_pcep_msg_read_pcep_initiate(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary(filename, pcep_initiate_hexbyte_strs, + pcep_initiate_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate_hexbyte_strs_length); + + /* Verify each of the object types */ + + /* SRP object */ + double_linked_list_node *node = msg->obj_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* LSP object and its TLV*/ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_EQUAL(((struct pcep_object_lsp *)obj_hdr)->plsp_id, 0); + CU_ASSERT_TRUE(((struct pcep_object_lsp *)obj_hdr)->flag_d); + CU_ASSERT_TRUE(((struct pcep_object_lsp *)obj_hdr)->flag_a); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_s); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_r); + CU_ASSERT_FALSE(((struct pcep_object_lsp *)obj_hdr)->flag_c); + + /* LSP TLV */ + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 8); + + /* Endpoints object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + assert(ero_subobj_list != NULL); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 2); + double_linked_list_node *subobj_node = ero_subobj_list->head; + struct pcep_object_ro_subobj *subobj_hdr = + (struct pcep_object_ro_subobj *)subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_IPV4); + struct in_addr ero_subobj_ip; + inet_pton(AF_INET, "10.0.1.1", &ero_subobj_ip); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->ip_addr.s_addr, + ero_subobj_ip.s_addr); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->prefix_length, 24); + + subobj_hdr = + (struct pcep_object_ro_subobj *)subobj_node->next_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_IPV4); + inet_pton(AF_INET, "10.0.7.4", &ero_subobj_ip); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->ip_addr.s_addr, + ero_subobj_ip.s_addr); + CU_ASSERT_EQUAL( + ((struct pcep_ro_subobj_ipv4 *)subobj_hdr)->prefix_length, 24); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + + +void test_pcep_msg_read_pcep_initiate2(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = + convert_hexstrs_to_binary(filename, pcep_initiate2_hexbyte_strs, + pcep_initiate2_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate2_hexbyte_strs_length); + + /* Verify each of the object types */ + + /* SRP object */ + double_linked_list_node *node = msg->obj_list->head; + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + /* TODO test the TLVs */ + + /* LSP object and its TLV*/ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + + /* LSP TLV */ + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 8); + + /* Endpoints object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 16); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + assert(ero_subobj_list != NULL); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 0); + double_linked_list_node *subobj_node = ero_subobj_list->head; + CU_ASSERT_PTR_NULL(subobj_node); + /* We no longer support draft07 SR sub-object type=5, and only support + type=36 struct pcep_object_ro_subobj *subobj_hdr = (struct + pcep_object_ro_subobj *) subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_SR); + struct pcep_ro_subobj_sr *subobj_sr = (struct pcep_ro_subobj_sr *) + subobj_hdr; CU_ASSERT_EQUAL(subobj_sr->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); CU_ASSERT_TRUE(subobj_sr->flag_m); + CU_ASSERT_FALSE(subobj_sr->flag_c); + CU_ASSERT_FALSE(subobj_sr->flag_s); + CU_ASSERT_FALSE(subobj_sr->flag_f); + CU_ASSERT_EQUAL(subobj_sr->sid, 65576960); + CU_ASSERT_EQUAL(*((uint32_t *) subobj_sr->nai_list->head->data), + 0x01010101); + */ + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_open(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary(filename, pcep_open_odl_hexbyte_strs, + pcep_open_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + + /* Verify the Open message */ + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)msg->obj_list->head->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_OPEN); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 24); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + + /* Open TLV: Stateful PCE Capability */ + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 2); + double_linked_list_node *tlv_node = obj_hdr->tlv_list->head; + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)tlv_node->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + + /* Open TLV: SR PCE Capability */ + tlv_node = tlv_node->next_node; + tlv = (struct pcep_object_tlv_header *)tlv_node->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_update(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary(filename, pcep_update_hexbyte_strs, + pcep_update_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 3); + + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_update_hexbyte_strs_length); + + /* Verify each of the object types */ + + double_linked_list_node *node = msg->obj_list->head; + + /* SRP object */ + struct pcep_object_header *obj_hdr = + (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 20); + CU_ASSERT_TRUE(pcep_obj_has_tlv(obj_hdr)); + + /* SRP TLV */ + CU_ASSERT_EQUAL(obj_hdr->tlv_list->num_entries, 1); + struct pcep_object_tlv_header *tlv = + (struct pcep_object_tlv_header *)obj_hdr->tlv_list->head->data; + CU_ASSERT_EQUAL(tlv->type, PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(tlv->encoded_tlv_length, 4); + /* TODO verify the path setup type */ + + /* LSP object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, + pcep_object_get_length_by_hdr(obj_hdr)); + CU_ASSERT_FALSE(pcep_obj_has_tlv(obj_hdr)); + + /* ERO object */ + node = node->next_node; + obj_hdr = (struct pcep_object_header *)node->data; + CU_ASSERT_EQUAL(obj_hdr->object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(obj_hdr->object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(obj_hdr->encoded_object_length, 16); + + /* ERO Subobjects */ + double_linked_list *ero_subobj_list = + ((struct pcep_object_ro *)obj_hdr)->sub_objects; + CU_ASSERT_PTR_NOT_NULL(ero_subobj_list); + assert(ero_subobj_list != NULL); + CU_ASSERT_EQUAL(ero_subobj_list->num_entries, 0); + double_linked_list_node *subobj_node = ero_subobj_list->head; + CU_ASSERT_PTR_NULL(subobj_node); + /* We no longer support draft07 SR sub-object type=5, and only support + type=36 struct pcep_object_ro_subobj *subobj_hdr = (struct + pcep_object_ro_subobj *) subobj_node->data; + CU_ASSERT_EQUAL(subobj_hdr->ro_subobj_type, RO_SUBOBJ_TYPE_SR); + struct pcep_ro_subobj_sr *subobj_sr = (struct pcep_ro_subobj_sr *) + subobj_hdr; CU_ASSERT_EQUAL(subobj_sr->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); CU_ASSERT_TRUE(subobj_sr->flag_m); + CU_ASSERT_FALSE(subobj_sr->flag_c); + CU_ASSERT_FALSE(subobj_sr->flag_s); + CU_ASSERT_FALSE(subobj_sr->flag_f); + CU_ASSERT_EQUAL(subobj_sr->sid, 65576960); + CU_ASSERT_EQUAL(*((uint32_t *) subobj_sr->nai_list->head->data), + 0x01010101); + */ + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_open_initiate(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary( + filename, pcep_open_initiate_odl_hexbyte_strs, + pcep_open_initiate_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 2); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + + msg = (struct pcep_message *)msg_list->head->next_node->data; + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate2_hexbyte_strs_length); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_open_cisco_pce(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary( + filename, pcep_open_cisco_pce_hexbyte_strs, + pcep_open_cisco_pce_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_open_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 1); + + /* Open object */ + struct pcep_object_open *open = + (struct pcep_object_open *)msg->obj_list->head->data; + CU_ASSERT_EQUAL(open->header.object_class, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_EQUAL(open->header.object_type, PCEP_OBJ_TYPE_OPEN); + CU_ASSERT_EQUAL(open->header.encoded_object_length, 24); + CU_ASSERT_EQUAL(open->open_deadtimer, 120); + CU_ASSERT_EQUAL(open->open_keepalive, 60); + CU_ASSERT_EQUAL(open->open_sid, 0); + CU_ASSERT_EQUAL(open->open_version, 1); + CU_ASSERT_PTR_NOT_NULL(open->header.tlv_list); + assert(open->header.tlv_list != NULL); + CU_ASSERT_EQUAL(open->header.tlv_list->num_entries, 2); + + /* Stateful PCE Capability TLV */ + double_linked_list_node *tlv_node = open->header.tlv_list->head; + struct pcep_object_tlv_stateful_pce_capability *pce_cap_tlv = + (struct pcep_object_tlv_stateful_pce_capability *) + tlv_node->data; + CU_ASSERT_EQUAL(pce_cap_tlv->header.type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(pce_cap_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_TRUE(pce_cap_tlv->flag_u_lsp_update_capability); + CU_ASSERT_TRUE(pce_cap_tlv->flag_i_lsp_instantiation_capability); + CU_ASSERT_FALSE(pce_cap_tlv->flag_s_include_db_version); + CU_ASSERT_FALSE(pce_cap_tlv->flag_t_triggered_resync); + CU_ASSERT_FALSE(pce_cap_tlv->flag_d_delta_lsp_sync); + CU_ASSERT_FALSE(pce_cap_tlv->flag_f_triggered_initial_sync); + + /* SR PCE Capability TLV */ + tlv_node = tlv_node->next_node; + struct pcep_object_tlv_sr_pce_capability *sr_pce_cap_tlv = + (struct pcep_object_tlv_sr_pce_capability *)tlv_node->data; + CU_ASSERT_EQUAL(sr_pce_cap_tlv->header.type, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + CU_ASSERT_EQUAL(sr_pce_cap_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_FALSE(sr_pce_cap_tlv->flag_n); + CU_ASSERT_FALSE(sr_pce_cap_tlv->flag_x); + CU_ASSERT_EQUAL(sr_pce_cap_tlv->max_sid_depth, 10); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_update_cisco_pce(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary( + filename, pcep_update_cisco_pce_hexbyte_strs, + pcep_update_cisco_pce_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_UPDATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_update_cisco_pce_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 4); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + assert(srp->header.tlv_list != NULL); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 1); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* SRP Path Setup Type TLV */ + double_linked_list_node *tlv_node = srp->header.tlv_list->head; + struct pcep_object_tlv_path_setup_type *pst_tlv = + (struct pcep_object_tlv_path_setup_type *)tlv_node->data; + CU_ASSERT_EQUAL(pst_tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(pst_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(pst_tlv->path_setup_type, 1); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 24); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + assert(lsp->header.tlv_list != NULL); + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(lsp->plsp_id, 524303); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_c); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* LSP Vendor Info TLV */ + tlv_node = lsp->header.tlv_list->head; + struct pcep_object_tlv_vendor_info *vendor_tlv = + (struct pcep_object_tlv_vendor_info *)tlv_node->data; + CU_ASSERT_EQUAL(vendor_tlv->header.type, PCEP_OBJ_TLV_TYPE_VENDOR_INFO); + CU_ASSERT_EQUAL(vendor_tlv->header.encoded_tlv_length, 12); + CU_ASSERT_EQUAL(vendor_tlv->enterprise_number, 9); + CU_ASSERT_EQUAL(vendor_tlv->enterprise_specific_info, 0x00030004); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 40); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + CU_ASSERT_PTR_NOT_NULL(ero->sub_objects); + assert(ero->sub_objects != NULL); + CU_ASSERT_EQUAL(ero->sub_objects->num_entries, 3); + + /* ERO Subobjects */ + double_linked_list_node *ero_subobj_node = ero->sub_objects->head; + struct pcep_ro_subobj_sr *sr_subobj_ipv4_node = + (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73748480); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a05)); + + ero_subobj_node = ero_subobj_node->next_node; + sr_subobj_ipv4_node = (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73736192); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a02)); + + ero_subobj_node = ero_subobj_node->next_node; + sr_subobj_ipv4_node = (struct pcep_ro_subobj_sr *)ero_subobj_node->data; + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->ro_subobj.ro_subobj_type, + RO_SUBOBJ_TYPE_SR); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->ro_subobj.flag_subobj_loose_hop); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->nai_type, + PCEP_SR_SUBOBJ_NAI_IPV4_NODE); + CU_ASSERT_TRUE(sr_subobj_ipv4_node->flag_m); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_c); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_f); + CU_ASSERT_FALSE(sr_subobj_ipv4_node->flag_s); + CU_ASSERT_EQUAL(sr_subobj_ipv4_node->sid, 73732096); + CU_ASSERT_EQUAL( + *((uint32_t *)sr_subobj_ipv4_node->nai_list->head->data), + htonl(0x0a0a0a01)); + + /* Metric object */ + obj_node = obj_node->next_node; + struct pcep_object_metric *metric = + (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_FALSE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_TE); + CU_ASSERT_EQUAL(metric->value, 30.0); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_report_cisco_pcc(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary( + filename, pcep_report_cisco_pcc_hexbyte_strs, + pcep_report_cisco_pcc_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_REPORT); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_report_cisco_pcc_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 8); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + assert(srp->header.tlv_list != NULL); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 0); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* SRP Path Setup Type TLV */ + double_linked_list_node *tlv_node = srp->header.tlv_list->head; + struct pcep_object_tlv_path_setup_type *pst_tlv = + (struct pcep_object_tlv_path_setup_type *)tlv_node->data; + CU_ASSERT_EQUAL(pst_tlv->header.type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE); + CU_ASSERT_EQUAL(pst_tlv->header.encoded_tlv_length, 4); + CU_ASSERT_EQUAL(pst_tlv->path_setup_type, 1); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 60); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + /* The TLV with ID 65505 is now recognized, and its in the list */ + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 3); + CU_ASSERT_EQUAL(lsp->plsp_id, 524303); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_FALSE(lsp->flag_c); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* LSP IPv4 LSP Identifier TLV */ + tlv_node = lsp->header.tlv_list->head; + struct pcep_object_tlv_ipv4_lsp_identifier *ipv4_lsp_id = + (struct pcep_object_tlv_ipv4_lsp_identifier *)tlv_node->data; + CU_ASSERT_EQUAL(ipv4_lsp_id->header.type, + PCEP_OBJ_TLV_TYPE_IPV4_LSP_IDENTIFIERS); + CU_ASSERT_EQUAL(ipv4_lsp_id->header.encoded_tlv_length, 16); + CU_ASSERT_EQUAL(ipv4_lsp_id->ipv4_tunnel_sender.s_addr, + htonl(0x0a0a0a06)); + CU_ASSERT_EQUAL(ipv4_lsp_id->ipv4_tunnel_endpoint.s_addr, + htonl(0x0a0a0a01)); + CU_ASSERT_EQUAL(ipv4_lsp_id->extended_tunnel_id.s_addr, + htonl(0x0a0a0a06)); + CU_ASSERT_EQUAL(ipv4_lsp_id->tunnel_id, 15); + CU_ASSERT_EQUAL(ipv4_lsp_id->lsp_id, 2); + + /* LSP Symbolic Path Name TLV */ + tlv_node = tlv_node->next_node; + struct pcep_object_tlv_symbolic_path_name *sym_path_name = + (struct pcep_object_tlv_symbolic_path_name *)tlv_node->data; + CU_ASSERT_EQUAL(sym_path_name->header.type, + PCEP_OBJ_TLV_TYPE_SYMBOLIC_PATH_NAME); + CU_ASSERT_EQUAL(sym_path_name->header.encoded_tlv_length, 13); + CU_ASSERT_EQUAL(sym_path_name->symbolic_path_name_length, 13); + CU_ASSERT_EQUAL( + strncmp(sym_path_name->symbolic_path_name, "cfg_R6-to-R1", 13), + 0); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 4); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + CU_ASSERT_PTR_NOT_NULL(ero->sub_objects); + assert(ero->sub_objects != NULL); + CU_ASSERT_EQUAL(ero->sub_objects->num_entries, 0); + + /* LSPA object */ + obj_node = obj_node->next_node; + struct pcep_object_lspa *lspa = + (struct pcep_object_lspa *)obj_node->data; + CU_ASSERT_EQUAL(lspa->header.object_class, PCEP_OBJ_CLASS_LSPA); + CU_ASSERT_EQUAL(lspa->header.object_type, PCEP_OBJ_TYPE_LSPA); + CU_ASSERT_EQUAL(lspa->header.encoded_object_length, 20); + CU_ASSERT_PTR_NULL(lspa->header.tlv_list); + CU_ASSERT_TRUE(lspa->flag_local_protection); + CU_ASSERT_EQUAL(lspa->holding_priority, 7); + CU_ASSERT_EQUAL(lspa->setup_priority, 7); + CU_ASSERT_EQUAL(lspa->lspa_include_all, 0); + CU_ASSERT_EQUAL(lspa->lspa_include_any, 0); + CU_ASSERT_EQUAL(lspa->lspa_exclude_any, 0); + + /* Bandwidth object 1 */ + obj_node = obj_node->next_node; + struct pcep_object_bandwidth *bandwidth = + (struct pcep_object_bandwidth *)obj_node->data; + CU_ASSERT_EQUAL(bandwidth->header.object_class, + PCEP_OBJ_CLASS_BANDWIDTH); + CU_ASSERT_EQUAL(bandwidth->header.object_type, + PCEP_OBJ_TYPE_BANDWIDTH_REQ); + CU_ASSERT_EQUAL(bandwidth->header.encoded_object_length, 8); + CU_ASSERT_EQUAL(bandwidth->bandwidth, 0); + + /* Bandwidth object 2 */ + obj_node = obj_node->next_node; + bandwidth = (struct pcep_object_bandwidth *)obj_node->data; + CU_ASSERT_EQUAL(bandwidth->header.object_class, + PCEP_OBJ_CLASS_BANDWIDTH); + CU_ASSERT_EQUAL(bandwidth->header.object_type, + PCEP_OBJ_TYPE_BANDWIDTH_CISCO); + CU_ASSERT_EQUAL(bandwidth->header.encoded_object_length, 8); + CU_ASSERT_EQUAL(bandwidth->bandwidth, 0); + + /* Metric object 1 */ + obj_node = obj_node->next_node; + struct pcep_object_metric *metric = + (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_FALSE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_TE); + CU_ASSERT_EQUAL(metric->value, 0); + + /* Metric object 2 */ + obj_node = obj_node->next_node; + metric = (struct pcep_object_metric *)obj_node->data; + CU_ASSERT_EQUAL(metric->header.object_class, PCEP_OBJ_CLASS_METRIC); + CU_ASSERT_EQUAL(metric->header.object_type, PCEP_OBJ_TYPE_METRIC); + CU_ASSERT_EQUAL(metric->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(metric->header.tlv_list); + CU_ASSERT_TRUE(metric->flag_b); + CU_ASSERT_FALSE(metric->flag_c); + CU_ASSERT_EQUAL(metric->type, PCEP_METRIC_AGGREGATE_BW); + CU_ASSERT_EQUAL(metric->value, 16.0); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_pcep_msg_read_pcep_initiate_cisco_pcc(void) +{ + char filename[BASE_TMPFILE_SIZE]; + + int fd = convert_hexstrs_to_binary( + filename, pcep_initiate_cisco_pcc_hexbyte_strs, + pcep_initiate_cisco_pcc_hexbyte_strs_length); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + double_linked_list *msg_list = pcep_msg_read(fd); + CU_ASSERT_PTR_NOT_NULL(msg_list); + assert(msg_list != NULL); + CU_ASSERT_EQUAL(msg_list->num_entries, 1); + + struct pcep_message *msg = (struct pcep_message *)msg_list->head->data; + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_INITIATE); + CU_ASSERT_EQUAL(msg->encoded_message_length, + pcep_initiate_cisco_pcc_hexbyte_strs_length); + CU_ASSERT_EQUAL(msg->obj_list->num_entries, 6); + + /* SRP object */ + double_linked_list_node *obj_node = msg->obj_list->head; + struct pcep_object_srp *srp = (struct pcep_object_srp *)obj_node->data; + CU_ASSERT_EQUAL(srp->header.object_class, PCEP_OBJ_CLASS_SRP); + CU_ASSERT_EQUAL(srp->header.object_type, PCEP_OBJ_TYPE_SRP); + CU_ASSERT_EQUAL(srp->header.encoded_object_length, 20); + CU_ASSERT_PTR_NOT_NULL(srp->header.tlv_list); + assert(srp->header.tlv_list != NULL); + CU_ASSERT_EQUAL(srp->header.tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(srp->srp_id_number, 1); + CU_ASSERT_FALSE(srp->flag_lsp_remove); + + /* LSP object */ + obj_node = obj_node->next_node; + struct pcep_object_lsp *lsp = (struct pcep_object_lsp *)obj_node->data; + CU_ASSERT_EQUAL(lsp->header.object_class, PCEP_OBJ_CLASS_LSP); + CU_ASSERT_EQUAL(lsp->header.object_type, PCEP_OBJ_TYPE_LSP); + CU_ASSERT_EQUAL(lsp->header.encoded_object_length, 48); + CU_ASSERT_PTR_NOT_NULL(lsp->header.tlv_list); + assert(lsp->header.tlv_list != NULL); + CU_ASSERT_EQUAL(lsp->header.tlv_list->num_entries, 2); + CU_ASSERT_EQUAL(lsp->plsp_id, 0); + CU_ASSERT_EQUAL(lsp->operational_status, PCEP_LSP_OPERATIONAL_DOWN); + CU_ASSERT_TRUE(lsp->flag_a); + CU_ASSERT_TRUE(lsp->flag_d); + CU_ASSERT_TRUE(lsp->flag_c); + CU_ASSERT_FALSE(lsp->flag_r); + CU_ASSERT_FALSE(lsp->flag_s); + + /* Endpoint object */ + obj_node = obj_node->next_node; + struct pcep_object_endpoints_ipv4 *endpoint = + (struct pcep_object_endpoints_ipv4 *)obj_node->data; + CU_ASSERT_EQUAL(endpoint->header.object_class, + PCEP_OBJ_CLASS_ENDPOINTS); + CU_ASSERT_EQUAL(endpoint->header.object_type, + PCEP_OBJ_TYPE_ENDPOINT_IPV4); + CU_ASSERT_EQUAL(endpoint->header.encoded_object_length, 12); + CU_ASSERT_PTR_NULL(endpoint->header.tlv_list); + CU_ASSERT_EQUAL(endpoint->src_ipv4.s_addr, htonl(0x0a0a0a0a)); + CU_ASSERT_EQUAL(endpoint->dst_ipv4.s_addr, htonl(0x0a0a0a04)); + + /* Inter-Layer object */ + obj_node = obj_node->next_node; + struct pcep_object_inter_layer *inter_layer = + (struct pcep_object_inter_layer *)obj_node->data; + CU_ASSERT_EQUAL(inter_layer->header.object_class, + PCEP_OBJ_CLASS_INTER_LAYER); + CU_ASSERT_EQUAL(inter_layer->header.object_type, + PCEP_OBJ_TYPE_INTER_LAYER); + CU_ASSERT_EQUAL(inter_layer->header.encoded_object_length, 8); + CU_ASSERT_PTR_NULL(inter_layer->header.tlv_list); + CU_ASSERT_TRUE(inter_layer->flag_i); + CU_ASSERT_FALSE(inter_layer->flag_m); + CU_ASSERT_TRUE(inter_layer->flag_t); + + /* Switch-Layer object */ + obj_node = obj_node->next_node; + struct pcep_object_switch_layer *switch_layer = + (struct pcep_object_switch_layer *)obj_node->data; + CU_ASSERT_EQUAL(switch_layer->header.object_class, + PCEP_OBJ_CLASS_SWITCH_LAYER); + CU_ASSERT_EQUAL(switch_layer->header.object_type, + PCEP_OBJ_TYPE_SWITCH_LAYER); + CU_ASSERT_EQUAL(switch_layer->header.encoded_object_length, 8); + CU_ASSERT_PTR_NULL(switch_layer->header.tlv_list); + assert(switch_layer->header.tlv_list == NULL); + CU_ASSERT_PTR_NOT_NULL(switch_layer->switch_layer_rows); + assert(switch_layer->switch_layer_rows != NULL); + CU_ASSERT_EQUAL(switch_layer->switch_layer_rows->num_entries, 1); + struct pcep_object_switch_layer_row *switch_layer_row = + (struct pcep_object_switch_layer_row *) + switch_layer->switch_layer_rows->head->data; + CU_ASSERT_EQUAL(switch_layer_row->lsp_encoding_type, 0); + CU_ASSERT_EQUAL(switch_layer_row->switching_type, 0); + CU_ASSERT_FALSE(switch_layer_row->flag_i); + + /* ERO object */ + obj_node = obj_node->next_node; + struct pcep_object_ro *ero = (struct pcep_object_ro *)obj_node->data; + CU_ASSERT_EQUAL(ero->header.object_class, PCEP_OBJ_CLASS_ERO); + CU_ASSERT_EQUAL(ero->header.object_type, PCEP_OBJ_TYPE_ERO); + CU_ASSERT_EQUAL(ero->header.encoded_object_length, 4); + CU_ASSERT_PTR_NULL(ero->header.tlv_list); + + pcep_msg_free_message_list(msg_list); + close(fd); + unlink(filename); +} + +void test_validate_message_header(void) +{ + uint8_t pcep_message_invalid_version[] = {0x40, 0x01, 0x04, 0x00}; + uint8_t pcep_message_invalid_flags[] = {0x22, 0x01, 0x04, 0x00}; + uint8_t pcep_message_invalid_length[] = {0x20, 0x01, 0x00, 0x00}; + uint8_t pcep_message_invalid_type[] = {0x20, 0xff, 0x04, 0x00}; + uint8_t pcep_message_valid[] = {0x20, 0x01, 0x00, 0x04}; + + /* Verify invalid message header version */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_version) + < 0); + + /* Verify invalid message header flags */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_flags) + < 0); + + /* Verify invalid message header lengths */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_length) + < 0); + pcep_message_invalid_length[3] = 0x05; + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_length) + < 0); + + /* Verify invalid message header types */ + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_type) < 0); + pcep_message_invalid_type[1] = 0x00; + CU_ASSERT_TRUE( + pcep_decode_validate_msg_header(pcep_message_invalid_type) < 0); + + /* Verify a valid message header */ + CU_ASSERT_EQUAL(pcep_decode_validate_msg_header(pcep_message_valid), 4); +} + +/* Internal util function */ +struct pcep_message *create_message(uint8_t msg_type, uint8_t obj1_class, + uint8_t obj2_class, uint8_t obj3_class, + uint8_t obj4_class) +{ + struct pcep_message *msg = + pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + msg->obj_list = dll_initialize(); + msg->msg_header = pceplib_malloc(PCEPLIB_MESSAGES, + sizeof(struct pcep_message_header)); + msg->msg_header->type = msg_type; + msg->encoded_message = NULL; + + if (obj1_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj1_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj2_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj2_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj3_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj3_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + if (obj4_class > 0) { + struct pcep_object_header *obj_hdr = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_object_header)); + obj_hdr->object_class = obj4_class; + obj_hdr->tlv_list = NULL; + dll_append(msg->obj_list, obj_hdr); + } + + return msg; +} + +void test_validate_message_objects(void) +{ + /* Valid Open message */ + struct pcep_message *msg = + create_message(PCEP_TYPE_OPEN, PCEP_OBJ_CLASS_OPEN, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid KeepAlive message */ + msg = create_message(PCEP_TYPE_KEEPALIVE, 0, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid PcReq message */ + /* Using object_class=255 to verify it can take any object */ + msg = create_message(PCEP_TYPE_PCREQ, PCEP_OBJ_CLASS_RP, + PCEP_OBJ_CLASS_ENDPOINTS, any_obj_class, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid PcRep message */ + msg = create_message(PCEP_TYPE_PCREP, PCEP_OBJ_CLASS_RP, any_obj_class, + 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Notify message */ + msg = create_message(PCEP_TYPE_PCNOTF, PCEP_OBJ_CLASS_NOTF, + any_obj_class, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Error message */ + msg = create_message(PCEP_TYPE_ERROR, PCEP_OBJ_CLASS_ERROR, + any_obj_class, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Close message */ + msg = create_message(PCEP_TYPE_CLOSE, PCEP_OBJ_CLASS_CLOSE, 0, 0, 0); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Report message */ + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Update message */ + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Valid Initiate message */ + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, + PCEP_OBJ_CLASS_LSP, any_obj_class, any_obj_class); + CU_ASSERT_TRUE(validate_message_objects(msg)); + pcep_msg_free_message(msg); +} + +void test_validate_message_objects_invalid(void) +{ + /* unsupported message ID = 0 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + struct pcep_message *msg = create_message(0, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Open message + * {PCEP_OBJ_CLASS_OPEN, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_OPEN, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_OPEN, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_OPEN, PCEP_OBJ_CLASS_OPEN, any_obj_class, + 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* KeepAlive message + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_KEEPALIVE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* PcReq message + * {PCEP_OBJ_CLASS_RP, PCEP_OBJ_CLASS_ENDPOINTS, ANY_OBJECT, ANY_OBJECT} + */ + msg = create_message(PCEP_TYPE_PCREQ, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCREQ, PCEP_OBJ_CLASS_RP, any_obj_class, + 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* PcRep message + * {PCEP_OBJ_CLASS_RP, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_PCREP, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCREP, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Notify message + * {PCEP_OBJ_CLASS_NOTF, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_PCNOTF, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_PCNOTF, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Error message + * {PCEP_OBJ_CLASS_ERROR, ANY_OBJECT, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_ERROR, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_ERROR, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Close message + * {PCEP_OBJ_CLASS_CLOSE, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(PCEP_TYPE_CLOSE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_CLOSE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* unsupported message ID = 8 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(8, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* unsupported message ID = 9 + * {NO_OBJECT, NO_OBJECT, NO_OBJECT, NO_OBJECT} */ + msg = create_message(9, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Report message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_REPORT, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_REPORT, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Update message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_UPDATE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_UPDATE, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + /* Initiate message + * {PCEP_OBJ_CLASS_SRP, PCEP_OBJ_CLASS_LSP, ANY_OBJECT, ANY_OBJECT} */ + msg = create_message(PCEP_TYPE_INITIATE, 0, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, any_obj_class, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, 0, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); + + msg = create_message(PCEP_TYPE_INITIATE, PCEP_OBJ_CLASS_SRP, + any_obj_class, 0, 0); + CU_ASSERT_FALSE(validate_message_objects(msg)); + pcep_msg_free_message(msg); +} diff --git a/pceplib/test/pcep_msg_tools_test.h b/pceplib/test/pcep_msg_tools_test.h new file mode 100644 index 0000000..3d740d3 --- /dev/null +++ b/pceplib/test/pcep_msg_tools_test.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MSG_TOOLS_TEST_H_ +#define PCEP_MSG_TOOLS_TEST_H_ + + +int pcep_tools_test_suite_setup(void); +int pcep_tools_test_suite_teardown(void); +void pcep_tools_test_setup(void); +void pcep_tools_test_teardown(void); +void test_pcep_msg_read_pcep_initiate(void); +void test_pcep_msg_read_pcep_initiate2(void); +void test_pcep_msg_read_pcep_update(void); +void test_pcep_msg_read_pcep_open(void); +void test_pcep_msg_read_pcep_open_initiate(void); +void test_validate_message_header(void); +void test_validate_message_objects(void); +void test_validate_message_objects_invalid(void); +void test_pcep_msg_read_pcep_open_cisco_pce(void); +void test_pcep_msg_read_pcep_update_cisco_pce(void); +void test_pcep_msg_read_pcep_report_cisco_pcc(void); +void test_pcep_msg_read_pcep_initiate_cisco_pcc(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_pcc_api_test.c b/pceplib/test/pcep_pcc_api_test.c new file mode 100644 index 0000000..768325a --- /dev/null +++ b/pceplib/test/pcep_pcc_api_test.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include // gethostbyname +#include +#include +#include + +#include + +#include "pcep_pcc_api.h" +#include "pcep_pcc_api_test.h" +#include "pcep_socket_comm_mock.h" +#include "pcep_utils_memory.h" + +extern pcep_event_queue *session_logic_event_queue_; +extern const char MESSAGE_RECEIVED_STR[]; +extern const char UNKNOWN_EVENT_STR[]; + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_pcc_api_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_pcc_api_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_pcc_api_test_setup(void) +{ + setup_mock_socket_comm_info(); +} + + +void pcep_pcc_api_test_teardown(void) +{ + teardown_mock_socket_comm_info(); +} + +/* + * Unit test cases + */ + +void test_initialize_pcc(void) +{ + CU_ASSERT_TRUE(initialize_pcc()); + /* Give the PCC time to initialize */ + sleep(1); + CU_ASSERT_TRUE(destroy_pcc()); +} + +void test_connect_pce(void) +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + assert(open_msg->msg_header != NULL); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_connect_pce_ipv6(void) +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct in6_addr dest_address; + dest_address.__in6_u.__u6_addr32[0] = 0; + dest_address.__in6_u.__u6_addr32[1] = 0; + dest_address.__in6_u.__u6_addr32[2] = 0; + dest_address.__in6_u.__u6_addr32[3] = htonl(1); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce_ipv6(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + assert(session != NULL); + CU_ASSERT_TRUE(session->socket_comm_session->is_ipv6); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_connect_pce_with_src_ip(void) +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config->src_ip.src_ipv4.s_addr = 0x0a0a0102; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + + CU_ASSERT_PTR_NOT_NULL(session); + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 1); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + assert(open_msg->msg_header != NULL); + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + + pcep_msg_free_message(open_msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + +void test_disconnect_pce(void) +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + initialize_pcc(); + + pcep_session *session = connect_pce(config, &dest_address); + disconnect_pce(session); + + CU_ASSERT_EQUAL(mock_info->sent_message_list->num_entries, 2); + + /* First there should be an open message from connect_pce() */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + assert(msg != NULL); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_OPEN); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Then there should be a close message from disconnect_pce() */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + assert(msg != NULL); + assert(msg->msg_header != NULL); + CU_ASSERT_EQUAL(msg->msg_header->type, PCEP_TYPE_CLOSE); + + pcep_msg_free_message(msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + destroy_pcc(); +} + + +void test_send_message(void) +{ + pcep_configuration *config = create_default_pcep_configuration(); + struct hostent *host_info = gethostbyname("localhost"); + struct in_addr dest_address; + + initialize_pcc(); + + memcpy(&dest_address, host_info->h_addr, host_info->h_length); + pcep_session *session = connect_pce(config, &dest_address); + verify_socket_comm_times_called(0, 0, 1, 1, 0, 0, 0); + + struct pcep_message *msg = pcep_msg_create_keepalive(); + send_message(session, msg, false); + + verify_socket_comm_times_called(0, 0, 1, 2, 0, 0, 0); + + pcep_msg_free_message(msg); + destroy_pcep_session(session); + destroy_pcep_configuration(config); + + destroy_pcc(); +} + +void test_event_queue(void) +{ + /* This initializes the event_queue */ + CU_ASSERT_TRUE(initialize_pcc()); + + /* Verify correct behavior when the queue is empty */ + CU_ASSERT_TRUE(event_queue_is_empty()); + CU_ASSERT_EQUAL(event_queue_num_events_available(), 0); + CU_ASSERT_PTR_NULL(event_queue_get_event()); + destroy_pcep_event(NULL); + + /* Create an empty event and put it on the queue */ + pcep_event *event = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event)); + memset(event, 0, sizeof(pcep_event)); + pthread_mutex_lock(&session_logic_event_queue_->event_queue_mutex); + queue_enqueue(session_logic_event_queue_->event_queue, event); + pthread_mutex_unlock(&session_logic_event_queue_->event_queue_mutex); + + /* Verify correct behavior when there is an entry in the queue */ + CU_ASSERT_FALSE(event_queue_is_empty()); + CU_ASSERT_EQUAL(event_queue_num_events_available(), 1); + pcep_event *queued_event = event_queue_get_event(); + CU_ASSERT_PTR_NOT_NULL(queued_event); + CU_ASSERT_PTR_EQUAL(event, queued_event); + destroy_pcep_event(queued_event); + + CU_ASSERT_TRUE(destroy_pcc()); +} + +void test_get_event_type_str(void) +{ + CU_ASSERT_EQUAL(strcmp(get_event_type_str(MESSAGE_RECEIVED), + MESSAGE_RECEIVED_STR), + 0); + CU_ASSERT_EQUAL(strcmp(get_event_type_str(1000), UNKNOWN_EVENT_STR), 0); +} diff --git a/pceplib/test/pcep_pcc_api_test.h b/pceplib/test/pcep_pcc_api_test.h new file mode 100644 index 0000000..4933010 --- /dev/null +++ b/pceplib/test/pcep_pcc_api_test.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_PCC_API_TEST_ +#define PCEP_PCC_API_TEST_ + +int pcep_pcc_api_test_suite_setup(void); +int pcep_pcc_api_test_suite_teardown(void); +void pcep_pcc_api_test_setup(void); +void pcep_pcc_api_test_teardown(void); +void test_initialize_pcc(void); +void test_connect_pce(void); +void test_connect_pce_ipv6(void); +void test_connect_pce_with_src_ip(void); +void test_disconnect_pce(void); +void test_send_message(void); +void test_event_queue(void); +void test_get_event_type_str(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_pcc_api_tests.c b/pceplib/test/pcep_pcc_api_tests.c new file mode 100644 index 0000000..d0cfa68 --- /dev/null +++ b/pceplib/test/pcep_pcc_api_tests.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_pcc_api_test.h" + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_pcc_api_suite = CU_add_suite_with_setup_and_teardown( + "PCEP PCC API Test Suite", + pcep_pcc_api_test_suite_setup, // suite setup and cleanup + // function pointers + pcep_pcc_api_test_suite_teardown, + pcep_pcc_api_test_setup, // test case setup function pointer + pcep_pcc_api_test_teardown); // test case teardown function + // pointer + + CU_add_test(test_pcc_api_suite, "test_initialize_pcc", + test_initialize_pcc); + CU_add_test(test_pcc_api_suite, "test_connect_pce", test_connect_pce); + CU_add_test(test_pcc_api_suite, "test_connect_pce_ipv6", + test_connect_pce_ipv6); + CU_add_test(test_pcc_api_suite, "test_connect_pce_with_src_ip", + test_connect_pce_with_src_ip); + CU_add_test(test_pcc_api_suite, "test_disconnect_pce", + test_disconnect_pce); + CU_add_test(test_pcc_api_suite, "test_send_message", test_send_message); + CU_add_test(test_pcc_api_suite, "test_event_queue", test_event_queue); + CU_add_test(test_pcc_api_suite, "test_get_event_type_str", + test_get_event_type_str); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_pcc_api_tests_valgrind.sh b/pceplib/test/pcep_pcc_api_tests_valgrind.sh new file mode 100755 index 0000000..74494b7 --- /dev/null +++ b/pceplib/test/pcep_pcc_api_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_pcc_api_tests diff --git a/pceplib/test/pcep_session_logic_loop_test.c b/pceplib/test/pcep_session_logic_loop_test.c new file mode 100644 index 0000000..0f74cff --- /dev/null +++ b/pceplib/test/pcep_session_logic_loop_test.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pcep_msg_encoding.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_memory.h" +#include "pcep_session_logic_loop_test.h" + + +extern pcep_session_logic_handle *session_logic_handle_; +extern pcep_event_queue *session_logic_event_queue_; + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_loop_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_loop_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_loop_test_setup() +{ + /* We need to setup the session_logic_handle_ without starting the + * thread */ + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + session_logic_handle_->active = true; + session_logic_handle_->session_list = + ordered_list_initialize(pointer_compare_function); + session_logic_handle_->session_event_queue = queue_initialize(); + pthread_cond_init(&(session_logic_handle_->session_logic_cond_var), + NULL); + pthread_mutex_init(&(session_logic_handle_->session_logic_mutex), NULL); + pthread_mutex_init(&(session_logic_handle_->session_list_mutex), NULL); + + pthread_mutex_lock(&(session_logic_handle_->session_logic_mutex)); + session_logic_handle_->session_logic_condition = true; + pthread_cond_signal(&(session_logic_handle_->session_logic_cond_var)); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + memset(session_logic_event_queue_, 0, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); +} + + +void pcep_session_logic_loop_test_teardown() +{ + ordered_list_destroy(session_logic_handle_->session_list); + queue_destroy(session_logic_handle_->session_event_queue); + pthread_mutex_unlock(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_logic_mutex)); + pthread_mutex_destroy(&(session_logic_handle_->session_list_mutex)); + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + session_logic_handle_ = NULL; + + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + session_logic_event_queue_ = NULL; +} + + +/* + * Test cases + */ + +void test_session_logic_loop_null_data() +{ + /* Just testing that it does not core dump */ + session_logic_loop(NULL); +} + + +void test_session_logic_loop_inactive() +{ + session_logic_handle_->active = false; + + session_logic_loop(session_logic_handle_); +} + + +void test_session_logic_msg_ready_handler() +{ + /* Just testing that it does not core dump */ + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(NULL, 0), -1); + + /* Read from an empty file should return 0, thus + * session_logic_msg_ready_handler returns -1 */ + mode_t oldumask; + oldumask = umask(S_IXUSR | S_IXGRP | S_IWOTH | S_IROTH | S_IXOTH); + /* Set umask before anything for security */ + umask(0027); + char tmpfile[] = "/tmp/pceplib_XXXXXX"; + int fd = mkstemp(tmpfile); + umask(oldumask); + if (fd == -1) { + CU_ASSERT_TRUE(fd >= 0); + return; + } + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(&session, fd), 0); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL(socket_event); + assert(socket_event != NULL); + CU_ASSERT_TRUE(socket_event->socket_closed); + pceplib_free(PCEPLIB_INFRA, socket_event); + + /* A pcep_session_event should be created */ + struct pcep_versioning *versioning = create_default_pcep_versioning(); + struct pcep_message *keep_alive_msg = pcep_msg_create_keepalive(); + pcep_encode_message(keep_alive_msg, versioning); + int retval = write(fd, (char *)keep_alive_msg->encoded_message, + keep_alive_msg->encoded_message_length); + CU_ASSERT_TRUE(retval > 0); + lseek(fd, 0, SEEK_SET); + CU_ASSERT_EQUAL(session_logic_msg_ready_handler(&session, fd), + keep_alive_msg->encoded_message_length); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL(socket_event); + assert(socket_event != NULL); + CU_ASSERT_FALSE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, TIMER_ID_NOT_SET); + CU_ASSERT_PTR_NOT_NULL(socket_event->received_msg_list); + pcep_msg_free_message_list(socket_event->received_msg_list); + pcep_msg_free_message(keep_alive_msg); + destroy_pcep_versioning(versioning); + pceplib_free(PCEPLIB_INFRA, socket_event); + close(fd); + unlink(tmpfile); +} + + +void test_session_logic_conn_except_notifier() +{ + /* Just testing that it does not core dump */ + session_logic_conn_except_notifier(NULL, 1); + + /* A pcep_session_event should be created */ + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + session_logic_conn_except_notifier(&session, 10); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL_FATAL(socket_event); + assert(socket_event != NULL); + CU_ASSERT_TRUE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, TIMER_ID_NOT_SET); + CU_ASSERT_PTR_NULL(socket_event->received_msg_list); + + pceplib_free(PCEPLIB_INFRA, socket_event); +} + + +void test_session_logic_timer_expire_handler() +{ + /* Just testing that it does not core dump */ + session_logic_timer_expire_handler(NULL, 42); + + /* A pcep_session_event should be created */ + pcep_session session; + memset(&session, 0, sizeof(pcep_session)); + session.session_id = 100; + session_logic_timer_expire_handler(&session, 42); + CU_ASSERT_EQUAL(session_logic_handle_->session_event_queue->num_entries, + 1); + pcep_session_event *socket_event = (pcep_session_event *)queue_dequeue( + session_logic_handle_->session_event_queue); + CU_ASSERT_PTR_NOT_NULL_FATAL(socket_event); + assert(socket_event != NULL); + CU_ASSERT_FALSE(socket_event->socket_closed); + CU_ASSERT_PTR_EQUAL(socket_event->session, &session); + CU_ASSERT_EQUAL(socket_event->expired_timer_id, 42); + CU_ASSERT_PTR_NULL(socket_event->received_msg_list); + + pceplib_free(PCEPLIB_INFRA, socket_event); +} diff --git a/pceplib/test/pcep_session_logic_loop_test.h b/pceplib/test/pcep_session_logic_loop_test.h new file mode 100644 index 0000000..f2ad822 --- /dev/null +++ b/pceplib/test/pcep_session_logic_loop_test.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_LOOP_TEST_H_ +#define PCEP_SESSION_LOGIC_LOOP_TEST_H_ + +int pcep_session_logic_loop_test_suite_setup(void); +int pcep_session_logic_loop_test_suite_teardown(void); +void pcep_session_logic_loop_test_setup(void); +void pcep_session_logic_loop_test_teardown(void); +void test_session_logic_loop_null_data(void); +void test_session_logic_loop_inactive(void); +void test_session_logic_msg_ready_handler(void); +void test_session_logic_conn_except_notifier(void); +void test_session_logic_timer_expire_handler(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_states_test.c b/pceplib/test/pcep_session_logic_states_test.c new file mode 100644 index 0000000..695d59d --- /dev/null +++ b/pceplib/test/pcep_session_logic_states_test.c @@ -0,0 +1,921 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include + +#include "pcep_socket_comm_mock.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_internals.h" +#include "pcep_timers.h" +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_memory.h" +#include "pcep_msg_objects.h" +#include "pcep_msg_tools.h" +#include "pcep_session_logic_states_test.h" + +/* Functions being tested */ +extern pcep_session_logic_handle *session_logic_handle_; +extern pcep_event_queue *session_logic_event_queue_; + +static pcep_session_event event; +static pcep_session session; +/* A message list is a dll of struct pcep_messages_list_node items */ +static double_linked_list *msg_list; +struct pcep_message *message; +static bool free_msg_list; +static bool msg_enqueued; +/* Forward declaration */ +void destroy_message_for_test(void); +void create_message_for_test(uint8_t msg_type, bool free_msg_list_at_teardown, + bool was_msg_enqueued); +void test_handle_timer_event_open_keep_alive(void); + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_states_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_states_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_states_test_setup() +{ + session_logic_handle_ = pceplib_malloc( + PCEPLIB_INFRA, sizeof(pcep_session_logic_handle)); + memset(session_logic_handle_, 0, sizeof(pcep_session_logic_handle)); + + session_logic_event_queue_ = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_event_queue)); + memset(session_logic_event_queue_, 0, sizeof(pcep_event_queue)); + session_logic_event_queue_->event_queue = queue_initialize(); + + memset(&session, 0, sizeof(pcep_session)); + session.pcc_config.keep_alive_seconds = 5; + session.pcc_config.keep_alive_pce_negotiated_timer_seconds = 5; + session.pcc_config.min_keep_alive_seconds = 1; + session.pcc_config.max_keep_alive_seconds = 10; + session.pcc_config.dead_timer_seconds = 5; + session.pcc_config.dead_timer_pce_negotiated_seconds = 5; + session.pcc_config.min_dead_timer_seconds = 1; + session.pcc_config.max_dead_timer_seconds = 10; + session.pcc_config.max_unknown_messages = 2; + memcpy(&session.pce_config, &session.pcc_config, + sizeof(pcep_configuration)); + session.num_unknown_messages_time_queue = queue_initialize(); + + memset(&event, 0, sizeof(pcep_session_event)); + event.socket_closed = false; + event.session = &session; + + setup_mock_socket_comm_info(); + free_msg_list = false; + msg_enqueued = false; +} + + +void pcep_session_logic_states_test_teardown() +{ + destroy_message_for_test(); + pceplib_free(PCEPLIB_INFRA, session_logic_handle_); + queue_destroy(session_logic_event_queue_->event_queue); + pceplib_free(PCEPLIB_INFRA, session_logic_event_queue_); + session_logic_handle_ = NULL; + session_logic_event_queue_ = NULL; + queue_destroy_with_data(session.num_unknown_messages_time_queue); + teardown_mock_socket_comm_info(); +} + +void create_message_for_test(uint8_t msg_type, bool free_msg_list_at_teardown, + bool was_msg_enqueued) +{ + /* See the comments in destroy_message_for_test() about these 2 + * variables */ + free_msg_list = free_msg_list_at_teardown; + msg_enqueued = was_msg_enqueued; + + message = pceplib_malloc(PCEPLIB_MESSAGES, sizeof(struct pcep_message)); + memset(message, 0, sizeof(struct pcep_message)); + + message->msg_header = pceplib_malloc( + PCEPLIB_MESSAGES, sizeof(struct pcep_message_header)); + memset(message->msg_header, 0, sizeof(struct pcep_message_header)); + message->obj_list = dll_initialize(); + message->msg_header->type = msg_type; + + msg_list = dll_initialize(); + dll_append(msg_list, message); + event.received_msg_list = msg_list; +} + +void destroy_message_for_test() +{ + /* Some test cases internally free the message list, so we dont + * want to double free it */ + if (free_msg_list == true) { + /* This will destroy both the msg_list and the obj_list */ + pcep_msg_free_message_list(msg_list); + } + + /* Some tests cause the message to be enqueued and dont delete it, + * so we have to delete it here */ + if (msg_enqueued == true) { + pcep_msg_free_message(message); + } +} + +/* + * Test cases + */ + +void test_handle_timer_event_dead_timer() +{ + /* Dead Timer expired */ + event.expired_timer_id = session.timer_id_dead_timer = 100; + + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_dead_timer, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_DEAD_TIMER_EXPIRED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* verify_socket_comm_times_called( + * initialized, teardown, connect, send_message, close_after_write, + * close, destroy); */ + verify_socket_comm_times_called(0, 0, 0, 1, 1, 0, 0); +} + + +void test_handle_timer_event_keep_alive() +{ + /* Keep Alive timer expired */ + event.expired_timer_id = session.timer_id_keep_alive = 200; + + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_keep_alive, TIMER_ID_NOT_SET); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); +} + + +void test_handle_timer_event_open_keep_wait() +{ + /* Open Keep Wait timer expired */ + event.expired_timer_id = session.timer_id_open_keep_wait = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 1, 0, 0); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_OPEN_KEEP_WAIT_TIMER_EXPIRED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* If the state is not SESSION_STATE_PCEP_CONNECTED, then nothing should + * happen */ + reset_mock_socket_comm_info(); + session.session_state = SESSION_STATE_UNKNOWN; + event.expired_timer_id = session.timer_id_open_keep_wait = 300; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, 300); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_UNKNOWN); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); +} + + +void test_handle_timer_event_open_keep_alive() +{ + /* Open Keep Alive timer expired, but the Keep Alive should not be sent + * since the PCE Open has not been received yet */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = false; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + + /* Open Keep Alive timer expired, the Keep Alive should be sent, + * but the session should not be connected, since the PCC Open + * has not been accepted yet */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = true; + session.pce_open_rejected = false; + session.pcc_open_accepted = false; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + + /* Open Keep Alive timer expired, the Keep Alive should be sent, + * and the session is connected */ + event.expired_timer_id = session.timer_id_open_keep_alive = 300; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.pce_open_keep_alive_sent = false; + session.pce_open_received = true; + session.pce_open_rejected = false; + session.pcc_open_accepted = true; + handle_timer_event(&event); + + CU_ASSERT_EQUAL(session.timer_id_open_keep_alive, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); +} + + +void test_handle_socket_comm_event_null_params() +{ + /* Verify it doesn't core dump */ + handle_socket_comm_event(NULL); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + reset_mock_socket_comm_info(); + + event.received_msg_list = NULL; + handle_socket_comm_event(&event); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); +} + + +void test_handle_socket_comm_event_close() +{ + event.socket_closed = true; + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 1, 0); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_open() +{ + /* + * Test when a PCE Open is received, but the PCC Open has not been + * accepted yet + */ + create_message_for_test(PCEP_TYPE_OPEN, false, true); + struct pcep_object_open *open_object = + pcep_obj_create_open(1, 1, 1, NULL); + dll_append(message->obj_list, open_object); + session.pcc_open_accepted = false; + session.pce_open_received = false; + session.pce_open_accepted = false; + session.timer_id_open_keep_alive = 100; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_accepted); + CU_ASSERT_FALSE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_NOT_EQUAL(session.timer_id_open_keep_alive, 100); + /* A keep alive response should NOT be sent yet */ + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_OPEN, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* + * Test when a PCE Open is received, and the PCC Open has been accepted + */ + create_message_for_test(PCEP_TYPE_OPEN, false, true); + reset_mock_socket_comm_info(); + open_object = pcep_obj_create_open(1, 1, 1, NULL); + dll_append(message->obj_list, open_object); + session.pcc_open_accepted = true; + session.pce_open_received = false; + session.pce_open_accepted = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_accepted); + CU_ASSERT_FALSE(session.pce_open_rejected); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + /* A keep alive response should be sent, accepting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_OPEN, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTED_TO_PCE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* + * Send a 2nd Open, an error should be sent + */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + assert(msg != NULL); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_error *error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_ATTEMPT_TO_ESTABLISH_2ND_PCEP_SESSION, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_RECVD_INVALID_OPEN_MSG, + error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_open_error() +{ + /* Test when the PCE rejects the PCC Open with an Error + * that a "corrected" Open message is sent. */ + + create_message_for_test(PCEP_TYPE_ERROR, false, true); + struct pcep_object_error *error_object = pcep_obj_create_error( + PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG); + struct pcep_object_open *error_open_object = + pcep_obj_create_open(1, 1, 1, NULL); + /* The configured [Keep-alive, Dead-timer] values are [5, 5], + * this error open object will request they be changed to [10, 10] */ + error_open_object->open_keepalive = 10; + error_open_object->open_deadtimer = 10; + dll_append(message->obj_list, error_object); + dll_append(message->obj_list, error_open_object); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* Another Open should be sent */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_SENT_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + + /* Check the Corrected Open Message */ + + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + assert(encoded_msg != NULL); + struct pcep_message *open_msg_corrected = + pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg_corrected); + struct pcep_object_open *open_object_corrected = + (struct pcep_object_open *)pcep_obj_get( + open_msg_corrected->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_object_corrected); + assert(open_object_corrected != NULL); + /* Verify the Keep-alive and Dead timers have been negotiated */ + CU_ASSERT_EQUAL(error_open_object->open_keepalive, + open_object_corrected->open_keepalive); + CU_ASSERT_EQUAL(error_open_object->open_deadtimer, + open_object_corrected->open_deadtimer); + CU_ASSERT_EQUAL(session.pcc_config.dead_timer_pce_negotiated_seconds, + open_object_corrected->open_deadtimer); + CU_ASSERT_EQUAL( + session.pcc_config.keep_alive_pce_negotiated_timer_seconds, + open_object_corrected->open_keepalive); + CU_ASSERT_NOT_EQUAL( + session.pcc_config.dead_timer_pce_negotiated_seconds, + session.pcc_config.dead_timer_seconds); + CU_ASSERT_NOT_EQUAL( + session.pcc_config.keep_alive_pce_negotiated_timer_seconds, + session.pcc_config.keep_alive_seconds); + + pcep_msg_free_message(open_msg_corrected); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_keep_alive() +{ + /* Test when a Keep Alive is received, but the PCE Open has not been + * received yet */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_accepted = false; + session.pce_open_received = false; + session.pcc_open_accepted = false; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + + /* Test when a Keep Alive is received, and the PCE Open has been + * received and accepted */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_received = true; + session.pce_open_accepted = true; + session.pcc_open_accepted = false; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_TRUE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTED); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + + /* Test when a Keep Alive is received, and the PCE Open has been + * received and rejected */ + create_message_for_test(PCEP_TYPE_KEEPALIVE, false, false); + session.session_state = SESSION_STATE_PCEP_CONNECTING; + session.timer_id_dead_timer = 100; + session.timer_id_open_keep_wait = 200; + session.pce_open_received = true; + session.pce_open_accepted = false; + session.pce_open_rejected = true; + session.pce_open_keep_alive_sent = false; + session.pcc_open_accepted = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_accepted); + CU_ASSERT_FALSE(session.pce_open_keep_alive_sent); + CU_ASSERT_FALSE(session.pcc_open_rejected); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + CU_ASSERT_EQUAL(session.timer_id_open_keep_wait, TIMER_ID_NOT_SET); + CU_ASSERT_EQUAL(session.timer_id_dead_timer, 100); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + + /* The session is considered connected, when both the + * PCE and PCC Open messages have been accepted */ + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTED_TO_PCE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_pcrep() +{ + create_message_for_test(PCEP_TYPE_PCREP, false, true); + struct pcep_object_rp *rp = + pcep_obj_create_rp(1, true, true, true, true, 1, NULL); + dll_append(message->obj_list, rp); + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_pcreq() +{ + create_message_for_test(PCEP_TYPE_PCREQ, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* The PCC does not support receiving PcReq messages, so an error should + * be sent */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *error_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(error_msg); + assert(error_msg != NULL); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, error_msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, error_msg->obj_list->num_entries); + struct pcep_object_error *obj = error_msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, obj->error_value); + pcep_msg_free_message(error_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_report() +{ + create_message_for_test(PCEP_TYPE_REPORT, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* The PCC does not support receiving Report messages, so an error + * should be sent */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *error_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(error_msg); + assert(error_msg != NULL); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, error_msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, error_msg->obj_list->num_entries); + struct pcep_object_error *obj = error_msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, obj->error_value); + pcep_msg_free_message(error_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_handle_socket_comm_event_update() +{ + create_message_for_test(PCEP_TYPE_UPDATE, false, true); + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + double_linked_list *ero_subobj_list = dll_initialize(); + dll_append(ero_subobj_list, pcep_obj_create_ro_subobj_asn(0x0102)); + struct pcep_object_ro *ero = pcep_obj_create_ero(ero_subobj_list); + struct pcep_object_metric *metric = + pcep_obj_create_metric(PCEP_METRIC_TE, false, true, 16.0); + dll_append(message->obj_list, srp); + dll_append(message->obj_list, lsp); + dll_append(message->obj_list, ero); + dll_append(message->obj_list, metric); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_UPDATE, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_initiate() +{ + create_message_for_test(PCEP_TYPE_INITIATE, false, true); + struct pcep_object_srp *srp = pcep_obj_create_srp(false, 100, NULL); + struct pcep_object_lsp *lsp = + pcep_obj_create_lsp(100, PCEP_LSP_OPERATIONAL_UP, true, true, + true, true, true, NULL); + dll_append(message->obj_list, srp); + dll_append(message->obj_list, lsp); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_INITIATE, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_notify() +{ + create_message_for_test(PCEP_TYPE_PCNOTF, false, true); + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_PCNOTF, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_error() +{ + create_message_for_test(PCEP_TYPE_ERROR, false, true); + handle_socket_comm_event(&event); + + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 0, 0); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); +} + + +void test_handle_socket_comm_event_unknown_msg() +{ + create_message_for_test(13, false, false); + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + /* Sending an unsupported message type, so an error should be sent, + * but the connection should remain open, since max_unknown_messages = 2 + */ + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 0); + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + assert(msg != NULL); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_error *error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + destroy_message_for_test(); + + /* Send another unsupported message type, an error should be sent and + * the connection should be closed, since max_unknown_messages = 2 */ + create_message_for_test(13, false, false); + reset_mock_socket_comm_info(); + mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + + handle_socket_comm_event(&event); + + verify_socket_comm_times_called(0, 0, 0, 2, 1, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_MAX_UNKOWN_MSGS, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + /* Verify the error message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + assert(encoded_msg != NULL); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + error_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_ERROR, error_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_ERROR, error_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_ERRT_CAPABILITY_NOT_SUPPORTED, + error_obj->error_type); + CU_ASSERT_EQUAL(PCEP_ERRV_UNASSIGNED, error_obj->error_value); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the Close message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + assert(encoded_msg != NULL); + msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(msg); + assert(msg != NULL); + CU_ASSERT_EQUAL(PCEP_TYPE_CLOSE, msg->msg_header->type); + /* Verify the error object */ + CU_ASSERT_EQUAL(1, msg->obj_list->num_entries); + struct pcep_object_close *close_obj = msg->obj_list->head->data; + CU_ASSERT_EQUAL(PCEP_OBJ_CLASS_CLOSE, close_obj->header.object_class); + CU_ASSERT_EQUAL(PCEP_OBJ_TYPE_CLOSE, close_obj->header.object_type); + CU_ASSERT_EQUAL(PCEP_CLOSE_REASON_UNREC_MSG, close_obj->reason); + pcep_msg_free_message(msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); +} + + +void test_connection_failure(void) +{ + /* + * Test when 2 invalid Open messages are received that a + * PCC_CONNECTION_FAILURE event is generated. + */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + struct pcep_object_open *open_object = + pcep_obj_create_open(1, 1, 1, NULL); + /* Make the Open message invalid */ + open_object->open_deadtimer = + session.pcc_config.max_dead_timer_seconds + 1; + dll_append(message->obj_list, open_object); + session.pce_open_received = false; + session.pce_open_accepted = false; + session.pce_open_rejected = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* An error response should be sent, rejecting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 1); + pcep_event *e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* Send the same erroneous Open again */ + create_message_for_test(PCEP_TYPE_OPEN, false, false); + reset_mock_socket_comm_info(); + open_object = pcep_obj_create_open(1, 1, 1, NULL); + /* Make the Open message invalid */ + open_object->open_deadtimer = + session.pcc_config.max_dead_timer_seconds + 1; + dll_append(message->obj_list, open_object); + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pce_open_received); + CU_ASSERT_TRUE(session.pce_open_rejected); + CU_ASSERT_FALSE(session.pce_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + /* An error response should be sent, rejecting the Open */ + verify_socket_comm_times_called(0, 0, 0, 1, 1, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_RCVD_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTION_FAILURE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + + destroy_message_for_test(); + + /* + * Test when 2 invalid Open messages are sent that a + * PCC_CONNECTION_FAILURE event is generated. + */ + create_message_for_test(PCEP_TYPE_ERROR, false, false); + reset_mock_socket_comm_info(); + struct pcep_object_error *error_object = pcep_obj_create_error( + PCEP_ERRT_SESSION_FAILURE, PCEP_ERRV_UNACCEPTABLE_OPEN_MSG_NEG); + dll_append(message->obj_list, error_object); + session.pcc_open_accepted = false; + session.pcc_open_rejected = false; + session.session_state = SESSION_STATE_PCEP_CONNECTING; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pcc_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_PCEP_CONNECTING); + /* Another Open should be sent */ + verify_socket_comm_times_called(0, 0, 0, 1, 0, 0, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_SENT_INVALID_OPEN, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(MESSAGE_RECEIVED, e->event_type); + CU_ASSERT_EQUAL(PCEP_TYPE_ERROR, e->message->msg_header->type); + pceplib_free(PCEPLIB_INFRA, e); + destroy_message_for_test(); + + /* Send a socket close while connecting, which should + * generate a PCC_CONNECTION_FAILURE event */ + reset_mock_socket_comm_info(); + event.socket_closed = true; + event.received_msg_list = NULL; + + handle_socket_comm_event(&event); + + CU_ASSERT_TRUE(session.pcc_open_rejected); + CU_ASSERT_FALSE(session.pcc_open_accepted); + CU_ASSERT_EQUAL(session.session_state, SESSION_STATE_INITIALIZED); + verify_socket_comm_times_called(0, 0, 0, 0, 0, 1, 0); + CU_ASSERT_EQUAL(session_logic_event_queue_->event_queue->num_entries, + 2); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCE_CLOSED_SOCKET, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); + e = queue_dequeue(session_logic_event_queue_->event_queue); + CU_ASSERT_EQUAL(PCC_CONNECTION_FAILURE, e->event_type); + pceplib_free(PCEPLIB_INFRA, e); +} diff --git a/pceplib/test/pcep_session_logic_states_test.h b/pceplib/test/pcep_session_logic_states_test.h new file mode 100644 index 0000000..3d8dcc0 --- /dev/null +++ b/pceplib/test/pcep_session_logic_states_test.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_STATES_TEST_H +#define PCEP_SESSION_LOGIC_STATES_TEST_H + +int pcep_session_logic_states_test_suite_setup(void); +int pcep_session_logic_states_test_suite_teardown(void); +void pcep_session_logic_states_test_setup(void); +void pcep_session_logic_states_test_teardown(void); +void test_handle_timer_event_dead_timer(void); +void test_handle_timer_event_keep_alive(void); +void test_handle_timer_event_open_keep_wait(void); +void test_handle_socket_comm_event_null_params(void); +void test_handle_socket_comm_event_close(void); +void test_handle_socket_comm_event_open(void); +void test_handle_socket_comm_event_open_error(void); +void test_handle_socket_comm_event_keep_alive(void); +void test_handle_socket_comm_event_pcrep(void); +void test_handle_socket_comm_event_pcreq(void); +void test_handle_socket_comm_event_report(void); +void test_handle_socket_comm_event_update(void); +void test_handle_socket_comm_event_initiate(void); +void test_handle_socket_comm_event_notify(void); +void test_handle_socket_comm_event_error(void); +void test_handle_socket_comm_event_unknown_msg(void); +void test_connection_failure(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_test.c b/pceplib/test/pcep_session_logic_test.c new file mode 100644 index 0000000..c5b2bb3 --- /dev/null +++ b/pceplib/test/pcep_session_logic_test.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#include + +#include "pcep_socket_comm_mock.h" +#include "pcep_session_logic.h" +#include "pcep_session_logic_test.h" + +/* + * Test suite setup and teardown called before AND after the test suite. + */ + +int pcep_session_logic_test_suite_setup(void) +{ + pceplib_memory_reset(); + return 0; +} + +int pcep_session_logic_test_suite_teardown(void) +{ + printf("\n"); + pceplib_memory_dump(); + return 0; +} + +/* + * Test case setup and teardown called before AND after each test. + */ + +void pcep_session_logic_test_setup() +{ + setup_mock_socket_comm_info(); +} + + +void pcep_session_logic_test_teardown() +{ + stop_session_logic(); + teardown_mock_socket_comm_info(); +} + + +/* + * Test cases + */ + +void test_run_stop_session_logic() +{ + CU_ASSERT_TRUE(run_session_logic()); + CU_ASSERT_TRUE(stop_session_logic()); +} + + +void test_run_session_logic_twice() +{ + CU_ASSERT_TRUE(run_session_logic()); + CU_ASSERT_FALSE(run_session_logic()); +} + + +void test_session_logic_without_run() +{ + /* Verify the functions that depend on run_session_logic() being called + */ + CU_ASSERT_FALSE(stop_session_logic()); +} + + +void test_create_pcep_session_null_params() +{ + pcep_configuration config; + struct in_addr pce_ip; + + CU_ASSERT_PTR_NULL(create_pcep_session(NULL, NULL)); + CU_ASSERT_PTR_NULL(create_pcep_session(NULL, &pce_ip)); + CU_ASSERT_PTR_NULL(create_pcep_session(&config, NULL)); +} + + +void test_create_destroy_pcep_session() +{ + pcep_session *session; + pcep_configuration config; + struct in_addr pce_ip; + + run_session_logic(); + + memset(&config, 0, sizeof(pcep_configuration)); + config.keep_alive_seconds = 5; + config.dead_timer_seconds = 5; + config.request_time_seconds = 5; + config.max_unknown_messages = 5; + config.max_unknown_requests = 5; + inet_pton(AF_INET, "127.0.0.1", &(pce_ip)); + + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + /* Should be an Open, with no TLVs: length = 12 */ + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(open_msg->encoded_message_length, 12); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_create_destroy_pcep_session_ipv6() +{ + pcep_session *session; + pcep_configuration config; + struct in6_addr pce_ip; + + run_session_logic(); + + memset(&config, 0, sizeof(pcep_configuration)); + config.keep_alive_seconds = 5; + config.dead_timer_seconds = 5; + config.request_time_seconds = 5; + config.max_unknown_messages = 5; + config.max_unknown_requests = 5; + config.is_src_ipv6 = true; + inet_pton(AF_INET6, "::1", &pce_ip); + + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + session = create_pcep_session_ipv6(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + assert(session != NULL); + CU_ASSERT_TRUE(session->socket_comm_session->is_ipv6); + /* What gets saved in the mock is the msg byte buffer. The msg struct + * was deleted when it was sent. Instead of inspecting the msg byte + * buffer, lets just decode it. */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + struct pcep_message *open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + /* Should be an Open, with no TLVs: length = 12 */ + CU_ASSERT_EQUAL(open_msg->msg_header->type, PCEP_TYPE_OPEN); + CU_ASSERT_EQUAL(open_msg->encoded_message_length, 12); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_create_pcep_session_open_tlvs() +{ + pcep_session *session; + struct in_addr pce_ip; + struct pcep_message *open_msg; + struct pcep_object_header *open_obj; + pcep_configuration config; + memset(&config, 0, sizeof(pcep_configuration)); + config.pcep_msg_versioning = create_default_pcep_versioning(); + inet_pton(AF_INET, "127.0.0.1", &(pce_ip)); + + run_session_logic(); + + /* Verify the created Open message only has 1 TLV: + * pcep_tlv_create_stateful_pce_capability() */ + mock_socket_comm_info *mock_info = get_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_stateful_pce_lsp_update = true; + config.pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = false; + config.support_sr_te_pst = false; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + uint8_t *encoded_msg = + dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + assert(open_msg->obj_list != NULL); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + assert(open_obj->tlv_list != NULL); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 1); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->data) + ->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the created Open message only has 2 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_include_db_version = true; + config.lsp_db_version = 100; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + assert(open_msg != NULL); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + assert(open_obj->tlv_list != NULL); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 2); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->data) + ->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *) + open_obj->tlv_list->head->next_node->data) + ->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + + /* Verify the created Open message only has 4 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() + * pcep_tlv_create_sr_pce_capability() + * pcep_tlv_create_path_setup_type_capability() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.support_sr_te_pst = true; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + assert(open_msg->obj_list != NULL); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + assert(open_obj->tlv_list != NULL); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 3); + double_linked_list_node *tlv_node = open_obj->tlv_list->head; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + /* Verify the created Open message only has 4 TLVs: + * pcep_tlv_create_stateful_pce_capability() + * pcep_tlv_create_lsp_db_version() + * pcep_tlv_create_sr_pce_capability() + * pcep_tlv_create_path_setup_type_capability() */ + reset_mock_socket_comm_info(); + mock_info->send_message_save_message = true; + config.pcep_msg_versioning->draft_ietf_pce_segment_routing_07 = true; + + session = create_pcep_session(&config, &pce_ip); + CU_ASSERT_PTR_NOT_NULL(session); + /* Get and verify the Open Message */ + encoded_msg = dll_delete_first_node(mock_info->sent_message_list); + CU_ASSERT_PTR_NOT_NULL(encoded_msg); + assert(encoded_msg != NULL); + open_msg = pcep_decode_message(encoded_msg); + CU_ASSERT_PTR_NOT_NULL(open_msg); + assert(open_msg != NULL); + /* Get and verify the Open Message objects */ + CU_ASSERT_PTR_NOT_NULL(open_msg->obj_list); + assert(open_msg->obj_list != NULL); + CU_ASSERT_TRUE(open_msg->obj_list->num_entries > 0); + /* Get and verify the Open object */ + open_obj = pcep_obj_get(open_msg->obj_list, PCEP_OBJ_CLASS_OPEN); + CU_ASSERT_PTR_NOT_NULL(open_obj); + assert(open_obj != NULL); + /* Get and verify the Open object TLVs */ + CU_ASSERT_PTR_NOT_NULL(open_obj->tlv_list); + assert(open_obj->tlv_list != NULL); + CU_ASSERT_EQUAL(open_obj->tlv_list->num_entries, 4); + tlv_node = open_obj->tlv_list->head; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_STATEFUL_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_LSP_DB_VERSION); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_SR_PCE_CAPABILITY); + tlv_node = tlv_node->next_node; + CU_ASSERT_EQUAL(((struct pcep_object_tlv_header *)tlv_node->data)->type, + PCEP_OBJ_TLV_TYPE_PATH_SETUP_TYPE_CAPABILITY); + + destroy_pcep_versioning(config.pcep_msg_versioning); + destroy_pcep_session(session); + pcep_msg_free_message(open_msg); + pceplib_free(PCEPLIB_MESSAGES, encoded_msg); + + stop_session_logic(); +} + + +void test_destroy_pcep_session_null_session() +{ + /* Just testing that it does not core dump */ + destroy_pcep_session(NULL); +} diff --git a/pceplib/test/pcep_session_logic_test.h b/pceplib/test/pcep_session_logic_test.h new file mode 100644 index 0000000..325b807 --- /dev/null +++ b/pceplib/test/pcep_session_logic_test.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SESSION_LOGIC_TEST_H_ +#define PCEP_SESSION_LOGIC_TEST_H_ + +int pcep_session_logic_test_suite_setup(void); +int pcep_session_logic_test_suite_teardown(void); +void pcep_session_logic_test_setup(void); +void pcep_session_logic_test_teardown(void); +void test_run_stop_session_logic(void); +void test_run_session_logic_twice(void); +void test_session_logic_without_run(void); +void test_create_pcep_session_null_params(void); +void test_create_destroy_pcep_session(void); +void test_create_destroy_pcep_session_ipv6(void); +void test_create_pcep_session_open_tlvs(void); +void test_destroy_pcep_session_null_session(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_session_logic_tests.c b/pceplib/test/pcep_session_logic_tests.c new file mode 100644 index 0000000..eac574e --- /dev/null +++ b/pceplib/test/pcep_session_logic_tests.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_session_logic_loop_test.h" +#include "pcep_session_logic_states_test.h" +#include "pcep_session_logic_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_session_logic_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic Test Suite", + pcep_session_logic_test_suite_setup, // suite setup and + // cleanup function + // pointers + pcep_session_logic_test_suite_teardown, + pcep_session_logic_test_setup, // test case setup + // function pointer + pcep_session_logic_test_teardown); // test case teardown + // function pointer + + CU_add_test(test_session_logic_suite, "test_run_stop_session_logic", + test_run_stop_session_logic); + CU_add_test(test_session_logic_suite, "test_run_session_logic_twice", + test_run_session_logic_twice); + CU_add_test(test_session_logic_suite, "test_session_logic_without_run", + test_session_logic_without_run); + CU_add_test(test_session_logic_suite, + "test_create_pcep_session_null_params", + test_create_pcep_session_null_params); + CU_add_test(test_session_logic_suite, + "test_create_destroy_pcep_session", + test_create_destroy_pcep_session); + CU_add_test(test_session_logic_suite, + "test_create_destroy_pcep_session_ipv6", + test_create_destroy_pcep_session_ipv6); + CU_add_test(test_session_logic_suite, + "test_create_pcep_session_open_tlvs", + test_create_pcep_session_open_tlvs); + CU_add_test(test_session_logic_suite, + "test_destroy_pcep_session_null_session", + test_destroy_pcep_session_null_session); + + CU_pSuite test_session_logic_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic Loop Test Suite", + pcep_session_logic_loop_test_suite_setup, // suite setup + // and cleanup + // function + // pointers + pcep_session_logic_loop_test_suite_teardown, + pcep_session_logic_loop_test_setup, // test case setup + // function pointer + pcep_session_logic_loop_test_teardown); // test case + // teardown + // function + // pointer + + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_loop_null_data", + test_session_logic_loop_null_data); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_loop_inactive", + test_session_logic_loop_inactive); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_msg_ready_handler", + test_session_logic_msg_ready_handler); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_conn_except_notifier", + test_session_logic_conn_except_notifier); + CU_add_test(test_session_logic_loop_suite, + "test_session_logic_timer_expire_handler", + test_session_logic_timer_expire_handler); + + CU_pSuite test_session_logic_states_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Session Logic States Test Suite", + pcep_session_logic_states_test_suite_setup, // suite + // setup and + // cleanup + // function + // pointers + pcep_session_logic_states_test_suite_teardown, + pcep_session_logic_states_test_setup, // test case setup + // function + // pointer + pcep_session_logic_states_test_teardown); // test case + // teardown + // function + // pointer + + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_dead_timer", + test_handle_timer_event_dead_timer); + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_keep_alive", + test_handle_timer_event_keep_alive); + CU_add_test(test_session_logic_states_suite, + "test_handle_timer_event_open_keep_wait", + test_handle_timer_event_open_keep_wait); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_null_params", + test_handle_socket_comm_event_null_params); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_close", + test_handle_socket_comm_event_close); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_open", + test_handle_socket_comm_event_open); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_open_error", + test_handle_socket_comm_event_open_error); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_keep_alive", + test_handle_socket_comm_event_keep_alive); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_pcrep", + test_handle_socket_comm_event_pcrep); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_pcreq", + test_handle_socket_comm_event_pcreq); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_report", + test_handle_socket_comm_event_report); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_update", + test_handle_socket_comm_event_update); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_initiate", + test_handle_socket_comm_event_initiate); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_notify", + test_handle_socket_comm_event_notify); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_error", + test_handle_socket_comm_event_error); + CU_add_test(test_session_logic_states_suite, + "test_handle_socket_comm_event_unknown_msg", + test_handle_socket_comm_event_unknown_msg); + CU_add_test(test_session_logic_states_suite, "test_connection_failure", + test_connection_failure); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_session_logic_tests_valgrind.sh b/pceplib/test/pcep_session_logic_tests_valgrind.sh new file mode 100755 index 0000000..435bb3d --- /dev/null +++ b/pceplib/test/pcep_session_logic_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_session_logic_tests diff --git a/pceplib/test/pcep_socket_comm_loop_test.c b/pceplib/test/pcep_socket_comm_loop_test.c new file mode 100644 index 0000000..3b51635 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_loop_test.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_loop.h" +#include "pcep_socket_comm_loop_test.h" +#include "pcep_socket_comm.h" +#include "pcep_utils_memory.h" + +void test_loop_conn_except_notifier(void *session_data, int socket_fd); + +/* + * Functions to be tested, implemented in pcep_socket_comm_loop.c + */ + +typedef struct ready_to_read_handler_info_ { + bool handler_called; + bool except_handler_called; + void *data; + int socket_fd; + int bytes_read; + +} ready_to_read_handler_info; + +static ready_to_read_handler_info read_handler_info; +static pcep_socket_comm_session *test_comm_session; +static pcep_socket_comm_handle *test_socket_comm_handle = NULL; + +static int test_loop_message_ready_to_read_handler(void *session_data, + int socket_fd) +{ + read_handler_info.handler_called = true; + read_handler_info.data = session_data; + read_handler_info.socket_fd = socket_fd; + + return read_handler_info.bytes_read; +} + + +void test_loop_conn_except_notifier(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + read_handler_info.except_handler_called = true; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ +void pcep_socket_comm_loop_test_setup() +{ + test_socket_comm_handle = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_handle)); + memset(test_socket_comm_handle, 0, sizeof(pcep_socket_comm_handle)); + test_socket_comm_handle->active = false; + test_socket_comm_handle->read_list = + ordered_list_initialize(socket_fd_node_compare); + test_socket_comm_handle->write_list = + ordered_list_initialize(socket_fd_node_compare); + test_socket_comm_handle->session_list = + ordered_list_initialize(pointer_compare_function); + pthread_mutex_init(&test_socket_comm_handle->socket_comm_mutex, NULL); + test_socket_comm_handle->num_active_sessions = 0; + + test_comm_session = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_socket_comm_session)); + memset(test_comm_session, 0, sizeof(pcep_socket_comm_session)); + test_comm_session->message_ready_to_read_handler = + test_loop_message_ready_to_read_handler; + ordered_list_add_node(test_socket_comm_handle->session_list, + test_comm_session); + + read_handler_info.handler_called = false; + read_handler_info.except_handler_called = false; + read_handler_info.data = NULL; + read_handler_info.socket_fd = -1; + read_handler_info.bytes_read = 0; +} + + +void pcep_socket_comm_loop_test_teardown() +{ + pthread_mutex_destroy(&test_socket_comm_handle->socket_comm_mutex); + ordered_list_destroy(test_socket_comm_handle->read_list); + ordered_list_destroy(test_socket_comm_handle->write_list); + ordered_list_destroy(test_socket_comm_handle->session_list); + pceplib_free(PCEPLIB_INFRA, test_socket_comm_handle); + test_socket_comm_handle = NULL; + + if (test_comm_session != NULL) { + pceplib_free(PCEPLIB_INFRA, test_comm_session); + test_comm_session = NULL; + } +} + + +/* + * Test cases + */ + +void test_socket_comm_loop_null_handle() +{ + /* Verify that socket_comm_loop() correctly handles a NULL + * timers_context */ + socket_comm_loop(NULL); +} + + +void test_socket_comm_loop_not_active() +{ + /* Verify that event_loop() correctly handles an inactive flag */ + pcep_socket_comm_handle handle; + handle.active = false; + socket_comm_loop(&handle); +} + + +void test_handle_reads_no_read() +{ + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_FALSE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); +} + + +void test_handle_reads_read_message() +{ + /* Setup the comm session so that it can read. + * It should read 100 bytes, which simulates a successful read */ + test_comm_session->socket_fd = 10; + read_handler_info.bytes_read = 100; + FD_SET(test_comm_session->socket_fd, + &test_socket_comm_handle->read_master_set); + ordered_list_add_node(test_socket_comm_handle->read_list, + test_comm_session); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_TRUE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_EQUAL(test_comm_session->received_bytes, + read_handler_info.bytes_read); +} + + +void test_handle_reads_read_message_close() +{ + /* Setup the comm session so that it can read. + * It should read 0 bytes, which simulates that the socket closed */ + test_comm_session->socket_fd = 11; + read_handler_info.bytes_read = 0; + FD_SET(test_comm_session->socket_fd, + &test_socket_comm_handle->read_master_set); + ordered_list_add_node(test_socket_comm_handle->read_list, + test_comm_session); + + handle_reads(test_socket_comm_handle); + + CU_ASSERT_TRUE(read_handler_info.handler_called); + CU_ASSERT_FALSE(read_handler_info.except_handler_called); + CU_ASSERT_EQUAL(test_comm_session->received_bytes, + read_handler_info.bytes_read); + CU_ASSERT_PTR_NULL(test_socket_comm_handle->read_list->head); +} diff --git a/pceplib/test/pcep_socket_comm_loop_test.h b/pceplib/test/pcep_socket_comm_loop_test.h new file mode 100644 index 0000000..36220a9 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_loop_test.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SOCKET_COMM_LOOP_TEST_H_ +#define PCEP_SOCKET_COMM_LOOP_TEST_H_ + +void pcep_socket_comm_loop_test_setup(void); +void pcep_socket_comm_loop_test_teardown(void); +void test_socket_comm_loop_null_handle(void); +void test_socket_comm_loop_not_active(void); +void test_handle_reads_no_read(void); +void test_handle_reads_read_message(void); +void test_handle_reads_read_message_close(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_socket_comm_test.c b/pceplib/test/pcep_socket_comm_test.c new file mode 100644 index 0000000..c0b73c3 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_test.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_socket_comm.h" +#include "pcep_socket_comm_internals.h" +#include "pcep_socket_comm_test.h" + +extern pcep_socket_comm_handle *socket_comm_handle_; + +static pcep_socket_comm_session *test_session = NULL; +static struct in_addr test_host_ip; +static struct in_addr test_src_ip; +static struct in6_addr test_host_ipv6; +static struct in6_addr test_src_ipv6; +static short test_port = 4789; +static short test_src_port = 4999; +static uint32_t connect_timeout_millis = 500; + +/* + * Unit Test Basic pcep_socket_comm API usage. + * Testing sending messages, etc via sockets should be done + * with integration tests, not unit tests. + */ + +/* + * Different socket_comm handler test implementations + */ +static void test_message_received_handler(void *session_data, + const char *message_data, + unsigned int message_length) +{ + (void)session_data; + (void)message_data; + (void)message_length; +} + +static int test_message_ready_to_read_handler(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + return 1; +} + +static void test_message_sent_handler(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; + return; +} + +static void test_connection_except_notifier(void *session_data, int socket_fd) +{ + (void)session_data; + (void)socket_fd; +} + + +/* + * Test case setup and teardown called before AND after each test. + */ +void pcep_socket_comm_test_setup() +{ + inet_pton(AF_INET, "127.0.0.1", &(test_host_ip)); + inet_pton(AF_INET, "127.0.0.1", &(test_src_ip)); + inet_pton(AF_INET6, "::1", &(test_host_ipv6)); + inet_pton(AF_INET6, "::1", &(test_src_ipv6)); +} + +void pcep_socket_comm_test_teardown() +{ + socket_comm_session_teardown(test_session); + test_session = NULL; +} + + +/* + * Test cases + */ + +void test_pcep_socket_comm_initialize() +{ + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_FALSE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_ipv6() +{ + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_TRUE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_with_src() +{ + /* Test that INADDR_ANY will be used when src_ip is NULL */ + test_session = socket_comm_session_initialize_with_src( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, NULL, 0, &test_host_ip, + test_port, connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr, + INADDR_ANY); + CU_ASSERT_FALSE(test_session->is_ipv6); + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_with_src( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_src_ip, test_src_port, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv4.sin_addr.s_addr, + test_src_ip.s_addr); + CU_ASSERT_EQUAL(test_session->src_sock_addr.src_sock_addr_ipv4.sin_port, + ntohs(test_src_port)); + CU_ASSERT_FALSE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_with_src_ipv6() +{ + /* Test that INADDR6_ANY will be used when src_ip is NULL */ + test_session = socket_comm_session_initialize_with_src_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, NULL, 0, &test_host_ipv6, + test_port, connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(memcmp(&test_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + &in6addr_any, sizeof(struct in6_addr)), + 0); + CU_ASSERT_TRUE(test_session->is_ipv6); + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_with_src_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_src_ipv6, test_src_port, + &test_host_ipv6, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(memcmp(&test_session->src_sock_addr.src_sock_addr_ipv6 + .sin6_addr, + &test_src_ipv6, sizeof(struct in6_addr)), + 0); + CU_ASSERT_EQUAL( + test_session->src_sock_addr.src_sock_addr_ipv6.sin6_port, + ntohs(test_src_port)); + CU_ASSERT_TRUE(test_session->is_ipv6); +} + + +void test_pcep_socket_comm_initialize_tcpmd5() +{ + char tcp_md5_str[] = "hello"; + int tcp_md5_strlen = strlen(tcp_md5_str); + + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, 1, + tcp_md5_str, true, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_TRUE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); + /* This call does not work, it returns errno=92, Protocol not available + getsockopt(test_session->socket_fd, SOL_SOCKET, TCP_MD5SIG, &sig, + &siglen);*/ + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ip, test_port, 1, + tcp_md5_str, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_FALSE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); +} + + +void test_pcep_socket_comm_initialize_ipv6_tcpmd5() +{ + char tcp_md5_str[] = "hello"; + int tcp_md5_strlen = strlen(tcp_md5_str); + + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, 1, + tcp_md5_str, true, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_TRUE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); + /* This call does not work, it returns errno=92, Protocol not available + getsockopt(test_session->socket_fd, SOL_SOCKET, TCP_MD5SIG, &sig, + &siglen);*/ + + socket_comm_session_teardown(test_session); + test_session = socket_comm_session_initialize_ipv6( + test_message_received_handler, NULL, NULL, + test_connection_except_notifier, &test_host_ipv6, test_port, 1, + tcp_md5_str, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_EQUAL(0, strncmp(tcp_md5_str, + test_session->tcp_authentication_str, + tcp_md5_strlen)); + CU_ASSERT_FALSE(test_session->is_tcp_auth_md5); + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(test_session)); +} + + +void test_pcep_socket_comm_initialize_handlers() +{ + /* Verify incorrect handler usage is correctly handled */ + + /* Both receive handlers cannot be NULL */ + test_session = socket_comm_session_initialize( + NULL, NULL, NULL, test_connection_except_notifier, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NULL(test_session); + + /* Both receive handlers cannot be set */ + test_session = socket_comm_session_initialize( + test_message_received_handler, + test_message_ready_to_read_handler, test_message_sent_handler, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NULL(test_session); + + /* Only one receive handler can be set */ + test_session = socket_comm_session_initialize( + NULL, test_message_ready_to_read_handler, + test_message_sent_handler, test_connection_except_notifier, + &test_host_ip, test_port, connect_timeout_millis, NULL, false, + NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); +} + + +void test_pcep_socket_comm_session_not_initialized() +{ + CU_ASSERT_FALSE(socket_comm_session_connect_tcp(NULL)); + CU_ASSERT_FALSE(socket_comm_session_close_tcp(NULL)); + CU_ASSERT_FALSE(socket_comm_session_close_tcp_after_write(NULL)); + socket_comm_session_send_message(NULL, NULL, 0, true); + CU_ASSERT_FALSE(socket_comm_session_teardown(NULL)); +} + + +void test_pcep_socket_comm_session_destroy() +{ + test_session = socket_comm_session_initialize( + test_message_received_handler, NULL, test_message_sent_handler, + test_connection_except_notifier, &test_host_ip, test_port, + connect_timeout_millis, NULL, false, NULL); + CU_ASSERT_PTR_NOT_NULL(test_session); + assert(test_session != NULL); + CU_ASSERT_PTR_NOT_NULL(socket_comm_handle_); + assert(socket_comm_handle_ != NULL); + CU_ASSERT_EQUAL(socket_comm_handle_->num_active_sessions, 1); + + CU_ASSERT_TRUE(socket_comm_session_teardown(test_session)); + test_session = NULL; + CU_ASSERT_PTR_NOT_NULL(socket_comm_handle_); + + CU_ASSERT_TRUE(destroy_socket_comm_loop()); + CU_ASSERT_PTR_NULL(socket_comm_handle_); +} diff --git a/pceplib/test/pcep_socket_comm_test.h b/pceplib/test/pcep_socket_comm_test.h new file mode 100644 index 0000000..a4d1dd4 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_test.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_SOCKET_COMM_TEST_H_ +#define PCEP_SOCKET_COMM_TEST_H_ + +void pcep_socket_comm_test_teardown(void); +void pcep_socket_comm_test_setup(void); +void test_pcep_socket_comm_initialize(void); +void test_pcep_socket_comm_initialize_ipv6(void); +void test_pcep_socket_comm_initialize_with_src(void); +void test_pcep_socket_comm_initialize_with_src_ipv6(void); +void test_pcep_socket_comm_initialize_tcpmd5(void); +void test_pcep_socket_comm_initialize_ipv6_tcpmd5(void); +void test_pcep_socket_comm_initialize_handlers(void); +void test_pcep_socket_comm_session_not_initialized(void); +void test_pcep_socket_comm_session_destroy(void); + +#endif diff --git a/pceplib/test/pcep_socket_comm_tests.c b/pceplib/test/pcep_socket_comm_tests.c new file mode 100644 index 0000000..2f39a9d --- /dev/null +++ b/pceplib/test/pcep_socket_comm_tests.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_socket_comm_loop_test.h" +#include "pcep_socket_comm_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_socket_comm_test.c + */ + CU_pSuite test_socket_comm_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Socket Comm Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + pcep_socket_comm_test_setup, // test case setup function pointer + pcep_socket_comm_test_teardown); // test case teardown function + // pointer + + CU_add_test(test_socket_comm_suite, "test_pcep_socket_comm_initialize", + test_pcep_socket_comm_initialize); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_ipv6", + test_pcep_socket_comm_initialize_ipv6); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_with_src", + test_pcep_socket_comm_initialize_with_src); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_with_src_ipv6", + test_pcep_socket_comm_initialize_with_src_ipv6); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_tcpmd5", + test_pcep_socket_comm_initialize_tcpmd5); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_ipv6_tcpmd5", + test_pcep_socket_comm_initialize_ipv6_tcpmd5); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_initialize_handlers", + test_pcep_socket_comm_initialize_handlers); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_session_not_initialized", + test_pcep_socket_comm_session_not_initialized); + CU_add_test(test_socket_comm_suite, + "test_pcep_socket_comm_session_destroy", + test_pcep_socket_comm_session_destroy); + + /* + * Tests defined in pcep_socket_comm_loop_test.c + */ + CU_pSuite test_socket_comm_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Socket Comm Loop Test Suite", NULL, NULL, + pcep_socket_comm_loop_test_setup, // suite setup + // function pointer + pcep_socket_comm_loop_test_teardown); // suite cleanup + // function + // pointer + + CU_add_test(test_socket_comm_loop_suite, + "test_socket_comm_loop_null_handle", + test_socket_comm_loop_null_handle); + CU_add_test(test_socket_comm_loop_suite, + "test_socket_comm_loop_not_active", + test_socket_comm_loop_not_active); + CU_add_test(test_socket_comm_loop_suite, "test_handle_reads_no_read", + test_handle_reads_no_read); + CU_add_test(test_socket_comm_loop_suite, + "test_handle_reads_read_message", + test_handle_reads_read_message); + CU_add_test(test_socket_comm_loop_suite, + "test_handle_reads_read_message_close", + test_handle_reads_read_message_close); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_socket_comm_tests_valgrind.sh b/pceplib/test/pcep_socket_comm_tests_valgrind.sh new file mode 100755 index 0000000..d9e95e4 --- /dev/null +++ b/pceplib/test/pcep_socket_comm_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_socket_comm_tests diff --git a/pceplib/test/pcep_tests_valgrind.sh b/pceplib/test/pcep_tests_valgrind.sh new file mode 100755 index 0000000..ca4772c --- /dev/null +++ b/pceplib/test/pcep_tests_valgrind.sh @@ -0,0 +1,15 @@ +# +# Common function definition for PCEPlib valgrind tests +# + +function valgrind_test() +{ + local test_suite=$1 + [[ -z ${test_suite} ]] && { echo "${FUNCNAME}(): test_suite not specified."; exit 1; } + [[ ! -x "${test_suite}" ]] && { echo "${test_suite} is not an executable file."; exit 1; } + + G_SLICE=always-malloc + G_DEBUG=gc-friendly + VALGRIND="valgrind -v --tool=memcheck --leak-check=full --num-callers=40 --error-exitcode=1" + ${VALGRIND} --log-file=${test_suite}.val.log ./${test_suite} || ({ echo "Valgrind memory check error"; exit 1; }) +} diff --git a/pceplib/test/pcep_timers_event_loop_test.c b/pceplib/test/pcep_timers_event_loop_test.c new file mode 100644 index 0000000..6ab2bf7 --- /dev/null +++ b/pceplib/test/pcep_timers_event_loop_test.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include + +#include "pcep_timers.h" +#include "pcep_utils_memory.h" +#include "pcep_timers_event_loop.h" +#include "pcep_timers_event_loop_test.h" + + +typedef struct timer_expire_handler_info_ { + bool handler_called; + void *data; + int timerId; + +} timer_expire_handler_info; + +static pcep_timers_context *test_timers_context = NULL; +static timer_expire_handler_info expire_handler_info; +#define TEST_EVENT_LOOP_TIMER_ID 500 + + +/* Called when a timer expires */ +static void test_timer_expire_handler(void *data, int timerId) +{ + expire_handler_info.handler_called = true; + expire_handler_info.data = data; + expire_handler_info.timerId = timerId; +} + + +/* Test case setup called before each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_event_loop_test_setup() +{ + test_timers_context = + pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timers_context)); + memset(test_timers_context, 0, sizeof(pcep_timers_context)); + if (pthread_mutex_init(&(test_timers_context->timer_list_lock), NULL) + != 0) { + fprintf(stderr, + "ERROR initializing timers, cannot initialize the mutex\n"); + } + test_timers_context->active = false; + test_timers_context->expire_handler = test_timer_expire_handler; + test_timers_context->timer_list = + ordered_list_initialize(timer_list_node_timer_id_compare); + + expire_handler_info.handler_called = false; + expire_handler_info.data = NULL; + expire_handler_info.timerId = -1; +} + + +/* Test case teardown called after each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_event_loop_test_teardown() +{ + pthread_mutex_unlock(&test_timers_context->timer_list_lock); + pthread_mutex_destroy(&(test_timers_context->timer_list_lock)); + ordered_list_destroy(test_timers_context->timer_list); + pceplib_free(PCEPLIB_INFRA, test_timers_context); + test_timers_context = NULL; +} + + +/* + * Test functions + */ + +void test_walk_and_process_timers_no_timers() +{ + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); + + walk_and_process_timers(test_timers_context); + + CU_ASSERT_FALSE(expire_handler_info.handler_called); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); +} + + +void test_walk_and_process_timers_timer_not_expired() +{ + pcep_timer timer; + timer.data = &timer; + // Set the timer to expire 100 seconds from now + timer.expire_time = time(NULL) + 100; + timer.timer_id = TEST_EVENT_LOOP_TIMER_ID; + ordered_list_add_node(test_timers_context->timer_list, &timer); + + walk_and_process_timers(test_timers_context); + + /* The timer should still be in the list, since it hasnt expired yet */ + CU_ASSERT_FALSE(expire_handler_info.handler_called); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 1); + CU_ASSERT_PTR_NOT_NULL(test_timers_context->timer_list->head); +} + + +void test_walk_and_process_timers_timer_expired() +{ + /* We need to alloc it, since it will be free'd in + * walk_and_process_timers */ + pcep_timer *timer = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timer)); + timer->data = timer; + // Set the timer to expire 10 seconds ago + timer->expire_time = time(NULL) - 10; + pthread_mutex_lock(&test_timers_context->timer_list_lock); + timer->timer_id = TEST_EVENT_LOOP_TIMER_ID; + pthread_mutex_unlock(&test_timers_context->timer_list_lock); + ordered_list_add_node(test_timers_context->timer_list, timer); + + walk_and_process_timers(test_timers_context); + + /* Since the timer expired, the expire_handler should have been called + * and the timer should have been removed from the timer list */ + CU_ASSERT_TRUE(expire_handler_info.handler_called); + CU_ASSERT_PTR_EQUAL(expire_handler_info.data, timer); + CU_ASSERT_EQUAL(expire_handler_info.timerId, TEST_EVENT_LOOP_TIMER_ID); + CU_ASSERT_EQUAL(test_timers_context->timer_list->num_entries, 0); + CU_ASSERT_PTR_NULL(test_timers_context->timer_list->head); +} + +void test_event_loop_null_handle() +{ + /* Verify that event_loop() correctly handles a NULL timers_context */ + event_loop(NULL); +} + + +void test_event_loop_not_active() +{ + /* Verify that event_loop() correctly handles an inactive timers_context + * flag */ + test_timers_context->active = false; + event_loop(test_timers_context); +} diff --git a/pceplib/test/pcep_timers_event_loop_test.h b/pceplib/test/pcep_timers_event_loop_test.h new file mode 100644 index 0000000..cc49908 --- /dev/null +++ b/pceplib/test/pcep_timers_event_loop_test.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_EVENT_LOOP_TEST_H_ +#define PCEP_TIMERS_EVENT_LOOP_TEST_H_ + +void pcep_timers_event_loop_test_setup(void); +void pcep_timers_event_loop_test_teardown(void); +void test_walk_and_process_timers_no_timers(void); +void test_walk_and_process_timers_timer_not_expired(void); +void test_walk_and_process_timers_timer_expired(void); +void test_event_loop_null_handle(void); +void test_event_loop_not_active(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_timers_test.c b/pceplib/test/pcep_timers_test.c new file mode 100644 index 0000000..c782d79 --- /dev/null +++ b/pceplib/test/pcep_timers_test.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_timers.h" +#include "pcep_timers_test.h" + +/* Test case teardown called after each test. + * Declared in pcep_timers_tests.c */ +void pcep_timers_test_teardown() +{ + teardown_timers(); +} + +static void test_timer_expire_handler(void *data, int timerId) +{ + (void)data; + (void)timerId; +} + + +void test_double_initialization(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), false); +} + + +void test_initialization_null_callback(void) +{ + CU_ASSERT_EQUAL(initialize_timers(NULL), false); +} + + +void test_not_initialized(void) +{ + /* All of these should fail if initialize_timers() hasnt been called */ + CU_ASSERT_EQUAL(create_timer(5, NULL), -1); + CU_ASSERT_EQUAL(cancel_timer(7), false); + CU_ASSERT_EQUAL(reset_timer(7), false); + CU_ASSERT_EQUAL(teardown_timers(), false); +} + + +void test_create_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(0, NULL); + CU_ASSERT_TRUE(timer_id > -1); +} + + +void test_cancel_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(10, NULL); + CU_ASSERT_TRUE(timer_id > -1); + + CU_ASSERT_EQUAL(cancel_timer(timer_id), true); +} + + +void test_cancel_timer_invalid(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(cancel_timer(1), false); +} + + +void test_reset_timer(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + + int timer_id = create_timer(10, NULL); + CU_ASSERT_TRUE(timer_id > -1); + + CU_ASSERT_EQUAL(reset_timer(timer_id), true); +} + + +void test_reset_timer_invalid(void) +{ + CU_ASSERT_EQUAL(initialize_timers(test_timer_expire_handler), true); + CU_ASSERT_EQUAL(reset_timer(1), false); +} diff --git a/pceplib/test/pcep_timers_test.h b/pceplib/test/pcep_timers_test.h new file mode 100644 index 0000000..b37bb6b --- /dev/null +++ b/pceplib/test/pcep_timers_test.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_TIMERS_TEST_H_ +#define PCEP_TIMERS_TEST_H_ + +void pcep_timers_test_teardown(void); +void test_double_initialization(void); +void test_initialization_null_callback(void); +void test_not_initialized(void); +void test_create_timer(void); +void test_cancel_timer(void); +void test_cancel_timer_invalid(void); +void test_reset_timer(void); +void test_reset_timer_invalid(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_timers_tests.c b/pceplib/test/pcep_timers_tests.c new file mode 100644 index 0000000..7043bd4 --- /dev/null +++ b/pceplib/test/pcep_timers_tests.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "pcep_timers_test.h" +#include "pcep_timers_event_loop_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + /* + * Tests defined in pcep_timers_test.c + */ + CU_pSuite test_timers_suite = CU_add_suite_with_setup_and_teardown( + "PCEP Timers Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + NULL, pcep_timers_test_teardown); // test case setup and + // teardown function pointers + CU_add_test(test_timers_suite, "test_double_initialization", + test_double_initialization); + CU_add_test(test_timers_suite, "test_initialization_null_callback", + test_initialization_null_callback); + CU_add_test(test_timers_suite, "test_not_initialized", + test_not_initialized); + CU_add_test(test_timers_suite, "test_create_timer", test_create_timer); + CU_add_test(test_timers_suite, "test_cancel_timer", test_cancel_timer); + CU_add_test(test_timers_suite, "test_cancel_timer_invalid", + test_cancel_timer_invalid); + CU_add_test(test_timers_suite, "test_reset_timer", test_reset_timer); + CU_add_test(test_timers_suite, "test_reset_timer_invalid", + test_reset_timer_invalid); + + /* + * Tests defined in pcep_timers_event_loop_test.c + */ + CU_pSuite test_timers_event_loop_suite = + CU_add_suite_with_setup_and_teardown( + "PCEP Timers Event Loop Test Suite", NULL, + NULL, // suite setup and cleanup function pointers + pcep_timers_event_loop_test_setup, // test case setup + // function pointer + pcep_timers_event_loop_test_teardown); // test case + // teardown + // function + // pointer + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_no_timers", + test_walk_and_process_timers_no_timers); + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_timer_not_expired", + test_walk_and_process_timers_timer_not_expired); + CU_add_test(test_timers_event_loop_suite, + "test_walk_and_process_timers_timer_expired", + test_walk_and_process_timers_timer_expired); + CU_add_test(test_timers_event_loop_suite, "test_event_loop_null_handle", + test_event_loop_null_handle); + CU_add_test(test_timers_event_loop_suite, "test_event_loop_not_active", + test_event_loop_not_active); + + /* + * Run the tests and cleanup. + */ + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_timers_tests_valgrind.sh b/pceplib/test/pcep_timers_tests_valgrind.sh new file mode 100755 index 0000000..f9bff3b --- /dev/null +++ b/pceplib/test/pcep_timers_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_timers_tests diff --git a/pceplib/test/pcep_utils_counters_test.c b/pceplib/test/pcep_utils_counters_test.c new file mode 100644 index 0000000..7fa2f3c --- /dev/null +++ b/pceplib/test/pcep_utils_counters_test.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_utils_counters.h" +#include "pcep_utils_counters_test.h" + + +void test_create_counters_group(void) +{ + const char group_name[] = "group"; + uint16_t num_subgroups = 10; + + struct counters_group *group = + create_counters_group(NULL, num_subgroups); + CU_ASSERT_PTR_NULL(group); + + group = create_counters_group(group_name, MAX_COUNTER_GROUPS + 1); + CU_ASSERT_PTR_NULL(group); + + group = create_counters_group(group_name, num_subgroups); + CU_ASSERT_PTR_NOT_NULL(group); + assert(group != NULL); + + CU_ASSERT_EQUAL(group->num_subgroups, 0); + CU_ASSERT_EQUAL(group->max_subgroups, num_subgroups); + CU_ASSERT_EQUAL(strcmp(group->counters_group_name, group_name), 0); + + delete_counters_group(group); +} + +void test_create_counters_subgroup(void) +{ + const char subgroup_name[] = "subgroup"; + uint16_t subgroup_id = 10; + uint16_t num_counters = 20; + + struct counters_subgroup *subgroup = + create_counters_subgroup(NULL, subgroup_id, num_counters); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup( + subgroup_name, MAX_COUNTER_GROUPS + 1, num_counters); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup(subgroup_name, subgroup_id, + MAX_COUNTERS + 1); + CU_ASSERT_PTR_NULL(subgroup); + + subgroup = create_counters_subgroup(subgroup_name, subgroup_id, + num_counters); + CU_ASSERT_PTR_NOT_NULL(subgroup); + assert(subgroup != NULL); + + CU_ASSERT_EQUAL(subgroup->subgroup_id, subgroup_id); + CU_ASSERT_EQUAL(subgroup->num_counters, 0); + CU_ASSERT_EQUAL(subgroup->max_counters, num_counters); + CU_ASSERT_EQUAL(strcmp(subgroup->counters_subgroup_name, subgroup_name), + 0); + + delete_counters_subgroup(subgroup); +} + +void test_add_counters_subgroup(void) +{ + struct counters_group *group = create_counters_group("group", 1); + struct counters_subgroup *subgroup1 = + create_counters_subgroup("subgroup", 0, 5); + struct counters_subgroup *subgroup2 = + create_counters_subgroup("subgroup", 1, 5); + + CU_ASSERT_FALSE(add_counters_subgroup(NULL, NULL)); + CU_ASSERT_FALSE(add_counters_subgroup(NULL, subgroup1)); + CU_ASSERT_FALSE(add_counters_subgroup(group, NULL)); + + CU_ASSERT_EQUAL(group->num_subgroups, 0); + CU_ASSERT_TRUE(add_counters_subgroup(group, subgroup1)); + CU_ASSERT_EQUAL(group->num_subgroups, 1); + /* Cant add more than num_subgroups to the group */ + CU_ASSERT_FALSE(add_counters_subgroup(group, subgroup2)); + + CU_ASSERT_PTR_NOT_NULL(find_subgroup(group, 0)); + CU_ASSERT_PTR_NULL(find_subgroup(group, 1)); + + delete_counters_group(group); + delete_counters_subgroup(subgroup2); +} + +void test_create_subgroup_counter(void) +{ + uint16_t counter_id = 1; + char counter_name[] = "my counter"; + char counter_name_json[] = "myCounter"; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 2); + + CU_ASSERT_FALSE(create_subgroup_counter(NULL, counter_id, counter_name, + counter_name_json)); + CU_ASSERT_FALSE(create_subgroup_counter(subgroup, counter_id + 1, + counter_name, counter_name_json)); + CU_ASSERT_FALSE( + create_subgroup_counter(subgroup, counter_id, NULL, NULL)); + CU_ASSERT_EQUAL(subgroup->num_counters, 0); + CU_ASSERT_TRUE(create_subgroup_counter(subgroup, counter_id, + counter_name, counter_name_json)); + CU_ASSERT_EQUAL(subgroup->num_counters, 1); + + delete_counters_subgroup(subgroup); +} + +void test_delete_counters_group(void) +{ + struct counters_group *group = create_counters_group("group", 1); + + CU_ASSERT_FALSE(delete_counters_group(NULL)); + CU_ASSERT_TRUE(delete_counters_group(group)); +} + +void test_delete_counters_subgroup(void) +{ + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 1); + + CU_ASSERT_FALSE(delete_counters_subgroup(NULL)); + CU_ASSERT_TRUE(delete_counters_subgroup(subgroup)); +} + +void test_reset_group_counters(void) +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + add_counters_subgroup(group, subgroup); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(reset_group_counters(NULL)); + CU_ASSERT_TRUE(reset_group_counters(group)); + CU_ASSERT_EQUAL(counter->counter_value, 0); + + delete_counters_group(group); +} + +void test_reset_subgroup_counters(void) +{ + uint16_t counter_id = 1; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(reset_subgroup_counters(NULL)); + CU_ASSERT_TRUE(reset_subgroup_counters(subgroup)); + CU_ASSERT_EQUAL(counter->counter_value, 0); + + delete_counters_subgroup(subgroup); +} + +void test_increment_counter(void) +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + add_counters_subgroup(group, subgroup); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = 100; + + CU_ASSERT_FALSE(increment_counter(NULL, subgroup_id, counter_id)); + CU_ASSERT_FALSE(increment_counter(group, 100, counter_id)); + CU_ASSERT_FALSE(increment_counter(group, subgroup_id, 123)); + CU_ASSERT_TRUE(increment_counter(group, subgroup_id, counter_id)); + CU_ASSERT_EQUAL(counter->counter_value, 101); + CU_ASSERT_EQUAL(subgroup_counters_total(subgroup), 101); + + delete_counters_group(group); +} + +void test_increment_subgroup_counter(void) +{ + int counter_id = 1; + uint32_t counter_value = 100; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", 1, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + + struct counter *counter = subgroup->counters[counter_id]; + counter->counter_value = counter_value; + + CU_ASSERT_FALSE(increment_subgroup_counter(NULL, counter_id)); + CU_ASSERT_FALSE(increment_subgroup_counter(subgroup, counter_id + 1)); + CU_ASSERT_TRUE(increment_subgroup_counter(subgroup, counter_id)); + CU_ASSERT_EQUAL(counter->counter_value, counter_value + 1); + + delete_counters_subgroup(subgroup); +} + +void test_dump_counters_group_to_log(void) +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_group *group = create_counters_group("group", 10); + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + add_counters_subgroup(group, subgroup); + + CU_ASSERT_FALSE(dump_counters_group_to_log(NULL)); + CU_ASSERT_TRUE(dump_counters_group_to_log(group)); + + delete_counters_group(group); +} + +void test_dump_counters_subgroup_to_log(void) +{ + uint16_t subgroup_id = 1; + uint16_t counter_id = 1; + struct counters_subgroup *subgroup = + create_counters_subgroup("subgroup", subgroup_id, 10); + create_subgroup_counter(subgroup, counter_id, "counter", "counter"); + + CU_ASSERT_FALSE(dump_counters_subgroup_to_log(NULL)); + CU_ASSERT_TRUE(dump_counters_subgroup_to_log(subgroup)); + + delete_counters_subgroup(subgroup); +} diff --git a/pceplib/test/pcep_utils_counters_test.h b/pceplib/test/pcep_utils_counters_test.h new file mode 100644 index 0000000..2d4c801 --- /dev/null +++ b/pceplib/test/pcep_utils_counters_test.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_COUNTERS_TEST_H_ +#define PCEP_UTILS_COUNTERS_TEST_H_ + +void test_create_counters_group(void); +void test_create_counters_subgroup(void); +void test_add_counters_subgroup(void); +void test_create_subgroup_counter(void); +void test_delete_counters_group(void); +void test_delete_counters_subgroup(void); +void test_reset_group_counters(void); +void test_reset_subgroup_counters(void); +void test_increment_counter(void); +void test_increment_subgroup_counter(void); +void test_dump_counters_group_to_log(void); +void test_dump_counters_subgroup_to_log(void); + +#endif diff --git a/pceplib/test/pcep_utils_double_linked_list_test.c b/pceplib/test/pcep_utils_double_linked_list_test.c new file mode 100644 index 0000000..378c949 --- /dev/null +++ b/pceplib/test/pcep_utils_double_linked_list_test.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_double_linked_list.h" +#include "pcep_utils_double_linked_list_test.h" + +typedef struct dll_node_data_ { + int int_data; + +} dll_node_data; + +void test_empty_dl_list() +{ + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NULL(dll_delete_first_node(handle)); + CU_ASSERT_PTR_NULL(dll_delete_last_node(handle)); + CU_ASSERT_PTR_NULL(dll_delete_node(handle, NULL)); + + dll_destroy(handle); +} + +void test_null_dl_list_handle() +{ + dll_destroy(NULL); + CU_ASSERT_PTR_NULL(dll_prepend(NULL, NULL)); + CU_ASSERT_PTR_NULL(dll_append(NULL, NULL)); + CU_ASSERT_PTR_NULL(dll_delete_first_node(NULL)); + CU_ASSERT_PTR_NULL(dll_delete_last_node(NULL)); + CU_ASSERT_PTR_NULL(dll_delete_node(NULL, NULL)); +} + +void test_dll_prepend_data() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data3)); + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data2)); + CU_ASSERT_PTR_NOT_NULL(dll_prepend(handle, &data1)); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + double_linked_list_node *node = handle->head; + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data1); + CU_ASSERT_PTR_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data2); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data3); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NULL(node->next_node); + CU_ASSERT_PTR_EQUAL(handle->tail, node); + + dll_destroy(handle); +} + + +void test_dll_append_data() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + double_linked_list *handle = dll_initialize(); + + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data3)); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + double_linked_list_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + CU_ASSERT_PTR_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data2); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NOT_NULL(node->next_node); + + node = node->next_node; + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data3); + CU_ASSERT_PTR_NOT_NULL(node->prev_node); + CU_ASSERT_PTR_NULL(node->next_node); + CU_ASSERT_PTR_EQUAL(handle->tail, node); + + dll_destroy(handle); +} + + +void test_dll_delete_first_node() +{ + dll_node_data data1, data2; + data1.int_data = 1; + data2.int_data = 2; + + double_linked_list *handle = dll_initialize(); + + /* Test deleting with just 1 node in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* Test deleting with 2 nodes in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + deleted_data = dll_delete_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data2); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + + dll_destroy(handle); +} + + +void test_dll_delete_last_node() +{ + dll_node_data data1, data2; + data1.int_data = 1; + data2.int_data = 2; + + double_linked_list *handle = dll_initialize(); + + /* Test deleting with just 1 node in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_last_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* Test deleting with 2 nodes in the list */ + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data1)); + CU_ASSERT_PTR_NOT_NULL(dll_append(handle, &data2)); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + deleted_data = dll_delete_last_node(handle); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data1); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + + dll_destroy(handle); +} + + +void test_dll_delete_node() +{ + dll_node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + double_linked_list_node *node1, *node2, *node3; + double_linked_list *handle; + + /* Test deleting with just 1 node in the list */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + void *deleted_data = dll_delete_node(handle, node1); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NULL(handle->tail); + + /* + * Test deleting the head with 2 nodes in the list + */ + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_PTR_NOT_NULL(node2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + /* Delete the head entry */ + deleted_data = dll_delete_node(handle, node1); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data1, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data2); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + dll_destroy(handle); + + /* + * Test deleting the tail with 2 nodes in the list + */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node1); + CU_ASSERT_PTR_NOT_NULL(node2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + /* Delete the tail entry */ + deleted_data = dll_delete_node(handle, node2); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 1); + CU_ASSERT_PTR_EQUAL(handle->head->data, &data1); + CU_ASSERT_PTR_EQUAL(handle->head, handle->tail); + CU_ASSERT_PTR_NULL(handle->head->prev_node); + CU_ASSERT_PTR_NULL(handle->head->next_node); + dll_destroy(handle); + + /* + * Test deleting in the middle with 3 nodes in the list + */ + handle = dll_initialize(); + node1 = dll_append(handle, &data1); + node2 = dll_append(handle, &data2); + node3 = dll_append(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node1); + assert(node1 != NULL); + CU_ASSERT_PTR_NOT_NULL(node2); + assert(node2 != NULL); + CU_ASSERT_PTR_NOT_NULL(node3); + assert(node3 != NULL); + CU_ASSERT_EQUAL(handle->num_entries, 3); + + /* Delete the middle entry */ + deleted_data = dll_delete_node(handle, node2); + CU_ASSERT_PTR_NOT_NULL(deleted_data); + CU_ASSERT_PTR_EQUAL(&data2, deleted_data); + + CU_ASSERT_EQUAL(handle->num_entries, 2); + CU_ASSERT_PTR_EQUAL(handle->head, node1); + CU_ASSERT_PTR_EQUAL(handle->tail, node3); + CU_ASSERT_PTR_EQUAL(node1->data, &data1); + CU_ASSERT_PTR_EQUAL(node3->data, &data3); + CU_ASSERT_PTR_EQUAL(node1->next_node, node3); + CU_ASSERT_PTR_EQUAL(node3->prev_node, node1); + CU_ASSERT_PTR_NULL(node1->prev_node); + CU_ASSERT_PTR_NULL(node3->next_node); + + dll_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_double_linked_list_test.h b/pceplib/test/pcep_utils_double_linked_list_test.h new file mode 100644 index 0000000..f04d456 --- /dev/null +++ b/pceplib/test/pcep_utils_double_linked_list_test.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_DOUBLE_LINKED_LIST_TEST_H_ +#define PCEP_UTILS_DOUBLE_LINKED_LIST_TEST_H_ + +void test_empty_dl_list(void); +void test_null_dl_list_handle(void); +void test_dll_prepend_data(void); +void test_dll_append_data(void); +void test_dll_delete_first_node(void); +void test_dll_delete_last_node(void); +void test_dll_delete_node(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_memory_test.c b/pceplib/test/pcep_utils_memory_test.c new file mode 100644 index 0000000..a7d2521 --- /dev/null +++ b/pceplib/test/pcep_utils_memory_test.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include + +#include "pcep_utils_memory.h" +#include "pcep_utils_memory_test.h" + +void *test_pceplib_malloc(void *mem_type, size_t size); +void *test_pceplib_calloc(void *mem_type, size_t size); +void *test_pceplib_realloc(void *mem_type, void *ptr, size_t size); +void *test_pceplib_strdup(void *mem_type, const char *str); +void test_pceplib_free(void *mem_type, void *ptr); +void verify_memory_type(struct pceplib_memory_type *mt, uint32_t num_alloc, + uint32_t alloc_bytes, uint32_t num_free, + uint32_t free_bytes); +void verify_ext_memory_type(void *mt, int num_malloc_calls, + int num_calloc_calls, int num_realloc_calls, + int num_strdup_calls, int num_free_calls); + +struct test_memory_type { + int num_malloc_calls; + int num_calloc_calls; + int num_realloc_calls; + int num_strdup_calls; + int num_free_calls; +}; + +void *test_pceplib_malloc(void *mem_type, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_malloc_calls++; + return malloc(size); +} + +void *test_pceplib_calloc(void *mem_type, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_calloc_calls++; + return calloc(1, size); +} + +void *test_pceplib_realloc(void *mem_type, void *ptr, size_t size) +{ + ((struct test_memory_type *)mem_type)->num_realloc_calls++; + return realloc(ptr, size); +} + +void *test_pceplib_strdup(void *mem_type, const char *str) +{ + ((struct test_memory_type *)mem_type)->num_strdup_calls++; + return strdup(str); +} + +void test_pceplib_free(void *mem_type, void *ptr) +{ + ((struct test_memory_type *)mem_type)->num_free_calls++; + free(ptr); +} + +void verify_memory_type(struct pceplib_memory_type *mt, uint32_t num_alloc, + uint32_t alloc_bytes, uint32_t num_free, + uint32_t free_bytes) +{ + CU_ASSERT_EQUAL(num_alloc, mt->num_allocates); + CU_ASSERT_EQUAL(alloc_bytes, mt->total_bytes_allocated); + CU_ASSERT_EQUAL(num_free, mt->num_frees); + CU_ASSERT_EQUAL(free_bytes, mt->total_bytes_freed); +} + +void verify_ext_memory_type(void *mt, int num_malloc_calls, + int num_calloc_calls, int num_realloc_calls, + int num_strdup_calls, int num_free_calls) +{ + struct test_memory_type *mt_ptr = (struct test_memory_type *)mt; + CU_ASSERT_EQUAL(num_malloc_calls, mt_ptr->num_malloc_calls); + CU_ASSERT_EQUAL(num_calloc_calls, mt_ptr->num_calloc_calls); + CU_ASSERT_EQUAL(num_realloc_calls, mt_ptr->num_realloc_calls); + CU_ASSERT_EQUAL(num_strdup_calls, mt_ptr->num_strdup_calls); + CU_ASSERT_EQUAL(num_free_calls, mt_ptr->num_free_calls); +} + +void test_memory_internal_impl() +{ + int alloc_size = 100; + struct pceplib_memory_type *pceplib_infra_ptr = + (struct pceplib_memory_type *)PCEPLIB_INFRA; + struct pceplib_memory_type *pceplib_messages_ptr = + (struct pceplib_memory_type *)PCEPLIB_MESSAGES; + int alloc_counter = 1; + int free_counter = 1; + + /* reset the memory type counters for easier testing */ + pceplib_infra_ptr->num_allocates = + pceplib_infra_ptr->total_bytes_allocated = + pceplib_infra_ptr->num_frees = + pceplib_infra_ptr->total_bytes_freed = 0; + pceplib_messages_ptr->num_allocates = + pceplib_messages_ptr->total_bytes_allocated = + pceplib_messages_ptr->num_frees = + pceplib_messages_ptr->total_bytes_freed = 0; + + /* Make sure nothing crashes when all these are set NULL, since the + * internal default values should still be used. */ + pceplib_memory_initialize(NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + /* Test malloc() */ + void *ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + verify_memory_type(pceplib_infra_ptr, alloc_counter, alloc_size, + free_counter++, 0); + + /* Test calloc() */ + ptr = pceplib_calloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + alloc_counter++; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + alloc_size * alloc_counter, free_counter++, 0); + + /* Test realloc() */ + ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + ptr = pceplib_realloc(PCEPLIB_INFRA, ptr, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + alloc_counter += 2; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + alloc_size * alloc_counter, free_counter++, 0); + + /* Test strdup() */ + ptr = pceplib_malloc(PCEPLIB_INFRA, alloc_size); + /* Make strdup duplicate (alloc_size - 1) bytes */ + memset(ptr, 'a', alloc_size); + ((char *)ptr)[alloc_size - 1] = '\0'; + char *str = pceplib_strdup(PCEPLIB_INFRA, (char *)ptr); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_INFRA, ptr); + pceplib_free(PCEPLIB_INFRA, str); + alloc_counter += 2; + free_counter++; + verify_memory_type(pceplib_infra_ptr, alloc_counter, + (alloc_size * alloc_counter) - 1, free_counter, 0); + + /* Make sure only the pceplib_infra_ptr memory counters are incremented + */ + verify_memory_type(pceplib_messages_ptr, 0, 0, 0, 0); +} + +void test_memory_external_impl() +{ + int alloc_size = 100; + struct pceplib_memory_type *pceplib_infra_ptr = + (struct pceplib_memory_type *)PCEPLIB_INFRA; + struct pceplib_memory_type *pceplib_messages_ptr = + (struct pceplib_memory_type *)PCEPLIB_MESSAGES; + + /* reset the internal memory type counters to later verify they are NOT + * incremented since an external impl was provided */ + pceplib_infra_ptr->num_allocates = + pceplib_infra_ptr->total_bytes_allocated = + pceplib_infra_ptr->num_frees = + pceplib_infra_ptr->total_bytes_freed = 0; + pceplib_messages_ptr->num_allocates = + pceplib_messages_ptr->total_bytes_allocated = + pceplib_messages_ptr->num_frees = + pceplib_messages_ptr->total_bytes_freed = 0; + + /* Setup the external memory type */ + struct test_memory_type infra_mt, messages_mt; + void *infra_ptr = &infra_mt; + void *messages_ptr = &messages_mt; + memset(infra_ptr, 0, sizeof(struct test_memory_type)); + memset(messages_ptr, 0, sizeof(struct test_memory_type)); + int free_counter = 1; + + /* Initialize the PCEPlib memory system with an external implementation + */ + pceplib_memory_initialize(infra_ptr, messages_ptr, test_pceplib_malloc, + test_pceplib_calloc, test_pceplib_realloc, + test_pceplib_strdup, test_pceplib_free); + + /* Test malloc() */ + void *ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 1, 0, 0, 0, free_counter++); + + /* Test calloc() */ + ptr = pceplib_calloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 1, 1, 0, 0, free_counter++); + + /* Test realloc() */ + ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + ptr = pceplib_realloc(PCEPLIB_MESSAGES, ptr, alloc_size); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + verify_ext_memory_type(messages_ptr, 2, 1, 1, 0, free_counter++); + + /* Test strdup() */ + ptr = pceplib_malloc(PCEPLIB_MESSAGES, alloc_size); + /* Make strdup duplicate (alloc_size - 1) bytes */ + memset(ptr, 'a', alloc_size); + ((char *)ptr)[alloc_size - 1] = '\0'; + char *str = pceplib_strdup(PCEPLIB_MESSAGES, (char *)ptr); + CU_ASSERT_PTR_NOT_NULL(ptr); + pceplib_free(PCEPLIB_MESSAGES, ptr); + pceplib_free(PCEPLIB_MESSAGES, str); + verify_ext_memory_type(messages_ptr, 3, 1, 1, 1, free_counter + 1); + + /* Make sure the internal memory counters are NOT incremented */ + verify_memory_type(pceplib_infra_ptr, 0, 0, 0, 0); + verify_memory_type(pceplib_messages_ptr, 0, 0, 0, 0); + + verify_ext_memory_type(infra_ptr, 0, 0, 0, 0, 0); +} diff --git a/pceplib/test/pcep_utils_memory_test.h b/pceplib/test/pcep_utils_memory_test.h new file mode 100644 index 0000000..8516548 --- /dev/null +++ b/pceplib/test/pcep_utils_memory_test.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_MEMORY_TEST_H_ +#define PCEP_MEMORY_TEST_H_ + +void test_memory_internal_impl(void); +void test_memory_external_impl(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_ordered_list_test.c b/pceplib/test/pcep_utils_ordered_list_test.c new file mode 100644 index 0000000..8b236cc --- /dev/null +++ b/pceplib/test/pcep_utils_ordered_list_test.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_ordered_list.h" +#include "pcep_utils_ordered_list_test.h" + +typedef struct node_data_ { + int int_data; + +} node_data; + + +int node_data_compare(void *list_entry, void *new_entry) +{ + /* + * < 0 if new_entry < list_entry + * == 0 if new_entry == list_entry (new_entry will be inserted after + * list_entry) > 0 if new_entry > list_entry + */ + + return ((node_data *)new_entry)->int_data + - ((node_data *)list_entry)->int_data; +} + + +void test_empty_list() +{ + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + CU_ASSERT_PTR_NOT_NULL(handle); + assert(handle != NULL); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_PTR_NOT_NULL(handle->compare_function); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + ordered_list_destroy(handle); +} + + +void test_null_list_handle() +{ + node_data data; + ordered_list_node node_data; + + void *ptr = ordered_list_add_node(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_find(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_first_node(NULL); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_first_node_equals(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = ordered_list_remove_node(NULL, &node_data, &node_data); + CU_ASSERT_PTR_NULL(ptr); +} + + +void test_add_to_list() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data3); + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + ordered_list_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node, NULL); + + ordered_list_destroy(handle); +} + + +void test_find() +{ + node_data data1, data2, data3, data_not_inList; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + data_not_inList.int_data = 5; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data3); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data1); + + ordered_list_node *node = ordered_list_find(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = ordered_list_find(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = ordered_list_find(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node); + assert(node != NULL); + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = ordered_list_find(handle, &data_not_inList); + CU_ASSERT_PTR_NULL(node); + + ordered_list_destroy(handle); +} + + +void test_remove_first_node() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 0); + CU_ASSERT_PTR_NULL(handle->head); + + node_data = ordered_list_remove_first_node(handle); + CU_ASSERT_PTR_NULL(node_data); + + ordered_list_destroy(handle); +} + + +void test_remove_first_node_equals() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_add_node(handle, &data1); + ordered_list_add_node(handle, &data2); + ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_first_node_equals(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_first_node_equals(handle, &data3); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = ordered_list_remove_first_node_equals(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + node_data = ordered_list_remove_first_node_equals(handle, &data1); + CU_ASSERT_PTR_NULL(node_data); + + ordered_list_destroy(handle); +} + + +void test_remove_node() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + ordered_list_handle *handle = + ordered_list_initialize(node_data_compare); + + ordered_list_node *node1 = ordered_list_add_node(handle, &data1); + ordered_list_node *node2 = ordered_list_add_node(handle, &data2); + ordered_list_node *node3 = ordered_list_add_node(handle, &data3); + + void *node_data = ordered_list_remove_node(handle, node2, node3); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = ordered_list_remove_node(handle, node1, node2); + CU_ASSERT_PTR_NOT_NULL(node_data); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + ordered_list_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_ordered_list_test.h b/pceplib/test/pcep_utils_ordered_list_test.h new file mode 100644 index 0000000..550ac50 --- /dev/null +++ b/pceplib/test/pcep_utils_ordered_list_test.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_ORDERED_LIST_TEST_H_ +#define PCEP_UTILS_ORDERED_LIST_TEST_H_ + +void test_empty_list(void); +void test_null_list_handle(void); +void test_add_to_list(void); +void test_find(void); +void test_remove_first_node(void); +void test_remove_first_node_equals(void); +void test_remove_node(void); +int node_data_compare(void *list_entry, void *new_entry); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_queue_test.c b/pceplib/test/pcep_utils_queue_test.c new file mode 100644 index 0000000..545ad49 --- /dev/null +++ b/pceplib/test/pcep_utils_queue_test.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "pcep_utils_queue.h" +#include "pcep_utils_queue_test.h" + +typedef struct node_data_ { + int int_data; + +} node_data; + + +void test_empty_queue() +{ + queue_handle *handle = queue_initialize(); + + CU_ASSERT_PTR_NOT_NULL(handle); + assert(handle != NULL); + CU_ASSERT_PTR_NULL(handle->head); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + queue_destroy(handle); +} + + +void test_null_queue_handle() +{ + /* test each method handles a NULL handle without crashing */ + node_data data; + queue_destroy(NULL); + void *ptr = queue_enqueue(NULL, &data); + CU_ASSERT_PTR_NULL(ptr); + + ptr = queue_dequeue(NULL); + CU_ASSERT_PTR_NULL(ptr); +} + + +void test_enqueue() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize(); + + queue_enqueue(handle, &data1); + queue_enqueue(handle, &data2); + queue_enqueue(handle, &data3); + + CU_ASSERT_EQUAL(handle->num_entries, 3); + + queue_node *node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data3); + + node = node->next_node; + CU_ASSERT_PTR_NULL(node); + + queue_destroy(handle); +} + + +void test_enqueue_with_limit() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize_with_size(2); + + queue_node *node = queue_enqueue(handle, &data1); + CU_ASSERT_PTR_NOT_NULL(node); + + node = queue_enqueue(handle, &data2); + CU_ASSERT_PTR_NOT_NULL(node); + + node = queue_enqueue(handle, &data3); + CU_ASSERT_PTR_NULL(node); + + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node = handle->head; + CU_ASSERT_PTR_EQUAL(node->data, &data1); + + node = node->next_node; + CU_ASSERT_PTR_EQUAL(node->data, &data2); + + node = node->next_node; + CU_ASSERT_PTR_NULL(node); + + queue_destroy(handle); +} + + +void test_dequeue() +{ + node_data data1, data2, data3; + data1.int_data = 1; + data2.int_data = 2; + data3.int_data = 3; + + queue_handle *handle = queue_initialize(); + + /* first test dequeue handles an empty queue */ + void *node_data = queue_dequeue(handle); + CU_ASSERT_PTR_NULL(node_data); + + queue_enqueue(handle, &data1); + queue_enqueue(handle, &data2); + queue_enqueue(handle, &data3); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data1); + CU_ASSERT_EQUAL(handle->num_entries, 2); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data2); + CU_ASSERT_EQUAL(handle->num_entries, 1); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_EQUAL(node_data, &data3); + CU_ASSERT_EQUAL(handle->num_entries, 0); + + node_data = queue_dequeue(handle); + CU_ASSERT_PTR_NULL(node_data); + + queue_destroy(handle); +} diff --git a/pceplib/test/pcep_utils_queue_test.h b/pceplib/test/pcep_utils_queue_test.h new file mode 100644 index 0000000..bfe3f65 --- /dev/null +++ b/pceplib/test/pcep_utils_queue_test.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Javier Garcia + * + */ + +/* + * Timer definitions to be used internally by the pcep_timers library. + */ + +#ifndef PCEP_UTILS_QUEUE_TEST_H_ +#define PCEP_UTILS_QUEUE_TEST_H_ + +void test_empty_queue(void); +void test_null_queue_handle(void); +void test_enqueue(void); +void test_enqueue_with_limit(void); +void test_dequeue(void); + +#endif /* PCEPTIMERINTERNALS_H_ */ diff --git a/pceplib/test/pcep_utils_tests.c b/pceplib/test/pcep_utils_tests.c new file mode 100644 index 0000000..343c800 --- /dev/null +++ b/pceplib/test/pcep_utils_tests.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * Author : Brady Johnson + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "pcep_utils_ordered_list_test.h" +#include "pcep_utils_queue_test.h" +#include "pcep_utils_double_linked_list_test.h" +#include "pcep_utils_counters_test.h" +#include "pcep_utils_memory_test.h" + + +int main(int argc, char **argv) +{ + /* Unused parameters cause compilation warnings */ + (void)argc; + (void)argv; + + CU_initialize_registry(); + + CU_pSuite test_queue_suite = + CU_add_suite("PCEP Utils Queue Test Suite", NULL, NULL); + CU_add_test(test_queue_suite, "test_empty_queue", test_empty_queue); + CU_add_test(test_queue_suite, "test_null_queue_handle", + test_null_queue_handle); + CU_add_test(test_queue_suite, "test_enqueue", test_enqueue); + CU_add_test(test_queue_suite, "test_enqueue_with_limit", + test_enqueue_with_limit); + CU_add_test(test_queue_suite, "test_dequeue", test_dequeue); + + CU_pSuite test_list_suite = + CU_add_suite("PCEP Utils Ordered List Test Suite", NULL, NULL); + CU_add_test(test_list_suite, "test_empty_list", test_empty_list); + CU_add_test(test_list_suite, "test_null_handle", test_null_list_handle); + CU_add_test(test_list_suite, "test_add_toList", test_add_to_list); + CU_add_test(test_list_suite, "test_find", test_find); + CU_add_test(test_list_suite, "test_remove_first_node", + test_remove_first_node); + CU_add_test(test_list_suite, "test_remove_first_node_equals", + test_remove_first_node_equals); + CU_add_test(test_list_suite, "test_remove_node", test_remove_node); + + CU_pSuite test_dl_list_suite = CU_add_suite( + "PCEP Utils Double Linked List Test Suite", NULL, NULL); + CU_add_test(test_dl_list_suite, "test_empty_dl_list", + test_empty_dl_list); + CU_add_test(test_dl_list_suite, "test_null_dl_handle", + test_null_dl_list_handle); + CU_add_test(test_dl_list_suite, "test_dll_prepend_data", + test_dll_prepend_data); + CU_add_test(test_dl_list_suite, "test_dll_append_data", + test_dll_append_data); + CU_add_test(test_dl_list_suite, "test_dll_delete_first_node", + test_dll_delete_first_node); + CU_add_test(test_dl_list_suite, "test_dll_delete_last_node", + test_dll_delete_last_node); + CU_add_test(test_dl_list_suite, "test_dll_delete_node", + test_dll_delete_node); + + CU_pSuite test_counters_suite = + CU_add_suite("PCEP Utils Counters Test Suite", NULL, NULL); + CU_add_test(test_counters_suite, "test_create_counters_group", + test_create_counters_group); + CU_add_test(test_counters_suite, "test_create_counters_subgroup", + test_create_counters_subgroup); + CU_add_test(test_counters_suite, "test_add_counters_subgroup", + test_add_counters_subgroup); + CU_add_test(test_counters_suite, "test_create_subgroup_counter", + test_create_subgroup_counter); + CU_add_test(test_counters_suite, "test_delete_counters_group", + test_delete_counters_group); + CU_add_test(test_counters_suite, "test_delete_counters_subgroup", + test_delete_counters_subgroup); + CU_add_test(test_counters_suite, "test_reset_group_counters", + test_reset_group_counters); + CU_add_test(test_counters_suite, "test_reset_subgroup_counters", + test_reset_subgroup_counters); + CU_add_test(test_counters_suite, "test_increment_counter", + test_increment_counter); + CU_add_test(test_counters_suite, "test_increment_subgroup_counter", + test_increment_subgroup_counter); + CU_add_test(test_counters_suite, "test_dump_counters_group_to_log", + test_dump_counters_group_to_log); + CU_add_test(test_counters_suite, "test_dump_counters_subgroup_to_log", + test_dump_counters_subgroup_to_log); + + CU_pSuite test_memory_suite = + CU_add_suite("PCEP Utils Memory Test Suite", NULL, NULL); + CU_add_test(test_memory_suite, "test_memory_internal_impl", + test_memory_internal_impl); + CU_add_test(test_memory_suite, "test_memory_external_impl", + test_memory_external_impl); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + CU_FailureRecord *failure_record = CU_get_failure_list(); + if (failure_record != NULL) { + printf("\nFailed tests:\n\t [Suite] [Test] [File:line-number]\n"); + do { + printf("\t [%s] [%s] [%s:%d]\n", + failure_record->pSuite->pName, + failure_record->pTest->pName, + failure_record->strFileName, + failure_record->uiLineNumber); + failure_record = failure_record->pNext; + + } while (failure_record != NULL); + } + + CU_pRunSummary run_summary = CU_get_run_summary(); + int result = run_summary->nTestsFailed; + CU_cleanup_registry(); + + return result; +} diff --git a/pceplib/test/pcep_utils_tests_valgrind.sh b/pceplib/test/pcep_utils_tests_valgrind.sh new file mode 100755 index 0000000..6348d82 --- /dev/null +++ b/pceplib/test/pcep_utils_tests_valgrind.sh @@ -0,0 +1,2 @@ +source pceplib/test/pcep_tests_valgrind.sh +valgrind_test pceplib/test/pcep_utils_tests diff --git a/pceplib/test/subdir.am b/pceplib/test/subdir.am new file mode 100644 index 0000000..88af592 --- /dev/null +++ b/pceplib/test/subdir.am @@ -0,0 +1,122 @@ +if PATHD_PCEP +if PATHD_PCEP_TEST + +# The default Automake target is check, add a test target to call check. +# Also make sure the binaries are current before running the tests. +test: pceplib/test/pcep_msg_tests pceplib/test/pcep_pcc_api_tests pceplib/test/pcep_session_logic_tests pceplib/test/pcep_socket_comm_tests pceplib/test/pcep_timers_tests pceplib/test/pcep_utils_tests + +check_SCRIPTS = pceplib/test/pcep_msg_tests pceplib/test/pcep_pcc_api_tests pceplib/test/pcep_session_logic_tests pceplib/test/pcep_socket_comm_tests pceplib/test/pcep_timers_tests pceplib/test/pcep_utils_tests +TESTS = $(check_SCRIPTS) + + +# Definitions to build the Unit Test binaries with CUnit +noinst_PROGRAMS += pceplib/test/pcep_msg_tests \ + pceplib/test/pcep_pcc_api_tests \ + pceplib/test/pcep_session_logic_tests \ + pceplib/test/pcep_socket_comm_tests \ + pceplib/test/pcep_timers_tests \ + pceplib/test/pcep_utils_tests + +noinst_HEADERS += pceplib/test/pcep_msg_messages_test.h \ + pceplib/test/pcep_msg_object_error_types_test.h \ + pceplib/test/pcep_msg_objects_test.h \ + pceplib/test/pcep_msg_tlvs_test.h \ + pceplib/test/pcep_msg_tools_test.h \ + pceplib/test/pcep_pcc_api_test.h \ + pceplib/test/pcep_session_logic_loop_test.h \ + pceplib/test/pcep_session_logic_states_test.h \ + pceplib/test/pcep_session_logic_test.h \ + pceplib/test/pcep_socket_comm_loop_test.h \ + pceplib/test/pcep_socket_comm_test.h \ + pceplib/test/pcep_timers_event_loop_test.h \ + pceplib/test/pcep_timers_test.h \ + pceplib/test/pcep_utils_counters_test.h \ + pceplib/test/pcep_utils_double_linked_list_test.h \ + pceplib/test/pcep_utils_memory_test.h \ + pceplib/test/pcep_utils_ordered_list_test.h \ + pceplib/test/pcep_utils_queue_test.h + +pceplib_test_pcep_msg_tests_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/pceplib +pceplib_test_pcep_msg_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_msg_tests_SOURCES = pceplib/test/pcep_msg_messages_test.c \ + pceplib/test/pcep_msg_messages_tests.c \ + pceplib/test/pcep_msg_object_error_types_test.c \ + pceplib/test/pcep_msg_objects_test.c \ + pceplib/test/pcep_msg_tlvs_test.c \ + pceplib/test/pcep_msg_tools_test.c + +# The pcc_api_tests and pcep_session_logic_tests use the +# socket_comm_mock, so the LDADD variable needs to be modified +pceplib_test_pcep_pcc_api_tests_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/pceplib +pceplib_test_pcep_pcc_api_tests_LDADD = $(top_builddir)/pceplib/libsocket_comm_mock.la $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_pcc_api_tests_SOURCES = pceplib/test/pcep_pcc_api_test.c pceplib/test/pcep_pcc_api_tests.c + +pceplib_test_pcep_session_logic_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_session_logic_tests_LDADD = $(top_builddir)/pceplib/libsocket_comm_mock.la $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_session_logic_tests_SOURCES = pceplib/test/pcep_session_logic_loop_test.c \ + pceplib/test/pcep_session_logic_states_test.c \ + pceplib/test/pcep_session_logic_test.c \ + pceplib/test/pcep_session_logic_tests.c + +pceplib_test_pcep_socket_comm_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_socket_comm_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_socket_comm_tests_SOURCES = pceplib/test/pcep_socket_comm_loop_test.c \ + pceplib/test/pcep_socket_comm_test.c \ + pceplib/test/pcep_socket_comm_tests.c + +pceplib_test_pcep_timers_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_timers_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_timers_tests_SOURCES = pceplib/test/pcep_timers_event_loop_test.c \ + pceplib/test/pcep_timers_test.c \ + pceplib/test/pcep_timers_tests.c + +pceplib_test_pcep_utils_tests_CFLAGS = -I$(top_srcdir)/pceplib +pceplib_test_pcep_utils_tests_LDADD = $(top_builddir)/pceplib/libpcep_pcc.la lib/libfrr.la -lcunit -lpthread +pceplib_test_pcep_utils_tests_SOURCES = pceplib/test/pcep_utils_counters_test.c \ + pceplib/test/pcep_utils_double_linked_list_test.c \ + pceplib/test/pcep_utils_memory_test.c \ + pceplib/test/pcep_utils_ordered_list_test.c \ + pceplib/test/pcep_utils_queue_test.c \ + pceplib/test/pcep_utils_tests.c + +# These test scripts will call the test binaries +# defined above in noinst_PROGRAMS with Valgrind +if HAVE_VALGRIND_PCEP + +dist_noinst_SCRIPTS = pceplib/test/pcep_pcc_api_tests_valgrind.sh \ + pceplib/test/pcep_session_logic_tests_valgrind.sh \ + pceplib/test/pcep_socket_comm_tests_valgrind.sh \ + pceplib/test/pcep_timers_tests_valgrind.sh \ + pceplib/test/pcep_utils_tests_valgrind.sh \ + pceplib/test/pcep_msg_tests_valgrind.sh \ + pceplib/test/pcep_tests_valgrind.sh + +check_SCRIPTS += pceplib/test/pcep_msg_tests_valgrind.sh \ + pceplib/test/pcep_pcc_api_tests_valgrind.sh \ + pceplib/test/pcep_session_logic_tests_valgrind.sh \ + pceplib/test/pcep_socket_comm_tests_valgrind.sh \ + pceplib/test/pcep_timers_tests_valgrind.sh \ + pceplib/test/pcep_utils_tests_valgrind.sh + +TESTS += $(check_SCRIPTS) + + + +pceplib/test/pcep_msg_tests_valgrind.sh: + chmod +x pceplib/test/pcep_msg_tests_valgrind.sh +pceplib/test/pcep_pcc_api_tests_valgrind.sh: + chmod +x pceplib/test/pcep_pcc_api_tests_valgrind.sh +pceplib/test/pcep_session_logic_tests_valgrind.sh: + chmod +x pceplib/test/pcep_session_logic_tests_valgrind.sh +pceplib/test/pcep_socket_comm_tests_valgrind.sh: + chmod +x pceplib/test/pcep_socket_comm_tests_valgrind.sh +pceplib/test/pcep_timers_tests_valgrind.sh: + chmod +x pceplib/test/pcep_timers_tests_valgrind.sh +pceplib/test/pcep_utils_tests_valgrind.sh: + chmod +x pceplib/test/pcep_utils_tests_valgrind.sh + + +endif + +endif +endif diff --git a/pimd/.gitignore b/pimd/.gitignore new file mode 100644 index 0000000..3f73471 --- /dev/null +++ b/pimd/.gitignore @@ -0,0 +1,4 @@ +/pimd +/pim6d +/mtracebis +/test_igmpv3_join diff --git a/pimd/AUTHORS b/pimd/AUTHORS new file mode 100644 index 0000000..08869ff --- /dev/null +++ b/pimd/AUTHORS @@ -0,0 +1,7 @@ +# Everton da Silva Marques +$ more ~/.gitconfig +[user] + name = Everton Marques + email = everton.marques@gmail.com + +-x- diff --git a/pimd/CAVEATS b/pimd/CAVEATS new file mode 100644 index 0000000..120708b --- /dev/null +++ b/pimd/CAVEATS @@ -0,0 +1,182 @@ +C1 IGMPv3 backward compatibility with IGMPv1 and IGMPv2 is not + implemented. See RFC 3376, 7.3. Multicast Router Behavior. That's + because only Source-Specific Multicast is currently targeted. + +C2 IGMPv3 support for forwarding any-source groups is not + implemented. Traffic for groups in mode EXCLUDE {empty} won't be + forwarded. See RFC 3376, 6.3. Source-Specific Forwarding + Rules. That's because only Source-Specific Multicast is currently + targeted. + +C3 Load Splitting of IP Multicast Traffic over ECMP is not supported. + See also: RFC 2991 + Multipath Issues in Unicast and Multicast Next-Hop Selection + http://www.rfc-editor.org/rfc/rfc2991.txt + +C4 IPSec AH authentication is not supported (RFC 4601: + 6.3. Authentication Using IPsec). + +C5 PIM support is limited to SSM mode as defined in section 4.8.2 + (PIM-SSM-Only Routers) of RFC4601. That's because only + Source-Specific Multicast is currently targeted. + +C6 PIM implementation currently does not support IPv6. PIM-SSM + requires IGMPv3 for IPv4 and MLDv2 for IPv6. MLDv2 is currently + missing. See also CAVEAT C9. + +C7 FIXED (S,G) Assert state machine (RFC 4601, section 4.6.1) is not + implemented. See also TODO T6. See also CAVEAT C10. + +C8 It is not possible to disable join suppression in order to + explicitly track the join membership of individual downstream + routers. + - IGMPv3 Explicit Membership Tracking is not supported. + When explicit tracking is enabled on a router, the router can + individually track the Internet Group Management Protocol (IGMP) + membership state of all reporting hosts. This feature allows the + router to achieve minimal leave latencies when hosts leave a + multicast group or channel. Example: + conf t + interface eth0 + ip igmp explicit-tracking + +C9 Only IPv4 Address Family (number=1) is supported in the PIM Address + Family field. + See also RFC 4601: 5.1. PIM Address Family + See also CAVEAT C6. + See also http://www.iana.org/assignments/address-family-numbers + +C10 FIXED Assert metric depends on metric_preference and + route_metric. Those parameters should be fetched from RIB + (zebra). See also pim_rpf.c, pim_rpf_update(). + +C11 SSM Mapping is not supported + + SSM Mapping Overview: + + SSM mapping introduces a means for the last hop router to discover + sources sending to groups. When SSM mapping is configured, if a + router receives an IGMPv1 or IGMPv2 membership report for a + particular group G, the router translates this report into one or + more (S, G) channel memberships for the well-known sources + associated with this group. + + When the router receives an IGMPv1 or IGMPv2 membership report for + a group G, the router uses SSM mapping to determine one or more + source IP addresses for the group G. SSM mapping then translates + the membership report as an IGMPv3 report INCLUDE (G, [S1, G], + [S2, G]...[Sn, G] and continues as if it had received an IGMPv3 + report. The router then sends out PIM joins toward (S1, G) to (Sn, + G) and continues to be joined to these groups as long as it + continues to receive the IGMPv1 or IGMPv2 membership reports and + as long as the SSM mapping for the group remains the same. SSM + mapping, thus, enables you to leverage SSM for video delivery to + legacy STBs that do not support IGMPv3 or for applications that do + not take advantage of the IGMPv3 host stack. + + SSM mapping enables the last hop router to determine the source + addresses either by a statically configured table on the router or + by consulting a DNS server. When the statically configured table + is changed, or when the DNS mapping changes, the router will leave + the current sources associated with the joined groups. + +C12 FIXED MRIB for incongruent unicast/multicast topologies is not + supported. RPF mechanism currently just looks up the information + in the unicast routing table. + + See also: + RFC5110: 2.2.3. Issue: Overlapping Unicast/Multicast Topology + + Sometimes, multicast RPF mechanisms first look up the multicast + routing table, or M-RIB ("topology database") with a longest + prefix match algorithm, and if they find any entry (including a + default route), that is used; if no match is found, the unicast + routing table is used instead. + +C13 Can't detect change of primary address before the actual change. + Possible approach is to craft old interface address into ip source + address by using raw ip socket. + + See also: + + RFC 4601: 4.3.1. Sending Hello Messages + + Before an interface goes down or changes primary IP address, a + Hello message with a zero HoldTime should be sent immediately + (with the old IP address if the IP address changed). + + See also pim_sock_delete(). + +C14 FIXED Detection of interface primary address changes may fail when + there are multiple addresses. + See also TODO T32. + +C15 Changes in interface secondary address list are not immediately + detected. + See also detect_secondary_address_change + See also TODO T31. + +C16 AMT Draft (mboned-auto-multicast) is not supported. + AMT = Automatic IP Multicast Without Explicit Tunnels + + See also: + + Draft + http://tools.ietf.org/html/draft-ietf-mboned-auto-multicast + http://tools.ietf.org/html/draft-ietf-mboned-auto-multicast-09 + + AMT gateway implementation for Linux + http://cs.utdallas.edu/amt/ + + AMT for Streaming (IPTV) on Global IP Multicast by Greg Shepherd (Cisco) + http://nznog.miniconf.org/nznog-2008-sysadmin-miniconf-greg-shepherd-iptv.pdf + +C17 SNMP / RFC 5060 (PIM MIB) is not supported. + +C18 MFC never recovers from removal of static route to source + + # route add -host 1.2.3.4 gw 192.168.56.10 + Before removal: + quagga-pimd-router# sh ip mroute + Source Group Proto Input iVifI Output oVifI TTL Uptime + 1.2.3.4 232.1.2.3 I eth1 3 eth0 2 1 00:00:36 + + # route del -host 1.2.3.4 gw 192.168.56.10 + After removal: sh ip mroute --> empty output + + # route add -host 1.2.3.4 gw 192.168.56.10 + After the route is restored: sh ip mroute --> never recovers (empty output) + + At this point, "no ip pim ssm" on the upstream interface (eth0) crashes pimd: + + 2014/02/14 16:30:14 PIM: ifmembership_set: (S,G)=(1.2.3.4,232.1.2.3) membership now is NOINFO on interface eth0 + 2014/02/14 16:30:14 PIM: pim_ifchannel_update_assert_tracking_desired: AssertTrackingDesired(1.2.3.4,232.1.2.3,eth0) changed from 1 to 0 + 2014/02/14 16:30:14 PIM: pim_zebra.c del_oif: nonexistent protocol mask 2 removed OIF eth0 (vif_index=2, min_ttl=0) from channel (S,G)=(1.2.3.4,232.1.2.3) + 2014/02/14 16:30:14 PIM: pim_ifchannel_update_could_assert: CouldAssert(1.2.3.4,232.1.2.3,eth0) changed from 1 to 0 + 2014/02/14 16:30:14 PIM: pim_ifchannel_update_my_assert_metric: my_assert_metric(1.2.3.4,232.1.2.3,eth0) changed from 0,0,0,10.0.2.15 to 1,4294967295,4294967295,0.0.0.0 + 2014/02/14 16:30:14 PIM: pim_zebra.c del_oif: nonexistent protocol mask 1 removed OIF eth0 (vif_index=2, min_ttl=0) from channel (S,G)=(1.2.3.4,232.1.2.3) + 2014/02/14 16:30:14 PIM: Assertion `!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)' failed in file pim_igmpv3.c, line 412, function igmp_source_delete + +C19 Provision to prevent group mode clash + + Beware group mode clash. A host/application issuing IGMPv2 + any-source joins for a group will disrupt SSM multicast for that + group. + + For instance, support for source-specific static igmp WILL FAIL if + there is host/application issuing IGMPv2 any-source joins for the + same group. + + The reason is the IGMPv2 any-source join forces qpimd to switch + the group mode to ASM (any-source multicast); however, qpimd is + unable to program ASM groups into the kernel; multicast won't + flow. There could be some provision to prevent such a behavior, + but currently there is none. + +C20 Multicast traceroute module is based on: + draft-ietf-idmr-traceroute-ipm-07 + It only implements, so far, weak traceroutes. The multicast routing + state of the router is not quieried but RPF path is followed along + PIM and IGMP enabled interfaces. + +-x- diff --git a/pimd/COMMANDS b/pimd/COMMANDS new file mode 100644 index 0000000..141ec62 --- /dev/null +++ b/pimd/COMMANDS @@ -0,0 +1,83 @@ +global configuration commands: + pimd: + ip multicast-routing Enable IP multicast forwarding + ip ssmpingd Enable ssmpingd operation + + zebra: + ip mroute Configure static unicast route into MRIB for multicast RPF lookup + +interface configuration commands: + pimd: + ip igmp Enable IGMP operation + ip igmp join IGMP join multicast group + ip igmp query-interval <1-1800> IGMP host query interval + ip igmp query-max-response-time <1-25> IGMP max query response (seconds) + ip igmp query-max-response-time-dsec <10-250> IGMP max query response (deciseconds) + ip pim ssm Enable PIM SSM operation + +verification commands: + pimd: + show ip igmp interface IGMP interface information + show ip igmp join IGMP static join information + show ip igmp parameters IGMP parameters information + show ip igmp groups IGMP groups information + show ip igmp groups retransmissions IGMP group retransmission + show ip igmp sources IGMP sources information + show ip igmp sources retransmissions IGMP source retransmission + show ip igmp statistics IGMP statistics information + show ip pim address PIM interface address + show ip pim assert PIM interface assert + show ip pim assert-internal PIM interface internal assert state + show ip pim assert-metric PIM interface assert metric + show ip pim assert-winner-metric PIM interface assert winner metric + show ip pim designated-router PIM interface designated router + show ip pim hello PIM interface hello information + show ip pim interface PIM interface information + show ip pim lan-prune-delay PIM neighbors LAN prune delay parameters + show ip pim local-membership PIM interface local-membership + show ip pim jp-override-interval PIM interface J/P override interval + show ip pim join PIM interface join information + show ip pim neighbor PIM neighbor information + show ip pim rpf PIM cached source rpf information + show ip pim secondary PIM neighbor addresses + show ip pim upstream PIM upstream information + show ip pim upstream-join-desired PIM upstream join-desired + show ip pim upstream-rpf PIM upstream source rpf + show ip multicast Multicast global information + show ip mroute IP multicast routing table + show ip mroute count Route and packet count data + show ip rib IP unicast routing table + show ip ssmpingd ssmpingd operation + + zebra: + show ip rpf Display RPF information for multicast source + +debug commands: + pimd: + clear ip interfaces Reset interfaces + clear ip igmp interfaces Reset IGMP interfaces + clear ip mroute Reset multicast routes + clear ip pim interfaces Reset PIM interfaces + clear ip pim oil Rescan PIM OIL (output interface list) + debug igmp IGMP protocol activity + debug mtrace Mtrace protocol activity + debug mroute PIM interaction with kernel MFC cache + debug pim PIM protocol activity + debug pim zebra ZEBRA protocol activity + debug ssmpingd ssmpingd activity + show debugging State of each debugging option + test igmp receive report Test reception of IGMPv3 report + test pim receive assert Test reception of PIM assert + test pim receive dump Test reception of PIM packet dump + test pim receive hello Test reception of PIM hello + test pim receive join Test reception of PIM join + test pim receive prune Test reception of PIM prune + test pim receive upcall Test reception of kernel upcall + +statistics commands: + pimd: + show memory pim PIM memory statistics + +vtysh: + mtrace Multicast traceroute +-x- diff --git a/pimd/DEBUG b/pimd/DEBUG new file mode 100644 index 0000000..a6ad260 --- /dev/null +++ b/pimd/DEBUG @@ -0,0 +1,84 @@ +DEBUG HINTS + + - Check the source is issuing multicast packets with TTL high enough + to reach the recipients. + + - Check the multicast packets are not being dropped due to + fragmentation problems. + + - Three easy options to test IGMPv3 joins from the receiver host: + + 1) Configure pimd on the receiver host with "ip igmp join": + + interface eth0 + ip pim ssm + ip igmp join 239.1.1.1 1.1.1.1 + + 2) Use test_igmpv3_join command-line utility (provided with qpimd): + + test_igmpv3_join eth0 239.1.1.1 1.1.1.1 + + 3) User the Stig Venaas' ssmping utility: + + ssmping -I eth0 1.1.1.1 + + To see multicast responses with ssmping, you will need run on + the host 1.1.1.1 either: + a) Stig Venaas' ssmpingd command-line daemon + OR + b) qpimd built-in ssmpingd service: + conf t + ip ssmpingd 1.1.1.1 + + - Using nepim to generate multicast stream from 1.1.1.1 to 239.1.1.1: + + Notices: + + a) The host unicast address 1.1.1.1 must be reachable from the + receiver. + + b) nepim tool requires the receiver must be started *before* the + sender. + + First: Start a receiver for that stream by running: + + nepim -q -6 -j 1.1.1.1+239.1.1.1@eth0 + (Remember of enabling both "ip pim ssm" and "ip igmp" under eth0.) + + Second: Start the sender at host 1.1.1.1. + + The following command generates a 100-kbps multicast stream for + channel 1.1.1.1,239.1.1.1 with TTL 10 and 1000-byte payload per UDP + packet (to avoid fragmentation): + + nepim -6 -M -b 1.1.1.1 -c 239.1.1.1 -T 10 -W 1000 -r 100k -a 1d + + + +SAMPLE DEBUG COMMANDS + + conf t + int eth0 + ip pim ssm + + test pim receive hello eth0 192.168.0.2 600 10 111 1000 3000 0 + test pim receive join eth0 600 192.168.0.1 192.168.0.2 239.1.1.1 1.1.1.1 + + show ip pim join + + +INTEROPERABILITY WITH CISCO + + ! Cisco IP Multicast command reference: + ! ftp://ftpeng.cisco.com/ipmulticast/Multicast-Commands + ! + ip pim ssm default ! enable SSM mode for groups 232.0.0.0/8 + ip multicast-routing + ip pim state-refresh disable + no ip pim dm-fallback + ! + interface FastEthernet0 + ip pim sparse-mode + ip igmp version 3 + +-x- diff --git a/pimd/LINUX_KERNEL_MROUTE_MFC b/pimd/LINUX_KERNEL_MROUTE_MFC new file mode 100644 index 0000000..3e48246 --- /dev/null +++ b/pimd/LINUX_KERNEL_MROUTE_MFC @@ -0,0 +1,24 @@ +# +# The Linux Kernel MFC (Multicast Forwarding Cache) +# + +# Check Linux kernel multicast interfaces: +cat /proc/net/dev_mcast + +# Check that interface eth0 is forwarding multicast: +cat /proc/sys/net/ipv4/conf/eth0/mc_forwarding + +# Check Linux kernel multicast VIFs: +cat /proc/net/ip_mr_vif +Interface BytesIn PktsIn BytesOut PktsOut Flags Local Remote + +# Check Linux kernel MFC: +# Oifs format = vifi:TTL +cat /proc/net/ip_mr_cache +Group Origin Iif Pkts Bytes Wrong Oifs + +# iproute2 can display the MFC: +ip mroute show +(2.2.2.2, 239.2.2.2) Iif: eth1 Oifs: eth0 + +# -- end-of-file -- diff --git a/pimd/Makefile b/pimd/Makefile new file mode 100644 index 0000000..87a5388 --- /dev/null +++ b/pimd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pimd/pimd +%: ALWAYS + @$(MAKE) -s -C .. pimd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pimd/README b/pimd/README new file mode 100644 index 0000000..1db0aad --- /dev/null +++ b/pimd/README @@ -0,0 +1,97 @@ +INTRODUCTION + + qpimd aims to implement a PIM (Protocol Independent Multicast) + daemon for the FRR Routing Suite. + + qpimd implements PIM-SM (Sparse Mode) of RFC 4601. + Additionally MSDP has been implemented. + + In order to deliver end-to-end multicast routing control + plane, qpimd includes the router-side of IGMPv[2|3] (RFC 3376). + +LICENSE + + qpimd - pimd for FRR + Copyright (C) 2008 Everton da Silva Marques + + qpimd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, + or (at your option) any later version. + + qpimd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with qpimd; see the file COPYING. If not, write + to the Free Software Foundation, Inc., 59 Temple Place - Suite + 330, Boston, MA 02111-1307, USA. + +HOME SITE + + qpimd lives at: + + https://github.com/frrouting/frr + +PLATFORMS + + qpimd has been tested with Debian Jessie. + +REQUIREMENTS + + qpimd requires FRR (2.0 or higher) + + +CONFIGURATION COMMANDS + + See available commands in the file pimd/COMMANDS. + +KNOWN CAVEATS + + See list of known caveats in the file pimd/CAVEATS. + +SUPPORT + + Please post comments, questions, patches, bug reports at the + support site: + + https://frrouting.org/frr + +RELATED WORK + + igmprt: An IGMPv3-router implementation + - http://www.loria.fr/~lahmadi/igmpv3-router.html + + USC pimd: PIMv2-SM daemon + - http://netweb.usc.edu/pim/pimd (URL broken in 2008-12-23) + - http://packages.debian.org/source/sid/pimd (from Debian) + + troglobit pimd: This is the original USC pimd from + http://netweb.usc.edu/pim/. In January 16, 2010 it was revived + with the intention to collect patches floating around in + Debian, Gentoo, Lintrack and other distribution repositories + and to provide a central point of collaboration. + - http://github.com/troglobit/pimd + + zpimd: zpimd is not dependent of zebra or any other routing daemon + - ftp://robur.slu.se/pub/Routing/Zebra + - http://sunsite2.icm.edu.pl/pub/unix/routing/zpimd + + mrd6: an IPv6 Multicast Router for Linux systems + - http://fivebits.net/proj/mrd6/ + + MBGP: Implementation of RFC 2858 for Quagga + - git://git.coplanar.net/~balajig/quagga + - http://www.gossamer-threads.com/lists/quagga/dev/18000 + +REFERENCES + + IANA Protocol Independent Multicast (PIM) Parameters + http://www.iana.org/assignments/pim-parameters/pim-parameters.txt + + Address Family Numbers + http://www.iana.org/assignments/address-family-numbers + + -- END -- diff --git a/pimd/TODO b/pimd/TODO new file mode 100644 index 0000000..4a14775 --- /dev/null +++ b/pimd/TODO @@ -0,0 +1,70 @@ +T1 Consider reliable pim solution (refresh reduction) + A Reliable Transport Mechanism for PIM + http://tools.ietf.org/wg/pim/draft-ietf-pim-port/ + PORT=PIM-Over-Reliable-Transport + +T2 If an interface changes one of its secondary IP addresses, a Hello + message with an updated Address_List option and a non-zero + HoldTime should be sent immediately. + See also detect_secondary_address_change + See also CAVEAT C15. + See also RFC 4601: 4.3.1. Sending Hello Messages + +T3 Lightweight MLDv2 + http://tools.ietf.org/html/draft-ietf-mboned-lightweight-igmpv3-mldv2-05 + http://www.ietf.org/internet-drafts/draft-ietf-mboned-lightweight-igmpv3-mldv2-05.txt + http://www.ietf.org/html.charters/mboned-charter.html + +T4 Static igmp join fails when loading config at boot time + + ! Wrong behavior seen at boot time: + ! + 2010/02/22 08:59:00 PIM: igmp_source_forward_start: ignoring request for + looped MFC entry (S,G)=(3.3.3.3,239.3.3.3): igmp_sock=12 oif=eth0 vif_index=2 + + ! Correct behavior seen later: + ! + 2010/02/22 09:03:16 PIM: igmp_source_forward_start: ignoring request for + looped MFC entry (S,G)=(2.2.2.2,239.2.2.2): igmp_sock=17 oif=lo vif_index=1 + + ! To see the wrong message at boot: + ! + debug igmp trace + ! + interface lo + ip igmp + ip igmp join 239.2.2.2 2.2.2.2 + ip igmp join 239.3.3.3 3.3.3.3 + ! + + ! Interfaces indexes: + Interface Address ifi Vif PktsIn PktsOut BytesIn BytesOut + eth0 200.202.112.3 2 2 0 0 0 0 + lo 127.0.0.1 1 1 0 0 0 0 + +T5 PIM Neighbor Reduction + https://datatracker.ietf.org/doc/draft-wijnands-pim-neighbor-reduction/ + + "In a transit LAN (no directly connected source or receiver), many + of the PIM procedures don't apply. (...) This proposal describes + a procedure to reduce the amount of neighbors established over a + transit LAN." + +T6 Single Stream Multicast Fast Reroute (SMFR) Method + https://datatracker.ietf.org/doc/draft-liu-pim-single-stream-multicast-frr/ + + "This document proposes an IP multicast fast convergence method + based on differentiating primary and backup PIM join." + +T7 RFC5384 - The Join Attribute Format + "This document describes a modification of the Join message that + allows a node to associate attributes with a particular tree." + +T8 PIM Multi-Topology ID (MT-ID) Join-Attribute + http://tools.ietf.org/html/draft-cai-pim-mtid-00 + Depends on T7. + + "This draft introduces a new type of PIM Join Attribute used to + encode the identity of the topology PIM uses for RPF." + +-x- diff --git a/pimd/TROUBLESHOOTING b/pimd/TROUBLESHOOTING new file mode 100644 index 0000000..7d1f52d --- /dev/null +++ b/pimd/TROUBLESHOOTING @@ -0,0 +1,33 @@ +TROUBLESHOOTING + +# Check kernel mcast cache +# On Linux: +ip mroute show + +! qpimd on last-hop router +! . attached to mcast receiver +! . runnning both PIM-SSM and IGMPv3 +! +show ip mroute (kernel mcast programming is correct?) +show ip pim upstream (we joined our upstream?) +show ip pim neighbor (upstream is neighbor?) +show ip pim interface (pim enabled on interfaces?) +show ip multicast (multicast enabled at all?) +show ip rib SRC (unicast route towards source?) + +show ip igmp sources (receiver joined on interface?) +show ip igmp interface (igmp enabled on receiver interface?) + +! qpimd on intermmediate routers +! . may be attached to mcast source +! . runnning only PIM-SSM, not IGMPv3 +! +show ip mroute (kernel mcast programming is correct?) +show ip pim upstream (we joined our upstream?) +show ip pim join (downstream joined us?) +show ip pim neighbor (downstream is neighbor?) +show ip pim interface (pim enabled on interfaces?) +show ip multicast (multicast enabled at all?) +show ip rib SRC (unicast route towards source?) + +--EOF-- diff --git a/pimd/mtracebis.c b/pimd/mtracebis.c new file mode 100644 index 0000000..9c3f1a7 --- /dev/null +++ b/pimd/mtracebis.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multicast Traceroute for FRRouting + * Copyright (C) 2018 Mladen Sablic + */ + +#include + +#ifdef __linux__ + +#include "pim_igmp_mtrace.h" + +#include "checksum.h" +#include "prefix.h" +#include "mtracebis_routeget.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MTRACEBIS_VERSION "0.1" +#define MTRACE_TIMEOUT (5) + +#define IP_HDR_LEN (sizeof(struct ip)) +#define IP_RA_LEN (4) +#define MTRACE_BUF_LEN (MTRACE_HDR_SIZE + (MTRACE_MAX_HOPS * MTRACE_RSP_SIZE)) +#define IP_AND_MTRACE_BUF_LEN (IP_HDR_LEN + IP_RA_LEN + MTRACE_BUF_LEN) + +static const char *progname; +static void usage(void) +{ + fprintf(stderr, "Usage : %s []\n", + progname); +} +static void version(void) +{ + fprintf(stderr, "%s %s\n", progname, MTRACEBIS_VERSION); +} + +static void print_host(struct in_addr addr) +{ + struct hostent *h; + char buf[PREFIX_STRLEN]; + + h = gethostbyaddr(&addr, sizeof(addr), AF_INET); + if (h == NULL) + printf("?"); + else + printf("%s", h->h_name); + printf(" (%s) ", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); +} + +static void print_line_no(int i) +{ + printf("%3d ", -i); +} + +static const char *rtg_proto_str(enum mtrace_rtg_proto proto) +{ + static char buf[80]; + + buf[0] = '\0'; + + switch (proto) { + case MTRACE_RTG_PROTO_DVMRP: + return "DVMRP"; + case MTRACE_RTG_PROTO_MOSPF: + return "MOSPF"; + case MTRACE_RTG_PROTO_PIM: + return "PIM"; + case MTRACE_RTG_PROTO_CBT: + return "CBT"; + case MTRACE_RTG_PROTO_PIM_SPECIAL: + return "PIM special"; + case MTRACE_RTG_PROTO_PIM_STATIC: + return "PIM static"; + case MTRACE_RTG_PROTO_DVMRP_STATIC: + return "DVMRP static"; + case MTRACE_RTG_PROTO_PIM_MBGP: + return "PIM MBGP"; + case MTRACE_RTG_PROTO_CBT_SPECIAL: + return "CBT special"; + case MTRACE_RTG_PROTO_CBT_STATIC: + return "CBT static"; + case MTRACE_RTG_PROTO_PIM_ASSERT: + return "PIM assert"; + default: + snprintf(buf, sizeof(buf), "unknown protocol (%d)", proto); + return buf; + } +} + +static void print_rtg_proto(uint32_t rtg_proto) +{ + printf("%s", rtg_proto_str(rtg_proto)); +} + +static void print_fwd_ttl(uint32_t fwd_ttl) +{ + printf("thresh^ %d", fwd_ttl); +} + +static const char *fwd_code_str(enum mtrace_fwd_code code) +{ + static char buf[80]; + + buf[0] = '\0'; + + switch (code) { + case MTRACE_FWD_CODE_NO_ERROR: + return "no error"; + case MTRACE_FWD_CODE_WRONG_IF: + return "wrong interface"; + case MTRACE_FWD_CODE_PRUNE_SENT: + return "prune sent"; + case MTRACE_FWD_CODE_PRUNE_RCVD: + return "prune received"; + case MTRACE_FWD_CODE_SCOPED: + return "scoped"; + case MTRACE_FWD_CODE_NO_ROUTE: + return "no route"; + case MTRACE_FWD_CODE_WRONG_LAST_HOP: + return "wrong last hop"; + case MTRACE_FWD_CODE_NOT_FORWARDING: + return "not forwarding"; + case MTRACE_FWD_CODE_REACHED_RP: + return "reached RP"; + case MTRACE_FWD_CODE_RPF_IF: + return "RPF interface"; + case MTRACE_FWD_CODE_NO_MULTICAST: + return "no multicast"; + case MTRACE_FWD_CODE_INFO_HIDDEN: + return "info hidden"; + case MTRACE_FWD_CODE_NO_SPACE: + return "no space"; + case MTRACE_FWD_CODE_OLD_ROUTER: + return "old router"; + case MTRACE_FWD_CODE_ADMIN_PROHIB: + return "admin. prohib."; + default: + snprintf(buf, sizeof(buf), "unknown fwd. code (%d)", code); + return buf; + } +} + +static void print_fwd_code(uint32_t fwd_code) +{ + printf("%s", fwd_code_str(fwd_code)); +} + +static void print_rsp(struct igmp_mtrace_rsp *rsp) +{ + print_host(rsp->outgoing); + if (rsp->fwd_code == 0 || rsp->fwd_code == MTRACE_FWD_CODE_REACHED_RP) { + print_rtg_proto(rsp->rtg_proto); + printf(" "); + if (rsp->fwd_code == MTRACE_FWD_CODE_REACHED_RP) + printf("(RP) "); + if (rsp->rtg_proto == MTRACE_RTG_PROTO_PIM) { + switch (rsp->src_mask) { + case MTRACE_SRC_MASK_GROUP: + printf("(*,G) "); + break; + case MTRACE_SRC_MASK_SOURCE: + printf("(S,G) "); + break; + } + } + print_fwd_ttl(rsp->fwd_ttl); + } else { + print_fwd_code(rsp->fwd_code); + } + printf("\n"); +} + +static void print_dest(struct igmp_mtrace *mtrace) +{ + print_line_no(0); + print_host(mtrace->dst_addr); + printf("\n"); +} + +static void print_summary(struct igmp_mtrace *mtrace, int hops, long msec) +{ + int i; + int t = 0; + + for (i = 0; i < hops; i++) + t += mtrace->rsp[i].fwd_ttl; + + printf("Round trip time %ld ms; total ttl of %d required.\n", msec, t); +} + +static void print_responses(struct igmp_mtrace *mtrace, int hops, long msec) +{ + int i; + + print_dest(mtrace); + + for (i = 0; i < hops; i++) { + print_line_no(i + 1); + print_rsp(&mtrace->rsp[i]); + } + print_summary(mtrace, hops, msec); +} + +static int send_query(int fd, struct in_addr to_addr, + struct igmp_mtrace *mtrace) +{ + struct sockaddr_in to; + socklen_t tolen; + int sent; + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = to_addr; + tolen = sizeof(to); + + sent = sendto(fd, (char *)mtrace, sizeof(*mtrace), MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + + if (sent < 1) + return -1; + return 0; +} + +static void print_query(struct igmp_mtrace *mtrace) +{ + char src_str[INET_ADDRSTRLEN]; + char dst_str[INET_ADDRSTRLEN]; + char grp_str[INET_ADDRSTRLEN]; + + printf("* Mtrace from %s to %s via group %s\n", + inet_ntop(AF_INET, &mtrace->src_addr, src_str, sizeof(src_str)), + inet_ntop(AF_INET, &mtrace->dst_addr, dst_str, sizeof(dst_str)), + inet_ntop(AF_INET, &mtrace->grp_addr, grp_str, sizeof(grp_str))); +} + +static int recv_response(int fd, int *hops, struct igmp_mtrace *mtracer) +{ + int recvd; + char mtrace_buf[IP_AND_MTRACE_BUF_LEN]; + struct ip *ip; + struct igmp_mtrace *mtrace; + int mtrace_len; + int responses; + unsigned short sum; + size_t mtrace_off; + size_t ip_len; + + recvd = recvfrom(fd, mtrace_buf, IP_AND_MTRACE_BUF_LEN, 0, NULL, 0); + + if (recvd < 1) { + fprintf(stderr, "recvfrom error: %s\n", strerror(errno)); + return -1; + } + + if (recvd < (int)sizeof(struct ip)) { + fprintf(stderr, "no ip header\n"); + return -1; + } + + ip = (struct ip *)mtrace_buf; + + if (ip->ip_v != 4) { + fprintf(stderr, "IP not version 4\n"); + return -1; + } + + sum = ip->ip_sum; + ip->ip_sum = 0; + + if (sum != in_cksum(ip, ip->ip_hl * 4)) + return -1; + + /* Header overflow check */ + mtrace_off = 4 * ip->ip_hl; + if (mtrace_off > MTRACE_BUF_LEN) + return -1; + + /* Underflow/overflow check */ + ip_len = ntohs(ip->ip_len); + if (ip_len < mtrace_off || ip_len < MTRACE_HDR_SIZE + || ip_len > MTRACE_BUF_LEN) + return -1; + + mtrace_len = ip_len - mtrace_off; + mtrace = (struct igmp_mtrace *)(mtrace_buf + mtrace_off); + + sum = mtrace->checksum; + mtrace->checksum = 0; + if (sum != in_cksum(mtrace, mtrace_len)) { + fprintf(stderr, "mtrace checksum wrong\n"); + return -1; + } + + if (mtrace->type != PIM_IGMP_MTRACE_RESPONSE) + return -1; + + + responses = mtrace_len - sizeof(struct igmp_mtrace); + responses /= sizeof(struct igmp_mtrace_rsp); + + if (responses > MTRACE_MAX_HOPS) { + fprintf(stderr, "mtrace too large\n"); + return -1; + } + + if (hops) + *hops = responses; + + if (mtracer) + memcpy(mtracer, mtrace, mtrace_len); + + return 0; +} + +static int wait_for_response(int fd, int *hops, struct igmp_mtrace *mtrace, + long *ret_msec) +{ + fd_set readfds; + struct timeval timeout; + int ret; + long msec, rmsec, tmsec; + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + memset(&timeout, 0, sizeof(timeout)); + + timeout.tv_sec = MTRACE_TIMEOUT; + + tmsec = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; + do { + ret = select(fd + 1, &readfds, NULL, NULL, &timeout); + if (ret <= 0) + return ret; + rmsec = timeout.tv_sec * 1000 + timeout.tv_usec / 1000; + msec = tmsec - rmsec; + } while (recv_response(fd, hops, mtrace) != 0); + + if (ret_msec) + *ret_msec = msec; + + return ret; +} + +static bool check_end(struct igmp_mtrace *mtrace, int hops) +{ + return mtrace->src_addr.s_addr == mtrace->rsp[hops - 1].prev_hop.s_addr; +} + +int main(int argc, char *const argv[]) +{ + struct in_addr mc_source; + struct in_addr mc_group; + struct in_addr iface_addr; + struct in_addr gw_addr; + struct in_addr mtrace_addr; + struct igmp_mtrace mtrace; + struct igmp_mtrace *mtracep; + int hops = 255; + int rhops; + int maxhops = 255; + int perhop = 3; + int ifindex; + int unicast = 1; + int ttl = 64; + int fd = -1; + int ret = -1; + int c; + long msec; + int i, j; + char ifname[IF_NAMESIZE]; + char mbuf[MTRACE_BUF_LEN]; + bool not_group; + + mtrace_addr.s_addr = inet_addr("224.0.1.32"); + + uid_t uid = getuid(); + + if (uid != 0) { + printf("must run as root\n"); + exit(EXIT_FAILURE); + } + + if (argc <= 0) + progname = "mtracebis"; + else + progname = argv[0]; + + if (argc != 2 && argc != 3) { + usage(); + exit(EXIT_FAILURE); + } + + while (1) { + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0}}; + int option_index = 0; + + c = getopt_long(argc, argv, "vh", long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'h': + usage(); + exit(0); + case 'v': + version(); + exit(0); + default: + usage(); + exit(EXIT_FAILURE); + } + } + if (inet_pton(AF_INET, argv[1], &mc_source) != 1) { + usage(); + fprintf(stderr, "%s: %s is not a valid IPv4 address\n", argv[0], + argv[1]); + exit(EXIT_FAILURE); + } + + mc_group.s_addr = INADDR_ANY; + not_group = false; + + if (argc == 3) { + if (inet_pton(AF_INET, argv[2], &mc_group) != 1) + not_group = true; + if (!not_group && !IPV4_CLASS_DE(ntohl(mc_group.s_addr))) + not_group = true; + } + + if (not_group) { + usage(); + fprintf(stderr, "%s: %s is not a valid IPv4 group address\n", + argv[0], argv[2]); + exit(EXIT_FAILURE); + } + + ifindex = routeget(mc_source, &iface_addr, &gw_addr); + if (ifindex < 0) { + fprintf(stderr, "%s: failed to get route to source %s\n", + argv[0], argv[1]); + exit(EXIT_FAILURE); + } + + if (if_indextoname(ifindex, ifname) == NULL) { + fprintf(stderr, "%s: if_indextoname error: %s\n", argv[0], + strerror(errno)); + exit(EXIT_FAILURE); + } + + /* zero mtrace struct */ + memset((char *)&mtrace, 0, sizeof(mtrace)); + + /* set up query */ + mtrace.type = PIM_IGMP_MTRACE_QUERY_REQUEST; + mtrace.hops = hops; + mtrace.checksum = 0; + mtrace.grp_addr = mc_group; + mtrace.src_addr = mc_source; + mtrace.dst_addr = iface_addr; + mtrace.rsp_addr = unicast ? iface_addr : mtrace_addr; + mtrace.rsp_ttl = ttl; + mtrace.qry_id = 0xffffff & time(NULL); + + mtrace.checksum = in_cksum(&mtrace, sizeof(mtrace)); + + fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); + + if (fd < 1) { + fprintf(stderr, "%s: socket error: %s\n", argv[0], + strerror(errno)); + exit(EXIT_FAILURE); + } + + ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, + strlen(ifname)); + + if (ret < 0) { + fprintf(stderr, "%s: setsockopt error: %s\n", argv[0], + strerror(errno)); + ret = EXIT_FAILURE; + goto close_fd; + } + + print_query(&mtrace); + if (send_query(fd, gw_addr, &mtrace) < 0) { + fprintf(stderr, "%s: sendto error: %s\n", argv[0], + strerror(errno)); + ret = EXIT_FAILURE; + goto close_fd; + } + printf("Querying full reverse path...\n"); + mtracep = (struct igmp_mtrace *)mbuf; + ret = wait_for_response(fd, &rhops, mtracep, &msec); + if (ret > 0) { + print_responses(mtracep, rhops, msec); + ret = 0; + goto close_fd; + } + if (ret < 0) { + fprintf(stderr, "%s: select error: %s\n", argv[0], + strerror(errno)); + ret = EXIT_FAILURE; + goto close_fd; + } + printf(" * "); + printf("switching to hop-by-hop:\n"); + print_dest(&mtrace); + for (i = 1; i < maxhops; i++) { + print_line_no(i); + mtrace.hops = i; + for (j = 0; j < perhop; j++) { + mtrace.qry_id++; + mtrace.checksum = 0; + mtrace.checksum = in_cksum(&mtrace, sizeof(mtrace)); + if (send_query(fd, gw_addr, &mtrace) < 0) { + fprintf(stderr, "%s: sendto error: %s\n", + argv[0], strerror(errno)); + ret = EXIT_FAILURE; + goto close_fd; + } + ret = wait_for_response(fd, &rhops, mtracep, &msec); + if (ret > 0) { + if (check_end(mtracep, rhops)) { + print_rsp(&mtracep->rsp[rhops - 1]); + print_summary(mtracep, rhops, msec); + ret = 0; + goto close_fd; + } + if (i > rhops) { + printf(" * ...giving up.\n"); + ret = 0; + goto close_fd; + } + print_rsp(&mtracep->rsp[rhops - 1]); + break; + } + printf(" *"); + } + if (ret <= 0) + printf("\n"); + } + ret = 0; +close_fd: + close(fd); + exit(ret); +} + +#else /* __linux__ */ + +#include +#include + +int main(int argc, char *argv[]) +{ + printf("%s implemented only for GNU/Linux\n", argv[0]); + exit(0); +} + +#endif /* __linux__ */ diff --git a/pimd/mtracebis_netlink.c b/pimd/mtracebis_netlink.c new file mode 100644 index 0000000..16873ed --- /dev/null +++ b/pimd/mtracebis_netlink.c @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libnetlink.c RTnetlink service routines. + * + * Authors: Alexey Kuznetsov, + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __linux__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtracebis_netlink.h" + +int rcvbuf = 1024 * 1024; + +void rtnl_close(struct rtnl_handle *rth) +{ + if (rth->fd >= 0) { + close(rth->fd); + rth->fd = -1; + } +} + +int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol) +{ + socklen_t addr_len; + int sndbuf = 32768; + + memset(rth, 0, sizeof(*rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, protocol); + if (rth->fd < 0) { + perror("Cannot open netlink socket"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) + < 0) { + perror("SO_SNDBUF"); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) + < 0) { + perror("SO_RCVBUF"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) + < 0) { + perror("Cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) + < 0) { + perror("Cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + fprintf(stderr, "Wrong address length %d\n", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + fprintf(stderr, "Wrong address family %d\n", + rth->local.nl_family); + return -1; + } + rth->seq = getpid(); + return 0; +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return send(rth->fd, (void *)&req, sizeof(req), 0); +} + +int rtnl_send(struct rtnl_handle *rth, const char *buf, int len) +{ + return send(rth->fd, buf, len, 0); +} + +int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int len) +{ + struct nlmsghdr *h; + int status; + char resp[1024]; + + status = send(rth->fd, buf, len, 0); + if (status < 0) + return status; + + /* Check for immediate errors */ + status = recv(rth->fd, resp, sizeof(resp), MSG_DONTWAIT | MSG_PEEK); + if (status < 0) { + if (errno == EAGAIN) + return 0; + return -1; + } + + for (h = (struct nlmsghdr *)resp; NLMSG_OK(h, (uint32_t)status); + h = NLMSG_NEXT(h, status)) { + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(h); + if (h->nlmsg_len + < NLMSG_LENGTH(sizeof(struct nlmsgerr))) + fprintf(stderr, "ERROR truncated\n"); + else + errno = -err->error; + return -1; + } + } + + return 0; +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = {{.iov_base = &nlh, .iov_len = sizeof(nlh)}, + {.iov_base = req, .iov_len = len}}; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = iov, + .msg_iovlen = 2, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter_l(struct rtnl_handle *rth, + const struct rtnl_dump_filter_arg *arg) +{ + struct sockaddr_nl nladdr; + char buf[16384]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + while (1) { + int status; + const struct rtnl_dump_filter_arg *a; + int found_done = 0; + int msglen = 0; + + iov.iov_len = sizeof(buf); + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + fprintf(stderr, "netlink receive error %s (%d)\n", + strerror(errno), errno); + return -1; + } + + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + + for (a = arg; a->filter; a++) { + struct nlmsghdr *h = (struct nlmsghdr *)iov.iov_base; + msglen = status; + + while (NLMSG_OK(h, (uint32_t)msglen)) { + int err; + + if (nladdr.nl_pid != 0 + || h->nlmsg_pid != rth->local.nl_pid + || h->nlmsg_seq != rth->dump) { + if (a->junk) { + err = a->junk(&nladdr, h, + a->arg2); + if (err < 0) + return err; + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + found_done = 1; + break; /* process next filter */ + } + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *merr = + (struct nlmsgerr *)NLMSG_DATA( + h); + if (h->nlmsg_len + < NLMSG_LENGTH(sizeof( + struct nlmsgerr))) { + fprintf(stderr, + "ERROR truncated\n"); + } else { + errno = -merr->error; + perror("RTNETLINK answers"); + } + return -1; + } + err = a->filter(&nladdr, h, a->arg1); + if (err < 0) + return err; + + skip_it: + h = NLMSG_NEXT(h, msglen); + } + } + + if (found_done) + return 0; + + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (msglen) { + fprintf(stderr, "!!!Remnant of size %d\n", msglen); + exit(1); + } + } +} + +int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, void *arg1, + rtnl_filter_t junk, void *arg2) +{ + const struct rtnl_dump_filter_arg a[2] = { + {.filter = filter, .arg1 = arg1, .junk = junk, .arg2 = arg2}, + {.filter = NULL, .arg1 = NULL, .junk = NULL, .arg2 = NULL}}; + + return rtnl_dump_filter_l(rth, a); +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, rtnl_filter_t junk, + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = {.iov_base = (void *)n, .iov_len = n->nlmsg_len}; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + perror("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf, 0, sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + fprintf(stderr, "netlink receive error %s (%d)\n", + strerror(errno), errno); + return -1; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "sender address length == %d\n", + msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr *)iov.iov_base; + status >= (int)sizeof(*h);) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, + "!!!malformed message: len=%d\n", len); + exit(1); + } + + if ((int)nladdr.nl_pid != peer + || h->nlmsg_pid != rtnl->local.nl_pid + || h->nlmsg_seq != seq) { + if (junk) { + err = junk(&nladdr, h, jarg); + if (err < 0) + return err; + } + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *merr = + (struct nlmsgerr *)NLMSG_DATA(h); + if (l < (int)sizeof(struct nlmsgerr)) { + fprintf(stderr, "ERROR truncated\n"); + } else { + errno = -merr->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, + h->nlmsg_len); + return 0; + } + perror("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + fprintf(stderr, "Unexpected reply!!!\n"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_listen(struct rtnl_handle *rtnl, rtnl_filter_t handler, void *jarg) +{ + int status; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + char buf[8192]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + fprintf(stderr, "netlink receive error %s (%d)\n", + strerror(errno), errno); + if (errno == ENOBUFS) + continue; + return -1; + } + if (status == 0) { + fprintf(stderr, "EOF on netlink\n"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + fprintf(stderr, "Sender address length == %d\n", + msg.msg_namelen); + exit(1); + } + for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h);) { + int err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Truncated message\n"); + return -1; + } + fprintf(stderr, + "!!!malformed message: len=%d\n", len); + exit(1); + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + fprintf(stderr, "Message truncated\n"); + continue; + } + if (status) { + fprintf(stderr, "!!!Remnant of size %d\n", status); + exit(1); + } + } +} + +int rtnl_from_file(FILE *rtnl, rtnl_filter_t handler, void *jarg) +{ + struct sockaddr_nl nladdr; + char buf[8192]; + struct nlmsghdr *h = (void *)buf; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + while (1) { + int err; + size_t l, rl, arl; + + rl = sizeof(*h); + arl = fread(&buf, 1, rl, rtnl); + + if (arl != rl) { + if (arl == 0) + return 0; + + if (ferror(rtnl)) + fprintf(stderr, "%s: header read failed\n", + __func__); + else + fprintf(stderr, "%s: truncated header\n", + __func__); + return -1; + } + + l = h->nlmsg_len > rl ? h->nlmsg_len - rl : 0; + + if (l == 0 || (l + (size_t)NLMSG_HDRLEN) > sizeof(buf)) { + fprintf(stderr, "%s: malformed message: len=%zu @%lu\n", + __func__, (size_t)h->nlmsg_len, ftell(rtnl)); + return -1; + } + + rl = NLMSG_ALIGN(l); + arl = fread(NLMSG_DATA(h), 1, rl, rtnl); + + if (arl != rl) { + if (ferror(rtnl)) + fprintf(stderr, "%s: msg read failed\n", + __func__); + else + fprintf(stderr, "%s: truncated message\n", + __func__); + return -1; + } + + err = handler(&nladdr, h, jarg); + if (err < 0) + return err; + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) { + fprintf(stderr, + "addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if ((int)(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len)) > maxlen) { + fprintf(stderr, + "addattr_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + + if (data) + memcpy(RTA_DATA(rta), data, alen); + else + assert(alen == 0); + + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len) +{ + if ((int)(NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len)) > maxlen) { + fprintf(stderr, + "addraw_l ERROR: message exceeded bound of %d\n", + maxlen); + return -1; + } + + memcpy(NLMSG_TAIL(n), data, len); + memset((uint8_t *)NLMSG_TAIL(n) + len, 0, NLMSG_ALIGN(len) - len); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLMSG_ALIGN(len); + return 0; +} + +struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) +{ + struct rtattr *nest = NLMSG_TAIL(n); + + addattr_l(n, maxlen, type, NULL, 0); + return nest; +} + +int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) +{ + nest->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)nest; + return n->nlmsg_len; +} + +struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, int type, + const void *data, int len) +{ + struct rtattr *start = NLMSG_TAIL(n); + + addattr_l(n, maxlen, type, data, len); + addattr_nest(n, maxlen, type); + return start; +} + +int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *start) +{ + struct rtattr *nest = start + NLMSG_ALIGN(start->rta_len); + + start->rta_len = (uint8_t *)NLMSG_TAIL(n) - (uint8_t *)start; + addattr_nest_end(n, nest); + return n->nlmsg_len; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if ((int)(RTA_ALIGN(rta->rta_len) + len) > maxlen) { + fprintf(stderr, + "rta_addattr32: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, const void *data, + int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if ((int)(RTA_ALIGN(rta->rta_len) + RTA_ALIGN(len)) > maxlen) { + fprintf(stderr, + "rta_addattr_l: Error! max allowed bound %d exceeded\n", + maxlen); + return -1; + } + subrta = (struct rtattr *)(((char *)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + RTA_ALIGN(len); + return 0; +} + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + if ((rta->rta_type <= max) && (!tb[rta->rta_type])) + tb[rta->rta_type] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return 0; +} + +int parse_rtattr_byindex(struct rtattr *tb[], int max, struct rtattr *rta, + int len) +{ + int i = 0; + + memset(tb, 0, sizeof(struct rtattr *) * max); + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max && i < max) + tb[i++] = rta; + rta = RTA_NEXT(rta, len); + } + if (len) + fprintf(stderr, "!!!Deficit %d, rta_len=%d\n", len, + rta->rta_len); + return i; +} + +int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, + struct rtattr *rta, int len) +{ + if ((int)RTA_PAYLOAD(rta) < len) + return -1; + if (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) { + rta = (struct rtattr *)(uint8_t *)RTA_DATA(rta) + + RTA_ALIGN(len); + return parse_rtattr_nested(tb, max, rta); + } + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + return 0; +} + +#endif /* __linux__ */ diff --git a/pimd/mtracebis_netlink.h b/pimd/mtracebis_netlink.h new file mode 100644 index 0000000..1f22927 --- /dev/null +++ b/pimd/mtracebis_netlink.h @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * libnetlink.c RTnetlink service routines. + * + * Authors: Alexey Kuznetsov, + * + */ + +#ifdef __linux__ + +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include +#include +#include +#include +#include +#include + +struct rtnl_handle { + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rcvbuf; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern int rtnl_open_byproto(struct rtnl_handle *rth, unsigned subscriptions, + int protocol); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, + int len); + +typedef int (*rtnl_filter_t)(const struct sockaddr_nl *, struct nlmsghdr *n, + void *); + +struct rtnl_dump_filter_arg { + rtnl_filter_t filter; + void *arg1; + rtnl_filter_t junk; + void *arg2; +}; + +extern int rtnl_dump_filter_l(struct rtnl_handle *rth, + const struct rtnl_dump_filter_arg *arg); +extern int rtnl_dump_filter(struct rtnl_handle *rth, rtnl_filter_t filter, + void *arg1, rtnl_filter_t junk, void *arg2); + +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + rtnl_filter_t junk, void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, const char *buf, int); +extern int rtnl_send_check(struct rtnl_handle *rth, const char *buf, int); + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen); +extern int addraw_l(struct nlmsghdr *n, int maxlen, const void *data, int len); +extern struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type); +extern int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest); +extern struct rtattr *addattr_nest_compat(struct nlmsghdr *n, int maxlen, + int type, const void *data, int len); +extern int addattr_nest_compat_end(struct nlmsghdr *n, struct rtattr *nest); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, + const void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, + int len); +extern int parse_rtattr_byindex(struct rtattr *tb[], int max, + struct rtattr *rta, int len); +extern int __parse_rtattr_nested_compat(struct rtattr *tb[], int max, + struct rtattr *rta, int len); + +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) + +#define parse_rtattr_nested_compat(tb, max, rta, data, len) \ + ({ \ + data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \ + __parse_rtattr_nested_compat(tb, max, rta, len); \ + }) + +extern int rtnl_listen(struct rtnl_handle *, rtnl_filter_t handler, void *jarg); +extern int rtnl_from_file(FILE *, rtnl_filter_t handler, void *jarg); + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((uint8_t *)(nmsg)) \ + + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#ifndef IFA_RTA +#define IFA_RTA(r) \ + ((struct rtattr *)(((char *)(r)) \ + + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#endif +#ifndef IFA_PAYLOAD +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) +#endif + +#ifndef IFLA_RTA +#define IFLA_RTA(r) \ + ((struct rtattr *)(((char *)(r)) \ + + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#endif +#ifndef IFLA_PAYLOAD +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) +#endif + +#ifndef NDA_RTA +#define NDA_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) +#endif +#ifndef NDA_PAYLOAD +#define NDA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndmsg)) +#endif + +#ifndef NDTA_RTA +#define NDTA_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct ndtmsg)))) +#endif +#ifndef NDTA_PAYLOAD +#define NDTA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ndtmsg)) +#endif + +#endif /* __LIBNETLINK_H__ */ + +#endif /* __linux__ */ diff --git a/pimd/mtracebis_routeget.c b/pimd/mtracebis_routeget.c new file mode 100644 index 0000000..20618fa --- /dev/null +++ b/pimd/mtracebis_routeget.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multicast Traceroute for FRRouting + * Copyright (C) 2018 Mladen Sablic + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __linux__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtracebis_netlink.h" +#include "mtracebis_routeget.h" + +static int find_dst(struct nlmsghdr *n, struct in_addr *src, struct in_addr *gw) +{ + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr *tb[RTA_MAX + 1]; + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + if (tb[RTA_PREFSRC]) + src->s_addr = *(uint32_t *)RTA_DATA(tb[RTA_PREFSRC]); + if (tb[RTA_GATEWAY]) + gw->s_addr = *(uint32_t *)RTA_DATA(tb[RTA_GATEWAY]); + if (tb[RTA_OIF]) + return *(int *)RTA_DATA(tb[RTA_OIF]); + return 0; +} + +int routeget(struct in_addr dst, struct in_addr *src, struct in_addr *gw) +{ + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + int ret; + struct rtnl_handle rth = {.fd = -1}; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + req.r.rtm_family = AF_INET; + req.r.rtm_table = 0; + req.r.rtm_protocol = 0; + req.r.rtm_scope = 0; + req.r.rtm_type = 0; + req.r.rtm_src_len = 0; + req.r.rtm_dst_len = 0; + req.r.rtm_tos = 0; + + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.s_addr, 4); + req.r.rtm_dst_len = 32; + + ret = rtnl_open(&rth, 0); + + if (ret < 0 || rth.fd <= 0) + return ret; + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + ret = -1; + goto close_rth; + } + + ret = find_dst(&req.n, src, gw); +close_rth: + rtnl_close(&rth); + return ret; +} + +#endif /* __linux__ */ diff --git a/pimd/mtracebis_routeget.h b/pimd/mtracebis_routeget.h new file mode 100644 index 0000000..cf97723 --- /dev/null +++ b/pimd/mtracebis_routeget.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multicast Traceroute for FRRouting + * Copyright (C) 2018 Mladen Sablic + */ + +#ifdef __linux__ + +#ifndef ROUTEGET_H +#define ROUTEGET_H + +#include + +int routeget(struct in_addr dst, struct in_addr *src, struct in_addr *gw); + +#endif /* ROUTEGET */ + +#endif /* __linux__ */ diff --git a/pimd/pim6_cmd.c b/pimd/pim6_cmd.c new file mode 100644 index 0000000..ec91270 --- /dev/null +++ b/pimd/pim6_cmd.c @@ -0,0 +1,1900 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for IPv6 FRR + * Copyright (C) 2022 Vmware, Inc. + * Mobashshera Rasool + */ + +#include + +#include "lib/json.h" +#include "command.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "plist.h" +#include "hash.h" +#include "nexthop.h" +#include "vrf.h" +#include "ferr.h" + +#include "pimd.h" +#include "pim6_cmd.h" +#include "pim_cmd_common.h" +#include "pim_vty.h" +#include "lib/northbound_cli.h" +#include "pim_errors.h" +#include "pim_nb.h" +#include "pim_addr.h" +#include "pim_nht.h" +#include "pim_bsm.h" +#include "pim_iface.h" +#include "pim_zebra.h" +#include "pim_instance.h" + +#include "pimd/pim6_cmd_clippy.c" + +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = pim_debug_config_write, +}; + +DEFPY (ipv6_pim_joinprune_time, + ipv6_pim_joinprune_time_cmd, + "ipv6 pim join-prune-interval (1-65535)$jpi", + IPV6_STR + PIM_STR + "Join Prune Send Interval\n" + "Seconds\n") +{ + return pim_process_join_prune_cmd(vty, jpi_str); +} + +DEFPY (no_ipv6_pim_joinprune_time, + no_ipv6_pim_joinprune_time_cmd, + "no ipv6 pim join-prune-interval [(1-65535)]", + NO_STR + IPV6_STR + PIM_STR + "Join Prune Send Interval\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_join_prune_cmd(vty); +} + +DEFPY (ipv6_pim_spt_switchover_infinity, + ipv6_pim_spt_switchover_infinity_cmd, + "ipv6 pim spt-switchover infinity-and-beyond", + IPV6_STR + PIM_STR + "SPT-Switchover\n" + "Never switch to SPT Tree\n") +{ + return pim_process_spt_switchover_infinity_cmd(vty); +} + +DEFPY (ipv6_pim_spt_switchover_infinity_plist, + ipv6_pim_spt_switchover_infinity_plist_cmd, + "ipv6 pim spt-switchover infinity-and-beyond prefix-list PREFIXLIST6_NAME$plist", + IPV6_STR + PIM_STR + "SPT-Switchover\n" + "Never switch to SPT Tree\n" + "Prefix-List to control which groups to switch\n" + "Prefix-List name\n") +{ + return pim_process_spt_switchover_prefixlist_cmd(vty, plist); +} + +DEFPY (no_ipv6_pim_spt_switchover_infinity, + no_ipv6_pim_spt_switchover_infinity_cmd, + "no ipv6 pim spt-switchover infinity-and-beyond", + NO_STR + IPV6_STR + PIM_STR + "SPT_Switchover\n" + "Never switch to SPT Tree\n") +{ + return pim_process_no_spt_switchover_cmd(vty); +} + +DEFPY (no_ipv6_pim_spt_switchover_infinity_plist, + no_ipv6_pim_spt_switchover_infinity_plist_cmd, + "no ipv6 pim spt-switchover infinity-and-beyond prefix-list PREFIXLIST6_NAME", + NO_STR + IPV6_STR + PIM_STR + "SPT_Switchover\n" + "Never switch to SPT Tree\n" + "Prefix-List to control which groups to switch\n" + "Prefix-List name\n") +{ + return pim_process_no_spt_switchover_cmd(vty); +} + +DEFPY (ipv6_pim_packets, + ipv6_pim_packets_cmd, + "ipv6 pim packets (1-255)", + IPV6_STR + PIM_STR + "packets to process at one time per fd\n" + "Number of packets\n") +{ + return pim_process_pim_packet_cmd(vty, packets_str); +} + +DEFPY (no_ipv6_pim_packets, + no_ipv6_pim_packets_cmd, + "no ipv6 pim packets [(1-255)]", + NO_STR + IPV6_STR + PIM_STR + "packets to process at one time per fd\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_pim_packet_cmd(vty); +} + +DEFPY (ipv6_pim_keep_alive, + ipv6_pim_keep_alive_cmd, + "ipv6 pim keep-alive-timer (1-65535)$kat", + IPV6_STR + PIM_STR + "Keep alive Timer\n" + "Seconds\n") +{ + return pim_process_keepalivetimer_cmd(vty, kat_str); +} + +DEFPY (no_ipv6_pim_keep_alive, + no_ipv6_pim_keep_alive_cmd, + "no ipv6 pim keep-alive-timer [(1-65535)]", + NO_STR + IPV6_STR + PIM_STR + "Keep alive Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_keepalivetimer_cmd(vty); +} + +DEFPY (ipv6_pim_rp_keep_alive, + ipv6_pim_rp_keep_alive_cmd, + "ipv6 pim rp keep-alive-timer (1-65535)$kat", + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "Keep alive Timer\n" + "Seconds\n") +{ + return pim_process_rp_kat_cmd(vty, kat_str); +} + +DEFPY (no_ipv6_pim_rp_keep_alive, + no_ipv6_pim_rp_keep_alive_cmd, + "no ipv6 pim rp keep-alive-timer [(1-65535)]", + NO_STR + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "Keep alive Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_rp_kat_cmd(vty); +} + +DEFPY (ipv6_pim_register_suppress, + ipv6_pim_register_suppress_cmd, + "ipv6 pim register-suppress-time (1-65535)$rst", + IPV6_STR + PIM_STR + "Register Suppress Timer\n" + "Seconds\n") +{ + return pim_process_register_suppress_cmd(vty, rst_str); +} + +DEFPY (no_ipv6_pim_register_suppress, + no_ipv6_pim_register_suppress_cmd, + "no ipv6 pim register-suppress-time [(1-65535)]", + NO_STR + IPV6_STR + PIM_STR + "Register Suppress Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_register_suppress_cmd(vty); +} + +DEFPY (interface_ipv6_pim, + interface_ipv6_pim_cmd, + "ipv6 pim [passive$passive]", + IPV6_STR + PIM_STR + "Disable exchange of protocol packets\n") +{ + int ret; + + ret = pim_process_ip_pim_cmd(vty); + + if (ret != NB_OK) + return ret; + + if (passive) + return pim_process_ip_pim_passive_cmd(vty, true); + + return CMD_SUCCESS; +} + +DEFPY (interface_no_ipv6_pim, + interface_no_ipv6_pim_cmd, + "no ipv6 pim [passive$passive]", + NO_STR + IPV6_STR + PIM_STR + "Disable exchange of protocol packets\n") +{ + if (passive) + return pim_process_ip_pim_passive_cmd(vty, false); + + return pim_process_no_ip_pim_cmd(vty); +} + +DEFPY (interface_ipv6_pim_drprio, + interface_ipv6_pim_drprio_cmd, + "ipv6 pim drpriority (0-4294967295)", + IPV6_STR + PIM_STR + "Set the Designated Router Election Priority\n" + "Value of the new DR Priority\n") +{ + return pim_process_ip_pim_drprio_cmd(vty, drpriority_str); +} + +DEFPY (interface_no_ipv6_pim_drprio, + interface_no_ipv6_pim_drprio_cmd, + "no ipv6 pim drpriority [(0-4294967295)]", + NO_STR + IPV6_STR + PIM_STR + "Revert the Designated Router Priority to default\n" + "Old Value of the Priority\n") +{ + return pim_process_no_ip_pim_drprio_cmd(vty); +} + +DEFPY (interface_ipv6_pim_hello, + interface_ipv6_pim_hello_cmd, + "ipv6 pim hello (1-65535) [(1-65535)]$hold", + IPV6_STR + PIM_STR + IFACE_PIM_HELLO_STR + IFACE_PIM_HELLO_TIME_STR + IFACE_PIM_HELLO_HOLD_STR) +{ + return pim_process_ip_pim_hello_cmd(vty, hello_str, hold_str); +} + +DEFPY (interface_no_ipv6_pim_hello, + interface_no_ipv6_pim_hello_cmd, + "no ipv6 pim hello [(1-65535) [(1-65535)]]", + NO_STR + IPV6_STR + PIM_STR + IFACE_PIM_HELLO_STR + IGNORED_IN_NO_STR + IGNORED_IN_NO_STR) +{ + return pim_process_no_ip_pim_hello_cmd(vty); +} + +DEFPY (interface_ipv6_pim_activeactive, + interface_ipv6_pim_activeactive_cmd, + "[no] ipv6 pim active-active", + NO_STR + IPV6_STR + PIM_STR + "Mark interface as Active-Active for MLAG operations\n") +{ + return pim_process_ip_pim_activeactive_cmd(vty, no); +} + +DEFPY_HIDDEN (interface_ipv6_pim_ssm, + interface_ipv6_pim_ssm_cmd, + "ipv6 pim ssm", + IPV6_STR + PIM_STR + IFACE_PIM_STR) +{ + int ret; + + ret = pim_process_ip_pim_cmd(vty); + + if (ret != NB_OK) + return ret; + + vty_out(vty, + "Enabled PIM SM on interface; configure PIM SSM range if needed\n"); + + return NB_OK; +} + +DEFPY_HIDDEN (interface_no_ipv6_pim_ssm, + interface_no_ipv6_pim_ssm_cmd, + "no ipv6 pim ssm", + NO_STR + IPV6_STR + PIM_STR + IFACE_PIM_STR) +{ + return pim_process_no_ip_pim_cmd(vty); +} + +DEFPY_HIDDEN (interface_ipv6_pim_sm, + interface_ipv6_pim_sm_cmd, + "ipv6 pim sm", + IPV6_STR + PIM_STR + IFACE_PIM_SM_STR) +{ + return pim_process_ip_pim_cmd(vty); +} + +DEFPY_HIDDEN (interface_no_ipv6_pim_sm, + interface_no_ipv6_pim_sm_cmd, + "no ipv6 pim sm", + NO_STR + IPV6_STR + PIM_STR + IFACE_PIM_SM_STR) +{ + return pim_process_no_ip_pim_cmd(vty); +} + +/* boundaries */ +DEFPY (interface_ipv6_pim_boundary_oil, + interface_ipv6_pim_boundary_oil_cmd, + "ipv6 multicast boundary oil WORD", + IPV6_STR + "Generic multicast configuration options\n" + "Define multicast boundary\n" + "Filter OIL by group using prefix list\n" + "Prefix list to filter OIL with\n") +{ + return pim_process_ip_pim_boundary_oil_cmd(vty, oil); +} + +DEFPY (interface_no_ipv6_pim_boundary_oil, + interface_no_ipv6_pim_boundary_oil_cmd, + "no ipv6 multicast boundary oil [WORD]", + NO_STR + IPV6_STR + "Generic multicast configuration options\n" + "Define multicast boundary\n" + "Filter OIL by group using prefix list\n" + "Prefix list to filter OIL with\n") +{ + return pim_process_no_ip_pim_boundary_oil_cmd(vty); +} + +DEFPY (interface_ipv6_mroute, + interface_ipv6_mroute_cmd, + "ipv6 mroute INTERFACE X:X::X:X$group [X:X::X:X]$source", + IPV6_STR + "Add multicast route\n" + "Outgoing interface name\n" + "Group address\n" + "Source address\n") +{ + return pim_process_ip_mroute_cmd(vty, interface, group_str, source_str); +} + +DEFPY (interface_no_ipv6_mroute, + interface_no_ipv6_mroute_cmd, + "no ipv6 mroute INTERFACE X:X::X:X$group [X:X::X:X]$source", + NO_STR + IPV6_STR + "Add multicast route\n" + "Outgoing interface name\n" + "Group Address\n" + "Source Address\n") +{ + return pim_process_no_ip_mroute_cmd(vty, interface, group_str, + source_str); +} + +DEFPY (ipv6_pim_rp, + ipv6_pim_rp_cmd, + "ipv6 pim rp X:X::X:X$rp [X:X::X:X/M]$gp", + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "ipv6 address of RP\n" + "Group Address range to cover\n") +{ + const char *group_str = (gp_str) ? gp_str : "FF00::0/8"; + + return pim_process_rp_cmd(vty, rp_str, group_str); +} + +DEFPY (no_ipv6_pim_rp, + no_ipv6_pim_rp_cmd, + "no ipv6 pim rp X:X::X:X$rp [X:X::X:X/M]$gp", + NO_STR + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "ipv6 address of RP\n" + "Group Address range to cover\n") +{ + const char *group_str = (gp_str) ? gp_str : "FF00::0/8"; + + return pim_process_no_rp_cmd(vty, rp_str, group_str); +} + +DEFPY (ipv6_pim_rp_prefix_list, + ipv6_pim_rp_prefix_list_cmd, + "ipv6 pim rp X:X::X:X$rp prefix-list PREFIXLIST6_NAME$plist", + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "ipv6 address of RP\n" + "group prefix-list filter\n" + "Name of a prefix-list\n") +{ + return pim_process_rp_plist_cmd(vty, rp_str, plist); +} + +DEFPY (no_ipv6_pim_rp_prefix_list, + no_ipv6_pim_rp_prefix_list_cmd, + "no ipv6 pim rp X:X::X:X$rp prefix-list PREFIXLIST6_NAME$plist", + NO_STR + IPV6_STR + PIM_STR + "Rendezvous Point\n" + "ipv6 address of RP\n" + "group prefix-list filter\n" + "Name of a prefix-list\n") +{ + return pim_process_no_rp_plist_cmd(vty, rp_str, plist); +} + +DEFPY (ipv6_pim_bsm, + ipv6_pim_bsm_cmd, + "ipv6 pim bsm", + IPV6_STR + PIM_STR + "Enable BSM support on the interface\n") +{ + return pim_process_bsm_cmd(vty); +} + +DEFPY (no_ipv6_pim_bsm, + no_ipv6_pim_bsm_cmd, + "no ipv6 pim bsm", + NO_STR + IPV6_STR + PIM_STR + "Enable BSM support on the interface\n") +{ + return pim_process_no_bsm_cmd(vty); +} + +DEFPY (ipv6_pim_ucast_bsm, + ipv6_pim_ucast_bsm_cmd, + "ipv6 pim unicast-bsm", + IPV6_STR + PIM_STR + "Accept/Send unicast BSM on the interface\n") +{ + return pim_process_unicast_bsm_cmd(vty); +} + +DEFPY (no_ipv6_pim_ucast_bsm, + no_ipv6_pim_ucast_bsm_cmd, + "no ipv6 pim unicast-bsm", + NO_STR + IPV6_STR + PIM_STR + "Accept/Send unicast BSM on the interface\n") +{ + return pim_process_no_unicast_bsm_cmd(vty); +} + +DEFPY (ipv6_ssmpingd, + ipv6_ssmpingd_cmd, + "ipv6 ssmpingd [X:X::X:X]$source", + IPV6_STR + CONF_SSMPINGD_STR + "Source address\n") +{ + const char *src_str = (source_str) ? source_str : "::"; + + return pim_process_ssmpingd_cmd(vty, NB_OP_CREATE, src_str); +} + + +DEFPY (no_ipv6_ssmpingd, + no_ipv6_ssmpingd_cmd, + "no ipv6 ssmpingd [X:X::X:X]$source", + NO_STR + IPV6_STR + CONF_SSMPINGD_STR + "Source address\n") +{ + const char *src_str = (source_str) ? source_str : "::"; + + return pim_process_ssmpingd_cmd(vty, NB_OP_DESTROY, src_str); +} + +DEFPY (interface_ipv6_mld_join, + interface_ipv6_mld_join_cmd, + "ipv6 mld join X:X::X:X$group [X:X::X:X$source]", + IPV6_STR + IFACE_MLD_STR + "MLD join multicast group\n" + "Multicast group address\n" + "Source address\n") +{ + char xpath[XPATH_MAXLEN]; + + if (!IN6_IS_ADDR_MULTICAST(&group)) { + vty_out(vty, "Invalid Multicast Address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (source_str) { + if (IPV6_ADDR_SAME(&source, &in6addr_any)) { + vty_out(vty, "Bad source address %s\n", source_str); + return CMD_WARNING_CONFIG_FAILED; + } + } else + source_str = "::"; + + snprintf(xpath, sizeof(xpath), FRR_GMP_JOIN_XPATH, "frr-routing:ipv6", + group_str, source_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY (interface_no_ipv6_mld_join, + interface_no_ipv6_mld_join_cmd, + "no ipv6 mld join X:X::X:X$group [X:X::X:X$source]", + NO_STR + IPV6_STR + IFACE_MLD_STR + "MLD join multicast group\n" + "Multicast group address\n" + "Source address\n") +{ + char xpath[XPATH_MAXLEN]; + + if (source_str) { + if (IPV6_ADDR_SAME(&source, &in6addr_any)) { + vty_out(vty, "Bad source address %s\n", source_str); + return CMD_WARNING_CONFIG_FAILED; + } + } else + source_str = "::"; + + snprintf(xpath, sizeof(xpath), FRR_GMP_JOIN_XPATH, "frr-routing:ipv6", + group_str, source_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY (interface_ipv6_mld, + interface_ipv6_mld_cmd, + "ipv6 mld", + IPV6_STR + IFACE_MLD_STR) +{ + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (interface_no_ipv6_mld, + interface_no_ipv6_mld_cmd, + "no ipv6 mld", + NO_STR + IPV6_STR + IFACE_MLD_STR) +{ + const struct lyd_node *pim_enable_dnode; + char pim_if_xpath[XPATH_MAXLEN + 64]; + + snprintf(pim_if_xpath, sizeof(pim_if_xpath), + "%s/frr-pim:pim/address-family[address-family='%s']", + VTY_CURR_XPATH, "frr-routing:ipv6"); + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv6"); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, + NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "false"); + } + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (interface_ipv6_mld_version, + interface_ipv6_mld_version_cmd, + "ipv6 mld version (1-2)$version", + IPV6_STR + IFACE_MLD_STR + "MLD version\n" + "MLD version number\n") +{ + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + nb_cli_enqueue_change(vty, "./mld-version", NB_OP_MODIFY, version_str); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (interface_no_ipv6_mld_version, + interface_no_ipv6_mld_version_cmd, + "no ipv6 mld version [(1-2)]", + NO_STR + IPV6_STR + IFACE_MLD_STR + "MLD version\n" + "MLD version number\n") +{ + nb_cli_enqueue_change(vty, "./mld-version", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (interface_ipv6_mld_query_interval, + interface_ipv6_mld_query_interval_cmd, + "ipv6 mld query-interval (1-65535)$q_interval", + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_QUERY_INTERVAL_STR + "Query interval in seconds\n") +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv6"); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_MODIFY, + q_interval_str); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (interface_no_ipv6_mld_query_interval, + interface_no_ipv6_mld_query_interval_cmd, + "no ipv6 mld query-interval [(1-65535)]", + NO_STR + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_QUERY_INTERVAL_STR + IGNORED_IN_NO_STR) +{ + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv6"); +} + +DEFPY (ipv6_mld_group_watermark, + ipv6_mld_group_watermark_cmd, + "ipv6 mld watermark-warn (1-65535)$limit", + IPV6_STR + MLD_STR + "Configure group limit for watermark warning\n" + "Group count to generate watermark warning\n") +{ + PIM_DECLVAR_CONTEXT_VRF(vrf, pim); + pim->gm_watermark_limit = limit; + + return CMD_SUCCESS; +} + +DEFPY (no_ipv6_mld_group_watermark, + no_ipv6_mld_group_watermark_cmd, + "no ipv6 mld watermark-warn [(1-65535)$limit]", + NO_STR + IPV6_STR + MLD_STR + "Unconfigure group limit for watermark warning\n" + IGNORED_IN_NO_STR) +{ + PIM_DECLVAR_CONTEXT_VRF(vrf, pim); + pim->gm_watermark_limit = 0; + + return CMD_SUCCESS; +} + +DEFPY (interface_ipv6_mld_query_max_response_time, + interface_ipv6_mld_query_max_response_time_cmd, + "ipv6 mld query-max-response-time (1-65535)$qmrt", + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_QUERY_MAX_RESPONSE_TIME_STR + "Query response value in deci-seconds\n") +{ + return gm_process_query_max_response_time_cmd(vty, qmrt_str); +} + +DEFPY (interface_no_ipv6_mld_query_max_response_time, + interface_no_ipv6_mld_query_max_response_time_cmd, + "no ipv6 mld query-max-response-time [(1-65535)]", + NO_STR + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_QUERY_MAX_RESPONSE_TIME_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_query_max_response_time_cmd(vty); +} + +DEFPY (interface_ipv6_mld_last_member_query_count, + interface_ipv6_mld_last_member_query_count_cmd, + "ipv6 mld last-member-query-count (1-255)$lmqc", + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_LAST_MEMBER_QUERY_COUNT_STR + "Last member query count\n") +{ + return gm_process_last_member_query_count_cmd(vty, lmqc_str); +} + +DEFPY (interface_no_ipv6_mld_last_member_query_count, + interface_no_ipv6_mld_last_member_query_count_cmd, + "no ipv6 mld last-member-query-count [(1-255)]", + NO_STR + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_LAST_MEMBER_QUERY_COUNT_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_last_member_query_count_cmd(vty); +} + +DEFPY (interface_ipv6_mld_last_member_query_interval, + interface_ipv6_mld_last_member_query_interval_cmd, + "ipv6 mld last-member-query-interval (1-65535)$lmqi", + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_LAST_MEMBER_QUERY_INTERVAL_STR + "Last member query interval in deciseconds\n") +{ + return gm_process_last_member_query_interval_cmd(vty, lmqi_str); +} + +DEFPY (interface_no_ipv6_mld_last_member_query_interval, + interface_no_ipv6_mld_last_member_query_interval_cmd, + "no ipv6 mld last-member-query-interval [(1-65535)]", + NO_STR + IPV6_STR + IFACE_MLD_STR + IFACE_MLD_LAST_MEMBER_QUERY_INTERVAL_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_last_member_query_interval_cmd(vty); +} + +DEFPY (show_ipv6_pim_rp, + show_ipv6_pim_rp_cmd, + "show ipv6 pim [vrf NAME] rp-info [X:X::X:X/M$group] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM RP information\n" + "Multicast Group range\n" + JSON_STR) +{ + return pim_show_rp_helper(vrf, vty, group_str, (struct prefix *)group, + !!json); +} + +DEFPY (show_ipv6_pim_rp_vrf_all, + show_ipv6_pim_rp_vrf_all_cmd, + "show ipv6 pim vrf all rp-info [X:X::X:X/M$group] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM RP information\n" + "Multicast Group range\n" + JSON_STR) +{ + return pim_show_rp_vrf_all_helper(vty, group_str, + (struct prefix *)group, !!json); +} + +DEFPY (show_ipv6_pim_rpf, + show_ipv6_pim_rpf_cmd, + "show ipv6 pim [vrf NAME] rpf [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached source rpf information\n" + JSON_STR) +{ + return pim_show_rpf_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_rpf_vrf_all, + show_ipv6_pim_rpf_vrf_all_cmd, + "show ipv6 pim vrf all rpf [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached source rpf information\n" + JSON_STR) +{ + return pim_show_rpf_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ipv6_pim_secondary, + show_ipv6_pim_secondary_cmd, + "show ipv6 pim [vrf NAME] secondary", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor addresses\n") +{ + return pim_show_secondary_helper(vrf, vty); +} + +DEFPY (show_ipv6_pim_statistics, + show_ipv6_pim_statistics_cmd, + "show ipv6 pim [vrf NAME] statistics [interface WORD$word] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM statistics\n" + INTERFACE_STR + "PIM interface\n" + JSON_STR) +{ + return pim_show_statistics_helper(vrf, vty, word, !!json); +} + +DEFPY (show_ipv6_pim_upstream, + show_ipv6_pim_upstream_cmd, + "show ipv6 pim [vrf NAME] upstream [X:X::X:X$s_or_g [X:X::X:X$g]] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream information\n" + "The Source or Group\n" + "The Group\n" + JSON_STR) +{ + return pim_show_upstream_helper(vrf, vty, s_or_g, g, !!json); +} + +DEFPY (show_ipv6_pim_upstream_vrf_all, + show_ipv6_pim_upstream_vrf_all_cmd, + "show ipv6 pim vrf all upstream [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream information\n" + JSON_STR) +{ + return pim_show_upstream_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ipv6_pim_upstream_join_desired, + show_ipv6_pim_upstream_join_desired_cmd, + "show ipv6 pim [vrf NAME] upstream-join-desired [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream join-desired\n" + JSON_STR) +{ + return pim_show_upstream_join_desired_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_upstream_rpf, + show_ipv6_pim_upstream_rpf_cmd, + "show ipv6 pim [vrf NAME] upstream-rpf [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream source rpf\n" + JSON_STR) +{ + return pim_show_upstream_rpf_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_state, + show_ipv6_pim_state_cmd, + "show ipv6 pim [vrf NAME] state [X:X::X:X$s_or_g [X:X::X:X$g]] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM state information\n" + "Unicast or Multicast address\n" + "Multicast address\n" + JSON_STR) +{ + return pim_show_state_helper(vrf, vty, s_or_g_str, g_str, !!json); +} + +DEFPY (show_ipv6_pim_state_vrf_all, + show_ipv6_pim_state_vrf_all_cmd, + "show ipv6 pim vrf all state [X:X::X:X$s_or_g [X:X::X:X$g]] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM state information\n" + "Unicast or Multicast address\n" + "Multicast address\n" + JSON_STR) +{ + return pim_show_state_vrf_all_helper(vty, s_or_g_str, g_str, !!json); +} + +DEFPY (show_ipv6_pim_channel, + show_ipv6_pim_channel_cmd, + "show ipv6 pim [vrf NAME] channel [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM downstream channel info\n" + JSON_STR) +{ + return pim_show_channel_cmd_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_interface, + show_ipv6_pim_interface_cmd, + "show ipv6 pim [vrf NAME] interface [detail|WORD]$interface [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + return pim_show_interface_cmd_helper(vrf, vty, !!json, false, + interface); +} + +DEFPY (show_ipv6_pim_interface_vrf_all, + show_ipv6_pim_interface_vrf_all_cmd, + "show ipv6 pim vrf all interface [detail|WORD]$interface [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + return pim_show_interface_vrf_all_cmd_helper(vty, !!json, false, + interface); +} + +DEFPY (show_ipv6_pim_join, + show_ipv6_pim_join_cmd, + "show ipv6 pim [vrf NAME] join [X:X::X:X$s_or_g [X:X::X:X$g]] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface join information\n" + "The Source or Group\n" + "The Group\n" + JSON_STR) +{ + return pim_show_join_cmd_helper(vrf, vty, s_or_g, g, json); +} + +DEFPY (show_ipv6_pim_join_vrf_all, + show_ipv6_pim_join_vrf_all_cmd, + "show ipv6 pim vrf all join [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface join information\n" + JSON_STR) +{ + return pim_show_join_vrf_all_cmd_helper(vty, json); +} + +DEFPY (show_ipv6_pim_jp_agg, + show_ipv6_pim_jp_agg_cmd, + "show ipv6 pim [vrf NAME] jp-agg", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "join prune aggregation list\n") +{ + return pim_show_jp_agg_list_cmd_helper(vrf, vty); +} + +DEFPY (show_ipv6_pim_local_membership, + show_ipv6_pim_local_membership_cmd, + "show ipv6 pim [vrf NAME] local-membership [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface local-membership\n" + JSON_STR) +{ + return pim_show_membership_cmd_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_neighbor, + show_ipv6_pim_neighbor_cmd, + "show ipv6 pim [vrf NAME] neighbor [detail|WORD]$interface [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor information\n" + "Detailed output\n" + "Name of interface or neighbor\n" + JSON_STR) +{ + return pim_show_neighbors_cmd_helper(vrf, vty, json, interface); +} + +DEFPY (show_ipv6_pim_neighbor_vrf_all, + show_ipv6_pim_neighbor_vrf_all_cmd, + "show ipv6 pim vrf all neighbor [detail|WORD]$interface [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor information\n" + "Detailed output\n" + "Name of interface or neighbor\n" + JSON_STR) +{ + return pim_show_neighbors_vrf_all_cmd_helper(vty, json, interface); +} + +DEFPY (show_ipv6_pim_nexthop, + show_ipv6_pim_nexthop_cmd, + "show ipv6 pim [vrf NAME] nexthop [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached nexthop rpf information\n" + JSON_STR) +{ + return pim_show_nexthop_cmd_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_nexthop_lookup, + show_ipv6_pim_nexthop_lookup_cmd, + "show ipv6 pim [vrf NAME] nexthop-lookup X:X::X:X$source X:X::X:X$group", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached nexthop rpf lookup\n" + "Source/RP address\n" + "Multicast Group address\n") +{ + return pim_show_nexthop_lookup_cmd_helper(vrf, vty, source, group); +} + +DEFPY (show_ipv6_multicast, + show_ipv6_multicast_cmd, + "show ipv6 multicast [vrf NAME]", + SHOW_STR + IPV6_STR + "Multicast global information\n" + VRF_CMD_HELP_STR) +{ + return pim_show_multicast_helper(vrf, vty); +} + +DEFPY (show_ipv6_multicast_vrf_all, + show_ipv6_multicast_vrf_all_cmd, + "show ipv6 multicast vrf all", + SHOW_STR + IPV6_STR + "Multicast global information\n" + VRF_CMD_HELP_STR) +{ + return pim_show_multicast_vrf_all_helper(vty); +} + +DEFPY (show_ipv6_multicast_count, + show_ipv6_multicast_count_cmd, + "show ipv6 multicast count [vrf NAME] [json$json]", + SHOW_STR + IPV6_STR + "Multicast global information\n" + "Data packet count\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_multicast_count_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_multicast_count_vrf_all, + show_ipv6_multicast_count_vrf_all_cmd, + "show ipv6 multicast count vrf all [json$json]", + SHOW_STR + IPV6_STR + "Multicast global information\n" + "Data packet count\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_multicast_count_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ipv6_mroute, + show_ipv6_mroute_cmd, + "show ipv6 mroute [vrf NAME] [X:X::X:X$s_or_g [X:X::X:X$g]] [fill$fill] [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "The Source or Group\n" + "The Group\n" + "Fill in Assumed data\n" + JSON_STR) +{ + return pim_show_mroute_helper(vrf, vty, s_or_g, g, !!fill, !!json); +} + +DEFPY (show_ipv6_mroute_vrf_all, + show_ipv6_mroute_vrf_all_cmd, + "show ipv6 mroute vrf all [fill$fill] [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Fill in Assumed data\n" + JSON_STR) +{ + return pim_show_mroute_vrf_all_helper(vty, !!fill, !!json); +} + +DEFPY (show_ipv6_mroute_count, + show_ipv6_mroute_count_cmd, + "show ipv6 mroute [vrf NAME] count [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n" + JSON_STR) +{ + return pim_show_mroute_count_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_mroute_count_vrf_all, + show_ipv6_mroute_count_vrf_all_cmd, + "show ipv6 mroute vrf all count [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n" + JSON_STR) +{ + return pim_show_mroute_count_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ipv6_mroute_summary, + show_ipv6_mroute_summary_cmd, + "show ipv6 mroute [vrf NAME] summary [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n" + JSON_STR) +{ + return pim_show_mroute_summary_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_mroute_summary_vrf_all, + show_ipv6_mroute_summary_vrf_all_cmd, + "show ipv6 mroute vrf all summary [json$json]", + SHOW_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n" + JSON_STR) +{ + return pim_show_mroute_summary_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ipv6_pim_interface_traffic, + show_ipv6_pim_interface_traffic_cmd, + "show ipv6 pim [vrf NAME] interface traffic [WORD$if_name] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface information\n" + "Protocol Packet counters\n" + "Interface name\n" + JSON_STR) +{ + return pim_show_interface_traffic_helper(vrf, if_name, vty, !!json); +} + +DEFPY (show_ipv6_pim_bsr, + show_ipv6_pim_bsr_cmd, + "show ipv6 pim bsr [vrf NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + "boot-strap router information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_bsr_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_bsm_db, + show_ipv6_pim_bsm_db_cmd, + "show ipv6 pim bsm-database [vrf NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + "PIM cached bsm packets information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_bsm_db_helper(vrf, vty, !!json); +} + +DEFPY (show_ipv6_pim_bsrp, + show_ipv6_pim_bsrp_cmd, + "show ipv6 pim bsrp-info [vrf NAME] [json$json]", + SHOW_STR + IPV6_STR + PIM_STR + "PIM cached group-rp mappings information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_group_rp_mappings_info_helper(vrf, vty, !!json); +} + +DEFPY (clear_ipv6_pim_statistics, + clear_ipv6_pim_statistics_cmd, + "clear ipv6 pim statistics [vrf NAME]$name", + CLEAR_STR + IPV6_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM statistics\n") +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + clear_pim_statistics(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ipv6_pim_interface_traffic, + clear_ipv6_pim_interface_traffic_cmd, + "clear ipv6 pim [vrf NAME] interface traffic", + CLEAR_STR + IPV6_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM interfaces\n" + "Reset Protocol Packet counters\n") +{ + return clear_pim_interface_traffic(vrf, vty); +} + +DEFPY (clear_ipv6_mroute, + clear_ipv6_mroute_cmd, + "clear ipv6 mroute [vrf NAME]$name", + CLEAR_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR) +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + clear_mroute(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ipv6_pim_oil, + clear_ipv6_pim_oil_cmd, + "clear ipv6 pim [vrf NAME]$name oil", + CLEAR_STR + IPV6_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Rescan PIMv6 OIL (output interface list)\n") +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + pim_scan_oil(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ipv6_mroute_count, + clear_ipv6_mroute_count_cmd, + "clear ipv6 mroute [vrf NAME]$name count", + CLEAR_STR + IPV6_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n") +{ + return clear_ip_mroute_count_command(vty, name); +} + +DEFPY (clear_ipv6_pim_interfaces, + clear_ipv6_pim_interfaces_cmd, + "clear ipv6 pim [vrf NAME] interfaces", + CLEAR_STR + IPV6_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM interfaces\n") +{ + struct vrf *v = pim_cmd_lookup(vty, vrf); + + if (!v) + return CMD_WARNING; + + clear_pim_interfaces(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ipv6_pim_bsr_db, + clear_ipv6_pim_bsr_db_cmd, + "clear ipv6 pim [vrf NAME] bsr-data", + CLEAR_STR + IPV6_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset pim bsr data\n") +{ + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + if (!v) + return CMD_WARNING; + + pim_bsm_clear(v->info); + + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6, + debug_pimv6_cmd, + "[no] debug pimv6", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR) +{ + if (!no) + return pim_debug_pim_cmd(); + else + return pim_no_debug_pim_cmd(); +} + +DEFPY (debug_pimv6_nht, + debug_pimv6_nht_cmd, + "[no] debug pimv6 nht", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + "Nexthop Tracking\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_NHT; + else + PIM_DONT_DEBUG_PIM_NHT; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_nht_det, + debug_pimv6_nht_det_cmd, + "[no] debug pimv6 nht detail", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + "Nexthop Tracking\n" + "Detailed Information\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_NHT_DETAIL; + else + PIM_DONT_DEBUG_PIM_NHT_DETAIL; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_events, + debug_pimv6_events_cmd, + "[no] debug pimv6 events", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_EVENTS_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_EVENTS; + else + PIM_DONT_DEBUG_PIM_EVENTS; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_packets, + debug_pimv6_packets_cmd, + "[no] debug pimv6 packets []", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_PACKETS_STR + DEBUG_PIMV6_HELLO_PACKETS_STR + DEBUG_PIMV6_J_P_PACKETS_STR + DEBUG_PIMV6_PIM_REG_PACKETS_STR) +{ + if (!no) + return pim_debug_pim_packets_cmd(hello, joins, registers, vty); + else + return pim_no_debug_pim_packets_cmd(hello, joins, registers, + vty); +} + +DEFPY (debug_pimv6_packetdump_send, + debug_pimv6_packetdump_send_cmd, + "[no] debug pimv6 packet-dump send", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_PACKETDUMP_STR + DEBUG_PIMV6_PACKETDUMP_SEND_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_PACKETDUMP_SEND; + else + PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_packetdump_recv, + debug_pimv6_packetdump_recv_cmd, + "[no] debug pimv6 packet-dump receive", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_PACKETDUMP_STR + DEBUG_PIMV6_PACKETDUMP_RECV_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_PACKETDUMP_RECV; + else + PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_trace, + debug_pimv6_trace_cmd, + "[no] debug pimv6 trace", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_TRACE_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_TRACE; + else + PIM_DONT_DEBUG_PIM_TRACE; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_trace_detail, + debug_pimv6_trace_detail_cmd, + "[no] debug pimv6 trace detail", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_TRACE_STR + "Detailed Information\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_TRACE_DETAIL; + else + PIM_DONT_DEBUG_PIM_TRACE_DETAIL; + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_zebra, + debug_pimv6_zebra_cmd, + "[no] debug pimv6 zebra", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_ZEBRA_STR) +{ + if (!no) + PIM_DO_DEBUG_ZEBRA; + else + PIM_DONT_DEBUG_ZEBRA; + return CMD_SUCCESS; +} + +DEFPY (debug_mroute6, + debug_mroute6_cmd, + "[no] debug mroute6", + NO_STR + DEBUG_STR + DEBUG_MROUTE6_STR) +{ + if (!no) + PIM_DO_DEBUG_MROUTE; + else + PIM_DONT_DEBUG_MROUTE; + + return CMD_SUCCESS; +} + +DEFPY (debug_mroute6_detail, + debug_mroute6_detail_cmd, + "[no] debug mroute6 detail", + NO_STR + DEBUG_STR + DEBUG_MROUTE6_STR + "detailed\n") +{ + if (!no) + PIM_DO_DEBUG_MROUTE_DETAIL; + else + PIM_DONT_DEBUG_MROUTE_DETAIL; + + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_pimv6, + show_debugging_pimv6_cmd, + "show debugging [pimv6]", + SHOW_STR + DEBUG_STR + "PIMv6 Information\n") +{ + vty_out(vty, "PIMv6 debugging status\n"); + + pim_debug_config_write(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFPY (debug_mld, + debug_mld_cmd, + "[no] debug mld", + NO_STR + DEBUG_STR + DEBUG_MLD_STR) +{ + if (!no) { + PIM_DO_DEBUG_GM_EVENTS; + PIM_DO_DEBUG_GM_PACKETS; + PIM_DO_DEBUG_GM_TRACE; + } else { + PIM_DONT_DEBUG_GM_EVENTS; + PIM_DONT_DEBUG_GM_PACKETS; + PIM_DONT_DEBUG_GM_TRACE; + } + + return CMD_SUCCESS; +} + +DEFPY (debug_mld_events, + debug_mld_events_cmd, + "[no] debug mld events", + NO_STR + DEBUG_STR + DEBUG_MLD_STR + DEBUG_MLD_EVENTS_STR) +{ + if (!no) + PIM_DO_DEBUG_GM_EVENTS; + else + PIM_DONT_DEBUG_GM_EVENTS; + + return CMD_SUCCESS; +} + +DEFPY (debug_mld_packets, + debug_mld_packets_cmd, + "[no] debug mld packets", + NO_STR + DEBUG_STR + DEBUG_MLD_STR + DEBUG_MLD_PACKETS_STR) +{ + if (!no) + PIM_DO_DEBUG_GM_PACKETS; + else + PIM_DONT_DEBUG_GM_PACKETS; + + return CMD_SUCCESS; +} + +DEFPY (debug_mld_trace, + debug_mld_trace_cmd, + "[no] debug mld trace", + NO_STR + DEBUG_STR + DEBUG_MLD_STR + DEBUG_MLD_TRACE_STR) +{ + if (!no) + PIM_DO_DEBUG_GM_TRACE; + else + PIM_DONT_DEBUG_GM_TRACE; + + return CMD_SUCCESS; +} + +DEFPY (debug_mld_trace_detail, + debug_mld_trace_detail_cmd, + "[no] debug mld trace detail", + NO_STR + DEBUG_STR + DEBUG_MLD_STR + DEBUG_MLD_TRACE_STR + "detailed\n") +{ + if (!no) + PIM_DO_DEBUG_GM_TRACE_DETAIL; + else + PIM_DONT_DEBUG_GM_TRACE_DETAIL; + + return CMD_SUCCESS; +} + +DEFPY (debug_pimv6_bsm, + debug_pimv6_bsm_cmd, + "[no] debug pimv6 bsm", + NO_STR + DEBUG_STR + DEBUG_PIMV6_STR + DEBUG_PIMV6_BSM_STR) +{ + if (!no) + PIM_DO_DEBUG_BSM; + else + PIM_DONT_DEBUG_BSM; + + return CMD_SUCCESS; +} + +void pim_cmd_init(void) +{ + if_cmd_init(pim_interface_config_write); + + install_node(&debug_node); + + install_element(CONFIG_NODE, &ipv6_pim_joinprune_time_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_joinprune_time_cmd); + install_element(CONFIG_NODE, &ipv6_pim_spt_switchover_infinity_cmd); + install_element(CONFIG_NODE, &ipv6_pim_spt_switchover_infinity_plist_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_spt_switchover_infinity_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_spt_switchover_infinity_plist_cmd); + install_element(CONFIG_NODE, &ipv6_pim_packets_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_packets_cmd); + install_element(CONFIG_NODE, &ipv6_pim_keep_alive_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_keep_alive_cmd); + install_element(CONFIG_NODE, &ipv6_pim_rp_keep_alive_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_rp_keep_alive_cmd); + install_element(CONFIG_NODE, &ipv6_pim_register_suppress_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_register_suppress_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_drprio_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_drprio_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_hello_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_hello_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_activeactive_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_ssm_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_ssm_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_sm_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_sm_cmd); + install_element(INTERFACE_NODE, + &interface_ipv6_pim_boundary_oil_cmd); + install_element(INTERFACE_NODE, + &interface_no_ipv6_pim_boundary_oil_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mroute_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_mroute_cmd); + /* Install BSM command */ + install_element(INTERFACE_NODE, &ipv6_pim_bsm_cmd); + install_element(INTERFACE_NODE, &no_ipv6_pim_bsm_cmd); + install_element(INTERFACE_NODE, &ipv6_pim_ucast_bsm_cmd); + install_element(INTERFACE_NODE, &no_ipv6_pim_ucast_bsm_cmd); + install_element(CONFIG_NODE, &ipv6_pim_rp_cmd); + install_element(VRF_NODE, &ipv6_pim_rp_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_rp_cmd); + install_element(VRF_NODE, &no_ipv6_pim_rp_cmd); + install_element(CONFIG_NODE, &ipv6_pim_rp_prefix_list_cmd); + install_element(VRF_NODE, &ipv6_pim_rp_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ipv6_pim_rp_prefix_list_cmd); + install_element(VRF_NODE, &no_ipv6_pim_rp_prefix_list_cmd); + install_element(CONFIG_NODE, &ipv6_ssmpingd_cmd); + install_element(VRF_NODE, &ipv6_ssmpingd_cmd); + install_element(CONFIG_NODE, &no_ipv6_ssmpingd_cmd); + install_element(VRF_NODE, &no_ipv6_ssmpingd_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mld_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_mld_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mld_join_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_mld_join_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mld_version_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_mld_version_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mld_query_interval_cmd); + install_element(INTERFACE_NODE, + &interface_no_ipv6_mld_query_interval_cmd); + install_element(CONFIG_NODE, &ipv6_mld_group_watermark_cmd); + install_element(VRF_NODE, &ipv6_mld_group_watermark_cmd); + install_element(CONFIG_NODE, &no_ipv6_mld_group_watermark_cmd); + install_element(VRF_NODE, &no_ipv6_mld_group_watermark_cmd); + install_element(INTERFACE_NODE, + &interface_ipv6_mld_query_max_response_time_cmd); + install_element(INTERFACE_NODE, + &interface_no_ipv6_mld_query_max_response_time_cmd); + install_element(INTERFACE_NODE, + &interface_ipv6_mld_last_member_query_count_cmd); + install_element(INTERFACE_NODE, + &interface_no_ipv6_mld_last_member_query_count_cmd); + install_element(INTERFACE_NODE, + &interface_ipv6_mld_last_member_query_interval_cmd); + install_element(INTERFACE_NODE, + &interface_no_ipv6_mld_last_member_query_interval_cmd); + + install_element(VIEW_NODE, &show_ipv6_pim_rp_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_rp_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_rpf_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_rpf_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_secondary_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_statistics_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_upstream_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_upstream_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_upstream_join_desired_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_upstream_rpf_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_state_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_state_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_channel_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_interface_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_interface_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_join_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_join_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_jp_agg_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_local_membership_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_neighbor_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_neighbor_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_nexthop_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_nexthop_lookup_cmd); + install_element(VIEW_NODE, &show_ipv6_multicast_cmd); + install_element(VIEW_NODE, &show_ipv6_multicast_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_multicast_count_cmd); + install_element(VIEW_NODE, &show_ipv6_multicast_count_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_count_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_count_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_summary_cmd); + install_element(VIEW_NODE, &show_ipv6_mroute_summary_vrf_all_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_interface_traffic_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsr_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsm_db_cmd); + install_element(VIEW_NODE, &show_ipv6_pim_bsrp_cmd); + install_element(ENABLE_NODE, &clear_ipv6_pim_statistics_cmd); + install_element(ENABLE_NODE, &clear_ipv6_mroute_cmd); + install_element(ENABLE_NODE, &clear_ipv6_pim_oil_cmd); + install_element(ENABLE_NODE, &clear_ipv6_mroute_count_cmd); + install_element(ENABLE_NODE, &clear_ipv6_pim_bsr_db_cmd); + install_element(ENABLE_NODE, &clear_ipv6_pim_interfaces_cmd); + install_element(ENABLE_NODE, &clear_ipv6_pim_interface_traffic_cmd); + + install_element(ENABLE_NODE, &show_debugging_pimv6_cmd); + + install_element(ENABLE_NODE, &debug_pimv6_cmd); + install_element(ENABLE_NODE, &debug_pimv6_nht_cmd); + install_element(ENABLE_NODE, &debug_pimv6_nht_det_cmd); + install_element(ENABLE_NODE, &debug_pimv6_events_cmd); + install_element(ENABLE_NODE, &debug_pimv6_packets_cmd); + install_element(ENABLE_NODE, &debug_pimv6_packetdump_send_cmd); + install_element(ENABLE_NODE, &debug_pimv6_packetdump_recv_cmd); + install_element(ENABLE_NODE, &debug_pimv6_trace_cmd); + install_element(ENABLE_NODE, &debug_pimv6_trace_detail_cmd); + install_element(ENABLE_NODE, &debug_pimv6_zebra_cmd); + install_element(ENABLE_NODE, &debug_mroute6_cmd); + install_element(ENABLE_NODE, &debug_mroute6_detail_cmd); + install_element(ENABLE_NODE, &debug_mld_cmd); + install_element(ENABLE_NODE, &debug_mld_events_cmd); + install_element(ENABLE_NODE, &debug_mld_packets_cmd); + install_element(ENABLE_NODE, &debug_mld_trace_cmd); + install_element(ENABLE_NODE, &debug_mld_trace_detail_cmd); + install_element(ENABLE_NODE, &debug_pimv6_bsm_cmd); + + install_element(CONFIG_NODE, &debug_pimv6_cmd); + install_element(CONFIG_NODE, &debug_pimv6_nht_cmd); + install_element(CONFIG_NODE, &debug_pimv6_nht_det_cmd); + install_element(CONFIG_NODE, &debug_pimv6_events_cmd); + install_element(CONFIG_NODE, &debug_pimv6_packets_cmd); + install_element(CONFIG_NODE, &debug_pimv6_packetdump_send_cmd); + install_element(CONFIG_NODE, &debug_pimv6_packetdump_recv_cmd); + install_element(CONFIG_NODE, &debug_pimv6_trace_cmd); + install_element(CONFIG_NODE, &debug_pimv6_trace_detail_cmd); + install_element(CONFIG_NODE, &debug_pimv6_zebra_cmd); + install_element(CONFIG_NODE, &debug_mroute6_cmd); + install_element(CONFIG_NODE, &debug_mroute6_detail_cmd); + install_element(CONFIG_NODE, &debug_mld_cmd); + install_element(CONFIG_NODE, &debug_mld_events_cmd); + install_element(CONFIG_NODE, &debug_mld_packets_cmd); + install_element(CONFIG_NODE, &debug_mld_trace_cmd); + install_element(CONFIG_NODE, &debug_mld_trace_detail_cmd); + install_element(CONFIG_NODE, &debug_pimv6_bsm_cmd); +} diff --git a/pimd/pim6_cmd.h b/pimd/pim6_cmd.h new file mode 100644 index 0000000..201d8d6 --- /dev/null +++ b/pimd/pim6_cmd.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for IPv6 FRR + * Copyright (C) 2022 Vmware, Inc. + * Mobashshera Rasool + */ +#ifndef PIM6_CMD_H +#define PIM6_CMD_H + +#define PIM_STR "PIM information\n" +#define MLD_STR "MLD information\n" +#define MLD_GROUP_STR "MLD groups information\n" +#define MLD_SOURCE_STR "MLD sources information\n" +#define IFACE_MLD_STR "Enable MLD operation\n" +#define IFACE_MLD_QUERY_INTERVAL_STR "MLD host query interval\n" +#define IFACE_MLD_QUERY_MAX_RESPONSE_TIME_STR \ + "MLD max query response value (seconds)\n" +#define IFACE_MLD_QUERY_MAX_RESPONSE_TIME_DSEC_STR \ + "MLD max query response value (deciseconds)\n" +#define IFACE_MLD_LAST_MEMBER_QUERY_INTERVAL_STR \ + "MLD last member query interval\n" +#define IFACE_MLD_LAST_MEMBER_QUERY_COUNT_STR "MLD last member query count\n" +#define IFACE_PIM_STR "Enable PIM SSM operation\n" +#define IFACE_PIM_SM_STR "Enable PIM SM operation\n" +#define IFACE_PIM_HELLO_STR "Hello Interval\n" +#define IFACE_PIM_HELLO_TIME_STR "Time in seconds for Hello Interval\n" +#define IFACE_PIM_HELLO_HOLD_STR "Time in seconds for Hold Interval\n" +#define MROUTE_STR "IP multicast routing table\n" +#define CLEAR_IP_PIM_STR "PIM clear commands\n" +#define DEBUG_MLD_STR "MLD protocol activity\n" +#define DEBUG_MLD_EVENTS_STR "MLD protocol events\n" +#define DEBUG_MLD_PACKETS_STR "MLD protocol packets\n" +#define DEBUG_MLD_TRACE_STR "MLD internal daemon activity\n" +#define CONF_SSMPINGD_STR "Enable ssmpingd operation\n" +#define DEBUG_PIMV6_STR "PIMv6 protocol activity\n" +#define DEBUG_PIMV6_EVENTS_STR "PIMv6 protocol events\n" +#define DEBUG_PIMV6_PACKETS_STR "PIMv6 protocol packets\n" +#define DEBUG_PIMV6_HELLO_PACKETS_STR "PIMv6 Hello protocol packets\n" +#define DEBUG_PIMV6_J_P_PACKETS_STR "PIMv6 Join/Prune protocol packets\n" +#define DEBUG_PIMV6_PIM_REG_PACKETS_STR \ + "PIMv6 Register/Reg-Stop protocol packets\n" +#define DEBUG_PIMV6_PACKETDUMP_STR "PIMv6 packet dump\n" +#define DEBUG_PIMV6_PACKETDUMP_SEND_STR "Dump sent packets\n" +#define DEBUG_PIMV6_PACKETDUMP_RECV_STR "Dump received packets\n" +#define DEBUG_PIMV6_TRACE_STR "PIMv6 internal daemon activity\n" +#define DEBUG_PIMV6_ZEBRA_STR "ZEBRA protocol activity\n" +#define DEBUG_MROUTE6_STR "PIMv6 interaction with kernel MFC cache\n" +#define DEBUG_PIMV6_BSM_STR "BSR message processing activity\n" + +void pim_cmd_init(void); + +#endif /* PIM6_CMD_H */ diff --git a/pimd/pim6_main.c b/pimd/pim6_main.c new file mode 100644 index 0000000..5ce6985 --- /dev/null +++ b/pimd/pim6_main.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIMv6 main() + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + * Copyright (C) 2008 Everton da Silva Marques (pim_main.c) + */ + +#include + +#include "lib/vrf.h" +#include "lib/filter.h" +#include "lib/plist.h" +#include "lib/routemap.h" +#include "lib/routing_nb.h" + +#include "lib/privs.h" +#include "lib/sigevent.h" +#include "lib/libfrr.h" +#include "lib/version.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_errors.h" +#include "pim_iface.h" +#include "pim_zebra.h" +#include "pim_nb.h" +#include "pim6_cmd.h" +#include "pim6_mld.h" +#include "pim_zlookup.h" + +zebra_capabilities_t _caps_p[] = { + ZCAP_SYS_ADMIN, + ZCAP_NET_ADMIN, + ZCAP_NET_RAW, + ZCAP_BIND, +}; + +/* pimd privileges to run with */ +struct zebra_privs_t pimd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +static void pim6_terminate(void); + +static void pim6_sighup(void) +{ + zlog_info("SIGHUP received, ignoring"); +} + +static void pim6_sigint(void) +{ + zlog_notice("Terminating on signal SIGINT"); + pim6_terminate(); + exit(1); +} + +static void pim6_sigterm(void) +{ + zlog_notice("Terminating on signal SIGTERM"); + pim6_terminate(); + exit(1); +} + +static void pim6_sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t pim6d_signals[] = { + { + .signal = SIGHUP, + .handler = &pim6_sighup, + }, + { + .signal = SIGUSR1, + .handler = &pim6_sigusr1, + }, + { + .signal = SIGINT, + .handler = &pim6_sigint, + }, + { + .signal = SIGTERM, + .handler = &pim6_sigterm, + }, +}; + +static const struct frr_yang_module_info *const pim6d_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &frr_routing_info, + &frr_pim_info, + &frr_pim_rp_info, + &frr_gmp_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(pim6d, PIM6, + .vty_port = PIM6D_VTY_PORT, + .proghelp = "Protocol Independent Multicast (RFC7761) for IPv6", + + .signals = pim6d_signals, + .n_signals = array_size(pim6d_signals), + + .privs = &pimd_privs, + + .yang_modules = pim6d_yang_modules, + .n_yang_modules = array_size(pim6d_yang_modules), +); +/* clang-format on */ + +int main(int argc, char **argv, char **envp) +{ + static struct option longopts[] = { + {}, + }; + + frr_preinit(&pim6d_di, argc, argv); + frr_opt_add("", longopts, ""); + + /* this while just reads the options */ + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + pim_router_init(); + + access_list_init(); + prefix_list_init(); + + /* + * Initializations + */ + pim_error_init(); + pim_vrf_init(); +#if 0 + prefix_list_add_hook(pim_prefix_list_update); + prefix_list_delete_hook(pim_prefix_list_update); + + pim_route_map_init(); +#endif + pim_init(); + /* + * Initialize zclient "update" and "lookup" sockets + */ + pim_iface_init(); + + gm_cli_init(); + + pim_zebra_init(); +#if 0 + pim_bfd_init(); + pim_mlag_init(); +#endif + + hook_register(routing_conf_event, + routing_control_plane_protocols_name_validate); + + routing_control_plane_protocols_register_vrf_dependency(); + + frr_config_fork(); + frr_run(router->master); + + /* never reached */ + return 0; +} + +static void pim6_terminate(void) +{ + struct zclient *zclient; + + pim_vrf_terminate(); + pim_router_terminate(); + + prefix_list_reset(); + access_list_reset(); + + zclient = pim_zebra_zclient_get(); + if (zclient) { + zclient_stop(zclient); + zclient_free(zclient); + } + + zclient_lookup_free(); + frr_fini(); +} diff --git a/pimd/pim6_mld.c b/pimd/pim6_mld.c new file mode 100644 index 0000000..a39d182 --- /dev/null +++ b/pimd/pim6_mld.c @@ -0,0 +1,3233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIMv6 MLD querier + * Copyright (C) 2021-2022 David Lamparter for NetDEF, Inc. + */ + +/* + * keep pim6_mld.h open when working on this code. Most data structures are + * commented in the header. + * + * IPv4 support is pre-planned but hasn't been tackled yet. It is intended + * that this code will replace the old IGMP querier at some point. + */ + +#include +#include +#include + +#include "lib/memory.h" +#include "lib/jhash.h" +#include "lib/prefix.h" +#include "lib/checksum.h" +#include "lib/frrevent.h" +#include "termtable.h" + +#include "pimd/pim6_mld.h" +#include "pimd/pim6_mld_protocol.h" +#include "pimd/pim_memory.h" +#include "pimd/pim_instance.h" +#include "pimd/pim_iface.h" +#include "pimd/pim6_cmd.h" +#include "pimd/pim_cmd_common.h" +#include "pimd/pim_util.h" +#include "pimd/pim_tib.h" +#include "pimd/pimd.h" + +#ifndef IPV6_MULTICAST_ALL +#define IPV6_MULTICAST_ALL 29 +#endif + +DEFINE_MTYPE_STATIC(PIMD, GM_IFACE, "MLD interface"); +DEFINE_MTYPE_STATIC(PIMD, GM_PACKET, "MLD packet"); +DEFINE_MTYPE_STATIC(PIMD, GM_SUBSCRIBER, "MLD subscriber"); +DEFINE_MTYPE_STATIC(PIMD, GM_STATE, "MLD subscription state"); +DEFINE_MTYPE_STATIC(PIMD, GM_SG, "MLD (S,G)"); +DEFINE_MTYPE_STATIC(PIMD, GM_GRP_PENDING, "MLD group query state"); +DEFINE_MTYPE_STATIC(PIMD, GM_GSQ_PENDING, "MLD group/source query aggregate"); + +static void gm_t_query(struct event *t); +static void gm_trigger_specific(struct gm_sg *sg); +static void gm_sg_timer_start(struct gm_if *gm_ifp, struct gm_sg *sg, + struct timeval expire_wait); + +/* shorthand for log messages */ +#define log_ifp(msg) \ + "[MLD %s:%s] " msg, gm_ifp->ifp->vrf->name, gm_ifp->ifp->name +#define log_pkt_src(msg) \ + "[MLD %s:%s %pI6] " msg, gm_ifp->ifp->vrf->name, gm_ifp->ifp->name, \ + &pkt_src->sin6_addr +#define log_sg(sg, msg) \ + "[MLD %s:%s %pSG] " msg, sg->iface->ifp->vrf->name, \ + sg->iface->ifp->name, &sg->sgaddr + +/* clang-format off */ +#if PIM_IPV == 6 +static const pim_addr gm_all_hosts = { + .s6_addr = { + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, +}; +static const pim_addr gm_all_routers = { + .s6_addr = { + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, + }, +}; +/* MLDv1 does not allow subscriber tracking due to report suppression + * hence, the source address is replaced with ffff:...:ffff + */ +static const pim_addr gm_dummy_untracked = { + .s6_addr = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + }, +}; +#else +/* 224.0.0.1 */ +static const pim_addr gm_all_hosts = { .s_addr = htonl(0xe0000001), }; +/* 224.0.0.22 */ +static const pim_addr gm_all_routers = { .s_addr = htonl(0xe0000016), }; +static const pim_addr gm_dummy_untracked = { .s_addr = 0xffffffff, }; +#endif +/* clang-format on */ + +#define IPV6_MULTICAST_SCOPE_LINK 2 + +static inline uint8_t in6_multicast_scope(const pim_addr *addr) +{ + return addr->s6_addr[1] & 0xf; +} + +bool in6_multicast_nofwd(const pim_addr *addr) +{ + return in6_multicast_scope(addr) <= IPV6_MULTICAST_SCOPE_LINK; +} + +/* + * (S,G) -> subscriber,(S,G) + */ + +static int gm_packet_sg_cmp(const struct gm_packet_sg *a, + const struct gm_packet_sg *b) +{ + const struct gm_packet_state *s_a, *s_b; + + s_a = gm_packet_sg2state(a); + s_b = gm_packet_sg2state(b); + return IPV6_ADDR_CMP(&s_a->subscriber->addr, &s_b->subscriber->addr); +} + +DECLARE_RBTREE_UNIQ(gm_packet_sg_subs, struct gm_packet_sg, subs_itm, + gm_packet_sg_cmp); + +static struct gm_packet_sg *gm_packet_sg_find(struct gm_sg *sg, + enum gm_sub_sense sense, + struct gm_subscriber *sub) +{ + struct { + struct gm_packet_state hdr; + struct gm_packet_sg item; + } ref = { + /* clang-format off */ + .hdr = { + .subscriber = sub, + }, + .item = { + .offset = 0, + }, + /* clang-format on */ + }; + + return gm_packet_sg_subs_find(&sg->subs[sense], &ref.item); +} + +/* + * interface -> (*,G),pending + */ + +static int gm_grp_pending_cmp(const struct gm_grp_pending *a, + const struct gm_grp_pending *b) +{ + return IPV6_ADDR_CMP(&a->grp, &b->grp); +} + +DECLARE_RBTREE_UNIQ(gm_grp_pends, struct gm_grp_pending, itm, + gm_grp_pending_cmp); + +/* + * interface -> ([S1,S2,...],G),pending + */ + +static int gm_gsq_pending_cmp(const struct gm_gsq_pending *a, + const struct gm_gsq_pending *b) +{ + if (a->s_bit != b->s_bit) + return numcmp(a->s_bit, b->s_bit); + + return IPV6_ADDR_CMP(&a->grp, &b->grp); +} + +static uint32_t gm_gsq_pending_hash(const struct gm_gsq_pending *a) +{ + uint32_t seed = a->s_bit ? 0x68f0eb5e : 0x156b7f19; + + return jhash(&a->grp, sizeof(a->grp), seed); +} + +DECLARE_HASH(gm_gsq_pends, struct gm_gsq_pending, itm, gm_gsq_pending_cmp, + gm_gsq_pending_hash); + +/* + * interface -> (S,G) + */ + +int gm_sg_cmp(const struct gm_sg *a, const struct gm_sg *b) +{ + return pim_sgaddr_cmp(a->sgaddr, b->sgaddr); +} + +static struct gm_sg *gm_sg_find(struct gm_if *gm_ifp, pim_addr grp, + pim_addr src) +{ + struct gm_sg ref = {}; + + ref.sgaddr.grp = grp; + ref.sgaddr.src = src; + return gm_sgs_find(gm_ifp->sgs, &ref); +} + +static struct gm_sg *gm_sg_make(struct gm_if *gm_ifp, pim_addr grp, + pim_addr src) +{ + struct gm_sg *ret, *prev; + + ret = XCALLOC(MTYPE_GM_SG, sizeof(*ret)); + ret->sgaddr.grp = grp; + ret->sgaddr.src = src; + ret->iface = gm_ifp; + prev = gm_sgs_add(gm_ifp->sgs, ret); + + if (prev) { + XFREE(MTYPE_GM_SG, ret); + ret = prev; + } else { + monotime(&ret->created); + gm_packet_sg_subs_init(ret->subs_positive); + gm_packet_sg_subs_init(ret->subs_negative); + } + return ret; +} + +/* + * interface -> packets, sorted by expiry (because add_tail insert order) + */ + +DECLARE_DLIST(gm_packet_expires, struct gm_packet_state, exp_itm); + +/* + * subscriber -> packets + */ + +DECLARE_DLIST(gm_packets, struct gm_packet_state, pkt_itm); + +/* + * interface -> subscriber + */ + +static int gm_subscriber_cmp(const struct gm_subscriber *a, + const struct gm_subscriber *b) +{ + return IPV6_ADDR_CMP(&a->addr, &b->addr); +} + +static uint32_t gm_subscriber_hash(const struct gm_subscriber *a) +{ + return jhash(&a->addr, sizeof(a->addr), 0xd0e94ad4); +} + +DECLARE_HASH(gm_subscribers, struct gm_subscriber, itm, gm_subscriber_cmp, + gm_subscriber_hash); + +static struct gm_subscriber *gm_subscriber_findref(struct gm_if *gm_ifp, + pim_addr addr) +{ + struct gm_subscriber ref = {}, *ret; + + ref.addr = addr; + ret = gm_subscribers_find(gm_ifp->subscribers, &ref); + if (ret) + ret->refcount++; + return ret; +} + +static struct gm_subscriber *gm_subscriber_get(struct gm_if *gm_ifp, + pim_addr addr) +{ + struct gm_subscriber ref = {}, *ret; + + ref.addr = addr; + ret = gm_subscribers_find(gm_ifp->subscribers, &ref); + + if (!ret) { + ret = XCALLOC(MTYPE_GM_SUBSCRIBER, sizeof(*ret)); + ret->iface = gm_ifp; + ret->addr = addr; + ret->refcount = 1; + monotime(&ret->created); + gm_packets_init(ret->packets); + + gm_subscribers_add(gm_ifp->subscribers, ret); + } + return ret; +} + +static void gm_subscriber_drop(struct gm_subscriber **subp) +{ + struct gm_subscriber *sub = *subp; + struct gm_if *gm_ifp; + + if (!sub) + return; + gm_ifp = sub->iface; + + *subp = NULL; + sub->refcount--; + + if (sub->refcount) + return; + + gm_subscribers_del(gm_ifp->subscribers, sub); + XFREE(MTYPE_GM_SUBSCRIBER, sub); +} + +/****************************************************************************/ + +/* bundle query timer values for combined v1/v2 handling */ +struct gm_query_timers { + unsigned int qrv; + unsigned int max_resp_ms; + unsigned int qqic_ms; + + struct timeval fuzz; + struct timeval expire_wait; +}; + +static void gm_expiry_calc(struct gm_query_timers *timers) +{ + unsigned int expire = + (timers->qrv - 1) * timers->qqic_ms + timers->max_resp_ms; + ldiv_t exp_div = ldiv(expire, 1000); + + timers->expire_wait.tv_sec = exp_div.quot; + timers->expire_wait.tv_usec = exp_div.rem * 1000; + timeradd(&timers->expire_wait, &timers->fuzz, &timers->expire_wait); +} + +static void gm_sg_free(struct gm_sg *sg) +{ + /* t_sg_expiry is handled before this is reached */ + EVENT_OFF(sg->t_sg_query); + gm_packet_sg_subs_fini(sg->subs_negative); + gm_packet_sg_subs_fini(sg->subs_positive); + XFREE(MTYPE_GM_SG, sg); +} + +/* clang-format off */ +static const char *const gm_states[] = { + [GM_SG_NOINFO] = "NOINFO", + [GM_SG_JOIN] = "JOIN", + [GM_SG_JOIN_EXPIRING] = "JOIN_EXPIRING", + [GM_SG_PRUNE] = "PRUNE", + [GM_SG_NOPRUNE] = "NOPRUNE", + [GM_SG_NOPRUNE_EXPIRING] = "NOPRUNE_EXPIRING", +}; +/* clang-format on */ + +/* TODO: S,G entries in EXCLUDE (i.e. prune) unsupported" */ + +/* tib_sg_gm_prune() below is an "un-join", it doesn't prune S,G when *,G is + * joined. Whether we actually want/need to support this is a separate + * question - it is almost never used. In fact this is exactly what RFC5790 + * ("lightweight" MLDv2) does: it removes S,G EXCLUDE support. + */ + +static void gm_sg_update(struct gm_sg *sg, bool has_expired) +{ + struct gm_if *gm_ifp = sg->iface; + struct pim_interface *pim_ifp = gm_ifp->ifp->info; + enum gm_sg_state prev, desired; + bool new_join; + struct gm_sg *grp = NULL; + + if (!pim_addr_is_any(sg->sgaddr.src)) + grp = gm_sg_find(gm_ifp, sg->sgaddr.grp, PIMADDR_ANY); + else + assert(sg->state != GM_SG_PRUNE); + + if (gm_packet_sg_subs_count(sg->subs_positive)) { + desired = GM_SG_JOIN; + assert(!sg->t_sg_expire); + } else if ((sg->state == GM_SG_JOIN || + sg->state == GM_SG_JOIN_EXPIRING) && + !has_expired) + desired = GM_SG_JOIN_EXPIRING; + else if (!grp || !gm_packet_sg_subs_count(grp->subs_positive)) + desired = GM_SG_NOINFO; + else if (gm_packet_sg_subs_count(grp->subs_positive) == + gm_packet_sg_subs_count(sg->subs_negative)) { + if ((sg->state == GM_SG_NOPRUNE || + sg->state == GM_SG_NOPRUNE_EXPIRING) && + !has_expired) + desired = GM_SG_NOPRUNE_EXPIRING; + else + desired = GM_SG_PRUNE; + } else if (gm_packet_sg_subs_count(sg->subs_negative)) + desired = GM_SG_NOPRUNE; + else + desired = GM_SG_NOINFO; + + if (desired != sg->state && !gm_ifp->stopping) { + if (PIM_DEBUG_GM_EVENTS) + zlog_debug(log_sg(sg, "%s => %s"), gm_states[sg->state], + gm_states[desired]); + + if (desired == GM_SG_JOIN_EXPIRING || + desired == GM_SG_NOPRUNE_EXPIRING) { + struct gm_query_timers timers; + + timers.qrv = gm_ifp->cur_qrv; + timers.max_resp_ms = gm_ifp->cur_max_resp; + timers.qqic_ms = gm_ifp->cur_query_intv_trig; + timers.fuzz = gm_ifp->cfg_timing_fuzz; + + gm_expiry_calc(&timers); + gm_sg_timer_start(gm_ifp, sg, timers.expire_wait); + + EVENT_OFF(sg->t_sg_query); + sg->query_sbit = false; + /* Trigger the specific queries only for querier. */ + if (IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest)) { + sg->n_query = gm_ifp->cur_lmqc; + gm_trigger_specific(sg); + } + } + } + prev = sg->state; + sg->state = desired; + + if (in6_multicast_nofwd(&sg->sgaddr.grp) || gm_ifp->stopping) + new_join = false; + else + new_join = gm_sg_state_want_join(desired); + + if (new_join && !sg->tib_joined) { + /* this will retry if join previously failed */ + sg->tib_joined = tib_sg_gm_join(gm_ifp->pim, sg->sgaddr, + gm_ifp->ifp, &sg->oil); + if (!sg->tib_joined) + zlog_warn( + "MLD join for %pSG%%%s not propagated into TIB", + &sg->sgaddr, gm_ifp->ifp->name); + else + zlog_info(log_ifp("%pSG%%%s TIB joined"), &sg->sgaddr, + gm_ifp->ifp->name); + + } else if (sg->tib_joined && !new_join) { + tib_sg_gm_prune(gm_ifp->pim, sg->sgaddr, gm_ifp->ifp, &sg->oil); + + sg->oil = NULL; + sg->tib_joined = false; + } + + if (desired == GM_SG_NOINFO) { + /* multiple paths can lead to the last state going away; + * t_sg_expire can still be running if we're arriving from + * another path. + */ + if (has_expired) + EVENT_OFF(sg->t_sg_expire); + + assertf((!sg->t_sg_expire && + !gm_packet_sg_subs_count(sg->subs_positive) && + !gm_packet_sg_subs_count(sg->subs_negative)), + "%pSG%%%s hx=%u exp=%pTHD state=%s->%s pos=%zu neg=%zu grp=%p", + &sg->sgaddr, gm_ifp->ifp->name, has_expired, + sg->t_sg_expire, gm_states[prev], gm_states[desired], + gm_packet_sg_subs_count(sg->subs_positive), + gm_packet_sg_subs_count(sg->subs_negative), grp); + + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_sg(sg, "dropping")); + + gm_sgs_del(gm_ifp->sgs, sg); + gm_sg_free(sg); + } +} + +/****************************************************************************/ + +/* the following bunch of functions deals with transferring state from + * received packets into gm_packet_state. As a reminder, the querier is + * structured to keep all items received in one packet together, since they + * will share expiry timers and thus allows efficient handling. + */ + +static void gm_packet_free(struct gm_packet_state *pkt) +{ + gm_packet_expires_del(pkt->iface->expires, pkt); + gm_packets_del(pkt->subscriber->packets, pkt); + gm_subscriber_drop(&pkt->subscriber); + XFREE(MTYPE_GM_STATE, pkt); +} + +static struct gm_packet_sg *gm_packet_sg_setup(struct gm_packet_state *pkt, + struct gm_sg *sg, bool is_excl, + bool is_src) +{ + struct gm_packet_sg *item; + + assert(pkt->n_active < pkt->n_sg); + + item = &pkt->items[pkt->n_active]; + item->sg = sg; + item->is_excl = is_excl; + item->is_src = is_src; + item->offset = pkt->n_active; + + pkt->n_active++; + return item; +} + +static bool gm_packet_sg_drop(struct gm_packet_sg *item) +{ + struct gm_packet_state *pkt; + size_t i; + + assert(item->sg); + + pkt = gm_packet_sg2state(item); + if (item->sg->most_recent == item) + item->sg->most_recent = NULL; + + for (i = 0; i < item->n_exclude; i++) { + struct gm_packet_sg *excl_item; + + excl_item = item + 1 + i; + if (!excl_item->sg) + continue; + + gm_packet_sg_subs_del(excl_item->sg->subs_negative, excl_item); + excl_item->sg = NULL; + pkt->n_active--; + + assert(pkt->n_active > 0); + } + + if (item->is_excl && item->is_src) + gm_packet_sg_subs_del(item->sg->subs_negative, item); + else + gm_packet_sg_subs_del(item->sg->subs_positive, item); + item->sg = NULL; + pkt->n_active--; + + if (!pkt->n_active) { + gm_packet_free(pkt); + return true; + } + return false; +} + +static void gm_packet_drop(struct gm_packet_state *pkt, bool trace) +{ + for (size_t i = 0; i < pkt->n_sg; i++) { + struct gm_sg *sg = pkt->items[i].sg; + bool deleted; + + if (!sg) + continue; + + if (trace && PIM_DEBUG_GM_TRACE) + zlog_debug(log_sg(sg, "general-dropping from %pPA"), + &pkt->subscriber->addr); + deleted = gm_packet_sg_drop(&pkt->items[i]); + + gm_sg_update(sg, true); + if (deleted) + break; + } +} + +static void gm_packet_sg_remove_sources(struct gm_if *gm_ifp, + struct gm_subscriber *subscriber, + pim_addr grp, pim_addr *srcs, + size_t n_src, enum gm_sub_sense sense) +{ + struct gm_sg *sg; + struct gm_packet_sg *old_src; + size_t i; + + for (i = 0; i < n_src; i++) { + sg = gm_sg_find(gm_ifp, grp, srcs[i]); + if (!sg) + continue; + + old_src = gm_packet_sg_find(sg, sense, subscriber); + if (!old_src) + continue; + + gm_packet_sg_drop(old_src); + gm_sg_update(sg, false); + } +} + +static void gm_sg_expiry_cancel(struct gm_sg *sg) +{ + if (sg->t_sg_expire && PIM_DEBUG_GM_TRACE) + zlog_debug(log_sg(sg, "alive, cancelling expiry timer")); + EVENT_OFF(sg->t_sg_expire); + sg->query_sbit = true; +} + +/* first pass: process all changes resulting in removal of state: + * - {TO,IS}_INCLUDE removes *,G EXCLUDE state (and S,G) + * - ALLOW_NEW_SOURCES, if *,G in EXCLUDE removes S,G state + * - BLOCK_OLD_SOURCES, if *,G in INCLUDE removes S,G state + * - {TO,IS}_EXCLUDE, if *,G in INCLUDE removes S,G state + * note *replacing* state is NOT considered *removing* state here + * + * everything else is thrown into pkt for creation of state in pass 2 + */ +static void gm_handle_v2_pass1(struct gm_packet_state *pkt, + struct mld_v2_rec_hdr *rechdr, size_t n_src) +{ + /* NB: pkt->subscriber can be NULL here if the subscriber was not + * previously seen! + */ + struct gm_subscriber *subscriber = pkt->subscriber; + struct gm_sg *grp; + struct gm_packet_sg *old_grp = NULL; + struct gm_packet_sg *item; + size_t j; + bool is_excl = false; + + grp = gm_sg_find(pkt->iface, rechdr->grp, PIMADDR_ANY); + if (grp && subscriber) + old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber); + + assert(old_grp == NULL || old_grp->is_excl); + + switch (rechdr->type) { + case MLD_RECTYPE_IS_EXCLUDE: + case MLD_RECTYPE_CHANGE_TO_EXCLUDE: + /* this always replaces or creates state */ + is_excl = true; + if (!grp) + grp = gm_sg_make(pkt->iface, rechdr->grp, PIMADDR_ANY); + + item = gm_packet_sg_setup(pkt, grp, is_excl, false); + item->n_exclude = n_src; + + /* [EXCL_INCL_SG_NOTE] referenced below + * + * in theory, we should drop any S,G that the host may have + * previously added in INCLUDE mode. In practice, this is both + * incredibly rare and entirely irrelevant. It only makes any + * difference if an S,G that the host previously had on the + * INCLUDE list is now on the blocked list for EXCLUDE, which + * we can cover in processing the S,G list in pass2_excl(). + * + * Other S,G from the host are simply left to expire + * "naturally" through general expiry. + */ + break; + + case MLD_RECTYPE_IS_INCLUDE: + case MLD_RECTYPE_CHANGE_TO_INCLUDE: + if (old_grp) { + /* INCLUDE has no *,G state, so old_grp here refers to + * previous EXCLUDE => delete it + */ + gm_packet_sg_drop(old_grp); + gm_sg_update(grp, false); +/* TODO "need S,G PRUNE => NO_INFO transition here" */ + } + break; + + case MLD_RECTYPE_ALLOW_NEW_SOURCES: + if (old_grp) { + /* remove S,Gs from EXCLUDE, and then we're done */ + gm_packet_sg_remove_sources(pkt->iface, subscriber, + rechdr->grp, rechdr->srcs, + n_src, GM_SUB_NEG); + return; + } + /* in INCLUDE mode => ALLOW_NEW_SOURCES is functionally + * idential to IS_INCLUDE (because the list of sources in + * IS_INCLUDE is not exhaustive) + */ + break; + + case MLD_RECTYPE_BLOCK_OLD_SOURCES: + if (old_grp) { + /* this is intentionally not implemented because it + * would be complicated as hell. we only take the list + * of blocked sources from full group state records + */ + return; + } + + if (subscriber) + gm_packet_sg_remove_sources(pkt->iface, subscriber, + rechdr->grp, rechdr->srcs, + n_src, GM_SUB_POS); + return; + } + + for (j = 0; j < n_src; j++) { + struct gm_sg *sg; + + sg = gm_sg_find(pkt->iface, rechdr->grp, rechdr->srcs[j]); + if (!sg) + sg = gm_sg_make(pkt->iface, rechdr->grp, + rechdr->srcs[j]); + + gm_packet_sg_setup(pkt, sg, is_excl, true); + } +} + +/* second pass: creating/updating/refreshing state. All the items from the + * received packet have already been thrown into gm_packet_state. + */ + +static void gm_handle_v2_pass2_incl(struct gm_packet_state *pkt, size_t i) +{ + struct gm_packet_sg *item = &pkt->items[i]; + struct gm_packet_sg *old = NULL; + struct gm_sg *sg = item->sg; + + /* EXCLUDE state was already dropped in pass1 */ + assert(!gm_packet_sg_find(sg, GM_SUB_NEG, pkt->subscriber)); + + old = gm_packet_sg_find(sg, GM_SUB_POS, pkt->subscriber); + if (old) + gm_packet_sg_drop(old); + + pkt->n_active++; + gm_packet_sg_subs_add(sg->subs_positive, item); + + sg->most_recent = item; + gm_sg_expiry_cancel(sg); + gm_sg_update(sg, false); +} + +static void gm_handle_v2_pass2_excl(struct gm_packet_state *pkt, size_t offs) +{ + struct gm_packet_sg *item = &pkt->items[offs]; + struct gm_packet_sg *old_grp, *item_dup; + struct gm_sg *sg_grp = item->sg; + size_t i; + + old_grp = gm_packet_sg_find(sg_grp, GM_SUB_POS, pkt->subscriber); + if (old_grp) { + for (i = 0; i < item->n_exclude; i++) { + struct gm_packet_sg *item_src, *old_src; + + item_src = &pkt->items[offs + 1 + i]; + old_src = gm_packet_sg_find(item_src->sg, GM_SUB_NEG, + pkt->subscriber); + if (old_src) + gm_packet_sg_drop(old_src); + + /* See [EXCL_INCL_SG_NOTE] above - we can have old S,G + * items left over if the host previously had INCLUDE + * mode going. Remove them here if we find any. + */ + old_src = gm_packet_sg_find(item_src->sg, GM_SUB_POS, + pkt->subscriber); + if (old_src) + gm_packet_sg_drop(old_src); + } + + /* the previous loop has removed the S,G entries which are + * still excluded after this update. So anything left on the + * old item was previously excluded but is now included + * => need to trigger update on S,G + */ + for (i = 0; i < old_grp->n_exclude; i++) { + struct gm_packet_sg *old_src; + struct gm_sg *old_sg_src; + + old_src = old_grp + 1 + i; + old_sg_src = old_src->sg; + if (!old_sg_src) + continue; + + gm_packet_sg_drop(old_src); + gm_sg_update(old_sg_src, false); + } + + gm_packet_sg_drop(old_grp); + } + + item_dup = gm_packet_sg_subs_add(sg_grp->subs_positive, item); + assert(!item_dup); + pkt->n_active++; + + sg_grp->most_recent = item; + gm_sg_expiry_cancel(sg_grp); + + for (i = 0; i < item->n_exclude; i++) { + struct gm_packet_sg *item_src; + + item_src = &pkt->items[offs + 1 + i]; + item_dup = gm_packet_sg_subs_add(item_src->sg->subs_negative, + item_src); + + if (item_dup) + item_src->sg = NULL; + else { + pkt->n_active++; + gm_sg_update(item_src->sg, false); + } + } + + /* TODO: determine best ordering between gm_sg_update(S,G) and (*,G) + * to get lower PIM churn/flapping + */ + gm_sg_update(sg_grp, false); +} + +/* TODO: QRV/QQIC are not copied from queries to local state" */ + +/* on receiving a query, we need to update our robustness/query interval to + * match, so we correctly process group/source specific queries after last + * member leaves + */ + +static void gm_handle_v2_report(struct gm_if *gm_ifp, + const struct sockaddr_in6 *pkt_src, char *data, + size_t len) +{ + struct mld_v2_report_hdr *hdr; + size_t i, n_records, max_entries; + struct gm_packet_state *pkt; + + if (len < sizeof(*hdr)) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug(log_pkt_src( + "malformed MLDv2 report (truncated header)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + hdr = (struct mld_v2_report_hdr *)data; + data += sizeof(*hdr); + len -= sizeof(*hdr); + + n_records = ntohs(hdr->n_records); + if (n_records > len / sizeof(struct mld_v2_rec_hdr)) { + /* note this is only an upper bound, records with source lists + * are larger. This is mostly here to make coverity happy. + */ + zlog_warn(log_pkt_src( + "malformed MLDv2 report (infeasible record count)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + /* errors after this may at least partially process the packet */ + gm_ifp->stats.rx_new_report++; + + /* can't have more *,G and S,G items than there is space for ipv6 + * addresses, so just use this to allocate temporary buffer + */ + max_entries = len / sizeof(pim_addr); + pkt = XCALLOC(MTYPE_GM_STATE, + offsetof(struct gm_packet_state, items[max_entries])); + pkt->n_sg = max_entries; + pkt->iface = gm_ifp; + pkt->subscriber = gm_subscriber_findref(gm_ifp, pkt_src->sin6_addr); + + /* validate & remove state in v2_pass1() */ + for (i = 0; i < n_records; i++) { + struct mld_v2_rec_hdr *rechdr; + size_t n_src, record_size; + + if (len < sizeof(*rechdr)) { + zlog_warn(log_pkt_src( + "malformed MLDv2 report (truncated record header)")); + gm_ifp->stats.rx_trunc_report++; + break; + } + + rechdr = (struct mld_v2_rec_hdr *)data; + data += sizeof(*rechdr); + len -= sizeof(*rechdr); + + n_src = ntohs(rechdr->n_src); + record_size = n_src * sizeof(pim_addr) + rechdr->aux_len * 4; + + if (len < record_size) { + zlog_warn(log_pkt_src( + "malformed MLDv2 report (truncated source list)")); + gm_ifp->stats.rx_trunc_report++; + break; + } + if (!IN6_IS_ADDR_MULTICAST(&rechdr->grp)) { + zlog_warn( + log_pkt_src( + "malformed MLDv2 report (invalid group %pI6)"), + &rechdr->grp); + gm_ifp->stats.rx_trunc_report++; + break; + } + + data += record_size; + len -= record_size; + + gm_handle_v2_pass1(pkt, rechdr, n_src); + } + + if (!pkt->n_active) { + gm_subscriber_drop(&pkt->subscriber); + XFREE(MTYPE_GM_STATE, pkt); + return; + } + + pkt = XREALLOC(MTYPE_GM_STATE, pkt, + offsetof(struct gm_packet_state, items[pkt->n_active])); + pkt->n_sg = pkt->n_active; + pkt->n_active = 0; + + monotime(&pkt->received); + if (!pkt->subscriber) + pkt->subscriber = gm_subscriber_get(gm_ifp, pkt_src->sin6_addr); + gm_packets_add_tail(pkt->subscriber->packets, pkt); + gm_packet_expires_add_tail(gm_ifp->expires, pkt); + + for (i = 0; i < pkt->n_sg; i++) + if (!pkt->items[i].is_excl) + gm_handle_v2_pass2_incl(pkt, i); + else { + gm_handle_v2_pass2_excl(pkt, i); + i += pkt->items[i].n_exclude; + } + + if (pkt->n_active == 0) + gm_packet_free(pkt); +} + +static void gm_handle_v1_report(struct gm_if *gm_ifp, + const struct sockaddr_in6 *pkt_src, char *data, + size_t len) +{ + struct mld_v1_pkt *hdr; + struct gm_packet_state *pkt; + struct gm_sg *grp; + struct gm_packet_sg *item; + size_t max_entries; + + if (len < sizeof(*hdr)) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug(log_pkt_src( + "malformed MLDv1 report (truncated)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + gm_ifp->stats.rx_old_report++; + + hdr = (struct mld_v1_pkt *)data; + + max_entries = 1; + pkt = XCALLOC(MTYPE_GM_STATE, + offsetof(struct gm_packet_state, items[max_entries])); + pkt->n_sg = max_entries; + pkt->iface = gm_ifp; + pkt->subscriber = gm_subscriber_findref(gm_ifp, gm_dummy_untracked); + + /* { equivalent of gm_handle_v2_pass1() with IS_EXCLUDE */ + + grp = gm_sg_find(pkt->iface, hdr->grp, PIMADDR_ANY); + if (!grp) + grp = gm_sg_make(pkt->iface, hdr->grp, PIMADDR_ANY); + + item = gm_packet_sg_setup(pkt, grp, true, false); + item->n_exclude = 0; + +/* TODO "set v1-seen timer on grp here" */ + + /* } */ + + /* pass2 will count n_active back up to 1. Also since a v1 report + * has exactly 1 group, we can skip the realloc() that v2 needs here. + */ + assert(pkt->n_active == 1); + pkt->n_sg = pkt->n_active; + pkt->n_active = 0; + + monotime(&pkt->received); + if (!pkt->subscriber) + pkt->subscriber = gm_subscriber_get(gm_ifp, gm_dummy_untracked); + gm_packets_add_tail(pkt->subscriber->packets, pkt); + gm_packet_expires_add_tail(gm_ifp->expires, pkt); + + /* pass2 covers installing state & removing old state; all the v1 + * compat is handled at this point. + * + * Note that "old state" may be v2; subscribers will switch from v2 + * reports to v1 reports when the querier changes from v2 to v1. So, + * limiting this to v1 would be wrong. + */ + gm_handle_v2_pass2_excl(pkt, 0); + + if (pkt->n_active == 0) + gm_packet_free(pkt); +} + +static void gm_handle_v1_leave(struct gm_if *gm_ifp, + const struct sockaddr_in6 *pkt_src, char *data, + size_t len) +{ + struct mld_v1_pkt *hdr; + struct gm_subscriber *subscriber; + struct gm_sg *grp; + struct gm_packet_sg *old_grp; + + if (len < sizeof(*hdr)) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug(log_pkt_src( + "malformed MLDv1 leave (truncated)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + gm_ifp->stats.rx_old_leave++; + + hdr = (struct mld_v1_pkt *)data; + + subscriber = gm_subscriber_findref(gm_ifp, gm_dummy_untracked); + if (!subscriber) + return; + + /* { equivalent of gm_handle_v2_pass1() with IS_INCLUDE */ + + grp = gm_sg_find(gm_ifp, hdr->grp, PIMADDR_ANY); + if (grp) { + old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber); + if (old_grp) { + gm_packet_sg_drop(old_grp); + gm_sg_update(grp, false); + +/* TODO "need S,G PRUNE => NO_INFO transition here" */ + + } + } + + /* } */ + + /* nothing more to do here, pass2 is no-op for leaves */ + gm_subscriber_drop(&subscriber); +} + +/* for each general query received (or sent), a timer is started to expire + * _everything_ at the appropriate time (including robustness multiplier). + * + * So when this timer hits, all packets - with all of their items - that were + * received *before* the query are aged out, and state updated accordingly. + * Note that when we receive a refresh/update, the previous/old packet is + * already dropped and replaced with a new one, so in normal steady-state + * operation, this timer won't be doing anything. + * + * Additionally, if a subscriber actively leaves a group, that goes through + * its own path too and won't hit this. This is really only triggered when a + * host straight up disappears. + */ +static void gm_t_expire(struct event *t) +{ + struct gm_if *gm_ifp = EVENT_ARG(t); + struct gm_packet_state *pkt; + + zlog_info(log_ifp("general expiry timer")); + + while (gm_ifp->n_pending) { + struct gm_general_pending *pend = gm_ifp->pending; + struct timeval remain; + int64_t remain_ms; + + remain_ms = monotime_until(&pend->expiry, &remain); + if (remain_ms > 0) { + if (PIM_DEBUG_GM_EVENTS) + zlog_debug( + log_ifp("next general expiry in %" PRId64 "ms"), + remain_ms / 1000); + + event_add_timer_tv(router->master, gm_t_expire, gm_ifp, + &remain, &gm_ifp->t_expire); + return; + } + + while ((pkt = gm_packet_expires_first(gm_ifp->expires))) { + if (timercmp(&pkt->received, &pend->query, >=)) + break; + + if (PIM_DEBUG_GM_PACKETS) + zlog_debug(log_ifp("expire packet %p"), pkt); + gm_packet_drop(pkt, true); + } + + gm_ifp->n_pending--; + memmove(gm_ifp->pending, gm_ifp->pending + 1, + gm_ifp->n_pending * sizeof(gm_ifp->pending[0])); + } + + if (PIM_DEBUG_GM_EVENTS) + zlog_debug(log_ifp("next general expiry waiting for query")); +} + +/* NB: the receive handlers will also run when sending packets, since we + * receive our own packets back in. + */ +static void gm_handle_q_general(struct gm_if *gm_ifp, + struct gm_query_timers *timers) +{ + struct timeval now, expiry; + struct gm_general_pending *pend; + + monotime(&now); + timeradd(&now, &timers->expire_wait, &expiry); + + while (gm_ifp->n_pending) { + pend = &gm_ifp->pending[gm_ifp->n_pending - 1]; + + if (timercmp(&pend->expiry, &expiry, <)) + break; + + /* if we end up here, the last item in pending[] has an expiry + * later than the expiry for this query. But our query time + * (now) is later than that of the item (because, well, that's + * how time works.) This makes this query meaningless since + * it's "supersetted" within the preexisting query + */ + + if (PIM_DEBUG_GM_TRACE_DETAIL) + zlog_debug( + log_ifp("zapping supersetted general timer %pTVMu"), + &pend->expiry); + + gm_ifp->n_pending--; + if (!gm_ifp->n_pending) + EVENT_OFF(gm_ifp->t_expire); + } + + /* people might be messing with their configs or something */ + if (gm_ifp->n_pending == array_size(gm_ifp->pending)) + return; + + pend = &gm_ifp->pending[gm_ifp->n_pending]; + pend->query = now; + pend->expiry = expiry; + + if (!gm_ifp->n_pending++) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + log_ifp("starting general timer @ 0: %pTVMu"), + &pend->expiry); + event_add_timer_tv(router->master, gm_t_expire, gm_ifp, + &timers->expire_wait, &gm_ifp->t_expire); + } else if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_ifp("appending general timer @ %u: %pTVMu"), + gm_ifp->n_pending, &pend->expiry); +} + +static void gm_t_sg_expire(struct event *t) +{ + struct gm_sg *sg = EVENT_ARG(t); + struct gm_if *gm_ifp = sg->iface; + struct gm_packet_sg *item; + + assertf(sg->state == GM_SG_JOIN_EXPIRING || + sg->state == GM_SG_NOPRUNE_EXPIRING, + "%pSG%%%s %pTHD", &sg->sgaddr, gm_ifp->ifp->name, t); + + frr_each_safe (gm_packet_sg_subs, sg->subs_positive, item) + /* this will also drop EXCLUDE mode S,G lists together with + * the *,G entry + */ + gm_packet_sg_drop(item); + + /* subs_negative items are only timed out together with the *,G entry + * since we won't get any reports for a group-and-source query + */ + gm_sg_update(sg, true); +} + +static bool gm_sg_check_recent(struct gm_if *gm_ifp, struct gm_sg *sg, + struct timeval ref) +{ + struct gm_packet_state *pkt; + + if (!sg->most_recent) { + struct gm_packet_state *best_pkt = NULL; + struct gm_packet_sg *item; + + frr_each (gm_packet_sg_subs, sg->subs_positive, item) { + pkt = gm_packet_sg2state(item); + + if (!best_pkt || + timercmp(&pkt->received, &best_pkt->received, >)) { + best_pkt = pkt; + sg->most_recent = item; + } + } + } + if (sg->most_recent) { + struct timeval fuzz; + + pkt = gm_packet_sg2state(sg->most_recent); + + /* this shouldn't happen on plain old real ethernet segment, + * but on something like a VXLAN or VPLS it is very possible + * that we get a report before the query that triggered it. + * (imagine a triangle scenario with 3 datacenters, it's very + * possible A->B + B->C is faster than A->C due to odd routing) + * + * This makes a little tolerance allowance to handle that case. + */ + timeradd(&pkt->received, &gm_ifp->cfg_timing_fuzz, &fuzz); + + if (timercmp(&fuzz, &ref, >)) + return true; + } + return false; +} + +static void gm_sg_timer_start(struct gm_if *gm_ifp, struct gm_sg *sg, + struct timeval expire_wait) +{ + struct timeval now; + + if (!sg) + return; + if (sg->state == GM_SG_PRUNE) + return; + + monotime(&now); + if (gm_sg_check_recent(gm_ifp, sg, now)) + return; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_sg(sg, "expiring in %pTVI"), &expire_wait); + + if (sg->t_sg_expire) { + struct timeval remain; + + remain = event_timer_remain(sg->t_sg_expire); + if (timercmp(&remain, &expire_wait, <=)) + return; + + EVENT_OFF(sg->t_sg_expire); + } + + event_add_timer_tv(router->master, gm_t_sg_expire, sg, &expire_wait, + &sg->t_sg_expire); +} + +static void gm_handle_q_groupsrc(struct gm_if *gm_ifp, + struct gm_query_timers *timers, pim_addr grp, + const pim_addr *srcs, size_t n_src) +{ + struct gm_sg *sg; + size_t i; + + for (i = 0; i < n_src; i++) { + sg = gm_sg_find(gm_ifp, grp, srcs[i]); + GM_UPDATE_SG_STATE(sg); + gm_sg_timer_start(gm_ifp, sg, timers->expire_wait); + } +} + +static void gm_t_grp_expire(struct event *t) +{ + /* if we're here, that means when we received the group-specific query + * there was one or more active S,G for this group. For *,G the timer + * in sg->t_sg_expire is running separately and gets cancelled when we + * receive a report, so that work is left to gm_t_sg_expire and we + * shouldn't worry about it here. + */ + struct gm_grp_pending *pend = EVENT_ARG(t); + struct gm_if *gm_ifp = pend->iface; + struct gm_sg *sg, *sg_start, sg_ref = {}; + + if (PIM_DEBUG_GM_EVENTS) + zlog_debug(log_ifp("*,%pPAs S,G timer expired"), &pend->grp); + + /* gteq lookup - try to find *,G or S,G (S,G is > *,G) + * could technically be gt to skip a possible *,G + */ + sg_ref.sgaddr.grp = pend->grp; + sg_ref.sgaddr.src = PIMADDR_ANY; + sg_start = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref); + + frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) { + struct gm_packet_sg *item; + + if (pim_addr_cmp(sg->sgaddr.grp, pend->grp)) + break; + if (pim_addr_is_any(sg->sgaddr.src)) + /* handled by gm_t_sg_expire / sg->t_sg_expire */ + continue; + if (gm_sg_check_recent(gm_ifp, sg, pend->query)) + continue; + + /* we may also have a group-source-specific query going on in + * parallel. But if we received nothing for the *,G query, + * the S,G query is kinda irrelevant. + */ + EVENT_OFF(sg->t_sg_expire); + + frr_each_safe (gm_packet_sg_subs, sg->subs_positive, item) + /* this will also drop the EXCLUDE S,G lists */ + gm_packet_sg_drop(item); + + gm_sg_update(sg, true); + } + + gm_grp_pends_del(gm_ifp->grp_pends, pend); + XFREE(MTYPE_GM_GRP_PENDING, pend); +} + +static void gm_handle_q_group(struct gm_if *gm_ifp, + struct gm_query_timers *timers, pim_addr grp) +{ + struct gm_sg *sg, sg_ref = {}; + struct gm_grp_pending *pend, pend_ref = {}; + + sg_ref.sgaddr.grp = grp; + sg_ref.sgaddr.src = PIMADDR_ANY; + /* gteq lookup - try to find *,G or S,G (S,G is > *,G) */ + sg = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref); + + if (!sg || pim_addr_cmp(sg->sgaddr.grp, grp)) + /* we have nothing at all for this group - don't waste RAM */ + return; + + if (pim_addr_is_any(sg->sgaddr.src)) { + /* actually found *,G entry here */ + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_ifp("*,%pPAs expiry timer starting"), + &grp); + GM_UPDATE_SG_STATE(sg); + gm_sg_timer_start(gm_ifp, sg, timers->expire_wait); + + sg = gm_sgs_next(gm_ifp->sgs, sg); + if (!sg || pim_addr_cmp(sg->sgaddr.grp, grp)) + /* no S,G for this group */ + return; + } + + pend_ref.grp = grp; + pend = gm_grp_pends_find(gm_ifp->grp_pends, &pend_ref); + + if (pend) { + struct timeval remain; + + remain = event_timer_remain(pend->t_expire); + if (timercmp(&remain, &timers->expire_wait, <=)) + return; + + EVENT_OFF(pend->t_expire); + } else { + pend = XCALLOC(MTYPE_GM_GRP_PENDING, sizeof(*pend)); + pend->grp = grp; + pend->iface = gm_ifp; + gm_grp_pends_add(gm_ifp->grp_pends, pend); + } + + monotime(&pend->query); + event_add_timer_tv(router->master, gm_t_grp_expire, pend, + &timers->expire_wait, &pend->t_expire); + + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_ifp("*,%pPAs S,G timer started: %pTHD"), &grp, + pend->t_expire); +} + +static void gm_bump_querier(struct gm_if *gm_ifp) +{ + struct pim_interface *pim_ifp = gm_ifp->ifp->info; + + EVENT_OFF(gm_ifp->t_query); + + if (pim_addr_is_any(pim_ifp->ll_lowest)) + return; + if (!IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest)) + return; + + gm_ifp->n_startup = gm_ifp->cur_qrv; + + event_execute(router->master, gm_t_query, gm_ifp, 0, NULL); +} + +static void gm_t_other_querier(struct event *t) +{ + struct gm_if *gm_ifp = EVENT_ARG(t); + struct pim_interface *pim_ifp = gm_ifp->ifp->info; + + zlog_info(log_ifp("other querier timer expired")); + + gm_ifp->querier = pim_ifp->ll_lowest; + gm_ifp->n_startup = gm_ifp->cur_qrv; + + event_execute(router->master, gm_t_query, gm_ifp, 0, NULL); +} + +static void gm_handle_query(struct gm_if *gm_ifp, + const struct sockaddr_in6 *pkt_src, + pim_addr *pkt_dst, char *data, size_t len) +{ + struct mld_v2_query_hdr *hdr; + struct pim_interface *pim_ifp = gm_ifp->ifp->info; + struct gm_query_timers timers; + bool general_query; + + if (len < sizeof(struct mld_v2_query_hdr) && + len != sizeof(struct mld_v1_pkt)) { + zlog_warn(log_pkt_src("invalid query size")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + hdr = (struct mld_v2_query_hdr *)data; + general_query = pim_addr_is_any(hdr->grp); + + if (!general_query && !IN6_IS_ADDR_MULTICAST(&hdr->grp)) { + zlog_warn(log_pkt_src( + "malformed MLDv2 query (invalid group %pI6)"), + &hdr->grp); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + if (len >= sizeof(struct mld_v2_query_hdr)) { + size_t src_space = ntohs(hdr->n_src) * sizeof(pim_addr); + + if (len < sizeof(struct mld_v2_query_hdr) + src_space) { + zlog_warn(log_pkt_src( + "malformed MLDv2 query (truncated source list)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + + if (general_query && src_space) { + zlog_warn(log_pkt_src( + "malformed MLDv2 query (general query with non-empty source list)")); + gm_ifp->stats.rx_drop_malformed++; + return; + } + } + + /* accepting queries unicast to us (or addressed to a wrong group) + * can mess up querier election as well as cause us to terminate + * traffic (since after a unicast query no reports will be coming in) + */ + if (!IPV6_ADDR_SAME(pkt_dst, &gm_all_hosts)) { + if (pim_addr_is_any(hdr->grp)) { + zlog_warn( + log_pkt_src( + "wrong destination %pPA for general query"), + pkt_dst); + gm_ifp->stats.rx_drop_dstaddr++; + return; + } + + if (!IPV6_ADDR_SAME(&hdr->grp, pkt_dst)) { + gm_ifp->stats.rx_drop_dstaddr++; + zlog_warn( + log_pkt_src( + "wrong destination %pPA for group specific query"), + pkt_dst); + return; + } + } + + if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &gm_ifp->querier) < 0) { + if (PIM_DEBUG_GM_EVENTS) + zlog_debug( + log_pkt_src("replacing elected querier %pPA"), + &gm_ifp->querier); + + gm_ifp->querier = pkt_src->sin6_addr; + } + + if (len == sizeof(struct mld_v1_pkt)) { + timers.qrv = gm_ifp->cur_qrv; + timers.max_resp_ms = hdr->max_resp_code; + timers.qqic_ms = gm_ifp->cur_query_intv; + } else { + timers.qrv = (hdr->flags & 0x7) ?: 8; + timers.max_resp_ms = mld_max_resp_decode(hdr->max_resp_code); + timers.qqic_ms = igmp_msg_decode8to16(hdr->qqic) * 1000; + } + timers.fuzz = gm_ifp->cfg_timing_fuzz; + + gm_expiry_calc(&timers); + + if (PIM_DEBUG_GM_TRACE_DETAIL) + zlog_debug( + log_ifp("query timers: QRV=%u max_resp=%ums qqic=%ums expire_wait=%pTVI"), + timers.qrv, timers.max_resp_ms, timers.qqic_ms, + &timers.expire_wait); + + if (IPV6_ADDR_CMP(&pkt_src->sin6_addr, &pim_ifp->ll_lowest) < 0) { + unsigned int other_ms; + + EVENT_OFF(gm_ifp->t_query); + EVENT_OFF(gm_ifp->t_other_querier); + + other_ms = timers.qrv * timers.qqic_ms + timers.max_resp_ms / 2; + event_add_timer_msec(router->master, gm_t_other_querier, gm_ifp, + other_ms, &gm_ifp->t_other_querier); + } + + if (len == sizeof(struct mld_v1_pkt)) { + if (general_query) { + gm_handle_q_general(gm_ifp, &timers); + gm_ifp->stats.rx_query_old_general++; + } else { + gm_handle_q_group(gm_ifp, &timers, hdr->grp); + gm_ifp->stats.rx_query_old_group++; + } + return; + } + + /* v2 query - [S]uppress bit */ + if (hdr->flags & 0x8) { + gm_ifp->stats.rx_query_new_sbit++; + return; + } + + if (general_query) { + gm_handle_q_general(gm_ifp, &timers); + gm_ifp->stats.rx_query_new_general++; + } else if (!ntohs(hdr->n_src)) { + gm_handle_q_group(gm_ifp, &timers, hdr->grp); + gm_ifp->stats.rx_query_new_group++; + } else { + /* this is checked above: + * if (len >= sizeof(struct mld_v2_query_hdr)) { + * size_t src_space = ntohs(hdr->n_src) * sizeof(pim_addr); + * if (len < sizeof(struct mld_v2_query_hdr) + src_space) { + */ + assume(ntohs(hdr->n_src) <= + (len - sizeof(struct mld_v2_query_hdr)) / + sizeof(pim_addr)); + + gm_handle_q_groupsrc(gm_ifp, &timers, hdr->grp, hdr->srcs, + ntohs(hdr->n_src)); + gm_ifp->stats.rx_query_new_groupsrc++; + } +} + +static void gm_rx_process(struct gm_if *gm_ifp, + const struct sockaddr_in6 *pkt_src, pim_addr *pkt_dst, + void *data, size_t pktlen) +{ + struct icmp6_plain_hdr *icmp6 = data; + uint16_t pkt_csum, ref_csum; + struct ipv6_ph ph6 = { + .src = pkt_src->sin6_addr, + .dst = *pkt_dst, + .ulpl = htons(pktlen), + .next_hdr = IPPROTO_ICMPV6, + }; + + pkt_csum = icmp6->icmp6_cksum; + icmp6->icmp6_cksum = 0; + ref_csum = in_cksum_with_ph6(&ph6, data, pktlen); + + if (pkt_csum != ref_csum) { + zlog_warn( + log_pkt_src( + "(dst %pPA) packet RX checksum failure, expected %04hx, got %04hx"), + pkt_dst, pkt_csum, ref_csum); + gm_ifp->stats.rx_drop_csum++; + return; + } + + data = (icmp6 + 1); + pktlen -= sizeof(*icmp6); + + switch (icmp6->icmp6_type) { + case ICMP6_MLD_QUERY: + gm_handle_query(gm_ifp, pkt_src, pkt_dst, data, pktlen); + break; + case ICMP6_MLD_V1_REPORT: + gm_handle_v1_report(gm_ifp, pkt_src, data, pktlen); + break; + case ICMP6_MLD_V1_DONE: + gm_handle_v1_leave(gm_ifp, pkt_src, data, pktlen); + break; + case ICMP6_MLD_V2_REPORT: + gm_handle_v2_report(gm_ifp, pkt_src, data, pktlen); + break; + } +} + +static bool ip6_check_hopopts_ra(uint8_t *hopopts, size_t hopopt_len, + uint16_t alert_type) +{ + uint8_t *hopopt_end; + + if (hopopt_len < 8) + return false; + if (hopopt_len < (hopopts[1] + 1U) * 8U) + return false; + + hopopt_end = hopopts + (hopopts[1] + 1) * 8; + hopopts += 2; + + while (hopopts < hopopt_end) { + if (hopopts[0] == IP6OPT_PAD1) { + hopopts++; + continue; + } + + if (hopopts > hopopt_end - 2) + break; + if (hopopts > hopopt_end - 2 - hopopts[1]) + break; + + if (hopopts[0] == IP6OPT_ROUTER_ALERT && hopopts[1] == 2) { + uint16_t have_type = (hopopts[2] << 8) | hopopts[3]; + + if (have_type == alert_type) + return true; + } + + hopopts += 2 + hopopts[1]; + } + return false; +} + +static void gm_t_recv(struct event *t) +{ + struct pim_instance *pim = EVENT_ARG(t); + union { + char buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(256) /* hop options */ + + CMSG_SPACE(sizeof(int)) /* hopcount */]; + struct cmsghdr align; + } cmsgbuf; + struct cmsghdr *cmsg; + struct in6_pktinfo *pktinfo = NULL; + uint8_t *hopopts = NULL; + size_t hopopt_len = 0; + int *hoplimit = NULL; + char rxbuf[2048]; + struct msghdr mh[1] = {}; + struct iovec iov[1]; + struct sockaddr_in6 pkt_src[1] = {}; + ssize_t nread; + size_t pktlen; + + event_add_read(router->master, gm_t_recv, pim, pim->gm_socket, + &pim->t_gm_recv); + + iov->iov_base = rxbuf; + iov->iov_len = sizeof(rxbuf); + + mh->msg_name = pkt_src; + mh->msg_namelen = sizeof(pkt_src); + mh->msg_control = cmsgbuf.buf; + mh->msg_controllen = sizeof(cmsgbuf.buf); + mh->msg_iov = iov; + mh->msg_iovlen = array_size(iov); + mh->msg_flags = 0; + + nread = recvmsg(pim->gm_socket, mh, MSG_PEEK | MSG_TRUNC); + if (nread <= 0) { + zlog_err("(VRF %s) RX error: %m", pim->vrf->name); + pim->gm_rx_drop_sys++; + return; + } + + if ((size_t)nread > sizeof(rxbuf)) { + iov->iov_base = XMALLOC(MTYPE_GM_PACKET, nread); + iov->iov_len = nread; + } + nread = recvmsg(pim->gm_socket, mh, 0); + if (nread <= 0) { + zlog_err("(VRF %s) RX error: %m", pim->vrf->name); + pim->gm_rx_drop_sys++; + goto out_free; + } + + struct interface *ifp; + + ifp = if_lookup_by_index(pkt_src->sin6_scope_id, pim->vrf->vrf_id); + if (!ifp || !ifp->info) + goto out_free; + + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp = pim_ifp->mld; + + if (!gm_ifp) + goto out_free; + + for (cmsg = CMSG_FIRSTHDR(mh); cmsg; cmsg = CMSG_NXTHDR(mh, cmsg)) { + if (cmsg->cmsg_level != SOL_IPV6) + continue; + + switch (cmsg->cmsg_type) { + case IPV6_PKTINFO: + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); + break; + case IPV6_HOPOPTS: + hopopts = CMSG_DATA(cmsg); + hopopt_len = cmsg->cmsg_len - sizeof(*cmsg); + break; + case IPV6_HOPLIMIT: + hoplimit = (int *)CMSG_DATA(cmsg); + break; + } + } + + if (!pktinfo || !hoplimit) { + zlog_err(log_ifp( + "BUG: packet without IPV6_PKTINFO or IPV6_HOPLIMIT")); + pim->gm_rx_drop_sys++; + goto out_free; + } + + if (*hoplimit != 1) { + zlog_err(log_pkt_src("packet with hop limit != 1")); + /* spoofing attempt => count on srcaddr counter */ + gm_ifp->stats.rx_drop_srcaddr++; + goto out_free; + } + + if (!ip6_check_hopopts_ra(hopopts, hopopt_len, IP6_ALERT_MLD)) { + zlog_err(log_pkt_src( + "packet without IPv6 Router Alert MLD option")); + gm_ifp->stats.rx_drop_ra++; + goto out_free; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&pkt_src->sin6_addr)) + /* reports from :: happen in normal operation for DAD, so + * don't spam log messages about this + */ + goto out_free; + + if (!IN6_IS_ADDR_LINKLOCAL(&pkt_src->sin6_addr)) { + zlog_warn(log_pkt_src("packet from invalid source address")); + gm_ifp->stats.rx_drop_srcaddr++; + goto out_free; + } + + pktlen = nread; + if (pktlen < sizeof(struct icmp6_plain_hdr)) { + zlog_warn(log_pkt_src("truncated packet")); + gm_ifp->stats.rx_drop_malformed++; + goto out_free; + } + + gm_rx_process(gm_ifp, pkt_src, &pktinfo->ipi6_addr, iov->iov_base, + pktlen); + +out_free: + if (iov->iov_base != rxbuf) + XFREE(MTYPE_GM_PACKET, iov->iov_base); +} + +static void gm_send_query(struct gm_if *gm_ifp, pim_addr grp, + const pim_addr *srcs, size_t n_srcs, bool s_bit) +{ + struct pim_interface *pim_ifp = gm_ifp->ifp->info; + struct sockaddr_in6 dstaddr = { + .sin6_family = AF_INET6, + .sin6_scope_id = gm_ifp->ifp->ifindex, + }; + struct { + struct icmp6_plain_hdr hdr; + struct mld_v2_query_hdr v2_query; + } query = { + /* clang-format off */ + .hdr = { + .icmp6_type = ICMP6_MLD_QUERY, + .icmp6_code = 0, + }, + .v2_query = { + .grp = grp, + }, + /* clang-format on */ + }; + struct ipv6_ph ph6 = { + .src = pim_ifp->ll_lowest, + .ulpl = htons(sizeof(query)), + .next_hdr = IPPROTO_ICMPV6, + }; + union { + char buf[CMSG_SPACE(8) /* hop options */ + + CMSG_SPACE(sizeof(struct in6_pktinfo))]; + struct cmsghdr align; + } cmsg = {}; + struct cmsghdr *cmh; + struct msghdr mh[1] = {}; + struct iovec iov[3]; + size_t iov_len; + ssize_t ret, expect_ret; + uint8_t *dp; + struct in6_pktinfo *pktinfo; + + if (if_is_loopback(gm_ifp->ifp)) { + /* Linux is a bit odd with multicast on loopback */ + ph6.src = in6addr_loopback; + dstaddr.sin6_addr = in6addr_loopback; + } else if (pim_addr_is_any(grp)) + dstaddr.sin6_addr = gm_all_hosts; + else + dstaddr.sin6_addr = grp; + + query.v2_query.max_resp_code = + mld_max_resp_encode(gm_ifp->cur_max_resp); + query.v2_query.flags = (gm_ifp->cur_qrv < 8) ? gm_ifp->cur_qrv : 0; + if (s_bit) + query.v2_query.flags |= 0x08; + query.v2_query.qqic = + igmp_msg_encode16to8(gm_ifp->cur_query_intv / 1000); + query.v2_query.n_src = htons(n_srcs); + + ph6.dst = dstaddr.sin6_addr; + + /* ph6 not included in sendmsg */ + iov[0].iov_base = &ph6; + iov[0].iov_len = sizeof(ph6); + iov[1].iov_base = &query; + if (gm_ifp->cur_version == GM_MLDV1) { + iov_len = 2; + iov[1].iov_len = sizeof(query.hdr) + sizeof(struct mld_v1_pkt); + } else if (!n_srcs) { + iov_len = 2; + iov[1].iov_len = sizeof(query); + } else { + iov[1].iov_len = sizeof(query); + iov[2].iov_base = (void *)srcs; + iov[2].iov_len = n_srcs * sizeof(srcs[0]); + iov_len = 3; + } + + query.hdr.icmp6_cksum = in_cksumv(iov, iov_len); + + if (PIM_DEBUG_GM_PACKETS) + zlog_debug( + log_ifp("MLD query %pPA -> %pI6 (grp=%pPA, %zu srcs)"), + &pim_ifp->ll_lowest, &dstaddr.sin6_addr, &grp, n_srcs); + + mh->msg_name = &dstaddr; + mh->msg_namelen = sizeof(dstaddr); + mh->msg_iov = iov + 1; + mh->msg_iovlen = iov_len - 1; + mh->msg_control = &cmsg; + mh->msg_controllen = sizeof(cmsg.buf); + + cmh = CMSG_FIRSTHDR(mh); + cmh->cmsg_level = IPPROTO_IPV6; + cmh->cmsg_type = IPV6_HOPOPTS; + cmh->cmsg_len = CMSG_LEN(8); + dp = CMSG_DATA(cmh); + *dp++ = 0; /* next header */ + *dp++ = 0; /* length (8-byte blocks, minus 1) */ + *dp++ = IP6OPT_ROUTER_ALERT; /* router alert */ + *dp++ = 2; /* length */ + *dp++ = 0; /* value (2 bytes) */ + *dp++ = 0; /* value (2 bytes) (0 = MLD) */ + *dp++ = 0; /* pad0 */ + *dp++ = 0; /* pad0 */ + + cmh = CMSG_NXTHDR(mh, cmh); + cmh->cmsg_level = IPPROTO_IPV6; + cmh->cmsg_type = IPV6_PKTINFO; + cmh->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmh); + pktinfo->ipi6_ifindex = gm_ifp->ifp->ifindex; + pktinfo->ipi6_addr = gm_ifp->cur_ll_lowest; + + expect_ret = iov[1].iov_len; + if (iov_len == 3) + expect_ret += iov[2].iov_len; + + frr_with_privs (&pimd_privs) { + ret = sendmsg(gm_ifp->pim->gm_socket, mh, 0); + } + + if (ret != expect_ret) { + zlog_warn(log_ifp("failed to send query: %m")); + gm_ifp->stats.tx_query_fail++; + } else { + if (gm_ifp->cur_version == GM_MLDV1) { + if (pim_addr_is_any(grp)) + gm_ifp->stats.tx_query_old_general++; + else + gm_ifp->stats.tx_query_old_group++; + } else { + if (pim_addr_is_any(grp)) + gm_ifp->stats.tx_query_new_general++; + else if (!n_srcs) + gm_ifp->stats.tx_query_new_group++; + else + gm_ifp->stats.tx_query_new_groupsrc++; + } + } +} + +static void gm_t_query(struct event *t) +{ + struct gm_if *gm_ifp = EVENT_ARG(t); + unsigned int timer_ms = gm_ifp->cur_query_intv; + + if (gm_ifp->n_startup) { + timer_ms /= 4; + gm_ifp->n_startup--; + } + + event_add_timer_msec(router->master, gm_t_query, gm_ifp, timer_ms, + &gm_ifp->t_query); + + gm_send_query(gm_ifp, PIMADDR_ANY, NULL, 0, false); +} + +static void gm_t_sg_query(struct event *t) +{ + struct gm_sg *sg = EVENT_ARG(t); + + gm_trigger_specific(sg); +} + +/* S,G specific queries (triggered by a member leaving) get a little slack + * time so we can bundle queries for [S1,S2,S3,...],G into the same query + */ +static void gm_send_specific(struct gm_gsq_pending *pend_gsq) +{ + struct gm_if *gm_ifp = pend_gsq->iface; + + gm_send_query(gm_ifp, pend_gsq->grp, pend_gsq->srcs, pend_gsq->n_src, + pend_gsq->s_bit); + + gm_gsq_pends_del(gm_ifp->gsq_pends, pend_gsq); + XFREE(MTYPE_GM_GSQ_PENDING, pend_gsq); +} + +static void gm_t_gsq_pend(struct event *t) +{ + struct gm_gsq_pending *pend_gsq = EVENT_ARG(t); + + gm_send_specific(pend_gsq); +} + +static void gm_trigger_specific(struct gm_sg *sg) +{ + struct gm_if *gm_ifp = sg->iface; + struct gm_gsq_pending *pend_gsq, ref = {}; + + sg->n_query--; + if (sg->n_query) + event_add_timer_msec(router->master, gm_t_sg_query, sg, + gm_ifp->cur_query_intv_trig, + &sg->t_sg_query); + + /* As per RFC 2271, s6 p14: + * E.g. a router that starts as a Querier, receives a + * Done message for a group and then receives a Query from a router with + * a lower address (causing a transition to the Non-Querier state) + * continues to send multicast-address-specific queries for the group in + * question until it either receives a Report or its timer expires, at + * which time it starts performing the actions of a Non-Querier for this + * group. + */ + /* Therefore here we do not need to check if this router is querier or + * not. This is called only for querier, hence it will work even if the + * router transitions from querier to non-querier. + */ + + if (gm_ifp->pim->gm_socket == -1) + return; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_sg(sg, "triggered query")); + + if (pim_addr_is_any(sg->sgaddr.src)) { + gm_send_query(gm_ifp, sg->sgaddr.grp, NULL, 0, sg->query_sbit); + return; + } + + ref.grp = sg->sgaddr.grp; + ref.s_bit = sg->query_sbit; + + pend_gsq = gm_gsq_pends_find(gm_ifp->gsq_pends, &ref); + if (!pend_gsq) { + pend_gsq = XCALLOC(MTYPE_GM_GSQ_PENDING, sizeof(*pend_gsq)); + pend_gsq->grp = sg->sgaddr.grp; + pend_gsq->s_bit = sg->query_sbit; + pend_gsq->iface = gm_ifp; + gm_gsq_pends_add(gm_ifp->gsq_pends, pend_gsq); + + event_add_timer_tv(router->master, gm_t_gsq_pend, pend_gsq, + &gm_ifp->cfg_timing_fuzz, &pend_gsq->t_send); + } + + assert(pend_gsq->n_src < array_size(pend_gsq->srcs)); + + pend_gsq->srcs[pend_gsq->n_src] = sg->sgaddr.src; + pend_gsq->n_src++; + + if (pend_gsq->n_src == array_size(pend_gsq->srcs)) { + EVENT_OFF(pend_gsq->t_send); + gm_send_specific(pend_gsq); + pend_gsq = NULL; + } +} + +static void gm_vrf_socket_incref(struct pim_instance *pim) +{ + struct vrf *vrf = pim->vrf; + int ret, intval; + struct icmp6_filter filter[1]; + + if (pim->gm_socket_if_count++ && pim->gm_socket != -1) + return; + + ICMP6_FILTER_SETBLOCKALL(filter); + ICMP6_FILTER_SETPASS(ICMP6_MLD_QUERY, filter); + ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_REPORT, filter); + ICMP6_FILTER_SETPASS(ICMP6_MLD_V1_DONE, filter); + ICMP6_FILTER_SETPASS(ICMP6_MLD_V2_REPORT, filter); + + frr_with_privs (&pimd_privs) { + pim->gm_socket = vrf_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, + vrf->vrf_id, vrf->name); + if (pim->gm_socket < 0) { + zlog_err("(VRF %s) could not create MLD socket: %m", + vrf->name); + return; + } + + ret = setsockopt(pim->gm_socket, SOL_ICMPV6, ICMP6_FILTER, + filter, sizeof(filter)); + if (ret) + zlog_err("(VRF %s) failed to set ICMP6_FILTER: %m", + vrf->name); + + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVPKTINFO, + &intval, sizeof(intval)); + if (ret) + zlog_err("(VRF %s) failed to set IPV6_RECVPKTINFO: %m", + vrf->name); + + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPOPTS, + &intval, sizeof(intval)); + if (ret) + zlog_err("(VRF %s) failed to set IPV6_HOPOPTS: %m", + vrf->name); + + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_RECVHOPLIMIT, + &intval, sizeof(intval)); + if (ret) + zlog_err("(VRF %s) failed to set IPV6_HOPLIMIT: %m", + vrf->name); + + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_LOOP, + &intval, sizeof(intval)); + if (ret) + zlog_err( + "(VRF %s) failed to disable IPV6_MULTICAST_LOOP: %m", + vrf->name); + + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_HOPS, + &intval, sizeof(intval)); + if (ret) + zlog_err( + "(VRF %s) failed to set IPV6_MULTICAST_HOPS: %m", + vrf->name); + + /* NB: IPV6_MULTICAST_ALL does not completely bypass multicast + * RX filtering in Linux. It only means "receive all groups + * that something on the system has joined". To actually + * receive *all* MLD packets - which is what we need - + * multicast routing must be enabled on the interface. And + * this only works for MLD packets specifically. + * + * For reference, check ip6_mc_input() in net/ipv6/ip6_input.c + * and in particular the #ifdef CONFIG_IPV6_MROUTE block there. + * + * Also note that the code there explicitly checks for the IPv6 + * router alert MLD option (which is required by the RFC to be + * on MLD packets.) That implies trying to support hosts which + * erroneously don't add that option is just not possible. + */ + intval = 1; + ret = setsockopt(pim->gm_socket, SOL_IPV6, IPV6_MULTICAST_ALL, + &intval, sizeof(intval)); + if (ret) + zlog_info( + "(VRF %s) failed to set IPV6_MULTICAST_ALL: %m (OK on old kernels)", + vrf->name); + } + + event_add_read(router->master, gm_t_recv, pim, pim->gm_socket, + &pim->t_gm_recv); +} + +static void gm_vrf_socket_decref(struct pim_instance *pim) +{ + if (--pim->gm_socket_if_count) + return; + + EVENT_OFF(pim->t_gm_recv); + close(pim->gm_socket); + pim->gm_socket = -1; +} + +static void gm_start(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp; + + assert(pim_ifp); + assert(pim_ifp->pim); + assert(pim_ifp->mroute_vif_index >= 0); + assert(!pim_ifp->mld); + + gm_vrf_socket_incref(pim_ifp->pim); + + gm_ifp = XCALLOC(MTYPE_GM_IFACE, sizeof(*gm_ifp)); + gm_ifp->ifp = ifp; + pim_ifp->mld = gm_ifp; + gm_ifp->pim = pim_ifp->pim; + monotime(&gm_ifp->started); + + zlog_info(log_ifp("starting MLD")); + + if (pim_ifp->mld_version == 1) + gm_ifp->cur_version = GM_MLDV1; + else + gm_ifp->cur_version = GM_MLDV2; + + gm_ifp->cur_qrv = pim_ifp->gm_default_robustness_variable; + gm_ifp->cur_query_intv = pim_ifp->gm_default_query_interval * 1000; + gm_ifp->cur_query_intv_trig = + pim_ifp->gm_specific_query_max_response_time_dsec * 100; + gm_ifp->cur_max_resp = pim_ifp->gm_query_max_response_time_dsec * 100; + gm_ifp->cur_lmqc = pim_ifp->gm_last_member_query_count; + + gm_ifp->cfg_timing_fuzz.tv_sec = 0; + gm_ifp->cfg_timing_fuzz.tv_usec = 10 * 1000; + + gm_sgs_init(gm_ifp->sgs); + gm_subscribers_init(gm_ifp->subscribers); + gm_packet_expires_init(gm_ifp->expires); + gm_grp_pends_init(gm_ifp->grp_pends); + gm_gsq_pends_init(gm_ifp->gsq_pends); + + frr_with_privs (&pimd_privs) { + struct ipv6_mreq mreq; + int ret; + + /* all-MLDv2 group */ + mreq.ipv6mr_multiaddr = gm_all_routers; + mreq.ipv6mr_interface = ifp->ifindex; + ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6, + IPV6_JOIN_GROUP, &mreq, sizeof(mreq)); + if (ret) + zlog_err("(%s) failed to join ff02::16 (all-MLDv2): %m", + ifp->name); + } +} + +void gm_group_delete(struct gm_if *gm_ifp) +{ + struct gm_sg *sg; + struct gm_packet_state *pkt; + struct gm_grp_pending *pend_grp; + struct gm_gsq_pending *pend_gsq; + struct gm_subscriber *subscriber; + + while ((pkt = gm_packet_expires_first(gm_ifp->expires))) + gm_packet_drop(pkt, false); + + while ((pend_grp = gm_grp_pends_pop(gm_ifp->grp_pends))) { + EVENT_OFF(pend_grp->t_expire); + XFREE(MTYPE_GM_GRP_PENDING, pend_grp); + } + + while ((pend_gsq = gm_gsq_pends_pop(gm_ifp->gsq_pends))) { + EVENT_OFF(pend_gsq->t_send); + XFREE(MTYPE_GM_GSQ_PENDING, pend_gsq); + } + + while ((sg = gm_sgs_pop(gm_ifp->sgs))) { + EVENT_OFF(sg->t_sg_expire); + assertf(!gm_packet_sg_subs_count(sg->subs_negative), "%pSG", + &sg->sgaddr); + assertf(!gm_packet_sg_subs_count(sg->subs_positive), "%pSG", + &sg->sgaddr); + + gm_sg_free(sg); + } + while ((subscriber = gm_subscribers_pop(gm_ifp->subscribers))) { + assertf(!gm_packets_count(subscriber->packets), "%pPA", + &subscriber->addr); + XFREE(MTYPE_GM_SUBSCRIBER, subscriber); + } +} + +void gm_ifp_teardown(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp; + + if (!pim_ifp || !pim_ifp->mld) + return; + + gm_ifp = pim_ifp->mld; + gm_ifp->stopping = true; + if (PIM_DEBUG_GM_EVENTS) + zlog_debug(log_ifp("MLD stop")); + + EVENT_OFF(gm_ifp->t_query); + EVENT_OFF(gm_ifp->t_other_querier); + EVENT_OFF(gm_ifp->t_expire); + + frr_with_privs (&pimd_privs) { + struct ipv6_mreq mreq; + int ret; + + /* all-MLDv2 group */ + mreq.ipv6mr_multiaddr = gm_all_routers; + mreq.ipv6mr_interface = ifp->ifindex; + ret = setsockopt(gm_ifp->pim->gm_socket, SOL_IPV6, + IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)); + if (ret) + zlog_err( + "(%s) failed to leave ff02::16 (all-MLDv2): %m", + ifp->name); + } + + gm_vrf_socket_decref(gm_ifp->pim); + + gm_group_delete(gm_ifp); + + gm_grp_pends_fini(gm_ifp->grp_pends); + gm_packet_expires_fini(gm_ifp->expires); + gm_subscribers_fini(gm_ifp->subscribers); + gm_sgs_fini(gm_ifp->sgs); + + XFREE(MTYPE_GM_IFACE, gm_ifp); + pim_ifp->mld = NULL; +} + +static void gm_update_ll(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp = pim_ifp->mld; + bool was_querier; + + was_querier = + !IPV6_ADDR_CMP(&gm_ifp->cur_ll_lowest, &gm_ifp->querier) && + !pim_addr_is_any(gm_ifp->querier); + + gm_ifp->cur_ll_lowest = pim_ifp->ll_lowest; + if (was_querier) + gm_ifp->querier = pim_ifp->ll_lowest; + EVENT_OFF(gm_ifp->t_query); + + if (pim_addr_is_any(gm_ifp->cur_ll_lowest)) { + if (was_querier) + zlog_info(log_ifp( + "lost link-local address, stopping querier")); + return; + } + + if (was_querier) + zlog_info(log_ifp("new link-local %pPA while querier"), + &gm_ifp->cur_ll_lowest); + else if (IPV6_ADDR_CMP(&gm_ifp->cur_ll_lowest, &gm_ifp->querier) < 0 || + pim_addr_is_any(gm_ifp->querier)) { + zlog_info(log_ifp("new link-local %pPA, becoming querier"), + &gm_ifp->cur_ll_lowest); + gm_ifp->querier = gm_ifp->cur_ll_lowest; + } else + return; + + gm_ifp->n_startup = gm_ifp->cur_qrv; + event_execute(router->master, gm_t_query, gm_ifp, 0, NULL); +} + +void gm_ifp_update(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp; + bool changed = false; + + if (!pim_ifp) + return; + if (!if_is_operative(ifp) || !pim_ifp->pim || + pim_ifp->mroute_vif_index < 0) { + gm_ifp_teardown(ifp); + return; + } + + /* + * If ipv6 mld is not enabled on interface, do not start mld activites. + */ + if (!pim_ifp->gm_enable) + return; + + if (!pim_ifp->mld) { + changed = true; + gm_start(ifp); + assume(pim_ifp->mld != NULL); + } + + gm_ifp = pim_ifp->mld; + if (IPV6_ADDR_CMP(&pim_ifp->ll_lowest, &gm_ifp->cur_ll_lowest)) + gm_update_ll(ifp); + + unsigned int cfg_query_intv = pim_ifp->gm_default_query_interval * 1000; + + if (gm_ifp->cur_query_intv != cfg_query_intv) { + gm_ifp->cur_query_intv = cfg_query_intv; + changed = true; + } + + unsigned int cfg_query_intv_trig = + pim_ifp->gm_specific_query_max_response_time_dsec * 100; + + if (gm_ifp->cur_query_intv_trig != cfg_query_intv_trig) { + gm_ifp->cur_query_intv_trig = cfg_query_intv_trig; + changed = true; + } + + unsigned int cfg_max_response = + pim_ifp->gm_query_max_response_time_dsec * 100; + + if (gm_ifp->cur_max_resp != cfg_max_response) + gm_ifp->cur_max_resp = cfg_max_response; + + if (gm_ifp->cur_lmqc != pim_ifp->gm_last_member_query_count) + gm_ifp->cur_lmqc = pim_ifp->gm_last_member_query_count; + + enum gm_version cfg_version; + + if (pim_ifp->mld_version == 1) + cfg_version = GM_MLDV1; + else + cfg_version = GM_MLDV2; + if (gm_ifp->cur_version != cfg_version) { + gm_ifp->cur_version = cfg_version; + changed = true; + } + + if (changed) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug(log_ifp( + "MLD querier config changed, querying")); + gm_bump_querier(gm_ifp); + } +} + +/* + * CLI (show commands only) + */ + +#include "lib/command.h" + +#include "pimd/pim6_mld_clippy.c" + +static struct vrf *gm_cmd_vrf_lookup(struct vty *vty, const char *vrf_str, + int *err) +{ + struct vrf *ret; + + if (!vrf_str) + return vrf_lookup_by_id(VRF_DEFAULT); + if (!strcmp(vrf_str, "all")) + return NULL; + ret = vrf_lookup_by_name(vrf_str); + if (ret) + return ret; + + vty_out(vty, "%% VRF %pSQq does not exist\n", vrf_str); + *err = CMD_WARNING; + return NULL; +} + +static void gm_show_if_one_detail(struct vty *vty, struct interface *ifp) +{ + struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info; + struct gm_if *gm_ifp; + bool querier; + size_t i; + + if (!pim_ifp) { + vty_out(vty, "Interface %s: no PIM/MLD config\n\n", ifp->name); + return; + } + + gm_ifp = pim_ifp->mld; + if (!gm_ifp) { + vty_out(vty, "Interface %s: MLD not running\n\n", ifp->name); + return; + } + + querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest); + + vty_out(vty, "Interface %s: MLD running\n", ifp->name); + vty_out(vty, " Uptime: %pTVMs\n", &gm_ifp->started); + vty_out(vty, " MLD version: %d\n", gm_ifp->cur_version); + vty_out(vty, " Querier: %pPA%s\n", &gm_ifp->querier, + querier ? " (this system)" : ""); + vty_out(vty, " Query timer: %pTH\n", gm_ifp->t_query); + vty_out(vty, " Other querier timer: %pTH\n", + gm_ifp->t_other_querier); + vty_out(vty, " Robustness value: %u\n", gm_ifp->cur_qrv); + vty_out(vty, " Query interval: %ums\n", + gm_ifp->cur_query_intv); + vty_out(vty, " Query response timer: %ums\n", gm_ifp->cur_max_resp); + vty_out(vty, " Last member query intv.: %ums\n", + gm_ifp->cur_query_intv_trig); + vty_out(vty, " %u expiry timers from general queries:\n", + gm_ifp->n_pending); + for (i = 0; i < gm_ifp->n_pending; i++) { + struct gm_general_pending *p = &gm_ifp->pending[i]; + + vty_out(vty, " %9pTVMs ago (query) -> %9pTVMu (expiry)\n", + &p->query, &p->expiry); + } + vty_out(vty, " %zu expiry timers from *,G queries\n", + gm_grp_pends_count(gm_ifp->grp_pends)); + vty_out(vty, " %zu expiry timers from S,G queries\n", + gm_gsq_pends_count(gm_ifp->gsq_pends)); + vty_out(vty, " %zu total *,G/S,G from %zu hosts in %zu bundles\n", + gm_sgs_count(gm_ifp->sgs), + gm_subscribers_count(gm_ifp->subscribers), + gm_packet_expires_count(gm_ifp->expires)); + vty_out(vty, "\n"); +} + +static void gm_show_if_one(struct vty *vty, struct interface *ifp, + json_object *js_if, struct ttable *tt) +{ + struct pim_interface *pim_ifp = (struct pim_interface *)ifp->info; + struct gm_if *gm_ifp = pim_ifp->mld; + bool querier; + + assume(js_if || tt); + + querier = IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest); + + if (js_if) { + json_object_string_add(js_if, "name", ifp->name); + json_object_string_addf(js_if, "address", "%pPA", + &pim_ifp->primary_address); + json_object_string_add(js_if, "state", "up"); + json_object_string_addf(js_if, "version", "%d", + gm_ifp->cur_version); + json_object_string_addf(js_if, "upTime", "%pTVMs", + &gm_ifp->started); + json_object_boolean_add(js_if, "querier", querier); + json_object_string_addf(js_if, "querierIp", "%pPA", + &gm_ifp->querier); + if (querier) + json_object_string_addf(js_if, "queryTimer", "%pTH", + gm_ifp->t_query); + else + json_object_string_addf(js_if, "otherQuerierTimer", + "%pTH", + gm_ifp->t_other_querier); + json_object_int_add(js_if, "timerRobustnessValue", + gm_ifp->cur_qrv); + json_object_int_add(js_if, "lastMemberQueryCount", + gm_ifp->cur_lmqc); + json_object_int_add(js_if, "timerQueryIntervalMsec", + gm_ifp->cur_query_intv); + json_object_int_add(js_if, "timerQueryResponseTimerMsec", + gm_ifp->cur_max_resp); + json_object_int_add(js_if, "timerLastMemberQueryIntervalMsec", + gm_ifp->cur_query_intv_trig); + } else { + ttable_add_row(tt, "%s|%s|%pPAs|%d|%s|%pPAs|%pTH|%pTVMs", + ifp->name, "up", &pim_ifp->primary_address, + gm_ifp->cur_version, querier ? "local" : "other", + &gm_ifp->querier, gm_ifp->t_query, + &gm_ifp->started); + } +} + +static void gm_show_if_vrf(struct vty *vty, struct vrf *vrf, const char *ifname, + bool detail, json_object *js) +{ + struct interface *ifp; + json_object *js_vrf = NULL; + struct pim_interface *pim_ifp; + struct ttable *tt = NULL; + char *table = NULL; + + if (js) { + js_vrf = json_object_new_object(); + json_object_object_add(js, vrf->name, js_vrf); + } + + if (!js && !detail) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Interface|State|Address|V|Querier|QuerierIp|Query Timer|Uptime"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + FOR_ALL_INTERFACES (vrf, ifp) { + json_object *js_if = NULL; + + if (ifname && strcmp(ifp->name, ifname)) + continue; + if (detail && !js) { + gm_show_if_one_detail(vty, ifp); + continue; + } + + pim_ifp = ifp->info; + + if (!pim_ifp || !pim_ifp->mld) + continue; + + if (js) { + js_if = json_object_new_object(); + /* + * If we have js as true and detail as false + * and if Coverity thinks that js_if is NULL + * because of a failed call to new then + * when we call gm_show_if_one below + * the tt can be deref'ed and as such + * FRR will crash. But since we know + * that json_object_new_object never fails + * then let's tell Coverity that this assumption + * is true. I'm not worried about fast path + * here at all. + */ + assert(js_if); + json_object_object_add(js_vrf, ifp->name, js_if); + } + + gm_show_if_one(vty, ifp, js_if, tt); + } + + /* Dump the generated table. */ + if (!js && !detail) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void gm_show_if(struct vty *vty, struct vrf *vrf, const char *ifname, + bool detail, json_object *js) +{ + if (vrf) + gm_show_if_vrf(vty, vrf, ifname, detail, js); + else + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + gm_show_if_vrf(vty, vrf, ifname, detail, js); +} + +DEFPY(gm_show_interface, + gm_show_interface_cmd, + "show ipv6 mld [vrf $vrf_str] interface [IFNAME | detail$detail] [json$json]", + SHOW_STR + IPV6_STR + MLD_STR + VRF_FULL_CMD_HELP_STR + "MLD interface information\n" + "Interface name\n" + "Detailed output\n" + JSON_STR) +{ + int ret = CMD_SUCCESS; + struct vrf *vrf; + json_object *js = NULL; + + vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret); + if (ret != CMD_SUCCESS) + return ret; + + if (json) + js = json_object_new_object(); + gm_show_if(vty, vrf, ifname, !!detail, js); + return vty_json(vty, js); +} + +static void gm_show_stats_one(struct vty *vty, struct gm_if *gm_ifp, + json_object *js_if) +{ + struct gm_if_stats *stats = &gm_ifp->stats; + /* clang-format off */ + struct { + const char *text; + const char *js_key; + uint64_t *val; + } *item, items[] = { + { "v2 reports received", "rxV2Reports", &stats->rx_new_report }, + { "v1 reports received", "rxV1Reports", &stats->rx_old_report }, + { "v1 done received", "rxV1Done", &stats->rx_old_leave }, + + { "v2 *,* queries received", "rxV2QueryGeneral", &stats->rx_query_new_general }, + { "v2 *,G queries received", "rxV2QueryGroup", &stats->rx_query_new_group }, + { "v2 S,G queries received", "rxV2QueryGroupSource", &stats->rx_query_new_groupsrc }, + { "v2 S-bit queries received", "rxV2QuerySBit", &stats->rx_query_new_sbit }, + { "v1 *,* queries received", "rxV1QueryGeneral", &stats->rx_query_old_general }, + { "v1 *,G queries received", "rxV1QueryGroup", &stats->rx_query_old_group }, + + { "v2 *,* queries sent", "txV2QueryGeneral", &stats->tx_query_new_general }, + { "v2 *,G queries sent", "txV2QueryGroup", &stats->tx_query_new_group }, + { "v2 S,G queries sent", "txV2QueryGroupSource", &stats->tx_query_new_groupsrc }, + { "v1 *,* queries sent", "txV1QueryGeneral", &stats->tx_query_old_general }, + { "v1 *,G queries sent", "txV1QueryGroup", &stats->tx_query_old_group }, + { "TX errors", "txErrors", &stats->tx_query_fail }, + + { "RX dropped (checksum error)", "rxDropChecksum", &stats->rx_drop_csum }, + { "RX dropped (invalid source)", "rxDropSrcAddr", &stats->rx_drop_srcaddr }, + { "RX dropped (invalid dest.)", "rxDropDstAddr", &stats->rx_drop_dstaddr }, + { "RX dropped (missing alert)", "rxDropRtrAlert", &stats->rx_drop_ra }, + { "RX dropped (malformed pkt.)", "rxDropMalformed", &stats->rx_drop_malformed }, + { "RX truncated reports", "rxTruncatedRep", &stats->rx_trunc_report }, + }; + /* clang-format on */ + + for (item = items; item < items + array_size(items); item++) { + if (js_if) + json_object_int_add(js_if, item->js_key, *item->val); + else + vty_out(vty, " %-30s %" PRIu64 "\n", item->text, + *item->val); + } +} + +static void gm_show_stats_vrf(struct vty *vty, struct vrf *vrf, + const char *ifname, json_object *js) +{ + struct interface *ifp; + json_object *js_vrf; + + if (js) { + js_vrf = json_object_new_object(); + json_object_object_add(js, vrf->name, js_vrf); + } + + FOR_ALL_INTERFACES (vrf, ifp) { + struct pim_interface *pim_ifp; + struct gm_if *gm_ifp; + json_object *js_if = NULL; + + if (ifname && strcmp(ifp->name, ifname)) + continue; + + if (!ifp->info) + continue; + pim_ifp = ifp->info; + if (!pim_ifp->mld) + continue; + gm_ifp = pim_ifp->mld; + + if (js) { + js_if = json_object_new_object(); + json_object_object_add(js_vrf, ifp->name, js_if); + } else { + vty_out(vty, "Interface: %s\n", ifp->name); + } + gm_show_stats_one(vty, gm_ifp, js_if); + if (!js) + vty_out(vty, "\n"); + } +} + +DEFPY(gm_show_interface_stats, + gm_show_interface_stats_cmd, + "show ipv6 mld [vrf $vrf_str] statistics [interface IFNAME] [json$json]", + SHOW_STR + IPV6_STR + MLD_STR + VRF_FULL_CMD_HELP_STR + "MLD statistics\n" + INTERFACE_STR + "Interface name\n" + JSON_STR) +{ + int ret = CMD_SUCCESS; + struct vrf *vrf; + json_object *js = NULL; + + vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret); + if (ret != CMD_SUCCESS) + return ret; + + if (json) + js = json_object_new_object(); + + if (vrf) + gm_show_stats_vrf(vty, vrf, ifname, js); + else + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + gm_show_stats_vrf(vty, vrf, ifname, js); + return vty_json(vty, js); +} + +static void gm_show_joins_one(struct vty *vty, struct gm_if *gm_ifp, + const struct prefix_ipv6 *groups, + const struct prefix_ipv6 *sources, bool detail, + json_object *js_if) +{ + struct gm_sg *sg, *sg_start; + json_object *js_group = NULL; + pim_addr js_grpaddr = PIMADDR_ANY; + struct gm_subscriber sub_ref = {}, *sub_untracked; + + if (groups) { + struct gm_sg sg_ref = {}; + + sg_ref.sgaddr.grp = pim_addr_from_prefix(groups); + sg_start = gm_sgs_find_gteq(gm_ifp->sgs, &sg_ref); + } else + sg_start = gm_sgs_first(gm_ifp->sgs); + + sub_ref.addr = gm_dummy_untracked; + sub_untracked = gm_subscribers_find(gm_ifp->subscribers, &sub_ref); + /* NB: sub_untracked may be NULL if no untracked joins exist */ + + frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) { + struct timeval *recent = NULL, *untracked = NULL; + json_object *js_src; + + if (groups) { + struct prefix grp_p; + + pim_addr_to_prefix(&grp_p, sg->sgaddr.grp); + if (!prefix_match(groups, &grp_p)) + break; + } + + if (sources) { + struct prefix src_p; + + pim_addr_to_prefix(&src_p, sg->sgaddr.src); + if (!prefix_match(sources, &src_p)) + continue; + } + + if (sg->most_recent) { + struct gm_packet_state *packet; + + packet = gm_packet_sg2state(sg->most_recent); + recent = &packet->received; + } + + if (sub_untracked) { + struct gm_packet_state *packet; + struct gm_packet_sg *item; + + item = gm_packet_sg_find(sg, GM_SUB_POS, sub_untracked); + if (item) { + packet = gm_packet_sg2state(item); + untracked = &packet->received; + } + } + + if (!js_if) { + FMT_NSTD_BEGIN; /* %.0p */ + vty_out(vty, + "%-30pPA %-30pPAs %-16s %10.0pTVMs %10.0pTVMs %10.0pTVMs\n", + &sg->sgaddr.grp, &sg->sgaddr.src, + gm_states[sg->state], recent, untracked, + &sg->created); + + if (!detail) + continue; + + struct gm_packet_sg *item; + struct gm_packet_state *packet; + + frr_each (gm_packet_sg_subs, sg->subs_positive, item) { + packet = gm_packet_sg2state(item); + + if (packet->subscriber == sub_untracked) + continue; + vty_out(vty, " %-58pPA %-16s %10.0pTVMs\n", + &packet->subscriber->addr, "(JOIN)", + &packet->received); + } + frr_each (gm_packet_sg_subs, sg->subs_negative, item) { + packet = gm_packet_sg2state(item); + + if (packet->subscriber == sub_untracked) + continue; + vty_out(vty, " %-58pPA %-16s %10.0pTVMs\n", + &packet->subscriber->addr, "(PRUNE)", + &packet->received); + } + FMT_NSTD_END; /* %.0p */ + continue; + } + /* if (js_if) */ + + if (!js_group || pim_addr_cmp(js_grpaddr, sg->sgaddr.grp)) { + js_group = json_object_new_object(); + json_object_object_addf(js_if, js_group, "%pPA", + &sg->sgaddr.grp); + js_grpaddr = sg->sgaddr.grp; + } + + js_src = json_object_new_object(); + json_object_object_addf(js_group, js_src, "%pPAs", + &sg->sgaddr.src); + + json_object_string_add(js_src, "state", gm_states[sg->state]); + json_object_string_addf(js_src, "created", "%pTVMs", + &sg->created); + json_object_string_addf(js_src, "lastSeen", "%pTVMs", recent); + + if (untracked) + json_object_string_addf(js_src, "untrackedLastSeen", + "%pTVMs", untracked); + if (!detail) + continue; + + json_object *js_subs; + struct gm_packet_sg *item; + struct gm_packet_state *packet; + + js_subs = json_object_new_object(); + json_object_object_add(js_src, "joinedBy", js_subs); + frr_each (gm_packet_sg_subs, sg->subs_positive, item) { + packet = gm_packet_sg2state(item); + if (packet->subscriber == sub_untracked) + continue; + + json_object *js_sub; + + js_sub = json_object_new_object(); + json_object_object_addf(js_subs, js_sub, "%pPA", + &packet->subscriber->addr); + json_object_string_addf(js_sub, "lastSeen", "%pTVMs", + &packet->received); + } + + js_subs = json_object_new_object(); + json_object_object_add(js_src, "prunedBy", js_subs); + frr_each (gm_packet_sg_subs, sg->subs_negative, item) { + packet = gm_packet_sg2state(item); + if (packet->subscriber == sub_untracked) + continue; + + json_object *js_sub; + + js_sub = json_object_new_object(); + json_object_object_addf(js_subs, js_sub, "%pPA", + &packet->subscriber->addr); + json_object_string_addf(js_sub, "lastSeen", "%pTVMs", + &packet->received); + } + } +} + +static void gm_show_joins_vrf(struct vty *vty, struct vrf *vrf, + const char *ifname, + const struct prefix_ipv6 *groups, + const struct prefix_ipv6 *sources, bool detail, + json_object *js) +{ + struct interface *ifp; + json_object *js_vrf; + + if (js) { + js_vrf = json_object_new_object(); + json_object_string_add(js_vrf, "vrf", vrf->name); + json_object_object_add(js, vrf->name, js_vrf); + } + + FOR_ALL_INTERFACES (vrf, ifp) { + struct pim_interface *pim_ifp; + struct gm_if *gm_ifp; + json_object *js_if = NULL; + + if (ifname && strcmp(ifp->name, ifname)) + continue; + + if (!ifp->info) + continue; + pim_ifp = ifp->info; + if (!pim_ifp->mld) + continue; + gm_ifp = pim_ifp->mld; + + if (js) { + js_if = json_object_new_object(); + json_object_object_add(js_vrf, ifp->name, js_if); + } + + if (!js && !ifname) + vty_out(vty, "\nOn interface %s:\n", ifp->name); + + gm_show_joins_one(vty, gm_ifp, groups, sources, detail, js_if); + } +} + +DEFPY(gm_show_interface_joins, + gm_show_interface_joins_cmd, + "show ipv6 mld [vrf $vrf_str] joins [{interface IFNAME|groups X:X::X:X/M|sources X:X::X:X/M|detail$detail}] [json$json]", + SHOW_STR + IPV6_STR + MLD_STR + VRF_FULL_CMD_HELP_STR + "MLD joined groups & sources\n" + INTERFACE_STR + "Interface name\n" + "Limit output to group range\n" + "Show groups covered by this prefix\n" + "Limit output to source range\n" + "Show sources covered by this prefix\n" + "Show details, including tracked receivers\n" + JSON_STR) +{ + int ret = CMD_SUCCESS; + struct vrf *vrf; + json_object *js = NULL; + + vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret); + if (ret != CMD_SUCCESS) + return ret; + + if (json) + js = json_object_new_object(); + else + vty_out(vty, "%-30s %-30s %-16s %10s %10s %10s\n", "Group", + "Source", "State", "LastSeen", "NonTrkSeen", "Created"); + + if (vrf) + gm_show_joins_vrf(vty, vrf, ifname, groups, sources, !!detail, + js); + else + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + gm_show_joins_vrf(vty, vrf, ifname, groups, sources, + !!detail, js); + return vty_json(vty, js); +} + +static void gm_show_groups(struct vty *vty, struct vrf *vrf, bool uj) +{ + struct interface *ifp; + struct ttable *tt = NULL; + char *table; + json_object *json = NULL; + json_object *json_iface = NULL; + json_object *json_group = NULL; + json_object *json_groups = NULL; + struct pim_instance *pim = vrf->info; + + if (uj) { + json = json_object_new_object(); + json_object_int_add(json, "totalGroups", pim->gm_group_count); + json_object_int_add(json, "watermarkLimit", + pim->gm_watermark_limit); + } else { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Interface|Group|Version|Uptime"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + vty_out(vty, "Total MLD groups: %u\n", pim->gm_group_count); + vty_out(vty, "Watermark warn limit(%s): %u\n", + pim->gm_watermark_limit ? "Set" : "Not Set", + pim->gm_watermark_limit); + } + + /* scan interfaces */ + FOR_ALL_INTERFACES (vrf, ifp) { + + struct pim_interface *pim_ifp = ifp->info; + struct gm_if *gm_ifp; + struct gm_sg *sg; + + if (!pim_ifp) + continue; + + gm_ifp = pim_ifp->mld; + if (!gm_ifp) + continue; + + /* scan mld groups */ + frr_each (gm_sgs, gm_ifp->sgs, sg) { + + if (uj) { + json_object_object_get_ex(json, ifp->name, + &json_iface); + + if (!json_iface) { + json_iface = json_object_new_object(); + json_object_pim_ifp_add(json_iface, + ifp); + json_object_object_add(json, ifp->name, + json_iface); + json_groups = json_object_new_array(); + json_object_object_add(json_iface, + "groups", + json_groups); + } + + json_group = json_object_new_object(); + json_object_string_addf(json_group, "group", + "%pPAs", + &sg->sgaddr.grp); + + json_object_int_add(json_group, "version", + pim_ifp->mld_version); + json_object_string_addf(json_group, "uptime", + "%pTVMs", &sg->created); + json_object_array_add(json_groups, json_group); + } else { + ttable_add_row(tt, "%s|%pPAs|%d|%pTVMs", + ifp->name, &sg->sgaddr.grp, + pim_ifp->mld_version, + &sg->created); + } + } /* scan gm groups */ + } /* scan interfaces */ + + if (uj) + vty_json(vty, json); + else { + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +DEFPY(gm_show_mld_groups, + gm_show_mld_groups_cmd, + "show ipv6 mld [vrf $vrf_str] groups [json$json]", + SHOW_STR + IPV6_STR + MLD_STR + VRF_FULL_CMD_HELP_STR + MLD_GROUP_STR + JSON_STR) +{ + int ret = CMD_SUCCESS; + struct vrf *vrf; + + vrf = gm_cmd_vrf_lookup(vty, vrf_str, &ret); + if (ret != CMD_SUCCESS) + return ret; + + if (vrf) + gm_show_groups(vty, vrf, !!json); + else + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + gm_show_groups(vty, vrf, !!json); + + return CMD_SUCCESS; +} + +DEFPY(gm_debug_show, + gm_debug_show_cmd, + "debug show mld interface IFNAME", + DEBUG_STR + SHOW_STR + MLD_STR + INTERFACE_STR + "interface name\n") +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct gm_if *gm_ifp; + + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + if (!ifp) { + vty_out(vty, "%% no such interface: %pSQq\n", ifname); + return CMD_WARNING; + } + + pim_ifp = ifp->info; + if (!pim_ifp) { + vty_out(vty, "%% no PIM state for interface %pSQq\n", ifname); + return CMD_WARNING; + } + + gm_ifp = pim_ifp->mld; + if (!gm_ifp) { + vty_out(vty, "%% no MLD state for interface %pSQq\n", ifname); + return CMD_WARNING; + } + + vty_out(vty, "querier: %pPA\n", &gm_ifp->querier); + vty_out(vty, "ll_lowest: %pPA\n\n", &pim_ifp->ll_lowest); + vty_out(vty, "t_query: %pTHD\n", gm_ifp->t_query); + vty_out(vty, "t_other_querier: %pTHD\n", gm_ifp->t_other_querier); + vty_out(vty, "t_expire: %pTHD\n", gm_ifp->t_expire); + + vty_out(vty, "\nn_pending: %u\n", gm_ifp->n_pending); + for (size_t i = 0; i < gm_ifp->n_pending; i++) { + int64_t query, expiry; + + query = monotime_since(&gm_ifp->pending[i].query, NULL); + expiry = monotime_until(&gm_ifp->pending[i].expiry, NULL); + + vty_out(vty, "[%zu]: query %"PRId64"ms ago, expiry in %"PRId64"ms\n", + i, query / 1000, expiry / 1000); + } + + struct gm_sg *sg; + struct gm_packet_state *pkt; + struct gm_packet_sg *item; + struct gm_subscriber *subscriber; + + vty_out(vty, "\n%zu S,G entries:\n", gm_sgs_count(gm_ifp->sgs)); + frr_each (gm_sgs, gm_ifp->sgs, sg) { + vty_out(vty, "\t%pSG t_expire=%pTHD\n", &sg->sgaddr, + sg->t_sg_expire); + + vty_out(vty, "\t @pos:%zu\n", + gm_packet_sg_subs_count(sg->subs_positive)); + frr_each (gm_packet_sg_subs, sg->subs_positive, item) { + pkt = gm_packet_sg2state(item); + + vty_out(vty, "\t\t+%s%s [%pPAs %p] %p+%u\n", + item->is_src ? "S" : "", + item->is_excl ? "E" : "", + &pkt->subscriber->addr, pkt->subscriber, pkt, + item->offset); + + assert(item->sg == sg); + } + vty_out(vty, "\t @neg:%zu\n", + gm_packet_sg_subs_count(sg->subs_negative)); + frr_each (gm_packet_sg_subs, sg->subs_negative, item) { + pkt = gm_packet_sg2state(item); + + vty_out(vty, "\t\t-%s%s [%pPAs %p] %p+%u\n", + item->is_src ? "S" : "", + item->is_excl ? "E" : "", + &pkt->subscriber->addr, pkt->subscriber, pkt, + item->offset); + + assert(item->sg == sg); + } + } + + vty_out(vty, "\n%zu subscribers:\n", + gm_subscribers_count(gm_ifp->subscribers)); + frr_each (gm_subscribers, gm_ifp->subscribers, subscriber) { + vty_out(vty, "\t%pPA %p %zu packets\n", &subscriber->addr, + subscriber, gm_packets_count(subscriber->packets)); + + frr_each (gm_packets, subscriber->packets, pkt) { + vty_out(vty, "\t\t%p %.3fs ago %u of %u items active\n", + pkt, + monotime_since(&pkt->received, NULL) * + 0.000001f, + pkt->n_active, pkt->n_sg); + + for (size_t i = 0; i < pkt->n_sg; i++) { + item = pkt->items + i; + + vty_out(vty, "\t\t[%zu]", i); + + if (!item->sg) { + vty_out(vty, " inactive\n"); + continue; + } + + vty_out(vty, " %s%s %pSG nE=%u\n", + item->is_src ? "S" : "", + item->is_excl ? "E" : "", + &item->sg->sgaddr, item->n_exclude); + } + } + } + + return CMD_SUCCESS; +} + +DEFPY(gm_debug_iface_cfg, + gm_debug_iface_cfg_cmd, + "debug ipv6 mld {" + "robustness (0-7)|" + "query-max-response-time (1-8387584)" + "}", + DEBUG_STR + IPV6_STR + "Multicast Listener Discovery\n" + "QRV\nQRV\n" + "maxresp\nmaxresp\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct pim_interface *pim_ifp; + struct gm_if *gm_ifp; + bool changed = false; + + pim_ifp = ifp->info; + if (!pim_ifp) { + vty_out(vty, "%% no PIM state for interface %pSQq\n", + ifp->name); + return CMD_WARNING; + } + gm_ifp = pim_ifp->mld; + if (!gm_ifp) { + vty_out(vty, "%% no MLD state for interface %pSQq\n", + ifp->name); + return CMD_WARNING; + } + + if (robustness_str && gm_ifp->cur_qrv != robustness) { + gm_ifp->cur_qrv = robustness; + changed = true; + } + if (query_max_response_time_str && + gm_ifp->cur_max_resp != (unsigned int)query_max_response_time) { + gm_ifp->cur_max_resp = query_max_response_time; + changed = true; + } + + if (changed) { + vty_out(vty, "%% MLD querier config changed, bumping\n"); + gm_bump_querier(gm_ifp); + } + return CMD_SUCCESS; +} + +void gm_cli_init(void); + +void gm_cli_init(void) +{ + install_element(VIEW_NODE, &gm_show_interface_cmd); + install_element(VIEW_NODE, &gm_show_interface_stats_cmd); + install_element(VIEW_NODE, &gm_show_interface_joins_cmd); + install_element(VIEW_NODE, &gm_show_mld_groups_cmd); + + install_element(VIEW_NODE, &gm_debug_show_cmd); + install_element(INTERFACE_NODE, &gm_debug_iface_cfg_cmd); +} diff --git a/pimd/pim6_mld.h b/pimd/pim6_mld.h new file mode 100644 index 0000000..183ab2f --- /dev/null +++ b/pimd/pim6_mld.h @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIMv6 MLD querier + * Copyright (C) 2021-2022 David Lamparter for NetDEF, Inc. + */ + +#ifndef PIM6_MLD_H +#define PIM6_MLD_H + +#include "typesafe.h" +#include "pim_addr.h" + +struct event; +struct pim_instance; +struct gm_packet_sg; +struct gm_if; +struct channel_oil; + +#define MLD_DEFAULT_VERSION 2 + +/* see comment below on subs_negative/subs_positive */ +enum gm_sub_sense { + /* negative/pruning: S,G in EXCLUDE */ + GM_SUB_NEG = 0, + /* positive/joining: *,G in EXCLUDE and S,G in INCLUDE */ + GM_SUB_POS = 1, +}; + +enum gm_sg_state { + GM_SG_NOINFO = 0, + GM_SG_JOIN, + GM_SG_JOIN_EXPIRING, + /* remaining 3 only valid for S,G when *,G in EXCLUDE */ + GM_SG_PRUNE, + GM_SG_NOPRUNE, + GM_SG_NOPRUNE_EXPIRING, +}; + +/* If the timer gm_t_sg_expire is started without a leave message being received, + * the sg->state should be moved to expiring states. + * When the timer expires, we do not expect the state to be in join state. + * If a JOIN message is received while the timer is running, + * the state will be moved to JOIN and this timer will be switched off. + * Hence the below state transition is done. + */ +#define GM_UPDATE_SG_STATE(sg) \ + do { \ + if (sg->state == GM_SG_JOIN) \ + sg->state = GM_SG_JOIN_EXPIRING; \ + else if (sg->state == GM_SG_NOPRUNE) \ + sg->state = GM_SG_NOPRUNE_EXPIRING; \ + } while (0) + +static inline bool gm_sg_state_want_join(enum gm_sg_state state) +{ + return state != GM_SG_NOINFO && state != GM_SG_PRUNE; +} + +/* MLD (S,G) state (on an interface) + * + * group is always != ::, src is :: for (*,G) joins. sort order in RB tree is + * such that sources for a particular group can be iterated by starting at the + * group. For INCLUDE, no (*,G) entry exists, only (S,G). + */ + +PREDECL_RBTREE_UNIQ(gm_packet_sg_subs); +PREDECL_RBTREE_UNIQ(gm_sgs); +struct gm_sg { + pim_sgaddr sgaddr; + struct gm_if *iface; + struct gm_sgs_item itm; + + enum gm_sg_state state; + struct channel_oil *oil; + bool tib_joined; + + struct timeval created; + + /* if a group- or group-and-source specific query is running + * (implies we haven't received any report yet, since it's cancelled + * by that) + */ + struct event *t_sg_expire; + + /* last-member-left triggered queries (group/group-source specific) + * + * this timer will be running even if we aren't the elected querier, + * in case the election result changes midway through. + */ + struct event *t_sg_query; + + /* we must keep sending (QRV) queries even if we get a positive + * response, to make sure other routers are updated. query_sbit + * will be set in that case, since other routers need the *response*, + * not the *query* + */ + uint8_t n_query; + bool query_sbit; + + /* subs_positive tracks gm_packet_sg resulting in a JOIN, i.e. for + * (*,G) it has *EXCLUDE* items, for (S,G) it has *INCLUDE* items. + * + * subs_negative is always empty for (*,G) and tracks EXCLUDE items + * for (S,G). This means that an (S,G) entry is active as a PRUNE if + * len(src->subs_negative) == len(grp->subs_positive) + * && len(src->subs_positive) == 0 + * (i.e. all receivers for the group opted to exclude this S,G and + * noone did an SSM join for the S,G) + */ + union { + struct { + struct gm_packet_sg_subs_head subs_negative[1]; + struct gm_packet_sg_subs_head subs_positive[1]; + }; + struct gm_packet_sg_subs_head subs[2]; + }; + + /* If the elected querier is not ourselves, queries and reports might + * get reordered in rare circumstances, i.e. the report could arrive + * just a microsecond before the query kicks off the timer. This can + * then result in us thinking there are no more receivers since no + * report might be received during the query period. + * + * To avoid this, keep track of the most recent report for this (S,G) + * so we can do a quick check to add just a little bit of slack. + * + * EXCLUDE S,Gs are never in most_recent. + */ + struct gm_packet_sg *most_recent; +}; +int gm_sg_cmp(const struct gm_sg *a, const struct gm_sg *b); +DECLARE_RBTREE_UNIQ(gm_sgs, struct gm_sg, itm, gm_sg_cmp); + +/* host tracking entry. addr will be one of: + * + * :: - used by hosts during address acquisition + * ::1 - may show up on some OS for joins by the router itself + * link-local - regular operation by MLDv2 hosts + * ffff:..:ffff - MLDv1 entry (cannot be tracked due to report suppression) + * + * global scope IPv6 addresses can never show up here + */ +PREDECL_HASH(gm_subscribers); +PREDECL_DLIST(gm_packets); +struct gm_subscriber { + pim_addr addr; + struct gm_subscribers_item itm; + + struct gm_if *iface; + size_t refcount; + + struct gm_packets_head packets[1]; + + struct timeval created; +}; + +/* + * MLD join state is kept batched by packet. Since the timers for all items + * in a packet are the same, this reduces the number of timers we're keeping + * track of. It also eases tracking for EXCLUDE state groups because the + * excluded sources are in the same packet. (MLD does not support splitting + * that if it exceeds MTU, it's always a full replace for exclude.) + * + * Since packets may be partially superseded by newer packets, the "active" + * field is used to track this. + */ + +/* gm_packet_sg is allocated as part of gm_packet_state, note the items[0] + * array at the end of that. gm_packet_sg is NEVER directly allocated with + * XMALLOC/XFREE. + */ +struct gm_packet_sg { + /* non-NULL as long as this gm_packet_sg is the most recent entry + * for (subscriber,S,G). Cleared to NULL when a newer packet by the + * subscriber replaces this item. + * + * (Old items are kept around so we don't need to realloc/resize + * gm_packet_state, which would mess up a whole lot of pointers) + */ + struct gm_sg *sg; + + /* gm_sg -> (subscriber, gm_packet_sg) + * only on RB-tree while sg != NULL, i.e. not superseded by newer. + */ + struct gm_packet_sg_subs_item subs_itm; + + bool is_src : 1; /* := (src != ::) */ + bool is_excl : 1; + + /* for getting back to struct gm_packet_state, cf. + * gm_packet_sg2state() below + */ + uint16_t offset; + + /* if this is a group entry in EXCLUDE state, n_exclude counts how + * many sources are on the exclude list here. They follow immediately + * after. + */ + uint16_t n_exclude; +}; + +#define gm_packet_sg2state(sg) \ + container_of(sg, struct gm_packet_state, items[sg->offset]) + +PREDECL_DLIST(gm_packet_expires); +struct gm_packet_state { + struct gm_if *iface; + struct gm_subscriber *subscriber; + struct gm_packets_item pkt_itm; + + struct timeval received; + struct gm_packet_expires_item exp_itm; + + /* n_active starts equal to n_sg; whenever active is set to false on + * an item it is decremented. When n_active == 0, the packet can be + * freed. + */ + uint16_t n_sg, n_active; + struct gm_packet_sg items[0]; +}; + +/* general queries are rather different from group/S,G specific queries; it's + * not particularly efficient or useful to try to shoehorn them into the S,G + * timers. Instead, we keep a history of recent queries and their implied + * expiries. + */ +struct gm_general_pending { + struct timeval query, expiry; +}; + +/* similarly, group queries also age out S,G entries for the group, but in + * this case we only keep one query for each group + * + * why is this not in the *,G gm_sg? There may not be one (for INCLUDE mode + * groups, or groups we don't know about.) Also, malicious clients could spam + * random group-specific queries to trigger resource exhaustion, so it makes + * sense to limit these. + */ +PREDECL_RBTREE_UNIQ(gm_grp_pends); +struct gm_grp_pending { + struct gm_grp_pends_item itm; + struct gm_if *iface; + pim_addr grp; + + struct timeval query; + struct event *t_expire; +}; + +/* guaranteed MTU for IPv6 is 1280 bytes. IPv6 header is 40 bytes, MLDv2 + * query header is 24 bytes, RA option is 8 bytes - leaves 1208 bytes for the + * source list, which is 151 IPv6 addresses. But we may have some more IPv6 + * extension headers (e.g. IPsec AH), so just cap to 128 + */ +#define MLD_V2Q_MTU_MAX_SOURCES 128 + +/* group-and-source-specific queries are bundled together, if some host joins + * multiple sources it's likely to drop all at the same time. + * + * Unlike gm_grp_pending, this is only used for aggregation since the S,G + * state is kept directly in the gm_sg structure. + */ +PREDECL_HASH(gm_gsq_pends); +struct gm_gsq_pending { + struct gm_gsq_pends_item itm; + + struct gm_if *iface; + struct event *t_send; + + pim_addr grp; + bool s_bit; + + size_t n_src; + pim_addr srcs[MLD_V2Q_MTU_MAX_SOURCES]; +}; + + +/* The size of this history is limited by QRV, i.e. there can't be more than + * 8 items here. + */ +#define GM_MAX_PENDING 8 + +enum gm_version { + GM_NONE, + GM_MLDV1, + GM_MLDV2, +}; + +struct gm_if_stats { + uint64_t rx_drop_csum; + uint64_t rx_drop_srcaddr; + uint64_t rx_drop_dstaddr; + uint64_t rx_drop_ra; + uint64_t rx_drop_malformed; + uint64_t rx_trunc_report; + + /* since the types are different, this is rx_old_* not of rx_*_old */ + uint64_t rx_old_report; + uint64_t rx_old_leave; + uint64_t rx_new_report; + + uint64_t rx_query_new_general; + uint64_t rx_query_new_group; + uint64_t rx_query_new_groupsrc; + uint64_t rx_query_new_sbit; + uint64_t rx_query_old_general; + uint64_t rx_query_old_group; + + uint64_t tx_query_new_general; + uint64_t tx_query_new_group; + uint64_t tx_query_new_groupsrc; + uint64_t tx_query_old_general; + uint64_t tx_query_old_group; + + uint64_t tx_query_fail; +}; + +struct gm_if { + struct interface *ifp; + struct pim_instance *pim; + struct event *t_query, *t_other_querier, *t_expire; + + bool stopping; + + uint8_t n_startup; + + uint8_t cur_qrv; + unsigned int cur_query_intv; /* ms */ + unsigned int cur_query_intv_trig; /* ms */ + unsigned int cur_max_resp; /* ms */ + enum gm_version cur_version; + int cur_lmqc; /* last member query count in ds */ + + /* this value (positive, default 10ms) defines our "timing tolerance": + * - added to deadlines for expiring joins + * - used to look backwards in time for queries, in case a report was + * reordered before the query + */ + struct timeval cfg_timing_fuzz; + + /* items in pending[] are sorted by expiry, pending[0] is earliest */ + struct gm_general_pending pending[GM_MAX_PENDING]; + uint8_t n_pending; + struct gm_grp_pends_head grp_pends[1]; + struct gm_gsq_pends_head gsq_pends[1]; + + pim_addr querier; + pim_addr cur_ll_lowest; + + struct gm_sgs_head sgs[1]; + struct gm_subscribers_head subscribers[1]; + struct gm_packet_expires_head expires[1]; + + struct timeval started; + struct gm_if_stats stats; +}; + +#if PIM_IPV == 6 +extern void gm_ifp_update(struct interface *ifp); +extern void gm_ifp_teardown(struct interface *ifp); +extern void gm_group_delete(struct gm_if *gm_ifp); +#else +static inline void gm_ifp_update(struct interface *ifp) +{ +} + +static inline void gm_ifp_teardown(struct interface *ifp) +{ +} +#endif + +extern void gm_cli_init(void); +bool in6_multicast_nofwd(const pim_addr *addr); + +#endif /* PIM6_MLD_H */ diff --git a/pimd/pim6_mld_protocol.h b/pimd/pim6_mld_protocol.h new file mode 100644 index 0000000..08d7871 --- /dev/null +++ b/pimd/pim6_mld_protocol.h @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MLD protocol definitions + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + */ + +#ifndef _PIM6_MLD_PROTOCOL_H +#define _PIM6_MLD_PROTOCOL_H + +#include +#include + +/* There is a struct icmp6_hdr provided by OS, but it includes 4 bytes of data. + * Not helpful for us if we want to put the MLD struct after it. + */ + +struct icmp6_plain_hdr { + uint8_t icmp6_type; + uint8_t icmp6_code; + uint16_t icmp6_cksum; +}; +static_assert(sizeof(struct icmp6_plain_hdr) == 4, "struct mismatch"); +static_assert(alignof(struct icmp6_plain_hdr) <= 4, "struct mismatch"); + +/* for MLDv1 query, report and leave all use the same packet format */ +struct mld_v1_pkt { + uint16_t max_resp_code; + uint16_t rsvd0; + struct in6_addr grp; +}; +static_assert(sizeof(struct mld_v1_pkt) == 20, "struct mismatch"); +static_assert(alignof(struct mld_v1_pkt) <= 4, "struct mismatch"); + + +struct mld_v2_query_hdr { + uint16_t max_resp_code; + uint16_t rsvd0; + struct in6_addr grp; + uint8_t flags; + uint8_t qqic; + uint16_t n_src; + struct in6_addr srcs[0]; +}; +static_assert(sizeof(struct mld_v2_query_hdr) == 24, "struct mismatch"); +static_assert(alignof(struct mld_v2_query_hdr) <= 4, "struct mismatch"); + + +struct mld_v2_report_hdr { + uint16_t rsvd; + uint16_t n_records; +}; +static_assert(sizeof(struct mld_v2_report_hdr) == 4, "struct mismatch"); +static_assert(alignof(struct mld_v2_report_hdr) <= 4, "struct mismatch"); + + +struct mld_v2_rec_hdr { + uint8_t type; + uint8_t aux_len; + uint16_t n_src; + struct in6_addr grp; + struct in6_addr srcs[0]; +}; +static_assert(sizeof(struct mld_v2_rec_hdr) == 20, "struct mismatch"); +static_assert(alignof(struct mld_v2_rec_hdr) <= 4, "struct mismatch"); + +/* clang-format off */ +enum icmp6_mld_type { + ICMP6_MLD_QUERY = 130, + ICMP6_MLD_V1_REPORT = 131, + ICMP6_MLD_V1_DONE = 132, + ICMP6_MLD_V2_REPORT = 143, +}; + +enum mld_v2_rec_type { + MLD_RECTYPE_IS_INCLUDE = 1, + MLD_RECTYPE_IS_EXCLUDE = 2, + MLD_RECTYPE_CHANGE_TO_INCLUDE = 3, + MLD_RECTYPE_CHANGE_TO_EXCLUDE = 4, + MLD_RECTYPE_ALLOW_NEW_SOURCES = 5, + MLD_RECTYPE_BLOCK_OLD_SOURCES = 6, +}; +/* clang-format on */ + +/* helper functions */ + +static inline unsigned int mld_max_resp_decode(uint16_t wire) +{ + uint16_t code = ntohs(wire); + uint8_t exp; + + if (code < 0x8000) + return code; + exp = (code >> 12) & 0x7; + return ((code & 0xfff) | 0x1000) << (exp + 3); +} + +static inline uint16_t mld_max_resp_encode(uint32_t value) +{ + uint16_t code; + uint8_t exp; + + if (value < 0x8000) + code = value; + else { + exp = 16 - __builtin_clz(value); + code = (value >> (exp + 3)) & 0xfff; + code |= 0x8000 | (exp << 12); + } + return htons(code); +} + +#endif /* _PIM6_MLD_PROTOCOL_H */ diff --git a/pimd/pim_addr.c b/pimd/pim_addr.c new file mode 100644 index 0000000..91a0bb8 --- /dev/null +++ b/pimd/pim_addr.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM address generalizations + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + */ + +#include + +#include "pim_addr.h" +#include "printfrr.h" +#include "prefix.h" + + +printfrr_ext_autoreg_p("PA", printfrr_pimaddr); +static ssize_t printfrr_pimaddr(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const pim_addr *addr = vptr; + bool use_star = false; + + if (ea->fmt[0] == 's') { + use_star = true; + ea->fmt++; + } + + if (!addr) + return bputs(buf, "(null)"); + + if (use_star && pim_addr_is_any(*addr)) + return bputch(buf, '*'); + +#if PIM_IPV == 4 + return bprintfrr(buf, "%pI4", addr); +#else + return bprintfrr(buf, "%pI6", addr); +#endif +} + +printfrr_ext_autoreg_p("SG", printfrr_sgaddr); +static ssize_t printfrr_sgaddr(struct fbuf *buf, struct printfrr_eargs *ea, + const void *vptr) +{ + const pim_sgaddr *sga = vptr; + + if (!sga) + return bputs(buf, "(null)"); + + return bprintfrr(buf, "(%pPAs,%pPAs)", &sga->src, &sga->grp); +} diff --git a/pimd/pim_addr.h b/pimd/pim_addr.h new file mode 100644 index 0000000..ecba739 --- /dev/null +++ b/pimd/pim_addr.h @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM address generalizations + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + */ + +#ifndef _PIMD_PIM_ADDR_H +#define _PIMD_PIM_ADDR_H + +#include "jhash.h" +#include "prefix.h" + +/* clang-format off */ + +#if PIM_IPV == 4 +typedef struct in_addr pim_addr; + +#define PIM_ADDRSTRLEN INET_ADDRSTRLEN +#define PIM_AF AF_INET +#define PIM_AFI AFI_IP +#define PIM_PROTO_REG IPPROTO_RAW +#define PIM_IPADDR IPADDR_V4 +#define ipaddr_pim ipaddr_v4 +#define PIM_MAX_BITLEN IPV4_MAX_BITLEN +#define PIM_AF_NAME "ip" +#define PIM_AF_DBG "pim" +#define GM_AF_DBG "igmp" +#define PIM_MROUTE_DBG "mroute" +#define PIMREG "pimreg" +#define GM "IGMP" +#define IPPROTO_GM IPPROTO_IGMP + +#define PIM_ADDR_FUNCNAME(name) ipv4_##name + +union pimprefixptr { + uniontype(pimprefixptr, struct prefix, p) + uniontype(pimprefixptr, struct prefix_ipv4, p4) +} TRANSPARENT_UNION; + +union pimprefixconstptr { + uniontype(pimprefixconstptr, const struct prefix, p) + uniontype(pimprefixconstptr, const struct prefix_ipv4, p4) +} TRANSPARENT_UNION; + +#else +typedef struct in6_addr pim_addr; + +#define PIM_ADDRSTRLEN INET6_ADDRSTRLEN +#define PIM_AF AF_INET6 +#define PIM_AFI AFI_IP6 +#define PIM_PROTO_REG IPPROTO_PIM +#define PIM_IPADDR IPADDR_V6 +#define ipaddr_pim ipaddr_v6 +#define PIM_MAX_BITLEN IPV6_MAX_BITLEN +#define PIM_AF_NAME "ipv6" +#define PIM_AF_DBG "pimv6" +#define GM_AF_DBG "mld" +#define PIM_MROUTE_DBG "mroute6" +#define PIMREG "pim6reg" +#define GM "MLD" +#define IPPROTO_GM IPPROTO_ICMPV6 + +#define PIM_ADDR_FUNCNAME(name) ipv6_##name + +union pimprefixptr { + uniontype(pimprefixptr, struct prefix, p) + uniontype(pimprefixptr, struct prefix_ipv6, p6) +} TRANSPARENT_UNION; + +union pimprefixconstptr { + uniontype(pimprefixconstptr, const struct prefix, p) + uniontype(pimprefixconstptr, const struct prefix_ipv6, p6) +} TRANSPARENT_UNION; +#endif + +/* for assignment/initialization (C99 compound literal) + * named PIMADDR_ANY (not PIM_ADDR_ANY) to match INADDR_ANY + */ +#define PIMADDR_ANY (pim_addr){ } + +/* clang-format on */ + +static inline bool pim_addr_is_any(pim_addr addr) +{ + pim_addr zero = {}; + + return memcmp(&addr, &zero, sizeof(zero)) == 0; +} + +static inline int pim_addr_cmp(pim_addr a, pim_addr b) +{ + return memcmp(&a, &b, sizeof(a)); +} + +static inline void pim_addr_to_prefix(union pimprefixptr out, pim_addr in) +{ + out.p->family = PIM_AF; + out.p->prefixlen = PIM_MAX_BITLEN; + memcpy(out.p->u.val, &in, sizeof(in)); +} + +static inline pim_addr pim_addr_from_prefix(union pimprefixconstptr in) +{ + pim_addr ret; + + if (in.p->family != PIM_AF) + return PIMADDR_ANY; + + memcpy(&ret, in.p->u.val, sizeof(ret)); + return ret; +} + +static inline uint8_t pim_addr_scope(const pim_addr addr) +{ + return PIM_ADDR_FUNCNAME(mcast_scope)(&addr); +} + +static inline bool pim_addr_nofwd(const pim_addr addr) +{ + return PIM_ADDR_FUNCNAME(mcast_nofwd)(&addr); +} + +static inline bool pim_addr_ssm(const pim_addr addr) +{ + return PIM_ADDR_FUNCNAME(mcast_ssm)(&addr); +} + +/* don't use this struct directly, use the pim_sgaddr typedef */ +struct _pim_sgaddr { + pim_addr grp; + pim_addr src; +}; + +typedef struct _pim_sgaddr pim_sgaddr; + +static inline int pim_sgaddr_cmp(const pim_sgaddr a, const pim_sgaddr b) +{ + /* memcmp over the entire struct = memcmp(grp) + memcmp(src) */ + return memcmp(&a, &b, sizeof(a)); +} + +static inline uint32_t pim_sgaddr_hash(const pim_sgaddr a, uint32_t initval) +{ + return jhash2((uint32_t *)&a, sizeof(a) / sizeof(uint32_t), initval); +} + +#ifdef _FRR_ATTRIBUTE_PRINTFRR +#pragma FRR printfrr_ext "%pPA" (pim_addr *) +#pragma FRR printfrr_ext "%pSG" (pim_sgaddr *) +#endif + +/* + * There is no pim_sgaddr2str(). This is intentional. Instead, use: + * snprintfrr(buf, sizeof(buf), "%pPA", sgaddr) + * (and note that snprintfrr is implicit for vty_out and zlog_*) + */ + +#endif /* _PIMD_PIM_ADDR_H */ diff --git a/pimd/pim_assert.c b/pimd/pim_assert.c new file mode 100644 index 0000000..86d9a74 --- /dev/null +++ b/pimd/pim_assert.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "if.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_str.h" +#include "pim_tlv.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_int.h" +#include "pim_time.h" +#include "pim_iface.h" +#include "pim_hello.h" +#include "pim_macro.h" +#include "pim_assert.h" +#include "pim_zebra.h" +#include "pim_ifchannel.h" + +static int assert_action_a3(struct pim_ifchannel *ch); +static void assert_action_a2(struct pim_ifchannel *ch, + struct pim_assert_metric winner_metric); +static void assert_action_a6(struct pim_ifchannel *ch, + struct pim_assert_metric winner_metric); + +void pim_ifassert_winner_set(struct pim_ifchannel *ch, + enum pim_ifassert_state new_state, pim_addr winner, + struct pim_assert_metric winner_metric) +{ + struct pim_interface *pim_ifp = ch->interface->info; + int winner_changed = !!pim_addr_cmp(ch->ifassert_winner, winner); + int metric_changed = !pim_assert_metric_match( + &ch->ifassert_winner_metric, &winner_metric); + enum pim_rpf_result rpf_result; + struct pim_rpf old_rpf; + + if (PIM_DEBUG_PIM_EVENTS) { + if (ch->ifassert_state != new_state) { + zlog_debug( + "%s: (S,G)=%s assert state changed from %s to %s on interface %s", + __func__, ch->sg_str, + pim_ifchannel_ifassert_name(ch->ifassert_state), + pim_ifchannel_ifassert_name(new_state), + ch->interface->name); + } + + if (winner_changed) + zlog_debug( + "%s: (S,G)=%s assert winner changed from %pPAs to %pPAs on interface %s", + __func__, ch->sg_str, &ch->ifassert_winner, + &winner, ch->interface->name); + } /* PIM_DEBUG_PIM_EVENTS */ + + ch->ifassert_state = new_state; + ch->ifassert_winner = winner; + ch->ifassert_winner_metric = winner_metric; + ch->ifassert_creation = pim_time_monotonic_sec(); + + if (winner_changed || metric_changed) { + if (winner_changed) { + old_rpf.source_nexthop.interface = + ch->upstream->rpf.source_nexthop.interface; + rpf_result = pim_rpf_update(pim_ifp->pim, ch->upstream, + &old_rpf, __func__); + if (rpf_result == PIM_RPF_CHANGED || + (rpf_result == PIM_RPF_FAILURE && + old_rpf.source_nexthop.interface)) + pim_zebra_upstream_rpf_changed( + pim_ifp->pim, ch->upstream, &old_rpf); + /* update kernel multicast forwarding cache (MFC) */ + if (ch->upstream->rpf.source_nexthop.interface && + ch->upstream->channel_oil) + pim_upstream_mroute_iif_update( + ch->upstream->channel_oil, __func__); + } + pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); + pim_ifchannel_update_could_assert(ch); + pim_ifchannel_update_assert_tracking_desired(ch); + } +} + +static void on_trace(const char *label, struct interface *ifp, pim_addr src) +{ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: from %pPAs on %s", label, &src, ifp->name); +} + +static int preferred_assert(const struct pim_ifchannel *ch, + const struct pim_assert_metric *recv_metric) +{ + return pim_assert_metric_better(recv_metric, + &ch->ifassert_winner_metric); +} + +static int acceptable_assert(const struct pim_assert_metric *my_metric, + const struct pim_assert_metric *recv_metric) +{ + return pim_assert_metric_better(recv_metric, my_metric); +} + +static int inferior_assert(const struct pim_assert_metric *my_metric, + const struct pim_assert_metric *recv_metric) +{ + return pim_assert_metric_better(my_metric, recv_metric); +} + +static int cancel_assert(const struct pim_assert_metric *recv_metric) +{ + return (recv_metric->metric_preference + == PIM_ASSERT_METRIC_PREFERENCE_MAX) + && (recv_metric->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX); +} + +static void if_could_assert_do_a1(const char *caller, struct pim_ifchannel *ch) +{ + if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { + if (assert_action_a1(ch)) { + zlog_warn( + "%s: %s: (S,G)=%s assert_action_a1 failure on interface %s", + __func__, caller, ch->sg_str, + ch->interface->name); + /* log warning only */ + } + } +} + +static int dispatch_assert(struct interface *ifp, pim_addr source_addr, + pim_addr group_addr, + struct pim_assert_metric recv_metric) +{ + struct pim_ifchannel *ch; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = source_addr; + sg.grp = group_addr; + ch = pim_ifchannel_add(ifp, &sg, 0, 0); + + switch (ch->ifassert_state) { + case PIM_IFASSERT_NOINFO: + if (recv_metric.rpt_bit_flag) { + /* RPT bit set */ + if_could_assert_do_a1(__func__, ch); + } else { + /* RPT bit clear */ + if (inferior_assert(&ch->ifassert_my_metric, + &recv_metric)) { + if_could_assert_do_a1(__func__, ch); + } else if (acceptable_assert(&ch->ifassert_my_metric, + &recv_metric)) { + if (PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED( + ch->flags)) { + assert_action_a6(ch, recv_metric); + } + } + } + break; + case PIM_IFASSERT_I_AM_WINNER: + if (preferred_assert(ch, &recv_metric)) { + assert_action_a2(ch, recv_metric); + } else { + if (inferior_assert(&ch->ifassert_my_metric, + &recv_metric)) { + assert_action_a3(ch); + } + } + break; + case PIM_IFASSERT_I_AM_LOSER: + if (!pim_addr_cmp(recv_metric.ip_address, + ch->ifassert_winner)) { + /* Assert from current winner */ + + if (cancel_assert(&recv_metric)) { + assert_action_a5(ch); + } else { + if (inferior_assert(&ch->ifassert_my_metric, + &recv_metric)) { + assert_action_a5(ch); + } else if (acceptable_assert( + &ch->ifassert_my_metric, + &recv_metric)) { + if (!recv_metric.rpt_bit_flag) { + assert_action_a2(ch, + recv_metric); + } + } + } + } else if (preferred_assert(ch, &recv_metric)) { + assert_action_a2(ch, recv_metric); + } + break; + default: { + zlog_warn( + "%s: (S,G)=%s invalid assert state %d on interface %s", + __func__, ch->sg_str, ch->ifassert_state, ifp->name); + } + return -2; + } + + return 0; +} + +int pim_assert_recv(struct interface *ifp, struct pim_neighbor *neigh, + pim_addr src_addr, uint8_t *buf, int buf_size) +{ + pim_sgaddr sg; + pim_addr msg_source_addr; + bool wrong_af = false; + struct pim_assert_metric msg_metric; + int offset; + uint8_t *curr; + int curr_size; + struct pim_interface *pim_ifp = NULL; + + on_trace(__func__, ifp, src_addr); + + curr = buf; + curr_size = buf_size; + + /* + Parse assert group addr + */ + memset(&sg, 0, sizeof(sg)); + offset = pim_parse_addr_group(&sg, curr, curr_size); + if (offset < 1) { + zlog_warn( + "%s: pim_parse_addr_group() failure: from %pPAs on %s", + __func__, &src_addr, ifp->name); + return -1; + } + curr += offset; + curr_size -= offset; + + /* + Parse assert source addr + */ + offset = pim_parse_addr_ucast(&msg_source_addr, curr, curr_size, + &wrong_af); + if (offset < 1 || wrong_af) { + zlog_warn( + "%s: pim_parse_addr_ucast() failure: from %pPAs on %s", + __func__, &src_addr, ifp->name); + return -2; + } + curr += offset; + curr_size -= offset; + + if (curr_size < 8) { + zlog_warn( + "%s: preference/metric size is less than 8 bytes: size=%d from %pPAs on interface %s", + __func__, curr_size, &src_addr, ifp->name); + return -3; + } + + /* + Parse assert metric preference + */ + + msg_metric.metric_preference = pim_read_uint32_host(curr); + + msg_metric.rpt_bit_flag = msg_metric.metric_preference + & 0x80000000; /* save highest bit */ + msg_metric.metric_preference &= ~0x80000000; /* clear highest bit */ + + curr += 4; + + /* + Parse assert route metric + */ + + msg_metric.route_metric = pim_read_uint32_host(curr); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: from %pPAs on %s: (S,G)=(%pPAs,%pPAs) pref=%u metric=%u rpt_bit=%u", + __func__, &src_addr, ifp->name, &msg_source_addr, + &sg.grp, msg_metric.metric_preference, + msg_metric.route_metric, + PIM_FORCE_BOOLEAN(msg_metric.rpt_bit_flag)); + + msg_metric.ip_address = src_addr; + + pim_ifp = ifp->info; + assert(pim_ifp); + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + ++pim_ifp->pim_ifstat_assert_recv; + + return dispatch_assert(ifp, msg_source_addr, sg.grp, msg_metric); +} + +/* + RFC 4601: 4.6.3. Assert Metrics + + Assert metrics are defined as: + + When comparing assert_metrics, the rpt_bit_flag, metric_preference, + and route_metric field are compared in order, where the first lower + value wins. If all fields are equal, the primary IP address of the + router that sourced the Assert message is used as a tie-breaker, + with the highest IP address winning. +*/ +int pim_assert_metric_better(const struct pim_assert_metric *m1, + const struct pim_assert_metric *m2) +{ + if (m1->rpt_bit_flag < m2->rpt_bit_flag) + return 1; + if (m1->rpt_bit_flag > m2->rpt_bit_flag) + return 0; + + if (m1->metric_preference < m2->metric_preference) + return 1; + if (m1->metric_preference > m2->metric_preference) + return 0; + + if (m1->route_metric < m2->route_metric) + return 1; + if (m1->route_metric > m2->route_metric) + return 0; + + return pim_addr_cmp(m1->ip_address, m2->ip_address) > 0; +} + +int pim_assert_metric_match(const struct pim_assert_metric *m1, + const struct pim_assert_metric *m2) +{ + if (m1->rpt_bit_flag != m2->rpt_bit_flag) + return 0; + if (m1->metric_preference != m2->metric_preference) + return 0; + if (m1->route_metric != m2->route_metric) + return 0; + + return !pim_addr_cmp(m1->ip_address, m2->ip_address); +} + +int pim_assert_build_msg(uint8_t *pim_msg, int buf_size, struct interface *ifp, + pim_addr group_addr, pim_addr source_addr, + uint32_t metric_preference, uint32_t route_metric, + uint32_t rpt_bit_flag) +{ + struct pim_interface *pim_ifp = ifp->info; + uint8_t *buf_pastend = pim_msg + buf_size; + uint8_t *pim_msg_curr; + int pim_msg_size; + int remain; + + pim_msg_curr = + pim_msg + PIM_MSG_HEADER_LEN; /* skip room for pim header */ + + /* Encode group */ + remain = buf_pastend - pim_msg_curr; + pim_msg_curr = pim_msg_addr_encode_group(pim_msg_curr, group_addr); + if (!pim_msg_curr) { + zlog_warn( + "%s: failure encoding group address %pPA: space left=%d", + __func__, &group_addr, remain); + return -1; + } + + /* Encode source */ + remain = buf_pastend - pim_msg_curr; + pim_msg_curr = pim_msg_addr_encode_ucast(pim_msg_curr, source_addr); + if (!pim_msg_curr) { + zlog_warn( + "%s: failure encoding source address %pPA: space left=%d", + __func__, &source_addr, remain); + return -2; + } + + /* Metric preference */ + pim_write_uint32(pim_msg_curr, + rpt_bit_flag ? metric_preference | 0x80000000 + : metric_preference); + pim_msg_curr += 4; + + /* Route metric */ + pim_write_uint32(pim_msg_curr, route_metric); + pim_msg_curr += 4; + + /* + Add PIM header + */ + pim_msg_size = pim_msg_curr - pim_msg; + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, pim_msg_size, + PIM_MSG_TYPE_ASSERT, false); + + return pim_msg_size; +} + +static int pim_assert_do(struct pim_ifchannel *ch, + struct pim_assert_metric metric) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + uint8_t pim_msg[1000]; + int pim_msg_size; + + ifp = ch->interface; + if (!ifp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: channel%s has no associated interface!", + __func__, ch->sg_str); + return -1; + } + pim_ifp = ifp->info; + if (!pim_ifp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: channel %s pim not enabled on interface: %s", + __func__, ch->sg_str, ifp->name); + return -1; + } + + pim_msg_size = + pim_assert_build_msg(pim_msg, sizeof(pim_msg), ifp, ch->sg.grp, + ch->sg.src, metric.metric_preference, + metric.route_metric, metric.rpt_bit_flag); + if (pim_msg_size < 1) { + zlog_warn( + "%s: failure building PIM assert message: msg_size=%d", + __func__, pim_msg_size); + return -2; + } + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on + an interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. + */ + pim_hello_require(ifp); + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: to %s: (S,G)=%s pref=%u metric=%u rpt_bit=%u", + __func__, ifp->name, ch->sg_str, + metric.metric_preference, metric.route_metric, + PIM_FORCE_BOOLEAN(metric.rpt_bit_flag)); + } + if (!pim_ifp->pim_passive_enable) + ++pim_ifp->pim_ifstat_assert_send; + + if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, pim_msg_size, + ifp)) { + zlog_warn("%s: could not send PIM message on interface %s", + __func__, ifp->name); + return -3; + } + + return 0; +} + +int pim_assert_send(struct pim_ifchannel *ch) +{ + return pim_assert_do(ch, ch->ifassert_my_metric); +} + +/* + RFC 4601: 4.6.4. AssertCancel Messages + + An AssertCancel(S,G) is an infinite metric assert with the RPT bit + set that names S as the source. + */ +static int pim_assert_cancel(struct pim_ifchannel *ch) +{ + struct pim_assert_metric metric; + + metric.rpt_bit_flag = 0; + metric.metric_preference = PIM_ASSERT_METRIC_PREFERENCE_MAX; + metric.route_metric = PIM_ASSERT_ROUTE_METRIC_MAX; + metric.ip_address = ch->sg.src; + + return pim_assert_do(ch, metric); +} + +static void on_assert_timer(struct event *t) +{ + struct pim_ifchannel *ch; + struct interface *ifp; + + ch = EVENT_ARG(t); + + ifp = ch->interface; + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: (S,G)=%s timer expired on interface %s", + __func__, ch->sg_str, ifp->name); + } + + ch->t_ifassert_timer = NULL; + + switch (ch->ifassert_state) { + case PIM_IFASSERT_I_AM_WINNER: + assert_action_a3(ch); + break; + case PIM_IFASSERT_I_AM_LOSER: + assert_action_a5(ch); + break; + case PIM_IFASSERT_NOINFO: { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: (S,G)=%s invalid assert state %d on interface %s", + __func__, ch->sg_str, ch->ifassert_state, + ifp->name); + } + } +} + +static void assert_timer_off(struct pim_ifchannel *ch) +{ + if (PIM_DEBUG_PIM_TRACE) { + if (ch->t_ifassert_timer) { + zlog_debug( + "%s: (S,G)=%s cancelling timer on interface %s", + __func__, ch->sg_str, ch->interface->name); + } + } + EVENT_OFF(ch->t_ifassert_timer); +} + +static void pim_assert_timer_set(struct pim_ifchannel *ch, int interval) +{ + assert_timer_off(ch); + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: (S,G)=%s starting %u sec timer on interface %s", + __func__, ch->sg_str, interval, ch->interface->name); + } + + event_add_timer(router->master, on_assert_timer, ch, interval, + &ch->t_ifassert_timer); +} + +static void pim_assert_timer_reset(struct pim_ifchannel *ch) +{ + pim_assert_timer_set(ch, + PIM_ASSERT_TIME - PIM_ASSERT_OVERRIDE_INTERVAL); +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A1: Send Assert(S,G). + Set Assert Timer to (Assert_Time - Assert_Override_Interval). + Store self as AssertWinner(S,G,I). + Store spt_assert_metric(S,I) as AssertWinnerMetric(S,G,I). +*/ +int assert_action_a1(struct pim_ifchannel *ch) +{ + struct interface *ifp = ch->interface; + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%s multicast not enabled on interface %s", + __func__, ch->sg_str, ifp->name); + return -1; /* must return since pim_ifp is used below */ + } + + /* Switch to I_AM_WINNER before performing action_a3 below */ + pim_ifassert_winner_set( + ch, PIM_IFASSERT_I_AM_WINNER, pim_ifp->primary_address, + pim_macro_spt_assert_metric(&ch->upstream->rpf, + pim_ifp->primary_address)); + + if (assert_action_a3(ch)) { + zlog_warn( + "%s: (S,G)=%s assert_action_a3 failure on interface %s", + __func__, ch->sg_str, ifp->name); + /* warning only */ + } + + if (ch->ifassert_state != PIM_IFASSERT_I_AM_WINNER) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s not in expected PIM_IFASSERT_I_AM_WINNER state", + __func__, ch->sg_str); + } + + return 0; +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A2: Store new assert winner as AssertWinner(S,G,I) and assert + winner metric as AssertWinnerMetric(S,G,I). + Set Assert Timer to Assert_Time. +*/ +static void assert_action_a2(struct pim_ifchannel *ch, + struct pim_assert_metric winner_metric) +{ + pim_ifassert_winner_set(ch, PIM_IFASSERT_I_AM_LOSER, + winner_metric.ip_address, winner_metric); + + pim_assert_timer_set(ch, PIM_ASSERT_TIME); + + if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s not in expected PIM_IFASSERT_I_AM_LOSER state", + __func__, ch->sg_str); + } +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A3: Send Assert(S,G). + Set Assert Timer to (Assert_Time - Assert_Override_Interval). +*/ +static int assert_action_a3(struct pim_ifchannel *ch) +{ + if (ch->ifassert_state != PIM_IFASSERT_I_AM_WINNER) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s expected to be in PIM_IFASSERT_I_AM_WINNER state", + __func__, ch->sg_str); + return -1; + } + + pim_assert_timer_reset(ch); + + if (pim_assert_send(ch)) { + zlog_warn("%s: (S,G)=%s failure sending assert on interface %s", + __func__, ch->sg_str, ch->interface->name); + return -1; + } + + return 0; +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A4: Send AssertCancel(S,G). + Delete assert info (AssertWinner(S,G,I) and + AssertWinnerMetric(S,G,I) will then return their default + values). +*/ +void assert_action_a4(struct pim_ifchannel *ch) +{ + if (pim_assert_cancel(ch)) { + zlog_warn("%s: failure sending AssertCancel%s on interface %s", + __func__, ch->sg_str, ch->interface->name); + /* log warning only */ + } + + assert_action_a5(ch); + + if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s not in PIM_IFASSERT_NOINFO state as expected", + __func__, ch->sg_str); + } +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A5: Delete assert info (AssertWinner(S,G,I) and + AssertWinnerMetric(S,G,I) will then return their default values). +*/ +void assert_action_a5(struct pim_ifchannel *ch) +{ + reset_ifassert_state(ch); + if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s not in PIM_IFSSERT_NOINFO state as expected", + __func__, ch->sg_str); + } +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + (S,G) Assert State machine Actions + + A6: Store new assert winner as AssertWinner(S,G,I) and assert + winner metric as AssertWinnerMetric(S,G,I). + Set Assert Timer to Assert_Time. + If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true) + set SPTbit(S,G) to true. +*/ +static void assert_action_a6(struct pim_ifchannel *ch, + struct pim_assert_metric winner_metric) +{ + assert_action_a2(ch, winner_metric); + + /* + If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true) set + SPTbit(S,G) to true. + */ + if (ch->upstream->rpf.source_nexthop.interface == ch->interface) + if (ch->upstream->join_state == PIM_UPSTREAM_JOINED) + ch->upstream->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + + if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_warn( + "%s: channel%s not in PIM_IFASSERT_I_AM_LOSER state as expected", + __func__, ch->sg_str); + } +} diff --git a/pimd/pim_assert.h b/pimd/pim_assert.h new file mode 100644 index 0000000..41f32ea --- /dev/null +++ b/pimd/pim_assert.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_ASSERT_H +#define PIM_ASSERT_H + +#include + +#include "if.h" + +struct pim_ifchannel; +struct pim_neighbor; + +enum pim_ifassert_state { + PIM_IFASSERT_NOINFO, + PIM_IFASSERT_I_AM_WINNER, + PIM_IFASSERT_I_AM_LOSER +}; + +struct pim_assert_metric { + uint32_t rpt_bit_flag; + uint32_t metric_preference; + uint32_t route_metric; + pim_addr ip_address; /* neighbor router that sourced the Assert + message */ +}; + +/* + RFC 4601: 4.11. Timer Values + + Note that for historical reasons, the Assert message lacks a + Holdtime field. Thus, changing the Assert Time from the default + value is not recommended. + */ +#define PIM_ASSERT_OVERRIDE_INTERVAL (3) /* seconds */ +#define PIM_ASSERT_TIME (180) /* seconds */ + +#define PIM_ASSERT_METRIC_PREFERENCE_MAX (0xFFFFFFFF) +#define PIM_ASSERT_ROUTE_METRIC_MAX (0xFFFFFFFF) + +void pim_ifassert_winner_set(struct pim_ifchannel *ch, + enum pim_ifassert_state new_state, pim_addr winner, + struct pim_assert_metric winner_metric); + +int pim_assert_recv(struct interface *ifp, struct pim_neighbor *neigh, + pim_addr src_addr, uint8_t *buf, int buf_size); + +int pim_assert_metric_better(const struct pim_assert_metric *m1, + const struct pim_assert_metric *m2); +int pim_assert_metric_match(const struct pim_assert_metric *m1, + const struct pim_assert_metric *m2); + +int pim_assert_build_msg(uint8_t *pim_msg, int buf_size, struct interface *ifp, + pim_addr group_addr, pim_addr source_addr, + uint32_t metric_preference, uint32_t route_metric, + uint32_t rpt_bit_flag); + +int pim_assert_send(struct pim_ifchannel *ch); + +int assert_action_a1(struct pim_ifchannel *ch); +void assert_action_a4(struct pim_ifchannel *ch); +void assert_action_a5(struct pim_ifchannel *ch); + +#endif /* PIM_ASSERT_H */ diff --git a/pimd/pim_bfd.c b/pimd/pim_bfd.c new file mode 100644 index 0000000..43d9f08 --- /dev/null +++ b/pimd/pim_bfd.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_bfd.c: PIM BFD handling routines + * + * Copyright (C) 2017 Cumulus Networks, Inc. + * Chirag Shah + */ + +#include + +#include "lib/json.h" +#include "command.h" +#include "vty.h" +#include "zclient.h" + +#include "pim_instance.h" +#include "pim_neighbor.h" +#include "pim_vty.h" +#include "pim_iface.h" +#include "pim_bfd.h" +#include "bfd.h" +#include "pimd.h" +#include "pim_zebra.h" + +/* + * pim_bfd_write_config - Write the interface BFD configuration. + */ +void pim_bfd_write_config(struct vty *vty, struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp || !pim_ifp->bfd_config.enabled) + return; + +#if HAVE_BFDD == 0 + if (pim_ifp->bfd_config.detection_multiplier != BFD_DEF_DETECT_MULT + || pim_ifp->bfd_config.min_rx != BFD_DEF_MIN_RX + || pim_ifp->bfd_config.min_tx != BFD_DEF_MIN_TX) + vty_out(vty, " " PIM_AF_NAME " pim bfd %d %d %d\n", + pim_ifp->bfd_config.detection_multiplier, + pim_ifp->bfd_config.min_rx, pim_ifp->bfd_config.min_tx); + else +#endif /* ! HAVE_BFDD */ + vty_out(vty, " " PIM_AF_NAME " pim bfd\n"); + + if (pim_ifp->bfd_config.profile) + vty_out(vty, " " PIM_AF_NAME " pim bfd profile %s\n", + pim_ifp->bfd_config.profile); +} + +static void pim_neighbor_bfd_cb(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, void *arg) +{ + struct pim_neighbor *nbr = arg; + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: status %s old_status %s", __func__, + bfd_get_status_str(bss->state), + bfd_get_status_str(bss->previous_state)); + } + + if (bss->state == BFD_STATUS_DOWN + && bss->previous_state == BFD_STATUS_UP) + pim_neighbor_delete(nbr->interface, nbr, "BFD Session Expired"); +} + +/* + * pim_bfd_info_nbr_create - Create/update BFD information for a neighbor. + */ +void pim_bfd_info_nbr_create(struct pim_interface *pim_ifp, + struct pim_neighbor *neigh) +{ + /* Check if Pim Interface BFD is enabled */ + if (!pim_ifp || !pim_ifp->bfd_config.enabled) + return; + + if (neigh->bfd_session == NULL) + neigh->bfd_session = bfd_sess_new(pim_neighbor_bfd_cb, neigh); + + bfd_sess_set_timers( + neigh->bfd_session, pim_ifp->bfd_config.detection_multiplier, + pim_ifp->bfd_config.min_rx, pim_ifp->bfd_config.min_tx); +#if PIM_IPV == 4 + bfd_sess_set_ipv4_addrs(neigh->bfd_session, NULL, &neigh->source_addr); +#else + bfd_sess_set_ipv6_addrs(neigh->bfd_session, NULL, &neigh->source_addr); +#endif + bfd_sess_set_interface(neigh->bfd_session, neigh->interface->name); + bfd_sess_set_vrf(neigh->bfd_session, neigh->interface->vrf->vrf_id); + bfd_sess_set_profile(neigh->bfd_session, pim_ifp->bfd_config.profile); + bfd_sess_install(neigh->bfd_session); +} + +/* + * pim_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated + * with a interface with BFD through + * zebra for starting/stopping the monitoring of + * the neighbor rechahability. + */ +void pim_bfd_reg_dereg_all_nbr(struct interface *ifp) +{ + struct pim_interface *pim_ifp = NULL; + struct listnode *node = NULL; + struct pim_neighbor *neigh = NULL; + + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { + if (pim_ifp->bfd_config.enabled) + pim_bfd_info_nbr_create(pim_ifp, neigh); + else + bfd_sess_free(&neigh->bfd_session); + } +} + +void pim_bfd_init(void) +{ + bfd_protocol_integration_init(pim_zebra_zclient_get(), router->master); +} diff --git a/pimd/pim_bfd.h b/pimd/pim_bfd.h new file mode 100644 index 0000000..3d8e29a --- /dev/null +++ b/pimd/pim_bfd.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_bfd.h: PIM BFD definitions and structures + * + * Copyright (C) 2017 Cumulus Networks, Inc. + * Chirag Shah + */ + +#ifndef PIM_BFD_H +#define PIM_BFD_H + +#include "if.h" + +/** + * Initializes PIM BFD integration code. + */ +void pim_bfd_init(void); + +/** + * Write configuration to `show running-config`. + * + * \param vty the vty output pointer. + * \param ifp the interface pointer that has the configuration. + */ +void pim_bfd_write_config(struct vty *vty, struct interface *ifp); + +/** + * Enables or disables all peers BFD sessions. + * + * \param ifp interface pointer. + * \param enable session state to set. + */ +void pim_bfd_reg_dereg_all_nbr(struct interface *ifp); + +/** + * Create and configure peer BFD session if it does not exist. It will use + * the interface configured parameters as the peer configuration. + * + * \param pim_ifp the interface configuration pointer. + * \param neigh the neighbor configuration pointer. + */ +void pim_bfd_info_nbr_create(struct pim_interface *pim_ifp, + struct pim_neighbor *neigh); + +#endif /* _PIM_BFD_H */ diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c new file mode 100644 index 0000000..df91619 --- /dev/null +++ b/pimd/pim_bsm.c @@ -0,0 +1,1453 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_bsm.c: PIM BSM handling routines + * + * Copyright (C) 2018-19 Vmware, Inc. + * Saravanan K + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "if.h" +#include "pimd.h" +#include "pim_iface.h" +#include "pim_instance.h" +#include "pim_neighbor.h" +#include "pim_rpf.h" +#include "pim_hello.h" +#include "pim_pim.h" +#include "pim_nht.h" +#include "pim_bsm.h" +#include "pim_time.h" +#include "pim_zebra.h" +#include "pim_util.h" + +/* Functions forward declaration */ +static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout); +static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time); +static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp, + int hold_time); + +/* Memory Types */ +DEFINE_MTYPE_STATIC(PIMD, PIM_BSGRP_NODE, "PIM BSR advertised grp info"); +DEFINE_MTYPE_STATIC(PIMD, PIM_BSRP_INFO, "PIM BSR advertised RP info"); +DEFINE_MTYPE_STATIC(PIMD, PIM_BSM_FRAG, "PIM BSM fragment"); +DEFINE_MTYPE_STATIC(PIMD, PIM_BSM_PKT_VAR_MEM, "PIM BSM Packet"); + +/* All bsm packets forwarded shall be fit within ip mtu less iphdr(max) */ +#define MAX_IP_HDR_LEN 24 + +/* pim_bsm_write_config - Write the interface pim bsm configuration.*/ +void pim_bsm_write_config(struct vty *vty, struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (pim_ifp) { + if (!pim_ifp->bsm_enable) + vty_out(vty, " no " PIM_AF_NAME " pim bsm\n"); + if (!pim_ifp->ucast_bsm_accept) + vty_out(vty, " no " PIM_AF_NAME " pim unicast-bsm\n"); + } +} + +static void pim_bsm_rpinfo_free(struct bsm_rpinfo *bsrp_info) +{ + EVENT_OFF(bsrp_info->g2rp_timer); + XFREE(MTYPE_PIM_BSRP_INFO, bsrp_info); +} + +static void pim_bsm_rpinfos_free(struct bsm_rpinfos_head *head) +{ + struct bsm_rpinfo *bsrp_info; + + while ((bsrp_info = bsm_rpinfos_pop(head))) + pim_bsm_rpinfo_free(bsrp_info); +} + +static void pim_free_bsgrp_data(struct bsgrp_node *bsgrp_node) +{ + pim_bsm_rpinfos_free(bsgrp_node->bsrp_list); + pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list); + XFREE(MTYPE_PIM_BSGRP_NODE, bsgrp_node); +} + +static void pim_free_bsgrp_node(struct route_table *rt, struct prefix *grp) +{ + struct route_node *rn; + + rn = route_node_lookup(rt, grp); + if (rn) { + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } +} + +static void pim_bsm_frag_free(struct bsm_frag *bsfrag) +{ + XFREE(MTYPE_PIM_BSM_FRAG, bsfrag); +} + +static void pim_bsm_frags_free(struct bsm_scope *scope) +{ + struct bsm_frag *bsfrag; + + while ((bsfrag = bsm_frags_pop(scope->bsm_frags))) + pim_bsm_frag_free(bsfrag); +} + +int pim_bsm_rpinfo_cmp(const struct bsm_rpinfo *node1, + const struct bsm_rpinfo *node2) +{ + /* RP election Algo : + * Step-1 : Loweset Rp priority will have higher precedance. + * Step-2 : If priority same then higher hash val will have + * higher precedance. + * Step-3 : If Hash val is same then highest rp address will + * become elected RP. + */ + if (node1->rp_prio < node2->rp_prio) + return -1; + if (node1->rp_prio > node2->rp_prio) + return 1; + if (node1->hash < node2->hash) + return 1; + if (node1->hash > node2->hash) + return -1; + return pim_addr_cmp(node2->rp_address, node1->rp_address); +} + +static struct bsgrp_node *pim_bsm_new_bsgrp_node(struct route_table *rt, + struct prefix *grp) +{ + struct route_node *rn; + struct bsgrp_node *bsgrp; + + rn = route_node_get(rt, grp); + if (!rn) { + zlog_warn("%s: route node creation failed", __func__); + return NULL; + } + bsgrp = XCALLOC(MTYPE_PIM_BSGRP_NODE, sizeof(struct bsgrp_node)); + + rn->info = bsgrp; + bsm_rpinfos_init(bsgrp->bsrp_list); + bsm_rpinfos_init(bsgrp->partial_bsrp_list); + + prefix_copy(&bsgrp->group, grp); + return bsgrp; +} + +static void pim_on_bs_timer(struct event *t) +{ + struct route_node *rn; + struct bsm_scope *scope; + struct bsgrp_node *bsgrp_node; + struct bsm_rpinfo *bsrp; + + scope = EVENT_ARG(t); + EVENT_OFF(scope->bs_timer); + + if (PIM_DEBUG_BSM) + zlog_debug("%s: Bootstrap Timer expired for scope: %d", + __func__, scope->sz_id); + + pim_nht_bsr_del(scope->pim, scope->current_bsr); + /* Reset scope zone data */ + scope->state = ACCEPT_ANY; + scope->current_bsr = PIMADDR_ANY; + scope->current_bsr_prio = 0; + scope->current_bsr_first_ts = 0; + scope->current_bsr_last_ts = 0; + scope->bsm_frag_tag = 0; + pim_bsm_frags_free(scope); + + for (rn = route_top(scope->bsrp_table); rn; rn = route_next(rn)) { + + bsgrp_node = (struct bsgrp_node *)rn->info; + if (!bsgrp_node) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: bsgrp_node is null", __func__); + continue; + } + /* Give grace time for rp to continue for another hold time */ + bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list); + if (bsrp) + pim_g2rp_timer_restart(bsrp, bsrp->rp_holdtime); + + /* clear pending list */ + pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list); + bsgrp_node->pend_rp_cnt = 0; + } +} + +static void pim_bs_timer_stop(struct bsm_scope *scope) +{ + if (PIM_DEBUG_BSM) + zlog_debug("%s : BS timer being stopped of sz: %d", __func__, + scope->sz_id); + EVENT_OFF(scope->bs_timer); +} + +static void pim_bs_timer_start(struct bsm_scope *scope, int bs_timeout) +{ + if (!scope) { + if (PIM_DEBUG_BSM) + zlog_debug("%s : Invalid scope(NULL).", __func__); + return; + } + EVENT_OFF(scope->bs_timer); + if (PIM_DEBUG_BSM) + zlog_debug( + "%s : starting bs timer for scope %d with timeout %d secs", + __func__, scope->sz_id, bs_timeout); + event_add_timer(router->master, pim_on_bs_timer, scope, bs_timeout, + &scope->bs_timer); +} + +static inline void pim_bs_timer_restart(struct bsm_scope *scope, int bs_timeout) +{ + pim_bs_timer_start(scope, bs_timeout); +} + +void pim_bsm_proc_init(struct pim_instance *pim) +{ + memset(&pim->global_scope, 0, sizeof(struct bsm_scope)); + + pim->global_scope.sz_id = PIM_GBL_SZ_ID; + pim->global_scope.bsrp_table = route_table_init(); + pim->global_scope.accept_nofwd_bsm = true; + pim->global_scope.state = NO_INFO; + pim->global_scope.pim = pim; + bsm_frags_init(pim->global_scope.bsm_frags); + pim_bs_timer_start(&pim->global_scope, PIM_BS_TIME); +} + +void pim_bsm_proc_free(struct pim_instance *pim) +{ + struct route_node *rn; + struct bsgrp_node *bsgrp; + + pim_bs_timer_stop(&pim->global_scope); + pim_bsm_frags_free(&pim->global_scope); + + for (rn = route_top(pim->global_scope.bsrp_table); rn; + rn = route_next(rn)) { + bsgrp = rn->info; + if (!bsgrp) + continue; + pim_free_bsgrp_data(bsgrp); + } + + route_table_finish(pim->global_scope.bsrp_table); +} + +static bool is_hold_time_elapsed(void *data) +{ + struct bsm_rpinfo *bsrp; + + bsrp = data; + + if (bsrp->elapse_time < bsrp->rp_holdtime) + return false; + else + return true; +} + +static void pim_on_g2rp_timer(struct event *t) +{ + struct bsm_rpinfo *bsrp; + struct bsm_rpinfo *bsrp_node; + struct bsgrp_node *bsgrp_node; + struct pim_instance *pim; + struct rp_info *rp_info; + struct route_node *rn; + uint16_t elapse; + pim_addr bsrp_addr; + + bsrp = EVENT_ARG(t); + EVENT_OFF(bsrp->g2rp_timer); + bsgrp_node = bsrp->bsgrp_node; + + /* elapse time is the hold time of expired node */ + elapse = bsrp->rp_holdtime; + bsrp_addr = bsrp->rp_address; + + /* update elapse for all bsrp nodes */ + frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) { + bsrp_node->elapse_time += elapse; + + if (is_hold_time_elapsed(bsrp_node)) { + bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node); + pim_bsm_rpinfo_free(bsrp_node); + } + } + + /* Get the next elected rp node */ + bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list); + pim = bsgrp_node->scope->pim; + rn = route_node_lookup(pim->rp_table, &bsgrp_node->group); + + if (!rn) { + zlog_warn("%s: Route node doesn't exist", __func__); + return; + } + + rp_info = (struct rp_info *)rn->info; + + if (!rp_info) { + route_unlock_node(rn); + return; + } + + if (rp_info->rp_src != RP_SRC_STATIC) { + /* If new rp available, change it else delete the existing */ + if (bsrp) { + pim_g2rp_timer_start( + bsrp, (bsrp->rp_holdtime - bsrp->elapse_time)); + pim_rp_change(pim, bsrp->rp_address, bsgrp_node->group, + RP_SRC_BSR); + } else { + pim_rp_del(pim, bsrp_addr, bsgrp_node->group, NULL, + RP_SRC_BSR); + } + } + + if (!bsm_rpinfos_count(bsgrp_node->bsrp_list) + && !bsm_rpinfos_count(bsgrp_node->partial_bsrp_list)) { + pim_free_bsgrp_node(pim->global_scope.bsrp_table, + &bsgrp_node->group); + pim_free_bsgrp_data(bsgrp_node); + } +} + +static void pim_g2rp_timer_start(struct bsm_rpinfo *bsrp, int hold_time) +{ + if (!bsrp) { + if (PIM_DEBUG_BSM) + zlog_debug("%s : Invalid brsp(NULL).", __func__); + return; + } + EVENT_OFF(bsrp->g2rp_timer); + if (PIM_DEBUG_BSM) + zlog_debug( + "%s : starting g2rp timer for grp: %pFX - rp: %pPAs with timeout %d secs(Actual Hold time : %d secs)", + __func__, &bsrp->bsgrp_node->group, &bsrp->rp_address, + hold_time, bsrp->rp_holdtime); + + event_add_timer(router->master, pim_on_g2rp_timer, bsrp, hold_time, + &bsrp->g2rp_timer); +} + +static inline void pim_g2rp_timer_restart(struct bsm_rpinfo *bsrp, + int hold_time) +{ + pim_g2rp_timer_start(bsrp, hold_time); +} + +static void pim_g2rp_timer_stop(struct bsm_rpinfo *bsrp) +{ + if (!bsrp) + return; + + if (PIM_DEBUG_BSM) + zlog_debug("%s : stopping g2rp timer for grp: %pFX - rp: %pPAs", + __func__, &bsrp->bsgrp_node->group, + &bsrp->rp_address); + + EVENT_OFF(bsrp->g2rp_timer); +} + +static bool is_hold_time_zero(void *data) +{ + struct bsm_rpinfo *bsrp; + + bsrp = data; + + if (bsrp->rp_holdtime) + return false; + else + return true; +} + +static void pim_instate_pend_list(struct bsgrp_node *bsgrp_node) +{ + struct bsm_rpinfo *active; + struct bsm_rpinfo *pend; + struct rp_info *rp_info; + struct route_node *rn; + struct pim_instance *pim; + struct rp_info *rp_all; + struct prefix group_all; + bool had_rp_node = true; + + pim = bsgrp_node->scope->pim; + active = bsm_rpinfos_first(bsgrp_node->bsrp_list); + + /* Remove nodes with hold time 0 & check if list still has a head */ + frr_each_safe (bsm_rpinfos, bsgrp_node->partial_bsrp_list, pend) { + if (is_hold_time_zero(pend)) { + bsm_rpinfos_del(bsgrp_node->partial_bsrp_list, pend); + pim_bsm_rpinfo_free(pend); + } + } + + pend = bsm_rpinfos_first(bsgrp_node->partial_bsrp_list); + + if (!pim_get_all_mcast_group(&group_all)) + return; + + rp_all = pim_rp_find_match_group(pim, &group_all); + rn = route_node_lookup(pim->rp_table, &bsgrp_node->group); + + if (pend) + pim_g2rp_timer_start(pend, pend->rp_holdtime); + + /* if rp node doesn't exist or exist but not configured(rp_all), + * install the rp from head(if exists) of partial list. List is + * is sorted such that head is the elected RP for the group. + */ + if (!rn || (prefix_same(&rp_all->group, &bsgrp_node->group) && + pim_rpf_addr_is_inaddr_any(&rp_all->rp))) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: Route node doesn't exist", __func__); + if (pend) + pim_rp_new(pim, pend->rp_address, bsgrp_node->group, + NULL, RP_SRC_BSR); + had_rp_node = false; + } else { + rp_info = (struct rp_info *)rn->info; + if (!rp_info) { + route_unlock_node(rn); + if (pend) + pim_rp_new(pim, pend->rp_address, + bsgrp_node->group, NULL, RP_SRC_BSR); + had_rp_node = false; + } + } + + /* We didn't have rp node and pending list is empty(unlikely), cleanup*/ + if ((!had_rp_node) && (!pend)) { + pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, + &bsgrp_node->group); + pim_free_bsgrp_data(bsgrp_node); + return; + } + + if ((had_rp_node) && (rp_info->rp_src != RP_SRC_STATIC)) { + /* This means we searched and got rp node, needs unlock */ + route_unlock_node(rn); + + if (active && pend) { + if (pim_addr_cmp(active->rp_address, pend->rp_address)) + pim_rp_change(pim, pend->rp_address, + bsgrp_node->group, RP_SRC_BSR); + } + + /* Possible when the first BSM has group with 0 rp count */ + if ((!active) && (!pend)) { + if (PIM_DEBUG_BSM) { + zlog_debug( + "%s: Both bsrp and partial list are empty", + __func__); + } + pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, + &bsgrp_node->group); + pim_free_bsgrp_data(bsgrp_node); + return; + } + + /* Possible when a group with 0 rp count received in BSM */ + if ((active) && (!pend)) { + pim_rp_del(pim, active->rp_address, bsgrp_node->group, + NULL, RP_SRC_BSR); + pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, + &bsgrp_node->group); + if (PIM_DEBUG_BSM) { + zlog_debug("%s:Pend List is null,del grp node", + __func__); + } + pim_free_bsgrp_data(bsgrp_node); + return; + } + } + + if ((had_rp_node) && (rp_info->rp_src == RP_SRC_STATIC)) { + /* We need to unlock rn this case */ + route_unlock_node(rn); + /* there is a chance that static rp exist and bsrp cleaned + * so clean bsgrp node if pending list empty + */ + if (!pend) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: Partial list is empty, static rp exists", + __func__); + pim_free_bsgrp_node(bsgrp_node->scope->bsrp_table, + &bsgrp_node->group); + pim_free_bsgrp_data(bsgrp_node); + return; + } + } + + /* swap the list & delete all nodes in partial list (old bsrp_list) + * before swap + * active is head of bsrp list + * pend is head of partial list + * After swap + * active is head of partial list + * pend is head of bsrp list + * So check appriate head after swap and clean the new partial list + */ + bsm_rpinfos_swap_all(bsgrp_node->bsrp_list, + bsgrp_node->partial_bsrp_list); + + if (active) + pim_g2rp_timer_stop(active); + pim_bsm_rpinfos_free(bsgrp_node->partial_bsrp_list); +} + +static bool is_preferred_bsr(struct pim_instance *pim, pim_addr bsr, + uint32_t bsr_prio) +{ + if (!pim_addr_cmp(bsr, pim->global_scope.current_bsr)) + return true; + + if (bsr_prio > pim->global_scope.current_bsr_prio) + return true; + + else if (bsr_prio == pim->global_scope.current_bsr_prio) { + if (pim_addr_cmp(bsr, pim->global_scope.current_bsr) >= 0) + return true; + else + return false; + } else + return false; +} + +static void pim_bsm_update(struct pim_instance *pim, pim_addr bsr, + uint32_t bsr_prio) +{ + if (pim_addr_cmp(bsr, pim->global_scope.current_bsr)) { + pim_nht_bsr_del(pim, pim->global_scope.current_bsr); + pim_nht_bsr_add(pim, bsr); + + pim->global_scope.current_bsr = bsr; + pim->global_scope.current_bsr_first_ts = + pim_time_monotonic_sec(); + pim->global_scope.state = ACCEPT_PREFERRED; + } + pim->global_scope.current_bsr_prio = bsr_prio; + pim->global_scope.current_bsr_last_ts = pim_time_monotonic_sec(); +} + +void pim_bsm_clear(struct pim_instance *pim) +{ + struct route_node *rn; + struct route_node *rpnode; + struct bsgrp_node *bsgrp; + pim_addr nht_p; + struct prefix g_all; + struct rp_info *rp_all; + struct pim_upstream *up; + struct rp_info *rp_info; + bool upstream_updated = false; + + pim_nht_bsr_del(pim, pim->global_scope.current_bsr); + + /* Reset scope zone data */ + pim->global_scope.accept_nofwd_bsm = false; + pim->global_scope.state = ACCEPT_ANY; + pim->global_scope.current_bsr = PIMADDR_ANY; + pim->global_scope.current_bsr_prio = 0; + pim->global_scope.current_bsr_first_ts = 0; + pim->global_scope.current_bsr_last_ts = 0; + pim->global_scope.bsm_frag_tag = 0; + pim_bsm_frags_free(&pim->global_scope); + + pim_bs_timer_stop(&pim->global_scope); + + for (rn = route_top(pim->global_scope.bsrp_table); rn; + rn = route_next(rn)) { + bsgrp = rn->info; + if (!bsgrp) + continue; + + rpnode = route_node_lookup(pim->rp_table, &bsgrp->group); + + if (!rpnode) { + pim_free_bsgrp_node(bsgrp->scope->bsrp_table, + &bsgrp->group); + pim_free_bsgrp_data(bsgrp); + continue; + } + + rp_info = (struct rp_info *)rpnode->info; + + if ((!rp_info) || (rp_info->rp_src != RP_SRC_BSR)) { + pim_free_bsgrp_node(bsgrp->scope->bsrp_table, + &bsgrp->group); + pim_free_bsgrp_data(bsgrp); + continue; + } + + /* Deregister addr with Zebra NHT */ + nht_p = rp_info->rp.rpf_addr; + + if (PIM_DEBUG_PIM_NHT_RP) { + zlog_debug("%s: Deregister RP addr %pPA with Zebra ", + __func__, &nht_p); + } + + pim_delete_tracked_nexthop(pim, nht_p, NULL, rp_info); + + if (!pim_get_all_mcast_group(&g_all)) + return; + + rp_all = pim_rp_find_match_group(pim, &g_all); + + if (rp_all == rp_info) { + rp_all->rp.rpf_addr = PIMADDR_ANY; + rp_all->i_am_rp = 0; + } else { + /* Delete the rp_info from rp-list */ + listnode_delete(pim->rp_list, rp_info); + + /* Delete the rp node from rp_table */ + rpnode->info = NULL; + route_unlock_node(rpnode); + route_unlock_node(rpnode); + XFREE(MTYPE_PIM_RP, rp_info); + } + + pim_free_bsgrp_node(bsgrp->scope->bsrp_table, &bsgrp->group); + pim_free_bsgrp_data(bsgrp); + } + pim_rp_refresh_group_to_rp_mapping(pim); + + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* Find the upstream (*, G) whose upstream address is same as + * the RP + */ + if (!pim_addr_is_any(up->sg.src)) + continue; + + struct prefix grp; + struct rp_info *trp_info; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + + /* RP not found for the group grp */ + if (pim_rpf_addr_is_inaddr_any(&trp_info->rp)) { + pim_upstream_rpf_clear(pim, up); + pim_rp_set_upstream_addr(pim, &up->upstream_addr, + up->sg.src, up->sg.grp); + } else { + /* RP found for the group grp */ + pim_upstream_update(pim, up); + upstream_updated = true; + } + } + + if (upstream_updated) + pim_zebra_update_all_interfaces(pim); +} + +static bool pim_bsm_send_intf(uint8_t *buf, int len, struct interface *ifp, + pim_addr dst_addr) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: Pim interface not available for %s", + __func__, ifp->name); + return false; + } + + if (pim_ifp->pim_sock_fd == -1) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: Pim sock not available for %s", + __func__, ifp->name); + return false; + } + + if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, + dst_addr, buf, len, ifp)) { + zlog_warn("%s: Could not send BSM message on interface: %s", + __func__, ifp->name); + return false; + } + + if (!pim_ifp->pim_passive_enable) + pim_ifp->pim_ifstat_bsm_tx++; + + pim_ifp->pim->bsm_sent++; + return true; +} + +static bool pim_bsm_frag_send(uint8_t *buf, uint32_t len, struct interface *ifp, + uint32_t pim_mtu, pim_addr dst_addr, bool no_fwd) +{ + struct pim_interface *pim_ifp = ifp->info; + struct bsmmsg_grpinfo *grpinfo, *curgrp; + uint8_t *firstgrp_ptr; + uint8_t *pkt; + uint8_t *pak_start; + uint32_t parsed_len = 0; + uint32_t this_pkt_rem; + uint32_t copy_byte_count; + uint32_t this_pkt_len; + uint8_t total_rp_cnt; + uint8_t this_rp_cnt; + uint8_t frag_rp_cnt; + uint8_t rp_fit_cnt; + bool pak_pending = false; + + /* MTU passed here is PIM MTU (IP MTU less IP Hdr) */ + if (pim_mtu < (PIM_MIN_BSM_LEN)) { + zlog_warn( + "%s: mtu(pim mtu: %d) size less than minimum bootstrap len", + __func__, pim_mtu); + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: mtu (pim mtu:%d) less than minimum bootstrap len", + __func__, pim_mtu); + return false; + } + + pak_start = XCALLOC(MTYPE_PIM_BSM_PKT_VAR_MEM, pim_mtu); + + pkt = pak_start; + + /* Fill PIM header later before sending packet to calc checksum */ + pkt += PIM_MSG_HEADER_LEN; + buf += PIM_MSG_HEADER_LEN; + + /* copy bsm header to new packet at offset of pim hdr */ + memcpy(pkt, buf, PIM_BSM_HDR_LEN); + pkt += PIM_BSM_HDR_LEN; + buf += PIM_BSM_HDR_LEN; + parsed_len += (PIM_MSG_HEADER_LEN + PIM_BSM_HDR_LEN); + + /* Store the position of first grp ptr, which can be reused for + * next packet to start filling group. old bsm header and pim hdr + * remains. So need not be filled again for next packet onwards. + */ + firstgrp_ptr = pkt; + + /* we received mtu excluding IP hdr len as param + * now this_pkt_rem is mtu excluding + * PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN + */ + this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN); + + /* For each group till the packet length parsed */ + while (parsed_len < len) { + /* pkt ---> fragment's current pointer + * buf ---> input buffer's current pointer + * mtu ---> size of the pim packet - PIM header + * curgrp ---> current group on the fragment + * grpinfo ---> current group on the input buffer + * this_pkt_rem ---> bytes remaing on the current fragment + * rp_fit_cnt ---> num of rp for current grp that + * fits this frag + * total_rp_cnt ---> total rp present for the group in the buf + * frag_rp_cnt ---> no of rp for the group to be fit in + * the frag + * this_rp_cnt ---> how many rp have we parsed + */ + grpinfo = (struct bsmmsg_grpinfo *)buf; + memcpy(pkt, buf, PIM_BSM_GRP_LEN); + curgrp = (struct bsmmsg_grpinfo *)pkt; + parsed_len += PIM_BSM_GRP_LEN; + pkt += PIM_BSM_GRP_LEN; + buf += PIM_BSM_GRP_LEN; + this_pkt_rem -= PIM_BSM_GRP_LEN; + + /* initialize rp count and total_rp_cnt before the rp loop */ + this_rp_cnt = 0; + total_rp_cnt = grpinfo->frag_rp_count; + + /* Loop till all RPs for the group parsed */ + while (this_rp_cnt < total_rp_cnt) { + /* All RP from a group processed here. + * group is pointed by grpinfo. + * At this point make sure buf pointing to a RP + * within a group + */ + rp_fit_cnt = this_pkt_rem / PIM_BSM_RP_LEN; + + /* calculate how many rp am i going to copy in + * this frag + */ + if (rp_fit_cnt > (total_rp_cnt - this_rp_cnt)) + frag_rp_cnt = total_rp_cnt - this_rp_cnt; + else + frag_rp_cnt = rp_fit_cnt; + + /* populate the frag rp count for the current grp */ + curgrp->frag_rp_count = frag_rp_cnt; + copy_byte_count = frag_rp_cnt * PIM_BSM_RP_LEN; + + /* copy all the rp that we are fitting in this + * frag for the grp + */ + memcpy(pkt, buf, copy_byte_count); + this_rp_cnt += frag_rp_cnt; + buf += copy_byte_count; + pkt += copy_byte_count; + parsed_len += copy_byte_count; + this_pkt_rem -= copy_byte_count; + + /* Either we couldn't fit all rp for the group or the + * mtu reached + */ + if ((this_rp_cnt < total_rp_cnt) + || (this_pkt_rem + < (PIM_BSM_GRP_LEN + PIM_BSM_RP_LEN))) { + /* No space to fit in more rp, send this pkt */ + this_pkt_len = pim_mtu - this_pkt_rem; + pim_msg_build_header( + pim_ifp->primary_address, dst_addr, + pak_start, this_pkt_len, + PIM_MSG_TYPE_BOOTSTRAP, no_fwd); + pim_bsm_send_intf(pak_start, this_pkt_len, ifp, + dst_addr); + + /* Construct next fragment. Reuse old packet */ + pkt = firstgrp_ptr; + this_pkt_rem = pim_mtu - (PIM_BSM_HDR_LEN + + PIM_MSG_HEADER_LEN); + + /* If pkt can't accommodate next group + at + * least one rp, we must break out of this inner + * loop and process next RP + */ + if (total_rp_cnt == this_rp_cnt) + break; + + /* If some more RPs for the same group pending, + * fill grp hdr + */ + memcpy(pkt, (uint8_t *)grpinfo, + PIM_BSM_GRP_LEN); + curgrp = (struct bsmmsg_grpinfo *)pkt; + pkt += PIM_BSM_GRP_LEN; + this_pkt_rem -= PIM_BSM_GRP_LEN; + pak_pending = false; + } else { + /* We filled something but not yet sent out */ + pak_pending = true; + } + } /* while RP count */ + } /*while parsed len */ + + /* Send if we have any unsent packet */ + if (pak_pending) { + this_pkt_len = pim_mtu - this_pkt_rem; + pim_msg_build_header(pim_ifp->primary_address, dst_addr, + pak_start, this_pkt_len, + PIM_MSG_TYPE_BOOTSTRAP, no_fwd); + pim_bsm_send_intf(pak_start, (pim_mtu - this_pkt_rem), ifp, + dst_addr); + } + XFREE(MTYPE_PIM_BSM_PKT_VAR_MEM, pak_start); + return true; +} + +static void pim_bsm_fwd_whole_sz(struct pim_instance *pim, uint8_t *buf, + uint32_t len, int sz) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + pim_addr dst_addr; + uint32_t pim_mtu; + bool no_fwd = false; + bool ret = false; + + /* For now only global scope zone is supported, so send on all + * pim interfaces in the vrf + */ + dst_addr = qpim_all_pim_routers_addr; + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if ((!pim_ifp) || (!pim_ifp->bsm_enable)) + continue; + + /* + * RFC 5059 Sec 3.4: + * When a Bootstrap message is forwarded, it is forwarded out + * of every multicast-capable interface that has PIM neighbors. + * + * So skipping pim interfaces with no neighbors. + */ + if (listcount(pim_ifp->pim_neighbor_list) == 0) + continue; + + pim_hello_require(ifp); + pim_mtu = ifp->mtu - MAX_IP_HDR_LEN; + if (pim_mtu < len) { + ret = pim_bsm_frag_send(buf, len, ifp, pim_mtu, + dst_addr, no_fwd); + if (PIM_DEBUG_BSM) + zlog_debug("%s: pim_bsm_frag_send returned %s", + __func__, ret ? "TRUE" : "FALSE"); + } else { + pim_msg_build_header(pim_ifp->primary_address, dst_addr, + buf, len, PIM_MSG_TYPE_BOOTSTRAP, + no_fwd); + if (!pim_bsm_send_intf(buf, len, ifp, dst_addr)) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: pim_bsm_send_intf returned false", + __func__); + } + } + } +} + +bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp) +{ + pim_addr dst_addr; + struct pim_interface *pim_ifp; + struct bsm_scope *scope; + struct bsm_frag *bsfrag; + uint32_t pim_mtu; + bool no_fwd = true; + bool ret = false; + + if (PIM_DEBUG_BSM) + zlog_debug("%s: New neighbor %pPA seen on %s", __func__, + &neigh->source_addr, ifp->name); + + pim_ifp = ifp->info; + + /* DR only forwards BSM packet */ + if (!pim_addr_cmp(pim_ifp->pim_dr_addr, pim_ifp->primary_address)) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: It is not DR, so don't forward BSM packet", + __func__); + } + + if (!pim_ifp->bsm_enable) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: BSM proc not enabled on %s", __func__, + ifp->name); + return ret; + } + + scope = &pim_ifp->pim->global_scope; + + if (!bsm_frags_count(scope->bsm_frags)) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: BSM list for the scope is empty", + __func__); + return ret; + } + + if (!pim_ifp->ucast_bsm_accept) { + dst_addr = qpim_all_pim_routers_addr; + if (PIM_DEBUG_BSM) + zlog_debug("%s: Sending BSM mcast to %pPA", __func__, + &neigh->source_addr); + } else { + dst_addr = neigh->source_addr; + if (PIM_DEBUG_BSM) + zlog_debug("%s: Sending BSM ucast to %pPA", __func__, + &neigh->source_addr); + } + pim_mtu = ifp->mtu - MAX_IP_HDR_LEN; + pim_hello_require(ifp); + + frr_each (bsm_frags, scope->bsm_frags, bsfrag) { + if (pim_mtu < bsfrag->size) { + ret = pim_bsm_frag_send(bsfrag->data, bsfrag->size, ifp, + pim_mtu, dst_addr, no_fwd); + if (!ret) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: pim_bsm_frag_send failed", + __func__); + } + } else { + /* Pim header needs to be constructed */ + pim_msg_build_header(pim_ifp->primary_address, dst_addr, + bsfrag->data, bsfrag->size, + PIM_MSG_TYPE_BOOTSTRAP, no_fwd); + ret = pim_bsm_send_intf(bsfrag->data, bsfrag->size, ifp, + dst_addr); + if (!ret) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: pim_bsm_frag_send failed", + __func__); + } + } + } + return ret; +} + +struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope, + struct prefix *grp) +{ + struct route_node *rn; + struct bsgrp_node *bsgrp; + + rn = route_node_lookup(scope->bsrp_table, grp); + if (!rn) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: Route node doesn't exist for the group", + __func__); + return NULL; + } + bsgrp = rn->info; + route_unlock_node(rn); + + return bsgrp; +} + +static uint32_t hash_calc_on_grp_rp(struct prefix group, pim_addr rp, + uint8_t hashmasklen) +{ + uint64_t temp; + uint32_t hash; + uint32_t grpaddr; + uint32_t rp_add; + uint32_t mask = 0xffffffff; + + /* mask to be made zero if hashmasklen is 0 because mask << 32 + * may not give 0. hashmasklen can be 0 to 32. + */ + if (hashmasklen == 0) + mask = 0; + + /* in_addr stores ip in big endian, hence network byte order + * convert to uint32 before processing hash + */ +#if PIM_IPV == 4 + grpaddr = ntohl(group.u.prefix4.s_addr); +#else + grpaddr = group.u.prefix6.s6_addr32[0] ^ group.u.prefix6.s6_addr32[1] ^ + group.u.prefix6.s6_addr32[2] ^ group.u.prefix6.s6_addr32[3]; +#endif + /* Avoid shifting by 32 bit on a 32 bit register */ + if (hashmasklen) + grpaddr = grpaddr & ((mask << (32 - hashmasklen))); + else + grpaddr = grpaddr & mask; + +#if PIM_IPV == 4 + rp_add = ntohl(rp.s_addr); +#else + rp_add = rp.s6_addr32[0] ^ rp.s6_addr32[1] ^ rp.s6_addr32[2] ^ + rp.s6_addr32[3]; +#endif + temp = 1103515245 * ((1103515245 * (uint64_t)grpaddr + 12345) ^ rp_add) + + 12345; + hash = temp & (0x7fffffff); + return hash; +} + +static bool pim_install_bsm_grp_rp(struct pim_instance *pim, + struct bsgrp_node *grpnode, + struct bsmmsg_rpinfo *rp) +{ + struct bsm_rpinfo *bsm_rpinfo; + uint8_t hashMask_len = pim->global_scope.hashMasklen; + + /*memory allocation for bsm_rpinfo */ + bsm_rpinfo = XCALLOC(MTYPE_PIM_BSRP_INFO, sizeof(*bsm_rpinfo)); + + bsm_rpinfo->rp_prio = rp->rp_pri; + bsm_rpinfo->rp_holdtime = rp->rp_holdtime; + bsm_rpinfo->rp_address = rp->rpaddr.addr; + bsm_rpinfo->elapse_time = 0; + + /* Back pointer to the group node. */ + bsm_rpinfo->bsgrp_node = grpnode; + + /* update hash for this rp node */ + bsm_rpinfo->hash = hash_calc_on_grp_rp(grpnode->group, rp->rpaddr.addr, + hashMask_len); + if (bsm_rpinfos_add(grpnode->partial_bsrp_list, bsm_rpinfo) == NULL) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, bs_rpinfo node added to the partial bs_rplist.", + __func__); + return true; + } + + if (PIM_DEBUG_BSM) + zlog_debug("%s: list node not added", __func__); + + XFREE(MTYPE_PIM_BSRP_INFO, bsm_rpinfo); + return false; +} + +static void pim_update_pending_rp_cnt(struct bsm_scope *sz, + struct bsgrp_node *bsgrp, + uint16_t bsm_frag_tag, + uint32_t total_rp_count) +{ + if (bsgrp->pend_rp_cnt) { + /* received bsm is different packet , + * it is not same fragment. + */ + if (bsm_frag_tag != bsgrp->frag_tag) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s,Received a new BSM ,so clear the pending bs_rpinfo list.", + __func__); + pim_bsm_rpinfos_free(bsgrp->partial_bsrp_list); + bsgrp->pend_rp_cnt = total_rp_count; + } + } else + bsgrp->pend_rp_cnt = total_rp_count; + + bsgrp->frag_tag = bsm_frag_tag; +} + +/* Parsing BSR packet and adding to partial list of corresponding bsgrp node */ +static bool pim_bsm_parse_install_g2rp(struct bsm_scope *scope, uint8_t *buf, + int buflen, uint16_t bsm_frag_tag) +{ + struct bsmmsg_grpinfo grpinfo; + struct bsmmsg_rpinfo rpinfo; + struct prefix group; + struct bsgrp_node *bsgrp = NULL; + int frag_rp_cnt = 0; + int offset = 0; + int ins_count = 0; + pim_addr grp_addr; + + while (buflen > offset) { + if (offset + (int)sizeof(struct bsmmsg_grpinfo) > buflen) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: buflen received %d is less than the internal data structure of the packet would suggest", + __func__, buflen); + return false; + } + /* Extract Group tlv from BSM */ + memcpy(&grpinfo, buf, sizeof(struct bsmmsg_grpinfo)); + grp_addr = grpinfo.group.addr; + + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, Group %pPAs Rpcount:%d Fragment-Rp-count:%d", + __func__, &grp_addr, grpinfo.rp_count, + grpinfo.frag_rp_count); + + buf += sizeof(struct bsmmsg_grpinfo); + offset += sizeof(struct bsmmsg_grpinfo); + + group.family = PIM_AF; + if (grpinfo.group.mask > PIM_MAX_BITLEN) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, prefix length specified: %d is too long", + __func__, grpinfo.group.mask); + return false; + } + + pim_addr_to_prefix(&group, grp_addr); + group.prefixlen = grpinfo.group.mask; + + /* Get the Group node for the BSM rp table */ + bsgrp = pim_bsm_get_bsgrp_node(scope, &group); + + if (grpinfo.rp_count == 0) { + struct bsm_rpinfo *old_rpinfo; + + /* BSR explicitly no longer has RPs for this group */ + if (!bsgrp) + continue; + + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, Rp count is zero for group: %pPAs", + __func__, &grp_addr); + + old_rpinfo = bsm_rpinfos_first(bsgrp->bsrp_list); + if (old_rpinfo) + pim_rp_del(scope->pim, old_rpinfo->rp_address, + group, NULL, RP_SRC_BSR); + + pim_free_bsgrp_node(scope->bsrp_table, &bsgrp->group); + pim_free_bsgrp_data(bsgrp); + continue; + } + + if (!bsgrp) { + if (PIM_DEBUG_BSM) + zlog_debug("%s, Create new BSM Group node.", + __func__); + + /* create a new node to be added to the tree. */ + bsgrp = pim_bsm_new_bsgrp_node(scope->bsrp_table, + &group); + + if (!bsgrp) { + zlog_debug( + "%s, Failed to get the BSM group node.", + __func__); + continue; + } + + bsgrp->scope = scope; + } + + pim_update_pending_rp_cnt(scope, bsgrp, bsm_frag_tag, + grpinfo.rp_count); + frag_rp_cnt = grpinfo.frag_rp_count; + ins_count = 0; + + while (frag_rp_cnt--) { + if (offset + (int)sizeof(struct bsmmsg_rpinfo) + > buflen) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, buflen received: %u is less than the internal data structure of the packet would suggest", + __func__, buflen); + return false; + } + + /* Extract RP address tlv from BSM */ + memcpy(&rpinfo, buf, sizeof(struct bsmmsg_rpinfo)); + rpinfo.rp_holdtime = ntohs(rpinfo.rp_holdtime); + buf += sizeof(struct bsmmsg_rpinfo); + offset += sizeof(struct bsmmsg_rpinfo); + + if (PIM_DEBUG_BSM) { + pim_addr rp_addr; + + rp_addr = rpinfo.rpaddr.addr; + zlog_debug( + "%s, Rp address - %pPAs; pri:%d hold:%d", + __func__, &rp_addr, rpinfo.rp_pri, + rpinfo.rp_holdtime); + } + + /* Call Install api to update grp-rp mappings */ + if (pim_install_bsm_grp_rp(scope->pim, bsgrp, &rpinfo)) + ins_count++; + } + + bsgrp->pend_rp_cnt -= ins_count; + + if (!bsgrp->pend_rp_cnt) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s, Recvd all the rps for this group, so bsrp list with penidng rp list.", + __func__); + /* replace the bsrp_list with pending list */ + pim_instate_pend_list(bsgrp); + } + } + return true; +} + +int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf, + uint32_t buf_size, bool no_fwd) +{ + struct bsm_hdr *bshdr; + int sz = PIM_GBL_SZ_ID; + struct bsmmsg_grpinfo *msg_grp; + struct pim_interface *pim_ifp = NULL; + struct bsm_frag *bsfrag; + struct pim_instance *pim; + uint16_t frag_tag; + pim_addr bsr_addr; + bool empty_bsm = false; + + /* BSM Packet acceptance validation */ + pim_ifp = ifp->info; + if (!pim_ifp) { + if (PIM_DEBUG_BSM) + zlog_debug("%s: multicast not enabled on interface %s", + __func__, ifp->name); + return -1; + } + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + pim_ifp->pim_ifstat_bsm_rx++; + pim = pim_ifp->pim; + pim->bsm_rcvd++; + + /* Drop if bsm processing is disabled on interface */ + if (!pim_ifp->bsm_enable) { + zlog_warn("%s: BSM not enabled on interface %s", __func__, + ifp->name); + pim_ifp->pim_ifstat_bsm_cfg_miss++; + pim->bsm_dropped++; + return -1; + } + + if (buf_size < (PIM_MSG_HEADER_LEN + sizeof(struct bsm_hdr))) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s: received buffer length of %d which is too small to properly decode", + __func__, buf_size); + return -1; + } + + bshdr = (struct bsm_hdr *)(buf + PIM_MSG_HEADER_LEN); + if (bshdr->hm_len > PIM_MAX_BITLEN) { + zlog_warn( + "Bad hashmask length for %s; got %hhu, expected value in range 0-32", + PIM_AF_NAME, bshdr->hm_len); + pim->bsm_dropped++; + return -1; + } + pim->global_scope.hashMasklen = bshdr->hm_len; + frag_tag = ntohs(bshdr->frag_tag); + /* NB: bshdr->bsr_addr.addr is packed/unaligned => memcpy */ + memcpy(&bsr_addr, &bshdr->bsr_addr.addr, sizeof(bsr_addr)); + + /* Identify empty BSM */ + if ((buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN) < PIM_BSM_GRP_LEN) + empty_bsm = true; + + if (!empty_bsm) { + msg_grp = (struct bsmmsg_grpinfo *)(buf + PIM_MSG_HEADER_LEN + + PIM_BSM_HDR_LEN); + /* Currently we don't support scope zoned BSM */ + if (msg_grp->group.sz) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s : Administratively scoped range BSM received", + __func__); + pim_ifp->pim_ifstat_bsm_invalid_sz++; + pim->bsm_dropped++; + return -1; + } + } + + /* Drop if bsr is not preferred bsr */ + if (!is_preferred_bsr(pim, bsr_addr, bshdr->bsr_prio)) { + if (PIM_DEBUG_BSM) + zlog_debug("%s : Received a non-preferred BSM", + __func__); + pim->bsm_dropped++; + return -1; + } + + if (no_fwd) { + /* only accept no-forward BSM if quick refresh on startup */ + if ((pim->global_scope.accept_nofwd_bsm) + || (frag_tag == pim->global_scope.bsm_frag_tag)) { + pim->global_scope.accept_nofwd_bsm = false; + } else { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s : nofwd_bsm received on %pPAs when accpt_nofwd_bsm false", + __func__, &bsr_addr); + pim->bsm_dropped++; + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++; + return -1; + } + } + + /* BSM packet is seen, so resetting accept_nofwd_bsm to false */ + if (pim->global_scope.accept_nofwd_bsm) + pim->global_scope.accept_nofwd_bsm = false; + + if (!pim_addr_cmp(sg->grp, qpim_all_pim_routers_addr)) { + /* Multicast BSMs are only accepted if source interface & IP + * match RPF towards the BSR's IP address, or they have + * no-forward set + */ + if (!no_fwd && + !pim_nht_bsr_rpf_check(pim, bsr_addr, ifp, sg->src)) { + if (PIM_DEBUG_BSM) + zlog_debug( + "BSM check: RPF to BSR %pPAs is not %pPA%%%s", + &bsr_addr, &sg->src, ifp->name); + pim->bsm_dropped++; + return -1; + } + } else if (if_address_is_local(&sg->grp, PIM_AF, pim->vrf->vrf_id)) { + /* Unicast BSM received - if ucast bsm not enabled on + * the interface, drop it + */ + if (!pim_ifp->ucast_bsm_accept) { + if (PIM_DEBUG_BSM) + zlog_debug( + "%s : Unicast BSM not enabled on interface %s", + __func__, ifp->name); + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss++; + pim->bsm_dropped++; + return -1; + } + + } else { + if (PIM_DEBUG_BSM) + zlog_debug("%s : Invalid destination address", + __func__); + pim->bsm_dropped++; + return -1; + } + + if (empty_bsm) { + if (PIM_DEBUG_BSM) + zlog_debug("%s : Empty Pref BSM received", __func__); + } + /* Parse Update bsm rp table and install/uninstall rp if required */ + if (!pim_bsm_parse_install_g2rp( + &pim_ifp->pim->global_scope, + (buf + PIM_BSM_HDR_LEN + PIM_MSG_HEADER_LEN), + (buf_size - PIM_BSM_HDR_LEN - PIM_MSG_HEADER_LEN), + frag_tag)) { + if (PIM_DEBUG_BSM) { + zlog_debug("%s, Parsing BSM failed.", __func__); + } + pim->bsm_dropped++; + return -1; + } + /* Restart the bootstrap timer */ + pim_bs_timer_restart(&pim_ifp->pim->global_scope, + PIM_BSR_DEFAULT_TIMEOUT); + + /* If new BSM received, clear the old bsm database */ + if (pim_ifp->pim->global_scope.bsm_frag_tag != frag_tag) { + if (PIM_DEBUG_BSM) { + zlog_debug("%s: Current frag tag: %d Frag teg rcvd: %d", + __func__, + pim_ifp->pim->global_scope.bsm_frag_tag, + frag_tag); + } + pim_bsm_frags_free(&pim_ifp->pim->global_scope); + pim_ifp->pim->global_scope.bsm_frag_tag = frag_tag; + } + + /* update the scope information from bsm */ + pim_bsm_update(pim, bsr_addr, bshdr->bsr_prio); + + if (!no_fwd) { + pim_bsm_fwd_whole_sz(pim_ifp->pim, buf, buf_size, sz); + bsfrag = XCALLOC(MTYPE_PIM_BSM_FRAG, + sizeof(struct bsm_frag) + buf_size); + + bsfrag->size = buf_size; + memcpy(bsfrag->data, buf, buf_size); + bsm_frags_add_tail(pim_ifp->pim->global_scope.bsm_frags, + bsfrag); + } + + return 0; +} diff --git a/pimd/pim_bsm.h b/pimd/pim_bsm.h new file mode 100644 index 0000000..fb09e3b --- /dev/null +++ b/pimd/pim_bsm.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_bsm.h: PIM BSM handling related + * + * Copyright (C) 2018-19 Vmware, Inc. + * Saravanan K + */ + +#ifndef __PIM_BSM_H__ +#define __PIM_BSM_H__ + +#include "if.h" +#include "vty.h" +#include "typesafe.h" +#include "table.h" +#include "pim_rp.h" +#include "pim_msg.h" + +/* Defines */ +#define PIM_GBL_SZ_ID 0 /* global scope zone id set to 0 */ +#define PIM_BS_TIME 60 /* RFC 5059 - Sec 5 */ +#define PIM_BSR_DEFAULT_TIMEOUT 130 /* RFC 5059 - Sec 5 */ + +/* These structures are only encoded IPv4 specific */ +#define PIM_BSM_HDR_LEN sizeof(struct bsm_hdr) +#define PIM_BSM_GRP_LEN sizeof(struct bsmmsg_grpinfo) +#define PIM_BSM_RP_LEN sizeof(struct bsmmsg_rpinfo) + +#define PIM_MIN_BSM_LEN \ + (PIM_HDR_LEN + PIM_BSM_HDR_LEN + PIM_BSM_GRP_LEN + PIM_BSM_RP_LEN) + +/* Datastructures + * ============== + */ + +/* Non candidate BSR states */ +enum ncbsr_state { + NO_INFO = 0, + ACCEPT_ANY, + ACCEPT_PREFERRED +}; + +PREDECL_DLIST(bsm_frags); + +/* BSM scope - bsm processing is per scope */ +struct bsm_scope { + int sz_id; /* scope zone id */ + enum ncbsr_state state; /* non candidate BSR state */ + bool accept_nofwd_bsm; /* no fwd bsm accepted for scope */ + pim_addr current_bsr; /* current elected BSR for the sz */ + uint32_t current_bsr_prio; /* current BSR priority */ + int64_t current_bsr_first_ts; /* current BSR elected time */ + int64_t current_bsr_last_ts; /* Last BSM received from E-BSR */ + uint16_t bsm_frag_tag; /* Last received frag tag from E-BSR */ + uint8_t hashMasklen; /* Mask in hash calc RFC 7761 4.7.2 */ + struct pim_instance *pim; /* Back pointer to pim instance */ + + /* current set of fragments for forwarding */ + struct bsm_frags_head bsm_frags[1]; + + struct route_table *bsrp_table; /* group2rp mapping rcvd from BSR */ + struct event *bs_timer; /* Boot strap timer */ +}; + +/* BSM packet (= fragment) - this is stored as list in bsm_frags inside scope + * This is used for forwarding to new neighbors or restarting mcast routers + */ +struct bsm_frag { + struct bsm_frags_item item; + + uint32_t size; /* size of the packet */ + uint8_t data[0]; /* Actual packet (dyn size) */ +}; + +DECLARE_DLIST(bsm_frags, struct bsm_frag, item); + +PREDECL_SORTLIST_UNIQ(bsm_rpinfos); + +/* This is the group node of the bsrp table in scope. + * this node maintains the list of rp for the group. + */ +struct bsgrp_node { + struct prefix group; /* Group range */ + struct bsm_scope *scope; /* Back ptr to scope */ + + /* RPs advertised by BSR, and temporary list while receiving new set */ + struct bsm_rpinfos_head bsrp_list[1]; + struct bsm_rpinfos_head partial_bsrp_list[1]; + + int pend_rp_cnt; /* Total RP - Received RP */ + uint16_t frag_tag; /* frag tag to identify the fragment */ +}; + +/* Items on [partial_]bsrp_list above. + * Holds info of each candidate RP received for the bsgrp_node's prefix. + */ +struct bsm_rpinfo { + struct bsm_rpinfos_item item; + + uint32_t hash; /* Hash Value as per RFC 7761 4.7.2 */ + uint32_t elapse_time; /* upd at expiry of elected RP node */ + uint16_t rp_prio; /* RP priority */ + uint16_t rp_holdtime; /* RP holdtime - g2rp timer value */ + pim_addr rp_address; /* RP Address */ + struct bsgrp_node *bsgrp_node; /* Back ptr to bsgrp_node */ + struct event *g2rp_timer; /* Run only for elected RP node */ +}; + +extern int pim_bsm_rpinfo_cmp(const struct bsm_rpinfo *a, + const struct bsm_rpinfo *b); +DECLARE_SORTLIST_UNIQ(bsm_rpinfos, struct bsm_rpinfo, item, pim_bsm_rpinfo_cmp); + +/* Structures to extract Bootstrap Message header and Grp to RP Mappings + * ===================================================================== + * BSM Format: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |PIM Ver| Type |N| Reserved | Checksum | PIM HDR + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Fragment Tag | Hash Mask Len | BSR Priority | BS HDR(1) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | BSR Address (Encoded-Unicast format) | BS HDR(2) + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Group Address 1 (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Count 1 | Frag RP Cnt 1 | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address 1 (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP1 Holdtime | RP1 Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address 2 (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP2 Holdtime | RP2 Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address m (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RPm Holdtime | RPm Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Group Address 2 (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Group Address n (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Count n | Frag RP Cnt n | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address 1 (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP1 Holdtime | RP1 Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address 2 (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP2 Holdtime | RP2 Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RP Address m (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RPm Holdtime | RPm Priority | Reserved | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct bsm_hdr { + uint16_t frag_tag; + uint8_t hm_len; + uint8_t bsr_prio; +#if PIM_IPV == 4 + struct pim_encoded_ipv4_unicast bsr_addr; +#else + struct pim_encoded_ipv6_unicast bsr_addr; +#endif +} __attribute__((packed)); + +struct bsmmsg_grpinfo { +#if PIM_IPV == 4 + struct pim_encoded_group_ipv4 group; +#else + struct pim_encoded_group_ipv6 group; +#endif + uint8_t rp_count; + uint8_t frag_rp_count; + uint16_t reserved; +} __attribute__((packed)); + +struct bsmmsg_rpinfo { +#if PIM_IPV == 4 + struct pim_encoded_ipv4_unicast rpaddr; +#else + struct pim_encoded_ipv6_unicast rpaddr; +#endif + uint16_t rp_holdtime; + uint8_t rp_pri; + uint8_t reserved; +} __attribute__((packed)); + +/* API */ +void pim_bsm_proc_init(struct pim_instance *pim); +void pim_bsm_proc_free(struct pim_instance *pim); +void pim_bsm_clear(struct pim_instance *pim); +void pim_bsm_write_config(struct vty *vty, struct interface *ifp); +int pim_bsm_process(struct interface *ifp, pim_sgaddr *sg, uint8_t *buf, + uint32_t buf_size, bool no_fwd); +bool pim_bsm_new_nbr_fwd(struct pim_neighbor *neigh, struct interface *ifp); +struct bsgrp_node *pim_bsm_get_bsgrp_node(struct bsm_scope *scope, + struct prefix *grp); +#endif diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c new file mode 100644 index 0000000..a2d756a --- /dev/null +++ b/pimd/pim_cmd.c @@ -0,0 +1,6737 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "lib/json.h" +#include "command.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "plist.h" +#include "hash.h" +#include "nexthop.h" +#include "vrf.h" +#include "ferr.h" + +#include "pimd.h" +#include "pim_mroute.h" +#include "pim_cmd.h" +#include "pim_iface.h" +#include "pim_vty.h" +#include "pim_mroute.h" +#include "pim_str.h" +#include "pim_igmp.h" +#include "pim_igmpv3.h" +#include "pim_sock.h" +#include "pim_time.h" +#include "pim_util.h" +#include "pim_oil.h" +#include "pim_neighbor.h" +#include "pim_pim.h" +#include "pim_ifchannel.h" +#include "pim_hello.h" +#include "pim_msg.h" +#include "pim_upstream.h" +#include "pim_rpf.h" +#include "pim_macro.h" +#include "pim_ssmpingd.h" +#include "pim_zebra.h" +#include "pim_static.h" +#include "pim_rp.h" +#include "pim_zlookup.h" +#include "pim_msdp.h" +#include "pim_ssm.h" +#include "pim_nht.h" +#include "pim_bfd.h" +#include "pim_vxlan.h" +#include "pim_mlag.h" +#include "bfd.h" +#include "pim_bsm.h" +#include "lib/northbound_cli.h" +#include "pim_errors.h" +#include "pim_nb.h" +#include "pim_addr.h" +#include "pim_cmd_common.h" + +#include "pimd/pim_cmd_clippy.c" + +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = pim_debug_config_write, +}; + +static struct vrf *pim_cmd_lookup_vrf(struct vty *vty, struct cmd_token *argv[], + const int argc, int *idx, bool uj) +{ + struct vrf *vrf; + + if (argv_find(argv, argc, "NAME", idx)) + vrf = vrf_lookup_by_name(argv[*idx]->arg); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) { + if (uj) + vty_json_empty(vty, NULL); + else + vty_out(vty, "Specified VRF: %s does not exist\n", + argv[*idx]->arg); + } + + return vrf; +} + +static void pim_show_assert_helper(struct vty *vty, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch, time_t now) +{ + char winner_str[INET_ADDRSTRLEN]; + struct in_addr ifaddr; + char uptime[10]; + char timer[10]; + char buf[PREFIX_STRLEN]; + + ifaddr = pim_ifp->primary_address; + + pim_inet4_dump("", ch->ifassert_winner, winner_str, + sizeof(winner_str)); + + pim_time_uptime(uptime, sizeof(uptime), now - ch->ifassert_creation); + pim_time_timer_to_mmss(timer, sizeof(timer), ch->t_ifassert_timer); + + vty_out(vty, "%-16s %-15s %-15pPAs %-15pPAs %-6s %-15s %-8s %-5s\n", + ch->interface->name, + inet_ntop(AF_INET, &ifaddr, buf, sizeof(buf)), &ch->sg.src, + &ch->sg.grp, pim_ifchannel_ifassert_name(ch->ifassert_state), + winner_str, uptime, timer); +} + +static void pim_show_assert(struct pim_instance *pim, struct vty *vty) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + time_t now; + + now = pim_time_monotonic_sec(); + + vty_out(vty, + "Interface Address Source Group State Winner Uptime Timer\n"); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + if (ch->ifassert_state == PIM_IFASSERT_NOINFO) + continue; + + pim_show_assert_helper(vty, pim_ifp, ch, now); + } /* scan interface channels */ + } +} + +static void pim_show_assert_internal_helper(struct vty *vty, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch) +{ + struct in_addr ifaddr; + char buf[PREFIX_STRLEN]; + + ifaddr = pim_ifp->primary_address; + + vty_out(vty, "%-16s %-15s %-15pPAs %-15pPAs %-3s %-3s %-3s %-4s\n", + ch->interface->name, + inet_ntop(AF_INET, &ifaddr, buf, sizeof(buf)), &ch->sg.src, + &ch->sg.grp, + PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags) ? "yes" : "no", + pim_macro_ch_could_assert_eval(ch) ? "yes" : "no", + PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags) ? "yes" + : "no", + pim_macro_assert_tracking_desired_eval(ch) ? "yes" : "no"); +} + +static void pim_show_assert_internal(struct pim_instance *pim, struct vty *vty) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + + vty_out(vty, + "CA: CouldAssert\n" + "ECA: Evaluate CouldAssert\n" + "ATD: AssertTrackingDesired\n" + "eATD: Evaluate AssertTrackingDesired\n\n"); + + vty_out(vty, + "Interface Address Source Group CA eCA ATD eATD\n"); + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_show_assert_internal_helper(vty, pim_ifp, ch); + } /* scan interface channels */ + } +} + +static void pim_show_assert_metric_helper(struct vty *vty, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch) +{ + char addr_str[INET_ADDRSTRLEN]; + struct pim_assert_metric am; + struct in_addr ifaddr; + char buf[PREFIX_STRLEN]; + + ifaddr = pim_ifp->primary_address; + + am = pim_macro_spt_assert_metric(&ch->upstream->rpf, + pim_ifp->primary_address); + + pim_inet4_dump("", am.ip_address, addr_str, sizeof(addr_str)); + + vty_out(vty, "%-16s %-15s %-15pPAs %-15pPAs %-3s %4u %6u %-15s\n", + ch->interface->name, + inet_ntop(AF_INET, &ifaddr, buf, sizeof(buf)), &ch->sg.src, + &ch->sg.grp, am.rpt_bit_flag ? "yes" : "no", + am.metric_preference, am.route_metric, addr_str); +} + +static void pim_show_assert_metric(struct pim_instance *pim, struct vty *vty) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + + vty_out(vty, + "Interface Address Source Group RPT Pref Metric Address \n"); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_show_assert_metric_helper(vty, pim_ifp, ch); + } /* scan interface channels */ + } +} + +static void pim_show_assert_winner_metric_helper(struct vty *vty, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch) +{ + char addr_str[INET_ADDRSTRLEN]; + struct pim_assert_metric *am; + struct in_addr ifaddr; + char pref_str[16]; + char metr_str[16]; + char buf[PREFIX_STRLEN]; + + ifaddr = pim_ifp->primary_address; + + am = &ch->ifassert_winner_metric; + + pim_inet4_dump("", am->ip_address, addr_str, sizeof(addr_str)); + + if (am->metric_preference == PIM_ASSERT_METRIC_PREFERENCE_MAX) + snprintf(pref_str, sizeof(pref_str), "INFI"); + else + snprintf(pref_str, sizeof(pref_str), "%4u", + am->metric_preference); + + if (am->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX) + snprintf(metr_str, sizeof(metr_str), "INFI"); + else + snprintf(metr_str, sizeof(metr_str), "%6u", am->route_metric); + + vty_out(vty, "%-16s %-15s %-15pPAs %-15pPAs %-3s %-4s %-6s %-15s\n", + ch->interface->name, + inet_ntop(AF_INET, &ifaddr, buf, sizeof(buf)), &ch->sg.src, + &ch->sg.grp, am->rpt_bit_flag ? "yes" : "no", pref_str, + metr_str, addr_str); +} + +static void pim_show_assert_winner_metric(struct pim_instance *pim, + struct vty *vty) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + + vty_out(vty, + "Interface Address Source Group RPT Pref Metric Address \n"); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_show_assert_winner_metric_helper(vty, pim_ifp, ch); + } /* scan interface channels */ + } +} + +static void igmp_show_interfaces(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct interface *ifp; + time_t now; + char buf[PREFIX_STRLEN]; + json_object *json = NULL; + json_object *json_row = NULL; + + now = pim_time_monotonic_sec(); + + if (uj) + json = json_object_new_object(); + else + vty_out(vty, + "Interface State Address V Querier QuerierIp Query Timer Uptime\n"); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; + struct listnode *sock_node; + struct gm_sock *igmp; + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, + igmp)) { + char uptime[10]; + char query_hhmmss[10]; + + pim_time_uptime(uptime, sizeof(uptime), + now - igmp->sock_creation); + pim_time_timer_to_hhmmss(query_hhmmss, + sizeof(query_hhmmss), + igmp->t_igmp_query_timer); + + if (uj) { + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + json_object_string_add(json_row, "upTime", + uptime); + json_object_int_add(json_row, "version", + pim_ifp->igmp_version); + + if (igmp->t_igmp_query_timer) { + json_object_boolean_true_add(json_row, + "querier"); + json_object_string_add(json_row, + "queryTimer", + query_hhmmss); + } + json_object_string_addf(json_row, "querierIp", + "%pI4", + &igmp->querier_addr); + + json_object_object_add(json, ifp->name, + json_row); + + if (igmp->mtrace_only) { + json_object_boolean_true_add( + json_row, "mtraceOnly"); + } + } else { + vty_out(vty, + "%-16s %5s %15s %d %7s %17pI4 %11s %8s\n", + ifp->name, + if_is_up(ifp) + ? (igmp->mtrace_only ? "mtrc" + : "up") + : "down", + inet_ntop(AF_INET, &igmp->ifaddr, buf, + sizeof(buf)), + pim_ifp->igmp_version, + igmp->t_igmp_query_timer ? "local" + : "other", + &igmp->querier_addr, query_hhmmss, + uptime); + } + } + } + + if (uj) + vty_json(vty, json); +} + +static void igmp_show_interfaces_single(struct pim_instance *pim, + struct vty *vty, const char *ifname, + bool uj) +{ + struct gm_sock *igmp; + struct interface *ifp; + struct listnode *sock_node; + struct pim_interface *pim_ifp; + char uptime[10]; + char query_hhmmss[10]; + char other_hhmmss[10]; + int found_ifname = 0; + int sqi; + long gmi_msec; /* Group Membership Interval */ + long lmqt_msec; + long ohpi_msec; + long oqpi_msec; /* Other Querier Present Interval */ + long qri_msec; + time_t now; + int lmqc; + + json_object *json = NULL; + json_object *json_row = NULL; + + if (uj) + json = json_object_new_object(); + + now = pim_time_monotonic_sec(); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (strcmp(ifname, "detail") && strcmp(ifname, ifp->name)) + continue; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, + igmp)) { + found_ifname = 1; + pim_time_uptime(uptime, sizeof(uptime), + now - igmp->sock_creation); + pim_time_timer_to_hhmmss(query_hhmmss, + sizeof(query_hhmmss), + igmp->t_igmp_query_timer); + pim_time_timer_to_hhmmss(other_hhmmss, + sizeof(other_hhmmss), + igmp->t_other_querier_timer); + + gmi_msec = PIM_IGMP_GMI_MSEC( + igmp->querier_robustness_variable, + igmp->querier_query_interval, + pim_ifp->gm_query_max_response_time_dsec); + + sqi = PIM_IGMP_SQI(pim_ifp->gm_default_query_interval); + + oqpi_msec = PIM_IGMP_OQPI_MSEC( + igmp->querier_robustness_variable, + igmp->querier_query_interval, + pim_ifp->gm_query_max_response_time_dsec); + + lmqt_msec = PIM_IGMP_LMQT_MSEC( + pim_ifp->gm_specific_query_max_response_time_dsec, + pim_ifp->gm_last_member_query_count); + + ohpi_msec = + PIM_IGMP_OHPI_DSEC( + igmp->querier_robustness_variable, + igmp->querier_query_interval, + pim_ifp->gm_query_max_response_time_dsec) * + 100; + + qri_msec = + pim_ifp->gm_query_max_response_time_dsec * 100; + lmqc = pim_ifp->gm_last_member_query_count; + + if (uj) { + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + json_object_string_add(json_row, "upTime", + uptime); + json_object_string_add(json_row, "querier", + igmp->t_igmp_query_timer + ? "local" + : "other"); + json_object_string_addf(json_row, "querierIp", + "%pI4", + &igmp->querier_addr); + json_object_int_add(json_row, "queryStartCount", + igmp->startup_query_count); + json_object_string_add(json_row, + "queryQueryTimer", + query_hhmmss); + json_object_string_add(json_row, + "queryOtherTimer", + other_hhmmss); + json_object_int_add(json_row, "version", + pim_ifp->igmp_version); + json_object_int_add( + json_row, + "timerGroupMembershipIntervalMsec", + gmi_msec); + json_object_int_add(json_row, + "lastMemberQueryCount", + lmqc); + json_object_int_add(json_row, + "timerLastMemberQueryMsec", + lmqt_msec); + json_object_int_add( + json_row, + "timerOlderHostPresentIntervalMsec", + ohpi_msec); + json_object_int_add( + json_row, + "timerOtherQuerierPresentIntervalMsec", + oqpi_msec); + json_object_int_add( + json_row, "timerQueryInterval", + igmp->querier_query_interval); + json_object_int_add( + json_row, + "timerQueryResponseIntervalMsec", + qri_msec); + json_object_int_add( + json_row, "timerRobustnessVariable", + igmp->querier_robustness_variable); + json_object_int_add(json_row, + "timerStartupQueryInterval", + sqi); + + json_object_object_add(json, ifp->name, + json_row); + + if (igmp->mtrace_only) { + json_object_boolean_true_add( + json_row, "mtraceOnly"); + } + } else { + vty_out(vty, "Interface : %s\n", ifp->name); + vty_out(vty, "State : %s\n", + if_is_up(ifp) ? (igmp->mtrace_only ? + "mtrace" + : "up") + : "down"); + vty_out(vty, "Address : %pI4\n", + &pim_ifp->primary_address); + vty_out(vty, "Uptime : %s\n", uptime); + vty_out(vty, "Version : %d\n", + pim_ifp->igmp_version); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + vty_out(vty, "Querier\n"); + vty_out(vty, "-------\n"); + vty_out(vty, "Querier : %s\n", + igmp->t_igmp_query_timer ? "local" + : "other"); + vty_out(vty, "QuerierIp : %pI4", + &igmp->querier_addr); + if (pim_ifp->primary_address.s_addr + == igmp->querier_addr.s_addr) + vty_out(vty, " (this router)\n"); + else + vty_out(vty, "\n"); + + vty_out(vty, "Start Count : %d\n", + igmp->startup_query_count); + vty_out(vty, "Query Timer : %s\n", + query_hhmmss); + vty_out(vty, "Other Timer : %s\n", + other_hhmmss); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + vty_out(vty, "Timers\n"); + vty_out(vty, "------\n"); + vty_out(vty, + "Group Membership Interval : %lis\n", + gmi_msec / 1000); + vty_out(vty, + "Last Member Query Count : %d\n", + lmqc); + vty_out(vty, + "Last Member Query Time : %lis\n", + lmqt_msec / 1000); + vty_out(vty, + "Older Host Present Interval : %lis\n", + ohpi_msec / 1000); + vty_out(vty, + "Other Querier Present Interval : %lis\n", + oqpi_msec / 1000); + vty_out(vty, + "Query Interval : %ds\n", + igmp->querier_query_interval); + vty_out(vty, + "Query Response Interval : %lis\n", + qri_msec / 1000); + vty_out(vty, + "Robustness Variable : %d\n", + igmp->querier_robustness_variable); + vty_out(vty, + "Startup Query Interval : %ds\n", + sqi); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + pim_print_ifp_flags(vty, ifp); + } + } + } + + if (uj) + vty_json(vty, json); + else if (!found_ifname) + vty_out(vty, "%% No such interface\n"); +} + +static void igmp_show_interface_join(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct interface *ifp; + time_t now; + json_object *json = NULL; + json_object *json_iface = NULL; + json_object *json_grp = NULL; + json_object *json_grp_arr = NULL; + + now = pim_time_monotonic_sec(); + + if (uj) { + json = json_object_new_object(); + json_object_string_add(json, "vrf", + vrf_id_to_name(pim->vrf->vrf_id)); + } else { + vty_out(vty, + "Interface Address Source Group Socket Uptime \n"); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; + struct listnode *join_node; + struct gm_join *ij; + struct in_addr pri_addr; + char pri_addr_str[INET_ADDRSTRLEN]; + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (!pim_ifp->gm_join_list) + continue; + + pri_addr = pim_find_primary_addr(ifp); + pim_inet4_dump("", pri_addr, pri_addr_str, + sizeof(pri_addr_str)); + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_join_list, join_node, + ij)) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + char uptime[10]; + + pim_time_uptime(uptime, sizeof(uptime), + now - ij->sock_creation); + pim_inet4_dump("", ij->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", ij->source_addr, source_str, + sizeof(source_str)); + + if (uj) { + json_object_object_get_ex(json, ifp->name, + &json_iface); + + if (!json_iface) { + json_iface = json_object_new_object(); + json_object_string_add( + json_iface, "name", ifp->name); + json_object_object_add(json, ifp->name, + json_iface); + json_grp_arr = json_object_new_array(); + json_object_object_add(json_iface, + "groups", + json_grp_arr); + } + + json_grp = json_object_new_object(); + json_object_string_add(json_grp, "source", + source_str); + json_object_string_add(json_grp, "group", + group_str); + json_object_string_add(json_grp, "primaryAddr", + pri_addr_str); + json_object_int_add(json_grp, "sockFd", + ij->sock_fd); + json_object_string_add(json_grp, "upTime", + uptime); + json_object_array_add(json_grp_arr, json_grp); + } else { + vty_out(vty, + "%-16s %-15s %-15s %-15s %6d %8s\n", + ifp->name, pri_addr_str, source_str, + group_str, ij->sock_fd, uptime); + } + } /* for (pim_ifp->gm_join_list) */ + + } /* for (iflist) */ + + if (uj) + vty_json(vty, json); +} + +static void igmp_show_statistics(struct pim_instance *pim, struct vty *vty, + const char *ifname, bool uj) +{ + struct interface *ifp; + struct igmp_stats igmp_stats; + bool found_ifname = false; + json_object *json = NULL; + + igmp_stats_init(&igmp_stats); + + if (uj) + json = json_object_new_object(); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; + struct listnode *sock_node, *source_node, *group_node; + struct gm_sock *igmp; + struct gm_group *group; + struct gm_source *src; + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (ifname && strcmp(ifname, ifp->name)) + continue; + + found_ifname = true; + + igmp_stats.joins_failed += pim_ifp->igmp_ifstat_joins_failed; + igmp_stats.joins_sent += pim_ifp->igmp_ifstat_joins_sent; + igmp_stats.total_groups += + pim_ifp->gm_group_list + ? listcount(pim_ifp->gm_group_list) + : 0; + igmp_stats.peak_groups += pim_ifp->igmp_peak_group_count; + + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, group_node, + group)) { + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, + source_node, src)) { + if (pim_addr_is_any(src->source_addr)) + continue; + + igmp_stats.total_source_groups++; + } + } + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, + igmp)) { + igmp_stats_add(&igmp_stats, &igmp->igmp_stats); + } + } + + if (!found_ifname) { + if (uj) + vty_json(vty, json); + else + vty_out(vty, "%% No such interface\n"); + return; + } + + if (uj) { + json_object *json_row = json_object_new_object(); + + json_object_string_add(json_row, "name", + ifname ? ifname : "global"); + json_object_int_add(json_row, "queryV1", igmp_stats.query_v1); + json_object_int_add(json_row, "queryV2", igmp_stats.query_v2); + json_object_int_add(json_row, "queryV3", igmp_stats.query_v3); + json_object_int_add(json_row, "leaveV2", igmp_stats.leave_v2); + json_object_int_add(json_row, "reportV1", igmp_stats.report_v1); + json_object_int_add(json_row, "reportV2", igmp_stats.report_v2); + json_object_int_add(json_row, "reportV3", igmp_stats.report_v3); + json_object_int_add(json_row, "mtraceResponse", + igmp_stats.mtrace_rsp); + json_object_int_add(json_row, "mtraceRequest", + igmp_stats.mtrace_req); + json_object_int_add(json_row, "unsupported", + igmp_stats.unsupported); + json_object_int_add(json_row, "totalReceivedMessages", + igmp_stats.total_recv_messages); + json_object_int_add(json_row, "peakGroups", + igmp_stats.peak_groups); + json_object_int_add(json_row, "totalGroups", + igmp_stats.total_groups); + json_object_int_add(json_row, "totalSourceGroups", + igmp_stats.total_source_groups); + json_object_int_add(json_row, "joinsFailed", + igmp_stats.joins_failed); + json_object_int_add(json_row, "joinsSent", + igmp_stats.joins_sent); + json_object_int_add(json_row, "generalQueriesSent", + igmp_stats.general_queries_sent); + json_object_int_add(json_row, "groupQueriesSent", + igmp_stats.group_queries_sent); + json_object_object_add(json, ifname ? ifname : "global", + json_row); + vty_json(vty, json); + } else { + vty_out(vty, "IGMP statistics\n"); + vty_out(vty, "Interface : %s\n", + ifname ? ifname : "global"); + vty_out(vty, "V1 query : %u\n", + igmp_stats.query_v1); + vty_out(vty, "V2 query : %u\n", + igmp_stats.query_v2); + vty_out(vty, "V3 query : %u\n", + igmp_stats.query_v3); + vty_out(vty, "V2 leave : %u\n", + igmp_stats.leave_v2); + vty_out(vty, "V1 report : %u\n", + igmp_stats.report_v1); + vty_out(vty, "V2 report : %u\n", + igmp_stats.report_v2); + vty_out(vty, "V3 report : %u\n", + igmp_stats.report_v3); + vty_out(vty, "mtrace response : %u\n", + igmp_stats.mtrace_rsp); + vty_out(vty, "mtrace request : %u\n", + igmp_stats.mtrace_req); + vty_out(vty, "unsupported : %u\n", + igmp_stats.unsupported); + vty_out(vty, "total received messages : %u\n", + igmp_stats.total_recv_messages); + vty_out(vty, "joins failed : %u\n", + igmp_stats.joins_failed); + vty_out(vty, "joins sent : %u\n", + igmp_stats.joins_sent); + vty_out(vty, "general queries sent : %u\n", + igmp_stats.general_queries_sent); + vty_out(vty, "group queries sent : %u\n", + igmp_stats.group_queries_sent); + vty_out(vty, "peak groups : %u\n", + igmp_stats.peak_groups); + vty_out(vty, "total groups : %u\n", + igmp_stats.total_groups); + vty_out(vty, "total source groups : %u\n", + igmp_stats.total_source_groups); + } +} + +static void igmp_source_json_helper(struct gm_source *src, + json_object *json_sources, char *source_str, + char *mmss, char *uptime) +{ + json_object *json_source = NULL; + + json_source = json_object_new_object(); + if (!json_source) + return; + + json_object_string_add(json_source, "source", source_str); + json_object_string_add(json_source, "timer", mmss); + json_object_boolean_add(json_source, "forwarded", + IGMP_SOURCE_TEST_FORWARDING(src->source_flags)); + json_object_string_add(json_source, "uptime", uptime); + json_object_array_add(json_sources, json_source); +} + +static void igmp_group_print(struct interface *ifp, struct vty *vty, bool uj, + json_object *json, struct gm_group *grp, + time_t now, bool detail) +{ + json_object *json_iface = NULL; + json_object *json_group = NULL; + json_object *json_groups = NULL; + char group_str[INET_ADDRSTRLEN]; + char hhmmss[PIM_TIME_STRLEN]; + char uptime[PIM_TIME_STRLEN]; + + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + pim_time_timer_to_hhmmss(hhmmss, sizeof(hhmmss), grp->t_group_timer); + pim_time_uptime(uptime, sizeof(uptime), now - grp->group_creation); + + if (uj) { + json_object_object_get_ex(json, ifp->name, &json_iface); + if (!json_iface) { + json_iface = json_object_new_object(); + if (!json_iface) + return; + json_object_pim_ifp_add(json_iface, ifp); + json_object_object_add(json, ifp->name, json_iface); + json_groups = json_object_new_array(); + if (!json_groups) + return; + json_object_object_add(json_iface, "groups", + json_groups); + } + + json_object_object_get_ex(json_iface, "groups", &json_groups); + if (json_groups) { + json_group = json_object_new_object(); + if (!json_group) + return; + + json_object_string_add(json_group, "group", group_str); + if (grp->igmp_version == IGMP_DEFAULT_VERSION) + json_object_string_add( + json_group, "mode", + grp->group_filtermode_isexcl + ? "EXCLUDE" + : "INCLUDE"); + + json_object_string_add(json_group, "timer", hhmmss); + json_object_int_add( + json_group, "sourcesCount", + grp->group_source_list + ? listcount(grp->group_source_list) + : 0); + json_object_int_add(json_group, "version", + grp->igmp_version); + json_object_string_add(json_group, "uptime", uptime); + json_object_array_add(json_groups, json_group); + + if (detail) { + struct listnode *srcnode; + struct gm_source *src; + json_object *json_sources = NULL; + + json_sources = json_object_new_array(); + if (!json_sources) + return; + + json_object_object_add(json_group, "sources", + json_sources); + + for (ALL_LIST_ELEMENTS_RO( + grp->group_source_list, srcnode, + src)) { + char source_str[INET_ADDRSTRLEN]; + char mmss[PIM_TIME_STRLEN]; + char src_uptime[PIM_TIME_STRLEN]; + + pim_inet4_dump( + "", src->source_addr, + source_str, sizeof(source_str)); + pim_time_timer_to_mmss( + mmss, sizeof(mmss), + src->t_source_timer); + pim_time_uptime( + src_uptime, sizeof(src_uptime), + now - src->source_creation); + + igmp_source_json_helper( + src, json_sources, source_str, + mmss, src_uptime); + } + } + } + } else { + if (detail) { + struct listnode *srcnode; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + srcnode, src)) { + char source_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", src->source_addr, + source_str, sizeof(source_str)); + + vty_out(vty, + "%-16s %-15s %4s %8s %-15s %d %8s\n", + ifp->name, group_str, + grp->igmp_version == 3 + ? (grp->group_filtermode_isexcl + ? "EXCL" + : "INCL") + : "----", + hhmmss, source_str, grp->igmp_version, + uptime); + } + return; + } + + vty_out(vty, "%-16s %-15s %4s %8s %4d %d %8s\n", ifp->name, + group_str, + grp->igmp_version == 3 + ? (grp->group_filtermode_isexcl ? "EXCL" + : "INCL") + : "----", + hhmmss, + grp->group_source_list + ? listcount(grp->group_source_list) + : 0, + grp->igmp_version, uptime); + } +} + +static void igmp_show_groups_interface_single(struct pim_instance *pim, + struct vty *vty, bool uj, + const char *ifname, + const char *grp_str, bool detail) +{ + struct interface *ifp; + time_t now; + json_object *json = NULL; + struct pim_interface *pim_ifp = NULL; + struct gm_group *grp; + + now = pim_time_monotonic_sec(); + + if (uj) { + json = json_object_new_object(); + if (!json) + return; + json_object_int_add(json, "totalGroups", pim->gm_group_count); + json_object_int_add(json, "watermarkLimit", + pim->gm_watermark_limit); + } else { + vty_out(vty, "Total IGMP groups: %u\n", pim->gm_group_count); + vty_out(vty, "Watermark warn limit(%s): %u\n", + pim->gm_watermark_limit ? "Set" : "Not Set", + pim->gm_watermark_limit); + + if (!detail) + vty_out(vty, + "Interface Group Mode Timer Srcs V Uptime\n"); + else + vty_out(vty, + "Interface Group Mode Timer Source V Uptime\n"); + } + + ifp = if_lookup_by_name(ifname, pim->vrf->vrf_id); + if (!ifp) { + if (uj) + vty_json(vty, json); + return; + } + + pim_ifp = ifp->info; + if (!pim_ifp) { + if (uj) + vty_json(vty, json); + return; + } + + if (grp_str) { + struct in_addr group_addr; + struct gm_sock *igmp; + + if (inet_pton(AF_INET, grp_str, &group_addr) == 1) { + igmp = pim_igmp_sock_lookup_ifaddr( + pim_ifp->gm_socket_list, + pim_ifp->primary_address); + if (igmp) { + grp = find_group_by_addr(igmp, group_addr); + if (grp) + igmp_group_print(ifp, vty, uj, json, + grp, now, detail); + } + } + } else { + struct listnode *grpnode; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, grp)) + igmp_group_print(ifp, vty, uj, json, grp, now, detail); + } + + if (uj) { + if (detail) + vty_json_no_pretty(vty, json); + else + vty_json(vty, json); + } +} + +static void igmp_show_groups(struct pim_instance *pim, struct vty *vty, bool uj, + const char *grp_str, bool detail) +{ + struct interface *ifp; + time_t now; + json_object *json = NULL; + + now = pim_time_monotonic_sec(); + + if (uj) { + json = json_object_new_object(); + if (!json) + return; + json_object_int_add(json, "totalGroups", pim->gm_group_count); + json_object_int_add(json, "watermarkLimit", + pim->gm_watermark_limit); + } else { + vty_out(vty, "Total IGMP groups: %u\n", pim->gm_group_count); + vty_out(vty, "Watermark warn limit(%s): %u\n", + pim->gm_watermark_limit ? "Set" : "Not Set", + pim->gm_watermark_limit); + if (!detail) + vty_out(vty, + "Interface Group Mode Timer Srcs V Uptime\n"); + else + vty_out(vty, + "Interface Group Mode Timer Source V Uptime\n"); + } + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode; + struct gm_group *grp; + + if (!pim_ifp) + continue; + + if (grp_str) { + struct in_addr group_addr; + struct gm_sock *igmp; + + if (inet_pton(AF_INET, grp_str, &group_addr) == 1) { + igmp = pim_igmp_sock_lookup_ifaddr( + pim_ifp->gm_socket_list, + pim_ifp->primary_address); + if (igmp) { + grp = find_group_by_addr(igmp, + group_addr); + if (grp) + igmp_group_print(ifp, vty, uj, + json, grp, now, + detail); + } + } + } else { + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, + grpnode, grp)) + igmp_group_print(ifp, vty, uj, json, grp, now, + detail); + } + } /* scan interfaces */ + + if (uj) { + if (detail) + vty_json_no_pretty(vty, json); + else + vty_json(vty, json); + } +} + +static void igmp_show_group_retransmission(struct pim_instance *pim, + struct vty *vty) +{ + struct interface *ifp; + + vty_out(vty, + "Interface Group RetTimer Counter RetSrcs\n"); + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode; + struct gm_group *grp; + + if (!pim_ifp) + continue; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + grp)) { + char group_str[INET_ADDRSTRLEN]; + char grp_retr_mmss[10]; + struct listnode *src_node; + struct gm_source *src; + int grp_retr_sources = 0; + + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + pim_time_timer_to_mmss( + grp_retr_mmss, sizeof(grp_retr_mmss), + grp->t_group_query_retransmit_timer); + + + /* count group sources with retransmission state + */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + src_node, src)) { + if (src->source_query_retransmit_count > 0) { + ++grp_retr_sources; + } + } + + vty_out(vty, "%-16s %-15s %-8s %7d %7d\n", ifp->name, + group_str, grp_retr_mmss, + grp->group_specific_query_retransmit_count, + grp_retr_sources); + + } /* scan igmp groups */ + } /* scan interfaces */ +} + +static void igmp_sources_print(struct interface *ifp, char *group_str, + struct gm_source *src, time_t now, + json_object *json, struct vty *vty, bool uj) +{ + json_object *json_iface = NULL; + json_object *json_group = NULL; + json_object *json_sources = NULL; + char source_str[INET_ADDRSTRLEN]; + char mmss[PIM_TIME_STRLEN]; + char uptime[PIM_TIME_STRLEN]; + + pim_inet4_dump("", src->source_addr, source_str, + sizeof(source_str)); + pim_time_timer_to_mmss(mmss, sizeof(mmss), src->t_source_timer); + pim_time_uptime(uptime, sizeof(uptime), now - src->source_creation); + + if (uj) { + json_object_object_get_ex(json, ifp->name, &json_iface); + if (!json_iface) { + json_iface = json_object_new_object(); + if (!json_iface) + return; + json_object_string_add(json_iface, "name", ifp->name); + json_object_object_add(json, ifp->name, json_iface); + } + + json_object_object_get_ex(json_iface, group_str, &json_group); + if (!json_group) { + json_group = json_object_new_object(); + if (!json_group) + return; + json_object_string_add(json_group, "group", group_str); + json_object_object_add(json_iface, group_str, + json_group); + json_sources = json_object_new_array(); + if (!json_sources) + return; + json_object_object_add(json_group, "sources", + json_sources); + } + + json_object_object_get_ex(json_group, "sources", &json_sources); + if (json_sources) + igmp_source_json_helper(src, json_sources, source_str, + mmss, uptime); + } else { + vty_out(vty, "%-16s %-15s %-15s %5s %3s %8s\n", ifp->name, + group_str, source_str, mmss, + IGMP_SOURCE_TEST_FORWARDING(src->source_flags) ? "Y" + : "N", + uptime); + } +} + +static void igmp_show_sources_interface_single(struct pim_instance *pim, + struct vty *vty, bool uj, + const char *ifname, + const char *grp_str) +{ + struct interface *ifp; + time_t now; + json_object *json = NULL; + struct pim_interface *pim_ifp; + struct gm_group *grp; + + now = pim_time_monotonic_sec(); + + if (uj) { + json = json_object_new_object(); + if (!json) + return; + } else { + vty_out(vty, + "Interface Group Source Timer Fwd Uptime \n"); + } + + ifp = if_lookup_by_name(ifname, pim->vrf->vrf_id); + if (!ifp) { + if (uj) + vty_json(vty, json); + return; + } + + pim_ifp = ifp->info; + if (!pim_ifp) { + if (uj) + vty_json(vty, json); + return; + } + + if (grp_str) { + struct in_addr group_addr; + struct gm_sock *igmp; + struct listnode *srcnode; + struct gm_source *src; + char group_str[INET_ADDRSTRLEN]; + int res; + + res = inet_pton(AF_INET, grp_str, &group_addr); + if (res <= 0) { + if (uj) + vty_json(vty, json); + return; + } + + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, + pim_ifp->primary_address); + if (!igmp) { + if (uj) + vty_json(vty, json); + return; + } + + grp = find_group_by_addr(igmp, group_addr); + if (!grp) { + if (uj) + vty_json(vty, json); + return; + } + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) + igmp_sources_print(ifp, group_str, src, now, json, vty, + uj); + } else { + struct listnode *grpnode; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + grp)) { + char group_str[INET_ADDRSTRLEN]; + struct listnode *srcnode; + struct gm_source *src; + + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + srcnode, src)) + igmp_sources_print(ifp, group_str, src, now, + json, vty, uj); + + } /* scan igmp groups */ + } + + if (uj) + vty_json(vty, json); +} + +static void igmp_show_sources(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct interface *ifp; + time_t now; + json_object *json = NULL; + + now = pim_time_monotonic_sec(); + + if (uj) { + json = json_object_new_object(); + if (!json) + return; + } else { + vty_out(vty, + "Interface Group Source Timer Fwd Uptime\n"); + } + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode; + struct gm_group *grp; + + if (!pim_ifp) + continue; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + grp)) { + char group_str[INET_ADDRSTRLEN]; + struct listnode *srcnode; + struct gm_source *src; + + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + srcnode, src)) + igmp_sources_print(ifp, group_str, src, now, + json, vty, uj); + } /* scan igmp groups */ + } /* scan interfaces */ + + if (uj) + vty_json(vty, json); +} + +static void igmp_show_source_retransmission(struct pim_instance *pim, + struct vty *vty) +{ + struct interface *ifp; + + vty_out(vty, + "Interface Group Source Counter\n"); + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode; + struct gm_group *grp; + + if (!pim_ifp) + continue; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + grp)) { + char group_str[INET_ADDRSTRLEN]; + struct listnode *srcnode; + struct gm_source *src; + + pim_inet4_dump("", grp->group_addr, group_str, + sizeof(group_str)); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + srcnode, src)) { + char source_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", src->source_addr, + source_str, sizeof(source_str)); + + vty_out(vty, "%-16s %-15s %-15s %7d\n", + ifp->name, group_str, source_str, + src->source_query_retransmit_count); + + } /* scan group sources */ + } /* scan igmp groups */ + } /* scan interfaces */ +} + +static void clear_igmp_interfaces(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) + pim_if_addr_del_all_igmp(ifp); + + FOR_ALL_INTERFACES (pim->vrf, ifp) + pim_if_addr_add_all(ifp); +} + +static void clear_interfaces(struct pim_instance *pim) +{ + clear_igmp_interfaces(pim); + clear_pim_interfaces(pim); +} + +#define PIM_GET_PIM_INTERFACE(pim_ifp, ifp) \ + pim_ifp = ifp->info; \ + if (!pim_ifp) { \ + vty_out(vty, \ + "%% Enable PIM and/or IGMP on this interface first\n"); \ + return CMD_WARNING_CONFIG_FAILED; \ + } + +/** + * Compatibility function to keep the legacy mesh group CLI behavior: + * Delete group when there are no more configurations in it. + * + * NOTE: + * Don't forget to call `nb_cli_apply_changes` after this. + */ +static void pim_cli_legacy_mesh_group_behavior(struct vty *vty, + const char *gname) +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + char xpath_member_value[XPATH_MAXLEN]; + const struct lyd_node *member_dnode; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return; + + /* Get mesh group base XPath. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + /* Group must exists, otherwise just quit. */ + if (!yang_dnode_exists(vty->candidate_config->dnode, xpath_value)) + return; + + /* Group members check: */ + strlcpy(xpath_member_value, xpath_value, sizeof(xpath_member_value)); + strlcat(xpath_member_value, "/members", sizeof(xpath_member_value)); + if (yang_dnode_exists(vty->candidate_config->dnode, + xpath_member_value)) { + member_dnode = yang_dnode_get(vty->candidate_config->dnode, + xpath_member_value); + if (!member_dnode || !yang_is_last_list_dnode(member_dnode)) + return; + } + + /* Source address check: */ + strlcpy(xpath_member_value, xpath_value, sizeof(xpath_member_value)); + strlcat(xpath_member_value, "/source", sizeof(xpath_member_value)); + if (yang_dnode_exists(vty->candidate_config->dnode, xpath_member_value)) + return; + + /* No configurations found: delete it. */ + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, NULL); +} + +DEFUN (clear_ip_interfaces, + clear_ip_interfaces_cmd, + "clear ip interfaces [vrf NAME]", + CLEAR_STR + IP_STR + "Reset interfaces\n" + VRF_CMD_HELP_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + clear_interfaces(vrf->info); + + return CMD_SUCCESS; +} + +DEFUN (clear_ip_igmp_interfaces, + clear_ip_igmp_interfaces_cmd, + "clear ip igmp [vrf NAME] interfaces", + CLEAR_STR + IP_STR + CLEAR_IP_IGMP_STR + VRF_CMD_HELP_STR + "Reset IGMP interfaces\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + clear_igmp_interfaces(vrf->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ip_pim_statistics, + clear_ip_pim_statistics_cmd, + "clear ip pim statistics [vrf NAME]$name", + CLEAR_STR + IP_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM statistics\n") +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + clear_pim_statistics(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ip_mroute, + clear_ip_mroute_cmd, + "clear ip mroute [vrf NAME]$name", + CLEAR_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR) +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + clear_mroute(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ip_pim_interfaces, + clear_ip_pim_interfaces_cmd, + "clear ip pim [vrf NAME] interfaces", + CLEAR_STR + IP_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM interfaces\n") +{ + struct vrf *v = pim_cmd_lookup(vty, vrf); + + if (!v) + return CMD_WARNING; + + clear_pim_interfaces(v->info); + + return CMD_SUCCESS; +} + +DEFPY (clear_ip_pim_interface_traffic, + clear_ip_pim_interface_traffic_cmd, + "clear ip pim [vrf NAME] interface traffic", + CLEAR_STR + IP_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset PIM interfaces\n" + "Reset Protocol Packet counters\n") +{ + return clear_pim_interface_traffic(vrf, vty); +} + +DEFPY (clear_ip_pim_oil, + clear_ip_pim_oil_cmd, + "clear ip pim [vrf NAME]$name oil", + CLEAR_STR + IP_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Rescan PIM OIL (output interface list)\n") +{ + struct vrf *v = pim_cmd_lookup(vty, name); + + if (!v) + return CMD_WARNING; + + pim_scan_oil(v->info); + + return CMD_SUCCESS; +} + +DEFUN (clear_ip_pim_bsr_db, + clear_ip_pim_bsr_db_cmd, + "clear ip pim [vrf NAME] bsr-data", + CLEAR_STR + IP_STR + CLEAR_IP_PIM_STR + VRF_CMD_HELP_STR + "Reset pim bsr data\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + pim_bsm_clear(vrf->info); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_interface, + show_ip_igmp_interface_cmd, + "show ip igmp [vrf NAME] interface [detail|WORD] [json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + "IGMP interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + if (argv_find(argv, argc, "detail", &idx) + || argv_find(argv, argc, "WORD", &idx)) + igmp_show_interfaces_single(vrf->info, vty, argv[idx]->arg, uj); + else + igmp_show_interfaces(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_interface_vrf_all, + show_ip_igmp_interface_vrf_all_cmd, + "show ip igmp vrf all interface [detail|WORD] [json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + "IGMP interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf; + bool first = true; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + if (argv_find(argv, argc, "detail", &idx) + || argv_find(argv, argc, "WORD", &idx)) + igmp_show_interfaces_single(vrf->info, vty, + argv[idx]->arg, uj); + else + igmp_show_interfaces(vrf->info, vty, uj); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_join, + show_ip_igmp_join_cmd, + "show ip igmp [vrf NAME] join [json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + "IGMP static join information\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + igmp_show_interface_join(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_join_vrf_all, + show_ip_igmp_join_vrf_all_cmd, + "show ip igmp vrf all join [json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + "IGMP static join information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + bool first = true; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + igmp_show_interface_join(vrf->info, vty, uj); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_igmp_groups, + show_ip_igmp_groups_cmd, + "show ip igmp [vrf NAME$vrf_name] groups [INTERFACE$ifname [GROUP$grp_str]] [detail$detail] [json$json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + IGMP_GROUP_STR + "Interface name\n" + "Group address\n" + "Detailed Information\n" + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, !!json); + + if (!vrf) + return CMD_WARNING; + + if (ifname) + igmp_show_groups_interface_single(vrf->info, vty, !!json, + ifname, grp_str, !!detail); + else + igmp_show_groups(vrf->info, vty, !!json, NULL, !!detail); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_igmp_groups_vrf_all, + show_ip_igmp_groups_vrf_all_cmd, + "show ip igmp vrf all groups [GROUP$grp_str] [detail$detail] [json$json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + IGMP_GROUP_STR + "Group address\n" + "Detailed Information\n" + JSON_STR) +{ + bool uj = !!json; + struct vrf *vrf; + bool first = true; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + igmp_show_groups(vrf->info, vty, uj, grp_str, !!detail); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_groups_retransmissions, + show_ip_igmp_groups_retransmissions_cmd, + "show ip igmp [vrf NAME] groups retransmissions", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + IGMP_GROUP_STR + "IGMP group retransmissions\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + igmp_show_group_retransmission(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFPY(show_ip_igmp_sources, + show_ip_igmp_sources_cmd, + "show ip igmp [vrf NAME$vrf_name] sources [INTERFACE$ifname [GROUP$grp_str]] [json$json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + IGMP_SOURCE_STR + "Interface name\n" + "Group address\n" + JSON_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, !!json); + + if (!vrf) + return CMD_WARNING; + + if (ifname) + igmp_show_sources_interface_single(vrf->info, vty, !!json, + ifname, grp_str); + else + igmp_show_sources(vrf->info, vty, !!json); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_sources_retransmissions, + show_ip_igmp_sources_retransmissions_cmd, + "show ip igmp [vrf NAME] sources retransmissions", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + IGMP_SOURCE_STR + "IGMP source retransmissions\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + igmp_show_source_retransmission(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_igmp_statistics, + show_ip_igmp_statistics_cmd, + "show ip igmp [vrf NAME] statistics [interface WORD] [json]", + SHOW_STR + IP_STR + IGMP_STR + VRF_CMD_HELP_STR + "IGMP statistics\n" + "interface\n" + "IGMP interface\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + if (argv_find(argv, argc, "WORD", &idx)) + igmp_show_statistics(vrf->info, vty, argv[idx]->arg, uj); + else + igmp_show_statistics(vrf->info, vty, NULL, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_pim_mlag_summary, + show_ip_pim_mlag_summary_cmd, + "show ip pim mlag summary [json]", + SHOW_STR + IP_STR + PIM_STR + "MLAG\n" + "status and stats\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + char role_buf[MLAG_ROLE_STRSIZE]; + char addr_buf[INET_ADDRSTRLEN]; + + if (uj) { + json_object *json = NULL; + json_object *json_stat = NULL; + + json = json_object_new_object(); + json_object_boolean_add(json, "mlagConnUp", + CHECK_FLAG(router->mlag_flags, + PIM_MLAGF_LOCAL_CONN_UP)); + json_object_boolean_add(json, "mlagPeerConnUp", + CHECK_FLAG(router->mlag_flags, + PIM_MLAGF_PEER_CONN_UP)); + json_object_boolean_add(json, "mlagPeerZebraUp", + CHECK_FLAG(router->mlag_flags, + PIM_MLAGF_PEER_ZEBRA_UP)); + json_object_string_add(json, "mlagRole", + mlag_role2str(router->mlag_role, + role_buf, sizeof(role_buf))); + inet_ntop(AF_INET, &router->local_vtep_ip, + addr_buf, INET_ADDRSTRLEN); + json_object_string_add(json, "localVtepIp", addr_buf); + inet_ntop(AF_INET, &router->anycast_vtep_ip, + addr_buf, INET_ADDRSTRLEN); + json_object_string_add(json, "anycastVtepIp", addr_buf); + json_object_string_add(json, "peerlinkRif", + router->peerlink_rif); + + json_stat = json_object_new_object(); + json_object_int_add(json_stat, "mlagConnFlaps", + router->mlag_stats.mlagd_session_downs); + json_object_int_add(json_stat, "mlagPeerConnFlaps", + router->mlag_stats.peer_session_downs); + json_object_int_add(json_stat, "mlagPeerZebraFlaps", + router->mlag_stats.peer_zebra_downs); + json_object_int_add(json_stat, "mrouteAddRx", + router->mlag_stats.msg.mroute_add_rx); + json_object_int_add(json_stat, "mrouteAddTx", + router->mlag_stats.msg.mroute_add_tx); + json_object_int_add(json_stat, "mrouteDelRx", + router->mlag_stats.msg.mroute_del_rx); + json_object_int_add(json_stat, "mrouteDelTx", + router->mlag_stats.msg.mroute_del_tx); + json_object_int_add(json_stat, "mlagStatusUpdates", + router->mlag_stats.msg.mlag_status_updates); + json_object_int_add(json_stat, "peerZebraStatusUpdates", + router->mlag_stats.msg.peer_zebra_status_updates); + json_object_int_add(json_stat, "pimStatusUpdates", + router->mlag_stats.msg.pim_status_updates); + json_object_int_add(json_stat, "vxlanUpdates", + router->mlag_stats.msg.vxlan_updates); + json_object_object_add(json, "connStats", json_stat); + + vty_json(vty, json); + return CMD_SUCCESS; + } + + vty_out(vty, "MLAG daemon connection: %s\n", + (router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP) + ? "up" : "down"); + vty_out(vty, "MLAG peer state: %s\n", + (router->mlag_flags & PIM_MLAGF_PEER_CONN_UP) + ? "up" : "down"); + vty_out(vty, "Zebra peer state: %s\n", + (router->mlag_flags & PIM_MLAGF_PEER_ZEBRA_UP) + ? "up" : "down"); + vty_out(vty, "MLAG role: %s\n", + mlag_role2str(router->mlag_role, role_buf, sizeof(role_buf))); + inet_ntop(AF_INET, &router->local_vtep_ip, + addr_buf, INET_ADDRSTRLEN); + vty_out(vty, "Local VTEP IP: %s\n", addr_buf); + inet_ntop(AF_INET, &router->anycast_vtep_ip, + addr_buf, INET_ADDRSTRLEN); + vty_out(vty, "Anycast VTEP IP: %s\n", addr_buf); + vty_out(vty, "Peerlink: %s\n", router->peerlink_rif); + vty_out(vty, "Session flaps: mlagd: %d mlag-peer: %d zebra-peer: %d\n", + router->mlag_stats.mlagd_session_downs, + router->mlag_stats.peer_session_downs, + router->mlag_stats.peer_zebra_downs); + vty_out(vty, "Message Statistics:\n"); + vty_out(vty, " mroute adds: rx: %d, tx: %d\n", + router->mlag_stats.msg.mroute_add_rx, + router->mlag_stats.msg.mroute_add_tx); + vty_out(vty, " mroute dels: rx: %d, tx: %d\n", + router->mlag_stats.msg.mroute_del_rx, + router->mlag_stats.msg.mroute_del_tx); + vty_out(vty, " peer zebra status updates: %d\n", + router->mlag_stats.msg.peer_zebra_status_updates); + vty_out(vty, " PIM status updates: %d\n", + router->mlag_stats.msg.pim_status_updates); + vty_out(vty, " VxLAN updates: %d\n", + router->mlag_stats.msg.vxlan_updates); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_pim_assert, + show_ip_pim_assert_cmd, + "show ip pim [vrf NAME] assert", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface assert\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + pim_show_assert(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_pim_assert_internal, + show_ip_pim_assert_internal_cmd, + "show ip pim [vrf NAME] assert-internal", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface internal assert state\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + pim_show_assert_internal(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_pim_assert_metric, + show_ip_pim_assert_metric_cmd, + "show ip pim [vrf NAME] assert-metric", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface assert metric\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + pim_show_assert_metric(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_pim_assert_winner_metric, + show_ip_pim_assert_winner_metric_cmd, + "show ip pim [vrf NAME] assert-winner-metric", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface assert winner metric\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + pim_show_assert_winner_metric(vrf->info, vty); + + return CMD_SUCCESS; +} + +DEFPY (show_ip_pim_interface, + show_ip_pim_interface_cmd, + "show ip pim [mlag$mlag] [vrf NAME] interface [detail|WORD]$interface [json$json]", + SHOW_STR + IP_STR + PIM_STR + "MLAG\n" + VRF_CMD_HELP_STR + "PIM interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + return pim_show_interface_cmd_helper(vrf, vty, !!json, !!mlag, + interface); +} + +DEFPY (show_ip_pim_interface_vrf_all, + show_ip_pim_interface_vrf_all_cmd, + "show ip pim [mlag$mlag] vrf all interface [detail|WORD]$interface [json$json]", + SHOW_STR + IP_STR + PIM_STR + "MLAG\n" + VRF_CMD_HELP_STR + "PIM interface information\n" + "Detailed output\n" + "interface name\n" + JSON_STR) +{ + return pim_show_interface_vrf_all_cmd_helper(vty, !!json, !!mlag, + interface); +} + +DEFPY (show_ip_pim_join, + show_ip_pim_join_cmd, + "show ip pim [vrf NAME] join [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface join information\n" + "The Source or Group\n" + "The Group\n" + JSON_STR) +{ + return pim_show_join_cmd_helper(vrf, vty, s_or_g, g, json); +} + +DEFPY (show_ip_pim_join_vrf_all, + show_ip_pim_join_vrf_all_cmd, + "show ip pim vrf all join [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface join information\n" + JSON_STR) +{ + return pim_show_join_vrf_all_cmd_helper(vty, json); +} + +DEFPY (show_ip_pim_jp_agg, + show_ip_pim_jp_agg_cmd, + "show ip pim [vrf NAME] jp-agg", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "join prune aggregation list\n") +{ + return pim_show_jp_agg_list_cmd_helper(vrf, vty); +} + +DEFPY (show_ip_pim_local_membership, + show_ip_pim_local_membership_cmd, + "show ip pim [vrf NAME] local-membership [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface local-membership\n" + JSON_STR) +{ + return pim_show_membership_cmd_helper(vrf, vty, !!json); +} + +static void pim_show_mlag_up_entry_detail(struct vrf *vrf, + struct vty *vty, + struct pim_upstream *up, + char *src_str, char *grp_str, + json_object *json) +{ + if (json) { + json_object *json_row = NULL; + json_object *own_list = NULL; + json_object *json_group = NULL; + + + json_object_object_get_ex(json, grp_str, &json_group); + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + + own_list = json_object_new_array(); + if (pim_up_mlag_is_local(up)) + json_object_array_add(own_list, + json_object_new_string("local")); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_PEER)) + json_object_array_add(own_list, + json_object_new_string("peer")); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE)) + json_object_array_add( + own_list, json_object_new_string("Interface")); + json_object_object_add(json_row, "owners", own_list); + + json_object_int_add(json_row, "localCost", + pim_up_mlag_local_cost(up)); + json_object_int_add(json_row, "peerCost", + pim_up_mlag_peer_cost(up)); + if (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags)) + json_object_boolean_false_add(json_row, "df"); + else + json_object_boolean_true_add(json_row, "df"); + json_object_object_add(json_group, src_str, json_row); + } else { + char own_str[6]; + + own_str[0] = '\0'; + if (pim_up_mlag_is_local(up)) + strlcat(own_str, "L", sizeof(own_str)); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_PEER)) + strlcat(own_str, "P", sizeof(own_str)); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE)) + strlcat(own_str, "I", sizeof(own_str)); + /* XXX - fixup, print paragraph output */ + vty_out(vty, + "%-15s %-15s %-6s %-11u %-10d %2s\n", + src_str, grp_str, own_str, + pim_up_mlag_local_cost(up), + pim_up_mlag_peer_cost(up), + PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags) + ? "n" : "y"); + } +} + +static void pim_show_mlag_up_detail(struct vrf *vrf, + struct vty *vty, const char *src_or_group, + const char *group, bool uj) +{ + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + struct pim_upstream *up; + struct pim_instance *pim = vrf->info; + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + else + vty_out(vty, + "Source Group Owner Local-cost Peer-cost DF\n"); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_PEER) + && !(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE) + && !pim_up_mlag_is_local(up)) + continue; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &up->sg.src); + + /* XXX: strcmps are clearly inefficient. we should do uint comps + * here instead. + */ + if (group) { + if (strcmp(src_str, src_or_group) || + strcmp(grp_str, group)) + continue; + } else { + if (strcmp(src_str, src_or_group) && + strcmp(grp_str, src_or_group)) + continue; + } + pim_show_mlag_up_entry_detail(vrf, vty, up, + src_str, grp_str, json); + } + + if (uj) + vty_json(vty, json); +} + +static void pim_show_mlag_up_vrf(struct vrf *vrf, struct vty *vty, bool uj) +{ + json_object *json = NULL; + json_object *json_row; + struct pim_upstream *up; + struct pim_instance *pim = vrf->info; + json_object *json_group = NULL; + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, + "Source Group Owner Local-cost Peer-cost DF\n"); + } + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_PEER) + && !(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE) + && !pim_up_mlag_is_local(up)) + continue; + if (uj) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + json_object *own_list = NULL; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &up->sg.src); + + json_object_object_get_ex(json, grp_str, &json_group); + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "vrf", vrf->name); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + + own_list = json_object_new_array(); + if (pim_up_mlag_is_local(up)) { + + json_object_array_add(own_list, + json_object_new_string( + "local")); + } + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_PEER)) { + json_object_array_add(own_list, + json_object_new_string( + "peer")); + } + json_object_object_add(json_row, "owners", own_list); + + json_object_int_add(json_row, "localCost", + pim_up_mlag_local_cost(up)); + json_object_int_add(json_row, "peerCost", + pim_up_mlag_peer_cost(up)); + if (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags)) + json_object_boolean_false_add(json_row, "df"); + else + json_object_boolean_true_add(json_row, "df"); + json_object_object_add(json_group, src_str, json_row); + } else { + char own_str[6]; + + own_str[0] = '\0'; + if (pim_up_mlag_is_local(up)) + strlcat(own_str, "L", sizeof(own_str)); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_PEER)) + strlcat(own_str, "P", sizeof(own_str)); + if (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE)) + strlcat(own_str, "I", sizeof(own_str)); + vty_out(vty, + "%-15pPAs %-15pPAs %-6s %-11u %-10u %2s\n", + &up->sg.src, &up->sg.grp, own_str, + pim_up_mlag_local_cost(up), + pim_up_mlag_peer_cost(up), + PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags) + ? "n" : "y"); + } + } + if (uj) + vty_json(vty, json); +} + +static void pim_show_mlag_help_string(struct vty *vty, bool uj) +{ + if (!uj) { + vty_out(vty, "Owner codes:\n"); + vty_out(vty, + "L: EVPN-MLAG Entry, I:PIM-MLAG Entry, P: Peer Entry\n"); + } +} + + +DEFUN(show_ip_pim_mlag_up, show_ip_pim_mlag_up_cmd, + "show ip pim [vrf NAME] mlag upstream [A.B.C.D [A.B.C.D]] [json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "MLAG\n" + "upstream\n" + "Unicast or Multicast address\n" + "Multicast address\n" JSON_STR) +{ + const char *src_or_group = NULL; + const char *group = NULL; + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf || !vrf->info) { + vty_out(vty, "%s: VRF or Info missing\n", __func__); + return CMD_WARNING; + } + + if (uj) + argc--; + + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + src_or_group = argv[idx]->arg; + if (idx + 1 < argc) + group = argv[idx + 1]->arg; + } + + pim_show_mlag_help_string(vty, uj); + + if (src_or_group) + pim_show_mlag_up_detail(vrf, vty, src_or_group, group, uj); + else + pim_show_mlag_up_vrf(vrf, vty, uj); + + return CMD_SUCCESS; +} + + +DEFUN(show_ip_pim_mlag_up_vrf_all, show_ip_pim_mlag_up_vrf_all_cmd, + "show ip pim vrf all mlag upstream [json]", + SHOW_STR IP_STR PIM_STR VRF_CMD_HELP_STR + "MLAG\n" + "upstream\n" JSON_STR) +{ + struct vrf *vrf; + bool uj = use_json(argc, argv); + + pim_show_mlag_help_string(vty, uj); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + pim_show_mlag_up_vrf(vrf, vty, uj); + } + + return CMD_SUCCESS; +} + +DEFPY (show_ip_pim_neighbor, + show_ip_pim_neighbor_cmd, + "show ip pim [vrf NAME] neighbor [detail|WORD]$interface [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor information\n" + "Detailed output\n" + "Name of interface or neighbor\n" + JSON_STR) +{ + return pim_show_neighbors_cmd_helper(vrf, vty, json, interface); +} + +DEFPY (show_ip_pim_neighbor_vrf_all, + show_ip_pim_neighbor_vrf_all_cmd, + "show ip pim vrf all neighbor [detail|WORD]$interface [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor information\n" + "Detailed output\n" + "Name of interface or neighbor\n" + JSON_STR) +{ + return pim_show_neighbors_vrf_all_cmd_helper(vty, json, interface); +} + +DEFPY (show_ip_pim_secondary, + show_ip_pim_secondary_cmd, + "show ip pim [vrf NAME] secondary", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM neighbor addresses\n") +{ + return pim_show_secondary_helper(vrf, vty); +} + +DEFPY (show_ip_pim_state, + show_ip_pim_state_cmd, + "show ip pim [vrf NAME] state [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM state information\n" + "Unicast or Multicast address\n" + "Multicast address\n" + JSON_STR) +{ + return pim_show_state_helper(vrf, vty, s_or_g_str, g_str, !!json); +} + +DEFPY (show_ip_pim_state_vrf_all, + show_ip_pim_state_vrf_all_cmd, + "show ip pim vrf all state [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM state information\n" + "Unicast or Multicast address\n" + "Multicast address\n" + JSON_STR) +{ + return pim_show_state_vrf_all_helper(vty, s_or_g_str, g_str, !!json); +} + +DEFPY (show_ip_pim_upstream, + show_ip_pim_upstream_cmd, + "show ip pim [vrf NAME] upstream [A.B.C.D$s_or_g [A.B.C.D$g]] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream information\n" + "The Source or Group\n" + "The Group\n" + JSON_STR) +{ + return pim_show_upstream_helper(vrf, vty, s_or_g, g, !!json); +} + +DEFPY (show_ip_pim_upstream_vrf_all, + show_ip_pim_upstream_vrf_all_cmd, + "show ip pim vrf all upstream [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream information\n" + JSON_STR) +{ + return pim_show_upstream_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ip_pim_channel, + show_ip_pim_channel_cmd, + "show ip pim [vrf NAME] channel [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM downstream channel info\n" + JSON_STR) +{ + return pim_show_channel_cmd_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_upstream_join_desired, + show_ip_pim_upstream_join_desired_cmd, + "show ip pim [vrf NAME] upstream-join-desired [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream join-desired\n" + JSON_STR) +{ + return pim_show_upstream_join_desired_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_upstream_rpf, + show_ip_pim_upstream_rpf_cmd, + "show ip pim [vrf NAME] upstream-rpf [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM upstream source rpf\n" + JSON_STR) +{ + return pim_show_upstream_rpf_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_rp, + show_ip_pim_rp_cmd, + "show ip pim [vrf NAME] rp-info [A.B.C.D/M$group] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM RP information\n" + "Multicast Group range\n" + JSON_STR) +{ + return pim_show_rp_helper(vrf, vty, group_str, (struct prefix *)group, + !!json); +} + +DEFPY (show_ip_pim_rp_vrf_all, + show_ip_pim_rp_vrf_all_cmd, + "show ip pim vrf all rp-info [A.B.C.D/M$group] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM RP information\n" + "Multicast Group range\n" + JSON_STR) +{ + return pim_show_rp_vrf_all_helper(vty, group_str, + (struct prefix *)group, !!json); +} + +DEFPY (show_ip_pim_rpf, + show_ip_pim_rpf_cmd, + "show ip pim [vrf NAME] rpf [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached source rpf information\n" + JSON_STR) +{ + return pim_show_rpf_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_rpf_vrf_all, + show_ip_pim_rpf_vrf_all_cmd, + "show ip pim vrf all rpf [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached source rpf information\n" + JSON_STR) +{ + return pim_show_rpf_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ip_pim_nexthop, + show_ip_pim_nexthop_cmd, + "show ip pim [vrf NAME] nexthop [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached nexthop rpf information\n" + JSON_STR) +{ + return pim_show_nexthop_cmd_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_nexthop_lookup, + show_ip_pim_nexthop_lookup_cmd, + "show ip pim [vrf NAME] nexthop-lookup A.B.C.D$source A.B.C.D$group", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM cached nexthop rpf lookup\n" + "Source/RP address\n" + "Multicast Group address\n") +{ + return pim_show_nexthop_lookup_cmd_helper(vrf, vty, source, group); +} + +DEFPY (show_ip_pim_interface_traffic, + show_ip_pim_interface_traffic_cmd, + "show ip pim [vrf NAME] interface traffic [WORD$if_name] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM interface information\n" + "Protocol Packet counters\n" + "Interface name\n" + JSON_STR) +{ + return pim_show_interface_traffic_helper(vrf, if_name, vty, !!json); +} + +DEFPY (show_ip_pim_bsm_db, + show_ip_pim_bsm_db_cmd, + "show ip pim bsm-database [vrf NAME] [json$json]", + SHOW_STR + IP_STR + PIM_STR + "PIM cached bsm packets information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_bsm_db_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_bsrp, + show_ip_pim_bsrp_cmd, + "show ip pim bsrp-info [vrf NAME] [json$json]", + SHOW_STR + IP_STR + PIM_STR + "PIM cached group-rp mappings information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_group_rp_mappings_info_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_pim_statistics, + show_ip_pim_statistics_cmd, + "show ip pim [vrf NAME] statistics [interface WORD$word] [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM statistics\n" + INTERFACE_STR + "PIM interface\n" + JSON_STR) +{ + return pim_show_statistics_helper(vrf, vty, word, !!json); +} + +DEFPY (show_ip_multicast, + show_ip_multicast_cmd, + "show ip multicast [vrf NAME]", + SHOW_STR + IP_STR + "Multicast global information\n" + VRF_CMD_HELP_STR) +{ + return pim_show_multicast_helper(vrf, vty); +} + +DEFPY (show_ip_multicast_vrf_all, + show_ip_multicast_vrf_all_cmd, + "show ip multicast vrf all", + SHOW_STR + IP_STR + "Multicast global information\n" + VRF_CMD_HELP_STR) +{ + return pim_show_multicast_vrf_all_helper(vty); +} + +DEFPY (show_ip_multicast_count, + show_ip_multicast_count_cmd, + "show ip multicast count [vrf NAME] [json$json]", + SHOW_STR + IP_STR + "Multicast global information\n" + "Data packet count\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_multicast_count_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_multicast_count_vrf_all, + show_ip_multicast_count_vrf_all_cmd, + "show ip multicast count vrf all [json$json]", + SHOW_STR + IP_STR + "Multicast global information\n" + "Data packet count\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_multicast_count_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ip_mroute, + show_ip_mroute_cmd, + "show ip mroute [vrf NAME] [A.B.C.D$s_or_g [A.B.C.D$g]] [fill$fill] [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "The Source or Group\n" + "The Group\n" + "Fill in Assumed data\n" + JSON_STR) +{ + return pim_show_mroute_helper(vrf, vty, s_or_g, g, !!fill, !!json); +} + +DEFPY (show_ip_mroute_vrf_all, + show_ip_mroute_vrf_all_cmd, + "show ip mroute vrf all [fill$fill] [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Fill in Assumed data\n" + JSON_STR) +{ + return pim_show_mroute_vrf_all_helper(vty, !!fill, !!json); +} + +DEFPY (clear_ip_mroute_count, + clear_ip_mroute_count_cmd, + "clear ip mroute [vrf NAME]$name count", + CLEAR_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n") +{ + return clear_ip_mroute_count_command(vty, name); +} + +DEFPY (show_ip_mroute_count, + show_ip_mroute_count_cmd, + "show ip mroute [vrf NAME] count [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n" + JSON_STR) +{ + return pim_show_mroute_count_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_mroute_count_vrf_all, + show_ip_mroute_count_vrf_all_cmd, + "show ip mroute vrf all count [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Route and packet count data\n" + JSON_STR) +{ + return pim_show_mroute_count_vrf_all_helper(vty, !!json); +} + +DEFPY (show_ip_mroute_summary, + show_ip_mroute_summary_cmd, + "show ip mroute [vrf NAME] summary [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n" + JSON_STR) +{ + return pim_show_mroute_summary_helper(vrf, vty, !!json); +} + +DEFPY (show_ip_mroute_summary_vrf_all, + show_ip_mroute_summary_vrf_all_cmd, + "show ip mroute vrf all summary [json$json]", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n" + JSON_STR) +{ + return pim_show_mroute_summary_vrf_all_helper(vty, !!json); +} + +DEFUN (show_ip_rib, + show_ip_rib_cmd, + "show ip rib [vrf NAME] A.B.C.D", + SHOW_STR + IP_STR + RIB_STR + VRF_CMD_HELP_STR + "Unicast address\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + struct in_addr addr; + const char *addr_str; + struct pim_nexthop nexthop; + int result; + + if (!vrf) + return CMD_WARNING; + + memset(&nexthop, 0, sizeof(nexthop)); + argv_find(argv, argc, "A.B.C.D", &idx); + addr_str = argv[idx]->arg; + result = inet_pton(AF_INET, addr_str, &addr); + if (result <= 0) { + vty_out(vty, "Bad unicast address %s: errno=%d: %s\n", addr_str, + errno, safe_strerror(errno)); + return CMD_WARNING; + } + + if (!pim_nexthop_lookup(vrf->info, &nexthop, addr, 0)) { + vty_out(vty, + "Failure querying RIB nexthop for unicast address %s\n", + addr_str); + return CMD_WARNING; + } + + vty_out(vty, + "Address NextHop Interface Metric Preference\n"); + + vty_out(vty, "%-15s %-15pPAs %-9s %6d %10d\n", addr_str, + &nexthop.mrib_nexthop_addr, + nexthop.interface ? nexthop.interface->name : "", + nexthop.mrib_route_metric, nexthop.mrib_metric_preference); + + return CMD_SUCCESS; +} + +static void show_ssmpingd(struct pim_instance *pim, struct vty *vty) +{ + struct listnode *node; + struct ssmpingd_sock *ss; + time_t now; + + vty_out(vty, + "Source Socket Address Port Uptime Requests\n"); + + if (!pim->ssmpingd_list) + return; + + now = pim_time_monotonic_sec(); + + for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) { + char source_str[INET_ADDRSTRLEN]; + char ss_uptime[10]; + struct sockaddr_in bind_addr; + socklen_t len = sizeof(bind_addr); + char bind_addr_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", ss->source_addr, source_str, + sizeof(source_str)); + + if (pim_socket_getsockname( + ss->sock_fd, (struct sockaddr *)&bind_addr, &len)) { + vty_out(vty, + "%% Failure reading socket name for ssmpingd source %s on fd=%d\n", + source_str, ss->sock_fd); + } + + pim_inet4_dump("", bind_addr.sin_addr, bind_addr_str, + sizeof(bind_addr_str)); + pim_time_uptime(ss_uptime, sizeof(ss_uptime), + now - ss->creation); + + vty_out(vty, "%-15s %6d %-15s %5d %8s %8lld\n", source_str, + ss->sock_fd, bind_addr_str, ntohs(bind_addr.sin_port), + ss_uptime, (long long)ss->requests); + } +} + +DEFUN (show_ip_ssmpingd, + show_ip_ssmpingd_cmd, + "show ip ssmpingd [vrf NAME]", + SHOW_STR + IP_STR + SHOW_SSMPINGD_STR + VRF_CMD_HELP_STR) +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, false); + + if (!vrf) + return CMD_WARNING; + + show_ssmpingd(vrf->info, vty); + return CMD_SUCCESS; +} + +DEFUN (ip_pim_spt_switchover_infinity, + ip_pim_spt_switchover_infinity_cmd, + "ip pim spt-switchover infinity-and-beyond", + IP_STR + PIM_STR + "SPT-Switchover\n" + "Never switch to SPT Tree\n") +{ + return pim_process_spt_switchover_infinity_cmd(vty); +} + +DEFPY (ip_pim_spt_switchover_infinity_plist, + ip_pim_spt_switchover_infinity_plist_cmd, + "ip pim spt-switchover infinity-and-beyond prefix-list PREFIXLIST4_NAME$plist", + IP_STR + PIM_STR + "SPT-Switchover\n" + "Never switch to SPT Tree\n" + "Prefix-List to control which groups to switch\n" + "Prefix-List name\n") +{ + return pim_process_spt_switchover_prefixlist_cmd(vty, plist); +} + +DEFUN (no_ip_pim_spt_switchover_infinity, + no_ip_pim_spt_switchover_infinity_cmd, + "no ip pim spt-switchover infinity-and-beyond", + NO_STR + IP_STR + PIM_STR + "SPT_Switchover\n" + "Never switch to SPT Tree\n") +{ + return pim_process_no_spt_switchover_cmd(vty); +} + +DEFUN (no_ip_pim_spt_switchover_infinity_plist, + no_ip_pim_spt_switchover_infinity_plist_cmd, + "no ip pim spt-switchover infinity-and-beyond prefix-list PREFIXLIST4_NAME", + NO_STR + IP_STR + PIM_STR + "SPT_Switchover\n" + "Never switch to SPT Tree\n" + "Prefix-List to control which groups to switch\n" + "Prefix-List name\n") +{ + return pim_process_no_spt_switchover_cmd(vty); +} + +DEFPY (pim_register_accept_list, + pim_register_accept_list_cmd, + "[no] ip pim register-accept-list PREFIXLIST4_NAME$word", + NO_STR + IP_STR + PIM_STR + "Only accept registers from a specific source prefix list\n" + "Prefix-List name\n") +{ + const char *vrfname; + char reg_alist_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(reg_alist_xpath, sizeof(reg_alist_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + "frr-routing:ipv4"); + strlcat(reg_alist_xpath, "/register-accept-list", + sizeof(reg_alist_xpath)); + + if (no) + nb_cli_enqueue_change(vty, reg_alist_xpath, + NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, reg_alist_xpath, + NB_OP_MODIFY, word); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY (ip_pim_joinprune_time, + ip_pim_joinprune_time_cmd, + "ip pim join-prune-interval (1-65535)$jpi", + IP_STR + "pim multicast routing\n" + "Join Prune Send Interval\n" + "Seconds\n") +{ + return pim_process_join_prune_cmd(vty, jpi_str); +} + +DEFUN (no_ip_pim_joinprune_time, + no_ip_pim_joinprune_time_cmd, + "no ip pim join-prune-interval [(1-65535)]", + NO_STR + IP_STR + "pim multicast routing\n" + "Join Prune Send Interval\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_join_prune_cmd(vty); +} + +DEFPY (ip_pim_register_suppress, + ip_pim_register_suppress_cmd, + "ip pim register-suppress-time (1-65535)$rst", + IP_STR + "pim multicast routing\n" + "Register Suppress Timer\n" + "Seconds\n") +{ + return pim_process_register_suppress_cmd(vty, rst_str); +} + +DEFUN (no_ip_pim_register_suppress, + no_ip_pim_register_suppress_cmd, + "no ip pim register-suppress-time [(1-65535)]", + NO_STR + IP_STR + "pim multicast routing\n" + "Register Suppress Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_register_suppress_cmd(vty); +} + +DEFPY (ip_pim_rp_keep_alive, + ip_pim_rp_keep_alive_cmd, + "ip pim rp keep-alive-timer (1-65535)$kat", + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "Keep alive Timer\n" + "Seconds\n") +{ + return pim_process_rp_kat_cmd(vty, kat_str); +} + +DEFUN (no_ip_pim_rp_keep_alive, + no_ip_pim_rp_keep_alive_cmd, + "no ip pim rp keep-alive-timer [(1-65535)]", + NO_STR + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "Keep alive Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_rp_kat_cmd(vty); +} + +DEFPY (ip_pim_keep_alive, + ip_pim_keep_alive_cmd, + "ip pim keep-alive-timer (1-65535)$kat", + IP_STR + "pim multicast routing\n" + "Keep alive Timer\n" + "Seconds\n") +{ + return pim_process_keepalivetimer_cmd(vty, kat_str); +} + +DEFUN (no_ip_pim_keep_alive, + no_ip_pim_keep_alive_cmd, + "no ip pim keep-alive-timer [(1-65535)]", + NO_STR + IP_STR + "pim multicast routing\n" + "Keep alive Timer\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_keepalivetimer_cmd(vty); +} + +DEFPY (ip_pim_packets, + ip_pim_packets_cmd, + "ip pim packets (1-255)", + IP_STR + "pim multicast routing\n" + "packets to process at one time per fd\n" + "Number of packets\n") +{ + return pim_process_pim_packet_cmd(vty, packets_str); +} + +DEFUN (no_ip_pim_packets, + no_ip_pim_packets_cmd, + "no ip pim packets [(1-255)]", + NO_STR + IP_STR + "pim multicast routing\n" + "packets to process at one time per fd\n" + IGNORED_IN_NO_STR) +{ + return pim_process_no_pim_packet_cmd(vty); +} + +DEFPY (ip_igmp_group_watermark, + ip_igmp_group_watermark_cmd, + "ip igmp watermark-warn (1-65535)$limit", + IP_STR + IGMP_STR + "Configure group limit for watermark warning\n" + "Group count to generate watermark warning\n") +{ + PIM_DECLVAR_CONTEXT_VRF(vrf, pim); + pim->gm_watermark_limit = limit; + + return CMD_SUCCESS; +} + +DEFPY (no_ip_igmp_group_watermark, + no_ip_igmp_group_watermark_cmd, + "no ip igmp watermark-warn [(1-65535)$limit]", + NO_STR + IP_STR + IGMP_STR + "Unconfigure group limit for watermark warning\n" + IGNORED_IN_NO_STR) +{ + PIM_DECLVAR_CONTEXT_VRF(vrf, pim); + pim->gm_watermark_limit = 0; + + return CMD_SUCCESS; +} + +DEFUN (ip_pim_v6_secondary, + ip_pim_v6_secondary_cmd, + "ip pim send-v6-secondary", + IP_STR + "pim multicast routing\n" + "Send v6 secondary addresses\n") +{ + const char *vrfname; + char send_v6_secondary_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(send_v6_secondary_xpath, sizeof(send_v6_secondary_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(send_v6_secondary_xpath, "/send-v6-secondary", + sizeof(send_v6_secondary_xpath)); + + nb_cli_enqueue_change(vty, send_v6_secondary_xpath, NB_OP_MODIFY, + "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (no_ip_pim_v6_secondary, + no_ip_pim_v6_secondary_cmd, + "no ip pim send-v6-secondary", + NO_STR + IP_STR + "pim multicast routing\n" + "Send v6 secondary addresses\n") +{ + const char *vrfname; + char send_v6_secondary_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(send_v6_secondary_xpath, sizeof(send_v6_secondary_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(send_v6_secondary_xpath, "/send-v6-secondary", + sizeof(send_v6_secondary_xpath)); + + nb_cli_enqueue_change(vty, send_v6_secondary_xpath, NB_OP_MODIFY, + "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY (ip_pim_rp, + ip_pim_rp_cmd, + "ip pim rp A.B.C.D$rp [A.B.C.D/M]$gp", + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "ip address of RP\n" + "Group Address range to cover\n") +{ + const char *group_str = (gp_str) ? gp_str : "224.0.0.0/4"; + + return pim_process_rp_cmd(vty, rp_str, group_str); +} + +DEFPY (ip_pim_rp_prefix_list, + ip_pim_rp_prefix_list_cmd, + "ip pim rp A.B.C.D$rp prefix-list PREFIXLIST4_NAME$plist", + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "ip address of RP\n" + "group prefix-list filter\n" + "Name of a prefix-list\n") +{ + return pim_process_rp_plist_cmd(vty, rp_str, plist); +} + +DEFPY (no_ip_pim_rp, + no_ip_pim_rp_cmd, + "no ip pim rp A.B.C.D$rp [A.B.C.D/M]$gp", + NO_STR + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "ip address of RP\n" + "Group Address range to cover\n") +{ + const char *group_str = (gp_str) ? gp_str : "224.0.0.0/4"; + + return pim_process_no_rp_cmd(vty, rp_str, group_str); +} + +DEFPY (no_ip_pim_rp_prefix_list, + no_ip_pim_rp_prefix_list_cmd, + "no ip pim rp A.B.C.D$rp prefix-list PREFIXLIST4_NAME$plist", + NO_STR + IP_STR + "pim multicast routing\n" + "Rendezvous Point\n" + "ip address of RP\n" + "group prefix-list filter\n" + "Name of a prefix-list\n") +{ + return pim_process_no_rp_plist_cmd(vty, rp_str, plist); +} + +DEFUN (ip_pim_ssm_prefix_list, + ip_pim_ssm_prefix_list_cmd, + "ip pim ssm prefix-list PREFIXLIST4_NAME", + IP_STR + "pim multicast routing\n" + "Source Specific Multicast\n" + "group range prefix-list filter\n" + "Name of a prefix-list\n") +{ + const char *vrfname; + char ssm_plist_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ssm_plist_xpath, sizeof(ssm_plist_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ssm_plist_xpath, "/ssm-prefix-list", sizeof(ssm_plist_xpath)); + + nb_cli_enqueue_change(vty, ssm_plist_xpath, NB_OP_MODIFY, argv[4]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (no_ip_pim_ssm_prefix_list, + no_ip_pim_ssm_prefix_list_cmd, + "no ip pim ssm prefix-list", + NO_STR + IP_STR + "pim multicast routing\n" + "Source Specific Multicast\n" + "group range prefix-list filter\n") +{ + const char *vrfname; + char ssm_plist_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ssm_plist_xpath, sizeof(ssm_plist_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ssm_plist_xpath, "/ssm-prefix-list", sizeof(ssm_plist_xpath)); + + nb_cli_enqueue_change(vty, ssm_plist_xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (no_ip_pim_ssm_prefix_list_name, + no_ip_pim_ssm_prefix_list_name_cmd, + "no ip pim ssm prefix-list PREFIXLIST4_NAME", + NO_STR + IP_STR + "pim multicast routing\n" + "Source Specific Multicast\n" + "group range prefix-list filter\n" + "Name of a prefix-list\n") +{ + const char *vrfname; + const struct lyd_node *ssm_plist_dnode; + char ssm_plist_xpath[XPATH_MAXLEN]; + const char *ssm_plist_name; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ssm_plist_xpath, sizeof(ssm_plist_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ssm_plist_xpath, "/ssm-prefix-list", sizeof(ssm_plist_xpath)); + ssm_plist_dnode = yang_dnode_get(vty->candidate_config->dnode, + ssm_plist_xpath); + + if (!ssm_plist_dnode) { + vty_out(vty, + "%% pim ssm prefix-list %s doesn't exist\n", + argv[5]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + + ssm_plist_name = yang_dnode_get_string(ssm_plist_dnode, "."); + + if (ssm_plist_name && !strcmp(ssm_plist_name, argv[5]->arg)) { + nb_cli_enqueue_change(vty, ssm_plist_xpath, NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); + } + + vty_out(vty, "%% pim ssm prefix-list %s doesn't exist\n", argv[5]->arg); + + return CMD_WARNING_CONFIG_FAILED; +} + +DEFUN (show_ip_pim_ssm_range, + show_ip_pim_ssm_range_cmd, + "show ip pim [vrf NAME] group-type [json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM group type\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + ip_pim_ssm_show_group_range(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +static void ip_pim_ssm_show_group_type(struct pim_instance *pim, + struct vty *vty, bool uj, + const char *group) +{ + struct in_addr group_addr; + const char *type_str; + int result; + + result = inet_pton(AF_INET, group, &group_addr); + if (result <= 0) + type_str = "invalid"; + else { + if (pim_is_group_224_4(group_addr)) + type_str = + pim_is_grp_ssm(pim, group_addr) ? "SSM" : "ASM"; + else + type_str = "not-multicast"; + } + + if (uj) { + json_object *json; + json = json_object_new_object(); + json_object_string_add(json, "groupType", type_str); + vty_json(vty, json); + } else + vty_out(vty, "Group type : %s\n", type_str); +} + +DEFUN (show_ip_pim_group_type, + show_ip_pim_group_type_cmd, + "show ip pim [vrf NAME] group-type A.B.C.D [json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "multicast group type\n" + "group address\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + argv_find(argv, argc, "A.B.C.D", &idx); + ip_pim_ssm_show_group_type(vrf->info, vty, uj, argv[idx]->arg); + + return CMD_SUCCESS; +} + +DEFPY (show_ip_pim_bsr, + show_ip_pim_bsr_cmd, + "show ip pim bsr [vrf NAME] [json$json]", + SHOW_STR + IP_STR + PIM_STR + "boot-strap router information\n" + VRF_CMD_HELP_STR + JSON_STR) +{ + return pim_show_bsr_helper(vrf, vty, !!json); +} + +DEFUN (ip_ssmpingd, + ip_ssmpingd_cmd, + "ip ssmpingd [A.B.C.D]", + IP_STR + CONF_SSMPINGD_STR + "Source address\n") +{ + int idx_ipv4 = 2; + const char *src_str = (argc == 3) ? argv[idx_ipv4]->arg : "0.0.0.0"; + + return pim_process_ssmpingd_cmd(vty, NB_OP_CREATE, src_str); +} + +DEFUN (no_ip_ssmpingd, + no_ip_ssmpingd_cmd, + "no ip ssmpingd [A.B.C.D]", + NO_STR + IP_STR + CONF_SSMPINGD_STR + "Source address\n") +{ + int idx_ipv4 = 3; + const char *src_str = (argc == 4) ? argv[idx_ipv4]->arg : "0.0.0.0"; + + return pim_process_ssmpingd_cmd(vty, NB_OP_DESTROY, src_str); +} + +DEFUN (ip_pim_ecmp, + ip_pim_ecmp_cmd, + "ip pim ecmp", + IP_STR + "pim multicast routing\n" + "Enable PIM ECMP \n") +{ + const char *vrfname; + char ecmp_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ecmp_xpath, sizeof(ecmp_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ecmp_xpath, "/ecmp", sizeof(ecmp_xpath)); + + nb_cli_enqueue_change(vty, ecmp_xpath, NB_OP_MODIFY, "true"); + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (no_ip_pim_ecmp, + no_ip_pim_ecmp_cmd, + "no ip pim ecmp", + NO_STR + IP_STR + "pim multicast routing\n" + "Disable PIM ECMP \n") +{ + const char *vrfname; + char ecmp_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ecmp_xpath, sizeof(ecmp_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ecmp_xpath, "/ecmp", sizeof(ecmp_xpath)); + + nb_cli_enqueue_change(vty, ecmp_xpath, NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (ip_pim_ecmp_rebalance, + ip_pim_ecmp_rebalance_cmd, + "ip pim ecmp rebalance", + IP_STR + "pim multicast routing\n" + "Enable PIM ECMP \n" + "Enable PIM ECMP Rebalance\n") +{ + const char *vrfname; + char ecmp_xpath[XPATH_MAXLEN]; + char ecmp_rebalance_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ecmp_xpath, sizeof(ecmp_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ecmp_xpath, "/ecmp", sizeof(ecmp_xpath)); + snprintf(ecmp_rebalance_xpath, sizeof(ecmp_rebalance_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ecmp_rebalance_xpath, "/ecmp-rebalance", + sizeof(ecmp_rebalance_xpath)); + + nb_cli_enqueue_change(vty, ecmp_xpath, NB_OP_MODIFY, "true"); + nb_cli_enqueue_change(vty, ecmp_rebalance_xpath, NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (no_ip_pim_ecmp_rebalance, + no_ip_pim_ecmp_rebalance_cmd, + "no ip pim ecmp rebalance", + NO_STR + IP_STR + "pim multicast routing\n" + "Disable PIM ECMP \n" + "Disable PIM ECMP Rebalance\n") +{ + const char *vrfname; + char ecmp_rebalance_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ecmp_rebalance_xpath, sizeof(ecmp_rebalance_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + strlcat(ecmp_rebalance_xpath, "/ecmp-rebalance", + sizeof(ecmp_rebalance_xpath)); + + nb_cli_enqueue_change(vty, ecmp_rebalance_xpath, NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (interface_ip_igmp, + interface_ip_igmp_cmd, + "ip igmp", + IP_STR + IFACE_IGMP_STR) +{ + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_no_ip_igmp, + interface_no_ip_igmp_cmd, + "no ip igmp", + NO_STR + IP_STR + IFACE_IGMP_STR) +{ + const struct lyd_node *pim_enable_dnode; + char pim_if_xpath[XPATH_MAXLEN]; + + int printed = + snprintf(pim_if_xpath, sizeof(pim_if_xpath), + "%s/frr-pim:pim/address-family[address-family='%s']", + VTY_CURR_XPATH, "frr-routing:ipv4"); + + if (printed >= (int)(sizeof(pim_if_xpath))) { + vty_out(vty, "Xpath too long (%d > %u)", printed + 1, + XPATH_MAXLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv4"); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) { + nb_cli_enqueue_change(vty, pim_if_xpath, NB_OP_DESTROY, + NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else + nb_cli_enqueue_change(vty, "./enable", + NB_OP_MODIFY, "false"); + } + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_ip_igmp_join, + interface_ip_igmp_join_cmd, + "ip igmp join A.B.C.D [A.B.C.D]", + IP_STR + IFACE_IGMP_STR + "IGMP join multicast group\n" + "Multicast group address\n" + "Source address\n") +{ + int idx_group = 3; + int idx_source = 4; + const char *source_str; + char xpath[XPATH_MAXLEN]; + + if (argc == 5) { + source_str = argv[idx_source]->arg; + + if (strcmp(source_str, "0.0.0.0") == 0) { + vty_out(vty, "Bad source address %s\n", + argv[idx_source]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + } else + source_str = "0.0.0.0"; + + snprintf(xpath, sizeof(xpath), FRR_GMP_JOIN_XPATH, + "frr-routing:ipv4", argv[idx_group]->arg, source_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (interface_no_ip_igmp_join, + interface_no_ip_igmp_join_cmd, + "no ip igmp join A.B.C.D [A.B.C.D]", + NO_STR + IP_STR + IFACE_IGMP_STR + "IGMP join multicast group\n" + "Multicast group address\n" + "Source address\n") +{ + int idx_group = 4; + int idx_source = 5; + const char *source_str; + char xpath[XPATH_MAXLEN]; + + if (argc == 6) { + source_str = argv[idx_source]->arg; + + if (strcmp(source_str, "0.0.0.0") == 0) { + vty_out(vty, "Bad source address %s\n", + argv[idx_source]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + } else + source_str = "0.0.0.0"; + + snprintf(xpath, sizeof(xpath), FRR_GMP_JOIN_XPATH, + "frr-routing:ipv4", argv[idx_group]->arg, source_str); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN (interface_ip_igmp_query_interval, + interface_ip_igmp_query_interval_cmd, + "ip igmp query-interval (1-65535)", + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_INTERVAL_STR + "Query interval in seconds\n") +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = + yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv4"); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", + NB_OP_MODIFY, "true"); + } + + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_MODIFY, + argv[3]->arg); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_no_ip_igmp_query_interval, + interface_no_ip_igmp_query_interval_cmd, + "no ip igmp query-interval [(1-65535)]", + NO_STR + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_INTERVAL_STR + IGNORED_IN_NO_STR) +{ + nb_cli_enqueue_change(vty, "./query-interval", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_ip_igmp_version, + interface_ip_igmp_version_cmd, + "ip igmp version (2-3)", + IP_STR + IFACE_IGMP_STR + "IGMP version\n" + "IGMP version number\n") +{ + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + nb_cli_enqueue_change(vty, "./igmp-version", NB_OP_MODIFY, + argv[3]->arg); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_no_ip_igmp_version, + interface_no_ip_igmp_version_cmd, + "no ip igmp version (2-3)", + NO_STR + IP_STR + IFACE_IGMP_STR + "IGMP version\n" + "IGMP version number\n") +{ + nb_cli_enqueue_change(vty, "./igmp-version", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFPY (interface_ip_igmp_query_max_response_time, + interface_ip_igmp_query_max_response_time_cmd, + "ip igmp query-max-response-time (1-65535)$qmrt", + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR + "Query response value in deci-seconds\n") +{ + return gm_process_query_max_response_time_cmd(vty, qmrt_str); +} + +DEFUN (interface_no_ip_igmp_query_max_response_time, + interface_no_ip_igmp_query_max_response_time_cmd, + "no ip igmp query-max-response-time [(1-65535)]", + NO_STR + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_query_max_response_time_cmd(vty); +} + +DEFUN_HIDDEN (interface_ip_igmp_query_max_response_time_dsec, + interface_ip_igmp_query_max_response_time_dsec_cmd, + "ip igmp query-max-response-time-dsec (1-65535)", + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR + "Query response value in deciseconds\n") +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = + yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv4"); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", + NB_OP_MODIFY, "true"); + } + + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + argv[3]->arg); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN_HIDDEN (interface_no_ip_igmp_query_max_response_time_dsec, + interface_no_ip_igmp_query_max_response_time_dsec_cmd, + "no ip igmp query-max-response-time-dsec [(1-65535)]", + NO_STR + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR + IGNORED_IN_NO_STR) +{ + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFPY (interface_ip_igmp_last_member_query_count, + interface_ip_igmp_last_member_query_count_cmd, + "ip igmp last-member-query-count (1-255)$lmqc", + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR + "Last member query count\n") +{ + return gm_process_last_member_query_count_cmd(vty, lmqc_str); +} + +DEFUN (interface_no_ip_igmp_last_member_query_count, + interface_no_ip_igmp_last_member_query_count_cmd, + "no ip igmp last-member-query-count [(1-255)]", + NO_STR + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_last_member_query_count_cmd(vty); +} + +DEFPY (interface_ip_igmp_last_member_query_interval, + interface_ip_igmp_last_member_query_interval_cmd, + "ip igmp last-member-query-interval (1-65535)$lmqi", + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR + "Last member query interval in deciseconds\n") +{ + return gm_process_last_member_query_interval_cmd(vty, lmqi_str); +} + +DEFUN (interface_no_ip_igmp_last_member_query_interval, + interface_no_ip_igmp_last_member_query_interval_cmd, + "no ip igmp last-member-query-interval [(1-65535)]", + NO_STR + IP_STR + IFACE_IGMP_STR + IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR + IGNORED_IN_NO_STR) +{ + return gm_process_no_last_member_query_interval_cmd(vty); +} + +DEFUN (interface_ip_pim_drprio, + interface_ip_pim_drprio_cmd, + "ip pim drpriority (0-4294967295)", + IP_STR + PIM_STR + "Set the Designated Router Election Priority\n" + "Value of the new DR Priority\n") +{ + int idx_number = 3; + + return pim_process_ip_pim_drprio_cmd(vty, argv[idx_number]->arg); +} + +DEFUN (interface_no_ip_pim_drprio, + interface_no_ip_pim_drprio_cmd, + "no ip pim drpriority [(0-4294967295)]", + NO_STR + IP_STR + PIM_STR + "Revert the Designated Router Priority to default\n" + "Old Value of the Priority\n") +{ + return pim_process_no_ip_pim_drprio_cmd(vty); +} + +DEFPY_HIDDEN (interface_ip_igmp_query_generate, + interface_ip_igmp_query_generate_cmd, + "ip igmp generate-query-once [version (2-3)]", + IP_STR + IFACE_IGMP_STR + "Generate igmp general query once\n" + "IGMP version\n" + "IGMP version number\n") +{ +#if PIM_IPV == 4 + VTY_DECLVAR_CONTEXT(interface, ifp); + int igmp_version; + struct pim_interface *pim_ifp = ifp->info; + + if (!ifp->info) { + vty_out(vty, "IGMP/PIM is not enabled on the interface %s\n", + ifp->name); + return CMD_WARNING_CONFIG_FAILED; + } + + /* It takes the igmp version configured on the interface as default */ + igmp_version = pim_ifp->igmp_version; + + if (argc > 3) + igmp_version = atoi(argv[4]->arg); + + igmp_send_query_on_intf(ifp, igmp_version); +#endif + return CMD_SUCCESS; +} + +DEFPY_HIDDEN (pim_test_sg_keepalive, + pim_test_sg_keepalive_cmd, + "test pim [vrf NAME$name] keepalive-reset A.B.C.D$source A.B.C.D$group", + "Test code\n" + PIM_STR + VRF_CMD_HELP_STR + "Reset the Keepalive Timer\n" + "The Source we are resetting\n" + "The Group we are resetting\n") +{ + struct pim_upstream *up; + struct vrf *vrf; + struct pim_instance *pim; + pim_sgaddr sg; + + sg.src = source; + sg.grp = group; + + vrf = vrf_lookup_by_name(name ? name : VRF_DEFAULT_NAME); + if (!vrf) { + vty_out(vty, "%% Vrf specified: %s does not exist\n", name); + return CMD_WARNING; + } + + pim = vrf->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + up = pim_upstream_find(pim, &sg); + if (!up) { + vty_out(vty, "%% Unable to find %pSG specified\n", &sg); + return CMD_WARNING; + } + + vty_out(vty, "Setting %pSG to current keep alive time: %d\n", &sg, + pim->keep_alive_time); + pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); + + return CMD_SUCCESS; +} + +DEFPY (interface_ip_pim_activeactive, + interface_ip_pim_activeactive_cmd, + "[no$no] ip pim active-active", + NO_STR + IP_STR + PIM_STR + "Mark interface as Active-Active for MLAG operations, Hidden because not finished yet\n") +{ + return pim_process_ip_pim_activeactive_cmd(vty, no); +} + +DEFUN_HIDDEN (interface_ip_pim_ssm, + interface_ip_pim_ssm_cmd, + "ip pim ssm", + IP_STR + PIM_STR + IFACE_PIM_STR) +{ + int ret; + + ret = pim_process_ip_pim_cmd(vty); + + if (ret != NB_OK) + return ret; + + vty_out(vty, + "WARN: Enabled PIM SM on interface; configure PIM SSM range if needed\n"); + + return NB_OK; +} + +DEFUN_HIDDEN (interface_ip_pim_sm, + interface_ip_pim_sm_cmd, + "ip pim sm", + IP_STR + PIM_STR + IFACE_PIM_SM_STR) +{ + return pim_process_ip_pim_cmd(vty); +} + +DEFPY (interface_ip_pim, + interface_ip_pim_cmd, + "ip pim [passive$passive]", + IP_STR + PIM_STR + "Disable exchange of protocol packets\n") +{ + int ret; + + ret = pim_process_ip_pim_cmd(vty); + + if (ret != NB_OK) + return ret; + + if (passive) + return pim_process_ip_pim_passive_cmd(vty, true); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (interface_no_ip_pim_ssm, + interface_no_ip_pim_ssm_cmd, + "no ip pim ssm", + NO_STR + IP_STR + PIM_STR + IFACE_PIM_STR) +{ + return pim_process_no_ip_pim_cmd(vty); +} + +DEFUN_HIDDEN (interface_no_ip_pim_sm, + interface_no_ip_pim_sm_cmd, + "no ip pim sm", + NO_STR + IP_STR + PIM_STR + IFACE_PIM_SM_STR) +{ + return pim_process_no_ip_pim_cmd(vty); +} + +DEFPY (interface_no_ip_pim, + interface_no_ip_pim_cmd, + "no ip pim [passive$passive]", + NO_STR + IP_STR + PIM_STR + "Disable exchange of protocol packets\n") +{ + if (passive) + return pim_process_ip_pim_passive_cmd(vty, false); + + return pim_process_no_ip_pim_cmd(vty); +} + +/* boundaries */ +DEFUN(interface_ip_pim_boundary_oil, + interface_ip_pim_boundary_oil_cmd, + "ip multicast boundary oil WORD", + IP_STR + "Generic multicast configuration options\n" + "Define multicast boundary\n" + "Filter OIL by group using prefix list\n" + "Prefix list to filter OIL with\n") +{ + return pim_process_ip_pim_boundary_oil_cmd(vty, argv[4]->arg); +} + +DEFUN(interface_no_ip_pim_boundary_oil, + interface_no_ip_pim_boundary_oil_cmd, + "no ip multicast boundary oil [WORD]", + NO_STR + IP_STR + "Generic multicast configuration options\n" + "Define multicast boundary\n" + "Filter OIL by group using prefix list\n" + "Prefix list to filter OIL with\n") +{ + return pim_process_no_ip_pim_boundary_oil_cmd(vty); +} + +DEFUN (interface_ip_mroute, + interface_ip_mroute_cmd, + "ip mroute INTERFACE A.B.C.D [A.B.C.D]", + IP_STR + "Add multicast route\n" + "Outgoing interface name\n" + "Group address\n" + "Source address\n") +{ + int idx_interface = 2; + int idx_ipv4 = 3; + const char *source_str; + + if (argc == (idx_ipv4 + 1)) + source_str = "0.0.0.0"; + else + source_str = argv[idx_ipv4 + 1]->arg; + + return pim_process_ip_mroute_cmd(vty, argv[idx_interface]->arg, + argv[idx_ipv4]->arg, source_str); +} + +DEFUN (interface_no_ip_mroute, + interface_no_ip_mroute_cmd, + "no ip mroute INTERFACE A.B.C.D [A.B.C.D]", + NO_STR + IP_STR + "Add multicast route\n" + "Outgoing interface name\n" + "Group Address\n" + "Source Address\n") +{ + int idx_interface = 3; + int idx_ipv4 = 4; + const char *source_str; + + if (argc == (idx_ipv4 + 1)) + source_str = "0.0.0.0"; + else + source_str = argv[idx_ipv4 + 1]->arg; + + return pim_process_no_ip_mroute_cmd(vty, argv[idx_interface]->arg, + argv[idx_ipv4]->arg, source_str); +} + +DEFUN (interface_ip_pim_hello, + interface_ip_pim_hello_cmd, + "ip pim hello (1-65535) [(1-65535)]", + IP_STR + PIM_STR + IFACE_PIM_HELLO_STR + IFACE_PIM_HELLO_TIME_STR + IFACE_PIM_HELLO_HOLD_STR) +{ + int idx_time = 3; + int idx_hold = 4; + + if (argc == idx_hold + 1) + return pim_process_ip_pim_hello_cmd(vty, argv[idx_time]->arg, + argv[idx_hold]->arg); + + else + return pim_process_ip_pim_hello_cmd(vty, argv[idx_time]->arg, + NULL); +} + +DEFUN (interface_no_ip_pim_hello, + interface_no_ip_pim_hello_cmd, + "no ip pim hello [(1-65535) [(1-65535)]]", + NO_STR + IP_STR + PIM_STR + IFACE_PIM_HELLO_STR + IGNORED_IN_NO_STR + IGNORED_IN_NO_STR) +{ + return pim_process_no_ip_pim_hello_cmd(vty); +} + +DEFUN (debug_igmp, + debug_igmp_cmd, + "debug igmp", + DEBUG_STR + DEBUG_IGMP_STR) +{ + PIM_DO_DEBUG_GM_EVENTS; + PIM_DO_DEBUG_GM_PACKETS; + PIM_DO_DEBUG_GM_TRACE; + return CMD_SUCCESS; +} + +DEFUN (no_debug_igmp, + no_debug_igmp_cmd, + "no debug igmp", + NO_STR + DEBUG_STR + DEBUG_IGMP_STR) +{ + PIM_DONT_DEBUG_GM_EVENTS; + PIM_DONT_DEBUG_GM_PACKETS; + PIM_DONT_DEBUG_GM_TRACE; + return CMD_SUCCESS; +} + + +DEFUN (debug_igmp_events, + debug_igmp_events_cmd, + "debug igmp events", + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_EVENTS_STR) +{ + PIM_DO_DEBUG_GM_EVENTS; + return CMD_SUCCESS; +} + +DEFUN (no_debug_igmp_events, + no_debug_igmp_events_cmd, + "no debug igmp events", + NO_STR + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_EVENTS_STR) +{ + PIM_DONT_DEBUG_GM_EVENTS; + return CMD_SUCCESS; +} + + +DEFUN (debug_igmp_packets, + debug_igmp_packets_cmd, + "debug igmp packets", + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_PACKETS_STR) +{ + PIM_DO_DEBUG_GM_PACKETS; + return CMD_SUCCESS; +} + +DEFUN (no_debug_igmp_packets, + no_debug_igmp_packets_cmd, + "no debug igmp packets", + NO_STR + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_PACKETS_STR) +{ + PIM_DONT_DEBUG_GM_PACKETS; + return CMD_SUCCESS; +} + + +DEFUN (debug_igmp_trace, + debug_igmp_trace_cmd, + "debug igmp trace", + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_TRACE_STR) +{ + PIM_DO_DEBUG_GM_TRACE; + return CMD_SUCCESS; +} + +DEFUN (no_debug_igmp_trace, + no_debug_igmp_trace_cmd, + "no debug igmp trace", + NO_STR + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_TRACE_STR) +{ + PIM_DONT_DEBUG_GM_TRACE; + return CMD_SUCCESS; +} + + +DEFUN (debug_igmp_trace_detail, + debug_igmp_trace_detail_cmd, + "debug igmp trace detail", + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_TRACE_STR + "detailed\n") +{ + PIM_DO_DEBUG_GM_TRACE_DETAIL; + return CMD_SUCCESS; +} + +DEFUN (no_debug_igmp_trace_detail, + no_debug_igmp_trace_detail_cmd, + "no debug igmp trace detail", + NO_STR + DEBUG_STR + DEBUG_IGMP_STR + DEBUG_IGMP_TRACE_STR + "detailed\n") +{ + PIM_DONT_DEBUG_GM_TRACE_DETAIL; + return CMD_SUCCESS; +} + + +DEFUN (debug_mroute, + debug_mroute_cmd, + "debug mroute", + DEBUG_STR + DEBUG_MROUTE_STR) +{ + PIM_DO_DEBUG_MROUTE; + return CMD_SUCCESS; +} + +DEFUN (debug_mroute_detail, + debug_mroute_detail_cmd, + "debug mroute detail", + DEBUG_STR + DEBUG_MROUTE_STR + "detailed\n") +{ + PIM_DO_DEBUG_MROUTE_DETAIL; + return CMD_SUCCESS; +} + +DEFUN (no_debug_mroute, + no_debug_mroute_cmd, + "no debug mroute", + NO_STR + DEBUG_STR + DEBUG_MROUTE_STR) +{ + PIM_DONT_DEBUG_MROUTE; + return CMD_SUCCESS; +} + +DEFUN (no_debug_mroute_detail, + no_debug_mroute_detail_cmd, + "no debug mroute detail", + NO_STR + DEBUG_STR + DEBUG_MROUTE_STR + "detailed\n") +{ + PIM_DONT_DEBUG_MROUTE_DETAIL; + return CMD_SUCCESS; +} + +DEFUN (debug_pim_static, + debug_pim_static_cmd, + "debug pim static", + DEBUG_STR + DEBUG_PIM_STR + DEBUG_STATIC_STR) +{ + PIM_DO_DEBUG_STATIC; + return CMD_SUCCESS; +} + +DEFUN (no_debug_pim_static, + no_debug_pim_static_cmd, + "no debug pim static", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_STATIC_STR) +{ + PIM_DONT_DEBUG_STATIC; + return CMD_SUCCESS; +} + + +DEFPY (debug_pim, + debug_pim_cmd, + "[no] debug pim", + NO_STR + DEBUG_STR + DEBUG_PIM_STR) +{ + if (!no) + return pim_debug_pim_cmd(); + else + return pim_no_debug_pim_cmd(); +} + +DEFPY (debug_pim_nht, + debug_pim_nht_cmd, + "[no] debug pim nht", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + "Nexthop Tracking\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_NHT; + else + PIM_DONT_DEBUG_PIM_NHT; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_nht_det, + debug_pim_nht_det_cmd, + "[no] debug pim nht detail", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + "Nexthop Tracking\n" + "Detailed Information\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_NHT_DETAIL; + else + PIM_DONT_DEBUG_PIM_NHT_DETAIL; + return CMD_SUCCESS; +} + +DEFUN (debug_pim_nht_rp, + debug_pim_nht_rp_cmd, + "debug pim nht rp", + DEBUG_STR + DEBUG_PIM_STR + "Nexthop Tracking\n" + "RP Nexthop Tracking\n") +{ + PIM_DO_DEBUG_PIM_NHT_RP; + return CMD_SUCCESS; +} + +DEFUN (no_debug_pim_nht_rp, + no_debug_pim_nht_rp_cmd, + "no debug pim nht rp", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + "Nexthop Tracking\n" + "RP Nexthop Tracking\n") +{ + PIM_DONT_DEBUG_PIM_NHT_RP; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_events, + debug_pim_events_cmd, + "[no] debug pim events", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_EVENTS_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_EVENTS; + else + PIM_DONT_DEBUG_PIM_EVENTS; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_packets, + debug_pim_packets_cmd, + "[no] debug pim packets []", + NO_STR DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_PACKETS_STR + DEBUG_PIM_HELLO_PACKETS_STR + DEBUG_PIM_J_P_PACKETS_STR + DEBUG_PIM_PIM_REG_PACKETS_STR) +{ + if (!no) + return pim_debug_pim_packets_cmd(hello, joins, registers, vty); + else + return pim_no_debug_pim_packets_cmd(hello, joins, registers, + vty); +} + +DEFPY (debug_pim_packetdump_send, + debug_pim_packetdump_send_cmd, + "[no] debug pim packet-dump send", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_PACKETDUMP_STR + DEBUG_PIM_PACKETDUMP_SEND_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_PACKETDUMP_SEND; + else + PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_packetdump_recv, + debug_pim_packetdump_recv_cmd, + "[no] debug pim packet-dump receive", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_PACKETDUMP_STR + DEBUG_PIM_PACKETDUMP_RECV_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_PACKETDUMP_RECV; + else + PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_trace, + debug_pim_trace_cmd, + "[no] debug pim trace", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_TRACE_STR) +{ + if (!no) + PIM_DO_DEBUG_PIM_TRACE; + else + PIM_DONT_DEBUG_PIM_TRACE; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_trace_detail, + debug_pim_trace_detail_cmd, + "[no] debug pim trace detail", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_TRACE_STR + "Detailed Information\n") +{ + if (!no) + PIM_DO_DEBUG_PIM_TRACE_DETAIL; + else + PIM_DONT_DEBUG_PIM_TRACE_DETAIL; + return CMD_SUCCESS; +} + +DEFUN (debug_ssmpingd, + debug_ssmpingd_cmd, + "debug ssmpingd", + DEBUG_STR + DEBUG_SSMPINGD_STR) +{ + PIM_DO_DEBUG_SSMPINGD; + return CMD_SUCCESS; +} + +DEFUN (no_debug_ssmpingd, + no_debug_ssmpingd_cmd, + "no debug ssmpingd", + NO_STR + DEBUG_STR + DEBUG_SSMPINGD_STR) +{ + PIM_DONT_DEBUG_SSMPINGD; + return CMD_SUCCESS; +} + +DEFPY (debug_pim_zebra, + debug_pim_zebra_cmd, + "[no] debug pim zebra", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_ZEBRA_STR) +{ + if (!no) + PIM_DO_DEBUG_ZEBRA; + else + PIM_DONT_DEBUG_ZEBRA; + return CMD_SUCCESS; +} + +DEFUN(debug_pim_mlag, debug_pim_mlag_cmd, "debug pim mlag", + DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_MLAG_STR) +{ + PIM_DO_DEBUG_MLAG; + return CMD_SUCCESS; +} + +DEFUN(no_debug_pim_mlag, no_debug_pim_mlag_cmd, "no debug pim mlag", + NO_STR DEBUG_STR DEBUG_PIM_STR DEBUG_PIM_MLAG_STR) +{ + PIM_DONT_DEBUG_MLAG; + return CMD_SUCCESS; +} + +DEFUN (debug_pim_vxlan, + debug_pim_vxlan_cmd, + "debug pim vxlan", + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_VXLAN_STR) +{ + PIM_DO_DEBUG_VXLAN; + return CMD_SUCCESS; +} + +DEFUN (no_debug_pim_vxlan, + no_debug_pim_vxlan_cmd, + "no debug pim vxlan", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_VXLAN_STR) +{ + PIM_DONT_DEBUG_VXLAN; + return CMD_SUCCESS; +} + +DEFUN (debug_msdp, + debug_msdp_cmd, + "debug msdp", + DEBUG_STR + DEBUG_MSDP_STR) +{ + PIM_DO_DEBUG_MSDP_EVENTS; + PIM_DO_DEBUG_MSDP_PACKETS; + return CMD_SUCCESS; +} + +DEFUN (no_debug_msdp, + no_debug_msdp_cmd, + "no debug msdp", + NO_STR + DEBUG_STR + DEBUG_MSDP_STR) +{ + PIM_DONT_DEBUG_MSDP_EVENTS; + PIM_DONT_DEBUG_MSDP_PACKETS; + return CMD_SUCCESS; +} + +DEFUN (debug_msdp_events, + debug_msdp_events_cmd, + "debug msdp events", + DEBUG_STR + DEBUG_MSDP_STR + DEBUG_MSDP_EVENTS_STR) +{ + PIM_DO_DEBUG_MSDP_EVENTS; + return CMD_SUCCESS; +} + +DEFUN (no_debug_msdp_events, + no_debug_msdp_events_cmd, + "no debug msdp events", + NO_STR + DEBUG_STR + DEBUG_MSDP_STR + DEBUG_MSDP_EVENTS_STR) +{ + PIM_DONT_DEBUG_MSDP_EVENTS; + return CMD_SUCCESS; +} + +DEFUN (debug_msdp_packets, + debug_msdp_packets_cmd, + "debug msdp packets", + DEBUG_STR + DEBUG_MSDP_STR + DEBUG_MSDP_PACKETS_STR) +{ + PIM_DO_DEBUG_MSDP_PACKETS; + return CMD_SUCCESS; +} + +DEFUN (no_debug_msdp_packets, + no_debug_msdp_packets_cmd, + "no debug msdp packets", + NO_STR + DEBUG_STR + DEBUG_MSDP_STR + DEBUG_MSDP_PACKETS_STR) +{ + PIM_DONT_DEBUG_MSDP_PACKETS; + return CMD_SUCCESS; +} + +DEFUN (debug_mtrace, + debug_mtrace_cmd, + "debug mtrace", + DEBUG_STR + DEBUG_MTRACE_STR) +{ + PIM_DO_DEBUG_MTRACE; + return CMD_SUCCESS; +} + +DEFUN (no_debug_mtrace, + no_debug_mtrace_cmd, + "no debug mtrace", + NO_STR + DEBUG_STR + DEBUG_MTRACE_STR) +{ + PIM_DONT_DEBUG_MTRACE; + return CMD_SUCCESS; +} + +DEFUN (debug_bsm, + debug_bsm_cmd, + "debug pim bsm", + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_BSM_STR) +{ + PIM_DO_DEBUG_BSM; + return CMD_SUCCESS; +} + +DEFUN (no_debug_bsm, + no_debug_bsm_cmd, + "no debug pim bsm", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_BSM_STR) +{ + PIM_DONT_DEBUG_BSM; + return CMD_SUCCESS; +} + + +DEFUN_NOSH (show_debugging_pim, + show_debugging_pim_cmd, + "show debugging [pim]", + SHOW_STR + DEBUG_STR + PIM_STR) +{ + vty_out(vty, "PIM debugging status\n"); + + pim_debug_config_write(vty); + + cmd_show_lib_debugs(vty); + return CMD_SUCCESS; +} + +DEFUN (interface_pim_use_source, + interface_pim_use_source_cmd, + "ip pim use-source A.B.C.D", + IP_STR + PIM_STR + "Configure primary IP address\n" + "source ip address\n") +{ + nb_cli_enqueue_change(vty, "./use-source", NB_OP_MODIFY, argv[3]->arg); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (interface_no_pim_use_source, + interface_no_pim_use_source_cmd, + "no ip pim use-source [A.B.C.D]", + NO_STR + IP_STR + PIM_STR + "Delete source IP address\n" + "source ip address\n") +{ + nb_cli_enqueue_change(vty, "./use-source", NB_OP_MODIFY, "0.0.0.0"); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFPY (ip_pim_bfd, + ip_pim_bfd_cmd, + "ip pim bfd [profile BFDPROF$prof]", + IP_STR + PIM_STR + "Enables BFD support\n" + "Use BFD profile\n" + "Use BFD profile name\n") +{ + const struct lyd_node *igmp_enable_dnode; + + igmp_enable_dnode = + yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv4"); + if (!igmp_enable_dnode) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + else { + if (!yang_dnode_get_bool(igmp_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./bfd", NB_OP_CREATE, NULL); + if (prof) + nb_cli_enqueue_change(vty, "./bfd/profile", NB_OP_MODIFY, prof); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFPY(no_ip_pim_bfd_profile, no_ip_pim_bfd_profile_cmd, + "no ip pim bfd profile [BFDPROF]", + NO_STR + IP_STR + PIM_STR + "Enables BFD support\n" + "Disable BFD profile\n" + "BFD Profile name\n") +{ + nb_cli_enqueue_change(vty, "./bfd/profile", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (no_ip_pim_bfd, + no_ip_pim_bfd_cmd, + "no ip pim bfd", + NO_STR + IP_STR + PIM_STR + "Disables BFD support\n") +{ + nb_cli_enqueue_change(vty, "./bfd", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, + "frr-routing:ipv4"); +} + +DEFUN (ip_pim_bsm, + ip_pim_bsm_cmd, + "ip pim bsm", + IP_STR + PIM_STR + "Enable BSM support on the interface\n") +{ + return pim_process_bsm_cmd(vty); +} +DEFUN (no_ip_pim_bsm, + no_ip_pim_bsm_cmd, + "no ip pim bsm", + NO_STR + IP_STR + PIM_STR + "Enable BSM support on the interface\n") +{ + return pim_process_no_bsm_cmd(vty); +} + +DEFUN (ip_pim_ucast_bsm, + ip_pim_ucast_bsm_cmd, + "ip pim unicast-bsm", + IP_STR + PIM_STR + "Accept/Send unicast BSM on the interface\n") +{ + return pim_process_unicast_bsm_cmd(vty); +} + +DEFUN (no_ip_pim_ucast_bsm, + no_ip_pim_ucast_bsm_cmd, + "no ip pim unicast-bsm", + NO_STR + IP_STR + PIM_STR + "Accept/Send unicast BSM on the interface\n") +{ + return pim_process_no_unicast_bsm_cmd(vty); +} + +#if HAVE_BFDD > 0 +DEFUN_HIDDEN ( + ip_pim_bfd_param, + ip_pim_bfd_param_cmd, + "ip pim bfd (2-255) (1-65535) (1-65535)", + IP_STR + PIM_STR + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +#else + DEFUN( + ip_pim_bfd_param, + ip_pim_bfd_param_cmd, + "ip pim bfd (2-255) (1-65535) (1-65535)", + IP_STR + PIM_STR + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +#endif /* HAVE_BFDD */ +{ + int idx_number = 3; + int idx_number_2 = 4; + int idx_number_3 = 5; + const struct lyd_node *igmp_enable_dnode; + + igmp_enable_dnode = + yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + "frr-routing:ipv4"); + if (!igmp_enable_dnode) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + else { + if (!yang_dnode_get_bool(igmp_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./bfd", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./bfd/min-rx-interval", NB_OP_MODIFY, + argv[idx_number_2]->arg); + nb_cli_enqueue_change(vty, "./bfd/min-tx-interval", NB_OP_MODIFY, + argv[idx_number_3]->arg); + nb_cli_enqueue_change(vty, "./bfd/detect_mult", NB_OP_MODIFY, + argv[idx_number]->arg); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, "frr-routing:ipv4"); +} + +#if HAVE_BFDD == 0 +ALIAS(no_ip_pim_bfd, no_ip_pim_bfd_param_cmd, + "no ip pim bfd (2-255) (1-65535) (1-65535)", + NO_STR + IP_STR + PIM_STR + "Enables BFD support\n" + "Detect Multiplier\n" + "Required min receive interval\n" + "Desired min transmit interval\n") +#endif /* !HAVE_BFDD */ + +DEFPY(ip_msdp_peer, ip_msdp_peer_cmd, + "ip msdp peer A.B.C.D$peer source A.B.C.D$source", + IP_STR + CFG_MSDP_STR + "Configure MSDP peer\n" + "Peer IP address\n" + "Source address for TCP connection\n" + "Local IP address\n") +{ + const char *vrfname; + char temp_xpath[XPATH_MAXLEN]; + char msdp_peer_source_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(msdp_peer_source_xpath, sizeof(msdp_peer_source_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + "frr-routing:ipv4"); + snprintf(temp_xpath, sizeof(temp_xpath), + "/msdp-peer[peer-ip='%s']/source-ip", peer_str); + strlcat(msdp_peer_source_xpath, temp_xpath, + sizeof(msdp_peer_source_xpath)); + + nb_cli_enqueue_change(vty, msdp_peer_source_xpath, NB_OP_MODIFY, + source_str); + + return nb_cli_apply_changes(vty, + FRR_PIM_INTERFACE_XPATH, "frr-routing:ipv4"); +} + +DEFPY(ip_msdp_timers, ip_msdp_timers_cmd, + "ip msdp timers (1-65535)$keepalive (1-65535)$holdtime [(1-65535)$connretry]", + IP_STR + CFG_MSDP_STR + "MSDP timers configuration\n" + "Keep alive period (in seconds)\n" + "Hold time period (in seconds)\n" + "Connection retry period (in seconds)\n") +{ + const char *vrfname; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + nb_cli_enqueue_change(vty, "./hold-time", NB_OP_MODIFY, holdtime_str); + nb_cli_enqueue_change(vty, "./keep-alive", NB_OP_MODIFY, keepalive_str); + if (connretry_str) + nb_cli_enqueue_change(vty, "./connection-retry", NB_OP_MODIFY, + connretry_str); + else + nb_cli_enqueue_change(vty, "./connection-retry", NB_OP_DESTROY, + NULL); + + nb_cli_apply_changes(vty, FRR_PIM_MSDP_XPATH, "frr-pim:pimd", "pim", + vrfname, "frr-routing:ipv4"); + return CMD_SUCCESS; +} + +DEFPY(no_ip_msdp_timers, no_ip_msdp_timers_cmd, + "no ip msdp timers [(1-65535) (1-65535) [(1-65535)]]", + NO_STR + IP_STR + CFG_MSDP_STR + "MSDP timers configuration\n" + IGNORED_IN_NO_STR + IGNORED_IN_NO_STR + IGNORED_IN_NO_STR) +{ + const char *vrfname; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + nb_cli_enqueue_change(vty, "./hold-time", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./keep-alive", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./connection-retry", NB_OP_DESTROY, NULL); + + nb_cli_apply_changes(vty, FRR_PIM_MSDP_XPATH, "frr-pim:pimd", "pim", + vrfname, "frr-routing:ipv4"); + + return CMD_SUCCESS; +} + +DEFUN (no_ip_msdp_peer, + no_ip_msdp_peer_cmd, + "no ip msdp peer A.B.C.D", + NO_STR + IP_STR + CFG_MSDP_STR + "Delete MSDP peer\n" + "peer ip address\n") +{ + const char *vrfname; + char msdp_peer_xpath[XPATH_MAXLEN]; + char temp_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(msdp_peer_xpath, sizeof(msdp_peer_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4"); + snprintf(temp_xpath, sizeof(temp_xpath), + "/msdp-peer[peer-ip='%s']", + argv[4]->arg); + + strlcat(msdp_peer_xpath, temp_xpath, sizeof(msdp_peer_xpath)); + + nb_cli_enqueue_change(vty, msdp_peer_xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(ip_msdp_mesh_group_member, + ip_msdp_mesh_group_member_cmd, + "ip msdp mesh-group WORD$gname member A.B.C.D$maddr", + IP_STR + CFG_MSDP_STR + "Configure MSDP mesh-group\n" + "Mesh group name\n" + "Mesh group member\n" + "Peer IP address\n") +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + /* Create mesh group. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_CREATE, NULL); + + /* Create mesh group member. */ + strlcat(xpath_value, "/members[address='", sizeof(xpath_value)); + strlcat(xpath_value, maddr_str, sizeof(xpath_value)); + strlcat(xpath_value, "']", sizeof(xpath_value)); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_ip_msdp_mesh_group_member, + no_ip_msdp_mesh_group_member_cmd, + "no ip msdp mesh-group WORD$gname member A.B.C.D$maddr", + NO_STR + IP_STR + CFG_MSDP_STR + "Delete MSDP mesh-group member\n" + "Mesh group name\n" + "Mesh group member\n" + "Peer IP address\n") +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + char xpath_member_value[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + /* Get mesh group base XPath. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + + if (!yang_dnode_exists(vty->candidate_config->dnode, xpath_value)) { + vty_out(vty, "%% mesh-group does not exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + /* Remove mesh group member. */ + strlcpy(xpath_member_value, xpath_value, sizeof(xpath_member_value)); + strlcat(xpath_member_value, "/members[address='", + sizeof(xpath_member_value)); + strlcat(xpath_member_value, maddr_str, sizeof(xpath_member_value)); + strlcat(xpath_member_value, "']", sizeof(xpath_member_value)); + if (!yang_dnode_exists(vty->candidate_config->dnode, + xpath_member_value)) { + vty_out(vty, "%% mesh-group member does not exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, xpath_member_value, NB_OP_DESTROY, NULL); + + /* + * If this is the last member, then we must remove the group altogether + * to not break legacy CLI behaviour. + */ + pim_cli_legacy_mesh_group_behavior(vty, gname); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(ip_msdp_mesh_group_source, + ip_msdp_mesh_group_source_cmd, + "ip msdp mesh-group WORD$gname source A.B.C.D$saddr", + IP_STR + CFG_MSDP_STR + "Configure MSDP mesh-group\n" + "Mesh group name\n" + "Mesh group local address\n" + "Source IP address for the TCP connection\n") +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + /* Create mesh group. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_CREATE, NULL); + + /* Create mesh group source. */ + strlcat(xpath_value, "/source", sizeof(xpath_value)); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, saddr_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_ip_msdp_mesh_group_source, + no_ip_msdp_mesh_group_source_cmd, + "no ip msdp mesh-group WORD$gname source [A.B.C.D]", + NO_STR + IP_STR + CFG_MSDP_STR + "Delete MSDP mesh-group source\n" + "Mesh group name\n" + "Mesh group source\n" + "Mesh group local address\n") +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + /* Get mesh group base XPath. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_CREATE, NULL); + + /* Create mesh group source. */ + strlcat(xpath_value, "/source", sizeof(xpath_value)); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, NULL); + + /* + * If this is the last member, then we must remove the group altogether + * to not break legacy CLI behaviour. + */ + pim_cli_legacy_mesh_group_behavior(vty, gname); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY(no_ip_msdp_mesh_group, + no_ip_msdp_mesh_group_cmd, + "no ip msdp mesh-group WORD$gname", + NO_STR + IP_STR + CFG_MSDP_STR + "Delete MSDP mesh-group\n" + "Mesh group name\n") +{ + const char *vrfname; + char xpath_value[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + /* Get mesh group base XPath. */ + snprintf(xpath_value, sizeof(xpath_value), + FRR_PIM_VRF_XPATH "/msdp-mesh-groups[name='%s']", + "frr-pim:pimd", "pim", vrfname, "frr-routing:ipv4", gname); + if (!yang_dnode_exists(vty->candidate_config->dnode, xpath_value)) + return CMD_SUCCESS; + + nb_cli_enqueue_change(vty, xpath_value, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +static void ip_msdp_show_mesh_group(struct vty *vty, struct pim_msdp_mg *mg, + struct json_object *json) +{ + struct listnode *mbrnode; + struct pim_msdp_mg_mbr *mbr; + char mbr_str[INET_ADDRSTRLEN]; + char src_str[INET_ADDRSTRLEN]; + char state_str[PIM_MSDP_STATE_STRLEN]; + enum pim_msdp_peer_state state; + json_object *json_mg_row = NULL; + json_object *json_members = NULL; + json_object *json_row = NULL; + + pim_inet4_dump("", mg->src_ip, src_str, sizeof(src_str)); + if (json) { + /* currently there is only one mesh group but we should still + * make + * it a dict with mg-name as key */ + json_mg_row = json_object_new_object(); + json_object_string_add(json_mg_row, "name", + mg->mesh_group_name); + json_object_string_add(json_mg_row, "source", src_str); + } else { + vty_out(vty, "Mesh group : %s\n", mg->mesh_group_name); + vty_out(vty, " Source : %s\n", src_str); + vty_out(vty, " Member State\n"); + } + + for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbrnode, mbr)) { + pim_inet4_dump("", mbr->mbr_ip, mbr_str, sizeof(mbr_str)); + if (mbr->mp) { + state = mbr->mp->state; + } else { + state = PIM_MSDP_DISABLED; + } + pim_msdp_state_dump(state, state_str, sizeof(state_str)); + if (json) { + json_row = json_object_new_object(); + json_object_string_add(json_row, "member", mbr_str); + json_object_string_add(json_row, "state", state_str); + if (!json_members) { + json_members = json_object_new_object(); + json_object_object_add(json_mg_row, "members", + json_members); + } + json_object_object_add(json_members, mbr_str, json_row); + } else { + vty_out(vty, " %-15s %11s\n", mbr_str, state_str); + } + } + + if (json) + json_object_object_add(json, mg->mesh_group_name, json_mg_row); +} + +DEFUN (show_ip_msdp_mesh_group, + show_ip_msdp_mesh_group_cmd, + "show ip msdp [vrf NAME] mesh-group [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP mesh-group information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx = 2; + struct pim_msdp_mg *mg; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + struct pim_instance *pim; + struct json_object *json = NULL; + + if (!vrf) + return CMD_WARNING; + + pim = vrf->info; + /* Quick case: list is empty. */ + if (SLIST_EMPTY(&pim->msdp.mglist)) { + if (uj) + vty_out(vty, "{}\n"); + + return CMD_SUCCESS; + } + + if (uj) + json = json_object_new_object(); + + SLIST_FOREACH (mg, &pim->msdp.mglist, mg_entry) + ip_msdp_show_mesh_group(vty, mg, json); + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_msdp_mesh_group_vrf_all, + show_ip_msdp_mesh_group_vrf_all_cmd, + "show ip msdp vrf all mesh-group [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP mesh-group information\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct json_object *json = NULL, *vrf_json = NULL; + struct pim_instance *pim; + struct pim_msdp_mg *mg; + struct vrf *vrf; + + if (uj) + json = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + vrf_json = json_object_new_object(); + json_object_object_add(json, vrf->name, vrf_json); + } else + vty_out(vty, "VRF: %s\n", vrf->name); + + pim = vrf->info; + SLIST_FOREACH (mg, &pim->msdp.mglist, mg_entry) + ip_msdp_show_mesh_group(vty, mg, vrf_json); + } + + if (uj) + vty_json(vty, json); + + + return CMD_SUCCESS; +} + +static void ip_msdp_show_peers(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct listnode *mpnode; + struct pim_msdp_peer *mp; + char peer_str[INET_ADDRSTRLEN]; + char local_str[INET_ADDRSTRLEN]; + char state_str[PIM_MSDP_STATE_STRLEN]; + char timebuf[PIM_MSDP_UPTIME_STRLEN]; + int64_t now; + json_object *json = NULL; + json_object *json_row = NULL; + + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, + "Peer Local State Uptime SaCnt\n"); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { + if (mp->state == PIM_MSDP_ESTABLISHED) { + now = pim_time_monotonic_sec(); + pim_time_uptime(timebuf, sizeof(timebuf), + now - mp->uptime); + } else { + strlcpy(timebuf, "-", sizeof(timebuf)); + } + pim_inet4_dump("", mp->peer, peer_str, sizeof(peer_str)); + pim_inet4_dump("", mp->local, local_str, + sizeof(local_str)); + pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); + if (uj) { + json_row = json_object_new_object(); + json_object_string_add(json_row, "peer", peer_str); + json_object_string_add(json_row, "local", local_str); + json_object_string_add(json_row, "state", state_str); + json_object_string_add(json_row, "upTime", timebuf); + json_object_int_add(json_row, "saCount", mp->sa_cnt); + json_object_object_add(json, peer_str, json_row); + } else { + vty_out(vty, "%-15s %15s %11s %8s %6d\n", peer_str, + local_str, state_str, timebuf, mp->sa_cnt); + } + } + + if (uj) + vty_json(vty, json); +} + +static void ip_msdp_show_peers_detail(struct pim_instance *pim, struct vty *vty, + const char *peer, bool uj) +{ + struct listnode *mpnode; + struct pim_msdp_peer *mp; + char peer_str[INET_ADDRSTRLEN]; + char local_str[INET_ADDRSTRLEN]; + char state_str[PIM_MSDP_STATE_STRLEN]; + char timebuf[PIM_MSDP_UPTIME_STRLEN]; + char katimer[PIM_MSDP_TIMER_STRLEN]; + char crtimer[PIM_MSDP_TIMER_STRLEN]; + char holdtimer[PIM_MSDP_TIMER_STRLEN]; + int64_t now; + json_object *json = NULL; + json_object *json_row = NULL; + + if (uj) { + json = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { + pim_inet4_dump("", mp->peer, peer_str, sizeof(peer_str)); + if (strcmp(peer, "detail") && strcmp(peer, peer_str)) + continue; + + if (mp->state == PIM_MSDP_ESTABLISHED) { + now = pim_time_monotonic_sec(); + pim_time_uptime(timebuf, sizeof(timebuf), + now - mp->uptime); + } else { + strlcpy(timebuf, "-", sizeof(timebuf)); + } + pim_inet4_dump("", mp->local, local_str, + sizeof(local_str)); + pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); + pim_time_timer_to_hhmmss(katimer, sizeof(katimer), + mp->ka_timer); + pim_time_timer_to_hhmmss(crtimer, sizeof(crtimer), + mp->cr_timer); + pim_time_timer_to_hhmmss(holdtimer, sizeof(holdtimer), + mp->hold_timer); + + if (uj) { + json_row = json_object_new_object(); + json_object_string_add(json_row, "peer", peer_str); + json_object_string_add(json_row, "local", local_str); + if (mp->flags & PIM_MSDP_PEERF_IN_GROUP) + json_object_string_add(json_row, + "meshGroupName", + mp->mesh_group_name); + json_object_string_add(json_row, "state", state_str); + json_object_string_add(json_row, "upTime", timebuf); + json_object_string_add(json_row, "keepAliveTimer", + katimer); + json_object_string_add(json_row, "connRetryTimer", + crtimer); + json_object_string_add(json_row, "holdTimer", + holdtimer); + json_object_string_add(json_row, "lastReset", + mp->last_reset); + json_object_int_add(json_row, "connAttempts", + mp->conn_attempts); + json_object_int_add(json_row, "establishedChanges", + mp->est_flaps); + json_object_int_add(json_row, "saCount", mp->sa_cnt); + json_object_int_add(json_row, "kaSent", mp->ka_tx_cnt); + json_object_int_add(json_row, "kaRcvd", mp->ka_rx_cnt); + json_object_int_add(json_row, "saSent", mp->sa_tx_cnt); + json_object_int_add(json_row, "saRcvd", mp->sa_rx_cnt); + json_object_object_add(json, peer_str, json_row); + } else { + vty_out(vty, "Peer : %s\n", peer_str); + vty_out(vty, " Local : %s\n", local_str); + if (mp->flags & PIM_MSDP_PEERF_IN_GROUP) + vty_out(vty, " Mesh Group : %s\n", + mp->mesh_group_name); + vty_out(vty, " State : %s\n", state_str); + vty_out(vty, " Uptime : %s\n", timebuf); + + vty_out(vty, " Keepalive Timer : %s\n", katimer); + vty_out(vty, " Conn Retry Timer : %s\n", crtimer); + vty_out(vty, " Hold Timer : %s\n", holdtimer); + vty_out(vty, " Last Reset : %s\n", + mp->last_reset); + vty_out(vty, " Conn Attempts : %d\n", + mp->conn_attempts); + vty_out(vty, " Established Changes : %d\n", + mp->est_flaps); + vty_out(vty, " SA Count : %d\n", + mp->sa_cnt); + vty_out(vty, " Statistics :\n"); + vty_out(vty, + " Sent Rcvd\n"); + vty_out(vty, " Keepalives : %10d %10d\n", + mp->ka_tx_cnt, mp->ka_rx_cnt); + vty_out(vty, " SAs : %10d %10d\n", + mp->sa_tx_cnt, mp->sa_rx_cnt); + vty_out(vty, "\n"); + } + } + + if (uj) + vty_json(vty, json); +} + +DEFUN (show_ip_msdp_peer_detail, + show_ip_msdp_peer_detail_cmd, + "show ip msdp [vrf NAME] peer [detail|A.B.C.D] [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP peer information\n" + "Detailed output\n" + "peer ip address\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + char *arg = NULL; + + if (argv_find(argv, argc, "detail", &idx)) + arg = argv[idx]->text; + else if (argv_find(argv, argc, "A.B.C.D", &idx)) + arg = argv[idx]->arg; + + if (arg) + ip_msdp_show_peers_detail(vrf->info, vty, argv[idx]->arg, uj); + else + ip_msdp_show_peers(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_msdp_peer_detail_vrf_all, + show_ip_msdp_peer_detail_vrf_all_cmd, + "show ip msdp vrf all peer [detail|A.B.C.D] [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP peer information\n" + "Detailed output\n" + "peer ip address\n" + JSON_STR) +{ + int idx = 2; + bool uj = use_json(argc, argv); + struct vrf *vrf; + bool first = true; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + if (argv_find(argv, argc, "detail", &idx) + || argv_find(argv, argc, "A.B.C.D", &idx)) + ip_msdp_show_peers_detail(vrf->info, vty, + argv[idx]->arg, uj); + else + ip_msdp_show_peers(vrf->info, vty, uj); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +static void ip_msdp_show_sa(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + char rp_str[INET_ADDRSTRLEN]; + char timebuf[PIM_MSDP_UPTIME_STRLEN]; + char spt_str[8]; + char local_str[8]; + int64_t now; + json_object *json = NULL; + json_object *json_group = NULL; + json_object *json_row = NULL; + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, + "Source Group RP Local SPT Uptime\n"); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + now = pim_time_monotonic_sec(); + pim_time_uptime(timebuf, sizeof(timebuf), now - sa->uptime); + if (sa->flags & PIM_MSDP_SAF_PEER) { + pim_inet4_dump("", sa->rp, rp_str, sizeof(rp_str)); + if (sa->up) { + strlcpy(spt_str, "yes", sizeof(spt_str)); + } else { + strlcpy(spt_str, "no", sizeof(spt_str)); + } + } else { + strlcpy(rp_str, "-", sizeof(rp_str)); + strlcpy(spt_str, "-", sizeof(spt_str)); + } + if (sa->flags & PIM_MSDP_SAF_LOCAL) { + strlcpy(local_str, "yes", sizeof(local_str)); + } else { + strlcpy(local_str, "no", sizeof(local_str)); + } + if (uj) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &sa->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &sa->sg.src); + + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "rp", rp_str); + json_object_string_add(json_row, "local", local_str); + json_object_string_add(json_row, "sptSetup", spt_str); + json_object_string_add(json_row, "upTime", timebuf); + json_object_object_add(json_group, src_str, json_row); + } else { + vty_out(vty, "%-15pPAs %15pPAs %15s %5c %3c %8s\n", + &sa->sg.src, &sa->sg.grp, rp_str, local_str[0], + spt_str[0], timebuf); + } + } + + if (uj) + vty_json(vty, json); +} + +static void ip_msdp_show_sa_entry_detail(struct pim_msdp_sa *sa, + const char *src_str, + const char *grp_str, struct vty *vty, + bool uj, json_object *json) +{ + char rp_str[INET_ADDRSTRLEN]; + char peer_str[INET_ADDRSTRLEN]; + char timebuf[PIM_MSDP_UPTIME_STRLEN]; + char spt_str[8]; + char local_str[8]; + char statetimer[PIM_MSDP_TIMER_STRLEN]; + int64_t now; + json_object *json_group = NULL; + json_object *json_row = NULL; + + now = pim_time_monotonic_sec(); + pim_time_uptime(timebuf, sizeof(timebuf), now - sa->uptime); + if (sa->flags & PIM_MSDP_SAF_PEER) { + pim_inet4_dump("", sa->rp, rp_str, sizeof(rp_str)); + pim_inet4_dump("", sa->peer, peer_str, sizeof(peer_str)); + if (sa->up) { + strlcpy(spt_str, "yes", sizeof(spt_str)); + } else { + strlcpy(spt_str, "no", sizeof(spt_str)); + } + } else { + strlcpy(rp_str, "-", sizeof(rp_str)); + strlcpy(peer_str, "-", sizeof(peer_str)); + strlcpy(spt_str, "-", sizeof(spt_str)); + } + if (sa->flags & PIM_MSDP_SAF_LOCAL) { + strlcpy(local_str, "yes", sizeof(local_str)); + } else { + strlcpy(local_str, "no", sizeof(local_str)); + } + pim_time_timer_to_hhmmss(statetimer, sizeof(statetimer), + sa->sa_state_timer); + if (uj) { + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "rp", rp_str); + json_object_string_add(json_row, "local", local_str); + json_object_string_add(json_row, "sptSetup", spt_str); + json_object_string_add(json_row, "upTime", timebuf); + json_object_string_add(json_row, "stateTimer", statetimer); + json_object_object_add(json_group, src_str, json_row); + } else { + vty_out(vty, "SA : %s\n", sa->sg_str); + vty_out(vty, " RP : %s\n", rp_str); + vty_out(vty, " Peer : %s\n", peer_str); + vty_out(vty, " Local : %s\n", local_str); + vty_out(vty, " SPT Setup : %s\n", spt_str); + vty_out(vty, " Uptime : %s\n", timebuf); + vty_out(vty, " State Timer : %s\n", statetimer); + vty_out(vty, "\n"); + } +} + +static void ip_msdp_show_sa_detail(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + json_object *json = NULL; + + if (uj) { + json = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &sa->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &sa->sg.src); + + ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, uj, + json); + } + + if (uj) + vty_json(vty, json); +} + +DEFUN (show_ip_msdp_sa_detail, + show_ip_msdp_sa_detail_cmd, + "show ip msdp [vrf NAME] sa detail [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP active-source information\n" + "Detailed output\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + ip_msdp_show_sa_detail(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_msdp_sa_detail_vrf_all, + show_ip_msdp_sa_detail_vrf_all_cmd, + "show ip msdp vrf all sa detail [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP active-source information\n" + "Detailed output\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + bool first = true; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + ip_msdp_show_sa_detail(vrf->info, vty, uj); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +static void ip_msdp_show_sa_addr(struct pim_instance *pim, struct vty *vty, + const char *addr, bool uj) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + json_object *json = NULL; + + if (uj) { + json = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &sa->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &sa->sg.src); + + if (!strcmp(addr, src_str) || !strcmp(addr, grp_str)) { + ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, + uj, json); + } + } + + if (uj) + vty_json(vty, json); +} + +static void ip_msdp_show_sa_sg(struct pim_instance *pim, struct vty *vty, + const char *src, const char *grp, bool uj) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + json_object *json = NULL; + + if (uj) { + json = json_object_new_object(); + } + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &sa->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &sa->sg.src); + + if (!strcmp(src, src_str) && !strcmp(grp, grp_str)) { + ip_msdp_show_sa_entry_detail(sa, src_str, grp_str, vty, + uj, json); + } + } + + if (uj) + vty_json(vty, json); +} + +DEFUN (show_ip_msdp_sa_sg, + show_ip_msdp_sa_sg_cmd, + "show ip msdp [vrf NAME] sa [A.B.C.D [A.B.C.D]] [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP active-source information\n" + "source or group ip\n" + "group ip\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + int idx = 2; + + vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx++]->arg + : NULL; + char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) + ? argv[idx]->arg + : NULL; + + if (src_ip && grp_ip) + ip_msdp_show_sa_sg(vrf->info, vty, src_ip, grp_ip, uj); + else if (src_ip) + ip_msdp_show_sa_addr(vrf->info, vty, src_ip, uj); + else + ip_msdp_show_sa(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN (show_ip_msdp_sa_sg_vrf_all, + show_ip_msdp_sa_sg_vrf_all_cmd, + "show ip msdp vrf all sa [A.B.C.D [A.B.C.D]] [json]", + SHOW_STR + IP_STR + MSDP_STR + VRF_CMD_HELP_STR + "MSDP active-source information\n" + "source or group ip\n" + "group ip\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + bool first = true; + int idx = 2; + + char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? argv[idx++]->arg + : NULL; + char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) + ? argv[idx]->arg + : NULL; + + if (uj) + vty_out(vty, "{ "); + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (uj) { + if (!first) + vty_out(vty, ", "); + vty_out(vty, " \"%s\": ", vrf->name); + first = false; + } else + vty_out(vty, "VRF: %s\n", vrf->name); + + if (src_ip && grp_ip) + ip_msdp_show_sa_sg(vrf->info, vty, src_ip, grp_ip, uj); + else if (src_ip) + ip_msdp_show_sa_addr(vrf->info, vty, src_ip, uj); + else + ip_msdp_show_sa(vrf->info, vty, uj); + } + if (uj) + vty_out(vty, "}\n"); + + return CMD_SUCCESS; +} + +struct pim_sg_cache_walk_data { + struct vty *vty; + json_object *json; + json_object *json_group; + struct in_addr addr; + bool addr_match; +}; + +static void pim_show_vxlan_sg_entry(struct pim_vxlan_sg *vxlan_sg, + struct pim_sg_cache_walk_data *cwd) +{ + struct vty *vty = cwd->vty; + json_object *json = cwd->json; + json_object *json_row; + bool installed = (vxlan_sg->up) ? true : false; + const char *iif_name = vxlan_sg->iif?vxlan_sg->iif->name:"-"; + const char *oif_name; + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + oif_name = vxlan_sg->orig_oif?vxlan_sg->orig_oif->name:""; + else + oif_name = vxlan_sg->term_oif?vxlan_sg->term_oif->name:""; + + if (cwd->addr_match && pim_addr_cmp(vxlan_sg->sg.src, cwd->addr) && + pim_addr_cmp(vxlan_sg->sg.grp, cwd->addr)) { + return; + } + if (json) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &vxlan_sg->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &vxlan_sg->sg.src); + + json_object_object_get_ex(json, grp_str, &cwd->json_group); + + if (!cwd->json_group) { + cwd->json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + cwd->json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "input", iif_name); + json_object_string_add(json_row, "output", oif_name); + if (installed) + json_object_boolean_true_add(json_row, "installed"); + else + json_object_boolean_false_add(json_row, "installed"); + json_object_object_add(cwd->json_group, src_str, json_row); + } else { + vty_out(vty, "%-15pPAs %-15pPAs %-15s %-15s %-5s\n", + &vxlan_sg->sg.src, &vxlan_sg->sg.grp, iif_name, + oif_name, installed ? "I" : ""); + } +} + +static void pim_show_vxlan_sg_hash_entry(struct hash_bucket *bucket, void *arg) +{ + pim_show_vxlan_sg_entry((struct pim_vxlan_sg *)bucket->data, + (struct pim_sg_cache_walk_data *)arg); +} + +static void pim_show_vxlan_sg(struct pim_instance *pim, + struct vty *vty, bool uj) +{ + json_object *json = NULL; + struct pim_sg_cache_walk_data cwd; + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, "Codes: I -> installed\n"); + vty_out(vty, + "Source Group Input Output Flags\n"); + } + + memset(&cwd, 0, sizeof(cwd)); + cwd.vty = vty; + cwd.json = json; + hash_iterate(pim->vxlan.sg_hash, pim_show_vxlan_sg_hash_entry, &cwd); + + if (uj) + vty_json(vty, json); +} + +static void pim_show_vxlan_sg_match_addr(struct pim_instance *pim, + struct vty *vty, char *addr_str, + bool uj) +{ + json_object *json = NULL; + struct pim_sg_cache_walk_data cwd; + int result = 0; + + memset(&cwd, 0, sizeof(cwd)); + result = inet_pton(AF_INET, addr_str, &cwd.addr); + if (result <= 0) { + vty_out(vty, "Bad address %s: errno=%d: %s\n", addr_str, + errno, safe_strerror(errno)); + return; + } + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, "Codes: I -> installed\n"); + vty_out(vty, + "Source Group Input Output Flags\n"); + } + + cwd.vty = vty; + cwd.json = json; + cwd.addr_match = true; + hash_iterate(pim->vxlan.sg_hash, pim_show_vxlan_sg_hash_entry, &cwd); + + if (uj) + vty_json(vty, json); +} + +static void pim_show_vxlan_sg_one(struct pim_instance *pim, + struct vty *vty, char *src_str, char *grp_str, + bool uj) +{ + json_object *json = NULL; + pim_sgaddr sg; + int result = 0; + struct pim_vxlan_sg *vxlan_sg; + const char *iif_name; + bool installed; + const char *oif_name; + + result = inet_pton(AF_INET, src_str, &sg.src); + if (result <= 0) { + vty_out(vty, "Bad src address %s: errno=%d: %s\n", src_str, + errno, safe_strerror(errno)); + return; + } + result = inet_pton(AF_INET, grp_str, &sg.grp); + if (result <= 0) { + vty_out(vty, "Bad grp address %s: errno=%d: %s\n", grp_str, + errno, safe_strerror(errno)); + return; + } + + if (uj) + json = json_object_new_object(); + + vxlan_sg = pim_vxlan_sg_find(pim, &sg); + if (vxlan_sg) { + installed = (vxlan_sg->up) ? true : false; + iif_name = vxlan_sg->iif?vxlan_sg->iif->name:"-"; + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + oif_name = + vxlan_sg->orig_oif?vxlan_sg->orig_oif->name:""; + else + oif_name = + vxlan_sg->term_oif?vxlan_sg->term_oif->name:""; + + if (uj) { + json_object_string_add(json, "source", src_str); + json_object_string_add(json, "group", grp_str); + json_object_string_add(json, "input", iif_name); + json_object_string_add(json, "output", oif_name); + if (installed) + json_object_boolean_true_add(json, "installed"); + else + json_object_boolean_false_add(json, + "installed"); + } else { + vty_out(vty, "SG : %s\n", vxlan_sg->sg_str); + vty_out(vty, " Input : %s\n", iif_name); + vty_out(vty, " Output : %s\n", oif_name); + vty_out(vty, " installed : %s\n", + installed?"yes":"no"); + } + } + + if (uj) + vty_json(vty, json); +} + +DEFUN (show_ip_pim_vxlan_sg, + show_ip_pim_vxlan_sg_cmd, + "show ip pim [vrf NAME] vxlan-groups [A.B.C.D [A.B.C.D]] [json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "VxLAN BUM groups\n" + "source or group ip\n" + "group ip\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + int idx = 2; + + vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + char *src_ip = argv_find(argv, argc, "A.B.C.D", &idx) ? + argv[idx++]->arg:NULL; + char *grp_ip = idx < argc && argv_find(argv, argc, "A.B.C.D", &idx) ? + argv[idx]->arg:NULL; + + if (src_ip && grp_ip) + pim_show_vxlan_sg_one(vrf->info, vty, src_ip, grp_ip, uj); + else if (src_ip) + pim_show_vxlan_sg_match_addr(vrf->info, vty, src_ip, uj); + else + pim_show_vxlan_sg(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +static void pim_show_vxlan_sg_work(struct pim_instance *pim, + struct vty *vty, bool uj) +{ + json_object *json = NULL; + struct pim_sg_cache_walk_data cwd; + struct listnode *node; + struct pim_vxlan_sg *vxlan_sg; + + if (uj) { + json = json_object_new_object(); + } else { + vty_out(vty, "Codes: I -> installed\n"); + vty_out(vty, + "Source Group Input Flags\n"); + } + + memset(&cwd, 0, sizeof(cwd)); + cwd.vty = vty; + cwd.json = json; + for (ALL_LIST_ELEMENTS_RO(pim_vxlan_p->work_list, node, vxlan_sg)) + pim_show_vxlan_sg_entry(vxlan_sg, &cwd); + + if (uj) + vty_json(vty, json); +} + +DEFUN_HIDDEN (show_ip_pim_vxlan_sg_work, + show_ip_pim_vxlan_sg_work_cmd, + "show ip pim [vrf NAME] vxlan-work [json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "VxLAN work list\n" + JSON_STR) +{ + bool uj = use_json(argc, argv); + struct vrf *vrf; + int idx = 2; + + vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx, uj); + + if (!vrf) + return CMD_WARNING; + + pim_show_vxlan_sg_work(vrf->info, vty, uj); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN (no_ip_pim_mlag, + no_ip_pim_mlag_cmd, + "no ip pim mlag", + NO_STR + IP_STR + PIM_STR + "MLAG\n") +{ + char mlag_xpath[XPATH_MAXLEN]; + + snprintf(mlag_xpath, sizeof(mlag_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", "default", "frr-routing:ipv4"); + strlcat(mlag_xpath, "/mlag", sizeof(mlag_xpath)); + + nb_cli_enqueue_change(vty, mlag_xpath, NB_OP_DESTROY, NULL); + + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_HIDDEN (ip_pim_mlag, + ip_pim_mlag_cmd, + "ip pim mlag INTERFACE role [primary|secondary] state [up|down] addr A.B.C.D", + IP_STR + PIM_STR + "MLAG\n" + "peerlink sub interface\n" + "MLAG role\n" + "MLAG role primary\n" + "MLAG role secondary\n" + "peer session state\n" + "peer session state up\n" + "peer session state down\n" + "configure PIP\n" + "unique ip address\n") +{ + int idx; + char mlag_peerlink_rif_xpath[XPATH_MAXLEN]; + char mlag_my_role_xpath[XPATH_MAXLEN]; + char mlag_peer_state_xpath[XPATH_MAXLEN]; + char mlag_reg_address_xpath[XPATH_MAXLEN]; + + snprintf(mlag_peerlink_rif_xpath, sizeof(mlag_peerlink_rif_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", "default", "frr-routing:ipv4"); + strlcat(mlag_peerlink_rif_xpath, "/mlag/peerlink-rif", + sizeof(mlag_peerlink_rif_xpath)); + + idx = 3; + nb_cli_enqueue_change(vty, mlag_peerlink_rif_xpath, NB_OP_MODIFY, + argv[idx]->arg); + + snprintf(mlag_my_role_xpath, sizeof(mlag_my_role_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", "default", "frr-routing:ipv4"); + strlcat(mlag_my_role_xpath, "/mlag/my-role", + sizeof(mlag_my_role_xpath)); + + idx += 2; + if (!strcmp(argv[idx]->arg, "primary")) { + nb_cli_enqueue_change(vty, mlag_my_role_xpath, NB_OP_MODIFY, + "MLAG_ROLE_PRIMARY"); + + } else if (!strcmp(argv[idx]->arg, "secondary")) { + nb_cli_enqueue_change(vty, mlag_my_role_xpath, NB_OP_MODIFY, + "MLAG_ROLE_SECONDARY"); + + } else { + vty_out(vty, "unknown MLAG role %s\n", argv[idx]->arg); + return CMD_WARNING; + } + + snprintf(mlag_peer_state_xpath, sizeof(mlag_peer_state_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", "default", "frr-routing:ipv4"); + strlcat(mlag_peer_state_xpath, "/mlag/peer-state", + sizeof(mlag_peer_state_xpath)); + + idx += 2; + if (!strcmp(argv[idx]->arg, "up")) { + nb_cli_enqueue_change(vty, mlag_peer_state_xpath, NB_OP_MODIFY, + "true"); + + } else if (strcmp(argv[idx]->arg, "down")) { + nb_cli_enqueue_change(vty, mlag_peer_state_xpath, NB_OP_MODIFY, + "false"); + + } else { + vty_out(vty, "unknown MLAG state %s\n", argv[idx]->arg); + return CMD_WARNING; + } + + snprintf(mlag_reg_address_xpath, sizeof(mlag_reg_address_xpath), + FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", "default", "frr-routing:ipv4"); + strlcat(mlag_reg_address_xpath, "/mlag/reg-address", + sizeof(mlag_reg_address_xpath)); + + idx += 2; + nb_cli_enqueue_change(vty, mlag_reg_address_xpath, NB_OP_MODIFY, + argv[idx]->arg); + + return nb_cli_apply_changes(vty, NULL); +} + +void pim_cmd_init(void) +{ + if_cmd_init(pim_interface_config_write); + + install_node(&debug_node); + + install_element(ENABLE_NODE, &pim_test_sg_keepalive_cmd); + + install_element(CONFIG_NODE, &ip_pim_rp_cmd); + install_element(VRF_NODE, &ip_pim_rp_cmd); + install_element(CONFIG_NODE, &no_ip_pim_rp_cmd); + install_element(VRF_NODE, &no_ip_pim_rp_cmd); + install_element(CONFIG_NODE, &ip_pim_rp_prefix_list_cmd); + install_element(VRF_NODE, &ip_pim_rp_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ip_pim_rp_prefix_list_cmd); + install_element(VRF_NODE, &no_ip_pim_rp_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ip_pim_ssm_prefix_list_cmd); + install_element(VRF_NODE, &no_ip_pim_ssm_prefix_list_cmd); + install_element(CONFIG_NODE, &no_ip_pim_ssm_prefix_list_name_cmd); + install_element(VRF_NODE, &no_ip_pim_ssm_prefix_list_name_cmd); + install_element(CONFIG_NODE, &ip_pim_ssm_prefix_list_cmd); + install_element(VRF_NODE, &ip_pim_ssm_prefix_list_cmd); + install_element(CONFIG_NODE, &ip_pim_register_suppress_cmd); + install_element(CONFIG_NODE, &no_ip_pim_register_suppress_cmd); + install_element(CONFIG_NODE, &ip_pim_spt_switchover_infinity_cmd); + install_element(VRF_NODE, &ip_pim_spt_switchover_infinity_cmd); + install_element(CONFIG_NODE, &ip_pim_spt_switchover_infinity_plist_cmd); + install_element(VRF_NODE, &ip_pim_spt_switchover_infinity_plist_cmd); + install_element(CONFIG_NODE, &no_ip_pim_spt_switchover_infinity_cmd); + install_element(VRF_NODE, &no_ip_pim_spt_switchover_infinity_cmd); + install_element(CONFIG_NODE, + &no_ip_pim_spt_switchover_infinity_plist_cmd); + install_element(VRF_NODE, &no_ip_pim_spt_switchover_infinity_plist_cmd); + install_element(CONFIG_NODE, &pim_register_accept_list_cmd); + install_element(VRF_NODE, &pim_register_accept_list_cmd); + install_element(CONFIG_NODE, &ip_pim_joinprune_time_cmd); + install_element(CONFIG_NODE, &no_ip_pim_joinprune_time_cmd); + install_element(CONFIG_NODE, &ip_pim_keep_alive_cmd); + install_element(VRF_NODE, &ip_pim_keep_alive_cmd); + install_element(CONFIG_NODE, &ip_pim_rp_keep_alive_cmd); + install_element(VRF_NODE, &ip_pim_rp_keep_alive_cmd); + install_element(CONFIG_NODE, &no_ip_pim_keep_alive_cmd); + install_element(VRF_NODE, &no_ip_pim_keep_alive_cmd); + install_element(CONFIG_NODE, &no_ip_pim_rp_keep_alive_cmd); + install_element(VRF_NODE, &no_ip_pim_rp_keep_alive_cmd); + install_element(CONFIG_NODE, &ip_pim_packets_cmd); + install_element(CONFIG_NODE, &no_ip_pim_packets_cmd); + install_element(CONFIG_NODE, &ip_pim_v6_secondary_cmd); + install_element(VRF_NODE, &ip_pim_v6_secondary_cmd); + install_element(CONFIG_NODE, &no_ip_pim_v6_secondary_cmd); + install_element(VRF_NODE, &no_ip_pim_v6_secondary_cmd); + install_element(CONFIG_NODE, &ip_ssmpingd_cmd); + install_element(VRF_NODE, &ip_ssmpingd_cmd); + install_element(CONFIG_NODE, &no_ip_ssmpingd_cmd); + install_element(VRF_NODE, &no_ip_ssmpingd_cmd); + install_element(CONFIG_NODE, &ip_msdp_peer_cmd); + install_element(VRF_NODE, &ip_msdp_peer_cmd); + install_element(CONFIG_NODE, &no_ip_msdp_peer_cmd); + install_element(VRF_NODE, &no_ip_msdp_peer_cmd); + install_element(CONFIG_NODE, &ip_pim_ecmp_cmd); + install_element(VRF_NODE, &ip_pim_ecmp_cmd); + install_element(CONFIG_NODE, &no_ip_pim_ecmp_cmd); + install_element(VRF_NODE, &no_ip_pim_ecmp_cmd); + install_element(CONFIG_NODE, &ip_pim_ecmp_rebalance_cmd); + install_element(VRF_NODE, &ip_pim_ecmp_rebalance_cmd); + install_element(CONFIG_NODE, &no_ip_pim_ecmp_rebalance_cmd); + install_element(VRF_NODE, &no_ip_pim_ecmp_rebalance_cmd); + install_element(CONFIG_NODE, &ip_pim_mlag_cmd); + install_element(CONFIG_NODE, &no_ip_pim_mlag_cmd); + install_element(CONFIG_NODE, &ip_igmp_group_watermark_cmd); + install_element(VRF_NODE, &ip_igmp_group_watermark_cmd); + install_element(CONFIG_NODE, &no_ip_igmp_group_watermark_cmd); + install_element(VRF_NODE, &no_ip_igmp_group_watermark_cmd); + + install_element(INTERFACE_NODE, &interface_ip_igmp_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_igmp_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_join_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_igmp_join_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_version_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_igmp_version_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_query_interval_cmd); + install_element(INTERFACE_NODE, + &interface_no_ip_igmp_query_interval_cmd); + install_element(INTERFACE_NODE, + &interface_ip_igmp_query_max_response_time_cmd); + install_element(INTERFACE_NODE, + &interface_no_ip_igmp_query_max_response_time_cmd); + install_element(INTERFACE_NODE, + &interface_ip_igmp_query_max_response_time_dsec_cmd); + install_element(INTERFACE_NODE, + &interface_no_ip_igmp_query_max_response_time_dsec_cmd); + install_element(INTERFACE_NODE, + &interface_ip_igmp_last_member_query_count_cmd); + install_element(INTERFACE_NODE, + &interface_no_ip_igmp_last_member_query_count_cmd); + install_element(INTERFACE_NODE, + &interface_ip_igmp_last_member_query_interval_cmd); + install_element(INTERFACE_NODE, + &interface_no_ip_igmp_last_member_query_interval_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_activeactive_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_ssm_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_sm_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_sm_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_drprio_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_drprio_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_hello_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_hello_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_boundary_oil_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_boundary_oil_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_query_generate_cmd); + + // Static mroutes NEB + install_element(INTERFACE_NODE, &interface_ip_mroute_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_mroute_cmd); + + install_element(VIEW_NODE, &show_ip_igmp_interface_cmd); + install_element(VIEW_NODE, &show_ip_igmp_interface_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_igmp_join_cmd); + install_element(VIEW_NODE, &show_ip_igmp_join_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_igmp_groups_cmd); + install_element(VIEW_NODE, &show_ip_igmp_groups_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_igmp_groups_retransmissions_cmd); + install_element(VIEW_NODE, &show_ip_igmp_sources_cmd); + install_element(VIEW_NODE, &show_ip_igmp_sources_retransmissions_cmd); + install_element(VIEW_NODE, &show_ip_igmp_statistics_cmd); + install_element(VIEW_NODE, &show_ip_pim_assert_cmd); + install_element(VIEW_NODE, &show_ip_pim_assert_internal_cmd); + install_element(VIEW_NODE, &show_ip_pim_assert_metric_cmd); + install_element(VIEW_NODE, &show_ip_pim_assert_winner_metric_cmd); + install_element(VIEW_NODE, &show_ip_pim_interface_traffic_cmd); + install_element(VIEW_NODE, &show_ip_pim_interface_cmd); + install_element(VIEW_NODE, &show_ip_pim_interface_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_join_cmd); + install_element(VIEW_NODE, &show_ip_pim_join_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_jp_agg_cmd); + install_element(VIEW_NODE, &show_ip_pim_local_membership_cmd); + install_element(VIEW_NODE, &show_ip_pim_mlag_summary_cmd); + install_element(VIEW_NODE, &show_ip_pim_mlag_up_cmd); + install_element(VIEW_NODE, &show_ip_pim_mlag_up_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_neighbor_cmd); + install_element(VIEW_NODE, &show_ip_pim_neighbor_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_rpf_cmd); + install_element(VIEW_NODE, &show_ip_pim_rpf_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_secondary_cmd); + install_element(VIEW_NODE, &show_ip_pim_state_cmd); + install_element(VIEW_NODE, &show_ip_pim_state_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_upstream_cmd); + install_element(VIEW_NODE, &show_ip_pim_upstream_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_channel_cmd); + install_element(VIEW_NODE, &show_ip_pim_upstream_join_desired_cmd); + install_element(VIEW_NODE, &show_ip_pim_upstream_rpf_cmd); + install_element(VIEW_NODE, &show_ip_pim_rp_cmd); + install_element(VIEW_NODE, &show_ip_pim_rp_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsr_cmd); + install_element(VIEW_NODE, &show_ip_multicast_cmd); + install_element(VIEW_NODE, &show_ip_multicast_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_multicast_count_cmd); + install_element(VIEW_NODE, &show_ip_multicast_count_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_mroute_cmd); + install_element(VIEW_NODE, &show_ip_mroute_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_mroute_count_cmd); + install_element(VIEW_NODE, &show_ip_mroute_count_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_mroute_summary_cmd); + install_element(VIEW_NODE, &show_ip_mroute_summary_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_rib_cmd); + install_element(VIEW_NODE, &show_ip_ssmpingd_cmd); + install_element(VIEW_NODE, &show_ip_pim_nexthop_cmd); + install_element(VIEW_NODE, &show_ip_pim_nexthop_lookup_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsrp_cmd); + install_element(VIEW_NODE, &show_ip_pim_bsm_db_cmd); + install_element(VIEW_NODE, &show_ip_pim_statistics_cmd); + + install_element(ENABLE_NODE, &clear_ip_mroute_count_cmd); + install_element(ENABLE_NODE, &clear_ip_interfaces_cmd); + install_element(ENABLE_NODE, &clear_ip_igmp_interfaces_cmd); + install_element(ENABLE_NODE, &clear_ip_mroute_cmd); + install_element(ENABLE_NODE, &clear_ip_pim_interfaces_cmd); + install_element(ENABLE_NODE, &clear_ip_pim_interface_traffic_cmd); + install_element(ENABLE_NODE, &clear_ip_pim_oil_cmd); + install_element(ENABLE_NODE, &clear_ip_pim_statistics_cmd); + install_element(ENABLE_NODE, &clear_ip_pim_bsr_db_cmd); + + install_element(ENABLE_NODE, &show_debugging_pim_cmd); + + install_element(ENABLE_NODE, &debug_igmp_cmd); + install_element(ENABLE_NODE, &no_debug_igmp_cmd); + install_element(ENABLE_NODE, &debug_igmp_events_cmd); + install_element(ENABLE_NODE, &no_debug_igmp_events_cmd); + install_element(ENABLE_NODE, &debug_igmp_packets_cmd); + install_element(ENABLE_NODE, &no_debug_igmp_packets_cmd); + install_element(ENABLE_NODE, &debug_igmp_trace_cmd); + install_element(ENABLE_NODE, &no_debug_igmp_trace_cmd); + install_element(ENABLE_NODE, &debug_igmp_trace_detail_cmd); + install_element(ENABLE_NODE, &no_debug_igmp_trace_detail_cmd); + install_element(ENABLE_NODE, &debug_mroute_cmd); + install_element(ENABLE_NODE, &debug_mroute_detail_cmd); + install_element(ENABLE_NODE, &no_debug_mroute_cmd); + install_element(ENABLE_NODE, &no_debug_mroute_detail_cmd); + install_element(ENABLE_NODE, &debug_pim_static_cmd); + install_element(ENABLE_NODE, &no_debug_pim_static_cmd); + install_element(ENABLE_NODE, &debug_pim_cmd); + install_element(ENABLE_NODE, &debug_pim_nht_cmd); + install_element(ENABLE_NODE, &debug_pim_nht_det_cmd); + install_element(ENABLE_NODE, &debug_pim_nht_rp_cmd); + install_element(ENABLE_NODE, &no_debug_pim_nht_rp_cmd); + install_element(ENABLE_NODE, &debug_pim_events_cmd); + install_element(ENABLE_NODE, &debug_pim_packets_cmd); + install_element(ENABLE_NODE, &debug_pim_packetdump_send_cmd); + install_element(ENABLE_NODE, &debug_pim_packetdump_recv_cmd); + install_element(ENABLE_NODE, &debug_pim_trace_cmd); + install_element(ENABLE_NODE, &debug_pim_trace_detail_cmd); + install_element(ENABLE_NODE, &debug_ssmpingd_cmd); + install_element(ENABLE_NODE, &no_debug_ssmpingd_cmd); + install_element(ENABLE_NODE, &debug_pim_zebra_cmd); + install_element(ENABLE_NODE, &debug_pim_mlag_cmd); + install_element(ENABLE_NODE, &no_debug_pim_mlag_cmd); + install_element(ENABLE_NODE, &debug_pim_vxlan_cmd); + install_element(ENABLE_NODE, &no_debug_pim_vxlan_cmd); + install_element(ENABLE_NODE, &debug_msdp_cmd); + install_element(ENABLE_NODE, &no_debug_msdp_cmd); + install_element(ENABLE_NODE, &debug_msdp_events_cmd); + install_element(ENABLE_NODE, &no_debug_msdp_events_cmd); + install_element(ENABLE_NODE, &debug_msdp_packets_cmd); + install_element(ENABLE_NODE, &no_debug_msdp_packets_cmd); + install_element(ENABLE_NODE, &debug_mtrace_cmd); + install_element(ENABLE_NODE, &no_debug_mtrace_cmd); + install_element(ENABLE_NODE, &debug_bsm_cmd); + install_element(ENABLE_NODE, &no_debug_bsm_cmd); + + install_element(CONFIG_NODE, &debug_igmp_cmd); + install_element(CONFIG_NODE, &no_debug_igmp_cmd); + install_element(CONFIG_NODE, &debug_igmp_events_cmd); + install_element(CONFIG_NODE, &no_debug_igmp_events_cmd); + install_element(CONFIG_NODE, &debug_igmp_packets_cmd); + install_element(CONFIG_NODE, &no_debug_igmp_packets_cmd); + install_element(CONFIG_NODE, &debug_igmp_trace_cmd); + install_element(CONFIG_NODE, &no_debug_igmp_trace_cmd); + install_element(CONFIG_NODE, &debug_igmp_trace_detail_cmd); + install_element(CONFIG_NODE, &no_debug_igmp_trace_detail_cmd); + install_element(CONFIG_NODE, &debug_mroute_cmd); + install_element(CONFIG_NODE, &debug_mroute_detail_cmd); + install_element(CONFIG_NODE, &no_debug_mroute_cmd); + install_element(CONFIG_NODE, &no_debug_mroute_detail_cmd); + install_element(CONFIG_NODE, &debug_pim_static_cmd); + install_element(CONFIG_NODE, &no_debug_pim_static_cmd); + install_element(CONFIG_NODE, &debug_pim_cmd); + install_element(CONFIG_NODE, &debug_pim_nht_cmd); + install_element(CONFIG_NODE, &debug_pim_nht_det_cmd); + install_element(CONFIG_NODE, &debug_pim_nht_rp_cmd); + install_element(CONFIG_NODE, &no_debug_pim_nht_rp_cmd); + install_element(CONFIG_NODE, &debug_pim_events_cmd); + install_element(CONFIG_NODE, &debug_pim_packets_cmd); + install_element(CONFIG_NODE, &debug_pim_packetdump_send_cmd); + install_element(CONFIG_NODE, &debug_pim_packetdump_recv_cmd); + install_element(CONFIG_NODE, &debug_pim_trace_cmd); + install_element(CONFIG_NODE, &debug_pim_trace_detail_cmd); + install_element(CONFIG_NODE, &debug_ssmpingd_cmd); + install_element(CONFIG_NODE, &no_debug_ssmpingd_cmd); + install_element(CONFIG_NODE, &debug_pim_zebra_cmd); + install_element(CONFIG_NODE, &debug_pim_mlag_cmd); + install_element(CONFIG_NODE, &no_debug_pim_mlag_cmd); + install_element(CONFIG_NODE, &debug_pim_vxlan_cmd); + install_element(CONFIG_NODE, &no_debug_pim_vxlan_cmd); + install_element(CONFIG_NODE, &debug_msdp_cmd); + install_element(CONFIG_NODE, &no_debug_msdp_cmd); + install_element(CONFIG_NODE, &debug_msdp_events_cmd); + install_element(CONFIG_NODE, &no_debug_msdp_events_cmd); + install_element(CONFIG_NODE, &debug_msdp_packets_cmd); + install_element(CONFIG_NODE, &no_debug_msdp_packets_cmd); + install_element(CONFIG_NODE, &debug_mtrace_cmd); + install_element(CONFIG_NODE, &no_debug_mtrace_cmd); + install_element(CONFIG_NODE, &debug_bsm_cmd); + install_element(CONFIG_NODE, &no_debug_bsm_cmd); + + install_element(CONFIG_NODE, &ip_msdp_timers_cmd); + install_element(VRF_NODE, &ip_msdp_timers_cmd); + install_element(CONFIG_NODE, &no_ip_msdp_timers_cmd); + install_element(VRF_NODE, &no_ip_msdp_timers_cmd); + install_element(CONFIG_NODE, &ip_msdp_mesh_group_member_cmd); + install_element(VRF_NODE, &ip_msdp_mesh_group_member_cmd); + install_element(CONFIG_NODE, &no_ip_msdp_mesh_group_member_cmd); + install_element(VRF_NODE, &no_ip_msdp_mesh_group_member_cmd); + install_element(CONFIG_NODE, &ip_msdp_mesh_group_source_cmd); + install_element(VRF_NODE, &ip_msdp_mesh_group_source_cmd); + install_element(CONFIG_NODE, &no_ip_msdp_mesh_group_source_cmd); + install_element(VRF_NODE, &no_ip_msdp_mesh_group_source_cmd); + install_element(CONFIG_NODE, &no_ip_msdp_mesh_group_cmd); + install_element(VRF_NODE, &no_ip_msdp_mesh_group_cmd); + install_element(VIEW_NODE, &show_ip_msdp_peer_detail_cmd); + install_element(VIEW_NODE, &show_ip_msdp_peer_detail_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_msdp_sa_detail_cmd); + install_element(VIEW_NODE, &show_ip_msdp_sa_detail_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_msdp_sa_sg_cmd); + install_element(VIEW_NODE, &show_ip_msdp_sa_sg_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_msdp_mesh_group_cmd); + install_element(VIEW_NODE, &show_ip_msdp_mesh_group_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_ssm_range_cmd); + install_element(VIEW_NODE, &show_ip_pim_group_type_cmd); + install_element(VIEW_NODE, &show_ip_pim_vxlan_sg_cmd); + install_element(VIEW_NODE, &show_ip_pim_vxlan_sg_work_cmd); + install_element(INTERFACE_NODE, &interface_pim_use_source_cmd); + install_element(INTERFACE_NODE, &interface_no_pim_use_source_cmd); + /* Install BSM command */ + install_element(INTERFACE_NODE, &ip_pim_bsm_cmd); + install_element(INTERFACE_NODE, &no_ip_pim_bsm_cmd); + install_element(INTERFACE_NODE, &ip_pim_ucast_bsm_cmd); + install_element(INTERFACE_NODE, &no_ip_pim_ucast_bsm_cmd); + /* Install BFD command */ + install_element(INTERFACE_NODE, &ip_pim_bfd_cmd); + install_element(INTERFACE_NODE, &ip_pim_bfd_param_cmd); + install_element(INTERFACE_NODE, &no_ip_pim_bfd_profile_cmd); + install_element(INTERFACE_NODE, &no_ip_pim_bfd_cmd); +#if HAVE_BFDD == 0 + install_element(INTERFACE_NODE, &no_ip_pim_bfd_param_cmd); +#endif /* !HAVE_BFDD */ +} diff --git a/pimd/pim_cmd.h b/pimd/pim_cmd.h new file mode 100644 index 0000000..d39d77c --- /dev/null +++ b/pimd/pim_cmd.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_CMD_H +#define PIM_CMD_H + +#define PIM_STR "PIM information\n" +#define IGMP_STR "IGMP information\n" +#define IGMP_GROUP_STR "IGMP groups information\n" +#define IGMP_SOURCE_STR "IGMP sources information\n" +#define CONF_SSMPINGD_STR "Enable ssmpingd operation\n" +#define SHOW_SSMPINGD_STR "ssmpingd operation\n" +#define IFACE_PIM_STR "Enable PIM SSM operation\n" +#define IFACE_PIM_SM_STR "Enable PIM SM operation\n" +#define IFACE_PIM_HELLO_STR "Hello Interval\n" +#define IFACE_PIM_HELLO_TIME_STR "Time in seconds for Hello Interval\n" +#define IFACE_PIM_HELLO_HOLD_STR "Time in seconds for Hold Interval\n" +#define IFACE_IGMP_STR "Enable IGMP operation\n" +#define IFACE_IGMP_QUERY_INTERVAL_STR "IGMP host query interval\n" +#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "IGMP max query response value (seconds)\n" +#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "IGMP max query response value (deciseconds)\n" +#define IFACE_IGMP_LAST_MEMBER_QUERY_INTERVAL_STR "IGMP last member query interval\n" +#define IFACE_IGMP_LAST_MEMBER_QUERY_COUNT_STR "IGMP last member query count\n" +#define DEBUG_IGMP_STR "IGMP protocol activity\n" +#define DEBUG_IGMP_EVENTS_STR "IGMP protocol events\n" +#define DEBUG_IGMP_PACKETS_STR "IGMP protocol packets\n" +#define DEBUG_IGMP_TRACE_STR "IGMP internal daemon activity\n" +#define DEBUG_MROUTE_STR "PIM interaction with kernel MFC cache\n" +#define DEBUG_STATIC_STR "PIM Static Multicast Route activity\n" +#define DEBUG_PIM_STR "PIM protocol activity\n" +#define DEBUG_PIM_EVENTS_STR "PIM protocol events\n" +#define DEBUG_PIM_PACKETS_STR "PIM protocol packets\n" +#define DEBUG_PIM_HELLO_PACKETS_STR "PIM Hello protocol packets\n" +#define DEBUG_PIM_J_P_PACKETS_STR "PIM Join/Prune protocol packets\n" +#define DEBUG_PIM_PIM_REG_PACKETS_STR "PIM Register/Reg-Stop protocol packets\n" +#define DEBUG_PIM_PACKETDUMP_STR "PIM packet dump\n" +#define DEBUG_PIM_PACKETDUMP_SEND_STR "Dump sent packets\n" +#define DEBUG_PIM_PACKETDUMP_RECV_STR "Dump received packets\n" +#define DEBUG_PIM_TRACE_STR "PIM internal daemon activity\n" +#define DEBUG_PIM_ZEBRA_STR "ZEBRA protocol activity\n" +#define DEBUG_PIM_MLAG_STR "PIM Mlag activity\n" +#define DEBUG_PIM_VXLAN_STR "PIM VxLAN events\n" +#define DEBUG_SSMPINGD_STR "ssmpingd activity\n" +#define CLEAR_IP_IGMP_STR "IGMP clear commands\n" +#define CLEAR_IP_PIM_STR "PIM clear commands\n" +#define MROUTE_STR "IP multicast routing table\n" +#define RIB_STR "IP unicast routing table\n" +#define CFG_MSDP_STR "Configure multicast source discovery protocol\n" +#define MSDP_STR "MSDP information\n" +#define DEBUG_MSDP_STR "MSDP protocol activity\n" +#define DEBUG_MSDP_EVENTS_STR "MSDP protocol events\n" +#define DEBUG_MSDP_INTERNAL_STR "MSDP protocol internal\n" +#define DEBUG_MSDP_PACKETS_STR "MSDP protocol packets\n" +#define DEBUG_MTRACE_STR "Mtrace protocol activity\n" +#define DEBUG_PIM_BSM_STR "BSR message processing activity\n" + + +void pim_cmd_init(void); + +#define PIM_TIME_STRLEN 10 +#endif /* PIM_CMD_H */ diff --git a/pimd/pim_cmd_common.c b/pimd/pim_cmd_common.c new file mode 100644 index 0000000..5e50a09 --- /dev/null +++ b/pimd/pim_cmd_common.c @@ -0,0 +1,5707 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for IPv6 FRR + * Copyright (C) 2022 Vmware, Inc. + * Mobashshera Rasool + */ + +#include +#include + +#include "lib/json.h" +#include "command.h" +#include "if.h" +#include "prefix.h" +#include "zclient.h" +#include "plist.h" +#include "hash.h" +#include "nexthop.h" +#include "vrf.h" +#include "ferr.h" +#include "lib/srcdest_table.h" +#include "lib/linklist.h" +#include "termtable.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_vty.h" +#include "lib/northbound_cli.h" +#include "pim_errors.h" +#include "pim_nb.h" +#include "pim_mroute.h" +#include "pim_cmd.h" +#include "pim6_cmd.h" +#include "pim_cmd_common.h" +#include "pim_time.h" +#include "pim_zebra.h" +#include "pim_zlookup.h" +#include "pim_iface.h" +#include "pim_macro.h" +#include "pim_neighbor.h" +#include "pim_nht.h" +#include "pim_sock.h" +#include "pim_ssm.h" +#include "pim_static.h" +#include "pim_addr.h" +#include "pim_static.h" +#include "pim_util.h" +#include "pim6_mld.h" + +/** + * Get current node VRF name. + * + * NOTE: + * In case of failure it will print error message to user. + * + * \returns name or NULL if failed to get VRF. + */ +const char *pim_cli_get_vrf_name(struct vty *vty) +{ + const struct lyd_node *vrf_node; + + /* Not inside any VRF context. */ + if (vty->xpath_index == 0) + return VRF_DEFAULT_NAME; + + vrf_node = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH); + if (vrf_node == NULL) { + vty_out(vty, "%% Failed to get vrf dnode in configuration\n"); + return NULL; + } + + return yang_dnode_get_string(vrf_node, "name"); +} + +int pim_process_join_prune_cmd(struct vty *vty, const char *jpi_str) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/join-prune-interval", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, jpi_str); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_join_prune_cmd(struct vty *vty) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/join-prune-interval", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_spt_switchover_infinity_cmd(struct vty *vty) +{ + const char *vrfname; + char spt_plist_xpath[XPATH_MAXLEN]; + char spt_action_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(spt_plist_xpath, sizeof(spt_plist_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_plist_xpath, "/spt-switchover/spt-infinity-prefix-list", + sizeof(spt_plist_xpath)); + + snprintf(spt_action_xpath, sizeof(spt_action_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_action_xpath, "/spt-switchover/spt-action", + sizeof(spt_action_xpath)); + + if (yang_dnode_exists(vty->candidate_config->dnode, spt_plist_xpath)) + nb_cli_enqueue_change(vty, spt_plist_xpath, NB_OP_DESTROY, + NULL); + nb_cli_enqueue_change(vty, spt_action_xpath, NB_OP_MODIFY, + "PIM_SPT_INFINITY"); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_spt_switchover_prefixlist_cmd(struct vty *vty, + const char *plist) +{ + const char *vrfname; + char spt_plist_xpath[XPATH_MAXLEN]; + char spt_action_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(spt_plist_xpath, sizeof(spt_plist_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_plist_xpath, "/spt-switchover/spt-infinity-prefix-list", + sizeof(spt_plist_xpath)); + + snprintf(spt_action_xpath, sizeof(spt_action_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_action_xpath, "/spt-switchover/spt-action", + sizeof(spt_action_xpath)); + + nb_cli_enqueue_change(vty, spt_action_xpath, NB_OP_MODIFY, + "PIM_SPT_INFINITY"); + nb_cli_enqueue_change(vty, spt_plist_xpath, NB_OP_MODIFY, + plist); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_spt_switchover_cmd(struct vty *vty) +{ + const char *vrfname; + char spt_plist_xpath[XPATH_MAXLEN]; + char spt_action_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(spt_plist_xpath, sizeof(spt_plist_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_plist_xpath, "/spt-switchover/spt-infinity-prefix-list", + sizeof(spt_plist_xpath)); + + snprintf(spt_action_xpath, sizeof(spt_action_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(spt_action_xpath, "/spt-switchover/spt-action", + sizeof(spt_action_xpath)); + + nb_cli_enqueue_change(vty, spt_plist_xpath, NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, spt_action_xpath, NB_OP_MODIFY, + "PIM_SPT_IMMEDIATE"); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_pim_packet_cmd(struct vty *vty, const char *packet) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/packets", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, packet); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_pim_packet_cmd(struct vty *vty) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/packets", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_keepalivetimer_cmd(struct vty *vty, const char *kat) +{ + const char *vrfname; + char ka_timer_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ka_timer_xpath, sizeof(ka_timer_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL); + strlcat(ka_timer_xpath, "/keep-alive-timer", sizeof(ka_timer_xpath)); + + nb_cli_enqueue_change(vty, ka_timer_xpath, NB_OP_MODIFY, + kat); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_keepalivetimer_cmd(struct vty *vty) +{ + const char *vrfname; + char ka_timer_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ka_timer_xpath, sizeof(ka_timer_xpath), FRR_PIM_VRF_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL); + strlcat(ka_timer_xpath, "/keep-alive-timer", sizeof(ka_timer_xpath)); + + nb_cli_enqueue_change(vty, ka_timer_xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_rp_kat_cmd(struct vty *vty, const char *rpkat) +{ + const char *vrfname; + char rp_ka_timer_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_ka_timer_xpath, sizeof(rp_ka_timer_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(rp_ka_timer_xpath, "/rp-keep-alive-timer", + sizeof(rp_ka_timer_xpath)); + + nb_cli_enqueue_change(vty, rp_ka_timer_xpath, NB_OP_MODIFY, + rpkat); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_rp_kat_cmd(struct vty *vty) +{ + const char *vrfname; + char rp_ka_timer[6]; + char rp_ka_timer_xpath[XPATH_MAXLEN]; + uint v; + char rs_timer_xpath[XPATH_MAXLEN]; + + snprintf(rs_timer_xpath, sizeof(rs_timer_xpath), + FRR_PIM_ROUTER_XPATH, FRR_PIM_AF_XPATH_VAL); + strlcat(rs_timer_xpath, "/register-suppress-time", + sizeof(rs_timer_xpath)); + + /* RFC4601 */ + /* Check if register suppress time is configured or assigned + * the default register suppress time. + */ + if (yang_dnode_exists(vty->candidate_config->dnode, rs_timer_xpath)) + v = yang_dnode_get_uint16(vty->candidate_config->dnode, "%s", + rs_timer_xpath); + else + v = PIM_REGISTER_SUPPRESSION_TIME_DEFAULT; + + v = 3 * v + PIM_REGISTER_PROBE_TIME_DEFAULT; + if (v > UINT16_MAX) + v = UINT16_MAX; + snprintf(rp_ka_timer, sizeof(rp_ka_timer), "%u", v); + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_ka_timer_xpath, sizeof(rp_ka_timer_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + strlcat(rp_ka_timer_xpath, "/rp-keep-alive-timer", + sizeof(rp_ka_timer_xpath)); + + nb_cli_enqueue_change(vty, rp_ka_timer_xpath, NB_OP_MODIFY, + rp_ka_timer); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_register_suppress_cmd(struct vty *vty, const char *rst) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/register-suppress-time", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, rst); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_register_suppress_cmd(struct vty *vty) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), FRR_PIM_ROUTER_XPATH, + FRR_PIM_AF_XPATH_VAL); + strlcat(xpath, "/register-suppress-time", sizeof(xpath)); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_ip_pim_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_pim_passive_cmd(struct vty *vty, bool enable) +{ + if (enable) + nb_cli_enqueue_change(vty, "./pim-passive-enable", NB_OP_MODIFY, + "true"); + else + nb_cli_enqueue_change(vty, "./pim-passive-enable", NB_OP_MODIFY, + "false"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_ip_pim_cmd(struct vty *vty) +{ + const struct lyd_node *mld_enable_dnode; + char mld_if_xpath[XPATH_MAXLEN]; + + int printed = + snprintf(mld_if_xpath, sizeof(mld_if_xpath), + "%s/frr-gmp:gmp/address-family[address-family='%s']", + VTY_CURR_XPATH, FRR_PIM_AF_XPATH_VAL); + + if (printed >= (int)(sizeof(mld_if_xpath))) { + vty_out(vty, "Xpath too long (%d > %u)", printed + 1, + XPATH_MAXLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + mld_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + + if (!mld_enable_dnode) { + nb_cli_enqueue_change(vty, mld_if_xpath, NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else { + if (!yang_dnode_get_bool(mld_enable_dnode, ".")) { + nb_cli_enqueue_change(vty, mld_if_xpath, NB_OP_DESTROY, + NULL); + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + } else + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "false"); + } + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_pim_drprio_cmd(struct vty *vty, const char *drpriority_str) +{ + nb_cli_enqueue_change(vty, "./dr-priority", NB_OP_MODIFY, + drpriority_str); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_ip_pim_drprio_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./dr-priority", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_pim_hello_cmd(struct vty *vty, const char *hello_str, + const char *hold_str) +{ + const struct lyd_node *mld_enable_dnode; + + mld_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + + if (!mld_enable_dnode) { + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } else { + if (!yang_dnode_get_bool(mld_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./hello-interval", NB_OP_MODIFY, hello_str); + + if (hold_str) + nb_cli_enqueue_change(vty, "./hello-holdtime", NB_OP_MODIFY, + hold_str); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_ip_pim_hello_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./hello-interval", NB_OP_DESTROY, NULL); + nb_cli_enqueue_change(vty, "./hello-holdtime", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_pim_activeactive_cmd(struct vty *vty, const char *no) +{ + if (no) + nb_cli_enqueue_change(vty, "./active-active", NB_OP_MODIFY, + "false"); + else { + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + + nb_cli_enqueue_change(vty, "./active-active", NB_OP_MODIFY, + "true"); + } + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_pim_boundary_oil_cmd(struct vty *vty, const char *oil) +{ + nb_cli_enqueue_change(vty, "./multicast-boundary-oil", NB_OP_MODIFY, + oil); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_ip_pim_boundary_oil_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./multicast-boundary-oil", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ip_mroute_cmd(struct vty *vty, const char *interface, + const char *group_str, const char *source_str) +{ + nb_cli_enqueue_change(vty, "./oif", NB_OP_MODIFY, interface); + + if (!source_str) { + char buf[SRCDEST2STR_BUFFER]; + + inet_ntop(AF_INET6, &in6addr_any, buf, sizeof(buf)); + return nb_cli_apply_changes(vty, FRR_PIM_MROUTE_XPATH, + FRR_PIM_AF_XPATH_VAL, buf, + group_str); + } + + return nb_cli_apply_changes(vty, FRR_PIM_MROUTE_XPATH, + FRR_PIM_AF_XPATH_VAL, source_str, + group_str); +} + +int pim_process_no_ip_mroute_cmd(struct vty *vty, const char *interface, + const char *group_str, const char *source_str) +{ + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + if (!source_str) { + char buf[SRCDEST2STR_BUFFER]; + + inet_ntop(AF_INET6, &in6addr_any, buf, sizeof(buf)); + return nb_cli_apply_changes(vty, FRR_PIM_MROUTE_XPATH, + FRR_PIM_AF_XPATH_VAL, buf, + group_str); + } + + return nb_cli_apply_changes(vty, FRR_PIM_MROUTE_XPATH, + FRR_PIM_AF_XPATH_VAL, source_str, + group_str); +} + +int pim_process_rp_cmd(struct vty *vty, const char *rp_str, + const char *group_str) +{ + const char *vrfname; + char group_xpath[XPATH_MAXLEN]; + char rp_xpath[XPATH_MAXLEN]; + int printed; + int result = 0; + struct prefix group; + pim_addr rp_addr; + + result = str2prefix(group_str, &group); + if (result) { + struct prefix temp; + + prefix_copy(&temp, &group); + apply_mask(&temp); + if (!prefix_same(&group, &temp)) { + vty_out(vty, "%% Inconsistent address and mask: %s\n", + group_str); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (!result) { + vty_out(vty, "%% Bad group address specified: %s\n", group_str); + return CMD_WARNING_CONFIG_FAILED; + } + + result = inet_pton(PIM_AF, rp_str, &rp_addr); + if (result <= 0) { + vty_out(vty, "%% Bad RP address specified: %s\n", rp_str); + return CMD_WARNING_CONFIG_FAILED; + } + + if (pim_addr_is_any(rp_addr) || pim_addr_is_multicast(rp_addr)) { + vty_out(vty, "%% Bad RP address specified: %s\n", rp_str); + return CMD_WARNING_CONFIG_FAILED; + } + +#if PIM_IPV == 6 + if (IN6_IS_ADDR_LINKLOCAL(&rp_addr)) { + vty_out(vty, "%% Bad RP address specified: %s\n", rp_str); + return CMD_WARNING_CONFIG_FAILED; + } +#endif + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_xpath, sizeof(rp_xpath), FRR_PIM_STATIC_RP_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL, rp_str); + printed = snprintf(group_xpath, sizeof(group_xpath), + "%s/group-list[.='%s']", rp_xpath, group_str); + + if (printed >= (int)(sizeof(group_xpath))) { + vty_out(vty, "Xpath too long (%d > %u)", printed + 1, + XPATH_MAXLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, group_xpath, NB_OP_CREATE, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_rp_cmd(struct vty *vty, const char *rp_str, + const char *group_str) +{ + char group_xpath[XPATH_MAXLEN]; + char rp_xpath[XPATH_MAXLEN]; + int printed; + const char *vrfname; + const struct lyd_node *group_dnode; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_xpath, sizeof(rp_xpath), FRR_PIM_STATIC_RP_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL, rp_str); + printed = snprintf(group_xpath, sizeof(group_xpath), + "%s/group-list[.='%s']", rp_xpath, group_str); + + if (printed >= (int)(sizeof(group_xpath))) { + vty_out(vty, "Xpath too long (%d > %u)", printed + 1, + XPATH_MAXLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + group_dnode = yang_dnode_get(vty->candidate_config->dnode, group_xpath); + if (!group_dnode) { + vty_out(vty, "%% Unable to find specified RP\n"); + return NB_OK; + } + + if (yang_is_last_list_dnode(group_dnode)) + nb_cli_enqueue_change(vty, rp_xpath, NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, group_xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_rp_plist_cmd(struct vty *vty, const char *rp_str, + const char *prefix_list) +{ + const char *vrfname; + char rp_plist_xpath[XPATH_MAXLEN]; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_plist_xpath, sizeof(rp_plist_xpath), + FRR_PIM_STATIC_RP_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL, rp_str); + strlcat(rp_plist_xpath, "/prefix-list", sizeof(rp_plist_xpath)); + + nb_cli_enqueue_change(vty, rp_plist_xpath, NB_OP_MODIFY, prefix_list); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_rp_plist_cmd(struct vty *vty, const char *rp_str, + const char *prefix_list) +{ + char rp_xpath[XPATH_MAXLEN]; + char plist_xpath[XPATH_MAXLEN]; + const char *vrfname; + const struct lyd_node *plist_dnode; + const char *plist; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(rp_xpath, sizeof(rp_xpath), FRR_PIM_STATIC_RP_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL, rp_str); + + snprintf(plist_xpath, sizeof(plist_xpath), FRR_PIM_STATIC_RP_XPATH, + "frr-pim:pimd", "pim", vrfname, FRR_PIM_AF_XPATH_VAL, rp_str); + strlcat(plist_xpath, "/prefix-list", sizeof(plist_xpath)); + + plist_dnode = yang_dnode_get(vty->candidate_config->dnode, plist_xpath); + if (!plist_dnode) { + vty_out(vty, "%% Unable to find specified RP\n"); + return NB_OK; + } + + plist = yang_dnode_get_string(plist_dnode, "%s", plist_xpath); + if (strcmp(prefix_list, plist)) { + vty_out(vty, "%% Unable to find specified RP\n"); + return NB_OK; + } + + nb_cli_enqueue_change(vty, rp_xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +bool pim_sgaddr_match(pim_sgaddr item, pim_sgaddr match) +{ + return (pim_addr_is_any(match.grp) || + !pim_addr_cmp(match.grp, item.grp)) && + (pim_addr_is_any(match.src) || + !pim_addr_cmp(match.src, item.src)); +} + +void json_object_pim_ifp_add(struct json_object *json, struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + json_object_string_add(json, "name", ifp->name); + json_object_string_add(json, "state", if_is_up(ifp) ? "up" : "down"); + json_object_string_addf(json, "address", "%pPA", + &pim_ifp->primary_address); + json_object_int_add(json, "index", ifp->ifindex); + + if (if_is_multicast(ifp)) + json_object_boolean_true_add(json, "flagMulticast"); + + if (if_is_broadcast(ifp)) + json_object_boolean_true_add(json, "flagBroadcast"); + + if (ifp->flags & IFF_ALLMULTI) + json_object_boolean_true_add(json, "flagAllMulticast"); + + if (ifp->flags & IFF_PROMISC) + json_object_boolean_true_add(json, "flagPromiscuous"); + + if (PIM_IF_IS_DELETED(ifp)) + json_object_boolean_true_add(json, "flagDeleted"); + + if (pim_if_lan_delay_enabled(ifp)) + json_object_boolean_true_add(json, "lanDelayEnabled"); +} + +void pim_print_ifp_flags(struct vty *vty, struct interface *ifp) +{ + vty_out(vty, "Flags\n"); + vty_out(vty, "-----\n"); + vty_out(vty, "All Multicast : %s\n", + (ifp->flags & IFF_ALLMULTI) ? "yes" : "no"); + vty_out(vty, "Broadcast : %s\n", + if_is_broadcast(ifp) ? "yes" : "no"); + vty_out(vty, "Deleted : %s\n", + PIM_IF_IS_DELETED(ifp) ? "yes" : "no"); + vty_out(vty, "Interface Index : %d\n", ifp->ifindex); + vty_out(vty, "Multicast : %s\n", + if_is_multicast(ifp) ? "yes" : "no"); + vty_out(vty, "Promiscuous : %s\n", + (ifp->flags & IFF_PROMISC) ? "yes" : "no"); + vty_out(vty, "\n"); + vty_out(vty, "\n"); +} + +void json_object_pim_upstream_add(json_object *json, struct pim_upstream *up) +{ + json_object_boolean_add( + json, "drJoinDesired", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)); + json_object_boolean_add( + json, "drJoinDesiredUpdated", + CHECK_FLAG(up->flags, + PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)); + json_object_boolean_add( + json, "firstHopRouter", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_FHR)); + json_object_boolean_add( + json, "sourceIgmp", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_IGMP)); + json_object_boolean_add( + json, "sourcePim", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_PIM)); + json_object_boolean_add( + json, "sourceStream", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM)); + /* XXX: need to print ths flag in the plain text display as well */ + json_object_boolean_add( + json, "sourceMsdp", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_MSDP)); + json_object_boolean_add( + json, "sendSGRptPrune", + CHECK_FLAG(up->flags, + PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE)); + json_object_boolean_add( + json, "lastHopRouter", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_LHR)); + json_object_boolean_add( + json, "disableKATExpiry", + CHECK_FLAG(up->flags, + PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY)); + json_object_boolean_add( + json, "staticIncomingInterface", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_STATIC_IIF)); + json_object_boolean_add( + json, "allowIncomingInterfaceinOil", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL)); + json_object_boolean_add( + json, "noPimRegistrationData", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA)); + json_object_boolean_add( + json, "forcePimRegistration", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG)); + json_object_boolean_add( + json, "sourceVxlanOrigination", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG)); + json_object_boolean_add( + json, "sourceVxlanTermination", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM)); + json_object_boolean_add( + json, "mlagVxlan", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN)); + json_object_boolean_add( + json, "mlagNonDesignatedForwarder", + CHECK_FLAG(up->flags, PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF)); +} + +static const char * +pim_upstream_state2brief_str(enum pim_upstream_state join_state, + char *state_str, size_t state_str_len) +{ + switch (join_state) { + case PIM_UPSTREAM_NOTJOINED: + strlcpy(state_str, "NotJ", state_str_len); + break; + case PIM_UPSTREAM_JOINED: + strlcpy(state_str, "J", state_str_len); + break; + default: + strlcpy(state_str, "Unk", state_str_len); + } + return state_str; +} + +static const char *pim_reg_state2brief_str(enum pim_reg_state reg_state, + char *state_str, + size_t state_str_len) +{ + switch (reg_state) { + case PIM_REG_NOINFO: + strlcpy(state_str, "RegNI", state_str_len); + break; + case PIM_REG_JOIN: + strlcpy(state_str, "RegJ", state_str_len); + break; + case PIM_REG_JOIN_PENDING: + case PIM_REG_PRUNE: + strlcpy(state_str, "RegP", state_str_len); + break; + } + return state_str; +} + +void pim_show_rpf_refresh_stats(struct vty *vty, struct pim_instance *pim, + time_t now, json_object *json) +{ + char refresh_uptime[10]; + + pim_time_uptime_begin(refresh_uptime, sizeof(refresh_uptime), now, + pim->rpf_cache_refresh_last); + + if (json) { + json_object_int_add(json, "rpfCacheRefreshDelayMsecs", + router->rpf_cache_refresh_delay_msec); + json_object_int_add( + json, "rpfCacheRefreshTimer", + pim_time_timer_remain_msec(pim->rpf_cache_refresher)); + json_object_int_add(json, "rpfCacheRefreshRequests", + pim->rpf_cache_refresh_requests); + json_object_int_add(json, "rpfCacheRefreshEvents", + pim->rpf_cache_refresh_events); + json_object_string_add(json, "rpfCacheRefreshLast", + refresh_uptime); + json_object_int_add(json, "nexthopLookups", + pim->nexthop_lookups); + json_object_int_add(json, "nexthopLookupsAvoided", + pim->nexthop_lookups_avoided); + } else { + vty_out(vty, + "RPF Cache Refresh Delay: %ld msecs\n" + "RPF Cache Refresh Timer: %ld msecs\n" + "RPF Cache Refresh Requests: %lld\n" + "RPF Cache Refresh Events: %lld\n" + "RPF Cache Refresh Last: %s\n" + "Nexthop Lookups: %lld\n" + "Nexthop Lookups Avoided: %lld\n", + router->rpf_cache_refresh_delay_msec, + pim_time_timer_remain_msec(pim->rpf_cache_refresher), + (long long)pim->rpf_cache_refresh_requests, + (long long)pim->rpf_cache_refresh_events, + refresh_uptime, (long long)pim->nexthop_lookups, + (long long)pim->nexthop_lookups_avoided); + } +} + +void pim_show_rpf(struct pim_instance *pim, struct vty *vty, json_object *json) +{ + struct pim_upstream *up; + time_t now = pim_time_monotonic_sec(); + struct ttable *tt = NULL; + char *table = NULL; + json_object *json_group = NULL; + json_object *json_row = NULL; + + pim_show_rpf_refresh_stats(vty, pim, now, json); + + if (!json) { + vty_out(vty, "\n"); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Source|Group|RpfIface|RpfAddress|RibNextHop|Metric|Pref"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + const char *rpf_ifname; + struct pim_rpf *rpf = &up->rpf; + + rpf_ifname = + rpf->source_nexthop.interface ? rpf->source_nexthop + .interface->name + : ""; + + if (json) { + char grp_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &up->sg.src); + + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "rpfInterface", + rpf_ifname); + json_object_string_addf(json_row, "rpfAddress", "%pPA", + &rpf->rpf_addr); + json_object_string_addf( + json_row, "ribNexthop", "%pPAs", + &rpf->source_nexthop.mrib_nexthop_addr); + json_object_int_add( + json_row, "routeMetric", + rpf->source_nexthop.mrib_route_metric); + json_object_int_add( + json_row, "routePreference", + rpf->source_nexthop.mrib_metric_preference); + json_object_object_add(json_group, src_str, json_row); + + } else { + ttable_add_row( + tt, "%pPAs|%pPAs|%s|%pPA|%pPAs|%d|%d", + &up->sg.src, &up->sg.grp, rpf_ifname, + &rpf->rpf_addr, + &rpf->source_nexthop.mrib_nexthop_addr, + rpf->source_nexthop.mrib_route_metric, + rpf->source_nexthop.mrib_metric_preference); + } + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +void pim_show_neighbors_secondary(struct pim_instance *pim, struct vty *vty) +{ + struct interface *ifp; + struct ttable *tt = NULL; + char *table = NULL; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Interface|Address|Neighbor|Secondary"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; + pim_addr ifaddr; + struct listnode *neighnode; + struct pim_neighbor *neigh; + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (pim_ifp->pim_sock_fd < 0) + continue; + + ifaddr = pim_ifp->primary_address; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, + neigh)) { + struct listnode *prefix_node; + struct prefix *p; + + if (!neigh->prefix_list) + continue; + + for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, + prefix_node, p)) + ttable_add_row(tt, "%s|%pPAs|%pPAs|%pFX", + ifp->name, &ifaddr, + &neigh->source_addr, p); + } + } + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); +} + +void pim_show_state(struct pim_instance *pim, struct vty *vty, + const char *src_or_group, const char *group, + json_object *json) +{ + struct channel_oil *c_oil; +#if PIM_IPV != 4 + struct ttable *tt = NULL; + char *table = NULL; +#endif + char flag[50]; + json_object *json_group = NULL; + json_object *json_ifp_in = NULL; + json_object *json_ifp_out = NULL; + json_object *json_source = NULL; + time_t now; + int first_oif; + + now = pim_time_monotonic_sec(); + + if (!json) { + vty_out(vty, + "Codes: J -> Pim Join, I -> " GM " Report, S -> Source, * -> Inherited from (*,G), V -> VxLAN, M -> Muted\n"); +#if PIM_IPV == 4 + vty_out(vty, + "Active Source Group RPT IIF OIL\n"); +#else + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Active|Source|Group|RPT|IIF|OIL"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); +#endif + } + + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) { + char src_str[PIM_ADDRSTRLEN]; + char grp_str[PIM_ADDRSTRLEN]; + char in_ifname[IFNAMSIZ + 1]; + char out_ifname[IFNAMSIZ + 1]; + int oif_vif_index; + struct interface *ifp_in; + bool isRpt; + + first_oif = 1; + + if ((c_oil->up && + PIM_UPSTREAM_FLAG_TEST_USE_RPT(c_oil->up->flags)) || + pim_addr_is_any(*oil_origin(c_oil))) + isRpt = true; + else + isRpt = false; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + oil_mcastgrp(c_oil)); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + oil_origin(c_oil)); + ifp_in = pim_if_find_by_vif_index(pim, *oil_incoming_vif(c_oil)); + + if (ifp_in) + strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); + else + strlcpy(in_ifname, "", sizeof(in_ifname)); + + if (src_or_group) { + if (strcmp(src_or_group, src_str) && + strcmp(src_or_group, grp_str)) + continue; + + if (group && strcmp(group, grp_str)) + continue; + } + + if (json) { + + /* Find the group, create it if it doesn't exist */ + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + /* Find the source nested under the group, create it if + * it doesn't exist + */ + json_object_object_get_ex(json_group, src_str, + &json_source); + + if (!json_source) { + json_source = json_object_new_object(); + json_object_object_add(json_group, src_str, + json_source); + } + + /* Find the inbound interface nested under the source, + * create it if it doesn't exist + */ + json_object_object_get_ex(json_source, in_ifname, + &json_ifp_in); + + if (!json_ifp_in) { + json_ifp_in = json_object_new_object(); + json_object_object_add(json_source, in_ifname, + json_ifp_in); + json_object_int_add(json_source, "installed", + c_oil->installed); + json_object_boolean_add(json_source, "isRpt", + isRpt); + json_object_int_add(json_source, "refCount", + c_oil->oil_ref_count); + json_object_int_add(json_source, "oilListSize", + c_oil->oil_size); + json_object_int_add( + json_source, "oilRescan", + c_oil->oil_inherited_rescan); + json_object_int_add(json_source, "lastUsed", + c_oil->cc.lastused); + json_object_int_add(json_source, "packetCount", + c_oil->cc.pktcnt); + json_object_int_add(json_source, "byteCount", + c_oil->cc.bytecnt); + json_object_int_add(json_source, + "wrongInterface", + c_oil->cc.wrong_if); + } + } else +#if PIM_IPV == 4 + vty_out(vty, "%-6d %-15pPAs %-15pPAs %-3s %-16s ", + c_oil->installed, oil_origin(c_oil), + oil_mcastgrp(c_oil), isRpt ? "y" : "n", + in_ifname); +#else + /* Add a new row for c_oil with no OIF */ + ttable_add_row(tt, "%d|%pPAs|%pPAs|%s|%s|%c", + c_oil->installed, oil_origin(c_oil), + oil_mcastgrp(c_oil), isRpt ? "y" : "n", + in_ifname, ' '); +#endif + + for (oif_vif_index = 0; oif_vif_index < MAXVIFS; + ++oif_vif_index) { + struct interface *ifp_out; + char oif_uptime[10]; + int ttl; + + ttl = oil_if_has(c_oil, oif_vif_index); + if (ttl < 1) + continue; + + ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); + pim_time_uptime( + oif_uptime, sizeof(oif_uptime), + now - c_oil->oif_creation[oif_vif_index]); + + if (ifp_out) + strlcpy(out_ifname, ifp_out->name, + sizeof(out_ifname)); + else + strlcpy(out_ifname, "", + sizeof(out_ifname)); + + if (json) { + json_ifp_out = json_object_new_object(); + json_object_string_add(json_ifp_out, "source", + src_str); + json_object_string_add(json_ifp_out, "group", + grp_str); + json_object_string_add(json_ifp_out, + "inboundInterface", + in_ifname); + json_object_string_add(json_ifp_out, + "outboundInterface", + out_ifname); + json_object_int_add(json_ifp_out, "installed", + c_oil->installed); + + json_object_object_add(json_ifp_in, out_ifname, + json_ifp_out); + } else { + flag[0] = '\0'; + snprintf(flag, sizeof(flag), "(%c%c%c%c%c)", + (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_GM) + ? 'I' + : ' ', + (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_PIM) + ? 'J' + : ' ', + (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_VXLAN) + ? 'V' + : ' ', + (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_STAR) + ? '*' + : ' ', + (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_MUTE) + ? 'M' + : ' '); + + if (first_oif) { + first_oif = 0; +#if PIM_IPV == 4 + vty_out(vty, "%s%s", out_ifname, flag); +#else + /* OIF found. + * Delete the existing row for c_oil, + * with no OIF. + * Add a new row for c_oil with OIF and + * flag. + */ + ttable_del_row(tt, tt->nrows - 1); + ttable_add_row( + tt, "%d|%pPAs|%pPAs|%s|%s|%s%s", + c_oil->installed, + oil_origin(c_oil), + oil_mcastgrp(c_oil), + isRpt ? "y" : "n", in_ifname, + out_ifname, flag); +#endif + } else { +#if PIM_IPV == 4 + vty_out(vty, ", %s%s", out_ifname, + flag); +#else + ttable_add_row(tt, + "%c|%c|%c|%c|%c|%s%s", + ' ', ' ', ' ', ' ', ' ', + out_ifname, flag); +#endif + } + } + } +#if PIM_IPV == 4 + if (!json) + vty_out(vty, "\n"); +#endif + } + + /* Dump the generated table. */ + if (!json) { +#if PIM_IPV == 4 + vty_out(vty, "\n"); +#else + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); +#endif + } +} + +/* pim statistics - just adding only bsm related now. + * We can continue to add all pim related stats here. + */ +void pim_show_statistics(struct pim_instance *pim, struct vty *vty, + const char *ifname, bool uj) +{ + json_object *json = NULL; + struct interface *ifp; + + if (uj) { + json = json_object_new_object(); + json_object_int_add(json, "bsmRx", pim->bsm_rcvd); + json_object_int_add(json, "bsmTx", pim->bsm_sent); + json_object_int_add(json, "bsmDropped", pim->bsm_dropped); + } else { + vty_out(vty, "BSM Statistics :\n"); + vty_out(vty, "----------------\n"); + vty_out(vty, "Number of Received BSMs : %" PRIu64 "\n", + pim->bsm_rcvd); + vty_out(vty, "Number of Forwared BSMs : %" PRIu64 "\n", + pim->bsm_sent); + vty_out(vty, "Number of Dropped BSMs : %" PRIu64 "\n", + pim->bsm_dropped); + } + + vty_out(vty, "\n"); + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + + if (ifname && strcmp(ifname, ifp->name)) + continue; + + if (!pim_ifp) + continue; + + if (!uj) { + vty_out(vty, "Interface : %s\n", ifp->name); + vty_out(vty, "-------------------\n"); + vty_out(vty, + "Number of BSMs dropped due to config miss : %u\n", + pim_ifp->pim_ifstat_bsm_cfg_miss); + vty_out(vty, "Number of unicast BSMs dropped : %u\n", + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss); + vty_out(vty, + "Number of BSMs dropped due to invalid scope zone : %u\n", + pim_ifp->pim_ifstat_bsm_invalid_sz); + } else { + + json_object *json_row = NULL; + + json_row = json_object_new_object(); + + json_object_string_add(json_row, "If Name", ifp->name); + json_object_int_add(json_row, "bsmDroppedConfig", + pim_ifp->pim_ifstat_bsm_cfg_miss); + json_object_int_add( + json_row, "bsmDroppedUnicast", + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss); + json_object_int_add(json_row, + "bsmDroppedInvalidScopeZone", + pim_ifp->pim_ifstat_bsm_invalid_sz); + json_object_object_add(json, ifp->name, json_row); + } + vty_out(vty, "\n"); + } + + if (uj) + vty_json(vty, json); +} + +void pim_show_upstream(struct pim_instance *pim, struct vty *vty, + pim_sgaddr *sg, json_object *json) +{ + struct pim_upstream *up; + struct ttable *tt = NULL; + char *table = NULL; + time_t now; + json_object *json_group = NULL; + json_object *json_row = NULL; + + now = pim_time_monotonic_sec(); + + if (!json) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Iif|Source|Group|State|Uptime|JoinTimer|RSTimer|KATimer|RefCnt"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + char uptime[10]; + char join_timer[10]; + char rs_timer[10]; + char ka_timer[10]; + char msdp_reg_timer[10]; + char state_str[PIM_REG_STATE_STR_LEN]; + + if (!pim_sgaddr_match(up->sg, *sg)) + continue; + + pim_time_uptime(uptime, sizeof(uptime), + now - up->state_transition); + pim_time_timer_to_hhmmss(join_timer, sizeof(join_timer), + up->t_join_timer); + + /* + * If the upstream is not dummy and it has a J/P timer for the + * neighbor display that + */ + if (!up->t_join_timer && up->rpf.source_nexthop.interface) { + struct pim_neighbor *nbr; + + nbr = pim_neighbor_find( + up->rpf.source_nexthop.interface, + up->rpf.rpf_addr, false); + if (nbr) + pim_time_timer_to_hhmmss(join_timer, + sizeof(join_timer), + nbr->jp_timer); + } + + pim_time_timer_to_hhmmss(rs_timer, sizeof(rs_timer), + up->t_rs_timer); + pim_time_timer_to_hhmmss(ka_timer, sizeof(ka_timer), + up->t_ka_timer); + pim_time_timer_to_hhmmss(msdp_reg_timer, sizeof(msdp_reg_timer), + up->t_msdp_reg_timer); + + pim_upstream_state2brief_str(up->join_state, state_str, + sizeof(state_str)); + if (up->reg_state != PIM_REG_NOINFO) { + char tmp_str[PIM_REG_STATE_STR_LEN]; + char tmp[sizeof(state_str) + 1]; + + snprintf(tmp, sizeof(tmp), ",%s", + pim_reg_state2brief_str(up->reg_state, tmp_str, + sizeof(tmp_str))); + strlcat(state_str, tmp, sizeof(state_str)); + } + + if (json) { + char grp_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &up->sg.src); + + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_pim_upstream_add(json_row, up); + json_object_string_add( + json_row, "inboundInterface", + up->rpf.source_nexthop.interface + ? up->rpf.source_nexthop.interface->name + : "Unknown"); + + /* + * The RPF address we use is slightly different + * based upon what we are looking up. + * If we have a S, list that unless + * we are the FHR, else we just put + * the RP as the rpfAddress + */ + if (up->flags & PIM_UPSTREAM_FLAG_MASK_FHR || + pim_addr_is_any(up->sg.src)) { + struct pim_rpf *rpg; + + rpg = RP(pim, up->sg.grp); + json_object_string_addf(json_row, "rpfAddress", + "%pPA", &rpg->rpf_addr); + } else { + json_object_string_add(json_row, "rpfAddress", + src_str); + } + + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "state", state_str); + json_object_string_add( + json_row, "joinState", + pim_upstream_state2str(up->join_state)); + json_object_string_add( + json_row, "regState", + pim_reg_state2str(up->reg_state, state_str, + sizeof(state_str))); + json_object_string_add(json_row, "upTime", uptime); + json_object_string_add(json_row, "joinTimer", + join_timer); + json_object_string_add(json_row, "resetTimer", + rs_timer); + json_object_string_add(json_row, "keepaliveTimer", + ka_timer); + json_object_string_add(json_row, "msdpRegTimer", + msdp_reg_timer); + json_object_int_add(json_row, "refCount", + up->ref_count); + json_object_int_add(json_row, "sptBit", up->sptbit); + json_object_object_add(json_group, src_str, json_row); + } else { + ttable_add_row(tt, + "%s|%pPAs|%pPAs|%s|%s|%s|%s|%s|%d", + up->rpf.source_nexthop.interface + ? up->rpf.source_nexthop.interface->name + : "Unknown", + &up->sg.src, &up->sg.grp, state_str, uptime, + join_timer, rs_timer, ka_timer, up->ref_count); + } + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void pim_show_join_desired_helper(struct pim_instance *pim, + struct vty *vty, + struct pim_upstream *up, + json_object *json, bool uj, + struct ttable *tt) +{ + json_object *json_group = NULL; + json_object *json_row = NULL; + + if (uj) { + char grp_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &up->sg.src); + + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, json_group); + } + + json_row = json_object_new_object(); + json_object_pim_upstream_add(json_row, up); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + + if (pim_upstream_evaluate_join_desired(pim, up)) + json_object_boolean_true_add(json_row, + "evaluateJoinDesired"); + + json_object_object_add(json_group, src_str, json_row); + + } else { + ttable_add_row(tt, "%pPAs|%pPAs|%s", &up->sg.src, &up->sg.grp, + pim_upstream_evaluate_join_desired(pim, up) + ? "yes" + : "no"); + } +} + +void pim_show_join_desired(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct pim_upstream *up; + struct ttable *tt = NULL; + char *table = NULL; + + json_object *json = NULL; + + if (uj) + json = json_object_new_object(); + else { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Source|Group|EvalJD"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* scan all interfaces */ + pim_show_join_desired_helper(pim, vty, up, json, uj, tt); + } + + if (uj) + vty_json(vty, json); + else { + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +void pim_show_upstream_rpf(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct pim_upstream *up; + struct ttable *tt = NULL; + char *table = NULL; + json_object *json = NULL; + json_object *json_group = NULL; + json_object *json_row = NULL; + + if (uj) + json = json_object_new_object(); + else { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, + "Source|Group|RpfIface|RibNextHop|RpfAddress"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + struct pim_rpf *rpf; + const char *rpf_ifname; + + rpf = &up->rpf; + + rpf_ifname = + rpf->source_nexthop.interface ? rpf->source_nexthop + .interface->name + : ""; + + if (uj) { + char grp_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &up->sg.src); + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + json_row = json_object_new_object(); + json_object_pim_upstream_add(json_row, up); + json_object_string_add(json_row, "source", src_str); + json_object_string_add(json_row, "group", grp_str); + json_object_string_add(json_row, "rpfInterface", + rpf_ifname); + json_object_string_addf( + json_row, "ribNexthop", "%pPAs", + &rpf->source_nexthop.mrib_nexthop_addr); + json_object_string_addf(json_row, "rpfAddress", "%pPA", + &rpf->rpf_addr); + json_object_object_add(json_group, src_str, json_row); + } else { + ttable_add_row(tt, "%pPAs|%pPAs|%s|%pPA|%pPA", + &up->sg.src, &up->sg.grp, rpf_ifname, + &rpf->source_nexthop.mrib_nexthop_addr, + &rpf->rpf_addr); + } + } + + if (uj) + vty_json(vty, json); + else { + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void pim_show_join_helper(struct pim_interface *pim_ifp, + struct pim_ifchannel *ch, json_object *json, + time_t now, struct ttable *tt) +{ + json_object *json_iface = NULL; + json_object *json_row = NULL; + json_object *json_grp = NULL; + pim_addr ifaddr; + char uptime[10]; + char expire[10]; + char prune[10]; + + ifaddr = pim_ifp->primary_address; + + pim_time_uptime_begin(uptime, sizeof(uptime), now, ch->ifjoin_creation); + pim_time_timer_to_mmss(expire, sizeof(expire), + ch->t_ifjoin_expiry_timer); + pim_time_timer_to_mmss(prune, sizeof(prune), + ch->t_ifjoin_prune_pending_timer); + + if (json) { + char ch_grp_str[PIM_ADDRSTRLEN]; + + json_object_object_get_ex(json, ch->interface->name, + &json_iface); + + if (!json_iface) { + json_iface = json_object_new_object(); + json_object_pim_ifp_add(json_iface, ch->interface); + json_object_object_add(json, ch->interface->name, + json_iface); + } + + json_row = json_object_new_object(); + json_object_string_addf(json_row, "source", "%pPAs", + &ch->sg.src); + json_object_string_addf(json_row, "group", "%pPAs", + &ch->sg.grp); + json_object_string_add(json_row, "upTime", uptime); + json_object_string_add(json_row, "expire", expire); + json_object_string_add(json_row, "prune", prune); + json_object_string_add( + json_row, "channelJoinName", + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags)); + if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) + json_object_int_add(json_row, "sgRpt", 1); + if (PIM_IF_FLAG_TEST_PROTO_PIM(ch->flags)) + json_object_int_add(json_row, "protocolPim", 1); + if (PIM_IF_FLAG_TEST_PROTO_IGMP(ch->flags)) + json_object_int_add(json_row, "protocolIgmp", 1); + snprintfrr(ch_grp_str, sizeof(ch_grp_str), "%pPAs", + &ch->sg.grp); + json_object_object_get_ex(json_iface, ch_grp_str, &json_grp); + if (!json_grp) { + json_grp = json_object_new_object(); + json_object_object_addf(json_grp, json_row, "%pPAs", + &ch->sg.src); + json_object_object_addf(json_iface, json_grp, "%pPAs", + &ch->sg.grp); + } else + json_object_object_addf(json_grp, json_row, "%pPAs", + &ch->sg.src); + } else { + ttable_add_row( + tt, "%s|%pPAs|%pPAs|%pPAs|%s|%s|%s|%s", + ch->interface->name, &ifaddr, &ch->sg.src, &ch->sg.grp, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), + uptime, expire, prune); + } +} + +int pim_show_join_cmd_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, const char *json) +{ + pim_sgaddr sg = {}; + struct vrf *v; + struct pim_instance *pim; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) { + vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); + return CMD_WARNING; + } + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (!pim_addr_is_any(s_or_g)) { + if (!pim_addr_is_any(g)) { + sg.src = s_or_g; + sg.grp = g; + } else + sg.grp = s_or_g; + } + + if (json) + json_parent = json_object_new_object(); + + pim_show_join(pim, vty, &sg, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_join_vrf_all_cmd_helper(struct vty *vty, const char *json) +{ + pim_sgaddr sg = {0}; + struct vrf *vrf_struct; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf_struct, vrf_name_head, &vrfs_by_name) { + if (!json_parent) + vty_out(vty, "VRF: %s\n", vrf_struct->name); + else + json_vrf = json_object_new_object(); + pim_show_join(vrf_struct->info, vty, &sg, json_vrf); + + if (json) + json_object_object_add(json_parent, vrf_struct->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_WARNING; +} + +void pim_show_join(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, + json_object *json) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + time_t now; + struct ttable *tt = NULL; + char *table = NULL; + + now = pim_time_monotonic_sec(); + + if (!json) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Interface|Address|Source|Group|State|Uptime|Expire|Prune"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + if (!pim_sgaddr_match(ch->sg, *sg)) + continue; + + pim_show_join_helper(pim_ifp, ch, json, now, tt); + } /* scan interface channels */ + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void pim_show_jp_agg_helper(struct interface *ifp, + struct pim_neighbor *neigh, + struct pim_upstream *up, int is_join, + struct ttable *tt) +{ + ttable_add_row(tt, "%s|%pPAs|%pPAs|%pPAs|%s", ifp->name, + &neigh->source_addr, &up->sg.src, &up->sg.grp, + is_join ? "J" : "P"); +} + +int pim_show_jp_agg_list_cmd_helper(const char *vrf, struct vty *vty) +{ + struct vrf *v; + struct pim_instance *pim; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) { + vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); + return CMD_WARNING; + } + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_jp_agg_list(pim, vty); + + return CMD_SUCCESS; +} + +void pim_show_jp_agg_list(struct pim_instance *pim, struct vty *vty) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct listnode *n_node; + struct pim_neighbor *neigh; + struct listnode *jag_node; + struct pim_jp_agg_group *jag; + struct listnode *js_node; + struct pim_jp_sources *js; + struct ttable *tt; + char *table; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Interface|RPF Nbr|Source|Group|State"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, n_node, + neigh)) { + for (ALL_LIST_ELEMENTS_RO(neigh->upstream_jp_agg, + jag_node, jag)) { + for (ALL_LIST_ELEMENTS_RO(jag->sources, js_node, + js)) { + pim_show_jp_agg_helper(ifp, neigh, + js->up, + js->is_join, tt); + } + } + } + } + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); +} + +int pim_show_membership_cmd_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim_show_membership(v->info, vty, uj); + + return CMD_SUCCESS; +} + +static void pim_show_membership_helper(struct vty *vty, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch, + struct json_object *json) +{ + json_object *json_iface = NULL; + json_object *json_row = NULL; + + json_object_object_get_ex(json, ch->interface->name, &json_iface); + if (!json_iface) { + json_iface = json_object_new_object(); + json_object_pim_ifp_add(json_iface, ch->interface); + json_object_object_add(json, ch->interface->name, json_iface); + } + + json_row = json_object_new_object(); + json_object_string_addf(json_row, "source", "%pPAs", &ch->sg.src); + json_object_string_addf(json_row, "group", "%pPAs", &ch->sg.grp); + json_object_string_add(json_row, "localMembership", + ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO + ? "NOINFO" + : "INCLUDE"); + json_object_object_addf(json_iface, json_row, "%pPAs", &ch->sg.grp); +} + +void pim_show_membership(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + enum json_type type; + json_object *json = NULL; + json_object *json_tmp = NULL; + struct ttable *tt = NULL; + char *table = NULL; + + json = json_object_new_object(); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_show_membership_helper(vty, pim_ifp, ch, json); + } /* scan interface channels */ + } + + if (uj) { + vty_json(vty, json); + } else { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Interface|Address|Source|Group|Membership"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + /* + * Example of the json data we are traversing + * + * { + * "swp3":{ + * "name":"swp3", + * "state":"up", + * "address":"10.1.20.1", + * "index":5, + * "flagMulticast":true, + * "flagBroadcast":true, + * "lanDelayEnabled":true, + * "226.10.10.10":{ + * "source":"*", + * "group":"226.10.10.10", + * "localMembership":"INCLUDE" + * } + * } + * } + */ + + /* foreach interface */ + json_object_object_foreach(json, key, val) + { + + /* Find all of the keys where the val is an object. In + * the example + * above the only one is 226.10.10.10 + */ + json_object_object_foreach(val, if_field_key, + if_field_val) + { + type = json_object_get_type(if_field_val); + + if (type == json_type_object) { + const char *address, *source, + *localMembership; + + json_object_object_get_ex( + val, "address", &json_tmp); + address = json_object_get_string( + json_tmp); + + json_object_object_get_ex(if_field_val, + "source", + &json_tmp); + source = json_object_get_string( + json_tmp); + + json_object_object_get_ex( + if_field_val, "localMembership", + &json_tmp); + localMembership = + json_object_get_string( + json_tmp); + + ttable_add_row(tt, "%s|%s|%s|%s|%s", + key, address, source, + if_field_key, + localMembership); + } + } + } + json_object_free(json); + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void pim_show_channel_helper(struct pim_instance *pim, + struct pim_interface *pim_ifp, + struct pim_ifchannel *ch, json_object *json, + bool uj, struct ttable *tt) +{ + struct pim_upstream *up = ch->upstream; + json_object *json_group = NULL; + json_object *json_row = NULL; + + if (uj) { + char grp_str[PIM_ADDRSTRLEN]; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &up->sg.grp); + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, json_group); + } + + json_row = json_object_new_object(); + json_object_pim_upstream_add(json_row, up); + json_object_string_add(json_row, "interface", + ch->interface->name); + json_object_string_addf(json_row, "source", "%pPAs", + &up->sg.src); + json_object_string_addf(json_row, "group", "%pPAs", + &up->sg.grp); + + if (pim_macro_ch_lost_assert(ch)) + json_object_boolean_true_add(json_row, "lostAssert"); + + if (pim_macro_chisin_joins(ch)) + json_object_boolean_true_add(json_row, "joins"); + + if (pim_macro_chisin_pim_include(ch)) + json_object_boolean_true_add(json_row, "pimInclude"); + + if (pim_upstream_evaluate_join_desired(pim, up)) + json_object_boolean_true_add(json_row, + "evaluateJoinDesired"); + + json_object_object_addf(json_group, json_row, "%pPAs", + &up->sg.src); + + } else { + ttable_add_row(tt, "%s|%pPAs|%pPAs|%s|%s|%s|%s|%s", + ch->interface->name, &up->sg.src, &up->sg.grp, + pim_macro_ch_lost_assert(ch) ? "yes" : "no", + pim_macro_chisin_joins(ch) ? "yes" : "no", + pim_macro_chisin_pim_include(ch) ? "yes" : "no", + PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags) + ? "yes" + : "no", + pim_upstream_evaluate_join_desired(pim, up) + ? "yes" + : "no"); + } +} + +void pim_show_channel(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct interface *ifp; + struct ttable *tt = NULL; + json_object *json = NULL; + char *table = NULL; + + if (uj) + json = json_object_new_object(); + else { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Interface|Source|Group|LostAssert|Joins|PimInclude|JoinDesired|EvalJD"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + /* scan per-interface (S,G) state */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + if (!pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + /* scan all interfaces */ + pim_show_channel_helper(pim, pim_ifp, ch, json, uj, tt); + } + } + + if (uj) + vty_json(vty, json); + else { + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +int pim_show_channel_cmd_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim_show_channel(v->info, vty, uj); + + return CMD_SUCCESS; +} + +int pim_show_interface_cmd_helper(const char *vrf, struct vty *vty, bool uj, + bool mlag, const char *interface) +{ + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + if (uj) + json_parent = json_object_new_object(); + + if (interface) + pim_show_interfaces_single(v->info, vty, interface, mlag, + json_parent); + else + pim_show_interfaces(v->info, vty, mlag, json_parent); + + if (uj) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_interface_vrf_all_cmd_helper(struct vty *vty, bool uj, bool mlag, + const char *interface) +{ + struct vrf *v; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (uj) + json_parent = json_object_new_object(); + + RB_FOREACH (v, vrf_name_head, &vrfs_by_name) { + if (!uj) + vty_out(vty, "VRF: %s\n", v->name); + else + json_vrf = json_object_new_object(); + + if (interface) + pim_show_interfaces_single(v->info, vty, interface, + mlag, json_vrf); + else + pim_show_interfaces(v->info, vty, mlag, json_vrf); + + if (uj) + json_object_object_add(json_parent, v->name, json_vrf); + } + if (uj) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +void pim_show_interfaces(struct pim_instance *pim, struct vty *vty, bool mlag, + json_object *json) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_upstream *up; + int fhr = 0; + int pim_nbrs = 0; + int pim_ifchannels = 0; + bool uj = true; + struct ttable *tt = NULL; + char *table = NULL; + json_object *json_row = NULL; + json_object *json_tmp; + + if (!json) { + uj = false; + json = json_object_new_object(); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (mlag == true && pim_ifp->activeactive == false) + continue; + + pim_nbrs = pim_ifp->pim_neighbor_list->count; + pim_ifchannels = pim_if_ifchannel_count(pim_ifp); + fhr = 0; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) + if (ifp == up->rpf.source_nexthop.interface) + if (up->flags & PIM_UPSTREAM_FLAG_MASK_FHR) + fhr++; + + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + json_object_int_add(json_row, "pimNeighbors", pim_nbrs); + json_object_int_add(json_row, "pimIfChannels", pim_ifchannels); + json_object_int_add(json_row, "firstHopRouterCount", fhr); + json_object_string_addf(json_row, "pimDesignatedRouter", + "%pPAs", &pim_ifp->pim_dr_addr); + + if (!pim_addr_cmp(pim_ifp->pim_dr_addr, + pim_ifp->primary_address)) + json_object_boolean_true_add( + json_row, "pimDesignatedRouterLocal"); + + json_object_object_add(json, ifp->name, json_row); + } + + if (!uj) { + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Interface|State|Address|PIM Nbrs|PIM DR|FHR|IfChannels"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + json_object_object_foreach(json, key, val) + { + const char *state, *address, *pimdr; + int neighbors, firsthpr, pimifchnl; + + json_object_object_get_ex(val, "state", &json_tmp); + state = json_object_get_string(json_tmp); + + json_object_object_get_ex(val, "address", &json_tmp); + address = json_object_get_string(json_tmp); + + json_object_object_get_ex(val, "pimNeighbors", + &json_tmp); + neighbors = json_object_get_int(json_tmp); + + if (json_object_object_get_ex( + val, "pimDesignatedRouterLocal", + &json_tmp)) { + pimdr = "local"; + } else { + json_object_object_get_ex( + val, "pimDesignatedRouter", &json_tmp); + pimdr = json_object_get_string(json_tmp); + } + + json_object_object_get_ex(val, "firstHopRouter", + &json_tmp); + firsthpr = json_object_get_int(json_tmp); + + json_object_object_get_ex(val, "pimIfChannels", + &json_tmp); + pimifchnl = json_object_get_int(json_tmp); + + ttable_add_row(tt, "%s|%s|%s|%d|%s|%d|%d", key, state, + address, neighbors, pimdr, firsthpr, + pimifchnl); + } + json_object_free(json); + + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_del(tt); + } +} + +void pim_show_interfaces_single(struct pim_instance *pim, struct vty *vty, + const char *ifname, bool mlag, + json_object *json) +{ + pim_addr ifaddr; + struct interface *ifp; + struct listnode *neighnode; + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + struct pim_upstream *up; + time_t now; + char dr_str[PIM_ADDRSTRLEN]; + char dr_uptime[10]; + char expire[10]; + char grp_str[PIM_ADDRSTRLEN]; + char hello_period[10]; + char hello_timer[10]; + char neigh_src_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + char stat_uptime[10]; + char uptime[10]; + int found_ifname = 0; + int print_header; + json_object *json_row = NULL; + json_object *json_pim_neighbor = NULL; + json_object *json_pim_neighbors = NULL; + json_object *json_group = NULL; + json_object *json_group_source = NULL; + json_object *json_fhr_sources = NULL; + struct pim_secondary_addr *sec_addr; + struct listnode *sec_node; + + now = pim_time_monotonic_sec(); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (mlag == true && pim_ifp->activeactive == false) + continue; + + if (strcmp(ifname, "detail") && strcmp(ifname, ifp->name)) + continue; + + found_ifname = 1; + ifaddr = pim_ifp->primary_address; + snprintfrr(dr_str, sizeof(dr_str), "%pPAs", + &pim_ifp->pim_dr_addr); + pim_time_uptime_begin(dr_uptime, sizeof(dr_uptime), now, + pim_ifp->pim_dr_election_last); + pim_time_timer_to_hhmmss(hello_timer, sizeof(hello_timer), + pim_ifp->t_pim_hello_timer); + pim_time_mmss(hello_period, sizeof(hello_period), + pim_ifp->pim_hello_period); + pim_time_uptime(stat_uptime, sizeof(stat_uptime), + now - pim_ifp->pim_ifstat_start); + + if (json) { + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + + if (!pim_addr_is_any(pim_ifp->update_source)) { + json_object_string_addf( + json_row, "useSource", "%pPAs", + &pim_ifp->update_source); + } + if (pim_ifp->sec_addr_list) { + json_object *sec_list = NULL; + + sec_list = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO( + pim_ifp->sec_addr_list, sec_node, + sec_addr)) { + json_object_array_add( + sec_list, + json_object_new_stringf( + "%pFXh", + &sec_addr->addr)); + } + json_object_object_add(json_row, + "secondaryAddressList", + sec_list); + } + + if (pim_ifp->pim_passive_enable) + json_object_boolean_true_add(json_row, + "passive"); + + /* PIM neighbors */ + if (pim_ifp->pim_neighbor_list->count) { + json_pim_neighbors = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO( + pim_ifp->pim_neighbor_list, + neighnode, neigh)) { + json_pim_neighbor = + json_object_new_object(); + snprintfrr(neigh_src_str, + sizeof(neigh_src_str), + "%pPAs", + &neigh->source_addr); + pim_time_uptime(uptime, sizeof(uptime), + now - neigh->creation); + pim_time_timer_to_hhmmss( + expire, sizeof(expire), + neigh->t_expire_timer); + + json_object_string_add( + json_pim_neighbor, "address", + neigh_src_str); + json_object_string_add( + json_pim_neighbor, "upTime", + uptime); + json_object_string_add( + json_pim_neighbor, "holdtime", + expire); + + json_object_object_add( + json_pim_neighbors, + neigh_src_str, + json_pim_neighbor); + } + + json_object_object_add(json_row, "neighbors", + json_pim_neighbors); + } + + json_object_string_add(json_row, "drAddress", dr_str); + json_object_int_add(json_row, "drPriority", + pim_ifp->pim_dr_priority); + json_object_string_add(json_row, "drUptime", dr_uptime); + json_object_int_add(json_row, "drElections", + pim_ifp->pim_dr_election_count); + json_object_int_add(json_row, "drChanges", + pim_ifp->pim_dr_election_changes); + + /* FHR */ + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (ifp != up->rpf.source_nexthop.interface) + continue; + + if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_FHR)) + continue; + + if (!json_fhr_sources) + json_fhr_sources = + json_object_new_object(); + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + &up->sg.grp); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + &up->sg.src); + pim_time_uptime(uptime, sizeof(uptime), + now - up->state_transition); + + /* + * Does this group live in json_fhr_sources? + * If not create it. + */ + json_object_object_get_ex(json_fhr_sources, + grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json_fhr_sources, + grp_str, + json_group); + } + + json_group_source = json_object_new_object(); + json_object_string_add(json_group_source, + "source", src_str); + json_object_string_add(json_group_source, + "group", grp_str); + json_object_string_add(json_group_source, + "upTime", uptime); + json_object_object_add(json_group, src_str, + json_group_source); + } + + if (json_fhr_sources) { + json_object_object_add(json_row, + "firstHopRouter", + json_fhr_sources); + } + + json_object_int_add(json_row, "helloPeriod", + pim_ifp->pim_hello_period); + json_object_int_add(json_row, "holdTime", + PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); + json_object_string_add(json_row, "helloTimer", + hello_timer); + json_object_string_add(json_row, "helloStatStart", + stat_uptime); + json_object_int_add(json_row, "helloReceived", + pim_ifp->pim_ifstat_hello_recv); + json_object_int_add(json_row, "helloReceivedFailed", + pim_ifp->pim_ifstat_hello_recvfail); + json_object_int_add(json_row, "helloSend", + pim_ifp->pim_ifstat_hello_sent); + json_object_int_add(json_row, "hellosendFailed", + pim_ifp->pim_ifstat_hello_sendfail); + json_object_int_add(json_row, "helloGenerationId", + pim_ifp->pim_generation_id); + + json_object_int_add( + json_row, "effectivePropagationDelay", + pim_if_effective_propagation_delay_msec(ifp)); + json_object_int_add( + json_row, "effectiveOverrideInterval", + pim_if_effective_override_interval_msec(ifp)); + json_object_int_add( + json_row, "joinPruneOverrideInterval", + pim_if_jp_override_interval_msec(ifp)); + + json_object_int_add( + json_row, "propagationDelay", + pim_ifp->pim_propagation_delay_msec); + json_object_int_add( + json_row, "propagationDelayHighest", + pim_ifp->pim_neighbors_highest_propagation_delay_msec); + json_object_int_add( + json_row, "overrideInterval", + pim_ifp->pim_override_interval_msec); + json_object_int_add( + json_row, "overrideIntervalHighest", + pim_ifp->pim_neighbors_highest_override_interval_msec); + if (pim_ifp->bsm_enable) + json_object_boolean_true_add(json_row, + "bsmEnabled"); + if (pim_ifp->ucast_bsm_accept) + json_object_boolean_true_add(json_row, + "ucastBsmEnabled"); + json_object_object_add(json, ifp->name, json_row); + + } else { + vty_out(vty, "Interface : %s\n", ifp->name); + vty_out(vty, "State : %s\n", + if_is_up(ifp) ? "up" : "down"); + if (!pim_addr_is_any(pim_ifp->update_source)) { + vty_out(vty, "Use Source : %pPAs\n", + &pim_ifp->update_source); + } + if (pim_ifp->sec_addr_list) { + vty_out(vty, "Address : %pPAs (primary)\n", + &ifaddr); + for (ALL_LIST_ELEMENTS_RO( + pim_ifp->sec_addr_list, sec_node, + sec_addr)) + vty_out(vty, " %pFX\n", + &sec_addr->addr); + } else { + vty_out(vty, "Address : %pPAs\n", &ifaddr); + } + + if (pim_ifp->pim_passive_enable) + vty_out(vty, "Passive : %s\n", + (pim_ifp->pim_passive_enable) ? "yes" + : "no"); + + vty_out(vty, "\n"); + + /* PIM neighbors */ + print_header = 1; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, + neighnode, neigh)) { + + if (print_header) { + vty_out(vty, "PIM Neighbors\n"); + vty_out(vty, "-------------\n"); + print_header = 0; + } + + snprintfrr(neigh_src_str, sizeof(neigh_src_str), + "%pPAs", &neigh->source_addr); + pim_time_uptime(uptime, sizeof(uptime), + now - neigh->creation); + pim_time_timer_to_hhmmss(expire, sizeof(expire), + neigh->t_expire_timer); + vty_out(vty, + "%-15s : up for %s, holdtime expires in %s\n", + neigh_src_str, uptime, expire); + } + + if (!print_header) { + vty_out(vty, "\n"); + vty_out(vty, "\n"); + } + + vty_out(vty, "Designated Router\n"); + vty_out(vty, "-----------------\n"); + vty_out(vty, "Address : %s\n", dr_str); + vty_out(vty, "Priority : %u(%d)\n", + pim_ifp->pim_dr_priority, + pim_ifp->pim_dr_num_nondrpri_neighbors); + vty_out(vty, "Uptime : %s\n", dr_uptime); + vty_out(vty, "Elections : %d\n", + pim_ifp->pim_dr_election_count); + vty_out(vty, "Changes : %d\n", + pim_ifp->pim_dr_election_changes); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + /* FHR */ + print_header = 1; + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!up->rpf.source_nexthop.interface) + continue; + + if (strcmp(ifp->name, + up->rpf.source_nexthop + .interface->name) != 0) + continue; + + if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_FHR)) + continue; + + if (print_header) { + vty_out(vty, + "FHR - First Hop Router\n"); + vty_out(vty, + "----------------------\n"); + print_header = 0; + } + + pim_time_uptime(uptime, sizeof(uptime), + now - up->state_transition); + vty_out(vty, + "%pPAs : %pPAs is a source, uptime is %s\n", + &up->sg.grp, &up->sg.src, uptime); + } + + if (!print_header) { + vty_out(vty, "\n"); + vty_out(vty, "\n"); + } + + vty_out(vty, "Hellos\n"); + vty_out(vty, "------\n"); + vty_out(vty, "Period : %d\n", + pim_ifp->pim_hello_period); + vty_out(vty, "HoldTime : %d\n", + PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); + vty_out(vty, "Timer : %s\n", hello_timer); + vty_out(vty, "StatStart : %s\n", stat_uptime); + vty_out(vty, "Receive : %d\n", + pim_ifp->pim_ifstat_hello_recv); + vty_out(vty, "Receive Failed : %d\n", + pim_ifp->pim_ifstat_hello_recvfail); + vty_out(vty, "Send : %d\n", + pim_ifp->pim_ifstat_hello_sent); + vty_out(vty, "Send Failed : %d\n", + pim_ifp->pim_ifstat_hello_sendfail); + vty_out(vty, "Generation ID : %08x\n", + pim_ifp->pim_generation_id); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + pim_print_ifp_flags(vty, ifp); + + vty_out(vty, "Join Prune Interval\n"); + vty_out(vty, "-------------------\n"); + vty_out(vty, "LAN Delay : %s\n", + pim_if_lan_delay_enabled(ifp) ? "yes" : "no"); + vty_out(vty, "Effective Propagation Delay : %d msec\n", + pim_if_effective_propagation_delay_msec(ifp)); + vty_out(vty, "Effective Override Interval : %d msec\n", + pim_if_effective_override_interval_msec(ifp)); + vty_out(vty, "Join Prune Override Interval : %d msec\n", + pim_if_jp_override_interval_msec(ifp)); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + vty_out(vty, "LAN Prune Delay\n"); + vty_out(vty, "---------------\n"); + vty_out(vty, "Propagation Delay : %d msec\n", + pim_ifp->pim_propagation_delay_msec); + vty_out(vty, "Propagation Delay (Highest) : %d msec\n", + pim_ifp->pim_neighbors_highest_propagation_delay_msec); + vty_out(vty, "Override Interval : %d msec\n", + pim_ifp->pim_override_interval_msec); + vty_out(vty, "Override Interval (Highest) : %d msec\n", + pim_ifp->pim_neighbors_highest_override_interval_msec); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + + vty_out(vty, "BSM Status\n"); + vty_out(vty, "----------\n"); + vty_out(vty, "Bsm Enabled : %s\n", + pim_ifp->bsm_enable ? "yes" : "no"); + vty_out(vty, "Unicast Bsm Enabled : %s\n", + pim_ifp->ucast_bsm_accept ? "yes" : "no"); + vty_out(vty, "\n"); + vty_out(vty, "\n"); + } + } + + if (!found_ifname && !json) + vty_out(vty, "%% No such interface\n"); +} + +void ip_pim_ssm_show_group_range(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct pim_ssm *ssm = pim->ssm_info; + const char *range_str = + ssm->plist_name ? ssm->plist_name : PIM_SSM_STANDARD_RANGE; + + if (uj) { + json_object *json; + + json = json_object_new_object(); + json_object_string_add(json, "ssmGroups", range_str); + vty_json(vty, json); + } else + vty_out(vty, "SSM group range : %s\n", range_str); +} + +struct vty_pnc_cache_walk_data { + struct vty *vty; + struct pim_instance *pim; +}; + +struct json_pnc_cache_walk_data { + json_object *json_obj; + struct pim_instance *pim; +}; + +static int pim_print_vty_pnc_cache_walkcb(struct hash_bucket *bucket, void *arg) +{ + struct pim_nexthop_cache *pnc = bucket->data; + struct vty_pnc_cache_walk_data *cwd = arg; + struct vty *vty = cwd->vty; + struct pim_instance *pim = cwd->pim; + struct nexthop *nh_node = NULL; + ifindex_t first_ifindex; + struct interface *ifp = NULL; + struct ttable *tt = NULL; + char *table = NULL; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Address|Interface|Nexthop"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { + first_ifindex = nh_node->ifindex; + + ifp = if_lookup_by_index(first_ifindex, pim->vrf->vrf_id); + +#if PIM_IPV == 4 + ttable_add_row(tt, "%pPA|%s|%pI4", &pnc->rpf.rpf_addr, + ifp ? ifp->name : "NULL", &nh_node->gate.ipv4); +#else + ttable_add_row(tt, "%pPA|%s|%pI6", &pnc->rpf.rpf_addr, + ifp ? ifp->name : "NULL", &nh_node->gate.ipv6); +#endif + } + /* Dump the generated table. */ + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + + return CMD_SUCCESS; +} + +static int pim_print_json_pnc_cache_walkcb(struct hash_bucket *backet, + void *arg) +{ + struct pim_nexthop_cache *pnc = backet->data; + struct json_pnc_cache_walk_data *cwd = arg; + struct pim_instance *pim = cwd->pim; + struct nexthop *nh_node = NULL; + ifindex_t first_ifindex; + struct interface *ifp = NULL; + char addr_str[PIM_ADDRSTRLEN]; + json_object *json_row = NULL; + json_object *json_ifp = NULL; + json_object *json_arr = NULL; + struct pim_interface *pim_ifp = NULL; + bool pim_enable = false; + + for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { + first_ifindex = nh_node->ifindex; + ifp = if_lookup_by_index(first_ifindex, pim->vrf->vrf_id); + snprintfrr(addr_str, sizeof(addr_str), "%pPA", + &pnc->rpf.rpf_addr); + json_object_object_get_ex(cwd->json_obj, addr_str, &json_row); + if (!json_row) { + json_row = json_object_new_object(); + json_object_string_addf(json_row, "address", "%pPA", + &pnc->rpf.rpf_addr); + json_object_object_addf(cwd->json_obj, json_row, "%pPA", + &pnc->rpf.rpf_addr); + json_arr = json_object_new_array(); + json_object_object_add(json_row, "nexthops", json_arr); + } + json_ifp = json_object_new_object(); + json_object_string_add(json_ifp, "interface", + ifp ? ifp->name : "NULL"); + + if (ifp) + pim_ifp = ifp->info; + + if (pim_ifp && pim_ifp->pim_enable) + pim_enable = true; + + json_object_boolean_add(json_ifp, "pimEnabled", pim_enable); +#if PIM_IPV == 4 + json_object_string_addf(json_ifp, "nexthop", "%pI4", + &nh_node->gate.ipv4); +#else + json_object_string_addf(json_ifp, "nexthop", "%pI6", + &nh_node->gate.ipv6); +#endif + json_object_array_add(json_arr, json_ifp); + } + return CMD_SUCCESS; +} + +int pim_show_nexthop_lookup_cmd_helper(const char *vrf, struct vty *vty, + pim_addr source, pim_addr group) +{ + int result = 0; + pim_addr vif_source; + struct prefix grp; + struct pim_nexthop nexthop; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + +#if PIM_IPV == 4 + if (pim_is_group_224_4(source)) { + vty_out(vty, + "Invalid argument. Expected Valid Source Address.\n"); + return CMD_WARNING; + } + + if (!pim_is_group_224_4(group)) { + vty_out(vty, + "Invalid argument. Expected Valid Multicast Group Address.\n"); + return CMD_WARNING; + } +#endif + + if (!pim_rp_set_upstream_addr(v->info, &vif_source, source, group)) + return CMD_SUCCESS; + + pim_addr_to_prefix(&grp, group); + memset(&nexthop, 0, sizeof(nexthop)); + + result = + pim_ecmp_nexthop_lookup(v->info, &nexthop, vif_source, &grp, 0); + + if (!result) { + vty_out(vty, + "Nexthop Lookup failed, no usable routes returned.\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "Group %pFXh --- Nexthop %pPAs Interface %s\n", &grp, + &nexthop.mrib_nexthop_addr, nexthop.interface->name); + + return CMD_SUCCESS; +} + +int pim_show_nexthop_cmd_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim_show_nexthop(v->info, vty, uj); + + return CMD_SUCCESS; +} + +void pim_show_nexthop(struct pim_instance *pim, struct vty *vty, bool uj) +{ + struct vty_pnc_cache_walk_data cwd; + struct json_pnc_cache_walk_data jcwd; + + cwd.vty = vty; + cwd.pim = pim; + jcwd.pim = pim; + + if (uj) { + jcwd.json_obj = json_object_new_object(); + } else { + vty_out(vty, "Number of registered addresses: %lu\n", + pim->rpf_hash->count); + } + + if (uj) { + hash_walk(pim->rpf_hash, pim_print_json_pnc_cache_walkcb, + &jcwd); + vty_json(vty, jcwd.json_obj); + } else + hash_walk(pim->rpf_hash, pim_print_vty_pnc_cache_walkcb, &cwd); +} + +int pim_show_neighbors_cmd_helper(const char *vrf, struct vty *vty, + const char *json, const char *interface) +{ + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + if (json) + json_parent = json_object_new_object(); + + if (interface) + pim_show_neighbors_single(v->info, vty, interface, json_parent); + else + pim_show_neighbors(v->info, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_neighbors_vrf_all_cmd_helper(struct vty *vty, const char *json, + const char *interface) +{ + struct vrf *v; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + RB_FOREACH (v, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", v->name); + else + json_vrf = json_object_new_object(); + + if (interface) + pim_show_neighbors_single(v->info, vty, interface, + json_vrf); + else + pim_show_neighbors(v->info, vty, json_vrf); + + if (json) + json_object_object_add(json_parent, v->name, json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +void pim_show_neighbors_single(struct pim_instance *pim, struct vty *vty, + const char *neighbor, json_object *json) +{ + struct listnode *neighnode; + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + time_t now; + int found_neighbor = 0; + int option_address_list; + int option_dr_priority; + int option_generation_id; + int option_holdtime; + int option_lan_prune_delay; + int option_t_bit; + char uptime[10]; + char expire[10]; + char neigh_src_str[PIM_ADDRSTRLEN]; + + json_object *json_ifp = NULL; + json_object *json_row = NULL; + + now = pim_time_monotonic_sec(); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (pim_ifp->pim_sock_fd < 0) + continue; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, + neigh)) { + snprintfrr(neigh_src_str, sizeof(neigh_src_str), + "%pPAs", &neigh->source_addr); + + /* + * The user can specify either the interface name or the + * PIM neighbor IP. + * If this pim_ifp matches neither then skip. + */ + if (strcmp(neighbor, "detail") && + strcmp(neighbor, ifp->name) && + strcmp(neighbor, neigh_src_str)) + continue; + + found_neighbor = 1; + pim_time_uptime(uptime, sizeof(uptime), + now - neigh->creation); + pim_time_timer_to_hhmmss(expire, sizeof(expire), + neigh->t_expire_timer); + + option_address_list = 0; + option_dr_priority = 0; + option_generation_id = 0; + option_holdtime = 0; + option_lan_prune_delay = 0; + option_t_bit = 0; + + if (PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_ADDRESS_LIST)) + option_address_list = 1; + + if (PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_DR_PRIORITY)) + option_dr_priority = 1; + + if (PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_GENERATION_ID)) + option_generation_id = 1; + + if (PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_HOLDTIME)) + option_holdtime = 1; + + if (PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY)) + option_lan_prune_delay = 1; + + if (PIM_OPTION_IS_SET( + neigh->hello_options, + PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION)) + option_t_bit = 1; + + if (json) { + + /* Does this ifp live in json? If not create it + */ + json_object_object_get_ex(json, ifp->name, + &json_ifp); + + if (!json_ifp) { + json_ifp = json_object_new_object(); + json_object_pim_ifp_add(json_ifp, ifp); + json_object_object_add(json, ifp->name, + json_ifp); + } + + json_row = json_object_new_object(); + json_object_string_add(json_row, "interface", + ifp->name); + json_object_string_add(json_row, "address", + neigh_src_str); + json_object_string_add(json_row, "upTime", + uptime); + json_object_string_add(json_row, "holdtime", + expire); + json_object_int_add(json_row, "drPriority", + neigh->dr_priority); + json_object_int_add(json_row, "generationId", + neigh->generation_id); + + if (option_address_list) + json_object_boolean_true_add( + json_row, + "helloOptionAddressList"); + + if (option_dr_priority) + json_object_boolean_true_add( + json_row, + "helloOptionDrPriority"); + + if (option_generation_id) + json_object_boolean_true_add( + json_row, + "helloOptionGenerationId"); + + if (option_holdtime) + json_object_boolean_true_add( + json_row, + "helloOptionHoldtime"); + + if (option_lan_prune_delay) + json_object_boolean_true_add( + json_row, + "helloOptionLanPruneDelay"); + + if (option_t_bit) + json_object_boolean_true_add( + json_row, "helloOptionTBit"); + + json_object_object_add(json_ifp, neigh_src_str, + json_row); + + } else { + vty_out(vty, "Interface : %s\n", ifp->name); + vty_out(vty, "Neighbor : %s\n", neigh_src_str); + vty_out(vty, + " Uptime : %s\n", + uptime); + vty_out(vty, + " Holdtime : %s\n", + expire); + vty_out(vty, + " DR Priority : %d\n", + neigh->dr_priority); + vty_out(vty, + " Generation ID : %08x\n", + neigh->generation_id); + vty_out(vty, + " Override Interval (msec) : %d\n", + neigh->override_interval_msec); + vty_out(vty, + " Propagation Delay (msec) : %d\n", + neigh->propagation_delay_msec); + vty_out(vty, + " Hello Option - Address List : %s\n", + option_address_list ? "yes" : "no"); + vty_out(vty, + " Hello Option - DR Priority : %s\n", + option_dr_priority ? "yes" : "no"); + vty_out(vty, + " Hello Option - Generation ID : %s\n", + option_generation_id ? "yes" : "no"); + vty_out(vty, + " Hello Option - Holdtime : %s\n", + option_holdtime ? "yes" : "no"); + vty_out(vty, + " Hello Option - LAN Prune Delay : %s\n", + option_lan_prune_delay ? "yes" : "no"); + vty_out(vty, + " Hello Option - T-bit : %s\n", + option_t_bit ? "yes" : "no"); + bfd_sess_show(vty, json_ifp, + neigh->bfd_session); + vty_out(vty, "\n"); + } + } + } + + if (!found_neighbor && !json) + vty_out(vty, "%% No such interface or neighbor\n"); +} + +void pim_show_neighbors(struct pim_instance *pim, struct vty *vty, + json_object *json) +{ + struct listnode *neighnode; + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + struct ttable *tt = NULL; + char *table = NULL; + time_t now; + char uptime[10]; + char expire[10]; + char neigh_src_str[PIM_ADDRSTRLEN]; + json_object *json_ifp_rows = NULL; + json_object *json_row = NULL; + + now = pim_time_monotonic_sec(); + + if (!json) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Interface|Neighbor|Uptime|Holdtime|DR Pri"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (pim_ifp->pim_sock_fd < 0) + continue; + + if (json) + json_ifp_rows = json_object_new_object(); + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, + neigh)) { + snprintfrr(neigh_src_str, sizeof(neigh_src_str), + "%pPAs", &neigh->source_addr); + pim_time_uptime(uptime, sizeof(uptime), + now - neigh->creation); + pim_time_timer_to_hhmmss(expire, sizeof(expire), + neigh->t_expire_timer); + + if (json) { + json_row = json_object_new_object(); + json_object_string_add(json_row, "interface", + ifp->name); + json_object_string_add(json_row, "neighbor", + neigh_src_str); + json_object_string_add(json_row, "upTime", + uptime); + json_object_string_add(json_row, "holdTime", + expire); + json_object_int_add(json_row, "holdTimeMax", + neigh->holdtime); + json_object_int_add(json_row, "drPriority", + neigh->dr_priority); + json_object_object_add(json_ifp_rows, + neigh_src_str, json_row); + + } else { + ttable_add_row(tt, "%s|%pPAs|%s|%s|%d", + ifp->name, &neigh->source_addr, + uptime, expire, + neigh->dr_priority); + } + } + + if (json) { + json_object_object_add(json, ifp->name, json_ifp_rows); + json_ifp_rows = NULL; + } + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +int gm_process_query_max_response_time_cmd(struct vty *vty, + const char *qmrt_str) +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_MODIFY, + qmrt_str); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int gm_process_no_query_max_response_time_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./query-max-response-time", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int gm_process_last_member_query_count_cmd(struct vty *vty, + const char *lmqc_str) +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./robustness-variable", NB_OP_MODIFY, + lmqc_str); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int gm_process_no_last_member_query_count_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./robustness-variable", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int gm_process_last_member_query_interval_cmd(struct vty *vty, + const char *lmqi_str) +{ + const struct lyd_node *pim_enable_dnode; + + pim_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_PIM_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + if (!pim_enable_dnode) { + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, "true"); + } else { + if (!yang_dnode_get_bool(pim_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./last-member-query-interval", NB_OP_MODIFY, + lmqi_str); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int gm_process_no_last_member_query_interval_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./last-member-query-interval", + NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_ssmpingd_cmd(struct vty *vty, enum nb_operation operation, + const char *src_str) +{ + const char *vrfname; + char ssmpingd_ip_xpath[XPATH_MAXLEN]; + char ssmpingd_src_ip_xpath[XPATH_MAXLEN]; + int printed; + + vrfname = pim_cli_get_vrf_name(vty); + if (vrfname == NULL) + return CMD_WARNING_CONFIG_FAILED; + + snprintf(ssmpingd_ip_xpath, sizeof(ssmpingd_ip_xpath), + FRR_PIM_VRF_XPATH, "frr-pim:pimd", "pim", vrfname, + FRR_PIM_AF_XPATH_VAL); + printed = snprintf(ssmpingd_src_ip_xpath, sizeof(ssmpingd_src_ip_xpath), + "%s/ssm-pingd-source-ip[.='%s']", ssmpingd_ip_xpath, + src_str); + if (printed >= (int)sizeof(ssmpingd_src_ip_xpath)) { + vty_out(vty, "Xpath too long (%d > %u)", printed + 1, + XPATH_MAXLEN); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, ssmpingd_src_ip_xpath, operation, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_bsm_cmd(struct vty *vty) +{ + const struct lyd_node *gm_enable_dnode; + + gm_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + if (!gm_enable_dnode) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + else { + if (!yang_dnode_get_bool(gm_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./bsm", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_bsm_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./bsm", NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_unicast_bsm_cmd(struct vty *vty) +{ + const struct lyd_node *gm_enable_dnode; + + gm_enable_dnode = yang_dnode_getf(vty->candidate_config->dnode, + FRR_GMP_ENABLE_XPATH, VTY_CURR_XPATH, + FRR_PIM_AF_XPATH_VAL); + if (!gm_enable_dnode) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + else { + if (!yang_dnode_get_bool(gm_enable_dnode, ".")) + nb_cli_enqueue_change(vty, "./pim-enable", NB_OP_MODIFY, + "true"); + } + + nb_cli_enqueue_change(vty, "./unicast-bsm", NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +int pim_process_no_unicast_bsm_cmd(struct vty *vty) +{ + nb_cli_enqueue_change(vty, "./unicast-bsm", NB_OP_MODIFY, "false"); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, + FRR_PIM_AF_XPATH_VAL); +} + +static void show_scan_oil_stats(struct pim_instance *pim, struct vty *vty, + time_t now) +{ + char uptime_scan_oil[10]; + char uptime_mroute_add[10]; + char uptime_mroute_del[10]; + + pim_time_uptime_begin(uptime_scan_oil, sizeof(uptime_scan_oil), now, + pim->scan_oil_last); + pim_time_uptime_begin(uptime_mroute_add, sizeof(uptime_mroute_add), now, + pim->mroute_add_last); + pim_time_uptime_begin(uptime_mroute_del, sizeof(uptime_mroute_del), now, + pim->mroute_del_last); + + vty_out(vty, + "Scan OIL - Last: %s Events: %lld\n" + "MFC Add - Last: %s Events: %lld\n" + "MFC Del - Last: %s Events: %lld\n", + uptime_scan_oil, (long long)pim->scan_oil_events, + uptime_mroute_add, (long long)pim->mroute_add_events, + uptime_mroute_del, (long long)pim->mroute_del_events); +} + +void show_multicast_interfaces(struct pim_instance *pim, struct vty *vty, + json_object *json) +{ + struct interface *ifp; + struct ttable *tt = NULL; + char *table = NULL; + json_object *json_row = NULL; + + vty_out(vty, "\n"); + + if (!json) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Interface|Address|ifi|Vif|PktsIn|PktsOut|BytesIn|BytesOut"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; +#if PIM_IPV == 4 + struct sioc_vif_req vreq; +#else + struct sioc_mif_req6 vreq; +#endif + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + memset(&vreq, 0, sizeof(vreq)); +#if PIM_IPV == 4 + vreq.vifi = pim_ifp->mroute_vif_index; + if (ioctl(pim->mroute_socket, SIOCGETVIFCNT, &vreq)) { + zlog_warn( + "ioctl(SIOCGETVIFCNT=%lu) failure for interface %s vif_index=%d: errno=%d: %s", + (unsigned long)SIOCGETVIFCNT, ifp->name, + pim_ifp->mroute_vif_index, errno, + safe_strerror(errno)); + } +#else + vreq.mifi = pim_ifp->mroute_vif_index; + if (ioctl(pim->mroute_socket, SIOCGETMIFCNT_IN6, &vreq)) { + zlog_warn( + "ioctl(SIOCGETMIFCNT_IN6=%lu) failure for interface %s vif_index=%d: errno=%d: %s", + (unsigned long)SIOCGETMIFCNT_IN6, ifp->name, + pim_ifp->mroute_vif_index, errno, + safe_strerror(errno)); + } +#endif + + if (json) { + json_row = json_object_new_object(); + json_object_string_add(json_row, "name", ifp->name); + json_object_string_add(json_row, "state", + if_is_up(ifp) ? "up" : "down"); + json_object_string_addf(json_row, "address", "%pPA", + &pim_ifp->primary_address); + json_object_int_add(json_row, "ifIndex", ifp->ifindex); + json_object_int_add(json_row, "vif", + pim_ifp->mroute_vif_index); + json_object_int_add(json_row, "pktsIn", + (unsigned long)vreq.icount); + json_object_int_add(json_row, "pktsOut", + (unsigned long)vreq.ocount); + json_object_int_add(json_row, "bytesIn", + (unsigned long)vreq.ibytes); + json_object_int_add(json_row, "bytesOut", + (unsigned long)vreq.obytes); + json_object_object_add(json, ifp->name, json_row); + } else { + ttable_add_row(tt, "%s|%pPAs|%d|%d|%lu|%lu|%lu|%lu", + ifp->name, &pim_ifp->primary_address, + ifp->ifindex, pim_ifp->mroute_vif_index, + (unsigned long)vreq.icount, + (unsigned long)vreq.ocount, + (unsigned long)vreq.ibytes, + (unsigned long)vreq.obytes); + } + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +void pim_cmd_show_ip_multicast_helper(struct pim_instance *pim, struct vty *vty) +{ + struct vrf *vrf = pim->vrf; + time_t now = pim_time_monotonic_sec(); + char uptime[10]; + char mlag_role[80]; + + pim = vrf->info; + + vty_out(vty, "Router MLAG Role: %s\n", + mlag_role2str(router->mlag_role, mlag_role, sizeof(mlag_role))); + vty_out(vty, "Mroute socket descriptor:"); + + vty_out(vty, " %d(%s)\n", pim->mroute_socket, vrf->name); + vty_out(vty, "PIM Register socket descriptor:"); + vty_out(vty, " %d(%s)\n", pim->reg_sock, vrf->name); + + pim_time_uptime(uptime, sizeof(uptime), + now - pim->mroute_socket_creation); + vty_out(vty, "Mroute socket uptime: %s\n", uptime); + + vty_out(vty, "\n"); + + pim_zebra_zclient_update(vty); + pim_zlookup_show_ip_multicast(vty); + + vty_out(vty, "\n"); + vty_out(vty, "Maximum highest VifIndex: %d\n", PIM_MAX_USABLE_VIFS); + + vty_out(vty, "\n"); + vty_out(vty, "Upstream Join Timer: %d secs\n", router->t_periodic); + vty_out(vty, "Join/Prune Holdtime: %d secs\n", PIM_JP_HOLDTIME); + vty_out(vty, "PIM ECMP: %s\n", pim->ecmp_enable ? "Enable" : "Disable"); + vty_out(vty, "PIM ECMP Rebalance: %s\n", + pim->ecmp_rebalance_enable ? "Enable" : "Disable"); + + vty_out(vty, "\n"); + + pim_show_rpf_refresh_stats(vty, pim, now, NULL); + + vty_out(vty, "\n"); + + show_scan_oil_stats(pim, vty, now); + + show_multicast_interfaces(pim, vty, NULL); +} + +void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, + bool fill, json_object *json) +{ + struct listnode *node; + struct channel_oil *c_oil; + struct static_route *s_route; + struct ttable *tt = NULL; + char *table = NULL; + time_t now; + json_object *json_group = NULL; + json_object *json_source = NULL; + json_object *json_oil = NULL; + json_object *json_ifp_out = NULL; + int found_oif; + int first; + char grp_str[PIM_ADDRSTRLEN]; + char src_str[PIM_ADDRSTRLEN]; + char in_ifname[IFNAMSIZ + 1]; + char out_ifname[IFNAMSIZ + 1]; + int oif_vif_index; + struct interface *ifp_in; + char proto[100]; + char state_str[PIM_REG_STATE_STR_LEN]; + char mroute_uptime[10]; + + if (!json) { + vty_out(vty, "IP Multicast Routing Table\n"); + vty_out(vty, "Flags: S - Sparse, C - Connected, P - Pruned\n"); + vty_out(vty, + " R - SGRpt Pruned, F - Register flag, T - SPT-bit set\n"); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, "Source|Group|Flags|Proto|Input|Output|TTL|Uptime"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + now = pim_time_monotonic_sec(); + + /* print list of PIM and IGMP routes */ + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) { + found_oif = 0; + first = 1; + if (!c_oil->installed) + continue; + + if (!pim_addr_is_any(sg->grp) && + pim_addr_cmp(sg->grp, *oil_mcastgrp(c_oil))) + continue; + if (!pim_addr_is_any(sg->src) && + pim_addr_cmp(sg->src, *oil_origin(c_oil))) + continue; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", + oil_mcastgrp(c_oil)); + snprintfrr(src_str, sizeof(src_str), "%pPAs", + oil_origin(c_oil)); + + strlcpy(state_str, "S", sizeof(state_str)); + /* When a non DR receives a igmp join, it creates a (*,G) + * channel_oil without any upstream creation + */ + if (c_oil->up) { + if (PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(c_oil->up->flags)) + strlcat(state_str, "C", sizeof(state_str)); + if (pim_upstream_is_sg_rpt(c_oil->up)) + strlcat(state_str, "R", sizeof(state_str)); + if (PIM_UPSTREAM_FLAG_TEST_FHR(c_oil->up->flags)) + strlcat(state_str, "F", sizeof(state_str)); + if (c_oil->up->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) + strlcat(state_str, "T", sizeof(state_str)); + } + if (pim_channel_oil_empty(c_oil)) + strlcat(state_str, "P", sizeof(state_str)); + + ifp_in = pim_if_find_by_vif_index(pim, *oil_incoming_vif(c_oil)); + + if (ifp_in) + strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); + else + strlcpy(in_ifname, "", sizeof(in_ifname)); + + + pim_time_uptime(mroute_uptime, sizeof(mroute_uptime), + now - c_oil->mroute_creation); + + if (json) { + + /* Find the group, create it if it doesn't exist */ + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + /* Find the source nested under the group, create it if + * it doesn't exist + */ + json_object_object_get_ex(json_group, src_str, + &json_source); + + if (!json_source) { + json_source = json_object_new_object(); + json_object_object_add(json_group, src_str, + json_source); + } + + /* Find the inbound interface nested under the source, + * create it if it doesn't exist + */ + json_object_string_add(json_source, "source", src_str); + json_object_string_add(json_source, "group", grp_str); + json_object_int_add(json_source, "installed", + c_oil->installed); + json_object_int_add(json_source, "refCount", + c_oil->oil_ref_count); + json_object_int_add(json_source, "oilSize", + c_oil->oil_size); + json_object_int_add(json_source, "oilInheritedRescan", + c_oil->oil_inherited_rescan); + json_object_string_add(json_source, "iif", in_ifname); + json_object_string_add(json_source, "upTime", + mroute_uptime); + json_oil = NULL; + } + + for (oif_vif_index = 0; oif_vif_index < MAXVIFS; + ++oif_vif_index) { + struct interface *ifp_out; + int ttl; + + ttl = oil_if_has(c_oil, oif_vif_index); + if (ttl < 1) + continue; + + /* do not display muted OIFs */ + if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_MUTE) + continue; + + if (*oil_incoming_vif(c_oil) == oif_vif_index && + !pim_mroute_allow_iif_in_oil(c_oil, oif_vif_index)) + continue; + + ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); + found_oif = 1; + + if (ifp_out) + strlcpy(out_ifname, ifp_out->name, + sizeof(out_ifname)); + else + strlcpy(out_ifname, "", + sizeof(out_ifname)); + + if (json) { + json_ifp_out = json_object_new_object(); + json_object_string_add(json_ifp_out, "source", + src_str); + json_object_string_add(json_ifp_out, "group", + grp_str); + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_PIM) + json_object_boolean_true_add( + json_ifp_out, "protocolPim"); + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_GM) +#if PIM_IPV == 4 + json_object_boolean_true_add( + json_ifp_out, "protocolIgmp"); +#else + json_object_boolean_true_add( + json_ifp_out, "protocolMld"); +#endif + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_VXLAN) + json_object_boolean_true_add( + json_ifp_out, "protocolVxlan"); + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_STAR) + json_object_boolean_true_add( + json_ifp_out, + "protocolInherited"); + + json_object_string_add(json_ifp_out, + "inboundInterface", + in_ifname); + json_object_int_add(json_ifp_out, "iVifI", + *oil_incoming_vif(c_oil)); + json_object_string_add(json_ifp_out, + "outboundInterface", + out_ifname); + json_object_int_add(json_ifp_out, "oVifI", + oif_vif_index); + json_object_int_add(json_ifp_out, "ttl", ttl); + json_object_string_add(json_ifp_out, "upTime", + mroute_uptime); + json_object_string_add(json_source, "flags", + state_str); + if (!json_oil) { + json_oil = json_object_new_object(); + json_object_object_add(json_source, + "oil", json_oil); + } + json_object_object_add(json_oil, out_ifname, + json_ifp_out); + } else { + proto[0] = '\0'; + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_PIM) { + strlcpy(proto, "PIM", sizeof(proto)); + } + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_GM) { +#if PIM_IPV == 4 + strlcpy(proto, "IGMP", sizeof(proto)); +#else + strlcpy(proto, "MLD", sizeof(proto)); +#endif + } + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_VXLAN) { + strlcpy(proto, "VxLAN", sizeof(proto)); + } + + if (c_oil->oif_flags[oif_vif_index] & + PIM_OIF_FLAG_PROTO_STAR) { + strlcpy(proto, "STAR", sizeof(proto)); + } + + ttable_add_row(tt, "%s|%s|%s|%s|%s|%s|%d|%s", + src_str, grp_str, state_str, + proto, in_ifname, out_ifname, + ttl, mroute_uptime); + + if (first) { + src_str[0] = '\0'; + grp_str[0] = '\0'; + in_ifname[0] = '\0'; + state_str[0] = '\0'; + mroute_uptime[0] = '\0'; + first = 0; + } + } + } + + if (!json && !found_oif) { + ttable_add_row(tt, "%pPAs|%pPAs|%s|%s|%s|%s|%d|%s", + oil_origin(c_oil), oil_mcastgrp(c_oil), + state_str, "none", in_ifname, "none", 0, + "--:--:--"); + } + } + + /* Print list of static routes */ + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { + first = 1; + + if (!s_route->c_oil.installed) + continue; + + snprintfrr(grp_str, sizeof(grp_str), "%pPAs", &s_route->group); + snprintfrr(src_str, sizeof(src_str), "%pPAs", &s_route->source); + ifp_in = pim_if_find_by_vif_index(pim, s_route->iif); + found_oif = 0; + + if (ifp_in) + strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); + else + strlcpy(in_ifname, "", sizeof(in_ifname)); + + if (json) { + + /* Find the group, create it if it doesn't exist */ + json_object_object_get_ex(json, grp_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + + /* Find the source nested under the group, create it if + * it doesn't exist + */ + json_object_object_get_ex(json_group, src_str, + &json_source); + + if (!json_source) { + json_source = json_object_new_object(); + json_object_object_add(json_group, src_str, + json_source); + } + + json_object_string_add(json_source, "iif", in_ifname); + json_oil = NULL; + } else { + strlcpy(proto, "STATIC", sizeof(proto)); + } + + for (oif_vif_index = 0; oif_vif_index < MAXVIFS; + ++oif_vif_index) { + struct interface *ifp_out; + char oif_uptime[10]; + int ttl; + + ttl = s_route->oif_ttls[oif_vif_index]; + if (ttl < 1) + continue; + + ifp_out = pim_if_find_by_vif_index(pim, oif_vif_index); + pim_time_uptime( + oif_uptime, sizeof(oif_uptime), + now - s_route->c_oil + .oif_creation[oif_vif_index]); + found_oif = 1; + + if (ifp_out) + strlcpy(out_ifname, ifp_out->name, + sizeof(out_ifname)); + else + strlcpy(out_ifname, "", + sizeof(out_ifname)); + + if (json) { + json_ifp_out = json_object_new_object(); + json_object_string_add(json_ifp_out, "source", + src_str); + json_object_string_add(json_ifp_out, "group", + grp_str); + json_object_boolean_true_add(json_ifp_out, + "protocolStatic"); + json_object_string_add(json_ifp_out, + "inboundInterface", + in_ifname); + json_object_int_add(json_ifp_out, "iVifI", + *oil_incoming_vif( + &s_route->c_oil)); + json_object_string_add(json_ifp_out, + "outboundInterface", + out_ifname); + json_object_int_add(json_ifp_out, "oVifI", + oif_vif_index); + json_object_int_add(json_ifp_out, "ttl", ttl); + json_object_string_add(json_ifp_out, "upTime", + oif_uptime); + if (!json_oil) { + json_oil = json_object_new_object(); + json_object_object_add(json_source, + "oil", json_oil); + } + json_object_object_add(json_oil, out_ifname, + json_ifp_out); + } else { + ttable_add_row( + tt, "%pPAs|%pPAs|%s|%s|%s|%s|%d|%s", + &s_route->source, &s_route->group, "-", + proto, in_ifname, out_ifname, ttl, + oif_uptime); + if (first && !fill) { + src_str[0] = '\0'; + grp_str[0] = '\0'; + in_ifname[0] = '\0'; + first = 0; + } + } + } + + if (!json && !found_oif) { + ttable_add_row(tt, "%pPAs|%pPAs|%s|%s|%s|%s|%d|%s", + &s_route->source, &s_route->group, "-", + proto, in_ifname, "none", 0, "--:--:--"); + } + } + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +static void show_mroute_count_per_channel_oil(struct channel_oil *c_oil, + json_object *json, + struct ttable *tt) +{ + json_object *json_group = NULL; + json_object *json_source = NULL; + + if (!c_oil->installed) + return; + + pim_mroute_update_counters(c_oil); + + if (json) { + char group_str[PIM_ADDRSTRLEN]; + char source_str[PIM_ADDRSTRLEN]; + + snprintfrr(group_str, sizeof(group_str), "%pPAs", + oil_mcastgrp(c_oil)); + snprintfrr(source_str, sizeof(source_str), "%pPAs", + oil_origin(c_oil)); + + json_object_object_get_ex(json, group_str, &json_group); + + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, group_str, json_group); + } + + json_source = json_object_new_object(); + json_object_object_add(json_group, source_str, json_source); + json_object_int_add(json_source, "lastUsed", + c_oil->cc.lastused / 100); + json_object_int_add(json_source, "packets", c_oil->cc.pktcnt); + json_object_int_add(json_source, "bytes", c_oil->cc.bytecnt); + json_object_int_add(json_source, "wrongIf", c_oil->cc.wrong_if); + + } else { + ttable_add_row(tt, "%pPAs|%pPAs|%llu|%ld|%ld|%ld", + oil_origin(c_oil), oil_mcastgrp(c_oil), + c_oil->cc.lastused / 100, + c_oil->cc.pktcnt - c_oil->cc.origpktcnt, + c_oil->cc.bytecnt - c_oil->cc.origbytecnt, + c_oil->cc.wrong_if - c_oil->cc.origwrong_if); + } +} + +void show_mroute_count(struct pim_instance *pim, struct vty *vty, + json_object *json) +{ + struct listnode *node; + struct channel_oil *c_oil; + struct static_route *sr; + struct ttable *tt = NULL; + char *table = NULL; + + if (!json) { + vty_out(vty, "\n"); + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, + "Source|Group|LastUsed|Packets|Bytes|WrongIf"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + /* Print PIM and IGMP route counts */ + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) + show_mroute_count_per_channel_oil(c_oil, json, tt); + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sr)) + show_mroute_count_per_channel_oil(&sr->c_oil, json, tt); + + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } +} + +void show_mroute_summary(struct pim_instance *pim, struct vty *vty, + json_object *json) +{ + struct listnode *node; + struct channel_oil *c_oil; + struct static_route *s_route; + uint32_t starg_sw_mroute_cnt = 0; + uint32_t sg_sw_mroute_cnt = 0; + uint32_t starg_hw_mroute_cnt = 0; + uint32_t sg_hw_mroute_cnt = 0; + json_object *json_starg = NULL; + json_object *json_sg = NULL; + + if (!json) + vty_out(vty, "Mroute Type Installed/Total\n"); + + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) { + if (!c_oil->installed) { + if (pim_addr_is_any(*oil_origin(c_oil))) + starg_sw_mroute_cnt++; + else + sg_sw_mroute_cnt++; + } else { + if (pim_addr_is_any(*oil_origin(c_oil))) + starg_hw_mroute_cnt++; + else + sg_hw_mroute_cnt++; + } + } + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { + if (!s_route->c_oil.installed) { + if (pim_addr_is_any(*oil_origin(&s_route->c_oil))) + starg_sw_mroute_cnt++; + else + sg_sw_mroute_cnt++; + } else { + if (pim_addr_is_any(*oil_origin(&s_route->c_oil))) + starg_hw_mroute_cnt++; + else + sg_hw_mroute_cnt++; + } + } + + if (!json) { + vty_out(vty, "%-20s %u/%u\n", "(*, G)", starg_hw_mroute_cnt, + starg_sw_mroute_cnt + starg_hw_mroute_cnt); + vty_out(vty, "%-20s %u/%u\n", "(S, G)", sg_hw_mroute_cnt, + sg_sw_mroute_cnt + sg_hw_mroute_cnt); + vty_out(vty, "------\n"); + vty_out(vty, "%-20s %u/%u\n", "Total", + (starg_hw_mroute_cnt + sg_hw_mroute_cnt), + (starg_sw_mroute_cnt + starg_hw_mroute_cnt + + sg_sw_mroute_cnt + sg_hw_mroute_cnt)); + } else { + /* (*,G) route details */ + json_starg = json_object_new_object(); + json_object_object_add(json, "wildcardGroup", json_starg); + + json_object_int_add(json_starg, "installed", + starg_hw_mroute_cnt); + json_object_int_add(json_starg, "total", + starg_sw_mroute_cnt + starg_hw_mroute_cnt); + + /* (S, G) route details */ + json_sg = json_object_new_object(); + json_object_object_add(json, "sourceGroup", json_sg); + + json_object_int_add(json_sg, "installed", sg_hw_mroute_cnt); + json_object_int_add(json_sg, "total", + sg_sw_mroute_cnt + sg_hw_mroute_cnt); + + json_object_int_add(json, "totalNumOfInstalledMroutes", + starg_hw_mroute_cnt + sg_hw_mroute_cnt); + json_object_int_add(json, "totalNumOfMroutes", + starg_sw_mroute_cnt + starg_hw_mroute_cnt + + sg_sw_mroute_cnt + + sg_hw_mroute_cnt); + } +} + +int clear_ip_mroute_count_command(struct vty *vty, const char *name) +{ + struct listnode *node; + struct channel_oil *c_oil; + struct static_route *sr; + struct vrf *v = pim_cmd_lookup(vty, name); + struct pim_instance *pim; + + if (!v) + return CMD_WARNING; + + pim = v->info; + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) { + if (!c_oil->installed) + continue; + + pim_mroute_update_counters(c_oil); + c_oil->cc.origpktcnt = c_oil->cc.pktcnt; + c_oil->cc.origbytecnt = c_oil->cc.bytecnt; + c_oil->cc.origwrong_if = c_oil->cc.wrong_if; + } + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sr)) { + if (!sr->c_oil.installed) + continue; + + pim_mroute_update_counters(&sr->c_oil); + + sr->c_oil.cc.origpktcnt = sr->c_oil.cc.pktcnt; + sr->c_oil.cc.origbytecnt = sr->c_oil.cc.bytecnt; + sr->c_oil.cc.origwrong_if = sr->c_oil.cc.wrong_if; + } + return CMD_SUCCESS; +} + +struct vrf *pim_cmd_lookup(struct vty *vty, const char *name) +{ + struct vrf *vrf; + + if (name) + vrf = vrf_lookup_by_name(name); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) + vty_out(vty, "Specified VRF: %s does not exist\n", name); + + return vrf; +} + +void clear_mroute(struct pim_instance *pim) +{ + struct pim_upstream *up; + struct interface *ifp; + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct pim_ifchannel *ch; + + if (!pim_ifp) + continue; + + /* deleting all ifchannels */ + while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { + ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); + + pim_ifchannel_delete(ch); + } + +#if PIM_IPV == 4 + /* clean up all igmp groups */ + struct gm_group *grp; + + if (pim_ifp->gm_group_list) { + while (pim_ifp->gm_group_list->count) { + grp = listnode_head(pim_ifp->gm_group_list); + igmp_group_delete(grp); + } + } +#else + struct gm_if *gm_ifp; + + gm_ifp = pim_ifp->mld; + if (gm_ifp) + gm_group_delete(gm_ifp); +#endif + } + + /* clean up all upstreams*/ + while ((up = rb_pim_upstream_first(&pim->upstream_head))) + pim_upstream_del(pim, up, __func__); +} + +void clear_pim_statistics(struct pim_instance *pim) +{ + struct interface *ifp; + + pim->bsm_rcvd = 0; + pim->bsm_sent = 0; + pim->bsm_dropped = 0; + + /* scan interfaces */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + pim_ifp->pim_ifstat_bsm_cfg_miss = 0; + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss = 0; + pim_ifp->pim_ifstat_bsm_invalid_sz = 0; + } +} + +int clear_pim_interface_traffic(const char *vrf, struct vty *vty) +{ + struct interface *ifp = NULL; + struct pim_interface *pim_ifp = NULL; + + struct vrf *v = pim_cmd_lookup(vty, vrf); + + if (!v) + return CMD_WARNING; + + FOR_ALL_INTERFACES (v, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + pim_ifp->pim_ifstat_hello_recv = 0; + pim_ifp->pim_ifstat_hello_sent = 0; + pim_ifp->pim_ifstat_join_recv = 0; + pim_ifp->pim_ifstat_join_send = 0; + pim_ifp->pim_ifstat_prune_recv = 0; + pim_ifp->pim_ifstat_prune_send = 0; + pim_ifp->pim_ifstat_reg_recv = 0; + pim_ifp->pim_ifstat_reg_send = 0; + pim_ifp->pim_ifstat_reg_stop_recv = 0; + pim_ifp->pim_ifstat_reg_stop_send = 0; + pim_ifp->pim_ifstat_assert_recv = 0; + pim_ifp->pim_ifstat_assert_send = 0; + pim_ifp->pim_ifstat_bsm_rx = 0; + pim_ifp->pim_ifstat_bsm_tx = 0; +#if PIM_IPV == 4 + pim_ifp->igmp_ifstat_joins_sent = 0; + pim_ifp->igmp_ifstat_joins_failed = 0; + pim_ifp->igmp_peak_group_count = 0; +#endif + } + + return CMD_SUCCESS; +} + +int pim_debug_pim_cmd(void) +{ + PIM_DO_DEBUG_PIM_EVENTS; + PIM_DO_DEBUG_PIM_PACKETS; + PIM_DO_DEBUG_PIM_TRACE; + PIM_DO_DEBUG_MSDP_EVENTS; + PIM_DO_DEBUG_MSDP_PACKETS; + PIM_DO_DEBUG_BSM; + PIM_DO_DEBUG_VXLAN; + return CMD_SUCCESS; +} + +int pim_no_debug_pim_cmd(void) +{ + PIM_DONT_DEBUG_PIM_EVENTS; + PIM_DONT_DEBUG_PIM_PACKETS; + PIM_DONT_DEBUG_PIM_TRACE; + PIM_DONT_DEBUG_MSDP_EVENTS; + PIM_DONT_DEBUG_MSDP_PACKETS; + + PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND; + PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV; + PIM_DONT_DEBUG_BSM; + PIM_DONT_DEBUG_VXLAN; + return CMD_SUCCESS; +} + +int pim_debug_pim_packets_cmd(const char *hello, const char *joins, + const char *registers, struct vty *vty) +{ + if (hello) { + PIM_DO_DEBUG_PIM_HELLO; + vty_out(vty, "PIM Hello debugging is on\n"); + } else if (joins) { + PIM_DO_DEBUG_PIM_J_P; + vty_out(vty, "PIM Join/Prune debugging is on\n"); + } else if (registers) { + PIM_DO_DEBUG_PIM_REG; + vty_out(vty, "PIM Register debugging is on\n"); + } else { + PIM_DO_DEBUG_PIM_PACKETS; + vty_out(vty, "PIM Packet debugging is on\n"); + } + return CMD_SUCCESS; +} + +int pim_no_debug_pim_packets_cmd(const char *hello, const char *joins, + const char *registers, struct vty *vty) +{ + if (hello) { + PIM_DONT_DEBUG_PIM_HELLO; + vty_out(vty, "PIM Hello debugging is off\n"); + } else if (joins) { + PIM_DONT_DEBUG_PIM_J_P; + vty_out(vty, "PIM Join/Prune debugging is off\n"); + } else if (registers) { + PIM_DONT_DEBUG_PIM_REG; + vty_out(vty, "PIM Register debugging is off\n"); + } else { + PIM_DONT_DEBUG_PIM_PACKETS; + vty_out(vty, "PIM Packet debugging is off\n"); + } + + return CMD_SUCCESS; +} + +int pim_show_rpf_helper(const char *vrf, struct vty *vty, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + pim_show_rpf(pim, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_rpf_vrf_all_helper(struct vty *vty, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + pim_show_rpf(vrf->info, vty, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_rp_helper(const char *vrf, struct vty *vty, const char *group_str, + const struct prefix *group, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + struct prefix *range = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (group_str) { + range = prefix_new(); + prefix_copy(range, group); + apply_mask(range); + } + + if (json) + json_parent = json_object_new_object(); + + pim_rp_show_information(pim, range, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + prefix_free(&range); + + return CMD_SUCCESS; +} + +int pim_show_rp_vrf_all_helper(struct vty *vty, const char *group_str, + const struct prefix *group, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + struct prefix *range = NULL; + + if (group_str) { + range = prefix_new(); + prefix_copy(range, group); + apply_mask(range); + } + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + pim_rp_show_information(vrf->info, range, vty, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + prefix_free(&range); + + return CMD_SUCCESS; +} + +int pim_show_secondary_helper(const char *vrf, struct vty *vty) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_neighbors_secondary(pim, vty); + + return CMD_SUCCESS; +} + +int pim_show_statistics_helper(const char *vrf, struct vty *vty, + const char *word, bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (word) + pim_show_statistics(pim, vty, word, uj); + else + pim_show_statistics(pim, vty, NULL, uj); + + return CMD_SUCCESS; +} + +int pim_show_upstream_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, bool json) +{ + pim_sgaddr sg = {0}; + struct vrf *v; + struct pim_instance *pim; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) { + vty_out(vty, "%% Vrf specified: %s does not exist\n", vrf); + return CMD_WARNING; + } + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + if (!pim_addr_is_any(s_or_g)) { + if (!pim_addr_is_any(g)) { + sg.src = s_or_g; + sg.grp = g; + } else + sg.grp = s_or_g; + } + + pim_show_upstream(pim, vty, &sg, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_upstream_vrf_all_helper(struct vty *vty, bool json) +{ + pim_sgaddr sg = {0}; + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + pim_show_upstream(vrf->info, vty, &sg, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_upstream_join_desired_helper(const char *vrf, struct vty *vty, + bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_join_desired(pim, vty, uj); + + return CMD_SUCCESS; +} + +int pim_show_upstream_rpf_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_upstream_rpf(pim, vty, uj); + + return CMD_SUCCESS; +} + +int pim_show_state_helper(const char *vrf, struct vty *vty, + const char *s_or_g_str, const char *g_str, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + pim_show_state(pim, vty, s_or_g_str, g_str, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_state_vrf_all_helper(struct vty *vty, const char *s_or_g_str, + const char *g_str, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + pim_show_state(vrf->info, vty, s_or_g_str, g_str, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_multicast_helper(const char *vrf, struct vty *vty) +{ + struct vrf *v; + struct pim_instance *pim; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_cmd_show_ip_multicast_helper(pim, vty); + + return CMD_SUCCESS; +} + +int pim_show_multicast_vrf_all_helper(struct vty *vty) +{ + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + vty_out(vty, "VRF: %s\n", vrf->name); + pim_cmd_show_ip_multicast_helper(vrf->info, vty); + } + + return CMD_SUCCESS; +} + +int pim_show_multicast_count_helper(const char *vrf, struct vty *vty, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + show_multicast_interfaces(pim, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_multicast_count_vrf_all_helper(struct vty *vty, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + + show_multicast_interfaces(vrf->info, vty, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, bool fill, bool json) +{ + pim_sgaddr sg = {0}; + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + if (!pim_addr_is_any(s_or_g)) { + if (!pim_addr_is_any(g)) { + sg.src = s_or_g; + sg.grp = g; + } else + sg.grp = s_or_g; + } + + show_mroute(pim, vty, &sg, fill, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_vrf_all_helper(struct vty *vty, bool fill, bool json) +{ + pim_sgaddr sg = {0}; + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + show_mroute(vrf->info, vty, &sg, fill, json_vrf); + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_count_helper(const char *vrf, struct vty *vty, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + show_mroute_count(pim, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_count_vrf_all_helper(struct vty *vty, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + + show_mroute_count(vrf->info, vty, json_vrf); + + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_summary_helper(const char *vrf, struct vty *vty, bool json) +{ + struct pim_instance *pim; + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + show_mroute_summary(pim, vty, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +int pim_show_mroute_summary_vrf_all_helper(struct vty *vty, bool json) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + + show_mroute_summary(vrf->info, vty, json_vrf); + + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +void pim_show_interface_traffic(struct pim_instance *pim, struct vty *vty, + bool uj) +{ + struct interface *ifp = NULL; + struct pim_interface *pim_ifp = NULL; + json_object *json = NULL; + json_object *json_row = NULL; + + if (uj) + json = json_object_new_object(); + else { + vty_out(vty, "\n"); + vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", + "Interface", " HELLO", " JOIN", + " PRUNE", " REGISTER", "REGISTER-STOP", + " ASSERT", " BSM"); + vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", "", + " Rx/Tx", " Rx/Tx", " Rx/Tx", + " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx"); + vty_out(vty, + "---------------------------------------------------------------------------------------------------------------\n"); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (uj) { + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + json_object_int_add(json_row, "helloRx", + pim_ifp->pim_ifstat_hello_recv); + json_object_int_add(json_row, "helloTx", + pim_ifp->pim_ifstat_hello_sent); + json_object_int_add(json_row, "joinRx", + pim_ifp->pim_ifstat_join_recv); + json_object_int_add(json_row, "joinTx", + pim_ifp->pim_ifstat_join_send); + json_object_int_add(json_row, "pruneRx", + pim_ifp->pim_ifstat_prune_recv); + json_object_int_add(json_row, "pruneTx", + pim_ifp->pim_ifstat_prune_send); + json_object_int_add(json_row, "registerRx", + pim_ifp->pim_ifstat_reg_recv); + json_object_int_add(json_row, "registerTx", + pim_ifp->pim_ifstat_reg_send); + json_object_int_add(json_row, "registerStopRx", + pim_ifp->pim_ifstat_reg_stop_recv); + json_object_int_add(json_row, "registerStopTx", + pim_ifp->pim_ifstat_reg_stop_send); + json_object_int_add(json_row, "assertRx", + pim_ifp->pim_ifstat_assert_recv); + json_object_int_add(json_row, "assertTx", + pim_ifp->pim_ifstat_assert_send); + json_object_int_add(json_row, "bsmRx", + pim_ifp->pim_ifstat_bsm_rx); + json_object_int_add(json_row, "bsmTx", + pim_ifp->pim_ifstat_bsm_tx); + json_object_object_add(json, ifp->name, json_row); + } else { + vty_out(vty, + "%-16s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7" PRIu64 + "/%-7" PRIu64 "\n", + ifp->name, pim_ifp->pim_ifstat_hello_recv, + pim_ifp->pim_ifstat_hello_sent, + pim_ifp->pim_ifstat_join_recv, + pim_ifp->pim_ifstat_join_send, + pim_ifp->pim_ifstat_prune_recv, + pim_ifp->pim_ifstat_prune_send, + pim_ifp->pim_ifstat_reg_recv, + pim_ifp->pim_ifstat_reg_send, + pim_ifp->pim_ifstat_reg_stop_recv, + pim_ifp->pim_ifstat_reg_stop_send, + pim_ifp->pim_ifstat_assert_recv, + pim_ifp->pim_ifstat_assert_send, + pim_ifp->pim_ifstat_bsm_rx, + pim_ifp->pim_ifstat_bsm_tx); + } + } + if (uj) + vty_json(vty, json); +} + +void pim_show_interface_traffic_single(struct pim_instance *pim, + struct vty *vty, const char *ifname, + bool uj) +{ + struct interface *ifp = NULL; + struct pim_interface *pim_ifp = NULL; + json_object *json = NULL; + json_object *json_row = NULL; + uint8_t found_ifname = 0; + + if (uj) + json = json_object_new_object(); + else { + vty_out(vty, "\n"); + vty_out(vty, "%-16s%-17s%-17s%-17s%-17s%-17s%-17s%-17s\n", + "Interface", " HELLO", " JOIN", " PRUNE", + " REGISTER", " REGISTER-STOP", " ASSERT", + " BSM"); + vty_out(vty, "%-14s%-18s%-17s%-17s%-17s%-17s%-17s%-17s\n", "", + " Rx/Tx", " Rx/Tx", " Rx/Tx", " Rx/Tx", + " Rx/Tx", " Rx/Tx", " Rx/Tx"); + vty_out(vty, + "-------------------------------------------------------------------------------------------------------------------------------\n"); + } + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + if (strcmp(ifname, ifp->name)) + continue; + + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + found_ifname = 1; + if (uj) { + json_row = json_object_new_object(); + json_object_pim_ifp_add(json_row, ifp); + json_object_int_add(json_row, "helloRx", + pim_ifp->pim_ifstat_hello_recv); + json_object_int_add(json_row, "helloTx", + pim_ifp->pim_ifstat_hello_sent); + json_object_int_add(json_row, "joinRx", + pim_ifp->pim_ifstat_join_recv); + json_object_int_add(json_row, "joinTx", + pim_ifp->pim_ifstat_join_send); + json_object_int_add(json_row, "pruneRx", + pim_ifp->pim_ifstat_prune_recv); + json_object_int_add(json_row, "pruneTx", + pim_ifp->pim_ifstat_prune_send); + json_object_int_add(json_row, "registerRx", + pim_ifp->pim_ifstat_reg_recv); + json_object_int_add(json_row, "registerTx", + pim_ifp->pim_ifstat_reg_send); + json_object_int_add(json_row, "registerStopRx", + pim_ifp->pim_ifstat_reg_stop_recv); + json_object_int_add(json_row, "registerStopTx", + pim_ifp->pim_ifstat_reg_stop_send); + json_object_int_add(json_row, "assertRx", + pim_ifp->pim_ifstat_assert_recv); + json_object_int_add(json_row, "assertTx", + pim_ifp->pim_ifstat_assert_send); + json_object_int_add(json_row, "bsmRx", + pim_ifp->pim_ifstat_bsm_rx); + json_object_int_add(json_row, "bsmTx", + pim_ifp->pim_ifstat_bsm_tx); + + json_object_object_add(json, ifp->name, json_row); + } else { + vty_out(vty, + "%-16s %8u/%-8u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7u/%-7u %7" PRIu64 + "/%-7" PRIu64 "\n", + ifp->name, pim_ifp->pim_ifstat_hello_recv, + pim_ifp->pim_ifstat_hello_sent, + pim_ifp->pim_ifstat_join_recv, + pim_ifp->pim_ifstat_join_send, + pim_ifp->pim_ifstat_prune_recv, + pim_ifp->pim_ifstat_prune_send, + pim_ifp->pim_ifstat_reg_recv, + pim_ifp->pim_ifstat_reg_send, + pim_ifp->pim_ifstat_reg_stop_recv, + pim_ifp->pim_ifstat_reg_stop_send, + pim_ifp->pim_ifstat_assert_recv, + pim_ifp->pim_ifstat_assert_send, + pim_ifp->pim_ifstat_bsm_rx, + pim_ifp->pim_ifstat_bsm_tx); + } + } + if (uj) + vty_json(vty, json); + else if (!found_ifname) + vty_out(vty, "%% No such interface\n"); +} + +int pim_show_interface_traffic_helper(const char *vrf, const char *if_name, + struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (if_name) + pim_show_interface_traffic_single(v->info, vty, if_name, uj); + else + pim_show_interface_traffic(v->info, vty, uj); + + return CMD_SUCCESS; +} + +void clear_pim_interfaces(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + if (ifp->info) + pim_neighbor_delete_all(ifp, "interface cleared"); + } +} + +void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj) +{ + char uptime[10]; + char last_bsm_seen[10]; + time_t now; + char bsr_state[20]; + json_object *json = NULL; + + if (pim_addr_is_any(pim->global_scope.current_bsr)) { + pim_time_uptime(uptime, sizeof(uptime), + pim->global_scope.current_bsr_first_ts); + pim_time_uptime(last_bsm_seen, sizeof(last_bsm_seen), + pim->global_scope.current_bsr_last_ts); + } + + else { + now = pim_time_monotonic_sec(); + pim_time_uptime(uptime, sizeof(uptime), + (now - pim->global_scope.current_bsr_first_ts)); + pim_time_uptime(last_bsm_seen, sizeof(last_bsm_seen), + now - pim->global_scope.current_bsr_last_ts); + } + + switch (pim->global_scope.state) { + case NO_INFO: + strlcpy(bsr_state, "NO_INFO", sizeof(bsr_state)); + break; + case ACCEPT_ANY: + strlcpy(bsr_state, "ACCEPT_ANY", sizeof(bsr_state)); + break; + case ACCEPT_PREFERRED: + strlcpy(bsr_state, "ACCEPT_PREFERRED", sizeof(bsr_state)); + break; + default: + strlcpy(bsr_state, "", sizeof(bsr_state)); + } + + + if (uj) { + json = json_object_new_object(); + json_object_string_addf(json, "bsr", "%pPA", + &pim->global_scope.current_bsr); + json_object_int_add(json, "priority", + pim->global_scope.current_bsr_prio); + json_object_int_add(json, "fragmentTag", + pim->global_scope.bsm_frag_tag); + json_object_string_add(json, "state", bsr_state); + json_object_string_add(json, "upTime", uptime); + json_object_string_add(json, "lastBsmSeen", last_bsm_seen); + } + + else { + vty_out(vty, "PIMv2 Bootstrap information\n"); + vty_out(vty, "Current preferred BSR address: %pPA\n", + &pim->global_scope.current_bsr); + vty_out(vty, + "Priority Fragment-Tag State UpTime\n"); + vty_out(vty, " %-12d %-12d %-13s %7s\n", + pim->global_scope.current_bsr_prio, + pim->global_scope.bsm_frag_tag, bsr_state, uptime); + vty_out(vty, "Last BSM seen: %s\n", last_bsm_seen); + } + + if (uj) + vty_json(vty, json); +} + +int pim_show_bsr_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = pim_get_pim_instance(v->vrf_id); + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_bsr(v->info, vty, uj); + + return CMD_SUCCESS; +} + +/*Display the group-rp mappings */ +static void pim_show_group_rp_mappings_info(struct pim_instance *pim, + struct vty *vty, bool uj) +{ + struct bsgrp_node *bsgrp; + struct bsm_rpinfo *bsm_rp; + struct route_node *rn; + json_object *json = NULL; + json_object *json_group = NULL; + json_object *json_row = NULL; + struct ttable *tt = NULL; + + if (uj) { + json = json_object_new_object(); + json_object_string_addf(json, "BSR Address", "%pPA", + &pim->global_scope.current_bsr); + } else + vty_out(vty, "BSR Address %pPA\n", + &pim->global_scope.current_bsr); + + for (rn = route_top(pim->global_scope.bsrp_table); rn; + rn = route_next(rn)) { + bsgrp = (struct bsgrp_node *)rn->info; + + if (!bsgrp) + continue; + + char grp_str[PREFIX_STRLEN]; + + prefix2str(&bsgrp->group, grp_str, sizeof(grp_str)); + + if (uj) { + json_object_object_get_ex(json, grp_str, &json_group); + if (!json_group) { + json_group = json_object_new_object(); + json_object_object_add(json, grp_str, + json_group); + } + } else { + vty_out(vty, "Group Address %pFX\n", &bsgrp->group); + vty_out(vty, "--------------------------\n"); + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "Rp Address|priority|Holdtime|Hash"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + ttable_add_row(tt, "%s|%c|%c|%c", "(ACTIVE)", ' ', ' ', + ' '); + } + + frr_each (bsm_rpinfos, bsgrp->bsrp_list, bsm_rp) { + if (uj) { + json_row = json_object_new_object(); + json_object_string_addf(json_row, "Rp Address", + "%pPA", + &bsm_rp->rp_address); + json_object_int_add(json_row, "Rp HoldTime", + bsm_rp->rp_holdtime); + json_object_int_add(json_row, "Rp Priority", + bsm_rp->rp_prio); + json_object_int_add(json_row, "Hash Val", + bsm_rp->hash); + json_object_object_addf(json_group, json_row, + "%pPA", + &bsm_rp->rp_address); + + } else { + ttable_add_row( + tt, "%pPA|%u|%u|%u", + &bsm_rp->rp_address, bsm_rp->rp_prio, + bsm_rp->rp_holdtime, bsm_rp->hash); + } + } + /* Dump the generated table. */ + if (tt) { + char *table = NULL; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + tt = NULL; + } + if (!bsm_rpinfos_count(bsgrp->bsrp_list) && !uj) + vty_out(vty, "Active List is empty.\n"); + + if (uj) { + json_object_int_add(json_group, "Pending RP count", + bsgrp->pend_rp_cnt); + } else { + vty_out(vty, "(PENDING)\n"); + vty_out(vty, "Pending RP count :%d\n", + bsgrp->pend_rp_cnt); + if (bsgrp->pend_rp_cnt) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "Rp Address|priority|Holdtime|Hash"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + } + + frr_each (bsm_rpinfos, bsgrp->partial_bsrp_list, bsm_rp) { + if (uj) { + json_row = json_object_new_object(); + json_object_string_addf(json_row, "Rp Address", + "%pPA", + &bsm_rp->rp_address); + json_object_int_add(json_row, "Rp HoldTime", + bsm_rp->rp_holdtime); + json_object_int_add(json_row, "Rp Priority", + bsm_rp->rp_prio); + json_object_int_add(json_row, "Hash Val", + bsm_rp->hash); + json_object_object_addf(json_group, json_row, + "%pPA", + &bsm_rp->rp_address); + } else { + ttable_add_row( + tt, "%pPA|%u|%u|%u", + &bsm_rp->rp_address, bsm_rp->rp_prio, + bsm_rp->rp_holdtime, bsm_rp->hash); + } + } + /* Dump the generated table. */ + if (tt) { + char *table = NULL; + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } + if (!bsm_rpinfos_count(bsgrp->partial_bsrp_list) && !uj) + vty_out(vty, "Partial List is empty\n"); + + if (!uj) + vty_out(vty, "\n"); + } + + if (uj) + vty_json(vty, json); +} + +int pim_show_group_rp_mappings_info_helper(const char *vrf, struct vty *vty, + bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_group_rp_mappings_info(v->info, vty, uj); + + return CMD_SUCCESS; +} + +/* Display the bsm database details */ +static void pim_show_bsm_db(struct pim_instance *pim, struct vty *vty, bool uj) +{ + int count = 0; + int fragment = 1; + struct bsm_frag *bsfrag; + json_object *json = NULL; + json_object *json_group = NULL; + json_object *json_row = NULL; + + count = bsm_frags_count(pim->global_scope.bsm_frags); + + if (uj) { + json = json_object_new_object(); + json_object_int_add(json, "Number of the fragments", count); + } else { + vty_out(vty, "Scope Zone: Global\n"); + vty_out(vty, "Number of the fragments: %d\n", count); + vty_out(vty, "\n"); + } + + frr_each (bsm_frags, pim->global_scope.bsm_frags, bsfrag) { + char grp_str[PREFIX_STRLEN]; + struct bsmmsg_grpinfo *group; + struct bsmmsg_rpinfo *bsm_rpinfo; + struct prefix grp; + struct bsm_hdr *hdr; + pim_addr bsr_addr; + uint32_t offset = 0; + uint8_t *buf; + uint32_t len = 0; + uint32_t frag_rp_cnt = 0; + + buf = bsfrag->data; + len = bsfrag->size; + + /* skip pim header */ + buf += PIM_MSG_HEADER_LEN; + len -= PIM_MSG_HEADER_LEN; + + hdr = (struct bsm_hdr *)buf; + /* NB: bshdr->bsr_addr.addr is packed/unaligned => memcpy */ + memcpy(&bsr_addr, &hdr->bsr_addr.addr, sizeof(bsr_addr)); + + /* BSM starts with bsr header */ + buf += sizeof(struct bsm_hdr); + len -= sizeof(struct bsm_hdr); + + if (uj) { + json_object_string_addf(json, "BSR address", "%pPA", + &bsr_addr); + json_object_int_add(json, "BSR priority", + hdr->bsr_prio); + json_object_int_add(json, "Hashmask Length", + hdr->hm_len); + json_object_int_add(json, "Fragment Tag", + ntohs(hdr->frag_tag)); + } else { + vty_out(vty, "BSM Fragment : %d\n", fragment); + vty_out(vty, "------------------\n"); + vty_out(vty, "%-15s %-15s %-15s %-15s\n", "BSR-Address", + "BSR-Priority", "Hashmask-len", "Fragment-Tag"); + vty_out(vty, "%-15pPA %-15d %-15d %-15d\n", &bsr_addr, + hdr->bsr_prio, hdr->hm_len, + ntohs(hdr->frag_tag)); + } + + vty_out(vty, "\n"); + + while (offset < len) { + group = (struct bsmmsg_grpinfo *)buf; + + if (group->group.family == PIM_MSG_ADDRESS_FAMILY_IPV4) + grp.family = AF_INET; + else if (group->group.family == + PIM_MSG_ADDRESS_FAMILY_IPV6) + grp.family = AF_INET6; + + grp.prefixlen = group->group.mask; +#if PIM_IPV == 4 + grp.u.prefix4 = group->group.addr; +#else + grp.u.prefix6 = group->group.addr; +#endif + + prefix2str(&grp, grp_str, sizeof(grp_str)); + + buf += sizeof(struct bsmmsg_grpinfo); + offset += sizeof(struct bsmmsg_grpinfo); + + if (uj) { + json_object_object_get_ex(json, grp_str, + &json_group); + if (!json_group) { + json_group = json_object_new_object(); + json_object_int_add(json_group, + "Rp Count", + group->rp_count); + json_object_int_add( + json_group, "Fragment Rp count", + group->frag_rp_count); + json_object_object_add(json, grp_str, + json_group); + } + } else { + vty_out(vty, "Group : %s\n", grp_str); + vty_out(vty, "-------------------\n"); + vty_out(vty, "Rp Count:%d\n", group->rp_count); + vty_out(vty, "Fragment Rp Count : %d\n", + group->frag_rp_count); + } + + frag_rp_cnt = group->frag_rp_count; + + if (!frag_rp_cnt) + continue; + + if (!uj) + vty_out(vty, + "RpAddress HoldTime Priority\n"); + + while (frag_rp_cnt--) { + pim_addr rp_addr; + + bsm_rpinfo = (struct bsmmsg_rpinfo *)buf; + /* unaligned, again */ + memcpy(&rp_addr, &bsm_rpinfo->rpaddr.addr, + sizeof(rp_addr)); + + buf += sizeof(struct bsmmsg_rpinfo); + offset += sizeof(struct bsmmsg_rpinfo); + + if (uj) { + json_row = json_object_new_object(); + json_object_string_addf( + json_row, "Rp Address", "%pPA", + &rp_addr); + json_object_int_add( + json_row, "Rp HoldTime", + ntohs(bsm_rpinfo->rp_holdtime)); + json_object_int_add(json_row, + "Rp Priority", + bsm_rpinfo->rp_pri); + json_object_object_addf( + json_group, json_row, "%pPA", + &rp_addr); + } else { + vty_out(vty, "%-15pPA %-12d %d\n", + &rp_addr, + ntohs(bsm_rpinfo->rp_holdtime), + bsm_rpinfo->rp_pri); + } + } + vty_out(vty, "\n"); + } + + fragment++; + } + + if (uj) + vty_json(vty, json); +} + +int pim_show_bsm_db_helper(const char *vrf, struct vty *vty, bool uj) +{ + struct pim_instance *pim; + struct vrf *v; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + + if (!v) + return CMD_WARNING; + + pim = v->info; + + if (!pim) { + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + pim_show_bsm_db(v->info, vty, uj); + + return CMD_SUCCESS; +} diff --git a/pimd/pim_cmd_common.h b/pimd/pim_cmd_common.h new file mode 100644 index 0000000..e30203f --- /dev/null +++ b/pimd/pim_cmd_common.h @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for IPv6 FRR + * Copyright (C) 2022 Vmware, Inc. + * Mobashshera Rasool + */ +#ifndef PIM_CMD_COMMON_H +#define PIM_CMD_COMMON_H + +struct pim_upstream; +struct pim_instance; + +const char *pim_cli_get_vrf_name(struct vty *vty); +int pim_process_join_prune_cmd(struct vty *vty, const char *jpi_str); +int pim_process_no_join_prune_cmd(struct vty *vty); +int pim_process_spt_switchover_infinity_cmd(struct vty *vty); +int pim_process_spt_switchover_prefixlist_cmd(struct vty *vty, + const char *plist); +int pim_process_no_spt_switchover_cmd(struct vty *vty); +int pim_process_pim_packet_cmd(struct vty *vty, const char *packet); +int pim_process_no_pim_packet_cmd(struct vty *vty); +int pim_process_keepalivetimer_cmd(struct vty *vty, const char *kat); +int pim_process_no_keepalivetimer_cmd(struct vty *vty); +int pim_process_rp_kat_cmd(struct vty *vty, const char *rpkat); +int pim_process_no_rp_kat_cmd(struct vty *vty); +int pim_process_register_suppress_cmd(struct vty *vty, const char *rst); +int pim_process_no_register_suppress_cmd(struct vty *vty); +int pim_process_rp_cmd(struct vty *vty, const char *rp_str, + const char *group_str); +int pim_process_no_rp_cmd(struct vty *vty, const char *rp_str, + const char *group_str); +int pim_process_rp_plist_cmd(struct vty *vty, const char *rp_str, + const char *prefix_list); +int pim_process_no_rp_plist_cmd(struct vty *vty, const char *rp_str, + const char *prefix_list); + +int pim_process_ip_pim_cmd(struct vty *vty); +int pim_process_no_ip_pim_cmd(struct vty *vty); +int pim_process_ip_pim_passive_cmd(struct vty *vty, bool enable); +int pim_process_ip_pim_drprio_cmd(struct vty *vty, const char *drpriority_str); +int pim_process_no_ip_pim_drprio_cmd(struct vty *vty); +int pim_process_ip_pim_hello_cmd(struct vty *vty, const char *hello_str, + const char *hold_str); +int pim_process_no_ip_pim_hello_cmd(struct vty *vty); +int pim_process_ip_pim_activeactive_cmd(struct vty *vty, const char *no); +int pim_process_ip_pim_boundary_oil_cmd(struct vty *vty, const char *oil); +int pim_process_no_ip_pim_boundary_oil_cmd(struct vty *vty); +int pim_process_ip_mroute_cmd(struct vty *vty, const char *interface, + const char *group_str, const char *source_str); +int pim_process_no_ip_mroute_cmd(struct vty *vty, const char *interface, + const char *group_str, const char *src_str); +int pim_process_bsm_cmd(struct vty *vty); +int pim_process_no_bsm_cmd(struct vty *vty); +int pim_process_unicast_bsm_cmd(struct vty *vty); +int pim_process_no_unicast_bsm_cmd(struct vty *vty); +void json_object_pim_upstream_add(json_object *json, struct pim_upstream *up); +void pim_show_rpf(struct pim_instance *pim, struct vty *vty, json_object *json); +void pim_show_neighbors_secondary(struct pim_instance *pim, struct vty *vty); +void pim_show_state(struct pim_instance *pim, struct vty *vty, + const char *src_or_group, const char *group, + json_object *json); +void pim_show_statistics(struct pim_instance *pim, struct vty *vty, + const char *ifname, bool uj); +void pim_show_upstream(struct pim_instance *pim, struct vty *vty, + pim_sgaddr *sg, json_object *json); +void pim_show_join_desired(struct pim_instance *pim, struct vty *vty, bool uj); +void pim_show_upstream_rpf(struct pim_instance *pim, struct vty *vty, bool uj); +void pim_show_rpf_refresh_stats(struct vty *vty, struct pim_instance *pim, + time_t now, json_object *json); +bool pim_sgaddr_match(pim_sgaddr item, pim_sgaddr match); +void json_object_pim_ifp_add(struct json_object *json, struct interface *ifp); +void pim_print_ifp_flags(struct vty *vty, struct interface *ifp); +void json_object_pim_upstream_add(json_object *json, struct pim_upstream *up); +int pim_show_join_cmd_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, const char *json); +int pim_show_join_vrf_all_cmd_helper(struct vty *vty, const char *json); +void pim_show_join(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, + json_object *json); +int pim_show_jp_agg_list_cmd_helper(const char *vrf, struct vty *vty); +void pim_show_jp_agg_list(struct pim_instance *pim, struct vty *vty); +int pim_show_membership_cmd_helper(const char *vrf, struct vty *vty, bool uj); +void pim_show_membership(struct pim_instance *pim, struct vty *vty, bool uj); +void pim_show_channel(struct pim_instance *pim, struct vty *vty, bool uj); +int pim_show_channel_cmd_helper(const char *vrf, struct vty *vty, bool uj); +int pim_show_interface_cmd_helper(const char *vrf, struct vty *vty, bool uj, + bool mlag, const char *interface); +int pim_show_interface_vrf_all_cmd_helper(struct vty *vty, bool uj, bool mlag, + const char *interface); +void pim_show_interfaces(struct pim_instance *pim, struct vty *vty, bool mlag, + json_object *json); +void pim_show_interfaces_single(struct pim_instance *pim, struct vty *vty, + const char *ifname, bool mlag, + json_object *json); +void ip_pim_ssm_show_group_range(struct pim_instance *pim, struct vty *vty, + bool uj); +int pim_show_nexthop_lookup_cmd_helper(const char *vrf, struct vty *vty, + pim_addr source, pim_addr group); +int pim_show_nexthop_cmd_helper(const char *vrf, struct vty *vty, bool uj); +void pim_show_nexthop(struct pim_instance *pim, struct vty *vty, bool uj); +int pim_show_neighbors_cmd_helper(const char *vrf, struct vty *vty, + const char *json, const char *interface); +int pim_show_neighbors_vrf_all_cmd_helper(struct vty *vty, const char *json, + const char *interface); +void pim_show_neighbors_single(struct pim_instance *pim, struct vty *vty, + const char *neighbor, json_object *json); +void pim_show_neighbors(struct pim_instance *pim, struct vty *vty, + json_object *json); +int pim_show_group_rp_mappings_info_helper(const char *vrf, struct vty *vty, + bool uj); +int pim_show_bsm_db_helper(const char *vrf, struct vty *vty, bool uj); +int gm_process_query_max_response_time_cmd(struct vty *vty, + const char *qmrt_str); +int gm_process_no_query_max_response_time_cmd(struct vty *vty); +int gm_process_last_member_query_count_cmd(struct vty *vty, + const char *lmqc_str); +int gm_process_no_last_member_query_count_cmd(struct vty *vty); +int gm_process_last_member_query_interval_cmd(struct vty *vty, + const char *lmqi_str); +int gm_process_no_last_member_query_interval_cmd(struct vty *vty); +int pim_process_ssmpingd_cmd(struct vty *vty, enum nb_operation operation, + const char *src_str); +void pim_cmd_show_ip_multicast_helper(struct pim_instance *pim, + struct vty *vty); +void show_multicast_interfaces(struct pim_instance *pim, struct vty *vty, + json_object *json); +void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, + bool fill, json_object *json); +void show_mroute_count(struct pim_instance *pim, struct vty *vty, + json_object *json); +void show_mroute_summary(struct pim_instance *pim, struct vty *vty, + json_object *json); +int clear_ip_mroute_count_command(struct vty *vty, const char *name); +struct vrf *pim_cmd_lookup(struct vty *vty, const char *name); +void clear_mroute(struct pim_instance *pim); +void clear_pim_statistics(struct pim_instance *pim); +int clear_pim_interface_traffic(const char *vrf, struct vty *vty); +int pim_debug_pim_cmd(void); +int pim_no_debug_pim_cmd(void); +int pim_debug_pim_packets_cmd(const char *hello, const char *joins, + const char *registers, struct vty *vty); +int pim_no_debug_pim_packets_cmd(const char *hello, const char *joins, + const char *registers, struct vty *vty); +int pim_show_rpf_helper(const char *vrf, struct vty *vty, bool json); +int pim_show_rpf_vrf_all_helper(struct vty *vty, bool json); +int pim_show_rp_helper(const char *vrf, struct vty *vty, const char *group_str, + const struct prefix *group, bool json); +int pim_show_rp_vrf_all_helper(struct vty *vty, const char *group_str, + const struct prefix *group, bool json); +int pim_show_secondary_helper(const char *vrf, struct vty *vty); +int pim_show_statistics_helper(const char *vrf, struct vty *vty, + const char *word, bool uj); +int pim_show_upstream_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, bool json); +int pim_show_upstream_vrf_all_helper(struct vty *vty, bool json); +int pim_show_upstream_join_desired_helper(const char *vrf, struct vty *vty, + bool uj); +int pim_show_upstream_rpf_helper(const char *vrf, struct vty *vty, bool uj); +int pim_show_state_helper(const char *vrf, struct vty *vty, + const char *s_or_g_str, const char *g_str, bool json); +int pim_show_state_vrf_all_helper(struct vty *vty, const char *s_or_g_str, + const char *g_str, bool json); +int pim_show_multicast_helper(const char *vrf, struct vty *vty); +int pim_show_multicast_vrf_all_helper(struct vty *vty); +int pim_show_multicast_count_helper(const char *vrf, struct vty *vty, + bool json); +int pim_show_multicast_count_vrf_all_helper(struct vty *vty, bool json); +int pim_show_mroute_helper(const char *vrf, struct vty *vty, pim_addr s_or_g, + pim_addr g, bool fill, bool json); +int pim_show_mroute_vrf_all_helper(struct vty *vty, bool fill, bool json); +int pim_show_mroute_count_helper(const char *vrf, struct vty *vty, bool json); +int pim_show_mroute_count_vrf_all_helper(struct vty *vty, bool json); +int pim_show_mroute_summary_helper(const char *vrf, struct vty *vty, bool json); +int pim_show_mroute_summary_vrf_all_helper(struct vty *vty, bool json); + +void pim_show_interface_traffic_single(struct pim_instance *pim, + struct vty *vty, const char *ifname, + bool uj); +void pim_show_interface_traffic(struct pim_instance *pim, struct vty *vty, + bool uj); +int pim_show_interface_traffic_helper(const char *vrf, const char *if_name, + struct vty *vty, bool uj); +void clear_pim_interfaces(struct pim_instance *pim); +void pim_show_bsr(struct pim_instance *pim, struct vty *vty, bool uj); +int pim_show_bsr_helper(const char *vrf, struct vty *vty, bool uj); +/* + * Special Macro to allow us to get the correct pim_instance; + */ +#define PIM_DECLVAR_CONTEXT_VRF(vrfptr, pimptr) \ + VTY_DECLVAR_CONTEXT_VRF(vrfptr); \ + struct pim_instance *pimptr = vrfptr->info; \ + MACRO_REQUIRE_SEMICOLON() /* end */ + +#endif /* PIM_CMD_COMMON_H */ diff --git a/pimd/pim_errors.c b/pimd/pim_errors.c new file mode 100644 index 0000000..1f98cec --- /dev/null +++ b/pimd/pim_errors.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "pim_errors.h" + +/* clang-format off */ +static struct log_ref ferr_pim_err[] = { + { + .code = EC_PIM_MSDP_PACKET, + .title = "PIM MSDP Packet Error", + .description = "PIM has received a packet from a peer that does not correctly decode", + .suggestion = "Check MSDP peer and ensure it is correctly working" + }, + { + .code = EC_PIM_CONFIG, + .title = "PIM Configuration Error", + .description = "PIM has detected a configuration error", + .suggestion = "Ensure the configuration is correct and apply correct configuration" + }, + { + .code = END_FERR, + } +}; +/* clang-format on */ + +void pim_error_init(void) +{ + log_ref_add(ferr_pim_err); +} diff --git a/pimd/pim_errors.h b/pimd/pim_errors.h new file mode 100644 index 0000000..7de35f0 --- /dev/null +++ b/pimd/pim_errors.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __PIM_ERRORS_H__ +#define __PIM_ERRORS_H__ + +#include "lib/ferr.h" + +enum pim_log_refs { + EC_PIM_MSDP_PACKET = PIM_FERR_START, + EC_PIM_CONFIG, +}; + +extern void pim_error_init(void); + +#endif diff --git a/pimd/pim_hello.c b/pimd/pim_hello.c new file mode 100644 index 0000000..a0661ef --- /dev/null +++ b/pimd/pim_hello.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "if.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_pim.h" +#include "pim_str.h" +#include "pim_tlv.h" +#include "pim_util.h" +#include "pim_hello.h" +#include "pim_iface.h" +#include "pim_neighbor.h" +#include "pim_upstream.h" +#include "pim_bsm.h" + +static void on_trace(const char *label, struct interface *ifp, pim_addr src) +{ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: from %pPAs on %s", label, &src, ifp->name); +} + +static void tlv_trace_bool(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, int isset, + int value) +{ + if (isset) + zlog_debug( + "%s: PIM hello option from %pPAs on interface %s: %s=%d", + label, &src_addr, ifname, tlv_name, value); +} + +static void tlv_trace_uint16(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, int isset, + uint16_t value) +{ + if (isset) + zlog_debug( + "%s: PIM hello option from %pPAs on interface %s: %s=%u", + label, &src_addr, ifname, tlv_name, value); +} + +static void tlv_trace_uint32(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, int isset, + uint32_t value) +{ + if (isset) + zlog_debug( + "%s: PIM hello option from %pPAs on interface %s: %s=%u", + label, &src_addr, ifname, tlv_name, value); +} + +static void tlv_trace_uint32_hex(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, + int isset, uint32_t value) +{ + if (isset) + zlog_debug( + "%s: PIM hello option from %pPAs on interface %s: %s=%08x", + label, &src_addr, ifname, tlv_name, value); +} + +static void tlv_trace_list(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, int isset, + struct list *addr_list) +{ + if (isset) + zlog_debug( + "%s: PIM hello option from %pPAs on interface %s: %s size=%d list=%p", + label, &src_addr, ifname, tlv_name, + addr_list ? ((int)listcount(addr_list)) : -1, + (void *)addr_list); +} + +#define FREE_ADDR_LIST \ + if (hello_option_addr_list) { \ + list_delete(&hello_option_addr_list); \ + } + +#define FREE_ADDR_LIST_THEN_RETURN(code) \ + { \ + FREE_ADDR_LIST \ + return (code); \ + } + +int pim_hello_recv(struct interface *ifp, pim_addr src_addr, uint8_t *tlv_buf, + int tlv_buf_size) +{ + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + uint8_t *tlv_curr; + uint8_t *tlv_pastend; + pim_hello_options hello_options = + 0; /* bit array recording options found */ + uint16_t hello_option_holdtime = 0; + uint16_t hello_option_propagation_delay = 0; + uint16_t hello_option_override_interval = 0; + uint32_t hello_option_dr_priority = 0; + uint32_t hello_option_generation_id = 0; + struct list *hello_option_addr_list = 0; + + if (PIM_DEBUG_PIM_HELLO) + on_trace(__func__, ifp, src_addr); + + pim_ifp = ifp->info; + assert(pim_ifp); + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + ++pim_ifp->pim_ifstat_hello_recv; + + /* + Parse PIM hello TLVs + */ + assert(tlv_buf_size >= 0); + tlv_curr = tlv_buf; + tlv_pastend = tlv_buf + tlv_buf_size; + + while (tlv_curr < tlv_pastend) { + uint16_t option_type; + uint16_t option_len; + int remain = tlv_pastend - tlv_curr; + + if (remain < PIM_TLV_MIN_SIZE) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: short PIM hello TLV size=%d < min=%d from %pPAs on interface %s", + __func__, remain, PIM_TLV_MIN_SIZE, + &src_addr, ifp->name); + FREE_ADDR_LIST_THEN_RETURN(-1); + } + + option_type = PIM_TLV_GET_TYPE(tlv_curr); + tlv_curr += PIM_TLV_TYPE_SIZE; + option_len = PIM_TLV_GET_LENGTH(tlv_curr); + tlv_curr += PIM_TLV_LENGTH_SIZE; + + if ((tlv_curr + option_len) > tlv_pastend) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: long PIM hello TLV type=%d length=%d > left=%td from %pPAs on interface %s", + __func__, option_type, option_len, + tlv_pastend - tlv_curr, &src_addr, + ifp->name); + FREE_ADDR_LIST_THEN_RETURN(-2); + } + + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: parse left_size=%d: PIM hello TLV type=%d length=%d from %pPAs on %s", + __func__, remain, option_type, option_len, + &src_addr, ifp->name); + + switch (option_type) { + case PIM_MSG_OPTION_TYPE_HOLDTIME: + if (pim_tlv_parse_holdtime(ifp->name, src_addr, + &hello_options, + &hello_option_holdtime, + option_len, tlv_curr)) { + FREE_ADDR_LIST_THEN_RETURN(-3); + } + break; + case PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY: + if (pim_tlv_parse_lan_prune_delay( + ifp->name, src_addr, &hello_options, + &hello_option_propagation_delay, + &hello_option_override_interval, option_len, + tlv_curr)) { + FREE_ADDR_LIST_THEN_RETURN(-4); + } + break; + case PIM_MSG_OPTION_TYPE_DR_PRIORITY: + if (pim_tlv_parse_dr_priority(ifp->name, src_addr, + &hello_options, + &hello_option_dr_priority, + option_len, tlv_curr)) { + FREE_ADDR_LIST_THEN_RETURN(-5); + } + break; + case PIM_MSG_OPTION_TYPE_GENERATION_ID: + if (pim_tlv_parse_generation_id( + ifp->name, src_addr, &hello_options, + &hello_option_generation_id, option_len, + tlv_curr)) { + FREE_ADDR_LIST_THEN_RETURN(-6); + } + break; + case PIM_MSG_OPTION_TYPE_ADDRESS_LIST: + if (pim_tlv_parse_addr_list(ifp->name, src_addr, + &hello_options, + &hello_option_addr_list, + option_len, tlv_curr)) { + return -7; + } + break; + case PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH: + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: ignoring PIM hello dense-mode state refresh TLV option type=%d length=%d from %pPAs on interface %s", + __func__, option_type, option_len, + &src_addr, ifp->name); + break; + default: + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: ignoring unknown PIM hello TLV type=%d length=%d from %pPAs on interface %s", + __func__, option_type, option_len, + &src_addr, ifp->name); + } + + tlv_curr += option_len; + } + + /* + Check received PIM hello options + */ + + if (PIM_DEBUG_PIM_HELLO) { + tlv_trace_uint16(__func__, "holdtime", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_HOLDTIME), + hello_option_holdtime); + tlv_trace_uint16( + __func__, "propagation_delay", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY), + hello_option_propagation_delay); + tlv_trace_uint16( + __func__, "override_interval", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY), + hello_option_override_interval); + tlv_trace_bool( + __func__, "can_disable_join_suppression", ifp->name, + src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY), + PIM_OPTION_IS_SET( + hello_options, + PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION)); + tlv_trace_uint32(__func__, "dr_priority", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_DR_PRIORITY), + hello_option_dr_priority); + tlv_trace_uint32_hex( + __func__, "generation_id", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_GENERATION_ID), + hello_option_generation_id); + tlv_trace_list(__func__, "address_list", ifp->name, src_addr, + PIM_OPTION_IS_SET(hello_options, + PIM_OPTION_MASK_ADDRESS_LIST), + hello_option_addr_list); + } + + if (!PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: PIM hello missing holdtime from %pPAs on interface %s", + __func__, &src_addr, ifp->name); + } + + /* + New neighbor? + */ + + neigh = pim_neighbor_find(ifp, src_addr, false); + if (!neigh) { + /* Add as new neighbor */ + + neigh = pim_neighbor_add( + ifp, src_addr, hello_options, hello_option_holdtime, + hello_option_propagation_delay, + hello_option_override_interval, + hello_option_dr_priority, hello_option_generation_id, + hello_option_addr_list, PIM_NEIGHBOR_SEND_DELAY); + if (!neigh) { + if (PIM_DEBUG_PIM_HELLO) + zlog_warn( + "%s: failure creating PIM neighbor %pPAs on interface %s", + __func__, &src_addr, ifp->name); + FREE_ADDR_LIST_THEN_RETURN(-8); + } + /* Forward BSM if required */ + if (!pim_bsm_new_nbr_fwd(neigh, ifp)) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: forwarding bsm to new nbr failed", + __func__); + } + + /* actual addr list has been saved under neighbor */ + return 0; + } + + /* + Received generation ID ? + */ + + if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID)) { + /* GenID mismatch ? */ + if (!PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_GENERATION_ID) + || (hello_option_generation_id != neigh->generation_id)) { + /* GenID mismatch, then replace neighbor */ + + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: GenId mismatch new=%08x old=%08x: replacing neighbor %pPAs on %s", + __func__, hello_option_generation_id, + neigh->generation_id, &src_addr, + ifp->name); + + pim_upstream_rpf_genid_changed(pim_ifp->pim, + neigh->source_addr); + + pim_neighbor_delete(ifp, neigh, "GenID mismatch"); + neigh = pim_neighbor_add(ifp, src_addr, hello_options, + hello_option_holdtime, + hello_option_propagation_delay, + hello_option_override_interval, + hello_option_dr_priority, + hello_option_generation_id, + hello_option_addr_list, + PIM_NEIGHBOR_SEND_NOW); + if (!neigh) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: failure re-creating PIM neighbor %pPAs on interface %s", + __func__, &src_addr, ifp->name); + FREE_ADDR_LIST_THEN_RETURN(-9); + } + /* Forward BSM if required */ + if (!pim_bsm_new_nbr_fwd(neigh, ifp)) { + if (PIM_DEBUG_PIM_HELLO) + zlog_debug( + "%s: forwarding bsm to new nbr failed", + __func__); + } + /* actual addr list is saved under neighbor */ + return 0; + + } /* GenId mismatch: replace neighbor */ + + } /* GenId received */ + + /* + Update existing neighbor + */ + + pim_neighbor_update(neigh, hello_options, hello_option_holdtime, + hello_option_dr_priority, hello_option_addr_list); + /* actual addr list is saved under neighbor */ + return 0; +} + +int pim_hello_build_tlv(struct interface *ifp, uint8_t *tlv_buf, + int tlv_buf_size, uint16_t holdtime, + uint32_t dr_priority, uint32_t generation_id, + uint16_t propagation_delay, uint16_t override_interval, + int can_disable_join_suppression) +{ + uint8_t *curr = tlv_buf; + uint8_t *pastend = tlv_buf + tlv_buf_size; + uint8_t *tmp; +#if PIM_IPV == 4 + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; +#endif + + /* + * Append options + */ + + /* Holdtime */ + curr = pim_tlv_append_uint16(curr, pastend, + PIM_MSG_OPTION_TYPE_HOLDTIME, holdtime); + if (!curr) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not set PIM hello Holdtime option for interface %s", + __func__, ifp->name); + } + return -1; + } + + /* LAN Prune Delay */ + tmp = pim_tlv_append_2uint16(curr, pastend, + PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY, + propagation_delay, override_interval); + if (!tmp) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not set PIM LAN Prune Delay option for interface %s", + __func__, ifp->name); + } + return -1; + } + if (can_disable_join_suppression) { + *(curr + 4) |= 0x80; /* enable T bit */ + } + curr = tmp; + + /* DR Priority */ + curr = pim_tlv_append_uint32( + curr, pastend, PIM_MSG_OPTION_TYPE_DR_PRIORITY, dr_priority); + if (!curr) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not set PIM hello DR Priority option for interface %s", + __func__, ifp->name); + } + return -2; + } + + /* Generation ID */ + curr = pim_tlv_append_uint32(curr, pastend, + PIM_MSG_OPTION_TYPE_GENERATION_ID, + generation_id); + if (!curr) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not set PIM hello Generation ID option for interface %s", + __func__, ifp->name); + } + return -3; + } + + /* Secondary Address List */ + if (if_connected_count(ifp->connected)) { + curr = pim_tlv_append_addrlist_ucast(curr, pastend, ifp, + PIM_AF); + if (!curr) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not set PIM hello %s Secondary Address List option for interface %s", + __func__, PIM_AF_NAME, ifp->name); + } + return -4; + } +#if PIM_IPV == 4 + if (pim->send_v6_secondary) { + curr = pim_tlv_append_addrlist_ucast(curr, pastend, ifp, + AF_INET6); + if (!curr) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not sent PIM hello v6 secondary Address List option for interface %s", + __func__, ifp->name); + } + return -4; + } + } +#endif + } + + return curr - tlv_buf; +} + +/* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on an + interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. +*/ +void pim_hello_require(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + + pim_ifp = ifp->info; + + assert(pim_ifp); + + if (PIM_IF_FLAG_TEST_HELLO_SENT(pim_ifp->flags)) + return; + + pim_hello_restart_now(ifp); /* Send hello and restart timer */ +} diff --git a/pimd/pim_hello.h b/pimd/pim_hello.h new file mode 100644 index 0000000..0e57c8f --- /dev/null +++ b/pimd/pim_hello.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_HELLO_H +#define PIM_HELLO_H + +#include + +#include "if.h" + +int pim_hello_recv(struct interface *ifp, pim_addr src_addr, uint8_t *tlv_buf, + int tlv_buf_size); + +int pim_hello_build_tlv(struct interface *ifp, uint8_t *tlv_buf, + int tlv_buf_size, uint16_t holdtime, + uint32_t dr_priority, uint32_t generation_id, + uint16_t propagation_delay, uint16_t override_interval, + int can_disable_join_suppression); + +void pim_hello_require(struct interface *ifp); + +#endif /* PIM_HELLO_H */ diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c new file mode 100644 index 0000000..dcb6116 --- /dev/null +++ b/pimd/pim_iface.c @@ -0,0 +1,1829 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" +#include "log.h" +#include "vty.h" +#include "memory.h" +#include "prefix.h" +#include "vrf.h" +#include "linklist.h" +#include "plist.h" +#include "hash.h" +#include "ferr.h" +#include "network.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_zebra.h" +#include "pim_iface.h" +#include "pim_igmp.h" +#include "pim_mroute.h" +#include "pim_oil.h" +#include "pim_str.h" +#include "pim_pim.h" +#include "pim_neighbor.h" +#include "pim_ifchannel.h" +#include "pim_sock.h" +#include "pim_time.h" +#include "pim_ssmpingd.h" +#include "pim_rp.h" +#include "pim_nht.h" +#include "pim_jp_agg.h" +#include "pim_igmp_join.h" +#include "pim_vxlan.h" + +#include "pim6_mld.h" + +static void pim_if_gm_join_del_all(struct interface *ifp); + +static int gm_join_sock(const char *ifname, ifindex_t ifindex, + pim_addr group_addr, pim_addr source_addr, + struct pim_interface *pim_ifp); + +void pim_if_init(struct pim_instance *pim) +{ + int i; + + for (i = 0; i < MAXVIFS; i++) + pim->iface_vif_index[i] = 0; +} + +void pim_if_terminate(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + pim_if_delete(ifp); + } + return; +} + +static void pim_sec_addr_free(struct pim_secondary_addr *sec_addr) +{ + XFREE(MTYPE_PIM_SEC_ADDR, sec_addr); +} + +__attribute__((unused)) +static int pim_sec_addr_comp(const void *p1, const void *p2) +{ + const struct pim_secondary_addr *sec1 = p1; + const struct pim_secondary_addr *sec2 = p2; + + if (sec1->addr.family == AF_INET && sec2->addr.family == AF_INET6) + return -1; + + if (sec1->addr.family == AF_INET6 && sec2->addr.family == AF_INET) + return 1; + + if (sec1->addr.family == AF_INET) { + if (ntohl(sec1->addr.u.prefix4.s_addr) + < ntohl(sec2->addr.u.prefix4.s_addr)) + return -1; + + if (ntohl(sec1->addr.u.prefix4.s_addr) + > ntohl(sec2->addr.u.prefix4.s_addr)) + return 1; + } else { + return memcmp(&sec1->addr.u.prefix6, &sec2->addr.u.prefix6, + sizeof(struct in6_addr)); + } + + return 0; +} + +struct pim_interface *pim_if_new(struct interface *ifp, bool gm, bool pim, + bool ispimreg, bool is_vxlan_term) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + assert(!ifp->info); + + pim_ifp = XCALLOC(MTYPE_PIM_INTERFACE, sizeof(*pim_ifp)); + + pim_ifp->pim = ifp->vrf->info; + pim_ifp->mroute_vif_index = -1; + + pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; + pim_ifp->mld_version = MLD_DEFAULT_VERSION; + pim_ifp->gm_default_robustness_variable = + GM_DEFAULT_ROBUSTNESS_VARIABLE; + pim_ifp->gm_default_query_interval = GM_GENERAL_QUERY_INTERVAL; + pim_ifp->gm_query_max_response_time_dsec = + GM_QUERY_MAX_RESPONSE_TIME_DSEC; + pim_ifp->gm_specific_query_max_response_time_dsec = + GM_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC; + pim_ifp->gm_last_member_query_count = GM_DEFAULT_ROBUSTNESS_VARIABLE; + + /* BSM config on interface: true by default */ + pim_ifp->bsm_enable = true; + pim_ifp->ucast_bsm_accept = true; + pim_ifp->am_i_dr = false; + + /* + RFC 3376: 8.3. Query Response Interval + The number of seconds represented by the [Query Response Interval] + must be less than the [Query Interval]. + */ + assert(pim_ifp->gm_query_max_response_time_dsec < + pim_ifp->gm_default_query_interval); + + pim_ifp->pim_enable = pim; + pim_ifp->pim_passive_enable = false; + pim_ifp->gm_enable = gm; + + pim_ifp->gm_join_list = NULL; + pim_ifp->pim_neighbor_list = NULL; + pim_ifp->upstream_switch_list = NULL; + pim_ifp->pim_generation_id = 0; + + /* list of struct gm_sock */ + pim_igmp_if_init(pim_ifp, ifp); + + /* list of struct pim_neighbor */ + pim_ifp->pim_neighbor_list = list_new(); + pim_ifp->pim_neighbor_list->del = (void (*)(void *))pim_neighbor_free; + + pim_ifp->upstream_switch_list = list_new(); + pim_ifp->upstream_switch_list->del = + (void (*)(void *))pim_jp_agg_group_list_free; + pim_ifp->upstream_switch_list->cmp = pim_jp_agg_group_list_cmp; + + pim_ifp->sec_addr_list = list_new(); + pim_ifp->sec_addr_list->del = (void (*)(void *))pim_sec_addr_free; + pim_ifp->sec_addr_list->cmp = + (int (*)(void *, void *))pim_sec_addr_comp; + + pim_ifp->activeactive = false; + + RB_INIT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); + + ifp->info = pim_ifp; + + pim_sock_reset(ifp); + + pim_if_add_vif(ifp, ispimreg, is_vxlan_term); + pim_ifp->pim->mcast_if_count++; + + return pim_ifp; +} + +void pim_if_delete(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + pim_ifp = ifp->info; + assert(pim_ifp); + + pim_ifp->pim->mcast_if_count--; + if (pim_ifp->gm_join_list) { + pim_if_gm_join_del_all(ifp); + } + + pim_ifchannel_delete_all(ifp); +#if PIM_IPV == 4 + igmp_sock_delete_all(ifp); +#endif + if (pim_ifp->pim_sock_fd >= 0) + pim_sock_delete(ifp, "Interface removed from configuration"); + + pim_if_del_vif(ifp); + + pim_igmp_if_fini(pim_ifp); + + list_delete(&pim_ifp->pim_neighbor_list); + list_delete(&pim_ifp->upstream_switch_list); + list_delete(&pim_ifp->sec_addr_list); + + if (pim_ifp->bfd_config.profile) + XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile); + + XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); + XFREE(MTYPE_PIM_INTERFACE, pim_ifp); + + ifp->info = NULL; +} + +void pim_if_update_could_assert(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_ifchannel_update_could_assert(ch); + } +} + +static void pim_if_update_my_assert_metric(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_ifchannel_update_my_assert_metric(ch); + } +} + +static void pim_addr_change(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + pim_if_dr_election(ifp); /* router's own DR Priority (addr) changes -- + Done TODO T30 */ + pim_if_update_join_desired(pim_ifp); /* depends on DR */ + pim_if_update_could_assert(ifp); /* depends on DR */ + pim_if_update_my_assert_metric(ifp); /* depends on could_assert */ + pim_if_update_assert_tracking_desired( + ifp); /* depends on DR, join_desired */ + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + 1) Before an interface goes down or changes primary IP address, a + Hello message with a zero HoldTime should be sent immediately + (with the old IP address if the IP address changed). + -- Done at the caller of the function as new ip already updated here + + 2) After an interface has changed its IP address, it MUST send a + Hello message with its new IP address. + -- DONE below + + 3) If an interface changes one of its secondary IP addresses, a + Hello message with an updated Address_List option and a non-zero + HoldTime should be sent immediately. + -- FIXME See TODO T31 + */ + PIM_IF_FLAG_UNSET_HELLO_SENT(pim_ifp->flags); + if (pim_ifp->pim_sock_fd < 0) + return; + pim_hello_restart_now(ifp); /* send hello and restart timer */ +} + +static int detect_primary_address_change(struct interface *ifp, + int force_prim_as_any, + const char *caller) +{ + struct pim_interface *pim_ifp = ifp->info; + pim_addr new_prim_addr; + int changed; + + if (force_prim_as_any) + new_prim_addr = PIMADDR_ANY; + else + new_prim_addr = pim_find_primary_addr(ifp); + + changed = pim_addr_cmp(new_prim_addr, pim_ifp->primary_address); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: old=%pPA new=%pPA on interface %s: %s", + __func__, &pim_ifp->primary_address, &new_prim_addr, + ifp->name, changed ? "changed" : "unchanged"); + + if (changed) { + /* Before updating pim_ifp send Hello time with 0 hold time */ + if (pim_ifp->pim_enable) { + pim_hello_send(ifp, 0 /* zero-sec holdtime */); + } + pim_ifp->primary_address = new_prim_addr; + } + + return changed; +} + +static struct pim_secondary_addr * +pim_sec_addr_find(struct pim_interface *pim_ifp, struct prefix *addr) +{ + struct pim_secondary_addr *sec_addr; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { + if (prefix_cmp(&sec_addr->addr, addr) == 0) { + return sec_addr; + } + } + + return NULL; +} + +static void pim_sec_addr_del(struct pim_interface *pim_ifp, + struct pim_secondary_addr *sec_addr) +{ + listnode_delete(pim_ifp->sec_addr_list, sec_addr); + pim_sec_addr_free(sec_addr); +} + +static int pim_sec_addr_add(struct pim_interface *pim_ifp, struct prefix *addr) +{ + int changed = 0; + struct pim_secondary_addr *sec_addr; + + sec_addr = pim_sec_addr_find(pim_ifp, addr); + if (sec_addr) { + sec_addr->flags &= ~PIM_SEC_ADDRF_STALE; + return changed; + } + + sec_addr = XCALLOC(MTYPE_PIM_SEC_ADDR, sizeof(*sec_addr)); + + changed = 1; + sec_addr->addr = *addr; + listnode_add_sort(pim_ifp->sec_addr_list, sec_addr); + + return changed; +} + +static int pim_sec_addr_del_all(struct pim_interface *pim_ifp) +{ + int changed = 0; + + if (!list_isempty(pim_ifp->sec_addr_list)) { + changed = 1; + /* remove all nodes and free up the list itself */ + list_delete_all_node(pim_ifp->sec_addr_list); + } + + return changed; +} + +static int pim_sec_addr_update(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct connected *ifc; + struct listnode *node; + struct listnode *nextnode; + struct pim_secondary_addr *sec_addr; + int changed = 0; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { + sec_addr->flags |= PIM_SEC_ADDRF_STALE; + } + + frr_each (if_connected, ifp->connected, ifc) { + pim_addr addr = pim_addr_from_prefix(ifc->address); + + if (pim_addr_is_any(addr)) + continue; + + if (!pim_addr_cmp(addr, pim_ifp->primary_address)) { + /* don't add the primary address into the secondary + * address list */ + continue; + } + + if (pim_sec_addr_add(pim_ifp, ifc->address)) { + changed = 1; + } + } + + /* Drop stale entries */ + for (ALL_LIST_ELEMENTS(pim_ifp->sec_addr_list, node, nextnode, + sec_addr)) { + if (sec_addr->flags & PIM_SEC_ADDRF_STALE) { + pim_sec_addr_del(pim_ifp, sec_addr); + changed = 1; + } + } + + return changed; +} + +static int detect_secondary_address_change(struct interface *ifp, + int force_prim_as_any, + const char *caller) +{ + struct pim_interface *pim_ifp = ifp->info; + int changed = 0; + + if (force_prim_as_any) { + /* if primary address is being forced to zero just flush the + * secondary address list */ + changed = pim_sec_addr_del_all(pim_ifp); + } else { + /* re-evaluate the secondary address list */ + changed = pim_sec_addr_update(ifp); + } + + return changed; +} + +static void detect_address_change(struct interface *ifp, int force_prim_as_any, + const char *caller) +{ + int changed = 0; + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + if (detect_primary_address_change(ifp, force_prim_as_any, caller)) { + changed = 1; + } + + if (detect_secondary_address_change(ifp, force_prim_as_any, caller)) { + changed = 1; + } + + + if (changed) { + if (!pim_ifp->pim_enable) { + return; + } + + pim_addr_change(ifp); + } + + /* XXX: if we have unnumbered interfaces we need to run detect address + * address change on all of them when the lo address changes */ +} + +int pim_update_source_set(struct interface *ifp, pim_addr source) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) { + return PIM_IFACE_NOT_FOUND; + } + + if (!pim_addr_cmp(pim_ifp->update_source, source)) { + return PIM_UPDATE_SOURCE_DUP; + } + + pim_ifp->update_source = source; + detect_address_change(ifp, 0 /* force_prim_as_any */, __func__); + + return PIM_SUCCESS; +} + +void pim_if_addr_add(struct connected *ifc) +{ + struct pim_interface *pim_ifp; + struct interface *ifp; + bool vxlan_term; + + assert(ifc); + + ifp = ifc->ifp; + assert(ifp); + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + if (!if_is_operative(ifp)) + return; + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: %s ifindex=%d connected IP address %pFX %s", + __func__, ifp->name, ifp->ifindex, ifc->address, + CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) + ? "secondary" + : "primary"); +#if PIM_IPV != 4 + if (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6) || + IN6_IS_ADDR_LOOPBACK(&ifc->address->u.prefix6)) { + if (IN6_IS_ADDR_UNSPECIFIED(&pim_ifp->ll_lowest)) + pim_ifp->ll_lowest = ifc->address->u.prefix6; + else if (IPV6_ADDR_CMP(&ifc->address->u.prefix6, + &pim_ifp->ll_lowest) < 0) + pim_ifp->ll_lowest = ifc->address->u.prefix6; + + if (IPV6_ADDR_CMP(&ifc->address->u.prefix6, + &pim_ifp->ll_highest) > 0) + pim_ifp->ll_highest = ifc->address->u.prefix6; + + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s: new link-local %pI6, lowest now %pI6, highest %pI6", + ifc->ifp->name, &ifc->address->u.prefix6, + &pim_ifp->ll_lowest, &pim_ifp->ll_highest); + } +#endif + + detect_address_change(ifp, 0, __func__); + + // if (ifc->address->family != AF_INET) + // return; + +#if PIM_IPV == 4 + struct in_addr ifaddr = ifc->address->u.prefix4; + + if (pim_ifp->gm_enable) { + struct gm_sock *igmp; + + /* lookup IGMP socket */ + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, + ifaddr); + if (!igmp) { + /* if addr new, add IGMP socket */ + if (ifc->address->family == AF_INET) + pim_igmp_sock_add(pim_ifp->gm_socket_list, + ifaddr, ifp, false); + } else if (igmp->mtrace_only) { + igmp_sock_delete(igmp); + pim_igmp_sock_add(pim_ifp->gm_socket_list, ifaddr, ifp, + false); + } + + /* Replay Static IGMP groups */ + if (pim_ifp->gm_join_list) { + struct listnode *node; + struct listnode *nextnode; + struct gm_join *ij; + int join_fd; + + for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, node, + nextnode, ij)) { + /* Close socket and reopen with Source and Group + */ + close(ij->sock_fd); + join_fd = gm_join_sock( + ifp->name, ifp->ifindex, ij->group_addr, + ij->source_addr, pim_ifp); + if (join_fd < 0) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", ij->group_addr, + group_str, + sizeof(group_str)); + pim_inet4_dump( + "", ij->source_addr, + source_str, sizeof(source_str)); + zlog_warn( + "%s: gm_join_sock() failure for IGMP group %s source %s on interface %s", + __func__, group_str, source_str, + ifp->name); + /* warning only */ + } else + ij->sock_fd = join_fd; + } + } + } /* igmp */ + else { + struct gm_sock *igmp; + + /* lookup IGMP socket */ + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, + ifaddr); + if (ifc->address->family == AF_INET) { + if (igmp) + igmp_sock_delete(igmp); + /* if addr new, add IGMP socket */ + pim_igmp_sock_add(pim_ifp->gm_socket_list, ifaddr, ifp, + true); + } + } /* igmp mtrace only */ +#endif + + if (pim_ifp->pim_enable) { + + if (!pim_addr_is_any(pim_ifp->primary_address)) { + + /* Interface has a valid socket ? */ + if (pim_ifp->pim_sock_fd < 0) { + if (pim_sock_add(ifp)) { + zlog_warn( + "Failure creating PIM socket for interface %s", + ifp->name); + } + } + struct pim_nexthop_cache *pnc = NULL; + struct pim_rpf rpf; + struct zclient *zclient = NULL; + + zclient = pim_zebra_zclient_get(); + /* RP config might come prior to (local RP's interface) + IF UP event. + In this case, pnc would not have pim enabled + nexthops. + Once Interface is UP and pim info is available, + reregister + with RNH address to receive update and add the + interface as nexthop. */ + memset(&rpf, 0, sizeof(struct pim_rpf)); + rpf.rpf_addr = pim_addr_from_prefix(ifc->address); + pnc = pim_nexthop_cache_find(pim_ifp->pim, &rpf); + if (pnc) + pim_sendmsg_zebra_rnh(pim_ifp->pim, zclient, + pnc, + ZEBRA_NEXTHOP_REGISTER); + } + } /* pim */ + + /* + PIM or IGMP is enabled on interface, and there is at least one + address assigned, then try to create a vif_index. + */ + if (pim_ifp->mroute_vif_index < 0) { + vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp); + pim_if_add_vif(ifp, false, vxlan_term); + } + gm_ifp_update(ifp); + pim_ifchannel_scan_forward_start(ifp); +} + +static void pim_if_addr_del_igmp(struct connected *ifc) +{ +#if PIM_IPV == 4 + struct pim_interface *pim_ifp = ifc->ifp->info; + struct gm_sock *igmp; + struct in_addr ifaddr; + + if (ifc->address->family != AF_INET) { + /* non-IPv4 address */ + return; + } + + if (!pim_ifp) { + /* IGMP not enabled on interface */ + return; + } + + ifaddr = ifc->address->u.prefix4; + + /* lookup IGMP socket */ + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr); + if (igmp) { + /* if addr found, del IGMP socket */ + igmp_sock_delete(igmp); + } +#endif +} + +static void pim_if_addr_del_pim(struct connected *ifc) +{ + struct pim_interface *pim_ifp = ifc->ifp->info; + + if (ifc->address->family != PIM_AF) { + /* non-IPv4 address */ + return; + } + + if (!pim_ifp) { + /* PIM not enabled on interface */ + return; + } + + if (!pim_addr_is_any(pim_ifp->primary_address)) { + /* Interface keeps a valid primary address */ + return; + } + + if (pim_ifp->pim_sock_fd < 0) { + /* Interface does not hold a valid socket any longer */ + return; + } + + /* + pim_sock_delete() closes the socket, stops read and timer threads, + and kills all neighbors. + */ + pim_sock_delete(ifc->ifp, + "last address has been removed from interface"); +} + +void pim_if_addr_del(struct connected *ifc, int force_prim_as_any) +{ + struct interface *ifp; + + assert(ifc); + ifp = ifc->ifp; + assert(ifp); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: %s ifindex=%d disconnected IP address %pFX %s", + __func__, ifp->name, ifp->ifindex, ifc->address, + CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) + ? "secondary" + : "primary"); + +#if PIM_IPV == 6 + struct pim_interface *pim_ifp = ifc->ifp->info; + + if (pim_ifp && + (!IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_lowest) || + !IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_highest))) { + struct connected *cc; + + memset(&pim_ifp->ll_lowest, 0xff, sizeof(pim_ifp->ll_lowest)); + memset(&pim_ifp->ll_highest, 0, sizeof(pim_ifp->ll_highest)); + + frr_each (if_connected, ifc->ifp->connected, cc) { + if (!IN6_IS_ADDR_LINKLOCAL(&cc->address->u.prefix6) && + !IN6_IS_ADDR_LOOPBACK(&cc->address->u.prefix6)) + continue; + + if (IPV6_ADDR_CMP(&cc->address->u.prefix6, + &pim_ifp->ll_lowest) < 0) + pim_ifp->ll_lowest = cc->address->u.prefix6; + if (IPV6_ADDR_CMP(&cc->address->u.prefix6, + &pim_ifp->ll_highest) > 0) + pim_ifp->ll_highest = cc->address->u.prefix6; + } + + if (pim_ifp->ll_lowest.s6_addr[0] == 0xff) + memset(&pim_ifp->ll_lowest, 0, + sizeof(pim_ifp->ll_lowest)); + + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s: removed link-local %pI6, lowest now %pI6, highest %pI6", + ifc->ifp->name, &ifc->address->u.prefix6, + &pim_ifp->ll_lowest, &pim_ifp->ll_highest); + + gm_ifp_update(ifp); + } +#endif + + detect_address_change(ifp, force_prim_as_any, __func__); + + pim_if_addr_del_igmp(ifc); + pim_if_addr_del_pim(ifc); +} + +void pim_if_addr_add_all(struct interface *ifp) +{ + struct connected *ifc; + int v4_addrs = 0; + int v6_addrs = 0; + struct pim_interface *pim_ifp = ifp->info; + bool vxlan_term; + + + /* PIM/IGMP enabled ? */ + if (!pim_ifp) + return; + + frr_each (if_connected, ifp->connected, ifc) { + struct prefix *p = ifc->address; + + if (p->family != AF_INET) + v6_addrs++; + else + v4_addrs++; + pim_if_addr_add(ifc); + } + + if (!v4_addrs && v6_addrs && !if_is_loopback(ifp) && + pim_ifp->pim_enable && !pim_addr_is_any(pim_ifp->primary_address) && + pim_ifp->pim_sock_fd < 0 && pim_sock_add(ifp)) { + /* Interface has a valid primary address ? */ + /* Interface has a valid socket ? */ + zlog_warn("Failure creating PIM socket for interface %s", + ifp->name); + } + /* + * PIM or IGMP/MLD is enabled on interface, and there is at least one + * address assigned, then try to create a vif_index. + */ + if (pim_ifp->mroute_vif_index < 0) { + vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp); + pim_if_add_vif(ifp, false, vxlan_term); + } + gm_ifp_update(ifp); + pim_ifchannel_scan_forward_start(ifp); + + pim_rp_setup(pim_ifp->pim); + pim_rp_check_on_if_add(pim_ifp); +} + +void pim_if_addr_del_all(struct interface *ifp) +{ + struct connected *ifc; + struct pim_instance *pim; + + pim = ifp->vrf->info; + if (!pim) + return; + + /* PIM/IGMP enabled ? */ + if (!ifp->info) + return; + + frr_each_safe (if_connected, ifp->connected, ifc) { + struct prefix *p = ifc->address; + + if (p->family != PIM_AF) + continue; + + pim_if_addr_del(ifc, 1 /* force_prim_as_any=true */); + } + + pim_rp_setup(pim); + pim_i_am_rp_re_evaluate(pim); +} + +void pim_if_addr_del_all_igmp(struct interface *ifp) +{ + struct connected *ifc; + + /* PIM/IGMP enabled ? */ + if (!ifp->info) + return; + + frr_each_safe (if_connected, ifp->connected, ifc) { + struct prefix *p = ifc->address; + + if (p->family != AF_INET) + continue; + + pim_if_addr_del_igmp(ifc); + } +} + +pim_addr pim_find_primary_addr(struct interface *ifp) +{ + struct connected *ifc; + struct pim_interface *pim_ifp = ifp->info; + + if (pim_ifp && !pim_addr_is_any(pim_ifp->update_source)) + return pim_ifp->update_source; + +#if PIM_IPV == 6 + if (pim_ifp && !pim_addr_is_any(pim_ifp->ll_highest)) + return pim_ifp->ll_highest; + + pim_addr best_addr = PIMADDR_ANY; + + frr_each (if_connected, ifp->connected, ifc) { + pim_addr addr; + + if (ifc->address->family != AF_INET6) + continue; + + addr = pim_addr_from_prefix(ifc->address); + if (!IN6_IS_ADDR_LINKLOCAL(&addr)) + continue; + if (pim_addr_cmp(addr, best_addr) > 0) + best_addr = addr; + } + + return best_addr; +#else + int v4_addrs = 0; + int v6_addrs = 0; + struct connected *promote_ifc = NULL; + + frr_each (if_connected, ifp->connected, ifc) { + switch (ifc->address->family) { + case AF_INET: + v4_addrs++; + break; + case AF_INET6: + v6_addrs++; + break; + default: + continue; + } + + if (ifc->address->family != PIM_AF) + continue; + + if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) { + promote_ifc = ifc; + continue; + } + + return pim_addr_from_prefix(ifc->address); + } + + + /* Promote the new primary address. */ + if (v4_addrs && promote_ifc) { + UNSET_FLAG(promote_ifc->flags, ZEBRA_IFA_SECONDARY); + return pim_addr_from_prefix(promote_ifc->address); + } + + /* + * If we have no v4_addrs and v6 is configured + * We probably are using unnumbered + * So let's grab the loopbacks v4 address + * and use that as the primary address + */ + if (!v4_addrs && v6_addrs) { + struct interface *lo_ifp; + + // DBS - Come back and check here + if (ifp->vrf->vrf_id == VRF_DEFAULT) + lo_ifp = if_lookup_by_name("lo", ifp->vrf->vrf_id); + else + lo_ifp = if_lookup_by_name(ifp->vrf->name, + ifp->vrf->vrf_id); + + if (lo_ifp && (lo_ifp != ifp)) + return pim_find_primary_addr(lo_ifp); + } + return PIMADDR_ANY; +#endif +} + +static int pim_iface_next_vif_index(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; + int i; + + /* + * The pimreg vif is always going to be in index 0 + * of the table. + */ + if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) + return 0; + + for (i = 1; i < MAXVIFS; i++) { + if (pim->iface_vif_index[i] == 0) + return i; + } + return MAXVIFS; +} + +/* + pim_if_add_vif() uses ifindex as vif_index + + see also pim_if_find_vifindex_by_ifindex() + */ +int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term) +{ + struct pim_interface *pim_ifp = ifp->info; + pim_addr ifaddr; + unsigned char flags = 0; + + assert(pim_ifp); + + if (pim_ifp->mroute_vif_index > 0) { + zlog_warn("%s: vif_index=%d > 0 on interface %s ifindex=%d", + __func__, pim_ifp->mroute_vif_index, ifp->name, + ifp->ifindex); + return -1; + } + + if (ifp->ifindex < 0) { + zlog_warn("%s: ifindex=%d < 0 on interface %s", __func__, + ifp->ifindex, ifp->name); + return -2; + } else if ((ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) && + ((strncmp(ifp->name, "pimreg", 6)) && + (strncmp(ifp->name, "pim6reg", 7)))) { + zlog_warn("%s: ifindex=%d on interface %s", __func__, + ifp->ifindex, ifp->name); + return -2; + } + + ifaddr = pim_ifp->primary_address; +#if PIM_IPV != 6 + /* IPv6 API is always by interface index */ + if (!ispimreg && !is_vxlan_term && pim_addr_is_any(ifaddr)) { + zlog_warn( + "%s: could not get address for interface %s ifindex=%d", + __func__, ifp->name, ifp->ifindex); + return -4; + } +#endif + + pim_ifp->mroute_vif_index = pim_iface_next_vif_index(ifp); + + if (pim_ifp->mroute_vif_index >= MAXVIFS) { + zlog_warn( + "%s: Attempting to configure more than MAXVIFS=%d on pim enabled interface %s", + __func__, MAXVIFS, ifp->name); + return -3; + } + + if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) + flags = VIFF_REGISTER; +#ifdef VIFF_USE_IFINDEX + else + flags = VIFF_USE_IFINDEX; +#endif + + if (pim_mroute_add_vif(ifp, ifaddr, flags)) { + /* pim_mroute_add_vif reported error */ + return -5; + } + + pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 1; + + if (!ispimreg) + gm_ifp_update(ifp); + + /* if the device qualifies as pim_vxlan iif/oif update vxlan entries */ + pim_vxlan_add_vif(ifp); + return 0; +} + +int pim_if_del_vif(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (pim_ifp->mroute_vif_index < 1) { + zlog_warn("%s: vif_index=%d < 1 on interface %s ifindex=%d", + __func__, pim_ifp->mroute_vif_index, ifp->name, + ifp->ifindex); + return -1; + } + + /* if the device was a pim_vxlan iif/oif update vxlan mroute entries */ + pim_vxlan_del_vif(ifp); + + gm_ifp_teardown(ifp); + + pim_mroute_del_vif(ifp); + + /* + Update vif_index + */ + pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 0; + + pim_ifp->mroute_vif_index = -1; + return 0; +} + +// DBS - VRF Revist +struct interface *pim_if_find_by_vif_index(struct pim_instance *pim, + ifindex_t vif_index) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + if (ifp->info) { + struct pim_interface *pim_ifp; + pim_ifp = ifp->info; + + if (vif_index == pim_ifp->mroute_vif_index) + return ifp; + } + } + + return 0; +} + +/* + pim_if_add_vif() uses ifindex as vif_index + */ +int pim_if_find_vifindex_by_ifindex(struct pim_instance *pim, ifindex_t ifindex) +{ + struct pim_interface *pim_ifp; + struct interface *ifp; + + ifp = if_lookup_by_index(ifindex, pim->vrf->vrf_id); + if (!ifp || !ifp->info) + return -1; + pim_ifp = ifp->info; + + return pim_ifp->mroute_vif_index; +} + +int pim_if_lan_delay_enabled(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + assert(pim_ifp); + assert(pim_ifp->pim_number_of_nonlandelay_neighbors >= 0); + + return pim_ifp->pim_number_of_nonlandelay_neighbors == 0; +} + +uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp) +{ + if (pim_if_lan_delay_enabled(ifp)) { + struct pim_interface *pim_ifp; + pim_ifp = ifp->info; + return pim_ifp->pim_neighbors_highest_propagation_delay_msec; + } else { + return PIM_DEFAULT_PROPAGATION_DELAY_MSEC; + } +} + +uint16_t pim_if_effective_override_interval_msec(struct interface *ifp) +{ + if (pim_if_lan_delay_enabled(ifp)) { + struct pim_interface *pim_ifp; + pim_ifp = ifp->info; + return pim_ifp->pim_neighbors_highest_override_interval_msec; + } else { + return PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC; + } +} + +int pim_if_t_override_msec(struct interface *ifp) +{ + int effective_override_interval_msec; + int t_override_msec; + + effective_override_interval_msec = + pim_if_effective_override_interval_msec(ifp); + + t_override_msec = + frr_weak_random() % (effective_override_interval_msec + 1); + + return t_override_msec; +} + +uint16_t pim_if_jp_override_interval_msec(struct interface *ifp) +{ + return pim_if_effective_propagation_delay_msec(ifp) + + pim_if_effective_override_interval_msec(ifp); +} + +/* + RFC 4601: 4.1.6. State Summarization Macros + + The function NBR( I, A ) uses information gathered through PIM Hello + messages to map the IP address A of a directly connected PIM + neighbor router on interface I to the primary IP address of the same + router (Section 4.3.4). The primary IP address of a neighbor is the + address that it uses as the source of its PIM Hello messages. +*/ +struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp, pim_addr addr) +{ + struct listnode *neighnode; + struct pim_neighbor *neigh; + struct pim_interface *pim_ifp; + struct prefix p; + + assert(ifp); + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", __func__, + ifp->name); + return 0; + } + + pim_addr_to_prefix(&p, addr); + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, + neigh)) { + + /* primary address ? */ + if (!pim_addr_cmp(neigh->source_addr, addr)) + return neigh; + + /* secondary address ? */ + if (pim_neighbor_find_secondary(neigh, &p)) + return neigh; + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: neighbor not found for address %pPA on interface %s", + __func__, &addr, ifp->name); + + return NULL; +} + +long pim_if_t_suppressed_msec(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + long t_suppressed_msec; + uint32_t ramount = 0; + + pim_ifp = ifp->info; + assert(pim_ifp); + + /* join suppression disabled ? */ + if (pim_ifp->pim_can_disable_join_suppression) + return 0; + + /* t_suppressed = t_periodic * rand(1.1, 1.4) */ + ramount = 1100 + (frr_weak_random() % (1400 - 1100 + 1)); + t_suppressed_msec = router->t_periodic * ramount; + + return t_suppressed_msec; +} + +static void gm_join_free(struct gm_join *ij) +{ + XFREE(MTYPE_PIM_IGMP_JOIN, ij); +} + +static struct gm_join *gm_join_find(struct list *join_list, pim_addr group_addr, + pim_addr source_addr) +{ + struct listnode *node; + struct gm_join *ij; + + assert(join_list); + + for (ALL_LIST_ELEMENTS_RO(join_list, node, ij)) { + if ((!pim_addr_cmp(group_addr, ij->group_addr)) && + (!pim_addr_cmp(source_addr, ij->source_addr))) + return ij; + } + + return 0; +} + +static int gm_join_sock(const char *ifname, ifindex_t ifindex, + pim_addr group_addr, pim_addr source_addr, + struct pim_interface *pim_ifp) +{ + int join_fd; + + pim_ifp->igmp_ifstat_joins_sent++; + + join_fd = pim_socket_raw(IPPROTO_GM); + if (join_fd < 0) { + pim_ifp->igmp_ifstat_joins_failed++; + return -1; + } + + if (pim_gm_join_source(join_fd, ifindex, group_addr, source_addr)) { + zlog_warn( + "%s: setsockopt(fd=%d) failure for " GM + " group %pPAs source %pPAs ifindex %d on interface %s: errno=%d: %s", + __func__, join_fd, &group_addr, &source_addr, ifindex, + ifname, errno, safe_strerror(errno)); + + pim_ifp->igmp_ifstat_joins_failed++; + + close(join_fd); + return -2; + } + + return join_fd; +} + +static struct gm_join *gm_join_new(struct interface *ifp, pim_addr group_addr, + pim_addr source_addr) +{ + struct pim_interface *pim_ifp; + struct gm_join *ij; + int join_fd; + + pim_ifp = ifp->info; + assert(pim_ifp); + + join_fd = gm_join_sock(ifp->name, ifp->ifindex, group_addr, source_addr, + pim_ifp); + if (join_fd < 0) { + zlog_warn("%s: gm_join_sock() failure for " GM + " group %pPAs source %pPAs on interface %s", + __func__, &group_addr, &source_addr, ifp->name); + return 0; + } + + ij = XCALLOC(MTYPE_PIM_IGMP_JOIN, sizeof(*ij)); + + ij->sock_fd = join_fd; + ij->group_addr = group_addr; + ij->source_addr = source_addr; + ij->sock_creation = pim_time_monotonic_sec(); + + listnode_add(pim_ifp->gm_join_list, ij); + + return ij; +} + +ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr, + pim_addr source_addr) +{ + struct pim_interface *pim_ifp; + struct gm_join *ij; + + pim_ifp = ifp->info; + if (!pim_ifp) { + return ferr_cfg_invalid("multicast not enabled on interface %s", + ifp->name); + } + + if (!pim_ifp->gm_join_list) { + pim_ifp->gm_join_list = list_new(); + pim_ifp->gm_join_list->del = (void (*)(void *))gm_join_free; + } + + ij = gm_join_find(pim_ifp->gm_join_list, group_addr, source_addr); + + /* This interface has already been configured to join this IGMP/MLD + * group + */ + if (ij) { + return ferr_ok(); + } + + (void)gm_join_new(ifp, group_addr, source_addr); + + if (PIM_DEBUG_GM_EVENTS) { + zlog_debug( + "%s: issued static " GM + " join for channel (S,G)=(%pPA,%pPA) on interface %s", + __func__, &source_addr, &group_addr, ifp->name); + } + + return ferr_ok(); +} + +int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr, + pim_addr source_addr) +{ + struct pim_interface *pim_ifp; + struct gm_join *ij; + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", __func__, + ifp->name); + return -1; + } + + if (!pim_ifp->gm_join_list) { + zlog_warn("%s: no " GM " join on interface %s", __func__, + ifp->name); + return -2; + } + + ij = gm_join_find(pim_ifp->gm_join_list, group_addr, source_addr); + if (!ij) { + zlog_warn("%s: could not find " GM + " group %pPAs source %pPAs on interface %s", + __func__, &group_addr, &source_addr, ifp->name); + return -3; + } + + if (close(ij->sock_fd)) { + zlog_warn( + "%s: failure closing sock_fd=%d for " GM + " group %pPAs source %pPAs on interface %s: errno=%d: %s", + __func__, ij->sock_fd, &group_addr, &source_addr, + ifp->name, errno, safe_strerror(errno)); + /* warning only */ + } + listnode_delete(pim_ifp->gm_join_list, ij); + gm_join_free(ij); + if (listcount(pim_ifp->gm_join_list) < 1) { + list_delete(&pim_ifp->gm_join_list); + pim_ifp->gm_join_list = 0; + } + + return 0; +} + +__attribute__((unused)) +static void pim_if_gm_join_del_all(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct listnode *node; + struct listnode *nextnode; + struct gm_join *ij; + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", __func__, + ifp->name); + return; + } + + if (!pim_ifp->gm_join_list) + return; + + for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, node, nextnode, ij)) + pim_if_gm_join_del(ifp, ij->group_addr, ij->source_addr); +} + +/* + RFC 4601 + + Transitions from "I am Assert Loser" State + + Current Winner's GenID Changes or NLT Expires + + The Neighbor Liveness Timer associated with the current winner + expires or we receive a Hello message from the current winner + reporting a different GenID from the one it previously reported. + This indicates that the current winner's interface or router has + gone down (and may have come back up), and so we must assume it no + longer knows it was the winner. + */ +void pim_if_assert_on_neighbor_down(struct interface *ifp, pim_addr neigh_addr) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + /* Is (S,G,I) assert loser ? */ + if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER) + continue; + /* Dead neighbor was winner ? */ + if (pim_addr_cmp(ch->ifassert_winner, neigh_addr)) + continue; + + assert_action_a5(ch); + } +} + +void pim_if_update_join_desired(struct pim_interface *pim_ifp) +{ + struct pim_ifchannel *ch; + + /* clear off flag from interface's upstreams */ + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED( + ch->upstream->flags); + } + + /* scan per-interface (S,G,I) state on this I interface */ + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + struct pim_upstream *up = ch->upstream; + + if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(up->flags)) + continue; + + /* update join_desired for the global (S,G) state */ + pim_upstream_update_join_desired(pim_ifp->pim, up); + PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(up->flags); + } +} + +void pim_if_update_assert_tracking_desired(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + pim_ifchannel_update_assert_tracking_desired(ch); + } +} + +/* + * PIM wants to have an interface pointer for everything it does. + * The pimreg is a special interface that we have that is not + * quite an interface but a VIF is created for it. + */ +void pim_if_create_pimreg(struct pim_instance *pim) +{ + char pimreg_name[IFNAMSIZ]; + + if (!pim->regiface) { + if (pim->vrf->vrf_id == VRF_DEFAULT) + strlcpy(pimreg_name, PIMREG, sizeof(pimreg_name)); + else + snprintf(pimreg_name, sizeof(pimreg_name), PIMREG "%u", + pim->vrf->data.l.table_id); + + pim->regiface = if_get_by_name(pimreg_name, pim->vrf->vrf_id, + pim->vrf->name); + pim->regiface->ifindex = PIM_OIF_PIM_REGISTER_VIF; + + /* + * The pimreg interface might has been removed from + * kerenl with the VRF's deletion. It must be + * recreated, so delete the old one first. + */ + if (pim->regiface->info) + pim_if_delete(pim->regiface); + + pim_if_new(pim->regiface, false, false, true, + false /*vxlan_term*/); + + /* + * On vrf moves we delete the interface if there + * is nothing going on with it. We cannot have + * the pimregiface deleted. + */ + pim->regiface->configured = true; + + } +} + +struct prefix *pim_if_connected_to_source(struct interface *ifp, pim_addr src) +{ + struct connected *c; + struct prefix p; + + if (!ifp) + return NULL; + + pim_addr_to_prefix(&p, src); + + frr_each (if_connected, ifp->connected, c) { + if (c->address->family != PIM_AF) + continue; + if (prefix_match(c->address, &p)) + return c->address; + if (CONNECTED_PEER(c) && prefix_match(c->destination, &p)) + /* this is not a typo, on PtP we need to return the + * *local* address that lines up with src. + */ + return c->address; + } + + return NULL; +} + +bool pim_if_is_vrf_device(struct interface *ifp) +{ + if (if_is_vrf(ifp)) + return true; + + return false; +} + +int pim_if_ifchannel_count(struct pim_interface *pim_ifp) +{ + struct pim_ifchannel *ch; + int count = 0; + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + count++; + } + + return count; +} + +static int pim_ifp_create(struct interface *ifp) +{ + struct pim_instance *pim; + + pim = ifp->vrf->info; + if (PIM_DEBUG_ZEBRA) { + zlog_debug( + "%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d", + __func__, ifp->name, ifp->ifindex, ifp->vrf->name, + ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric, + ifp->mtu, if_is_operative(ifp)); + } + + if (if_is_operative(ifp)) { + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + /* + * If we have a pim_ifp already and this is an if_add + * that means that we probably have a vrf move event + * If that is the case, set the proper vrfness. + */ + if (pim_ifp) + pim_ifp->pim = pim; + pim_if_addr_add_all(ifp); + + /* + * Due to ordering issues based upon when + * a command is entered we should ensure that + * the pim reg is created for this vrf if we + * have configuration for it already. + * + * this is a no-op if it's already been done. + */ + pim_if_create_pimreg(pim); + } + +#if PIM_IPV == 4 + /* + * If we are a vrf device that is up, open up the pim_socket for + * listening + * to incoming pim messages irrelevant if the user has configured us + * for pim or not. + */ + if (pim_if_is_vrf_device(ifp)) { + struct pim_interface *pim_ifp; + + if (!ifp->info) { + pim_ifp = pim_if_new(ifp, false, false, false, + false /*vxlan_term*/); + ifp->info = pim_ifp; + } + + pim_sock_add(ifp); + } + + if (!strncmp(ifp->name, PIM_VXLAN_TERM_DEV_NAME, + sizeof(PIM_VXLAN_TERM_DEV_NAME))) { + if (pim->mcast_if_count < MAXVIFS) + pim_vxlan_add_term_dev(pim, ifp); + else + zlog_warn( + "%s: Cannot enable pim on %s. MAXVIFS(%d) reached. Deleting and readding the vxlan termimation device after unconfiguring pim from other interfaces may succeed.", + __func__, ifp->name, MAXVIFS); + } +#endif + + return 0; +} + +static int pim_ifp_up(struct interface *ifp) +{ + uint32_t table_id; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + + if (PIM_DEBUG_ZEBRA) { + zlog_debug( + "%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d", + __func__, ifp->name, ifp->ifindex, ifp->vrf->name, + ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric, + ifp->mtu, if_is_operative(ifp)); + } + + pim = ifp->vrf->info; + + pim_ifp = ifp->info; + /* + * If we have a pim_ifp already and this is an if_add + * that means that we probably have a vrf move event + * If that is the case, set the proper vrfness. + */ + if (pim_ifp) + pim_ifp->pim = pim; + + /* + pim_if_addr_add_all() suffices for bringing up both IGMP and + PIM + */ + pim_if_addr_add_all(ifp); + + /* + * If we have a pimreg device callback and it's for a specific + * table set the master appropriately + */ + if (sscanf(ifp->name, "" PIMREG "%" SCNu32, &table_id) == 1) { + struct vrf *vrf; + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if ((table_id == vrf->data.l.table_id) + && (ifp->vrf->vrf_id != vrf->vrf_id)) { + struct interface *master = if_lookup_by_name( + vrf->name, vrf->vrf_id); + + if (!master) { + zlog_debug( + "%s: Unable to find Master interface for %s", + __func__, vrf->name); + return 0; + } + + pim_zebra_interface_set_master(master, ifp); + break; + } + } + } + return 0; +} + +static int pim_ifp_down(struct interface *ifp) +{ + if (PIM_DEBUG_ZEBRA) { + zlog_debug( + "%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d", + __func__, ifp->name, ifp->ifindex, ifp->vrf->name, + ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric, + ifp->mtu, if_is_operative(ifp)); + } + + if (!if_is_operative(ifp)) { + pim_ifchannel_delete_all(ifp); + /* + pim_if_addr_del_all() suffices for shutting down IGMP, + but not for shutting down PIM + */ + pim_if_addr_del_all(ifp); + + /* + pim_sock_delete() closes the socket, stops read and timer + threads, + and kills all neighbors. + */ + if (ifp->info) { + pim_sock_delete(ifp, "link down"); + } + } + + if (ifp->info) { + pim_if_del_vif(ifp); + pim_ifstat_reset(ifp); + } + + return 0; +} + +static int pim_ifp_destroy(struct interface *ifp) +{ + if (PIM_DEBUG_ZEBRA) { + zlog_debug( + "%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d", + __func__, ifp->name, ifp->ifindex, ifp->vrf->name, + ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric, + ifp->mtu, if_is_operative(ifp)); + } + + if (!if_is_operative(ifp)) + pim_if_addr_del_all(ifp); + +#if PIM_IPV == 4 + struct pim_instance *pim; + + pim = ifp->vrf->info; + if (pim && pim->vxlan.term_if == ifp) + pim_vxlan_del_term_dev(pim); +#endif + + return 0; +} + +static int pim_if_new_hook(struct interface *ifp) +{ + return 0; +} + +static int pim_if_delete_hook(struct interface *ifp) +{ + if (ifp->info) + pim_if_delete(ifp); + + return 0; +} + +void pim_iface_init(void) +{ + hook_register_prio(if_add, 0, pim_if_new_hook); + hook_register_prio(if_del, 0, pim_if_delete_hook); + + hook_register_prio(if_real, 0, pim_ifp_create); + hook_register_prio(if_up, 0, pim_ifp_up); + hook_register_prio(if_down, 0, pim_ifp_down); + hook_register_prio(if_unreal, 0, pim_ifp_destroy); +} + +static void pim_if_membership_clear(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + if (pim_ifp->pim_enable && pim_ifp->gm_enable) + return; + + pim_ifchannel_membership_clear(ifp); +} + +void pim_pim_interface_delete(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + return; + + pim_ifp->pim_enable = false; + + pim_if_membership_clear(ifp); + + /* + * pim_sock_delete() removes all neighbors from + * pim_ifp->pim_neighbor_list. + */ + pim_sock_delete(ifp, "pim unconfigured on interface"); + pim_upstream_nh_if_update(pim_ifp->pim, ifp); + + if (!pim_ifp->gm_enable) { + pim_if_addr_del_all(ifp); + pim_if_delete(ifp); + } +} + +void pim_gm_interface_delete(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + return; + + pim_ifp->gm_enable = false; + + pim_if_membership_clear(ifp); + +#if PIM_IPV == 4 + igmp_sock_delete_all(ifp); +#else + gm_ifp_teardown(ifp); +#endif + + if (!pim_ifp->pim_enable) + pim_if_delete(ifp); +} diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h new file mode 100644 index 0000000..0312f71 --- /dev/null +++ b/pimd/pim_iface.h @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_IFACE_H +#define PIM_IFACE_H + +#include + +#include "if.h" +#include "vty.h" +#include "vrf.h" +#include "zclient.h" +#include "ferr.h" + +#include "pim_igmp.h" +#include "pim_upstream.h" +#include "bfd.h" +#include "pim_str.h" + +#define PIM_IF_IS_DELETED(ifp) ((ifp)->ifindex == IFINDEX_INTERNAL) + +#define PIM_I_am_DR(pim_ifp) \ + !pim_addr_cmp((pim_ifp)->pim_dr_addr, (pim_ifp)->primary_address) +#define PIM_I_am_DualActive(pim_ifp) (pim_ifp)->activeactive == true + +/* Macros for interface flags */ + +/* + * PIM needs to know if hello is required to send before other PIM messages + * like Join, prune, assert would go out + */ +#define PIM_IF_FLAG_HELLO_SENT (1 << 0) + +#define PIM_IF_FLAG_TEST_HELLO_SENT(flags) ((flags)&PIM_IF_FLAG_HELLO_SENT) + +#define PIM_IF_FLAG_SET_HELLO_SENT(flags) ((flags) |= PIM_IF_FLAG_HELLO_SENT) + +#define PIM_IF_FLAG_UNSET_HELLO_SENT(flags) ((flags) &= ~PIM_IF_FLAG_HELLO_SENT) + +struct pim_iface_upstream_switch { + pim_addr address; + struct list *us; +}; + +enum pim_secondary_addr_flags { + PIM_SEC_ADDRF_NONE = 0, + PIM_SEC_ADDRF_STALE = (1 << 0) +}; + +struct pim_secondary_addr { + struct prefix addr; + enum pim_secondary_addr_flags flags; +}; + +struct gm_if; + +struct pim_interface { + bool pim_enable : 1; + bool pim_can_disable_join_suppression : 1; + bool pim_passive_enable : 1; + + bool gm_enable : 1; + + ifindex_t mroute_vif_index; + struct pim_instance *pim; + +#if PIM_IPV == 6 + /* link-locals: MLD uses lowest addr, PIM uses highest... */ + pim_addr ll_lowest; + pim_addr ll_highest; +#endif + + pim_addr primary_address; /* remember addr to detect change */ + struct list *sec_addr_list; /* list of struct pim_secondary_addr */ + pim_addr update_source; /* user can statically set the primary + * address of the interface */ + + int igmp_version; /* IGMP version */ + int mld_version; + int gm_default_robustness_variable; /* IGMP or MLD QRV */ + int gm_default_query_interval; /* IGMP or MLD secs between general + queries */ + int gm_query_max_response_time_dsec; /* IGMP or MLD Max Response Time in + dsecs for general queries */ + int gm_specific_query_max_response_time_dsec; /* IGMP or MLD Max + Response Time in dsecs + called as last member + query interval, defines + the maximum response + time advertised in IGMP + group-specific + queries */ + int gm_last_member_query_count; /* IGMP or MLD last member + query count + */ + struct list *gm_socket_list; /* list of struct IGMP or MLD sock */ + struct list *gm_join_list; /* list of struct IGMP or MLD join */ + struct list *gm_group_list; /* list of struct IGMP or MLD group */ + struct hash *gm_group_hash; + + struct gm_if *mld; + + int pim_sock_fd; /* PIM socket file descriptor */ + struct event *t_pim_sock_read; /* thread for reading PIM socket */ + int64_t pim_sock_creation; /* timestamp of PIM socket creation */ + + struct event *t_pim_hello_timer; + int pim_hello_period; + int pim_default_holdtime; + int pim_triggered_hello_delay; + uint32_t pim_generation_id; + uint16_t pim_propagation_delay_msec; /* config */ + uint16_t pim_override_interval_msec; /* config */ + struct list *pim_neighbor_list; /* list of struct pim_neighbor */ + struct list *upstream_switch_list; + struct pim_ifchannel_rb ifchannel_rb; + + /* neighbors without lan_delay */ + int pim_number_of_nonlandelay_neighbors; + uint16_t pim_neighbors_highest_propagation_delay_msec; + uint16_t pim_neighbors_highest_override_interval_msec; + + /* DR Election */ + int64_t pim_dr_election_last; /* timestamp */ + int pim_dr_election_count; + int pim_dr_election_changes; + pim_addr pim_dr_addr; + uint32_t pim_dr_priority; /* config */ + int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */ + + /* boundary prefix-list */ + char *boundary_oil_plist; + + /* Turn on Active-Active for this interface */ + bool activeactive; + bool am_i_dr; + + int64_t pim_ifstat_start; /* start timestamp for stats */ + uint64_t pim_ifstat_bsm_rx; + uint64_t pim_ifstat_bsm_tx; + uint32_t pim_ifstat_hello_sent; + uint32_t pim_ifstat_hello_sendfail; + uint32_t pim_ifstat_hello_recv; + uint32_t pim_ifstat_hello_recvfail; + uint32_t pim_ifstat_join_recv; + uint32_t pim_ifstat_join_send; + uint32_t pim_ifstat_prune_recv; + uint32_t pim_ifstat_prune_send; + uint32_t pim_ifstat_reg_recv; + uint32_t pim_ifstat_reg_send; + uint32_t pim_ifstat_reg_stop_recv; + uint32_t pim_ifstat_reg_stop_send; + uint32_t pim_ifstat_assert_recv; + uint32_t pim_ifstat_assert_send; + uint32_t pim_ifstat_bsm_cfg_miss; + uint32_t pim_ifstat_ucast_bsm_cfg_miss; + uint32_t pim_ifstat_bsm_invalid_sz; + uint8_t flags; + bool bsm_enable; /* bsm processing enable */ + bool ucast_bsm_accept; /* ucast bsm processing */ + + uint32_t igmp_ifstat_joins_sent; + uint32_t igmp_ifstat_joins_failed; + uint32_t igmp_peak_group_count; + + struct { + bool enabled; + uint32_t min_rx; + uint32_t min_tx; + uint8_t detection_multiplier; + char *profile; + } bfd_config; +}; + +/* + * if default_holdtime is set (>= 0), use it; + * otherwise default_holdtime is 3.5 * hello_period + */ +#define PIM_IF_DEFAULT_HOLDTIME(pim_ifp) \ + (((pim_ifp)->pim_default_holdtime < 0) \ + ? ((pim_ifp)->pim_hello_period * 7 / 2) \ + : ((pim_ifp)->pim_default_holdtime)) + +void pim_if_init(struct pim_instance *pim); +void pim_if_terminate(struct pim_instance *pim); + +struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, + bool ispimreg, bool is_vxlan_term); +void pim_if_delete(struct interface *ifp); +void pim_if_addr_add(struct connected *ifc); +void pim_if_addr_del(struct connected *ifc, int force_prim_as_any); +void pim_if_addr_add_all(struct interface *ifp); +void pim_if_addr_del_all(struct interface *ifp); +void pim_if_addr_del_all_igmp(struct interface *ifp); + +int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term); +int pim_if_del_vif(struct interface *ifp); +void pim_if_add_vif_all(struct pim_instance *pim); +void pim_if_del_vif_all(struct pim_instance *pim); + +struct interface *pim_if_find_by_vif_index(struct pim_instance *pim, + ifindex_t vif_index); +int pim_if_find_vifindex_by_ifindex(struct pim_instance *pim, + ifindex_t ifindex); + +int pim_if_lan_delay_enabled(struct interface *ifp); +uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp); +uint16_t pim_if_effective_override_interval_msec(struct interface *ifp); +uint16_t pim_if_jp_override_interval_msec(struct interface *ifp); +struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp, pim_addr addr); + +long pim_if_t_suppressed_msec(struct interface *ifp); +int pim_if_t_override_msec(struct interface *ifp); + +pim_addr pim_find_primary_addr(struct interface *ifp); + +ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr, + pim_addr source_addr); +int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr, + pim_addr source_addr); + +void pim_if_update_could_assert(struct interface *ifp); + +void pim_if_assert_on_neighbor_down(struct interface *ifp, pim_addr neigh_addr); + +void pim_if_rpf_interface_changed(struct interface *old_rpf_ifp, + struct pim_upstream *up); + +void pim_if_update_join_desired(struct pim_interface *pim_ifp); + +void pim_if_update_assert_tracking_desired(struct interface *ifp); + +void pim_if_create_pimreg(struct pim_instance *pim); + +struct prefix *pim_if_connected_to_source(struct interface *ifp, pim_addr src); +int pim_update_source_set(struct interface *ifp, pim_addr source); + +bool pim_if_is_vrf_device(struct interface *ifp); + +int pim_if_ifchannel_count(struct pim_interface *pim_ifp); + +void pim_iface_init(void); +void pim_pim_interface_delete(struct interface *ifp); +void pim_gm_interface_delete(struct interface *ifp); + +#endif /* PIM_IFACE_H */ diff --git a/pimd/pim_ifchannel.c b/pimd/pim_ifchannel.c new file mode 100644 index 0000000..8f9e410 --- /dev/null +++ b/pimd/pim_ifchannel.c @@ -0,0 +1,1500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "linklist.h" +#include "frrevent.h" +#include "memory.h" +#include "if.h" +#include "vrf.h" +#include "hash.h" +#include "jhash.h" +#include "prefix.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_ifchannel.h" +#include "pim_zebra.h" +#include "pim_time.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_join.h" +#include "pim_rpf.h" +#include "pim_macro.h" +#include "pim_oil.h" +#include "pim_upstream.h" +#include "pim_ssm.h" +#include "pim_rp.h" +#include "pim_mlag.h" + +RB_GENERATE(pim_ifchannel_rb, pim_ifchannel, pim_ifp_rb, pim_ifchannel_compare); + +int pim_ifchannel_compare(const struct pim_ifchannel *ch1, + const struct pim_ifchannel *ch2) +{ + struct pim_interface *pim_ifp1; + struct pim_interface *pim_ifp2; + + pim_ifp1 = ch1->interface->info; + pim_ifp2 = ch2->interface->info; + + if (pim_ifp1->mroute_vif_index < pim_ifp2->mroute_vif_index) + return -1; + + if (pim_ifp1->mroute_vif_index > pim_ifp2->mroute_vif_index) + return 1; + + return pim_sgaddr_cmp(ch1->sg, ch2->sg); +} + +/* + * A (*,G) or a (*,*) is going away + * remove the parent pointer from + * those pointing at us + */ +static void pim_ifchannel_remove_children(struct pim_ifchannel *ch) +{ + struct pim_ifchannel *child; + + if (!ch->sources) + return; + + while (!list_isempty(ch->sources)) { + child = listnode_head(ch->sources); + child->parent = NULL; + listnode_delete(ch->sources, child); + } +} + +/* + * A (*,G) or a (*,*) is being created + * find all the children that would point + * at us. + */ +static void pim_ifchannel_find_new_children(struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp = ch->interface->info; + struct pim_ifchannel *child; + + // Basic Sanity that we are not being silly + if (!pim_addr_is_any(ch->sg.src) && !pim_addr_is_any(ch->sg.grp)) + return; + + if (pim_addr_is_any(ch->sg.src) && pim_addr_is_any(ch->sg.grp)) + return; + + RB_FOREACH (child, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) { + if (!pim_addr_is_any(ch->sg.grp) && + !pim_addr_cmp(child->sg.grp, ch->sg.grp) && (child != ch)) { + child->parent = ch; + listnode_add_sort(ch->sources, child); + } + } +} + +void pim_ifchannel_delete(struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp; + struct pim_upstream *up; + + pim_ifp = ch->interface->info; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel entry %s(%s) del start", __func__, + ch->sg_str, ch->interface->name); + + if (PIM_I_am_DualActive(pim_ifp)) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: if-chnanel-%s is deleted from a Dual active Interface", + __func__, ch->sg_str); + /* Post Delete only if it is the last Dual-active Interface */ + if (ch->upstream->dualactive_ifchannel_count == 1) { + pim_mlag_up_local_del(pim_ifp->pim, ch->upstream); + PIM_UPSTREAM_FLAG_UNSET_MLAG_INTERFACE( + ch->upstream->flags); + } + ch->upstream->dualactive_ifchannel_count--; + } + + if (ch->upstream->channel_oil) { + uint32_t mask = PIM_OIF_FLAG_PROTO_PIM; + if (ch->upstream->flags & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + mask |= PIM_OIF_FLAG_PROTO_GM; + + /* + * A S,G RPT channel can have an empty oil, we also + * need to take into account the fact that a ifchannel + * might have been suppressing a *,G ifchannel from + * being inherited. So let's figure out what + * needs to be done here + */ + if (!pim_addr_is_any(ch->sg.src) && ch->parent && + pim_upstream_evaluate_join_desired_interface( + ch->upstream, ch, ch->parent)) + pim_channel_add_oif(ch->upstream->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + + pim_channel_del_oif(ch->upstream->channel_oil, + ch->interface, mask, __func__); + /* + * Do we have any S,G's that are inheriting? + * Nuke from on high too. + */ + if (ch->upstream->sources) { + struct pim_upstream *child; + struct listnode *up_node; + + for (ALL_LIST_ELEMENTS_RO(ch->upstream->sources, + up_node, child)) + pim_channel_del_inherited_oif( + child->channel_oil, + ch->interface, + __func__); + } + } + + /* + * When this channel is removed + * we need to find all our children + * and make sure our pointers are fixed + */ + pim_ifchannel_remove_children(ch); + + if (ch->sources) + list_delete(&ch->sources); + + listnode_delete(ch->upstream->ifchannels, ch); + + up = ch->upstream; + + /* upstream is common across ifchannels, check if upstream's + ifchannel list is empty before deleting upstream_del + ref count will take care of it. + */ + if (ch->upstream->ref_count > 0) + up = pim_upstream_del(pim_ifp->pim, ch->upstream, __func__); + + else { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Avoiding deletion of upstream with ref_count %d from ifchannel(%s): %s", + __func__, ch->upstream->ref_count, + ch->interface->name, ch->sg_str); + } + + ch->upstream = NULL; + + EVENT_OFF(ch->t_ifjoin_expiry_timer); + EVENT_OFF(ch->t_ifjoin_prune_pending_timer); + EVENT_OFF(ch->t_ifassert_timer); + + if (ch->parent) { + listnode_delete(ch->parent->sources, ch); + ch->parent = NULL; + } + + RB_REMOVE(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel entry %s(%s) is deleted ", __func__, + ch->sg_str, ch->interface->name); + + XFREE(MTYPE_PIM_IFCHANNEL, ch); + + if (up) + pim_upstream_update_join_desired(pim_ifp->pim, up); +} + +void pim_ifchannel_delete_all(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + if (!pim_ifp) + return; + + while (!RB_EMPTY(pim_ifchannel_rb, &pim_ifp->ifchannel_rb)) { + ch = RB_ROOT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb); + + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_NOINFO); + pim_ifchannel_delete(ch); + } +} + +void delete_on_noinfo(struct pim_ifchannel *ch) +{ + if (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO + && ch->ifjoin_state == PIM_IFJOIN_NOINFO + && ch->t_ifjoin_expiry_timer == NULL) + pim_ifchannel_delete(ch); +} + +void pim_ifchannel_ifjoin_switch(const char *caller, struct pim_ifchannel *ch, + enum pim_ifjoin_state new_state) +{ + enum pim_ifjoin_state old_state = ch->ifjoin_state; + struct pim_interface *pim_ifp = ch->interface->info; + struct pim_ifchannel *child_ch; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "PIM_IFCHANNEL(%s): %s is switching from %s to %s", + ch->interface->name, ch->sg_str, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), + pim_ifchannel_ifjoin_name(new_state, 0)); + + + if (old_state == new_state) { + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug( + "%s called by %s: non-transition on state %d (%s)", + __func__, caller, new_state, + pim_ifchannel_ifjoin_name(new_state, 0)); + } + return; + } + + ch->ifjoin_state = new_state; + + if (pim_addr_is_any(ch->sg.src)) { + struct pim_upstream *up = ch->upstream; + struct pim_upstream *child; + struct listnode *up_node; + + if (up) { + if (ch->ifjoin_state == PIM_IFJOIN_NOINFO) { + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, + child)) { + struct channel_oil *c_oil = + child->channel_oil; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s %s: Prune(S,G)=%s from %s", + __FILE__, __func__, + child->sg_str, + up->sg_str); + if (!c_oil) + continue; + + /* + * If the S,G has no if channel and the + * c_oil still + * has output here then the *,G was + * supplying the implied + * if channel. So remove it. + */ + if (oil_if_has(c_oil, + pim_ifp->mroute_vif_index)) + pim_channel_del_inherited_oif( + c_oil, ch->interface, + __func__); + } + } + if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, + child)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s %s: Join(S,G)=%s from %s", + __FILE__, __func__, + child->sg_str, + up->sg_str); + + /* check if the channel can be + * inherited into the SG's OIL + */ + child_ch = pim_ifchannel_find( + ch->interface, + &child->sg); + if (pim_upstream_eval_inherit_if( + child, child_ch, ch)) { + pim_channel_add_oif( + child->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired( + pim_ifp->pim, child); + } + } + } + } + } + /* Transition to/from NOINFO ? */ + if ((old_state == PIM_IFJOIN_NOINFO) + || (new_state == PIM_IFJOIN_NOINFO)) { + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s", + ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" + : "UP"), + ch->sg_str, ch->interface->name); + } + + /* pim_upstream_update_join_desired looks at up->channel_oil, + * but that's updated from pim_forward_stop(). Need this here + * so we correctly determine join_desired right below. + */ + if (new_state == PIM_IFJOIN_NOINFO) + pim_forward_stop(ch); + + /* + Record uptime of state transition to/from NOINFO + */ + ch->ifjoin_creation = pim_time_monotonic_sec(); + + pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); + pim_ifchannel_update_could_assert(ch); + pim_ifchannel_update_assert_tracking_desired(ch); + } +} + +const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state, + int flags) +{ + switch (ifjoin_state) { + case PIM_IFJOIN_NOINFO: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(NI)"; + else + return "NOINFO"; + case PIM_IFJOIN_JOIN: + return "JOIN"; + case PIM_IFJOIN_PRUNE: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(P)"; + else + return "PRUNE"; + case PIM_IFJOIN_PRUNE_PENDING: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(PP)"; + else + return "PRUNEP"; + case PIM_IFJOIN_PRUNE_TMP: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(P')"; + else + return "PRUNET"; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (PIM_IF_FLAG_TEST_S_G_RPT(flags)) + return "SGRpt(PP')"; + else + return "PRUNEPT"; + } + + return "ifjoin_bad_state"; +} + +const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state) +{ + switch (ifassert_state) { + case PIM_IFASSERT_NOINFO: + return "NOINFO"; + case PIM_IFASSERT_I_AM_WINNER: + return "WINNER"; + case PIM_IFASSERT_I_AM_LOSER: + return "LOSER"; + } + + return "ifassert_bad_state"; +} + +/* + RFC 4601: 4.6.5. Assert State Macros + + AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I) + defaults to Infinity when in the NoInfo state. +*/ +void reset_ifassert_state(struct pim_ifchannel *ch) +{ + EVENT_OFF(ch->t_ifassert_timer); + + pim_ifassert_winner_set(ch, PIM_IFASSERT_NOINFO, PIMADDR_ANY, + router->infinite_assert_metric); +} + +struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp, pim_sgaddr *sg) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct pim_ifchannel lookup; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%pSG: multicast not enabled on interface %s", + __func__, sg, ifp->name); + return NULL; + } + + lookup.sg = *sg; + lookup.interface = ifp; + ch = RB_FIND(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, &lookup); + + return ch; +} + +static void ifmembership_set(struct pim_ifchannel *ch, + enum pim_ifmembership membership) +{ + struct pim_interface *pim_ifp = ch->interface->info; + + if (ch->local_ifmembership == membership) + return; + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("%s: (S,G)=%s membership now is %s on interface %s", + __func__, ch->sg_str, + membership == PIM_IFMEMBERSHIP_INCLUDE ? "INCLUDE" + : "NOINFO", + ch->interface->name); + } + + ch->local_ifmembership = membership; + + pim_upstream_update_join_desired(pim_ifp->pim, ch->upstream); + pim_ifchannel_update_could_assert(ch); + pim_ifchannel_update_assert_tracking_desired(ch); +} + + +void pim_ifchannel_membership_clear(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) + ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); +} + +void pim_ifchannel_delete_on_noinfo(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch, *ch_tmp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch_tmp) + delete_on_noinfo(ch); +} + +/* + * For a given Interface, if we are given a S,G + * Find the *,G (If we have it). + * If we are passed a *,G, find the *,* ifchannel + * if we have it. + */ +static struct pim_ifchannel *pim_ifchannel_find_parent(struct pim_ifchannel *ch) +{ + pim_sgaddr parent_sg = ch->sg; + struct pim_ifchannel *parent = NULL; + + // (S,G) + if (!pim_addr_is_any(parent_sg.src) && + !pim_addr_is_any(parent_sg.grp)) { + parent_sg.src = PIMADDR_ANY; + parent = pim_ifchannel_find(ch->interface, &parent_sg); + + if (parent) + listnode_add(parent->sources, ch); + return parent; + } + + return NULL; +} + +struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp, pim_sgaddr *sg, + uint8_t source_flags, int up_flags) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + struct pim_upstream *up; + + ch = pim_ifchannel_find(ifp, sg); + if (ch) { + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_PIM) + PIM_IF_FLAG_SET_PROTO_PIM(ch->flags); + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + PIM_IF_FLAG_SET_PROTO_IGMP(ch->flags); + + ch->upstream->flags |= up_flags; + + return ch; + } + + pim_ifp = ifp->info; + + ch = XCALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch)); + + ch->flags = 0; + if ((source_flags & PIM_ENCODE_RPT_BIT) + && !(source_flags & PIM_ENCODE_WC_BIT)) + PIM_IF_FLAG_SET_S_G_RPT(ch->flags); + + ch->interface = ifp; + ch->sg = *sg; + snprintfrr(ch->sg_str, sizeof(ch->sg_str), "%pSG", sg); + ch->parent = pim_ifchannel_find_parent(ch); + if (pim_addr_is_any(ch->sg.src)) { + ch->sources = list_new(); + ch->sources->cmp = + (int (*)(void *, void *))pim_ifchannel_compare; + } else + ch->sources = NULL; + + pim_ifchannel_find_new_children(ch); + ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO; + + ch->ifjoin_state = PIM_IFJOIN_NOINFO; + ch->t_ifjoin_expiry_timer = NULL; + ch->t_ifjoin_prune_pending_timer = NULL; + ch->ifjoin_creation = 0; + + RB_INSERT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb, ch); + + up = pim_upstream_add(pim_ifp->pim, sg, NULL, up_flags, __func__, ch); + + ch->upstream = up; + + listnode_add_sort(up->ifchannels, ch); + + ch->ifassert_my_metric = pim_macro_ch_my_assert_metric_eval(ch); + ch->ifassert_winner_metric = pim_macro_ch_my_assert_metric_eval(ch); + + ch->ifassert_winner = PIMADDR_ANY; + + /* Assert state */ + ch->t_ifassert_timer = NULL; + ch->ifassert_state = PIM_IFASSERT_NOINFO; + reset_ifassert_state(ch); + if (pim_macro_ch_could_assert_eval(ch)) + PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); + else + PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); + + if (pim_macro_assert_tracking_desired_eval(ch)) + PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); + else + PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); + + /* + * advertise MLAG Data to MLAG peer + */ + if (PIM_I_am_DualActive(pim_ifp)) { + up->dualactive_ifchannel_count++; + /* Sync once for upstream */ + if (up->dualactive_ifchannel_count == 1) { + PIM_UPSTREAM_FLAG_SET_MLAG_INTERFACE(up->flags); + pim_mlag_up_local_add(pim_ifp->pim, up); + } + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: New Dual active if-chnanel is added to upstream:%s count:%d, flags:0x%x", + __func__, up->sg_str, + up->dualactive_ifchannel_count, up->flags); + } + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_PIM) + PIM_IF_FLAG_SET_PROTO_PIM(ch->flags); + + if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) + PIM_IF_FLAG_SET_PROTO_IGMP(ch->flags); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel %s(%s) is created ", __func__, + ch->sg_str, ch->interface->name); + + return ch; +} + +static void ifjoin_to_noinfo(struct pim_ifchannel *ch) +{ + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_NOINFO); + + PIM_UPSTREAM_FLAG_UNSET_SRC_PIM(ch->upstream->flags); + + PIM_IF_FLAG_UNSET_PROTO_PIM(ch->flags); + + delete_on_noinfo(ch); +} + +static void on_ifjoin_expiry_timer(struct event *t) +{ + struct pim_ifchannel *ch; + + ch = EVENT_ARG(t); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: ifchannel %s expiry timer", __func__, + ch->sg_str); + + ifjoin_to_noinfo(ch); + /* ch may have been deleted */ +} + +static void on_ifjoin_prune_pending_timer(struct event *t) +{ + struct pim_ifchannel *ch; + int send_prune_echo; /* boolean */ + struct interface *ifp; + struct pim_interface *pim_ifp; + + ch = EVENT_ARG(t); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: IFCHANNEL%pSG %s Prune Pending Timer Popped", + __func__, &ch->sg, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags)); + + if (ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING) { + ifp = ch->interface; + pim_ifp = ifp->info; + if (!PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) { + /* Send PruneEcho(S,G) ? */ + send_prune_echo = + (listcount(pim_ifp->pim_neighbor_list) > 1); + + if (send_prune_echo) { + struct pim_rpf rpf; + + rpf.source_nexthop.interface = ifp; + rpf.rpf_addr = pim_ifp->primary_address; + pim_jp_agg_single_upstream_send( + &rpf, ch->upstream, 0); + } + + ifjoin_to_noinfo(ch); + } else { + /* If SGRpt flag is set on ifchannel, Trigger SGRpt + * message on RP path upon prune timer expiry. + */ + ch->ifjoin_state = PIM_IFJOIN_PRUNE; + struct pim_upstream *parent = + ch->upstream->parent; + + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + + pim_jp_agg_single_upstream_send(&parent->rpf, + parent, true); + /* + * SGRpt prune pending expiry has to install + * SG entry with empty olist to drop the SG + * traffic incase no other intf exists. + * On that scenario, SG entry wouldn't have + * got installed until Prune pending timer + * expired. So install now. + */ + pim_channel_del_oif( + ch->upstream->channel_oil, ifp, + PIM_OIF_FLAG_PROTO_STAR, __func__); + pim_channel_del_oif(ch->upstream->channel_oil, ifp, + PIM_OIF_FLAG_PROTO_PIM, __func__); + if (!ch->upstream->channel_oil->installed) + pim_upstream_mroute_add( + ch->upstream->channel_oil, + __func__); + } + /* from here ch may have been deleted */ + } +} + +static void check_recv_upstream(int is_join, struct interface *recv_ifp, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, int holdtime) +{ + struct pim_upstream *up; + struct pim_interface *pim_ifp = recv_ifp->info; + pim_addr rpf_addr; + + /* Upstream (S,G) in Joined state ? */ + up = pim_upstream_find(pim_ifp->pim, sg); + if (!up) + return; + if (up->join_state != PIM_UPSTREAM_JOINED) + return; + + /* Upstream (S,G) in Joined state */ + + if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { + /* RPF'(S,G) not found */ + zlog_warn("%s %s: RPF'%s not found", __FILE__, __func__, + up->sg_str); + return; + } + + rpf_addr = up->rpf.rpf_addr; + + /* upstream directed to RPF'(S,G) ? */ + if (pim_addr_cmp(upstream, rpf_addr)) { + zlog_warn( + "%s %s: (S,G)=%s upstream=%pPAs not directed to RPF'(S,G)=%pPAs on interface %s", + __FILE__, __func__, up->sg_str, &upstream, &rpf_addr, + recv_ifp->name); + return; + } + /* upstream directed to RPF'(S,G) */ + + if (is_join) { + /* Join(S,G) to RPF'(S,G) */ + pim_upstream_join_suppress(up, up->rpf.rpf_addr, holdtime); + return; + } + + /* Prune to RPF'(S,G) */ + + if (source_flags & PIM_RPT_BIT_MASK) { + if (source_flags & PIM_WILDCARD_BIT_MASK) { + /* Prune(*,G) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override( + "Prune(*,G)", up); + return; + } + + /* Prune(S,G,rpt) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)", + up); + return; + } + + /* Prune(S,G) to RPF'(S,G) */ + pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up); +} + +static int nonlocal_upstream(int is_join, struct interface *recv_ifp, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, uint16_t holdtime) +{ + struct pim_interface *recv_pim_ifp; + int is_local; /* boolean */ + + recv_pim_ifp = recv_ifp->info; + assert(recv_pim_ifp); + + is_local = !pim_addr_cmp(upstream, recv_pim_ifp->primary_address); + + if (is_local) + return 0; + + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_warn( + "%s: recv %s (S,G)=%pSG to non-local upstream=%pPAs on %s", + __func__, is_join ? "join" : "prune", sg, &upstream, + recv_ifp->name); + + /* + * Since recv upstream addr was not directed to our primary + * address, check if we should react to it in any way. + */ + check_recv_upstream(is_join, recv_ifp, upstream, sg, source_flags, + holdtime); + + return 1; /* non-local */ +} + +static void pim_ifchannel_ifjoin_handler(struct pim_ifchannel *ch, + struct pim_interface *pim_ifp) +{ + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_JOIN); + PIM_IF_FLAG_UNSET_S_G_RPT(ch->flags); + /* check if the interface qualifies as an immediate + * OIF + */ + if (pim_upstream_evaluate_join_desired_interface( + ch->upstream, ch, + NULL /*starch*/)) { + pim_channel_add_oif(ch->upstream->channel_oil, + ch->interface, + PIM_OIF_FLAG_PROTO_PIM, + __func__); + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + } +} + + +void pim_ifchannel_join_add(struct interface *ifp, pim_addr neigh_addr, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, uint16_t holdtime) +{ + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + if (nonlocal_upstream(1 /* join */, ifp, upstream, sg, source_flags, + holdtime)) { + return; + } + + ch = pim_ifchannel_add(ifp, sg, source_flags, + PIM_UPSTREAM_FLAG_MASK_SRC_PIM); + + /* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + Transitions from "I am Assert Loser" State + + Receive Join(S,G) on Interface I + + We receive a Join(S,G) that has the Upstream Neighbor Address + field set to my primary IP address on interface I. The action is + to transition to NoInfo state, delete this (S,G) assert state + (Actions A5 below), and allow the normal PIM Join/Prune mechanisms + to operate. + + Notice: The nonlocal_upstream() test above ensures the upstream + address of the join message is our primary address. + */ + if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + zlog_warn("%s: Assert Loser recv Join%s from %pPA on %s", + __func__, ch->sg_str, &neigh_addr, ifp->name); + + assert_action_a5(ch); + } + + pim_ifp = ifp->info; + assert(pim_ifp); + + switch (ch->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + pim_ifchannel_ifjoin_switch(__func__, ch, PIM_IFJOIN_JOIN); + if (pim_macro_chisin_oiflist(ch)) { + pim_upstream_inherited_olist(pim_ifp->pim, + ch->upstream); + pim_forward_start(ch); + } + /* + * If we are going to be a LHR, we need to note it + */ + if (ch->upstream->parent && + (PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR( + ch->upstream->parent->flags)) + && !(ch->upstream->flags + & PIM_UPSTREAM_FLAG_MASK_SRC_LHR)) { + pim_upstream_ref(ch->upstream, + PIM_UPSTREAM_FLAG_MASK_SRC_LHR, + __func__); + pim_upstream_keep_alive_timer_start( + ch->upstream, pim_ifp->pim->keep_alive_time); + } + break; + case PIM_IFJOIN_JOIN: + assert(!ch->t_ifjoin_prune_pending_timer); + + /* + In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to + a + previously received join message with holdtime=0xFFFF. + */ + if (ch->t_ifjoin_expiry_timer) { + unsigned long remain = event_timer_remain_second( + ch->t_ifjoin_expiry_timer); + if (remain > holdtime) { + /* + RFC 4601: 4.5.3. Receiving (S,G) Join/Prune + Messages + + Transitions from Join State + + The (S,G) downstream state machine on + interface I remains in + Join state, and the Expiry Timer (ET) is + restarted, set to + maximum of its current value and the HoldTime + from the + triggering Join/Prune message. + + Conclusion: Do not change the ET if the + current value is + higher than the received join holdtime. + */ + return; + } + } + EVENT_OFF(ch->t_ifjoin_expiry_timer); + break; + case PIM_IFJOIN_PRUNE: + if (source_flags & PIM_ENCODE_RPT_BIT) { + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_NOINFO); + EVENT_OFF(ch->t_ifjoin_expiry_timer); + delete_on_noinfo(ch); + return; + } else + pim_ifchannel_ifjoin_handler(ch, pim_ifp); + break; + case PIM_IFJOIN_PRUNE_PENDING: + /* + * Transitions from Prune-Pending State (Receive Join) + * RFC 7761 Sec 4.5.2: + * The (S,G) downstream state machine on interface I + * transitions to the Join state. The Prune-Pending Timer is + * canceled (without triggering an expiry event). The + * Expiry Timer (ET) is restarted and is then set to the + * maximum of its current value and the HoldTime from the + * triggering Join/Prune message. + */ + EVENT_OFF(ch->t_ifjoin_prune_pending_timer); + + /* Check if SGRpt join Received */ + if ((source_flags & PIM_ENCODE_RPT_BIT) && + !pim_addr_is_any(sg->src)) { + /* + * Transitions from Prune-Pending State (Rcv SGRpt Join) + * RFC 7761 Sec 4.5.3: + * The (S,G,rpt) downstream state machine on interface + * I transitions to the NoInfo state.The ET and PPT are + * cancelled. + */ + EVENT_OFF(ch->t_ifjoin_expiry_timer); + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_NOINFO); + return; + } + + pim_ifchannel_ifjoin_handler(ch, pim_ifp); + + if (ch->t_ifjoin_expiry_timer) { + unsigned long remain = event_timer_remain_second( + ch->t_ifjoin_expiry_timer); + + if (remain > holdtime) + return; + } + EVENT_OFF(ch->t_ifjoin_expiry_timer); + + break; + case PIM_IFJOIN_PRUNE_TMP: + break; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + break; + } + + if (holdtime != 0xFFFF) { + event_add_timer(router->master, on_ifjoin_expiry_timer, ch, + holdtime, &ch->t_ifjoin_expiry_timer); + } +} + +void pim_ifchannel_prune(struct interface *ifp, pim_addr upstream, + pim_sgaddr *sg, uint8_t source_flags, + uint16_t holdtime) +{ + struct pim_ifchannel *ch; + struct pim_interface *pim_ifp; + int jp_override_interval_msec; + + if (nonlocal_upstream(0 /* prune */, ifp, upstream, sg, source_flags, + holdtime)) { + return; + } + + ch = pim_ifchannel_find(ifp, sg); + if (!ch && !(source_flags & PIM_ENCODE_RPT_BIT)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: Received prune with no relevant ifchannel %s%pSG state: %d", + __func__, ifp->name, sg, + source_flags); + return; + } + + ch = pim_ifchannel_add(ifp, sg, source_flags, + PIM_UPSTREAM_FLAG_MASK_SRC_PIM); + + pim_ifp = ifp->info; + + switch (ch->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + if (source_flags & PIM_ENCODE_RPT_BIT) { + if (!(source_flags & PIM_ENCODE_WC_BIT)) + PIM_IF_FLAG_SET_S_G_RPT(ch->flags); + + ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; + if (listcount(pim_ifp->pim_neighbor_list) > 1) + jp_override_interval_msec = + pim_if_jp_override_interval_msec(ifp); + else + jp_override_interval_msec = + 0; /* schedule to expire immediately */ + /* If we called ifjoin_prune() directly instead, care + should + be taken not to use "ch" afterwards since it would be + deleted. */ + + EVENT_OFF(ch->t_ifjoin_prune_pending_timer); + EVENT_OFF(ch->t_ifjoin_expiry_timer); + event_add_timer_msec(router->master, + on_ifjoin_prune_pending_timer, ch, + jp_override_interval_msec, + &ch->t_ifjoin_prune_pending_timer); + event_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + pim_upstream_update_join_desired(pim_ifp->pim, + ch->upstream); + } + break; + case PIM_IFJOIN_PRUNE_PENDING: + /* nothing to do */ + break; + case PIM_IFJOIN_JOIN: + /* + * The (S,G) downstream state machine on interface I + * transitions to the Prune-Pending state. The + * Prune-Pending Timer is started. It is set to the + * J/P_Override_Interval(I) if the router has more than one + * neighbor on that interface; otherwise, it is set to zero, + * causing it to expire immediately. + */ + + pim_ifchannel_ifjoin_switch(__func__, ch, + PIM_IFJOIN_PRUNE_PENDING); + + if (listcount(pim_ifp->pim_neighbor_list) > 1) + jp_override_interval_msec = + pim_if_jp_override_interval_msec(ifp); + else + jp_override_interval_msec = + 0; /* schedule to expire immediately */ + /* If we called ifjoin_prune() directly instead, care should + be taken not to use "ch" afterwards since it would be + deleted. */ + EVENT_OFF(ch->t_ifjoin_prune_pending_timer); + event_add_timer_msec(router->master, + on_ifjoin_prune_pending_timer, ch, + jp_override_interval_msec, + &ch->t_ifjoin_prune_pending_timer); + break; + case PIM_IFJOIN_PRUNE: + if (source_flags & PIM_ENCODE_RPT_BIT) { + EVENT_OFF(ch->t_ifjoin_prune_pending_timer); + /* + * While in Prune State, Receive SGRpt Prune. + * RFC 7761 Sec 4.5.3: + * The (S,G,rpt) downstream state machine on interface I + * remains in Prune state. The Expiry Timer (ET) is + * restarted and is then set to the maximum of its + * current value and the HoldTime from the triggering + * Join/Prune message. + */ + if (ch->t_ifjoin_expiry_timer) { + unsigned long rem = event_timer_remain_second( + ch->t_ifjoin_expiry_timer); + + if (rem > holdtime) + return; + EVENT_OFF(ch->t_ifjoin_expiry_timer); + } + + event_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + case PIM_IFJOIN_PRUNE_TMP: + if (source_flags & PIM_ENCODE_RPT_BIT) { + ch->ifjoin_state = PIM_IFJOIN_PRUNE; + EVENT_OFF(ch->t_ifjoin_expiry_timer); + event_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (source_flags & PIM_ENCODE_RPT_BIT) { + ch->ifjoin_state = PIM_IFJOIN_PRUNE_PENDING; + EVENT_OFF(ch->t_ifjoin_expiry_timer); + event_add_timer(router->master, on_ifjoin_expiry_timer, + ch, holdtime, + &ch->t_ifjoin_expiry_timer); + } + break; + } +} + +int pim_ifchannel_local_membership_add(struct interface *ifp, pim_sgaddr *sg, + bool is_vxlan) +{ + struct pim_ifchannel *ch, *starch; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + int up_flags; + + /* PIM enabled on interface? */ + pim_ifp = ifp->info; + if (!pim_ifp) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s:%pSG Expected pim interface setup for %s", + __func__, sg, ifp->name); + return 0; + } + + if (!pim_ifp->pim_enable) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s:%pSG PIM is not configured on this interface %s", + __func__, sg, ifp->name); + return 0; + } + + pim = pim_ifp->pim; + + /* skip (*,G) ch creation if G is of type SSM */ + if (pim_addr_is_any(sg->src)) { + if (pim_is_grp_ssm(pim, sg->grp)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: local membership (S,G)=%pSG ignored as group is SSM", + __func__, sg); + return 1; + } + } + + /* vxlan term mroutes use ipmr-lo as local member to + * pull down multicast vxlan tunnel traffic + */ + up_flags = is_vxlan ? PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM : + PIM_UPSTREAM_FLAG_MASK_SRC_IGMP; + ch = pim_ifchannel_add(ifp, sg, 0, up_flags); + + ifmembership_set(ch, PIM_IFMEMBERSHIP_INCLUDE); + + if (pim_addr_is_any(sg->src)) { + struct pim_upstream *up = pim_upstream_find(pim, sg); + struct pim_upstream *child; + struct listnode *up_node; + + starch = ch; + + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { + if (PIM_DEBUG_EVENTS) + zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s", + __FILE__, __func__, child->sg_str, + ifp->name, up->sg_str); + + if (!child->rpf.source_nexthop.interface) { + /* when iif unknown, do not inherit */ + if (PIM_DEBUG_EVENTS) + zlog_debug( + "Skipped (S,G)=%s(%s) from %s: no iif", + child->sg_str, ifp->name, + up->sg_str); + continue; + } + + ch = pim_ifchannel_find(ifp, &child->sg); + if (pim_upstream_evaluate_join_desired_interface( + child, ch, starch)) { + pim_channel_add_oif(child->channel_oil, ifp, + PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired(pim, child); + } + } + + if (pim->spt.switchover == PIM_SPT_INFINITY) { + if (pim->spt.plist) { + struct prefix_list *plist = prefix_list_lookup( + AFI_IP, pim->spt.plist); + struct prefix g; + + pim_addr_to_prefix(&g, up->sg.grp); + if (prefix_list_apply_ext(plist, NULL, &g, + true) == + PREFIX_DENY) { + pim_channel_add_oif( + up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, + __func__); + } + } + } else + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + } + + return 1; +} + +void pim_ifchannel_local_membership_del(struct interface *ifp, pim_sgaddr *sg) +{ + struct pim_ifchannel *starch, *ch, *orig; + struct pim_interface *pim_ifp; + + /* PIM enabled on interface? */ + pim_ifp = ifp->info; + if (!pim_ifp) + return; + if (!pim_ifp->pim_enable) + return; + + orig = ch = pim_ifchannel_find(ifp, sg); + if (!ch) + return; + ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO); + + if (pim_addr_is_any(sg->src)) { + struct pim_upstream *up = pim_upstream_find(pim_ifp->pim, sg); + struct pim_upstream *child; + struct listnode *up_node, *up_nnode; + + starch = ch; + + for (ALL_LIST_ELEMENTS(up->sources, up_node, up_nnode, child)) { + struct channel_oil *c_oil = child->channel_oil; + struct pim_ifchannel *chchannel = + pim_ifchannel_find(ifp, &child->sg); + + pim_ifp = ifp->info; + + if (PIM_DEBUG_EVENTS) + zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s", + __FILE__, __func__, up->sg_str, + ifp->name, child->sg_str); + + ch = pim_ifchannel_find(ifp, &child->sg); + /* + * If the S,G has no if channel and the c_oil still + * has output here then the *,G was supplying the + * implied + * if channel. So remove it. + */ + if (!pim_upstream_evaluate_join_desired_interface( + child, ch, starch) || + (!chchannel && + oil_if_has(c_oil, pim_ifp->mroute_vif_index))) { + pim_channel_del_inherited_oif(c_oil, ifp, + __func__); + } + + /* Child node removal/ref count-- will happen as part of + * parent' delete_no_info */ + } + } + + /* Resettng the IGMP flags here */ + if (orig->upstream) + PIM_UPSTREAM_FLAG_UNSET_SRC_IGMP(orig->upstream->flags); + + PIM_IF_FLAG_UNSET_PROTO_IGMP(orig->flags); + + delete_on_noinfo(orig); +} + +void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch) +{ + int old_couldassert = + PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)); + int new_couldassert = + PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch)); + + if (new_couldassert == old_couldassert) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: CouldAssert(%pPAs,%pPAs,%s) changed from %d to %d", + __func__, &ch->sg.src, &ch->sg.grp, + ch->interface->name, old_couldassert, + new_couldassert); + + if (new_couldassert) { + /* CouldAssert(S,G,I) switched from false to true */ + PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags); + } else { + /* CouldAssert(S,G,I) switched from true to false */ + PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags); + + if (ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER) { + assert_action_a4(ch); + } + } + + pim_ifchannel_update_my_assert_metric(ch); +} + +/* + my_assert_metric may be affected by: + + CouldAssert(S,G) + pim_ifp->primary_address + rpf->source_nexthop.mrib_metric_preference; + rpf->source_nexthop.mrib_route_metric; + */ +void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch) +{ + struct pim_assert_metric my_metric_new = + pim_macro_ch_my_assert_metric_eval(ch); + + if (pim_assert_metric_match(&my_metric_new, &ch->ifassert_my_metric)) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "%s: my_assert_metric(%pPAs,%pPAs,%s) changed from %u,%u,%u,%pPAs to %u,%u,%u,%pPAs", + __func__, &ch->sg.src, &ch->sg.grp, ch->interface->name, + ch->ifassert_my_metric.rpt_bit_flag, + ch->ifassert_my_metric.metric_preference, + ch->ifassert_my_metric.route_metric, + &ch->ifassert_my_metric.ip_address, + my_metric_new.rpt_bit_flag, + my_metric_new.metric_preference, + my_metric_new.route_metric, &my_metric_new.ip_address); + + ch->ifassert_my_metric = my_metric_new; + + if (pim_assert_metric_better(&ch->ifassert_my_metric, + &ch->ifassert_winner_metric)) { + assert_action_a5(ch); + } +} + +void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch) +{ + int old_atd = PIM_FORCE_BOOLEAN( + PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags)); + int new_atd = + PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch)); + + if (new_atd == old_atd) + return; + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "%s: AssertTrackingDesired(%pPAs,%pPAs,%s) changed from %d to %d", + __func__, &ch->sg.src, &ch->sg.grp, ch->interface->name, + old_atd, new_atd); + + if (new_atd) { + /* AssertTrackingDesired(S,G,I) switched from false to true */ + PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags); + } else { + /* AssertTrackingDesired(S,G,I) switched from true to false */ + PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags); + + if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + assert_action_a5(ch); + } + } +} + +/* + * If we have a new pim interface, check to + * see if any of the pre-existing channels have + * their upstream out that way and turn on forwarding + * for that ifchannel then. + */ +void pim_ifchannel_scan_forward_start(struct interface *new_ifp) +{ + struct pim_interface *new_pim_ifp = new_ifp->info; + struct pim_instance *pim = new_pim_ifp->pim; + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *loop_pim_ifp = ifp->info; + struct pim_ifchannel *ch; + + if (!loop_pim_ifp) + continue; + + if (new_pim_ifp == loop_pim_ifp) + continue; + + RB_FOREACH (ch, pim_ifchannel_rb, &loop_pim_ifp->ifchannel_rb) { + if (ch->ifjoin_state == PIM_IFJOIN_JOIN) { + struct pim_upstream *up = ch->upstream; + if ((!up->channel_oil) + && (up->rpf.source_nexthop + .interface == new_ifp)) + pim_forward_start(ch); + } + } + } +} + +/* + * Downstream per-interface (S,G,rpt) state machine + * states that we need to move (S,G,rpt) items + * into different states at the start of the + * reception of a *,G join as well, when + * we get End of Message + */ +void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel *ch, int eom, + uint8_t join) +{ + bool send_upstream_starg = false; + struct pim_ifchannel *child; + struct listnode *ch_node, *nch_node; + struct pim_instance *pim = + ((struct pim_interface *)ch->interface->info)->pim; + struct pim_upstream *starup = ch->upstream; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: %s %s eom: %d join %u", __func__, + pim_ifchannel_ifjoin_name(ch->ifjoin_state, ch->flags), + ch->sg_str, eom, join); + if (!ch->sources) + return; + + for (ALL_LIST_ELEMENTS(ch->sources, ch_node, nch_node, child)) { + if (!PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) + continue; + + switch (child->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + case PIM_IFJOIN_JOIN: + break; + case PIM_IFJOIN_PRUNE: + if (!eom) + child->ifjoin_state = PIM_IFJOIN_PRUNE_TMP; + break; + case PIM_IFJOIN_PRUNE_PENDING: + if (!eom) + child->ifjoin_state = + PIM_IFJOIN_PRUNE_PENDING_TMP; + break; + case PIM_IFJOIN_PRUNE_TMP: + case PIM_IFJOIN_PRUNE_PENDING_TMP: + if (!eom) + break; + + if (child->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING_TMP) + EVENT_OFF(child->t_ifjoin_prune_pending_timer); + EVENT_OFF(child->t_ifjoin_expiry_timer); + + PIM_IF_FLAG_UNSET_S_G_RPT(child->flags); + child->ifjoin_state = PIM_IFJOIN_NOINFO; + + if ((I_am_RP(pim, child->sg.grp)) && + (!pim_upstream_empty_inherited_olist( + child->upstream))) { + pim_channel_add_oif( + child->upstream->channel_oil, + ch->interface, PIM_OIF_FLAG_PROTO_STAR, + __func__); + pim_upstream_update_join_desired(pim, + child->upstream); + } + send_upstream_starg = true; + + delete_on_noinfo(child); + break; + } + } + + if (send_upstream_starg) + pim_jp_agg_single_upstream_send(&starup->rpf, starup, true); +} diff --git a/pimd/pim_ifchannel.h b/pimd/pim_ifchannel.h new file mode 100644 index 0000000..4b0ff95 --- /dev/null +++ b/pimd/pim_ifchannel.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_IFCHANNEL_H +#define PIM_IFCHANNEL_H + +#include + +#include "if.h" +#include "prefix.h" + +#include "pim_assert.h" + +struct pim_ifchannel; +#include "pim_upstream.h" + +enum pim_ifmembership { PIM_IFMEMBERSHIP_NOINFO, PIM_IFMEMBERSHIP_INCLUDE }; + +enum pim_ifjoin_state { + PIM_IFJOIN_NOINFO, + PIM_IFJOIN_JOIN, + PIM_IFJOIN_PRUNE, + PIM_IFJOIN_PRUNE_PENDING, + PIM_IFJOIN_PRUNE_TMP, + PIM_IFJOIN_PRUNE_PENDING_TMP, +}; + +/* + Flag to detect change in CouldAssert(S,G,I) +*/ +#define PIM_IF_FLAG_MASK_COULD_ASSERT (1 << 0) +#define PIM_IF_FLAG_TEST_COULD_ASSERT(flags) ((flags) & PIM_IF_FLAG_MASK_COULD_ASSERT) +#define PIM_IF_FLAG_SET_COULD_ASSERT(flags) ((flags) |= PIM_IF_FLAG_MASK_COULD_ASSERT) +#define PIM_IF_FLAG_UNSET_COULD_ASSERT(flags) ((flags) &= ~PIM_IF_FLAG_MASK_COULD_ASSERT) +/* + Flag to detect change in AssertTrackingDesired(S,G,I) +*/ +#define PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED (1 << 1) +#define PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(flags) ((flags) & PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) +#define PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(flags) ((flags) |= PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) +#define PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(flags) ((flags) &= ~PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED) + +/* + * Flag to tell us if the ifchannel is (S,G,rpt) + */ +#define PIM_IF_FLAG_MASK_S_G_RPT (1 << 2) +#define PIM_IF_FLAG_TEST_S_G_RPT(flags) ((flags) & PIM_IF_FLAG_MASK_S_G_RPT) +#define PIM_IF_FLAG_SET_S_G_RPT(flags) ((flags) |= PIM_IF_FLAG_MASK_S_G_RPT) +#define PIM_IF_FLAG_UNSET_S_G_RPT(flags) ((flags) &= ~PIM_IF_FLAG_MASK_S_G_RPT) + +/* + * Flag to tell us if the ifchannel is proto PIM + */ +#define PIM_IF_FLAG_MASK_PROTO_PIM (1 << 3) +#define PIM_IF_FLAG_TEST_PROTO_PIM(flags) ((flags)&PIM_IF_FLAG_MASK_PROTO_PIM) +#define PIM_IF_FLAG_SET_PROTO_PIM(flags) ((flags) |= PIM_IF_FLAG_MASK_PROTO_PIM) +#define PIM_IF_FLAG_UNSET_PROTO_PIM(flags) \ + ((flags) &= ~PIM_IF_FLAG_MASK_PROTO_PIM) +/* + * Flag to tell us if the ifchannel is proto IGMP + */ +#define PIM_IF_FLAG_MASK_PROTO_IGMP (1 << 4) +#define PIM_IF_FLAG_TEST_PROTO_IGMP(flags) ((flags)&PIM_IF_FLAG_MASK_PROTO_IGMP) +#define PIM_IF_FLAG_SET_PROTO_IGMP(flags) \ + ((flags) |= PIM_IF_FLAG_MASK_PROTO_IGMP) +#define PIM_IF_FLAG_UNSET_PROTO_IGMP(flags) \ + ((flags) &= ~PIM_IF_FLAG_MASK_PROTO_IGMP) +/* + Per-interface (S,G) state +*/ +struct pim_ifchannel { + RB_ENTRY(rb_ifchannel) pim_ifp_rb; + + struct pim_ifchannel *parent; + struct list *sources; + pim_sgaddr sg; + char sg_str[PIM_SG_LEN]; + struct interface *interface; /* backpointer to interface */ + uint32_t flags; + + /* IGMPv3 determined interface has local members for (S,G) ? */ + enum pim_ifmembership local_ifmembership; + + /* Per-interface (S,G) Join/Prune State (Section 4.1.4 of RFC4601) */ + enum pim_ifjoin_state ifjoin_state; + struct event *t_ifjoin_expiry_timer; + struct event *t_ifjoin_prune_pending_timer; + int64_t ifjoin_creation; /* Record uptime of ifjoin state */ + + /* Per-interface (S,G) Assert State (Section 4.6.1 of RFC4601) */ + enum pim_ifassert_state ifassert_state; + struct event *t_ifassert_timer; + pim_addr ifassert_winner; + struct pim_assert_metric ifassert_winner_metric; + int64_t ifassert_creation; /* Record uptime of ifassert state */ + struct pim_assert_metric ifassert_my_metric; + + /* Upstream (S,G) state */ + struct pim_upstream *upstream; +}; + +RB_HEAD(pim_ifchannel_rb, pim_ifchannel); +RB_PROTOTYPE(pim_ifchannel_rb, pim_ifchannel, pim_ifp_rb, + pim_ifchannel_compare); + +void pim_ifchannel_delete(struct pim_ifchannel *ch); +void pim_ifchannel_delete_all(struct interface *ifp); +void pim_ifchannel_membership_clear(struct interface *ifp); +void pim_ifchannel_delete_on_noinfo(struct interface *ifp); +struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp, pim_sgaddr *sg); +struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp, pim_sgaddr *sg, + uint8_t ch_flags, int up_flags); +void pim_ifchannel_join_add(struct interface *ifp, pim_addr neigh_addr, + pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags, uint16_t holdtime); +void pim_ifchannel_prune(struct interface *ifp, pim_addr upstream, + pim_sgaddr *sg, uint8_t source_flags, + uint16_t holdtime); +int pim_ifchannel_local_membership_add(struct interface *ifp, pim_sgaddr *sg, + bool is_vxlan); +void pim_ifchannel_local_membership_del(struct interface *ifp, pim_sgaddr *sg); + +void pim_ifchannel_ifjoin_switch(const char *caller, struct pim_ifchannel *ch, + enum pim_ifjoin_state new_state); +const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state, + int flags); +const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state); + +int pim_ifchannel_isin_oiflist(struct pim_ifchannel *ch); + +void reset_ifassert_state(struct pim_ifchannel *ch); + +void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch); +void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch); +void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch); + +void pim_ifchannel_scan_forward_start(struct interface *new_ifp); +void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel *ch, int eom, + uint8_t join); + +int pim_ifchannel_compare(const struct pim_ifchannel *ch1, + const struct pim_ifchannel *ch2); + +void delete_on_noinfo(struct pim_ifchannel *ch); +#endif /* PIM_IFCHANNEL_H */ diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c new file mode 100644 index 0000000..063ba6e --- /dev/null +++ b/pimd/pim_igmp.c @@ -0,0 +1,1537 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "memory.h" +#include "prefix.h" +#include "if.h" +#include "hash.h" +#include "jhash.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_igmp.h" +#include "pim_igmpv2.h" +#include "pim_igmpv3.h" +#include "pim_igmp_mtrace.h" +#include "pim_iface.h" +#include "pim_sock.h" +#include "pim_mroute.h" +#include "pim_str.h" +#include "pim_util.h" +#include "pim_time.h" +#include "pim_ssm.h" +#include "pim_tib.h" + +static void group_timer_off(struct gm_group *group); +static void pim_igmp_general_query(struct event *t); + +void igmp_anysource_forward_start(struct pim_instance *pim, + struct gm_group *group) +{ + struct gm_source *source; + struct in_addr src_addr = {.s_addr = 0}; + /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ + assert(group->group_filtermode_isexcl); + assert(listcount(group->group_source_list) < 1); + + source = igmp_get_source_by_addr(group, src_addr, NULL); + if (!source) { + zlog_warn("%s: Failure to create * source", __func__); + return; + } + + igmp_source_forward_start(pim, source); +} + +void igmp_anysource_forward_stop(struct gm_group *group) +{ + struct gm_source *source; + struct in_addr star = {.s_addr = 0}; + + source = igmp_find_source_by_addr(group, star); + if (source) + igmp_source_forward_stop(source); +} + +static void igmp_source_forward_reevaluate_one(struct pim_instance *pim, + struct gm_source *source, + int is_grp_ssm) +{ + pim_sgaddr sg; + struct gm_group *group = source->source_group; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = group->group_addr; + + /** if there is no PIM state **/ + if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + if (pim_addr_is_any(source->source_addr)) { + if (is_grp_ssm) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "local membership del for %pSG as G is now SSM", + &sg); + igmp_source_forward_stop(source); + } + } else { + if (!is_grp_ssm) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "local membership del for %pSG as G is now ASM", + &sg); + igmp_source_forward_stop(source); + } + } + } else { + if (!pim_addr_is_any(source->source_addr) && (is_grp_ssm)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "local membership add for %pSG as G is now SSM", + &sg); + igmp_source_forward_start(pim, source); + } + } +} + +void igmp_source_forward_reevaluate_all(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode, *grp_nextnode; + struct gm_group *grp; + struct pim_ifchannel *ch, *ch_temp; + + if (!pim_ifp) + continue; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS(pim_ifp->gm_group_list, grpnode, + grp_nextnode, grp)) { + struct listnode *srcnode; + struct gm_source *src; + int is_grp_ssm; + + /* + * RFC 4604 + * section 2.2.1 + * EXCLUDE mode does not apply to SSM addresses, + * and an SSM-aware router will ignore + * MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE + * requests in the SSM range. + */ + is_grp_ssm = pim_is_grp_ssm(pim, grp->group_addr); + if (is_grp_ssm && grp->group_filtermode_isexcl) { + igmp_group_delete(grp); + } else { + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO( + grp->group_source_list, srcnode, + src)) { + igmp_source_forward_reevaluate_one( + pim, src, is_grp_ssm); + } /* scan group sources */ + } + } /* scan igmp groups */ + + RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, + ch_temp) { + if (pim_is_grp_ssm(pim, ch->sg.grp)) { + if (pim_addr_is_any(ch->sg.src)) + pim_ifchannel_delete(ch); + } + } + } /* scan interfaces */ +} + +void igmp_source_forward_start(struct pim_instance *pim, + struct gm_source *source) +{ + struct gm_group *group; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = source->source_group->group_addr; + + if (PIM_DEBUG_GM_TRACE) { + zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, + source->source_group->interface->name, + IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); + } + + /* + * PIM state should not be allowed for ASM group with valid source + * address. + */ + if ((!pim_is_grp_ssm(pim, source->source_group->group_addr)) && + !pim_addr_is_any(source->source_addr)) { + zlog_warn( + "%s: (S,G)=%pSG ASM range having source address, not allowed to create PIM state", + __func__, &sg); + return; + } + + /* Prevent IGMP interface from installing multicast route multiple + times */ + if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + return; + } + + group = source->source_group; + + if (tib_sg_gm_join(pim, sg, group->interface, + &source->source_channel_oil)) + IGMP_SOURCE_DO_FORWARDING(source->source_flags); +} + +/* + igmp_source_forward_stop: stop forwarding, but keep the source + igmp_source_delete: stop forwarding, and delete the source + */ +void igmp_source_forward_stop(struct gm_source *source) +{ + struct pim_interface *pim_oif; + struct gm_group *group; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = source->source_group->group_addr; + + if (PIM_DEBUG_GM_TRACE) { + zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, + source->source_group->interface->name, + IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); + } + + /* Prevent IGMP interface from removing multicast route multiple + times */ + if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + return; + } + + group = source->source_group; + pim_oif = group->interface->info; + + tib_sg_gm_prune(pim_oif->pim, sg, group->interface, + &source->source_channel_oil); + IGMP_SOURCE_DONT_FORWARDING(source->source_flags); +} + +/* This socket is used for TXing IGMP packets only, IGMP RX happens + * in pim_mroute_msg() + */ +static int igmp_sock_open(struct in_addr ifaddr, struct interface *ifp) +{ + int fd; + int join = 0; + struct in_addr group; + struct pim_interface *pim_ifp = ifp->info; + + fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifp, 1); + + if (fd < 0) + return -1; + + if (inet_aton(PIM_ALL_ROUTERS, &group)) { + if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex, pim_ifp)) + ++join; + } else { + zlog_warn( + "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s", + __FILE__, __func__, fd, &ifaddr, PIM_ALL_ROUTERS, errno, + safe_strerror(errno)); + } + + /* + IGMP routers periodically send IGMP general queries to + AllSystems=224.0.0.1 + IGMP routers must receive general queries for querier election. + */ + if (inet_aton(PIM_ALL_SYSTEMS, &group)) { + if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex, pim_ifp)) + ++join; + } else { + zlog_warn( + "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s", + __FILE__, __func__, fd, &ifaddr, + PIM_ALL_SYSTEMS, errno, safe_strerror(errno)); + } + + if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) { + if (!pim_socket_join(fd, group, ifaddr, ifp->ifindex, + pim_ifp)) { + ++join; + } + } else { + zlog_warn( + "%s %s: IGMP socket fd=%d interface %pI4: could not solve %s to group address: errno=%d: %s", + __FILE__, __func__, fd, &ifaddr, + PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno)); + } + + if (!join) { + flog_err_sys( + EC_LIB_SOCKET, + "IGMP socket fd=%d could not join any group on interface address %pI4", + fd, &ifaddr); + close(fd); + fd = -1; + } + + return fd; +} + +#undef IGMP_SOCK_DUMP + +#ifdef IGMP_SOCK_DUMP +static void igmp_sock_dump(array_t *igmp_sock_array) +{ + int size = array_size(igmp_sock_array); + for (int i = 0; i < size; ++i) { + + struct gm_sock *igmp = array_get(igmp_sock_array, i); + + zlog_debug("%s %s: [%d/%d] igmp_addr=%pI4 fd=%d", __FILE__, + __func__, i, size, &igmp->ifaddr, + igmp->fd); + } +} +#endif + +struct gm_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list, + struct in_addr ifaddr) +{ + struct listnode *sock_node; + struct gm_sock *igmp; + +#ifdef IGMP_SOCK_DUMP + igmp_sock_dump(igmp_sock_list); +#endif + + for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp)) + if (ifaddr.s_addr == igmp->ifaddr.s_addr) + return igmp; + + return NULL; +} + +static void pim_igmp_other_querier_expire(struct event *t) +{ + struct gm_sock *igmp; + + igmp = EVENT_ARG(t); + + assert(!igmp->t_igmp_query_timer); + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug("%s: Querier %s resuming", __func__, ifaddr_str); + } + /* Mark the interface address as querier address */ + igmp->querier_addr = igmp->ifaddr; + + /* + We are the current querier, then + re-start sending general queries. + RFC 2236 - sec 7 Other Querier + present timer expired (Send General + Query, Set Gen. Query. timer) + */ + pim_igmp_general_query(t); +} + +void pim_igmp_other_querier_timer_on(struct gm_sock *igmp) +{ + long other_querier_present_interval_msec; + struct pim_interface *pim_ifp; + + assert(igmp); + assert(igmp->interface); + assert(igmp->interface->info); + + pim_ifp = igmp->interface->info; + + if (igmp->t_other_querier_timer) { + /* + There is other querier present already, + then reset the other-querier-present timer. + */ + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "Querier %s resetting TIMER event for Other-Querier-Present", + ifaddr_str); + } + EVENT_OFF(igmp->t_other_querier_timer); + } else { + /* + We are the current querier, then stop sending general queries: + igmp->t_igmp_query_timer = NULL; + */ + pim_igmp_general_query_off(igmp); + } + + /* + Since this socket is starting the other-querier-present timer, + there should not be periodic query timer for this socket. + */ + assert(!igmp->t_igmp_query_timer); + + /* + RFC 3376: 8.5. Other Querier Present Interval + + The Other Querier Present Interval is the length of time that must + pass before a multicast router decides that there is no longer + another multicast router which should be the querier. This value + MUST be ((the Robustness Variable) times (the Query Interval)) plus + (one half of one Query Response Interval). + + other_querier_present_interval_msec = \ + igmp->querier_robustness_variable * \ + 1000 * igmp->querier_query_interval + \ + 100 * (pim_ifp->query_max_response_time_dsec >> 1); + */ + other_querier_present_interval_msec = PIM_IGMP_OQPI_MSEC( + igmp->querier_robustness_variable, igmp->querier_query_interval, + pim_ifp->gm_query_max_response_time_dsec); + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present", + ifaddr_str, other_querier_present_interval_msec / 1000, + other_querier_present_interval_msec % 1000); + } + + event_add_timer_msec(router->master, pim_igmp_other_querier_expire, + igmp, other_querier_present_interval_msec, + &igmp->t_other_querier_timer); +} + +void pim_igmp_other_querier_timer_off(struct gm_sock *igmp) +{ + assert(igmp); + + if (PIM_DEBUG_GM_TRACE) { + if (igmp->t_other_querier_timer) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s", + ifaddr_str, igmp->fd, igmp->interface->name); + } + } + EVENT_OFF(igmp->t_other_querier_timer); +} + +int igmp_validate_checksum(char *igmp_msg, int igmp_msg_len) +{ + uint16_t recv_checksum; + uint16_t checksum; + + IGMP_GET_INT16((unsigned char *)(igmp_msg + IGMP_CHECKSUM_OFFSET), + recv_checksum); + + /* Clear the checksum field */ + memset(igmp_msg + IGMP_CHECKSUM_OFFSET, 0, 2); + + checksum = in_cksum(igmp_msg, igmp_msg_len); + if (ntohs(checksum) != recv_checksum) { + zlog_warn("Invalid checksum received %x, calculated %x", + recv_checksum, ntohs(checksum)); + return -1; + } + + return 0; +} + +static int igmp_recv_query(struct gm_sock *igmp, int query_version, + int max_resp_code, struct in_addr from, + const char *from_str, char *igmp_msg, + int igmp_msg_len) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct in_addr group_addr; + + if (igmp->mtrace_only) + return 0; + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + ifp = igmp->interface; + pim_ifp = ifp->info; + + if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) { + zlog_warn( + "Recv IGMP query v%d from %s on %s with invalid checksum", + query_version, from_str, ifp->name); + return -1; + } + + if (!pim_if_connected_to_source(ifp, from)) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug("Recv IGMP query on interface: %s from a non-connected source: %s", + ifp->name, from_str); + return 0; + } + + if (if_address_is_local(&from, AF_INET, ifp->vrf->vrf_id)) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug("Recv IGMP query on interface: %s from ourself %s", + ifp->name, from_str); + return 0; + } + + /* Collecting IGMP Rx stats */ + switch (query_version) { + case 1: + igmp->igmp_stats.query_v1++; + break; + case 2: + igmp->igmp_stats.query_v2++; + break; + case 3: + igmp->igmp_stats.query_v3++; + break; + default: + igmp->igmp_stats.unsupported++; + } + + /* + * RFC 3376 defines some guidelines on operating in backwards + * compatibility with older versions of IGMP but there are some gaps in + * the logic: + * + * - once we drop from say version 3 to version 2 we will never go back + * to version 3 even if the node that TXed an IGMP v2 query upgrades + * to v3 + * + * - The node with the lowest IP is the querier so we will only know to + * drop from v3 to v2 if the node that is the querier is also the one + * that is running igmp v2. If a non-querier only supports igmp v2 + * we will have no way of knowing. + * + * For now we will simplify things and inform the user that they need to + * configure all PIM routers to use the same version of IGMP. + */ + if (query_version != pim_ifp->igmp_version) { + zlog_warn( + "Recv IGMP query v%d from %s on %s but we are using v%d, please configure all PIM routers on this subnet to use the same IGMP version", + query_version, from_str, ifp->name, + pim_ifp->igmp_version); + return 0; + } + + if (PIM_DEBUG_GM_PACKETS) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_debug("Recv IGMP query v%d from %s on %s for group %s", + query_version, from_str, ifp->name, group_str); + } + + /* + RFC 3376: 6.6.2. Querier Election + + When a router receives a query with a lower IP address, it sets + the Other-Querier-Present timer to Other Querier Present Interval + and ceases to send queries on the network if it was the previously + elected querier. + */ + if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) { + + /* As per RFC 2236 section 3: + * When a Querier receives a Leave Group message for a group + * that has group members on the reception interface, it sends + * [Last Member Query Count] Group-Specific Queries every [Last + * Member Query Interval] to the group being left. These + * Group-Specific Queries have their Max Response time set to + * [Last Member Query Interval]. If no Reports are received + * after the response time of the last query expires, the + * routers assume that the group has no local members, as above. + * Any Querier to non-Querier transition is ignored during this + * time; the same router keeps sending the Group-Specific + * Queries. + */ + const struct gm_group *group; + const struct listnode *grpnode; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + group)) { + if (!group->t_group_query_retransmit_timer) + continue; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s: lower address query packet from %s is ignored when last member query interval timer is running", + ifp->name, from_str); + return 0; + } + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "%s: local address %s (%u) lost querier election to %s (%u)", + ifp->name, ifaddr_str, + ntohl(igmp->ifaddr.s_addr), from_str, + ntohl(from.s_addr)); + } + /* Reset the other querier timer only if query is received from + * the previously elected querier or a better new querier + * This will make sure that non-querier elects the new querier + * whose ip address is higher than the old querier + * in case the old querier goes down via other querier present + * timer expiry + */ + if (ntohl(from.s_addr) <= ntohl(igmp->querier_addr.s_addr)) { + igmp->querier_addr.s_addr = from.s_addr; + pim_igmp_other_querier_timer_on(igmp); + } + } + + /* IGMP version 3 is the only one where we process the RXed query */ + if (query_version == 3) { + igmp_v3_recv_query(igmp, from_str, igmp_msg); + } + + return 0; +} + +static void on_trace(const char *label, struct interface *ifp, + struct in_addr from) +{ + if (PIM_DEBUG_GM_TRACE) { + char from_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", from, from_str, sizeof(from_str)); + zlog_debug("%s: from %s on %s", label, from_str, ifp->name); + } +} + +static int igmp_v1_recv_report(struct gm_sock *igmp, struct in_addr from, + const char *from_str, char *igmp_msg, + int igmp_msg_len) +{ + struct interface *ifp = igmp->interface; + struct gm_group *group; + struct in_addr group_addr; + + on_trace(__func__, igmp->interface, from); + + if (igmp->mtrace_only) + return 0; + + if (igmp_msg_len != IGMP_V12_MSG_SIZE) { + zlog_warn( + "Recv IGMP report v1 from %s on %s: size=%d other than correct=%d", + from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); + return -1; + } + + if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) { + zlog_warn( + "Recv IGMP report v1 from %s on %s with invalid checksum", + from_str, ifp->name); + return -1; + } + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.report_v1++; + + if (PIM_DEBUG_GM_TRACE) { + zlog_warn("%s %s: FIXME WRITEME", __FILE__, __func__); + } + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + if (pim_is_group_filtered(ifp->info, &group_addr)) + return -1; + + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return -1; + } + + group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec(); + + return 0; +} + +bool pim_igmp_verify_header(struct ip *ip_hdr, size_t len, size_t *hlen) +{ + char *igmp_msg; + int igmp_msg_len; + int msg_type; + size_t ip_hlen; /* ip header length in bytes */ + + if (len < sizeof(*ip_hdr)) { + zlog_warn("IGMP packet size=%zu shorter than minimum=%zu", len, + sizeof(*ip_hdr)); + return false; + } + + ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */ + *hlen = ip_hlen; + + if (ip_hlen > len) { + zlog_warn( + "IGMP packet header claims size %zu, but we only have %zu bytes", + ip_hlen, len); + return false; + } + + igmp_msg = (char *)ip_hdr + ip_hlen; + igmp_msg_len = len - ip_hlen; + msg_type = *igmp_msg; + + if (igmp_msg_len < PIM_IGMP_MIN_LEN) { + zlog_warn("IGMP message size=%d shorter than minimum=%d", + igmp_msg_len, PIM_IGMP_MIN_LEN); + return false; + } + + if ((msg_type != PIM_IGMP_MTRACE_RESPONSE) + && (msg_type != PIM_IGMP_MTRACE_QUERY_REQUEST)) { + if (ip_hdr->ip_ttl != 1) { + zlog_warn( + "Recv IGMP packet with invalid ttl=%u, discarding the packet", + ip_hdr->ip_ttl); + return false; + } + } + + return true; +} + +int pim_igmp_packet(struct gm_sock *igmp, char *buf, size_t len) +{ + struct ip *ip_hdr = (struct ip *)buf; + size_t ip_hlen; /* ip header length in bytes */ + char *igmp_msg; + int igmp_msg_len; + int msg_type; + char from_str[INET_ADDRSTRLEN]; + char to_str[INET_ADDRSTRLEN]; + + if (!pim_igmp_verify_header(ip_hdr, len, &ip_hlen)) + return -1; + + igmp_msg = buf + ip_hlen; + igmp_msg_len = len - ip_hlen; + msg_type = *igmp_msg; + + pim_inet4_dump("", ip_hdr->ip_src, from_str, sizeof(from_str)); + pim_inet4_dump("", ip_hdr->ip_dst, to_str, sizeof(to_str)); + + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Recv IGMP packet from %s to %s on %s: size=%zu ttl=%d msg_type=%d msg_size=%d", + from_str, to_str, igmp->interface->name, len, ip_hdr->ip_ttl, + msg_type, igmp_msg_len); + } + + switch (msg_type) { + case PIM_IGMP_MEMBERSHIP_QUERY: { + int max_resp_code = igmp_msg[1]; + int query_version; + + /* + RFC 3376: 7.1. Query Version Distinctions + IGMPv1 Query: length = 8 octets AND Max Resp Code field is + zero + IGMPv2 Query: length = 8 octets AND Max Resp Code field is + non-zero + IGMPv3 Query: length >= 12 octets + */ + + if (igmp_msg_len == 8) { + query_version = max_resp_code ? 2 : 1; + } else if (igmp_msg_len >= 12) { + query_version = 3; + } else { + zlog_warn("Unknown IGMP query version"); + return -1; + } + + return igmp_recv_query(igmp, query_version, max_resp_code, + ip_hdr->ip_src, from_str, igmp_msg, + igmp_msg_len); + } + + case PIM_IGMP_V3_MEMBERSHIP_REPORT: + return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); + + case PIM_IGMP_V2_MEMBERSHIP_REPORT: + return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); + + case PIM_IGMP_V1_MEMBERSHIP_REPORT: + return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); + + case PIM_IGMP_V2_LEAVE_GROUP: + return igmp_v2_recv_leave(igmp, ip_hdr, from_str, igmp_msg, + igmp_msg_len); + + case PIM_IGMP_MTRACE_RESPONSE: + return igmp_mtrace_recv_response(igmp, ip_hdr, ip_hdr->ip_src, + from_str, igmp_msg, + igmp_msg_len); + case PIM_IGMP_MTRACE_QUERY_REQUEST: + return igmp_mtrace_recv_qry_req(igmp, ip_hdr, ip_hdr->ip_src, + from_str, igmp_msg, + igmp_msg_len); + } + + zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type); + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.unsupported++; + + return -1; +} + +void pim_igmp_general_query_on(struct gm_sock *igmp) +{ + struct pim_interface *pim_ifp; + int startup_mode; + int query_interval; + + /* + Since this socket is starting as querier, + there should not exist a timer for other-querier-present. + */ + assert(!igmp->t_other_querier_timer); + pim_ifp = igmp->interface->info; + assert(pim_ifp); + + /* + RFC 3376: 8.6. Startup Query Interval + + The Startup Query Interval is the interval between General Queries + sent by a Querier on startup. Default: 1/4 the Query Interval. + The first one should be sent out immediately instead of 125/4 + seconds from now. + */ + startup_mode = igmp->startup_query_count > 0; + if (startup_mode) { + /* + * If this is the first time we are sending a query on a + * newly configured igmp interface send it out in 1 second + * just to give the entire world a tiny bit of time to settle + * else the query interval is: + * query_interval = pim_ifp->gm_default_query_interval >> 2; + */ + if (igmp->startup_query_count == + igmp->querier_robustness_variable) + query_interval = 1; + else + query_interval = PIM_IGMP_SQI( + pim_ifp->gm_default_query_interval); + + --igmp->startup_query_count; + } else { + query_interval = igmp->querier_query_interval; + } + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d", + ifaddr_str, query_interval, + startup_mode ? "startup" : "non-startup", igmp->fd); + } + event_add_timer(router->master, pim_igmp_general_query, igmp, + query_interval, &igmp->t_igmp_query_timer); +} + +void pim_igmp_general_query_off(struct gm_sock *igmp) +{ + assert(igmp); + + if (PIM_DEBUG_GM_TRACE) { + if (igmp->t_igmp_query_timer) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "IGMP querier %s fd=%d cancelling query TIMER event on %s", + ifaddr_str, igmp->fd, igmp->interface->name); + } + } + EVENT_OFF(igmp->t_igmp_query_timer); +} + +/* Issue IGMP general query */ +static void pim_igmp_general_query(struct event *t) +{ + struct gm_sock *igmp; + struct in_addr dst_addr; + struct in_addr group_addr; + struct pim_interface *pim_ifp; + int query_buf_size; + + igmp = EVENT_ARG(t); + + assert(igmp->interface); + assert(igmp->interface->info); + + pim_ifp = igmp->interface->info; + + if (pim_ifp->igmp_version == 3) { + query_buf_size = PIM_IGMP_BUFSIZE_WRITE; + } else { + query_buf_size = IGMP_V12_MSG_SIZE; + } + + char query_buf[query_buf_size]; + + /* + RFC3376: 4.1.12. IP Destination Addresses for Queries + + In IGMPv3, General Queries are sent with an IP destination address + of 224.0.0.1, the all-systems multicast address. Group-Specific + and Group-and-Source-Specific Queries are sent with an IP + destination address equal to the multicast address of interest. + */ + + dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); + group_addr.s_addr = PIM_NET_INADDR_ANY; + + if (PIM_DEBUG_GM_TRACE) { + char querier_str[INET_ADDRSTRLEN]; + char dst_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, querier_str, + sizeof(querier_str)); + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + zlog_debug("Querier %s issuing IGMP general query to %s on %s", + querier_str, dst_str, igmp->interface->name); + } + + igmp_send_query(pim_ifp->igmp_version, 0 /* igmp_group */, query_buf, + sizeof(query_buf), 0 /* num_sources */, dst_addr, + group_addr, pim_ifp->gm_query_max_response_time_dsec, + 1 /* s_flag: always set for general queries */, igmp); + + pim_igmp_general_query_on(igmp); +} + +static void sock_close(struct gm_sock *igmp) +{ + pim_igmp_other_querier_timer_off(igmp); + pim_igmp_general_query_off(igmp); + + if (PIM_DEBUG_GM_TRACE_DETAIL) { + if (igmp->t_igmp_read) { + zlog_debug( + "Cancelling READ event on IGMP socket %pI4 fd=%d on interface %s", + &igmp->ifaddr, igmp->fd, + igmp->interface->name); + } + } + EVENT_OFF(igmp->t_igmp_read); + + if (close(igmp->fd)) { + flog_err( + EC_LIB_SOCKET, + "Failure closing IGMP socket %pI4 fd=%d on interface %s: errno=%d: %s", + &igmp->ifaddr, igmp->fd, + igmp->interface->name, errno, safe_strerror(errno)); + } + + if (PIM_DEBUG_GM_TRACE_DETAIL) { + zlog_debug("Deleted IGMP socket %pI4 fd=%d on interface %s", + &igmp->ifaddr, igmp->fd, + igmp->interface->name); + } +} + +void igmp_startup_mode_on(struct gm_sock *igmp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = igmp->interface->info; + + /* + RFC 3376: 8.7. Startup Query Count + + The Startup Query Count is the number of Queries sent out on + startup, separated by the Startup Query Interval. Default: the + Robustness Variable. + */ + igmp->startup_query_count = igmp->querier_robustness_variable; + + /* + Since we're (re)starting, reset QQI to default Query Interval + */ + igmp->querier_query_interval = pim_ifp->gm_default_query_interval; +} + +static void igmp_group_free(struct gm_group *group) +{ + list_delete(&group->group_source_list); + + XFREE(MTYPE_PIM_IGMP_GROUP, group); +} + +static void igmp_group_count_incr(struct pim_interface *pim_ifp) +{ + uint32_t group_count = listcount(pim_ifp->gm_group_list); + + ++pim_ifp->pim->gm_group_count; + if (pim_ifp->pim->gm_group_count == pim_ifp->pim->gm_watermark_limit) { + zlog_warn( + "IGMP group count reached watermark limit: %u(vrf: %s)", + pim_ifp->pim->gm_group_count, + VRF_LOGNAME(pim_ifp->pim->vrf)); + } + + if (pim_ifp->igmp_peak_group_count < group_count) + pim_ifp->igmp_peak_group_count = group_count; +} + +static void igmp_group_count_decr(struct pim_interface *pim_ifp) +{ + if (pim_ifp->pim->gm_group_count == 0) { + zlog_warn("Cannot decrement igmp group count below 0(vrf: %s)", + VRF_LOGNAME(pim_ifp->pim->vrf)); + return; + } + + --pim_ifp->pim->gm_group_count; +} + +void igmp_group_delete(struct gm_group *group) +{ + struct listnode *src_node; + struct listnode *src_nextnode; + struct gm_source *src; + struct pim_interface *pim_ifp = group->interface->info; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug("Deleting IGMP group %s from interface %s", + group_str, group->interface->name); + } + + for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, + src)) { + igmp_source_delete(src); + } + + EVENT_OFF(group->t_group_query_retransmit_timer); + + group_timer_off(group); + igmp_group_count_decr(pim_ifp); + listnode_delete(pim_ifp->gm_group_list, group); + hash_release(pim_ifp->gm_group_hash, group); + + igmp_group_free(group); +} + +void igmp_group_delete_empty_include(struct gm_group *group) +{ + assert(!group->group_filtermode_isexcl); + assert(!listcount(group->group_source_list)); + + igmp_group_delete(group); +} + +void igmp_sock_free(struct gm_sock *igmp) +{ + assert(!igmp->t_igmp_read); + assert(!igmp->t_igmp_query_timer); + assert(!igmp->t_other_querier_timer); + + XFREE(MTYPE_PIM_IGMP_SOCKET, igmp); +} + +void igmp_sock_delete(struct gm_sock *igmp) +{ + struct pim_interface *pim_ifp; + + sock_close(igmp); + + pim_ifp = igmp->interface->info; + + listnode_delete(pim_ifp->gm_socket_list, igmp); + + igmp_sock_free(igmp); + + if (!listcount(pim_ifp->gm_socket_list)) + pim_igmp_if_reset(pim_ifp); +} + +void igmp_sock_delete_all(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct listnode *igmp_node, *igmp_nextnode; + struct gm_sock *igmp; + + pim_ifp = ifp->info; + + for (ALL_LIST_ELEMENTS(pim_ifp->gm_socket_list, igmp_node, + igmp_nextnode, igmp)) { + igmp_sock_delete(igmp); + } +} + +static unsigned int igmp_group_hash_key(const void *arg) +{ + const struct gm_group *group = arg; + + return jhash_1word(group->group_addr.s_addr, 0); +} + +static bool igmp_group_hash_equal(const void *arg1, const void *arg2) +{ + const struct gm_group *g1 = (const struct gm_group *)arg1; + const struct gm_group *g2 = (const struct gm_group *)arg2; + + if (g1->group_addr.s_addr == g2->group_addr.s_addr) + return true; + + return false; +} + +void pim_igmp_if_init(struct pim_interface *pim_ifp, struct interface *ifp) +{ + char hash_name[64]; + + pim_ifp->gm_socket_list = list_new(); + pim_ifp->gm_socket_list->del = (void (*)(void *))igmp_sock_free; + + pim_ifp->gm_group_list = list_new(); + pim_ifp->gm_group_list->del = (void (*)(void *))igmp_group_free; + + snprintf(hash_name, sizeof(hash_name), "IGMP %s hash", ifp->name); + pim_ifp->gm_group_hash = hash_create(igmp_group_hash_key, + igmp_group_hash_equal, hash_name); +} + +void pim_igmp_if_reset(struct pim_interface *pim_ifp) +{ + struct listnode *grp_node, *grp_nextnode; + struct gm_group *grp; + + for (ALL_LIST_ELEMENTS(pim_ifp->gm_group_list, grp_node, grp_nextnode, + grp)) { + igmp_group_delete(grp); + } +} + +void pim_igmp_if_fini(struct pim_interface *pim_ifp) +{ + pim_igmp_if_reset(pim_ifp); + + assert(pim_ifp->gm_group_list); + assert(!listcount(pim_ifp->gm_group_list)); + + list_delete(&pim_ifp->gm_group_list); + hash_free(pim_ifp->gm_group_hash); + + list_delete(&pim_ifp->gm_socket_list); +} + +static struct gm_sock *igmp_sock_new(int fd, struct in_addr ifaddr, + struct interface *ifp, int mtrace_only) +{ + struct pim_interface *pim_ifp; + struct gm_sock *igmp; + + pim_ifp = ifp->info; + + if (PIM_DEBUG_GM_TRACE) { + zlog_debug( + "Creating IGMP socket fd=%d for address %pI4 on interface %s", + fd, &ifaddr, ifp->name); + } + + igmp = XCALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp)); + + igmp->fd = fd; + igmp->interface = ifp; + igmp->ifaddr = ifaddr; + igmp->querier_addr = ifaddr; + igmp->t_igmp_read = NULL; + igmp->t_igmp_query_timer = NULL; + igmp->t_other_querier_timer = NULL; /* no other querier present */ + igmp->querier_robustness_variable = + pim_ifp->gm_default_robustness_variable; + igmp->sock_creation = pim_time_monotonic_sec(); + + igmp_stats_init(&igmp->igmp_stats); + + if (mtrace_only) { + igmp->mtrace_only = mtrace_only; + return igmp; + } + + igmp->mtrace_only = false; + + /* + igmp_startup_mode_on() will reset QQI: + + igmp->querier_query_interval = pim_ifp->gm_default_query_interval; + */ + igmp_startup_mode_on(igmp); + pim_igmp_general_query_on(igmp); + + return igmp; +} + +static void igmp_read_on(struct gm_sock *igmp); + +static void pim_igmp_read(struct event *t) +{ + uint8_t buf[10000]; + struct gm_sock *igmp = (struct gm_sock *)EVENT_ARG(t); + struct sockaddr_storage from; + struct sockaddr_storage to; + socklen_t fromlen = sizeof(from); + socklen_t tolen = sizeof(to); + ifindex_t ifindex = -1; + int len; + + while (1) { + len = pim_socket_recvfromto(igmp->fd, buf, sizeof(buf), &from, + &fromlen, &to, &tolen, &ifindex); + if (len < 0) { + if (errno == EINTR) + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + + goto done; + } + } + +done: + igmp_read_on(igmp); +} + +static void igmp_read_on(struct gm_sock *igmp) +{ + + if (PIM_DEBUG_GM_TRACE_DETAIL) { + zlog_debug("Scheduling READ event on IGMP socket fd=%d", + igmp->fd); + } + event_add_read(router->master, pim_igmp_read, igmp, igmp->fd, + &igmp->t_igmp_read); +} + +struct gm_sock *pim_igmp_sock_add(struct list *igmp_sock_list, + struct in_addr ifaddr, struct interface *ifp, + bool mtrace_only) +{ + struct gm_sock *igmp; + struct sockaddr_in sin; + int fd; + + fd = igmp_sock_open(ifaddr, ifp); + if (fd < 0) { + zlog_warn("Could not open IGMP socket for %pI4 on %s", + &ifaddr, ifp->name); + return NULL; + } + + sin.sin_family = AF_INET; + sin.sin_addr = ifaddr; + sin.sin_port = 0; + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin)) != 0) { + zlog_warn("Could not bind IGMP socket for %pI4 on %s: %s(%d)", + &ifaddr, ifp->name, strerror(errno), errno); + close(fd); + + return NULL; + } + + igmp = igmp_sock_new(fd, ifaddr, ifp, mtrace_only); + + igmp_read_on(igmp); + + listnode_add(igmp_sock_list, igmp); + +#ifdef IGMP_SOCK_DUMP + igmp_sock_dump(igmp_sock_array); +#endif + + return igmp; +} + +/* + RFC 3376: 6.5. Switching Router Filter-Modes + + When a router's filter-mode for a group is EXCLUDE and the group + timer expires, the router filter-mode for the group transitions to + INCLUDE. + + A router uses source records with running source timers as its state + for the switch to a filter-mode of INCLUDE. If there are any source + records with source timers greater than zero (i.e., requested to be + forwarded), a router switches to filter-mode of INCLUDE using those + source records. Source records whose timers are zero (from the + previous EXCLUDE mode) are deleted. + */ +static void igmp_group_timer(struct event *t) +{ + struct gm_group *group; + + group = EVENT_ARG(t); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug("%s: Timer for group %s on interface %s", __func__, + group_str, group->interface->name); + } + + assert(group->group_filtermode_isexcl); + + group->group_filtermode_isexcl = 0; + + /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ + igmp_anysource_forward_stop(group); + + igmp_source_delete_expired(group->group_source_list); + + assert(!group->group_filtermode_isexcl); + + /* + RFC 3376: 6.2.2. Definition of Group Timers + + If there are no more source records for the group, delete group + record. + */ + if (listcount(group->group_source_list) < 1) { + igmp_group_delete_empty_include(group); + } +} + +static void group_timer_off(struct gm_group *group) +{ + if (!group->t_group_timer) + return; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug("Cancelling TIMER event for group %s on %s", + group_str, group->interface->name); + } + EVENT_OFF(group->t_group_timer); +} + +void igmp_group_timer_on(struct gm_group *group, long interval_msec, + const char *ifname) +{ + group_timer_off(group); + + if (PIM_DEBUG_GM_EVENTS) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "Scheduling %ld.%03ld sec TIMER event for group %s on %s", + interval_msec / 1000, interval_msec % 1000, group_str, + ifname); + } + + /* + RFC 3376: 6.2.2. Definition of Group Timers + + The group timer is only used when a group is in EXCLUDE mode and + it represents the time for the *filter-mode* of the group to + expire and switch to INCLUDE mode. + */ + assert(group->group_filtermode_isexcl); + + event_add_timer_msec(router->master, igmp_group_timer, group, + interval_msec, &group->t_group_timer); +} + +struct gm_group *find_group_by_addr(struct gm_sock *igmp, + struct in_addr group_addr) +{ + struct gm_group lookup; + struct pim_interface *pim_ifp = igmp->interface->info; + + lookup.group_addr.s_addr = group_addr.s_addr; + + return hash_lookup(pim_ifp->gm_group_hash, &lookup); +} + +struct gm_group *igmp_add_group_by_addr(struct gm_sock *igmp, + struct in_addr group_addr) +{ + struct gm_group *group; + struct pim_interface *pim_ifp = igmp->interface->info; + + group = find_group_by_addr(igmp, group_addr); + if (group) { + return group; + } + + if (!pim_is_group_224_4(group_addr)) { + zlog_warn("%s: Group Specified is not part of 224.0.0.0/4", + __func__); + return NULL; + } + + if (pim_is_group_224_0_0_0_24(group_addr)) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s: Group specified %pI4 is part of 224.0.0.0/24", + __func__, &group_addr); + return NULL; + } + /* + Non-existant group is created as INCLUDE {empty}: + + RFC 3376 - 5.1. Action on Change of Interface State + + If no interface state existed for that multicast address before + the change (i.e., the change consisted of creating a new + per-interface record), or if no state exists after the change + (i.e., the change consisted of deleting a per-interface record), + then the "non-existent" state is considered to have a filter mode + of INCLUDE and an empty source list. + */ + + group = XCALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group)); + + group->group_source_list = list_new(); + group->group_source_list->del = (void (*)(void *))igmp_source_free; + + group->t_group_timer = NULL; + group->t_group_query_retransmit_timer = NULL; + group->group_specific_query_retransmit_count = 0; + group->group_addr = group_addr; + group->interface = igmp->interface; + group->last_igmp_v1_report_dsec = -1; + group->last_igmp_v2_report_dsec = -1; + group->group_creation = pim_time_monotonic_sec(); + group->igmp_version = IGMP_DEFAULT_VERSION; + + /* initialize new group as INCLUDE {empty} */ + group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */ + + listnode_add(pim_ifp->gm_group_list, group); + group = hash_get(pim_ifp->gm_group_hash, group, hash_alloc_intern); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "Creating new IGMP group %s on socket %d interface %s", + group_str, igmp->fd, igmp->interface->name); + } + + igmp_group_count_incr(pim_ifp); + + /* + RFC 3376: 6.2.2. Definition of Group Timers + + The group timer is only used when a group is in EXCLUDE mode and + it represents the time for the *filter-mode* of the group to + expire and switch to INCLUDE mode. + */ + assert(!group->group_filtermode_isexcl); /* INCLUDE mode */ + assert(!group->t_group_timer); /* group timer == 0 */ + + /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ + igmp_anysource_forward_stop(group); + + return group; +} + +void igmp_send_query(int igmp_version, struct gm_group *group, char *query_buf, + int query_buf_size, int num_sources, + struct in_addr dst_addr, struct in_addr group_addr, + int query_max_response_time_dsec, uint8_t s_flag, + struct gm_sock *igmp) +{ + if (pim_addr_is_any(group_addr) && + ntohl(dst_addr.s_addr) == INADDR_ALLHOSTS_GROUP) + igmp->igmp_stats.general_queries_sent++; + else if (group) + igmp->igmp_stats.group_queries_sent++; + + if (igmp_version == 3) { + igmp_v3_send_query(group, igmp->fd, igmp->interface->name, + query_buf, query_buf_size, num_sources, + dst_addr, group_addr, + query_max_response_time_dsec, s_flag, + igmp->querier_robustness_variable, + igmp->querier_query_interval); + } else if (igmp_version == 2) { + igmp_v2_send_query(group, igmp->fd, igmp->interface->name, + query_buf, dst_addr, group_addr, + query_max_response_time_dsec); + } +} + +void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver) +{ + struct pim_interface *pim_ifp = ifp->info; + struct listnode *sock_node = NULL; + struct gm_sock *igmp = NULL; + struct in_addr dst_addr; + struct in_addr group_addr; + int query_buf_size; + + if (!igmp_ver) + igmp_ver = 2; + + if (igmp_ver == 3) + query_buf_size = PIM_IGMP_BUFSIZE_WRITE; + else + query_buf_size = IGMP_V12_MSG_SIZE; + + dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); + group_addr.s_addr = PIM_NET_INADDR_ANY; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug("Issuing general query on request on %s", ifp->name); + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) { + + char query_buf[query_buf_size]; + + igmp_send_query( + igmp_ver, 0 /* igmp_group */, query_buf, + sizeof(query_buf), 0 /* num_sources */, dst_addr, + group_addr, pim_ifp->gm_query_max_response_time_dsec, + 1 /* s_flag: always set for general queries */, igmp); + } +} diff --git a/pimd/pim_igmp.h b/pimd/pim_igmp.h new file mode 100644 index 0000000..a1f19b3 --- /dev/null +++ b/pimd/pim_igmp.h @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_IGMP_H +#define PIM_IGMP_H + +#include + +#include +#include "vty.h" +#include "linklist.h" +#include "pim_igmp_stats.h" +#include "pim_str.h" + +/* + The following sizes are likely to support + any message sent within local MTU. +*/ +#define PIM_IGMP_BUFSIZE_READ (20000) +#define PIM_IGMP_BUFSIZE_WRITE (20000) + +#define PIM_IGMP_MEMBERSHIP_QUERY (0x11) +#define PIM_IGMP_V1_MEMBERSHIP_REPORT (0x12) +#define PIM_IGMP_V2_MEMBERSHIP_REPORT (0x16) +#define PIM_IGMP_V2_LEAVE_GROUP (0x17) +#define PIM_IGMP_MTRACE_RESPONSE (0x1E) +#define PIM_IGMP_MTRACE_QUERY_REQUEST (0x1F) +#define PIM_IGMP_V3_MEMBERSHIP_REPORT (0x22) + +#define IGMP_V3_REPORT_HEADER_SIZE (8) +#define IGMP_V3_GROUP_RECORD_MIN_SIZE (8) +#define IGMP_V3_MSG_MIN_SIZE \ + (IGMP_V3_REPORT_HEADER_SIZE + IGMP_V3_GROUP_RECORD_MIN_SIZE) +#define IGMP_V12_MSG_SIZE (8) + +#define IGMP_V3_GROUP_RECORD_TYPE_OFFSET (0) +#define IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET (1) +#define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2) +#define IGMP_V3_GROUP_RECORD_GROUP_OFFSET (4) +#define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET (8) +#define IGMP_CHECKSUM_OFFSET (2) + +#define IGMP_DEFAULT_VERSION (3) + +#define IGMP_GET_INT16(ptr, output) \ + do { \ + output = *(ptr) << 8; \ + output |= *((ptr) + 1); \ + } while (0) + +struct gm_join { + pim_addr group_addr; + pim_addr source_addr; + int sock_fd; + time_t sock_creation; +}; + +struct gm_sock { + int fd; + struct interface *interface; + pim_addr ifaddr; + time_t sock_creation; + + struct event *t_igmp_read; /* read: IGMP sockets */ + /* timer: issue IGMP general queries */ + struct event *t_igmp_query_timer; + struct event *t_other_querier_timer; /* timer: other querier present */ + pim_addr querier_addr; /* IP address of the querier */ + int querier_query_interval; /* QQI */ + int querier_robustness_variable; /* QRV */ + int startup_query_count; + + bool mtrace_only; + + struct igmp_stats igmp_stats; +}; + +struct pim_interface; + +#if PIM_IPV == 4 +void pim_igmp_if_init(struct pim_interface *pim_ifp, struct interface *ifp); +void pim_igmp_if_reset(struct pim_interface *pim_ifp); +void pim_igmp_if_fini(struct pim_interface *pim_ifp); + +struct gm_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list, + struct in_addr ifaddr); +struct gm_sock *pim_igmp_sock_add(struct list *igmp_sock_list, + struct in_addr ifaddr, struct interface *ifp, + bool mtrace_only); +void igmp_sock_delete(struct gm_sock *igmp); +void igmp_sock_free(struct gm_sock *igmp); +void igmp_sock_delete_all(struct interface *ifp); +int pim_igmp_packet(struct gm_sock *igmp, char *buf, size_t len); +bool pim_igmp_verify_header(struct ip *ip_hdr, size_t len, size_t *ip_hlen); +void pim_igmp_general_query_on(struct gm_sock *igmp); +void pim_igmp_general_query_off(struct gm_sock *igmp); +void pim_igmp_other_querier_timer_on(struct gm_sock *igmp); +void pim_igmp_other_querier_timer_off(struct gm_sock *igmp); + +int igmp_validate_checksum(char *igmp_msg, int igmp_msg_len); + +#else /* PIM_IPV != 4 */ +static inline void pim_igmp_if_init(struct pim_interface *pim_ifp, + struct interface *ifp) +{ +} + +static inline void pim_igmp_if_fini(struct pim_interface *pim_ifp) +{ +} + +static inline void pim_igmp_general_query_on(struct gm_sock *igmp) +{ +} + +static inline void pim_igmp_general_query_off(struct gm_sock *igmp) +{ +} + +static inline void pim_igmp_other_querier_timer_on(struct gm_sock *igmp) +{ +} + +static inline void pim_igmp_other_querier_timer_off(struct gm_sock *igmp) +{ +} +#endif /* PIM_IPV == 4 */ + +#define IGMP_SOURCE_MASK_FORWARDING (1 << 0) +#define IGMP_SOURCE_MASK_DELETE (1 << 1) +#define IGMP_SOURCE_MASK_SEND (1 << 2) +#define IGMP_SOURCE_TEST_FORWARDING(flags) ((flags) & IGMP_SOURCE_MASK_FORWARDING) +#define IGMP_SOURCE_TEST_DELETE(flags) ((flags) & IGMP_SOURCE_MASK_DELETE) +#define IGMP_SOURCE_TEST_SEND(flags) ((flags) & IGMP_SOURCE_MASK_SEND) +#define IGMP_SOURCE_DO_FORWARDING(flags) ((flags) |= IGMP_SOURCE_MASK_FORWARDING) +#define IGMP_SOURCE_DO_DELETE(flags) ((flags) |= IGMP_SOURCE_MASK_DELETE) +#define IGMP_SOURCE_DO_SEND(flags) ((flags) |= IGMP_SOURCE_MASK_SEND) +#define IGMP_SOURCE_DONT_FORWARDING(flags) ((flags) &= ~IGMP_SOURCE_MASK_FORWARDING) +#define IGMP_SOURCE_DONT_DELETE(flags) ((flags) &= ~IGMP_SOURCE_MASK_DELETE) +#define IGMP_SOURCE_DONT_SEND(flags) ((flags) &= ~IGMP_SOURCE_MASK_SEND) + +struct gm_source { + pim_addr source_addr; + struct event *t_source_timer; + struct gm_group *source_group; /* back pointer */ + time_t source_creation; + uint32_t source_flags; + struct channel_oil *source_channel_oil; + + /* + RFC 3376: 6.6.3.2. Building and Sending Group and Source Specific + Queries + */ + int source_query_retransmit_count; +}; + +struct gm_group { + /* + RFC 3376: 6.2.2. Definition of Group Timers + + The group timer is only used when a group is in EXCLUDE mode and it + represents the time for the *filter-mode* of the group to expire and + switch to INCLUDE mode. + */ + struct event *t_group_timer; + + /* Shared between group-specific and + group-and-source-specific retransmissions */ + struct event *t_group_query_retransmit_timer; + + /* Counter exclusive for group-specific retransmissions + (not used by group-and-source-specific retransmissions, + since sources have their counters) */ + int group_specific_query_retransmit_count; + + /* compatibility mode - igmp v1, v2 or v3 */ + int igmp_version; + pim_addr group_addr; + int group_filtermode_isexcl; /* 0=INCLUDE, 1=EXCLUDE */ + struct list *group_source_list; /* list of struct gm_source */ + time_t group_creation; + struct interface *interface; + int64_t last_igmp_v1_report_dsec; + int64_t last_igmp_v2_report_dsec; +}; + +#if PIM_IPV == 4 +struct pim_instance; + +void igmp_anysource_forward_start(struct pim_instance *pim, + struct gm_group *group); +void igmp_anysource_forward_stop(struct gm_group *group); + +void igmp_source_forward_start(struct pim_instance *pim, + struct gm_source *source); +void igmp_source_forward_stop(struct gm_source *source); +void igmp_source_forward_reevaluate_all(struct pim_instance *pim); + +struct gm_group *find_group_by_addr(struct gm_sock *igmp, + struct in_addr group_addr); +struct gm_group *igmp_add_group_by_addr(struct gm_sock *igmp, + struct in_addr group_addr); + +struct gm_source *igmp_get_source_by_addr(struct gm_group *group, + struct in_addr src_addr, + bool *created); + +void igmp_group_delete_empty_include(struct gm_group *group); + +void igmp_startup_mode_on(struct gm_sock *igmp); + +void igmp_group_timer_on(struct gm_group *group, long interval_msec, + const char *ifname); + +void igmp_send_query(int igmp_version, struct gm_group *group, char *query_buf, + int query_buf_size, int num_sources, + struct in_addr dst_addr, struct in_addr group_addr, + int query_max_response_time_dsec, uint8_t s_flag, + struct gm_sock *igmp); +void igmp_group_delete(struct gm_group *group); + +void igmp_send_query_on_intf(struct interface *ifp, int igmp_ver); + +#else /* PIM_IPV != 4 */ +static inline void igmp_startup_mode_on(struct gm_sock *igmp) +{ +} +#endif /* PIM_IPV != 4 */ + +#endif /* PIM_IGMP_H */ diff --git a/pimd/pim_igmp_join.h b/pimd/pim_igmp_join.h new file mode 100644 index 0000000..0e9498c --- /dev/null +++ b/pimd/pim_igmp_join.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_IGMP_JOIN_H +#define PIM_IGMP_JOIN_H + +#include "pim_addr.h" + +/* required headers #include'd by caller */ + +#ifndef SOL_IP +#define SOL_IP IPPROTO_IP +#endif + +#ifndef MCAST_JOIN_GROUP +#define MCAST_JOIN_GROUP 42 +#endif + +#ifndef MCAST_JOIN_SOURCE_GROUP +#define MCAST_JOIN_SOURCE_GROUP 46 +struct group_source_req { + uint32_t gsr_interface; + struct sockaddr_storage gsr_group; + struct sockaddr_storage gsr_source; +}; +#endif + +#if PIM_IPV == 4 +static inline int pim_gm_join_source(int fd, ifindex_t ifindex, + pim_addr group_addr, pim_addr source_addr) +{ + struct group_source_req req; + struct sockaddr_in group = {}; + struct sockaddr_in source = {}; + + memset(&req, 0, sizeof(req)); + + group.sin_family = PIM_AF; + group.sin_addr = group_addr; + group.sin_port = htons(0); + memcpy(&req.gsr_group, &group, sizeof(group)); + + source.sin_family = PIM_AF; + source.sin_addr = source_addr; + source.sin_port = htons(0); + memcpy(&req.gsr_source, &source, sizeof(source)); + + req.gsr_interface = ifindex; + + if (pim_addr_is_any(source_addr)) + return setsockopt(fd, SOL_IP, MCAST_JOIN_GROUP, &req, + sizeof(req)); + else + return setsockopt(fd, SOL_IP, MCAST_JOIN_SOURCE_GROUP, &req, + sizeof(req)); +} +#else /* PIM_IPV != 4*/ +static inline int pim_gm_join_source(int fd, ifindex_t ifindex, + pim_addr group_addr, pim_addr source_addr) +{ + struct group_source_req req; + struct sockaddr_in6 group = {}; + struct sockaddr_in6 source = {}; + + memset(&req, 0, sizeof(req)); + + group.sin6_family = PIM_AF; + group.sin6_addr = group_addr; + group.sin6_port = htons(0); + memcpy(&req.gsr_group, &group, sizeof(group)); + + source.sin6_family = PIM_AF; + source.sin6_addr = source_addr; + source.sin6_port = htons(0); + memcpy(&req.gsr_source, &source, sizeof(source)); + + req.gsr_interface = ifindex; + + if (pim_addr_is_any(source_addr)) + return setsockopt(fd, SOL_IPV6, MCAST_JOIN_GROUP, &req, + sizeof(req)); + else + return setsockopt(fd, SOL_IPV6, MCAST_JOIN_SOURCE_GROUP, &req, + sizeof(req)); +} +#endif /* PIM_IPV != 4*/ + +#endif /* PIM_IGMP_JOIN_H */ diff --git a/pimd/pim_igmp_mtrace.c b/pimd/pim_igmp_mtrace.c new file mode 100644 index 0000000..309da13 --- /dev/null +++ b/pimd/pim_igmp_mtrace.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multicast traceroute for FRRouting + * Copyright (C) 2017 Mladen Sablic + */ + +/* based on draft-ietf-idmr-traceroute-ipm-07 */ + +#include + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_util.h" +#include "pim_sock.h" +#include "pim_rp.h" +#include "pim_oil.h" +#include "pim_ifchannel.h" +#include "pim_macro.h" +#include "pim_igmp_mtrace.h" + +static struct in_addr mtrace_primary_address(struct interface *ifp) +{ + struct connected *ifc; + struct in_addr any; + struct pim_interface *pim_ifp; + + if (ifp->info) { + pim_ifp = ifp->info; + return pim_ifp->primary_address; + } + + any.s_addr = INADDR_ANY; + + frr_each (if_connected, ifp->connected, ifc) { + struct prefix *p = ifc->address; + + if (p->family != AF_INET) + continue; + + if (!CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) + return p->u.prefix4; + /* in case no primary found, return a secondary */ + any = p->u.prefix4; + } + return any; +} + +static bool mtrace_fwd_info_weak(struct pim_instance *pim, + struct igmp_mtrace *mtracep, + struct igmp_mtrace_rsp *rspp, + struct interface **ifpp) +{ + struct pim_nexthop nexthop; + struct interface *ifp_in; + struct in_addr nh_addr; + + nh_addr.s_addr = INADDR_ANY; + + memset(&nexthop, 0, sizeof(nexthop)); + + if (!pim_nexthop_lookup(pim, &nexthop, mtracep->src_addr, 1)) { + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace not found neighbor"); + return false; + } + + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace pim_nexthop_lookup OK"); + + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace next_hop=%pPAs", &nexthop.mrib_nexthop_addr); + + nh_addr = nexthop.mrib_nexthop_addr; + + ifp_in = nexthop.interface; + + /* return interface for forwarding mtrace packets */ + *ifpp = ifp_in; + + /* 6.2.2. 4. Fill in the Incoming Interface Address... */ + rspp->incoming = mtrace_primary_address(ifp_in); + rspp->prev_hop = nh_addr; + rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); + rspp->total = htonl(MTRACE_UNKNOWN_COUNT); + rspp->rtg_proto = MTRACE_RTG_PROTO_PIM; + return true; +} + +static bool mtrace_fwd_info(struct pim_instance *pim, + struct igmp_mtrace *mtracep, + struct igmp_mtrace_rsp *rspp, + struct interface **ifpp) +{ + pim_sgaddr sg; + struct pim_upstream *up; + struct interface *ifp_in; + struct in_addr nh_addr; + uint32_t total; + + memset(&sg, 0, sizeof(sg)); + sg.src = mtracep->src_addr; + sg.grp = mtracep->grp_addr; + + up = pim_upstream_find(pim, &sg); + + if (!up) { + sg.src = PIMADDR_ANY; + up = pim_upstream_find(pim, &sg); + } + + if (!up) + return false; + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return false; + } + + ifp_in = up->rpf.source_nexthop.interface; + nh_addr = up->rpf.source_nexthop.mrib_nexthop_addr; + total = htonl(MTRACE_UNKNOWN_COUNT); + + if (PIM_DEBUG_MTRACE) + zlog_debug("fwd_info: upstream next hop=%pI4", &nh_addr); + + if (up->channel_oil) + total = up->channel_oil->cc.pktcnt; + + /* return interface for forwarding mtrace packets */ + *ifpp = ifp_in; + + /* 6.2.2. 4. Fill in the Incoming Interface Address... */ + rspp->incoming = mtrace_primary_address(ifp_in); + rspp->prev_hop = nh_addr; + rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); + rspp->total = total; + rspp->rtg_proto = MTRACE_RTG_PROTO_PIM; + + /* 6.2.2. 4. Fill in ... S, and Src Mask */ + if (!pim_addr_is_any(sg.src)) { + rspp->s = 1; + rspp->src_mask = MTRACE_SRC_MASK_SOURCE; + } else { + rspp->s = 0; + rspp->src_mask = MTRACE_SRC_MASK_GROUP; + } + + return true; +} + +static void mtrace_rsp_set_fwd_code(struct igmp_mtrace_rsp *mtrace_rspp, + enum mtrace_fwd_code fwd_code) +{ + if (mtrace_rspp->fwd_code == MTRACE_FWD_CODE_NO_ERROR) + mtrace_rspp->fwd_code = fwd_code; +} + +static void mtrace_rsp_init(struct igmp_mtrace_rsp *mtrace_rspp) +{ + mtrace_rspp->arrival = 0; + mtrace_rspp->incoming.s_addr = INADDR_ANY; + mtrace_rspp->outgoing.s_addr = INADDR_ANY; + mtrace_rspp->prev_hop.s_addr = INADDR_ANY; + mtrace_rspp->in_count = htonl(MTRACE_UNKNOWN_COUNT); + mtrace_rspp->out_count = htonl(MTRACE_UNKNOWN_COUNT); + mtrace_rspp->total = htonl(MTRACE_UNKNOWN_COUNT); + mtrace_rspp->rtg_proto = 0; + mtrace_rspp->fwd_ttl = 0; + mtrace_rspp->mbz = 0; + mtrace_rspp->s = 0; + mtrace_rspp->src_mask = 0; + mtrace_rspp->fwd_code = MTRACE_FWD_CODE_NO_ERROR; +} + +static void mtrace_rsp_debug(uint32_t qry_id, int rsp, + struct igmp_mtrace_rsp *mrspp) +{ + struct in_addr incoming = mrspp->incoming; + struct in_addr outgoing = mrspp->outgoing; + struct in_addr prev_hop = mrspp->prev_hop; + + zlog_debug( + "Rx mt(%d) qid=%ud arr=%x in=%pI4 out=%pI4 prev=%pI4 proto=%d fwd=%d", + rsp, ntohl(qry_id), mrspp->arrival, &incoming, &outgoing, + &prev_hop, mrspp->rtg_proto, mrspp->fwd_code); +} + +static void mtrace_debug(struct pim_interface *pim_ifp, + struct igmp_mtrace *mtracep, int mtrace_len) +{ + struct in_addr ga, sa, da, ra; + + ga = mtracep->grp_addr; + sa = mtracep->src_addr; + da = mtracep->dst_addr; + ra = mtracep->rsp_addr; + + zlog_debug( + "Rx mtrace packet incoming on %pI4: hops=%d type=%d size=%d, grp=%pI4, src=%pI4, dst=%pI4 rsp=%pI4 ttl=%d qid=%ud", + &pim_ifp->primary_address, mtracep->hops, mtracep->type, + mtrace_len, &ga, &sa, &da, &ra, mtracep->rsp_ttl, + ntohl(mtracep->qry_id)); + if (mtrace_len > (int)sizeof(struct igmp_mtrace)) { + + int i; + + int responses = mtrace_len - sizeof(struct igmp_mtrace); + + if ((responses % sizeof(struct igmp_mtrace_rsp)) != 0) + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Mtrace response block of wrong length"); + + responses = responses / sizeof(struct igmp_mtrace_rsp); + + for (i = 0; i < responses; i++) + mtrace_rsp_debug(mtracep->qry_id, i, &mtracep->rsp[i]); + } +} + +/* 5.1 Query Arrival Time */ +static uint32_t query_arrival_time(void) +{ + struct timeval tv; + uint32_t qat; + + if (gettimeofday(&tv, NULL) < 0) { + if (PIM_DEBUG_MTRACE) + zlog_debug("Query arrival time lookup failed: errno=%d: %s", + errno, safe_strerror(errno)); + return 0; + } + /* not sure second offset correct, as I get different value */ + qat = ((tv.tv_sec + 32384) << 16) + ((tv.tv_usec << 10) / 15625); + + return qat; +} + +static int mtrace_send_packet(struct interface *ifp, + struct igmp_mtrace *mtracep, + size_t mtrace_buf_len, struct in_addr dst_addr, + struct in_addr group_addr) +{ + struct sockaddr_in to; + socklen_t tolen; + ssize_t sent; + int ret; + int fd; + uint8_t ttl; + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = dst_addr; + tolen = sizeof(to); + + if (PIM_DEBUG_MTRACE) { + struct in_addr if_addr; + struct in_addr rsp_addr = mtracep->rsp_addr; + + if_addr = mtrace_primary_address(ifp); + zlog_debug("Sending mtrace packet to %pI4 on %pI4", &rsp_addr, + &if_addr); + } + + fd = pim_socket_raw(IPPROTO_IGMP); + + if (fd < 0) + return -1; + + ret = pim_socket_bind(fd, ifp); + + if (ret < 0) { + ret = -1; + goto close_fd; + } + + if (IPV4_CLASS_DE(ntohl(dst_addr.s_addr))) { + if (IPV4_MC_LINKLOCAL(ntohl(dst_addr.s_addr))) { + ttl = 1; + } else { + if (mtracep->type == PIM_IGMP_MTRACE_RESPONSE) + ttl = mtracep->rsp_ttl; + else + ttl = 64; + } + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, + sizeof(ttl)); + + if (ret < 0) { + if (PIM_DEBUG_MTRACE) + zlog_debug("Failed to set socket multicast TTL"); + ret = -1; + goto close_fd; + } + } + + sent = sendto(fd, (char *)mtracep, mtrace_buf_len, MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + + if (sent != (ssize_t)mtrace_buf_len) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + if (sent < 0) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Send mtrace request failed for %s on%s: group=%s msg_size=%zd: errno=%d: %s", + dst_str, ifp->name, group_str, + mtrace_buf_len, errno, + safe_strerror(errno)); + } else { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Send mtrace request failed for %s on %s: group=%s msg_size=%zd: sent=%zd", + dst_str, ifp->name, group_str, + mtrace_buf_len, sent); + } + ret = -1; + goto close_fd; + } + ret = 0; +close_fd: + close(fd); + return ret; +} + +static int mtrace_un_forward_packet(struct pim_instance *pim, struct ip *ip_hdr, + struct interface *interface) +{ + struct pim_nexthop nexthop; + struct sockaddr_in to; + struct interface *if_out; + socklen_t tolen; + int ret; + int fd; + int sent; + uint16_t checksum; + + checksum = ip_hdr->ip_sum; + + ip_hdr->ip_sum = 0; + + if (checksum != in_cksum(ip_hdr, ip_hdr->ip_hl * 4)) + return -1; + + if (ip_hdr->ip_ttl-- <= 1) + return -1; + + if (interface == NULL) { + memset(&nexthop, 0, sizeof(nexthop)); + if (!pim_nexthop_lookup(pim, &nexthop, ip_hdr->ip_dst, 0)) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Dropping mtrace packet, no route to destination"); + return -1; + } + + if_out = nexthop.interface; + } else { + if_out = interface; + } + + ip_hdr->ip_sum = in_cksum(ip_hdr, ip_hdr->ip_hl * 4); + + fd = pim_socket_raw(IPPROTO_RAW); + + if (fd < 0) + return -1; + + pim_socket_ip_hdr(fd); + + ret = pim_socket_bind(fd, if_out); + + if (ret < 0) { + close(fd); + return -1; + } + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = ip_hdr->ip_dst; + tolen = sizeof(to); + + sent = sendto(fd, ip_hdr, ntohs(ip_hdr->ip_len), 0, + (struct sockaddr *)&to, tolen); + + close(fd); + + if (sent < 0) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Failed to forward mtrace packet: sendto errno=%d, %s", + errno, safe_strerror(errno)); + return -1; + } + + if (PIM_DEBUG_MTRACE) { + zlog_debug("Fwd mtrace packet len=%u to %pI4 ttl=%u", + ntohs(ip_hdr->ip_len), &ip_hdr->ip_dst, + ip_hdr->ip_ttl); + } + + return 0; +} + +static int mtrace_mc_forward_packet(struct pim_instance *pim, struct ip *ip_hdr) +{ + pim_sgaddr sg; + struct channel_oil *c_oil; + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch = NULL; + int ret = -1; + + memset(&sg, 0, sizeof(sg)); + sg.grp = ip_hdr->ip_dst; + + c_oil = pim_find_channel_oil(pim, &sg); + + if (c_oil == NULL) { + if (PIM_DEBUG_MTRACE) { + zlog_debug( + "Dropping mtrace multicast packet len=%u to %pI4 ttl=%u", + ntohs(ip_hdr->ip_len), + &ip_hdr->ip_dst, ip_hdr->ip_ttl); + } + return -1; + } + if (c_oil->up == NULL) + return -1; + if (c_oil->up->ifchannels == NULL) + return -1; + for (ALL_LIST_ELEMENTS(c_oil->up->ifchannels, chnode, chnextnode, ch)) { + if (pim_macro_chisin_oiflist(ch)) { + int r; + + r = mtrace_un_forward_packet(pim, ip_hdr, + ch->interface); + if (r == 0) + ret = 0; + } + } + return ret; +} + + +static int mtrace_forward_packet(struct pim_instance *pim, struct ip *ip_hdr) +{ + if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) + return mtrace_mc_forward_packet(pim, ip_hdr); + else + return mtrace_un_forward_packet(pim, ip_hdr, NULL); +} + +static int mtrace_send_mc_response(struct pim_instance *pim, + struct igmp_mtrace *mtracep, + size_t mtrace_len) +{ + pim_sgaddr sg; + struct channel_oil *c_oil; + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch = NULL; + int ret = -1; + + memset(&sg, 0, sizeof(sg)); + sg.grp = mtracep->rsp_addr; + + c_oil = pim_find_channel_oil(pim, &sg); + + if (c_oil == NULL) { + if (PIM_DEBUG_MTRACE) { + struct in_addr rsp_addr = mtracep->rsp_addr; + + zlog_debug( + "Dropping mtrace multicast response packet len=%u to %pI4", + (unsigned int)mtrace_len, &rsp_addr); + } + return -1; + } + if (c_oil->up == NULL) + return -1; + if (c_oil->up->ifchannels == NULL) + return -1; + for (ALL_LIST_ELEMENTS(c_oil->up->ifchannels, chnode, chnextnode, ch)) { + if (pim_macro_chisin_oiflist(ch)) { + int r; + + r = mtrace_send_packet(ch->interface, mtracep, + mtrace_len, mtracep->rsp_addr, + mtracep->grp_addr); + if (r == 0) + ret = 0; + } + } + return ret; +} + +/* 6.5 Sending Traceroute Responses */ +static int mtrace_send_response(struct pim_instance *pim, + struct igmp_mtrace *mtracep, size_t mtrace_len) +{ + struct pim_nexthop nexthop; + + mtracep->type = PIM_IGMP_MTRACE_RESPONSE; + + mtracep->checksum = 0; + mtracep->checksum = in_cksum((char *)mtracep, mtrace_len); + + if (IPV4_CLASS_DE(ntohl(mtracep->rsp_addr.s_addr))) { + struct pim_rpf *p_rpf; + + if (pim_rp_i_am_rp(pim, mtracep->rsp_addr)) + return mtrace_send_mc_response(pim, mtracep, + mtrace_len); + + p_rpf = pim_rp_g(pim, mtracep->rsp_addr); + + if (p_rpf == NULL) { + if (PIM_DEBUG_MTRACE) { + struct in_addr rsp_addr = mtracep->rsp_addr; + + zlog_debug("mtrace no RP for %pI4", &rsp_addr); + } + return -1; + } + nexthop = p_rpf->source_nexthop; + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace response to RP"); + } else { + memset(&nexthop, 0, sizeof(nexthop)); + /* TODO: should use unicast rib lookup */ + if (!pim_nexthop_lookup(pim, &nexthop, mtracep->rsp_addr, 1)) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Dropped response qid=%ud, no route to response address", + mtracep->qry_id); + return -1; + } + } + + return mtrace_send_packet(nexthop.interface, mtracep, mtrace_len, + mtracep->rsp_addr, mtracep->grp_addr); +} + +int igmp_mtrace_recv_qry_req(struct gm_sock *igmp, struct ip *ip_hdr, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) +{ + static uint32_t qry_id, qry_src; + char mtrace_buf[MTRACE_HDR_SIZE + MTRACE_MAX_HOPS * MTRACE_RSP_SIZE]; + struct interface *ifp; + struct interface *out_ifp = NULL; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct igmp_mtrace *mtracep; + struct igmp_mtrace_rsp *rspp; + struct in_addr nh_addr; + enum mtrace_fwd_code fwd_code = MTRACE_FWD_CODE_NO_ERROR; + size_t r_len; + int last_rsp_ind = 0; + size_t mtrace_len; + uint16_t recv_checksum; + uint16_t checksum; + bool reached_source; + bool fwd_info; + + ifp = igmp->interface; + pim_ifp = ifp->info; + pim = pim_ifp->pim; + + /* + * 6. Router Behaviour + * Check if mtrace packet is addressed elsewhere and forward, + * if applicable + */ + if (!IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) + if (!if_address_is_local(&ip_hdr->ip_dst, AF_INET, + pim->vrf->vrf_id)) + return mtrace_forward_packet(pim, ip_hdr); + + if (igmp_msg_len < (int)sizeof(struct igmp_mtrace)) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace packet from %s on %s: too short, len=%d, min=%zu", + from_str, ifp->name, igmp_msg_len, + sizeof(struct igmp_mtrace)); + return -1; + } + + mtracep = (struct igmp_mtrace *)igmp_msg; + + recv_checksum = mtracep->checksum; + + mtracep->checksum = 0; + + checksum = in_cksum(igmp_msg, igmp_msg_len); + + if (recv_checksum != checksum) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace packet from %s on %s: checksum mismatch: received=%x computed=%x", + from_str, ifp->name, recv_checksum, checksum); + return -1; + } + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.mtrace_req++; + + if (PIM_DEBUG_MTRACE) + mtrace_debug(pim_ifp, mtracep, igmp_msg_len); + + /* subtract header from message length */ + r_len = igmp_msg_len - sizeof(struct igmp_mtrace); + + /* Classify mtrace packet, check if it is a query */ + if (!r_len) { + if (PIM_DEBUG_MTRACE) + zlog_debug("Received IGMP multicast traceroute query"); + + /* 6.1.1 Packet verification */ + if (!pim_if_connected_to_source(ifp, mtracep->dst_addr)) { + if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr))) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Dropping multicast query on wrong interface"); + return -1; + } + /* Unicast query on wrong interface */ + fwd_code = MTRACE_FWD_CODE_WRONG_IF; + if (PIM_DEBUG_MTRACE) + zlog_debug("Multicast query on wrong interface"); + } + if (qry_id == mtracep->qry_id && qry_src == from.s_addr) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Dropping multicast query with duplicate source and id"); + return -1; + } + qry_id = mtracep->qry_id; + qry_src = from.s_addr; + } + /* if response fields length is equal to a whole number of responses */ + else if ((r_len % sizeof(struct igmp_mtrace_rsp)) == 0) { + r_len = igmp_msg_len - sizeof(struct igmp_mtrace); + + if (r_len != 0) + last_rsp_ind = r_len / sizeof(struct igmp_mtrace_rsp); + if (last_rsp_ind > MTRACE_MAX_HOPS) { + if (PIM_DEBUG_MTRACE) + zlog_debug("Mtrace request of excessive size"); + return -1; + } + } else { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace packet from %s on %s: invalid length %d", + from_str, ifp->name, igmp_msg_len); + return -1; + } + + /* 6.2.1 Packet Verification - drop not link-local multicast */ + if (IPV4_CLASS_DE(ntohl(ip_hdr->ip_dst.s_addr)) + && !IPV4_MC_LINKLOCAL(ntohl(ip_hdr->ip_dst.s_addr))) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace packet from %s on %s: not link-local multicast %pI4", + from_str, ifp->name, &ip_hdr->ip_dst); + return -1; + } + + /* 6.2.2. Normal Processing */ + + /* 6.2.2. 1. If there is room in the current buffer? */ + + if (last_rsp_ind == MTRACE_MAX_HOPS) { + /* ...there was no room... */ + mtracep->rsp[MTRACE_MAX_HOPS - 1].fwd_code = + MTRACE_FWD_CODE_NO_SPACE; + return mtrace_send_response(pim_ifp->pim, mtracep, + igmp_msg_len); + } + + /* ...insert new response block... */ + + /* calculate new mtrace lenght with extra response */ + mtrace_len = igmp_msg_len + sizeof(struct igmp_mtrace_rsp); + + /* copy received query/request */ + memcpy(mtrace_buf, igmp_msg, igmp_msg_len); + + /* repoint mtracep pointer to copy */ + mtracep = (struct igmp_mtrace *)mtrace_buf; + + /* pointer for extra response field to be filled in */ + rspp = &mtracep->rsp[last_rsp_ind]; + + /* initialize extra response field */ + mtrace_rsp_init(rspp); + + /* carry over any error noted when receiving the query */ + rspp->fwd_code = fwd_code; + + /* ...and fill in Query Arrival Time... */ + rspp->arrival = htonl(query_arrival_time()); + rspp->outgoing = pim_ifp->primary_address; + rspp->out_count = htonl(MTRACE_UNKNOWN_COUNT); + rspp->fwd_ttl = 1; + + /* 6.2.2. 2. Attempt to determine the forwarding information... */ + + if (mtracep->grp_addr.s_addr != INADDR_ANY) + fwd_info = mtrace_fwd_info(pim, mtracep, rspp, &out_ifp); + else + fwd_info = mtrace_fwd_info_weak(pim, mtracep, rspp, &out_ifp); + + /* 6.2.2 3. If no forwarding information... */ + if (!fwd_info) { + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace not found multicast state"); + mtrace_rsp_set_fwd_code(rspp, MTRACE_FWD_CODE_NO_ROUTE); + /* 6.2.2. 3. forward the packet to requester */ + return mtrace_send_response(pim, mtracep, mtrace_len); + } + + nh_addr = rspp->prev_hop; + + reached_source = false; + + if (nh_addr.s_addr == INADDR_ANY) { + /* no pim? i.e. 7.5.3. No Previous Hop */ + if (!out_ifp->info) { + if (PIM_DEBUG_MTRACE) + zlog_debug("mtrace not found incoming if w/ pim"); + mtrace_rsp_set_fwd_code(rspp, + MTRACE_FWD_CODE_NO_MULTICAST); + return mtrace_send_response(pim, mtracep, mtrace_len); + } + /* reached source? i.e. 7.5.1 Arriving at source */ + if (pim_if_connected_to_source(out_ifp, mtracep->src_addr)) { + reached_source = true; + rspp->prev_hop = mtracep->src_addr; + } + /* + * 6.4 Forwarding Traceroute Requests: + * Previous-hop router not known, + * packet is sent to an appropriate multicast address + */ + (void)inet_aton(MCAST_ALL_ROUTERS, &nh_addr); + } + + /* 6.2.2 8. If this router is the Rendez-vous Point */ + if (mtracep->grp_addr.s_addr != INADDR_ANY && + pim_rp_i_am_rp(pim, mtracep->grp_addr)) { + mtrace_rsp_set_fwd_code(rspp, MTRACE_FWD_CODE_REACHED_RP); + /* 7.7.1. PIM-SM ...RP has not performed source-specific join */ + if (rspp->src_mask == MTRACE_SRC_MASK_GROUP) + return mtrace_send_response(pim, mtracep, mtrace_len); + } + + /* + * 6.4 Forwarding Traceroute Requests: the number of response + * blocks exceeds number of responses, so forward to the requester. + */ + if (mtracep->hops <= (last_rsp_ind + 1)) + return mtrace_send_response(pim, mtracep, mtrace_len); + + /* 7.5.1. Arriving at source: terminate trace */ + if (reached_source) + return mtrace_send_response(pim, mtracep, mtrace_len); + + mtracep->checksum = 0; + + mtracep->checksum = in_cksum(mtrace_buf, mtrace_len); + + /* 6.4 Forwarding Traceroute Requests: response blocks less than req. */ + return mtrace_send_packet(out_ifp, mtracep, mtrace_len, nh_addr, + mtracep->grp_addr); +} + +/* 6.3. Traceroute responses */ +int igmp_mtrace_recv_response(struct gm_sock *igmp, struct ip *ip_hdr, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) +{ + static uint32_t qry_id, rsp_dst; + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct igmp_mtrace *mtracep; + uint16_t recv_checksum; + uint16_t checksum; + + ifp = igmp->interface; + pim_ifp = ifp->info; + pim = pim_ifp->pim; + + if (igmp_msg_len < (int)sizeof(struct igmp_mtrace)) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace packet from %s on %s: too short, len=%d, min=%zu", + from_str, ifp->name, igmp_msg_len, + sizeof(struct igmp_mtrace)); + return -1; + } + + mtracep = (struct igmp_mtrace *)igmp_msg; + + recv_checksum = mtracep->checksum; + + mtracep->checksum = 0; + + checksum = in_cksum(igmp_msg, igmp_msg_len); + + if (recv_checksum != checksum) { + if (PIM_DEBUG_MTRACE) + zlog_debug( + "Recv mtrace response from %s on %s: checksum mismatch: received=%x computed=%x", + from_str, ifp->name, recv_checksum, checksum); + return -1; + } + + mtracep->checksum = checksum; + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.mtrace_rsp++; + + if (PIM_DEBUG_MTRACE) + mtrace_debug(pim_ifp, mtracep, igmp_msg_len); + + /* Drop duplicate packets */ + if (qry_id == mtracep->qry_id && rsp_dst == ip_hdr->ip_dst.s_addr) { + if (PIM_DEBUG_MTRACE) + zlog_debug("duplicate mtrace response packet dropped"); + return -1; + } + + qry_id = mtracep->qry_id; + rsp_dst = ip_hdr->ip_dst.s_addr; + + return mtrace_forward_packet(pim, ip_hdr); +} diff --git a/pimd/pim_igmp_mtrace.h b/pimd/pim_igmp_mtrace.h new file mode 100644 index 0000000..bba9c10 --- /dev/null +++ b/pimd/pim_igmp_mtrace.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Multicast traceroute for FRRouting + * Copyright (C) 2017 Mladen Sablic + */ + +#ifndef PIM_IGMP_MTRACE_H +#define PIM_IGMP_MTRACE_H + +#include + +#include "pim_igmp.h" + +#define MTRACE_MAX_HOPS (255) +#define MTRACE_UNKNOWN_COUNT (0xffffffff) +#define MTRACE_SRC_MASK_GROUP (0x3f) /* forwarding on group state (*,G) */ +#define MTRACE_SRC_MASK_SOURCE (0x20) /* i.e. 32 forwarding on (S,G) */ + +enum mtrace_fwd_code { + MTRACE_FWD_CODE_NO_ERROR = 0x00, + MTRACE_FWD_CODE_WRONG_IF = 0x01, + MTRACE_FWD_CODE_PRUNE_SENT = 0x02, + MTRACE_FWD_CODE_PRUNE_RCVD = 0x03, + MTRACE_FWD_CODE_SCOPED = 0x04, + MTRACE_FWD_CODE_NO_ROUTE = 0x05, + MTRACE_FWD_CODE_WRONG_LAST_HOP = 0x06, + MTRACE_FWD_CODE_NOT_FORWARDING = 0x07, + MTRACE_FWD_CODE_REACHED_RP = 0x08, + MTRACE_FWD_CODE_RPF_IF = 0x09, + MTRACE_FWD_CODE_NO_MULTICAST = 0x0A, + MTRACE_FWD_CODE_INFO_HIDDEN = 0x0B, + MTRACE_FWD_CODE_NO_SPACE = 0x81, + MTRACE_FWD_CODE_OLD_ROUTER = 0x82, + MTRACE_FWD_CODE_ADMIN_PROHIB = 0x83 +}; + +enum mtrace_rtg_proto { + MTRACE_RTG_PROTO_DVMRP = 1, + MTRACE_RTG_PROTO_MOSPF = 2, + MTRACE_RTG_PROTO_PIM = 3, + MTRACE_RTG_PROTO_CBT = 4, + MTRACE_RTG_PROTO_PIM_SPECIAL = 5, + MTRACE_RTG_PROTO_PIM_STATIC = 6, + MTRACE_RTG_PROTO_DVMRP_STATIC = 7, + MTRACE_RTG_PROTO_PIM_MBGP = 8, + MTRACE_RTG_PROTO_CBT_SPECIAL = 9, + MTRACE_RTG_PROTO_CBT_STATIC = 10, + MTRACE_RTG_PROTO_PIM_ASSERT = 11, +}; + +struct igmp_mtrace_rsp { + uint32_t arrival; + struct in_addr incoming; + struct in_addr outgoing; + struct in_addr prev_hop; + uint32_t in_count; + uint32_t out_count; + uint32_t total; + uint32_t rtg_proto : 8; + uint32_t fwd_ttl : 8; + /* little endian order for next three fields */ + uint32_t src_mask : 6; + uint32_t s : 1; + uint32_t mbz : 1; + uint32_t fwd_code : 8; +} __attribute__((packed)); + +struct igmp_mtrace { + uint8_t type; + uint8_t hops; + uint16_t checksum; + struct in_addr grp_addr; + struct in_addr src_addr; + struct in_addr dst_addr; + struct in_addr rsp_addr; + uint32_t rsp_ttl : 8; + uint32_t qry_id : 24; + struct igmp_mtrace_rsp rsp[0]; +} __attribute__((packed)); + +#define MTRACE_HDR_SIZE (sizeof(struct igmp_mtrace)) +#define MTRACE_RSP_SIZE (sizeof(struct igmp_mtrace_rsp)) + +int igmp_mtrace_recv_qry_req(struct gm_sock *igmp, struct ip *ip_hdr, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len); + +int igmp_mtrace_recv_response(struct gm_sock *igmp, struct ip *ip_hdr, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len); + +#endif /* PIM_IGMP_MTRACE_H */ diff --git a/pimd/pim_igmp_stats.c b/pimd/pim_igmp_stats.c new file mode 100644 index 0000000..8ad3fb5 --- /dev/null +++ b/pimd/pim_igmp_stats.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRRouting + * Copyright (C) 2018 Mladen Sablic + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pim_igmp_stats.h" + +void igmp_stats_init(struct igmp_stats *stats) +{ + memset(stats, 0, sizeof(struct igmp_stats)); +} + +void igmp_stats_add(struct igmp_stats *a, struct igmp_stats *b) +{ + if (!a || !b) + return; + + a->query_v1 += b->query_v1; + a->query_v2 += b->query_v2; + a->query_v3 += b->query_v3; + a->report_v1 += b->report_v1; + a->report_v2 += b->report_v2; + a->report_v3 += b->report_v3; + a->leave_v2 += b->leave_v2; + a->mtrace_rsp += b->mtrace_rsp; + a->mtrace_req += b->mtrace_req; + a->unsupported += b->unsupported; + a->peak_groups += b->peak_groups; + a->total_groups += b->total_groups; + a->total_source_groups += b->total_source_groups; + a->joins_sent += b->joins_sent; + a->joins_failed += b->joins_failed; + a->general_queries_sent += b->general_queries_sent; + a->group_queries_sent += b->group_queries_sent; + a->total_recv_messages += b->query_v1 + b->query_v2 + b->query_v3 + + b->report_v1 + b->report_v2 + b->report_v3 + + b->leave_v2 + b->mtrace_rsp + b->mtrace_req; +} diff --git a/pimd/pim_igmp_stats.h b/pimd/pim_igmp_stats.h new file mode 100644 index 0000000..a3ce486 --- /dev/null +++ b/pimd/pim_igmp_stats.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRRouting + * Copyright (C) 2018 Mladen Sablic + */ + +#ifndef PIM_IGMP_STATS_H +#define PIM_IGMP_STATS_H + +#include + +struct igmp_stats { + uint32_t query_v1; + uint32_t query_v2; + uint32_t query_v3; + uint32_t report_v1; + uint32_t report_v2; + uint32_t report_v3; + uint32_t leave_v2; + uint32_t mtrace_rsp; + uint32_t mtrace_req; + uint32_t unsupported; + uint32_t peak_groups; + uint32_t total_groups; + uint32_t total_source_groups; + uint32_t joins_sent; + uint32_t joins_failed; + uint32_t general_queries_sent; + uint32_t group_queries_sent; + uint32_t total_recv_messages; +}; + +#if PIM_IPV == 4 +void igmp_stats_init(struct igmp_stats *stats); +void igmp_stats_add(struct igmp_stats *a, struct igmp_stats *b); +#else +static inline void igmp_stats_init(struct igmp_stats *stats) +{ +} + +static inline void igmp_stats_add(struct igmp_stats *a, struct igmp_stats *b) +{ +} +#endif + +#endif /* PIM_IGMP_STATS_H */ diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c new file mode 100644 index 0000000..944dffd --- /dev/null +++ b/pimd/pim_igmpv2.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + * Daniel Walton + */ + +#include "zebra.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_igmp.h" +#include "pim_igmpv2.h" +#include "pim_igmpv3.h" +#include "pim_ssm.h" +#include "pim_str.h" +#include "pim_time.h" +#include "pim_util.h" + + +static void on_trace(const char *label, struct interface *ifp, + struct in_addr from) +{ + if (PIM_DEBUG_GM_TRACE) { + char from_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", from, from_str, sizeof(from_str)); + zlog_debug("%s: from %s on %s", label, from_str, ifp->name); + } +} + +void igmp_v2_send_query(struct gm_group *group, int fd, const char *ifname, + char *query_buf, struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec) +{ + ssize_t msg_size = 8; + uint8_t max_resp_code; + ssize_t sent; + struct sockaddr_in to; + socklen_t tolen; + uint16_t checksum; + + /* max_resp_code must be non-zero else this will look like an IGMP v1 + * query */ + /* RFC 2236: 2.2. , v2's is equal to it */ + max_resp_code = query_max_response_time_dsec; + assert(max_resp_code > 0); + + query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; + query_buf[1] = max_resp_code; + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = + 0; /* for computing checksum */ + memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr)); + + checksum = in_cksum(query_buf, msg_size); + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; + + if (PIM_DEBUG_GM_PACKETS) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s", + dst_str, ifname, group_str); + } + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = dst_addr; + tolen = sizeof(to); + + sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + if (sent != (ssize_t)msg_size) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + if (sent < 0) { + zlog_warn( + "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", + dst_str, ifname, group_str, msg_size, errno, + safe_strerror(errno)); + } else { + zlog_warn( + "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", + dst_str, ifname, group_str, msg_size, sent); + } + return; + } +} + +int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from, + const char *from_str, char *igmp_msg, int igmp_msg_len) +{ + struct interface *ifp = igmp->interface; + struct in_addr group_addr; + struct pim_interface *pim_ifp; + char group_str[INET_ADDRSTRLEN]; + + on_trace(__func__, igmp->interface, from); + + pim_ifp = ifp->info; + + if (igmp->mtrace_only) + return 0; + + if (igmp_msg_len != IGMP_V12_MSG_SIZE) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug( + "Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d", + from_str, ifp->name, igmp_msg_len, + IGMP_V12_MSG_SIZE); + } + + if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) { + zlog_warn( + "Recv IGMPv2 REPORT from %s on %s: size=%d with invalid checksum", + from_str, ifp->name, igmp_msg_len); + return -1; + } + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.report_v2++; + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + if (PIM_DEBUG_GM_PACKETS) { + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s", from_str, + ifp->name, group_str); + } + + /* + * RFC 4604 + * section 2.2.1 + * EXCLUDE mode does not apply to SSM addresses, and an SSM-aware router + * will ignore MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE requests in + * the SSM range. + */ + if (pim_is_grp_ssm(pim_ifp->pim, group_addr)) { + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Ignoring IGMPv2 group record %pI4 from %s on %s exclude mode in SSM range", + &group_addr.s_addr, from_str, ifp->name); + } + return -1; + } + + + /* + * RFC 3376 + * 7.3.2. In the Presence of Older Version Group Members + * + * When Group Compatibility Mode is IGMPv2, a router internally + * translates the following IGMPv2 messages for that group to their + * IGMPv3 equivalents: + * + * IGMPv2 Message IGMPv3 Equivalent + * -------------- ----------------- + * Report IS_EX( {} ) + * Leave TO_IN( {} ) + */ + igmpv3_report_isex(igmp, from, group_addr, 0, NULL, 1); + + return 0; +} + +int igmp_v2_recv_leave(struct gm_sock *igmp, struct ip *ip_hdr, + const char *from_str, char *igmp_msg, int igmp_msg_len) +{ + struct interface *ifp = igmp->interface; + struct in_addr group_addr; + char group_str[INET_ADDRSTRLEN]; + struct in_addr from = ip_hdr->ip_src; + + on_trace(__func__, igmp->interface, from); + + if (igmp->mtrace_only) + return 0; + + if (igmp_msg_len != IGMP_V12_MSG_SIZE) { + if (PIM_DEBUG_GM_PACKETS) + zlog_debug( + "Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d", + from_str, ifp->name, igmp_msg_len, + IGMP_V12_MSG_SIZE); + } + + if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) { + zlog_warn( + "Recv IGMPv2 LEAVE from %s on %s with invalid checksum", + from_str, ifp->name); + return -1; + } + + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + if (PIM_DEBUG_GM_PACKETS) { + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s", from_str, + ifp->name, group_str); + } + /* + * As per RFC 2236, section 9: + Message Type Destination Group + ------------ ----------------- + General Query ALL-SYSTEMS (224.0.0.1) + Group-Specific Query The group being queried + Membership Report The group being reported + Leave Message ALL-ROUTERS (224.0.0.2) + + Note: in older (i.e., non-standard and now obsolete) versions of + IGMPv2, hosts send Leave Messages to the group being left. A + router SHOULD accept Leave Messages addressed to the group being + left in the interests of backwards compatibility with such hosts. + In all cases, however, hosts MUST send to the ALL-ROUTERS address + to be compliant with this specification. + */ + if ((ntohl(ip_hdr->ip_dst.s_addr) != INADDR_ALLRTRS_GROUP) + && (ip_hdr->ip_dst.s_addr != group_addr.s_addr)) { + if (PIM_DEBUG_GM_EVENTS) + zlog_debug( + "IGMPv2 Leave message is ignored since received on address other than ALL-ROUTERS or Group-address"); + return -1; + } + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.leave_v2++; + + /* + * RFC 3376 + * 7.3.2. In the Presence of Older Version Group Members + * + * When Group Compatibility Mode is IGMPv2, a router internally + * translates the following IGMPv2 messages for that group to their + * IGMPv3 equivalents: + * + * IGMPv2 Message IGMPv3 Equivalent + * -------------- ----------------- + * Report IS_EX( {} ) + * Leave TO_IN( {} ) + */ + igmpv3_report_toin(igmp, from, group_addr, 0, NULL); + + return 0; +} diff --git a/pimd/pim_igmpv2.h b/pimd/pim_igmpv2.h new file mode 100644 index 0000000..e968d83 --- /dev/null +++ b/pimd/pim_igmpv2.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + * Daniel Walton + */ + +#ifndef PIM_IGMPV2_H +#define PIM_IGMPV2_H + +void igmp_v2_send_query(struct gm_group *group, int fd, const char *ifname, + char *query_buf, struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec); + +int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from, + const char *from_str, char *igmp_msg, int igmp_msg_len); + +int igmp_v2_recv_leave(struct gm_sock *igmp, struct ip *ip_hdr, + const char *from_str, char *igmp_msg, int igmp_msg_len); + +#endif /* PIM_IGMPV2_H */ diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c new file mode 100644 index 0000000..2c5ad4d --- /dev/null +++ b/pimd/pim_igmpv3.c @@ -0,0 +1,2026 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include +#include "log.h" +#include "memory.h" +#include "if.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_iface.h" +#include "pim_igmp.h" +#include "pim_igmpv3.h" +#include "pim_str.h" +#include "pim_util.h" +#include "pim_time.h" +#include "pim_zebra.h" +#include "pim_oil.h" +#include "pim_ssm.h" + +static void group_retransmit_timer_on(struct gm_group *group); +static long igmp_group_timer_remain_msec(struct gm_group *group); +static long igmp_source_timer_remain_msec(struct gm_source *source); +static void group_query_send(struct gm_group *group); +static void source_query_send_by_flag(struct gm_group *group, + int num_sources_tosend); + +static void on_trace(const char *label, struct interface *ifp, + struct in_addr from, struct in_addr group_addr, + int num_sources, struct in_addr *sources) +{ + if (PIM_DEBUG_GM_TRACE) { + char from_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", from, from_str, sizeof(from_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + + zlog_debug("%s: from %s on %s: group=%s sources=%d", label, + from_str, ifp->name, group_str, num_sources); + } +} + +static inline long igmp_gmi_msec(struct gm_group *group) +{ + struct pim_interface *pim_ifp = group->interface->info; + struct gm_sock *igmp; + struct listnode *sock_node; + + long qrv = 0, qqi = 0; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) { + qrv = MAX(qrv, igmp->querier_robustness_variable); + qqi = MAX(qqi, igmp->querier_query_interval); + } + return PIM_IGMP_GMI_MSEC(qrv, qqi, + pim_ifp->gm_query_max_response_time_dsec); +} + +void igmp_group_reset_gmi(struct gm_group *group) +{ + long group_membership_interval_msec; + struct interface *ifp; + + ifp = group->interface; + + /* + RFC 3376: 8.4. Group Membership Interval + + The Group Membership Interval is the amount of time that must pass + before a multicast router decides there are no more members of a + group or a particular source on a network. + + This value MUST be ((the Robustness Variable) times (the Query + Interval)) plus (one Query Response Interval). + + group_membership_interval_msec = querier_robustness_variable * + (1000 * querier_query_interval) + + 100 * query_response_interval_dsec; + */ + group_membership_interval_msec = igmp_gmi_msec(group); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "Resetting group %s timer to GMI=%ld.%03ld sec on %s", + group_str, group_membership_interval_msec / 1000, + group_membership_interval_msec % 1000, ifp->name); + } + + /* + RFC 3376: 6.2.2. Definition of Group Timers + + The group timer is only used when a group is in EXCLUDE mode and + it represents the time for the *filter-mode* of the group to + expire and switch to INCLUDE mode. + */ + assert(group->group_filtermode_isexcl); + + igmp_group_timer_on(group, group_membership_interval_msec, ifp->name); +} + +static void igmp_source_timer(struct event *t) +{ + struct gm_source *source; + struct gm_group *group; + + source = EVENT_ARG(t); + + group = source->source_group; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_debug( + "%s: Source timer expired for group %s source %s on %s", + __func__, group_str, source_str, + group->interface->name); + } + + /* + RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules + + Group + Filter-Mode Source Timer Value Action + ----------- ------------------ ------ + INCLUDE TIMER == 0 Suggest to stop forwarding + traffic from source and + remove source record. If + there are no more source + records for the group, delete + group record. + + EXCLUDE TIMER == 0 Suggest to not forward + traffic from source + (DO NOT remove record) + + Source timer switched from (T > 0) to (T == 0): disable forwarding. + */ + + if (group->group_filtermode_isexcl) { + /* EXCLUDE mode */ + + igmp_source_forward_stop(source); + } else { + /* INCLUDE mode */ + + /* igmp_source_delete() will stop forwarding source */ + igmp_source_delete(source); + + /* + If there are no more source records for the group, delete + group + record. + */ + if (!listcount(group->group_source_list)) { + igmp_group_delete_empty_include(group); + } + } +} + +static void source_timer_off(struct gm_group *group, struct gm_source *source) +{ + if (!source->t_source_timer) + return; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_debug( + "Cancelling TIMER event for group %s source %s on %s", + group_str, source_str, group->interface->name); + } + + EVENT_OFF(source->t_source_timer); +} + +static void igmp_source_timer_on(struct gm_group *group, + struct gm_source *source, long interval_msec) +{ + source_timer_off(group, source); + struct pim_interface *pim_ifp = group->interface->info; + + if (PIM_DEBUG_GM_EVENTS) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_debug( + "Scheduling %ld.%03ld sec TIMER event for group %s source %s on %s", + interval_msec / 1000, interval_msec % 1000, group_str, + source_str, group->interface->name); + } + + event_add_timer_msec(router->master, igmp_source_timer, source, + interval_msec, &source->t_source_timer); + + /* + RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules + + Source timer switched from (T == 0) to (T > 0): enable forwarding. + */ + igmp_source_forward_start(pim_ifp->pim, source); +} + +void igmp_source_reset_gmi(struct gm_group *group, struct gm_source *source) +{ + long group_membership_interval_msec; + struct interface *ifp; + + ifp = group->interface; + + group_membership_interval_msec = igmp_gmi_msec(group); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + + zlog_debug( + "Resetting source %s timer to GMI=%ld.%03ld sec for group %s on %s", + source_str, group_membership_interval_msec / 1000, + group_membership_interval_msec % 1000, group_str, + ifp->name); + } + + igmp_source_timer_on(group, source, group_membership_interval_msec); +} + +static void source_mark_delete_flag(struct gm_group *group) +{ + struct listnode *src_node; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { + IGMP_SOURCE_DO_DELETE(src->source_flags); + } +} + +static void source_mark_send_flag(struct gm_group *group) +{ + struct listnode *src_node; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { + IGMP_SOURCE_DO_SEND(src->source_flags); + } +} + +static int source_mark_send_flag_by_timer(struct gm_group *group) +{ + struct listnode *src_node; + struct gm_source *src; + int num_marked_sources = 0; + + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { + /* Is source timer running? */ + if (src->t_source_timer) { + IGMP_SOURCE_DO_SEND(src->source_flags); + ++num_marked_sources; + } else { + IGMP_SOURCE_DONT_SEND(src->source_flags); + } + } + + return num_marked_sources; +} + +static void source_clear_send_flag(struct list *source_list) +{ + struct listnode *src_node; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) { + IGMP_SOURCE_DONT_SEND(src->source_flags); + } +} + +/* + Any source (*,G) is forwarded only if mode is EXCLUDE {empty} +*/ +static void group_exclude_fwd_anysrc_ifempty(struct gm_group *group) +{ + struct pim_interface *pim_ifp = group->interface->info; + + assert(group->group_filtermode_isexcl); + + if (listcount(group->group_source_list) < 1) { + igmp_anysource_forward_start(pim_ifp->pim, group); + } +} + +void igmp_source_free(struct gm_source *source) +{ + /* make sure there is no source timer running */ + assert(!source->t_source_timer); + + XFREE(MTYPE_PIM_IGMP_GROUP_SOURCE, source); +} + +/* + igmp_source_delete: stop forwarding, and delete the source + igmp_source_forward_stop: stop forwarding, but keep the source +*/ +void igmp_source_delete(struct gm_source *source) +{ + struct gm_group *group; + struct in_addr src; + + group = source->source_group; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_debug( + "Deleting IGMP source %s for group %s from interface %s c_oil ref_count %d", + source_str, group_str, group->interface->name, + source->source_channel_oil + ? source->source_channel_oil->oil_ref_count + : 0); + } + + source_timer_off(group, source); + igmp_source_forward_stop(source); + source->source_channel_oil = NULL; + + /* sanity check that forwarding has been disabled */ + if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_warn( + "%s: forwarding=ON(!) IGMP source %s for group %s from interface %s", + __func__, source_str, group_str, + group->interface->name); + /* warning only */ + } + + /* + notice that listnode_delete() can't be moved + into igmp_source_free() because the later is + called by list_delete_all_node() + */ + listnode_delete(group->group_source_list, source); + + src.s_addr = source->source_addr.s_addr; + igmp_source_free(source); + + /* Group source list is empty and current source is * then + *,G group going away so do not trigger start */ + if (group->group_filtermode_isexcl + && (listcount(group->group_source_list) != 0) + && src.s_addr != INADDR_ANY) { + group_exclude_fwd_anysrc_ifempty(group); + } +} + +static void source_delete_by_flag(struct list *source_list) +{ + struct listnode *src_node; + struct listnode *src_nextnode; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src)) + if (IGMP_SOURCE_TEST_DELETE(src->source_flags)) + igmp_source_delete(src); +} + +void igmp_source_delete_expired(struct list *source_list) +{ + struct listnode *src_node; + struct listnode *src_nextnode; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src)) + if (!src->t_source_timer) + igmp_source_delete(src); +} + +struct gm_source *igmp_find_source_by_addr(struct gm_group *group, + struct in_addr src_addr) +{ + struct listnode *src_node; + struct gm_source *src; + + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) + if (src_addr.s_addr == src->source_addr.s_addr) + return src; + + return 0; +} + +struct gm_source *igmp_get_source_by_addr(struct gm_group *group, + struct in_addr src_addr, bool *new) +{ + struct gm_source *src; + + if (new) + *new = false; + + src = igmp_find_source_by_addr(group, src_addr); + if (src) + return src; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", src_addr, source_str, + sizeof(source_str)); + zlog_debug( + "Creating new IGMP source %s for group %s on interface %s", + source_str, group_str, group->interface->name); + } + + src = XCALLOC(MTYPE_PIM_IGMP_GROUP_SOURCE, sizeof(*src)); + + if (new) + *new = true; + + src->t_source_timer = NULL; + src->source_group = group; /* back pointer */ + src->source_addr = src_addr; + src->source_creation = pim_time_monotonic_sec(); + src->source_flags = 0; + src->source_query_retransmit_count = 0; + src->source_channel_oil = NULL; + + listnode_add(group->group_source_list, src); + + return src; +} + +static void allow(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + struct gm_source *source; + struct gm_group *group; + int i; + + if (num_sources == 0) { + /* + RFC 3376: 3.1. Socket-State + If the requested filter mode is INCLUDE *and* the requested + source list is empty, then the entry corresponding to the + requested interface and multicast address is deleted if + present. If no such entry is present, the request is ignored. + So, deleting the group present. + */ + group = find_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + if (group->group_filtermode_isexcl) { + if (listcount(group->group_source_list) == 1) { + struct in_addr star = {.s_addr = INADDR_ANY}; + + source = igmp_find_source_by_addr(group, star); + if (source) + igmp_source_reset_gmi(group, source); + } + } else { + igmp_group_delete(group); + } + + return; + } + + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + + /* scan received sources */ + for (i = 0; i < num_sources; ++i) { + struct in_addr *src_addr; + + src_addr = sources + i; + + source = igmp_get_source_by_addr(group, *src_addr, NULL); + if (!source) + continue; + + /* + RFC 3376: 6.4.1. Reception of Current-State Records + + When receiving IS_IN reports for groups in EXCLUDE mode is + sources should be moved from set with (timers = 0) to set with + (timers > 0). + + igmp_source_reset_gmi() below, resetting the source timers to + GMI, accomplishes this. + */ + igmp_source_reset_gmi(group, source); + + } /* scan received sources */ +} + +void igmpv3_report_isin(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + on_trace(__func__, igmp->interface, from, group_addr, num_sources, + sources); + + allow(igmp, from, group_addr, num_sources, sources); +} + +static void isex_excl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + struct gm_source *source; + int i; + + /* EXCLUDE mode */ + assert(group->group_filtermode_isexcl); + + /* E.1: set deletion flag for known sources (X,Y) */ + source_mark_delete_flag(group); + + /* scan received sources (A) */ + for (i = 0; i < num_sources; ++i) { + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* E.2: lookup reported source from (A) in (X,Y) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (!new) { + /* E.3: if found, clear deletion flag: (X*A) or (Y*A) */ + IGMP_SOURCE_DONT_DELETE(source->source_flags); + } else { + /* E.4: if not found, create source with timer=GMI: + * (A-X-Y) */ + assert(!source->t_source_timer); /* timer == 0 */ + igmp_source_reset_gmi(group, source); + assert(source->t_source_timer); /* (A-X-Y) timer > 0 */ + } + + } /* scan received sources */ + + /* + * If we are in isexcl mode and num_sources == 0 + * than that means we have a *,g entry that + * needs to be handled + */ + if (group->group_filtermode_isexcl && num_sources == 0) { + struct in_addr star = {.s_addr = INADDR_ANY}; + source = igmp_find_source_by_addr(group, star); + if (source) { + IGMP_SOURCE_DONT_DELETE(source->source_flags); + igmp_source_reset_gmi(group, source); + } + } + + /* E.5: delete all sources marked with deletion flag: (X-A) and (Y-A) */ + source_delete_by_flag(group->group_source_list); +} + +static void isex_incl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int i; + + /* INCLUDE mode */ + assert(!group->group_filtermode_isexcl); + + /* I.1: set deletion flag for known sources (A) */ + source_mark_delete_flag(group); + + /* scan received sources (B) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* I.2: lookup reported source (B) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (!new) { + /* I.3: if found, clear deletion flag (A*B) */ + IGMP_SOURCE_DONT_DELETE(source->source_flags); + } else { + /* I.4: if not found, create source with timer=0 (B-A) + */ + assert(!source->t_source_timer); /* (B-A) timer=0 */ + } + + } /* scan received sources */ + + /* I.5: delete all sources marked with deletion flag (A-B) */ + source_delete_by_flag(group->group_source_list); + + group->group_filtermode_isexcl = 1; /* boolean=true */ + + assert(group->group_filtermode_isexcl); + + group_exclude_fwd_anysrc_ifempty(group); +} + +void igmpv3_report_isex(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources, int from_igmp_v2_report) +{ + struct interface *ifp = igmp->interface; + struct gm_group *group; + + on_trace(__func__, ifp, from, group_addr, num_sources, sources); + + if (pim_is_group_filtered(ifp->info, &group_addr)) + return; + + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + + /* So we can display how we learned the group in our show command output + */ + if (from_igmp_v2_report) + group->igmp_version = 2; + + if (group->group_filtermode_isexcl) { + /* EXCLUDE mode */ + isex_excl(group, num_sources, sources); + } else { + /* INCLUDE mode */ + isex_incl(group, num_sources, sources); + assert(group->group_filtermode_isexcl); + } + + assert(group->group_filtermode_isexcl); + + igmp_group_reset_gmi(group); +} + +static void toin_incl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend = listcount(group->group_source_list); + int i; + + /* Set SEND flag for all known sources (A) */ + source_mark_send_flag(group); + + /* Scan received sources (B) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* Lookup reported source (B) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (!new) { + /* If found, clear SEND flag (A*B) */ + IGMP_SOURCE_DONT_SEND(source->source_flags); + --num_sources_tosend; + } + + /* (B)=GMI */ + igmp_source_reset_gmi(group, source); + } + + /* Send sources marked with SEND flag: Q(G,A-B) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } +} + +static void toin_excl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend; + int i; + + /* Set SEND flag for X (sources with timer > 0) */ + num_sources_tosend = source_mark_send_flag_by_timer(group); + + /* Scan received sources (A) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* Lookup reported source (A) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (source->t_source_timer) { + /* If found and timer running, clear SEND flag + * (X*A) */ + IGMP_SOURCE_DONT_SEND(source->source_flags); + --num_sources_tosend; + } + + /* (A)=GMI */ + igmp_source_reset_gmi(group, source); + } + + /* Send sources marked with SEND flag: Q(G,X-A) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } + + /* Send Q(G) */ + group_query_send(group); +} + +void igmpv3_report_toin(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + struct interface *ifp = igmp->interface; + struct gm_group *group; + + on_trace(__func__, ifp, from, group_addr, num_sources, sources); + + /* + * If the requested filter mode is INCLUDE *and* the requested source + * list is empty, then the entry corresponding to the requested + * interface and multicast address is deleted if present. If no such + * entry is present, the request is ignored. + */ + if (num_sources) { + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + } else { + group = find_group_by_addr(igmp, group_addr); + if (!group) + return; + } + + if (group->group_filtermode_isexcl) { + /* EXCLUDE mode */ + toin_excl(group, num_sources, sources); + } else { + /* INCLUDE mode */ + toin_incl(group, num_sources, sources); + } +} + +static void toex_incl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend = 0; + int i; + + assert(!group->group_filtermode_isexcl); + + /* Set DELETE flag for all known sources (A) */ + source_mark_delete_flag(group); + + /* Clear off SEND flag from all known sources (A) */ + source_clear_send_flag(group->group_source_list); + + /* Scan received sources (B) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* Lookup reported source (B) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!new) { + /* If found, clear deletion flag: (A*B) */ + IGMP_SOURCE_DONT_DELETE(source->source_flags); + /* and set SEND flag (A*B) */ + IGMP_SOURCE_DO_SEND(source->source_flags); + ++num_sources_tosend; + } + + } /* Scan received sources (B) */ + + group->group_filtermode_isexcl = 1; /* boolean=true */ + + /* Delete all sources marked with DELETE flag (A-B) */ + source_delete_by_flag(group->group_source_list); + + /* Send sources marked with SEND flag: Q(G,A*B) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } + + assert(group->group_filtermode_isexcl); + + group_exclude_fwd_anysrc_ifempty(group); +} + +static void toex_excl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend = 0; + int i; + + /* set DELETE flag for all known sources (X,Y) */ + source_mark_delete_flag(group); + + /* clear off SEND flag from all known sources (X,Y) */ + source_clear_send_flag(group->group_source_list); + + if (num_sources == 0) { + struct gm_source *source; + struct in_addr any = {.s_addr = INADDR_ANY}; + + source = igmp_find_source_by_addr(group, any); + if (source) + IGMP_SOURCE_DONT_DELETE(source->source_flags); + } + + /* scan received sources (A) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* lookup reported source (A) in known sources (X,Y) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (!new) { + /* if found, clear off DELETE flag from reported source + * (A) */ + IGMP_SOURCE_DONT_DELETE(source->source_flags); + } else { + /* if not found, create source with Group Timer: + * (A-X-Y)=Group Timer */ + long group_timer_msec; + + assert(!source->t_source_timer); /* timer == 0 */ + group_timer_msec = igmp_group_timer_remain_msec(group); + igmp_source_timer_on(group, source, group_timer_msec); + assert(source->t_source_timer); /* (A-X-Y) timer > 0 */ + + /* make sure source is created with DELETE flag unset */ + assert(!IGMP_SOURCE_TEST_DELETE(source->source_flags)); + } + + /* make sure reported source has DELETE flag unset */ + assert(!IGMP_SOURCE_TEST_DELETE(source->source_flags)); + + if (source->t_source_timer) { + /* if source timer>0 mark SEND flag: Q(G,A-Y) */ + IGMP_SOURCE_DO_SEND(source->source_flags); + ++num_sources_tosend; + } + + } /* scan received sources (A) */ + + /* + delete all sources marked with DELETE flag: + Delete (X-A) + Delete (Y-A) + */ + source_delete_by_flag(group->group_source_list); + + /* send sources marked with SEND flag: Q(G,A-Y) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } +} + +void igmpv3_report_toex(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + struct interface *ifp = igmp->interface; + struct gm_group *group; + + on_trace(__func__, ifp, from, group_addr, num_sources, sources); + + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + + if (group->group_filtermode_isexcl) { + /* EXCLUDE mode */ + toex_excl(group, num_sources, sources); + } else { + /* INCLUDE mode */ + toex_incl(group, num_sources, sources); + assert(group->group_filtermode_isexcl); + } + assert(group->group_filtermode_isexcl); + + /* Group Timer=GMI */ + igmp_group_reset_gmi(group); +} + +void igmpv3_report_allow(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + on_trace(__func__, igmp->interface, from, group_addr, num_sources, + sources); + + allow(igmp, from, group_addr, num_sources, sources); +} + +static void igmp_send_query_group(struct gm_group *group, char *query_buf, + size_t query_buf_size, int num_sources, + int s_flag) +{ + struct interface *ifp = group->interface; + struct pim_interface *pim_ifp = ifp->info; + struct gm_sock *igmp; + struct listnode *sock_node; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) { + igmp_send_query( + pim_ifp->igmp_version, group, query_buf, query_buf_size, + num_sources, group->group_addr, group->group_addr, + pim_ifp->gm_specific_query_max_response_time_dsec, + s_flag, igmp); + } +} + +/* + RFC3376: 6.6.3.1. Building and Sending Group Specific Queries + + When transmitting a group specific query, if the group timer is + larger than LMQT, the "Suppress Router-Side Processing" bit is set + in the query message. +*/ +static void group_retransmit_group(struct gm_group *group) +{ + struct pim_interface *pim_ifp; + long lmqc; /* Last Member Query Count */ + long lmqi_msec; /* Last Member Query Interval */ + long lmqt_msec; /* Last Member Query Time */ + int s_flag; + int query_buf_size; + + pim_ifp = group->interface->info; + + if (pim_ifp->igmp_version == 3) { + query_buf_size = PIM_IGMP_BUFSIZE_WRITE; + } else { + query_buf_size = IGMP_V12_MSG_SIZE; + } + + char query_buf[query_buf_size]; + + lmqc = pim_ifp->gm_last_member_query_count; + lmqi_msec = 100 * pim_ifp->gm_specific_query_max_response_time_dsec; + lmqt_msec = lmqc * lmqi_msec; + + /* + RFC3376: 6.6.3.1. Building and Sending Group Specific Queries + + When transmitting a group specific query, if the group timer is + larger than LMQT, the "Suppress Router-Side Processing" bit is set + in the query message. + */ + s_flag = igmp_group_timer_remain_msec(group) > lmqt_msec; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "retransmit_group_specific_query: group %s on %s: s_flag=%d count=%d", + group_str, group->interface->name, s_flag, + group->group_specific_query_retransmit_count); + } + + /* + RFC3376: 4.1.12. IP Destination Addresses for Queries + + Group-Specific and Group-and-Source-Specific Queries are sent with + an IP destination address equal to the multicast address of + interest. + */ + + igmp_send_query_group(group, query_buf, sizeof(query_buf), 0, s_flag); +} + +/* + RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries + + When building a group and source specific query for a group G, two + separate query messages are sent for the group. The first one has + the "Suppress Router-Side Processing" bit set and contains all the + sources with retransmission state and timers greater than LMQT. The + second has the "Suppress Router-Side Processing" bit clear and + contains all the sources with retransmission state and timers lower + or equal to LMQT. If either of the two calculated messages does not + contain any sources, then its transmission is suppressed. + */ +static int group_retransmit_sources(struct gm_group *group, + int send_with_sflag_set) +{ + struct pim_interface *pim_ifp; + long lmqc; /* Last Member Query Count */ + long lmqi_msec; /* Last Member Query Interval */ + long lmqt_msec; /* Last Member Query Time */ + char query_buf1[PIM_IGMP_BUFSIZE_WRITE]; /* 1 = with s_flag set */ + char query_buf2[PIM_IGMP_BUFSIZE_WRITE]; /* 2 = with s_flag clear */ + int query_buf1_max_sources; + int query_buf2_max_sources; + struct in_addr *source_addr1; + struct in_addr *source_addr2; + int num_sources_tosend1; + int num_sources_tosend2; + struct listnode *src_node; + struct gm_source *src; + int num_retransmit_sources_left = 0; + + source_addr1 = (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET); + source_addr2 = (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET); + + pim_ifp = group->interface->info; + + lmqc = pim_ifp->gm_last_member_query_count; + lmqi_msec = 100 * pim_ifp->gm_specific_query_max_response_time_dsec; + lmqt_msec = lmqc * lmqi_msec; + + /* Scan all group sources */ + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { + + /* Source has retransmission state? */ + if (src->source_query_retransmit_count < 1) + continue; + + if (--src->source_query_retransmit_count > 0) { + ++num_retransmit_sources_left; + } + + /* Copy source address into appropriate query buffer */ + if (igmp_source_timer_remain_msec(src) > lmqt_msec) { + *source_addr1 = src->source_addr; + ++source_addr1; + } else { + *source_addr2 = src->source_addr; + ++source_addr2; + } + } + + num_sources_tosend1 = + source_addr1 + - (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET); + num_sources_tosend2 = + source_addr2 + - (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "retransmit_grp&src_specific_query: group %s on %s: srcs_with_sflag=%d srcs_wo_sflag=%d will_send_sflag=%d retransmit_src_left=%d", + group_str, group->interface->name, num_sources_tosend1, + num_sources_tosend2, send_with_sflag_set, + num_retransmit_sources_left); + } + + if (num_sources_tosend1 > 0) { + /* + Send group-and-source-specific query with s_flag set and all + sources with timers greater than LMQT. + */ + + if (send_with_sflag_set) { + + query_buf1_max_sources = + (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) + >> 2; + if (num_sources_tosend1 > query_buf1_max_sources) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, + group_str, sizeof(group_str)); + zlog_warn( + "%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%zu (max_sources=%d)", + __func__, group_str, + group->interface->name, + num_sources_tosend1, sizeof(query_buf1), + query_buf1_max_sources); + } else { + /* + RFC3376: 4.1.12. IP Destination Addresses for + Queries + + Group-Specific and Group-and-Source-Specific + Queries are sent with + an IP destination address equal to the + multicast address of + interest. + */ + + igmp_send_query_group( + group, query_buf1, sizeof(query_buf1), + num_sources_tosend1, 1 /* s_flag */); + } + + } /* send_with_sflag_set */ + } + + if (num_sources_tosend2 > 0) { + /* + Send group-and-source-specific query with s_flag clear and all + sources with timers lower or equal to LMQT. + */ + + query_buf2_max_sources = + (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2; + if (num_sources_tosend2 > query_buf2_max_sources) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_warn( + "%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%zu (max_sources=%d)", + __func__, group_str, group->interface->name, + num_sources_tosend2, sizeof(query_buf2), + query_buf2_max_sources); + } else { + /* + RFC3376: 4.1.12. IP Destination Addresses for Queries + + Group-Specific and Group-and-Source-Specific Queries + are sent with + an IP destination address equal to the multicast + address of + interest. + */ + + igmp_send_query_group( + group, query_buf2, sizeof(query_buf2), + num_sources_tosend2, 0 /* s_flag */); + } + } + + return num_retransmit_sources_left; +} + +static void igmp_group_retransmit(struct event *t) +{ + struct gm_group *group; + int num_retransmit_sources_left; + int send_with_sflag_set; /* boolean */ + + group = EVENT_ARG(t); + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug("group_retransmit_timer: group %s on %s", group_str, + group->interface->name); + } + + /* Retransmit group-specific queries? (RFC3376: 6.6.3.1) */ + if (group->group_specific_query_retransmit_count > 0) { + + /* Retransmit group-specific queries (RFC3376: 6.6.3.1) */ + group_retransmit_group(group); + --group->group_specific_query_retransmit_count; + + /* + RFC3376: 6.6.3.2 + If a group specific query is scheduled to be transmitted at + the + same time as a group and source specific query for the same + group, + then transmission of the group and source specific message + with the + "Suppress Router-Side Processing" bit set may be suppressed. + */ + send_with_sflag_set = 0; /* boolean=false */ + } else { + send_with_sflag_set = 1; /* boolean=true */ + } + + /* Retransmit group-and-source-specific queries (RFC3376: 6.6.3.2) */ + num_retransmit_sources_left = + group_retransmit_sources(group, send_with_sflag_set); + + /* + Keep group retransmit timer running if there is any retransmit + counter pending + */ + if ((num_retransmit_sources_left > 0) + || (group->group_specific_query_retransmit_count > 0)) { + group_retransmit_timer_on(group); + } +} + +/* + group_retransmit_timer_on: + if group retransmit timer isn't running, starts it; + otherwise, do nothing +*/ +static void group_retransmit_timer_on(struct gm_group *group) +{ + struct pim_interface *pim_ifp; + long lmqi_msec; /* Last Member Query Interval */ + + /* if group retransmit timer is running, do nothing */ + if (group->t_group_query_retransmit_timer) { + return; + } + + pim_ifp = group->interface->info; + + lmqi_msec = 100 * pim_ifp->gm_specific_query_max_response_time_dsec; + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "Scheduling %ld.%03ld sec retransmit timer for group %s on %s", + lmqi_msec / 1000, lmqi_msec % 1000, group_str, + group->interface->name); + } + + event_add_timer_msec(router->master, igmp_group_retransmit, group, + lmqi_msec, &group->t_group_query_retransmit_timer); +} + +static long igmp_group_timer_remain_msec(struct gm_group *group) +{ + return pim_time_timer_remain_msec(group->t_group_timer); +} + +static long igmp_source_timer_remain_msec(struct gm_source *source) +{ + return pim_time_timer_remain_msec(source->t_source_timer); +} + +/* + RFC3376: 6.6.3.1. Building and Sending Group Specific Queries +*/ +static void group_query_send(struct gm_group *group) +{ + struct pim_interface *pim_ifp; + long lmqc; /* Last Member Query Count */ + + pim_ifp = group->interface->info; + lmqc = pim_ifp->gm_last_member_query_count; + + /* lower group timer to lmqt */ + igmp_group_timer_lower_to_lmqt(group); + + /* reset retransmission counter */ + group->group_specific_query_retransmit_count = lmqc; + + /* immediately send group specific query (decrease retransmit counter by + * 1)*/ + group_retransmit_group(group); + + /* make sure group retransmit timer is running */ + group_retransmit_timer_on(group); +} + +/* + RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries +*/ +static void source_query_send_by_flag(struct gm_group *group, + int num_sources_tosend) +{ + struct pim_interface *pim_ifp; + struct listnode *src_node; + struct gm_source *src; + long lmqc; /* Last Member Query Count */ + long lmqi_msec; /* Last Member Query Interval */ + long lmqt_msec; /* Last Member Query Time */ + + assert(num_sources_tosend > 0); + + pim_ifp = group->interface->info; + + lmqc = pim_ifp->gm_last_member_query_count; + lmqi_msec = 100 * pim_ifp->gm_specific_query_max_response_time_dsec; + lmqt_msec = lmqc * lmqi_msec; + + /* + RFC3376: 6.6.3.2. Building and Sending Group and Source Specific + Queries + + (...) for each of the sources in X of group G, with source timer + larger + than LMQT: + o Set number of retransmissions for each source to [Last Member + Query Count]. + o Lower source timer to LMQT. + */ + for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) { + if (IGMP_SOURCE_TEST_SEND(src->source_flags)) { + /* source "src" in X of group G */ + if (igmp_source_timer_remain_msec(src) > lmqt_msec) { + src->source_query_retransmit_count = lmqc; + igmp_source_timer_lower_to_lmqt(src); + } + } + } + + /* send group-and-source specific queries */ + group_retransmit_sources(group, 1 /* send_with_sflag_set=true */); + + /* make sure group retransmit timer is running */ + group_retransmit_timer_on(group); +} + +static void block_excl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend = 0; + int i; + + /* 1. clear off SEND flag from all known sources (X,Y) */ + source_clear_send_flag(group->group_source_list); + + /* 2. scan received sources (A) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + bool new; + + src_addr = sources + i; + + /* lookup reported source (A) in known sources (X,Y) */ + source = igmp_get_source_by_addr(group, *src_addr, &new); + if (!source) + continue; + + if (new) { + /* 3: if not found, create source with Group Timer: + * (A-X-Y)=Group Timer */ + long group_timer_msec; + + assert(!source->t_source_timer); /* timer == 0 */ + group_timer_msec = igmp_group_timer_remain_msec(group); + igmp_source_timer_on(group, source, group_timer_msec); + assert(source->t_source_timer); /* (A-X-Y) timer > 0 */ + } + + if (source->t_source_timer) { + /* 4. if source timer>0 mark SEND flag: Q(G,A-Y) */ + IGMP_SOURCE_DO_SEND(source->source_flags); + ++num_sources_tosend; + } + } + + /* 5. send sources marked with SEND flag: Q(G,A-Y) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } +} + +static void block_incl(struct gm_group *group, int num_sources, + struct in_addr *sources) +{ + int num_sources_tosend = 0; + int i; + + /* 1. clear off SEND flag from all known sources (B) */ + source_clear_send_flag(group->group_source_list); + + /* 2. scan received sources (A) */ + for (i = 0; i < num_sources; ++i) { + struct gm_source *source; + struct in_addr *src_addr; + + src_addr = sources + i; + + /* lookup reported source (A) in known sources (B) */ + source = igmp_find_source_by_addr(group, *src_addr); + if (source) { + /* 3. if found (A*B), mark SEND flag: Q(G,A*B) */ + IGMP_SOURCE_DO_SEND(source->source_flags); + ++num_sources_tosend; + } + } + + /* 4. send sources marked with SEND flag: Q(G,A*B) */ + if (num_sources_tosend > 0) { + source_query_send_by_flag(group, num_sources_tosend); + } +} + +void igmpv3_report_block(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources) +{ + struct interface *ifp = igmp->interface; + struct gm_group *group; + + on_trace(__func__, ifp, from, group_addr, num_sources, sources); + + /* non-existent group is created as INCLUDE {empty} */ + group = igmp_add_group_by_addr(igmp, group_addr); + if (!group) { + return; + } + + if (group->group_filtermode_isexcl) { + /* EXCLUDE mode */ + block_excl(group, num_sources, sources); + } else { + /* INCLUDE mode */ + block_incl(group, num_sources, sources); + } +} + +void igmp_group_timer_lower_to_lmqt(struct gm_group *group) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + char *ifname; + int lmqi_dsec; /* Last Member Query Interval */ + int lmqc; /* Last Member Query Count */ + int lmqt_msec; /* Last Member Query Time */ + + /* + RFC 3376: 6.2.2. Definition of Group Timers + + The group timer is only used when a group is in EXCLUDE mode and + it represents the time for the *filter-mode* of the group to + expire and switch to INCLUDE mode. + */ + if (!group->group_filtermode_isexcl) { + return; + } + + ifp = group->interface; + pim_ifp = ifp->info; + ifname = ifp->name; + + lmqi_dsec = pim_ifp->gm_specific_query_max_response_time_dsec; + lmqc = pim_ifp->gm_last_member_query_count; + lmqt_msec = PIM_IGMP_LMQT_MSEC( + lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "%s: group %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec", + __func__, group_str, ifname, lmqc, lmqi_dsec, + lmqt_msec); + } + + assert(group->group_filtermode_isexcl); + + igmp_group_timer_on(group, lmqt_msec, ifname); +} + +void igmp_source_timer_lower_to_lmqt(struct gm_source *source) +{ + struct gm_group *group; + struct interface *ifp; + struct pim_interface *pim_ifp; + char *ifname; + int lmqi_dsec; /* Last Member Query Interval */ + int lmqc; /* Last Member Query Count */ + int lmqt_msec; /* Last Member Query Time */ + + group = source->source_group; + ifp = group->interface; + pim_ifp = ifp->info; + ifname = ifp->name; + + lmqi_dsec = pim_ifp->gm_specific_query_max_response_time_dsec; + lmqc = pim_ifp->gm_last_member_query_count; + lmqt_msec = PIM_IGMP_LMQT_MSEC( + lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ + + if (PIM_DEBUG_GM_TRACE) { + char group_str[INET_ADDRSTRLEN]; + char source_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group->group_addr, group_str, + sizeof(group_str)); + pim_inet4_dump("", source->source_addr, source_str, + sizeof(source_str)); + zlog_debug( + "%s: group %s source %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec", + __func__, group_str, source_str, ifname, lmqc, + lmqi_dsec, lmqt_msec); + } + + igmp_source_timer_on(group, source, lmqt_msec); +} + +void igmp_v3_send_query(struct gm_group *group, int fd, const char *ifname, + char *query_buf, int query_buf_size, int num_sources, + struct in_addr dst_addr, struct in_addr group_addr, + int query_max_response_time_dsec, uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval) +{ + ssize_t msg_size; + uint8_t max_resp_code; + uint8_t qqic; + ssize_t sent; + struct sockaddr_in to; + socklen_t tolen; + uint16_t checksum; + + assert(num_sources >= 0); + + msg_size = IGMP_V3_SOURCES_OFFSET + (num_sources << 2); + if (msg_size > query_buf_size) { + flog_err( + EC_LIB_DEVELOPMENT, + "%s %s: unable to send: msg_size=%zd larger than query_buf_size=%d", + __FILE__, __func__, msg_size, query_buf_size); + return; + } + + s_flag = PIM_FORCE_BOOLEAN(s_flag); + assert((s_flag == 0) || (s_flag == 1)); + + max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec); + qqic = igmp_msg_encode16to8(querier_query_interval); + + /* + RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) + + If non-zero, the QRV field contains the [Robustness Variable] + value used by the querier, i.e., the sender of the Query. If the + querier's [Robustness Variable] exceeds 7, the maximum value of + the QRV field, the QRV is set to zero. + */ + if (querier_robustness_variable > 7) { + querier_robustness_variable = 0; + } + + query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; + query_buf[1] = max_resp_code; + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = + 0; /* for computing checksum */ + memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr)); + + query_buf[8] = (s_flag << 3) | querier_robustness_variable; + query_buf[9] = qqic; + *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = + htons(num_sources); + + checksum = in_cksum(query_buf, msg_size); + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; + + if (PIM_DEBUG_GM_PACKETS) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_debug( + "Send IGMPv3 query to %s on %s for group %s, sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x", + dst_str, ifname, group_str, num_sources, msg_size, + s_flag, querier_robustness_variable, + querier_query_interval, qqic); + } + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = dst_addr; + tolen = sizeof(to); + + sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + if (sent != (ssize_t)msg_size) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + if (sent < 0) { + zlog_warn( + "Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", + dst_str, ifname, group_str, msg_size, errno, + safe_strerror(errno)); + } else { + zlog_warn( + "Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", + dst_str, ifname, group_str, msg_size, sent); + } + return; + } + + /* + s_flag sanity test: s_flag must be set for general queries + + RFC 3376: 6.6.1. Timer Updates + + When a router sends or receives a query with a clear Suppress + Router-Side Processing flag, it must update its timers to reflect + the correct timeout values for the group or sources being queried. + + General queries don't trigger timer update. + */ + if (!s_flag) { + /* general query? */ + if (group_addr.s_addr == INADDR_ANY) { + char dst_str[INET_ADDRSTRLEN]; + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", dst_addr, dst_str, + sizeof(dst_str)); + pim_inet4_dump("", group_addr, group_str, + sizeof(group_str)); + zlog_warn( + "%s: to %s on %s: group=%s sources=%d: s_flag is clear for general query!", + __func__, dst_str, ifname, group_str, + num_sources); + } + } +} + +void igmp_v3_recv_query(struct gm_sock *igmp, const char *from_str, + char *igmp_msg) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct in_addr group_addr; + uint8_t resv_s_qrv = 0; + uint8_t s_flag = 0; + uint8_t qrv = 0; + int i; + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + ifp = igmp->interface; + pim_ifp = ifp->info; + + /* + * RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) + * + * Routers adopt the QRV value from the most recently received Query + * as their own [Robustness Variable] value, unless that most + * recently received QRV was zero, in which case the receivers use + * the default [Robustness Variable] value specified in section 8.1 + * or a statically configured value. + */ + resv_s_qrv = igmp_msg[8]; + qrv = 7 & resv_s_qrv; + igmp->querier_robustness_variable = + qrv ? qrv : pim_ifp->gm_default_robustness_variable; + + /* + * RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) + * + * Multicast routers that are not the current querier adopt the QQI + * value from the most recently received Query as their own [Query + * Interval] value, unless that most recently received QQI was zero, + * in which case the receiving routers use the default. + */ + if (igmp->t_other_querier_timer) { + /* other querier present */ + uint8_t qqic; + uint16_t qqi; + qqic = igmp_msg[9]; + qqi = igmp_msg_decode8to16(qqic); + igmp->querier_query_interval = + qqi ? qqi : pim_ifp->gm_default_query_interval; + + if (PIM_DEBUG_GM_TRACE) { + char ifaddr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", igmp->ifaddr, ifaddr_str, + sizeof(ifaddr_str)); + zlog_debug( + "Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)", + ifaddr_str, + qqi ? "recv-non-default" : "default", + igmp->querier_query_interval, qqic, from_str); + } + } + + /* + * RFC 3376: 6.6.1. Timer Updates + * + * When a router sends or receives a query with a clear Suppress + * Router-Side Processing flag, it must update its timers to reflect + * the correct timeout values for the group or sources being queried. + * + * General queries don't trigger timer update. + */ + s_flag = (1 << 3) & resv_s_qrv; + + if (!s_flag) { + /* s_flag is clear */ + + if (group_addr.s_addr == INADDR_ANY) { + /* this is a general query */ + /* log that general query should have the s_flag set */ + zlog_warn( + "General IGMP query v3 from %s on %s: Suppress Router-Side Processing flag is clear", + from_str, ifp->name); + } else { + struct gm_group *group; + + /* this is a non-general query: perform timer updates */ + + group = find_group_by_addr(igmp, group_addr); + if (group) { + int recv_num_sources = ntohs(*( + uint16_t + *)(igmp_msg + + IGMP_V3_NUMSOURCES_OFFSET)); + + /* + * RFC 3376: 6.6.1. Timer Updates + * Query Q(G,A): Source Timer for sources in A + * are lowered to LMQT + * Query Q(G): Group Timer is lowered to LMQT + */ + if (recv_num_sources < 1) { + /* Query Q(G): Group Timer is lowered to + * LMQT */ + + igmp_group_timer_lower_to_lmqt(group); + } else { + /* Query Q(G,A): Source Timer for + * sources in A are lowered to LMQT */ + + /* Scan sources in query and lower their + * timers to LMQT */ + struct in_addr *sources = + (struct in_addr + *)(igmp_msg + + IGMP_V3_SOURCES_OFFSET); + for (i = 0; i < recv_num_sources; ++i) { + struct in_addr src_addr; + struct gm_source *src; + memcpy(&src_addr, sources + i, + sizeof(struct in_addr)); + src = igmp_find_source_by_addr( + group, src_addr); + if (src) { + igmp_source_timer_lower_to_lmqt( + src); + } + } + } + } else { + char group_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", group_addr, + group_str, sizeof(group_str)); + zlog_warn( + "IGMP query v3 from %s on %s: could not find group %s for timer update", + from_str, ifp->name, group_str); + } + } + } /* s_flag is clear: timer updates */ +} + +static bool igmp_pkt_grp_addr_ok(struct interface *ifp, const char *from_str, + struct in_addr grp, int rec_type) +{ + struct pim_interface *pim_ifp; + struct in_addr grp_addr; + + pim_ifp = ifp->info; + + /* determine filtering status for group */ + if (pim_is_group_filtered(pim_ifp, &grp)) { + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Filtering IGMPv3 group record %pI4 from %s on %s per prefix-list %s", + &grp.s_addr, from_str, ifp->name, + pim_ifp->boundary_oil_plist); + } + return false; + } + + /* + * If we receive a igmp report with the group in 224.0.0.0/24 + * then we should ignore it + */ + + grp_addr.s_addr = ntohl(grp.s_addr); + + if (pim_is_group_224_0_0_0_24(grp_addr)) { + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Ignoring IGMPv3 group record %pI4 from %s on %s group range falls in 224.0.0.0/24", + &grp.s_addr, from_str, ifp->name); + } + return false; + } + + /* + * RFC 4604 + * section 2.2.1 + * EXCLUDE mode does not apply to SSM addresses, and an SSM-aware router + * will ignore MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE requests in + * the SSM range. + */ + if (pim_is_grp_ssm(pim_ifp->pim, grp)) { + switch (rec_type) { + case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE: + case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE: + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Ignoring IGMPv3 group record %pI4 from %s on %s exclude mode in SSM range", + &grp.s_addr, from_str, ifp->name); + } + return false; + } + } + + return true; +} + +int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from, + const char *from_str, char *igmp_msg, int igmp_msg_len) +{ + int num_groups; + uint8_t *group_record; + uint8_t *report_pastend = (uint8_t *)igmp_msg + igmp_msg_len; + struct interface *ifp = igmp->interface; + struct pim_interface *pim_ifp = ifp->info; + int i; + + if (igmp->mtrace_only) + return 0; + + if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) { + zlog_warn( + "Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d", + from_str, ifp->name, igmp_msg_len, + IGMP_V3_MSG_MIN_SIZE); + return -1; + } + + if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) { + zlog_warn( + "Recv IGMPv3 report from %s on %s with invalid checksum", + from_str, ifp->name); + return -1; + } + + /* Collecting IGMP Rx stats */ + igmp->igmp_stats.report_v3++; + + if (pim_ifp->igmp_version == 2) { + zlog_warn( + "Received Version 3 packet but interface: %s is configured for version 2", + ifp->name); + return -1; + } + + num_groups = ntohs( + *(uint16_t *)(igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET)); + if (num_groups < 1) { + zlog_warn( + "Recv IGMP report v3 from %s on %s: missing group records", + from_str, ifp->name); + return -1; + } + + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Recv IGMP report v3 from %s on %s: size=%d groups=%d", + from_str, ifp->name, igmp_msg_len, num_groups); + } + + group_record = (uint8_t *)igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET; + + /* Scan groups */ + for (i = 0; i < num_groups; ++i) { + struct in_addr rec_group; + uint8_t *sources; + uint8_t *src; + int rec_type; + int rec_auxdatalen; + int rec_num_sources; + int j; + + if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) + > report_pastend) { + zlog_warn( + "Recv IGMP report v3 from %s on %s: group record beyond report end", + from_str, ifp->name); + return -1; + } + + rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET]; + rec_auxdatalen = + group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET]; + rec_num_sources = ntohs(*( + uint16_t *)(group_record + + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET)); + + memcpy(&rec_group, + group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, + sizeof(struct in_addr)); + + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + " Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%pI4", + from_str, ifp->name, i, rec_type, + rec_auxdatalen, rec_num_sources, + &rec_group); + } + + /* Scan sources */ + + sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET; + + for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) { + + if ((src + 4) > report_pastend) { + zlog_warn( + "Recv IGMP report v3 from %s on %s: group source beyond report end", + from_str, ifp->name); + return -1; + } + + if (PIM_DEBUG_GM_PACKETS) { + char src_str[200]; + + if (!inet_ntop(AF_INET, src, src_str, + sizeof(src_str))) + snprintf(src_str, sizeof(src_str), + ""); + + zlog_debug( + " Recv IGMP report v3 from %s on %s: record=%d group=%pI4 source=%s", + from_str, ifp->name, i, + &rec_group, src_str); + } + } /* for (sources) */ + + + if (igmp_pkt_grp_addr_ok(ifp, from_str, rec_group, rec_type)) + switch (rec_type) { + case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE: + igmpv3_report_isin(igmp, from, rec_group, + rec_num_sources, + (struct in_addr *)sources); + break; + case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE: + igmpv3_report_isex( + igmp, from, rec_group, rec_num_sources, + (struct in_addr *)sources, 0); + break; + case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE: + igmpv3_report_toin(igmp, from, rec_group, + rec_num_sources, + (struct in_addr *)sources); + break; + case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE: + igmpv3_report_toex(igmp, from, rec_group, + rec_num_sources, + (struct in_addr *)sources); + break; + case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES: + igmpv3_report_allow(igmp, from, rec_group, + rec_num_sources, + (struct in_addr *)sources); + break; + case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES: + igmpv3_report_block(igmp, from, rec_group, + rec_num_sources, + (struct in_addr *)sources); + break; + default: + zlog_warn( + "Recv IGMP report v3 from %s on %s: unknown record type: type=%d", + from_str, ifp->name, rec_type); + } + + group_record += + 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2); + + } /* for (group records) */ + + return 0; +} diff --git a/pimd/pim_igmpv3.h b/pimd/pim_igmpv3.h new file mode 100644 index 0000000..43c7df4 --- /dev/null +++ b/pimd/pim_igmpv3.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_IGMPV3_H +#define PIM_IGMPV3_H + +#include +#include "if.h" + +#include "pim_igmp.h" + +#define IGMP_V3_CHECKSUM_OFFSET (2) +#define IGMP_V3_REPORT_NUMGROUPS_OFFSET (6) +#define IGMP_V3_REPORT_GROUPPRECORD_OFFSET (8) +#define IGMP_V3_NUMSOURCES_OFFSET (10) +#define IGMP_V3_SOURCES_OFFSET (12) + +#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE (1) +#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE (2) +#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3) +#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4) +#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES (5) +#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES (6) + +/* GMI: Group Membership Interval */ +#define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec)) + +/* OQPI: Other Querier Present Interval */ +#define PIM_IGMP_OQPI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * ((qri_dsec) >> 1)) + +/* SQI: Startup Query Interval */ +#define PIM_IGMP_SQI(qi) (((qi) < 4) ? 1 : ((qi) >> 2)) + +/* LMQT: Last Member Query Time */ +#define PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc) ((lmqc) * (100 * (lmqi_dsec))) + +/* OHPI: Older Host Present Interval */ +#define PIM_IGMP_OHPI_DSEC(qrv,qqi,qri_dsec) ((qrv) * (10 * (qqi)) + (qri_dsec)) + +#if PIM_IPV == 4 +void igmp_group_reset_gmi(struct gm_group *group); +void igmp_source_reset_gmi(struct gm_group *group, struct gm_source *source); + +void igmp_source_free(struct gm_source *source); +void igmp_source_delete(struct gm_source *source); +void igmp_source_delete_expired(struct list *source_list); + +void igmpv3_report_isin(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources); +void igmpv3_report_isex(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources, int from_igmp_v2_report); +void igmpv3_report_toin(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources); +void igmpv3_report_toex(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources); +void igmpv3_report_allow(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources); +void igmpv3_report_block(struct gm_sock *igmp, struct in_addr from, + struct in_addr group_addr, int num_sources, + struct in_addr *sources); + +void igmp_group_timer_lower_to_lmqt(struct gm_group *group); +void igmp_source_timer_lower_to_lmqt(struct gm_source *source); + +struct gm_source *igmp_find_source_by_addr(struct gm_group *group, + struct in_addr src_addr); + +void igmp_v3_send_query(struct gm_group *group, int fd, const char *ifname, + char *query_buf, int query_buf_size, int num_sources, + struct in_addr dst_addr, struct in_addr group_addr, + int query_max_response_time_dsec, uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval); + +void igmp_v3_recv_query(struct gm_sock *igmp, const char *from_str, + char *igmp_msg); + +int igmp_v3_recv_report(struct gm_sock *igmp, struct in_addr from, + const char *from_str, char *igmp_msg, int igmp_msg_len); + +#else /* PIM_IPV != 4 */ +static inline void igmp_group_reset_gmi(struct gm_group *group) +{ +} + + +static inline void igmp_source_reset_gmi(struct gm_group *group, + struct gm_source *source) +{ +} +#endif + +#endif /* PIM_IGMPV3_H */ diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c new file mode 100644 index 0000000..b3410d1 --- /dev/null +++ b/pimd/pim_instance.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRR - PIM Instance + * Copyright (C) 2017 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "hash.h" +#include "vrf.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_ssm.h" +#include "pim_rpf.h" +#include "pim_rp.h" +#include "pim_mroute.h" +#include "pim_oil.h" +#include "pim_static.h" +#include "pim_ssmpingd.h" +#include "pim_vty.h" +#include "pim_bsm.h" +#include "pim_mlag.h" +#include "pim_sock.h" + +static void pim_instance_terminate(struct pim_instance *pim) +{ + pim_vxlan_exit(pim); + + if (pim->ssm_info) { + pim_ssm_terminate(pim->ssm_info); + pim->ssm_info = NULL; + } + + if (pim->static_routes) + list_delete(&pim->static_routes); + + pim_instance_mlag_terminate(pim); + + pim_upstream_terminate(pim); + + pim_rp_free(pim); + + pim_bsm_proc_free(pim); + + /* Traverse and cleanup rpf_hash */ + hash_clean_and_free(&pim->rpf_hash, (void *)pim_rp_list_hash_clean); + + pim_if_terminate(pim); + + pim_oil_terminate(pim); + + pim_msdp_exit(pim); + + close(pim->reg_sock); + + pim_mroute_socket_disable(pim); + + XFREE(MTYPE_PIM_PLIST_NAME, pim->spt.plist); + XFREE(MTYPE_PIM_PLIST_NAME, pim->register_plist); + + pim->vrf = NULL; + XFREE(MTYPE_PIM_PIM_INSTANCE, pim); +} + +static struct pim_instance *pim_instance_init(struct vrf *vrf) +{ + struct pim_instance *pim; + char hash_name[64]; + + pim = XCALLOC(MTYPE_PIM_PIM_INSTANCE, sizeof(struct pim_instance)); + + pim_if_init(pim); + + pim->mcast_if_count = 0; + pim->keep_alive_time = PIM_KEEPALIVE_PERIOD; + pim->rp_keep_alive_time = PIM_RP_KEEPALIVE_PERIOD; + + pim->ecmp_enable = false; + pim->ecmp_rebalance_enable = false; + + pim->vrf = vrf; + + pim->spt.switchover = PIM_SPT_IMMEDIATE; + pim->spt.plist = NULL; + + pim_msdp_init(pim, router->master); + pim_vxlan_init(pim); + + snprintf(hash_name, sizeof(hash_name), "PIM %s RPF Hash", vrf->name); + pim->rpf_hash = hash_create_size(256, pim_rpf_hash_key, pim_rpf_equal, + hash_name); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: NHT rpf hash init ", __func__); + + pim->ssm_info = pim_ssm_init(); + + pim->static_routes = list_new(); + pim->static_routes->del = (void (*)(void *))pim_static_route_free; + + pim->send_v6_secondary = 1; + + pim->gm_socket = -1; + + pim_rp_init(pim); + + pim_bsm_proc_init(pim); + + pim_oil_init(pim); + + pim_upstream_init(pim); + + pim_instance_mlag_init(pim); + + pim->last_route_change_time = -1; + + pim->reg_sock = pim_reg_sock(); + if (pim->reg_sock < 0) + assert(0); + + /* MSDP global timer defaults. */ + pim->msdp.hold_time = PIM_MSDP_PEER_HOLD_TIME; + pim->msdp.keep_alive = PIM_MSDP_PEER_KA_TIME; + pim->msdp.connection_retry = PIM_MSDP_PEER_CONNECT_RETRY_TIME; + + return pim; +} + +struct pim_instance *pim_get_pim_instance(vrf_id_t vrf_id) +{ + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + if (vrf) + return vrf->info; + + return NULL; +} + +static int pim_vrf_new(struct vrf *vrf) +{ + struct pim_instance *pim = pim_instance_init(vrf); + + zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id); + + vrf->info = (void *)pim; + + pim_ssmpingd_init(pim); + return 0; +} + +static int pim_vrf_delete(struct vrf *vrf) +{ + struct pim_instance *pim = vrf->info; + + if (!pim) + return 0; + + zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id); + + pim_ssmpingd_destroy(pim); + pim_instance_terminate(pim); + + vrf->info = NULL; + + return 0; +} + +/* + * Code to turn on the pim instance that + * we have created with new + */ +static int pim_vrf_enable(struct vrf *vrf) +{ + struct pim_instance *pim = (struct pim_instance *)vrf->info; + struct interface *ifp; + + zlog_debug("%s: for %s %u", __func__, vrf->name, vrf->vrf_id); + + pim_mroute_socket_enable(pim); + + FOR_ALL_INTERFACES (vrf, ifp) { + if (!ifp->info) + continue; + + pim_if_create_pimreg(pim); + break; + } + + return 0; +} + +static int pim_vrf_disable(struct vrf *vrf) +{ + /* Note: This is a callback, the VRF will be deleted by the caller. */ + return 0; +} + +static int pim_vrf_config_write(struct vty *vty) +{ + struct vrf *vrf; + struct pim_instance *pim; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + + if (!pim) + continue; + + if (vrf->vrf_id != VRF_DEFAULT) + vty_frame(vty, "vrf %s\n", vrf->name); + + pim_global_config_write_worker(pim, vty); + + if (vrf->vrf_id != VRF_DEFAULT) + vty_endframe(vty, "exit-vrf\n!\n"); + } + + return 0; +} + +void pim_vrf_init(void) +{ + vrf_init(pim_vrf_new, pim_vrf_enable, pim_vrf_disable, pim_vrf_delete); + + vrf_cmd_init(pim_vrf_config_write); +} + +void pim_vrf_terminate(void) +{ + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + struct pim_instance *pim; + + pim = vrf->info; + if (!pim) + continue; + + pim_ssmpingd_destroy(pim); + pim_instance_terminate(pim); + + vrf->info = NULL; + } + + vrf_terminate(); +} diff --git a/pimd/pim_instance.h b/pimd/pim_instance.h new file mode 100644 index 0000000..ec33133 --- /dev/null +++ b/pimd/pim_instance.h @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRR - PIM Instance + * Copyright (C) 2017 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PIM_INSTANCE_H__ +#define __PIM_INSTANCE_H__ + +#include + +#include "pim_str.h" +#include "pim_msdp.h" +#include "pim_assert.h" +#include "pim_bsm.h" +#include "pim_vxlan_instance.h" +#include "pim_oil.h" +#include "pim_upstream.h" +#include "pim_mroute.h" + +enum pim_spt_switchover { + PIM_SPT_IMMEDIATE, + PIM_SPT_INFINITY, +}; + +/* stats for updates rxed from the MLAG component during the life of a + * session + */ +struct pim_mlag_msg_stats { + uint32_t mroute_add_rx; + uint32_t mroute_add_tx; + uint32_t mroute_del_rx; + uint32_t mroute_del_tx; + uint32_t mlag_status_updates; + uint32_t pim_status_updates; + uint32_t vxlan_updates; + uint32_t peer_zebra_status_updates; +}; + +struct pim_mlag_stats { + /* message stats are reset when the connection to mlagd flaps */ + struct pim_mlag_msg_stats msg; + uint32_t mlagd_session_downs; + uint32_t peer_session_downs; + uint32_t peer_zebra_downs; +}; + +enum pim_mlag_flags { + PIM_MLAGF_NONE = 0, + /* connection to the local MLAG daemon is up */ + PIM_MLAGF_LOCAL_CONN_UP = (1 << 0), + /* connection to the MLAG daemon on the peer switch is up. note + * that there is no direct connection between FRR and the peer MLAG + * daemon. this is just a peer-session status provided by the local + * MLAG daemon. + */ + PIM_MLAGF_PEER_CONN_UP = (1 << 1), + /* status update rxed from the local daemon */ + PIM_MLAGF_STATUS_RXED = (1 << 2), + /* initial dump of data done post peerlink flap */ + PIM_MLAGF_PEER_REPLAY_DONE = (1 << 3), + /* zebra is up on the peer */ + PIM_MLAGF_PEER_ZEBRA_UP = (1 << 4) +}; + +struct pim_router { + struct event_loop *master; + + uint32_t debugs; + + int t_periodic; + struct pim_assert_metric infinite_assert_metric; + long rpf_cache_refresh_delay_msec; + uint32_t register_suppress_time; + int packet_process; + uint32_t register_probe_time; + uint16_t multipath; + + /* + * What is the default vrf that we work in + */ + vrf_id_t vrf_id; + + enum mlag_role mlag_role; + uint32_t pim_mlag_intf_cnt; + /* if true we have registered with MLAG */ + bool mlag_process_register; + /* if true local MLAG process reported that it is connected + * with the peer MLAG process + */ + bool connected_to_mlag; + /* Holds the client data(unencoded) that need to be pushed to MCLAGD*/ + struct stream_fifo *mlag_fifo; + struct stream *mlag_stream; + struct event *zpthread_mlag_write; + struct in_addr anycast_vtep_ip; + struct in_addr local_vtep_ip; + struct pim_mlag_stats mlag_stats; + enum pim_mlag_flags mlag_flags; + char peerlink_rif[IFNAMSIZ]; + struct interface *peerlink_rif_p; +}; + +/* Per VRF PIM DB */ +struct pim_instance { + // vrf_id_t vrf_id; + struct vrf *vrf; + + struct { + enum pim_spt_switchover switchover; + char *plist; + } spt; + + /* The name of the register-accept prefix-list */ + char *register_plist; + + struct hash *rpf_hash; + + void *ssm_info; /* per-vrf SSM configuration */ + + int send_v6_secondary; + + struct event *thread; + int mroute_socket; + int reg_sock; /* Socket to send register msg */ + int64_t mroute_socket_creation; + int64_t mroute_add_events; + int64_t mroute_add_last; + int64_t mroute_del_events; + int64_t mroute_del_last; + + struct interface *regiface; + + // List of static routes; + struct list *static_routes; + + // Upstream vrf specific information + struct rb_pim_upstream_head upstream_head; + struct timer_wheel *upstream_sg_wheel; + + /* + * RP information + */ + struct list *rp_list; + struct route_table *rp_table; + + int iface_vif_index[MAXVIFS]; + int mcast_if_count; + + struct rb_pim_oil_head channel_oil_head; + + struct pim_msdp msdp; + struct pim_vxlan_instance vxlan; + + struct list *ssmpingd_list; + pim_addr ssmpingd_group_addr; + + unsigned int gm_socket_if_count; + int gm_socket; + struct event *t_gm_recv; + + unsigned int gm_group_count; + unsigned int gm_watermark_limit; + unsigned int keep_alive_time; + unsigned int rp_keep_alive_time; + + bool ecmp_enable; + bool ecmp_rebalance_enable; + /* No. of Dual active I/fs in pim_instance */ + uint32_t inst_mlag_intf_cnt; + + /* Bsm related */ + struct bsm_scope global_scope; + uint64_t bsm_rcvd; + uint64_t bsm_sent; + uint64_t bsm_dropped; + + /* If we need to rescan all our upstreams */ + struct event *rpf_cache_refresher; + int64_t rpf_cache_refresh_requests; + int64_t rpf_cache_refresh_events; + int64_t rpf_cache_refresh_last; + int64_t scan_oil_events; + int64_t scan_oil_last; + + int64_t nexthop_lookups; + int64_t nexthop_lookups_avoided; + int64_t last_route_change_time; + + uint64_t gm_rx_drop_sys; +}; + +void pim_vrf_init(void); +void pim_vrf_terminate(void); + +extern struct pim_router *router; + +struct pim_instance *pim_get_pim_instance(vrf_id_t vrf_id); + +#endif diff --git a/pimd/pim_int.c b/pimd/pim_int.c new file mode 100644 index 0000000..6c98a80 --- /dev/null +++ b/pimd/pim_int.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include +#include +#include + +#include "pim_int.h" + +uint32_t pim_read_uint32_host(const uint8_t *buf) +{ + uint32_t val; + memcpy(&val, buf, sizeof(val)); + /* val is in netorder */ + val = ntohl(val); + /* val is in hostorder */ + return val; +} + +void pim_write_uint32(uint8_t *buf, uint32_t val_host) +{ + /* val_host is in host order */ + val_host = htonl(val_host); + /* val_host is in netorder */ + memcpy(buf, &val_host, sizeof(val_host)); +} diff --git a/pimd/pim_int.h b/pimd/pim_int.h new file mode 100644 index 0000000..9e38672 --- /dev/null +++ b/pimd/pim_int.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_INT_H +#define PIM_INT_H + +#include + +uint32_t pim_read_uint32_host(const uint8_t *buf); +void pim_write_uint32(uint8_t *buf, uint32_t val_host); + +#endif /* PIM_INT_H */ diff --git a/pimd/pim_join.c b/pimd/pim_join.c new file mode 100644 index 0000000..bfdb0f0 --- /dev/null +++ b/pimd/pim_join.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + + +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "if.h" +#include "vty.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_str.h" +#include "pim_tlv.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_join.h" +#include "pim_oil.h" +#include "pim_iface.h" +#include "pim_hello.h" +#include "pim_ifchannel.h" +#include "pim_rpf.h" +#include "pim_rp.h" +#include "pim_jp_agg.h" +#include "pim_util.h" +#include "pim_ssm.h" + +static void on_trace(const char *label, struct interface *ifp, pim_addr src) +{ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: from %pPA on %s", label, &src, ifp->name); +} + +static void recv_join(struct interface *ifp, struct pim_neighbor *neigh, + uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags) +{ + struct pim_interface *pim_ifp = NULL; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: join (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s", + __func__, sg, !!(source_flags & PIM_RPT_BIT_MASK), + !!(source_flags & PIM_WILDCARD_BIT_MASK), &upstream, + holdtime, &neigh->source_addr, ifp->name); + + pim_ifp = ifp->info; + assert(pim_ifp); + + ++pim_ifp->pim_ifstat_join_recv; + + /* + * If the RPT and WC are set it's a (*,G) + * and the source is the RP + */ + if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) { + /* As per RFC 7761 Section 4.9.1: + * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for + * use with PIM Join/Prune messages (see Section 4.9.5.1). If + * the WC bit is 1, the RPT bit MUST be 1. + */ + if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "Discarding (*,G)=%pSG join since WC bit is set but RPT bit is unset", + sg); + + return; + } + + struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp); + pim_addr rpf_addr; + + if (!rp) { + zlog_warn("%s: Lookup of RP failed for %pSG", __func__, + sg); + return; + } + /* + * If the RP sent in the message is not + * our RP for the group, drop the message + */ + rpf_addr = rp->rpf_addr; + if (pim_addr_cmp(sg->src, rpf_addr)) { + zlog_warn( + "%s: Specified RP(%pPAs) in join is different than our configured RP(%pPAs)", + __func__, &sg->src, &rpf_addr); + return; + } + + if (pim_is_grp_ssm(pim_ifp->pim, sg->grp)) { + zlog_warn( + "%s: Specified Group(%pPA) in join is now in SSM, not allowed to create PIM state", + __func__, &sg->grp); + return; + } + + sg->src = PIMADDR_ANY; + } + + /* Restart join expiry timer */ + pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, sg, + source_flags, holdtime); +} + +static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh, + uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags) +{ + struct pim_interface *pim_ifp = NULL; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: prune (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s", + __func__, sg, source_flags & PIM_RPT_BIT_MASK, + source_flags & PIM_WILDCARD_BIT_MASK, &upstream, + holdtime, &neigh->source_addr, ifp->name); + + pim_ifp = ifp->info; + assert(pim_ifp); + + ++pim_ifp->pim_ifstat_prune_recv; + + if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) { + /* As per RFC 7761 Section 4.9.1: + * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for + * use with PIM Join/Prune messages (see Section 4.9.5.1). If + * the WC bit is 1, the RPT bit MUST be 1. + */ + if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "Discarding (*,G)=%pSG prune since WC bit is set but RPT bit is unset", + sg); + + return; + } + + /* + * RFC 4601 Section 4.5.2: + * Received Prune(*,G) messages are processed even if the + * RP in the message does not match RP(G). + */ + if (PIM_DEBUG_PIM_J_P) + zlog_debug("%s: Prune received with RP(%pPAs) for %pSG", + __func__, &sg->src, sg); + + sg->src = PIMADDR_ANY; + } + + pim_ifchannel_prune(ifp, upstream, sg, source_flags, holdtime); +} + +int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, + pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) +{ + pim_addr msg_upstream_addr; + bool wrong_af = false; + struct pim_interface *pim_ifp; + uint8_t msg_num_groups; + uint16_t msg_holdtime; + int addr_offset; + uint8_t *buf; + uint8_t *pastend; + int remain; + int group; + struct pim_ifchannel *child = NULL; + struct listnode *ch_node, *nch_node; + + buf = tlv_buf; + pastend = tlv_buf + tlv_buf_size; + pim_ifp = ifp->info; + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + /* + Parse ucast addr + */ + addr_offset = pim_parse_addr_ucast(&msg_upstream_addr, buf, + pastend - buf, &wrong_af); + if (addr_offset < 1) { + zlog_warn("%s: pim_parse_addr_ucast() failure: from %pPA on %s", + __func__, &src_addr, ifp->name); + return -1; + } + buf += addr_offset; + + /* + Check upstream address family + */ + if (wrong_af) { + zlog_warn( + "%s: ignoring join/prune directed to unexpected addr family from %pPA on %s", + __func__, &src_addr, ifp->name); + return -2; + } + + remain = pastend - buf; + if (remain < 4) { + zlog_warn( + "%s: short join/prune message buffer for group list: size=%d minimum=%d from %pPA on %s", + __func__, remain, 4, &src_addr, ifp->name); + return -4; + } + + ++buf; /* skip reserved byte */ + msg_num_groups = *(const uint8_t *)buf; + ++buf; + msg_holdtime = ntohs(*(const uint16_t *)buf); + ++buf; + ++buf; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: join/prune upstream=%pPAs groups=%d holdtime=%d from %pPA on %s", + __func__, &msg_upstream_addr, msg_num_groups, + msg_holdtime, &src_addr, ifp->name); + + /* Scan groups */ + for (group = 0; group < msg_num_groups; ++group) { + pim_sgaddr sg; + uint8_t msg_source_flags; + uint16_t msg_num_joined_sources; + uint16_t msg_num_pruned_sources; + int source; + struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL; + bool filtered = false; + + memset(&sg, 0, sizeof(sg)); + addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf); + if (addr_offset < 1) { + return -5; + } + buf += addr_offset; + + remain = pastend - buf; + if (remain < 4) { + zlog_warn( + "%s: short join/prune buffer for source list: size=%d minimum=%d from %pPA on %s", + __func__, remain, 4, &src_addr, ifp->name); + return -6; + } + + msg_num_joined_sources = ntohs(*(const uint16_t *)buf); + buf += 2; + msg_num_pruned_sources = ntohs(*(const uint16_t *)buf); + buf += 2; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: join/prune upstream=%pPAs group=%pPA/32 join_src=%d prune_src=%d from %pPA on %s", + __func__, &msg_upstream_addr, &sg.grp, + msg_num_joined_sources, msg_num_pruned_sources, + &src_addr, ifp->name); + + /* boundary check */ + filtered = pim_is_group_filtered(pim_ifp, &sg.grp); + + /* Scan joined sources */ + for (source = 0; source < msg_num_joined_sources; ++source) { + addr_offset = pim_parse_addr_source( + &sg, &msg_source_flags, buf, pastend - buf); + if (addr_offset < 1) { + return -7; + } + + buf += addr_offset; + + /* if we are filtering this group, skip the join */ + if (filtered) + continue; + + recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr, + &sg, msg_source_flags); + + if (pim_addr_is_any(sg.src)) { + starg_ch = pim_ifchannel_find(ifp, &sg); + if (starg_ch) + pim_ifchannel_set_star_g_join_state( + starg_ch, 0, 1); + } + } + + /* Scan pruned sources */ + for (source = 0; source < msg_num_pruned_sources; ++source) { + addr_offset = pim_parse_addr_source( + &sg, &msg_source_flags, buf, pastend - buf); + if (addr_offset < 1) { + return -8; + } + + buf += addr_offset; + + /* if we are filtering this group, skip the prune */ + if (filtered) + continue; + + recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr, + &sg, msg_source_flags); + /* + * So if we are receiving a S,G,RPT prune + * before we have any data for that S,G + * We need to retrieve the sg_ch after + * we parse the prune. + */ + sg_ch = pim_ifchannel_find(ifp, &sg); + + if (!sg_ch) + continue; + + /* (*,G) prune received */ + for (ALL_LIST_ELEMENTS(sg_ch->sources, ch_node, + nch_node, child)) { + if (PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) { + if (child->ifjoin_state + == PIM_IFJOIN_PRUNE_PENDING_TMP) + EVENT_OFF( + child->t_ifjoin_prune_pending_timer); + EVENT_OFF(child->t_ifjoin_expiry_timer); + PIM_IF_FLAG_UNSET_S_G_RPT(child->flags); + child->ifjoin_state = PIM_IFJOIN_NOINFO; + delete_on_noinfo(child); + } + } + + /* Received SG-RPT Prune delete oif from specific S,G */ + if (starg_ch && (msg_source_flags & PIM_RPT_BIT_MASK) + && !(msg_source_flags & PIM_WILDCARD_BIT_MASK)) { + struct pim_upstream *up = sg_ch->upstream; + PIM_IF_FLAG_SET_S_G_RPT(sg_ch->flags); + if (up) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: SGRpt flag is set, del inherit oif from up %s", + __func__, up->sg_str); + pim_channel_del_inherited_oif( + up->channel_oil, + starg_ch->interface, + __func__); + } + } + } + if (starg_ch && !filtered) + pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0); + starg_ch = NULL; + } /* scan groups */ + + return 0; +} + +/* + * J/P Message Format + * + * While the RFC clearly states that this is 32 bits wide, it + * is cheating. These fields: + * Encoded-Unicast format (6 bytes MIN) + * Encoded-Group format (8 bytes MIN) + * Encoded-Source format (8 bytes MIN) + * are *not* 32 bits wide. + * + * Nor does the RFC explicitly call out the size for: + * Reserved (1 byte) + * Num Groups (1 byte) + * Holdtime (2 bytes) + * Number of Joined Sources (2 bytes) + * Number of Pruned Sources (2 bytes) + * + * This leads to a missleading representation from casual + * reading and making assumptions. Be careful! + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |PIM Ver| Type | Reserved | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Upstream Neighbor Address (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Num groups | Holdtime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Multicast Group Address 1 (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of Joined Sources | Number of Pruned Sources | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Multicast Group Address m (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of Joined Sources | Number of Pruned Sources | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups) +{ + struct pim_jp_agg_group *group; + struct pim_interface *pim_ifp = NULL; + struct pim_jp_groups *grp = NULL; + struct pim_jp *msg = NULL; + struct listnode *node, *nnode; + uint8_t pim_msg[10000]; + uint8_t *curr_ptr = pim_msg; + bool new_packet = true; + size_t packet_left = 0; + size_t packet_size = 0; + size_t group_size = 0; + + if (rpf->source_nexthop.interface) + pim_ifp = rpf->source_nexthop.interface->info; + else { + zlog_warn("%s: RPF interface is not present", __func__); + return -1; + } + + + on_trace(__func__, rpf->source_nexthop.interface, rpf->rpf_addr); + + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", __func__, + rpf->source_nexthop.interface->name); + return -1; + } + + if (pim_addr_is_any(rpf->rpf_addr)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: upstream=%pPA is myself on interface %s", + __func__, &rpf->rpf_addr, + rpf->source_nexthop.interface->name); + return 0; + } + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on + an interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. + */ + pim_hello_require(rpf->source_nexthop.interface); + + for (ALL_LIST_ELEMENTS(groups, node, nnode, group)) { + if (new_packet) { + msg = (struct pim_jp *)pim_msg; + + memset(msg, 0, sizeof(*msg)); + + pim_msg_addr_encode_ucast((uint8_t *)&msg->addr, + rpf->rpf_addr); + msg->reserved = 0; + msg->holdtime = htons(PIM_JP_HOLDTIME); + + new_packet = false; + + grp = &msg->groups[0]; + curr_ptr = (uint8_t *)grp; + packet_size = sizeof(struct pim_msg_header); + packet_size += sizeof(pim_encoded_unicast); + packet_size += + 4; // reserved (1) + groups (1) + holdtime (2) + + packet_left = rpf->source_nexthop.interface->mtu - 24; + packet_left -= packet_size; + } + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: sending (G)=%pPAs to upstream=%pPA on interface %s", + __func__, &group->group, &rpf->rpf_addr, + rpf->source_nexthop.interface->name); + + group_size = pim_msg_get_jp_group_size(group->sources); + if (group_size > packet_left) { + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, + pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, + rpf->source_nexthop.interface->name); + } + + msg = (struct pim_jp *)pim_msg; + memset(msg, 0, sizeof(*msg)); + + pim_msg_addr_encode_ucast((uint8_t *)&msg->addr, + rpf->rpf_addr); + msg->reserved = 0; + msg->holdtime = htons(PIM_JP_HOLDTIME); + + new_packet = false; + + grp = &msg->groups[0]; + curr_ptr = (uint8_t *)grp; + packet_size = sizeof(struct pim_msg_header); + packet_size += sizeof(pim_encoded_unicast); + packet_size += + 4; // reserved (1) + groups (1) + holdtime (2) + + packet_left = rpf->source_nexthop.interface->mtu - 24; + packet_left -= packet_size; + } + + msg->num_groups++; + /* + Build PIM message + */ + + curr_ptr += group_size; + packet_left -= group_size; + packet_size += group_size; + pim_msg_build_jp_groups(grp, group, group_size); + + if (!pim_ifp->pim_passive_enable) { + pim_ifp->pim_ifstat_join_send += ntohs(grp->joins); + pim_ifp->pim_ifstat_prune_send += ntohs(grp->prunes); + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: interface %s num_joins %u num_prunes %u", + __func__, rpf->source_nexthop.interface->name, + ntohs(grp->joins), ntohs(grp->prunes)); + + grp = (struct pim_jp_groups *)curr_ptr; + if (packet_left < sizeof(struct pim_jp_groups) + || msg->num_groups == 255) { + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, + pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, + rpf->source_nexthop.interface->name); + } + + new_packet = true; + } + } + + + if (!new_packet) { + // msg->num_groups = htons (msg->num_groups); + pim_msg_build_header( + pim_ifp->primary_address, qpim_all_pim_routers_addr, + pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, rpf->source_nexthop.interface->name); + } + } + return 0; +} diff --git a/pimd/pim_join.h b/pimd/pim_join.h new file mode 100644 index 0000000..a28d805 --- /dev/null +++ b/pimd/pim_join.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_JOIN_H +#define PIM_JOIN_H + +#include + +#include "if.h" + +#include "pim_neighbor.h" + +int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, + pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size); + +int pim_joinprune_send(struct pim_rpf *nexthop, struct list *groups); + +#endif /* PIM_JOIN_H */ diff --git a/pimd/pim_jp_agg.c b/pimd/pim_jp_agg.c new file mode 100644 index 0000000..40332ed --- /dev/null +++ b/pimd/pim_jp_agg.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRR - J/P Aggregation + * Copyright (C) 2017 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "linklist.h" +#include "log.h" +#include "vrf.h" +#include "if.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_msg.h" +#include "pim_jp_agg.h" +#include "pim_join.h" +#include "pim_iface.h" + +void pim_jp_agg_group_list_free(struct pim_jp_agg_group *jag) +{ + list_delete(&jag->sources); + + XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); +} + +static void pim_jp_agg_src_free(struct pim_jp_sources *js) +{ + struct pim_upstream *up = js->up; + + /* + * When we are being called here, we know + * that the neighbor is going away start + * the normal j/p timer so that it can + * pick this shit back up when the + * nbr comes back alive + */ + if (up) + join_timer_start(js->up); + XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); +} + +int pim_jp_agg_group_list_cmp(void *arg1, void *arg2) +{ + const struct pim_jp_agg_group *jag1 = + (const struct pim_jp_agg_group *)arg1; + const struct pim_jp_agg_group *jag2 = + (const struct pim_jp_agg_group *)arg2; + + return pim_addr_cmp(jag1->group, jag2->group); +} + +static int pim_jp_agg_src_cmp(void *arg1, void *arg2) +{ + const struct pim_jp_sources *js1 = (const struct pim_jp_sources *)arg1; + const struct pim_jp_sources *js2 = (const struct pim_jp_sources *)arg2; + + if (js1->is_join && !js2->is_join) + return -1; + + if (!js1->is_join && js2->is_join) + return 1; + + return pim_addr_cmp(js1->up->sg.src, js2->up->sg.src); +} + +/* + * This function is used by scan_oil to clear + * the created jp_agg_group created when + * figuring out where to send prunes + * and joins. + */ +void pim_jp_agg_clear_group(struct list *group) +{ + struct listnode *gnode, *gnnode; + struct listnode *snode, *snnode; + struct pim_jp_agg_group *jag; + struct pim_jp_sources *js; + + for (ALL_LIST_ELEMENTS(group, gnode, gnnode, jag)) { + for (ALL_LIST_ELEMENTS(jag->sources, snode, snnode, js)) { + listnode_delete(jag->sources, js); + js->up = NULL; + XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); + } + list_delete(&jag->sources); + listnode_delete(group, jag); + XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); + } +} + +static struct pim_iface_upstream_switch * +pim_jp_agg_get_interface_upstream_switch_list(struct pim_rpf *rpf) +{ + struct interface *ifp = rpf->source_nexthop.interface; + struct pim_interface *pim_ifp; + struct pim_iface_upstream_switch *pius; + struct listnode *node, *nnode; + + if (!ifp) + return NULL; + + pim_ifp = ifp->info; + + /* Old interface is pim disabled */ + if (!pim_ifp) + return NULL; + + for (ALL_LIST_ELEMENTS(pim_ifp->upstream_switch_list, node, nnode, + pius)) { + if (!pim_addr_cmp(pius->address, rpf->rpf_addr)) + break; + } + + if (!pius) { + pius = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, + sizeof(struct pim_iface_upstream_switch)); + pius->address = rpf->rpf_addr; + pius->us = list_new(); + listnode_add_sort(pim_ifp->upstream_switch_list, pius); + } + + return pius; +} + +void pim_jp_agg_remove_group(struct list *group, struct pim_upstream *up, + struct pim_neighbor *nbr) +{ + struct listnode *node, *nnode; + struct pim_jp_agg_group *jag = NULL; + struct pim_jp_sources *js = NULL; + + for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { + if (!pim_addr_cmp(jag->group, up->sg.grp)) + break; + } + + if (!jag) + return; + + for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { + if (js->up == up) + break; + } + + if (nbr) { + if (PIM_DEBUG_TRACE) + zlog_debug("up %s remove from nbr %s/%pPAs jp-agg-list", + up->sg_str, nbr->interface->name, + &nbr->source_addr); + } + + if (js) { + js->up = NULL; + listnode_delete(jag->sources, js); + XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); + } + + if (jag->sources->count == 0) { + list_delete(&jag->sources); + listnode_delete(group, jag); + XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); + } +} + +int pim_jp_agg_is_in_list(struct list *group, struct pim_upstream *up) +{ + struct listnode *node, *nnode; + struct pim_jp_agg_group *jag = NULL; + struct pim_jp_sources *js = NULL; + + for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { + if (!pim_addr_cmp(jag->group, up->sg.grp)) + break; + } + + if (!jag) + return 0; + + for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { + if (js->up == up) + return 1; + } + + return 0; +} + +//#define PIM_JP_AGG_DEBUG 1 +/* + * For the given upstream, check all the neighbor + * jp_agg lists and ensure that it is not + * in another list + * + * *IF* ignore is true we can skip + * up->rpf.source_nexthop.interface particular interface for checking + * + * This is a debugging function, Probably + * can be safely compiled out in real + * builds + */ +void pim_jp_agg_upstream_verification(struct pim_upstream *up, bool ignore) +{ +#ifdef PIM_JP_AGG_DEBUG + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return; + } + + pim_ifp = up->rpf.source_nexthop.interface->info; + pim = pim_ifp->pim; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + struct listnode *nnode; + + if (ignore && ifp == up->rpf.source_nexthop.interface) + continue; + + if (pim_ifp) { + struct pim_neighbor *neigh; + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, + nnode, neigh)) { + assert(!pim_jp_agg_is_in_list( + neigh->upstream_jp_agg, up)); + } + } + } +#else + return; +#endif +} + +void pim_jp_agg_add_group(struct list *group, struct pim_upstream *up, + bool is_join, struct pim_neighbor *nbr) +{ + struct listnode *node, *nnode; + struct pim_jp_agg_group *jag = NULL; + struct pim_jp_sources *js = NULL; + + for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { + if (!pim_addr_cmp(jag->group, up->sg.grp)) + break; + } + + if (!jag) { + jag = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, + sizeof(struct pim_jp_agg_group)); + jag->group = up->sg.grp; + jag->sources = list_new(); + jag->sources->cmp = pim_jp_agg_src_cmp; + jag->sources->del = (void (*)(void *))pim_jp_agg_src_free; + listnode_add_sort(group, jag); + } + + for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { + if (js->up == up) + break; + } + + if (nbr) { + if (PIM_DEBUG_TRACE) + zlog_debug("up %s add to nbr %s/%pPAs jp-agg-list", + up->sg_str, + up->rpf.source_nexthop.interface->name, + &nbr->source_addr); + } + + if (!js) { + js = XCALLOC(MTYPE_PIM_JP_AGG_SOURCE, + sizeof(struct pim_jp_sources)); + js->up = up; + js->is_join = is_join; + listnode_add_sort(jag->sources, js); + } else { + if (js->is_join != is_join) { + listnode_delete(jag->sources, js); + js->is_join = is_join; + listnode_add_sort(jag->sources, js); + } + } +} + +void pim_jp_agg_switch_interface(struct pim_rpf *orpf, struct pim_rpf *nrpf, + struct pim_upstream *up) +{ + struct pim_iface_upstream_switch *opius; + struct pim_iface_upstream_switch *npius; + + opius = pim_jp_agg_get_interface_upstream_switch_list(orpf); + npius = pim_jp_agg_get_interface_upstream_switch_list(nrpf); + + /* + * RFC 4601: 4.5.7. Sending (S,G) Join/Prune Messages + * + * Transitions from Joined State + * + * RPF'(S,G) changes not due to an Assert + * + * The upstream (S,G) state machine remains in Joined + * state. Send Join(S,G) to the new upstream neighbor, which is + * the new value of RPF'(S,G). Send Prune(S,G) to the old + * upstream neighbor, which is the old value of RPF'(S,G). Set + * the Join Timer (JT) to expire after t_periodic seconds. + */ + + /* send Prune(S,G) to the old upstream neighbor */ + if (opius) + pim_jp_agg_add_group(opius->us, up, false, NULL); + + /* send Join(S,G) to the current upstream neighbor */ + if (npius) + pim_jp_agg_add_group(npius->us, up, true, NULL); +} + + +void pim_jp_agg_single_upstream_send(struct pim_rpf *rpf, + struct pim_upstream *up, bool is_join) +{ + struct list groups, sources; + struct pim_jp_agg_group jag; + struct pim_jp_sources js; + + /* skip JP upstream messages if source is directly connected */ + if (!up || !rpf->source_nexthop.interface || + pim_if_connected_to_source(rpf->source_nexthop.interface, + up->sg.src) || + if_is_loopback(rpf->source_nexthop.interface)) + return; + + memset(&groups, 0, sizeof(groups)); + memset(&sources, 0, sizeof(sources)); + jag.sources = &sources; + + listnode_add(&groups, &jag); + listnode_add(jag.sources, &js); + + jag.group = up->sg.grp; + js.up = up; + js.is_join = is_join; + + pim_joinprune_send(rpf, &groups); + + list_delete_all_node(jag.sources); + list_delete_all_node(&groups); +} diff --git a/pimd/pim_jp_agg.h b/pimd/pim_jp_agg.h new file mode 100644 index 0000000..926d211 --- /dev/null +++ b/pimd/pim_jp_agg.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for FRR - J/P Aggregation + * Copyright (C) 2017 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __PIM_JP_AGG_H__ +#define __PIM_JP_AGG_H__ + +#include "pim_rpf.h" + +struct pim_jp_sources { + struct pim_upstream *up; + int is_join; +}; + +struct pim_jp_agg_group { + pim_addr group; + struct list *sources; +}; + +void pim_jp_agg_upstream_verification(struct pim_upstream *up, bool ignore); +int pim_jp_agg_is_in_list(struct list *group, struct pim_upstream *up); + +void pim_jp_agg_group_list_free(struct pim_jp_agg_group *jag); +int pim_jp_agg_group_list_cmp(void *arg1, void *arg2); + +void pim_jp_agg_clear_group(struct list *group); +void pim_jp_agg_remove_group(struct list *group, struct pim_upstream *up, + struct pim_neighbor *nbr); + +void pim_jp_agg_add_group(struct list *group, struct pim_upstream *up, + bool is_join, struct pim_neighbor *nbr); + +void pim_jp_agg_switch_interface(struct pim_rpf *orpf, struct pim_rpf *nrpf, + struct pim_upstream *up); + +void pim_jp_agg_single_upstream_send(struct pim_rpf *rpf, + struct pim_upstream *up, bool is_join); +#endif diff --git a/pimd/pim_macro.c b/pimd/pim_macro.c new file mode 100644 index 0000000..2690fca --- /dev/null +++ b/pimd/pim_macro.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_macro.h" +#include "pim_iface.h" +#include "pim_ifchannel.h" +#include "pim_rp.h" + +/* + DownstreamJPState(S,G,I) is the per-interface state machine for + receiving (S,G) Join/Prune messages. + + DownstreamJPState(S,G,I) is either Join or Prune-Pending + DownstreamJPState(*,G,I) is either Join or Prune-Pending +*/ +static int downstream_jpstate_isjoined(const struct pim_ifchannel *ch) +{ + switch (ch->ifjoin_state) { + case PIM_IFJOIN_NOINFO: + case PIM_IFJOIN_PRUNE: + case PIM_IFJOIN_PRUNE_TMP: + case PIM_IFJOIN_PRUNE_PENDING_TMP: + return 0; + case PIM_IFJOIN_JOIN: + case PIM_IFJOIN_PRUNE_PENDING: + return 1; + } + return 0; +} + +/* + The clause "local_receiver_include(S,G,I)" is true if the IGMP/MLD + module or other local membership mechanism has determined that local + members on interface I desire to receive traffic sent specifically + by S to G. +*/ +static int local_receiver_include(const struct pim_ifchannel *ch) +{ + /* local_receiver_include(S,G,I) ? */ + return ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE; +} + +/* + RFC 4601: 4.1.6. State Summarization Macros + + The set "joins(S,G)" is the set of all interfaces on which the + router has received (S,G) Joins: + + joins(S,G) = + { all interfaces I such that + DownstreamJPState(S,G,I) is either Join or Prune-Pending } + + DownstreamJPState(S,G,I) is either Join or Prune-Pending ? +*/ +int pim_macro_chisin_joins(const struct pim_ifchannel *ch) +{ + return downstream_jpstate_isjoined(ch); +} + +/* + RFC 4601: 4.6.5. Assert State Macros + + The set "lost_assert(S,G)" is the set of all interfaces on which the + router has received (S,G) joins but has lost an (S,G) assert. + + lost_assert(S,G) = + { all interfaces I such that + lost_assert(S,G,I) == true } + + bool lost_assert(S,G,I) { + if ( RPF_interface(S) == I ) { + return false + } else { + return ( AssertWinner(S,G,I) != NULL AND + AssertWinner(S,G,I) != me AND + (AssertWinnerMetric(S,G,I) is better + than spt_assert_metric(S,I) ) + } + } + + AssertWinner(S,G,I) is the IP source address of the Assert(S,G) + packet that won an Assert. +*/ +int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct pim_assert_metric spt_assert_metric; + + ifp = ch->interface; + if (!ifp) { + zlog_warn("%s: (S,G)=%s: null interface", __func__, ch->sg_str); + return 0; /* false */ + } + + /* RPF_interface(S) == I ? */ + if (ch->upstream->rpf.source_nexthop.interface == ifp) + return 0; /* false */ + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", + __func__, ch->sg_str, ifp->name); + return 0; /* false */ + } + + if (pim_addr_is_any(ch->ifassert_winner)) + return 0; /* false */ + + /* AssertWinner(S,G,I) == me ? */ + if (!pim_addr_cmp(ch->ifassert_winner, pim_ifp->primary_address)) + return 0; /* false */ + + spt_assert_metric = pim_macro_spt_assert_metric( + &ch->upstream->rpf, pim_ifp->primary_address); + + return pim_assert_metric_better(&ch->ifassert_winner_metric, + &spt_assert_metric); +} + +/* + RFC 4601: 4.1.6. State Summarization Macros + + pim_include(S,G) = + { all interfaces I such that: + ( (I_am_DR( I ) AND lost_assert(S,G,I) == false ) + OR AssertWinner(S,G,I) == me ) + AND local_receiver_include(S,G,I) } + + AssertWinner(S,G,I) is the IP source address of the Assert(S,G) + packet that won an Assert. +*/ +int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp = ch->interface->info; + bool mlag_active = false; + + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", + __func__, ch->sg_str, ch->interface->name); + return 0; /* false */ + } + + /* local_receiver_include(S,G,I) ? */ + if (!local_receiver_include(ch)) + return 0; /* false */ + + /* OR AssertWinner(S,G,I) == me ? */ + if (!pim_addr_cmp(ch->ifassert_winner, pim_ifp->primary_address)) + return 1; /* true */ + + /* + * When we have a activeactive interface we need to signal + * that this interface is interesting to the upstream + * decision to JOIN *if* we are syncing over the interface + */ + if (pim_ifp->activeactive) { + struct pim_upstream *up = ch->upstream; + + if (PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(up->flags)) + mlag_active = true; + } + + return ( + /* I_am_DR( I ) ? */ + (PIM_I_am_DR(pim_ifp) || mlag_active) && + /* lost_assert(S,G,I) == false ? */ + (!pim_macro_ch_lost_assert(ch))); +} + +int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch) +{ + if (pim_macro_chisin_joins(ch)) + return 1; /* true */ + + return pim_macro_chisin_pim_include(ch); +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + CouldAssert(S,G,I) = + SPTbit(S,G)==TRUE + AND (RPF_interface(S) != I) + AND (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) + (+) ( pim_include(*,G) (-) pim_exclude(S,G) ) + (-) lost_assert(*,G) + (+) joins(S,G) (+) pim_include(S,G) ) ) + + CouldAssert(S,G,I) is true for downstream interfaces that would be in + the inherited_olist(S,G) if (S,G) assert information was not taken + into account. + + CouldAssert(S,G,I) may be affected by changes in the following: + + pim_ifp->primary_address + pim_ifp->pim_dr_addr + ch->ifassert_winner_metric + ch->ifassert_winner + ch->local_ifmembership + ch->ifjoin_state + ch->upstream->rpf.source_nexthop.mrib_metric_preference + ch->upstream->rpf.source_nexthop.mrib_route_metric + ch->upstream->rpf.source_nexthop.interface +*/ +int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch) +{ + struct interface *ifp; + + ifp = ch->interface; + if (!ifp) { + zlog_warn("%s: (S,G)=%s: null interface", __func__, ch->sg_str); + return 0; /* false */ + } + + /* SPTbit(S,G) == true */ + if (ch->upstream->sptbit == PIM_UPSTREAM_SPTBIT_FALSE) + return 0; /* false */ + + /* RPF_interface(S) != I ? */ + if (ch->upstream->rpf.source_nexthop.interface == ifp) + return 0; /* false */ + + /* I in joins(S,G) (+) pim_include(S,G) ? */ + return pim_macro_chisin_joins_or_include(ch); +} + +/* + RFC 4601: 4.6.3. Assert Metrics + + spt_assert_metric(S,I) gives the assert metric we use if we're + sending an assert based on active (S,G) forwarding state: + + assert_metric + spt_assert_metric(S,I) { + return {0,MRIB.pref(S),MRIB.metric(S),my_ip_address(I)} + } +*/ +struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf, + pim_addr ifaddr) +{ + struct pim_assert_metric metric; + + metric.rpt_bit_flag = 0; + metric.metric_preference = rpf->source_nexthop.mrib_metric_preference; + metric.route_metric = rpf->source_nexthop.mrib_route_metric; + metric.ip_address = ifaddr; + + return metric; +} + +/* + RFC 4601: 4.6.3. Assert Metrics + + An assert metric for (S,G) to include in (or compare against) an + Assert message sent on interface I should be computed using the + following pseudocode: + + assert_metric my_assert_metric(S,G,I) { + if( CouldAssert(S,G,I) == true ) { + return spt_assert_metric(S,I) + } else if( CouldAssert(*,G,I) == true ) { + return rpt_assert_metric(G,I) + } else { + return infinite_assert_metric() + } + } +*/ +struct pim_assert_metric +pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ch->interface->info; + + if (pim_ifp) { + if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { + return pim_macro_spt_assert_metric( + &ch->upstream->rpf, pim_ifp->primary_address); + } + } + + return router->infinite_assert_metric; +} + +/* + RFC 4601 4.2. Data Packet Forwarding Rules + + Macro: + inherited_olist(S,G) = + inherited_olist(S,G,rpt) (+) + joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) +*/ +static int pim_macro_chisin_inherited_olist(const struct pim_ifchannel *ch) +{ + if (pim_macro_ch_lost_assert(ch)) + return 0; /* false */ + + return pim_macro_chisin_joins_or_include(ch); +} + +/* + RFC 4601 4.2. Data Packet Forwarding Rules + RFC 4601 4.8.2. PIM-SSM-Only Routers + + Additionally, the Packet forwarding rules of Section 4.2 can be + simplified in a PIM-SSM-only router: + + iif is the incoming interface of the packet. + oiflist = NULL + if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) { + oiflist = inherited_olist(S,G) + } else if (iif is in inherited_olist(S,G)) { + send Assert(S,G) on iif + } + oiflist = oiflist (-) iif + forward packet on all interfaces in oiflist + + Macro: + inherited_olist(S,G) = + joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) + + Note: + - The following test is performed as response to WRONGVIF kernel + upcall: + if (iif is in inherited_olist(S,G)) { + send Assert(S,G) on iif + } + See pim_mroute.c mroute_msg(). +*/ +int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch) +{ + if (ch->upstream->join_state == PIM_UPSTREAM_NOTJOINED) { + /* oiflist is NULL */ + return 0; /* false */ + } + + /* oiflist = oiflist (-) iif */ + if (ch->interface == ch->upstream->rpf.source_nexthop.interface) + return 0; /* false */ + + return pim_macro_chisin_inherited_olist(ch); +} + +/* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + AssertTrackingDesired(S,G,I) = + (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) + (+) ( pim_include(*,G) (-) pim_exclude(S,G) ) + (-) lost_assert(*,G) + (+) joins(S,G) ) ) + OR (local_receiver_include(S,G,I) == true + AND (I_am_DR(I) OR (AssertWinner(S,G,I) == me))) + OR ((RPF_interface(S) == I) AND (JoinDesired(S,G) == true)) + OR ((RPF_interface(RP(G)) == I) AND (JoinDesired(*,G) == true) + AND (SPTbit(S,G) == false)) + + AssertTrackingDesired(S,G,I) is true on any interface in which an + (S,G) assert might affect our behavior. +*/ +int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch) +{ + struct pim_interface *pim_ifp; + struct interface *ifp; + + ifp = ch->interface; + if (!ifp) { + zlog_warn("%s: (S,G)=%s: null interface", __func__, ch->sg_str); + return 0; /* false */ + } + + pim_ifp = ifp->info; + if (!pim_ifp) { + zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s", + __func__, ch->sg_str, ch->interface->name); + return 0; /* false */ + } + + /* I in joins(S,G) ? */ + if (pim_macro_chisin_joins(ch)) + return 1; /* true */ + + /* local_receiver_include(S,G,I) ? */ + if (local_receiver_include(ch)) { + /* I_am_DR(I) ? */ + if (PIM_I_am_DR(pim_ifp)) + return 1; /* true */ + + /* AssertWinner(S,G,I) == me ? */ + if (!pim_addr_cmp(ch->ifassert_winner, + pim_ifp->primary_address)) + return 1; /* true */ + } + + /* RPF_interface(S) == I ? */ + if (ch->upstream->rpf.source_nexthop.interface == ifp) { + /* JoinDesired(S,G) ? */ + if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(ch->upstream->flags)) + return 1; /* true */ + } + + return 0; /* false */ +} diff --git a/pimd/pim_macro.h b/pimd/pim_macro.h new file mode 100644 index 0000000..39fa535 --- /dev/null +++ b/pimd/pim_macro.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_MACRO_H +#define PIM_MACRO_H + +#include + +#include "if.h" + +#include "pim_upstream.h" +#include "pim_ifchannel.h" + +int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch); +int pim_macro_chisin_joins(const struct pim_ifchannel *ch); +int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch); +int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch); +int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch); +struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf, + pim_addr ifaddr); +struct pim_assert_metric +pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch); +int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch); +int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch); + +#endif /* PIM_MACRO_H */ diff --git a/pimd/pim_main.c b/pimd/pim_main.c new file mode 100644 index 0000000..400db39 --- /dev/null +++ b/pimd/pim_main.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "privs.h" +#include "lib/version.h" +#include +#include "command.h" +#include "frrevent.h" +#include + +#include "memory.h" +#include "vrf.h" +#include "filter.h" +#include "vty.h" +#include "sigevent.h" +#include "prefix.h" +#include "plist.h" +#include "vrf.h" +#include "libfrr.h" +#include "routemap.h" +#include "routing_nb.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_signals.h" +#include "pim_zebra.h" +#include "pim_msdp.h" +#include "pim_iface.h" +#include "pim_bfd.h" +#include "pim_mlag.h" +#include "pim_errors.h" +#include "pim_nb.h" + +extern struct host host; + +struct option longopts[] = {{0}}; + +/* pimd privileges */ +zebra_capabilities_t _caps_p[] = { + ZCAP_NET_ADMIN, ZCAP_SYS_ADMIN, ZCAP_NET_RAW, ZCAP_BIND, +}; + +/* pimd privileges to run with */ +struct zebra_privs_t pimd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +static const struct frr_yang_module_info *const pimd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, + &frr_routing_info, + &frr_pim_info, + &frr_pim_rp_info, + &frr_gmp_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(pimd, PIM, + .vty_port = PIMD_VTY_PORT, + .proghelp = "Implementation of the PIM routing protocol.", + + .signals = pimd_signals, + .n_signals = 4 /* XXX array_size(pimd_signals) XXX*/, + + .privs = &pimd_privs, + + .yang_modules = pimd_yang_modules, + .n_yang_modules = array_size(pimd_yang_modules), +); +/* clang-format on */ + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pimd_di, argc, argv); + frr_opt_add("", longopts, ""); + + /* this while just reads the options */ + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + pim_router_init(); + + /* + * Initializations + */ + pim_error_init(); + pim_vrf_init(); + access_list_init(); + prefix_list_init(); + prefix_list_add_hook(pim_prefix_list_update); + prefix_list_delete_hook(pim_prefix_list_update); + + pim_route_map_init(); + pim_init(); + + /* + * Initialize zclient "update" and "lookup" sockets + */ + pim_iface_init(); + pim_zebra_init(); + pim_bfd_init(); + pim_mlag_init(); + + hook_register(routing_conf_event, + routing_control_plane_protocols_name_validate); + + routing_control_plane_protocols_register_vrf_dependency(); + + frr_config_fork(); + +#ifdef PIM_DEBUG_BYDEFAULT + zlog_notice("PIM_DEBUG_BYDEFAULT: Enabling all debug commands"); + PIM_DO_DEBUG_PIM_EVENTS; + PIM_DO_DEBUG_PIM_PACKETS; + PIM_DO_DEBUG_PIM_TRACE; + PIM_DO_DEBUG_GM_EVENTS; + PIM_DO_DEBUG_GM_PACKETS; + PIM_DO_DEBUG_GM_TRACE; + PIM_DO_DEBUG_ZEBRA; +#endif + +#ifdef PIM_CHECK_RECV_IFINDEX_SANITY + zlog_notice( + "PIM_CHECK_RECV_IFINDEX_SANITY: will match sock/recv ifindex"); +#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH + zlog_notice( + "PIM_REPORT_RECV_IFINDEX_MISMATCH: will report sock/recv ifindex mismatch"); +#endif +#endif + +#ifdef PIM_UNEXPECTED_KERNEL_UPCALL + zlog_notice( + "PIM_UNEXPECTED_KERNEL_UPCALL: report unexpected kernel upcall"); +#endif + + frr_run(router->master); + + /* never reached */ + return 0; +} diff --git a/pimd/pim_memory.c b/pimd/pim_memory.c new file mode 100644 index 0000000..85780f0 --- /dev/null +++ b/pimd/pim_memory.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* pimd memory type definitions + * + * Copyright (C) 2015 David Lamparter + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pim_memory.h" + +DEFINE_MGROUP(PIMD, "pimd"); +DEFINE_MTYPE(PIMD, PIM_CHANNEL_OIL, "PIM SSM (S,G) channel OIL"); +DEFINE_MTYPE(PIMD, PIM_INTERFACE, "PIM interface"); +DEFINE_MTYPE(PIMD, PIM_IGMP_JOIN, "PIM interface IGMP static join"); +DEFINE_MTYPE(PIMD, PIM_IGMP_SOCKET, "PIM interface IGMP socket"); +DEFINE_MTYPE(PIMD, PIM_IGMP_GROUP, "PIM interface IGMP group"); +DEFINE_MTYPE(PIMD, PIM_IGMP_GROUP_SOURCE, "PIM interface IGMP source"); +DEFINE_MTYPE(PIMD, PIM_NEIGHBOR, "PIM interface neighbor"); +DEFINE_MTYPE(PIMD, PIM_IFCHANNEL, "PIM interface (S,G) state"); +DEFINE_MTYPE(PIMD, PIM_UPSTREAM, "PIM upstream (S,G) state"); +DEFINE_MTYPE(PIMD, PIM_SSMPINGD, "PIM sspimgd socket"); +DEFINE_MTYPE(PIMD, PIM_STATIC_ROUTE, "PIM Static Route"); +DEFINE_MTYPE(PIMD, PIM_RP, "PIM RP info"); +DEFINE_MTYPE(PIMD, PIM_FILTER_NAME, "PIM RP filter info"); +DEFINE_MTYPE(PIMD, PIM_MSDP_PEER, "PIM MSDP peer"); +DEFINE_MTYPE(PIMD, PIM_MSDP_MG_NAME, "PIM MSDP mesh-group name"); +DEFINE_MTYPE(PIMD, PIM_MSDP_SA, "PIM MSDP source-active cache"); +DEFINE_MTYPE(PIMD, PIM_MSDP_MG, "PIM MSDP mesh group"); +DEFINE_MTYPE(PIMD, PIM_MSDP_MG_MBR, "PIM MSDP mesh group mbr"); +DEFINE_MTYPE(PIMD, PIM_SEC_ADDR, "PIM secondary address"); +DEFINE_MTYPE(PIMD, PIM_JP_AGG_GROUP, "PIM JP AGG Group"); +DEFINE_MTYPE(PIMD, PIM_JP_AGG_SOURCE, "PIM JP AGG Source"); +DEFINE_MTYPE(PIMD, PIM_PIM_INSTANCE, "PIM global state"); +DEFINE_MTYPE(PIMD, PIM_NEXTHOP_CACHE, "PIM nexthop cache state"); +DEFINE_MTYPE(PIMD, PIM_SSM_INFO, "PIM SSM configuration"); +DEFINE_MTYPE(PIMD, PIM_PLIST_NAME, "PIM Prefix List Names"); +DEFINE_MTYPE(PIMD, PIM_VXLAN_SG, "PIM VxLAN mroute cache"); diff --git a/pimd/pim_memory.h b/pimd/pim_memory.h new file mode 100644 index 0000000..41730e7 --- /dev/null +++ b/pimd/pim_memory.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* pimd memory type declarations + * + * Copyright (C) 2015 David Lamparter + */ + +#ifndef _QUAGGA_PIM_MEMORY_H +#define _QUAGGA_PIM_MEMORY_H + +#include "memory.h" + +DECLARE_MGROUP(PIMD); +DECLARE_MTYPE(PIM_CHANNEL_OIL); +DECLARE_MTYPE(PIM_INTERFACE); +DECLARE_MTYPE(PIM_IGMP_JOIN); +DECLARE_MTYPE(PIM_IGMP_SOCKET); +DECLARE_MTYPE(PIM_IGMP_GROUP); +DECLARE_MTYPE(PIM_IGMP_GROUP_SOURCE); +DECLARE_MTYPE(PIM_NEIGHBOR); +DECLARE_MTYPE(PIM_IFCHANNEL); +DECLARE_MTYPE(PIM_UPSTREAM); +DECLARE_MTYPE(PIM_SSMPINGD); +DECLARE_MTYPE(PIM_STATIC_ROUTE); +DECLARE_MTYPE(PIM_RP); +DECLARE_MTYPE(PIM_FILTER_NAME); +DECLARE_MTYPE(PIM_MSDP_PEER); +DECLARE_MTYPE(PIM_MSDP_MG_NAME); +DECLARE_MTYPE(PIM_MSDP_SA); +DECLARE_MTYPE(PIM_MSDP_MG); +DECLARE_MTYPE(PIM_MSDP_MG_MBR); +DECLARE_MTYPE(PIM_SEC_ADDR); +DECLARE_MTYPE(PIM_JP_AGG_GROUP); +DECLARE_MTYPE(PIM_JP_AGG_SOURCE); +DECLARE_MTYPE(PIM_PIM_INSTANCE); +DECLARE_MTYPE(PIM_NEXTHOP_CACHE); +DECLARE_MTYPE(PIM_SSM_INFO); +DECLARE_MTYPE(PIM_PLIST_NAME); +DECLARE_MTYPE(PIM_VXLAN_SG); + +#endif /* _QUAGGA_PIM_MEMORY_H */ diff --git a/pimd/pim_mlag.c b/pimd/pim_mlag.c new file mode 100644 index 0000000..dcef2d0 --- /dev/null +++ b/pimd/pim_mlag.c @@ -0,0 +1,1089 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of PIM MLAG Functionality + * + * Module name: PIM MLAG + * + * Author: sathesh Kumar karra + * + * Copyright (C) 2019 Cumulus Networks http://www.cumulusnetworks.com + */ +#include + +#include "pimd.h" +#include "pim_mlag.h" +#include "pim_upstream.h" +#include "pim_vxlan.h" + +extern struct zclient *zclient; + +#define PIM_MLAG_METADATA_LEN 4 + +/*********************ACtual Data processing *****************************/ +/* TBD: There can be duplicate updates to FIB***/ +#define PIM_MLAG_ADD_OIF_TO_OIL(ch, ch_oil) \ + do { \ + if (PIM_DEBUG_MLAG) \ + zlog_debug( \ + "%s: add Dual-active Interface to %s " \ + "to oil:%s", \ + __func__, ch->interface->name, ch->sg_str); \ + pim_channel_update_oif_mute(ch_oil, ch->interface->info); \ + } while (0) + +#define PIM_MLAG_DEL_OIF_TO_OIL(ch, ch_oil) \ + do { \ + if (PIM_DEBUG_MLAG) \ + zlog_debug( \ + "%s: del Dual-active Interface to %s " \ + "to oil:%s", \ + __func__, ch->interface->name, ch->sg_str); \ + pim_channel_update_oif_mute(ch_oil, ch->interface->info); \ + } while (0) + + +static void pim_mlag_calculate_df_for_ifchannels(struct pim_upstream *up, + bool is_df) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + struct pim_interface *pim_ifp = NULL; + struct channel_oil *ch_oil = NULL; + + ch_oil = (up) ? up->channel_oil : NULL; + + if (!ch_oil) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Calculating DF for Dual active if-channel%s", + __func__, up->sg_str); + + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + pim_ifp = (ch->interface) ? ch->interface->info : NULL; + if (!pim_ifp || !PIM_I_am_DualActive(pim_ifp)) + continue; + + if (is_df) + PIM_MLAG_ADD_OIF_TO_OIL(ch, ch_oil); + else + PIM_MLAG_DEL_OIF_TO_OIL(ch, ch_oil); + } +} + +static void pim_mlag_inherit_mlag_flags(struct pim_upstream *up, bool is_df) +{ + struct listnode *listnode; + struct pim_upstream *child; + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + struct pim_interface *pim_ifp = NULL; + struct channel_oil *ch_oil = NULL; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Updating DF for uptream:%s children", __func__, + up->sg_str); + + + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + pim_ifp = (ch->interface) ? ch->interface->info : NULL; + if (!pim_ifp || !PIM_I_am_DualActive(pim_ifp)) + continue; + + for (ALL_LIST_ELEMENTS_RO(up->sources, listnode, child)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Updating DF for child:%s", + __func__, child->sg_str); + ch_oil = (child) ? child->channel_oil : NULL; + + if (!ch_oil) + continue; + + if (is_df) + PIM_MLAG_ADD_OIF_TO_OIL(ch, ch_oil); + else + PIM_MLAG_DEL_OIF_TO_OIL(ch, ch_oil); + } + } +} + +/******************************* pim upstream sync **************************/ +/* Update DF role for the upstream entry and return true on role change */ +bool pim_mlag_up_df_role_update(struct pim_instance *pim, + struct pim_upstream *up, bool is_df, const char *reason) +{ + struct channel_oil *c_oil = up->channel_oil; + bool old_is_df = !PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags); + struct pim_interface *vxlan_ifp; + + if (is_df == old_is_df) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: Ignoring Role update for %s, since no change", + __func__, up->sg_str); + return false; + } + + if (PIM_DEBUG_MLAG) + zlog_debug("local MLAG mroute %s role changed to %s based on %s", + up->sg_str, is_df ? "df" : "non-df", reason); + + if (is_df) + PIM_UPSTREAM_FLAG_UNSET_MLAG_NON_DF(up->flags); + else + PIM_UPSTREAM_FLAG_SET_MLAG_NON_DF(up->flags); + + + /* + * This Upstream entry synced to peer Because of Dual-active + * Interface configuration + */ + if (PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(up->flags)) { + pim_mlag_inherit_mlag_flags(up, is_df); + pim_mlag_calculate_df_for_ifchannels(up, is_df); + } + + /* If the DF role has changed check if ipmr-lo needs to be + * muted/un-muted. Active-Active devices and vxlan termination + * devices (ipmr-lo) are suppressed on the non-DF. + * This may leave the mroute with the empty OIL in which case the + * the forwarding entry's sole purpose is to just blackhole the flow + * headed to the switch. + */ + if (c_oil) { + vxlan_ifp = pim_vxlan_get_term_ifp(pim); + if (vxlan_ifp) + pim_channel_update_oif_mute(c_oil, vxlan_ifp); + } + + /* If DF role changed on a (*,G) termination mroute update the + * associated DF role on the inherited (S,G) entries + */ + if (pim_addr_is_any(up->sg.src) && + PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->flags)) + pim_vxlan_inherit_mlag_flags(pim, up, true /* inherit */); + + return true; +} + +/* Run per-upstream entry DF election and return true on role change */ +static bool pim_mlag_up_df_role_elect(struct pim_instance *pim, + struct pim_upstream *up) +{ + bool is_df; + uint32_t peer_cost; + uint32_t local_cost; + bool rv; + + if (!pim_up_mlag_is_local(up)) + return false; + + /* We are yet to rx a status update from the local MLAG daemon so + * we will assume DF status. + */ + if (!(router->mlag_flags & PIM_MLAGF_STATUS_RXED)) + return pim_mlag_up_df_role_update(pim, up, + true /*is_df*/, "mlagd-down"); + + /* If not connected to peer assume DF role on the MLAG primary + * switch (and non-DF on the secondary switch. + */ + if (!(router->mlag_flags & PIM_MLAGF_PEER_CONN_UP)) { + is_df = (router->mlag_role == MLAG_ROLE_PRIMARY) ? true : false; + return pim_mlag_up_df_role_update(pim, up, + is_df, "peer-down"); + } + + /* If MLAG peer session is up but zebra is down on the peer + * assume DF role. + */ + if (!(router->mlag_flags & PIM_MLAGF_PEER_ZEBRA_UP)) + return pim_mlag_up_df_role_update(pim, up, + true /*is_df*/, "zebra-down"); + + /* If we are connected to peer switch but don't have a mroute + * from it we have to assume non-DF role to avoid duplicates. + * Note: When the peer connection comes up we wait for initial + * replay to complete before moving "strays" i.e. local-mlag-mroutes + * without a peer reference to non-df role. + */ + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(up->flags)) + return pim_mlag_up_df_role_update(pim, up, + false /*is_df*/, "no-peer-mroute"); + + /* switch with the lowest RPF cost wins. if both switches have the same + * cost MLAG role is used as a tie breaker (MLAG primary wins). + */ + peer_cost = up->mlag.peer_mrib_metric; + local_cost = pim_up_mlag_local_cost(up); + if (local_cost == peer_cost) { + is_df = (router->mlag_role == MLAG_ROLE_PRIMARY) ? true : false; + rv = pim_mlag_up_df_role_update(pim, up, is_df, "equal-cost"); + } else { + is_df = (local_cost < peer_cost) ? true : false; + rv = pim_mlag_up_df_role_update(pim, up, is_df, "cost"); + } + + return rv; +} + +/* Handle upstream entry add from the peer MLAG switch - + * - if a local entry doesn't exist one is created with reference + * _MLAG_PEER + * - if a local entry exists and has a MLAG OIF DF election is run. + * the non-DF switch stop forwarding traffic to MLAG devices. + */ +static void pim_mlag_up_peer_add(struct mlag_mroute_add *msg) +{ + struct pim_upstream *up; + struct pim_instance *pim; + int flags = 0; + pim_sgaddr sg; + struct vrf *vrf; + + memset(&sg, 0, sizeof(sg)); + sg.src.s_addr = htonl(msg->source_ip); + sg.grp.s_addr = htonl(msg->group_ip); + + if (PIM_DEBUG_MLAG) + zlog_debug("peer MLAG mroute add %s:%pSG cost %d", + msg->vrf_name, &sg, msg->cost_to_rp); + + /* XXX - this is not correct. we MUST cache updates to avoid losing + * an entry because of race conditions with the peer switch. + */ + vrf = vrf_lookup_by_name(msg->vrf_name); + if (!vrf) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "peer MLAG mroute add failed %s:%pSG; no vrf", + msg->vrf_name, &sg); + return; + } + pim = vrf->info; + + up = pim_upstream_find(pim, &sg); + if (up) { + /* upstream already exists; create peer reference if it + * doesn't already exist. + */ + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(up->flags)) + pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_MLAG_PEER, + __func__); + } else { + PIM_UPSTREAM_FLAG_SET_MLAG_PEER(flags); + up = pim_upstream_add(pim, &sg, NULL /*iif*/, flags, __func__, + NULL /*if_ch*/); + + if (!up) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "peer MLAG mroute add failed %s:%pSG", + vrf->name, &sg); + return; + } + } + up->mlag.peer_mrib_metric = msg->cost_to_rp; + pim_mlag_up_df_role_elect(pim, up); +} + +/* Handle upstream entry del from the peer MLAG switch - + * - peer reference is removed. this can result in the upstream + * being deleted altogether. + * - if a local entry continues to exisy and has a MLAG OIF DF election + * is re-run (at the end of which the local entry will be the DF). + */ +static struct pim_upstream *pim_mlag_up_peer_deref(struct pim_instance *pim, + struct pim_upstream *up) +{ + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(up->flags)) + return up; + + PIM_UPSTREAM_FLAG_UNSET_MLAG_PEER(up->flags); + up = pim_upstream_del(pim, up, __func__); + if (up) + pim_mlag_up_df_role_elect(pim, up); + + return up; +} + +static void pim_mlag_up_peer_del(struct mlag_mroute_del *msg) +{ + struct pim_upstream *up; + struct pim_instance *pim; + pim_sgaddr sg; + struct vrf *vrf; + + memset(&sg, 0, sizeof(sg)); + sg.src.s_addr = htonl(msg->source_ip); + sg.grp.s_addr = htonl(msg->group_ip); + + if (PIM_DEBUG_MLAG) + zlog_debug("peer MLAG mroute del %s:%pSG", msg->vrf_name, &sg); + + vrf = vrf_lookup_by_name(msg->vrf_name); + if (!vrf) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "peer MLAG mroute del skipped %s:%pSG; no vrf", + msg->vrf_name, &sg); + return; + } + pim = vrf->info; + + up = pim_upstream_find(pim, &sg); + if (!up) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "peer MLAG mroute del skipped %s:%pSG; no up", + vrf->name, &sg); + return; + } + + (void)pim_mlag_up_peer_deref(pim, up); +} + +/* When we lose connection to the local MLAG daemon we can drop all peer + * references. + */ +static void pim_mlag_up_peer_del_all(void) +{ + struct list *temp = list_new(); + struct pim_upstream *up; + struct vrf *vrf; + struct pim_instance *pim; + + /* + * So why these gyrations? + * pim->upstream_head has the list of *,G and S,G + * that are in the system. The problem of course + * is that it is an ordered list: + * (*,G1) -> (S1,G1) -> (S2,G2) -> (S3, G2) -> (*,G2) -> (S1,G2) + * And the *,G1 has pointers to S1,G1 and S2,G1 + * if we delete *,G1 then we have a situation where + * S1,G1 and S2,G2 can be deleted as well. Then a + * simple ALL_LIST_ELEMENTS will have the next listnode + * pointer become invalid and we crash. + * So let's grab the list of MLAG_PEER upstreams + * add a refcount put on another list and delete safely + */ + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(up->flags)) + continue; + listnode_add(temp, up); + /* + * Add a reference since we are adding to this + * list for deletion + */ + up->ref_count++; + } + + while (temp->count) { + up = listnode_head(temp); + listnode_delete(temp, up); + + up = pim_mlag_up_peer_deref(pim, up); + /* + * This is the deletion of the reference added + * above + */ + if (up) + pim_upstream_del(pim, up, __func__); + } + } + + list_delete(&temp); +} + +/* Send upstream entry to the local MLAG daemon (which will subsequently + * send it to the peer MLAG switch). + */ +static void pim_mlag_up_local_add_send(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct stream *s = NULL; + struct vrf *vrf = pim->vrf; + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) + return; + + s = stream_new(sizeof(struct mlag_mroute_add) + PIM_MLAG_METADATA_LEN); + if (!s) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("local MLAG mroute add %s:%s", + vrf->name, up->sg_str); + + ++router->mlag_stats.msg.mroute_add_tx; + + stream_putl(s, MLAG_MROUTE_ADD); + stream_put(s, vrf->name, VRF_NAMSIZ); + stream_putl(s, ntohl(up->sg.src.s_addr)); + stream_putl(s, ntohl(up->sg.grp.s_addr)); + + stream_putl(s, pim_up_mlag_local_cost(up)); + /* XXX - who is addding*/ + stream_putl(s, MLAG_OWNER_VXLAN); + /* XXX - am_i_DR field should be removed */ + stream_putc(s, false); + stream_putc(s, !(PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags))); + stream_putl(s, vrf->vrf_id); + /* XXX - this field is a No-op for VXLAN*/ + stream_put(s, NULL, IFNAMSIZ); + + stream_fifo_push_safe(router->mlag_fifo, s); + pim_mlag_signal_zpthread(); +} + +static void pim_mlag_up_local_del_send(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct stream *s = NULL; + struct vrf *vrf = pim->vrf; + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) + return; + + s = stream_new(sizeof(struct mlag_mroute_del) + PIM_MLAG_METADATA_LEN); + if (!s) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("local MLAG mroute del %s:%s", + vrf->name, up->sg_str); + + ++router->mlag_stats.msg.mroute_del_tx; + + stream_putl(s, MLAG_MROUTE_DEL); + stream_put(s, vrf->name, VRF_NAMSIZ); + stream_putl(s, ntohl(up->sg.src.s_addr)); + stream_putl(s, ntohl(up->sg.grp.s_addr)); + /* XXX - who is adding */ + stream_putl(s, MLAG_OWNER_VXLAN); + stream_putl(s, vrf->vrf_id); + /* XXX - this field is a No-op for VXLAN */ + stream_put(s, NULL, IFNAMSIZ); + + /* XXX - is this the the most optimal way to do things */ + stream_fifo_push_safe(router->mlag_fifo, s); + pim_mlag_signal_zpthread(); +} + + +/* Called when a local upstream entry is created or if it's cost changes */ +void pim_mlag_up_local_add(struct pim_instance *pim, + struct pim_upstream *up) +{ + pim_mlag_up_df_role_elect(pim, up); + /* XXX - need to add some dup checks here */ + pim_mlag_up_local_add_send(pim, up); +} + +/* Called when local MLAG reference is removed from an upstream entry */ +void pim_mlag_up_local_del(struct pim_instance *pim, + struct pim_upstream *up) +{ + pim_mlag_up_df_role_elect(pim, up); + pim_mlag_up_local_del_send(pim, up); +} + +/* When connection to local MLAG daemon is established all the local + * MLAG upstream entries are replayed to it. + */ +static void pim_mlag_up_local_replay(void) +{ + struct pim_upstream *up; + struct vrf *vrf; + struct pim_instance *pim; + + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_up_mlag_is_local(up)) + pim_mlag_up_local_add_send(pim, up); + } + } +} + +/* on local/peer mlag connection and role changes the DF status needs + * to be re-evaluated + */ +static void pim_mlag_up_local_reeval(bool mlagd_send, const char *reason_code) +{ + struct pim_upstream *up; + struct vrf *vrf; + struct pim_instance *pim; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s re-run DF election because of %s", + __func__, reason_code); + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!pim_up_mlag_is_local(up)) + continue; + /* if role changes re-send to peer */ + if (pim_mlag_up_df_role_elect(pim, up) && + mlagd_send) + pim_mlag_up_local_add_send(pim, up); + } + } +} + +/*****************PIM Actions for MLAG state changes**********************/ + +/* notify the anycast VTEP component about state changes */ +static inline void pim_mlag_vxlan_state_update(void) +{ + bool enable = !!(router->mlag_flags & PIM_MLAGF_STATUS_RXED); + bool peer_state = !!(router->mlag_flags & PIM_MLAGF_PEER_CONN_UP); + + pim_vxlan_mlag_update(enable, peer_state, router->mlag_role, + router->peerlink_rif_p, &router->local_vtep_ip); + +} + +/**************End of PIM Actions for MLAG State changes******************/ + + +/********************API to process PIM MLAG Data ************************/ + +static void pim_mlag_process_mlagd_state_change(struct mlag_status msg) +{ + bool role_chg = false; + bool state_chg = false; + bool notify_vxlan = false; + struct interface *peerlink_rif_p; + char buf[MLAG_ROLE_STRSIZE]; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg dump: my_role: %s, peer_state: %s", + __func__, + mlag_role2str(msg.my_role, buf, sizeof(buf)), + (msg.peer_state == MLAG_STATE_RUNNING ? "RUNNING" + : "DOWN")); + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg ignored mlagd process state down", + __func__); + return; + } + ++router->mlag_stats.msg.mlag_status_updates; + + /* evaluate the changes first */ + if (router->mlag_role != msg.my_role) { + role_chg = true; + notify_vxlan = true; + router->mlag_role = msg.my_role; + } + + strlcpy(router->peerlink_rif, msg.peerlink_rif, + sizeof(router->peerlink_rif)); + + /* XXX - handle the case where we may rx the interface name from the + * MLAG daemon before we get the interface from zebra. + */ + peerlink_rif_p = if_lookup_by_name(router->peerlink_rif, VRF_DEFAULT); + if (router->peerlink_rif_p != peerlink_rif_p) { + router->peerlink_rif_p = peerlink_rif_p; + notify_vxlan = true; + } + + if (msg.peer_state == MLAG_STATE_RUNNING) { + if (!(router->mlag_flags & PIM_MLAGF_PEER_CONN_UP)) { + state_chg = true; + notify_vxlan = true; + router->mlag_flags |= PIM_MLAGF_PEER_CONN_UP; + } + router->connected_to_mlag = true; + } else { + if (router->mlag_flags & PIM_MLAGF_PEER_CONN_UP) { + ++router->mlag_stats.peer_session_downs; + state_chg = true; + notify_vxlan = true; + router->mlag_flags &= ~PIM_MLAGF_PEER_CONN_UP; + } + router->connected_to_mlag = false; + } + + /* apply the changes */ + /* when connection to mlagd comes up we hold send mroutes till we have + * rxed the status and had a chance to re-valuate DF state + */ + if (!(router->mlag_flags & PIM_MLAGF_STATUS_RXED)) { + router->mlag_flags |= PIM_MLAGF_STATUS_RXED; + pim_mlag_vxlan_state_update(); + /* on session up re-eval DF status */ + pim_mlag_up_local_reeval(false /*mlagd_send*/, "mlagd_up"); + /* replay all the upstream entries to the local MLAG daemon */ + pim_mlag_up_local_replay(); + return; + } + + if (notify_vxlan) + pim_mlag_vxlan_state_update(); + + if (state_chg) { + if (!(router->mlag_flags & PIM_MLAGF_PEER_CONN_UP)) + /* when a connection goes down the primary takes over + * DF role for all entries + */ + pim_mlag_up_local_reeval(true /*mlagd_send*/, + "peer_down"); + else + /* XXX - when session comes up we need to wait for + * PEER_REPLAY_DONE before running re-election on + * local-mlag entries that are missing peer reference + */ + pim_mlag_up_local_reeval(true /*mlagd_send*/, + "peer_up"); + } else if (role_chg) { + /* MLAG role changed without a state change */ + pim_mlag_up_local_reeval(true /*mlagd_send*/, "role_chg"); + } +} + +static void pim_mlag_process_peer_frr_state_change(struct mlag_frr_status msg) +{ + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: msg dump: peer_frr_state: %s", __func__, + (msg.frr_state == MLAG_FRR_STATE_UP ? "UP" : "DOWN")); + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg ignored mlagd process state down", + __func__); + return; + } + ++router->mlag_stats.msg.peer_zebra_status_updates; + + /* evaluate the changes first */ + if (msg.frr_state == MLAG_FRR_STATE_UP) { + if (!(router->mlag_flags & PIM_MLAGF_PEER_ZEBRA_UP)) { + router->mlag_flags |= PIM_MLAGF_PEER_ZEBRA_UP; + /* XXX - when peer zebra comes up we need to wait for + * for some time to let the peer setup MDTs before + * before relinquishing DF status + */ + pim_mlag_up_local_reeval(true /*mlagd_send*/, + "zebra_up"); + } + } else { + if (router->mlag_flags & PIM_MLAGF_PEER_ZEBRA_UP) { + ++router->mlag_stats.peer_zebra_downs; + router->mlag_flags &= ~PIM_MLAGF_PEER_ZEBRA_UP; + /* when a peer zebra goes down we assume DF role */ + pim_mlag_up_local_reeval(true /*mlagd_send*/, + "zebra_down"); + } + } +} + +static void pim_mlag_process_vxlan_update(struct mlag_vxlan *msg) +{ + char addr_buf1[INET_ADDRSTRLEN]; + char addr_buf2[INET_ADDRSTRLEN]; + uint32_t local_ip; + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg ignored mlagd process state down", + __func__); + return; + } + + ++router->mlag_stats.msg.vxlan_updates; + router->anycast_vtep_ip.s_addr = htonl(msg->anycast_ip); + local_ip = htonl(msg->local_ip); + if (router->local_vtep_ip.s_addr != local_ip) { + router->local_vtep_ip.s_addr = local_ip; + pim_mlag_vxlan_state_update(); + } + + if (PIM_DEBUG_MLAG) { + inet_ntop(AF_INET, &router->local_vtep_ip, + addr_buf1, INET_ADDRSTRLEN); + inet_ntop(AF_INET, &router->anycast_vtep_ip, + addr_buf2, INET_ADDRSTRLEN); + + zlog_debug("%s: msg dump: local-ip:%s, anycast-ip:%s", + __func__, addr_buf1, addr_buf2); + } +} + +static void pim_mlag_process_mroute_add(struct mlag_mroute_add msg) +{ + if (PIM_DEBUG_MLAG) { + pim_sgaddr sg; + + sg.grp.s_addr = ntohl(msg.group_ip); + sg.src.s_addr = ntohl(msg.source_ip); + + zlog_debug( + "%s: msg dump: vrf_name: %s, s.ip: 0x%x, g.ip: 0x%x (%pSG) cost: %u", + __func__, msg.vrf_name, msg.source_ip, msg.group_ip, + &sg, msg.cost_to_rp); + zlog_debug( + "(%pSG)owner_id: %d, DR: %d, Dual active: %d, vrf_id: 0x%x intf_name: %s", + &sg, msg.owner_id, msg.am_i_dr, msg.am_i_dual_active, + msg.vrf_id, msg.intf_name); + } + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg ignored mlagd process state down", + __func__); + return; + } + + ++router->mlag_stats.msg.mroute_add_rx; + + pim_mlag_up_peer_add(&msg); +} + +static void pim_mlag_process_mroute_del(struct mlag_mroute_del msg) +{ + if (PIM_DEBUG_MLAG) { + pim_sgaddr sg; + + sg.grp.s_addr = ntohl(msg.group_ip); + sg.src.s_addr = ntohl(msg.source_ip); + zlog_debug( + "%s: msg dump: vrf_name: %s, s.ip: 0x%x, g.ip: 0x%x(%pSG)", + __func__, msg.vrf_name, msg.source_ip, msg.group_ip, + &sg); + zlog_debug("(%pSG)owner_id: %d, vrf_id: 0x%x intf_name: %s", + &sg, msg.owner_id, msg.vrf_id, msg.intf_name); + } + + if (!(router->mlag_flags & PIM_MLAGF_LOCAL_CONN_UP)) { + if (PIM_DEBUG_MLAG) + zlog_debug("%s: msg ignored mlagd process state down", + __func__); + return; + } + + ++router->mlag_stats.msg.mroute_del_rx; + + pim_mlag_up_peer_del(&msg); +} + +int pim_zebra_mlag_handle_msg(int cmd, struct zclient *zclient, + uint16_t zapi_length, vrf_id_t vrf_id) +{ + struct stream *s = zclient->ibuf; + struct mlag_msg mlag_msg; + char buf[80]; + int rc = 0; + size_t length; + + rc = mlag_lib_decode_mlag_hdr(s, &mlag_msg, &length); + if (rc) + return (rc); + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Received msg type: %s length: %d, bulk_cnt: %d", + __func__, + mlag_lib_msgid_to_str(mlag_msg.msg_type, buf, + sizeof(buf)), + mlag_msg.data_len, mlag_msg.msg_cnt); + + switch (mlag_msg.msg_type) { + case MLAG_STATUS_UPDATE: { + struct mlag_status msg; + + rc = mlag_lib_decode_mlag_status(s, &msg); + if (rc) + return (rc); + pim_mlag_process_mlagd_state_change(msg); + } break; + case MLAG_PEER_FRR_STATUS: { + struct mlag_frr_status msg; + + rc = mlag_lib_decode_frr_status(s, &msg); + if (rc) + return (rc); + pim_mlag_process_peer_frr_state_change(msg); + } break; + case MLAG_VXLAN_UPDATE: { + struct mlag_vxlan msg; + + rc = mlag_lib_decode_vxlan_update(s, &msg); + if (rc) + return rc; + pim_mlag_process_vxlan_update(&msg); + } break; + case MLAG_MROUTE_ADD: { + struct mlag_mroute_add msg; + + rc = mlag_lib_decode_mroute_add(s, &msg, &length); + if (rc) + return (rc); + pim_mlag_process_mroute_add(msg); + } break; + case MLAG_MROUTE_DEL: { + struct mlag_mroute_del msg; + + rc = mlag_lib_decode_mroute_del(s, &msg, &length); + if (rc) + return (rc); + pim_mlag_process_mroute_del(msg); + } break; + case MLAG_MROUTE_ADD_BULK: { + struct mlag_mroute_add msg; + int i; + + for (i = 0; i < mlag_msg.msg_cnt; i++) { + rc = mlag_lib_decode_mroute_add(s, &msg, &length); + if (rc) + return (rc); + pim_mlag_process_mroute_add(msg); + } + } break; + case MLAG_MROUTE_DEL_BULK: { + struct mlag_mroute_del msg; + int i; + + for (i = 0; i < mlag_msg.msg_cnt; i++) { + rc = mlag_lib_decode_mroute_del(s, &msg, &length); + if (rc) + return (rc); + pim_mlag_process_mroute_del(msg); + } + } break; + case MLAG_MSG_NONE: + case MLAG_REGISTER: + case MLAG_DEREGISTER: + case MLAG_DUMP: + case MLAG_PIM_CFG_DUMP: + break; + } + return 0; +} + +/****************End of PIM Mesasge processing handler********************/ + +int pim_zebra_mlag_process_up(ZAPI_CALLBACK_ARGS) +{ + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Received Process-Up from Mlag", __func__); + + /* + * Incase of local MLAG restart, PIM needs to replay all the data + * since MLAG is empty. + */ + router->connected_to_mlag = true; + router->mlag_flags |= PIM_MLAGF_LOCAL_CONN_UP; + return 0; +} + +static void pim_mlag_param_reset(void) +{ + /* reset the cached params and stats */ + router->mlag_flags &= ~(PIM_MLAGF_STATUS_RXED | + PIM_MLAGF_LOCAL_CONN_UP | + PIM_MLAGF_PEER_CONN_UP | + PIM_MLAGF_PEER_ZEBRA_UP); + router->local_vtep_ip.s_addr = INADDR_ANY; + router->anycast_vtep_ip.s_addr = INADDR_ANY; + router->mlag_role = MLAG_ROLE_NONE; + memset(&router->mlag_stats.msg, 0, sizeof(router->mlag_stats.msg)); + router->peerlink_rif[0] = '\0'; +} + +int pim_zebra_mlag_process_down(ZAPI_CALLBACK_ARGS) +{ + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Received Process-Down from Mlag", __func__); + + /* Local CLAG is down, reset peer data and forward the traffic if + * we are DR + */ + if (router->mlag_flags & PIM_MLAGF_PEER_CONN_UP) + ++router->mlag_stats.peer_session_downs; + if (router->mlag_flags & PIM_MLAGF_PEER_ZEBRA_UP) + ++router->mlag_stats.peer_zebra_downs; + router->connected_to_mlag = false; + pim_mlag_param_reset(); + /* on mlagd session down re-eval DF status */ + pim_mlag_up_local_reeval(false /*mlagd_send*/, "mlagd_down"); + /* flush all peer references */ + pim_mlag_up_peer_del_all(); + /* notify the vxlan component */ + pim_mlag_vxlan_state_update(); + return 0; +} + +static void pim_mlag_register_handler(struct event *thread) +{ + uint32_t bit_mask = 0; + + if (!zclient) + return; + + SET_FLAG(bit_mask, (1 << MLAG_STATUS_UPDATE)); + SET_FLAG(bit_mask, (1 << MLAG_MROUTE_ADD)); + SET_FLAG(bit_mask, (1 << MLAG_MROUTE_DEL)); + SET_FLAG(bit_mask, (1 << MLAG_DUMP)); + SET_FLAG(bit_mask, (1 << MLAG_MROUTE_ADD_BULK)); + SET_FLAG(bit_mask, (1 << MLAG_MROUTE_DEL_BULK)); + SET_FLAG(bit_mask, (1 << MLAG_PIM_CFG_DUMP)); + SET_FLAG(bit_mask, (1 << MLAG_VXLAN_UPDATE)); + SET_FLAG(bit_mask, (1 << MLAG_PEER_FRR_STATUS)); + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Posting Client Register to MLAG mask: 0x%x", + __func__, bit_mask); + + zclient_send_mlag_register(zclient, bit_mask); +} + +void pim_mlag_register(void) +{ + if (router->mlag_process_register) + return; + + router->mlag_process_register = true; + + event_add_event(router->master, pim_mlag_register_handler, NULL, 0, + NULL); +} + +static void pim_mlag_deregister_handler(struct event *thread) +{ + if (!zclient) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Posting Client De-Register to MLAG from PIM", + __func__); + router->connected_to_mlag = false; + zclient_send_mlag_deregister(zclient); +} + +void pim_mlag_deregister(void) +{ + /* if somebody still interested in the MLAG channel skip de-reg */ + if (router->pim_mlag_intf_cnt || pim_vxlan_do_mlag_reg()) + return; + + /* not registered; nothing do */ + if (!router->mlag_process_register) + return; + + router->mlag_process_register = false; + + event_add_event(router->master, pim_mlag_deregister_handler, NULL, 0, + NULL); +} + +void pim_if_configure_mlag_dualactive(struct pim_interface *pim_ifp) +{ + if (!pim_ifp || !pim_ifp->pim || pim_ifp->activeactive == true) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: Configuring active-active on Interface: %s", + __func__, "NULL"); + + pim_ifp->activeactive = true; + if (pim_ifp->pim) + pim_ifp->pim->inst_mlag_intf_cnt++; + + router->pim_mlag_intf_cnt++; + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: Total MLAG configured Interfaces on router: %d, Inst: %d", + __func__, router->pim_mlag_intf_cnt, + pim_ifp->pim->inst_mlag_intf_cnt); + + if (router->pim_mlag_intf_cnt == 1) { + /* + * at least one Interface is configured for MLAG, send register + * to Zebra for receiving MLAG Updates + */ + pim_mlag_register(); + } +} + +void pim_if_unconfigure_mlag_dualactive(struct pim_interface *pim_ifp) +{ + if (!pim_ifp || !pim_ifp->pim || pim_ifp->activeactive == false) + return; + + if (PIM_DEBUG_MLAG) + zlog_debug("%s: UnConfiguring active-active on Interface: %s", + __func__, "NULL"); + + pim_ifp->activeactive = false; + pim_ifp->pim->inst_mlag_intf_cnt--; + + router->pim_mlag_intf_cnt--; + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: Total MLAG configured Interfaces on router: %d, Inst: %d", + __func__, router->pim_mlag_intf_cnt, + pim_ifp->pim->inst_mlag_intf_cnt); + + if (router->pim_mlag_intf_cnt == 0) { + /* + * all the Interfaces are MLAG un-configured, post MLAG + * De-register to Zebra + */ + pim_mlag_deregister(); + pim_mlag_param_reset(); + } +} + + +void pim_instance_mlag_init(struct pim_instance *pim) +{ + if (!pim) + return; + + pim->inst_mlag_intf_cnt = 0; +} + + +void pim_instance_mlag_terminate(struct pim_instance *pim) +{ + struct interface *ifp; + + if (!pim) + return; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp || pim_ifp->activeactive == false) + continue; + + pim_if_unconfigure_mlag_dualactive(pim_ifp); + } + pim->inst_mlag_intf_cnt = 0; +} + +void pim_mlag_terminate(void) +{ + stream_free(router->mlag_stream); + router->mlag_stream = NULL; + stream_fifo_free(router->mlag_fifo); + router->mlag_fifo = NULL; +} + +void pim_mlag_init(void) +{ + pim_mlag_param_reset(); + router->pim_mlag_intf_cnt = 0; + router->connected_to_mlag = false; + router->mlag_fifo = stream_fifo_new(); + router->zpthread_mlag_write = NULL; + router->mlag_stream = stream_new(MLAG_BUF_LIMIT); +} diff --git a/pimd/pim_mlag.h b/pimd/pim_mlag.h new file mode 100644 index 0000000..9cabd32 --- /dev/null +++ b/pimd/pim_mlag.h @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is an implementation of PIM MLAG Functionality + * + * Module name: PIM MLAG + * + * Author: sathesh Kumar karra + * + * Copyright (C) 2019 Cumulus Networks http://www.cumulusnetworks.com + */ +#ifndef __PIM_MLAG_H__ +#define __PIM_MLAG_H__ + +#include "zclient.h" +#include "mlag.h" +#include "pim_iface.h" + +#if PIM_IPV == 4 +extern void pim_mlag_init(void); +extern void pim_mlag_terminate(void); +extern void pim_instance_mlag_init(struct pim_instance *pim); +extern void pim_instance_mlag_terminate(struct pim_instance *pim); +extern void pim_if_configure_mlag_dualactive(struct pim_interface *pim_ifp); +extern void pim_if_unconfigure_mlag_dualactive(struct pim_interface *pim_ifp); +extern int pim_zebra_mlag_process_up(ZAPI_CALLBACK_ARGS); +extern int pim_zebra_mlag_process_down(ZAPI_CALLBACK_ARGS); +extern int pim_zebra_mlag_handle_msg(ZAPI_CALLBACK_ARGS); + +/* pm_zpthread.c */ +extern int pim_mlag_signal_zpthread(void); +extern void pim_zpthread_init(void); +extern void pim_zpthread_terminate(void); + +extern void pim_mlag_register(void); +extern void pim_mlag_deregister(void); +extern void pim_mlag_up_local_add(struct pim_instance *pim, + struct pim_upstream *upstream); +extern void pim_mlag_up_local_del(struct pim_instance *pim, + struct pim_upstream *upstream); +extern bool pim_mlag_up_df_role_update(struct pim_instance *pim, + struct pim_upstream *up, bool is_df, + const char *reason); +#else /* PIM_IPV == 4 */ +static inline void pim_mlag_terminate(void) +{ +} + +static inline void pim_instance_mlag_init(struct pim_instance *pim) +{ +} + +static inline void pim_instance_mlag_terminate(struct pim_instance *pim) +{ +} + +static inline void pim_if_configure_mlag_dualactive( + struct pim_interface *pim_ifp) +{ +} + +static inline void pim_if_unconfigure_mlag_dualactive( + struct pim_interface *pim_ifp) +{ +} + +static inline void pim_mlag_register(void) +{ +} + +static inline void pim_mlag_up_local_add(struct pim_instance *pim, + struct pim_upstream *upstream) +{ +} + +static inline void pim_mlag_up_local_del(struct pim_instance *pim, + struct pim_upstream *upstream) +{ +} + +static inline bool pim_mlag_up_df_role_update(struct pim_instance *pim, + struct pim_upstream *up, + bool is_df, const char *reason) +{ + return false; +} +#endif + +#endif diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c new file mode 100644 index 0000000..adc47e7 --- /dev/null +++ b/pimd/pim_mroute.c @@ -0,0 +1,1373 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include +#include +#include + +#include "log.h" +#include "privs.h" +#include "if.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" +#include "sockopt.h" +#include "lib_errors.h" +#include "lib/network.h" + +#include "pimd.h" +#include "pim_rpf.h" +#include "pim_mroute.h" +#include "pim_oil.h" +#include "pim_str.h" +#include "pim_time.h" +#include "pim_iface.h" +#include "pim_macro.h" +#include "pim_rp.h" +#include "pim_oil.h" +#include "pim_register.h" +#include "pim_ifchannel.h" +#include "pim_zlookup.h" +#include "pim_ssm.h" +#include "pim_sock.h" +#include "pim_vxlan.h" +#include "pim_msg.h" + +static void mroute_read_on(struct pim_instance *pim); +static int pim_upstream_mroute_update(struct channel_oil *c_oil, + const char *name); + +int pim_mroute_set(struct pim_instance *pim, int enable) +{ + int err; + int opt, data; + socklen_t data_len = sizeof(data); + + /* + * We need to create the VRF table for the pim mroute_socket + */ + if (enable && pim->vrf->vrf_id != VRF_DEFAULT) { + frr_with_privs (&pimd_privs) { + + data = pim->vrf->data.l.table_id; + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, + MRT_TABLE, &data, data_len); + if (err) { + if (err == ENOPROTOOPT) + zlog_err("%s Kernel is not compiled with CONFIG_IP_MROUTE_MULTIPLE_TABLES and vrf's will not work", + __func__); + else + zlog_warn("%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO, MRT_TABLE=%d): errno=%d: %s", + __FILE__, __func__, + pim->mroute_socket, data, + errno, safe_strerror(errno)); + return -1; + } + } + } + + frr_with_privs (&pimd_privs) { + opt = enable ? MRT_INIT : MRT_DONE; + /* + * *BSD *cares* about what value we pass down + * here + */ + data = 1; + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, opt, &data, + data_len); + if (err) { + zlog_warn( + "%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,%s=%d): errno=%d: %s", + __FILE__, __func__, pim->mroute_socket, + enable ? "MRT_INIT" : "MRT_DONE", data, errno, + safe_strerror(errno)); + return -1; + } + } + +#if defined(HAVE_IP_PKTINFO) + if (enable) { + /* Linux and Solaris IP_PKTINFO */ + data = 1; + if (setsockopt(pim->mroute_socket, PIM_IPPROTO, IP_PKTINFO, + &data, data_len)) { + zlog_warn( + "Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", + pim->mroute_socket, errno, + safe_strerror(errno)); + } + } +#endif + +#if PIM_IPV == 6 + if (enable) { + /* Linux and Solaris IPV6_PKTINFO */ + data = 1; + if (setsockopt(pim->mroute_socket, PIM_IPPROTO, + IPV6_RECVPKTINFO, &data, data_len)) { + zlog_warn( + "Could not set IPV6_RECVPKTINFO on socket fd=%d: errno=%d: %s", + pim->mroute_socket, errno, + safe_strerror(errno)); + } + } +#endif + setsockopt_so_recvbuf(pim->mroute_socket, 1024 * 1024 * 8); + + if (set_nonblocking(pim->mroute_socket) < 0) { + zlog_warn( + "Could not set non blocking on socket fd=%d: errno=%d: %s", + pim->mroute_socket, errno, safe_strerror(errno)); + return -1; + } + + if (enable) { +#if defined linux + int upcalls = GMMSG_WRVIFWHOLE; + opt = MRT_PIM; + + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, opt, &upcalls, + sizeof(upcalls)); + if (err) { + zlog_warn( + "Failure to register for VIFWHOLE and WRONGVIF upcalls %d %s", + errno, safe_strerror(errno)); + return -1; + } +#else + zlog_warn( + "PIM-SM will not work properly on this platform, until the ability to receive the WRVIFWHOLE upcall"); +#endif + } + + return 0; +} + +static const char *const gmmsgtype2str[GMMSG_WRVIFWHOLE + 1] = { + "", "NOCACHE", "WRONGVIF", "WHOLEPKT", "WRVIFWHOLE"}; + + +int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_upstream *up; + pim_sgaddr sg; + bool desync = false; + + memset(&sg, 0, sizeof(sg)); + sg.src = msg->msg_im_src; + sg.grp = msg->msg_im_dst; + + + if (!pim_ifp || !pim_ifp->pim_enable) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: %s on interface, dropping packet to %pSG", + ifp->name, + !pim_ifp ? "Multicast not enabled" + : "PIM not enabled", + &sg); + return 0; + } + + if (!pim_is_grp_ssm(pim_ifp->pim, sg.grp)) { + /* for ASM, check that we have enough information (i.e. path + * to RP) to make a decision on what to do with this packet. + * + * for SSM, this is meaningless, everything is join-driven, + * and for NOCACHE we need to install an empty OIL MFC entry + * so the kernel doesn't keep nagging us. + */ + struct pim_rpf *rpg; + + rpg = RP(pim_ifp->pim, msg->msg_im_dst); + if (!rpg) { + if (PIM_DEBUG_MROUTE) + zlog_debug("%s: no RPF for packet to %pSG", + ifp->name, &sg); + return 0; + } + if (pim_rpf_addr_is_inaddr_any(rpg)) { + if (PIM_DEBUG_MROUTE) + zlog_debug("%s: null RPF for packet to %pSG", + ifp->name, &sg); + return 0; + } + } + + /* + * If we've received a multicast packet that isn't connected to + * us + */ + if (!pim_if_connected_to_source(ifp, msg->msg_im_src)) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: incoming packet to %pSG from non-connected source", + ifp->name, &sg); + return 0; + } + + if (!(PIM_I_am_DR(pim_ifp))) { + /* unlike the other debug messages, this one is further in the + * "normal operation" category and thus under _DETAIL + */ + if (PIM_DEBUG_MROUTE_DETAIL) + zlog_debug( + "%s: not DR on interface, not forwarding traffic for %pSG", + ifp->name, &sg); + + /* + * We are not the DR, but we are still receiving packets + * Let's blackhole those packets for the moment + * As that they will be coming up to the cpu + * and causing us to consider them. + * + * This *will* create a dangling channel_oil + * that I see no way to get rid of. Just noting + * this for future reference. + */ + up = pim_upstream_find_or_add( + &sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, __func__); + pim_upstream_mroute_add(up->channel_oil, __func__); + + return 0; + } + + up = pim_upstream_find_or_add(&sg, ifp, PIM_UPSTREAM_FLAG_MASK_FHR, + __func__); + if (up->channel_oil->installed) { + zlog_warn( + "%s: NOCACHE for %pSG, MFC entry disappeared - reinstalling", + ifp->name, &sg); + desync = true; + } + + /* + * I moved this debug till after the actual add because + * I want to take advantage of the up->sg_str being filled in. + */ + if (PIM_DEBUG_MROUTE) { + zlog_debug("%s: Adding a Route %s for WHOLEPKT consumption", + __func__, up->sg_str); + } + + PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); + pim_upstream_keep_alive_timer_start(up, pim_ifp->pim->keep_alive_time); + + up->channel_oil->cc.pktcnt++; + // resolve mfcc_parent prior to mroute_add in channel_add_oif + if (up->rpf.source_nexthop.interface && + *oil_incoming_vif(up->channel_oil) >= MAXVIFS) { + pim_upstream_mroute_iif_update(up->channel_oil, __func__); + } + pim_register_join(up); + /* if we have receiver, inherit from parent */ + pim_upstream_inherited_olist_decide(pim_ifp->pim, up); + + /* we just got NOCACHE from the kernel, so... MFC is not in the + * kernel for some reason or another. Try installing again. + */ + if (desync) + pim_upstream_mroute_update(up->channel_oil, __func__); + return 0; +} + +int pim_mroute_msg_wholepkt(int fd, struct interface *ifp, const char *buf, + size_t len) +{ + struct pim_interface *pim_ifp; + pim_sgaddr sg; + struct pim_rpf *rpg; + const ipv_hdr *ip_hdr; + struct pim_upstream *up; + + pim_ifp = ifp->info; + + ip_hdr = (const ipv_hdr *)buf; + + memset(&sg, 0, sizeof(sg)); + sg.src = IPV_SRC(ip_hdr); + sg.grp = IPV_DST(ip_hdr); + + up = pim_upstream_find(pim_ifp->pim, &sg); + if (!up) { + pim_sgaddr star = sg; + star.src = PIMADDR_ANY; + + up = pim_upstream_find(pim_ifp->pim, &star); + + if (up && PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR(up->flags)) { + up = pim_upstream_add(pim_ifp->pim, &sg, ifp, + PIM_UPSTREAM_FLAG_MASK_SRC_LHR, + __func__, NULL); + if (!up) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: Unable to create upstream information for %pSG", + __func__, &sg); + return 0; + } + pim_upstream_keep_alive_timer_start( + up, pim_ifp->pim->keep_alive_time); + pim_upstream_inherited_olist(pim_ifp->pim, up); + pim_upstream_update_join_desired(pim_ifp->pim, up); + + if (PIM_DEBUG_MROUTE) + zlog_debug("%s: Creating %s upstream on LHR", + __func__, up->sg_str); + return 0; + } + if (PIM_DEBUG_MROUTE_DETAIL) { + zlog_debug( + "%s: Unable to find upstream channel WHOLEPKT%pSG", + __func__, &sg); + } + return 0; + } + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return 0; + } + + pim_ifp = up->rpf.source_nexthop.interface->info; + + rpg = pim_ifp ? RP(pim_ifp->pim, sg.grp) : NULL; + + if ((pim_rpf_addr_is_inaddr_any(rpg)) || (!pim_ifp) || + (!(PIM_I_am_DR(pim_ifp)))) { + if (PIM_DEBUG_MROUTE) { + zlog_debug("%s: Failed Check send packet", __func__); + } + return 0; + } + + /* + * If we've received a register suppress + */ + if (!up->t_rs_timer) { + if (pim_is_grp_ssm(pim_ifp->pim, sg.grp)) { + if (PIM_DEBUG_PIM_REG) + zlog_debug( + "%pSG register forward skipped as group is SSM", + &sg); + return 0; + } + + if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) { + if (PIM_DEBUG_PIM_REG) + zlog_debug( + "%s register forward skipped, not FHR", + up->sg_str); + return 0; + } + + pim_register_send((uint8_t *)buf + sizeof(ipv_hdr), + len - sizeof(ipv_hdr), + pim_ifp->primary_address, rpg, 0, up); + } + return 0; +} + +int pim_mroute_msg_wrongvif(int fd, struct interface *ifp, const kernmsg *msg) +{ + struct pim_ifchannel *ch; + struct pim_interface *pim_ifp; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = msg->msg_im_src; + sg.grp = msg->msg_im_dst; + + /* + Send Assert(S,G) on iif as response to WRONGVIF kernel upcall. + + RFC 4601 4.8.2. PIM-SSM-Only Routers + + iif is the incoming interface of the packet. + if (iif is in inherited_olist(S,G)) { + send Assert(S,G) on iif + } + */ + + if (!ifp) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: WRONGVIF (S,G)=%pSG could not find input interface for input_vif_index=%d", + __func__, &sg, msg->msg_im_vif); + return -1; + } + + pim_ifp = ifp->info; + if (!pim_ifp) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: WRONGVIF (S,G)=%pSG multicast not enabled on interface %s", + __func__, &sg, ifp->name); + return -2; + } + + ch = pim_ifchannel_find(ifp, &sg); + if (!ch) { + pim_sgaddr star_g = sg; + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: WRONGVIF (S,G)=%pSG could not find channel on interface %s", + __func__, &sg, ifp->name); + + star_g.src = PIMADDR_ANY; + ch = pim_ifchannel_find(ifp, &star_g); + if (!ch) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s: WRONGVIF (*,G)=%pSG could not find channel on interface %s", + __func__, &star_g, ifp->name); + return -3; + } + } + + /* + RFC 4601: 4.6.1. (S,G) Assert Message State Machine + + Transitions from NoInfo State + + An (S,G) data packet arrives on interface I, AND + CouldAssert(S,G,I)==TRUE An (S,G) data packet arrived on an + downstream interface that is in our (S,G) outgoing interface + list. We optimistically assume that we will be the assert + winner for this (S,G), and so we transition to the "I am Assert + Winner" state and perform Actions A1 (below), which will + initiate the assert negotiation for (S,G). + */ + + if (ch->ifassert_state != PIM_IFASSERT_NOINFO) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s: WRONGVIF (S,G)=%s channel is not on Assert NoInfo state for interface %s", + __func__, ch->sg_str, ifp->name); + } + return -4; + } + + if (!PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s: WRONGVIF (S,G)=%s interface %s is not downstream for channel", + __func__, ch->sg_str, ifp->name); + } + return -5; + } + + if (assert_action_a1(ch)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s: WRONGVIF (S,G)=%s assert_action_a1 failure on interface %s", + __func__, ch->sg_str, ifp->name); + } + return -6; + } + + return 0; +} + +int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf, + size_t len) +{ + const ipv_hdr *ip_hdr = (const ipv_hdr *)buf; + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct pim_ifchannel *ch; + struct pim_upstream *up; + pim_sgaddr star_g; + pim_sgaddr sg; + + pim_ifp = ifp->info; + + memset(&sg, 0, sizeof(sg)); + sg.src = IPV_SRC(ip_hdr); + sg.grp = IPV_DST(ip_hdr); + + ch = pim_ifchannel_find(ifp, &sg); + if (ch) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "WRVIFWHOLE (S,G)=%s found ifchannel on interface %s", + ch->sg_str, ifp->name); + return -1; + } + + star_g = sg; + star_g.src = PIMADDR_ANY; + + pim = pim_ifp->pim; + /* + * If the incoming interface is the pimreg, then + * we know the callback is associated with a pim register + * packet and there is nothing to do here as that + * normal pim processing will see the packet and allow + * us to do the right thing. + */ + if (ifp == pim->regiface) { + return 0; + } + + up = pim_upstream_find(pim_ifp->pim, &sg); + if (up) { + struct pim_upstream *parent; + struct pim_nexthop source; + struct pim_rpf *rpf = RP(pim_ifp->pim, sg.grp); + + /* No RPF or No RPF interface or No mcast on RPF interface */ + if (!rpf || !rpf->source_nexthop.interface || + !rpf->source_nexthop.interface->info) + return 0; + + /* + * If we have received a WRVIFWHOLE and are at this + * point, we could be receiving the packet on the *,G + * tree, let's check and if so we can safely drop + * it. + */ + parent = pim_upstream_find(pim_ifp->pim, &star_g); + if (parent && parent->rpf.source_nexthop.interface == ifp) + return 0; + + pim_ifp = rpf->source_nexthop.interface->info; + + memset(&source, 0, sizeof(source)); + /* + * If we are the fhr that means we are getting a callback during + * the pimreg period, so I believe we can ignore this packet + */ + if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) { + /* + * No if channel, but upstream we are at the RP. + * + * This could be a anycast RP too and we may + * not have received a register packet from + * the source here at all. So gracefully + * bow out of doing a nexthop lookup and + * setting the SPTBIT to true + */ + if (!(pim_addr_is_any(up->upstream_register)) && + pim_nexthop_lookup(pim_ifp->pim, &source, + up->upstream_register, 0)) { + pim_register_stop_send(source.interface, &sg, + pim_ifp->primary_address, + up->upstream_register); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + } + + pim_upstream_inherited_olist(pim_ifp->pim, up); + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, + __func__); + } else { + if (I_am_RP(pim_ifp->pim, up->sg.grp)) { + if (pim_nexthop_lookup(pim_ifp->pim, &source, + up->upstream_register, + 0)) + pim_register_stop_send( + source.interface, &sg, + pim_ifp->primary_address, + up->upstream_register); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + } else { + /* + * At this point pimd is connected to + * the source, it has a parent, we are not + * the RP and the SPTBIT should be set + * since we know *the* S,G is on the SPT. + * The first time this happens, let's cause + * an immediate join to go out so that + * the RP can trim this guy immediately + * if necessary, instead of waiting + * one join/prune send cycle + */ + if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE && + up->parent && + up->rpf.source_nexthop.interface != + up->parent->rpf.source_nexthop + .interface) { + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + pim_jp_agg_single_upstream_send( + &up->parent->rpf, up->parent, + true); + } + } + pim_upstream_keep_alive_timer_start( + up, pim_ifp->pim->keep_alive_time); + pim_upstream_inherited_olist(pim_ifp->pim, up); + pim_mroute_msg_wholepkt(fd, ifp, buf, len); + } + return 0; + } + + pim_ifp = ifp->info; + if (pim_if_connected_to_source(ifp, sg.src)) { + up = pim_upstream_add(pim_ifp->pim, &sg, ifp, + PIM_UPSTREAM_FLAG_MASK_FHR, __func__, + NULL); + if (!up) { + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%pSG: WRONGVIF%s unable to create upstream on interface", + &sg, ifp->name); + return -2; + } + PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); + pim_upstream_keep_alive_timer_start( + up, pim_ifp->pim->keep_alive_time); + up->channel_oil->cc.pktcnt++; + pim_register_join(up); + pim_upstream_inherited_olist(pim_ifp->pim, up); + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, __func__); + + // Send the packet to the RP + pim_mroute_msg_wholepkt(fd, ifp, buf, len); + } else { + up = pim_upstream_add(pim_ifp->pim, &sg, ifp, + PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, + __func__, NULL); + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, __func__); + } + + return 0; +} + +#if PIM_IPV == 4 +static int process_igmp_packet(struct pim_instance *pim, const char *buf, + size_t buf_size, ifindex_t ifindex) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct in_addr ifaddr; + struct gm_sock *igmp; + const struct prefix *connected_src; + const struct ip *ip_hdr = (const struct ip *)buf; + + /* We have the IP packet but we do not know which interface this + * packet was + * received on. Find the interface that is on the same subnet as + * the source + * of the IP packet. + */ + ifp = if_lookup_by_index(ifindex, pim->vrf->vrf_id); + + if (!ifp || !ifp->info) + return 0; + + connected_src = pim_if_connected_to_source(ifp, ip_hdr->ip_src); + + if (!connected_src && !pim_addr_is_any(ip_hdr->ip_src)) { + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "Recv IGMP packet on interface: %s from a non-connected source: %pI4", + ifp->name, &ip_hdr->ip_src); + } + return 0; + } + + pim_ifp = ifp->info; + ifaddr = connected_src ? connected_src->u.prefix4 + : pim_ifp->primary_address; + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr); + + if (PIM_DEBUG_GM_PACKETS) { + zlog_debug( + "%s(%s): igmp kernel upcall on %s(%p) for %pI4 -> %pI4", + __func__, pim->vrf->name, ifp->name, igmp, + &ip_hdr->ip_src, &ip_hdr->ip_dst); + } + if (igmp) + pim_igmp_packet(igmp, (char *)buf, buf_size); + else if (PIM_DEBUG_GM_PACKETS) + zlog_debug( + "No IGMP socket on interface: %s with connected source: %pI4", + ifp->name, &ifaddr); + + return 0; +} +#endif + +int pim_mroute_msg(struct pim_instance *pim, const char *buf, size_t buf_size, + ifindex_t ifindex) +{ + struct interface *ifp; + const ipv_hdr *ip_hdr; + const kernmsg *msg; + + if (buf_size < (int)sizeof(ipv_hdr)) + return 0; + + ip_hdr = (const ipv_hdr *)buf; + +#if PIM_IPV == 4 + if (ip_hdr->ip_p == IPPROTO_IGMP) { + process_igmp_packet(pim, buf, buf_size, ifindex); + } else if (ip_hdr->ip_p) { + if (PIM_DEBUG_MROUTE_DETAIL) { + zlog_debug( + "%s: no kernel upcall proto=%d src: %pI4 dst: %pI4 msg_size=%ld", + __func__, ip_hdr->ip_p, &ip_hdr->ip_src, + &ip_hdr->ip_dst, (long int)buf_size); + } + + } else { +#else + + if ((ip_hdr->ip6_vfc & 0xf) == 0) { +#endif + msg = (const kernmsg *)buf; + + ifp = pim_if_find_by_vif_index(pim, msg->msg_im_vif); + + if (!ifp) + return 0; + if (PIM_DEBUG_MROUTE) { +#if PIM_IPV == 4 + zlog_debug( + "%s: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI4,%pI4) on %s vifi=%d size=%ld", + __func__, gmmsgtype2str[msg->msg_im_msgtype], + msg->msg_im_msgtype, ip_hdr->ip_p, + pim->mroute_socket, &msg->msg_im_src, + &msg->msg_im_dst, ifp->name, msg->msg_im_vif, + (long int)buf_size); +#else + zlog_debug( + "%s: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI6,%pI6) on %s vifi=%d size=%ld", + __func__, gmmsgtype2str[msg->msg_im_msgtype], + msg->msg_im_msgtype, ip_hdr->ip6_nxt, + pim->mroute_socket, &msg->msg_im_src, + &msg->msg_im_dst, ifp->name, msg->msg_im_vif, + (long int)buf_size); +#endif + } + + switch (msg->msg_im_msgtype) { + case GMMSG_WRONGVIF: + return pim_mroute_msg_wrongvif(pim->mroute_socket, ifp, + msg); + case GMMSG_NOCACHE: + return pim_mroute_msg_nocache(pim->mroute_socket, ifp, + msg); + case GMMSG_WHOLEPKT: + return pim_mroute_msg_wholepkt(pim->mroute_socket, ifp, + (const char *)msg, + buf_size); + case GMMSG_WRVIFWHOLE: + return pim_mroute_msg_wrvifwhole(pim->mroute_socket, + ifp, (const char *)msg, + buf_size); + default: + break; + } + } + + return 0; +} + +static void mroute_read(struct event *t) +{ + struct pim_instance *pim; + static long long count; + char buf[10000]; + int cont = 1; + int rd; + ifindex_t ifindex; + pim = EVENT_ARG(t); + + while (cont) { + rd = pim_socket_recvfromto(pim->mroute_socket, (uint8_t *)buf, + sizeof(buf), NULL, NULL, NULL, NULL, + &ifindex); + if (rd <= 0) { + if (errno == EINTR) + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + + zlog_warn( + "%s: failure reading rd=%d: fd=%d: errno=%d: %s", + __func__, rd, pim->mroute_socket, errno, + safe_strerror(errno)); + goto done; + } + + pim_mroute_msg(pim, buf, rd, ifindex); + + count++; + if (count % router->packet_process == 0) + cont = 0; + } +/* Keep reading */ +done: + mroute_read_on(pim); + + return; +} + +static void mroute_read_on(struct pim_instance *pim) +{ + event_add_read(router->master, mroute_read, pim, pim->mroute_socket, + &pim->thread); +} + +static void mroute_read_off(struct pim_instance *pim) +{ + EVENT_OFF(pim->thread); +} + +int pim_mroute_socket_enable(struct pim_instance *pim) +{ + int fd; + + frr_with_privs(&pimd_privs) { + +#if PIM_IPV == 4 + fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); +#else + fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); +#endif + if (fd < 0) { + zlog_warn("Could not create mroute socket: errno=%d: %s", + errno, + safe_strerror(errno)); + return -2; + } + +#if PIM_IPV == 6 + struct icmp6_filter filter[1]; + int ret; + + /* Unlike IPv4, this socket is not used for MLD, so just drop + * everything with an empty ICMP6 filter. Otherwise we get + * all kinds of garbage here, possibly even non-multicast + * related ICMPv6 traffic (e.g. ping) + * + * (mroute kernel upcall "packets" are injected directly on the + * socket, this sockopt -or any other- has no effect on them) + */ + ICMP6_FILTER_SETBLOCKALL(filter); + ret = setsockopt(fd, SOL_ICMPV6, ICMP6_FILTER, filter, + sizeof(filter)); + if (ret) + zlog_err( + "(VRF %s) failed to set mroute control filter: %m", + pim->vrf->name); +#endif + +#ifdef SO_BINDTODEVICE + if (pim->vrf->vrf_id != VRF_DEFAULT + && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, + pim->vrf->name, strlen(pim->vrf->name))) { + zlog_warn("Could not setsockopt SO_BINDTODEVICE: %s", + safe_strerror(errno)); + close(fd); + return -3; + } +#endif + + } + + pim->mroute_socket = fd; + if (pim_mroute_set(pim, 1)) { + zlog_warn( + "Could not enable mroute on socket fd=%d: errno=%d: %s", + fd, errno, safe_strerror(errno)); + close(fd); + pim->mroute_socket = -1; + return -3; + } + + pim->mroute_socket_creation = pim_time_monotonic_sec(); + + mroute_read_on(pim); + + return 0; +} + +int pim_mroute_socket_disable(struct pim_instance *pim) +{ + if (pim_mroute_set(pim, 0)) { + zlog_warn( + "Could not disable mroute on socket fd=%d: errno=%d: %s", + pim->mroute_socket, errno, safe_strerror(errno)); + return -2; + } + + if (close(pim->mroute_socket)) { + zlog_warn("Failure closing mroute socket: fd=%d errno=%d: %s", + pim->mroute_socket, errno, safe_strerror(errno)); + return -3; + } + + mroute_read_off(pim); + pim->mroute_socket = -1; + + return 0; +} + +/* + For each network interface (e.g., physical or a virtual tunnel) that + would be used for multicast forwarding, a corresponding multicast + interface must be added to the kernel. + */ +int pim_mroute_add_vif(struct interface *ifp, pim_addr ifaddr, + unsigned char flags) +{ + struct pim_interface *pim_ifp = ifp->info; + pim_vifctl vc; + int err; + + if (PIM_DEBUG_MROUTE) + zlog_debug("%s: Add Vif %d (%s[%s])", __func__, + pim_ifp->mroute_vif_index, ifp->name, + pim_ifp->pim->vrf->name); + + memset(&vc, 0, sizeof(vc)); + vc.vc_vifi = pim_ifp->mroute_vif_index; +#if PIM_IPV == 4 +#ifdef VIFF_USE_IFINDEX + vc.vc_lcl_ifindex = ifp->ifindex; +#else + if (ifaddr.s_addr == INADDR_ANY) { + zlog_warn( + "%s: unnumbered interfaces are not supported on this platform", + __func__); + return -1; + } + memcpy(&vc.vc_lcl_addr, &ifaddr, sizeof(vc.vc_lcl_addr)); +#endif +#else + vc.vc_pifi = ifp->ifindex; +#endif + vc.vc_flags = flags; + vc.vc_threshold = PIM_MROUTE_MIN_TTL; + vc.vc_rate_limit = 0; + +#if PIM_IPV == 4 +#ifdef PIM_DVMRP_TUNNEL + if (vc.vc_flags & VIFF_TUNNEL) { + memcpy(&vc.vc_rmt_addr, &vif_remote_addr, + sizeof(vc.vc_rmt_addr)); + } +#endif +#endif + + err = setsockopt(pim_ifp->pim->mroute_socket, PIM_IPPROTO, MRT_ADD_VIF, + (void *)&vc, sizeof(vc)); + if (err) { + zlog_warn( + "%s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_ADD_VIF,vif_index=%d,ifaddr=%pPAs,flag=%d): errno=%d: %s", + __func__, pim_ifp->pim->mroute_socket, ifp->ifindex, + &ifaddr, flags, errno, safe_strerror(errno)); + return -2; + } + + return 0; +} + +int pim_mroute_del_vif(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + pim_vifctl vc; + int err; + + if (PIM_DEBUG_MROUTE) + zlog_debug("%s: Del Vif %d (%s[%s])", __func__, + pim_ifp->mroute_vif_index, ifp->name, + pim_ifp->pim->vrf->name); + + memset(&vc, 0, sizeof(vc)); + vc.vc_vifi = pim_ifp->mroute_vif_index; + + err = setsockopt(pim_ifp->pim->mroute_socket, PIM_IPPROTO, MRT_DEL_VIF, + (void *)&vc, sizeof(vc)); + if (err) { + zlog_warn( + "%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_DEL_VIF,vif_index=%d): errno=%d: %s", + __FILE__, __func__, pim_ifp->pim->mroute_socket, + pim_ifp->mroute_vif_index, errno, safe_strerror(errno)); + return -2; + } + + return 0; +} + +/* + * Prevent creating MFC entry with OIF=IIF. + * + * This is a protection against implementation mistakes. + * + * PIM protocol implicitely ensures loopfree multicast topology. + * + * IGMP must be protected against adding looped MFC entries created + * by both source and receiver attached to the same interface. See + * TODO T22. + * We shall allow igmp to create upstream when it is DR for the intf. + * Assume RP reachable via non DR. + */ +bool pim_mroute_allow_iif_in_oil(struct channel_oil *c_oil, + int oif_index) +{ +#ifdef PIM_ENFORCE_LOOPFREE_MFC + struct interface *ifp_out; + struct pim_interface *pim_ifp; + + if (c_oil->up && + PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(c_oil->up->flags)) + return true; + + ifp_out = pim_if_find_by_vif_index(c_oil->pim, oif_index); + if (!ifp_out) + return false; + pim_ifp = ifp_out->info; + if (!pim_ifp) + return false; + if ((c_oil->oif_flags[oif_index] & PIM_OIF_FLAG_PROTO_GM) && + PIM_I_am_DR(pim_ifp)) + return true; + + return false; +#else + return true; +#endif +} + +static inline void pim_mroute_copy(struct channel_oil *out, + struct channel_oil *in) +{ + int i; + + *oil_origin(out) = *oil_origin(in); + *oil_mcastgrp(out) = *oil_mcastgrp(in); + *oil_incoming_vif(out) = *oil_incoming_vif(in); + + for (i = 0; i < MAXVIFS; ++i) { + if (*oil_incoming_vif(out) == i && + !pim_mroute_allow_iif_in_oil(in, i)) { + oil_if_set(out, i, 0); + continue; + } + + if (in->oif_flags[i] & PIM_OIF_FLAG_MUTE) + oil_if_set(out, i, 0); + else + oil_if_set(out, i, oil_if_has(in, i)); + } +} + +/* This function must not be called directly 0 + * use pim_upstream_mroute_add or pim_static_mroute_add instead + */ +static int pim_mroute_add(struct channel_oil *c_oil, const char *name) +{ + struct pim_instance *pim = c_oil->pim; + struct channel_oil tmp_oil[1] = { }; + int err; + + pim->mroute_add_last = pim_time_monotonic_sec(); + ++pim->mroute_add_events; + + /* Copy the oil to a temporary structure to fixup (without need to + * later restore) before sending the mroute add to the dataplane + */ + pim_mroute_copy(tmp_oil, c_oil); + + /* The linux kernel *expects* the incoming + * vif to be part of the outgoing list + * in the case of a (*,G). + */ + if (pim_addr_is_any(*oil_origin(c_oil))) { + oil_if_set(tmp_oil, *oil_incoming_vif(c_oil), 1); + } + + /* + * If we have an unresolved cache entry for the S,G + * it is owned by the pimreg for the incoming IIF + * So set pimreg as the IIF temporarily to cause + * the packets to be forwarded. Then set it + * to the correct IIF afterwords. + */ + if (!c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) && + *oil_incoming_vif(c_oil) != 0) { + *oil_incoming_vif(tmp_oil) = 0; + } + /* For IPv6 MRT_ADD_MFC is defined to MRT6_ADD_MFC */ + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC, + &tmp_oil->oil, sizeof(tmp_oil->oil)); + + if (!err && !c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) && + *oil_incoming_vif(c_oil) != 0) { + *oil_incoming_vif(tmp_oil) = *oil_incoming_vif(c_oil); + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC, + &tmp_oil->oil, sizeof(tmp_oil->oil)); + } + + if (err) { + zlog_warn( + "%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_ADD_MFC): errno=%d: %s", + __FILE__, __func__, pim->mroute_socket, errno, + safe_strerror(errno)); + return -2; + } + + if (PIM_DEBUG_MROUTE) { + char buf[1000]; + zlog_debug("%s(%s), vrf %s Added Route: %s", __func__, name, + pim->vrf->name, + pim_channel_oil_dump(c_oil, buf, sizeof(buf))); + } + + if (!c_oil->installed) { + c_oil->installed = 1; + c_oil->mroute_creation = pim_time_monotonic_sec(); + } + + return 0; +} + +static int pim_upstream_get_mroute_iif(struct channel_oil *c_oil, + const char *name) +{ + vifi_t iif = MAXVIFS; + struct interface *ifp = NULL; + struct pim_interface *pim_ifp; + struct pim_upstream *up = c_oil->up; + + if (up) { + if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags)) { + if (up->parent) + ifp = up->parent->rpf.source_nexthop.interface; + } else { + ifp = up->rpf.source_nexthop.interface; + } + if (ifp) { + pim_ifp = (struct pim_interface *)ifp->info; + if (pim_ifp) + iif = pim_ifp->mroute_vif_index; + } + } + return iif; +} + +static int pim_upstream_mroute_update(struct channel_oil *c_oil, + const char *name) +{ + char buf[1000]; + + if (*oil_incoming_vif(c_oil) >= MAXVIFS) { + /* the c_oil cannot be installed as a mroute yet */ + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s(%s) %s mroute not ready to be installed; %s", + __func__, name, + pim_channel_oil_dump(c_oil, buf, + sizeof(buf)), + c_oil->installed ? + "uninstall" : "skip"); + /* if already installed flush it out as we are going to stop + * updates to it leaving it in a stale state + */ + if (c_oil->installed) + pim_mroute_del(c_oil, name); + /* return success (skipped) */ + return 0; + } + + return pim_mroute_add(c_oil, name); +} + +/* IIF associated with SGrpt entries are re-evaluated when the parent + * (*,G) entries IIF changes + */ +static void pim_upstream_all_sources_iif_update(struct pim_upstream *up) +{ + struct listnode *listnode; + struct pim_upstream *child; + + for (ALL_LIST_ELEMENTS_RO(up->sources, listnode, + child)) { + if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(child->flags)) + pim_upstream_mroute_iif_update(child->channel_oil, + __func__); + } +} + +/* In the case of "PIM state machine" added mroutes an upstream entry + * must be present to decide on the SPT-forwarding vs. RPT-forwarding. + */ +int pim_upstream_mroute_add(struct channel_oil *c_oil, const char *name) +{ + vifi_t iif; + + iif = pim_upstream_get_mroute_iif(c_oil, name); + + if (*oil_incoming_vif(c_oil) != iif) { + *oil_incoming_vif(c_oil) = iif; + if (pim_addr_is_any(*oil_origin(c_oil)) && + c_oil->up) + pim_upstream_all_sources_iif_update(c_oil->up); + } else { + *oil_incoming_vif(c_oil) = iif; + } + + return pim_upstream_mroute_update(c_oil, name); +} + +/* Look for IIF changes and update the dataplane entry only if the IIF + * has changed. + */ +int pim_upstream_mroute_iif_update(struct channel_oil *c_oil, const char *name) +{ + vifi_t iif; + char buf[1000]; + + iif = pim_upstream_get_mroute_iif(c_oil, name); + if (*oil_incoming_vif(c_oil) == iif) { + /* no change */ + return 0; + } + *oil_incoming_vif(c_oil) = iif; + + if (pim_addr_is_any(*oil_origin(c_oil)) && + c_oil->up) + pim_upstream_all_sources_iif_update(c_oil->up); + + if (PIM_DEBUG_MROUTE_DETAIL) + zlog_debug("%s(%s) %s mroute iif update %d", + __func__, name, + pim_channel_oil_dump(c_oil, buf, + sizeof(buf)), iif); + /* XXX: is this hack needed? */ + c_oil->oil_inherited_rescan = 1; + return pim_upstream_mroute_update(c_oil, name); +} + +int pim_static_mroute_add(struct channel_oil *c_oil, const char *name) +{ + return pim_mroute_add(c_oil, name); +} + +void pim_static_mroute_iif_update(struct channel_oil *c_oil, + int input_vif_index, + const char *name) +{ + if (*oil_incoming_vif(c_oil) == input_vif_index) + return; + + *oil_incoming_vif(c_oil) = input_vif_index; + if (input_vif_index == MAXVIFS) + pim_mroute_del(c_oil, name); + else + pim_static_mroute_add(c_oil, name); +} + +int pim_mroute_del(struct channel_oil *c_oil, const char *name) +{ + struct pim_instance *pim = c_oil->pim; + int err; + + pim->mroute_del_last = pim_time_monotonic_sec(); + ++pim->mroute_del_events; + + if (!c_oil->installed) { + if (PIM_DEBUG_MROUTE) { + char buf[1000]; + struct interface *iifp = + pim_if_find_by_vif_index(pim, *oil_incoming_vif( + c_oil)); + + zlog_debug("%s %s: incoming interface %s for route is %s not installed, do not need to send del req. ", + __FILE__, __func__, + iifp ? iifp->name : "Unknown", + pim_channel_oil_dump(c_oil, buf, + sizeof(buf))); + } + return -2; + } + + err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_DEL_MFC, + &c_oil->oil, sizeof(c_oil->oil)); + if (err) { + if (PIM_DEBUG_MROUTE) + zlog_warn( + "%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_DEL_MFC): errno=%d: %s", + __FILE__, __func__, pim->mroute_socket, errno, + safe_strerror(errno)); + return -2; + } + + if (PIM_DEBUG_MROUTE) { + char buf[1000]; + zlog_debug("%s(%s), vrf %s Deleted Route: %s", __func__, name, + pim->vrf->name, + pim_channel_oil_dump(c_oil, buf, sizeof(buf))); + } + + // Reset kernel installed flag + c_oil->installed = 0; + + return 0; +} + +void pim_mroute_update_counters(struct channel_oil *c_oil) +{ + struct pim_instance *pim = c_oil->pim; + pim_sioc_sg_req sgreq; + + c_oil->cc.oldpktcnt = c_oil->cc.pktcnt; + c_oil->cc.oldbytecnt = c_oil->cc.bytecnt; + c_oil->cc.oldwrong_if = c_oil->cc.wrong_if; + + if (!c_oil->installed) { + c_oil->cc.lastused = 100 * pim->keep_alive_time; + if (PIM_DEBUG_MROUTE) { + pim_sgaddr sg; + + sg.src = *oil_origin(c_oil); + sg.grp = *oil_mcastgrp(c_oil); + zlog_debug("Channel%pSG is not installed no need to collect data from kernel", + &sg); + } + return; + } + + + memset(&sgreq, 0, sizeof(sgreq)); + + pim_zlookup_sg_statistics(c_oil); + +#if PIM_IPV == 4 + sgreq.src = *oil_origin(c_oil); + sgreq.grp = *oil_mcastgrp(c_oil); +#else + sgreq.src = c_oil->oil.mf6cc_origin; + sgreq.grp = c_oil->oil.mf6cc_mcastgrp; +#endif + if (ioctl(pim->mroute_socket, PIM_SIOCGETSGCNT, &sgreq)) { + pim_sgaddr sg; + + sg.src = *oil_origin(c_oil); + sg.grp = *oil_mcastgrp(c_oil); + + zlog_warn( + "ioctl(PIM_SIOCGETSGCNT=%lu) failure for (S,G)=%pSG: errno=%d: %s", + (unsigned long)PIM_SIOCGETSGCNT, &sg, errno, + safe_strerror(errno)); + return; + } + + c_oil->cc.pktcnt = sgreq.pktcnt; + c_oil->cc.bytecnt = sgreq.bytecnt; + c_oil->cc.wrong_if = sgreq.wrong_if; + return; +} diff --git a/pimd/pim_mroute.h b/pimd/pim_mroute.h new file mode 100644 index 0000000..fd4913c --- /dev/null +++ b/pimd/pim_mroute.h @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_MROUTE_H +#define PIM_MROUTE_H + +/* + For msghdr.msg_control in Solaris 10 +*/ +#ifndef _XPG4_2 +#define _XPG4_2 +#endif +#ifndef __EXTENSIONS__ +#define __EXTENSIONS__ +#endif + + +#define PIM_MROUTE_MIN_TTL (1) + +#if PIM_IPV == 4 + +#include +#if defined(HAVE_LINUX_MROUTE_H) +#include +#endif + +#if defined(HAVE_NETINET_IP_MROUTE_H) +#include +/* + * MRT_TABLE of 155 is needed because it is not defined + * on FreeBSD. MRT_TABLE is for vrf's. There is no + * equivalent on BSD at this point in time. Let's + * just get it compiling + */ +#ifndef MRT_TABLE +#define MRT_TABLE 155 +#endif +#endif + +typedef struct vifctl pim_vifctl; +typedef struct igmpmsg kernmsg; +typedef struct sioc_sg_req pim_sioc_sg_req; + +#define vc_vifi vifc_vifi +#define vc_flags vifc_flags +#define vc_threshold vifc_threshold +#define vc_rate_limit vifc_rate_limit +#define vc_lcl_addr vifc_lcl_addr +#define vc_lcl_ifindex vifc_lcl_ifindex +#define vc_rmt_addr vifc_rmt_addr + +#define msg_im_msgtype im_msgtype +#define msg_im_vif im_vif +#define msg_im_src im_src +#define msg_im_dst im_dst + +#ifndef IGMPMSG_WRVIFWHOLE +#define IGMPMSG_WRVIFWHOLE 4 /* For PIM processing */ +#endif + +#ifndef GMMSG_NOCACHE +#define GMMSG_NOCACHE IGMPMSG_NOCACHE /* For PIM processing */ +#define GMMSG_WHOLEPKT IGMPMSG_WHOLEPKT /* For PIM processing */ +#define GMMSG_WRONGVIF IGMPMSG_WRONGVIF /* For PIM processing */ +#define GMMSG_WRVIFWHOLE IGMPMSG_WRVIFWHOLE /* For PIM processing */ +#endif + +#ifndef PIM_IPPROTO +#define PIM_IPPROTO IPPROTO_IP +#endif +#ifndef PIM_SIOCGETSGCNT +#define PIM_SIOCGETSGCNT SIOCGETSGCNT +#endif + +#else /* PIM_IPV != 4 */ + +#include + +#if defined(HAVE_LINUX_MROUTE6_H) +#include +#endif +#if defined(HAVE_NETINET_IP6_MROUTE_H) +#include +#include + +/* + * See the v4 discussion above + */ +#ifndef MRT_TABLE +#define MRT_TABLE 155 +#endif +#endif + +#ifndef MRT_INIT +#define MRT_BASE MRT6_BASE +#define MRT_INIT MRT6_INIT +#define MRT_DONE MRT6_DONE +#define MRT_ADD_VIF MRT6_ADD_MIF +#define MRT_DEL_VIF MRT6_DEL_MIF +#define MRT_ADD_MFC MRT6_ADD_MFC +#define MRT_DEL_MFC MRT6_DEL_MFC +#define MRT_VERSION MRT6_VERSION +#define MRT_ASSERT MRT6_ASSERT +#define MRT_PIM MRT6_PIM +#define MRT_TABLE MRT6_TABLE +#endif + +#ifndef PIM_IPPROTO +#define PIM_IPPROTO IPPROTO_IPV6 +#endif + +#ifndef PIM_SIOCGETSGCNT +#define PIM_SIOCGETSGCNT SIOCGETSGCNT_IN6 +#endif + +#ifndef MRT6MSG_WRMIFWHOLE +#define MRT6MSG_WRMIFWHOLE 4 /* For PIM processing */ +#endif + +#ifndef GMMSG_NOCACHE +#define GMMSG_NOCACHE MRT6MSG_NOCACHE /* For PIM processing */ +#define GMMSG_WHOLEPKT MRT6MSG_WHOLEPKT /* For PIM processing */ +#define GMMSG_WRONGVIF MRT6MSG_WRONGMIF /* For PIM processing */ +#define GMMSG_WRVIFWHOLE MRT6MSG_WRMIFWHOLE /* For PIM processing */ +#endif + +typedef struct mif6ctl pim_vifctl; +typedef struct mrt6msg kernmsg; +typedef mifi_t vifi_t; +typedef struct sioc_sg_req6 pim_sioc_sg_req; + +#define vc_vifi mif6c_mifi +#define vc_flags mif6c_flags +#define vc_threshold vifc_threshold +#define vc_pifi mif6c_pifi +#define vc_rate_limit vifc_rate_limit + +#define msg_im_msgtype im6_msgtype +#define msg_im_vif im6_mif +#define msg_im_src im6_src +#define msg_im_dst im6_dst + +#ifndef MAXVIFS +#define MAXVIFS IF_SETSIZE +#endif + +#define VIFF_REGISTER MIFF_REGISTER +#endif + + +/* + Above: from +*/ + +struct channel_oil; +struct pim_instance; + +int pim_mroute_socket_enable(struct pim_instance *pim); +int pim_mroute_socket_disable(struct pim_instance *pim); + +int pim_mroute_add_vif(struct interface *ifp, pim_addr ifaddr, + unsigned char flags); +int pim_mroute_del_vif(struct interface *ifp); + +int pim_upstream_mroute_add(struct channel_oil *c_oil, const char *name); +int pim_upstream_mroute_iif_update(struct channel_oil *c_oil, const char *name); +int pim_static_mroute_add(struct channel_oil *c_oil, const char *name); +void pim_static_mroute_iif_update(struct channel_oil *c_oil, + int input_vif_index, + const char *name); +int pim_mroute_del(struct channel_oil *c_oil, const char *name); + +void pim_mroute_update_counters(struct channel_oil *c_oil); +bool pim_mroute_allow_iif_in_oil(struct channel_oil *c_oil, + int oif_index); +int pim_mroute_msg(struct pim_instance *pim, const char *buf, size_t buf_size, + ifindex_t ifindex); +int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg); +int pim_mroute_msg_wholepkt(int fd, struct interface *ifp, const char *buf, + size_t len); +int pim_mroute_msg_wrongvif(int fd, struct interface *ifp, const kernmsg *msg); +int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf, + size_t len); +int pim_mroute_set(struct pim_instance *pim, int enable); +#endif /* PIM_MROUTE_H */ diff --git a/pimd/pim_msdp.c b/pimd/pim_msdp.c new file mode 100644 index 0000000..ea8c84c --- /dev/null +++ b/pimd/pim_msdp.c @@ -0,0 +1,1446 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pimd.h" +#include "pim_memory.h" +#include "pim_instance.h" +#include "pim_iface.h" +#include "pim_rp.h" +#include "pim_str.h" +#include "pim_time.h" +#include "pim_upstream.h" +#include "pim_oil.h" + +#include "pim_msdp.h" +#include "pim_msdp_packet.h" +#include "pim_msdp_socket.h" + +// struct pim_msdp pim_msdp, *msdp = &pim_msdp; + +static void pim_msdp_peer_listen(struct pim_msdp_peer *mp); +static void pim_msdp_peer_cr_timer_setup(struct pim_msdp_peer *mp, bool start); +static void pim_msdp_peer_ka_timer_setup(struct pim_msdp_peer *mp, bool start); +static void pim_msdp_peer_hold_timer_setup(struct pim_msdp_peer *mp, + bool start); +static void pim_msdp_peer_free(struct pim_msdp_peer *mp); +static void pim_msdp_enable(struct pim_instance *pim); +static void pim_msdp_sa_adv_timer_setup(struct pim_instance *pim, bool start); +static void pim_msdp_sa_deref(struct pim_msdp_sa *sa, + enum pim_msdp_sa_flags flags); +static int pim_msdp_mg_mbr_comp(const void *p1, const void *p2); +static void pim_msdp_mg_mbr_free(struct pim_msdp_mg_mbr *mbr); + +/************************ SA cache management ******************************/ +static void pim_msdp_sa_timer_expiry_log(struct pim_msdp_sa *sa, + const char *timer_str) +{ + zlog_debug("MSDP SA %s %s timer expired", sa->sg_str, timer_str); +} + +/* RFC-3618:Sec-5.1 - global active source advertisement timer */ +static void pim_msdp_sa_adv_timer_cb(struct event *t) +{ + struct pim_instance *pim = EVENT_ARG(t); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA advertisement timer expired"); + } + + pim_msdp_sa_adv_timer_setup(pim, true /* start */); + pim_msdp_pkt_sa_tx(pim); +} + +static void pim_msdp_sa_adv_timer_setup(struct pim_instance *pim, bool start) +{ + EVENT_OFF(pim->msdp.sa_adv_timer); + if (start) { + event_add_timer(pim->msdp.master, pim_msdp_sa_adv_timer_cb, pim, + PIM_MSDP_SA_ADVERTISMENT_TIME, + &pim->msdp.sa_adv_timer); + } +} + +/* RFC-3618:Sec-5.3 - SA cache state timer */ +static void pim_msdp_sa_state_timer_cb(struct event *t) +{ + struct pim_msdp_sa *sa; + + sa = EVENT_ARG(t); + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_sa_timer_expiry_log(sa, "state"); + } + + pim_msdp_sa_deref(sa, PIM_MSDP_SAF_PEER); +} + +static void pim_msdp_sa_state_timer_setup(struct pim_msdp_sa *sa, bool start) +{ + EVENT_OFF(sa->sa_state_timer); + if (start) { + event_add_timer(sa->pim->msdp.master, + pim_msdp_sa_state_timer_cb, sa, + PIM_MSDP_SA_HOLD_TIME, &sa->sa_state_timer); + } +} + +static void pim_msdp_sa_upstream_del(struct pim_msdp_sa *sa) +{ + struct pim_upstream *up = sa->up; + if (!up) { + return; + } + + sa->up = NULL; + if (PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags)) { + PIM_UPSTREAM_FLAG_UNSET_SRC_MSDP(up->flags); + sa->flags |= PIM_MSDP_SAF_UP_DEL_IN_PROG; + up = pim_upstream_del(sa->pim, up, __func__); + /* re-eval joinDesired; clearing peer-msdp-sa flag can + * cause JD to change + */ + if (up) + pim_upstream_update_join_desired(sa->pim, up); + sa->flags &= ~PIM_MSDP_SAF_UP_DEL_IN_PROG; + } + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s de-referenced SPT", sa->sg_str); + } +} + +static bool pim_msdp_sa_upstream_add_ok(struct pim_msdp_sa *sa, + struct pim_upstream *xg_up) +{ + if (!(sa->flags & PIM_MSDP_SAF_PEER)) { + /* SA should have been rxed from a peer */ + return false; + } + /* check if we are RP */ + if (!I_am_RP(sa->pim, sa->sg.grp)) { + return false; + } + + /* check if we have a (*, G) with a non-empty immediate OIL */ + if (!xg_up) { + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.grp = sa->sg.grp; + + xg_up = pim_upstream_find(sa->pim, &sg); + } + if (!xg_up || (xg_up->join_state != PIM_UPSTREAM_JOINED)) { + /* join desired will be true for such (*, G) entries so we will + * just look at join_state and let the PIM state machine do the + * rest of + * the magic */ + return false; + } + + return true; +} + +/* Upstream add evaluation needs to happen everytime - + * 1. Peer reference is added or removed. + * 2. The RP for a group changes. + * 3. joinDesired for the associated (*, G) changes + * 4. associated (*, G) is removed - this seems like a bit redundant + * (considering #4); but just in case an entry gets nuked without + * upstream state transition + * */ +static void pim_msdp_sa_upstream_update(struct pim_msdp_sa *sa, + struct pim_upstream *xg_up, + const char *ctx) +{ + struct pim_upstream *up; + + if (!pim_msdp_sa_upstream_add_ok(sa, xg_up)) { + pim_msdp_sa_upstream_del(sa); + return; + } + + if (sa->up) { + /* nothing to do */ + return; + } + + up = pim_upstream_find(sa->pim, &sa->sg); + if (up && (PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags))) { + /* somehow we lost track of the upstream ptr? best log it */ + sa->up = up; + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s SPT reference missing", + sa->sg_str); + } + return; + } + + /* RFC3618: "RP triggers a (S, G) join event towards the data source + * as if a JP message was rxed addressed to the RP itself." */ + up = pim_upstream_add(sa->pim, &sa->sg, NULL /* iif */, + PIM_UPSTREAM_FLAG_MASK_SRC_MSDP, __func__, NULL); + + sa->up = up; + if (up) { + /* update inherited oil */ + pim_upstream_inherited_olist(sa->pim, up); + /* should we also start the kat in parallel? we will need it + * when the + * SA ages out */ + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s referenced SPT", sa->sg_str); + } + } else { + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s SPT reference failed", + sa->sg_str); + } + } +} + +/* release all mem associated with a sa */ +static void pim_msdp_sa_free(struct pim_msdp_sa *sa) +{ + pim_msdp_sa_state_timer_setup(sa, false); + + XFREE(MTYPE_PIM_MSDP_SA, sa); +} + +static struct pim_msdp_sa *pim_msdp_sa_new(struct pim_instance *pim, + pim_sgaddr *sg, struct in_addr rp) +{ + struct pim_msdp_sa *sa; + + sa = XCALLOC(MTYPE_PIM_MSDP_SA, sizeof(*sa)); + + sa->pim = pim; + sa->sg = *sg; + snprintfrr(sa->sg_str, sizeof(sa->sg_str), "%pSG", sg); + sa->rp = rp; + sa->uptime = pim_time_monotonic_sec(); + + /* insert into misc tables for easy access */ + sa = hash_get(pim->msdp.sa_hash, sa, hash_alloc_intern); + listnode_add_sort(pim->msdp.sa_list, sa); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s created", sa->sg_str); + } + + return sa; +} + +static struct pim_msdp_sa *pim_msdp_sa_find(struct pim_instance *pim, + pim_sgaddr *sg) +{ + struct pim_msdp_sa lookup; + + lookup.sg = *sg; + return hash_lookup(pim->msdp.sa_hash, &lookup); +} + +static struct pim_msdp_sa *pim_msdp_sa_add(struct pim_instance *pim, + pim_sgaddr *sg, struct in_addr rp) +{ + struct pim_msdp_sa *sa; + + sa = pim_msdp_sa_find(pim, sg); + if (sa) { + return sa; + } + + return pim_msdp_sa_new(pim, sg, rp); +} + +static void pim_msdp_sa_del(struct pim_msdp_sa *sa) +{ + /* this is somewhat redundant - still want to be careful not to leave + * stale upstream references */ + pim_msdp_sa_upstream_del(sa); + + /* stop timers */ + pim_msdp_sa_state_timer_setup(sa, false /* start */); + + /* remove the entry from various tables */ + listnode_delete(sa->pim->msdp.sa_list, sa); + hash_release(sa->pim->msdp.sa_hash, sa); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s deleted", sa->sg_str); + } + + /* free up any associated memory */ + pim_msdp_sa_free(sa); +} + +static void pim_msdp_sa_peer_ip_set(struct pim_msdp_sa *sa, + struct pim_msdp_peer *mp, struct in_addr rp) +{ + struct pim_msdp_peer *old_mp; + + /* optimize the "no change" case as it will happen + * frequently/periodically */ + if (mp && (sa->peer.s_addr == mp->peer.s_addr)) { + return; + } + + /* any time the peer ip changes also update the rp address */ + if (sa->peer.s_addr != INADDR_ANY) { + old_mp = pim_msdp_peer_find(sa->pim, sa->peer); + if (old_mp && old_mp->sa_cnt) { + --old_mp->sa_cnt; + } + } + + if (mp) { + ++mp->sa_cnt; + sa->peer = mp->peer; + } else { + sa->peer.s_addr = PIM_NET_INADDR_ANY; + } + sa->rp = rp; +} + +/* When a local active-source is removed there is no way to withdraw the + * source from peers. We will simply remove it from the SA cache so it will + * not be sent in supsequent SA updates. Peers will consequently timeout the + * SA. + * Similarly a "peer-added" SA is never explicitly deleted. It is simply + * aged out overtime if not seen in the SA updates from the peers. + * XXX: should we provide a knob to drop entries learnt from a peer when the + * peer goes down? */ +static void pim_msdp_sa_deref(struct pim_msdp_sa *sa, + enum pim_msdp_sa_flags flags) +{ + bool update_up = false; + + if ((sa->flags & PIM_MSDP_SAF_LOCAL)) { + if (flags & PIM_MSDP_SAF_LOCAL) { + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s local reference removed", + sa->sg_str); + } + if (sa->pim->msdp.local_cnt) + --sa->pim->msdp.local_cnt; + } + } + + if ((sa->flags & PIM_MSDP_SAF_PEER)) { + if (flags & PIM_MSDP_SAF_PEER) { + struct in_addr rp; + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s peer reference removed", + sa->sg_str); + } + pim_msdp_sa_state_timer_setup(sa, false /* start */); + rp.s_addr = INADDR_ANY; + pim_msdp_sa_peer_ip_set(sa, NULL /* mp */, rp); + /* if peer ref was removed we need to remove the msdp + * reference on the + * msdp entry */ + update_up = true; + } + } + + sa->flags &= ~flags; + if (update_up) { + pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, "sa-deref"); + } + + if (!(sa->flags & PIM_MSDP_SAF_REF)) { + pim_msdp_sa_del(sa); + } +} + +void pim_msdp_sa_ref(struct pim_instance *pim, struct pim_msdp_peer *mp, + pim_sgaddr *sg, struct in_addr rp) +{ + struct pim_msdp_sa *sa; + struct rp_info *rp_info; + struct prefix grp; + + sa = pim_msdp_sa_add(pim, sg, rp); + if (!sa) { + return; + } + + /* reference it */ + if (mp) { + if (!(sa->flags & PIM_MSDP_SAF_PEER)) { + sa->flags |= PIM_MSDP_SAF_PEER; + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s added by peer", + sa->sg_str); + } + } + pim_msdp_sa_peer_ip_set(sa, mp, rp); + /* start/re-start the state timer to prevent cache expiry */ + pim_msdp_sa_state_timer_setup(sa, true /* start */); + /* We re-evaluate SA "SPT-trigger" everytime we hear abt it from + * a + * peer. XXX: If this becomes too much of a periodic overhead we + * can make it event based */ + pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, "peer-ref"); + } else { + if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { + sa->flags |= PIM_MSDP_SAF_LOCAL; + ++sa->pim->msdp.local_cnt; + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP SA %s added locally", + sa->sg_str); + } + /* send an immediate SA update to peers */ + pim_addr_to_prefix(&grp, sa->sg.grp); + rp_info = pim_rp_find_match_group(pim, &grp); + if (rp_info) { + sa->rp = rp_info->rp.rpf_addr; + } else { + sa->rp = pim->msdp.originator_id; + } + pim_msdp_pkt_sa_tx_one(sa); + } + sa->flags &= ~PIM_MSDP_SAF_STALE; + } +} + +/* The following criteria must be met to originate an SA from the MSDP + * speaker - + * 1. KAT must be running i.e. source is active. + * 2. We must be RP for the group. + * 3. Source must be registrable to the RP (this is where the RFC is vague + * and especially ambiguous in CLOS networks; with anycast RP all sources + * are potentially registrable to all RPs in the domain). We assume #3 is + * satisfied if - + * a. We are also the FHR-DR for the source (OR) + * b. We rxed a pim register (null or data encapsulated) within the last + * (3 * (1.5 * register_suppression_timer))). + */ +static bool pim_msdp_sa_local_add_ok(struct pim_upstream *up) +{ + struct pim_instance *pim = up->channel_oil->pim; + + if (!(pim->msdp.flags & PIM_MSDPF_ENABLE)) { + return false; + } + + if (!pim_upstream_is_kat_running(up)) + /* stream is not active */ + return false; + + if (!I_am_RP(pim, up->sg.grp)) { + /* we are not RP for the group */ + return false; + } + + /* we are the FHR-DR for this stream or we are RP and have seen + * registers + * from a FHR for this source */ + if (PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) || up->t_msdp_reg_timer) { + return true; + } + + return false; +} + +static void pim_msdp_sa_local_add(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct in_addr rp; + rp.s_addr = INADDR_ANY; + pim_msdp_sa_ref(pim, NULL /* mp */, sg, rp); +} + +void pim_msdp_sa_local_del(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct pim_msdp_sa *sa; + + sa = pim_msdp_sa_find(pim, sg); + if (sa) { + pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); + } +} + +/* we need to be very cautious with this API as SA del too can trigger an + * upstream del and we will get stuck in a simple loop */ +static void pim_msdp_sa_local_del_on_up_del(struct pim_instance *pim, + pim_sgaddr *sg) +{ + struct pim_msdp_sa *sa; + + sa = pim_msdp_sa_find(pim, sg); + if (sa) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP local sa %s del on up del", + sa->sg_str); + } + + /* if there is no local reference escape */ + if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP local sa %s del; no local ref", + sa->sg_str); + } + return; + } + + if (sa->flags & PIM_MSDP_SAF_UP_DEL_IN_PROG) { + /* MSDP is the one that triggered the upstream del. if + * this happens + * we most certainly have a bug in the PIM upstream + * state machine. We + * will not have a local reference unless the KAT is + * running. And if the + * KAT is running there MUST be an additional + * source-stream reference to + * the flow. Accounting for such cases requires lot of + * changes; perhaps + * address this in the next release? - XXX */ + flog_err( + EC_LIB_DEVELOPMENT, + "MSDP sa %s SPT teardown is causing the local entry to be removed", + sa->sg_str); + return; + } + + /* we are dropping the sa on upstream del we should not have an + * upstream reference */ + if (sa->up) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP local sa %s del; up non-NULL", + sa->sg_str); + } + sa->up = NULL; + } + pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); + } +} + +/* Local SA qualification needs to be re-evaluated when - + * 1. KAT is started or stopped + * 2. on RP changes + * 3. Whenever FHR status changes for a (S,G) - XXX - currently there + * is no clear path to transition an entry out of "MASK_FHR" need + * to discuss this with Donald. May result in some strangeness if the + * FHR is also the RP. + * 4. When msdp_reg timer is started or stopped + */ +void pim_msdp_sa_local_update(struct pim_upstream *up) +{ + struct pim_instance *pim = up->channel_oil->pim; + + if (pim_msdp_sa_local_add_ok(up)) { + pim_msdp_sa_local_add(pim, &up->sg); + } else { + pim_msdp_sa_local_del(pim, &up->sg); + } +} + +static void pim_msdp_sa_local_setup(struct pim_instance *pim) +{ + struct pim_upstream *up; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) + pim_msdp_sa_local_update(up); +} + +/* whenever the RP changes we need to re-evaluate the "local" SA-cache */ +/* XXX: needs to be tested */ +void pim_msdp_i_am_rp_changed(struct pim_instance *pim) +{ + struct listnode *sanode; + struct listnode *nextnode; + struct pim_msdp_sa *sa; + + if (!(pim->msdp.flags & PIM_MSDPF_ENABLE)) { + /* if the feature is not enabled do nothing */ + return; + } + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP i_am_rp changed"); + } + + /* mark all local entries as stale */ + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + if (sa->flags & PIM_MSDP_SAF_LOCAL) { + sa->flags |= PIM_MSDP_SAF_STALE; + } + } + + /* re-setup local SA entries */ + pim_msdp_sa_local_setup(pim); + + for (ALL_LIST_ELEMENTS(pim->msdp.sa_list, sanode, nextnode, sa)) { + /* purge stale SA entries */ + if (sa->flags & PIM_MSDP_SAF_STALE) { + /* clear the stale flag; the entry may be kept even + * after + * "local-deref" */ + sa->flags &= ~PIM_MSDP_SAF_STALE; + /* sa_deref can end up freeing the sa; so don't access + * contents after */ + pim_msdp_sa_deref(sa, PIM_MSDP_SAF_LOCAL); + } else { + /* if the souce is still active check if we can + * influence SPT */ + pim_msdp_sa_upstream_update(sa, NULL /* xg_up */, + "rp-change"); + } + } +} + +/* We track the join state of (*, G) entries. If G has sources in the SA-cache + * we need to setup or teardown SPT when the JoinDesired status changes for + * (*, G) */ +void pim_msdp_up_join_state_changed(struct pim_instance *pim, + struct pim_upstream *xg_up) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP join state changed for %s", xg_up->sg_str); + } + + /* If this is not really an XG entry just move on */ + if (!pim_addr_is_any(xg_up->sg.src) || pim_addr_is_any(xg_up->sg.grp)) { + return; + } + + /* XXX: Need to maintain SAs per-group to avoid all this unnecessary + * walking */ + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + if (pim_addr_cmp(sa->sg.grp, xg_up->sg.grp)) { + continue; + } + pim_msdp_sa_upstream_update(sa, xg_up, "up-jp-change"); + } +} + +static void pim_msdp_up_xg_del(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP %pSG del", sg); + } + + /* If this is not really an XG entry just move on */ + if (!pim_addr_is_any(sg->src) || pim_addr_is_any(sg->grp)) { + return; + } + + /* XXX: Need to maintain SAs per-group to avoid all this unnecessary + * walking */ + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + if (pim_addr_cmp(sa->sg.grp, sg->grp)) { + continue; + } + pim_msdp_sa_upstream_update(sa, NULL /* xg */, "up-jp-change"); + } +} + +void pim_msdp_up_del(struct pim_instance *pim, pim_sgaddr *sg) +{ + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP up %pSG del", sg); + } + if (pim_addr_is_any(sg->src)) { + pim_msdp_up_xg_del(pim, sg); + } else { + pim_msdp_sa_local_del_on_up_del(pim, sg); + } +} + +/* sa hash and peer list helpers */ +static unsigned int pim_msdp_sa_hash_key_make(const void *p) +{ + const struct pim_msdp_sa *sa = p; + + return pim_sgaddr_hash(sa->sg, 0); +} + +static bool pim_msdp_sa_hash_eq(const void *p1, const void *p2) +{ + const struct pim_msdp_sa *sa1 = p1; + const struct pim_msdp_sa *sa2 = p2; + + return !pim_sgaddr_cmp(sa1->sg, sa2->sg); +} + +static int pim_msdp_sa_comp(const void *p1, const void *p2) +{ + const struct pim_msdp_sa *sa1 = p1; + const struct pim_msdp_sa *sa2 = p2; + + return pim_sgaddr_cmp(sa1->sg, sa2->sg); +} + +/* RFC-3618:Sec-10.1.3 - Peer-RPF forwarding */ +/* XXX: this can use a bit of refining and extensions */ +bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp) +{ + struct pim_nexthop nexthop = {0}; + + if (mp->peer.s_addr == rp.s_addr) { + return true; + } + + /* check if the MSDP peer is the nexthop for the RP */ + if (pim_nexthop_lookup(mp->pim, &nexthop, rp, 0) && + nexthop.mrib_nexthop_addr.s_addr == mp->peer.s_addr) { + return true; + } + + return false; +} + +/************************ Peer session management **************************/ +char *pim_msdp_state_dump(enum pim_msdp_peer_state state, char *buf, + int buf_size) +{ + switch (state) { + case PIM_MSDP_DISABLED: + snprintf(buf, buf_size, "%s", "disabled"); + break; + case PIM_MSDP_INACTIVE: + snprintf(buf, buf_size, "%s", "inactive"); + break; + case PIM_MSDP_LISTEN: + snprintf(buf, buf_size, "%s", "listen"); + break; + case PIM_MSDP_CONNECTING: + snprintf(buf, buf_size, "%s", "connecting"); + break; + case PIM_MSDP_ESTABLISHED: + snprintf(buf, buf_size, "%s", "established"); + break; + default: + snprintf(buf, buf_size, "unk-%d", state); + } + return buf; +} + +static void pim_msdp_peer_state_chg_log(struct pim_msdp_peer *mp) +{ + char state_str[PIM_MSDP_STATE_STRLEN]; + + pim_msdp_state_dump(mp->state, state_str, sizeof(state_str)); + zlog_debug("MSDP peer %s state chg to %s", mp->key_str, state_str); +} + +/* MSDP Connection State Machine actions (defined in RFC-3618:Sec-11.2) */ +/* 11.2.A2: active peer - start connect retry timer; when the timer fires + * a tcp connection will be made */ +static void pim_msdp_peer_connect(struct pim_msdp_peer *mp) +{ + mp->state = PIM_MSDP_CONNECTING; + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_state_chg_log(mp); + } + + pim_msdp_peer_cr_timer_setup(mp, true /* start */); +} + +/* 11.2.A3: passive peer - just listen for connections */ +static void pim_msdp_peer_listen(struct pim_msdp_peer *mp) +{ + mp->state = PIM_MSDP_LISTEN; + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_state_chg_log(mp); + } + + /* this is interntionally asymmetric i.e. we set up listen-socket when + * the + * first listening peer is configured; but don't bother tearing it down + * when + * all the peers go down */ + pim_msdp_sock_listen(mp->pim); +} + +/* 11.2.A4 and 11.2.A5: transition active or passive peer to + * established state */ +void pim_msdp_peer_established(struct pim_msdp_peer *mp) +{ + if (mp->state != PIM_MSDP_ESTABLISHED) { + ++mp->est_flaps; + } + + mp->state = PIM_MSDP_ESTABLISHED; + mp->uptime = pim_time_monotonic_sec(); + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_state_chg_log(mp); + } + + /* stop retry timer on active peers */ + pim_msdp_peer_cr_timer_setup(mp, false /* start */); + + /* send KA; start KA and hold timers */ + pim_msdp_pkt_ka_tx(mp); + pim_msdp_peer_ka_timer_setup(mp, true /* start */); + pim_msdp_peer_hold_timer_setup(mp, true /* start */); + + pim_msdp_pkt_sa_tx_to_one_peer(mp); + + PIM_MSDP_PEER_WRITE_ON(mp); + PIM_MSDP_PEER_READ_ON(mp); +} + +/* 11.2.A6, 11.2.A7 and 11.2.A8: shutdown the peer tcp connection */ +void pim_msdp_peer_stop_tcp_conn(struct pim_msdp_peer *mp, bool chg_state) +{ + if (chg_state) { + if (mp->state == PIM_MSDP_ESTABLISHED) { + ++mp->est_flaps; + } + mp->state = PIM_MSDP_INACTIVE; + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_state_chg_log(mp); + } + } + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_peer_stop_tcp_conn", + mp->key_str); + } + /* stop read and write threads */ + PIM_MSDP_PEER_READ_OFF(mp); + PIM_MSDP_PEER_WRITE_OFF(mp); + + /* reset buffers */ + mp->packet_size = 0; + if (mp->ibuf) + stream_reset(mp->ibuf); + if (mp->obuf) + stream_fifo_clean(mp->obuf); + + /* stop all peer timers */ + pim_msdp_peer_ka_timer_setup(mp, false /* start */); + pim_msdp_peer_cr_timer_setup(mp, false /* start */); + pim_msdp_peer_hold_timer_setup(mp, false /* start */); + + /* close connection */ + if (mp->fd >= 0) { + close(mp->fd); + mp->fd = -1; + } +} + +/* RFC-3618:Sec-5.6 - stop the peer tcp connection and startover */ +void pim_msdp_peer_reset_tcp_conn(struct pim_msdp_peer *mp, const char *rc_str) +{ + if (PIM_DEBUG_EVENTS) { + zlog_debug("MSDP peer %s tcp reset %s", mp->key_str, rc_str); + snprintf(mp->last_reset, sizeof(mp->last_reset), "%s", rc_str); + } + + /* close the connection and transition to listening or connecting */ + pim_msdp_peer_stop_tcp_conn(mp, true /* chg_state */); + if (PIM_MSDP_PEER_IS_LISTENER(mp)) { + pim_msdp_peer_listen(mp); + } else { + pim_msdp_peer_connect(mp); + } +} + +static void pim_msdp_peer_timer_expiry_log(struct pim_msdp_peer *mp, + const char *timer_str) +{ + zlog_debug("MSDP peer %s %s timer expired", mp->key_str, timer_str); +} + +/* RFC-3618:Sec-5.4 - peer hold timer */ +static void pim_msdp_peer_hold_timer_cb(struct event *t) +{ + struct pim_msdp_peer *mp; + + mp = EVENT_ARG(t); + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_timer_expiry_log(mp, "hold"); + } + + if (mp->state != PIM_MSDP_ESTABLISHED) { + return; + } + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_state_chg_log(mp); + } + pim_msdp_peer_reset_tcp_conn(mp, "ht-expired"); +} + +static void pim_msdp_peer_hold_timer_setup(struct pim_msdp_peer *mp, bool start) +{ + struct pim_instance *pim = mp->pim; + EVENT_OFF(mp->hold_timer); + if (start) { + event_add_timer(pim->msdp.master, pim_msdp_peer_hold_timer_cb, + mp, pim->msdp.hold_time, &mp->hold_timer); + } +} + + +/* RFC-3618:Sec-5.5 - peer keepalive timer */ +static void pim_msdp_peer_ka_timer_cb(struct event *t) +{ + struct pim_msdp_peer *mp; + + mp = EVENT_ARG(t); + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_timer_expiry_log(mp, "ka"); + } + + pim_msdp_pkt_ka_tx(mp); + pim_msdp_peer_ka_timer_setup(mp, true /* start */); +} + +static void pim_msdp_peer_ka_timer_setup(struct pim_msdp_peer *mp, bool start) +{ + EVENT_OFF(mp->ka_timer); + if (start) { + event_add_timer(mp->pim->msdp.master, pim_msdp_peer_ka_timer_cb, + mp, mp->pim->msdp.keep_alive, &mp->ka_timer); + } +} + +static void pim_msdp_peer_active_connect(struct pim_msdp_peer *mp) +{ + int rc; + ++mp->conn_attempts; + rc = pim_msdp_sock_connect(mp); + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_peer_active_connect: %d", + mp->key_str, rc); + } + + switch (rc) { + case connect_error: + case -1: + /* connect failed restart the connect-retry timer */ + pim_msdp_peer_cr_timer_setup(mp, true /* start */); + break; + + case connect_success: + /* connect was sucessful move to established */ + pim_msdp_peer_established(mp); + break; + + case connect_in_progress: + /* for NB content we need to wait till sock is readable or + * writeable */ + PIM_MSDP_PEER_WRITE_ON(mp); + PIM_MSDP_PEER_READ_ON(mp); + /* also restart connect-retry timer to reset the socket if + * connect is + * not sucessful */ + pim_msdp_peer_cr_timer_setup(mp, true /* start */); + break; + } +} + +/* RFC-3618:Sec-5.6 - connection retry on active peer */ +static void pim_msdp_peer_cr_timer_cb(struct event *t) +{ + struct pim_msdp_peer *mp; + + mp = EVENT_ARG(t); + + if (PIM_DEBUG_MSDP_EVENTS) { + pim_msdp_peer_timer_expiry_log(mp, "connect-retry"); + } + + if (mp->state != PIM_MSDP_CONNECTING || PIM_MSDP_PEER_IS_LISTENER(mp)) { + return; + } + + pim_msdp_peer_active_connect(mp); +} + +static void pim_msdp_peer_cr_timer_setup(struct pim_msdp_peer *mp, bool start) +{ + EVENT_OFF(mp->cr_timer); + if (start) { + event_add_timer(mp->pim->msdp.master, pim_msdp_peer_cr_timer_cb, + mp, mp->pim->msdp.connection_retry, + &mp->cr_timer); + } +} + +/* if a valid packet is rxed from the peer we can restart hold timer */ +void pim_msdp_peer_pkt_rxed(struct pim_msdp_peer *mp) +{ + if (mp->state == PIM_MSDP_ESTABLISHED) { + pim_msdp_peer_hold_timer_setup(mp, true /* start */); + } +} + +/* if a valid packet is txed to the peer we can restart ka timer and avoid + * unnecessary ka noise in the network */ +void pim_msdp_peer_pkt_txed(struct pim_msdp_peer *mp) +{ + if (mp->state == PIM_MSDP_ESTABLISHED) { + pim_msdp_peer_ka_timer_setup(mp, true /* start */); + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP ka timer restart on pkt tx to %s", + mp->key_str); + } + } +} + +static void pim_msdp_addr2su(union sockunion *su, struct in_addr addr) +{ + sockunion_init(su); + su->sin.sin_addr = addr; + su->sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + su->sin.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ +} + +/* 11.2.A1: create a new peer and transition state to listen or connecting */ +struct pim_msdp_peer *pim_msdp_peer_add(struct pim_instance *pim, + const struct in_addr *peer, + const struct in_addr *local, + const char *mesh_group_name) +{ + struct pim_msdp_peer *mp; + + pim_msdp_enable(pim); + + mp = XCALLOC(MTYPE_PIM_MSDP_PEER, sizeof(*mp)); + + mp->pim = pim; + mp->peer = *peer; + pim_inet4_dump("", mp->peer, mp->key_str, sizeof(mp->key_str)); + pim_msdp_addr2su(&mp->su_peer, mp->peer); + mp->local = *local; + /* XXX: originator_id setting needs to move to the mesh group */ + pim->msdp.originator_id = *local; + pim_msdp_addr2su(&mp->su_local, mp->local); + if (mesh_group_name) + mp->mesh_group_name = + XSTRDUP(MTYPE_PIM_MSDP_MG_NAME, mesh_group_name); + + mp->state = PIM_MSDP_INACTIVE; + mp->fd = -1; + strlcpy(mp->last_reset, "-", sizeof(mp->last_reset)); + /* higher IP address is listener */ + if (ntohl(mp->local.s_addr) > ntohl(mp->peer.s_addr)) { + mp->flags |= PIM_MSDP_PEERF_LISTENER; + } + + /* setup packet buffers */ + mp->ibuf = stream_new(PIM_MSDP_MAX_PACKET_SIZE); + mp->obuf = stream_fifo_new(); + + /* insert into misc tables for easy access */ + mp = hash_get(pim->msdp.peer_hash, mp, hash_alloc_intern); + listnode_add_sort(pim->msdp.peer_list, mp); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP peer %s created", mp->key_str); + + pim_msdp_peer_state_chg_log(mp); + } + + /* fireup the connect state machine */ + if (PIM_MSDP_PEER_IS_LISTENER(mp)) { + pim_msdp_peer_listen(mp); + } else { + pim_msdp_peer_connect(mp); + } + return mp; +} + +struct pim_msdp_peer *pim_msdp_peer_find(struct pim_instance *pim, + struct in_addr peer_addr) +{ + struct pim_msdp_peer lookup; + + lookup.peer = peer_addr; + return hash_lookup(pim->msdp.peer_hash, &lookup); +} + +/* release all mem associated with a peer */ +static void pim_msdp_peer_free(struct pim_msdp_peer *mp) +{ + /* + * Let's make sure we are not running when we delete + * the underlying data structure + */ + pim_msdp_peer_stop_tcp_conn(mp, false); + + if (mp->ibuf) { + stream_free(mp->ibuf); + } + + if (mp->obuf) { + stream_fifo_free(mp->obuf); + } + + XFREE(MTYPE_PIM_MSDP_MG_NAME, mp->mesh_group_name); + + mp->pim = NULL; + XFREE(MTYPE_PIM_MSDP_PEER, mp); +} + +/* delete the peer config */ +void pim_msdp_peer_del(struct pim_msdp_peer **mp) +{ + if (*mp == NULL) + return; + + /* stop the tcp connection and shutdown all timers */ + pim_msdp_peer_stop_tcp_conn(*mp, true /* chg_state */); + + /* remove the session from various tables */ + listnode_delete((*mp)->pim->msdp.peer_list, *mp); + hash_release((*mp)->pim->msdp.peer_hash, *mp); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP peer %s deleted", (*mp)->key_str); + } + + /* free up any associated memory */ + pim_msdp_peer_free(*mp); + *mp = NULL; +} + +void pim_msdp_peer_change_source(struct pim_msdp_peer *mp, + const struct in_addr *addr) +{ + pim_msdp_peer_stop_tcp_conn(mp, true); + + mp->local = *addr; + + if (PIM_MSDP_PEER_IS_LISTENER(mp)) + pim_msdp_peer_listen(mp); + else + pim_msdp_peer_connect(mp); +} + +/* peer hash and peer list helpers */ +static unsigned int pim_msdp_peer_hash_key_make(const void *p) +{ + const struct pim_msdp_peer *mp = p; + return (jhash_1word(mp->peer.s_addr, 0)); +} + +static bool pim_msdp_peer_hash_eq(const void *p1, const void *p2) +{ + const struct pim_msdp_peer *mp1 = p1; + const struct pim_msdp_peer *mp2 = p2; + + return (mp1->peer.s_addr == mp2->peer.s_addr); +} + +static int pim_msdp_peer_comp(const void *p1, const void *p2) +{ + const struct pim_msdp_peer *mp1 = p1; + const struct pim_msdp_peer *mp2 = p2; + + if (ntohl(mp1->peer.s_addr) < ntohl(mp2->peer.s_addr)) + return -1; + + if (ntohl(mp1->peer.s_addr) > ntohl(mp2->peer.s_addr)) + return 1; + + return 0; +} + +/************************** Mesh group management **************************/ +void pim_msdp_mg_free(struct pim_instance *pim, struct pim_msdp_mg **mgp) +{ + struct pim_msdp_mg_mbr *mbr; + struct listnode *n, *nn; + + if (*mgp == NULL) + return; + + /* SIP is being removed - tear down all active peer sessions */ + for (ALL_LIST_ELEMENTS((*mgp)->mbr_list, n, nn, mbr)) + pim_msdp_mg_mbr_del((*mgp), mbr); + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP mesh-group %s deleted", + (*mgp)->mesh_group_name); + } + + XFREE(MTYPE_PIM_MSDP_MG_NAME, (*mgp)->mesh_group_name); + + if ((*mgp)->mbr_list) + list_delete(&(*mgp)->mbr_list); + + SLIST_REMOVE(&pim->msdp.mglist, (*mgp), pim_msdp_mg, mg_entry); + XFREE(MTYPE_PIM_MSDP_MG, (*mgp)); +} + +struct pim_msdp_mg *pim_msdp_mg_new(struct pim_instance *pim, + const char *mesh_group_name) +{ + struct pim_msdp_mg *mg; + + mg = XCALLOC(MTYPE_PIM_MSDP_MG, sizeof(*mg)); + + mg->mesh_group_name = XSTRDUP(MTYPE_PIM_MSDP_MG_NAME, mesh_group_name); + mg->mbr_list = list_new(); + mg->mbr_list->del = (void (*)(void *))pim_msdp_mg_mbr_free; + mg->mbr_list->cmp = (int (*)(void *, void *))pim_msdp_mg_mbr_comp; + + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP mesh-group %s created", mg->mesh_group_name); + } + + SLIST_INSERT_HEAD(&pim->msdp.mglist, mg, mg_entry); + + return mg; +} + +static int pim_msdp_mg_mbr_comp(const void *p1, const void *p2) +{ + const struct pim_msdp_mg_mbr *mbr1 = p1; + const struct pim_msdp_mg_mbr *mbr2 = p2; + + if (ntohl(mbr1->mbr_ip.s_addr) < ntohl(mbr2->mbr_ip.s_addr)) + return -1; + + if (ntohl(mbr1->mbr_ip.s_addr) > ntohl(mbr2->mbr_ip.s_addr)) + return 1; + + return 0; +} + +static void pim_msdp_mg_mbr_free(struct pim_msdp_mg_mbr *mbr) +{ + XFREE(MTYPE_PIM_MSDP_MG_MBR, mbr); +} + +void pim_msdp_mg_mbr_del(struct pim_msdp_mg *mg, struct pim_msdp_mg_mbr *mbr) +{ + /* Delete active peer session if any */ + if (mbr->mp) { + pim_msdp_peer_del(&mbr->mp); + } + + listnode_delete(mg->mbr_list, mbr); + if (PIM_DEBUG_MSDP_EVENTS) { + char ip_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", mbr->mbr_ip, ip_str, sizeof(ip_str)); + zlog_debug("MSDP mesh-group %s mbr %s deleted", + mg->mesh_group_name, ip_str); + } + pim_msdp_mg_mbr_free(mbr); + if (mg->mbr_cnt) { + --mg->mbr_cnt; + } +} + +static void pim_msdp_src_del(struct pim_msdp_mg *mg) +{ + struct pim_msdp_mg_mbr *mbr; + struct listnode *mbr_node; + + /* SIP is being removed - tear down all active peer sessions */ + for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbr_node, mbr)) { + if (mbr->mp) + pim_msdp_peer_del(&mbr->mp); + } + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_debug("MSDP mesh-group %s src cleared", + mg->mesh_group_name); + } +} + +/*********************** MSDP feature APIs *********************************/ +int pim_msdp_config_write(struct pim_instance *pim, struct vty *vty, + const char *spaces) +{ + struct pim_msdp_mg *mg; + struct listnode *mbrnode; + struct pim_msdp_mg_mbr *mbr; + char src_str[INET_ADDRSTRLEN]; + int count = 0; + + if (SLIST_EMPTY(&pim->msdp.mglist)) + return count; + + SLIST_FOREACH (mg, &pim->msdp.mglist, mg_entry) { + if (mg->src_ip.s_addr != INADDR_ANY) { + pim_inet4_dump("", mg->src_ip, src_str, + sizeof(src_str)); + vty_out(vty, "%sip msdp mesh-group %s source %s\n", + spaces, mg->mesh_group_name, src_str); + ++count; + } + + for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbrnode, mbr)) { + vty_out(vty, "%sip msdp mesh-group %s member %pI4\n", + spaces, mg->mesh_group_name, &mbr->mbr_ip); + ++count; + } + } + + return count; +} + +bool pim_msdp_peer_config_write(struct vty *vty, struct pim_instance *pim, + const char *spaces) +{ + struct pim_msdp_peer *mp; + struct listnode *node; + bool written = false; + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, node, mp)) { + /* Skip meshed group peers. */ + if (mp->flags & PIM_MSDP_PEERF_IN_GROUP) + continue; + + vty_out(vty, "%sip msdp peer %pI4 source %pI4\n", spaces, + &mp->peer, &mp->local); + written = true; + } + + return written; +} + +/* Enable feature including active/periodic timers etc. on the first peer + * config. Till then MSDP should just stay quiet. */ +static void pim_msdp_enable(struct pim_instance *pim) +{ + if (pim->msdp.flags & PIM_MSDPF_ENABLE) { + /* feature is already enabled */ + return; + } + pim->msdp.flags |= PIM_MSDPF_ENABLE; + pim->msdp.work_obuf = stream_new(PIM_MSDP_MAX_PACKET_SIZE); + pim_msdp_sa_adv_timer_setup(pim, true /* start */); + /* setup sa cache based on local sources */ + pim_msdp_sa_local_setup(pim); +} + +/* MSDP init */ +void pim_msdp_init(struct pim_instance *pim, struct event_loop *master) +{ + pim->msdp.master = master; + char hash_name[64]; + + snprintf(hash_name, sizeof(hash_name), "PIM %s MSDP Peer Hash", + pim->vrf->name); + pim->msdp.peer_hash = hash_create(pim_msdp_peer_hash_key_make, + pim_msdp_peer_hash_eq, hash_name); + pim->msdp.peer_list = list_new(); + pim->msdp.peer_list->del = (void (*)(void *))pim_msdp_peer_free; + pim->msdp.peer_list->cmp = (int (*)(void *, void *))pim_msdp_peer_comp; + + snprintf(hash_name, sizeof(hash_name), "PIM %s MSDP SA Hash", + pim->vrf->name); + pim->msdp.sa_hash = hash_create(pim_msdp_sa_hash_key_make, + pim_msdp_sa_hash_eq, hash_name); + pim->msdp.sa_list = list_new(); + pim->msdp.sa_list->del = (void (*)(void *))pim_msdp_sa_free; + pim->msdp.sa_list->cmp = (int (*)(void *, void *))pim_msdp_sa_comp; +} + +/* counterpart to MSDP init; XXX: unused currently */ +void pim_msdp_exit(struct pim_instance *pim) +{ + struct pim_msdp_mg *mg; + + pim_msdp_sa_adv_timer_setup(pim, false); + + /* Stop listener and delete all peer sessions */ + while ((mg = SLIST_FIRST(&pim->msdp.mglist)) != NULL) + pim_msdp_mg_free(pim, &mg); + + hash_clean_and_free(&pim->msdp.peer_hash, NULL); + + if (pim->msdp.peer_list) { + list_delete(&pim->msdp.peer_list); + } + + hash_clean_and_free(&pim->msdp.sa_hash, NULL); + + if (pim->msdp.sa_list) { + list_delete(&pim->msdp.sa_list); + } + + if (pim->msdp.work_obuf) + stream_free(pim->msdp.work_obuf); + pim->msdp.work_obuf = NULL; +} + +void pim_msdp_mg_src_add(struct pim_instance *pim, struct pim_msdp_mg *mg, + struct in_addr *ai) +{ + struct pim_msdp_mg_mbr *mbr; + struct listnode *mbr_node; + + /* Stop all connections and remove data structures. */ + pim_msdp_src_del(mg); + + /* Set new address. */ + mg->src_ip = *ai; + + /* No new address, disable everyone. */ + if (ai->s_addr == INADDR_ANY) { + if (PIM_DEBUG_MSDP_EVENTS) + zlog_debug("MSDP mesh-group %s src unset", + mg->mesh_group_name); + return; + } + + /* Create data structures and start TCP connection. */ + for (ALL_LIST_ELEMENTS_RO(mg->mbr_list, mbr_node, mbr)) + mbr->mp = pim_msdp_peer_add(pim, &mbr->mbr_ip, &mg->src_ip, + mg->mesh_group_name); + + if (PIM_DEBUG_MSDP_EVENTS) + zlog_debug("MSDP mesh-group %s src %pI4 set", + mg->mesh_group_name, &mg->src_ip); +} + +struct pim_msdp_mg_mbr *pim_msdp_mg_mbr_add(struct pim_instance *pim, + struct pim_msdp_mg *mg, + struct in_addr *ia) +{ + struct pim_msdp_mg_mbr *mbr; + + mbr = XCALLOC(MTYPE_PIM_MSDP_MG_MBR, sizeof(*mbr)); + mbr->mbr_ip = *ia; + listnode_add_sort(mg->mbr_list, mbr); + + /* if valid SIP has been configured add peer session */ + if (mg->src_ip.s_addr != INADDR_ANY) + mbr->mp = pim_msdp_peer_add(pim, &mbr->mbr_ip, &mg->src_ip, + mg->mesh_group_name); + + if (PIM_DEBUG_MSDP_EVENTS) + zlog_debug("MSDP mesh-group %s mbr %pI4 created", + mg->mesh_group_name, &mbr->mbr_ip); + + ++mg->mbr_cnt; + + return mbr; +} diff --git a/pimd/pim_msdp.h b/pimd/pim_msdp.h new file mode 100644 index 0000000..ddc015f --- /dev/null +++ b/pimd/pim_msdp.h @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +#ifndef PIM_MSDP_H +#define PIM_MSDP_H + +#include "lib/openbsd-queue.h" + +enum pim_msdp_peer_state { + PIM_MSDP_DISABLED, + PIM_MSDP_INACTIVE, + PIM_MSDP_LISTEN, + PIM_MSDP_CONNECTING, + PIM_MSDP_ESTABLISHED +}; + +/* SA and KA TLVs are processed; rest ignored */ +enum pim_msdp_tlv { + PIM_MSDP_V4_SOURCE_ACTIVE = 1, + PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST, + PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE, + PIM_MSDP_KEEPALIVE, + PIM_MSDP_RESERVED, + PIM_MSDP_TRACEROUTE_PROGRESS, + PIM_MSDP_TRACEROUTE_REPLY, +}; + +/* MSDP error codes */ +enum pim_msdp_err { + PIM_MSDP_ERR_NONE = 0, + PIM_MSDP_ERR_OOM = -1, + PIM_MSDP_ERR_PEER_EXISTS = -2, + PIM_MSDP_ERR_MAX_MESH_GROUPS = -3, + PIM_MSDP_ERR_NO_PEER = -4, + PIM_MSDP_ERR_MG_MBR_EXISTS = -5, + PIM_MSDP_ERR_NO_MG = -6, + PIM_MSDP_ERR_NO_MG_MBR = -7, + PIM_MSDP_ERR_SIP_EQ_DIP = -8, +}; + +#define PIM_MSDP_STATE_STRLEN 16 +#define PIM_MSDP_UPTIME_STRLEN 80 +#define PIM_MSDP_TIMER_STRLEN 12 +#define PIM_MSDP_TCP_PORT 639 +#define PIM_MSDP_SOCKET_SNDBUF_SIZE 65536 + +enum pim_msdp_sa_flags { + PIM_MSDP_SAF_NONE = 0, + /* There are two cases where we can pickup an active source locally - + * 1. We are RP and got a source-register from the FHR + * 2. We are RP and FHR and learnt a new directly connected source on a + * DR interface */ + PIM_MSDP_SAF_LOCAL = (1 << 0), + /* We got this in the MSDP SA TLV from a peer (and this passed peer-RPF + * checks) */ + PIM_MSDP_SAF_PEER = (1 << 1), + PIM_MSDP_SAF_REF = (PIM_MSDP_SAF_LOCAL | PIM_MSDP_SAF_PEER), + PIM_MSDP_SAF_STALE = (1 << 2), /* local entries can get kicked out on + * misc pim events such as RP change */ + PIM_MSDP_SAF_UP_DEL_IN_PROG = (1 << 3) +}; + +struct pim_msdp_sa { + struct pim_instance *pim; + + pim_sgaddr sg; + char sg_str[PIM_SG_LEN]; + struct in_addr rp; /* Last RP address associated with this SA */ + struct in_addr peer; /* last peer from who we heard this SA */ + enum pim_msdp_sa_flags flags; + +/* rfc-3618 is missing default value for SA-hold-down-Period. pulled + * this number from industry-standards */ +#define PIM_MSDP_SA_HOLD_TIME ((3*60)+30) + struct event *sa_state_timer; // 5.6 + int64_t uptime; + + struct pim_upstream *up; +}; + +enum pim_msdp_peer_flags { + PIM_MSDP_PEERF_NONE = 0, + PIM_MSDP_PEERF_LISTENER = (1 << 0), +#define PIM_MSDP_PEER_IS_LISTENER(mp) (mp->flags & PIM_MSDP_PEERF_LISTENER) + PIM_MSDP_PEERF_SA_JUST_SENT = (1 << 1), + /** Flag to signalize that peer belongs to a group. */ + PIM_MSDP_PEERF_IN_GROUP = (1 << 2), +}; + +struct pim_msdp_peer { + struct pim_instance *pim; + + /* configuration */ + struct in_addr local; + struct in_addr peer; + char *mesh_group_name; + char key_str[INET_ADDRSTRLEN]; + + /* state */ + enum pim_msdp_peer_state state; + enum pim_msdp_peer_flags flags; + + /* TCP socket info */ + union sockunion su_local; + union sockunion su_peer; + int fd; + +/* protocol timers */ +#define PIM_MSDP_PEER_HOLD_TIME 75 + struct event *hold_timer; // 5.4 +#define PIM_MSDP_PEER_KA_TIME 60 + struct event *ka_timer; // 5.5 +#define PIM_MSDP_PEER_CONNECT_RETRY_TIME 30 + struct event *cr_timer; // 5.6 + + /* packet thread and buffers */ + uint32_t packet_size; + struct stream *ibuf; + struct stream_fifo *obuf; + struct event *t_read; + struct event *t_write; + + /* stats */ + uint32_t conn_attempts; + uint32_t est_flaps; + uint32_t sa_cnt; /* number of SAs attributed to this peer */ +#define PIM_MSDP_PEER_LAST_RESET_STR 20 + char last_reset[PIM_MSDP_PEER_LAST_RESET_STR]; + + /* packet stats */ + uint32_t ka_tx_cnt; + uint32_t sa_tx_cnt; + uint32_t ka_rx_cnt; + uint32_t sa_rx_cnt; + uint32_t unk_rx_cnt; + + /* timestamps */ + int64_t uptime; +}; + +struct pim_msdp_mg_mbr { + struct in_addr mbr_ip; + struct pim_msdp_peer *mp; +}; + +/* PIM MSDP mesh-group */ +struct pim_msdp_mg { + char *mesh_group_name; + struct in_addr src_ip; + uint32_t mbr_cnt; + struct list *mbr_list; + + /** Belongs to PIM instance list. */ + SLIST_ENTRY(pim_msdp_mg) mg_entry; +}; + +SLIST_HEAD(pim_mesh_group_list, pim_msdp_mg); + +enum pim_msdp_flags { + PIM_MSDPF_NONE = 0, + PIM_MSDPF_ENABLE = (1 << 0), + PIM_MSDPF_LISTENER = (1 << 1) +}; + +struct pim_msdp_listener { + int fd; + union sockunion su; + struct event *thread; +}; + +struct pim_msdp { + enum pim_msdp_flags flags; + struct event_loop *master; + struct pim_msdp_listener listener; + uint32_t rejected_accepts; + + /* MSDP peer info */ + struct hash *peer_hash; + struct list *peer_list; + +/* MSDP active-source info */ +#define PIM_MSDP_SA_ADVERTISMENT_TIME 60 + struct event *sa_adv_timer; // 5.6 + struct hash *sa_hash; + struct list *sa_list; + uint32_t local_cnt; + + /* keep a scratch pad for building SA TLVs */ + struct stream *work_obuf; + + struct in_addr originator_id; + + /** List of mesh groups. */ + struct pim_mesh_group_list mglist; + + /** MSDP global hold time period. */ + uint32_t hold_time; + /** MSDP global keep alive period. */ + uint32_t keep_alive; + /** MSDP global connection retry period. */ + uint32_t connection_retry; +}; + +#define PIM_MSDP_PEER_READ_ON(mp) \ + event_add_read(mp->pim->msdp.master, pim_msdp_read, mp, mp->fd, \ + &mp->t_read) + +#define PIM_MSDP_PEER_WRITE_ON(mp) \ + event_add_write(mp->pim->msdp.master, pim_msdp_write, mp, mp->fd, \ + &mp->t_write) + +#define PIM_MSDP_PEER_READ_OFF(mp) event_cancel(&mp->t_read) +#define PIM_MSDP_PEER_WRITE_OFF(mp) event_cancel(&mp->t_write) + +#if PIM_IPV != 6 +// struct pim_msdp *msdp; +struct pim_instance; +void pim_msdp_init(struct pim_instance *pim, struct event_loop *master); +void pim_msdp_exit(struct pim_instance *pim); +char *pim_msdp_state_dump(enum pim_msdp_peer_state state, char *buf, + int buf_size); +struct pim_msdp_peer *pim_msdp_peer_find(struct pim_instance *pim, + struct in_addr peer_addr); +void pim_msdp_peer_established(struct pim_msdp_peer *mp); +void pim_msdp_peer_pkt_rxed(struct pim_msdp_peer *mp); +void pim_msdp_peer_stop_tcp_conn(struct pim_msdp_peer *mp, bool chg_state); +void pim_msdp_peer_reset_tcp_conn(struct pim_msdp_peer *mp, const char *rc_str); +void pim_msdp_write(struct event *thread); +int pim_msdp_config_write(struct pim_instance *pim, struct vty *vty, + const char *spaces); +bool pim_msdp_peer_config_write(struct vty *vty, struct pim_instance *pim, + const char *spaces); +void pim_msdp_peer_pkt_txed(struct pim_msdp_peer *mp); +void pim_msdp_sa_ref(struct pim_instance *pim, struct pim_msdp_peer *mp, + pim_sgaddr *sg, struct in_addr rp); +void pim_msdp_sa_local_update(struct pim_upstream *up); +void pim_msdp_sa_local_del(struct pim_instance *pim, pim_sgaddr *sg); +void pim_msdp_i_am_rp_changed(struct pim_instance *pim); +bool pim_msdp_peer_rpf_check(struct pim_msdp_peer *mp, struct in_addr rp); +void pim_msdp_up_join_state_changed(struct pim_instance *pim, + struct pim_upstream *xg_up); +void pim_msdp_up_del(struct pim_instance *pim, pim_sgaddr *sg); +enum pim_msdp_err pim_msdp_mg_del(struct pim_instance *pim, + const char *mesh_group_name); + +/** + * Allocates a new mesh group data structure under PIM instance. + */ +struct pim_msdp_mg *pim_msdp_mg_new(struct pim_instance *pim, + const char *mesh_group_name); +/** + * Deallocates mesh group data structure under PIM instance. + */ +void pim_msdp_mg_free(struct pim_instance *pim, struct pim_msdp_mg **mgp); + +/** + * Change the source address of a mesh group peers. It will do the following: + * - Close all peers TCP connections + * - Recreate peers data structure + * - Start TCP connections with new local address. + */ +void pim_msdp_mg_src_add(struct pim_instance *pim, struct pim_msdp_mg *mg, + struct in_addr *ai); + +/** + * Add new peer to mesh group and starts the connection if source address is + * configured. + */ +struct pim_msdp_mg_mbr *pim_msdp_mg_mbr_add(struct pim_instance *pim, + struct pim_msdp_mg *mg, + struct in_addr *ia); + +/** + * Stops the connection and removes the peer data structures. + */ +void pim_msdp_mg_mbr_del(struct pim_msdp_mg *mg, struct pim_msdp_mg_mbr *mbr); + +/** + * Allocates MSDP peer data structure, adds peer to group name + * `mesh_group_name` and starts state machine. If no group name is provided then + * the peer will work standalone. + * + * \param pim PIM instance + * \param peer_addr peer address + * \param local_addr local listening address + * \param mesh_group_name mesh group name (or `NULL` for peers without group). + */ +struct pim_msdp_peer *pim_msdp_peer_add(struct pim_instance *pim, + const struct in_addr *peer_addr, + const struct in_addr *local_addr, + const char *mesh_group_name); + +/** + * Stops peer state machine and free memory. + */ +void pim_msdp_peer_del(struct pim_msdp_peer **mp); + +/** + * Changes peer source address. + * + * NOTE: + * This will cause the connection to drop and start again. + */ +void pim_msdp_peer_change_source(struct pim_msdp_peer *mp, + const struct in_addr *addr); + +#else /* PIM_IPV == 6 */ +static inline void pim_msdp_init(struct pim_instance *pim, + struct event_loop *master) +{ +} + +static inline void pim_msdp_exit(struct pim_instance *pim) +{ +} + +static inline void pim_msdp_i_am_rp_changed(struct pim_instance *pim) +{ +} + +static inline void pim_msdp_up_join_state_changed(struct pim_instance *pim, + struct pim_upstream *xg_up) +{ +} + +static inline void pim_msdp_up_del(struct pim_instance *pim, pim_sgaddr *sg) +{ +} + +static inline void pim_msdp_sa_local_update(struct pim_upstream *up) +{ +} + +static inline void pim_msdp_sa_local_del(struct pim_instance *pim, + pim_sgaddr *sg) +{ +} + +static inline int pim_msdp_config_write(struct pim_instance *pim, + struct vty *vty, const char *spaces) +{ + return 0; +} + +static inline bool pim_msdp_peer_config_write(struct vty *vty, + struct pim_instance *pim, + const char *spaces) +{ + return false; +} +#endif /* PIM_IPV == 6 */ + +#endif diff --git a/pimd/pim_msdp_packet.c b/pimd/pim_msdp_packet.c new file mode 100644 index 0000000..4324a96 --- /dev/null +++ b/pimd/pim_msdp_packet.c @@ -0,0 +1,780 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP packet helper + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +#include + +#include +#include +#include +#include "frrevent.h" +#include +#include + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_rp.h" +#include "pim_str.h" +#include "pim_util.h" +#include "pim_errors.h" + +#include "pim_msdp.h" +#include "pim_msdp_packet.h" +#include "pim_msdp_socket.h" + +static char *pim_msdp_pkt_type_dump(enum pim_msdp_tlv type, char *buf, + int buf_size) +{ + switch (type) { + case PIM_MSDP_V4_SOURCE_ACTIVE: + snprintf(buf, buf_size, "%s", "SA"); + break; + case PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST: + snprintf(buf, buf_size, "%s", "SA_REQ"); + break; + case PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE: + snprintf(buf, buf_size, "%s", "SA_RESP"); + break; + case PIM_MSDP_KEEPALIVE: + snprintf(buf, buf_size, "%s", "KA"); + break; + case PIM_MSDP_RESERVED: + snprintf(buf, buf_size, "%s", "RSVD"); + break; + case PIM_MSDP_TRACEROUTE_PROGRESS: + snprintf(buf, buf_size, "%s", "TRACE_PROG"); + break; + case PIM_MSDP_TRACEROUTE_REPLY: + snprintf(buf, buf_size, "%s", "TRACE_REPLY"); + break; + default: + snprintf(buf, buf_size, "UNK-%d", type); + } + return buf; +} + +static void pim_msdp_pkt_sa_dump_one(struct stream *s) +{ + pim_sgaddr sg; + + /* just throw away the three reserved bytes */ + stream_get3(s); + /* throw away the prefix length also */ + stream_getc(s); + + memset(&sg, 0, sizeof(sg)); + sg.grp.s_addr = stream_get_ipv4(s); + sg.src.s_addr = stream_get_ipv4(s); + + zlog_debug(" sg %pSG", &sg); +} + +static void pim_msdp_pkt_sa_dump(struct stream *s) +{ + const size_t header_length = PIM_MSDP_SA_X_SIZE - PIM_MSDP_HEADER_SIZE; + size_t payload_length; + int entry_cnt; + int i; + struct in_addr rp; /* Last RP address associated with this SA */ + + if (header_length > STREAM_READABLE(s)) { + zlog_err("BUG MSDP SA bad header (readable %zu expected %zu)", + STREAM_READABLE(s), header_length); + return; + } + + entry_cnt = stream_getc(s); + rp.s_addr = stream_get_ipv4(s); + + if (PIM_DEBUG_MSDP_PACKETS) { + char rp_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", rp, rp_str, sizeof(rp_str)); + zlog_debug(" entry_cnt %d rp %s", entry_cnt, rp_str); + } + + payload_length = (size_t)entry_cnt * PIM_MSDP_SA_ONE_ENTRY_SIZE; + if (payload_length > STREAM_READABLE(s)) { + zlog_err("BUG MSDP SA bad length (readable %zu expected %zu)", + STREAM_READABLE(s), payload_length); + return; + } + + /* dump SAs */ + for (i = 0; i < entry_cnt; ++i) { + pim_msdp_pkt_sa_dump_one(s); + } +} + +static void pim_msdp_pkt_dump(struct pim_msdp_peer *mp, int type, int len, + bool rx, struct stream *s) +{ + char type_str[PIM_MSDP_PKT_TYPE_STRLEN]; + + pim_msdp_pkt_type_dump(type, type_str, sizeof(type_str)); + + zlog_debug("MSDP peer %s pkt %s type %s len %d", mp->key_str, + rx ? "rx" : "tx", type_str, len); + + if (!s) { + return; + } + + if (len < PIM_MSDP_HEADER_SIZE) { + zlog_err("invalid MSDP header length"); + return; + } + + switch (type) { + case PIM_MSDP_V4_SOURCE_ACTIVE: + pim_msdp_pkt_sa_dump(s); + break; + default:; + } +} + +/* Check file descriptor whether connect is established. */ +static void pim_msdp_connect_check(struct pim_msdp_peer *mp) +{ + int status; + socklen_t slen; + int ret; + + if (mp->state != PIM_MSDP_CONNECTING) { + /* if we are here it means we are not in a connecting or + * established state + * for now treat this as a fatal error */ + pim_msdp_peer_reset_tcp_conn(mp, "invalid-state"); + return; + } + + PIM_MSDP_PEER_READ_OFF(mp); + PIM_MSDP_PEER_WRITE_OFF(mp); + + /* Check file descriptor. */ + slen = sizeof(status); + ret = getsockopt(mp->fd, SOL_SOCKET, SO_ERROR, (void *)&status, &slen); + + /* If getsockopt is fail, this is fatal error. */ + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, + "can't get sockopt for nonblocking connect"); + pim_msdp_peer_reset_tcp_conn(mp, "connect-failed"); + return; + } + + /* When status is 0 then TCP connection is established. */ + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_connect_check %s", mp->key_str, + status ? "fail" : "success"); + } + if (status == 0) { + pim_msdp_peer_established(mp); + } else { + pim_msdp_peer_reset_tcp_conn(mp, "connect-failed"); + } +} + +static void pim_msdp_pkt_delete(struct pim_msdp_peer *mp) +{ + stream_free(stream_fifo_pop(mp->obuf)); +} + +static void pim_msdp_pkt_add(struct pim_msdp_peer *mp, struct stream *s) +{ + stream_fifo_push(mp->obuf, s); +} + +static void pim_msdp_write_proceed_actions(struct pim_msdp_peer *mp) +{ + if (stream_fifo_head(mp->obuf)) { + PIM_MSDP_PEER_WRITE_ON(mp); + } +} + +void pim_msdp_write(struct event *thread) +{ + struct pim_msdp_peer *mp; + struct stream *s; + int num; + enum pim_msdp_tlv type; + int len; + int work_cnt = 0; + int work_max_cnt = 100; + + mp = EVENT_ARG(thread); + mp->t_write = NULL; + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_write", mp->key_str); + } + if (mp->fd < 0) { + return; + } + + /* check if TCP connection is established */ + if (mp->state != PIM_MSDP_ESTABLISHED) { + pim_msdp_connect_check(mp); + return; + } + + s = stream_fifo_head(mp->obuf); + if (!s) { + pim_msdp_write_proceed_actions(mp); + return; + } + + /* Nonblocking write until TCP output buffer is full */ + do { + int writenum; + + /* Number of bytes to be sent */ + writenum = stream_get_endp(s) - stream_get_getp(s); + + /* Call write() system call */ + num = write(mp->fd, stream_pnt(s), writenum); + if (num < 0) { + /* write failed either retry needed or error */ + if (ERRNO_IO_RETRY(errno)) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug( + "MSDP peer %s pim_msdp_write io retry", + mp->key_str); + } + break; + } + + pim_msdp_peer_reset_tcp_conn(mp, "pkt-tx-failed"); + return; + } + + if (num != writenum) { + /* Partial write */ + stream_forward_getp(s, num); + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug( + "MSDP peer %s pim_msdp_partial_write", + mp->key_str); + } + break; + } + + /* Retrieve msdp packet type. */ + stream_set_getp(s, 0); + type = stream_getc(s); + len = stream_getw(s); + switch (type) { + case PIM_MSDP_KEEPALIVE: + mp->ka_tx_cnt++; + break; + case PIM_MSDP_V4_SOURCE_ACTIVE: + mp->sa_tx_cnt++; + break; + case PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST: + case PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE: + case PIM_MSDP_RESERVED: + case PIM_MSDP_TRACEROUTE_PROGRESS: + case PIM_MSDP_TRACEROUTE_REPLY: + break; + } + if (PIM_DEBUG_MSDP_PACKETS) { + pim_msdp_pkt_dump(mp, type, len, false /*rx*/, s); + } + + /* packet sent delete it. */ + pim_msdp_pkt_delete(mp); + + ++work_cnt; + /* may need to pause if we have done too much work in this + * loop */ + if (work_cnt >= work_max_cnt) { + break; + } + } while ((s = stream_fifo_head(mp->obuf)) != NULL); + pim_msdp_write_proceed_actions(mp); + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_write wrote %d packets", + mp->key_str, work_cnt); + } +} + +static void pim_msdp_pkt_send(struct pim_msdp_peer *mp, struct stream *s) +{ + /* Add packet to the end of list. */ + pim_msdp_pkt_add(mp, s); + + PIM_MSDP_PEER_WRITE_ON(mp); +} + +void pim_msdp_pkt_ka_tx(struct pim_msdp_peer *mp) +{ + struct stream *s; + + if (mp->state != PIM_MSDP_ESTABLISHED) { + /* don't tx anything unless a session is established */ + return; + } + s = stream_new(PIM_MSDP_KA_TLV_MAX_SIZE); + stream_putc(s, PIM_MSDP_KEEPALIVE); + stream_putw(s, PIM_MSDP_KA_TLV_MAX_SIZE); + + pim_msdp_pkt_send(mp, s); +} + +static void pim_msdp_pkt_sa_push_to_one_peer(struct pim_instance *pim, + struct pim_msdp_peer *mp) +{ + struct stream *s; + + if (mp->state != PIM_MSDP_ESTABLISHED) { + /* don't tx anything unless a session is established */ + return; + } + s = stream_dup(pim->msdp.work_obuf); + if (s) { + pim_msdp_pkt_send(mp, s); + mp->flags |= PIM_MSDP_PEERF_SA_JUST_SENT; + } +} + +/* push the stream into the obuf fifo of all the peers */ +static void pim_msdp_pkt_sa_push(struct pim_instance *pim, + struct pim_msdp_peer *mp) +{ + struct listnode *mpnode; + + if (mp) { + pim_msdp_pkt_sa_push_to_one_peer(pim, mp); + } else { + for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_pkt_sa_push", + mp->key_str); + } + pim_msdp_pkt_sa_push_to_one_peer(pim, mp); + } + } +} + +static int pim_msdp_pkt_sa_fill_hdr(struct pim_instance *pim, int local_cnt, + struct in_addr rp) +{ + int curr_tlv_ecnt; + + stream_reset(pim->msdp.work_obuf); + curr_tlv_ecnt = local_cnt > PIM_MSDP_SA_MAX_ENTRY_CNT + ? PIM_MSDP_SA_MAX_ENTRY_CNT + : local_cnt; + local_cnt -= curr_tlv_ecnt; + stream_putc(pim->msdp.work_obuf, PIM_MSDP_V4_SOURCE_ACTIVE); + stream_putw(pim->msdp.work_obuf, + PIM_MSDP_SA_ENTRY_CNT2SIZE(curr_tlv_ecnt)); + stream_putc(pim->msdp.work_obuf, curr_tlv_ecnt); + stream_put_ipv4(pim->msdp.work_obuf, rp.s_addr); + + return local_cnt; +} + +static void pim_msdp_pkt_sa_fill_one(struct pim_msdp_sa *sa) +{ + stream_put3(sa->pim->msdp.work_obuf, 0 /* reserved */); + stream_putc(sa->pim->msdp.work_obuf, 32 /* sprefix len */); + stream_put_ipv4(sa->pim->msdp.work_obuf, sa->sg.grp.s_addr); + stream_put_ipv4(sa->pim->msdp.work_obuf, sa->sg.src.s_addr); +} + +static void pim_msdp_pkt_sa_gen(struct pim_instance *pim, + struct pim_msdp_peer *mp) +{ + struct listnode *sanode; + struct pim_msdp_sa *sa; + struct rp_info *rp_info; + struct prefix group_all; + struct in_addr rp; + int sa_count; + int local_cnt = pim->msdp.local_cnt; + + sa_count = 0; + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug(" sa gen %d", local_cnt); + } + + rp = pim->msdp.originator_id; + if (pim_get_all_mcast_group(&group_all)) { + rp_info = pim_rp_find_match_group(pim, &group_all); + if (rp_info) { + rp = rp_info->rp.rpf_addr; + } + } + + local_cnt = pim_msdp_pkt_sa_fill_hdr(pim, local_cnt, rp); + + for (ALL_LIST_ELEMENTS_RO(pim->msdp.sa_list, sanode, sa)) { + if (!(sa->flags & PIM_MSDP_SAF_LOCAL)) { + /* current implementation of MSDP is for anycast i.e. + * full mesh. so + * no re-forwarding of SAs that we learnt from other + * peers */ + continue; + } + /* add sa into scratch pad */ + pim_msdp_pkt_sa_fill_one(sa); + ++sa_count; + if (sa_count >= PIM_MSDP_SA_MAX_ENTRY_CNT) { + pim_msdp_pkt_sa_push(pim, mp); + /* reset headers */ + sa_count = 0; + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug(" sa gen for remainder %d", + local_cnt); + } + local_cnt = pim_msdp_pkt_sa_fill_hdr( + pim, local_cnt, rp); + } + } + + if (sa_count) { + pim_msdp_pkt_sa_push(pim, mp); + } + return; +} + +static void pim_msdp_pkt_sa_tx_done(struct pim_instance *pim) +{ + struct listnode *mpnode; + struct pim_msdp_peer *mp; + + /* if SA were sent to the peers we restart ka timer and avoid + * unnecessary ka noise */ + for (ALL_LIST_ELEMENTS_RO(pim->msdp.peer_list, mpnode, mp)) { + if (mp->flags & PIM_MSDP_PEERF_SA_JUST_SENT) { + mp->flags &= ~PIM_MSDP_PEERF_SA_JUST_SENT; + pim_msdp_peer_pkt_txed(mp); + } + } +} + +void pim_msdp_pkt_sa_tx(struct pim_instance *pim) +{ + pim_msdp_pkt_sa_gen(pim, NULL /* mp */); + pim_msdp_pkt_sa_tx_done(pim); +} + +void pim_msdp_pkt_sa_tx_one(struct pim_msdp_sa *sa) +{ + pim_msdp_pkt_sa_fill_hdr(sa->pim, 1 /* cnt */, sa->rp); + pim_msdp_pkt_sa_fill_one(sa); + pim_msdp_pkt_sa_push(sa->pim, NULL); + pim_msdp_pkt_sa_tx_done(sa->pim); +} + +/* when a connection is first established we push all SAs immediately */ +void pim_msdp_pkt_sa_tx_to_one_peer(struct pim_msdp_peer *mp) +{ + pim_msdp_pkt_sa_gen(mp->pim, mp); + pim_msdp_pkt_sa_tx_done(mp->pim); +} + +void pim_msdp_pkt_sa_tx_one_to_one_peer(struct pim_msdp_peer *mp, + struct in_addr rp, pim_sgaddr sg) +{ + struct pim_msdp_sa sa; + + /* Fills the SA header. */ + pim_msdp_pkt_sa_fill_hdr(mp->pim, 1, rp); + + /* Fills the message contents. */ + sa.pim = mp->pim; + sa.sg = sg; + pim_msdp_pkt_sa_fill_one(&sa); + + /* Pushes the message. */ + pim_msdp_pkt_sa_push(sa.pim, mp); + pim_msdp_pkt_sa_tx_done(sa.pim); +} + +static void pim_msdp_pkt_rxed_with_fatal_error(struct pim_msdp_peer *mp) +{ + pim_msdp_peer_reset_tcp_conn(mp, "invalid-pkt-rx"); +} + +static void pim_msdp_pkt_ka_rx(struct pim_msdp_peer *mp, int len) +{ + mp->ka_rx_cnt++; + if (len != PIM_MSDP_KA_TLV_MAX_SIZE) { + pim_msdp_pkt_rxed_with_fatal_error(mp); + return; + } + pim_msdp_peer_pkt_rxed(mp); +} + +static void pim_msdp_pkt_sa_rx_one(struct pim_msdp_peer *mp, struct in_addr rp) +{ + int prefix_len; + pim_sgaddr sg; + struct listnode *peer_node; + struct pim_msdp_peer *peer; + + /* just throw away the three reserved bytes */ + stream_get3(mp->ibuf); + prefix_len = stream_getc(mp->ibuf); + + memset(&sg, 0, sizeof(sg)); + sg.grp.s_addr = stream_get_ipv4(mp->ibuf); + sg.src.s_addr = stream_get_ipv4(mp->ibuf); + + if (prefix_len != IPV4_MAX_BITLEN) { + /* ignore SA update if the prefix length is not 32 */ + flog_err(EC_PIM_MSDP_PACKET, + "rxed sa update with invalid prefix length %d", + prefix_len); + return; + } + if (PIM_DEBUG_MSDP_PACKETS) { + zlog_debug(" sg %pSG", &sg); + } + pim_msdp_sa_ref(mp->pim, mp, &sg, rp); + + /* Forwards the SA to the peers that are not in the RPF to the RP nor in + * the same mesh group as the peer from which we received the message. + * If the message group is not set, i.e. "default", then we assume that + * the message must be forwarded.*/ + for (ALL_LIST_ELEMENTS_RO(mp->pim->msdp.peer_list, peer_node, peer)) { + /* Not a RPF peer, so skip it. */ + if (pim_msdp_peer_rpf_check(peer, rp)) + continue; + /* Don't forward inside the meshed group. */ + if ((mp->flags & PIM_MSDP_PEERF_IN_GROUP) + && strcmp(mp->mesh_group_name, peer->mesh_group_name) == 0) + continue; + + pim_msdp_pkt_sa_tx_one_to_one_peer(peer, rp, sg); + } +} + +static void pim_msdp_pkt_sa_rx(struct pim_msdp_peer *mp, int len) +{ + int entry_cnt; + int i; + struct in_addr rp; /* Last RP address associated with this SA */ + + mp->sa_rx_cnt++; + + if (len < PIM_MSDP_SA_TLV_MIN_SIZE) { + pim_msdp_pkt_rxed_with_fatal_error(mp); + return; + } + + entry_cnt = stream_getc(mp->ibuf); + /* some vendors include the actual multicast data in the tlv (at the + * end). we will ignore such data. in the future we may consider pushing + * it down the RPT + */ + if (len < PIM_MSDP_SA_ENTRY_CNT2SIZE(entry_cnt)) { + pim_msdp_pkt_rxed_with_fatal_error(mp); + return; + } + rp.s_addr = stream_get_ipv4(mp->ibuf); + + if (PIM_DEBUG_MSDP_PACKETS) { + char rp_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", rp, rp_str, sizeof(rp_str)); + zlog_debug(" entry_cnt %d rp %s", entry_cnt, rp_str); + } + + pim_msdp_peer_pkt_rxed(mp); + + if (!pim_msdp_peer_rpf_check(mp, rp)) { + /* if peer-RPF check fails don't process the packet any further + */ + if (PIM_DEBUG_MSDP_PACKETS) { + zlog_debug(" peer RPF check failed"); + } + return; + } + + /* update SA cache */ + for (i = 0; i < entry_cnt; ++i) { + pim_msdp_pkt_sa_rx_one(mp, rp); + } +} + +static void pim_msdp_pkt_rx(struct pim_msdp_peer *mp) +{ + enum pim_msdp_tlv type; + int len; + + /* re-read type and len */ + type = stream_getc_from(mp->ibuf, 0); + len = stream_getw_from(mp->ibuf, 1); + if (len < PIM_MSDP_HEADER_SIZE) { + pim_msdp_pkt_rxed_with_fatal_error(mp); + return; + } + + if (len > PIM_MSDP_SA_TLV_MAX_SIZE) { + /* if tlv size if greater than max just ignore the tlv */ + return; + } + + if (PIM_DEBUG_MSDP_PACKETS) { + pim_msdp_pkt_dump(mp, type, len, true /*rx*/, NULL /*s*/); + } + + switch (type) { + case PIM_MSDP_KEEPALIVE: + pim_msdp_pkt_ka_rx(mp, len); + break; + case PIM_MSDP_V4_SOURCE_ACTIVE: + mp->sa_rx_cnt++; + pim_msdp_pkt_sa_rx(mp, len); + break; + case PIM_MSDP_V4_SOURCE_ACTIVE_REQUEST: + case PIM_MSDP_V4_SOURCE_ACTIVE_RESPONSE: + case PIM_MSDP_RESERVED: + case PIM_MSDP_TRACEROUTE_PROGRESS: + case PIM_MSDP_TRACEROUTE_REPLY: + mp->unk_rx_cnt++; + break; + } +} + +/* pim msdp read utility function. */ +static int pim_msdp_read_packet(struct pim_msdp_peer *mp) +{ + int nbytes; + int readsize; + int old_endp; + int new_endp; + + old_endp = stream_get_endp(mp->ibuf); + readsize = mp->packet_size - old_endp; + if (!readsize) { + return 0; + } + + /* Read packet from fd */ + nbytes = stream_read_try(mp->ibuf, mp->fd, readsize); + new_endp = stream_get_endp(mp->ibuf); + if (nbytes < 0) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s read failed %d", mp->key_str, + nbytes); + } + if (nbytes == -2) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug( + "MSDP peer %s pim_msdp_read io retry old_end: %d new_end: %d", + mp->key_str, old_endp, new_endp); + } + /* transient error retry */ + return -1; + } + pim_msdp_pkt_rxed_with_fatal_error(mp); + return -1; + } + + if (!nbytes) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s read failed %d", mp->key_str, + nbytes); + } + pim_msdp_peer_reset_tcp_conn(mp, "peer-down"); + return -1; + } + + /* We read partial packet. */ + if (stream_get_endp(mp->ibuf) != mp->packet_size) { + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug( + "MSDP peer %s read partial len %d old_endp %d new_endp %d", + mp->key_str, mp->packet_size, old_endp, + new_endp); + } + return -1; + } + + return 0; +} + +void pim_msdp_read(struct event *thread) +{ + struct pim_msdp_peer *mp; + int rc; + uint32_t len; + + mp = EVENT_ARG(thread); + mp->t_read = NULL; + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s pim_msdp_read", mp->key_str); + } + + if (mp->fd < 0) { + return; + } + + /* check if TCP connection is established */ + if (mp->state != PIM_MSDP_ESTABLISHED) { + pim_msdp_connect_check(mp); + return; + } + + PIM_MSDP_PEER_READ_ON(mp); + + if (!mp->packet_size) { + mp->packet_size = PIM_MSDP_HEADER_SIZE; + } + + if (stream_get_endp(mp->ibuf) < PIM_MSDP_HEADER_SIZE) { + /* start by reading the TLV header */ + rc = pim_msdp_read_packet(mp); + if (rc < 0) + return; + + /* Find TLV type and len */ + stream_getc(mp->ibuf); + len = stream_getw(mp->ibuf); + if (len < PIM_MSDP_HEADER_SIZE) { + pim_msdp_pkt_rxed_with_fatal_error(mp); + return; + } + + /* + * Handle messages with longer than expected TLV size: resize + * the stream to handle reading the whole message. + * + * RFC 3618 Section 12. 'Packet Formats': + * > ... If an implementation receives a TLV whose length + * > exceeds the maximum TLV length specified below, the TLV + * > SHOULD be accepted. Any additional data, including possible + * > next TLV's in the same message, SHOULD be ignored, and the + * > MSDP session should not be reset. ... + */ + if (len > PIM_MSDP_SA_TLV_MAX_SIZE) { + /* Check if the current buffer is big enough. */ + if (mp->ibuf->size < len) { + if (PIM_DEBUG_MSDP_PACKETS) + zlog_debug( + "MSDP peer %s sent TLV with unexpected large length (%d bytes)", + mp->key_str, len); + + stream_resize_inplace(&mp->ibuf, len); + } + } + + /* read complete TLV */ + mp->packet_size = len; + } + + rc = pim_msdp_read_packet(mp); + if (rc < 0) + return; + + pim_msdp_pkt_rx(mp); + + /* reset input buffers and get ready for the next packet */ + mp->packet_size = 0; + stream_reset(mp->ibuf); +} diff --git a/pimd/pim_msdp_packet.h b/pimd/pim_msdp_packet.h new file mode 100644 index 0000000..1584a24 --- /dev/null +++ b/pimd/pim_msdp_packet.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP packet helpers + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +#ifndef PIM_MSDP_PACKET_H +#define PIM_MSDP_PACKET_H + +/* type and length of a single tlv can be consider packet header */ +#define PIM_MSDP_HEADER_SIZE 3 + +/* Keepalive TLV + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| 4 | 3 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +#define PIM_MSDP_KA_TLV_MAX_SIZE PIM_MSDP_HEADER_SIZE + +/* Source-Active TLV (x=8, y=12xEntryCount) + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| 1 | x + y | Entry Count | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RP Address | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reserved | Sprefix Len | \ ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \ +| Group Address | ) z ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ / +| Source Address | / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +#define PIM_MSDP_SA_TLV_MAX_SIZE 9192 +#define PIM_MSDP_SA_X_SIZE 8 +#define PIM_MSDP_SA_ONE_ENTRY_SIZE 12 +#define PIM_MSDP_SA_Y_SIZE(entry_cnt) (PIM_MSDP_SA_ONE_ENTRY_SIZE * entry_cnt) +#define PIM_MSDP_SA_ENTRY_CNT2SIZE(entry_cnt) \ + (PIM_MSDP_SA_X_SIZE + PIM_MSDP_SA_Y_SIZE(entry_cnt)) +/* SA TLV has to have atleast only one entry in it so x=8 + y=12 */ +#define PIM_MSDP_SA_TLV_MIN_SIZE PIM_MSDP_SA_ENTRY_CNT2SIZE(1) +/* XXX: theoretically we can fix a max of 255 but that may result in packet + * fragmentation */ +#define PIM_MSDP_SA_MAX_ENTRY_CNT 120 + +#define PIM_MSDP_MAX_PACKET_SIZE \ + MAX(PIM_MSDP_SA_TLV_MAX_SIZE, PIM_MSDP_KA_TLV_MAX_SIZE) + +#define PIM_MSDP_PKT_TYPE_STRLEN 16 + +void pim_msdp_pkt_ka_tx(struct pim_msdp_peer *mp); +void pim_msdp_read(struct event *thread); +void pim_msdp_pkt_sa_tx(struct pim_instance *pim); +void pim_msdp_pkt_sa_tx_one(struct pim_msdp_sa *sa); +void pim_msdp_pkt_sa_tx_to_one_peer(struct pim_msdp_peer *mp); +void pim_msdp_pkt_sa_tx_one_to_one_peer(struct pim_msdp_peer *mp, + struct in_addr rp, pim_sgaddr sg); + +#endif diff --git a/pimd/pim_msdp_socket.c b/pimd/pim_msdp_socket.c new file mode 100644 index 0000000..fe8d5e9 --- /dev/null +++ b/pimd/pim_msdp_socket.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP socket management + * Copyright (C) 2016 Cumulus Networks, Inc. + */ + +#include + +#include +#include +#include +#include "frrevent.h" +#include +#include +#include +#include + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_sock.h" +#include "pim_errors.h" + +#include "pim_msdp.h" +#include "pim_msdp_socket.h" + +#include "sockopt.h" + +/* increase socket send buffer size */ +static void pim_msdp_update_sock_send_buffer_size(int fd) +{ + int size = PIM_MSDP_SOCKET_SNDBUF_SIZE; + int optval; + socklen_t optlen = sizeof(optval); + + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &optval, &optlen) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "getsockopt of SO_SNDBUF failed %s", + safe_strerror(errno)); + return; + } + + if (optval < size) { + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) + < 0) { + flog_err_sys(EC_LIB_SOCKET, + "Couldn't increase send buffer: %s", + safe_strerror(errno)); + } + } +} + +/* passive peer socket accept */ +static void pim_msdp_sock_accept(struct event *thread) +{ + union sockunion su; + struct pim_instance *pim = EVENT_ARG(thread); + int accept_sock; + int msdp_sock; + struct pim_msdp_peer *mp; + + sockunion_init(&su); + + /* re-register accept thread */ + accept_sock = EVENT_FD(thread); + if (accept_sock < 0) { + flog_err(EC_LIB_DEVELOPMENT, "accept_sock is negative value %d", + accept_sock); + return; + } + pim->msdp.listener.thread = NULL; + event_add_read(router->master, pim_msdp_sock_accept, pim, accept_sock, + &pim->msdp.listener.thread); + + /* accept client connection. */ + msdp_sock = sockunion_accept(accept_sock, &su); + if (msdp_sock < 0) { + flog_err_sys(EC_LIB_SOCKET, "pim_msdp_sock_accept failed (%s)", + safe_strerror(errno)); + return; + } + + /* see if have peer config for this */ + mp = pim_msdp_peer_find(pim, su.sin.sin_addr); + if (!mp || !PIM_MSDP_PEER_IS_LISTENER(mp)) { + ++pim->msdp.rejected_accepts; + if (PIM_DEBUG_MSDP_EVENTS) { + flog_err(EC_PIM_MSDP_PACKET, + "msdp peer connection refused from %pSU", &su); + } + close(msdp_sock); + return; + } + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s accept success%s", mp->key_str, + mp->fd >= 0 ? "(dup)" : ""); + } + + /* if we have an existing connection we need to kill that one + * with this one */ + if (mp->fd >= 0) { + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_notice( + "msdp peer new connection from %pSU stop old connection", + &su); + } + pim_msdp_peer_stop_tcp_conn(mp, true /* chg_state */); + } + mp->fd = msdp_sock; + set_nonblocking(mp->fd); + pim_msdp_update_sock_send_buffer_size(mp->fd); + pim_msdp_peer_established(mp); +} + +/* global listener for the MSDP well know TCP port */ +int pim_msdp_sock_listen(struct pim_instance *pim) +{ + int sock; + int socklen; + struct sockaddr_in sin; + int rc; + struct pim_msdp_listener *listener = &pim->msdp.listener; + + if (pim->msdp.flags & PIM_MSDPF_LISTENER) { + /* listener already setup */ + return 0; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + flog_err_sys(EC_LIB_SOCKET, "socket: %s", safe_strerror(errno)); + return sock; + } + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(PIM_MSDP_TCP_PORT); + socklen = sizeof(struct sockaddr_in); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin.sin_len = socklen; +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + + if (pim->vrf->vrf_id != VRF_DEFAULT) { + struct interface *ifp = + if_lookup_by_name(pim->vrf->name, pim->vrf->vrf_id); + if (!ifp) { + flog_err(EC_LIB_INTERFACE, + "%s: Unable to lookup vrf interface: %s", + __func__, pim->vrf->name); + close(sock); + return -1; + } + if (pim_socket_bind(sock, ifp)) { + flog_err_sys(EC_LIB_SOCKET, + "%s: Unable to bind to socket: %s", + __func__, safe_strerror(errno)); + close(sock); + return -1; + } + } + + frr_with_privs(&pimd_privs) { + /* bind to well known TCP port */ + rc = bind(sock, (struct sockaddr *)&sin, socklen); + } + + if (rc < 0) { + flog_err_sys(EC_LIB_SOCKET, + "pim_msdp_socket bind to port %d: %s", + ntohs(sin.sin_port), safe_strerror(errno)); + close(sock); + return rc; + } + + rc = listen(sock, 3 /* backlog */); + if (rc < 0) { + flog_err_sys(EC_LIB_SOCKET, "pim_msdp_socket listen: %s", + safe_strerror(errno)); + close(sock); + return rc; + } + + /* Set socket DSCP byte */ + if (setsockopt_ipv4_tos(sock, IPTOS_PREC_INTERNETCONTROL)) { + zlog_warn("can't set sockopt IP_TOS to MSDP socket %d: %s", + sock, safe_strerror(errno)); + } + + /* add accept thread */ + listener->fd = sock; + memcpy(&listener->su, &sin, socklen); + event_add_read(pim->msdp.master, pim_msdp_sock_accept, pim, sock, + &listener->thread); + + pim->msdp.flags |= PIM_MSDPF_LISTENER; + return 0; +} + +/* active peer socket setup */ +int pim_msdp_sock_connect(struct pim_msdp_peer *mp) +{ + int rc; + + if (PIM_DEBUG_MSDP_INTERNAL) { + zlog_debug("MSDP peer %s attempt connect%s", mp->key_str, + mp->fd < 0 ? "" : "(dup)"); + } + + /* if we have an existing connection we need to kill that one + * with this one */ + if (mp->fd >= 0) { + if (PIM_DEBUG_MSDP_EVENTS) { + zlog_notice( + "msdp duplicate connect to %s nuke old connection", + mp->key_str); + } + pim_msdp_peer_stop_tcp_conn(mp, false /* chg_state */); + } + + /* Make socket for the peer. */ + mp->fd = sockunion_socket(&mp->su_peer); + if (mp->fd < 0) { + flog_err_sys(EC_LIB_SOCKET, + "pim_msdp_socket socket failure: %s", + safe_strerror(errno)); + return -1; + } + + if (mp->pim->vrf->vrf_id != VRF_DEFAULT) { + struct interface *ifp = if_lookup_by_name(mp->pim->vrf->name, + mp->pim->vrf->vrf_id); + if (!ifp) { + flog_err(EC_LIB_INTERFACE, + "%s: Unable to lookup vrf interface: %s", + __func__, mp->pim->vrf->name); + return -1; + } + if (pim_socket_bind(mp->fd, ifp)) { + flog_err_sys(EC_LIB_SOCKET, + "%s: Unable to bind to socket: %s", + __func__, safe_strerror(errno)); + close(mp->fd); + mp->fd = -1; + return -1; + } + } + + set_nonblocking(mp->fd); + + /* Set socket send buffer size */ + pim_msdp_update_sock_send_buffer_size(mp->fd); + sockopt_reuseaddr(mp->fd); + sockopt_reuseport(mp->fd); + + /* source bind */ + rc = sockunion_bind(mp->fd, &mp->su_local, 0, &mp->su_local); + if (rc < 0) { + flog_err_sys(EC_LIB_SOCKET, + "pim_msdp_socket connect bind failure: %s", + safe_strerror(errno)); + close(mp->fd); + mp->fd = -1; + return rc; + } + + /* Set socket DSCP byte */ + if (setsockopt_ipv4_tos(mp->fd, IPTOS_PREC_INTERNETCONTROL)) { + zlog_warn("can't set sockopt IP_TOS to MSDP socket %d: %s", + mp->fd, safe_strerror(errno)); + } + + /* Connect to the remote mp. */ + return (sockunion_connect(mp->fd, &mp->su_peer, + htons(PIM_MSDP_TCP_PORT), 0)); +} diff --git a/pimd/pim_msdp_socket.h b/pimd/pim_msdp_socket.h new file mode 100644 index 0000000..ae31664 --- /dev/null +++ b/pimd/pim_msdp_socket.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP MSDP socket management for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + */ +#ifndef PIM_MSDP_SOCKET_H +#define PIM_MSDP_SOCKET_H + +int pim_msdp_sock_listen(struct pim_instance *pim); +int pim_msdp_sock_connect(struct pim_msdp_peer *mp); +#endif diff --git a/pimd/pim_msg.c b/pimd/pim_msg.c new file mode 100644 index 0000000..7a86e6b --- /dev/null +++ b/pimd/pim_msg.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_vty.h" +#include "pim_pim.h" +#include "pim_msg.h" +#include "pim_util.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_rp.h" +#include "pim_rpf.h" +#include "pim_register.h" +#include "pim_jp_agg.h" +#include "pim_oil.h" + +void pim_msg_build_header(pim_addr src, pim_addr dst, uint8_t *pim_msg, + size_t pim_msg_size, uint8_t pim_msg_type, + bool no_fwd) +{ + struct pim_msg_header *header = (struct pim_msg_header *)pim_msg; + struct iovec iov[2], *iovp = iov; + + /* + * The checksum for Registers is done only on the first 8 bytes of the + * packet, including the PIM header and the next 4 bytes, excluding the + * data packet portion + * + * for IPv6, the pseudoheader upper-level protocol length is also + * truncated, so let's just set it here before everything else. + */ + if (pim_msg_type == PIM_MSG_TYPE_REGISTER) + pim_msg_size = PIM_MSG_REGISTER_LEN; + +#if PIM_IPV == 6 + struct ipv6_ph phdr = { + .src = src, + .dst = dst, + .ulpl = htonl(pim_msg_size), + .next_hdr = IPPROTO_PIM, + }; + + iovp->iov_base = &phdr; + iovp->iov_len = sizeof(phdr); + iovp++; +#endif + + /* + * Write header + */ + header->ver = PIM_PROTO_VERSION; + header->type = pim_msg_type; + header->Nbit = no_fwd; + header->reserved = 0; + + header->checksum = 0; + iovp->iov_base = header; + iovp->iov_len = pim_msg_size; + iovp++; + + header->checksum = in_cksumv(iov, iovp - iov); +} + +uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf, struct in_addr addr) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ + buf[1] = '\0'; /* native encoding */ + memcpy(buf + 2, &addr, sizeof(struct in_addr)); + + return buf + PIM_ENCODED_IPV4_UCAST_SIZE; +} + +uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf, struct in_addr addr) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ + buf[1] = '\0'; /* native encoding */ + buf[2] = '\0'; /* reserved */ + buf[3] = 32; /* mask len */ + memcpy(buf + 4, &addr, sizeof(struct in_addr)); + + return buf + PIM_ENCODED_IPV4_GROUP_SIZE; +} + +uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf, struct in_addr addr, + uint8_t bits) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */ + buf[1] = '\0'; /* native encoding */ + buf[2] = bits; + buf[3] = 32; /* mask len */ + memcpy(buf + 4, &addr, sizeof(struct in_addr)); + + return buf + PIM_ENCODED_IPV4_SOURCE_SIZE; +} + +uint8_t *pim_msg_addr_encode_ipv6_source(uint8_t *buf, struct in6_addr addr, + uint8_t bits) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV6; /* addr family */ + buf[1] = '\0'; /* native encoding */ + buf[2] = bits; + buf[3] = 128; /* mask len */ + buf += 4; + + memcpy(buf, &addr, sizeof(addr)); + buf += sizeof(addr); + + return buf; +} + +uint8_t *pim_msg_addr_encode_ipv6_ucast(uint8_t *buf, struct in6_addr addr) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV6; /* addr family */ + buf[1] = '\0'; /* native encoding */ + buf += 2; + + memcpy(buf, &addr, sizeof(addr)); + buf += sizeof(addr); + + return buf; +} + +uint8_t *pim_msg_addr_encode_ipv6_group(uint8_t *buf, struct in6_addr addr) +{ + buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV6; /* addr family */ + buf[1] = '\0'; /* native encoding */ + buf[2] = '\0'; /* reserved */ + buf[3] = 128; /* mask len */ + buf += 4; + + memcpy(buf, &addr, sizeof(addr)); + buf += sizeof(addr); + + return buf; +} + +#if PIM_IPV == 4 +#define pim_msg_addr_encode(what) pim_msg_addr_encode_ipv4_##what +#else +#define pim_msg_addr_encode(what) pim_msg_addr_encode_ipv6_##what +#endif + +uint8_t *pim_msg_addr_encode_ucast(uint8_t *buf, pim_addr addr) +{ + return pim_msg_addr_encode(ucast)(buf, addr); +} + +uint8_t *pim_msg_addr_encode_group(uint8_t *buf, pim_addr addr) +{ + return pim_msg_addr_encode(group)(buf, addr); +} + +uint8_t *pim_msg_addr_encode_source(uint8_t *buf, pim_addr addr, uint8_t bits) +{ + return pim_msg_addr_encode(source)(buf, addr, bits); +} + +/* + * For the given 'struct pim_jp_sources' list + * determine the size_t it would take up. + */ +size_t pim_msg_get_jp_group_size(struct list *sources) +{ + struct pim_jp_sources *js; + size_t size = 0; + + if (!sources) + return 0; + + size += sizeof(pim_encoded_group); + size += 4; // Joined sources (2) + Pruned Sources (2) + + size += sizeof(pim_encoded_source) * sources->count; + + js = listgetdata(listhead(sources)); + if (!js || !pim_addr_is_any(js->up->sg.src) || !js->is_join) + return size; + + struct pim_upstream *child, *up; + struct listnode *up_node; + + up = js->up; + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: Considering (%s) children for (S,G,rpt) prune", + __func__, up->sg_str); + + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { + /* + * PIM VXLAN is weird + * It auto creates the S,G and populates a bunch + * of flags that make it look like a SPT prune should + * be sent. But this regularly scheduled join + * for the *,G in the VXLAN setup can happen at + * scheduled times *before* the null register + * is received by the RP to cause it to initiate + * the S,G joins toward the source. Let's just + * assume that if this is a SRC VXLAN ORIG route + * and no actual ifchannels( joins ) have been + * created then do not send the embedded prune + * Why you may ask? Well if the prune is S,G + * RPT Prune is received *before* the join + * from the RP( if it flows to this routers + * upstream interface ) then we'll just wisely + * create a mroute with an empty oil on + * the upstream intermediate router preventing + * packets from flowing to the RP + */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(child->flags) && + listcount(child->ifchannels) == 0) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: %s Vxlan originated S,G route with no ifchannels, not adding prune to compound message", + __func__, child->sg_str); + } else if (!PIM_UPSTREAM_FLAG_TEST_USE_RPT(child->flags)) { + /* If we are using SPT and the SPT and RPT IIFs + * are different we can prune the source off + * of the RPT. + * If RPF_interface(S) is not resolved hold + * decision to prune as SPT may end up on the + * same IIF as RPF_interface(RP). + */ + if (child->rpf.source_nexthop.interface && + !pim_rpf_is_same(&up->rpf, &child->rpf)) { + size += sizeof(pim_encoded_source); + PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE( + child->flags); + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: SPT Bit and RPF'(%s) != RPF'(S,G): Add Prune (%s,rpt) to compound message", + __func__, up->sg_str, + child->sg_str); + } else if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: SPT Bit and RPF'(%s) == RPF'(S,G): Not adding Prune for (%s,rpt)", + __func__, up->sg_str, child->sg_str); + } else if (pim_upstream_empty_inherited_olist(child)) { + /* S is supposed to be forwarded along the RPT + * but it's inherited OIL is empty. So just + * prune it off. + */ + size += sizeof(pim_encoded_source); + PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE(child->flags); + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: inherited_olist(%s,rpt) is NULL, Add Prune to compound message", + __func__, child->sg_str); + } else if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: Do not add Prune %s to compound message %s", + __func__, child->sg_str, up->sg_str); + } + + return size; +} + +size_t pim_msg_build_jp_groups(struct pim_jp_groups *grp, + struct pim_jp_agg_group *sgs, size_t size) +{ + struct listnode *node, *nnode; + struct pim_jp_sources *source; + struct pim_upstream *up = NULL; + pim_addr stosend; + uint8_t bits; + uint8_t tgroups = 0; + + memset(grp, 0, size); + pim_msg_addr_encode_group((uint8_t *)&grp->g, sgs->group); + + for (ALL_LIST_ELEMENTS(sgs->sources, node, nnode, source)) { + /* number of joined/pruned sources */ + if (source->is_join) + grp->joins++; + else + grp->prunes++; + + if (pim_addr_is_any(source->up->sg.src)) { + struct pim_instance *pim = source->up->channel_oil->pim; + struct pim_rpf *rpf = pim_rp_g(pim, source->up->sg.grp); + bits = PIM_ENCODE_SPARSE_BIT | PIM_ENCODE_WC_BIT + | PIM_ENCODE_RPT_BIT; + stosend = rpf->rpf_addr; + /* Only Send SGRpt in case of *,G Join */ + if (source->is_join) + up = source->up; + } else { + bits = PIM_ENCODE_SPARSE_BIT; + stosend = source->up->sg.src; + } + + pim_msg_addr_encode_source((uint8_t *)&grp->s[tgroups], stosend, + bits); + tgroups++; + } + + if (up) { + struct pim_upstream *child; + + for (ALL_LIST_ELEMENTS(up->sources, node, nnode, child)) { + if (PIM_UPSTREAM_FLAG_TEST_SEND_SG_RPT_PRUNE( + child->flags)) { + pim_msg_addr_encode_source( + (uint8_t *)&grp->s[tgroups], + child->sg.src, + PIM_ENCODE_SPARSE_BIT | + PIM_ENCODE_RPT_BIT); + tgroups++; + PIM_UPSTREAM_FLAG_UNSET_SEND_SG_RPT_PRUNE( + child->flags); + grp->prunes++; + } + } + } + + grp->joins = htons(grp->joins); + grp->prunes = htons(grp->prunes); + + return size; +} diff --git a/pimd/pim_msg.h b/pimd/pim_msg.h new file mode 100644 index 0000000..56923b7 --- /dev/null +++ b/pimd/pim_msg.h @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_MSG_H +#define PIM_MSG_H + +#include +#if PIM_IPV == 6 +#include +#endif + +#include "pim_jp_agg.h" + +#define PIM_HDR_LEN sizeof(struct pim_msg_header) +/* + Number Description + ---------- ------------------ + 0 Reserved + 1 IP (IP version 4) + 2 IP6 (IP version 6) + + From: + http://www.iana.org/assignments/address-family-numbers +*/ +enum pim_msg_address_family { + PIM_MSG_ADDRESS_FAMILY_RESERVED, + PIM_MSG_ADDRESS_FAMILY_IPV4, + PIM_MSG_ADDRESS_FAMILY_IPV6, +}; + +/* + * pim_msg_hdr + * ========================= + * PIM Header definition as per RFC 5059. N bit introduced to indicate + * do-not-forward option in PIM Boot strap Message. + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |PIM Ver| Type |N| Reserved | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +struct pim_msg_header { +#if (BYTE_ORDER == LITTLE_ENDIAN) + uint8_t type : 4; + uint8_t ver : 4; + uint8_t reserved : 7; + uint8_t Nbit : 1; /* No Fwd Bit */ +#elif (BYTE_ORDER == BIG_ENDIAN) + uint8_t ver : 4; + uint8_t type : 4; + uint8_t Nbit : 1; /* No Fwd Bit */ + uint8_t reserved : 7; +#else +#error"Please set byte order" +#endif + uint16_t checksum; +} __attribute__((packed)); + +struct pim_encoded_ipv4_unicast { + uint8_t family; + uint8_t reserved; + struct in_addr addr; +} __attribute__((packed)); + +struct pim_encoded_ipv6_unicast { + uint8_t family; + uint8_t reserved; + struct in6_addr addr; +} __attribute__((packed)); + +/* + * Encoded Group format. RFC 4601 Sec 4.9.1 + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Addr Family | Encoding Type |B| Reserved |Z| Mask Len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Group multicast Address + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... + */ +struct pim_encoded_group_ipv4 { + uint8_t family; + uint8_t ne; +#if (BYTE_ORDER == LITTLE_ENDIAN) + uint8_t sz : 1; /* scope zone bit */ + uint8_t reserved : 6; /* Reserved */ + uint8_t bidir : 1; /* Bidir bit */ +#elif (BYTE_ORDER == BIG_ENDIAN) + uint8_t bidir : 1; /* Bidir bit */ + uint8_t reserved : 6; /* Reserved */ + uint8_t sz : 1; /* scope zone bit */ +#else +#error"Please set byte order" +#endif + uint8_t mask; + struct in_addr addr; +} __attribute__((packed)); + +struct pim_encoded_group_ipv6 { + uint8_t family; + uint8_t ne; +#if (BYTE_ORDER == LITTLE_ENDIAN) + uint8_t sz : 1; /* scope zone bit */ + uint8_t reserved : 6; /* Reserved */ + uint8_t bidir : 1; /* Bidir bit */ +#elif (BYTE_ORDER == BIG_ENDIAN) + uint8_t bidir : 1; /* Bidir bit */ + uint8_t reserved : 6; /* Reserved */ + uint8_t sz : 1; /* scope zone bit */ +#else +#error "Please set byte order" +#endif + uint8_t mask; + struct in6_addr addr; +} __attribute__((packed)); + +/* + * Encoded Source format. RFC 4601 Sec 4.9.1 + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Addr Family | Encoding Type | Rsrvd |S|W|R| Mask Len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Source Address + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... + */ +struct pim_encoded_source_ipv4 { + uint8_t family; + uint8_t ne; + uint8_t bits; + uint8_t mask; + struct in_addr addr; +} __attribute__((packed)); + +struct pim_encoded_source_ipv6 { + uint8_t family; + uint8_t ne; + uint8_t bits; + uint8_t mask; + struct in6_addr addr; +} __attribute__((packed)); + +/* clang-format off */ +#if PIM_IPV == 4 +typedef struct pim_encoded_ipv4_unicast pim_encoded_unicast; +typedef struct pim_encoded_group_ipv4 pim_encoded_group; +typedef struct pim_encoded_source_ipv4 pim_encoded_source; +typedef struct ip ipv_hdr; +#define IPV_SRC(ip_hdr) ((ip_hdr))->ip_src +#define IPV_DST(ip_hdr) ((ip_hdr))->ip_dst +#define IPV_LEN(ip_hdr) ((ip_hdr))->ip_len +#else +typedef struct pim_encoded_ipv6_unicast pim_encoded_unicast; +typedef struct pim_encoded_group_ipv6 pim_encoded_group; +typedef struct pim_encoded_source_ipv6 pim_encoded_source; +typedef struct ip6_hdr ipv_hdr; +#define IPV_SRC(ip_hdr) ((ip_hdr))->ip6_src +#define IPV_DST(ip_hdr) ((ip_hdr))->ip6_dst +#define IPV_LEN(ip_hdr) ((ip_hdr))->ip6_plen +#endif +/* clang-format on */ + +struct pim_jp_groups { + pim_encoded_group g; + uint16_t joins; + uint16_t prunes; + pim_encoded_source s[1]; +} __attribute__((packed)); + +struct pim_jp { + struct pim_msg_header header; + pim_encoded_unicast addr; + uint8_t reserved; + uint8_t num_groups; + uint16_t holdtime; + struct pim_jp_groups groups[1]; +} __attribute__((packed)); + +#if PIM_IPV == 4 +static inline pim_sgaddr pim_sgaddr_from_iphdr(const void *iphdr) +{ + const struct ip *ipv4_hdr = iphdr; + pim_sgaddr sg; + + sg.src = ipv4_hdr->ip_src; + sg.grp = ipv4_hdr->ip_dst; + + return sg; +} +#else +static inline pim_sgaddr pim_sgaddr_from_iphdr(const void *iphdr) +{ + const struct ip6_hdr *ipv6_hdr = iphdr; + pim_sgaddr sg; + + sg.src = ipv6_hdr->ip6_src; + sg.grp = ipv6_hdr->ip6_dst; + + return sg; +} +#endif + +void pim_msg_build_header(pim_addr src, pim_addr dst, uint8_t *pim_msg, + size_t pim_msg_size, uint8_t pim_msg_type, + bool no_fwd); +uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf, struct in_addr addr); +uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf, struct in_addr addr); + +#define PIM_ENCODE_SPARSE_BIT 0x04 +#define PIM_ENCODE_WC_BIT 0x02 +#define PIM_ENCODE_RPT_BIT 0x01 +uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf, struct in_addr addr, + uint8_t bits); + +uint8_t *pim_msg_addr_encode_ipv6_ucast(uint8_t *buf, struct in6_addr addr); +uint8_t *pim_msg_addr_encode_ipv6_group(uint8_t *buf, struct in6_addr addr); +uint8_t *pim_msg_addr_encode_ipv6_source(uint8_t *buf, struct in6_addr addr, + uint8_t bits); + +uint8_t *pim_msg_addr_encode_ucast(uint8_t *buf, pim_addr addr); +uint8_t *pim_msg_addr_encode_group(uint8_t *buf, pim_addr addr); +uint8_t *pim_msg_addr_encode_source(uint8_t *buf, pim_addr addr, uint8_t bits); + +size_t pim_msg_get_jp_group_size(struct list *sources); +size_t pim_msg_build_jp_groups(struct pim_jp_groups *grp, + struct pim_jp_agg_group *sgs, size_t size); +#endif /* PIM_MSG_H */ diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c new file mode 100644 index 0000000..339935f --- /dev/null +++ b/pimd/pim_nb.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 VmWare + * Sarita Patra + */ + +#include + +#include "northbound.h" +#include "libfrr.h" +#include "vrf.h" +#include "pimd/pim_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_pim_info = { + .name = "frr-pim", + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ecmp", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ecmp-rebalance", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_rebalance_modify, + } + }, + { + .xpath = "/frr-pim:pim/address-family/join-prune-interval", + .cbs = { + .modify = pim_address_family_join_prune_interval_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/keep-alive-timer", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_keep_alive_timer_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/rp-keep-alive-timer", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_keep_alive_timer_modify, + } + }, + { + .xpath = "/frr-pim:pim/address-family", + .cbs = { + .create = pim_address_family_create, + .destroy = pim_address_family_destroy, + } + }, + { + .xpath = "/frr-pim:pim/address-family/packets", + .cbs = { + .modify = pim_address_family_packets_modify, + } + }, + { + .xpath = "/frr-pim:pim/address-family/register-suppress-time", + .cbs = { + .modify = pim_address_family_register_suppress_time_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/send-v6-secondary", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover", + .cbs = { + .apply_finish = routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_apply_finish, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover/spt-action", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_action_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover/spt-infinity-prefix-list", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ssm-prefix-list", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ssm-pingd-source-ip", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/hold-time", + .cbs = { + .modify = pim_msdp_hold_time_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/keep-alive", + .cbs = { + .modify = pim_msdp_keep_alive_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/connection-retry", + .cbs = { + .modify = pim_msdp_connection_retry_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups", + .cbs = { + .create = pim_msdp_mesh_group_create, + .destroy = pim_msdp_mesh_group_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups/source", + .cbs = { + .modify = pim_msdp_mesh_group_source_modify, + .destroy = pim_msdp_mesh_group_source_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups/members", + .cbs = { + .create = pim_msdp_mesh_group_members_create, + .destroy = pim_msdp_mesh_group_members_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-peer", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-peer/source-ip", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_source_ip_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_destroy, + .apply_finish = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_apply_finish, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/peerlink-rif", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/reg-address", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/my-role", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_my_role_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/peer-state", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peer_state_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/register-accept-list", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family", + .cbs = { + .create = lib_interface_pim_address_family_create, + .destroy = lib_interface_pim_address_family_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/pim-enable", + .cbs = { + .modify = lib_interface_pim_address_family_pim_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/pim-passive-enable", + .cbs = { + .modify = lib_interface_pim_address_family_pim_passive_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/dr-priority", + .cbs = { + .modify = lib_interface_pim_address_family_dr_priority_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/hello-interval", + .cbs = { + .modify = lib_interface_pim_address_family_hello_interval_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/hello-holdtime", + .cbs = { + .modify = lib_interface_pim_address_family_hello_holdtime_modify, + .destroy = lib_interface_pim_address_family_hello_holdtime_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd", + .cbs = { + .create = lib_interface_pim_address_family_bfd_create, + .destroy = lib_interface_pim_address_family_bfd_destroy, + .apply_finish = lib_interface_pim_address_family_bfd_apply_finish, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd/min-rx-interval", + .cbs = { + .modify = lib_interface_pim_address_family_bfd_min_rx_interval_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd/min-tx-interval", + .cbs = { + .modify = lib_interface_pim_address_family_bfd_min_tx_interval_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd/detect_mult", + .cbs = { + .modify = lib_interface_pim_address_family_bfd_detect_mult_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd/profile", + .cbs = { + .modify = lib_interface_pim_address_family_bfd_profile_modify, + .destroy = lib_interface_pim_address_family_bfd_profile_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bsm", + .cbs = { + .modify = lib_interface_pim_address_family_bsm_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/unicast-bsm", + .cbs = { + .modify = lib_interface_pim_address_family_unicast_bsm_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/active-active", + .cbs = { + .modify = lib_interface_pim_address_family_active_active_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/use-source", + .cbs = { + .modify = lib_interface_pim_address_family_use_source_modify, + .destroy = lib_interface_pim_address_family_use_source_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/multicast-boundary-oil", + .cbs = { + .modify = lib_interface_pim_address_family_multicast_boundary_oil_modify, + .destroy = lib_interface_pim_address_family_multicast_boundary_oil_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/mroute", + .cbs = { + .create = lib_interface_pim_address_family_mroute_create, + .destroy = lib_interface_pim_address_family_mroute_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/mroute/oif", + .cbs = { + .modify = lib_interface_pim_address_family_mroute_oif_modify, + .destroy = lib_interface_pim_address_family_mroute_oif_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + +/* clang-format off */ +const struct frr_yang_module_info frr_pim_rp_info = { + .name = "frr-pim-rp", + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list/group-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list/prefix-list", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + +/* clang-format off */ +const struct frr_yang_module_info frr_gmp_info = { + .name = "frr-gmp", + .nodes = { + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family", + .cbs = { + .create = lib_interface_gmp_address_family_create, + .destroy = lib_interface_gmp_address_family_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/enable", + .cbs = { + .modify = lib_interface_gmp_address_family_enable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/igmp-version", + .cbs = { + .modify = lib_interface_gmp_address_family_igmp_version_modify, + .destroy = lib_interface_gmp_address_family_igmp_version_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/mld-version", + .cbs = { + .modify = lib_interface_gmp_address_family_mld_version_modify, + .destroy = lib_interface_gmp_address_family_mld_version_destroy, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/query-interval", + .cbs = { + .modify = lib_interface_gmp_address_family_query_interval_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/query-max-response-time", + .cbs = { + .modify = lib_interface_gmp_address_family_query_max_response_time_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/last-member-query-interval", + .cbs = { + .modify = lib_interface_gmp_address_family_last_member_query_interval_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/robustness-variable", + .cbs = { + .modify = lib_interface_gmp_address_family_robustness_variable_modify, + } + }, + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/static-group", + .cbs = { + .create = lib_interface_gmp_address_family_static_group_create, + .destroy = lib_interface_gmp_address_family_static_group_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; + diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h new file mode 100644 index 0000000..0321d07 --- /dev/null +++ b/pimd/pim_nb.h @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 VmWare + * Sarita Patra + */ + +#ifndef _FRR_PIM_NB_H_ +#define _FRR_PIM_NB_H_ + +extern const struct frr_yang_module_info frr_pim_info; +extern const struct frr_yang_module_info frr_pim_rp_info; +extern const struct frr_yang_module_info frr_gmp_info; + +/* frr-pim prototypes*/ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_rebalance_modify( + struct nb_cb_modify_args *args); +int pim_address_family_join_prune_interval_modify(struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_keep_alive_timer_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_keep_alive_timer_modify( + struct nb_cb_modify_args *args); +int pim_address_family_create(struct nb_cb_create_args *args); +int pim_address_family_destroy(struct nb_cb_destroy_args *args); +int pim_address_family_packets_modify(struct nb_cb_modify_args *args); +int pim_address_family_register_suppress_time_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_destroy( + struct nb_cb_destroy_args *args); +void routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_apply_finish( + struct nb_cb_apply_finish_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_action_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_destroy( + struct nb_cb_destroy_args *args); +int pim_msdp_hold_time_modify(struct nb_cb_modify_args *args); +int pim_msdp_keep_alive_modify(struct nb_cb_modify_args *args); +int pim_msdp_connection_retry_modify(struct nb_cb_modify_args *args); +int pim_msdp_mesh_group_create(struct nb_cb_create_args *args); +int pim_msdp_mesh_group_destroy(struct nb_cb_destroy_args *args); +int pim_msdp_mesh_group_members_create(struct nb_cb_create_args *args); +int pim_msdp_mesh_group_members_destroy(struct nb_cb_destroy_args *args); +int pim_msdp_mesh_group_source_modify(struct nb_cb_modify_args *args); +int pim_msdp_mesh_group_source_destroy(struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_source_ip_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_destroy( + struct nb_cb_destroy_args *args); +void routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_apply_finish( + struct nb_cb_apply_finish_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_my_role_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peer_state_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_dr_priority_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_create(struct nb_cb_create_args *args); +int lib_interface_pim_address_family_destroy(struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_pim_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_pim_passive_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_hello_interval_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_hello_holdtime_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_hello_holdtime_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_bfd_create(struct nb_cb_create_args *args); +int lib_interface_pim_address_family_bfd_destroy( + struct nb_cb_destroy_args *args); +void lib_interface_pim_address_family_bfd_apply_finish( + struct nb_cb_apply_finish_args *args); +int lib_interface_pim_address_family_bfd_min_rx_interval_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_bfd_min_tx_interval_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_bfd_detect_mult_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_bfd_profile_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_bfd_profile_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_bsm_modify(struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_unicast_bsm_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_active_active_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_use_source_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_use_source_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_multicast_boundary_oil_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_multicast_boundary_oil_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_mroute_create( + struct nb_cb_create_args *args); +int lib_interface_pim_address_family_mroute_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_pim_address_family_mroute_oif_modify( + struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_mroute_oif_destroy( + struct nb_cb_destroy_args *args); + +/* frr-pim-rp prototypes*/ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy( + struct nb_cb_destroy_args *args); + +/* frr-gmp prototypes*/ +int lib_interface_gmp_address_family_create( + struct nb_cb_create_args *args); +int lib_interface_gmp_address_family_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_gmp_address_family_enable_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_igmp_version_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_igmp_version_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_gmp_address_family_mld_version_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_mld_version_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_gmp_address_family_query_interval_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_query_max_response_time_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_last_member_query_interval_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_robustness_variable_modify( + struct nb_cb_modify_args *args); +int lib_interface_gmp_address_family_static_group_create( + struct nb_cb_create_args *args); +int lib_interface_gmp_address_family_static_group_destroy( + struct nb_cb_destroy_args *args); + +/* + * Callback registered with routing_nb lib to validate only + * one instance of staticd is allowed + */ +int routing_control_plane_protocols_name_validate( + struct nb_cb_create_args *args); + +#if PIM_IPV == 4 +#define FRR_PIM_AF_XPATH_VAL "frr-routing:ipv4" +#else +#define FRR_PIM_AF_XPATH_VAL "frr-routing:ipv6" +#endif + +#define FRR_PIM_VRF_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-pim:pim/address-family[address-family='%s']" +#define FRR_PIM_INTERFACE_XPATH \ + "./frr-pim:pim/address-family[address-family='%s']" +#define FRR_PIM_ENABLE_XPATH \ + "%s/frr-pim:pim/address-family[address-family='%s']/pim-enable" +#define FRR_PIM_ROUTER_XPATH \ + "/frr-pim:pim/address-family[address-family='%s']" +#define FRR_PIM_MROUTE_XPATH \ + "./frr-pim:pim/address-family[address-family='%s']/" \ + "mroute[source-addr='%s'][group-addr='%s']" +#define FRR_PIM_STATIC_RP_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-pim:pim/address-family[address-family='%s']/" \ + "frr-pim-rp:rp/static-rp/rp-list[rp-address='%s']" +#define FRR_GMP_INTERFACE_XPATH \ + "./frr-gmp:gmp/address-family[address-family='%s']" +#define FRR_GMP_ENABLE_XPATH \ + "%s/frr-gmp:gmp/address-family[address-family='%s']/enable" +#define FRR_GMP_JOIN_XPATH \ + "./frr-gmp:gmp/address-family[address-family='%s']/" \ + "static-group[group-addr='%s'][source-addr='%s']" +#define FRR_PIM_MSDP_XPATH FRR_PIM_VRF_XPATH "/msdp" + +#endif /* _FRR_PIM_NB_H_ */ diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c new file mode 100644 index 0000000..be0be85 --- /dev/null +++ b/pimd/pim_nb_config.c @@ -0,0 +1,2918 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 VmWare + * Sarita Patra + */ + +#include + +#include "pimd.h" +#include "pim_nb.h" +#include "lib/northbound_cli.h" +#include "pim_igmpv3.h" +#include "pim_neighbor.h" +#include "pim_nht.h" +#include "pim_pim.h" +#include "pim_mlag.h" +#include "pim_bfd.h" +#include "pim_static.h" +#include "pim_ssm.h" +#include "pim_ssmpingd.h" +#include "pim_vxlan.h" +#include "pim_util.h" +#include "log.h" +#include "lib_errors.h" +#include "pim_util.h" +#include "pim6_mld.h" + +#if PIM_IPV == 6 +#define pim6_msdp_err(funcname, argtype) \ +int funcname(struct argtype *args) \ +{ \ + snprintf(args->errmsg, args->errmsg_len, \ + "Trying to configure MSDP in pim6d. " \ + "MSDP does not exist for IPv6."); \ + return NB_ERR_VALIDATION; \ +} \ +MACRO_REQUIRE_SEMICOLON() + +#define yang_dnode_get_pimaddr yang_dnode_get_ipv6 + +#else /* PIM_IPV != 6 */ +#define pim6_msdp_err(funcname, argtype) \ +MACRO_REQUIRE_SEMICOLON() + +#define yang_dnode_get_pimaddr yang_dnode_get_ipv4 +#endif /* PIM_IPV != 6 */ + +/* + * When PIM is disabled on interface, IGMPv3 local membership + * information is not injected into PIM interface state. + + * The function pim_if_membership_refresh() fetches all IGMPv3 local + * membership information into PIM. It is intented to be called + * whenever PIM is enabled on the interface in order to collect missed + * local membership information. + */ +static void pim_if_membership_refresh(struct interface *ifp) +{ + struct pim_interface *pim_ifp; +#if PIM_IPV == 4 + struct listnode *grpnode; + struct gm_group *grp; +#else + struct gm_if *gm_ifp; + struct gm_sg *sg, *sg_start; +#endif + + pim_ifp = ifp->info; + assert(pim_ifp); + + if (!pim_ifp->pim_enable) + return; + if (!pim_ifp->gm_enable) + return; + +#if PIM_IPV == 6 + gm_ifp = pim_ifp->mld; + if (!gm_ifp) + return; +#endif + /* + * First clear off membership from all PIM (S,G) entries on the + * interface + */ + + pim_ifchannel_membership_clear(ifp); + +#if PIM_IPV == 4 + /* + * Then restore PIM (S,G) membership from all IGMPv3 (S,G) entries on + * the interface + */ + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, grp)) { + struct listnode *srcnode; + struct gm_source *src; + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, + src)) { + + if (IGMP_SOURCE_TEST_FORWARDING(src->source_flags)) { + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = src->source_addr; + sg.grp = grp->group_addr; + pim_ifchannel_local_membership_add( + ifp, &sg, false /*is_vxlan*/); + } + + } /* scan group sources */ + } /* scan igmp groups */ +#else + sg_start = gm_sgs_first(gm_ifp->sgs); + + frr_each_from (gm_sgs, gm_ifp->sgs, sg, sg_start) { + if (!in6_multicast_nofwd(&sg->sgaddr.grp)) { + pim_ifchannel_local_membership_add( + ifp, &sg->sgaddr, false /*is_vxlan*/); + } + } +#endif + + /* + * Finally delete every PIM (S,G) entry lacking all state info + */ + + pim_ifchannel_delete_on_noinfo(ifp); +} + +static int pim_cmd_interface_add(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + pim_ifp = pim_if_new(ifp, false, true, false, false); + else + pim_ifp->pim_enable = true; + + pim_if_addr_add_all(ifp); + pim_upstream_nh_if_update(pim_ifp->pim, ifp); + pim_if_membership_refresh(ifp); + + pim_if_create_pimreg(pim_ifp->pim); + return 1; +} + +static int interface_pim_use_src_cmd_worker(struct interface *ifp, + pim_addr source_addr, char *errmsg, size_t errmsg_len) +{ + int result; + int ret = NB_OK; + + result = pim_update_source_set(ifp, source_addr); + + switch (result) { + case PIM_SUCCESS: + break; + case PIM_IFACE_NOT_FOUND: + ret = NB_ERR; + snprintf(errmsg, errmsg_len, + "Pim not enabled on this interface %s", + ifp->name); + break; + case PIM_UPDATE_SOURCE_DUP: + ret = NB_ERR; + snprintf(errmsg, errmsg_len, "Source already set"); + break; + default: + ret = NB_ERR; + snprintf(errmsg, errmsg_len, "Source set failed"); + } + + return ret; +} + +static int pim_cmd_spt_switchover(struct pim_instance *pim, + enum pim_spt_switchover spt, + const char *plist) +{ + pim->spt.switchover = spt; + + switch (pim->spt.switchover) { + case PIM_SPT_IMMEDIATE: + XFREE(MTYPE_PIM_PLIST_NAME, pim->spt.plist); + + pim_upstream_add_lhr_star_pimreg(pim); + break; + case PIM_SPT_INFINITY: + pim_upstream_remove_lhr_star_pimreg(pim, plist); + + XFREE(MTYPE_PIM_PLIST_NAME, pim->spt.plist); + + if (plist) + pim->spt.plist = XSTRDUP(MTYPE_PIM_PLIST_NAME, plist); + break; + } + + return NB_OK; +} + +static int pim_ssm_cmd_worker(struct pim_instance *pim, const char *plist, + char *errmsg, size_t errmsg_len) +{ + int result = pim_ssm_range_set(pim, pim->vrf->vrf_id, plist); + int ret = NB_ERR; + + if (result == PIM_SSM_ERR_NONE) + return NB_OK; + + switch (result) { + case PIM_SSM_ERR_NO_VRF: + snprintf(errmsg, errmsg_len, + "VRF doesn't exist"); + break; + case PIM_SSM_ERR_DUP: + snprintf(errmsg, errmsg_len, + "duplicate config"); + break; + default: + snprintf(errmsg, errmsg_len, + "ssm range config failed"); + } + + return ret; +} + +static int pim_rp_cmd_worker(struct pim_instance *pim, pim_addr rp_addr, + struct prefix group, const char *plist, + char *errmsg, size_t errmsg_len) +{ + int result; + + result = pim_rp_new(pim, rp_addr, group, plist, RP_SRC_STATIC); + + if (result == PIM_RP_NO_PATH) { + snprintfrr(errmsg, errmsg_len, + "No Path to RP address specified: %pPA", &rp_addr); + return NB_OK; + } + + if (result == PIM_GROUP_OVERLAP) { + snprintf(errmsg, errmsg_len, + "Group range specified cannot exact match another"); + return NB_ERR_INCONSISTENCY; + } + + if (result == PIM_GROUP_PFXLIST_OVERLAP) { + snprintf(errmsg, errmsg_len, + "This group is already covered by a RP prefix-list"); + return NB_ERR_INCONSISTENCY; + } + + if (result == PIM_RP_PFXLIST_IN_USE) { + snprintf(errmsg, errmsg_len, + "The same prefix-list cannot be applied to multiple RPs"); + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static int pim_no_rp_cmd_worker(struct pim_instance *pim, pim_addr rp_addr, + struct prefix group, const char *plist, + char *errmsg, size_t errmsg_len) +{ + char group_str[PREFIX2STR_BUFFER]; + int result; + + prefix2str(&group, group_str, sizeof(group_str)); + + result = pim_rp_del(pim, rp_addr, group, plist, RP_SRC_STATIC); + + if (result == PIM_GROUP_BAD_ADDRESS) { + snprintf(errmsg, errmsg_len, + "Bad group address specified: %s", group_str); + return NB_ERR_INCONSISTENCY; + } + + if (result == PIM_RP_BAD_ADDRESS) { + snprintfrr(errmsg, errmsg_len, "Bad RP address specified: %pPA", + &rp_addr); + return NB_ERR_INCONSISTENCY; + } + + if (result == PIM_RP_NOT_FOUND) { + snprintf(errmsg, errmsg_len, + "Unable to find specified RP"); + return NB_ERR_INCONSISTENCY; + } + + return NB_OK; +} + +static bool is_pim_interface(const struct lyd_node *dnode) +{ + char if_xpath[XPATH_MAXLEN]; + const struct lyd_node *pim_enable_dnode; + const struct lyd_node *igmp_enable_dnode; + + yang_dnode_get_path(dnode, if_xpath, sizeof(if_xpath)); + pim_enable_dnode = + yang_dnode_getf(dnode, + "%s/frr-pim:pim/address-family[address-family='%s']/pim-enable", + if_xpath, FRR_PIM_AF_XPATH_VAL); + igmp_enable_dnode = yang_dnode_getf(dnode, + "%s/frr-gmp:gmp/address-family[address-family='%s']/enable", + if_xpath, FRR_PIM_AF_XPATH_VAL); + + if (((pim_enable_dnode) && + (yang_dnode_get_bool(pim_enable_dnode, "."))) || + ((igmp_enable_dnode) && + (yang_dnode_get_bool(igmp_enable_dnode, ".")))) + return true; + + return false; +} + +static int pim_cmd_gm_start(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + uint8_t need_startup = 0; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + pim_ifp = pim_if_new(ifp, true, false, false, false); + need_startup = 1; + } else { + if (!pim_ifp->gm_enable) { + pim_ifp->gm_enable = true; + need_startup = 1; + } + } + pim_if_create_pimreg(pim_ifp->pim); + + /* 'ip igmp' executed multiple times, with need_startup + * avoid multiple if add all and membership refresh + */ + if (need_startup) { + pim_if_addr_add_all(ifp); + pim_if_membership_refresh(ifp); + } + + return NB_OK; +} + +/* + * CLI reconfiguration affects the interface level (struct pim_interface). + * This function propagates the reconfiguration to every active socket + * for that interface. + */ +#if PIM_IPV == 4 +static void igmp_sock_query_interval_reconfig(struct gm_sock *igmp) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + assert(igmp); + assert(igmp->interface); + assert(igmp->interface->info); + + ifp = igmp->interface; + pim_ifp = ifp->info; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug("%s: Querier %pPAs on %s reconfig query_interval=%d", + __func__, &igmp->ifaddr, ifp->name, + pim_ifp->gm_default_query_interval); + + /* + * igmp_startup_mode_on() will reset QQI: + + * igmp->querier_query_interval = pim_ifp->gm_default_query_interval; + */ + igmp_startup_mode_on(igmp); +} + +static void igmp_sock_query_reschedule(struct gm_sock *igmp) +{ + if (igmp->mtrace_only) + return; + + if (igmp->t_igmp_query_timer) { + /* other querier present */ + assert(igmp->t_igmp_query_timer); + assert(!igmp->t_other_querier_timer); + + pim_igmp_general_query_off(igmp); + pim_igmp_general_query_on(igmp); + + assert(igmp->t_igmp_query_timer); + assert(!igmp->t_other_querier_timer); + } else { + /* this is the querier */ + + assert(!igmp->t_igmp_query_timer); + assert(igmp->t_other_querier_timer); + + pim_igmp_other_querier_timer_off(igmp); + pim_igmp_other_querier_timer_on(igmp); + + assert(!igmp->t_igmp_query_timer); + assert(igmp->t_other_querier_timer); + } +} +#endif /* PIM_IPV == 4 */ + +#if PIM_IPV == 4 +static void change_query_interval(struct pim_interface *pim_ifp, + int query_interval) +{ + struct listnode *sock_node; + struct gm_sock *igmp; + + pim_ifp->gm_default_query_interval = query_interval; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) { + igmp_sock_query_interval_reconfig(igmp); + igmp_sock_query_reschedule(igmp); + } +} +#endif + +static void change_query_max_response_time(struct interface *ifp, + int query_max_response_time_dsec) +{ +#if PIM_IPV == 4 + struct listnode *sock_node; + struct gm_sock *igmp; + struct listnode *grp_node; + struct gm_group *grp; +#endif + + struct pim_interface *pim_ifp = ifp->info; + + if (pim_ifp->gm_query_max_response_time_dsec == + query_max_response_time_dsec) + return; + + pim_ifp->gm_query_max_response_time_dsec = query_max_response_time_dsec; + +#if PIM_IPV == 6 + gm_ifp_update(ifp); +#else + /* + * Below we modify socket/group/source timers in order to quickly + * reflect the change. Otherwise, those timers would args->eventually + * catch up. + */ + + /* scan all sockets */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_socket_list, sock_node, igmp)) { + /* reschedule socket general query */ + igmp_sock_query_reschedule(igmp); + } + + /* scan socket groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grp_node, grp)) { + struct listnode *src_node; + struct gm_source *src; + + /* reset group timers for groups in EXCLUDE mode */ + if (grp->group_filtermode_isexcl) + igmp_group_reset_gmi(grp); + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, + src)) { + + /* reset source timers for sources with running + * timers + */ + if (src->t_source_timer) + igmp_source_reset_gmi(grp, src); + } + } +#endif /* PIM_IPV == 4 */ +} + +int routing_control_plane_protocols_name_validate( + struct nb_cb_create_args *args) +{ + const char *name; + + name = yang_dnode_get_string(args->dnode, "name"); + if (!strmatch(name, "pim")) { + snprintf(args->errmsg, args->errmsg_len, + "pim supports only one instance with name pimd"); + return NB_ERR_VALIDATION; + } + return NB_OK; +} + +/* + * XPath: /frr-pim:pim/address-family + */ +int pim_address_family_create(struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int pim_address_family_destroy(struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-pim:pim/address-family/packets + */ +int pim_address_family_packets_modify(struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + router->packet_process = yang_dnode_get_uint8(args->dnode, + NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-pim:pim/address-family/join-prune-interval + */ +int pim_address_family_join_prune_interval_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + router->t_periodic = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-pim:pim/address-family/register-suppress-time + */ +int pim_address_family_register_suppress_time_modify( + struct nb_cb_modify_args *args) +{ + uint16_t value; + switch (args->event) { + case NB_EV_VALIDATE: + value = yang_dnode_get_uint16(args->dnode, NULL); + /* + * As soon as this is non-constant it needs to be replaced with + * a yang_dnode_get to lookup the candidate value, *not* the + * operational value. Since the code has a field assigned and + * used for this value it should have YANG/CLI to set it too, + * otherwise just use the #define! + */ + /* RFC7761: 4.11. Timer Values */ + if (value <= router->register_probe_time * 2) { + snprintf( + args->errmsg, args->errmsg_len, + "Register suppress time (%u) must be more than " + "twice the register probe time (%u).", + value, router->register_probe_time); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + pim_update_suppress_timers( + yang_dnode_get_uint16(args->dnode, NULL)); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ecmp + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->ecmp_enable = yang_dnode_get_bool(args->dnode, NULL); + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ecmp-rebalance + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ecmp_rebalance_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->ecmp_rebalance_enable = + yang_dnode_get_bool(args->dnode, NULL); + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/keep-alive-timer + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_keep_alive_timer_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->keep_alive_time = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/rp-keep-alive-timer + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_keep_alive_timer_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->rp_keep_alive_time = yang_dnode_get_uint16(args->dnode, + NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_create( + struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/send-v6-secondary + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->send_v6_secondary = yang_dnode_get_bool(args->dnode, NULL); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_send_v6_secondary_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover + */ +void routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + int spt_switch_action; + const char *prefix_list = NULL; + + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + spt_switch_action = yang_dnode_get_enum(args->dnode, "spt-action"); + + switch (spt_switch_action) { + case PIM_SPT_INFINITY: + if (yang_dnode_exists(args->dnode, + "./spt-infinity-prefix-list")) + prefix_list = yang_dnode_get_string( + args->dnode, "./spt-infinity-prefix-list"); + + pim_cmd_spt_switchover(pim, PIM_SPT_INFINITY, + prefix_list); + break; + case PIM_SPT_IMMEDIATE: + pim_cmd_spt_switchover(pim, PIM_SPT_IMMEDIATE, NULL); + } +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover/spt-action + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_action_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/spt-switchover/spt-infinity-prefix-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_spt_switchover_spt_infinity_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ssm-prefix-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + const char *plist_name; + int result; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + plist_name = yang_dnode_get_string(args->dnode, NULL); + result = pim_ssm_cmd_worker(pim, plist_name, args->errmsg, + args->errmsg_len); + + if (result) + return NB_ERR_INCONSISTENCY; + + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + int result; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + result = pim_ssm_cmd_worker(pim, NULL, args->errmsg, + args->errmsg_len); + + if (result) + return NB_ERR_INCONSISTENCY; + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/ssm-pingd-source-ip + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + int result; + pim_addr source_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&source_addr, args->dnode, NULL); + result = pim_ssmpingd_start(pim, source_addr); + if (result) { + snprintf( + args->errmsg, args->errmsg_len, + "%% Failure starting ssmpingd for source %pPA: %d", + &source_addr, result); + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_ssm_pingd_source_ip_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + int result; + pim_addr source_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&source_addr, args->dnode, NULL); + result = pim_ssmpingd_stop(pim, source_addr); + if (result) { + snprintf( + args->errmsg, args->errmsg_len, + "%% Failure stopping ssmpingd for source %pPA: %d", + &source_addr, result); + return NB_ERR_INCONSISTENCY; + } + + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/hold-time + */ +int pim_msdp_hold_time_modify(struct nb_cb_modify_args *args) +{ + struct pim_instance *pim; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->msdp.hold_time = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/keep-alive + */ +int pim_msdp_keep_alive_modify(struct nb_cb_modify_args *args) +{ + struct pim_instance *pim; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->msdp.keep_alive = yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp/connection-retry + */ +int pim_msdp_connection_retry_modify(struct nb_cb_modify_args *args) +{ + struct pim_instance *pim; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim->msdp.connection_retry = + yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; +} + +pim6_msdp_err(pim_msdp_mesh_group_destroy, nb_cb_destroy_args); +pim6_msdp_err(pim_msdp_mesh_group_create, nb_cb_create_args); +pim6_msdp_err(pim_msdp_mesh_group_source_modify, nb_cb_modify_args); +pim6_msdp_err(pim_msdp_mesh_group_source_destroy, nb_cb_destroy_args); +pim6_msdp_err(pim_msdp_mesh_group_members_create, nb_cb_create_args); +pim6_msdp_err(pim_msdp_mesh_group_members_destroy, nb_cb_destroy_args); +pim6_msdp_err(routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_source_ip_modify, + nb_cb_modify_args); +pim6_msdp_err(routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_destroy, + nb_cb_destroy_args); +pim6_msdp_err(routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_create, + nb_cb_create_args); + +#if PIM_IPV != 6 +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups + */ +int pim_msdp_mesh_group_create(struct nb_cb_create_args *args) +{ + struct pim_msdp_mg *mg; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + mg = pim_msdp_mg_new(vrf->info, yang_dnode_get_string( + args->dnode, "./name")); + nb_running_set_entry(args->dnode, mg); + break; + } + + return NB_OK; +} + +int pim_msdp_mesh_group_destroy(struct nb_cb_destroy_args *args) +{ + struct pim_msdp_mg *mg; + struct vrf *vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mg = nb_running_unset_entry(args->dnode); + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim_msdp_mg_free(vrf->info, &mg); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups/source + */ +int pim_msdp_mesh_group_source_modify(struct nb_cb_modify_args *args) +{ + const struct lyd_node *vrf_dnode; + struct pim_msdp_mg *mg; + struct vrf *vrf; + struct ipaddr ip; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mg = nb_running_get_entry(args->dnode, NULL, true); + vrf_dnode = + yang_dnode_get_parent(args->dnode, "address-family"); + vrf = nb_running_get_entry(vrf_dnode, "../../", true); + yang_dnode_get_ip(&ip, args->dnode, NULL); + + pim_msdp_mg_src_add(vrf->info, mg, &ip.ip._v4_addr); + break; + } + return NB_OK; +} + +int pim_msdp_mesh_group_source_destroy(struct nb_cb_destroy_args *args) +{ + const struct lyd_node *vrf_dnode; + struct pim_msdp_mg *mg; + struct vrf *vrf; + struct in_addr addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mg = nb_running_get_entry(args->dnode, NULL, true); + vrf_dnode = + yang_dnode_get_parent(args->dnode, "address-family"); + vrf = nb_running_get_entry(vrf_dnode, "../../", true); + + addr.s_addr = INADDR_ANY; + pim_msdp_mg_src_add(vrf->info, mg, &addr); + break; + } + return NB_OK; +} + + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-mesh-groups/members + */ +int pim_msdp_mesh_group_members_create(struct nb_cb_create_args *args) +{ + const struct lyd_node *vrf_dnode; + struct pim_msdp_mg_mbr *mbr; + struct pim_msdp_mg *mg; + struct vrf *vrf; + struct ipaddr ip; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mg = nb_running_get_entry(args->dnode, NULL, true); + vrf_dnode = + yang_dnode_get_parent(args->dnode, "address-family"); + vrf = nb_running_get_entry(vrf_dnode, "../../", true); + yang_dnode_get_ip(&ip, args->dnode, "address"); + + mbr = pim_msdp_mg_mbr_add(vrf->info, mg, &ip.ip._v4_addr); + nb_running_set_entry(args->dnode, mbr); + break; + } + + return NB_OK; +} + +int pim_msdp_mesh_group_members_destroy(struct nb_cb_destroy_args *args) +{ + struct pim_msdp_mg_mbr *mbr; + struct pim_msdp_mg *mg; + const struct lyd_node *mg_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mbr = nb_running_get_entry(args->dnode, NULL, true); + mg_dnode = + yang_dnode_get_parent(args->dnode, "msdp-mesh-groups"); + mg = nb_running_get_entry(mg_dnode, NULL, true); + pim_msdp_mg_mbr_del(mg, mbr); + nb_running_unset_entry(args->dnode); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-peer + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_create( + struct nb_cb_create_args *args) +{ + struct pim_msdp_peer *mp; + struct pim_instance *pim; + struct vrf *vrf; + struct ipaddr peer_ip; + struct ipaddr source_ip; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_ip(&peer_ip, args->dnode, "peer-ip"); + yang_dnode_get_ip(&source_ip, args->dnode, "source-ip"); + mp = pim_msdp_peer_add(pim, &peer_ip.ipaddr_v4, + &source_ip.ipaddr_v4, NULL); + nb_running_set_entry(args->dnode, mp); + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_destroy( + struct nb_cb_destroy_args *args) +{ + struct pim_msdp_peer *mp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mp = nb_running_unset_entry(args->dnode); + pim_msdp_peer_del(&mp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/msdp-peer/source-ip + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_msdp_peer_source_ip_modify( + struct nb_cb_modify_args *args) +{ + struct pim_msdp_peer *mp; + struct ipaddr source_ip; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + mp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ip(&source_ip, args->dnode, NULL); + pim_msdp_peer_change_source(mp, &source_ip.ipaddr_v4); + break; + } + + return NB_OK; +} +#endif /* PIM_IPV != 6 */ + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_create( + struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_destroy( + struct nb_cb_destroy_args *args) +{ + struct in_addr addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + addr.s_addr = 0; + pim_vxlan_mlag_update(true/*mlag_enable*/, + false/*peer_state*/, MLAG_ROLE_NONE, + NULL/*peerlink*/, &addr); + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag + */ +void routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + const char *ifname; + uint32_t role; + bool peer_state; + struct interface *ifp; + struct ipaddr reg_addr; + + ifname = yang_dnode_get_string(args->dnode, "peerlink-rif"); + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + if (!ifp) { + snprintf(args->errmsg, args->errmsg_len, + "No such interface name %s", ifname); + return; + } + role = yang_dnode_get_enum(args->dnode, "my-role"); + peer_state = yang_dnode_get_bool(args->dnode, "peer-state"); + yang_dnode_get_ip(®_addr, args->dnode, "reg-address"); + + pim_vxlan_mlag_update(true, peer_state, role, ifp, + ®_addr.ip._v4_addr); +} + + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/peerlink-rif + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peerlink_rif_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/reg-address + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_reg_address_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/my-role + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_my_role_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/mlag/peer-state + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mlag_peer_state_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/register-accept-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + const char *plist; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + plist = yang_dnode_get_string(args->dnode, NULL); + + XFREE(MTYPE_PIM_PLIST_NAME, pim->register_plist); + pim->register_plist = XSTRDUP(MTYPE_PIM_PLIST_NAME, plist); + + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_register_accept_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + + XFREE(MTYPE_PIM_PLIST_NAME, pim->register_plist); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family + */ +int lib_interface_pim_address_family_create(struct nb_cb_create_args *args) +{ + struct interface *ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_APPLY: + case NB_EV_ABORT: + break; + case NB_EV_PREPARE: + ifp = nb_running_get_entry(args->dnode, NULL, true); + if (ifp->info) + return NB_OK; + + pim_if_new(ifp, false, false, false, false); + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (!pim_ifp) + return NB_OK; + + pim_pim_interface_delete(ifp); + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/pim-enable + */ +int lib_interface_pim_address_family_pim_enable_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + int mcast_if_count; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + mcast_if_count = + yang_get_list_elements_count(if_dnode); + + /* Limiting mcast interfaces to number of VIFs */ + if (mcast_if_count == MAXVIFS) { + snprintf(args->errmsg, args->errmsg_len, + "Max multicast interfaces(%d) reached.", + MAXVIFS); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + + if (yang_dnode_get_bool(args->dnode, NULL)) { + if (!pim_cmd_interface_add(ifp)) { + snprintf(args->errmsg, args->errmsg_len, + "Could not enable PIM SM on interface %s", + ifp->name); + return NB_ERR_INCONSISTENCY; + } + } else { + pim_ifp = ifp->info; + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + pim_pim_interface_delete(ifp); + } + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-pim:pim/address-family/pim-passive-enable + */ +int lib_interface_pim_address_family_pim_passive_enable_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->pim_passive_enable = + yang_dnode_get_bool(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/hello-interval + */ +int lib_interface_pim_address_family_hello_interval_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->pim_hello_period = + yang_dnode_get_uint16(args->dnode, NULL); + pim_ifp->pim_default_holdtime = -1; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/hello-holdtime + */ +int lib_interface_pim_address_family_hello_holdtime_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->pim_default_holdtime = + yang_dnode_get_uint16(args->dnode, NULL); + break; + } + + return NB_OK; + +} + +int lib_interface_pim_address_family_hello_holdtime_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->pim_default_holdtime = -1; + break; + } + + return NB_OK; +} +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd + */ +int lib_interface_pim_address_family_bfd_create(struct nb_cb_create_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->bfd_config.enabled = true; + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_bfd_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "Pim not enabled on this interface"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->bfd_config.enabled = false; + pim_bfd_reg_dereg_all_nbr(ifp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd + */ +void lib_interface_pim_address_family_bfd_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + + if (!pim_ifp) { + zlog_debug("Pim not enabled on this interface"); + return; + } + + pim_ifp->bfd_config.detection_multiplier = + yang_dnode_get_uint8(args->dnode, "detect_mult"); + pim_ifp->bfd_config.min_rx = + yang_dnode_get_uint16(args->dnode, "min-rx-interval"); + pim_ifp->bfd_config.min_tx = + yang_dnode_get_uint16(args->dnode, "min-tx-interval"); + + pim_bfd_reg_dereg_all_nbr(ifp); +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd/min-rx-interval + */ +int lib_interface_pim_address_family_bfd_min_rx_interval_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd/min-tx-interval + */ +int lib_interface_pim_address_family_bfd_min_tx_interval_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd/detect_mult + */ +int lib_interface_pim_address_family_bfd_detect_mult_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd/profile + */ +int lib_interface_pim_address_family_bfd_profile_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile); + pim_ifp->bfd_config.profile = XSTRDUP( + MTYPE_TMP, yang_dnode_get_string(args->dnode, NULL)); + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_bfd_profile_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + /* NOTHING */ + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bsm + */ +int lib_interface_pim_address_family_bsm_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->bsm_enable = yang_dnode_get_bool(args->dnode, NULL); + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/unicast-bsm + */ +int lib_interface_pim_address_family_unicast_bsm_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->ucast_bsm_accept = + yang_dnode_get_bool(args->dnode, NULL); + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/active-active + */ +int lib_interface_pim_address_family_active_active_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (yang_dnode_get_bool(args->dnode, NULL)) { + if (PIM_DEBUG_MLAG) + zlog_debug( + "Configuring PIM active-active on Interface: %s", + ifp->name); + pim_if_configure_mlag_dualactive(pim_ifp); + } else { + if (PIM_DEBUG_MLAG) + zlog_debug( + "UnConfiguring PIM active-active on Interface: %s", + ifp->name); + pim_if_unconfigure_mlag_dualactive(pim_ifp); + } + + break; + } + + return NB_OK; + +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/dr-priority + */ +int lib_interface_pim_address_family_dr_priority_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + uint32_t old_dr_prio; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "Pim not enabled on this interface"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + old_dr_prio = pim_ifp->pim_dr_priority; + pim_ifp->pim_dr_priority = yang_dnode_get_uint32(args->dnode, + NULL); + + if (old_dr_prio != pim_ifp->pim_dr_priority) { + pim_if_dr_election(ifp); + pim_hello_restart_now(ifp); + } + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/use-source + */ +int lib_interface_pim_address_family_use_source_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + pim_addr source_addr; + int result; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "Pim not enabled on this interface"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); +#if PIM_IPV == 4 + yang_dnode_get_ipv4(&source_addr, args->dnode, NULL); +#else + yang_dnode_get_ipv6(&source_addr, args->dnode, NULL); +#endif + + result = interface_pim_use_src_cmd_worker( + ifp, source_addr, + args->errmsg, args->errmsg_len); + + if (result != PIM_SUCCESS) + return NB_ERR_INCONSISTENCY; + + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_use_source_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + int result; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "Pim not enabled on this interface"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + + result = interface_pim_use_src_cmd_worker(ifp, PIMADDR_ANY, + args->errmsg, + args->errmsg_len); + + if (result != PIM_SUCCESS) + return NB_ERR_INCONSISTENCY; + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/multicast-boundary-oil + */ +int lib_interface_pim_address_family_multicast_boundary_oil_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const char *plist; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "Pim not enabled on this interface"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + plist = yang_dnode_get_string(args->dnode, NULL); + + if (pim_ifp->boundary_oil_plist) + XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); + + pim_ifp->boundary_oil_plist = + XSTRDUP(MTYPE_PIM_INTERFACE, plist); + + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_multicast_boundary_oil_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (pim_ifp->boundary_oil_plist) + XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/mroute + */ +int lib_interface_pim_address_family_mroute_create( + struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_mroute_destroy( + struct nb_cb_destroy_args *args) +{ + struct pim_instance *pim; + struct pim_interface *pim_iifp; + struct interface *iif; + struct interface *oif; + const char *oifname; + pim_addr source_addr; + pim_addr group_addr; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + iif = nb_running_get_entry(args->dnode, NULL, true); + pim_iifp = iif->info; + pim = pim_iifp->pim; + + oifname = yang_dnode_get_string(args->dnode, "oif"); + oif = if_lookup_by_name(oifname, pim->vrf->vrf_id); + + if (!oif) { + snprintf(args->errmsg, args->errmsg_len, + "No such interface name %s", + oifname); + return NB_ERR_INCONSISTENCY; + } + + yang_dnode_get_pimaddr(&source_addr, args->dnode, "source-addr"); + yang_dnode_get_pimaddr(&group_addr, args->dnode, "group-addr"); + + if (pim_static_del(pim, iif, oif, group_addr, source_addr)) { + snprintf(args->errmsg, args->errmsg_len, + "Failed to remove static mroute"); + return NB_ERR_INCONSISTENCY; + } + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/mroute/oif + */ +int lib_interface_pim_address_family_mroute_oif_modify( + struct nb_cb_modify_args *args) +{ + struct pim_instance *pim; + struct pim_interface *pim_iifp; + struct interface *iif; + struct interface *oif; + const char *oifname; + pim_addr source_addr; + pim_addr group_addr; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Enable PIM and/or IGMP on this interface first"); + return NB_ERR_VALIDATION; + } + +#ifdef PIM_ENFORCE_LOOPFREE_MFC + iif = nb_running_get_entry(args->dnode, NULL, false); + if (!iif) { + return NB_OK; + } + + pim_iifp = iif->info; + pim = pim_iifp->pim; + + oifname = yang_dnode_get_string(args->dnode, NULL); + oif = if_lookup_by_name(oifname, pim->vrf->vrf_id); + + if (oif && (iif->ifindex == oif->ifindex)) { + strlcpy(args->errmsg, + "% IIF same as OIF and loopfree enforcement is enabled; rejecting", + args->errmsg_len); + return NB_ERR_VALIDATION; + } +#endif + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + iif = nb_running_get_entry(args->dnode, NULL, true); + pim_iifp = iif->info; + pim = pim_iifp->pim; + + oifname = yang_dnode_get_string(args->dnode, NULL); + oif = if_lookup_by_name(oifname, pim->vrf->vrf_id); + if (!oif) { + snprintf(args->errmsg, args->errmsg_len, + "No such interface name %s", + oifname); + return NB_ERR_INCONSISTENCY; + } + + yang_dnode_get_pimaddr(&source_addr, args->dnode, "../source-addr"); + yang_dnode_get_pimaddr(&group_addr, args->dnode, "../group-addr"); + + if (pim_static_add(pim, iif, oif, group_addr, source_addr)) { + snprintf(args->errmsg, args->errmsg_len, + "Failed to add static mroute"); + return NB_ERR_INCONSISTENCY; + } + + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_mroute_oif_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_create( + struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + const char *plist; + int result = 0; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "rp-address"); + + if (yang_dnode_get(args->dnode, "group-list")) { + yang_dnode_get_prefix(&group, args->dnode, + "./group-list"); + apply_mask(&group); + result = pim_no_rp_cmd_worker(pim, rp_addr, group, NULL, + args->errmsg, + args->errmsg_len); + } + + else if (yang_dnode_get(args->dnode, "prefix-list")) { + plist = yang_dnode_get_string(args->dnode, + "./prefix-list"); + if (!pim_get_all_mcast_group(&group)) { + flog_err( + EC_LIB_DEVELOPMENT, + "Unable to convert 224.0.0.0/4 to prefix"); + return NB_ERR_INCONSISTENCY; + } + + result = pim_no_rp_cmd_worker(pim, rp_addr, group, + plist, args->errmsg, + args->errmsg_len); + } + + if (result) + return NB_ERR_INCONSISTENCY; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list/group-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_create( + struct nb_cb_create_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + yang_dnode_get_prefix(&group, args->dnode, NULL); + apply_mask(&group); + return pim_rp_cmd_worker(pim, rp_addr, group, NULL, + args->errmsg, args->errmsg_len); + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_group_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + yang_dnode_get_prefix(&group, args->dnode, NULL); + apply_mask(&group); + + return pim_no_rp_cmd_worker(pim, rp_addr, group, NULL, + args->errmsg, args->errmsg_len); + } + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/static-rp/rp-list/prefix-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_modify( + struct nb_cb_modify_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + const char *plist; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + plist = yang_dnode_get_string(args->dnode, NULL); + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + if (!pim_get_all_mcast_group(&group)) { + flog_err(EC_LIB_DEVELOPMENT, + "Unable to convert 224.0.0.0/4 to prefix"); + return NB_ERR_INCONSISTENCY; + } + return pim_rp_cmd_worker(pim, rp_addr, group, plist, + args->errmsg, args->errmsg_len); + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + const char *plist; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + plist = yang_dnode_get_string(args->dnode, NULL); + if (!pim_get_all_mcast_group(&group)) { + flog_err(EC_LIB_DEVELOPMENT, + "Unable to convert 224.0.0.0/4 to prefix"); + return NB_ERR_INCONSISTENCY; + } + return pim_no_rp_cmd_worker(pim, rp_addr, group, plist, + args->errmsg, args->errmsg_len); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family + */ +int lib_interface_gmp_address_family_create(struct nb_cb_create_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int lib_interface_gmp_address_family_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_gm_interface_delete(ifp); + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/enable + */ +int lib_interface_gmp_address_family_enable_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + bool gm_enable; + int mcast_if_count; + const char *ifp_name; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + mcast_if_count = + yang_get_list_elements_count(if_dnode); + /* Limiting mcast interfaces to number of VIFs */ + if (mcast_if_count == MAXVIFS) { + ifp_name = yang_dnode_get_string(if_dnode, "name"); + snprintf( + args->errmsg, args->errmsg_len, + "Max multicast interfaces(%d) Reached. Could not enable %s on interface %s", + MAXVIFS, GM, ifp_name); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + gm_enable = yang_dnode_get_bool(args->dnode, NULL); + + if (gm_enable) + return pim_cmd_gm_start(ifp); + + else + pim_gm_interface_delete(ifp); + } + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/igmp-version + */ +int lib_interface_gmp_address_family_igmp_version_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + int igmp_version, old_version = 0; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + igmp_version = yang_dnode_get_uint8(args->dnode, NULL); + old_version = pim_ifp->igmp_version; + pim_ifp->igmp_version = igmp_version; + + /* Current and new version is different refresh existing + * membership. Going from 3 -> 2 or 2 -> 3. + */ + if (old_version != igmp_version) + pim_if_membership_refresh(ifp); + + break; + } + + return NB_OK; +} + +int lib_interface_gmp_address_family_igmp_version_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/mld-version + */ +int lib_interface_gmp_address_family_mld_version_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + pim_ifp->mld_version = yang_dnode_get_uint8(args->dnode, NULL); + gm_ifp_update(ifp); + break; + } + + return NB_OK; +} + +int lib_interface_gmp_address_family_mld_version_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + pim_ifp->mld_version = 2; + gm_ifp_update(ifp); + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/query-interval + */ +int lib_interface_gmp_address_family_query_interval_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + int query_interval; + +#if PIM_IPV == 4 + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + query_interval = yang_dnode_get_uint16(args->dnode, NULL); + change_query_interval(ifp->info, query_interval); + } +#else + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + if (!pim_ifp) + return NB_ERR_INCONSISTENCY; + + query_interval = yang_dnode_get_uint16(args->dnode, NULL); + pim_ifp->gm_default_query_interval = query_interval; + gm_ifp_update(ifp); + } +#endif + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/query-max-response-time + */ +int lib_interface_gmp_address_family_query_max_response_time_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + int query_max_response_time_dsec; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + query_max_response_time_dsec = + yang_dnode_get_uint16(args->dnode, NULL); + change_query_max_response_time(ifp, + query_max_response_time_dsec); + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/last-member-query-interval + */ +int lib_interface_gmp_address_family_last_member_query_interval_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + int last_member_query_interval; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + last_member_query_interval = + yang_dnode_get_uint16(args->dnode, NULL); + pim_ifp->gm_specific_query_max_response_time_dsec = + last_member_query_interval; +#if PIM_IPV == 6 + gm_ifp_update(ifp); +#endif + + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/robustness-variable + */ +int lib_interface_gmp_address_family_robustness_variable_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + int last_member_query_count; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + last_member_query_count = + yang_dnode_get_uint8(args->dnode, NULL); + pim_ifp->gm_last_member_query_count = last_member_query_count; +#if PIM_IPV == 6 + gm_ifp_update(ifp); +#endif + break; + } + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/static-group + */ +int lib_interface_gmp_address_family_static_group_create( + struct nb_cb_create_args *args) +{ + struct interface *ifp; + pim_addr source_addr; + pim_addr group_addr; + int result; + const char *ifp_name; + const struct lyd_node *if_dnode; + + switch (args->event) { + case NB_EV_VALIDATE: + if_dnode = yang_dnode_get_parent(args->dnode, "interface"); + if (!is_pim_interface(if_dnode)) { + ifp_name = yang_dnode_get_string(if_dnode, "name"); + snprintf(args->errmsg, args->errmsg_len, + "multicast not enabled on interface %s", + ifp_name); + return NB_ERR_VALIDATION; + } + + yang_dnode_get_pimaddr(&group_addr, args->dnode, + "./group-addr"); +#if PIM_IPV == 4 + if (pim_is_group_224_0_0_0_24(group_addr)) { + snprintf( + args->errmsg, args->errmsg_len, + "Groups within 224.0.0.0/24 are reserved and cannot be joined"); + return NB_ERR_VALIDATION; + } +#else + if (ipv6_mcast_reserved(&group_addr)) { + snprintf( + args->errmsg, args->errmsg_len, + "Groups within ffx2::/16 are reserved and cannot be joined"); + return NB_ERR_VALIDATION; + } +#endif + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_pimaddr(&source_addr, args->dnode, + "./source-addr"); + yang_dnode_get_pimaddr(&group_addr, args->dnode, + "./group-addr"); + result = pim_if_gm_join_add(ifp, group_addr, source_addr); + if (result) { + snprintf(args->errmsg, args->errmsg_len, + "Failure joining " GM " group"); + return NB_ERR_INCONSISTENCY; + } + } + return NB_OK; +} + +int lib_interface_gmp_address_family_static_group_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + pim_addr source_addr; + pim_addr group_addr; + int result; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_pimaddr(&source_addr, args->dnode, + "./source-addr"); + yang_dnode_get_pimaddr(&group_addr, args->dnode, + "./group-addr"); + result = pim_if_gm_join_del(ifp, group_addr, source_addr); + + if (result) { + snprintf(args->errmsg, args->errmsg_len, + "%% Failure leaving " GM + " group %pPAs %pPAs on interface %s: %d", + &source_addr, &group_addr, ifp->name, result); + + return NB_ERR_INCONSISTENCY; + } + + break; + } + + return NB_OK; +} diff --git a/pimd/pim_neighbor.c b/pimd/pim_neighbor.c new file mode 100644 index 0000000..1cd7cce --- /dev/null +++ b/pimd/pim_neighbor.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "memory.h" +#include "if.h" +#include "vty.h" +#include "plist.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_neighbor.h" +#include "pim_time.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_pim.h" +#include "pim_upstream.h" +#include "pim_ifchannel.h" +#include "pim_rp.h" +#include "pim_zebra.h" +#include "pim_join.h" +#include "pim_jp_agg.h" +#include "pim_bfd.h" +#include "pim_register.h" +#include "pim_oil.h" + +static void dr_election_by_addr(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct listnode *node; + struct pim_neighbor *neigh; + + pim_ifp = ifp->info; + assert(pim_ifp); + + pim_ifp->pim_dr_addr = pim_ifp->primary_address; + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: on interface %s", __func__, ifp->name); + } + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { + if (pim_addr_cmp(neigh->source_addr, pim_ifp->pim_dr_addr) > 0) + pim_ifp->pim_dr_addr = neigh->source_addr; + } +} + +static void dr_election_by_pri(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct listnode *node; + struct pim_neighbor *neigh; + uint32_t dr_pri; + + pim_ifp = ifp->info; + assert(pim_ifp); + + pim_ifp->pim_dr_addr = pim_ifp->primary_address; + dr_pri = pim_ifp->pim_dr_priority; + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: dr pri %u on interface %s", __func__, dr_pri, + ifp->name); + } + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_info("%s: neigh pri %u addr %pPA if dr addr %pPA", + __func__, neigh->dr_priority, + &neigh->source_addr, &pim_ifp->pim_dr_addr); + } + if ((neigh->dr_priority > dr_pri) || + ((neigh->dr_priority == dr_pri) && + (pim_addr_cmp(neigh->source_addr, pim_ifp->pim_dr_addr) > + 0))) { + pim_ifp->pim_dr_addr = neigh->source_addr; + dr_pri = neigh->dr_priority; + } + } +} + +/* + RFC 4601: 4.3.2. DR Election + + A router's idea of the current DR on an interface can change when a + PIM Hello message is received, when a neighbor times out, or when a + router's own DR Priority changes. + */ +int pim_if_dr_election(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + pim_addr old_dr_addr; + + ++pim_ifp->pim_dr_election_count; + + old_dr_addr = pim_ifp->pim_dr_addr; + + if (pim_ifp->pim_dr_num_nondrpri_neighbors) { + dr_election_by_addr(ifp); + } else { + dr_election_by_pri(ifp); + } + + /* DR changed ? */ + if (pim_addr_cmp(old_dr_addr, pim_ifp->pim_dr_addr)) { + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "%s: DR was %pPA now is %pPA on interface %s", + __func__, &old_dr_addr, &pim_ifp->pim_dr_addr, + ifp->name); + + pim_ifp->pim_dr_election_last = + pim_time_monotonic_sec(); /* timestamp */ + ++pim_ifp->pim_dr_election_changes; + pim_if_update_join_desired(pim_ifp); + pim_if_update_could_assert(ifp); + pim_if_update_assert_tracking_desired(ifp); + + if (PIM_I_am_DR(pim_ifp)) { + pim_ifp->am_i_dr = true; + pim_clear_nocache_state(pim_ifp); + } else { + if (pim_ifp->am_i_dr == true) { + pim_reg_del_on_couldreg_fail(ifp); + pim_ifp->am_i_dr = false; + } + } + + return 1; + } + + return 0; +} + +static void update_dr_priority(struct pim_neighbor *neigh, + pim_hello_options hello_options, + uint32_t dr_priority) +{ + pim_hello_options will_set_pri; /* boolean */ + pim_hello_options bit_flip; /* boolean */ + pim_hello_options pri_change; /* boolean */ + + will_set_pri = + PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY); + + bit_flip = (will_set_pri + != PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_DR_PRIORITY)); + + if (bit_flip) { + struct pim_interface *pim_ifp = neigh->interface->info; + + /* update num. of neighbors without dr_pri */ + + if (will_set_pri) { + --pim_ifp->pim_dr_num_nondrpri_neighbors; + } else { + ++pim_ifp->pim_dr_num_nondrpri_neighbors; + } + } + + pri_change = (bit_flip || (neigh->dr_priority != dr_priority)); + + if (will_set_pri) { + neigh->dr_priority = dr_priority; + } else { + neigh->dr_priority = 0; /* cosmetic unset */ + } + + if (pri_change) { + /* + RFC 4601: 4.3.2. DR Election + + A router's idea of the current DR on an interface can change + when a + PIM Hello message is received, when a neighbor times out, or + when a + router's own DR Priority changes. + */ + pim_if_dr_election( + neigh->interface); // router's own DR Priority changes + } +} + +static void on_neighbor_timer(struct event *t) +{ + struct pim_neighbor *neigh; + struct interface *ifp; + char msg[100]; + + neigh = EVENT_ARG(t); + + ifp = neigh->interface; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "Expired %d sec holdtime for neighbor %pPA on interface %s", + neigh->holdtime, &neigh->source_addr, ifp->name); + + snprintf(msg, sizeof(msg), "%d-sec holdtime expired", neigh->holdtime); + pim_neighbor_delete(ifp, neigh, msg); + + /* + RFC 4601: 4.3.2. DR Election + + A router's idea of the current DR on an interface can change when a + PIM Hello message is received, when a neighbor times out, or when a + router's own DR Priority changes. + */ + pim_if_dr_election(ifp); // neighbor times out +} + +void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime) +{ + neigh->holdtime = holdtime; + + EVENT_OFF(neigh->t_expire_timer); + + /* + 0xFFFF is request for no holdtime + */ + if (neigh->holdtime == 0xFFFF) { + return; + } + + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_debug("%s: starting %u sec timer for neighbor %pPA on %s", + __func__, neigh->holdtime, &neigh->source_addr, + neigh->interface->name); + + event_add_timer(router->master, on_neighbor_timer, neigh, + neigh->holdtime, &neigh->t_expire_timer); +} + +static void on_neighbor_jp_timer(struct event *t) +{ + struct pim_neighbor *neigh = EVENT_ARG(t); + struct pim_rpf rpf; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s:Sending JP Agg to %pPA on %s with %d groups", + __func__, &neigh->source_addr, + neigh->interface->name, + neigh->upstream_jp_agg->count); + + rpf.source_nexthop.interface = neigh->interface; + rpf.rpf_addr = neigh->source_addr; + pim_joinprune_send(&rpf, neigh->upstream_jp_agg); + + event_add_timer(router->master, on_neighbor_jp_timer, neigh, + router->t_periodic, &neigh->jp_timer); +} + +static void pim_neighbor_start_jp_timer(struct pim_neighbor *neigh) +{ + EVENT_OFF(neigh->jp_timer); + event_add_timer(router->master, on_neighbor_jp_timer, neigh, + router->t_periodic, &neigh->jp_timer); +} + +static struct pim_neighbor * +pim_neighbor_new(struct interface *ifp, pim_addr source_addr, + pim_hello_options hello_options, uint16_t holdtime, + uint16_t propagation_delay, uint16_t override_interval, + uint32_t dr_priority, uint32_t generation_id, + struct list *addr_list) +{ + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + + assert(ifp); + pim_ifp = ifp->info; + assert(pim_ifp); + + neigh = XCALLOC(MTYPE_PIM_NEIGHBOR, sizeof(*neigh)); + + neigh->creation = pim_time_monotonic_sec(); + neigh->source_addr = source_addr; + neigh->hello_options = hello_options; + neigh->propagation_delay_msec = propagation_delay; + neigh->override_interval_msec = override_interval; + neigh->dr_priority = dr_priority; + neigh->generation_id = generation_id; + neigh->prefix_list = addr_list; + neigh->t_expire_timer = NULL; + neigh->interface = ifp; + + neigh->upstream_jp_agg = list_new(); + neigh->upstream_jp_agg->cmp = pim_jp_agg_group_list_cmp; + neigh->upstream_jp_agg->del = + (void (*)(void *))pim_jp_agg_group_list_free; + pim_neighbor_start_jp_timer(neigh); + + pim_neighbor_timer_reset(neigh, holdtime); + /* + * The pim_ifstat_hello_sent variable is used to decide if + * we should expedite a hello out the interface. If we + * establish a new neighbor, we unfortunately need to + * reset the value so that we can know to hurry up and + * hello + */ + PIM_IF_FLAG_UNSET_HELLO_SENT(pim_ifp->flags); + + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: creating PIM neighbor %pPA on interface %s", + __func__, &source_addr, ifp->name); + + zlog_notice("PIM NEIGHBOR UP: neighbor %pPA on interface %s", + &source_addr, ifp->name); + + if (neigh->propagation_delay_msec + > pim_ifp->pim_neighbors_highest_propagation_delay_msec) { + pim_ifp->pim_neighbors_highest_propagation_delay_msec = + neigh->propagation_delay_msec; + } + if (neigh->override_interval_msec + > pim_ifp->pim_neighbors_highest_override_interval_msec) { + pim_ifp->pim_neighbors_highest_override_interval_msec = + neigh->override_interval_msec; + } + + if (!PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY)) { + /* update num. of neighbors without hello option lan_delay */ + ++pim_ifp->pim_number_of_nonlandelay_neighbors; + } + + if (!PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_DR_PRIORITY)) { + /* update num. of neighbors without hello option dr_pri */ + ++pim_ifp->pim_dr_num_nondrpri_neighbors; + } + + // Register PIM Neighbor with BFD + pim_bfd_info_nbr_create(pim_ifp, neigh); + + return neigh; +} + +static void delete_prefix_list(struct pim_neighbor *neigh) +{ + if (neigh->prefix_list) { + +#ifdef DUMP_PREFIX_LIST + struct listnode *p_node; + struct prefix *p; + int list_size = neigh->prefix_list + ? (int)listcount(neigh->prefix_list) + : -1; + int i = 0; + for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, p_node, p)) { + zlog_debug( + "%s: DUMP_PREFIX_LIST neigh=%x prefix_list=%x prefix=%x addr=%pFXh [%d/%d]", + __func__, (unsigned)neigh, + (unsigned)neigh->prefix_list, (unsigned)p, p, i, + list_size); + ++i; + } +#endif + + list_delete(&neigh->prefix_list); + } +} + +void pim_neighbor_free(struct pim_neighbor *neigh) +{ + assert(!neigh->t_expire_timer); + + delete_prefix_list(neigh); + + list_delete(&neigh->upstream_jp_agg); + EVENT_OFF(neigh->jp_timer); + + bfd_sess_free(&neigh->bfd_session); + + XFREE(MTYPE_PIM_NEIGHBOR, neigh); +} + +struct pim_neighbor *pim_neighbor_find_by_secondary(struct interface *ifp, + struct prefix *src) +{ + struct pim_interface *pim_ifp; + struct listnode *node, *pnode; + struct pim_neighbor *neigh; + struct prefix *p; + + if (!ifp || !ifp->info) + return NULL; + + pim_ifp = ifp->info; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { + for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, pnode, p)) { + if (prefix_same(p, src)) + return neigh; + } + } + + return NULL; +} + +struct pim_neighbor *pim_neighbor_find(struct interface *ifp, + pim_addr source_addr, bool secondary) +{ + struct pim_interface *pim_ifp; + struct listnode *node; + struct pim_neighbor *neigh; + + if (!ifp) + return NULL; + + pim_ifp = ifp->info; + if (!pim_ifp) + return NULL; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) { + if (!pim_addr_cmp(source_addr, neigh->source_addr)) { + return neigh; + } + } + + if (secondary) { + struct prefix p; + + pim_addr_to_prefix(&p, source_addr); + return pim_neighbor_find_by_secondary(ifp, &p); + } + + return NULL; +} + +/* + * Find the *one* interface out + * this interface. If more than + * one return NULL + */ +struct pim_neighbor *pim_neighbor_find_if(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp || pim_ifp->pim_neighbor_list->count != 1) + return NULL; + + return listnode_head(pim_ifp->pim_neighbor_list); +} + +struct pim_neighbor * +pim_neighbor_add(struct interface *ifp, pim_addr source_addr, + pim_hello_options hello_options, uint16_t holdtime, + uint16_t propagation_delay, uint16_t override_interval, + uint32_t dr_priority, uint32_t generation_id, + struct list *addr_list, int send_hello_now) +{ + struct pim_interface *pim_ifp; + struct pim_neighbor *neigh; + + neigh = pim_neighbor_new(ifp, source_addr, hello_options, holdtime, + propagation_delay, override_interval, + dr_priority, generation_id, addr_list); + if (!neigh) { + return 0; + } + + pim_ifp = ifp->info; + assert(pim_ifp); + + listnode_add(pim_ifp->pim_neighbor_list, neigh); + + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_debug("%s: neighbor %pPA added ", __func__, &source_addr); + /* + RFC 4601: 4.3.2. DR Election + + A router's idea of the current DR on an interface can change when a + PIM Hello message is received, when a neighbor times out, or when a + router's own DR Priority changes. + */ + pim_if_dr_election(neigh->interface); // new neighbor -- should not + // trigger dr election... + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + To allow new or rebooting routers to learn of PIM neighbors quickly, + when a Hello message is received from a new neighbor, or a Hello + message with a new GenID is received from an existing neighbor, a + new Hello message should be sent on this interface after a + randomized delay between 0 and Triggered_Hello_Delay. + + This is a bit silly to do it that way. If I get a new + genid we need to send the hello *now* because we've + lined up a bunch of join/prune messages to go out the + interface. + */ + if (send_hello_now) + pim_hello_restart_now(ifp); + else + pim_hello_restart_triggered(neigh->interface); + + pim_upstream_find_new_rpf(pim_ifp->pim); + + /* RNH can send nexthop update prior to PIM neibhor UP + in that case nexthop cache would not consider this neighbor + as RPF. + Upon PIM neighbor UP, iterate all RPs and update + nexthop cache with this neighbor. + */ + pim_resolve_rp_nh(pim_ifp->pim, neigh); + + pim_rp_setup(pim_ifp->pim); + + sched_rpf_cache_refresh(pim_ifp->pim); + return neigh; +} + +static uint16_t find_neighbors_next_highest_propagation_delay_msec( + struct interface *ifp, struct pim_neighbor *highest_neigh) +{ + struct pim_interface *pim_ifp; + struct listnode *neigh_node; + struct pim_neighbor *neigh; + uint16_t next_highest_delay_msec; + + pim_ifp = ifp->info; + assert(pim_ifp); + + next_highest_delay_msec = pim_ifp->pim_propagation_delay_msec; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, + neigh)) { + if (neigh == highest_neigh) + continue; + if (neigh->propagation_delay_msec > next_highest_delay_msec) + next_highest_delay_msec = neigh->propagation_delay_msec; + } + + return next_highest_delay_msec; +} + +static uint16_t find_neighbors_next_highest_override_interval_msec( + struct interface *ifp, struct pim_neighbor *highest_neigh) +{ + struct pim_interface *pim_ifp; + struct listnode *neigh_node; + struct pim_neighbor *neigh; + uint16_t next_highest_interval_msec; + + pim_ifp = ifp->info; + assert(pim_ifp); + + next_highest_interval_msec = pim_ifp->pim_override_interval_msec; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, + neigh)) { + if (neigh == highest_neigh) + continue; + if (neigh->override_interval_msec > next_highest_interval_msec) + next_highest_interval_msec = + neigh->override_interval_msec; + } + + return next_highest_interval_msec; +} + +void pim_neighbor_delete(struct interface *ifp, struct pim_neighbor *neigh, + const char *delete_message) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + zlog_notice("PIM NEIGHBOR DOWN: neighbor %pPA on interface %s: %s", + &neigh->source_addr, ifp->name, delete_message); + + EVENT_OFF(neigh->t_expire_timer); + + pim_if_assert_on_neighbor_down(ifp, neigh->source_addr); + + if (!PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY)) { + /* update num. of neighbors without hello option lan_delay */ + + --pim_ifp->pim_number_of_nonlandelay_neighbors; + } + + if (!PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_DR_PRIORITY)) { + /* update num. of neighbors without dr_pri */ + + --pim_ifp->pim_dr_num_nondrpri_neighbors; + } + + assert(neigh->propagation_delay_msec + <= pim_ifp->pim_neighbors_highest_propagation_delay_msec); + assert(neigh->override_interval_msec + <= pim_ifp->pim_neighbors_highest_override_interval_msec); + + if (pim_if_lan_delay_enabled(ifp)) { + + /* will delete a neighbor with highest propagation delay? */ + if (neigh->propagation_delay_msec + == pim_ifp->pim_neighbors_highest_propagation_delay_msec) { + /* then find the next highest propagation delay */ + pim_ifp->pim_neighbors_highest_propagation_delay_msec = + find_neighbors_next_highest_propagation_delay_msec( + ifp, neigh); + } + + /* will delete a neighbor with highest override interval? */ + if (neigh->override_interval_msec + == pim_ifp->pim_neighbors_highest_override_interval_msec) { + /* then find the next highest propagation delay */ + pim_ifp->pim_neighbors_highest_override_interval_msec = + find_neighbors_next_highest_override_interval_msec( + ifp, neigh); + } + } + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: deleting PIM neighbor %pPA on interface %s", + __func__, &neigh->source_addr, ifp->name); + } + + listnode_delete(pim_ifp->pim_neighbor_list, neigh); + + pim_neighbor_free(neigh); + + sched_rpf_cache_refresh(pim_ifp->pim); +} + +void pim_neighbor_delete_all(struct interface *ifp, const char *delete_message) +{ + struct pim_interface *pim_ifp; + struct listnode *neigh_node; + struct listnode *neigh_nextnode; + struct pim_neighbor *neigh; + + pim_ifp = ifp->info; + assert(pim_ifp); + + for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node, + neigh_nextnode, neigh)) { + pim_neighbor_delete(ifp, neigh, delete_message); + } +} + +struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh, + struct prefix *addr) +{ + struct listnode *node; + struct prefix *p; + + if (!neigh->prefix_list) + return 0; + + for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, node, p)) { + if (prefix_same(p, addr)) + return p; + } + + return NULL; +} + +/* + RFC 4601: 4.3.4. Maintaining Secondary Address Lists + + All the advertised secondary addresses in received Hello messages + must be checked against those previously advertised by all other + PIM neighbors on that interface. If there is a conflict and the + same secondary address was previously advertised by another + neighbor, then only the most recently received mapping MUST be + maintained, and an error message SHOULD be logged to the + administrator in a rate-limited manner. +*/ +static void delete_from_neigh_addr(struct interface *ifp, + struct list *addr_list, pim_addr neigh_addr) +{ + struct listnode *addr_node; + struct prefix *addr; + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + assert(pim_ifp); + + assert(addr_list); + + /* + Scan secondary address list + */ + for (ALL_LIST_ELEMENTS_RO(addr_list, addr_node, addr)) { + struct listnode *neigh_node; + struct pim_neighbor *neigh; + + if (addr->family != PIM_AF) + continue; + /* + Scan neighbors + */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, + neigh_node, neigh)) { + { + struct prefix *p = pim_neighbor_find_secondary( + neigh, addr); + if (p) { + zlog_info( + "secondary addr %pFXh recvd from neigh %pPA deleted from neigh %pPA on %s", + addr, &neigh_addr, + &neigh->source_addr, ifp->name); + + listnode_delete(neigh->prefix_list, p); + prefix_free(&p); + } + } + + } /* scan neighbors */ + + } /* scan addr list */ +} + +void pim_neighbor_update(struct pim_neighbor *neigh, + pim_hello_options hello_options, uint16_t holdtime, + uint32_t dr_priority, struct list *addr_list) +{ + struct pim_interface *pim_ifp = neigh->interface->info; + uint32_t old, new; + + /* Received holdtime ? */ + if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) { + pim_neighbor_timer_reset(neigh, holdtime); + } else { + pim_neighbor_timer_reset(neigh, + PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); + } + +#ifdef DUMP_PREFIX_LIST + zlog_debug( + "%s: DUMP_PREFIX_LIST old_prefix_list=%x old_size=%d new_prefix_list=%x new_size=%d", + __func__, (unsigned)neigh->prefix_list, + neigh->prefix_list ? (int)listcount(neigh->prefix_list) : -1, + (unsigned)addr_list, + addr_list ? (int)listcount(addr_list) : -1); +#endif + + if (neigh->prefix_list == addr_list) { + if (addr_list) { + flog_err( + EC_LIB_DEVELOPMENT, + "%s: internal error: trying to replace same prefix list=%p", + __func__, (void *)addr_list); + } + } else { + /* Delete existing secondary address list */ + delete_prefix_list(neigh); + } + + if (addr_list) { + delete_from_neigh_addr(neigh->interface, addr_list, + neigh->source_addr); + } + + /* Replace secondary address list */ + neigh->prefix_list = addr_list; + + update_dr_priority(neigh, hello_options, dr_priority); + new = PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY); + old = PIM_OPTION_IS_SET(neigh->hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY); + + if (old != new) { + if (old) + ++pim_ifp->pim_number_of_nonlandelay_neighbors; + else + --pim_ifp->pim_number_of_nonlandelay_neighbors; + } + /* + Copy flags + */ + neigh->hello_options = hello_options; +} diff --git a/pimd/pim_neighbor.h b/pimd/pim_neighbor.h new file mode 100644 index 0000000..69e9976 --- /dev/null +++ b/pimd/pim_neighbor.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_NEIGHBOR_H +#define PIM_NEIGHBOR_H + +#include + +#include "if.h" +#include "linklist.h" +#include "prefix.h" + +#include "pim_tlv.h" +#include "pim_iface.h" +#include "pim_str.h" + +struct pim_neighbor { + int64_t creation; /* timestamp of creation */ + pim_addr source_addr; + pim_hello_options hello_options; + uint16_t holdtime; + uint16_t propagation_delay_msec; + uint16_t override_interval_msec; + uint32_t dr_priority; + uint32_t generation_id; + struct list *prefix_list; /* list of struct prefix */ + struct event *t_expire_timer; + struct interface *interface; + + struct event *jp_timer; + struct list *upstream_jp_agg; + struct bfd_session_params *bfd_session; +}; + +void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime); +void pim_neighbor_free(struct pim_neighbor *neigh); +struct pim_neighbor *pim_neighbor_find(struct interface *ifp, + pim_addr source_addr, bool secondary); +struct pim_neighbor *pim_neighbor_find_by_secondary(struct interface *ifp, + struct prefix *src); +struct pim_neighbor *pim_neighbor_find_if(struct interface *ifp); + + +#define PIM_NEIGHBOR_SEND_DELAY 0 +#define PIM_NEIGHBOR_SEND_NOW 1 +struct pim_neighbor * +pim_neighbor_add(struct interface *ifp, pim_addr source_addr, + pim_hello_options hello_options, uint16_t holdtime, + uint16_t propagation_delay, uint16_t override_interval, + uint32_t dr_priority, uint32_t generation_id, + struct list *addr_list, int send_hello_now); +void pim_neighbor_delete(struct interface *ifp, struct pim_neighbor *neigh, + const char *delete_message); +void pim_neighbor_delete_all(struct interface *ifp, const char *delete_message); +void pim_neighbor_update(struct pim_neighbor *neigh, + pim_hello_options hello_options, uint16_t holdtime, + uint32_t dr_priority, struct list *addr_list); +struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh, + struct prefix *addr); +int pim_if_dr_election(struct interface *ifp); + +#endif /* PIM_NEIGHBOR_H */ diff --git a/pimd/pim_nht.c b/pimd/pim_nht.c new file mode 100644 index 0000000..32cdf4b --- /dev/null +++ b/pimd/pim_nht.c @@ -0,0 +1,1103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2017 Cumulus Networks, Inc. + * Chirag Shah + */ +#include +#include "network.h" +#include "zclient.h" +#include "stream.h" +#include "nexthop.h" +#include "if.h" +#include "hash.h" +#include "jhash.h" + +#include "lib/printfrr.h" + +#include "pimd.h" +#include "pimd/pim_nht.h" +#include "pim_instance.h" +#include "log.h" +#include "pim_time.h" +#include "pim_oil.h" +#include "pim_ifchannel.h" +#include "pim_mroute.h" +#include "pim_zebra.h" +#include "pim_upstream.h" +#include "pim_join.h" +#include "pim_jp_agg.h" +#include "pim_zebra.h" +#include "pim_zlookup.h" +#include "pim_rp.h" +#include "pim_addr.h" +#include "pim_register.h" +#include "pim_vxlan.h" + +/** + * pim_sendmsg_zebra_rnh -- Format and send a nexthop register/Unregister + * command to Zebra. + */ +void pim_sendmsg_zebra_rnh(struct pim_instance *pim, struct zclient *zclient, + struct pim_nexthop_cache *pnc, int command) +{ + struct prefix p; + int ret; + + pim_addr_to_prefix(&p, pnc->rpf.rpf_addr); + ret = zclient_send_rnh(zclient, command, &p, SAFI_UNICAST, false, false, + pim->vrf->vrf_id); + if (ret == ZCLIENT_SEND_FAILURE) + zlog_warn("sendmsg_nexthop: zclient_send_message() failed"); + + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: NHT %sregistered addr %pFX(%s) with Zebra ret:%d ", + __func__, + (command == ZEBRA_NEXTHOP_REGISTER) ? " " : "de", &p, + pim->vrf->name, ret); + + return; +} + +struct pim_nexthop_cache *pim_nexthop_cache_find(struct pim_instance *pim, + struct pim_rpf *rpf) +{ + struct pim_nexthop_cache *pnc = NULL; + struct pim_nexthop_cache lookup; + + lookup.rpf.rpf_addr = rpf->rpf_addr; + pnc = hash_lookup(pim->rpf_hash, &lookup); + + return pnc; +} + +static struct pim_nexthop_cache *pim_nexthop_cache_add(struct pim_instance *pim, + struct pim_rpf *rpf_addr) +{ + struct pim_nexthop_cache *pnc; + char hash_name[64]; + + pnc = XCALLOC(MTYPE_PIM_NEXTHOP_CACHE, + sizeof(struct pim_nexthop_cache)); + pnc->rpf.rpf_addr = rpf_addr->rpf_addr; + + pnc = hash_get(pim->rpf_hash, pnc, hash_alloc_intern); + + pnc->rp_list = list_new(); + pnc->rp_list->cmp = pim_rp_list_cmp; + + snprintfrr(hash_name, sizeof(hash_name), "PNC %pPA(%s) Upstream Hash", + &pnc->rpf.rpf_addr, pim->vrf->name); + pnc->upstream_hash = hash_create_size(8192, pim_upstream_hash_key, + pim_upstream_equal, hash_name); + + return pnc; +} + +static struct pim_nexthop_cache *pim_nht_get(struct pim_instance *pim, + pim_addr addr) +{ + struct pim_nexthop_cache *pnc = NULL; + struct pim_rpf rpf; + struct zclient *zclient = NULL; + + zclient = pim_zebra_zclient_get(); + memset(&rpf, 0, sizeof(rpf)); + rpf.rpf_addr = addr; + + pnc = pim_nexthop_cache_find(pim, &rpf); + if (!pnc) { + pnc = pim_nexthop_cache_add(pim, &rpf); + pim_sendmsg_zebra_rnh(pim, zclient, pnc, + ZEBRA_NEXTHOP_REGISTER); + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug( + "%s: NHT cache and zebra notification added for %pPA(%s)", + __func__, &addr, pim->vrf->name); + } + + return pnc; +} + +/* TBD: this does several distinct things and should probably be split up. + * (checking state vs. returning pnc vs. adding upstream vs. adding rp) + */ +int pim_find_or_track_nexthop(struct pim_instance *pim, pim_addr addr, + struct pim_upstream *up, struct rp_info *rp, + struct pim_nexthop_cache *out_pnc) +{ + struct pim_nexthop_cache *pnc; + struct listnode *ch_node = NULL; + + pnc = pim_nht_get(pim, addr); + + assertf(up || rp, "addr=%pPA", &addr); + + if (rp != NULL) { + ch_node = listnode_lookup(pnc->rp_list, rp); + if (ch_node == NULL) + listnode_add_sort(pnc->rp_list, rp); + } + + if (up != NULL) + (void)hash_get(pnc->upstream_hash, up, hash_alloc_intern); + + if (CHECK_FLAG(pnc->flags, PIM_NEXTHOP_VALID)) { + if (out_pnc) + memcpy(out_pnc, pnc, sizeof(struct pim_nexthop_cache)); + return 1; + } + + return 0; +} + +void pim_nht_bsr_add(struct pim_instance *pim, pim_addr addr) +{ + struct pim_nexthop_cache *pnc; + + pnc = pim_nht_get(pim, addr); + + pnc->bsr_count++; +} + +static void pim_nht_drop_maybe(struct pim_instance *pim, + struct pim_nexthop_cache *pnc) +{ + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: NHT %pPA(%s) rp_list count:%d upstream count:%ld BSR count:%u", + __func__, &pnc->rpf.rpf_addr, pim->vrf->name, + pnc->rp_list->count, pnc->upstream_hash->count, + pnc->bsr_count); + + if (pnc->rp_list->count == 0 && pnc->upstream_hash->count == 0 + && pnc->bsr_count == 0) { + struct zclient *zclient = pim_zebra_zclient_get(); + + pim_sendmsg_zebra_rnh(pim, zclient, pnc, + ZEBRA_NEXTHOP_UNREGISTER); + + list_delete(&pnc->rp_list); + hash_free(pnc->upstream_hash); + + hash_release(pim->rpf_hash, pnc); + if (pnc->nexthop) + nexthops_free(pnc->nexthop); + XFREE(MTYPE_PIM_NEXTHOP_CACHE, pnc); + } +} + +void pim_delete_tracked_nexthop(struct pim_instance *pim, pim_addr addr, + struct pim_upstream *up, struct rp_info *rp) +{ + struct pim_nexthop_cache *pnc = NULL; + struct pim_nexthop_cache lookup; + struct pim_upstream *upstream = NULL; + + /* Remove from RPF hash if it is the last entry */ + lookup.rpf.rpf_addr = addr; + pnc = hash_lookup(pim->rpf_hash, &lookup); + if (!pnc) { + zlog_warn("attempting to delete nonexistent NHT entry %pPA", + &addr); + return; + } + + if (rp) { + /* Release the (*, G)upstream from pnc->upstream_hash, + * whose Group belongs to the RP getting deleted + */ + frr_each (rb_pim_upstream, &pim->upstream_head, upstream) { + struct prefix grp; + struct rp_info *trp_info; + + if (!pim_addr_is_any(upstream->sg.src)) + continue; + + pim_addr_to_prefix(&grp, upstream->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + if (trp_info == rp) + hash_release(pnc->upstream_hash, upstream); + } + listnode_delete(pnc->rp_list, rp); + } + + if (up) + hash_release(pnc->upstream_hash, up); + + pim_nht_drop_maybe(pim, pnc); +} + +void pim_nht_bsr_del(struct pim_instance *pim, pim_addr addr) +{ + struct pim_nexthop_cache *pnc = NULL; + struct pim_nexthop_cache lookup; + + /* + * Nothing to do here if the address to unregister + * is 0.0.0.0 as that the BSR has not been registered + * for tracking yet. + */ + if (pim_addr_is_any(addr)) + return; + + lookup.rpf.rpf_addr = addr; + + pnc = hash_lookup(pim->rpf_hash, &lookup); + + if (!pnc) { + zlog_warn("attempting to delete nonexistent NHT BSR entry %pPA", + &addr); + return; + } + + assertf(pnc->bsr_count > 0, "addr=%pPA", &addr); + pnc->bsr_count--; + + pim_nht_drop_maybe(pim, pnc); +} + +bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr, + struct interface *src_ifp, pim_addr src_ip) +{ + struct pim_nexthop_cache *pnc = NULL; + struct pim_nexthop_cache lookup; + struct pim_neighbor *nbr = NULL; + struct nexthop *nh; + struct interface *ifp; + + lookup.rpf.rpf_addr = bsr_addr; + + pnc = hash_lookup(pim->rpf_hash, &lookup); + if (!pnc || !CHECK_FLAG(pnc->flags, PIM_NEXTHOP_ANSWER_RECEIVED)) { + /* BSM from a new freshly registered BSR - do a synchronous + * zebra query since otherwise we'd drop the first packet, + * leading to additional delay in picking up BSM data + */ + + /* FIXME: this should really be moved into a generic NHT + * function that does "add and get immediate result" or maybe + * "check cache or get immediate result." But until that can + * be worked in, here's a copy of the code below :( + */ + struct pim_zlookup_nexthop nexthop_tab[router->multipath]; + ifindex_t i; + struct interface *ifp = NULL; + int num_ifindex; + + memset(nexthop_tab, 0, sizeof(nexthop_tab)); + num_ifindex = zclient_lookup_nexthop( + pim, nexthop_tab, router->multipath, bsr_addr, + PIM_NEXTHOP_LOOKUP_MAX); + + if (num_ifindex <= 0) + return false; + + for (i = 0; i < num_ifindex; i++) { + struct pim_zlookup_nexthop *znh = &nexthop_tab[i]; + + /* pim_zlookup_nexthop has no ->type */ + + /* 1:1 match code below with znh instead of nh */ + ifp = if_lookup_by_index(znh->ifindex, + pim->vrf->vrf_id); + + if (!ifp || !ifp->info) + continue; + + if (if_is_loopback(ifp) && if_is_loopback(src_ifp)) + return true; + + nbr = pim_neighbor_find(ifp, znh->nexthop_addr, true); + if (!nbr) + continue; + + return znh->ifindex == src_ifp->ifindex; + } + return false; + } + + if (!CHECK_FLAG(pnc->flags, PIM_NEXTHOP_VALID)) + return false; + + /* if we accept BSMs from more than one ECMP nexthop, this will cause + * BSM message "multiplication" for each ECMP hop. i.e. if you have + * 4-way ECMP and 4 hops you end up with 256 copies of each BSM + * message. + * + * so... only accept the first (IPv4) valid nexthop as source. + */ + + for (nh = pnc->nexthop; nh; nh = nh->next) { + pim_addr nhaddr; + + switch (nh->type) { +#if PIM_IPV == 4 + case NEXTHOP_TYPE_IPV4: + if (nh->ifindex == IFINDEX_INTERNAL) + continue; + + fallthrough; + case NEXTHOP_TYPE_IPV4_IFINDEX: + nhaddr = nh->gate.ipv4; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + continue; +#else + case NEXTHOP_TYPE_IPV6: + if (nh->ifindex == IFINDEX_INTERNAL) + continue; + + fallthrough; + case NEXTHOP_TYPE_IPV6_IFINDEX: + nhaddr = nh->gate.ipv6; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + continue; +#endif + case NEXTHOP_TYPE_IFINDEX: + nhaddr = bsr_addr; + break; + + case NEXTHOP_TYPE_BLACKHOLE: + continue; + } + + ifp = if_lookup_by_index(nh->ifindex, pim->vrf->vrf_id); + if (!ifp || !ifp->info) + continue; + + if (if_is_loopback(ifp) && if_is_loopback(src_ifp)) + return true; + + /* MRIB (IGP) may be pointing at a router where PIM is down */ + + nbr = pim_neighbor_find(ifp, nhaddr, true); + + if (!nbr) + continue; + + return nh->ifindex == src_ifp->ifindex; + } + return false; +} + +void pim_rp_nexthop_del(struct rp_info *rp_info) +{ + rp_info->rp.source_nexthop.interface = NULL; + rp_info->rp.source_nexthop.mrib_nexthop_addr = PIMADDR_ANY; + rp_info->rp.source_nexthop.mrib_metric_preference = + router->infinite_assert_metric.metric_preference; + rp_info->rp.source_nexthop.mrib_route_metric = + router->infinite_assert_metric.route_metric; +} + +/* Update RP nexthop info based on Nexthop update received from Zebra.*/ +static void pim_update_rp_nh(struct pim_instance *pim, + struct pim_nexthop_cache *pnc) +{ + struct listnode *node = NULL; + struct rp_info *rp_info = NULL; + struct interface *ifp; + + /*Traverse RP list and update each RP Nexthop info */ + for (ALL_LIST_ELEMENTS_RO(pnc->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + ifp = rp_info->rp.source_nexthop.interface; + // Compute PIM RPF using cached nexthop + if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, + rp_info->rp.rpf_addr, + &rp_info->group, 1)) + pim_rp_nexthop_del(rp_info); + + /* + * If we transition from no path to a path + * we need to search through all the vxlan's + * that use this rp and send NULL registers + * for all the vxlan S,G streams + */ + if (!ifp && rp_info->rp.source_nexthop.interface) + pim_vxlan_rp_info_is_alive(pim, &rp_info->rp); + } +} + +/* Update Upstream nexthop info based on Nexthop update received from Zebra.*/ +static int pim_update_upstream_nh_helper(struct hash_bucket *bucket, void *arg) +{ + struct pim_instance *pim = (struct pim_instance *)arg; + struct pim_upstream *up = (struct pim_upstream *)bucket->data; + + enum pim_rpf_result rpf_result; + struct pim_rpf old; + + old.source_nexthop.interface = up->rpf.source_nexthop.interface; + rpf_result = pim_rpf_update(pim, up, &old, __func__); + + /* update kernel multicast forwarding cache (MFC); if the + * RPF nbr is now unreachable the MFC has already been updated + * by pim_rpf_clear + */ + if (rpf_result == PIM_RPF_CHANGED) + pim_upstream_mroute_iif_update(up->channel_oil, __func__); + + if (rpf_result == PIM_RPF_CHANGED || + (rpf_result == PIM_RPF_FAILURE && old.source_nexthop.interface)) + pim_zebra_upstream_rpf_changed(pim, up, &old); + + /* + * If we are a VXLAN source and we are transitioning from not + * having an outgoing interface to having an outgoing interface + * let's immediately send the null pim register + */ + if (!old.source_nexthop.interface && up->rpf.source_nexthop.interface && + PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(up->flags) && + (up->reg_state == PIM_REG_NOINFO || up->reg_state == PIM_REG_JOIN)) { + pim_null_register_send(up); + } + + if (PIM_DEBUG_PIM_NHT) { + zlog_debug("%s: NHT upstream %s(%s) old ifp %s new ifp %s rpf_result: %d", + __func__, up->sg_str, pim->vrf->name, + old.source_nexthop.interface ? old.source_nexthop + .interface->name + : "Unknown", + up->rpf.source_nexthop.interface ? up->rpf.source_nexthop + .interface->name + : "Unknown", + rpf_result); + } + + return HASHWALK_CONTINUE; +} + +static int pim_update_upstream_nh(struct pim_instance *pim, + struct pim_nexthop_cache *pnc) +{ + hash_walk(pnc->upstream_hash, pim_update_upstream_nh_helper, pim); + + pim_zebra_update_all_interfaces(pim); + + return 0; +} + +static int pim_upstream_nh_if_update_helper(struct hash_bucket *bucket, + void *arg) +{ + struct pim_nexthop_cache *pnc = bucket->data; + struct pnc_hash_walk_data *pwd = arg; + struct pim_instance *pim = pwd->pim; + struct interface *ifp = pwd->ifp; + struct nexthop *nh_node = NULL; + ifindex_t first_ifindex; + + for (nh_node = pnc->nexthop; nh_node; nh_node = nh_node->next) { + first_ifindex = nh_node->ifindex; + if (ifp != if_lookup_by_index(first_ifindex, pim->vrf->vrf_id)) + continue; + + if (pnc->upstream_hash->count) { + pim_update_upstream_nh(pim, pnc); + break; + } + } + + return HASHWALK_CONTINUE; +} + +void pim_upstream_nh_if_update(struct pim_instance *pim, struct interface *ifp) +{ + struct pnc_hash_walk_data pwd; + + pwd.pim = pim; + pwd.ifp = ifp; + + hash_walk(pim->rpf_hash, pim_upstream_nh_if_update_helper, &pwd); +} + +uint32_t pim_compute_ecmp_hash(struct prefix *src, struct prefix *grp) +{ + uint32_t hash_val; + + if (!src) + return 0; + + hash_val = prefix_hash_key(src); + if (grp) + hash_val ^= prefix_hash_key(grp); + return hash_val; +} + +static int pim_ecmp_nexthop_search(struct pim_instance *pim, + struct pim_nexthop_cache *pnc, + struct pim_nexthop *nexthop, pim_addr src, + struct prefix *grp, int neighbor_needed) +{ + struct pim_neighbor *nbrs[router->multipath], *nbr = NULL; + struct interface *ifps[router->multipath]; + struct nexthop *nh_node = NULL; + ifindex_t first_ifindex; + struct interface *ifp = NULL; + uint32_t hash_val = 0, mod_val = 0; + uint8_t nh_iter = 0, found = 0; + uint32_t i, num_nbrs = 0; + struct pim_interface *pim_ifp; + + if (!pnc || !pnc->nexthop_num || !nexthop) + return 0; + + pim_addr nh_addr = nexthop->mrib_nexthop_addr; + pim_addr grp_addr = pim_addr_from_prefix(grp); + + memset(&nbrs, 0, sizeof(nbrs)); + memset(&ifps, 0, sizeof(ifps)); + + + // Current Nexthop is VALID, check to stay on the current path. + if (nexthop->interface && nexthop->interface->info && + (!pim_addr_is_any(nh_addr))) { + /* User configured knob to explicitly switch + to new path is disabled or current path + metric is less than nexthop update. + */ + + if (pim->ecmp_rebalance_enable == 0) { + uint8_t curr_route_valid = 0; + // Check if current nexthop is present in new updated + // Nexthop list. + // If the current nexthop is not valid, candidate to + // choose new Nexthop. + for (nh_node = pnc->nexthop; nh_node; + nh_node = nh_node->next) { + curr_route_valid = (nexthop->interface->ifindex + == nh_node->ifindex); + if (curr_route_valid) + break; + } + + if (curr_route_valid && + !pim_if_connected_to_source(nexthop->interface, + src)) { + nbr = pim_neighbor_find( + nexthop->interface, + nexthop->mrib_nexthop_addr, true); + if (!nbr + && !if_is_loopback(nexthop->interface)) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: current nexthop does not have nbr ", + __func__); + } else { + /* update metric even if the upstream + * neighbor stays unchanged + */ + nexthop->mrib_metric_preference = + pnc->distance; + nexthop->mrib_route_metric = + pnc->metric; + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: (%pPA,%pPA)(%s) current nexthop %s is valid, skipping new path selection", + __func__, &src, + &grp_addr, + pim->vrf->name, + nexthop->interface->name); + return 1; + } + } + } + } + + /* + * Look up all interfaces and neighbors, + * store for later usage + */ + for (nh_node = pnc->nexthop, i = 0; nh_node; + nh_node = nh_node->next, i++) { + ifps[i] = + if_lookup_by_index(nh_node->ifindex, pim->vrf->vrf_id); + if (ifps[i]) { +#if PIM_IPV == 4 + pim_addr nhaddr = nh_node->gate.ipv4; +#else + pim_addr nhaddr = nh_node->gate.ipv6; +#endif + nbrs[i] = pim_neighbor_find(ifps[i], nhaddr, true); + if (nbrs[i] || pim_if_connected_to_source(ifps[i], src)) + num_nbrs++; + } + } + if (pim->ecmp_enable) { + struct prefix src_pfx; + uint32_t consider = pnc->nexthop_num; + + if (neighbor_needed && num_nbrs < consider) + consider = num_nbrs; + + if (consider == 0) + return 0; + + // PIM ECMP flag is enable then choose ECMP path. + pim_addr_to_prefix(&src_pfx, src); + hash_val = pim_compute_ecmp_hash(&src_pfx, grp); + mod_val = hash_val % consider; + } + + for (nh_node = pnc->nexthop; nh_node && (found == 0); + nh_node = nh_node->next) { + first_ifindex = nh_node->ifindex; + ifp = ifps[nh_iter]; + if (!ifp) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s %s: could not find interface for ifindex %d (address %pPA(%s))", + __FILE__, __func__, first_ifindex, &src, + pim->vrf->name); + if (nh_iter == mod_val) + mod_val++; // Select nexthpath + nh_iter++; + continue; + } + + pim_ifp = ifp->info; + + if (!pim_ifp || !pim_ifp->pim_enable) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: pim not enabled on input interface %s(%s) (ifindex=%d, RPF for source %pPA)", + __func__, ifp->name, pim->vrf->name, + first_ifindex, &src); + if (nh_iter == mod_val) + mod_val++; // Select nexthpath + nh_iter++; + continue; + } + + if (neighbor_needed && !pim_if_connected_to_source(ifp, src)) { + nbr = nbrs[nh_iter]; + if (!nbr && !if_is_loopback(ifp)) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: pim nbr not found on input interface %s(%s)", + __func__, ifp->name, + pim->vrf->name); + if (nh_iter == mod_val) + mod_val++; // Select nexthpath + nh_iter++; + continue; + } + } + + if (nh_iter == mod_val) { + nexthop->interface = ifp; +#if PIM_IPV == 4 + nexthop->mrib_nexthop_addr = nh_node->gate.ipv4; +#else + nexthop->mrib_nexthop_addr = nh_node->gate.ipv6; +#endif + nexthop->mrib_metric_preference = pnc->distance; + nexthop->mrib_route_metric = pnc->metric; + nexthop->last_lookup = src; + nexthop->last_lookup_time = pim_time_monotonic_usec(); + nexthop->nbr = nbr; + found = 1; + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: (%pPA,%pPA)(%s) selected nhop interface %s addr %pPAs mod_val %u iter %d ecmp %d", + __func__, &src, &grp_addr, + pim->vrf->name, ifp->name, &nh_addr, + mod_val, nh_iter, pim->ecmp_enable); + } + nh_iter++; + } + + if (found) + return 1; + else + return 0; +} + +/* This API is used to parse Registered address nexthop update coming from Zebra + */ +void pim_nexthop_update(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr) +{ + struct nexthop *nexthop; + struct nexthop *nhlist_head = NULL; + struct nexthop *nhlist_tail = NULL; + int i; + struct pim_rpf rpf; + struct pim_nexthop_cache *pnc = NULL; + struct interface *ifp = NULL; + struct pim_instance *pim; + + pim = vrf->info; + + rpf.rpf_addr = pim_addr_from_prefix(match); + pnc = pim_nexthop_cache_find(pim, &rpf); + if (!pnc) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: Skipping NHT update, addr %pPA is not in local cached DB.", + __func__, &rpf.rpf_addr); + return; + } + + pnc->last_update = pim_time_monotonic_usec(); + + if (nhr->nexthop_num) { + pnc->nexthop_num = 0; + + for (i = 0; i < nhr->nexthop_num; i++) { + nexthop = nexthop_from_zapi_nexthop(&nhr->nexthops[i]); + switch (nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + /* + * Connected route (i.e. no nexthop), use + * RPF address from nexthop cache (i.e. + * destination) as PIM nexthop. + */ +#if PIM_IPV == 4 + nexthop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + nexthop->gate.ipv4 = pnc->rpf.rpf_addr; +#else + nexthop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + nexthop->gate.ipv6 = pnc->rpf.rpf_addr; +#endif + break; +#if PIM_IPV == 4 + /* RFC5549 IPv4-over-IPv6 nexthop handling: + * if we get an IPv6 nexthop in IPv4 PIM, hunt down a + * PIM neighbor and use that instead. + */ + case NEXTHOP_TYPE_IPV6_IFINDEX: { + struct interface *ifp1 = NULL; + struct pim_neighbor *nbr = NULL; + + ifp1 = if_lookup_by_index(nexthop->ifindex, + pim->vrf->vrf_id); + + if (!ifp1) + nbr = NULL; + else + /* FIXME: should really use nbr's + * secondary address list here + */ + nbr = pim_neighbor_find_if(ifp1); + + /* Overwrite with Nbr address as NH addr */ + if (nbr) + nexthop->gate.ipv4 = nbr->source_addr; + else + // Mark nexthop address to 0 until PIM + // Nbr is resolved. + nexthop->gate.ipv4 = PIMADDR_ANY; + + break; + } +#else + case NEXTHOP_TYPE_IPV6_IFINDEX: +#endif + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + /* nothing to do for the other nexthop types */ + break; + } + + ifp = if_lookup_by_index(nexthop->ifindex, + pim->vrf->vrf_id); + if (!ifp) { + if (PIM_DEBUG_PIM_NHT) { + char buf[NEXTHOP_STRLEN]; + zlog_debug( + "%s: could not find interface for ifindex %d(%s) (addr %s)", + __func__, nexthop->ifindex, + pim->vrf->name, + nexthop2str(nexthop, buf, + sizeof(buf))); + } + nexthop_free(nexthop); + continue; + } + + if (PIM_DEBUG_PIM_NHT) { +#if PIM_IPV == 4 + pim_addr nhaddr = nexthop->gate.ipv4; +#else + pim_addr nhaddr = nexthop->gate.ipv6; +#endif + zlog_debug("%s: NHT addr %pFX(%s) %d-nhop via %pPA(%s) type %d distance:%u metric:%u ", + __func__, match, pim->vrf->name, + i + 1, &nhaddr, ifp->name, + nexthop->type, nhr->distance, + nhr->metric); + } + + if (!ifp->info) { + /* + * Though Multicast is not enabled on this + * Interface store it in database otheriwse we + * may miss this update and this will not cause + * any issue, because while choosing the path we + * are ommitting the Interfaces which are not + * multicast enabled + */ + if (PIM_DEBUG_PIM_NHT) { + char buf[NEXTHOP_STRLEN]; + + zlog_debug( + "%s: multicast not enabled on input interface %s(%s) (ifindex=%d, addr %s)", + __func__, ifp->name, + pim->vrf->name, + nexthop->ifindex, + nexthop2str(nexthop, buf, + sizeof(buf))); + } + } + + if (nhlist_tail) { + nhlist_tail->next = nexthop; + nhlist_tail = nexthop; + } else { + nhlist_tail = nexthop; + nhlist_head = nexthop; + } + + // Keep track of all nexthops, even PIM-disabled ones. + pnc->nexthop_num++; + } + /* Reset existing pnc->nexthop before assigning new list */ + nexthops_free(pnc->nexthop); + pnc->nexthop = nhlist_head; + if (pnc->nexthop_num) { + pnc->flags |= PIM_NEXTHOP_VALID; + pnc->distance = nhr->distance; + pnc->metric = nhr->metric; + } + } else { + pnc->flags &= ~PIM_NEXTHOP_VALID; + pnc->nexthop_num = nhr->nexthop_num; + nexthops_free(pnc->nexthop); + pnc->nexthop = NULL; + } + SET_FLAG(pnc->flags, PIM_NEXTHOP_ANSWER_RECEIVED); + + if (PIM_DEBUG_PIM_NHT) + zlog_debug("%s: NHT Update for %pFX(%s) num_nh %d num_pim_nh %d vrf:%u up %ld rp %d", + __func__, match, pim->vrf->name, nhr->nexthop_num, + pnc->nexthop_num, vrf->vrf_id, + pnc->upstream_hash->count, listcount(pnc->rp_list)); + + pim_rpf_set_refresh_time(pim); + + if (listcount(pnc->rp_list)) + pim_update_rp_nh(pim, pnc); + if (pnc->upstream_hash->count) + pim_update_upstream_nh(pim, pnc); +} + +int pim_ecmp_nexthop_lookup(struct pim_instance *pim, + struct pim_nexthop *nexthop, pim_addr src, + struct prefix *grp, int neighbor_needed) +{ + struct pim_nexthop_cache *pnc; + struct pim_zlookup_nexthop nexthop_tab[router->multipath]; + struct pim_neighbor *nbrs[router->multipath], *nbr = NULL; + struct pim_rpf rpf; + int num_ifindex; + struct interface *ifps[router->multipath], *ifp; + int first_ifindex; + int found = 0; + uint8_t i = 0; + uint32_t hash_val = 0, mod_val = 0; + uint32_t num_nbrs = 0; + struct pim_interface *pim_ifp; + + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: Looking up: %pPA(%s), last lookup time: %lld", + __func__, &src, pim->vrf->name, + nexthop->last_lookup_time); + + rpf.rpf_addr = src; + + pnc = pim_nexthop_cache_find(pim, &rpf); + if (pnc) { + if (CHECK_FLAG(pnc->flags, PIM_NEXTHOP_ANSWER_RECEIVED)) + return pim_ecmp_nexthop_search(pim, pnc, nexthop, src, grp, + neighbor_needed); + } + + memset(nexthop_tab, 0, + sizeof(struct pim_zlookup_nexthop) * router->multipath); + num_ifindex = + zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, src, + PIM_NEXTHOP_LOOKUP_MAX); + if (num_ifindex < 1) { + if (PIM_DEBUG_PIM_NHT) + zlog_warn( + "%s: could not find nexthop ifindex for address %pPA(%s)", + __func__, &src, pim->vrf->name); + return 0; + } + + memset(&nbrs, 0, sizeof(nbrs)); + memset(&ifps, 0, sizeof(ifps)); + + /* + * Look up all interfaces and neighbors, + * store for later usage + */ + for (i = 0; i < num_ifindex; i++) { + ifps[i] = if_lookup_by_index(nexthop_tab[i].ifindex, + pim->vrf->vrf_id); + if (ifps[i]) { + nbrs[i] = pim_neighbor_find( + ifps[i], nexthop_tab[i].nexthop_addr, true); + + if (nbrs[i] || pim_if_connected_to_source(ifps[i], src)) + num_nbrs++; + } + } + + // If PIM ECMP enable then choose ECMP path. + if (pim->ecmp_enable) { + struct prefix src_pfx; + uint32_t consider = num_ifindex; + + if (neighbor_needed && num_nbrs < consider) + consider = num_nbrs; + + if (consider == 0) + return 0; + + pim_addr_to_prefix(&src_pfx, src); + hash_val = pim_compute_ecmp_hash(&src_pfx, grp); + mod_val = hash_val % consider; + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: hash_val %u mod_val %u", __func__, + hash_val, mod_val); + } + + i = 0; + while (!found && (i < num_ifindex)) { + first_ifindex = nexthop_tab[i].ifindex; + + ifp = ifps[i]; + if (!ifp) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s %s: could not find interface for ifindex %d (address %pPA(%s))", + __FILE__, __func__, first_ifindex, &src, + pim->vrf->name); + if (i == mod_val) + mod_val++; + i++; + continue; + } + + pim_ifp = ifp->info; + + if (!pim_ifp || !pim_ifp->pim_enable) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: pim not enabled on input interface %s(%s) (ifindex=%d, RPF for source %pPA)", + __func__, ifp->name, pim->vrf->name, + first_ifindex, &src); + if (i == mod_val) + mod_val++; + i++; + continue; + } + if (neighbor_needed && !pim_if_connected_to_source(ifp, src)) { + nbr = nbrs[i]; + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("ifp name: %s(%s), pim nbr: %p", + ifp->name, pim->vrf->name, nbr); + if (!nbr && !if_is_loopback(ifp)) { + if (i == mod_val) + mod_val++; + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: NBR (%pPA) not found on input interface %s(%s) (RPF for source %pPA)", + __func__, + &nexthop_tab[i].nexthop_addr, + ifp->name, pim->vrf->name, + &src); + i++; + continue; + } + } + + if (i == mod_val) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: found nhop %pPA for addr %pPA interface %s(%s) metric %d dist %d", + __func__, &nexthop_tab[i].nexthop_addr, + &src, ifp->name, pim->vrf->name, + nexthop_tab[i].route_metric, + nexthop_tab[i].protocol_distance); + /* update nexthop data */ + nexthop->interface = ifp; + nexthop->mrib_nexthop_addr = + nexthop_tab[i].nexthop_addr; + nexthop->mrib_metric_preference = + nexthop_tab[i].protocol_distance; + nexthop->mrib_route_metric = + nexthop_tab[i].route_metric; + nexthop->last_lookup = src; + nexthop->last_lookup_time = pim_time_monotonic_usec(); + nexthop->nbr = nbr; + found = 1; + } + i++; + } + + if (found) + return 1; + else + return 0; +} + +int pim_ecmp_fib_lookup_if_vif_index(struct pim_instance *pim, pim_addr src, + struct prefix *grp) +{ + struct pim_nexthop nhop; + int vif_index; + ifindex_t ifindex; + + memset(&nhop, 0, sizeof(nhop)); + if (!pim_ecmp_nexthop_lookup(pim, &nhop, src, grp, 1)) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: could not find nexthop ifindex for address %pPA(%s)", + __func__, &src, pim->vrf->name); + return -1; + } + + ifindex = nhop.interface->ifindex; + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: found nexthop ifindex=%d (interface %s(%s)) for address %pPA", + __func__, ifindex, + ifindex2ifname(ifindex, pim->vrf->vrf_id), + pim->vrf->name, &src); + + vif_index = pim_if_find_vifindex_by_ifindex(pim, ifindex); + + if (vif_index < 0) { + if (PIM_DEBUG_PIM_NHT) { + zlog_debug( + "%s: low vif_index=%d(%s) < 1 nexthop for address %pPA", + __func__, vif_index, pim->vrf->name, &src); + } + return -2; + } + + return vif_index; +} diff --git a/pimd/pim_nht.h b/pimd/pim_nht.h new file mode 100644 index 0000000..a1feb76 --- /dev/null +++ b/pimd/pim_nht.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2017 Cumulus Networks, Inc. + * Chirag Shah + */ +#ifndef PIM_NHT_H +#define PIM_NHT_H + +#include "prefix.h" +#include +#include "zclient.h" +#include "vrf.h" + +#include "pimd.h" +#include "pim_rp.h" +#include "pim_rpf.h" + +/* PIM nexthop cache value structure. */ +struct pim_nexthop_cache { + struct pim_rpf rpf; + /* IGP route's metric. */ + uint32_t metric; + uint32_t distance; + /* Nexthop number and nexthop linked list. */ + uint8_t nexthop_num; + struct nexthop *nexthop; + int64_t last_update; + uint16_t flags; +#define PIM_NEXTHOP_VALID (1 << 0) +#define PIM_NEXTHOP_ANSWER_RECEIVED (1 << 1) + + struct list *rp_list; + struct hash *upstream_hash; + + /* bsr_count won't currently go above 1 as we only have global_scope, + * but if anyone adds scope support multiple scopes may NHT-track the + * same BSR + */ + uint32_t bsr_count; +}; + +struct pnc_hash_walk_data { + struct pim_instance *pim; + struct interface *ifp; +}; + +void pim_nexthop_update(struct vrf *vrf, struct prefix *match, + struct zapi_route *nhr); +int pim_find_or_track_nexthop(struct pim_instance *pim, pim_addr addr, + struct pim_upstream *up, struct rp_info *rp, + struct pim_nexthop_cache *out_pnc); +void pim_delete_tracked_nexthop(struct pim_instance *pim, pim_addr addr, + struct pim_upstream *up, struct rp_info *rp); +struct pim_nexthop_cache *pim_nexthop_cache_find(struct pim_instance *pim, + struct pim_rpf *rpf); +uint32_t pim_compute_ecmp_hash(struct prefix *src, struct prefix *grp); +int pim_ecmp_nexthop_lookup(struct pim_instance *pim, + struct pim_nexthop *nexthop, pim_addr src, + struct prefix *grp, int neighbor_needed); +void pim_sendmsg_zebra_rnh(struct pim_instance *pim, struct zclient *zclient, + struct pim_nexthop_cache *pnc, int command); +int pim_ecmp_fib_lookup_if_vif_index(struct pim_instance *pim, pim_addr src, + struct prefix *grp); +void pim_rp_nexthop_del(struct rp_info *rp_info); + +/* for RPF check on BSM message receipt */ +void pim_nht_bsr_add(struct pim_instance *pim, pim_addr bsr_addr); +void pim_nht_bsr_del(struct pim_instance *pim, pim_addr bsr_addr); +/* RPF(bsr_addr) == src_ip%src_ifp? */ +bool pim_nht_bsr_rpf_check(struct pim_instance *pim, pim_addr bsr_addr, + struct interface *src_ifp, pim_addr src_ip); +void pim_upstream_nh_if_update(struct pim_instance *pim, struct interface *ifp); +#endif diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c new file mode 100644 index 0000000..d18406d --- /dev/null +++ b/pimd/pim_oil.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "memory.h" +#include "linklist.h" +#include "if.h" +#include "hash.h" +#include "jhash.h" + +#include "pimd.h" +#include "pim_oil.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_time.h" +#include "pim_vxlan.h" + +static void pim_channel_update_mute(struct channel_oil *c_oil); + +char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size) +{ + char *out; + struct interface *ifp; + pim_sgaddr sg; + int i; + + sg.src = *oil_origin(c_oil); + sg.grp = *oil_mcastgrp(c_oil); + ifp = pim_if_find_by_vif_index(c_oil->pim, *oil_incoming_vif(c_oil)); + snprintfrr(buf, size, "%pSG IIF: %s, OIFS: ", &sg, + ifp ? ifp->name : "(?)"); + + out = buf + strlen(buf); + for (i = 0; i < MAXVIFS; i++) { + if (oil_if_has(c_oil, i) != 0) { + ifp = pim_if_find_by_vif_index(c_oil->pim, i); + snprintf(out, buf + size - out, "%s ", + ifp ? ifp->name : "(?)"); + out += strlen(out); + } + } + + return buf; +} + +int pim_channel_oil_compare(const struct channel_oil *cc1, + const struct channel_oil *cc2) +{ + struct channel_oil *c1 = (struct channel_oil *)cc1; + struct channel_oil *c2 = (struct channel_oil *)cc2; + int rv; + + rv = pim_addr_cmp(*oil_mcastgrp(c1), *oil_mcastgrp(c2)); + if (rv) + return rv; + rv = pim_addr_cmp(*oil_origin(c1), *oil_origin(c2)); + if (rv) + return rv; + return 0; +} + +void pim_oil_init(struct pim_instance *pim) +{ + rb_pim_oil_init(&pim->channel_oil_head); +} + +void pim_oil_terminate(struct pim_instance *pim) +{ + struct channel_oil *c_oil; + + while ((c_oil = rb_pim_oil_pop(&pim->channel_oil_head))) + pim_channel_oil_free(c_oil); + + rb_pim_oil_fini(&pim->channel_oil_head); +} + +void pim_channel_oil_free(struct channel_oil *c_oil) +{ + XFREE(MTYPE_PIM_CHANNEL_OIL, c_oil); +} + +struct channel_oil *pim_find_channel_oil(struct pim_instance *pim, + pim_sgaddr *sg) +{ + struct channel_oil *c_oil = NULL; + struct channel_oil lookup; + + *oil_mcastgrp(&lookup) = sg->grp; + *oil_origin(&lookup) = sg->src; + + c_oil = rb_pim_oil_find(&pim->channel_oil_head, &lookup); + + return c_oil; +} + +struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, + pim_sgaddr *sg, const char *name) +{ + struct channel_oil *c_oil; + + c_oil = pim_find_channel_oil(pim, sg); + if (c_oil) { + ++c_oil->oil_ref_count; + + if (!c_oil->up) { + /* channel might be present prior to upstream */ + c_oil->up = pim_upstream_find( + pim, sg); + /* if the upstream entry is being anchored to an + * already existing channel OIL we need to re-evaluate + * the "Mute" state on AA OIFs + */ + pim_channel_update_mute(c_oil); + } + + /* check if the IIF has changed + * XXX - is this really needed + */ + pim_upstream_mroute_iif_update(c_oil, __func__); + + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s(%s): Existing oil for %pSG Ref Count: %d (Post Increment)", + __func__, name, sg, c_oil->oil_ref_count); + return c_oil; + } + + c_oil = XCALLOC(MTYPE_PIM_CHANNEL_OIL, sizeof(*c_oil)); + + *oil_mcastgrp(c_oil) = sg->grp; + *oil_origin(c_oil) = sg->src; + + *oil_incoming_vif(c_oil) = MAXVIFS; + c_oil->oil_ref_count = 1; + c_oil->installed = 0; + c_oil->up = pim_upstream_find(pim, sg); + c_oil->pim = pim; + + rb_pim_oil_add(&pim->channel_oil_head, c_oil); + + if (PIM_DEBUG_MROUTE) + zlog_debug("%s(%s): c_oil %pSG add", __func__, name, sg); + + return c_oil; +} + + +/* + * Clean up mroute and channel oil created for dropping pkts from directly + * connected source when the interface was non DR. + */ +void pim_clear_nocache_state(struct pim_interface *pim_ifp) +{ + struct channel_oil *c_oil; + + frr_each_safe (rb_pim_oil, &pim_ifp->pim->channel_oil_head, c_oil) { + + if ((!c_oil->up) || + !(PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(c_oil->up->flags))) + continue; + + if (*oil_incoming_vif(c_oil) != pim_ifp->mroute_vif_index) + continue; + + EVENT_OFF(c_oil->up->t_ka_timer); + PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(c_oil->up->flags); + PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(c_oil->up->flags); + pim_upstream_del(pim_ifp->pim, c_oil->up, __func__); + } +} + +struct channel_oil *pim_channel_oil_del(struct channel_oil *c_oil, + const char *name) +{ + if (PIM_DEBUG_MROUTE) { + pim_sgaddr sg = {.src = *oil_origin(c_oil), + .grp = *oil_mcastgrp(c_oil)}; + + zlog_debug( + "%s(%s): Del oil for %pSG, Ref Count: %d (Predecrement)", + __func__, name, &sg, c_oil->oil_ref_count); + } + --c_oil->oil_ref_count; + + if (c_oil->oil_ref_count < 1) { + /* + * notice that listnode_delete() can't be moved + * into pim_channel_oil_free() because the later is + * called by list_delete_all_node() + */ + c_oil->up = NULL; + rb_pim_oil_del(&c_oil->pim->channel_oil_head, c_oil); + + pim_channel_oil_free(c_oil); + return NULL; + } + + return c_oil; +} + +void pim_channel_oil_upstream_deref(struct channel_oil *c_oil) +{ + /* The upstream entry associated with a channel_oil is abt to be + * deleted. If the channel_oil is kept around because of other + * references we need to remove upstream based states out of it. + */ + c_oil = pim_channel_oil_del(c_oil, __func__); + if (c_oil) { + /* note: here we assume that c_oil->up has already been + * cleared + */ + pim_channel_update_mute(c_oil); + } +} + +int pim_channel_del_oif(struct channel_oil *channel_oil, struct interface *oif, + uint32_t proto_mask, const char *caller) +{ + struct pim_interface *pim_ifp; + + assert(channel_oil); + assert(oif); + + pim_ifp = oif->info; + + assertf(pim_ifp->mroute_vif_index >= 0, + "trying to del OIF %s with VIF (%d)", oif->name, + pim_ifp->mroute_vif_index); + + /* + * Don't do anything if we've been asked to remove a source + * that is not actually on it. + */ + if (!(channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: no existing protocol mask %u(%u) for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, + channel_oil + ->oif_flags[pim_ifp->mroute_vif_index], + oif->name, pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return 0; + } + + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~proto_mask; + + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & + PIM_OIF_FLAG_PROTO_ANY) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: other protocol masks remain for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return 0; + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, 0); + /* clear mute; will be re-evaluated when the OIF becomes valid again */ + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~PIM_OIF_FLAG_MUTE; + + if (pim_upstream_mroute_add(channel_oil, __func__)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: could not remove output interface %s (vif_index=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -1; + } + + --channel_oil->oil_size; + + if (PIM_DEBUG_MROUTE) { + struct interface *iifp = + pim_if_find_by_vif_index(pim_ifp->pim, + *oil_incoming_vif(channel_oil)); + + zlog_debug("%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u IIF:%s OIF=%s vif_index=%d", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), proto_mask, + iifp ? iifp->name : "Unknown", oif->name, + pim_ifp->mroute_vif_index); + } + + return 0; +} + +void pim_channel_del_inherited_oif(struct channel_oil *c_oil, + struct interface *oif, const char *caller) +{ + struct pim_upstream *up = c_oil->up; + + pim_channel_del_oif(c_oil, oif, PIM_OIF_FLAG_PROTO_STAR, + caller); + + /* if an inherited OIF is being removed join-desired can change + * if the inherited OIL is now empty and KAT is running + */ + if (up && !pim_addr_is_any(up->sg.src) && + pim_upstream_empty_inherited_olist(up)) + pim_upstream_update_join_desired(up->pim, up); +} + +static bool pim_channel_eval_oif_mute(struct channel_oil *c_oil, + struct pim_interface *pim_ifp) +{ + struct pim_interface *pim_reg_ifp; + struct pim_interface *vxlan_ifp; + bool do_mute = false; + struct pim_instance *pim = c_oil->pim; + + if (!c_oil->up) + return do_mute; + + pim_reg_ifp = pim->regiface->info; + if (pim_ifp == pim_reg_ifp) { + /* suppress pimreg in the OIL if the mroute is not supposed to + * trigger register encapsulated data + */ + if (PIM_UPSTREAM_FLAG_TEST_NO_PIMREG_DATA(c_oil->up->flags)) + do_mute = true; + + return do_mute; + } + + vxlan_ifp = pim_vxlan_get_term_ifp(pim); + if (pim_ifp == vxlan_ifp) { + /* 1. vxlan termination device must never be added to the + * origination mroute (and that can actually happen because + * of XG inheritance from the termination mroute) otherwise + * traffic will end up looping. + * PS: This check has also been extended to non-orig mroutes + * that have a local SIP as such mroutes can move back and + * forth between orig<=>non-orig type. + * 2. vxlan termination device should be removed from the non-DF + * to prevent duplicates to the overlay rxer + */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(c_oil->up->flags) || + PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(c_oil->up->flags) || + pim_vxlan_is_local_sip(c_oil->up)) + do_mute = true; + + return do_mute; + } + + if (PIM_I_am_DualActive(pim_ifp)) { + struct pim_upstream *starup = c_oil->up->parent; + if (PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(c_oil->up->flags) + && (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(c_oil->up->flags))) + do_mute = true; + + /* In case entry is (S,G), Negotiation happens at (*.G) */ + if (starup + + && PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(starup->flags) + && (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(starup->flags))) + do_mute = true; + return do_mute; + } + return do_mute; +} + +void pim_channel_update_oif_mute(struct channel_oil *c_oil, + struct pim_interface *pim_ifp) +{ + bool old_mute; + bool new_mute; + + /* If pim_ifp is not a part of the OIL there is nothing to do */ + if (!oil_if_has(c_oil, pim_ifp->mroute_vif_index)) + return; + + old_mute = !!(c_oil->oif_flags[pim_ifp->mroute_vif_index] & + PIM_OIF_FLAG_MUTE); + new_mute = pim_channel_eval_oif_mute(c_oil, pim_ifp); + if (old_mute == new_mute) + return; + + if (new_mute) + c_oil->oif_flags[pim_ifp->mroute_vif_index] |= + PIM_OIF_FLAG_MUTE; + else + c_oil->oif_flags[pim_ifp->mroute_vif_index] &= + ~PIM_OIF_FLAG_MUTE; + + pim_upstream_mroute_add(c_oil, __func__); +} + +/* pim_upstream has been set or cleared on the c_oil. re-eval mute state + * on all existing OIFs + */ +static void pim_channel_update_mute(struct channel_oil *c_oil) +{ + struct pim_interface *pim_reg_ifp; + struct pim_interface *vxlan_ifp; + + if (c_oil->pim->regiface) { + pim_reg_ifp = c_oil->pim->regiface->info; + if (pim_reg_ifp) + pim_channel_update_oif_mute(c_oil, pim_reg_ifp); + } + vxlan_ifp = pim_vxlan_get_term_ifp(c_oil->pim); + if (vxlan_ifp) + pim_channel_update_oif_mute(c_oil, vxlan_ifp); +} + +int pim_channel_add_oif(struct channel_oil *channel_oil, struct interface *oif, + uint32_t proto_mask, const char *caller) +{ + struct pim_interface *pim_ifp; + int old_ttl; + + /* + * If we've gotten here we've gone bad, but let's + * not take down pim + */ + if (!channel_oil) { + zlog_warn("Attempt to Add OIF for non-existent channel oil"); + return -1; + } + + pim_ifp = oif->info; + + assertf(pim_ifp->mroute_vif_index >= 0, + "trying to add OIF %s with VIF (%d)", oif->name, + pim_ifp->mroute_vif_index); + + /* Prevent single protocol from subscribing same interface to + channel (S,G) multiple times */ + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask) { + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: existing protocol mask %u requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -3; + } + + /* Allow other protocol to request subscription of same interface to + * channel (S,G), we need to note this information + */ + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] + & PIM_OIF_FLAG_PROTO_ANY) { + + /* Updating time here is not required as this time has to + * indicate when the interface is added + */ + + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + /* Check the OIF really exists before returning, and only log + warning otherwise */ + if (oil_if_has(channel_oil, pim_ifp->mroute_vif_index) < 1) { + zlog_warn( + "%s %s: new protocol mask %u requested nonexistent OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u OIF=%s vif_index=%d added to 0x%x", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), + proto_mask, oif->name, + pim_ifp->mroute_vif_index, + channel_oil + ->oif_flags[pim_ifp->mroute_vif_index]); + } + return 0; + } + + old_ttl = oil_if_has(channel_oil, pim_ifp->mroute_vif_index); + + if (old_ttl > 0) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: interface %s (vif_index=%d) is existing output for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -4; + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, PIM_MROUTE_MIN_TTL); + + /* Some OIFs are held in a muted state i.e. the PIM state machine + * decided to include the OIF but additional status check such as + * MLAG DF role prevent it from being activated for traffic + * forwarding. + */ + if (pim_channel_eval_oif_mute(channel_oil, pim_ifp)) + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= + PIM_OIF_FLAG_MUTE; + else + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= + ~PIM_OIF_FLAG_MUTE; + + /* channel_oil->oil.mfcc_parent != MAXVIFS indicate this entry is not + * valid to get installed in kernel. + */ + if (*oil_incoming_vif(channel_oil) != MAXVIFS) { + if (pim_upstream_mroute_add(channel_oil, __func__)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: could not add output interface %s (vif_index=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, + old_ttl); + return -5; + } + } + + channel_oil->oif_creation[pim_ifp->mroute_vif_index] = + pim_time_monotonic_sec(); + ++channel_oil->oil_size; + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u OIF=%s vif_index=%d: DONE", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), + proto_mask, + oif->name, pim_ifp->mroute_vif_index); + } + + return 0; +} + +int pim_channel_oil_empty(struct channel_oil *c_oil) +{ + static struct channel_oil null_oil; + + if (!c_oil) + return 1; + + /* exclude pimreg from the OIL when checking if the inherited_oil is + * non-NULL. + * pimreg device (in all vrfs) uses a vifi of + * 0 (PIM_OIF_PIM_REGISTER_VIF) so we simply mfcc_ttls[0] */ + if (oil_if_has(c_oil, 0)) + oil_if_set(&null_oil, 0, 1); + else + oil_if_set(&null_oil, 0, 0); + + return !oil_if_cmp(&c_oil->oil, &null_oil.oil); +} diff --git a/pimd/pim_oil.h b/pimd/pim_oil.h new file mode 100644 index 0000000..6a52227 --- /dev/null +++ b/pimd/pim_oil.h @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_OIL_H +#define PIM_OIL_H + +struct pim_interface; + +#include "pim_mroute.h" + +/* + * Where did we get this (S,G) from? + * + * GM - Learned from IGMP/MLD + * PIM - Learned from PIM + * SOURCE - Learned from Source multicast packet received + * STAR - Inherited + */ +#define PIM_OIF_FLAG_PROTO_GM (1 << 0) +#define PIM_OIF_FLAG_PROTO_PIM (1 << 1) +#define PIM_OIF_FLAG_PROTO_STAR (1 << 2) +#define PIM_OIF_FLAG_PROTO_VXLAN (1 << 3) +#define PIM_OIF_FLAG_PROTO_ANY \ + (PIM_OIF_FLAG_PROTO_GM | PIM_OIF_FLAG_PROTO_PIM | \ + PIM_OIF_FLAG_PROTO_STAR | PIM_OIF_FLAG_PROTO_VXLAN) + +/* OIF is present in the OIL but must not be used for forwarding traffic */ +#define PIM_OIF_FLAG_MUTE (1 << 4) +/* + * We need a pimreg vif id from the kernel. + * Since ifindex == vif id for most cases and the number + * of expected interfaces is at most 100, using MAXVIFS -1 + * is probably ok. + * Don't come running to me if this assumption is bad, + * fix it. + */ +#define PIM_OIF_PIM_REGISTER_VIF 0 +#define PIM_MAX_USABLE_VIFS (MAXVIFS - 1) + +struct channel_counts { + unsigned long long lastused; + unsigned long origpktcnt; + unsigned long pktcnt; + unsigned long oldpktcnt; + unsigned long origbytecnt; + unsigned long bytecnt; + unsigned long oldbytecnt; + unsigned long origwrong_if; + unsigned long wrong_if; + unsigned long oldwrong_if; +}; + +/* + qpim_channel_oil_list holds a list of struct channel_oil. + + Each channel_oil.oil is used to control an (S,G) entry in the Kernel + Multicast Forwarding Cache. + + There is a case when we create a channel_oil but don't install in the kernel + + Case where (S, G) entry not installed in the kernel: + FRR receives IGMP/PIM (*, G) join and RP is not configured or + not-reachable, then create a channel_oil for the group G with the incoming + interface(channel_oil.oil.mfcc_parent) as invalid i.e "MAXVIF" and populate + the outgoing interface where join is received. Keep this entry in the stack, + but don't install in the kernel(channel_oil.installed = 0). + + Case where (S, G) entry installed in the kernel: + When RP is configured and is reachable for the group G, and receiving a + join if channel_oil is already present then populate the incoming interface + and install the entry in the kernel, if channel_oil not present, then create + a new_channel oil(channel_oil.installed = 1). + + is_valid: indicate if this entry is valid to get installed in kernel. + installed: indicate if this entry is installed in the kernel. + +*/ +PREDECL_RBTREE_UNIQ(rb_pim_oil); + +struct channel_oil { + struct pim_instance *pim; + + struct rb_pim_oil_item oil_rb; + +#if PIM_IPV == 4 + struct mfcctl oil; +#else + struct mf6cctl oil; +#endif + int installed; + int oil_inherited_rescan; + int oil_size; + int oil_ref_count; + time_t oif_creation[MAXVIFS]; + uint32_t oif_flags[MAXVIFS]; + struct channel_counts cc; + struct pim_upstream *up; + time_t mroute_creation; +}; + +#if PIM_IPV == 4 +static inline pim_addr *oil_origin(struct channel_oil *c_oil) +{ + return &c_oil->oil.mfcc_origin; +} + +static inline pim_addr *oil_mcastgrp(struct channel_oil *c_oil) +{ + return &c_oil->oil.mfcc_mcastgrp; +} + +static inline vifi_t *oil_incoming_vif(struct channel_oil *c_oil) +{ + return &c_oil->oil.mfcc_parent; +} + +static inline bool oil_if_has(struct channel_oil *c_oil, vifi_t ifi) +{ + return !!c_oil->oil.mfcc_ttls[ifi]; +} + +static inline void oil_if_set(struct channel_oil *c_oil, vifi_t ifi, uint8_t set) +{ + c_oil->oil.mfcc_ttls[ifi] = set; +} + +static inline int oil_if_cmp(struct mfcctl *oil1, struct mfcctl *oil2) +{ + return memcmp(&oil1->mfcc_ttls[0], &oil2->mfcc_ttls[0], + sizeof(oil1->mfcc_ttls)); +} +#else +static inline pim_addr *oil_origin(struct channel_oil *c_oil) +{ + return &c_oil->oil.mf6cc_origin.sin6_addr; +} + +static inline pim_addr *oil_mcastgrp(struct channel_oil *c_oil) +{ + return &c_oil->oil.mf6cc_mcastgrp.sin6_addr; +} + +static inline mifi_t *oil_incoming_vif(struct channel_oil *c_oil) +{ + return &c_oil->oil.mf6cc_parent; +} + +static inline bool oil_if_has(struct channel_oil *c_oil, mifi_t ifi) +{ + return !!IF_ISSET(ifi, &c_oil->oil.mf6cc_ifset); +} + +static inline void oil_if_set(struct channel_oil *c_oil, mifi_t ifi, + uint8_t set) +{ + if (set) + IF_SET(ifi, &c_oil->oil.mf6cc_ifset); + else + IF_CLR(ifi, &c_oil->oil.mf6cc_ifset); +} + +static inline int oil_if_cmp(struct mf6cctl *oil1, struct mf6cctl *oil2) +{ + return memcmp(&oil1->mf6cc_ifset, &oil2->mf6cc_ifset, + sizeof(oil1->mf6cc_ifset)); +} +#endif + +extern int pim_channel_oil_compare(const struct channel_oil *c1, + const struct channel_oil *c2); +DECLARE_RBTREE_UNIQ(rb_pim_oil, struct channel_oil, oil_rb, + pim_channel_oil_compare); + +void pim_oil_init(struct pim_instance *pim); +void pim_oil_terminate(struct pim_instance *pim); + +void pim_channel_oil_free(struct channel_oil *c_oil); +struct channel_oil *pim_find_channel_oil(struct pim_instance *pim, + pim_sgaddr *sg); +struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, + pim_sgaddr *sg, const char *name); +void pim_clear_nocache_state(struct pim_interface *pim_ifp); +struct channel_oil *pim_channel_oil_del(struct channel_oil *c_oil, + const char *name); + +int pim_channel_add_oif(struct channel_oil *c_oil, struct interface *oif, + uint32_t proto_mask, const char *caller); +int pim_channel_del_oif(struct channel_oil *c_oil, struct interface *oif, + uint32_t proto_mask, const char *caller); + +int pim_channel_oil_empty(struct channel_oil *c_oil); + +char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size); + +void pim_channel_update_oif_mute(struct channel_oil *c_oil, + struct pim_interface *pim_ifp); + +void pim_channel_oil_upstream_deref(struct channel_oil *c_oil); +void pim_channel_del_inherited_oif(struct channel_oil *c_oil, + struct interface *oif, const char *caller); + +#endif /* PIM_OIL_H */ diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c new file mode 100644 index 0000000..1bc265b --- /dev/null +++ b/pimd/pim_pim.c @@ -0,0 +1,996 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "frrevent.h" +#include "memory.h" +#include "if.h" +#include "network.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_pim.h" +#include "pim_time.h" +#include "pim_iface.h" +#include "pim_sock.h" +#include "pim_str.h" +#include "pim_util.h" +#include "pim_tlv.h" +#include "pim_neighbor.h" +#include "pim_hello.h" +#include "pim_join.h" +#include "pim_assert.h" +#include "pim_msg.h" +#include "pim_register.h" +#include "pim_errors.h" +#include "pim_bsm.h" +#include + +static void on_pim_hello_send(struct event *t); + +static const char *pim_pim_msgtype2str(enum pim_msg_type type) +{ + switch (type) { + case PIM_MSG_TYPE_HELLO: + return "HELLO"; + case PIM_MSG_TYPE_REGISTER: + return "REGISTER"; + case PIM_MSG_TYPE_REG_STOP: + return "REGSTOP"; + case PIM_MSG_TYPE_JOIN_PRUNE: + return "JOINPRUNE"; + case PIM_MSG_TYPE_BOOTSTRAP: + return "BOOT"; + case PIM_MSG_TYPE_ASSERT: + return "ASSERT"; + case PIM_MSG_TYPE_GRAFT: + return "GRAFT"; + case PIM_MSG_TYPE_GRAFT_ACK: + return "GACK"; + case PIM_MSG_TYPE_CANDIDATE: + return "CANDIDATE"; + } + + return "UNKNOWN"; +} + +static void sock_close(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (PIM_DEBUG_PIM_TRACE) { + if (pim_ifp->t_pim_sock_read) { + zlog_debug( + "Cancelling READ event for PIM socket fd=%d on interface %s", + pim_ifp->pim_sock_fd, ifp->name); + } + } + EVENT_OFF(pim_ifp->t_pim_sock_read); + + if (PIM_DEBUG_PIM_TRACE) { + if (pim_ifp->t_pim_hello_timer) { + zlog_debug( + "Cancelling PIM hello timer for interface %s", + ifp->name); + } + } + EVENT_OFF(pim_ifp->t_pim_hello_timer); + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("Deleting PIM socket fd=%d on interface %s", + pim_ifp->pim_sock_fd, ifp->name); + } + + /* + * If the fd is already deleted no need to do anything here + */ + if (pim_ifp->pim_sock_fd > 0 && close(pim_ifp->pim_sock_fd)) { + zlog_warn( + "Failure closing PIM socket fd=%d on interface %s: errno=%d: %s", + pim_ifp->pim_sock_fd, ifp->name, errno, + safe_strerror(errno)); + } + + pim_ifp->pim_sock_fd = -1; + pim_ifp->pim_sock_creation = 0; +} + +void pim_sock_delete(struct interface *ifp, const char *delete_message) +{ + zlog_info("PIM INTERFACE DOWN: on interface %s: %s", ifp->name, + delete_message); + + if (!ifp->info) { + flog_err(EC_PIM_CONFIG, + "%s: %s: but PIM not enabled on interface %s (!)", + __func__, delete_message, ifp->name); + return; + } + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + Before an interface goes down or changes primary IP address, a Hello + message with a zero HoldTime should be sent immediately (with the + old IP address if the IP address changed). + */ + pim_hello_send(ifp, 0 /* zero-sec holdtime */); + + pim_neighbor_delete_all(ifp, delete_message); + + sock_close(ifp); +} + +/* For now check dst address for hello, assrt and join/prune is all pim rtr */ +static bool pim_pkt_dst_addr_ok(enum pim_msg_type type, pim_addr addr) +{ + if ((type == PIM_MSG_TYPE_HELLO) || (type == PIM_MSG_TYPE_ASSERT) + || (type == PIM_MSG_TYPE_JOIN_PRUNE)) { + if (pim_addr_cmp(addr, qpim_all_pim_routers_addr)) + return false; + } + + return true; +} + +int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, + pim_sgaddr sg) +{ + struct iovec iov[2], *iovp = iov; +#if PIM_IPV == 4 + struct ip *ip_hdr = (struct ip *)buf; + size_t ip_hlen; /* ip header length in bytes */ +#endif + uint8_t *pim_msg; + uint32_t pim_msg_len = 0; + uint16_t pim_checksum; /* received checksum */ + uint16_t checksum; /* computed checksum */ + struct pim_neighbor *neigh; + struct pim_msg_header *header; + bool no_fwd; + +#if PIM_IPV == 4 + if (len <= sizeof(*ip_hdr)) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "PIM packet size=%zu shorter than minimum=%zu", + len, sizeof(*ip_hdr)); + return -1; + } + + ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */ + sg = pim_sgaddr_from_iphdr(ip_hdr); + + pim_msg = buf + ip_hlen; + pim_msg_len = len - ip_hlen; +#else + struct ipv6_ph phdr = { + .src = sg.src, + .dst = sg.grp, + .ulpl = htonl(len), + .next_hdr = IPPROTO_PIM, + }; + + iovp->iov_base = &phdr; + iovp->iov_len = sizeof(phdr); + iovp++; + + /* NB: header is not included in IPv6 RX */ + pim_msg = buf; + pim_msg_len = len; +#endif + + iovp->iov_base = pim_msg; + iovp->iov_len = pim_msg_len; + iovp++; + + if (pim_msg_len < PIM_PIM_MIN_LEN) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "PIM message size=%d shorter than minimum=%d", + pim_msg_len, PIM_PIM_MIN_LEN); + return -1; + } + header = (struct pim_msg_header *)pim_msg; + + if (header->ver != PIM_PROTO_VERSION) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "Ignoring PIM pkt from %s with unsupported version: %d", + ifp->name, header->ver); + return -1; + } + + /* save received checksum */ + pim_checksum = header->checksum; + + /* for computing checksum */ + header->checksum = 0; + no_fwd = header->Nbit; + + if (header->type == PIM_MSG_TYPE_REGISTER) { + if (pim_msg_len < PIM_MSG_REGISTER_LEN) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("PIM Register Message size=%d shorther than min length %d", + pim_msg_len, PIM_MSG_REGISTER_LEN); + return -1; + } + +#if PIM_IPV == 6 + phdr.ulpl = htonl(PIM_MSG_REGISTER_LEN); +#endif + /* First 8 byte header checksum */ + iovp[-1].iov_len = PIM_MSG_REGISTER_LEN; + checksum = in_cksumv(iov, iovp - iov); + + if (checksum != pim_checksum) { +#if PIM_IPV == 6 + phdr.ulpl = htonl(pim_msg_len); +#endif + iovp[-1].iov_len = pim_msg_len; + + checksum = in_cksumv(iov, iovp - iov); + if (checksum != pim_checksum) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x", + ifp->name, pim_checksum, + checksum); + + return -1; + } + } + } else { + checksum = in_cksumv(iov, iovp - iov); + if (checksum != pim_checksum) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x", + ifp->name, pim_checksum, checksum); + + return -1; + } + } + + if (PIM_DEBUG_PIM_PACKETS) { + zlog_debug( + "Recv PIM %s packet from %pPA to %pPA on %s: pim_version=%d pim_msg_size=%d checksum=%x", + pim_pim_msgtype2str(header->type), &sg.src, &sg.grp, + ifp->name, header->ver, pim_msg_len, checksum); + if (PIM_DEBUG_PIM_PACKETDUMP_RECV) + pim_pkt_dump(__func__, pim_msg, pim_msg_len); + } + + if (!pim_pkt_dst_addr_ok(header->type, sg.grp)) { + zlog_warn( + "%s: Ignoring Pkt. Unexpected IP destination %pPA for %s (Expected: all_pim_routers_addr) from %pPA", + __func__, &sg.grp, pim_pim_msgtype2str(header->type), + &sg.src); + return -1; + } + + switch (header->type) { + case PIM_MSG_TYPE_HELLO: + return pim_hello_recv(ifp, sg.src, pim_msg + PIM_MSG_HEADER_LEN, + pim_msg_len - PIM_MSG_HEADER_LEN); + break; + case PIM_MSG_TYPE_REGISTER: + return pim_register_recv(ifp, sg.grp, sg.src, + pim_msg + PIM_MSG_HEADER_LEN, + pim_msg_len - PIM_MSG_HEADER_LEN); + break; + case PIM_MSG_TYPE_REG_STOP: + return pim_register_stop_recv(ifp, pim_msg + PIM_MSG_HEADER_LEN, + pim_msg_len - PIM_MSG_HEADER_LEN); + break; + case PIM_MSG_TYPE_JOIN_PRUNE: + neigh = pim_neighbor_find(ifp, sg.src, false); + if (!neigh) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "%s %s: non-hello PIM message type=%d from non-neighbor %pPA on %s", + __FILE__, __func__, header->type, + &sg.src, ifp->name); + return -1; + } + pim_neighbor_timer_reset(neigh, neigh->holdtime); + return pim_joinprune_recv(ifp, neigh, sg.src, + pim_msg + PIM_MSG_HEADER_LEN, + pim_msg_len - PIM_MSG_HEADER_LEN); + break; + case PIM_MSG_TYPE_ASSERT: + neigh = pim_neighbor_find(ifp, sg.src, false); + if (!neigh) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "%s %s: non-hello PIM message type=%d from non-neighbor %pPA on %s", + __FILE__, __func__, header->type, + &sg.src, ifp->name); + return -1; + } + pim_neighbor_timer_reset(neigh, neigh->holdtime); + return pim_assert_recv(ifp, neigh, sg.src, + pim_msg + PIM_MSG_HEADER_LEN, + pim_msg_len - PIM_MSG_HEADER_LEN); + break; + case PIM_MSG_TYPE_BOOTSTRAP: + return pim_bsm_process(ifp, &sg, pim_msg, pim_msg_len, no_fwd); + break; + + default: + if (PIM_DEBUG_PIM_PACKETS) { + zlog_debug( + "Recv PIM packet type %d which is not currently understood", + header->type); + } + return -1; + } +} + +static void pim_sock_read_on(struct interface *ifp); + +static void pim_sock_read(struct event *t) +{ + struct interface *ifp, *orig_ifp; + struct pim_interface *pim_ifp; + int fd; + struct sockaddr_storage from; + struct sockaddr_storage to; + socklen_t fromlen = sizeof(from); + socklen_t tolen = sizeof(to); + uint8_t buf[PIM_PIM_BUFSIZE_READ]; + int len; + ifindex_t ifindex = -1; + int result = -1; /* defaults to bad */ + static long long count = 0; + int cont = 1; + + orig_ifp = ifp = EVENT_ARG(t); + fd = EVENT_FD(t); + + pim_ifp = ifp->info; + + while (cont) { + pim_sgaddr sg; + + len = pim_socket_recvfromto(fd, buf, sizeof(buf), &from, + &fromlen, &to, &tolen, &ifindex); + if (len < 0) { + if (errno == EINTR) + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("Received errno: %d %s", errno, + safe_strerror(errno)); + goto done; + } + + /* + * What? So with vrf's the incoming packet is received + * on the vrf interface but recvfromto above returns + * the right ifindex, so just use it. We know + * it's the right interface because we bind to it + */ + ifp = if_lookup_by_index(ifindex, pim_ifp->pim->vrf->vrf_id); + if (!ifp || !ifp->info) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "%s: Received incoming pim packet on interface(%s:%d) not yet configured for pim", + __func__, ifp ? ifp->name : "Unknown", + ifindex); + goto done; + } +#if PIM_IPV == 4 + sg.src = ((struct sockaddr_in *)&from)->sin_addr; + sg.grp = ((struct sockaddr_in *)&to)->sin_addr; +#else + sg.src = ((struct sockaddr_in6 *)&from)->sin6_addr; + sg.grp = ((struct sockaddr_in6 *)&to)->sin6_addr; +#endif + + int fail = pim_pim_packet(ifp, buf, len, sg); + if (fail) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: pim_pim_packet() return=%d", + __func__, fail); + goto done; + } + + count++; + if (count % router->packet_process == 0) + cont = 0; + } + + result = 0; /* good */ + +done: + pim_sock_read_on(orig_ifp); + + if (result) { + ++pim_ifp->pim_ifstat_hello_recvfail; + } +} + +static void pim_sock_read_on(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + assert(ifp->info); + + pim_ifp = ifp->info; + + if (PIM_DEBUG_PIM_TRACE_DETAIL) { + zlog_debug("Scheduling READ event on PIM socket fd=%d", + pim_ifp->pim_sock_fd); + } + event_add_read(router->master, pim_sock_read, ifp, pim_ifp->pim_sock_fd, + &pim_ifp->t_pim_sock_read); +} + +static int pim_sock_open(struct interface *ifp) +{ + int fd; + struct pim_interface *pim_ifp = ifp->info; + + fd = pim_socket_mcast(IPPROTO_PIM, pim_ifp->primary_address, ifp, + 0 /* loop=false */); + if (fd < 0) + return -1; + + if (pim_socket_join(fd, qpim_all_pim_routers_addr, + pim_ifp->primary_address, ifp->ifindex, pim_ifp)) { + close(fd); + return -2; + } + + return fd; +} + +void pim_ifstat_reset(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + + pim_ifp = ifp->info; + if (!pim_ifp) { + return; + } + + pim_ifp->pim_ifstat_start = pim_time_monotonic_sec(); + pim_ifp->pim_ifstat_hello_sent = 0; + pim_ifp->pim_ifstat_hello_sendfail = 0; + pim_ifp->pim_ifstat_hello_recv = 0; + pim_ifp->pim_ifstat_hello_recvfail = 0; + pim_ifp->pim_ifstat_bsm_rx = 0; + pim_ifp->pim_ifstat_bsm_tx = 0; + pim_ifp->pim_ifstat_join_recv = 0; + pim_ifp->pim_ifstat_join_send = 0; + pim_ifp->pim_ifstat_prune_recv = 0; + pim_ifp->pim_ifstat_prune_send = 0; + pim_ifp->pim_ifstat_reg_recv = 0; + pim_ifp->pim_ifstat_reg_send = 0; + pim_ifp->pim_ifstat_reg_stop_recv = 0; + pim_ifp->pim_ifstat_reg_stop_send = 0; + pim_ifp->pim_ifstat_assert_recv = 0; + pim_ifp->pim_ifstat_assert_send = 0; + pim_ifp->pim_ifstat_bsm_cfg_miss = 0; + pim_ifp->pim_ifstat_ucast_bsm_cfg_miss = 0; + pim_ifp->pim_ifstat_bsm_invalid_sz = 0; + pim_ifp->igmp_ifstat_joins_sent = 0; + pim_ifp->igmp_ifstat_joins_failed = 0; + pim_ifp->igmp_peak_group_count = 0; +} + +void pim_sock_reset(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + assert(ifp); + assert(ifp->info); + + pim_ifp = ifp->info; + + pim_ifp->primary_address = pim_find_primary_addr(ifp); + + pim_ifp->pim_sock_fd = -1; + pim_ifp->pim_sock_creation = 0; + pim_ifp->t_pim_sock_read = NULL; + + pim_ifp->t_pim_hello_timer = NULL; + pim_ifp->pim_hello_period = PIM_DEFAULT_HELLO_PERIOD; + pim_ifp->pim_default_holdtime = + -1; /* unset: means 3.5 * pim_hello_period */ + pim_ifp->pim_triggered_hello_delay = PIM_DEFAULT_TRIGGERED_HELLO_DELAY; + pim_ifp->pim_dr_priority = PIM_DEFAULT_DR_PRIORITY; + pim_ifp->pim_propagation_delay_msec = + PIM_DEFAULT_PROPAGATION_DELAY_MSEC; + pim_ifp->pim_override_interval_msec = + PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC; + pim_ifp->pim_can_disable_join_suppression = + PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION; + + /* neighbors without lan_delay */ + pim_ifp->pim_number_of_nonlandelay_neighbors = 0; + pim_ifp->pim_neighbors_highest_propagation_delay_msec = 0; + pim_ifp->pim_neighbors_highest_override_interval_msec = 0; + + /* DR Election */ + pim_ifp->pim_dr_election_last = 0; /* timestamp */ + pim_ifp->pim_dr_election_count = 0; + pim_ifp->pim_dr_election_changes = 0; + pim_ifp->pim_dr_num_nondrpri_neighbors = + 0; /* neighbors without dr_pri */ + pim_ifp->pim_dr_addr = pim_ifp->primary_address; + pim_ifp->am_i_dr = true; + + pim_ifstat_reset(ifp); +} + +#if PIM_IPV == 4 +static uint16_t ip_id = 0; +#endif + +#if PIM_IPV == 4 +static int pim_msg_send_frame(int fd, char *buf, size_t len, + struct sockaddr *dst, size_t salen, + const char *ifname) +{ + if (sendto(fd, buf, len, MSG_DONTWAIT, dst, salen) >= 0) + return 0; + + if (errno == EMSGSIZE) { + struct ip *ip = (struct ip *)buf; + size_t hdrsize = sizeof(struct ip); + size_t newlen1 = ((len - hdrsize) / 2) & 0xFFF8; + size_t sendlen = newlen1 + hdrsize; + size_t offset = ntohs(ip->ip_off); + int ret; + + ip->ip_len = htons(sendlen); + ip->ip_off = htons(offset | IP_MF); + + ret = pim_msg_send_frame(fd, buf, sendlen, dst, salen, ifname); + if (ret) + return ret; + + struct ip *ip2 = (struct ip *)(buf + newlen1); + size_t newlen2 = len - sendlen; + + sendlen = newlen2 + hdrsize; + + memcpy(ip2, ip, hdrsize); + ip2->ip_len = htons(sendlen); + ip2->ip_off = htons(offset + (newlen1 >> 3)); + return pim_msg_send_frame(fd, (char *)ip2, sendlen, dst, salen, + ifname); + } + + zlog_warn( + "%s: sendto() failure to %pSU: iface=%s fd=%d msg_size=%zd: %m", + __func__, dst, ifname, fd, len); + return -1; +} + +#else +static int pim_msg_send_frame(pim_addr src, pim_addr dst, ifindex_t ifindex, + struct iovec *message, int fd) +{ + int retval; + struct msghdr smsghdr = {}; + struct cmsghdr *scmsgp; + union cmsgbuf { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + }; + struct in6_pktinfo *pktinfo; + struct sockaddr_in6 dst_sin6 = {}; + + union cmsgbuf cmsg_buf = {}; + + /* destination address */ + dst_sin6.sin6_family = AF_INET6; +#ifdef SIN6_LEN + dst_sin6.sin6_len = sizeof(struct sockaddr_in6); +#endif /*SIN6_LEN*/ + dst_sin6.sin6_addr = dst; + dst_sin6.sin6_scope_id = ifindex; + + /* send msg hdr */ + smsghdr.msg_iov = message; + smsghdr.msg_iovlen = 1; + smsghdr.msg_name = (caddr_t)&dst_sin6; + smsghdr.msg_namelen = sizeof(dst_sin6); + smsghdr.msg_control = (caddr_t)&cmsg_buf.buf; + smsghdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + smsghdr.msg_flags = 0; + + scmsgp = CMSG_FIRSTHDR(&smsghdr); + scmsgp->cmsg_level = IPPROTO_IPV6; + scmsgp->cmsg_type = IPV6_PKTINFO; + scmsgp->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + + pktinfo = (struct in6_pktinfo *)(CMSG_DATA(scmsgp)); + pktinfo->ipi6_ifindex = ifindex; + pktinfo->ipi6_addr = src; + + retval = sendmsg(fd, &smsghdr, 0); + if (retval < 0) + flog_err( + EC_LIB_SOCKET, + "sendmsg failed: source: %pI6 Dest: %pI6 ifindex: %d: %s (%d)", + &src, &dst, ifindex, safe_strerror(errno), errno); + + return retval; +} +#endif + +int pim_msg_send(int fd, pim_addr src, pim_addr dst, uint8_t *pim_msg, + int pim_msg_size, struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + + pim_ifp = ifp->info; + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip sending PIM message on passive interface %s", + ifp->name); + return 0; + } + +#if PIM_IPV == 4 + uint8_t ttl; + struct pim_msg_header *header; + unsigned char buffer[10000]; + + memset(buffer, 0, 10000); + + header = (struct pim_msg_header *)pim_msg; + +/* + * Omnios apparently doesn't have a #define for IP default + * ttl that is the same as all other platforms. + */ +#ifndef IPDEFTTL +#define IPDEFTTL 64 +#endif + /* TTL for packets destine to ALL-PIM-ROUTERS is 1 */ + switch (header->type) { + case PIM_MSG_TYPE_HELLO: + case PIM_MSG_TYPE_JOIN_PRUNE: + case PIM_MSG_TYPE_BOOTSTRAP: + case PIM_MSG_TYPE_ASSERT: + ttl = 1; + break; + case PIM_MSG_TYPE_REGISTER: + case PIM_MSG_TYPE_REG_STOP: + case PIM_MSG_TYPE_GRAFT: + case PIM_MSG_TYPE_GRAFT_ACK: + case PIM_MSG_TYPE_CANDIDATE: + ttl = IPDEFTTL; + break; + default: + ttl = MAXTTL; + break; + } + + struct ip *ip = (struct ip *)buffer; + struct sockaddr_in to = {}; + int sendlen = sizeof(*ip) + pim_msg_size; + socklen_t tolen; + unsigned char *msg_start; + + ip->ip_id = htons(++ip_id); + ip->ip_hl = 5; + ip->ip_v = 4; + ip->ip_tos = IPTOS_PREC_INTERNETCONTROL; + ip->ip_p = PIM_IP_PROTO_PIM; + ip->ip_src = src; + ip->ip_dst = dst; + ip->ip_ttl = ttl; + ip->ip_len = htons(sendlen); + + to.sin_family = AF_INET; + to.sin_addr = dst; + tolen = sizeof(to); + + msg_start = buffer + sizeof(*ip); + memcpy(msg_start, pim_msg, pim_msg_size); + + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("%s: to %pPA on %s: msg_size=%d checksum=%x", + __func__, &dst, ifp->name, pim_msg_size, + header->checksum); + + if (PIM_DEBUG_PIM_PACKETDUMP_SEND) { + pim_pkt_dump(__func__, pim_msg, pim_msg_size); + } + + pim_msg_send_frame(fd, (char *)buffer, sendlen, (struct sockaddr *)&to, + tolen, ifp->name); + return 0; + +#else + struct iovec iovector[2]; + + iovector[0].iov_base = pim_msg; + iovector[0].iov_len = pim_msg_size; + + pim_msg_send_frame(src, dst, ifp->ifindex, &iovector[0], fd); + + return 0; +#endif +} + +static int hello_send(struct interface *ifp, uint16_t holdtime) +{ + uint8_t pim_msg[PIM_PIM_BUFSIZE_WRITE]; + struct pim_interface *pim_ifp; + int pim_tlv_size; + int pim_msg_size; + + pim_ifp = ifp->info; + + if (PIM_DEBUG_PIM_HELLO) + zlog_debug("%s: to %pPA on %s: holdt=%u prop_d=%u overr_i=%u dis_join_supp=%d dr_prio=%u gen_id=%08x addrs=%zu", + __func__, &qpim_all_pim_routers_addr, ifp->name, + holdtime, pim_ifp->pim_propagation_delay_msec, + pim_ifp->pim_override_interval_msec, + pim_ifp->pim_can_disable_join_suppression, + pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id, + if_connected_count(ifp->connected)); + + pim_tlv_size = pim_hello_build_tlv( + ifp, pim_msg + PIM_PIM_MIN_LEN, + sizeof(pim_msg) - PIM_PIM_MIN_LEN, holdtime, + pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id, + pim_ifp->pim_propagation_delay_msec, + pim_ifp->pim_override_interval_msec, + pim_ifp->pim_can_disable_join_suppression); + if (pim_tlv_size < 0) { + return -1; + } + + pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN; + + assert(pim_msg_size >= PIM_PIM_MIN_LEN); + assert(pim_msg_size <= PIM_PIM_BUFSIZE_WRITE); + + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, pim_msg_size, + PIM_MSG_TYPE_HELLO, false); + + if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, pim_msg_size, + ifp)) { + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug( + "%s: could not send PIM message on interface %s", + __func__, ifp->name); + } + return -2; + } + + return 0; +} + +int pim_hello_send(struct interface *ifp, uint16_t holdtime) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (if_is_loopback(ifp)) + return 0; + + if (hello_send(ifp, holdtime)) { + ++pim_ifp->pim_ifstat_hello_sendfail; + + if (PIM_DEBUG_PIM_HELLO) { + zlog_warn("Could not send PIM hello on interface %s", + ifp->name); + } + return -1; + } + + if (!pim_ifp->pim_passive_enable) { + ++pim_ifp->pim_ifstat_hello_sent; + PIM_IF_FLAG_SET_HELLO_SENT(pim_ifp->flags); + } + + return 0; +} + +static void hello_resched(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug("Rescheduling %d sec hello on interface %s", + pim_ifp->pim_hello_period, ifp->name); + } + EVENT_OFF(pim_ifp->t_pim_hello_timer); + event_add_timer(router->master, on_pim_hello_send, ifp, + pim_ifp->pim_hello_period, &pim_ifp->t_pim_hello_timer); +} + +/* + Periodic hello timer + */ +static void on_pim_hello_send(struct event *t) +{ + struct pim_interface *pim_ifp; + struct interface *ifp; + + ifp = EVENT_ARG(t); + pim_ifp = ifp->info; + + /* + * Schedule next hello + */ + hello_resched(ifp); + + /* + * Send hello + */ + pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); +} + +/* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on an + interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. + */ +void pim_hello_restart_now(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + + /* + * Reset next hello timer + */ + hello_resched(ifp); + + /* + * Immediately send hello + */ + pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp)); +} + +/* + RFC 4601: 4.3.1. Sending Hello Messages + + To allow new or rebooting routers to learn of PIM neighbors quickly, + when a Hello message is received from a new neighbor, or a Hello + message with a new GenID is received from an existing neighbor, a + new Hello message should be sent on this interface after a + randomized delay between 0 and Triggered_Hello_Delay. + */ +void pim_hello_restart_triggered(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + int triggered_hello_delay_msec; + int random_msec; + + pim_ifp = ifp->info; + + /* + * No need to ever start loopback or vrf device hello's + */ + if (if_is_loopback(ifp)) + return; + + /* + * There exists situations where we have the a RPF out this + * interface, but we haven't formed a neighbor yet. This + * happens especially during interface flaps. While + * we would like to handle this more gracefully in other + * parts of the code. In order to get us up and running + * let's just send the hello immediate'ish + * This should be revisited when we get nexthop tracking + * in and when we have a better handle on safely + * handling the rpf information for upstreams that + * we cannot legally reach yet. + */ + triggered_hello_delay_msec = 1; + // triggered_hello_delay_msec = 1000 * + // pim_ifp->pim_triggered_hello_delay; + + if (pim_ifp->t_pim_hello_timer) { + long remain_msec = + pim_time_timer_remain_msec(pim_ifp->t_pim_hello_timer); + if (remain_msec <= triggered_hello_delay_msec) { + /* Rescheduling hello would increase the delay, then + it's faster + to just wait for the scheduled periodic hello. */ + return; + } + + EVENT_OFF(pim_ifp->t_pim_hello_timer); + } + + random_msec = triggered_hello_delay_msec; + // random_msec = random() % (triggered_hello_delay_msec + 1); + + if (PIM_DEBUG_PIM_HELLO) { + zlog_debug("Scheduling %d msec triggered hello on interface %s", + random_msec, ifp->name); + } + + event_add_timer_msec(router->master, on_pim_hello_send, ifp, + random_msec, &pim_ifp->t_pim_hello_timer); +} + +int pim_sock_add(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + uint32_t old_genid; + + pim_ifp = ifp->info; + assert(pim_ifp); + + if (pim_ifp->pim_sock_fd >= 0) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "Can't recreate existing PIM socket fd=%d for interface %s", + pim_ifp->pim_sock_fd, ifp->name); + return -1; + } + + pim_ifp->pim_sock_fd = pim_sock_open(ifp); + if (pim_ifp->pim_sock_fd < 0) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("Could not open PIM socket on interface %s", + ifp->name); + return -2; + } + + pim_socket_ip_hdr(pim_ifp->pim_sock_fd); + + pim_ifp->t_pim_sock_read = NULL; + pim_ifp->pim_sock_creation = pim_time_monotonic_sec(); + + /* + * Just ensure that the new generation id + * actually chooses something different. + * Actually ran across a case where this + * happened, pre-switch to random(). + * While this is unlikely to happen now + * let's make sure it doesn't. + */ + old_genid = pim_ifp->pim_generation_id; + + while (old_genid == pim_ifp->pim_generation_id) + pim_ifp->pim_generation_id = frr_weak_random(); + + zlog_info("PIM INTERFACE UP: on interface %s ifindex=%d", ifp->name, + ifp->ifindex); + + /* + * Start receiving PIM messages + */ + pim_sock_read_on(ifp); + + /* + * Start sending PIM hello's + */ + pim_hello_restart_triggered(ifp); + + return 0; +} diff --git a/pimd/pim_pim.h b/pimd/pim_pim.h new file mode 100644 index 0000000..35e6930 --- /dev/null +++ b/pimd/pim_pim.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_PIM_H +#define PIM_PIM_H + +#include + +#include "if.h" + +#define PIM_PIM_BUFSIZE_READ (20000) +#define PIM_PIM_BUFSIZE_WRITE (20000) + +#define PIM_DEFAULT_HELLO_PERIOD (30) /* seconds, RFC 4601: 4.11 */ +#define PIM_DEFAULT_TRIGGERED_HELLO_DELAY (5) /* seconds, RFC 4601: 4.11 */ +#define PIM_DEFAULT_DR_PRIORITY (1) /* RFC 4601: 4.3.1 */ +#define PIM_DEFAULT_PROPAGATION_DELAY_MSEC (500) /* RFC 4601: 4.11. Timer Values */ +#define PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC (2500) /* RFC 4601: 4.11. Timer Values */ +#define PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION (0) /* boolean */ +#define PIM_DEFAULT_T_PERIODIC (60) /* RFC 4601: 4.11. Timer Values */ + +enum pim_msg_type { + PIM_MSG_TYPE_HELLO = 0, + PIM_MSG_TYPE_REGISTER, + PIM_MSG_TYPE_REG_STOP, + PIM_MSG_TYPE_JOIN_PRUNE, + PIM_MSG_TYPE_BOOTSTRAP, + PIM_MSG_TYPE_ASSERT, + PIM_MSG_TYPE_GRAFT, + PIM_MSG_TYPE_GRAFT_ACK, + PIM_MSG_TYPE_CANDIDATE +}; + +void pim_ifstat_reset(struct interface *ifp); +void pim_sock_reset(struct interface *ifp); +int pim_sock_add(struct interface *ifp); +void pim_sock_delete(struct interface *ifp, const char *delete_message); +void pim_hello_restart_now(struct interface *ifp); +void pim_hello_restart_triggered(struct interface *ifp); + +int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, + pim_sgaddr sg); + +int pim_msg_send(int fd, pim_addr src, pim_addr dst, uint8_t *pim_msg, + int pim_msg_size, struct interface *ifp); + +int pim_hello_send(struct interface *ifp, uint16_t holdtime); +#endif /* PIM_PIM_H */ diff --git a/pimd/pim_register.c b/pimd/pim_register.c new file mode 100644 index 0000000..b149b5a --- /dev/null +++ b/pimd/pim_register.c @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2015 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "log.h" +#include "if.h" +#include "frrevent.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_mroute.h" +#include "pim_iface.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_str.h" +#include "pim_rp.h" +#include "pim_register.h" +#include "pim_upstream.h" +#include "pim_rpf.h" +#include "pim_oil.h" +#include "pim_zebra.h" +#include "pim_join.h" +#include "pim_util.h" +#include "pim_ssm.h" +#include "pim_vxlan.h" +#include "pim_addr.h" + +struct event *send_test_packet_timer = NULL; + +void pim_register_join(struct pim_upstream *up) +{ + struct pim_instance *pim = up->channel_oil->pim; + + if (pim_is_grp_ssm(pim, up->sg.grp)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s register setup skipped as group is SSM", + up->sg_str); + return; + } + + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, __func__); + up->reg_state = PIM_REG_JOIN; + pim_vxlan_update_sg_reg_state(pim, up, true); +} + +void pim_register_stop_send(struct interface *ifp, pim_sgaddr *sg, pim_addr src, + pim_addr originator) +{ + struct pim_interface *pinfo; + unsigned char buffer[10000]; + unsigned int b1length = 0; + unsigned int length; + uint8_t *b1; + + if (PIM_DEBUG_PIM_REG) { + zlog_debug("Sending Register stop for %pSG to %pPA on %s", sg, + &originator, ifp->name); + } + + memset(buffer, 0, 10000); + b1 = (uint8_t *)buffer + PIM_MSG_REGISTER_STOP_LEN; + + length = pim_encode_addr_group(b1, AFI_IP, 0, 0, sg->grp); + b1length += length; + b1 += length; + + length = pim_encode_addr_ucast(b1, sg->src); + b1length += length; + + pim_msg_build_header(src, originator, buffer, + b1length + PIM_MSG_REGISTER_STOP_LEN, + PIM_MSG_TYPE_REG_STOP, false); + + pinfo = (struct pim_interface *)ifp->info; + if (!pinfo) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: No pinfo!", __func__); + return; + } + if (pim_msg_send(pinfo->pim->reg_sock, src, originator, buffer, + b1length + PIM_MSG_REGISTER_STOP_LEN, ifp)) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s: could not send PIM register stop message on interface %s", + __func__, ifp->name); + } + } + + if (!pinfo->pim_passive_enable) + ++pinfo->pim_ifstat_reg_stop_send; +} + +static void pim_reg_stop_upstream(struct pim_instance *pim, + struct pim_upstream *up) +{ + switch (up->reg_state) { + case PIM_REG_NOINFO: + case PIM_REG_PRUNE: + return; + case PIM_REG_JOIN: + up->reg_state = PIM_REG_PRUNE; + pim_channel_del_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, __func__); + pim_upstream_start_register_probe_timer(up); + pim_vxlan_update_sg_reg_state(pim, up, false); + break; + case PIM_REG_JOIN_PENDING: + up->reg_state = PIM_REG_PRUNE; + pim_upstream_start_register_probe_timer(up); + return; + } +} + +int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; + struct pim_upstream *up = NULL; + struct pim_rpf *rp; + pim_sgaddr sg; + struct listnode *up_node; + struct pim_upstream *child; + bool wrong_af = false; + bool handling_star = false; + int l; + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + ++pim_ifp->pim_ifstat_reg_stop_recv; + + memset(&sg, 0, sizeof(sg)); + l = pim_parse_addr_group(&sg, buf, buf_size); + buf += l; + buf_size -= l; + pim_parse_addr_ucast(&sg.src, buf, buf_size, &wrong_af); + + if (wrong_af) { + zlog_err("invalid AF in Register-Stop on %s", ifp->name); + return -1; + } + + + if (PIM_DEBUG_PIM_REG) + zlog_debug("Received Register stop for %pSG", &sg); + + rp = RP(pim_ifp->pim, sg.grp); + if (rp) { + /* As per RFC 7761, Section 4.9.4: + * A special wildcard value consisting of an address field of + * all zeros can be used to indicate any source. + */ + if ((pim_addr_cmp(sg.src, rp->rpf_addr) == 0) || + pim_addr_is_any(sg.src)) { + handling_star = true; + sg.src = PIMADDR_ANY; + } + } + + /* + * RFC 7761 Sec 4.4.1 + * Handling Register-Stop(*,G) Messages at the DR: + * A Register-Stop(*,G) should be treated as a + * Register-Stop(S,G) for all (S,G) Register state + * machines that are not in the NoInfo state. + */ + up = pim_upstream_find(pim, &sg); + if (up) { + /* + * If the upstream find actually found a particular + * S,G then we *know* that the following for loop + * is not going to execute and this is ok + */ + for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { + if (PIM_DEBUG_PIM_REG) + zlog_debug("Executing Reg stop for %s", + child->sg_str); + + pim_reg_stop_upstream(pim, child); + } + + if (PIM_DEBUG_PIM_REG) + zlog_debug("Executing Reg stop for %s", up->sg_str); + pim_reg_stop_upstream(pim, up); + } else { + if (!handling_star) + return 0; + /* + * Unfortunately pim was unable to find a *,G + * but pim may still actually have individual + * S,G's that need to be processed. In that + * case pim must do the expensive walk to find + * and stop + */ + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_addr_cmp(up->sg.grp, sg.grp) == 0) { + if (PIM_DEBUG_PIM_REG) + zlog_debug("Executing Reg stop for %s", + up->sg_str); + pim_reg_stop_upstream(pim, up); + } + } + } + + return 0; +} + +#if PIM_IPV == 6 +struct in6_addr pim_register_get_unicast_v6_addr(struct pim_interface *p_ifp) +{ + struct listnode *node; + struct listnode *nextnode; + struct pim_secondary_addr *sec_addr; + struct pim_interface *pim_ifp; + struct interface *ifp; + struct pim_instance *pim = p_ifp->pim; + + /* Trying to get the unicast address from the RPF interface first */ + for (ALL_LIST_ELEMENTS(p_ifp->sec_addr_list, node, nextnode, + sec_addr)) { + if (!is_ipv6_global_unicast(&sec_addr->addr.u.prefix6)) + continue; + + return sec_addr->addr.u.prefix6; + } + + /* Loop through all the pim interface and try to return a global + * unicast ipv6 address + */ + FOR_ALL_INTERFACES (pim->vrf, ifp) { + pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + for (ALL_LIST_ELEMENTS(pim_ifp->sec_addr_list, node, nextnode, + sec_addr)) { + if (!is_ipv6_global_unicast(&sec_addr->addr.u.prefix6)) + continue; + + return sec_addr->addr.u.prefix6; + } + } + + zlog_warn("No global address found for use to send register message"); + return PIMADDR_ANY; +} +#endif + +void pim_register_send(const uint8_t *buf, int buf_size, pim_addr src, + struct pim_rpf *rpg, int null_register, + struct pim_upstream *up) +{ + unsigned char buffer[10000]; + unsigned char *b1; + struct pim_interface *pinfo; + struct interface *ifp; + + if (PIM_DEBUG_PIM_REG) { + zlog_debug("Sending %s %sRegister Packet to %pPA", up->sg_str, + null_register ? "NULL " : "", &rpg->rpf_addr); + } + + ifp = rpg->source_nexthop.interface; + if (!ifp) { + if (PIM_DEBUG_PIM_REG) + zlog_debug("%s: No interface to transmit register on", + __func__); + return; + } + pinfo = (struct pim_interface *)ifp->info; + if (!pinfo) { + if (PIM_DEBUG_PIM_REG) + zlog_debug( + "%s: Interface: %s not configured for pim to transmit on!", + __func__, ifp->name); + return; + } + + if (PIM_DEBUG_PIM_REG) { + zlog_debug("%s: Sending %s %sRegister Packet to %pPA on %s", + __func__, up->sg_str, null_register ? "NULL " : "", + &rpg->rpf_addr, ifp->name); + } + + memset(buffer, 0, 10000); + b1 = buffer + PIM_MSG_HEADER_LEN; + *b1 |= null_register << 6; + b1 = buffer + PIM_MSG_REGISTER_LEN; + + memcpy(b1, (const unsigned char *)buf, buf_size); + +#if PIM_IPV == 6 + /* While sending Register message to RP, we cannot use link-local + * address therefore using unicast ipv6 address here, choosing it + * from the RPF Interface + */ + src = pim_register_get_unicast_v6_addr(pinfo); +#endif + pim_msg_build_header(src, rpg->rpf_addr, buffer, + buf_size + PIM_MSG_REGISTER_LEN, + PIM_MSG_TYPE_REGISTER, false); + + if (!pinfo->pim_passive_enable) + ++pinfo->pim_ifstat_reg_send; + + if (pim_msg_send(pinfo->pim->reg_sock, src, rpg->rpf_addr, buffer, + buf_size + PIM_MSG_REGISTER_LEN, ifp)) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s: could not send PIM register message on interface %s", + __func__, ifp->name); + } + return; + } +} + +#if PIM_IPV == 4 +void pim_null_register_send(struct pim_upstream *up) +{ + struct ip ip_hdr; + struct pim_interface *pim_ifp; + struct pim_rpf *rpg; + pim_addr src; + + pim_ifp = up->rpf.source_nexthop.interface->info; + if (!pim_ifp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Cannot send null-register for %s no valid iif", + __func__, up->sg_str); + return; + } + + rpg = RP(pim_ifp->pim, up->sg.grp); + if (!rpg) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Cannot send null-register for %s no RPF to the RP", + __func__, up->sg_str); + return; + } + + memset(&ip_hdr, 0, sizeof(ip_hdr)); + ip_hdr.ip_p = PIM_IP_PROTO_PIM; + ip_hdr.ip_hl = 5; + ip_hdr.ip_v = 4; + ip_hdr.ip_src = up->sg.src; + ip_hdr.ip_dst = up->sg.grp; + ip_hdr.ip_len = htons(20); + + /* checksum is broken */ + src = pim_ifp->primary_address; + if (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(up->flags)) { + if (!pim_vxlan_get_register_src(pim_ifp->pim, up, &src)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Cannot send null-register for %s vxlan-aa PIP unavailable", + __func__, up->sg_str); + return; + } + } + pim_register_send((uint8_t *)&ip_hdr, sizeof(struct ip), src, rpg, 1, + up); +} +#else +void pim_null_register_send(struct pim_upstream *up) +{ + struct ip6_hdr ip6_hdr; + struct pim_msg_header pim_msg_header; + struct pim_interface *pim_ifp; + struct pim_rpf *rpg; + pim_addr src; + unsigned char buffer[sizeof(ip6_hdr) + sizeof(pim_msg_header)]; + struct ipv6_ph ph; + + pim_ifp = up->rpf.source_nexthop.interface->info; + if (!pim_ifp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "Cannot send null-register for %s no valid iif", + up->sg_str); + return; + } + + rpg = RP(pim_ifp->pim, up->sg.grp); + if (!rpg) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "Cannot send null-register for %s no RPF to the RP", + up->sg_str); + return; + } + + memset(&ip6_hdr, 0, sizeof(ip6_hdr)); + ip6_hdr.ip6_nxt = PIM_IP_PROTO_PIM; + ip6_hdr.ip6_plen = PIM_MSG_HEADER_LEN; + ip6_hdr.ip6_vfc = 6 << 4; + ip6_hdr.ip6_hlim = MAXTTL; + ip6_hdr.ip6_src = up->sg.src; + ip6_hdr.ip6_dst = up->sg.grp; + + memset(buffer, 0, (sizeof(ip6_hdr) + sizeof(pim_msg_header))); + memcpy(buffer, &ip6_hdr, sizeof(ip6_hdr)); + + memset(&pim_msg_header, 0, sizeof(pim_msg_header)); + memset(&ph, 0, sizeof(ph)); + + ph.src = up->sg.src; + ph.dst = up->sg.grp; + ph.ulpl = htonl(PIM_MSG_HEADER_LEN); + ph.next_hdr = IPPROTO_PIM; + pim_msg_header.checksum = + in_cksum_with_ph6(&ph, &pim_msg_header, PIM_MSG_HEADER_LEN); + + memcpy(buffer + sizeof(ip6_hdr), &pim_msg_header, PIM_MSG_HEADER_LEN); + + + src = pim_ifp->primary_address; + pim_register_send((uint8_t *)buffer, + sizeof(ip6_hdr) + PIM_MSG_HEADER_LEN, src, rpg, 1, + up); +} +#endif + +/* + * 4.4.2 Receiving Register Messages at the RP + * + * When an RP receives a Register message, the course of action is + * decided according to the following pseudocode: + * + * packet_arrives_on_rp_tunnel( pkt ) { + * if( outer.dst is not one of my addresses ) { + * drop the packet silently. + * # Note: this may be a spoofing attempt + * } + * if( I_am_RP(G) AND outer.dst == RP(G) ) { + * sentRegisterStop = false; + * if ( register.borderbit == true ) { + * if ( PMBR(S,G) == unknown ) { + * PMBR(S,G) = outer.src + * } else if ( outer.src != PMBR(S,G) ) { + * send Register-Stop(S,G) to outer.src + * drop the packet silently. + * } + * } + * if ( SPTbit(S,G) OR + * ( SwitchToSptDesired(S,G) AND + * ( inherited_olist(S,G) == NULL ))) { + * send Register-Stop(S,G) to outer.src + * sentRegisterStop = true; + * } + * if ( SPTbit(S,G) OR SwitchToSptDesired(S,G) ) { + * if ( sentRegisterStop == true ) { + * set KeepaliveTimer(S,G) to RP_Keepalive_Period; + * } else { + * set KeepaliveTimer(S,G) to Keepalive_Period; + * } + * } + * if( !SPTbit(S,G) AND ! pkt.NullRegisterBit ) { + * decapsulate and forward the inner packet to + * inherited_olist(S,G,rpt) # Note (+) + * } + * } else { + * send Register-Stop(S,G) to outer.src + * # Note (*) + * } + * } + */ +int pim_register_recv(struct interface *ifp, pim_addr dest_addr, + pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) +{ + int sentRegisterStop = 0; + const void *ip_hdr; + pim_sgaddr sg; + uint32_t *bits; + int i_am_rp = 0; + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; + pim_addr rp_addr; + struct pim_rpf *rpg; + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + +#define PIM_MSG_REGISTER_BIT_RESERVED_LEN 4 + ip_hdr = (tlv_buf + PIM_MSG_REGISTER_BIT_RESERVED_LEN); + + if (!if_address_is_local(&dest_addr, PIM_AF, pim->vrf->vrf_id)) { + if (PIM_DEBUG_PIM_REG) + zlog_debug( + "%s: Received Register message for destination address: %pPA that I do not own", + __func__, &dest_addr); + return 0; + } + + ++pim_ifp->pim_ifstat_reg_recv; + + /* + * Please note this is not drawn to get the correct bit/data size + * + * The entirety of the REGISTER packet looks like this: + * ------------------------------------------------------------- + * | Ver | Type | Reserved | Checksum | + * |-----------------------------------------------------------| + * |B|N| Reserved 2 | + * |-----------------------------------------------------------| + * | Encap | IP HDR | + * | Mcast | | + * | Packet |--------------------------------------------------| + * | | Mcast Data | + * | | | + * ... + * + * tlv_buf when received from the caller points at the B bit + * We need to know the inner source and dest + */ + bits = (uint32_t *)tlv_buf; + + /* + * tlv_buf points to the start of the |B|N|... Reserved + * Line above. So we need to add 4 bytes to get to the + * start of the actual Encapsulated data. + */ + memset(&sg, 0, sizeof(sg)); + sg = pim_sgaddr_from_iphdr(ip_hdr); + +#if PIM_IPV == 6 + /* + * According to RFC section 4.9.3, If Dummy PIM Header is included + * in NULL Register as a payload there would be two PIM headers. + * The inner PIM Header's checksum field should also be validated + * in addition to the outer PIM Header's checksum. Validation of + * inner PIM header checksum is done here. + */ + if ((*bits & PIM_REGISTER_NR_BIT) && + ((tlv_buf_size - PIM_MSG_REGISTER_BIT_RESERVED_LEN) > + (int)sizeof(struct ip6_hdr))) { + uint16_t computed_checksum; + uint16_t received_checksum; + struct ipv6_ph ph; + struct pim_msg_header *header; + + header = (struct pim_msg_header + *)(tlv_buf + + PIM_MSG_REGISTER_BIT_RESERVED_LEN + + sizeof(struct ip6_hdr)); + ph.src = sg.src; + ph.dst = sg.grp; + ph.ulpl = htonl(PIM_MSG_HEADER_LEN); + ph.next_hdr = IPPROTO_PIM; + + received_checksum = header->checksum; + + header->checksum = 0; + computed_checksum = in_cksum_with_ph6( + &ph, header, htonl(PIM_MSG_HEADER_LEN)); + + if (computed_checksum != received_checksum) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "Ignoring Null Register message%pSG from %pPA due to bad checksum in Encapsulated dummy PIM header", + &sg, &src_addr); + return 0; + } + } +#endif + i_am_rp = I_am_RP(pim, sg.grp); + + if (PIM_DEBUG_PIM_REG) + zlog_debug( + "Received Register message%pSG from %pPA on %s, rp: %d", + &sg, &src_addr, ifp->name, i_am_rp); + + if (pim_is_grp_ssm(pim_ifp->pim, sg.grp)) { + if (pim_addr_is_any(sg.src)) { + zlog_warn( + "%s: Received Register message for Group(%pPA) is now in SSM, dropping the packet", + __func__, &sg.grp); + /* Drop Packet Silently */ + return 0; + } + } + + rpg = RP(pim, sg.grp); + if (!rpg) { + zlog_warn("%s: Received Register Message %pSG from %pPA on %s where the RP could not be looked up", + __func__, &sg, &src_addr, ifp->name); + return 0; + } + + rp_addr = rpg->rpf_addr; + if (i_am_rp && (!pim_addr_cmp(dest_addr, rp_addr))) { + sentRegisterStop = 0; + + if (pim->register_plist) { + struct prefix_list *plist; + struct prefix src; + + plist = prefix_list_lookup(PIM_AFI, + pim->register_plist); + + pim_addr_to_prefix(&src, sg.src); + + if (prefix_list_apply_ext(plist, NULL, &src, true) == + PREFIX_DENY) { + pim_register_stop_send(ifp, &sg, dest_addr, + src_addr); + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "%s: Sending register-stop to %pPA for %pSG due to prefix-list denial, dropping packet", + __func__, &src_addr, &sg); + + return 0; + } + } + + if (*bits & PIM_REGISTER_BORDER_BIT) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "%s: Received Register message with Border bit set, ignoring", + __func__); + + /* Drop Packet Silently */ + return 0; + } + + struct pim_upstream *upstream = pim_upstream_find(pim, &sg); + /* + * If we don't have a place to send ignore the packet + */ + if (!upstream) { + upstream = pim_upstream_add( + pim, &sg, ifp, + PIM_UPSTREAM_FLAG_MASK_SRC_STREAM, __func__, + NULL); + if (!upstream) { + zlog_warn("Failure to create upstream state"); + return 1; + } + + upstream->upstream_register = src_addr; + } else { + /* + * If the FHR has set a very very fast register timer + * there exists a possibility that the incoming NULL + * register + * is happening before we set the spt bit. If so + * Do a quick check to update the counters and + * then set the spt bit as appropriate + */ + if (upstream->sptbit != PIM_UPSTREAM_SPTBIT_TRUE) { + pim_mroute_update_counters( + upstream->channel_oil); + /* + * Have we seen packets? + */ + if (upstream->channel_oil->cc.oldpktcnt + < upstream->channel_oil->cc.pktcnt) + pim_upstream_set_sptbit( + upstream, + upstream->rpf.source_nexthop + .interface); + } + } + + if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) + || ((SwitchToSptDesiredOnRp(pim, &sg)) + && pim_upstream_inherited_olist(pim, upstream) == 0)) { + pim_register_stop_send(ifp, &sg, dest_addr, src_addr); + sentRegisterStop = 1; + } else { + if (PIM_DEBUG_PIM_REG) + zlog_debug("(%s) sptbit: %d", upstream->sg_str, + upstream->sptbit); + } + if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) + || (SwitchToSptDesiredOnRp(pim, &sg))) { + if (sentRegisterStop) { + pim_upstream_keep_alive_timer_start( + upstream, pim->rp_keep_alive_time); + } else { + pim_upstream_keep_alive_timer_start( + upstream, pim->keep_alive_time); + } + } + + if (!(upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) + && !(*bits & PIM_REGISTER_NR_BIT)) { + // decapsulate and forward the iner packet to + // inherited_olist(S,G,rpt) + // This is taken care of by the kernel for us + } + pim_upstream_msdp_reg_timer_start(upstream); + } else { + if (PIM_DEBUG_PIM_REG) { + if (!i_am_rp) + zlog_debug("Received Register packet for %pSG, Rejecting packet because I am not the RP configured for group", + &sg); + else + zlog_debug("Received Register packet for %pSG, Rejecting packet because the dst ip address is not the actual RP", + &sg); + } + pim_register_stop_send(ifp, &sg, dest_addr, src_addr); + } + + return 0; +} + +/* + * This routine scan all upstream and update register state and remove pimreg + * when couldreg becomes false. + */ +void pim_reg_del_on_couldreg_fail(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim; + struct pim_upstream *up; + + if (!pim_ifp) + return; + + pim = pim_ifp->pim; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (ifp != up->rpf.source_nexthop.interface) + continue; + + if (!pim_upstream_could_register(up) + && (up->reg_state != PIM_REG_NOINFO)) { + pim_channel_del_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, __func__); + EVENT_OFF(up->t_rs_timer); + up->reg_state = PIM_REG_NOINFO; + PIM_UPSTREAM_FLAG_UNSET_FHR(up->flags); + } + } +} diff --git a/pimd/pim_register.h b/pimd/pim_register.h new file mode 100644 index 0000000..d1240d7 --- /dev/null +++ b/pimd/pim_register.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2015 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef PIM_REGISTER_H +#define PIM_REGISTER_H + +#include + +#include "if.h" + +#define PIM_REGISTER_BORDER_BIT 0x80000000 +#define PIM_REGISTER_NR_BIT 0x40000000 + +#define PIM_MSG_REGISTER_LEN (8) +#define PIM_MSG_REGISTER_STOP_LEN (4) + +int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size); + +int pim_register_recv(struct interface *ifp, pim_addr dest_addr, + pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size); +#if PIM_IPV == 6 +struct in6_addr pim_register_get_unicast_v6_addr(struct pim_interface *p_ifp); +#endif +void pim_register_send(const uint8_t *buf, int buf_size, pim_addr src, + struct pim_rpf *rpg, int null_register, + struct pim_upstream *up); +void pim_register_stop_send(struct interface *ifp, pim_sgaddr *sg, pim_addr src, + pim_addr originator); +void pim_register_join(struct pim_upstream *up); +void pim_null_register_send(struct pim_upstream *up); +void pim_reg_del_on_couldreg_fail(struct interface *ifp); + +#endif diff --git a/pimd/pim_routemap.c b/pimd/pim_routemap.c new file mode 100644 index 0000000..bd1a24e --- /dev/null +++ b/pimd/pim_routemap.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PIM Route-map Code + * Copyright (C) 2016 Cumulus Networks + * Copyright (C) 1999 Kunihiro Ishiguro + * + * This file is part of Quagga + */ +#include + +#include "if.h" +#include "vty.h" +#include "routemap.h" + +#include "pimd.h" + +static void pim_route_map_add(const char *rmap_name) +{ + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); +} + +static void pim_route_map_delete(const char *rmap_name) +{ + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_DELETED); +} + +static void pim_route_map_event(const char *rmap_name) +{ + route_map_notify_dependencies(rmap_name, RMAP_EVENT_MATCH_ADDED); +} + +void pim_route_map_init(void) +{ + route_map_init(); + + route_map_add_hook(pim_route_map_add); + route_map_delete_hook(pim_route_map_delete); + route_map_event_hook(pim_route_map_event); +} + +void pim_route_map_terminate(void) +{ + route_map_finish(); +} diff --git a/pimd/pim_rp.c b/pimd/pim_rp.c new file mode 100644 index 0000000..d8d2571 --- /dev/null +++ b/pimd/pim_rp.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2015 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "lib/json.h" +#include "log.h" +#include "network.h" +#include "if.h" +#include "linklist.h" +#include "prefix.h" +#include "memory.h" +#include "vty.h" +#include "vrf.h" +#include "plist.h" +#include "nexthop.h" +#include "table.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_vty.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_rp.h" +#include "pim_rpf.h" +#include "pim_sock.h" +#include "pim_memory.h" +#include "pim_neighbor.h" +#include "pim_msdp.h" +#include "pim_nht.h" +#include "pim_mroute.h" +#include "pim_oil.h" +#include "pim_zebra.h" +#include "pim_bsm.h" +#include "pim_util.h" +#include "pim_ssm.h" +#include "termtable.h" + +/* Cleanup pim->rpf_hash each node data */ +void pim_rp_list_hash_clean(void *data) +{ + struct pim_nexthop_cache *pnc = (struct pim_nexthop_cache *)data; + + list_delete(&pnc->rp_list); + + hash_clean_and_free(&pnc->upstream_hash, NULL); + if (pnc->nexthop) + nexthops_free(pnc->nexthop); + + XFREE(MTYPE_PIM_NEXTHOP_CACHE, pnc); +} + +static void pim_rp_info_free(struct rp_info *rp_info) +{ + XFREE(MTYPE_PIM_FILTER_NAME, rp_info->plist); + + XFREE(MTYPE_PIM_RP, rp_info); +} + +int pim_rp_list_cmp(void *v1, void *v2) +{ + struct rp_info *rp1 = (struct rp_info *)v1; + struct rp_info *rp2 = (struct rp_info *)v2; + int ret; + + /* + * Sort by RP IP address + */ + ret = pim_addr_cmp(rp1->rp.rpf_addr, rp2->rp.rpf_addr); + if (ret) + return ret; + + /* + * Sort by group IP address + */ + ret = prefix_cmp(&rp1->group, &rp2->group); + if (ret) + return ret; + + return 0; +} + +void pim_rp_init(struct pim_instance *pim) +{ + struct rp_info *rp_info; + struct route_node *rn; + + pim->rp_list = list_new(); + pim->rp_list->del = (void (*)(void *))pim_rp_info_free; + pim->rp_list->cmp = pim_rp_list_cmp; + + pim->rp_table = route_table_init(); + + rp_info = XCALLOC(MTYPE_PIM_RP, sizeof(*rp_info)); + + if (!pim_get_all_mcast_group(&rp_info->group)) { + flog_err(EC_LIB_DEVELOPMENT, + "Unable to convert all-multicast prefix"); + list_delete(&pim->rp_list); + route_table_finish(pim->rp_table); + XFREE(MTYPE_PIM_RP, rp_info); + return; + } + rp_info->rp.rpf_addr = PIMADDR_ANY; + + listnode_add(pim->rp_list, rp_info); + + rn = route_node_get(pim->rp_table, &rp_info->group); + rn->info = rp_info; + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("Allocated: %p for rp_info: %p(%pFX) Lock: %d", rn, + rp_info, &rp_info->group, + route_node_get_lock_count(rn)); +} + +void pim_rp_free(struct pim_instance *pim) +{ + if (pim->rp_table) + route_table_finish(pim->rp_table); + pim->rp_table = NULL; + + if (pim->rp_list) + list_delete(&pim->rp_list); +} + +/* + * Given an RP's prefix-list, return the RP's rp_info for that prefix-list + */ +static struct rp_info *pim_rp_find_prefix_list(struct pim_instance *pim, + pim_addr rp, const char *plist) +{ + struct listnode *node; + struct rp_info *rp_info; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if ((!pim_addr_cmp(rp, rp_info->rp.rpf_addr)) && + rp_info->plist && strcmp(rp_info->plist, plist) == 0) { + return rp_info; + } + } + + return NULL; +} + +/* + * Return true if plist is used by any rp_info + */ +static int pim_rp_prefix_list_used(struct pim_instance *pim, const char *plist) +{ + struct listnode *node; + struct rp_info *rp_info; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (rp_info->plist && strcmp(rp_info->plist, plist) == 0) { + return 1; + } + } + + return 0; +} + +/* + * Given an RP's address, return the RP's rp_info that is an exact match for + * 'group' + */ +static struct rp_info *pim_rp_find_exact(struct pim_instance *pim, pim_addr rp, + const struct prefix *group) +{ + struct listnode *node; + struct rp_info *rp_info; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if ((!pim_addr_cmp(rp, rp_info->rp.rpf_addr)) && + prefix_same(&rp_info->group, group)) + return rp_info; + } + + return NULL; +} + +/* + * XXX: long-term issue: we don't actually have a good "ip address-list" + * implementation. ("access-list XYZ" is the closest but honestly it's + * kinda garbage.) + * + * So it's using a prefix-list to match an address here, which causes very + * unexpected results for the user since prefix-lists by default only match + * when the prefix length is an exact match too. i.e. you'd have to add the + * "le 32" and do "ip prefix-list foo permit 10.0.0.0/24 le 32" + * + * To avoid this pitfall, this code uses "address_mode = true" for the prefix + * list match (this is the only user for that.) + * + * In the long run, we need to add a "ip address-list", but that's a wholly + * separate bag of worms, and existing configs using ip prefix-list would + * drop into the UX pitfall. + */ + +#include "lib/plist_int.h" + +/* + * Given a group, return the rp_info for that group + */ +struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, + const struct prefix *group) +{ + struct listnode *node; + struct rp_info *best = NULL; + struct rp_info *rp_info; + struct prefix_list *plist; + const struct prefix *bp; + const struct prefix_list_entry *entry; + struct route_node *rn; + + bp = NULL; + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (rp_info->plist) { + plist = prefix_list_lookup(PIM_AFI, rp_info->plist); + + if (prefix_list_apply_ext(plist, &entry, group, true) + == PREFIX_DENY || !entry) + continue; + + if (!best) { + best = rp_info; + bp = &entry->prefix; + continue; + } + + if (bp && bp->prefixlen < entry->prefix.prefixlen) { + best = rp_info; + bp = &entry->prefix; + } + } + } + + rn = route_node_match(pim->rp_table, group); + if (!rn) { + flog_err( + EC_LIB_DEVELOPMENT, + "%s: BUG We should have found default group information", + __func__); + return best; + } + + rp_info = rn->info; + if (PIM_DEBUG_PIM_TRACE_DETAIL) { + if (best) + zlog_debug( + "Lookedup(%pFX): prefix_list match %s, rn %p found: %pFX", + group, best->plist, rn, &rp_info->group); + else + zlog_debug("Lookedup(%pFX): rn %p found:%pFX", group, + rn, &rp_info->group); + } + + route_unlock_node(rn); + + /* + * rp's with prefix lists have the group as 224.0.0.0/4 which will + * match anything. So if we have a rp_info that should match a prefix + * list then if we do match then best should be the answer( even + * if it is NULL ) + */ + if (!rp_info || (rp_info && rp_info->plist)) + return best; + + /* + * So we have a non plist rp_info found in the lookup and no plists + * at all to be choosen, return it! + */ + if (!best) + return rp_info; + + /* + * If we have a matching non prefix list and a matching prefix + * list we should return the actual rp_info that has the LPM + * If they are equal, use the prefix-list( but let's hope + * the end-operator doesn't do this ) + */ + if (rp_info->group.prefixlen > bp->prefixlen) + best = rp_info; + + return best; +} + +/* + * When the user makes "ip pim rp" configuration changes or if they change the + * prefix-list(s) used by these statements we must tickle the upstream state + * for each group to make them re-lookup who their RP should be. + * + * This is a placeholder function for now. + */ +void pim_rp_refresh_group_to_rp_mapping(struct pim_instance *pim) +{ + pim_msdp_i_am_rp_changed(pim); + pim_upstream_reeval_use_rpt(pim); +} + +void pim_rp_prefix_list_update(struct pim_instance *pim, + struct prefix_list *plist) +{ + struct listnode *node; + struct rp_info *rp_info; + int refresh_needed = 0; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (rp_info->plist + && strcmp(rp_info->plist, prefix_list_name(plist)) == 0) { + refresh_needed = 1; + break; + } + } + + if (refresh_needed) + pim_rp_refresh_group_to_rp_mapping(pim); +} + +static int pim_rp_check_interface_addrs(struct rp_info *rp_info, + struct pim_interface *pim_ifp) +{ + struct listnode *node; + struct pim_secondary_addr *sec_addr; + pim_addr sec_paddr; + + if (!pim_addr_cmp(pim_ifp->primary_address, rp_info->rp.rpf_addr)) + return 1; + + if (!pim_ifp->sec_addr_list) { + return 0; + } + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) { + sec_paddr = pim_addr_from_prefix(&sec_addr->addr); + /* If an RP-address is self, It should be enough to say + * I am RP the prefix-length should not matter here */ + if (!pim_addr_cmp(sec_paddr, rp_info->rp.rpf_addr)) + return 1; + } + + return 0; +} + +static void pim_rp_check_interfaces(struct pim_instance *pim, + struct rp_info *rp_info) +{ + struct interface *ifp; + + rp_info->i_am_rp = 0; + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + + if (!pim_ifp) + continue; + + if (pim_rp_check_interface_addrs(rp_info, pim_ifp)) { + rp_info->i_am_rp = 1; + } + } +} + +void pim_upstream_update(struct pim_instance *pim, struct pim_upstream *up) +{ + struct pim_rpf old_rpf; + enum pim_rpf_result rpf_result; + pim_addr old_upstream_addr; + pim_addr new_upstream_addr; + + old_upstream_addr = up->upstream_addr; + pim_rp_set_upstream_addr(pim, &new_upstream_addr, up->sg.src, + up->sg.grp); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: pim upstream update for old upstream %pPA", + __func__, &old_upstream_addr); + + if (!pim_addr_cmp(old_upstream_addr, new_upstream_addr)) + return; + + /* Lets consider a case, where a PIM upstream has a better RP as a + * result of a new RP configuration with more precise group range. + * This upstream has to be added to the upstream hash of new RP's + * NHT(pnc) and has to be removed from old RP's NHT upstream hash + */ + if (!pim_addr_is_any(old_upstream_addr)) { + /* Deregister addr with Zebra NHT */ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Deregister upstream %s addr %pPA with Zebra NHT", + __func__, up->sg_str, &old_upstream_addr); + pim_delete_tracked_nexthop(pim, old_upstream_addr, up, NULL); + } + + /* Update the upstream address */ + up->upstream_addr = new_upstream_addr; + + old_rpf.source_nexthop.interface = up->rpf.source_nexthop.interface; + + rpf_result = pim_rpf_update(pim, up, &old_rpf, __func__); + if (rpf_result == PIM_RPF_FAILURE) + pim_mroute_del(up->channel_oil, __func__); + + /* update kernel multicast forwarding cache (MFC) */ + if (up->rpf.source_nexthop.interface && up->channel_oil) + pim_upstream_mroute_iif_update(up->channel_oil, __func__); + + if (rpf_result == PIM_RPF_CHANGED || + (rpf_result == PIM_RPF_FAILURE && + old_rpf.source_nexthop.interface)) + pim_zebra_upstream_rpf_changed(pim, up, &old_rpf); + +} + +int pim_rp_new(struct pim_instance *pim, pim_addr rp_addr, struct prefix group, + const char *plist, enum rp_source rp_src_flag) +{ + int result = 0; + struct rp_info *rp_info; + struct rp_info *rp_all; + struct prefix group_all; + struct listnode *node, *nnode; + struct rp_info *tmp_rp_info; + char buffer[BUFSIZ]; + pim_addr nht_p; + struct route_node *rn = NULL; + struct pim_upstream *up; + bool upstream_updated = false; + + if (pim_addr_is_any(rp_addr)) + return PIM_RP_BAD_ADDRESS; + + rp_info = XCALLOC(MTYPE_PIM_RP, sizeof(*rp_info)); + + rp_info->rp.rpf_addr = rp_addr; + prefix_copy(&rp_info->group, &group); + rp_info->rp_src = rp_src_flag; + + if (plist) { + /* + * Return if the prefix-list is already configured for this RP + */ + if (pim_rp_find_prefix_list(pim, rp_addr, plist)) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_SUCCESS; + } + + /* + * Barf if the prefix-list is already configured for an RP + */ + if (pim_rp_prefix_list_used(pim, plist)) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_RP_PFXLIST_IN_USE; + } + + /* + * Free any existing rp_info entries for this RP + */ + for (ALL_LIST_ELEMENTS(pim->rp_list, node, nnode, + tmp_rp_info)) { + if (!pim_addr_cmp(rp_info->rp.rpf_addr, + tmp_rp_info->rp.rpf_addr)) { + if (tmp_rp_info->plist) + pim_rp_del_config(pim, rp_addr, NULL, + tmp_rp_info->plist); + else + pim_rp_del_config( + pim, rp_addr, + prefix2str(&tmp_rp_info->group, + buffer, BUFSIZ), + NULL); + } + } + + rp_info->plist = XSTRDUP(MTYPE_PIM_FILTER_NAME, plist); + } else { + + if (!pim_get_all_mcast_group(&group_all)) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_GROUP_BAD_ADDRESS; + } + rp_all = pim_rp_find_match_group(pim, &group_all); + + /* + * Barf if group is a non-multicast subnet + */ + if (!prefix_match(&rp_all->group, &rp_info->group)) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_GROUP_BAD_ADDRESS; + } + + /* + * Remove any prefix-list rp_info entries for this RP + */ + for (ALL_LIST_ELEMENTS(pim->rp_list, node, nnode, + tmp_rp_info)) { + if (tmp_rp_info->plist && + (!pim_addr_cmp(rp_info->rp.rpf_addr, + tmp_rp_info->rp.rpf_addr))) { + pim_rp_del_config(pim, rp_addr, NULL, + tmp_rp_info->plist); + } + } + + /* + * Take over the 224.0.0.0/4 group if the rp is INADDR_ANY + */ + if (prefix_same(&rp_all->group, &rp_info->group) && + pim_rpf_addr_is_inaddr_any(&rp_all->rp)) { + rp_all->rp.rpf_addr = rp_info->rp.rpf_addr; + rp_all->rp_src = rp_src_flag; + XFREE(MTYPE_PIM_RP, rp_info); + + /* Register addr with Zebra NHT */ + nht_p = rp_all->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug( + "%s: NHT Register rp_all addr %pPA grp %pFX ", + __func__, &nht_p, &rp_all->group); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* Find (*, G) upstream whose RP is not + * configured yet + */ + if (pim_addr_is_any(up->upstream_addr) && + pim_addr_is_any(up->sg.src)) { + struct prefix grp; + struct rp_info *trp_info; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group( + pim, &grp); + if (trp_info == rp_all) { + pim_upstream_update(pim, up); + upstream_updated = true; + } + } + } + if (upstream_updated) + pim_zebra_update_all_interfaces(pim); + + pim_rp_check_interfaces(pim, rp_all); + pim_rp_refresh_group_to_rp_mapping(pim); + pim_find_or_track_nexthop(pim, nht_p, NULL, rp_all, + NULL); + + if (!pim_ecmp_nexthop_lookup(pim, + &rp_all->rp.source_nexthop, + nht_p, &rp_all->group, 1)) + return PIM_RP_NO_PATH; + return PIM_SUCCESS; + } + + /* + * Return if the group is already configured for this RP + */ + tmp_rp_info = pim_rp_find_exact(pim, rp_addr, &rp_info->group); + if (tmp_rp_info) { + if ((tmp_rp_info->rp_src != rp_src_flag) + && (rp_src_flag == RP_SRC_STATIC)) + tmp_rp_info->rp_src = rp_src_flag; + XFREE(MTYPE_PIM_RP, rp_info); + return result; + } + + /* + * Barf if this group is already covered by some other RP + */ + tmp_rp_info = pim_rp_find_match_group(pim, &rp_info->group); + + if (tmp_rp_info) { + if (tmp_rp_info->plist) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_GROUP_PFXLIST_OVERLAP; + } else { + /* + * If the only RP that covers this group is an + * RP configured for + * 224.0.0.0/4 that is fine, ignore that one. + * For all others + * though we must return PIM_GROUP_OVERLAP + */ + if (prefix_same(&rp_info->group, + &tmp_rp_info->group)) { + if ((rp_src_flag == RP_SRC_STATIC) + && (tmp_rp_info->rp_src + == RP_SRC_STATIC)) { + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_GROUP_OVERLAP; + } + + result = pim_rp_change( + pim, rp_addr, + tmp_rp_info->group, + rp_src_flag); + XFREE(MTYPE_PIM_RP, rp_info); + return result; + } + } + } + } + + listnode_add_sort(pim->rp_list, rp_info); + + if (!rp_info->plist) { + rn = route_node_get(pim->rp_table, &rp_info->group); + rn->info = rp_info; + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("Allocated: %p for rp_info: %p(%pFX) Lock: %d", rn, + rp_info, &rp_info->group, + rn ? route_node_get_lock_count(rn) : 0); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_addr_is_any(up->sg.src)) { + struct prefix grp; + struct rp_info *trp_info; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + + if (trp_info == rp_info) { + pim_upstream_update(pim, up); + upstream_updated = true; + } + } + } + + if (upstream_updated) + pim_zebra_update_all_interfaces(pim); + + pim_rp_check_interfaces(pim, rp_info); + pim_rp_refresh_group_to_rp_mapping(pim); + + /* Register addr with Zebra NHT */ + nht_p = rp_info->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: NHT Register RP addr %pPA grp %pFX with Zebra ", + __func__, &nht_p, &rp_info->group); + pim_find_or_track_nexthop(pim, nht_p, NULL, rp_info, NULL); + if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, nht_p, + &rp_info->group, 1)) + return PIM_RP_NO_PATH; + + return PIM_SUCCESS; +} + +void pim_rp_del_config(struct pim_instance *pim, pim_addr rp_addr, + const char *group_range, const char *plist) +{ + struct prefix group; + int result; + + if (group_range == NULL) + result = pim_get_all_mcast_group(&group); + else + result = str2prefix(group_range, &group); + + if (!result) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: String to prefix failed for %pPAs group", + __func__, &rp_addr); + return; + } + + pim_rp_del(pim, rp_addr, group, plist, RP_SRC_STATIC); +} + +int pim_rp_del(struct pim_instance *pim, pim_addr rp_addr, struct prefix group, + const char *plist, enum rp_source rp_src_flag) +{ + struct prefix g_all; + struct rp_info *rp_info; + struct rp_info *rp_all; + pim_addr nht_p; + struct route_node *rn; + bool was_plist = false; + struct rp_info *trp_info; + struct pim_upstream *up; + struct bsgrp_node *bsgrp = NULL; + struct bsm_rpinfo *bsrp = NULL; + bool upstream_updated = false; + + if (plist) + rp_info = pim_rp_find_prefix_list(pim, rp_addr, plist); + else + rp_info = pim_rp_find_exact(pim, rp_addr, &group); + + if (!rp_info) + return PIM_RP_NOT_FOUND; + + if (rp_info->plist) { + XFREE(MTYPE_PIM_FILTER_NAME, rp_info->plist); + was_plist = true; + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: Delete RP %pPA for the group %pFX", __func__, + &rp_addr, &group); + + /* While static RP is getting deleted, we need to check if dynamic RP + * present for the same group in BSM RP table, then install the dynamic + * RP for the group node into the main rp table + */ + if (rp_src_flag == RP_SRC_STATIC) { + bsgrp = pim_bsm_get_bsgrp_node(&pim->global_scope, &group); + + if (bsgrp) { + bsrp = bsm_rpinfos_first(bsgrp->bsrp_list); + if (bsrp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: BSM RP %pPA found for the group %pFX", + __func__, &bsrp->rp_address, + &group); + return pim_rp_change(pim, bsrp->rp_address, + group, RP_SRC_BSR); + } + } else { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: BSM RP not found for the group %pFX", + __func__, &group); + } + } + + /* Deregister addr with Zebra NHT */ + nht_p = rp_info->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: Deregister RP addr %pPA with Zebra ", __func__, + &nht_p); + pim_delete_tracked_nexthop(pim, nht_p, NULL, rp_info); + + if (!pim_get_all_mcast_group(&g_all)) + return PIM_RP_BAD_ADDRESS; + + rp_all = pim_rp_find_match_group(pim, &g_all); + + if (rp_all == rp_info) { + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* Find the upstream (*, G) whose upstream address is + * same as the deleted RP + */ + pim_addr rpf_addr; + + rpf_addr = rp_info->rp.rpf_addr; + if (!pim_addr_cmp(up->upstream_addr, rpf_addr) && + pim_addr_is_any(up->sg.src)) { + struct prefix grp; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + if (trp_info == rp_all) { + pim_upstream_rpf_clear(pim, up); + up->upstream_addr = PIMADDR_ANY; + } + } + } + rp_all->rp.rpf_addr = PIMADDR_ANY; + rp_all->i_am_rp = 0; + return PIM_SUCCESS; + } + + listnode_delete(pim->rp_list, rp_info); + + if (!was_plist) { + rn = route_node_get(pim->rp_table, &rp_info->group); + if (rn) { + if (rn->info != rp_info) + flog_err( + EC_LIB_DEVELOPMENT, + "Expected rn->info to be equal to rp_info"); + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s:Found for Freeing: %p for rp_info: %p(%pFX) Lock: %d", + __func__, rn, rp_info, &rp_info->group, + route_node_get_lock_count(rn)); + + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } + } + + pim_rp_refresh_group_to_rp_mapping(pim); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* Find the upstream (*, G) whose upstream address is same as + * the deleted RP + */ + pim_addr rpf_addr; + + rpf_addr = rp_info->rp.rpf_addr; + if (!pim_addr_cmp(up->upstream_addr, rpf_addr) && + pim_addr_is_any(up->sg.src)) { + struct prefix grp; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + + /* RP not found for the group grp */ + if (pim_rpf_addr_is_inaddr_any(&trp_info->rp)) { + pim_upstream_rpf_clear(pim, up); + pim_rp_set_upstream_addr( + pim, &up->upstream_addr, up->sg.src, + up->sg.grp); + } + + /* RP found for the group grp */ + else { + pim_upstream_update(pim, up); + upstream_updated = true; + } + } + } + + if (upstream_updated) + pim_zebra_update_all_interfaces(pim); + + XFREE(MTYPE_PIM_RP, rp_info); + return PIM_SUCCESS; +} + +int pim_rp_change(struct pim_instance *pim, pim_addr new_rp_addr, + struct prefix group, enum rp_source rp_src_flag) +{ + pim_addr nht_p; + struct route_node *rn; + int result = 0; + struct rp_info *rp_info = NULL; + struct pim_upstream *up; + bool upstream_updated = false; + pim_addr old_rp_addr; + + rn = route_node_lookup(pim->rp_table, &group); + if (!rn) { + result = pim_rp_new(pim, new_rp_addr, group, NULL, rp_src_flag); + return result; + } + + rp_info = rn->info; + + if (!rp_info) { + route_unlock_node(rn); + result = pim_rp_new(pim, new_rp_addr, group, NULL, rp_src_flag); + return result; + } + + old_rp_addr = rp_info->rp.rpf_addr; + if (!pim_addr_cmp(new_rp_addr, old_rp_addr)) { + if (rp_info->rp_src != rp_src_flag) { + rp_info->rp_src = rp_src_flag; + route_unlock_node(rn); + return PIM_SUCCESS; + } + } + + /* Deregister old RP addr with Zebra NHT */ + + if (!pim_addr_is_any(old_rp_addr)) { + nht_p = rp_info->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: Deregister RP addr %pPA with Zebra ", + __func__, &nht_p); + pim_delete_tracked_nexthop(pim, nht_p, NULL, rp_info); + } + + pim_rp_nexthop_del(rp_info); + listnode_delete(pim->rp_list, rp_info); + /* Update the new RP address*/ + + rp_info->rp.rpf_addr = new_rp_addr; + rp_info->rp_src = rp_src_flag; + rp_info->i_am_rp = 0; + + listnode_add_sort(pim->rp_list, rp_info); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_addr_is_any(up->sg.src)) { + struct prefix grp; + struct rp_info *trp_info; + + pim_addr_to_prefix(&grp, up->sg.grp); + trp_info = pim_rp_find_match_group(pim, &grp); + + if (trp_info == rp_info) { + pim_upstream_update(pim, up); + upstream_updated = true; + } + } + } + + if (upstream_updated) + pim_zebra_update_all_interfaces(pim); + + /* Register new RP addr with Zebra NHT */ + nht_p = rp_info->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: NHT Register RP addr %pPA grp %pFX with Zebra ", + __func__, &nht_p, &rp_info->group); + + pim_find_or_track_nexthop(pim, nht_p, NULL, rp_info, NULL); + if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, nht_p, + &rp_info->group, 1)) { + route_unlock_node(rn); + return PIM_RP_NO_PATH; + } + + pim_rp_check_interfaces(pim, rp_info); + + route_unlock_node(rn); + + pim_rp_refresh_group_to_rp_mapping(pim); + + return result; +} + +void pim_rp_setup(struct pim_instance *pim) +{ + struct listnode *node; + struct rp_info *rp_info; + pim_addr nht_p; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + nht_p = rp_info->rp.rpf_addr; + + pim_find_or_track_nexthop(pim, nht_p, NULL, rp_info, NULL); + if (!pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, + nht_p, &rp_info->group, 1)) { + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug( + "Unable to lookup nexthop for rp specified"); + pim_rp_nexthop_del(rp_info); + } + } +} + +/* + * Checks to see if we should elect ourself the actual RP when new if + * addresses are added against an interface. + */ +void pim_rp_check_on_if_add(struct pim_interface *pim_ifp) +{ + struct listnode *node; + struct rp_info *rp_info; + bool i_am_rp_changed = false; + struct pim_instance *pim = pim_ifp->pim; + + if (pim->rp_list == NULL) + return; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + /* if i_am_rp is already set nothing to be done (adding new + * addresses + * is not going to make a difference). */ + if (rp_info->i_am_rp) { + continue; + } + + if (pim_rp_check_interface_addrs(rp_info, pim_ifp)) { + i_am_rp_changed = true; + rp_info->i_am_rp = 1; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: %pPA: i am rp", __func__, + &rp_info->rp.rpf_addr); + } + } + + if (i_am_rp_changed) { + pim_msdp_i_am_rp_changed(pim); + pim_upstream_reeval_use_rpt(pim); + } +} + +/* up-optimized re-evaluation of "i_am_rp". this is used when ifaddresses + * are removed. Removing numbers is an uncommon event in an active network + * so I have made no attempt to optimize it. */ +void pim_i_am_rp_re_evaluate(struct pim_instance *pim) +{ + struct listnode *node; + struct rp_info *rp_info; + bool i_am_rp_changed = false; + int old_i_am_rp; + + if (pim->rp_list == NULL) + return; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + old_i_am_rp = rp_info->i_am_rp; + pim_rp_check_interfaces(pim, rp_info); + + if (old_i_am_rp != rp_info->i_am_rp) { + i_am_rp_changed = true; + if (PIM_DEBUG_PIM_NHT_RP) { + if (rp_info->i_am_rp) + zlog_debug("%s: %pPA: i am rp", + __func__, + &rp_info->rp.rpf_addr); + else + zlog_debug( + "%s: %pPA: i am no longer rp", + __func__, + &rp_info->rp.rpf_addr); + } + } + } + + if (i_am_rp_changed) { + pim_msdp_i_am_rp_changed(pim); + pim_upstream_reeval_use_rpt(pim); + } +} + +/* + * I_am_RP(G) is true if the group-to-RP mapping indicates that + * this router is the RP for the group. + * + * Since we only have static RP, all groups are part of this RP + */ +int pim_rp_i_am_rp(struct pim_instance *pim, pim_addr group) +{ + struct prefix g; + struct rp_info *rp_info; + + memset(&g, 0, sizeof(g)); + pim_addr_to_prefix(&g, group); + rp_info = pim_rp_find_match_group(pim, &g); + + if (rp_info) + return rp_info->i_am_rp; + return 0; +} + +/* + * RP(G) + * + * Return the RP that the Group belongs too. + */ +struct pim_rpf *pim_rp_g(struct pim_instance *pim, pim_addr group) +{ + struct prefix g; + struct rp_info *rp_info; + + memset(&g, 0, sizeof(g)); + pim_addr_to_prefix(&g, group); + + rp_info = pim_rp_find_match_group(pim, &g); + + if (rp_info) { + pim_addr nht_p; + + if (pim_addr_is_any(rp_info->rp.rpf_addr)) { + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug( + "%s: Skipping NHT Register since RP is not configured for the group %pPA", + __func__, &group); + return &rp_info->rp; + } + + /* Register addr with Zebra NHT */ + nht_p = rp_info->rp.rpf_addr; + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug( + "%s: NHT Register RP addr %pPA grp %pFX with Zebra", + __func__, &nht_p, &rp_info->group); + pim_find_or_track_nexthop(pim, nht_p, NULL, rp_info, NULL); + pim_rpf_set_refresh_time(pim); + (void)pim_ecmp_nexthop_lookup(pim, &rp_info->rp.source_nexthop, + nht_p, &rp_info->group, 1); + return (&rp_info->rp); + } + + // About to Go Down + return NULL; +} + +/* + * Set the upstream IP address we want to talk to based upon + * the rp configured and the source address + * + * If we have don't have a RP configured and the source address is * + * then set the upstream addr as INADDR_ANY and return failure. + * + */ +int pim_rp_set_upstream_addr(struct pim_instance *pim, pim_addr *up, + pim_addr source, pim_addr group) +{ + struct rp_info *rp_info; + struct prefix g; + + memset(&g, 0, sizeof(g)); + + pim_addr_to_prefix(&g, group); + + rp_info = pim_rp_find_match_group(pim, &g); + + if (!rp_info || ((pim_rpf_addr_is_inaddr_any(&rp_info->rp)) && + (pim_addr_is_any(source)))) { + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug("%s: Received a (*,G) with no RP configured", + __func__); + *up = PIMADDR_ANY; + return 0; + } + + if (pim_addr_is_any(source)) + *up = rp_info->rp.rpf_addr; + else + *up = source; + + return 1; +} + +int pim_rp_config_write(struct pim_instance *pim, struct vty *vty, + const char *spaces) +{ + struct listnode *node; + struct rp_info *rp_info; + int count = 0; + pim_addr rp_addr; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + if (rp_info->rp_src == RP_SRC_BSR) + continue; + + rp_addr = rp_info->rp.rpf_addr; + if (rp_info->plist) + vty_out(vty, + "%s" PIM_AF_NAME + " pim rp %pPA prefix-list %s\n", + spaces, &rp_addr, rp_info->plist); + else + vty_out(vty, "%s" PIM_AF_NAME " pim rp %pPA %pFX\n", + spaces, &rp_addr, &rp_info->group); + count++; + } + + return count; +} + +void pim_rp_show_information(struct pim_instance *pim, struct prefix *range, + struct vty *vty, json_object *json) +{ + struct rp_info *rp_info; + struct rp_info *prev_rp_info = NULL; + struct listnode *node; + struct ttable *tt = NULL; + char *table = NULL; + char source[7]; + char grp[INET6_ADDRSTRLEN]; + + json_object *json_rp_rows = NULL; + json_object *json_row = NULL; + + if (!json) { + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row( + tt, + "RP address|group/prefix-list|OIF|I am RP|Source|Group-Type"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + } + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + +#if PIM_IPV == 4 + pim_addr group = rp_info->group.u.prefix4; +#else + pim_addr group = rp_info->group.u.prefix6; +#endif + const char *group_type = + pim_is_grp_ssm(pim, group) ? "SSM" : "ASM"; + + if (range && !prefix_match(&rp_info->group, range)) + continue; + + if (rp_info->rp_src == RP_SRC_STATIC) + strlcpy(source, "Static", sizeof(source)); + else if (rp_info->rp_src == RP_SRC_BSR) + strlcpy(source, "BSR", sizeof(source)); + else + strlcpy(source, "None", sizeof(source)); + if (json) { + /* + * If we have moved on to a new RP then add the + * entry for the previous RP + */ + if (prev_rp_info && + (pim_addr_cmp(prev_rp_info->rp.rpf_addr, + rp_info->rp.rpf_addr))) { + json_object_object_addf( + json, json_rp_rows, "%pPA", + &prev_rp_info->rp.rpf_addr); + json_rp_rows = NULL; + } + + if (!json_rp_rows) + json_rp_rows = json_object_new_array(); + + json_row = json_object_new_object(); + json_object_string_addf(json_row, "rpAddress", "%pPA", + &rp_info->rp.rpf_addr); + if (rp_info->rp.source_nexthop.interface) + json_object_string_add( + json_row, "outboundInterface", + rp_info->rp.source_nexthop + .interface->name); + else + json_object_string_add(json_row, + "outboundInterface", + "Unknown"); + if (rp_info->i_am_rp) + json_object_boolean_true_add(json_row, "iAmRP"); + else + json_object_boolean_false_add(json_row, + "iAmRP"); + + if (rp_info->plist) + json_object_string_add(json_row, "prefixList", + rp_info->plist); + else + json_object_string_addf(json_row, "group", + "%pFX", + &rp_info->group); + json_object_string_add(json_row, "source", source); + json_object_string_add(json_row, "groupType", + group_type); + + json_object_array_add(json_rp_rows, json_row); + } else { + prefix2str(&rp_info->group, grp, sizeof(grp)); + ttable_add_row(tt, "%pPA|%s|%s|%s|%s|%s", + &rp_info->rp.rpf_addr, + rp_info->plist + ? rp_info->plist + : grp, + rp_info->rp.source_nexthop.interface + ? rp_info->rp.source_nexthop + .interface->name + : "Unknown", + rp_info->i_am_rp + ? "yes" + : "no", + source, group_type); + } + prev_rp_info = rp_info; + } + + /* Dump the generated table. */ + if (!json) { + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + ttable_del(tt); + } else { + if (prev_rp_info && json_rp_rows) + json_object_object_addf(json, json_rp_rows, "%pPA", + &prev_rp_info->rp.rpf_addr); + } +} + +void pim_resolve_rp_nh(struct pim_instance *pim, struct pim_neighbor *nbr) +{ + struct listnode *node = NULL; + struct rp_info *rp_info = NULL; + struct nexthop *nh_node = NULL; + pim_addr nht_p; + struct pim_nexthop_cache pnc; + + for (ALL_LIST_ELEMENTS_RO(pim->rp_list, node, rp_info)) { + if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) + continue; + + nht_p = rp_info->rp.rpf_addr; + memset(&pnc, 0, sizeof(struct pim_nexthop_cache)); + if (!pim_find_or_track_nexthop(pim, nht_p, NULL, rp_info, &pnc)) + continue; + + for (nh_node = pnc.nexthop; nh_node; nh_node = nh_node->next) { +#if PIM_IPV == 4 + if (!pim_addr_is_any(nh_node->gate.ipv4)) + continue; +#else + if (!pim_addr_is_any(nh_node->gate.ipv6)) + continue; +#endif + + struct interface *ifp1 = if_lookup_by_index( + nh_node->ifindex, pim->vrf->vrf_id); + + if (nbr->interface != ifp1) + continue; + +#if PIM_IPV == 4 + nh_node->gate.ipv4 = nbr->source_addr; +#else + nh_node->gate.ipv6 = nbr->source_addr; +#endif + if (PIM_DEBUG_PIM_NHT_RP) + zlog_debug( + "%s: addr %pPA new nexthop addr %pPAs interface %s", + __func__, &nht_p, &nbr->source_addr, + ifp1->name); + } + } +} diff --git a/pimd/pim_rp.h b/pimd/pim_rp.h new file mode 100644 index 0000000..9416a9a --- /dev/null +++ b/pimd/pim_rp.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2015 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef PIM_RP_H +#define PIM_RP_H + +#include +#include "prefix.h" +#include "vty.h" +#include "plist.h" +#include "pim_rpf.h" +#include "lib/json.h" + +struct pim_interface; + +enum rp_source { + RP_SRC_NONE = 0, + RP_SRC_STATIC, + RP_SRC_BSR +}; + +struct rp_info { + struct prefix group; + struct pim_rpf rp; + enum rp_source rp_src; + int i_am_rp; + char *plist; +}; + +void pim_rp_init(struct pim_instance *pim); +void pim_rp_free(struct pim_instance *pim); + +void pim_rp_list_hash_clean(void *data); + +int pim_rp_new(struct pim_instance *pim, pim_addr rp_addr, struct prefix group, + const char *plist, enum rp_source rp_src_flag); +void pim_rp_del_config(struct pim_instance *pim, pim_addr rp_addr, + const char *group, const char *plist); +int pim_rp_del(struct pim_instance *pim, pim_addr rp_addr, struct prefix group, + const char *plist, enum rp_source rp_src_flag); +int pim_rp_change(struct pim_instance *pim, pim_addr new_rp_addr, + struct prefix group, enum rp_source rp_src_flag); +void pim_rp_prefix_list_update(struct pim_instance *pim, + struct prefix_list *plist); + +int pim_rp_config_write(struct pim_instance *pim, struct vty *vty, + const char *spaces); + +void pim_rp_setup(struct pim_instance *pim); + +int pim_rp_i_am_rp(struct pim_instance *pim, pim_addr group); +void pim_rp_check_on_if_add(struct pim_interface *pim_ifp); +void pim_i_am_rp_re_evaluate(struct pim_instance *pim); + +bool pim_rp_check_is_my_ip_address(struct pim_instance *pim, + struct in_addr dest_addr); + +int pim_rp_set_upstream_addr(struct pim_instance *pim, pim_addr *up, + pim_addr source, pim_addr group); + +struct pim_rpf *pim_rp_g(struct pim_instance *pim, pim_addr group); + +#define I_am_RP(P, G) pim_rp_i_am_rp ((P), (G)) +#define RP(P, G) pim_rp_g ((P), (G)) + +void pim_rp_show_information(struct pim_instance *pim, struct prefix *range, + struct vty *vty, json_object *json); +void pim_resolve_rp_nh(struct pim_instance *pim, struct pim_neighbor *nbr); +int pim_rp_list_cmp(void *v1, void *v2); +struct rp_info *pim_rp_find_match_group(struct pim_instance *pim, + const struct prefix *group); +void pim_upstream_update(struct pim_instance *pim, struct pim_upstream *up); +void pim_rp_refresh_group_to_rp_mapping(struct pim_instance *pim); +#endif diff --git a/pimd/pim_rpf.c b/pimd/pim_rpf.c new file mode 100644 index 0000000..d18ec49 --- /dev/null +++ b/pimd/pim_rpf.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" + +#include "log.h" +#include "prefix.h" +#include "memory.h" +#include "jhash.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_rpf.h" +#include "pim_pim.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_neighbor.h" +#include "pim_zlookup.h" +#include "pim_ifchannel.h" +#include "pim_time.h" +#include "pim_nht.h" +#include "pim_oil.h" +#include "pim_mlag.h" + +static pim_addr pim_rpf_find_rpf_addr(struct pim_upstream *up); + +void pim_rpf_set_refresh_time(struct pim_instance *pim) +{ + pim->last_route_change_time = pim_time_monotonic_usec(); + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_debug("%s: vrf(%s) New last route change time: %" PRId64, + __func__, pim->vrf->name, + pim->last_route_change_time); +} + +bool pim_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, + pim_addr addr, int neighbor_needed) +{ + struct pim_zlookup_nexthop nexthop_tab[router->multipath]; + struct pim_neighbor *nbr = NULL; + int num_ifindex; + struct interface *ifp = NULL; + ifindex_t first_ifindex = 0; + int found = 0; + int i = 0; + struct pim_interface *pim_ifp; + +#if PIM_IPV == 4 + /* + * We should not attempt to lookup a + * 255.255.255.255 address, since + * it will never work + */ + if (pim_addr_is_any(addr)) + return false; +#endif + + if ((!pim_addr_cmp(nexthop->last_lookup, addr)) && + (nexthop->last_lookup_time > pim->last_route_change_time)) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: Using last lookup for %pPAs at %lld, %" PRId64 + " addr %pPAs", + __func__, &addr, nexthop->last_lookup_time, + pim->last_route_change_time, + &nexthop->mrib_nexthop_addr); + pim->nexthop_lookups_avoided++; + return true; + } else { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: Looking up: %pPAs, last lookup time: %lld, %" PRId64, + __func__, &addr, nexthop->last_lookup_time, + pim->last_route_change_time); + } + + memset(nexthop_tab, 0, + sizeof(struct pim_zlookup_nexthop) * router->multipath); + num_ifindex = + zclient_lookup_nexthop(pim, nexthop_tab, router->multipath, + addr, PIM_NEXTHOP_LOOKUP_MAX); + if (num_ifindex < 1) { + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s %s: could not find nexthop ifindex for address %pPAs", + __FILE__, __func__, &addr); + return false; + } + + while (!found && (i < num_ifindex)) { + first_ifindex = nexthop_tab[i].ifindex; + + ifp = if_lookup_by_index(first_ifindex, pim->vrf->vrf_id); + if (!ifp) { + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s %s: could not find interface for ifindex %d (address %pPAs)", + __FILE__, __func__, first_ifindex, + &addr); + i++; + continue; + } + + pim_ifp = ifp->info; + if (!pim_ifp || !pim_ifp->pim_enable) { + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s: pim not enabled on input interface %s (ifindex=%d, RPF for source %pPAs)", + __func__, ifp->name, first_ifindex, + &addr); + i++; + } else if (neighbor_needed && + !pim_if_connected_to_source(ifp, addr)) { + nbr = pim_neighbor_find( + ifp, nexthop_tab[i].nexthop_addr, true); + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_debug("ifp name: %s, pim nbr: %p", + ifp->name, nbr); + if (!nbr && !if_is_loopback(ifp)) + i++; + else + found = 1; + } else + found = 1; + } + + if (found) { + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s %s: found nexthop %pPAs for address %pPAs: interface %s ifindex=%d metric=%d pref=%d", + __FILE__, __func__, + &nexthop_tab[i].nexthop_addr, &addr, ifp->name, + first_ifindex, nexthop_tab[i].route_metric, + nexthop_tab[i].protocol_distance); + + /* update nexthop data */ + nexthop->interface = ifp; + nexthop->mrib_nexthop_addr = nexthop_tab[i].nexthop_addr; + nexthop->mrib_metric_preference = + nexthop_tab[i].protocol_distance; + nexthop->mrib_route_metric = nexthop_tab[i].route_metric; + nexthop->last_lookup = addr; + nexthop->last_lookup_time = pim_time_monotonic_usec(); + nexthop->nbr = nbr; + return true; + } else + return false; +} + +static int nexthop_mismatch(const struct pim_nexthop *nh1, + const struct pim_nexthop *nh2) +{ + return (nh1->interface != nh2->interface) || + (pim_addr_cmp(nh1->mrib_nexthop_addr, nh2->mrib_nexthop_addr)) || + (nh1->mrib_metric_preference != nh2->mrib_metric_preference) || + (nh1->mrib_route_metric != nh2->mrib_route_metric); +} + +static void pim_rpf_cost_change(struct pim_instance *pim, + struct pim_upstream *up, uint32_t old_cost) +{ + struct pim_rpf *rpf = &up->rpf; + uint32_t new_cost; + + new_cost = pim_up_mlag_local_cost(up); + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: Cost_to_rp of upstream-%s changed to:%u, from:%u", + __func__, up->sg_str, new_cost, old_cost); + + if (old_cost == new_cost) + return; + + /* Cost changed, it might Impact MLAG DF election, update */ + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s: Cost_to_rp of upstream-%s changed to:%u", + __func__, up->sg_str, + rpf->source_nexthop.mrib_route_metric); + + if (pim_up_mlag_is_local(up)) + pim_mlag_up_local_add(pim, up); +} + +enum pim_rpf_result pim_rpf_update(struct pim_instance *pim, + struct pim_upstream *up, struct pim_rpf *old, + const char *caller) +{ + struct pim_rpf *rpf = &up->rpf; + struct pim_rpf saved; + pim_addr src; + struct prefix grp; + bool neigh_needed = true; + uint32_t saved_mrib_route_metric; + + if (PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags)) + return PIM_RPF_OK; + + if (pim_addr_is_any(up->upstream_addr)) { + zlog_debug("%s(%s): RP is not configured yet for %s", + __func__, caller, up->sg_str); + return PIM_RPF_OK; + } + + saved.source_nexthop = rpf->source_nexthop; + saved.rpf_addr = rpf->rpf_addr; + saved_mrib_route_metric = pim_up_mlag_local_cost(up); + if (old) { + old->source_nexthop = saved.source_nexthop; + old->rpf_addr = saved.rpf_addr; + } + + src = up->upstream_addr; // RP or Src address + pim_addr_to_prefix(&grp, up->sg.grp); + + if ((pim_addr_is_any(up->sg.src) && I_am_RP(pim, up->sg.grp)) || + PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) + neigh_needed = false; + pim_find_or_track_nexthop(pim, up->upstream_addr, up, NULL, NULL); + if (!pim_ecmp_nexthop_lookup(pim, &rpf->source_nexthop, src, &grp, + neigh_needed)) { + /* Route is Deleted in Zebra, reset the stored NH data */ + pim_upstream_rpf_clear(pim, up); + pim_rpf_cost_change(pim, up, saved_mrib_route_metric); + return PIM_RPF_FAILURE; + } + + rpf->rpf_addr = pim_rpf_find_rpf_addr(up); + + if (pim_rpf_addr_is_inaddr_any(rpf) && PIM_DEBUG_ZEBRA) { + /* RPF'(S,G) not found */ + zlog_debug("%s(%s): RPF'%s not found: won't send join upstream", + __func__, caller, up->sg_str); + /* warning only */ + } + + /* detect change in pim_nexthop */ + if (nexthop_mismatch(&rpf->source_nexthop, &saved.source_nexthop)) { + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s(%s): (S,G)=%s source nexthop now is: interface=%s address=%pPAs pref=%d metric=%d", + __func__, caller, + up->sg_str, + rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "", + &rpf->source_nexthop.mrib_nexthop_addr, + rpf->source_nexthop.mrib_metric_preference, + rpf->source_nexthop.mrib_route_metric); + + pim_upstream_update_join_desired(pim, up); + pim_upstream_update_could_assert(up); + pim_upstream_update_my_assert_metric(up); + } + + /* detect change in RPF_interface(S) */ + if (saved.source_nexthop.interface != rpf->source_nexthop.interface) { + + if (PIM_DEBUG_ZEBRA) { + zlog_debug("%s(%s): (S,G)=%s RPF_interface(S) changed from %s to %s", + __func__, caller, + up->sg_str, + saved.source_nexthop.interface ? saved.source_nexthop.interface->name : "", + rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""); + /* warning only */ + } + + pim_upstream_rpf_interface_changed( + up, saved.source_nexthop.interface); + } + + /* detect change in RPF'(S,G) */ + if (pim_addr_cmp(saved.rpf_addr, rpf->rpf_addr) || + saved.source_nexthop.interface != rpf->source_nexthop.interface) { + pim_rpf_cost_change(pim, up, saved_mrib_route_metric); + return PIM_RPF_CHANGED; + } + + if (PIM_DEBUG_MLAG) + zlog_debug( + "%s(%s): Cost_to_rp of upstream-%s changed to:%u", + __func__, caller, up->sg_str, + rpf->source_nexthop.mrib_route_metric); + + pim_rpf_cost_change(pim, up, saved_mrib_route_metric); + + return PIM_RPF_OK; +} + +/* + * In the case of RP deletion and RP unreachablity, + * uninstall the mroute in the kernel and clear the + * rpf information in the pim upstream and pim channel + * oil data structure. + */ +void pim_upstream_rpf_clear(struct pim_instance *pim, + struct pim_upstream *up) +{ + if (up->rpf.source_nexthop.interface) { + pim_upstream_switch(pim, up, PIM_UPSTREAM_NOTJOINED); + up->rpf.source_nexthop.interface = NULL; + up->rpf.source_nexthop.mrib_nexthop_addr = PIMADDR_ANY; + up->rpf.source_nexthop.mrib_metric_preference = + router->infinite_assert_metric.metric_preference; + up->rpf.source_nexthop.mrib_route_metric = + router->infinite_assert_metric.route_metric; + up->rpf.rpf_addr = PIMADDR_ANY; + pim_upstream_mroute_iif_update(up->channel_oil, __func__); + } +} + +/* + RFC 4601: 4.1.6. State Summarization Macros + + neighbor RPF'(S,G) { + if ( I_Am_Assert_Loser(S, G, RPF_interface(S) )) { + return AssertWinner(S, G, RPF_interface(S) ) + } else { + return NBR( RPF_interface(S), MRIB.next_hop( S ) ) + } + } + + RPF'(*,G) and RPF'(S,G) indicate the neighbor from which data + packets should be coming and to which joins should be sent on the RP + tree and SPT, respectively. +*/ +static pim_addr pim_rpf_find_rpf_addr(struct pim_upstream *up) +{ + struct pim_ifchannel *rpf_ch; + struct pim_neighbor *neigh; + pim_addr rpf_addr; + + if (!up->rpf.source_nexthop.interface) { + zlog_warn("%s: missing RPF interface for upstream (S,G)=%s", + __func__, up->sg_str); + + return PIMADDR_ANY; + } + + rpf_ch = pim_ifchannel_find(up->rpf.source_nexthop.interface, &up->sg); + if (rpf_ch) { + if (rpf_ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + return rpf_ch->ifassert_winner; + } + } + + /* return NBR( RPF_interface(S), MRIB.next_hop( S ) ) */ + + neigh = pim_if_find_neighbor(up->rpf.source_nexthop.interface, + up->rpf.source_nexthop.mrib_nexthop_addr); + if (neigh) + rpf_addr = neigh->source_addr; + else + rpf_addr = PIMADDR_ANY; + + return rpf_addr; +} + +int pim_rpf_addr_is_inaddr_any(struct pim_rpf *rpf) +{ + return pim_addr_is_any(rpf->rpf_addr); +} + +int pim_rpf_is_same(struct pim_rpf *rpf1, struct pim_rpf *rpf2) +{ + if (rpf1->source_nexthop.interface == rpf2->source_nexthop.interface) + return 1; + + return 0; +} + +unsigned int pim_rpf_hash_key(const void *arg) +{ + const struct pim_nexthop_cache *r = arg; + +#if PIM_IPV == 4 + return jhash_1word(r->rpf.rpf_addr.s_addr, 0); +#else + return jhash2(r->rpf.rpf_addr.s6_addr32, + array_size(r->rpf.rpf_addr.s6_addr32), 0); +#endif +} + +bool pim_rpf_equal(const void *arg1, const void *arg2) +{ + const struct pim_nexthop_cache *r1 = + (const struct pim_nexthop_cache *)arg1; + const struct pim_nexthop_cache *r2 = + (const struct pim_nexthop_cache *)arg2; + + return (!pim_addr_cmp(r1->rpf.rpf_addr, r2->rpf.rpf_addr)); +} diff --git a/pimd/pim_rpf.h b/pimd/pim_rpf.h new file mode 100644 index 0000000..7dae53f --- /dev/null +++ b/pimd/pim_rpf.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_RPF_H +#define PIM_RPF_H + +#include +#include "pim_str.h" + +struct pim_instance; + +/* + RFC 4601: + + Metric Preference + Preference value assigned to the unicast routing protocol that + provided the route to the multicast source or Rendezvous-Point. + + Metric + The unicast routing table metric associated with the route used to + reach the multicast source or Rendezvous-Point. The metric is in + units applicable to the unicast routing protocol used. +*/ +struct pim_nexthop { + pim_addr last_lookup; + long long last_lookup_time; + struct interface *interface; /* RPF_interface(S) */ + pim_addr mrib_nexthop_addr; /* MRIB.next_hop(S) */ + uint32_t mrib_metric_preference; /* MRIB.pref(S) */ + uint32_t mrib_route_metric; /* MRIB.metric(S) */ + struct pim_neighbor *nbr; +}; + +struct pim_rpf { + struct pim_nexthop source_nexthop; + pim_addr rpf_addr; /* RPF'(S,G) */ +}; + +enum pim_rpf_result { PIM_RPF_OK = 0, PIM_RPF_CHANGED, PIM_RPF_FAILURE }; + +struct pim_upstream; + +unsigned int pim_rpf_hash_key(const void *arg); +bool pim_rpf_equal(const void *arg1, const void *arg2); + +bool pim_nexthop_lookup(struct pim_instance *pim, struct pim_nexthop *nexthop, + pim_addr addr, int neighbor_needed); +enum pim_rpf_result pim_rpf_update(struct pim_instance *pim, + struct pim_upstream *up, + struct pim_rpf *old, const char *caller); +void pim_upstream_rpf_clear(struct pim_instance *pim, + struct pim_upstream *up); +int pim_rpf_addr_is_inaddr_any(struct pim_rpf *rpf); + +int pim_rpf_is_same(struct pim_rpf *rpf1, struct pim_rpf *rpf2); +void pim_rpf_set_refresh_time(struct pim_instance *pim); +#endif /* PIM_RPF_H */ diff --git a/pimd/pim_signals.c b/pimd/pim_signals.c new file mode 100644 index 0000000..146a4e9 --- /dev/null +++ b/pimd/pim_signals.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include + +#include "sigevent.h" +#include "memory.h" +#include "log.h" +#include "if.h" + +#include "pim_signals.h" +#include "pimd.h" + +/* + * Signal handlers + */ + +static void pim_sighup(void) +{ + zlog_info("SIGHUP received, ignoring"); +} + +static void pim_sigint(void) +{ + zlog_notice("Terminating on signal SIGINT"); + pim_terminate(); + exit(1); +} + +static void pim_sigterm(void) +{ + zlog_notice("Terminating on signal SIGTERM"); + pim_terminate(); + exit(1); +} + +static void pim_sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t pimd_signals[] = { + { + .signal = SIGHUP, + .handler = &pim_sighup, + }, + { + .signal = SIGUSR1, + .handler = &pim_sigusr1, + }, + { + .signal = SIGINT, + .handler = &pim_sigint, + }, + { + .signal = SIGTERM, + .handler = &pim_sigterm, + }, +}; diff --git a/pimd/pim_signals.h b/pimd/pim_signals.h new file mode 100644 index 0000000..a3ee604 --- /dev/null +++ b/pimd/pim_signals.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_SIGNALS_H +#define PIM_SIGNALS_H + +#include "sigevent.h" +extern struct frr_signal_t pimd_signals[]; + +#endif /* PIM_SIGNALS_H */ diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c new file mode 100644 index 0000000..3476c17 --- /dev/null +++ b/pimd/pim_sock.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "privs.h" +#include "if.h" +#include "vrf.h" +#include "sockopt.h" +#include "lib_errors.h" +#include "network.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_mroute.h" +#include "pim_iface.h" +#include "pim_sock.h" +#include "pim_str.h" + +#if PIM_IPV == 4 +#define setsockopt_iptos setsockopt_ipv4_tos +#define setsockopt_multicast_loop setsockopt_ipv4_multicast_loop +#else +#define setsockopt_iptos setsockopt_ipv6_tclass +#define setsockopt_multicast_loop setsockopt_ipv6_multicast_loop +#endif + +int pim_socket_raw(int protocol) +{ + int fd; + + frr_with_privs(&pimd_privs) { + fd = socket(PIM_AF, SOCK_RAW, protocol); + } + + if (fd < 0) { + zlog_warn("Could not create raw socket: errno=%d: %s", errno, + safe_strerror(errno)); + return PIM_SOCK_ERR_SOCKET; + } + + return fd; +} + +void pim_socket_ip_hdr(int fd) +{ + frr_with_privs(&pimd_privs) { +#if PIM_IPV == 4 + const int on = 1; + + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))) + zlog_err("%s: Could not turn on IP_HDRINCL option: %m", + __func__); +#endif + } +} + +/* + * Given a socket and a interface, + * Bind that socket to that interface + */ +int pim_socket_bind(int fd, struct interface *ifp) +{ + int ret = 0; + +#ifdef SO_BINDTODEVICE + frr_with_privs(&pimd_privs) { + ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifp->name, + strlen(ifp->name)); + } +#endif + return ret; +} + +#if PIM_IPV == 4 +static inline int pim_setsockopt(int protocol, int fd, struct interface *ifp) +{ + int one = 1; + int ttl = 1; + +#if defined(HAVE_IP_PKTINFO) + /* Linux and Solaris IP_PKTINFO */ + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one))) + zlog_warn("Could not set PKTINFO on socket fd=%d: %m", fd); +#elif defined(HAVE_IP_RECVDSTADDR) + /* BSD IP_RECVDSTADDR */ + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &one, sizeof(one))) + zlog_warn("Could not set IP_RECVDSTADDR on socket fd=%d: %m", + fd); +#else + flog_err( + EC_LIB_DEVELOPMENT, + "Missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()"); + close(fd); + return PIM_SOCK_ERR_DSTADDR; +#endif + + /* Set router alert (RFC 2113) for all IGMP messages (RFC + * 3376 4. Message Formats)*/ + if (protocol == IPPROTO_IGMP) { + uint8_t ra[4]; + + ra[0] = 148; + ra[1] = 4; + ra[2] = 0; + ra[3] = 0; + if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, ra, 4)) { + zlog_warn( + "Could not set Router Alert Option on socket fd=%d: %m", + fd); + close(fd); + return PIM_SOCK_ERR_RA; + } + } + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))) { + zlog_warn("Could not set multicast TTL=%d on socket fd=%d: %m", + ttl, fd); + close(fd); + return PIM_SOCK_ERR_TTL; + } + + if (setsockopt_ipv4_multicast_if(fd, PIMADDR_ANY, ifp->ifindex)) { + zlog_warn( + "Could not set Outgoing Interface Option on socket fd=%d: %m", + fd); + close(fd); + return PIM_SOCK_ERR_IFACE; + } + + return 0; +} +#else /* PIM_IPV != 4 */ +static inline int pim_setsockopt(int protocol, int fd, struct interface *ifp) +{ + int ttl = 1; + struct ipv6_mreq mreq = {}; + + setsockopt_ipv6_pktinfo(fd, 1); + setsockopt_ipv6_multicast_hops(fd, ttl); + + mreq.ipv6mr_interface = ifp->ifindex; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mreq, + sizeof(mreq))) { + zlog_warn( + "Could not set Outgoing Interface Option on socket fd=%d: %m", + fd); + close(fd); + return PIM_SOCK_ERR_IFACE; + } + + return 0; +} +#endif + +int pim_reg_sock(void) +{ + int fd; + long flags; + + frr_with_privs (&pimd_privs) { + fd = socket(PIM_AF, SOCK_RAW, PIM_PROTO_REG); + } + + if (fd < 0) { + zlog_warn("Could not create raw socket: errno=%d: %s", errno, + safe_strerror(errno)); + return PIM_SOCK_ERR_SOCKET; + } + + if (sockopt_reuseaddr(fd)) { + close(fd); + return PIM_SOCK_ERR_REUSE; + } + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + zlog_warn( + "Could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", + fd, errno, safe_strerror(errno)); + close(fd); + return PIM_SOCK_ERR_NONBLOCK_GETFL; + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) { + zlog_warn( + "Could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s", + fd, errno, safe_strerror(errno)); + close(fd); + return PIM_SOCK_ERR_NONBLOCK_SETFL; + } + + return fd; +} + +int pim_socket_mcast(int protocol, pim_addr ifaddr, struct interface *ifp, + uint8_t loop) +{ + int fd; + int ret; + + fd = pim_socket_raw(protocol); + if (fd < 0) { + zlog_warn("Could not create multicast socket: errno=%d: %s", + errno, safe_strerror(errno)); + return PIM_SOCK_ERR_SOCKET; + } + + /* XXX: if SO_BINDTODEVICE isn't available, use IP_PKTINFO / IP_RECVIF + * to emulate behaviour? Or change to only use 1 socket for all + * interfaces? */ + ret = pim_socket_bind(fd, ifp); + if (ret) { + close(fd); + zlog_warn("Could not set fd: %d for interface: %s to device", + fd, ifp->name); + return PIM_SOCK_ERR_BIND; + } + + set_nonblocking(fd); + sockopt_reuseaddr(fd); + setsockopt_so_recvbuf(fd, 8 * 1024 * 1024); + + ret = pim_setsockopt(protocol, fd, ifp); + if (ret) { + zlog_warn("pim_setsockopt failed for interface: %s to device ", + ifp->name); + return ret; + } + + /* leftover common sockopts */ + if (setsockopt_multicast_loop(fd, loop)) { + zlog_warn( + "Could not %s Multicast Loopback Option on socket fd=%d: %m", + loop ? "enable" : "disable", fd); + close(fd); + return PIM_SOCK_ERR_LOOP; + } + + /* Set Tx socket DSCP byte */ + if (setsockopt_iptos(fd, IPTOS_PREC_INTERNETCONTROL)) + zlog_warn("can't set sockopt IP[V6]_TOS to socket %d: %m", fd); + + return fd; +} + +int pim_socket_join(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, + struct pim_interface *pim_ifp) +{ + int ret; + +#if PIM_IPV == 4 + ret = setsockopt_ipv4_multicast(fd, IP_ADD_MEMBERSHIP, ifaddr, + group.s_addr, ifindex); +#else + struct ipv6_mreq opt; + + memcpy(&opt.ipv6mr_multiaddr, &group, 16); + opt.ipv6mr_interface = ifindex; + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &opt, sizeof(opt)); +#endif + + pim_ifp->igmp_ifstat_joins_sent++; + + if (ret) { + flog_err( + EC_LIB_SOCKET, + "Failure socket joining fd=%d group %pPAs on interface address %pPAs: %m", + fd, &group, &ifaddr); + pim_ifp->igmp_ifstat_joins_failed++; + return ret; + } + + if (PIM_DEBUG_TRACE) + zlog_debug( + "Socket fd=%d joined group %pPAs on interface address %pPAs", + fd, &group, &ifaddr); + return ret; +} + +#if PIM_IPV == 4 +static void cmsg_getdstaddr(struct msghdr *mh, struct sockaddr_storage *dst, + ifindex_t *ifindex) +{ + struct cmsghdr *cmsg; + struct sockaddr_in *dst4 = (struct sockaddr_in *)dst; + + for (cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL; + cmsg = CMSG_NXTHDR(mh, cmsg)) { +#ifdef HAVE_IP_PKTINFO + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + struct in_pktinfo *i; + + i = (struct in_pktinfo *)CMSG_DATA(cmsg); + if (dst4) + dst4->sin_addr = i->ipi_addr; + if (ifindex) + *ifindex = i->ipi_ifindex; + + break; + } +#endif + +#ifdef HAVE_IP_RECVDSTADDR + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_RECVDSTADDR)) { + struct in_addr *i = (struct in_addr *)CMSG_DATA(cmsg); + + if (dst4) + dst4->sin_addr = *i; + + break; + } +#endif + +#if defined(HAVE_IP_RECVIF) && defined(CMSG_IFINDEX) + if (cmsg->cmsg_type == IP_RECVIF) + if (ifindex) + *ifindex = CMSG_IFINDEX(cmsg); +#endif + } +} +#else /* PIM_IPV != 4 */ +static void cmsg_getdstaddr(struct msghdr *mh, struct sockaddr_storage *dst, + ifindex_t *ifindex) +{ + struct cmsghdr *cmsg; + struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst; + + for (cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL; + cmsg = CMSG_NXTHDR(mh, cmsg)) { + if ((cmsg->cmsg_level == IPPROTO_IPV6) && + (cmsg->cmsg_type == IPV6_PKTINFO)) { + struct in6_pktinfo *i; + + i = (struct in6_pktinfo *)CMSG_DATA(cmsg); + + if (dst6) + dst6->sin6_addr = i->ipi6_addr; + if (ifindex) + *ifindex = i->ipi6_ifindex; + break; + } + } +} +#endif /* PIM_IPV != 4 */ + +int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len, + struct sockaddr_storage *from, socklen_t *fromlen, + struct sockaddr_storage *to, socklen_t *tolen, + ifindex_t *ifindex) +{ + struct msghdr msgh; + struct iovec iov; + char cbuf[1000]; + int err; + + /* + * IP_PKTINFO / IP_RECVDSTADDR don't yield sin_port. + * Use getsockname() to get sin_port. + */ + if (to) { + socklen_t to_len = sizeof(*to); + + pim_socket_getsockname(fd, (struct sockaddr *)to, &to_len); + + if (tolen) + *tolen = sizeof(*to); + } + + memset(&msgh, 0, sizeof(msgh)); + iov.iov_base = buf; + iov.iov_len = len; + msgh.msg_control = cbuf; + msgh.msg_controllen = sizeof(cbuf); + msgh.msg_name = from; + msgh.msg_namelen = fromlen ? *fromlen : 0; + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_flags = 0; + + err = recvmsg(fd, &msgh, 0); + if (err < 0) + return err; + + if (fromlen) + *fromlen = msgh.msg_namelen; + + cmsg_getdstaddr(&msgh, to, ifindex); + + return err; /* len */ +} + +int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen) +{ + if (getsockname(fd, name, namelen)) { + int e = errno; + zlog_warn( + "Could not get Socket Name for socket fd=%d: errno=%d: %s", + fd, errno, safe_strerror(errno)); + errno = e; + return PIM_SOCK_ERR_NAME; + } + + return PIM_SOCK_ERR_NONE; +} diff --git a/pimd/pim_sock.h b/pimd/pim_sock.h new file mode 100644 index 0000000..04ab864 --- /dev/null +++ b/pimd/pim_sock.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_SOCK_H +#define PIM_SOCK_H + +#include + +#define PIM_SOCK_ERR_NONE (0) /* No error */ +#define PIM_SOCK_ERR_SOCKET (-1) /* socket() */ +#define PIM_SOCK_ERR_RA (-2) /* Router Alert option */ +#define PIM_SOCK_ERR_REUSE (-3) /* Reuse option */ +#define PIM_SOCK_ERR_TTL (-4) /* TTL option */ +#define PIM_SOCK_ERR_LOOP (-5) /* Loopback option */ +#define PIM_SOCK_ERR_IFACE (-6) /* Outgoing interface option */ +#define PIM_SOCK_ERR_DSTADDR (-7) /* Outgoing interface option */ +#define PIM_SOCK_ERR_NONBLOCK_GETFL (-8) /* Get O_NONBLOCK */ +#define PIM_SOCK_ERR_NONBLOCK_SETFL (-9) /* Set O_NONBLOCK */ +#define PIM_SOCK_ERR_NAME (-10) /* Socket name (getsockname) */ +#define PIM_SOCK_ERR_BIND (-11) /* Can't bind to interface */ + +struct pim_instance; + +int pim_socket_bind(int fd, struct interface *ifp); +void pim_socket_ip_hdr(int fd); +int pim_socket_raw(int protocol); +int pim_socket_mcast(int protocol, pim_addr ifaddr, struct interface *ifp, + uint8_t loop); +int pim_socket_join(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, + struct pim_interface *pim_ifp); +int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len, + struct sockaddr_storage *from, socklen_t *fromlen, + struct sockaddr_storage *to, socklen_t *tolen, + ifindex_t *ifindex); + +int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen); + +int pim_reg_sock(void); + +#endif /* PIM_SOCK_H */ diff --git a/pimd/pim_ssm.c b/pimd/pim_ssm.c new file mode 100644 index 0000000..7b7503a --- /dev/null +++ b/pimd/pim_ssm.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP SSM ranges for FRR + * Copyright (C) 2017 Cumulus Networks, Inc. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_ssm.h" +#include "pim_igmp.h" + +static void pim_ssm_range_reevaluate(struct pim_instance *pim) +{ +#if PIM_IPV == 4 + /* 1. Setup register state for (S,G) entries if G has changed from SSM + * to + * ASM. + * 2. check existing (*,G) IGMP registrations to see if they are + * still ASM. if they are now SSM delete them. + * 3. Allow channel setup for IGMP (*,G) members if G is now ASM + * 4. I could tear down all (*,G), (S,G,rpt) states. But that is an + * unnecessary sladge hammer and may not be particularly useful as it is + * likely the SPT switchover has already happened for flows along such + * RPTs. + * As for the RPT states it seems that the best thing to do is let them + * age + * out gracefully. As long as the FHR and LHR do the right thing RPTs + * will + * disappear in time for SSM groups. + */ + pim_upstream_register_reevaluate(pim); + igmp_source_forward_reevaluate_all(pim); +#endif +} + +void pim_ssm_prefix_list_update(struct pim_instance *pim, + struct prefix_list *plist) +{ + struct pim_ssm *ssm = pim->ssm_info; + + if (!ssm->plist_name + || strcmp(ssm->plist_name, prefix_list_name(plist))) { + /* not ours */ + return; + } + + pim_ssm_range_reevaluate(pim); +} + +static int pim_is_grp_standard_ssm(struct prefix *group) +{ + pim_addr addr = pim_addr_from_prefix(group); + + return pim_addr_ssm(addr); +} + +int pim_is_grp_ssm(struct pim_instance *pim, pim_addr group_addr) +{ + struct pim_ssm *ssm; + struct prefix group; + struct prefix_list *plist; + + pim_addr_to_prefix(&group, group_addr); + + ssm = pim->ssm_info; + if (!ssm->plist_name) { + return pim_is_grp_standard_ssm(&group); + } + + plist = prefix_list_lookup(PIM_AFI, ssm->plist_name); + if (!plist) + return 0; + + return (prefix_list_apply_ext(plist, NULL, &group, true) == + PREFIX_PERMIT); +} + +int pim_ssm_range_set(struct pim_instance *pim, vrf_id_t vrf_id, + const char *plist_name) +{ + struct pim_ssm *ssm; + int change = 0; + + if (vrf_id != pim->vrf->vrf_id) + return PIM_SSM_ERR_NO_VRF; + + ssm = pim->ssm_info; + if (plist_name) { + if (ssm->plist_name) { + if (!strcmp(ssm->plist_name, plist_name)) + return PIM_SSM_ERR_DUP; + XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); + } + ssm->plist_name = XSTRDUP(MTYPE_PIM_FILTER_NAME, plist_name); + change = 1; + } else { + if (ssm->plist_name) { + change = 1; + XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); + } + } + + if (change) + pim_ssm_range_reevaluate(pim); + + return PIM_SSM_ERR_NONE; +} + +void *pim_ssm_init(void) +{ + struct pim_ssm *ssm; + + ssm = XCALLOC(MTYPE_PIM_SSM_INFO, sizeof(*ssm)); + + return ssm; +} + +void pim_ssm_terminate(struct pim_ssm *ssm) +{ + if (!ssm) + return; + + XFREE(MTYPE_PIM_FILTER_NAME, ssm->plist_name); + + XFREE(MTYPE_PIM_SSM_INFO, ssm); +} diff --git a/pimd/pim_ssm.h b/pimd/pim_ssm.h new file mode 100644 index 0000000..24a037e --- /dev/null +++ b/pimd/pim_ssm.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * IP SSM ranges for FRR + * Copyright (C) 2017 Cumulus Networks, Inc. + */ +#ifndef PIM_SSM_H +#define PIM_SSM_H + +#define PIM_SSM_STANDARD_RANGE "232.0.0.0/8" + +struct pim_instance; + +/* SSM error codes */ +enum pim_ssm_err { + PIM_SSM_ERR_NONE = 0, + PIM_SSM_ERR_NO_VRF = -1, + PIM_SSM_ERR_DUP = -2, +}; + +struct pim_ssm { + char *plist_name; /* prefix list of group ranges */ +}; + +void pim_ssm_prefix_list_update(struct pim_instance *pim, + struct prefix_list *plist); +extern int pim_is_grp_ssm(struct pim_instance *pim, pim_addr group_addr); +int pim_ssm_range_set(struct pim_instance *pim, vrf_id_t vrf_id, + const char *plist_name); +void *pim_ssm_init(void); +void pim_ssm_terminate(struct pim_ssm *ssm); +#endif diff --git a/pimd/pim_ssmpingd.c b/pimd/pim_ssmpingd.c new file mode 100644 index 0000000..27dbb0d --- /dev/null +++ b/pimd/pim_ssmpingd.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" +#include "log.h" +#include "memory.h" +#include "sockopt.h" +#include "vrf.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_ssmpingd.h" +#include "pim_time.h" +#include "pim_sock.h" +#include "network.h" + +#if PIM_IPV == 4 +static const char *const PIM_SSMPINGD_REPLY_GROUP = "232.43.211.234"; +#else +static const char *const PIM_SSMPINGD_REPLY_GROUP = "ff3e::4321:1234"; +#endif + +enum { PIM_SSMPINGD_REQUEST = 'Q', PIM_SSMPINGD_REPLY = 'A' }; + +static void ssmpingd_read_on(struct ssmpingd_sock *ss); + +void pim_ssmpingd_init(struct pim_instance *pim) +{ + int result; + + assert(!pim->ssmpingd_list); + + result = inet_pton(PIM_AF, PIM_SSMPINGD_REPLY_GROUP, + &pim->ssmpingd_group_addr); + + assert(result > 0); +} + +void pim_ssmpingd_destroy(struct pim_instance *pim) +{ + if (pim->ssmpingd_list) + list_delete(&pim->ssmpingd_list); +} + +static struct ssmpingd_sock *ssmpingd_find(struct pim_instance *pim, + pim_addr source_addr) +{ + struct listnode *node; + struct ssmpingd_sock *ss; + + if (!pim->ssmpingd_list) + return 0; + + for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) + if (!pim_addr_cmp(source_addr, ss->source_addr)) + return ss; + + return 0; +} + +static void ssmpingd_free(struct ssmpingd_sock *ss) +{ + XFREE(MTYPE_PIM_SSMPINGD, ss); +} + +#if PIM_IPV == 4 +static inline int ssmpingd_setsockopt(int fd, pim_addr addr, int mttl) +{ + /* Needed to obtain destination address from recvmsg() */ +#if defined(HAVE_IP_PKTINFO) + /* Linux and Solaris IP_PKTINFO */ + int opt = 1; + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))) { + zlog_warn( + "%s: could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + } +#elif defined(HAVE_IP_RECVDSTADDR) + /* BSD IP_RECVDSTADDR */ + int opt = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) { + zlog_warn( + "%s: could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + } +#else + flog_err( + EC_LIB_DEVELOPMENT, + "%s %s: missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()", + __FILE__, __func__); + close(fd); + return -1; +#endif + + if (setsockopt_ipv4_multicast_loop(fd, 0)) { + zlog_warn( + "%s: could not disable Multicast Loopback Option on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + close(fd); + return PIM_SOCK_ERR_LOOP; + } + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (void *)&addr, + sizeof(addr))) { + zlog_warn( + "%s: could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + close(fd); + return -1; + } + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&mttl, + sizeof(mttl))) { + zlog_warn( + "%s: could not set multicast TTL=%d on socket fd=%d: errno=%d: %s", + __func__, mttl, fd, errno, safe_strerror(errno)); + close(fd); + return -1; + } + + return 0; +} +#else +static inline int ssmpingd_setsockopt(int fd, pim_addr addr, int mttl) +{ + setsockopt_ipv6_pktinfo(fd, 1); + setsockopt_ipv6_multicast_hops(fd, mttl); + + if (setsockopt_ipv6_multicast_loop(fd, 0)) { + zlog_warn( + "%s: could not disable Multicast Loopback Option on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + close(fd); + return PIM_SOCK_ERR_LOOP; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (void *)&addr, + sizeof(addr))) { + zlog_warn( + "%s: could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s", + __func__, fd, errno, safe_strerror(errno)); + close(fd); + return -1; + } + return 0; +} +#endif + + +static int ssmpingd_socket(pim_addr addr, int port, int mttl) +{ + struct sockaddr_storage sockaddr; + int fd; + int ret; + socklen_t len = sizeof(sockaddr); + + fd = socket(PIM_AF, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s: could not create socket: errno=%d: %s", + __func__, errno, safe_strerror(errno)); + return -1; + } + + pim_socket_getsockname(fd, (struct sockaddr *)&sockaddr, &len); + + if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) { + zlog_warn( + "%s: bind(fd=%d,addr=%pSUp,port=%d,len=%zu) failure: errno=%d: %s", + __func__, fd, &sockaddr, port, sizeof(sockaddr), errno, + safe_strerror(errno)); + close(fd); + return -1; + } + + set_nonblocking(fd); + sockopt_reuseaddr(fd); + + ret = ssmpingd_setsockopt(fd, addr, mttl); + if (ret) { + zlog_warn("ssmpingd_setsockopt failed"); + return -1; + } + + return fd; +} + +static void ssmpingd_delete(struct ssmpingd_sock *ss) +{ + assert(ss); + + EVENT_OFF(ss->t_sock_read); + + if (close(ss->sock_fd)) { + zlog_warn( + "%s: failure closing ssmpingd sock_fd=%d for source %pPA: errno=%d: %s", + __func__, ss->sock_fd, &ss->source_addr, errno, + safe_strerror(errno)); + /* warning only */ + } + + listnode_delete(ss->pim->ssmpingd_list, ss); + ssmpingd_free(ss); +} + +static void ssmpingd_sendto(struct ssmpingd_sock *ss, const uint8_t *buf, + int len, struct sockaddr_storage to) +{ + socklen_t tolen = sizeof(to); + int sent; + + sent = sendto(ss->sock_fd, buf, len, MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + if (sent != len) { + if (sent < 0) { + zlog_warn( + "%s: sendto() failure to %pSUp,fd=%d len=%d: errno=%d: %s", + __func__, &to, ss->sock_fd, len, errno, + safe_strerror(errno)); + } else { + zlog_warn( + "%s: sendto() partial to %pSUp, fd=%d len=%d: sent=%d", + __func__, &to, ss->sock_fd, len, sent); + } + } +} + +static int ssmpingd_read_msg(struct ssmpingd_sock *ss) +{ + struct interface *ifp; + struct sockaddr_storage from; + struct sockaddr_storage to; + socklen_t fromlen = sizeof(from); + socklen_t tolen = sizeof(to); + ifindex_t ifindex = -1; + uint8_t buf[1000]; + int len; + + ++ss->requests; + + len = pim_socket_recvfromto(ss->sock_fd, buf, sizeof(buf), &from, + &fromlen, &to, &tolen, &ifindex); + + if (len < 0) { + zlog_warn( + "%s: failure receiving ssmping for source %pPA on fd=%d: errno=%d: %s", + __func__, &ss->source_addr, ss->sock_fd, errno, + safe_strerror(errno)); + return -1; + } + + ifp = if_lookup_by_index(ifindex, ss->pim->vrf->vrf_id); + + if (buf[0] != PIM_SSMPINGD_REQUEST) { + zlog_warn( + "%s: bad ssmping type=%d from %pSUp to %pSUp on interface %s ifindex=%d fd=%d src=%pPA", + __func__, buf[0], &from, &to, + ifp ? ifp->name : "", ifindex, ss->sock_fd, + &ss->source_addr); + return 0; + } + + if (PIM_DEBUG_SSMPINGD) { + zlog_debug( + "%s: recv ssmping from %pSUp, to %pSUp, on interface %s ifindex=%d fd=%d src=%pPA", + __func__, &from, &to, ifp ? ifp->name : "", + ifindex, ss->sock_fd, &ss->source_addr); + } + + buf[0] = PIM_SSMPINGD_REPLY; + + /* unicast reply */ + ssmpingd_sendto(ss, buf, len, from); + + /* multicast reply */ + memcpy(&from, &ss->pim->ssmpingd_group_addr, sizeof(pim_addr)); + ssmpingd_sendto(ss, buf, len, from); + + return 0; +} + +static void ssmpingd_sock_read(struct event *t) +{ + struct ssmpingd_sock *ss; + + ss = EVENT_ARG(t); + + ssmpingd_read_msg(ss); + + /* Keep reading */ + ssmpingd_read_on(ss); +} + +static void ssmpingd_read_on(struct ssmpingd_sock *ss) +{ + event_add_read(router->master, ssmpingd_sock_read, ss, ss->sock_fd, + &ss->t_sock_read); +} + +static struct ssmpingd_sock *ssmpingd_new(struct pim_instance *pim, + pim_addr source_addr) +{ + struct ssmpingd_sock *ss; + int sock_fd; + + if (!pim->ssmpingd_list) { + pim->ssmpingd_list = list_new(); + pim->ssmpingd_list->del = (void (*)(void *))ssmpingd_free; + } + + sock_fd = + ssmpingd_socket(source_addr, /* port: */ 4321, /* mTTL: */ 64); + if (sock_fd < 0) { + zlog_warn("%s: ssmpingd_socket() failure for source %pPA", + __func__, &source_addr); + return 0; + } + + ss = XCALLOC(MTYPE_PIM_SSMPINGD, sizeof(*ss)); + + ss->pim = pim; + ss->sock_fd = sock_fd; + ss->t_sock_read = NULL; + ss->source_addr = source_addr; + ss->creation = pim_time_monotonic_sec(); + ss->requests = 0; + + listnode_add(pim->ssmpingd_list, ss); + + ssmpingd_read_on(ss); + + return ss; +} + +int pim_ssmpingd_start(struct pim_instance *pim, pim_addr source_addr) +{ + struct ssmpingd_sock *ss; + + ss = ssmpingd_find(pim, source_addr); + if (ss) { + /* silently ignore request to recreate entry */ + return 0; + } + + zlog_info("%s: starting ssmpingd for source %pPAs", __func__, + &source_addr); + + ss = ssmpingd_new(pim, source_addr); + if (!ss) { + zlog_warn("%s: ssmpingd_new() failure for source %pPAs", + __func__, &source_addr); + return -1; + } + + return 0; +} + +int pim_ssmpingd_stop(struct pim_instance *pim, pim_addr source_addr) +{ + struct ssmpingd_sock *ss; + + ss = ssmpingd_find(pim, source_addr); + if (!ss) { + zlog_warn("%s: could not find ssmpingd for source %pPAs", + __func__, &source_addr); + return -1; + } + + zlog_info("%s: stopping ssmpingd for source %pPAs", __func__, + &source_addr); + + ssmpingd_delete(ss); + + return 0; +} diff --git a/pimd/pim_ssmpingd.h b/pimd/pim_ssmpingd.h new file mode 100644 index 0000000..71286e4 --- /dev/null +++ b/pimd/pim_ssmpingd.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_SSMPINGD_H +#define PIM_SSMPINGD_H + +#include + +#include "if.h" + +#include "pim_iface.h" + +struct ssmpingd_sock { + struct pim_instance *pim; + + int sock_fd; /* socket */ + struct event *t_sock_read; /* thread for reading socket */ + pim_addr source_addr; /* source address */ + int64_t creation; /* timestamp of socket creation */ + int64_t requests; /* counter */ +}; + +void pim_ssmpingd_init(struct pim_instance *pim); +void pim_ssmpingd_destroy(struct pim_instance *pim); +int pim_ssmpingd_start(struct pim_instance *pim, pim_addr source_addr); +int pim_ssmpingd_stop(struct pim_instance *pim, pim_addr source_addr); + +#endif /* PIM_SSMPINGD_H */ diff --git a/pimd/pim_static.c b/pimd/pim_static.c new file mode 100644 index 0000000..b9effa2 --- /dev/null +++ b/pimd/pim_static.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga: add the ability to configure multicast static routes + * Copyright (C) 2014 Nathan Bahr, ATCorp + */ + +#include + +#include "vty.h" +#include "if.h" +#include "log.h" +#include "memory.h" +#include "linklist.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_oil.h" +#include "pim_static.h" +#include "pim_time.h" +#include "pim_str.h" +#include "pim_iface.h" + +void pim_static_route_free(struct static_route *s_route) +{ + XFREE(MTYPE_PIM_STATIC_ROUTE, s_route); +} + +static struct static_route *static_route_alloc(void) +{ + return XCALLOC(MTYPE_PIM_STATIC_ROUTE, sizeof(struct static_route)); +} + +static struct static_route *static_route_new(ifindex_t iif, ifindex_t oif, + pim_addr group, + pim_addr source) +{ + struct static_route *s_route; + s_route = static_route_alloc(); + + s_route->group = group; + s_route->source = source; + s_route->iif = iif; + s_route->oif_ttls[oif] = 1; + s_route->c_oil.oil_ref_count = 1; + *oil_origin(&s_route->c_oil) = source; + *oil_mcastgrp(&s_route->c_oil) = group; + *oil_incoming_vif(&s_route->c_oil) = iif; + oil_if_set(&s_route->c_oil, oif, 1); + s_route->c_oil.oif_creation[oif] = pim_time_monotonic_sec(); + + return s_route; +} + + +int pim_static_add(struct pim_instance *pim, struct interface *iif, + struct interface *oif, pim_addr group, pim_addr source) +{ + struct listnode *node = NULL; + struct static_route *s_route = NULL; + struct static_route *original_s_route = NULL; + struct pim_interface *pim_iif = iif ? iif->info : NULL; + struct pim_interface *pim_oif = oif ? oif->info : NULL; + ifindex_t iif_index = pim_iif ? pim_iif->mroute_vif_index : 0; + ifindex_t oif_index = pim_oif ? pim_oif->mroute_vif_index : 0; + + if (!iif_index || !oif_index || iif_index == -1 || oif_index == -1) { + zlog_warn( + "%s %s: Unable to add static route: Invalid interface index(iif=%d,oif=%d)", + __FILE__, __func__, iif_index, oif_index); + return -2; + } + +#ifdef PIM_ENFORCE_LOOPFREE_MFC + if (iif_index == oif_index) { + /* looped MFC entry */ + zlog_warn( + "%s %s: Unable to add static route: Looped MFC entry(iif=%d,oif=%d)", + __FILE__, __func__, iif_index, oif_index); + return -4; + } +#endif + if (iif->vrf->vrf_id != oif->vrf->vrf_id) { + return -3; + } + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { + if (!pim_addr_cmp(s_route->group, group) && + !pim_addr_cmp(s_route->source, source) && + (s_route->iif == iif_index)) { + + if (s_route->oif_ttls[oif_index]) { + zlog_warn( + "%s %s: Unable to add static route: Route already exists (iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __FILE__, __func__, iif_index, + oif_index, &group, &source); + return -3; + } + + /* Ok, from here on out we will be making changes to the + * s_route structure, but if + * for some reason we fail to commit these changes to + * the kernel, we want to be able + * restore the state of the list. So copy the node data + * and if need be, we can copy + * back if it fails. + */ + original_s_route = static_route_alloc(); + memcpy(original_s_route, s_route, + sizeof(struct static_route)); + + /* Route exists and has the same input interface, but + * adding a new output interface */ + s_route->oif_ttls[oif_index] = 1; + oil_if_set(&s_route->c_oil, oif_index, 1); + s_route->c_oil.oif_creation[oif_index] = + pim_time_monotonic_sec(); + ++s_route->c_oil.oil_ref_count; + break; + } + } + + /* If node is null then we reached the end of the list without finding a + * match */ + if (!node) { + s_route = static_route_new(iif_index, oif_index, group, source); + listnode_add(pim->static_routes, s_route); + } + + s_route->c_oil.pim = pim; + + if (pim_static_mroute_add(&s_route->c_oil, __func__)) { + zlog_warn( + "%s %s: Unable to add static route(iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __FILE__, __func__, iif_index, oif_index, &group, + &source); + + /* Need to put s_route back to the way it was */ + if (original_s_route) { + memcpy(s_route, original_s_route, + sizeof(struct static_route)); + } else { + /* we never stored off a copy, so it must have been a + * fresh new route */ + listnode_delete(pim->static_routes, s_route); + pim_static_route_free(s_route); + } + + if (original_s_route) { + pim_static_route_free(original_s_route); + } + + return -1; + } + + /* Make sure we free the memory for the route copy if used */ + if (original_s_route) { + pim_static_route_free(original_s_route); + } + + if (PIM_DEBUG_STATIC) { + zlog_debug( + "%s: Static route added(iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __func__, iif_index, oif_index, &group, + &source); + } + + return 0; +} + +int pim_static_del(struct pim_instance *pim, struct interface *iif, + struct interface *oif, pim_addr group, pim_addr source) +{ + struct listnode *node = NULL; + struct listnode *nextnode = NULL; + struct static_route *s_route = NULL; + struct pim_interface *pim_iif = iif ? iif->info : 0; + struct pim_interface *pim_oif = oif ? oif->info : 0; + ifindex_t iif_index = pim_iif ? pim_iif->mroute_vif_index : 0; + ifindex_t oif_index = pim_oif ? pim_oif->mroute_vif_index : 0; + + if (!iif_index || !oif_index) { + zlog_warn( + "%s %s: Unable to remove static route: Invalid interface index(iif=%d,oif=%d)", + __FILE__, __func__, iif_index, oif_index); + return -2; + } + + for (ALL_LIST_ELEMENTS(pim->static_routes, node, nextnode, s_route)) { + if (s_route->iif == iif_index + && !pim_addr_cmp(s_route->group, group) + && !pim_addr_cmp(s_route->source, source) + && s_route->oif_ttls[oif_index]) { + s_route->oif_ttls[oif_index] = 0; + oil_if_set(&s_route->c_oil, oif_index, 0); + --s_route->c_oil.oil_ref_count; + + /* If there are no more outputs then delete the whole + * route, otherwise set the route with the new outputs + */ + if (s_route->c_oil.oil_ref_count <= 0 + ? pim_mroute_del(&s_route->c_oil, __func__) + : pim_static_mroute_add(&s_route->c_oil, + __func__)) { + zlog_warn( + "%s %s: Unable to remove static route(iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __FILE__, __func__, iif_index, + oif_index, &group, &source); + + s_route->oif_ttls[oif_index] = 1; + oil_if_set(&s_route->c_oil, oif_index, 1); + ++s_route->c_oil.oil_ref_count; + + return -1; + } + + s_route->c_oil.oif_creation[oif_index] = 0; + + if (s_route->c_oil.oil_ref_count <= 0) { + listnode_delete(pim->static_routes, s_route); + pim_static_route_free(s_route); + } + + if (PIM_DEBUG_STATIC) { + zlog_debug( + "%s: Static route removed(iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __func__, iif_index, oif_index, + &group, &source); + } + + break; + } + } + + if (!node) { + zlog_warn( + "%s %s: Unable to remove static route: Route does not exist(iif=%d,oif=%d,group=%pPAs,source=%pPAs)", + __FILE__, __func__, iif_index, oif_index, &group, + &source); + return -3; + } + + return 0; +} + +int pim_static_write_mroute(struct pim_instance *pim, struct vty *vty, + struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct listnode *node; + struct static_route *sroute; + int count = 0; + + if (!pim_ifp) + return 0; + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, sroute)) { + if (sroute->iif == pim_ifp->mroute_vif_index) { + int i; + for (i = 0; i < MAXVIFS; i++) + if (sroute->oif_ttls[i]) { + struct interface *oifp = + pim_if_find_by_vif_index(pim, + i); + if (pim_addr_is_any(sroute->source)) + vty_out(vty, + " " PIM_AF_NAME " mroute %s %pPA\n", + oifp->name, &sroute->group); + else + vty_out(vty, + " " PIM_AF_NAME " mroute %s %pPA %pPA\n", + oifp->name, &sroute->group, + &sroute->source); + count++; + } + } + } + + return count; +} diff --git a/pimd/pim_static.h b/pimd/pim_static.h new file mode 100644 index 0000000..c868d02 --- /dev/null +++ b/pimd/pim_static.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga: add the ability to configure multicast static routes + * Copyright (C) 2014 Nathan Bahr, ATCorp + */ + +#ifndef PIM_STATIC_H_ +#define PIM_STATIC_H_ + +#include +#include "pim_mroute.h" +#include "pim_oil.h" +#include "if.h" + +struct static_route { + /* Each static route is unique by these pair of addresses */ + pim_addr group; + pim_addr source; + + struct channel_oil c_oil; + ifindex_t iif; + unsigned char oif_ttls[MAXVIFS]; +}; + +void pim_static_route_free(struct static_route *s_route); + +int pim_static_add(struct pim_instance *pim, struct interface *iif, + struct interface *oif, pim_addr group, pim_addr source); +int pim_static_del(struct pim_instance *pim, struct interface *iif, + struct interface *oif, pim_addr group, pim_addr source); +int pim_static_write_mroute(struct pim_instance *pim, struct vty *vty, + struct interface *ifp); + +#endif /* PIM_STATIC_H_ */ diff --git a/pimd/pim_str.h b/pimd/pim_str.h new file mode 100644 index 0000000..029a9f4 --- /dev/null +++ b/pimd/pim_str.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_STR_H +#define PIM_STR_H + +#include +#include +#include + +#include "prefix.h" +#include "pim_addr.h" + +#if PIM_IPV == 4 +/* + * Longest possible length of a IPV4 (S,G) string is 34 bytes + * 123.123.123.123 = 16 * 2 + * (,) = 3 + * NULL Character at end = 1 + * (123.123.123.123,123.123.123.123) + */ +#define PIM_SG_LEN PREFIX_SG_STR_LEN +#else +/* + * Longest possible length of a IPV6 (S,G) string is 94 bytes + * INET6_ADDRSTRLEN * 2 = 46 * 2 + * (,) = 3 + * NULL Character at end = 1 + */ +#define PIM_SG_LEN 96 +#endif + +#define pim_inet4_dump prefix_mcast_inet4_dump + +void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, + int buf_size); + +#endif diff --git a/pimd/pim_tib.c b/pimd/pim_tib.c new file mode 100644 index 0000000..4081786 --- /dev/null +++ b/pimd/pim_tib.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TIB (Tree Information Base) - just PIM <> IGMP/MLD glue for now + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + */ + +#include + +#include "pim_tib.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_iface.h" +#include "pim_upstream.h" +#include "pim_oil.h" +#include "pim_nht.h" + +static struct channel_oil * +tib_sg_oil_setup(struct pim_instance *pim, pim_sgaddr sg, struct interface *oif) +{ + struct pim_interface *pim_oif = oif->info; + int input_iface_vif_index = 0; + pim_addr vif_source; + struct prefix grp; + struct pim_nexthop nexthop; + struct pim_upstream *up = NULL; + + if (!pim_rp_set_upstream_addr(pim, &vif_source, sg.src, sg.grp)) { + /* no PIM RP - create a dummy channel oil */ + return pim_channel_oil_add(pim, &sg, __func__); + } + + pim_addr_to_prefix(&grp, sg.grp); + + up = pim_upstream_find(pim, &sg); + if (up) { + memcpy(&nexthop, &up->rpf.source_nexthop, + sizeof(struct pim_nexthop)); + (void)pim_ecmp_nexthop_lookup(pim, &nexthop, vif_source, &grp, + 0); + if (nexthop.interface) + input_iface_vif_index = pim_if_find_vifindex_by_ifindex( + pim, nexthop.interface->ifindex); + } else + input_iface_vif_index = + pim_ecmp_fib_lookup_if_vif_index(pim, vif_source, &grp); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: NHT %pSG vif_source %pPAs vif_index:%d", + __func__, &sg, &vif_source, input_iface_vif_index); + + if (input_iface_vif_index < 1) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s %s: could not find input interface for %pSG", + __FILE__, __func__, &sg); + + return pim_channel_oil_add(pim, &sg, __func__); + } + + /* + * Protect IGMP against adding looped MFC entries created by both + * source and receiver attached to the same interface. See TODO T22. + * Block only when the intf is non DR DR must create upstream. + */ + if ((input_iface_vif_index == pim_oif->mroute_vif_index) && + !(PIM_I_am_DR(pim_oif))) { + /* ignore request for looped MFC entry */ + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s: ignoring request for looped MFC entry (S,G)=%pSG: oif=%s vif_index=%d", + __func__, &sg, oif->name, + input_iface_vif_index); + + return NULL; + } + + return pim_channel_oil_add(pim, &sg, __func__); +} + +bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp) +{ + struct pim_interface *pim_oif = oif->info; + + if (!pim_oif) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug("%s: multicast not enabled on oif=%s?", + __func__, oif->name); + return false; + } + + if (!*oilp) + *oilp = tib_sg_oil_setup(pim, sg, oif); + if (!*oilp) + return false; + + if (PIM_I_am_DR(pim_oif) || PIM_I_am_DualActive(pim_oif)) { + int result; + + result = pim_channel_add_oif(*oilp, oif, PIM_OIF_FLAG_PROTO_GM, + __func__); + if (result) { + if (PIM_DEBUG_MROUTE) + zlog_warn("%s: add_oif() failed with return=%d", + __func__, result); + return false; + } + } else { + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s: %pSG was received on %s interface but we are not DR for that interface", + __func__, &sg, oif->name); + + return false; + } + /* + Feed IGMPv3-gathered local membership information into PIM + per-interface (S,G) state. + */ + if (!pim_ifchannel_local_membership_add(oif, &sg, false /*is_vxlan*/)) { + if (PIM_DEBUG_MROUTE) + zlog_warn( + "%s: Failure to add local membership for %pSG", + __func__, &sg); + + pim_channel_del_oif(*oilp, oif, PIM_OIF_FLAG_PROTO_GM, + __func__); + return false; + } + + return true; +} + +void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp) +{ + int result; + + /* + It appears that in certain circumstances that + igmp_source_forward_stop is called when IGMP forwarding + was not enabled in oif_flags for this outgoing interface. + Possibly because of multiple calls. When that happens, we + enter the below if statement and this function returns early + which in turn triggers the calling function to assert. + Making the call to pim_channel_del_oif and ignoring the return code + fixes the issue without ill effect, similar to + pim_forward_stop below. + */ + result = pim_channel_del_oif(*oilp, oif, PIM_OIF_FLAG_PROTO_GM, + __func__); + if (result) { + if (PIM_DEBUG_GM_TRACE) + zlog_debug( + "%s: pim_channel_del_oif() failed with return=%d", + __func__, result); + return; + } + + /* + Feed IGMPv3-gathered local membership information into PIM + per-interface (S,G) state. + */ + pim_ifchannel_local_membership_del(oif, &sg); + + pim_channel_oil_del(*oilp, __func__); +} diff --git a/pimd/pim_tib.h b/pimd/pim_tib.h new file mode 100644 index 0000000..081ad90 --- /dev/null +++ b/pimd/pim_tib.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TIB (Tree Information Base) - just PIM <> IGMP/MLD glue for now + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + */ + +#ifndef _FRR_PIM_GLUE_H +#define _FRR_PIM_GLUE_H + +#include "pim_addr.h" + +struct pim_instance; +struct channel_oil; + +extern bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp); +extern void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp); + +#endif /* _FRR_PIM_GLUE_H */ diff --git a/pimd/pim_time.c b/pimd/pim_time.c new file mode 100644 index 0000000..205945e --- /dev/null +++ b/pimd/pim_time.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include +#include +#include + +#include "log.h" +#include "frrevent.h" +#include "lib_errors.h" + +#include "pim_time.h" + +static int gettime_monotonic(struct timeval *tv) +{ + int result; + + result = gettimeofday(tv, 0); + if (result) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: gettimeofday() failure: errno=%d: %s", + __func__, errno, safe_strerror(errno)); + } + + return result; +} + +/* + pim_time_monotonic_sec(): + number of seconds since some unspecified starting point +*/ +int64_t pim_time_monotonic_sec(void) +{ + struct timeval now_tv; + + if (gettime_monotonic(&now_tv)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: gettime_monotonic() failure: errno=%d: %s", + __func__, errno, safe_strerror(errno)); + return -1; + } + + return now_tv.tv_sec; +} + +/* + pim_time_monotonic_dsec(): + number of deciseconds since some unspecified starting point +*/ +int64_t pim_time_monotonic_dsec(void) +{ + struct timeval now_tv; + int64_t now_dsec; + + if (gettime_monotonic(&now_tv)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: gettime_monotonic() failure: errno=%d: %s", + __func__, errno, safe_strerror(errno)); + return -1; + } + + now_dsec = ((int64_t)now_tv.tv_sec) * 10 + + ((int64_t)now_tv.tv_usec) / 100000; + + return now_dsec; +} + +int64_t pim_time_monotonic_usec(void) +{ + struct timeval now_tv; + int64_t now_dsec; + + if (gettime_monotonic(&now_tv)) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "%s: gettime_monotonic() failure: errno=%d: %s", + __func__, errno, safe_strerror(errno)); + return -1; + } + + now_dsec = + ((int64_t)now_tv.tv_sec) * 1000000 + ((int64_t)now_tv.tv_usec); + + return now_dsec; +} + +int pim_time_mmss(char *buf, int buf_size, long sec) +{ + long mm; + int wr; + + assert(buf_size >= 5); + + mm = sec / 60; + sec %= 60; + + wr = snprintf(buf, buf_size, "%02ld:%02ld", mm, sec); + + return wr != 8; +} + +static int pim_time_hhmmss(char *buf, int buf_size, long sec) +{ + long hh; + long mm; + int wr; + + assert(buf_size >= 8); + + hh = sec / 3600; + sec %= 3600; + mm = sec / 60; + sec %= 60; + + wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec); + + return wr != 8; +} + +void pim_time_timer_to_mmss(char *buf, int buf_size, struct event *t_timer) +{ + if (t_timer) { + pim_time_mmss(buf, buf_size, + event_timer_remain_second(t_timer)); + } else { + snprintf(buf, buf_size, "--:--"); + } +} + +void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct event *t_timer) +{ + if (t_timer) { + pim_time_hhmmss(buf, buf_size, + event_timer_remain_second(t_timer)); + } else { + snprintf(buf, buf_size, "--:--:--"); + } +} + +void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec) +{ + assert(buf_size >= 8); + + pim_time_hhmmss(buf, buf_size, uptime_sec); +} + +void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin) +{ + if (begin > 0) + pim_time_uptime(buf, buf_size, now - begin); + else + snprintf(buf, buf_size, "--:--:--"); +} + +long pim_time_timer_remain_msec(struct event *t_timer) +{ + /* no timer thread running means timer has expired: return 0 */ + + return t_timer ? event_timer_remain_msec(t_timer) : 0; +} diff --git a/pimd/pim_time.h b/pimd/pim_time.h new file mode 100644 index 0000000..6c0e073 --- /dev/null +++ b/pimd/pim_time.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_TIME_H +#define PIM_TIME_H + +#include + +#include +#include "frrevent.h" + +int64_t pim_time_monotonic_sec(void); +int64_t pim_time_monotonic_dsec(void); +int64_t pim_time_monotonic_usec(void); +int pim_time_mmss(char *buf, int buf_size, long sec); +void pim_time_timer_to_mmss(char *buf, int buf_size, struct event *t); +void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct event *t); +void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec); +void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin); +long pim_time_timer_remain_msec(struct event *t_timer); + +#endif /* PIM_TIME_H */ diff --git a/pimd/pim_tlv.c b/pimd/pim_tlv.c new file mode 100644 index 0000000..c463fa2 --- /dev/null +++ b/pimd/pim_tlv.c @@ -0,0 +1,767 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "if.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_int.h" +#include "pim_tlv.h" +#include "pim_str.h" +#include "pim_msg.h" +#include "pim_iface.h" +#include "pim_addr.h" + +#if PIM_IPV == 4 +#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV4 +#else +#define PIM_MSG_ADDRESS_FAMILY PIM_MSG_ADDRESS_FAMILY_IPV6 +#endif + +uint8_t *pim_tlv_append_uint16(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint16_t option_value) +{ + uint16_t option_len = 2; + + if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) + return NULL; + + *(uint16_t *)buf = htons(option_type); + buf += 2; + *(uint16_t *)buf = htons(option_len); + buf += 2; + *(uint16_t *)buf = htons(option_value); + buf += option_len; + + return buf; +} + +uint8_t *pim_tlv_append_2uint16(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint16_t option_value1, + uint16_t option_value2) +{ + uint16_t option_len = 4; + + if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) + return NULL; + + *(uint16_t *)buf = htons(option_type); + buf += 2; + *(uint16_t *)buf = htons(option_len); + buf += 2; + *(uint16_t *)buf = htons(option_value1); + buf += 2; + *(uint16_t *)buf = htons(option_value2); + buf += 2; + + return buf; +} + +uint8_t *pim_tlv_append_uint32(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint32_t option_value) +{ + uint16_t option_len = 4; + + if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) + return NULL; + + *(uint16_t *)buf = htons(option_type); + buf += 2; + *(uint16_t *)buf = htons(option_len); + buf += 2; + pim_write_uint32(buf, option_value); + buf += option_len; + + return buf; +} + +#define ucast_ipv4_encoding_len (2 + sizeof(struct in_addr)) +#define ucast_ipv6_encoding_len (2 + sizeof(struct in6_addr)) + +/* + * An Encoded-Unicast address takes the following format: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Addr Family | Encoding Type | Unicast Address + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... + * + * Addr Family + * The PIM address family of the 'Unicast Address' field of this + * address. + * + * Values 0-127 are as assigned by the IANA for Internet Address * + * Families in [7]. Values 128-250 are reserved to be assigned by + * the IANA for PIM-specific Address Families. Values 251 though + * 255 are designated for private use. As there is no assignment + * authority for this space, collisions should be expected. + * + * Encoding Type + * The type of encoding used within a specific Address Family. The + * value '0' is reserved for this field and represents the native + * encoding of the Address Family. + * + * Unicast Address + * The unicast address as represented by the given Address Family + * and Encoding Type. + */ +int pim_encode_addr_ucast(uint8_t *buf, pim_addr addr) +{ + uint8_t *start = buf; + + *buf++ = PIM_MSG_ADDRESS_FAMILY; + *buf++ = 0; + memcpy(buf, &addr, sizeof(addr)); + buf += sizeof(addr); + + return buf - start; +} + +int pim_encode_addr_ucast_prefix(uint8_t *buf, struct prefix *p) +{ + switch (p->family) { + case AF_INET: + *buf = PIM_MSG_ADDRESS_FAMILY_IPV4; /* notice: AF_INET != + PIM_MSG_ADDRESS_FAMILY_IPV4 + */ + ++buf; + *buf = 0; /* ucast IPv4 native encoding type (RFC + 4601: 4.9.1) */ + ++buf; + memcpy(buf, &p->u.prefix4, sizeof(struct in_addr)); + return ucast_ipv4_encoding_len; + case AF_INET6: + *buf = PIM_MSG_ADDRESS_FAMILY_IPV6; + ++buf; + *buf = 0; + ++buf; + memcpy(buf, &p->u.prefix6, sizeof(struct in6_addr)); + return ucast_ipv6_encoding_len; + default: + return 0; + } +} + +#define group_ipv4_encoding_len (4 + sizeof(struct in_addr)) + +/* + * Encoded-Group addresses take the following format: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Addr Family | Encoding Type |B| Reserved |Z| Mask Len | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Group multicast Address + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... + * + * Addr Family + * Described above. + * + * Encoding Type + * Described above. + * + * [B]idirectional PIM + * Indicates the group range should use Bidirectional PIM [13]. + * For PIM-SM defined in this specification, this bit MUST be zero. + * + * Reserved + * Transmitted as zero. Ignored upon receipt. + * + * Admin Scope [Z]one + * indicates the group range is an admin scope zone. This is used + * in the Bootstrap Router Mechanism [11] only. For all other + * purposes, this bit is set to zero and ignored on receipt. + * + * Mask Len + * The Mask length field is 8 bits. The value is the number of + * contiguous one bits that are left justified and used as a mask; + * when combined with the group address, it describes a range of + * groups. It is less than or equal to the address length in bits + * for the given Address Family and Encoding Type. If the message + * is sent for a single group, then the Mask length must equal the + * address length in bits for the given Address Family and Encoding + * Type (e.g., 32 for IPv4 native encoding, 128 for IPv6 native + * encoding). + * + * Group multicast Address + * Contains the group address. + */ +int pim_encode_addr_group(uint8_t *buf, afi_t afi, int bidir, int scope, + pim_addr group) +{ + uint8_t *start = buf; + uint8_t flags = 0; + + flags |= bidir << 8; + flags |= scope; + + *buf++ = PIM_MSG_ADDRESS_FAMILY; + *buf++ = 0; + *buf++ = flags; + *buf++ = sizeof(group) * 8; + memcpy(buf, &group, sizeof(group)); + buf += sizeof(group); + + return buf - start; +} + +uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf, const uint8_t *buf_pastend, + struct interface *ifp, int family) +{ + uint16_t option_len = 0; + uint8_t *curr; + size_t uel; + struct connected *ifc; + struct pim_interface *pim_ifp = ifp->info; + pim_addr addr; + + ifc = if_connected_first(ifp->connected); + + /* Empty address list ? */ + if (!ifc) { + return buf; + } + + if (family == AF_INET) + uel = ucast_ipv4_encoding_len; + else + uel = ucast_ipv6_encoding_len; + + /* Scan secondary address list */ + curr = buf + 4; /* skip T and L */ + for (; ifc; ifc = if_connected_next(ifp->connected, ifc)) { + struct prefix *p = ifc->address; + int l_encode; + + addr = pim_addr_from_prefix(p); + if (!pim_addr_cmp(pim_ifp->primary_address, addr)) + /* don't add the primary address + * into the secondary address list */ + continue; + + if ((curr + uel) > buf_pastend) + return 0; + + if (p->family != family) + continue; + + l_encode = pim_encode_addr_ucast_prefix(curr, p); + curr += l_encode; + option_len += l_encode; + } + + if (PIM_DEBUG_PIM_TRACE_DETAIL) { + zlog_debug( + "%s: number of encoded secondary unicast IPv4 addresses: %zu", + __func__, option_len / uel); + } + + if (option_len < 1) { + /* Empty secondary unicast IPv4 address list */ + return buf; + } + + /* + * Write T and L + */ + *(uint16_t *)buf = htons(PIM_MSG_OPTION_TYPE_ADDRESS_LIST); + *(uint16_t *)(buf + 2) = htons(option_len); + + return curr; +} + +static int check_tlv_length(const char *label, const char *tlv_name, + const char *ifname, pim_addr src_addr, + int correct_len, int option_len) +{ + if (option_len != correct_len) { + zlog_warn( + "%s: PIM hello %s TLV with incorrect value size=%d correct=%d from %pPAs on interface %s", + label, tlv_name, option_len, correct_len, &src_addr, + ifname); + return -1; + } + + return 0; +} + +static void check_tlv_redefinition_uint16(const char *label, + const char *tlv_name, + const char *ifname, pim_addr src_addr, + pim_hello_options options, + pim_hello_options opt_mask, + uint16_t new, uint16_t old) +{ + if (PIM_OPTION_IS_SET(options, opt_mask)) + zlog_warn( + "%s: PIM hello TLV redefined %s=%u old=%u from %pPAs on interface %s", + label, tlv_name, new, old, &src_addr, ifname); +} + +static void check_tlv_redefinition_uint32(const char *label, + const char *tlv_name, + const char *ifname, pim_addr src_addr, + pim_hello_options options, + pim_hello_options opt_mask, + uint32_t new, uint32_t old) +{ + if (PIM_OPTION_IS_SET(options, opt_mask)) + zlog_warn( + "%s: PIM hello TLV redefined %s=%u old=%u from %pPAs on interface %s", + label, tlv_name, new, old, &src_addr, ifname); +} + +static void check_tlv_redefinition_uint32_hex( + const char *label, const char *tlv_name, const char *ifname, + pim_addr src_addr, pim_hello_options options, + pim_hello_options opt_mask, uint32_t new, uint32_t old) +{ + if (PIM_OPTION_IS_SET(options, opt_mask)) + zlog_warn( + "%s: PIM hello TLV redefined %s=%08x old=%08x from %pPAs on interface %s", + label, tlv_name, new, old, &src_addr, ifname); +} + +int pim_tlv_parse_holdtime(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint16_t *hello_option_holdtime, uint16_t option_len, + const uint8_t *tlv_curr) +{ + const char *label = "holdtime"; + + if (check_tlv_length(__func__, label, ifname, src_addr, + sizeof(uint16_t), option_len)) { + return -1; + } + + check_tlv_redefinition_uint16(__func__, label, ifname, src_addr, + *hello_options, PIM_OPTION_MASK_HOLDTIME, + PIM_TLV_GET_HOLDTIME(tlv_curr), + *hello_option_holdtime); + + PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_HOLDTIME); + + *hello_option_holdtime = PIM_TLV_GET_HOLDTIME(tlv_curr); + + return 0; +} + +int pim_tlv_parse_lan_prune_delay(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint16_t *hello_option_propagation_delay, + uint16_t *hello_option_override_interval, + uint16_t option_len, const uint8_t *tlv_curr) +{ + if (check_tlv_length(__func__, "lan_prune_delay", ifname, src_addr, + sizeof(uint32_t), option_len)) { + return -1; + } + + check_tlv_redefinition_uint16(__func__, "propagation_delay", ifname, + src_addr, *hello_options, + PIM_OPTION_MASK_LAN_PRUNE_DELAY, + PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr), + *hello_option_propagation_delay); + + PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY); + + *hello_option_propagation_delay = + PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr); + if (PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(tlv_curr)) { + PIM_OPTION_SET(*hello_options, + PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION); + } else { + PIM_OPTION_UNSET(*hello_options, + PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION); + } + ++tlv_curr; + ++tlv_curr; + *hello_option_override_interval = + PIM_TLV_GET_OVERRIDE_INTERVAL(tlv_curr); + + return 0; +} + +int pim_tlv_parse_dr_priority(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint32_t *hello_option_dr_priority, + uint16_t option_len, const uint8_t *tlv_curr) +{ + const char *label = "dr_priority"; + + if (check_tlv_length(__func__, label, ifname, src_addr, + sizeof(uint32_t), option_len)) { + return -1; + } + + check_tlv_redefinition_uint32( + __func__, label, ifname, src_addr, *hello_options, + PIM_OPTION_MASK_DR_PRIORITY, PIM_TLV_GET_DR_PRIORITY(tlv_curr), + *hello_option_dr_priority); + + PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_DR_PRIORITY); + + *hello_option_dr_priority = PIM_TLV_GET_DR_PRIORITY(tlv_curr); + + return 0; +} + +int pim_tlv_parse_generation_id(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint32_t *hello_option_generation_id, + uint16_t option_len, const uint8_t *tlv_curr) +{ + const char *label = "generation_id"; + + if (check_tlv_length(__func__, label, ifname, src_addr, + sizeof(uint32_t), option_len)) { + return -1; + } + + check_tlv_redefinition_uint32_hex(__func__, label, ifname, src_addr, + *hello_options, + PIM_OPTION_MASK_GENERATION_ID, + PIM_TLV_GET_GENERATION_ID(tlv_curr), + *hello_option_generation_id); + + PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_GENERATION_ID); + + *hello_option_generation_id = PIM_TLV_GET_GENERATION_ID(tlv_curr); + + return 0; +} + +int pim_parse_addr_ucast_prefix(struct prefix *p, const uint8_t *buf, + int buf_size) +{ + const int ucast_encoding_min_len = 3; /* 1 family + 1 type + 1 addr */ + const uint8_t *addr; + const uint8_t *pastend; + int family; + int type; + + if (buf_size < ucast_encoding_min_len) { + zlog_warn( + "%s: unicast address encoding overflow: left=%d needed=%d", + __func__, buf_size, ucast_encoding_min_len); + return -1; + } + + addr = buf; + pastend = buf + buf_size; + + family = *addr++; + type = *addr++; + + if (type) { + zlog_warn("%s: unknown unicast address encoding type=%d", + __func__, type); + return -2; + } + + switch (family) { + case PIM_MSG_ADDRESS_FAMILY_IPV4: + if ((addr + sizeof(struct in_addr)) > pastend) { + zlog_warn( + "%s: IPv4 unicast address overflow: left=%td needed=%zu", + __func__, pastend - addr, + sizeof(struct in_addr)); + return -3; + } + + p->family = AF_INET; /* notice: AF_INET != + PIM_MSG_ADDRESS_FAMILY_IPV4 */ + memcpy(&p->u.prefix4, addr, sizeof(struct in_addr)); + p->prefixlen = IPV4_MAX_BITLEN; + addr += sizeof(struct in_addr); + + break; + case PIM_MSG_ADDRESS_FAMILY_IPV6: + if ((addr + sizeof(struct in6_addr)) > pastend) { + zlog_warn( + "%s: IPv6 unicast address overflow: left=%td needed %zu", + __func__, pastend - addr, + sizeof(struct in6_addr)); + return -3; + } + + p->family = AF_INET6; + p->prefixlen = IPV6_MAX_BITLEN; + memcpy(&p->u.prefix6, addr, sizeof(struct in6_addr)); + addr += sizeof(struct in6_addr); + + break; + default: { + zlog_warn("%s: unknown unicast address encoding family=%d from", + __func__, family); + return -4; + } + } + + return addr - buf; +} + +int pim_parse_addr_ucast(pim_addr *out, const uint8_t *buf, int buf_size, + bool *wrong_af) +{ + struct prefix p; + int ret; + + ret = pim_parse_addr_ucast_prefix(&p, buf, buf_size); + if (ret < 0) + return ret; + + if (p.family != PIM_AF) { + *wrong_af = true; + return -5; + } + + memcpy(out, &p.u.val, sizeof(*out)); + return ret; +} + +int pim_parse_addr_group(pim_sgaddr *sg, const uint8_t *buf, int buf_size) +{ + const int grp_encoding_min_len = + 4; /* 1 family + 1 type + 1 reserved + 1 addr */ + const uint8_t *addr; + const uint8_t *pastend; + int family; + int type; + int mask_len; + + if (buf_size < grp_encoding_min_len) { + zlog_warn( + "%s: group address encoding overflow: left=%d needed=%d", + __func__, buf_size, grp_encoding_min_len); + return -1; + } + + addr = buf; + pastend = buf + buf_size; + + family = *addr++; + type = *addr++; + ++addr; /* skip b_reserved_z fields */ + mask_len = *addr++; + + if (type) { + zlog_warn("%s: unknown group address encoding type=%d from", + __func__, type); + return -2; + } + + if (family != PIM_MSG_ADDRESS_FAMILY) { + zlog_warn( + "%s: unknown group address encoding family=%d mask_len=%d from", + __func__, family, mask_len); + return -4; + } + + if ((addr + sizeof(sg->grp)) > pastend) { + zlog_warn( + "%s: group address overflow: left=%td needed=%zu from", + __func__, pastend - addr, sizeof(sg->grp)); + return -3; + } + + memcpy(&sg->grp, addr, sizeof(sg->grp)); + addr += sizeof(sg->grp); + + return addr - buf; +} + +int pim_parse_addr_source(pim_sgaddr *sg, uint8_t *flags, const uint8_t *buf, + int buf_size) +{ + const int src_encoding_min_len = + 4; /* 1 family + 1 type + 1 reserved + 1 addr */ + const uint8_t *addr; + const uint8_t *pastend; + int family; + int type; + int mask_len; + + if (buf_size < src_encoding_min_len) { + zlog_warn( + "%s: source address encoding overflow: left=%d needed=%d", + __func__, buf_size, src_encoding_min_len); + return -1; + } + + addr = buf; + pastend = buf + buf_size; + + family = *addr++; + type = *addr++; + *flags = *addr++; + mask_len = *addr++; + + if (type) { + zlog_warn( + "%s: unknown source address encoding type=%d: %02x%02x%02x%02x", + __func__, type, buf[0], buf[1], buf[2], buf[3]); + return -2; + } + + switch (family) { + case PIM_MSG_ADDRESS_FAMILY: + if ((addr + sizeof(sg->src)) > pastend) { + zlog_warn( + "%s: IP source address overflow: left=%td needed=%zu", + __func__, pastend - addr, sizeof(sg->src)); + return -3; + } + + memcpy(&sg->src, addr, sizeof(sg->src)); + + /* + RFC 4601: 4.9.1 Encoded Source and Group Address Formats + + Encoded-Source Address + + The mask length MUST be equal to the mask length in bits for + the given Address Family and Encoding Type (32 for IPv4 + native and 128 for IPv6 native). A router SHOULD ignore any + messages received with any other mask length. + */ + if (mask_len != PIM_MAX_BITLEN) { + zlog_warn("%s: IP bad source address mask: %d", + __func__, mask_len); + return -4; + } + + addr += sizeof(sg->src); + + break; + default: + zlog_warn( + "%s: unknown source address encoding family=%d: %02x%02x%02x%02x", + __func__, family, buf[0], buf[1], buf[2], buf[3]); + return -5; + } + + return addr - buf; +} + +#define FREE_ADDR_LIST(hello_option_addr_list) \ + { \ + if (hello_option_addr_list) { \ + list_delete(&hello_option_addr_list); \ + hello_option_addr_list = 0; \ + } \ + } + +int pim_tlv_parse_addr_list(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + struct list **hello_option_addr_list, + uint16_t option_len, const uint8_t *tlv_curr) +{ + const uint8_t *addr; + const uint8_t *pastend; + + assert(hello_option_addr_list); + + /* + Scan addr list + */ + addr = tlv_curr; + pastend = tlv_curr + option_len; + while (addr < pastend) { + struct prefix tmp, src_pfx; + int addr_offset; + + /* + Parse ucast addr + */ + addr_offset = + pim_parse_addr_ucast_prefix(&tmp, addr, pastend - addr); + if (addr_offset < 1) { + zlog_warn( + "%s: pim_parse_addr_ucast() failure: from %pPAs on %s", + __func__, &src_addr, ifname); + FREE_ADDR_LIST(*hello_option_addr_list); + return -1; + } + addr += addr_offset; + + /* + Debug + */ + if (PIM_DEBUG_PIM_TRACE) { + switch (tmp.family) { + case AF_INET: { + char addr_str[INET_ADDRSTRLEN]; + pim_inet4_dump("", tmp.u.prefix4, + addr_str, sizeof(addr_str)); + zlog_debug( + "%s: PIM hello TLV option: list_old_size=%d IPv4 address %s from %pPAs on %s", + __func__, + *hello_option_addr_list + ? ((int)listcount( + *hello_option_addr_list)) + : -1, + addr_str, &src_addr, ifname); + } break; + case AF_INET6: + break; + default: + zlog_debug( + "%s: PIM hello TLV option: list_old_size=%d UNKNOWN address family from %pPAs on %s", + __func__, + *hello_option_addr_list + ? ((int)listcount( + *hello_option_addr_list)) + : -1, + &src_addr, ifname); + } + } + + /* + Exclude neighbor's primary address if incorrectly included in + the secondary address list + */ + pim_addr_to_prefix(&src_pfx, src_addr); + if (!prefix_cmp(&tmp, &src_pfx)) { + zlog_warn( + "%s: ignoring primary address in secondary list from %pPAs on %s", + __func__, &src_addr, ifname); + continue; + } + + /* + Allocate list if needed + */ + if (!*hello_option_addr_list) { + *hello_option_addr_list = list_new(); + (*hello_option_addr_list)->del = prefix_free_lists; + } + + /* + Attach addr to list + */ + { + struct prefix *p; + p = prefix_new(); + prefix_copy(p, &tmp); + listnode_add(*hello_option_addr_list, p); + } + + } /* while (addr < pastend) */ + + /* + Mark hello option + */ + PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_ADDRESS_LIST); + + return 0; +} diff --git a/pimd/pim_tlv.h b/pimd/pim_tlv.h new file mode 100644 index 0000000..ea2af64 --- /dev/null +++ b/pimd/pim_tlv.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_TLV_H +#define PIM_TLV_H + +#include + +#include "config.h" +#include "if.h" +#include "linklist.h" + +#define PIM_MSG_OPTION_TYPE_HOLDTIME (1) +#define PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY (2) +#define PIM_MSG_OPTION_TYPE_DR_PRIORITY (19) +#define PIM_MSG_OPTION_TYPE_GENERATION_ID (20) +#define PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH (21) +#define PIM_MSG_OPTION_TYPE_ADDRESS_LIST (24) + +typedef uint32_t pim_hello_options; +#define PIM_OPTION_MASK_HOLDTIME (1 << 0) /* recv holdtime */ +#define PIM_OPTION_MASK_LAN_PRUNE_DELAY (1 << 1) /* recv lan_prune_delay */ +#define PIM_OPTION_MASK_DR_PRIORITY (1 << 2) /* recv dr_priority */ +#define PIM_OPTION_MASK_GENERATION_ID (1 << 3) /* recv generation_id */ +#define PIM_OPTION_MASK_ADDRESS_LIST (1 << 4) /* recv secondary address list */ +#define PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION (1 << 5) /* T bit value (valid if recv lan_prune_delay) */ + +#define PIM_RPT_BIT_MASK (1 << 0) +#define PIM_WILDCARD_BIT_MASK (1 << 1) + +#define PIM_OPTION_SET(options, option_mask) ((options) |= (option_mask)) +#define PIM_OPTION_UNSET(options, option_mask) ((options) &= ~(option_mask)) +#define PIM_OPTION_IS_SET(options, option_mask) ((options) & (option_mask)) + +#define PIM_TLV_GET_UINT16(buf) \ + ({ \ + uint16_t _tmp; \ + memcpy(&_tmp, (buf), sizeof(uint16_t)); \ + ntohs(_tmp); \ + }) +#define PIM_TLV_GET_UINT32(buf) \ + ({ \ + uint32_t _tmp; \ + memcpy(&_tmp, (buf), sizeof(uint32_t)); \ + ntohl(_tmp); \ + }) +#define PIM_TLV_GET_TYPE(buf) PIM_TLV_GET_UINT16(buf) +#define PIM_TLV_GET_LENGTH(buf) PIM_TLV_GET_UINT16(buf) +#define PIM_TLV_GET_HOLDTIME(buf) PIM_TLV_GET_UINT16(buf) +#define PIM_TLV_GET_PROPAGATION_DELAY(buf) (PIM_TLV_GET_UINT16(buf) & 0x7FFF) +#define PIM_TLV_GET_OVERRIDE_INTERVAL(buf) PIM_TLV_GET_UINT16(buf) +#define PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(buf) ((*(const uint8_t *)(buf)) & 0x80) +#define PIM_TLV_GET_DR_PRIORITY(buf) PIM_TLV_GET_UINT32(buf) +#define PIM_TLV_GET_GENERATION_ID(buf) PIM_TLV_GET_UINT32(buf) + +#define PIM_TLV_TYPE_SIZE (2) +#define PIM_TLV_LENGTH_SIZE (2) +#define PIM_TLV_MIN_SIZE (PIM_TLV_TYPE_SIZE + PIM_TLV_LENGTH_SIZE) +#define PIM_TLV_OPTION_SIZE(option_len) (PIM_TLV_MIN_SIZE + (option_len)) + +uint8_t *pim_tlv_append_uint16(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint16_t option_value); +uint8_t *pim_tlv_append_2uint16(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint16_t option_value1, + uint16_t option_value2); +uint8_t *pim_tlv_append_uint32(uint8_t *buf, const uint8_t *buf_pastend, + uint16_t option_type, uint32_t option_value); +uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf, const uint8_t *buf_pastend, + struct interface *ifp, int family); + +int pim_tlv_parse_holdtime(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint16_t *hello_option_holdtime, uint16_t option_len, + const uint8_t *tlv_curr); +int pim_tlv_parse_lan_prune_delay(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint16_t *hello_option_propagation_delay, + uint16_t *hello_option_override_interval, + uint16_t option_len, const uint8_t *tlv_curr); +int pim_tlv_parse_dr_priority(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint32_t *hello_option_dr_priority, + uint16_t option_len, const uint8_t *tlv_curr); +int pim_tlv_parse_generation_id(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + uint32_t *hello_option_generation_id, + uint16_t option_len, const uint8_t *tlv_curr); +int pim_tlv_parse_addr_list(const char *ifname, pim_addr src_addr, + pim_hello_options *hello_options, + struct list **hello_option_addr_list, + uint16_t option_len, const uint8_t *tlv_curr); + +int pim_encode_addr_ucast(uint8_t *buf, pim_addr addr); +int pim_encode_addr_ucast_prefix(uint8_t *buf, struct prefix *p); +int pim_encode_addr_group(uint8_t *buf, afi_t afi, int bidir, int scope, + pim_addr group); + +int pim_parse_addr_ucast(pim_addr *out, const uint8_t *buf, int buf_size, + bool *wrong_af); +int pim_parse_addr_ucast_prefix(struct prefix *out, const uint8_t *buf, + int buf_size); +int pim_parse_addr_group(pim_sgaddr *sg, const uint8_t *buf, int buf_size); +int pim_parse_addr_source(pim_sgaddr *sg, uint8_t *flags, const uint8_t *buf, + int buf_size); + +#endif /* PIM_TLV_H */ diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c new file mode 100644 index 0000000..7417f31 --- /dev/null +++ b/pimd/pim_upstream.c @@ -0,0 +1,2236 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "zclient.h" +#include "memory.h" +#include "frrevent.h" +#include "linklist.h" +#include "vty.h" +#include "plist.h" +#include "hash.h" +#include "jhash.h" +#include "wheel.h" +#include "network.h" +#include "frrdistance.h" + +#include "pimd.h" +#include "pim_pim.h" +#include "pim_str.h" +#include "pim_time.h" +#include "pim_iface.h" +#include "pim_join.h" +#include "pim_zlookup.h" +#include "pim_upstream.h" +#include "pim_ifchannel.h" +#include "pim_neighbor.h" +#include "pim_rpf.h" +#include "pim_zebra.h" +#include "pim_oil.h" +#include "pim_macro.h" +#include "pim_rp.h" +#include "pim_register.h" +#include "pim_msdp.h" +#include "pim_jp_agg.h" +#include "pim_nht.h" +#include "pim_ssm.h" +#include "pim_vxlan.h" +#include "pim_mlag.h" + +static void join_timer_stop(struct pim_upstream *up); +static void +pim_upstream_update_assert_tracking_desired(struct pim_upstream *up); +static bool pim_upstream_sg_running_proc(struct pim_upstream *up); + +/* + * A (*,G) or a (*,*) is going away + * remove the parent pointer from + * those pointing at us + */ +static void pim_upstream_remove_children(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct pim_upstream *child; + + if (!up->sources) + return; + + while (!list_isempty(up->sources)) { + child = listnode_head(up->sources); + listnode_delete(up->sources, child); + if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(child->flags)) { + PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(child->flags); + child = pim_upstream_del(pim, child, __func__); + } + if (child) { + child->parent = NULL; + if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(child->flags)) + pim_upstream_mroute_iif_update( + child->channel_oil, + __func__); + } + } + list_delete(&up->sources); +} + +/* + * A (*,G) or a (*,*) is being created + * Find the children that would point + * at us. + */ +static void pim_upstream_find_new_children(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct pim_upstream *child; + + if (!pim_addr_is_any(up->sg.src) && !pim_addr_is_any(up->sg.grp)) + return; + + if (pim_addr_is_any(up->sg.src) && pim_addr_is_any(up->sg.grp)) + return; + + frr_each (rb_pim_upstream, &pim->upstream_head, child) { + if (!pim_addr_is_any(up->sg.grp) && + !pim_addr_cmp(child->sg.grp, up->sg.grp) && (child != up)) { + child->parent = up; + listnode_add_sort(up->sources, child); + if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(child->flags)) + pim_upstream_mroute_iif_update( + child->channel_oil, + __func__); + } + } +} + +/* + * If we have a (*,*) || (S,*) there is no parent + * If we have a (S,G), find the (*,G) + * If we have a (*,G), find the (*,*) + */ +static struct pim_upstream *pim_upstream_find_parent(struct pim_instance *pim, + struct pim_upstream *child) +{ + pim_sgaddr any = child->sg; + struct pim_upstream *up = NULL; + + // (S,G) + if (!pim_addr_is_any(child->sg.src) && + !pim_addr_is_any(child->sg.grp)) { + any.src = PIMADDR_ANY; + up = pim_upstream_find(pim, &any); + + if (up) + listnode_add(up->sources, child); + + /* + * In case parent is MLAG entry copy the data to child + */ + if (up && PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(up->flags)) { + PIM_UPSTREAM_FLAG_SET_MLAG_INTERFACE(child->flags); + if (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags)) + PIM_UPSTREAM_FLAG_SET_MLAG_NON_DF(child->flags); + else + PIM_UPSTREAM_FLAG_UNSET_MLAG_NON_DF( + child->flags); + } + + return up; + } + + return NULL; +} + +static void upstream_channel_oil_detach(struct pim_upstream *up) +{ + struct channel_oil *channel_oil = up->channel_oil; + + if (channel_oil) { + /* Detaching from channel_oil, channel_oil may exist post del, + but upstream would not keep reference of it + */ + channel_oil->up = NULL; + up->channel_oil = NULL; + + /* attempt to delete channel_oil; if channel_oil is being held + * because of other references cleanup info such as "Mute" + * inferred from the parent upstream + */ + pim_channel_oil_upstream_deref(channel_oil); + } + +} + +static void pim_upstream_timers_stop(struct pim_upstream *up) +{ + EVENT_OFF(up->t_ka_timer); + EVENT_OFF(up->t_rs_timer); + EVENT_OFF(up->t_msdp_reg_timer); + EVENT_OFF(up->t_join_timer); +} + +struct pim_upstream *pim_upstream_del(struct pim_instance *pim, + struct pim_upstream *up, const char *name) +{ + struct listnode *node, *nnode; + struct pim_ifchannel *ch; + bool notify_msdp = false; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s(%s): Delete %s[%s] ref count: %d, flags: %d c_oil ref count %d (Pre decrement)", + __func__, name, up->sg_str, pim->vrf->name, + up->ref_count, up->flags, + up->channel_oil->oil_ref_count); + + assert(up->ref_count > 0); + + --up->ref_count; + + if (up->ref_count >= 1) + return up; + + if (PIM_DEBUG_TRACE) + zlog_debug("pim_upstream free vrf:%s %s flags 0x%x", + pim->vrf->name, up->sg_str, up->flags); + + if (pim_up_mlag_is_local(up)) + pim_mlag_up_local_del(pim, up); + + pim_upstream_timers_stop(up); + + if (up->join_state == PIM_UPSTREAM_JOINED) { + pim_jp_agg_single_upstream_send(&up->rpf, up, 0); + + if (pim_addr_is_any(up->sg.src)) { + /* if a (*, G) entry in the joined state is being + * deleted we + * need to notify MSDP */ + notify_msdp = true; + } + } + + join_timer_stop(up); + pim_jp_agg_upstream_verification(up, false); + up->rpf.source_nexthop.interface = NULL; + + if (!pim_addr_is_any(up->sg.src)) { + if (pim->upstream_sg_wheel) + wheel_remove_item(pim->upstream_sg_wheel, up); + notify_msdp = true; + } + + pim_mroute_del(up->channel_oil, __func__); + upstream_channel_oil_detach(up); + + for (ALL_LIST_ELEMENTS(up->ifchannels, node, nnode, ch)) + pim_ifchannel_delete(ch); + list_delete(&up->ifchannels); + + pim_upstream_remove_children(pim, up); + if (up->sources) + list_delete(&up->sources); + + if (up->parent && up->parent->sources) + listnode_delete(up->parent->sources, up); + up->parent = NULL; + + rb_pim_upstream_del(&pim->upstream_head, up); + + if (notify_msdp) { + pim_msdp_up_del(pim, &up->sg); + } + + /* When RP gets deleted, pim_rp_del() deregister addr with Zebra NHT + * and assign up->upstream_addr as INADDR_ANY. + * So before de-registering the upstream address, check if is not equal + * to INADDR_ANY. This is done in order to avoid de-registering for + * 255.255.255.255 which is maintained for some reason.. + */ + if (!pim_addr_is_any(up->upstream_addr)) { + /* Deregister addr with Zebra NHT */ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Deregister upstream %s addr %pPA with Zebra NHT", + __func__, up->sg_str, &up->upstream_addr); + pim_delete_tracked_nexthop(pim, up->upstream_addr, up, NULL); + } + + XFREE(MTYPE_PIM_UPSTREAM, up); + + return NULL; +} + +void pim_upstream_send_join(struct pim_upstream *up) +{ + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return; + } + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: RPF'%s=%pPA(%s) for Interface %s", __func__, + up->sg_str, &up->rpf.rpf_addr, + pim_upstream_state2str(up->join_state), + up->rpf.source_nexthop.interface->name); + if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { + zlog_debug("%s: can't send join upstream: RPF'%s=%pPA", + __func__, up->sg_str, &up->rpf.rpf_addr); + /* warning only */ + } + } + + /* send Join(S,G) to the current upstream neighbor */ + pim_jp_agg_single_upstream_send(&up->rpf, up, 1 /* join */); +} + +static void on_join_timer(struct event *t) +{ + struct pim_upstream *up; + + up = EVENT_ARG(t); + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return; + } + + /* + * In the case of a HFR we will not ahve anyone to send this to. + */ + if (PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) + return; + + /* + * Don't send the join if the outgoing interface is a loopback + * But since this might change leave the join timer running + */ + if (up->rpf.source_nexthop + .interface && !if_is_loopback(up->rpf.source_nexthop.interface)) + pim_upstream_send_join(up); + + join_timer_start(up); +} + +static void join_timer_stop(struct pim_upstream *up) +{ + struct pim_neighbor *nbr = NULL; + + EVENT_OFF(up->t_join_timer); + + if (up->rpf.source_nexthop.interface) + nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, + up->rpf.rpf_addr, true); + + if (nbr) + pim_jp_agg_remove_group(nbr->upstream_jp_agg, up, nbr); + + pim_jp_agg_upstream_verification(up, false); +} + +void join_timer_start(struct pim_upstream *up) +{ + struct pim_neighbor *nbr = NULL; + + if (up->rpf.source_nexthop.interface) { + nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, + up->rpf.rpf_addr, true); + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug( + "%s: starting %d sec timer for upstream (S,G)=%s", + __func__, router->t_periodic, up->sg_str); + } + } + + if (nbr) + pim_jp_agg_add_group(nbr->upstream_jp_agg, up, 1, nbr); + else { + EVENT_OFF(up->t_join_timer); + event_add_timer(router->master, on_join_timer, up, + router->t_periodic, &up->t_join_timer); + } + pim_jp_agg_upstream_verification(up, true); +} + +/* + * This is only called when we are switching the upstream + * J/P from one neighbor to another + * + * As such we need to remove from the old list and + * add to the new list. + */ +void pim_upstream_join_timer_restart(struct pim_upstream *up, + struct pim_rpf *old) +{ + // EVENT_OFF(up->t_join_timer); + join_timer_start(up); +} + +static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up, + int interval_msec) +{ + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("%s: restarting %d msec timer for upstream (S,G)=%s", + __func__, interval_msec, up->sg_str); + } + + EVENT_OFF(up->t_join_timer); + event_add_timer_msec(router->master, on_join_timer, up, interval_msec, + &up->t_join_timer); +} + +void pim_update_suppress_timers(uint32_t suppress_time) +{ + struct pim_instance *pim; + struct vrf *vrf; + unsigned int old_rp_ka_time; + + /* stash the old one so we know which values were manually configured */ + old_rp_ka_time = (3 * router->register_suppress_time + + router->register_probe_time); + router->register_suppress_time = suppress_time; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + if (!pim) + continue; + + /* Only adjust if not manually configured */ + if (pim->rp_keep_alive_time == old_rp_ka_time) + pim->rp_keep_alive_time = PIM_RP_KEEPALIVE_PERIOD; + } +} + +void pim_upstream_join_suppress(struct pim_upstream *up, pim_addr rpf, + int holdtime) +{ + long t_joinsuppress_msec; + long join_timer_remain_msec = 0; + struct pim_neighbor *nbr = NULL; + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return; + } + + t_joinsuppress_msec = + MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface), + 1000 * holdtime); + + if (up->t_join_timer) + join_timer_remain_msec = + pim_time_timer_remain_msec(up->t_join_timer); + else { + /* Remove it from jp agg from the nbr for suppression */ + nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, + up->rpf.rpf_addr, true); + + if (nbr) { + join_timer_remain_msec = + pim_time_timer_remain_msec(nbr->jp_timer); + } + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s %s: detected Join%s to RPF'(S,G)=%pPA: join_timer=%ld msec t_joinsuppress=%ld msec", + __FILE__, __func__, up->sg_str, &rpf, + join_timer_remain_msec, t_joinsuppress_msec); + + if (join_timer_remain_msec < t_joinsuppress_msec) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s %s: suppressing Join(S,G)=%s for %ld msec", + __FILE__, __func__, up->sg_str, + t_joinsuppress_msec); + } + + if (nbr) + pim_jp_agg_remove_group(nbr->upstream_jp_agg, up, nbr); + + pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec); + } +} + +void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label, + struct pim_upstream *up) +{ + long join_timer_remain_msec; + int t_override_msec; + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + return; + } + + t_override_msec = + pim_if_t_override_msec(up->rpf.source_nexthop.interface); + + if (up->t_join_timer) { + join_timer_remain_msec = + pim_time_timer_remain_msec(up->t_join_timer); + } else { + /* upstream join tracked with neighbor jp timer */ + struct pim_neighbor *nbr; + + nbr = pim_neighbor_find(up->rpf.source_nexthop.interface, + up->rpf.rpf_addr, true); + + if (nbr) + join_timer_remain_msec = + pim_time_timer_remain_msec(nbr->jp_timer); + else + /* Manipulate such that override takes place */ + join_timer_remain_msec = t_override_msec + 1; + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: to RPF'%s=%pPA: join_timer=%ld msec t_override=%d msec", + debug_label, up->sg_str, &up->rpf.rpf_addr, + join_timer_remain_msec, t_override_msec); + + if (join_timer_remain_msec > t_override_msec) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s: decreasing (S,G)=%s join timer to t_override=%d msec", + debug_label, up->sg_str, t_override_msec); + } + + pim_upstream_join_timer_restart_msec(up, t_override_msec); + } +} + +static void forward_on(struct pim_upstream *up) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch = NULL; + + /* scan (S,G) state */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + if (pim_macro_chisin_oiflist(ch)) + pim_forward_start(ch); + + } /* scan iface channel list */ +} + +static void forward_off(struct pim_upstream *up) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + + /* scan per-interface (S,G) state */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + + pim_forward_stop(ch); + + } /* scan iface channel list */ +} + +int pim_upstream_could_register(struct pim_upstream *up) +{ + struct pim_interface *pim_ifp = NULL; + + /* FORCE_PIMREG is a generic flag to let an app like VxLAN-AA register + * a source on an upstream entry even if the source is not directly + * connected on the IIF. + */ + if (PIM_UPSTREAM_FLAG_TEST_FORCE_PIMREG(up->flags)) + return 1; + + if (up->rpf.source_nexthop.interface) + pim_ifp = up->rpf.source_nexthop.interface->info; + else { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + } + + if (pim_ifp && PIM_I_am_DR(pim_ifp) + && pim_if_connected_to_source(up->rpf.source_nexthop.interface, + up->sg.src)) + return 1; + + return 0; +} + +/* Source registration is suppressed for SSM groups. When the SSM range changes + * we re-revaluate register setup for existing upstream entries */ +void pim_upstream_register_reevaluate(struct pim_instance *pim) +{ + struct pim_upstream *up; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + /* If FHR is set CouldRegister is True. Also check if the flow + * is actually active; if it is not kat setup will trigger + * source + * registration whenever the flow becomes active. */ + if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) || + !pim_upstream_is_kat_running(up)) + continue; + + if (pim_is_grp_ssm(pim, up->sg.grp)) { + /* clear the register state for SSM groups */ + if (up->reg_state != PIM_REG_NOINFO) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "Clear register for %s as G is now SSM", + up->sg_str); + /* remove regiface from the OIL if it is there*/ + pim_channel_del_oif(up->channel_oil, + pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, + __func__); + up->reg_state = PIM_REG_NOINFO; + } + } else { + /* register ASM sources with the RP */ + if (up->reg_state == PIM_REG_NOINFO) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "Register %s as G is now ASM", + up->sg_str); + pim_channel_add_oif(up->channel_oil, + pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, + __func__); + up->reg_state = PIM_REG_JOIN; + } + } + } +} + +/* RFC7761, Section 4.2 “Data Packet Forwarding Rules” says we should + * forward a S - + * 1. along the SPT if SPTbit is set + * 2. and along the RPT if SPTbit is not set + * If forwarding is hw accelerated i.e. control and dataplane components + * are separate you may not be able to reliably set SPT bit on intermediate + * routers while still forwarding on the (S,G,rpt). + * + * This macro is a slight deviation on the RFC and uses "traffic-agnostic" + * criteria to decide between using the RPT vs. SPT for forwarding. + */ +void pim_upstream_update_use_rpt(struct pim_upstream *up, + bool update_mroute) +{ + bool old_use_rpt; + bool new_use_rpt; + + if (pim_addr_is_any(up->sg.src)) + return; + + old_use_rpt = !!PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags); + + /* We will use the SPT (IIF=RPF_interface(S) if - + * 1. We have decided to join the SPT + * 2. We are FHR + * 3. Source is directly connected + * 4. We are RP (parent's IIF is lo or vrf-device) + * In all other cases the source will stay along the RPT and + * IIF=RPF_interface(RP). + */ + if (up->join_state == PIM_UPSTREAM_JOINED || + PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) || + pim_if_connected_to_source( + up->rpf.source_nexthop.interface, + up->sg.src) || + /* XXX - need to switch this to a more efficient + * lookup API + */ + I_am_RP(up->pim, up->sg.grp)) + /* use SPT */ + PIM_UPSTREAM_FLAG_UNSET_USE_RPT(up->flags); + else + /* use RPT */ + PIM_UPSTREAM_FLAG_SET_USE_RPT(up->flags); + + new_use_rpt = !!PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags); + if (old_use_rpt != new_use_rpt) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s switched from %s to %s", up->sg_str, + old_use_rpt ? "RPT" : "SPT", + new_use_rpt ? "RPT" : "SPT"); + if (update_mroute) + pim_upstream_mroute_add(up->channel_oil, __func__); + } +} + +/* some events like RP change require re-evaluation of SGrpt across + * all groups + */ +void pim_upstream_reeval_use_rpt(struct pim_instance *pim) +{ + struct pim_upstream *up; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_addr_is_any(up->sg.src)) + continue; + + pim_upstream_update_use_rpt(up, true /*update_mroute*/); + } +} + +void pim_upstream_switch(struct pim_instance *pim, struct pim_upstream *up, + enum pim_upstream_state new_state) +{ + enum pim_upstream_state old_state = up->join_state; + + if (pim_addr_is_any(up->upstream_addr)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: RPF not configured for %s", __func__, + up->sg_str); + return; + } + + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug("%s: RP not reachable for %s", __func__, + up->sg_str); + return; + } + + if (PIM_DEBUG_PIM_EVENTS) { + zlog_debug("%s: PIM_UPSTREAM_%s: (S,G) old: %s new: %s", + __func__, up->sg_str, + pim_upstream_state2str(up->join_state), + pim_upstream_state2str(new_state)); + } + + up->join_state = new_state; + if (old_state != new_state) + up->state_transition = pim_time_monotonic_sec(); + + pim_upstream_update_assert_tracking_desired(up); + + if (new_state == PIM_UPSTREAM_JOINED) { + pim_upstream_inherited_olist_decide(pim, up); + if (old_state != PIM_UPSTREAM_JOINED) { + int old_fhr = PIM_UPSTREAM_FLAG_TEST_FHR(up->flags); + + pim_msdp_up_join_state_changed(pim, up); + if (pim_upstream_could_register(up)) { + PIM_UPSTREAM_FLAG_SET_FHR(up->flags); + if (!old_fhr + && PIM_UPSTREAM_FLAG_TEST_SRC_STREAM( + up->flags)) { + pim_upstream_keep_alive_timer_start( + up, pim->keep_alive_time); + pim_register_join(up); + } + } else { + pim_upstream_send_join(up); + join_timer_start(up); + } + } + if (old_state != new_state) + pim_upstream_update_use_rpt(up, true /*update_mroute*/); + } else { + bool old_use_rpt; + bool new_use_rpt; + bool send_xg_jp = false; + + forward_off(up); + /* + * RFC 4601 Sec 4.5.7: + * JoinDesired(S,G) -> False, set SPTbit to false. + */ + if (!pim_addr_is_any(up->sg.src)) + up->sptbit = PIM_UPSTREAM_SPTBIT_FALSE; + + if (old_state == PIM_UPSTREAM_JOINED) + pim_msdp_up_join_state_changed(pim, up); + + if (old_state != new_state) { + old_use_rpt = + !!PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags); + pim_upstream_update_use_rpt(up, true /*update_mroute*/); + new_use_rpt = + !!PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags); + if (new_use_rpt && + (new_use_rpt != old_use_rpt) && + up->parent) + /* we have decided to switch from the SPT back + * to the RPT which means we need to cancel + * any previously sent SGrpt prunes immediately + */ + send_xg_jp = true; + } + + /* IHR, Trigger SGRpt on *,G IIF to prune S,G from RPT towards + RP. + If I am RP for G then send S,G prune to its IIF. */ + if (pim_upstream_is_sg_rpt(up) && up->parent && + !I_am_RP(pim, up->sg.grp)) + send_xg_jp = true; + + pim_jp_agg_single_upstream_send(&up->rpf, up, 0 /* prune */); + + if (send_xg_jp) { + if (PIM_DEBUG_PIM_TRACE_DETAIL) + zlog_debug( + "re-join RPT; *,G IIF %s S,G IIF %s ", + up->parent->rpf.source_nexthop.interface ? + up->parent->rpf.source_nexthop.interface->name + : "Unknown", + up->rpf.source_nexthop.interface ? + up->rpf.source_nexthop.interface->name : + "Unknown"); + pim_jp_agg_single_upstream_send(&up->parent->rpf, + up->parent, + 1 /* (W,G) Join */); + } + join_timer_stop(up); + } +} + +int pim_upstream_compare(const struct pim_upstream *up1, + const struct pim_upstream *up2) +{ + return pim_sgaddr_cmp(up1->sg, up2->sg); +} + +void pim_upstream_fill_static_iif(struct pim_upstream *up, + struct interface *incoming) +{ + up->rpf.source_nexthop.interface = incoming; + + /* reset other parameters to matched a connected incoming interface */ + up->rpf.source_nexthop.mrib_nexthop_addr = PIMADDR_ANY; + up->rpf.source_nexthop.mrib_metric_preference = + ZEBRA_CONNECT_DISTANCE_DEFAULT; + up->rpf.source_nexthop.mrib_route_metric = 0; + up->rpf.rpf_addr = PIMADDR_ANY; +} + +static struct pim_upstream *pim_upstream_new(struct pim_instance *pim, + pim_sgaddr *sg, + struct interface *incoming, + int flags, + struct pim_ifchannel *ch) +{ + enum pim_rpf_result rpf_result; + struct pim_interface *pim_ifp; + struct pim_upstream *up; + + up = XCALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up)); + + up->pim = pim; + up->sg = *sg; + snprintfrr(up->sg_str, sizeof(up->sg_str), "%pSG", sg); + if (ch) + ch->upstream = up; + + rb_pim_upstream_add(&pim->upstream_head, up); + /* Set up->upstream_addr as INADDR_ANY, if RP is not + * configured and retain the upstream data structure + */ + if (!pim_rp_set_upstream_addr(pim, &up->upstream_addr, sg->src, + sg->grp)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: Received a (*,G) with no RP configured", + __func__); + } + + up->parent = pim_upstream_find_parent(pim, up); + if (pim_addr_is_any(up->sg.src)) { + up->sources = list_new(); + up->sources->cmp = + (int (*)(void *, void *))pim_upstream_compare; + } else + up->sources = NULL; + + pim_upstream_find_new_children(pim, up); + up->flags = flags; + up->ref_count = 1; + up->t_join_timer = NULL; + up->t_ka_timer = NULL; + up->t_rs_timer = NULL; + up->t_msdp_reg_timer = NULL; + up->join_state = PIM_UPSTREAM_NOTJOINED; + up->reg_state = PIM_REG_NOINFO; + up->state_transition = pim_time_monotonic_sec(); + up->channel_oil = pim_channel_oil_add(pim, &up->sg, __func__); + up->sptbit = PIM_UPSTREAM_SPTBIT_FALSE; + + up->rpf.source_nexthop.interface = NULL; + up->rpf.source_nexthop.mrib_nexthop_addr = PIMADDR_ANY; + up->rpf.source_nexthop.mrib_metric_preference = + router->infinite_assert_metric.metric_preference; + up->rpf.source_nexthop.mrib_route_metric = + router->infinite_assert_metric.route_metric; + up->rpf.rpf_addr = PIMADDR_ANY; + up->ifchannels = list_new(); + up->ifchannels->cmp = (int (*)(void *, void *))pim_ifchannel_compare; + + if (!pim_addr_is_any(up->sg.src)) { + wheel_add_item(pim->upstream_sg_wheel, up); + + /* Inherit the DF role from the parent (*, G) entry for + * VxLAN BUM groups + */ + if (up->parent + && PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->parent->flags) + && PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->parent->flags)) { + PIM_UPSTREAM_FLAG_SET_MLAG_NON_DF(up->flags); + if (PIM_DEBUG_VXLAN) + zlog_debug( + "upstream %s inherited mlag non-df flag from parent", + up->sg_str); + } + } + + if (PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags) + || PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) { + pim_upstream_fill_static_iif(up, incoming); + pim_ifp = up->rpf.source_nexthop.interface->info; + assert(pim_ifp); + pim_upstream_update_use_rpt(up, + false /*update_mroute*/); + pim_upstream_mroute_iif_update(up->channel_oil, __func__); + + if (PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) { + /* + * Set the right RPF so that future changes will + * be right + */ + (void)pim_rpf_update(pim, up, NULL, __func__); + pim_upstream_keep_alive_timer_start( + up, pim->keep_alive_time); + } + } else if (!pim_addr_is_any(up->upstream_addr)) { + pim_upstream_update_use_rpt(up, + false /*update_mroute*/); + rpf_result = pim_rpf_update(pim, up, NULL, __func__); + if (rpf_result == PIM_RPF_FAILURE) { + up->channel_oil->oil_inherited_rescan = 1; + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Attempting to create upstream(%s), Unable to RPF for source", + __func__, up->sg_str); + } + + /* Consider a case where (S,G,rpt) prune is received and this + * upstream is getting created due to that, then as per RFC + * until prune pending time we need to behave same as NOINFO + * state, therefore do not install if OIF is NULL until then + * This is for PIM Conformance PIM-SM 16.3 fix + * When the prune pending timer pop, this mroute will get + * installed with none as OIF */ + if (up->rpf.source_nexthop.interface && + !(pim_upstream_empty_inherited_olist(up) && (ch != NULL) && + PIM_IF_FLAG_TEST_S_G_RPT(ch->flags))) { + pim_upstream_mroute_iif_update(up->channel_oil, + __func__); + } + } + + /* send the entry to the MLAG peer */ + /* XXX - duplicate send is possible here if pim_rpf_update + * successfully resolved the nexthop + */ + if (pim_up_mlag_is_local(up) + || PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(up->flags)) + pim_mlag_up_local_add(pim, up); + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s: Created Upstream %s upstream_addr %pPAs ref count %d increment", + __func__, up->sg_str, &up->upstream_addr, + up->ref_count); + } + + return up; +} + +uint32_t pim_up_mlag_local_cost(struct pim_upstream *up) +{ + if (!(pim_up_mlag_is_local(up)) + && !(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE)) + return router->infinite_assert_metric.route_metric; + + if ((up->rpf.source_nexthop.interface == + up->pim->vxlan.peerlink_rif) && + (up->rpf.source_nexthop.mrib_route_metric < + (router->infinite_assert_metric.route_metric - + PIM_UPSTREAM_MLAG_PEERLINK_PLUS_METRIC))) + return up->rpf.source_nexthop.mrib_route_metric + + PIM_UPSTREAM_MLAG_PEERLINK_PLUS_METRIC; + + return up->rpf.source_nexthop.mrib_route_metric; +} + +uint32_t pim_up_mlag_peer_cost(struct pim_upstream *up) +{ + if (!(up->flags & PIM_UPSTREAM_FLAG_MASK_MLAG_PEER)) + return router->infinite_assert_metric.route_metric; + + return up->mlag.peer_mrib_metric; +} + +struct pim_upstream *pim_upstream_find(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct pim_upstream lookup; + struct pim_upstream *up = NULL; + + lookup.sg = *sg; + up = rb_pim_upstream_find(&pim->upstream_head, &lookup); + return up; +} + +struct pim_upstream *pim_upstream_find_or_add(pim_sgaddr *sg, + struct interface *incoming, + int flags, const char *name) +{ + struct pim_interface *pim_ifp = incoming->info; + + return (pim_upstream_add(pim_ifp->pim, sg, incoming, flags, name, + NULL)); +} + +void pim_upstream_ref(struct pim_upstream *up, int flags, const char *name) +{ + /* if a local MLAG reference is being created we need to send the mroute + * to the peer + */ + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->flags) && + PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(flags)) { + PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(up->flags); + pim_mlag_up_local_add(up->pim, up); + } + + /* when we go from non-FHR to FHR we need to re-eval traffic + * forwarding path + */ + if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags) && + PIM_UPSTREAM_FLAG_TEST_FHR(flags)) { + PIM_UPSTREAM_FLAG_SET_FHR(up->flags); + pim_upstream_update_use_rpt(up, true /*update_mroute*/); + } + + /* re-eval joinDesired; clearing peer-msdp-sa flag can + * cause JD to change + */ + if (!PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags) && + PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(flags)) { + PIM_UPSTREAM_FLAG_SET_SRC_MSDP(up->flags); + pim_upstream_update_join_desired(up->pim, up); + } + + up->flags |= flags; + ++up->ref_count; + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s(%s): upstream %s ref count %d increment", + __func__, name, up->sg_str, up->ref_count); +} + +struct pim_upstream *pim_upstream_add(struct pim_instance *pim, pim_sgaddr *sg, + struct interface *incoming, int flags, + const char *name, + struct pim_ifchannel *ch) +{ + struct pim_upstream *up = NULL; + int found = 0; + + up = pim_upstream_find(pim, sg); + if (up) { + pim_upstream_ref(up, flags, name); + found = 1; + } else { + up = pim_upstream_new(pim, sg, incoming, flags, ch); + } + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s(%s): %s, iif %pPA (%s) found: %d: ref_count: %d", + __func__, name, up->sg_str, &up->rpf.rpf_addr, + up->rpf.source_nexthop.interface ? up->rpf.source_nexthop + .interface->name + : "Unknown", + found, up->ref_count); + } + + return up; +} + +/* + * Passed in up must be the upstream for ch. starch is NULL if no + * information + * This function is copied over from + * pim_upstream_evaluate_join_desired_interface but limited to + * parent (*,G)'s includes/joins. + */ +int pim_upstream_eval_inherit_if(struct pim_upstream *up, + struct pim_ifchannel *ch, + struct pim_ifchannel *starch) +{ + /* if there is an explicit prune for this interface we cannot + * add it to the OIL + */ + if (ch) { + if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) + return 0; + } + + /* Check if the OIF can be inherited fron the (*,G) entry + */ + if (starch) { + if (!pim_macro_ch_lost_assert(starch) + && pim_macro_chisin_joins_or_include(starch)) + return 1; + } + + return 0; +} + +/* + * Passed in up must be the upstream for ch. starch is NULL if no + * information + */ +int pim_upstream_evaluate_join_desired_interface(struct pim_upstream *up, + struct pim_ifchannel *ch, + struct pim_ifchannel *starch) +{ + if (ch) { + if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) + return 0; + + if (!pim_macro_ch_lost_assert(ch) + && pim_macro_chisin_joins_or_include(ch)) + return 1; + } + + /* + * joins (*,G) + */ + if (starch) { + /* XXX: check on this with donald + * we are looking for PIM_IF_FLAG_MASK_S_G_RPT in + * upstream flags? + */ +#if 0 + if (PIM_IF_FLAG_TEST_S_G_RPT(starch->upstream->flags)) + return 0; +#endif + + if (!pim_macro_ch_lost_assert(starch) + && pim_macro_chisin_joins_or_include(starch)) + return 1; + } + + return 0; +} + +/* Returns true if immediate OIL is empty and is used to evaluate + * JoinDesired. See pim_upstream_evaluate_join_desired. + */ +static bool pim_upstream_empty_immediate_olist(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct interface *ifp; + struct pim_ifchannel *ch; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + if (!ifp->info) + continue; + + ch = pim_ifchannel_find(ifp, &up->sg); + if (!ch) + continue; + + /* If we have even one immediate OIF we can return with + * not-empty + */ + if (pim_upstream_evaluate_join_desired_interface(up, ch, + NULL /* starch */)) + return false; + } /* scan iface channel list */ + + /* immediate_oil is empty */ + return true; +} + + +static inline bool pim_upstream_is_msdp_peer_sa(struct pim_upstream *up) +{ + return PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(up->flags); +} + +/* + * bool JoinDesired(*,G) { + * if (immediate_olist(*,G) != NULL) + * return TRUE + * else + * return FALSE + * } + * + * bool JoinDesired(S,G) { + * return( immediate_olist(S,G) != NULL + * OR ( KeepaliveTimer(S,G) is running + * AND inherited_olist(S,G) != NULL ) ) + * } + */ +bool pim_upstream_evaluate_join_desired(struct pim_instance *pim, + struct pim_upstream *up) +{ + bool empty_imm_oil; + bool empty_inh_oil; + + empty_imm_oil = pim_upstream_empty_immediate_olist(pim, up); + + /* (*,G) */ + if (pim_addr_is_any(up->sg.src)) + return !empty_imm_oil; + + /* (S,G) */ + if (!empty_imm_oil) + return true; + empty_inh_oil = pim_upstream_empty_inherited_olist(up); + if (!empty_inh_oil && + (pim_upstream_is_kat_running(up) || + pim_upstream_is_msdp_peer_sa(up))) + return true; + + return false; +} + +/* + See also pim_upstream_evaluate_join_desired() above. +*/ +void pim_upstream_update_join_desired(struct pim_instance *pim, + struct pim_upstream *up) +{ + int was_join_desired; /* boolean */ + int is_join_desired; /* boolean */ + + was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags); + + is_join_desired = pim_upstream_evaluate_join_desired(pim, up); + if (is_join_desired) + PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags); + else + PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags); + + /* switched from false to true */ + if (is_join_desired && (up->join_state == PIM_UPSTREAM_NOTJOINED)) { + pim_upstream_switch(pim, up, PIM_UPSTREAM_JOINED); + return; + } + + /* switched from true to false */ + if (!is_join_desired && was_join_desired) { + pim_upstream_switch(pim, up, PIM_UPSTREAM_NOTJOINED); + return; + } +} + +/* + RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages + Transitions from Joined State + RPF'(S,G) GenID changes + + The upstream (S,G) state machine remains in Joined state. If the + Join Timer is set to expire in more than t_override seconds, reset + it so that it expires after t_override seconds. +*/ +void pim_upstream_rpf_genid_changed(struct pim_instance *pim, + pim_addr neigh_addr) +{ + struct pim_upstream *up; + + /* + * Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr + */ + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + pim_addr rpf_addr; + + rpf_addr = up->rpf.rpf_addr; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: matching neigh=%pPA against upstream (S,G)=%s[%s] joined=%d rpf_addr=%pPA", + __func__, &neigh_addr, up->sg_str, + pim->vrf->name, + up->join_state == PIM_UPSTREAM_JOINED, + &rpf_addr); + + /* consider only (S,G) upstream in Joined state */ + if (up->join_state != PIM_UPSTREAM_JOINED) + continue; + + /* match RPF'(S,G)=neigh_addr */ + if (pim_addr_cmp(rpf_addr, neigh_addr)) + continue; + + pim_upstream_join_timer_decrease_to_t_override( + "RPF'(S,G) GenID change", up); + } +} + + +void pim_upstream_rpf_interface_changed(struct pim_upstream *up, + struct interface *old_rpf_ifp) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + + /* search all ifchannels */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { + if ( + /* RPF_interface(S) was NOT I */ + (old_rpf_ifp == ch->interface) && + /* RPF_interface(S) stopped being I */ + (ch->upstream->rpf.source_nexthop + .interface) && + (ch->upstream->rpf.source_nexthop + .interface != ch->interface)) { + assert_action_a5(ch); + } + } /* PIM_IFASSERT_I_AM_LOSER */ + + pim_ifchannel_update_assert_tracking_desired(ch); + } +} + +void pim_upstream_update_could_assert(struct pim_upstream *up) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + + /* scan per-interface (S,G) state */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + pim_ifchannel_update_could_assert(ch); + } /* scan iface channel list */ +} + +void pim_upstream_update_my_assert_metric(struct pim_upstream *up) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_ifchannel *ch; + + /* scan per-interface (S,G) state */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + pim_ifchannel_update_my_assert_metric(ch); + + } /* scan iface channel list */ +} + +static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up) +{ + struct listnode *chnode; + struct listnode *chnextnode; + struct pim_interface *pim_ifp; + struct pim_ifchannel *ch; + + /* scan per-interface (S,G) state */ + for (ALL_LIST_ELEMENTS(up->ifchannels, chnode, chnextnode, ch)) { + if (!ch->interface) + continue; + pim_ifp = ch->interface->info; + if (!pim_ifp) + continue; + + pim_ifchannel_update_assert_tracking_desired(ch); + + } /* scan iface channel list */ +} + +/* When kat is stopped CouldRegister goes to false so we need to + * transition the (S, G) on FHR to NI state and remove reg tunnel + * from the OIL */ +static void pim_upstream_fhr_kat_expiry(struct pim_instance *pim, + struct pim_upstream *up) +{ + if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) + return; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("kat expired on %s; clear fhr reg state", + up->sg_str); + + /* stop reg-stop timer */ + EVENT_OFF(up->t_rs_timer); + /* remove regiface from the OIL if it is there*/ + pim_channel_del_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, __func__); + /* clear the register state */ + up->reg_state = PIM_REG_NOINFO; + PIM_UPSTREAM_FLAG_UNSET_FHR(up->flags); +} + +/* When kat is started CouldRegister can go to true. And if it does we + * need to transition the (S, G) on FHR to JOINED state and add reg tunnel + * to the OIL */ +static void pim_upstream_fhr_kat_start(struct pim_upstream *up) +{ + if (pim_upstream_could_register(up)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "kat started on %s; set fhr reg state to joined", + up->sg_str); + + PIM_UPSTREAM_FLAG_SET_FHR(up->flags); + if (up->reg_state == PIM_REG_NOINFO) + pim_register_join(up); + pim_upstream_update_use_rpt(up, true /*update_mroute*/); + } +} + +/* + * On an RP, the PMBR value must be cleared when the + * Keepalive Timer expires + * KAT expiry indicates that flow is inactive. If the flow was created or + * maintained by activity now is the time to deref it. + */ +struct pim_upstream *pim_upstream_keep_alive_timer_proc( + struct pim_upstream *up) +{ + struct pim_instance *pim; + + pim = up->channel_oil->pim; + + if (PIM_UPSTREAM_FLAG_TEST_DISABLE_KAT_EXPIRY(up->flags)) { + /* if the router is a PIM vxlan encapsulator we prevent expiry + * of KAT as the mroute is pre-setup without any traffic + */ + pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); + return up; + } + + if (I_am_RP(pim, up->sg.grp)) { + /* + * Handle Border Router + * We need to do more here :) + * But this is the start. + */ + } + + /* source is no longer active - pull the SA from MSDP's cache */ + pim_msdp_sa_local_del(pim, &up->sg); + + /* JoinDesired can change when KAT is started or stopped */ + pim_upstream_update_join_desired(pim, up); + + /* if entry was created because of activity we need to deref it */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { + pim_upstream_fhr_kat_expiry(pim, up); + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "kat expired on %s[%s]; remove stream reference", + up->sg_str, pim->vrf->name); + PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(up->flags); + + /* Return if upstream entry got deleted.*/ + if (!pim_upstream_del(pim, up, __func__)) + return NULL; + } + if (PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) { + PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(up->flags); + + if (!pim_upstream_del(pim, up, __func__)) + return NULL; + } + + /* upstream reference would have been added to track the local + * membership if it is LHR. We have to clear it when KAT expires. + * Otherwise would result in stale entry with uncleared ref count. + */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) { + struct pim_upstream *parent = up->parent; + + PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(up->flags); + up = pim_upstream_del(pim, up, __func__); + + if (parent) { + pim_jp_agg_single_upstream_send(&parent->rpf, parent, + true); + } + } + + return up; +} +static void pim_upstream_keep_alive_timer(struct event *t) +{ + struct pim_upstream *up; + + up = EVENT_ARG(t); + + /* pull the stats and re-check */ + if (pim_upstream_sg_running_proc(up)) + /* kat was restarted because of new activity */ + return; + + pim_upstream_keep_alive_timer_proc(up); +} + +void pim_upstream_keep_alive_timer_start(struct pim_upstream *up, uint32_t time) +{ + if (!PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("kat start on %s with no stream reference", + up->sg_str); + } + EVENT_OFF(up->t_ka_timer); + event_add_timer(router->master, pim_upstream_keep_alive_timer, up, time, + &up->t_ka_timer); + + /* any time keepalive is started against a SG we will have to + * re-evaluate our active source database */ + pim_msdp_sa_local_update(up); + /* JoinDesired can change when KAT is started or stopped */ + pim_upstream_update_join_desired(up->pim, up); +} + +/* MSDP on RP needs to know if a source is registerable to this RP */ +static void pim_upstream_msdp_reg_timer(struct event *t) +{ + struct pim_upstream *up = EVENT_ARG(t); + struct pim_instance *pim = up->channel_oil->pim; + + /* source is no longer active - pull the SA from MSDP's cache */ + pim_msdp_sa_local_del(pim, &up->sg); +} + +void pim_upstream_msdp_reg_timer_start(struct pim_upstream *up) +{ + EVENT_OFF(up->t_msdp_reg_timer); + event_add_timer(router->master, pim_upstream_msdp_reg_timer, up, + PIM_MSDP_REG_RXED_PERIOD, &up->t_msdp_reg_timer); + + pim_msdp_sa_local_update(up); +} + +/* + * 4.2.1 Last-Hop Switchover to the SPT + * + * In Sparse-Mode PIM, last-hop routers join the shared tree towards the + * RP. Once traffic from sources to joined groups arrives at a last-hop + * router, it has the option of switching to receive the traffic on a + * shortest path tree (SPT). + * + * The decision for a router to switch to the SPT is controlled as + * follows: + * + * void + * CheckSwitchToSpt(S,G) { + * if ( ( pim_include(*,G) (-) pim_exclude(S,G) + * (+) pim_include(S,G) != NULL ) + * AND SwitchToSptDesired(S,G) ) { + * # Note: Restarting the KAT will result in the SPT switch + * set KeepaliveTimer(S,G) to Keepalive_Period + * } + * } + * + * SwitchToSptDesired(S,G) is a policy function that is implementation + * defined. An "infinite threshold" policy can be implemented by making + * SwitchToSptDesired(S,G) return false all the time. A "switch on + * first packet" policy can be implemented by making + * SwitchToSptDesired(S,G) return true once a single packet has been + * received for the source and group. + */ +int pim_upstream_switch_to_spt_desired_on_rp(struct pim_instance *pim, + pim_sgaddr *sg) +{ + if (I_am_RP(pim, sg->grp)) + return 1; + + return 0; +} + +int pim_upstream_is_sg_rpt(struct pim_upstream *up) +{ + struct listnode *chnode; + struct pim_ifchannel *ch; + + for (ALL_LIST_ELEMENTS_RO(up->ifchannels, chnode, ch)) { + if (PIM_IF_FLAG_TEST_S_G_RPT(ch->flags)) + return 1; + } + + return 0; +} +/* + * After receiving a packet set SPTbit: + * void + * Update_SPTbit(S,G,iif) { + * if ( iif == RPF_interface(S) + * AND JoinDesired(S,G) == true + * AND ( DirectlyConnected(S) == true + * OR RPF_interface(S) != RPF_interface(RP(G)) + * OR inherited_olist(S,G,rpt) == NULL + * OR ( ( RPF'(S,G) == RPF'(*,G) ) AND + * ( RPF'(S,G) != NULL ) ) + * OR ( I_Am_Assert_Loser(S,G,iif) ) { + * Set SPTbit(S,G) to true + * } + * } + */ +void pim_upstream_set_sptbit(struct pim_upstream *up, + struct interface *incoming) +{ + struct pim_upstream *starup = up->parent; + + // iif == RPF_interfvace(S) + if (up->rpf.source_nexthop.interface != incoming) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Incoming Interface: %s is different than RPF_interface(S) %s", + __func__, incoming->name, + up->rpf.source_nexthop.interface->name); + return; + } + + // AND JoinDesired(S,G) == true + if (!pim_upstream_evaluate_join_desired(up->channel_oil->pim, up)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: %s Join is not Desired", __func__, + up->sg_str); + return; + } + + // DirectlyConnected(S) == true + if (pim_if_connected_to_source(up->rpf.source_nexthop.interface, + up->sg.src)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: %s is directly connected to the source", + __func__, up->sg_str); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + return; + } + + // OR RPF_interface(S) != RPF_interface(RP(G)) + if (!starup + || up->rpf.source_nexthop + .interface != starup->rpf.source_nexthop.interface) { + struct pim_upstream *starup = up->parent; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: %s RPF_interface(S) != RPF_interface(RP(G))", + __func__, up->sg_str); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + + pim_jp_agg_single_upstream_send(&starup->rpf, starup, true); + return; + } + + // OR inherited_olist(S,G,rpt) == NULL + if (pim_upstream_is_sg_rpt(up) + && pim_upstream_empty_inherited_olist(up)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: %s OR inherited_olist(S,G,rpt) == NULL", + __func__, up->sg_str); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + return; + } + + // OR ( ( RPF'(S,G) == RPF'(*,G) ) AND + // ( RPF'(S,G) != NULL ) ) + if (up->parent && pim_rpf_is_same(&up->rpf, &up->parent->rpf)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: %s RPF'(S,G) is the same as RPF'(*,G)", + __func__, up->sg_str); + up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE; + return; + } + + return; +} + +const char *pim_upstream_state2str(enum pim_upstream_state join_state) +{ + switch (join_state) { + case PIM_UPSTREAM_NOTJOINED: + return "NotJoined"; + case PIM_UPSTREAM_JOINED: + return "Joined"; + } + return "Unknown"; +} + +const char *pim_reg_state2str(enum pim_reg_state reg_state, char *state_str, + size_t state_str_len) +{ + switch (reg_state) { + case PIM_REG_NOINFO: + strlcpy(state_str, "RegNoInfo", state_str_len); + break; + case PIM_REG_JOIN: + strlcpy(state_str, "RegJoined", state_str_len); + break; + case PIM_REG_JOIN_PENDING: + strlcpy(state_str, "RegJoinPend", state_str_len); + break; + case PIM_REG_PRUNE: + strlcpy(state_str, "RegPrune", state_str_len); + break; + } + return state_str; +} + +static void pim_upstream_start_register_stop_timer(struct pim_upstream *up); + +static void pim_upstream_register_stop_timer(struct event *t) +{ + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct pim_upstream *up; + up = EVENT_ARG(t); + pim = up->channel_oil->pim; + + if (PIM_DEBUG_PIM_TRACE) { + char state_str[PIM_REG_STATE_STR_LEN]; + zlog_debug("%s: (S,G)=%s[%s] upstream register stop timer %s", + __func__, up->sg_str, pim->vrf->name, + pim_reg_state2str(up->reg_state, state_str, + sizeof(state_str))); + } + + switch (up->reg_state) { + case PIM_REG_JOIN_PENDING: + up->reg_state = PIM_REG_JOIN; + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_PIM, + __func__); + pim_vxlan_update_sg_reg_state(pim, up, true /*reg_join*/); + break; + case PIM_REG_JOIN: + break; + case PIM_REG_PRUNE: + /* This is equalent to Couldreg -> False */ + if (!up->rpf.source_nexthop.interface) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", + __func__, up->sg_str); + up->reg_state = PIM_REG_NOINFO; + PIM_UPSTREAM_FLAG_UNSET_FHR(up->flags); + return; + } + + pim_ifp = up->rpf.source_nexthop.interface->info; + if (!pim_ifp) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Interface: %s is not configured for pim", + __func__, + up->rpf.source_nexthop.interface->name); + return; + } + up->reg_state = PIM_REG_JOIN_PENDING; + pim_upstream_start_register_stop_timer(up); + + if (((up->channel_oil->cc.lastused / 100) + > pim->keep_alive_time) + && (I_am_RP(pim_ifp->pim, up->sg.grp))) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Stop sending the register, because I am the RP and we haven't seen a packet in a while", + __func__); + return; + } + pim_null_register_send(up); + break; + case PIM_REG_NOINFO: + break; + } +} + +static void pim_upstream_start_register_stop_timer(struct pim_upstream *up) +{ + uint32_t time; + + EVENT_OFF(up->t_rs_timer); + + time = router->register_probe_time; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: (S,G)=%s Starting upstream register stop timer %d", + __func__, up->sg_str, time); + event_add_timer(router->master, pim_upstream_register_stop_timer, up, + time, &up->t_rs_timer); +} + +static void pim_upstream_register_probe_timer(struct event *t) +{ + struct pim_upstream *up = EVENT_ARG(t); + + if (!up->rpf.source_nexthop.interface || + !up->rpf.source_nexthop.interface->info) { + if (PIM_DEBUG_PIM_REG) + zlog_debug("cannot send Null register for %pSG, no path to RP", + &up->sg); + } else + pim_null_register_send(up); + + pim_upstream_start_register_stop_timer(up); +} + +void pim_upstream_start_register_probe_timer(struct pim_upstream *up) +{ + uint32_t time; + + EVENT_OFF(up->t_rs_timer); + + uint32_t lower = (0.5 * router->register_suppress_time); + uint32_t upper = (1.5 * router->register_suppress_time); + time = lower + (frr_weak_random() % (upper - lower + 1)); + /* Make sure we don't wrap around */ + if (time >= router->register_probe_time) + time -= router->register_probe_time; + else + time = 0; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: (S,G)=%s Starting upstream register stop null probe timer %d", + __func__, up->sg_str, time); + + event_add_timer(router->master, pim_upstream_register_probe_timer, up, + time, &up->t_rs_timer); +} + +int pim_upstream_inherited_olist_decide(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct interface *ifp; + struct pim_ifchannel *ch, *starch; + struct pim_upstream *starup = up->parent; + int output_intf = 0; + + if (!up->rpf.source_nexthop.interface) + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp; + if (!ifp->info) + continue; + + ch = pim_ifchannel_find(ifp, &up->sg); + + if (starup) + starch = pim_ifchannel_find(ifp, &starup->sg); + else + starch = NULL; + + if (!ch && !starch) + continue; + + pim_ifp = ifp->info; + if (PIM_I_am_DualActive(pim_ifp) + && PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(up->flags) + && (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->flags) + || !PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(up->flags))) + continue; + if (pim_upstream_evaluate_join_desired_interface(up, ch, + starch)) { + int flag = 0; + + if (!ch) + flag = PIM_OIF_FLAG_PROTO_STAR; + else { + if (PIM_IF_FLAG_TEST_PROTO_IGMP(ch->flags)) + flag = PIM_OIF_FLAG_PROTO_GM; + if (PIM_IF_FLAG_TEST_PROTO_PIM(ch->flags)) + flag |= PIM_OIF_FLAG_PROTO_PIM; + if (starch) + flag |= PIM_OIF_FLAG_PROTO_STAR; + } + + pim_channel_add_oif(up->channel_oil, ifp, flag, + __func__); + output_intf++; + } + } + + return output_intf; +} + +/* + * For a given upstream, determine the inherited_olist + * and apply it. + * + * inherited_olist(S,G,rpt) = + * ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) ) + * (+) ( pim_include(*,G) (-) pim_exclude(S,G)) + * (-) ( lost_assert(*,G) (+) lost_assert(S,G,rpt) ) + * + * inherited_olist(S,G) = + * inherited_olist(S,G,rpt) (+) + * joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) + * + * return 1 if there are any output interfaces + * return 0 if there are not any output interfaces + */ +int pim_upstream_inherited_olist(struct pim_instance *pim, + struct pim_upstream *up) +{ + int output_intf = pim_upstream_inherited_olist_decide(pim, up); + + /* + * If we have output_intf switch state to Join and work like normal + * If we don't have an output_intf that means we are probably a + * switch on a stick so turn on forwarding to just accept the + * incoming packets so we don't bother the other stuff! + */ + pim_upstream_update_join_desired(pim, up); + + if (!output_intf) + forward_on(up); + + return output_intf; +} + +int pim_upstream_empty_inherited_olist(struct pim_upstream *up) +{ + return pim_channel_oil_empty(up->channel_oil); +} + +/* + * When we have a new neighbor, + * find upstreams that don't have their rpf_addr + * set and see if the new neighbor allows + * the join to be sent + */ +void pim_upstream_find_new_rpf(struct pim_instance *pim) +{ + struct pim_upstream *up; + struct pim_rpf old; + enum pim_rpf_result rpf_result; + + /* + * Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr + */ + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (pim_addr_is_any(up->upstream_addr)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: RP not configured for Upstream %s", + __func__, up->sg_str); + continue; + } + + if (pim_rpf_addr_is_inaddr_any(&up->rpf)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: Upstream %s without a path to send join, checking", + __func__, up->sg_str); + old.source_nexthop.interface = + up->rpf.source_nexthop.interface; + rpf_result = pim_rpf_update(pim, up, &old, __func__); + if (rpf_result == PIM_RPF_CHANGED || + (rpf_result == PIM_RPF_FAILURE && + old.source_nexthop.interface)) + pim_zebra_upstream_rpf_changed(pim, up, &old); + /* update kernel multicast forwarding cache (MFC) */ + pim_upstream_mroute_iif_update(up->channel_oil, + __func__); + } + } + pim_zebra_update_all_interfaces(pim); +} + +unsigned int pim_upstream_hash_key(const void *arg) +{ + const struct pim_upstream *up = arg; + + return pim_sgaddr_hash(up->sg, 0); +} + +void pim_upstream_terminate(struct pim_instance *pim) +{ + struct pim_upstream *up; + + while ((up = rb_pim_upstream_first(&pim->upstream_head))) { + if (pim_upstream_del(pim, up, __func__)) + pim_upstream_timers_stop(up); + } + + rb_pim_upstream_fini(&pim->upstream_head); + + if (pim->upstream_sg_wheel) + wheel_delete(pim->upstream_sg_wheel); + pim->upstream_sg_wheel = NULL; +} +bool pim_sg_is_reevaluate_oil_req(struct pim_instance *pim, + struct pim_upstream *up) +{ + struct pim_interface *pim_ifp = NULL; + + /* + * Attempt to retrieve the PIM interface information if the RPF + * interface is present + */ + if (up->rpf.source_nexthop.interface) { + pim_ifp = up->rpf.source_nexthop.interface->info; + } else { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: up %s RPF is not present", __func__, + up->sg_str); + } + } + + /* + * Determine if a reevaluation of the outgoing interface list (OIL) is + * required. This may be necessary in scenarios such as MSDP where the + * RP role for a group changes from secondary to primary. In such cases, + * SGRpt may receive a prune, resulting in an S,G entry with a NULL OIL. + * The S,G upstream should then inherit the OIL from *,G, which is + * particularly important for VXLAN setups. + */ + if (up->channel_oil->oil_inherited_rescan || + (pim_ifp && I_am_RP(pim_ifp->pim, up->sg.grp)) || + pim_upstream_empty_inherited_olist(up)) { + return true; + } + + return false; +} + +bool pim_upstream_equal(const void *arg1, const void *arg2) +{ + const struct pim_upstream *up1 = (const struct pim_upstream *)arg1; + const struct pim_upstream *up2 = (const struct pim_upstream *)arg2; + + return !pim_sgaddr_cmp(up1->sg, up2->sg); +} + +/* rfc4601:section-4.2:"Data Packet Forwarding Rules" defines + * the cases where kat has to be restarted on rxing traffic - + * + * if( DirectlyConnected(S) == true AND iif == RPF_interface(S) ) { + * set KeepaliveTimer(S,G) to Keepalive_Period + * # Note: a register state transition or UpstreamJPState(S,G) + * # transition may happen as a result of restarting + * # KeepaliveTimer, and must be dealt with here. + * } + * if( iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined AND + * inherited_olist(S,G) != NULL ) { + * set KeepaliveTimer(S,G) to Keepalive_Period + * } + */ +static bool pim_upstream_kat_start_ok(struct pim_upstream *up) +{ + struct channel_oil *c_oil = up->channel_oil; + struct interface *ifp = up->rpf.source_nexthop.interface; + struct pim_interface *pim_ifp; + struct pim_instance *pim = up->channel_oil->pim; + + /* "iif == RPF_interface(S)" check is not easy to do as the info + * we get from the kernel/ASIC is really a "lookup/key hit". + * So we will do an approximate check here to avoid starting KAT + * because of (S,G,rpt) forwarding on a non-LHR. + */ + if (!ifp) + return false; + + pim_ifp = ifp->info; + if (pim_ifp->mroute_vif_index != *oil_incoming_vif(c_oil)) + return false; + + if (pim_if_connected_to_source(up->rpf.source_nexthop.interface, + up->sg.src)) { + return true; + } + + if ((up->join_state == PIM_UPSTREAM_JOINED) + && !pim_upstream_empty_inherited_olist(up)) { + if (I_am_RP(pim, up->sg.grp)) + return true; + } + + return false; +} + +static bool pim_upstream_sg_running_proc(struct pim_upstream *up) +{ + bool rv = false; + struct pim_instance *pim = up->pim; + + if (!up->channel_oil->installed) + return rv; + + pim_mroute_update_counters(up->channel_oil); + + // Have we seen packets? + if ((up->channel_oil->cc.oldpktcnt >= up->channel_oil->cc.pktcnt) + && (up->channel_oil->cc.lastused / 100 > 30)) { + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug( + "%s[%s]: %s old packet count is equal or lastused is greater than 30, (%ld,%ld,%lld)", + __func__, up->sg_str, pim->vrf->name, + up->channel_oil->cc.oldpktcnt, + up->channel_oil->cc.pktcnt, + up->channel_oil->cc.lastused / 100); + } + return rv; + } + + if (pim_upstream_kat_start_ok(up)) { + /* Add a source reference to the stream if + * one doesn't already exist */ + if (!PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "source reference created on kat restart %s[%s]", + up->sg_str, pim->vrf->name); + + pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM, + __func__); + PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags); + pim_upstream_fhr_kat_start(up); + } + pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); + rv = true; + } else if (PIM_UPSTREAM_FLAG_TEST_SRC_LHR(up->flags)) { + pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time); + rv = true; + } + + if ((up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE) && + (up->rpf.source_nexthop.interface)) { + pim_upstream_set_sptbit(up, up->rpf.source_nexthop.interface); + pim_upstream_update_could_assert(up); + } + + return rv; +} + +/* + * Code to check and see if we've received packets on a S,G mroute + * and if so to set the SPT bit appropriately + */ +static void pim_upstream_sg_running(void *arg) +{ + struct pim_upstream *up = (struct pim_upstream *)arg; + struct pim_instance *pim = up->channel_oil->pim; + + // No packet can have arrived here if this is the case + if (!up->channel_oil->installed) { + if (PIM_DEBUG_TRACE) + zlog_debug("%s: %s[%s] is not installed in mroute", + __func__, up->sg_str, pim->vrf->name); + return; + } + + /* + * This is a bit of a hack + * We've noted that we should rescan but + * we've missed the window for doing so in + * pim_zebra.c for some reason. I am + * only doing this at this point in time + * to get us up and working for the moment + */ + if (pim_sg_is_reevaluate_oil_req(pim, up)) { + if (PIM_DEBUG_TRACE) + zlog_debug( + "%s: Handling unscanned inherited_olist for %s[%s]", + __func__, up->sg_str, pim->vrf->name); + pim_upstream_inherited_olist_decide(pim, up); + up->channel_oil->oil_inherited_rescan = 0; + } + + pim_upstream_sg_running_proc(up); +} + +void pim_upstream_add_lhr_star_pimreg(struct pim_instance *pim) +{ + struct pim_upstream *up; + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!pim_addr_is_any(up->sg.src)) + continue; + + if (!PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR(up->flags)) + continue; + + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + } +} + +void pim_upstream_spt_prefix_list_update(struct pim_instance *pim, + struct prefix_list *pl) +{ + const char *pname = prefix_list_name(pl); + + if (pim->spt.plist && strcmp(pim->spt.plist, pname) == 0) { + pim_upstream_remove_lhr_star_pimreg(pim, pname); + } +} + +/* + * nlist -> The new prefix list + * + * Per Group Application of pimreg to the OIL + * If the prefix list tells us DENY then + * we need to Switchover to SPT immediate + * so add the pimreg. + * If the prefix list tells us to ACCEPT than + * we need to Never do the SPT so remove + * the interface + * + */ +void pim_upstream_remove_lhr_star_pimreg(struct pim_instance *pim, + const char *nlist) +{ + struct pim_upstream *up; + struct prefix_list *np; + struct prefix g; + enum prefix_list_type apply_new; + + np = prefix_list_lookup(PIM_AFI, nlist); + + frr_each (rb_pim_upstream, &pim->upstream_head, up) { + if (!pim_addr_is_any(up->sg.src)) + continue; + + if (!PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR(up->flags)) + continue; + + if (!nlist) { + pim_channel_del_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + continue; + } + pim_addr_to_prefix(&g, up->sg.grp); + apply_new = prefix_list_apply_ext(np, NULL, &g, true); + if (apply_new == PREFIX_DENY) + pim_channel_add_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + else + pim_channel_del_oif(up->channel_oil, pim->regiface, + PIM_OIF_FLAG_PROTO_GM, __func__); + } +} + +void pim_upstream_init(struct pim_instance *pim) +{ + char name[64]; + + snprintf(name, sizeof(name), "PIM %s Timer Wheel", pim->vrf->name); + pim->upstream_sg_wheel = + wheel_init(router->master, 31000, 100, pim_upstream_hash_key, + pim_upstream_sg_running, name); + + rb_pim_upstream_init(&pim->upstream_head); +} diff --git a/pimd/pim_upstream.h b/pimd/pim_upstream.h new file mode 100644 index 0000000..8b4a35b --- /dev/null +++ b/pimd/pim_upstream.h @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_UPSTREAM_H +#define PIM_UPSTREAM_H + +#include +#include +#include "plist.h" + +#include "pim_rpf.h" +#include "pim_str.h" +#include "pim_ifchannel.h" + +#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED (1 << 0) +#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED (1 << 1) +#define PIM_UPSTREAM_FLAG_MASK_FHR (1 << 2) +#define PIM_UPSTREAM_FLAG_MASK_SRC_IGMP (1 << 3) +#define PIM_UPSTREAM_FLAG_MASK_SRC_PIM (1 << 4) +#define PIM_UPSTREAM_FLAG_MASK_SRC_STREAM (1 << 5) +#define PIM_UPSTREAM_FLAG_MASK_SRC_MSDP (1 << 6) +#define PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE (1 << 7) +#define PIM_UPSTREAM_FLAG_MASK_SRC_LHR (1 << 8) +/* In the case of pim vxlan we prime the pump by registering the + * vxlan source and keeping the SPT (FHR-RP) alive by sending periodic + * NULL registers. So we need to prevent KAT expiry because of the + * lack of BUM traffic. + */ +#define PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY (1 << 9) +/* for pim vxlan we need to pin the IIF to lo or MLAG-ISL on the + * originating VTEP. This flag allows that by setting IIF to the + * value specified and preventing next-hop-tracking on the entry + */ +#define PIM_UPSTREAM_FLAG_MASK_STATIC_IIF (1 << 10) +#define PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL (1 << 11) +/* Disable pimreg encasulation for a flow */ +#define PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA (1 << 12) +/* For some MDTs we need to register the router as a source even + * if the not DR or directly connected on the IIF. This is typically + * needed on a VxLAN-AA (MLAG) setup. + */ +#define PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG (1 << 13) +/* VxLAN origination mroute - SG was registered by EVPN where S is the + * local VTEP IP and G is the BUM multicast group address + */ +#define PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG (1 << 14) +/* VxLAN termination mroute - *G entry where G is the BUM multicast group + * address + */ +#define PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM (1 << 15) +/* MLAG mroute - synced to the MLAG peer and subject to DF (designated + * forwarder) election + */ +#define PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN (1 << 16) +/* MLAG mroute that lost the DF election with peer and is installed in + * a dormant state i.e. MLAG OIFs are removed from the MFC. + * In most cases the OIL is empty (but not not always) simply + * blackholing the traffic pulled down to the LHR. + */ +#define PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF (1 << 17) +/* MLAG mroute rxed from the peer MLAG switch */ +#define PIM_UPSTREAM_FLAG_MASK_MLAG_PEER (1 << 18) +/* + * We are creating a non-joined upstream data structure + * for this S,G as that we want to have a channel oil + * associated with an upstream + */ +#define PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE (1 << 19) +/* By default as SG entry will use the SPT for forwarding traffic + * unless it was setup as a result of a Prune(S,G,rpt) from a + * downstream router and has JoinDesired(S,G) as False. + * This flag is only relevant for (S,G) entries. + */ +#define PIM_UPSTREAM_FLAG_MASK_USE_RPT (1 << 20) +/* PIM Syncs upstream entries to peer Nodes via MLAG in 2 cases. + * one is to support plain PIM Redundancy and another one is to support + * PIM REdundancy. + */ +#define PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE (1 << 21) + + +#define PIM_UPSTREAM_FLAG_ALL 0xFFFFFFFF + +#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) +#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) +#define PIM_UPSTREAM_FLAG_TEST_FHR(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_FHR) +#define PIM_UPSTREAM_FLAG_TEST_SRC_IGMP(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) +#define PIM_UPSTREAM_FLAG_TEST_SRC_PIM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_PIM) +#define PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) +#define PIM_UPSTREAM_FLAG_TEST_SRC_MSDP(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) +#define PIM_UPSTREAM_FLAG_TEST_SEND_SG_RPT_PRUNE(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) +#define PIM_UPSTREAM_FLAG_TEST_SRC_LHR(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_LHR) +#define PIM_UPSTREAM_FLAG_TEST_DISABLE_KAT_EXPIRY(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) +#define PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) +#define PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) +#define PIM_UPSTREAM_FLAG_TEST_NO_PIMREG_DATA(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) +#define PIM_UPSTREAM_FLAG_TEST_FORCE_PIMREG(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) +#define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) +#define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_TERM(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) +#define PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN(flags) ((flags) & (PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG | PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM)) +#define PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) +#define PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) +#define PIM_UPSTREAM_FLAG_TEST_MLAG_PEER(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_MLAG_PEER) +#define PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(flags) ((flags) &PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE) +#define PIM_UPSTREAM_FLAG_TEST_USE_RPT(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_USE_RPT) +#define PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR(flags) ((flags) & (PIM_UPSTREAM_FLAG_MASK_SRC_IGMP | PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM)) +#define PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(flags) ((flags)&PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE) + +#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) +#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) +#define PIM_UPSTREAM_FLAG_SET_FHR(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_FHR) +#define PIM_UPSTREAM_FLAG_SET_SRC_IGMP(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) +#define PIM_UPSTREAM_FLAG_SET_SRC_PIM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_PIM) +#define PIM_UPSTREAM_FLAG_SET_SRC_STREAM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) +#define PIM_UPSTREAM_FLAG_SET_SRC_MSDP(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) +#define PIM_UPSTREAM_FLAG_SET_SEND_SG_RPT_PRUNE(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) +#define PIM_UPSTREAM_FLAG_SET_SRC_LHR(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_LHR) +#define PIM_UPSTREAM_FLAG_SET_DISABLE_KAT_EXPIRY(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) +#define PIM_UPSTREAM_FLAG_SET_STATIC_IIF(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) +#define PIM_UPSTREAM_FLAG_SET_ALLOW_IIF_IN_OIL(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) +#define PIM_UPSTREAM_FLAG_SET_NO_PIMREG_DATA(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) +#define PIM_UPSTREAM_FLAG_SET_FORCE_PIMREG(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) +#define PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_ORIG(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) +#define PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) +#define PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) +#define PIM_UPSTREAM_FLAG_SET_MLAG_NON_DF(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) +#define PIM_UPSTREAM_FLAG_SET_MLAG_PEER(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_PEER) +#define PIM_UPSTREAM_FLAG_SET_USE_RPT(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_USE_RPT) +#define PIM_UPSTREAM_FLAG_SET_MLAG_INTERFACE(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE) + +#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED) +#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED) +#define PIM_UPSTREAM_FLAG_UNSET_FHR(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_FHR) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_IGMP(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_IGMP) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_PIM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_PIM) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_STREAM) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_MSDP(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_MSDP) +#define PIM_UPSTREAM_FLAG_UNSET_SEND_SG_RPT_PRUNE(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SEND_SG_RPT_PRUNE) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_LHR(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_LHR) +#define PIM_UPSTREAM_FLAG_UNSET_DISABLE_KAT_EXPIRY(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY) +#define PIM_UPSTREAM_FLAG_UNSET_STATIC_IIF(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_STATIC_IIF) +#define PIM_UPSTREAM_FLAG_UNSET_ALLOW_IIF_IN_OIL(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL) +#define PIM_UPSTREAM_FLAG_UNSET_NO_PIMREG_DATA(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA) +#define PIM_UPSTREAM_FLAG_UNSET_FORCE_PIMREG(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_VXLAN_ORIG(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_VXLAN_TERM(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) +#define PIM_UPSTREAM_FLAG_UNSET_MLAG_VXLAN(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN) +#define PIM_UPSTREAM_FLAG_UNSET_MLAG_NON_DF(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_NON_DF) +#define PIM_UPSTREAM_FLAG_UNSET_MLAG_PEER(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_PEER) +#define PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE) +#define PIM_UPSTREAM_FLAG_UNSET_USE_RPT(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_USE_RPT) +#define PIM_UPSTREAM_FLAG_UNSET_MLAG_INTERFACE(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE) + +/* The RPF cost is incremented by 10 if the RPF interface is the peerlink-rif. + * This is used to force the MLAG switch with the lowest cost to the RPF + * to become the MLAG DF. + */ +#define PIM_UPSTREAM_MLAG_PEERLINK_PLUS_METRIC 10 + +enum pim_upstream_state { + PIM_UPSTREAM_NOTJOINED, + PIM_UPSTREAM_JOINED, +}; + +enum pim_reg_state { + PIM_REG_NOINFO, + PIM_REG_JOIN, + PIM_REG_JOIN_PENDING, + PIM_REG_PRUNE, +}; + +enum pim_upstream_sptbit { + PIM_UPSTREAM_SPTBIT_FALSE, + PIM_UPSTREAM_SPTBIT_TRUE +}; + +struct pim_up_mlag { + /* MRIB.metric(S) from the peer switch. This is used for DF election + * and switch with the lowest cost wins. + */ + uint32_t peer_mrib_metric; +}; + +PREDECL_RBTREE_UNIQ(rb_pim_upstream); +/* + Upstream (S,G) channel in Joined state + (S,G) in the "Not Joined" state is not represented + See RFC 4601: 4.5.7. Sending (S,G) Join/Prune Message + + upstream_addr : Who we are talking to. + For (*, G), upstream_addr is RP address or INADDR_ANY(if RP not configured) + For (S, G), upstream_addr is source address + + rpf: contains the nexthop information to whom we are talking to. + + join_state: JOINED/NOTJOINED + + In the case when FRR receives IGMP/PIM (*, G) join for group G and RP is not + configured, then create a pim_upstream with the below information. + pim_upstream->upstream address: INADDR_ANY + pim_upstream->rpf: Unknown + pim_upstream->state: NOTJOINED + + When a new RP gets configured for G, find the corresponding pim upstream (*,G) + entries and update the upstream address as new RP address if it the better one + for the group G. + + When RP becomes reachable, populate the nexthop information in + pim_upstream->rpf and update the state to JOINED. + +*/ +struct pim_upstream { + struct pim_instance *pim; + struct rb_pim_upstream_item upstream_rb; + struct pim_upstream *parent; + pim_addr upstream_addr; /* Who we are talking to */ + pim_addr upstream_register; /*Who we received a register from*/ + pim_sgaddr sg; /* (S,G) group key */ + char sg_str[PIM_SG_LEN]; + uint32_t flags; + struct channel_oil *channel_oil; + struct list *sources; + struct list *ifchannels; + /* Counter for Dual active ifchannels*/ + uint32_t dualactive_ifchannel_count; + + enum pim_upstream_state join_state; + enum pim_reg_state reg_state; + enum pim_upstream_sptbit sptbit; + + int ref_count; + + struct pim_rpf rpf; + + struct pim_up_mlag mlag; + + struct event *t_join_timer; + + /* + * RST(S,G) + */ + struct event *t_rs_timer; +#define PIM_REGISTER_SUPPRESSION_PERIOD (60) +#define PIM_REGISTER_PROBE_PERIOD (5) + + /* + * KAT(S,G) + */ + struct event *t_ka_timer; +#define PIM_KEEPALIVE_PERIOD (210) +#define PIM_RP_KEEPALIVE_PERIOD \ + (3 * router->register_suppress_time + router->register_probe_time) + + /* on the RP we restart a timer to indicate if registers are being rxed + * for + * SG. This is needed by MSDP to determine its local SA cache */ + struct event *t_msdp_reg_timer; +#define PIM_MSDP_REG_RXED_PERIOD (3 * (1.5 * router->register_suppress_time)) + + int64_t state_transition; /* Record current state uptime */ +}; + +static inline bool pim_upstream_is_kat_running(struct pim_upstream *up) +{ + return (up->t_ka_timer != NULL); +} + +static inline bool pim_up_mlag_is_local(struct pim_upstream *up) +{ + /* XXX: extend this to also return true if the channel-oil has + * any AA devices + */ + return (up->flags & (PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN + | PIM_UPSTREAM_FLAG_MASK_MLAG_INTERFACE)); +} + +struct pim_upstream *pim_upstream_find(struct pim_instance *pim, + pim_sgaddr *sg); +struct pim_upstream *pim_upstream_find_or_add(pim_sgaddr *sg, + struct interface *ifp, int flags, + const char *name); +struct pim_upstream *pim_upstream_add(struct pim_instance *pim, pim_sgaddr *sg, + struct interface *ifp, int flags, + const char *name, + struct pim_ifchannel *ch); +void pim_upstream_ref(struct pim_upstream *up, + int flags, const char *name); +struct pim_upstream *pim_upstream_del(struct pim_instance *pim, + struct pim_upstream *up, + const char *name); + +bool pim_upstream_evaluate_join_desired(struct pim_instance *pim, + struct pim_upstream *up); +int pim_upstream_evaluate_join_desired_interface(struct pim_upstream *up, + struct pim_ifchannel *ch, + struct pim_ifchannel *starch); +int pim_upstream_eval_inherit_if(struct pim_upstream *up, + struct pim_ifchannel *ch, + struct pim_ifchannel *starch); +void pim_upstream_update_join_desired(struct pim_instance *pim, + struct pim_upstream *up); + +void pim_update_suppress_timers(uint32_t suppress_time); +void pim_upstream_join_suppress(struct pim_upstream *up, pim_addr rpf, + int holdtime); + +void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label, + struct pim_upstream *up); + +void pim_upstream_join_timer_restart(struct pim_upstream *up, + struct pim_rpf *old); +void pim_upstream_rpf_genid_changed(struct pim_instance *pim, + pim_addr neigh_addr); +void pim_upstream_rpf_interface_changed(struct pim_upstream *up, + struct interface *old_rpf_ifp); + +void pim_upstream_update_could_assert(struct pim_upstream *up); +void pim_upstream_update_my_assert_metric(struct pim_upstream *up); + +void pim_upstream_keep_alive_timer_start(struct pim_upstream *up, + uint32_t time); + +int pim_upstream_switch_to_spt_desired_on_rp(struct pim_instance *pim, + pim_sgaddr *sg); +#define SwitchToSptDesiredOnRp(pim, sg) pim_upstream_switch_to_spt_desired_on_rp (pim, sg) +int pim_upstream_is_sg_rpt(struct pim_upstream *up); + +void pim_upstream_set_sptbit(struct pim_upstream *up, + struct interface *incoming); + +void pim_upstream_start_register_probe_timer(struct pim_upstream *up); + +void pim_upstream_send_join(struct pim_upstream *up); + +void pim_upstream_switch(struct pim_instance *pim, struct pim_upstream *up, + enum pim_upstream_state new_state); + +const char *pim_upstream_state2str(enum pim_upstream_state join_state); +#define PIM_REG_STATE_STR_LEN 12 +const char *pim_reg_state2str(enum pim_reg_state state, char *state_str, + size_t state_str_len); + +int pim_upstream_inherited_olist_decide(struct pim_instance *pim, + struct pim_upstream *up); +int pim_upstream_inherited_olist(struct pim_instance *pim, + struct pim_upstream *up); +int pim_upstream_empty_inherited_olist(struct pim_upstream *up); + +void pim_upstream_find_new_rpf(struct pim_instance *pim); +void pim_upstream_msdp_reg_timer_start(struct pim_upstream *up); + +void pim_upstream_init(struct pim_instance *pim); +void pim_upstream_terminate(struct pim_instance *pim); + +void join_timer_start(struct pim_upstream *up); +int pim_upstream_compare(const struct pim_upstream *up1, + const struct pim_upstream *up2); +DECLARE_RBTREE_UNIQ(rb_pim_upstream, struct pim_upstream, upstream_rb, + pim_upstream_compare); + +void pim_upstream_register_reevaluate(struct pim_instance *pim); + +void pim_upstream_add_lhr_star_pimreg(struct pim_instance *pim); +void pim_upstream_remove_lhr_star_pimreg(struct pim_instance *pim, + const char *nlist); + +void pim_upstream_spt_prefix_list_update(struct pim_instance *pim, + struct prefix_list *pl); + +unsigned int pim_upstream_hash_key(const void *arg); +bool pim_upstream_equal(const void *arg1, const void *arg2); +struct pim_upstream *pim_upstream_keep_alive_timer_proc( + struct pim_upstream *up); +void pim_upstream_fill_static_iif(struct pim_upstream *up, + struct interface *incoming); +void pim_upstream_update_use_rpt(struct pim_upstream *up, + bool update_mroute); +uint32_t pim_up_mlag_local_cost(struct pim_upstream *up); +uint32_t pim_up_mlag_peer_cost(struct pim_upstream *up); +void pim_upstream_reeval_use_rpt(struct pim_instance *pim); +int pim_upstream_could_register(struct pim_upstream *up); +bool pim_sg_is_reevaluate_oil_req(struct pim_instance *pim, + struct pim_upstream *up); +#endif /* PIM_UPSTREAM_H */ diff --git a/pimd/pim_util.c b/pimd/pim_util.c new file mode 100644 index 0000000..657e84a --- /dev/null +++ b/pimd/pim_util.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "plist.h" + +#include "pim_util.h" + +/* + RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) + + If QQIC < 128, QQI = QQIC + If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3) + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |1| exp | mant | + +-+-+-+-+-+-+-+-+ + + Since exp=0..7 then (exp+3)=3..10, then QQI has + one of the following bit patterns: + + exp=0: QQI = 0000.0000.1MMM.M000 + exp=1: QQI = 0000.0001.MMMM.0000 + ... + exp=6: QQI = 001M.MMM0.0000.0000 + exp=7: QQI = 01MM.MM00.0000.0000 + --------- --------- + 0x4 0x0 0x0 0x0 +*/ +uint8_t igmp_msg_encode16to8(uint16_t value) +{ + uint8_t code; + + if (value < 128) { + code = value; + } else { + uint16_t mask = 0x4000; + uint8_t exp; + uint16_t mant; + for (exp = 7; exp > 0; --exp) { + if (mask & value) + break; + mask >>= 1; + } + mant = 0x000F & (value >> (exp + 3)); + code = ((uint8_t)1 << 7) | ((uint8_t)exp << 4) | (uint8_t)mant; + } + + return code; +} + +/* + RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) + + If QQIC < 128, QQI = QQIC + If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3) + + 0 1 2 3 4 5 6 7 + +-+-+-+-+-+-+-+-+ + |1| exp | mant | + +-+-+-+-+-+-+-+-+ +*/ +uint16_t igmp_msg_decode8to16(uint8_t code) +{ + uint16_t value; + + if (code < 128) { + value = code; + } else { + uint16_t mant = (code & 0x0F); + uint8_t exp = (code & 0x70) >> 4; + value = (mant | 0x10) << (exp + 3); + } + + return value; +} + +void pim_pkt_dump(const char *label, const uint8_t *buf, int size) +{ + zlog_debug("%s: pkt dump size=%d", label, size); + zlog_hexdump(buf, size); +} + +int pim_is_group_224_0_0_0_24(struct in_addr group_addr) +{ + static int first = 1; + static struct prefix group_224; + struct prefix group; + + if (first) { + if (!str2prefix("224.0.0.0/24", &group_224)) + return 0; + first = 0; + } + + group.family = AF_INET; + group.u.prefix4 = group_addr; + group.prefixlen = IPV4_MAX_BITLEN; + + return prefix_match(&group_224, &group); +} + +int pim_is_group_224_4(struct in_addr group_addr) +{ + static int first = 1; + static struct prefix group_all; + struct prefix group; + + if (first) { + if (!str2prefix("224.0.0.0/4", &group_all)) + return 0; + first = 0; + } + + group.family = AF_INET; + group.u.prefix4 = group_addr; + group.prefixlen = IPV4_MAX_BITLEN; + + return prefix_match(&group_all, &group); +} + +bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp) +{ + struct prefix grp_pfx; + struct prefix_list *pl; + + if (!pim_ifp->boundary_oil_plist) + return false; + + pim_addr_to_prefix(&grp_pfx, *grp); + + pl = prefix_list_lookup(PIM_AFI, pim_ifp->boundary_oil_plist); + return pl ? prefix_list_apply_ext(pl, NULL, &grp_pfx, true) == + PREFIX_DENY + : false; +} + + +/* This function returns all multicast group */ +int pim_get_all_mcast_group(struct prefix *prefix) +{ +#if PIM_IPV == 4 + if (!str2prefix("224.0.0.0/4", prefix)) + return 0; +#else + if (!str2prefix("FF00::0/8", prefix)) + return 0; +#endif + return 1; +} + +bool pim_addr_is_multicast(pim_addr addr) +{ +#if PIM_IPV == 4 + if (IN_MULTICAST(ntohl(addr.s_addr))) + return true; +#else + if (IN6_IS_ADDR_MULTICAST(&addr)) + return true; +#endif + return false; +} diff --git a/pimd/pim_util.h b/pimd/pim_util.h new file mode 100644 index 0000000..c882fe4 --- /dev/null +++ b/pimd/pim_util.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_UTIL_H +#define PIM_UTIL_H + +#include + +#include + +#include "checksum.h" +#include "pimd.h" +#include "pim_iface.h" + +uint8_t igmp_msg_encode16to8(uint16_t value); +uint16_t igmp_msg_decode8to16(uint8_t code); + +void pim_pkt_dump(const char *label, const uint8_t *buf, int size); + +int pim_is_group_224_0_0_0_24(struct in_addr group_addr); +int pim_is_group_224_4(struct in_addr group_addr); +bool pim_is_group_filtered(struct pim_interface *pim_ifp, pim_addr *grp); +int pim_get_all_mcast_group(struct prefix *prefix); +bool pim_addr_is_multicast(pim_addr addr); +#endif /* PIM_UTIL_H */ diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c new file mode 100644 index 0000000..0f6547e --- /dev/null +++ b/pimd/pim_vty.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" +#include "linklist.h" +#include "prefix.h" +#include "vty.h" +#include "vrf.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_vty.h" +#include "pim_iface.h" +#include "pim_str.h" +#include "pim_ssmpingd.h" +#include "pim_pim.h" +#include "pim_oil.h" +#include "pim_static.h" +#include "pim_rp.h" +#include "pim_msdp.h" +#include "pim_ssm.h" +#include "pim_bfd.h" +#include "pim_bsm.h" +#include "pim_vxlan.h" +#include "pim6_mld.h" + +int pim_debug_config_write(struct vty *vty) +{ + int writes = 0; + + if (PIM_DEBUG_MSDP_EVENTS) { + vty_out(vty, "debug msdp events\n"); + ++writes; + } + if (PIM_DEBUG_MSDP_PACKETS) { + vty_out(vty, "debug msdp packets\n"); + ++writes; + } + if (PIM_DEBUG_MSDP_INTERNAL) { + vty_out(vty, "debug msdp internal\n"); + ++writes; + } + if (PIM_DEBUG_GM_EVENTS) { + vty_out(vty, "debug " GM_AF_DBG " events\n"); + ++writes; + } + if (PIM_DEBUG_GM_PACKETS) { + vty_out(vty, "debug " GM_AF_DBG " packets\n"); + ++writes; + } + /* PIM_DEBUG_GM_TRACE catches _DETAIL too */ + if (router->debugs & PIM_MASK_GM_TRACE) { + vty_out(vty, "debug " GM_AF_DBG " trace\n"); + ++writes; + } + if (PIM_DEBUG_GM_TRACE_DETAIL) { + vty_out(vty, "debug " GM_AF_DBG " trace detail\n"); + ++writes; + } + + /* PIM_DEBUG_MROUTE catches _DETAIL too */ + if (router->debugs & PIM_MASK_MROUTE) { + vty_out(vty, "debug " PIM_MROUTE_DBG "\n"); + ++writes; + } + if (PIM_DEBUG_MROUTE_DETAIL) { + vty_out(vty, "debug " PIM_MROUTE_DBG " detail\n"); + ++writes; + } + + if (PIM_DEBUG_MTRACE) { + vty_out(vty, "debug mtrace\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_EVENTS) { + vty_out(vty, "debug " PIM_AF_DBG " events\n"); + ++writes; + } + if (PIM_DEBUG_PIM_PACKETS) { + vty_out(vty, "debug " PIM_AF_DBG " packets\n"); + ++writes; + } + if (PIM_DEBUG_PIM_PACKETDUMP_SEND) { + vty_out(vty, "debug " PIM_AF_DBG " packet-dump send\n"); + ++writes; + } + if (PIM_DEBUG_PIM_PACKETDUMP_RECV) { + vty_out(vty, "debug " PIM_AF_DBG " packet-dump receive\n"); + ++writes; + } + + /* PIM_DEBUG_PIM_TRACE catches _DETAIL too */ + if (router->debugs & PIM_MASK_PIM_TRACE) { + vty_out(vty, "debug " PIM_AF_DBG " trace\n"); + ++writes; + } + if (PIM_DEBUG_PIM_TRACE_DETAIL) { + vty_out(vty, "debug " PIM_AF_DBG " trace detail\n"); + ++writes; + } + + if (PIM_DEBUG_ZEBRA) { + vty_out(vty, "debug " PIM_AF_DBG " zebra\n"); + ++writes; + } + + if (PIM_DEBUG_MLAG) { + vty_out(vty, "debug pim mlag\n"); + ++writes; + } + + if (PIM_DEBUG_BSM) { + vty_out(vty, "debug " PIM_AF_DBG " bsm\n"); + ++writes; + } + + if (PIM_DEBUG_VXLAN) { + vty_out(vty, "debug " PIM_AF_DBG " vxlan\n"); + ++writes; + } + + if (PIM_DEBUG_SSMPINGD) { + vty_out(vty, "debug ssmpingd\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_HELLO) { + vty_out(vty, "debug " PIM_AF_DBG " packets hello\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_J_P) { + vty_out(vty, "debug " PIM_AF_DBG " packets joins\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_REG) { + vty_out(vty, "debug " PIM_AF_DBG " packets register\n"); + ++writes; + } + + if (PIM_DEBUG_STATIC) { + vty_out(vty, "debug pim static\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_NHT) { + vty_out(vty, "debug " PIM_AF_DBG " nht\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_NHT_RP) { + vty_out(vty, "debug pim nht rp\n"); + ++writes; + } + + if (PIM_DEBUG_PIM_NHT_DETAIL) { + vty_out(vty, "debug " PIM_AF_DBG " nht detail\n"); + ++writes; + } + + return writes; +} + +int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty) +{ + int writes = 0; + struct pim_ssm *ssm = pim->ssm_info; + char spaces[10]; + + if (pim->vrf->vrf_id == VRF_DEFAULT) + snprintf(spaces, sizeof(spaces), "%s", ""); + else + snprintf(spaces, sizeof(spaces), "%s", " "); + + writes += pim_msdp_peer_config_write(vty, pim, spaces); + writes += pim_msdp_config_write(pim, vty, spaces); + + if (!pim->send_v6_secondary) { + vty_out(vty, "%sno ip pim send-v6-secondary\n", spaces); + ++writes; + } + + writes += pim_rp_config_write(pim, vty, spaces); + + if (pim->vrf->vrf_id == VRF_DEFAULT) { + if (router->register_suppress_time + != PIM_REGISTER_SUPPRESSION_TIME_DEFAULT) { + vty_out(vty, "%s" PIM_AF_NAME " pim register-suppress-time %d\n", + spaces, router->register_suppress_time); + ++writes; + } + if (router->t_periodic != PIM_DEFAULT_T_PERIODIC) { + vty_out(vty, "%s" PIM_AF_NAME " pim join-prune-interval %d\n", + spaces, router->t_periodic); + ++writes; + } + + if (router->packet_process != PIM_DEFAULT_PACKET_PROCESS) { + vty_out(vty, "%s" PIM_AF_NAME " pim packets %d\n", spaces, + router->packet_process); + ++writes; + } + } + if (pim->keep_alive_time != PIM_KEEPALIVE_PERIOD) { + vty_out(vty, "%s" PIM_AF_NAME " pim keep-alive-timer %d\n", + spaces, pim->keep_alive_time); + ++writes; + } + if (pim->rp_keep_alive_time != (unsigned int)PIM_RP_KEEPALIVE_PERIOD) { + vty_out(vty, "%s" PIM_AF_NAME " pim rp keep-alive-timer %d\n", + spaces, pim->rp_keep_alive_time); + ++writes; + } + if (ssm->plist_name) { + vty_out(vty, "%sip pim ssm prefix-list %s\n", spaces, + ssm->plist_name); + ++writes; + } + if (pim->register_plist) { + vty_out(vty, "%sip pim register-accept-list %s\n", spaces, + pim->register_plist); + ++writes; + } + if (pim->spt.switchover == PIM_SPT_INFINITY) { + if (pim->spt.plist) + vty_out(vty, + "%s" PIM_AF_NAME " pim spt-switchover infinity-and-beyond prefix-list %s\n", + spaces, pim->spt.plist); + else + vty_out(vty, + "%s" PIM_AF_NAME " pim spt-switchover infinity-and-beyond\n", + spaces); + ++writes; + } + if (pim->ecmp_rebalance_enable) { + vty_out(vty, "%sip pim ecmp rebalance\n", spaces); + ++writes; + } else if (pim->ecmp_enable) { + vty_out(vty, "%sip pim ecmp\n", spaces); + ++writes; + } + + if (pim->gm_watermark_limit != 0) { +#if PIM_IPV == 4 + vty_out(vty, "%s" PIM_AF_NAME " igmp watermark-warn %u\n", + spaces, pim->gm_watermark_limit); +#else + vty_out(vty, "%s" PIM_AF_NAME " mld watermark-warn %u\n", + spaces, pim->gm_watermark_limit); +#endif + ++writes; + } + + if (pim->ssmpingd_list) { + struct listnode *node; + struct ssmpingd_sock *ss; + ++writes; + for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss)) { + vty_out(vty, "%s" PIM_AF_NAME " ssmpingd %pPA\n", + spaces, &ss->source_addr); + ++writes; + } + } + + if (pim->msdp.hold_time != PIM_MSDP_PEER_HOLD_TIME + || pim->msdp.keep_alive != PIM_MSDP_PEER_KA_TIME + || pim->msdp.connection_retry != PIM_MSDP_PEER_CONNECT_RETRY_TIME) { + vty_out(vty, "%sip msdp timers %u %u", spaces, + pim->msdp.hold_time, pim->msdp.keep_alive); + if (pim->msdp.connection_retry + != PIM_MSDP_PEER_CONNECT_RETRY_TIME) + vty_out(vty, " %u", pim->msdp.connection_retry); + vty_out(vty, "\n"); + } + + return writes; +} + +#if PIM_IPV == 4 +static int gm_config_write(struct vty *vty, int writes, + struct pim_interface *pim_ifp) +{ + /* IF ip igmp */ + if (pim_ifp->gm_enable) { + vty_out(vty, " ip igmp\n"); + ++writes; + } + + /* ip igmp version */ + if (pim_ifp->igmp_version != IGMP_DEFAULT_VERSION) { + vty_out(vty, " ip igmp version %d\n", pim_ifp->igmp_version); + ++writes; + } + + /* IF ip igmp query-max-response-time */ + if (pim_ifp->gm_query_max_response_time_dsec != + GM_QUERY_MAX_RESPONSE_TIME_DSEC) { + vty_out(vty, " ip igmp query-max-response-time %d\n", + pim_ifp->gm_query_max_response_time_dsec); + ++writes; + } + + /* IF ip igmp query-interval */ + if (pim_ifp->gm_default_query_interval != GM_GENERAL_QUERY_INTERVAL) { + vty_out(vty, " ip igmp query-interval %d\n", + pim_ifp->gm_default_query_interval); + ++writes; + } + + /* IF ip igmp last-member_query-count */ + if (pim_ifp->gm_last_member_query_count != + GM_DEFAULT_ROBUSTNESS_VARIABLE) { + vty_out(vty, " ip igmp last-member-query-count %d\n", + pim_ifp->gm_last_member_query_count); + ++writes; + } + + /* IF ip igmp last-member_query-interval */ + if (pim_ifp->gm_specific_query_max_response_time_dsec != + GM_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC) { + vty_out(vty, " ip igmp last-member-query-interval %d\n", + pim_ifp->gm_specific_query_max_response_time_dsec); + ++writes; + } + + /* IF ip igmp join */ + if (pim_ifp->gm_join_list) { + struct listnode *node; + struct gm_join *ij; + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_join_list, node, ij)) { + if (pim_addr_is_any(ij->source_addr)) + vty_out(vty, " ip igmp join %pPAs\n", + &ij->group_addr); + else + vty_out(vty, " ip igmp join %pPAs %pPAs\n", + &ij->group_addr, &ij->source_addr); + ++writes; + } + } + + return writes; +} +#else +static int gm_config_write(struct vty *vty, int writes, + struct pim_interface *pim_ifp) +{ + /* IF ipv6 mld */ + if (pim_ifp->gm_enable) { + vty_out(vty, " ipv6 mld\n"); + ++writes; + } + + if (pim_ifp->mld_version != MLD_DEFAULT_VERSION) + vty_out(vty, " ipv6 mld version %d\n", pim_ifp->mld_version); + + /* IF ipv6 mld query-max-response-time */ + if (pim_ifp->gm_query_max_response_time_dsec != + GM_QUERY_MAX_RESPONSE_TIME_DSEC) + vty_out(vty, " ipv6 mld query-max-response-time %d\n", + pim_ifp->gm_query_max_response_time_dsec); + + if (pim_ifp->gm_default_query_interval != GM_GENERAL_QUERY_INTERVAL) + vty_out(vty, " ipv6 mld query-interval %d\n", + pim_ifp->gm_default_query_interval); + + /* IF ipv6 mld last-member_query-count */ + if (pim_ifp->gm_last_member_query_count != + GM_DEFAULT_ROBUSTNESS_VARIABLE) + vty_out(vty, " ipv6 mld last-member-query-count %d\n", + pim_ifp->gm_last_member_query_count); + + /* IF ipv6 mld last-member_query-interval */ + if (pim_ifp->gm_specific_query_max_response_time_dsec != + GM_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC) + vty_out(vty, " ipv6 mld last-member-query-interval %d\n", + pim_ifp->gm_specific_query_max_response_time_dsec); + + /* IF ipv6 mld join */ + if (pim_ifp->gm_join_list) { + struct listnode *node; + struct gm_join *ij; + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_join_list, node, ij)) { + if (pim_addr_is_any(ij->source_addr)) + vty_out(vty, " ipv6 mld join %pPAs\n", + &ij->group_addr); + else + vty_out(vty, " ipv6 mld join %pPAs %pPAs\n", + &ij->group_addr, &ij->source_addr); + ++writes; + } + } + + return writes; +} +#endif + +int pim_config_write(struct vty *vty, int writes, struct interface *ifp, + struct pim_instance *pim) +{ + struct pim_interface *pim_ifp = ifp->info; + + if (pim_ifp->pim_enable) { + vty_out(vty, " " PIM_AF_NAME " pim\n"); + ++writes; + } + + /* IF ip pim drpriority */ + if (pim_ifp->pim_dr_priority != PIM_DEFAULT_DR_PRIORITY) { + vty_out(vty, " " PIM_AF_NAME " pim drpriority %u\n", + pim_ifp->pim_dr_priority); + ++writes; + } + + /* IF ip pim hello */ + if (pim_ifp->pim_hello_period != PIM_DEFAULT_HELLO_PERIOD) { + vty_out(vty, " " PIM_AF_NAME " pim hello %d", pim_ifp->pim_hello_period); + if (pim_ifp->pim_default_holdtime != -1) + vty_out(vty, " %d", pim_ifp->pim_default_holdtime); + vty_out(vty, "\n"); + ++writes; + } + + writes += gm_config_write(vty, writes, pim_ifp); + + /* update source */ + if (!pim_addr_is_any(pim_ifp->update_source)) { + vty_out(vty, " " PIM_AF_NAME " pim use-source %pPA\n", + &pim_ifp->update_source); + ++writes; + } + + if (pim_ifp->activeactive) + vty_out(vty, " " PIM_AF_NAME " pim active-active\n"); + + /* boundary */ + if (pim_ifp->boundary_oil_plist) { + vty_out(vty, " " PIM_AF_NAME " multicast boundary oil %s\n", + pim_ifp->boundary_oil_plist); + ++writes; + } + + if (pim_ifp->pim_passive_enable) { + vty_out(vty, " " PIM_AF_NAME " pim passive\n"); + ++writes; + } + + writes += pim_static_write_mroute(pim, vty, ifp); + pim_bsm_write_config(vty, ifp); + ++writes; + pim_bfd_write_config(vty, ifp); + ++writes; + + return writes; +} + +int pim_interface_config_write(struct vty *vty) +{ + struct pim_instance *pim; + struct interface *ifp; + struct vrf *vrf; + int writes = 0; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + if (!pim) + continue; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + /* pim is enabled internally/implicitly on the vxlan + * termination device ipmr-lo. skip displaying that + * config to avoid confusion + */ + if (pim_vxlan_is_term_dev_cfg(pim, ifp)) + continue; + + /* IF name */ + if_vty_config_start(vty, ifp); + + ++writes; + + if (ifp->desc) { + vty_out(vty, " description %s\n", ifp->desc); + ++writes; + } + + if (ifp->info) { + pim_config_write(vty, writes, ifp, pim); + } + if_vty_config_end(vty); + + ++writes; + } + } + + return writes; +} diff --git a/pimd/pim_vty.h b/pimd/pim_vty.h new file mode 100644 index 0000000..84155af --- /dev/null +++ b/pimd/pim_vty.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_VTY_H +#define PIM_VTY_H + +#include "vty.h" + +struct pim_instance; + +int pim_debug_config_write(struct vty *vty); +int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty); +int pim_interface_config_write(struct vty *vty); +int pim_config_write(struct vty *vty, int writes, struct interface *ifp, + struct pim_instance *pim); +#endif /* PIM_VTY_H */ diff --git a/pimd/pim_vxlan.c b/pimd/pim_vxlan.c new file mode 100644 index 0000000..f1f315c --- /dev/null +++ b/pimd/pim_vxlan.c @@ -0,0 +1,1261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PIM support for VxLAN BUM flooding + * + * Copyright (C) 2019 Cumulus Networks, Inc. + */ + +#include + +#include +#include +#include +#include +#include + +#include "pimd.h" +#include "pim_iface.h" +#include "pim_memory.h" +#include "pim_oil.h" +#include "pim_register.h" +#include "pim_str.h" +#include "pim_upstream.h" +#include "pim_ifchannel.h" +#include "pim_nht.h" +#include "pim_zebra.h" +#include "pim_vxlan.h" +#include "pim_mlag.h" + +/* pim-vxlan global info */ +struct pim_vxlan vxlan_info, *pim_vxlan_p = &vxlan_info; + +static void pim_vxlan_work_timer_setup(bool start); +static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim, + struct interface *ifp); + +#define PIM_VXLAN_STARTUP_NULL_REGISTERS 10 + +static void pim_vxlan_rp_send_null_register_startup(struct event *e) +{ + struct pim_vxlan_sg *vxlan_sg = EVENT_ARG(e); + + vxlan_sg->null_register_sent++; + + if (vxlan_sg->null_register_sent > PIM_VXLAN_STARTUP_NULL_REGISTERS) { + if (PIM_DEBUG_VXLAN) + zlog_debug("Null registering stopping for %s", + vxlan_sg->sg_str); + return; + } + + pim_null_register_send(vxlan_sg->up); + + if (PIM_DEBUG_VXLAN) + zlog_debug("Sent null register for %s", vxlan_sg->sg_str); + + event_add_timer(router->master, pim_vxlan_rp_send_null_register_startup, + vxlan_sg, PIM_VXLAN_WORK_TIME, &vxlan_sg->null_register); +} + +/* + * The rp info has gone from no path to having a + * path. Let's immediately send out the null pim register + * as that else we will be sitting for up to 60 seconds waiting + * for it too pop. Which is not cool. + */ +void pim_vxlan_rp_info_is_alive(struct pim_instance *pim, + struct pim_rpf *rpg_changed) +{ + struct listnode *listnode; + struct pim_vxlan_sg *vxlan_sg; + struct pim_rpf *rpg; + + /* + * No vxlan here, move along, nothing to see + */ + if (!vxlan_info.work_list) + return; + + for (listnode = vxlan_info.work_list->head; listnode; + listnode = listnode->next) { + vxlan_sg = listgetdata(listnode); + + rpg = RP(pim, vxlan_sg->up->sg.grp); + + /* + * If the rp is the same we should send + */ + if (rpg == rpg_changed) { + if (PIM_DEBUG_VXLAN) + zlog_debug("VXLAN RP info for %s alive sending", + vxlan_sg->sg_str); + vxlan_sg->null_register_sent = 0; + event_add_event(router->master, + pim_vxlan_rp_send_null_register_startup, + vxlan_sg, 0, &vxlan_sg->null_register); + } + } +} + +/*************************** vxlan work list ********************************** + * A work list is maintained for staggered generation of pim null register + * messages for vxlan SG entries that are in a reg_join state. + * + * A max of 500 NULL registers are generated at one shot. If paused reg + * generation continues on the next second and so on till all register + * messages have been sent out. And the process is restarted every 60s. + * + * purpose of this null register generation is to setup the SPT and maintain + * independent of the presence of overlay BUM traffic. + ****************************************************************************/ +static void pim_vxlan_do_reg_work(void) +{ + struct listnode *listnode; + int work_cnt = 0; + struct pim_vxlan_sg *vxlan_sg; + static int sec_count; + + ++sec_count; + + if (sec_count > PIM_VXLAN_NULL_REG_INTERVAL) { + sec_count = 0; + listnode = vxlan_info.next_work ? + vxlan_info.next_work : + vxlan_info.work_list->head; + if (PIM_DEBUG_VXLAN && listnode) + zlog_debug("vxlan SG work %s", + vxlan_info.next_work ? "continues" : "starts"); + } else { + listnode = vxlan_info.next_work; + } + + for (; listnode; listnode = listnode->next) { + vxlan_sg = (struct pim_vxlan_sg *)listnode->data; + + if (vxlan_sg->up && (vxlan_sg->up->reg_state == PIM_REG_JOIN)) { + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s periodic NULL register", + vxlan_sg->sg_str); + + /* + * If we are on the work queue *and* the rpf + * has been lost on the vxlan_sg->up let's + * make sure that we don't send it. + */ + if (vxlan_sg->up->rpf.source_nexthop.interface) { + pim_null_register_send(vxlan_sg->up); + ++work_cnt; + } + } + + if (work_cnt > vxlan_info.max_work_cnt) { + vxlan_info.next_work = listnode->next; + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %d work items proc and pause", + work_cnt); + return; + } + } + + if (work_cnt) { + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %d work items proc", work_cnt); + } + vxlan_info.next_work = NULL; +} + +/* Staggered work related info is initialized when the first work comes + * along + */ +static void pim_vxlan_init_work(void) +{ + if (vxlan_info.flags & PIM_VXLANF_WORK_INITED) + return; + + vxlan_info.max_work_cnt = PIM_VXLAN_WORK_MAX; + vxlan_info.flags |= PIM_VXLANF_WORK_INITED; + vxlan_info.work_list = list_new(); + pim_vxlan_work_timer_setup(true/* start */); +} + +static void pim_vxlan_add_work(struct pim_vxlan_sg *vxlan_sg) +{ + if (vxlan_sg->flags & PIM_VXLAN_SGF_DEL_IN_PROG) { + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s skip work list; del-in-prog", + vxlan_sg->sg_str); + return; + } + + pim_vxlan_init_work(); + + /* already a part of the work list */ + if (vxlan_sg->work_node) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s work list add", + vxlan_sg->sg_str); + vxlan_sg->work_node = listnode_add(vxlan_info.work_list, vxlan_sg); + /* XXX: adjust max_work_cnt if needed */ +} + +static void pim_vxlan_del_work(struct pim_vxlan_sg *vxlan_sg) +{ + if (!vxlan_sg->work_node) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s work list del", + vxlan_sg->sg_str); + + if (vxlan_sg->work_node == vxlan_info.next_work) + vxlan_info.next_work = vxlan_sg->work_node->next; + + list_delete_node(vxlan_info.work_list, vxlan_sg->work_node); + vxlan_sg->work_node = NULL; +} + +void pim_vxlan_update_sg_reg_state(struct pim_instance *pim, + struct pim_upstream *up, bool reg_join) +{ + struct pim_vxlan_sg *vxlan_sg; + + vxlan_sg = pim_vxlan_sg_find(pim, &up->sg); + if (!vxlan_sg) + return; + + /* add the vxlan sg entry to a work list for periodic reg joins. + * the entry will stay in the list as long as the register state is + * PIM_REG_JOIN + */ + if (reg_join) + pim_vxlan_add_work(vxlan_sg); + else { + /* + * Stop the event that is sending NULL Registers on startup + * there is no need to keep spamming it + */ + if (PIM_DEBUG_VXLAN) + zlog_debug("Received Register stop for %s", + vxlan_sg->sg_str); + + EVENT_OFF(vxlan_sg->null_register); + pim_vxlan_del_work(vxlan_sg); + } +} + +static void pim_vxlan_work_timer_cb(struct event *t) +{ + pim_vxlan_do_reg_work(); + pim_vxlan_work_timer_setup(true /* start */); +} + +/* global 1second timer used for periodic processing */ +static void pim_vxlan_work_timer_setup(bool start) +{ + EVENT_OFF(vxlan_info.work_timer); + if (start) + event_add_timer(router->master, pim_vxlan_work_timer_cb, NULL, + PIM_VXLAN_WORK_TIME, &vxlan_info.work_timer); +} + +/**************************** vxlan origination mroutes *********************** + * For every (local-vtep-ip, bum-mcast-grp) registered by evpn an origination + * mroute is setup by pimd. The purpose of this mroute is to forward vxlan + * encapsulated BUM (broadcast, unknown-unicast and unknown-multicast packets + * over the underlay.) + * + * Sample mroute (single VTEP): + * (27.0.0.7, 239.1.1.100) Iif: lo Oifs: uplink-1 + * + * Sample mroute (anycast VTEP): + * (36.0.0.9, 239.1.1.100) Iif: peerlink-3.4094\ + * Oifs: peerlink-3.4094 uplink-1 + ***************************************************************************/ +static void pim_vxlan_orig_mr_up_del(struct pim_vxlan_sg *vxlan_sg) +{ + struct pim_upstream *up = vxlan_sg->up; + + if (!up) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig mroute-up del", + vxlan_sg->sg_str); + + vxlan_sg->up = NULL; + + if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG) { + /* clear out all the vxlan properties */ + up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_ORIG | + PIM_UPSTREAM_FLAG_MASK_STATIC_IIF | + PIM_UPSTREAM_FLAG_MASK_DISABLE_KAT_EXPIRY | + PIM_UPSTREAM_FLAG_MASK_FORCE_PIMREG | + PIM_UPSTREAM_FLAG_MASK_NO_PIMREG_DATA | + PIM_UPSTREAM_FLAG_MASK_ALLOW_IIF_IN_OIL); + + /* We bring things to a grinding halt by force expirying + * the kat. Doing this will also remove the reference we + * created as a "vxlan" source and delete the upstream entry + * if there are no other references. + */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) { + EVENT_OFF(up->t_ka_timer); + up = pim_upstream_keep_alive_timer_proc(up); + } else { + /* this is really unexpected as we force vxlan + * origination mroutes active sources but just in + * case + */ + up = pim_upstream_del(vxlan_sg->pim, up, __func__); + } + /* if there are other references register the source + * for nht + */ + if (up) { + enum pim_rpf_result r; + + r = pim_rpf_update(vxlan_sg->pim, up, NULL, __func__); + if (r == PIM_RPF_FAILURE) { + if (PIM_DEBUG_VXLAN) + zlog_debug( + "vxlan SG %s rpf_update failure", + vxlan_sg->sg_str); + } + } + } +} + +static void pim_vxlan_orig_mr_up_iif_update(struct pim_vxlan_sg *vxlan_sg) +{ + /* update MFC with the new IIF */ + pim_upstream_fill_static_iif(vxlan_sg->up, vxlan_sg->iif); + pim_upstream_mroute_iif_update(vxlan_sg->up->channel_oil, __func__); + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig mroute-up updated with iif %s", + vxlan_sg->sg_str, + vxlan_sg->iif?vxlan_sg->iif->name:"-"); + +} + +/* For every VxLAN BUM multicast group we setup a SG-up that has the following + * "forced properties" - + * 1. Directly connected on a DR interface i.e. we must act as an FHR + * 2. We prime the pump i.e. no multicast data is needed to register this + * source with the FHR. To do that we send periodic null registers if + * the SG entry is in a register-join state. We also prevent expiry of + * KAT. + * 3. As this SG is setup without data there is no need to register encapsulate + * data traffic. This encapsulation is explicitly skipped for the following + * reasons - + * a) Many levels of encapsulation are needed creating MTU disc challenges. + * Overlay BUM is encapsulated in a vxlan/UDP/IP header and then + * encapsulated again in a pim-register header. + * b) On a vxlan-aa setup both switches rx a copy of each BUM packet. if + * they both reg encapsulated traffic the RP will accept the duplicates + * as there are no RPF checks for this encapsulated data. + * a), b) can be workarounded if needed, but there is really no need because + * of (2) i.e. the pump is primed without data. + */ +static void pim_vxlan_orig_mr_up_add(struct pim_vxlan_sg *vxlan_sg) +{ + struct pim_upstream *up; + struct pim_interface *term_ifp; + int flags = 0; + struct pim_instance *pim = vxlan_sg->pim; + + if (vxlan_sg->up) { + /* nothing to do */ + return; + } + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig mroute-up add with iif %s", + vxlan_sg->sg_str, + vxlan_sg->iif?vxlan_sg->iif->name:"-"); + + PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_ORIG(flags); + /* pin the IIF to lo or peerlink-subinterface and disable NHT */ + PIM_UPSTREAM_FLAG_SET_STATIC_IIF(flags); + /* Fake traffic by setting SRC_STREAM and starting KAT */ + /* We intentionally skip updating ref count for SRC_STREAM/FHR. + * Setting SRC_VXLAN should have already created a reference + * preventing the entry from being deleted + */ + PIM_UPSTREAM_FLAG_SET_FHR(flags); + PIM_UPSTREAM_FLAG_SET_SRC_STREAM(flags); + /* Force pimreg even if non-DR. This is needed on a MLAG setup for + * VxLAN AA + */ + PIM_UPSTREAM_FLAG_SET_FORCE_PIMREG(flags); + /* prevent KAT expiry. we want the MDT setup even if there is no BUM + * traffic + */ + PIM_UPSTREAM_FLAG_SET_DISABLE_KAT_EXPIRY(flags); + /* SPT for vxlan BUM groups is primed and maintained via NULL + * registers so there is no need to reg-encapsulate + * vxlan-encapsulated overlay data traffic + */ + PIM_UPSTREAM_FLAG_SET_NO_PIMREG_DATA(flags); + /* On a MLAG setup we force a copy to the MLAG peer while also + * accepting traffic from the peer. To do this we set peerlink-rif as + * the IIF and also add it to the OIL + */ + PIM_UPSTREAM_FLAG_SET_ALLOW_IIF_IN_OIL(flags); + + /* XXX: todo: defer pim_upstream add if pim is not enabled on the iif */ + up = pim_upstream_find(vxlan_sg->pim, &vxlan_sg->sg); + if (up) { + /* if the iif is set to something other than the vxlan_sg->iif + * we must dereg the old nexthop and force to new "static" + * iif + */ + if (!PIM_UPSTREAM_FLAG_TEST_STATIC_IIF(up->flags)) { + pim_delete_tracked_nexthop(vxlan_sg->pim, + up->upstream_addr, up, NULL); + } + /* We are acting FHR; clear out use_rpt setting if any */ + pim_upstream_update_use_rpt(up, false /*update_mroute*/); + pim_upstream_ref(up, flags, __func__); + vxlan_sg->up = up; + term_ifp = pim_vxlan_get_term_ifp(pim); + /* mute termination device on origination mroutes */ + if (term_ifp) + pim_channel_update_oif_mute(up->channel_oil, + term_ifp); + pim_vxlan_orig_mr_up_iif_update(vxlan_sg); + /* mute pimreg on origination mroutes */ + if (pim->regiface) + pim_channel_update_oif_mute(up->channel_oil, + pim->regiface->info); + } else { + up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg, + vxlan_sg->iif, flags, __func__, NULL); + vxlan_sg->up = up; + } + + if (!up) { + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig mroute-up add failed", + vxlan_sg->sg_str); + return; + } + + pim_upstream_keep_alive_timer_start(up, vxlan_sg->pim->keep_alive_time); + + /* register the source with the RP */ + switch (up->reg_state) { + + case PIM_REG_NOINFO: + pim_register_join(up); + pim_null_register_send(up); + break; + + case PIM_REG_JOIN: + /* if the pim upstream entry is already in reg-join state + * send null_register right away and add to the register + * worklist + */ + pim_null_register_send(up); + pim_vxlan_update_sg_reg_state(pim, up, true); + break; + + case PIM_REG_JOIN_PENDING: + case PIM_REG_PRUNE: + break; + } + + /* update the inherited OIL */ + pim_upstream_inherited_olist(vxlan_sg->pim, up); + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, __func__); +} + +static void pim_vxlan_orig_mr_oif_add(struct pim_vxlan_sg *vxlan_sg) +{ + if (!vxlan_sg->up || !vxlan_sg->orig_oif) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s oif %s add", + vxlan_sg->sg_str, vxlan_sg->orig_oif->name); + + vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED; + pim_channel_add_oif(vxlan_sg->up->channel_oil, + vxlan_sg->orig_oif, PIM_OIF_FLAG_PROTO_VXLAN, + __func__); +} + +static void pim_vxlan_orig_mr_oif_del(struct pim_vxlan_sg *vxlan_sg) +{ + struct interface *orig_oif; + + orig_oif = vxlan_sg->orig_oif; + vxlan_sg->orig_oif = NULL; + + if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s oif %s del", + vxlan_sg->sg_str, orig_oif->name); + + vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED; + pim_channel_del_oif(vxlan_sg->up->channel_oil, + orig_oif, PIM_OIF_FLAG_PROTO_VXLAN, __func__); +} + +static inline struct interface *pim_vxlan_orig_mr_oif_get( + struct pim_instance *pim) +{ + return (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) ? + pim->vxlan.peerlink_rif : NULL; +} + +/* Single VTEPs: IIF for the vxlan-origination-mroutes is lo or vrf-dev (if + * the mroute is in a non-default vrf). + * Anycast VTEPs: IIF is the MLAG ISL/peerlink. + */ +static inline struct interface *pim_vxlan_orig_mr_iif_get( + struct pim_instance *pim) +{ + return ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) && + pim->vxlan.peerlink_rif) ? + pim->vxlan.peerlink_rif : pim->vxlan.default_iif; +} + +static bool pim_vxlan_orig_mr_add_is_ok(struct pim_vxlan_sg *vxlan_sg) +{ + struct pim_interface *pim_ifp; + + vxlan_sg->iif = pim_vxlan_orig_mr_iif_get(vxlan_sg->pim); + if (!vxlan_sg->iif) + return false; + + pim_ifp = (struct pim_interface *)vxlan_sg->iif->info; + if (!pim_ifp || (pim_ifp->mroute_vif_index < 0)) + return false; + + return true; +} + +static void pim_vxlan_orig_mr_install(struct pim_vxlan_sg *vxlan_sg) +{ + pim_vxlan_orig_mr_up_add(vxlan_sg); + + vxlan_sg->orig_oif = pim_vxlan_orig_mr_oif_get(vxlan_sg->pim); + pim_vxlan_orig_mr_oif_add(vxlan_sg); +} + +static void pim_vxlan_orig_mr_add(struct pim_vxlan_sg *vxlan_sg) +{ + if (!pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig-mr add", vxlan_sg->sg_str); + + pim_vxlan_orig_mr_install(vxlan_sg); +} + +static void pim_vxlan_orig_mr_del(struct pim_vxlan_sg *vxlan_sg) +{ + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s orig-mr del", vxlan_sg->sg_str); + + pim_vxlan_orig_mr_oif_del(vxlan_sg); + pim_vxlan_orig_mr_up_del(vxlan_sg); +} + +static void pim_vxlan_orig_mr_iif_update(struct hash_bucket *bucket, void *arg) +{ + struct interface *ifp; + struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data; + struct interface *old_iif = vxlan_sg->iif; + + if (!pim_vxlan_is_orig_mroute(vxlan_sg)) + return; + + ifp = pim_vxlan_orig_mr_iif_get(vxlan_sg->pim); + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s iif changed from %s to %s", + vxlan_sg->sg_str, + old_iif ? old_iif->name : "-", + ifp ? ifp->name : "-"); + + if (pim_vxlan_orig_mr_add_is_ok(vxlan_sg)) { + if (vxlan_sg->up) { + /* upstream exists but iif changed */ + pim_vxlan_orig_mr_up_iif_update(vxlan_sg); + } else { + /* install mroute */ + pim_vxlan_orig_mr_install(vxlan_sg); + } + } else { + pim_vxlan_orig_mr_del(vxlan_sg); + } +} + +/**************************** vxlan termination mroutes *********************** + * For every bum-mcast-grp registered by evpn a *G termination + * mroute is setup by pimd. The purpose of this mroute is to pull down vxlan + * packets with the bum-mcast-grp dip from the underlay and terminate the + * tunnel. This is done by including the vxlan termination device (ipmr-lo) in + * its OIL. The vxlan de-capsulated packets are subject to subsequent overlay + * bridging. + * + * Sample mroute: + * (0.0.0.0, 239.1.1.100) Iif: uplink-1 Oifs: ipmr-lo, uplink-1 + *****************************************************************************/ +struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim) +{ + return pim->vxlan.term_if ? + (struct pim_interface *)pim->vxlan.term_if->info : NULL; +} + +static void pim_vxlan_term_mr_oif_add(struct pim_vxlan_sg *vxlan_sg) +{ + if (vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term-oif %s add", + vxlan_sg->sg_str, vxlan_sg->term_oif->name); + + if (pim_ifchannel_local_membership_add(vxlan_sg->term_oif, + &vxlan_sg->sg, true /*is_vxlan */)) { + vxlan_sg->flags |= PIM_VXLAN_SGF_OIF_INSTALLED; + /* update the inherited OIL */ + /* XXX - I don't see the inherited OIL updated when a local + * member is added. And that probably needs to be fixed. Till + * that happens we do a force update on the inherited OIL + * here. + */ + pim_upstream_inherited_olist(vxlan_sg->pim, vxlan_sg->up); + } else { + zlog_warn("vxlan SG %s term-oif %s add failed", + vxlan_sg->sg_str, vxlan_sg->term_oif->name); + } +} + +static void pim_vxlan_term_mr_oif_del(struct pim_vxlan_sg *vxlan_sg) +{ + if (!(vxlan_sg->flags & PIM_VXLAN_SGF_OIF_INSTALLED)) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s oif %s del", + vxlan_sg->sg_str, vxlan_sg->term_oif->name); + + vxlan_sg->flags &= ~PIM_VXLAN_SGF_OIF_INSTALLED; + pim_ifchannel_local_membership_del(vxlan_sg->term_oif, &vxlan_sg->sg); + /* update the inherited OIL */ + /* XXX - I don't see the inherited OIL updated when a local member + * is deleted. And that probably needs to be fixed. Till that happens + * we do a force update on the inherited OIL here. + */ + pim_upstream_inherited_olist(vxlan_sg->pim, vxlan_sg->up); +} + +static void pim_vxlan_update_sg_entry_mlag(struct pim_instance *pim, + struct pim_upstream *up, bool inherit) +{ + bool is_df = true; + + if (inherit && up->parent && + PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->parent->flags) && + PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(up->parent->flags)) + is_df = false; + + pim_mlag_up_df_role_update(pim, up, is_df, "inherit_xg_df"); +} + +/* We run MLAG DF election only on mroutes that have the termination + * device ipmr-lo in the immediate OIL. This is only (*, G) entries at the + * moment. For (S, G) entries that (with ipmr-lo in the inherited OIL) we + * inherit the DF role from the (*, G) entry. + */ +void pim_vxlan_inherit_mlag_flags(struct pim_instance *pim, + struct pim_upstream *up, bool inherit) +{ + struct listnode *listnode; + struct pim_upstream *child; + + for (ALL_LIST_ELEMENTS_RO(up->sources, listnode, + child)) { + pim_vxlan_update_sg_entry_mlag(pim, + child, true /* inherit */); + } +} + +static void pim_vxlan_term_mr_up_add(struct pim_vxlan_sg *vxlan_sg) +{ + struct pim_upstream *up; + int flags = 0; + + if (vxlan_sg->up) { + /* nothing to do */ + return; + } + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term mroute-up add", + vxlan_sg->sg_str); + + PIM_UPSTREAM_FLAG_SET_SRC_VXLAN_TERM(flags); + /* enable MLAG designated-forwarder election on termination mroutes */ + PIM_UPSTREAM_FLAG_SET_MLAG_VXLAN(flags); + + up = pim_upstream_add(vxlan_sg->pim, &vxlan_sg->sg, NULL /* iif */, + flags, __func__, NULL); + vxlan_sg->up = up; + + if (!up) { + zlog_warn("vxlan SG %s term mroute-up add failed", + vxlan_sg->sg_str); + return; + } + + /* update existing SG entries with the parent's MLAG flag */ + pim_vxlan_inherit_mlag_flags(vxlan_sg->pim, up, true /*enable*/); +} + +static void pim_vxlan_term_mr_up_del(struct pim_vxlan_sg *vxlan_sg) +{ + struct pim_upstream *up = vxlan_sg->up; + + if (!up) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term mroute-up del", + vxlan_sg->sg_str); + vxlan_sg->up = NULL; + if (up->flags & PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM) { + /* update SG entries that are inheriting from this XG entry */ + pim_vxlan_inherit_mlag_flags(vxlan_sg->pim, up, + false /*enable*/); + /* clear out all the vxlan related flags */ + up->flags &= ~(PIM_UPSTREAM_FLAG_MASK_SRC_VXLAN_TERM | + PIM_UPSTREAM_FLAG_MASK_MLAG_VXLAN); + pim_mlag_up_local_del(vxlan_sg->pim, up); + pim_upstream_del(vxlan_sg->pim, up, __func__); + } +} + +static void pim_vxlan_term_mr_add(struct pim_vxlan_sg *vxlan_sg) +{ + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term mroute add", vxlan_sg->sg_str); + + vxlan_sg->term_oif = vxlan_sg->pim->vxlan.term_if; + if (!vxlan_sg->term_oif) + /* defer termination mroute till we have a termination device */ + return; + + pim_vxlan_term_mr_up_add(vxlan_sg); + /* set up local membership for the term-oif */ + pim_vxlan_term_mr_oif_add(vxlan_sg); +} + +static void pim_vxlan_term_mr_del(struct pim_vxlan_sg *vxlan_sg) +{ + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term mroute del", vxlan_sg->sg_str); + + /* remove local membership associated with the term oif */ + pim_vxlan_term_mr_oif_del(vxlan_sg); + /* remove references to the upstream entry */ + pim_vxlan_term_mr_up_del(vxlan_sg); +} + +/************************** vxlan SG cache management ************************/ +static unsigned int pim_vxlan_sg_hash_key_make(const void *p) +{ + const struct pim_vxlan_sg *vxlan_sg = p; + + return pim_sgaddr_hash(vxlan_sg->sg, 0); +} + +static bool pim_vxlan_sg_hash_eq(const void *p1, const void *p2) +{ + const struct pim_vxlan_sg *sg1 = p1; + const struct pim_vxlan_sg *sg2 = p2; + + return !pim_sgaddr_cmp(sg1->sg, sg2->sg); +} + +static struct pim_vxlan_sg *pim_vxlan_sg_new(struct pim_instance *pim, + pim_sgaddr *sg) +{ + struct pim_vxlan_sg *vxlan_sg; + + vxlan_sg = XCALLOC(MTYPE_PIM_VXLAN_SG, sizeof(*vxlan_sg)); + + vxlan_sg->pim = pim; + vxlan_sg->sg = *sg; + snprintfrr(vxlan_sg->sg_str, sizeof(vxlan_sg->sg_str), "%pSG", sg); + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s alloc", vxlan_sg->sg_str); + + vxlan_sg = hash_get(pim->vxlan.sg_hash, vxlan_sg, hash_alloc_intern); + + /* we register with the MLAG daemon in the first VxLAN SG and never + * de-register during that life of the pimd + */ + if (pim->vxlan.sg_hash->count == 1) { + vxlan_mlag.flags |= PIM_VXLAN_MLAGF_DO_REG; + pim_mlag_register(); + } + + return vxlan_sg; +} + +struct pim_vxlan_sg *pim_vxlan_sg_find(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct pim_vxlan_sg lookup; + + lookup.sg = *sg; + return hash_lookup(pim->vxlan.sg_hash, &lookup); +} + +struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct pim_vxlan_sg *vxlan_sg; + + vxlan_sg = pim_vxlan_sg_find(pim, sg); + if (vxlan_sg) + return vxlan_sg; + + vxlan_sg = pim_vxlan_sg_new(pim, sg); + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + pim_vxlan_orig_mr_add(vxlan_sg); + else + pim_vxlan_term_mr_add(vxlan_sg); + + return vxlan_sg; +} + +static void pim_vxlan_sg_del_item(struct pim_vxlan_sg *vxlan_sg) +{ + vxlan_sg->flags |= PIM_VXLAN_SGF_DEL_IN_PROG; + + EVENT_OFF(vxlan_sg->null_register); + pim_vxlan_del_work(vxlan_sg); + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + pim_vxlan_orig_mr_del(vxlan_sg); + else + pim_vxlan_term_mr_del(vxlan_sg); + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s free", vxlan_sg->sg_str); + + XFREE(MTYPE_PIM_VXLAN_SG, vxlan_sg); +} + +void pim_vxlan_sg_del(struct pim_instance *pim, pim_sgaddr *sg) +{ + struct pim_vxlan_sg *vxlan_sg; + + vxlan_sg = pim_vxlan_sg_find(pim, sg); + if (!vxlan_sg) + return; + + hash_release(pim->vxlan.sg_hash, vxlan_sg); + pim_vxlan_sg_del_item(vxlan_sg); +} + +/******************************* MLAG handling *******************************/ +bool pim_vxlan_do_mlag_reg(void) +{ + return (vxlan_mlag.flags & PIM_VXLAN_MLAGF_DO_REG); +} + +/* The peerlink sub-interface is added as an OIF to the origination-mroute. + * This is done to send a copy of the multicast-vxlan encapsulated traffic + * to the MLAG peer which may mroute it over the underlay if there are any + * interested receivers. + */ +static void pim_vxlan_sg_peerlink_oif_update(struct hash_bucket *bucket, + void *arg) +{ + struct interface *new_oif = (struct interface *)arg; + struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data; + + if (!pim_vxlan_is_orig_mroute(vxlan_sg)) + return; + + if (vxlan_sg->orig_oif == new_oif) + return; + + pim_vxlan_orig_mr_oif_del(vxlan_sg); + + vxlan_sg->orig_oif = new_oif; + pim_vxlan_orig_mr_oif_add(vxlan_sg); +} + +/* In the case of anycast VTEPs the VTEP-PIP must be used as the + * register source. + */ +bool pim_vxlan_get_register_src(struct pim_instance *pim, + struct pim_upstream *up, struct in_addr *src_p) +{ + if (!(vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED)) + return true; + + /* if address is not available suppress the pim-register */ + if (vxlan_mlag.reg_addr.s_addr == INADDR_ANY) + return false; + + *src_p = vxlan_mlag.reg_addr; + return true; +} + +void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role, + struct interface *peerlink_rif, + struct in_addr *reg_addr) +{ + struct pim_instance *pim; + char addr_buf[INET_ADDRSTRLEN]; + struct pim_interface *pim_ifp = NULL; + + if (PIM_DEBUG_VXLAN) { + inet_ntop(AF_INET, reg_addr, + addr_buf, INET_ADDRSTRLEN); + zlog_debug("vxlan MLAG update %s state %s role %d rif %s addr %s", + enable ? "enable" : "disable", + peer_state ? "up" : "down", + role, + peerlink_rif ? peerlink_rif->name : "-", + addr_buf); + } + + /* XXX: for now vxlan termination is only possible in the default VRF + * when that changes this will need to change to iterate all VRFs + */ + pim = pim_get_pim_instance(VRF_DEFAULT); + + if (!pim) { + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: Unable to find pim instance", __func__); + return; + } + + if (enable) + vxlan_mlag.flags |= PIM_VXLAN_MLAGF_ENABLED; + else + vxlan_mlag.flags &= ~PIM_VXLAN_MLAGF_ENABLED; + + if (vxlan_mlag.peerlink_rif != peerlink_rif) + vxlan_mlag.peerlink_rif = peerlink_rif; + + vxlan_mlag.reg_addr = *reg_addr; + vxlan_mlag.peer_state = peer_state; + vxlan_mlag.role = role; + + /* process changes */ + if (vxlan_mlag.peerlink_rif) + pim_ifp = (struct pim_interface *)vxlan_mlag.peerlink_rif->info; + if ((vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED) && + pim_ifp && (pim_ifp->mroute_vif_index > 0)) + pim_vxlan_set_peerlink_rif(pim, peerlink_rif); + else + pim_vxlan_set_peerlink_rif(pim, NULL); +} + +/****************************** misc callbacks *******************************/ +static void pim_vxlan_set_default_iif(struct pim_instance *pim, + struct interface *ifp) +{ + struct interface *old_iif; + + if (pim->vxlan.default_iif == ifp) + return; + + old_iif = pim->vxlan.default_iif; + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: vxlan default iif changed from %s to %s", + __func__, old_iif ? old_iif->name : "-", + ifp ? ifp->name : "-"); + + old_iif = pim_vxlan_orig_mr_iif_get(pim); + pim->vxlan.default_iif = ifp; + ifp = pim_vxlan_orig_mr_iif_get(pim); + if (old_iif == ifp) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: vxlan orig iif changed from %s to %s", __func__, + old_iif ? old_iif->name : "-", + ifp ? ifp->name : "-"); + + /* add/del upstream entries for the existing vxlan SG when the + * interface becomes available + */ + if (pim->vxlan.sg_hash) + hash_iterate(pim->vxlan.sg_hash, + pim_vxlan_orig_mr_iif_update, NULL); +} + +static void pim_vxlan_up_cost_update(struct pim_instance *pim, + struct pim_upstream *up, + struct interface *old_peerlink_rif) +{ + if (!PIM_UPSTREAM_FLAG_TEST_MLAG_VXLAN(up->flags)) + return; + + if (up->rpf.source_nexthop.interface && + ((up->rpf.source_nexthop.interface == + pim->vxlan.peerlink_rif) || + (up->rpf.source_nexthop.interface == + old_peerlink_rif))) { + if (PIM_DEBUG_VXLAN) + zlog_debug("RPF cost adjust for %s on peerlink-rif (old: %s, new: %s) change", + up->sg_str, + old_peerlink_rif ? + old_peerlink_rif->name : "-", + pim->vxlan.peerlink_rif ? + pim->vxlan.peerlink_rif->name : "-"); + pim_mlag_up_local_add(pim, up); + } +} + +static void pim_vxlan_term_mr_cost_update(struct hash_bucket *bucket, void *arg) +{ + struct interface *old_peerlink_rif = (struct interface *)arg; + struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data; + struct pim_upstream *up; + struct listnode *listnode; + struct pim_upstream *child; + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + return; + + /* Lookup all XG and SG entries with RPF-interface peerlink_rif */ + up = vxlan_sg->up; + if (!up) + return; + + pim_vxlan_up_cost_update(vxlan_sg->pim, up, + old_peerlink_rif); + + for (ALL_LIST_ELEMENTS_RO(up->sources, listnode, + child)) + pim_vxlan_up_cost_update(vxlan_sg->pim, child, + old_peerlink_rif); +} + +static void pim_vxlan_sg_peerlink_rif_update(struct hash_bucket *bucket, + void *arg) +{ + pim_vxlan_orig_mr_iif_update(bucket, NULL); + pim_vxlan_term_mr_cost_update(bucket, arg); +} + +static void pim_vxlan_set_peerlink_rif(struct pim_instance *pim, + struct interface *ifp) +{ + struct interface *old_iif; + struct interface *new_iif; + struct interface *old_oif; + struct interface *new_oif; + + if (pim->vxlan.peerlink_rif == ifp) + return; + + old_iif = pim->vxlan.peerlink_rif; + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: vxlan peerlink_rif changed from %s to %s", + __func__, old_iif ? old_iif->name : "-", + ifp ? ifp->name : "-"); + + old_iif = pim_vxlan_orig_mr_iif_get(pim); + old_oif = pim_vxlan_orig_mr_oif_get(pim); + pim->vxlan.peerlink_rif = ifp; + + new_iif = pim_vxlan_orig_mr_iif_get(pim); + if (old_iif != new_iif) { + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: vxlan orig iif changed from %s to %s", + __func__, old_iif ? old_iif->name : "-", + new_iif ? new_iif->name : "-"); + + /* add/del upstream entries for the existing vxlan SG when the + * interface becomes available + */ + if (pim->vxlan.sg_hash) + hash_iterate(pim->vxlan.sg_hash, + pim_vxlan_sg_peerlink_rif_update, + old_iif); + } + + new_oif = pim_vxlan_orig_mr_oif_get(pim); + if (old_oif != new_oif) { + if (PIM_DEBUG_VXLAN) + zlog_debug("%s: vxlan orig oif changed from %s to %s", + __func__, old_oif ? old_oif->name : "-", + new_oif ? new_oif->name : "-"); + if (pim->vxlan.sg_hash) + hash_iterate(pim->vxlan.sg_hash, + pim_vxlan_sg_peerlink_oif_update, + new_oif); + } +} + +static void pim_vxlan_term_mr_oif_update(struct hash_bucket *bucket, void *arg) +{ + struct interface *ifp = (struct interface *)arg; + struct pim_vxlan_sg *vxlan_sg = (struct pim_vxlan_sg *)bucket->data; + + if (pim_vxlan_is_orig_mroute(vxlan_sg)) + return; + + if (vxlan_sg->term_oif == ifp) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan SG %s term oif changed from %s to %s", + vxlan_sg->sg_str, + vxlan_sg->term_oif ? vxlan_sg->term_oif->name : "-", + ifp ? ifp->name : "-"); + + pim_vxlan_term_mr_del(vxlan_sg); + vxlan_sg->term_oif = ifp; + pim_vxlan_term_mr_add(vxlan_sg); +} + +static void pim_vxlan_term_oif_update(struct pim_instance *pim, + struct interface *ifp) +{ + if (pim->vxlan.term_if == ifp) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan term oif changed from %s to %s", + pim->vxlan.term_if ? pim->vxlan.term_if->name : "-", + ifp ? ifp->name : "-"); + + pim->vxlan.term_if = ifp; + if (pim->vxlan.sg_hash) + hash_iterate(pim->vxlan.sg_hash, + pim_vxlan_term_mr_oif_update, ifp); +} + +void pim_vxlan_add_vif(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; + + if (pim->vrf->vrf_id != VRF_DEFAULT) + return; + + if (if_is_loopback(ifp)) + pim_vxlan_set_default_iif(pim, ifp); + + if (vxlan_mlag.flags & PIM_VXLAN_MLAGF_ENABLED && + (ifp == vxlan_mlag.peerlink_rif)) + pim_vxlan_set_peerlink_rif(pim, ifp); + + if (pim->vxlan.term_if_cfg == ifp) + pim_vxlan_term_oif_update(pim, ifp); +} + +void pim_vxlan_del_vif(struct interface *ifp) +{ + struct pim_interface *pim_ifp = ifp->info; + struct pim_instance *pim = pim_ifp->pim; + + if (pim->vrf->vrf_id != VRF_DEFAULT) + return; + + if (pim->vxlan.default_iif == ifp) + pim_vxlan_set_default_iif(pim, NULL); + + if (pim->vxlan.peerlink_rif == ifp) + pim_vxlan_set_peerlink_rif(pim, NULL); + + if (pim->vxlan.term_if == ifp) + pim_vxlan_term_oif_update(pim, NULL); +} + +/* enable pim implicitly on the termination device add */ +void pim_vxlan_add_term_dev(struct pim_instance *pim, + struct interface *ifp) +{ + struct pim_interface *pim_ifp; + + if (pim->vxlan.term_if_cfg == ifp) + return; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan term oif cfg changed from %s to %s", + pim->vxlan.term_if_cfg ? + pim->vxlan.term_if_cfg->name : "-", + ifp->name); + + pim->vxlan.term_if_cfg = ifp; + + /* enable pim on the term ifp */ + pim_ifp = (struct pim_interface *)ifp->info; + if (pim_ifp) { + pim_ifp->pim_enable = true; + /* ifp is already oper up; activate it as a term dev */ + if (pim_ifp->mroute_vif_index >= 0) + pim_vxlan_term_oif_update(pim, ifp); + } else { + /* ensure that pimreg exists before using the newly created + * vxlan termination device + */ + pim_if_create_pimreg(pim); + (void)pim_if_new(ifp, false /*igmp*/, true /*pim*/, + false /*pimreg*/, true /*vxlan_term*/); + } +} + +/* disable pim implicitly, if needed, on the termination device deletion */ +void pim_vxlan_del_term_dev(struct pim_instance *pim) +{ + struct interface *ifp = pim->vxlan.term_if_cfg; + struct pim_interface *pim_ifp; + + if (PIM_DEBUG_VXLAN) + zlog_debug("vxlan term oif cfg changed from %s to -", + ifp->name); + + pim->vxlan.term_if_cfg = NULL; + + pim_ifp = (struct pim_interface *)ifp->info; + if (pim_ifp) { + pim_ifp->pim_enable = false; + if (!pim_ifp->gm_enable) + pim_if_delete(ifp); + } +} + +void pim_vxlan_init(struct pim_instance *pim) +{ + char hash_name[64]; + + snprintf(hash_name, sizeof(hash_name), + "PIM %s vxlan SG hash", pim->vrf->name); + pim->vxlan.sg_hash = hash_create(pim_vxlan_sg_hash_key_make, + pim_vxlan_sg_hash_eq, hash_name); +} + +void pim_vxlan_exit(struct pim_instance *pim) +{ + hash_clean_and_free(&pim->vxlan.sg_hash, + (void (*)(void *))pim_vxlan_sg_del_item); + + if (vxlan_info.work_list) + list_delete(&vxlan_info.work_list); +} + +void pim_vxlan_terminate(void) +{ + pim_vxlan_work_timer_setup(false); +} diff --git a/pimd/pim_vxlan.h b/pimd/pim_vxlan.h new file mode 100644 index 0000000..fe3d625 --- /dev/null +++ b/pimd/pim_vxlan.h @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PIM support for VxLAN BUM flooding + * + * Copyright (C) 2019 Cumulus Networks, Inc. + */ + +#ifndef PIM_VXLAN_H +#define PIM_VXLAN_H + +#include "pim_instance.h" + +/* global timer used for miscellaneous staggered processing */ +#define PIM_VXLAN_WORK_TIME 1 +/* number of SG entries processed at one shot */ +#define PIM_VXLAN_WORK_MAX 500 +/* frequency of periodic NULL registers */ +#define PIM_VXLAN_NULL_REG_INTERVAL 60 /* seconds */ + +#define vxlan_mlag (vxlan_info.mlag) + +enum pim_vxlan_sg_flags { + PIM_VXLAN_SGF_NONE = 0, + PIM_VXLAN_SGF_DEL_IN_PROG = (1 << 0), + PIM_VXLAN_SGF_OIF_INSTALLED = (1 << 1) +}; + +struct pim_vxlan_sg { + struct pim_instance *pim; + + /* key */ + pim_sgaddr sg; + char sg_str[PIM_SG_LEN]; + + enum pim_vxlan_sg_flags flags; + struct pim_upstream *up; + struct listnode *work_node; /* to pim_vxlan.work_list */ + + /* termination info (only applicable to termination XG mroutes) + * term_if - termination device ipmr-lo is added to the OIL + * as local/IGMP membership to allow termination of vxlan traffic + */ + struct interface *term_oif; + + /* origination info + * iif - lo/vrf or peerlink (on MLAG setups) + * peerlink_oif - added to the OIL to send encapsulated BUM traffic to + * the MLAG peer switch + */ + struct interface *iif; + /* on a MLAG setup the peerlink is added as a static OIF */ + struct interface *orig_oif; + + struct event *null_register; + uint32_t null_register_sent; +}; + +enum pim_vxlan_mlag_flags { + PIM_VXLAN_MLAGF_NONE = 0, + PIM_VXLAN_MLAGF_ENABLED = (1 << 0), + PIM_VXLAN_MLAGF_DO_REG = (1 << 1) +}; + +struct pim_vxlan_mlag { + enum pim_vxlan_mlag_flags flags; + /* XXX - remove this variable from here */ + int role; + bool peer_state; + /* routed interface setup on top of MLAG peerlink */ + struct interface *peerlink_rif; + struct in_addr reg_addr; +}; + +enum pim_vxlan_flags { + PIM_VXLANF_NONE = 0, + PIM_VXLANF_WORK_INITED = (1 << 0) +}; + +struct pim_vxlan { + enum pim_vxlan_flags flags; + + struct event *work_timer; + struct list *work_list; + struct listnode *next_work; + int max_work_cnt; + + struct pim_vxlan_mlag mlag; +}; + +/* zebra adds- + * 1. one (S, G) entry where S=local-VTEP-IP and G==BUM-mcast-grp for + * each BUM MDT. This is the origination entry. + * 2. and one (*, G) entry each MDT. This is the termination place holder. + * + * Note: This doesn't mean that only (*, G) mroutes are used for tunnel + * termination. (S, G) mroutes with ipmr-lo in the OIL can also be + * used for tunnel termiation if SPT switchover happens; however such + * SG entries are created by traffic and will NOT be a part of the vxlan SG + * database. + */ +static inline bool pim_vxlan_is_orig_mroute(struct pim_vxlan_sg *vxlan_sg) +{ + return !pim_addr_is_any(vxlan_sg->sg.src); +} + +static inline bool pim_vxlan_is_local_sip(struct pim_upstream *up) +{ + return !pim_addr_is_any(up->sg.src) && + up->rpf.source_nexthop.interface && + if_is_loopback(up->rpf.source_nexthop.interface); +} + +static inline bool pim_vxlan_is_term_dev_cfg(struct pim_instance *pim, + struct interface *ifp) +{ + return pim->vxlan.term_if_cfg == ifp; +} + +extern struct pim_vxlan *pim_vxlan_p; +extern struct pim_vxlan_sg *pim_vxlan_sg_find(struct pim_instance *pim, + pim_sgaddr *sg); +extern struct pim_vxlan_sg *pim_vxlan_sg_add(struct pim_instance *pim, + pim_sgaddr *sg); +extern void pim_vxlan_sg_del(struct pim_instance *pim, pim_sgaddr *sg); +extern void pim_vxlan_update_sg_reg_state(struct pim_instance *pim, + struct pim_upstream *up, bool reg_join); +extern struct pim_interface *pim_vxlan_get_term_ifp(struct pim_instance *pim); +extern void pim_vxlan_add_vif(struct interface *ifp); +extern void pim_vxlan_del_vif(struct interface *ifp); +extern void pim_vxlan_add_term_dev(struct pim_instance *pim, + struct interface *ifp); +extern void pim_vxlan_del_term_dev(struct pim_instance *pim); +extern bool pim_vxlan_get_register_src(struct pim_instance *pim, + struct pim_upstream *up, struct in_addr *src_p); +extern void pim_vxlan_mlag_update(bool enable, bool peer_state, uint32_t role, + struct interface *peerlink_rif, + struct in_addr *reg_addr); +extern bool pim_vxlan_do_mlag_reg(void); +extern void pim_vxlan_inherit_mlag_flags(struct pim_instance *pim, + struct pim_upstream *up, bool inherit); + +extern void pim_vxlan_rp_info_is_alive(struct pim_instance *pim, + struct pim_rpf *rpg_changed); + +/* Shutdown of PIM stop the thread */ +extern void pim_vxlan_terminate(void); +#endif /* PIM_VXLAN_H */ diff --git a/pimd/pim_vxlan_instance.h b/pimd/pim_vxlan_instance.h new file mode 100644 index 0000000..65a5955 --- /dev/null +++ b/pimd/pim_vxlan_instance.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* PIM support for VxLAN BUM flooding + * + * Copyright (C) 2019 Cumulus Networks, Inc. + * + */ + +#ifndef PIM_VXLAN_INSTANCE_H +#define PIM_VXLAN_INSTANCE_H + +/* pim termination device is expected to include the substring ipmr-lo */ +#define PIM_VXLAN_TERM_DEV_NAME "ipmr-lo" + +struct pim_vxlan_instance { + struct hash *sg_hash; + + /* this is lo for default instance and vrf-dev for non-default + * instances + */ + struct interface *default_iif; + + /* In a MLAG/VxLAN-AA setup the peerlink sub-interface (ISL-rif) is + * used as the IIF in + */ + struct interface *peerlink_rif; + + /* device used by the dataplane to terminate multicast encapsulated + * vxlan traffic + */ + struct interface *term_if_cfg; + struct interface *term_if; +}; + +extern void pim_vxlan_init(struct pim_instance *pim); +extern void pim_vxlan_exit(struct pim_instance *pim); + +#endif /* PIM_VXLAN_INSTANCE_H */ diff --git a/pimd/pim_zebra.c b/pimd/pim_zebra.c new file mode 100644 index 0000000..e25eafc --- /dev/null +++ b/pimd/pim_zebra.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "if.h" +#include "log.h" +#include "prefix.h" +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "vty.h" +#include "plist.h" +#include "lib/bfd.h" + +#include "pimd.h" +#include "pim_pim.h" +#include "pim_zebra.h" +#include "pim_iface.h" +#include "pim_str.h" +#include "pim_oil.h" +#include "pim_rpf.h" +#include "pim_time.h" +#include "pim_join.h" +#include "pim_zlookup.h" +#include "pim_ifchannel.h" +#include "pim_rp.h" +#include "pim_igmpv3.h" +#include "pim_jp_agg.h" +#include "pim_nht.h" +#include "pim_ssm.h" +#include "pim_vxlan.h" +#include "pim_mlag.h" + +#undef PIM_DEBUG_IFADDR_DUMP +#define PIM_DEBUG_IFADDR_DUMP + +struct zclient *zclient; + + +/* Router-id update message from zebra. */ +static int pim_router_id_update_zebra(ZAPI_CALLBACK_ARGS) +{ + struct prefix router_id; + + zebra_router_id_update_read(zclient->ibuf, &router_id); + + return 0; +} + +#ifdef PIM_DEBUG_IFADDR_DUMP +static void dump_if_address(struct interface *ifp) +{ + struct connected *ifc; + + zlog_debug("%s %s: interface %s addresses:", __FILE__, __func__, + ifp->name); + + frr_each (if_connected, ifp->connected, ifc) { + struct prefix *p = ifc->address; + + if (p->family != AF_INET) + continue; + + zlog_debug("%s %s: interface %s address %pI4 %s", __FILE__, + __func__, ifp->name, &p->u.prefix4, + CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) + ? "secondary" + : "primary"); + } +} +#endif + +static int pim_zebra_if_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct prefix *p; + struct pim_interface *pim_ifp; + + /* + zebra api notifies address adds/dels events by using the same call + interface_add_read below, see comments in lib/zclient.c + + zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...) + will add address to interface list by calling + connected_add_by_prefix() + */ + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (!c) + return 0; + + pim_ifp = c->ifp->info; + p = c->address; + + if (PIM_DEBUG_ZEBRA) { + zlog_debug("%s: %s(%s) connected IP address %pFX flags %u %s", + __func__, c->ifp->name, + (pim_ifp ? VRF_LOGNAME(pim_ifp->pim->vrf) + : "Unknown"), + p, c->flags, + CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) + ? "secondary" + : "primary"); + +#ifdef PIM_DEBUG_IFADDR_DUMP + dump_if_address(c->ifp); +#endif + } + +#if PIM_IPV == 4 + if (p->family != PIM_AF) + SET_FLAG(c->flags, ZEBRA_IFA_SECONDARY); + else if (!CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY)) { + /* trying to add primary address? */ + pim_addr primary_addr = pim_find_primary_addr(c->ifp); + pim_addr addr = pim_addr_from_prefix(p); + + if (pim_addr_cmp(primary_addr, addr)) { + if (PIM_DEBUG_ZEBRA) + zlog_warn( + "%s: %s : forcing secondary flag on %pFX", + __func__, c->ifp->name, p); + SET_FLAG(c->flags, ZEBRA_IFA_SECONDARY); + } + } +#else /* PIM_IPV != 4 */ + if (p->family != PIM_AF) + return 0; +#endif + + pim_if_addr_add(c); + if (pim_ifp) { + struct pim_instance *pim; + + pim = pim_get_pim_instance(vrf_id); + if (!pim) { + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: Unable to find pim instance", + __func__); + return 0; + } + + pim_ifp->pim = pim; + + pim_rp_check_on_if_add(pim_ifp); + } + + if (if_is_loopback(c->ifp)) { + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) { + if (!if_is_loopback(ifp) && if_is_operative(ifp)) + pim_if_addr_add_all(ifp); + } + } + return 0; +} + +static int pim_zebra_if_address_del(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct prefix *p; + struct vrf *vrf = vrf_lookup_by_id(vrf_id); + + if (!vrf) + return 0; + + /* + zebra api notifies address adds/dels events by using the same call + interface_add_read below, see comments in lib/zclient.c + + zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...) + will remove address from interface list by calling + connected_delete_by_prefix() + */ + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + if (!c) + return 0; + + p = c->address; + + if (PIM_DEBUG_ZEBRA) { + zlog_debug( + "%s: %s(%s) disconnected IP address %pFX flags %u %s", + __func__, c->ifp->name, VRF_LOGNAME(vrf), p, c->flags, + CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) + ? "secondary" + : "primary"); +#ifdef PIM_DEBUG_IFADDR_DUMP + dump_if_address(c->ifp); +#endif + } + + if (p->family == PIM_AF) { + struct pim_instance *pim; + + pim = vrf->info; + pim_if_addr_del(c, 0); + pim_rp_setup(pim); + pim_i_am_rp_re_evaluate(pim); + } + + connected_free(&c); + return 0; +} + +void pim_zebra_update_all_interfaces(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct pim_iface_upstream_switch *us; + struct listnode *node; + + if (!pim_ifp) + continue; + + for (ALL_LIST_ELEMENTS_RO(pim_ifp->upstream_switch_list, node, + us)) { + struct pim_rpf rpf; + + rpf.source_nexthop.interface = ifp; + rpf.rpf_addr = us->address; + pim_joinprune_send(&rpf, us->us); + pim_jp_agg_clear_group(us->us); + } + } +} + +void pim_zebra_upstream_rpf_changed(struct pim_instance *pim, + struct pim_upstream *up, + struct pim_rpf *old) +{ + if (old->source_nexthop.interface) { + struct pim_neighbor *nbr; + + nbr = pim_neighbor_find(old->source_nexthop.interface, + old->rpf_addr, true); + + if (nbr) + pim_jp_agg_remove_group(nbr->upstream_jp_agg, up, nbr); + + /* + * We have detected a case where we might need + * to rescan the inherited o_list so do it. + */ + if (up->channel_oil->oil_inherited_rescan) { + pim_upstream_inherited_olist_decide(pim, up); + up->channel_oil->oil_inherited_rescan = 0; + } + + if (up->join_state == PIM_UPSTREAM_JOINED) { + /* + * If we come up real fast we can be here + * where the mroute has not been installed + * so install it. + */ + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, + __func__); + + /* + * RFC 4601: 4.5.7. Sending (S,G) + * Join/Prune Messages + * + * Transitions from Joined State + * + * RPF'(S,G) changes not due to an Assert + * + * The upstream (S,G) state machine remains + * in Joined state. Send Join(S,G) to the new + * upstream neighbor, which is the new value + * of RPF'(S,G). Send Prune(S,G) to the old + * upstream neighbor, which is the old value + * of RPF'(S,G). Set the Join Timer (JT) to + * expire after t_periodic seconds. + */ + pim_jp_agg_switch_interface(old, &up->rpf, up); + + pim_upstream_join_timer_restart(up, old); + } /* up->join_state == PIM_UPSTREAM_JOINED */ + } + + else { + /* + * We have detected a case where we might need + * to rescan the inherited o_list so do it. + */ + if (up->channel_oil->oil_inherited_rescan) { + pim_upstream_inherited_olist_decide(pim, up); + up->channel_oil->oil_inherited_rescan = 0; + } + + if (up->join_state == PIM_UPSTREAM_JOINED) + pim_jp_agg_switch_interface(old, &up->rpf, up); + + if (!up->channel_oil->installed) + pim_upstream_mroute_add(up->channel_oil, __func__); + } + + /* FIXME can join_desired actually be changed by pim_rpf_update() + * returning PIM_RPF_CHANGED ? + */ + pim_upstream_update_join_desired(pim, up); +} + +__attribute__((unused)) +static int pim_zebra_vxlan_sg_proc(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct pim_instance *pim; + pim_sgaddr sg; + size_t prefixlen; + + pim = pim_get_pim_instance(vrf_id); + if (!pim) + return 0; + + s = zclient->ibuf; + + prefixlen = stream_getl(s); + stream_get(&sg.src, s, prefixlen); + stream_get(&sg.grp, s, prefixlen); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s:recv SG %s %pSG", VRF_LOGNAME(pim->vrf), + (cmd == ZEBRA_VXLAN_SG_ADD) ? "add" : "del", &sg); + + if (cmd == ZEBRA_VXLAN_SG_ADD) + pim_vxlan_sg_add(pim, &sg); + else + pim_vxlan_sg_del(pim, &sg); + + return 0; +} + +__attribute__((unused)) +static void pim_zebra_vxlan_replay(void) +{ + struct stream *s = NULL; + + /* Check socket. */ + if (!zclient || zclient->sock < 0) + return; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_VXLAN_SG_REPLAY, VRF_DEFAULT); + stream_putw_at(s, 0, stream_get_endp(s)); + + zclient_send_message(zclient); +} + +void pim_scan_oil(struct pim_instance *pim) +{ + struct channel_oil *c_oil; + + pim->scan_oil_last = pim_time_monotonic_sec(); + ++pim->scan_oil_events; + + frr_each (rb_pim_oil, &pim->channel_oil_head, c_oil) + pim_upstream_mroute_iif_update(c_oil, __func__); +} + +static void on_rpf_cache_refresh(struct event *t) +{ + struct pim_instance *pim = EVENT_ARG(t); + + /* update kernel multicast forwarding cache (MFC) */ + pim_scan_oil(pim); + + pim->rpf_cache_refresh_last = pim_time_monotonic_sec(); + ++pim->rpf_cache_refresh_events; + + // It is called as part of pim_neighbor_add + // pim_rp_setup (); +} + +void sched_rpf_cache_refresh(struct pim_instance *pim) +{ + ++pim->rpf_cache_refresh_requests; + + pim_rpf_set_refresh_time(pim); + + if (pim->rpf_cache_refresher) { + /* Refresh timer is already running */ + return; + } + + /* Start refresh timer */ + + if (PIM_DEBUG_ZEBRA) { + zlog_debug("%s: triggering %ld msec timer", __func__, + router->rpf_cache_refresh_delay_msec); + } + + event_add_timer_msec(router->master, on_rpf_cache_refresh, pim, + router->rpf_cache_refresh_delay_msec, + &pim->rpf_cache_refresher); +} + +static void pim_zebra_connected(struct zclient *zclient) +{ +#if PIM_IPV == 4 + /* Send the client registration */ + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, router->vrf_id); +#endif + + zclient_send_reg_requests(zclient, router->vrf_id); + +#if PIM_IPV == 4 + /* request for VxLAN BUM group addresses */ + pim_zebra_vxlan_replay(); +#endif +} + +static void pim_zebra_capabilities(struct zclient_capabilities *cap) +{ + router->mlag_role = cap->role; + router->multipath = cap->ecmp; +} + +static zclient_handler *const pim_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = pim_zebra_if_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = pim_zebra_if_address_del, + + [ZEBRA_ROUTER_ID_UPDATE] = pim_router_id_update_zebra, + +#if PIM_IPV == 4 + [ZEBRA_VXLAN_SG_ADD] = pim_zebra_vxlan_sg_proc, + [ZEBRA_VXLAN_SG_DEL] = pim_zebra_vxlan_sg_proc, + + [ZEBRA_MLAG_PROCESS_UP] = pim_zebra_mlag_process_up, + [ZEBRA_MLAG_PROCESS_DOWN] = pim_zebra_mlag_process_down, + [ZEBRA_MLAG_FORWARD_MSG] = pim_zebra_mlag_handle_msg, +#endif +}; + +void pim_zebra_init(void) +{ + /* Socket for receiving updates from Zebra daemon */ + zclient = zclient_new(router->master, &zclient_options_default, + pim_handlers, array_size(pim_handlers)); + + zclient->zebra_capabilities = pim_zebra_capabilities; + zclient->zebra_connected = pim_zebra_connected; + zclient->nexthop_update = pim_nexthop_update; + + zclient_init(zclient, ZEBRA_ROUTE_PIM, 0, &pimd_privs); + if (PIM_DEBUG_PIM_TRACE) { + zlog_notice("%s: zclient socket initialized", __func__); + } + + zclient_lookup_new(); +} + +void pim_forward_start(struct pim_ifchannel *ch) +{ + struct pim_upstream *up = ch->upstream; + uint32_t mask = 0; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: (S,G)=%pSG oif=%s (%pPA)", __func__, &ch->sg, + ch->interface->name, &up->upstream_addr); + + if (PIM_IF_FLAG_TEST_PROTO_IGMP(ch->flags)) + mask = PIM_OIF_FLAG_PROTO_GM; + + if (PIM_IF_FLAG_TEST_PROTO_PIM(ch->flags)) + mask |= PIM_OIF_FLAG_PROTO_PIM; + + pim_channel_add_oif(up->channel_oil, ch->interface, + mask, __func__); +} + +void pim_forward_stop(struct pim_ifchannel *ch) +{ + struct pim_upstream *up = ch->upstream; + + if (PIM_DEBUG_PIM_TRACE) { + zlog_debug("%s: (S,G)=%s oif=%s installed: %d", + __func__, ch->sg_str, ch->interface->name, + up->channel_oil->installed); + } + + /* + * If a channel is being removed, check to see if we still need + * to inherit the interface. If so make sure it is added in + */ + if (pim_upstream_evaluate_join_desired_interface(up, ch, ch->parent)) + pim_channel_add_oif(up->channel_oil, ch->interface, + PIM_OIF_FLAG_PROTO_PIM, __func__); + else + pim_channel_del_oif(up->channel_oil, ch->interface, + PIM_OIF_FLAG_PROTO_PIM, __func__); +} + +void pim_zebra_zclient_update(struct vty *vty) +{ + vty_out(vty, "Zclient update socket: "); + + if (zclient) { + vty_out(vty, "%d failures=%d\n", zclient->sock, zclient->fail); + } else { + vty_out(vty, "\n"); + } +} + +struct zclient *pim_zebra_zclient_get(void) +{ + if (zclient) + return zclient; + else + return NULL; +} + +void pim_zebra_interface_set_master(struct interface *vrf, + struct interface *ifp) +{ + zclient_interface_set_master(zclient, vrf, ifp); +} diff --git a/pimd/pim_zebra.h b/pimd/pim_zebra.h new file mode 100644 index 0000000..0ef63f2 --- /dev/null +++ b/pimd/pim_zebra.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_ZEBRA_H +#define PIM_ZEBRA_H + +#include +#include "zclient.h" + +#include "pim_ifchannel.h" + +void pim_zebra_init(void); +void pim_zebra_zclient_update(struct vty *vty); + +void pim_scan_oil(struct pim_instance *pim_matcher); + +void pim_forward_start(struct pim_ifchannel *ch); +void pim_forward_stop(struct pim_ifchannel *ch); + +void sched_rpf_cache_refresh(struct pim_instance *pim); +struct zclient *pim_zebra_zclient_get(void); + +void pim_zebra_update_all_interfaces(struct pim_instance *pim); +void pim_zebra_upstream_rpf_changed(struct pim_instance *pim, + struct pim_upstream *up, + struct pim_rpf *old); + +void pim_zebra_interface_set_master(struct interface *vrf, + struct interface *ifp); +#endif /* PIM_ZEBRA_H */ diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c new file mode 100644 index 0000000..c19119f --- /dev/null +++ b/pimd/pim_zlookup.c @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "prefix.h" +#include "zclient.h" +#include "stream.h" +#include "network.h" +#include "frrevent.h" +#include "prefix.h" +#include "vty.h" +#include "lib_errors.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_iface.h" +#include "pim_neighbor.h" +#include "pim_pim.h" +#include "pim_str.h" +#include "pim_oil.h" +#include "pim_zlookup.h" +#include "pim_addr.h" + +static struct zclient *zlookup = NULL; +struct event *zlookup_read; + +static void zclient_lookup_sched(struct zclient *zlookup, int delay); +static void zclient_lookup_read_pipe(struct event *thread); + +/* Connect to zebra for nexthop lookup. */ +static void zclient_lookup_connect(struct event *t) +{ + struct zclient *zlookup; + + zlookup = EVENT_ARG(t); + + if (zlookup->sock >= 0) { + return; + } + + if (zclient_socket_connect(zlookup) < 0) { + ++zlookup->fail; + zlog_warn("%s: failure connecting zclient socket: failures=%d", + __func__, zlookup->fail); + } else { + zlookup->fail = 0; /* reset counter on connection */ + } + + if (zclient_send_hello(zlookup) == ZCLIENT_SEND_FAILURE) { + if (close(zlookup->sock)) { + zlog_warn("%s: closing fd=%d: errno=%d %s", __func__, + zlookup->sock, errno, safe_strerror(errno)); + } + zlookup->sock = -1; + } + + if (zlookup->sock < 0) { + /* Since last connect failed, retry within 10 secs */ + zclient_lookup_sched(zlookup, 10); + return; + } + + event_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60, + &zlookup_read); +} + +/* Schedule connection with delay. */ +static void zclient_lookup_sched(struct zclient *zlookup, int delay) +{ + event_add_timer(router->master, zclient_lookup_connect, zlookup, delay, + &zlookup->t_connect); + + zlog_notice("%s: zclient lookup connection scheduled for %d seconds", + __func__, delay); +} + +/* Schedule connection for now. */ +static void zclient_lookup_sched_now(struct zclient *zlookup) +{ + event_add_event(router->master, zclient_lookup_connect, zlookup, 0, + &zlookup->t_connect); + + zlog_notice("%s: zclient lookup immediate connection scheduled", + __func__); +} + +/* Schedule reconnection, if needed. */ +static void zclient_lookup_reconnect(struct zclient *zlookup) +{ + if (zlookup->t_connect) { + return; + } + + zclient_lookup_sched_now(zlookup); +} + +static void zclient_lookup_failed(struct zclient *zlookup) +{ + if (zlookup->sock >= 0) { + if (close(zlookup->sock)) { + zlog_warn("%s: closing fd=%d: errno=%d %s", __func__, + zlookup->sock, errno, safe_strerror(errno)); + } + zlookup->sock = -1; + } + + zclient_lookup_reconnect(zlookup); +} + +void zclient_lookup_free(void) +{ + EVENT_OFF(zlookup_read); + zclient_stop(zlookup); + zclient_free(zlookup); + zlookup = NULL; +} + +void zclient_lookup_new(void) +{ + zlookup = zclient_new(router->master, &zclient_options_sync, NULL, 0); + if (!zlookup) { + flog_err(EC_LIB_ZAPI_SOCKET, "%s: zclient_new() failure", + __func__); + return; + } + + zlookup->sock = -1; + zlookup->t_connect = NULL; + zlookup->privs = &pimd_privs; + + zclient_lookup_sched_now(zlookup); + + zlog_notice("%s: zclient lookup socket initialized", __func__); +} + +static int zclient_read_nexthop(struct pim_instance *pim, + struct zclient *zlookup, + struct pim_zlookup_nexthop nexthop_tab[], + const int tab_size, pim_addr addr) +{ + int num_ifindex = 0; + struct stream *s; + uint16_t length; + uint8_t marker; + uint8_t version; + vrf_id_t vrf_id; + uint16_t command = 0; + struct ipaddr raddr; + uint8_t distance; + uint32_t metric; + int nexthop_num; + int i, err; + + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: addr=%pPAs(%s)", __func__, &addr, + pim->vrf->name); + + s = zlookup->ibuf; + + while (command != ZEBRA_NEXTHOP_LOOKUP_MRIB) { + stream_reset(s); + err = zclient_read_header(s, zlookup->sock, &length, &marker, + &version, &vrf_id, &command); + if (err < 0) { + flog_err(EC_LIB_ZAPI_MISSMATCH, + "%s: zclient_read_header() failed", __func__); + zclient_lookup_failed(zlookup); + return -1; + } + + if (command == ZEBRA_ERROR) { + enum zebra_error_types error; + + zapi_error_decode(s, &error); + /* Do nothing with it for now */ + return -1; + } + } + + stream_get_ipaddr(s, &raddr); + + if (raddr.ipa_type != PIM_IPADDR || + pim_addr_cmp(raddr.ipaddr_pim, addr)) { + zlog_warn("%s: address mismatch: addr=%pPA(%s) raddr=%pIA", + __func__, &addr, pim->vrf->name, &raddr); + /* warning only */ + } + + distance = stream_getc(s); + metric = stream_getl(s); + nexthop_num = stream_getc(s); + + if (nexthop_num < 1 || nexthop_num > router->multipath) { + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: socket %d bad nexthop_num=%d", __func__, + zlookup->sock, nexthop_num); + return -6; + } + + for (i = 0; i < nexthop_num; ++i) { + vrf_id_t nexthop_vrf_id; + enum nexthop_types_t nexthop_type; + struct in_addr nh_ip4; + struct in6_addr nh_ip6; + ifindex_t nh_ifi; + + nexthop_vrf_id = stream_getl(s); + nexthop_type = stream_getc(s); + if (num_ifindex >= tab_size) { + zlog_warn( + "%s: found too many nexthop ifindexes (%d > %d) for address %pPAs(%s)", + __func__, (num_ifindex + 1), tab_size, &addr, + pim->vrf->name); + return num_ifindex; + } + nexthop_tab[num_ifindex].protocol_distance = distance; + nexthop_tab[num_ifindex].route_metric = metric; + nexthop_tab[num_ifindex].vrf_id = nexthop_vrf_id; + switch (nexthop_type) { + case NEXTHOP_TYPE_IFINDEX: + nexthop_tab[num_ifindex].ifindex = stream_getl(s); + /* + * Connected route (i.e. no nexthop), use + * address passed in as PIM nexthop. This will + * allow us to work in cases where we are + * trying to find a route for this box. + */ + nexthop_tab[num_ifindex].nexthop_addr = addr; + ++num_ifindex; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + nh_ip4.s_addr = stream_get_ipv4(s); + nh_ifi = stream_getl(s); +#if PIM_IPV == 4 + nexthop_tab[num_ifindex].nexthop_addr = nh_ip4; + nexthop_tab[num_ifindex].ifindex = nh_ifi; + ++num_ifindex; +#else + zlog_warn( + "cannot use IPv4 nexthop %pI4(%d) for IPv6 %pPA", + &nh_ip4, nh_ifi, &addr); +#endif + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + stream_get(&nh_ip6, s, sizeof(nh_ip6)); + nh_ifi = stream_getl(s); + +#if PIM_IPV == 6 + nexthop_tab[num_ifindex].nexthop_addr = nh_ip6; + nexthop_tab[num_ifindex].ifindex = nh_ifi; + ++num_ifindex; +#else + /* RFC 5549 v4-over-v6 nexthop handling */ + + /* + * If we are sending v6 secondary assume we receive v6 + * secondary + */ + struct interface *ifp = if_lookup_by_index( + nh_ifi, + nexthop_vrf_id); + + if (!ifp) + break; + + struct pim_neighbor *nbr; + + if (pim->send_v6_secondary) { + struct prefix p; + + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_BITLEN; + p.u.prefix6 = nh_ip6; + + nbr = pim_neighbor_find_by_secondary(ifp, &p); + } else + nbr = pim_neighbor_find_if(ifp); + + if (!nbr) + break; + + nexthop_tab[num_ifindex].nexthop_addr = + nbr->source_addr; + nexthop_tab[num_ifindex].ifindex = nh_ifi; + ++num_ifindex; +#endif + break; + case NEXTHOP_TYPE_BLACKHOLE: + /* do nothing */ + zlog_warn( + "%s: found non-ifindex nexthop type=%d for address %pPAs(%s)", + __func__, nexthop_type, &addr, pim->vrf->name); + break; + } + } + + return num_ifindex; +} + +static int zclient_lookup_nexthop_once(struct pim_instance *pim, + struct pim_zlookup_nexthop nexthop_tab[], + const int tab_size, pim_addr addr) +{ + struct stream *s; + int ret; + struct ipaddr ipaddr; + + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: addr=%pPAs(%s)", __func__, &addr, + pim->vrf->name); + + /* Check socket. */ + if (zlookup->sock < 0) { + flog_err(EC_LIB_ZAPI_SOCKET, + "%s: zclient lookup socket is not connected", + __func__); + zclient_lookup_failed(zlookup); + return -1; + } + + if (pim->vrf->vrf_id == VRF_UNKNOWN) { + zlog_notice( + "%s: VRF: %s does not fully exist yet, delaying lookup", + __func__, pim->vrf->name); + return -1; + } + + ipaddr.ipa_type = PIM_IPADDR; + ipaddr.ipaddr_pim = addr; + + s = zlookup->obuf; + stream_reset(s); + zclient_create_header(s, ZEBRA_NEXTHOP_LOOKUP_MRIB, pim->vrf->vrf_id); + stream_put_ipaddr(s, &ipaddr); + stream_putw_at(s, 0, stream_get_endp(s)); + + ret = writen(zlookup->sock, s->data, stream_get_endp(s)); + if (ret < 0) { + flog_err( + EC_LIB_SOCKET, + "%s: writen() failure: %d writing to zclient lookup socket", + __func__, errno); + zclient_lookup_failed(zlookup); + return -2; + } + if (ret == 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s: connection closed on zclient lookup socket", + __func__); + zclient_lookup_failed(zlookup); + return -3; + } + + return zclient_read_nexthop(pim, zlookup, nexthop_tab, tab_size, addr); +} + +void zclient_lookup_read_pipe(struct event *thread) +{ + struct zclient *zlookup = EVENT_ARG(thread); + struct pim_instance *pim = pim_get_pim_instance(VRF_DEFAULT); + struct pim_zlookup_nexthop nexthop_tab[10]; + pim_addr l = PIMADDR_ANY; + + if (!pim) { + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug("%s: Unable to find pim instance", __func__); + return; + } + + zclient_lookup_nexthop_once(pim, nexthop_tab, 10, l); + event_add_timer(router->master, zclient_lookup_read_pipe, zlookup, 60, + &zlookup_read); +} + +int zclient_lookup_nexthop(struct pim_instance *pim, + struct pim_zlookup_nexthop nexthop_tab[], + const int tab_size, pim_addr addr, + int max_lookup) +{ + int lookup; + uint32_t route_metric = 0xFFFFFFFF; + uint8_t protocol_distance = 0xFF; + + pim->nexthop_lookups++; + + for (lookup = 0; lookup < max_lookup; ++lookup) { + int num_ifindex; + int first_ifindex; + pim_addr nexthop_addr; + + num_ifindex = zclient_lookup_nexthop_once(pim, nexthop_tab, + tab_size, addr); + if (num_ifindex < 1) { + if (PIM_DEBUG_PIM_NHT_DETAIL) + zlog_debug( + "%s: lookup=%d/%d: could not find nexthop ifindex for address %pPA(%s)", + __func__, lookup, max_lookup, &addr, + pim->vrf->name); + return -1; + } + + if (lookup < 1) { + /* this is the non-recursive lookup - save original + * metric/distance */ + route_metric = nexthop_tab[0].route_metric; + protocol_distance = nexthop_tab[0].protocol_distance; + } + + /* + * FIXME: Non-recursive nexthop ensured only for first ifindex. + * However, recursive route lookup should really be fixed in + * zebra daemon. + * See also TODO T24. + * + * So Zebra for NEXTHOP_TYPE_IPV4 returns the ifindex now since + * it was being stored. This Doesn't solve all cases of + * recursive lookup but for the most common types it does. + */ + first_ifindex = nexthop_tab[0].ifindex; + nexthop_addr = nexthop_tab[0].nexthop_addr; + if (first_ifindex > 0) { + /* found: first ifindex is non-recursive nexthop */ + + if (lookup > 0) { + /* Report non-recursive success after first + * lookup */ + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: lookup=%d/%d: found non-recursive ifindex=%d for address %pPA(%s) dist=%d met=%d", + __func__, lookup, max_lookup, + first_ifindex, &addr, + pim->vrf->name, + nexthop_tab[0] + .protocol_distance, + nexthop_tab[0].route_metric); + + /* use last address as nexthop address */ + nexthop_tab[0].nexthop_addr = addr; + + /* report original route metric/distance */ + nexthop_tab[0].route_metric = route_metric; + nexthop_tab[0].protocol_distance = + protocol_distance; + } + + return num_ifindex; + } + + if (PIM_DEBUG_PIM_NHT) + zlog_debug( + "%s: lookup=%d/%d: zebra returned recursive nexthop %pPAs for address %pPA(%s) dist=%d met=%d", + __func__, lookup, max_lookup, &nexthop_addr, + &addr, pim->vrf->name, + nexthop_tab[0].protocol_distance, + nexthop_tab[0].route_metric); + + addr = nexthop_addr; /* use nexthop + addr for recursive lookup */ + + } /* for (max_lookup) */ + + if (PIM_DEBUG_PIM_NHT) + zlog_warn( + "%s: lookup=%d/%d: failure searching recursive nexthop ifindex for address %pPA(%s)", + __func__, lookup, max_lookup, &addr, pim->vrf->name); + + return -2; +} + +void pim_zlookup_show_ip_multicast(struct vty *vty) +{ + vty_out(vty, "Zclient lookup socket: "); + if (zlookup) { + vty_out(vty, "%d failures=%d\n", zlookup->sock, zlookup->fail); + } else { + vty_out(vty, "\n"); + } +} + +int pim_zlookup_sg_statistics(struct channel_oil *c_oil) +{ + struct stream *s = zlookup->obuf; + uint16_t command = 0; + unsigned long long lastused; + pim_sgaddr sg; + int count = 0; + int ret; + pim_sgaddr more = {}; + struct interface *ifp = + pim_if_find_by_vif_index(c_oil->pim, *oil_incoming_vif(c_oil)); + + if (PIM_DEBUG_ZEBRA) { + more.src = *oil_origin(c_oil); + more.grp = *oil_mcastgrp(c_oil); + zlog_debug("Sending Request for New Channel Oil Information%pSG VIIF %d(%s:%s)", + &more, *oil_incoming_vif(c_oil), + ifp ? ifp->name : "Unknown", c_oil->pim->vrf->name); + } + + if (!ifp) + return -1; + + stream_reset(s); + zclient_create_header(s, ZEBRA_IPMR_ROUTE_STATS, + c_oil->pim->vrf->vrf_id); + stream_putl(s, PIM_AF); + stream_write(s, oil_origin(c_oil), sizeof(pim_addr)); + stream_write(s, oil_mcastgrp(c_oil), sizeof(pim_addr)); + stream_putl(s, ifp->ifindex); + stream_putw_at(s, 0, stream_get_endp(s)); + + count = stream_get_endp(s); + ret = writen(zlookup->sock, s->data, count); + if (ret <= 0) { + flog_err( + EC_LIB_SOCKET, + "%s: writen() failure: %d writing to zclient lookup socket", + __func__, errno); + return -1; + } + + s = zlookup->ibuf; + + while (command != ZEBRA_IPMR_ROUTE_STATS) { + int err; + uint16_t length = 0; + vrf_id_t vrf_id; + uint8_t marker; + uint8_t version; + + stream_reset(s); + err = zclient_read_header(s, zlookup->sock, &length, &marker, + &version, &vrf_id, &command); + if (err < 0) { + flog_err(EC_LIB_ZAPI_MISSMATCH, + "%s: zclient_read_header() failed", __func__); + zclient_lookup_failed(zlookup); + return -1; + } + } + + stream_get(&sg.src, s, sizeof(pim_addr)); + stream_get(&sg.grp, s, sizeof(pim_addr)); + + more.src = *oil_origin(c_oil); + more.grp = *oil_mcastgrp(c_oil); + if (pim_sgaddr_cmp(sg, more)) { + if (PIM_DEBUG_ZEBRA) + flog_err( + EC_LIB_ZAPI_MISSMATCH, + "%s: Received wrong %pSG(%s) information requested", + __func__, &more, c_oil->pim->vrf->name); + zclient_lookup_failed(zlookup); + return -3; + } + + stream_get(&lastused, s, sizeof(lastused)); + /* signed success value from netlink_talk; currently unused */ + (void)stream_getl(s); + + c_oil->cc.lastused = lastused; + + return 0; +} diff --git a/pimd/pim_zlookup.h b/pimd/pim_zlookup.h new file mode 100644 index 0000000..ee2dd20 --- /dev/null +++ b/pimd/pim_zlookup.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIM_ZLOOKUP_H +#define PIM_ZLOOKUP_H + +#include + +#include "zclient.h" + +#define PIM_NEXTHOP_LOOKUP_MAX (3) /* max. recursive route lookup */ + +struct channel_oil; + +struct pim_zlookup_nexthop { + vrf_id_t vrf_id; + pim_addr nexthop_addr; + ifindex_t ifindex; + uint32_t route_metric; + uint8_t protocol_distance; +}; + +void zclient_lookup_new(void); +void zclient_lookup_free(void); + +int zclient_lookup_nexthop(struct pim_instance *pim, + struct pim_zlookup_nexthop nexthop_tab[], + const int tab_size, pim_addr addr, + int max_lookup); + +void pim_zlookup_show_ip_multicast(struct vty *vty); + +int pim_zlookup_sg_statistics(struct channel_oil *c_oil); +#endif /* PIM_ZLOOKUP_H */ diff --git a/pimd/pim_zpthread.c b/pimd/pim_zpthread.c new file mode 100644 index 0000000..d6b2621 --- /dev/null +++ b/pimd/pim_zpthread.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include +#include +#include + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_mlag.h" +#include "pim_zebra.h" + +extern struct zclient *zclient; + +#define PIM_MLAG_POST_LIMIT 100 + +int32_t mlag_bulk_cnt; + +static void pim_mlag_zebra_fill_header(enum mlag_msg_type msg_type) +{ + uint32_t fill_msg_type = msg_type; + uint16_t data_len = 0; + uint16_t msg_cnt = 1; + + switch (msg_type) { + case MLAG_REGISTER: + case MLAG_DEREGISTER: + data_len = sizeof(struct mlag_msg); + break; + case MLAG_MROUTE_ADD: + data_len = sizeof(struct mlag_mroute_add); + fill_msg_type = MLAG_MROUTE_ADD_BULK; + break; + case MLAG_MROUTE_DEL: + data_len = sizeof(struct mlag_mroute_del); + fill_msg_type = MLAG_MROUTE_DEL_BULK; + break; + case MLAG_MSG_NONE: + return; + case MLAG_STATUS_UPDATE: + case MLAG_DUMP: + case MLAG_MROUTE_ADD_BULK: + case MLAG_MROUTE_DEL_BULK: + case MLAG_PIM_CFG_DUMP: + case MLAG_VXLAN_UPDATE: + case MLAG_PEER_FRR_STATUS: + data_len = 0; + break; + } + + stream_reset(router->mlag_stream); + /* ADD Hedaer */ + stream_putl(router->mlag_stream, fill_msg_type); + /* + * In case of Bulk actual size & msg_cnt will be updated + * just before writing onto zebra + */ + stream_putw(router->mlag_stream, data_len); + stream_putw(router->mlag_stream, msg_cnt); + + if (PIM_DEBUG_MLAG) + zlog_debug(":%s: msg_type: %d/%d len %d", + __func__, msg_type, fill_msg_type, data_len); +} + +static void pim_mlag_zebra_flush_buffer(void) +{ + uint32_t msg_type; + + /* Stream had bulk messages update the Hedaer */ + if (mlag_bulk_cnt > 1) { + /* + * No need to reset the pointer, below api reads from data[0] + */ + STREAM_GETL(router->mlag_stream, msg_type); + if (msg_type == MLAG_MROUTE_ADD_BULK) { + stream_putw_at( + router->mlag_stream, 4, + (mlag_bulk_cnt * sizeof(struct mlag_mroute_add))); + stream_putw_at(router->mlag_stream, 6, mlag_bulk_cnt); + } else if (msg_type == MLAG_MROUTE_DEL_BULK) { + stream_putw_at( + router->mlag_stream, 4, + (mlag_bulk_cnt * sizeof(struct mlag_mroute_del))); + stream_putw_at(router->mlag_stream, 6, mlag_bulk_cnt); + } else { + flog_err(EC_LIB_ZAPI_ENCODE, + "unknown bulk message type %d bulk_count %d", + msg_type, mlag_bulk_cnt); + stream_reset(router->mlag_stream); + mlag_bulk_cnt = 0; + return; + } + } + + zclient_send_mlag_data(zclient, router->mlag_stream); +stream_failure: + stream_reset(router->mlag_stream); + mlag_bulk_cnt = 0; +} + +/* + * Only ROUTE add & Delete will be bulked. + * Buffer will be flushed, when + * 1) there were no messages in the queue + * 2) Curr_msg_type != prev_msg_type + */ + +static void pim_mlag_zebra_check_for_buffer_flush(uint32_t curr_msg_type, + uint32_t prev_msg_type) +{ + /* First Message, keep bulking */ + if (prev_msg_type == MLAG_MSG_NONE) { + mlag_bulk_cnt = 1; + return; + } + + /*msg type is route add & delete, keep bulking */ + if (curr_msg_type == prev_msg_type + && (curr_msg_type == MLAG_MROUTE_ADD + || curr_msg_type == MLAG_MROUTE_DEL)) { + mlag_bulk_cnt++; + return; + } + + pim_mlag_zebra_flush_buffer(); +} + +/* + * Thsi thread reads the clients data from the Gloabl queue and encodes with + * protobuf and pass on to the MLAG socket. + */ +static void pim_mlag_zthread_handler(struct event *event) +{ + struct stream *read_s; + uint32_t wr_count = 0; + uint32_t prev_msg_type = MLAG_MSG_NONE; + uint32_t curr_msg_type = MLAG_MSG_NONE; + + router->zpthread_mlag_write = NULL; + wr_count = stream_fifo_count_safe(router->mlag_fifo); + + if (PIM_DEBUG_MLAG) + zlog_debug(":%s: Processing MLAG write, %d messages in queue", + __func__, wr_count); + + if (wr_count == 0) + return; + + for (wr_count = 0; wr_count < PIM_MLAG_POST_LIMIT; wr_count++) { + /* FIFO is empty,wait for teh message to be add */ + if (stream_fifo_count_safe(router->mlag_fifo) == 0) + break; + + read_s = stream_fifo_pop_safe(router->mlag_fifo); + if (!read_s) { + zlog_debug(":%s: Got a NULL Messages, some thing wrong", + __func__); + break; + } + STREAM_GETL(read_s, curr_msg_type); + /* + * Check for Buffer Overflow, + * MLAG Can't process more than 'PIM_MLAG_BUF_LIMIT' bytes + */ + if (router->mlag_stream->endp + read_s->endp + ZEBRA_HEADER_SIZE + > MLAG_BUF_LIMIT) + pim_mlag_zebra_flush_buffer(); + + pim_mlag_zebra_check_for_buffer_flush(curr_msg_type, + prev_msg_type); + + /* + * First message to Buffer, fill the Header + */ + if (router->mlag_stream->endp == 0) + pim_mlag_zebra_fill_header(curr_msg_type); + + /* + * add the data now + */ + stream_put(router->mlag_stream, read_s->data + read_s->getp, + read_s->endp - read_s->getp); + + stream_free(read_s); + prev_msg_type = curr_msg_type; + } + +stream_failure: + /* + * we are here , because + * 1. Queue might be empty + * 2. we crossed the max Q Read limit + * In any acse flush the buffer towards zebra + */ + pim_mlag_zebra_flush_buffer(); + + if (wr_count >= PIM_MLAG_POST_LIMIT) + pim_mlag_signal_zpthread(); +} + + +int pim_mlag_signal_zpthread(void) +{ + if (router->master) { + if (PIM_DEBUG_MLAG) + zlog_debug(":%s: Scheduling PIM MLAG write Thread", + __func__); + event_add_event(router->master, pim_mlag_zthread_handler, NULL, + 0, &router->zpthread_mlag_write); + } + return (0); +} diff --git a/pimd/pimd.c b/pimd/pimd.c new file mode 100644 index 0000000..db61974 --- /dev/null +++ b/pimd/pimd.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include "log.h" +#include "memory.h" +#include "if.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" +#include "hash.h" +#include "jhash.h" +#include "vrf.h" +#include "lib_errors.h" +#include "bfd.h" + +#include "pimd.h" +#if PIM_IPV == 4 +#include "pim_cmd.h" +#else +#include "pim6_cmd.h" +#endif +#include "pim_str.h" +#include "pim_oil.h" +#include "pim_pim.h" +#include "pim_ssmpingd.h" +#include "pim_static.h" +#include "pim_rp.h" +#include "pim_ssm.h" +#include "pim_vxlan.h" +#include "pim_zlookup.h" +#include "pim_zebra.h" +#include "pim_mlag.h" + +#if MAXVIFS > 256 +CPP_NOTICE("Work needs to be done to make this work properly via the pim mroute socket\n"); +#endif /* MAXVIFS > 256 */ + +#if PIM_IPV == 4 +const char *const PIM_ALL_SYSTEMS = MCAST_ALL_SYSTEMS; +const char *const PIM_ALL_ROUTERS = MCAST_ALL_ROUTERS; +const char *const PIM_ALL_PIM_ROUTERS = MCAST_ALL_PIM_ROUTERS; +const char *const PIM_ALL_IGMP_ROUTERS = MCAST_ALL_IGMP_ROUTERS; +#else +const char *const PIM_ALL_SYSTEMS = "ff02::1"; +const char *const PIM_ALL_ROUTERS = "ff02::2"; +const char *const PIM_ALL_PIM_ROUTERS = "ff02::d"; +const char *const PIM_ALL_IGMP_ROUTERS = "ff02::16"; +#endif + +DEFINE_MTYPE_STATIC(PIMD, ROUTER, "PIM Router information"); + +struct pim_router *router = NULL; +pim_addr qpim_all_pim_routers_addr; + +void pim_prefix_list_update(struct prefix_list *plist) +{ + struct pim_instance *pim; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + pim = vrf->info; + if (!pim) + continue; + + pim_rp_prefix_list_update(pim, plist); + pim_ssm_prefix_list_update(pim, plist); + pim_upstream_spt_prefix_list_update(pim, plist); + } +} + +static void pim_free(void) +{ + pim_route_map_terminate(); + + zclient_lookup_free(); +} + +void pim_router_init(void) +{ + router = XCALLOC(MTYPE_ROUTER, sizeof(*router)); + + router->debugs = 0; + router->master = frr_init(); + router->t_periodic = PIM_DEFAULT_T_PERIODIC; + router->multipath = MULTIPATH_NUM; + + /* + RFC 4601: 4.6.3. Assert Metrics + + assert_metric + infinite_assert_metric() { + return {1,infinity,infinity,0} + } + */ + router->infinite_assert_metric.rpt_bit_flag = 1; + router->infinite_assert_metric.metric_preference = + PIM_ASSERT_METRIC_PREFERENCE_MAX; + router->infinite_assert_metric.route_metric = + PIM_ASSERT_ROUTE_METRIC_MAX; + router->infinite_assert_metric.ip_address = PIMADDR_ANY; + router->rpf_cache_refresh_delay_msec = 50; + router->register_suppress_time = PIM_REGISTER_SUPPRESSION_TIME_DEFAULT; + router->packet_process = PIM_DEFAULT_PACKET_PROCESS; + router->register_probe_time = PIM_REGISTER_PROBE_TIME_DEFAULT; + router->vrf_id = VRF_DEFAULT; + router->pim_mlag_intf_cnt = 0; + router->connected_to_mlag = false; +} + +void pim_router_terminate(void) +{ + XFREE(MTYPE_ROUTER, router); +} + +void pim_init(void) +{ + if (!inet_pton(PIM_AF, PIM_ALL_PIM_ROUTERS, + &qpim_all_pim_routers_addr)) { + flog_err( + EC_LIB_SOCKET, + "%s %s: could not solve %s to group address: errno=%d: %s", + __FILE__, __func__, PIM_ALL_PIM_ROUTERS, errno, + safe_strerror(errno)); + assert(0); + return; + } + + pim_cmd_init(); +} + +void pim_terminate(void) +{ + struct zclient *zclient; + + bfd_protocol_integration_set_shutdown(true); + + /* reverse prefix_list_init */ + prefix_list_add_hook(NULL); + prefix_list_delete_hook(NULL); + prefix_list_reset(); + + pim_vxlan_terminate(); + pim_vrf_terminate(); + + zclient = pim_zebra_zclient_get(); + if (zclient) { + zclient_stop(zclient); + zclient_free(zclient); + } + + pim_free(); + pim_mlag_terminate(); + pim_router_terminate(); + + frr_fini(); +} diff --git a/pimd/pimd.h b/pimd/pimd.h new file mode 100644 index 0000000..3d93189 --- /dev/null +++ b/pimd/pimd.h @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#ifndef PIMD_H +#define PIMD_H + +#include +#include "zebra.h" +#include "libfrr.h" +#include "prefix.h" +#include "vty.h" +#include "plist.h" + +#include "pim_addr.h" +#include "pim_str.h" +#include "pim_memory.h" +#include "pim_assert.h" + +#define PIM_IP_PROTO_IGMP (2) +#define PIM_IP_PROTO_PIM (103) +#define PIM_IGMP_MIN_LEN (8) + +#define PIM_ENFORCE_LOOPFREE_MFC + +/* + * PIM MSG Header Format + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |PIM Ver| Type | Reserved | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +#define PIM_MSG_HEADER_LEN (4) +#define PIM_PIM_MIN_LEN PIM_MSG_HEADER_LEN + +#define PIM_ENCODED_IPV4_UCAST_SIZE (6) +#define PIM_ENCODED_IPV4_GROUP_SIZE (8) +#define PIM_ENCODED_IPV4_SOURCE_SIZE (8) + +/* + * J/P Message Format, Group Header + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Upstream Neighbor Address (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Num groups | Holdtime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Multicast Group Address 1 (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of Joined Sources | Number of Pruned Sources | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +#define PIM_JP_GROUP_HEADER_SIZE \ + (PIM_ENCODED_IPV4_UCAST_SIZE + 1 + 1 + 2 + PIM_ENCODED_IPV4_GROUP_SIZE \ + + 2 + 2) + +#define PIM_PROTO_VERSION (2) + +#define MCAST_ALL_SYSTEMS "224.0.0.1" +#define MCAST_ALL_ROUTERS "224.0.0.2" +#define MCAST_ALL_PIM_ROUTERS "224.0.0.13" +#define MCAST_ALL_IGMP_ROUTERS "224.0.0.22" + +#define PIM_FORCE_BOOLEAN(expr) ((expr) != 0) + +#define PIM_NET_INADDR_ANY (htonl(INADDR_ANY)) + +#define PIM_MASK_PIM_EVENTS (1 << 0) +#define PIM_MASK_PIM_EVENTS_DETAIL (1 << 1) +#define PIM_MASK_PIM_PACKETS (1 << 2) +#define PIM_MASK_PIM_PACKETDUMP_SEND (1 << 3) +#define PIM_MASK_PIM_PACKETDUMP_RECV (1 << 4) +#define PIM_MASK_PIM_TRACE (1 << 5) +#define PIM_MASK_PIM_TRACE_DETAIL (1 << 6) +#define PIM_MASK_GM_EVENTS (1 << 7) +#define PIM_MASK_GM_PACKETS (1 << 8) +#define PIM_MASK_GM_TRACE (1 << 9) +#define PIM_MASK_GM_TRACE_DETAIL (1 << 10) +#define PIM_MASK_ZEBRA (1 << 11) +#define PIM_MASK_SSMPINGD (1 << 12) +#define PIM_MASK_MROUTE (1 << 13) +#define PIM_MASK_MROUTE_DETAIL (1 << 14) +#define PIM_MASK_PIM_HELLO (1 << 15) +#define PIM_MASK_PIM_J_P (1 << 16) +#define PIM_MASK_STATIC (1 << 17) +#define PIM_MASK_PIM_REG (1 << 18) +#define PIM_MASK_MSDP_EVENTS (1 << 19) +#define PIM_MASK_MSDP_PACKETS (1 << 20) +#define PIM_MASK_MSDP_INTERNAL (1 << 21) +#define PIM_MASK_PIM_NHT (1 << 22) +#define PIM_MASK_PIM_NHT_DETAIL (1 << 23) +#define PIM_MASK_PIM_NHT_RP (1 << 24) +#define PIM_MASK_MTRACE (1 << 25) +#define PIM_MASK_VXLAN (1 << 26) +#define PIM_MASK_BSM_PROC (1 << 27) +#define PIM_MASK_MLAG (1 << 28) +/* Remember 32 bits!!! */ + +/* PIM error codes */ +#define PIM_SUCCESS 0 +#define PIM_GROUP_BAD_ADDRESS -2 +#define PIM_GROUP_OVERLAP -3 +#define PIM_GROUP_PFXLIST_OVERLAP -4 +#define PIM_RP_BAD_ADDRESS -5 +#define PIM_RP_NO_PATH -6 +#define PIM_RP_NOT_FOUND -7 +#define PIM_RP_PFXLIST_IN_USE -8 +#define PIM_IFACE_NOT_FOUND -9 +#define PIM_UPDATE_SOURCE_DUP -10 +#define PIM_GROUP_BAD_ADDR_MASK_COMBO -11 + +extern const char *const PIM_ALL_SYSTEMS; +extern const char *const PIM_ALL_ROUTERS; +extern const char *const PIM_ALL_PIM_ROUTERS; +extern const char *const PIM_ALL_IGMP_ROUTERS; + +extern struct zebra_privs_t pimd_privs; +extern pim_addr qpim_all_pim_routers_addr; +extern uint8_t qpim_ecmp_enable; +extern uint8_t qpim_ecmp_rebalance_enable; + +#define PIM_DEFAULT_PACKET_PROCESS 3 + +#define PIM_JP_HOLDTIME (router->t_periodic * 7 / 2) + +/* + * Register-Stop Timer (RST(S,G)) + * Default values + */ +#define PIM_REGISTER_SUPPRESSION_TIME_DEFAULT (60) +#define PIM_REGISTER_PROBE_TIME_DEFAULT (5) + +#define PIM_DEBUG_PIM_EVENTS (router->debugs & PIM_MASK_PIM_EVENTS) +#define PIM_DEBUG_PIM_EVENTS_DETAIL \ + (router->debugs & (PIM_MASK_PIM_EVENTS_DETAIL | PIM_MASK_PIM_EVENTS)) +#define PIM_DEBUG_PIM_PACKETS (router->debugs & PIM_MASK_PIM_PACKETS) +#define PIM_DEBUG_PIM_PACKETDUMP_SEND \ + (router->debugs & PIM_MASK_PIM_PACKETDUMP_SEND) +#define PIM_DEBUG_PIM_PACKETDUMP_RECV \ + (router->debugs & PIM_MASK_PIM_PACKETDUMP_RECV) +#define PIM_DEBUG_PIM_TRACE \ + (router->debugs & (PIM_MASK_PIM_TRACE | PIM_MASK_PIM_TRACE_DETAIL)) +#define PIM_DEBUG_PIM_TRACE_DETAIL \ + (router->debugs & PIM_MASK_PIM_TRACE_DETAIL) +#define PIM_DEBUG_GM_EVENTS (router->debugs & PIM_MASK_GM_EVENTS) +#define PIM_DEBUG_GM_PACKETS (router->debugs & PIM_MASK_GM_PACKETS) +#define PIM_DEBUG_GM_TRACE \ + (router->debugs & (PIM_MASK_GM_TRACE | PIM_MASK_GM_TRACE_DETAIL)) +#define PIM_DEBUG_GM_TRACE_DETAIL (router->debugs & PIM_MASK_GM_TRACE_DETAIL) +#define PIM_DEBUG_ZEBRA (router->debugs & PIM_MASK_ZEBRA) +#define PIM_DEBUG_MLAG (router->debugs & PIM_MASK_MLAG) +#define PIM_DEBUG_SSMPINGD (router->debugs & PIM_MASK_SSMPINGD) +#define PIM_DEBUG_MROUTE \ + (router->debugs & (PIM_MASK_MROUTE | PIM_MASK_MROUTE_DETAIL)) +#define PIM_DEBUG_MROUTE_DETAIL (router->debugs & PIM_MASK_MROUTE_DETAIL) +#define PIM_DEBUG_PIM_HELLO (router->debugs & PIM_MASK_PIM_HELLO) +#define PIM_DEBUG_PIM_J_P (router->debugs & PIM_MASK_PIM_J_P) +#define PIM_DEBUG_PIM_REG (router->debugs & PIM_MASK_PIM_REG) +#define PIM_DEBUG_STATIC (router->debugs & PIM_MASK_STATIC) +#define PIM_DEBUG_MSDP_EVENTS (router->debugs & PIM_MASK_MSDP_EVENTS) +#define PIM_DEBUG_MSDP_PACKETS (router->debugs & PIM_MASK_MSDP_PACKETS) +#define PIM_DEBUG_MSDP_INTERNAL (router->debugs & PIM_MASK_MSDP_INTERNAL) +#define PIM_DEBUG_PIM_NHT (router->debugs & PIM_MASK_PIM_NHT) +#define PIM_DEBUG_PIM_NHT_DETAIL (router->debugs & PIM_MASK_PIM_NHT_DETAIL) +#define PIM_DEBUG_PIM_NHT_RP (router->debugs & PIM_MASK_PIM_NHT_RP) +#define PIM_DEBUG_MTRACE (router->debugs & PIM_MASK_MTRACE) +#define PIM_DEBUG_VXLAN (router->debugs & PIM_MASK_VXLAN) +#define PIM_DEBUG_BSM (router->debugs & PIM_MASK_BSM_PROC) + +#define PIM_DEBUG_EVENTS \ + (router->debugs & (PIM_MASK_PIM_EVENTS | PIM_MASK_GM_EVENTS | \ + PIM_MASK_MSDP_EVENTS | PIM_MASK_BSM_PROC)) +#define PIM_DEBUG_PACKETS \ + (router->debugs & \ + (PIM_MASK_PIM_PACKETS | PIM_MASK_GM_PACKETS | PIM_MASK_MSDP_PACKETS)) +#define PIM_DEBUG_TRACE \ + (router->debugs & (PIM_MASK_PIM_TRACE | PIM_MASK_GM_TRACE)) + +#define PIM_DO_DEBUG_PIM_EVENTS (router->debugs |= PIM_MASK_PIM_EVENTS) +#define PIM_DO_DEBUG_PIM_PACKETS (router->debugs |= PIM_MASK_PIM_PACKETS) +#define PIM_DO_DEBUG_PIM_PACKETDUMP_SEND \ + (router->debugs |= PIM_MASK_PIM_PACKETDUMP_SEND) +#define PIM_DO_DEBUG_PIM_PACKETDUMP_RECV \ + (router->debugs |= PIM_MASK_PIM_PACKETDUMP_RECV) +#define PIM_DO_DEBUG_PIM_TRACE (router->debugs |= PIM_MASK_PIM_TRACE) +#define PIM_DO_DEBUG_PIM_TRACE_DETAIL \ + (router->debugs |= PIM_MASK_PIM_TRACE_DETAIL) +#define PIM_DO_DEBUG_GM_EVENTS (router->debugs |= PIM_MASK_GM_EVENTS) +#define PIM_DO_DEBUG_GM_PACKETS (router->debugs |= PIM_MASK_GM_PACKETS) +#define PIM_DO_DEBUG_GM_TRACE (router->debugs |= PIM_MASK_GM_TRACE) +#define PIM_DO_DEBUG_GM_TRACE_DETAIL \ + (router->debugs |= PIM_MASK_GM_TRACE_DETAIL) +#define PIM_DO_DEBUG_ZEBRA (router->debugs |= PIM_MASK_ZEBRA) +#define PIM_DO_DEBUG_MLAG (router->debugs |= PIM_MASK_MLAG) +#define PIM_DO_DEBUG_SSMPINGD (router->debugs |= PIM_MASK_SSMPINGD) +#define PIM_DO_DEBUG_MROUTE (router->debugs |= PIM_MASK_MROUTE) +#define PIM_DO_DEBUG_MROUTE_DETAIL (router->debugs |= PIM_MASK_MROUTE_DETAIL) +#define PIM_DO_DEBUG_BSM (router->debugs |= PIM_MASK_BSM_PROC) +#define PIM_DO_DEBUG_PIM_HELLO (router->debugs |= PIM_MASK_PIM_HELLO) +#define PIM_DO_DEBUG_PIM_J_P (router->debugs |= PIM_MASK_PIM_J_P) +#define PIM_DO_DEBUG_PIM_REG (router->debugs |= PIM_MASK_PIM_REG) +#define PIM_DO_DEBUG_STATIC (router->debugs |= PIM_MASK_STATIC) +#define PIM_DO_DEBUG_MSDP_EVENTS (router->debugs |= PIM_MASK_MSDP_EVENTS) +#define PIM_DO_DEBUG_MSDP_PACKETS (router->debugs |= PIM_MASK_MSDP_PACKETS) +#define PIM_DO_DEBUG_MSDP_INTERNAL (router->debugs |= PIM_MASK_MSDP_INTERNAL) +#define PIM_DO_DEBUG_PIM_NHT (router->debugs |= PIM_MASK_PIM_NHT) +#define PIM_DO_DEBUG_PIM_NHT_DETAIL (router->debugs |= PIM_MASK_PIM_NHT_DETAIL) +#define PIM_DO_DEBUG_PIM_NHT_RP (router->debugs |= PIM_MASK_PIM_NHT_RP) +#define PIM_DO_DEBUG_MTRACE (router->debugs |= PIM_MASK_MTRACE) +#define PIM_DO_DEBUG_VXLAN (router->debugs |= PIM_MASK_VXLAN) + +#define PIM_DONT_DEBUG_PIM_EVENTS (router->debugs &= ~PIM_MASK_PIM_EVENTS) +#define PIM_DONT_DEBUG_PIM_PACKETS (router->debugs &= ~PIM_MASK_PIM_PACKETS) +#define PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND \ + (router->debugs &= ~PIM_MASK_PIM_PACKETDUMP_SEND) +#define PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV \ + (router->debugs &= ~PIM_MASK_PIM_PACKETDUMP_RECV) +#define PIM_DONT_DEBUG_PIM_TRACE (router->debugs &= ~PIM_MASK_PIM_TRACE) +#define PIM_DONT_DEBUG_PIM_TRACE_DETAIL \ + (router->debugs &= ~PIM_MASK_PIM_TRACE_DETAIL) +#define PIM_DONT_DEBUG_GM_EVENTS (router->debugs &= ~PIM_MASK_GM_EVENTS) +#define PIM_DONT_DEBUG_GM_PACKETS (router->debugs &= ~PIM_MASK_GM_PACKETS) +#define PIM_DONT_DEBUG_GM_TRACE (router->debugs &= ~PIM_MASK_GM_TRACE) +#define PIM_DONT_DEBUG_GM_TRACE_DETAIL \ + (router->debugs &= ~PIM_MASK_GM_TRACE_DETAIL) +#define PIM_DONT_DEBUG_ZEBRA (router->debugs &= ~PIM_MASK_ZEBRA) +#define PIM_DONT_DEBUG_MLAG (router->debugs &= ~PIM_MASK_MLAG) +#define PIM_DONT_DEBUG_SSMPINGD (router->debugs &= ~PIM_MASK_SSMPINGD) +#define PIM_DONT_DEBUG_MROUTE (router->debugs &= ~PIM_MASK_MROUTE) +#define PIM_DONT_DEBUG_MROUTE_DETAIL (router->debugs &= ~PIM_MASK_MROUTE_DETAIL) +#define PIM_DONT_DEBUG_PIM_HELLO (router->debugs &= ~PIM_MASK_PIM_HELLO) +#define PIM_DONT_DEBUG_PIM_J_P (router->debugs &= ~PIM_MASK_PIM_J_P) +#define PIM_DONT_DEBUG_PIM_REG (router->debugs &= ~PIM_MASK_PIM_REG) +#define PIM_DONT_DEBUG_STATIC (router->debugs &= ~PIM_MASK_STATIC) +#define PIM_DONT_DEBUG_MSDP_EVENTS (router->debugs &= ~PIM_MASK_MSDP_EVENTS) +#define PIM_DONT_DEBUG_MSDP_PACKETS (router->debugs &= ~PIM_MASK_MSDP_PACKETS) +#define PIM_DONT_DEBUG_MSDP_INTERNAL (router->debugs &= ~PIM_MASK_MSDP_INTERNAL) +#define PIM_DONT_DEBUG_PIM_NHT (router->debugs &= ~PIM_MASK_PIM_NHT) +#define PIM_DONT_DEBUG_PIM_NHT_DETAIL \ + (router->debugs &= ~PIM_MASK_PIM_NHT_DETAIL) +#define PIM_DONT_DEBUG_PIM_NHT_RP (router->debugs &= ~PIM_MASK_PIM_NHT_RP) +#define PIM_DONT_DEBUG_MTRACE (router->debugs &= ~PIM_MASK_MTRACE) +#define PIM_DONT_DEBUG_VXLAN (router->debugs &= ~PIM_MASK_VXLAN) +#define PIM_DONT_DEBUG_BSM (router->debugs &= ~PIM_MASK_BSM_PROC) + +/* RFC 3376: 8.1. Robustness Variable - Default: 2 for IGMP */ +/* RFC 2710: 7.1. Robustness Variable - Default: 2 for MLD */ +#define GM_DEFAULT_ROBUSTNESS_VARIABLE 2 + +/* RFC 3376: 8.2. Query Interval - Default: 125 seconds for IGMP */ +/* RFC 2710: 7.2. Query Interval - Default: 125 seconds for MLD */ +#define GM_GENERAL_QUERY_INTERVAL 125 + +/* RFC 3376: 8.3. Query Response Interval - Default: 100 deciseconds for IGMP */ +/* RFC 2710: 7.3. Query Response Interval - Default: 100 deciseconds for MLD */ +#define GM_QUERY_MAX_RESPONSE_TIME_DSEC 100 + +/* RFC 3376: 8.8. Last Member Query Interval - Default: 10 deciseconds for IGMP + */ +/* RFC 2710: 7.8. Last Listener Query Interval - Default: 10 deciseconds for MLD + */ +#define GM_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC 10 + +void pim_router_init(void); +void pim_router_terminate(void); + +void pim_init(void); +void pim_terminate(void); + +extern void pim_route_map_init(void); +extern void pim_route_map_terminate(void); +void pim_prefix_list_update(struct prefix_list *plist); + +#endif /* PIMD_H */ diff --git a/pimd/subdir.am b/pimd/subdir.am new file mode 100644 index 0000000..1e787a3 --- /dev/null +++ b/pimd/subdir.am @@ -0,0 +1,180 @@ +# +# pimd +# + +if PIMD +sbin_PROGRAMS += pimd/pimd +bin_PROGRAMS += pimd/mtracebis +noinst_PROGRAMS += pimd/test_igmpv3_join +vtysh_daemons += pimd +vtysh_daemons += pim6d +man8 += $(MANBUILD)/frr-pimd.8 +man8 += $(MANBUILD)/mtracebis.8 +endif + +pim_common = \ + pimd/pim_addr.c \ + pimd/pim_assert.c \ + pimd/pim_bfd.c \ + pimd/pim_bsm.c \ + pimd/pim_cmd_common.c \ + pimd/pim_errors.c \ + pimd/pim_hello.c \ + pimd/pim_iface.c \ + pimd/pim_ifchannel.c \ + pimd/pim_instance.c \ + pimd/pim_int.c \ + pimd/pim_join.c \ + pimd/pim_jp_agg.c \ + pimd/pim_macro.c \ + pimd/pim_memory.c \ + pimd/pim_mroute.c \ + pimd/pim_msg.c \ + pimd/pim_nb.c \ + pimd/pim_nb_config.c \ + pimd/pim_neighbor.c \ + pimd/pim_nht.c \ + pimd/pim_oil.c \ + pimd/pim_pim.c \ + pimd/pim_routemap.c \ + pimd/pim_rp.c \ + pimd/pim_rpf.c \ + pimd/pim_sock.c \ + pimd/pim_ssm.c \ + pimd/pim_ssmpingd.c \ + pimd/pim_static.c \ + pimd/pim_tib.c \ + pimd/pim_time.c \ + pimd/pim_tlv.c \ + pimd/pim_upstream.c \ + pimd/pim_util.c \ + pimd/pim_vty.c \ + pimd/pim_zebra.c \ + pimd/pim_zlookup.c \ + pimd/pim_vxlan.c \ + pimd/pim_register.c \ + pimd/pimd.c \ + # end + +pimd_pimd_SOURCES = \ + $(pim_common) \ + pimd/pim_cmd.c \ + pimd/pim_igmp.c \ + pimd/pim_igmp_mtrace.c \ + pimd/pim_igmp_stats.c \ + pimd/pim_igmpv2.c \ + pimd/pim_igmpv3.c \ + pimd/pim_main.c \ + pimd/pim_mlag.c \ + pimd/pim_msdp.c \ + pimd/pim_msdp_packet.c \ + pimd/pim_msdp_socket.c \ + pimd/pim_signals.c \ + pimd/pim_zpthread.c \ + # end + +nodist_pimd_pimd_SOURCES = \ + yang/frr-pim.yang.c \ + yang/frr-pim-rp.yang.c \ + yang/frr-gmp.yang.c \ + # end + +pimd_pim6d_SOURCES = \ + $(pim_common) \ + pimd/pim6_main.c \ + pimd/pim6_mld.c \ + pimd/pim6_cmd.c \ + # end + +nodist_pimd_pim6d_SOURCES = \ + yang/frr-pim.yang.c \ + yang/frr-pim-rp.yang.c \ + yang/frr-gmp.yang.c \ + # end + +noinst_HEADERS += \ + pimd/pim_addr.h \ + pimd/pim_assert.h \ + pimd/pim_bfd.h \ + pimd/pim_bsm.h \ + pimd/pim_cmd.h \ + pimd/pim_cmd_common.h \ + pimd/pim_errors.h \ + pimd/pim_hello.h \ + pimd/pim_iface.h \ + pimd/pim_ifchannel.h \ + pimd/pim_igmp.h \ + pimd/pim_igmp_join.h \ + pimd/pim_igmp_mtrace.h \ + pimd/pim_igmp_stats.h \ + pimd/pim_igmpv2.h \ + pimd/pim_igmpv3.h \ + pimd/pim_instance.h \ + pimd/pim_int.h \ + pimd/pim_join.h \ + pimd/pim_jp_agg.h \ + pimd/pim_macro.h \ + pimd/pim_memory.h \ + pimd/pim_mlag.h \ + pimd/pim_mroute.h \ + pimd/pim_msdp.h \ + pimd/pim_msdp_packet.h \ + pimd/pim_msdp_socket.h \ + pimd/pim_msg.h \ + pimd/pim_nb.h \ + pimd/pim_neighbor.h \ + pimd/pim_nht.h \ + pimd/pim_oil.h \ + pimd/pim_pim.h \ + pimd/pim_register.h \ + pimd/pim_rp.h \ + pimd/pim_rpf.h \ + pimd/pim_signals.h \ + pimd/pim_sock.h \ + pimd/pim_ssm.h \ + pimd/pim_ssmpingd.h \ + pimd/pim_static.h \ + pimd/pim_str.h \ + pimd/pim_tib.h \ + pimd/pim_time.h \ + pimd/pim_tlv.h \ + pimd/pim_upstream.h \ + pimd/pim_util.h \ + pimd/pim_vty.h \ + pimd/pim_zebra.h \ + pimd/pim_zlookup.h \ + pimd/pim_vxlan.h \ + pimd/pim_vxlan_instance.h \ + pimd/pimd.h \ + pimd/pim6_mld.h \ + pimd/pim6_mld_protocol.h \ + pimd/mtracebis_netlink.h \ + pimd/mtracebis_routeget.h \ + pimd/pim6_cmd.h \ + # end + +clippy_scan += \ + pimd/pim_cmd.c \ + pimd/pim6_cmd.c \ + pimd/pim6_mld.c \ + # end + +pimd_pimd_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4 +pimd_pimd_LDADD = lib/libfrr.la $(LIBCAP) + +if PIM6D +sbin_PROGRAMS += pimd/pim6d +pimd_pim6d_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=6 +pimd_pim6d_LDADD = lib/libfrr.la $(LIBCAP) +endif + +pimd_test_igmpv3_join_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4 +pimd_test_igmpv3_join_LDADD = lib/libfrr.la +pimd_test_igmpv3_join_SOURCES = pimd/test_igmpv3_join.c + +pimd_mtracebis_LDADD = lib/libfrr.la +pimd_mtracebis_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4 +pimd_mtracebis_SOURCES = pimd/mtracebis.c \ + pimd/mtracebis_netlink.c \ + pimd/mtracebis_routeget.c \ + # end diff --git a/pimd/test_igmpv3_join.c b/pimd/test_igmpv3_join.c new file mode 100644 index 0000000..926e453 --- /dev/null +++ b/pimd/test_igmpv3_join.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "if.h" +#include "pim_igmp_join.h" + +const char *prog_name = 0; + +static int iface_solve_index(const char *ifname) +{ + struct if_nameindex *ini; + ifindex_t ifindex = -1; + int i; + + if (!ifname) + return -1; + + ini = if_nameindex(); + if (!ini) { + int err = errno; + fprintf(stderr, + "%s: interface=%s: failure solving index: errno=%d: %s\n", + prog_name, ifname, err, strerror(err)); + errno = err; + return -1; + } + + for (i = 0; ini[i].if_index; ++i) { + if (!strcmp(ini[i].if_name, ifname)) { + ifindex = ini[i].if_index; + break; + } + } + + if_freenameindex(ini); + + return ifindex; +} + +int main(int argc, const char *argv[]) +{ + pim_addr group_addr; + pim_addr source_addr; + const char *ifname; + const char *group; + const char *source; + ifindex_t ifindex; + int result; + int fd; + + prog_name = argv[0]; + + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + fprintf(stderr, + "%s: could not create socket: socket(): errno=%d: %s\n", + prog_name, errno, strerror(errno)); + exit(1); + } + + if (argc != 4) { + fprintf(stderr, + "usage: %s interface group source\n" + "example: %s eth0 232.1.1.1 1.1.1.1\n", + prog_name, prog_name); + exit(1); + } + + ifname = argv[1]; + group = argv[2]; + source = argv[3]; + + ifindex = iface_solve_index(ifname); + if (ifindex < 0) { + fprintf(stderr, "%s: could not find interface: %s\n", prog_name, + ifname); + exit(1); + } + + result = inet_pton(AF_INET, group, &group_addr); + if (result <= 0) { + fprintf(stderr, "%s: bad group address: %s\n", prog_name, + group); + exit(1); + } + + result = inet_pton(AF_INET, source, &source_addr); + if (result <= 0) { + fprintf(stderr, "%s: bad source address: %s\n", prog_name, + source); + exit(1); + } + + result = pim_gm_join_source(fd, ifindex, group_addr, source_addr); + if (result) { + fprintf(stderr, + "%s: setsockopt(fd=%d) failure for IGMP group %s source %s ifindex %d on interface %s: errno=%d: %s\n", + prog_name, fd, group, source, ifindex, ifname, errno, + strerror(errno)); + exit(1); + } + + printf("%s: joined channel (S,G)=(%s,%s) on interface %s\n", prog_name, + source, group, ifname); + + printf("%s: waiting...\n", prog_name); + + if (getchar() == EOF) + fprintf(stderr, "getchar failure\n"); + + close(fd); + + printf("%s: left channel (S,G)=(%s,%s) on interface %s\n", prog_name, + source, group, ifname); + + exit(0); +} diff --git a/pkgsrc/.gitignore b/pkgsrc/.gitignore new file mode 100644 index 0000000..c97f963 --- /dev/null +++ b/pkgsrc/.gitignore @@ -0,0 +1 @@ +*.sh diff --git a/pkgsrc/README.txt b/pkgsrc/README.txt new file mode 100644 index 0000000..b70bb3f --- /dev/null +++ b/pkgsrc/README.txt @@ -0,0 +1,5 @@ +This directory contains files for use with the pkgsrc framework +(http://www.pkgsrc.org) used with NetBSD and other operating systems. +Eventually it will be hooked into automake such that they can be +installed in /usr/pkg/etc/rc.d (via configure option, probably). + diff --git a/pkgsrc/bgpd.sh.in b/pkgsrc/bgpd.sh.in new file mode 100644 index 0000000..a8ba7c6 --- /dev/null +++ b/pkgsrc/bgpd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# bgpd is part of the quagga routing beast +# +# PROVIDE: bgpd +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="bgpd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/eigrpd.sh.in b/pkgsrc/eigrpd.sh.in new file mode 100644 index 0000000..dd8caf8 --- /dev/null +++ b/pkgsrc/eigrpd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# eigrpd is part of the quagga routing beast +# +# PROVIDE: eigrpd +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="eigrpd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/mgmtd.sh.in b/pkgsrc/mgmtd.sh.in new file mode 100644 index 0000000..313633a --- /dev/null +++ b/pkgsrc/mgmtd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# mgmtd is part of the quagga routing beast +# +# PROVIDE: mgmtd +# REQUIRE: none +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="mgmtd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/ospf6d.sh.in b/pkgsrc/ospf6d.sh.in new file mode 100644 index 0000000..c9854ed --- /dev/null +++ b/pkgsrc/ospf6d.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# ospf6d is part of the quagga routing beast +# +# PROVIDE: ospf6d +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="ospf6d" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/ospfd.sh.in b/pkgsrc/ospfd.sh.in new file mode 100644 index 0000000..07dee55 --- /dev/null +++ b/pkgsrc/ospfd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# ospfd is part of the quagga routing beast +# +# PROVIDE: ospfd +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="ospfd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/ripd.sh.in b/pkgsrc/ripd.sh.in new file mode 100644 index 0000000..6c9b580 --- /dev/null +++ b/pkgsrc/ripd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# ripd is part of the quagga routing beast +# +# PROVIDE: ripd +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="ripd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/ripngd.sh.in b/pkgsrc/ripngd.sh.in new file mode 100644 index 0000000..d316d46 --- /dev/null +++ b/pkgsrc/ripngd.sh.in @@ -0,0 +1,44 @@ +#!/bin/sh +# +# ripngd is part of the quagga routing beast +# +# PROVIDE: ripngd +# REQUIRE: zebra +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="ripngd" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +load_rc_config $name +run_rc_command "$1" diff --git a/pkgsrc/zebra.sh.in b/pkgsrc/zebra.sh.in new file mode 100644 index 0000000..7a24106 --- /dev/null +++ b/pkgsrc/zebra.sh.in @@ -0,0 +1,55 @@ +#!/bin/sh +# +# zebra is the head of the quagga routing beast +# +# PROVIDE: zebra +# REQUIRE: NETWORKING +## + +PATH=/sbin:/bin:/usr/sbin:/usr/bin:@prefix@/sbin:@prefix@/bin +export PATH + +if [ -f /etc/rc.subr ] +then + . /etc/rc.subr +fi + +name="zebra" +rcvar=$name +required_files="@e_sysconfdir@/${name}.conf" +command="@prefix@/sbin/${name}" +command_args="-d" + +start_precmd="zebra_precmd" +stop_postcmd="zebra_postcmd" +socket_dir=@localstatedir@ +pidfile="${socket_dir}/${name}.pid" + +zebra_precmd() +{ + mkdir -p "${socket_dir}" + chown quagga.quagga "${socket_dir}" + chmod 750 "${socket_dir}" + rc_flags="$( + set -- $rc_flags + while [ $# -ne 0 ]; do + if [ X"$1" = X-P -o X"$1" = X-A ]; then + break + fi + shift + done + if [ $# -eq 0 ]; then + echo "-P 0" + fi + ) $rc_flags" +} + +zebra_postcmd() +{ + if [ -d "${socketdir}" ]; then + rmdir ${socketdir} + fi +} + +load_rc_config $name +run_rc_command "$1" diff --git a/python/callgraph-dot.py b/python/callgraph-dot.py new file mode 100644 index 0000000..4e58b19 --- /dev/null +++ b/python/callgraph-dot.py @@ -0,0 +1,494 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# callgraph json to graphviz generator for FRR +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import re +import sys +import json + + +class FunctionNode(object): + funcs = {} + + def __init__(self, name): + super().__init__() + FunctionNode.funcs[name] = self + + self.name = name + self.out = [] + self.inb = [] + self.rank = None + self.defined = False + self.defs = [] + + def __repr__(self): + return '<"%s()" rank=%r>' % (self.name, self.rank) + + def define(self, attrs): + self.defined = True + self.defs.append((attrs["filename"], attrs["line"])) + return self + + def add_call(self, called, attrs): + return CallEdge(self, called, attrs) + + def calls(self): + for e in self.out: + yield e.o + + def calld(self): + for e in self.inb: + yield e.i + + def unlink(self, other): + self.out = list([edge for edge in self.out if edge.o != other]) + other.inb = list([edge for edge in other.inb if edge.i != other]) + + @classmethod + def get(cls, name): + if name in cls.funcs: + return cls.funcs[name] + return FunctionNode(name) + + +class CallEdge(object): + def __init__(self, i, o, attrs): + self.i = i + self.o = o + self.is_external = attrs["is_external"] + self.attrs = attrs + + i.out.append(self) + o.inb.append(self) + + def __repr__(self): + return '<"%s()" -> "%s()">' % (self.i.name, self.o.name) + + +def nameclean(n): + if "." in n: + return n.split(".", 1)[0] + return n + + +def calc_rank(queue, direction): + nextq = queue + + if direction == 1: + aggr = max + elem = lambda x: x.calls() + else: + aggr = min + elem = lambda x: x.calld() + + currank = direction + cont = True + + while len(nextq) > 0 and cont: + queue = nextq + nextq = [] + + # sys.stderr.write('rank %d\n' % currank) + + cont = False + + for node in queue: + if not node.defined: + node.rank = 0 + continue + + rank = direction + for other in elem(node): + if other is node: + continue + if other.rank is None: + nextq.append(node) + break + rank = aggr(rank, other.rank + direction) + else: + cont = True + node.rank = rank + + currank += direction + + return nextq + + +class Graph(dict): + class Subgraph(set): + def __init__(self): + super().__init__() + + class NodeGroup(set): + def __init__(self, members): + super().__init__(members) + + class Node(object): + def __init__(self, graph, fn): + super().__init__() + self._fn = fn + self._fns = [fn] + self._graph = graph + self._calls = set() + self._calld = set() + self._group = None + + def __repr__(self): + return '' % (self._fn.name, len(self._fns)) + + def __hash__(self): + return hash(self._fn.name) + + def _finalize(self): + for called in self._fn.calls(): + if called.name == self._fn.name: + continue + if called.name in self._graph: + self._calls.add(self._graph[called.name]) + self._graph[called.name]._calld.add(self) + + def unlink(self, other): + self._calls.remove(other) + other._calld.remove(self) + + @property + def name(self): + return self._fn.name + + def calls(self): + return self._calls + + def calld(self): + return self._calld + + def group(self, members): + assert self in members + + pregroups = [] + for g in [m._group for m in members]: + if g is None: + continue + if g in pregroups: + continue + + assert g <= members + pregroups.append(g) + + if len(pregroups) == 0: + group = self._graph.NodeGroup(members) + self._graph._groups.append(group) + elif len(pregroups) == 1: + group = pregroups[0] + group |= members + else: + for g in pregroups: + self._graph._groups.remove(g) + group = self._graph.NodeGroup(members) + self._graph._groups.append(group) + + for m in members: + m._group = group + return group + + def merge(self, other): + self._fns.extend(other._fns) + self._calls = (self._calls | other._calls) - {self, other} + self._calld = (self._calld | other._calld) - {self, other} + for c in other._calls: + if c == self: + continue + c._calld.remove(other) + c._calld.add(self) + for c in other._calld: + if c == self: + continue + c._calls.remove(other) + c._calls.add(self) + del self._graph[other._fn.name] + + def __init__(self, funcs): + super().__init__() + self._funcs = funcs + for fn in funcs: + self[fn.name] = self.Node(self, fn) + for node in self.values(): + node._finalize() + self._groups = [] + + def automerge(self): + nodes = list(self.values()) + + while len(nodes): + node = nodes.pop(0) + + candidates = {node} + evalset = set(node.calls()) + prevevalset = None + + while prevevalset != evalset: + prevevalset = evalset + evalset = set() + + for evnode in prevevalset: + inbound = set(evnode.calld()) + if inbound <= candidates: + candidates.add(evnode) + evalset |= set(evnode.calls()) - candidates + else: + evalset.add(evnode) + + # if len(candidates) > 1: + # for candidate in candidates: + # if candidate != node: + # #node.merge(candidate) + # if candidate in nodes: + # nodes.remove(candidate) + node.group(candidates) + + for candidate in candidates: + if candidate in nodes: + nodes.remove(candidate) + + def calc_subgraphs(self): + nodes = list(self.values()) + self._subgraphs = [] + up = {} + down = {} + + self._linear_nodes = [] + + while len(nodes): + sys.stderr.write("%d\n" % len(nodes)) + node = nodes.pop(0) + + down[node] = set() + queue = [node] + while len(queue): + now = queue.pop() + down[node].add(now) + for calls in now.calls(): + if calls in down[node]: + continue + queue.append(calls) + + up[node] = set() + queue = [node] + while len(queue): + now = queue.pop() + up[node].add(now) + for calld in now.calld(): + if calld in up[node]: + continue + queue.append(calld) + + common = up[node] & down[node] + + if len(common) == 1: + self._linear_nodes.append(node) + else: + sg = self.Subgraph() + sg |= common + self._subgraphs.append(sg) + for n in common: + if n != node: + nodes.remove(n) + + return self._subgraphs, self._linear_nodes + + +with open(sys.argv[1], "r") as fd: + data = json.load(fd) + +extra_info = { + # zebra - LSP WQ + ("lsp_processq_add", "work_queue_add"): [ + "lsp_process", + "lsp_processq_del", + "lsp_processq_complete", + ], + # zebra - main WQ + ("mq_add_handler", "work_queue_add"): [ + "meta_queue_process", + ], + ("meta_queue_process", "work_queue_add"): [ + "meta_queue_process", + ], + # bgpd - label pool WQ + ("bgp_lp_get", "work_queue_add"): [ + "lp_cbq_docallback", + ], + ("bgp_lp_event_chunk", "work_queue_add"): [ + "lp_cbq_docallback", + ], + ("bgp_lp_event_zebra_up", "work_queue_add"): [ + "lp_cbq_docallback", + ], + # bgpd - main WQ + ("bgp_process", "work_queue_add"): [ + "bgp_process_wq", + "bgp_processq_del", + ], + ("bgp_add_eoiu_mark", "work_queue_add"): [ + "bgp_process_wq", + "bgp_processq_del", + ], + # clear node WQ + ("bgp_clear_route_table", "work_queue_add"): [ + "bgp_clear_route_node", + "bgp_clear_node_queue_del", + "bgp_clear_node_complete", + ], + # rfapi WQs + ("rfapi_close", "work_queue_add"): [ + "rfapi_deferred_close_workfunc", + ], + ("rfapiRibUpdatePendingNode", "work_queue_add"): [ + "rfapiRibDoQueuedCallback", + "rfapiRibQueueItemDelete", + ], +} + + +for func, fdata in data["functions"].items(): + func = nameclean(func) + fnode = FunctionNode.get(func).define(fdata) + + for call in fdata["calls"]: + if call.get("type") in [None, "unnamed", "thread_sched"]: + if call.get("target") is None: + continue + tgt = nameclean(call["target"]) + fnode.add_call(FunctionNode.get(tgt), call) + for fptr in call.get("funcptrs", []): + fnode.add_call(FunctionNode.get(nameclean(fptr)), call) + if tgt == "work_queue_add": + if (func, tgt) not in extra_info: + sys.stderr.write( + "%s:%d:%s(): work_queue_add() not handled\n" + % (call["filename"], call["line"], func) + ) + else: + attrs = dict(call) + attrs.update({"is_external": False, "type": "workqueue"}) + for dst in extra_info[func, tgt]: + fnode.add_call(FunctionNode.get(dst), call) + elif call["type"] == "install_element": + vty_node = FunctionNode.get("VTY_NODE_%d" % call["vty_node"]) + vty_node.add_call(FunctionNode.get(nameclean(call["target"])), call) + elif call["type"] == "hook": + # TODO: edges for hooks from data['hooks'] + pass + +n = FunctionNode.funcs + +# fix some very low end functions cycling back very far to the top +if "peer_free" in n: + n["peer_free"].unlink(n["bgp_timer_set"]) + n["peer_free"].unlink(n["bgp_addpath_set_peer_type"]) +if "bgp_path_info_extra_free" in n: + n["bgp_path_info_extra_free"].rank = 0 + +if "zlog_ref" in n: + n["zlog_ref"].rank = 0 +if "mt_checkalloc" in n: + n["mt_checkalloc"].rank = 0 + +queue = list(FunctionNode.funcs.values()) +queue = calc_rank(queue, 1) +queue = calc_rank(queue, -1) + +sys.stderr.write("%d functions in cyclic set\n" % len(queue)) + +graph = Graph(queue) +graph.automerge() + +gv_nodes = [] +gv_edges = [] + +sys.stderr.write("%d groups after automerge\n" % len(graph._groups)) + + +def is_vnc(n): + return n.startswith("rfapi") or n.startswith("vnc") or ("_vnc_" in n) + + +_vncstyle = ',fillcolor="#ffffcc",style=filled' +cyclic_set_names = set([fn.name for fn in graph.values()]) + +for i, group in enumerate(graph._groups): + if len(group) > 1: + group.num = i + gv_nodes.append("\tsubgraph cluster_%d {" % i) + gv_nodes.append("\t\tcolor=blue;") + for gn in group: + has_cycle_callers = set(gn.calld()) - group + has_ext_callers = ( + set([edge.i.name for edge in gn._fn.inb]) - cyclic_set_names + ) + + style = "" + etext = "" + if is_vnc(gn.name): + style += _vncstyle + if has_cycle_callers: + style += ",color=blue,penwidth=3" + if has_ext_callers: + style += ',fillcolor="#ffeebb",style=filled' + etext += '
(%d other callers)' % ( + len(has_ext_callers) + ) + + gv_nodes.append( + '\t\t"%s" [shape=box,label=<%s%s>%s];' + % (gn.name, "
".join([fn.name for fn in gn._fns]), etext, style) + ) + gv_nodes.append("\t}") + else: + for gn in group: + has_ext_callers = ( + set([edge.i.name for edge in gn._fn.inb]) - cyclic_set_names + ) + + style = "" + etext = "" + if is_vnc(gn.name): + style += _vncstyle + if has_ext_callers: + style += ',fillcolor="#ffeebb",style=filled' + etext += '
(%d other callers)' % ( + len(has_ext_callers) + ) + gv_nodes.append( + '\t"%s" [shape=box,label=<%s%s>%s];' + % (gn.name, "
".join([fn.name for fn in gn._fns]), etext, style) + ) + +edges = set() +for gn in graph.values(): + for calls in gn.calls(): + if gn._group == calls._group: + gv_edges.append( + '\t"%s" -> "%s" [color="#55aa55",style=dashed];' % (gn.name, calls.name) + ) + else: + + def xname(nn): + if len(nn._group) > 1: + return "cluster_%d" % nn._group.num + else: + return nn.name + + tup = xname(gn), calls.name + if tup[0] != tup[1] and tup not in edges: + gv_edges.append('\t"%s" -> "%s" [weight=0.0,w=0.0,color=blue];' % tup) + edges.add(tup) + +with open(sys.argv[2], "w") as fd: + fd.write( + """digraph { + node [fontsize=13,fontname="Fira Sans"]; +%s +}""" + % "\n".join(gv_nodes + [""] + gv_edges) + ) diff --git a/python/clidef.py b/python/clidef.py new file mode 100644 index 0000000..244a820 --- /dev/null +++ b/python/clidef.py @@ -0,0 +1,494 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR CLI preprocessor (DEFPY) +# +# Copyright (C) 2017 David Lamparter for NetDEF, Inc. + +import clippy, traceback, sys, os +from collections import OrderedDict +from functools import reduce +from pprint import pprint +from string import Template +from io import StringIO + +# the various handlers generate output C code for a particular type of +# CLI token, choosing the most useful output C type. + + +class RenderHandler(object): + def __init__(self, token): + pass + + def combine(self, other): + if type(self) == type(other): + return other + return StringHandler(None) + + deref = "" + drop_str = False + canfail = True + canassert = False + + +class StringHandler(RenderHandler): + argtype = "const char *" + decl = Template("const char *$varname = NULL;") + code = Template( + "$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;" + ) + drop_str = True + canfail = False + canassert = True + + +class LongHandler(RenderHandler): + argtype = "long" + decl = Template("long $varname = 0;") + code = Template( + """\ +char *_end; +$varname = strtol(argv[_i]->arg, &_end, 10); +_fail = (_end == argv[_i]->arg) || (*_end != '\\0');""" + ) + + +class AsDotHandler(RenderHandler): + argtype = "as_t" + decl = Template("as_t $varname = 0;") + code = Template("_fail = !asn_str2asn(argv[_i]->arg, &$varname);") + + +# A.B.C.D/M (prefix_ipv4) and +# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a +# struct prefix: + + +class PrefixBase(RenderHandler): + def combine(self, other): + if type(self) == type(other): + return other + if isinstance(other, PrefixBase): + return PrefixGenHandler(None) + return StringHandler(None) + + deref = "&" + + +class Prefix4Handler(PrefixBase): + argtype = "const struct prefix_ipv4 *" + decl = Template("struct prefix_ipv4 $varname = { };") + code = Template("_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);") + + +class Prefix6Handler(PrefixBase): + argtype = "const struct prefix_ipv6 *" + decl = Template("struct prefix_ipv6 $varname = { };") + code = Template("_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);") + + +class PrefixEthHandler(PrefixBase): + argtype = "struct prefix_eth *" + decl = Template("struct prefix_eth $varname = { };") + code = Template("_fail = !str2prefix_eth(argv[_i]->arg, &$varname);") + + +class PrefixGenHandler(PrefixBase): + argtype = "const struct prefix *" + decl = Template("struct prefix $varname = { };") + code = Template("_fail = !str2prefix(argv[_i]->arg, &$varname);") + + +# same for IP addresses. result is union sockunion. +class IPBase(RenderHandler): + def combine(self, other): + if type(self) == type(other): + return other + if type(other) in [IP4Handler, IP6Handler, IPGenHandler]: + return IPGenHandler(None) + return StringHandler(None) + + +class IP4Handler(IPBase): + argtype = "struct in_addr" + decl = Template("struct in_addr $varname = { INADDR_ANY };") + code = Template("_fail = !inet_aton(argv[_i]->arg, &$varname);") + + +class IP6Handler(IPBase): + argtype = "struct in6_addr" + decl = Template("struct in6_addr $varname = {};") + code = Template("_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);") + + +class IPGenHandler(IPBase): + argtype = "const union sockunion *" + decl = Template( + """union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;""" + ) + code = Template( + """\ +if (argv[_i]->text[0] == 'X') { + s__$varname.sa.sa_family = AF_INET6; + _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr); + $varname = &s__$varname; +} else { + s__$varname.sa.sa_family = AF_INET; + _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr); + $varname = &s__$varname; +}""" + ) + canassert = True + + +def mix_handlers(handlers): + def combine(a, b): + if a is None: + return b + return a.combine(b) + + return reduce(combine, handlers, None) + + +handlers = { + "WORD_TKN": StringHandler, + "VARIABLE_TKN": StringHandler, + "RANGE_TKN": LongHandler, + "IPV4_TKN": IP4Handler, + "IPV4_PREFIX_TKN": Prefix4Handler, + "IPV6_TKN": IP6Handler, + "IPV6_PREFIX_TKN": Prefix6Handler, + "MAC_TKN": PrefixEthHandler, + "MAC_PREFIX_TKN": PrefixEthHandler, + "ASNUM_TKN": AsDotHandler, +} + +# core template invoked for each occurence of DEFPY. +# +# the "#if $..." bits are there to keep this template unified into one +# common form, without requiring a more advanced template engine (e.g. +# jinja2) +templ = Template( + """$cond_begin/* $fnname => "$cmddef" */ +DEFUN_CMD_FUNC_DECL($fnname) +#define funcdecl_$fnname static int ${fnname}_magic(\\ + const struct cmd_element *self __attribute__ ((unused)),\\ + struct vty *vty __attribute__ ((unused)),\\ + int argc __attribute__ ((unused)),\\ + struct cmd_token *argv[] __attribute__ ((unused))$argdefs) +funcdecl_$fnname; +DEFUN_CMD_FUNC_TEXT($fnname) +{ +#if $nonempty /* anything to parse? */ + int _i; +#if $canfail /* anything that can fail? */ + unsigned _fail = 0, _failcnt = 0; +#endif +$argdecls + for (_i = 0; _i < argc; _i++) { + if (!argv[_i]->varname) + continue; +#if $canfail /* anything that can fail? */ + _fail = 0; +#endif +$argblocks +#if $canfail /* anything that can fail? */ + if (_fail) + vty_out (vty, "%% invalid input for %s: %s\\n", + argv[_i]->varname, argv[_i]->arg); + _failcnt += _fail; +#endif + } +#if $canfail /* anything that can fail? */ + if (_failcnt) + return CMD_WARNING; +#endif +#endif +$argassert + return ${fnname}_magic(self, vty, argc, argv$arglist); +} +$cond_end +""" +) + +# invoked for each named parameter +argblock = Template( + """ + if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock + $code + }""" +) + + +def get_always_args(token, always_args, args=[], stack=[]): + if token in stack: + return + if token.type == "END_TKN": + for arg in list(always_args): + if arg not in args: + always_args.remove(arg) + return + + stack = stack + [token] + if token.type in handlers and token.varname is not None: + args = args + [token.varname] + for nexttkn in token.next(): + get_always_args(nexttkn, always_args, args, stack) + + +class Macros(dict): + def __init__(self): + super().__init__() + self._loc = {} + + def load(self, filename): + filedata = clippy.parse(filename) + for entry in filedata["data"]: + if entry["type"] != "PREPROC": + continue + self.load_preproc(filename, entry) + + def setup(self, key, val, where="built-in"): + self[key] = val + self._loc[key] = (where, 0) + + def load_preproc(self, filename, entry): + ppdir = entry["line"].lstrip().split(None, 1) + if ppdir[0] != "define" or len(ppdir) != 2: + return + ppdef = ppdir[1].split(None, 1) + name = ppdef[0] + if "(" in name: + return + val = ppdef[1] if len(ppdef) == 2 else "" + + val = val.strip(" \t\n\\") + if self.get(name, val) != val: + sys.stderr.write( + "%s:%d: warning: macro %s redefined!\n" + % ( + filename, + entry["lineno"], + name, + ) + ) + sys.stderr.write( + "%s:%d: note: previously defined here\n" + % ( + self._loc[name][0], + self._loc[name][1], + ) + ) + else: + self[name] = val + self._loc[name] = (filename, entry["lineno"]) + + +def process_file(fn, ofd, dumpfd, all_defun, macros): + errors = 0 + filedata = clippy.parse(fn) + + cond_stack = [] + + for entry in filedata["data"]: + if entry["type"] == "PREPROC": + line = entry["line"].lstrip() + tokens = line.split(maxsplit=1) + line = "#" + line + "\n" + + if not tokens: + continue + + if tokens[0] in ["if", "ifdef", "ifndef"]: + cond_stack.append(line) + elif tokens[0] in ["elif", "else"]: + prev_line = cond_stack.pop(-1) + cond_stack.append(prev_line + line) + elif tokens[0] in ["endif"]: + cond_stack.pop(-1) + elif tokens[0] in ["define"]: + if not cond_stack: + macros.load_preproc(fn, entry) + elif len(cond_stack) == 1 and cond_stack[0] == "#ifdef CLIPPY\n": + macros.load_preproc(fn, entry) + continue + if entry["type"].startswith("DEFPY") or ( + all_defun and entry["type"].startswith("DEFUN") + ): + if len(entry["args"][0]) != 1: + sys.stderr.write( + "%s:%d: DEFPY function name not parseable (%r)\n" + % (fn, entry["lineno"], entry["args"][0]) + ) + errors += 1 + continue + + cmddef = entry["args"][2] + cmddefx = [] + for i in cmddef: + while i in macros: + i = macros[i] + if i.startswith('"') and i.endswith('"'): + cmddefx.append(i[1:-1]) + continue + + sys.stderr.write( + "%s:%d: DEFPY command string not parseable (%r)\n" + % (fn, entry["lineno"], cmddef) + ) + errors += 1 + cmddefx = None + break + if cmddefx is None: + continue + cmddef = "".join([i for i in cmddefx]) + + graph = clippy.Graph(cmddef) + args = OrderedDict() + always_args = set() + for token, depth in clippy.graph_iterate(graph): + if token.type not in handlers: + continue + if token.varname is None: + continue + arg = args.setdefault(token.varname, []) + arg.append(handlers[token.type](token)) + always_args.add(token.varname) + + get_always_args(graph.first(), always_args) + + # print('-' * 76) + # pprint(entry) + # clippy.dump(graph) + # pprint(args) + + params = {"cmddef": cmddef, "fnname": entry["args"][0][0]} + argdefs = [] + argdecls = [] + arglist = [] + argblocks = [] + argassert = [] + doc = [] + canfail = 0 + + def do_add(handler, basename, varname, attr=""): + argdefs.append(",\\\n\t%s %s%s" % (handler.argtype, varname, attr)) + argdecls.append( + "\t%s\n" + % ( + handler.decl.substitute({"varname": varname}).replace( + "\n", "\n\t" + ) + ) + ) + arglist.append(", %s%s" % (handler.deref, varname)) + if basename in always_args and handler.canassert: + argassert.append( + """\tif (!%s) { +\t\tvty_out(vty, "Internal CLI error [%%s]\\n", "%s"); +\t\treturn CMD_WARNING; +\t}\n""" + % (varname, varname) + ) + if attr == "": + at = handler.argtype + if not at.startswith("const "): + at = ". . . " + at + doc.append( + "\t%-26s %s %s" + % (at, "alw" if basename in always_args else "opt", varname) + ) + + for varname in args.keys(): + handler = mix_handlers(args[varname]) + # print(varname, handler) + if handler is None: + continue + do_add(handler, varname, varname) + code = handler.code.substitute({"varname": varname}).replace( + "\n", "\n\t\t\t" + ) + if handler.canfail: + canfail = 1 + strblock = "" + if not handler.drop_str: + do_add( + StringHandler(None), + varname, + "%s_str" % (varname), + " __attribute__ ((unused))", + ) + strblock = "\n\t\t\t%s_str = argv[_i]->arg;" % (varname) + argblocks.append( + argblock.substitute( + {"varname": varname, "strblock": strblock, "code": code} + ) + ) + + if dumpfd is not None: + if len(arglist) > 0: + dumpfd.write('"%s":\n%s\n\n' % (cmddef, "\n".join(doc))) + else: + dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef)) + + params["cond_begin"] = "".join(cond_stack) + params["cond_end"] = "".join(["#endif\n"] * len(cond_stack)) + params["argdefs"] = "".join(argdefs) + params["argdecls"] = "".join(argdecls) + params["arglist"] = "".join(arglist) + params["argblocks"] = "".join(argblocks) + params["canfail"] = canfail + params["nonempty"] = len(argblocks) + params["argassert"] = "".join(argassert) + ofd.write(templ.substitute(params)) + + return errors + + +if __name__ == "__main__": + import argparse + + argp = argparse.ArgumentParser(description="FRR CLI preprocessor in Python") + argp.add_argument( + "--all-defun", + action="store_const", + const=True, + help="process DEFUN() statements in addition to DEFPY()", + ) + argp.add_argument( + "--show", + action="store_const", + const=True, + help="print out list of arguments and types for each definition", + ) + argp.add_argument("-o", type=str, metavar="OUTFILE", help="output C file name") + argp.add_argument("cfile", type=str) + args = argp.parse_args() + + dumpfd = None + if args.o is not None: + ofd = StringIO() + if args.show: + dumpfd = sys.stdout + else: + ofd = sys.stdout + if args.show: + dumpfd = sys.stderr + + basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + macros = Macros() + macros.load("lib/route_types.h") + macros.load(os.path.join(basepath, "lib/command.h")) + macros.load(os.path.join(basepath, "bgpd/bgp_vty.h")) + # sigh :( + macros.setup("PROTO_REDIST_STR", "FRR_REDIST_STR_ISISD") + macros.setup("PROTO_IP_REDIST_STR", "FRR_IP_REDIST_STR_ISISD") + macros.setup("PROTO_IP6_REDIST_STR", "FRR_IP6_REDIST_STR_ISISD") + + errors = process_file(args.cfile, ofd, dumpfd, args.all_defun, macros) + if errors != 0: + sys.exit(1) + + if args.o is not None: + clippy.wrdiff( + args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable] + ) diff --git a/python/clippy/__init__.py b/python/clippy/__init__.py new file mode 100644 index 0000000..668724a --- /dev/null +++ b/python/clippy/__init__.py @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR CLI preprocessor +# +# Copyright (C) 2017 David Lamparter for NetDEF, Inc. + +import os, stat + +try: + from enum import IntFlag as _IntFlag +except ImportError: + # python <3.6 + from enum import IntEnum as _IntFlag # type: ignore + +import _clippy +from _clippy import ( + parse, + Graph, + GraphNode, + CMD_ATTR_YANG, + CMD_ATTR_HIDDEN, + CMD_ATTR_DEPRECATED, + CMD_ATTR_NOSH, + elf_notes, +) + + +frr_top_src = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +) + + +def graph_iterate(graph): + """iterator yielding all nodes of a graph + + nodes arrive in input/definition order, graph circles are avoided. + """ + + queue = [(graph.first(), frozenset(), 0)] + while len(queue) > 0: + node, stop, depth = queue.pop(0) + yield node, depth + + join = node.join() + if join is not None: + queue.insert(0, (join, stop.union(frozenset([node])), depth)) + join = frozenset([join]) + + stop = join or stop + nnext = node.next() + for n in reversed(nnext): + if n not in stop and n is not node: + queue.insert(0, (n, stop, depth + 1)) + + +def dump(graph): + """print out clippy.Graph""" + + for i, depth in graph_iterate(graph): + print("\t%s%s %r" % (" " * (depth * 2), i.type, i.text)) + + +def wrdiff(filename, buf, reffiles=[]): + """write buffer to file if contents changed""" + + expl = "" + if hasattr(buf, "getvalue"): + buf = buf.getvalue() + old = None + try: + old = open(filename, "r").read() + except: + pass + if old == buf: + for reffile in reffiles: + # ensure output timestamp is newer than inputs, for make + reftime = os.stat(reffile)[stat.ST_MTIME] + outtime = os.stat(filename)[stat.ST_MTIME] + if outtime <= reftime: + os.utime(filename, (reftime + 1, reftime + 1)) + # sys.stderr.write('%s unchanged, not written\n' % (filename)) + return + + newname = "%s.new-%d" % (filename, os.getpid()) + with open(newname, "w") as out: + out.write(buf) + os.rename(newname, filename) + + +class CmdAttr(_IntFlag): + YANG = CMD_ATTR_YANG + HIDDEN = CMD_ATTR_HIDDEN + DEPRECATED = CMD_ATTR_DEPRECATED + NOSH = CMD_ATTR_NOSH diff --git a/python/clippy/elf.py b/python/clippy/elf.py new file mode 100644 index 0000000..fd34842 --- /dev/null +++ b/python/clippy/elf.py @@ -0,0 +1,622 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR libelf wrapper +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +""" +Wrapping layer and additional utility around _clippy.ELFFile. + +Essentially, the C bits have the low-level ELF access bits that should be +fast while this has the bits that string everything together (and would've +been a PITA to do in C.) + +Surprisingly - or maybe through proper engineering - this actually works +across architecture, word size and even endianness boundaries. Both the C +module (through GElf_*) and this code (cf. struct.unpack format mangling +in ELFDissectStruct) will take appropriate measures to flip and resize +fields as needed. +""" + +import struct +from collections import OrderedDict +from weakref import WeakValueDictionary + +from _clippy import ELFFile, ELFAccessError + +# +# data access +# + + +class ELFNull(object): + """ + NULL pointer, returned instead of ELFData + """ + + def __init__(self): + self.symname = None + self._dstsect = None + + def __repr__(self): + return "" + + def __hash__(self): + return hash(None) + + def get_string(self): + return None + + +class ELFUnresolved(object): + """ + Reference to an unresolved external symbol, returned instead of ELFData + + :param symname: name of the referenced symbol + :param addend: offset added to the symbol, normally zero + """ + + def __init__(self, symname, addend): + self.addend = addend + self.symname = symname + self._dstsect = None + + def __repr__(self): + return "" % (self.symname, self.addend) + + def __hash__(self): + return hash((self.symname, self.addend)) + + +class ELFData(object): + """ + Actual data somewhere in the ELF file. + + :type dstsect: ELFSubset + :param dstsect: container data area (section or entire file) + :param dstoffs: byte offset into dstsect + :param dstlen: byte size of object, or None if unknown, open-ended or string + """ + + def __init__(self, dstsect, dstoffs, dstlen): + self._dstsect = dstsect + self._dstoffs = dstoffs + self._dstlen = dstlen + self.symname = None + + def __repr__(self): + return "" % ( + self._dstsect.name, + self._dstoffs, + self._dstlen or -1, + ) + + def __hash__(self): + return hash((self._dstsect, self._dstoffs)) + + def get_string(self): + """ + Interpret as C string / null terminated UTF-8 and get the actual text. + """ + try: + return self._dstsect[self._dstoffs : str].decode("UTF-8") + except: + import pdb + + pdb.set_trace() + + def get_data(self, reflen): + """ + Interpret as some structure (and check vs. expected length) + + :param reflen: expected size of the object, compared against actual + size (which is only known in rare cases, mostly when directly + accessing a symbol since symbols have their destination object + size recorded) + """ + if self._dstlen is not None and self._dstlen != reflen: + raise ValueError( + "symbol size mismatch (got %d, expected %d)" % (self._dstlen, reflen) + ) + return self._dstsect[self._dstoffs : self._dstoffs + reflen] + + def offset(self, offs, within_symbol=False): + """ + Get another ELFData at an offset + + :param offs: byte offset, can be negative (e.g. in container_of) + :param within_symbol: retain length information + """ + if self._dstlen is None or not within_symbol: + return ELFData(self._dstsect, self._dstoffs + offs, None) + else: + return ELFData(self._dstsect, self._dstoffs + offs, self._dstlen - offs) + + +# +# dissection data items +# + + +class ELFDissectData(object): + """ + Common bits for ELFDissectStruct and ELFDissectUnion + """ + + def __init__(self): + self._data = None + self.elfclass = None + + def __len__(self): + """ + Used for boolean evaluation, e.g. "if struct: ..." + """ + return not ( + isinstance(self._data, ELFNull) or isinstance(self._data, ELFUnresolved) + ) + + def container_of(self, parent, fieldname): + """ + Assume this struct is embedded in a larger struct and get at the larger + + Python ``self.container_of(a, b)`` = C ``container_of(self, a, b)`` + + :param parent: class (not instance) of the larger struct + :param fieldname: fieldname that refers back to this + :returns: instance of parent, with fieldname set to this object + """ + offset = 0 + if not hasattr(parent, "_efields"): + parent._setup_efields() + + for field in parent._efields[self.elfclass]: + if field[0] == fieldname: + break + spec = field[1] + if spec == "P": + spec = "I" if self.elfclass == 32 else "Q" + offset += struct.calcsize(spec) + else: + raise AttributeError("%r not found in %r.fields" % (fieldname, parent)) + + return parent(self._data.offset(-offset), replace={fieldname: self}) + + +class ELFDissectStruct(ELFDissectData): + """ + Decode and provide access to a struct somewhere in the ELF file + + Handles pointers and strings somewhat nicely. Create a subclass for each + struct that is to be accessed, and give a field list in a "fields" + class-member. + + :param dataptr: ELFData referring to the data bits to decode. + :param parent: where this was instantiated from; only for reference, has + no functional impact. + :param replace: substitute data values for specific fields. Used by + `container_of` to replace the inner struct when creating the outer + one. + + .. attribute:: fields + + List of tuples describing the struct members. Items can be: + - ``('name', ELFDissectData)`` - directly embed another struct + - ``('name', 'I')`` - simple data types; second item for struct.unpack + - ``('name', 'I', None)`` - field to ignore + - ``('name', 'P', str)`` - pointer to string + - ``('name', 'P', ELFDissectData)`` - pointer to another struct + + ``P`` is added as unpack format for pointers (sized appropriately for + the ELF file.) + + Refer to tiabwarfo.py for extracting this from ``pahole``. + + TBD: replace tuples with a class. + + .. attribute:: fieldrename + + Dictionary to rename fields, useful if fields comes from tiabwarfo.py. + """ + + class Pointer(object): + """ + Quick wrapper for pointers to further structs + + This is just here to avoid going into infinite loops when loading + structs that have pointers to each other (e.g. struct xref <--> + struct xrefdata.) The pointer destination is only instantiated when + actually accessed. + """ + + def __init__(self, cls, ptr): + self.cls = cls + self.ptr = ptr + + def __repr__(self): + return "" % (self.cls.__name__, self.ptr) + + def __call__(self): + if isinstance(self.ptr, ELFNull): + return None + return self.cls(self.ptr) + + def __new__(cls, dataptr, parent=None, replace=None): + if dataptr._dstsect is None: + return super().__new__(cls) + + obj = dataptr._dstsect._pointers.get((cls, dataptr)) + if obj is not None: + return obj + obj = super().__new__(cls) + dataptr._dstsect._pointers[(cls, dataptr)] = obj + return obj + + replacements = "lLnN" + + @classmethod + def _preproc_structspec(cls, elfclass, spec): + elfbits = elfclass + + if hasattr(spec, "calcsize"): + spec = "%ds" % (spec.calcsize(elfclass),) + + if elfbits == 32: + repl = ["i", "I"] + else: + repl = ["q", "Q"] + for c in cls.replacements: + spec = spec.replace(c, repl[int(c.isupper())]) + return spec + + @classmethod + def _setup_efields(cls): + cls._efields = {} + cls._esize = {} + for elfclass in [32, 64]: + cls._efields[elfclass] = [] + size = 0 + for f in cls.fields: + newf = (f[0], cls._preproc_structspec(elfclass, f[1])) + f[2:] + cls._efields[elfclass].append(newf) + size += struct.calcsize(newf[1]) + cls._esize[elfclass] = size + + def __init__(self, dataptr, parent=None, replace=None): + if not hasattr(self.__class__, "_efields"): + self._setup_efields() + + self._fdata = None + self._data = dataptr + self._parent = parent + self.symname = dataptr.symname + if isinstance(dataptr, ELFNull) or isinstance(dataptr, ELFUnresolved): + self._fdata = {} + return + + self._elfsect = dataptr._dstsect + self.elfclass = self._elfsect._elffile.elfclass + self.offset = dataptr._dstoffs + + pspecl = [f[1] for f in self._efields[self.elfclass]] + + # need to correlate output from struct.unpack with extra metadata + # about the particular fields, so note down byte offsets (in locs) + # and tuple indices of pointers (in ptrs) + pspec = "" + locs = {} + ptrs = set() + + for idx, spec in enumerate(pspecl): + if spec == "P": + ptrs.add(idx) + spec = self._elfsect.ptrtype + + locs[idx] = struct.calcsize(pspec) + pspec = pspec + spec + + self._total_size = struct.calcsize(pspec) + + def replace_ptrs(v): + idx, val = v[0], v[1] + if idx not in ptrs: + return val + return self._elfsect.pointer(self.offset + locs[idx]) + + data = dataptr.get_data(struct.calcsize(pspec)) + unpacked = struct.unpack(self._elfsect.endian + pspec, data) + unpacked = list(map(replace_ptrs, enumerate(unpacked))) + self._fraw = unpacked + self._fdata = OrderedDict() + replace = replace or {} + + for i, item in enumerate(unpacked): + name = self.fields[i][0] + if name is None: + continue + + if name in replace: + self._fdata[name] = replace[name] + continue + + if isinstance(self.fields[i][1], type) and issubclass( + self.fields[i][1], ELFDissectData + ): + dataobj = self.fields[i][1](dataptr.offset(locs[i]), self) + self._fdata[name] = dataobj + continue + if len(self.fields[i]) == 3: + if self.fields[i][2] == str: + self._fdata[name] = item.get_string() + continue + elif self.fields[i][2] is None: + pass + elif issubclass(self.fields[i][2], ELFDissectData): + cls = self.fields[i][2] + dataobj = self.Pointer(cls, item) + self._fdata[name] = dataobj + continue + + self._fdata[name] = item + + def __getattr__(self, attrname): + if attrname not in self._fdata: + raise AttributeError(attrname) + if isinstance(self._fdata[attrname], self.Pointer): + self._fdata[attrname] = self._fdata[attrname]() + return self._fdata[attrname] + + def __repr__(self): + if not isinstance(self._data, ELFData): + return "<%s: %r>" % (self.__class__.__name__, self._data) + return "<%s: %s>" % ( + self.__class__.__name__, + ", ".join(["%s=%r" % t for t in self._fdata.items()]), + ) + + @classmethod + def calcsize(cls, elfclass): + """ + Sum up byte size of this struct + + Wraps struct.calcsize with some extra features. + """ + if not hasattr(cls, "_efields"): + cls._setup_efields() + + pspec = "".join([f[1] for f in cls._efields[elfclass]]) + + ptrtype = "I" if elfclass == 32 else "Q" + pspec = pspec.replace("P", ptrtype) + + return struct.calcsize(pspec) + + +class ELFDissectUnion(ELFDissectData): + """ + Decode multiple structs in the same place. + + Not currently used (and hence not tested.) Worked at some point but not + needed anymore and may be borked now. Remove this comment when using. + """ + + members = {} + + def __init__(self, dataptr, parent=None): + self._dataptr = dataptr + self._parent = parent + self.members = [] + for name, membercls in self.__class__.members: + item = membercls(dataptr, parent) + self.members.append(item) + setattr(self, name, item) + + def __repr__(self): + return "<%s: %s>" % ( + self.__class__.__name__, + ", ".join([repr(i) for i in self.members]), + ) + + @classmethod + def calcsize(cls, elfclass): + return max([member.calcsize(elfclass) for name, member in cls.members]) + + +# +# wrappers for spans of ELF data +# + + +class ELFSubset(object): + """ + Common abstract base for section-level and file-level access. + """ + + def __init__(self): + super().__init__() + + self.name = None + self._obj = None + self._elffile = None + self.ptrtype = None + self.endian = None + self._pointers = WeakValueDictionary() + + def _wrap_data(self, data, dstsect): + raise NotImplementedError() + + def __hash__(self): + return hash(self.name) + + def __getitem__(self, k): + """ + Read data from slice + + Subscript **must** be a slice; a simple index will not return a byte + but rather throw an exception. Valid slice syntaxes are defined by + the C module: + + - `this[123:456]` - extract specific range + - `this[123:str]` - extract until null byte. The slice stop value is + the `str` type (or, technically, `unicode`.) + """ + if k.start < getattr(self._obj, "len", float("+Inf")): + return self._obj[k] + + real_sect = self._elffile.get_section_addr(self._obj.sh_addr + k.start) + offs = self._obj.sh_addr - real_sect.sh_addr + if k.stop is str: + new_k = slice(k.start + offs, str) + else: + new_k = slice(k.start + offs, k.stop + offs) + return real_sect[new_k] + + def getreloc(self, offset): + """ + Check for a relocation record at the specified offset. + """ + return self._obj.getreloc(offset) + + def iter_data(self, scls, slice_=slice(None)): + """ + Assume an array of structs present at a particular slice and decode + + :param scls: ELFDissectData subclass for the struct + :param slice_: optional range specification + """ + size = scls.calcsize(self._elffile.elfclass) + + offset = slice_.start or 0 + stop = slice_.stop or self._obj.len + if stop < 0: + stop = self._obj.len - stop + + while offset < stop: + yield scls(ELFData(self, offset, size)) + offset += size + + def pointer(self, offset): + """ + Try to dereference a pointer value + + This checks whether there's a relocation at the given offset and + uses that; otherwise (e.g. in a non-PIE executable where the pointer + is already resolved by the linker) the data at the location is used. + + :param offset: byte offset from beginning of section, + or virtual address in file + :returns: ELFData wrapping pointed-to object + """ + + ptrsize = struct.calcsize(self.ptrtype) + data = struct.unpack( + self.endian + self.ptrtype, self[offset : offset + ptrsize] + )[0] + + reloc = self.getreloc(offset) + dstsect = None + if reloc: + # section won't be available in whole-file operation + dstsect = reloc.getsection(data) + addend = reloc.r_addend + + if reloc.relative: + # old-style ELF REL instead of RELA, not well-tested + addend += data + + if reloc.unresolved and reloc.symvalid: + return ELFUnresolved(reloc.symname, addend) + elif reloc.symvalid: + data = addend + reloc.st_value + else: + data = addend + + # 0 could technically be a valid pointer for a shared library, + # since libraries may use 0 as default virtual start address (it'll + # be adjusted on loading) + # That said, if the library starts at 0, that's where the ELF header + # would be so it's still an invalid pointer. + if data == 0 and dstsect == None: + return ELFNull() + + # wrap_data is different between file & section + return self._wrap_data(data, dstsect) + + +class ELFDissectSection(ELFSubset): + """ + Access the contents of an ELF section like ``.text`` or ``.data`` + + :param elfwrap: ELFDissectFile wrapper for the file + :param idx: section index in section header table + :param section: section object from C module + """ + + def __init__(self, elfwrap, idx, section): + super().__init__() + + self._elfwrap = elfwrap + self._elffile = elfwrap._elffile + self._idx = idx + self._section = self._obj = section + self.name = section.name + self.ptrtype = elfwrap.ptrtype + self.endian = elfwrap.endian + + def _wrap_data(self, data, dstsect): + if dstsect is None: + dstsect = self._elfwrap._elffile.get_section_addr(data) + offs = data - dstsect.sh_addr + dstsect = self._elfwrap.get_section(dstsect.idx) + return ELFData(dstsect, offs, None) + + +class ELFDissectFile(ELFSubset): + """ + Access the contents of an ELF file. + + Note that offsets for array subscript and relocation/pointer access are + based on the file's virtual address space and are NOT offsets to the + start of the file on disk! + + (Shared libraries frequently have a virtual address space starting at 0, + but non-PIE executables have an architecture specific default loading + address like 0x400000 on x86. + + :param filename: ELF file to open + """ + + def __init__(self, filename): + super().__init__() + + self.name = filename + self._elffile = self._obj = ELFFile(filename) + self._sections = {} + + self.ptrtype = "I" if self._elffile.elfclass == 32 else "Q" + self.endian = ">" if self._elffile.bigendian else "<" + + @property + def _elfwrap(self): + return self + + def _wrap_data(self, data, dstsect): + return ELFData(self, data, None) + + def get_section(self, secname): + """ + Look up section by name or index + """ + if isinstance(secname, int): + sh_idx = secname + section = self._elffile.get_section_idx(secname) + else: + section = self._elffile.get_section(secname) + + if section is None: + return None + + sh_idx = section.idx + + if sh_idx not in self._sections: + self._sections[sh_idx] = ELFDissectSection(self, sh_idx, section) + + return self._sections[sh_idx] diff --git a/python/clippy/uidhash.py b/python/clippy/uidhash.py new file mode 100644 index 0000000..73570b2 --- /dev/null +++ b/python/clippy/uidhash.py @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# xref unique ID hash calculation +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import struct +from hashlib import sha256 + + +def bititer(data, bits, startbit=True): + """ + just iterate the individual bits out from a bytes object + + if startbit is True, an '1' bit is inserted at the very beginning + goes at a time, starts at LSB. + """ + bitavail, v = 0, 0 + if startbit and len(data) > 0: + v = data.pop(0) + yield (v & ((1 << bits) - 1)) | (1 << (bits - 1)) + bitavail = 9 - bits + v >>= bits - 1 + + while len(data) > 0: + while bitavail < bits: + v |= data.pop(0) << bitavail + bitavail += 8 + yield v & ((1 << bits) - 1) + bitavail -= bits + v >>= bits + + +def base32c(data): + """ + Crockford base32 with extra dashes + """ + chs = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + o = "" + if type(data) == str: + data = [ord(v) for v in data] + else: + data = list(data) + for i, bits in enumerate(bititer(data, 5)): + if i == 5: + o = o + "-" + elif i == 10: + break + o = o + chs[bits] + return o + + +def uidhash(filename, hashstr, hashu32a, hashu32b): + """ + xref Unique ID hash used in FRRouting + """ + filename = "/".join(filename.rsplit("/")[-2:]) + + hdata = filename.encode("UTF-8") + hashstr.encode("UTF-8") + hdata += struct.pack(">II", hashu32a, hashu32b) + i = sha256(hdata).digest() + return base32c(i) diff --git a/python/firstheader.py b/python/firstheader.py new file mode 100644 index 0000000..06e2895 --- /dev/null +++ b/python/firstheader.py @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# check that the first header included in C files is either +# zebra.h or config.h +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import sys +import os +import re +import subprocess +import argparse + +argp = argparse.ArgumentParser(description="include fixer") +argp.add_argument("--autofix", action="store_const", const=True) +argp.add_argument("--warn-empty", action="store_const", const=True) +argp.add_argument("--pipe", action="store_const", const=True) + +include_re = re.compile('^#\s*include\s+["<]([^ ">]+)[">]', re.M) + +ignore = [ + lambda fn: fn.startswith("tools/"), + lambda fn: fn + in [ + "lib/elf_py.c", + ], +] + + +def run(args): + out = [] + + files = subprocess.check_output(["git", "ls-files"]).decode("ASCII") + for fn in files.splitlines(): + if not fn.endswith(".c"): + continue + if max([i(fn) for i in ignore]): + continue + + with open(fn, "r") as fd: + data = fd.read() + + m = include_re.search(data) + if m is None: + if args.warn_empty: + sys.stderr.write("no #include in %s?\n" % (fn)) + continue + if m.group(1) in ["config.h", "zebra.h", "lib/zebra.h"]: + continue + + if args.autofix: + sys.stderr.write("%s: %s - fixing\n" % (fn, m.group(0))) + if fn.startswith("pceplib/"): + insert = '#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif\n\n' + else: + insert = "#include \n\n" + + pos = m.span()[0] + + data = data[:pos] + insert + data[pos:] + with open(fn + ".new", "w") as fd: + fd.write(data) + os.rename(fn + ".new", fn) + else: + sys.stderr.write("%s: %s\n" % (fn, m.group(0))) + out.append(fn) + + if len(out): + if args.pipe: + # for "vim `firstheader.py`" + print("\n".join(out)) + return 1 + return 0 + + +if __name__ == "__main__": + args = argp.parse_args() + sys.exit(run(args)) diff --git a/python/makefile.py b/python/makefile.py new file mode 100644 index 0000000..573871f --- /dev/null +++ b/python/makefile.py @@ -0,0 +1,217 @@ +#!/usr/bin/python3 +# +# FRR extended automake/Makefile functionality helper +# +# This script is executed on/after generating Makefile to add some pieces for +# clippy. + +import sys +import os +import subprocess +import re +import argparse +from string import Template +from makevars import MakeReVars + +argp = argparse.ArgumentParser(description="FRR Makefile extensions") +argp.add_argument( + "--dev-build", + action="store_const", + const=True, + help="run additional developer checks", +) +args = argp.parse_args() + +with open("Makefile", "r") as fd: + before = fd.read() + +mv = MakeReVars(before) + +clippy_scan = mv["clippy_scan"].strip().split() +for clippy_file in clippy_scan: + assert clippy_file.endswith(".c") + +xref_targets = [] +for varname in [ + "bin_PROGRAMS", + "sbin_PROGRAMS", + "lib_LTLIBRARIES", + "module_LTLIBRARIES", +]: + xref_targets.extend(mv[varname].strip().split()) + +# check for files using clippy but not listed in clippy_scan +if args.dev_build: + basepath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + if os.path.exists(os.path.join(basepath, ".git")): + clippy_ref = subprocess.check_output( + [ + "git", + "-C", + basepath, + "grep", + "-l", + "-P", + "^#\s*include.*_clippy.c", + "--", + "**.c", + ] + ).decode("US-ASCII") + + clippy_ref = set(clippy_ref.splitlines()) + missing = clippy_ref - set(clippy_scan) + + if len(missing) > 0: + sys.stderr.write( + 'error: files seem to be using clippy, but not listed in "clippy_scan" in subdir.am:\n\t%s\n' + % ("\n\t".join(sorted(missing))) + ) + sys.exit(1) + +# this additional-dependency rule is stuck onto all compile targets that +# compile a file which uses clippy-generated input, so it has a dependency to +# make that first. +clippydep = Template( + """ +${clippybase}.$$(OBJEXT): ${clippybase}_clippy.c +${clippybase}.lo: ${clippybase}_clippy.c +${clippybase}_clippy.c: $$(CLIPPY_DEPS)""" +) + +# this one is used when one .c file is built multiple times with different +# CFLAGS +clippyauxdep = Template( + """# clippy{ +# auxiliary clippy target +${target}: ${clippybase}_clippy.c +# }clippy""" +) + +lines = before.splitlines() +autoderp = "#AUTODERP# " +out_lines = [] +bcdeps = [] +make_rule_re = re.compile("^([^:\s]+):\s*([^:\s]+)\s*($|\n)") + +while lines: + line = lines.pop(0) + if line.startswith(autoderp): + line = line[len(autoderp) :] + + # allow rerunning on already-clippified Makefile + if line == "# clippy{": + while lines: + line = lines.pop(0) + if line == "# }clippy": + break + continue + + if line.startswith("#"): + out_lines.append(line) + continue + + full_line = line + full_lines = lines[:] + while full_line.endswith("\\"): + full_line = full_line[:-1] + full_lines.pop(0) + + m = make_rule_re.match(full_line) + if m is None: + out_lines.append(line) + continue + + line, lines = full_line, full_lines + + target, dep = m.group(1), m.group(2) + + filename = os.path.basename(target) + if "-" in filename: + # dashes in output filename = building same .c with different CFLAGS + am_name, _ = filename.split("-", 1) + am_name = os.path.join(os.path.dirname(target), am_name) + am_name = am_name.replace("/", "_") + extraflags = " $(%s_CFLAGS)" % (am_name,) + else: + # this path isn't really triggered because automake is using a generic + # .c => .o rule unless CFLAGS are customized for a target + extraflags = "" + + if target.endswith(".lo") or target.endswith(".o"): + if not dep.endswith(".h"): + # LLVM bitcode targets for analysis tools + bcdeps.append("%s.bc: %s" % (target, target)) + bcdeps.append( + "\t$(AM_V_LLVM_BC)$(COMPILE)%s -emit-llvm -c -o $@ %s" + % (extraflags, dep) + ) + if m.group(2) in clippy_scan: + # again - this is only hit for targets with custom CFLAGS, because + # automake uses a generic .c -> .o rule for standard CFLAGS + out_lines.append( + clippyauxdep.substitute(target=m.group(1), clippybase=m.group(2)[:-2]) + ) + + out_lines.append(line) + +# now, cover all the .c files that don't have special build rules +out_lines.append("# clippy{\n# main clippy targets") +for clippy_file in clippy_scan: + out_lines.append(clippydep.substitute(clippybase=clippy_file[:-2])) + +# combine daemon .xref files into frr.xref +out_lines.append("") +xref_targets = [ + target + for target in xref_targets + if target + not in [ + "bgpd/rfp-example/rfptest/rfptest", + "pimd/mtracebis", + "tools/ssd", + "vtysh/vtysh", + ] +] +out_lines.append( + "xrefs = %s" % (" ".join(["%s.xref" % target for target in xref_targets])) +) +out_lines.append("frr.xref: $(xrefs)") +out_lines.append("") + +# analog but slower way to get the same frr.xref +# frr.xref: $(bin_PROGRAMS) $(sbin_PROGRAMS) $(lib_LTLIBRARIES) $(module_LTLIBRARIES) +# $(AM_V_XRELFO) $(CLIPPY) $(top_srcdir)/python/xrelfo.py -o $@ $^ + +# LLVM bitcode link targets creating a .bc file for whole daemon or lib +out_lines.append("") +out_lines.extend(bcdeps) +out_lines.append("") +bc_targets = [] +for varname in [ + "bin_PROGRAMS", + "sbin_PROGRAMS", + "lib_LTLIBRARIES", + "module_LTLIBRARIES", + "noinst_LIBRARIES", +]: + bc_targets.extend(mv[varname].strip().split()) +for target in bc_targets: + amtgt = target.replace("/", "_").replace(".", "_").replace("-", "_") + objs = mv[amtgt + "_OBJECTS"].strip().split() + objs = [obj + ".bc" for obj in objs] + deps = mv.get(amtgt + "_DEPENDENCIES", "").strip().split() + deps = [d + ".bc" for d in deps if d.endswith(".a")] + objs.extend(deps) + out_lines.append("%s.bc: %s" % (target, " ".join(objs))) + out_lines.append("\t$(AM_V_LLVM_LD)$(LLVM_LINK) -o $@ $^") + out_lines.append("") + +out_lines.append("# }clippy") +out_lines.append("") + +after = "\n".join(out_lines) +if after == before: + sys.exit(0) + +with open("Makefile.pyout", "w") as fd: + fd.write(after) +os.rename("Makefile.pyout", "Makefile") diff --git a/python/makevars.py b/python/makevars.py new file mode 100644 index 0000000..951cd34 --- /dev/null +++ b/python/makevars.py @@ -0,0 +1,100 @@ +# +# helper class to grab variables from FRR's Makefile +# + +import os +import subprocess +import re + + +class MakeVarsBase(object): + """ + common code between MakeVars and MakeReVars + """ + + def __init__(self): + self._data = dict() + + def __getitem__(self, k): + if k not in self._data: + self.getvars([k]) + return self._data[k] + + def get(self, k, defval=None): + if k not in self._data: + self.getvars([k]) + return self._data.get(k) or defval + + +class MakeVars(MakeVarsBase): + """ + makevars['FOO_CFLAGS'] gets you "FOO_CFLAGS" from Makefile + + This variant works by invoking make as a subprocess, i.e. Makefile must + be valid and working. (This is sometimes a problem if depfiles have not + been generated.) + """ + + def getvars(self, varlist): + """ + get a batch list of variables from make. faster than individual calls. + """ + rdfd, wrfd = os.pipe() + + shvars = ["shvar-%s" % s for s in varlist] + make = subprocess.Popen( + ["make", "-s", "VARFD=%d" % wrfd] + shvars, pass_fds=[wrfd] + ) + os.close(wrfd) + data = b"" + + rdf = os.fdopen(rdfd, "rb") + while True: + rdata = rdf.read() + if len(rdata) == 0: + break + data += rdata + + del rdf + make.wait() + + data = data.decode("US-ASCII").strip().split("\n") + for row in data: + k, v = row.split("=", 1) + v = v[1:-1] + self._data[k] = v + + +class MakeReVars(MakeVarsBase): + """ + makevars['FOO_CFLAGS'] gets you "FOO_CFLAGS" from Makefile + + This variant works by regexing through Makefile. This means the Makefile + does not need to be fully working, but on the other hand it doesn't support + fancy complicated make expressions. + """ + + var_re = re.compile( + r"^([^=#\n\s]+)[ \t]*=[ \t]*([^#\n]*)(?:#.*)?$", flags=re.MULTILINE + ) + repl_re = re.compile(r"\$(?:([A-Za-z])|\(([^\)]+)\))") + + def __init__(self, maketext): + super(MakeReVars, self).__init__() + self._vars = dict(self.var_re.findall(maketext.replace("\\\n", ""))) + + def replacevar(self, match): + varname = match.group(1) or match.group(2) + return self._vars.get(varname, "") + + def getvars(self, varlist): + for varname in varlist: + if varname not in self._vars: + continue + + val, prevval = self._vars[varname], None + while val != prevval: + prevval = val + val = self.repl_re.sub(self.replacevar, val) + + self._data[varname] = val diff --git a/python/runtests.py b/python/runtests.py new file mode 100644 index 0000000..70deaa3 --- /dev/null +++ b/python/runtests.py @@ -0,0 +1,16 @@ +import pytest +import sys +import os + +try: + import _clippy +except ImportError: + sys.stderr.write( + """these tests need to be run with the _clippy C extension +module available. Try running "clippy runtests.py ...". +""" + ) + sys.exit(1) + +os.chdir(os.path.dirname(os.path.abspath(__file__))) +raise SystemExit(pytest.main(sys.argv[1:])) diff --git a/python/test_xrelfo.py b/python/test_xrelfo.py new file mode 100644 index 0000000..c851bb0 --- /dev/null +++ b/python/test_xrelfo.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# some basic tests for xrelfo & the python ELF machinery +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import sys +import os +import pytest +from pprint import pprint + +root = os.path.dirname(os.path.dirname(__file__)) +sys.path.append(os.path.join(root, "python")) + +import xrelfo +from clippy import elf, uidhash + + +def test_uidhash(): + assert uidhash.uidhash("lib/test_xref.c", "logging call", 3, 0) == "H7KJB-67TBH" + + +def test_xrelfo_other(): + for data in [ + elf.ELFNull(), + elf.ELFUnresolved("somesym", 0), + ]: + + dissect = xrelfo.XrefPtr(data) + print(repr(dissect)) + + with pytest.raises(AttributeError): + dissect.xref + + +def test_xrelfo_obj(): + xrelfo_ = xrelfo.Xrelfo() + edf = xrelfo_.load_elf(os.path.join(root, "lib/.libs/zclient.o"), "zclient.lo") + xrefs = xrelfo_._xrefs + + with pytest.raises(elf.ELFAccessError): + edf[0:4] + + pprint(xrefs[0]) + pprint(xrefs[0]._data) + + +def test_xrelfo_bin(): + xrelfo_ = xrelfo.Xrelfo() + edf = xrelfo_.load_elf(os.path.join(root, "lib/.libs/libfrr.so"), "libfrr.la") + xrefs = xrelfo_._xrefs + + assert edf[0:4] == b"\x7fELF" + + pprint(xrefs[0]) + pprint(xrefs[0]._data) diff --git a/python/tiabwarfo.py b/python/tiabwarfo.py new file mode 100644 index 0000000..da20801 --- /dev/null +++ b/python/tiabwarfo.py @@ -0,0 +1,225 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR DWARF structure definition extractor +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import sys +import os +import subprocess +import re +import argparse +import json + +structs = [ + "xref", + "xref_logmsg", + "xref_threadsched", + "xref_install_element", + "xrefdata", + "xrefdata_logmsg", + "cmd_element", +] + + +def extract(filename="lib/.libs/libfrr.so"): + """ + Convert output from "pahole" to JSON. + + Example pahole output: + $ pahole -C xref lib/.libs/libfrr.so + struct xref { + struct xrefdata * xrefdata; /* 0 8 */ + enum xref_type type; /* 8 4 */ + int line; /* 12 4 */ + const char * file; /* 16 8 */ + const char * func; /* 24 8 */ + + /* size: 32, cachelines: 1, members: 5 */ + /* last cacheline: 32 bytes */ + }; + """ + pahole = subprocess.check_output( + ["pahole", "-C", ",".join(structs), filename] + ).decode("UTF-8") + + struct_re = re.compile(r"^struct ([^ ]+) \{([^\}]+)};", flags=re.M | re.S) + field_re = re.compile( + r"^\s*(?P[^;\(]+)\s+(?P[^;\[\]]+)(?:\[(?P\d+)\])?;\s*\/\*(?P.*)\*\/\s*$" + ) + comment_re = re.compile(r"^\s*\/\*.*\*\/\s*$") + + pastructs = struct_re.findall(pahole) + out = {} + + for sname, data in pastructs: + this = out.setdefault(sname, {}) + fields = this.setdefault("fields", []) + + lines = data.strip().splitlines() + + next_offs = 0 + + for line in lines: + if line.strip() == "": + continue + m = comment_re.match(line) + if m is not None: + continue + + m = field_re.match(line) + if m is not None: + offs, size = m.group("comment").strip().split() + offs = int(offs) + size = int(size) + typ_ = m.group("type").strip() + name = m.group("name") + + if name.startswith("(*"): + # function pointer + typ_ = typ_ + " *" + name = name[2:].split(")")[0] + + data = { + "name": name, + "type": typ_, + # 'offset': offs, + # 'size': size, + } + if m.group("array"): + data["array"] = int(m.group("array")) + + fields.append(data) + if offs != next_offs: + raise ValueError( + "%d padding bytes before struct %s.%s" + % (offs - next_offs, sname, name) + ) + next_offs = offs + size + continue + + raise ValueError("cannot process line: %s" % line) + + return out + + +class FieldApplicator(object): + """ + Fill ELFDissectStruct fields list from pahole/JSON + + Uses the JSON file created by the above code to fill in the struct fields + in subclasses of ELFDissectStruct. + """ + + # only what we really need. add more as needed. + packtypes = { + "int": "i", + "uint8_t": "B", + "uint16_t": "H", + "uint32_t": "I", + "char": "s", + } + + def __init__(self, data): + self.data = data + self.classes = [] + self.clsmap = {} + + def add(self, cls): + self.classes.append(cls) + self.clsmap[cls.struct] = cls + + def resolve(self, cls): + out = [] + # offset = 0 + + fieldrename = getattr(cls, "fieldrename", {}) + + def mkname(n): + return (fieldrename.get(n, n),) + + for field in self.data[cls.struct]["fields"]: + typs = field["type"].split() + typs = [i for i in typs if i not in ["const"]] + + # this will break reuse of xrefstructs.json across 32bit & 64bit + # platforms + + # if field['offset'] != offset: + # assert offset < field['offset'] + # out.append(('_pad', '%ds' % (field['offset'] - offset,))) + + # pretty hacky C types handling, but covers what we need + + ptrlevel = 0 + while typs[-1] == "*": + typs.pop(-1) + ptrlevel += 1 + + if ptrlevel > 0: + packtype = ("P", None) + if ptrlevel == 1: + if typs[0] == "char": + packtype = ("P", str) + elif typs[0] == "struct" and typs[1] in self.clsmap: + packtype = ("P", self.clsmap[typs[1]]) + elif typs[0] == "enum": + packtype = ("I",) + elif typs[0] in self.packtypes: + packtype = (self.packtypes[typs[0]],) + elif typs[0] == "struct": + if typs[1] in self.clsmap: + packtype = (self.clsmap[typs[1]],) + else: + raise ValueError( + "embedded struct %s not in extracted data" % (typs[1],) + ) + else: + raise ValueError( + "cannot decode field %s in struct %s (%s)" + % (cls.struct, field["name"], field["type"]) + ) + + if "array" in field and typs[0] == "char": + packtype = ("%ds" % field["array"],) + out.append(mkname(field["name"]) + packtype) + elif "array" in field: + for i in range(0, field["array"]): + out.append(mkname("%s_%d" % (field["name"], i)) + packtype) + else: + out.append(mkname(field["name"]) + packtype) + + # offset = field['offset'] + field['size'] + + cls.fields = out + + def __call__(self): + for cls in self.classes: + self.resolve(cls) + + +def main(): + argp = argparse.ArgumentParser(description="FRR DWARF structure extractor") + argp.add_argument( + "-o", + dest="output", + type=str, + help="write JSON output", + default="python/xrefstructs.json", + ) + argp.add_argument( + "-i", + dest="input", + type=str, + help="ELF file to read", + default="lib/.libs/libfrr.so", + ) + args = argp.parse_args() + + out = extract(args.input) + with open(args.output + ".tmp", "w") as fd: + json.dump(out, fd, indent=2, sort_keys=True) + os.rename(args.output + ".tmp", args.output) + + +if __name__ == "__main__": + main() diff --git a/python/tsexpand.py b/python/tsexpand.py new file mode 100644 index 0000000..5d60997 --- /dev/null +++ b/python/tsexpand.py @@ -0,0 +1,131 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: MIT +# +# 2024 by David Lamparter +# +# this tool edits an FRR source .c file to expand the typesafe DECLARE_DLIST +# et al. definitions. This can be helpful to get better warnings/errors from +# GCC when something re. a typesafe container is involved. You can also use +# it on .h files. +# The actual expansions created by this tool are written to separate files +# called something like "lib/cspf__visited_tsexpand.h" (for a container named +# "visited") +# +# THIS TOOL EDITS THE FILE IN PLACE. MAKE A BACKUP IF YOU HAVE UNSAVED WORK +# IN PROGRESS (which is likely because you're debugging a typesafe container +# problem!) +# +# The PREDECL_XYZ is irrelevant for this tool, it needs to be run on the file +# that has the DECLARE_XYZ (can be .c or .h) +# +# the lines added by this tool all have /* $ts_expand: remove$ */ at the end +# you can undo the effects of this tool by calling sed: +# +# sed -e '/\$ts_expand: remove\$/ d' -i.orig filename.c + +import os +import sys +import re +import subprocess +import shlex + +decl_re = re.compile( + r"""(?<=\n)[ \t]*DECLARE_(LIST|ATOMLIST|DLIST|HEAP|HASH|(SORTLIST|SKIPLIST|RBTREE|ATOMSORT)_(NON)?UNIQ)\(\s*(?P[^, \t\n]+)\s*,[^)]+\)\s*;[ \t]*\n""" +) +kill_re = re.compile(r"""(?<=\n)[^\n]*/\* \$ts_expand: remove\$ \*/\n""") + +src_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# some files may be compiled with different CPPFLAGS, that's not supported +# here... +cpp = subprocess.check_output( + ["make", "var-CPP", "var-AM_CPPFLAGS", "var-DEFS"], cwd=src_root +) +cpp = shlex.split(cpp.decode("UTF-8")) + + +def process_file(filename): + with open(filename, "r") as ifd: + data = ifd.read() + + data = kill_re.sub("", data) + + before = 0 + + dirname = os.path.dirname(filename) + basename = os.path.basename(filename).removesuffix(".c").removesuffix(".h") + + xname = filename + ".exp" + with open(filename + ".exp", "w") as ofd: + for m in decl_re.finditer(data): + s = m.start() + e = m.end() + ofd.write(data[before:s]) + + # start gcc/clang with some "magic" options to make it expand the + # typesafe macros, but nothing else. + # -P removes the "#line" markers (which are useless because + # everything ends up on one line anyway) + # -D_TYPESAFE_EXPAND_MACROS prevents the system header files + # (stddef.h, stdint.h, etc.) from being included and expanded + # -imacros loads the macro definitions from typesafe.h, but + # doesn't include any of the "plain text" (i.e. prototypes + # and outside-macro struct definitions) from it + # atomlist.h is sufficient because it includes typesafe.h which + # includes typerb.h, that's all of them + p_expand = subprocess.Popen( + cpp + + [ + "-P", + "-D_TYPESAFE_EXPAND_MACROS", + "-imacros", + "lib/atomlist.h", + "-", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + cwd=src_root, + ) + # the output will look like shit, all on one line. format it. + p_format = subprocess.Popen( + ["clang-format", "-"], + stdin=p_expand.stdout, + stdout=subprocess.PIPE, + cwd=src_root, + ) + # pipe between cpp & clang-format needs to be closed + p_expand.stdout.close() + + # ... and finally, write the DECLARE_XYZ statement, and ONLY that + # statements. No headers, no other definitions. + p_expand.stdin.write(data[s:e].encode("UTF-8")) + p_expand.stdin.close() + + odata = b"" + while rd := p_format.stdout.read(): + odata = odata + rd + + p_expand.wait() + p_format.wait() + + # and now that we have the expanded text, write it out, put an + # #include in the .c file, and put "#if 0" around the original + # DECLARE_XYZ statement (otherwise it'll be duplicate...) + newname = os.path.join(dirname, f"{basename}__{m.group('name')}_tsexpand.h") + with open(newname, "wb") as nfd: + nfd.write(odata) + + ofd.write(f'#include "{newname}" /* $ts_expand: remove$ */\n') + ofd.write("#if 0 /* $ts_expand: remove$ */\n") + ofd.write(data[s:e]) + ofd.write("#endif /* $ts_expand: remove$ */\n") + before = e + + ofd.write(data[before:]) + + os.rename(xname, filename) + + +if __name__ == "__main__": + for filename in sys.argv[1:]: + process_file(filename) diff --git a/python/xref2vtysh.py b/python/xref2vtysh.py new file mode 100644 index 0000000..75d9ccf --- /dev/null +++ b/python/xref2vtysh.py @@ -0,0 +1,375 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR xref vtysh command extraction +# +# Copyright (C) 2022 David Lamparter for NetDEF, Inc. + +""" +Generate vtysh_cmd.c from frr .xref file(s). + +This can run either standalone or as part of xrelfo. The latter saves a +non-negligible amount of time (0.5s on average systems, more on e.g. slow ARMs) +since serializing and deserializing JSON is a significant bottleneck in this. +""" + +import sys +import os +import re +import pathlib +import argparse +from collections import defaultdict +import difflib + +import json + +try: + import ujson as json # type: ignore +except ImportError: + pass + +frr_top_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# vtysh needs to know which daemon(s) to send commands to. For lib/, this is +# not quite obvious... + +daemon_flags = { + "lib/libagentx.c": "VTYSH_ISISD|VTYSH_RIPD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ZEBRA", + "lib/filter.c": "VTYSH_ACL_SHOW", + "lib/filter_cli.c": "VTYSH_ACL_CONFIG", + "lib/if.c": "VTYSH_INTERFACE", + "lib/keychain_cli.c": "VTYSH_KEYS", + "lib/mgmt_be_client.c": "VTYSH_MGMT_BACKEND", + "lib/mgmt_fe_client.c": "VTYSH_MGMT_FRONTEND", + "lib/lib_vty.c": "VTYSH_ALL", + "lib/log_vty.c": "VTYSH_ALL", + "lib/nexthop_group.c": "VTYSH_NH_GROUP", + "lib/resolver.c": "VTYSH_NHRPD|VTYSH_BGPD", + "lib/routemap.c": "VTYSH_RMAP_SHOW", + "lib/routemap_cli.c": "VTYSH_RMAP_CONFIG", + "lib/spf_backoff.c": "VTYSH_ISISD", + "lib/event.c": "VTYSH_ALL", + "lib/vrf.c": "VTYSH_VRF", + "lib/vty.c": "VTYSH_ALL", +} + +vtysh_cmd_head = """/* autogenerated file, DO NOT EDIT! */ +#include + +#include "command.h" +#include "linklist.h" + +#include "vtysh/vtysh.h" +""" + +if sys.stderr.isatty(): + _fmt_red = "\033[31m" + _fmt_green = "\033[32m" + _fmt_clear = "\033[m" +else: + _fmt_red = _fmt_green = _fmt_clear = "" + + +def c_escape(text: str) -> str: + """ + Escape string for output into C source code. + + Handles only what's needed here. CLI strings and help text don't contain + weird special characters. + """ + return text.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + + +class NodeDict(defaultdict): + """ + CLI node ID (integer) -> dict of commands in that node. + """ + + nodenames = {} # Dict[int, str] + + def __init__(self): + super().__init__(dict) + + def items_named(self): + for k, v in self.items(): + yield self.nodename(k), v + + @classmethod + def nodename(cls, nodeid: int) -> str: + return cls.nodenames.get(nodeid, str(nodeid)) + + @classmethod + def load_nodenames(cls): + with open(os.path.join(frr_top_src, "lib", "command.h"), "r") as fd: + command_h = fd.read() + + nodes = re.search(r"enum\s+node_type\s+\{(.*?)\}", command_h, re.S) + if nodes is None: + raise RuntimeError( + "regex failed to match on lib/command.h (to get CLI node names)" + ) + + text = nodes.group(1) + text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) + text = re.sub(r"//.*?$", "", text, flags=re.M) + text = text.replace(",", " ") + text = text.split() + + for i, name in enumerate(text): + cls.nodenames[i] = name + + +class CommandEntry: + """ + CLI command definition. + + - one DEFUN creates at most one of these, even if the same command is + installed in multiple CLI nodes (e.g. BGP address-family nodes) + - for each CLI node, commands with the same CLI string are merged. This + is *almost* irrelevant - ospfd & ospf6d define some identical commands + in the route-map node. Those must be merged for things to work + correctly. + """ + + all_defs = [] # List[CommandEntry] + warn_counter = 0 + + def __init__(self, origin, name, spec): + self.origin = origin + self.name = name + self._spec = spec + self._registered = False + + self.cmd = spec["string"] + self._cmd_normalized = self.normalize_cmd(self.cmd) + + self.hidden = "hidden" in spec.get("attrs", []) + self.daemons = self._get_daemons() + + self.doclines = self._spec["doc"].splitlines(keepends=True) + if not self.doclines[-1].endswith("\n"): + self.warn_loc("docstring does not end with \\n") + + def warn_loc(self, wtext, nodename=None): + """ + Print warning with parseable (compiler style) location + + Matching the way compilers emit file/lineno means editors/IDE can + identify / jump to the error location. + """ + + if nodename: + prefix = ": [%s] %s:" % (nodename, self.name) + else: + prefix = ": %s:" % (self.name,) + + for line in wtext.rstrip("\n").split("\n"): + sys.stderr.write( + "%s:%d%s %s\n" + % ( + self._spec["defun"]["file"], + self._spec["defun"]["line"], + prefix, + line, + ) + ) + prefix = "- " + + CommandEntry.warn_counter += 1 + + def _get_daemons(self): + path = pathlib.Path(self.origin) + if path.name == "vtysh": + return {} + + defun_file = os.path.relpath(self._spec["defun"]["file"], frr_top_src) + defun_path = pathlib.Path(defun_file) + + if defun_path.parts[0] != "lib": + if "." not in path.name: + # daemons don't have dots in their filename + return {"VTYSH_" + path.name.upper()} + + # loadable modules - use directory name to determine daemon + return {"VTYSH_" + path.parts[-2].upper()} + + if defun_file in daemon_flags: + return {daemon_flags[defun_file]} + + v6_cmd = "ipv6" in self.name + if defun_file == "lib/plist.c": + if v6_cmd: + return { + "VTYSH_RIPNGD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ZEBRA|VTYSH_PIM6D|VTYSH_BABELD|VTYSH_ISISD|VTYSH_FABRICD" + } + else: + return { + "VTYSH_RIPD|VTYSH_OSPFD|VTYSH_BGPD|VTYSH_ZEBRA|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_ISISD|VTYSH_FABRICD" + } + + if defun_file == "lib/if_rmap.c": + return {"VTYSH_MGMTD"} + + return {} + + def __repr__(self): + return "" % (self.name, self.cmd) + + def register(self): + """Track DEFUNs so each is only output once.""" + if not self._registered: + self.all_defs.append(self) + self._registered = True + return self + + def merge(self, other, nodename): + if self._cmd_normalized != other._cmd_normalized: + self.warn_loc( + "command definition mismatch, first definied as:\n%r" % (self.cmd,), + nodename=nodename, + ) + other.warn_loc("later defined as:\n%r" % (other.cmd,), nodename=nodename) + + if self._spec["doc"] != other._spec["doc"]: + self.warn_loc( + "help string mismatch, first defined here (-)", nodename=nodename + ) + other.warn_loc( + "later defined here (+)\nnote: both commands define %r in same node (%s)" + % (self.cmd, nodename), + nodename=nodename, + ) + + d = difflib.Differ() + for diffline in d.compare(self.doclines, other.doclines): + if diffline.startswith(" "): + continue + if diffline.startswith("+ "): + diffline = _fmt_green + diffline + elif diffline.startswith("- "): + diffline = _fmt_red + diffline + sys.stderr.write("\t" + diffline.rstrip("\n") + _fmt_clear + "\n") + + if self.hidden != other.hidden: + self.warn_loc( + "hidden flag mismatch, first %r here" % (self.hidden,), + nodename=nodename, + ) + other.warn_loc( + "later %r here (+)\nnote: both commands define %r in same node (%s)" + % (other.hidden, self.cmd, nodename), + nodename=nodename, + ) + + # ensure name is deterministic regardless of input DEFUN order + self.name = min([self.name, other.name], key=lambda i: (len(i), i)) + self.daemons.update(other.daemons) + + def get_def(self): + doc = "\n".join(['\t"%s"' % c_escape(line) for line in self.doclines]) + defsh = "DEFSH_HIDDEN" if self.hidden else "DEFSH" + + # make daemon list deterministic + daemons = set() + for daemon in self.daemons: + daemons.update(daemon.split("|")) + daemon_str = "|".join(sorted(daemons)) + + return """ +%s (%s, %s_vtysh, +\t"%s", +%s) +""" % ( + defsh, + daemon_str, + self.name, + c_escape(self.cmd), + doc, + ) + + # accept slightly different command definitions that result in the same command + re_collapse_ws = re.compile(r"\s+") + re_remove_varnames = re.compile(r"\$[a-z][a-z0-9_]*") + + @classmethod + def normalize_cmd(cls, cmd): + cmd = cmd.strip() + cmd = cls.re_collapse_ws.sub(" ", cmd) + cmd = cls.re_remove_varnames.sub("", cmd) + return cmd + + @classmethod + def process(cls, nodes, name, origin, spec): + if "nosh" in spec.get("attrs", []): + return + if origin == "vtysh/vtysh": + return + + if origin == "isisd/fabricd": + # dirty workaround :( + name = "fabricd_" + name + + entry = cls(origin, name, spec) + if not entry.daemons: + return + + for nodedata in spec.get("nodes", []): + node = nodes[nodedata["node"]] + if entry._cmd_normalized not in node: + node[entry._cmd_normalized] = entry.register() + else: + node[entry._cmd_normalized].merge( + entry, nodes.nodename(nodedata["node"]) + ) + + @classmethod + def load(cls, xref): + nodes = NodeDict() + + for cmd_name, origins in xref.get("cli", {}).items(): + for origin, spec in origins.items(): + CommandEntry.process(nodes, cmd_name, origin, spec) + return nodes + + @classmethod + def output_defs(cls, ofd): + for entry in sorted(cls.all_defs, key=lambda i: i.name): + ofd.write(entry.get_def()) + + @classmethod + def output_install(cls, ofd, nodes): + ofd.write("\nvoid vtysh_init_cmd(void)\n{\n") + + for name, items in sorted(nodes.items_named()): + for item in sorted(items.values(), key=lambda i: i.name): + ofd.write("\tinstall_element(%s, &%s_vtysh);\n" % (name, item.name)) + + ofd.write("}\n") + + @classmethod + def run(cls, xref, ofd): + ofd.write(vtysh_cmd_head) + + NodeDict.load_nodenames() + nodes = cls.load(xref) + cls.output_defs(ofd) + cls.output_install(ofd, nodes) + + +def main(): + argp = argparse.ArgumentParser(description="FRR xref to vtysh defs") + argp.add_argument( + "xreffile", metavar="XREFFILE", type=str, help=".xref file to read" + ) + argp.add_argument("-Werror", action="store_const", const=True) + args = argp.parse_args() + + with open(args.xreffile, "r") as fd: + data = json.load(fd) + + CommandEntry.run(data, sys.stdout) + + if args.Werror and CommandEntry.warn_counter: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/python/xrefstructs.json b/python/xrefstructs.json new file mode 100644 index 0000000..25c48c9 --- /dev/null +++ b/python/xrefstructs.json @@ -0,0 +1,140 @@ +{ + "cmd_element": { + "fields": [ + { + "name": "string", + "type": "const char *" + }, + { + "name": "doc", + "type": "const char *" + }, + { + "name": "daemon", + "type": "int" + }, + { + "name": "attr", + "type": "uint32_t" + }, + { + "name": "func", + "type": "int *" + }, + { + "name": "name", + "type": "const char *" + }, + { + "name": "xref", + "type": "struct xref" + } + ] + }, + "xref": { + "fields": [ + { + "name": "xrefdata", + "type": "struct xrefdata *" + }, + { + "name": "type", + "type": "enum xref_type" + }, + { + "name": "line", + "type": "int" + }, + { + "name": "file", + "type": "const char *" + }, + { + "name": "func", + "type": "const char *" + } + ] + }, + "xref_install_element": { + "fields": [ + { + "name": "xref", + "type": "struct xref" + }, + { + "name": "cmd_element", + "type": "const struct cmd_element *" + }, + { + "name": "node_type", + "type": "enum node_type" + } + ] + }, + "xref_logmsg": { + "fields": [ + { + "name": "xref", + "type": "struct xref" + }, + { + "name": "fmtstring", + "type": "const char *" + }, + { + "name": "priority", + "type": "uint32_t" + }, + { + "name": "ec", + "type": "uint32_t" + }, + { + "name": "args", + "type": "const char *" + } + ] + }, + "xref_threadsched": { + "fields": [ + { + "name": "xref", + "type": "struct xref" + }, + { + "name": "funcname", + "type": "const char *" + }, + { + "name": "dest", + "type": "const char *" + }, + { + "name": "thread_type", + "type": "uint32_t" + } + ] + }, + "xrefdata": { + "fields": [ + { + "name": "xref", + "type": "const struct xref *" + }, + { + "array": 16, + "name": "uid", + "type": "char" + }, + { + "name": "hashstr", + "type": "const char *" + }, + { + "array": 2, + "name": "hashu32", + "type": "uint32_t" + } + ] + } +} \ No newline at end of file diff --git a/python/xrelfo.py b/python/xrelfo.py new file mode 100644 index 0000000..07cd740 --- /dev/null +++ b/python/xrelfo.py @@ -0,0 +1,539 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# FRR ELF xref extractor +# +# Copyright (C) 2020 David Lamparter for NetDEF, Inc. + +import sys +import os +import struct +import re +import traceback + +json_dump_args = {} + +try: + import ujson as json + + json_dump_args["escape_forward_slashes"] = False +except ImportError: + import json + +import argparse + +from clippy.uidhash import uidhash +from clippy.elf import * +from clippy import frr_top_src, CmdAttr, elf_notes +from tiabwarfo import FieldApplicator +from xref2vtysh import CommandEntry + +try: + with open(os.path.join(frr_top_src, "python", "xrefstructs.json"), "r") as fd: + xrefstructs = json.load(fd) +except FileNotFoundError: + sys.stderr.write( + """ +The "xrefstructs.json" file (created by running tiabwarfo.py with the pahole +tool available) could not be found. It should be included with the sources. +""" + ) + sys.exit(1) + +# constants, need to be kept in sync manually... + +XREFT_EVENTSCHED = 0x100 +XREFT_LOGMSG = 0x200 +XREFT_DEFUN = 0x300 +XREFT_INSTALL_ELEMENT = 0x301 + +# LOG_* +priovals = {} +prios = ["0", "1", "2", "E", "W", "N", "I", "D"] + + +class XrelfoJson(object): + def dump(self): + pass + + def check(self, wopt): + yield from [] + + def to_dict(self, refs): + pass + + +class Xref(ELFDissectStruct, XrelfoJson): + struct = "xref" + fieldrename = {"type": "typ"} + containers = {} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._container = None + if self.xrefdata: + self.xrefdata.ref_from(self, self.typ) + + def container(self): + if self._container is None: + if self.typ in self.containers: + self._container = self.container_of(self.containers[self.typ], "xref") + return self._container + + def check(self, *args, **kwargs): + if self._container: + yield from self._container.check(*args, **kwargs) + + +class Xrefdata(ELFDissectStruct): + struct = "xrefdata" + + # uid is all zeroes in the data loaded from ELF + fieldrename = {"uid": "_uid"} + + def ref_from(self, xref, typ): + self.xref = xref + + @property + def uid(self): + if self.hashstr is None: + return None + return uidhash(self.xref.file, self.hashstr, self.hashu32_0, self.hashu32_1) + + +class XrefPtr(ELFDissectStruct): + fields = [ + ("xref", "P", Xref), + ] + + +class XrefThreadSched(ELFDissectStruct, XrelfoJson): + struct = "xref_threadsched" + + +Xref.containers[XREFT_EVENTSCHED] = XrefThreadSched + + +class XrefLogmsg(ELFDissectStruct, XrelfoJson): + struct = "xref_logmsg" + + def _warn_fmt(self, text): + lines = text.split("\n") + yield ( + (self.xref.file, self.xref.line), + "%s:%d: %s (in %s())%s\n" + % ( + self.xref.file, + self.xref.line, + lines[0], + self.xref.func, + "".join(["\n" + l for l in lines[1:]]), + ), + ) + + fmt_regexes = [ + (re.compile(r"([\n\t]+)"), "error: log message contains tab or newline"), + # (re.compile(r'^(\s+)'), 'warning: log message starts with whitespace'), + ( + re.compile(r"^((?:warn(?:ing)?|error):\s*)", re.I), + "warning: log message starts with severity", + ), + ] + arg_regexes = [ + # the (?" if edf._elffile.bigendian else "<" + mem = edf._elffile[note] + if edf._elffile.elfclass == 64: + start, end = struct.unpack(endian + "QQ", mem) + start += note.start + end += note.start + 8 + else: + start, end = struct.unpack(endian + "II", mem) + start += note.start + end += note.start + 4 + + ptrs = edf.iter_data(XrefPtr, slice(start, end)) + + else: + if elf_notes: + self.note_warn = True + sys.stderr.write( + """%s: warning: binary has no FRRouting.XREF note +%s- one of FRR_MODULE_SETUP, FRR_DAEMON_INFO or XREF_SETUP must be used +""" + % (orig_filename, orig_filename) + ) + + xrefarray = edf.get_section("xref_array") + if xrefarray is None: + raise ValueError("file has neither xref note nor xref_array section") + + ptrs = xrefarray.iter_data(XrefPtr) + + for ptr in ptrs: + if ptr.xref is None: + print("NULL xref") + continue + self._xrefs.append(ptr.xref) + + container = ptr.xref.container() + if container is None: + continue + container.to_dict(self) + + return edf + + def load_json(self, fd): + data = json.load(fd) + for uid, items in data["refs"].items(): + myitems = self["refs"].setdefault(uid, []) + for item in items: + if item in myitems: + continue + myitems.append(item) + + for cmd, items in data["cli"].items(): + self["cli"].setdefault(cmd, {}).update(items) + + return data + + def check(self, checks): + for xref in self._xrefs: + yield from xref.check(checks) + + +def main(): + argp = argparse.ArgumentParser(description="FRR xref ELF extractor") + argp.add_argument("-o", dest="output", type=str, help="write JSON output") + argp.add_argument("--out-by-file", type=str, help="write by-file JSON output") + argp.add_argument("-c", dest="vtysh_cmds", type=str, help="write vtysh_cmd.c") + argp.add_argument("-Wlog-format", action="store_const", const=True) + argp.add_argument("-Wlog-args", action="store_const", const=True) + argp.add_argument("-Werror", action="store_const", const=True) + argp.add_argument("--profile", action="store_const", const=True) + argp.add_argument( + "binaries", + metavar="BINARY", + nargs="+", + type=str, + help="files to read (ELF files or libtool objects)", + ) + args = argp.parse_args() + + if args.profile: + import cProfile + + cProfile.runctx("_main(args)", globals(), {"args": args}, sort="cumtime") + else: + _main(args) + + +def _main(args): + errors = 0 + xrelfo = Xrelfo() + + for fn in args.binaries: + try: + xrelfo.load_file(fn) + except: + errors += 1 + sys.stderr.write("while processing %s:\n" % (fn)) + traceback.print_exc() + + if xrelfo.note_warn and args.Werror: + errors += 1 + + for option in dir(args): + if option.startswith("W") and option != "Werror": + checks = sorted(xrelfo.check(args)) + sys.stderr.write("".join([c[-1] for c in checks])) + + if args.Werror and len(checks) > 0: + errors += 1 + break + + refs = xrelfo["refs"] + + counts = {} + for k, v in refs.items(): + strs = set([i["fmtstring"] for i in v]) + if len(strs) != 1: + print("\033[31;1m%s\033[m" % k) + counts[k] = len(v) + + out = xrelfo + outbyfile = {} + for uid, locs in refs.items(): + for loc in locs: + filearray = outbyfile.setdefault(loc["file"], []) + loc = dict(loc) + del loc["file"] + filearray.append(loc) + + for k in outbyfile.keys(): + outbyfile[k] = sorted(outbyfile[k], key=lambda x: x["line"]) + + if errors: + sys.exit(1) + + if args.output: + with open(args.output + ".tmp", "w") as fd: + json.dump(out, fd, indent=2, sort_keys=True, **json_dump_args) + os.rename(args.output + ".tmp", args.output) + + if args.out_by_file: + with open(args.out_by_file + ".tmp", "w") as fd: + json.dump(outbyfile, fd, indent=2, sort_keys=True, **json_dump_args) + os.rename(args.out_by_file + ".tmp", args.out_by_file) + + if args.vtysh_cmds: + with open(args.vtysh_cmds + ".tmp", "w") as fd: + CommandEntry.run(out, fd) + os.rename(args.vtysh_cmds + ".tmp", args.vtysh_cmds) + if args.Werror and CommandEntry.warn_counter: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/qpb/Makefile b/qpb/Makefile new file mode 100644 index 0000000..2237def --- /dev/null +++ b/qpb/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. fpm/libfrr_pb.la +%: ALWAYS + @$(MAKE) -s -C .. fpm/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/qpb/README.txt b/qpb/README.txt new file mode 100644 index 0000000..7a09452 --- /dev/null +++ b/qpb/README.txt @@ -0,0 +1 @@ +Protobuf definitions and code that is applicable to all of Quagga/FRR. diff --git a/qpb/linear_allocator.h b/qpb/linear_allocator.h new file mode 100644 index 0000000..352d3ea --- /dev/null +++ b/qpb/linear_allocator.h @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linear_allocator.h + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +/* + * Header file for the linear allocator. + * + * An allocator that allocates memory by walking down towards the end + * of a buffer. No attempt is made to reuse blocks that are freed + * subsequently. The assumption is that the buffer is big enough to + * cover allocations for a given purpose. + */ +#include +#include +#include +#include + +/* + * Alignment for block allocated by the allocator. Must be a power of 2. + */ +#define LINEAR_ALLOCATOR_ALIGNMENT 8 + +#define LINEAR_ALLOCATOR_ALIGN(value) \ + (((value) + LINEAR_ALLOCATOR_ALIGNMENT - 1) \ + & ~(LINEAR_ALLOCATOR_ALIGNMENT - 1)); + +/* + * linear_allocator_align_ptr + */ +static inline char *linear_allocator_align_ptr(char *ptr) +{ + return (char *)LINEAR_ALLOCATOR_ALIGN((intptr_t)ptr); +} + +typedef struct linear_allocator_t_ { + char *buf; + + /* + * Current location in the buffer. + */ + char *cur; + + /* + * End of buffer. + */ + char *end; + + /* + * Version number of the allocator, this is bumped up when the allocator + * is reset and helps identifies bad frees. + */ + uint32_t version; + + /* + * The number of blocks that are currently allocated. + */ + int num_allocated; +} linear_allocator_t; + +/* + * linear_allocator_block_t + * + * Header structure at the begining of each block. + */ +typedef struct linear_allocator_block_t_ { + uint32_t flags; + + /* + * The version of the allocator when this block was allocated. + */ + uint32_t version; + char data[0]; +} linear_allocator_block_t; + +#define LINEAR_ALLOCATOR_BLOCK_IN_USE 0x01 + +#define LINEAR_ALLOCATOR_HDR_SIZE (sizeof(linear_allocator_block_t)) + +/* + * linear_allocator_block_size + * + * The total amount of space a block will take in the buffer, + * including the size of the header. + */ +static inline size_t linear_allocator_block_size(size_t user_size) +{ + return LINEAR_ALLOCATOR_ALIGN(LINEAR_ALLOCATOR_HDR_SIZE + user_size); +} + +/* + * linear_allocator_ptr_to_block + */ +static inline linear_allocator_block_t *linear_allocator_ptr_to_block(void *ptr) +{ + void *block_ptr; + block_ptr = ((char *)ptr) - offsetof(linear_allocator_block_t, data); + return block_ptr; +} + +/* + * linear_allocator_init + */ +static inline void linear_allocator_init(linear_allocator_t *allocator, + char *buf, size_t buf_len) +{ + memset(allocator, 0, sizeof(*allocator)); + + assert(linear_allocator_align_ptr(buf) == buf); + allocator->buf = buf; + allocator->cur = buf; + allocator->end = buf + buf_len; +} + +/* + * linear_allocator_reset + * + * Prepare an allocator for reuse. + * + * *** NOTE ** This implicitly frees all the blocks in the allocator. + */ +static inline void linear_allocator_reset(linear_allocator_t *allocator) +{ + allocator->num_allocated = 0; + allocator->version++; + allocator->cur = allocator->buf; +} + +/* + * linear_allocator_alloc + */ +static inline void *linear_allocator_alloc(linear_allocator_t *allocator, + size_t user_size) +{ + size_t block_size; + linear_allocator_block_t *block; + + block_size = linear_allocator_block_size(user_size); + + if (allocator->cur + block_size > allocator->end) { + return NULL; + } + + block = (linear_allocator_block_t *)allocator->cur; + allocator->cur += block_size; + + block->flags = LINEAR_ALLOCATOR_BLOCK_IN_USE; + block->version = allocator->version; + allocator->num_allocated++; + return block->data; +} + +/* + * linear_allocator_free + */ +static inline void linear_allocator_free(linear_allocator_t *allocator, + void *ptr) +{ + linear_allocator_block_t *block; + + if (((char *)ptr) < allocator->buf || ((char *)ptr) >= allocator->end) { + assert(0); + return; + } + + block = linear_allocator_ptr_to_block(ptr); + if (block->version != allocator->version) { + assert(0); + return; + } + + block->flags = block->flags & ~LINEAR_ALLOCATOR_BLOCK_IN_USE; + + if (--allocator->num_allocated < 0) { + assert(0); + } +} diff --git a/qpb/qpb.c b/qpb/qpb.c new file mode 100644 index 0000000..6258178 --- /dev/null +++ b/qpb/qpb.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * qpb.c + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +/* + * Main file for the qpb library. + */ + +#include "config.h" +#include "xref.h" + +XREF_SETUP(); diff --git a/qpb/qpb.h b/qpb/qpb.h new file mode 100644 index 0000000..2804614 --- /dev/null +++ b/qpb/qpb.h @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * qpb.h + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +/* + * Main public header file for the quagga protobuf library. + */ + +#ifndef _QPB_H +#define _QPB_H + +#include "nexthop.h" +#include "prefix.h" + +#include "qpb/qpb.pb-c.h" + +#include "qpb/qpb_allocator.h" + +/* + * qpb__address_family__set + */ +#define qpb_address_family_set qpb__address_family__set +static inline int qpb__address_family__set(Qpb__AddressFamily *pb_family, + uint8_t family) +{ + switch (family) { + case AF_INET: + *pb_family = QPB__ADDRESS_FAMILY__IPV4; + return 1; + + case AF_INET6: + *pb_family = QPB__ADDRESS_FAMILY__IPV6; + return 1; + + default: + *pb_family = QPB__ADDRESS_FAMILY__UNKNOWN_AF; + } + + return 0; +} + +/* + * qpb__address_family__get + */ +#define qpb_address_family_get qpb__address_family__get +static inline int qpb__address_family__get(Qpb__AddressFamily pb_family, + uint8_t *family) +{ + + switch (pb_family) { + case QPB__ADDRESS_FAMILY__IPV4: + *family = AF_INET; + return 1; + + case QPB__ADDRESS_FAMILY__IPV6: + *family = AF_INET6; + return 1; + + case QPB__ADDRESS_FAMILY__UNKNOWN_AF: + return 0; + case _QPB__ADDRESS_FAMILY_IS_INT_SIZE: + return 0; + } + + return 0; +} + +/* + * qpb__l3_prefix__create + */ +#define qpb_l3_prefix_create qpb__l3_prefix__create +static inline Qpb__L3Prefix *qpb__l3_prefix__create(qpb_allocator_t *allocator, + struct prefix *p) +{ + Qpb__L3Prefix *prefix; + + prefix = QPB_ALLOC(allocator, typeof(*prefix)); + if (!prefix) { + return NULL; + } + qpb__l3_prefix__init(prefix); + prefix->length = p->prefixlen; + prefix->bytes.len = (p->prefixlen + 7) / 8; + prefix->bytes.data = qpb_alloc(allocator, prefix->bytes.len); + if (!prefix->bytes.data) { + return NULL; + } + + memcpy(prefix->bytes.data, &p->u.prefix, prefix->bytes.len); + + return prefix; +} + +/* + * qpb__l3_prefix__get + */ +#define qpb_l3_prefix_get qpb__l3_prefix__get +static inline int qpb__l3_prefix__get(const Qpb__L3Prefix *pb_prefix, + uint8_t family, struct prefix *prefix) +{ + + switch (family) { + + case AF_INET: + memset((struct prefix_ipv4 *)prefix, 0, + sizeof(struct prefix_ipv4)); + break; + + case AF_INET6: + memset((struct prefix_ipv6 *)prefix, 0, + sizeof(struct prefix_ipv6)); + break; + + default: + memset(prefix, 0, sizeof(*prefix)); + } + + prefix->prefixlen = pb_prefix->length; + prefix->family = family; + memcpy(&prefix->u.prefix, pb_prefix->bytes.data, pb_prefix->bytes.len); + return 1; +} + +/* + * qpb__protocol__set + * + * Translate a quagga route type to a protobuf protocol. + */ +#define qpb_protocol_set qpb__protocol__set +static inline int qpb__protocol__set(Qpb__Protocol *pb_proto, int route_type) +{ + switch (route_type) { + case ZEBRA_ROUTE_KERNEL: + *pb_proto = QPB__PROTOCOL__KERNEL; + break; + + case ZEBRA_ROUTE_CONNECT: + *pb_proto = QPB__PROTOCOL__CONNECTED; + break; + + case ZEBRA_ROUTE_STATIC: + *pb_proto = QPB__PROTOCOL__STATIC; + break; + + case ZEBRA_ROUTE_RIP: + *pb_proto = QPB__PROTOCOL__RIP; + break; + + case ZEBRA_ROUTE_RIPNG: + *pb_proto = QPB__PROTOCOL__RIPNG; + break; + + case ZEBRA_ROUTE_OSPF: + case ZEBRA_ROUTE_OSPF6: + *pb_proto = QPB__PROTOCOL__OSPF; + break; + + case ZEBRA_ROUTE_ISIS: + *pb_proto = QPB__PROTOCOL__ISIS; + break; + + case ZEBRA_ROUTE_BGP: + *pb_proto = QPB__PROTOCOL__BGP; + break; + + case ZEBRA_ROUTE_HSLS: + case ZEBRA_ROUTE_OLSR: + case ZEBRA_ROUTE_MAX: + case ZEBRA_ROUTE_SYSTEM: + default: + *pb_proto = QPB__PROTOCOL__OTHER; + } + + return 1; +} + +/* + * qpb__ipv4_address__create + */ +static inline Qpb__Ipv4Address * +qpb__ipv4_address__create(qpb_allocator_t *allocator, struct in_addr *addr) +{ + Qpb__Ipv4Address *v4; + + v4 = QPB_ALLOC(allocator, typeof(*v4)); + if (!v4) { + return NULL; + } + qpb__ipv4_address__init(v4); + + v4->value = ntohl(addr->s_addr); + return v4; +} + +/* + * qpb__ipv4_address__get + */ +static inline int qpb__ipv4_address__get(const Qpb__Ipv4Address *v4, + struct in_addr *addr) +{ + addr->s_addr = htonl(v4->value); + return 1; +} + +/* + * qpb__ipv6_address__create + */ +static inline Qpb__Ipv6Address * +qpb__ipv6_address__create(qpb_allocator_t *allocator, struct in6_addr *addr) +{ + Qpb__Ipv6Address *v6; + + v6 = QPB_ALLOC(allocator, typeof(*v6)); + if (!v6) + return NULL; + + qpb__ipv6_address__init(v6); + v6->bytes.len = 16; + v6->bytes.data = qpb_alloc(allocator, 16); + if (!v6->bytes.data) + return NULL; + + memcpy(v6->bytes.data, addr->s6_addr, v6->bytes.len); + return v6; +} + +/* + * qpb__ipv6_address__get + * + * Read out information from a protobuf ipv6 address structure. + */ +static inline int qpb__ipv6_address__get(const Qpb__Ipv6Address *v6, + struct in6_addr *addr) +{ + if (v6->bytes.len != 16) + return 0; + + memcpy(addr->s6_addr, v6->bytes.data, v6->bytes.len); + return 1; +} + +/* + * qpb__l3_address__create + */ +#define qpb_l3_address_create qpb__l3_address__create +static inline Qpb__L3Address * +qpb__l3_address__create(qpb_allocator_t *allocator, union g_addr *addr, + uint8_t family) +{ + Qpb__L3Address *l3_addr; + + l3_addr = QPB_ALLOC(allocator, typeof(*l3_addr)); + if (!l3_addr) + return NULL; + + qpb__l3_address__init(l3_addr); + + switch (family) { + + case AF_INET: + l3_addr->v4 = qpb__ipv4_address__create(allocator, &addr->ipv4); + if (!l3_addr->v4) + return NULL; + + break; + + case AF_INET6: + l3_addr->v6 = qpb__ipv6_address__create(allocator, &addr->ipv6); + if (!l3_addr->v6) + return NULL; + + break; + } + return l3_addr; +} + +/* + * qpb__l3_address__get + * + * Read out a gateway address from a protobuf l3 address. + */ +#define qpb_l3_address_get qpb__l3_address__get +static inline int qpb__l3_address__get(const Qpb__L3Address *l3_addr, + uint8_t *family, union g_addr *addr) +{ + if (l3_addr->v4) { + qpb__ipv4_address__get(l3_addr->v4, &addr->ipv4); + *family = AF_INET; + return 1; + } + + if (l3_addr->v6) { + qpb__ipv6_address__get(l3_addr->v6, &addr->ipv6); + *family = AF_INET6; + return 1; + } + + return 0; +} + +/* + * qpb__if_identifier__create + */ +#define qpb_if_identifier_create qpb__if_identifier__create +static inline Qpb__IfIdentifier * +qpb__if_identifier__create(qpb_allocator_t *allocator, uint if_index) +{ + Qpb__IfIdentifier *if_id; + + if_id = QPB_ALLOC(allocator, typeof(*if_id)); + if (!if_id) { + return NULL; + } + qpb__if_identifier__init(if_id); + if_id->has_index = 1; + if_id->index = if_index; + return if_id; +} + +/* + * qpb__if_identifier__get + * + * Get interface name and/or if_index from an if identifier. + */ +#define qpb_if_identifier_get qpb__if_identifier__get +static inline int qpb__if_identifier__get(Qpb__IfIdentifier *if_id, + uint *if_index, char **name) +{ + char *str; + uint ix; + + if (!if_index) + if_index = &ix; + + if (!name) + name = &str; + + if (if_id->has_index) + *if_index = if_id->index; + else + *if_index = 0; + + *name = if_id->name; + return 1; +} + +#endif diff --git a/qpb/qpb.proto b/qpb/qpb.proto new file mode 100644 index 0000000..b095368 --- /dev/null +++ b/qpb/qpb.proto @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: ISC +/* + * qpb.proto + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + * + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear + * in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +syntax = "proto2"; + +/* + * Protobuf definitions pertaining to the Quagga/FRR Protobuf component. + */ +package qpb; + +enum AddressFamily { + UNKNOWN_AF = 0; + IPV4 = 1; // IP version 4 + IPV6 = 2; // IP version 6 +}; + +enum SubAddressFamily { + UNKNOWN_SAF = 0; + UNICAST = 1; + MULTICAST = 2; +}; + +// +// An IP version 4 address, such as 10.1.1.1. +// +message Ipv4Address { + required fixed32 value = 1 ; +}; + +message Ipv6Address { + + // 16 bytes. + required bytes bytes = 1; +}; + +// +// An IP version 4 or IP version 6 address. +// +message L3Address { + optional Ipv4Address v4 = 1; + optional Ipv6Address v6 = 2; +}; + +// +// An IP prefix, such as 10.1/16. +// We use the message below to represent both IPv4 and IPv6 prefixes. +message L3Prefix { + required uint32 length = 1; + required bytes bytes = 2; +}; + +// +// Something that identifies an interface on a machine. It can either +// be a name (for instance, 'eth0') or a number currently. +// +message IfIdentifier { + optional uint32 index = 1; + optional string name = 2; +}; + +enum Protocol { + UNKNOWN_PROTO = 0; + LOCAL = 1; + CONNECTED = 2; + KERNEL = 3; + STATIC = 4; + RIP = 5; + RIPNG = 6; + OSPF = 7; + ISIS = 8; + BGP = 9; + OTHER = 10; +} diff --git a/qpb/qpb_allocator.c b/qpb/qpb_allocator.c new file mode 100644 index 0000000..e2749d6 --- /dev/null +++ b/qpb/qpb_allocator.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * qpb_allocator.c + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "linear_allocator.h" + +#include "qpb_allocator.h" + +/* + * _qpb_alloc + */ +static void *_qpb_alloc(void *allocator_data, size_t size) +{ + return linear_allocator_alloc(allocator_data, size); +} + +/* + * _qpb_free + */ +static void _qpb_free(void *allocator_data, void *ptr) +{ + linear_allocator_free(allocator_data, ptr); +} + +static ProtobufCAllocator allocator_template = {_qpb_alloc, _qpb_free, NULL}; + +/* + * qpb_allocator_init_linear + * + * Initialize qpb_allocator_t with the given linear allocator. + */ +void qpb_allocator_init_linear(qpb_allocator_t *allocator, + linear_allocator_t *linear_allocator) +{ + *allocator = allocator_template; + allocator->allocator_data = linear_allocator; +} diff --git a/qpb/qpb_allocator.h b/qpb/qpb_allocator.h new file mode 100644 index 0000000..ff0e466 --- /dev/null +++ b/qpb/qpb_allocator.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * qpb_allocator.h + * + * @copyright Copyright (C) 2016 Sproute Networks, Inc. + * + * @author Avneesh Sachdev + */ + +/* + * Header file for Quagga/FRR protobuf memory management code. + */ + +#ifndef _QPB_ALLOCATOR_H_ +#define _QPB_ALLOCATOR_H_ + +#include + +struct linear_allocator_t_; + +/* + * Alias for ProtobufCAllocator that is easier on the fingers. + */ +typedef ProtobufCAllocator qpb_allocator_t; + +/* + * qpb_alloc + */ +static inline void *qpb_alloc(qpb_allocator_t *allocator, size_t size) +{ + return allocator->alloc(allocator->allocator_data, size); +} + +/* + * qpb_alloc_ptr_array + * + * Allocate space for the specified number of pointers. + */ +static inline void *qpb_alloc_ptr_array(qpb_allocator_t *allocator, + size_t num_ptrs) +{ + return qpb_alloc(allocator, num_ptrs * sizeof(void *)); +} + +/* + * qpb_free + */ +static inline void qpb_free(qpb_allocator_t *allocator, void *ptr) +{ + allocator->free(allocator->allocator_data, ptr); +} + +/* + * QPB_ALLOC + * + * Convenience macro to reduce the probability of allocating memory of + * incorrect size. It returns enough memory to store the given type, + * and evaluates to an appropriately typed pointer. + */ +#define QPB_ALLOC(allocator, type) (type *)qpb_alloc(allocator, sizeof(type)) + +/* + * Externs. + */ +extern void qpb_allocator_init_linear(qpb_allocator_t *, + struct linear_allocator_t_ *); + +/* + * The following macros are for the common case where a qpb allocator + * is being used alongside a linear allocator that allocates memory + * off of the stack. + */ +#define QPB_DECLARE_STACK_ALLOCATOR(allocator, size) \ + qpb_allocator_t allocator; \ + linear_allocator_t lin_##allocator; \ + char lin_##allocator##_buf[size] + +#define QPB_INIT_STACK_ALLOCATOR(allocator) \ + do { \ + linear_allocator_init(&(lin_##allocator), \ + lin_##allocator##_buf, \ + sizeof(lin_##allocator##_buf)); \ + qpb_allocator_init_linear(&allocator, &(lin_##allocator)); \ + } while (0) + +#define QPB_RESET_STACK_ALLOCATOR(allocator) \ + do { \ + linear_allocator_reset(&(lin_##allocator)); \ + } while (0) + +#endif /* _QPB_ALLOCATOR_H_ */ diff --git a/qpb/subdir.am b/qpb/subdir.am new file mode 100644 index 0000000..21aa84d --- /dev/null +++ b/qpb/subdir.am @@ -0,0 +1,31 @@ +if HAVE_PROTOBUF +lib_LTLIBRARIES += qpb/libfrr_pb.la +endif + +qpb_libfrr_pb_la_CPPFLAGS = $(AM_CPPFLAGS) $(PROTOBUF_C_CFLAGS) +qpb_libfrr_pb_la_LIBADD = $(PROTOBUF_C_LIBS) +qpb_libfrr_pb_la_LDFLAGS = $(LIB_LDFLAGS) -version-info 0:0:0 + +qpb_libfrr_pb_la_SOURCES = \ + qpb/qpb.c \ + qpb/qpb_allocator.c \ + # end + +if HAVE_PROTOBUF +nodist_qpb_libfrr_pb_la_SOURCES = \ + qpb/qpb.pb-c.c \ + # end +endif + +noinst_HEADERS += \ + qpb/linear_allocator.h \ + qpb/qpb.h \ + qpb/qpb_allocator.h \ + # end + +CLEANFILES += \ + qpb/qpb.pb-c.c \ + qpb/qpb.pb-c.h \ + # end + +EXTRA_DIST += qpb/qpb.proto diff --git a/redhat/.gitignore b/redhat/.gitignore new file mode 100644 index 0000000..15804ce --- /dev/null +++ b/redhat/.gitignore @@ -0,0 +1,2 @@ +zebra.spec +frr.spec diff --git a/redhat/frr.logrotate b/redhat/frr.logrotate new file mode 100644 index 0000000..31061e3 --- /dev/null +++ b/redhat/frr.logrotate @@ -0,0 +1,168 @@ +/var/log/frr/frr.log { + notifempty + missingok + postrotate + /bin/kill -HUP `cat /var/run/*syslog*.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/zebra.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/zebra.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/babeld.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/babeld.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/bgpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/bgpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/isisd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/isisd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/ospfd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/ospfd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/ospf6d.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/ospf6d.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/ripd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/ripd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/ripngd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/ripngd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/ldpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/ldpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/nhrpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/nhrpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/eigrpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/eigrpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/bfdd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/bfdd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/fabricd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/fabricd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/pathd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/pathd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/pbrd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/pbrd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/pimd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/pimd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/pim6d.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/pim6d.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/sharpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/sharpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/staticd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/staticd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + +/var/log/frr/vrrpd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/vrrpd.pid 2> /dev/null` 2> /dev/null || true + endscript +} + diff --git a/redhat/frr.pam b/redhat/frr.pam new file mode 100644 index 0000000..a574c5e --- /dev/null +++ b/redhat/frr.pam @@ -0,0 +1,27 @@ +#%PAM-1.0 +# + +##### if running frr as root: +# Only allow root (and possibly wheel) to use this because enable access +# is unrestricted. +auth sufficient pam_permit.so +account sufficient pam_permit.so + +# Uncomment the following line to implicitly trust users in the "wheel" group. +#auth sufficient pam_wheel.so trust use_uid +# Uncomment the following line to require a user to be in the "wheel" group. +#auth required pam_wheel.so use_uid +########################################################### + +# If using frr privileges and with a seperate group for vty access, then +# access can be controlled via the vty access group, and pam can simply +# check for valid user/password, eg: +# +# only allow local users. +#auth required pam_securetty.so +#auth include system-auth +#auth required pam_nologin.so +#account include system-auth +#password include system-auth +#session include system-auth +#session optional pam_console.so diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in new file mode 100644 index 0000000..e979b7f --- /dev/null +++ b/redhat/frr.spec.in @@ -0,0 +1,2050 @@ +# configure options +# +# Some can be overridden on rpmbuild commandline with: +# rpmbuild --define 'variable value' +# (use any value, ie 1 for flag "with_XXXX" definitions) +# +# E.g. rpmbuild --define 'release_rev 02' may be useful if building +# rpms again and again on the same day, so the newer rpms can be installed. +# bumping the number each time. + +#################### FRRouting (FRR) configure options ##################### +# with-feature options +%{!?with_babeld: %global with_babeld 1 } +%{!?with_bfdd: %global with_bfdd 1 } +%{!?with_bgp_vnc: %global with_bgp_vnc 0 } +%{!?with_cumulus: %global with_cumulus 0 } +%{!?with_eigrpd: %global with_eigrpd 1 } +%{!?with_fpm: %global with_fpm 1 } +%{!?with_mgmtd_test_be_client: %global with_mgmtd_test_be_client 0 } +%{!?with_ldpd: %global with_ldpd 1 } +%{!?with_multipath: %global with_multipath 256 } +%{!?with_nhrpd: %global with_nhrpd 1 } +%{!?with_ospfapi: %global with_ospfapi 1 } +%{!?with_ospfclient: %global with_ospfclient 1 } +%{!?with_pam: %global with_pam 0 } +%{!?with_pbrd: %global with_pbrd 1 } +%{!?with_pimd: %global with_pimd 1 } +%{!?with_pim6d: %global with_pim6d 1 } +%{!?with_vrrpd: %global with_vrrpd 1 } +%{!?with_rtadv: %global with_rtadv 1 } +%{!?with_watchfrr: %global with_watchfrr 1 } +%{!?with_pathd: %global with_pathd 1 } + +# user and group +%{!?frr_user: %global frr_user frr } +%{!?vty_group: %global vty_group frrvty } + +# path defines +%define configdir %{_sysconfdir}/%{name} +%define _sbindir /usr/lib/frr +%define zeb_src %{_builddir}/%{name}-%{frrversion} +%define zeb_rh_src %{zeb_src}/redhat +%define zeb_docs %{zeb_src}/doc +%define frr_tools %{zeb_src}/tools + +%if 0%{!?_runstatedir:1} +%define _runstatedir %{_localstatedir}/run +%endif + +############################################################################ + +#### Version String tweak +# Remove invalid characters form version string and replace with _ +%{expand: %%global rpmversion %(echo '@VERSION@' | tr [:blank:]- _ )} +%define frrversion @VERSION@ + +#### Check for systemd or init.d (upstart) +# Check for init.d (upstart) as used in CentOS 6 or systemd (ie CentOS 7) +%if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1210 + %global initsystem systemd +%else +%if 0%{?rhel} && 0%{?rhel} < 7 + %global initsystem upstart +%else + %{expand: %%global initsystem %(if [[ `/sbin/init --version 2> /dev/null` =~ upstart ]]; then echo upstart; elif [[ `readlink -f /sbin/init` = /usr/lib/systemd/systemd ]]; then echo systemd; elif [[ `systemctl` =~ -\.mount ]]; then echo systemd; fi)} +%endif +%endif + +# Check for python version - use python2.7 on CentOS 6, otherwise python3 +%if 0%{?rhel} && 0%{?rhel} < 7 + %global use_python2 1 +%else + %global use_python2 0 +%endif + +# If init system is systemd, then always enable watchfrr +%if "%{initsystem}" == "systemd" + %global with_watchfrr 1 +%endif + +#### Check for RedHat 6.x or CentOS 6.x - they are too old to support PIM. +#### Always disable it on these old systems unconditionally +# +# if CentOS / RedHat and version < 7, then disable PIMd (too old, won't work) +%if 0%{?rhel} && 0%{?rhel} < 7 + %global with_pimd 0 + %global with_pim6d 0 +%endif + +# misc internal defines +%{!?frr_uid: %global frr_uid 92 } +%{!?frr_gid: %global frr_gid 92 } +%{!?vty_gid: %global vty_gid 85 } + +%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd fabricd pathd + +%if %{with_ldpd} + %define daemon_ldpd ldpd +%else + %define daemon_ldpd "" +%endif + +%if %{with_pimd} + %define daemon_pimd pimd +%else + %define daemon_pimd "" +%endif + +%if %{with_pim6d} + %define daemon_pim6d pim6d +%else + %define daemon_pim6d "" +%endif + +%if %{with_pbrd} + %define daemon_pbrd pbrd +%else + %define daemon_pbrd "" +%endif + +%if %{with_nhrpd} + %define daemon_nhrpd nhrpd +%else + %define daemon_nhrpd "" +%endif + +%if %{with_eigrpd} + %define daemon_eigrpd eigrpd +%else + %define daemon_eigrpd "" +%endif + +%if %{with_babeld} + %define daemon_babeld babeld +%else + %define daemon_babeld "" +%endif + +%if %{with_vrrpd} + %define daemon_vrrpd vrrpd +%else + %define daemon_vrrpd "" +%endif + +%if %{with_watchfrr} + %define daemon_watchfrr watchfrr +%else + %define daemon_watchfrr "" +%endif + +%if %{with_bfdd} + %define daemon_bfdd bfdd +%else + %define daemon_bfdd "" +%endif + +%if %{with_pathd} + %define daemon_pathd pathd +%else + %define daemon_pathd "" +%endif + +%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_pim6d} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} %{daemon_vrrpd} %{daemon_pathd} + +#release sub-revision (the two digits after the CONFDATE) +%{!?release_rev: %global release_rev 01 } + +Summary: Routing daemon +Name: frr +Version: %{rpmversion} +Release: %{release_rev}%{?dist} +License: GPLv2+ +Group: System Environment/Daemons +Source0: https://github.com/FRRouting/frr/archive/%{name}-%{frrversion}.tar.gz +URL: https://www.frrouting.org +Requires(pre): shadow-utils +Requires(preun): info +Requires(post): info +BuildRequires: bison >= 2.7 +BuildRequires: c-ares-devel +BuildRequires: flex +BuildRequires: gcc +BuildRequires: json-c-devel +BuildRequires: libcap-devel +BuildRequires: protobuf-c-devel +BuildRequires: make +BuildRequires: ncurses-devel +BuildRequires: readline-devel +BuildRequires: texinfo +BuildRequires: libyang-devel >= 2.1.128 +%if 0%{?rhel} && 0%{?rhel} < 7 +#python27-devel is available from ius community repo for RedHat/CentOS 6 +BuildRequires: python27-devel +BuildRequires: python27-sphinx +%else +%if %{use_python2} +BuildRequires: python-devel >= 2.7 +BuildRequires: python-sphinx +%else +BuildRequires: python3-devel +BuildRequires: python3-sphinx +%endif +%endif +%if 0%{?rhel} > 7 +#platform-python-devel is needed for /usr/bin/pathfix.py +BuildRequires: platform-python-devel +%endif +Requires: initscripts +%if %{with_pam} +BuildRequires: pam-devel +%endif +%if "%{initsystem}" == "systemd" +BuildRequires: systemd +BuildRequires: systemd-devel +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +%else +Requires(post): chkconfig +Requires(preun): chkconfig +# Initscripts > 5.60 is required for IPv6 support +Requires(pre): initscripts >= 5.60 +%endif +Provides: routingdaemon = %{version}-%{release} +Obsoletes: gated mrt zebra frr-sysvinit +Conflicts: bird + + +%description +FRRouting is a free software that manages TCP/IP based routing +protocol. It takes multi-server and multi-thread approach to resolve +the current complexity of the Internet. + +FRRouting supports BGP4, OSPFv2, OSPFv3, ISIS, RIP, RIPng, PIM, LDP +NHRP, Babel, PBR, EIGRP and BFD. + +FRRouting is a fork of Quagga. + + +%package contrib +Summary: contrib tools for frr +Group: System Environment/Daemons + +%description contrib +Contributed/3rd party tools which may be of use with frr. + + +%package pythontools +Summary: python tools for frr +%if 0%{?rhel} && 0%{?rhel} < 7 +#python27 is available from ius community repo for RedHat/CentOS 6 +BuildRequires: python27 +Requires: python27-ipaddress +%else +%if %{use_python2} +BuildRequires: python2 +Requires: python2-ipaddress +%else +BuildRequires: python3 +%endif +%endif +Group: System Environment/Daemons + +%description pythontools +Contributed python 2.7 tools which may be of use with frr. + + +%package devel +Summary: Header and object files for frr development +Group: System Environment/Daemons +Requires: %{name} = %{version}-%{release} + +%description devel +The frr-devel package contains the header and object files necessary for +developing OSPF-API and frr applications. + + +%package rpki-rtrlib +Summary: BGP RPKI support (rtrlib) +Group: System Environment/Daemons +BuildRequires: librtr-devel >= 0.8 +Requires: %{name} = %{version}-%{release} + +%description rpki-rtrlib +Adds RPKI support to FRR's bgpd, allowing validation of BGP routes +against cryptographic information stored in WHOIS databases. This is +used to prevent hijacking of networks on the wider internet. It is only +relevant to internet service providers using their own autonomous system +number. + + +%package snmp +Summary: SNMP support +Group: System Environment/Daemons +BuildRequires: net-snmp-devel +Requires: %{name} = %{version}-%{release} + +%description snmp +Adds SNMP support to FRR's daemons by attaching to net-snmp's snmpd +through the AgentX protocol. Provides read-only access to current +routing state through standard SNMP MIBs. + + +%prep +%setup -q -n frr-%{frrversion} + + +%build + +# For standard gcc verbosity, uncomment these lines: +#CFLAGS="%{optflags} -Wall -Wsign-compare -Wpointer-arith" +#CFLAGS="${CFLAGS} -Wbad-function-cast -Wwrite-strings" + +# For ultra gcc verbosity, uncomment these lines also: +#CFLAGS="${CFLAGS} -W -Wcast-qual -Wstrict-prototypes" +#CFLAGS="${CFLAGS} -Wmissing-declarations -Wmissing-noreturn" +#CFLAGS="${CFLAGS} -Wmissing-format-attribute -Wunreachable-code" +#CFLAGS="${CFLAGS} -Wpacked -Wpadded" + +%configure \ + --sbindir=%{_sbindir} \ + --sysconfdir=%{_sysconfdir} \ + --localstatedir=%{_localstatedir} \ + --disable-static \ + --disable-werror \ +%if %{with_mgmtd_test_be_client} + --enable-mgmtd-test-be-client \ +%endif +%if %{with_multipath} + --enable-multipath=%{with_multipath} \ +%endif + --enable-vtysh \ +%if %{with_ospfclient} + --enable-ospfclient \ +%else + --disable-ospfclient\ +%endif +%if %{with_ospfapi} + --enable-ospfapi \ +%else + --disable-ospfapi \ +%endif +%if %{with_rtadv} + --enable-rtadv \ +%else + --disable-rtadv \ +%endif +%if %{with_ldpd} + --enable-ldpd \ +%else + --disable-ldpd \ +%endif +%if %{with_pimd} + --enable-pimd \ +%else + --disable-pimd \ +%endif +%if %{with_pim6d} + --enable-pim6d \ +%else + --disable-pim6d \ +%endif +%if %{with_pbrd} + --enable-pbrd \ +%else + --disable-pbrd \ +%endif +%if %{with_nhrpd} + --enable-nhrpd \ +%else + --disable-nhrpd \ +%endif +%if %{with_eigrpd} + --enable-eigrpd \ +%else + --disable-eigrpd \ +%endif +%if %{with_babeld} + --enable-babeld \ +%else + --disable-babeld \ +%endif +%if %{with_vrrpd} + --enable-vrrpd \ +%else + --disable-vrrpd \ +%endif +%if %{with_pam} + --with-libpam \ +%endif +%if 0%{?frr_user:1} + --enable-user=%{frr_user} \ + --enable-group=%{frr_user} \ +%endif +%if 0%{?vty_group:1} + --enable-vty-group=%{vty_group} \ +%endif +%if %{with_fpm} + --enable-fpm \ +%else + --disable-fpm \ +%endif +%if %{with_watchfrr} + --enable-watchfrr \ +%else + --disable-watchfrr \ +%endif +%if %{with_cumulus} + --enable-cumulus \ +%endif +%if %{with_bgp_vnc} + --enable-bgp-vnc \ +%else + --disable-bgp-vnc \ +%endif + --enable-isisd \ + --enable-rpki \ +%if %{with_bfdd} + --enable-bfdd \ +%else + --disable-bfdd \ +%endif +%if %{with_pathd} + --enable-pathd \ +%else + --disable-pathd \ +%endif + --enable-snmp + # end + +make %{?_smp_mflags} MAKEINFO="makeinfo --no-split" + +%if %{use_python2} +# Change frr-reload.py to use python2.7 +sed -e '1c #!/usr/bin/python2.7' -i %{zeb_src}/tools/frr-reload.py +sed -e '1c #!/usr/bin/python2.7' -i %{zeb_src}/tools/generate_support_bundle.py +%else +# Change frr-reload.py to use python3 +sed -e '1c #!/usr/bin/python3' -i %{zeb_src}/tools/frr-reload.py +sed -e '1c #!/usr/bin/python3' -i %{zeb_src}/tools/generate_support_bundle.py +%endif + +pushd doc +make info +popd + + +%install +mkdir -p %{buildroot}%{_sysconfdir}/{frr,sysconfig,logrotate.d,pam.d,default} \ + %{buildroot}%{_infodir} +mkdir -m 0755 -p %{buildroot}%{_localstatedir}/log/frr +make DESTDIR=%{buildroot} INSTALL="install -p" CP="cp -p" install + +# Remove this file, as it is uninstalled and causes errors when building on RH9 +rm -rf %{buildroot}/usr/share/info/dir + +# Remove debian init script if it was installed +rm -f %{buildroot}%{_sbindir}/frr + +# kill bogus libtool files +rm -vf %{buildroot}%{_libdir}/frr/modules/*.la +rm -vf %{buildroot}%{_libdir}/*.la +rm -vf %{buildroot}%{_libdir}/frr/libyang_plugins/*.la + +# install /etc sources +%if "%{initsystem}" == "systemd" +mkdir -p %{buildroot}%{_unitdir} +install -m644 %{zeb_src}/tools/frr.service %{buildroot}%{_unitdir}/frr.service +%else +mkdir -p %{buildroot}%{_initddir} +ln -s %{_sbindir}/frrinit.sh %{buildroot}%{_initddir}/frr +%endif + +install %{zeb_src}/tools/etc/frr/daemons %{buildroot}%{_sysconfdir}/frr +install %{zeb_src}/tools/etc/frr/frr.conf %{buildroot}%{_sysconfdir}/frr/frr.conf.template +install -m644 %{zeb_rh_src}/frr.pam %{buildroot}%{_sysconfdir}/pam.d/frr +install -m644 %{zeb_src}/tools/etc/logrotate.d/frr %{buildroot}%{_sysconfdir}/logrotate.d/frr +install -d -m750 %{buildroot}%{_runstatedir}/frr + +%if 0%{?rhel} > 7 || 0%{?fedora} > 29 +# avoid `ERROR: ambiguous python shebang in` errors +pathfix.py -pni "%{__python3} %{py3_shbang_opts}" %{buildroot}/usr/lib/frr/*.py +%py_byte_compile %{__python3} %{buildroot}/usr/lib/frr/*.py +%else +# remove ospfclient.py (if present) as it requires > python36 +rm -f %{buildroot}%{_sbindir}/ospfclient.py +%endif + +%pre +# add vty_group +%if 0%{?vty_group:1} + getent group %{vty_group} >/dev/null || groupadd -r -g %{vty_gid} %{vty_group} +%endif + +# add frr user and group +%if 0%{?frr_user:1} + # Ensure that frr_gid gets correctly allocated + getent group %{frr_user} >/dev/null || groupadd -g %{frr_gid} %{frr_user} + getent passwd %{frr_user} >/dev/null || \ + useradd -r -u %{frr_uid} -g %{frr_user} \ + -s /sbin/nologin -c "FRRouting suite" \ + -d %{_runstatedir}/frr %{frr_user} + + %if 0%{?vty_group:1} + usermod -a -G %{vty_group} %{frr_user} + %endif +%endif +exit 0 + + +%post +# zebra_spec_add_service +# e.g. zebra_spec_add_service zebrasrv 2600/tcp "zebra service" + +zebra_spec_add_service () +{ + # Add port /etc/services entry if it isn't already there + if [ -f %{_sysconfdir}/services ] && \ + ! %__sed -e 's/#.*$//' %{_sysconfdir}/services 2>/dev/null | %__grep -wq $1 ; then + echo "$1 $2 # $3" >> %{_sysconfdir}/services + fi +} + +zebra_spec_add_service zebrasrv 2600/tcp "zebra service" +zebra_spec_add_service zebra 2601/tcp "zebra vty" +zebra_spec_add_service staticd 2616/tcp "staticd vty" +zebra_spec_add_service ripd 2602/tcp "RIPd vty" +zebra_spec_add_service ripngd 2603/tcp "RIPngd vty" +zebra_spec_add_service ospfd 2604/tcp "OSPFd vty" +zebra_spec_add_service bgpd 2605/tcp "BGPd vty" +zebra_spec_add_service ospf6d 2606/tcp "OSPF6d vty" +zebra_spec_add_service isisd 2608/tcp "ISISd vty" +%if %{with_ospfapi} + zebra_spec_add_service ospfapi 2607/tcp "OSPF-API" +%endif +%if %{with_babeld} + zebra_spec_add_service babeld 2609/tcp "BABELd vty" +%endif +%if %{with_nhrpd} + zebra_spec_add_service nhrpd 2610/tcp "NHRPd vty" +%endif +%if %{with_pimd} + zebra_spec_add_service pimd 2611/tcp "PIMd vty" +%endif +%if %{with_pbrd} + zebra_spec_add_service pbrd 2615/tcp "PBRd vty" +%endif +%if %{with_ldpd} + zebra_spec_add_service ldpd 2612/tcp "LDPd vty" +%endif +%if %{with_eigrpd} + zebra_spec_add_service eigrpd 2613/tcp "EIGRPd vty" +%endif +%if %{with_bfdd} + zebra_spec_add_service bfdd 2617/tcp "BFDd vty" +%endif +zebra_spec_add_service fabricd 2618/tcp "Fabricd vty" +%if %{with_vrrpd} + zebra_spec_add_service vrrpd 2619/tcp "VRRPd vty" +%endif +%if %{with_pathd} + zebra_spec_add_service pathd 2620/tcp "Pathd vty" +%endif + +%if "%{initsystem}" == "systemd" + for daemon in %all_daemons ; do + %systemd_post frr.service + done +%else + /sbin/chkconfig --add frr +%endif + +# Fix bad path in previous config files +# Config files won't get replaced by default, so we do this ugly hack to fix it +%__sed -i 's|watchfrr_options=|#watchfrr_options=|g' %{configdir}/daemons 2> /dev/null || true + +# With systemd, watchfrr is mandatory. Fix config to make sure it's enabled if +# we install or upgrade to a frr built with systemd +%if "%{initsystem}" == "systemd" + %__sed -i 's|watchfrr_enable=no|watchfrr_enable=yes|g' %{configdir}/daemons 2> /dev/null || true +%endif + +/sbin/install-info %{_infodir}/frr.info.gz %{_infodir}/dir + +# Create dummy config file if they don't exist so basic functions can be used. +if [ ! -e %{configdir}/frr.conf ] && [ ! -e %{configdir}/zebra.conf ]; then + # No frr.conf and per daemon configs exist + mv %{configdir}/frr.conf.template %{configdir}/frr.conf +%if 0%{?frr_user:1} + chown %{frr_user}:%{frr_user} %{configdir}/frr.conf +%endif + chmod 640 %{configdir}/frr.conf +fi +%if 0%{?frr_user:1} + chown %{frr_user}:%{frr_user} %{configdir}/daemons +%endif + +%if %{with_watchfrr} + # No config for watchfrr - this is part of /etc/sysconfig/frr + rm -f %{configdir}/watchfrr.* +%endif + +if [ ! -e %{configdir}/vtysh.conf ]; then + touch %{configdir}/vtysh.conf + chmod 640 %{configdir}/vtysh.conf +%if 0%{?frr_user:1} + %if 0%{?vty_group:1} + chown %{frr_user}:%{vty_group} %{configdir}/vtysh.conf* + %endif +%endif +fi + + +%postun +if [ "$1" -ge 1 ]; then + # + # Upgrade from older version + # + %if "%{initsystem}" == "systemd" + ## + ## Systemd Version + ## + %systemd_postun_with_restart frr.service + %else + ## + ## init.d Version + ## + service frr restart >/dev/null 2>&1 + %endif + : +fi + + +%preun +%if "%{initsystem}" == "systemd" + ## + ## Systemd Version + ## + if [ $1 -eq 0 ] ; then + %systemd_preun frr.service + fi +%else + ## + ## init.d Version + ## + if [ $1 -eq 0 ] ; then + service frr stop >/dev/null 2>&1 + /sbin/chkconfig --del frr + fi +%endif +/sbin/install-info --delete %{_infodir}/frr.info.gz %{_infodir}/dir + + +%files +%doc COPYING +%doc doc/mpls +%doc README.md +/usr/share/yang/*.yang +%if 0%{?frr_user:1} + %dir %attr(751,%{frr_user},%{frr_user}) %{configdir} + %dir %attr(755,%{frr_user},%{frr_user}) %{_localstatedir}/log/frr + %dir %attr(751,%{frr_user},%{frr_user}) %{_runstatedir}/frr +%else + %dir %attr(750,root,root) %{configdir} + %dir %attr(755,root,root) %{_localstatedir}/log/frr + %dir %attr(750,root,root) %{_runstatedir}/frr +%endif +%{_infodir}/frr.info.gz +%{_mandir}/man*/* +%{_sbindir}/zebra +%{_sbindir}/staticd +%{_sbindir}/ospfd +%{_sbindir}/ripd +%{_sbindir}/bgpd +%{_sbindir}/mgmtd +%if %{with_mgmtd_test_be_client} + %{_sbindir}/mgmtd_testc +%endif +%exclude %{_sbindir}/ssd +%exclude %{_sbindir}/fpm_listener +%if %{with_watchfrr} + %{_sbindir}/watchfrr +%endif +%{_sbindir}/ripngd +%{_sbindir}/ospf6d +%if %{with_pimd} + %{_sbindir}/pimd +%endif +%if %{with_pim6d} + %{_sbindir}/pim6d +%endif +%if %{with_pbrd} + %{_sbindir}/pbrd +%endif +%if %{with_vrrpd} + %{_sbindir}/vrrpd +%endif +%{_sbindir}/isisd +%{_sbindir}/fabricd +%if %{with_ldpd} + %{_sbindir}/ldpd +%endif +%if %{with_eigrpd} + %{_sbindir}/eigrpd +%endif +%if %{with_nhrpd} + %{_sbindir}/nhrpd +%endif +%if %{with_babeld} + %{_sbindir}/babeld +%endif +%if %{with_bfdd} + %{_sbindir}/bfdd +%endif +%if %{with_pathd} + %{_sbindir}/pathd + %{_libdir}/frr/modules/pathd_pcep.so +%endif +%{_libdir}/libfrr.so* +%{_libdir}/libfrrcares* +%{_libdir}/libfrrospf* +%if %{with_fpm} + %{_libdir}/frr/modules/zebra_fpm.so +%endif +%{_libdir}/frr/modules/zebra_cumulus_mlag.so +%{_libdir}/frr/modules/dplane_fpm_nl.so +%{_libdir}/frr/modules/bgpd_bmp.so +%{_libdir}/libfrr_pb.so* +%{_libdir}/libfrrfpm_pb.so* +%{_libdir}/libmgmt_be_nb.so* +%{_bindir}/* +%config(noreplace) %{configdir}/[!v]*.conf* +%config(noreplace) %attr(750,%{frr_user},%{frr_user}) %{configdir}/daemons +%if "%{initsystem}" == "systemd" + %{_unitdir}/frr.service +%else + %{_initddir}/frr +%endif +%config(noreplace) %{_sysconfdir}/pam.d/frr +%config(noreplace) %{_sysconfdir}/logrotate.d/frr +%{_sbindir}/frr-reload +%{_sbindir}/frrcommon.sh +%{_sbindir}/frrinit.sh +%{_sbindir}/watchfrr.sh + + +%files contrib +%doc tools + +%files pythontools +%{_sbindir}/generate_support_bundle.py +%{_sbindir}/frr-reload.py +%{_sbindir}/frr_babeltrace.py +%if %{with_ospfclient} && (0%{?rhel} > 7 || 0%{?fedora} > 29) +%{_sbindir}/ospfclient.py +%endif +%if 0%{?rhel} > 7 || 0%{?fedora} > 29 +%{_sbindir}/__pycache__/* +%else +%{_sbindir}/generate_support_bundle.pyc +%{_sbindir}/generate_support_bundle.pyo +%{_sbindir}/frr-reload.pyc +%{_sbindir}/frr-reload.pyo +%{_sbindir}/frr_babeltrace.pyc +%{_sbindir}/frr_babeltrace.pyo +%endif + + +%post rpki-rtrlib +# add rpki module to daemons +sed -i -e 's/^\(bgpd_options=\)\(.*\)\(".*\)/\1\2 -M rpki\3/' %{_sysconfdir}/frr/daemons + +%postun rpki-rtrlib +# remove rpki module from daemons +sed -i 's/ -M rpki//' %{_sysconfdir}/frr/daemons + +%files rpki-rtrlib +%{_libdir}/frr/modules/bgpd_rpki.so + + +%files snmp +%{_libdir}/libfrrsnmp.so* +%{_libdir}/frr/modules/*snmp.so + + +%files devel +%{_libdir}/lib*.so +%dir %{_includedir}/%{name} +%{_includedir}/%{name}/*.h +%dir %{_includedir}/%{name}/ospfd +%{_includedir}/%{name}/ospfd/*.h +%if %{with_bfdd} + %dir %{_includedir}/%{name}/bfdd + %{_includedir}/%{name}/bfdd/bfddp_packet.h +%endif +%if %{with_ospfapi} + %dir %{_includedir}/%{name}/ospfapi + %{_includedir}/%{name}/ospfapi/*.h +%endif +%if %{with_eigrpd} + %dir %{_includedir}/%{name}/eigrpd + %{_includedir}/%{name}/eigrpd/*.h +%endif + + +%changelog + +* Wed Sep 11 2024 Martin Winter - %{version} + +* Wed Sep 11 2024 Donatas Abraitis - 10.1.1 +- bgpd +- Fix as-path exclude modify crash +- Fix labels static-analyser +- Fix, do not access peer->notify.data when it is null +- Fix crash at no rpki +- Fix memory type for static->prd_pretty +- Revert "topotests: add an ebgp 6vpe test" +- Revert "topotests: add bgp_nexthop_mp_ipv4_6 test" +- Revert "bgpd: optimize bgp_interface_address_del" +- Revert "bgpd: fix removing ipv6 global nexhop" +- Revert "bgpd: fix "used" json key on link-local nexthop" +- Revert "tests: ipv6 global removal in bgp_nexthop_mp_ipv4_6" +- Revert "bgpd: set ipv4-mapped ipv6 for ipv4 with ipv6 nexthop" +- Revert "bgpd: prefer link-local to a ipv4-mapped ipv6 global" +- Revert "topotests: update bgp_vrf_leaking_5549_routes" +- Revert "bgpd: optimize bgp_interface_address_add" +- Revert "bgpd: reduce bgp_interface_address_add indentation" +- Revert "bgpd: log new ipv6 global in bgp_interface_address_add" +- Revert "bgpd: fix sending ipv6 local nexthop if global present" +- isisd +- Fix crash when reading asla +- Add missing `exit` statement +- Fix update link params after circuit is up +- Fix crash at flex-algo without mpls-te +- Fix memory handling in isis_adj_process_threeway() +- nhrpd +- Fix show nhrp shortcut json +- Fix sending /32 shortcut +- pimd +- Fix crash in pimd +- mgmtd +- Don't add implicit state data when reading config from file +- lib +- Fix distribute-list deletion +- Fix crash on distribute-list delete +- Fix LYD_NEW_PATH_OUTPUT issue to support libyang v3.x +- ripd +- Fix show run output for distribute-list +- zebra +- Ensure non-equal id's are not same nhg's +- Mimic GNU basename() API for non-glibc library e.g. musl + +* Fri Jul 26 2024 Jafar Al-Gharaibeh - 10.1 +- Breaking changes +- Enable BGP dynamic capability by default for datacenter profile +- Split BGP `rpki cache` command into separate per SSH/TCP +- Add deprecation cycle for OSPF `router-info X [A.B.C.D]` command +- Major highlights: +- BGP dampening per-neighbor support +- BMP send-experimental stats +- Implement extended link-bandwidth for BGP +- Paths Limit for Multiple Paths in BGP +- New command for OSPFv2 `ip ospf neighbor-filter NAME [A.B.C.D]` +- Implement non-broadcast support for point-to-multipoint networks + +* Mon Mar 25 2024 Jafar Al-Gharaibeh - 10.0 +- Major highlights: +- Introduce local host routes +- Require libyang 2.1.128 +- Add suport to configire a log file per daemon +- BGP BMP Loc-RIB (RFC9069) support +- eBGP-OAD (One Administrative Domain) support +- BGP RPKI VRF support +- BGP SNMP traps for BGP4-MIBV2 +- Management (mgmtd) daemon "replace" operation support +- BGP dynamic capabilities for addpath, fqdn, orf capabilities +- SRv6 encapsulation source address feature +- OSPFv3 Point-To-Multipoint mode + +* Mon Oct 09 2023 Donatas Abraitis - 9.1 +- Major highlights: +- OSPFv2 HMAC-SHA Cryptographic Authentication +- BGP MAC-VRF Site-Of-Origin support +- BGP Dynamic capability support +- IS-IS SRv6 uSID support (RFC 9352) +- Change next-hop resolution via the default route for a traditional profile +- Add support for VLAN, ECN, DSCP mangling/filtering +- Zebra support for route replace semantics in FPM +- New command for BGP `neighbor x addpath-tx-best-selected` +- New command for BGP `mpls bgp l3vpn-multi-domain-switching` +- A couple more new BGP route-map commands for as-path, communities manipulation + +* Tue Jun 06 2023 Jafar Al-Gharaibeh - 9.0 +- Major highlights: +- Centralized Management Daemon (mgmtd) +- Switched to libyang minimum version 2.1.80 +- Memory footprint for BGP reduced drastically +- Add BGP `neighbor path-attribute treat-as-withdraw` command +- Add BGP ASN dot notation support (RFC 5396) +- Add BGP Software Version capability +- Allow BGP peering via 127.0.0.0/8 +- Deprecate BGP `internet` community - this is the Cisco-specific community, which is never been RFC-defined and confusing +- Implement `match source-protocol` for BGP route maps +- Implement BGP Node Target extended communities (draft-ietf-idr-node-target-ext-comm) +- Implement Flex-Algo for SR-MPLS (RFC 9350) +- Add support for IS-IS `advertise-passive-only` +- Add IS-IS `affinity-map` support +- Add the `graceful-restart hello-delay` OSPFv2/OSPFv3 command +- Add the `ipv6 mld join` PIMv6 command +- Add `allow-ecmp x` RIP/RIPng command +- Add BFD support for RIP +- For a full list of new features and bug fixes, please refer to: +- https://frrouting.org/release/ + +* Fri Mar 10 2023 Jafar Al-Gharaibeh - 8.5 +- Major Highlights: +- Add support for per-VRF SRv6 SID +- Add BGP labeled-unicast Add-Path functionality +- Implementation of SNMP BGP4v2-MIB (IPv6 support) for better network management and monitoring +- Add BGP new command neighbor path-attribute discard +- Add BGP new command neighbor path-attribute treat-as-withdraw +- Implement L3 route-target auto/wildcard configuration +- Implement BGP ACCEPT_OWN Community Attribute (rfc7611) +- Implement The Accumulated IGP Metric Attribute for BGP (rfc7311) +- Implement graceful-shutdown command per neighbor +- Add BGP new command to configure TCP keepalives for a peer bgp tcp-keepalive +- Traffic control (TC) ZAPI implementation +- SRv6 uSID (microSID) implementation +- Start deprecating start-shell, ssh, and telnet commands due to security reasons +- Add VRRPv3 an ability to disable IPv4 pseudo-header checksum +- BFD integration for static routes +- Allow protocols to configure BFD sessions with automatic source selection +- Allow zero-length opaque LSAs for OSPF (rfc5250) +- Add ISIS new command set-overload-bit on-startup +- PIMv6 BSM support +- For a full list of new features and bug fixes, please refer to: +- https://frrouting.org/release/ + +* Tue Nov 01 2022 Jafar Al-Gharaibeh - 8.4 +- New BGP command (neighbor PEER soo) to configure SoO to prevent routing loops and suboptimal routing on dual-homed sites. +- Command debug bgp allow-martian replaced to bgp allow-martian-nexthop because previously we allowed using martian next-hops when debug is turned on. +- Implement BGP Prefix Origin Validation State Extended Community rfc8097 +- Implement Route Leak Prevention and Detection Using Roles in UPDATE and OPEN Messages rfc9234 +- BMP L3VPN support +- PIMv6 support +- MLD support +- New command to enable using reserved IPv4 ranges as normal addresses for BGP next-hops, interface addresses, etc. +- For a full list of bug fixes, please refer to https://frrouting.org/release/ + +* Wed Jul 13 2022 Jafar Al-Gharaibeh - 8.3 +- General: +- Add camelcase json keys in addition to pascalcase (Wrong JSON keys will be depracated) +- Fix corruption when route-map delete/add sequence happens (fast re-add) +- Reworked gRPC +- RFC5424 & journald extended syslog target +- bfdd: +- Fix broken FSM in active/passive modes +- bgpd: +- Notification Message Support for BGP Graceful Restart (rfc8538) +- BGP Cease Notification Subcode For BFD +- Send Hold Timer for BGP (own implementation without an additional knob) +- New `set as-path replace` command for BGP route-map +- New `match peer` command for BGP route-map +- New `ead-es-frag evi-limit` command for EVPN +- New `match evpn route-type` command for EVPN route-map to match Type-1/Type-4 +- JSON outputs for all RPKI show commands +- Set attributes via route-map for BGP conditional advertisements +- Pass non-transitive extended communities between RS and RS-clients +- Send MED attribute when aggregate prefix is created +- Fix aspath memory leak in aggr_suppress_map_test +- Fix crash for `show ip bgp vrf all all neighbors 192.168.0.1 ...` +- Fix crash for `show ip bgp vrf all all` +- Fix memory leak for BGP Community Alias in CLI +- Fix memory leak when setting BGP community at egress +- Fix memory leak when setting BGP large-community at egress +- Fix SR color nexthop processing in BGP +- Fix setting local-preference in route-map using +/- +- Fix crash using Lua and route-map to set attributes via scripts +- Fix crash when issuing various forms of `bgp no-rib` +- isisd: +- JSON output for show summary command +- Fix crash when MTU mismatch occurs +- Fix crash with xfrm interface type +- Fix infinite loop when parsing LSPs +- Fix router capability TLV parsing issues +- vtysh: +- New `show thread timers` command +- ospfd6: +- Add LSA statistics to LSA database +- Add LSA stats to `show area json` output +- Show time left in hello timer for `show ipv6 ospf6 int` +- Restart SPF when distance is updated +- Support keychain for ospf6 authentication +- ospfd: +- New `show ip ospf reachable-routers` command +- Restart SPF when distance is updated +- Use consistent JSON keys for `show ip ospf neighbor` and detail version +- pimd: +- Add additional IGMP stats +- Add IGMP join sent/failed statistics +- Add IGMP total groups and total source groups to statistics +- New `debug igmp trace detail` command +- New `ip pim passive` command +- JSON support added for command `show ip igmp sources` +- Allow the LPM match work properly with prefix lists and normal RPs +- Do not allow 224.0.0.0/24 range in IGMP join +- Fix IGMP packet/query check +- Handle PIM join/prune receive flow for IPv6 +- Handle receive of (*,G) register stop with source address as 0 +- Handle of exclude mode IGMPv3 report messages for SSM-aware group +- Handle of IGMPv2 report message for SSM-aware group range +- Send immediate join with possible sg rpt prune bit set +- Show group-type under `show ip pim rp-info` +- Show total received messages IGMP stats +- staticd: +- Capture zebra advertised ECMP limit +- Do not register existing nexthop to Zebra +- Reject route config with too many nexthops +- Track nexthops per-safi +- watchfrr: +- Add some more information to `show watchfrr` +- Send operational state to systemd +- zebra: +- Add ability to know when FRR is not ASIC offloaded +- Add command for setting protodown bit +- Add dplane type for netconf data +- Add ECMP supported to `show zebra` +- Add EVPN status to `show zebra` +- Add if v4/v6 forwarding is turned on/off to `show zebra` +- Add initial zebra tracepoint support +- Add kernel nexthop group support to `show zebra` +- Add knowledge about ra and rfc 5549 to `show zebra` +- Add mpls status to `show zebra` +- Add netlink debug dump for netconf messages +- Add netlink debugs for ip rules +- Add OS and version to `show zebra` +- Add support for end.dt4 +- Add to `show zebra` the type of vrf devices being used +- Allow *BSD to specify a receive buffer size +- Allow multiple connected routes to be choosen for kernel routes +- Allow system routes to recurse through themselves +- Do not send RAs w/o link-local v6 or on bridge-ports +- Evpn disable remove l2vni from l3vni list +- Evpn-mh bonds protodown check for set +- Evpn-mh use protodown update reason api +- Fix cleanup of meta queues on vrf disable +- Fix crash in evpn neigh cleanup all +- Fix missing delete vtep during vni transition +- Fix missing vrf change of l2vni on vxlan interface +- Fix rtadv startup when config read in is before interface up +- Fix use after deletion event in FreeBSD +- Fix v6 route replace failure turned into success +- Get zebra graceful restart working when restarting on *BSD +- Handle FreeBSD routing socket enobufs +- Handle protodown netlink for vxlan device +- Include mpls enabled status in interface output +- Include old reason in evpn-mh bond update +- Keep the interface flags safe on multiple ioctl calls +- Let /32 host route with same ip cross vrf +- Make router advertisement warnings show up once every 6 hours +- Prevent crash if zebra_route_all is used for a route type +- Prevent installation of connected multiple times +- Protodown-up event trigger interface up +- Register nht nexthops with proper safi +- Update advertise-svi-ip macips w/ new mac +- When handling unprocessed messages from kernel print usable string +- New `show ip nht mrib` command +- Handle ENOBUFS errors for FreeBSD + +* Tue Mar 1 2022 Jafar Al-Gharaibeh - 8.2 +- The FRRouting community would like to announce FRR Release 8.2. +- This release consists of just over 800 commits from 62 authors. +- Selected features and bug fixes are listed below. +- babeld: +- Fix the checks for truncated packets +- bfdd: +- Correct one spelling error of comment +- Fix detection timeout update +- Fix possibly wrong counter of control packets +- bgpd: +- Add "json" option to a few more show commands +- Add 'show bgp json detail' header data +- Add a 6 hour warning to missing policy +- Add an ability to match ipv6 next-hop by prefix-list +- Add autocomplete for access-list under bmp node +- Add autocomplete for as-path filters +- Add autocomplete for set/match community/large/ext lists +- Add long-lived graceful restart capability +- Add peer-groups to neighbor autocomplete +- Adjust symbolic names for cease notifications according to rfc4486 +- Deprecate dpa, advertiser and rcid_path path attributes +- Extended bgp administrative shutdown communication +- Fix crash when using "show bgp vrf all" +- Fix inconsistency of match ip/ipv6 next-hop commands +- Fix missing name of default vrf +- Handle TCP connection errors with connection callbacks for RPKI +- Implement llgr helper mode +- Implement rfc9072 +- Support redirect import more than one route-target ipv6 +- docker: +- Update alpine build enable set own version +- isisd: +- Add link state traffic engineering support +- Fix router capability tlv parsing issues +- Fix running-config for fast-reroute +- Make isis work with default vrf name different than 'default' +- ospf6d +- Add missing vrf parameter to "clear ipv6 ospf6 interface" +- Add prompt for commands with non-exist vrf +- Add support for nssa type-7 address ranges +- Add the ability of specifying router-id/area-id in no debug ospf6 +- Do not originate type-4 lsa when nssa +- Do not send type-5 into stub area +- Fix ecmp inter-area route nexthop update +- Fix memory leak for `show ipv6 ospf6 zebra json` +- ospfd +- Fix wrong comparison of routemap name +- Fix crash on "ospf send-extra-data zebra" +- Fix incorrect detection of topology changes in helper mode +- Fix loss of mixed form in "range" command +- Fix no-form of "graceful-restart" command +- Fix summary-address deletion +- Fix wrong parsing of te subtlv +- pbrd +- Add vlan actions to vty +- Pbr route maps get addr family of nhgs +- Protect from a possible null dereference +- pimd +- Do not allow 224.0.0.0/24 range in igmp join +- Fix igmp user config +- Fix msdp mesh grp with wildcard member addr +- Fix stale forwarding entries left around after join goes away +- Fix FRR drops IGMP packets for TOS value other than 0XC0 +- redhat +- Check if frr.conf already exists +- Logrotate file has typo for staticd +- ripd +- Fix packet send for non primary addresses +- vtysh +- Add missing rpki node when showing config +- Improve startup time by ca. ×6 +- remove `address-family evpn` +- watchfrr +- Allow an integrated config to work within a namespace +- zebra +- Add optional nhg id output to `show ip ro` +- Add resolver flag for nexthop in json +- Add support for json output in srv6 locator detail command +- Don't lose next hop weights while exporting via fpm +- Fix buffer overflow +- Fix netns deletion +- Fix route-map application when when using vrfs + +* Tue Nov 2 2021 Jafar Al-Gharaibeh - 8.1 +- FRR 8.1 brings a long list of enhancements and fixes with 1200 commits from +- 75 developers. Thanks to all contributers. +- New Features: +- Lua hooks are now feature complete, with one hook available for use (http://docs.frrouting.org/en/latest/scripting.html) +- Improvements to SRv6 (Segment Routing over IPv6) (http://docs.frrouting.org/en/latest/zebra.html#segment-routing-ipv6) +- Improvements to Prefix-SID (Type 5) +- EVPN route type-5 gateway IP overlay Index (http://docs.frrouting.org/en/latest/bgp.html#evpn-overlay-index-gateway-ip) +- OSPFv3 NSSA and NSSA totally stub areas (http://docs.frrouting.org/en/latest/ospf6d.html#ospf6-area) +- OSPFv3 ASBR summarization (http://docs.frrouting.org/en/latest/ospf6d.html#asbr-summarisation-support-in-ospfv3) +- OSPFv3 Graceful Restart (http://docs.frrouting.org/en/latest/ospf6d.html#graceful-restart) +- OSPFv2 Graceful Restart (restarting mode added, helper was already implemented) (http://docs.frrouting.org/en/latest/ospfd.html#graceful-restart) +- Behavior Changes +- Every node in running config now has an explicit "exit" tag +- Link bandwidth in BGP is now correctly encoded according to IEEE 754. To stay with old incorrect encoding use: +- `neighbor PEER disable-link-bw-encoding-ieee` +- Changelog +- alpine: + Fix path for daemons file install +- BGP: +- Add "json" option to "show bgp as-path-access-list" +- Add `disable-addpath-rx` knob +- Add an ability to set extcommunity to none in route-maps +- Add counter of displayed show bgp summary when filtering +- Add knob to config cond-adv scanner period +- Add route-map `match alias` command +- Add rpki source address configuration +- Add show bgp summary filter by neighbor or as +- Add terse display option on show bgp summary +- Allow for auto-completion of community alias's created +- Bgp knob to teardown session immediately when peer is unreachable +- Expand 'bgp default -' cmds +- Extend evpn next hop tracking to type-1 and type-4 routes +- Fix "no router bgp x vrf default" +- Flowspec redirect vrf uses vrf table instead of allocated table id +- Handle quick flaps of an evpn prefix properly +- Initial batch of evpn lttng tracepoints +- Limit processing to what is needed in rpki validation +- Modify vrf/view display in show bgp summary +- Set 4096 instead of 65535 as new max packet size for a new peer +- Set extended msg size only if we advertised and received capability +- Show bgp community alias in json community list output +- Show bgp prefixes by community alias +- Show max packet size per update-group +- Split soft reconfigure table task into several jobs to not block vtysh +- Store distance received from a redistribute statement +- Update route-type-1 legend to match output +- ISIS: +- Fix sending of lsp with null seqno +- lib: +- Add "json" option to "show ip[v6] access-list" +- Add "json" option to "show ip[v6] prefix-list" +- Add "json" option to "show route-map" +- Prevent grpc assert on missing yang node +- NHRP: +- Clear cache when shortcuts are cleared +- Fix corrupt address being shown for shortcuts with no cache entry +- Set prefix correctly in resolution request +- OSPF6: +- Add debug commands for lsa all and route all +- Add warning log for late hello packets +- Add write-multiplier configuration +- Don't update router-id if at least one adjacency is full +- Extend the "redistribute" command with more options +- Fix issue when displaying the redistribute command +- Fix logging of border router routes +- Json output for database dump show command +- Link state id in lsa database json output +- Send lsa update immediately when ospf instance is deleted +- OSPF: +- Fix crash when creating vlink in unknown vrf +- Gr conformance fix for hello packet dr election +- Print extra lsa information in some log messages +- Rfc conformance test case 25.23 issue fix +- Show ip ospf route json does not shown metric and tag +- Summary lsa is not originated when process is reset +- pathd: +- Handle pcinitiated configuration, main thread +- Handle pcinitiated messages, thread controller +- Handle srp_id correctly +- If pce ret no-path to pcreq don't retry pcreq nor delegate +- PBR: +- Add `match ip-protocol [tcp|udp]` +- Add ability to set/unset src and dest ports +- Nhg "add" edge case for last in table range +- Start inclusion of src and dst ports for pbrd +- PIM: +- Add tos/ttl check for igmp conformance +- Allow join prune intervals to be as small as 5 seconds +- Allow msdp group name 'default' +- Fix register suppress timer code +- Fix uaf/heap corruption in bsm code +- Fix command "no ip msdp mesh-group member" +- Igmp groups are not getting timeout +- Igmp memberships are not querier specific +- Igmp sockets need to be iface-bound too +- Prevent uninited usage of nexthop +- Support msdp global timers configuration +- vtysh +- Add cli timestamp '-t' flag +- Add error code if daemon is not running +- Fix searching commands in parent nodes +- yang: +- Add msdp timer configuration +- Fix bgp multicast prefix type +- Mark a couple of prefix-list/access-list leafs as mandatory +- Move multicast prefix type definition +- Replace an empty pattern with a zero-length restriction +- Rework pim msdp mesh group +- Simplify msdp peer handling +- zebra : +- Add "json" option to "show interface" +- Various improvment to dataplane interface +- Add message counts for `show zebra client` +- Add nhg id to show ip route json +- Add show command for ra interface lists +- Fix ipv4 routes with ipv6 link local next hops install in fpm +- Handle bridge mac address update in evpn contexts +- Move individual lines to table in `show zebra client` command +- Refresh vxlan evpn contexts, when bridge interface goes up +- Update zl3vni when bridge link refreshed in other namespaces + +* Wed Jul 21 2021 Martin Winter - 8.0 +- Major changes +- New daemon pathd for segment routing +- EVPN Multihoming is now fully supported +- OSPFv3 now supports VRFs +- TI-LFA has been implemented in IS-IS and OSPF +- Ability for Zebra to dump netlink messages in a human-friendly format +- LDP gained SNMP support +- libyang minimun version is now 2.0 +- BABEL: +- Add `distribute-list` commands +- Fix memory leak in connected route handling +- BGP: +- Add support for use of aliases with communities +- Add support of tcp-mss for neighbors +- Add support for EVPN Multihoming +- Add ability to show BGP routes from a particular table version +- Add support for for RFC 8050 (MRT add-path) +- Add SNMP support for MPLS VPN +- Add `show bgp summary wide` command to show more detailed output + on wide terminals +- Add ability for peer-groups to have `ttl-security hops` configured +- Add support for conditional Advertisement +- Add support for RFC 4271 Delay Open Timer +- Add a knob to not advertise until route is installed in fib +- Add BGP-wide configuration for graceful shutdown +- Add support for RFC 8654 extended messages +- Improve RPKI reporting as well as new show commands +- Improve handling of VRF route leaking +- Improve scaling behavior for dynamic neighbors +- Improve LL nexthop tracking to be interface based +- Improve route reachability handling with respect to blackhole routes +- Improve SNMP traps to RFC 4273 notifications +- Improve EVPN routes to use L3 NHG's where applicable +- Improvements to EVPN +- Improvements to update behavior +- Fix various issues with connection resolution +- Fix statistics commands in some situations +- Fix non-determistic locally-originated paths in bestpath selection +- Continue working on transitioning to YANG/Northbound configuration +- Various bug fixes and performance improvements +- EIGRP: +- Add `distribute-list` commands +- Ensure received AS number is the same as ours in all situations +- Properly validate TLV lengths in some situations +- IS-IS: +- Add ldb-sync functionality +- Add TI-LFA functionality +- Add support for Anycast-SID's +- Add support for classic LFA RFC 5286 +- Add `show isis fast-reroute summary` command +- Add support for Remote LFA RFC 7490 +- Fix Attach-bit processing in some scenarios +- Cleanup BFD integration +- Various bug fixes and performance improvements +- LDP: +- Add SNMP support +- Support for LDP IGP Synchronization +- Support for RLFA clients +- Various bug fixes and performance improvements +- LIBFRR: +- Various bugfixes and performance improvements +- NHRP: +- Add `nhrp multicast-nflog-group (1-65535)` command +- Add configuration options for vici socket path +- Add support for forwarding multicast packets +- Fix handling of MTU +- Fix handling of NAT extension +- Retry IPsec under some conditions +- OSPFv2: +- Add OSPF GR helper support +- Add JSON support for various commands +- Add `summary-address A.B.C.D/M ...` commands +- Add `area X nssa suppress-fa` command +- Add support for TI-LFA +- Add support for BFD profiles +- Add support for Traffic Engineering database +- Add support for usage of DMPVPN with OSPF +- Add `clear ip ospf neighbor` commands +- Add YANG support for route-maps +- Improvements to SNMP +- Fixes for type 5 and type 7 LSA handling +- Various bug fixes and performance improvements +- OSPFv3: +- Add support for VRFs +- Add JSON support to a bunch of commands +- Add ability to control maximum paths for routes +- Add `show ipv6 ospf6 vrfs` command +- Add support for BFD profiles +- Fix to not send hellos on loopbacks +- Cleanup area handling around interfaces +- YANG support for route-maps +- Various bug fixes and performance improvements +- OSPFCLIENT: +- Cleanup trust of user input +- PATHd: +- Add support of SR-TE policy management daemon +- Add optional support for PCEP to pathd +- Integrate PCEP-LIB into FRR +- PBR: +- Add `set installable` nhg command +- Improve interface up/down event handling +- PIM: +- Add YANG integration +- Add JSON support to various commands +- Add BFD profile support +- Fixes to IGMP conformance +- Fixes to behavior surrounding Prune and Prune-pending +- Various bug fixes and performance improvements +- RIPNG: +- Fix interface wakeup after shutdown +- SHARP: +- Add ability to use Nexthop Groups +- Add v4 redistribute watching +- Add TED support +- Various bug fixes +- STATIC: +- Fix nexthop handling in some situations +- Forbid blackhole and non-blackhole nexthops in a single route +- VRRP: +- printf formatting cleanups +- VTYSH: +- Add a `show history` command +- Add `show memory ` support +- Start deprecation cycle for `address-family evpn` +- Display version with --help +- Various bug fixes +- WATCHFRR: +- Fix some crashes +- ZEBRA: +- Add JSON support to various commands +- Add Human readable netlink dumps +- Add L2 NHG support +- Add support for LSPs to FPM dataplane +- Add ability for other protocol daemons to install nexthop groups into the kernel +- Add YANG support for route-maps +- Improve scale performance when handling a large number of VRF's +- Improve network namespace handling +- Improve asic-offload handling +- Improve FreeBSD interface and route handling +- Improve handling of neighbors in kernel dataplane plugin +- Improve label manager +- Improve route-map processing +- Improve debug-ability of routes and VRFs +- Improve FPM dataplane plugin +- Improve handling of reachability / nexthop tracking on shutdown interfaces +- Improve EVPN support +- Fix startup handling of `set src X` +- Various bug fixes and performance improvements + +* Wed Mar 3 2021 Martin Winter - 7.5.1 +- BABEL: +- Fix connected route leak on change +- BFD: +- Session lookup was sometimes wrong +- Memory leak and handling cleanups +- In some situations handle vrf appropriately when receiving packets +- BGP: +- Peer Group Inheritance Fixes +- Dissallow attempt to peer peers reachable via blackholes +- Send BMP down message when reachability fails +- Cleanup handling of aggregator data when the AGG AS is 0 +- Handle `neighbor - 7.5 +- BFD +- Profile support +- Minimum ttl support +- BGP +- rpki VRF support +- GR fixes +- Add wide option to display of routes +- Add `maximum-prefix force` +- Add `bestpath-routes` to neighbor command +- Add `bgp shutdown message MSG...` command +- Add v6 Flowspec support +- Add `neighbor shutdown rtt` command +- Allow update-delay to be applied globaly +- EVPN +- Beginning of MultiHoming Support +- ISIS +- Segment Routing Support +- VRF Support +- Guard against adj timer display overflow +- Add support for Anycast-SIDs +- Add support for Topology Independent LFA (TI-LFA) +- Add `lsp-gen-interval 2` to isis configuration +- OSPF +- Segment Routing support for ECMP +- Various LSA fixes +- Prevent crash if transferring config amongst instances +- PBR +- Adding json support to commands +- DSCP/ECN based PBR Matching +- PIM +- Add more json support to commands +- Fix missing mesh-group commands +- MSDP SA forwarding +- Clear (s,g,rpt) ifchannel on (*, G) prune received +- Fix igmp querier election and IP address mapping +- Crash fix when RP is removed +- STATIC +- Northbound Support +- YANG +- Filter and route-map Support +- OSPF model definition +- BGP model definition +- VTYSH +- Speed up output across daemons +- Fix build-time errors for some --enable flags +- Speed up output of configuration across daemons +- ZEBRA +- nexthop group support for FPM +- northbound support for rib model +- Backup nexthop support +- netlink batching support +- Allow upper level protocols to request ARP +- Add json output for zebra ES, ES-EVI and access vlan dumps +- +- Upgrade to using libyang1.0.184 +- +- RPM +- Moved RPKI to subpackage +- Added SNMP subpackage +- +- As always there are too many bugfixes to list individually. This release +- compromises just over 1k of commits by the community, with contributors from +- 70 people. + +* Tue Jun 30 2020 Martin Winter - 7.4 +- BGPd +- Use sequence numbers for community lists +- Fixes to nexthop groups +- Add feature to limit outgoing number of routes +- Per Neighbor Graceful Restart +- Multiple Graceful Restart fixes +- Support sub-Type-4 and sub-Type-5 for the VPNv4 SRv6 backend +- rfc7606 support: treat certain malformed routes as withdraw +- allow origin override for route aggregates +- rfc6608 support: Subcodes for BGP Finite State Machine Error +- rfc7607 support: Codification of AS 0 Processing +- rfc6286 support: Autonomous-System-Wide Unique BGP Identifier for BGP-4 +- Unequal cost multipath (a.ka. weighted ECMP) with BGP link-bandwidth +- Enable rfc8212 by default except datacenter profile +- staticd +- Add debug support +- vtysh +- Add copy command to copy config from file into running config +- LDPd +- adding support for LDP ordered label distribution control +- ISISd +- IS-IS Segment Routing support +- SHARPd +- add initial support to add/remove lsps +- Zebra +- fix broadcast address in IPv4 networks with /31 mask +- Add Graceful Restart support for Protocol Daemon restarts +- lib +- migrate route-maps to use northbound interface +- plus countless bug fixes and other improvements + +* Mon Jun 15 2020 Sascha Kattelmann +- Add Pathd support + +* Wed May 06 2020 David Lamparter - 7.3.1 +- upstream 7.3.1 + +* Fri Feb 14 2020 Martin Winter - 7.3 +- BGPd +- EVPN PIP Support +- Route Aggregation code speed ups +- BGP Vector I/O speed ups +- New CLI: `set distance XXX` +- New CLI: `aggregate-address A.B.C.D/M route-map WORD` +- New CLI: `bgp reject-as-sets` +- New CLI: `advertise pip ...` +- New CLI: `match evpn rd ASN:NN_OR_IP-ADDRESS:NN` +- New CLI: `show bgp l2vpn evpn community|large-community X` +- New CLI: `show bgp l2vpn evpn A.B.C.D` +- Auto-completion for clear bgp command +- Add ability to set tcp socket buffer size +- OSPFd +- Partial MPLS TE support +- PBRd +- New CLI: `set vrf unchanged|NAME` +- BFDd +- VRF Support +- New CLI: 'show bfd peers brief' +- New CLI: 'clear bfd peer ...' +- PIMd +- Significant Speedups in accessing Internal Data for higher scale +- Support for joining any-source Multicast +- Updated CLI: 'show ip pim upstream-join-desired' +- New CLI: 'show ip pim channel' +- Debug Cleanup +- MLAG experimental support +- VRRPd +- VRF Support +- Northbound Conversion- NHRPd +- LDPd +- vtysh +- New CLI: `banner motd line LINE...` +- yang +- New CLI: `show yang operational-data XPATH` +- New CLI: `debug northbound` +- Zebra +- Nexthop Group support +- New CLI: 'debug zebra nexthop [detail]' +- New CLI: 'show router-id' +- MLAG experimental support +- watchfrr +- Additional status messages of system state to systemd +- New CLI: `watchfrr ignore DAEMON` +- Others +- As always all daemons have received too many bug fixes to fully list +- There has been a significant focus on increasing test coverage +- Change in Behavior: +- ISISd +- All areas created default automatically to level-1-2 +- Zebra +- Nexthop Group Installation in Kernel is turned on by default + if the kernel supports- New CLI: 'show nexthop-group rib [singleton]' +- Man Pages +- Renamed to frr-* to remove collision with other packages + +* Fri Jan 17 2020 Martin Winter - 7.2.1 +- BGPd +- Fix Addpath issue +- Do not apply eBGP policy for iBGP peers +- Show `ip` and `fqdn` in json output for `show [ip] bgp json` +- Fix large route-distinguisher's format +- Fix `no bgp listen range ...` configuration command +- Autocomplete neighbor for clear bgp +- Reflect the distance in RIB when it is changed for an arbitrary afi/safi +- Notify "Peer De-configured" after entering 'no neighbor cmd +- Fix per afi/safi addpath peer counting +- Rework BGP dampening to be per AFI/SAFI +- Do not send next-hop as :: in MP_REACH_NLRI if no link-local exists +- Override peer's TTL only if peer-group is configured with TTL +- Remove error message for unkown afi/safi combination +- Keep the session down if maximum-prefix is reached +- OSPFd +- Fix BFD down not tearing down OSPF adjacency for point-to-point net +- BFDd +- Fix multiple VRF handling +- VRF security improvement +- PIMd +- Fix rp crash +- NHRPd +- Make sure `no ip nhrp map ` works as expected +- LDPd +- Add missing sanity check in the parsing of label messages +- Zebra +- Use correct state when installing evpn macs +- Capture dplane plugin flags +- lib +- Fix interface config when vrf changes +- Fix Interface Infinite Loop Walk (for special interfaces such as bond) +- snapcraft +- fix missing vrrpd daemon +- Others +- Rename man pages (to avoid conflicts with other packages) +- Various other fixes for code cleanup and memory leaks + +* Fri Dec 27 2019 Donatas Abraitis +- Add CentOS 8 support + +* Tue Oct 15 2019 Martin Winter - 7.2 +- ALL Daemons +- -N to allow for config file locating when running FRR inside + of a namespace +- Impoved Testing across all daemons +- BFD +- VRF Support +- Conversion to Northbound interface +- BGP +- Aggregate-address add route-map support +- BMP Support +- Improved JSON output for many commands +- `show bgp afi safi summary failed` command +- `clear bop *` clears all peers +- Show FQDN for `show bgp ipv4 uni` commands +- Display BestPath selection reason as part of show commands +- EIGRP +- Infrastructure changes to allow VRF's +- SIGHUP signals the config reload +- Conversion to Northbound interface +- ISIS +- BFD Support +- Support for circuits with MTU > 8192 +- PBRD +- fwmark support as part of match criteria +- autocompletion of PBRMAPS +- Improved Nexthop Support +- PIMD +- PIM-BSM receive support +- Improved debugging support +- Store ECMP paths that are not currently legal for use +- Disallow igmp query from a non-connected source +- Many new cli improvements and changes +- VRRPD +- Add Support for RFC 3768 and RFC 5798 +- Route-Maps +- Add sequence numbers to access-lists +- Add `match ip next-hop type blackhole` +- Improved ability to notice dependency changes +- SHARPD +- `sharp watch [import|nexthop]` you can now specify a prefix instead + of assuming a /32 +- STATICD +- Significantly Improved NHT +- ZEBRA +- Many dataplane improvements for routes, neighbor table and EVPN +- NHT cli can now be specified per VRF and improved ability to control + NHT data being shown +- Removed duplicate processing of routes +- Improved debugablility +- RMAC and VxLan support for the FPM +- LIB +- RCU support +- Nexthop Group Improvements +- `log-filter WORD` added +- Building +- openssl support +- libcap should be used as part of build or significant slowdowns + will be experienced +- Lua builds have been fixed +- Improved Cross building + +* Mon Jun 17 2019 David Lamparter - 7.1 +- gRPC northbound plugin +- "table NNN" removed from zebra +- more dataplane MT work +- EVPN in non-default VRFs +- RFC 8212 (default deny policy for eBGP) +- RFC 8106 (IPv6 RA DNS options) + +* Wed May 8 2019 Martin Winter - 7.0.1 +- bgp: +- Don't send Updates with BGP Max-Prefix Overflow +- Make sure `next-hop-self all` backward compatible with force +- Fix as-path validation in "show bgp regexp" +- Fix interface-based peers to override peergroups +- Fix removing private AS numbers if local-as is used +- Fix show bgp labeled_unicast +- Add command to lookup prefixes in rpki table +- Fix peer count in "show bgp ipv6 summary" +- Add missing ipv6 only peer flag action +- Fix address family output in "show bgp [ipv4|ipv6] neighbors" +- Add missing checks for vpnv6 nexthops +- Fix nexthop for ipv6 vpn case +- rip: Fix removal of passive interfaces +- ospf: +- Fix json timer output +- Fix milliseconds in json output +- bfd: +- Fix source port according RFC 5881, Sec 4 +- Fix IPv6 link-local peer removal +- Fix interface clean up when deleting interface +- pim: Fix interface clean up when deleting interface +- nhrp: Fix interface clean up when deleting interface +- lib: +- Workaround to get FRR building with libyang 0.x and 1.x +- Fix in priv handling +- Make priv elevation thread-safe +- zebra: +- Pseudowire event recovery +- Fix race condition in label manager +- Fix system routes selection and next-hop tracking +- Set connected route metric based on devaddr metric +- Display metric for connected routes +- Add selected fib details to json output +- Always use replace if installing new route +- watchfrr: Silently ignore declare failures (for backward compatibility) +- RPM packages: Switch to new init script + +* Thu Feb 28 2019 Martin Winter - 7.0 +- Added libyang dependency: New work for northbound interface based on libyang +- Fabricd: New Daemon based on https://datatracker.ietf.org/doc/draft-white-openfabric/ +- various bug fixes and other enhancements + +* Sun Oct 7 2018 Martin Winter - 6.0 +- Staticd: New daemon responsible for management of static routes +- ISISd: Implement dst-src routing as per draft-ietf-isis-ipv6-dst-src-routing +- BFDd: new daemon for BFD (Bidrectional Forwarding Detection). Responsible + for notifying link changes to make routing protocols converge faster. +- various bug fixes + +* Thu Jul 5 2018 Martin Winter - 5.0.1 +- Support Automake 1.16.1 +- BGPd: Support for flowspec ICMP, DSCP, packet length, fragment and tcp flags +- BGPd: fix rpki validation for ipv6 +- VRF: Workaround for kernel bug on Linux 4.14 and newer +- Zebra: Fix interface based routes from zebra not marked up +- Zebra: Fix large zebra memory usage when redistribute between protocols +- Zebra: Allow route-maps to match on source instance +- BGPd: Backport peer-attr overrides, peer-level enforce-first-as and filtered-routes fix +- BGPd: fix for crash during display of filtered-routes +- BGPd: Actually display labeled unicast routes received +- Label Manager: Fix to work correctly behind a label manager proxy + +* Thu Jun 7 2018 Martin Winter - 5.0 +- PIM: Add a Multicast Trace Command draft-ietf-idmr-traceroute-ipm-05 +- IS-IS: Implement Three-Way Handshake as per RFC5303 +- BGPD: Implement VPN-VRF route leaking per RFC4364. +- BGPD: Implement VRF with NETNS backend +- BGPD: Flowspec +- PBRD: Add a new Policy Based Routing Daemon + +* Mon May 28 2018 Rafael Zalamena +- Add BFDd support + +* Sun May 20 2018 Martin Winter +- Fixed RPKI RPM build + +* Sun Mar 4 2018 Martin Winter +- Add option to build with RPKI (default: disabled) + +* Tue Feb 20 2018 Martin Winter +- Adapt to new documentation structure based on Sphinx + +* Fri Oct 20 2017 Martin Winter +- Fix script location for watchfrr restart functions in daemon config +- Fix postun script to restart frr during upgrade + +* Mon Jun 5 2017 Martin Winter +- added NHRP and EIGRP daemon + +* Mon Apr 17 2017 Martin Winter +- new subpackage frr-pythontools with python 2.7 restart script +- remove PIMd from CentOS/RedHat 6 RPM packages (won't work - too old) +- converted to single frr init script (not per daemon) based on debian init script +- created systemd service file for systemd based systems (which uses init script) +- Various other RPM package fixes for FRR 2.0 + +* Fri Jan 6 2017 Martin Winter +- Renamed to frr for FRRouting fork of Quagga + +* Thu Feb 11 2016 Paul Jakma +- remove with_ipv6 conditionals, always build v6 +- Fix UTF-8 char in spec changelog +- remove quagga.pam.stack, long deprecated. + +* Thu Oct 22 2015 Martin Winter +- Cleanup configure: remove --enable-ipv6 (default now), --enable-nssa, + --enable-netlink +- Remove support for old fedora 4/5 +- Fix for package nameing +- Fix Weekdays of previous changelogs (bogus dates) +- Add conditional logic to only build tex footnotes with supported texi2html +- Added pimd to files section and fix double listing of /var/lib*/quagga +- Numerous fixes to unify upstart/systemd startup into same spec file +- Only allow use of watchfrr for non-systemd systems. no need with systemd + +* Fri Sep 4 2015 Paul Jakma +- buildreq updates +- add a default define for with_pimd + +* Mon Sep 12 2005 Paul Jakma +- Steal some changes from Fedora spec file: +- Add with_rtadv variable +- Test for groups/users with getent before group/user adding +- Readline need not be an explicit prerequisite +- install-info delete should be postun, not preun + +* Wed Jan 12 2005 Andrew J. Schorr +- on package upgrade, implement careful, phased restart logic +- use gcc -rdynamic flag when linking for better backtraces + +* Wed Dec 22 2004 Andrew J. Schorr +- daemonv6_list should contain only IPv6 daemons + +* Wed Dec 22 2004 Andrew J. Schorr +- watchfrr added +- on upgrade, all daemons should be condrestart'ed +- on removal, all daemons should be stopped + +* Mon Nov 08 2004 Paul Jakma +- Use makeinfo --html to generate quagga.html + +* Sun Nov 07 2004 Paul Jakma +- Fix with_ipv6 set to 0 build + +* Sat Oct 23 2004 Paul Jakma +- Update to 0.97.2 + +* Sat Oct 23 2004 Andrew J. Schorr +- Make directories be owned by the packages concerned +- Update logrotate scripts to use correct path to killall and use pid files + +* Fri Oct 08 2004 Paul Jakma +- Update to 0.97.0 + +* Wed Sep 15 2004 Paul Jakma +- build snmp support by default +- build irdp support +- build with shared libs +- devel subpackage for archives and headers + +* Thu Jan 08 2004 Paul Jakma +- updated sysconfig files to specify local dir +- added ospf_dump.c crash quick fix patch +- added ospfd persistent interface configuration patch + +* Tue Dec 30 2003 Paul Jakma +- sync to CVS +- integrate RH sysconfig patch to specify daemon options (RH) +- default to have vty listen only to 127.1 (RH) +- add user with fixed UID/GID (RH) +- create user with shell /sbin/nologin rather than /bin/false (RH) +- stop daemons on uninstall (RH) +- delete info file on preun, not postun to avoid deletion on upgrade. (RH) +- isisd added +- cleanup tasks carried out for every daemon + +* Sun Nov 2 2003 Paul Jakma +- Fix -devel package to include all files +- Sync to 0.96.4 + +* Tue Aug 12 2003 Paul Jakma +- Renamed to Quagga +- Sync to Quagga release 0.96 + +* Thu Mar 20 2003 Paul Jakma +- zebra privileges support + +* Tue Mar 18 2003 Paul Jakma +- Fix mem leak in 'show thread cpu' +- Ralph Keller's OSPF-API +- Amir: Fix configure.ac for net-snmp + +* Sat Mar 1 2003 Paul Jakma +- ospfd IOS prefix to interface matching for 'network' statement +- temporary fix for PtP and IPv6 +- sync to zebra.org CVS + +* Mon Jan 20 2003 Paul Jakma +- update to latest cvs +- Yon's "show thread cpu" patch - 17217 +- walk up tree - 17218 +- ospfd NSSA fixes - 16681 +- ospfd nsm fixes - 16824 +- ospfd OLSA fixes and new feature - 16823 +- KAME and ifindex fixes - 16525 +- spec file changes to allow redhat files to be in tree + +* Sat Dec 28 2002 Alexander Hoogerhuis +- Added conditionals for building with(out) IPv6, vtysh, RIP, BGP +- Fixed up some build requirements (patch) +- Added conditional build requirements for vtysh / snmp +- Added conditional to files for _bindir depending on vtysh + +* Mon Nov 11 2002 Paul Jakma +- update to latest CVS +- add Greg Troxel's md5 buffer copy/dup fix +- add RIPv1 fix +- add Frank's multicast flag fix + +* Wed Oct 09 2002 Paul Jakma +- update to latest CVS +- timestamped crypt_seqnum patch +- oi->on_write_q fix + +* Mon Sep 30 2002 Paul Jakma +- update to latest CVS +- add vtysh 'write-config (integrated|daemon)' patch +- always 'make rebuild' in vtysh/ to catch new commands + +* Fri Sep 13 2002 Paul Jakma +- update to 0.93b + +* Wed Sep 11 2002 Paul Jakma +- update to latest CVS +- add "/sbin/ip route flush proto zebra" to zebra RH init on startup + +* Sat Aug 24 2002 Paul Jakma +- update to current CVS +- add OSPF point to multipoint patch +- add OSPF bugfixes +- add BGP hash optimisation patch + +* Fri Jun 14 2002 Paul Jakma +- update to 0.93-pre1 / CVS +- add link state detection support +- add generic PtP and RFC3021 support +- various bug fixes + +* Thu Aug 09 2001 Elliot Lee 0.91a-6 +- Fix bug #51336 + +* Wed Aug 1 2001 Trond Eivind Glomsrød 0.91a-5 +- Use generic initscript strings instead of initscript specific + ( "Starting foo: " -> "Starting $prog:" ) + +* Fri Jul 27 2001 Elliot Lee 0.91a-4 +- Bump the release when rebuilding into the dist. + +* Tue Feb 6 2001 Tim Powers +- built for Powertools + +* Sun Feb 4 2001 Pekka Savola +- Hacked up from PLD Linux 0.90-1, Mandrake 0.90-1mdk and one from zebra.org. +- Update to 0.91a +- Very heavy modifications to init.d/*, .spec, pam, i18n, logrotate, etc. +- Should be quite Red Hat'isque now. diff --git a/ripd/.gitignore b/ripd/.gitignore new file mode 100644 index 0000000..f149501 --- /dev/null +++ b/ripd/.gitignore @@ -0,0 +1,2 @@ +ripd +ripd.conf diff --git a/ripd/Makefile b/ripd/Makefile new file mode 100644 index 0000000..2d6f838 --- /dev/null +++ b/ripd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ripd/ripd +%: ALWAYS + @$(MAKE) -s -C .. ripd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ripd/rip_bfd.c b/ripd/rip_bfd.c new file mode 100644 index 0000000..b59db11 --- /dev/null +++ b/ripd/rip_bfd.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIP BFD integration. + * Copyright (C) 2021-2023 Network Device Education Foundation, Inc. ("NetDEF") + */ + +#include + +#include "lib/zclient.h" +#include "lib/bfd.h" + +#include "ripd/ripd.h" +#include "ripd/rip_bfd.h" +#include "ripd/rip_debug.h" + +DEFINE_MTYPE(RIPD, RIP_BFD_PROFILE, "RIP BFD profile name"); + +extern struct zclient *zclient; + +static const char *rip_bfd_interface_profile(struct rip_interface *ri) +{ + struct rip *rip = ri->rip; + + if (ri->bfd.profile) + return ri->bfd.profile; + + if (rip->default_bfd_profile) + return rip->default_bfd_profile; + + return NULL; +} + +static void rip_bfd_session_change(struct bfd_session_params *bsp, + const struct bfd_session_status *bss, + void *arg) +{ + struct rip_peer *rp = arg; + + /* BFD peer went down. */ + if (bss->state == BFD_STATUS_DOWN && + bss->previous_state == BFD_STATUS_UP) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: peer %pI4: BFD Down", __func__, + &rp->addr); + + rip_peer_delete_routes(rp); + listnode_delete(rp->rip->peer_list, rp); + rip_peer_free(rp); + return; + } + + /* BFD peer went up. */ + if (bss->state == BSS_UP && bss->previous_state == BSS_DOWN) + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: peer %pI4: BFD Up", __func__, + &rp->addr); +} + +void rip_bfd_session_update(struct rip_peer *rp) +{ + struct rip_interface *ri = rp->ri; + + /* BFD configuration was removed. */ + if (ri == NULL || !ri->bfd.enabled) { + bfd_sess_free(&rp->bfd_session); + return; + } + + /* New BFD session. */ + if (rp->bfd_session == NULL) { + rp->bfd_session = bfd_sess_new(rip_bfd_session_change, rp); + bfd_sess_set_ipv4_addrs(rp->bfd_session, NULL, &rp->addr); + bfd_sess_set_interface(rp->bfd_session, ri->ifp->name); + bfd_sess_set_vrf(rp->bfd_session, rp->rip->vrf->vrf_id); + } + + /* Set new configuration. */ + bfd_sess_set_timers(rp->bfd_session, BFD_DEF_DETECT_MULT, + BFD_DEF_MIN_RX, BFD_DEF_MIN_TX); + bfd_sess_set_profile(rp->bfd_session, rip_bfd_interface_profile(ri)); + + bfd_sess_install(rp->bfd_session); +} + +void rip_bfd_interface_update(struct rip_interface *ri) +{ + struct rip *rip; + struct rip_peer *rp; + struct listnode *node; + + rip = ri->rip; + if (!rip) + return; + + for (ALL_LIST_ELEMENTS_RO(rip->peer_list, node, rp)) { + if (rp->ri != ri) + continue; + + rip_bfd_session_update(rp); + } +} + +void rip_bfd_instance_update(struct rip *rip) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (rip->vrf, ifp) { + struct rip_interface *ri; + + ri = ifp->info; + if (!ri) + continue; + + rip_bfd_interface_update(ri); + } +} + +void rip_bfd_init(struct event_loop *tm) +{ + bfd_protocol_integration_init(zclient, tm); +} diff --git a/ripd/rip_bfd.h b/ripd/rip_bfd.h new file mode 100644 index 0000000..7621498 --- /dev/null +++ b/ripd/rip_bfd.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIP BFD integration. + * Copyright (C) 2021-2023 Network Device Education Foundation, Inc. ("NetDEF") + */ + +#ifndef _RIP_BFD_ +#define _RIP_BFD_ + +#include "frrevent.h" + +DECLARE_MTYPE(RIP_BFD_PROFILE); + +struct rip; +struct rip_interface; +struct rip_peer; + +void rip_bfd_session_update(struct rip_peer *rp); +void rip_bfd_interface_update(struct rip_interface *ri); +void rip_bfd_instance_update(struct rip *rip); +void rip_bfd_init(struct event_loop *tm); + +#endif /* _RIP_BFD_ */ diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c new file mode 100644 index 0000000..5712a0b --- /dev/null +++ b/ripd/rip_cli.c @@ -0,0 +1,1457 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "if_rmap.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "northbound_cli.h" +#include "libfrr.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_cli_clippy.c" + +/* + * XPath: /frr-ripd:ripd/instance + */ +DEFPY_YANG_NOSH (router_rip, + router_rip_cmd, + "router rip [vrf NAME]", + "Enable a routing process\n" + "Routing Information Protocol (RIP)\n" + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + int ret; + + /* Build RIP instance XPath. */ + if (!vrf) + vrf = VRF_DEFAULT_NAME; + snprintf(xpath, sizeof(xpath), "/frr-ripd:ripd/instance[vrf='%s']", + vrf); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(RIP_NODE, xpath); + + return ret; +} + +DEFPY_YANG (no_router_rip, + no_router_rip_cmd, + "no router rip [vrf NAME]", + NO_STR + "Enable a routing process\n" + "Routing Information Protocol (RIP)\n" + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + + /* Build RIP instance XPath. */ + if (!vrf) + vrf = VRF_DEFAULT_NAME; + snprintf(xpath, sizeof(xpath), "/frr-ripd:ripd/instance[vrf='%s']", + vrf); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes_clear_pending(vty, NULL); +} + +void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *vrf_name; + + vrf_name = yang_dnode_get_string(dnode, "vrf"); + + vty_out(vty, "!\n"); + vty_out(vty, "router rip"); + if (!strmatch(vrf_name, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", vrf_name); + vty_out(vty, "\n"); +} + +void cli_show_end_router_rip(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/allow-ecmp + */ +DEFUN_YANG (rip_allow_ecmp, + rip_allow_ecmp_cmd, + "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", + "Allow Equal Cost MultiPath\n" + "Number of paths\n") +{ + int idx_number = 0; + char mpaths[3] = {}; + uint32_t paths = MULTIPATH_NUM; + + if (argv_find(argv, argc, CMD_RANGE_STR(1, MULTIPATH_NUM), &idx_number)) + paths = strtol(argv[idx_number]->arg, NULL, 10); + snprintf(mpaths, sizeof(mpaths), "%u", paths); + + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, mpaths); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_rip_allow_ecmp, + no_rip_allow_ecmp_cmd, + "no allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", + NO_STR + "Allow Equal Cost MultiPath\n" + "Number of paths\n") +{ + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, 0); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + uint8_t paths; + + paths = yang_dnode_get_uint8(dnode, NULL); + + if (!paths) + vty_out(vty, " no allow-ecmp\n"); + else + vty_out(vty, " allow-ecmp %d\n", paths); +} + +/* + * XPath: /frr-ripd:ripd/instance/default-information-originate + */ +DEFPY_YANG (rip_default_information_originate, + rip_default_information_originate_cmd, + "[no] default-information originate", + NO_STR + "Control distribution of default route\n" + "Distribute a default route\n") +{ + nb_cli_enqueue_change(vty, "./default-information-originate", + NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_default_information_originate(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " default-information originate\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/default-metric + */ +DEFPY_YANG (rip_default_metric, + rip_default_metric_cmd, + "default-metric (1-16)", + "Set a metric of redistribute routes\n" + "Default metric\n") +{ + nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, + default_metric_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_rip_default_metric, + no_rip_default_metric_cmd, + "no default-metric [(1-16)]", + NO_STR + "Set a metric of redistribute routes\n" + "Default metric\n") +{ + nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_default_metric(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " default-metric %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/default + */ +DEFPY_YANG (rip_distance, + rip_distance_cmd, + "distance (1-255)", + "Administrative distance\n" + "Distance value\n") +{ + nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY, + distance_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_rip_distance, + no_rip_distance_cmd, + "no distance [(1-255)]", + NO_STR + "Administrative distance\n" + "Distance value\n") +{ + nb_cli_enqueue_change(vty, "./distance/default", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_distance(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (yang_dnode_is_default(dnode, NULL)) + vty_out(vty, " no distance\n"); + else + vty_out(vty, " distance %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/source + */ +DEFPY_YANG (rip_distance_source, + rip_distance_source_cmd, + "[no] distance (1-255) A.B.C.D/M$prefix [WORD$acl]", + NO_STR + "Administrative distance\n" + "Distance value\n" + "IP source prefix\n" + "Access list name\n") +{ + if (!no) { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./distance", NB_OP_MODIFY, + distance_str); + nb_cli_enqueue_change(vty, "./access-list", + acl ? NB_OP_MODIFY : NB_OP_DESTROY, acl); + } else + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./distance/source[prefix='%s']", + prefix_str); +} + +void cli_show_rip_distance_source(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " distance %s %s", + yang_dnode_get_string(dnode, "distance"), + yang_dnode_get_string(dnode, "prefix")); + if (yang_dnode_exists(dnode, "access-list")) + vty_out(vty, " %s", + yang_dnode_get_string(dnode, "access-list")); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/explicit-neighbor + */ +DEFPY_YANG (rip_neighbor, + rip_neighbor_cmd, + "[no] neighbor A.B.C.D", + NO_STR + "Specify a neighbor router\n" + "Neighbor address\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./explicit-neighbor[.='%s']", + neighbor_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_neighbor(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " neighbor %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/network + */ +DEFPY_YANG (rip_network_prefix, + rip_network_prefix_cmd, + "[no] network A.B.C.D/M", + NO_STR + "Enable routing on an IP network\n" + "IP prefix /, e.g., 35.0.0.0/8\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./network[.='%s']", network_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_network_prefix(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/interface + */ +DEFPY_YANG (rip_network_if, + rip_network_if_cmd, + "[no] network WORD", + NO_STR + "Enable routing on an IP network\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./interface[.='%s']", network); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_network_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/offset-list + */ +DEFPY_YANG (rip_offset_list, + rip_offset_list_cmd, + "[no] offset-list ACCESSLIST4_NAME$acl $direction (0-16)$metric [IFNAME]", + NO_STR + "Modify RIP metric\n" + "Access-list name\n" + "For incoming updates\n" + "For outgoing updates\n" + "Metric value\n" + "Interface to match\n") +{ + if (!no) { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./access-list", NB_OP_MODIFY, acl); + nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, + metric_str); + } else + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes( + vty, "./offset-list[interface='%s'][direction='%s']", + ifname ? ifname : "*", direction); +} + +void cli_show_rip_offset_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *interface; + + interface = yang_dnode_get_string(dnode, "interface"); + + vty_out(vty, " offset-list %s %s %s", + yang_dnode_get_string(dnode, "access-list"), + yang_dnode_get_string(dnode, "direction"), + yang_dnode_get_string(dnode, "metric")); + if (!strmatch(interface, "*")) + vty_out(vty, " %s", interface); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/passive-default + */ +DEFPY_YANG (rip_passive_default, + rip_passive_default_cmd, + "[no] passive-interface default", + NO_STR + "Suppress routing updates on an interface\n" + "default for all interfaces\n") +{ + nb_cli_enqueue_change(vty, "./passive-default", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_passive_default(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " passive-interface default\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/passive-interface + * /frr-ripd:ripd/instance/non-passive-interface + */ +DEFPY_YANG (rip_passive_interface, + rip_passive_interface_cmd, + "[no] passive-interface IFNAME", + NO_STR + "Suppress routing updates on an interface\n" + "Interface name\n") +{ + bool passive_default = + yang_dnode_get_bool(vty->candidate_config->dnode, "%s%s", + VTY_CURR_XPATH, "/passive-default"); + char xpath[XPATH_MAXLEN]; + enum nb_operation op; + + if (passive_default) { + snprintf(xpath, sizeof(xpath), + "./non-passive-interface[.='%s']", ifname); + op = no ? NB_OP_CREATE : NB_OP_DESTROY; + } else { + snprintf(xpath, sizeof(xpath), "./passive-interface[.='%s']", + ifname); + op = no ? NB_OP_DESTROY : NB_OP_CREATE; + } + + nb_cli_enqueue_change(vty, xpath, op, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " passive-interface %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +void cli_show_rip_non_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " no passive-interface %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/redistribute + */ +DEFPY_YANG (rip_redistribute, + rip_redistribute_cmd, + "[no] redistribute " FRR_REDIST_STR_RIPD "$protocol [{metric (0-16)|route-map RMAP_NAME$route_map}]", + NO_STR + REDIST_STR + FRR_REDIST_HELP_STR_RIPD + "Metric\n" + "Metric value\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + if (!no) { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./route-map", + route_map ? NB_OP_MODIFY : NB_OP_DESTROY, + route_map); + nb_cli_enqueue_change(vty, "./metric", + metric_str ? NB_OP_MODIFY : NB_OP_DESTROY, + metric_str); + } else + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./redistribute[protocol='%s']", + protocol); +} + +void cli_show_rip_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " redistribute %s", + yang_dnode_get_string(dnode, "protocol")); + if (yang_dnode_exists(dnode, "metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "metric")); + if (yang_dnode_exists(dnode, "route-map")) + vty_out(vty, " route-map %s", + yang_dnode_get_string(dnode, "route-map")); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-ripd:ripd/instance/static-route + */ +DEFPY_YANG (rip_route, + rip_route_cmd, + "[no] route A.B.C.D/M", + NO_STR + "RIP static route configuration\n" + "IP prefix /\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./static-route[.='%s']", route_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_route(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " route %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripd:ripd/instance/timers + */ +DEFPY_YANG (rip_timers, + rip_timers_cmd, + "timers basic (5-2147483647)$update (5-2147483647)$timeout (5-2147483647)$garbage", + "Adjust routing timers\n" + "Basic routing protocol update timers\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") +{ + nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, + update_str); + nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, + timeout_str); + nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, + garbage_str); + + return nb_cli_apply_changes(vty, "./timers"); +} + +DEFPY_YANG (no_rip_timers, + no_rip_timers_cmd, + "no timers basic [(5-2147483647) (5-2147483647) (5-2147483647)]", + NO_STR + "Adjust routing timers\n" + "Basic routing protocol update timers\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") +{ + nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, "./timers"); +} + +void cli_show_rip_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " timers basic %s %s %s\n", + yang_dnode_get_string(dnode, "update-interval"), + yang_dnode_get_string(dnode, "holddown-interval"), + yang_dnode_get_string(dnode, "flush-interval")); +} + +/* + * XPath: /frr-ripd:ripd/instance/version + */ +DEFPY_YANG (rip_version, + rip_version_cmd, + "version (1-2)", + "Set routing protocol version\n" + "version\n") +{ + nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY, + version_str); + nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, version_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_rip_version, + no_rip_version_cmd, + "no version [(1-2)]", + NO_STR + "Set routing protocol version\n" + "version\n") +{ + nb_cli_enqueue_change(vty, "./version/receive", NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, "./version/send", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_rip_version(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + /* + * We have only one "version" command and three possible combinations of + * send/receive values. + */ + switch (yang_dnode_get_enum(dnode, "receive")) { + case RI_RIP_VERSION_1: + vty_out(vty, " version 1\n"); + break; + case RI_RIP_VERSION_2: + vty_out(vty, " version 2\n"); + break; + case RI_RIP_VERSION_1_AND_2: + vty_out(vty, " no version\n"); + break; + } +} + +/* + * XPath: /frr-ripd:ripd/instance/default-bfd-profile + */ +DEFPY_YANG(rip_bfd_default_profile, rip_bfd_default_profile_cmd, + "bfd default-profile BFDPROF$profile", + "Bidirectional Forwarding Detection\n" + "BFD default profile\n" + "Profile name\n") +{ + nb_cli_enqueue_change(vty, "./default-bfd-profile", NB_OP_MODIFY, + profile); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_rip_bfd_default_profile, no_rip_bfd_default_profile_cmd, + "no bfd default-profile [BFDPROF]", + NO_STR + "Bidirectional Forwarding Detection\n" + "BFD default profile\n" + "Profile name\n") +{ + nb_cli_enqueue_change(vty, "./default-bfd-profile", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripd_instance_default_bfd_profile(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " bfd default-profile %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/split-horizon + */ +DEFPY_YANG (ip_rip_split_horizon, + ip_rip_split_horizon_cmd, + "[no] ip rip split-horizon [poisoned-reverse$poisoned_reverse]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Perform split horizon\n" + "With poisoned-reverse\n") +{ + const char *value; + + if (no) + value = "disabled"; + else if (poisoned_reverse) + value = "poison-reverse"; + else + value = "simple"; + + nb_cli_enqueue_change(vty, "./split-horizon", NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_split_horizon(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + int value; + + value = yang_dnode_get_enum(dnode, NULL); + switch (value) { + case RIP_NO_SPLIT_HORIZON: + vty_out(vty, " no ip rip split-horizon\n"); + break; + case RIP_SPLIT_HORIZON: + vty_out(vty, " ip rip split-horizon\n"); + break; + case RIP_SPLIT_HORIZON_POISONED_REVERSE: + vty_out(vty, " ip rip split-horizon poisoned-reverse\n"); + break; + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/v2-broadcast + */ +DEFPY_YANG (ip_rip_v2_broadcast, + ip_rip_v2_broadcast_cmd, + "[no] ip rip v2-broadcast", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Send ip broadcast v2 update\n") +{ + nb_cli_enqueue_change(vty, "./v2-broadcast", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_v2_broadcast(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " ip rip v2-broadcast\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-receive + */ +DEFPY_YANG (ip_rip_receive_version, + ip_rip_receive_version_cmd, + "ip rip receive version <{1$v1|2$v2}|none>", + IP_STR + "Routing Information Protocol\n" + "Advertisement reception\n" + "Version control\n" + "RIP version 1\n" + "RIP version 2\n" + "None\n") +{ + const char *value; + + if (v1 && v2) + value = "both"; + else if (v1) + value = "1"; + else if (v2) + value = "2"; + else + value = "none"; + + nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG (no_ip_rip_receive_version, + no_ip_rip_receive_version_cmd, + "no ip rip receive version [<{1|2}|none>]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Advertisement reception\n" + "Version control\n" + "RIP version 1\n" + "RIP version 2\n" + "None\n") +{ + nb_cli_enqueue_change(vty, "./version-receive", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_receive_version(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + switch (yang_dnode_get_enum(dnode, NULL)) { + case RI_RIP_UNSPEC: + vty_out(vty, " no ip rip receive version\n"); + break; + case RI_RIP_VERSION_1: + vty_out(vty, " ip rip receive version 1\n"); + break; + case RI_RIP_VERSION_2: + vty_out(vty, " ip rip receive version 2\n"); + break; + case RI_RIP_VERSION_1_AND_2: + vty_out(vty, " ip rip receive version 1 2\n"); + break; + case RI_RIP_VERSION_NONE: + vty_out(vty, " ip rip receive version none\n"); + break; + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-send + */ +DEFPY_YANG (ip_rip_send_version, + ip_rip_send_version_cmd, + "ip rip send version <{1$v1|2$v2}|none>", + IP_STR + "Routing Information Protocol\n" + "Advertisement transmission\n" + "Version control\n" + "RIP version 1\n" + "RIP version 2\n" + "None\n") +{ + const char *value; + + if (v1 && v2) + value = "both"; + else if (v1) + value = "1"; + else if (v2) + value = "2"; + else + value = "none"; + + nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG (no_ip_rip_send_version, + no_ip_rip_send_version_cmd, + "no ip rip send version [<{1|2}|none>]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Advertisement transmission\n" + "Version control\n" + "RIP version 1\n" + "RIP version 2\n" + "None\n") +{ + nb_cli_enqueue_change(vty, "./version-send", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_send_version(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + switch (yang_dnode_get_enum(dnode, NULL)) { + case RI_RIP_UNSPEC: + vty_out(vty, " no ip rip send version\n"); + break; + case RI_RIP_VERSION_1: + vty_out(vty, " ip rip send version 1\n"); + break; + case RI_RIP_VERSION_2: + vty_out(vty, " ip rip send version 2\n"); + break; + case RI_RIP_VERSION_1_AND_2: + vty_out(vty, " ip rip send version 1 2\n"); + break; + case RI_RIP_VERSION_NONE: + vty_out(vty, " ip rip send version none\n"); + break; + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme + */ +DEFPY_YANG (ip_rip_authentication_mode, + ip_rip_authentication_mode_cmd, + "ip rip authentication mode $auth_length]|text$mode>", + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication mode\n" + "Keyed message digest\n" + "MD5 authentication data length\n" + "RFC compatible\n" + "Old ripd compatible\n" + "Clear text authentication\n") +{ + const char *value = NULL; + + if (auth_length) { + if (strmatch(auth_length, "rfc")) + value = "16"; + else + value = "20"; + } + + nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY, + strmatch(mode, "md5") ? "md5" : "plain-text"); + if (strmatch(mode, "md5")) + nb_cli_enqueue_change(vty, + "./authentication-scheme/md5-auth-length", + NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG (no_ip_rip_authentication_mode, + no_ip_rip_authentication_mode_cmd, + "no ip rip authentication mode []|text>]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication mode\n" + "Keyed message digest\n" + "MD5 authentication data length\n" + "RFC compatible\n" + "Old ripd compatible\n" + "Clear text authentication\n") +{ + nb_cli_enqueue_change(vty, "./authentication-scheme/mode", NB_OP_MODIFY, + NULL); + nb_cli_enqueue_change(vty, "./authentication-scheme/md5-auth-length", + NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_authentication_scheme(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + switch (yang_dnode_get_enum(dnode, "mode")) { + case RIP_NO_AUTH: + vty_out(vty, " no ip rip authentication mode\n"); + break; + case RIP_AUTH_SIMPLE_PASSWORD: + vty_out(vty, " ip rip authentication mode text\n"); + break; + case RIP_AUTH_MD5: + vty_out(vty, " ip rip authentication mode md5"); + if (show_defaults + || !yang_dnode_is_default(dnode, "md5-auth-length")) { + if (yang_dnode_get_enum(dnode, "md5-auth-length") + == RIP_AUTH_MD5_SIZE) + vty_out(vty, " auth-length rfc"); + else + vty_out(vty, " auth-length old-ripd"); + } + vty_out(vty, "\n"); + break; + } +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-password + */ +DEFPY_YANG (ip_rip_authentication_string, + ip_rip_authentication_string_cmd, + "ip rip authentication string LINE$password", + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication string\n" + "Authentication string\n") +{ + if (strlen(password) > 16) { + vty_out(vty, + "%% RIPv2 authentication string must be shorter than 16\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (yang_dnode_existsf(vty->candidate_config->dnode, "%s%s", + VTY_CURR_XPATH, + "/frr-ripd:rip/authentication-key-chain")) { + vty_out(vty, "%% key-chain configuration exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_MODIFY, + password); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG (no_ip_rip_authentication_string, + no_ip_rip_authentication_string_cmd, + "no ip rip authentication string [LINE]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication string\n" + "Authentication string\n") +{ + nb_cli_enqueue_change(vty, "./authentication-password", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_authentication_string(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " ip rip authentication string %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain + */ +DEFPY_YANG (ip_rip_authentication_key_chain, + ip_rip_authentication_key_chain_cmd, + "ip rip authentication key-chain LINE$keychain", + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication key-chain\n" + "name of key-chain\n") +{ + if (yang_dnode_existsf(vty->candidate_config->dnode, "%s%s", + VTY_CURR_XPATH, + "/frr-ripd:rip/authentication-password")) { + vty_out(vty, "%% authentication string configuration exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_MODIFY, + keychain); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG (no_ip_rip_authentication_key_chain, + no_ip_rip_authentication_key_chain_cmd, + "no ip rip authentication key-chain [LINE]", + NO_STR + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication key-chain\n" + "name of key-chain\n") +{ + nb_cli_enqueue_change(vty, "./authentication-key-chain", NB_OP_DESTROY, + NULL); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_authentication_key_chain(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " ip rip authentication key-chain %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/enable + */ +DEFPY_YANG(ip_rip_bfd, ip_rip_bfd_cmd, "[no] ip rip bfd", + NO_STR IP_STR + "Routing Information Protocol\n" + "Enable BFD support\n") +{ + nb_cli_enqueue_change(vty, "./bfd-monitoring/enable", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_bfd_enable(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " ip rip bfd\n"); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/bfd/profile + */ +DEFPY_YANG(ip_rip_bfd_profile, ip_rip_bfd_profile_cmd, + "[no] ip rip bfd profile BFDPROF$profile", + NO_STR IP_STR + "Routing Information Protocol\n" + "Enable BFD support\n" + "Use a pre-configured profile\n" + "Profile name\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./bfd-monitoring/profile", + NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./bfd-monitoring/profile", + NB_OP_MODIFY, profile); + + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +DEFPY_YANG(no_ip_rip_bfd_profile, no_ip_rip_bfd_profile_cmd, + "no ip rip bfd profile", + NO_STR IP_STR + "Routing Information Protocol\n" + "Enable BFD support\n" + "Use a pre-configured profile\n") +{ + nb_cli_enqueue_change(vty, "./bfd-monitoring/profile", NB_OP_DESTROY, + NULL); + return nb_cli_apply_changes(vty, "./frr-ripd:rip"); +} + +void cli_show_ip_rip_bfd_profile(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " ip rip bfd profile %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +DEFPY_YANG( + rip_distribute_list, rip_distribute_list_cmd, + "distribute-list ACCESSLIST4_NAME$name $dir [WORD$ifname]", + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + rip_distribute_list_prefix, rip_distribute_list_prefix_cmd, + "distribute-list prefix PREFIXLIST4_NAME$name $dir [WORD$ifname]", + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_rip_distribute_list, + no_rip_distribute_list_cmd, + "no distribute-list [ACCESSLIST4_NAME$name] $dir [WORD$ifname]", + NO_STR + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_rip_distribute_list_prefix, + no_rip_distribute_list_prefix_cmd, + "no distribute-list prefix [PREFIXLIST4_NAME$name] $dir [WORD$ifname]", + NO_STR + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-ripd:clear-rip-route + */ +DEFPY_YANG (clear_ip_rip, + clear_ip_rip_cmd, + "clear ip rip [vrf WORD]", + CLEAR_STR + IP_STR + "Clear IP RIP database\n" + VRF_CMD_HELP_STR) +{ + if (vrf) + nb_cli_rpc_enqueue(vty, "vrf", vrf); + + return nb_cli_rpc(vty, "/frr-ripd:clear-rip-route", NULL); +} + +/* RIP node structure. */ +static struct cmd_node rip_node = { + .name = "rip", + .node = RIP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", + // .config_write = config_write_rip, +}; + +void rip_cli_init(void) +{ + install_node(&rip_node); + + install_element(CONFIG_NODE, &router_rip_cmd); + install_element(CONFIG_NODE, &no_router_rip_cmd); + + install_element(RIP_NODE, &rip_distribute_list_cmd); + install_element(RIP_NODE, &rip_distribute_list_prefix_cmd); + install_element(RIP_NODE, &no_rip_distribute_list_cmd); + install_element(RIP_NODE, &no_rip_distribute_list_prefix_cmd); + + install_element(RIP_NODE, &rip_allow_ecmp_cmd); + install_element(RIP_NODE, &no_rip_allow_ecmp_cmd); + install_element(RIP_NODE, &rip_default_information_originate_cmd); + install_element(RIP_NODE, &rip_default_metric_cmd); + install_element(RIP_NODE, &no_rip_default_metric_cmd); + install_element(RIP_NODE, &rip_distance_cmd); + install_element(RIP_NODE, &no_rip_distance_cmd); + install_element(RIP_NODE, &rip_distance_source_cmd); + install_element(RIP_NODE, &rip_neighbor_cmd); + install_element(RIP_NODE, &rip_network_prefix_cmd); + install_element(RIP_NODE, &rip_network_if_cmd); + install_element(RIP_NODE, &rip_offset_list_cmd); + install_element(RIP_NODE, &rip_passive_default_cmd); + install_element(RIP_NODE, &rip_passive_interface_cmd); + install_element(RIP_NODE, &rip_redistribute_cmd); + install_element(RIP_NODE, &rip_route_cmd); + install_element(RIP_NODE, &rip_timers_cmd); + install_element(RIP_NODE, &no_rip_timers_cmd); + install_element(RIP_NODE, &rip_version_cmd); + install_element(RIP_NODE, &no_rip_version_cmd); + install_element(RIP_NODE, &rip_bfd_default_profile_cmd); + install_element(RIP_NODE, &no_rip_bfd_default_profile_cmd); + install_default(RIP_NODE); + + install_element(INTERFACE_NODE, &ip_rip_split_horizon_cmd); + install_element(INTERFACE_NODE, &ip_rip_v2_broadcast_cmd); + install_element(INTERFACE_NODE, &ip_rip_receive_version_cmd); + install_element(INTERFACE_NODE, &no_ip_rip_receive_version_cmd); + install_element(INTERFACE_NODE, &ip_rip_send_version_cmd); + install_element(INTERFACE_NODE, &no_ip_rip_send_version_cmd); + install_element(INTERFACE_NODE, &ip_rip_authentication_mode_cmd); + install_element(INTERFACE_NODE, &no_ip_rip_authentication_mode_cmd); + install_element(INTERFACE_NODE, &ip_rip_authentication_string_cmd); + install_element(INTERFACE_NODE, &no_ip_rip_authentication_string_cmd); + install_element(INTERFACE_NODE, &ip_rip_authentication_key_chain_cmd); + install_element(INTERFACE_NODE, + &no_ip_rip_authentication_key_chain_cmd); + install_element(INTERFACE_NODE, &ip_rip_bfd_cmd); + install_element(INTERFACE_NODE, &ip_rip_bfd_profile_cmd); + install_element(INTERFACE_NODE, &no_ip_rip_bfd_profile_cmd); + + install_element(ENABLE_NODE, &clear_ip_rip_cmd); + + if_rmap_init(RIP_NODE); +} +/* clang-format off */ +const struct frr_yang_module_info frr_ripd_cli_info = { + .name = "frr-ripd", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-ripd:ripd/instance", + .cbs.cli_show = cli_show_router_rip, + .cbs.cli_show_end = cli_show_end_router_rip, + }, + { + .xpath = "/frr-ripd:ripd/instance/allow-ecmp", + .cbs.cli_show = cli_show_rip_allow_ecmp, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-information-originate", + .cbs.cli_show = cli_show_rip_default_information_originate, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-metric", + .cbs.cli_show = cli_show_rip_default_metric, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/default", + .cbs.cli_show = cli_show_rip_distance, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/source", + .cbs.cli_show = cli_show_rip_distance_source, + }, + { + .xpath = "/frr-ripd:ripd/instance/explicit-neighbor", + .cbs.cli_show = cli_show_rip_neighbor, + }, + { + .xpath = "/frr-ripd:ripd/instance/network", + .cbs.cli_show = cli_show_rip_network_prefix, + }, + { + .xpath = "/frr-ripd:ripd/instance/interface", + .cbs.cli_show = cli_show_rip_network_interface, + }, + { + .xpath = "/frr-ripd:ripd/instance/offset-list", + .cbs.cli_show = cli_show_rip_offset_list, + }, + { + .xpath = "/frr-ripd:ripd/instance/passive-default", + .cbs.cli_show = cli_show_rip_passive_default, + }, + { + .xpath = "/frr-ripd:ripd/instance/passive-interface", + .cbs.cli_show = cli_show_rip_passive_interface, + }, + { + .xpath = "/frr-ripd:ripd/instance/non-passive-interface", + .cbs.cli_show = cli_show_rip_non_passive_interface, + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/in/access-list", + .cbs.cli_show = group_distribute_list_ipv4_cli_show, + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/out/access-list", + .cbs.cli_show = group_distribute_list_ipv4_cli_show, + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/in/prefix-list", + .cbs.cli_show = group_distribute_list_ipv4_cli_show, + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/out/prefix-list", + .cbs.cli_show = group_distribute_list_ipv4_cli_show, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute", + .cbs.cli_show = cli_show_rip_redistribute, + }, + { + .xpath = "/frr-ripd:ripd/instance/if-route-maps/if-route-map", + .cbs.cli_show = cli_show_if_route_map, + }, + { + .xpath = "/frr-ripd:ripd/instance/static-route", + .cbs.cli_show = cli_show_rip_route, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers", + .cbs.cli_show = cli_show_rip_timers, + }, + { + .xpath = "/frr-ripd:ripd/instance/version", + .cbs.cli_show = cli_show_rip_version, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-bfd-profile", + .cbs.cli_show = cli_show_ripd_instance_default_bfd_profile, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/split-horizon", + .cbs.cli_show = cli_show_ip_rip_split_horizon, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/v2-broadcast", + .cbs.cli_show = cli_show_ip_rip_v2_broadcast, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-receive", + .cbs.cli_show = cli_show_ip_rip_receive_version, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-send", + .cbs.cli_show = cli_show_ip_rip_send_version, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme", + .cbs.cli_show = cli_show_ip_rip_authentication_scheme, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-password", + .cbs.cli_show = cli_show_ip_rip_authentication_string, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain", + .cbs.cli_show = cli_show_ip_rip_authentication_key_chain, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/enable", + .cbs.cli_show = cli_show_ip_rip_bfd_enable, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/profile", + .cbs.cli_show = cli_show_ip_rip_bfd_profile, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ripd/rip_debug.c b/ripd/rip_debug.c new file mode 100644 index 0000000..e91d791 --- /dev/null +++ b/ripd/rip_debug.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP debug routines + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include +#include "command.h" +#include "ripd/rip_debug.h" + +/* For debug statement. */ +unsigned long rip_debug_event = 0; +unsigned long rip_debug_packet = 0; +unsigned long rip_debug_zebra = 0; + +DEFUN_NOSH (show_debugging_rip, + show_debugging_rip_cmd, + "show debugging [rip]", + SHOW_STR + DEBUG_STR + RIP_STR) +{ + vty_out(vty, "RIP debugging status:\n"); + + if (IS_RIP_DEBUG_EVENT) + vty_out(vty, " RIP event debugging is on\n"); + + if (IS_RIP_DEBUG_PACKET) { + if (IS_RIP_DEBUG_SEND && IS_RIP_DEBUG_RECV) { + vty_out(vty, " RIP packet debugging is on\n"); + } else { + if (IS_RIP_DEBUG_SEND) + vty_out(vty, + " RIP packet send debugging is on\n"); + else + vty_out(vty, + " RIP packet receive debugging is on\n"); + } + } + + if (IS_RIP_DEBUG_ZEBRA) + vty_out(vty, " RIP zebra debugging is on\n"); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFUN (debug_rip_events, + debug_rip_events_cmd, + "debug rip events", + DEBUG_STR + RIP_STR + "RIP events\n") +{ + rip_debug_event = RIP_DEBUG_EVENT; + return CMD_SUCCESS; +} + +DEFUN (debug_rip_packet, + debug_rip_packet_cmd, + "debug rip packet", + DEBUG_STR + RIP_STR + "RIP packet\n") +{ + rip_debug_packet = RIP_DEBUG_PACKET; + rip_debug_packet |= RIP_DEBUG_SEND; + rip_debug_packet |= RIP_DEBUG_RECV; + return CMD_SUCCESS; +} + +DEFUN (debug_rip_packet_direct, + debug_rip_packet_direct_cmd, + "debug rip packet ", + DEBUG_STR + RIP_STR + "RIP packet\n" + "RIP receive packet\n" + "RIP send packet\n") +{ + int idx_recv_send = 3; + rip_debug_packet |= RIP_DEBUG_PACKET; + if (strcmp("send", argv[idx_recv_send]->text) == 0) + rip_debug_packet |= RIP_DEBUG_SEND; + if (strcmp("recv", argv[idx_recv_send]->text) == 0) + rip_debug_packet |= RIP_DEBUG_RECV; + return CMD_SUCCESS; +} + +DEFUN (debug_rip_zebra, + debug_rip_zebra_cmd, + "debug rip zebra", + DEBUG_STR + RIP_STR + "RIP and ZEBRA communication\n") +{ + rip_debug_zebra = RIP_DEBUG_ZEBRA; + return CMD_SUCCESS; +} + +DEFUN (no_debug_rip_events, + no_debug_rip_events_cmd, + "no debug rip events", + NO_STR + DEBUG_STR + RIP_STR + "RIP events\n") +{ + rip_debug_event = 0; + return CMD_SUCCESS; +} + +DEFUN (no_debug_rip_packet, + no_debug_rip_packet_cmd, + "no debug rip packet", + NO_STR + DEBUG_STR + RIP_STR + "RIP packet\n") +{ + rip_debug_packet = 0; + return CMD_SUCCESS; +} + +DEFUN (no_debug_rip_packet_direct, + no_debug_rip_packet_direct_cmd, + "no debug rip packet ", + NO_STR + DEBUG_STR + RIP_STR + "RIP packet\n" + "RIP option set for receive packet\n" + "RIP option set for send packet\n") +{ + int idx_recv_send = 4; + if (strcmp("send", argv[idx_recv_send]->text) == 0) { + if (IS_RIP_DEBUG_RECV) + rip_debug_packet &= ~RIP_DEBUG_SEND; + else + rip_debug_packet = 0; + } else if (strcmp("recv", argv[idx_recv_send]->text) == 0) { + if (IS_RIP_DEBUG_SEND) + rip_debug_packet &= ~RIP_DEBUG_RECV; + else + rip_debug_packet = 0; + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_rip_zebra, + no_debug_rip_zebra_cmd, + "no debug rip zebra", + NO_STR + DEBUG_STR + RIP_STR + "RIP and ZEBRA communication\n") +{ + rip_debug_zebra = 0; + return CMD_SUCCESS; +} + +static int config_write_debug(struct vty *vty); +/* Debug node. */ +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_debug, +}; + +static int config_write_debug(struct vty *vty) +{ + int write = 0; + + if (IS_RIP_DEBUG_EVENT) { + vty_out(vty, "debug rip events\n"); + write++; + } + if (IS_RIP_DEBUG_PACKET) { + if (IS_RIP_DEBUG_SEND && IS_RIP_DEBUG_RECV) { + vty_out(vty, "debug rip packet\n"); + write++; + } else { + if (IS_RIP_DEBUG_SEND) + vty_out(vty, "debug rip packet send\n"); + else + vty_out(vty, "debug rip packet recv\n"); + write++; + } + } + if (IS_RIP_DEBUG_ZEBRA) { + vty_out(vty, "debug rip zebra\n"); + write++; + } + return write; +} + +void rip_debug_init(void) +{ + rip_debug_event = 0; + rip_debug_packet = 0; + rip_debug_zebra = 0; + + install_node(&debug_node); + + install_element(ENABLE_NODE, &show_debugging_rip_cmd); + install_element(ENABLE_NODE, &debug_rip_events_cmd); + install_element(ENABLE_NODE, &debug_rip_packet_cmd); + install_element(ENABLE_NODE, &debug_rip_packet_direct_cmd); + install_element(ENABLE_NODE, &debug_rip_zebra_cmd); + install_element(ENABLE_NODE, &no_debug_rip_events_cmd); + install_element(ENABLE_NODE, &no_debug_rip_packet_cmd); + install_element(ENABLE_NODE, &no_debug_rip_packet_direct_cmd); + install_element(ENABLE_NODE, &no_debug_rip_zebra_cmd); + + install_element(CONFIG_NODE, &debug_rip_events_cmd); + install_element(CONFIG_NODE, &debug_rip_packet_cmd); + install_element(CONFIG_NODE, &debug_rip_packet_direct_cmd); + install_element(CONFIG_NODE, &debug_rip_zebra_cmd); + install_element(CONFIG_NODE, &no_debug_rip_events_cmd); + install_element(CONFIG_NODE, &no_debug_rip_packet_cmd); + install_element(CONFIG_NODE, &no_debug_rip_packet_direct_cmd); + install_element(CONFIG_NODE, &no_debug_rip_zebra_cmd); +} diff --git a/ripd/rip_debug.h b/ripd/rip_debug.h new file mode 100644 index 0000000..ca9188f --- /dev/null +++ b/ripd/rip_debug.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP debug routines + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_RIP_DEBUG_H +#define _ZEBRA_RIP_DEBUG_H + +/* RIP debug event flags. */ +#define RIP_DEBUG_EVENT 0x01 + +/* RIP debug packet flags. */ +#define RIP_DEBUG_PACKET 0x01 +#define RIP_DEBUG_SEND 0x20 +#define RIP_DEBUG_RECV 0x40 +#define RIP_DEBUG_DETAIL 0x80 + +/* RIP debug zebra flags. */ +#define RIP_DEBUG_ZEBRA 0x01 + +/* Debug related macro. */ +#define IS_RIP_DEBUG_EVENT (rip_debug_event & RIP_DEBUG_EVENT) + +#define IS_RIP_DEBUG_PACKET (rip_debug_packet & RIP_DEBUG_PACKET) +#define IS_RIP_DEBUG_SEND (rip_debug_packet & RIP_DEBUG_SEND) +#define IS_RIP_DEBUG_RECV (rip_debug_packet & RIP_DEBUG_RECV) + +#define IS_RIP_DEBUG_ZEBRA (rip_debug_zebra & RIP_DEBUG_ZEBRA) + +extern unsigned long rip_debug_event; +extern unsigned long rip_debug_packet; +extern unsigned long rip_debug_zebra; + +extern void rip_debug_init(void); + +#endif /* _ZEBRA_RIP_DEBUG_H */ diff --git a/ripd/rip_errors.c b/ripd/rip_errors.c new file mode 100644 index 0000000..483ea77 --- /dev/null +++ b/ripd/rip_errors.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#include + +#include "lib/ferr.h" +#include "rip_errors.h" + +static struct log_ref ferr_rip_err[] = { + {.code = EC_RIP_PACKET, + .title = "RIP Packet Error", + .description = "RIP has detected a packet encode/decode issue", + .suggestion = "Gather log files from both sides and open a Issue"}, + { + .code = END_FERR, + }}; + +void rip_error_init(void) +{ + log_ref_add(ferr_rip_err); +} diff --git a/ripd/rip_errors.h b/ripd/rip_errors.h new file mode 100644 index 0000000..93b4474 --- /dev/null +++ b/ripd/rip_errors.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIP-specific error messages. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ + +#ifndef __RIP_ERRORS_H__ +#define __RIP_ERRORS_H__ + +#include "lib/ferr.h" + +enum rip_log_refs { + EC_RIP_PACKET = RIP_FERR_START, + RIP_ERR_CONFIG, +}; + +extern void rip_error_init(void); + +#endif diff --git a/ripd/rip_interface.c b/ripd/rip_interface.c new file mode 100644 index 0000000..486d7b0 --- /dev/null +++ b/ripd/rip_interface.c @@ -0,0 +1,1116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Interface related function for RIP. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "if.h" +#include "sockunion.h" +#include "prefix.h" +#include "memory.h" +#include "network.h" +#include "table.h" +#include "log.h" +#include "stream.h" +#include "frrevent.h" +#include "zclient.h" +#include "filter.h" +#include "sockopt.h" +#include "privs.h" +#include "lib_errors.h" +#include "northbound_cli.h" + +#include "zebra/connected.h" + +#include "ripd/ripd.h" +#include "ripd/rip_bfd.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" + +DEFINE_MTYPE_STATIC(RIPD, RIP_INTERFACE, "RIP interface"); +DEFINE_MTYPE(RIPD, RIP_INTERFACE_STRING, "RIP Interface String"); +DEFINE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc)); +DEFINE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc)); + +/* static prototypes */ +static void rip_enable_apply(struct interface *); +static void rip_passive_interface_apply(struct interface *); +static int rip_if_down(struct interface *ifp); +static int rip_enable_if_lookup(struct rip *rip, const char *ifname); +static int rip_enable_network_lookup2(struct connected *connected); +static void rip_enable_apply_all(struct rip *rip); + +const struct message ri_version_msg[] = {{RI_RIP_VERSION_1, "1"}, + {RI_RIP_VERSION_2, "2"}, + {RI_RIP_VERSION_1_AND_2, "1 2"}, + {RI_RIP_VERSION_NONE, "none"}, + {0}}; + +/* Join to the RIP version 2 multicast group. */ +static int ipv4_multicast_join(int sock, struct in_addr group, + struct in_addr ifa, ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(sock, IP_ADD_MEMBERSHIP, ifa, + group.s_addr, ifindex); + + if (ret < 0) + zlog_info("can't setsockopt IP_ADD_MEMBERSHIP %s", + safe_strerror(errno)); + + return ret; +} + +/* Leave from the RIP version 2 multicast group. */ +static int ipv4_multicast_leave(int sock, struct in_addr group, + struct in_addr ifa, ifindex_t ifindex) +{ + int ret; + + ret = setsockopt_ipv4_multicast(sock, IP_DROP_MEMBERSHIP, ifa, + group.s_addr, ifindex); + + if (ret < 0) + zlog_info("can't setsockopt IP_DROP_MEMBERSHIP"); + + return ret; +} + +static void rip_interface_reset(struct rip_interface *); + +/* Allocate new RIP's interface configuration. */ +static struct rip_interface *rip_interface_new(void) +{ + struct rip_interface *ri; + + ri = XCALLOC(MTYPE_RIP_INTERFACE, sizeof(struct rip_interface)); + + rip_interface_reset(ri); + + return ri; +} + +void rip_interface_multicast_set(int sock, struct connected *connected) +{ + struct in_addr addr; + + assert(connected != NULL); + + addr = CONNECTED_ID(connected)->u.prefix4; + + if (setsockopt_ipv4_multicast_if(sock, addr, connected->ifp->ifindex) + < 0) { + zlog_warn( + "Can't setsockopt IP_MULTICAST_IF on fd %d to ifindex %d for interface %s", + sock, connected->ifp->ifindex, connected->ifp->name); + } + + return; +} + +/* Send RIP request packet to specified interface. */ +static void rip_request_interface_send(struct interface *ifp, uint8_t version) +{ + struct sockaddr_in to; + + /* RIPv2 support multicast. */ + if (version == RIPv2 && if_is_multicast(ifp)) { + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("multicast request on %s", ifp->name); + + rip_request_send(NULL, ifp, version, NULL); + return; + } + + /* RIPv1 and non multicast interface. */ + if (if_is_pointopoint(ifp) || if_is_broadcast(ifp)) { + struct connected *connected; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("broadcast request to %s", ifp->name); + + frr_each (if_connected, ifp->connected, connected) { + if (connected->address->family != AF_INET) + continue; + + memset(&to, 0, sizeof(struct sockaddr_in)); + to.sin_port = htons(RIP_PORT_DEFAULT); + if (connected->destination) + /* use specified broadcast or peer + * destination addr */ + to.sin_addr = connected->destination->u.prefix4; + else if (connected->address->prefixlen + < IPV4_MAX_BITLEN) + /* calculate the appropriate broadcast + * address */ + to.sin_addr.s_addr = ipv4_broadcast_addr( + connected->address->u.prefix4.s_addr, + connected->address->prefixlen); + else + /* do not know where to send the packet + */ + continue; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("SEND request to %pI4", + &to.sin_addr); + + rip_request_send(&to, ifp, version, connected); + } + } +} + +/* This will be executed when interface goes up. */ +static void rip_request_interface(struct interface *ifp) +{ + struct rip_interface *ri; + int vsend; + + /* In default ripd doesn't send RIP_REQUEST to the loopback interface. + */ + if (if_is_loopback(ifp)) + return; + + /* If interface is down, don't send RIP packet. */ + if (!if_is_operative(ifp)) + return; + + /* Fetch RIP interface information. */ + ri = ifp->info; + + /* If there is no version configuration in the interface, + use rip's version setting. */ + vsend = ((ri->ri_send == RI_RIP_UNSPEC) ? ri->rip->version_send + : ri->ri_send); + if (vsend & RIPv1) + rip_request_interface_send(ifp, RIPv1); + if (vsend & RIPv2) + rip_request_interface_send(ifp, RIPv2); +} + +/* Multicast packet receive socket. */ +static int rip_multicast_join(struct interface *ifp, int sock) +{ + struct connected *ifc; + + if (if_is_operative(ifp) && if_is_multicast(ifp)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("multicast join at %s", ifp->name); + + frr_each (if_connected, ifp->connected, ifc) { + struct prefix_ipv4 *p; + struct in_addr group; + + p = (struct prefix_ipv4 *)ifc->address; + + if (p->family != AF_INET) + continue; + + group.s_addr = htonl(INADDR_RIP_GROUP); + if (ipv4_multicast_join(sock, group, p->prefix, + ifp->ifindex) + < 0) + return -1; + else + return 0; + } + } + return 0; +} + +/* Leave from multicast group. */ +static void rip_multicast_leave(struct interface *ifp, int sock) +{ + struct connected *connected; + + if (if_is_up(ifp) && if_is_multicast(ifp)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("multicast leave from %s", ifp->name); + + frr_each (if_connected, ifp->connected, connected) { + struct prefix_ipv4 *p; + struct in_addr group; + + p = (struct prefix_ipv4 *)connected->address; + + if (p->family != AF_INET) + continue; + + group.s_addr = htonl(INADDR_RIP_GROUP); + if (ipv4_multicast_leave(sock, group, p->prefix, + ifp->ifindex) + == 0) + return; + } + } +} + +/* Is there and address on interface that I could use ? */ +static int rip_if_ipv4_address_check(struct interface *ifp) +{ + struct connected *connected; + int count = 0; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + + p = connected->address; + + if (p->family == AF_INET) + count++; + } + + return count; +} + + +/* Does this address belongs to me ? */ +int if_check_address(struct rip *rip, struct in_addr addr) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (rip->vrf, ifp) { + struct connected *connected; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix_ipv4 *p; + + p = (struct prefix_ipv4 *)connected->address; + + if (p->family != AF_INET) + continue; + + if (IPV4_ADDR_CMP(&p->prefix, &addr) == 0) + return 1; + } + } + return 0; +} + +/* Interface link down message processing. */ +static int rip_ifp_down(struct interface *ifp) +{ + rip_interface_sync(ifp); + rip_if_down(ifp); + + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug( + "interface %s vrf %s(%u) index %d flags %llx metric %d mtu %d is down", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + return 0; +} + +/* Interface link up message processing */ +static int rip_ifp_up(struct interface *ifp) +{ + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug( + "interface %s vrf %s(%u) index %d flags %#llx metric %d mtu %d is up", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + rip_interface_sync(ifp); + + /* Check if this interface is RIP enabled or not.*/ + rip_enable_apply(ifp); + + /* Check for a passive interface */ + rip_passive_interface_apply(ifp); + + /* Apply distribute list to the all interface. */ + rip_distribute_update_interface(ifp); + + return 0; +} + +/* Interface addition message from zebra. */ +static int rip_ifp_create(struct interface *ifp) +{ + rip_interface_sync(ifp); + + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug( + "interface add %s vrf %s(%u) index %d flags %#llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + /* Check if this interface is RIP enabled or not.*/ + rip_enable_apply(ifp); + + /* Check for a passive interface */ + rip_passive_interface_apply(ifp); + + /* Apply distribute list to the all interface. */ + rip_distribute_update_interface(ifp); + + /* rip_request_neighbor_all (); */ + + /* Check interface routemap. */ + rip_if_rmap_update_interface(ifp); + + return 0; +} + +static int rip_ifp_destroy(struct interface *ifp) +{ + rip_interface_sync(ifp); + if (if_is_up(ifp)) { + rip_if_down(ifp); + } + + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug( + "interface delete %s vrf %s(%u) index %d flags %#llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu); + + return 0; +} + +static void rip_interface_clean(struct rip_interface *ri) +{ + ri->enable_network = 0; + ri->enable_interface = 0; + ri->running = 0; + + EVENT_OFF(ri->t_wakeup); +} + +void rip_interfaces_clean(struct rip *rip) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (rip->vrf, ifp) + rip_interface_clean(ifp->info); +} + +static void rip_interface_reset(struct rip_interface *ri) +{ + ri->auth_type = yang_get_default_enum("%s/authentication-scheme/mode", + RIP_IFACE); + ri->md5_auth_len = yang_get_default_enum( + "%s/authentication-scheme/md5-auth-length", RIP_IFACE); + + /* Set default split-horizon behavior. If the interface is Frame + Relay or SMDS is enabled, the default value for split-horizon is + off. But currently Zebra does detect Frame Relay or SMDS + interface. So all interface is set to split horizon. */ + ri->split_horizon = + yang_get_default_enum("%s/split-horizon", RIP_IFACE); + + ri->ri_send = yang_get_default_enum("%s/version-send", RIP_IFACE); + ri->ri_receive = yang_get_default_enum("%s/version-receive", RIP_IFACE); + ri->v2_broadcast = yang_get_default_bool("%s/v2-broadcast", RIP_IFACE); + + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); + + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); + + ri->list[RIP_FILTER_IN] = NULL; + ri->list[RIP_FILTER_OUT] = NULL; + + ri->prefix[RIP_FILTER_IN] = NULL; + ri->prefix[RIP_FILTER_OUT] = NULL; + + ri->recv_badpackets = 0; + ri->recv_badroutes = 0; + ri->sent_updates = 0; + + ri->passive = 0; + XFREE(MTYPE_RIP_BFD_PROFILE, ri->bfd.profile); + + rip_interface_clean(ri); +} + +int rip_if_down(struct interface *ifp) +{ + struct rip *rip; + struct route_node *rp; + struct rip_info *rinfo; + struct rip_interface *ri = NULL; + struct list *list = NULL; + struct listnode *listnode = NULL, *nextnode = NULL; + + ri = ifp->info; + + EVENT_OFF(ri->t_wakeup); + + rip = ri->rip; + if (rip) { + for (rp = route_top(rip->table); rp; rp = route_next(rp)) + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS(list, listnode, nextnode, + rinfo)) + if (rinfo->nh.ifindex == ifp->ifindex) + rip_ecmp_delete(rip, rinfo); + + if (ri->running) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("turn off %s", ifp->name); + + /* Leave from multicast group. */ + rip_multicast_leave(ifp, rip->sock); + + ri->running = 0; + } + } + + return 0; +} + +static void rip_apply_address_add(struct connected *ifc) +{ + struct rip_interface *ri = ifc->ifp->info; + struct rip *rip = ri->rip; + struct prefix_ipv4 address; + struct nexthop nh; + struct prefix *p; + + if (!rip) + return; + + if (!if_is_up(ifc->ifp)) + return; + + p = ifc->address; + + memset(&address, 0, sizeof(address)); + memset(&nh, 0, sizeof(nh)); + + address.family = p->family; + address.prefix = p->u.prefix4; + address.prefixlen = p->prefixlen; + apply_mask_ipv4(&address); + + nh.ifindex = ifc->ifp->ifindex; + nh.type = NEXTHOP_TYPE_IFINDEX; + + /* Check if this interface is RIP enabled or not + or Check if this address's prefix is RIP enabled */ + if ((rip_enable_if_lookup(rip, ifc->ifp->name) >= 0) + || (rip_enable_network_lookup2(ifc) >= 0)) + rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, + RIP_ROUTE_INTERFACE, &address, &nh, 0, 0, + 0); +} + +int rip_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct prefix *p; + + ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, + zclient->ibuf, vrf_id); + + if (ifc == NULL) + return 0; + + p = ifc->address; + + if (p->family == AF_INET) { + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug("connected address %pFX is added", p); + + rip_enable_apply(ifc->ifp); + /* Check if this prefix needs to be redistributed */ + rip_apply_address_add(ifc); + + hook_call(rip_ifaddr_add, ifc); + } + + return 0; +} + +static void rip_apply_address_del(struct connected *ifc) +{ + struct rip_interface *ri = ifc->ifp->info; + struct rip *rip = ri->rip; + struct prefix_ipv4 address; + struct prefix *p; + + if (!rip) + return; + + if (!if_is_up(ifc->ifp)) + return; + + p = ifc->address; + + memset(&address, 0, sizeof(address)); + address.family = p->family; + address.prefix = p->u.prefix4; + address.prefixlen = p->prefixlen; + apply_mask_ipv4(&address); + + rip_redistribute_delete(rip, ZEBRA_ROUTE_CONNECT, RIP_ROUTE_INTERFACE, + &address, ifc->ifp->ifindex); +} + +int rip_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct prefix *p; + + ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, + zclient->ibuf, vrf_id); + + if (ifc) { + p = ifc->address; + if (p->family == AF_INET) { + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug("connected address %pFX is deleted", + p); + + hook_call(rip_ifaddr_del, ifc); + + /* Chech whether this prefix needs to be removed */ + rip_apply_address_del(ifc); + } + + connected_free(&ifc); + } + + return 0; +} + +/* Check interface is enabled by network statement. */ +/* Check whether the interface has at least a connected prefix that + * is within the ripng_enable_network table. */ +static int rip_enable_network_lookup_if(struct interface *ifp) +{ + struct rip_interface *ri = ifp->info; + struct rip *rip = ri->rip; + struct connected *connected; + struct prefix_ipv4 address; + + if (!rip) + return -1; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + struct route_node *n; + + p = connected->address; + + if (p->family == AF_INET) { + address.family = AF_INET; + address.prefix = p->u.prefix4; + address.prefixlen = IPV4_MAX_BITLEN; + + n = route_node_match(rip->enable_network, + (struct prefix *)&address); + if (n) { + route_unlock_node(n); + return 1; + } + } + } + return -1; +} + +/* Check whether connected is within the ripng_enable_network table. */ +static int rip_enable_network_lookup2(struct connected *connected) +{ + struct rip_interface *ri = connected->ifp->info; + struct rip *rip = ri->rip; + struct prefix_ipv4 address; + struct prefix *p; + + p = connected->address; + + if (p->family == AF_INET) { + struct route_node *node; + + address.family = p->family; + address.prefix = p->u.prefix4; + address.prefixlen = IPV4_MAX_BITLEN; + + /* LPM on p->family, p->u.prefix4/IPV4_MAX_BITLEN within + * rip->enable_network */ + node = route_node_match(rip->enable_network, + (struct prefix *)&address); + + if (node) { + route_unlock_node(node); + return 1; + } + } + + return -1; +} +/* Add RIP enable network. */ +int rip_enable_network_add(struct rip *rip, struct prefix *p) +{ + struct route_node *node; + + node = route_node_get(rip->enable_network, p); + + if (node->info) { + route_unlock_node(node); + return NB_ERR_INCONSISTENCY; + } else + node->info = (void *)1; + + /* XXX: One should find a better solution than a generic one */ + rip_enable_apply_all(rip); + + return NB_OK; +} + +/* Delete RIP enable network. */ +int rip_enable_network_delete(struct rip *rip, struct prefix *p) +{ + struct route_node *node; + + node = route_node_lookup(rip->enable_network, p); + if (node) { + node->info = NULL; + + /* Unlock info lock. */ + route_unlock_node(node); + + /* Unlock lookup lock. */ + route_unlock_node(node); + + /* XXX: One should find a better solution than a generic one */ + rip_enable_apply_all(rip); + + return NB_OK; + } + + return NB_ERR_INCONSISTENCY; +} + +/* Check interface is enabled by ifname statement. */ +static int rip_enable_if_lookup(struct rip *rip, const char *ifname) +{ + unsigned int i; + char *str; + + if (!rip) + return -1; + + for (i = 0; i < vector_active(rip->enable_interface); i++) + if ((str = vector_slot(rip->enable_interface, i)) != NULL) + if (strcmp(str, ifname) == 0) + return i; + return -1; +} + +/* Add interface to rip_enable_if. */ +int rip_enable_if_add(struct rip *rip, const char *ifname) +{ + int ret; + + ret = rip_enable_if_lookup(rip, ifname); + if (ret >= 0) + return NB_ERR_INCONSISTENCY; + + vector_set(rip->enable_interface, + XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); + + rip_enable_apply_all(rip); /* TODOVJ */ + + return NB_OK; +} + +/* Delete interface from rip_enable_if. */ +int rip_enable_if_delete(struct rip *rip, const char *ifname) +{ + int index; + char *str; + + index = rip_enable_if_lookup(rip, ifname); + if (index < 0) + return NB_ERR_INCONSISTENCY; + + str = vector_slot(rip->enable_interface, index); + XFREE(MTYPE_RIP_INTERFACE_STRING, str); + vector_unset(rip->enable_interface, index); + + rip_enable_apply_all(rip); /* TODOVJ */ + + return NB_OK; +} + +/* Join to multicast group and send request to the interface. */ +static void rip_interface_wakeup(struct event *t) +{ + struct interface *ifp; + struct rip_interface *ri; + + /* Get interface. */ + ifp = EVENT_ARG(t); + + ri = ifp->info; + + /* Join to multicast group. */ + if (rip_multicast_join(ifp, ri->rip->sock) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "multicast join failed, interface %s not running", + ifp->name); + return; + } + + /* Set running flag. */ + ri->running = 1; + + /* Send RIP request to the interface. */ + rip_request_interface(ifp); +} + +static void rip_connect_set(struct interface *ifp, int set) +{ + struct rip_interface *ri = ifp->info; + struct rip *rip = ri->rip; + struct connected *connected; + struct prefix_ipv4 address; + struct nexthop nh; + + memset(&nh, 0, sizeof(nh)); + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + p = connected->address; + + if (p->family != AF_INET) + continue; + + address.family = AF_INET; + address.prefix = p->u.prefix4; + address.prefixlen = p->prefixlen; + apply_mask_ipv4(&address); + + nh.ifindex = connected->ifp->ifindex; + nh.type = NEXTHOP_TYPE_IFINDEX; + if (set) { + /* Check once more whether this prefix is within a + * "network IF_OR_PREF" one */ + if ((rip_enable_if_lookup(rip, connected->ifp->name) + >= 0) + || (rip_enable_network_lookup2(connected) >= 0)) + rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, + RIP_ROUTE_INTERFACE, + &address, &nh, 0, 0, 0); + } else { + rip_redistribute_delete(rip, ZEBRA_ROUTE_CONNECT, + RIP_ROUTE_INTERFACE, &address, + connected->ifp->ifindex); + if (rip_redistribute_check(rip, ZEBRA_ROUTE_CONNECT)) + rip_redistribute_add(rip, ZEBRA_ROUTE_CONNECT, + RIP_ROUTE_REDISTRIBUTE, + &address, &nh, 0, 0, 0); + } + } +} + +/* Update interface status. */ +void rip_enable_apply(struct interface *ifp) +{ + int ret; + struct rip_interface *ri = NULL; + + /* Check interface. */ + if (!if_is_operative(ifp)) + return; + + ri = ifp->info; + + /* Check network configuration. */ + ret = rip_enable_network_lookup_if(ifp); + + /* If the interface is matched. */ + if (ret > 0) + ri->enable_network = 1; + else + ri->enable_network = 0; + + /* Check interface name configuration. */ + ret = rip_enable_if_lookup(ri->rip, ifp->name); + if (ret >= 0) + ri->enable_interface = 1; + else + ri->enable_interface = 0; + + /* any interface MUST have an IPv4 address */ + if (!rip_if_ipv4_address_check(ifp)) { + ri->enable_network = 0; + ri->enable_interface = 0; + } + + /* Update running status of the interface. */ + if (ri->enable_network || ri->enable_interface) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("turn on %s", ifp->name); + + /* Add interface wake up thread. */ + event_add_timer(master, rip_interface_wakeup, ifp, 1, + &ri->t_wakeup); + rip_connect_set(ifp, 1); + } else if (ri->running) { + /* Might as well clean up the route table as well + * rip_if_down sets to 0 ri->running, and displays "turn + *off %s" + **/ + rip_if_down(ifp); + + rip_connect_set(ifp, 0); + } +} + +/* Apply network configuration to all interface. */ +static void rip_enable_apply_all(struct rip *rip) +{ + struct interface *ifp; + + /* Check each interface. */ + FOR_ALL_INTERFACES (rip->vrf, ifp) + rip_enable_apply(ifp); +} + +int rip_neighbor_lookup(struct rip *rip, struct sockaddr_in *from) +{ + struct prefix_ipv4 p; + struct route_node *node; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET; + p.prefix = from->sin_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + node = route_node_lookup(rip->neighbor, (struct prefix *)&p); + if (node) { + route_unlock_node(node); + return 1; + } + return 0; +} + +/* Add new RIP neighbor to the neighbor tree. */ +int rip_neighbor_add(struct rip *rip, struct prefix_ipv4 *p) +{ + struct route_node *node; + + node = route_node_get(rip->neighbor, (struct prefix *)p); + + if (node->info) + return NB_ERR_INCONSISTENCY; + + node->info = rip->neighbor; + + return NB_OK; +} + +/* Delete RIP neighbor from the neighbor tree. */ +int rip_neighbor_delete(struct rip *rip, struct prefix_ipv4 *p) +{ + struct route_node *node; + + /* Lock for look up. */ + node = route_node_lookup(rip->neighbor, (struct prefix *)p); + if (!node) + return NB_ERR_INCONSISTENCY; + + node->info = NULL; + + /* Unlock lookup lock. */ + route_unlock_node(node); + + /* Unlock real neighbor information lock. */ + route_unlock_node(node); + + return NB_OK; +} + +/* Clear all network and neighbor configuration. */ +void rip_clean_network(struct rip *rip) +{ + unsigned int i; + char *str; + struct route_node *rn; + + /* rip->enable_network. */ + for (rn = route_top(rip->enable_network); rn; rn = route_next(rn)) + if (rn->info) { + rn->info = NULL; + route_unlock_node(rn); + } + + /* rip->enable_interface. */ + for (i = 0; i < vector_active(rip->enable_interface); i++) + if ((str = vector_slot(rip->enable_interface, i)) != NULL) { + XFREE(MTYPE_RIP_INTERFACE_STRING, str); + vector_slot(rip->enable_interface, i) = NULL; + } +} + +/* Utility function for looking up passive interface settings. */ +static int rip_passive_nondefault_lookup(struct rip *rip, const char *ifname) +{ + unsigned int i; + char *str; + + for (i = 0; i < vector_active(rip->passive_nondefault); i++) + if ((str = vector_slot(rip->passive_nondefault, i)) != NULL) + if (strcmp(str, ifname) == 0) + return i; + return -1; +} + +static void rip_passive_interface_apply(struct interface *ifp) +{ + struct rip *rip; + struct rip_interface *ri; + + ri = ifp->info; + rip = ri->rip; + if (rip == NULL) + return; + + ri->passive = ((rip_passive_nondefault_lookup(rip, ifp->name) < 0) + ? rip->passive_default + : !rip->passive_default); + + if (IS_RIP_DEBUG_ZEBRA) + zlog_debug("interface %s: passive = %d", ifp->name, + ri->passive); +} + +static void rip_passive_interface_apply_all(struct rip *rip) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (rip->vrf, ifp) + rip_passive_interface_apply(ifp); +} + +/* Passive interface. */ +int rip_passive_nondefault_set(struct rip *rip, const char *ifname) +{ + if (rip_passive_nondefault_lookup(rip, ifname) >= 0) + /* + * Don't return an error, this can happen after changing + * 'passive-default'. + */ + return NB_OK; + + vector_set(rip->passive_nondefault, + XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); + + rip_passive_interface_apply_all(rip); + + return NB_OK; +} + +int rip_passive_nondefault_unset(struct rip *rip, const char *ifname) +{ + int i; + char *str; + + i = rip_passive_nondefault_lookup(rip, ifname); + if (i < 0) + /* + * Don't return an error, this can happen after changing + * 'passive-default'. + */ + return NB_OK; + + str = vector_slot(rip->passive_nondefault, i); + XFREE(MTYPE_RIP_INTERFACE_STRING, str); + vector_unset(rip->passive_nondefault, i); + + rip_passive_interface_apply_all(rip); + + return NB_OK; +} + +/* Free all configured RIP passive-interface settings. */ +void rip_passive_nondefault_clean(struct rip *rip) +{ + unsigned int i; + char *str; + + for (i = 0; i < vector_active(rip->passive_nondefault); i++) + if ((str = vector_slot(rip->passive_nondefault, i)) != NULL) { + XFREE(MTYPE_RIP_INTERFACE_STRING, str); + vector_slot(rip->passive_nondefault, i) = NULL; + } + rip_passive_interface_apply_all(rip); +} + +int rip_show_network_config(struct vty *vty, struct rip *rip) +{ + unsigned int i; + char *ifname; + struct route_node *node; + + /* Network type RIP enable interface statement. */ + for (node = route_top(rip->enable_network); node; + node = route_next(node)) + if (node->info) + vty_out(vty, " %pFX\n", &node->p); + + /* Interface name RIP enable statement. */ + for (i = 0; i < vector_active(rip->enable_interface); i++) + if ((ifname = vector_slot(rip->enable_interface, i)) != NULL) + vty_out(vty, " %s\n", ifname); + + /* RIP neighbors listing. */ + for (node = route_top(rip->neighbor); node; node = route_next(node)) + if (node->info) + vty_out(vty, " %pI4\n", &node->p.u.prefix4); + + return 0; +} + +void rip_interface_sync(struct interface *ifp) +{ + struct rip_interface *ri; + + ri = ifp->info; + if (ri) { + ri->rip = ifp->vrf->info; + ri->ifp = ifp; + } +} + +/* Called when interface structure allocated. */ +static int rip_interface_new_hook(struct interface *ifp) +{ + ifp->info = rip_interface_new(); + rip_interface_sync(ifp); + + return 0; +} + +/* Called when interface structure deleted. */ +static int rip_interface_delete_hook(struct interface *ifp) +{ + rip_interface_reset(ifp->info); + XFREE(MTYPE_RIP_INTERFACE, ifp->info); + return 0; +} + +/* Allocate and initialize interface vector. */ +void rip_if_init(void) +{ + /* Default initial size of interface vector. */ + hook_register_prio(if_add, 0, rip_interface_new_hook); + hook_register_prio(if_del, 0, rip_interface_delete_hook); + + /* Install interface node. */ + hook_register_prio(if_real, 0, rip_ifp_create); + hook_register_prio(if_up, 0, rip_ifp_up); + hook_register_prio(if_down, 0, rip_ifp_down); + hook_register_prio(if_unreal, 0, rip_ifp_destroy); +} diff --git a/ripd/rip_interface.h b/ripd/rip_interface.h new file mode 100644 index 0000000..eee654b --- /dev/null +++ b/ripd/rip_interface.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP interface routines + * + * This file is part of Quagga + */ + +#ifndef _QUAGGA_RIP_INTERFACE_H +#define _QUAGGA_RIP_INTERFACE_H + +#include "memory.h" +#include "zclient.h" + +DECLARE_MTYPE(RIP_INTERFACE_STRING); + +extern int rip_interface_down(int, struct zclient *, zebra_size_t, vrf_id_t); +extern int rip_interface_up(int, struct zclient *, zebra_size_t, vrf_id_t); +extern int rip_interface_add(int, struct zclient *, zebra_size_t, vrf_id_t); +extern int rip_interface_delete(int, struct zclient *, zebra_size_t, vrf_id_t); +extern int rip_interface_address_add(int, struct zclient *, zebra_size_t, + vrf_id_t); +extern int rip_interface_address_delete(int, struct zclient *, zebra_size_t, + vrf_id_t); +extern int rip_interface_vrf_update(ZAPI_CALLBACK_ARGS); +extern void rip_interface_sync(struct interface *ifp); + +#endif /* _QUAGGA_RIP_INTERFACE_H */ diff --git a/ripd/rip_main.c b/ripd/rip_main.c new file mode 100644 index 0000000..67469f5 --- /dev/null +++ b/ripd/rip_main.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPd main routine. + * Copyright (C) 1997, 98 Kunihiro Ishiguro + */ + +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "command.h" +#include "memory.h" +#include "prefix.h" +#include "filter.h" +#include "keychain.h" +#include "log.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "vrf.h" +#include "if_rmap.h" +#include "libfrr.h" +#include "routemap.h" +#include "bfd.h" +#include "mgmt_be_client.h" +#include "libagentx.h" + +#include "ripd/ripd.h" +#include "ripd/rip_bfd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_errors.h" + +/* ripd options. */ +static struct option longopts[] = {{0}}; + +/* ripd privileges */ +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; + +uint32_t zebra_ecmp_count = MULTIPATH_NUM; + +struct zebra_privs_t ripd_privs = { +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +/* Master of threads. */ +struct event_loop *master; + +struct mgmt_be_client *mgmt_be_client; + +static struct frr_daemon_info ripd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, ripd_di.config_file, config_default); +} + +/* SIGINT handler. */ +static void sigint(void) +{ + struct vrf *vrf; + + zlog_notice("Terminating on signal"); + + bfd_protocol_integration_set_shutdown(true); + + + nb_oper_cancel_all_walks(); + mgmt_be_client_destroy(mgmt_be_client); + mgmt_be_client = NULL; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!vrf->info) + continue; + + rip_clean(vrf->info); + } + + rip_vrf_terminate(); + if_rmap_terminate(); + rip_zclient_stop(); + + route_map_finish(); + + keychain_terminate(); + frr_fini(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +static struct frr_signal_t ripd_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const ripd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_ripd_info, + &frr_route_map_info, + &frr_vrf_info, + &ietf_key_chain_info, + &ietf_key_chain_deviation_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(ripd, RIP, + .vty_port = RIP_VTY_PORT, + .proghelp = "Implementation of the RIP routing protocol.", + + .signals = ripd_signals, + .n_signals = array_size(ripd_signals), + + .privs = &ripd_privs, + + .yang_modules = ripd_yang_modules, + .n_yang_modules = array_size(ripd_yang_modules), + + /* mgmtd will load the per-daemon config file now */ + .flags = FRR_NO_SPLIT_CONFIG, +); +/* clang-format on */ + +#define DEPRECATED_OPTIONS "" + +/* Main routine of ripd. */ +int main(int argc, char **argv) +{ + frr_preinit(&ripd_di, argc, argv); + + frr_opt_add("" DEPRECATED_OPTIONS, longopts, ""); + + /* Command line option parse. */ + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { + fprintf(stderr, + "The -%c option no longer exists.\nPlease refer to the manual.\n", + opt); + continue; + } + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + /* Prepare master thread. */ + master = frr_init(); + + /* Library initialization. */ + libagentx_init(); + rip_error_init(); + keychain_init_new(true); + rip_vrf_init(); + + /* RIP related initialization. */ + rip_init(); + rip_if_init(); + + mgmt_be_client = mgmt_be_client_create("ripd", NULL, 0, master); + + rip_zclient_init(master); + rip_bfd_init(master); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/ripd/rip_nb.c b/ripd/rip_nb.c new file mode 100644 index 0000000..231099d --- /dev/null +++ b/ripd/rip_nb.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "distribute.h" +#include "if_rmap.h" +#include "libfrr.h" +#include "northbound.h" + +#include "ripd/rip_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_ripd_info = { + .name = "frr-ripd", + .nodes = { + { + .xpath = "/frr-ripd:ripd/instance", + .cbs = { + .create = ripd_instance_create, + .destroy = ripd_instance_destroy, + .get_keys = ripd_instance_get_keys, + .get_next = ripd_instance_get_next, + .lookup_entry = ripd_instance_lookup_entry, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/allow-ecmp", + .cbs = { + .modify = ripd_instance_allow_ecmp_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-information-originate", + .cbs = { + .modify = ripd_instance_default_information_originate_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-metric", + .cbs = { + .modify = ripd_instance_default_metric_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/default", + .cbs = { + .modify = ripd_instance_distance_default_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/source", + .cbs = { + .create = ripd_instance_distance_source_create, + .destroy = ripd_instance_distance_source_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/source/distance", + .cbs = { + .modify = ripd_instance_distance_source_distance_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/distance/source/access-list", + .cbs = { + .destroy = ripd_instance_distance_source_access_list_destroy, + .modify = ripd_instance_distance_source_access_list_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/explicit-neighbor", + .cbs = { + .create = ripd_instance_explicit_neighbor_create, + .destroy = ripd_instance_explicit_neighbor_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/network", + .cbs = { + .create = ripd_instance_network_create, + .destroy = ripd_instance_network_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/interface", + .cbs = { + .create = ripd_instance_interface_create, + .destroy = ripd_instance_interface_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/offset-list", + .cbs = { + .create = ripd_instance_offset_list_create, + .destroy = ripd_instance_offset_list_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/offset-list/access-list", + .cbs = { + .modify = ripd_instance_offset_list_access_list_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/offset-list/metric", + .cbs = { + .modify = ripd_instance_offset_list_metric_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/passive-default", + .cbs = { + .modify = ripd_instance_passive_default_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/passive-interface", + .cbs = { + .create = ripd_instance_passive_interface_create, + .destroy = ripd_instance_passive_interface_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/non-passive-interface", + .cbs = { + .create = ripd_instance_non_passive_interface_create, + .destroy = ripd_instance_non_passive_interface_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list", + .cbs = { + .create = ripd_instance_distribute_list_create, + .destroy = group_distribute_list_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/in/access-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/out/access-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/in/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/distribute-list/out/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv4_modify, + .destroy = group_distribute_list_ipv4_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute", + .cbs = { + .apply_finish = ripd_instance_redistribute_apply_finish, + .create = ripd_instance_redistribute_create, + .destroy = ripd_instance_redistribute_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/route-map", + .cbs = { + .destroy = ripd_instance_redistribute_route_map_destroy, + .modify = ripd_instance_redistribute_route_map_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/metric", + .cbs = { + .destroy = ripd_instance_redistribute_metric_destroy, + .modify = ripd_instance_redistribute_metric_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/if-route-maps/if-route-map", + .cbs = { + .create = ripd_instance_if_route_maps_if_route_map_create, + .destroy = ripd_instance_if_route_maps_if_route_map_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/if-route-maps/if-route-map/in-route-map", + .cbs = { + .modify = ripd_instance_if_route_maps_if_route_map_in_route_map_modify, + .destroy = ripd_instance_if_route_maps_if_route_map_in_route_map_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/if-route-maps/if-route-map/out-route-map", + .cbs = { + .modify = ripd_instance_if_route_maps_if_route_map_out_route_map_modify, + .destroy = ripd_instance_if_route_maps_if_route_map_out_route_map_destroy, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/static-route", + .cbs = { + .create = ripd_instance_static_route_create, + .destroy = ripd_instance_static_route_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers", + .cbs = { + .apply_finish = ripd_instance_timers_apply_finish, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", + .cbs = { + .modify = ripd_instance_timers_flush_interval_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", + .cbs = { + .modify = ripd_instance_timers_holddown_interval_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/update-interval", + .cbs = { + .modify = ripd_instance_timers_update_interval_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/version/receive", + .cbs = { + .modify = ripd_instance_version_receive_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/version/send", + .cbs = { + .modify = ripd_instance_version_send_modify, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/default-bfd-profile", + .cbs = { + .modify = ripd_instance_default_bfd_profile_modify, + .destroy = ripd_instance_default_bfd_profile_destroy, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/split-horizon", + .cbs = { + .modify = lib_interface_rip_split_horizon_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/v2-broadcast", + .cbs = { + .modify = lib_interface_rip_v2_broadcast_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-receive", + .cbs = { + .modify = lib_interface_rip_version_receive_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/version-send", + .cbs = { + .modify = lib_interface_rip_version_send_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode", + .cbs = { + .modify = lib_interface_rip_authentication_scheme_mode_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length", + .cbs = { + .destroy = lib_interface_rip_authentication_scheme_md5_auth_length_destroy, + .modify = lib_interface_rip_authentication_scheme_md5_auth_length_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-password", + .cbs = { + .destroy = lib_interface_rip_authentication_password_destroy, + .modify = lib_interface_rip_authentication_password_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain", + .cbs = { + .destroy = lib_interface_rip_authentication_key_chain_destroy, + .modify = lib_interface_rip_authentication_key_chain_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring", + .cbs = { + .create = lib_interface_rip_bfd_create, + .destroy = lib_interface_rip_bfd_destroy, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/enable", + .cbs = { + .modify = lib_interface_rip_bfd_enable_modify, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/profile", + .cbs = { + .modify = lib_interface_rip_bfd_profile_modify, + .destroy = lib_interface_rip_bfd_profile_destroy, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor", + .cbs = { + .get_keys = ripd_instance_state_neighbors_neighbor_get_keys, + .get_next = ripd_instance_state_neighbors_neighbor_get_next, + .lookup_entry = ripd_instance_state_neighbors_neighbor_lookup_entry, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/address", + .cbs = { + .get_elem = ripd_instance_state_neighbors_neighbor_address_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/last-update", + .cbs = { + .get_elem = ripd_instance_state_neighbors_neighbor_last_update_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/bad-packets-rcvd", + .cbs = { + .get_elem = ripd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/neighbors/neighbor/bad-routes-rcvd", + .cbs = { + .get_elem = ripd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route", + .cbs = { + .get_keys = ripd_instance_state_routes_route_get_keys, + .get_next = ripd_instance_state_routes_route_get_next, + .lookup_entry = ripd_instance_state_routes_route_lookup_entry, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/prefix", + .cbs = { + .get_elem = ripd_instance_state_routes_route_prefix_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/next-hop", + .cbs = { + .get_elem = ripd_instance_state_routes_route_next_hop_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/interface", + .cbs = { + .get_elem = ripd_instance_state_routes_route_interface_get_elem, + }, + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop", + .cbs = { + .get_next = ripd_instance_state_routes_route_nexthops_nexthop_get_next, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/nh-type", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_nh_type_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/protocol", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_protocol_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/rip-type", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_rip_type_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/gateway", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_gateway_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/interface", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_interface_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/from", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_from_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/tag", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_tag_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/external-metric", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_external_metric_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/expire-time", + .cbs = { + .get_elem = ripd_instance_state_routes_route_nexthops_nexthop_expire_time_get_elem, + } + }, + { + .xpath = "/frr-ripd:ripd/instance/state/routes/route/metric", + .cbs = { + .get_elem = ripd_instance_state_routes_route_metric_get_elem, + }, + }, + { + .xpath = "/frr-ripd:clear-rip-route", + .cbs = { + .rpc = clear_rip_route_rpc, + }, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ripd/rip_nb.h b/ripd/rip_nb.h new file mode 100644 index 0000000..ee592da --- /dev/null +++ b/ripd/rip_nb.h @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_RIP_NB_H_ +#define _FRR_RIP_NB_H_ + +#include "northbound.h" + +extern const struct frr_yang_module_info frr_ripd_info; +extern const struct frr_yang_module_info frr_ripd_cli_info; + +/* Mandatory callbacks. */ +int ripd_instance_create(struct nb_cb_create_args *args); +int ripd_instance_destroy(struct nb_cb_destroy_args *args); +const void *ripd_instance_get_next(struct nb_cb_get_next_args *args); +int ripd_instance_get_keys(struct nb_cb_get_keys_args *args); +const void *ripd_instance_lookup_entry(struct nb_cb_lookup_entry_args *args); +int ripd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args); +int ripd_instance_default_information_originate_modify( + struct nb_cb_modify_args *args); +int ripd_instance_default_metric_modify(struct nb_cb_modify_args *args); +int ripd_instance_distance_default_modify(struct nb_cb_modify_args *args); +int ripd_instance_distance_source_create(struct nb_cb_create_args *args); +int ripd_instance_distance_source_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_distance_source_distance_modify( + struct nb_cb_modify_args *args); +int ripd_instance_distance_source_access_list_modify( + struct nb_cb_modify_args *args); +int ripd_instance_distance_source_access_list_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_explicit_neighbor_create(struct nb_cb_create_args *args); +int ripd_instance_explicit_neighbor_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_network_create(struct nb_cb_create_args *args); +int ripd_instance_network_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_interface_create(struct nb_cb_create_args *args); +int ripd_instance_interface_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_offset_list_create(struct nb_cb_create_args *args); +int ripd_instance_offset_list_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_offset_list_access_list_modify( + struct nb_cb_modify_args *args); +int ripd_instance_offset_list_metric_modify(struct nb_cb_modify_args *args); +int ripd_instance_passive_default_modify(struct nb_cb_modify_args *args); +int ripd_instance_passive_interface_create(struct nb_cb_create_args *args); +int ripd_instance_passive_interface_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_non_passive_interface_create(struct nb_cb_create_args *args); +int ripd_instance_non_passive_interface_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_distribute_list_create(struct nb_cb_create_args *args); +int ripd_instance_distribute_list_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_redistribute_create(struct nb_cb_create_args *args); +int ripd_instance_redistribute_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_redistribute_route_map_modify(struct nb_cb_modify_args *args); +int ripd_instance_redistribute_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_redistribute_metric_modify(struct nb_cb_modify_args *args); +int ripd_instance_redistribute_metric_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_if_route_maps_if_route_map_create( + struct nb_cb_create_args *args); +int ripd_instance_if_route_maps_if_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_if_route_maps_if_route_map_in_route_map_modify( + struct nb_cb_modify_args *args); +int ripd_instance_if_route_maps_if_route_map_in_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_if_route_maps_if_route_map_out_route_map_modify( + struct nb_cb_modify_args *args); +int ripd_instance_if_route_maps_if_route_map_out_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripd_instance_static_route_create(struct nb_cb_create_args *args); +int ripd_instance_static_route_destroy(struct nb_cb_destroy_args *args); +int ripd_instance_timers_flush_interval_modify(struct nb_cb_modify_args *args); +int ripd_instance_timers_holddown_interval_modify( + struct nb_cb_modify_args *args); +int ripd_instance_timers_update_interval_modify(struct nb_cb_modify_args *args); +int ripd_instance_version_receive_modify(struct nb_cb_modify_args *args); +int ripd_instance_version_send_modify(struct nb_cb_modify_args *args); +int ripd_instance_default_bfd_profile_modify(struct nb_cb_modify_args *args); +int ripd_instance_default_bfd_profile_destroy(struct nb_cb_destroy_args *args); +const void *ripd_instance_state_neighbors_neighbor_get_next( + struct nb_cb_get_next_args *args); +int ripd_instance_state_neighbors_neighbor_get_keys( + struct nb_cb_get_keys_args *args); +const void *ripd_instance_state_neighbors_neighbor_lookup_entry( + struct nb_cb_lookup_entry_args *args); +struct yang_data *ripd_instance_state_neighbors_neighbor_address_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripd_instance_state_neighbors_neighbor_last_update_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( + struct nb_cb_get_elem_args *args); +const void * +ripd_instance_state_routes_route_get_next(struct nb_cb_get_next_args *args); +int ripd_instance_state_routes_route_get_keys(struct nb_cb_get_keys_args *args); +const void *ripd_instance_state_routes_route_lookup_entry( + struct nb_cb_lookup_entry_args *args); +struct yang_data *ripd_instance_state_routes_route_prefix_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripd_instance_state_routes_route_next_hop_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripd_instance_state_routes_route_interface_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripd_instance_state_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args); +const void *ripd_instance_state_routes_route_nexthops_nexthop_get_next( + struct nb_cb_get_next_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_nh_type_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_protocol_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_rip_type_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_gateway_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_interface_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_from_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_tag_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_external_metric_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_expire_time_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripd_instance_state_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args); +int clear_rip_route_rpc(struct nb_cb_rpc_args *args); +int lib_interface_rip_split_horizon_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_v2_broadcast_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_version_receive_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_version_send_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_authentication_scheme_mode_modify( + struct nb_cb_modify_args *args); +int lib_interface_rip_authentication_scheme_md5_auth_length_modify( + struct nb_cb_modify_args *args); +int lib_interface_rip_authentication_scheme_md5_auth_length_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_rip_authentication_password_modify( + struct nb_cb_modify_args *args); +int lib_interface_rip_authentication_password_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_rip_authentication_key_chain_modify( + struct nb_cb_modify_args *args); +int lib_interface_rip_authentication_key_chain_destroy( + struct nb_cb_destroy_args *args); +int lib_interface_rip_bfd_create(struct nb_cb_create_args *args); +int lib_interface_rip_bfd_destroy(struct nb_cb_destroy_args *args); +int lib_interface_rip_bfd_enable_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_bfd_enable_destroy(struct nb_cb_destroy_args *args); +int lib_interface_rip_bfd_profile_modify(struct nb_cb_modify_args *args); +int lib_interface_rip_bfd_profile_destroy(struct nb_cb_destroy_args *args); + +/* Optional 'apply_finish' callbacks. */ +void ripd_instance_redistribute_apply_finish( + struct nb_cb_apply_finish_args *args); +void ripd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_end_router_rip(struct vty *vty, const struct lyd_node *dnode); +void cli_show_rip_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_default_information_originate(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_default_metric(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_distance(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_distance_source(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_neighbor(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_network_prefix(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_network_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_offset_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_passive_default(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_non_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_route(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_rip_version(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_split_horizon(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_v2_broadcast(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_receive_version(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_send_version(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripd_instance_default_bfd_profile(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_authentication_scheme(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_authentication_string(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_authentication_key_chain(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_bfd_enable(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ip_rip_bfd_profile(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); + +/* Notifications. */ +extern void ripd_notif_send_auth_type_failure(const char *ifname); +extern void ripd_notif_send_auth_failure(const char *ifname); + +extern void rip_cli_init(void); + +#endif /* _FRR_RIP_NB_H_ */ diff --git a/ripd/rip_nb_config.c b/ripd/rip_nb_config.c new file mode 100644 index 0000000..fb75337 --- /dev/null +++ b/ripd/rip_nb_config.c @@ -0,0 +1,1263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * Copyright (C) 2023 LabN Consulting, L.L.C. + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "if_rmap.h" +#include "routemap.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" +#include "ripd/rip_bfd.h" + +/* + * XPath: /frr-ripd:ripd/instance + */ +int ripd_instance_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + struct vrf *vrf; + const char *vrf_name; + int socket; + + vrf_name = yang_dnode_get_string(args->dnode, "vrf"); + vrf = vrf_lookup_by_name(vrf_name); + + /* + * Try to create a RIP socket only if the VRF is enabled, otherwise + * create a disabled RIP instance and wait for the VRF to be enabled. + */ + switch (args->event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + if (!vrf || !vrf_is_enabled(vrf)) + break; + + socket = rip_create_socket(vrf); + if (socket < 0) + return NB_ERR_RESOURCE; + args->resource->fd = socket; + break; + case NB_EV_ABORT: + if (!vrf || !vrf_is_enabled(vrf)) + break; + + socket = args->resource->fd; + close(socket); + break; + case NB_EV_APPLY: + if (vrf && vrf_is_enabled(vrf)) + socket = args->resource->fd; + else + socket = -1; + + rip = rip_create(vrf_name, vrf, socket); + nb_running_set_entry(args->dnode, rip); + break; + } + + return NB_OK; +} + +int ripd_instance_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_unset_entry(args->dnode); + rip_clean(rip); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/allow-ecmp + */ +int ripd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->ecmp = + MIN(yang_dnode_get_uint8(args->dnode, NULL), zebra_ecmp_count); + if (!rip->ecmp) { + rip_ecmp_disable(rip); + return NB_OK; + } + + rip_ecmp_change(rip); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/default-information-originate + */ +int ripd_instance_default_information_originate_modify( + struct nb_cb_modify_args *args) +{ + struct rip *rip; + bool default_information; + struct prefix_ipv4 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + default_information = yang_dnode_get_bool(args->dnode, NULL); + + memset(&p, 0, sizeof(p)); + p.family = AF_INET; + if (default_information) { + struct nexthop nh; + + memset(&nh, 0, sizeof(nh)); + nh.type = NEXTHOP_TYPE_IPV4; + rip_redistribute_add(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_DEFAULT, + &p, &nh, 0, 0, 0); + } else { + rip_redistribute_delete(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_DEFAULT, + &p, 0); + } + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/default-metric + */ +int ripd_instance_default_metric_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->default_metric = yang_dnode_get_uint8(args->dnode, NULL); + /* rip_update_default_metric (); */ + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/default + */ +int ripd_instance_distance_default_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->distance = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/source + */ +int ripd_instance_distance_source_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + struct prefix_ipv4 prefix; + struct route_node *rn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + yang_dnode_get_ipv4p(&prefix, args->dnode, "prefix"); + apply_mask_ipv4(&prefix); + + /* Get RIP distance node. */ + rip = nb_running_get_entry(args->dnode, NULL, true); + rn = route_node_get(rip->distance_table, (struct prefix *)&prefix); + rn->info = rip_distance_new(); + nb_running_set_entry(args->dnode, rn); + + return NB_OK; +} + +int ripd_instance_distance_source_destroy(struct nb_cb_destroy_args *args) +{ + struct route_node *rn; + struct rip_distance *rdistance; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rn = nb_running_unset_entry(args->dnode); + rdistance = rn->info; + rip_distance_free(rdistance); + rn->info = NULL; + route_unlock_node(rn); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/source/distance + */ +int ripd_instance_distance_source_distance_modify( + struct nb_cb_modify_args *args) +{ + struct route_node *rn; + uint8_t distance; + struct rip_distance *rdistance; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Set distance value. */ + rn = nb_running_get_entry(args->dnode, NULL, true); + distance = yang_dnode_get_uint8(args->dnode, NULL); + rdistance = rn->info; + rdistance->distance = distance; + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/distance/source/access-list + */ +int ripd_instance_distance_source_access_list_modify( + struct nb_cb_modify_args *args) +{ + const char *acl_name; + struct route_node *rn; + struct rip_distance *rdistance; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + acl_name = yang_dnode_get_string(args->dnode, NULL); + + /* Set access-list */ + rn = nb_running_get_entry(args->dnode, NULL, true); + rdistance = rn->info; + if (rdistance->access_list) + free(rdistance->access_list); + rdistance->access_list = strdup(acl_name); + + return NB_OK; +} + +int ripd_instance_distance_source_access_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct route_node *rn; + struct rip_distance *rdistance; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* Reset access-list configuration. */ + rn = nb_running_get_entry(args->dnode, NULL, true); + rdistance = rn->info; + free(rdistance->access_list); + rdistance->access_list = NULL; + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/explicit-neighbor + */ +int ripd_instance_explicit_neighbor_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + struct prefix_ipv4 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + yang_dnode_get_ipv4(&p.prefix, args->dnode, NULL); + + return rip_neighbor_add(rip, &p); +} + +int ripd_instance_explicit_neighbor_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + struct prefix_ipv4 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + yang_dnode_get_ipv4(&p.prefix, args->dnode, NULL); + + return rip_neighbor_delete(rip, &p); +} + +/* + * XPath: /frr-ripd:ripd/instance/network + */ +int ripd_instance_network_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + struct prefix p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv4p(&p, args->dnode, NULL); + apply_mask_ipv4((struct prefix_ipv4 *)&p); + + return rip_enable_network_add(rip, &p); +} + +int ripd_instance_network_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + struct prefix p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv4p(&p, args->dnode, NULL); + apply_mask_ipv4((struct prefix_ipv4 *)&p); + + return rip_enable_network_delete(rip, &p); +} + +/* + * XPath: /frr-ripd:ripd/instance/interface + */ +int ripd_instance_interface_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_enable_if_add(rip, ifname); +} + +int ripd_instance_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_enable_if_delete(rip, ifname); +} + +/* + * XPath: /frr-ripd:ripd/instance/offset-list + */ +int ripd_instance_offset_list_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + const char *ifname; + struct rip_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, "interface"); + + offset = rip_offset_list_new(rip, ifname); + nb_running_set_entry(args->dnode, offset); + + return NB_OK; +} + +int ripd_instance_offset_list_destroy(struct nb_cb_destroy_args *args) +{ + int direct; + struct rip_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "direction"); + + offset = nb_running_unset_entry(args->dnode); + if (offset->direct[direct].alist_name) { + free(offset->direct[direct].alist_name); + offset->direct[direct].alist_name = NULL; + } + if (offset->direct[RIP_OFFSET_LIST_IN].alist_name == NULL + && offset->direct[RIP_OFFSET_LIST_OUT].alist_name == NULL) + offset_list_del(offset); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/offset-list/access-list + */ +int ripd_instance_offset_list_access_list_modify(struct nb_cb_modify_args *args) +{ + int direct; + struct rip_offset_list *offset; + const char *alist_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "../direction"); + alist_name = yang_dnode_get_string(args->dnode, NULL); + + offset = nb_running_get_entry(args->dnode, NULL, true); + if (offset->direct[direct].alist_name) + free(offset->direct[direct].alist_name); + offset->direct[direct].alist_name = strdup(alist_name); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/offset-list/metric + */ +int ripd_instance_offset_list_metric_modify(struct nb_cb_modify_args *args) +{ + int direct; + uint8_t metric; + struct rip_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "../direction"); + metric = yang_dnode_get_uint8(args->dnode, NULL); + + offset = nb_running_get_entry(args->dnode, NULL, true); + offset->direct[direct].metric = metric; + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/passive-default + */ +int ripd_instance_passive_default_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->passive_default = yang_dnode_get_bool(args->dnode, NULL); + rip_passive_nondefault_clean(rip); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/passive-interface + */ +int ripd_instance_passive_interface_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_passive_nondefault_set(rip, ifname); +} + +int ripd_instance_passive_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_passive_nondefault_unset(rip, ifname); +} + +/* + * XPath: /frr-ripd:ripd/instance/non-passive-interface + */ +int ripd_instance_non_passive_interface_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_passive_nondefault_set(rip, ifname); +} + +int ripd_instance_non_passive_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return rip_passive_nondefault_unset(rip, ifname); +} + + +/* + * XPath: /frr-ripd:ripd/instance/distribute-list + */ +int ripd_instance_distribute_list_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + group_distribute_list_create_helper(args, rip->distribute_ctx); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/redistribute + */ +int ripd_instance_redistribute_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + rip->redist[type].enabled = true; + + return NB_OK; +} + +int ripd_instance_redistribute_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + rip->redist[type].enabled = false; + if (rip->redist[type].route_map.name) { + free(rip->redist[type].route_map.name); + rip->redist[type].route_map.name = NULL; + rip->redist[type].route_map.map = NULL; + } + rip->redist[type].metric_config = false; + rip->redist[type].metric = 0; + + if (rip->enabled) + rip_redistribute_conf_delete(rip, type); + + return NB_OK; +} + +void ripd_instance_redistribute_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct rip *rip; + int type; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + if (rip->enabled) + rip_redistribute_conf_update(rip, type); +} + +/* + * XPath: /frr-ripd:ripd/instance/redistribute/route-map + */ +int ripd_instance_redistribute_route_map_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + int type; + const char *rmap_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + rmap_name = yang_dnode_get_string(args->dnode, NULL); + + if (rip->redist[type].route_map.name) + free(rip->redist[type].route_map.name); + rip->redist[type].route_map.name = strdup(rmap_name); + rip->redist[type].route_map.map = route_map_lookup_by_name(rmap_name); + + return NB_OK; +} + +int ripd_instance_redistribute_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + struct rip *rip; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + + free(rip->redist[type].route_map.name); + rip->redist[type].route_map.name = NULL; + rip->redist[type].route_map.map = NULL; + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/redistribute/metric + */ +int ripd_instance_redistribute_metric_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + int type; + uint8_t metric; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + metric = yang_dnode_get_uint8(args->dnode, NULL); + + rip->redist[type].metric_config = true; + rip->redist[type].metric = metric; + + return NB_OK; +} + +int ripd_instance_redistribute_metric_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + + rip->redist[type].metric_config = false; + rip->redist[type].metric = 0; + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/if-route-maps/if-route-map + */ +int ripd_instance_if_route_maps_if_route_map_create( + struct nb_cb_create_args *args) +{ + /* if_rmap is created when first routemap is added */ + return NB_OK; +} + +int ripd_instance_if_route_maps_if_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* + * YANG will prune edit deletes up to the most general deleted node so + * we need to handle deleting any existing state underneath and not + * count on those more specific callbacks being called individually. + */ + + rip = nb_running_get_entry(args->dnode, NULL, true); + if_rmap_yang_destroy_cb(rip->if_rmap_ctx, args->dnode); + + return NB_OK; +} + +static void if_route_map_modify(const struct lyd_node *dnode, + enum if_rmap_type type, bool delete) +{ + struct rip *rip = nb_running_get_entry(dnode, NULL, true); + + if_rmap_yang_modify_cb(rip->if_rmap_ctx, dnode, type, delete); +} + +/* + * XPath: /frr-ripd:ripd/instance/if-route-maps/if-route-map/in-route-map + */ +int ripd_instance_if_route_maps_if_route_map_in_route_map_modify( + struct nb_cb_modify_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_IN, false); + + return NB_OK; +} + +int ripd_instance_if_route_maps_if_route_map_in_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_IN, true); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/if-route-maps/if-route-map/out-route-map + */ +int ripd_instance_if_route_maps_if_route_map_out_route_map_modify( + struct nb_cb_modify_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_OUT, false); + + return NB_OK; +} + +int ripd_instance_if_route_maps_if_route_map_out_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_OUT, true); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/static-route + */ +int ripd_instance_static_route_create(struct nb_cb_create_args *args) +{ + struct rip *rip; + struct nexthop nh; + struct prefix_ipv4 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv4p(&p, args->dnode, NULL); + apply_mask_ipv4(&p); + + memset(&nh, 0, sizeof(nh)); + nh.type = NEXTHOP_TYPE_IPV4; + rip_redistribute_add(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_STATIC, &p, &nh, 0, + 0, 0); + + return NB_OK; +} + +int ripd_instance_static_route_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + struct prefix_ipv4 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv4p(&p, args->dnode, NULL); + apply_mask_ipv4(&p); + + rip_redistribute_delete(rip, ZEBRA_ROUTE_RIP, RIP_ROUTE_STATIC, &p, 0); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/timers/ + */ +void ripd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args) +{ + struct rip *rip; + + rip = nb_running_get_entry(args->dnode, NULL, true); + + /* Reset update timer thread. */ + rip_event(rip, RIP_UPDATE_EVENT, 0); +} + +/* + * XPath: /frr-ripd:ripd/instance/timers/flush-interval + */ +int ripd_instance_timers_flush_interval_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->garbage_time = yang_dnode_get_uint32(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/timers/holddown-interval + */ +int ripd_instance_timers_holddown_interval_modify( + struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->timeout_time = yang_dnode_get_uint32(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/timers/update-interval + */ +int ripd_instance_timers_update_interval_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->update_time = yang_dnode_get_uint32(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/version/receive + */ +int ripd_instance_version_receive_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->version_recv = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/version/send + */ +int ripd_instance_version_send_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + rip->version_send = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripd:ripd/instance/default-bfd-profile + */ +int ripd_instance_default_bfd_profile_modify(struct nb_cb_modify_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_RIP_BFD_PROFILE, rip->default_bfd_profile); + rip->default_bfd_profile = + XSTRDUP(MTYPE_RIP_BFD_PROFILE, + yang_dnode_get_string(args->dnode, NULL)); + rip_bfd_instance_update(rip); + + return NB_OK; +} + +int ripd_instance_default_bfd_profile_destroy(struct nb_cb_destroy_args *args) +{ + struct rip *rip; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + rip = nb_running_get_entry(args->dnode, NULL, true); + XFREE(MTYPE_RIP_BFD_PROFILE, rip->default_bfd_profile); + rip_bfd_instance_update(rip); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/split-horizon + */ +int lib_interface_rip_split_horizon_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->split_horizon = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/v2-broadcast + */ +int lib_interface_rip_v2_broadcast_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->v2_broadcast = yang_dnode_get_bool(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-receive + */ +int lib_interface_rip_version_receive_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->ri_receive = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/version-send + */ +int lib_interface_rip_version_send_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->ri_send = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode + */ +int lib_interface_rip_authentication_scheme_mode_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->auth_type = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length + */ +int lib_interface_rip_authentication_scheme_md5_auth_length_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->md5_auth_len = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} + +int lib_interface_rip_authentication_scheme_md5_auth_length_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->md5_auth_len = yang_get_default_enum( + "%s/authentication-scheme/md5-auth-length", RIP_IFACE); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-password + */ +int lib_interface_rip_authentication_password_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); + ri->auth_str = XSTRDUP(MTYPE_RIP_INTERFACE_STRING, + yang_dnode_get_string(args->dnode, NULL)); + + return NB_OK; +} + +int lib_interface_rip_authentication_password_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->auth_str); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring + */ +int lib_interface_rip_bfd_create(struct nb_cb_create_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->bfd.enabled = yang_dnode_get_bool(args->dnode, "enable"); + XFREE(MTYPE_RIP_BFD_PROFILE, ri->bfd.profile); + if (yang_dnode_exists(args->dnode, "profile")) + ri->bfd.profile = XSTRDUP( + MTYPE_RIP_BFD_PROFILE, + yang_dnode_get_string(args->dnode, "profile")); + + rip_bfd_interface_update(ri); + + return NB_OK; +} + +int lib_interface_rip_bfd_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->bfd.enabled = false; + XFREE(MTYPE_RIP_BFD_PROFILE, ri->bfd.profile); + rip_bfd_interface_update(ri); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/enable + */ +int lib_interface_rip_bfd_enable_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->bfd.enabled = yang_dnode_get_bool(args->dnode, NULL); + rip_bfd_interface_update(ri); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/bfd-monitoring/profile + */ +int lib_interface_rip_bfd_profile_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_BFD_PROFILE, ri->bfd.profile); + ri->bfd.profile = XSTRDUP(MTYPE_RIP_BFD_PROFILE, + yang_dnode_get_string(args->dnode, NULL)); + rip_bfd_interface_update(ri); + + return NB_OK; +} + +int lib_interface_rip_bfd_profile_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_BFD_PROFILE, ri->bfd.profile); + rip_bfd_interface_update(ri); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripd:rip/authentication-key-chain + */ +int lib_interface_rip_authentication_key_chain_modify( + struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); + ri->key_chain = XSTRDUP(MTYPE_RIP_INTERFACE_STRING, + yang_dnode_get_string(args->dnode, NULL)); + + return NB_OK; +} + +int lib_interface_rip_authentication_key_chain_destroy( + struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct rip_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + XFREE(MTYPE_RIP_INTERFACE_STRING, ri->key_chain); + + return NB_OK; +} diff --git a/ripd/rip_nb_notifications.c b/ripd/rip_nb_notifications.c new file mode 100644 index 0000000..80da39b --- /dev/null +++ b/ripd/rip_nb_notifications.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "routemap.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" + +/* + * XPath: /frr-ripd:authentication-type-failure + */ +void ripd_notif_send_auth_type_failure(const char *ifname) +{ + const char *xpath = "/frr-ripd:authentication-type-failure"; + struct list *arguments; + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + arguments = yang_data_list_new(); + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); + data = yang_data_new_string(xpath_arg, ifname); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} + +/* + * XPath: /frr-ripd:authentication-failure + */ +void ripd_notif_send_auth_failure(const char *ifname) +{ + const char *xpath = "/frr-ripd:authentication-failure"; + struct list *arguments; + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + arguments = yang_data_list_new(); + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); + data = yang_data_new_string(xpath_arg, ifname); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); +} diff --git a/ripd/rip_nb_rpcs.c b/ripd/rip_nb_rpcs.c new file mode 100644 index 0000000..5d3d714 --- /dev/null +++ b/ripd/rip_nb_rpcs.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "routemap.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" + +/* + * XPath: /frr-ripd:clear-rip-route + */ +static void clear_rip_route(struct rip *rip) +{ + struct route_node *rp; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("Clearing all RIP routes (VRF %s)", rip->vrf_name); + + /* Clear received RIP routes */ + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + struct list *list; + struct listnode *listnode; + struct rip_info *rinfo; + + list = rp->info; + if (!list) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (!rip_route_rte(rinfo)) + continue; + + if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + rip_zebra_ipv4_delete(rip, rp); + break; + } + + if (rinfo) { + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + rip_info_free(rinfo); + } + + if (list_isempty(list)) { + list_delete(&list); + rp->info = NULL; + route_unlock_node(rp); + } + } +} + +int clear_rip_route_rpc(struct nb_cb_rpc_args *args) +{ + struct rip *rip; + + if (args->input && yang_dnode_exists(args->input, "vrf")) { + const char *name = yang_dnode_get_string(args->input, "vrf"); + + rip = rip_lookup_by_vrf_name(name); + if (rip) + clear_rip_route(rip); + } else { + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + rip = vrf->info; + if (!rip) + continue; + + clear_rip_route(rip); + } + } + + return NB_OK; +} diff --git a/ripd/rip_nb_state.c b/ripd/rip_nb_state.c new file mode 100644 index 0000000..fa0d382 --- /dev/null +++ b/ripd/rip_nb_state.c @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "routemap.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" + +/* + * XPath: /frr-ripd:ripd/instance + */ +const void *ripd_instance_get_next(struct nb_cb_get_next_args *args) +{ + struct rip *rip = (struct rip *)args->list_entry; + + if (args->list_entry == NULL) + rip = RB_MIN(rip_instance_head, &rip_instances); + else + rip = RB_NEXT(rip_instance_head, rip); + + return rip; +} + +int ripd_instance_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct rip *rip = args->list_entry; + + args->keys->num = 1; + strlcpy(args->keys->key[0], rip->vrf_name, sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripd_instance_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *vrf_name = args->keys->key[0]; + + return rip_lookup_by_vrf_name(vrf_name); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor + */ +const void *ripd_instance_state_neighbors_neighbor_get_next( + struct nb_cb_get_next_args *args) +{ + const struct rip *rip = args->parent_list_entry; + struct listnode *node; + + if (args->list_entry == NULL) + node = listhead(rip->peer_list); + else + node = listnextnode((struct listnode *)args->list_entry); + + return node; +} + +int ripd_instance_state_neighbors_neighbor_get_keys( + struct nb_cb_get_keys_args *args) +{ + const struct listnode *node = args->list_entry; + const struct rip_peer *peer = listgetdata(node); + + args->keys->num = 1; + (void)inet_ntop(AF_INET, &peer->addr, args->keys->key[0], + sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripd_instance_state_neighbors_neighbor_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + const struct rip *rip = args->parent_list_entry; + struct in_addr address; + struct rip_peer *peer; + struct listnode *node; + + yang_str2ipv4(args->keys->key[0], &address); + + for (ALL_LIST_ELEMENTS_RO(rip->peer_list, node, peer)) { + if (IPV4_ADDR_SAME(&peer->addr, &address)) + return node; + } + + return NULL; +} + +/* + * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/address + */ +struct yang_data *ripd_instance_state_neighbors_neighbor_address_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct rip_peer *peer = listgetdata(node); + + return yang_data_new_ipv4(args->xpath, &peer->addr); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/last-update + */ +struct yang_data *ripd_instance_state_neighbors_neighbor_last_update_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* TODO: yang:date-and-time is tricky */ + return NULL; +} + +/* + * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/bad-packets-rcvd + */ +struct yang_data * +ripd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct rip_peer *peer = listgetdata(node); + + return yang_data_new_uint32(args->xpath, peer->recv_badpackets); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/neighbors/neighbor/bad-routes-rcvd + */ +struct yang_data * +ripd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct rip_peer *peer = listgetdata(node); + + return yang_data_new_uint32(args->xpath, peer->recv_badroutes); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route + */ +const void * +ripd_instance_state_routes_route_get_next(struct nb_cb_get_next_args *args) +{ + const struct rip *rip = args->parent_list_entry; + struct route_node *rn; + + if (args->list_entry == NULL) + rn = route_top(rip->table); + else + rn = route_next((struct route_node *)args->list_entry); + /* Optimization: skip empty route nodes. */ + while (rn && rn->info == NULL) + rn = route_next(rn); + + return rn; +} + +int ripd_instance_state_routes_route_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct route_node *rn = args->list_entry; + + args->keys->num = 1; + (void)prefix2str(&rn->p, args->keys->key[0], + sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripd_instance_state_routes_route_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + const struct rip *rip = args->parent_list_entry; + struct prefix prefix; + struct route_node *rn; + + yang_str2ipv4p(args->keys->key[0], &prefix); + + rn = route_node_lookup(rip->table, &prefix); + if (!rn || !rn->info) + return NULL; + + route_unlock_node(rn); + + return rn; +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/prefix + */ +struct yang_data *ripd_instance_state_routes_route_prefix_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct route_node *rn = args->list_entry; + const struct rip_info *rinfo = listnode_head(rn->info); + + assert(rinfo); + return yang_data_new_ipv4p(args->xpath, &rinfo->rp->p); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop + */ +const void *ripd_instance_state_routes_route_nexthops_nexthop_get_next( + struct nb_cb_get_next_args *args) +{ + const struct route_node *rn = args->parent_list_entry; + const struct listnode *node = args->list_entry; + + assert(rn); + if (node) + return listnextnode(node); + assert(rn->info); + return listhead((struct list *)rn->info); +} + +static inline const struct rip_info *get_rip_info(const void *info) +{ + return (const struct rip_info *)listgetdata( + (const struct listnode *)info); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/nh-type + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_nh_type_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + assert(rinfo); + return yang_data_new_enum(args->xpath, rinfo->nh.type); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/protocol + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_protocol_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + assert(rinfo); + return yang_data_new_enum(args->xpath, rinfo->type); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/rip-type + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_rip_type_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + assert(rinfo); + return yang_data_new_enum(args->xpath, rinfo->sub_type); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/gateway + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_gateway_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + if (rinfo->nh.type != NEXTHOP_TYPE_IPV4 && + rinfo->nh.type != NEXTHOP_TYPE_IPV4_IFINDEX) + return NULL; + + return yang_data_new_ipv4(args->xpath, &rinfo->nh.gate.ipv4); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/interface + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_interface_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + const struct rip *rip = rip_info_get_instance(rinfo); + + if (rinfo->nh.type != NEXTHOP_TYPE_IFINDEX && + rinfo->nh.type != NEXTHOP_TYPE_IPV4_IFINDEX) + return NULL; + + return yang_data_new_string( + args->xpath, + ifindex2ifname(rinfo->nh.ifindex, rip->vrf->vrf_id)); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/from + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_from_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + if (rinfo->type != ZEBRA_ROUTE_RIP || rinfo->sub_type != RIP_ROUTE_RTE) + return NULL; + + return yang_data_new_ipv4(args->xpath, &rinfo->from); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/tag + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_tag_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + return yang_data_new_uint32(args->xpath, rinfo->tag); +} + +/* + * XPath: + * /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/external-metric + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_external_metric_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + + if ((rinfo->type == ZEBRA_ROUTE_RIP && + rinfo->sub_type == RIP_ROUTE_RTE) || + rinfo->metric == RIP_METRIC_INFINITY || rinfo->external_metric == 0) + return NULL; + return yang_data_new_uint32(args->xpath, rinfo->external_metric); +} + +/* + * XPath: + * /frr-ripd:ripd/instance/state/routes/route/nexthops/nexthop/expire-time + */ +struct yang_data * +ripd_instance_state_routes_route_nexthops_nexthop_expire_time_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct rip_info *rinfo = get_rip_info(args->list_entry); + struct event *event; + + if ((event = rinfo->t_timeout) == NULL) + event = rinfo->t_garbage_collect; + if (!event) + return NULL; + + return yang_data_new_uint32(args->xpath, + event_timer_remain_second(event)); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/next-hop + */ +struct yang_data *ripd_instance_state_routes_route_next_hop_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct route_node *rn = args->list_entry; + const struct rip_info *rinfo = listnode_head(rn->info); + + switch (rinfo->nh.type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + return yang_data_new_ipv4(args->xpath, &rinfo->nh.gate.ipv4); + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return NULL; + } + + assert(!"Reached end of function where we do not expect to reach"); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/interface + */ +struct yang_data *ripd_instance_state_routes_route_interface_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct route_node *rn = args->list_entry; + const struct rip_info *rinfo = listnode_head(rn->info); + const struct rip *rip = rip_info_get_instance(rinfo); + + switch (rinfo->nh.type) { + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV4_IFINDEX: + return yang_data_new_string( + args->xpath, + ifindex2ifname(rinfo->nh.ifindex, rip->vrf->vrf_id)); + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return NULL; + } + + assert(!"Reached end of function where we do not expect to reach"); +} + +/* + * XPath: /frr-ripd:ripd/instance/state/routes/route/metric + */ +struct yang_data *ripd_instance_state_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct route_node *rn = args->list_entry; + const struct rip_info *rinfo = listnode_head(rn->info); + + return yang_data_new_uint8(args->xpath, rinfo->metric); +} diff --git a/ripd/rip_offset.c b/ripd/rip_offset.c new file mode 100644 index 0000000..4c93f71 --- /dev/null +++ b/ripd/rip_offset.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP offset-list + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include + +#include "if.h" +#include "prefix.h" +#include "filter.h" +#include "command.h" +#include "linklist.h" +#include "memory.h" + +#include "ripd/ripd.h" + +DEFINE_MTYPE_STATIC(RIPD, RIP_OFFSET_LIST, "RIP offset list"); + +#define OFFSET_LIST_IN_NAME(O) ((O)->direct[RIP_OFFSET_LIST_IN].alist_name) +#define OFFSET_LIST_IN_METRIC(O) ((O)->direct[RIP_OFFSET_LIST_IN].metric) + +#define OFFSET_LIST_OUT_NAME(O) ((O)->direct[RIP_OFFSET_LIST_OUT].alist_name) +#define OFFSET_LIST_OUT_METRIC(O) ((O)->direct[RIP_OFFSET_LIST_OUT].metric) + +struct rip_offset_list *rip_offset_list_new(struct rip *rip, const char *ifname) +{ + struct rip_offset_list *offset; + + offset = XCALLOC(MTYPE_RIP_OFFSET_LIST, sizeof(struct rip_offset_list)); + offset->rip = rip; + offset->ifname = strdup(ifname); + listnode_add_sort(rip->offset_list_master, offset); + + return offset; +} + +void offset_list_del(struct rip_offset_list *offset) +{ + listnode_delete(offset->rip->offset_list_master, offset); + offset_list_free(offset); +} + +void offset_list_free(struct rip_offset_list *offset) +{ + if (OFFSET_LIST_IN_NAME(offset)) + free(OFFSET_LIST_IN_NAME(offset)); + if (OFFSET_LIST_OUT_NAME(offset)) + free(OFFSET_LIST_OUT_NAME(offset)); + free(offset->ifname); + XFREE(MTYPE_RIP_OFFSET_LIST, offset); +} + +struct rip_offset_list *rip_offset_list_lookup(struct rip *rip, + const char *ifname) +{ + struct rip_offset_list *offset; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(rip->offset_list_master, node, nnode, offset)) { + if (strcmp(offset->ifname, ifname) == 0) + return offset; + } + return NULL; +} + +/* If metric is modified return 1. */ +int rip_offset_list_apply_in(struct prefix_ipv4 *p, struct interface *ifp, + uint32_t *metric) +{ + struct rip_interface *ri = ifp->info; + struct rip_offset_list *offset; + struct access_list *alist; + + /* Look up offset-list with interface name. */ + offset = rip_offset_list_lookup(ri->rip, ifp->name); + if (offset && OFFSET_LIST_IN_NAME(offset)) { + alist = access_list_lookup(AFI_IP, OFFSET_LIST_IN_NAME(offset)); + + if (alist + && access_list_apply(alist, (struct prefix *)p) + == FILTER_PERMIT) { + *metric += OFFSET_LIST_IN_METRIC(offset); + return 1; + } + return 0; + } + /* Look up offset-list without interface name. */ + offset = rip_offset_list_lookup(ri->rip, "*"); + if (offset && OFFSET_LIST_IN_NAME(offset)) { + alist = access_list_lookup(AFI_IP, OFFSET_LIST_IN_NAME(offset)); + + if (alist + && access_list_apply(alist, (struct prefix *)p) + == FILTER_PERMIT) { + *metric += OFFSET_LIST_IN_METRIC(offset); + return 1; + } + return 0; + } + return 0; +} + +/* If metric is modified return 1. */ +int rip_offset_list_apply_out(struct prefix_ipv4 *p, struct interface *ifp, + uint32_t *metric) +{ + struct rip_interface *ri = ifp->info; + struct rip_offset_list *offset; + struct access_list *alist; + + /* Look up offset-list with interface name. */ + offset = rip_offset_list_lookup(ri->rip, ifp->name); + if (offset && OFFSET_LIST_OUT_NAME(offset)) { + alist = access_list_lookup(AFI_IP, + OFFSET_LIST_OUT_NAME(offset)); + + if (alist + && access_list_apply(alist, (struct prefix *)p) + == FILTER_PERMIT) { + *metric += OFFSET_LIST_OUT_METRIC(offset); + return 1; + } + return 0; + } + + /* Look up offset-list without interface name. */ + offset = rip_offset_list_lookup(ri->rip, "*"); + if (offset && OFFSET_LIST_OUT_NAME(offset)) { + alist = access_list_lookup(AFI_IP, + OFFSET_LIST_OUT_NAME(offset)); + + if (alist + && access_list_apply(alist, (struct prefix *)p) + == FILTER_PERMIT) { + *metric += OFFSET_LIST_OUT_METRIC(offset); + return 1; + } + return 0; + } + return 0; +} + +int offset_list_cmp(struct rip_offset_list *o1, struct rip_offset_list *o2) +{ + return strcmp(o1->ifname, o2->ifname); +} diff --git a/ripd/rip_peer.c b/ripd/rip_peer.c new file mode 100644 index 0000000..7e848be --- /dev/null +++ b/ripd/rip_peer.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP peer support + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +#include + +#include "if.h" +#include "prefix.h" +#include "command.h" +#include "linklist.h" +#include "frrevent.h" +#include "memory.h" +#include "table.h" +#include "frrdistance.h" + +#include "ripd/ripd.h" +#include "ripd/rip_bfd.h" + +DEFINE_MTYPE_STATIC(RIPD, RIP_PEER, "RIP peer"); + +static struct rip_peer *rip_peer_new(void) +{ + return XCALLOC(MTYPE_RIP_PEER, sizeof(struct rip_peer)); +} + +void rip_peer_free(struct rip_peer *peer) +{ + bfd_sess_free(&peer->bfd_session); + EVENT_OFF(peer->t_timeout); + XFREE(MTYPE_RIP_PEER, peer); +} + +struct rip_peer *rip_peer_lookup(struct rip *rip, struct in_addr *addr) +{ + struct rip_peer *peer; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { + if (IPV4_ADDR_SAME(&peer->addr, addr)) + return peer; + } + return NULL; +} + +struct rip_peer *rip_peer_lookup_next(struct rip *rip, struct in_addr *addr) +{ + struct rip_peer *peer; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { + if (htonl(peer->addr.s_addr) > htonl(addr->s_addr)) + return peer; + } + return NULL; +} + +/* RIP peer is timeout. */ +static void rip_peer_timeout(struct event *t) +{ + struct rip_peer *peer; + + peer = EVENT_ARG(t); + listnode_delete(peer->rip->peer_list, peer); + rip_peer_free(peer); +} + +/* Get RIP peer. At the same time update timeout thread. */ +static struct rip_peer *rip_peer_get(struct rip *rip, struct rip_interface *ri, + struct in_addr *addr) +{ + struct rip_peer *peer; + + peer = rip_peer_lookup(rip, addr); + + if (peer) { + EVENT_OFF(peer->t_timeout); + } else { + peer = rip_peer_new(); + peer->rip = rip; + peer->ri = ri; + peer->addr = *addr; + rip_bfd_session_update(peer); + listnode_add_sort(rip->peer_list, peer); + } + + /* Update timeout thread. */ + event_add_timer(master, rip_peer_timeout, peer, RIP_PEER_TIMER_DEFAULT, + &peer->t_timeout); + + /* Last update time set. */ + time(&peer->uptime); + + return peer; +} + +void rip_peer_update(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from, uint8_t version) +{ + struct rip_peer *peer; + peer = rip_peer_get(rip, ri, &from->sin_addr); + peer->version = version; +} + +void rip_peer_bad_route(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from) +{ + struct rip_peer *peer; + peer = rip_peer_get(rip, ri, &from->sin_addr); + peer->recv_badroutes++; +} + +void rip_peer_bad_packet(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from) +{ + struct rip_peer *peer; + peer = rip_peer_get(rip, ri, &from->sin_addr); + peer->recv_badpackets++; +} + +/* Display peer uptime. */ +static char *rip_peer_uptime(struct rip_peer *peer, char *buf, size_t len) +{ + time_t uptime; + + /* If there is no connection has been done before print `never'. */ + if (peer->uptime == 0) { + snprintf(buf, len, "never "); + return buf; + } + + /* Get current time. */ + uptime = time(NULL); + uptime -= peer->uptime; + + frrtime_to_interval(uptime, buf, len); + + return buf; +} + +void rip_peer_display(struct vty *vty, struct rip *rip) +{ + struct rip_peer *peer; + struct listnode *node, *nnode; +#define RIP_UPTIME_LEN 25 + char timebuf[RIP_UPTIME_LEN]; + + for (ALL_LIST_ELEMENTS(rip->peer_list, node, nnode, peer)) { + vty_out(vty, " %-17pI4 %9d %9d %9d %11s\n", + &peer->addr, peer->recv_badpackets, + peer->recv_badroutes, ZEBRA_RIP_DISTANCE_DEFAULT, + rip_peer_uptime(peer, timebuf, RIP_UPTIME_LEN)); + } +} + +int rip_peer_list_cmp(struct rip_peer *p1, struct rip_peer *p2) +{ + if (p2->addr.s_addr == p1->addr.s_addr) + return 0; + + return (htonl(p1->addr.s_addr) < htonl(p2->addr.s_addr)) ? -1 : 1; +} + +void rip_peer_list_del(void *arg) +{ + rip_peer_free(arg); +} + +void rip_peer_delete_routes(const struct rip_peer *peer) +{ + struct route_node *route_node; + + for (route_node = route_top(peer->rip->table); route_node; + route_node = route_next(route_node)) { + struct rip_info *route_entry; + struct listnode *listnode; + struct listnode *listnode_next; + struct list *list; + + list = route_node->info; + if (list == NULL) + continue; + + for (ALL_LIST_ELEMENTS(list, listnode, listnode_next, + route_entry)) { + if (!rip_route_rte(route_entry)) + continue; + if (route_entry->from.s_addr != peer->addr.s_addr) + continue; + + if (listcount(list) == 1) { + EVENT_OFF(route_entry->t_timeout); + EVENT_OFF(route_entry->t_garbage_collect); + listnode_delete(list, route_entry); + if (list_isempty(list)) { + list_delete((struct list **)&route_node + ->info); + route_unlock_node(route_node); + } + rip_info_free(route_entry); + + /* Signal the output process to trigger an + * update (see section 2.5). */ + rip_event(peer->rip, RIP_TRIGGERED_UPDATE, 0); + } else + rip_ecmp_delete(peer->rip, route_entry); + break; + } + } +} diff --git a/ripd/rip_routemap.c b/ripd/rip_routemap.c new file mode 100644 index 0000000..be17277 --- /dev/null +++ b/ripd/rip_routemap.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPv2 routemap. + * Copyright (C) 2005 6WIND + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "memory.h" +#include "prefix.h" +#include "vty.h" +#include "routemap.h" +#include "command.h" +#include "filter.h" +#include "log.h" +#include "sockunion.h" /* for inet_aton () */ +#include "plist.h" +#include "vrf.h" + +#include "ripd/ripd.h" + +struct rip_metric_modifier { + enum { metric_increment, metric_decrement, metric_absolute } type; + bool used; + uint8_t metric; +}; + +/* `match metric METRIC' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_metric(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *metric; + uint32_t check; + struct rip_info *rinfo; + + metric = rule; + rinfo = object; + + /* If external metric is available, the route-map should + work on this one (for redistribute purpose) */ + check = (rinfo->external_metric) ? rinfo->external_metric + : rinfo->metric; + if (check == *metric) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + +/* Route map `match metric' match statement. `arg' is METRIC value */ +static void *route_match_metric_compile(const char *arg) +{ + uint32_t *metric; + + metric = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + *metric = atoi(arg); + + if (*metric > 0) + return metric; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, metric); + return NULL; +} + +/* Free route map's compiled `match metric' value. */ +static void route_match_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_metric_cmd = { + "metric", + route_match_metric, + route_match_metric_compile, + route_match_metric_free +}; + +/* `match interface IFNAME' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_interface(void *rule, const struct prefix *prefix, void *object) +{ + struct rip_info *rinfo; + struct interface *ifp; + char *ifname; + + ifname = rule; + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + + if (!ifp) + return RMAP_NOMATCH; + + rinfo = object; + + if (rinfo->ifindex_out == ifp->ifindex + || rinfo->nh.ifindex == ifp->ifindex) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + +/* Route map `match interface' match statement. `arg' is IFNAME value */ +/* XXX I don`t know if I need to check does interface exist? */ +static void *route_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `match interface' value. */ +static void route_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for interface matching. */ +static const struct route_map_rule_cmd route_match_interface_cmd = { + "interface", + route_match_interface, + route_match_interface_compile, + route_match_interface_free +}; + +/* `match ip next-hop IP_ACCESS_LIST' */ + +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_ip_next_hop(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + struct rip_info *rinfo; + struct prefix_ipv4 p; + + rinfo = object; + p.family = AF_INET; + p.prefix = (rinfo->nh.gate.ipv4.s_addr != INADDR_ANY) + ? rinfo->nh.gate.ipv4 + : rinfo->from; + p.prefixlen = IPV4_MAX_BITLEN; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) + return RMAP_NOMATCH; + + return (access_list_apply(alist, &p) == FILTER_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Route map `ip next-hop' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_next_hop_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `. */ +static void route_match_ip_next_hop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip next-hop matching. */ +static const struct route_map_rule_cmd route_match_ip_next_hop_cmd = { + "ip next-hop", + route_match_ip_next_hop, + route_match_ip_next_hop_compile, + route_match_ip_next_hop_free +}; + +/* `match ip next-hop prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + struct rip_info *rinfo; + struct prefix_ipv4 p; + + rinfo = object; + p.family = AF_INET; + p.prefix = (rinfo->nh.gate.ipv4.s_addr != INADDR_ANY) + ? rinfo->nh.gate.ipv4 + : rinfo->from; + p.prefixlen = IPV4_MAX_BITLEN; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, &p) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void *route_match_ip_next_hop_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_prefix_list_cmd = { + "ip next-hop prefix-list", + route_match_ip_next_hop_prefix_list, + route_match_ip_next_hop_prefix_list_compile, + route_match_ip_next_hop_prefix_list_free +}; + +/* `match ip next-hop type ' */ + +static enum route_map_cmd_result_t +route_match_ip_next_hop_type(void *rule, const struct prefix *prefix, + void *object) +{ + struct rip_info *rinfo; + + if (prefix->family == AF_INET) { + rinfo = (struct rip_info *)object; + if (!rinfo) + return RMAP_NOMATCH; + + if (rinfo->nh.type == NEXTHOP_TYPE_BLACKHOLE) + return RMAP_MATCH; + } + return RMAP_NOMATCH; +} + +static void *route_match_ip_next_hop_type_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_next_hop_type_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_next_hop_type_cmd = { + "ip next-hop type", + route_match_ip_next_hop_type, + route_match_ip_next_hop_type_compile, + route_match_ip_next_hop_type_free +}; + +/* `match ip address IP_ACCESS_LIST' */ + +/* Match function should return 1 if match is success else return + zero. */ +static enum route_map_cmd_result_t +route_match_ip_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP, (char *)rule); + if (alist == NULL) + return RMAP_NOMATCH; + + return (access_list_apply(alist, prefix) == FILTER_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +/* Route map `ip address' match statement. `arg' should be + access-list name. */ +static void *route_match_ip_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +/* Free route map's compiled `ip address' value. */ +static void route_match_ip_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip address matching. */ +static const struct route_map_rule_cmd route_match_ip_address_cmd = { + "ip address", + route_match_ip_address, + route_match_ip_address_compile, + route_match_ip_address_free +}; + +/* `match ip address prefix-list PREFIX_LIST' */ + +static enum route_map_cmd_result_t +route_match_ip_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP, (char *)rule); + if (plist == NULL) + return RMAP_NOMATCH; + + return (prefix_list_apply(plist, prefix) == PREFIX_DENY ? RMAP_NOMATCH + : RMAP_MATCH); +} + +static void *route_match_ip_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ip_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ip_address_prefix_list_cmd = { + "ip address prefix-list", + route_match_ip_address_prefix_list, + route_match_ip_address_prefix_list_compile, + route_match_ip_address_prefix_list_free +}; + +/* `match tag TAG' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_tag(void *rule, const struct prefix *p, void *object) +{ + route_tag_t *tag; + struct rip_info *rinfo; + route_tag_t rinfo_tag; + + tag = rule; + rinfo = object; + + /* The information stored by rinfo is host ordered. */ + rinfo_tag = rinfo->tag; + if (rinfo_tag == *tag) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + +/* Route map commands for tag matching. */ +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* `set metric METRIC' */ + +/* Set metric to attribute. */ +static enum route_map_cmd_result_t +route_set_metric(void *rule, const struct prefix *prefix, void *object) +{ + struct rip_metric_modifier *mod; + struct rip_info *rinfo; + + mod = rule; + rinfo = object; + + if (!mod->used) + return RMAP_OKAY; + + if (mod->type == metric_increment) + rinfo->metric_out += mod->metric; + else if (mod->type == metric_decrement) + rinfo->metric_out -= mod->metric; + else if (mod->type == metric_absolute) + rinfo->metric_out = mod->metric; + + if ((signed int)rinfo->metric_out < 1) + rinfo->metric_out = 1; + if (rinfo->metric_out > RIP_METRIC_INFINITY) + rinfo->metric_out = RIP_METRIC_INFINITY; + + rinfo->metric_set = 1; + return RMAP_OKAY; +} + +/* set metric compilation. */ +static void *route_set_metric_compile(const char *arg) +{ + int len; + const char *pnt; + long metric; + char *endptr = NULL; + struct rip_metric_modifier *mod; + + mod = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct rip_metric_modifier)); + mod->used = false; + + len = strlen(arg); + pnt = arg; + + if (len == 0) + return mod; + + /* Examine first character. */ + if (arg[0] == '+') { + mod->type = metric_increment; + pnt++; + } else if (arg[0] == '-') { + mod->type = metric_decrement; + pnt++; + } else + mod->type = metric_absolute; + + /* Check beginning with digit string. */ + if (*pnt < '0' || *pnt > '9') + return mod; + + /* Convert string to integer. */ + metric = strtol(pnt, &endptr, 10); + + if (*endptr != '\0' || metric < 0) { + return mod; + } + if (metric > RIP_METRIC_INFINITY) { + zlog_info( + "%s: Metric specified: %ld is greater than RIP_METRIC_INFINITY, using INFINITY instead", + __func__, metric); + mod->metric = RIP_METRIC_INFINITY; + } else + mod->metric = metric; + + mod->used = true; + + return mod; +} + +/* Free route map's compiled `set metric' value. */ +static void route_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Set metric rule structure. */ +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_set_metric_compile, + route_set_metric_free, +}; + +/* `set ip next-hop IP_ADDRESS' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ip_nexthop(void *rule, const struct prefix *prefix, + + void *object) +{ + struct in_addr *address; + struct rip_info *rinfo; + + /* Fetch routemap's rule information. */ + address = rule; + rinfo = object; + + /* Set next hop value. */ + rinfo->nexthop_out = *address; + + return RMAP_OKAY; +} + +/* Route map `ip nexthop' compile function. Given string is converted + to struct in_addr structure. */ +static void *route_set_ip_nexthop_compile(const char *arg) +{ + int ret; + struct in_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in_addr)); + + ret = inet_aton(arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* Free route map's compiled `ip nexthop' value. */ +static void route_set_ip_nexthop_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ip nexthop set. */ +static const struct route_map_rule_cmd route_set_ip_nexthop_cmd = { + "ip next-hop", + route_set_ip_nexthop, + route_set_ip_nexthop_compile, + route_set_ip_nexthop_free +}; + +/* `set tag TAG' */ + +/* Set tag to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct rip_info *rinfo; + + /* Fetch routemap's rule information. */ + tag = rule; + rinfo = object; + + /* Set next hop value. */ + rinfo->tag_out = *tag; + + return RMAP_OKAY; +} + +/* Route map commands for tag set. */ +static const struct route_map_rule_cmd route_set_tag_cmd = { + "tag", + route_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free +}; + +#define MATCH_STR "Match values from routing table\n" +#define SET_STR "Set values in destination routing protocol\n" + +/* Route-map init */ +void rip_route_map_init(void) +{ + route_map_init_new(true); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_match_ip_address_hook(generic_match_add); + route_map_no_match_ip_address_hook(generic_match_delete); + + route_map_match_ip_address_prefix_list_hook(generic_match_add); + route_map_no_match_ip_address_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_hook(generic_match_add); + route_map_no_match_ip_next_hop_hook(generic_match_delete); + + route_map_match_ip_next_hop_prefix_list_hook(generic_match_add); + route_map_no_match_ip_next_hop_prefix_list_hook(generic_match_delete); + + route_map_match_ip_next_hop_type_hook(generic_match_add); + route_map_no_match_ip_next_hop_type_hook(generic_match_delete); + + route_map_match_metric_hook(generic_match_add); + route_map_no_match_metric_hook(generic_match_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_set_ip_nexthop_hook(generic_set_add); + route_map_no_set_ip_nexthop_hook(generic_set_delete); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_install_match(&route_match_metric_cmd); + route_map_install_match(&route_match_interface_cmd); + route_map_install_match(&route_match_ip_next_hop_cmd); + route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); + route_map_install_match(&route_match_ip_next_hop_type_cmd); + route_map_install_match(&route_match_ip_address_cmd); + route_map_install_match(&route_match_ip_address_prefix_list_cmd); + route_map_install_match(&route_match_tag_cmd); + + route_map_install_set(&route_set_metric_cmd); + route_map_install_set(&route_set_ip_nexthop_cmd); + route_map_install_set(&route_set_tag_cmd); +} diff --git a/ripd/rip_snmp.c b/ripd/rip_snmp.c new file mode 100644 index 0000000..f6a7a82 --- /dev/null +++ b/ripd/rip_snmp.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP SNMP support + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "table.h" +#include "smux.h" +#include "libfrr.h" +#include "lib/version.h" + +#include "ripd/ripd.h" + +/* RIPv2-MIB. */ +#define RIPV2MIB 1,3,6,1,2,1,23 + +/* RIPv2-MIB rip2Globals values. */ +#define RIP2GLOBALROUTECHANGES 1 +#define RIP2GLOBALQUERIES 2 + +/* RIPv2-MIB rip2IfStatEntry. */ +#define RIP2IFSTATENTRY 1 + +/* RIPv2-MIB rip2IfStatTable. */ +#define RIP2IFSTATADDRESS 1 +#define RIP2IFSTATRCVBADPACKETS 2 +#define RIP2IFSTATRCVBADROUTES 3 +#define RIP2IFSTATSENTUPDATES 4 +#define RIP2IFSTATSTATUS 5 + +/* RIPv2-MIB rip2IfConfTable. */ +#define RIP2IFCONFADDRESS 1 +#define RIP2IFCONFDOMAIN 2 +#define RIP2IFCONFAUTHTYPE 3 +#define RIP2IFCONFAUTHKEY 4 +#define RIP2IFCONFSEND 5 +#define RIP2IFCONFRECEIVE 6 +#define RIP2IFCONFDEFAULTMETRIC 7 +#define RIP2IFCONFSTATUS 8 +#define RIP2IFCONFSRCADDRESS 9 + +/* RIPv2-MIB rip2PeerTable. */ +#define RIP2PEERADDRESS 1 +#define RIP2PEERDOMAIN 2 +#define RIP2PEERLASTUPDATE 3 +#define RIP2PEERVERSION 4 +#define RIP2PEERRCVBADPACKETS 5 +#define RIP2PEERRCVBADROUTES 6 + +/* SNMP value hack. */ +#define COUNTER ASN_COUNTER +#define INTEGER ASN_INTEGER +#define TIMETICKS ASN_TIMETICKS +#define IPADDRESS ASN_IPADDRESS +#define STRING ASN_OCTET_STR + +/* Define SNMP local variables. */ +SNMP_LOCAL_VARIABLES + +/* RIP-MIB instances. */ +static oid rip_oid[] = {RIPV2MIB}; + +/* Interface cache table sorted by interface's address. */ +static struct route_table *rip_ifaddr_table; + +/* Hook functions. */ +static uint8_t *rip2Globals(struct variable *, oid[], size_t *, int, size_t *, + WriteMethod **); +static uint8_t *rip2IfStatEntry(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); +static uint8_t *rip2IfConfAddress(struct variable *, oid[], size_t *, int, + size_t *, WriteMethod **); +static uint8_t *rip2PeerTable(struct variable *, oid[], size_t *, int, size_t *, + WriteMethod **); + +static struct variable rip_variables[] = { + /* RIP Global Counters. */ + {RIP2GLOBALROUTECHANGES, COUNTER, RONLY, rip2Globals, 2, {1, 1}}, + {RIP2GLOBALQUERIES, COUNTER, RONLY, rip2Globals, 2, {1, 2}}, + /* RIP Interface Tables. */ + {RIP2IFSTATADDRESS, IPADDRESS, RONLY, rip2IfStatEntry, 3, {2, 1, 1}}, + {RIP2IFSTATRCVBADPACKETS, + COUNTER, + RONLY, + rip2IfStatEntry, + 3, + {2, 1, 2}}, + {RIP2IFSTATRCVBADROUTES, COUNTER, RONLY, rip2IfStatEntry, 3, {2, 1, 3}}, + {RIP2IFSTATSENTUPDATES, COUNTER, RONLY, rip2IfStatEntry, 3, {2, 1, 4}}, + {RIP2IFSTATSTATUS, COUNTER, RWRITE, rip2IfStatEntry, 3, {2, 1, 5}}, + {RIP2IFCONFADDRESS, + IPADDRESS, + RONLY, + rip2IfConfAddress, + /* RIP Interface Configuration Table. */ + 3, + {3, 1, 1}}, + {RIP2IFCONFDOMAIN, STRING, RONLY, rip2IfConfAddress, 3, {3, 1, 2}}, + {RIP2IFCONFAUTHTYPE, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 3}}, + {RIP2IFCONFAUTHKEY, STRING, RONLY, rip2IfConfAddress, 3, {3, 1, 4}}, + {RIP2IFCONFSEND, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 5}}, + {RIP2IFCONFRECEIVE, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 6}}, + {RIP2IFCONFDEFAULTMETRIC, + COUNTER, + RONLY, + rip2IfConfAddress, + 3, + {3, 1, 7}}, + {RIP2IFCONFSTATUS, COUNTER, RONLY, rip2IfConfAddress, 3, {3, 1, 8}}, + {RIP2IFCONFSRCADDRESS, + IPADDRESS, + RONLY, + rip2IfConfAddress, + 3, + {3, 1, 9}}, + {RIP2PEERADDRESS, + IPADDRESS, + RONLY, + rip2PeerTable, + /* RIP Peer Table. */ + 3, + {4, 1, 1}}, + {RIP2PEERDOMAIN, STRING, RONLY, rip2PeerTable, 3, {4, 1, 2}}, + {RIP2PEERLASTUPDATE, TIMETICKS, RONLY, rip2PeerTable, 3, {4, 1, 3}}, + {RIP2PEERVERSION, INTEGER, RONLY, rip2PeerTable, 3, {4, 1, 4}}, + {RIP2PEERRCVBADPACKETS, COUNTER, RONLY, rip2PeerTable, 3, {4, 1, 5}}, + {RIP2PEERRCVBADROUTES, COUNTER, RONLY, rip2PeerTable, 3, {4, 1, 6}}}; + +extern struct event_loop *master; + +static uint8_t *rip2Globals(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct rip *rip; + + if (smux_header_generic(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + rip = rip_lookup_by_vrf_id(VRF_DEFAULT); + if (!rip) + return NULL; + + /* Return global counter. */ + switch (v->magic) { + case RIP2GLOBALROUTECHANGES: + return SNMP_INTEGER(rip->counters.route_changes); + case RIP2GLOBALQUERIES: + return SNMP_INTEGER(rip->counters.queries); + default: + return NULL; + } + return NULL; +} + +static int rip_snmp_ifaddr_add(struct connected *ifc) +{ + struct interface *ifp = ifc->ifp; + struct prefix *p; + struct route_node *rn; + + p = ifc->address; + + if (p->family != AF_INET) + return 0; + + rn = route_node_get(rip_ifaddr_table, p); + rn->info = ifp; + return 0; +} + +static int rip_snmp_ifaddr_del(struct connected *ifc) +{ + struct interface *ifp = ifc->ifp; + struct prefix *p; + struct route_node *rn; + struct interface *i; + + p = ifc->address; + + if (p->family != AF_INET) + return 0; + + rn = route_node_lookup(rip_ifaddr_table, p); + if (!rn) + return 0; + i = rn->info; + if (!strncmp(i->name, ifp->name, IFNAMSIZ)) { + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + } + return 0; +} + +static struct interface *rip_ifaddr_lookup_next(struct in_addr *addr) +{ + struct prefix_ipv4 p; + struct route_node *rn; + struct interface *ifp; + + p.family = AF_INET; + p.prefixlen = IPV4_MAX_BITLEN; + p.prefix = *addr; + + rn = route_node_get(rip_ifaddr_table, (struct prefix *)&p); + + for (rn = route_next(rn); rn; rn = route_next(rn)) + if (rn->info) + break; + + if (rn && rn->info) { + ifp = rn->info; + *addr = rn->p.u.prefix4; + route_unlock_node(rn); + return ifp; + } + return NULL; +} + +static struct interface *rip2IfLookup(struct variable *v, oid name[], + size_t *length, struct in_addr *addr, + int exact) +{ + int len; + struct interface *ifp; + + if (exact) { + /* Check the length. */ + if (*length - v->namelen != sizeof(struct in_addr)) + return NULL; + + oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); + + return if_lookup_address_local((void *)addr, AF_INET, + VRF_DEFAULT); + } else { + len = *length - v->namelen; + if (len > 4) + len = 4; + + oid2in_addr(name + v->namelen, len, addr); + + ifp = rip_ifaddr_lookup_next(addr); + + if (ifp == NULL) + return NULL; + + oid_copy_in_addr(name + v->namelen, addr); + + *length = v->namelen + sizeof(struct in_addr); + + return ifp; + } + return NULL; +} + +static struct rip_peer *rip2PeerLookup(struct variable *v, oid name[], + size_t *length, struct in_addr *addr, + int exact) +{ + struct rip *rip; + int len; + struct rip_peer *peer; + + rip = rip_lookup_by_vrf_id(VRF_DEFAULT); + if (!rip) + return NULL; + + if (exact) { + /* Check the length. */ + if (*length - v->namelen != sizeof(struct in_addr) + 1) + return NULL; + + oid2in_addr(name + v->namelen, sizeof(struct in_addr), addr); + + peer = rip_peer_lookup(rip, addr); + + if (peer->domain + == (int)name[v->namelen + sizeof(struct in_addr)]) + return peer; + + return NULL; + } else { + len = *length - v->namelen; + if (len > 4) + len = 4; + + oid2in_addr(name + v->namelen, len, addr); + + len = *length - v->namelen; + peer = rip_peer_lookup(rip, addr); + if (peer) { + if ((len < (int)sizeof(struct in_addr) + 1) + || (peer->domain + > (int)name[v->namelen + + sizeof(struct in_addr)])) { + oid_copy_in_addr(name + v->namelen, + &peer->addr); + name[v->namelen + sizeof(struct in_addr)] = + peer->domain; + *length = + sizeof(struct in_addr) + v->namelen + 1; + return peer; + } + } + peer = rip_peer_lookup_next(rip, addr); + + if (!peer) + return NULL; + + oid_copy_in_addr(name + v->namelen, &peer->addr); + name[v->namelen + sizeof(struct in_addr)] = peer->domain; + *length = sizeof(struct in_addr) + v->namelen + 1; + + return peer; + } + return NULL; +} + +static uint8_t *rip2IfStatEntry(struct variable *v, oid name[], size_t *length, + int exact, size_t *var_len, + WriteMethod **write_method) +{ + struct interface *ifp; + struct rip_interface *ri; + static struct in_addr addr; + static long valid = SNMP_VALID; + + if (smux_header_table(v, name, length, exact, var_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + /* Lookup interface. */ + ifp = rip2IfLookup(v, name, length, &addr, exact); + if (!ifp) + return NULL; + + /* Fetch rip_interface information. */ + ri = ifp->info; + + switch (v->magic) { + case RIP2IFSTATADDRESS: + return SNMP_IPADDRESS(addr); + case RIP2IFSTATRCVBADPACKETS: + *var_len = sizeof(long); + return (uint8_t *)&ri->recv_badpackets; + + case RIP2IFSTATRCVBADROUTES: + *var_len = sizeof(long); + return (uint8_t *)&ri->recv_badroutes; + + case RIP2IFSTATSENTUPDATES: + *var_len = sizeof(long); + return (uint8_t *)&ri->sent_updates; + + case RIP2IFSTATSTATUS: + *var_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&valid; + + default: + return NULL; + } + return NULL; +} + +static long rip2IfConfSend(struct rip_interface *ri) +{ +#define doNotSend 1 +#define ripVersion1 2 +#define rip1Compatible 3 +#define ripVersion2 4 +#define ripV1Demand 5 +#define ripV2Demand 6 + + if (!ri->running) + return doNotSend; + + if (ri->ri_send & RIPv2) + return ripVersion2; + else if (ri->ri_send & RIPv1) + return ripVersion1; + else if (ri->rip) { + if (ri->rip->version_send == RIPv2) + return ripVersion2; + else if (ri->rip->version_send == RIPv1) + return ripVersion1; + } + return doNotSend; +} + +static long rip2IfConfReceive(struct rip_interface *ri) +{ +#define rip1 1 +#define rip2 2 +#define rip1OrRip2 3 +#define doNotReceive 4 + + int recvv; + + if (!ri->running) + return doNotReceive; + + recvv = (ri->ri_receive == RI_RIP_UNSPEC) ? ri->rip->version_recv + : ri->ri_receive; + if (recvv == RI_RIP_VERSION_1_AND_2) + return rip1OrRip2; + else if (recvv & RIPv2) + return rip2; + else if (recvv & RIPv1) + return rip1; + else + return doNotReceive; +} + +static uint8_t *rip2IfConfAddress(struct variable *v, oid name[], + size_t *length, int exact, size_t *val_len, + WriteMethod **write_method) +{ + static struct in_addr addr; + static long valid = SNMP_INVALID; + static long domain = 0; + static long config = 0; + static unsigned int auth = 0; + struct interface *ifp; + struct rip_interface *ri; + + if (smux_header_table(v, name, length, exact, val_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + /* Lookup interface. */ + ifp = rip2IfLookup(v, name, length, &addr, exact); + if (!ifp) + return NULL; + + /* Fetch rip_interface information. */ + ri = ifp->info; + + switch (v->magic) { + case RIP2IFCONFADDRESS: + *val_len = sizeof(struct in_addr); + return (uint8_t *)&addr; + + case RIP2IFCONFDOMAIN: + *val_len = 2; + return (uint8_t *)&domain; + + case RIP2IFCONFAUTHTYPE: + auth = ri->auth_type; + *val_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&auth; + + case RIP2IFCONFAUTHKEY: + *val_len = 0; + return (uint8_t *)&domain; + case RIP2IFCONFSEND: + config = rip2IfConfSend(ri); + *val_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&config; + case RIP2IFCONFRECEIVE: + config = rip2IfConfReceive(ri); + *val_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&config; + + case RIP2IFCONFDEFAULTMETRIC: + *val_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&ifp->metric; + case RIP2IFCONFSTATUS: + *val_len = sizeof(long); + v->type = ASN_INTEGER; + return (uint8_t *)&valid; + case RIP2IFCONFSRCADDRESS: + *val_len = sizeof(struct in_addr); + return (uint8_t *)&addr; + + default: + return NULL; + } + return NULL; +} + +static uint8_t *rip2PeerTable(struct variable *v, oid name[], size_t *length, + int exact, size_t *val_len, + WriteMethod **write_method) +{ + static struct in_addr addr; + static int domain = 0; + static int version; + /* static time_t uptime; */ + + struct rip_peer *peer; + + if (smux_header_table(v, name, length, exact, val_len, write_method) + == MATCH_FAILED) + return NULL; + + memset(&addr, 0, sizeof(addr)); + + /* Lookup interface. */ + peer = rip2PeerLookup(v, name, length, &addr, exact); + if (!peer) + return NULL; + + switch (v->magic) { + case RIP2PEERADDRESS: + *val_len = sizeof(struct in_addr); + return (uint8_t *)&peer->addr; + + case RIP2PEERDOMAIN: + *val_len = 2; + return (uint8_t *)&domain; + + case RIP2PEERLASTUPDATE: + return (uint8_t *)NULL; + + case RIP2PEERVERSION: + *val_len = sizeof(int); + version = peer->version; + return (uint8_t *)&version; + + case RIP2PEERRCVBADPACKETS: + *val_len = sizeof(int); + return (uint8_t *)&peer->recv_badpackets; + + case RIP2PEERRCVBADROUTES: + *val_len = sizeof(int); + return (uint8_t *)&peer->recv_badroutes; + + default: + return NULL; + } + return NULL; +} + +/* Register RIPv2-MIB. */ +static int rip_snmp_init(struct event_loop *master) +{ + rip_ifaddr_table = route_table_init(); + + smux_init(master); + REGISTER_MIB("mibII/rip", rip_variables, variable, rip_oid); + return 0; +} + +static int rip_snmp_module_init(void) +{ + hook_register(rip_ifaddr_add, rip_snmp_ifaddr_add); + hook_register(rip_ifaddr_del, rip_snmp_ifaddr_del); + + hook_register(frr_late_init, rip_snmp_init); + return 0; +} + +FRR_MODULE_SETUP(.name = "ripd_snmp", .version = FRR_VERSION, + .description = "ripd AgentX SNMP module", + .init = rip_snmp_module_init, +); diff --git a/ripd/rip_zebra.c b/ripd/rip_zebra.c new file mode 100644 index 0000000..ce94e8e --- /dev/null +++ b/ripd/rip_zebra.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPd and zebra interface. + * Copyright (C) 1997, 1999 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "log.h" +#include "vrf.h" +#include "bfd.h" +#include "frrdistance.h" + +#include "ripd/ripd.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_interface.h" + +/* All information about zebra. */ +struct zclient *zclient = NULL; + +/* Send ECMP routes to zebra. */ +static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp, + uint8_t cmd) +{ + struct list *list = (struct list *)rp->info; + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct listnode *listnode = NULL; + struct rip_info *rinfo = NULL; + uint32_t count = 0; + + memset(&api, 0, sizeof(api)); + api.vrf_id = rip->vrf->vrf_id; + api.type = ZEBRA_ROUTE_RIP; + api.safi = SAFI_UNICAST; + + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (count >= zebra_ecmp_count) + break; + api_nh = &api.nexthops[count]; + api_nh->vrf_id = rip->vrf->vrf_id; + api_nh->gate = rinfo->nh.gate; + api_nh->type = NEXTHOP_TYPE_IPV4; + if (cmd == ZEBRA_ROUTE_ADD) + SET_FLAG(rinfo->flags, RIP_RTF_FIB); + else + UNSET_FLAG(rinfo->flags, RIP_RTF_FIB); + count++; + } + + api.prefix = rp->p; + api.nexthop_num = count; + + rinfo = listgetdata(listhead(list)); + + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = rinfo->metric; + + if (rinfo->distance && rinfo->distance != ZEBRA_RIP_DISTANCE_DEFAULT) { + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + api.distance = rinfo->distance; + } + + if (rinfo->tag) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = rinfo->tag; + } + + zclient_route_send(cmd, zclient, &api); + + if (IS_RIP_DEBUG_ZEBRA) { + if (rip->ecmp) + zlog_debug("%s: %pFX nexthops %d", + (cmd == ZEBRA_ROUTE_ADD) + ? "Install into zebra" + : "Delete from zebra", + &rp->p, count); + else + zlog_debug("%s: %pFX", + (cmd == ZEBRA_ROUTE_ADD) + ? "Install into zebra" + : "Delete from zebra", &rp->p); + } + + rip->counters.route_changes++; +} + +/* Add/update ECMP routes to zebra. */ +void rip_zebra_ipv4_add(struct rip *rip, struct route_node *rp) +{ + rip_zebra_ipv4_send(rip, rp, ZEBRA_ROUTE_ADD); +} + +/* Delete ECMP routes from zebra. */ +void rip_zebra_ipv4_delete(struct rip *rip, struct route_node *rp) +{ + rip_zebra_ipv4_send(rip, rp, ZEBRA_ROUTE_DELETE); +} + +/* Zebra route add and delete treatment. */ +static int rip_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct rip *rip; + struct zapi_route api; + struct nexthop nh; + + rip = rip_lookup_by_vrf_id(vrf_id); + if (!rip) + return 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + memset(&nh, 0, sizeof(nh)); + nh.type = api.nexthops[0].type; + nh.gate.ipv4 = api.nexthops[0].gate.ipv4; + nh.ifindex = api.nexthops[0].ifindex; + + /* Then fetch IPv4 prefixes. */ + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) + rip_redistribute_add(rip, api.type, RIP_ROUTE_REDISTRIBUTE, + (struct prefix_ipv4 *)&api.prefix, &nh, + api.metric, api.distance, api.tag); + else if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_DEL) + rip_redistribute_delete(rip, api.type, RIP_ROUTE_REDISTRIBUTE, + (struct prefix_ipv4 *)&api.prefix, + nh.ifindex); + + return 0; +} + +void rip_redistribute_conf_update(struct rip *rip, int type) +{ + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + type, 0, rip->vrf->vrf_id); +} + +void rip_redistribute_conf_delete(struct rip *rip, int type) +{ + if (zclient->sock > 0) + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP, type, 0, rip->vrf->vrf_id); + + /* Remove the routes from RIP table. */ + rip_redistribute_withdraw(rip, type); +} + +int rip_redistribute_check(struct rip *rip, int type) +{ + return rip->redist[type].enabled; +} + +void rip_redistribute_enable(struct rip *rip) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!rip_redistribute_check(rip, i)) + continue; + + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + i, 0, rip->vrf->vrf_id); + } +} + +void rip_redistribute_disable(struct rip *rip) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!rip_redistribute_check(rip, i)) + continue; + + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP, i, 0, rip->vrf->vrf_id); + } +} + +void rip_show_redistribute_config(struct vty *vty, struct rip *rip) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (i == zclient->redist_default + || !rip_redistribute_check(rip, i)) + continue; + + vty_out(vty, " %s", zebra_route_string(i)); + } +} + +void rip_zebra_vrf_register(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: register VRF %s(%u) to zebra", __func__, + vrf->name, vrf->vrf_id); + + zclient_send_reg_requests(zclient, vrf->vrf_id); + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf->vrf_id); +} + +void rip_zebra_vrf_deregister(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: deregister VRF %s(%u) from zebra.", __func__, + vrf->name, vrf->vrf_id); + + zclient_send_dereg_requests(zclient, vrf->vrf_id); + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_DEREGISTER, vrf->vrf_id); +} + +static void rip_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); + bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); +} + +zclient_handler *const rip_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = rip_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = rip_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = rip_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = rip_zebra_read_route, +}; + +static void rip_zebra_capabilities(struct zclient_capabilities *cap) +{ + zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count); +} + +void rip_zclient_init(struct event_loop *master) +{ + /* Set default value to the zebra client structure. */ + zclient = zclient_new(master, &zclient_options_default, rip_handlers, + array_size(rip_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_RIP, 0, &ripd_privs); + zclient->zebra_connected = rip_zebra_connected; + zclient->zebra_capabilities = rip_zebra_capabilities; +} + +void rip_zclient_stop(void) +{ + zclient_stop(zclient); + zclient_free(zclient); +} diff --git a/ripd/ripd.c b/ripd/ripd.c new file mode 100644 index 0000000..ab4ffe5 --- /dev/null +++ b/ripd/ripd.c @@ -0,0 +1,3652 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP version 1 and 2. + * Copyright (C) 2005 6WIND + * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro + */ + +#include + +#ifdef CRYPTO_OPENSSL +#include +#include +#endif + +#include "vrf.h" +#include "if.h" +#include "command.h" +#include "prefix.h" +#include "table.h" +#include "frrevent.h" +#include "memory.h" +#include "log.h" +#include "stream.h" +#include "filter.h" +#include "sockunion.h" +#include "sockopt.h" +#include "routemap.h" +#include "if_rmap.h" +#include "plist.h" +#include "distribute.h" +#ifdef CRYPTO_INTERNAL +#include "md5.h" +#endif +#include "keychain.h" +#include "privs.h" +#include "lib_errors.h" +#include "northbound_cli.h" +#include "mgmt_be_client.h" +#include "network.h" +#include "lib/printfrr.h" +#include "frrdistance.h" + +#include "ripd/ripd.h" +#include "ripd/rip_nb.h" +#include "ripd/rip_bfd.h" +#include "ripd/rip_debug.h" +#include "ripd/rip_errors.h" +#include "ripd/rip_interface.h" + +/* UDP receive buffer size */ +#define RIP_UDP_RCV_BUF 41600 + +DEFINE_MGROUP(RIPD, "ripd"); +DEFINE_MTYPE_STATIC(RIPD, RIP, "RIP structure"); +DEFINE_MTYPE_STATIC(RIPD, RIP_VRF_NAME, "RIP VRF name"); +DEFINE_MTYPE_STATIC(RIPD, RIP_INFO, "RIP route info"); +DEFINE_MTYPE_STATIC(RIPD, RIP_DISTANCE, "RIP distance"); + +/* Prototypes. */ +static void rip_output_process(struct connected *, struct sockaddr_in *, int, + uint8_t); +static void rip_triggered_update(struct event *); +static int rip_update_jitter(unsigned long); +static void rip_distance_table_node_cleanup(struct route_table *table, + struct route_node *node); +static void rip_instance_enable(struct rip *rip, struct vrf *vrf, int sock); +static void rip_instance_disable(struct rip *rip); + +static void rip_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist); + +static void rip_if_rmap_update(struct if_rmap_ctx *ctx, + struct if_rmap *if_rmap); + +/* RIP output routes type. */ +enum { rip_all_route, rip_changed_route }; + +/* RIP command strings. */ +static const struct message rip_msg[] = {{RIP_REQUEST, "REQUEST"}, + {RIP_RESPONSE, "RESPONSE"}, + {RIP_TRACEON, "TRACEON"}, + {RIP_TRACEOFF, "TRACEOFF"}, + {RIP_POLL, "POLL"}, + {RIP_POLL_ENTRY, "POLL ENTRY"}, + {0}}; + +/* Generate rb-tree of RIP instances. */ +static inline int rip_instance_compare(const struct rip *a, const struct rip *b) +{ + return strcmp(a->vrf_name, b->vrf_name); +} +RB_GENERATE(rip_instance_head, rip, entry, rip_instance_compare) + +struct rip_instance_head rip_instances = RB_INITIALIZER(&rip_instances); + +/* Utility function to set broadcast option to the socket. */ +static int sockopt_broadcast(int sock) +{ + int ret; + int on = 1; + + ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&on, + sizeof(on)); + if (ret < 0) { + zlog_warn("can't set sockopt SO_BROADCAST to socket %d", sock); + return -1; + } + return 0; +} + +int rip_route_rte(struct rip_info *rinfo) +{ + return (rinfo->type == ZEBRA_ROUTE_RIP + && rinfo->sub_type == RIP_ROUTE_RTE); +} + +static struct rip_info *rip_info_new(void) +{ + return XCALLOC(MTYPE_RIP_INFO, sizeof(struct rip_info)); +} + +void rip_info_free(struct rip_info *rinfo) +{ + XFREE(MTYPE_RIP_INFO, rinfo); +} + +struct rip *rip_info_get_instance(const struct rip_info *rinfo) +{ + return route_table_get_info(rinfo->rp->table); +} + +/* RIP route garbage collect timer. */ +static void rip_garbage_collect(struct event *t) +{ + struct rip_info *rinfo; + struct route_node *rp; + + rinfo = EVENT_ARG(t); + + /* Off timeout timer. */ + EVENT_OFF(rinfo->t_timeout); + + /* Get route_node pointer. */ + rp = rinfo->rp; + + /* Unlock route_node. */ + listnode_delete(rp->info, rinfo); + if (list_isempty((struct list *)rp->info)) { + list_delete((struct list **)&rp->info); + route_unlock_node(rp); + } + + /* Free RIP routing information. */ + rip_info_free(rinfo); +} + +static void rip_timeout_update(struct rip *rip, struct rip_info *rinfo); + +/* Add new route to the ECMP list. + * RETURN: the new entry added in the list, or NULL if it is not the first + * entry and ECMP is not allowed. + */ +struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new) +{ + struct route_node *rp = rinfo_new->rp; + struct rip_info *rinfo = NULL; + struct rip_info *rinfo_exist = NULL; + struct list *list = NULL; + struct listnode *node = NULL; + struct listnode *nnode = NULL; + + if (rp->info == NULL) + rp->info = list_new(); + list = (struct list *)rp->info; + + /* If ECMP is not allowed and some entry already exists in the list, + * do nothing. */ + if (listcount(list) && !rip->ecmp) + return NULL; + + /* Add or replace an existing ECMP path with lower neighbor IP */ + if (listcount(list) && listcount(list) >= rip->ecmp) { + struct rip_info *from_highest = NULL; + + /* Find the rip_info struct that has the highest nexthop IP */ + for (ALL_LIST_ELEMENTS(list, node, nnode, rinfo_exist)) + if (!from_highest || + (from_highest && + IPV4_ADDR_CMP(&rinfo_exist->from, + &from_highest->from) > 0)) { + from_highest = rinfo_exist; + } + + /* If we have a route in ECMP group, delete the old + * one that has a higher next-hop address. Lower IP is + * preferred. + */ + if (rip->ecmp > 1 && from_highest && + IPV4_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) { + rip_ecmp_delete(rip, from_highest); + goto add_or_replace; + } + + return NULL; + } + +add_or_replace: + rinfo = rip_info_new(); + memcpy(rinfo, rinfo_new, sizeof(struct rip_info)); + listnode_add(list, rinfo); + + if (rip_route_rte(rinfo)) { + rip_timeout_update(rip, rinfo); + rip_zebra_ipv4_add(rip, rp); + } + + /* Set the route change flag on the first entry. */ + rinfo = listgetdata(listhead(list)); + SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + + /* Signal the output process to trigger an update (see section 2.5). */ + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Replace the ECMP list with the new route. + * RETURN: the new entry added in the list + */ +struct rip_info *rip_ecmp_replace(struct rip *rip, struct rip_info *rinfo_new) +{ + struct route_node *rp = rinfo_new->rp; + struct list *list = (struct list *)rp->info; + struct rip_info *rinfo = NULL, *tmp_rinfo = NULL; + struct listnode *node = NULL, *nextnode = NULL; + + if (list == NULL || listcount(list) == 0) + return rip_ecmp_add(rip, rinfo_new); + + /* Get the first entry */ + rinfo = listgetdata(listhead(list)); + + /* Learnt route replaced by a local one. Delete it from zebra. */ + if (rip_route_rte(rinfo) && !rip_route_rte(rinfo_new)) + if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + rip_zebra_ipv4_delete(rip, rp); + + /* Re-use the first entry, and delete the others. */ + for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) { + if (tmp_rinfo == rinfo) + continue; + + EVENT_OFF(tmp_rinfo->t_timeout); + EVENT_OFF(tmp_rinfo->t_garbage_collect); + list_delete_node(list, node); + rip_info_free(tmp_rinfo); + } + + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + memcpy(rinfo, rinfo_new, sizeof(struct rip_info)); + + if (rip_route_rte(rinfo)) { + rip_timeout_update(rip, rinfo); + /* The ADD message implies an update. */ + rip_zebra_ipv4_add(rip, rp); + } + + /* Set the route change flag. */ + SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + + /* Signal the output process to trigger an update (see section 2.5). */ + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Delete one route from the ECMP list. + * RETURN: + * null - the entry is freed, and other entries exist in the list + * the entry - the entry is the last one in the list; its metric is set + * to INFINITY, and the garbage collector is started for it + */ +struct rip_info *rip_ecmp_delete(struct rip *rip, struct rip_info *rinfo) +{ + struct route_node *rp = rinfo->rp; + struct list *list = (struct list *)rp->info; + + EVENT_OFF(rinfo->t_timeout); + + if (listcount(list) > 1) { + /* Some other ECMP entries still exist. Just delete this entry. + */ + EVENT_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + if (rip_route_rte(rinfo) + && CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + /* The ADD message implies the update. */ + rip_zebra_ipv4_add(rip, rp); + rip_info_free(rinfo); + rinfo = NULL; + } else { + assert(rinfo == listgetdata(listhead(list))); + + /* This is the only entry left in the list. We must keep it in + * the list for garbage collection time, with INFINITY metric. + */ + + rinfo->metric = RIP_METRIC_INFINITY; + RIP_TIMER_ON(rinfo->t_garbage_collect, rip_garbage_collect, + rip->garbage_time); + + if (rip_route_rte(rinfo) + && CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + rip_zebra_ipv4_delete(rip, rp); + } + + /* Set the route change flag on the first entry. */ + rinfo = listgetdata(listhead(list)); + SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + + /* Signal the output process to trigger an update (see section 2.5). */ + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Timeout RIP routes. */ +static void rip_timeout(struct event *t) +{ + struct rip_info *rinfo = EVENT_ARG(t); + struct rip *rip = rip_info_get_instance(rinfo); + + rip_ecmp_delete(rip, rinfo); +} + +static void rip_timeout_update(struct rip *rip, struct rip_info *rinfo) +{ + if (rinfo->metric != RIP_METRIC_INFINITY) { + EVENT_OFF(rinfo->t_timeout); + event_add_timer(master, rip_timeout, rinfo, rip->timeout_time, + &rinfo->t_timeout); + } +} + +static int rip_filter(int rip_distribute, struct prefix_ipv4 *p, + struct rip_interface *ri) +{ + struct distribute *dist; + struct access_list *alist; + struct prefix_list *plist; + int distribute = rip_distribute == RIP_FILTER_OUT ? DISTRIBUTE_V4_OUT + : DISTRIBUTE_V4_IN; + const char *inout = rip_distribute == RIP_FILTER_OUT ? "out" : "in"; + + /* Input distribute-list filtering. */ + if (ri->list[rip_distribute] && + access_list_apply(ri->list[rip_distribute], (struct prefix *)p) == + FILTER_DENY) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("%pFX filtered by distribute %s", p, inout); + return -1; + } + + if (ri->prefix[rip_distribute] && + prefix_list_apply(ri->prefix[rip_distribute], (struct prefix *)p) == + PREFIX_DENY) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("%pFX filtered by prefix-list %s", p, inout); + return -1; + } + + /* All interface filter check. */ + dist = distribute_lookup(ri->rip->distribute_ctx, NULL); + if (!dist) + return 0; + + if (dist->list[distribute]) { + alist = access_list_lookup(AFI_IP, dist->list[distribute]); + + if (alist) { + if (access_list_apply(alist, (struct prefix *)p) == + FILTER_DENY) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "%pFX filtered by distribute %s", + p, inout); + return -1; + } + } + } + if (dist->prefix[distribute]) { + plist = prefix_list_lookup(AFI_IP, dist->prefix[distribute]); + + if (plist) { + if (prefix_list_apply(plist, (struct prefix *)p) == + PREFIX_DENY) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "%pFX filtered by prefix-list %s", + p, inout); + return -1; + } + } + } + + return 0; +} + +/* Check nexthop address validity. */ +static int rip_nexthop_check(struct rip *rip, struct in_addr *addr) +{ + struct interface *ifp; + struct connected *ifc; + struct prefix *p; + + /* If nexthop address matches local configured address then it is + invalid nexthop. */ + + FOR_ALL_INTERFACES (rip->vrf, ifp) { + frr_each (if_connected, ifp->connected, ifc) { + p = ifc->address; + + if (p->family == AF_INET + && IPV4_ADDR_SAME(&p->u.prefix4, addr)) + return -1; + } + } + return 0; +} + +/* RIP add route to routing table. */ +static void rip_rte_process(struct rte *rte, struct sockaddr_in *from, + struct interface *ifp) +{ + struct rip *rip; + int ret; + struct prefix_ipv4 p; + struct route_node *rp; + struct rip_info *rinfo = NULL, newinfo; + struct rip_interface *ri; + struct in_addr *nexthop; + int same = 0; + unsigned char old_dist, new_dist; + struct list *list = NULL; + struct listnode *node = NULL; + + /* Make prefix structure. */ + memset(&p, 0, sizeof(struct prefix_ipv4)); + p.family = AF_INET; + p.prefix = rte->prefix; + p.prefixlen = ip_masklen(rte->mask); + + /* Make sure mask is applied. */ + apply_mask_ipv4(&p); + + ri = ifp->info; + rip = ri->rip; + + /* Apply input filters. */ + ret = rip_filter(RIP_FILTER_IN, &p, ri); + if (ret < 0) + return; + + memset(&newinfo, 0, sizeof(newinfo)); + newinfo.type = ZEBRA_ROUTE_RIP; + newinfo.sub_type = RIP_ROUTE_RTE; + newinfo.nh.gate.ipv4 = rte->nexthop; + newinfo.from = from->sin_addr; + newinfo.nh.ifindex = ifp->ifindex; + newinfo.nh.type = NEXTHOP_TYPE_IPV4_IFINDEX; + newinfo.metric = rte->metric; + newinfo.metric_out = rte->metric; /* XXX */ + newinfo.tag = ntohs(rte->tag); /* XXX */ + + /* Modify entry according to the interface routemap. */ + if (ri->routemap[RIP_FILTER_IN]) { + /* The object should be of the type of rip_info */ + ret = route_map_apply(ri->routemap[RIP_FILTER_IN], + (struct prefix *)&p, &newinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIP %pFX is filtered by route-map in", + &p); + return; + } + + /* Get back the object */ + rte->nexthop = newinfo.nexthop_out; + rte->tag = htons(newinfo.tag_out); /* XXX */ + rte->metric = newinfo.metric_out; /* XXX: the routemap uses the + metric_out field */ + } + + /* Once the entry has been validated, update the metric by + adding the cost of the network on which the message + arrived. If the result is greater than infinity, use infinity + (RFC2453 Sec. 3.9.2) */ + /* Zebra ripd can handle offset-list in. */ + ret = rip_offset_list_apply_in(&p, ifp, &rte->metric); + + /* If offset-list does not modify the metric use interface's + metric. */ + if (!ret) + rte->metric += ifp->metric ? ifp->metric : 1; + + if (rte->metric > RIP_METRIC_INFINITY) + rte->metric = RIP_METRIC_INFINITY; + + /* Set nexthop pointer. */ + if (rte->nexthop.s_addr == INADDR_ANY) + nexthop = &from->sin_addr; + else + nexthop = &rte->nexthop; + + /* Check if nexthop address is myself, then do nothing. */ + if (rip_nexthop_check(rip, nexthop) < 0) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("Nexthop address %pI4 is myself", + nexthop); + return; + } + + /* Get index for the prefix. */ + rp = route_node_get(rip->table, (struct prefix *)&p); + + newinfo.rp = rp; + newinfo.nh.gate.ipv4 = *nexthop; + newinfo.nh.type = NEXTHOP_TYPE_IPV4; + newinfo.metric = rte->metric; + newinfo.tag = ntohs(rte->tag); + newinfo.distance = rip_distance_apply(rip, &newinfo); + + new_dist = newinfo.distance ? newinfo.distance + : ZEBRA_RIP_DISTANCE_DEFAULT; + + /* Check to see whether there is already RIP route on the table. */ + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { + /* Need to compare with redistributed entry or local + * entry */ + if (!rip_route_rte(rinfo)) + break; + + if (IPV4_ADDR_SAME(&rinfo->from, &from->sin_addr) + && IPV4_ADDR_SAME(&rinfo->nh.gate.ipv4, nexthop)) + break; + + if (listnextnode(node)) + continue; + + /* Not found in the list */ + + if (rte->metric > rinfo->metric) { + /* New route has a greater metric. + * Discard it. */ + route_unlock_node(rp); + return; + } + + if (rte->metric < rinfo->metric) + /* New route has a smaller metric. + * Replace the ECMP list + * with the new one in below. */ + break; + + /* Metrics are same. We compare the distances. + */ + old_dist = rinfo->distance ? rinfo->distance + : ZEBRA_RIP_DISTANCE_DEFAULT; + + if (new_dist > old_dist) { + /* New route has a greater distance. + * Discard it. */ + route_unlock_node(rp); + return; + } + + if (new_dist < old_dist) + /* New route has a smaller distance. + * Replace the ECMP list + * with the new one in below. */ + break; + + /* Metrics and distances are both same. Keep + * "rinfo" null and + * the new route is added in the ECMP list in + * below. */ + } + + if (rinfo) { + /* Local static route. */ + if (rinfo->type == ZEBRA_ROUTE_RIP + && ((rinfo->sub_type == RIP_ROUTE_STATIC) + || (rinfo->sub_type == RIP_ROUTE_DEFAULT)) + && rinfo->metric != RIP_METRIC_INFINITY) { + route_unlock_node(rp); + return; + } + + /* Redistributed route check. */ + if (rinfo->type != ZEBRA_ROUTE_RIP + && rinfo->metric != RIP_METRIC_INFINITY) { + old_dist = rinfo->distance; + /* Only routes directly connected to an interface + * (nexthop == 0) + * may have a valid NULL distance */ + if (rinfo->nh.gate.ipv4.s_addr != INADDR_ANY) + old_dist = old_dist + ? old_dist + : ZEBRA_RIP_DISTANCE_DEFAULT; + /* If imported route does not have STRICT precedence, + mark it as a ghost */ + if (new_dist <= old_dist + && rte->metric != RIP_METRIC_INFINITY) + rip_ecmp_replace(rip, &newinfo); + + route_unlock_node(rp); + return; + } + } + + if (!rinfo) { + if (rp->info) + route_unlock_node(rp); + + /* Now, check to see whether there is already an explicit route + for the destination prefix. If there is no such route, add + this route to the routing table, unless the metric is + infinity (there is no point in adding a route which + unusable). */ + if (rte->metric != RIP_METRIC_INFINITY) + rip_ecmp_add(rip, &newinfo); + } else { + /* Route is there but we are not sure the route is RIP or not. + */ + + /* If there is an existing route, compare the next hop address + to the address of the router from which the datagram came. + If this datagram is from the same router as the existing + route, reinitialize the timeout. */ + same = (IPV4_ADDR_SAME(&rinfo->from, &from->sin_addr) + && (rinfo->nh.ifindex == ifp->ifindex)); + + old_dist = rinfo->distance ? rinfo->distance + : ZEBRA_RIP_DISTANCE_DEFAULT; + + /* Next, compare the metrics. If the datagram is from the same + router as the existing route, and the new metric is different + than the old one; or, if the new metric is lower than the old + one, or if the tag has been changed; or if there is a route + with a lower administrave distance; or an update of the + distance on the actual route; do the following actions: */ + if ((same && rinfo->metric != rte->metric) + || (rte->metric < rinfo->metric) + || ((same) && (rinfo->metric == rte->metric) + && (newinfo.tag != rinfo->tag)) + || (old_dist > new_dist) + || ((old_dist != new_dist) && same)) { + if (listcount(list) == 1) { + if (newinfo.metric != RIP_METRIC_INFINITY) + rip_ecmp_replace(rip, &newinfo); + else + rip_ecmp_delete(rip, rinfo); + } else { + if (newinfo.metric < rinfo->metric) + rip_ecmp_replace(rip, &newinfo); + else if (newinfo.metric > rinfo->metric) + rip_ecmp_delete(rip, rinfo); + else if (new_dist < old_dist) + rip_ecmp_replace(rip, &newinfo); + else if (new_dist > old_dist) + rip_ecmp_delete(rip, rinfo); + else { + int update = CHECK_FLAG(rinfo->flags, + RIP_RTF_FIB) + ? 1 + : 0; + + assert(newinfo.metric + != RIP_METRIC_INFINITY); + + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + memcpy(rinfo, &newinfo, + sizeof(struct rip_info)); + rip_timeout_update(rip, rinfo); + + if (update) + rip_zebra_ipv4_add(rip, rp); + + /* - Set the route change flag on the + * first entry. */ + rinfo = listgetdata(listhead(list)); + SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + } + } + } else /* same & no change */ + rip_timeout_update(rip, rinfo); + + /* Unlock tempolary lock of the route. */ + route_unlock_node(rp); + } +} + +/* Dump RIP packet */ +static void rip_packet_dump(struct rip_packet *packet, int size, + const char *sndrcv) +{ + caddr_t lim; + struct rte *rte; + const char *command_str; + uint8_t netmask = 0; + uint8_t *p; + + /* Set command string. */ + if (packet->command > 0 && packet->command < RIP_COMMAND_MAX) + command_str = lookup_msg(rip_msg, packet->command, NULL); + else + command_str = "unknown"; + + /* Dump packet header. */ + zlog_debug("%s %s version %d packet size %d", sndrcv, command_str, + packet->version, size); + + /* Dump each routing table entry. */ + rte = packet->rte; + + for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { + if (packet->version == RIPv2) { + netmask = ip_masklen(rte->mask); + + if (rte->family == htons(RIP_FAMILY_AUTH)) { + if (rte->tag + == htons(RIP_AUTH_SIMPLE_PASSWORD)) { + p = (uint8_t *)&rte->prefix; + + zlog_debug( + " family 0x%X type %d auth string: %s", + ntohs(rte->family), + ntohs(rte->tag), p); + } else if (rte->tag == htons(RIP_AUTH_MD5)) { + struct rip_md5_info *md5; + + md5 = (struct rip_md5_info *)&packet + ->rte; + + zlog_debug( + " family 0x%X type %d (MD5 authentication)", + ntohs(md5->family), + ntohs(md5->type)); + zlog_debug( + " RIP-2 packet len %d Key ID %d Auth Data len %d", + ntohs(md5->packet_len), + md5->keyid, md5->auth_len); + zlog_debug(" Sequence Number %ld", + (unsigned long)ntohl( + md5->sequence)); + } else if (rte->tag == htons(RIP_AUTH_DATA)) { + p = (uint8_t *)&rte->prefix; + + zlog_debug( + " family 0x%X type %d (MD5 data)", + ntohs(rte->family), + ntohs(rte->tag)); + zlog_debug( + " MD5: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + p[0], p[1], p[2], p[3], p[4], + p[5], p[6], p[7], p[8], p[9], + p[10], p[11], p[12], p[13], + p[14], p[15]); + } else { + zlog_debug( + " family 0x%X type %d (Unknown auth type)", + ntohs(rte->family), + ntohs(rte->tag)); + } + } else + zlog_debug( + " %pI4/%d -> %pI4 family %d tag %" ROUTE_TAG_PRI + " metric %ld", + &rte->prefix, netmask, &rte->nexthop, + ntohs(rte->family), + (route_tag_t)ntohs(rte->tag), + (unsigned long)ntohl(rte->metric)); + } else { + zlog_debug(" %pI4 family %d tag %" ROUTE_TAG_PRI + " metric %ld", + &rte->prefix, ntohs(rte->family), + (route_tag_t)ntohs(rte->tag), + (unsigned long)ntohl(rte->metric)); + } + } +} + +/* Check if the destination address is valid (unicast; not net 0 + or 127) (RFC2453 Section 3.9.2 - Page 26). But we don't + check net 0 because we accept default route. */ +static int rip_destination_check(struct in_addr addr) +{ + uint32_t destination; + + /* Convert to host byte order. */ + destination = ntohl(addr.s_addr); + + if (IPV4_NET127(destination)) + return 0; + + /* Net 0 may match to the default route. */ + if (IPV4_NET0(destination) && destination != 0) + return 0; + + /* Unicast address must belong to class A, B, C. */ + if (IN_CLASSA(destination)) + return 1; + if (IN_CLASSB(destination)) + return 1; + if (IN_CLASSC(destination)) + return 1; + + return 0; +} + +/* RIP version 2 authentication. */ +static int rip_auth_simple_password(struct rte *rte, struct sockaddr_in *from, + struct interface *ifp) +{ + struct rip_interface *ri; + char *auth_str = (char *)rte + offsetof(struct rte, prefix); + int i; + + /* reject passwords with zeros in the middle of the string */ + for (i = strnlen(auth_str, 16); i < 16; i++) { + if (auth_str[i] != '\0') + return 0; + } + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("RIPv2 simple password authentication from %pI4", + &from->sin_addr); + + ri = ifp->info; + + if (ri->auth_type != RIP_AUTH_SIMPLE_PASSWORD + || rte->tag != htons(RIP_AUTH_SIMPLE_PASSWORD)) + return 0; + + /* Simple password authentication. */ + if (ri->auth_str) { + if (strncmp(auth_str, ri->auth_str, 16) == 0) + return 1; + } + if (ri->key_chain) { + struct keychain *keychain; + struct key *key; + + keychain = keychain_lookup(ri->key_chain); + if (keychain == NULL || keychain->key == NULL) + return 0; + + key = key_match_for_accept(keychain, auth_str); + if (key) + return 1; + } + return 0; +} + +/* RIP version 2 authentication with MD5. */ +static int rip_auth_md5(struct rip_packet *packet, struct sockaddr_in *from, + int length, struct interface *ifp) +{ + struct rip_interface *ri; + struct rip_md5_info *md5; + struct rip_md5_data *md5data; + struct keychain *keychain; + struct key *key; +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + uint8_t digest[RIP_AUTH_MD5_SIZE]; + uint16_t packet_len; + char auth_str[RIP_AUTH_MD5_SIZE] = {}; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("RIPv2 MD5 authentication from %pI4", + &from->sin_addr); + + ri = ifp->info; + md5 = (struct rip_md5_info *)&packet->rte; + + /* Check auth type. */ + if (ri->auth_type != RIP_AUTH_MD5 || md5->type != htons(RIP_AUTH_MD5)) + return 0; + + /* If the authentication length is less than 16, then it must be wrong + * for + * any interpretation of rfc2082. Some implementations also interpret + * this as RIP_HEADER_SIZE+ RIP_AUTH_MD5_SIZE, aka + * RIP_AUTH_MD5_COMPAT_SIZE. + */ + if (!((md5->auth_len == RIP_AUTH_MD5_SIZE) + || (md5->auth_len == RIP_AUTH_MD5_COMPAT_SIZE))) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "RIPv2 MD5 authentication, strange authentication length field %d", + md5->auth_len); + return 0; + } + + /* grab and verify check packet length */ + packet_len = ntohs(md5->packet_len); + + if (packet_len > (length - RIP_HEADER_SIZE - RIP_AUTH_MD5_SIZE)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "RIPv2 MD5 authentication, packet length field %d greater than received length %d!", + md5->packet_len, length); + return 0; + } + + /* retrieve authentication data */ + md5data = (struct rip_md5_data *)(((uint8_t *)packet) + packet_len); + + if (ri->key_chain) { + keychain = keychain_lookup(ri->key_chain); + if (keychain == NULL) + return 0; + + key = key_lookup_for_accept(keychain, md5->keyid); + if (key == NULL || key->string == NULL) + return 0; + + memcpy(auth_str, key->string, + MIN(sizeof(auth_str), strlen(key->string))); + } else if (ri->auth_str) + memcpy(auth_str, ri->auth_str, + MIN(sizeof(auth_str), strlen(ri->auth_str))); + + if (auth_str[0] == 0) + return 0; + + /* MD5 digest authentication. */ +#ifdef CRYPTO_OPENSSL + unsigned int md5_size = RIP_AUTH_MD5_SIZE; + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, packet, packet_len + RIP_HEADER_SIZE); + EVP_DigestUpdate(ctx, auth_str, RIP_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, packet, packet_len + RIP_HEADER_SIZE); + MD5Update(&ctx, auth_str, RIP_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + if (memcmp(md5data->digest, digest, RIP_AUTH_MD5_SIZE) == 0) + return packet_len; + else + return 0; +} + +/* Pick correct auth string for sends, prepare auth_str buffer for use. + * (left justified and padded). + * + * presumes one of ri or key is valid, and that the auth strings they point + * to are nul terminated. If neither are present, auth_str will be fully + * zero padded. + * + */ +static void rip_auth_prepare_str_send(struct rip_interface *ri, struct key *key, + char *auth_str, int len) +{ + assert(ri || key); + + memset(auth_str, 0, len); + if (key && key->string) + memcpy(auth_str, key->string, + MIN((size_t)len, strlen(key->string))); + else if (ri->auth_str) + memcpy(auth_str, ri->auth_str, + MIN((size_t)len, strlen(ri->auth_str))); + + return; +} + +/* Write RIPv2 simple password authentication information + * + * auth_str is presumed to be 2 bytes and correctly prepared + * (left justified and zero padded). + */ +static void rip_auth_simple_write(struct stream *s, char *auth_str, int len) +{ + assert(s && len == RIP_AUTH_SIMPLE_SIZE); + + stream_putw(s, RIP_FAMILY_AUTH); + stream_putw(s, RIP_AUTH_SIMPLE_PASSWORD); + stream_put(s, auth_str, RIP_AUTH_SIMPLE_SIZE); + + return; +} + +/* write RIPv2 MD5 "authentication header" + * (uses the auth key data field) + * + * Digest offset field is set to 0. + * + * returns: offset of the digest offset field, which must be set when + * length to the auth-data MD5 digest is known. + */ +static size_t rip_auth_md5_ah_write(struct stream *s, struct rip_interface *ri, + struct key *key) +{ + size_t doff = 0; + static uint32_t seq = 0; + + assert(s && ri && ri->auth_type == RIP_AUTH_MD5); + + /* MD5 authentication. */ + stream_putw(s, RIP_FAMILY_AUTH); + stream_putw(s, RIP_AUTH_MD5); + + /* MD5 AH digest offset field. + * + * Set to placeholder value here, to true value when RIP-2 Packet length + * is known. Actual value is set in .....(). + */ + doff = stream_get_endp(s); + stream_putw(s, 0); + + /* Key ID. */ + if (key) + stream_putc(s, key->index % 256); + else + stream_putc(s, 1); + + /* Auth Data Len. Set 16 for MD5 authentication data. Older ripds + * however expect RIP_HEADER_SIZE + RIP_AUTH_MD5_SIZE so we allow for + * this + * to be configurable. + */ + stream_putc(s, ri->md5_auth_len); + + /* Sequence Number (non-decreasing). */ + /* RFC2080: The value used in the sequence number is + arbitrary, but two suggestions are the time of the + message's creation or a simple message counter. */ + stream_putl(s, seq++); + + /* Reserved field must be zero. */ + stream_putl(s, 0); + stream_putl(s, 0); + + return doff; +} + +/* If authentication is in used, write the appropriate header + * returns stream offset to which length must later be written + * or 0 if this is not required + */ +static size_t rip_auth_header_write(struct stream *s, struct rip_interface *ri, + struct key *key, char *auth_str, int len) +{ + assert(ri->auth_type != RIP_NO_AUTH); + + switch (ri->auth_type) { + case RIP_AUTH_SIMPLE_PASSWORD: + rip_auth_prepare_str_send(ri, key, auth_str, len); + rip_auth_simple_write(s, auth_str, len); + return 0; + case RIP_AUTH_MD5: + return rip_auth_md5_ah_write(s, ri, key); + } + assert(1); + return 0; +} + +/* Write RIPv2 MD5 authentication data trailer */ +static void rip_auth_md5_set(struct stream *s, struct rip_interface *ri, + size_t doff, char *auth_str, int authlen) +{ + unsigned long len; +#ifdef CRYPTO_OPENSSL + EVP_MD_CTX *ctx; +#elif CRYPTO_INTERNAL + MD5_CTX ctx; +#endif + unsigned char digest[RIP_AUTH_MD5_SIZE]; + + /* Make it sure this interface is configured as MD5 + authentication. */ + assert((ri->auth_type == RIP_AUTH_MD5) + && (authlen == RIP_AUTH_MD5_SIZE)); + assert(doff > 0); + + /* Get packet length. */ + len = stream_get_endp(s); + + /* Check packet length. */ + if (len < (RIP_HEADER_SIZE + RIP_RTE_SIZE)) { + flog_err(EC_RIP_PACKET, + "%s: packet length %ld is less than minimum length.", + __func__, len); + return; + } + + /* Set the digest offset length in the header */ + stream_putw_at(s, doff, len); + + /* Set authentication data. */ + stream_putw(s, RIP_FAMILY_AUTH); + stream_putw(s, RIP_AUTH_DATA); + + /* Generate a digest for the RIP packet. */ +#ifdef CRYPTO_OPENSSL + unsigned int md5_size = RIP_AUTH_MD5_SIZE; + ctx = EVP_MD_CTX_new(); + EVP_DigestInit(ctx, EVP_md5()); + EVP_DigestUpdate(ctx, STREAM_DATA(s), stream_get_endp(s)); + EVP_DigestUpdate(ctx, auth_str, RIP_AUTH_MD5_SIZE); + EVP_DigestFinal(ctx, digest, &md5_size); + EVP_MD_CTX_free(ctx); +#elif CRYPTO_INTERNAL + memset(&ctx, 0, sizeof(ctx)); + MD5Init(&ctx); + MD5Update(&ctx, STREAM_DATA(s), stream_get_endp(s)); + MD5Update(&ctx, auth_str, RIP_AUTH_MD5_SIZE); + MD5Final(digest, &ctx); +#endif + + /* Copy the digest to the packet. */ + stream_write(s, digest, RIP_AUTH_MD5_SIZE); +} + +/* RIP routing information. */ +static void rip_response_process(struct rip_packet *packet, int size, + struct sockaddr_in *from, + struct connected *ifc) +{ + struct rip_interface *ri = ifc->ifp->info; + struct rip *rip = ri->rip; + caddr_t lim; + struct rte *rte; + struct prefix_ipv4 ifaddr; + struct prefix_ipv4 ifaddrclass; + int subnetted; + + memset(&ifaddr, 0, sizeof(ifaddr)); + /* We don't know yet. */ + subnetted = -1; + + /* The Response must be ignored if it is not from the RIP + port. (RFC2453 - Sec. 3.9.2)*/ + if (from->sin_port != htons(RIP_PORT_DEFAULT)) { + zlog_info("response doesn't come from RIP port: %d", + from->sin_port); + rip_peer_bad_packet(rip, ri, from); + return; + } + + /* The datagram's IPv4 source address should be checked to see + whether the datagram is from a valid neighbor; the source of the + datagram must be on a directly connected network (RFC2453 - Sec. + 3.9.2) */ + if (if_lookup_address((void *)&from->sin_addr, AF_INET, + rip->vrf->vrf_id) + == NULL) { + zlog_info( + "This datagram doesn't come from a valid neighbor: %pI4", + &from->sin_addr); + rip_peer_bad_packet(rip, ri, from); + return; + } + + /* It is also worth checking to see whether the response is from one + of the router's own addresses. */ + + ; /* Alredy done in rip_read () */ + + /* Update RIP peer. */ + rip_peer_update(rip, ri, from, packet->version); + + /* Set RTE pointer. */ + rte = packet->rte; + + for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { + /* RIPv2 authentication check. */ + /* If the Address Family Identifier of the first (and only the + first) entry in the message is 0xFFFF, then the remainder of + the entry contains the authentication. */ + /* If the packet gets here it means authentication enabled */ + /* Check is done in rip_read(). So, just skipping it */ + if (packet->version == RIPv2 && rte == packet->rte + && rte->family == htons(RIP_FAMILY_AUTH)) + continue; + + if (rte->family != htons(AF_INET)) { + /* Address family check. RIP only supports AF_INET. */ + zlog_info("Unsupported family %d from %pI4", + ntohs(rte->family), + &from->sin_addr); + continue; + } + + if (packet->version == RIPv1 && rte->tag != 0) { + zlog_warn("RIPv1 reserved field is nonzero: %d", + ntohs(rte->tag)); + continue; + } + + /* - is the destination address valid (e.g., unicast; not net 0 + or 127) */ + if (!rip_destination_check(rte->prefix)) { + zlog_info( + "Network is net 0 or net 127 or it is not unicast network"); + rip_peer_bad_route(rip, ri, from); + continue; + } + + /* Convert metric value to host byte order. */ + rte->metric = ntohl(rte->metric); + + /* - is the metric valid (i.e., between 1 and 16, inclusive) */ + if (!(rte->metric >= 1 && rte->metric <= 16)) { + zlog_info("Route's metric is not in the 1-16 range."); + rip_peer_bad_route(rip, ri, from); + continue; + } + + /* RIPv1 does not have nexthop value. */ + if (packet->version == RIPv1 + && rte->nexthop.s_addr != INADDR_ANY) { + zlog_info("RIPv1 packet with nexthop value %pI4", + &rte->nexthop); + rip_peer_bad_route(rip, ri, from); + continue; + } + + /* That is, if the provided information is ignored, a possibly + sub-optimal, but absolutely valid, route may be taken. If + the received Next Hop is not directly reachable, it should be + treated as 0.0.0.0. */ + if (packet->version == RIPv2 + && rte->nexthop.s_addr != INADDR_ANY) { + uint32_t addrval; + + /* Multicast address check. */ + addrval = ntohl(rte->nexthop.s_addr); + if (IN_CLASSD(addrval)) { + zlog_info( + "Nexthop %pI4 is multicast address, skip this rte", + &rte->nexthop); + continue; + } + + if (!if_lookup_address((void *)&rte->nexthop, AF_INET, + rip->vrf->vrf_id)) { + struct route_node *rn; + struct rip_info *rinfo; + + rn = route_node_match_ipv4(rip->table, + &rte->nexthop); + + if (rn) { + rinfo = rn->info; + + if (rinfo->type == ZEBRA_ROUTE_RIP + && rinfo->sub_type + == RIP_ROUTE_RTE) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "Next hop %pI4 is on RIP network. Set nexthop to the packet's originator", + &rte->nexthop); + rte->nexthop = rinfo->from; + } else { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "Next hop %pI4 is not directly reachable. Treat it as 0.0.0.0", + &rte->nexthop); + rte->nexthop.s_addr = + INADDR_ANY; + } + + route_unlock_node(rn); + } else { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "Next hop %pI4 is not directly reachable. Treat it as 0.0.0.0", + &rte->nexthop); + rte->nexthop.s_addr = INADDR_ANY; + } + } + } + + /* For RIPv1, there won't be a valid netmask. + * This is a best guess at the masks. If everyone was using old + * Ciscos before the 'ip subnet zero' option, it would be almost + * right too :-) + * + * Cisco summarize ripv1 advertisements to the classful boundary + * (/16 for class B's) except when the RIP packet does to inside + * the classful network in question. + */ + if ((packet->version == RIPv1 + && rte->prefix.s_addr != INADDR_ANY) + || (packet->version == RIPv2 + && (rte->prefix.s_addr != INADDR_ANY + && rte->mask.s_addr == INADDR_ANY))) { + uint32_t destination; + + if (subnetted == -1) { + memcpy(&ifaddr, ifc->address, sizeof(ifaddr)); + memcpy(&ifaddrclass, &ifaddr, + sizeof(ifaddrclass)); + apply_classful_mask_ipv4(&ifaddrclass); + subnetted = 0; + if (ifaddr.prefixlen > ifaddrclass.prefixlen) + subnetted = 1; + } + + destination = ntohl(rte->prefix.s_addr); + + if (IN_CLASSA(destination)) + masklen2ip(8, &rte->mask); + else if (IN_CLASSB(destination)) + masklen2ip(16, &rte->mask); + else if (IN_CLASSC(destination)) + masklen2ip(24, &rte->mask); + + if (subnetted == 1) + masklen2ip(ifaddrclass.prefixlen, + (struct in_addr *)&destination); + if ((subnetted == 1) + && ((rte->prefix.s_addr & destination) + == ifaddrclass.prefix.s_addr)) { + masklen2ip(ifaddr.prefixlen, &rte->mask); + if ((rte->prefix.s_addr & rte->mask.s_addr) + != rte->prefix.s_addr) + masklen2ip(32, &rte->mask); + if (IS_RIP_DEBUG_EVENT) + zlog_debug("Subnetted route %pI4", + &rte->prefix); + } else { + if ((rte->prefix.s_addr & rte->mask.s_addr) + != rte->prefix.s_addr) + continue; + } + + if (IS_RIP_DEBUG_EVENT) { + zlog_debug("Resultant route %pI4", + &rte->prefix); + zlog_debug("Resultant mask %pI4", + &rte->mask); + } + } + + /* In case of RIPv2, if prefix in RTE is not netmask applied one + ignore the entry. */ + if ((packet->version == RIPv2) + && (rte->mask.s_addr != INADDR_ANY) + && ((rte->prefix.s_addr & rte->mask.s_addr) + != rte->prefix.s_addr)) { + zlog_warn( + "RIPv2 address %pI4 is not mask /%d applied one", + &rte->prefix, ip_masklen(rte->mask)); + rip_peer_bad_route(rip, ri, from); + continue; + } + + /* Default route's netmask is ignored. */ + if (packet->version == RIPv2 + && (rte->prefix.s_addr == INADDR_ANY) + && (rte->mask.s_addr != INADDR_ANY)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "Default route with non-zero netmask. Set zero to netmask"); + rte->mask.s_addr = INADDR_ANY; + } + + /* Routing table updates. */ + rip_rte_process(rte, from, ifc->ifp); + } +} + +/* Make socket for RIP protocol. */ +int rip_create_socket(struct vrf *vrf) +{ + int ret; + int sock; + struct sockaddr_in addr; + const char *vrf_dev = NULL; + + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + addr.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + /* sending port must always be the RIP port */ + addr.sin_port = htons(RIP_PORT_DEFAULT); + + /* Make datagram socket. */ + if (vrf->vrf_id != VRF_DEFAULT) + vrf_dev = vrf->name; + frr_with_privs(&ripd_privs) { + sock = vrf_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, vrf->vrf_id, + vrf_dev); + if (sock < 0) { + flog_err_sys(EC_LIB_SOCKET, + "Cannot create UDP socket: %s", + safe_strerror(errno)); + return -1; + } + } + + sockopt_broadcast(sock); + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + setsockopt_ipv4_multicast_loop(sock, 0); +#ifdef IPTOS_PREC_INTERNETCONTROL + setsockopt_ipv4_tos(sock, IPTOS_PREC_INTERNETCONTROL); +#endif + setsockopt_so_recvbuf(sock, RIP_UDP_RCV_BUF); + + frr_with_privs(&ripd_privs) { + if ((ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr))) + < 0) { + zlog_err("%s: Can't bind socket %d to %pI4 port %d: %s", + __func__, sock, &addr.sin_addr, + (int)ntohs(addr.sin_port), + safe_strerror(errno)); + + close(sock); + return ret; + } + } + + return sock; +} + +/* RIP packet send to destination address, on interface denoted by + * by connected argument. NULL to argument denotes destination should be + * should be RIP multicast group + */ +static int rip_send_packet(uint8_t *buf, int size, struct sockaddr_in *to, + struct connected *ifc) +{ + struct rip_interface *ri; + struct rip *rip; + int ret; + struct sockaddr_in sin; + struct msghdr msg; + struct iovec iov; +#ifdef GNU_LINUX + struct cmsghdr *cmsgptr; + char adata[256] = {}; + struct in_pktinfo *pkt; +#endif /* GNU_LINUX */ + + assert(ifc != NULL); + ri = ifc->ifp->info; + rip = ri->rip; + + if (IS_RIP_DEBUG_PACKET) { +#define ADDRESS_SIZE 20 + char dst[ADDRESS_SIZE]; + + if (to) { + inet_ntop(AF_INET, &to->sin_addr, dst, sizeof(dst)); + } else { + sin.sin_addr.s_addr = htonl(INADDR_RIP_GROUP); + inet_ntop(AF_INET, &sin.sin_addr, dst, sizeof(dst)); + } +#undef ADDRESS_SIZE + zlog_debug("%s %pI4 > %s (%s)", __func__, + &ifc->address->u.prefix4, dst, ifc->ifp->name); + } + + if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) { + /* + * ZEBRA_IFA_SECONDARY is set on linux when an interface is + * configured with multiple addresses on the same + * subnet: the first address on the subnet is configured + * "primary", and all subsequent addresses on that subnet + * are treated as "secondary" addresses. In order to avoid + * routing-table bloat on other rip listeners, we do not send + * out RIP packets with ZEBRA_IFA_SECONDARY source addrs. + * XXX Since Linux is the only system for which the + * ZEBRA_IFA_SECONDARY flag is set, we would end up + * sending a packet for a "secondary" source address on + * non-linux systems. + */ + if (IS_RIP_DEBUG_PACKET) + zlog_debug("duplicate dropped"); + return 0; + } + + /* Make destination address. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin.sin_len = sizeof(struct sockaddr_in); +#endif /* HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + + /* When destination is specified, use it's port and address. */ + if (to) { + sin.sin_port = to->sin_port; + sin.sin_addr = to->sin_addr; + } else { + sin.sin_port = htons(RIP_PORT_DEFAULT); + sin.sin_addr.s_addr = htonl(INADDR_RIP_GROUP); + + rip_interface_multicast_set(rip->sock, ifc); + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)&sin; + msg.msg_namelen = sizeof(struct sockaddr_in); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + iov.iov_base = buf; + iov.iov_len = size; + +#ifdef GNU_LINUX + msg.msg_control = (void *)adata; + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); + + cmsgptr = (struct cmsghdr *)adata; + cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmsgptr->cmsg_level = IPPROTO_IP; + cmsgptr->cmsg_type = IP_PKTINFO; + pkt = (struct in_pktinfo *)CMSG_DATA(cmsgptr); + pkt->ipi_ifindex = ifc->ifp->ifindex; + pkt->ipi_spec_dst.s_addr = ifc->address->u.prefix4.s_addr; +#endif /* GNU_LINUX */ + + ret = sendmsg(rip->sock, &msg, 0); + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("SEND to %pI4 port %d", &sin.sin_addr, + ntohs(sin.sin_port)); + + if (ret < 0) + zlog_warn("can't send packet : %s", safe_strerror(errno)); + + return ret; +} + +/* Add redistributed route to RIP table. */ +void rip_redistribute_add(struct rip *rip, int type, int sub_type, + struct prefix_ipv4 *p, struct nexthop *nh, + unsigned int metric, unsigned char distance, + route_tag_t tag) +{ + int ret; + struct route_node *rp = NULL; + struct rip_info *rinfo = NULL, newinfo; + struct list *list = NULL; + + /* Redistribute route */ + ret = rip_destination_check(p->prefix); + if (!ret) + return; + + rp = route_node_get(rip->table, (struct prefix *)p); + + memset(&newinfo, 0, sizeof(newinfo)); + newinfo.type = type; + newinfo.sub_type = sub_type; + newinfo.metric = 1; + newinfo.external_metric = metric; + newinfo.distance = distance; + if (tag <= UINT16_MAX) /* RIP only supports 16 bit tags */ + newinfo.tag = tag; + newinfo.rp = rp; + newinfo.nh = *nh; + + if ((list = rp->info) != NULL && listcount(list) != 0) { + rinfo = listgetdata(listhead(list)); + + if (rinfo->type == ZEBRA_ROUTE_CONNECT + && rinfo->sub_type == RIP_ROUTE_INTERFACE + && rinfo->metric != RIP_METRIC_INFINITY) { + route_unlock_node(rp); + return; + } + + /* Manually configured RIP route check. */ + if (rinfo->type == ZEBRA_ROUTE_RIP + && ((rinfo->sub_type == RIP_ROUTE_STATIC) + || (rinfo->sub_type == RIP_ROUTE_DEFAULT))) { + if (type != ZEBRA_ROUTE_RIP + || ((sub_type != RIP_ROUTE_STATIC) + && (sub_type != RIP_ROUTE_DEFAULT))) { + route_unlock_node(rp); + return; + } + } + + (void)rip_ecmp_replace(rip, &newinfo); + route_unlock_node(rp); + } else + (void)rip_ecmp_add(rip, &newinfo); + + if (IS_RIP_DEBUG_EVENT) { + zlog_debug("Redistribute new prefix %pFX", p); + } + + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); +} + +/* Delete redistributed route from RIP table. */ +void rip_redistribute_delete(struct rip *rip, int type, int sub_type, + struct prefix_ipv4 *p, ifindex_t ifindex) +{ + int ret; + struct route_node *rp; + struct rip_info *rinfo; + + ret = rip_destination_check(p->prefix); + if (!ret) + return; + + rp = route_node_lookup(rip->table, (struct prefix *)p); + if (rp) { + struct list *list = rp->info; + + if (list != NULL && listcount(list) != 0) { + rinfo = listgetdata(listhead(list)); + if (rinfo != NULL && rinfo->type == type + && rinfo->sub_type == sub_type + && rinfo->nh.ifindex == ifindex) { + /* Perform poisoned reverse. */ + rinfo->metric = RIP_METRIC_INFINITY; + RIP_TIMER_ON(rinfo->t_garbage_collect, + rip_garbage_collect, + rip->garbage_time); + EVENT_OFF(rinfo->t_timeout); + rinfo->flags |= RIP_RTF_CHANGED; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "Poison %pFX on the interface %s with an infinity metric [delete]", + p, + ifindex2ifname( + ifindex, + rip->vrf->vrf_id)); + + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + } + } + route_unlock_node(rp); + } +} + +/* Response to request called from rip_read ().*/ +static void rip_request_process(struct rip_packet *packet, int size, + struct sockaddr_in *from, struct connected *ifc) +{ + struct rip *rip; + caddr_t lim; + struct rte *rte; + struct prefix_ipv4 p; + struct route_node *rp; + struct rip_info *rinfo; + struct rip_interface *ri; + + /* Does not reponse to the requests on the loopback interfaces */ + if (if_is_loopback(ifc->ifp)) + return; + + /* Check RIP process is enabled on this interface. */ + ri = ifc->ifp->info; + if (!ri->running) + return; + rip = ri->rip; + + /* When passive interface is specified, suppress responses */ + if (ri->passive) + return; + + /* RIP peer update. */ + rip_peer_update(rip, ri, from, packet->version); + + lim = ((caddr_t)packet) + size; + rte = packet->rte; + + /* The Request is processed entry by entry. If there are no + entries, no response is given. */ + if (lim == (caddr_t)rte) + return; + + /* There is one special case. If there is exactly one entry in the + request, and it has an address family identifier of zero and a + metric of infinity (i.e., 16), then this is a request to send the + entire routing table. */ + if (lim == ((caddr_t)(rte + 1)) && ntohs(rte->family) == 0 + && ntohl(rte->metric) == RIP_METRIC_INFINITY) { + /* All route with split horizon */ + rip_output_process(ifc, from, rip_all_route, packet->version); + } else { + if (ntohs(rte->family) != AF_INET) + return; + + /* Examine the list of RTEs in the Request one by one. For each + entry, look up the destination in the router's routing + database and, if there is a route, put that route's metric in + the metric field of the RTE. If there is no explicit route + to the specified destination, put infinity in the metric + field. Once all the entries have been filled in, change the + command from Request to Response and send the datagram back + to the requestor. */ + p.family = AF_INET; + + for (; ((caddr_t)rte) < lim; rte++) { + p.prefix = rte->prefix; + p.prefixlen = ip_masklen(rte->mask); + apply_mask_ipv4(&p); + + rp = route_node_lookup(rip->table, (struct prefix *)&p); + if (rp) { + rinfo = listgetdata( + listhead((struct list *)rp->info)); + rte->metric = htonl(rinfo->metric); + route_unlock_node(rp); + } else + rte->metric = htonl(RIP_METRIC_INFINITY); + } + packet->command = RIP_RESPONSE; + + (void)rip_send_packet((uint8_t *)packet, size, from, ifc); + } + rip->counters.queries++; +} + +/* First entry point of RIP packet. */ +static void rip_read(struct event *t) +{ + struct rip *rip = EVENT_ARG(t); + int sock; + int ret; + int rtenum; + union rip_buf rip_buf; + struct rip_packet *packet; + struct sockaddr_in from; + int len; + int vrecv; + socklen_t fromlen; + struct interface *ifp = NULL; + struct connected *ifc; + struct rip_interface *ri = NULL; + struct prefix p; + + /* Fetch socket then register myself. */ + sock = EVENT_FD(t); + + /* Add myself to tne next event */ + rip_event(rip, RIP_READ, sock); + + /* RIPd manages only IPv4. */ + memset(&from, 0, sizeof(from)); + fromlen = sizeof(struct sockaddr_in); + + len = recvfrom(sock, (char *)&rip_buf.buf, sizeof(rip_buf.buf), 0, + (struct sockaddr *)&from, &fromlen); + if (len < 0) { + zlog_info("recvfrom failed (VRF %s): %s", rip->vrf_name, + safe_strerror(errno)); + return; + } + + /* Check is this packet comming from myself? */ + if (if_check_address(rip, from.sin_addr)) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("ignore packet comes from myself (VRF %s)", + rip->vrf_name); + return; + } + + /* Which interface is this packet comes from. */ + ifc = if_lookup_address((void *)&from.sin_addr, AF_INET, + rip->vrf->vrf_id); + if (ifc) { + ifp = ifc->ifp; + ri = ifp->info; + } + + /* RIP packet received */ + if (IS_RIP_DEBUG_EVENT) + zlog_debug("RECV packet from %pI4 port %d on %s (VRF %s)", + &from.sin_addr, ntohs(from.sin_port), + ifp ? ifp->name : "unknown", rip->vrf_name); + + /* If this packet come from unknown interface, ignore it. */ + if (ifp == NULL || ri == NULL) { + zlog_info( + "%s: cannot find interface for packet from %pI4 port %d (VRF %s)", + __func__, &from.sin_addr, ntohs(from.sin_port), + rip->vrf_name); + return; + } + + p.family = AF_INET; + p.u.prefix4 = from.sin_addr; + p.prefixlen = IPV4_MAX_BITLEN; + + ifc = connected_lookup_prefix(ifp, &p); + + if (ifc == NULL) { + zlog_info( + "%s: cannot find connected address for packet from %pI4 port %d on interface %s (VRF %s)", + __func__, &from.sin_addr, ntohs(from.sin_port), + ifp->name, rip->vrf_name); + return; + } + + /* Packet length check. */ + if (len < RIP_PACKET_MINSIZ) { + zlog_warn("packet size %d is smaller than minimum size %d", len, + RIP_PACKET_MINSIZ); + rip_peer_bad_packet(rip, ri, &from); + return; + } + if (len > RIP_PACKET_MAXSIZ) { + zlog_warn("packet size %d is larger than max size %d", len, + RIP_PACKET_MAXSIZ); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* Packet alignment check. */ + if ((len - RIP_PACKET_MINSIZ) % 20) { + zlog_warn("packet size %d is wrong for RIP packet alignment", + len); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* Set RTE number. */ + rtenum = ((len - RIP_PACKET_MINSIZ) / 20); + + /* For easy to handle. */ + packet = &rip_buf.rip_packet; + + /* RIP version check. */ + if (packet->version == 0) { + zlog_info("version 0 with command %d received.", + packet->command); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* Dump RIP packet. */ + if (IS_RIP_DEBUG_RECV) + rip_packet_dump(packet, len, "RECV"); + + /* RIP version adjust. This code should rethink now. RFC1058 says + that "Version 1 implementations are to ignore this extra data and + process only the fields specified in this document.". So RIPv3 + packet should be treated as RIPv1 ignoring must be zero field. */ + if (packet->version > RIPv2) + packet->version = RIPv2; + + /* Is RIP running or is this RIP neighbor ?*/ + if (!ri->running && !rip_neighbor_lookup(rip, &from)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("RIP is not enabled on interface %s.", + ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* RIP Version check. RFC2453, 4.6 and 5.1 */ + vrecv = ((ri->ri_receive == RI_RIP_UNSPEC) ? rip->version_recv + : ri->ri_receive); + if (vrecv == RI_RIP_VERSION_NONE + || ((packet->version == RIPv1) && !(vrecv & RIPv1)) + || ((packet->version == RIPv2) && !(vrecv & RIPv2))) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + " packet's v%d doesn't fit to if version spec", + packet->version); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* RFC2453 5.2 If the router is not configured to authenticate RIP-2 + messages, then RIP-1 and unauthenticated RIP-2 messages will be + accepted; authenticated RIP-2 messages shall be discarded. */ + if ((ri->auth_type == RIP_NO_AUTH) && rtenum + && (packet->version == RIPv2) + && (packet->rte->family == htons(RIP_FAMILY_AUTH))) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug( + "packet RIPv%d is dropped because authentication disabled", + packet->version); + ripd_notif_send_auth_type_failure(ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* RFC: + If the router is configured to authenticate RIP-2 messages, then + RIP-1 messages and RIP-2 messages which pass authentication + testing shall be accepted; unauthenticated and failed + authentication RIP-2 messages shall be discarded. For maximum + security, RIP-1 messages should be ignored when authentication is + in use (see section 4.1); otherwise, the routing information from + authenticated messages will be propagated by RIP-1 routers in an + unauthenticated manner. + */ + /* We make an exception for RIPv1 REQUEST packets, to which we'll + * always reply regardless of authentication settings, because: + * + * - if there other authorised routers on-link, the REQUESTor can + * passively obtain the routing updates anyway + * - if there are no other authorised routers on-link, RIP can + * easily be disabled for the link to prevent giving out information + * on state of this routers RIP routing table.. + * + * I.e. if RIPv1 has any place anymore these days, it's as a very + * simple way to distribute routing information (e.g. to embedded + * hosts / appliances) and the ability to give out RIPv1 + * routing-information freely, while still requiring RIPv2 + * authentication for any RESPONSEs might be vaguely useful. + */ + if (ri->auth_type != RIP_NO_AUTH && packet->version == RIPv1) { + /* Discard RIPv1 messages other than REQUESTs */ + if (packet->command != RIP_REQUEST) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv1 dropped because authentication enabled"); + ripd_notif_send_auth_type_failure(ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + } else if (ri->auth_type != RIP_NO_AUTH) { + const char *auth_desc; + + if (rtenum == 0) { + /* There definitely is no authentication in the packet. + */ + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv2 authentication failed: no auth RTE in packet"); + ripd_notif_send_auth_type_failure(ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* First RTE must be an Authentication Family RTE */ + if (packet->rte->family != htons(RIP_FAMILY_AUTH)) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv2 dropped because authentication enabled"); + ripd_notif_send_auth_type_failure(ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + + /* Check RIPv2 authentication. */ + switch (ntohs(packet->rte->tag)) { + case RIP_AUTH_SIMPLE_PASSWORD: + auth_desc = "simple"; + ret = rip_auth_simple_password(packet->rte, &from, ifp); + break; + + case RIP_AUTH_MD5: + auth_desc = "MD5"; + ret = rip_auth_md5(packet, &from, len, ifp); + /* Reset RIP packet length to trim MD5 data. */ + len = ret; + break; + + default: + ret = 0; + auth_desc = "unknown type"; + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv2 Unknown authentication type %d", + ntohs(packet->rte->tag)); + } + + if (ret) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("RIPv2 %s authentication success", + auth_desc); + } else { + if (IS_RIP_DEBUG_PACKET) + zlog_debug("RIPv2 %s authentication failure", + auth_desc); + ripd_notif_send_auth_failure(ifp->name); + rip_peer_bad_packet(rip, ri, &from); + return; + } + } + + /* Process each command. */ + switch (packet->command) { + case RIP_RESPONSE: + rip_response_process(packet, len, &from, ifc); + break; + case RIP_REQUEST: + case RIP_POLL: + rip_request_process(packet, len, &from, ifc); + break; + case RIP_TRACEON: + case RIP_TRACEOFF: + zlog_info( + "Obsolete command %s received, please sent it to routed", + lookup_msg(rip_msg, packet->command, NULL)); + rip_peer_bad_packet(rip, ri, &from); + break; + case RIP_POLL_ENTRY: + zlog_info("Obsolete command %s received", + lookup_msg(rip_msg, packet->command, NULL)); + rip_peer_bad_packet(rip, ri, &from); + break; + default: + zlog_info("Unknown RIP command %d received", packet->command); + rip_peer_bad_packet(rip, ri, &from); + break; + } +} + +/* Write routing table entry to the stream and return next index of + the routing table entry in the stream. */ +static int rip_write_rte(int num, struct stream *s, struct prefix_ipv4 *p, + uint8_t version, struct rip_info *rinfo) +{ + struct in_addr mask; + + /* Write routing table entry. */ + if (version == RIPv1) { + stream_putw(s, AF_INET); + stream_putw(s, 0); + stream_put_ipv4(s, p->prefix.s_addr); + stream_put_ipv4(s, 0); + stream_put_ipv4(s, 0); + stream_putl(s, rinfo->metric_out); + } else { + masklen2ip(p->prefixlen, &mask); + + stream_putw(s, AF_INET); + stream_putw(s, rinfo->tag_out); + stream_put_ipv4(s, p->prefix.s_addr); + stream_put_ipv4(s, mask.s_addr); + stream_put_ipv4(s, rinfo->nexthop_out.s_addr); + stream_putl(s, rinfo->metric_out); + } + + return ++num; +} + +/* Send update to the ifp or spcified neighbor. */ +void rip_output_process(struct connected *ifc, struct sockaddr_in *to, + int route_type, uint8_t version) +{ + struct rip *rip; + int ret; + struct stream *s; + struct route_node *rp; + struct rip_info *rinfo; + struct rip_interface *ri; + struct prefix_ipv4 *p; + struct prefix_ipv4 classfull; + struct prefix_ipv4 ifaddrclass; + struct key *key = NULL; + /* this might need to made dynamic if RIP ever supported auth methods + with larger key string sizes */ + char auth_str[RIP_AUTH_SIMPLE_SIZE]; + size_t doff = 0; /* offset of digest offset field */ + int num = 0; + int rtemax; + int subnetted = 0; + struct list *list = NULL; + struct listnode *listnode = NULL; + + /* Logging output event. */ + if (IS_RIP_DEBUG_EVENT) { + if (to) + zlog_debug("update routes to neighbor %pI4", + &to->sin_addr); + else + zlog_debug("update routes on interface %s ifindex %d", + ifc->ifp->name, ifc->ifp->ifindex); + } + + /* Get RIP interface. */ + ri = ifc->ifp->info; + rip = ri->rip; + + /* Set output stream. */ + s = rip->obuf; + + /* Reset stream and RTE counter. */ + stream_reset(s); + rtemax = RIP_MAX_RTE; + + /* If output interface is in simple password authentication mode, we + need space for authentication data. */ + if (ri->auth_type == RIP_AUTH_SIMPLE_PASSWORD) + rtemax -= 1; + + /* If output interface is in MD5 authentication mode, we need space + for authentication header and data. */ + if (ri->auth_type == RIP_AUTH_MD5) + rtemax -= 2; + + /* If output interface is in simple password authentication mode + and string or keychain is specified we need space for auth. data */ + if (ri->auth_type != RIP_NO_AUTH) { + if (ri->key_chain) { + struct keychain *keychain; + + keychain = keychain_lookup(ri->key_chain); + if (keychain) + key = key_lookup_for_send(keychain); + } + /* to be passed to auth functions later */ + rip_auth_prepare_str_send(ri, key, auth_str, sizeof(auth_str)); + if (strlen(auth_str) == 0) + return; + } + + if (version == RIPv1) { + memcpy(&ifaddrclass, ifc->address, sizeof(ifaddrclass)); + apply_classful_mask_ipv4(&ifaddrclass); + subnetted = 0; + if (ifc->address->prefixlen > ifaddrclass.prefixlen) + subnetted = 1; + } + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + + if (list == NULL) + continue; + + if (listcount(list) == 0) + continue; + + rinfo = listgetdata(listhead(list)); + /* + * For RIPv1, if we are subnetted, output subnets in our + * network that have the same mask as the output "interface". + * For other networks, only the classfull version is output. + */ + if (version == RIPv1) { + p = (struct prefix_ipv4 *)&rp->p; + + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv1 mask check, %pFX considered for output", + &rp->p); + + if (subnetted && + prefix_match((struct prefix *)&ifaddrclass, + &rp->p)) { + if ((ifc->address->prefixlen != + rp->p.prefixlen) && + (rp->p.prefixlen != IPV4_MAX_BITLEN)) + continue; + } else { + memcpy(&classfull, &rp->p, + sizeof(struct prefix_ipv4)); + apply_classful_mask_ipv4(&classfull); + if (rp->p.u.prefix4.s_addr != INADDR_ANY && + classfull.prefixlen != rp->p.prefixlen) + continue; + } + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIPv1 mask check, %pFX made it through", + &rp->p); + } else + p = (struct prefix_ipv4 *)&rp->p; + + /* Apply output filters. */ + ret = rip_filter(RIP_FILTER_OUT, p, ri); + if (ret < 0) + continue; + + /* Changed route only output. */ + if (route_type == rip_changed_route && + (!(rinfo->flags & RIP_RTF_CHANGED))) + continue; + + /* Split horizon. */ + if (ri->split_horizon == RIP_SPLIT_HORIZON) { + /* + * We perform split horizon for RIP and connected + * route. For rip routes, we want to suppress the + * route if we would end up sending the route back on + * the interface that we learned it from, with a + * higher metric. For connected routes, we suppress + * the route if the prefix is a subset of the source + * address that we are going to use for the packet + * (in order to handle the case when multiple subnets + * are configured on the same interface). + */ + int suppress = 0; + struct rip_info *tmp_rinfo = NULL; + struct connected *tmp_ifc = NULL; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) + if (tmp_rinfo->type == ZEBRA_ROUTE_RIP && + tmp_rinfo->nh.ifindex == + ifc->ifp->ifindex) { + suppress = 1; + break; + } + + if (!suppress && rinfo->type == ZEBRA_ROUTE_CONNECT) { + frr_each (if_connected, ifc->ifp->connected, + tmp_ifc) + if (prefix_match((struct prefix *)p, + tmp_ifc->address)) { + suppress = 1; + break; + } + } + + if (suppress) + continue; + } + + /* Preparation for route-map. */ + rinfo->metric_set = 0; + rinfo->nexthop_out.s_addr = 0; + rinfo->metric_out = rinfo->metric; + rinfo->tag_out = rinfo->tag; + rinfo->ifindex_out = ifc->ifp->ifindex; + + /* In order to avoid some local loops, if the RIP route has + * a nexthop via this interface, keep the nexthop, otherwise + * set it to 0. The nexthop should not be propagated beyond + * the local broadcast/multicast area in order to avoid an + * IGP multi-level recursive look-up. see (4.4) + */ + if (rinfo->nh.ifindex == ifc->ifp->ifindex) + rinfo->nexthop_out = rinfo->nh.gate.ipv4; + + /* Interface route-map */ + if (ri->routemap[RIP_FILTER_OUT]) { + ret = route_map_apply(ri->routemap[RIP_FILTER_OUT], + (struct prefix *)p, rinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "RIP %pFX is filtered by route-map out", + p); + continue; + } + } + + /* Apply redistribute route map - continue, if deny */ + if (rip->redist[rinfo->type].route_map.name && + rinfo->sub_type != RIP_ROUTE_INTERFACE) { + ret = route_map_apply( + rip->redist[rinfo->type].route_map.map, + (struct prefix *)p, rinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIP_DEBUG_PACKET) + zlog_debug( + "%pFX is filtered by route-map", + p); + continue; + } + } + + /* When route-map does not set metric. */ + if (!rinfo->metric_set) { + /* If redistribute metric is set. */ + if (rip->redist[rinfo->type].metric_config && + rinfo->metric != RIP_METRIC_INFINITY) { + rinfo->metric_out = + rip->redist[rinfo->type].metric; + } else { + /* If the route is not connected or localy + * generated one, use default-metric value + */ + if (rinfo->type != ZEBRA_ROUTE_RIP && + rinfo->type != ZEBRA_ROUTE_CONNECT && + rinfo->metric != RIP_METRIC_INFINITY) + rinfo->metric_out = rip->default_metric; + } + } + + /* Apply offset-list */ + if (rinfo->metric != RIP_METRIC_INFINITY) + rip_offset_list_apply_out(p, ifc->ifp, + &rinfo->metric_out); + + if (rinfo->metric_out > RIP_METRIC_INFINITY) + rinfo->metric_out = RIP_METRIC_INFINITY; + + /* Perform split-horizon with poisoned reverse + * for RIP and connected routes. + **/ + if (ri->split_horizon == RIP_SPLIT_HORIZON_POISONED_REVERSE) { + /* + * We perform split horizon for RIP and connected + * route. For rip routes, we want to suppress the + * route if we would end up sending the route back + * on the interface that we learned it from, with a + * higher metric. For connected routes, we suppress + * the route if the prefix is a subset of the source + * address that we are going to use for the packet + * (in order to handle the case when multiple + * subnets are configured on the same interface). + */ + struct rip_info *tmp_rinfo = NULL; + struct connected *tmp_ifc = NULL; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, tmp_rinfo)) + if (tmp_rinfo->type == ZEBRA_ROUTE_RIP && + tmp_rinfo->nh.ifindex == ifc->ifp->ifindex) + rinfo->metric_out = RIP_METRIC_INFINITY; + + if (rinfo->metric_out != RIP_METRIC_INFINITY && + rinfo->type == ZEBRA_ROUTE_CONNECT) { + frr_each (if_connected, ifc->ifp->connected, + tmp_ifc) + if (prefix_match((struct prefix *)p, + tmp_ifc->address)) { + rinfo->metric_out = + RIP_METRIC_INFINITY; + break; + } + } + } + + /* Prepare preamble, auth headers, if needs be */ + if (num == 0) { + stream_putc(s, RIP_RESPONSE); + stream_putc(s, version); + stream_putw(s, 0); + + /* auth header for !v1 && !no_auth */ + if ((ri->auth_type != RIP_NO_AUTH) && + (version != RIPv1)) + doff = rip_auth_header_write( + s, ri, key, auth_str, + RIP_AUTH_SIMPLE_SIZE); + } + + /* Write RTE to the stream. */ + num = rip_write_rte(num, s, p, version, rinfo); + if (num == rtemax) { + if (version == RIPv2 && ri->auth_type == RIP_AUTH_MD5) + rip_auth_md5_set(s, ri, doff, auth_str, + RIP_AUTH_SIMPLE_SIZE); + + ret = rip_send_packet(STREAM_DATA(s), + stream_get_endp(s), to, ifc); + + if (ret >= 0 && IS_RIP_DEBUG_SEND) + rip_packet_dump( + (struct rip_packet *)STREAM_DATA(s), + stream_get_endp(s), "SEND"); + num = 0; + stream_reset(s); + } + } + + /* Flush unwritten RTE. */ + if (num != 0) { + if (version == RIPv2 && ri->auth_type == RIP_AUTH_MD5) + rip_auth_md5_set(s, ri, doff, auth_str, + RIP_AUTH_SIMPLE_SIZE); + + ret = rip_send_packet(STREAM_DATA(s), stream_get_endp(s), to, + ifc); + + if (ret >= 0 && IS_RIP_DEBUG_SEND) + rip_packet_dump((struct rip_packet *)STREAM_DATA(s), + stream_get_endp(s), "SEND"); + stream_reset(s); + } + + /* Statistics updates. */ + ri->sent_updates++; +} + +/* Send RIP packet to the interface. */ +static void rip_update_interface(struct connected *ifc, uint8_t version, + int route_type) +{ + struct interface *ifp = ifc->ifp; + struct rip_interface *ri = ifp->info; + struct sockaddr_in to; + + /* When RIP version is 2 and multicast enable interface. */ + if (version == RIPv2 && !ri->v2_broadcast && if_is_multicast(ifp)) { + if (IS_RIP_DEBUG_EVENT) + zlog_debug("multicast announce on %s ", ifp->name); + + rip_output_process(ifc, NULL, route_type, version); + return; + } + + /* If we can't send multicast packet, send it with unicast. */ + if (if_is_broadcast(ifp) || if_is_pointopoint(ifp)) { + if (ifc->address->family == AF_INET) { + /* Destination address and port setting. */ + memset(&to, 0, sizeof(to)); + if (ifc->destination) + /* use specified broadcast or peer destination + * addr */ + to.sin_addr = ifc->destination->u.prefix4; + else if (ifc->address->prefixlen < IPV4_MAX_BITLEN) + /* calculate the appropriate broadcast address + */ + to.sin_addr.s_addr = ipv4_broadcast_addr( + ifc->address->u.prefix4.s_addr, + ifc->address->prefixlen); + else + /* do not know where to send the packet */ + return; + to.sin_port = htons(RIP_PORT_DEFAULT); + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s announce to %pI4 on %s", + CONNECTED_PEER(ifc) ? "unicast" + : "broadcast", + &to.sin_addr, ifp->name); + + rip_output_process(ifc, &to, route_type, version); + } + } +} + +/* Update send to all interface and neighbor. */ +static void rip_update_process(struct rip *rip, int route_type) +{ + struct connected *connected; + struct interface *ifp; + struct rip_interface *ri; + struct route_node *rp; + struct sockaddr_in to; + struct prefix *p; + + /* Send RIP update to each interface. */ + FOR_ALL_INTERFACES (rip->vrf, ifp) { + if (if_is_loopback(ifp)) + continue; + + if (!if_is_operative(ifp)) + continue; + + /* Fetch RIP interface information. */ + ri = ifp->info; + + /* When passive interface is specified, suppress announce to the + interface. */ + if (ri->passive) + continue; + + if (!ri->running) + continue; + + /* + * If there is no version configuration in the + * interface, use rip's version setting. + */ + int vsend = ((ri->ri_send == RI_RIP_UNSPEC) ? rip->version_send + : ri->ri_send); + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("SEND UPDATE to %s ifindex %d", ifp->name, + ifp->ifindex); + + /* send update on each connected network */ + frr_each (if_connected, ifp->connected, connected) { + if (connected->address->family == AF_INET) { + if (vsend & RIPv1) + rip_update_interface(connected, RIPv1, + route_type); + if ((vsend & RIPv2) && if_is_multicast(ifp)) + rip_update_interface(connected, RIPv2, + route_type); + } + } + } + + /* RIP send updates to each neighbor. */ + for (rp = route_top(rip->neighbor); rp; rp = route_next(rp)) { + if (rp->info == NULL) + continue; + + p = &rp->p; + + connected = if_lookup_address(&p->u.prefix4, AF_INET, + rip->vrf->vrf_id); + if (!connected) { + zlog_warn( + "Neighbor %pI4 doesn't have connected interface!", + &p->u.prefix4); + continue; + } + + /* Set destination address and port */ + memset(&to, 0, sizeof(struct sockaddr_in)); + to.sin_addr = p->u.prefix4; + to.sin_port = htons(RIP_PORT_DEFAULT); + + /* RIP version is rip's configuration. */ + rip_output_process(connected, &to, route_type, + rip->version_send); + } +} + +/* RIP's periodical timer. */ +static void rip_update(struct event *t) +{ + struct rip *rip = EVENT_ARG(t); + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("update timer fire!"); + + /* Process update output. */ + rip_update_process(rip, rip_all_route); + + /* Triggered updates may be suppressed if a regular update is due by + the time the triggered update would be sent. */ + EVENT_OFF(rip->t_triggered_interval); + rip->trigger = 0; + + /* Register myself. */ + rip_event(rip, RIP_UPDATE_EVENT, 0); +} + +/* Walk down the RIP routing table then clear changed flag. */ +static void rip_clear_changed_flag(struct rip *rip) +{ + struct route_node *rp; + struct rip_info *rinfo = NULL; + struct list *list = NULL; + struct listnode *listnode = NULL; + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + + if (list == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + UNSET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + /* This flag can be set only on the first entry. */ + break; + } + } +} + +/* Triggered update interval timer. */ +static void rip_triggered_interval(struct event *t) +{ + struct rip *rip = EVENT_ARG(t); + + if (rip->trigger) { + rip->trigger = 0; + rip_triggered_update(t); + } +} + +/* Execute triggered update. */ +static void rip_triggered_update(struct event *t) +{ + struct rip *rip = EVENT_ARG(t); + int interval; + + /* Cancel interval timer. */ + EVENT_OFF(rip->t_triggered_interval); + rip->trigger = 0; + + /* Logging triggered update. */ + if (IS_RIP_DEBUG_EVENT) + zlog_debug("triggered update!"); + + /* Split Horizon processing is done when generating triggered + updates as well as normal updates (see section 2.6). */ + rip_update_process(rip, rip_changed_route); + + /* Once all of the triggered updates have been generated, the route + change flags should be cleared. */ + rip_clear_changed_flag(rip); + + /* After a triggered update is sent, a timer should be set for a + random interval between 1 and 5 seconds. If other changes that + would trigger updates occur before the timer expires, a single + update is triggered when the timer expires. */ + interval = (frr_weak_random() % 5) + 1; + + event_add_timer(master, rip_triggered_interval, rip, interval, + &rip->t_triggered_interval); +} + +/* Withdraw redistributed route. */ +void rip_redistribute_withdraw(struct rip *rip, int type) +{ + struct route_node *rp; + struct rip_info *rinfo = NULL; + struct list *list = NULL; + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + + if (list == NULL) + continue; + + rinfo = listgetdata(listhead(list)); + + if (rinfo->type != type) + continue; + + if (rinfo->sub_type == RIP_ROUTE_INTERFACE) + continue; + + /* Perform poisoned reverse. */ + rinfo->metric = RIP_METRIC_INFINITY; + RIP_TIMER_ON(rinfo->t_garbage_collect, rip_garbage_collect, + rip->garbage_time); + EVENT_OFF(rinfo->t_timeout); + rinfo->flags |= RIP_RTF_CHANGED; + + if (IS_RIP_DEBUG_EVENT) { + struct prefix_ipv4 *p = (struct prefix_ipv4 *)&rp->p; + + zlog_debug( + "Poisone %pFX on the interface %s with an infinity metric [withdraw]", + p, + ifindex2ifname(rinfo->nh.ifindex, + rip->vrf->vrf_id)); + } + + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + } +} + +struct rip *rip_lookup_by_vrf_id(vrf_id_t vrf_id) +{ + struct vrf *vrf; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + return vrf->info; +} + +struct rip *rip_lookup_by_vrf_name(const char *vrf_name) +{ + struct rip rip; + + rip.vrf_name = (char *)vrf_name; + + return RB_FIND(rip_instance_head, &rip_instances, &rip); +} + +/* Update ECMP routes to zebra when `allow-ecmp` changed. */ +void rip_ecmp_change(struct rip *rip) +{ + struct route_node *rp; + struct rip_info *rinfo; + struct list *list; + struct listnode *node, *nextnode; + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + if (list && listcount(list) > 1) { + while (listcount(list) > rip->ecmp) { + struct rip_info *from_highest = NULL; + + for (ALL_LIST_ELEMENTS(list, node, nextnode, + rinfo)) { + if (!from_highest || + (from_highest && + IPV4_ADDR_CMP( + &rinfo->from, + &from_highest->from) > 0)) + from_highest = rinfo; + } + + rip_ecmp_delete(rip, from_highest); + } + } + } +} + +/* Create new RIP instance and set it to global variable. */ +struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket) +{ + struct rip *rip; + + rip = XCALLOC(MTYPE_RIP, sizeof(struct rip)); + rip->vrf_name = XSTRDUP(MTYPE_RIP_VRF_NAME, vrf_name); + + /* Set initial value. */ + rip->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIP_INSTANCE); + rip->default_metric = + yang_get_default_uint8("%s/default-metric", RIP_INSTANCE); + rip->distance = + yang_get_default_uint8("%s/distance/default", RIP_INSTANCE); + rip->passive_default = + yang_get_default_bool("%s/passive-default", RIP_INSTANCE); + rip->garbage_time = yang_get_default_uint32("%s/timers/flush-interval", + RIP_INSTANCE); + rip->timeout_time = yang_get_default_uint32( + "%s/timers/holddown-interval", RIP_INSTANCE); + rip->update_time = yang_get_default_uint32("%s/timers/update-interval", + RIP_INSTANCE); + rip->version_send = + yang_get_default_enum("%s/version/send", RIP_INSTANCE); + rip->version_recv = + yang_get_default_enum("%s/version/receive", RIP_INSTANCE); + + /* Initialize RIP data structures. */ + rip->table = route_table_init(); + route_table_set_info(rip->table, rip); + rip->neighbor = route_table_init(); + rip->peer_list = list_new(); + rip->peer_list->cmp = (int (*)(void *, void *))rip_peer_list_cmp; + rip->peer_list->del = rip_peer_list_del; + rip->distance_table = route_table_init(); + rip->distance_table->cleanup = rip_distance_table_node_cleanup; + rip->enable_interface = vector_init(1); + rip->enable_network = route_table_init(); + rip->passive_nondefault = vector_init(1); + rip->offset_list_master = list_new(); + rip->offset_list_master->cmp = (int (*)(void *, void *))offset_list_cmp; + rip->offset_list_master->del = (void (*)(void *))offset_list_free; + + /* Distribute list install. */ + rip->distribute_ctx = distribute_list_ctx_create(vrf); + distribute_list_add_hook(rip->distribute_ctx, rip_distribute_update); + distribute_list_delete_hook(rip->distribute_ctx, rip_distribute_update); + + /* if rmap install. */ + rip->if_rmap_ctx = if_rmap_ctx_create(vrf_name); + if_rmap_hook_add(rip->if_rmap_ctx, rip_if_rmap_update); + if_rmap_hook_delete(rip->if_rmap_ctx, rip_if_rmap_update); + + /* Make output stream. */ + rip->obuf = stream_new(1500); + + /* Enable the routing instance if possible. */ + if (vrf && vrf_is_enabled(vrf)) + rip_instance_enable(rip, vrf, socket); + else { + rip->vrf = NULL; + rip->sock = -1; + } + + RB_INSERT(rip_instance_head, &rip_instances, rip); + + return rip; +} + +/* Sned RIP request to the destination. */ +int rip_request_send(struct sockaddr_in *to, struct interface *ifp, + uint8_t version, struct connected *connected) +{ + struct rte *rte; + struct rip_packet rip_packet; + + memset(&rip_packet, 0, sizeof(rip_packet)); + + rip_packet.command = RIP_REQUEST; + rip_packet.version = version; + rte = rip_packet.rte; + rte->metric = htonl(RIP_METRIC_INFINITY); + + if (connected) { + /* + * connected is only sent for ripv1 case, or when + * interface does not support multicast. Caller loops + * over each connected address for this case. + */ + if (rip_send_packet((uint8_t *)&rip_packet, sizeof(rip_packet), + to, connected) + != sizeof(rip_packet)) + return -1; + else + return sizeof(rip_packet); + } + + /* send request on each connected network */ + frr_each (if_connected, ifp->connected, connected) { + struct prefix_ipv4 *p; + + p = (struct prefix_ipv4 *)connected->address; + + if (p->family != AF_INET) + continue; + + if (rip_send_packet((uint8_t *)&rip_packet, sizeof(rip_packet), + to, connected) + != sizeof(rip_packet)) + return -1; + } + return sizeof(rip_packet); +} + +static int rip_update_jitter(unsigned long time) +{ +#define JITTER_BOUND 4 + /* We want to get the jitter to +/- 1/JITTER_BOUND the interval. + Given that, we cannot let time be less than JITTER_BOUND seconds. + The RIPv2 RFC says jitter should be small compared to + update_time. We consider 1/JITTER_BOUND to be small. + */ + + int jitter_input = time; + int jitter; + + if (jitter_input < JITTER_BOUND) + jitter_input = JITTER_BOUND; + + jitter = (((frr_weak_random() % ((jitter_input * 2) + 1)) + - jitter_input)); + + return jitter / JITTER_BOUND; +} + +void rip_event(struct rip *rip, enum rip_event event, int sock) +{ + int jitter = 0; + + switch (event) { + case RIP_READ: + event_add_read(master, rip_read, rip, sock, &rip->t_read); + break; + case RIP_UPDATE_EVENT: + EVENT_OFF(rip->t_update); + jitter = rip_update_jitter(rip->update_time); + event_add_timer(master, rip_update, rip, + sock ? 2 : rip->update_time + jitter, + &rip->t_update); + break; + case RIP_TRIGGERED_UPDATE: + if (rip->t_triggered_interval) + rip->trigger = 1; + else + event_add_event(master, rip_triggered_update, rip, 0, + &rip->t_triggered_update); + break; + default: + break; + } +} + +struct rip_distance *rip_distance_new(void) +{ + return XCALLOC(MTYPE_RIP_DISTANCE, sizeof(struct rip_distance)); +} + +void rip_distance_free(struct rip_distance *rdistance) +{ + if (rdistance->access_list) + free(rdistance->access_list); + XFREE(MTYPE_RIP_DISTANCE, rdistance); +} + +static void rip_distance_table_node_cleanup(struct route_table *table, + struct route_node *node) +{ + struct rip_distance *rdistance; + + rdistance = node->info; + if (rdistance) + rip_distance_free(rdistance); +} + +/* Apply RIP information to distance method. */ +uint8_t rip_distance_apply(struct rip *rip, struct rip_info *rinfo) +{ + struct route_node *rn; + struct prefix_ipv4 p; + struct rip_distance *rdistance; + struct access_list *alist; + + memset(&p, 0, sizeof(p)); + p.family = AF_INET; + p.prefix = rinfo->from; + p.prefixlen = IPV4_MAX_BITLEN; + + /* Check source address. */ + rn = route_node_match(rip->distance_table, (struct prefix *)&p); + if (rn) { + rdistance = rn->info; + route_unlock_node(rn); + + if (rdistance->access_list) { + alist = access_list_lookup(AFI_IP, + rdistance->access_list); + if (alist == NULL) + return 0; + if (access_list_apply(alist, &rinfo->rp->p) + == FILTER_DENY) + return 0; + } + return rdistance->distance; + } + + return rip->distance; +} + +static void rip_distance_show(struct vty *vty, struct rip *rip) +{ + struct route_node *rn; + struct rip_distance *rdistance; + int header = 1; + char buf[BUFSIZ]; + + vty_out(vty, " Distance: (default is %u)\n", + rip->distance ? rip->distance : ZEBRA_RIP_DISTANCE_DEFAULT); + + for (rn = route_top(rip->distance_table); rn; rn = route_next(rn)) { + rdistance = rn->info; + + if (rdistance == NULL) + continue; + + if (header) { + vty_out(vty, " Address Distance List\n"); + header = 0; + } + snprintfrr(buf, sizeof(buf), "%pFX", &rn->p); + vty_out(vty, " %-20s %4d %s\n", buf, rdistance->distance, + rdistance->access_list ? rdistance->access_list : ""); + } +} + +/* Update ECMP routes to zebra when ECMP is disabled. */ +void rip_ecmp_disable(struct rip *rip) +{ + struct route_node *rp; + struct rip_info *rinfo, *tmp_rinfo; + struct list *list; + struct listnode *node, *nextnode; + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + + if (!list) + continue; + if (listcount(list) == 0) + continue; + + rinfo = listgetdata(listhead(list)); + if (!rip_route_rte(rinfo)) + continue; + + /* Drop all other entries, except the first one. */ + for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) { + if (tmp_rinfo == rinfo) + continue; + + EVENT_OFF(tmp_rinfo->t_timeout); + EVENT_OFF(tmp_rinfo->t_garbage_collect); + list_delete_node(list, node); + rip_info_free(tmp_rinfo); + } + + /* Update zebra. */ + rip_zebra_ipv4_add(rip, rp); + + /* Set the route change flag. */ + SET_FLAG(rinfo->flags, RIP_RTF_CHANGED); + + /* Signal the output process to trigger an update. */ + rip_event(rip, RIP_TRIGGERED_UPDATE, 0); + } +} + +/* Print out routes update time. */ +static void rip_vty_out_uptime(struct vty *vty, struct rip_info *rinfo) +{ + time_t clock; + struct tm tm; +#define TIME_BUF 25 + char timebuf[TIME_BUF]; + struct event *thread; + + if ((thread = rinfo->t_timeout) != NULL) { + clock = event_timer_remain_second(thread); + gmtime_r(&clock, &tm); + strftime(timebuf, TIME_BUF, "%M:%S", &tm); + vty_out(vty, "%5s", timebuf); + } else if ((thread = rinfo->t_garbage_collect) != NULL) { + clock = event_timer_remain_second(thread); + gmtime_r(&clock, &tm); + strftime(timebuf, TIME_BUF, "%M:%S", &tm); + vty_out(vty, "%5s", timebuf); + } +} + +static const char *rip_route_type_print(int sub_type) +{ + switch (sub_type) { + case RIP_ROUTE_RTE: + return "n"; + case RIP_ROUTE_STATIC: + return "s"; + case RIP_ROUTE_DEFAULT: + return "d"; + case RIP_ROUTE_REDISTRIBUTE: + return "r"; + case RIP_ROUTE_INTERFACE: + return "i"; + default: + return "?"; + } +} + +DEFUN (show_ip_rip, + show_ip_rip_cmd, + "show ip rip [vrf NAME]", + SHOW_STR + IP_STR + "Show RIP routes\n" + VRF_CMD_HELP_STR) +{ + struct rip *rip; + struct route_node *np; + struct rip_info *rinfo = NULL; + struct list *list = NULL; + struct listnode *listnode = NULL; + const char *vrf_name; + int idx = 0; + + if (argv_find(argv, argc, "vrf", &idx)) + vrf_name = argv[idx + 1]->arg; + else + vrf_name = VRF_DEFAULT_NAME; + + rip = rip_lookup_by_vrf_name(vrf_name); + if (!rip) { + vty_out(vty, "%% RIP instance not found\n"); + return CMD_SUCCESS; + } + if (!rip->enabled) { + vty_out(vty, "%% RIP instance is disabled\n"); + return CMD_SUCCESS; + } + + vty_out(vty, + "Codes: R - RIP, C - connected, S - Static, O - OSPF, B - BGP\n" + "Sub-codes:\n" + " (n) - normal, (s) - static, (d) - default, (r) - redistribute,\n" + " (i) - interface\n\n" + " Network Next Hop Metric From Tag Time\n"); + + for (np = route_top(rip->table); np; np = route_next(np)) { + list = np->info; + + if (!list) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + int len; + + len = vty_out(vty, "%c(%s) %pFX", + /* np->lock, For debugging. */ + zebra_route_char(rinfo->type), + rip_route_type_print(rinfo->sub_type), + &np->p); + + len = 24 - len; + + if (len > 0) + vty_out(vty, "%*s", len, " "); + + switch (rinfo->nh.type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + vty_out(vty, "%-20pI4 %2d ", + &rinfo->nh.gate.ipv4, rinfo->metric); + break; + case NEXTHOP_TYPE_IFINDEX: + vty_out(vty, "0.0.0.0 %2d ", + rinfo->metric); + break; + case NEXTHOP_TYPE_BLACKHOLE: + vty_out(vty, "blackhole %2d ", + rinfo->metric); + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + vty_out(vty, "V6 Address Hidden %2d ", + rinfo->metric); + break; + } + + /* Route which exist in kernel routing table. */ + if ((rinfo->type == ZEBRA_ROUTE_RIP) && + (rinfo->sub_type == RIP_ROUTE_RTE)) { + vty_out(vty, "%-15pI4 ", &rinfo->from); + vty_out(vty, "%3" ROUTE_TAG_PRI " ", + (route_tag_t)rinfo->tag); + rip_vty_out_uptime(vty, rinfo); + } else if (rinfo->metric == RIP_METRIC_INFINITY) { + vty_out(vty, "self "); + vty_out(vty, "%3" ROUTE_TAG_PRI " ", + (route_tag_t)rinfo->tag); + rip_vty_out_uptime(vty, rinfo); + } else { + if (rinfo->external_metric) { + len = vty_out( + vty, "self (%s:%d)", + zebra_route_string(rinfo->type), + rinfo->external_metric); + len = 16 - len; + if (len > 0) + vty_out(vty, "%*s", len, " "); + } else + vty_out(vty, "self "); + vty_out(vty, "%3" ROUTE_TAG_PRI, + (route_tag_t)rinfo->tag); + } + + vty_out(vty, "\n"); + } + } + return CMD_SUCCESS; +} + +/* Vincent: formerly, it was show_ip_protocols_rip: "show ip protocols" */ +DEFUN (show_ip_rip_status, + show_ip_rip_status_cmd, + "show ip rip [vrf NAME] status", + SHOW_STR + IP_STR + "Show RIP routes\n" + VRF_CMD_HELP_STR + "IP routing protocol process parameters and statistics\n") +{ + struct rip *rip; + struct interface *ifp; + struct rip_interface *ri; + extern const struct message ri_version_msg[]; + const char *send_version; + const char *receive_version; + const char *vrf_name; + int idx = 0; + + if (argv_find(argv, argc, "vrf", &idx)) + vrf_name = argv[idx + 1]->arg; + else + vrf_name = VRF_DEFAULT_NAME; + + rip = rip_lookup_by_vrf_name(vrf_name); + if (!rip) { + vty_out(vty, "%% RIP instance not found\n"); + return CMD_SUCCESS; + } + if (!rip->enabled) { + vty_out(vty, "%% RIP instance is disabled\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "Routing Protocol is \"rip\"\n"); + vty_out(vty, " Sending updates every %u seconds with +/-50%%,", + rip->update_time); + vty_out(vty, " next due in %lu seconds\n", + event_timer_remain_second(rip->t_update)); + vty_out(vty, " Timeout after %u seconds,", rip->timeout_time); + vty_out(vty, " garbage collect after %u seconds\n", rip->garbage_time); + + /* Filtering status show. */ + config_show_distribute(vty, rip->distribute_ctx); + + /* Default metric information. */ + vty_out(vty, " Default redistribution metric is %u\n", + rip->default_metric); + + /* Redistribute information. */ + vty_out(vty, " Redistributing:"); + rip_show_redistribute_config(vty, rip); + vty_out(vty, "\n"); + + vty_out(vty, " Default version control: send version %s,", + lookup_msg(ri_version_msg, rip->version_send, NULL)); + if (rip->version_recv == RI_RIP_VERSION_1_AND_2) + vty_out(vty, " receive any version \n"); + else + vty_out(vty, " receive version %s \n", + lookup_msg(ri_version_msg, rip->version_recv, NULL)); + + vty_out(vty, " Interface Send Recv Key-chain\n"); + + FOR_ALL_INTERFACES (rip->vrf, ifp) { + ri = ifp->info; + + if (!ri->running) + continue; + + if (ri->enable_network || ri->enable_interface) { + if (ri->ri_send == RI_RIP_UNSPEC) + send_version = + lookup_msg(ri_version_msg, + rip->version_send, NULL); + else + send_version = lookup_msg(ri_version_msg, + ri->ri_send, NULL); + + if (ri->ri_receive == RI_RIP_UNSPEC) + receive_version = + lookup_msg(ri_version_msg, + rip->version_recv, NULL); + else + receive_version = lookup_msg( + ri_version_msg, ri->ri_receive, NULL); + + vty_out(vty, " %-17s%-3s %-3s %s\n", ifp->name, + send_version, receive_version, + ri->key_chain ? ri->key_chain : ""); + } + } + + vty_out(vty, " Routing for Networks:\n"); + rip_show_network_config(vty, rip); + + int found_passive = 0; + FOR_ALL_INTERFACES (rip->vrf, ifp) { + ri = ifp->info; + + if ((ri->enable_network || ri->enable_interface) && + ri->passive) { + if (!found_passive) { + vty_out(vty, " Passive Interface(s):\n"); + found_passive = 1; + } + vty_out(vty, " %s\n", ifp->name); + } + } + + vty_out(vty, " Routing Information Sources:\n"); + vty_out(vty, + " Gateway BadPackets BadRoutes Distance Last Update\n"); + rip_peer_display(vty, rip); + + rip_distance_show(vty, rip); + + return CMD_SUCCESS; +} + +/* Distribute-list update functions. */ +static void rip_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist) +{ + struct interface *ifp; + struct rip_interface *ri; + struct access_list *alist; + struct prefix_list *plist; + + if (!ctx->vrf || !dist->ifname) + return; + + ifp = if_lookup_by_name(dist->ifname, ctx->vrf->vrf_id); + if (ifp == NULL) + return; + + ri = ifp->info; + + if (dist->list[DISTRIBUTE_V4_IN]) { + alist = access_list_lookup(AFI_IP, + dist->list[DISTRIBUTE_V4_IN]); + if (alist) + ri->list[RIP_FILTER_IN] = alist; + else + ri->list[RIP_FILTER_IN] = NULL; + } else + ri->list[RIP_FILTER_IN] = NULL; + + if (dist->list[DISTRIBUTE_V4_OUT]) { + alist = access_list_lookup(AFI_IP, + dist->list[DISTRIBUTE_V4_OUT]); + if (alist) + ri->list[RIP_FILTER_OUT] = alist; + else + ri->list[RIP_FILTER_OUT] = NULL; + } else + ri->list[RIP_FILTER_OUT] = NULL; + + if (dist->prefix[DISTRIBUTE_V4_IN]) { + plist = prefix_list_lookup(AFI_IP, + dist->prefix[DISTRIBUTE_V4_IN]); + if (plist) + ri->prefix[RIP_FILTER_IN] = plist; + else + ri->prefix[RIP_FILTER_IN] = NULL; + } else + ri->prefix[RIP_FILTER_IN] = NULL; + + if (dist->prefix[DISTRIBUTE_V4_OUT]) { + plist = prefix_list_lookup(AFI_IP, + dist->prefix[DISTRIBUTE_V4_OUT]); + if (plist) + ri->prefix[RIP_FILTER_OUT] = plist; + else + ri->prefix[RIP_FILTER_OUT] = NULL; + } else + ri->prefix[RIP_FILTER_OUT] = NULL; +} + +void rip_distribute_update_interface(struct interface *ifp) +{ + struct rip_interface *ri = ifp->info; + struct rip *rip = ri->rip; + struct distribute *dist; + + if (!rip) + return; + dist = distribute_lookup(rip->distribute_ctx, ifp->name); + if (dist) + rip_distribute_update(rip->distribute_ctx, dist); +} + +/* Update all interface's distribute list. */ +/* ARGSUSED */ +static void rip_distribute_update_all(struct prefix_list *notused) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) + rip_distribute_update_interface(ifp); +} +/* ARGSUSED */ +static void rip_distribute_update_all_wrapper(struct access_list *notused) +{ + rip_distribute_update_all(NULL); +} + +/* Delete all added rip route. */ +void rip_clean(struct rip *rip) +{ + rip_interfaces_clean(rip); + + if (rip->enabled) + rip_instance_disable(rip); + + stream_free(rip->obuf); + + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) + if (rip->redist[i].route_map.name) + free(rip->redist[i].route_map.name); + + route_table_finish(rip->table); + route_table_finish(rip->neighbor); + list_delete(&rip->peer_list); + distribute_list_delete(&rip->distribute_ctx); + if_rmap_ctx_delete(rip->if_rmap_ctx); + + rip_clean_network(rip); + rip_passive_nondefault_clean(rip); + vector_free(rip->enable_interface); + route_table_finish(rip->enable_network); + vector_free(rip->passive_nondefault); + list_delete(&rip->offset_list_master); + route_table_finish(rip->distance_table); + + RB_REMOVE(rip_instance_head, &rip_instances, rip); + XFREE(MTYPE_RIP_BFD_PROFILE, rip->default_bfd_profile); + XFREE(MTYPE_RIP_VRF_NAME, rip->vrf_name); + XFREE(MTYPE_RIP, rip); +} + +static void rip_if_rmap_update(struct if_rmap_ctx *ctx, + struct if_rmap *if_rmap) +{ + struct interface *ifp = NULL; + struct rip_interface *ri; + struct route_map *rmap; + struct vrf *vrf = NULL; + + if (ctx->name) + vrf = vrf_lookup_by_name(ctx->name); + if (vrf) + ifp = if_lookup_by_name(if_rmap->ifname, vrf->vrf_id); + if (ifp == NULL) + return; + + ri = ifp->info; + if (if_rmap->routemap[IF_RMAP_IN]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_IN]); + if (rmap) + ri->routemap[IF_RMAP_IN] = rmap; + else + ri->routemap[IF_RMAP_IN] = NULL; + } else + ri->routemap[RIP_FILTER_IN] = NULL; + + if (if_rmap->routemap[IF_RMAP_OUT]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_OUT]); + if (rmap) + ri->routemap[IF_RMAP_OUT] = rmap; + else + ri->routemap[IF_RMAP_OUT] = NULL; + } else + ri->routemap[RIP_FILTER_OUT] = NULL; +} + +void rip_if_rmap_update_interface(struct interface *ifp) +{ + struct rip_interface *ri = ifp->info; + struct rip *rip = ri->rip; + struct if_rmap *if_rmap; + struct if_rmap_ctx *ctx; + + if (!rip) + return; + ctx = rip->if_rmap_ctx; + if (!ctx) + return; + if_rmap = if_rmap_lookup(ctx, ifp->name); + if (if_rmap) + rip_if_rmap_update(ctx, if_rmap); +} + +static void rip_routemap_update_redistribute(struct rip *rip) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (rip->redist[i].route_map.name) { + rip->redist[i].route_map.map = route_map_lookup_by_name( + rip->redist[i].route_map.name); + route_map_counter_increment( + rip->redist[i].route_map.map); + } + } +} + +/* ARGSUSED */ +static void rip_routemap_update(const char *notused) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct rip *rip; + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) + rip_if_rmap_update_interface(ifp); + + rip = vrf->info; + if (rip) + rip_routemap_update_redistribute(rip); +} + +/* Link RIP instance to VRF. */ +static void rip_vrf_link(struct rip *rip, struct vrf *vrf) +{ + struct interface *ifp; + + rip->vrf = vrf; + rip->distribute_ctx->vrf = vrf; + vrf->info = rip; + + FOR_ALL_INTERFACES (vrf, ifp) + rip_interface_sync(ifp); +} + +/* Unlink RIP instance from VRF. */ +static void rip_vrf_unlink(struct rip *rip, struct vrf *vrf) +{ + struct interface *ifp; + + rip->vrf = NULL; + rip->distribute_ctx->vrf = NULL; + vrf->info = NULL; + + FOR_ALL_INTERFACES (vrf, ifp) + rip_interface_sync(ifp); +} + +static void rip_instance_enable(struct rip *rip, struct vrf *vrf, int sock) +{ + rip->sock = sock; + + rip_vrf_link(rip, vrf); + rip->enabled = true; + + /* Resend all redistribute requests. */ + rip_redistribute_enable(rip); + + /* Create read and timer thread. */ + rip_event(rip, RIP_READ, rip->sock); + rip_event(rip, RIP_UPDATE_EVENT, 1); + + rip_zebra_vrf_register(vrf); +} + +static void rip_instance_disable(struct rip *rip) +{ + struct vrf *vrf = rip->vrf; + struct route_node *rp; + + /* Clear RIP routes */ + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + struct rip_info *rinfo; + struct list *list; + struct listnode *listnode; + + if ((list = rp->info) == NULL) + continue; + + rinfo = listgetdata(listhead(list)); + if (rip_route_rte(rinfo)) + rip_zebra_ipv4_delete(rip, rp); + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + rip_info_free(rinfo); + } + list_delete(&list); + rp->info = NULL; + route_unlock_node(rp); + } + + /* Flush all redistribute requests. */ + rip_redistribute_disable(rip); + + /* Cancel RIP related timers. */ + EVENT_OFF(rip->t_update); + EVENT_OFF(rip->t_triggered_update); + EVENT_OFF(rip->t_triggered_interval); + + /* Cancel read thread. */ + EVENT_OFF(rip->t_read); + + /* Close RIP socket. */ + close(rip->sock); + rip->sock = -1; + + /* Clear existing peers. */ + list_delete_all_node(rip->peer_list); + + rip_zebra_vrf_deregister(vrf); + + rip_vrf_unlink(rip, vrf); + rip->enabled = false; +} + +static int rip_vrf_new(struct vrf *vrf) +{ + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: VRF created: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static int rip_vrf_delete(struct vrf *vrf) +{ + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: VRF deleted: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static int rip_vrf_enable(struct vrf *vrf) +{ + struct rip *rip; + int socket; + + rip = rip_lookup_by_vrf_name(vrf->name); + if (!rip || rip->enabled) + return 0; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: VRF %s(%u) enabled", __func__, vrf->name, + vrf->vrf_id); + + /* Activate the VRF RIP instance. */ + if (!rip->enabled) { + socket = rip_create_socket(vrf); + if (socket < 0) + return -1; + + rip_instance_enable(rip, vrf, socket); + } + + return 0; +} + +static int rip_vrf_disable(struct vrf *vrf) +{ + struct rip *rip; + + rip = rip_lookup_by_vrf_name(vrf->name); + if (!rip || !rip->enabled) + return 0; + + if (IS_RIP_DEBUG_EVENT) + zlog_debug("%s: VRF %s(%u) disabled", __func__, vrf->name, + vrf->vrf_id); + + /* Deactivate the VRF RIP instance. */ + if (rip->enabled) + rip_instance_disable(rip); + + return 0; +} + +void rip_vrf_init(void) +{ + vrf_init(rip_vrf_new, rip_vrf_enable, rip_vrf_disable, rip_vrf_delete); +} + +void rip_vrf_terminate(void) +{ + vrf_terminate(); +} + +/* Allocate new rip structure and set default value. */ +void rip_init(void) +{ + /* Install rip commands. */ + install_element(VIEW_NODE, &show_ip_rip_cmd); + install_element(VIEW_NODE, &show_ip_rip_status_cmd); + + /* Debug related init. */ + rip_debug_init(); + /* Enable mgmt be debug */ + mgmt_be_client_lib_vty_init(); + + /* Access list install. */ + access_list_init_new(true); + access_list_add_hook(rip_distribute_update_all_wrapper); + access_list_delete_hook(rip_distribute_update_all_wrapper); + + /* Prefix list initialize.*/ + prefix_list_init(); + prefix_list_add_hook(rip_distribute_update_all); + prefix_list_delete_hook(rip_distribute_update_all); + + /* Route-map */ + rip_route_map_init(); + + route_map_add_hook(rip_routemap_update); + route_map_delete_hook(rip_routemap_update); +} diff --git a/ripd/ripd.h b/ripd/ripd.h new file mode 100644 index 0000000..08f7a24 --- /dev/null +++ b/ripd/ripd.h @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIP related values and structures. + * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_RIP_H +#define _ZEBRA_RIP_H + +#include "hook.h" +#include "nexthop.h" +#include "distribute.h" +#include "memory.h" +#include "bfd.h" + +/* RIP version number. */ +#define RIPv1 1 +#define RIPv2 2 +/* N.B. stuff will break if + (RIPv1 != RI_RIP_VERSION_1) || (RIPv2 != RI_RIP_VERSION_2) */ + + +/* RIP command list. */ +#define RIP_REQUEST 1 +#define RIP_RESPONSE 2 +#define RIP_TRACEON 3 /* Obsolete */ +#define RIP_TRACEOFF 4 /* Obsolete */ +#define RIP_POLL 5 +#define RIP_POLL_ENTRY 6 +#define RIP_COMMAND_MAX 7 + +/* RIP metric infinity value.*/ +#define RIP_METRIC_INFINITY 16 + +/* Normal RIP packet min and max size. */ +#define RIP_PACKET_MINSIZ 4 +#define RIP_PACKET_MAXSIZ 512 + +#define RIP_HEADER_SIZE 4 +#define RIP_RTE_SIZE 20 + +/* Max count of routing table entry in one rip packet. */ +#define RIP_MAX_RTE ((RIP_PACKET_MAXSIZ - RIP_HEADER_SIZE) / RIP_RTE_SIZE) + +/* RIP version 2 multicast address. */ +#ifndef INADDR_RIP_GROUP +#define INADDR_RIP_GROUP 0xe0000009 /* 224.0.0.9 */ +#endif + +/* RIP peer timeout value. */ +#define RIP_PEER_TIMER_DEFAULT 180 + +/* RIP port number. */ +#define RIP_PORT_DEFAULT 520 + +/* Default configuration file name. */ +#define RIPD_DEFAULT_CONFIG "ripd.conf" + +/* RIP route types. */ +#define RIP_ROUTE_RTE 0 +#define RIP_ROUTE_STATIC 1 +#define RIP_ROUTE_DEFAULT 2 +#define RIP_ROUTE_REDISTRIBUTE 3 +#define RIP_ROUTE_INTERFACE 4 + +/* RIPv2 special RTE family types */ +#define RIP_FAMILY_AUTH 0xffff + +/* RIPv2 authentication types, for RIP_FAMILY_AUTH RTE's */ +#define RIP_NO_AUTH 0 +#define RIP_AUTH_DATA 1 +#define RIP_AUTH_SIMPLE_PASSWORD 2 +#define RIP_AUTH_MD5 3 + +/* RIPv2 Simple authentication */ +#define RIP_AUTH_SIMPLE_SIZE 16 + +/* RIPv2 MD5 authentication. */ +#define RIP_AUTH_MD5_SIZE 16 +#define RIP_AUTH_MD5_COMPAT_SIZE RIP_RTE_SIZE + +/* YANG paths */ +#define RIP_INSTANCE "/frr-ripd:ripd/instance" +#define RIP_IFACE "/frr-interface:lib/interface/frr-ripd:rip" + +DECLARE_MGROUP(RIPD); + +/* RIP structure. */ +struct rip { + RB_ENTRY(rip) entry; + + /* VRF this routing instance is associated with. */ + char *vrf_name; + + /* VRF backpointer (might be NULL if the VRF doesn't exist). */ + struct vrf *vrf; + + /* Status of the routing instance. */ + bool enabled; + + /* RIP socket. */ + int sock; + + /* Default version of rip instance. */ + int version_send; /* version 1 or 2 (but not both) */ + int version_recv; /* version 1 or 2 or both */ + + /* Output buffer of RIP. */ + struct stream *obuf; + + /* RIP routing information base. */ + struct route_table *table; + + /* RIP static neighbors. */ + struct route_table *neighbor; + + /* Linked list of RIP peers. */ + struct list *peer_list; + + /* RIP threads. */ + struct event *t_read; + + /* Update and garbage timer. */ + struct event *t_update; + + /* Triggered update hack. */ + int trigger; + struct event *t_triggered_update; + struct event *t_triggered_interval; + + /* RIP timer values. */ + uint32_t update_time; + uint32_t timeout_time; + uint32_t garbage_time; + + /* RIP default metric. */ + uint8_t default_metric; + + /* RIP default distance. */ + uint8_t distance; + struct route_table *distance_table; + + /* RIP ECMP flag */ + uint8_t ecmp; + + /* Are we in passive-interface default mode? */ + bool passive_default; + + /* RIP enabled interfaces. */ + vector enable_interface; + + /* RIP enabled networks. */ + struct route_table *enable_network; + + /* Vector to store passive-interface name. */ + vector passive_nondefault; + + /* RIP offset-lists. */ + struct list *offset_list_master; + + /* RIP redistribute configuration. */ + struct { + bool enabled; + struct { + char *name; + struct route_map *map; + } route_map; + bool metric_config; + uint8_t metric; + } redist[ZEBRA_ROUTE_MAX]; + + /* For distribute-list container */ + struct distribute_ctx *distribute_ctx; + + /* For if_rmap container */ + struct if_rmap_ctx *if_rmap_ctx; + + /* Counters for SNMP. */ + struct { + /* RIP route changes. */ + long route_changes; + + /* RIP queries. */ + long queries; + } counters; + + /* Default BFD profile to use with BFD sessions. */ + char *default_bfd_profile; +}; +RB_HEAD(rip_instance_head, rip); +RB_PROTOTYPE(rip_instance_head, rip, entry, rip_instance_compare) + +/* RIP routing table entry which belong to rip_packet. */ +struct rte { + uint16_t family; /* Address family of this route. */ + uint16_t tag; /* Route Tag which included in RIP2 packet. */ + struct in_addr prefix; /* Prefix of rip route. */ + struct in_addr mask; /* Netmask of rip route. */ + struct in_addr nexthop; /* Next hop of rip route. */ + uint32_t metric; /* Metric value of rip route. */ +}; + +/* RIP packet structure. */ +struct rip_packet { + unsigned char command; /* Command type of RIP packet. */ + unsigned char version; /* RIP version which coming from peer. */ + unsigned char pad1; /* Padding of RIP packet header. */ + unsigned char pad2; /* Same as above. */ + struct rte rte[1]; /* Address structure. */ +}; + +/* Buffer to read RIP packet. */ +union rip_buf { + struct rip_packet rip_packet; + char buf[RIP_PACKET_MAXSIZ]; +}; + +/* RIP route information. */ +struct rip_info { + /* This route's type. */ + int type; + + /* Sub type. */ + int sub_type; + + /* RIP nexthop. */ + struct nexthop nh; + struct in_addr from; + + /* Metric of this route. */ + uint32_t metric; + + /* External metric of this route. + if learnt from an externalm proto */ + uint32_t external_metric; + + /* Tag information of this route. */ + uint16_t tag; + +/* Flags of RIP route. */ +#define RIP_RTF_FIB 1 +#define RIP_RTF_CHANGED 2 + uint8_t flags; + + /* Garbage collect timer. */ + struct event *t_timeout; + struct event *t_garbage_collect; + + /* Route-map futures - this variables can be changed. */ + struct in_addr nexthop_out; + uint8_t metric_set; + uint32_t metric_out; + uint16_t tag_out; + ifindex_t ifindex_out; + + struct route_node *rp; + + uint8_t distance; +}; + +typedef enum { + RIP_NO_SPLIT_HORIZON = 0, + RIP_SPLIT_HORIZON, + RIP_SPLIT_HORIZON_POISONED_REVERSE +} split_horizon_policy_t; + +/* RIP specific interface configuration. */ +struct rip_interface { + /* Parent routing instance. */ + struct rip *rip; + + /* Interface data from zebra. */ + struct interface *ifp; + + /* RIP is enabled on this interface. */ + int enable_network; + int enable_interface; + + /* RIP is running on this interface. */ + int running; + + /* RIP version control. */ + int ri_send; + int ri_receive; + + /* RIPv2 broadcast mode */ + bool v2_broadcast; + + /* RIPv2 authentication type. */ + int auth_type; + + /* RIPv2 authentication string. */ + char *auth_str; + + /* RIPv2 authentication key chain. */ + char *key_chain; + + /* value to use for md5->auth_len */ + int md5_auth_len; + + /* Split horizon flag. */ + split_horizon_policy_t split_horizon; + +/* For filter type slot. */ +#define RIP_FILTER_IN 0 +#define RIP_FILTER_OUT 1 +#define RIP_FILTER_MAX 2 + + /* Access-list. */ + struct access_list *list[RIP_FILTER_MAX]; + + /* Prefix-list. */ + struct prefix_list *prefix[RIP_FILTER_MAX]; + + /* Route-map. */ + struct route_map *routemap[RIP_FILTER_MAX]; + + /* Wake up thread. */ + struct event *t_wakeup; + + /* Interface statistics. */ + int recv_badpackets; + int recv_badroutes; + int sent_updates; + + /* Passive interface. */ + int passive; + + /* BFD information. */ + struct { + bool enabled; + char *profile; + } bfd; +}; + +/* RIP peer information. */ +struct rip_peer { + /* Parent routing instance. */ + struct rip *rip; + + /* Back-pointer to RIP interface. */ + struct rip_interface *ri; + + /* Peer address. */ + struct in_addr addr; + + /* Peer RIP tag value. */ + int domain; + + /* Last update time. */ + time_t uptime; + + /* Peer RIP version. */ + uint8_t version; + + /* Statistics. */ + int recv_badpackets; + int recv_badroutes; + + /* Timeout thread. */ + struct event *t_timeout; + + /* BFD information */ + struct bfd_session_params *bfd_session; +}; + +struct rip_distance { + /* Distance value for the IP source prefix. */ + uint8_t distance; + + /* Name of the access-list to be matched. */ + char *access_list; +}; + +struct rip_md5_info { + uint16_t family; + uint16_t type; + uint16_t packet_len; + uint8_t keyid; + uint8_t auth_len; + uint32_t sequence; + uint32_t reserv1; + uint32_t reserv2; +}; + +struct rip_md5_data { + uint16_t family; + uint16_t type; + uint8_t digest[16]; +}; + +/* RIP accepet/announce methods. */ +#define RI_RIP_UNSPEC 0 +#define RI_RIP_VERSION_1 1 +#define RI_RIP_VERSION_2 2 +#define RI_RIP_VERSION_1_AND_2 3 +#define RI_RIP_VERSION_NONE 4 +/* N.B. stuff will break if + (RIPv1 != RI_RIP_VERSION_1) || (RIPv2 != RI_RIP_VERSION_2) */ + +/* RIP event. */ +enum rip_event { + RIP_READ, + RIP_UPDATE_EVENT, + RIP_TRIGGERED_UPDATE, +}; + +/* Macro for timer turn on. */ +#define RIP_TIMER_ON(T, F, V) event_add_timer(master, (F), rinfo, (V), &(T)) + +#define RIP_OFFSET_LIST_IN 0 +#define RIP_OFFSET_LIST_OUT 1 +#define RIP_OFFSET_LIST_MAX 2 + +struct rip_offset_list { + /* Parent routing instance. */ + struct rip *rip; + + char *ifname; + + struct { + char *alist_name; + /* struct access_list *alist; */ + uint8_t metric; + } direct[RIP_OFFSET_LIST_MAX]; +}; + +/* Prototypes. */ +extern void rip_init(void); +extern void rip_clean(struct rip *rip); +extern void rip_clean_network(struct rip *rip); +extern void rip_interfaces_clean(struct rip *rip); +extern int rip_passive_nondefault_set(struct rip *rip, const char *ifname); +extern int rip_passive_nondefault_unset(struct rip *rip, const char *ifname); +extern void rip_passive_nondefault_clean(struct rip *rip); +extern void rip_if_init(void); +extern void rip_route_map_init(void); +extern void rip_zebra_vrf_register(struct vrf *vrf); +extern void rip_zebra_vrf_deregister(struct vrf *vrf); +extern void rip_zclient_init(struct event_loop *e); +extern void rip_zclient_stop(void); +extern int if_check_address(struct rip *rip, struct in_addr addr); +extern struct rip *rip_lookup_by_vrf_id(vrf_id_t vrf_id); +extern struct rip *rip_lookup_by_vrf_name(const char *vrf_name); +extern struct rip *rip_create(const char *vrf_name, struct vrf *vrf, + int socket); + +extern int rip_request_send(struct sockaddr_in *, struct interface *, uint8_t, + struct connected *); +extern int rip_neighbor_lookup(struct rip *rip, struct sockaddr_in *from); +extern int rip_neighbor_add(struct rip *rip, struct prefix_ipv4 *p); +extern int rip_neighbor_delete(struct rip *rip, struct prefix_ipv4 *p); + +extern int rip_enable_network_add(struct rip *rip, struct prefix *p); +extern int rip_enable_network_delete(struct rip *rip, struct prefix *p); +extern int rip_enable_if_add(struct rip *rip, const char *ifname); +extern int rip_enable_if_delete(struct rip *rip, const char *ifname); + +extern void rip_event(struct rip *rip, enum rip_event event, int sock); +extern void rip_ecmp_disable(struct rip *rip); + +extern int rip_create_socket(struct vrf *vrf); + +extern int rip_redistribute_check(struct rip *rip, int type); +extern void rip_redistribute_conf_update(struct rip *rip, int type); +extern void rip_redistribute_conf_delete(struct rip *rip, int type); +extern void rip_redistribute_add(struct rip *rip, int type, int sub_type, + struct prefix_ipv4 *p, struct nexthop *nh, + unsigned int metric, unsigned char distance, + route_tag_t tag); +extern void rip_redistribute_delete(struct rip *rip, int type, int sub_type, + struct prefix_ipv4 *p, ifindex_t ifindex); +extern void rip_redistribute_withdraw(struct rip *rip, int type); +extern void rip_zebra_ipv4_add(struct rip *rip, struct route_node *rp); +extern void rip_zebra_ipv4_delete(struct rip *rip, struct route_node *rp); +extern void rip_interface_multicast_set(int, struct connected *); +extern void rip_distribute_update_interface(struct interface *); +extern void rip_if_rmap_update_interface(struct interface *ifp); + +extern int rip_show_network_config(struct vty *vty, struct rip *rip); +extern void rip_show_redistribute_config(struct vty *vty, struct rip *rip); + +extern void rip_peer_free(struct rip_peer *peer); +extern void rip_peer_update(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from, uint8_t version); +extern void rip_peer_bad_route(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from); +extern void rip_peer_bad_packet(struct rip *rip, struct rip_interface *ri, + struct sockaddr_in *from); +extern void rip_peer_display(struct vty *vty, struct rip *rip); +extern struct rip_peer *rip_peer_lookup(struct rip *rip, struct in_addr *addr); +extern struct rip_peer *rip_peer_lookup_next(struct rip *rip, + struct in_addr *addr); +extern int rip_peer_list_cmp(struct rip_peer *p1, struct rip_peer *p2); +extern void rip_peer_list_del(void *arg); +void rip_peer_delete_routes(const struct rip_peer *peer); + +extern void rip_info_free(struct rip_info *); +extern struct rip *rip_info_get_instance(const struct rip_info *rinfo); +extern struct rip_distance *rip_distance_new(void); +extern void rip_distance_free(struct rip_distance *rdistance); +extern uint8_t rip_distance_apply(struct rip *rip, struct rip_info *rinfo); +extern void rip_redistribute_enable(struct rip *rip); +extern void rip_redistribute_disable(struct rip *rip); + +extern int rip_route_rte(struct rip_info *rinfo); +extern struct rip_info *rip_ecmp_add(struct rip *rip, + struct rip_info *rinfo_new); +extern struct rip_info *rip_ecmp_replace(struct rip *rip, + struct rip_info *rinfo_new); +extern struct rip_info *rip_ecmp_delete(struct rip *rip, + struct rip_info *rinfo); + +extern struct rip_offset_list *rip_offset_list_new(struct rip *rip, + const char *ifname); +extern void offset_list_del(struct rip_offset_list *offset); +extern void offset_list_free(struct rip_offset_list *offset); +extern struct rip_offset_list *rip_offset_list_lookup(struct rip *rip, + const char *ifname); +extern int rip_offset_list_apply_in(struct prefix_ipv4 *, struct interface *, + uint32_t *); +extern int rip_offset_list_apply_out(struct prefix_ipv4 *, struct interface *, + uint32_t *); +extern int offset_list_cmp(struct rip_offset_list *o1, + struct rip_offset_list *o2); + +extern void rip_vrf_init(void); +extern void rip_vrf_terminate(void); + +extern struct zebra_privs_t ripd_privs; +extern struct rip_instance_head rip_instances; + +/* Master thread structure. */ +extern struct event_loop *master; + +DECLARE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc)); +DECLARE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc)); + +extern void rip_ecmp_change(struct rip *rip); + +extern uint32_t zebra_ecmp_count; + +#endif /* _ZEBRA_RIP_H */ diff --git a/ripd/subdir.am b/ripd/subdir.am new file mode 100644 index 0000000..aed8d24 --- /dev/null +++ b/ripd/subdir.am @@ -0,0 +1,56 @@ +# +# ripd +# + +if RIPD +sbin_PROGRAMS += ripd/ripd +vtysh_daemons += ripd + +if SNMP +module_LTLIBRARIES += ripd/ripd_snmp.la +endif +man8 += $(MANBUILD)/frr-ripd.8 +endif + +ripd_ripd_SOURCES = \ + ripd/rip_bfd.c \ + ripd/rip_debug.c \ + ripd/rip_errors.c \ + ripd/rip_interface.c \ + ripd/rip_offset.c \ + ripd/rip_main.c \ + ripd/rip_nb.c \ + ripd/rip_nb_config.c \ + ripd/rip_nb_rpcs.c \ + ripd/rip_nb_notifications.c \ + ripd/rip_nb_state.c \ + ripd/rip_peer.c \ + ripd/rip_routemap.c \ + ripd/rip_zebra.c \ + ripd/ripd.c \ + # end + +clippy_scan += \ + ripd/rip_bfd.c \ + ripd/rip_cli.c \ + # end + +noinst_HEADERS += \ + ripd/rip_bfd.h \ + ripd/rip_debug.h \ + ripd/rip_errors.h \ + ripd/rip_interface.h \ + ripd/rip_nb.h \ + ripd/ripd.h \ + # end + +ripd_ripd_LDADD = lib/libfrr.la $(LIBCAP) +nodist_ripd_ripd_SOURCES = \ + yang/frr-ripd.yang.c \ + yang/frr-bfdd.yang.c \ + # end + +ripd_ripd_snmp_la_SOURCES = ripd/rip_snmp.c +ripd_ripd_snmp_la_CFLAGS = $(AM_CFLAGS) $(SNMP_CFLAGS) -std=gnu11 +ripd_ripd_snmp_la_LDFLAGS = $(MODULE_LDFLAGS) +ripd_ripd_snmp_la_LIBADD = lib/libfrrsnmp.la diff --git a/ripngd/.gitignore b/ripngd/.gitignore new file mode 100644 index 0000000..e6a8ee6 --- /dev/null +++ b/ripngd/.gitignore @@ -0,0 +1,2 @@ +ripngd +ripngd.conf diff --git a/ripngd/Makefile b/ripngd/Makefile new file mode 100644 index 0000000..5b76bb2 --- /dev/null +++ b/ripngd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. ripngd/ripngd +%: ALWAYS + @$(MAKE) -s -C .. ripngd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/ripngd/ripng_cli.c b/ripngd/ripng_cli.c new file mode 100644 index 0000000..99cb68e --- /dev/null +++ b/ripngd/ripng_cli.c @@ -0,0 +1,772 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1998 Kunihiro Ishiguro + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "if_rmap.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "command.h" +#include "northbound_cli.h" +#include "libfrr.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nb.h" +#include "ripngd/ripng_cli_clippy.c" + +/* + * XPath: /frr-ripngd:ripngd/instance + */ +DEFPY_YANG_NOSH (router_ripng, + router_ripng_cmd, + "router ripng [vrf NAME]", + "Enable a routing process\n" + "Make RIPng instance command\n" + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + int ret; + + /* Build RIPng instance XPath. */ + if (!vrf) + vrf = VRF_DEFAULT_NAME; + snprintf(xpath, sizeof(xpath), "/frr-ripngd:ripngd/instance[vrf='%s']", + vrf); + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + + ret = nb_cli_apply_changes(vty, NULL); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(RIPNG_NODE, xpath); + + return ret; +} + +DEFPY_YANG (no_router_ripng, + no_router_ripng_cmd, + "no router ripng [vrf NAME]", + NO_STR + "Enable a routing process\n" + "Make RIPng instance command\n" + VRF_CMD_HELP_STR) +{ + char xpath[XPATH_MAXLEN]; + + /* Build RIPng instance XPath. */ + if (!vrf) + vrf = VRF_DEFAULT_NAME; + snprintf(xpath, sizeof(xpath), "/frr-ripngd:ripngd/instance[vrf='%s']", + vrf); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes_clear_pending(vty, NULL); +} + +void cli_show_router_ripng(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *vrf_name; + + vrf_name = yang_dnode_get_string(dnode, "vrf"); + + vty_out(vty, "!\n"); + vty_out(vty, "router ripng"); + if (!strmatch(vrf_name, VRF_DEFAULT_NAME)) + vty_out(vty, " vrf %s", vrf_name); + vty_out(vty, "\n"); +} + +void cli_show_end_router_ripng(struct vty *vty, const struct lyd_node *dnode) +{ + vty_out(vty, "exit\n"); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/allow-ecmp + */ +DEFUN_YANG (ripng_allow_ecmp, + ripng_allow_ecmp_cmd, + "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", + "Allow Equal Cost MultiPath\n" + "Number of paths\n") +{ + int idx_number = 0; + char mpaths[3] = {}; + uint32_t paths = MULTIPATH_NUM; + + if (argv_find(argv, argc, CMD_RANGE_STR(1, MULTIPATH_NUM), &idx_number)) + paths = strtol(argv[idx_number]->arg, NULL, 10); + snprintf(mpaths, sizeof(mpaths), "%u", paths); + + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, mpaths); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_ripng_allow_ecmp, + no_ripng_allow_ecmp_cmd, + "no allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR + "Allow Equal Cost MultiPath\n" + "Number of paths\n") +{ + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, 0); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + uint8_t paths; + + paths = yang_dnode_get_uint8(dnode, NULL); + + if (!paths) + vty_out(vty, " no allow-ecmp\n"); + else + vty_out(vty, " allow-ecmp %d\n", paths); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/default-information-originate + */ +DEFPY_YANG (ripng_default_information_originate, + ripng_default_information_originate_cmd, + "[no] default-information originate", + NO_STR + "Default route information\n" + "Distribute default route\n") +{ + nb_cli_enqueue_change(vty, "./default-information-originate", + NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_default_information_originate(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + + vty_out(vty, " default-information originate\n"); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/default-metric + */ +DEFPY_YANG (ripng_default_metric, + ripng_default_metric_cmd, + "default-metric (1-16)", + "Set a metric of redistribute routes\n" + "Default metric\n") +{ + nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, + default_metric_str); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_ripng_default_metric, + no_ripng_default_metric_cmd, + "no default-metric [(1-16)]", + NO_STR + "Set a metric of redistribute routes\n" + "Default metric\n") +{ + nb_cli_enqueue_change(vty, "./default-metric", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_default_metric(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " default-metric %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/network + */ +DEFPY_YANG (ripng_network_prefix, + ripng_network_prefix_cmd, + "[no] network X:X::X:X/M", + NO_STR + "RIPng enable on specified interface or network.\n" + "IPv6 network\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./network[.='%s']", network_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_network_prefix(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/interface + */ +DEFPY_YANG (ripng_network_if, + ripng_network_if_cmd, + "[no] network WORD", + NO_STR + "RIPng enable on specified interface or network.\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./interface[.='%s']", network); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_network_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " network %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/offset-list + */ +DEFPY_YANG (ripng_offset_list, + ripng_offset_list_cmd, + "[no] offset-list ACCESSLIST6_NAME$acl $direction (0-16)$metric [IFNAME]", + NO_STR + "Modify RIPng metric\n" + "Access-list name\n" + "For incoming updates\n" + "For outgoing updates\n" + "Metric value\n" + "Interface to match\n") +{ + if (!no) { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./access-list", NB_OP_MODIFY, acl); + nb_cli_enqueue_change(vty, "./metric", NB_OP_MODIFY, + metric_str); + } else + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes( + vty, "./offset-list[interface='%s'][direction='%s']", + ifname ? ifname : "*", direction); +} + +void cli_show_ripng_offset_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *interface; + + interface = yang_dnode_get_string(dnode, "interface"); + + vty_out(vty, " offset-list %s %s %s", + yang_dnode_get_string(dnode, "access-list"), + yang_dnode_get_string(dnode, "direction"), + yang_dnode_get_string(dnode, "metric")); + if (!strmatch(interface, "*")) + vty_out(vty, " %s", interface); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/passive-interface + */ +DEFPY_YANG (ripng_passive_interface, + ripng_passive_interface_cmd, + "[no] passive-interface IFNAME", + NO_STR + "Suppress routing updates on an interface\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./passive-interface[.='%s']", ifname); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + ifname); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " passive-interface %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/redistribute + */ +DEFPY_YANG (ripng_redistribute, + ripng_redistribute_cmd, + "[no] redistribute " FRR_REDIST_STR_RIPNGD "$protocol [{metric (0-16)|route-map RMAP_NAME$route_map}]", + NO_STR + REDIST_STR + FRR_REDIST_HELP_STR_RIPNGD + "Metric\n" + "Metric value\n" + "Route map reference\n" + "Pointer to route-map entries\n") +{ + if (!no) { + nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); + nb_cli_enqueue_change(vty, "./route-map", + route_map ? NB_OP_MODIFY : NB_OP_DESTROY, + route_map); + nb_cli_enqueue_change(vty, "./metric", + metric_str ? NB_OP_MODIFY : NB_OP_DESTROY, + metric_str); + } else + nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, "./redistribute[protocol='%s']", + protocol); +} + +void cli_show_ripng_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " redistribute %s", + yang_dnode_get_string(dnode, "protocol")); + if (yang_dnode_exists(dnode, "metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "metric")); + if (yang_dnode_exists(dnode, "route-map")) + vty_out(vty, " route-map %s", + yang_dnode_get_string(dnode, "route-map")); + vty_out(vty, "\n"); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/static-route + */ +DEFPY_YANG (ripng_route, + ripng_route_cmd, + "[no] route X:X::X:X/M", + NO_STR + "Static route setup\n" + "Set static RIPng route announcement\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./static-route[.='%s']", route_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_route(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " route %s\n", yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/aggregate-addres + */ +DEFPY_YANG (ripng_aggregate_address, + ripng_aggregate_address_cmd, + "[no] aggregate-address X:X::X:X/M", + NO_STR + "Set aggregate RIPng route announcement\n" + "Aggregate network\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "./aggregate-address[.='%s']", + aggregate_address_str); + + nb_cli_enqueue_change(vty, xpath, no ? NB_OP_DESTROY : NB_OP_CREATE, + NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_ripng_aggregate_address(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " aggregate-address %s\n", + yang_dnode_get_string(dnode, NULL)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/timers + */ +DEFPY_YANG (ripng_timers, + ripng_timers_cmd, + "timers basic (1-65535)$update (1-65535)$timeout (1-65535)$garbage", + "RIPng timers setup\n" + "Basic timer\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") +{ + nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, + update_str); + nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, + timeout_str); + nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, + garbage_str); + + return nb_cli_apply_changes(vty, "./timers"); +} + +DEFPY_YANG (no_ripng_timers, + no_ripng_timers_cmd, + "no timers basic [(1-65535) (1-65535) (1-65535)]", + NO_STR + "RIPng timers setup\n" + "Basic timer\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") +{ + nb_cli_enqueue_change(vty, "./update-interval", NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, "./holddown-interval", NB_OP_MODIFY, NULL); + nb_cli_enqueue_change(vty, "./flush-interval", NB_OP_MODIFY, NULL); + + return nb_cli_apply_changes(vty, "./timers"); +} + +void cli_show_ripng_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + vty_out(vty, " timers basic %s %s %s\n", + yang_dnode_get_string(dnode, "update-interval"), + yang_dnode_get_string(dnode, "holddown-interval"), + yang_dnode_get_string(dnode, "flush-interval")); +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripngd:ripng/split-horizon + */ +DEFPY_YANG (ipv6_ripng_split_horizon, + ipv6_ripng_split_horizon_cmd, + "[no] ipv6 ripng split-horizon [poisoned-reverse$poisoned_reverse]", + NO_STR + IPV6_STR + "Routing Information Protocol\n" + "Perform split horizon\n" + "With poisoned-reverse\n") +{ + const char *value; + + if (no) + value = "disabled"; + else if (poisoned_reverse) + value = "poison-reverse"; + else + value = "simple"; + + nb_cli_enqueue_change(vty, "./split-horizon", NB_OP_MODIFY, value); + + return nb_cli_apply_changes(vty, "./frr-ripngd:ripng"); +} + +void cli_show_ipv6_ripng_split_horizon(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + int value; + + value = yang_dnode_get_enum(dnode, NULL); + switch (value) { + case RIPNG_NO_SPLIT_HORIZON: + vty_out(vty, " no ipv6 ripng split-horizon\n"); + break; + case RIPNG_SPLIT_HORIZON: + vty_out(vty, " ipv6 ripng split-horizon\n"); + break; + case RIPNG_SPLIT_HORIZON_POISONED_REVERSE: + vty_out(vty, " ipv6 ripng split-horizon poisoned-reverse\n"); + break; + } +} + +DEFPY_YANG( + ripng_ipv6_distribute_list, ripng_ipv6_distribute_list_cmd, + "ipv6 distribute-list ACCESSLIST6_NAME$name $dir [WORD$ifname]", + "IPv6\n" + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG( + ripng_ipv6_distribute_list_prefix, ripng_ipv6_distribute_list_prefix_cmd, + "ipv6 distribute-list prefix PREFIXLIST6_NAME$name $dir [WORD$ifname]", + "IPv6\n" + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL); */ + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, name); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_ripng_ipv6_distribute_list, + no_ripng_ipv6_distribute_list_cmd, + "no ipv6 distribute-list [ACCESSLIST6_NAME$name] $dir [WORD$ifname]", + NO_STR + "IPv6\n" + "Filter networks in routing updates\n" + "Access-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/access-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG(no_ripng_ipv6_distribute_list_prefix, + no_ripng_ipv6_distribute_list_prefix_cmd, + "no ipv6 distribute-list prefix [PREFIXLIST6_NAME$name] $dir [WORD$ifname]", + NO_STR + "IPv6\n" + "Filter networks in routing updates\n" + "Specify a prefix list\n" + "Prefix-list name\n" + "Filter incoming routing updates\n" + "Filter outgoing routing updates\n" + "Interface name\n") +{ + const struct lyd_node *value_node; + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), + "./distribute-list[interface='%s']/%s/prefix-list", + ifname ? ifname : "", dir); + /* + * See if the user has specified specific list so check it exists. + * + * NOTE: Other FRR CLI commands do not do this sort of verification and + * there may be an official decision not to. + */ + if (name) { + value_node = yang_dnode_getf(vty->candidate_config->dnode, "%s/%s", + VTY_CURR_XPATH, xpath); + if (!value_node || strcmp(name, lyd_get_value(value_node))) { + vty_out(vty, "distribute list doesn't exist\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + return nb_cli_apply_changes(vty, NULL); +} + +/* + * XPath: /frr-ripngd:clear-ripng-route + */ +DEFPY_YANG (clear_ipv6_rip, + clear_ipv6_rip_cmd, + "clear ipv6 ripng [vrf WORD]", + CLEAR_STR + IPV6_STR + "Clear IPv6 RIP database\n" + VRF_CMD_HELP_STR) +{ + if (vrf) + nb_cli_rpc_enqueue(vty, "vrf", vrf); + + return nb_cli_rpc(vty, "/frr-ripngd:clear-ripng-route", NULL); +} + +/* RIPng node structure. */ +static struct cmd_node cmd_ripng_node = { + .name = "ripng", + .node = RIPNG_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +void ripng_cli_init(void) +{ + /* Install RIPNG_NODE. */ + install_node(&cmd_ripng_node); + install_default(RIPNG_NODE); + + install_element(CONFIG_NODE, &router_ripng_cmd); + install_element(CONFIG_NODE, &no_router_ripng_cmd); + + install_element(RIPNG_NODE, &ripng_ipv6_distribute_list_cmd); + install_element(RIPNG_NODE, &ripng_ipv6_distribute_list_prefix_cmd); + install_element(RIPNG_NODE, &no_ripng_ipv6_distribute_list_cmd); + install_element(RIPNG_NODE, &no_ripng_ipv6_distribute_list_prefix_cmd); + + install_element(RIPNG_NODE, &ripng_allow_ecmp_cmd); + install_element(RIPNG_NODE, &no_ripng_allow_ecmp_cmd); + install_element(RIPNG_NODE, &ripng_default_information_originate_cmd); + install_element(RIPNG_NODE, &ripng_default_metric_cmd); + install_element(RIPNG_NODE, &no_ripng_default_metric_cmd); + install_element(RIPNG_NODE, &ripng_network_prefix_cmd); + install_element(RIPNG_NODE, &ripng_network_if_cmd); + install_element(RIPNG_NODE, &ripng_offset_list_cmd); + install_element(RIPNG_NODE, &ripng_passive_interface_cmd); + install_element(RIPNG_NODE, &ripng_redistribute_cmd); + install_element(RIPNG_NODE, &ripng_route_cmd); + install_element(RIPNG_NODE, &ripng_aggregate_address_cmd); + install_element(RIPNG_NODE, &ripng_timers_cmd); + install_element(RIPNG_NODE, &no_ripng_timers_cmd); + + install_element(INTERFACE_NODE, &ipv6_ripng_split_horizon_cmd); + + install_element(ENABLE_NODE, &clear_ipv6_rip_cmd); + + if_rmap_init(RIPNG_NODE); +} + +/* clang-format off */ +const struct frr_yang_module_info frr_ripngd_cli_info = { + .name = "frr-ripngd", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-ripngd:ripngd/instance", + .cbs.cli_show = cli_show_router_ripng, + .cbs.cli_show_end = cli_show_end_router_ripng, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/allow-ecmp", + .cbs.cli_show = cli_show_ripng_allow_ecmp, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/default-information-originate", + .cbs.cli_show = cli_show_ripng_default_information_originate, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/default-metric", + .cbs.cli_show = cli_show_ripng_default_metric, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/network", + .cbs.cli_show = cli_show_ripng_network_prefix, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/interface", + .cbs.cli_show = cli_show_ripng_network_interface, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/offset-list", + .cbs.cli_show = cli_show_ripng_offset_list, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/passive-interface", + .cbs.cli_show = cli_show_ripng_passive_interface, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/in/access-list", + .cbs.cli_show = group_distribute_list_ipv6_cli_show, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/out/access-list", + .cbs.cli_show = group_distribute_list_ipv6_cli_show, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/in/prefix-list", + .cbs.cli_show = group_distribute_list_ipv6_cli_show, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/out/prefix-list", + .cbs.cli_show = group_distribute_list_ipv6_cli_show, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/redistribute", + .cbs.cli_show = cli_show_ripng_redistribute, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/if-route-maps/if-route-map", + .cbs.cli_show = cli_show_if_route_map, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/static-route", + .cbs.cli_show = cli_show_ripng_route, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/aggregate-address", + .cbs.cli_show = cli_show_ripng_aggregate_address, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/timers", + .cbs.cli_show = cli_show_ripng_timers, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripngd:ripng/split-horizon", + .cbs = { + .cli_show = cli_show_ipv6_ripng_split_horizon, + }, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ripngd/ripng_debug.c b/ripngd/ripng_debug.c new file mode 100644 index 0000000..5ddd7ac --- /dev/null +++ b/ripngd/ripng_debug.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPng debug output routines + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#include +#include "command.h" +#include "ripngd/ripng_debug.h" + +/* For debug statement. */ +unsigned long ripng_debug_event = 0; +unsigned long ripng_debug_packet = 0; +unsigned long ripng_debug_zebra = 0; + +DEFUN_NOSH (show_debugging_ripng, + show_debugging_ripng_cmd, + "show debugging [ripng]", + SHOW_STR + DEBUG_STR + "RIPng configuration\n") +{ + vty_out(vty, "RIPng debugging status:\n"); + + if (IS_RIPNG_DEBUG_EVENT) + vty_out(vty, " RIPng event debugging is on\n"); + + if (IS_RIPNG_DEBUG_PACKET) { + if (IS_RIPNG_DEBUG_SEND && IS_RIPNG_DEBUG_RECV) { + vty_out(vty, " RIPng packet debugging is on\n"); + } else { + if (IS_RIPNG_DEBUG_SEND) + vty_out(vty, + " RIPng packet send debugging is on\n"); + else + vty_out(vty, + " RIPng packet receive debugging is on\n"); + } + } + + if (IS_RIPNG_DEBUG_ZEBRA) + vty_out(vty, " RIPng zebra debugging is on\n"); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFUN (debug_ripng_events, + debug_ripng_events_cmd, + "debug ripng events", + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng events\n") +{ + ripng_debug_event = RIPNG_DEBUG_EVENT; + return CMD_SUCCESS; +} + +DEFUN (debug_ripng_packet, + debug_ripng_packet_cmd, + "debug ripng packet", + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng packet\n") +{ + ripng_debug_packet = RIPNG_DEBUG_PACKET; + ripng_debug_packet |= RIPNG_DEBUG_SEND; + ripng_debug_packet |= RIPNG_DEBUG_RECV; + return CMD_SUCCESS; +} + +DEFUN (debug_ripng_packet_direct, + debug_ripng_packet_direct_cmd, + "debug ripng packet ", + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng packet\n" + "Debug option set for receive packet\n" + "Debug option set for send packet\n") +{ + int idx_recv_send = 3; + ripng_debug_packet |= RIPNG_DEBUG_PACKET; + if (strcmp("send", argv[idx_recv_send]->text) == 0) + ripng_debug_packet |= RIPNG_DEBUG_SEND; + if (strcmp("recv", argv[idx_recv_send]->text) == 0) + ripng_debug_packet |= RIPNG_DEBUG_RECV; + + return CMD_SUCCESS; +} + +DEFUN (debug_ripng_zebra, + debug_ripng_zebra_cmd, + "debug ripng zebra", + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng and zebra communication\n") +{ + ripng_debug_zebra = RIPNG_DEBUG_ZEBRA; + return CMD_SUCCESS; +} + +DEFUN (no_debug_ripng_events, + no_debug_ripng_events_cmd, + "no debug ripng events", + NO_STR + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng events\n") +{ + ripng_debug_event = 0; + return CMD_SUCCESS; +} + +DEFUN (no_debug_ripng_packet, + no_debug_ripng_packet_cmd, + "no debug ripng packet", + NO_STR + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng packet\n") +{ + ripng_debug_packet = 0; + return CMD_SUCCESS; +} + +DEFUN (no_debug_ripng_packet_direct, + no_debug_ripng_packet_direct_cmd, + "no debug ripng packet ", + NO_STR + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng packet\n" + "Debug option set for receive packet\n" + "Debug option set for send packet\n") +{ + int idx_recv_send = 4; + if (strcmp("send", argv[idx_recv_send]->text) == 0) { + if (IS_RIPNG_DEBUG_RECV) + ripng_debug_packet &= ~RIPNG_DEBUG_SEND; + else + ripng_debug_packet = 0; + } else if (strcmp("recv", argv[idx_recv_send]->text) == 0) { + if (IS_RIPNG_DEBUG_SEND) + ripng_debug_packet &= ~RIPNG_DEBUG_RECV; + else + ripng_debug_packet = 0; + } + return CMD_SUCCESS; +} + +DEFUN (no_debug_ripng_zebra, + no_debug_ripng_zebra_cmd, + "no debug ripng zebra", + NO_STR + DEBUG_STR + "RIPng configuration\n" + "Debug option set for ripng and zebra communication\n") +{ + ripng_debug_zebra = 0; + return CMD_SUCCESS; +} + +static int config_write_debug(struct vty *vty); +/* Debug node. */ +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = config_write_debug, +}; + +static int config_write_debug(struct vty *vty) +{ + int write = 0; + + if (IS_RIPNG_DEBUG_EVENT) { + vty_out(vty, "debug ripng events\n"); + write++; + } + if (IS_RIPNG_DEBUG_PACKET) { + if (IS_RIPNG_DEBUG_SEND && IS_RIPNG_DEBUG_RECV) { + vty_out(vty, "debug ripng packet\n"); + write++; + } else { + if (IS_RIPNG_DEBUG_SEND) + vty_out(vty, "debug ripng packet send\n"); + else + vty_out(vty, "debug ripng packet recv\n"); + write++; + } + } + if (IS_RIPNG_DEBUG_ZEBRA) { + vty_out(vty, "debug ripng zebra\n"); + write++; + } + return write; +} + +void ripng_debug_init(void) +{ + ripng_debug_event = 0; + ripng_debug_packet = 0; + ripng_debug_zebra = 0; + + install_node(&debug_node); + + install_element(ENABLE_NODE, &show_debugging_ripng_cmd); + + install_element(ENABLE_NODE, &debug_ripng_events_cmd); + install_element(ENABLE_NODE, &debug_ripng_packet_cmd); + install_element(ENABLE_NODE, &debug_ripng_packet_direct_cmd); + install_element(ENABLE_NODE, &debug_ripng_zebra_cmd); + install_element(ENABLE_NODE, &no_debug_ripng_events_cmd); + install_element(ENABLE_NODE, &no_debug_ripng_packet_cmd); + install_element(ENABLE_NODE, &no_debug_ripng_packet_direct_cmd); + install_element(ENABLE_NODE, &no_debug_ripng_zebra_cmd); + + install_element(CONFIG_NODE, &debug_ripng_events_cmd); + install_element(CONFIG_NODE, &debug_ripng_packet_cmd); + install_element(CONFIG_NODE, &debug_ripng_packet_direct_cmd); + install_element(CONFIG_NODE, &debug_ripng_zebra_cmd); + install_element(CONFIG_NODE, &no_debug_ripng_events_cmd); + install_element(CONFIG_NODE, &no_debug_ripng_packet_cmd); + install_element(CONFIG_NODE, &no_debug_ripng_packet_direct_cmd); + install_element(CONFIG_NODE, &no_debug_ripng_zebra_cmd); +} diff --git a/ripngd/ripng_debug.h b/ripngd/ripng_debug.h new file mode 100644 index 0000000..7af9206 --- /dev/null +++ b/ripngd/ripng_debug.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPng debug output routines + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_RIPNG_DEBUG_H +#define _ZEBRA_RIPNG_DEBUG_H + +/* Debug flags. */ +#define RIPNG_DEBUG_EVENT 0x01 + +#define RIPNG_DEBUG_PACKET 0x01 +#define RIPNG_DEBUG_SEND 0x20 +#define RIPNG_DEBUG_RECV 0x40 + +#define RIPNG_DEBUG_ZEBRA 0x01 + +/* Debug related macro. */ +#define IS_RIPNG_DEBUG_EVENT (ripng_debug_event & RIPNG_DEBUG_EVENT) + +#define IS_RIPNG_DEBUG_PACKET (ripng_debug_packet & RIPNG_DEBUG_PACKET) +#define IS_RIPNG_DEBUG_SEND (ripng_debug_packet & RIPNG_DEBUG_SEND) +#define IS_RIPNG_DEBUG_RECV (ripng_debug_packet & RIPNG_DEBUG_RECV) + +#define IS_RIPNG_DEBUG_ZEBRA (ripng_debug_zebra & RIPNG_DEBUG_ZEBRA) + +extern unsigned long ripng_debug_event; +extern unsigned long ripng_debug_packet; +extern unsigned long ripng_debug_zebra; + +extern void ripng_debug_init(void); + +#endif /* _ZEBRA_RIPNG_DEBUG_H */ diff --git a/ripngd/ripng_interface.c b/ripngd/ripng_interface.c new file mode 100644 index 0000000..9ef9f89 --- /dev/null +++ b/ripngd/ripng_interface.c @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface related function for RIPng. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#include + +#include "linklist.h" +#include "if.h" +#include "prefix.h" +#include "memory.h" +#include "network.h" +#include "filter.h" +#include "log.h" +#include "stream.h" +#include "zclient.h" +#include "command.h" +#include "agg_table.h" +#include "frrevent.h" +#include "privs.h" +#include "vrf.h" +#include "lib_errors.h" +#include "northbound_cli.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_debug.h" + +/* If RFC2133 definition is used. */ +#ifndef IPV6_JOIN_GROUP +#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP +#endif +#ifndef IPV6_LEAVE_GROUP +#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP +#endif + +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_IF, "ripng interface"); + +/* Static utility function. */ +static void ripng_enable_apply(struct interface *); +static void ripng_passive_interface_apply(struct interface *); +static int ripng_enable_if_lookup(struct ripng *ripng, const char *ifname); +static int ripng_enable_network_lookup2(struct connected *); +static void ripng_enable_apply_all(struct ripng *ripng); + +/* Join to the all rip routers multicast group. */ +static int ripng_multicast_join(struct interface *ifp, int sock) +{ + int ret; + struct ipv6_mreq mreq; + int save_errno; + + if (if_is_multicast(ifp)) { + memset(&mreq, 0, sizeof(mreq)); + inet_pton(AF_INET6, RIPNG_GROUP, &mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = ifp->ifindex; + + /* + * NetBSD 1.6.2 requires root to join groups on gif(4). + * While this is bogus, privs are available and easy to use + * for this call as a workaround. + */ + frr_with_privs(&ripngd_privs) { + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, + (char *)&mreq, sizeof(mreq)); + save_errno = errno; + + } + + if (ret < 0 && save_errno == EADDRINUSE) { + /* + * Group is already joined. This occurs due to sloppy + * group + * management, in particular declining to leave the + * group on + * an interface that has just gone down. + */ + zlog_warn("ripng join on %s EADDRINUSE (ignoring)", + ifp->name); + return 0; /* not an error */ + } + + if (ret < 0) + zlog_warn("can't setsockopt IPV6_JOIN_GROUP: %s", + safe_strerror(save_errno)); + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug( + "RIPng %s join to all-rip-routers multicast group", + ifp->name); + + if (ret < 0) + return -1; + } + return 0; +} + +/* Leave from the all rip routers multicast group. */ +static int ripng_multicast_leave(struct interface *ifp, int sock) +{ + int ret; + struct ipv6_mreq mreq; + + if (if_is_multicast(ifp)) { + memset(&mreq, 0, sizeof(mreq)); + inet_pton(AF_INET6, RIPNG_GROUP, &mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = ifp->ifindex; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + (char *)&mreq, sizeof(mreq)); + if (ret < 0) + zlog_warn("can't setsockopt IPV6_LEAVE_GROUP: %s", + safe_strerror(errno)); + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug( + "RIPng %s leave from all-rip-routers multicast group", + ifp->name); + + if (ret < 0) + return -1; + } + + return 0; +} + +/* How many link local IPv6 address could be used on the interface ? */ +static int ripng_if_ipv6_lladdress_check(struct interface *ifp) +{ + struct connected *connected; + int count = 0; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + p = connected->address; + + if ((p->family == AF_INET6) && + IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6)) + count++; + } + + return count; +} + +static int ripng_if_down(struct interface *ifp) +{ + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_interface *ri; + struct ripng *ripng; + struct list *list = NULL; + struct listnode *listnode = NULL, *nextnode = NULL; + + ri = ifp->info; + + EVENT_OFF(ri->t_wakeup); + + ripng = ri->ripng; + + if (ripng) + for (rp = agg_route_top(ripng->table); rp; + rp = agg_route_next(rp)) + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS(list, listnode, nextnode, + rinfo)) + if (rinfo->ifindex == ifp->ifindex) + ripng_ecmp_delete(ripng, rinfo); + + + if (ri->running) { + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("turn off %s", ifp->name); + + /* Leave from multicast group. */ + if (ripng) + ripng_multicast_leave(ifp, ripng->sock); + + ri->running = 0; + } + + return 0; +} + +/* Interface link up message processing. */ +static int ripng_ifp_up(struct interface *ifp) +{ + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug( + "interface up %s vrf %s(%u) index %d flags %llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu6); + + ripng_interface_sync(ifp); + + /* Check if this interface is RIPng enabled or not. */ + ripng_enable_apply(ifp); + + /* Check for a passive interface. */ + ripng_passive_interface_apply(ifp); + + /* Apply distribute list to the all interface. */ + ripng_distribute_update_interface(ifp); + + return 0; +} + +/* Interface link down message processing. */ +static int ripng_ifp_down(struct interface *ifp) +{ + ripng_interface_sync(ifp); + ripng_if_down(ifp); + + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug( + "interface down %s vrf %s(%u) index %d flags %#llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu6); + + return 0; +} + +/* Interface addition message from zebra. */ +static int ripng_ifp_create(struct interface *ifp) +{ + ripng_interface_sync(ifp); + + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug( + "RIPng interface add %s vrf %s(%u) index %d flags %#llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu6); + + /* Check is this interface is RIP enabled or not.*/ + ripng_enable_apply(ifp); + + /* Apply distribute list to the interface. */ + ripng_distribute_update_interface(ifp); + + /* Check interface routemap. */ + ripng_if_rmap_update_interface(ifp); + + return 0; +} + +static int ripng_ifp_destroy(struct interface *ifp) +{ + ripng_interface_sync(ifp); + if (if_is_up(ifp)) { + ripng_if_down(ifp); + } + + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug( + "interface delete %s vrf %s(%u) index %d flags %#llx metric %d mtu %d", + ifp->name, ifp->vrf->name, ifp->vrf->vrf_id, + ifp->ifindex, (unsigned long long)ifp->flags, + ifp->metric, ifp->mtu6); + + return 0; +} + +void ripng_interface_clean(struct ripng *ripng) +{ + struct interface *ifp; + struct ripng_interface *ri; + + FOR_ALL_INTERFACES (ripng->vrf, ifp) { + ri = ifp->info; + + ri->enable_network = 0; + ri->enable_interface = 0; + ri->running = 0; + + EVENT_OFF(ri->t_wakeup); + } +} + +static void ripng_apply_address_add(struct connected *ifc) +{ + struct ripng_interface *ri = ifc->ifp->info; + struct ripng *ripng = ri->ripng; + struct prefix_ipv6 address; + struct prefix *p; + + if (!ripng) + return; + + if (!if_is_up(ifc->ifp)) + return; + + p = ifc->address; + + memset(&address, 0, sizeof(address)); + address.family = p->family; + address.prefix = p->u.prefix6; + address.prefixlen = p->prefixlen; + apply_mask_ipv6(&address); + + /* Check if this interface is RIP enabled or not + or Check if this address's prefix is RIP enabled */ + if ((ripng_enable_if_lookup(ripng, ifc->ifp->name) >= 0) + || (ripng_enable_network_lookup2(ifc) >= 0)) + ripng_redistribute_add(ripng, ZEBRA_ROUTE_CONNECT, + RIPNG_ROUTE_INTERFACE, &address, + ifc->ifp->ifindex, NULL, 0); +} + +int ripng_interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + struct prefix *p; + + c = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, + zclient->ibuf, vrf_id); + + if (c == NULL) + return 0; + + p = c->address; + + if (p->family == AF_INET6) { + struct ripng_interface *ri = c->ifp->info; + + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug("RIPng connected address %pFX add", p); + + /* Check is this prefix needs to be redistributed. */ + ripng_apply_address_add(c); + + /* Let's try once again whether the interface could be activated + */ + if (!ri->running) { + /* Check if this interface is RIP enabled or not.*/ + ripng_enable_apply(c->ifp); + + /* Apply distribute list to the interface. */ + ripng_distribute_update_interface(c->ifp); + + /* Check interface routemap. */ + ripng_if_rmap_update_interface(c->ifp); + } + } + + return 0; +} + +static void ripng_apply_address_del(struct connected *ifc) +{ + struct ripng_interface *ri = ifc->ifp->info; + struct ripng *ripng = ri->ripng; + struct prefix_ipv6 address; + struct prefix *p; + + if (!ripng) + return; + + if (!if_is_up(ifc->ifp)) + return; + + p = ifc->address; + + memset(&address, 0, sizeof(address)); + address.family = p->family; + address.prefix = p->u.prefix6; + address.prefixlen = p->prefixlen; + apply_mask_ipv6(&address); + + ripng_redistribute_delete(ripng, ZEBRA_ROUTE_CONNECT, + RIPNG_ROUTE_INTERFACE, &address, + ifc->ifp->ifindex); +} + +int ripng_interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *ifc; + struct prefix *p; + + ifc = zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, + zclient->ibuf, vrf_id); + + if (ifc) { + p = ifc->address; + + if (p->family == AF_INET6) { + if (IS_RIPNG_DEBUG_ZEBRA) + zlog_debug( + "RIPng connected address %pFX delete", + p); + + /* Check whether this prefix needs to be removed. */ + ripng_apply_address_del(ifc); + } + connected_free(&ifc); + } + + return 0; +} + +/* Lookup RIPng enable network. */ +/* Check whether the interface has at least a connected prefix that + * is within the ripng->enable_network table. */ +static int ripng_enable_network_lookup_if(struct interface *ifp) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + struct connected *connected; + struct prefix_ipv6 address; + + if (!ripng) + return -1; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + struct agg_node *n; + + p = connected->address; + + if (p->family == AF_INET6) { + address.family = AF_INET6; + address.prefix = p->u.prefix6; + address.prefixlen = IPV6_MAX_BITLEN; + + n = agg_node_match(ripng->enable_network, + (struct prefix *)&address); + if (n) { + agg_unlock_node(n); + return 1; + } + } + } + return -1; +} + +/* Check whether connected is within the ripng->enable_network table. */ +static int ripng_enable_network_lookup2(struct connected *connected) +{ + struct ripng_interface *ri = connected->ifp->info; + struct ripng *ripng = ri->ripng; + struct prefix_ipv6 address; + struct prefix *p; + + if (!ripng) + return -1; + + p = connected->address; + + if (p->family == AF_INET6) { + struct agg_node *node; + + address.family = p->family; + address.prefix = p->u.prefix6; + address.prefixlen = IPV6_MAX_BITLEN; + + /* LPM on p->family, p->u.prefix6/IPV6_MAX_BITLEN within + * ripng->enable_network */ + node = agg_node_match(ripng->enable_network, + (struct prefix *)&address); + + if (node) { + agg_unlock_node(node); + return 1; + } + } + + return -1; +} + +/* Add RIPng enable network. */ +int ripng_enable_network_add(struct ripng *ripng, struct prefix *p) +{ + struct agg_node *node; + + node = agg_node_get(ripng->enable_network, p); + + if (node->info) { + agg_unlock_node(node); + return NB_ERR_INCONSISTENCY; + } else + node->info = (void *)1; + + /* XXX: One should find a better solution than a generic one */ + ripng_enable_apply_all(ripng); + + return NB_OK; +} + +/* Delete RIPng enable network. */ +int ripng_enable_network_delete(struct ripng *ripng, struct prefix *p) +{ + struct agg_node *node; + + node = agg_node_lookup(ripng->enable_network, p); + if (node) { + node->info = NULL; + + /* Unlock info lock. */ + agg_unlock_node(node); + + /* Unlock lookup lock. */ + agg_unlock_node(node); + + return NB_OK; + } + + return NB_ERR_INCONSISTENCY; +} + +/* Lookup function. */ +static int ripng_enable_if_lookup(struct ripng *ripng, const char *ifname) +{ + unsigned int i; + char *str; + + if (!ripng) + return -1; + + for (i = 0; i < vector_active(ripng->enable_if); i++) + if ((str = vector_slot(ripng->enable_if, i)) != NULL) + if (strcmp(str, ifname) == 0) + return i; + return -1; +} + +int ripng_enable_if_add(struct ripng *ripng, const char *ifname) +{ + int ret; + + ret = ripng_enable_if_lookup(ripng, ifname); + if (ret >= 0) + return NB_ERR_INCONSISTENCY; + + vector_set(ripng->enable_if, strdup(ifname)); + + ripng_enable_apply_all(ripng); + + return NB_OK; +} + +int ripng_enable_if_delete(struct ripng *ripng, const char *ifname) +{ + int index; + char *str; + + index = ripng_enable_if_lookup(ripng, ifname); + if (index < 0) + return NB_ERR_INCONSISTENCY; + + str = vector_slot(ripng->enable_if, index); + free(str); + vector_unset(ripng->enable_if, index); + + ripng_enable_apply_all(ripng); + + return NB_OK; +} + +/* Wake up interface. */ +static void ripng_interface_wakeup(struct event *t) +{ + struct interface *ifp; + struct ripng_interface *ri; + + /* Get interface. */ + ifp = EVENT_ARG(t); + + ri = ifp->info; + + /* Join to multicast group. */ + if (ripng_multicast_join(ifp, ri->ripng->sock) < 0) { + flog_err_sys(EC_LIB_SOCKET, + "multicast join failed, interface %s not running", + ifp->name); + return; + } + + /* Set running flag. */ + ri->running = 1; + + /* Send RIP request to the interface. */ + ripng_request(ifp); +} + +static void ripng_connect_set(struct interface *ifp, int set) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + struct connected *connected; + struct prefix_ipv6 address; + + frr_each (if_connected, ifp->connected, connected) { + struct prefix *p; + p = connected->address; + + if (p->family != AF_INET6) + continue; + + address.family = AF_INET6; + address.prefix = p->u.prefix6; + address.prefixlen = p->prefixlen; + apply_mask_ipv6(&address); + + if (set) { + /* Check once more whether this prefix is within a + * "network IF_OR_PREF" one */ + if ((ripng_enable_if_lookup( + ripng, connected->ifp->name) >= 0) || + (ripng_enable_network_lookup2(connected) >= 0)) + ripng_redistribute_add( + ripng, ZEBRA_ROUTE_CONNECT, + RIPNG_ROUTE_INTERFACE, &address, + connected->ifp->ifindex, NULL, 0); + } else { + ripng_redistribute_delete(ripng, ZEBRA_ROUTE_CONNECT, + RIPNG_ROUTE_INTERFACE, + &address, + connected->ifp->ifindex); + if (ripng_redistribute_check(ripng, + ZEBRA_ROUTE_CONNECT)) + ripng_redistribute_add( + ripng, ZEBRA_ROUTE_CONNECT, + RIPNG_ROUTE_REDISTRIBUTE, &address, + connected->ifp->ifindex, NULL, 0); + } + } +} + +/* Check RIPng is enabed on this interface. */ +void ripng_enable_apply(struct interface *ifp) +{ + int ret; + struct ripng_interface *ri = NULL; + + /* Check interface. */ + if (!if_is_up(ifp)) + return; + + ri = ifp->info; + + /* Is this interface a candidate for RIPng ? */ + ret = ripng_enable_network_lookup_if(ifp); + + /* If the interface is matched. */ + if (ret > 0) + ri->enable_network = 1; + else + ri->enable_network = 0; + + /* Check interface name configuration. */ + ret = ripng_enable_if_lookup(ri->ripng, ifp->name); + if (ret >= 0) + ri->enable_interface = 1; + else + ri->enable_interface = 0; + + /* any candidate interface MUST have a link-local IPv6 address */ + if ((!ripng_if_ipv6_lladdress_check(ifp)) + && (ri->enable_network || ri->enable_interface)) { + ri->enable_network = 0; + ri->enable_interface = 0; + zlog_warn("Interface %s does not have any link-local address", + ifp->name); + } + + /* Update running status of the interface. */ + if (ri->enable_network || ri->enable_interface) { + zlog_info("RIPng INTERFACE ON %s", ifp->name); + + /* Add interface wake up thread. */ + event_add_timer(master, ripng_interface_wakeup, ifp, 1, + &ri->t_wakeup); + + ripng_connect_set(ifp, 1); + } else { + if (ri->running) { + /* Might as well clean up the route table as well + * ripng_if_down sets to 0 ri->running, and displays + *"turn off %s" + **/ + ripng_if_down(ifp); + + ripng_connect_set(ifp, 0); + } + } +} + +/* Set distribute list to all interfaces. */ +static void ripng_enable_apply_all(struct ripng *ripng) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (ripng->vrf, ifp) + ripng_enable_apply(ifp); +} + +/* Clear all network and neighbor configuration */ +void ripng_clean_network(struct ripng *ripng) +{ + unsigned int i; + char *str; + struct agg_node *rn; + + /* ripng->enable_network */ + for (rn = agg_route_top(ripng->enable_network); rn; + rn = agg_route_next(rn)) + if (rn->info) { + rn->info = NULL; + agg_unlock_node(rn); + } + + /* ripng->enable_if */ + for (i = 0; i < vector_active(ripng->enable_if); i++) + if ((str = vector_slot(ripng->enable_if, i)) != NULL) { + free(str); + vector_slot(ripng->enable_if, i) = NULL; + } +} + +/* Utility function for looking up passive interface settings. */ +static int ripng_passive_interface_lookup(struct ripng *ripng, + const char *ifname) +{ + unsigned int i; + char *str; + + for (i = 0; i < vector_active(ripng->passive_interface); i++) + if ((str = vector_slot(ripng->passive_interface, i)) != NULL) + if (strcmp(str, ifname) == 0) + return i; + return -1; +} + +void ripng_passive_interface_apply(struct interface *ifp) +{ + int ret; + struct ripng_interface *ri; + struct ripng *ripng; + + ri = ifp->info; + ripng = ri->ripng; + if (!ripng) + return; + + ret = ripng_passive_interface_lookup(ripng, ifp->name); + if (ret < 0) + ri->passive = 0; + else + ri->passive = 1; +} + +static void ripng_passive_interface_apply_all(struct ripng *ripng) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (ripng->vrf, ifp) + ripng_passive_interface_apply(ifp); +} + +/* Passive interface. */ +int ripng_passive_interface_set(struct ripng *ripng, const char *ifname) +{ + if (ripng_passive_interface_lookup(ripng, ifname) >= 0) + return NB_ERR_INCONSISTENCY; + + vector_set(ripng->passive_interface, strdup(ifname)); + + ripng_passive_interface_apply_all(ripng); + + return NB_OK; +} + +int ripng_passive_interface_unset(struct ripng *ripng, const char *ifname) +{ + int i; + char *str; + + i = ripng_passive_interface_lookup(ripng, ifname); + if (i < 0) + return NB_ERR_INCONSISTENCY; + + str = vector_slot(ripng->passive_interface, i); + free(str); + vector_unset(ripng->passive_interface, i); + + ripng_passive_interface_apply_all(ripng); + + return NB_OK; +} + +/* Free all configured RIP passive-interface settings. */ +void ripng_passive_interface_clean(struct ripng *ripng) +{ + unsigned int i; + char *str; + + for (i = 0; i < vector_active(ripng->passive_interface); i++) + if ((str = vector_slot(ripng->passive_interface, i)) != NULL) { + free(str); + vector_slot(ripng->passive_interface, i) = NULL; + } + ripng_passive_interface_apply_all(ripng); +} + +/* Write RIPng enable network and interface to the vty. */ +int ripng_network_write(struct vty *vty, struct ripng *ripng) +{ + unsigned int i; + const char *ifname; + struct agg_node *node; + + /* Write enable network. */ + for (node = agg_route_top(ripng->enable_network); node; + node = agg_route_next(node)) + if (node->info) + vty_out(vty, " %pRN\n", node); + + /* Write enable interface. */ + for (i = 0; i < vector_active(ripng->enable_if); i++) + if ((ifname = vector_slot(ripng->enable_if, i)) != NULL) + vty_out(vty, " %s\n", ifname); + + return 0; +} + +static struct ripng_interface *ri_new(void) +{ + struct ripng_interface *ri; + + ri = XCALLOC(MTYPE_RIPNG_IF, sizeof(struct ripng_interface)); + + /* Set default split-horizon behavior. If the interface is Frame + Relay or SMDS is enabled, the default value for split-horizon is + off. But currently Zebra does detect Frame Relay or SMDS + interface. So all interface is set to split horizon. */ + ri->split_horizon = + yang_get_default_enum("%s/split-horizon", RIPNG_IFACE); + + return ri; +} + +void ripng_interface_sync(struct interface *ifp) +{ + struct ripng_interface *ri; + + ri = ifp->info; + if (ri) + ri->ripng = ifp->vrf->info; +} + +static int ripng_if_new_hook(struct interface *ifp) +{ + ifp->info = ri_new(); + ripng_interface_sync(ifp); + + return 0; +} + +/* Called when interface structure deleted. */ +static int ripng_if_delete_hook(struct interface *ifp) +{ + XFREE(MTYPE_RIPNG_IF, ifp->info); + return 0; +} + +/* Initialization of interface. */ +void ripng_if_init(void) +{ + /* Interface initialize. */ + hook_register_prio(if_add, 0, ripng_if_new_hook); + hook_register_prio(if_del, 0, ripng_if_delete_hook); + + /* Install interface node. */ + hook_register_prio(if_real, 0, ripng_ifp_create); + hook_register_prio(if_up, 0, ripng_ifp_up); + hook_register_prio(if_down, 0, ripng_ifp_down); + hook_register_prio(if_unreal, 0, ripng_ifp_destroy); +} diff --git a/ripngd/ripng_main.c b/ripngd/ripng_main.c new file mode 100644 index 0000000..ada9ad4 --- /dev/null +++ b/ripngd/ripng_main.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPngd main routine. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include +#include "getopt.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "frrevent.h" +#include "log.h" +#include "prefix.h" +#include "if.h" +#include "privs.h" +#include "sigevent.h" +#include "vrf.h" +#include "if_rmap.h" +#include "libfrr.h" +#include "routemap.h" +#include "mgmt_be_client.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nb.h" + +/* RIPngd options. */ +struct option longopts[] = {{0}}; + +/* ripngd privileges */ +zebra_capabilities_t _caps_p[] = {ZCAP_NET_RAW, ZCAP_BIND, ZCAP_SYS_ADMIN}; + +uint32_t zebra_ecmp_count = MULTIPATH_NUM; + +struct zebra_privs_t ripngd_privs = { +#if defined(FRR_USER) + .user = FRR_USER, +#endif +#if defined FRR_GROUP + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + + +/* Master of threads. */ +struct event_loop *master; + +struct mgmt_be_client *mgmt_be_client; + +static struct frr_daemon_info ripngd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); + + /* Reload config file. */ + vty_read_config(NULL, ripngd_di.config_file, config_default); +} + +/* SIGINT handler. */ +static void sigint(void) +{ + struct vrf *vrf; + + zlog_notice("Terminating on signal"); + + nb_oper_cancel_all_walks(); + mgmt_be_client_destroy(mgmt_be_client); + mgmt_be_client = NULL; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (!vrf->info) + continue; + + ripng_clean(vrf->info); + } + + ripng_vrf_terminate(); + if_rmap_terminate(); + ripng_zebra_stop(); + + route_map_finish(); + + frr_fini(); + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t ripng_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const ripngd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_ripngd_info, + &frr_route_map_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(ripngd, RIPNG, + .vty_port = RIPNG_VTY_PORT, + .proghelp = "Implementation of the RIPng routing protocol.", + + .signals = ripng_signals, + .n_signals = array_size(ripng_signals), + + .privs = &ripngd_privs, + + .yang_modules = ripngd_yang_modules, + .n_yang_modules = array_size(ripngd_yang_modules), + + /* mgmtd will load the per-daemon config file now */ + .flags = FRR_NO_SPLIT_CONFIG, +); +/* clang-format on */ + +#define DEPRECATED_OPTIONS "" + +/* RIPngd main routine. */ +int main(int argc, char **argv) +{ + frr_preinit(&ripngd_di, argc, argv); + + frr_opt_add("" DEPRECATED_OPTIONS, longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt && opt < 128 && strchr(DEPRECATED_OPTIONS, opt)) { + fprintf(stderr, + "The -%c option no longer exists.\nPlease refer to the manual.\n", + opt); + continue; + } + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + /* Library inits. */ + ripng_vrf_init(); + + /* RIPngd inits. */ + ripng_init(); + + mgmt_be_client = mgmt_be_client_create("ripngd", NULL, 0, master); + + zebra_init(master); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/ripngd/ripng_nb.c b/ripngd/ripng_nb.c new file mode 100644 index 0000000..8e20541 --- /dev/null +++ b/ripngd/ripng_nb.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "distribute.h" +#include "if_rmap.h" +#include "libfrr.h" +#include "northbound.h" + +#include "ripngd/ripng_nb.h" + +/* clang-format off */ +const struct frr_yang_module_info frr_ripngd_info = { + .name = "frr-ripngd", + .nodes = { + { + .xpath = "/frr-ripngd:ripngd/instance", + .cbs = { + .create = ripngd_instance_create, + .destroy = ripngd_instance_destroy, + .get_keys = ripngd_instance_get_keys, + .get_next = ripngd_instance_get_next, + .lookup_entry = ripngd_instance_lookup_entry, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/allow-ecmp", + .cbs = { + .modify = ripngd_instance_allow_ecmp_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/default-information-originate", + .cbs = { + .modify = ripngd_instance_default_information_originate_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/default-metric", + .cbs = { + .modify = ripngd_instance_default_metric_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/network", + .cbs = { + .create = ripngd_instance_network_create, + .destroy = ripngd_instance_network_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/interface", + .cbs = { + .create = ripngd_instance_interface_create, + .destroy = ripngd_instance_interface_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/offset-list", + .cbs = { + .create = ripngd_instance_offset_list_create, + .destroy = ripngd_instance_offset_list_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/offset-list/access-list", + .cbs = { + .modify = ripngd_instance_offset_list_access_list_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/offset-list/metric", + .cbs = { + .modify = ripngd_instance_offset_list_metric_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/passive-interface", + .cbs = { + .create = ripngd_instance_passive_interface_create, + .destroy = ripngd_instance_passive_interface_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list", + .cbs = { + .create = ripngd_instance_distribute_list_create, + .destroy = group_distribute_list_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/in/access-list", + .cbs = { + .modify = group_distribute_list_ipv6_modify, + .destroy = group_distribute_list_ipv6_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/out/access-list", + .cbs = { + .modify = group_distribute_list_ipv6_modify, + .destroy = group_distribute_list_ipv6_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/in/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv6_modify, + .destroy = group_distribute_list_ipv6_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/distribute-list/out/prefix-list", + .cbs = { + .modify = group_distribute_list_ipv6_modify, + .destroy = group_distribute_list_ipv6_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/redistribute", + .cbs = { + .apply_finish = ripngd_instance_redistribute_apply_finish, + .create = ripngd_instance_redistribute_create, + .destroy = ripngd_instance_redistribute_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/redistribute/route-map", + .cbs = { + .destroy = ripngd_instance_redistribute_route_map_destroy, + .modify = ripngd_instance_redistribute_route_map_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/redistribute/metric", + .cbs = { + .destroy = ripngd_instance_redistribute_metric_destroy, + .modify = ripngd_instance_redistribute_metric_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/if-route-maps/if-route-map", + .cbs = { + .create = ripngd_instance_if_route_maps_if_route_map_create, + .destroy = ripngd_instance_if_route_maps_if_route_map_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/if-route-maps/if-route-map/in-route-map", + .cbs = { + .modify = ripngd_instance_if_route_maps_if_route_map_in_route_map_modify, + .destroy = ripngd_instance_if_route_maps_if_route_map_in_route_map_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/if-route-maps/if-route-map/out-route-map", + .cbs = { + .modify = ripngd_instance_if_route_maps_if_route_map_out_route_map_modify, + .destroy = ripngd_instance_if_route_maps_if_route_map_out_route_map_destroy, + } + }, + { + .xpath = "/frr-ripngd:ripngd/instance/static-route", + .cbs = { + .create = ripngd_instance_static_route_create, + .destroy = ripngd_instance_static_route_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/aggregate-address", + .cbs = { + .create = ripngd_instance_aggregate_address_create, + .destroy = ripngd_instance_aggregate_address_destroy, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/timers", + .cbs = { + .apply_finish = ripngd_instance_timers_apply_finish, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/timers/flush-interval", + .cbs = { + .modify = ripngd_instance_timers_flush_interval_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/timers/holddown-interval", + .cbs = { + .modify = ripngd_instance_timers_holddown_interval_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/timers/update-interval", + .cbs = { + .modify = ripngd_instance_timers_update_interval_modify, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor", + .cbs = { + .get_keys = ripngd_instance_state_neighbors_neighbor_get_keys, + .get_next = ripngd_instance_state_neighbors_neighbor_get_next, + .lookup_entry = ripngd_instance_state_neighbors_neighbor_lookup_entry, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/address", + .cbs = { + .get_elem = ripngd_instance_state_neighbors_neighbor_address_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/last-update", + .cbs = { + .get_elem = ripngd_instance_state_neighbors_neighbor_last_update_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-packets-rcvd", + .cbs = { + .get_elem = ripngd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-routes-rcvd", + .cbs = { + .get_elem = ripngd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/routes/route", + .cbs = { + .get_keys = ripngd_instance_state_routes_route_get_keys, + .get_next = ripngd_instance_state_routes_route_get_next, + .lookup_entry = ripngd_instance_state_routes_route_lookup_entry, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/prefix", + .cbs = { + .get_elem = ripngd_instance_state_routes_route_prefix_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/next-hop", + .cbs = { + .get_elem = ripngd_instance_state_routes_route_next_hop_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/interface", + .cbs = { + .get_elem = ripngd_instance_state_routes_route_interface_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:ripngd/instance/state/routes/route/metric", + .cbs = { + .get_elem = ripngd_instance_state_routes_route_metric_get_elem, + }, + }, + { + .xpath = "/frr-ripngd:clear-ripng-route", + .cbs = { + .rpc = clear_ripng_route_rpc, + }, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripngd:ripng/split-horizon", + .cbs = { + .modify = lib_interface_ripng_split_horizon_modify, + }, + }, + { + .xpath = NULL, + }, + } +}; diff --git a/ripngd/ripng_nb.h b/ripngd/ripng_nb.h new file mode 100644 index 0000000..790a50f --- /dev/null +++ b/ripngd/ripng_nb.h @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _FRR_RIPNG_NB_H_ +#define _FRR_RIPNG_NB_H_ + +#include "northbound.h" + +extern const struct frr_yang_module_info frr_ripngd_info; +extern const struct frr_yang_module_info frr_ripngd_cli_info; + +/* Mandatory callbacks. */ +int ripngd_instance_create(struct nb_cb_create_args *args); +int ripngd_instance_destroy(struct nb_cb_destroy_args *args); +const void *ripngd_instance_get_next(struct nb_cb_get_next_args *args); +int ripngd_instance_get_keys(struct nb_cb_get_keys_args *args); +const void *ripngd_instance_lookup_entry(struct nb_cb_lookup_entry_args *args); +int ripngd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args); +int ripngd_instance_default_information_originate_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_default_metric_modify(struct nb_cb_modify_args *args); +int ripngd_instance_network_create(struct nb_cb_create_args *args); +int ripngd_instance_network_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_interface_create(struct nb_cb_create_args *args); +int ripngd_instance_interface_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_offset_list_create(struct nb_cb_create_args *args); +int ripngd_instance_offset_list_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_offset_list_access_list_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_offset_list_metric_modify(struct nb_cb_modify_args *args); +int ripngd_instance_passive_interface_create(struct nb_cb_create_args *args); +int ripngd_instance_passive_interface_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_distribute_list_create(struct nb_cb_create_args *args); +int ripngd_instance_distribute_list_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_redistribute_create(struct nb_cb_create_args *args); +int ripngd_instance_redistribute_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_redistribute_route_map_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_redistribute_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripngd_instance_redistribute_metric_modify(struct nb_cb_modify_args *args); +int ripngd_instance_redistribute_metric_destroy( + struct nb_cb_destroy_args *args); +int ripngd_instance_if_route_maps_if_route_map_create( + struct nb_cb_create_args *args); +int ripngd_instance_if_route_maps_if_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripngd_instance_if_route_maps_if_route_map_in_route_map_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_if_route_maps_if_route_map_in_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripngd_instance_if_route_maps_if_route_map_out_route_map_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_if_route_maps_if_route_map_out_route_map_destroy( + struct nb_cb_destroy_args *args); +int ripngd_instance_static_route_create(struct nb_cb_create_args *args); +int ripngd_instance_static_route_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_aggregate_address_create(struct nb_cb_create_args *args); +int ripngd_instance_aggregate_address_destroy(struct nb_cb_destroy_args *args); +int ripngd_instance_timers_flush_interval_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_timers_holddown_interval_modify( + struct nb_cb_modify_args *args); +int ripngd_instance_timers_update_interval_modify( + struct nb_cb_modify_args *args); +const void *ripngd_instance_state_neighbors_neighbor_get_next( + struct nb_cb_get_next_args *args); +int ripngd_instance_state_neighbors_neighbor_get_keys( + struct nb_cb_get_keys_args *args); +const void *ripngd_instance_state_neighbors_neighbor_lookup_entry( + struct nb_cb_lookup_entry_args *args); +struct yang_data *ripngd_instance_state_neighbors_neighbor_address_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripngd_instance_state_neighbors_neighbor_last_update_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripngd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +ripngd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( + struct nb_cb_get_elem_args *args); +const void * +ripngd_instance_state_routes_route_get_next(struct nb_cb_get_next_args *args); +int ripngd_instance_state_routes_route_get_keys( + struct nb_cb_get_keys_args *args); +const void *ripngd_instance_state_routes_route_lookup_entry( + struct nb_cb_lookup_entry_args *args); +struct yang_data *ripngd_instance_state_routes_route_prefix_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripngd_instance_state_routes_route_next_hop_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripngd_instance_state_routes_route_interface_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data *ripngd_instance_state_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args); +int clear_ripng_route_rpc(struct nb_cb_rpc_args *args); +int lib_interface_ripng_split_horizon_modify(struct nb_cb_modify_args *args); + +/* Optional 'apply_finish' callbacks. */ +void ripngd_instance_redistribute_apply_finish( + struct nb_cb_apply_finish_args *args); +void ripngd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args); + +/* Optional 'cli_show' callbacks. */ +void cli_show_router_ripng(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_end_router_ripng(struct vty *vty, const struct lyd_node *dnode); +void cli_show_ripng_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_default_information_originate(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_default_metric(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_network_prefix(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_network_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_offset_list(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_passive_interface(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_redistribute(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_route(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_aggregate_address(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ripng_timers(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults); +void cli_show_ipv6_ripng_split_horizon(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults); + +extern void ripng_cli_init(void); + +#endif /* _FRR_RIPNG_NB_H_ */ diff --git a/ripngd/ripng_nb_config.c b/ripngd/ripng_nb_config.c new file mode 100644 index 0000000..d05d91c --- /dev/null +++ b/ripngd/ripng_nb_config.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 1998 Kunihiro Ishiguro + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * Copyright (C) 2023 LabN Consulting, L.L.C. + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "if_rmap.h" +#include "routemap.h" +#include "agg_table.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nb.h" +#include "ripngd/ripng_debug.h" +#include "ripngd/ripng_route.h" + +/* + * XPath: /frr-ripngd:ripngd/instance + */ +int ripngd_instance_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + struct vrf *vrf; + const char *vrf_name; + int socket; + + vrf_name = yang_dnode_get_string(args->dnode, "vrf"); + vrf = vrf_lookup_by_name(vrf_name); + + /* + * Try to create a RIPng socket only if the VRF is enabled, otherwise + * create a disabled RIPng instance and wait for the VRF to be enabled. + */ + switch (args->event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + if (!vrf || !vrf_is_enabled(vrf)) + break; + + socket = ripng_make_socket(vrf); + if (socket < 0) + return NB_ERR_RESOURCE; + args->resource->fd = socket; + break; + case NB_EV_ABORT: + if (!vrf || !vrf_is_enabled(vrf)) + break; + + socket = args->resource->fd; + close(socket); + break; + case NB_EV_APPLY: + if (vrf && vrf_is_enabled(vrf)) + socket = args->resource->fd; + else + socket = -1; + + ripng = ripng_create(vrf_name, vrf, socket); + nb_running_set_entry(args->dnode, ripng); + break; + } + + return NB_OK; +} + +int ripngd_instance_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_unset_entry(args->dnode); + ripng_clean(ripng); + + return NB_OK; +} + +const void *ripngd_instance_get_next(struct nb_cb_get_next_args *args) +{ + struct ripng *ripng = (struct ripng *)args->list_entry; + + if (args->list_entry == NULL) + ripng = RB_MIN(ripng_instance_head, &ripng_instances); + else + ripng = RB_NEXT(ripng_instance_head, ripng); + + return ripng; +} + +int ripngd_instance_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct ripng *ripng = args->list_entry; + + args->keys->num = 1; + strlcpy(args->keys->key[0], ripng->vrf_name, + sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripngd_instance_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + const char *vrf_name = args->keys->key[0]; + + return ripng_lookup_by_vrf_name(vrf_name); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/allow-ecmp + */ +int ripngd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ripng->ecmp = + MIN(yang_dnode_get_uint8(args->dnode, NULL), zebra_ecmp_count); + if (!ripng->ecmp) { + ripng_ecmp_disable(ripng); + return NB_OK; + } + + ripng_ecmp_change(ripng); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/default-information-originate + */ +int ripngd_instance_default_information_originate_modify( + struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + bool default_information; + struct prefix_ipv6 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + default_information = yang_dnode_get_bool(args->dnode, NULL); + + (void)str2prefix_ipv6("::/0", &p); + if (default_information) { + ripng_redistribute_add(ripng, ZEBRA_ROUTE_RIPNG, + RIPNG_ROUTE_DEFAULT, &p, 0, NULL, 0); + } else { + ripng_redistribute_delete(ripng, ZEBRA_ROUTE_RIPNG, + RIPNG_ROUTE_DEFAULT, &p, 0); + } + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/default-metric + */ +int ripngd_instance_default_metric_modify(struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ripng->default_metric = yang_dnode_get_uint8(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/network + */ +int ripngd_instance_network_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + struct prefix p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6((struct prefix_ipv6 *)&p); + + return ripng_enable_network_add(ripng, &p); +} + +int ripngd_instance_network_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + struct prefix p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6((struct prefix_ipv6 *)&p); + + return ripng_enable_network_delete(ripng, &p); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/interface + */ +int ripngd_instance_interface_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return ripng_enable_if_add(ripng, ifname); +} + +int ripngd_instance_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return ripng_enable_if_delete(ripng, ifname); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/offset-list + */ +int ripngd_instance_offset_list_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + const char *ifname; + struct ripng_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, "interface"); + + offset = ripng_offset_list_new(ripng, ifname); + nb_running_set_entry(args->dnode, offset); + + return NB_OK; +} + +int ripngd_instance_offset_list_destroy(struct nb_cb_destroy_args *args) +{ + int direct; + struct ripng_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "direction"); + + offset = nb_running_unset_entry(args->dnode); + if (offset->direct[direct].alist_name) { + free(offset->direct[direct].alist_name); + offset->direct[direct].alist_name = NULL; + } + if (offset->direct[RIPNG_OFFSET_LIST_IN].alist_name == NULL + && offset->direct[RIPNG_OFFSET_LIST_OUT].alist_name == NULL) + ripng_offset_list_del(offset); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/offset-list/access-list + */ +int ripngd_instance_offset_list_access_list_modify( + struct nb_cb_modify_args *args) +{ + int direct; + struct ripng_offset_list *offset; + const char *alist_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "../direction"); + alist_name = yang_dnode_get_string(args->dnode, NULL); + + offset = nb_running_get_entry(args->dnode, NULL, true); + if (offset->direct[direct].alist_name) + free(offset->direct[direct].alist_name); + offset->direct[direct].alist_name = strdup(alist_name); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/offset-list/metric + */ +int ripngd_instance_offset_list_metric_modify(struct nb_cb_modify_args *args) +{ + int direct; + uint8_t metric; + struct ripng_offset_list *offset; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + direct = yang_dnode_get_enum(args->dnode, "../direction"); + metric = yang_dnode_get_uint8(args->dnode, NULL); + + offset = nb_running_get_entry(args->dnode, NULL, true); + offset->direct[direct].metric = metric; + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/passive-interface + */ +int ripngd_instance_passive_interface_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return ripng_passive_interface_set(ripng, ifname); +} + +int ripngd_instance_passive_interface_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ifname = yang_dnode_get_string(args->dnode, NULL); + + return ripng_passive_interface_unset(ripng, ifname); +} + +/* + * XPath: /frr-ripng:ripng/instance/distribute-list + */ +int ripngd_instance_distribute_list_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + group_distribute_list_create_helper(args, ripng->distribute_ctx); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/redistribute + */ +int ripngd_instance_redistribute_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + ripng->redist[type].enabled = true; + + return NB_OK; +} + +int ripngd_instance_redistribute_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + ripng->redist[type].enabled = false; + if (ripng->redist[type].route_map.name) { + free(ripng->redist[type].route_map.name); + ripng->redist[type].route_map.name = NULL; + ripng->redist[type].route_map.map = NULL; + } + ripng->redist[type].metric_config = false; + ripng->redist[type].metric = 0; + + if (ripng->enabled) + ripng_redistribute_conf_delete(ripng, type); + + return NB_OK; +} + +void ripngd_instance_redistribute_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct ripng *ripng; + int type; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "protocol"); + + if (ripng->enabled) + ripng_redistribute_conf_update(ripng, type); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/redistribute/route-map + */ +int ripngd_instance_redistribute_route_map_modify( + struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + int type; + const char *rmap_name; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + rmap_name = yang_dnode_get_string(args->dnode, NULL); + + if (ripng->redist[type].route_map.name) + free(ripng->redist[type].route_map.name); + ripng->redist[type].route_map.name = strdup(rmap_name); + ripng->redist[type].route_map.map = route_map_lookup_by_name(rmap_name); + + return NB_OK; +} + +int ripngd_instance_redistribute_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + + free(ripng->redist[type].route_map.name); + ripng->redist[type].route_map.name = NULL; + ripng->redist[type].route_map.map = NULL; + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/redistribute/metric + */ +int ripngd_instance_redistribute_metric_modify(struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + int type; + uint8_t metric; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + metric = yang_dnode_get_uint8(args->dnode, NULL); + + ripng->redist[type].metric_config = true; + ripng->redist[type].metric = metric; + + return NB_OK; +} + +int ripngd_instance_redistribute_metric_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + int type; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + type = yang_dnode_get_enum(args->dnode, "../protocol"); + + ripng->redist[type].metric_config = false; + ripng->redist[type].metric = 0; + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/if-route-maps/if-route-map + */ +int ripngd_instance_if_route_maps_if_route_map_create( + struct nb_cb_create_args *args) +{ + /* if_rmap is created when first routemap is added */ + return NB_OK; +} + +int ripngd_instance_if_route_maps_if_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + /* + * YANG will prune edit deletes up to the most general deleted node so + * we need to handle deleting any existing state underneath and not + * count on those more specific callbacks being called individually. + */ + + ripng = nb_running_get_entry(args->dnode, NULL, true); + if_rmap_yang_destroy_cb(ripng->if_rmap_ctx, args->dnode); + + return NB_OK; +} + +static void if_route_map_modify(const struct lyd_node *dnode, + enum if_rmap_type type, bool delete) +{ + struct ripng *ripng = nb_running_get_entry(dnode, NULL, true); + + if_rmap_yang_modify_cb(ripng->if_rmap_ctx, dnode, type, delete); +} +/* + * XPath: /frr-ripng:ripng/instance/if-route-maps/if-route-map/in-route-map + */ +int ripngd_instance_if_route_maps_if_route_map_in_route_map_modify( + struct nb_cb_modify_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_IN, false); + + return NB_OK; +} + +int ripngd_instance_if_route_maps_if_route_map_in_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_IN, true); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/if-route-maps/if-route-map/out-route-map + */ +int ripngd_instance_if_route_maps_if_route_map_out_route_map_modify( + struct nb_cb_modify_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_OUT, false); + + return NB_OK; +} + +int ripngd_instance_if_route_maps_if_route_map_out_route_map_destroy( + struct nb_cb_destroy_args *args) +{ + if (args->event != NB_EV_APPLY) + return NB_OK; + + if_route_map_modify(args->dnode, IF_RMAP_OUT, true); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/static-route + */ +int ripngd_instance_static_route_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + struct prefix_ipv6 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6(&p); + + ripng_redistribute_add(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_STATIC, &p, + 0, NULL, 0); + + return NB_OK; +} + +int ripngd_instance_static_route_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + struct prefix_ipv6 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6(&p); + + ripng_redistribute_delete(ripng, ZEBRA_ROUTE_RIPNG, RIPNG_ROUTE_STATIC, + &p, 0); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/aggregate-address + */ +int ripngd_instance_aggregate_address_create(struct nb_cb_create_args *args) +{ + struct ripng *ripng; + struct prefix_ipv6 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6(&p); + + ripng_aggregate_add(ripng, (struct prefix *)&p); + + return NB_OK; +} + +int ripngd_instance_aggregate_address_destroy(struct nb_cb_destroy_args *args) +{ + struct ripng *ripng; + struct prefix_ipv6 p; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ipv6p(&p, args->dnode, NULL); + apply_mask_ipv6(&p); + + ripng_aggregate_delete(ripng, (struct prefix *)&p); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/timers + */ +void ripngd_instance_timers_apply_finish(struct nb_cb_apply_finish_args *args) +{ + struct ripng *ripng; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + + /* Reset update timer thread. */ + ripng_event(ripng, RIPNG_UPDATE_EVENT, 0); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/timers/flush-interval + */ +int ripngd_instance_timers_flush_interval_modify(struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ripng->garbage_time = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/timers/holddown-interval + */ +int ripngd_instance_timers_holddown_interval_modify( + struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ripng->timeout_time = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/timers/update-interval + */ +int ripngd_instance_timers_update_interval_modify( + struct nb_cb_modify_args *args) +{ + struct ripng *ripng; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ripng = nb_running_get_entry(args->dnode, NULL, true); + ripng->update_time = yang_dnode_get_uint16(args->dnode, NULL); + + return NB_OK; +} + +/* + * XPath: /frr-interface:lib/interface/frr-ripngd:ripng/split-horizon + */ +int lib_interface_ripng_split_horizon_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct ripng_interface *ri; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + ifp = nb_running_get_entry(args->dnode, NULL, true); + ri = ifp->info; + ri->split_horizon = yang_dnode_get_enum(args->dnode, NULL); + + return NB_OK; +} diff --git a/ripngd/ripng_nb_rpcs.c b/ripngd/ripng_nb_rpcs.c new file mode 100644 index 0000000..5498bbf --- /dev/null +++ b/ripngd/ripng_nb_rpcs.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "routemap.h" +#include "agg_table.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nb.h" +#include "ripngd/ripng_debug.h" +#include "ripngd/ripng_route.h" + +/* + * XPath: /frr-ripngd:clear-ripng-route + */ +static void clear_ripng_route(struct ripng *ripng) +{ + struct agg_node *rp; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("Clearing all RIPng routes (VRF %s)", + ripng->vrf_name); + + /* Clear received RIPng routes */ + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { + struct list *list; + struct listnode *listnode; + struct ripng_info *rinfo; + + list = rp->info; + if (list == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (!ripng_route_rte(rinfo)) + continue; + + if (CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) + ripng_zebra_ipv6_delete(ripng, rp); + break; + } + + if (rinfo) { + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + ripng_info_free(rinfo); + } + + if (list_isempty(list)) { + list_delete(&list); + rp->info = NULL; + agg_unlock_node(rp); + } + } +} + +int clear_ripng_route_rpc(struct nb_cb_rpc_args *args) +{ + struct ripng *ripng; + + if (args->input && yang_dnode_exists(args->input, "vrf")) { + const char *name = yang_dnode_get_string(args->input, "vrf"); + + ripng = ripng_lookup_by_vrf_name(name); + if (ripng) + clear_ripng_route(ripng); + } else { + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + ripng = vrf->info; + if (!ripng) + continue; + + clear_ripng_route(ripng); + } + } + + return NB_OK; +} diff --git a/ripngd/ripng_nb_state.c b/ripngd/ripng_nb_state.c new file mode 100644 index 0000000..3f37df3 --- /dev/null +++ b/ripngd/ripng_nb_state.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "if.h" +#include "vrf.h" +#include "log.h" +#include "prefix.h" +#include "table.h" +#include "command.h" +#include "routemap.h" +#include "agg_table.h" +#include "northbound.h" +#include "libfrr.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nb.h" +#include "ripngd/ripng_debug.h" +#include "ripngd/ripng_route.h" + +/* + * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor + */ +const void *ripngd_instance_state_neighbors_neighbor_get_next( + struct nb_cb_get_next_args *args) +{ + const struct ripng *ripng = args->parent_list_entry; + struct listnode *node; + + if (args->list_entry == NULL) + node = listhead(ripng->peer_list); + else + node = listnextnode((struct listnode *)args->list_entry); + + return node; +} + +int ripngd_instance_state_neighbors_neighbor_get_keys( + struct nb_cb_get_keys_args *args) +{ + const struct listnode *node = args->list_entry; + const struct ripng_peer *peer = listgetdata(node); + + args->keys->num = 1; + (void)inet_ntop(AF_INET6, &peer->addr, args->keys->key[0], + sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripngd_instance_state_neighbors_neighbor_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + const struct ripng *ripng = args->parent_list_entry; + struct in6_addr address; + struct ripng_peer *peer; + struct listnode *node; + + yang_str2ipv6(args->keys->key[0], &address); + + for (ALL_LIST_ELEMENTS_RO(ripng->peer_list, node, peer)) { + if (IPV6_ADDR_SAME(&peer->addr, &address)) + return node; + } + + return NULL; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/address + */ +struct yang_data *ripngd_instance_state_neighbors_neighbor_address_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct ripng_peer *peer = listgetdata(node); + + return yang_data_new_ipv6(args->xpath, &peer->addr); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/last-update + */ +struct yang_data *ripngd_instance_state_neighbors_neighbor_last_update_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* TODO: yang:date-and-time is tricky */ + return NULL; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-packets-rcvd + */ +struct yang_data * +ripngd_instance_state_neighbors_neighbor_bad_packets_rcvd_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct ripng_peer *peer = listgetdata(node); + + return yang_data_new_uint32(args->xpath, peer->recv_badpackets); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/neighbors/neighbor/bad-routes-rcvd + */ +struct yang_data * +ripngd_instance_state_neighbors_neighbor_bad_routes_rcvd_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct listnode *node = args->list_entry; + const struct ripng_peer *peer = listgetdata(node); + + return yang_data_new_uint32(args->xpath, peer->recv_badroutes); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/routes/route + */ +const void * +ripngd_instance_state_routes_route_get_next(struct nb_cb_get_next_args *args) +{ + const struct ripng *ripng = args->parent_list_entry; + struct agg_node *rn; + + if (args->list_entry == NULL) + rn = agg_route_top(ripng->table); + else + rn = agg_route_next((struct agg_node *)args->list_entry); + /* Optimization: skip empty route nodes. */ + while (rn && rn->info == NULL) + rn = agg_route_next(rn); + + return rn; +} + +int ripngd_instance_state_routes_route_get_keys( + struct nb_cb_get_keys_args *args) +{ + const struct agg_node *rn = args->list_entry; + + args->keys->num = 1; + (void)prefix2str(agg_node_get_prefix(rn), args->keys->key[0], + sizeof(args->keys->key[0])); + + return NB_OK; +} + +const void *ripngd_instance_state_routes_route_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + const struct ripng *ripng = args->parent_list_entry; + struct prefix prefix; + struct agg_node *rn; + + yang_str2ipv6p(args->keys->key[0], &prefix); + + rn = agg_node_lookup(ripng->table, &prefix); + if (!rn || !rn->info) + return NULL; + + agg_unlock_node(rn); + + return rn; +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/routes/route/prefix + */ +struct yang_data *ripngd_instance_state_routes_route_prefix_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct agg_node *rn = args->list_entry; + const struct ripng_info *rinfo = listnode_head(rn->info); + + return yang_data_new_ipv6p(args->xpath, agg_node_get_prefix(rinfo->rp)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/routes/route/next-hop + */ +struct yang_data *ripngd_instance_state_routes_route_next_hop_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct agg_node *rn = args->list_entry; + const struct ripng_info *rinfo = listnode_head(rn->info); + + return yang_data_new_ipv6(args->xpath, &rinfo->nexthop); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/routes/route/interface + */ +struct yang_data *ripngd_instance_state_routes_route_interface_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct agg_node *rn = args->list_entry; + const struct ripng_info *rinfo = listnode_head(rn->info); + const struct ripng *ripng = ripng_info_get_instance(rinfo); + + return yang_data_new_string( + args->xpath, + ifindex2ifname(rinfo->ifindex, ripng->vrf->vrf_id)); +} + +/* + * XPath: /frr-ripngd:ripngd/instance/state/routes/route/metric + */ +struct yang_data *ripngd_instance_state_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct agg_node *rn = args->list_entry; + const struct ripng_info *rinfo = listnode_head(rn->info); + + return yang_data_new_uint8(args->xpath, rinfo->metric); +} diff --git a/ripngd/ripng_nexthop.c b/ripngd/ripng_nexthop.c new file mode 100644 index 0000000..38e7ce2 --- /dev/null +++ b/ripngd/ripng_nexthop.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPngd Zebra + * Copyright (C) 2002 6WIND + */ + +/* This file is required in order to support properly the RIPng nexthop + * feature. + */ + +#include + +/* For struct udphdr. */ +#include + +#include "linklist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "vty.h" +#include "if.h" +#include "prefix.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_debug.h" +#include "ripngd/ripng_nexthop.h" + +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_RTE_DATA, "RIPng rte data"); + +#define DEBUG 1 + +struct ripng_rte_data { + struct prefix_ipv6 *p; + struct ripng_info *rinfo; + struct ripng_aggregate *aggregate; +}; + +void _ripng_rte_del(struct ripng_rte_data *A); +int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B); + +#define METRIC_OUT(a) \ + ((a)->rinfo ? (a)->rinfo->metric_out : (a)->aggregate->metric_out) +#define NEXTHOP_OUT_PTR(a) \ + ((a)->rinfo ? &((a)->rinfo->nexthop_out) \ + : &((a)->aggregate->nexthop_out)) +#define TAG_OUT(a) ((a)->rinfo ? (a)->rinfo->tag_out : (a)->aggregate->tag_out) + +struct list *ripng_rte_new(void) +{ + struct list *rte; + + rte = list_new(); + rte->cmp = (int (*)(void *, void *))_ripng_rte_cmp; + rte->del = (void (*)(void *))_ripng_rte_del; + + return rte; +} + +void ripng_rte_free(struct list *ripng_rte_list) +{ + list_delete(&ripng_rte_list); +} + +/* Delete RTE */ +void _ripng_rte_del(struct ripng_rte_data *A) +{ + XFREE(MTYPE_RIPNG_RTE_DATA, A); +} + +/* Compare RTE: + * return + if A > B + * 0 if A = B + * - if A < B + */ +int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B) +{ + return addr6_cmp(NEXTHOP_OUT_PTR(A), NEXTHOP_OUT_PTR(B)); +} + +/* Add routing table entry */ +void ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p, + struct ripng_info *rinfo, struct ripng_aggregate *aggregate) +{ + + struct ripng_rte_data *data; + + /* At least one should not be null */ + assert(!rinfo || !aggregate); + + data = XMALLOC(MTYPE_RIPNG_RTE_DATA, sizeof(*data)); + data->p = p; + data->rinfo = rinfo; + data->aggregate = aggregate; + + listnode_add_sort(ripng_rte_list, data); +} + +/* Send the RTE with the nexthop support + */ +void ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp, + struct sockaddr_in6 *to) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + struct ripng_rte_data *data; + struct listnode *node, *nnode; + + struct in6_addr last_nexthop; + struct in6_addr myself_nexthop; + + struct stream *s; + int num; + int mtu; + int rtemax; + int ret; + + /* Most of the time, there is no nexthop */ + memset(&last_nexthop, 0, sizeof(last_nexthop)); + + /* Use myself_nexthop if the nexthop is not a link-local address, + * because + * we remain a right path without beeing the optimal one. + */ + memset(&myself_nexthop, 0, sizeof(myself_nexthop)); + + /* Output stream get from ripng structre. XXX this should be + interface structure. */ + s = ripng->obuf; + + /* Reset stream and RTE counter. */ + stream_reset(s); + num = 0; + + mtu = ifp->mtu6; + if (mtu < 0) + mtu = IFMINMTU; + + rtemax = (MIN(mtu, RIPNG_MAX_PACKET_SIZE) - IPV6_HDRLEN + - sizeof(struct udphdr) - sizeof(struct ripng_packet) + + sizeof(struct rte)) + / sizeof(struct rte); + + for (ALL_LIST_ELEMENTS(ripng_rte_list, node, nnode, data)) { + /* (2.1) Next hop support */ + if (!IPV6_ADDR_SAME(&last_nexthop, NEXTHOP_OUT_PTR(data))) { + + /* A nexthop entry should be at least followed by 1 RTE + */ + if (num == (rtemax - 1)) { + ret = ripng_send_packet((caddr_t)STREAM_DATA(s), + stream_get_endp(s), to, + ifp); + + if (ret >= 0 && IS_RIPNG_DEBUG_SEND) + ripng_packet_dump( + (struct ripng_packet *) + STREAM_DATA(s), + stream_get_endp(s), "SEND"); + num = 0; + stream_reset(s); + } + + /* Add the nexthop (2.1) */ + + /* If the received next hop address is not a link-local + * address, + * it should be treated as 0:0:0:0:0:0:0:0. + */ + if (!IN6_IS_ADDR_LINKLOCAL(NEXTHOP_OUT_PTR(data))) + last_nexthop = myself_nexthop; + else + last_nexthop = *NEXTHOP_OUT_PTR(data); + + num = ripng_write_rte(num, s, NULL, &last_nexthop, 0, + RIPNG_METRIC_NEXTHOP); + } else { + /* Rewrite the nexthop for each new packet */ + if ((num == 0) + && !IPV6_ADDR_SAME(&last_nexthop, &myself_nexthop)) + num = ripng_write_rte(num, s, NULL, + &last_nexthop, 0, + RIPNG_METRIC_NEXTHOP); + } + num = ripng_write_rte(num, s, data->p, NULL, TAG_OUT(data), + METRIC_OUT(data)); + + if (num == rtemax) { + ret = ripng_send_packet((caddr_t)STREAM_DATA(s), + stream_get_endp(s), to, ifp); + + if (ret >= 0 && IS_RIPNG_DEBUG_SEND) + ripng_packet_dump( + (struct ripng_packet *)STREAM_DATA(s), + stream_get_endp(s), "SEND"); + num = 0; + stream_reset(s); + } + } + + /* If unwritten RTE exist, flush it. */ + if (num != 0) { + ret = ripng_send_packet((caddr_t)STREAM_DATA(s), + stream_get_endp(s), to, ifp); + + if (ret >= 0 && IS_RIPNG_DEBUG_SEND) + ripng_packet_dump((struct ripng_packet *)STREAM_DATA(s), + stream_get_endp(s), "SEND"); + stream_reset(s); + } +} diff --git a/ripngd/ripng_nexthop.h b/ripngd/ripng_nexthop.h new file mode 100644 index 0000000..2d29939 --- /dev/null +++ b/ripngd/ripng_nexthop.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPng nexthop support + * Copyright (C) 6WIND Vincent Jardin + */ + +#ifndef _ZEBRA_RIPNG_RIPNG_NEXTHOP_H +#define _ZEBRA_RIPNG_RIPNG_NEXTHOP_H + +#include +#include "linklist.h" +#include "ripngd/ripng_route.h" +#include "ripngd/ripngd.h" + +extern struct list *ripng_rte_new(void); +extern void ripng_rte_free(struct list *ripng_rte_list); +extern void ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p, + struct ripng_info *rinfo, + struct ripng_aggregate *aggregate); +extern void ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp, + struct sockaddr_in6 *to); + +/*** + * 1 if A > B + * 0 if A = B + * -1 if A < B + **/ +static inline int addr6_cmp(struct in6_addr *A, struct in6_addr *B) +{ +#define a(i) A->s6_addr32[i] +#define b(i) B->s6_addr32[i] + + if (a(3) > b(3)) + return 1; + else if ((a(3) == b(3)) && (a(2) > b(2))) + return 1; + else if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) > b(1))) + return 1; + else if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) == b(1)) + && (a(0) > b(0))) + return 1; + + if ((a(3) == b(3)) && (a(2) == b(2)) && (a(1) == b(1)) + && (a(0) == b(0))) + return 0; + + return -1; +} + +#endif /* _ZEBRA_RIPNG_RIPNG_NEXTHOP_H */ diff --git a/ripngd/ripng_offset.c b/ripngd/ripng_offset.c new file mode 100644 index 0000000..d842f15 --- /dev/null +++ b/ripngd/ripng_offset.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPng offset-list + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +/* RIPng support by Vincent Jardin + * Copyright (C) 2002 6WIND + */ + +#include + +#include "if.h" +#include "prefix.h" +#include "filter.h" +#include "command.h" +#include "linklist.h" +#include "memory.h" + +#include "ripngd/ripngd.h" + +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_OFFSET_LIST, "RIPng offset lst"); + +#define OFFSET_LIST_IN_NAME(O) ((O)->direct[RIPNG_OFFSET_LIST_IN].alist_name) +#define OFFSET_LIST_IN_METRIC(O) ((O)->direct[RIPNG_OFFSET_LIST_IN].metric) + +#define OFFSET_LIST_OUT_NAME(O) ((O)->direct[RIPNG_OFFSET_LIST_OUT].alist_name) +#define OFFSET_LIST_OUT_METRIC(O) ((O)->direct[RIPNG_OFFSET_LIST_OUT].metric) + +struct ripng_offset_list *ripng_offset_list_new(struct ripng *ripng, + const char *ifname) +{ + struct ripng_offset_list *new; + + new = XCALLOC(MTYPE_RIPNG_OFFSET_LIST, + sizeof(struct ripng_offset_list)); + new->ripng = ripng; + new->ifname = strdup(ifname); + listnode_add_sort(ripng->offset_list_master, new); + + return new; +} + +void ripng_offset_list_del(struct ripng_offset_list *offset) +{ + listnode_delete(offset->ripng->offset_list_master, offset); + ripng_offset_list_free(offset); +} + +void ripng_offset_list_free(struct ripng_offset_list *offset) +{ + if (OFFSET_LIST_IN_NAME(offset)) + free(OFFSET_LIST_IN_NAME(offset)); + if (OFFSET_LIST_OUT_NAME(offset)) + free(OFFSET_LIST_OUT_NAME(offset)); + free(offset->ifname); + XFREE(MTYPE_RIPNG_OFFSET_LIST, offset); +} + +struct ripng_offset_list *ripng_offset_list_lookup(struct ripng *ripng, + const char *ifname) +{ + struct ripng_offset_list *offset; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(ripng->offset_list_master, node, nnode, + offset)) { + if (strcmp(offset->ifname, ifname) == 0) + return offset; + } + return NULL; +} + +/* If metric is modified return 1. */ +int ripng_offset_list_apply_in(struct ripng *ripng, struct prefix_ipv6 *p, + struct interface *ifp, uint8_t *metric) +{ + struct ripng_offset_list *offset; + struct access_list *alist; + + /* Look up offset-list with interface name. */ + offset = ripng_offset_list_lookup(ripng, ifp->name); + if (offset && OFFSET_LIST_IN_NAME(offset)) { + alist = access_list_lookup(AFI_IP6, + OFFSET_LIST_IN_NAME(offset)); + + if (alist && access_list_apply(alist, (struct prefix *)p) == + FILTER_PERMIT) { + *metric += OFFSET_LIST_IN_METRIC(offset); + return 1; + } + return 0; + } + /* Look up offset-list without interface name. */ + offset = ripng_offset_list_lookup(ripng, "*"); + if (offset && OFFSET_LIST_IN_NAME(offset)) { + alist = access_list_lookup(AFI_IP6, + OFFSET_LIST_IN_NAME(offset)); + + if (alist && access_list_apply(alist, (struct prefix *)p) == + FILTER_PERMIT) { + *metric += OFFSET_LIST_IN_METRIC(offset); + return 1; + } + return 0; + } + return 0; +} + +/* If metric is modified return 1. */ +int ripng_offset_list_apply_out(struct ripng *ripng, struct prefix_ipv6 *p, + struct interface *ifp, uint8_t *metric) +{ + struct ripng_offset_list *offset; + struct access_list *alist; + + /* Look up offset-list with interface name. */ + offset = ripng_offset_list_lookup(ripng, ifp->name); + if (offset && OFFSET_LIST_OUT_NAME(offset)) { + alist = access_list_lookup(AFI_IP6, + OFFSET_LIST_OUT_NAME(offset)); + + if (alist && access_list_apply(alist, (struct prefix *)p) == + FILTER_PERMIT) { + *metric += OFFSET_LIST_OUT_METRIC(offset); + return 1; + } + return 0; + } + + /* Look up offset-list without interface name. */ + offset = ripng_offset_list_lookup(ripng, "*"); + if (offset && OFFSET_LIST_OUT_NAME(offset)) { + alist = access_list_lookup(AFI_IP6, + OFFSET_LIST_OUT_NAME(offset)); + + if (alist && access_list_apply(alist, (struct prefix *)p) == + FILTER_PERMIT) { + *metric += OFFSET_LIST_OUT_METRIC(offset); + return 1; + } + return 0; + } + return 0; +} + +int offset_list_cmp(struct ripng_offset_list *o1, struct ripng_offset_list *o2) +{ + return strcmp(o1->ifname, o2->ifname); +} diff --git a/ripngd/ripng_peer.c b/ripngd/ripng_peer.c new file mode 100644 index 0000000..247bac4 --- /dev/null +++ b/ripngd/ripng_peer.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPng peer support + * Copyright (C) 2000 Kunihiro Ishiguro + */ + +/* RIPng support added by Vincent Jardin + * Copyright (C) 2002 6WIND + */ + +#include + +#include "if.h" +#include "prefix.h" +#include "command.h" +#include "linklist.h" +#include "frrevent.h" +#include "memory.h" +#include "frrdistance.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_nexthop.h" + +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_PEER, "RIPng peer"); + +static struct ripng_peer *ripng_peer_new(void) +{ + return XCALLOC(MTYPE_RIPNG_PEER, sizeof(struct ripng_peer)); +} + +static void ripng_peer_free(struct ripng_peer *peer) +{ + EVENT_OFF(peer->t_timeout); + XFREE(MTYPE_RIPNG_PEER, peer); +} + +struct ripng_peer *ripng_peer_lookup(struct ripng *ripng, struct in6_addr *addr) +{ + struct ripng_peer *peer; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { + if (IPV6_ADDR_SAME(&peer->addr, addr)) + return peer; + } + return NULL; +} + +struct ripng_peer *ripng_peer_lookup_next(struct ripng *ripng, + struct in6_addr *addr) +{ + struct ripng_peer *peer; + struct listnode *node, *nnode; + + for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { + if (addr6_cmp(&peer->addr, addr) > 0) + return peer; + } + return NULL; +} + +/* RIPng peer is timeout. + * Garbage collector. + **/ +static void ripng_peer_timeout(struct event *t) +{ + struct ripng_peer *peer; + + peer = EVENT_ARG(t); + listnode_delete(peer->ripng->peer_list, peer); + ripng_peer_free(peer); +} + +/* Get RIPng peer. At the same time update timeout thread. */ +static struct ripng_peer *ripng_peer_get(struct ripng *ripng, + struct in6_addr *addr) +{ + struct ripng_peer *peer; + + peer = ripng_peer_lookup(ripng, addr); + + if (peer) { + EVENT_OFF(peer->t_timeout); + } else { + peer = ripng_peer_new(); + peer->ripng = ripng; + peer->addr = *addr; + listnode_add_sort(ripng->peer_list, peer); + } + + /* Update timeout thread. */ + event_add_timer(master, ripng_peer_timeout, peer, + RIPNG_PEER_TIMER_DEFAULT, &peer->t_timeout); + + /* Last update time set. */ + time(&peer->uptime); + + return peer; +} + +void ripng_peer_update(struct ripng *ripng, struct sockaddr_in6 *from, + uint8_t version) +{ + struct ripng_peer *peer; + peer = ripng_peer_get(ripng, &from->sin6_addr); + peer->version = version; +} + +void ripng_peer_bad_route(struct ripng *ripng, struct sockaddr_in6 *from) +{ + struct ripng_peer *peer; + peer = ripng_peer_get(ripng, &from->sin6_addr); + peer->recv_badroutes++; +} + +void ripng_peer_bad_packet(struct ripng *ripng, struct sockaddr_in6 *from) +{ + struct ripng_peer *peer; + peer = ripng_peer_get(ripng, &from->sin6_addr); + peer->recv_badpackets++; +} + +/* Display peer uptime. */ +static char *ripng_peer_uptime(struct ripng_peer *peer, char *buf, size_t len) +{ + time_t uptime; + + /* If there is no connection has been done before print `never'. */ + if (peer->uptime == 0) { + snprintf(buf, len, "never "); + return buf; + } + + /* Get current time. */ + uptime = time(NULL); + uptime -= peer->uptime; + + frrtime_to_interval(uptime, buf, len); + + return buf; +} + +void ripng_peer_display(struct vty *vty, struct ripng *ripng) +{ + struct ripng_peer *peer; + struct listnode *node, *nnode; +#define RIPNG_UPTIME_LEN 25 + char timebuf[RIPNG_UPTIME_LEN]; + + for (ALL_LIST_ELEMENTS(ripng->peer_list, node, nnode, peer)) { + vty_out(vty, " %pI6 \n%14s %10d %10d %10d %s\n", + &peer->addr, " ", peer->recv_badpackets, + peer->recv_badroutes, ZEBRA_RIPNG_DISTANCE_DEFAULT, + ripng_peer_uptime(peer, timebuf, RIPNG_UPTIME_LEN)); + } +} + +int ripng_peer_list_cmp(struct ripng_peer *p1, struct ripng_peer *p2) +{ + return memcmp(&p1->addr, &p2->addr, sizeof(struct in6_addr)); +} + +void ripng_peer_list_del(void *arg) +{ + ripng_peer_free(arg); +} diff --git a/ripngd/ripng_route.c b/ripngd/ripng_route.c new file mode 100644 index 0000000..80041fb --- /dev/null +++ b/ripngd/ripng_route.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPng routes function. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "agg_table.h" +#include "memory.h" +#include "if.h" +#include "vty.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_route.h" + +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_AGGREGATE, "RIPng aggregate"); + +static struct ripng_aggregate *ripng_aggregate_new(void) +{ + struct ripng_aggregate *new; + + new = XCALLOC(MTYPE_RIPNG_AGGREGATE, sizeof(struct ripng_aggregate)); + return new; +} + +void ripng_aggregate_free(struct ripng_aggregate *aggregate) +{ + XFREE(MTYPE_RIPNG_AGGREGATE, aggregate); +} + +/* Aggregate count increment check. */ +void ripng_aggregate_increment(struct agg_node *child, struct ripng_info *rinfo) +{ + struct agg_node *np; + struct ripng_aggregate *aggregate; + + for (np = child; np; np = agg_node_parent(np)) + if ((aggregate = np->aggregate) != NULL) { + aggregate->count++; + rinfo->suppress++; + } +} + +/* Aggregate count decrement check. */ +void ripng_aggregate_decrement(struct agg_node *child, struct ripng_info *rinfo) +{ + struct agg_node *np; + struct ripng_aggregate *aggregate; + + for (np = child; np; np = agg_node_parent(np)) + if ((aggregate = np->aggregate) != NULL) { + aggregate->count--; + rinfo->suppress--; + } +} + +/* Aggregate count decrement check for a list. */ +void ripng_aggregate_decrement_list(struct agg_node *child, struct list *list) +{ + struct agg_node *np; + struct ripng_aggregate *aggregate; + struct ripng_info *rinfo = NULL; + struct listnode *node = NULL; + + for (np = child; np; np = agg_node_parent(np)) + if ((aggregate = np->aggregate) != NULL) + aggregate->count -= listcount(list); + + for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) + rinfo->suppress--; +} + +/* RIPng routes treatment. */ +int ripng_aggregate_add(struct ripng *ripng, struct prefix *p) +{ + struct agg_node *top; + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_aggregate *aggregate; + struct ripng_aggregate *sub; + struct list *list = NULL; + struct listnode *node = NULL; + + /* Get top node for aggregation. */ + top = agg_node_get(ripng->table, p); + + /* Allocate new aggregate. */ + aggregate = ripng_aggregate_new(); + aggregate->metric = 1; + + top->aggregate = aggregate; + + /* Suppress routes match to the aggregate. */ + for (rp = agg_lock_node(top); rp; rp = agg_route_next_until(rp, top)) { + /* Suppress normal route. */ + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { + aggregate->count++; + rinfo->suppress++; + } + /* Suppress aggregate route. This may not need. */ + if (rp != top && (sub = rp->aggregate) != NULL) { + aggregate->count++; + sub->suppress++; + } + } + + return 0; +} + +/* Delete RIPng static route. */ +int ripng_aggregate_delete(struct ripng *ripng, struct prefix *p) +{ + struct agg_node *top; + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_aggregate *aggregate; + struct ripng_aggregate *sub; + struct list *list = NULL; + struct listnode *node = NULL; + + /* Get top node for aggregation. */ + top = agg_node_get(ripng->table, p); + + /* Allocate new aggregate. */ + aggregate = top->aggregate; + + /* Suppress routes match to the aggregate. */ + for (rp = agg_lock_node(top); rp; rp = agg_route_next_until(rp, top)) { + /* Suppress normal route. */ + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { + aggregate->count--; + rinfo->suppress--; + } + + if (rp != top && (sub = rp->aggregate) != NULL) { + aggregate->count--; + sub->suppress--; + } + } + + top->aggregate = NULL; + ripng_aggregate_free(aggregate); + + agg_unlock_node(top); + agg_unlock_node(top); + + return 0; +} diff --git a/ripngd/ripng_route.h b/ripngd/ripng_route.h new file mode 100644 index 0000000..fb3d71c --- /dev/null +++ b/ripngd/ripng_route.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPng daemon + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_RIPNG_ROUTE_H +#define _ZEBRA_RIPNG_ROUTE_H + +struct ripng_aggregate { + /* Aggregate route count. */ + unsigned int count; + + /* Suppressed route count. */ + unsigned int suppress; + + /* Metric of this route. */ + uint8_t metric; + + /* Tag field of RIPng packet.*/ + uint16_t tag; + + /* Route-map futures - this variables can be changed. */ + struct in6_addr nexthop_out; + uint8_t metric_set; + uint8_t metric_out; + uint16_t tag_out; +}; + +extern void ripng_aggregate_increment(struct agg_node *rp, + struct ripng_info *rinfo); +extern void ripng_aggregate_decrement(struct agg_node *rp, + struct ripng_info *rinfo); +extern void ripng_aggregate_decrement_list(struct agg_node *rp, + struct list *list); +extern int ripng_aggregate_add(struct ripng *ripng, struct prefix *p); +extern int ripng_aggregate_delete(struct ripng *ripng, struct prefix *p); +extern void ripng_aggregate_free(struct ripng_aggregate *aggregate); + +#endif /* _ZEBRA_RIPNG_ROUTE_H */ diff --git a/ripngd/ripng_routemap.c b/ripngd/ripng_routemap.c new file mode 100644 index 0000000..3370546 --- /dev/null +++ b/ripngd/ripng_routemap.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPng routemap. + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include + +#include "if.h" +#include "memory.h" +#include "prefix.h" +#include "vty.h" +#include "routemap.h" +#include "command.h" +#include "sockunion.h" + +#include "ripngd/ripngd.h" + +struct rip_metric_modifier { + enum { metric_increment, metric_decrement, metric_absolute } type; + bool used; + uint8_t metric; +}; + +/* `match metric METRIC' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_metric(void *rule, const struct prefix *prefix, void *object) +{ + uint32_t *metric; + struct ripng_info *rinfo; + + metric = rule; + rinfo = object; + + if (rinfo->metric == *metric) + return RMAP_MATCH; + else + return RMAP_NOMATCH; + + return RMAP_NOMATCH; +} + +/* Route map `match metric' match statement. `arg' is METRIC value */ +static void *route_match_metric_compile(const char *arg) +{ + uint32_t *metric; + + metric = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(uint32_t)); + *metric = atoi(arg); + + if (*metric > 0) + return metric; + + XFREE(MTYPE_ROUTE_MAP_COMPILED, metric); + return NULL; +} + +/* Free route map's compiled `match metric' value. */ +static void route_match_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for metric matching. */ +static const struct route_map_rule_cmd route_match_metric_cmd = { + "metric", + route_match_metric, + route_match_metric_compile, + route_match_metric_free +}; + +/* `match interface IFNAME' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t +route_match_interface(void *rule, const struct prefix *prefix, void *object) +{ + struct ripng_info *rinfo; + struct interface *ifp; + char *ifname; + + ifname = rule; + ifp = if_lookup_by_name(ifname, VRF_DEFAULT); + + if (!ifp) + return RMAP_NOMATCH; + + rinfo = object; + + if (rinfo->ifindex == ifp->ifindex) + return RMAP_MATCH; + else + return RMAP_NOMATCH; + + return RMAP_NOMATCH; +} + +/* Route map `match interface' match statement. `arg' is IFNAME value */ +static void *route_match_interface_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_interface_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_interface_cmd = { + "interface", + route_match_interface, + route_match_interface_compile, + route_match_interface_free +}; + +/* match ipv6 address WORD */ + +static enum route_map_cmd_result_t +route_match_ipv6_address(void *rule, const struct prefix *prefix, void *object) +{ + struct access_list *alist; + + alist = access_list_lookup(AFI_IP6, (char *)rule); + if (access_list_apply(alist, prefix) != FILTER_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_address_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_ipv6_address_cmd = { + "ipv6 address", + route_match_ipv6_address, + route_match_ipv6_address_compile, + route_match_ipv6_address_free +}; + +/* match ipv6 address prefix-list PREFIX_LIST */ + +static enum route_map_cmd_result_t +route_match_ipv6_address_prefix_list(void *rule, const struct prefix *prefix, + void *object) +{ + struct prefix_list *plist; + + plist = prefix_list_lookup(AFI_IP6, (char *)rule); + if (prefix_list_apply(plist, prefix) != PREFIX_DENY) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_ipv6_address_prefix_list_compile(const char *arg) +{ + return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg); +} + +static void route_match_ipv6_address_prefix_list_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd + route_match_ipv6_address_prefix_list_cmd = { + "ipv6 address prefix-list", + route_match_ipv6_address_prefix_list, + route_match_ipv6_address_prefix_list_compile, + route_match_ipv6_address_prefix_list_free +}; + +/* `match tag TAG' */ +/* Match function return 1 if match is success else return zero. */ +static enum route_map_cmd_result_t route_match_tag(void *rule, + const struct prefix *prefix, + void *object) +{ + route_tag_t *tag; + struct ripng_info *rinfo; + route_tag_t rinfo_tag; + + tag = rule; + rinfo = object; + + /* The information stored by rinfo is host ordered. */ + rinfo_tag = rinfo->tag; + if (rinfo_tag == *tag) + return RMAP_MATCH; + else + return RMAP_NOMATCH; +} + + +static const struct route_map_rule_cmd route_match_tag_cmd = { + "tag", + route_match_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free, +}; + +/* `set metric METRIC' */ + +/* Set metric to attribute. */ +static enum route_map_cmd_result_t +route_set_metric(void *rule, const struct prefix *prefix, void *object) +{ + struct rip_metric_modifier *mod; + struct ripng_info *rinfo; + + mod = rule; + rinfo = object; + + if (!mod->used) + return RMAP_OKAY; + + if (mod->type == metric_increment) + rinfo->metric_out += mod->metric; + else if (mod->type == metric_decrement) + rinfo->metric_out -= mod->metric; + else if (mod->type == metric_absolute) + rinfo->metric_out = mod->metric; + + if (rinfo->metric_out < 1) + rinfo->metric_out = 1; + if (rinfo->metric_out > RIPNG_METRIC_INFINITY) + rinfo->metric_out = RIPNG_METRIC_INFINITY; + + rinfo->metric_set = 1; + + return RMAP_OKAY; +} + +/* set metric compilation. */ +static void *route_set_metric_compile(const char *arg) +{ + int len; + const char *pnt; + long metric; + char *endptr = NULL; + struct rip_metric_modifier *mod; + + len = strlen(arg); + pnt = arg; + + mod = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, + sizeof(struct rip_metric_modifier)); + mod->used = false; + + if (len == 0) + return mod; + + /* Examine first character. */ + if (arg[0] == '+') { + mod->type = metric_increment; + pnt++; + } else if (arg[0] == '-') { + mod->type = metric_decrement; + pnt++; + } else + mod->type = metric_absolute; + + /* Check beginning with digit string. */ + if (*pnt < '0' || *pnt > '9') + return mod; + + /* Convert string to integer. */ + metric = strtol(pnt, &endptr, 10); + + if (*endptr != '\0' || metric < 0) + return mod; + + if (metric > RIPNG_METRIC_INFINITY) { + zlog_info( + "%s: Metric specified: %ld is being converted into METRIC_INFINITY", + __func__, metric); + mod->metric = RIPNG_METRIC_INFINITY; + } else + mod->metric = metric; + + mod->used = true; + return mod; +} + +/* Free route map's compiled `set metric' value. */ +static void route_set_metric_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_set_metric_cmd = { + "metric", + route_set_metric, + route_set_metric_compile, + route_set_metric_free, +}; + +/* `set ipv6 next-hop local IP_ADDRESS' */ + +/* Set nexthop to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_ipv6_nexthop_local(void *rule, const struct prefix *p, void *object) +{ + struct in6_addr *address; + struct ripng_info *rinfo; + + /* Fetch routemap's rule information. */ + address = rule; + rinfo = object; + + /* Set next hop value. */ + rinfo->nexthop_out = *address; + + return RMAP_OKAY; +} + +/* Route map `ipv6 nexthop local' compile function. Given string is converted + to struct in6_addr structure. */ +static void *route_set_ipv6_nexthop_local_compile(const char *arg) +{ + int ret; + struct in6_addr *address; + + address = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(struct in6_addr)); + + ret = inet_pton(AF_INET6, arg, address); + + if (ret == 0) { + XFREE(MTYPE_ROUTE_MAP_COMPILED, address); + return NULL; + } + + return address; +} + +/* Free route map's compiled `ipv6 nexthop local' value. */ +static void route_set_ipv6_nexthop_local_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +/* Route map commands for ipv6 nexthop local set. */ +static const struct route_map_rule_cmd + route_set_ipv6_nexthop_local_cmd = { + "ipv6 next-hop local", + route_set_ipv6_nexthop_local, + route_set_ipv6_nexthop_local_compile, + route_set_ipv6_nexthop_local_free +}; + +/* `set tag TAG' */ + +/* Set tag to object. object must be pointer to struct attr. */ +static enum route_map_cmd_result_t +route_set_tag(void *rule, const struct prefix *prefix, void *object) +{ + route_tag_t *tag; + struct ripng_info *rinfo; + + /* Fetch routemap's rule information. */ + tag = rule; + rinfo = object; + + /* Set next hop value. */ + rinfo->tag_out = *tag; + + return RMAP_OKAY; +} + +/* Route map commands for tag set. */ +static const struct route_map_rule_cmd route_set_tag_cmd = { + "tag", + route_set_tag, + route_map_rule_tag_compile, + route_map_rule_tag_free +}; + +#define MATCH_STR "Match values from routing table\n" +#define SET_STR "Set values in destination routing protocol\n" + +void ripng_route_map_init(void) +{ + route_map_init_new(true); + + route_map_match_interface_hook(generic_match_add); + route_map_no_match_interface_hook(generic_match_delete); + + route_map_match_ipv6_address_hook(generic_match_add); + route_map_no_match_ipv6_address_hook(generic_match_delete); + + route_map_match_ipv6_address_prefix_list_hook(generic_match_add); + route_map_no_match_ipv6_address_prefix_list_hook(generic_match_delete); + + route_map_match_metric_hook(generic_match_add); + route_map_no_match_metric_hook(generic_match_delete); + + route_map_match_tag_hook(generic_match_add); + route_map_no_match_tag_hook(generic_match_delete); + + route_map_set_ipv6_nexthop_local_hook(generic_set_add); + route_map_no_set_ipv6_nexthop_local_hook(generic_set_delete); + + route_map_set_metric_hook(generic_set_add); + route_map_no_set_metric_hook(generic_set_delete); + + route_map_set_tag_hook(generic_set_add); + route_map_no_set_tag_hook(generic_set_delete); + + route_map_install_match(&route_match_metric_cmd); + route_map_install_match(&route_match_interface_cmd); + route_map_install_match(&route_match_ipv6_address_cmd); + route_map_install_match(&route_match_ipv6_address_prefix_list_cmd); + route_map_install_match(&route_match_tag_cmd); + route_map_install_set(&route_set_metric_cmd); + route_map_install_set(&route_set_ipv6_nexthop_local_cmd); + route_map_install_set(&route_set_tag_cmd); +} diff --git a/ripngd/ripng_zebra.c b/ripngd/ripng_zebra.c new file mode 100644 index 0000000..bb5a880 --- /dev/null +++ b/ripngd/ripng_zebra.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPngd and zebra interface. + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "command.h" +#include "prefix.h" +#include "agg_table.h" +#include "stream.h" +#include "memory.h" +#include "routemap.h" +#include "zclient.h" +#include "log.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_debug.h" + +/* All information about zebra. */ +struct zclient *zclient = NULL; + +/* Send ECMP routes to zebra. */ +static void ripng_zebra_ipv6_send(struct ripng *ripng, struct agg_node *rp, + uint8_t cmd) +{ + struct list *list = (struct list *)rp->info; + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct listnode *listnode = NULL; + struct ripng_info *rinfo = NULL; + uint32_t count = 0; + const struct prefix *p = agg_node_get_prefix(rp); + + memset(&api, 0, sizeof(api)); + api.vrf_id = ripng->vrf->vrf_id; + api.type = ZEBRA_ROUTE_RIPNG; + api.safi = SAFI_UNICAST; + api.prefix = *p; + + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (count >= zebra_ecmp_count) + break; + api_nh = &api.nexthops[count]; + api_nh->vrf_id = ripng->vrf->vrf_id; + api_nh->gate.ipv6 = rinfo->nexthop; + api_nh->ifindex = rinfo->ifindex; + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + count++; + if (cmd == ZEBRA_ROUTE_ADD) + SET_FLAG(rinfo->flags, RIPNG_RTF_FIB); + else + UNSET_FLAG(rinfo->flags, RIPNG_RTF_FIB); + } + + api.nexthop_num = count; + + rinfo = listgetdata(listhead(list)); + + SET_FLAG(api.message, ZAPI_MESSAGE_METRIC); + api.metric = rinfo->metric; + + if (rinfo->tag) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = rinfo->tag; + } + + zclient_route_send(cmd, zclient, &api); + + if (IS_RIPNG_DEBUG_ZEBRA) { + if (ripng->ecmp) + zlog_debug("%s: %pRN nexthops %d", + (cmd == ZEBRA_ROUTE_ADD) + ? "Install into zebra" + : "Delete from zebra", + rp, count); + else + zlog_debug("%s: %pRN", + (cmd == ZEBRA_ROUTE_ADD) + ? "Install into zebra" + : "Delete from zebra", + rp); + } +} + +/* Add/update ECMP routes to zebra. */ +void ripng_zebra_ipv6_add(struct ripng *ripng, struct agg_node *rp) +{ + ripng_zebra_ipv6_send(ripng, rp, ZEBRA_ROUTE_ADD); +} + +/* Delete ECMP routes from zebra. */ +void ripng_zebra_ipv6_delete(struct ripng *ripng, struct agg_node *rp) +{ + ripng_zebra_ipv6_send(ripng, rp, ZEBRA_ROUTE_DELETE); +} + +/* Zebra route add and delete treatment. */ +static int ripng_zebra_read_route(ZAPI_CALLBACK_ARGS) +{ + struct ripng *ripng; + struct zapi_route api; + struct in6_addr nexthop; + unsigned long ifindex; + + ripng = ripng_lookup_by_vrf_id(vrf_id); + if (!ripng) + return 0; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + return -1; + + /* we completely ignore srcdest routes for now. */ + if (CHECK_FLAG(api.message, ZAPI_MESSAGE_SRCPFX)) + return 0; + + if (IN6_IS_ADDR_LINKLOCAL(&api.prefix.u.prefix6)) + return 0; + + nexthop = api.nexthops[0].gate.ipv6; + ifindex = api.nexthops[0].ifindex; + + if (cmd == ZEBRA_REDISTRIBUTE_ROUTE_ADD) + ripng_redistribute_add(ripng, api.type, + RIPNG_ROUTE_REDISTRIBUTE, + (struct prefix_ipv6 *)&api.prefix, + ifindex, &nexthop, api.tag); + else + ripng_redistribute_delete( + ripng, api.type, RIPNG_ROUTE_REDISTRIBUTE, + (struct prefix_ipv6 *)&api.prefix, ifindex); + + return 0; +} + +void ripng_redistribute_conf_update(struct ripng *ripng, int type) +{ + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP6, + type, 0, ripng->vrf->vrf_id); +} + +void ripng_redistribute_conf_delete(struct ripng *ripng, int type) +{ + if (zclient->sock > 0) + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP6, type, 0, ripng->vrf->vrf_id); + + ripng_redistribute_withdraw(ripng, type); +} + +int ripng_redistribute_check(struct ripng *ripng, int type) +{ + return ripng->redist[type].enabled; +} + +void ripng_redistribute_enable(struct ripng *ripng) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!ripng_redistribute_check(ripng, i)) + continue; + + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, + AFI_IP6, i, 0, ripng->vrf->vrf_id); + } +} + +void ripng_redistribute_disable(struct ripng *ripng) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (!ripng_redistribute_check(ripng, i)) + continue; + + zebra_redistribute_send(ZEBRA_REDISTRIBUTE_DELETE, zclient, + AFI_IP6, i, 0, ripng->vrf->vrf_id); + } +} + +void ripng_redistribute_write(struct vty *vty, struct ripng *ripng) +{ + int i; + + for (i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (i == zclient->redist_default + || !ripng_redistribute_check(ripng, i)) + continue; + + vty_out(vty, " %s", zebra_route_string(i)); + } +} + +void ripng_zebra_vrf_register(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: register VRF %s(%u) to zebra", __func__, + vrf->name, vrf->vrf_id); + + zclient_send_reg_requests(zclient, vrf->vrf_id); +} + +void ripng_zebra_vrf_deregister(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: deregister VRF %s(%u) from zebra.", __func__, + vrf->name, vrf->vrf_id); + + zclient_send_dereg_requests(zclient, vrf->vrf_id); +} + +static void ripng_zebra_connected(struct zclient *zclient) +{ + zclient_send_reg_requests(zclient, VRF_DEFAULT); +} + +static zclient_handler *const ripng_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = ripng_interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = ripng_interface_address_delete, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = ripng_zebra_read_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = ripng_zebra_read_route, +}; + +static void ripng_zebra_capabilities(struct zclient_capabilities *cap) +{ + zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count); +} + +/* Initialize zebra structure and it's commands. */ +void zebra_init(struct event_loop *master) +{ + /* Allocate zebra structure. */ + zclient = zclient_new(master, &zclient_options_default, ripng_handlers, + array_size(ripng_handlers)); + zclient_init(zclient, ZEBRA_ROUTE_RIPNG, 0, &ripngd_privs); + + zclient->zebra_connected = ripng_zebra_connected; + zclient->zebra_capabilities = ripng_zebra_capabilities; +} + +void ripng_zebra_stop(void) +{ + zclient_stop(zclient); + zclient_free(zclient); +} diff --git a/ripngd/ripngd.c b/ripngd/ripngd.c new file mode 100644 index 0000000..f4dadf3 --- /dev/null +++ b/ripngd/ripngd.c @@ -0,0 +1,2673 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* RIPng daemon + * Copyright (C) 1998, 1999 Kunihiro Ishiguro + */ + +#include + +#include "prefix.h" +#include "filter.h" +#include "log.h" +#include "frrevent.h" +#include "memory.h" +#include "if.h" +#include "stream.h" +#include "agg_table.h" +#include "command.h" +#include "sockopt.h" +#include "distribute.h" +#include "plist.h" +#include "routemap.h" +#include "if_rmap.h" +#include "privs.h" +#include "lib_errors.h" +#include "northbound_cli.h" +#include "network.h" +#include "mgmt_be_client.h" + +#include "ripngd/ripngd.h" +#include "ripngd/ripng_route.h" +#include "ripngd/ripng_debug.h" +#include "ripngd/ripng_nexthop.h" + +DEFINE_MGROUP(RIPNGD, "ripngd"); +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG, "RIPng structure"); +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_VRF_NAME, "RIPng VRF name"); +DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_ROUTE, "RIPng route info"); + +enum { ripng_all_route, + ripng_changed_route, +}; + +static void ripng_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist); + +/* Prototypes. */ +void ripng_output_process(struct interface *, struct sockaddr_in6 *, int); +static void ripng_instance_enable(struct ripng *ripng, struct vrf *vrf, + int sock); +static void ripng_instance_disable(struct ripng *ripng); +static void ripng_triggered_update(struct event *); +static void ripng_if_rmap_update(struct if_rmap_ctx *ctx, + struct if_rmap *if_rmap); + +/* Generate rb-tree of RIPng instances. */ +static inline int ripng_instance_compare(const struct ripng *a, + const struct ripng *b) +{ + return strcmp(a->vrf_name, b->vrf_name); +} +RB_GENERATE(ripng_instance_head, ripng, entry, ripng_instance_compare) + +struct ripng_instance_head ripng_instances = RB_INITIALIZER(&ripng_instances); + +/* RIPng next hop specification. */ +struct ripng_nexthop { + enum ripng_nexthop_type { + RIPNG_NEXTHOP_UNSPEC, + RIPNG_NEXTHOP_ADDRESS + } flag; + struct in6_addr address; +}; + +int ripng_route_rte(struct ripng_info *rinfo) +{ + return (rinfo->type == ZEBRA_ROUTE_RIPNG + && rinfo->sub_type == RIPNG_ROUTE_RTE); +} + +/* Allocate new ripng information. */ +struct ripng_info *ripng_info_new(void) +{ + struct ripng_info *new; + + new = XCALLOC(MTYPE_RIPNG_ROUTE, sizeof(struct ripng_info)); + return new; +} + +/* Free ripng information. */ +void ripng_info_free(struct ripng_info *rinfo) +{ + XFREE(MTYPE_RIPNG_ROUTE, rinfo); +} + +struct ripng *ripng_info_get_instance(const struct ripng_info *rinfo) +{ + return agg_get_table_info(agg_get_table(rinfo->rp)); +} + +/* Create ripng socket. */ +int ripng_make_socket(struct vrf *vrf) +{ + int ret; + int sock; + struct sockaddr_in6 ripaddr; + const char *vrf_dev = NULL; + + /* Make datagram socket. */ + if (vrf->vrf_id != VRF_DEFAULT) + vrf_dev = vrf->name; + frr_with_privs(&ripngd_privs) { + sock = vrf_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, + vrf->vrf_id, vrf_dev); + if (sock < 0) { + flog_err_sys(EC_LIB_SOCKET, + "Cannot create UDP socket: %s", + safe_strerror(errno)); + return -1; + } + } + + sockopt_reuseaddr(sock); + sockopt_reuseport(sock); + setsockopt_so_recvbuf(sock, 8096); + ret = setsockopt_ipv6_pktinfo(sock, 1); + if (ret < 0) + goto error; +#ifdef IPTOS_PREC_INTERNETCONTROL + ret = setsockopt_ipv6_tclass(sock, IPTOS_PREC_INTERNETCONTROL); + if (ret < 0) + goto error; +#endif + ret = setsockopt_ipv6_multicast_hops(sock, 255); + if (ret < 0) + goto error; + ret = setsockopt_ipv6_multicast_loop(sock, 0); + if (ret < 0) + goto error; + ret = setsockopt_ipv6_hoplimit(sock, 1); + if (ret < 0) + goto error; + + memset(&ripaddr, 0, sizeof(ripaddr)); + ripaddr.sin6_family = AF_INET6; +#ifdef SIN6_LEN + ripaddr.sin6_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + ripaddr.sin6_port = htons(RIPNG_PORT_DEFAULT); + + frr_with_privs(&ripngd_privs) { + ret = bind(sock, (struct sockaddr *)&ripaddr, sizeof(ripaddr)); + if (ret < 0) { + zlog_err("Can't bind ripng socket: %s.", + safe_strerror(errno)); + goto error; + } + } + return sock; + +error: + close(sock); + return ret; +} + +/* Send RIPng packet. */ +int ripng_send_packet(caddr_t buf, int bufsize, struct sockaddr_in6 *to, + struct interface *ifp) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + int ret; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsgptr; + char adata[256] = {}; + struct in6_pktinfo *pkt; + struct sockaddr_in6 addr; + + if (IS_RIPNG_DEBUG_SEND) { + if (to) + zlog_debug("send to %pI6", &to->sin6_addr); + zlog_debug(" send interface %s", ifp->name); + zlog_debug(" send packet size %d", bufsize); + } + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; +#ifdef SIN6_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + addr.sin6_flowinfo = htonl(RIPNG_PRIORITY_DEFAULT); + + /* When destination is specified. */ + if (to != NULL) { + addr.sin6_addr = to->sin6_addr; + addr.sin6_port = to->sin6_port; + } else { + inet_pton(AF_INET6, RIPNG_GROUP, &addr.sin6_addr); + addr.sin6_port = htons(RIPNG_PORT_DEFAULT); + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)&addr; + msg.msg_namelen = sizeof(struct sockaddr_in6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = adata; + msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + + iov.iov_base = buf; + iov.iov_len = bufsize; + + cmsgptr = (struct cmsghdr *)adata; + cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmsgptr->cmsg_level = IPPROTO_IPV6; + cmsgptr->cmsg_type = IPV6_PKTINFO; + + pkt = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); + memset(&pkt->ipi6_addr, 0, sizeof(struct in6_addr)); + pkt->ipi6_ifindex = ifp->ifindex; + + ret = sendmsg(ripng->sock, &msg, 0); + + if (ret < 0) { + if (to) + flog_err_sys(EC_LIB_SOCKET, + "RIPng send fail on %s to %pI6: %s", + ifp->name, &to->sin6_addr, + safe_strerror(errno)); + else + flog_err_sys(EC_LIB_SOCKET, "RIPng send fail on %s: %s", + ifp->name, safe_strerror(errno)); + } + + return ret; +} + +/* Receive UDP RIPng packet from socket. */ +static int ripng_recv_packet(int sock, uint8_t *buf, int bufsize, + struct sockaddr_in6 *from, ifindex_t *ifindex, + int *hoplimit) +{ + int ret; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsgptr; + struct in6_addr dst = {.s6_addr = {0}}; + + memset(&dst, 0, sizeof(dst)); + + /* Ancillary data. This store cmsghdr and in6_pktinfo. But at this + point I can't determine size of cmsghdr */ + char adata[1024]; + + /* Fill in message and iovec. */ + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)from; + msg.msg_namelen = sizeof(struct sockaddr_in6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = (void *)adata; + msg.msg_controllen = sizeof(adata); + iov.iov_base = buf; + iov.iov_len = bufsize; + + /* If recvmsg fail return minus value. */ + ret = recvmsg(sock, &msg, 0); + if (ret < 0) + return ret; + + for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; + cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + /* I want interface index which this packet comes from. */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 + && cmsgptr->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *ptr; + + ptr = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); + *ifindex = ptr->ipi6_ifindex; + dst = ptr->ipi6_addr; + + if (*ifindex == 0) + zlog_warn( + "Interface index returned by IPV6_PKTINFO is zero"); + } + + /* Incoming packet's multicast hop limit. */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 + && cmsgptr->cmsg_type == IPV6_HOPLIMIT) { + int *phoplimit = (int *)CMSG_DATA(cmsgptr); + *hoplimit = *phoplimit; + } + } + + /* Hoplimit check shold be done when destination address is + multicast address. */ + if (!IN6_IS_ADDR_MULTICAST(&dst)) + *hoplimit = -1; + + return ret; +} + +/* Dump rip packet */ +void ripng_packet_dump(struct ripng_packet *packet, int size, + const char *sndrcv) +{ + caddr_t lim; + struct rte *rte; + const char *command_str; + + /* Set command string. */ + if (packet->command == RIPNG_REQUEST) + command_str = "request"; + else if (packet->command == RIPNG_RESPONSE) + command_str = "response"; + else + command_str = "unknown"; + + /* Dump packet header. */ + zlog_debug("%s %s version %d packet size %d", sndrcv, command_str, + packet->version, size); + + /* Dump each routing table entry. */ + rte = packet->rte; + + for (lim = (caddr_t)packet + size; (caddr_t)rte < lim; rte++) { + if (rte->metric == RIPNG_METRIC_NEXTHOP) + zlog_debug(" nexthop %pI6/%d", &rte->addr, + rte->prefixlen); + else + zlog_debug(" %pI6/%d metric %d tag %" ROUTE_TAG_PRI, + &rte->addr, rte->prefixlen, + rte->metric, (route_tag_t)ntohs(rte->tag)); + } +} + +/* RIPng next hop address RTE (Route Table Entry). */ +static void ripng_nexthop_rte(struct rte *rte, struct sockaddr_in6 *from, + struct ripng_nexthop *nexthop) +{ + /* Logging before checking RTE. */ + if (IS_RIPNG_DEBUG_RECV) + zlog_debug("RIPng nexthop RTE address %pI6 tag %" ROUTE_TAG_PRI + " prefixlen %d", + &rte->addr, (route_tag_t)ntohs(rte->tag), + rte->prefixlen); + + /* RFC2080 2.1.1 Next Hop: + The route tag and prefix length in the next hop RTE must be + set to zero on sending and ignored on receiption. */ + if (ntohs(rte->tag) != 0) + zlog_warn( + "RIPng nexthop RTE with non zero tag value %" ROUTE_TAG_PRI + " from %pI6", + (route_tag_t)ntohs(rte->tag), &from->sin6_addr); + + if (rte->prefixlen != 0) + zlog_warn( + "RIPng nexthop RTE with non zero prefixlen value %d from %pI6", + rte->prefixlen, &from->sin6_addr); + + /* Specifying a value of 0:0:0:0:0:0:0:0 in the prefix field of a + next hop RTE indicates that the next hop address should be the + originator of the RIPng advertisement. An address specified as a + next hop must be a link-local address. */ + if (IN6_IS_ADDR_UNSPECIFIED(&rte->addr)) { + nexthop->flag = RIPNG_NEXTHOP_UNSPEC; + memset(&nexthop->address, 0, sizeof(struct in6_addr)); + return; + } + + if (IN6_IS_ADDR_LINKLOCAL(&rte->addr)) { + nexthop->flag = RIPNG_NEXTHOP_ADDRESS; + IPV6_ADDR_COPY(&nexthop->address, &rte->addr); + return; + } + + /* The purpose of the next hop RTE is to eliminate packets being + routed through extra hops in the system. It is particularly useful + when RIPng is not being run on all of the routers on a network. + Note that next hop RTE is "advisory". That is, if the provided + information is ignored, a possibly sub-optimal, but absolutely + valid, route may be taken. If the received next hop address is not + a link-local address, it should be treated as 0:0:0:0:0:0:0:0. */ + zlog_warn("RIPng nexthop RTE with non link-local address %pI6 from %pI6", + &rte->addr, &from->sin6_addr); + + nexthop->flag = RIPNG_NEXTHOP_UNSPEC; + memset(&nexthop->address, 0, sizeof(struct in6_addr)); + + return; +} + +/* If ifp has same link-local address then return 1. */ +static int ripng_lladdr_check(struct interface *ifp, struct in6_addr *addr) +{ + struct connected *connected; + struct prefix *p; + + frr_each (if_connected, ifp->connected, connected) { + p = connected->address; + + if (p->family == AF_INET6 + && IN6_IS_ADDR_LINKLOCAL(&p->u.prefix6) + && IN6_ARE_ADDR_EQUAL(&p->u.prefix6, addr)) + return 1; + } + return 0; +} + +/* RIPng route garbage collect timer. */ +static void ripng_garbage_collect(struct event *t) +{ + struct ripng_info *rinfo; + struct agg_node *rp; + + rinfo = EVENT_ARG(t); + + /* Off timeout timer. */ + EVENT_OFF(rinfo->t_timeout); + + /* Get route_node pointer. */ + rp = rinfo->rp; + + /* Unlock route_node. */ + listnode_delete(rp->info, rinfo); + if (list_isempty((struct list *)rp->info)) { + list_delete((struct list **)&rp->info); + agg_unlock_node(rp); + } + + /* Free RIPng routing information. */ + ripng_info_free(rinfo); +} + +static void ripng_timeout_update(struct ripng *ripng, struct ripng_info *rinfo); + +/* Add new route to the ECMP list. + * RETURN: the new entry added in the list, or NULL if it is not the first + * entry and ECMP is not allowed. + */ +struct ripng_info *ripng_ecmp_add(struct ripng *ripng, + struct ripng_info *rinfo_new) +{ + struct agg_node *rp = rinfo_new->rp; + struct ripng_info *rinfo = NULL; + struct ripng_info *rinfo_exist = NULL; + struct list *list = NULL; + struct listnode *node = NULL; + struct listnode *nnode = NULL; + + if (rp->info == NULL) + rp->info = list_new(); + list = (struct list *)rp->info; + + /* If ECMP is not allowed and some entry already exists in the list, + * do nothing. */ + if (listcount(list) && !ripng->ecmp) + return NULL; + + /* Add or replace an existing ECMP path with lower neighbor IP */ + if (listcount(list) && listcount(list) >= ripng->ecmp) { + struct ripng_info *from_highest = NULL; + + /* Find the rip_info struct that has the highest nexthop IP */ + for (ALL_LIST_ELEMENTS(list, node, nnode, rinfo_exist)) + if (!from_highest || + (from_highest && + IPV6_ADDR_CMP(&rinfo_exist->from, + &from_highest->from) > 0)) { + from_highest = rinfo_exist; + } + + /* If we have a route in ECMP group, delete the old + * one that has a higher next-hop address. Lower IP is + * preferred. + */ + if (ripng->ecmp > 1 && from_highest && + IPV6_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) { + ripng_ecmp_delete(ripng, from_highest); + goto add_or_replace; + } + + return NULL; + } + +add_or_replace: + rinfo = ripng_info_new(); + memcpy(rinfo, rinfo_new, sizeof(struct ripng_info)); + listnode_add(list, rinfo); + + if (ripng_route_rte(rinfo)) { + ripng_timeout_update(ripng, rinfo); + ripng_zebra_ipv6_add(ripng, rp); + } + + ripng_aggregate_increment(rp, rinfo); + + /* Set the route change flag on the first entry. */ + rinfo = listgetdata(listhead(list)); + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + /* Signal the output process to trigger an update. */ + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Update ECMP routes to zebra when `allow-ecmp` changed. */ +void ripng_ecmp_change(struct ripng *ripng) +{ + struct agg_node *rp; + struct ripng_info *rinfo; + struct list *list; + struct listnode *node, *nextnode; + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { + list = rp->info; + if (list && listcount(list) > 1) { + while (listcount(list) > ripng->ecmp) { + struct ripng_info *from_highest = NULL; + + for (ALL_LIST_ELEMENTS(list, node, nextnode, + rinfo)) { + if (!from_highest || + (from_highest && + IPV6_ADDR_CMP( + &rinfo->from, + &from_highest->from) > 0)) + from_highest = rinfo; + } + + ripng_ecmp_delete(ripng, from_highest); + } + } + } +} + +/* Replace the ECMP list with the new route. + * RETURN: the new entry added in the list + */ +struct ripng_info *ripng_ecmp_replace(struct ripng *ripng, + struct ripng_info *rinfo_new) +{ + struct agg_node *rp = rinfo_new->rp; + struct list *list = (struct list *)rp->info; + struct ripng_info *rinfo = NULL, *tmp_rinfo = NULL; + struct listnode *node = NULL, *nextnode = NULL; + + if (list == NULL || listcount(list) == 0) + return ripng_ecmp_add(ripng, rinfo_new); + + /* Get the first entry */ + rinfo = listgetdata(listhead(list)); + + /* Learnt route replaced by a local one. Delete it from zebra. */ + if (ripng_route_rte(rinfo) && !ripng_route_rte(rinfo_new)) + if (CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) + ripng_zebra_ipv6_delete(ripng, rp); + + if (rinfo->metric != RIPNG_METRIC_INFINITY) + ripng_aggregate_decrement_list(rp, list); + + /* Re-use the first entry, and delete the others. */ + for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) + if (tmp_rinfo != rinfo) { + EVENT_OFF(tmp_rinfo->t_timeout); + EVENT_OFF(tmp_rinfo->t_garbage_collect); + list_delete_node(list, node); + ripng_info_free(tmp_rinfo); + } + + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + memcpy(rinfo, rinfo_new, sizeof(struct ripng_info)); + + if (ripng_route_rte(rinfo)) { + ripng_timeout_update(ripng, rinfo); + /* The ADD message implies an update. */ + ripng_zebra_ipv6_add(ripng, rp); + } + + ripng_aggregate_increment(rp, rinfo); + + /* Set the route change flag. */ + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + /* Signal the output process to trigger an update. */ + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Delete one route from the ECMP list. + * RETURN: + * null - the entry is freed, and other entries exist in the list + * the entry - the entry is the last one in the list; its metric is set + * to INFINITY, and the garbage collector is started for it + */ +struct ripng_info *ripng_ecmp_delete(struct ripng *ripng, + struct ripng_info *rinfo) +{ + struct agg_node *rp = rinfo->rp; + struct list *list = (struct list *)rp->info; + + EVENT_OFF(rinfo->t_timeout); + + if (rinfo->metric != RIPNG_METRIC_INFINITY) + ripng_aggregate_decrement(rp, rinfo); + + if (listcount(list) > 1) { + /* Some other ECMP entries still exist. Just delete this entry. + */ + EVENT_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + if (ripng_route_rte(rinfo) && + CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) + /* The ADD message implies the update. */ + ripng_zebra_ipv6_add(ripng, rp); + ripng_info_free(rinfo); + rinfo = NULL; + } else { + assert(rinfo == listgetdata(listhead(list))); + + /* This is the only entry left in the list. We must keep it in + * the list for garbage collection time, with INFINITY metric. + */ + + rinfo->metric = RIPNG_METRIC_INFINITY; + RIPNG_TIMER_ON(rinfo->t_garbage_collect, ripng_garbage_collect, + ripng->garbage_time); + + if (ripng_route_rte(rinfo) && + CHECK_FLAG(rinfo->flags, RIPNG_RTF_FIB)) + ripng_zebra_ipv6_delete(ripng, rp); + } + + /* Set the route change flag on the first entry. */ + rinfo = listgetdata(listhead(list)); + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + /* Signal the output process to trigger an update. */ + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + + return rinfo; +} + +/* Timeout RIPng routes. */ +static void ripng_timeout(struct event *t) +{ + struct ripng_info *rinfo = EVENT_ARG(t); + struct ripng *ripng = ripng_info_get_instance(rinfo); + + ripng_ecmp_delete(ripng, rinfo); +} + +static void ripng_timeout_update(struct ripng *ripng, struct ripng_info *rinfo) +{ + if (rinfo->metric != RIPNG_METRIC_INFINITY) { + EVENT_OFF(rinfo->t_timeout); + event_add_timer(master, ripng_timeout, rinfo, + ripng->timeout_time, &rinfo->t_timeout); + } +} + +static int ripng_filter(int ripng_distribute, struct prefix_ipv6 *p, + struct ripng_interface *ri) +{ + struct distribute *dist; + struct access_list *alist; + struct prefix_list *plist; + int distribute = ripng_distribute == RIPNG_FILTER_OUT + ? DISTRIBUTE_V6_OUT + : DISTRIBUTE_V6_IN; + const char *inout = ripng_distribute == RIPNG_FILTER_OUT ? "out" : "in"; + + /* Input distribute-list filtering. */ + if (ri->list[ripng_distribute]) { + if (access_list_apply(ri->list[ripng_distribute], + (struct prefix *)p) == FILTER_DENY) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug("%pFX filtered by distribute %s", p, + inout); + return -1; + } + } + if (ri->prefix[ripng_distribute]) { + if (prefix_list_apply(ri->prefix[ripng_distribute], + (struct prefix *)p) == PREFIX_DENY) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug("%pFX filtered by prefix-list %s", p, + inout); + return -1; + } + } + + /* All interface filter check. */ + dist = distribute_lookup(ri->ripng->distribute_ctx, NULL); + if (dist) { + if (dist->list[distribute]) { + alist = access_list_lookup(AFI_IP6, + dist->list[distribute]); + + if (alist) { + if (access_list_apply(alist, (struct prefix *)p) + == FILTER_DENY) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "%pFX filtered by distribute %s", + p, inout); + return -1; + } + } + } + if (dist->prefix[distribute]) { + plist = prefix_list_lookup(AFI_IP6, + dist->prefix[distribute]); + + if (plist) { + if (prefix_list_apply(plist, (struct prefix *)p) + == PREFIX_DENY) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "%pFX filtered by prefix-list %s", + p, inout); + return -1; + } + } + } + } + return 0; +} + +/* Process RIPng route according to RFC2080. */ +static void ripng_route_process(struct rte *rte, struct sockaddr_in6 *from, + struct ripng_nexthop *ripng_nexthop, + struct interface *ifp) +{ + int ret; + struct prefix_ipv6 p; + struct agg_node *rp; + struct ripng_info *rinfo = NULL, newinfo; + struct ripng_interface *ri; + struct ripng *ripng; + struct in6_addr *nexthop; + int same = 0; + struct list *list = NULL; + struct listnode *node = NULL; + + /* Make prefix structure. */ + memset(&p, 0, sizeof(struct prefix_ipv6)); + p.family = AF_INET6; + /* p.prefix = rte->addr; */ + IPV6_ADDR_COPY(&p.prefix, &rte->addr); + p.prefixlen = rte->prefixlen; + + /* Make sure mask is applied. */ + /* XXX We have to check the prefix is valid or not before call + apply_mask_ipv6. */ + apply_mask_ipv6(&p); + + ri = ifp->info; + ripng = ri->ripng; + + /* Apply input filters. */ + ret = ripng_filter(RIPNG_FILTER_IN, &p, ri); + if (ret < 0) + return; + + memset(&newinfo, 0, sizeof(newinfo)); + newinfo.type = ZEBRA_ROUTE_RIPNG; + newinfo.sub_type = RIPNG_ROUTE_RTE; + if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) + newinfo.nexthop = ripng_nexthop->address; + else + newinfo.nexthop = from->sin6_addr; + newinfo.from = from->sin6_addr; + newinfo.ifindex = ifp->ifindex; + newinfo.metric = rte->metric; + newinfo.metric_out = rte->metric; /* XXX */ + newinfo.tag = ntohs(rte->tag); /* XXX */ + + /* Modify entry. */ + if (ri->routemap[RIPNG_FILTER_IN]) { + ret = route_map_apply(ri->routemap[RIPNG_FILTER_IN], + (struct prefix *)&p, &newinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "RIPng %pFX is filtered by route-map in", + &p); + return; + } + + /* Get back the object */ + if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) { + if (!IPV6_ADDR_SAME(&newinfo.nexthop, + &ripng_nexthop->address)) { + /* the nexthop get changed by the routemap */ + if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop)) + ripng_nexthop->address = + newinfo.nexthop; + else + ripng_nexthop->address = in6addr_any; + } + } else { + if (!IPV6_ADDR_SAME(&newinfo.nexthop, + &from->sin6_addr)) { + /* the nexthop get changed by the routemap */ + if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop)) { + ripng_nexthop->flag = + RIPNG_NEXTHOP_ADDRESS; + ripng_nexthop->address = + newinfo.nexthop; + } + } + } + rte->tag = htons(newinfo.tag_out); /* XXX */ + rte->metric = newinfo.metric_out; /* XXX: the routemap uses the + metric_out field */ + } + + /* Once the entry has been validated, update the metric by + * adding the cost of the network on wich the message + * arrived. If the result is greater than infinity, use infinity + * (RFC2453 Sec. 3.9.2) + **/ + + /* Zebra ripngd can handle offset-list in. */ + ret = ripng_offset_list_apply_in(ripng, &p, ifp, &rte->metric); + + /* If offset-list does not modify the metric use interface's + * one. */ + if (!ret) + rte->metric += ifp->metric ? ifp->metric : 1; + + if (rte->metric > RIPNG_METRIC_INFINITY) + rte->metric = RIPNG_METRIC_INFINITY; + + /* Set nexthop pointer. */ + if (ripng_nexthop->flag == RIPNG_NEXTHOP_ADDRESS) + nexthop = &ripng_nexthop->address; + else + nexthop = &from->sin6_addr; + + /* Lookup RIPng routing table. */ + rp = agg_node_get(ripng->table, (struct prefix *)&p); + + newinfo.rp = rp; + newinfo.nexthop = *nexthop; + newinfo.metric = rte->metric; + newinfo.tag = ntohs(rte->tag); + + /* Check to see whether there is already RIPng route on the table. */ + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, node, rinfo)) { + /* Need to compare with redistributed entry or local + * entry */ + if (!ripng_route_rte(rinfo)) + break; + + if (IPV6_ADDR_SAME(&rinfo->from, &from->sin6_addr) + && IPV6_ADDR_SAME(&rinfo->nexthop, nexthop)) + break; + + if (!listnextnode(node)) { + /* Not found in the list */ + + if (rte->metric > rinfo->metric) { + /* New route has a greater metric. + * Discard it. */ + agg_unlock_node(rp); + return; + } + + if (rte->metric < rinfo->metric) + /* New route has a smaller metric. + * Replace the ECMP list + * with the new one in below. */ + break; + + /* Metrics are same. Unless ECMP is disabled, + * keep "rinfo" null and + * the new route is added in the ECMP list in + * below. */ + if (!ripng->ecmp) + break; + } + } + + if (rinfo) { + /* Redistributed route check. */ + if (rinfo->type != ZEBRA_ROUTE_RIPNG + && rinfo->metric != RIPNG_METRIC_INFINITY) { + agg_unlock_node(rp); + return; + } + + /* Local static route. */ + if (rinfo->type == ZEBRA_ROUTE_RIPNG + && ((rinfo->sub_type == RIPNG_ROUTE_STATIC) + || (rinfo->sub_type == RIPNG_ROUTE_DEFAULT)) + && rinfo->metric != RIPNG_METRIC_INFINITY) { + agg_unlock_node(rp); + return; + } + } + + if (!rinfo) { + /* Now, check to see whether there is already an explicit route + for the destination prefix. If there is no such route, add + this route to the routing table, unless the metric is + infinity (there is no point in adding a route which + unusable). */ + if (rte->metric != RIPNG_METRIC_INFINITY) + ripng_ecmp_add(ripng, &newinfo); + else + agg_unlock_node(rp); + } else { + /* If there is an existing route, compare the next hop address + to the address of the router from which the datagram came. + If this datagram is from the same router as the existing + route, reinitialize the timeout. */ + same = (IN6_ARE_ADDR_EQUAL(&rinfo->from, &from->sin6_addr) + && (rinfo->ifindex == ifp->ifindex)); + + /* + * RFC 2080 - Section 2.4.2: + * "If the new metric is the same as the old one, examine the + * timeout + * for the existing route. If it is at least halfway to the + * expiration + * point, switch to the new route. This heuristic is optional, + * but + * highly recommended". + */ + if (!ripng->ecmp && !same && rinfo->metric == rte->metric && + rinfo->t_timeout && + (event_timer_remain_second(rinfo->t_timeout) < + (ripng->timeout_time / 2))) { + ripng_ecmp_replace(ripng, &newinfo); + } + /* Next, compare the metrics. If the datagram is from the same + router as the existing route, and the new metric is different + than the old one; or, if the new metric is lower than the old + one; do the following actions: */ + else if ((same && rinfo->metric != rte->metric) || + rte->metric < rinfo->metric) { + if (listcount(list) == 1) { + if (newinfo.metric != RIPNG_METRIC_INFINITY) + ripng_ecmp_replace(ripng, &newinfo); + else + ripng_ecmp_delete(ripng, rinfo); + } else { + if (newinfo.metric < rinfo->metric) + ripng_ecmp_replace(ripng, &newinfo); + else /* newinfo.metric > rinfo->metric */ + ripng_ecmp_delete(ripng, rinfo); + } + } else /* same & no change */ + ripng_timeout_update(ripng, rinfo); + + /* Unlock tempolary lock of the route. */ + agg_unlock_node(rp); + } +} + +/* Add redistributed route to RIPng table. */ +void ripng_redistribute_add(struct ripng *ripng, int type, int sub_type, + struct prefix_ipv6 *p, ifindex_t ifindex, + struct in6_addr *nexthop, route_tag_t tag) +{ + struct agg_node *rp; + struct ripng_info *rinfo = NULL, newinfo; + struct list *list = NULL; + + /* Redistribute route */ + if (IN6_IS_ADDR_LINKLOCAL(&p->prefix)) + return; + if (IN6_IS_ADDR_LOOPBACK(&p->prefix)) + return; + + rp = agg_node_get(ripng->table, (struct prefix *)p); + + memset(&newinfo, 0, sizeof(newinfo)); + newinfo.type = type; + newinfo.sub_type = sub_type; + newinfo.ifindex = ifindex; + newinfo.metric = 1; + if (tag <= UINT16_MAX) /* RIPng only supports 16 bit tags */ + newinfo.tag = tag; + newinfo.rp = rp; + if (nexthop && IN6_IS_ADDR_LINKLOCAL(nexthop)) + newinfo.nexthop = *nexthop; + + if ((list = rp->info) != NULL && listcount(list) != 0) { + rinfo = listgetdata(listhead(list)); + + if (rinfo->type == ZEBRA_ROUTE_CONNECT + && rinfo->sub_type == RIPNG_ROUTE_INTERFACE + && rinfo->metric != RIPNG_METRIC_INFINITY) { + agg_unlock_node(rp); + return; + } + + /* Manually configured RIPng route check. + * They have the precedence on all the other entries. + **/ + if (rinfo->type == ZEBRA_ROUTE_RIPNG + && ((rinfo->sub_type == RIPNG_ROUTE_STATIC) + || (rinfo->sub_type == RIPNG_ROUTE_DEFAULT))) { + if (type != ZEBRA_ROUTE_RIPNG + || ((sub_type != RIPNG_ROUTE_STATIC) + && (sub_type != RIPNG_ROUTE_DEFAULT))) { + agg_unlock_node(rp); + return; + } + } + + ripng_ecmp_replace(ripng, &newinfo); + agg_unlock_node(rp); + } else + ripng_ecmp_add(ripng, &newinfo); + + if (IS_RIPNG_DEBUG_EVENT) { + if (!nexthop) + zlog_debug( + "Redistribute new prefix %pFX on the interface %s", + p, ifindex2ifname(ifindex, ripng->vrf->vrf_id)); + else + zlog_debug( + "Redistribute new prefix %pFX with nexthop %pI6 on the interface %s", + p, nexthop, + ifindex2ifname(ifindex, ripng->vrf->vrf_id)); + } + + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); +} + +/* Delete redistributed route to RIPng table. */ +void ripng_redistribute_delete(struct ripng *ripng, int type, int sub_type, + struct prefix_ipv6 *p, ifindex_t ifindex) +{ + struct agg_node *rp; + struct ripng_info *rinfo; + + if (IN6_IS_ADDR_LINKLOCAL(&p->prefix)) + return; + if (IN6_IS_ADDR_LOOPBACK(&p->prefix)) + return; + + rp = agg_node_lookup(ripng->table, (struct prefix *)p); + + if (rp) { + struct list *list = rp->info; + + if (list != NULL && listcount(list) != 0) { + rinfo = listgetdata(listhead(list)); + if (rinfo != NULL && rinfo->type == type + && rinfo->sub_type == sub_type + && rinfo->ifindex == ifindex) { + /* Perform poisoned reverse. */ + rinfo->metric = RIPNG_METRIC_INFINITY; + RIPNG_TIMER_ON(rinfo->t_garbage_collect, + ripng_garbage_collect, + ripng->garbage_time); + EVENT_OFF(rinfo->t_timeout); + + /* Aggregate count decrement. */ + ripng_aggregate_decrement(rp, rinfo); + + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug( + "Poisone %pFX on the interface %s with an infinity metric [delete]", + p, + ifindex2ifname( + ifindex, + ripng->vrf->vrf_id)); + + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + } + } + agg_unlock_node(rp); + } +} + +/* Withdraw redistributed route. */ +void ripng_redistribute_withdraw(struct ripng *ripng, int type) +{ + struct agg_node *rp; + struct ripng_info *rinfo = NULL; + struct list *list = NULL; + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) + if ((list = rp->info) != NULL) { + rinfo = listgetdata(listhead(list)); + if ((rinfo->type == type) + && (rinfo->sub_type != RIPNG_ROUTE_INTERFACE)) { + /* Perform poisoned reverse. */ + rinfo->metric = RIPNG_METRIC_INFINITY; + RIPNG_TIMER_ON(rinfo->t_garbage_collect, + ripng_garbage_collect, + ripng->garbage_time); + EVENT_OFF(rinfo->t_timeout); + + /* Aggregate count decrement. */ + ripng_aggregate_decrement(rp, rinfo); + + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + if (IS_RIPNG_DEBUG_EVENT) { + struct prefix_ipv6 *p = + (struct prefix_ipv6 *) + agg_node_get_prefix(rp); + + zlog_debug( + "Poisone %pFX on the interface %s [withdraw]", + p, + ifindex2ifname( + rinfo->ifindex, + ripng->vrf->vrf_id)); + } + + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + } + } +} + +/* RIP routing information. */ +static void ripng_response_process(struct ripng_packet *packet, int size, + struct sockaddr_in6 *from, + struct interface *ifp, int hoplimit) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + caddr_t lim; + struct rte *rte; + struct ripng_nexthop nexthop; + + /* RFC2080 2.4.2 Response Messages: + The Response must be ignored if it is not from the RIPng port. */ + if (ntohs(from->sin6_port) != RIPNG_PORT_DEFAULT) { + zlog_warn("RIPng packet comes from non RIPng port %d from %pI6", + ntohs(from->sin6_port), &from->sin6_addr); + ripng_peer_bad_packet(ripng, from); + return; + } + + /* The datagram's IPv6 source address should be checked to see + whether the datagram is from a valid neighbor; the source of the + datagram must be a link-local address. */ + if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { + zlog_warn("RIPng packet comes from non link local address %pI6", + &from->sin6_addr); + ripng_peer_bad_packet(ripng, from); + return; + } + + /* It is also worth checking to see whether the response is from one + of the router's own addresses. Interfaces on broadcast networks + may receive copies of their own multicasts immediately. If a + router processes its own output as new input, confusion is likely, + and such datagrams must be ignored. */ + if (ripng_lladdr_check(ifp, &from->sin6_addr)) { + zlog_warn( + "RIPng packet comes from my own link local address %pI6", + &from->sin6_addr); + ripng_peer_bad_packet(ripng, from); + return; + } + + /* As an additional check, periodic advertisements must have their + hop counts set to 255, and inbound, multicast packets sent from the + RIPng port (i.e. periodic advertisement or triggered update + packets) must be examined to ensure that the hop count is 255. */ + if (hoplimit >= 0 && hoplimit != 255) { + zlog_warn( + "RIPng packet comes with non 255 hop count %d from %pI6", + hoplimit, &from->sin6_addr); + ripng_peer_bad_packet(ripng, from); + return; + } + + /* Update RIPng peer. */ + ripng_peer_update(ripng, from, packet->version); + + /* Reset nexthop. */ + memset(&nexthop, 0, sizeof(nexthop)); + nexthop.flag = RIPNG_NEXTHOP_UNSPEC; + + /* Set RTE pointer. */ + rte = packet->rte; + + for (lim = ((caddr_t)packet) + size; (caddr_t)rte < lim; rte++) { + /* First of all, we have to check this RTE is next hop RTE or + not. Next hop RTE is completely different with normal RTE so + we need special treatment. */ + if (rte->metric == RIPNG_METRIC_NEXTHOP) { + ripng_nexthop_rte(rte, from, &nexthop); + continue; + } + + /* RTE information validation. */ + + /* - is the destination prefix valid (e.g., not a multicast + prefix and not a link-local address) A link-local address + should never be present in an RTE. */ + if (IN6_IS_ADDR_MULTICAST(&rte->addr)) { + zlog_warn( + "Destination prefix is a multicast address %pI6/%d [%d]", + &rte->addr, rte->prefixlen, rte->metric); + ripng_peer_bad_route(ripng, from); + continue; + } + if (IN6_IS_ADDR_LINKLOCAL(&rte->addr)) { + zlog_warn( + "Destination prefix is a link-local address %pI6/%d [%d]", + &rte->addr, rte->prefixlen, rte->metric); + ripng_peer_bad_route(ripng, from); + continue; + } + if (IN6_IS_ADDR_LOOPBACK(&rte->addr)) { + zlog_warn( + "Destination prefix is a loopback address %pI6/%d [%d]", + &rte->addr, rte->prefixlen, rte->metric); + ripng_peer_bad_route(ripng, from); + continue; + } + + /* - is the prefix length valid (i.e., between 0 and 128, + inclusive) */ + if (rte->prefixlen > IPV6_MAX_BITLEN) { + zlog_warn("Invalid prefix length %pI6/%d from %pI6%%%s", + &rte->addr, rte->prefixlen, + &from->sin6_addr, ifp->name); + ripng_peer_bad_route(ripng, from); + continue; + } + + /* - is the metric valid (i.e., between 1 and 16, inclusive) */ + if (!(rte->metric >= 1 && rte->metric <= 16)) { + zlog_warn("Invalid metric %d from %pI6%%%s", + rte->metric, &from->sin6_addr, ifp->name); + ripng_peer_bad_route(ripng, from); + continue; + } + + /* Vincent: XXX Should we compute the direclty reachable nexthop + * for our RIPng network ? + **/ + + /* Routing table updates. */ + ripng_route_process(rte, from, &nexthop, ifp); + } +} + +/* Response to request message. */ +static void ripng_request_process(struct ripng_packet *packet, int size, + struct sockaddr_in6 *from, + struct interface *ifp) +{ + struct ripng *ripng; + caddr_t lim; + struct rte *rte; + struct prefix_ipv6 p; + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_interface *ri; + + /* Does not reponse to the requests on the loopback interfaces */ + if (if_is_loopback(ifp)) + return; + + /* Check RIPng process is enabled on this interface. */ + ri = ifp->info; + if (!ri->running) + return; + ripng = ri->ripng; + + /* When passive interface is specified, suppress responses */ + if (ri->passive) + return; + + /* RIPng peer update. */ + ripng_peer_update(ripng, from, packet->version); + + lim = ((caddr_t)packet) + size; + rte = packet->rte; + + /* The Request is processed entry by entry. If there are no + entries, no response is given. */ + if (lim == (caddr_t)rte) + return; + + /* There is one special case. If there is exactly one entry in the + request, and it has a destination prefix of zero, a prefix length + of zero, and a metric of infinity (i.e., 16), then this is a + request to send the entire routing table. In that case, a call + is made to the output process to send the routing table to the + requesting address/port. */ + if (lim == ((caddr_t)(rte + 1)) && IN6_IS_ADDR_UNSPECIFIED(&rte->addr) + && rte->prefixlen == 0 && rte->metric == RIPNG_METRIC_INFINITY) { + /* All route with split horizon */ + ripng_output_process(ifp, from, ripng_all_route); + } else { + /* Except for this special case, processing is quite simple. + Examine the list of RTEs in the Request one by one. For each + entry, look up the destination in the router's routing + database and, if there is a route, put that route's metric in + the metric field of the RTE. If there is no explicit route + to the specified destination, put infinity in the metric + field. Once all the entries have been filled in, change the + command from Request to Response and send the datagram back + to the requestor. */ + memset(&p, 0, sizeof(p)); + p.family = AF_INET6; + + for (; ((caddr_t)rte) < lim; rte++) { + p.prefix = rte->addr; + p.prefixlen = rte->prefixlen; + apply_mask_ipv6(&p); + + rp = agg_node_lookup(ripng->table, (struct prefix *)&p); + + if (rp) { + rinfo = listgetdata( + listhead((struct list *)rp->info)); + rte->metric = rinfo->metric; + agg_unlock_node(rp); + } else + rte->metric = RIPNG_METRIC_INFINITY; + } + packet->command = RIPNG_RESPONSE; + + ripng_send_packet((caddr_t)packet, size, from, ifp); + } +} + +/* First entry point of reading RIPng packet. */ +static void ripng_read(struct event *thread) +{ + struct ripng *ripng = EVENT_ARG(thread); + int len; + int sock; + struct sockaddr_in6 from; + struct ripng_packet *packet; + ifindex_t ifindex = 0; + struct interface *ifp; + int hoplimit = -1; + + /* Check ripng is active and alive. */ + assert(ripng != NULL); + assert(ripng->sock >= 0); + + /* Fetch thread data and set read pointer to empty for event + managing. `sock' sould be same as ripng->sock. */ + sock = EVENT_FD(thread); + + /* Add myself to the next event. */ + ripng_event(ripng, RIPNG_READ, sock); + + /* Read RIPng packet. */ + len = ripng_recv_packet(sock, STREAM_DATA(ripng->ibuf), + STREAM_SIZE(ripng->ibuf), &from, &ifindex, + &hoplimit); + if (len < 0) { + zlog_warn("RIPng recvfrom failed (VRF %s): %s.", + ripng->vrf_name, safe_strerror(errno)); + return; + } + + /* Check RTE boundary. RTE size (Packet length - RIPng header size + (4)) must be multiple size of one RTE size (20). */ + if (((len - 4) % 20) != 0) { + zlog_warn("RIPng invalid packet size %d from %pI6 (VRF %s)", + len, &from.sin6_addr, ripng->vrf_name); + ripng_peer_bad_packet(ripng, &from); + return; + } + + packet = (struct ripng_packet *)STREAM_DATA(ripng->ibuf); + ifp = if_lookup_by_index(ifindex, ripng->vrf->vrf_id); + + /* RIPng packet received. */ + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug( + "RIPng packet received from %pI6 port %d on %s (VRF %s)", + &from.sin6_addr, ntohs(from.sin6_port), + ifp ? ifp->name : "unknown", ripng->vrf_name); + + /* Logging before packet checking. */ + if (IS_RIPNG_DEBUG_RECV) + ripng_packet_dump(packet, len, "RECV"); + + /* Packet comes from unknown interface. */ + if (ifp == NULL) { + zlog_warn( + "RIPng packet comes from unknown interface %d (VRF %s)", + ifindex, ripng->vrf_name); + return; + } + + /* Packet version mismatch checking. */ + if (packet->version != ripng->version) { + zlog_warn( + "RIPng packet version %d doesn't fit to my version %d (VRF %s)", + packet->version, ripng->version, ripng->vrf_name); + ripng_peer_bad_packet(ripng, &from); + return; + } + + /* Process RIPng packet. */ + switch (packet->command) { + case RIPNG_REQUEST: + ripng_request_process(packet, len, &from, ifp); + break; + case RIPNG_RESPONSE: + ripng_response_process(packet, len, &from, ifp, hoplimit); + break; + default: + zlog_warn("Invalid RIPng command %d (VRF %s)", packet->command, + ripng->vrf_name); + ripng_peer_bad_packet(ripng, &from); + break; + } +} + +/* Walk down the RIPng routing table then clear changed flag. */ +static void ripng_clear_changed_flag(struct ripng *ripng) +{ + struct agg_node *rp; + struct ripng_info *rinfo = NULL; + struct list *list = NULL; + struct listnode *listnode = NULL; + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + UNSET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + /* This flag can be set only on the first entry. + */ + break; + } +} + +/* Regular update of RIPng route. Send all routing formation to RIPng + enabled interface. */ +static void ripng_update(struct event *t) +{ + struct ripng *ripng = EVENT_ARG(t); + struct interface *ifp; + struct ripng_interface *ri; + + /* Logging update event. */ + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("RIPng update timer expired!"); + + /* Supply routes to each interface. */ + FOR_ALL_INTERFACES (ripng->vrf, ifp) { + ri = ifp->info; + + if (if_is_loopback(ifp) || !if_is_up(ifp)) + continue; + + if (!ri->running) + continue; + + /* When passive interface is specified, suppress announce to the + interface. */ + if (ri->passive) + continue; + +#ifdef RIPNG_ADVANCED + if (ri->ri_send == RIPNG_SEND_OFF) { + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug( + "[Event] RIPng send to if %d is suppressed by config", + ifp->ifindex); + continue; + } +#endif /* RIPNG_ADVANCED */ + + ripng_output_process(ifp, NULL, ripng_all_route); + } + + /* Triggered updates may be suppressed if a regular update is due by + the time the triggered update would be sent. */ + EVENT_OFF(ripng->t_triggered_interval); + ripng->trigger = 0; + + /* Reset flush event. */ + ripng_event(ripng, RIPNG_UPDATE_EVENT, 0); +} + +/* Triggered update interval timer. */ +static void ripng_triggered_interval(struct event *t) +{ + struct ripng *ripng = EVENT_ARG(t); + + if (ripng->trigger) { + ripng->trigger = 0; + ripng_triggered_update(t); + } +} + +/* Execute triggered update. */ +void ripng_triggered_update(struct event *t) +{ + struct ripng *ripng = EVENT_ARG(t); + struct interface *ifp; + struct ripng_interface *ri; + int interval; + + /* Cancel interval timer. */ + EVENT_OFF(ripng->t_triggered_interval); + ripng->trigger = 0; + + /* Logging triggered update. */ + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("RIPng triggered update!"); + + /* Split Horizon processing is done when generating triggered + updates as well as normal updates (see section 2.6). */ + FOR_ALL_INTERFACES (ripng->vrf, ifp) { + ri = ifp->info; + + if (if_is_loopback(ifp) || !if_is_up(ifp)) + continue; + + if (!ri->running) + continue; + + /* When passive interface is specified, suppress announce to the + interface. */ + if (ri->passive) + continue; + + ripng_output_process(ifp, NULL, ripng_changed_route); + } + + /* Once all of the triggered updates have been generated, the route + change flags should be cleared. */ + ripng_clear_changed_flag(ripng); + + /* After a triggered update is sent, a timer should be set for a + random interval between 1 and 5 seconds. If other changes that + would trigger updates occur before the timer expires, a single + update is triggered when the timer expires. */ + interval = (frr_weak_random() % 5) + 1; + + event_add_timer(master, ripng_triggered_interval, ripng, interval, + &ripng->t_triggered_interval); +} + +/* Write routing table entry to the stream and return next index of + the routing table entry in the stream. */ +int ripng_write_rte(int num, struct stream *s, struct prefix_ipv6 *p, + struct in6_addr *nexthop, uint16_t tag, uint8_t metric) +{ + /* RIPng packet header. */ + if (num == 0) { + stream_putc(s, RIPNG_RESPONSE); + stream_putc(s, RIPNG_V1); + stream_putw(s, 0); + } + + /* Write routing table entry. */ + if (!nexthop) { + assert(p); + stream_write(s, (uint8_t *)&p->prefix, sizeof(struct in6_addr)); + } else + stream_write(s, (uint8_t *)nexthop, sizeof(struct in6_addr)); + stream_putw(s, tag); + if (p) + stream_putc(s, p->prefixlen); + else + stream_putc(s, 0); + stream_putc(s, metric); + + return ++num; +} + +/* Send RESPONSE message to specified destination. */ +void ripng_output_process(struct interface *ifp, struct sockaddr_in6 *to, + int route_type) +{ + struct ripng *ripng; + int ret; + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_interface *ri; + struct ripng_aggregate *aggregate; + struct prefix_ipv6 *p; + struct list *ripng_rte_list; + struct list *list = NULL; + struct listnode *listnode = NULL; + + if (IS_RIPNG_DEBUG_EVENT) { + if (to) + zlog_debug("RIPng update routes to neighbor %pI6", + &to->sin6_addr); + else + zlog_debug("RIPng update routes on interface %s", + ifp->name); + } + + /* Get RIPng interface and instance. */ + ri = ifp->info; + ripng = ri->ripng; + + ripng_rte_list = ripng_rte_new(); + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { + if ((list = rp->info) != NULL + && (rinfo = listgetdata(listhead(list))) != NULL + && rinfo->suppress == 0) { + /* If no route-map are applied, the RTE will be these + * following + * information. + */ + p = (struct prefix_ipv6 *)agg_node_get_prefix(rp); + rinfo->metric_out = rinfo->metric; + rinfo->tag_out = rinfo->tag; + memset(&rinfo->nexthop_out, 0, + sizeof(rinfo->nexthop_out)); + /* In order to avoid some local loops, + * if the RIPng route has a nexthop via this interface, + * keep the nexthop, + * otherwise set it to 0. The nexthop should not be + * propagated + * beyond the local broadcast/multicast area in order + * to avoid an IGP multi-level recursive look-up. + */ + if (rinfo->ifindex == ifp->ifindex) + rinfo->nexthop_out = rinfo->nexthop; + + /* Apply output filters. */ + ret = ripng_filter(RIPNG_FILTER_OUT, p, ri); + if (ret < 0) + continue; + + /* Changed route only output. */ + if (route_type == ripng_changed_route && + (!CHECK_FLAG(rinfo->flags, RIPNG_RTF_CHANGED))) + continue; + + /* Split horizon. */ + if (ri->split_horizon == RIPNG_SPLIT_HORIZON) { + /* We perform split horizon for RIPng routes. */ + int suppress = 0; + struct ripng_info *tmp_rinfo = NULL; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, + tmp_rinfo)) + if (tmp_rinfo->type == + ZEBRA_ROUTE_RIPNG && + tmp_rinfo->ifindex == + ifp->ifindex) { + suppress = 1; + break; + } + if (suppress) + continue; + } + + /* Preparation for route-map. */ + rinfo->metric_set = 0; + /* nexthop_out, + * metric_out + * and tag_out are already initialized. + */ + + /* Interface route-map */ + if (ri->routemap[RIPNG_FILTER_OUT]) { + ret = route_map_apply( + ri->routemap[RIPNG_FILTER_OUT], + (struct prefix *)p, rinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "RIPng %pFX is filtered by route-map out", + p); + continue; + } + } + + /* Redistribute route-map. */ + if (ripng->redist[rinfo->type].route_map.name) { + ret = route_map_apply(ripng->redist[rinfo->type] + .route_map.map, + (struct prefix *)p, + rinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "RIPng %pFX is filtered by route-map", + p); + continue; + } + } + + /* When the route-map does not set metric. */ + if (!rinfo->metric_set) { + /* If the redistribute metric is set. */ + if (ripng->redist[rinfo->type].metric_config + && rinfo->metric != RIPNG_METRIC_INFINITY) { + rinfo->metric_out = + ripng->redist[rinfo->type] + .metric; + } else { + /* If the route is not connected or + localy generated + one, use default-metric value */ + if (rinfo->type != ZEBRA_ROUTE_RIPNG && + rinfo->type != + ZEBRA_ROUTE_CONNECT && + rinfo->metric != + RIPNG_METRIC_INFINITY) + rinfo->metric_out = + ripng->default_metric; + } + } + + /* Apply offset-list */ + if (rinfo->metric_out != RIPNG_METRIC_INFINITY) + ripng_offset_list_apply_out(ripng, p, ifp, + &rinfo->metric_out); + + if (rinfo->metric_out > RIPNG_METRIC_INFINITY) + rinfo->metric_out = RIPNG_METRIC_INFINITY; + + /* Perform split-horizon with poisoned reverse + * for RIPng routes. + **/ + if (ri->split_horizon == + RIPNG_SPLIT_HORIZON_POISONED_REVERSE) { + struct ripng_info *tmp_rinfo = NULL; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, + tmp_rinfo)) + if ((tmp_rinfo->type == + ZEBRA_ROUTE_RIPNG) && + tmp_rinfo->ifindex == ifp->ifindex) + rinfo->metric_out = + RIPNG_METRIC_INFINITY; + } + + /* Add RTE to the list */ + ripng_rte_add(ripng_rte_list, p, rinfo, NULL); + } + + /* Process the aggregated RTE entry */ + if ((aggregate = rp->aggregate) != NULL && aggregate->count > 0 + && aggregate->suppress == 0) { + /* If no route-map are applied, the RTE will be these + * following + * information. + */ + p = (struct prefix_ipv6 *)agg_node_get_prefix(rp); + aggregate->metric_set = 0; + aggregate->metric_out = aggregate->metric; + aggregate->tag_out = aggregate->tag; + memset(&aggregate->nexthop_out, 0, + sizeof(aggregate->nexthop_out)); + + /* Apply output filters.*/ + ret = ripng_filter(RIPNG_FILTER_OUT, p, ri); + if (ret < 0) + continue; + + /* Interface route-map */ + if (ri->routemap[RIPNG_FILTER_OUT]) { + struct ripng_info newinfo; + + /* let's cast the aggregate structure to + * ripng_info */ + memset(&newinfo, 0, sizeof(struct ripng_info)); + /* the nexthop is :: */ + newinfo.metric = aggregate->metric; + newinfo.metric_out = aggregate->metric_out; + newinfo.tag = aggregate->tag; + newinfo.tag_out = aggregate->tag_out; + + ret = route_map_apply( + ri->routemap[RIPNG_FILTER_OUT], + (struct prefix *)p, &newinfo); + + if (ret == RMAP_DENYMATCH) { + if (IS_RIPNG_DEBUG_PACKET) + zlog_debug( + "RIPng %pFX is filtered by route-map out", + p); + continue; + } + + aggregate->metric_out = newinfo.metric_out; + aggregate->tag_out = newinfo.tag_out; + if (IN6_IS_ADDR_LINKLOCAL(&newinfo.nexthop_out)) + aggregate->nexthop_out = + newinfo.nexthop_out; + } + + /* There is no redistribute routemap for the aggregated + * RTE */ + + /* Changed route only output. */ + /* XXX, vincent, in order to increase time convergence, + * it should be announced if a child has changed. + */ + if (route_type == ripng_changed_route) + continue; + + /* Apply offset-list */ + if (aggregate->metric_out != RIPNG_METRIC_INFINITY) + ripng_offset_list_apply_out( + ripng, p, ifp, &aggregate->metric_out); + + if (aggregate->metric_out > RIPNG_METRIC_INFINITY) + aggregate->metric_out = RIPNG_METRIC_INFINITY; + + /* Add RTE to the list */ + ripng_rte_add(ripng_rte_list, p, NULL, aggregate); + } + } + + /* Flush the list */ + ripng_rte_send(ripng_rte_list, ifp, to); + ripng_rte_free(ripng_rte_list); +} + +struct ripng *ripng_lookup_by_vrf_id(vrf_id_t vrf_id) +{ + struct vrf *vrf; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf) + return NULL; + + return vrf->info; +} + +struct ripng *ripng_lookup_by_vrf_name(const char *vrf_name) +{ + struct ripng ripng; + + ripng.vrf_name = (char *)vrf_name; + + return RB_FIND(ripng_instance_head, &ripng_instances, &ripng); +} + +/* Create new RIPng instance and set it to global variable. */ +struct ripng *ripng_create(const char *vrf_name, struct vrf *vrf, int socket) +{ + struct ripng *ripng; + + /* Allocaste RIPng instance. */ + ripng = XCALLOC(MTYPE_RIPNG, sizeof(struct ripng)); + ripng->vrf_name = XSTRDUP(MTYPE_RIPNG_VRF_NAME, vrf_name); + + /* Default version and timer values. */ + ripng->version = RIPNG_V1; + ripng->update_time = yang_get_default_uint32( + "%s/timers/update-interval", RIPNG_INSTANCE); + ripng->timeout_time = yang_get_default_uint32( + "%s/timers/holddown-interval", RIPNG_INSTANCE); + ripng->garbage_time = yang_get_default_uint32( + "%s/timers/flush-interval", RIPNG_INSTANCE); + ripng->default_metric = + yang_get_default_uint8("%s/default-metric", RIPNG_INSTANCE); + ripng->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIPNG_INSTANCE); + + /* Make buffer. */ + ripng->ibuf = stream_new(RIPNG_MAX_PACKET_SIZE * 5); + ripng->obuf = stream_new(RIPNG_MAX_PACKET_SIZE); + + /* Initialize RIPng data structures. */ + ripng->table = agg_table_init(); + agg_set_table_info(ripng->table, ripng); + ripng->peer_list = list_new(); + ripng->peer_list->cmp = (int (*)(void *, void *))ripng_peer_list_cmp; + ripng->peer_list->del = ripng_peer_list_del; + ripng->enable_if = vector_init(1); + ripng->enable_network = agg_table_init(); + ripng->passive_interface = vector_init(1); + ripng->offset_list_master = list_new(); + ripng->offset_list_master->cmp = + (int (*)(void *, void *))offset_list_cmp; + ripng->offset_list_master->del = + (void (*)(void *))ripng_offset_list_free; + ripng->distribute_ctx = distribute_list_ctx_create(vrf); + distribute_list_add_hook(ripng->distribute_ctx, + ripng_distribute_update); + distribute_list_delete_hook(ripng->distribute_ctx, + ripng_distribute_update); + + /* if rmap install. */ + ripng->if_rmap_ctx = if_rmap_ctx_create(vrf_name); + if_rmap_hook_add(ripng->if_rmap_ctx, ripng_if_rmap_update); + if_rmap_hook_delete(ripng->if_rmap_ctx, ripng_if_rmap_update); + + /* Enable the routing instance if possible. */ + if (vrf && vrf_is_enabled(vrf)) + ripng_instance_enable(ripng, vrf, socket); + else { + ripng->vrf = NULL; + ripng->sock = -1; + } + + RB_INSERT(ripng_instance_head, &ripng_instances, ripng); + + return ripng; +} + +/* Send RIPng request to the interface. */ +int ripng_request(struct interface *ifp) +{ + struct rte *rte; + struct ripng_packet ripng_packet; + + /* In default ripd doesn't send RIP_REQUEST to the loopback interface. + */ + if (if_is_loopback(ifp)) + return 0; + + /* If interface is down, don't send RIP packet. */ + if (!if_is_up(ifp)) + return 0; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("RIPng send request to %s", ifp->name); + + memset(&ripng_packet, 0, sizeof(ripng_packet)); + ripng_packet.command = RIPNG_REQUEST; + ripng_packet.version = RIPNG_V1; + rte = ripng_packet.rte; + rte->metric = RIPNG_METRIC_INFINITY; + + return ripng_send_packet((caddr_t)&ripng_packet, sizeof(ripng_packet), + NULL, ifp); +} + + +static int ripng_update_jitter(int time) +{ + return ((frr_weak_random() % (time + 1)) - (time / 2)); +} + +void ripng_event(struct ripng *ripng, enum ripng_event event, int sock) +{ + int jitter = 0; + + switch (event) { + case RIPNG_READ: + event_add_read(master, ripng_read, ripng, sock, &ripng->t_read); + break; + case RIPNG_UPDATE_EVENT: + EVENT_OFF(ripng->t_update); + + /* Update timer jitter. */ + jitter = ripng_update_jitter(ripng->update_time); + + event_add_timer(master, ripng_update, ripng, + sock ? 2 : ripng->update_time + jitter, + &ripng->t_update); + break; + case RIPNG_TRIGGERED_UPDATE: + if (ripng->t_triggered_interval) + ripng->trigger = 1; + else + event_add_event(master, ripng_triggered_update, ripng, + 0, &ripng->t_triggered_update); + break; + case RIPNG_ZEBRA: + case RIPNG_REQUEST_EVENT: + break; + } +} + + +/* Print out routes update time. */ +static void ripng_vty_out_uptime(struct vty *vty, struct ripng_info *rinfo) +{ + time_t clock; + struct tm tm; +#define TIME_BUF 25 + char timebuf[TIME_BUF]; + struct event *thread; + + if ((thread = rinfo->t_timeout) != NULL) { + clock = event_timer_remain_second(thread); + gmtime_r(&clock, &tm); + strftime(timebuf, TIME_BUF, "%M:%S", &tm); + vty_out(vty, "%5s", timebuf); + } else if ((thread = rinfo->t_garbage_collect) != NULL) { + clock = event_timer_remain_second(thread); + gmtime_r(&clock, &tm); + strftime(timebuf, TIME_BUF, "%M:%S", &tm); + vty_out(vty, "%5s", timebuf); + } +} + +static char *ripng_route_subtype_print(struct ripng_info *rinfo) +{ + static char str[3]; + memset(str, 0, 3); + + if (rinfo->suppress) + strlcat(str, "S", sizeof(str)); + + switch (rinfo->sub_type) { + case RIPNG_ROUTE_RTE: + strlcat(str, "n", sizeof(str)); + break; + case RIPNG_ROUTE_STATIC: + strlcat(str, "s", sizeof(str)); + break; + case RIPNG_ROUTE_DEFAULT: + strlcat(str, "d", sizeof(str)); + break; + case RIPNG_ROUTE_REDISTRIBUTE: + strlcat(str, "r", sizeof(str)); + break; + case RIPNG_ROUTE_INTERFACE: + strlcat(str, "i", sizeof(str)); + break; + default: + strlcat(str, "?", sizeof(str)); + break; + } + + return str; +} + +DEFUN (show_ipv6_ripng, + show_ipv6_ripng_cmd, + "show ipv6 ripng [vrf NAME]", + SHOW_STR + IPV6_STR + "Show RIPng routes\n" + VRF_CMD_HELP_STR) +{ + struct ripng *ripng; + struct agg_node *rp; + struct ripng_info *rinfo; + struct ripng_aggregate *aggregate; + struct list *list = NULL; + struct listnode *listnode = NULL; + int len; + const char *vrf_name; + int idx = 0; + + if (argv_find(argv, argc, "vrf", &idx)) + vrf_name = argv[idx + 1]->arg; + else + vrf_name = VRF_DEFAULT_NAME; + + ripng = ripng_lookup_by_vrf_name(vrf_name); + if (!ripng) { + vty_out(vty, "%% RIPng instance not found\n"); + return CMD_SUCCESS; + } + if (!ripng->enabled) { + vty_out(vty, "%% RIPng instance is disabled\n"); + return CMD_SUCCESS; + } + + /* Header of display. */ + vty_out(vty, + "Codes: R - RIPng, C - connected, S - Static, O - OSPF, B - BGP\n" + "Sub-codes:\n" + " (n) - normal, (s) - static, (d) - default, (r) - redistribute,\n" + " (i) - interface, (a/S) - aggregated/Suppressed\n\n" + " Network Next Hop Via Metric Tag Time\n"); + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { + if ((aggregate = rp->aggregate) != NULL) { +#ifdef DEBUG + vty_out(vty, "R(a) %d/%d %pRN ", aggregate->count, + aggregate->suppress, rp); +#else + vty_out(vty, "R(a) %pRN ", rp); +#endif /* DEBUG */ + vty_out(vty, "\n"); + vty_out(vty, "%*s", 18, " "); + + vty_out(vty, "%*s", 28, " "); + vty_out(vty, "self %2d %3" ROUTE_TAG_PRI "\n", + aggregate->metric, (route_tag_t)aggregate->tag); + } + + if ((list = rp->info) != NULL) + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { +#ifdef DEBUG + vty_out(vty, "%c(%s) 0/%d %pRN ", + zebra_route_char(rinfo->type), + ripng_route_subtype_print(rinfo), + rinfo->suppress, rp); +#else + vty_out(vty, "%c(%s) %pRN ", + zebra_route_char(rinfo->type), + ripng_route_subtype_print(rinfo), rp); +#endif /* DEBUG */ + vty_out(vty, "\n"); + vty_out(vty, "%*s", 18, " "); + len = vty_out(vty, "%pI6", + &rinfo->nexthop); + + len = 28 - len; + if (len > 0) + vty_out(vty, "%*s", len, " "); + + /* from */ + if ((rinfo->type == ZEBRA_ROUTE_RIPNG) + && (rinfo->sub_type == RIPNG_ROUTE_RTE)) { + len = vty_out( + vty, "%s", + ifindex2ifname( + rinfo->ifindex, + ripng->vrf->vrf_id)); + } else if (rinfo->metric + == RIPNG_METRIC_INFINITY) { + len = vty_out(vty, "kill"); + } else + len = vty_out(vty, "self"); + + len = 9 - len; + if (len > 0) + vty_out(vty, "%*s", len, " "); + + vty_out(vty, " %2d %3" ROUTE_TAG_PRI " ", + rinfo->metric, (route_tag_t)rinfo->tag); + + /* time */ + if ((rinfo->type == ZEBRA_ROUTE_RIPNG) + && (rinfo->sub_type == RIPNG_ROUTE_RTE)) { + /* RTE from remote RIP routers */ + ripng_vty_out_uptime(vty, rinfo); + } else if (rinfo->metric + == RIPNG_METRIC_INFINITY) { + /* poisonous reversed routes (gc) */ + ripng_vty_out_uptime(vty, rinfo); + } + + vty_out(vty, "\n"); + } + } + + return CMD_SUCCESS; +} + +DEFUN (show_ipv6_ripng_status, + show_ipv6_ripng_status_cmd, + "show ipv6 ripng [vrf NAME] status", + SHOW_STR + IPV6_STR + "Show RIPng routes\n" + VRF_CMD_HELP_STR + "IPv6 routing protocol process parameters and statistics\n") +{ + struct ripng *ripng; + struct interface *ifp; + const char *vrf_name; + int idx = 0; + + if (argv_find(argv, argc, "vrf", &idx)) + vrf_name = argv[idx + 1]->arg; + else + vrf_name = VRF_DEFAULT_NAME; + + ripng = ripng_lookup_by_vrf_name(vrf_name); + if (!ripng) { + vty_out(vty, "%% RIPng instance not found\n"); + return CMD_SUCCESS; + } + if (!ripng->enabled) { + vty_out(vty, "%% RIPng instance is disabled\n"); + return CMD_SUCCESS; + } + + vty_out(vty, "Routing Protocol is \"RIPng\"\n"); + vty_out(vty, " Sending updates every %u seconds with +/-50%%,", + ripng->update_time); + vty_out(vty, " next due in %lu seconds\n", + event_timer_remain_second(ripng->t_update)); + vty_out(vty, " Timeout after %u seconds,", ripng->timeout_time); + vty_out(vty, " garbage collect after %u seconds\n", + ripng->garbage_time); + + /* Filtering status show. */ + config_show_distribute(vty, ripng->distribute_ctx); + + /* Default metric information. */ + vty_out(vty, " Default redistribution metric is %d\n", + ripng->default_metric); + + /* Redistribute information. */ + vty_out(vty, " Redistributing:"); + ripng_redistribute_write(vty, ripng); + vty_out(vty, "\n"); + + vty_out(vty, " Default version control: send version %d,", + ripng->version); + vty_out(vty, " receive version %d \n", ripng->version); + + vty_out(vty, " Interface Send Recv\n"); + + FOR_ALL_INTERFACES (ripng->vrf, ifp) { + struct ripng_interface *ri; + + ri = ifp->info; + + if (ri->enable_network || ri->enable_interface) { + + vty_out(vty, " %-17s%-3d %-3d\n", ifp->name, + ripng->version, ripng->version); + } + } + + vty_out(vty, " Routing for Networks:\n"); + ripng_network_write(vty, ripng); + + vty_out(vty, " Routing Information Sources:\n"); + vty_out(vty, + " Gateway BadPackets BadRoutes Distance Last Update\n"); + ripng_peer_display(vty, ripng); + + return CMD_SUCCESS; +} + +/* Update ECMP routes to zebra when ECMP is disabled. */ +void ripng_ecmp_disable(struct ripng *ripng) +{ + struct agg_node *rp; + struct ripng_info *rinfo, *tmp_rinfo; + struct list *list; + struct listnode *node, *nextnode; + + if (!ripng) + return; + + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) + if ((list = rp->info) != NULL && listcount(list) > 1) { + rinfo = listgetdata(listhead(list)); + if (!ripng_route_rte(rinfo)) + continue; + + /* Drop all other entries, except the first one. */ + for (ALL_LIST_ELEMENTS(list, node, nextnode, tmp_rinfo)) + if (tmp_rinfo != rinfo) { + EVENT_OFF(tmp_rinfo->t_timeout); + EVENT_OFF(tmp_rinfo->t_garbage_collect); + list_delete_node(list, node); + ripng_info_free(tmp_rinfo); + } + + /* Update zebra. */ + ripng_zebra_ipv6_add(ripng, rp); + + /* Set the route change flag. */ + SET_FLAG(rinfo->flags, RIPNG_RTF_CHANGED); + + /* Signal the output process to trigger an update. */ + ripng_event(ripng, RIPNG_TRIGGERED_UPDATE, 0); + } +} + +static void ripng_distribute_update(struct distribute_ctx *ctx, + struct distribute *dist) +{ + struct interface *ifp; + struct ripng_interface *ri; + struct access_list *alist; + struct prefix_list *plist; + + if (!ctx->vrf || !dist->ifname) + return; + + ifp = if_lookup_by_name(dist->ifname, ctx->vrf->vrf_id); + if (ifp == NULL) + return; + + ri = ifp->info; + + if (dist->list[DISTRIBUTE_V6_IN]) { + alist = access_list_lookup(AFI_IP6, + dist->list[DISTRIBUTE_V6_IN]); + if (alist) + ri->list[RIPNG_FILTER_IN] = alist; + else + ri->list[RIPNG_FILTER_IN] = NULL; + } else + ri->list[RIPNG_FILTER_IN] = NULL; + + if (dist->list[DISTRIBUTE_V6_OUT]) { + alist = access_list_lookup(AFI_IP6, + dist->list[DISTRIBUTE_V6_OUT]); + if (alist) + ri->list[RIPNG_FILTER_OUT] = alist; + else + ri->list[RIPNG_FILTER_OUT] = NULL; + } else + ri->list[RIPNG_FILTER_OUT] = NULL; + + if (dist->prefix[DISTRIBUTE_V6_IN]) { + plist = prefix_list_lookup(AFI_IP6, + dist->prefix[DISTRIBUTE_V6_IN]); + if (plist) + ri->prefix[RIPNG_FILTER_IN] = plist; + else + ri->prefix[RIPNG_FILTER_IN] = NULL; + } else + ri->prefix[RIPNG_FILTER_IN] = NULL; + + if (dist->prefix[DISTRIBUTE_V6_OUT]) { + plist = prefix_list_lookup(AFI_IP6, + dist->prefix[DISTRIBUTE_V6_OUT]); + if (plist) + ri->prefix[RIPNG_FILTER_OUT] = plist; + else + ri->prefix[RIPNG_FILTER_OUT] = NULL; + } else + ri->prefix[RIPNG_FILTER_OUT] = NULL; +} + +void ripng_distribute_update_interface(struct interface *ifp) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + struct distribute *dist; + + if (!ripng) + return; + dist = distribute_lookup(ripng->distribute_ctx, ifp->name); + if (dist) + ripng_distribute_update(ripng->distribute_ctx, dist); +} + +/* Update all interface's distribute list. */ +static void ripng_distribute_update_all(struct prefix_list *notused) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) + ripng_distribute_update_interface(ifp); +} + +static void ripng_distribute_update_all_wrapper(struct access_list *notused) +{ + ripng_distribute_update_all(NULL); +} + +/* delete all the added ripng routes. */ +void ripng_clean(struct ripng *ripng) +{ + ripng_interface_clean(ripng); + + if (ripng->enabled) + ripng_instance_disable(ripng); + + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) + if (ripng->redist[i].route_map.name) + free(ripng->redist[i].route_map.name); + + agg_table_finish(ripng->table); + list_delete(&ripng->peer_list); + distribute_list_delete(&ripng->distribute_ctx); + if_rmap_ctx_delete(ripng->if_rmap_ctx); + + stream_free(ripng->ibuf); + stream_free(ripng->obuf); + + ripng_clean_network(ripng); + ripng_passive_interface_clean(ripng); + vector_free(ripng->enable_if); + agg_table_finish(ripng->enable_network); + vector_free(ripng->passive_interface); + list_delete(&ripng->offset_list_master); + + RB_REMOVE(ripng_instance_head, &ripng_instances, ripng); + XFREE(MTYPE_RIPNG_VRF_NAME, ripng->vrf_name); + XFREE(MTYPE_RIPNG, ripng); +} + +static void ripng_if_rmap_update(struct if_rmap_ctx *ctx, + struct if_rmap *if_rmap) +{ + struct interface *ifp = NULL; + struct ripng_interface *ri; + struct route_map *rmap; + struct vrf *vrf = NULL; + + if (ctx->name) + vrf = vrf_lookup_by_name(ctx->name); + if (vrf) + ifp = if_lookup_by_name(if_rmap->ifname, vrf->vrf_id); + if (ifp == NULL) + return; + + ri = ifp->info; + + if (if_rmap->routemap[IF_RMAP_IN]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_IN]); + if (rmap) + ri->routemap[IF_RMAP_IN] = rmap; + else + ri->routemap[IF_RMAP_IN] = NULL; + } else + ri->routemap[RIPNG_FILTER_IN] = NULL; + + if (if_rmap->routemap[IF_RMAP_OUT]) { + rmap = route_map_lookup_by_name(if_rmap->routemap[IF_RMAP_OUT]); + if (rmap) + ri->routemap[IF_RMAP_OUT] = rmap; + else + ri->routemap[IF_RMAP_OUT] = NULL; + } else + ri->routemap[RIPNG_FILTER_OUT] = NULL; +} + +void ripng_if_rmap_update_interface(struct interface *ifp) +{ + struct ripng_interface *ri = ifp->info; + struct ripng *ripng = ri->ripng; + struct if_rmap *if_rmap; + struct if_rmap_ctx *ctx; + + if (!ripng) + return; + ctx = ripng->if_rmap_ctx; + if (!ctx) + return; + if_rmap = if_rmap_lookup(ctx, ifp->name); + if (if_rmap) + ripng_if_rmap_update(ctx, if_rmap); +} + +static void ripng_routemap_update_redistribute(struct ripng *ripng) +{ + for (int i = 0; i < ZEBRA_ROUTE_MAX; i++) { + if (ripng->redist[i].route_map.name) { + ripng->redist[i].route_map.map = + route_map_lookup_by_name( + ripng->redist[i].route_map.name); + route_map_counter_increment( + ripng->redist[i].route_map.map); + } + } +} + +static void ripng_routemap_update(const char *unused) +{ + struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT); + struct ripng *ripng; + struct interface *ifp; + + FOR_ALL_INTERFACES (vrf, ifp) + ripng_if_rmap_update_interface(ifp); + + ripng = vrf->info; + if (ripng) + ripng_routemap_update_redistribute(ripng); +} + +/* Link RIPng instance to VRF. */ +static void ripng_vrf_link(struct ripng *ripng, struct vrf *vrf) +{ + struct interface *ifp; + + ripng->vrf = vrf; + ripng->distribute_ctx->vrf = vrf; + vrf->info = ripng; + + FOR_ALL_INTERFACES (vrf, ifp) + ripng_interface_sync(ifp); +} + +/* Unlink RIPng instance from VRF. */ +static void ripng_vrf_unlink(struct ripng *ripng, struct vrf *vrf) +{ + struct interface *ifp; + + ripng->vrf = NULL; + ripng->distribute_ctx->vrf = NULL; + vrf->info = NULL; + + FOR_ALL_INTERFACES (vrf, ifp) + ripng_interface_sync(ifp); +} + +static void ripng_instance_enable(struct ripng *ripng, struct vrf *vrf, + int sock) +{ + ripng->sock = sock; + + ripng_vrf_link(ripng, vrf); + ripng->enabled = true; + + /* Resend all redistribute requests. */ + ripng_redistribute_enable(ripng); + + /* Create read and timer thread. */ + ripng_event(ripng, RIPNG_READ, ripng->sock); + ripng_event(ripng, RIPNG_UPDATE_EVENT, 1); + + ripng_zebra_vrf_register(vrf); +} + +static void ripng_instance_disable(struct ripng *ripng) +{ + struct vrf *vrf = ripng->vrf; + struct agg_node *rp; + + /* Clear RIPng routes */ + for (rp = agg_route_top(ripng->table); rp; rp = agg_route_next(rp)) { + struct ripng_aggregate *aggregate; + struct list *list; + + if ((list = rp->info) != NULL) { + struct ripng_info *rinfo; + struct listnode *listnode; + + rinfo = listgetdata(listhead(list)); + if (ripng_route_rte(rinfo)) + ripng_zebra_ipv6_delete(ripng, rp); + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + EVENT_OFF(rinfo->t_timeout); + EVENT_OFF(rinfo->t_garbage_collect); + ripng_info_free(rinfo); + } + list_delete(&list); + rp->info = NULL; + agg_unlock_node(rp); + } + + if ((aggregate = rp->aggregate) != NULL) { + ripng_aggregate_free(aggregate); + rp->aggregate = NULL; + agg_unlock_node(rp); + } + } + + /* Flush all redistribute requests. */ + ripng_redistribute_disable(ripng); + + /* Cancel the RIPng timers */ + EVENT_OFF(ripng->t_update); + EVENT_OFF(ripng->t_triggered_update); + EVENT_OFF(ripng->t_triggered_interval); + + /* Cancel the read thread */ + EVENT_OFF(ripng->t_read); + + /* Close the RIPng socket */ + if (ripng->sock >= 0) { + close(ripng->sock); + ripng->sock = -1; + } + + /* Clear existing peers. */ + list_delete_all_node(ripng->peer_list); + + ripng_zebra_vrf_deregister(vrf); + + ripng_vrf_unlink(ripng, vrf); + ripng->enabled = false; +} + +static int ripng_vrf_new(struct vrf *vrf) +{ + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: VRF created: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static int ripng_vrf_delete(struct vrf *vrf) +{ + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: VRF deleted: %s(%u)", __func__, vrf->name, + vrf->vrf_id); + + return 0; +} + +static int ripng_vrf_enable(struct vrf *vrf) +{ + struct ripng *ripng; + int socket; + + ripng = ripng_lookup_by_vrf_name(vrf->name); + if (!ripng || ripng->enabled) + return 0; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: VRF %s(%u) enabled", __func__, vrf->name, + vrf->vrf_id); + + /* Activate the VRF RIPng instance. */ + socket = ripng_make_socket(vrf); + if (socket < 0) + return -1; + + ripng_instance_enable(ripng, vrf, socket); + + return 0; +} + +static int ripng_vrf_disable(struct vrf *vrf) +{ + struct ripng *ripng; + + ripng = ripng_lookup_by_vrf_name(vrf->name); + if (!ripng || !ripng->enabled) + return 0; + + if (IS_RIPNG_DEBUG_EVENT) + zlog_debug("%s: VRF %s(%u) disabled", __func__, vrf->name, + vrf->vrf_id); + + /* Deactivate the VRF RIPng instance. */ + if (ripng->enabled) + ripng_instance_disable(ripng); + + return 0; +} + +void ripng_vrf_init(void) +{ + vrf_init(ripng_vrf_new, ripng_vrf_enable, ripng_vrf_disable, + ripng_vrf_delete); +} + +void ripng_vrf_terminate(void) +{ + vrf_terminate(); +} + +/* Initialize ripng structure and set commands. */ +void ripng_init(void) +{ + /* Install ripng commands. */ + install_element(VIEW_NODE, &show_ipv6_ripng_cmd); + install_element(VIEW_NODE, &show_ipv6_ripng_status_cmd); + + ripng_if_init(); + ripng_debug_init(); + + /* Enable mgmt be debug */ + mgmt_be_client_lib_vty_init(); + + /* Access list install. */ + access_list_init_new(true); + access_list_add_hook(ripng_distribute_update_all_wrapper); + access_list_delete_hook(ripng_distribute_update_all_wrapper); + + /* Prefix list initialize.*/ + prefix_list_init(); + prefix_list_add_hook(ripng_distribute_update_all); + prefix_list_delete_hook(ripng_distribute_update_all); + + /* Route-map for interface. */ + ripng_route_map_init(); + + route_map_add_hook(ripng_routemap_update); + route_map_delete_hook(ripng_routemap_update); +} diff --git a/ripngd/ripngd.h b/ripngd/ripngd.h new file mode 100644 index 0000000..a316f2b --- /dev/null +++ b/ripngd/ripngd.h @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RIPng related value and structure. + * Copyright (C) 1998 Kunihiro Ishiguro + */ + +#ifndef _ZEBRA_RIPNG_RIPNGD_H +#define _ZEBRA_RIPNG_RIPNGD_H + +#include +#include +#include +#include +#include + +/* RIPng version and port number. */ +#define RIPNG_V1 1 +#define RIPNG_PORT_DEFAULT 521 +#define RIPNG_MAX_PACKET_SIZE 1500 +#define RIPNG_PRIORITY_DEFAULT 0 + +/* RIPng commands. */ +#define RIPNG_REQUEST 1 +#define RIPNG_RESPONSE 2 + +/* RIPng metric and multicast group address. */ +#define RIPNG_METRIC_INFINITY 16 +#define RIPNG_METRIC_NEXTHOP 0xff +#define RIPNG_GROUP "ff02::9" + +/* RIPng peer timeout value. */ +#define RIPNG_PEER_TIMER_DEFAULT 180 + +/* Default config file name. */ +#define RIPNG_DEFAULT_CONFIG "ripngd.conf" + +/* RIPng route types. */ +#define RIPNG_ROUTE_RTE 0 +#define RIPNG_ROUTE_STATIC 1 +#define RIPNG_ROUTE_DEFAULT 2 +#define RIPNG_ROUTE_REDISTRIBUTE 3 +#define RIPNG_ROUTE_INTERFACE 4 +#define RIPNG_ROUTE_AGGREGATE 5 + +/* Interface send/receive configuration. */ +#define RIPNG_SEND_UNSPEC 0 +#define RIPNG_SEND_OFF 1 +#define RIPNG_RECEIVE_UNSPEC 0 +#define RIPNG_RECEIVE_OFF 1 + +/* RIP default route's accept/announce methods. */ +#define RIPNG_DEFAULT_ADVERTISE_UNSPEC 0 +#define RIPNG_DEFAULT_ADVERTISE_NONE 1 +#define RIPNG_DEFAULT_ADVERTISE 2 + +#define RIPNG_DEFAULT_ACCEPT_UNSPEC 0 +#define RIPNG_DEFAULT_ACCEPT_NONE 1 +#define RIPNG_DEFAULT_ACCEPT 2 + +/* For max RTE calculation. */ +#ifndef IPV6_HDRLEN +#define IPV6_HDRLEN 40 +#endif /* IPV6_HDRLEN */ + +#ifndef IFMINMTU +#define IFMINMTU 576 +#endif /* IFMINMTU */ + +/* YANG paths */ +#define RIPNG_INSTANCE "/frr-ripngd:ripngd/instance" +#define RIPNG_IFACE "/frr-interface:lib/interface/frr-ripngd:ripng" + +DECLARE_MGROUP(RIPNGD); + +/* RIPng structure. */ +struct ripng { + RB_ENTRY(ripng) entry; + + /* VRF this routing instance is associated with. */ + char *vrf_name; + + /* VRF backpointer (might be NULL if the VRF doesn't exist). */ + struct vrf *vrf; + + /* Status of the routing instance. */ + bool enabled; + + /* RIPng socket. */ + int sock; + + /* RIPng Parameters.*/ + uint8_t command; + uint8_t version; + uint16_t update_time; + uint16_t timeout_time; + uint16_t garbage_time; + int max_mtu; + uint8_t default_metric; + + /* Input/output buffer of RIPng. */ + struct stream *ibuf; + struct stream *obuf; + + /* RIPng routing information base. */ + struct agg_table *table; + + /* Linked list of RIPng peers. */ + struct list *peer_list; + + /* RIPng enabled interfaces. */ + vector enable_if; + + /* RIPng enabled networks. */ + struct agg_table *enable_network; + + /* Vector to store passive-interface name. */ + vector passive_interface; + + /* RIPng offset-lists. */ + struct list *offset_list_master; + + /* RIPng threads. */ + struct event *t_read; + struct event *t_update; + + /* Triggered update hack. */ + int trigger; + struct event *t_triggered_update; + struct event *t_triggered_interval; + + /* RIPng ECMP flag */ + uint8_t ecmp; + + /* RIPng redistribute configuration. */ + struct { + bool enabled; + struct { + char *name; + struct route_map *map; + } route_map; + bool metric_config; + uint8_t metric; + } redist[ZEBRA_ROUTE_MAX]; + + /* For distribute-list container */ + struct distribute_ctx *distribute_ctx; + + /* For if_rmap container */ + struct if_rmap_ctx *if_rmap_ctx; +}; +RB_HEAD(ripng_instance_head, ripng); +RB_PROTOTYPE(ripng_instance_head, ripng, entry, ripng_instance_compare) + +/* Routing table entry. */ +struct rte { + struct in6_addr addr; /* RIPng destination prefix */ + uint16_t tag; /* RIPng tag */ + uint8_t prefixlen; /* Length of the RIPng prefix */ + uint8_t metric; /* Metric of the RIPng route */ + /* The nexthop is stored by the structure + * ripng_nexthop within ripngd.c */ +}; + +/* RIPNG send packet. */ +struct ripng_packet { + uint8_t command; + uint8_t version; + uint16_t zero; + struct rte rte[1]; +}; + +/* Each route's information. */ +struct ripng_info { + /* This route's type. Static, ripng or aggregate. */ + uint8_t type; + + /* Sub type for static route. */ + uint8_t sub_type; + + /* RIPng specific information */ + struct in6_addr nexthop; + struct in6_addr from; + + /* Which interface does this route come from. */ + ifindex_t ifindex; + + /* Metric of this route. */ + uint8_t metric; + + /* Tag field of RIPng packet.*/ + uint16_t tag; + + /* For aggregation. */ + unsigned int suppress; + +/* Flags of RIPng route. */ +#define RIPNG_RTF_FIB 1 +#define RIPNG_RTF_CHANGED 2 + uint8_t flags; + + /* Garbage collect timer. */ + struct event *t_timeout; + struct event *t_garbage_collect; + + /* Route-map features - this variables can be changed. */ + struct in6_addr nexthop_out; + uint8_t metric_set; + uint8_t metric_out; + uint16_t tag_out; + + struct agg_node *rp; +}; + +typedef enum { + RIPNG_NO_SPLIT_HORIZON = 0, + RIPNG_SPLIT_HORIZON, + RIPNG_SPLIT_HORIZON_POISONED_REVERSE +} split_horizon_policy_t; + +/* RIPng specific interface configuration. */ +struct ripng_interface { + /* Parent routing instance. */ + struct ripng *ripng; + + /* RIPng is enabled on this interface. */ + int enable_network; + int enable_interface; + + /* RIPng is running on this interface. */ + int running; + + /* Split horizon flag. */ + split_horizon_policy_t split_horizon; + +/* For filter type slot. */ +#define RIPNG_FILTER_IN 0 +#define RIPNG_FILTER_OUT 1 +#define RIPNG_FILTER_MAX 2 + + /* Access-list. */ + struct access_list *list[RIPNG_FILTER_MAX]; + + /* Prefix-list. */ + struct prefix_list *prefix[RIPNG_FILTER_MAX]; + + /* Route-map. */ + struct route_map *routemap[RIPNG_FILTER_MAX]; + + /* Default information originate. */ + uint8_t default_originate; + + /* Default information only. */ + uint8_t default_only; + + /* Wake up thread. */ + struct event *t_wakeup; + + /* Passive interface. */ + int passive; +}; + +/* RIPng peer information. */ +struct ripng_peer { + /* Parent routing instance. */ + struct ripng *ripng; + + /* Peer address. */ + struct in6_addr addr; + + /* Peer RIPng tag value. */ + int domain; + + /* Last update time. */ + time_t uptime; + + /* Peer RIP version. */ + uint8_t version; + + /* Statistics. */ + int recv_badpackets; + int recv_badroutes; + + /* Timeout thread. */ + struct event *t_timeout; +}; + +/* All RIPng events. */ +enum ripng_event { + RIPNG_READ, + RIPNG_ZEBRA, + RIPNG_REQUEST_EVENT, + RIPNG_UPDATE_EVENT, + RIPNG_TRIGGERED_UPDATE, +}; + +/* RIPng timer on/off macro. */ +#define RIPNG_TIMER_ON(T, F, V) event_add_timer(master, (F), rinfo, (V), &(T)) + +#define RIPNG_OFFSET_LIST_IN 0 +#define RIPNG_OFFSET_LIST_OUT 1 +#define RIPNG_OFFSET_LIST_MAX 2 + +struct ripng_offset_list { + /* Parent routing instance. */ + struct ripng *ripng; + + char *ifname; + + struct { + char *alist_name; + /* struct access_list *alist; */ + uint8_t metric; + } direct[RIPNG_OFFSET_LIST_MAX]; +}; + +/* Extern variables. */ +extern struct zebra_privs_t ripngd_privs; +extern struct event_loop *master; +extern struct ripng_instance_head ripng_instances; + +/* Prototypes. */ +extern void ripng_init(void); +extern void ripng_clean(struct ripng *ripng); +extern void ripng_clean_network(struct ripng *ripng); +extern void ripng_interface_clean(struct ripng *ripng); +extern int ripng_enable_network_add(struct ripng *ripng, struct prefix *p); +extern int ripng_enable_network_delete(struct ripng *ripng, struct prefix *p); +extern int ripng_enable_if_add(struct ripng *ripng, const char *ifname); +extern int ripng_enable_if_delete(struct ripng *ripng, const char *ifname); +extern int ripng_passive_interface_set(struct ripng *ripng, const char *ifname); +extern int ripng_passive_interface_unset(struct ripng *ripng, + const char *ifname); +extern void ripng_passive_interface_clean(struct ripng *ripng); +extern void ripng_if_init(void); +extern void ripng_route_map_init(void); +extern void ripng_zebra_vrf_register(struct vrf *vrf); +extern void ripng_zebra_vrf_deregister(struct vrf *vrf); +extern void ripng_terminate(void); +/* zclient_init() is done by ripng_zebra.c:zebra_init() */ +extern void zebra_init(struct event_loop *master); +extern void ripng_zebra_stop(void); +extern void ripng_redistribute_conf_update(struct ripng *ripng, int type); +extern void ripng_redistribute_conf_delete(struct ripng *ripng, int type); + +extern void ripng_peer_update(struct ripng *ripng, struct sockaddr_in6 *from, + uint8_t version); +extern void ripng_peer_bad_route(struct ripng *ripng, + struct sockaddr_in6 *from); +extern void ripng_peer_bad_packet(struct ripng *ripng, + struct sockaddr_in6 *from); +extern void ripng_peer_display(struct vty *vty, struct ripng *ripng); +extern struct ripng_peer *ripng_peer_lookup(struct ripng *ripng, + struct in6_addr *addr); +extern struct ripng_peer *ripng_peer_lookup_next(struct ripng *ripng, + struct in6_addr *addr); +extern int ripng_peer_list_cmp(struct ripng_peer *p1, struct ripng_peer *p2); +extern void ripng_peer_list_del(void *arg); + +extern struct ripng_offset_list *ripng_offset_list_new(struct ripng *ripng, + const char *ifname); +extern void ripng_offset_list_del(struct ripng_offset_list *offset); +extern void ripng_offset_list_free(struct ripng_offset_list *offset); +extern struct ripng_offset_list *ripng_offset_list_lookup(struct ripng *ripng, + const char *ifname); +extern int ripng_offset_list_apply_in(struct ripng *ripng, + struct prefix_ipv6 *p, + struct interface *ifp, uint8_t *metric); +extern int ripng_offset_list_apply_out(struct ripng *ripng, + struct prefix_ipv6 *p, + struct interface *ifp, uint8_t *metric); +extern int offset_list_cmp(struct ripng_offset_list *o1, + struct ripng_offset_list *o2); + +extern int ripng_route_rte(struct ripng_info *rinfo); +extern struct ripng_info *ripng_info_new(void); +extern void ripng_info_free(struct ripng_info *rinfo); +extern struct ripng *ripng_info_get_instance(const struct ripng_info *rinfo); +extern void ripng_event(struct ripng *ripng, enum ripng_event event, int sock); +extern int ripng_request(struct interface *ifp); +extern void ripng_redistribute_add(struct ripng *ripng, int type, int sub_type, + struct prefix_ipv6 *p, ifindex_t ifindex, + struct in6_addr *nexthop, route_tag_t tag); +extern void ripng_redistribute_delete(struct ripng *ripng, int type, + int sub_type, struct prefix_ipv6 *p, + ifindex_t ifindex); +extern void ripng_redistribute_withdraw(struct ripng *ripng, int type); + +extern void ripng_ecmp_disable(struct ripng *ripng); +extern void ripng_distribute_update_interface(struct interface *); +extern void ripng_if_rmap_update_interface(struct interface *); + +extern void ripng_zebra_ipv6_add(struct ripng *ripng, struct agg_node *node); +extern void ripng_zebra_ipv6_delete(struct ripng *ripng, struct agg_node *node); + +extern void ripng_redistribute_enable(struct ripng *ripng); +extern void ripng_redistribute_disable(struct ripng *ripng); +extern int ripng_redistribute_check(struct ripng *ripng, int type); +extern void ripng_redistribute_write(struct vty *vty, struct ripng *ripng); + +extern int ripng_write_rte(int num, struct stream *s, struct prefix_ipv6 *p, + struct in6_addr *nexthop, uint16_t tag, + uint8_t metric); +extern int ripng_send_packet(caddr_t buf, int bufsize, struct sockaddr_in6 *to, + struct interface *ifp); + +extern void ripng_packet_dump(struct ripng_packet *packet, int size, + const char *sndrcv); + +extern int ripng_interface_up(ZAPI_CALLBACK_ARGS); +extern int ripng_interface_down(ZAPI_CALLBACK_ARGS); +extern int ripng_interface_add(ZAPI_CALLBACK_ARGS); +extern int ripng_interface_delete(ZAPI_CALLBACK_ARGS); +extern int ripng_interface_address_add(ZAPI_CALLBACK_ARGS); +extern int ripng_interface_address_delete(ZAPI_CALLBACK_ARGS); +extern void ripng_interface_sync(struct interface *ifp); + +extern struct ripng *ripng_lookup_by_vrf_id(vrf_id_t vrf_id); +extern struct ripng *ripng_lookup_by_vrf_name(const char *vrf_name); +extern struct ripng *ripng_create(const char *vrf_name, struct vrf *vrf, + int socket); +extern int ripng_make_socket(struct vrf *vrf); +extern int ripng_network_write(struct vty *vty, struct ripng *ripng); + +extern struct ripng_info *ripng_ecmp_add(struct ripng *ripng, + struct ripng_info *rinfo); +extern struct ripng_info *ripng_ecmp_replace(struct ripng *ripng, + struct ripng_info *rinfo); +extern struct ripng_info *ripng_ecmp_delete(struct ripng *ripng, + struct ripng_info *rinfo); +extern void ripng_ecmp_change(struct ripng *ripng); + +extern void ripng_vrf_init(void); +extern void ripng_vrf_terminate(void); + +extern uint32_t zebra_ecmp_count; + +#endif /* _ZEBRA_RIPNG_RIPNGD_H */ diff --git a/ripngd/subdir.am b/ripngd/subdir.am new file mode 100644 index 0000000..83e376b --- /dev/null +++ b/ripngd/subdir.am @@ -0,0 +1,43 @@ +# +# ripngd +# + +if RIPNGD +sbin_PROGRAMS += ripngd/ripngd +vtysh_daemons += ripngd +man8 += $(MANBUILD)/frr-ripngd.8 +endif + +ripngd_ripngd_SOURCES = \ + ripngd/ripng_debug.c \ + ripngd/ripng_interface.c \ + ripngd/ripng_nexthop.c \ + ripngd/ripng_offset.c \ + ripngd/ripng_main.c \ + ripngd/ripng_nb.c \ + ripngd/ripng_nb_config.c \ + ripngd/ripng_nb_rpcs.c \ + ripngd/ripng_nb_state.c \ + ripngd/ripng_peer.c \ + ripngd/ripng_route.c \ + ripngd/ripng_routemap.c \ + ripngd/ripng_zebra.c \ + ripngd/ripngd.c \ + # end + +clippy_scan += \ + ripngd/ripng_cli.c \ + # end + +noinst_HEADERS += \ + ripngd/ripng_debug.h \ + ripngd/ripng_nb.h \ + ripngd/ripng_nexthop.h \ + ripngd/ripng_route.h \ + ripngd/ripngd.h \ + # end + +ripngd_ripngd_LDADD = lib/libfrr.la $(LIBCAP) +nodist_ripngd_ripngd_SOURCES = \ + yang/frr-ripngd.yang.c \ + # end diff --git a/sharpd/.gitignore b/sharpd/.gitignore new file mode 100644 index 0000000..91b9f2e --- /dev/null +++ b/sharpd/.gitignore @@ -0,0 +1,2 @@ +sharpd +sharpd.conf diff --git a/sharpd/Makefile b/sharpd/Makefile new file mode 100644 index 0000000..6a904d1 --- /dev/null +++ b/sharpd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. sharpd/sharpd +%: ALWAYS + @$(MAKE) -s -C .. sharpd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/sharpd/sharp_globals.h b/sharpd/sharp_globals.h new file mode 100644 index 0000000..76b043b --- /dev/null +++ b/sharpd/sharp_globals.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SHARP - code to track globals + * Copyright (C) 2019 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __SHARP_GLOBAL_H__ +#define __SHARP_GLOBAL_H__ + +#include "lib/srv6.h" + +DECLARE_MGROUP(SHARPD); + +struct sharp_routes { + /* The original prefix for route installation */ + struct prefix orig_prefix; + + /* The nexthop info we are using for installation */ + struct nexthop nhop; + struct nexthop backup_nhop; + uint32_t nhgid; + struct nexthop_group nhop_group; + struct nexthop_group backup_nhop_group; + + uint32_t total_routes; + uint32_t installed_routes; + uint32_t removed_routes; + int32_t repeat; + + /* ZAPI_ROUTE's flag */ + uint32_t flags; + + uint8_t inst; + vrf_id_t vrf_id; + + struct timeval t_start; + struct timeval t_end; + + char opaque[ZAPI_MESSAGE_OPAQUE_LENGTH]; +}; + +struct sharp_srv6_locator { + /* name of locator */ + char name[SRV6_LOCNAME_SIZE]; + + /* list of struct prefix_ipv6 */ + struct list *chunks; +}; + +struct sharp_global { + /* Global data about route install/deletions */ + struct sharp_routes r; + + /* The list of nexthops that we are watching and data about them */ + struct list *nhs; + + /* Traffic Engineering Database */ + struct ls_ted *ted; + + /* list of sharp_srv6_locator */ + struct list *srv6_locators; +}; + +extern struct sharp_global sg; +#endif diff --git a/sharpd/sharp_logpump.c b/sharpd/sharp_logpump.c new file mode 100644 index 0000000..d02921f --- /dev/null +++ b/sharpd/sharp_logpump.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * testing log message generator + * Copyright (C) 2019-2020 David Lamparter for NetDEF, Inc. + */ + +#include +#include + +#include "vty.h" +#include "command.h" +#include "prefix.h" +#include "nexthop.h" +#include "log.h" +#include "frrevent.h" +#include "vrf.h" +#include "zclient.h" +#include "frr_pthread.h" + +#include "sharpd/sharp_vty.h" + +/* this is quite hacky, but then again it's a test tool and it does its job. */ +static struct frr_pthread *lpt; + +static unsigned long lp_duration; +static unsigned lp_frequency; +static unsigned lp_burst; +static size_t lp_ctr, lp_expect; +static struct rusage lp_rusage; +static struct vty *lp_vty; + +extern struct event_loop *master; + +static void logpump_done(struct event *thread) +{ + double x; + + vty_out(lp_vty, "\nlogpump done\n"); + vty_out(lp_vty, "%9zu messages written\n", lp_ctr); + x = (double)lp_ctr / (double)lp_expect * 100.; + vty_out(lp_vty, "%9zu messages targeted = %5.1lf%%\n", lp_expect, x); + + x = lp_rusage.ru_utime.tv_sec * 1000000 + lp_rusage.ru_utime.tv_usec; + x /= (double)lp_ctr; + vty_out(lp_vty, "%6llu.%06u usr %9.1lfns/msg\n", + (unsigned long long)lp_rusage.ru_utime.tv_sec, + (unsigned)lp_rusage.ru_utime.tv_usec, x * 1000.); + + x = lp_rusage.ru_stime.tv_sec * 1000000 + lp_rusage.ru_stime.tv_usec; + x /= (double)lp_ctr; + vty_out(lp_vty, "%6llu.%06u sys %9.1lfns/msg\n", + (unsigned long long)lp_rusage.ru_stime.tv_sec, + (unsigned)lp_rusage.ru_stime.tv_usec, x * 1000.); + + frr_pthread_stop(lpt, NULL); + frr_pthread_destroy(lpt); + lpt = NULL; +} + +static void *logpump_run(void *arg) +{ + struct timespec start, next, now; + unsigned long delta, period; + + period = 1000000000L / lp_frequency; + + zlog_tls_buffer_init(); + + clock_gettime(CLOCK_MONOTONIC, &start); + next = start; + do { + for (size_t inburst = 0; inburst < lp_burst; inburst++) + zlog_debug("log pump: %zu (burst %zu)", + lp_ctr++, inburst); + + clock_gettime(CLOCK_MONOTONIC, &now); + delta = (now.tv_sec - start.tv_sec) * 1000000000L + + (now.tv_nsec - start.tv_nsec); + + next.tv_nsec += period; + if (next.tv_nsec > 1000000000L) { + next.tv_sec++; + next.tv_nsec -= 1000000000L; + } +#ifdef HAVE_CLOCK_NANOSLEEP + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); +#else + struct timespec slpdur; + + slpdur.tv_sec = next.tv_sec - now.tv_sec; + slpdur.tv_nsec = next.tv_nsec - now.tv_nsec; + if (slpdur.tv_nsec < 0) { + slpdur.tv_sec--; + slpdur.tv_nsec += 1000000000L; + } + + nanosleep(&slpdur, NULL); +#endif + } while (delta < lp_duration); + + zlog_tls_buffer_fini(); + +#ifdef RUSAGE_THREAD + getrusage(RUSAGE_THREAD, &lp_rusage); +#else + getrusage(RUSAGE_SELF, &lp_rusage); +#endif + + event_add_timer_msec(master, logpump_done, NULL, 0, NULL); + return NULL; +} + +static int logpump_halt(struct frr_pthread *fpt, void **res) +{ + return 0; +} + +/* default frr_pthread attributes */ +static const struct frr_pthread_attr attr = { + .start = logpump_run, + .stop = logpump_halt, +}; + +void sharp_logpump_run(struct vty *vty, unsigned duration, unsigned frequency, + unsigned burst) +{ + if (lpt != NULL) { + vty_out(vty, "logpump already running\n"); + return; + } + + vty_out(vty, "starting logpump...\n"); + vty_out(vty, "keep this VTY open and press Enter to see results\n"); + + lp_vty = vty; + lp_duration = duration * 1000000000UL; + lp_frequency = frequency; + lp_burst = burst; + lp_expect = duration * frequency * burst; + lp_ctr = 0; + + lpt = frr_pthread_new(&attr, "logpump", "logpump"); + frr_pthread_run(lpt, NULL); +} diff --git a/sharpd/sharp_main.c b/sharpd/sharp_main.c new file mode 100644 index 0000000..f4ce147 --- /dev/null +++ b/sharpd/sharp_main.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SHARP - main code + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "keychain.h" +#include "distribute.h" +#include "libfrr.h" +#include "routemap.h" +#include "nexthop_group.h" +#include "link_state.h" + +#include "sharp_zebra.h" +#include "sharp_vty.h" +#include "sharp_globals.h" +#include "sharp_nht.h" + +DEFINE_MGROUP(SHARPD, "sharpd"); + +zebra_capabilities_t _caps_p[] = { +}; + +struct zebra_privs_t sharp_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = {{0}}; + +struct sharp_global sg; + +static void sharp_global_init(void) +{ + memset(&sg, 0, sizeof(sg)); + sg.nhs = list_new(); + sg.nhs->del = (void (*)(void *))sharp_nh_tracker_free; + sg.ted = NULL; + sg.srv6_locators = list_new(); +} + +static void sharp_global_destroy(void) +{ + list_delete(&sg.nhs); + list_delete(&sg.srv6_locators); +} + +/* Master of threads. */ +struct event_loop *master; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + vrf_terminate(); + sharp_zebra_terminate(); + + sharp_global_destroy(); + + frr_fini(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t sharp_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const sharpd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_route_map_info, + &frr_vrf_info, +}; + +/* clang-format off */ +FRR_DAEMON_INFO(sharpd, SHARP, + .vty_port = SHARP_VTY_PORT, + .proghelp = "Implementation of a Sharp of routes daemon.", + + .signals = sharp_signals, + .n_signals = array_size(sharp_signals), + + .privs = &sharp_privs, + + .yang_modules = sharpd_yang_modules, + .n_yang_modules = array_size(sharpd_yang_modules), +); +/* clang-format on */ + +static void sharp_start_configuration(void) +{ + zlog_debug("Configuration has started to be read"); +} + +static void sharp_end_configuration(void) +{ + zlog_debug("Configuration has finished being read"); +} + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&sharpd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + cmd_init_config_callbacks(sharp_start_configuration, + sharp_end_configuration); + sharp_global_init(); + + sharp_nhgroup_init(); + vrf_init(NULL, NULL, NULL, NULL); + + sharp_zebra_init(); + + /* Get configuration file. */ + sharp_vty_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/sharpd/sharp_nht.c b/sharpd/sharp_nht.c new file mode 100644 index 0000000..6d64fcf --- /dev/null +++ b/sharpd/sharp_nht.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SHARP - code to track nexthops + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "memory.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "vty.h" +#include "typesafe.h" +#include "zclient.h" + +#include "sharp_nht.h" +#include "sharp_globals.h" +#include "sharp_zebra.h" + +DEFINE_MTYPE_STATIC(SHARPD, NH_TRACKER, "Nexthop Tracker"); +DEFINE_MTYPE_STATIC(SHARPD, NHG, "Nexthop Group"); + +struct sharp_nh_tracker *sharp_nh_tracker_get(struct prefix *p) +{ + struct listnode *node; + struct sharp_nh_tracker *nht; + + for (ALL_LIST_ELEMENTS_RO(sg.nhs, node, nht)) { + if (prefix_same(&nht->p, p)) + break; + } + + if (nht) + return nht; + + nht = XCALLOC(MTYPE_NH_TRACKER, sizeof(*nht)); + prefix_copy(&nht->p, p); + + listnode_add(sg.nhs, nht); + return nht; +} + +void sharp_nh_tracker_free(struct sharp_nh_tracker *nht) +{ + XFREE(MTYPE_NH_TRACKER, nht); +} + +void sharp_nh_tracker_dump(struct vty *vty) +{ + struct listnode *node; + struct sharp_nh_tracker *nht; + + for (ALL_LIST_ELEMENTS_RO(sg.nhs, node, nht)) + vty_out(vty, "%pFX: Nexthops: %u Updates: %u\n", &nht->p, + nht->nhop_num, nht->updates); +} + +PREDECL_RBTREE_UNIQ(sharp_nhg_rb); + +struct sharp_nhg { + struct sharp_nhg_rb_item mylistitem; + + uint32_t id; + +#define NHG_NAME_LEN 256 + char name[NHG_NAME_LEN]; + + bool installed; +}; + +static uint32_t nhg_id; + +static uint32_t sharp_get_next_nhid(void) +{ + zlog_debug("NHG ID assigned: %u", nhg_id); + return nhg_id++; +} + +struct sharp_nhg_rb_head nhg_head; + +static int sharp_nhg_compare_func(const struct sharp_nhg *a, + const struct sharp_nhg *b) +{ + return strncmp(a->name, b->name, NHG_NAME_LEN); +} + +DECLARE_RBTREE_UNIQ(sharp_nhg_rb, struct sharp_nhg, mylistitem, + sharp_nhg_compare_func); + +static struct sharp_nhg *sharp_nhgroup_find_id(uint32_t id) +{ + struct sharp_nhg *lookup; + + /* Yea its just a for loop, I don't want add complexity + * to sharpd with another RB tree for just IDs + */ + + frr_each (sharp_nhg_rb, &nhg_head, lookup) { + if (lookup->id == id) + return lookup; + } + + return NULL; +} + +static void sharp_nhgroup_add_cb(const char *name) +{ + struct sharp_nhg *snhg; + + snhg = XCALLOC(MTYPE_NHG, sizeof(*snhg)); + snhg->id = sharp_get_next_nhid(); + strlcpy(snhg->name, name, sizeof(snhg->name)); + + sharp_nhg_rb_add(&nhg_head, snhg); +} + +static void sharp_nhgroup_modify_cb(const struct nexthop_group_cmd *nhgc) +{ + struct sharp_nhg lookup; + struct sharp_nhg *snhg; + struct nexthop_group_cmd *bnhgc = NULL; + + strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + + if (!nhgc->nhg.nexthop) + return; + + if (nhgc->backup_list_name[0]) + bnhgc = nhgc_find(nhgc->backup_list_name); + + nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); +} + +static void sharp_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + struct sharp_nhg lookup; + struct sharp_nhg *snhg; + struct nexthop_group_cmd *bnhgc = NULL; + + strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + + if (nhgc->backup_list_name[0]) + bnhgc = nhgc_find(nhgc->backup_list_name); + + nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); +} + +static void sharp_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + struct sharp_nhg lookup; + struct sharp_nhg *snhg; + struct nexthop_group_cmd *bnhgc = NULL; + + strlcpy(lookup.name, nhgc->name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + + if (nhgc->backup_list_name[0]) + bnhgc = nhgc_find(nhgc->backup_list_name); + + nhg_add(snhg->id, &nhgc->nhg, (bnhgc ? &bnhgc->nhg : NULL)); +} + +static void sharp_nhgroup_delete_cb(const char *name) +{ + struct sharp_nhg lookup; + struct sharp_nhg *snhg; + + strlcpy(lookup.name, name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg) + return; + + if (sharp_nhgroup_id_is_installed(snhg->id)) + nhg_del(snhg->id); + sharp_nhg_rb_del(&nhg_head, snhg); + XFREE(MTYPE_NHG, snhg); +} + +uint32_t sharp_nhgroup_get_id(const char *name) +{ + struct sharp_nhg lookup; + struct sharp_nhg *snhg; + + strlcpy(lookup.name, name, sizeof(lookup.name)); + snhg = sharp_nhg_rb_find(&nhg_head, &lookup); + if (!snhg) + return 0; + + return snhg->id; +} + +void sharp_nhgroup_id_set_installed(uint32_t id, bool installed) +{ + struct sharp_nhg *snhg; + + snhg = sharp_nhgroup_find_id(id); + if (!snhg) { + zlog_debug("%s: nhg %u not found", __func__, id); + return; + } + + snhg->installed = installed; +} + +bool sharp_nhgroup_id_is_installed(uint32_t id) +{ + struct sharp_nhg *snhg; + + snhg = sharp_nhgroup_find_id(id); + if (!snhg) { + zlog_debug("%s: nhg %u not found", __func__, id); + return false; + } + + return snhg->installed; +} + +void sharp_nhgroup_init(void) +{ + sharp_nhg_rb_init(&nhg_head); + nhg_id = zclient_get_nhg_start(ZEBRA_ROUTE_SHARP); + + nexthop_group_init(sharp_nhgroup_add_cb, sharp_nhgroup_modify_cb, + sharp_nhgroup_add_nexthop_cb, + sharp_nhgroup_del_nexthop_cb, + sharp_nhgroup_delete_cb); +} diff --git a/sharpd/sharp_nht.h b/sharpd/sharp_nht.h new file mode 100644 index 0000000..b27952a --- /dev/null +++ b/sharpd/sharp_nht.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SHARP - code to track nexthops + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __SHARP_NHT_H__ +#define __SHARP_NHT_H__ + +struct sharp_nh_tracker { + /* What are we watching */ + struct prefix p; + + /* Number of valid nexthops */ + uint32_t nhop_num; + + uint32_t updates; +}; + +extern struct sharp_nh_tracker *sharp_nh_tracker_get(struct prefix *p); +extern void sharp_nh_tracker_free(struct sharp_nh_tracker *nht); + +extern void sharp_nh_tracker_dump(struct vty *vty); + +extern uint32_t sharp_nhgroup_get_id(const char *name); +extern void sharp_nhgroup_id_set_installed(uint32_t id, bool installed); +extern bool sharp_nhgroup_id_is_installed(uint32_t id); + +extern void sharp_nhgroup_init(void); +#endif diff --git a/sharpd/sharp_vty.c b/sharpd/sharp_vty.c new file mode 100644 index 0000000..21c596b --- /dev/null +++ b/sharpd/sharp_vty.c @@ -0,0 +1,1486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SHARP - vty code + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "vty.h" +#include "command.h" +#include "prefix.h" +#include "nexthop.h" +#include "log.h" +#include "vrf.h" +#include "zclient.h" +#include "nexthop_group.h" +#include "linklist.h" +#include "link_state.h" +#include "cspf.h" +#include "tc.h" + +#include "sharpd/sharp_globals.h" +#include "sharpd/sharp_zebra.h" +#include "sharpd/sharp_nht.h" +#include "sharpd/sharp_vty.h" +#include "sharpd/sharp_vty_clippy.c" + +DEFINE_MTYPE_STATIC(SHARPD, SRV6_LOCATOR, "SRv6 Locator"); + +DEFPY(watch_neighbor, watch_neighbor_cmd, + "sharp watch [vrf NAME$vrf_name] neighbor", + "Sharp routing Protocol\n" + "Watch for changes\n" + "The vrf we would like to watch if non-default\n" + "The NAME of the vrf\n" + "Neighbor events\n") +{ + struct vrf *vrf; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + vrf = vrf_lookup_by_name(vrf_name); + + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + sharp_zebra_register_neigh(vrf->vrf_id, AFI_IP, true); + + return CMD_SUCCESS; +} + + +DEFPY(watch_redistribute, watch_redistribute_cmd, + "[no] sharp watch [vrf NAME$vrf_name] redistribute " FRR_REDIST_STR_SHARPD, + NO_STR + "Sharp routing Protocol\n" + "Watch for changes\n" + "The vrf we would like to watch if non-default\n" + "The NAME of the vrf\n" + "Redistribute into Sharp\n" + FRR_REDIST_HELP_STR_SHARPD) +{ + struct vrf *vrf; + int source; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + source = proto_redistnum(AFI_IP, argv[argc-1]->text); + sharp_redistribute_vrf(vrf, source, !no); + + return CMD_SUCCESS; +} + +DEFPY(watch_nexthop_v6, watch_nexthop_v6_cmd, + "sharp watch [vrf NAME$vrf_name] [connected$connected]", + "Sharp routing Protocol\n" + "Watch for changes\n" + "The vrf we would like to watch if non-default\n" + "The NAME of the vrf\n" + "Watch for nexthop changes\n" + "The v6 nexthop to signal for watching\n" + "Watch for import check changes\n" + "The v6 prefix to signal for watching\n" + "Should the route be connected\n") +{ + struct vrf *vrf; + struct prefix p; + bool type_import; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + memset(&p, 0, sizeof(p)); + + if (n) { + type_import = false; + p.prefixlen = IPV6_MAX_BITLEN; + memcpy(&p.u.prefix6, &nhop, IPV6_MAX_BYTELEN); + p.family = AF_INET6; + } else { + type_import = true; + prefix_copy(&p, inhop); + } + + sharp_nh_tracker_get(&p); + sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, + true, !!connected); + + return CMD_SUCCESS; +} + +DEFPY(watch_nexthop_v4, watch_nexthop_v4_cmd, + "sharp watch [vrf NAME$vrf_name] [connected$connected]", + "Sharp routing Protocol\n" + "Watch for changes\n" + "The vrf we would like to watch if non-default\n" + "The NAME of the vrf\n" + "Watch for nexthop changes\n" + "The v4 address to signal for watching\n" + "Watch for import check changes\n" + "The v4 prefix for import check to watch\n" + "Should the route be connected\n") +{ + struct vrf *vrf; + struct prefix p; + bool type_import; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + memset(&p, 0, sizeof(p)); + + if (n) { + type_import = false; + p.prefixlen = IPV4_MAX_BITLEN; + p.u.prefix4 = nhop; + p.family = AF_INET; + } + else { + type_import = true; + prefix_copy(&p, inhop); + } + + sharp_nh_tracker_get(&p); + sharp_zebra_nexthop_watch(&p, vrf->vrf_id, type_import, + true, !!connected); + + return CMD_SUCCESS; +} + +DEFPY(sharp_nht_data_dump, + sharp_nht_data_dump_cmd, + "sharp data nexthop", + "Sharp routing Protocol\n" + "Data about what is going on\n" + "Nexthop information\n") +{ + sharp_nh_tracker_dump(vty); + + return CMD_SUCCESS; +} + +DEFPY (install_routes_data_dump, + install_routes_data_dump_cmd, + "sharp data route", + "Sharp routing Protocol\n" + "Data about what is going on\n" + "Route Install/Removal Information\n") +{ + struct timeval r; + + timersub(&sg.r.t_end, &sg.r.t_start, &r); + vty_out(vty, "Prefix: %pFX Total: %u %u %u Time: %jd.%ld\n", + &sg.r.orig_prefix, sg.r.total_routes, sg.r.installed_routes, + sg.r.removed_routes, (intmax_t)r.tv_sec, (long)r.tv_usec); + + return CMD_SUCCESS; +} + +DEFPY (install_routes, + install_routes_cmd, + "sharp install routes [vrf NAME$vrf_name]\ + \ + |\ + nexthop-group NHGNAME$nexthop_group>\ + [backup$backup ] \ + (1-1000000)$routes [instance (0-255)$instance] [repeat (2-1000)$rpt] [opaque WORD] [no-recurse$norecurse]", + "Sharp routing Protocol\n" + "install some routes\n" + "Routes to install\n" + "The vrf we would like to install into if non-default\n" + "The NAME of the vrf\n" + "v4 Address to start /32 generation at\n" + "v6 Address to start /32 generation at\n" + "Nexthop to use(Can be an IPv4 or IPv6 address)\n" + "V4 Nexthop address to use\n" + "V6 Nexthop address to use\n" + "Nexthop-Group to use\n" + "The Name of the nexthop-group\n" + "Backup nexthop to use(Can be an IPv4 or IPv6 address)\n" + "Backup V4 Nexthop address to use\n" + "Backup V6 Nexthop address to use\n" + "How many to create\n" + "Instance to use\n" + "Instance\n" + "Should we repeat this command\n" + "How many times to repeat this command\n" + "What opaque data to send down\n" + "The opaque data\n" + "No recursive nexthops\n") +{ + struct vrf *vrf; + struct prefix prefix; + uint32_t rts; + uint32_t nhgid = 0; + + sg.r.total_routes = routes; + sg.r.installed_routes = 0; + sg.r.flags = 0; + + if (rpt >= 2) + sg.r.repeat = rpt * 2; + else + sg.r.repeat = 0; + + memset(&prefix, 0, sizeof(prefix)); + memset(&sg.r.orig_prefix, 0, sizeof(sg.r.orig_prefix)); + nexthop_del_srv6_seg6local(&sg.r.nhop); + nexthop_del_srv6_seg6(&sg.r.nhop); + memset(&sg.r.nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.nhop_group, 0, sizeof(sg.r.nhop_group)); + memset(&sg.r.backup_nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.backup_nhop_group, 0, sizeof(sg.r.nhop_group)); + + if (start4.s_addr != INADDR_ANY) { + prefix.family = AF_INET; + prefix.prefixlen = IPV4_MAX_BITLEN; + prefix.u.prefix4 = start4; + } else { + prefix.family = AF_INET6; + prefix.prefixlen = IPV6_MAX_BITLEN; + prefix.u.prefix6 = start6; + } + sg.r.orig_prefix = prefix; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + /* Explicit backup not available with named nexthop-group */ + if (backup && nexthop_group) { + vty_out(vty, "%% Invalid: cannot specify both nexthop-group and backup\n"); + return CMD_WARNING; + } + + if (nexthop_group) { + struct nexthop_group_cmd *nhgc = nhgc_find(nexthop_group); + if (!nhgc) { + vty_out(vty, + "Specified Nexthop Group: %s does not exist\n", + nexthop_group); + return CMD_WARNING; + } + + nhgid = sharp_nhgroup_get_id(nexthop_group); + sg.r.nhgid = nhgid; + sg.r.nhop_group.nexthop = nhgc->nhg.nexthop; + + /* Use group's backup nexthop info if present */ + if (nhgc->backup_list_name[0]) { + struct nexthop_group_cmd *bnhgc = + nhgc_find(nhgc->backup_list_name); + + if (!bnhgc) { + vty_out(vty, "%% Backup group %s not found for group %s\n", + nhgc->backup_list_name, + nhgc->name); + return CMD_WARNING; + } + + sg.r.backup_nhop.vrf_id = vrf->vrf_id; + sg.r.backup_nhop_group.nexthop = bnhgc->nhg.nexthop; + } + } else { + if (nexthop4.s_addr != INADDR_ANY) { + sg.r.nhop.gate.ipv4 = nexthop4; + sg.r.nhop.type = NEXTHOP_TYPE_IPV4; + } else { + sg.r.nhop.gate.ipv6 = nexthop6; + sg.r.nhop.type = NEXTHOP_TYPE_IPV6; + } + + sg.r.nhop.vrf_id = vrf->vrf_id; + sg.r.nhop_group.nexthop = &sg.r.nhop; + } + + /* Use single backup nexthop if specified */ + if (backup) { + /* Set flag and index in primary nexthop */ + SET_FLAG(sg.r.nhop.flags, NEXTHOP_FLAG_HAS_BACKUP); + sg.r.nhop.backup_num = 1; + sg.r.nhop.backup_idx[0] = 0; + + if (backup_nexthop4.s_addr != INADDR_ANY) { + sg.r.backup_nhop.gate.ipv4 = backup_nexthop4; + sg.r.backup_nhop.type = NEXTHOP_TYPE_IPV4; + } else { + sg.r.backup_nhop.gate.ipv6 = backup_nexthop6; + sg.r.backup_nhop.type = NEXTHOP_TYPE_IPV6; + } + + sg.r.backup_nhop.vrf_id = vrf->vrf_id; + sg.r.backup_nhop_group.nexthop = &sg.r.backup_nhop; + } + + if (opaque) + strlcpy(sg.r.opaque, opaque, ZAPI_MESSAGE_OPAQUE_LENGTH); + else + sg.r.opaque[0] = '\0'; + + /* Default is to ask for recursive nexthop resolution */ + if (norecurse == NULL) + SET_FLAG(sg.r.flags, ZEBRA_FLAG_ALLOW_RECURSION); + + sg.r.inst = instance; + sg.r.vrf_id = vrf->vrf_id; + rts = routes; + sharp_install_routes_helper(&prefix, sg.r.vrf_id, sg.r.inst, nhgid, + &sg.r.nhop_group, &sg.r.backup_nhop_group, + rts, sg.r.flags, sg.r.opaque); + + return CMD_SUCCESS; +} + +DEFPY (install_seg6_routes, + install_seg6_routes_cmd, + "sharp install seg6-routes [vrf NAME$vrf_name]\ + \ + nexthop-seg6 X:X::X:X$seg6_nh6 encap X:X::X:X$seg6_seg\ + (1-1000000)$routes [repeat (2-1000)$rpt]", + "Sharp routing Protocol\n" + "install some routes\n" + "Routes to install\n" + "The vrf we would like to install into if non-default\n" + "The NAME of the vrf\n" + "v4 Address to start /32 generation at\n" + "v6 Address to start /32 generation at\n" + "Nexthop-seg6 to use\n" + "V6 Nexthop address to use\n" + "Encap mode\n" + "Segment List to use\n" + "How many to create\n" + "Should we repeat this command\n" + "How many times to repeat this command\n") +{ + struct vrf *vrf; + struct prefix prefix; + uint32_t route_flags = 0; + + sg.r.total_routes = routes; + sg.r.installed_routes = 0; + + if (rpt >= 2) + sg.r.repeat = rpt * 2; + else + sg.r.repeat = 0; + + memset(&prefix, 0, sizeof(prefix)); + memset(&sg.r.orig_prefix, 0, sizeof(sg.r.orig_prefix)); + nexthop_del_srv6_seg6local(&sg.r.nhop); + nexthop_del_srv6_seg6(&sg.r.nhop); + memset(&sg.r.nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.nhop_group, 0, sizeof(sg.r.nhop_group)); + memset(&sg.r.backup_nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.backup_nhop_group, 0, sizeof(sg.r.nhop_group)); + sg.r.opaque[0] = '\0'; + sg.r.inst = 0; + + if (start4.s_addr != INADDR_ANY) { + prefix.family = AF_INET; + prefix.prefixlen = IPV4_MAX_BITLEN; + prefix.u.prefix4 = start4; + } else { + prefix.family = AF_INET6; + prefix.prefixlen = IPV6_MAX_BITLEN; + prefix.u.prefix6 = start6; + } + sg.r.orig_prefix = prefix; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + sg.r.nhop.type = NEXTHOP_TYPE_IPV6; + sg.r.nhop.gate.ipv6 = seg6_nh6; + sg.r.nhop.vrf_id = vrf->vrf_id; + sg.r.nhop_group.nexthop = &sg.r.nhop; + nexthop_add_srv6_seg6(&sg.r.nhop, &seg6_seg, 1); + + sg.r.vrf_id = vrf->vrf_id; + sharp_install_routes_helper(&prefix, sg.r.vrf_id, sg.r.inst, 0, + &sg.r.nhop_group, &sg.r.backup_nhop_group, + routes, route_flags, sg.r.opaque); + + return CMD_SUCCESS; +} + +DEFPY (install_seg6local_routes, + install_seg6local_routes_cmd, + "sharp install seg6local-routes [vrf NAME$vrf_name]\ + X:X::X:X$start6\ + nexthop-seg6local NAME$seg6l_oif\ + \ + (1-1000000)$routes [repeat (2-1000)$rpt]", + "Sharp routing Protocol\n" + "install some routes\n" + "Routes to install\n" + "The vrf we would like to install into if non-default\n" + "The NAME of the vrf\n" + "v6 Address to start /32 generation at\n" + "Nexthop-seg6local to use\n" + "Output device to use\n" + "SRv6 End function to use\n" + "SRv6 End.X function to use\n" + "V6 Nexthop address to use\n" + "SRv6 End.T function to use\n" + "Redirect table id to use\n" + "SRv6 End.DX4 function to use\n" + "V4 Nexthop address to use\n" + "SRv6 End.DX6 function to use\n" + "V6 Nexthop address to use\n" + "SRv6 End.DT6 function to use\n" + "Redirect table id to use\n" + "SRv6 End.DT4 function to use\n" + "Redirect table id to use\n" + "SRv6 End.DT46 function to use\n" + "Redirect table id to use\n" + "How many to create\n" + "Should we repeat this command\n" + "How many times to repeat this command\n") +{ + struct vrf *vrf; + uint32_t route_flags = 0; + struct seg6local_context ctx = {}; + enum seg6local_action_t action; + + sg.r.total_routes = routes; + sg.r.installed_routes = 0; + + if (rpt >= 2) + sg.r.repeat = rpt * 2; + else + sg.r.repeat = 0; + + memset(&sg.r.orig_prefix, 0, sizeof(sg.r.orig_prefix)); + nexthop_del_srv6_seg6local(&sg.r.nhop); + nexthop_del_srv6_seg6(&sg.r.nhop); + memset(&sg.r.nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.nhop_group, 0, sizeof(sg.r.nhop_group)); + memset(&sg.r.backup_nhop, 0, sizeof(sg.r.nhop)); + memset(&sg.r.backup_nhop_group, 0, sizeof(sg.r.nhop_group)); + sg.r.opaque[0] = '\0'; + sg.r.inst = 0; + sg.r.orig_prefix.family = AF_INET6; + sg.r.orig_prefix.prefixlen = 128; + sg.r.orig_prefix.u.prefix6 = start6; + + if (!vrf_name) + vrf_name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(vrf_name); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name); + return CMD_WARNING; + } + + if (seg6l_enddx4) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_DX4; + ctx.nh4 = seg6l_enddx4_nh4; + } else if (seg6l_enddx6) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_DX6; + ctx.nh6 = seg6l_enddx6_nh6; + } else if (seg6l_endx) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_X; + ctx.nh6 = seg6l_endx_nh6; + } else if (seg6l_endt) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_T; + ctx.table = seg6l_endt_table; + } else if (seg6l_enddt6) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_DT6; + ctx.table = seg6l_enddt6_table; + } else if (seg6l_enddt4) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_DT4; + ctx.table = seg6l_enddt4_table; + } else if (seg6l_enddt46) { + action = ZEBRA_SEG6_LOCAL_ACTION_END_DT46; + ctx.table = seg6l_enddt46_table; + } else { + action = ZEBRA_SEG6_LOCAL_ACTION_END; + } + + sg.r.nhop.type = NEXTHOP_TYPE_IFINDEX; + sg.r.nhop.ifindex = ifname2ifindex(seg6l_oif, vrf->vrf_id); + sg.r.nhop.vrf_id = vrf->vrf_id; + sg.r.nhop_group.nexthop = &sg.r.nhop; + nexthop_add_srv6_seg6local(&sg.r.nhop, action, &ctx); + + sg.r.vrf_id = vrf->vrf_id; + sharp_install_routes_helper(&sg.r.orig_prefix, sg.r.vrf_id, sg.r.inst, + 0, &sg.r.nhop_group, + &sg.r.backup_nhop_group, routes, + route_flags, sg.r.opaque); + + return CMD_SUCCESS; +} + +DEFPY(vrf_label, vrf_label_cmd, + "sharp label vrf NAME$vrf_name label (0-100000)$label", + "Sharp Routing Protocol\n" + "Give a vrf a label\n" + "Pop and forward for IPv4\n" + "Pop and forward for IPv6\n" + VRF_CMD_HELP_STR + "The label to use, 0 specifies remove the label installed from previous\n" + "Specified range to use\n") +{ + struct vrf *vrf; + afi_t afi = (ipv4) ? AFI_IP : AFI_IP6; + + if (strcmp(vrf_name, "default") == 0) + vrf = vrf_lookup_by_id(VRF_DEFAULT); + else + vrf = vrf_lookup_by_name(vrf_name); + + if (!vrf) { + vty_out(vty, "Unable to find vrf you silly head\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (label == 0) + label = MPLS_LABEL_NONE; + + vrf_label_add(vrf->vrf_id, afi, label); + return CMD_SUCCESS; +} + +DEFPY (remove_routes, + remove_routes_cmd, + "sharp remove routes [vrf NAME$vrf_name] (1-1000000)$routes [instance (0-255)$instance]", + "Sharp Routing Protocol\n" + "Remove some routes\n" + "Routes to remove\n" + "The vrf we would like to remove from if non-default\n" + "The NAME of the vrf\n" + "v4 Starting spot\n" + "v6 Starting spot\n" + "Routes to uninstall\n" + "instance to use\n" + "Value of instance\n") +{ + struct vrf *vrf; + struct prefix prefix; + + sg.r.total_routes = routes; + sg.r.removed_routes = 0; + uint32_t rts; + + memset(&prefix, 0, sizeof(prefix)); + + if (start4.s_addr != INADDR_ANY) { + prefix.family = AF_INET; + prefix.prefixlen = IPV4_MAX_BITLEN; + prefix.u.prefix4 = start4; + } else { + prefix.family = AF_INET6; + prefix.prefixlen = IPV6_MAX_BITLEN; + prefix.u.prefix6 = start6; + } + + vrf = vrf_lookup_by_name(vrf_name ? vrf_name : VRF_DEFAULT_NAME); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name ? vrf_name : VRF_DEFAULT_NAME); + return CMD_WARNING; + } + + sg.r.inst = instance; + sg.r.vrf_id = vrf->vrf_id; + rts = routes; + sharp_remove_routes_helper(&prefix, sg.r.vrf_id, + sg.r.inst, rts); + + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_sharpd, + show_debugging_sharpd_cmd, + "show debugging [sharp]", + SHOW_STR + DEBUG_STR + "Sharp Information\n") +{ + vty_out(vty, "Sharp debugging status:\n"); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +DEFPY (sharp_lsp_prefix_v4, sharp_lsp_prefix_v4_cmd, + "sharp lsp [update]$update (0-100000)$inlabel\ + nexthop-group NHGNAME$nhgname\ + [prefix A.B.C.D/M$pfx\ + " FRR_IP_REDIST_STR_ZEBRA "$type_str [instance (0-255)$instance]]", + "Sharp Routing Protocol\n" + "Add an LSP\n" + "Update an LSP\n" + "The ingress label to use\n" + "Use nexthops from a nexthop-group\n" + "The nexthop-group name\n" + "Label a prefix\n" + "The v4 prefix to label\n" + FRR_IP_REDIST_HELP_STR_ZEBRA + "Instance to use\n" + "Instance\n") +{ + struct nexthop_group_cmd *nhgc = NULL; + struct nexthop_group_cmd *backup_nhgc = NULL; + struct nexthop_group *backup_nhg = NULL; + struct prefix p = {}; + int type = 0; + bool update_p; + + update_p = (update != NULL); + + /* We're offered a v4 prefix */ + if (pfx->family > 0 && type_str) { + p.family = pfx->family; + p.prefixlen = pfx->prefixlen; + p.u.prefix4 = pfx->prefix; + + type = proto_redistnum(AFI_IP, type_str); + if (type < 0) { + vty_out(vty, "%% Unknown route type '%s'\n", type_str); + return CMD_WARNING; + } + } else if (pfx->family > 0 || type_str) { + vty_out(vty, "%% Must supply both prefix and type\n"); + return CMD_WARNING; + } + + nhgc = nhgc_find(nhgname); + if (!nhgc) { + vty_out(vty, "%% Nexthop-group '%s' does not exist\n", + nhgname); + return CMD_WARNING; + } + + if (nhgc->nhg.nexthop == NULL) { + vty_out(vty, "%% Nexthop-group '%s' is empty\n", nhgname); + return CMD_WARNING; + } + + /* Use group's backup nexthop info if present */ + if (nhgc->backup_list_name[0]) { + backup_nhgc = nhgc_find(nhgc->backup_list_name); + + if (!backup_nhgc) { + vty_out(vty, + "%% Backup group %s not found for group %s\n", + nhgc->backup_list_name, + nhgname); + return CMD_WARNING; + } + backup_nhg = &(backup_nhgc->nhg); + } + + if (sharp_install_lsps_helper(true /*install*/, update_p, + pfx->family > 0 ? &p : NULL, + type, instance, inlabel, + &(nhgc->nhg), backup_nhg) == 0) + return CMD_SUCCESS; + else { + vty_out(vty, "%% LSP install failed!\n"); + return CMD_WARNING; + } +} + +DEFPY(sharp_remove_lsp_prefix_v4, sharp_remove_lsp_prefix_v4_cmd, + "sharp remove lsp \ + (0-100000)$inlabel\ + [nexthop-group NHGNAME$nhgname] \ + [prefix A.B.C.D/M$pfx\ + " FRR_IP_REDIST_STR_ZEBRA "$type_str [instance (0-255)$instance]]", + "Sharp Routing Protocol\n" + "Remove data\n" + "Remove an LSP\n" + "The ingress label\n" + "Use nexthops from a nexthop-group\n" + "The nexthop-group name\n" + "Specify a v4 prefix\n" + "The v4 prefix to label\n" + FRR_IP_REDIST_HELP_STR_ZEBRA + "Routing instance\n" + "Instance to use\n") +{ + struct nexthop_group_cmd *nhgc = NULL; + struct prefix p = {}; + int type = 0; + struct nexthop_group *nhg = NULL; + + /* We're offered a v4 prefix */ + if (pfx->family > 0 && type_str) { + p.family = pfx->family; + p.prefixlen = pfx->prefixlen; + p.u.prefix4 = pfx->prefix; + + type = proto_redistnum(AFI_IP, type_str); + if (type < 0) { + vty_out(vty, "%% Unknown route type '%s'\n", type_str); + return CMD_WARNING; + } + } else if (pfx->family > 0 || type_str) { + vty_out(vty, "%% Must supply both prefix and type\n"); + return CMD_WARNING; + } + + if (nhgname) { + nhgc = nhgc_find(nhgname); + if (!nhgc) { + vty_out(vty, "%% Nexthop-group '%s' does not exist\n", + nhgname); + return CMD_WARNING; + } + + if (nhgc->nhg.nexthop == NULL) { + vty_out(vty, "%% Nexthop-group '%s' is empty\n", + nhgname); + return CMD_WARNING; + } + nhg = &(nhgc->nhg); + } + + if (sharp_install_lsps_helper(false /*!install*/, false, + pfx->family > 0 ? &p : NULL, + type, instance, inlabel, nhg, NULL) == 0) + return CMD_SUCCESS; + else { + vty_out(vty, "%% LSP remove failed!\n"); + return CMD_WARNING; + } +} + +DEFPY (logpump, + logpump_cmd, + "sharp logpump duration (1-60) frequency (1-1000000) burst (1-1000)", + "Sharp Routing Protocol\n" + "Generate bulk log messages for testing\n" + "Duration of run (s)\n" + "Duration of run (s)\n" + "Frequency of bursts (s^-1)\n" + "Frequency of bursts (s^-1)\n" + "Number of log messages per each burst\n" + "Number of log messages per each burst\n") +{ + sharp_logpump_run(vty, duration, frequency, burst); + return CMD_SUCCESS; +} + +DEFPY (create_session, + create_session_cmd, + "sharp create session (1-1024)", + "Sharp Routing Protocol\n" + "Create data\n" + "Create a test session\n" + "Session ID\n") +{ + if (sharp_zclient_create(session) != 0) { + vty_out(vty, "%% Client session error\n"); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFPY (remove_session, + remove_session_cmd, + "sharp remove session (1-1024)", + "Sharp Routing Protocol\n" + "Remove data\n" + "Remove a test session\n" + "Session ID\n") +{ + sharp_zclient_delete(session); + return CMD_SUCCESS; +} + +DEFPY (send_opaque, + send_opaque_cmd, + "sharp send opaque type (1-255) (1-1000)$count", + SHARP_STR + "Send messages for testing\n" + "Send opaque messages\n" + "Type code to send\n" + "Type code to send\n" + "Number of messages to send\n") +{ + sharp_opaque_send(type, 0, 0, 0, count); + return CMD_SUCCESS; +} + +DEFPY (send_opaque_unicast, + send_opaque_unicast_cmd, + "sharp send opaque unicast type (1-255) \ + " FRR_IP_REDIST_STR_ZEBRA "$proto_str \ + [{instance (0-1000) | session (1-1000)}] (1-1000)$count", + SHARP_STR + "Send messages for testing\n" + "Send opaque messages\n" + "Send unicast messages\n" + "Type code to send\n" + "Type code to send\n" + FRR_IP_REDIST_HELP_STR_ZEBRA + "Daemon instance\n" + "Daemon instance\n" + "Session ID\n" + "Session ID\n" + "Number of messages to send\n") +{ + uint32_t proto; + + proto = proto_redistnum(AFI_IP, proto_str); + + sharp_opaque_send(type, proto, instance, session, count); + + return CMD_SUCCESS; +} + +DEFPY (send_opaque_reg, + send_opaque_reg_cmd, + "sharp send opaque \ + " FRR_IP_REDIST_STR_ZEBRA "$proto_str \ + [{instance (0-1000) | session (1-1000)}] type (1-1000)", + SHARP_STR + "Send messages for testing\n" + "Send opaque messages\n" + "Send opaque registration\n" + "Send opaque unregistration\n" + FRR_IP_REDIST_HELP_STR_ZEBRA + "Daemon instance\n" + "Daemon instance\n" + "Session ID\n" + "Session ID\n" + "Opaque sub-type code\n" + "Opaque sub-type code\n") +{ + int proto; + + proto = proto_redistnum(AFI_IP, proto_str); + + sharp_opaque_reg_send((reg != NULL), proto, instance, session, type); + return CMD_SUCCESS; +} + +/* Opaque notifications - register or unregister */ +DEFPY (send_opaque_notif_reg, + send_opaque_notif_reg_cmd, + "sharp send opaque notify type (1-1000)", + SHARP_STR + "Send messages for testing\n" + "Send opaque messages\n" + "Opaque notification messages\n" + "Send notify registration\n" + "Send notify unregistration\n" + "Opaque sub-type code\n" + "Opaque sub-type code\n") +{ + sharp_zebra_opaque_notif_reg((reg != NULL), type); + + return CMD_SUCCESS; +} + +DEFPY (neigh_discover, + neigh_discover_cmd, + "sharp neigh discover [vrf NAME$vrf_name] IFNAME$ifname", + SHARP_STR + "Discover neighbours\n" + "Send an ARP/NDP request\n" + VRF_CMD_HELP_STR + "v4 Destination address\n" + "v6 Destination address\n" + "Interface name\n") +{ + struct vrf *vrf; + struct interface *ifp; + struct prefix prefix; + + memset(&prefix, 0, sizeof(prefix)); + + if (dst4.s_addr != INADDR_ANY) { + prefix.family = AF_INET; + prefix.prefixlen = IPV4_MAX_BITLEN; + prefix.u.prefix4 = dst4; + } else { + prefix.family = AF_INET6; + prefix.prefixlen = 128; + prefix.u.prefix6 = dst6; + } + + vrf = vrf_lookup_by_name(vrf_name ? vrf_name : VRF_DEFAULT_NAME); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name ? vrf_name : VRF_DEFAULT_NAME); + return CMD_WARNING; + } + + ifp = if_lookup_by_name_vrf(ifname, vrf); + if (ifp == NULL) { + vty_out(vty, "%% Can't find interface %s\n", ifname); + return CMD_WARNING; + } + + sharp_zebra_send_arp(ifp, &prefix); + + return CMD_SUCCESS; +} + +DEFPY (import_te, + import_te_cmd, + "sharp import-te", + SHARP_STR + "Import Traffic Engineering\n") +{ + sg.ted = ls_ted_new(1, "Sharp", 0); + sharp_zebra_register_te(); + + return CMD_SUCCESS; +} + +static void sharp_srv6_locator_chunk_free(struct prefix_ipv6 *chunk) +{ + prefix_ipv6_free(&chunk); +} + +DEFPY (sharp_srv6_manager_get_locator_chunk, + sharp_srv6_manager_get_locator_chunk_cmd, + "sharp srv6-manager get-locator-chunk NAME$locator_name", + SHARP_STR + "Segment-Routing IPv6\n" + "Get SRv6 locator-chunk\n" + "SRv6 Locator name\n") +{ + int ret; + struct listnode *node; + struct sharp_srv6_locator *loc; + struct sharp_srv6_locator *loc_found = NULL; + + for (ALL_LIST_ELEMENTS_RO(sg.srv6_locators, node, loc)) { + if (strcmp(loc->name, locator_name)) + continue; + loc_found = loc; + break; + } + if (!loc_found) { + loc = XCALLOC(MTYPE_SRV6_LOCATOR, + sizeof(struct sharp_srv6_locator)); + loc->chunks = list_new(); + loc->chunks->del = + (void (*)(void *))sharp_srv6_locator_chunk_free; + snprintf(loc->name, SRV6_LOCNAME_SIZE, "%s", locator_name); + listnode_add(sg.srv6_locators, loc); + } + + ret = sharp_zebra_srv6_manager_get_locator_chunk(locator_name); + if (ret < 0) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +DEFUN (show_sharp_ted, + show_sharp_ted_cmd, + "show sharp ted [] [verbose|json]", + SHOW_STR + SHARP_STR + "Traffic Engineering Database\n" + "MPLS-TE Vertex\n" + "MPLS-TE router ID (as an IP address)\n" + "MPLS-TE Edge\n" + "MPLS-TE Edge ID (as an IP address)\n" + "MPLS-TE Subnet\n" + "MPLS-TE Subnet ID (as an IP prefix)\n" + "Verbose output\n" + JSON_STR) +{ + int idx = 0; + struct in_addr ip_addr; + struct prefix pref; + struct ls_vertex *vertex; + struct ls_edge *edge; + struct ls_subnet *subnet; + uint64_t key; + struct ls_edge_key ekey; + bool verbose = false; + bool uj = use_json(argc, argv); + json_object *json = NULL; + + if (sg.ted == NULL) { + vty_out(vty, "MPLS-TE import is not enabled\n"); + return CMD_WARNING; + } + + if (uj) + json = json_object_new_object(); + + if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "verbose")) + verbose = true; + + if (argv_find(argv, argc, "vertex", &idx)) { + /* Show Vertex */ + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx + 1]->arg, &ip_addr)) { + vty_out(vty, + "Specified Router ID %s is invalid\n", + argv[idx + 1]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Vertex from the Link State Database */ + key = ((uint64_t)ip_addr.s_addr) & 0xffffffff; + vertex = ls_find_vertex_by_key(sg.ted, key); + if (!vertex) { + vty_out(vty, "No vertex found for ID %pI4\n", + &ip_addr); + return CMD_WARNING; + } + } else + vertex = NULL; + + if (vertex) + ls_show_vertex(vertex, vty, json, verbose); + else + ls_show_vertices(sg.ted, vty, json, verbose); + + } else if (argv_find(argv, argc, "edge", &idx)) { + /* Show Edge */ + if (argv_find(argv, argc, "A.B.C.D", &idx)) { + if (!inet_aton(argv[idx]->arg, &ip_addr)) { + vty_out(vty, + "Specified Edge ID %s is invalid\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Edge from the Link State Database */ + ekey.family = AF_INET; + IPV4_ADDR_COPY(&ekey.k.addr, &ip_addr); + edge = ls_find_edge_by_key(sg.ted, ekey); + if (!edge) { + vty_out(vty, "No edge found for ID %pI4\n", + &ip_addr); + return CMD_WARNING; + } + } else + edge = NULL; + + if (edge) + ls_show_edge(edge, vty, json, verbose); + else + ls_show_edges(sg.ted, vty, json, verbose); + + } else if (argv_find(argv, argc, "subnet", &idx)) { + /* Show Subnet */ + if (argv_find(argv, argc, "A.B.C.D/M", &idx)) { + if (!str2prefix(argv[idx]->arg, &pref)) { + vty_out(vty, "Invalid prefix format %s\n", + argv[idx]->arg); + return CMD_WARNING_CONFIG_FAILED; + } + /* Get the Subnet from the Link State Database */ + subnet = ls_find_subnet(sg.ted, &pref); + if (!subnet) { + vty_out(vty, "No subnet found for ID %pFX\n", + &pref); + return CMD_WARNING; + } + } else + subnet = NULL; + + if (subnet) + ls_show_subnet(subnet, vty, json, verbose); + else + ls_show_subnets(sg.ted, vty, json, verbose); + + } else { + /* Show the complete TED */ + ls_show_ted(sg.ted, vty, json, verbose); + } + + if (uj) + vty_json(vty, json); + + return CMD_SUCCESS; +} + +DEFPY (sharp_srv6_manager_release_locator_chunk, + sharp_srv6_manager_release_locator_chunk_cmd, + "sharp srv6-manager release-locator-chunk NAME$locator_name", + SHARP_STR + "Segment-Routing IPv6\n" + "Release SRv6 locator-chunk\n" + "SRv6 Locator name\n") +{ + int ret; + struct listnode *loc_node; + struct sharp_srv6_locator *loc; + + for (ALL_LIST_ELEMENTS_RO(sg.srv6_locators, loc_node, loc)) { + if (!strcmp(loc->name, locator_name)) { + list_delete_all_node(loc->chunks); + list_delete(&loc->chunks); + listnode_delete(sg.srv6_locators, loc); + XFREE(MTYPE_SRV6_LOCATOR, loc); + break; + } + } + + ret = sharp_zebra_srv6_manager_release_locator_chunk(locator_name); + if (ret < 0) + return CMD_WARNING_CONFIG_FAILED; + + return CMD_SUCCESS; +} + +DEFPY (show_sharp_segment_routing_srv6, + show_sharp_segment_routing_srv6_cmd, + "show sharp segment-routing srv6 [json]", + SHOW_STR + SHARP_STR + "Segment-Routing\n" + "Segment-Routing IPv6\n" + JSON_STR) +{ + char str[256]; + struct listnode *loc_node; + struct listnode *chunk_node; + struct sharp_srv6_locator *loc; + struct prefix_ipv6 *chunk; + bool uj = use_json(argc, argv); + json_object *jo_locs = NULL; + json_object *jo_loc = NULL; + json_object *jo_chunks = NULL; + + if (uj) { + jo_locs = json_object_new_array(); + for (ALL_LIST_ELEMENTS_RO(sg.srv6_locators, loc_node, loc)) { + jo_loc = json_object_new_object(); + json_object_array_add(jo_locs, jo_loc); + json_object_string_add(jo_loc, "name", loc->name); + jo_chunks = json_object_new_array(); + json_object_object_add(jo_loc, "chunks", jo_chunks); + for (ALL_LIST_ELEMENTS_RO(loc->chunks, chunk_node, + chunk)) { + prefix2str(chunk, str, sizeof(str)); + json_array_string_add(jo_chunks, str); + } + } + + vty_json(vty, jo_locs); + } else { + for (ALL_LIST_ELEMENTS_RO(sg.srv6_locators, loc_node, loc)) { + vty_out(vty, "Locator %s has %d prefix chunks\n", + loc->name, listcount(loc->chunks)); + for (ALL_LIST_ELEMENTS_RO(loc->chunks, chunk_node, + chunk)) { + prefix2str(chunk, str, sizeof(str)); + vty_out(vty, " %s\n", str); + } + vty_out(vty, "\n"); + } + } + + return CMD_SUCCESS; +} + +DEFPY (show_sharp_cspf, + show_sharp_cspf_cmd, + "show sharp cspf source \ + destination \ + (0-16777215)$cost \ + [rsv-bw (0-7)$cos BANDWIDTH$bw]", + SHOW_STR + SHARP_STR + "Constraint Shortest Path First path computation\n" + "Source of the path\n" + "IPv4 Source address in dot decimal A.B.C.D\n" + "IPv6 Source address as X:X:X:X\n" + "Destination of the path\n" + "IPv4 Destination address in dot decimal A.B.C.D\n" + "IPv6 Destination address as X:X:X:X\n" + "Maximum Metric\n" + "Maximum TE Metric\n" + "Maxim Delay\n" + "Value of Maximum cost\n" + "Reserved Bandwidth of this path\n" + "Class of Service or Priority level\n" + "Bytes/second (IEEE floating point format)\n") +{ + + struct cspf *algo; + struct constraints csts; + struct c_path *path; + struct listnode *node; + struct ls_edge *edge; + int idx; + + if (sg.ted == NULL) { + vty_out(vty, "MPLS-TE import is not enabled\n"); + return CMD_WARNING; + } + + if ((src4.s_addr != INADDR_ANY && dst4.s_addr == INADDR_ANY) || + (src4.s_addr == INADDR_ANY && dst4.s_addr != INADDR_ANY)) { + vty_out(vty, "Don't mix IPv4 and IPv6 addresses\n"); + return CMD_WARNING; + } + + idx = 6; + memset(&csts, 0, sizeof(struct constraints)); + if (argv_find(argv, argc, "metric", &idx)) { + csts.ctype = CSPF_METRIC; + csts.cost = cost; + } + idx = 6; + if (argv_find(argv, argc, "te-metric", &idx)) { + csts.ctype = CSPF_TE_METRIC; + csts.cost = cost; + } + idx = 6; + if (argv_find(argv, argc, "delay", &idx)) { + csts.ctype = CSPF_DELAY; + csts.cost = cost; + } + if (argc > 9) { + if (sscanf(bw, "%g", &csts.bw) != 1) { + vty_out(vty, "Bandwidth constraints: fscanf: %s\n", + safe_strerror(errno)); + return CMD_WARNING_CONFIG_FAILED; + } + csts.cos = cos; + } + + /* Initialize and call point-to-point Path computation */ + if (src4.s_addr != INADDR_ANY) + algo = cspf_init_v4(NULL, sg.ted, src4, dst4, &csts); + else + algo = cspf_init_v6(NULL, sg.ted, src6, dst6, &csts); + path = compute_p2p_path(algo, sg.ted); + cspf_del(algo); + + if (!path) { + vty_out(vty, "Path computation failed without error\n"); + return CMD_SUCCESS; + } + if (path->status != SUCCESS) { + vty_out(vty, "Path computation failed: %d\n", path->status); + cpath_del(path); + return CMD_SUCCESS; + } + + vty_out(vty, "Path computation success\n"); + vty_out(vty, "\tCost: %d\n", path->weight); + vty_out(vty, "\tEdges:"); + for (ALL_LIST_ELEMENTS_RO(path->edges, node, edge)) { + if (src4.s_addr != INADDR_ANY) + vty_out(vty, " %pI4", + &edge->attributes->standard.remote); + else + vty_out(vty, " %pI6", + &edge->attributes->standard.remote6); + } + vty_out(vty, "\n"); + cpath_del(path); + return CMD_SUCCESS; +} + +static struct interface *if_lookup_vrf_all(const char *ifname) +{ + struct interface *ifp; + struct vrf *vrf; + + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + ifp = if_lookup_by_name(ifname, vrf->vrf_id); + if (ifp) + return ifp; + } + + return NULL; +} + +DEFPY (sharp_interface_protodown, + sharp_interface_protodown_cmd, + "sharp interface IFNAME$ifname protodown", + SHARP_STR + INTERFACE_STR + IFNAME_STR + "Set interface protodown\n") +{ + struct interface *ifp; + + ifp = if_lookup_vrf_all(ifname); + + if (!ifp) { + vty_out(vty, "%% Can't find interface %s\n", ifname); + return CMD_WARNING; + } + + if (sharp_zebra_send_interface_protodown(ifp, true) != 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFPY (no_sharp_interface_protodown, + no_sharp_interface_protodown_cmd, + "no sharp interface IFNAME$ifname protodown", + NO_STR + SHARP_STR + INTERFACE_STR + IFNAME_STR + "Set interface protodown\n") +{ + struct interface *ifp; + + ifp = if_lookup_vrf_all(ifname); + + if (!ifp) { + vty_out(vty, "%% Can't find interface %s\n", ifname); + return CMD_WARNING; + } + + if (sharp_zebra_send_interface_protodown(ifp, false) != 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFPY (tc_filter_rate, + tc_filter_rate_cmd, + "sharp tc dev IFNAME$ifname \ + source $src \ + destination $dst \ + ip-protocol $ip_proto \ + src-port (1-65535)$src_port \ + dst-port (1-65535)$dst_port \ + rate RATE$ratestr", + SHARP_STR + "Traffic control\n" + "TC interface (for qdisc, class, filter)\n" + "TC interface name\n" + "TC filter source\n" + "TC filter source IPv4 prefix\n" + "TC filter source IPv6 prefix\n" + "TC filter destination\n" + "TC filter destination IPv4 prefix\n" + "TC filter destination IPv6 prefix\n" + "TC filter IP protocol\n" + "TC filter IP protocol TCP\n" + "TC filter IP protocol UDP\n" + "TC filter source port\n" + "TC filter source port\n" + "TC filter destination port\n" + "TC filter destination port\n" + "TC rate\n" + "TC rate number (bits/s) or rate string (suffixed with Bps or bit)\n") +{ + struct interface *ifp; + struct protoent *p; + uint64_t rate; + + ifp = if_lookup_vrf_all(ifname); + + if (!ifp) { + vty_out(vty, "%% Can't find interface %s\n", ifname); + return CMD_WARNING; + } + + p = getprotobyname(ip_proto); + if (!p) { + vty_out(vty, "Unable to convert %s to proto id\n", ip_proto); + return CMD_WARNING; + } + + if (tc_getrate(ratestr, &rate) != 0) { + vty_out(vty, "Unable to convert %s to rate\n", ratestr); + return CMD_WARNING; + } + + if (sharp_zebra_send_tc_filter_rate(ifp, src, dst, p->p_proto, src_port, + dst_port, rate) != 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +void sharp_vty_init(void) +{ + install_element(ENABLE_NODE, &install_routes_data_dump_cmd); + install_element(ENABLE_NODE, &install_routes_cmd); + install_element(ENABLE_NODE, &install_seg6_routes_cmd); + install_element(ENABLE_NODE, &install_seg6local_routes_cmd); + install_element(ENABLE_NODE, &remove_routes_cmd); + install_element(ENABLE_NODE, &vrf_label_cmd); + install_element(ENABLE_NODE, &sharp_nht_data_dump_cmd); + install_element(ENABLE_NODE, &watch_neighbor_cmd); + install_element(ENABLE_NODE, &watch_redistribute_cmd); + install_element(ENABLE_NODE, &watch_nexthop_v6_cmd); + install_element(ENABLE_NODE, &watch_nexthop_v4_cmd); + install_element(ENABLE_NODE, &sharp_lsp_prefix_v4_cmd); + install_element(ENABLE_NODE, &sharp_remove_lsp_prefix_v4_cmd); + install_element(ENABLE_NODE, &logpump_cmd); + install_element(ENABLE_NODE, &create_session_cmd); + install_element(ENABLE_NODE, &remove_session_cmd); + install_element(ENABLE_NODE, &send_opaque_cmd); + install_element(ENABLE_NODE, &send_opaque_unicast_cmd); + install_element(ENABLE_NODE, &send_opaque_reg_cmd); + install_element(ENABLE_NODE, &send_opaque_notif_reg_cmd); + install_element(ENABLE_NODE, &neigh_discover_cmd); + install_element(ENABLE_NODE, &import_te_cmd); + + install_element(ENABLE_NODE, &show_debugging_sharpd_cmd); + install_element(ENABLE_NODE, &show_sharp_ted_cmd); + install_element(ENABLE_NODE, &show_sharp_cspf_cmd); + + install_element(ENABLE_NODE, &sharp_srv6_manager_get_locator_chunk_cmd); + install_element(ENABLE_NODE, + &sharp_srv6_manager_release_locator_chunk_cmd); + install_element(ENABLE_NODE, &show_sharp_segment_routing_srv6_cmd); + + install_element(ENABLE_NODE, &sharp_interface_protodown_cmd); + install_element(ENABLE_NODE, &no_sharp_interface_protodown_cmd); + + install_element(ENABLE_NODE, &tc_filter_rate_cmd); + + return; +} diff --git a/sharpd/sharp_vty.h b/sharpd/sharp_vty.h new file mode 100644 index 0000000..460e4f5 --- /dev/null +++ b/sharpd/sharp_vty.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * VTY library for SHARP + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __SHARP_VTY_H__ +#define __SHARP_VTY_H__ + +extern void sharp_vty_init(void); + +struct vty; + +extern void sharp_logpump_run(struct vty *vty, unsigned duration, + unsigned frequency, unsigned burst); + +#endif diff --git a/sharpd/sharp_zebra.c b/sharpd/sharp_zebra.c new file mode 100644 index 0000000..133da91 --- /dev/null +++ b/sharpd/sharp_zebra.c @@ -0,0 +1,1147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect code. + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "frrevent.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "link_state.h" +#include "tc.h" + +#include "sharp_globals.h" +#include "sharp_nht.h" +#include "sharp_zebra.h" + +/* Zebra structure to hold current status. */ +struct zclient *zclient = NULL; + +/* For registering threads. */ +extern struct event_loop *master; + +/* Privs info */ +extern struct zebra_privs_t sharp_privs; + +DEFINE_MTYPE_STATIC(SHARPD, ZC, "Test zclients"); + +/* Struct to hold list of test zclients */ +struct sharp_zclient { + struct sharp_zclient *prev; + struct sharp_zclient *next; + struct zclient *client; +}; + +/* Head of test zclient list */ +static struct sharp_zclient *sharp_clients_head; + +static int sharp_opaque_handler(ZAPI_CALLBACK_ARGS); + +/* Utility to add a test zclient struct to the list */ +static void add_zclient(struct zclient *client) +{ + struct sharp_zclient *node; + + node = XCALLOC(MTYPE_ZC, sizeof(struct sharp_zclient)); + + node->client = client; + + node->next = sharp_clients_head; + if (sharp_clients_head) + sharp_clients_head->prev = node; + sharp_clients_head = node; +} + +/* Interface addition message from zebra. */ +static int sharp_ifp_create(struct interface *ifp) +{ + return 0; +} + +static int sharp_ifp_destroy(struct interface *ifp) +{ + return 0; +} + +static int interface_address_add(ZAPI_CALLBACK_ARGS) +{ + zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + return 0; +} + +static int interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (!c) + return 0; + + connected_free(&c); + return 0; +} + +static int sharp_ifp_up(struct interface *ifp) +{ + return 0; +} + +static int sharp_ifp_down(struct interface *ifp) +{ + return 0; +} + +int sharp_install_lsps_helper(bool install_p, bool update_p, + const struct prefix *p, uint8_t type, + int instance, uint32_t in_label, + const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg) +{ + struct zapi_labels zl = {}; + struct zapi_nexthop *znh; + const struct nexthop *nh; + int i, cmd, ret; + + zl.type = ZEBRA_LSP_SHARP; + zl.local_label = in_label; + + if (p) { + SET_FLAG(zl.message, ZAPI_LABELS_FTN); + prefix_copy(&zl.route.prefix, p); + zl.route.type = type; + zl.route.instance = instance; + } + + /* List of nexthops is optional for delete */ + i = 0; + if (nhg) { + for (ALL_NEXTHOPS_PTR(nhg, nh)) { + znh = &zl.nexthops[i]; + + /* Must have labels to be useful */ + if (nh->nh_label == NULL || + nh->nh_label->num_labels == 0) + continue; + + if (nh->type == NEXTHOP_TYPE_IFINDEX || + nh->type == NEXTHOP_TYPE_BLACKHOLE) + /* Hmm - can't really deal with these types */ + continue; + + ret = zapi_nexthop_from_nexthop(znh, nh); + if (ret < 0) + return -1; + + i++; + if (i >= MULTIPATH_NUM) + break; + } + } + + /* Whoops - no nexthops isn't very useful for install */ + if (i == 0 && install_p) + return -1; + + zl.nexthop_num = i; + + /* Add optional backup nexthop info. Since these are used by index, + * we can't just skip over an invalid backup nexthop: we will + * invalidate the entire operation. + */ + if (backup_nhg != NULL) { + i = 0; + for (ALL_NEXTHOPS_PTR(backup_nhg, nh)) { + znh = &zl.backup_nexthops[i]; + + /* Must have labels to be useful */ + if (nh->nh_label == NULL || + nh->nh_label->num_labels == 0) + return -1; + + if (nh->type == NEXTHOP_TYPE_IFINDEX || + nh->type == NEXTHOP_TYPE_BLACKHOLE) + /* Hmm - can't really deal with these types */ + return -1; + + ret = zapi_nexthop_from_nexthop(znh, nh); + if (ret < 0) + return -1; + + i++; + if (i >= MULTIPATH_NUM) + break; + } + + if (i > 0) + SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS); + + zl.backup_nexthop_num = i; + } + + + if (install_p) { + if (update_p) + cmd = ZEBRA_MPLS_LABELS_REPLACE; + else + cmd = ZEBRA_MPLS_LABELS_ADD; + } else { + cmd = ZEBRA_MPLS_LABELS_DELETE; + } + + if (zebra_send_mpls_labels(zclient, cmd, &zl) == ZCLIENT_SEND_FAILURE) + return -1; + + return 0; +} + +enum where_to_restart { + SHARP_INSTALL_ROUTES_RESTART, + SHARP_DELETE_ROUTES_RESTART, +}; + +struct buffer_delay { + struct prefix p; + uint32_t count; + uint32_t routes; + vrf_id_t vrf_id; + uint8_t instance; + uint32_t nhgid; + uint32_t flags; + const struct nexthop_group *nhg; + const struct nexthop_group *backup_nhg; + enum where_to_restart restart; + char *opaque; +} wb; + +/* + * route_add - Encodes a route to zebra + * + * This function returns true when the route was buffered + * by the underlying stream system + */ +static bool route_add(const struct prefix *p, vrf_id_t vrf_id, uint8_t instance, + uint32_t nhgid, const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg, uint32_t flags, + char *opaque) +{ + struct zapi_route api; + struct zapi_nexthop *api_nh; + struct nexthop *nh; + int i = 0; + + memset(&api, 0, sizeof(api)); + api.vrf_id = vrf_id; + api.type = ZEBRA_ROUTE_SHARP; + api.instance = instance; + api.safi = SAFI_UNICAST; + memcpy(&api.prefix, p, sizeof(*p)); + + api.flags = flags; + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + + /* Only send via ID if nhgroup has been successfully installed */ + if (nhgid && sharp_nhgroup_id_is_installed(nhgid)) { + zapi_route_set_nhg_id(&api, &nhgid); + } else { + for (ALL_NEXTHOPS_PTR(nhg, nh)) { + /* Check if we set a VNI label */ + if (nh->nh_label && + (nh->nh_label_type == ZEBRA_LSP_EVPN)) + SET_FLAG(api.flags, ZEBRA_FLAG_EVPN_ROUTE); + + api_nh = &api.nexthops[i]; + + zapi_nexthop_from_nexthop(api_nh, nh); + + i++; + } + api.nexthop_num = i; + } + + /* Include backup nexthops, if present */ + if (backup_nhg && backup_nhg->nexthop) { + SET_FLAG(api.message, ZAPI_MESSAGE_BACKUP_NEXTHOPS); + + i = 0; + for (ALL_NEXTHOPS_PTR(backup_nhg, nh)) { + api_nh = &api.backup_nexthops[i]; + + zapi_backup_nexthop_from_nexthop(api_nh, nh); + + i++; + } + + api.backup_nexthop_num = i; + } + + if (strlen(opaque)) { + SET_FLAG(api.message, ZAPI_MESSAGE_OPAQUE); + api.opaque.length = strlen(opaque) + 1; + assert(api.opaque.length <= ZAPI_MESSAGE_OPAQUE_LENGTH); + memcpy(api.opaque.data, opaque, api.opaque.length); + } + + if (zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api) == + ZCLIENT_SEND_BUFFERED) + return true; + else + return false; +} + +/* + * route_delete - Encodes a route for deletion to zebra + * + * This function returns true when the route sent was + * buffered by the underlying stream system. + */ +static bool route_delete(struct prefix *p, vrf_id_t vrf_id, uint8_t instance) +{ + struct zapi_route api; + + memset(&api, 0, sizeof(api)); + api.vrf_id = vrf_id; + api.type = ZEBRA_ROUTE_SHARP; + api.safi = SAFI_UNICAST; + api.instance = instance; + memcpy(&api.prefix, p, sizeof(*p)); + + if (zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api) == + ZCLIENT_SEND_BUFFERED) + return true; + else + return false; +} + +static void sharp_install_routes_restart(struct prefix *p, uint32_t count, + vrf_id_t vrf_id, uint8_t instance, + uint32_t nhgid, + const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg, + uint32_t routes, uint32_t flags, + char *opaque) +{ + uint32_t temp, i; + bool v4 = false; + + if (p->family == AF_INET) { + v4 = true; + temp = ntohl(p->u.prefix4.s_addr); + } else + temp = ntohl(p->u.val32[3]); + + for (i = count; i < routes; i++) { + bool buffered = route_add(p, vrf_id, (uint8_t)instance, nhgid, + nhg, backup_nhg, flags, opaque); + if (v4) + p->u.prefix4.s_addr = htonl(++temp); + else + p->u.val32[3] = htonl(++temp); + + if (buffered) { + wb.p = *p; + wb.count = i + 1; + wb.routes = routes; + wb.vrf_id = vrf_id; + wb.instance = instance; + wb.nhgid = nhgid; + wb.nhg = nhg; + wb.flags = flags; + wb.backup_nhg = backup_nhg; + wb.opaque = opaque; + wb.restart = SHARP_INSTALL_ROUTES_RESTART; + + return; + } + } +} + +void sharp_install_routes_helper(struct prefix *p, vrf_id_t vrf_id, + uint8_t instance, uint32_t nhgid, + const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg, + uint32_t routes, uint32_t flags, char *opaque) +{ + zlog_debug("Inserting %u routes", routes); + + /* Only use backup route/nexthops if present */ + if (backup_nhg && (backup_nhg->nexthop == NULL)) + backup_nhg = NULL; + + monotime(&sg.r.t_start); + sharp_install_routes_restart(p, 0, vrf_id, instance, nhgid, nhg, + backup_nhg, routes, flags, opaque); +} + +static void sharp_remove_routes_restart(struct prefix *p, uint32_t count, + vrf_id_t vrf_id, uint8_t instance, + uint32_t routes) +{ + uint32_t temp, i; + bool v4 = false; + + if (p->family == AF_INET) { + v4 = true; + temp = ntohl(p->u.prefix4.s_addr); + } else + temp = ntohl(p->u.val32[3]); + + for (i = count; i < routes; i++) { + bool buffered = route_delete(p, vrf_id, (uint8_t)instance); + + if (v4) + p->u.prefix4.s_addr = htonl(++temp); + else + p->u.val32[3] = htonl(++temp); + + if (buffered) { + wb.p = *p; + wb.count = i + 1; + wb.vrf_id = vrf_id; + wb.instance = instance; + wb.routes = routes; + wb.restart = SHARP_DELETE_ROUTES_RESTART; + + return; + } + } +} + +void sharp_remove_routes_helper(struct prefix *p, vrf_id_t vrf_id, + uint8_t instance, uint32_t routes) +{ + zlog_debug("Removing %u routes", routes); + + monotime(&sg.r.t_start); + + sharp_remove_routes_restart(p, 0, vrf_id, instance, routes); +} + +static void handle_repeated(bool installed) +{ + struct prefix p = sg.r.orig_prefix; + sg.r.repeat--; + + if (sg.r.repeat <= 0) + return; + + if (installed) { + sg.r.removed_routes = 0; + sharp_remove_routes_helper(&p, sg.r.vrf_id, sg.r.inst, + sg.r.total_routes); + } + + if (!installed) { + sg.r.installed_routes = 0; + sharp_install_routes_helper( + &p, sg.r.vrf_id, sg.r.inst, sg.r.nhgid, + &sg.r.nhop_group, &sg.r.backup_nhop_group, + sg.r.total_routes, sg.r.flags, sg.r.opaque); + } +} + +static void sharp_zclient_buffer_ready(void) +{ + switch (wb.restart) { + case SHARP_INSTALL_ROUTES_RESTART: + sharp_install_routes_restart( + &wb.p, wb.count, wb.vrf_id, wb.instance, wb.nhgid, + wb.nhg, wb.backup_nhg, wb.routes, wb.flags, wb.opaque); + return; + case SHARP_DELETE_ROUTES_RESTART: + sharp_remove_routes_restart(&wb.p, wb.count, wb.vrf_id, + wb.instance, wb.routes); + return; + } +} + +static int route_notify_owner(ZAPI_CALLBACK_ARGS) +{ + struct timeval r; + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table, ¬e, NULL, + NULL)) + return -1; + + switch (note) { + case ZAPI_ROUTE_INSTALLED: + sg.r.installed_routes++; + if (sg.r.total_routes == sg.r.installed_routes) { + monotime(&sg.r.t_end); + timersub(&sg.r.t_end, &sg.r.t_start, &r); + zlog_debug("Installed All Items %jd.%ld", + (intmax_t)r.tv_sec, (long)r.tv_usec); + handle_repeated(true); + } + break; + case ZAPI_ROUTE_FAIL_INSTALL: + zlog_debug("Failed install of route"); + break; + case ZAPI_ROUTE_BETTER_ADMIN_WON: + zlog_debug("Better Admin Distance won over us"); + break; + case ZAPI_ROUTE_REMOVED: + sg.r.removed_routes++; + if (sg.r.total_routes == sg.r.removed_routes) { + monotime(&sg.r.t_end); + timersub(&sg.r.t_end, &sg.r.t_start, &r); + zlog_debug("Removed all Items %jd.%ld", + (intmax_t)r.tv_sec, (long)r.tv_usec); + handle_repeated(false); + } + break; + case ZAPI_ROUTE_REMOVE_FAIL: + zlog_debug("Route removal Failure"); + break; + } + return 0; +} + +static void zebra_connected(struct zclient *zclient) +{ + zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, zclient, true); + zclient_send_reg_requests(zclient, VRF_DEFAULT); + + /* + * Do not actually turn this on yet + * This is just the start of the infrastructure needed here + * This can be fixed at a later time. + * + * zebra_redistribute_send(ZEBRA_REDISTRIBUTE_ADD, zclient, AFI_IP, + * ZEBRA_ROUTE_ALL, 0, VRF_DEFAULT); + */ +} + +void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label) +{ + zclient_send_vrf_label(zclient, vrf_id, afi, label, ZEBRA_LSP_SHARP); +} + +void nhg_add(uint32_t id, const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg) +{ + struct zapi_nhg api_nhg = {}; + struct zapi_nexthop *api_nh; + struct nexthop *nh; + bool is_valid = true; + + api_nhg.id = id; + + api_nhg.resilience = nhg->nhgr; + + for (ALL_NEXTHOPS_PTR(nhg, nh)) { + if (api_nhg.nexthop_num >= MULTIPATH_NUM) { + zlog_warn( + "%s: number of nexthops greater than max multipath size, truncating", + __func__); + break; + } + + /* Unresolved nexthops will lead to failure - only send + * nexthops that zebra will consider valid. + */ + if (nh->ifindex == 0) + continue; + + api_nh = &api_nhg.nexthops[api_nhg.nexthop_num]; + + zapi_nexthop_from_nexthop(api_nh, nh); + api_nhg.nexthop_num++; + } + + if (api_nhg.nexthop_num == 0) { + if (sharp_nhgroup_id_is_installed(id)) { + zlog_debug("%s: nhg %u: no nexthops, deleting nexthop group", __func__, + id); + zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); + return; + } + zlog_debug("%s: nhg %u not sent: no valid nexthops", __func__, + id); + is_valid = false; + goto done; + } + + if (backup_nhg) { + for (ALL_NEXTHOPS_PTR(backup_nhg, nh)) { + if (api_nhg.backup_nexthop_num >= MULTIPATH_NUM) { + zlog_warn( + "%s: number of backup nexthops greater than max multipath size, truncating", + __func__); + break; + } + + /* Unresolved nexthop: will be rejected by zebra. + * That causes a problem, since the primary nexthops + * rely on array indexing into the backup nexthops. If + * that array isn't valid, the backup indexes won't be + * valid. + */ + if (nh->ifindex == 0) { + zlog_debug("%s: nhg %u: invalid backup nexthop", + __func__, id); + is_valid = false; + break; + } + + api_nh = &api_nhg.backup_nexthops + [api_nhg.backup_nexthop_num]; + + zapi_backup_nexthop_from_nexthop(api_nh, nh); + api_nhg.backup_nexthop_num++; + } + } + +done: + if (is_valid) + zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); +} + +void nhg_del(uint32_t id) +{ + struct zapi_nhg api_nhg = {}; + + api_nhg.id = id; + + zclient_nhg_send(zclient, ZEBRA_NHG_DEL, &api_nhg); +} + +void sharp_zebra_nexthop_watch(struct prefix *p, vrf_id_t vrf_id, bool import, + bool watch, bool connected) +{ + int command; + + command = ZEBRA_NEXTHOP_REGISTER; + + if (!watch) + command = ZEBRA_NEXTHOP_UNREGISTER; + + if (zclient_send_rnh(zclient, command, p, SAFI_UNICAST, connected, + false, vrf_id) == ZCLIENT_SEND_FAILURE) + zlog_warn("%s: Failure to send nexthop to zebra", __func__); +} + +static int sharp_debug_nexthops(struct zapi_route *api) +{ + int i; + + if (api->nexthop_num == 0) { + zlog_debug(" Not installed"); + return 0; + } + + for (i = 0; i < api->nexthop_num; i++) { + struct zapi_nexthop *znh = &api->nexthops[i]; + + switch (znh->type) { + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + zlog_debug( + " Nexthop %pI4, type: %d, ifindex: %d, vrf: %d, label_num: %d", + &znh->gate.ipv4.s_addr, znh->type, znh->ifindex, + znh->vrf_id, znh->label_num); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + case NEXTHOP_TYPE_IPV6: + zlog_debug( + " Nexthop %pI6, type: %d, ifindex: %d, vrf: %d, label_num: %d", + &znh->gate.ipv6, znh->type, znh->ifindex, + znh->vrf_id, znh->label_num); + break; + case NEXTHOP_TYPE_IFINDEX: + zlog_debug(" Nexthop IFINDEX: %d, ifindex: %d", + znh->type, znh->ifindex); + break; + case NEXTHOP_TYPE_BLACKHOLE: + zlog_debug(" Nexthop blackhole"); + break; + } + } + + return i; +} + +static void sharp_nexthop_update(struct vrf *vrf, struct prefix *matched, + struct zapi_route *nhr) +{ + struct sharp_nh_tracker *nht; + + zlog_debug("Received update for %pFX actual match: %pFX metric: %u", + matched, &nhr->prefix, nhr->metric); + + nht = sharp_nh_tracker_get(matched); + nht->nhop_num = nhr->nexthop_num; + nht->updates++; + + sharp_debug_nexthops(nhr); +} + +static int sharp_redistribute_route(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route api; + + if (zapi_route_decode(zclient->ibuf, &api) < 0) + zlog_warn("%s: Decode of redistribute failed: %d", __func__, + ZEBRA_REDISTRIBUTE_ROUTE_ADD); + + zlog_debug("%s: %pFX (%s)", zserv_command_string(cmd), &api.prefix, + zebra_route_string(api.type)); + + sharp_debug_nexthops(&api); + + return 0; +} + +void sharp_redistribute_vrf(struct vrf *vrf, int type, bool turn_on) +{ + zebra_redistribute_send(turn_on ? ZEBRA_REDISTRIBUTE_ADD + : ZEBRA_REDISTRIBUTE_DELETE, + zclient, AFI_IP, type, 0, vrf->vrf_id); +} + +static zclient_handler *const sharp_opaque_handlers[] = { + [ZEBRA_OPAQUE_MESSAGE] = sharp_opaque_handler, +}; + +/* Add a zclient with a specified session id, for testing. */ +int sharp_zclient_create(uint32_t session_id) +{ + struct zclient *client; + struct sharp_zclient *node; + + /* Check for duplicates */ + for (node = sharp_clients_head; node != NULL; node = node->next) { + if (node->client->session_id == session_id) + return -1; + } + + client = zclient_new(master, &zclient_options_default, + sharp_opaque_handlers, + array_size(sharp_opaque_handlers)); + client->sock = -1; + client->session_id = session_id; + + zclient_init(client, ZEBRA_ROUTE_SHARP, 0, &sharp_privs); + + /* Enqueue on the list of test clients */ + add_zclient(client); + + return 0; +} + +/* Delete one of the extra test zclients */ +int sharp_zclient_delete(uint32_t session_id) +{ + struct sharp_zclient *node; + + /* Search for session */ + for (node = sharp_clients_head; node != NULL; node = node->next) { + if (node->client->session_id == session_id) { + /* Dequeue from list */ + if (node->next) + node->next->prev = node->prev; + if (node->prev) + node->prev->next = node->next; + if (node == sharp_clients_head) + sharp_clients_head = node->next; + + /* Clean up zclient */ + zclient_stop(node->client); + zclient_free(node->client); + + /* Free memory */ + XFREE(MTYPE_ZC, node); + break; + } + } + + return 0; +} + +static const char *const type2txt[] = {"Generic", "Vertex", "Edge", "Subnet"}; +static const char *const status2txt[] = {"Unknown", "New", "Update", + "Delete", "Sync", "Orphan"}; +/* Handler for opaque messages */ +static int sharp_opaque_handler(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct zapi_opaque_msg info; + struct ls_element *lse; + + s = zclient->ibuf; + + if (zclient_opaque_decode(s, &info) != 0) + return -1; + + zlog_debug("%s: [%u] received opaque type %u", __func__, + zclient->session_id, info.type); + + if (info.type == LINK_STATE_UPDATE) { + lse = ls_stream2ted(sg.ted, s, true); + if (lse) { + zlog_debug(" |- Got %s %s from Link State Database", + status2txt[lse->status], + type2txt[lse->type]); + lse->status = SYNC; + } else + zlog_debug( + "%s: Error to convert Stream into Link State", + __func__); + } + + return 0; +} + +/* Handler for opaque notification messages */ +static int sharp_opq_notify_handler(ZAPI_CALLBACK_ARGS) +{ + struct stream *s; + struct zapi_opaque_notif_info info; + + s = zclient->ibuf; + + if (zclient_opaque_notif_decode(s, &info) != 0) + return -1; + + if (info.reg) + zlog_debug("%s: received opaque notification REG, type %u => %d/%d/%d", + __func__, info.msg_type, info.proto, info.instance, + info.session_id); + else + zlog_debug("%s: received opaque notification UNREG, type %u", + __func__, info.msg_type); + + return 0; +} + +/* + * Send OPAQUE messages, using subtype 'type'. + */ +void sharp_opaque_send(uint32_t type, uint32_t proto, uint32_t instance, + uint32_t session_id, uint32_t count) +{ + uint8_t buf[32]; + int ret; + uint32_t i; + + /* Prepare a small payload */ + for (i = 0; i < sizeof(buf); i++) { + if (type < 255) + buf[i] = type; + else + buf[i] = 255; + } + + /* Send some messages - broadcast and unicast are supported */ + for (i = 0; i < count; i++) { + if (proto == 0) + ret = zclient_send_opaque(zclient, type, buf, + sizeof(buf)); + else + ret = zclient_send_opaque_unicast(zclient, type, proto, + instance, session_id, + buf, sizeof(buf)); + if (ret == ZCLIENT_SEND_FAILURE) { + zlog_debug("%s: send_opaque() failed => %d", __func__, + ret); + break; + } + } +} + +/* + * Register/unregister for opaque notifications from zebra about 'type'. + */ +void sharp_zebra_opaque_notif_reg(bool is_reg, uint32_t type) +{ + if (is_reg) + zclient_opaque_request_notify(zclient, type); + else + zclient_opaque_drop_notify(zclient, type); +} + +/* + * Send OPAQUE registration messages, using subtype 'type'. + */ +void sharp_opaque_reg_send(bool is_reg, uint32_t proto, uint32_t instance, + uint32_t session_id, uint32_t type) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + if (is_reg) + zclient_create_header(s, ZEBRA_OPAQUE_REGISTER, VRF_DEFAULT); + else + zclient_create_header(s, ZEBRA_OPAQUE_UNREGISTER, VRF_DEFAULT); + + /* Send sub-type */ + stream_putl(s, type); + + /* Add zclient info */ + stream_putc(s, proto); + stream_putw(s, instance); + stream_putl(s, session_id); + + /* Put length at the first point of the stream. */ + stream_putw_at(s, 0, stream_get_endp(s)); + + (void)zclient_send_message(zclient); +} + +/* Link State registration */ +void sharp_zebra_register_te(void) +{ + /* First register to received Link State Update messages */ + zclient_register_opaque(zclient, LINK_STATE_UPDATE); + + /* Then, request initial TED with SYNC message */ + ls_request_sync(zclient); +} + +void sharp_zebra_send_arp(const struct interface *ifp, const struct prefix *p) +{ + zclient_send_neigh_discovery_req(zclient, ifp, p); +} + +static int nhg_notify_owner(ZAPI_CALLBACK_ARGS) +{ + enum zapi_nhg_notify_owner note; + uint32_t id; + + if (!zapi_nhg_notify_decode(zclient->ibuf, &id, ¬e)) + return -1; + + switch (note) { + case ZAPI_NHG_INSTALLED: + sharp_nhgroup_id_set_installed(id, true); + zlog_debug("Installed nhg %u", id); + break; + case ZAPI_NHG_FAIL_INSTALL: + zlog_debug("Failed install of nhg %u", id); + break; + case ZAPI_NHG_REMOVED: + sharp_nhgroup_id_set_installed(id, false); + zlog_debug("Removed nhg %u", id); + break; + case ZAPI_NHG_REMOVE_FAIL: + zlog_debug("Failed removal of nhg %u", id); + break; + } + + return 0; +} + +int sharp_zebra_srv6_manager_get_locator_chunk(const char *locator_name) +{ + return srv6_manager_get_locator_chunk(zclient, locator_name); +} + +int sharp_zebra_srv6_manager_release_locator_chunk(const char *locator_name) +{ + return srv6_manager_release_locator_chunk(zclient, locator_name); +} + +static int sharp_zebra_process_srv6_locator_chunk(ZAPI_CALLBACK_ARGS) +{ + struct stream *s = NULL; + struct srv6_locator_chunk s6c = {}; + struct listnode *node, *nnode; + struct sharp_srv6_locator *loc; + + s = zclient->ibuf; + zapi_srv6_locator_chunk_decode(s, &s6c); + + for (ALL_LIST_ELEMENTS(sg.srv6_locators, node, nnode, loc)) { + struct prefix_ipv6 *chunk = NULL; + struct listnode *chunk_node; + struct prefix_ipv6 *c; + + if (strcmp(loc->name, s6c.locator_name) != 0) { + zlog_err("%s: Locator name unmatch %s:%s", __func__, + loc->name, s6c.locator_name); + continue; + } + + for (ALL_LIST_ELEMENTS_RO(loc->chunks, chunk_node, c)) + if (!prefix_cmp(c, &s6c.prefix)) + return 0; + + chunk = prefix_ipv6_new(); + *chunk = s6c.prefix; + listnode_add(loc->chunks, chunk); + return 0; + } + + zlog_err("%s: can't get locator_chunk!!", __func__); + return 0; +} + +static int sharp_zebra_process_neigh(ZAPI_CALLBACK_ARGS) +{ + union sockunion addr = {}, lladdr = {}; + struct zapi_neigh_ip api = {}; + struct interface *ifp; + + zlog_debug("Received a neighbor event"); + zclient_neigh_ip_decode(zclient->ibuf, &api); + + if (api.ip_in.ipa_type == AF_UNSPEC) + return 0; + + sockunion_family(&addr) = api.ip_in.ipa_type; + memcpy((uint8_t *)sockunion_get_addr(&addr), &api.ip_in.ip.addr, + family2addrsize(api.ip_in.ipa_type)); + + sockunion_family(&lladdr) = api.ip_out.ipa_type; + if (api.ip_out.ipa_type != AF_UNSPEC) + memcpy((uint8_t *)sockunion_get_addr(&lladdr), + &api.ip_out.ip.addr, + family2addrsize(api.ip_out.ipa_type)); + ifp = if_lookup_by_index(api.index, vrf_id); + if (!ifp) { + zlog_debug("Failed to lookup interface for neighbor entry: %u for %u", + api.index, vrf_id); + return 0; + } + + zlog_debug("Received: %s %pSU dev %s lladr %pSU", + (cmd == ZEBRA_NEIGH_ADDED) ? "NEW" : "DEL", &addr, ifp->name, + &lladdr); + + return 0; +} + +int sharp_zebra_send_interface_protodown(struct interface *ifp, bool down) +{ + zlog_debug("Sending zebra to set %s protodown %s", ifp->name, + down ? "on" : "off"); + + if (zclient_send_interface_protodown(zclient, ifp->vrf->vrf_id, ifp, + down) == ZCLIENT_SEND_FAILURE) + return -1; + + return 0; +} + +int sharp_zebra_send_tc_filter_rate(struct interface *ifp, + const struct prefix *source, + const struct prefix *destination, + uint8_t ip_proto, uint16_t src_port, + uint16_t dst_port, uint64_t rate) +{ +#define SHARPD_TC_HANDLE 0x0001 + struct stream *s; + + s = zclient->obuf; + + struct tc_qdisc q = {.ifindex = ifp->ifindex, .kind = TC_QDISC_HTB}; + + zapi_tc_qdisc_encode(ZEBRA_TC_QDISC_INSTALL, s, &q); + if (zclient_send_message(zclient) == ZCLIENT_SEND_FAILURE) + return -1; + + struct tc_class c = {.ifindex = ifp->ifindex, + .handle = SHARPD_TC_HANDLE & 0xffff, + .kind = TC_QDISC_HTB, + .u.htb.ceil = rate, + .u.htb.rate = rate}; + + zapi_tc_class_encode(ZEBRA_TC_CLASS_ADD, s, &c); + if (zclient_send_message(zclient) == ZCLIENT_SEND_FAILURE) + return -1; + + struct tc_filter f = {.ifindex = ifp->ifindex, + .handle = SHARPD_TC_HANDLE, + .priority = 0x1, + .kind = TC_FILTER_FLOWER, + .u.flower.filter_bm = 0}; + +#ifdef ETH_P_IP + f.protocol = ETH_P_IP; +#else + f.protocol = 0x0800; +#endif + + f.u.flower.filter_bm |= TC_FLOWER_IP_PROTOCOL; + f.u.flower.ip_proto = ip_proto; + f.u.flower.filter_bm |= TC_FLOWER_SRC_IP; + prefix_copy(&f.u.flower.src_ip, source); + f.u.flower.filter_bm |= TC_FLOWER_DST_IP; + prefix_copy(&f.u.flower.dst_ip, destination); + f.u.flower.filter_bm |= TC_FLOWER_SRC_PORT; + f.u.flower.src_port_min = f.u.flower.src_port_max = src_port; + f.u.flower.filter_bm |= TC_FLOWER_DST_PORT; + f.u.flower.dst_port_min = f.u.flower.dst_port_max = dst_port; + f.u.flower.classid = SHARPD_TC_HANDLE & 0xffff; + + zapi_tc_filter_encode(ZEBRA_TC_FILTER_ADD, s, &f); + if (zclient_send_message(zclient) == ZCLIENT_SEND_FAILURE) + return -1; + + return 0; +} + +void sharp_zebra_register_neigh(vrf_id_t vrf_id, afi_t afi, bool reg) +{ + zclient_register_neigh(zclient, vrf_id, afi, reg); +} + + +static zclient_handler *const sharp_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = interface_address_delete, + [ZEBRA_ROUTE_NOTIFY_OWNER] = route_notify_owner, + [ZEBRA_NHG_NOTIFY_OWNER] = nhg_notify_owner, + [ZEBRA_REDISTRIBUTE_ROUTE_ADD] = sharp_redistribute_route, + [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = sharp_redistribute_route, + [ZEBRA_OPAQUE_MESSAGE] = sharp_opaque_handler, + [ZEBRA_OPAQUE_NOTIFY] = sharp_opq_notify_handler, + [ZEBRA_SRV6_MANAGER_GET_LOCATOR_CHUNK] = + sharp_zebra_process_srv6_locator_chunk, + [ZEBRA_NEIGH_ADDED] = sharp_zebra_process_neigh, + [ZEBRA_NEIGH_REMOVED] = sharp_zebra_process_neigh, +}; + +void sharp_zebra_init(void) +{ + hook_register_prio(if_real, 0, sharp_ifp_create); + hook_register_prio(if_up, 0, sharp_ifp_up); + hook_register_prio(if_down, 0, sharp_ifp_down); + hook_register_prio(if_unreal, 0, sharp_ifp_destroy); + + zclient = zclient_new(master, &zclient_options_default, sharp_handlers, + array_size(sharp_handlers)); + + zclient_init(zclient, ZEBRA_ROUTE_SHARP, 0, &sharp_privs); + zclient->zebra_connected = zebra_connected; + zclient->zebra_buffer_write_ready = sharp_zclient_buffer_ready; + zclient->nexthop_update = sharp_nexthop_update; +} + +void sharp_zebra_terminate(void) +{ + struct sharp_zclient *node = sharp_clients_head; + + while (node) { + sharp_zclient_delete(node->client->session_id); + + node = sharp_clients_head; + } + + zclient_stop(zclient); + zclient_free(zclient); +} diff --git a/sharpd/sharp_zebra.h b/sharpd/sharp_zebra.h new file mode 100644 index 0000000..5cbcc14 --- /dev/null +++ b/sharpd/sharp_zebra.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for SHARP + * Copyright (C) Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __SHARP_ZEBRA_H__ +#define __SHARP_ZEBRA_H__ + +extern void sharp_zebra_init(void); +extern void sharp_zebra_terminate(void); + +/* Add and delete extra zapi client sessions, for testing */ +int sharp_zclient_create(uint32_t session_id); +int sharp_zclient_delete(uint32_t session_id); + +extern void vrf_label_add(vrf_id_t vrf_id, afi_t afi, mpls_label_t label); +extern void nhg_add(uint32_t id, const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg); +extern void nhg_del(uint32_t id); +extern void sharp_zebra_nexthop_watch(struct prefix *p, vrf_id_t vrf_id, + bool import, bool watch, bool connected); + +extern void sharp_install_routes_helper(struct prefix *p, vrf_id_t vrf_id, + uint8_t instance, uint32_t nhgid, + const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg, + uint32_t routes, uint32_t flags, + char *opaque); +extern void sharp_remove_routes_helper(struct prefix *p, vrf_id_t vrf_id, + uint8_t instance, uint32_t routes); + +int sharp_install_lsps_helper(bool install_p, bool update_p, + const struct prefix *p, uint8_t type, + int instance, uint32_t in_label, + const struct nexthop_group *nhg, + const struct nexthop_group *backup_nhg); + +/* Send OPAQUE messages, using subtype 'type'. */ +void sharp_opaque_send(uint32_t type, uint32_t proto, uint32_t instance, + uint32_t session_id, uint32_t count); + +/* Send OPAQUE registration or notification registration messages, + * for opaque subtype 'type'. + */ +void sharp_opaque_reg_send(bool is_reg, uint32_t proto, uint32_t instance, + uint32_t session_id, uint32_t type); + +/* Register/unregister for opaque notifications from zebra about 'type'. */ +void sharp_zebra_opaque_notif_reg(bool is_reg, uint32_t type); + +extern void sharp_zebra_send_arp(const struct interface *ifp, + const struct prefix *p); + +/* Register Link State Opaque messages */ +extern void sharp_zebra_register_te(void); + +extern void sharp_redistribute_vrf(struct vrf *vrf, int source, bool turn_on); + +extern int sharp_zebra_srv6_manager_get_locator_chunk(const char *lname); +extern int sharp_zebra_srv6_manager_release_locator_chunk(const char *lname); +extern void sharp_install_seg6local_route_helper(struct prefix *p, + uint8_t instance, + enum seg6local_action_t act, + struct seg6local_context *ctx); + +extern int sharp_zebra_send_interface_protodown(struct interface *ifp, + bool down); +extern int sharp_zebra_send_tc_filter_rate(struct interface *ifp, + const struct prefix *source, + const struct prefix *destination, + uint8_t ip_proto, uint16_t src_port, + uint16_t dst_port, uint64_t rate); + +extern void sharp_zebra_register_neigh(vrf_id_t vrf_id, afi_t afi, bool reg); +#endif diff --git a/sharpd/subdir.am b/sharpd/subdir.am new file mode 100644 index 0000000..3eb8d1d --- /dev/null +++ b/sharpd/subdir.am @@ -0,0 +1,32 @@ +# +# sharpd +# + +if SHARPD +noinst_LIBRARIES += sharpd/libsharp.a +sbin_PROGRAMS += sharpd/sharpd +vtysh_daemons += sharpd +man8 += $(MANBUILD)/frr-sharpd.8 +endif + +sharpd_libsharp_a_SOURCES = \ + sharpd/sharp_nht.c \ + sharpd/sharp_zebra.c \ + sharpd/sharp_vty.c \ + sharpd/sharp_logpump.c \ + # end + +noinst_HEADERS += \ + sharpd/sharp_nht.h \ + sharpd/sharp_vty.h \ + sharpd/sharp_globals.h \ + sharpd/sharp_zebra.h \ + # end + +clippy_scan += \ + sharpd/sharp_vty.c \ + # end + +sharpd_sharpd_SOURCES = sharpd/sharp_main.c +sharpd_sharpd_LDADD = sharpd/libsharp.a lib/libfrr.la $(LIBCAP) + diff --git a/snapcraft/.gitignore b/snapcraft/.gitignore new file mode 100644 index 0000000..a4796fd --- /dev/null +++ b/snapcraft/.gitignore @@ -0,0 +1,5 @@ +snapcraft.yaml +parts +prime +stage +frr*.snap diff --git a/snapcraft/README.snap_build.md b/snapcraft/README.snap_build.md new file mode 100644 index 0000000..9d83a52 --- /dev/null +++ b/snapcraft/README.snap_build.md @@ -0,0 +1,117 @@ +Building your own FRRouting Snap +======================================== +(Tested on Ubuntu 18.04) + +1. Install snapcraft: + + sudo apt-get install snapcraft + +2. Checkout FRRouting under a **unpriviledged** user account + + git clone https://github.com/frrouting/frr.git + cd frr + +3. (Optional) Add extra version information to + `snapcraft/extra_version_info.txt`. Information in this file will + be displayed with the frr.version command (simple `cat` after + the display of the `zebra --version` output) + +4. Run Bootstrap and make distribution tar.gz + + ./bootstrap.sh + ./configure --with-pkg-extra-version=-MySnapVersion + make dist + + Note: configure parameters are not important for the Snap building, + except the `with-pkg-extra-version` if you want to give the Snap + a specific name to mark your own unoffical build + + This will build `frr-something.tar.gz` - the distribution tar and + the snapcraft/snapcraft.yaml with the matching version number + +5. Create snap + + cd snapcraft + snapcraft + + You should now end up with `frr_something.snap` + +Installing the snap +=================== +(This can be done on a different system) + +1. Install snapd + + sudo apt-get install snapd + +2. Install self-built frr snap. (`--force-dangerous` is required to + install a unsigned self-built snap) + + snap install --force-dangerous ./frr*.snap + + Connect the priviledged `network-control` plug to the snap: + + snap connect frr:network-control core:network-control + +See README.usage.md for more details on setting up and using the snap + +DONE. + +The Snap will be auto-started and running. + +Operations +========== + +### FRRouting Daemons +At this time, all FRRouting daemons are auto-started. + +A daemon can be stopped/started with (ie ospf6d) + + systemctl stop snap.frr.ospf6d.service + systemctl start snap.frr.ospf6d.service + +or disabled/enabled with + + systemctl disable snap.frr.ospf6d.service + systemctl enable snap.frr.ospf6d.service + +### FRRouting Commands +All the commands are prefixed with frr. + + frr.vtysh -> vtysh + frr.version -> Just gives version output (zebra --version) + frr.readme -> Returns simple README with hints on using FRR + + frr.bgpd-debug -> Directly start each daemon (without service) + frr.isisd-debug + frr.ospf6d-debug + frr.ospfd-debug + frr.pimd-debug + frr.pim6d-debug + frr.ripd-debug + frr.ripngd-debug + frr.ldp-debug + frr.zebra-debug + frr.nhrpd-debug + frr.babeld-debug + frr.eigrpd-debug + frr.pbrd-debug + frr.staticd-debug + frr.bfdd-debug + frr.fabricd-debug + frr.pathd-debug + +vtysh can be accessed as frr.vtysh (Make sure you have /snap/bin in your +path). If access as `vtysh` instead of `frr.vtysh` is needed, you can enable it +via a snap alias as follows: + + sudo snap alias frr vtysh + +This will add the vtysh command to your /snap/bin for direct access. The output of + + sudo snap aliases + +should list vtysh command alias as enabled: + +App Alias Notes +frr.vtysh vtysh enabled diff --git a/snapcraft/README.usage.md b/snapcraft/README.usage.md new file mode 100644 index 0000000..888a228 --- /dev/null +++ b/snapcraft/README.usage.md @@ -0,0 +1,174 @@ +Using the FRRouting Snap +=============================== + +After installing the Snap, the priviledged plug need to be connected: + + snap connect frr:network-control core:network-control + +Enabling/Disabling FRRouting Daemons +------------------------------------------- + +By default (at this time), all FRRouting daemons will be enabled +on installation. If you want to disable a specific daemon, then use +the systemctl commands + +ie for `ospf6d` (OSPFv3): + + systemctl disable snap.frr.ospf6d.service + systemctl enable snap.frr.ospf6d.service + +The daemons are: `ripd`, `ripngd`, `ospfd`, `ospf6d`, `isisd`, `bgpd`, +`pimd`, `pim6d`, `ldpd`, `eigrpd`, `babeld`, `nhrpd`, `bfdd`, `vrrpd`, +`pbrd`, `pathd`, `fabricd`, `staticd`, `zebra` + +Commands defined by this snap +----------------------------- + +- `frr.vtysh`: + FRRouting VTY Shell (configuration tool) +- `frr.version`: + Returns output of `zebra --version` to display version and configured + options +- `frr.readme`: + Returns this document `cat README_usage.md` +- `frr.set`: + Allows to enable `FPM` and/or disable RPKIi module. See Module section below + +and for debugging defined at this time (May get removed later - do not +depend on them). These are mainly intended to debug the Snap + +- `frr.zebra-debug`: + Starts zebra daemon in foreground +- `frr.ripd-debug`: + Starts ripd daemon in foreground +- `frr.ripngd-debug`: + Starts ripng daemon in foreground +- `frr.ospfd-debug`: + Starts ospfd daemon in foreground +- `frr.ospf6d-debug`: + Starts ospf6d daemon in foreground +- `frr.isisd-debug`: + Starts isisd daemon in foreground +- `frr.bgpd-debug`: + Starts bgpd daemon in foreground +- `frr.pimd-debug`: + Starts pimd daemon in foreground +- `frr.pim6d-debug`: + Starts pim6d daemon in foreground +- `frr.ldpd-debug`: + Starts ldpd daemon in foreground +- `frr.nhrpd-debug`: + Starts nhrpd daemon in foreground +- `frr.babeld-debug`: + Starts babeld daemon in foreground +- `frr.eigrpd-debug`: + Starts eigrpd daemon in foreground +- `frr.pbrd-debug`: + Starts pbrd daemon in foreground +- `frr.staticd-debug`: + Starts staticd daemon in foreground +- `frr.bfdd-debug`: + Starts bfdd daemon in foreground +- `frr.fabricd-debug`: + Starts fabricd daemon in foreground + +MPLS (LDP) +---------- +The MPLS forwarding requires a Linux Kernel version 4.5 or newer and +specific MPLS kernel modules loaded. It will be auto-detected by +FRR. You can check the detected setup with the `show mpls status` +command from within `frr.vtysh` + +The following kernel modules `mpls-router` and `mpls-iptunnel` +need to be loaded. On Ubuntu 16.04, this can be done by editing +'/etc/modules-load.d/modules.conf' and add the following lines: + + # Load MPLS Kernel Modules + mpls-router + mpls-iptunnel + +For other distributions, please check the documentation on loading +modules. You need to either reboot or use `modprobe` to manually load +the modules as well before MPLS will be available. + +In addition to this, the MPLS Label-Processing needs to be enabled +with `sysctl` on the required interfaces. Assuming the interfaces +are named `eth0`, `eth1` and `eth2`, then the additional lines in +`/etc/sysctl.conf` will enable it on a Ubuntu 16.04 system: + + # Enable MPLS Label processing on all interfaces + net.mpls.conf.eth0.input=1 + net.mpls.conf.eth1.input=1 + net.mpls.conf.eth2.input=1 + net.mpls.platform_labels=100000 + +These settings require either a reboot or a manual configuration with +`sysctl` as well. + +Modules +---------- +The `frr.set` allows to turn FPM module ond the RPKI module on or off. + + frr.set fpm {disable|protobuf|netlink} + + Disables FPM or enables FPM with selected mode (default: disabled) + +By default, the FPM module is disabled, but installed with netlink and +protobuf support. To enable the FPM module, use the `frr.set fpm protobuf` +or `frr.set fpm netlink` command. The command will only enable the mode +for the next restart of zebra. Please reboot or restart zebra after +changing the mode to become effective. + + frr.set rpki {enable|disable} + + Disables or enables BGP RPKI (default: enabled) + +By default, the RPKI module is enabled. To disable the RPKI module +use the `frr.set rpki disable` command. The command will only enable +the module after the next restart of the bgp daemon. Please reboot or +restart bgpd after changing the mode to become effective. +(Normally, there is no need to disable the module as it has no effect +if there are no RPKI configurations in BGP) + +FAQ +--- +- frr.vtysh displays `--MORE--` on long output. How to suppress this? + - Define `VTYSH_PAGER` to `cat` (default is `more`). (Ie add + `export VTYSH_PAGER=cat` to the end of your `.profile`) + +- bfdd / ospfd / ospf6d / nhrpd are not running after installation + - Installing a new snap starts the daemons, but at this time they + may not have the required privileged access. Make sure you + issue the `snap connect` command as given above (can be verified + with `snap interfaces`) and **THEN** restart the daemons (or + reboot the system). + This is a limitation of any snap package at this time which + requires privileged interfaces (ie to manipulate routing tables) + +- Can I run vtysh directly without the "frr." prefix? + - Yes, enable the vtysh alias in the frr snap package by: + sudo snap alias frr vtysh + +Sourcecode available +==================== + +The source for this SNAP is available as part of the FRRouting +Source Code Distribution under `GPLv2 or later` + + + +Instructions for rebuilding the snap are in `snapcraft/README.snap_build.md` + +*Please checkout the desired branch before following the instructions +as they may have changed between versions of FRR* + +Official Webpage for FRR +======================== + +Official webpage for FRR is at + +Feedback welcome +================ + +Please send Feedback about this snap to Martin Winter at +`mwinter@opensourcerouting.org` diff --git a/snapcraft/defaults/babeld.conf.default b/snapcraft/defaults/babeld.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/bfdd.conf.default b/snapcraft/defaults/bfdd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/bgpd.conf.default b/snapcraft/defaults/bgpd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/eigrpd.conf.default b/snapcraft/defaults/eigrpd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/fabricd.conf.default b/snapcraft/defaults/fabricd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/isisd.conf.default b/snapcraft/defaults/isisd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/ldpd.conf.default b/snapcraft/defaults/ldpd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/nhrpd.conf.default b/snapcraft/defaults/nhrpd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/ospf6d.conf.default b/snapcraft/defaults/ospf6d.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/ospfd.conf.default b/snapcraft/defaults/ospfd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/pathd.conf.default b/snapcraft/defaults/pathd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/pbrd.conf.default b/snapcraft/defaults/pbrd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/pim6d.conf.default b/snapcraft/defaults/pim6d.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/pimd.conf.default b/snapcraft/defaults/pimd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/ripd.conf.default b/snapcraft/defaults/ripd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/ripngd.conf.default b/snapcraft/defaults/ripngd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/staticd.conf.default b/snapcraft/defaults/staticd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/vrrpd.conf.default b/snapcraft/defaults/vrrpd.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/defaults/vtysh.conf.default b/snapcraft/defaults/vtysh.conf.default new file mode 100644 index 0000000..5c15e6b --- /dev/null +++ b/snapcraft/defaults/vtysh.conf.default @@ -0,0 +1 @@ +no service integrated-vtysh-config diff --git a/snapcraft/defaults/zebra.conf.default b/snapcraft/defaults/zebra.conf.default new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/extra_version_info.txt b/snapcraft/extra_version_info.txt new file mode 100644 index 0000000..e69de29 diff --git a/snapcraft/helpers/Makefile b/snapcraft/helpers/Makefile new file mode 100644 index 0000000..c44bee7 --- /dev/null +++ b/snapcraft/helpers/Makefile @@ -0,0 +1,7 @@ +all: + +install: + install -D -m 0755 $(DESTDIR)/usr/bin/telnet.netkit $(DESTDIR)/bin/telnet + install -D -m 0755 $(DESTDIR)/usr/bin/traceroute.db $(DESTDIR)/bin/traceroute + install -D -m 0755 $(DESTDIR)/usr/bin/traceroute6.db $(DESTDIR)/bin/traceroute6 + diff --git a/snapcraft/scripts/Makefile b/snapcraft/scripts/Makefile new file mode 100644 index 0000000..951625f --- /dev/null +++ b/snapcraft/scripts/Makefile @@ -0,0 +1,25 @@ +all: + +install: + mkdir -p $(DESTDIR)/bin + install -D -m 0755 zebra-service $(DESTDIR)/bin/ + install -D -m 0755 bgpd-service $(DESTDIR)/bin/ + install -D -m 0755 ospfd-service $(DESTDIR)/bin/ + install -D -m 0755 ospf6d-service $(DESTDIR)/bin/ + install -D -m 0755 ripd-service $(DESTDIR)/bin/ + install -D -m 0755 ripngd-service $(DESTDIR)/bin/ + install -D -m 0755 isisd-service $(DESTDIR)/bin/ + install -D -m 0755 pimd-service $(DESTDIR)/bin/ + install -D -m 0755 pim6d-service $(DESTDIR)/bin/ + install -D -m 0755 ldpd-service $(DESTDIR)/bin/ + install -D -m 0755 nhrpd-service $(DESTDIR)/bin/ + install -D -m 0755 babeld-service $(DESTDIR)/bin/ + install -D -m 0755 eigrpd-service $(DESTDIR)/bin/ + install -D -m 0755 pbrd-service $(DESTDIR)/bin/ + install -D -m 0755 staticd-service $(DESTDIR)/bin/ + install -D -m 0755 bfdd-service $(DESTDIR)/bin/ + install -D -m 0755 fabricd-service $(DESTDIR)/bin/ + install -D -m 0755 vrrpd-service $(DESTDIR)/bin/ + install -D -m 0755 pathd-service $(DESTDIR)/bin/ + install -D -m 0755 set-options $(DESTDIR)/bin/ + install -D -m 0755 show_version $(DESTDIR)/bin/ diff --git a/snapcraft/scripts/babeld-service b/snapcraft/scripts/babeld-service new file mode 100644 index 0000000..9e022f8 --- /dev/null +++ b/snapcraft/scripts/babeld-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/babeld.conf ]; then + cp $SNAP/etc/frr/babeld.conf.default $SNAP_DATA/babeld.conf +fi +exec $SNAP/sbin/babeld \ + -f $SNAP_DATA/babeld.conf \ + --pid_file $SNAP_DATA/babeld.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/bfdd-service b/snapcraft/scripts/bfdd-service new file mode 100644 index 0000000..f94a7ab --- /dev/null +++ b/snapcraft/scripts/bfdd-service @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/bfdd.conf ]; then + cp $SNAP/etc/frr/bfdd.conf.default $SNAP_DATA/bfdd.conf +fi +exec $SNAP/sbin/bfdd \ + -f $SNAP_DATA/bfdd.conf \ + --pid_file $SNAP_DATA/bfdd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA \ + --bfdctl $SNAP_DATA/bfdd.sock + diff --git a/snapcraft/scripts/bgpd-service b/snapcraft/scripts/bgpd-service new file mode 100644 index 0000000..64273d9 --- /dev/null +++ b/snapcraft/scripts/bgpd-service @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/bgpd.conf ]; then + cp $SNAP/etc/frr/bgpd.conf.default $SNAP_DATA/bgpd.conf +fi +# If no RPKI option is specified, then we create a default +# with RPKI enabled +if ! [ -e $SNAP_DATA/rpki.conf ]; then + echo "-M rpki" > $SNAP_DATA/rpki.conf +fi +EXTRA_OPTIONS="`$SNAP/bin/cat $SNAP_DATA/rpki.conf`" +exec $SNAP/sbin/bgpd \ + -f $SNAP_DATA/bgpd.conf \ + --pid_file $SNAP_DATA/bgpd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA \ + --moduledir $SNAP/lib/frr/modules $EXTRA_OPTIONS + diff --git a/snapcraft/scripts/eigrpd-service b/snapcraft/scripts/eigrpd-service new file mode 100644 index 0000000..fe945e5 --- /dev/null +++ b/snapcraft/scripts/eigrpd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/eigrpd.conf ]; then + cp $SNAP/etc/frr/eigrpd.conf.default $SNAP_DATA/eigrpd.conf +fi +exec $SNAP/sbin/eigrpd \ + -f $SNAP_DATA/eigrpd.conf \ + --pid_file $SNAP_DATA/eigrpd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/fabricd-service b/snapcraft/scripts/fabricd-service new file mode 100644 index 0000000..586f061 --- /dev/null +++ b/snapcraft/scripts/fabricd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/fabricd.conf ]; then + cp $SNAP/etc/frr/fabricd.conf.default $SNAP_DATA/fabricd.conf +fi +exec $SNAP/sbin/fabricd \ + -f $SNAP_DATA/fabricd.conf \ + --pid_file $SNAP_DATA/fabricd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/isisd-service b/snapcraft/scripts/isisd-service new file mode 100644 index 0000000..aef92e9 --- /dev/null +++ b/snapcraft/scripts/isisd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/isisd.conf ]; then + cp $SNAP/etc/frr/isisd.conf.default $SNAP_DATA/isisd.conf +fi +exec $SNAP/sbin/isisd \ + -f $SNAP_DATA/isisd.conf \ + --pid_file $SNAP_DATA/isisd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/ldpd-service b/snapcraft/scripts/ldpd-service new file mode 100644 index 0000000..4c4a8eb --- /dev/null +++ b/snapcraft/scripts/ldpd-service @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/ldpd.conf ]; then + cp $SNAP/etc/frr/ldpd.conf.default $SNAP_DATA/ldpd.conf +fi +exec $SNAP/sbin/ldpd \ + -f $SNAP_DATA/ldpd.conf \ + --pid_file $SNAP_DATA/ldpd.pid \ + --socket $SNAP_DATA/zsock \ + --ctl_socket $SNAP_DATA \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/nhrpd-service b/snapcraft/scripts/nhrpd-service new file mode 100644 index 0000000..a3ba5e3 --- /dev/null +++ b/snapcraft/scripts/nhrpd-service @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/nhrpd.conf ]; then + cp $SNAP/etc/frr/nhrpd.conf.default $SNAP_DATA/nhrpd.conf +fi +exec $SNAP/sbin/nhrpd \ + -f $SNAP_DATA/nhrpd.conf \ + --pid_file $SNAP_DATA/nhrpd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA diff --git a/snapcraft/scripts/ospf6d-service b/snapcraft/scripts/ospf6d-service new file mode 100644 index 0000000..4dc3cb0 --- /dev/null +++ b/snapcraft/scripts/ospf6d-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/ospf6d.conf ]; then + cp $SNAP/etc/frr/ospf6d.conf.default $SNAP_DATA/ospf6d.conf +fi +exec $SNAP/sbin/ospf6d \ + -f $SNAP_DATA/ospf6d.conf \ + --pid_file $SNAP_DATA/ospf6d.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/ospfd-service b/snapcraft/scripts/ospfd-service new file mode 100644 index 0000000..7cac34b --- /dev/null +++ b/snapcraft/scripts/ospfd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/ospfd.conf ]; then + cp $SNAP/etc/frr/ospfd.conf.default $SNAP_DATA/ospfd.conf +fi +exec $SNAP/sbin/ospfd \ + -f $SNAP_DATA/ospfd.conf \ + --pid_file $SNAP_DATA/ospfd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/pathd-service b/snapcraft/scripts/pathd-service new file mode 100644 index 0000000..6473c59 --- /dev/null +++ b/snapcraft/scripts/pathd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/pathd.conf ]; then + cp $SNAP/etc/frr/pathd.conf.default $SNAP_DATA/pathd.conf +fi +exec $SNAP/sbin/pathd \ + -f $SNAP_DATA/pathd.conf \ + --pid_file $SNAP_DATA/pathd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/pbrd-service b/snapcraft/scripts/pbrd-service new file mode 100644 index 0000000..a9265a1 --- /dev/null +++ b/snapcraft/scripts/pbrd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/pbrd.conf ]; then + cp $SNAP/etc/frr/pbrd.conf.default $SNAP_DATA/pbrd.conf +fi +exec $SNAP/sbin/pbrd \ + -f $SNAP_DATA/pbrd.conf \ + --pid_file $SNAP_DATA/pbrd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/pim6d-service b/snapcraft/scripts/pim6d-service new file mode 100644 index 0000000..386c616 --- /dev/null +++ b/snapcraft/scripts/pim6d-service @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/pim6d.conf ]; then + cp $SNAP/etc/frr/pim6d.conf.default $SNAP_DATA/pim6d.conf +fi +exec $SNAP/sbin/pim6d \ + -f $SNAP_DATA/pim6d.conf \ + --pid_file $SNAP_DATA/pim6d.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA diff --git a/snapcraft/scripts/pimd-service b/snapcraft/scripts/pimd-service new file mode 100644 index 0000000..3ddd394 --- /dev/null +++ b/snapcraft/scripts/pimd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/pimd.conf ]; then + cp $SNAP/etc/frr/pimd.conf.default $SNAP_DATA/pimd.conf +fi +exec $SNAP/sbin/pimd \ + -f $SNAP_DATA/pimd.conf \ + --pid_file $SNAP_DATA/pimd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/ripd-service b/snapcraft/scripts/ripd-service new file mode 100644 index 0000000..f9959be --- /dev/null +++ b/snapcraft/scripts/ripd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/ripd.conf ]; then + cp $SNAP/etc/frr/ripd.conf.default $SNAP_DATA/ripd.conf +fi +exec $SNAP/sbin/ripd \ + -f $SNAP_DATA/ripd.conf \ + --pid_file $SNAP_DATA/ripd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/ripngd-service b/snapcraft/scripts/ripngd-service new file mode 100644 index 0000000..bd06e6b --- /dev/null +++ b/snapcraft/scripts/ripngd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/ripngd.conf ]; then + cp $SNAP/etc/frr/ripngd.conf.default $SNAP_DATA/ripngd.conf +fi +exec $SNAP/sbin/ripngd \ + -f $SNAP_DATA/ripngd.conf \ + --pid_file $SNAP_DATA/ripngd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/set-options b/snapcraft/scripts/set-options new file mode 100755 index 0000000..0637d2c --- /dev/null +++ b/snapcraft/scripts/set-options @@ -0,0 +1,63 @@ +#!/bin/sh + +set -e + +case $1 in + fpm) + case $2 in + disable) + rm -f $SNAP_DATA/fpm.conf + echo "FPM module disabled. Please restart FRR" + ;; + protobuf) + echo "-M fpm:protobuf" > $SNAP_DATA/fpm.conf + echo "FPM enabled and set to protobuf mode. Please restart FRR" + ;; + netlink) + echo "-M fpm:netlink" > $SNAP_DATA/fpm.conf + echo "FPM enabled and set to netlink mode. Please restart FRR" + ;; + *) + echo "Usage:" + echo " ${SNAP_NAME}.set fpm {disable|protobuf|netlink}" + echo "" + echo " Disables FPM module or enables it with specified mode" + echo " Mode will be saved for next restart of zebra, but zebra" + echo " is not automatically restarted" + exit 1 + ;; + esac + ;; + rpki) + case $2 in + disable) + echo "" > $SNAP_DATA/rpki.conf + echo "RPKI module disabled. Please restart FRR" + ;; + enable) + echo "-M rpki" > $SNAP_DATA/rpki.conf + echo "RPKI module enabled. Please restart FRR" + ;; + *) + echo "Usage:" + echo " ${SNAP_NAME}.set rpki {disable|enable}" + echo "" + echo " Disables BGP RPKI module or enables it (default: enabled)" + echo " Mode will be saved for next restart of bgpd, but bgpd" + echo " is not automatically restarted" + exit 1 + ;; + esac + ;; + *) + echo "Usage:" + echo " ${SNAP_NAME}.set fpm {disable|protobuf|netlink}" + echo " ${SNAP_NAME}.set rpki {disable|enable}" + echo "" + echo " fpm: Disables FPM or enables FPM with selected mode" + echo " rpki: Disables BGP RPKI or enables it (default: enabled)" + exit 1 + ;; +esac + +exit 0 diff --git a/snapcraft/scripts/show_version b/snapcraft/scripts/show_version new file mode 100644 index 0000000..ca1c2aa --- /dev/null +++ b/snapcraft/scripts/show_version @@ -0,0 +1,6 @@ +#!/bin/sh + +$SNAP/sbin/zebra --version +$SNAP/bin/cat $SNAP/doc/extra_version_info.txt + +exit 0 diff --git a/snapcraft/scripts/staticd-service b/snapcraft/scripts/staticd-service new file mode 100644 index 0000000..3b22881 --- /dev/null +++ b/snapcraft/scripts/staticd-service @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/staticd.conf ]; then + if [ -e $SNAP_DATA/zebra.conf ]; then + # if we have a zebra.conf, but no staticd conf, then we use + # this file as the default config for staticd + cp $SNAP_DATA/zebra.conf $SNAP_DATA/staticd.conf + else + # new config, start with template + cp $SNAP/etc/frr/staticd.conf $SNAP_DATA/staticd.conf + fi +fi +exec $SNAP/sbin/staticd \ + -f $SNAP_DATA/staticd.conf \ + --pid_file $SNAP_DATA/staticd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/vrrpd-service b/snapcraft/scripts/vrrpd-service new file mode 100644 index 0000000..1f60d11 --- /dev/null +++ b/snapcraft/scripts/vrrpd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/vrrpd.conf ]; then + cp $SNAP/etc/frr/vrrpd.conf.default $SNAP_DATA/vrrpd.conf +fi +exec $SNAP/sbin/vrrpd \ + -f $SNAP_DATA/vrrpd.conf \ + --pid_file $SNAP_DATA/vrrpd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/scripts/zebra-service b/snapcraft/scripts/zebra-service new file mode 100644 index 0000000..2ee131f --- /dev/null +++ b/snapcraft/scripts/zebra-service @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/zebra.conf ]; then + cp $SNAP/etc/frr/zebra.conf.default $SNAP_DATA/zebra.conf +fi +if ! [ -e $SNAP_DATA/vtysh.conf ]; then + cp $SNAP/etc/frr/vtysh.conf.default $SNAP_DATA/vtysh.conf +fi +EXTRA_OPTIONS="" +if [ -e $SNAP_DATA/fpm.conf ]; then + EXTRA_OPTIONS="`cat $SNAP_DATA/fpm.conf`" +fi +exec $SNAP/sbin/zebra \ + -f $SNAP_DATA/zebra.conf \ + --pid_file $SNAP_DATA/zebra.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA \ + --moduledir $SNAP/lib/frr/modules $EXTRA_OPTIONS diff --git a/snapcraft/snap/gui/icon.png b/snapcraft/snap/gui/icon.png new file mode 100644 index 0000000..3ab3f8f Binary files /dev/null and b/snapcraft/snap/gui/icon.png differ diff --git a/snapcraft/snapcraft.yaml.in b/snapcraft/snapcraft.yaml.in new file mode 100644 index 0000000..d90236f --- /dev/null +++ b/snapcraft/snapcraft.yaml.in @@ -0,0 +1,433 @@ +name: frr +version: '@VERSION@' +summary: FRRouting BGP/OSPFv2/OSPFv3/ISIS/RIP/RIPng/PIM/LDP/EIGRP/BFD routing daemon +description: BGP/OSPFv2/OSPFv3/ISIS/RIP/RIPng/PIM/LDP/EIGRP/BFD routing daemon + FRRouting (FRR) is free software which manages TCP/IP based routing + protocols. It supports BGP4, BGP4+, OSPFv2, OSPFv3, IS-IS, RIPv1, RIPv2, + RIPng, PIM, LDP, Babel, EIGRP, PBR (Policy-based routing), PATHD (Segment + routing), BFD and OpenFabric as well as the IPv6 versions of these. + FRRouting (frr) is a fork of Quagga. +confinement: strict +grade: devel +base: core18 + +apps: + vtysh: + command: bin/vtysh --vty_socket $SNAP_DATA --config_dir $SNAP_DATA + plugs: + - network + - network-bind + - network-control + aliases: [vtysh] + version: + command: bin/show_version + readme: + command: bin/cat $SNAP/doc/README.usage.md + zebra: + command: bin/zebra-service + daemon: simple + plugs: + - network + - network-bind + - network-control + bgpd: + command: bin/bgpd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + ospfd: + command: bin/ospfd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + ospf6d: + command: bin/ospf6d-service + daemon: simple + plugs: + - network + - network-bind + - network-control + isisd: + command: bin/isisd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + ripd: + command: bin/ripd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + ripngd: + command: bin/ripngd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + pimd: + command: bin/pimd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + pim6d: + command: bin/pim6d-service + daemon: simple + plugs: + - network + - network-bind + - network-control + ldpd: + command: bin/ldpd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + nhrpd: + command: bin/nhrpd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + babeld: + command: bin/babeld-service + daemon: simple + plugs: + - network + - network-bind + - network-control + eigrpd: + command: bin/eigrpd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + pbrd: + command: bin/pbrd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + staticd: + command: bin/staticd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + bfdd: + command: bin/bfdd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + fabricd: + command: bin/fabricd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + vrrpd: + command: bin/vrrpd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + pathd: + command: bin/pathd-service + daemon: simple + plugs: + - network + - network-bind + - network-control + set: + command: bin/set-options + zebra-debug: + command: sbin/zebra -f $SNAP_DATA/zebra.conf --pid_file $SNAP_DATA/zebra.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + bgpd-debug: + command: sbin/bgpd -f $SNAP_DATA/bgpd.conf --pid_file $SNAP_DATA/bgpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA --moduledir $SNAP/lib/frr/modules + plugs: + - network + - network-bind + - network-control + ospfd-debug: + command: sbin/ospfd -f $SNAP_DATA/ospfd.conf --pid_file $SNAP_DATA/ospfd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + ospf6d-debug: + command: sbin/ospf6d -f $SNAP_DATA/ospf6d.conf --pid_file $SNAP_DATA/ospf6d.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + isisd-debug: + command: sbin/isisd -f $SNAP_DATA/isisd.conf --pid_file $SNAP_DATA/isisd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + ripd-debug: + command: sbin/ripd -f $SNAP_DATA/ripd.conf --pid_file $SNAP_DATA/ripd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + ripngd-debug: + command: sbin/ripngd -f $SNAP_DATA/ripngd.conf --pid_file $SNAP_DATA/ripngd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + pimd-debug: + command: sbin/pimd -f $SNAP_DATA/pimd.conf --pid_file $SNAP_DATA/pimd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + pim6d-debug: + command: sbin/pim6d -f $SNAP_DATA/pim6d.conf --pid_file $SNAP_DATA/pim6d.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + ldpd-debug: + command: sbin/ldpd -f $SNAP_DATA/ldpd.conf --pid_file $SNAP_DATA/ldpd.pid --socket $SNAP_DATA/zsock --ctl_socket $SNAP_DATA --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + nhrpd-debug: + command: sbin/nhrpd -f $SNAP_DATA/nhrpd.conf --pid_file $SNAP_DATA/nhrpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + babeld-debug: + command: sbin/babeld -f $SNAP_DATA/babeld.conf --pid_file $SNAP_DATA/babeld.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + eigrpd-debug: + command: sbin/eigrpd -f $SNAP_DATA/eigrpd.conf --pid_file $SNAP_DATA/eigrpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + pbrd-debug: + command: sbin/pbrd -f $SNAP_DATA/pbrd.conf --pid_file $SNAP_DATA/pbrd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + staticd-debug: + command: sbin/staticd -f $SNAP_DATA/staticd.conf --pid_file $SNAP_DATA/staticd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + bfdd-debug: + command: sbin/bfdd -f $SNAP_DATA/bfdd.conf --pid_file $SNAP_DATA/bfdd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA --bfdctl $SNAP_DATA/bfdd.sock + plugs: + - network + - network-bind + - network-control + fabricd-debug: + command: sbin/fabricd -f $SNAP_DATA/fabricd.conf --pid_file $SNAP_DATA/fabricd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + vrrpd-debug: + command: sbin/vrrpd -f $SNAP_DATA/vrrpd.conf --pid_file $SNAP_DATA/vrrpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + pathd-debug: + command: sbin/pathd -f $SNAP_DATA/pathd.conf --pid_file $SNAP_DATA/pathd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control + +parts: + rtrlib: + build-packages: + - cmake + - make + - gcc + - libssh-dev + stage-packages: + - libssh-4 + - zlib1g + prime: + - lib/librtr.so* + - usr/lib/$SNAPCRAFT_ARCH_TRIPLET/libssh.so* + source: https://github.com/rtrlib/rtrlib.git + source-type: git + source-tag: v0.8.0 + plugin: cmake + configflags: + - -DCMAKE_BUILD_TYPE=Release + libyang: + build-packages: + - cmake + - make + - gcc + - libpcre2-dev + stage-packages: + - libpcre2-8-0 + source: https://github.com/CESNET/libyang.git + source-type: git + source-tag: v2.1.128 + plugin: cmake + configflags: + - -DCMAKE_INSTALL_PREFIX:PATH=/usr + - -DENABLE_LYD_PRIV=ON + - -DENABLE_CACHE=ON + - -DCMAKE_BUILD_TYPE:String="Release" + frr: + after: [rtrlib,libyang] + build-packages: + - gcc + - autoconf + - automake + - libtool + - make + - gawk + - libreadline-dev + - libelf-dev + - texinfo + - libncurses5-dev + - texlive-latex-base + - texlive-generic-recommended + - libcap-dev + - imagemagick + - ghostscript + - groff + - libpcre3-dev + - chrpath + - pkg-config + - libjson-c-dev + - libc-ares-dev + - bison + - flex + - python3-dev + - libprotobuf-c-dev + - protobuf-c-compiler + - python3-sphinx + stage-packages: + - coreutils + - iproute2 + - logrotate + - libcap2 + - libtinfo5 + - libreadline7 + - libjson-c3 + - libc-ares2 + - libatm1 + - libprotobuf-c1 + - libdb5.3 + - libacl1 + - libattr1 + - libaudit1 + - libcap-ng0 + - libpam0g + - libpcre3 + - libselinux1 + - libxtables12 + plugin: autotools + source: ../frr-@PACKAGE_VERSION@.tar.gz + configflags: + - --enable-vtysh + - --enable-isisd + - --enable-watchfrr + - --enable-ospfclient=yes + - --enable-ospfapi=yes + - --enable-multipath=64 + - --enable-rtadv + - --enable-user=root + - --enable-group=root + - --enable-pimd + - --enable-pim6d + - --enable-ldpd + - --enable-fpm + - --enable-protobuf + - --enable-rpki + - --enable-vrrpd + - --enable-configfile-mask=0640 + - --enable-logfile-mask=0640 + - --sysconfdir=/etc + - --localstatedir=/var + - --sbindir=/sbin + - --bindir=/bin + - --with-pkg-extra-version=@PACKAGE_EXTRAVERSION@ + frr-defaults: + plugin: dump + source: defaults + organize: + zebra.conf.default: etc/frr/zebra.conf.default + bgpd.conf.default: etc/frr/bgpd.conf.default + isisd.conf.default: etc/frr/isisd.conf.default + ospf6d.conf.default: etc/frr/ospf6d.conf.default + ospfd.conf.default: etc/frr/ospfd.conf.default + pimd.conf.default: etc/frr/pimd.conf.default + pim6d.conf.default: etc/frr/pim6d.conf.default + ripd.conf.default: etc/frr/ripd.conf.default + ripngd.conf.default: etc/frr/ripngd.conf.default + ldpd.conf.default: etc/frr/ldpd.conf.default + nhrpd.conf.default: etc/frr/nhrpd.conf.default + babeld.conf.default: etc/frr/babeld.conf.default + eigrpd.conf.default: etc/frr/eigrpd.conf.default + pbrd.conf.default: etc/frr/pbrd.conf.default + bfdd.conf.default: etc/frr/bfdd.conf.default + fabricd.conf.default: etc/frr/fabricd.conf.default + vrrpd.conf.default: etc/frr/vrrpd.conf.default + pathd.conf.default: etc/frr/pathd.conf.default + vtysh.conf.default: etc/frr/vtysh.conf.default + staticd.conf.default: etc/frr/staticd.conf.default + frr-scripts: + plugin: make + source: scripts + helpers: + stage-packages: + - telnet + - traceroute + - libgcc1 + - libstdc++6 + plugin: make + source: helpers + prime: + - bin/telnet + - bin/traceroute + - bin/traceroute6 + docs: + plugin: dump + source: . + organize: + README.usage.md: doc/README.usage.md + README.snap_build.md: doc/README.snap_build.md + extra_version_info.txt: doc/extra_version_info.txt diff --git a/stamp-h.in b/stamp-h.in new file mode 100644 index 0000000..9788f70 --- /dev/null +++ b/stamp-h.in @@ -0,0 +1 @@ +timestamp diff --git a/staticd/.gitignore b/staticd/.gitignore new file mode 100644 index 0000000..af89530 --- /dev/null +++ b/staticd/.gitignore @@ -0,0 +1,2 @@ +libstatic.a +staticd diff --git a/staticd/Makefile b/staticd/Makefile new file mode 100644 index 0000000..ecd33df --- /dev/null +++ b/staticd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. staticd/staticd +%: ALWAYS + @$(MAKE) -s -C .. staticd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/staticd/static_bfd.c b/staticd/static_bfd.c new file mode 100644 index 0000000..c35751f --- /dev/null +++ b/staticd/static_bfd.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Static daemon BFD integration. + * + * Copyright (C) 2020-2022 Network Device Education Foundation, Inc. ("NetDEF") + * Rafael Zalamena + */ + +#include + +#include "lib/bfd.h" +#include "lib/printfrr.h" +#include "lib/srcdest_table.h" + +#include "staticd/static_routes.h" +#include "staticd/static_zebra.h" +#include "staticd/static_debug.h" + +#include "lib/openbsd-queue.h" + +/* + * Next hop BFD monitoring settings. + */ +static void static_next_hop_bfd_change(struct static_nexthop *sn, + const struct bfd_session_status *bss) +{ + switch (bss->state) { + case BSS_UNKNOWN: + /* FALLTHROUGH: no known state yet. */ + case BSS_ADMIN_DOWN: + /* NOTHING: we or the remote end administratively shutdown. */ + break; + case BSS_DOWN: + /* Peer went down, remove this next hop. */ + DEBUGD(&static_dbg_bfd, + "%s: next hop is down, remove it from RIB", __func__); + sn->path_down = true; + static_zebra_route_add(sn->pn, true); + break; + case BSS_UP: + /* Peer is back up, add this next hop. */ + DEBUGD(&static_dbg_bfd, "%s: next hop is up, add it to RIB", + __func__); + sn->path_down = false; + static_zebra_route_add(sn->pn, true); + break; + } +} + +static void static_next_hop_bfd_updatecb( + __attribute__((unused)) struct bfd_session_params *bsp, + const struct bfd_session_status *bss, void *arg) +{ + static_next_hop_bfd_change(arg, bss); +} + +static inline int +static_next_hop_type_to_family(const struct static_nexthop *sn) +{ + switch (sn->type) { + case STATIC_IPV4_GATEWAY_IFNAME: + case STATIC_IPV6_GATEWAY_IFNAME: + case STATIC_IPV4_GATEWAY: + case STATIC_IPV6_GATEWAY: + if (sn->type == STATIC_IPV4_GATEWAY || + sn->type == STATIC_IPV4_GATEWAY_IFNAME) + return AF_INET; + else + return AF_INET6; + break; + case STATIC_IFNAME: + case STATIC_BLACKHOLE: + default: + zlog_err("%s: invalid next hop type", __func__); + break; + } + + return AF_UNSPEC; +} + +void static_next_hop_bfd_monitor_enable(struct static_nexthop *sn, + const struct lyd_node *dnode) +{ + bool use_interface; + bool use_profile; + bool use_source; + bool onlink; + bool mhop; + int family; + struct ipaddr source; + struct vrf *vrf = NULL; + + use_interface = false; + use_source = yang_dnode_exists(dnode, "source"); + use_profile = yang_dnode_exists(dnode, "profile"); + onlink = yang_dnode_exists(dnode, "../onlink") && + yang_dnode_get_bool(dnode, "../onlink"); + mhop = yang_dnode_get_bool(dnode, "multi-hop"); + vrf = vrf_lookup_by_name(yang_dnode_get_string(dnode, "../vrf")); + + family = static_next_hop_type_to_family(sn); + if (family == AF_UNSPEC) + return; + + if (sn->type == STATIC_IPV4_GATEWAY_IFNAME || + sn->type == STATIC_IPV6_GATEWAY_IFNAME) + use_interface = true; + + /* Reconfigure or allocate new memory. */ + if (sn->bsp == NULL) + sn->bsp = bfd_sess_new(static_next_hop_bfd_updatecb, sn); + + /* Configure the session. */ + if (use_source) + yang_dnode_get_ip(&source, dnode, "source"); + + if (onlink || mhop == false) + bfd_sess_set_auto_source(sn->bsp, false); + else + bfd_sess_set_auto_source(sn->bsp, !use_source); + + /* Configure the session.*/ + if (family == AF_INET) + bfd_sess_set_ipv4_addrs(sn->bsp, + use_source ? &source.ip._v4_addr : NULL, + &sn->addr.ipv4); + else if (family == AF_INET6) + bfd_sess_set_ipv6_addrs(sn->bsp, + use_source ? &source.ip._v6_addr : NULL, + &sn->addr.ipv6); + + bfd_sess_set_interface(sn->bsp, use_interface ? sn->ifname : NULL); + + bfd_sess_set_profile(sn->bsp, use_profile ? yang_dnode_get_string( + dnode, "./profile") + : NULL); + if (vrf && vrf->vrf_id != VRF_UNKNOWN) + bfd_sess_set_vrf(sn->bsp, vrf->vrf_id); + + bfd_sess_set_hop_count(sn->bsp, (onlink || mhop == false) ? 1 : 254); + + /* Install or update the session. */ + bfd_sess_install(sn->bsp); + + /* Update current path status. */ + sn->path_down = (bfd_sess_status(sn->bsp) != BSS_UP); +} + +void static_next_hop_bfd_monitor_disable(struct static_nexthop *sn) +{ + bfd_sess_free(&sn->bsp); + + /* Reset path status. */ + sn->path_down = false; +} + +void static_next_hop_bfd_source(struct static_nexthop *sn, + const struct ipaddr *source) +{ + int family; + + if (sn->bsp == NULL) + return; + + family = static_next_hop_type_to_family(sn); + if (family == AF_UNSPEC) + return; + + bfd_sess_set_auto_source(sn->bsp, false); + if (family == AF_INET) + bfd_sess_set_ipv4_addrs(sn->bsp, &source->ip._v4_addr, + &sn->addr.ipv4); + else if (family == AF_INET6) + bfd_sess_set_ipv6_addrs(sn->bsp, &source->ip._v6_addr, + &sn->addr.ipv6); + + bfd_sess_install(sn->bsp); +} + +void static_next_hop_bfd_auto_source(struct static_nexthop *sn) +{ + if (sn->bsp == NULL) + return; + + bfd_sess_set_auto_source(sn->bsp, true); + bfd_sess_install(sn->bsp); +} + +void static_next_hop_bfd_multi_hop(struct static_nexthop *sn, bool mhop) +{ + if (sn->bsp == NULL) + return; + + bfd_sess_set_hop_count(sn->bsp, mhop ? 254 : 1); + bfd_sess_install(sn->bsp); +} + +void static_next_hop_bfd_profile(struct static_nexthop *sn, const char *name) +{ + if (sn->bsp == NULL) + return; + + bfd_sess_set_profile(sn->bsp, name); + bfd_sess_install(sn->bsp); +} + +void static_bfd_initialize(struct zclient *zc, struct event_loop *tm) +{ + /* Initialize BFD integration library. */ + bfd_protocol_integration_init(zc, tm); +} + +/* + * Display functions + */ +static void static_bfd_show_nexthop_json(struct vty *vty, + struct json_object *jo, + const struct static_nexthop *sn) +{ + const struct prefix *dst_p, *src_p; + struct json_object *jo_nh; + + jo_nh = json_object_new_object(); + + srcdest_rnode_prefixes(sn->rn, &dst_p, &src_p); + if (src_p) + json_object_string_addf(jo_nh, "from", "%pFX", src_p); + + json_object_string_addf(jo_nh, "prefix", "%pFX", dst_p); + json_object_string_add(jo_nh, "vrf", sn->nh_vrfname); + + json_object_boolean_add(jo_nh, "installed", !sn->path_down); + + json_object_array_add(jo, jo_nh); +} + +static void static_bfd_show_path_json(struct vty *vty, struct json_object *jo, + struct route_table *rt) +{ + struct route_node *rn; + + for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) { + struct static_route_info *si = static_route_info_from_rnode(rn); + struct static_path *sp; + + if (si == NULL) + continue; + + frr_each (static_path_list, &si->path_list, sp) { + struct static_nexthop *sn; + + frr_each (static_nexthop_list, &sp->nexthop_list, sn) { + /* Skip non configured BFD sessions. */ + if (sn->bsp == NULL) + continue; + + static_bfd_show_nexthop_json(vty, jo, sn); + } + } + } +} + +static void static_bfd_show_json(struct vty *vty) +{ + struct json_object *jo, *jo_path, *jo_afi_safi; + struct static_vrf *svrf; + + jo = json_object_new_object(); + jo_path = json_object_new_object(); + + json_object_object_add(jo, "path-list", jo_path); + RB_FOREACH (svrf, svrf_name_head, &svrfs) { + struct route_table *rt; + + jo_afi_safi = json_object_new_array(); + json_object_object_add(jo_path, "ipv4-unicast", jo_afi_safi); + rt = svrf->stable[AFI_IP][SAFI_UNICAST]; + if (rt) + static_bfd_show_path_json(vty, jo_afi_safi, rt); + + jo_afi_safi = json_object_new_array(); + json_object_object_add(jo_path, "ipv4-multicast", jo_afi_safi); + rt = svrf->stable[AFI_IP][SAFI_MULTICAST]; + if (rt) + static_bfd_show_path_json(vty, jo_afi_safi, rt); + + jo_afi_safi = json_object_new_array(); + json_object_object_add(jo_path, "ipv6-unicast", jo_afi_safi); + rt = svrf->stable[AFI_IP6][SAFI_UNICAST]; + if (rt) + static_bfd_show_path_json(vty, jo_afi_safi, rt); + } + + vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); + json_object_free(jo); +} + +static void static_bfd_show_nexthop(struct vty *vty, + const struct static_nexthop *sn) +{ + vty_out(vty, " %pRN", sn->rn); + + if (sn->bsp == NULL) { + vty_out(vty, "\n"); + return; + } + + if (sn->type == STATIC_IPV4_GATEWAY || + sn->type == STATIC_IPV4_GATEWAY_IFNAME) + vty_out(vty, " peer %pI4", &sn->addr.ipv4); + else if (sn->type == STATIC_IPV6_GATEWAY || + sn->type == STATIC_IPV6_GATEWAY_IFNAME) + vty_out(vty, " peer %pI6", &sn->addr.ipv6); + else + vty_out(vty, " peer unknown"); + + vty_out(vty, " (status: %s)\n", + sn->path_down ? "uninstalled" : "installed"); +} + +static void static_bfd_show_path(struct vty *vty, struct route_table *rt) +{ + struct route_node *rn; + + for (rn = route_top(rt); rn; rn = srcdest_route_next(rn)) { + struct static_route_info *si = static_route_info_from_rnode(rn); + struct static_path *sp; + + if (si == NULL) + continue; + + frr_each (static_path_list, &si->path_list, sp) { + struct static_nexthop *sn; + + frr_each (static_nexthop_list, &sp->nexthop_list, sn) { + /* Skip non configured BFD sessions. */ + if (sn->bsp == NULL) + continue; + + static_bfd_show_nexthop(vty, sn); + } + } + } +} + +void static_bfd_show(struct vty *vty, bool json) +{ + struct static_vrf *svrf; + + if (json) { + static_bfd_show_json(vty); + return; + } + + vty_out(vty, "Showing BFD monitored static routes:\n"); + vty_out(vty, "\n Next hops:\n"); + RB_FOREACH (svrf, svrf_name_head, &svrfs) { + struct route_table *rt; + + vty_out(vty, " VRF %s IPv4 Unicast:\n", svrf->name); + rt = svrf->stable[AFI_IP][SAFI_UNICAST]; + if (rt) + static_bfd_show_path(vty, rt); + + vty_out(vty, "\n VRF %s IPv4 Multicast:\n", svrf->name); + rt = svrf->stable[AFI_IP][SAFI_MULTICAST]; + if (rt) + static_bfd_show_path(vty, rt); + + vty_out(vty, "\n VRF %s IPv6 Unicast:\n", svrf->name); + rt = svrf->stable[AFI_IP6][SAFI_UNICAST]; + if (rt) + static_bfd_show_path(vty, rt); + } + + vty_out(vty, "\n"); +} diff --git a/staticd/static_debug.c b/staticd/static_debug.c new file mode 100644 index 0000000..a65752c --- /dev/null +++ b/staticd/static_debug.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Staticd debug related functions + * Copyright (C) 2019 Volta Networks Inc. + * Mark Stapp + */ + +#include + +#include "lib/command.h" +#include "lib/debug.h" +#include "lib/bfd.h" + +#include "static_debug.h" + +/* + * Debug infra: a debug struct for each category, and a corresponding + * string. + */ + +/* clang-format off */ +struct debug static_dbg_events = {0, "Staticd events"}; +struct debug static_dbg_route = {0, "Staticd route"}; +struct debug static_dbg_bfd = {0, "Staticd bfd"}; + +struct debug *static_debug_arr[] = { + &static_dbg_events, + &static_dbg_route, + &static_dbg_bfd +}; + +const char *static_debugs_conflines[] = { + "debug static events", + "debug static route", + "debug static bfd" +}; +/* clang-format on */ + + +/* + * Set or unset all staticd debugs + * + * flags + * The flags to set + * + * set + * Whether to set or unset the specified flags + */ +static void static_debug_set_all(uint32_t flags, bool set) +{ + for (unsigned int i = 0; i < array_size(static_debug_arr); i++) { + DEBUG_FLAGS_SET(static_debug_arr[i], flags, set); + + /* if all modes have been turned off, don't preserve options */ + if (!DEBUG_MODE_CHECK(static_debug_arr[i], DEBUG_MODE_ALL)) + DEBUG_CLEAR(static_debug_arr[i]); + } +} + +static int static_debug_config_write_helper(struct vty *vty, bool config) +{ + uint32_t mode = DEBUG_MODE_ALL; + + if (config) + mode = DEBUG_MODE_CONF; + + for (unsigned int i = 0; i < array_size(static_debug_arr); i++) + if (DEBUG_MODE_CHECK(static_debug_arr[i], mode)) + vty_out(vty, "%s\n", static_debugs_conflines[i]); + + return 0; +} + +int static_config_write_debug(struct vty *vty) +{ + return static_debug_config_write_helper(vty, true); +} + +int static_debug_status_write(struct vty *vty) +{ + return static_debug_config_write_helper(vty, false); +} + +/* + * Set debugging status. + * + * vtynode + * vty->node + * + * onoff + * Whether to turn the specified debugs on or off + * + * events + * Debug general internal events + * + */ +void static_debug_set(int vtynode, bool onoff, bool events, bool route, + bool bfd) +{ + uint32_t mode = DEBUG_NODE2MODE(vtynode); + + if (events) + DEBUG_MODE_SET(&static_dbg_events, mode, onoff); + if (route) + DEBUG_MODE_SET(&static_dbg_route, mode, onoff); + if (bfd) { + DEBUG_MODE_SET(&static_dbg_bfd, mode, onoff); + bfd_protocol_integration_set_debug(onoff); + } +} + +/* + * Debug lib initialization + */ + +struct debug_callbacks static_dbg_cbs = { + .debug_set_all = static_debug_set_all +}; + +void static_debug_init(void) +{ + debug_init(&static_dbg_cbs); +} diff --git a/staticd/static_debug.h b/staticd/static_debug.h new file mode 100644 index 0000000..c910068 --- /dev/null +++ b/staticd/static_debug.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Staticd debug related functions + * Copyright (C) 2019 Volta Networks Inc. + * Mark Stapp + */ + +#ifndef _STATIC_DEBUG_H +#define _STATIC_DEBUG_H + +#include + +#include "lib/debug.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* staticd debugging records */ +extern struct debug static_dbg_events; +extern struct debug static_dbg_route; +extern struct debug static_dbg_bfd; + +/* + * Initialize staticd debugging. + * + * Installs VTY commands and registers callbacks. + */ +void static_debug_init(void); + +/* + * Print staticd debugging configuration. + * + * vty + * VTY to print debugging configuration to. + */ +int static_config_write_debug(struct vty *vty); + +/* + * Print staticd debugging configuration, human readable form. + * + * vty + * VTY to print debugging configuration to. + */ +int static_debug_status_write(struct vty *vty); + +/* + * Set debugging status. + * + * vtynode + * vty->node + * + * onoff + * Whether to turn the specified debugs on or off + * + * events + * Debug general internal events + * + */ +void static_debug_set(int vtynode, bool onoff, bool events, bool route, + bool bfd); + +#ifdef __cplusplus +} +#endif + +#endif /* _STATIC_DEBUG_H */ diff --git a/staticd/static_main.c b/staticd/static_main.c new file mode 100644 index 0000000..9468a98 --- /dev/null +++ b/staticd/static_main.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - main code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "command.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "libfrr.h" +#include "vrf.h" +#include "nexthop.h" +#include "filter.h" +#include "routing_nb.h" + +#include "static_vrf.h" +#include "static_vty.h" +#include "static_routes.h" +#include "static_zebra.h" +#include "static_debug.h" +#include "static_nb.h" + +#include "mgmt_be_client.h" + +char backup_config_file[256]; + +bool mpls_enabled; + +zebra_capabilities_t _caps_p[] = { +}; + +struct zebra_privs_t static_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = { { 0 } }; + +/* Master of threads. */ +struct event_loop *master; + +static struct mgmt_be_client *mgmt_be_client; + +static struct frr_daemon_info staticd_di; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received and ignored"); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + /* Disable BFD events to avoid wasting processing. */ + bfd_protocol_integration_set_shutdown(true); + + mgmt_be_client_destroy(mgmt_be_client); + + static_vrf_terminate(); + + static_zebra_stop(); + frr_fini(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t static_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +static const struct frr_yang_module_info *const staticd_yang_modules[] = { + &frr_interface_info, + &frr_vrf_info, + &frr_routing_info, + &frr_staticd_info, +}; + +/* + * NOTE: .flags == FRR_NO_SPLIT_CONFIG to avoid reading split config, mgmtd will + * do this for us now + */ +/* clang-format off */ +FRR_DAEMON_INFO(staticd, STATIC, + .vty_port = STATIC_VTY_PORT, + .proghelp = "Implementation of STATIC.", + + .signals = static_signals, + .n_signals = array_size(static_signals), + + .privs = &static_privs, + + .yang_modules = staticd_yang_modules, + .n_yang_modules = array_size(staticd_yang_modules), + + .flags = FRR_NO_SPLIT_CONFIG, +); +/* clang-format on */ + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&staticd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + static_debug_init(); + static_vrf_init(); + + static_zebra_init(); + static_vty_init(); + + /* Initialize MGMT backend functionalities */ + mgmt_be_client = mgmt_be_client_create("staticd", NULL, 0, master); + + hook_register(routing_conf_event, + routing_control_plane_protocols_name_validate); + hook_register(routing_create, + routing_control_plane_protocols_staticd_create); + hook_register(routing_destroy, + routing_control_plane_protocols_staticd_destroy); + + /* + * We set FRR_NO_SPLIT_CONFIG flag to avoid reading our config, but we + * still need to write one if vtysh tells us to. Setting the host + * config filename does this. + */ + host_config_set(config_default); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/staticd/static_nb.c b/staticd/static_nb.c new file mode 100644 index 0000000..e6aa71a --- /dev/null +++ b/staticd/static_nb.c @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Vmware + * Vishal Dhingra + */ +#include + +#include "northbound.h" +#include "libfrr.h" +#include "static_nb.h" +#include "static_vty.h" + +/* clang-format off */ + +const struct frr_yang_module_info frr_staticd_info = { + .name = "frr-staticd", + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/tag", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_tag_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop", + .cbs = { + .apply_finish = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_apply_finish, + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_destroy, + .pre_validate = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_pre_validate, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bh-type", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_bh_type_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/onlink", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srte-color", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy, + + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy, + + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/label", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/ttl", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/traffic-class", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring", + .cbs = { + .create = route_next_hop_bfd_create, + .destroy = route_next_hop_bfd_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/source", + .cbs = { + .modify = route_next_hop_bfd_source_modify, + .destroy = route_next_hop_bfd_source_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/multi-hop", + .cbs = { + .modify = route_next_hop_bfd_multi_hop_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/profile", + .cbs = { + .modify = route_next_hop_bfd_profile_modify, + .destroy = route_next_hop_bfd_profile_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/tag", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_tag_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop", + .cbs = { + .apply_finish = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_apply_finish, + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_destroy, + .pre_validate = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_pre_validate, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/bh-type", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_bh_type_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/onlink", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srte-color", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/label", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/ttl", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/traffic-class", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy, + } + }, + { + .xpath = NULL, + }, + } +}; diff --git a/staticd/static_nb.h b/staticd/static_nb.h new file mode 100644 index 0000000..be75d9d --- /dev/null +++ b/staticd/static_nb.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Vmware + * Vishal Dhingra + */ +#ifndef _FRR_STATIC_NB_H_ +#define _FRR_STATIC_NB_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct frr_yang_module_info frr_staticd_info; +extern const struct frr_yang_module_info frr_staticd_cli_info; + +int routing_control_plane_protocols_staticd_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_staticd_destroy( + struct nb_cb_destroy_args *args); + +/* Mandatory callbacks. */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_tag_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_bh_type_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy( + struct nb_cb_destroy_args *args); +int route_next_hop_bfd_create(struct nb_cb_create_args *args); +int route_next_hop_bfd_destroy(struct nb_cb_destroy_args *args); +int route_next_hop_bfd_source_modify(struct nb_cb_modify_args *args); +int route_next_hop_bfd_source_destroy(struct nb_cb_destroy_args *args); +int route_next_hop_bfd_profile_modify(struct nb_cb_modify_args *args); +int route_next_hop_bfd_profile_destroy(struct nb_cb_destroy_args *args); +int route_next_hop_bfd_multi_hop_modify(struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_tag_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_bh_type_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy( + struct nb_cb_destroy_args *args); + +/* Optional 'apply_finish' callbacks. */ + +void routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_apply_finish( + struct nb_cb_apply_finish_args *args); +void routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_apply_finish( + struct nb_cb_apply_finish_args *args); + +/* Optional 'pre_validate' callbacks. */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_pre_validate( + struct nb_cb_pre_validate_args *args); + +/* + * Callback registered with routing_nb lib to validate only + * one instance of staticd is allowed + */ +int routing_control_plane_protocols_name_validate( + struct nb_cb_create_args *args); + +/* xpath macros */ +/* route-list */ +#define FRR_STATIC_ROUTE_INFO_KEY_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-staticd:staticd/route-list[prefix='%s'][afi-safi='%s']/" \ + "path-list[table-id='%u'][distance='%u']" + +#define FRR_STATIC_ROUTE_INFO_KEY_NO_DISTANCE_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-staticd:staticd/route-list[prefix='%s'][afi-safi='%s']/" \ + "path-list[table-id='%u']" + + +#define FRR_STATIC_ROUTE_PATH_TAG_XPATH "/tag" + +/* route-list/frr-nexthops */ +#define FRR_STATIC_ROUTE_NH_KEY_XPATH \ + "/frr-nexthops/" \ + "nexthop[nh-type='%s'][vrf='%s'][gateway='%s'][interface='%s']" + +#define FRR_STATIC_ROUTE_NH_ONLINK_XPATH "/onlink" + +#define FRR_STATIC_ROUTE_NH_COLOR_XPATH "/srte-color" + +#define FRR_STATIC_ROUTE_NH_BH_XPATH "/bh-type" + +#define FRR_STATIC_ROUTE_NH_LABEL_XPATH "/mpls-label-stack" + +#define FRR_STATIC_ROUTE_NHLB_KEY_XPATH "/entry[id='%u']/label" + +#define FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH "/srv6-segs-stack" + +#define FRR_STATIC_ROUTE_NH_SRV6_KEY_SEG_XPATH "/entry[id='%u']/seg" + +/* route-list/srclist */ +#define FRR_S_ROUTE_SRC_INFO_KEY_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-staticd:staticd/route-list[prefix='%s'][afi-safi='%s']/" \ + "src-list[src-prefix='%s']/path-list[table-id='%u'][distance='%u']" + +#define FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH \ + "/frr-routing:routing/control-plane-protocols/" \ + "control-plane-protocol[type='%s'][name='%s'][vrf='%s']/" \ + "frr-staticd:staticd/route-list[prefix='%s'][afi-safi='%s']/" \ + "src-list[src-prefix='%s']/path-list[table-id='%u']" + +/* route-list/frr-nexthops */ +#define FRR_DEL_S_ROUTE_NH_KEY_XPATH \ + FRR_STATIC_ROUTE_INFO_KEY_XPATH \ + FRR_STATIC_ROUTE_NH_KEY_XPATH + +/* route-list/frr-nexthops */ +#define FRR_DEL_S_ROUTE_NH_KEY_NO_DISTANCE_XPATH \ + FRR_STATIC_ROUTE_INFO_KEY_NO_DISTANCE_XPATH \ + FRR_STATIC_ROUTE_NH_KEY_XPATH + +/* route-list/src/src-list/frr-nexthops*/ +#define FRR_DEL_S_ROUTE_SRC_NH_KEY_XPATH \ + FRR_S_ROUTE_SRC_INFO_KEY_XPATH \ + FRR_STATIC_ROUTE_NH_KEY_XPATH + +/* route-list/src/src-list/frr-nexthops*/ +#define FRR_DEL_S_ROUTE_SRC_NH_KEY_NO_DISTANCE_XPATH \ + FRR_S_ROUTE_SRC_INFO_KEY_NO_DISTANCE_XPATH \ + FRR_STATIC_ROUTE_NH_KEY_XPATH + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/static_nb_config.c b/staticd/static_nb_config.c new file mode 100644 index 0000000..7de5f04 --- /dev/null +++ b/staticd/static_nb_config.c @@ -0,0 +1,1369 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 Vmware + * Vishal Dhingra + */ +#include + +#include "northbound.h" +#include "libfrr.h" +#include "log.h" +#include "lib_errors.h" +#include "prefix.h" +#include "table.h" +#include "vrf.h" +#include "nexthop.h" +#include "srcdest_table.h" + +#include "static_vrf.h" +#include "static_routes.h" +#include "static_nb.h" +#include "static_zebra.h" + + +static int static_path_list_create(struct nb_cb_create_args *args) +{ + struct route_node *rn; + struct static_path *pn; + const struct lyd_node *vrf_dnode; + const char *vrf; + uint8_t distance; + uint32_t table_id; + + switch (args->event) { + case NB_EV_VALIDATE: + vrf_dnode = yang_dnode_get_parent(args->dnode, + "control-plane-protocol"); + vrf = yang_dnode_get_string(vrf_dnode, "vrf"); + table_id = yang_dnode_get_uint32(args->dnode, "table-id"); + + /* + * TableId is not applicable for VRF. Consider the case of + * l3mdev, there is one uint32_t space to work with. + * A l3mdev device points at a specific table that it + * relates to and a set of interfaces it belongs to. + */ + if (table_id && (strcmp(vrf, vrf_get_default_name()) != 0) + && !vrf_is_backend_netns()) { + snprintf( + args->errmsg, args->errmsg_len, + "%% table param only available when running on netns-based vrfs"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + rn = nb_running_get_entry(args->dnode, NULL, true); + distance = yang_dnode_get_uint8(args->dnode, "distance"); + table_id = yang_dnode_get_uint32(args->dnode, "table-id"); + pn = static_add_path(rn, table_id, distance); + nb_running_set_entry(args->dnode, pn); + } + + return NB_OK; +} + +static int static_path_list_destroy(struct nb_cb_destroy_args *args) +{ + struct static_path *pn; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + pn = nb_running_unset_entry(args->dnode); + static_del_path(pn); + break; + } + + return NB_OK; +} + +static int static_path_list_tag_modify(struct nb_cb_modify_args *args) +{ + struct static_path *pn; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + pn = nb_running_get_entry(args->dnode, NULL, true); + pn->tag = yang_dnode_get_uint32(args->dnode, NULL); + static_install_path(pn); + break; + } + + return NB_OK; +} + +struct nexthop_iter { + uint32_t count; + bool blackhole; +}; + +static int nexthop_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct nexthop_iter *iter = arg; + enum static_nh_type nh_type; + + nh_type = yang_dnode_get_enum(dnode, "nh-type"); + + if (nh_type == STATIC_BLACKHOLE) + iter->blackhole = true; + + iter->count++; + + return YANG_ITER_CONTINUE; +} + +static bool static_nexthop_create(struct nb_cb_create_args *args) +{ + const struct lyd_node *pn_dnode; + struct nexthop_iter iter; + struct static_path *pn; + struct ipaddr ipaddr; + struct static_nexthop *nh; + enum static_nh_type nh_type; + const char *ifname; + const char *nh_vrf; + + switch (args->event) { + case NB_EV_VALIDATE: + ifname = yang_dnode_get_string(args->dnode, "interface"); + if (ifname != NULL) { + if (strcasecmp(ifname, "Null0") == 0 + || strcasecmp(ifname, "reject") == 0 + || strcasecmp(ifname, "blackhole") == 0) { + snprintf(args->errmsg, args->errmsg_len, + "%s: Nexthop interface name can not be from reserved keywords(Null0, reject, blackhole)", + ifname); + return NB_ERR_VALIDATION; + } + } + + iter.count = 0; + iter.blackhole = false; + + pn_dnode = yang_dnode_get_parent(args->dnode, "path-list"); + yang_dnode_iterate(nexthop_iter_cb, &iter, pn_dnode, + "./frr-nexthops/nexthop"); + + if (iter.blackhole && iter.count > 1) { + snprintf( + args->errmsg, args->errmsg_len, + "Route cannot have blackhole and non-blackhole nexthops simultaneously"); + return NB_ERR_VALIDATION; + } else if (iter.count > zebra_ecmp_count) { + snprintf(args->errmsg, args->errmsg_len, + "Route cannot have more than %d ECMP nexthops", + zebra_ecmp_count); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + yang_dnode_get_ip(&ipaddr, args->dnode, "gateway"); + nh_type = yang_dnode_get_enum(args->dnode, "nh-type"); + ifname = yang_dnode_get_string(args->dnode, "interface"); + nh_vrf = yang_dnode_get_string(args->dnode, "vrf"); + pn = nb_running_get_entry(args->dnode, NULL, true); + + if (strmatch(ifname, "(null)")) + ifname = ""; + + if (!static_add_nexthop_validate(nh_vrf, nh_type, &ipaddr)) + flog_warn( + EC_LIB_NB_CB_CONFIG_VALIDATE, + "Warning!! Local connected address is configured as Gateway IP((%s))", + yang_dnode_get_string(args->dnode, + "./gateway")); + nh = static_add_nexthop(pn, nh_type, &ipaddr, ifname, nh_vrf, + 0); + nb_running_set_entry(args->dnode, nh); + break; + } + + return NB_OK; +} + +static bool static_nexthop_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *nh; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_unset_entry(args->dnode); + static_delete_nexthop(nh); + break; + } + + return NB_OK; +} + +static int nexthop_srv6_segs_stack_entry_create(struct nb_cb_create_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + memset(&nh->snh_seg.seg[index], 0, sizeof(struct in6_addr)); + nh->snh_seg.num_segs++; + break; + } + + return NB_OK; +} + +static int nexthop_srv6_segs_stack_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + int old_num_segs; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + index = pos - 1; + old_num_segs = nh->snh_seg.num_segs; + memset(&nh->snh_seg.seg[index], 0, sizeof(struct in6_addr)); + nh->snh_seg.num_segs--; + + if (old_num_segs != nh->snh_seg.num_segs) + nh->state = STATIC_START; + break; + } + + return NB_OK; +} + +static int static_nexthop_srv6_segs_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + struct in6_addr old_seg; + struct in6_addr cli_seg; + + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(lyd_parent(args->dnode)); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + + old_seg = nh->snh_seg.seg[index]; + yang_dnode_get_ipv6(&cli_seg, args->dnode, NULL); + + memcpy(&nh->snh_seg.seg[index], &cli_seg, sizeof(struct in6_addr)); + + if (memcmp(&old_seg, &nh->snh_seg.seg[index], + sizeof(struct in6_addr)) != 0) + nh->state = STATIC_START; + + return NB_OK; +} + +static int nexthop_mpls_label_stack_entry_create(struct nb_cb_create_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + + switch (args->event) { + case NB_EV_VALIDATE: + if (!mpls_enabled) { + snprintf( + args->errmsg, args->errmsg_len, + "%% MPLS not turned on in kernel ignoring static route"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid label position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + nh->snh_label.label[index] = 0; + nh->snh_label.num_labels++; + break; + } + + return NB_OK; +} + +static int +nexthop_mpls_label_stack_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + uint old_num_labels; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid label position"); + return NB_ERR; + } + index = pos - 1; + old_num_labels = nh->snh_label.num_labels; + nh->snh_label.label[index] = 0; + nh->snh_label.num_labels--; + + if (old_num_labels != nh->snh_label.num_labels) + nh->state = STATIC_START; + break; + } + + return NB_OK; +} + +static int static_nexthop_mpls_label_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + mpls_label_t old_label; + + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(lyd_parent(args->dnode)); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid label position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + + old_label = nh->snh_label.label[index]; + nh->snh_label.label[index] = yang_dnode_get_uint32(args->dnode, NULL); + + if (old_label != nh->snh_label.label[index]) + nh->state = STATIC_START; + + return NB_OK; +} + +static int static_nexthop_onlink_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + enum static_nh_type nh_type; + bool old_onlink; + + switch (args->event) { + case NB_EV_VALIDATE: + nh_type = yang_dnode_get_enum(args->dnode, "../nh-type"); + if ((nh_type != STATIC_IPV4_GATEWAY_IFNAME) + && (nh_type != STATIC_IPV6_GATEWAY_IFNAME)) { + snprintf( + args->errmsg, args->errmsg_len, + "nexthop type is not the ipv4 or ipv6 interface type"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + old_onlink = nh->onlink; + nh->onlink = yang_dnode_get_bool(args->dnode, NULL); + + if (old_onlink != nh->onlink) + nh->state = STATIC_START; + break; + } + + return NB_OK; +} + +static int static_nexthop_color_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + uint32_t old_color; + + nh = nb_running_get_entry(args->dnode, NULL, true); + old_color = nh->color; + nh->color = yang_dnode_get_uint32(args->dnode, NULL); + + if (old_color != nh->color) + nh->state = STATIC_START; + + return NB_OK; +} + +static int static_nexthop_color_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *nh; + uint32_t old_color; + + nh = nb_running_get_entry(args->dnode, NULL, true); + old_color = nh->color; + nh->color = 0; + + if (old_color != nh->color) + nh->state = STATIC_START; + + return NB_OK; +} + +static int static_nexthop_bh_type_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + enum static_nh_type nh_type; + + switch (args->event) { + case NB_EV_VALIDATE: + nh_type = yang_dnode_get_enum(args->dnode, "../nh-type"); + if (nh_type != STATIC_BLACKHOLE) { + snprintf(args->errmsg, args->errmsg_len, + "nexthop type is not the blackhole type"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + nh->bh_type = yang_dnode_get_enum(args->dnode, NULL); + break; + } + + return NB_OK; +} + +void routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct static_nexthop *nh; + + nh = nb_running_get_entry(args->dnode, NULL, true); + + static_install_nexthop(nh); +} + +void routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_apply_finish( + struct nb_cb_apply_finish_args *args) +{ + struct static_nexthop *nh; + + nh = nb_running_get_entry(args->dnode, NULL, true); + + static_install_nexthop(nh); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_pre_validate( + struct nb_cb_pre_validate_args *args) +{ + const struct lyd_node *mls_dnode; + uint32_t count; + + mls_dnode = yang_dnode_get(args->dnode, "mpls-label-stack"); + count = yang_get_list_elements_count(lyd_child(mls_dnode)); + + if (count > MPLS_MAX_LABELS) { + snprintf(args->errmsg, args->errmsg_len, + "Too many labels, Enter %d or fewer", + MPLS_MAX_LABELS); + return NB_ERR_VALIDATION; + } + return NB_OK; +} + +int routing_control_plane_protocols_name_validate( + struct nb_cb_create_args *args) +{ + const char *name; + + name = yang_dnode_get_string(args->dnode, "name"); + if (!strmatch(name, "staticd")) { + snprintf(args->errmsg, args->errmsg_len, + "static routing supports only one instance with name staticd"); + return NB_ERR_VALIDATION; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol + */ +int routing_control_plane_protocols_staticd_create(struct nb_cb_create_args *args) +{ + struct static_vrf *svrf; + const char *vrf; + + vrf = yang_dnode_get_string(args->dnode, "vrf"); + svrf = static_vrf_alloc(vrf); + nb_running_set_entry(args->dnode, svrf); + + return NB_OK; +} + +int routing_control_plane_protocols_staticd_destroy( + struct nb_cb_destroy_args *args) +{ + struct static_vrf *svrf; + struct route_table *stable; + struct route_node *rn; + afi_t afi; + safi_t safi; + + svrf = nb_running_unset_entry(args->dnode); + + FOREACH_AFI_SAFI (afi, safi) { + stable = svrf->stable[afi][safi]; + if (!stable) + continue; + + for (rn = route_top(stable); rn; rn = route_next(rn)) + static_del_route(rn); + } + + static_vrf_free(svrf); + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_create( + struct nb_cb_create_args *args) +{ + struct static_vrf *svrf; + struct route_node *rn; + const struct lyd_node *vrf_dnode; + struct prefix prefix; + const char *afi_safi; + afi_t prefix_afi; + afi_t afi; + safi_t safi; + + switch (args->event) { + case NB_EV_VALIDATE: + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + afi_safi = yang_dnode_get_string(args->dnode, "afi-safi"); + yang_afi_safi_identity2value(afi_safi, &afi, &safi); + prefix_afi = family2afi(prefix.family); + if (afi != prefix_afi) { + flog_warn( + EC_LIB_NB_CB_CONFIG_VALIDATE, + "route node %s creation failed", + yang_dnode_get_string(args->dnode, "prefix")); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf_dnode = yang_dnode_get_parent(args->dnode, + "control-plane-protocol"); + svrf = nb_running_get_entry(vrf_dnode, NULL, true); + + yang_dnode_get_prefix(&prefix, args->dnode, "prefix"); + afi_safi = yang_dnode_get_string(args->dnode, "afi-safi"); + yang_afi_safi_identity2value(afi_safi, &afi, &safi); + + rn = static_add_route(afi, safi, &prefix, NULL, svrf); + if (!svrf->vrf || svrf->vrf->vrf_id == VRF_UNKNOWN) + snprintf( + args->errmsg, args->errmsg_len, + "Static Route to %s not installed currently because dependent config not fully available", + yang_dnode_get_string(args->dnode, "prefix")); + nb_running_set_entry(args->dnode, rn); + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct route_node *rn; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + rn = nb_running_unset_entry(args->dnode); + static_del_route(rn); + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_create( + struct nb_cb_create_args *args) +{ + return static_path_list_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_destroy( + struct nb_cb_destroy_args *args) +{ + return static_path_list_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/tag + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_tag_modify( + struct nb_cb_modify_args *args) +{ + return static_path_list_tag_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_create( + struct nb_cb_create_args *args) +{ + return static_nexthop_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_destroy( + struct nb_cb_destroy_args *args) +{ + return static_nexthop_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bh-type + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_bh_type_modify( + struct nb_cb_modify_args *args) +{ + return static_nexthop_bh_type_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/onlink + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_onlink_modify( + struct nb_cb_modify_args *args) +{ + return static_nexthop_onlink_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srte-color + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_color_modify(args) != NB_OK) + return NB_ERR; + + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_color_destroy(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_srv6_segs_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_srv6_segs_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_srv6_segs_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_srv6_segs_stack_entry_destroy() will take care + * to reset the seg vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_mpls_label_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_mpls_label_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/label + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_mpls_label_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_mpls_label_stack_entry_destroy() will take care + * to reset the label vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/ttl + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/traffic-class + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring + */ +int route_next_hop_bfd_create(struct nb_cb_create_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_monitor_enable(sn, args->dnode); + return NB_OK; +} + +int route_next_hop_bfd_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_monitor_disable(sn); + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/source + */ +int route_next_hop_bfd_source_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *sn; + struct ipaddr source; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + yang_dnode_get_ip(&source, args->dnode, NULL); + static_next_hop_bfd_source(sn, &source); + return NB_OK; +} + +int route_next_hop_bfd_source_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_auto_source(sn); + + /* NHT information are needed by BFD to automatically find the source + * + * Force zebra to resend the information to BFD by unregistering and + * registering again NHT. The (...)/frr-nexthops/nexthop northbound + * apply_finish function will trigger a call to static_install_nexthop() + * that does a call to static_zebra_nht_register(nh, true); + * static_zebra_nht_register(sn, false); + */ + static_zebra_nht_register(sn, false); + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/multi-hop + */ +int route_next_hop_bfd_multi_hop_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_multi_hop(sn, + yang_dnode_get_bool(args->dnode, NULL)); + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/bfd-monitoring/profile + */ +int route_next_hop_bfd_profile_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_profile(sn, + yang_dnode_get_string(args->dnode, NULL)); + + return NB_OK; +} + +int route_next_hop_bfd_profile_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *sn; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sn = nb_running_get_entry(args->dnode, NULL, true); + static_next_hop_bfd_profile(sn, NULL); + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_create( + struct nb_cb_create_args *args) +{ + struct static_vrf *s_vrf; + struct route_node *rn; + struct route_node *src_rn; + struct prefix_ipv6 src_prefix = {}; + struct stable_info *info; + afi_t afi; + safi_t safi = SAFI_UNICAST; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + rn = nb_running_get_entry(args->dnode, NULL, true); + info = route_table_get_info(rn->table); + s_vrf = info->svrf; + yang_dnode_get_ipv6p(&src_prefix, args->dnode, "src-prefix"); + afi = family2afi(src_prefix.family); + src_rn = + static_add_route(afi, safi, &rn->p, &src_prefix, s_vrf); + nb_running_set_entry(args->dnode, src_rn); + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_destroy( + struct nb_cb_destroy_args *args) +{ + struct route_node *src_rn; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + src_rn = nb_running_unset_entry(args->dnode); + static_del_route(src_rn); + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_create( + struct nb_cb_create_args *args) +{ + return static_path_list_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_destroy( + struct nb_cb_destroy_args *args) +{ + return static_path_list_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/tag + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_tag_modify( + struct nb_cb_modify_args *args) +{ + return static_path_list_tag_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_create( + struct nb_cb_create_args *args) +{ + return static_nexthop_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_destroy( + struct nb_cb_destroy_args *args) +{ + return static_nexthop_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/bh-type + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_bh_type_modify( + struct nb_cb_modify_args *args) +{ + return static_nexthop_bh_type_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/onlink + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_onlink_modify( + struct nb_cb_modify_args *args) +{ + return static_nexthop_onlink_modify(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srte-color + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_color_modify(args) != NB_OK) + return NB_ERR; + + break; + } + return NB_OK; +} + + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_color_destroy(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_srv6_segs_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_srv6_segs_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_srv6_segs_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_mpls_seg_stack_entry_destroy() will take care + * to reset the seg vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_mpls_label_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_mpls_label_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/label + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_mpls_label_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_label_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_mpls_label_stack_entry_destroy() will take care + * to reset the label vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/ttl + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_ttl_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry/traffic-class + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_traffic_class_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + + return NB_OK; +} diff --git a/staticd/static_nht.c b/staticd/static_nht.c new file mode 100644 index 0000000..6be5984 --- /dev/null +++ b/staticd/static_nht.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Static NHT code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "prefix.h" +#include "table.h" +#include "vrf.h" +#include "nexthop.h" +#include "srcdest_table.h" + +#include "static_vrf.h" +#include "static_routes.h" +#include "static_zebra.h" +#include "static_nht.h" + +static void static_nht_update_path(struct static_path *pn, struct prefix *nhp, + uint32_t nh_num, vrf_id_t nh_vrf_id) +{ + struct static_nexthop *nh; + + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + if (nh->nh_vrf_id != nh_vrf_id) + continue; + + if (nh->type != STATIC_IPV4_GATEWAY + && nh->type != STATIC_IPV4_GATEWAY_IFNAME + && nh->type != STATIC_IPV6_GATEWAY + && nh->type != STATIC_IPV6_GATEWAY_IFNAME) + continue; + + if (nhp->family == AF_INET + && nhp->u.prefix4.s_addr == nh->addr.ipv4.s_addr) + nh->nh_valid = !!nh_num; + + if (nhp->family == AF_INET6 + && memcmp(&nhp->u.prefix6, &nh->addr.ipv6, IPV6_MAX_BYTELEN) + == 0) + nh->nh_valid = !!nh_num; + + if (nh->state == STATIC_START) + static_zebra_route_add(pn, true); + } +} + +static void static_nht_update_safi(struct prefix *sp, struct prefix *nhp, + uint32_t nh_num, afi_t afi, safi_t safi, + struct static_vrf *svrf, vrf_id_t nh_vrf_id) +{ + struct route_table *stable; + struct route_node *rn; + struct static_path *pn; + struct static_route_info *si; + + stable = static_vrf_static_table(afi, safi, svrf); + if (!stable) + return; + + if (sp) { + rn = srcdest_rnode_lookup(stable, sp, NULL); + if (rn && rn->info) { + si = static_route_info_from_rnode(rn); + frr_each(static_path_list, &si->path_list, pn) { + static_nht_update_path(pn, nhp, nh_num, + nh_vrf_id); + } + route_unlock_node(rn); + } + return; + } + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) { + static_nht_update_path(pn, nhp, nh_num, nh_vrf_id); + } + } +} + +void static_nht_update(struct prefix *sp, struct prefix *nhp, uint32_t nh_num, + afi_t afi, safi_t safi, vrf_id_t nh_vrf_id) +{ + struct static_vrf *svrf; + + RB_FOREACH (svrf, svrf_name_head, &svrfs) + static_nht_update_safi(sp, nhp, nh_num, afi, safi, svrf, + nh_vrf_id); +} + +static void static_nht_reset_start_safi(struct prefix *nhp, afi_t afi, + safi_t safi, struct static_vrf *svrf, + vrf_id_t nh_vrf_id) +{ + struct route_table *stable; + struct static_nexthop *nh; + struct static_path *pn; + struct route_node *rn; + struct static_route_info *si; + + stable = static_vrf_static_table(afi, safi, svrf); + if (!stable) + return; + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) { + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + if (nh->nh_vrf_id != nh_vrf_id) + continue; + + if (nhp->family == AF_INET + && nhp->u.prefix4.s_addr + != nh->addr.ipv4.s_addr) + continue; + + if (nhp->family == AF_INET6 + && memcmp(&nhp->u.prefix6, &nh->addr.ipv6, + 16) + != 0) + continue; + + /* + * We've been told that a nexthop we + * depend on has changed in some manner, + * so reset the state machine to allow + * us to start over. + */ + nh->state = STATIC_START; + } + } + } +} + +void static_nht_reset_start(struct prefix *nhp, afi_t afi, safi_t safi, + vrf_id_t nh_vrf_id) +{ + struct static_vrf *svrf; + + RB_FOREACH (svrf, svrf_name_head, &svrfs) + static_nht_reset_start_safi(nhp, afi, safi, svrf, nh_vrf_id); +} + +static void static_nht_mark_state_safi(struct prefix *sp, afi_t afi, + safi_t safi, struct vrf *vrf, + enum static_install_states state) +{ + struct static_vrf *svrf; + struct route_table *stable; + struct route_node *rn; + struct static_nexthop *nh; + struct static_path *pn; + struct static_route_info *si; + + svrf = vrf->info; + if (!svrf) + return; + + stable = static_vrf_static_table(afi, safi, svrf); + if (!stable) + return; + + rn = srcdest_rnode_lookup(stable, sp, NULL); + if (!rn) + return; + si = rn->info; + if (si) { + frr_each(static_path_list, &si->path_list, pn) { + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + nh->state = state; + } + } + } + + route_unlock_node(rn); +} + +void static_nht_mark_state(struct prefix *sp, safi_t safi, vrf_id_t vrf_id, + enum static_install_states state) +{ + struct vrf *vrf; + + afi_t afi = AFI_IP; + + if (sp->family == AF_INET6) + afi = AFI_IP6; + + vrf = vrf_lookup_by_id(vrf_id); + if (!vrf || !vrf->info) + return; + + static_nht_mark_state_safi(sp, afi, safi, vrf, state); +} diff --git a/staticd/static_nht.h b/staticd/static_nht.h new file mode 100644 index 0000000..74f4401 --- /dev/null +++ b/staticd/static_nht.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Static NHT header. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __STATIC_NHT_H__ +#define __STATIC_NHT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * When we get notification that nexthop tracking has an answer for + * us call this function to find the nexthop we are tracking so it + * can be installed or removed. + * + * sp -> The route we are looking at. If NULL then look at all + * routes. + * nhp -> The nexthop that is being tracked. + * nh_num -> number of valid nexthops. + * afi -> The afi we are working in. + * vrf_id -> The vrf the nexthop is in. + */ +extern void static_nht_update(struct prefix *sp, struct prefix *nhp, + uint32_t nh_num, afi_t afi, safi_t safi, + vrf_id_t vrf_id); + +/* + * For the given tracked nexthop, nhp, mark all routes that use + * this route as in starting state again. + */ +extern void static_nht_reset_start(struct prefix *nhp, afi_t afi, safi_t safi, + vrf_id_t nh_vrf_id); + +/* + * For the given prefix, sp, mark it as in a particular state + */ +extern void static_nht_mark_state(struct prefix *sp, safi_t safi, + vrf_id_t vrf_id, + enum static_install_states state); + +/* + * For the given nexthop, returns the string + */ +extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop, + size_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/static_routes.c b/staticd/static_routes.c new file mode 100644 index 0000000..cba3818 --- /dev/null +++ b/staticd/static_routes.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - route code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "printfrr.h" + +#include "static_vrf.h" +#include "static_routes.h" +#include "static_zebra.h" +#include "static_debug.h" + +DEFINE_MGROUP(STATIC, "staticd"); + +DEFINE_MTYPE_STATIC(STATIC, STATIC_ROUTE, "Static Route Info"); +DEFINE_MTYPE_STATIC(STATIC, STATIC_PATH, "Static Path"); +DEFINE_MTYPE_STATIC(STATIC, STATIC_NEXTHOP, "Static Nexthop"); + +void zebra_stable_node_cleanup(struct route_table *table, + struct route_node *node) +{ + struct static_nexthop *nh; + struct static_path *pn; + struct static_route_info *si; + struct route_table *src_table; + struct route_node *src_node; + struct static_path *src_pn; + struct static_route_info *src_si; + + si = node->info; + + if (si) { + frr_each_safe(static_path_list, &si->path_list, pn) { + frr_each_safe(static_nexthop_list, &pn->nexthop_list, + nh) { + static_nexthop_list_del(&pn->nexthop_list, nh); + XFREE(MTYPE_STATIC_NEXTHOP, nh); + } + static_path_list_del(&si->path_list, pn); + XFREE(MTYPE_STATIC_PATH, pn); + } + + /* clean up for dst table */ + src_table = srcdest_srcnode_table(node); + if (src_table) { + /* This means the route_node is part of the top + * hierarchy and refers to a destination prefix. + */ + for (src_node = route_top(src_table); src_node; + src_node = route_next(src_node)) { + src_si = src_node->info; + + frr_each_safe(static_path_list, + &src_si->path_list, src_pn) { + frr_each_safe(static_nexthop_list, + &src_pn->nexthop_list, + nh) { + static_nexthop_list_del( + &src_pn->nexthop_list, + nh); + XFREE(MTYPE_STATIC_NEXTHOP, nh); + } + static_path_list_del(&src_si->path_list, + src_pn); + XFREE(MTYPE_STATIC_PATH, src_pn); + } + + XFREE(MTYPE_STATIC_ROUTE, src_node->info); + } + } + + XFREE(MTYPE_STATIC_ROUTE, node->info); + } +} + +/* Install static path into rib. */ +void static_install_path(struct static_path *pn) +{ + if (static_nexthop_list_count(&pn->nexthop_list)) + static_zebra_route_add(pn, true); +} + +/* Uninstall static path from RIB. */ +static void static_uninstall_path(struct static_path *pn) +{ + if (static_nexthop_list_count(&pn->nexthop_list)) + static_zebra_route_add(pn, true); + else + static_zebra_route_add(pn, false); +} + +struct route_node *static_add_route(afi_t afi, safi_t safi, struct prefix *p, + struct prefix_ipv6 *src_p, + struct static_vrf *svrf) +{ + struct route_node *rn; + struct static_route_info *si; + struct route_table *stable = svrf->stable[afi][safi]; + + assert(stable); + + /* Lookup static route prefix. */ + rn = srcdest_rnode_get(stable, p, src_p); + + si = XCALLOC(MTYPE_STATIC_ROUTE, sizeof(struct static_route_info)); + + si->svrf = svrf; + si->safi = safi; + static_path_list_init(&(si->path_list)); + + rn->info = si; + + return rn; +} + +/* To delete the srcnodes */ +static void static_del_src_route(struct route_node *rn) +{ + struct static_path *pn; + struct static_route_info *si; + + si = rn->info; + + frr_each_safe(static_path_list, &si->path_list, pn) { + static_del_path(pn); + } + + XFREE(MTYPE_STATIC_ROUTE, rn->info); + route_unlock_node(rn); +} + +void static_del_route(struct route_node *rn) +{ + struct static_path *pn; + struct static_route_info *si; + struct route_table *src_table; + struct route_node *src_node; + + si = rn->info; + + frr_each_safe(static_path_list, &si->path_list, pn) { + static_del_path(pn); + } + + /* clean up for dst table */ + src_table = srcdest_srcnode_table(rn); + if (src_table) { + /* This means the route_node is part of the top hierarchy + * and refers to a destination prefix. + */ + for (src_node = route_top(src_table); src_node; + src_node = route_next(src_node)) { + static_del_src_route(src_node); + } + } + XFREE(MTYPE_STATIC_ROUTE, rn->info); + route_unlock_node(rn); +} + +bool static_add_nexthop_validate(const char *nh_vrf_name, + enum static_nh_type type, + struct ipaddr *ipaddr) +{ + struct vrf *vrf; + + vrf = vrf_lookup_by_name(nh_vrf_name); + if (!vrf) + return true; + + switch (type) { + case STATIC_IPV4_GATEWAY: + case STATIC_IPV4_GATEWAY_IFNAME: + if (if_address_is_local(&ipaddr->ipaddr_v4, AF_INET, + vrf->vrf_id)) + return false; + break; + case STATIC_IPV6_GATEWAY: + case STATIC_IPV6_GATEWAY_IFNAME: + if (if_address_is_local(&ipaddr->ipaddr_v6, AF_INET6, + vrf->vrf_id)) + return false; + break; + case STATIC_IFNAME: + case STATIC_BLACKHOLE: + break; + } + + return true; +} + +struct static_path *static_add_path(struct route_node *rn, uint32_t table_id, + uint8_t distance) +{ + struct static_path *pn; + struct static_route_info *si; + + route_lock_node(rn); + + /* Make new static route structure. */ + pn = XCALLOC(MTYPE_STATIC_PATH, sizeof(struct static_path)); + + pn->rn = rn; + pn->distance = distance; + pn->table_id = table_id; + static_nexthop_list_init(&(pn->nexthop_list)); + + si = rn->info; + static_path_list_add_head(&(si->path_list), pn); + + return pn; +} + +void static_del_path(struct static_path *pn) +{ + struct route_node *rn = pn->rn; + struct static_route_info *si; + struct static_nexthop *nh; + + si = rn->info; + + static_path_list_del(&si->path_list, pn); + + frr_each_safe(static_nexthop_list, &pn->nexthop_list, nh) { + static_delete_nexthop(nh); + } + + route_unlock_node(rn); + + XFREE(MTYPE_STATIC_PATH, pn); +} + +struct static_nexthop * +static_add_nexthop(struct static_path *pn, enum static_nh_type type, + struct ipaddr *ipaddr, const char *ifname, + const char *nh_vrfname, uint32_t color) +{ + struct route_node *rn = pn->rn; + struct static_nexthop *nh; + struct vrf *nh_vrf; + struct interface *ifp; + struct static_nexthop *cp; + + route_lock_node(rn); + + nh_vrf = vrf_lookup_by_name(nh_vrfname); + + /* Make new static route structure. */ + nh = XCALLOC(MTYPE_STATIC_NEXTHOP, sizeof(struct static_nexthop)); + + /* Copy back pointers. */ + nh->rn = rn; + nh->pn = pn; + + nh->type = type; + nh->color = color; + + if (nh->type == STATIC_BLACKHOLE) + nh->bh_type = STATIC_BLACKHOLE_NULL; + + nh->nh_vrf_id = nh_vrf ? nh_vrf->vrf_id : VRF_UNKNOWN; + strlcpy(nh->nh_vrfname, nh_vrfname, sizeof(nh->nh_vrfname)); + + if (ifname) + strlcpy(nh->ifname, ifname, sizeof(nh->ifname)); + nh->ifindex = IFINDEX_INTERNAL; + + switch (type) { + case STATIC_IPV4_GATEWAY: + case STATIC_IPV4_GATEWAY_IFNAME: + nh->addr.ipv4 = ipaddr->ipaddr_v4; + break; + case STATIC_IPV6_GATEWAY: + case STATIC_IPV6_GATEWAY_IFNAME: + nh->addr.ipv6 = ipaddr->ipaddr_v6; + break; + case STATIC_IFNAME: + case STATIC_BLACKHOLE: + break; + } + /* + * Add new static route information to the tree with sort by + * gateway address. + */ + frr_each(static_nexthop_list, &pn->nexthop_list, cp) { + if (nh->type == STATIC_IPV4_GATEWAY + && cp->type == STATIC_IPV4_GATEWAY) { + if (ntohl(nh->addr.ipv4.s_addr) + < ntohl(cp->addr.ipv4.s_addr)) + break; + if (ntohl(nh->addr.ipv4.s_addr) + > ntohl(cp->addr.ipv4.s_addr)) + continue; + } + } + static_nexthop_list_add_after(&(pn->nexthop_list), cp, nh); + + if (nh->nh_vrf_id == VRF_UNKNOWN) { + zlog_warn( + "Static Route to %pFX not installed currently because dependent config not fully available", + &rn->p); + return nh; + } + + /* check whether interface exists in system & install if it does */ + switch (nh->type) { + case STATIC_IPV4_GATEWAY: + case STATIC_IPV6_GATEWAY: + case STATIC_BLACKHOLE: + break; + case STATIC_IPV4_GATEWAY_IFNAME: + case STATIC_IPV6_GATEWAY_IFNAME: + case STATIC_IFNAME: + ifp = if_lookup_by_name(ifname, nh->nh_vrf_id); + if (ifp && ifp->ifindex != IFINDEX_INTERNAL) + nh->ifindex = ifp->ifindex; + else + zlog_warn( + "Static Route using %s interface not installed because the interface does not exist in specified vrf", + ifname); + break; + } + + return nh; +} + +void static_install_nexthop(struct static_nexthop *nh) +{ + struct static_path *pn = nh->pn; + struct route_node *rn = pn->rn; + struct interface *ifp; + + if (nh->nh_vrf_id == VRF_UNKNOWN) { + char nexthop_str[NEXTHOP_STR]; + + static_get_nh_str(nh, nexthop_str, sizeof(nexthop_str)); + DEBUGD(&static_dbg_route, + "Static Route %pFX not installed for %s vrf %s is unknown", + &rn->p, nexthop_str, nh->nh_vrfname); + return; + } + + /* check whether interface exists in system & install if it does */ + switch (nh->type) { + case STATIC_IPV4_GATEWAY: + case STATIC_IPV6_GATEWAY: + static_zebra_nht_register(nh, true); + break; + case STATIC_IPV4_GATEWAY_IFNAME: + case STATIC_IPV6_GATEWAY_IFNAME: + static_zebra_nht_register(nh, true); + break; + case STATIC_BLACKHOLE: + static_install_path(pn); + break; + case STATIC_IFNAME: + ifp = if_lookup_by_name(nh->ifname, nh->nh_vrf_id); + if (ifp && ifp->ifindex != IFINDEX_INTERNAL) + static_install_path(pn); + + break; + } +} + +void static_uninstall_nexthop(struct static_nexthop *nh) +{ + struct static_path *pn = nh->pn; + + if (nh->nh_vrf_id == VRF_UNKNOWN) + return; + + static_zebra_nht_register(nh, false); + static_uninstall_path(pn); +} + +void static_delete_nexthop(struct static_nexthop *nh) +{ + struct static_path *pn = nh->pn; + struct route_node *rn = pn->rn; + + static_nexthop_list_del(&(pn->nexthop_list), nh); + /* Remove BFD session/configuration if any. */ + bfd_sess_free(&nh->bsp); + + static_uninstall_nexthop(nh); + + route_unlock_node(rn); + /* Free static route configuration. */ + XFREE(MTYPE_STATIC_NEXTHOP, nh); +} + +static void static_ifindex_update_nh(struct interface *ifp, bool up, + struct route_node *rn, + struct static_path *pn, + struct static_nexthop *nh, + struct static_vrf *svrf, safi_t safi) +{ + if (!nh->ifname[0]) + return; + if (up) { + if (strcmp(nh->ifname, ifp->name)) + return; + if (nh->nh_vrf_id != ifp->vrf->vrf_id) + return; + nh->ifindex = ifp->ifindex; + } else { + if (nh->ifindex != ifp->ifindex) + return; + if (nh->nh_vrf_id != ifp->vrf->vrf_id) + return; + nh->ifindex = IFINDEX_INTERNAL; + } + + /* Remove previously configured route if any. */ + static_uninstall_path(pn); + static_install_path(pn); +} + +static void static_ifindex_update_af(struct interface *ifp, bool up, afi_t afi, + safi_t safi) +{ + struct route_table *stable; + struct route_node *rn; + struct static_nexthop *nh; + struct static_path *pn; + struct static_vrf *svrf; + struct static_route_info *si; + + RB_FOREACH (svrf, svrf_name_head, &svrfs) { + stable = static_vrf_static_table(afi, safi, svrf); + if (!stable) + continue; + for (rn = route_top(stable); rn; rn = srcdest_route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) { + frr_each(static_nexthop_list, + &pn->nexthop_list, nh) { + static_ifindex_update_nh(ifp, up, rn, + pn, nh, svrf, + safi); + } + } + } + } +} + +/* + * This function looks at a svrf's stable and notices if any of the + * nexthops we are using are part of the vrf coming up. + * If we are using them then cleanup the nexthop vrf id + * to be the new value and then re-installs them + * + * + * stable -> The table we are looking at. + * svrf -> The newly changed vrf. + * afi -> The afi to look at + * safi -> the safi to look at + */ +static void static_fixup_vrf(struct vrf *vrf, struct route_table *stable, + afi_t afi, safi_t safi) +{ + struct route_node *rn; + struct static_nexthop *nh; + struct interface *ifp; + struct static_path *pn; + struct static_route_info *si; + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) { + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + if (strcmp(vrf->name, nh->nh_vrfname) != 0) + continue; + + nh->nh_vrf_id = vrf->vrf_id; + if (nh->ifname[0]) { + ifp = if_lookup_by_name(nh->ifname, + nh->nh_vrf_id); + if (ifp) + nh->ifindex = ifp->ifindex; + else + continue; + } + + static_install_nexthop(nh); + } + } + } +} + +/* + * This function enables static routes in a svrf as it + * is coming up. It sets the new vrf_id as appropriate. + * + * svrf -> The svrf that is being brought up and enabled by the kernel + * stable -> The stable we are looking at. + * afi -> the afi in question + * safi -> the safi in question + */ +static void static_enable_vrf(struct route_table *stable, afi_t afi, safi_t safi) +{ + struct route_node *rn; + struct static_path *pn; + struct static_route_info *si; + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) + static_install_path(pn); + } +} + +/* + * When a vrf is being enabled by the kernel, go through all the + * static routes in the system that use this vrf (both nexthops vrfs + * and the routes vrf ) + * + * enable_svrf -> the vrf being enabled + */ +void static_fixup_vrf_ids(struct vrf *vrf) +{ + struct route_table *stable; + struct static_vrf *svrf, *enable_svrf; + afi_t afi; + safi_t safi; + + enable_svrf = vrf->info; + + RB_FOREACH (svrf, svrf_name_head, &svrfs) { + /* Install any static routes configured for this VRF. */ + FOREACH_AFI_SAFI (afi, safi) { + stable = svrf->stable[afi][safi]; + if (!stable) + continue; + + static_fixup_vrf(vrf, stable, afi, safi); + + if (enable_svrf == svrf) + static_enable_vrf(stable, afi, safi); + } + } +} + +/* + * Look at the specified stable and if any of the routes in + * this table are using the svrf as the nexthop, uninstall + * those routes. + * + * svrf -> the vrf being disabled + * stable -> the table we need to look at. + * afi -> the afi in question + * safi -> the safi in question + */ +static void static_cleanup_vrf(struct vrf *vrf, struct route_table *stable, + afi_t afi, safi_t safi) +{ + struct route_node *rn; + struct static_nexthop *nh; + struct static_path *pn; + struct static_route_info *si; + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) { + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + if (strcmp(vrf->name, nh->nh_vrfname) != 0) + continue; + + static_uninstall_nexthop(nh); + + nh->nh_vrf_id = VRF_UNKNOWN; + nh->ifindex = IFINDEX_INTERNAL; + } + } + } +} + +/* + * Look at all static routes in this table and uninstall + * them. + * + * stable -> The table to uninstall from + * afi -> The afi in question + * safi -> the safi in question + */ +static void static_disable_vrf(struct route_table *stable, + afi_t afi, safi_t safi) +{ + struct route_node *rn; + struct static_path *pn; + struct static_route_info *si; + + for (rn = route_top(stable); rn; rn = route_next(rn)) { + si = static_route_info_from_rnode(rn); + if (!si) + continue; + frr_each(static_path_list, &si->path_list, pn) + static_uninstall_path(pn); + } +} + +/* + * When the disable_svrf is shutdown by the kernel, we call + * this function and it cleans up all static routes using + * this vrf as a nexthop as well as all static routes + * in it's stables. + * + * disable_svrf - The vrf being disabled + */ +void static_cleanup_vrf_ids(struct vrf *vrf) +{ + struct route_table *stable; + struct static_vrf *svrf, *disable_svrf; + afi_t afi; + safi_t safi; + + disable_svrf = vrf->info; + + RB_FOREACH (svrf, svrf_name_head, &svrfs) { + /* Uninstall any static routes configured for this VRF. */ + FOREACH_AFI_SAFI (afi, safi) { + stable = svrf->stable[afi][safi]; + if (!stable) + continue; + + static_cleanup_vrf(vrf, stable, afi, safi); + + if (disable_svrf == svrf) + static_disable_vrf(stable, afi, safi); + } + } +} + +/* called from if_{add,delete}_update, i.e. when ifindex becomes [in]valid */ +void static_ifindex_update(struct interface *ifp, bool up) +{ + static_ifindex_update_af(ifp, up, AFI_IP, SAFI_UNICAST); + static_ifindex_update_af(ifp, up, AFI_IP, SAFI_MULTICAST); + static_ifindex_update_af(ifp, up, AFI_IP6, SAFI_UNICAST); + static_ifindex_update_af(ifp, up, AFI_IP6, SAFI_MULTICAST); +} + +struct stable_info *static_get_stable_info(struct route_node *rn) +{ + struct route_table *table; + + table = srcdest_rnode_table(rn); + return table->info; +} + +void static_get_nh_str(struct static_nexthop *nh, char *nexthop, size_t size) +{ + switch (nh->type) { + case STATIC_IFNAME: + snprintfrr(nexthop, size, "ifindex : %s", nh->ifname); + break; + case STATIC_IPV4_GATEWAY: + snprintfrr(nexthop, size, "ip4 : %pI4", &nh->addr.ipv4); + break; + case STATIC_IPV4_GATEWAY_IFNAME: + snprintfrr(nexthop, size, "ip4-ifindex : %pI4 : %s", + &nh->addr.ipv4, nh->ifname); + break; + case STATIC_BLACKHOLE: + snprintfrr(nexthop, size, "blackhole : %d", nh->bh_type); + break; + case STATIC_IPV6_GATEWAY: + snprintfrr(nexthop, size, "ip6 : %pI6", &nh->addr.ipv6); + break; + case STATIC_IPV6_GATEWAY_IFNAME: + snprintfrr(nexthop, size, "ip6-ifindex : %pI6 : %s", + &nh->addr.ipv6, nh->ifname); + break; + }; +} diff --git a/staticd/static_routes.h b/staticd/static_routes.h new file mode 100644 index 0000000..2e2e498 --- /dev/null +++ b/staticd/static_routes.h @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - static routes header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __STATIC_ROUTES_H__ +#define __STATIC_ROUTES_H__ + +#include "lib/bfd.h" +#include "lib/mpls.h" +#include "lib/srv6.h" +#include "table.h" +#include "memory.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DECLARE_MGROUP(STATIC); + +#include "staticd/static_vrf.h" + +/* Static route label information */ +struct static_nh_label { + uint8_t num_labels; + uint8_t reserved[3]; + mpls_label_t label[MPLS_MAX_LABELS]; +}; + +/* Static route seg information */ +struct static_nh_seg { + int num_segs; + struct in6_addr seg[SRV6_MAX_SIDS]; +}; + +enum static_blackhole_type { + STATIC_BLACKHOLE_DROP = 0, + STATIC_BLACKHOLE_NULL, + STATIC_BLACKHOLE_REJECT +}; + +/* + * The order for below macros should be in sync with + * yang model typedef nexthop-type + */ +enum static_nh_type { + STATIC_IFNAME = 1, + STATIC_IPV4_GATEWAY, + STATIC_IPV4_GATEWAY_IFNAME, + STATIC_IPV6_GATEWAY, + STATIC_IPV6_GATEWAY_IFNAME, + STATIC_BLACKHOLE, +}; + +/* + * Route Creation gives us: + * START -> Initial State, only exit is when we send the route to + * zebra for installation + * When we send the route to Zebra move to SENT_TO_ZEBRA + * SENT_TO_ZEBRA -> A way to notice that we've sent the route to zebra + * But have not received a response on it's status yet + * After The response from zebra we move to INSTALLED or FAILED + * INSTALLED -> Route was accepted + * FAILED -> Route was rejected + * When we receive notification about a nexthop that a route uses + * We move the route back to START and initiate the process again. + */ +enum static_install_states { + STATIC_START, + STATIC_SENT_TO_ZEBRA, + STATIC_INSTALLED, + STATIC_NOT_INSTALLED, +}; + +PREDECL_DLIST(static_path_list); +PREDECL_DLIST(static_nexthop_list); + +/* Static route information */ +struct static_route_info { + struct static_vrf *svrf; + safi_t safi; + /* path list */ + struct static_path_list_head path_list; +}; + +/* Static path information */ +struct static_path { + /* Route node back pointer. */ + struct route_node *rn; + /* Linkage for static path lists */ + struct static_path_list_item list; + /* Administrative distance. */ + uint8_t distance; + /* Tag */ + route_tag_t tag; + /* Table-id */ + uint32_t table_id; + /* Nexthop list */ + struct static_nexthop_list_head nexthop_list; +}; + +DECLARE_DLIST(static_path_list, struct static_path, list); + +/* Static route information. */ +struct static_nexthop { + /* Path back pointer. */ + struct static_path *pn; + /* For linked list. */ + struct static_nexthop_list_item list; + + /* VRF identifier. */ + vrf_id_t nh_vrf_id; + char nh_vrfname[VRF_NAMSIZ + 1]; + + /* + * States that we walk the route through + * To know where we are. + */ + enum static_install_states state; + + /* Flag for this static route's type. */ + enum static_nh_type type; + + /* + * Nexthop value. + */ + enum static_blackhole_type bh_type; + union g_addr addr; + ifindex_t ifindex; + bool nh_registered; + bool nh_valid; + + char ifname[IFNAMSIZ + 1]; + + /* Label information */ + struct static_nh_label snh_label; + + /* SRv6 Seg information */ + struct static_nh_seg snh_seg; + + /* + * Whether to pretend the nexthop is directly attached to the specified + * link. Only meaningful when both a gateway address and interface name + * are specified. + */ + bool onlink; + + /* SR-TE color */ + uint32_t color; + + /** BFD integration data. */ + struct bfd_session_params *bsp; + /** Back pointer for route node. */ + struct route_node *rn; + /** Path connection status. */ + bool path_down; +}; + +DECLARE_DLIST(static_nexthop_list, struct static_nexthop, list); + + +/* + * rib_dest_from_rnode + */ +static inline struct static_route_info * +static_route_info_from_rnode(struct route_node *rn) +{ + return (struct static_route_info *)(rn->info); +} + +static inline void static_get_nh_type(enum static_nh_type stype, char *type, + size_t size) +{ + switch (stype) { + case STATIC_IFNAME: + strlcpy(type, "ifindex", size); + break; + case STATIC_IPV4_GATEWAY: + strlcpy(type, "ip4", size); + break; + case STATIC_IPV4_GATEWAY_IFNAME: + strlcpy(type, "ip4-ifindex", size); + break; + case STATIC_BLACKHOLE: + strlcpy(type, "blackhole", size); + break; + case STATIC_IPV6_GATEWAY: + strlcpy(type, "ip6", size); + break; + case STATIC_IPV6_GATEWAY_IFNAME: + strlcpy(type, "ip6-ifindex", size); + break; + }; +} + +extern bool mpls_enabled; +extern uint32_t zebra_ecmp_count; + +extern struct zebra_privs_t static_privs; + +extern void static_fixup_vrf_ids(struct vrf *vrf); +extern void static_cleanup_vrf_ids(struct vrf *vrf); + +extern struct static_nexthop * +static_add_nexthop(struct static_path *pn, enum static_nh_type type, + struct ipaddr *ipaddr, const char *ifname, + const char *nh_vrf, uint32_t color); +extern void static_install_nexthop(struct static_nexthop *nh); +extern void static_uninstall_nexthop(struct static_nexthop *nh); + +extern void static_delete_nexthop(struct static_nexthop *nh); + +extern void static_ifindex_update(struct interface *ifp, bool up); + +extern void static_install_path(struct static_path *pn); + +extern struct route_node *static_add_route(afi_t afi, safi_t safi, + struct prefix *p, + struct prefix_ipv6 *src_p, + struct static_vrf *svrf); +extern void static_del_route(struct route_node *rn); + +extern struct static_path *static_add_path(struct route_node *rn, + uint32_t table_id, uint8_t distance); +extern void static_del_path(struct static_path *pn); + +extern bool static_add_nexthop_validate(const char *nh_vrf_name, + enum static_nh_type type, + struct ipaddr *ipaddr); +extern struct stable_info *static_get_stable_info(struct route_node *rn); + +extern void zebra_stable_node_cleanup(struct route_table *table, + struct route_node *node); + +/* + * Max string return via API static_get_nh_str in size_t + */ + +#define NEXTHOP_STR (INET6_ADDRSTRLEN + IFNAMSIZ + 25) +/* + * For the given nexthop, returns the string + * nexthop : returns the formatted string in nexthop + * size : max size of formatted string + */ +extern void static_get_nh_str(struct static_nexthop *nh, char *nexthop, + size_t size); + +/* + * BFD integration. + */ +extern void static_next_hop_bfd_source(struct static_nexthop *sn, + const struct ipaddr *source); +extern void static_next_hop_bfd_auto_source(struct static_nexthop *sn); +extern void static_next_hop_bfd_monitor_enable(struct static_nexthop *sn, + const struct lyd_node *dnode); +extern void static_next_hop_bfd_monitor_disable(struct static_nexthop *sn); +extern void static_next_hop_bfd_profile(struct static_nexthop *sn, + const char *name); +extern void static_next_hop_bfd_multi_hop(struct static_nexthop *sn, bool mhop); + +/** Call this function after zebra client initialization. */ +extern void static_bfd_initialize(struct zclient *zc, struct event_loop *tm); + +extern void static_bfd_show(struct vty *vty, bool isjson); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/static_vrf.c b/staticd/static_vrf.c new file mode 100644 index 0000000..710827a --- /dev/null +++ b/staticd/static_vrf.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - vrf code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "vrf.h" +#include "nexthop.h" +#include "table.h" +#include "srcdest_table.h" +#include "northbound_cli.h" + +#include "static_vrf.h" +#include "static_routes.h" +#include "static_zebra.h" + +DEFINE_MTYPE_STATIC(STATIC, STATIC_RTABLE_INFO, "Static Route Table Info"); + +static int svrf_name_compare(const struct static_vrf *a, + const struct static_vrf *b) +{ + return strcmp(a->name, b->name); +} + +RB_GENERATE(svrf_name_head, static_vrf, entry, svrf_name_compare); + +struct svrf_name_head svrfs = RB_INITIALIZER(&svrfs); + +static struct static_vrf *static_vrf_lookup_by_name(const char *name) +{ + struct static_vrf svrf; + + strlcpy(svrf.name, name, sizeof(svrf.name)); + return RB_FIND(svrf_name_head, &svrfs, &svrf); +} + +struct static_vrf *static_vrf_alloc(const char *name) +{ + struct route_table *table; + struct static_vrf *svrf; + struct stable_info *info; + struct vrf *vrf; + safi_t safi; + afi_t afi; + + svrf = XCALLOC(MTYPE_STATIC_RTABLE_INFO, sizeof(struct static_vrf)); + + strlcpy(svrf->name, name, sizeof(svrf->name)); + + for (afi = AFI_IP; afi <= AFI_IP6; afi++) { + for (safi = SAFI_UNICAST; safi <= SAFI_MULTICAST; safi++) { + if (afi == AFI_IP6) + table = srcdest_table_init(); + else + table = route_table_init(); + + info = XCALLOC(MTYPE_STATIC_RTABLE_INFO, + sizeof(struct stable_info)); + info->svrf = svrf; + info->afi = afi; + info->safi = safi; + route_table_set_info(table, info); + + table->cleanup = zebra_stable_node_cleanup; + svrf->stable[afi][safi] = table; + } + } + + RB_INSERT(svrf_name_head, &svrfs, svrf); + + vrf = vrf_lookup_by_name(name); + if (vrf) { + svrf->vrf = vrf; + vrf->info = svrf; + } + + return svrf; +} + +void static_vrf_free(struct static_vrf *svrf) +{ + struct route_table *table; + struct vrf *vrf; + safi_t safi; + afi_t afi; + void *info; + + vrf = svrf->vrf; + if (vrf) { + vrf->info = NULL; + svrf->vrf = NULL; + } + + RB_REMOVE(svrf_name_head, &svrfs, svrf); + + for (afi = AFI_IP; afi <= AFI_IP6; afi++) { + for (safi = SAFI_UNICAST; safi <= SAFI_MULTICAST; safi++) { + table = svrf->stable[afi][safi]; + info = route_table_get_info(table); + route_table_finish(table); + XFREE(MTYPE_STATIC_RTABLE_INFO, info); + svrf->stable[afi][safi] = NULL; + } + } + + XFREE(MTYPE_STATIC_RTABLE_INFO, svrf); +} + +static int static_vrf_new(struct vrf *vrf) +{ + struct static_vrf *svrf; + + svrf = static_vrf_lookup_by_name(vrf->name); + if (svrf) { + vrf->info = svrf; + svrf->vrf = vrf; + } + + return 0; +} + +static int static_vrf_enable(struct vrf *vrf) +{ + static_zebra_vrf_register(vrf); + static_fixup_vrf_ids(vrf); + return 0; +} + +static int static_vrf_disable(struct vrf *vrf) +{ + static_cleanup_vrf_ids(vrf); + static_zebra_vrf_unregister(vrf); + return 0; +} + +static int static_vrf_delete(struct vrf *vrf) +{ + struct static_vrf *svrf; + + svrf = vrf->info; + if (svrf) { + svrf->vrf = NULL; + vrf->info = NULL; + } + + return 0; +} + +/* Lookup the static routing table in a VRF. */ +struct route_table *static_vrf_static_table(afi_t afi, safi_t safi, + struct static_vrf *svrf) +{ + if (!svrf) + return NULL; + + if (afi >= AFI_MAX || safi >= SAFI_MAX) + return NULL; + + return svrf->stable[afi][safi]; +} + +void static_vrf_init(void) +{ + vrf_init(static_vrf_new, static_vrf_enable, static_vrf_disable, + static_vrf_delete); + + vrf_cmd_init(NULL); +} + +void static_vrf_terminate(void) +{ + struct static_vrf *svrf, *svrf_next; + + RB_FOREACH_SAFE (svrf, svrf_name_head, &svrfs, svrf_next) + static_vrf_free(svrf); + + vrf_terminate(); +} diff --git a/staticd/static_vrf.h b/staticd/static_vrf.h new file mode 100644 index 0000000..26ee28f --- /dev/null +++ b/staticd/static_vrf.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - vrf header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __STATIC_VRF_H__ +#define __STATIC_VRF_H__ + +#include "openbsd-tree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct static_vrf { + RB_ENTRY(static_vrf) entry; + + char name[VRF_NAMSIZ + 1]; + struct vrf *vrf; + + struct route_table *stable[AFI_MAX][SAFI_MAX]; +}; +RB_HEAD(svrf_name_head, static_vrf); +RB_PROTOTYPE(svrf_name_head, static_vrf, entry, svrf_name_compare) + +extern struct svrf_name_head svrfs; + +struct static_vrf *static_vrf_alloc(const char *name); +void static_vrf_free(struct static_vrf *svrf); + +struct stable_info { + struct static_vrf *svrf; + afi_t afi; + safi_t safi; +}; + +#define GET_STABLE_VRF_ID(info) info->svrf->vrf->vrf_id + +void static_vrf_init(void); + +struct route_table *static_vrf_static_table(afi_t afi, safi_t safi, + struct static_vrf *svrf); +extern void static_vrf_terminate(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/static_vty.c b/staticd/static_vty.c new file mode 100644 index 0000000..a18028e --- /dev/null +++ b/staticd/static_vty.c @@ -0,0 +1,1687 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - vty code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "command.h" +#include "vty.h" +#include "vrf.h" +#include "prefix.h" +#include "nexthop.h" +#include "table.h" +#include "srcdest_table.h" +#include "mgmt_be_client.h" +#include "mpls.h" +#include "northbound.h" +#include "libfrr.h" +#include "routing_nb.h" +#include "northbound_cli.h" +#include "frrdistance.h" + +#include "static_vrf.h" +#include "static_vty.h" +#include "static_routes.h" +#include "static_debug.h" +#include "staticd/static_vty_clippy.c" +#include "static_nb.h" + +#define STATICD_STR "Static route daemon\n" + +/** All possible route parameters available in CLI. */ +struct static_route_args { + /** "no" command? */ + bool delete; + /** Is VRF obtained from XPath? */ + bool xpath_vrf; + + bool onlink; + afi_t afi; + safi_t safi; + + const char *vrf; + const char *nexthop_vrf; + const char *prefix; + const char *prefix_mask; + const char *source; + const char *gateway; + const char *interface_name; + const char *segs; + const char *flag; + const char *tag; + const char *distance; + const char *label; + const char *table; + const char *color; + + bool bfd; + bool bfd_multi_hop; + const char *bfd_source; + const char *bfd_profile; +}; + +static int static_route_nb_run(struct vty *vty, struct static_route_args *args) +{ + int ret; + struct prefix p, src; + struct in_addr mask; + enum static_nh_type type; + const char *bh_type; + char xpath_prefix[XPATH_MAXLEN]; + char xpath_nexthop[XPATH_MAXLEN]; + char xpath_mpls[XPATH_MAXLEN]; + char xpath_label[XPATH_MAXLEN]; + char xpath_segs[XPATH_MAXLEN]; + char xpath_seg[XPATH_MAXLEN]; + char ab_xpath[XPATH_MAXLEN]; + char buf_prefix[PREFIX_STRLEN]; + char buf_src_prefix[PREFIX_STRLEN] = {}; + char buf_nh_type[PREFIX_STRLEN] = {}; + char buf_tag[PREFIX_STRLEN]; + uint8_t label_stack_id = 0; + uint8_t segs_stack_id = 0; + char *orig_label = NULL, *orig_seg = NULL; + const char *buf_gate_str; + uint8_t distance = ZEBRA_STATIC_DISTANCE_DEFAULT; + route_tag_t tag = 0; + uint32_t table_id = 0; + const struct lyd_node *dnode; + const struct lyd_node *vrf_dnode; + + if (args->xpath_vrf) { + vrf_dnode = yang_dnode_get(vty->candidate_config->dnode, + VTY_CURR_XPATH); + if (vrf_dnode == NULL) { + vty_out(vty, + "%% Failed to get vrf dnode in candidate db\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + args->vrf = yang_dnode_get_string(vrf_dnode, "name"); + } else { + if (args->vrf == NULL) + args->vrf = VRF_DEFAULT_NAME; + } + if (args->nexthop_vrf == NULL) + args->nexthop_vrf = args->vrf; + + if (args->interface_name && + !strcasecmp(args->interface_name, "Null0")) { + args->flag = "Null0"; + args->interface_name = NULL; + } + + assert(!!str2prefix(args->prefix, &p)); + + switch (args->afi) { + case AFI_IP: + /* Cisco like mask notation. */ + if (args->prefix_mask) { + assert(inet_pton(AF_INET, args->prefix_mask, &mask) == + 1); + p.prefixlen = ip_masklen(mask); + } + break; + case AFI_IP6: + /* srcdest routing */ + if (args->source) + assert(!!str2prefix(args->source, &src)); + break; + case AFI_L2VPN: + case AFI_UNSPEC: + case AFI_MAX: + break; + } + + /* Apply mask for given prefix. */ + apply_mask(&p); + prefix2str(&p, buf_prefix, sizeof(buf_prefix)); + + if (args->bfd && args->gateway == NULL) { + vty_out(vty, "%% Route monitoring requires a gateway\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (args->source) + prefix2str(&src, buf_src_prefix, sizeof(buf_src_prefix)); + if (args->gateway) + buf_gate_str = args->gateway; + else + buf_gate_str = ""; + + if (args->gateway == NULL && args->interface_name == NULL) + type = STATIC_BLACKHOLE; + else if (args->gateway && args->interface_name) { + if (args->afi == AFI_IP) + type = STATIC_IPV4_GATEWAY_IFNAME; + else + type = STATIC_IPV6_GATEWAY_IFNAME; + } else if (args->interface_name) + type = STATIC_IFNAME; + else { + if (args->afi == AFI_IP) + type = STATIC_IPV4_GATEWAY; + else + type = STATIC_IPV6_GATEWAY; + } + + /* Administrative distance. */ + if (args->distance) + distance = strtol(args->distance, NULL, 10); + + /* tag */ + if (args->tag) + tag = strtoul(args->tag, NULL, 10); + + /* TableID */ + if (args->table) + table_id = strtol(args->table, NULL, 10); + + static_get_nh_type(type, buf_nh_type, sizeof(buf_nh_type)); + if (!args->delete) { + if (args->source) + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_SRC_NH_KEY_NO_DISTANCE_XPATH, + "frr-staticd:staticd", "staticd", args->vrf, + buf_prefix, + yang_afi_safi_value2identity(args->afi, + args->safi), + buf_src_prefix, table_id, buf_nh_type, + args->nexthop_vrf, buf_gate_str, + args->interface_name); + else + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_NH_KEY_NO_DISTANCE_XPATH, + "frr-staticd:staticd", "staticd", args->vrf, + buf_prefix, + yang_afi_safi_value2identity(args->afi, + args->safi), + table_id, buf_nh_type, args->nexthop_vrf, + buf_gate_str, args->interface_name); + + /* + * If there's already the same nexthop but with a different + * distance, then remove it for the replacement. + */ + dnode = yang_dnode_get(vty->candidate_config->dnode, ab_xpath); + if (dnode) { + dnode = yang_get_subtree_with_no_sibling(dnode); + assert(dnode); + yang_dnode_get_path(dnode, ab_xpath, XPATH_MAXLEN); + + nb_cli_enqueue_change(vty, ab_xpath, NB_OP_DESTROY, + NULL); + } + + /* route + path procesing */ + if (args->source) + snprintf(xpath_prefix, sizeof(xpath_prefix), + FRR_S_ROUTE_SRC_INFO_KEY_XPATH, + "frr-staticd:staticd", "staticd", args->vrf, + buf_prefix, + yang_afi_safi_value2identity(args->afi, + args->safi), + buf_src_prefix, table_id, distance); + else + snprintf(xpath_prefix, sizeof(xpath_prefix), + FRR_STATIC_ROUTE_INFO_KEY_XPATH, + "frr-staticd:staticd", "staticd", args->vrf, + buf_prefix, + yang_afi_safi_value2identity(args->afi, + args->safi), + table_id, distance); + + nb_cli_enqueue_change(vty, xpath_prefix, NB_OP_CREATE, NULL); + + /* Tag processing */ + snprintf(buf_tag, sizeof(buf_tag), "%u", tag); + strlcpy(ab_xpath, xpath_prefix, sizeof(ab_xpath)); + strlcat(ab_xpath, FRR_STATIC_ROUTE_PATH_TAG_XPATH, + sizeof(ab_xpath)); + nb_cli_enqueue_change(vty, ab_xpath, NB_OP_MODIFY, buf_tag); + + /* nexthop processing */ + + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_STATIC_ROUTE_NH_KEY_XPATH, buf_nh_type, + args->nexthop_vrf, buf_gate_str, args->interface_name); + strlcpy(xpath_nexthop, xpath_prefix, sizeof(xpath_nexthop)); + strlcat(xpath_nexthop, ab_xpath, sizeof(xpath_nexthop)); + nb_cli_enqueue_change(vty, xpath_nexthop, NB_OP_CREATE, NULL); + + if (type == STATIC_BLACKHOLE) { + strlcpy(ab_xpath, xpath_nexthop, sizeof(ab_xpath)); + strlcat(ab_xpath, FRR_STATIC_ROUTE_NH_BH_XPATH, + sizeof(ab_xpath)); + + /* Route flags */ + if (args->flag) { + switch (args->flag[0]) { + case 'r': + bh_type = "reject"; + break; + case 'b': + bh_type = "unspec"; + break; + case 'N': + bh_type = "null"; + break; + default: + bh_type = NULL; + break; + } + nb_cli_enqueue_change(vty, ab_xpath, + NB_OP_MODIFY, bh_type); + } else { + nb_cli_enqueue_change(vty, ab_xpath, + NB_OP_MODIFY, "null"); + } + } + if (type == STATIC_IPV4_GATEWAY_IFNAME + || type == STATIC_IPV6_GATEWAY_IFNAME) { + strlcpy(ab_xpath, xpath_nexthop, sizeof(ab_xpath)); + strlcat(ab_xpath, FRR_STATIC_ROUTE_NH_ONLINK_XPATH, + sizeof(ab_xpath)); + + if (args->onlink) + nb_cli_enqueue_change(vty, ab_xpath, + NB_OP_MODIFY, "true"); + else + nb_cli_enqueue_change(vty, ab_xpath, + NB_OP_MODIFY, "false"); + } + if (type == STATIC_IPV4_GATEWAY || + type == STATIC_IPV6_GATEWAY || + type == STATIC_IPV4_GATEWAY_IFNAME || + type == STATIC_IPV6_GATEWAY_IFNAME) { + strlcpy(ab_xpath, xpath_nexthop, sizeof(ab_xpath)); + strlcat(ab_xpath, FRR_STATIC_ROUTE_NH_COLOR_XPATH, + sizeof(ab_xpath)); + if (args->color) + nb_cli_enqueue_change(vty, ab_xpath, + NB_OP_MODIFY, + args->color); + } + if (args->label) { + /* copy of label string (start) */ + char *ostr; + /* pointer to next segment */ + char *nump; + + strlcpy(xpath_mpls, xpath_nexthop, sizeof(xpath_mpls)); + strlcat(xpath_mpls, FRR_STATIC_ROUTE_NH_LABEL_XPATH, + sizeof(xpath_mpls)); + + nb_cli_enqueue_change(vty, xpath_mpls, NB_OP_DESTROY, + NULL); + + orig_label = ostr = XSTRDUP(MTYPE_TMP, args->label); + while ((nump = strsep(&ostr, "/")) != NULL) { + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_STATIC_ROUTE_NHLB_KEY_XPATH, + label_stack_id); + strlcpy(xpath_label, xpath_mpls, + sizeof(xpath_label)); + strlcat(xpath_label, ab_xpath, + sizeof(xpath_label)); + nb_cli_enqueue_change(vty, xpath_label, + NB_OP_MODIFY, nump); + label_stack_id++; + } + } else { + strlcpy(xpath_mpls, xpath_nexthop, sizeof(xpath_mpls)); + strlcat(xpath_mpls, FRR_STATIC_ROUTE_NH_LABEL_XPATH, + sizeof(xpath_mpls)); + nb_cli_enqueue_change(vty, xpath_mpls, NB_OP_DESTROY, + NULL); + } + if (args->segs) { + /* copy of seg string (start) */ + char *ostr; + /* pointer to next segment */ + char *nump; + + strlcpy(xpath_segs, xpath_nexthop, sizeof(xpath_segs)); + strlcat(xpath_segs, FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH, + sizeof(xpath_segs)); + + nb_cli_enqueue_change(vty, xpath_segs, NB_OP_DESTROY, + NULL); + + orig_seg = ostr = XSTRDUP(MTYPE_TMP, args->segs); + while ((nump = strsep(&ostr, "/")) != NULL) { + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_STATIC_ROUTE_NH_SRV6_KEY_SEG_XPATH, + segs_stack_id); + strlcpy(xpath_seg, xpath_segs, + sizeof(xpath_seg)); + strlcat(xpath_seg, ab_xpath, sizeof(xpath_seg)); + nb_cli_enqueue_change(vty, xpath_seg, + NB_OP_MODIFY, nump); + segs_stack_id++; + } + } else { + strlcpy(xpath_segs, xpath_nexthop, sizeof(xpath_segs)); + strlcat(xpath_segs, FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH, + sizeof(xpath_segs)); + nb_cli_enqueue_change(vty, xpath_segs, NB_OP_DESTROY, + NULL); + } + if (args->bfd) { + char xpath_bfd[XPATH_MAXLEN]; + + if (args->bfd_source) { + strlcpy(xpath_bfd, xpath_nexthop, + sizeof(xpath_bfd)); + strlcat(xpath_bfd, + "/frr-staticd:bfd-monitoring/source", + sizeof(xpath_bfd)); + nb_cli_enqueue_change(vty, xpath_bfd, + NB_OP_MODIFY, + args->bfd_source); + } + + strlcpy(xpath_bfd, xpath_nexthop, sizeof(xpath_bfd)); + strlcat(xpath_bfd, + "/frr-staticd:bfd-monitoring/multi-hop", + sizeof(xpath_bfd)); + nb_cli_enqueue_change(vty, xpath_bfd, NB_OP_MODIFY, + args->bfd_multi_hop ? "true" + : "false"); + + if (args->bfd_profile) { + strlcpy(xpath_bfd, xpath_nexthop, + sizeof(xpath_bfd)); + strlcat(xpath_bfd, + "/frr-staticd:bfd-monitoring/profile", + sizeof(xpath_bfd)); + nb_cli_enqueue_change(vty, xpath_bfd, + NB_OP_MODIFY, + args->bfd_profile); + } + } + + ret = nb_cli_apply_changes(vty, "%s", xpath_prefix); + + if (orig_label) + XFREE(MTYPE_TMP, orig_label); + if (orig_seg) + XFREE(MTYPE_TMP, orig_seg); + } else { + if (args->source) { + if (args->distance) + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_SRC_NH_KEY_XPATH, + "frr-staticd:staticd", "staticd", + args->vrf, buf_prefix, + yang_afi_safi_value2identity( + args->afi, args->safi), + buf_src_prefix, table_id, distance, + buf_nh_type, args->nexthop_vrf, + buf_gate_str, args->interface_name); + else + snprintf( + ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_SRC_NH_KEY_NO_DISTANCE_XPATH, + "frr-staticd:staticd", "staticd", + args->vrf, buf_prefix, + yang_afi_safi_value2identity( + args->afi, args->safi), + buf_src_prefix, table_id, buf_nh_type, + args->nexthop_vrf, buf_gate_str, + args->interface_name); + } else { + if (args->distance) + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_NH_KEY_XPATH, + "frr-staticd:staticd", "staticd", + args->vrf, buf_prefix, + yang_afi_safi_value2identity( + args->afi, args->safi), + table_id, distance, buf_nh_type, + args->nexthop_vrf, buf_gate_str, + args->interface_name); + else + snprintf( + ab_xpath, sizeof(ab_xpath), + FRR_DEL_S_ROUTE_NH_KEY_NO_DISTANCE_XPATH, + "frr-staticd:staticd", "staticd", + args->vrf, buf_prefix, + yang_afi_safi_value2identity( + args->afi, args->safi), + table_id, buf_nh_type, + args->nexthop_vrf, buf_gate_str, + args->interface_name); + } + + dnode = yang_dnode_get(vty->candidate_config->dnode, ab_xpath); + if (!dnode) { + vty_out(vty, + "%% Refusing to remove a non-existent route\n"); + return CMD_SUCCESS; + } + + dnode = yang_get_subtree_with_no_sibling(dnode); + assert(dnode); + yang_dnode_get_path(dnode, ab_xpath, XPATH_MAXLEN); + + nb_cli_enqueue_change(vty, ab_xpath, NB_OP_DESTROY, NULL); + ret = nb_cli_apply_changes(vty, "%s", ab_xpath); + } + + return ret; +} + +/* Static unicast routes for multicast RPF lookup. */ +DEFPY_YANG (ip_mroute_dist, + ip_mroute_dist_cmd, + "[no] ip mroute A.B.C.D/M$prefix [{" + "(1-255)$distance" + "|bfd$bfd [{multi-hop$bfd_multi_hop|source A.B.C.D$bfd_source|profile BFDPROF$bfd_profile}]" + "}]", + NO_STR + IP_STR + "Configure static unicast route into MRIB for multicast RPF lookup\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "Nexthop address\n" + "Nexthop interface name\n" + "Distance\n" + BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR + BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_MULTICAST, + .prefix = prefix_str, + .gateway = gate_str, + .interface_name = ifname, + .distance = distance_str, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + }; + + return static_route_nb_run(vty, &args); +} + +/* Static route configuration. */ +DEFPY_YANG(ip_route_blackhole, + ip_route_blackhole_cmd, + "[no] ip route\ + \ + $flag \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "Emit an ICMP unreachable when matched\n" + "Silently discard pkts when matched\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + VRF_CMD_HELP_STR + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .flag = flag, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .vrf = vrf, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ip_route_blackhole_vrf, + ip_route_blackhole_vrf_cmd, + "[no] ip route\ + \ + $flag \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "Emit an ICMP unreachable when matched\n" + "Silently discard pkts when matched\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .flag = flag, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .xpath_vrf = true, + }; + + /* + * Coverity is complaining that prefix could + * be dereferenced, but we know that prefix will + * valid. Add an assert to make it happy + */ + assert(args.prefix); + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ip_route_address_interface, + ip_route_address_interface_cmd, + "[no] ip route\ + \ + A.B.C.D$gate \ + $ifname \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |onlink$onlink \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source A.B.C.D$bfd_source|profile BFDPROF$bfd_profile}] \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "IP gateway address\n" + "IP gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + VRF_CMD_HELP_STR + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" + VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" + BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR + BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .onlink = !!onlink, + .vrf = vrf, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ip_route_address_interface_vrf, + ip_route_address_interface_vrf_cmd, + "[no] ip route\ + \ + A.B.C.D$gate \ + $ifname \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |onlink$onlink \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source A.B.C.D$bfd_source|profile BFDPROF$bfd_profile}] \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "IP gateway address\n" + "IP gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" + VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" + BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR + BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .onlink = !!onlink, + .xpath_vrf = true, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ip_route, + ip_route_cmd, + "[no] ip route\ + \ + $ifname> \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source A.B.C.D$bfd_source|profile BFDPROF$bfd_profile}] \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "IP gateway address\n" + "IP gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + VRF_CMD_HELP_STR + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" + VRF_CMD_HELP_STR + "SR-TE color\n" + "The SR-TE color to configure\n" + BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR + BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .vrf = vrf, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ip_route_vrf, + ip_route_vrf_cmd, + "[no] ip route\ + \ + $ifname> \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source A.B.C.D$bfd_source|profile BFDPROF$bfd_profile}] \ + }]", + NO_STR IP_STR + "Establish static routes\n" + "IP destination prefix (e.g. 10.0.0.0/8)\n" + "IP destination prefix\n" + "IP destination prefix mask\n" + "IP gateway address\n" + "IP gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this route\n" + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" + VRF_CMD_HELP_STR + "SR-TE color\n" + "The SR-TE color to configure\n" + BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR + BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR + BFD_PROFILE_STR + BFD_PROFILE_NAME_STR) +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP, + .safi = SAFI_UNICAST, + .prefix = prefix, + .prefix_mask = mask_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .xpath_vrf = true, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route_blackhole, + ipv6_route_blackhole_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + $flag \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + }]", + NO_STR + IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "Emit an ICMP unreachable when matched\n" + "Silently discard pkts when matched\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" + VRF_CMD_HELP_STR + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .flag = flag, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .vrf = vrf, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route_blackhole_vrf, + ipv6_route_blackhole_vrf_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + $flag \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + }]", + NO_STR + IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "Emit an ICMP unreachable when matched\n" + "Silently discard pkts when matched\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" + MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .flag = flag, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .xpath_vrf = true, + }; + + /* + * Coverity is complaining that prefix could + * be dereferenced, but we know that prefix will + * valid. Add an assert to make it happy + */ + assert(args.prefix); + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route_address_interface, ipv6_route_address_interface_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + X:X::X:X$gate \ + $ifname \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |onlink$onlink \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ + }]", + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .onlink = !!onlink, + .vrf = vrf, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + .segs = segments, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route_address_interface_vrf, + ipv6_route_address_interface_vrf_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + X:X::X:X$gate \ + $ifname \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |onlink$onlink \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ + }]", + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .onlink = !!onlink, + .xpath_vrf = true, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + .segs = segments, + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route, ipv6_route_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + $ifname> \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |vrf NAME \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ + }]", + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .vrf = vrf, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + .segs = segments, + + }; + + return static_route_nb_run(vty, &args); +} + +DEFPY_YANG(ipv6_route_vrf, ipv6_route_vrf_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + $ifname> \ + [{ \ + tag (1-4294967295) \ + |(1-255)$distance \ + |label WORD \ + |table (1-4294967295) \ + |nexthop-vrf NAME \ + |color (1-4294967295) \ + |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ + }]", + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") +{ + struct static_route_args args = { + .delete = !!no, + .afi = AFI_IP6, + .safi = SAFI_UNICAST, + .prefix = prefix_str, + .source = from_str, + .gateway = gate_str, + .interface_name = ifname, + .tag = tag_str, + .distance = distance_str, + .label = label, + .table = table_str, + .color = color_str, + .xpath_vrf = true, + .nexthop_vrf = nexthop_vrf, + .bfd = !!bfd, + .bfd_multi_hop = !!bfd_multi_hop, + .bfd_source = bfd_source_str, + .bfd_profile = bfd_profile, + .segs = segments, + }; + + return static_route_nb_run(vty, &args); +} + +#ifdef INCLUDE_MGMTD_CMDDEFS_ONLY + +static void static_cli_show(struct vty *vty, const struct lyd_node *dnode, + bool show_defaults) +{ + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "../vrf"); + if (strcmp(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, "vrf %s\n", vrf); +} + +static void static_cli_show_end(struct vty *vty, const struct lyd_node *dnode) +{ + const char *vrf; + + vrf = yang_dnode_get_string(dnode, "../vrf"); + if (strcmp(vrf, VRF_DEFAULT_NAME)) + vty_out(vty, "exit-vrf\n"); +} + +struct mpls_label_iter { + struct vty *vty; + bool first; +}; + +static int mpls_label_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct mpls_label_iter *iter = arg; + + if (yang_dnode_exists(dnode, "label")) { + if (iter->first) + vty_out(iter->vty, " label %s", + yang_dnode_get_string(dnode, "label")); + else + vty_out(iter->vty, "/%s", + yang_dnode_get_string(dnode, "label")); + iter->first = false; + } + + return YANG_ITER_CONTINUE; +} + +struct srv6_seg_iter { + struct vty *vty; + bool first; +}; + +static int srv6_seg_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct srv6_seg_iter *iter = arg; + char buffer[INET6_ADDRSTRLEN]; + struct in6_addr cli_seg; + + if (yang_dnode_exists(dnode, "seg")) { + if (iter->first) { + yang_dnode_get_ipv6(&cli_seg, dnode, "seg"); + if (inet_ntop(AF_INET6, &cli_seg, buffer, + INET6_ADDRSTRLEN) == NULL) { + return 1; + } + vty_out(iter->vty, " segments %s", buffer); + } else { + yang_dnode_get_ipv6(&cli_seg, dnode, "seg"); + if (inet_ntop(AF_INET6, &cli_seg, buffer, + INET6_ADDRSTRLEN) == NULL) { + return 1; + } + vty_out(iter->vty, "/%s", buffer); + } + iter->first = false; + } + + return YANG_ITER_CONTINUE; +} + +static void nexthop_cli_show(struct vty *vty, const struct lyd_node *route, + const struct lyd_node *src, + const struct lyd_node *path, + const struct lyd_node *nexthop, bool show_defaults) +{ + const char *vrf; + const char *afi_safi; + afi_t afi; + safi_t safi; + enum static_nh_type nh_type; + enum static_blackhole_type bh_type; + uint32_t tag; + uint8_t distance; + struct mpls_label_iter iter; + struct srv6_seg_iter seg_iter; + const char *nexthop_vrf; + uint32_t table_id; + bool onlink; + + vrf = yang_dnode_get_string(route, "../../vrf"); + + afi_safi = yang_dnode_get_string(route, "afi-safi"); + yang_afi_safi_identity2value(afi_safi, &afi, &safi); + + if (afi == AFI_IP) + vty_out(vty, "%sip", + strmatch(vrf, VRF_DEFAULT_NAME) ? "" : " "); + else + vty_out(vty, "%sipv6", + strmatch(vrf, VRF_DEFAULT_NAME) ? "" : " "); + + if (safi == SAFI_UNICAST) + vty_out(vty, " route"); + else + vty_out(vty, " mroute"); + + vty_out(vty, " %s", yang_dnode_get_string(route, "prefix")); + + if (src) + vty_out(vty, " from %s", + yang_dnode_get_string(src, "src-prefix")); + + nh_type = yang_dnode_get_enum(nexthop, "nh-type"); + switch (nh_type) { + case STATIC_IFNAME: + vty_out(vty, " %s", + yang_dnode_get_string(nexthop, "interface")); + break; + case STATIC_IPV4_GATEWAY: + case STATIC_IPV6_GATEWAY: + vty_out(vty, " %s", + yang_dnode_get_string(nexthop, "gateway")); + break; + case STATIC_IPV4_GATEWAY_IFNAME: + case STATIC_IPV6_GATEWAY_IFNAME: + vty_out(vty, " %s", + yang_dnode_get_string(nexthop, "gateway")); + vty_out(vty, " %s", + yang_dnode_get_string(nexthop, "interface")); + break; + case STATIC_BLACKHOLE: + bh_type = yang_dnode_get_enum(nexthop, "bh-type"); + switch (bh_type) { + case STATIC_BLACKHOLE_DROP: + vty_out(vty, " blackhole"); + break; + case STATIC_BLACKHOLE_NULL: + vty_out(vty, " Null0"); + break; + case STATIC_BLACKHOLE_REJECT: + vty_out(vty, " reject"); + break; + } + break; + } + + if (yang_dnode_exists(path, "tag")) { + tag = yang_dnode_get_uint32(path, "tag"); + if (tag != 0 || show_defaults) + vty_out(vty, " tag %" PRIu32, tag); + } + + distance = yang_dnode_get_uint8(path, "distance"); + if (distance != ZEBRA_STATIC_DISTANCE_DEFAULT || show_defaults) + vty_out(vty, " %" PRIu8, distance); + + iter.vty = vty; + iter.first = true; + yang_dnode_iterate(mpls_label_iter_cb, &iter, nexthop, + "./mpls-label-stack/entry"); + + seg_iter.vty = vty; + seg_iter.first = true; + yang_dnode_iterate(srv6_seg_iter_cb, &seg_iter, nexthop, + "./srv6-segs-stack/entry"); + + nexthop_vrf = yang_dnode_get_string(nexthop, "vrf"); + if (strcmp(vrf, nexthop_vrf)) + vty_out(vty, " nexthop-vrf %s", nexthop_vrf); + + table_id = yang_dnode_get_uint32(path, "table-id"); + if (table_id || show_defaults) + vty_out(vty, " table %" PRIu32, table_id); + + if (yang_dnode_exists(nexthop, "onlink")) { + onlink = yang_dnode_get_bool(nexthop, "onlink"); + if (onlink) + vty_out(vty, " onlink"); + } + + if (yang_dnode_exists(nexthop, "srte-color")) + vty_out(vty, " color %s", + yang_dnode_get_string(nexthop, "srte-color")); + + if (yang_dnode_exists(nexthop, "bfd-monitoring")) { + const struct lyd_node *bfd_dnode = + yang_dnode_get(nexthop, "bfd-monitoring"); + + if (yang_dnode_get_bool(bfd_dnode, "multi-hop")) { + vty_out(vty, " bfd multi-hop"); + + if (yang_dnode_exists(bfd_dnode, "source")) + vty_out(vty, " source %s", + yang_dnode_get_string(bfd_dnode, + "./source")); + } else + vty_out(vty, " bfd"); + + if (yang_dnode_exists(bfd_dnode, "profile")) + vty_out(vty, " profile %s", + yang_dnode_get_string(bfd_dnode, "profile")); + } + + vty_out(vty, "\n"); +} + +static void static_nexthop_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const struct lyd_node *path = yang_dnode_get_parent(dnode, "path-list"); + const struct lyd_node *route = + yang_dnode_get_parent(path, "route-list"); + + nexthop_cli_show(vty, route, NULL, path, dnode, show_defaults); +} + +static void static_src_nexthop_cli_show(struct vty *vty, + const struct lyd_node *dnode, + bool show_defaults) +{ + const struct lyd_node *path = yang_dnode_get_parent(dnode, "path-list"); + const struct lyd_node *src = yang_dnode_get_parent(path, "src-list"); + const struct lyd_node *route = yang_dnode_get_parent(src, "route-list"); + + nexthop_cli_show(vty, route, src, path, dnode, show_defaults); +} + +static int static_nexthop_cli_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + enum static_nh_type nh_type1, nh_type2; + struct prefix prefix1, prefix2; + const char *vrf1, *vrf2; + int ret = 0; + + nh_type1 = yang_dnode_get_enum(dnode1, "nh-type"); + nh_type2 = yang_dnode_get_enum(dnode2, "nh-type"); + + if (nh_type1 != nh_type2) + return (int)nh_type1 - (int)nh_type2; + + switch (nh_type1) { + case STATIC_IFNAME: + ret = if_cmp_name_func( + yang_dnode_get_string(dnode1, "interface"), + yang_dnode_get_string(dnode2, "interface")); + break; + case STATIC_IPV4_GATEWAY: + case STATIC_IPV6_GATEWAY: + yang_dnode_get_prefix(&prefix1, dnode1, "gateway"); + yang_dnode_get_prefix(&prefix2, dnode2, "gateway"); + ret = prefix_cmp(&prefix1, &prefix2); + break; + case STATIC_IPV4_GATEWAY_IFNAME: + case STATIC_IPV6_GATEWAY_IFNAME: + yang_dnode_get_prefix(&prefix1, dnode1, "gateway"); + yang_dnode_get_prefix(&prefix2, dnode2, "gateway"); + ret = prefix_cmp(&prefix1, &prefix2); + if (!ret) + ret = if_cmp_name_func( + yang_dnode_get_string(dnode1, "interface"), + yang_dnode_get_string(dnode2, "interface")); + break; + case STATIC_BLACKHOLE: + /* There's only one blackhole nexthop per route */ + ret = 0; + break; + } + + if (ret) + return ret; + + vrf1 = yang_dnode_get_string(dnode1, "vrf"); + if (strmatch(vrf1, "default")) + vrf1 = ""; + vrf2 = yang_dnode_get_string(dnode2, "vrf"); + if (strmatch(vrf2, "default")) + vrf2 = ""; + + return if_cmp_name_func(vrf1, vrf2); +} + +static int static_route_list_cli_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + const char *afi_safi1, *afi_safi2; + afi_t afi1, afi2; + safi_t safi1, safi2; + struct prefix prefix1, prefix2; + + afi_safi1 = yang_dnode_get_string(dnode1, "afi-safi"); + yang_afi_safi_identity2value(afi_safi1, &afi1, &safi1); + + afi_safi2 = yang_dnode_get_string(dnode2, "afi-safi"); + yang_afi_safi_identity2value(afi_safi2, &afi2, &safi2); + + if (afi1 != afi2) + return (int)afi1 - (int)afi2; + + if (safi1 != safi2) + return (int)safi1 - (int)safi2; + + yang_dnode_get_prefix(&prefix1, dnode1, "prefix"); + yang_dnode_get_prefix(&prefix2, dnode2, "prefix"); + + return prefix_cmp(&prefix1, &prefix2); +} + +static int static_src_list_cli_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + struct prefix prefix1, prefix2; + + yang_dnode_get_prefix(&prefix1, dnode1, "src-prefix"); + yang_dnode_get_prefix(&prefix2, dnode2, "src-prefix"); + + return prefix_cmp(&prefix1, &prefix2); +} + +static int static_path_list_cli_cmp(const struct lyd_node *dnode1, + const struct lyd_node *dnode2) +{ + uint32_t table_id1, table_id2; + uint8_t distance1, distance2; + + table_id1 = yang_dnode_get_uint32(dnode1, "table-id"); + table_id2 = yang_dnode_get_uint32(dnode2, "table-id"); + + if (table_id1 != table_id2) + return (int)table_id1 - (int)table_id2; + + distance1 = yang_dnode_get_uint8(dnode1, "distance"); + distance2 = yang_dnode_get_uint8(dnode2, "distance"); + + return (int)distance1 - (int)distance2; +} + +const struct frr_yang_module_info frr_staticd_cli_info = { + .name = "frr-staticd", + .ignore_cfg_cbs = true, + .nodes = { + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd", + .cbs = { + .cli_show = static_cli_show, + .cli_show_end = static_cli_show_end, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list", + .cbs = { + .cli_cmp = static_route_list_cli_cmp, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list", + .cbs = { + .cli_cmp = static_path_list_cli_cmp, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop", + .cbs = { + .cli_show = static_nexthop_cli_show, + .cli_cmp = static_nexthop_cli_cmp, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list", + .cbs = { + .cli_cmp = static_src_list_cli_cmp, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list", + .cbs = { + .cli_cmp = static_path_list_cli_cmp, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop", + .cbs = { + .cli_show = static_src_nexthop_cli_show, + .cli_cmp = static_nexthop_cli_cmp, + } + }, + { + .xpath = NULL, + }, + } +}; + +#else /* ifdef INCLUDE_MGMTD_CMDDEFS_ONLY */ + +DEFPY_YANG(debug_staticd, debug_staticd_cmd, + "[no] debug static [{events$events|route$route|bfd$bfd}]", + NO_STR DEBUG_STR STATICD_STR + "Debug events\n" + "Debug route\n" + "Debug bfd\n") +{ + /* If no specific category, change all */ + if (strmatch(argv[argc - 1]->text, "static")) + static_debug_set(vty->node, !no, true, true, true); + else + static_debug_set(vty->node, !no, !!events, !!route, !!bfd); + + return CMD_SUCCESS; +} + +DEFPY(staticd_show_bfd_routes, staticd_show_bfd_routes_cmd, + "show bfd static route [json]$isjson", + SHOW_STR + BFD_INTEGRATION_STR + STATICD_STR + ROUTE_STR + JSON_STR) +{ + static_bfd_show(vty, !!isjson); + return CMD_SUCCESS; +} + +DEFUN_NOSH (show_debugging_static, + show_debugging_static_cmd, + "show debugging [static]", + SHOW_STR + DEBUG_STR + "Static Information\n") +{ + vty_out(vty, "Staticd debugging status\n"); + + static_debug_status_write(vty); + + cmd_show_lib_debugs(vty); + + return CMD_SUCCESS; +} + +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = static_config_write_debug, +}; + +#endif /* ifndef INCLUDE_MGMTD_CMDDEFS_ONLY */ + +void static_vty_init(void) +{ +#ifndef INCLUDE_MGMTD_CMDDEFS_ONLY + install_node(&debug_node); + install_element(ENABLE_NODE, &debug_staticd_cmd); + install_element(CONFIG_NODE, &debug_staticd_cmd); + install_element(ENABLE_NODE, &show_debugging_static_cmd); + install_element(ENABLE_NODE, &staticd_show_bfd_routes_cmd); +#else /* else INCLUDE_MGMTD_CMDDEFS_ONLY */ + install_element(CONFIG_NODE, &ip_mroute_dist_cmd); + + install_element(CONFIG_NODE, &ip_route_blackhole_cmd); + install_element(VRF_NODE, &ip_route_blackhole_vrf_cmd); + install_element(CONFIG_NODE, &ip_route_address_interface_cmd); + install_element(VRF_NODE, &ip_route_address_interface_vrf_cmd); + install_element(CONFIG_NODE, &ip_route_cmd); + install_element(VRF_NODE, &ip_route_vrf_cmd); + + install_element(CONFIG_NODE, &ipv6_route_blackhole_cmd); + install_element(VRF_NODE, &ipv6_route_blackhole_vrf_cmd); + install_element(CONFIG_NODE, &ipv6_route_address_interface_cmd); + install_element(VRF_NODE, &ipv6_route_address_interface_vrf_cmd); + install_element(CONFIG_NODE, &ipv6_route_cmd); + install_element(VRF_NODE, &ipv6_route_vrf_cmd); +#endif /* ifndef INCLUDE_MGMTD_CMDDEFS_ONLY */ + +#ifndef INCLUDE_MGMTD_CMDDEFS_ONLY + mgmt_be_client_lib_vty_init(); +#endif +} diff --git a/staticd/static_vty.h b/staticd/static_vty.h new file mode 100644 index 0000000..4b4cc1c --- /dev/null +++ b/staticd/static_vty.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STATICd - vty header + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __STATIC_VTY_H__ +#define __STATIC_VTY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +void static_vty_init(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/static_zebra.c b/staticd/static_zebra.c new file mode 100644 index 0000000..420ed79 --- /dev/null +++ b/staticd/static_zebra.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect code. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#include + +#include "frrevent.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "routemap.h" +#include "table.h" +#include "srcdest_table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "filter.h" +#include "plist.h" +#include "log.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "hash.h" +#include "jhash.h" + +#include "static_vrf.h" +#include "static_routes.h" +#include "static_zebra.h" +#include "static_nht.h" +#include "static_vty.h" +#include "static_debug.h" + +DEFINE_MTYPE_STATIC(STATIC, STATIC_NHT_DATA, "Static Nexthop tracking data"); +PREDECL_HASH(static_nht_hash); + +struct static_nht_data { + struct static_nht_hash_item itm; + + struct prefix nh; + safi_t safi; + + vrf_id_t nh_vrf_id; + + uint32_t refcount; + uint8_t nh_num; + bool registered; +}; + +static int static_nht_data_cmp(const struct static_nht_data *nhtd1, + const struct static_nht_data *nhtd2) +{ + if (nhtd1->nh_vrf_id != nhtd2->nh_vrf_id) + return numcmp(nhtd1->nh_vrf_id, nhtd2->nh_vrf_id); + if (nhtd1->safi != nhtd2->safi) + return numcmp(nhtd1->safi, nhtd2->safi); + + return prefix_cmp(&nhtd1->nh, &nhtd2->nh); +} + +static unsigned int static_nht_data_hash(const struct static_nht_data *nhtd) +{ + unsigned int key = 0; + + key = prefix_hash_key(&nhtd->nh); + return jhash_2words(nhtd->nh_vrf_id, nhtd->safi, key); +} + +DECLARE_HASH(static_nht_hash, struct static_nht_data, itm, static_nht_data_cmp, + static_nht_data_hash); + +static struct static_nht_hash_head static_nht_hash[1]; + +/* Zebra structure to hold current status. */ +struct zclient *zclient; +uint32_t zebra_ecmp_count = MULTIPATH_NUM; + +/* Interface addition message from zebra. */ +static int static_ifp_create(struct interface *ifp) +{ + static_ifindex_update(ifp, true); + + return 0; +} + +static int static_ifp_destroy(struct interface *ifp) +{ + static_ifindex_update(ifp, false); + return 0; +} + +static int interface_address_add(ZAPI_CALLBACK_ARGS) +{ + zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + return 0; +} + +static int interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (!c) + return 0; + + connected_free(&c); + return 0; +} + +static int static_ifp_up(struct interface *ifp) +{ + static_ifindex_update(ifp, true); + + return 0; +} + +static int static_ifp_down(struct interface *ifp) +{ + static_ifindex_update(ifp, false); + + return 0; +} + +static int route_notify_owner(ZAPI_CALLBACK_ARGS) +{ + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table_id; + safi_t safi; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e, NULL, + &safi)) + return -1; + + switch (note) { + case ZAPI_ROUTE_FAIL_INSTALL: + static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); + zlog_warn("%s: Route %pFX failed to install for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_BETTER_ADMIN_WON: + static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); + zlog_warn( + "%s: Route %pFX over-ridden by better route for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_INSTALLED: + static_nht_mark_state(&p, safi, vrf_id, STATIC_INSTALLED); + break; + case ZAPI_ROUTE_REMOVED: + static_nht_mark_state(&p, safi, vrf_id, STATIC_NOT_INSTALLED); + break; + case ZAPI_ROUTE_REMOVE_FAIL: + static_nht_mark_state(&p, safi, vrf_id, STATIC_INSTALLED); + zlog_warn("%s: Route %pFX failure to remove for table: %u", + __func__, &p, table_id); + break; + } + + return 0; +} + +static void zebra_connected(struct zclient *zclient) +{ + struct vrf *vrf; + + zebra_route_notify_send(ZEBRA_ROUTE_NOTIFY_REQUEST, zclient, true); + zclient_send_reg_requests(zclient, VRF_DEFAULT); + + vrf = vrf_lookup_by_id(VRF_DEFAULT); + assert(vrf); + static_fixup_vrf_ids(vrf); +} + +/* API to check whether the configured nexthop address is + * one of its local connected address or not. + */ +static bool +static_nexthop_is_local(vrf_id_t vrfid, struct prefix *addr, int family) +{ + if (family == AF_INET) { + if (if_address_is_local(&addr->u.prefix4, AF_INET, vrfid)) + return true; + } else if (family == AF_INET6) { + if (if_address_is_local(&addr->u.prefix6, AF_INET6, vrfid)) + return true; + } + return false; +} + +static void static_zebra_nexthop_update(struct vrf *vrf, struct prefix *matched, + struct zapi_route *nhr) +{ + struct static_nht_data *nhtd, lookup; + afi_t afi = AFI_IP; + + if (zclient->bfd_integration) + bfd_nht_update(matched, nhr); + + if (matched->family == AF_INET6) + afi = AFI_IP6; + + if (nhr->type == ZEBRA_ROUTE_CONNECT) { + if (static_nexthop_is_local(vrf->vrf_id, matched, + nhr->prefix.family)) + nhr->nexthop_num = 0; + } + + memset(&lookup, 0, sizeof(lookup)); + lookup.nh = *matched; + lookup.nh_vrf_id = vrf->vrf_id; + lookup.safi = nhr->safi; + + nhtd = static_nht_hash_find(static_nht_hash, &lookup); + + if (nhtd) { + nhtd->nh_num = nhr->nexthop_num; + + static_nht_reset_start(matched, afi, nhr->safi, nhtd->nh_vrf_id); + static_nht_update(NULL, matched, nhr->nexthop_num, afi, + nhr->safi, nhtd->nh_vrf_id); + } else + zlog_err("No nhtd?"); +} + +static void static_zebra_capabilities(struct zclient_capabilities *cap) +{ + mpls_enabled = cap->mpls_enabled; + zebra_ecmp_count = cap->ecmp; +} + +static struct static_nht_data * +static_nht_hash_getref(const struct static_nht_data *ref) +{ + struct static_nht_data *nhtd; + + nhtd = static_nht_hash_find(static_nht_hash, ref); + if (!nhtd) { + nhtd = XCALLOC(MTYPE_STATIC_NHT_DATA, sizeof(*nhtd)); + + prefix_copy(&nhtd->nh, &ref->nh); + nhtd->nh_vrf_id = ref->nh_vrf_id; + nhtd->safi = ref->safi; + + static_nht_hash_add(static_nht_hash, nhtd); + } + + nhtd->refcount++; + return nhtd; +} + +static bool static_nht_hash_decref(struct static_nht_data **nhtd_p) +{ + struct static_nht_data *nhtd = *nhtd_p; + + *nhtd_p = NULL; + + if (--nhtd->refcount > 0) + return true; + + static_nht_hash_del(static_nht_hash, nhtd); + XFREE(MTYPE_STATIC_NHT_DATA, nhtd); + return false; +} + +static void static_nht_hash_clear(void) +{ + struct static_nht_data *nhtd; + + while ((nhtd = static_nht_hash_pop(static_nht_hash))) + XFREE(MTYPE_STATIC_NHT_DATA, nhtd); +} + +static bool static_zebra_nht_get_prefix(const struct static_nexthop *nh, + struct prefix *p) +{ + switch (nh->type) { + case STATIC_IFNAME: + case STATIC_BLACKHOLE: + p->family = AF_UNSPEC; + return false; + + case STATIC_IPV4_GATEWAY: + case STATIC_IPV4_GATEWAY_IFNAME: + p->family = AF_INET; + p->prefixlen = IPV4_MAX_BITLEN; + p->u.prefix4 = nh->addr.ipv4; + return true; + + case STATIC_IPV6_GATEWAY: + case STATIC_IPV6_GATEWAY_IFNAME: + p->family = AF_INET6; + p->prefixlen = IPV6_MAX_BITLEN; + p->u.prefix6 = nh->addr.ipv6; + return true; + } + + assertf(0, "BUG: someone forgot to add nexthop type %u", nh->type); + return false; +} + +void static_zebra_nht_register(struct static_nexthop *nh, bool reg) +{ + struct static_path *pn = nh->pn; + struct route_node *rn = pn->rn; + struct static_route_info *si = static_route_info_from_rnode(rn); + struct static_nht_data *nhtd, lookup = {}; + uint32_t cmd; + + if (!static_zebra_nht_get_prefix(nh, &lookup.nh)) + return; + lookup.nh_vrf_id = nh->nh_vrf_id; + lookup.safi = si->safi; + + if (nh->nh_registered) { + /* nh->nh_registered means we own a reference on the nhtd */ + nhtd = static_nht_hash_find(static_nht_hash, &lookup); + + assertf(nhtd, "BUG: NH %pFX registered but not in hashtable", + &lookup.nh); + } else if (reg) { + nhtd = static_nht_hash_getref(&lookup); + + if (nhtd->refcount > 1) + DEBUGD(&static_dbg_route, + "Reusing registered nexthop(%pFX) for %pRN %d", + &lookup.nh, rn, nhtd->nh_num); + } else { + /* !reg && !nh->nh_registered */ + zlog_warn("trying to unregister nexthop %pFX twice", + &lookup.nh); + return; + } + + nh->nh_registered = reg; + + if (reg) { + if (nhtd->nh_num) { + /* refresh with existing data */ + afi_t afi = prefix_afi(&lookup.nh); + + if (nh->state == STATIC_NOT_INSTALLED || + nh->state == STATIC_SENT_TO_ZEBRA) + nh->state = STATIC_START; + static_nht_update(&rn->p, &nhtd->nh, nhtd->nh_num, afi, + si->safi, nh->nh_vrf_id); + return; + } + + if (nhtd->registered) + /* have no data, but did send register */ + return; + + cmd = ZEBRA_NEXTHOP_REGISTER; + DEBUGD(&static_dbg_route, "Registering nexthop(%pFX) for %pRN", + &lookup.nh, rn); + } else { + bool was_zebra_registered; + + was_zebra_registered = nhtd->registered; + if (static_nht_hash_decref(&nhtd)) + /* still got references alive */ + return; + + /* NB: nhtd is now NULL. */ + if (!was_zebra_registered) + return; + + cmd = ZEBRA_NEXTHOP_UNREGISTER; + DEBUGD(&static_dbg_route, + "Unregistering nexthop(%pFX) for %pRN", &lookup.nh, rn); + } + + if (zclient_send_rnh(zclient, cmd, &lookup.nh, si->safi, false, false, + nh->nh_vrf_id) == ZCLIENT_SEND_FAILURE) + zlog_warn("%s: Failure to send nexthop %pFX for %pRN to zebra", + __func__, &lookup.nh, rn); + else if (reg) + nhtd->registered = true; +} + +extern void static_zebra_route_add(struct static_path *pn, bool install) +{ + struct route_node *rn = pn->rn; + struct static_route_info *si = rn->info; + struct static_nexthop *nh; + const struct prefix *p, *src_pp; + struct zapi_nexthop *api_nh; + struct zapi_route api; + uint32_t nh_num = 0; + + if (!si->svrf->vrf || si->svrf->vrf->vrf_id == VRF_UNKNOWN) + return; + + p = src_pp = NULL; + srcdest_rnode_prefixes(rn, &p, &src_pp); + + memset(&api, 0, sizeof(api)); + api.vrf_id = si->svrf->vrf->vrf_id; + api.type = ZEBRA_ROUTE_STATIC; + api.safi = si->safi; + memcpy(&api.prefix, p, sizeof(api.prefix)); + + if (src_pp) { + SET_FLAG(api.message, ZAPI_MESSAGE_SRCPFX); + memcpy(&api.src_prefix, src_pp, sizeof(api.src_prefix)); + } + SET_FLAG(api.flags, ZEBRA_FLAG_RR_USE_DISTANCE); + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + if (pn->distance) { + SET_FLAG(api.message, ZAPI_MESSAGE_DISTANCE); + api.distance = pn->distance; + } + if (pn->tag) { + SET_FLAG(api.message, ZAPI_MESSAGE_TAG); + api.tag = pn->tag; + } + if (pn->table_id != 0) { + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + api.tableid = pn->table_id; + } + frr_each(static_nexthop_list, &pn->nexthop_list, nh) { + /* Don't overrun the nexthop array */ + if (nh_num == zebra_ecmp_count) + break; + + api_nh = &api.nexthops[nh_num]; + if (nh->nh_vrf_id == VRF_UNKNOWN) + continue; + /* Skip next hop which peer is down. */ + if (nh->path_down) + continue; + + api_nh->vrf_id = nh->nh_vrf_id; + if (nh->onlink) + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_ONLINK); + if (nh->color != 0) { + SET_FLAG(api.message, ZAPI_MESSAGE_SRTE); + api_nh->srte_color = nh->color; + } + + nh->state = STATIC_SENT_TO_ZEBRA; + + switch (nh->type) { + case STATIC_IFNAME: + if (nh->ifindex == IFINDEX_INTERNAL) + continue; + api_nh->ifindex = nh->ifindex; + api_nh->type = NEXTHOP_TYPE_IFINDEX; + break; + case STATIC_IPV4_GATEWAY: + if (!nh->nh_valid) + continue; + api_nh->type = NEXTHOP_TYPE_IPV4; + api_nh->gate = nh->addr; + break; + case STATIC_IPV4_GATEWAY_IFNAME: + if (nh->ifindex == IFINDEX_INTERNAL) + continue; + api_nh->ifindex = nh->ifindex; + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + api_nh->gate = nh->addr; + break; + case STATIC_IPV6_GATEWAY: + if (!nh->nh_valid) + continue; + api_nh->type = NEXTHOP_TYPE_IPV6; + api_nh->gate = nh->addr; + break; + case STATIC_IPV6_GATEWAY_IFNAME: + if (nh->ifindex == IFINDEX_INTERNAL) + continue; + api_nh->type = NEXTHOP_TYPE_IPV6_IFINDEX; + api_nh->ifindex = nh->ifindex; + api_nh->gate = nh->addr; + break; + case STATIC_BLACKHOLE: + api_nh->type = NEXTHOP_TYPE_BLACKHOLE; + switch (nh->bh_type) { + case STATIC_BLACKHOLE_DROP: + case STATIC_BLACKHOLE_NULL: + api_nh->bh_type = BLACKHOLE_NULL; + break; + case STATIC_BLACKHOLE_REJECT: + api_nh->bh_type = BLACKHOLE_REJECT; + } + break; + } + + if (nh->snh_label.num_labels) { + int i; + + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL); + api_nh->label_num = nh->snh_label.num_labels; + for (i = 0; i < api_nh->label_num; i++) + api_nh->labels[i] = nh->snh_label.label[i]; + } + if (nh->snh_seg.num_segs) { + int i; + + api_nh->seg6local_action = + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + api.safi = SAFI_UNICAST; + + api_nh->seg_num = nh->snh_seg.num_segs; + for (i = 0; i < api_nh->seg_num; i++) + memcpy(&api_nh->seg6_segs[i], + &nh->snh_seg.seg[i], + sizeof(struct in6_addr)); + } + nh_num++; + } + + api.nexthop_num = nh_num; + + /* + * If we have been given an install but nothing is valid + * go ahead and delete the route for double plus fun + */ + if (!nh_num && install) + install = false; + + zclient_route_send(install ? + ZEBRA_ROUTE_ADD : ZEBRA_ROUTE_DELETE, + zclient, &api); +} + +static zclient_handler *const static_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = interface_address_delete, + [ZEBRA_ROUTE_NOTIFY_OWNER] = route_notify_owner, +}; + +void static_zebra_init(void) +{ + hook_register_prio(if_real, 0, static_ifp_create); + hook_register_prio(if_up, 0, static_ifp_up); + hook_register_prio(if_down, 0, static_ifp_down); + hook_register_prio(if_unreal, 0, static_ifp_destroy); + + zclient = zclient_new(master, &zclient_options_default, static_handlers, + array_size(static_handlers)); + + zclient_init(zclient, ZEBRA_ROUTE_STATIC, 0, &static_privs); + zclient->zebra_capabilities = static_zebra_capabilities; + zclient->zebra_connected = zebra_connected; + zclient->nexthop_update = static_zebra_nexthop_update; + + static_nht_hash_init(static_nht_hash); + static_bfd_initialize(zclient, master); +} + +/* static_zebra_stop used by tests/lib/test_grpc.cpp */ +void static_zebra_stop(void) +{ + static_nht_hash_clear(); + static_nht_hash_fini(static_nht_hash); + + if (!zclient) + return; + zclient_stop(zclient); + zclient_free(zclient); + zclient = NULL; +} + +void static_zebra_vrf_register(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + zclient_send_reg_requests(zclient, vrf->vrf_id); +} + +void static_zebra_vrf_unregister(struct vrf *vrf) +{ + if (vrf->vrf_id == VRF_DEFAULT) + return; + zclient_send_dereg_requests(zclient, vrf->vrf_id); +} diff --git a/staticd/static_zebra.h b/staticd/static_zebra.h new file mode 100644 index 0000000..c4f4ebd --- /dev/null +++ b/staticd/static_zebra.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zebra connect library for staticd + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + */ +#ifndef __STATIC_ZEBRA_H__ +#define __STATIC_ZEBRA_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct event_loop *master; + +extern void static_zebra_nht_register(struct static_nexthop *nh, bool reg); + +extern void static_zebra_route_add(struct static_path *pn, bool install); +extern void static_zebra_init(void); +/* static_zebra_stop used by tests/lib/test_grpc.cpp */ +extern void static_zebra_stop(void); +extern void static_zebra_vrf_register(struct vrf *vrf); +extern void static_zebra_vrf_unregister(struct vrf *vrf); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/staticd/subdir.am b/staticd/subdir.am new file mode 100644 index 0000000..07ebe3c --- /dev/null +++ b/staticd/subdir.am @@ -0,0 +1,44 @@ +# +# staticd +# + +if STATICD +noinst_LIBRARIES += staticd/libstatic.a +sbin_PROGRAMS += staticd/staticd +vtysh_daemons += staticd +man8 += $(MANBUILD)/frr-staticd.8 +endif + +staticd_libstatic_a_SOURCES = \ + staticd/static_bfd.c \ + staticd/static_debug.c \ + staticd/static_nht.c \ + staticd/static_routes.c \ + staticd/static_zebra.c \ + staticd/static_vrf.c \ + staticd/static_vty.c \ + staticd/static_nb.c \ + staticd/static_nb_config.c \ + # end + +noinst_HEADERS += \ + staticd/static_debug.h \ + staticd/static_nht.h \ + staticd/static_zebra.h \ + staticd/static_routes.h \ + staticd/static_vty.h \ + staticd/static_vrf.h \ + staticd/static_nb.h \ + # end + +clippy_scan += \ + staticd/static_vty.c \ + # end + +staticd_staticd_SOURCES = staticd/static_main.c +staticd_staticd_LDADD = staticd/libstatic.a lib/libfrr.la $(LIBCAP) $(LIBYANG_LIBS) + +nodist_staticd_staticd_SOURCES = \ + yang/frr-bfdd.yang.c \ + yang/frr-staticd.yang.c \ + # end diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..681438f --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,67 @@ +*.log +*.sum +*.xml +frr-northbound.proto +frr_northbound* +.pytest_cache +/bgpd/test_aspath +/bgpd/test_bgp_table +/bgpd/test_capability +/bgpd/test_ecommunity +/bgpd/test_mp_attr +/bgpd/test_mpath +/bgpd/test_packet +/bgpd/test_peer_attr +/isisd/test_fuzz_isis_tlv +/isisd/test_fuzz_isis_tlv_tests.h +/isisd/test_isis_lspdb +/isisd/test_isis_spf +/isisd/test_isis_vertex_queue +/lib/cli/test_cli +/lib/cli/test_cli_clippy.c +/lib/cli/test_commands +/lib/cli/test_commands_defun.c +/lib/northbound/test_oper_data +/lib/cxxcompat +/lib/fuzz_zlog +/lib/test_assert +/lib/test_atomlist +/lib/test_buffer +/lib/test_checksum +/lib/test_frrscript +/lib/test_darr +/lib/test_frrlua +/lib/test_graph +/lib/test_grpc +/lib/test_heavy +/lib/test_heavy_thread +/lib/test_heavy_wq +/lib/test_idalloc +/lib/test_memory +/lib/test_nexthop +/lib/test_nexthop_iter +/lib/test_ntop +/lib/test_plist +/lib/test_prefix2str +/lib/test_printfrr +/lib/test_privs +/lib/test_resolver +/lib/test_ringbuf +/lib/test_segv +/lib/test_seqlock +/lib/test_sig +/lib/test_skiplist +/lib/test_srcdest_table +/lib/test_stream +/lib/test_table +/lib/test_timer_correctness +/lib/test_timer_performance +/lib/test_ttable +/lib/test_typelist +/lib/test_versioncmp +/lib/test_xref +/lib/test_zlog +/lib/test_zmq +/ospf6d/test_lsdb +/ospf6d/test_lsdb_clippy.c +/zebra/test_lm_plugin diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..dd4594f --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. check +%: ALWAYS + @$(MAKE) -s -C .. tests/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/tests/bgpd/subdir.am b/tests/bgpd/subdir.am new file mode 100644 index 0000000..5148e7e --- /dev/null +++ b/tests/bgpd/subdir.am @@ -0,0 +1,82 @@ +if !BGPD +PYTEST_IGNORE += --ignore=bgpd/ +endif +BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) $(UST_LIBS) -lm + + +if BGPD +check_PROGRAMS += tests/bgpd/test_aspath +endif +tests_bgpd_test_aspath_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_aspath_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_aspath_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_aspath_SOURCES = tests/bgpd/test_aspath.c +EXTRA_DIST += tests/bgpd/test_aspath.py + + +if BGPD +check_PROGRAMS += tests/bgpd/test_bgp_table +endif +tests_bgpd_test_bgp_table_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_bgp_table_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_bgp_table_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_bgp_table_SOURCES = tests/bgpd/test_bgp_table.c + + +if BGPD +check_PROGRAMS += tests/bgpd/test_capability +endif +tests_bgpd_test_capability_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_capability_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_capability_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_capability_SOURCES = tests/bgpd/test_capability.c +EXTRA_DIST += tests/bgpd/test_capability.py + + +if BGPD +check_PROGRAMS += tests/bgpd/test_ecommunity +endif +tests_bgpd_test_ecommunity_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_ecommunity_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_ecommunity_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_ecommunity_SOURCES = tests/bgpd/test_ecommunity.c +EXTRA_DIST += tests/bgpd/test_ecommunity.py + + +if BGPD +check_PROGRAMS += tests/bgpd/test_mp_attr +endif +tests_bgpd_test_mp_attr_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_mp_attr_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_mp_attr_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_mp_attr_SOURCES = tests/bgpd/test_mp_attr.c +EXTRA_DIST += tests/bgpd/test_mp_attr.py + + +if BGPD +check_PROGRAMS += tests/bgpd/test_mpath +endif +tests_bgpd_test_mpath_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_mpath_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_mpath_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_mpath_SOURCES = tests/bgpd/test_mpath.c +EXTRA_DIST += tests/bgpd/test_mpath.py + + +if BGPD +check_PROGRAMS += tests/bgpd/test_packet +endif +tests_bgpd_test_packet_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_packet_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_packet_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_packet_SOURCES = tests/bgpd/test_packet.c + + +if BGPD +check_PROGRAMS += tests/bgpd/test_peer_attr +endif +tests_bgpd_test_peer_attr_CFLAGS = $(TESTS_CFLAGS) +tests_bgpd_test_peer_attr_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_bgpd_test_peer_attr_LDADD = $(BGP_TEST_LDADD) +tests_bgpd_test_peer_attr_SOURCES = tests/bgpd/test_peer_attr.c +EXTRA_DIST += tests/bgpd/test_peer_attr.py diff --git a/tests/bgpd/test_aspath.c b/tests/bgpd/test_aspath.c new file mode 100644 index 0000000..29d2e4c --- /dev/null +++ b/tests/bgpd/test_aspath.c @@ -0,0 +1,1463 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005 Sun Microsystems, Inc. + */ + +#include + +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "queue.h" +#include "filter.h" +#include "frr_pthread.h" + +#include "bgpd/bgpd.c" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_packet.h" + +#define VT100_RESET "\x1b[0m" +#define VT100_RED "\x1b[31m" +#define VT100_GREEN "\x1b[32m" +#define VT100_YELLOW "\x1b[33m" +#define OK VT100_GREEN "OK" VT100_RESET +#define FAILED VT100_RED "failed" VT100_RESET + +/* need these to link in libbgp */ +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master = NULL; + +static int failed = 0; + +/* specification for a test - what the results should be */ +struct test_spec { + const char *shouldbe; /* the string the path should parse to */ + const char *shouldbe_delete_confed; /* ditto, but once confeds are + deleted */ + const unsigned int hops; /* aspath_count_hops result */ + const unsigned int confeds; /* aspath_count_confeds */ + const int private_as; /* whether the private_as check should pass or + fail */ +#define NOT_ALL_PRIVATE 0 +#define ALL_PRIVATE 1 + const as_t does_loop; /* an ASN which should trigger loop-check */ + const as_t doesnt_loop; /* one which should not */ + const as_t first; /* the first ASN, if there is one */ +#define NULL_ASN 0 +}; + + +/* test segments to parse and validate, and use for other tests */ +static struct test_segment { + const char *name; + const char *desc; + const uint8_t asdata[1024]; + int len; + struct test_spec sp; + enum asnotation_mode asnotation; +} test_segments[] = { + { + /* 0 */ + "seq1", + "seq(8466,3,52737,4096)", + {0x2, 0x4, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00}, + 10, + {"8466 3 52737 4096", "8466 3 52737 4096", 4, 0, + NOT_ALL_PRIVATE, 4096, 4, 8466}, + 0, + }, + { + /* 1 */ + "seq2", + "seq(8722) seq(4)", + {0x2, 0x1, 0x22, 0x12, 0x2, 0x1, 0x00, 0x04}, + 8, + { + "8722 4", + "8722 4", + 2, + 0, + NOT_ALL_PRIVATE, + 4, + 5, + 8722, + }, + 0, + }, + { + /* 2 */ + "seq3", + "seq(8466,3,52737,4096,8722,4)", + {0x2, 0x6, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x22, + 0x12, 0x00, 0x04}, + 14, + {"8466 3 52737 4096 8722 4", "8466 3 52737 4096 8722 4", 6, 0, + NOT_ALL_PRIVATE, 3, 5, 8466}, + 0, + }, + { + /* 3 */ + "seqset", + "seq(8482,51457) set(5204)", + {0x2, 0x2, 0x21, 0x22, 0xc9, 0x01, 0x1, 0x1, 0x14, 0x54}, + 10, + {"8482 51457 {5204}", "8482 51457 {5204}", 3, 0, + NOT_ALL_PRIVATE, 5204, 51456, 8482}, + 0, + }, + { + /* 4 */ + "seqset2", + "seq(8467, 59649) set(4196,48658) set(17322,30745)", + {0x2, 0x2, 0x21, 0x13, 0xe9, 0x01, 0x1, 0x2, 0x10, 0x64, 0xbe, + 0x12, 0x1, 0x2, 0x43, 0xaa, 0x78, 0x19}, + 18, + {"8467 59649 {4196,48658} {17322,30745}", + "8467 59649 {4196,48658} {17322,30745}", 4, 0, NOT_ALL_PRIVATE, + 48658, 1, 8467}, + 0, + }, + { + /* 5 */ + "multi", + "seq(6435,59408,21665) set(2457,61697,4369), seq(1842,41590,51793)", + {0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, + 0x1, 0x3, 0x09, 0x99, 0xf1, 0x01, 0x11, 0x11, + 0x2, 0x3, 0x07, 0x32, 0xa2, 0x76, 0xca, 0x51}, + 24, + {"6435 59408 21665 {2457,4369,61697} 1842 41590 51793", + "6435 59408 21665 {2457,4369,61697} 1842 41590 51793", 7, 0, + NOT_ALL_PRIVATE, 51793, 1, 6435}, + 0, + }, + { + /* 6 */ + "confed", + "confseq(123,456,789)", + {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15}, + 8, + {"(123 456 789)", "", 0, 3, NOT_ALL_PRIVATE, 789, 1, NULL_ASN}, + 0, + }, + { + /* 7 */ + "confed2", + "confseq(123,456,789) confseq(111,222)", + {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, 0x3, 0x2, 0x00, + 0x6f, 0x00, 0xde}, + 14, + {"(123 456 789) (111 222)", "", 0, 5, NOT_ALL_PRIVATE, 111, 1, + NULL_ASN}, + 0, + }, + { + /* 8 */ + "confset", + "confset(456,123,789)", + {0x4, 0x3, 0x01, 0xc8, 0x00, 0x7b, 0x03, 0x15}, + 8, + {"[123,456,789]", "", 0, 1, NOT_ALL_PRIVATE, 123, 1, NULL_ASN}, + 0, + }, + { + /* 9 */ + "confmulti", + "confseq(123,456,789) confset(222,111) seq(8722) set(4196,48658)", + {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, + 0x4, 0x2, 0x00, 0xde, 0x00, 0x6f, 0x2, 0x1, + 0x22, 0x12, 0x1, 0x2, 0x10, 0x64, 0xbe, 0x12}, + 24, + {"(123 456 789) [111,222] 8722 {4196,48658}", + "8722 {4196,48658}", 2, 4, NOT_ALL_PRIVATE, 123, 1, NULL_ASN}, + 0, + }, + { + /* 10 */ + "seq4", + "seq(8466,2,52737,4096,8722,4)", + {0x2, 0x6, 0x21, 0x12, 0x00, 0x02, 0xce, 0x01, 0x10, 0x00, 0x22, + 0x12, 0x00, 0x04}, + 14, + {"8466 2 52737 4096 8722 4", "8466 2 52737 4096 8722 4", 6, 0, + NOT_ALL_PRIVATE, 4096, 1, 8466}, + 0, + }, + { + /* 11 */ + "tripleseq1", + "seq(8466,2,52737) seq(4096,8722,4) seq(8722)", + {0x2, 0x3, 0x21, 0x12, 0x00, 0x02, 0xce, 0x01, 0x2, 0x3, + 0x10, 0x00, 0x22, 0x12, 0x00, 0x04, 0x2, 0x1, 0x22, 0x12}, + 20, + {"8466 2 52737 4096 8722 4 8722", + "8466 2 52737 4096 8722 4 8722", 7, 0, NOT_ALL_PRIVATE, 4096, + 1, 8466}, + 0, + }, + { + /* 12 */ + "someprivate", + "seq(8466,64512,52737,65535)", + {0x2, 0x4, 0x21, 0x12, 0xfc, 0x00, 0xce, 0x01, 0xff, 0xff}, + 10, + {"8466 64512 52737 65535", "8466 64512 52737 65535", 4, 0, + NOT_ALL_PRIVATE, 65535, 4, 8466}, + 0, + }, + { + /* 13 */ + "allprivate", + "seq(65534,64512,64513,65535)", + {0x2, 0x4, 0xff, 0xfe, 0xfc, 0x00, 0xfc, 0x01, 0xff, 0xff}, + 10, + {"65534 64512 64513 65535", "65534 64512 64513 65535", 4, 0, + ALL_PRIVATE, 65534, 4, 65534}, + 0, + }, + { + /* 14 */ + "long", + "seq(8466,3,52737,4096,34285,)", + { + 0x2, 0xfa, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, + 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, + 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, + 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, + 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, + 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, + 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, + 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, + 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, + 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, + 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, + 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, + 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, + 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, + 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, + 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, + 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, + 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, + 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, + 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, + 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, + 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, 0xce, + 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, 0x03, + 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, 0x21, 0x12, 0x00, + 0x03, 0xce, 0x01, 0x10, 0x00, 0x85, 0xed, + }, + 502, + {"8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285", + + "8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285", + 250, 0, NOT_ALL_PRIVATE, 4096, 4, 8466}, + 0, + }, + { + /* 15 */ + "seq1extra", + "seq(8466,3,52737,4096,3456)", + {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, + 0x80}, + 12, + {"8466 3 52737 4096 3456", "8466 3 52737 4096 3456", 5, 0, + NOT_ALL_PRIVATE, 4096, 4, 8466}, + 0, + }, + { + /* 16 */ + "empty", + "", + {}, + 0, + {"", "", 0, 0, 0, 0, 0, 0}, + 0, + }, + { + /* 17 */ + "redundantset", + "seq(8466,3,52737,4096,3456) set(7099,8153,8153,8153)", + {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x0d, 0x80, 0x1, 0x4, 0x1b, 0xbb, + 0x1f, 0xd9, 0x1f, 0xd9, 0x1f, 0xd9}, + 22, + {/* We shouldn't ever /generate/ such paths. However, we should + * cope with them fine. + */ + "8466 3 52737 4096 3456 {7099,8153}", + "8466 3 52737 4096 3456 {7099,8153}", 6, 0, NOT_ALL_PRIVATE, + 4096, 4, 8466}, + 0, + }, + { + /* 18 */ + "reconcile_lead_asp", + "seq(6435,59408,21665) set(23456,23456,23456), seq(23456,23456,23456)", + {0x2, 0x3, 0x19, 0x23, 0xe8, 0x10, 0x54, 0xa1, + 0x1, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0, + 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0}, + 24, + {"6435 59408 21665 {23456} 23456 23456 23456", + "6435 59408 21665 {23456} 23456 23456 23456", 7, 0, + NOT_ALL_PRIVATE, 23456, 1, 6435}, + 0, + }, + { + /* 19 */ + "reconcile_new_asp", + "set(2457,61697,4369), seq(1842,41591,51793)", + {0x1, 0x3, 0x09, 0x99, 0xf1, 0x01, 0x11, 0x11, 0x2, 0x3, 0x07, + 0x32, 0xa2, 0x77, 0xca, 0x51}, + 16, + {"{2457,4369,61697} 1842 41591 51793", + "{2457,4369,61697} 1842 41591 51793", 4, 0, NOT_ALL_PRIVATE, + 51793, 1, 2457}, + 0, + }, + { + /* 20 */ + "reconcile_confed", + "confseq(123,456,789) confset(456,124,788) seq(6435,59408,21665) set(23456,23456,23456), seq(23456,23456,23456)", + {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, 0x4, 0x3, + 0x01, 0xc8, 0x00, 0x7c, 0x03, 0x14, 0x2, 0x3, 0x19, 0x23, + 0xe8, 0x10, 0x54, 0xa1, 0x1, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, + 0x5b, 0xa0, 0x2, 0x3, 0x5b, 0xa0, 0x5b, 0xa0, 0x5b, 0xa0}, + 40, + {"(123 456 789) [124,456,788] 6435 59408 21665 {23456} 23456 23456 23456", + "6435 59408 21665 {23456} 23456 23456 23456", 7, 4, + NOT_ALL_PRIVATE, 23456, 1, 6435}, + 0, + }, + { + /* 21 */ + "reconcile_start_trans", + "seq(23456,23456,23456) seq(6435,59408,21665)", + { + 0x2, + 0x3, + 0x5b, + 0xa0, + 0x5b, + 0xa0, + 0x5b, + 0xa0, + 0x2, + 0x3, + 0x19, + 0x23, + 0xe8, + 0x10, + 0x54, + 0xa1, + }, + 16, + {"23456 23456 23456 6435 59408 21665", + "23456 23456 23456 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, + 21665, 1, 23456}, + 0, + }, + { + /* 22 */ + "reconcile_start_trans4", + "seq(1842,41591,51793) seq(6435,59408,21665)", + { + 0x2, + 0x3, + 0x07, + 0x32, + 0xa2, + 0x77, + 0xca, + 0x51, + 0x2, + 0x3, + 0x19, + 0x23, + 0xe8, + 0x10, + 0x54, + 0xa1, + }, + 16, + {"1842 41591 51793 6435 59408 21665", + "1842 41591 51793 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, + 41591, 1, 1842}, + 0, + }, + { + /* 23 */ + "reconcile_start_trans_error", + "seq(23456,23456,23456) seq(6435,59408)", + { + 0x2, + 0x3, + 0x5b, + 0xa0, + 0x5b, + 0xa0, + 0x5b, + 0xa0, + 0x2, + 0x2, + 0x19, + 0x23, + 0xe8, + 0x10, + }, + 14, + {"23456 23456 23456 6435 59408", "23456 23456 23456 6435 59408", + 5, 0, NOT_ALL_PRIVATE, 59408, 1, 23456}, + 0, + }, + { + /* 24 */ + "redundantset2", + "seq(8466,3,52737,4096,3456) set(7099,8153,8153,8153,7099)", + { + 0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, + 0x10, 0x00, 0x0d, 0x80, 0x1, 0x5, 0x1b, 0xbb, + 0x1f, 0xd9, 0x1f, 0xd9, 0x1f, 0xd9, 0x1b, 0xbb, + }, + 24, + {/* We should weed out duplicate set members. */ + "8466 3 52737 4096 3456 {7099,8153}", + "8466 3 52737 4096 3456 {7099,8153}", 6, 0, NOT_ALL_PRIVATE, + 4096, 4, 8466}, + 0, + }, + { + /* 25 */ + "zero-size overflow", + "#ASNs = 0, data = seq(8466 3 52737 4096 3456)", + {0x2, 0x0, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x0d, + 0x80}, + 12, + {NULL, NULL, 0, 0, 0, 0, 0, 0}, + 0, + }, + { + /* 26 */ + "zero-size overflow + valid segment", + "seq(#AS=0:8466 3 52737),seq(4096 3456)", + {0x2, 0x0, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x2, 0x2, 0x10, + 0x00, 0x0d, 0x80}, + 14, + {NULL, NULL, 0, 0, 0, 0, 0, 0}, + 0, + }, + { + /* 27 */ + "invalid segment type", + "type=8(4096 3456)", + {0x8, 0x2, 0x10, 0x00, 0x0d, 0x80}, + 14, + {NULL, NULL, 0, 0, 0, 0, 0, 0}, + 0, + }, + { + /* 28 */ + "BGP_AS_ZERO", + "seq(8466,3,52737,0,4096)", + {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x00, 0x00, 0x10, + 0x00}, + 12, + {"8466 3 52737 0 4096", "8466 3 52737 0 4096", 5, 0, + NOT_ALL_PRIVATE, 4096, 4, 8466}, + 0, + }, + { + /* 29 */ + "seq3_asdot+", + "seq(0.8466,0.3,0.52737,0.4096,0.8722,0.4)", + {0x2, 0x6, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x10, 0x00, 0x22, + 0x12, 0x00, 0x04}, + 14, + {"0.8466 0.3 0.52737 0.4096 0.8722 0.4", + "0.8466 0.3 0.52737 0.4096 0.8722 0.4", 6, 0, NOT_ALL_PRIVATE, + 3, 5, 8466}, + ASNOTATION_DOTPLUS, + }, + { + /* 30 */ + "confmulti_asdot+", + "confseq(0.123,0.456,0.789) confset(0.222,0.111) seq(0.8722) set(0.4196,0.48658)", + {0x3, 0x3, 0x00, 0x7b, 0x01, 0xc8, 0x03, 0x15, + 0x4, 0x2, 0x00, 0xde, 0x00, 0x6f, 0x2, 0x1, + 0x22, 0x12, 0x1, 0x2, 0x10, 0x64, 0xbe, 0x12}, + 24, + {"(0.123 0.456 0.789) [0.111,0.222] 0.8722 {0.4196,0.48658}", + "0.8722 {0.4196,0.48658}", 2, 4, NOT_ALL_PRIVATE, 123, 1, + NULL_ASN}, + ASNOTATION_DOTPLUS, + }, + { + /* 31 */ + "someprivate asdot+", + "seq(0.8466,0.64512,0.52737,0.65535)", + {0x2, 0x4, 0x21, 0x12, 0xfc, 0x00, 0xce, 0x01, 0xff, 0xff}, + 10, + {"0.8466 0.64512 0.52737 0.65535", + "0.8466 0.64512 0.52737 0.65535", 4, 0, NOT_ALL_PRIVATE, 65535, + 4, 8466}, + ASNOTATION_DOTPLUS, + }, + { + /* 32 */ + "BGP_AS_ZERO asdot+", + "seq(0.8466,0.3,0.52737,0.0,0.4096)", + {0x2, 0x5, 0x21, 0x12, 0x00, 0x03, 0xce, 0x01, 0x00, 0x00, 0x10, + 0x00}, + 12, + {"0.8466 0.3 0.52737 0.0 0.4096", + "0.8466 0.3 0.52737 0.0 0.4096", 5, 0, NOT_ALL_PRIVATE, 4096, + 4, 8466}, + ASNOTATION_DOTPLUS, + }, + {NULL, NULL, {0}, 0, {NULL, 0, 0}}}; + +#define COMMON_ATTRS \ + BGP_ATTR_FLAG_TRANS, BGP_ATTR_ORIGIN, 1, BGP_ORIGIN_EGP, \ + BGP_ATTR_FLAG_TRANS, BGP_ATTR_NEXT_HOP, 4, 192, 0, 2, 0 +#define COMMON_ATTR_SIZE 11 + +/* */ +static struct aspath_tests { + const char *desc; + const struct test_segment *segment; + const char *shouldbe; /* String it should evaluate to */ + const enum as4 { + AS4_DATA, + AS2_DATA + } as4; /* whether data should be as4 or not (ie as2) */ + const int result; /* expected result for bgp_attr_parse */ + const int cap; /* capabilities to set for peer */ + const char attrheader[1024]; + size_t len; + const struct test_segment *old_segment; +} aspath_tests[] = { + /* 0 */ + { + "basic test", + &test_segments[0], + "8466 3 52737 4096", + AS2_DATA, + 0, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 10, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 1 */ + { + "length too short", + &test_segments[0], + "8466 3 52737 4096", + AS2_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 8, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 2 */ + { + "length too long", + &test_segments[0], + "8466 3 52737 4096", + AS2_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 12, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 3 */ + { + "incorrect flag", + &test_segments[0], + "8466 3 52737 4096", + AS2_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS_PATH, + 10, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 4 */ + { + "as4_path, with as2 format data", + &test_segments[0], + "8466 3 52737 4096", + AS2_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS4_PATH, + 10, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 5 */ + { + "as4, with incorrect attr length", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS4_PATH, + 10, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 6 */ + { + "basic 4-byte as-path", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + 0, + PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 18, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 7 */ + { + "4b AS_PATH: too short", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 16, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 8 */ + { + "4b AS_PATH: too long", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 20, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 9 */ + { + "4b AS_PATH: too long2", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS, + BGP_ATTR_AS_PATH, + 22, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 10 */ + { + "4b AS_PATH: bad flags", + &test_segments[0], + "8466 3 52737 4096", + AS4_DATA, + -1, + 0, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS_PATH, + 18, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 11 */ + { + "4b AS4_PATH w/o AS_PATH", + &test_segments[6], + NULL, + AS4_DATA, + -2, + PEER_CAP_AS4_ADV, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS4_PATH, + 14, + }, + COMMON_ATTR_SIZE + 3, + }, + /* 12 */ + { + "4b AS4_PATH: confed", + &test_segments[6], + "8466 3 52737 4096 (123 456 789)", + AS4_DATA, + 0, + PEER_CAP_AS4_ADV, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS4_PATH, + 14, + }, + COMMON_ATTR_SIZE + 3, + &test_segments[0], + }, + /* 13 */ + { + "4b AS4_PATH: BGP_AS_ZERO", + &test_segments[28], + "8466 3 52737 0 4096", + AS4_DATA, + -2, + PEER_CAP_AS4_RCV | PEER_CAP_AS4_ADV, + { + COMMON_ATTRS, + BGP_ATTR_FLAG_TRANS | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_AS4_PATH, + 22, + }, + COMMON_ATTR_SIZE + 3, + }, + {NULL, NULL, NULL, 0, 0, 0, {0}, 0}, +}; + +/* prepending tests */ +static struct tests { + const struct test_segment *test1; + const struct test_segment *test2; + struct test_spec sp; +} prepend_tests[] = { + /* 0 */ + { + &test_segments[0], + &test_segments[1], + {"8466 3 52737 4096 8722 4", "8466 3 52737 4096 8722 4", 6, 0, + NOT_ALL_PRIVATE, 4096, 1, 8466}, + }, + /* 1 */ + {&test_segments[1], + &test_segments[3], + {"8722 4 8482 51457 {5204}", "8722 4 8482 51457 {5204}", 5, 0, + NOT_ALL_PRIVATE, 5204, 1, 8722}}, + /* 2 */ + { + &test_segments[3], + &test_segments[4], + {"8482 51457 {5204} 8467 59649 {4196,48658} {17322,30745}", + "8482 51457 {5204} 8467 59649 {4196,48658} {17322,30745}", 7, + 0, NOT_ALL_PRIVATE, 5204, 1, 8482}, + }, + /* 3 */ + {&test_segments[4], + &test_segments[5], + {"8467 59649 {4196,48658} {17322,30745} 6435 59408 21665 {2457,4369,61697} 1842 41590 51793", + "8467 59649 {4196,48658} {17322,30745} 6435 59408 21665 {2457,4369,61697} 1842 41590 51793", + 11, 0, NOT_ALL_PRIVATE, 61697, 1, 8467}}, + /* 4 */ + { + &test_segments[5], + &test_segments[6], + {"6435 59408 21665 {2457,4369,61697} 1842 41590 51793", + "6435 59408 21665 {2457,4369,61697} 1842 41590 51793", 7, 0, + NOT_ALL_PRIVATE, 1842, 1, 6435}, + }, + /* 5 */ + {&test_segments[6], + &test_segments[7], + {"(123 456 789) (123 456 789) (111 222)", "", 0, 8, NOT_ALL_PRIVATE, + 111, 1, 0}}, + {&test_segments[7], + &test_segments[8], + {"(123 456 789) (111 222) [123,456,789]", "", 0, 6, NOT_ALL_PRIVATE, + 111, 1, 0}}, + { + &test_segments[8], + &test_segments[9], + {"[123,456,789] (123 456 789) [111,222] 8722 {4196,48658}", + "8722 {4196,48658}", 2, 5, NOT_ALL_PRIVATE, 456, 1, NULL_ASN}, + }, + { + &test_segments[9], + &test_segments[8], + {"(123 456 789) [111,222] 8722 {4196,48658} [123,456,789]", + "8722 {4196,48658}", 2, 5, NOT_ALL_PRIVATE, 48658, 1, + NULL_ASN}, + }, + { + &test_segments[14], + &test_segments[11], + {"8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 2 52737 4096 8722 4 8722", + + "8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 3 52737 4096 34285 8466 2 52737 4096 8722 4 8722", + 257, 0, NOT_ALL_PRIVATE, 4096, 1000, 8466}, + }, + {NULL, + NULL, + { + NULL, 0, 0, 0, 0, 0, 0, + }}, +}; + +struct tests reconcile_tests[] = { + { + &test_segments[18], + &test_segments[19], + {"6435 59408 21665 {2457,4369,61697} 1842 41591 51793", + "6435 59408 21665 {2457,4369,61697} 1842 41591 51793", 7, 0, + NOT_ALL_PRIVATE, 51793, 1, 6435}, + }, + { + &test_segments[19], + &test_segments[18], + /* AS_PATH (19) has more hops than NEW_AS_PATH, + * so just AS_PATH should be used (though, this practice + * is bad imho). + */ + {"{2457,4369,61697} 1842 41591 51793 6435 59408 21665 {23456} 23456 23456 23456", + "{2457,4369,61697} 1842 41591 51793 6435 59408 21665 {23456} 23456 23456 23456", + 11, 0, NOT_ALL_PRIVATE, 51793, 1, 6435}, + }, + { + &test_segments[20], + &test_segments[19], + {"(123 456 789) [124,456,788] 6435 59408 21665 {2457,4369,61697} 1842 41591 51793", + "6435 59408 21665 {2457,4369,61697} 1842 41591 51793", 7, 4, + NOT_ALL_PRIVATE, 51793, 1, 6435}, + }, + { + &test_segments[21], + &test_segments[22], + {"1842 41591 51793 6435 59408 21665", + "1842 41591 51793 6435 59408 21665", 6, 0, NOT_ALL_PRIVATE, + 51793, 1, 1842}, + }, + { + &test_segments[23], + &test_segments[22], + {"23456 23456 23456 6435 59408 1842 41591 51793 6435 59408 21665", + "23456 23456 23456 6435 59408 1842 41591 51793 6435 59408 21665", + 11, 0, NOT_ALL_PRIVATE, 51793, 1, 1842}, + }, + {NULL, + NULL, + { + NULL, 0, 0, 0, 0, 0, 0, + }}, +}; + +struct tests aggregate_tests[] = { + { + &test_segments[0], + &test_segments[2], + {"8466 3 52737 4096 {4,8722}", "8466 3 52737 4096 {4,8722}", 5, + 0, NOT_ALL_PRIVATE, 4, 1, 8466}, + }, + { + &test_segments[2], + &test_segments[0], + {"8466 3 52737 4096 {4,8722}", "8466 3 52737 4096 {4,8722}", 5, + 0, NOT_ALL_PRIVATE, 8722, 1, 8466}, + }, + { + &test_segments[2], + &test_segments[10], + {"8466 {2,3,4,4096,8722,52737}", "8466 {2,3,4,4096,8722,52737}", + 2, 0, NOT_ALL_PRIVATE, 8722, 5, 8466}, + }, + { + &test_segments[10], + &test_segments[2], + {"8466 {2,3,4,4096,8722,52737}", "8466 {2,3,4,4096,8722,52737}", + 2, 0, NOT_ALL_PRIVATE, 2, 20000, 8466}, + }, + + { + &test_segments[5], + &test_segments[18], + {"6435 59408 21665 {1842,2457,4369,23456,41590,51793,61697}", + "6435 59408 21665 {1842,2457,4369,23456,41590,51793,61697}", 4, + 0, NOT_ALL_PRIVATE, 41590, 1, 6435}, + }, + + {NULL, NULL, {NULL, 0, 0}}, +}; + +struct compare_tests { + int test_index1; + int test_index2; +#define CMP_RES_YES 1 +#define CMP_RES_NO 0 + char shouldbe_cmp; + char shouldbe_confed; +} left_compare[] = { + {0, 1, CMP_RES_NO, CMP_RES_NO}, {0, 2, CMP_RES_YES, CMP_RES_NO}, + {0, 11, CMP_RES_YES, CMP_RES_NO}, {0, 15, CMP_RES_YES, CMP_RES_NO}, + {0, 16, CMP_RES_NO, CMP_RES_NO}, {1, 11, CMP_RES_NO, CMP_RES_NO}, + {6, 7, CMP_RES_NO, CMP_RES_YES}, {6, 8, CMP_RES_NO, CMP_RES_NO}, + {7, 8, CMP_RES_NO, CMP_RES_NO}, {1, 9, CMP_RES_YES, CMP_RES_NO}, + {0, 9, CMP_RES_NO, CMP_RES_NO}, {3, 9, CMP_RES_NO, CMP_RES_NO}, + {0, 6, CMP_RES_NO, CMP_RES_NO}, {1, 6, CMP_RES_NO, CMP_RES_NO}, + {0, 8, CMP_RES_NO, CMP_RES_NO}, {1, 8, CMP_RES_NO, CMP_RES_NO}, + {11, 6, CMP_RES_NO, CMP_RES_NO}, {11, 7, CMP_RES_NO, CMP_RES_NO}, + {11, 8, CMP_RES_NO, CMP_RES_NO}, {9, 6, CMP_RES_NO, CMP_RES_YES}, + {9, 7, CMP_RES_NO, CMP_RES_YES}, {9, 8, CMP_RES_NO, CMP_RES_NO}, +}; + +/* make an aspath from a data stream */ +static struct aspath *make_aspath(const uint8_t *data, size_t len, int use32bit, + enum asnotation_mode asnotation) +{ + struct stream *s = NULL; + struct aspath *as; + if (len) { + s = stream_new(len); + stream_put(s, data, len); + } + as = aspath_parse(s, len, use32bit, asnotation); + + if (s) + stream_free(s); + + return as; +} + +static void printbytes(const uint8_t *bytes, int len) +{ + int i = 0; + while (i < len) { + if (i % 2) + printf("%02hhx%s", bytes[i], " "); + else + printf("0x%02hhx", bytes[i]); + i++; + } + printf("\n"); +} + +/* validate the given aspath */ +static int validate(struct aspath *as, const struct test_spec *sp) +{ + size_t bytes, bytes4; + int fails = 0; + const uint8_t *out; + static struct stream *s; + struct aspath *asinout, *asconfeddel, *asstr, *as4; + + if (as == NULL && sp->shouldbe == NULL) { + printf("Correctly failed to parse\n"); + return fails; + } + + out = aspath_snmp_pathseg(as, &bytes); + asinout = make_aspath(out, bytes, 0, as->asnotation); + /* Excercise AS4 parsing a bit, with a dogfood test */ + if (!s) + s = stream_new(BGP_MAX_PACKET_SIZE); + bytes4 = aspath_put(s, as, 1); + as4 = make_aspath(STREAM_DATA(s), bytes4, 1, as->asnotation); + + asn_relax_as_zero(true); + asstr = aspath_str2aspath(sp->shouldbe, as->asnotation); + asn_relax_as_zero(false); + + asconfeddel = aspath_delete_confed_seq(aspath_dup(asinout)); + + printf("got: %s\n", aspath_print(as)); + + /* the parsed path should match the specified 'shouldbe' string. + * We should pass the "eat our own dog food" test, be able to output + * this path and then input it again. Ie the path resulting from: + * + * aspath_parse(aspath_put(as)) + * + * should: + * + * - also match the specified 'shouldbe' value + * - hash to same value as original path + * - have same hops and confed counts as original, and as the + * the specified counts + * + * aspath_str2aspath() and shouldbe should match + * + * We do the same for: + * + * aspath_parse(aspath_put(as,USE32BIT)) + * + * Confederation related tests: + * - aspath_delete_confed_seq(aspath) should match shouldbe_confed + * - aspath_delete_confed_seq should be idempotent. + */ + if (strcmp(aspath_print(as), sp->shouldbe) + /* hash validation */ + || (aspath_key_make(as) != aspath_key_make(asinout)) + /* by string */ + || strcmp(aspath_print(asinout), sp->shouldbe) + /* By 4-byte parsing */ + || strcmp(aspath_print(as4), sp->shouldbe) + /* by various path counts */ + || (aspath_count_hops(as) != sp->hops) + || (aspath_count_confeds(as) != sp->confeds) + || (aspath_count_hops(asinout) != sp->hops) + || (aspath_count_confeds(asinout) != sp->confeds)) { + failed++; + fails++; + printf("shouldbe:\n%s\n", sp->shouldbe); + printf("as4:\n%s\n", aspath_print(as4)); + printf("hash keys: in: %d out->in: %d\n", aspath_key_make(as), + aspath_key_make(asinout)); + printf("hops: %d, counted %d %d\n", sp->hops, + aspath_count_hops(as), aspath_count_hops(asinout)); + printf("confeds: %d, counted %d %d\n", sp->confeds, + aspath_count_confeds(as), aspath_count_confeds(asinout)); + printf("out->in:\n%s\nbytes: ", aspath_print(asinout)); + printbytes(out, bytes); + } + /* basic confed related tests */ + if ((aspath_print(asconfeddel) == NULL + && sp->shouldbe_delete_confed != NULL) + || (aspath_print(asconfeddel) != NULL + && sp->shouldbe_delete_confed == NULL) + || strcmp(aspath_print(asconfeddel), sp->shouldbe_delete_confed) + /* delete_confed_seq should be idempotent */ + || (aspath_key_make(asconfeddel) + != aspath_key_make(aspath_delete_confed_seq(asconfeddel)))) { + failed++; + fails++; + printf("as-path minus confeds is: %s\n", + aspath_print(asconfeddel)); + printf("as-path minus confeds should be: %s\n", + sp->shouldbe_delete_confed); + } + /* aspath_str2aspath test */ + if ((aspath_print(asstr) == NULL && sp->shouldbe != NULL) + || (aspath_print(asstr) != NULL && sp->shouldbe == NULL) + || strcmp(aspath_print(asstr), sp->shouldbe)) { + failed++; + fails++; + printf("asstr: %s\n", aspath_print(asstr)); + } + + /* loop, private and first as checks */ + if ((sp->does_loop && aspath_loop_check(as, sp->does_loop) == 0) + || (sp->doesnt_loop && aspath_loop_check(as, sp->doesnt_loop) != 0) + || (aspath_private_as_check(as) != sp->private_as) + || (aspath_firstas_check(as, sp->first) && sp->first == 0)) { + failed++; + fails++; + printf("firstas: %d, got %d\n", sp->first, + aspath_firstas_check(as, sp->first)); + printf("loop does: %d %d, doesn't: %d %d\n", sp->does_loop, + aspath_loop_check(as, sp->does_loop), sp->doesnt_loop, + aspath_loop_check(as, sp->doesnt_loop)); + printf("private check: %d %d\n", sp->private_as, + aspath_private_as_check(as)); + } + aspath_unintern(&asinout); + aspath_unintern(&as4); + + aspath_free(asconfeddel); + aspath_free(asstr); + stream_reset(s); + + return fails; +} + +static void empty_get_test(void) +{ + struct aspath *as = aspath_empty_get(); + struct test_spec sp = {"", "", 0, 0, 0, 0, 0, 0}; + + printf("empty_get_test, as: %s\n", aspath_print(as)); + if (!validate(as, &sp)) + printf("%s\n", OK); + else + printf("%s!\n", FAILED); + + printf("\n"); + + aspath_free(as); +} + +/* basic parsing test */ +static void parse_test(struct test_segment *t) +{ + struct aspath *asp; + + printf("%s: %s\n", t->name, t->desc); + + asp = make_aspath(t->asdata, t->len, 0, t->asnotation); + + printf("aspath: %s\nvalidating...:\n", aspath_print(asp)); + + if (!validate(asp, &t->sp)) + printf(OK "\n"); + else + printf(FAILED "\n"); + + printf("\n"); + + aspath_unintern(&asp); +} + +/* prepend testing */ +static void prepend_test(struct tests *t) +{ + struct aspath *asp1, *asp2, *ascratch; + + printf("prepend %s: %s\n", t->test1->name, t->test1->desc); + printf("to %s: %s\n", t->test2->name, t->test2->desc); + + asp1 = make_aspath(t->test1->asdata, t->test1->len, 0, + ASNOTATION_PLAIN); + asp2 = make_aspath(t->test2->asdata, t->test2->len, 0, + ASNOTATION_PLAIN); + + ascratch = aspath_dup(asp2); + aspath_unintern(&asp2); + + asp2 = aspath_prepend(asp1, ascratch); + + printf("aspath: %s\n", aspath_print(asp2)); + + if (!validate(asp2, &t->sp)) + printf("%s\n", OK); + else + printf("%s!\n", FAILED); + + printf("\n"); + aspath_unintern(&asp1); + aspath_free(asp2); +} + +/* empty-prepend testing */ +static void empty_prepend_test(struct test_segment *t) +{ + struct aspath *asp1, *asp2, *ascratch; + + printf("empty prepend %s: %s\n", t->name, t->desc); + + asp1 = make_aspath(t->asdata, t->len, 0, t->asnotation); + asp2 = aspath_empty(t->asnotation); + + ascratch = aspath_dup(asp2); + aspath_unintern(&asp2); + + asp2 = aspath_prepend(asp1, ascratch); + + printf("aspath: %s\n", aspath_print(asp2)); + + if (!validate(asp2, &t->sp)) + printf(OK "\n"); + else + printf(FAILED "!\n"); + + printf("\n"); + aspath_unintern(&asp1); + aspath_free(asp2); +} + +/* as2+as4 reconciliation testing */ +static void as4_reconcile_test(struct tests *t) +{ + struct aspath *asp1, *asp2, *ascratch; + + printf("reconciling %s:\n %s\n", t->test1->name, t->test1->desc); + printf("with %s:\n %s\n", t->test2->name, t->test2->desc); + + asp1 = make_aspath(t->test1->asdata, t->test1->len, 0, + ASNOTATION_PLAIN); + asp2 = make_aspath(t->test2->asdata, t->test2->len, 0, + ASNOTATION_PLAIN); + + ascratch = aspath_reconcile_as4(asp1, asp2); + + if (!validate(ascratch, &t->sp)) + printf(OK "\n"); + else + printf(FAILED "!\n"); + + printf("\n"); + aspath_unintern(&asp1); + aspath_unintern(&asp2); + aspath_free(ascratch); +} + + +/* aggregation testing */ +static void aggregate_test(struct tests *t) +{ + struct aspath *asp1, *asp2, *ascratch; + + printf("aggregate %s: %s\n", t->test1->name, t->test1->desc); + printf("with %s: %s\n", t->test2->name, t->test2->desc); + + asp1 = make_aspath(t->test1->asdata, t->test1->len, 0, + ASNOTATION_PLAIN); + asp2 = make_aspath(t->test2->asdata, t->test2->len, 0, + ASNOTATION_PLAIN); + + ascratch = aspath_aggregate(asp1, asp2); + + if (!validate(ascratch, &t->sp)) + printf(OK "\n"); + else + printf(FAILED "!\n"); + + printf("\n"); + aspath_unintern(&asp1); + aspath_unintern(&asp2); + aspath_free(ascratch); + /* aspath_unintern (ascratch);*/ +} + +/* cmp_left tests */ +static void cmp_test(void) +{ + unsigned int i; +#define CMP_TESTS_MAX (sizeof(left_compare) / sizeof(struct compare_tests)) + + for (i = 0; i < CMP_TESTS_MAX; i++) { + struct test_segment *t1 = + &test_segments[left_compare[i].test_index1]; + struct test_segment *t2 = + &test_segments[left_compare[i].test_index2]; + struct aspath *asp1, *asp2; + + printf("left cmp %s: %s\n", t1->name, t1->desc); + printf("and %s: %s\n", t2->name, t2->desc); + + asp1 = make_aspath(t1->asdata, t1->len, 0, ASNOTATION_PLAIN); + asp2 = make_aspath(t2->asdata, t2->len, 0, ASNOTATION_PLAIN); + + if (aspath_cmp_left(asp1, asp2) != left_compare[i].shouldbe_cmp + || aspath_cmp_left(asp2, asp1) + != left_compare[i].shouldbe_cmp + || aspath_cmp_left_confed(asp1, asp2) + != left_compare[i].shouldbe_confed + || aspath_cmp_left_confed(asp2, asp1) + != left_compare[i].shouldbe_confed) { + failed++; + printf(FAILED "\n"); + printf("result should be: cmp: %d, confed: %d\n", + left_compare[i].shouldbe_cmp, + left_compare[i].shouldbe_confed); + printf("got: cmp %d, cmp_confed: %d\n", + aspath_cmp_left(asp1, asp2), + aspath_cmp_left_confed(asp1, asp2)); + printf("path1: %s\npath2: %s\n", aspath_print(asp1), + aspath_print(asp2)); + } else + printf(OK "\n"); + + printf("\n"); + aspath_unintern(&asp1); + aspath_unintern(&asp2); + } +} + +static int handle_attr_test(struct aspath_tests *t) +{ + struct bgp bgp = {0}; + struct peer peer = {0}; + struct attr attr = {0}; + int ret; + int initfail = failed; + struct aspath *asp; + size_t datalen; + + asp = make_aspath(t->segment->asdata, t->segment->len, 0, + t->segment->asnotation); + bgp.asnotation = t->segment->asnotation; + + peer.curr = stream_new(BGP_MAX_PACKET_SIZE); + peer.connection = bgp_peer_connection_new(&peer); + peer.connection->obuf = stream_fifo_new(); + peer.bgp = &bgp; + peer.host = (char *)"none"; + peer.connection->fd = -1; + peer.cap = t->cap; + peer.max_packet_size = BGP_STANDARD_MESSAGE_MAX_PACKET_SIZE; + + stream_write(peer.curr, t->attrheader, t->len); + datalen = aspath_put(peer.curr, asp, t->as4 == AS4_DATA); + if (t->old_segment) { + char dummyaspath[] = {BGP_ATTR_FLAG_TRANS, BGP_ATTR_AS_PATH, + t->old_segment->len}; + stream_write(peer.curr, dummyaspath, sizeof(dummyaspath)); + stream_write(peer.curr, t->old_segment->asdata, + t->old_segment->len); + datalen += sizeof(dummyaspath) + t->old_segment->len; + } + + ret = bgp_attr_parse(&peer, &attr, t->len + datalen, NULL, NULL); + + if (ret != t->result) { + printf("bgp_attr_parse returned %d, expected %d\n", ret, + t->result); + printf("datalen %zd\n", datalen); + failed++; + } + if (ret != 0) + goto out; + + if (t->shouldbe && attr.aspath == NULL) { + printf("aspath is NULL, but should be: %s\n", t->shouldbe); + failed++; + } + if (t->shouldbe && attr.aspath + && strcmp(attr.aspath->str, t->shouldbe)) { + printf("attr str and 'shouldbe' mismatched!\n" + "attr str: %s\n" + "shouldbe: %s\n", + attr.aspath->str, t->shouldbe); + failed++; + } + if (!t->shouldbe && attr.aspath) { + printf("aspath should be NULL, but is: %s\n", attr.aspath->str); + failed++; + } + +out: + aspath_unintern(&attr.aspath); + aspath_unintern(&asp); + return failed - initfail; +} + +static void attr_test(struct aspath_tests *t) +{ + printf("%s\n", t->desc); + printf("%s\n\n", handle_attr_test(t) ? FAILED : OK); +} + +int main(void) +{ + int i = 0; + qobj_init(); + bgp_master_init(event_master_create(NULL), BGP_SOCKET_SNDBUF_SIZE, + list_new()); + master = bm->master; + bgp_option_set(BGP_OPT_NO_LISTEN); + bgp_attr_init(); + + while (test_segments[i].name) { + printf("test %u\n", i); + parse_test(&test_segments[i]); + empty_prepend_test(&test_segments[i++]); + } + i = 0; + + while (prepend_tests[i].test1) { + printf("prepend test %u\n", i); + prepend_test(&prepend_tests[i++]); + } + + i = 0; + while (aggregate_tests[i].test1) { + printf("aggregate test %u\n", i); + aggregate_test(&aggregate_tests[i++]); + } + + i = 0; + + while (reconcile_tests[i].test1) { + printf("reconcile test %u\n", i); + as4_reconcile_test(&reconcile_tests[i++]); + } + + i = 0; + + cmp_test(); + + i = 0; + + empty_get_test(); + + i = 0; + + frr_pthread_init(); + bgp_pthreads_init(); + bgp_pth_ka->running = true; + + while (aspath_tests[i].desc) { + printf("aspath_attr test %d\n", i); + attr_test(&aspath_tests[i++]); + } + + printf("failures: %d\n", failed); + printf("aspath count: %ld\n", aspath_count()); + + return (failed + aspath_count()); +} diff --git a/tests/bgpd/test_aspath.py b/tests/bgpd/test_aspath.py new file mode 100644 index 0000000..88579ad --- /dev/null +++ b/tests/bgpd/test_aspath.py @@ -0,0 +1,84 @@ +import frrtest +import re + +re_okfail = re.compile( + r"^(?:\x1b\[3[12]m)?(?POK|failed)".encode("utf8"), re.MULTILINE +) + + +class TestAspath(frrtest.TestMultiOut): + program = "./test_aspath" + + def _parsertest(self, line): + if not hasattr(self, "parserno"): + self.parserno = -1 + self.parserno += 1 + + self._onesimple("test %d" % self.parserno) + self._okfail("%s:" % line, okfail=re_okfail) + self._okfail("empty prepend %s:" % line, okfail=re_okfail) + + def _attrtest(self, line): + if not hasattr(self, "attrno"): + self.attrno = -1 + self.attrno += 1 + + self._onesimple("aspath_attr test %d" % self.attrno) + self._okfail(line, okfail=re_okfail) + + +TestAspath.parsertest("seq1") +TestAspath.parsertest("seq2") +TestAspath.parsertest("seq3") +TestAspath.parsertest("seqset") +TestAspath.parsertest("seqset2") +TestAspath.parsertest("multi") +TestAspath.parsertest("confed") +TestAspath.parsertest("confed2") +TestAspath.parsertest("confset") +TestAspath.parsertest("confmulti") +TestAspath.parsertest("seq4") +TestAspath.parsertest("tripleseq1") +TestAspath.parsertest("someprivate") +TestAspath.parsertest("allprivate") +TestAspath.parsertest("long") +TestAspath.parsertest("seq1extra") +TestAspath.parsertest("empty") +TestAspath.parsertest("redundantset") +TestAspath.parsertest("reconcile_lead_asp") +TestAspath.parsertest("reconcile_new_asp") +TestAspath.parsertest("reconcile_confed") +TestAspath.parsertest("reconcile_start_trans") +TestAspath.parsertest("reconcile_start_trans4") +TestAspath.parsertest("reconcile_start_trans_error") +TestAspath.parsertest("redundantset2") +TestAspath.parsertest("zero-size overflow") +TestAspath.parsertest("zero-size overflow + valid segment") +TestAspath.parsertest("invalid segment type") +TestAspath.parsertest("BGP_AS_ZERO") + +for i in range(10): + TestAspath.okfail("prepend test %d" % i) +for i in range(5): + TestAspath.okfail("aggregate test %d" % i) +for i in range(5): + TestAspath.okfail("reconcile test %d" % i) +for _ in range(22): + TestAspath.okfail("left cmp ") + +TestAspath.okfail("empty_get_test") + +TestAspath.attrtest("basic test") +TestAspath.attrtest("length too short") +TestAspath.attrtest("length too long") +TestAspath.attrtest("incorrect flag") +TestAspath.attrtest("as4_path, with as2 format data") +TestAspath.attrtest("as4, with incorrect attr length") +TestAspath.attrtest("basic 4-byte as-path") +TestAspath.attrtest("4b AS_PATH: too short") +TestAspath.attrtest("4b AS_PATH: too long") +TestAspath.attrtest("4b AS_PATH: too long2") +TestAspath.attrtest("4b AS_PATH: bad flags") +TestAspath.attrtest("4b AS4_PATH w/o AS_PATH") +TestAspath.attrtest("4b AS4_PATH: confed") +TestAspath.attrtest("4b AS4_PATH: BGP_AS_ZERO") diff --git a/tests/bgpd/test_bgp_table.c b/tests/bgpd/test_bgp_table.c new file mode 100644 index 0000000..bed86f5 --- /dev/null +++ b/tests/bgpd/test_bgp_table.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Routing table range lookup test + * Copyright (C) 2012 OSR. + * Copyright (C) 2018 Marcel Röthke (marcel.roethke@haw-hamburg.de), for HAW + * Hamburg + * + * This file is part of FRRouting + */ + +#include + +#include "prefix.h" +#include "table.h" +#include "bgpd/bgp_table.h" +#include "linklist.h" + +/* Satisfy link requirements from including bgpd.h */ +struct zebra_privs_t bgpd_privs = {0}; +/* + * test_node_t + * + * Information that is kept for each node in the radix tree. + */ +struct test_node_t { + + /* + * Human readable representation of the string. Allocated using + * malloc()/dup(). + */ + char *prefix_str; +}; + +/* + * add_node + * + * Add the given prefix (passed in as a string) to the given table. + */ +static void add_node(struct bgp_table *table, const char *prefix_str) +{ + struct prefix_ipv4 p; + struct test_node_t *node; + struct bgp_dest *dest; + + assert(prefix_str); + + if (str2prefix_ipv4(prefix_str, &p) <= 0) + assert(0); + + dest = bgp_node_get(table, (struct prefix *)&p); + if (dest->info) { + assert(0); + return; + } + + node = malloc(sizeof(struct test_node_t)); + assert(node); + node->prefix_str = strdup(prefix_str); + assert(node->prefix_str); + dest->info = node; +} + +static bool prefix_in_array(const struct prefix *p, struct prefix *prefix_array, + size_t prefix_array_size) +{ + for (size_t i = 0; i < prefix_array_size; ++i) { + if (prefix_same(p, &prefix_array[i])) + return true; + } + return false; +} + +static void check_lookup_result(struct bgp_dest *match, va_list arglist) +{ + char *prefix_str; + struct prefix *prefixes = NULL; + size_t prefix_count = 0; + + while ((prefix_str = va_arg(arglist, char *))) { + ++prefix_count; + prefixes = realloc(prefixes, sizeof(*prefixes) * prefix_count); + + if (str2prefix(prefix_str, &prefixes[prefix_count - 1]) <= 0) + assert(0); + } + + /* check if the result is empty and if it is allowd to be empty */ + assert((prefix_count == 0 && !match) || prefix_count > 0); + if (!match) + return; + + struct bgp_dest *dest = match; + + while ((dest = bgp_route_next_until(dest, match))) { + const struct prefix *dest_p = bgp_dest_get_prefix(dest); + + if (bgp_dest_has_bgp_path_info_data(dest) + && !prefix_in_array(dest_p, prefixes, prefix_count)) { + printf("prefix %pFX was not expected!\n", dest_p); + assert(0); + } + } +} + +static void do_test(struct bgp_table *table, const char *prefix, ...) +{ + va_list arglist; + struct prefix p; + + + va_start(arglist, prefix); + printf("\nDoing lookup for %s\n", prefix); + if (str2prefix(prefix, &p) <= 0) + assert(0); + struct bgp_dest *dest = bgp_table_subtree_lookup(table, &p); + + check_lookup_result(dest, arglist); + + va_end(arglist); + + printf("Checks successfull\n"); +} + +/* + * test_range_lookup + */ +static void test_range_lookup(void) +{ + struct bgp_table *table = bgp_table_init(NULL, AFI_IP, SAFI_UNICAST); + + printf("Testing bgp_table_range_lookup\n"); + + printf("Setup bgp_table"); + const char *prefixes[] = {"1.16.0.0/16", "1.16.128.0/18", + "1.16.192.0/18", "1.16.64.0/19", + "1.16.160.0/19", "1.16.32.0/20", + "1.16.32.0/21", "16.0.0.0/16"}; + + int num_prefixes = array_size(prefixes); + + for (int i = 0; i < num_prefixes; i++) + add_node(table, prefixes[i]); + + do_test(table, "1.16.0.0/17", "1.16.64.0/19", "1.16.32.0/20", + "1.16.32.0/20", "1.16.32.0/21", NULL); + do_test(table, "1.16.128.0/17", "1.16.128.0/18", "1.16.192.0/18", + "1.16.160.0/19", NULL); + + do_test(table, "1.16.0.0/16", "1.16.0.0/16", "1.16.128.0/18", + "1.16.192.0/18", "1.16.64.0/19", "1.16.160.0/19", + "1.16.32.0/20", "1.16.32.0/21", NULL); + + do_test(table, "1.17.0.0/16", NULL); + + do_test(table, "128.0.0.0/8", NULL); + + do_test(table, "16.0.0.0/8", "16.0.0.0/16", NULL); + + do_test(table, "0.0.0.0/2", "1.16.0.0/16", "1.16.128.0/18", + "1.16.192.0/18", "1.16.64.0/19", "1.16.160.0/19", + "1.16.32.0/20", "1.16.32.0/21", "16.0.0.0/16", NULL); +} + +int main(void) +{ + test_range_lookup(); +} diff --git a/tests/bgpd/test_bgp_table.py b/tests/bgpd/test_bgp_table.py new file mode 100644 index 0000000..8f05442 --- /dev/null +++ b/tests/bgpd/test_bgp_table.py @@ -0,0 +1,9 @@ +import frrtest + + +class TestTable(frrtest.TestMultiOut): + program = "./test_bgp_table" + + +for i in range(7): + TestTable.onesimple("Checks successfull") diff --git a/tests/bgpd/test_capability.c b/tests/bgpd/test_capability.c new file mode 100644 index 0000000..1ee47a3 --- /dev/null +++ b/tests/bgpd/test_capability.c @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Sun Microsystems, Inc. + */ + +#include + +#include "qobj.h" +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" +#include "frr_pthread.h" + +#include "bgpd/bgpd.c" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_packet.h" + +#define VT100_RESET "\x1b[0m" +#define VT100_RED "\x1b[31m" +#define VT100_GREEN "\x1b[32m" +#define VT100_YELLOW "\x1b[33m" + +#define CAPABILITY 0 +#define DYNCAP 1 +#define OPT_PARAM 2 + +/* need these to link in libbgp */ +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master = NULL; + +static int failed = 0; +static int tty = 0; + +/* test segments to parse and validate, and use for other tests */ +static struct test_segment { + const char *name; + const char *desc; + const uint8_t data[1024]; + int len; +#define SHOULD_PARSE 0 +#define SHOULD_ERR -1 + int parses; /* whether it should parse or not */ + as_t peek_for; /* what peek_for_as4_capability should say */ + + /* AFI/SAFI validation */ + int validate_afi; + iana_afi_t afi; + iana_safi_t safi; +#define VALID_AFI 1 +#define INVALID_AFI 0 + int afi_valid; +} test_segments[] = { + /* 0 */ + { + "caphdr", + "capability header, and no more", + {CAPABILITY_CODE_REFRESH, 0x0}, + 2, + SHOULD_PARSE, + }, + /* 1 */ + { + "nodata", + "header, no data but length says there is", + {0x1, 0xa}, + 2, + SHOULD_ERR, + }, + /* 2 */ + { + "padded", + "valid, with padding", + {CAPABILITY_CODE_REFRESH, 0x2, 0x0, 0x0}, + 4, + SHOULD_PARSE, + }, + /* 3 */ + { + "minsize", + "violates minsize requirement", + {CAPABILITY_CODE_ORF, 0x2, 0x0, 0x0}, + 4, + SHOULD_ERR, + }, + {NULL, NULL, {0}, 0, 0}, +}; + +static struct test_segment mp_segments[] = { + { + "MP4", + "MP IP/Uni", + {0x1, 0x4, 0x0, 0x1, 0x0, 0x1}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV4, + IANA_SAFI_UNICAST, + VALID_AFI, + }, + { + "MPv6", + "MP IPv6/Uni", + {0x1, 0x4, 0x0, 0x2, 0x0, 0x1}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV6, + IANA_SAFI_UNICAST, + VALID_AFI, + }, + /* 5 */ + { + "MP2", + "MP IP/Multicast", + {CAPABILITY_CODE_MP, 0x4, 0x0, 0x1, 0x0, 0x2}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV4, + IANA_SAFI_MULTICAST, + VALID_AFI, + }, + /* 6 */ + { + "MP3", + "MP IP6/MPLS-labeled VPN", + {CAPABILITY_CODE_MP, 0x4, 0x0, 0x2, 0x0, 0x80}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV6, + IANA_SAFI_MPLS_VPN, + VALID_AFI, + }, + /* 7 */ + { + "MP5", + "MP IP6/MPLS-VPN", + {CAPABILITY_CODE_MP, 0x4, 0x0, 0x2, 0x0, 0x4}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV6, + IANA_SAFI_MPLS_VPN, + VALID_AFI, + }, + /* 8 */ + { + "MP6", + "MP IP4/MPLS-labeled VPN", + {CAPABILITY_CODE_MP, 0x4, 0x0, 0x1, 0x0, 0x80}, + 6, + SHOULD_PARSE, + 0, + 1, + IANA_AFI_IPV4, + IANA_SAFI_MPLS_VPN, + VALID_AFI, + }, + /* 10 */ + { + "MP8", + "MP unknown AFI/SAFI", + {CAPABILITY_CODE_MP, 0x4, 0x0, 0xa, 0x0, 0x81}, + 6, + SHOULD_PARSE, + 0, + 1, + 0xa, + 0x81, + INVALID_AFI, /* parses, but unknown */ + }, + /* 11 */ + { + "MP-short", + "MP IP4/Unicast, length too short (< minimum)", + {CAPABILITY_CODE_MP, 0x2, 0x0, 0x1, 0x0, 0x1}, + 6, + SHOULD_ERR, + }, + /* 12 */ + { + "MP-overflow", + "MP IP4/Unicast, length too long", + {CAPABILITY_CODE_MP, 0x6, 0x0, 0x1, 0x0, 0x1}, + 6, + SHOULD_ERR, + 0, + 1, + IANA_AFI_IPV4, + IANA_SAFI_UNICAST, + VALID_AFI, + }, + {NULL, NULL, {0}, 0, 0}}; + +static struct test_segment misc_segments[] = + { + /* 13 */ + { + "ORF", + "ORF, simple, single entry, single tuple", + {/* hdr */ CAPABILITY_CODE_ORF, 0x7, + /* mpc */ 0x0, 0x1, 0x0, 0x1, + /* num */ 0x1, + /* tuples */ 0x40, 0x3}, + 9, + SHOULD_PARSE, + }, + /* 14 */ + { + "ORF-many", + "ORF, multi entry/tuple", + { + /* hdr */ CAPABILITY_CODE_ORF, + 0x21, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + ORF_MODE_BOTH, + 0x80, + ORF_MODE_RECEIVE, + 0x80, + ORF_MODE_SEND, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + ORF_MODE_BOTH, + 0x80, + ORF_MODE_RECEIVE, + 0x80, + ORF_MODE_SEND, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + ORF_MODE_RECEIVE, + 0x80, + ORF_MODE_SEND, + 0x80, + ORF_MODE_BOTH, + }, + 35, + SHOULD_PARSE, + }, + /* 15 */ + { + "ORFlo", + "ORF, multi entry/tuple, hdr length too short", + { + /* hdr */ CAPABILITY_CODE_ORF, + 0x15, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + }, + 35, + SHOULD_ERR, /* It should error on invalid + Route-Refresh.. */ + }, + /* 16 */ + {"ORFlu", + "ORF, multi entry/tuple, length too long", + { + /* hdr */ 0x3, + 0x22, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + }, + 35, + SHOULD_ERR}, + /* 17 */ + { + "ORFnu", + "ORF, multi entry/tuple, entry number too long", + { + /* hdr */ 0x3, + 0x21, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x1, + /* num */ 0x4, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + }, + 35, + SHOULD_PARSE, /* parses, but last few tuples should be + gibberish */ + }, + /* 18 */ + { + "ORFno", + "ORF, multi entry/tuple, entry number too short", + { + /* hdr */ 0x3, + 0x21, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x1, + /* num */ 0x1, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + }, + 35, + SHOULD_PARSE, /* Parses, but should get gibberish + afi/safis */ + }, + /* 17 */ + { + "ORFpad", + "ORF, multi entry/tuple, padded to align", + { + /* hdr */ 0x3, + 0x22, + /* mpc */ 0x0, + 0x1, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x1, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + /* mpc */ 0x0, + 0x2, + 0x0, + 0x2, + /* num */ 0x3, + /* tuples */ 0x40, + 0x3, + 0x80, + 0x1, + 0x80, + 0x2, + 0x00, + }, + 36, + SHOULD_PARSE, + }, + /* 19 */ + { + "AS4", + "AS4 capability", + {0x41, 0x4, 0xab, 0xcd, 0xef, + 0x12}, /* AS: 2882400018 */ + 6, + SHOULD_PARSE, + 2882400018, + }, + { + "AS4", + "AS4 capability: short", + {0x41, 0x4, 0xab, 0xcd, 0xef}, /* AS: 2882400018 */ + 5, + SHOULD_ERR, + }, + { + "AS4", + "AS4 capability: long", + {0x41, 0x4, 0xab, 0xcd, 0xef, 0x12, 0x12}, + 7, + SHOULD_ERR, + 2882400018, + }, + { + "GR", + "GR capability", + { + /* hdr */ CAPABILITY_CODE_RESTART, 0xe, + /* R-bit, time */ 0xf1, 0x12, + /* afi */ 0x0, 0x1, + /* safi */ 0x1, + /* flags */ 0xf, + /* afi */ 0x0, 0x2, + /* safi */ 0x1, + /* flags */ 0x0, + /* afi */ 0x0, 0x2, + /* safi */ 0x2, + /* flags */ 0x1, + }, + 16, + SHOULD_PARSE, + }, + { + "GR-short", + "GR capability, but header length too short", + { + /* hdr */ 0x40, 0xa, + /* R-bit, time */ 0xf1, 0x12, + /* afi */ 0x0, 0x1, + /* safi */ 0x1, + /* flags */ 0xf, + /* afi */ 0x0, 0x2, + /* safi */ 0x1, + /* flags */ 0x0, + /* afi */ 0x0, 0x2, + /* safi */ 0x2, + /* flags */ 0x1, + }, + 15 /* array is 16 though */, + SHOULD_ERR, + }, + { + "GR-long", + "GR capability, but header length too long", + { + /* hdr */ 0x40, 0xf, + /* R-bit, time */ 0xf1, 0x12, + /* afi */ 0x0, 0x1, + /* safi */ 0x1, + /* flags */ 0xf, + /* afi */ 0x0, 0x2, + /* safi */ 0x1, + /* flags */ 0x0, + /* afi */ 0x0, 0x2, + /* safi */ 0x2, + /* flags */ 0x01, + }, + 16, + SHOULD_ERR, + }, + { + "GR-trunc", + "GR capability, but truncated", + { + /* hdr */ 0x40, 0xf, + /* R-bit, time */ 0xf1, 0x12, + /* afi */ 0x0, 0x1, + /* safi */ 0x1, + /* flags */ 0xf, + /* afi */ 0x0, 0x2, + /* safi */ 0x1, + /* flags */ 0x0, + /* afi */ 0x0, 0x2, + /* safi */ 0x2, + /* flags */ 0x1, + }, + 15, + SHOULD_ERR, + }, + { + "GR-empty", + "GR capability, but empty.", + { + /* hdr */ 0x40, 0x0, + }, + 2, + SHOULD_ERR, + }, + { + "MP-empty", + "MP capability, but empty.", + { + /* hdr */ 0x1, 0x0, + }, + 2, + SHOULD_ERR, + }, + { + "ORF-empty", + "ORF capability, but empty.", + { + /* hdr */ 0x3, 0x0, + }, + 2, + SHOULD_ERR, + }, + { + "AS4-empty", + "AS4 capability, but empty.", + { + /* hdr */ 0x41, 0x0, + }, + 2, + SHOULD_ERR, + -1, + }, + { + "dyn-empty", + "Dynamic capability, but empty.", + { + /* hdr */ 0x42, 0x0, + }, + 2, + SHOULD_PARSE, + }, + { + "dyn-old", + "Dynamic capability (deprecated version)", + {CAPABILITY_CODE_DYNAMIC, 0x0}, + 2, + SHOULD_PARSE, + }, + { + "Role", + "Role capability", + { + /* hdr */ 0x9, 0x1, + 0x1, + }, + 3, + SHOULD_PARSE, + }, + { + "Role-long", + "Role capability, but too long", + { + /* hdr */ 0x9, 0x4, + 0x0, 0x0, 0x0, 0x1, + }, + 6, + SHOULD_ERR, + }, + { + "Role-empty", + "Role capability, but empty.", + { + /* hdr */ 0x9, 0x0, + }, + 2, + SHOULD_ERR, + }, + {NULL, NULL, {0}, 0, 0}}; + +/* DYNAMIC message */ +struct test_segment dynamic_cap_msgs[] = { + { + "DynCap", + "Dynamic Capability Message, IP/Multicast", + {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2}, + 7, + SHOULD_PARSE, /* horrible alignment, just as with ORF */ + }, + { + "DynCapLong", + "Dynamic Capability Message, IP/Multicast, truncated", + {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2}, + 5, + SHOULD_ERR, + }, + { + "DynCapPadded", + "Dynamic Capability Message, IP/Multicast, padded", + {0x0, 0x1, 0x4, 0x0, 0x1, 0x0, 0x2, 0x0}, + 8, + SHOULD_ERR, /* No way to tell padding from data.. */ + }, + { + "DynCapMPCpadded", + "Dynamic Capability Message, IP/Multicast, cap data padded", + {0x0, 0x1, 0x5, 0x0, 0x1, 0x0, 0x2, 0x0}, + 8, + SHOULD_PARSE, /* You can though add padding to the capability + data */ + }, + { + "DynCapMPCoverflow", + "Dynamic Capability Message, IP/Multicast, cap data != length", + {0x0, 0x1, 0x3, 0x0, 0x1, 0x0, 0x2, 0x0}, + 8, + SHOULD_ERR, + }, + {NULL, NULL, {0}, 0, 0}}; + +/* Entire Optional-Parameters block */ +struct test_segment opt_params[] = { + { + "Cap-singlets", + "One capability per Optional-Param", + { + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ + 0x02, 0x02, 0x80, 0x00, /* RR (old) */ + 0x02, 0x02, 0x02, 0x00, /* RR */ + }, + 24, + SHOULD_PARSE, + }, + { + "Cap-series", + "Series of capability, one Optional-Param", + { + 0x02, 0x10, 0x01, 0x04, 0x00, 0x01, 0x00, + 0x01, /* MP IPv4/Uni */ + 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ + 0x80, 0x00, /* RR (old) */ + 0x02, 0x00, /* RR */ + }, + 18, + SHOULD_PARSE, + }, + { + "AS4more", + "AS4 capability after other caps (singlets)", + { + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x01, 0x00, 0x01, /* MP IPv4/Uni */ + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ + 0x02, 0x02, 0x80, 0x00, /* RR (old) */ + 0x02, 0x02, 0x02, 0x00, /* RR */ + 0x02, 0x06, 0x41, 0x04, + 0x00, 0x03, 0x00, 0x06 /* AS4: 1996614 */ + }, + 32, + SHOULD_PARSE, + 196614, + }, + { + "AS4series", + "AS4 capability, in series of capabilities", + { + 0x02, 0x16, 0x01, 0x04, 0x00, 0x01, + 0x00, 0x01, /* MP IPv4/Uni */ + 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, /* MP IPv6/Uni */ + 0x80, 0x00, /* RR (old) */ + 0x02, 0x00, /* RR */ + 0x41, 0x04, 0x00, 0x03, 0x00, 0x06 /* AS4: 1996614 */ + }, + 24, + SHOULD_PARSE, + 196614, + }, + { + "AS4real", + "AS4 capability, in series of capabilities", + { + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x01, 0x00, 0x01, /* MP IPv4/uni */ + 0x02, 0x06, 0x01, 0x04, + 0x00, 0x02, 0x00, 0x01, /* MP IPv6/uni */ + 0x02, 0x02, 0x80, 0x00, /* RR old */ + 0x02, 0x02, 0x02, 0x00, /* RR */ + 0x02, 0x06, 0x41, 0x04, + 0x00, 0x03, 0x00, 0x06, /* AS4 */ + }, + 32, + SHOULD_PARSE, + 196614, + }, + { + "AS4real2", + "AS4 capability, in series of capabilities", + { + 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, + 0x06, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x02, 0x02, + 0x80, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x06, 0x41, + 0x04, 0x00, 0x00, 0xfc, 0x03, 0x02, 0x09, 0x82, 0x07, + 0x00, 0x01, 0x00, 0x01, 0x01, 0x80, 0x03, 0x02, 0x09, + 0x03, 0x07, 0x00, 0x01, 0x00, 0x01, 0x01, 0x40, 0x03, + 0x02, 0x02, 0x42, 0x00, + }, + 58, + SHOULD_PARSE, + 64515, + }, + + {NULL, NULL, {0}, 0, 0}}; + +/* basic parsing test */ +static void parse_test(struct peer *peer, struct test_segment *t, int type) +{ + int ret; + int capability = 0; + as_t as4 = 0; + int oldfailed = failed; + int len = t->len; +#define RANDOM_FUZZ 35 + + stream_reset(peer->curr); + stream_put(peer->curr, NULL, RANDOM_FUZZ); + stream_set_getp(peer->curr, RANDOM_FUZZ); + + switch (type) { + case CAPABILITY: + stream_putc(peer->curr, BGP_OPEN_OPT_CAP); + stream_putc(peer->curr, t->len); + break; + case DYNCAP: + /* for (i = 0; i < BGP_MARKER_SIZE; i++) + stream_putc (peer->, 0xff); + stream_putw (s, 0); + stream_putc (s, BGP_MSG_CAPABILITY);*/ + break; + } + stream_write(peer->curr, t->data, t->len); + + printf("%s: %s\n", t->name, t->desc); + + switch (type) { + case CAPABILITY: + len += 2; /* to cover the OPT-Param header */ + fallthrough; + case OPT_PARAM: + printf("len: %u\n", len); + /* peek_for_as4 wants getp at capibility*/ + as4 = peek_for_as4_capability(peer, len); + printf("peek_for_as4: as4 is %u\n", as4); + /* and it should leave getp as it found it */ + assert(stream_get_getp(peer->curr) == RANDOM_FUZZ); + + ret = bgp_open_option_parse(peer, len, &capability); + break; + case DYNCAP: + ret = bgp_capability_receive(peer->connection, peer, t->len); + break; + default: + printf("unknown type %u\n", type); + exit(1); + } + + if (ret != BGP_Stop && t->validate_afi) { + afi_t afi; + safi_t safi; + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_iana2int(t->afi, t->safi, &afi, &safi)) { + if (t->afi_valid == VALID_AFI) + failed++; + } + printf("MP: %u(%u)/%u(%u): recv %u, nego %u\n", t->afi, afi, + t->safi, safi, peer->afc_recv[afi][safi], + peer->afc_nego[afi][safi]); + + if (t->afi_valid == VALID_AFI) { + + if (!peer->afc_recv[afi][safi]) + failed++; + if (!peer->afc_nego[afi][safi]) + failed++; + } + } + + if (as4 != t->peek_for) { + printf("as4 %u != %u\n", as4, t->peek_for); + failed++; + } + + /* + * Some of the functions used return BGP_Stop on error and some return + * -1. If we have -1, keep it; if we have BGP_Stop, transform it to the + * correct pass/fail code + */ + if (ret != -1) + ret = (ret == BGP_Stop) ? -1 : 0; + + printf("parsed?: %s\n", ret ? "no" : "yes"); + + if (ret != t->parses) { + printf("t->parses: %d\nret: %d\n", t->parses, ret); + failed++; + } + + if (tty) + printf("%s", + (failed > oldfailed) ? VT100_RED "failed!" VT100_RESET + : VT100_GREEN "OK" VT100_RESET); + else + printf("%s", (failed > oldfailed) ? "failed!" : "OK"); + + if (failed) + printf(" (%u)", failed); + + printf("\n\n"); +} + +static struct bgp *bgp; +static as_t asn = 100; + +int main(void) +{ + struct peer *peer; + int i, j; + + conf_bgp_debug_neighbor_events = -1UL; + conf_bgp_debug_packet = -1UL; + conf_bgp_debug_as4 = -1UL; + term_bgp_debug_neighbor_events = -1UL; + term_bgp_debug_packet = -1UL; + term_bgp_debug_as4 = -1UL; + + qobj_init(); + master = event_master_create(NULL); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + vrf_init(NULL, NULL, NULL, NULL); + bgp_option_set(BGP_OPT_NO_LISTEN); + + frr_pthread_init(); + bgp_pthreads_init(); + bgp_pth_ka->running = true; + + if (fileno(stdout) >= 0) + tty = isatty(fileno(stdout)); + + if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT, NULL, + ASNOTATION_PLAIN) < 0) + return -1; + + peer = peer_create_accept(bgp); + peer->host = (char *)"foo"; + + for (i = AFI_IP; i < AFI_MAX; i++) + for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { + peer->afc[i][j] = 1; + peer->afc_adv[i][j] = 1; + } + + peer->curr = stream_new(BGP_MAX_PACKET_SIZE); + + i = 0; + while (mp_segments[i].name) + parse_test(peer, &mp_segments[i++], CAPABILITY); + + /* These tests assume mp_segments tests set at least + * one of the afc_nego's + */ + i = 0; + while (test_segments[i].name) + parse_test(peer, &test_segments[i++], CAPABILITY); + + i = 0; + while (misc_segments[i].name) + parse_test(peer, &misc_segments[i++], CAPABILITY); + + i = 0; + while (opt_params[i].name) + parse_test(peer, &opt_params[i++], OPT_PARAM); + + SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); + peer->connection = bgp_peer_connection_new(peer); + peer->connection->status = Established; + + i = 0; + while (dynamic_cap_msgs[i].name) + parse_test(peer, &dynamic_cap_msgs[i++], DYNCAP); + + printf("failures: %d\n", failed); + return failed; +} diff --git a/tests/bgpd/test_capability.py b/tests/bgpd/test_capability.py new file mode 100644 index 0000000..da9245b --- /dev/null +++ b/tests/bgpd/test_capability.py @@ -0,0 +1,56 @@ +import frrtest + + +class TestCapability(frrtest.TestMultiOut): + program = "./test_capability" + + +TestCapability.okfail("MP4: MP IP/Uni") +TestCapability.okfail("MPv6: MP IPv6/Uni") +TestCapability.okfail("MP2: MP IP/Multicast") +TestCapability.okfail("MP3: MP IP6/MPLS-labeled VPN") +TestCapability.okfail("MP5: MP IP6/MPLS-VPN") +TestCapability.okfail("MP6: MP IP4/MPLS-labeled VPN") +TestCapability.okfail("MP8: MP unknown AFI/SAFI") +TestCapability.okfail("MP-short: MP IP4/Unicast, length too short (< minimum)") +TestCapability.okfail("MP-overflow: MP IP4/Unicast, length too long") +TestCapability.okfail("caphdr: capability header, and no more") +TestCapability.okfail("nodata: header, no data but length says there is") +TestCapability.okfail("padded: valid, with padding") +TestCapability.okfail("minsize: violates minsize requirement") +TestCapability.okfail("ORF: ORF, simple, single entry, single tuple") +TestCapability.okfail("ORF-many: ORF, multi entry/tuple") +TestCapability.okfail("ORFlo: ORF, multi entry/tuple, hdr length too short") +TestCapability.okfail("ORFlu: ORF, multi entry/tuple, length too long") +TestCapability.okfail("ORFnu: ORF, multi entry/tuple, entry number too long") +TestCapability.okfail("ORFno: ORF, multi entry/tuple, entry number too short") +TestCapability.okfail("ORFpad: ORF, multi entry/tuple, padded to align") +TestCapability.okfail("AS4: AS4 capability") +TestCapability.okfail("GR: GR capability") +TestCapability.okfail("GR-short: GR capability, but header length too short") +TestCapability.okfail("GR-long: GR capability, but header length too long") +TestCapability.okfail("GR-trunc: GR capability, but truncated") +TestCapability.okfail("GR-empty: GR capability, but empty.") +TestCapability.okfail("MP-empty: MP capability, but empty.") +TestCapability.okfail("ORF-empty: ORF capability, but empty.") +TestCapability.okfail("AS4-empty: AS4 capability, but empty.") +TestCapability.okfail("dyn-empty: Dynamic capability, but empty.") +TestCapability.okfail("dyn-old: Dynamic capability (deprecated version)") +TestCapability.okfail("Role: Role capability") +TestCapability.okfail("Role-long: Role capability, but too long") +TestCapability.okfail("Role-empty: Role capability, but empty.") +TestCapability.okfail("Cap-singlets: One capability per Optional-Param") +TestCapability.okfail("Cap-series: Series of capability, one Optional-Param") +TestCapability.okfail("AS4more: AS4 capability after other caps (singlets)") +TestCapability.okfail("AS4series: AS4 capability, in series of capabilities") +TestCapability.okfail("AS4real: AS4 capability, in series of capabilities") +TestCapability.okfail("AS4real2: AS4 capability, in series of capabilities") +TestCapability.okfail("DynCap: Dynamic Capability Message, IP/Multicast") +TestCapability.okfail("DynCapLong: Dynamic Capability Message, IP/Multicast, truncated") +TestCapability.okfail("DynCapPadded: Dynamic Capability Message, IP/Multicast, padded") +TestCapability.okfail( + "DynCapMPCpadded: Dynamic Capability Message, IP/Multicast, cap data padded" +) +TestCapability.okfail( + "DynCapMPCoverflow: Dynamic Capability Message, IP/Multicast, cap data != length" +) diff --git a/tests/bgpd/test_ecommunity.c b/tests/bgpd/test_ecommunity.c new file mode 100644 index 0000000..49322d3 --- /dev/null +++ b/tests/bgpd/test_ecommunity.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Sun Microsystems, Inc. + */ +#include + +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_ecommunity.h" + +/* need these to link in libbgp */ +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master = NULL; + +static int failed = 0; + +/* specification for a test - what the results should be */ +struct test_spec { + const char *shouldbe; /* the string the path should parse to */ +}; + + +/* test segments to parse and validate, and use for other tests */ +static struct test_segment { + const char *name; + const char *desc; + const uint8_t data[1024]; + int len; + struct test_spec sp; +} test_segments[] = {{/* 0 */ + "ipaddr", + "rt 1.2.3.4:257", + {ECOMMUNITY_ENCODE_IP, ECOMMUNITY_ROUTE_TARGET, 0x1, 0x2, + 0x3, 0x4, 0x1, 0x1}, + 8, + {"rt 1.2.3.4:257"}}, + {/* 1 */ + "ipaddr-so", + "soo 1.2.3.4:257", + {ECOMMUNITY_ENCODE_IP, ECOMMUNITY_SITE_ORIGIN, 0x1, 0x2, + 0x3, 0x4, 0x1, 0x1}, + 8, + {"soo 1.2.3.4:257"}}, + {/* 2 */ + "asn", + "rt 23456:987654321", + {ECOMMUNITY_ENCODE_AS, ECOMMUNITY_SITE_ORIGIN, 0x5b, 0xa0, + 0x3a, 0xde, 0x68, 0xb1}, + 8, + {"soo 23456:987654321"}}, + {/* 3 */ + "asn4", + "rt 168450976:4321", + {ECOMMUNITY_ENCODE_AS4, ECOMMUNITY_SITE_ORIGIN, 0xa, 0xa, + 0x5b, 0xa0, 0x10, 0xe1}, + 8, + {"soo 168450976:4321"}}, + {NULL, NULL, {0}, 0, {NULL}}}; + + +/* validate the given aspath */ +static int validate(struct ecommunity *ecom, const struct test_spec *sp) +{ + int fails = 0; + struct ecommunity *etmp; + char *str1, *str2; + + printf("got:\n %s\n", ecommunity_str(ecom)); + str1 = ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); + etmp = ecommunity_str2com(str1, 0, 1); + if (etmp) + str2 = ecommunity_ecom2str(etmp, + ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0); + else + str2 = NULL; + + if (strcmp(sp->shouldbe, str1)) { + failed++; + fails++; + printf("shouldbe: %s\n%s\n", str1, sp->shouldbe); + } + if (!etmp || strcmp(str1, str2)) { + failed++; + fails++; + printf("dogfood: in %s\n" + " in->out %s\n", + str1, (etmp && str2) ? str2 : "NULL"); + } + ecommunity_free(&etmp); + XFREE(MTYPE_ECOMMUNITY_STR, str1); + XFREE(MTYPE_ECOMMUNITY_STR, str2); + + return fails; +} + +/* basic parsing test */ +static void parse_test(struct test_segment *t) +{ + struct ecommunity *ecom; + + printf("%s: %s\n", t->name, t->desc); + + ecom = ecommunity_parse((uint8_t *)t->data, t->len, 0); + + printf("ecom: %s\nvalidating...:\n", ecommunity_str(ecom)); + + if (!validate(ecom, &t->sp)) + printf("OK\n"); + else + printf("failed\n"); + + printf("\n"); + ecommunity_unintern(&ecom); +} + + +int main(void) +{ + int i = 0; + ecommunity_init(); + while (test_segments[i].name) + parse_test(&test_segments[i++]); + + printf("failures: %d\n", failed); + // printf ("aspath count: %ld\n", aspath_count()); + return failed; + // return (failed + aspath_count()); +} diff --git a/tests/bgpd/test_ecommunity.py b/tests/bgpd/test_ecommunity.py new file mode 100644 index 0000000..1499294 --- /dev/null +++ b/tests/bgpd/test_ecommunity.py @@ -0,0 +1,11 @@ +import frrtest + + +class TestEcommunity(frrtest.TestMultiOut): + program = "./test_ecommunity" + + +TestEcommunity.okfail("ipaddr") +TestEcommunity.okfail("ipaddr-so") +TestEcommunity.okfail("asn") +TestEcommunity.okfail("asn4") diff --git a/tests/bgpd/test_mp_attr.c b/tests/bgpd/test_mp_attr.c new file mode 100644 index 0000000..44a2104 --- /dev/null +++ b/tests/bgpd/test_mp_attr.c @@ -0,0 +1,1119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2008 Sun Microsystems, Inc. + */ + +#include + +#include "qobj.h" +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_mplsvpn.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_label.h" + +#define VT100_RESET "\x1b[0m" +#define VT100_RED "\x1b[31m" +#define VT100_GREEN "\x1b[32m" +#define VT100_YELLOW "\x1b[33m" + +#define CAPABILITY 0 +#define DYNCAP 1 +#define OPT_PARAM 2 + +/* need these to link in libbgp */ +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master = NULL; + +static int failed = 0; +static int tty = 0; + +/* test segments to parse and validate, and use for other tests */ +static struct test_segment { + const char *name; + const char *desc; + const uint8_t data[1024]; + int len; +#define SHOULD_PARSE 0 +#define SHOULD_ERR -1 + int parses; /* whether it should parse or not */ +} mp_reach_segments[] = { + { + "IPv6", + "IPV6 MP Reach, global nexthop, 1 NLRI", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 16, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + }, + (4 + 16 + 1 + 5), + SHOULD_PARSE, + }, + { + "IPv6-2", + "IPV6 MP Reach, global nexthop, 2 NLRIs", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 16, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* ffee:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + }, + (4 + 16 + 1 + 5 + 9), + SHOULD_PARSE, + }, + { + "IPv6-default", + "IPV6 MP Reach, global nexthop, 2 NLRIs + default", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 16, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0x0, /* ::/0 */ + }, + (4 + 16 + 1 + 5 + 9 + 1), + SHOULD_PARSE, + }, + { + "IPv6-lnh", + "IPV6 MP Reach, global+local nexthops, 2 NLRIs + default", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 32, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x0, + 0x0, /* fe80::210:2ff:.. */ + 0x0, + 0x0, + 0x0, + 0x0, + 0x2, + 0x10, + 0x2, + 0xff, + 0x1, + 0x2, + 0x3, + 0x4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0x0, /* ::/0 */ + }, + (4 + 32 + 1 + 5 + 9 + 1), + SHOULD_PARSE, + }, + { + "IPv6-nhlen", + "IPV6 MP Reach, inappropriate nexthop length", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 4, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x0, + 0x0, /* fe80::210:2ff:.. */ + 0x0, + 0x0, + 0x0, + 0x0, + 0x2, + 0x10, + 0x2, + 0xff, + 0x1, + 0x2, + 0x3, + 0x4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0x0, /* ::/0 */ + }, + (4 + 32 + 1 + 5 + 9 + 1), + SHOULD_ERR, + }, + { + "IPv6-nhlen2", + "IPV6 MP Reach, invalid nexthop length", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 5, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x0, + 0x0, /* fe80::210:2ff:.. */ + 0x0, + 0x0, + 0x0, + 0x0, + 0x2, + 0x10, + 0x2, + 0xff, + 0x1, + 0x2, + 0x3, + 0x4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0x0, /* ::/0 */ + }, + (4 + 32 + 1 + 5 + 9 + 1), + SHOULD_ERR, + }, + { + "IPv6-nhlen3", + "IPV6 MP Reach, nexthop length overflow", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 32, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + }, + (4 + 16), + SHOULD_ERR, + }, + { + "IPv6-nhlen4", + "IPV6 MP Reach, nexthop length short", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 16, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x0, + 0x0, /* fe80::210:2ff:.. */ + 0x0, + 0x0, + 0x0, + 0x0, + 0x2, + 0x10, + 0x2, + 0xff, + 0x1, + 0x2, + 0x3, + 0x4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 32, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0x0, /* ::/0 */ + }, + (4 + 32 + 1 + 5 + 9 + 1), + SHOULD_ERR, + }, + { + "IPv6-nlri", + "IPV6 MP Reach, NLRI bitlen overflow", + { + /* AFI / SAFI */ 0x0, + AFI_IP6, + SAFI_UNICAST, + /* nexthop bytes */ 32, + /* Nexthop (global) */ 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102:... */ + 0xaa, + 0xbb, + 0xcc, + 0xdd, + 0x3, + 0x4, + 0x5, + 0x6, + 0xa1, + 0xa2, + 0xa3, + 0xa4, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x0, + 0x0, /* fe80::210:2ff:.. */ + 0x0, + 0x0, + 0x0, + 0x0, + 0x2, + 0x10, + 0x2, + 0xff, + 0x1, + 0x2, + 0x3, + 0x4, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 120, + 0xff, + 0xfe, + 0x1, + 0x2, /* fffe:102::/32 */ + 64, + 0xff, + 0xfe, + 0x0, + 0x1, /* fffe:1:2:3::/64 */ + 0x0, + 0x2, + 0x0, + 0x3, + 0, /* ::/0 */ + }, + (4 + 32 + 1 + 5 + 9 + 1), + SHOULD_ERR, + }, + { + "IPv4", + "IPv4 MP Reach, 2 NLRIs + default", + { + /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, + /* nexthop bytes */ 4, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ + 17, 10, 2, 3, /* 10.2.3/17 */ + 0, /* 0/0 */ + }, + (4 + 4 + 1 + 3 + 4 + 1), + SHOULD_PARSE, + }, + { + "IPv4-nhlen", + "IPv4 MP Reach, nexthop lenth overflow", + { + /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, + /* nexthop bytes */ 32, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ + 17, 10, 2, 3, /* 10.2.3/17 */ + 0, /* 0/0 */ + }, + (4 + 4 + 1 + 3 + 4 + 1), + SHOULD_ERR, + }, + { + "IPv4-nlrilen", + "IPv4 MP Reach, nlri lenth overflow", + { + /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, + /* nexthop bytes */ 4, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ + 30, 10, 0, /* 0/0 */ + }, + (4 + 4 + 1 + 3 + 2 + 1), + SHOULD_ERR, + }, + { + "IPv4-VPNv4", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_PARSE, + }, + { + "IPv4-VPNv4-bogus-plen", + "IPv4/MPLS-labeled VPN MP Reach, RD, Nexthop, NLRI / bogus p'len", + { + /* AFI / SAFI */ 0x0, + AFI_IP, + IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, + 0, + 1, + 2, + 0, + 0xff, + 3, + 4, + /* Nexthop */ 192, + 168, + 0, + 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 16, + 10, + 1, /* 10.1/16 */ + 17, + 10, + 2, + 3, /* 10.2.3/17 */ + 0, /* 0/0 */ + }, + (3 + 1 + 3 * 4 + 1 + 3 + 4 + 1), + SHOULD_ERR, + }, + { + "IPv4-VPNv4-plen1-short", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen short", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 1, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_ERR, + }, + { + "IPv4-VPNv4-plen1-long", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen long", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 32, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_ERR, + }, + { + "IPv4-VPNv4-plenn-long", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRIs, last plen long", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + 88 + 1, /* bogus */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), + SHOULD_ERR, + }, + { + "IPv4-VPNv4-plenn-short", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, last plen short", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 2, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_ERR, + }, + { + "IPv4-VPNv4-bogus-rd-type", + "IPv4/VPNv4 MP Reach, RD, NH, 2 NLRI, unknown RD in 1st (log, but parse)", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0xff, 0, /* Bogus RD */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_PARSE, + }, + { + "IPv4-VPNv4-0-nlri", + "IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRI, 3rd 0 bogus", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* nexthop bytes */ 12, + /* RD */ 0, 0, 0, 0, /* RD defined to be 0 */ + 0, 0, 0, 0, + /* Nexthop */ 192, 168, 0, 1, + /* SNPA (defunct, MBZ) */ 0x0, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + 0 /* 0/0, bogus for vpnv4 ?? */ + }, + (4 + 12 + 1 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3) + 1), + SHOULD_ERR, + }, + + /* From bug #385 */ + { + "IPv6-bug", + "IPv6, global nexthop, 1 default NLRI", + { + /* AFI / SAFI */ 0x0, + 0x2, + 0x1, + /* nexthop bytes */ 0x20, + /* Nexthop (global) */ 0x20, + 0x01, + 0x04, + 0x70, + 0x00, + 0x01, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + /* Nexthop (local) */ 0xfe, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x0c, + 0xdb, + 0xff, + 0xfe, + 0xfe, + 0xeb, + 0x00, + /* SNPA (defunct, MBZ) */ 0, + /* NLRI tuples */ /* Should have 0 here for ::/0, but + dont */ + }, + 37, + SHOULD_ERR, + }, + { + .name = "IPv4", + .desc = "IPV4 MP Reach, flowspec, 1 NLRI", + .data = { + /* AFI / SAFI */ 0x0, + AFI_IP, + IANA_SAFI_FLOWSPEC, + 0x00, /* no NH */ + 0x00, + 0x06, /* FS Length */ + 0x01, /* FS dest prefix ID */ + 0x1e, /* IP */ + 0x1e, + 0x28, + 0x28, + 0x0 + }, + .len = 12, + .parses = SHOULD_PARSE, + }, + {NULL, NULL, {0}, 0, 0}}; + +/* MP_UNREACH_NLRI tests */ +static struct test_segment mp_unreach_segments[] = { + { + "IPv6-unreach", + "IPV6 MP Unreach, 1 NLRI", + { + /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, + /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, + 0x2, /* fffe:102::/32 */ + }, + (3 + 5), + SHOULD_PARSE, + }, + { + "IPv6-unreach2", + "IPV6 MP Unreach, 2 NLRIs", + { + /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, + /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, + 0x2, /* fffe:102::/32 */ + 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ + 0x0, 0x2, 0x0, 0x3, + }, + (3 + 5 + 9), + SHOULD_PARSE, + }, + { + "IPv6-unreach-default", + "IPV6 MP Unreach, 2 NLRIs + default", + { + /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, + /* NLRI tuples */ 32, 0xff, 0xfe, 0x1, + 0x2, /* fffe:102::/32 */ + 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ + 0x0, 0x2, 0x0, 0x3, 0x0, /* ::/0 */ + }, + (3 + 5 + 9 + 1), + SHOULD_PARSE, + }, + { + "IPv6-unreach-nlri", + "IPV6 MP Unreach, NLRI bitlen overflow", + { + /* AFI / SAFI */ 0x0, AFI_IP6, SAFI_UNICAST, + /* NLRI tuples */ 120, 0xff, 0xfe, 0x1, + 0x2, /* fffe:102::/32 */ + 64, 0xff, 0xfe, 0x0, 0x1, /* fffe:1:2:3::/64 */ + 0x0, 0x2, 0x0, 0x3, 0, /* ::/0 */ + }, + (3 + 5 + 9 + 1), + SHOULD_ERR, + }, + { + "IPv4-unreach", + "IPv4 MP Unreach, 2 NLRIs + default", + { + /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, + /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ + 17, 10, 2, 3, /* 10.2.3/17 */ + 0, /* 0/0 */ + }, + (3 + 3 + 4 + 1), + SHOULD_PARSE, + }, + { + "IPv4-unreach-nlrilen", + "IPv4 MP Unreach, nlri length overflow", + { + /* AFI / SAFI */ 0x0, AFI_IP, SAFI_UNICAST, + /* NLRI tuples */ 16, 10, 1, /* 10.1/16 */ + 30, 10, 0, /* 0/0 */ + }, + (3 + 3 + 2 + 1), + SHOULD_ERR, + }, + { + "IPv4-unreach-VPNv4", + "IPv4/MPLS-labeled VPN MP Unreach, RD, 3 NLRIs", + { + /* AFI / SAFI */ 0x0, AFI_IP, IANA_SAFI_MPLS_VPN, + /* NLRI tuples */ 88 + 16, 0, 1, 2, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_AS */ + 0, 2, 0, 0xff, 3, 4, /* AS(2):val(4) */ + 10, 1, /* 10.1/16 */ + 88 + 17, 0xff, 0, 0, /* tag */ + /* rd, 8 octets */ + 0, 0, /* RD_TYPE_IP */ + 192, 168, 0, 1, /* IPv4 */ + 10, 2, 3, /* 10.2.3/17 */ + }, + (3 + (1 + 3 + 8 + 2) + (1 + 3 + 8 + 3)), + SHOULD_PARSE, + }, + { + .name = "IPv4", + .desc = "IPV4 MP Unreach, flowspec, 1 NLRI", + .data = { + /* AFI / SAFI */ 0x0, + AFI_IP, + IANA_SAFI_FLOWSPEC, + 0x06, /* FS Length */ + 0x01, /* FS dest prefix ID */ + 0x1e, /* IP */ + 0x1e, + 0x28, + 0x28, + 0x0 + }, + .len = 10, + .parses = SHOULD_PARSE, + }, + {NULL, NULL, {0}, 0, 0}}; + +static struct test_segment mp_prefix_sid[] = { + { + "PREFIX-SID", + "PREFIX-SID Test 1", + { + /* TLV[0] Latel-Index TLV */ + 0x01, /* Type 0x01:Label-Index */ + 0x00, 0x07, /* Length */ + 0x00, /* RESERVED */ + 0x00, 0x00, /* Flags */ + 0x00, 0x00, 0x00, 0x02, /* Label Index */ + + /* TLV[1] SRGB TLV */ + 0x03, /* Type 0x03:SRGB */ + 0x00, 0x08, /* Length */ + 0x00, 0x00, /* Flags */ + 0x0a, 0x1b, 0xfe, /* SRGB[0] first label */ + 0x00, 0x00, 0x0a /* SRBG[0] nb-labels in range */ + }, + .len = 21, + .parses = SHOULD_PARSE, + }, + {NULL, NULL, { 0 }, 0, 0}, +}; + +/* nlri_parse indicates 0 on successful parse, and -1 otherwise. + * attr_parse indicates BGP_ATTR_PARSE_PROCEED/0 on success, + * and BGP_ATTR_PARSE_ERROR/-1 or lower negative ret on err. + */ +static void handle_result(struct peer *peer, struct test_segment *t, + int parse_ret, int nlri_ret) +{ + int oldfailed = failed; + + printf("mp attr parsed?: %s\n", parse_ret ? "no" : "yes"); + if (!parse_ret) + printf("nrli parsed?: %s\n", nlri_ret ? "no" : "yes"); + printf("should parse?: %s\n", t->parses ? "no" : "yes"); + + if ((parse_ret != 0 || nlri_ret != 0) != (t->parses != 0)) + failed++; + + + if (tty) + printf("%s", + (failed > oldfailed) ? VT100_RED "failed!" VT100_RESET + : VT100_GREEN "OK" VT100_RESET); + else + printf("%s", (failed > oldfailed) ? "failed!" : "OK"); + + if (failed) + printf(" (%u)", failed); + + printf("\n\n"); +} + +/* basic parsing test */ +static void parse_test(struct peer *peer, struct test_segment *t, int type) +{ + int parse_ret = 0, nlri_ret = 0; + struct attr attr = {}; + struct bgp_nlri nlri = {}; + struct bgp_attr_parser_args attr_args = { + .peer = peer, + .length = t->len, + .total = 1, + .attr = &attr, + .type = type, + .flags = BGP_ATTR_FLAG_OPTIONAL, + .startp = BGP_INPUT_PNT(peer), + }; +#define RANDOM_FUZZ 35 + stream_reset(peer->curr); + stream_put(peer->curr, NULL, RANDOM_FUZZ); + stream_set_getp(peer->curr, RANDOM_FUZZ); + + stream_write(peer->curr, t->data, t->len); + + printf("%s: %s\n", t->name, t->desc); + + switch (type) { + case BGP_ATTR_MP_REACH_NLRI: + parse_ret = bgp_mp_reach_parse(&attr_args, &nlri); + break; + case BGP_ATTR_MP_UNREACH_NLRI: + parse_ret = bgp_mp_unreach_parse(&attr_args, &nlri); + break; + case BGP_ATTR_PREFIX_SID: + parse_ret = bgp_attr_prefix_sid(&attr_args); + break; + default: + printf("unknown type"); + return; + } + if (!parse_ret) { + iana_afi_t pkt_afi; + iana_safi_t pkt_safi; + + /* Convert AFI, SAFI to internal values, check. */ + if (bgp_map_afi_safi_int2iana(nlri.afi, nlri.safi, &pkt_afi, + &pkt_safi)) + assert(0); + + printf("MP: %u(%u)/%u(%u): recv %u, nego %u\n", nlri.afi, + pkt_afi, nlri.safi, pkt_safi, + peer->afc_recv[nlri.afi][nlri.safi], + peer->afc_nego[nlri.afi][nlri.safi]); + } + + if (!parse_ret) { + if (type == BGP_ATTR_MP_REACH_NLRI) + nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, false); + else if (type == BGP_ATTR_MP_UNREACH_NLRI) + nlri_ret = bgp_nlri_parse(peer, &attr, &nlri, true); + } + handle_result(peer, t, parse_ret, nlri_ret); +} + +static struct bgp *bgp; +static as_t asn = 100; + +int main(void) +{ + struct interface ifp; + struct peer *peer; + int i, j; + + conf_bgp_debug_neighbor_events = -1UL; + conf_bgp_debug_packet = -1UL; + conf_bgp_debug_as4 = -1UL; + conf_bgp_debug_flowspec = -1UL; + term_bgp_debug_neighbor_events = -1UL; + term_bgp_debug_packet = -1UL; + term_bgp_debug_as4 = -1UL; + term_bgp_debug_flowspec = -1UL; + + qobj_init(); + cmd_init(0); + bgp_vty_init(); + master = event_master_create("test mp attr"); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + vrf_init(NULL, NULL, NULL, NULL); + bgp_option_set(BGP_OPT_NO_LISTEN); + bgp_attr_init(); + bgp_labels_init(); + + if (fileno(stdout) >= 0) + tty = isatty(fileno(stdout)); + + if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT, NULL, + ASNOTATION_PLAIN) < 0) + return -1; + + peer = peer_create_accept(bgp); + peer->host = (char *)"foo"; + peer->connection = bgp_peer_connection_new(peer); + peer->connection->status = Established; + peer->curr = stream_new(BGP_MAX_PACKET_SIZE); + + ifp.ifindex = 0; + peer->nexthop.ifp = &ifp; + + for (i = AFI_IP; i < AFI_MAX; i++) + for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { + peer->afc[i][j] = 1; + peer->afc_adv[i][j] = 1; + } + + i = 0; + while (mp_reach_segments[i].name) + parse_test(peer, &mp_reach_segments[i++], + BGP_ATTR_MP_REACH_NLRI); + + i = 0; + while (mp_unreach_segments[i].name) + parse_test(peer, &mp_unreach_segments[i++], + BGP_ATTR_MP_UNREACH_NLRI); + + i = 0; + while (mp_prefix_sid[i].name) + parse_test(peer, &mp_prefix_sid[i++], + BGP_ATTR_PREFIX_SID); + printf("failures: %d\n", failed); + return failed; +} diff --git a/tests/bgpd/test_mp_attr.py b/tests/bgpd/test_mp_attr.py new file mode 100644 index 0000000..d9612bb --- /dev/null +++ b/tests/bgpd/test_mp_attr.py @@ -0,0 +1,49 @@ +import frrtest + + +class TestMpAttr(frrtest.TestMultiOut): + program = "./test_mp_attr" + + +TestMpAttr.okfail("IPv6: IPV6 MP Reach, global nexthop, 1 NLRI") +TestMpAttr.okfail("IPv6-2: IPV6 MP Reach, global nexthop, 2 NLRIs") +TestMpAttr.okfail("IPv6-default: IPV6 MP Reach, global nexthop, 2 NLRIs + default") +TestMpAttr.okfail("IPv6-lnh: IPV6 MP Reach, global+local nexthops, 2 NLRIs + default") +TestMpAttr.okfail("IPv6-nhlen: IPV6 MP Reach, inappropriate nexthop length") +TestMpAttr.okfail("IPv6-nhlen2: IPV6 MP Reach, invalid nexthop length") +TestMpAttr.okfail("IPv6-nhlen3: IPV6 MP Reach, nexthop length overflow") +TestMpAttr.okfail("IPv6-nhlen4: IPV6 MP Reach, nexthop length short") +TestMpAttr.okfail("IPv6-nlri: IPV6 MP Reach, NLRI bitlen overflow") +TestMpAttr.okfail("IPv4: IPv4 MP Reach, 2 NLRIs + default") +TestMpAttr.okfail("IPv4-nhlen: IPv4 MP Reach, nexthop lenth overflow") +TestMpAttr.okfail("IPv4-nlrilen: IPv4 MP Reach, nlri lenth overflow") +TestMpAttr.okfail("IPv4-VPNv4: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs") +TestMpAttr.okfail( + "IPv4-VPNv4-bogus-plen: IPv4/MPLS-labeled VPN MP Reach, RD, Nexthop, NLRI / bogus p'len" +) +TestMpAttr.okfail( + "IPv4-VPNv4-plen1-short: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen short" +) +TestMpAttr.okfail( + "IPv4-VPNv4-plen1-long: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, 1st plen long" +) +TestMpAttr.okfail( + "IPv4-VPNv4-plenn-long: IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRIs, last plen long" +) +TestMpAttr.okfail( + "IPv4-VPNv4-plenn-short: IPv4/VPNv4 MP Reach, RD, Nexthop, 2 NLRIs, last plen short" +) +TestMpAttr.okfail( + "IPv4-VPNv4-bogus-rd-type: IPv4/VPNv4 MP Reach, RD, NH, 2 NLRI, unknown RD in 1st (log, but parse)" +) +TestMpAttr.okfail( + "IPv4-VPNv4-0-nlri: IPv4/VPNv4 MP Reach, RD, Nexthop, 3 NLRI, 3rd 0 bogus" +) +TestMpAttr.okfail("IPv6-bug: IPv6, global nexthop, 1 default NLRI") +TestMpAttr.okfail("IPv6-unreach: IPV6 MP Unreach, 1 NLRI") +TestMpAttr.okfail("IPv6-unreach2: IPV6 MP Unreach, 2 NLRIs") +TestMpAttr.okfail("IPv6-unreach-default: IPV6 MP Unreach, 2 NLRIs + default") +TestMpAttr.okfail("IPv6-unreach-nlri: IPV6 MP Unreach, NLRI bitlen overflow") +TestMpAttr.okfail("IPv4-unreach: IPv4 MP Unreach, 2 NLRIs + default") +TestMpAttr.okfail("IPv4-unreach-nlrilen: IPv4 MP Unreach, nlri length overflow") +TestMpAttr.okfail("IPv4-unreach-VPNv4: IPv4/MPLS-labeled VPN MP Unreach, RD, 3 NLRIs") diff --git a/tests/bgpd/test_mpath.c b/tests/bgpd/test_mpath.c new file mode 100644 index 0000000..ebbe3ac --- /dev/null +++ b/tests/bgpd/test_mpath.c @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Multipath Unit Test + * Copyright (C) 2010 Google Inc. + * + * This file is part of Quagga + */ + +#include + +#include "qobj.h" +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "linklist.h" +#include "memory.h" +#include "zclient.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_table.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_nexthop.h" +#include "bgpd/bgp_mpath.h" +#include "bgpd/bgp_evpn.h" +#include "bgpd/bgp_network.h" + +#define VT100_RESET "\x1b[0m" +#define VT100_RED "\x1b[31m" +#define VT100_GREEN "\x1b[32m" +#define VT100_YELLOW "\x1b[33m" +#define OK VT100_GREEN "OK" VT100_RESET +#define FAILED VT100_RED "failed" VT100_RESET + +#define TEST_PASSED 0 +#define TEST_FAILED -1 + +#define EXPECT_TRUE(expr, res) \ + if (!(expr)) { \ + printf("Test failure in %s line %u: %s\n", __func__, __LINE__, \ + #expr); \ + (res) = TEST_FAILED; \ + } + +typedef struct testcase_t__ testcase_t; + +typedef int (*test_setup_func)(testcase_t *); +typedef int (*test_run_func)(testcase_t *); +typedef int (*test_cleanup_func)(testcase_t *); + +struct testcase_t__ { + const char *desc; + void *test_data; + void *verify_data; + void *tmp_data; + test_setup_func setup; + test_run_func run; + test_cleanup_func cleanup; +}; + +/* need these to link in libbgp */ +struct event_loop *master = NULL; +extern struct zclient *zclient; +struct zebra_privs_t bgpd_privs = { + .user = NULL, + .group = NULL, + .vty_group = NULL, +}; + +static int tty = 0; + +/* Create fake bgp instance */ +static struct bgp *bgp_create_fake(as_t *as, const char *name) +{ + struct bgp *bgp; + afi_t afi; + safi_t safi; + + if ((bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp))) == NULL) + return NULL; + + bgp_lock(bgp); + // bgp->peer_self = peer_new (bgp); + // bgp->peer_self->host = XSTRDUP (MTYPE_BGP_PEER_HOST, "Static + // announcement"); + + bgp->peer = list_new(); + // bgp->peer->cmp = (int (*)(void *, void *)) peer_cmp; + + bgp->group = list_new(); + // bgp->group->cmp = (int (*)(void *, void *)) peer_group_cmp; + + bgp_evpn_init(bgp); + FOREACH_AFI_SAFI (afi, safi) { + bgp->route[afi][safi] = bgp_table_init(bgp, afi, safi); + bgp->aggregate[afi][safi] = bgp_table_init(bgp, afi, safi); + bgp->rib[afi][safi] = bgp_table_init(bgp, afi, safi); + bgp->maxpaths[afi][safi].maxpaths_ebgp = MULTIPATH_NUM; + bgp->maxpaths[afi][safi].maxpaths_ibgp = MULTIPATH_NUM; + } + + bgp_scan_init(bgp); + bgp->default_local_pref = BGP_DEFAULT_LOCAL_PREF; + bgp->default_holdtime = BGP_DEFAULT_HOLDTIME; + bgp->default_keepalive = BGP_DEFAULT_KEEPALIVE; + bgp->restart_time = BGP_DEFAULT_RESTART_TIME; + bgp->stalepath_time = BGP_DEFAULT_STALEPATH_TIME; + + bgp->as = *as; + + if (name) + bgp->name = strdup(name); + + return bgp; +} + +/*========================================================= + * Testcase for maximum-paths configuration + */ +static int setup_bgp_cfg_maximum_paths(testcase_t *t) +{ + as_t asn = 1; + t->tmp_data = bgp_create_fake(&asn, NULL); + if (!t->tmp_data) + return -1; + return 0; +} + +static int run_bgp_cfg_maximum_paths(testcase_t *t) +{ + afi_t afi; + safi_t safi; + struct bgp *bgp; + int api_result; + int test_result = TEST_PASSED; + + bgp = t->tmp_data; + FOREACH_AFI_SAFI (afi, safi) { + /* test bgp_maximum_paths_set */ + api_result = bgp_maximum_paths_set(bgp, afi, safi, + BGP_PEER_EBGP, 10, 0); + EXPECT_TRUE(api_result == 0, test_result); + api_result = bgp_maximum_paths_set(bgp, afi, safi, + BGP_PEER_IBGP, 10, 0); + EXPECT_TRUE(api_result == 0, test_result); + EXPECT_TRUE(bgp->maxpaths[afi][safi].maxpaths_ebgp == 10, + test_result); + EXPECT_TRUE(bgp->maxpaths[afi][safi].maxpaths_ibgp == 10, + test_result); + + /* test bgp_maximum_paths_unset */ + api_result = + bgp_maximum_paths_unset(bgp, afi, safi, BGP_PEER_EBGP); + EXPECT_TRUE(api_result == 0, test_result); + api_result = + bgp_maximum_paths_unset(bgp, afi, safi, BGP_PEER_IBGP); + EXPECT_TRUE(api_result == 0, test_result); + EXPECT_TRUE((bgp->maxpaths[afi][safi].maxpaths_ebgp + == MULTIPATH_NUM), + test_result); + EXPECT_TRUE((bgp->maxpaths[afi][safi].maxpaths_ibgp + == MULTIPATH_NUM), + test_result); + } + + return test_result; +} + +static int cleanup_bgp_cfg_maximum_paths(testcase_t *t) +{ + return bgp_delete((struct bgp *)t->tmp_data); +} + +testcase_t test_bgp_cfg_maximum_paths = { + .desc = "Test bgp maximum-paths config", + .setup = setup_bgp_cfg_maximum_paths, + .run = run_bgp_cfg_maximum_paths, + .cleanup = cleanup_bgp_cfg_maximum_paths, +}; + +/*========================================================= + * Testcase for bgp_mp_list + */ +struct peer test_mp_list_peer[] = { + {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, + {.local_as = 1, .as = 2}, {.local_as = 1, .as = 2}, + {.local_as = 1, .as = 2}, +}; +int test_mp_list_peer_count = array_size(test_mp_list_peer); +struct attr test_mp_list_attr[4]; +struct bgp_path_info test_mp_list_info[] = { + {.peer = &test_mp_list_peer[0], .attr = &test_mp_list_attr[0]}, + {.peer = &test_mp_list_peer[1], .attr = &test_mp_list_attr[1]}, + {.peer = &test_mp_list_peer[2], .attr = &test_mp_list_attr[1]}, + {.peer = &test_mp_list_peer[3], .attr = &test_mp_list_attr[2]}, + {.peer = &test_mp_list_peer[4], .attr = &test_mp_list_attr[3]}, +}; +int test_mp_list_info_count = array_size(test_mp_list_info); + +static int setup_bgp_mp_list(testcase_t *t) +{ + test_mp_list_attr[0].nexthop.s_addr = 0x01010101; + test_mp_list_attr[1].nexthop.s_addr = 0x02020202; + test_mp_list_attr[2].nexthop.s_addr = 0x03030303; + test_mp_list_attr[3].nexthop.s_addr = 0x04040404; + + if ((test_mp_list_peer[0].su_remote = sockunion_str2su("1.1.1.1")) + == NULL) + return -1; + if ((test_mp_list_peer[1].su_remote = sockunion_str2su("2.2.2.2")) + == NULL) + return -1; + if ((test_mp_list_peer[2].su_remote = sockunion_str2su("3.3.3.3")) + == NULL) + return -1; + if ((test_mp_list_peer[3].su_remote = sockunion_str2su("4.4.4.4")) + == NULL) + return -1; + if ((test_mp_list_peer[4].su_remote = sockunion_str2su("5.5.5.5")) + == NULL) + return -1; + + return 0; +} + +static int run_bgp_mp_list(testcase_t *t) +{ + struct list mp_list; + struct listnode *mp_node; + struct bgp_path_info *info; + int i; + int test_result = TEST_PASSED; + bgp_mp_list_init(&mp_list); + EXPECT_TRUE(listcount(&mp_list) == 0, test_result); + + bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[4]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[2]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[3]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); + + for (i = 0, mp_node = mp_list.head; i < test_mp_list_info_count; + i++, mp_node = listnextnode(mp_node)) { + info = listgetdata(mp_node); + info->lock++; + EXPECT_TRUE(info == &test_mp_list_info[i], test_result); + } + + bgp_mp_list_clear(&mp_list); + EXPECT_TRUE(listcount(&mp_list) == 0, test_result); + + return test_result; +} + +static int cleanup_bgp_mp_list(testcase_t *t) +{ + int i; + + for (i = 0; i < test_mp_list_peer_count; i++) + sockunion_free(test_mp_list_peer[i].su_remote); + + return 0; +} + +testcase_t test_bgp_mp_list = { + .desc = "Test bgp_mp_list", + .setup = setup_bgp_mp_list, + .run = run_bgp_mp_list, + .cleanup = cleanup_bgp_mp_list, +}; + +/*========================================================= + * Testcase for bgp_path_info_mpath_update + */ + +static struct bgp_dest *dest; + +static int setup_bgp_path_info_mpath_update(testcase_t *t) +{ + int i; + struct bgp *bgp; + struct bgp_table *rt; + struct prefix p; + as_t asn = 1; + + t->tmp_data = bgp_create_fake(&asn, NULL); + if (!t->tmp_data) + return -1; + + bgp = t->tmp_data; + rt = bgp->rib[AFI_IP][SAFI_UNICAST]; + + if (!rt) + return -1; + + str2prefix("42.1.1.0/24", &p); + dest = bgp_node_get(rt, &p); + + setup_bgp_mp_list(t); + for (i = 0; i < test_mp_list_info_count; i++) + bgp_path_info_add(dest, &test_mp_list_info[i]); + return 0; +} + +static int run_bgp_path_info_mpath_update(testcase_t *t) +{ + struct bgp_path_info *new_best, *old_best, *mpath; + struct list mp_list; + struct bgp_maxpaths_cfg mp_cfg = {3, 3}; + + int test_result = TEST_PASSED; + bgp_mp_list_init(&mp_list); + bgp_mp_list_add(&mp_list, &test_mp_list_info[4]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[3]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); + new_best = &test_mp_list_info[3]; + old_best = NULL; + bgp_path_info_mpath_update(NULL, dest, new_best, old_best, &mp_list, + &mp_cfg); + bgp_mp_list_clear(&mp_list); + EXPECT_TRUE(bgp_path_info_mpath_count(new_best) == 2, test_result); + mpath = bgp_path_info_mpath_first(new_best); + EXPECT_TRUE(mpath == &test_mp_list_info[0], test_result); + EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); + mpath = bgp_path_info_mpath_next(mpath); + EXPECT_TRUE(mpath == &test_mp_list_info[1], test_result); + EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); + + bgp_mp_list_add(&mp_list, &test_mp_list_info[0]); + bgp_mp_list_add(&mp_list, &test_mp_list_info[1]); + new_best = &test_mp_list_info[0]; + old_best = &test_mp_list_info[3]; + bgp_path_info_mpath_update(NULL, dest, new_best, old_best, &mp_list, + &mp_cfg); + bgp_mp_list_clear(&mp_list); + EXPECT_TRUE(bgp_path_info_mpath_count(new_best) == 1, test_result); + mpath = bgp_path_info_mpath_first(new_best); + EXPECT_TRUE(mpath == &test_mp_list_info[1], test_result); + EXPECT_TRUE(CHECK_FLAG(mpath->flags, BGP_PATH_MULTIPATH), test_result); + EXPECT_TRUE(!CHECK_FLAG(test_mp_list_info[0].flags, BGP_PATH_MULTIPATH), + test_result); + + return test_result; +} + +static int cleanup_bgp_path_info_mpath_update(testcase_t *t) +{ + int i; + + for (i = 0; i < test_mp_list_peer_count; i++) + sockunion_free(test_mp_list_peer[i].su_remote); + + return bgp_delete((struct bgp *)t->tmp_data); +} + +testcase_t test_bgp_path_info_mpath_update = { + .desc = "Test bgp_path_info_mpath_update", + .setup = setup_bgp_path_info_mpath_update, + .run = run_bgp_path_info_mpath_update, + .cleanup = cleanup_bgp_path_info_mpath_update, +}; + +/*========================================================= + * Set up testcase vector + */ +testcase_t *all_tests[] = { + &test_bgp_cfg_maximum_paths, &test_bgp_mp_list, + &test_bgp_path_info_mpath_update, +}; + +int all_tests_count = array_size(all_tests); + +/*========================================================= + * Test Driver Functions + */ +static int global_test_init(void) +{ + qobj_init(); + master = event_master_create(NULL); + zclient = zclient_new(master, &zclient_options_default, NULL, 0); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + vrf_init(NULL, NULL, NULL, NULL); + bgp_option_set(BGP_OPT_NO_LISTEN); + + if (fileno(stdout) >= 0) + tty = isatty(fileno(stdout)); + return 0; +} + +static int global_test_cleanup(void) +{ + if (zclient != NULL) + zclient_free(zclient); + event_master_free(master); + return 0; +} + +static void display_result(testcase_t *test, int result) +{ + if (tty) + printf("%s: %s\n", test->desc, + result == TEST_PASSED ? OK : FAILED); + else + printf("%s: %s\n", test->desc, + result == TEST_PASSED ? "OK" : "FAILED"); +} + +static int setup_test(testcase_t *t) +{ + int res = 0; + if (t->setup) + res = t->setup(t); + return res; +} + +static int cleanup_test(testcase_t *t) +{ + int res = 0; + if (t->cleanup) + res = t->cleanup(t); + return res; +} + +static void run_tests(testcase_t *tests[], int num_tests, int *pass_count, + int *fail_count) +{ + int test_index, result; + testcase_t *cur_test; + + *pass_count = *fail_count = 0; + + for (test_index = 0; test_index < num_tests; test_index++) { + cur_test = tests[test_index]; + if (!cur_test->desc) { + printf("error: test %d has no description!\n", + test_index); + continue; + } + if (!cur_test->run) { + printf("error: test %s has no run function!\n", + cur_test->desc); + continue; + } + if (setup_test(cur_test) != 0) { + printf("error: setup failed for test %s\n", + cur_test->desc); + continue; + } + result = cur_test->run(cur_test); + if (result == TEST_PASSED) + *pass_count += 1; + else + *fail_count += 1; + display_result(cur_test, result); + if (cleanup_test(cur_test) != 0) { + printf("error: cleanup failed for test %s\n", + cur_test->desc); + continue; + } + } +} + +int main(void) +{ + int pass_count, fail_count; + time_t cur_time; + char buf[32]; + + time(&cur_time); + printf("BGP Multipath Tests Run at %s", ctime_r(&cur_time, buf)); + if (global_test_init() != 0) { + printf("Global init failed. Terminating.\n"); + exit(1); + } + run_tests(all_tests, all_tests_count, &pass_count, &fail_count); + global_test_cleanup(); + printf("Total pass/fail: %d/%d\n", pass_count, fail_count); + return fail_count; +} diff --git a/tests/bgpd/test_mpath.py b/tests/bgpd/test_mpath.py new file mode 100644 index 0000000..582fd25 --- /dev/null +++ b/tests/bgpd/test_mpath.py @@ -0,0 +1,10 @@ +import frrtest + + +class TestMpath(frrtest.TestMultiOut): + program = "./test_mpath" + + +TestMpath.okfail("bgp maximum-paths config") +TestMpath.okfail("bgp_mp_list") +TestMpath.okfail("bgp_path_info_mpath_update") diff --git a/tests/bgpd/test_packet.c b/tests/bgpd/test_packet.c new file mode 100644 index 0000000..e050fd4 --- /dev/null +++ b/tests/bgpd/test_packet.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Cumulus Networks Inc. + * Donald Sharp + * + * This file is part of FRR + */ + +#include +#include + +#include "qobj.h" +#include "vty.h" +#include "stream.h" +#include "privs.h" +#include "memory.h" +#include "queue.h" +#include "filter.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_open.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_packet.h" +#include "bgpd/bgp_aspath.h" +#include "bgpd/bgp_network.h" + +/* need these to link in libbgp */ +struct zebra_privs_t bgpd_privs = {}; +struct event_loop *master = NULL; + +static struct bgp *bgp; +static as_t asn = 100; + +extern int bgp_read_packet(struct peer *peer); + +/* + * This file is intended to be used as input for some sort of + * fuzzer. Specifically I had afl in mind when I wrote + * this code. + */ +int main(int argc, char *argv[]) +{ + struct peer *peer; + int i, j; + struct event t; + + qobj_init(); + bgp_attr_init(); + master = event_master_create(NULL); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + vrf_init(NULL, NULL, NULL, NULL); + bgp_option_set(BGP_OPT_NO_LISTEN); + + if (bgp_get(&bgp, &asn, NULL, BGP_INSTANCE_TYPE_DEFAULT, NULL, + ASNOTATION_PLAIN) < 0) + return -1; + + peer = peer_create_accept(bgp); + peer->host = (char *)"foo"; + + for (i = AFI_IP; i < AFI_MAX; i++) + for (j = SAFI_UNICAST; j < SAFI_MAX; j++) { + peer->afc[i][j] = 1; + peer->afc_adv[i][j] = 1; + } + + SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); + peer->connection = bgp_peer_connection_new(peer); + peer->connection->status = Established; + + peer->connection->fd = open(argv[1], O_RDONLY | O_NONBLOCK); + t.arg = peer; + peer->connection->t_read = &t; + + // printf("bgp_read_packet returns: %d\n", bgp_read(&t)); +} diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c new file mode 100644 index 0000000..767c41c --- /dev/null +++ b/tests/bgpd/test_peer_attr.c @@ -0,0 +1,1481 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BGP Peer Attribute Unit Tests + * Copyright (C) 2018 Pascal Mathis + */ +#include + +#include "memory.h" +#include "plist.h" +#include "printfrr.h" +#include "bgpd/bgpd.h" +#include "bgpd/bgp_attr.h" +#include "bgpd/bgp_regex.h" +#include "bgpd/bgp_clist.h" +#include "bgpd/bgp_dump.h" +#include "bgpd/bgp_filter.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_network.h" +#include "bgpd/bgp_label.h" + +#ifdef ENABLE_BGP_VNC +#include "bgpd/rfapi/rfapi_backend.h" +#endif + +#define OUT_SYMBOL_INFO "\u25ba" +#define OUT_SYMBOL_OK "\u2714" +#define OUT_SYMBOL_NOK "\u2716" + +#define TEST_ASSERT(T, C) \ + do { \ + if ((T)->state != TEST_SUCCESS || (C)) \ + break; \ + (T)->state = TEST_ASSERT_ERROR; \ + (T)->error = \ + asprintfrr(MTYPE_TMP, "assertion failed: %s (%s:%d)", \ + (#C), __FILE__, __LINE__); \ + } while (0) + +#define TEST_ASSERT_EQ(T, A, B) \ + do { \ + if ((T)->state != TEST_SUCCESS || ((A) == (B))) \ + break; \ + (T)->state = TEST_ASSERT_ERROR; \ + (T)->error = asprintfrr( \ + MTYPE_TMP, \ + "assertion failed: %s[%lld] == [%lld]%s (%s:%d)", \ + (#A), (long long)(A), (long long)(B), (#B), __FILE__, \ + __LINE__); \ + } while (0) + +#define TEST_HANDLER_MAX 5 +#define TEST_HANDLER(name) _test_handler_##name +#define TEST_HANDLER_DECL(name) \ + static void _test_handler_##name( \ + struct test *test, struct test_peer_attr *pa, \ + struct peer *peer, struct peer *group, bool peer_set, \ + bool group_set) + +#define TEST_ATTR_HANDLER_DECL(name, attr, pval, gval) \ + TEST_HANDLER_DECL(name) \ + { \ + if (peer_set) \ + TEST_ASSERT_EQ(test, peer->attr, (pval)); \ + else if (peer_group_active(peer) && group_set) \ + TEST_ASSERT_EQ(test, peer->attr, (gval)); \ + if (group_set) \ + TEST_ASSERT_EQ(test, group->attr, (gval)); \ + } \ + TEST_HANDLER_DECL(name) + +#define TEST_STR_ATTR_HANDLER_DECL(name, attr, pval, gval) \ + TEST_HANDLER_DECL(name) \ + { \ + if (peer_set) { \ + TEST_ASSERT(test, peer->attr != NULL); \ + TEST_ASSERT(test, !strcmp(peer->attr, (pval))); \ + } else if (peer_group_active(peer) && group_set) { \ + TEST_ASSERT(test, peer->attr != NULL); \ + TEST_ASSERT(test, !strcmp(peer->attr, (gval))); \ + } \ + if (group_set) { \ + TEST_ASSERT(test, group->attr != NULL); \ + TEST_ASSERT(test, !strcmp(group->attr, (gval))); \ + } \ + } \ + TEST_HANDLER_DECL(name) + +#define TEST_SU_ATTR_HANDLER_DECL(name, attr, pval, gval) \ + TEST_HANDLER_DECL(name) \ + { \ + union sockunion su; \ + if (peer_set) { \ + str2sockunion(pval, &su); \ + TEST_ASSERT(test, !sockunion_cmp(peer->attr, &su)); \ + } else if (peer_group_active(peer) && group_set) { \ + str2sockunion(gval, &su); \ + TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ + } \ + if (group_set) { \ + str2sockunion(gval, &su); \ + TEST_ASSERT(test, !sockunion_cmp(group->attr, &su)); \ + } \ + } \ + TEST_HANDLER_DECL(name) + +/* Required variables to link in libbgp */ +struct zebra_privs_t bgpd_privs = {0}; +struct event_loop *master; + +enum test_state { + TEST_SUCCESS, + TEST_SKIPPING, + TEST_COMMAND_ERROR, + TEST_CONFIG_ERROR, + TEST_ASSERT_ERROR, + TEST_CUSTOM_ERROR, + TEST_INTERNAL_ERROR, +}; + +enum test_peer_attr_type { + PEER_AT_AF_FLAG = 0, + PEER_AT_AF_FILTER = 1, + PEER_AT_AF_CUSTOM = 2, + PEER_AT_GLOBAL_FLAG = 3, + PEER_AT_GLOBAL_CUSTOM = 4 +}; + +struct test { + enum test_state state; + char *desc; + char *error; + struct list *log; + + struct vty *vty; + struct bgp *bgp; + struct peer *peer; + struct peer_group *group; + + struct { + bool use_ibgp; + bool use_iface_peer; + } o; +}; + +struct test_config { + int local_asn; + int peer_asn; + const char *peer_address; + const char *peer_interface; + const char *peer_group; +}; + +struct test_peer_family { + afi_t afi; + safi_t safi; +}; + +struct test_peer_attr { + const char *cmd; + const char *peer_cmd; + const char *group_cmd; + + enum test_peer_attr_type type; + union { + uint64_t flag; + struct { + uint64_t flag; + size_t direct; + } filter; + } u; + struct { + bool invert_peer; + bool invert_group; + bool use_ibgp; + bool use_iface_peer; + bool skip_xfer_cases; + } o; + + afi_t afi; + safi_t safi; + struct test_peer_family families[AFI_MAX * SAFI_MAX]; + + void (*handlers[TEST_HANDLER_MAX])(struct test *test, + struct test_peer_attr *pa, + struct peer *peer, + struct peer *group, bool peer_set, + bool group_set); +}; + +static struct test_config cfg = { + .local_asn = 100, + .peer_asn = 200, + .peer_address = "1.1.1.1", + .peer_interface = "IP-TEST", + .peer_group = "PG-TEST", +}; + +static struct test_peer_family test_default_families[] = { + {.afi = AFI_IP, .safi = SAFI_UNICAST}, + {.afi = AFI_IP, .safi = SAFI_MULTICAST}, + {.afi = AFI_IP6, .safi = SAFI_UNICAST}, + {.afi = AFI_IP6, .safi = SAFI_MULTICAST}, +}; + +TEST_ATTR_HANDLER_DECL(advertisement_interval, v_routeadv, 10, 20); +TEST_STR_ATTR_HANDLER_DECL(password, password, "FRR-Peer", "FRR-Group"); +TEST_ATTR_HANDLER_DECL(local_as, change_local_as, 1, 2); +TEST_ATTR_HANDLER_DECL(timers_1, keepalive, 10, 20); +TEST_ATTR_HANDLER_DECL(timers_2, holdtime, 30, 60); +TEST_ATTR_HANDLER_DECL(addpath_types, addpath_type[pa->afi][pa->safi], + BGP_ADDPATH_ALL, BGP_ADDPATH_BEST_PER_AS); +TEST_SU_ATTR_HANDLER_DECL(update_source_su, update_source, "255.255.255.1", + "255.255.255.2"); +TEST_STR_ATTR_HANDLER_DECL(update_source_if, update_if, "IF-PEER", "IF-GROUP"); + +TEST_ATTR_HANDLER_DECL(allowas_in, allowas_in[pa->afi][pa->safi], 1, 2); +TEST_STR_ATTR_HANDLER_DECL(default_originate_route_map, + default_rmap[pa->afi][pa->safi].name, "RM-PEER", + "RM-GROUP"); +TEST_STR_ATTR_HANDLER_DECL( + distribute_list, + filter[pa->afi][pa->safi].dlist[pa->u.filter.direct].name, "DL-PEER", + "DL-GROUP"); +TEST_STR_ATTR_HANDLER_DECL( + filter_list, filter[pa->afi][pa->safi].aslist[pa->u.filter.direct].name, + "FL-PEER", "FL-GROUP"); +TEST_ATTR_HANDLER_DECL(maximum_prefix, pmax[pa->afi][pa->safi], 10, 20); +TEST_ATTR_HANDLER_DECL(maximum_prefix_threshold, + pmax_threshold[pa->afi][pa->safi], 1, 2); +TEST_ATTR_HANDLER_DECL(maximum_prefix_restart, pmax_restart[pa->afi][pa->safi], + 100, 200); +TEST_STR_ATTR_HANDLER_DECL( + prefix_list, filter[pa->afi][pa->safi].plist[pa->u.filter.direct].name, + "PL-PEER", "PL-GROUP"); +TEST_STR_ATTR_HANDLER_DECL( + route_map, filter[pa->afi][pa->safi].map[pa->u.filter.direct].name, + "RM-PEER", "RM-GROUP"); +TEST_STR_ATTR_HANDLER_DECL(unsuppress_map, filter[pa->afi][pa->safi].usmap.name, + "UM-PEER", "UM-GROUP"); +TEST_ATTR_HANDLER_DECL(weight, weight[pa->afi][pa->safi], 100, 200); + +/* clang-format off */ +static struct test_peer_attr test_peer_attrs[] = { + /* Peer Attributes */ + { + .cmd = "advertisement-interval", + .peer_cmd = "advertisement-interval 10", + .group_cmd = "advertisement-interval 20", + .u.flag = PEER_FLAG_ROUTEADV, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(advertisement_interval), + }, + { + .cmd = "capability dynamic", + .u.flag = PEER_FLAG_DYNAMIC_CAPABILITY, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "capability extended-nexthop", + .u.flag = PEER_FLAG_CAPABILITY_ENHE, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "capability software-version", + .u.flag = PEER_FLAG_CAPABILITY_SOFT_VERSION, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "description", + .peer_cmd = "description FRR Peer", + .group_cmd = "description FRR Group", + .type = PEER_AT_GLOBAL_CUSTOM, + }, + { + .cmd = "disable-connected-check", + .u.flag = PEER_FLAG_DISABLE_CONNECTED_CHECK, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "dont-capability-negotiate", + .u.flag = PEER_FLAG_DONT_CAPABILITY, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "capability fqdn", + .u.flag = PEER_FLAG_CAPABILITY_FQDN, + .type = PEER_AT_GLOBAL_FLAG, + .o.invert_peer = true, + .o.invert_group = true, + }, + { + .cmd = "local-as", + .peer_cmd = "local-as 1", + .group_cmd = "local-as 2", + .u.flag = PEER_FLAG_LOCAL_AS, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(local_as), + }, + { + .cmd = "local-as 1 no-prepend", + .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_NO_PREPEND, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "local-as 1 no-prepend replace-as", + .u.flag = PEER_FLAG_LOCAL_AS | PEER_FLAG_LOCAL_AS_REPLACE_AS, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "override-capability", + .u.flag = PEER_FLAG_OVERRIDE_CAPABILITY, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "passive", + .u.flag = PEER_FLAG_PASSIVE, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "password", + .peer_cmd = "password FRR-Peer", + .group_cmd = "password FRR-Group", + .u.flag = PEER_FLAG_PASSWORD, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(password), + }, + { + .cmd = "shutdown", + .u.flag = PEER_FLAG_SHUTDOWN, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "strict-capability-match", + .u.flag = PEER_FLAG_STRICT_CAP_MATCH, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "timers", + .peer_cmd = "timers 10 30", + .group_cmd = "timers 20 60", + .u.flag = PEER_FLAG_TIMER, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(timers_1), + .handlers[1] = TEST_HANDLER(timers_2), + }, + { + .cmd = "timers connect", + .peer_cmd = "timers connect 10", + .group_cmd = "timers connect 20", + .u.flag = PEER_FLAG_TIMER_CONNECT, + .type = PEER_AT_GLOBAL_FLAG, + }, + { + .cmd = "update-source", + .peer_cmd = "update-source 255.255.255.1", + .group_cmd = "update-source 255.255.255.2", + .u.flag = PEER_FLAG_UPDATE_SOURCE, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(update_source_su), + }, + { + .cmd = "update-source", + .peer_cmd = "update-source IF-PEER", + .group_cmd = "update-source IF-GROUP", + .u.flag = PEER_FLAG_UPDATE_SOURCE, + .type = PEER_AT_GLOBAL_FLAG, + .handlers[0] = TEST_HANDLER(update_source_if), + }, + + /* Address Family Attributes */ + { + .cmd = "addpath", + .peer_cmd = "addpath-tx-all-paths", + .group_cmd = "addpath-tx-bestpath-per-AS", + .type = PEER_AT_AF_CUSTOM, + .handlers[0] = TEST_HANDLER(addpath_types), + }, + { + .cmd = "allowas-in", + .peer_cmd = "allowas-in 1", + .group_cmd = "allowas-in 2", + .u.flag = PEER_FLAG_ALLOWAS_IN, + .handlers[0] = TEST_HANDLER(allowas_in), + }, + { + .cmd = "allowas-in origin", + .u.flag = PEER_FLAG_ALLOWAS_IN_ORIGIN, + }, + { + .cmd = "as-override", + .u.flag = PEER_FLAG_AS_OVERRIDE, + }, + { + .cmd = "attribute-unchanged as-path", + .u.flag = PEER_FLAG_AS_PATH_UNCHANGED, + }, + { + .cmd = "attribute-unchanged next-hop", + .u.flag = PEER_FLAG_NEXTHOP_UNCHANGED, + }, + { + .cmd = "attribute-unchanged med", + .u.flag = PEER_FLAG_MED_UNCHANGED, + }, + { + .cmd = "attribute-unchanged as-path next-hop", + .u.flag = PEER_FLAG_AS_PATH_UNCHANGED + | PEER_FLAG_NEXTHOP_UNCHANGED, + }, + { + .cmd = "attribute-unchanged as-path med", + .u.flag = PEER_FLAG_AS_PATH_UNCHANGED + | PEER_FLAG_MED_UNCHANGED, + }, + { + .cmd = "attribute-unchanged as-path next-hop med", + .u.flag = PEER_FLAG_AS_PATH_UNCHANGED + | PEER_FLAG_NEXTHOP_UNCHANGED + | PEER_FLAG_MED_UNCHANGED, + }, + { + .cmd = "capability orf prefix-list send", + .u.flag = PEER_FLAG_ORF_PREFIX_SM, + }, + { + .cmd = "capability orf prefix-list receive", + .u.flag = PEER_FLAG_ORF_PREFIX_RM, + }, + { + .cmd = "capability orf prefix-list both", + .u.flag = PEER_FLAG_ORF_PREFIX_SM | PEER_FLAG_ORF_PREFIX_RM, + }, + { + .cmd = "default-originate", + .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, + }, + { + .cmd = "default-originate route-map", + .peer_cmd = "default-originate route-map RM-PEER", + .group_cmd = "default-originate route-map RM-GROUP", + .u.flag = PEER_FLAG_DEFAULT_ORIGINATE, + .handlers[0] = TEST_HANDLER(default_originate_route_map), + }, + { + .cmd = "distribute-list", + .peer_cmd = "distribute-list DL-PEER in", + .group_cmd = "distribute-list DL-GROUP in", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, + .u.filter.direct = FILTER_IN, + .handlers[0] = TEST_HANDLER(distribute_list), + }, + { + .cmd = "distribute-list", + .peer_cmd = "distribute-list DL-PEER out", + .group_cmd = "distribute-list DL-GROUP out", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_DISTRIBUTE_LIST, + .u.filter.direct = FILTER_OUT, + .handlers[0] = TEST_HANDLER(distribute_list), + }, + { + .cmd = "filter-list", + .peer_cmd = "filter-list FL-PEER in", + .group_cmd = "filter-list FL-GROUP in", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_FILTER_LIST, + .u.filter.direct = FILTER_IN, + .handlers[0] = TEST_HANDLER(filter_list), + }, + { + .cmd = "filter-list", + .peer_cmd = "filter-list FL-PEER out", + .group_cmd = "filter-list FL-GROUP out", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_FILTER_LIST, + .u.filter.direct = FILTER_OUT, + .handlers[0] = TEST_HANDLER(filter_list), + }, + { + .cmd = "maximum-prefix", + .peer_cmd = "maximum-prefix 10", + .group_cmd = "maximum-prefix 20", + .u.flag = PEER_FLAG_MAX_PREFIX, + .handlers[0] = TEST_HANDLER(maximum_prefix), + }, + { + .cmd = "maximum-prefix", + .peer_cmd = "maximum-prefix 10 restart 100", + .group_cmd = "maximum-prefix 20 restart 200", + .u.flag = PEER_FLAG_MAX_PREFIX, + .handlers[0] = TEST_HANDLER(maximum_prefix), + .handlers[1] = TEST_HANDLER(maximum_prefix_restart), + }, + { + .cmd = "maximum-prefix", + .peer_cmd = "maximum-prefix 10 1 restart 100", + .group_cmd = "maximum-prefix 20 2 restart 200", + .u.flag = PEER_FLAG_MAX_PREFIX, + .handlers[0] = TEST_HANDLER(maximum_prefix), + .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), + .handlers[2] = TEST_HANDLER(maximum_prefix_restart), + }, + { + .cmd = "maximum-prefix", + .peer_cmd = "maximum-prefix 10 warning-only", + .group_cmd = "maximum-prefix 20 warning-only", + .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, + .handlers[0] = TEST_HANDLER(maximum_prefix), + }, + { + .cmd = "maximum-prefix", + .peer_cmd = "maximum-prefix 10 1 warning-only", + .group_cmd = "maximum-prefix 20 2 warning-only", + .u.flag = PEER_FLAG_MAX_PREFIX | PEER_FLAG_MAX_PREFIX_WARNING, + .handlers[0] = TEST_HANDLER(maximum_prefix), + .handlers[1] = TEST_HANDLER(maximum_prefix_threshold), + }, + { + .cmd = "next-hop-self", + .u.flag = PEER_FLAG_NEXTHOP_SELF, + }, + { + .cmd = "next-hop-self force", + .u.flag = PEER_FLAG_FORCE_NEXTHOP_SELF, + }, + { + .cmd = "prefix-list", + .peer_cmd = "prefix-list PL-PEER in", + .group_cmd = "prefix-list PL-GROUP in", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_PREFIX_LIST, + .u.filter.direct = FILTER_IN, + .handlers[0] = TEST_HANDLER(prefix_list), + }, + { + .cmd = "prefix-list", + .peer_cmd = "prefix-list PL-PEER out", + .group_cmd = "prefix-list PL-GROUP out", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_PREFIX_LIST, + .u.filter.direct = FILTER_OUT, + .handlers[0] = TEST_HANDLER(prefix_list), + }, + { + .cmd = "remove-private-AS", + .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS, + }, + { + .cmd = "remove-private-AS all", + .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS + | PEER_FLAG_REMOVE_PRIVATE_AS_ALL, + }, + { + .cmd = "remove-private-AS replace-AS", + .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS + | PEER_FLAG_REMOVE_PRIVATE_AS_REPLACE, + }, + { + .cmd = "remove-private-AS all replace-AS", + .u.flag = PEER_FLAG_REMOVE_PRIVATE_AS_ALL_REPLACE, + }, + { + .cmd = "route-map", + .peer_cmd = "route-map RM-PEER in", + .group_cmd = "route-map RM-GROUP in", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_ROUTE_MAP, + .u.filter.direct = FILTER_IN, + .handlers[0] = TEST_HANDLER(route_map), + }, + { + .cmd = "route-map", + .peer_cmd = "route-map RM-PEER out", + .group_cmd = "route-map RM-GROUP out", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_ROUTE_MAP, + .u.filter.direct = FILTER_OUT, + .handlers[0] = TEST_HANDLER(route_map), + }, + { + .cmd = "route-reflector-client", + .u.flag = PEER_FLAG_REFLECTOR_CLIENT, + .o.use_ibgp = true, + .o.skip_xfer_cases = true, + }, + { + .cmd = "route-server-client", + .u.flag = PEER_FLAG_RSERVER_CLIENT, + }, + { + .cmd = "send-community", + .u.flag = PEER_FLAG_SEND_COMMUNITY, + .o.invert_peer = true, + .o.invert_group = true, + }, + { + .cmd = "send-community extended", + .u.flag = PEER_FLAG_SEND_EXT_COMMUNITY, + .o.invert_peer = true, + .o.invert_group = true, + }, + { + .cmd = "send-community large", + .u.flag = PEER_FLAG_SEND_LARGE_COMMUNITY, + .o.invert_peer = true, + .o.invert_group = true, + }, + { + .cmd = "soft-reconfiguration inbound", + .u.flag = PEER_FLAG_SOFT_RECONFIG, + }, + { + .cmd = "unsuppress-map", + .peer_cmd = "unsuppress-map UM-PEER", + .group_cmd = "unsuppress-map UM-GROUP", + .type = PEER_AT_AF_FILTER, + .u.filter.flag = PEER_FT_UNSUPPRESS_MAP, + .u.filter.direct = 0, + .handlers[0] = TEST_HANDLER(unsuppress_map), + }, + { + .cmd = "weight", + .peer_cmd = "weight 100", + .group_cmd = "weight 200", + .u.flag = PEER_FLAG_WEIGHT, + .handlers[0] = TEST_HANDLER(weight), + }, + { + .cmd = "accept-own", + .peer_cmd = "accept-own", + .group_cmd = "accept-own", + .families[0] = {.afi = AFI_IP, .safi = SAFI_MPLS_VPN}, + .families[1] = {.afi = AFI_IP6, .safi = SAFI_MPLS_VPN}, + .u.flag = PEER_FLAG_ACCEPT_OWN, + }, + {NULL} +}; +/* clang-format on */ + +static const char *str_from_afi(afi_t afi) +{ + switch (afi) { + case AFI_IP: + return "ipv4"; + case AFI_IP6: + return "ipv6"; + case AFI_L2VPN: + return "l2vpn"; + case AFI_MAX: + case AFI_UNSPEC: + return "bad-value"; + } + + assert(!"Reached end of function we should never reach"); +} + +static const char *str_from_attr_type(enum test_peer_attr_type at) +{ + switch (at) { + case PEER_AT_GLOBAL_FLAG: + return "peer-flag"; + case PEER_AT_AF_FLAG: + return "af-flag"; + case PEER_AT_AF_FILTER: + return "af-filter"; + case PEER_AT_GLOBAL_CUSTOM: + case PEER_AT_AF_CUSTOM: + return "custom"; + default: + return NULL; + } +} + +static bool is_attr_type_global(enum test_peer_attr_type at) +{ + return at == PEER_AT_GLOBAL_FLAG || at == PEER_AT_GLOBAL_CUSTOM; +} + +PRINTFRR(2, 3) +static void test_log(struct test *test, const char *fmt, ...) +{ + va_list ap; + + /* Skip logging if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Store formatted log message. */ + va_start(ap, fmt); + listnode_add(test->log, vasprintfrr(MTYPE_TMP, fmt, ap)); + va_end(ap); +} + +PRINTFRR(2, 3) +static void test_execute(struct test *test, const char *fmt, ...) +{ + int ret; + char *cmd; + va_list ap; + vector vline; + + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Format command string with variadic arguments. */ + va_start(ap, fmt); + cmd = vasprintfrr(MTYPE_TMP, fmt, ap); + va_end(ap); + if (!cmd) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, "could not format command string [%s]", fmt); + return; + } + + /* Tokenize formatted command string. */ + vline = cmd_make_strvec(cmd); + if (vline == NULL) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, + "tokenizing command string [%s] returned empty result", + cmd); + XFREE(MTYPE_TMP, cmd); + + return; + } + + /* Execute command (non-strict). */ + ret = cmd_execute_command(vline, test->vty, NULL, 0); + if (ret != CMD_SUCCESS) { + test->state = TEST_COMMAND_ERROR; + test->error = asprintfrr( + MTYPE_TMP, + "execution of command [%s] has failed with code [%d]", + cmd, ret); + } + + /* Free memory. */ + cmd_free_strvec(vline); + XFREE(MTYPE_TMP, cmd); +} + +PRINTFRR(2, 0) +static void test_config(struct test *test, const char *fmt, bool invert, + va_list ap) +{ + char *matcher; + char *config; + bool matched; + va_list apc; + + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Format matcher string with variadic arguments. */ + va_copy(apc, ap); + matcher = vasprintfrr(MTYPE_TMP, fmt, apc); + va_end(apc); + if (!matcher) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, "could not format matcher string [%s]", fmt); + return; + } + + /* Fetch BGP configuration into buffer. */ + bgp_config_write(test->vty); + config = buffer_getstr(test->vty->obuf); + buffer_reset(test->vty->obuf); + + /* Match config against matcher. */ + matched = !!strstr(config, matcher); + if (!matched && !invert) { + test->state = TEST_CONFIG_ERROR; + test->error = asprintfrr(MTYPE_TMP, + "expected config [%s] to be present", + matcher); + } else if (matched && invert) { + test->state = TEST_CONFIG_ERROR; + test->error = asprintfrr(MTYPE_TMP, + "expected config [%s] to be absent", + matcher); + } + + /* Free memory and return. */ + XFREE(MTYPE_TMP, matcher); + XFREE(MTYPE_TMP, config); +} + +PRINTFRR(2, 3) +static void test_config_present(struct test *test, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + test_config(test, fmt, false, ap); + va_end(ap); +} + +PRINTFRR(2, 3) +static void test_config_absent(struct test *test, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + test_config(test, fmt, true, ap); + va_end(ap); +} + +static void test_initialize(struct test *test) +{ + union sockunion su; + + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Log message about (re)-initialization */ + test_log(test, "prepare: %sinitialize bgp test environment", + test->bgp ? "re-" : ""); + + /* Attempt gracefully to purge previous BGP configuration. */ + test_execute(test, "no router bgp"); + test->state = TEST_SUCCESS; + + /* Initialize BGP test environment. */ + test_execute(test, "router bgp %d", cfg.local_asn); + test_execute(test, "no bgp default ipv4-unicast"); + test_execute(test, "neighbor %s peer-group", cfg.peer_group); + if (test->o.use_iface_peer) { + test_execute(test, "neighbor %s interface", cfg.peer_interface); + test_execute(test, "neighbor %s remote-as %d", + cfg.peer_interface, + test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); + } else { + test_execute(test, "neighbor %s remote-as %d", cfg.peer_address, + test->o.use_ibgp ? cfg.local_asn : cfg.peer_asn); + } + + if (test->state != TEST_SUCCESS) + return; + + /* Fetch default BGP instance. */ + test->bgp = bgp_get_default(); + if (!test->bgp) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, "could not retrieve default bgp instance"); + return; + } + + /* Fetch peer instance. */ + if (test->o.use_iface_peer) { + test->peer = + peer_lookup_by_conf_if(test->bgp, cfg.peer_interface); + } else { + str2sockunion(cfg.peer_address, &su); + test->peer = peer_lookup(test->bgp, &su); + } + if (!test->peer) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, + "could not retrieve instance of bgp peer [%s]", + cfg.peer_address); + return; + } + + /* Fetch peer-group instance. */ + test->group = peer_group_lookup(test->bgp, cfg.peer_group); + if (!test->group) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, + "could not retrieve instance of bgp peer-group [%s]", + cfg.peer_group); + return; + } +} + +static struct test *test_new(const char *desc, bool use_ibgp, + bool use_iface_peer) +{ + struct test *test; + + test = XCALLOC(MTYPE_TMP, sizeof(struct test)); + test->state = TEST_SUCCESS; + test->desc = XSTRDUP(MTYPE_TMP, desc); + test->log = list_new(); + test->o.use_ibgp = use_ibgp; + test->o.use_iface_peer = use_iface_peer; + + test->vty = vty_new(); + test->vty->type = VTY_TERM; + test->vty->node = CONFIG_NODE; + + test_initialize(test); + + return test; +}; + +static void test_finish(struct test *test) +{ + char *msg; + struct listnode *node, *nnode; + + /* Print test output header. */ + printf("%s [test] %s\n", + (test->state == TEST_SUCCESS) ? OUT_SYMBOL_OK : OUT_SYMBOL_NOK, + test->desc); + + /* Print test log messages. */ + for (ALL_LIST_ELEMENTS(test->log, node, nnode, msg)) { + printf("%s %s\n", OUT_SYMBOL_INFO, msg); + XFREE(MTYPE_TMP, msg); + } + + /* Print test error message if available. */ + if (test->state != TEST_SUCCESS && test->error) + printf("%s error: %s\n", OUT_SYMBOL_INFO, test->error); + + /* Print machine-readable result of test. */ + printf("%s\n", test->state == TEST_SUCCESS ? "OK" : "failed"); + + /* Cleanup allocated memory. */ + if (test->vty) { + vty_close(test->vty); + test->vty = NULL; + } + if (test->log) + list_delete(&test->log); + if (test->desc) + XFREE(MTYPE_TMP, test->desc); + if (test->error) + XFREE(MTYPE_TMP, test->error); + XFREE(MTYPE_TMP, test); +} + +static void test_peer_flags(struct test *test, struct test_peer_attr *pa, + struct peer *peer, bool exp_val, bool exp_ovrd) +{ + bool exp_inv, cur_val, cur_ovrd, cur_inv; + + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Detect if flag is meant to be inverted. */ + if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) + exp_inv = pa->o.invert_group; + else + exp_inv = pa->o.invert_peer; + + /* Flip expected value if flag is inverted. */ + exp_val ^= exp_inv; + + /* Fetch current state of value, override and invert flags. */ + if (pa->type == PEER_AT_GLOBAL_FLAG) { + cur_val = !!CHECK_FLAG(peer->flags, pa->u.flag); + cur_ovrd = !!CHECK_FLAG(peer->flags_override, pa->u.flag); + cur_inv = !!CHECK_FLAG(peer->flags_invert, pa->u.flag); + } else /* if (pa->type == PEER_AT_AF_FLAG) */ { + cur_val = !!CHECK_FLAG(peer->af_flags[pa->afi][pa->safi], + pa->u.flag); + cur_ovrd = !!CHECK_FLAG( + peer->af_flags_override[pa->afi][pa->safi], pa->u.flag); + cur_inv = !!CHECK_FLAG(peer->af_flags_invert[pa->afi][pa->safi], + pa->u.flag); + } + + /* Assert expected flag states. */ + TEST_ASSERT_EQ(test, cur_val, exp_val); + TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); + TEST_ASSERT_EQ(test, cur_inv, exp_inv); +} + +static void test_af_filter(struct test *test, struct test_peer_attr *pa, + struct peer *peer, bool exp_state, bool exp_ovrd) +{ + bool cur_ovrd; + struct bgp_filter *filter; + + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Fetch and assert current state of override flag. */ + cur_ovrd = !!CHECK_FLAG( + peer->filter_override[pa->afi][pa->safi][pa->u.filter.direct], + pa->u.filter.flag); + + TEST_ASSERT_EQ(test, cur_ovrd, exp_ovrd); + + /* Assert that map/list matches expected state (set/unset). */ + filter = &peer->filter[pa->afi][pa->safi]; + + switch (pa->u.filter.flag) { + case PEER_FT_DISTRIBUTE_LIST: + TEST_ASSERT_EQ(test, + !!(filter->dlist[pa->u.filter.direct].name), + exp_state); + break; + case PEER_FT_FILTER_LIST: + TEST_ASSERT_EQ(test, + !!(filter->aslist[pa->u.filter.direct].name), + exp_state); + break; + case PEER_FT_PREFIX_LIST: + TEST_ASSERT_EQ(test, + !!(filter->plist[pa->u.filter.direct].name), + exp_state); + break; + case PEER_FT_ROUTE_MAP: + TEST_ASSERT_EQ(test, !!(filter->map[pa->u.filter.direct].name), + exp_state); + break; + case PEER_FT_UNSUPPRESS_MAP: + TEST_ASSERT_EQ(test, !!(filter->usmap.name), exp_state); + break; + } +} + +static void test_custom(struct test *test, struct test_peer_attr *pa, + struct peer *peer, struct peer *group, bool peer_set, + bool group_set) +{ + int i; + char *handler_error; + + for (i = 0; i < TEST_HANDLER_MAX; i++) { + /* Skip execution if test instance has previously failed. */ + if (test->state != TEST_SUCCESS) + return; + + /* Skip further execution if handler is undefined. */ + if (!pa->handlers[i]) + return; + + /* Execute custom handler. */ + pa->handlers[i](test, pa, peer, group, peer_set, group_set); + if (test->state != TEST_SUCCESS) { + test->state = TEST_CUSTOM_ERROR; + handler_error = test->error; + test->error = asprintfrr(MTYPE_TMP, + "custom handler failed: %s", + handler_error); + XFREE(MTYPE_TMP, handler_error); + } + } +} + + +static void test_process(struct test *test, struct test_peer_attr *pa, + struct peer *peer, struct peer *group, bool peer_set, + bool group_set) +{ + switch (pa->type) { + case PEER_AT_GLOBAL_FLAG: + case PEER_AT_AF_FLAG: + test_peer_flags( + test, pa, peer, + peer_set || (peer_group_active(peer) && group_set), + peer_set); + test_peer_flags(test, pa, group, group_set, false); + break; + + case PEER_AT_AF_FILTER: + test_af_filter( + test, pa, peer, + peer_set || (peer_group_active(peer) && group_set), + peer_set); + test_af_filter(test, pa, group, group_set, false); + break; + + case PEER_AT_GLOBAL_CUSTOM: + case PEER_AT_AF_CUSTOM: + /* + * Do nothing here - a custom handler can be executed, but this + * is not required. This will allow defining peer attributes + * which shall not be checked for flag/filter/other internal + * states. + */ + break; + + default: + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, "invalid attribute type: %d", pa->type); + break; + } + + /* Attempt to call a custom handler if set for further processing. */ + test_custom(test, pa, peer, group, peer_set, group_set); +} + +static void test_peer_attr(struct test *test, struct test_peer_attr *pa) +{ + int tc = 1; + const char *type; + const char *ecp = pa->o.invert_peer ? "no " : ""; + const char *dcp = pa->o.invert_peer ? "" : "no "; + const char *ecg = pa->o.invert_group ? "no " : ""; + const char *dcg = pa->o.invert_group ? "" : "no "; + const char *peer_cmd = pa->peer_cmd ?: pa->cmd; + const char *group_cmd = pa->group_cmd ?: pa->cmd; + struct peer *p = test->peer; + struct peer_group *g = test->group; + + /* Determine type and if test is address-family relevant */ + type = str_from_attr_type(pa->type); + if (!type) { + test->state = TEST_INTERNAL_ERROR; + test->error = asprintfrr( + MTYPE_TMP, "invalid attribute type: %d", pa->type); + return; + } + + /* + * ===================================================================== + * Test Case Suite 1: Config persistence after adding peer to group + * + * Example: If a peer attribute has value [1] and a group attribute has + * value [2], the peer attribute value should be persisted when the peer + * gets added to the peer-group. + * + * This test suite is meant to test the group2peer functions which can + * be found inside bgpd/bgpd.c, which are related to initial peer-group + * inheritance. + * ===================================================================== + */ + + /* Test Preparation: Switch and activate address-family. */ + if (!is_attr_type_global(pa->type)) { + test_log(test, "prepare: switch address-family to [%s]", + get_afi_safi_str(pa->afi, pa->safi, false)); + test_execute(test, "address-family %s %s", + str_from_afi(pa->afi), safi2str(pa->safi)); + test_execute(test, "neighbor %s activate", g->name); + test_execute(test, "neighbor %s activate", p->host); + } + + /* Skip peer-group to peer transfer test cases if requested. */ + if (pa->o.skip_xfer_cases && test->state == TEST_SUCCESS) + test->state = TEST_SKIPPING; + + /* Test Case: Set flag on BGP peer. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, + p->host); + test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* Test Case: Set flag on BGP peer-group. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, + g->name); + test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, true, true); + + /* Test Case: Add BGP peer to peer-group. */ + test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, + g->name); + test_execute(test, "neighbor %s peer-group %s", p->host, g->name); + test_config_present(test, "neighbor %s %speer-group %s", p->host, + p->conf_if ? "interface " : "", g->name); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, true, true); + + /* Test Case: Unset flag on BGP peer-group. */ + test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, + group_cmd, g->name); + test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* + * ===================================================================== + * Test Case Suite 2: Config inheritance after adding peer to group + * + * Example: If a peer attribute has not been set and a group attribute + * has a value of [2], the group attribute should be inherited to the + * peer without flagging the newly set value as overridden. + * + * This test suite is meant to test the group2peer functions which can + * be found inside bgpd/bgpd.c, which are related to initial peer-group + * inheritance. + * ===================================================================== + */ + + /* Test Preparation: Re-initialize test environment. */ + test_initialize(test); + p = test->peer; + g = test->group; + + /* Test Preparation: Switch and activate address-family. */ + if (!is_attr_type_global(pa->type)) { + test_log(test, "prepare: switch address-family to [%s]", + get_afi_safi_str(pa->afi, pa->safi, false)); + test_execute(test, "address-family %s %s", + str_from_afi(pa->afi), safi2str(pa->safi)); + test_execute(test, "neighbor %s activate", g->name); + test_execute(test, "neighbor %s activate", p->host); + } + + /* Test Case: Set flag on BGP peer-group. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, + g->name); + test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, false, true); + + /* Test Case: Add BGP peer to peer-group. */ + test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, + g->name); + test_execute(test, "neighbor %s peer-group %s", p->host, g->name); + test_config_present(test, "neighbor %s %speer-group %s", p->host, + p->conf_if ? "interface " : "", g->name); + test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, false, true); + + /* Stop skipping test cases if previously enabled. */ + if (pa->o.skip_xfer_cases && test->state == TEST_SKIPPING) + test->state = TEST_SUCCESS; + + /* + * ===================================================================== + * Test Case Suite 3: Miscellaneous flag checks + * + * This test suite does not focus on initial peer-group inheritance and + * instead executes various different commands to set/unset attributes + * on both peer- and group-level. These checks should always be executed + * and must pass. + * ===================================================================== + */ + + /* Test Preparation: Re-initialize test environment. */ + test_initialize(test); + p = test->peer; + g = test->group; + + /* Test Preparation: Switch and activate address-family. */ + if (!is_attr_type_global(pa->type)) { + test_log(test, "prepare: switch address-family to [%s]", + get_afi_safi_str(pa->afi, pa->safi, false)); + test_execute(test, "address-family %s %s", + str_from_afi(pa->afi), safi2str(pa->safi)); + test_execute(test, "neighbor %s activate", g->name); + test_execute(test, "neighbor %s activate", p->host); + } + + /* Test Case: Set flag on BGP peer. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, + p->host); + test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* Test Case: Add BGP peer to peer-group. */ + test_log(test, "case %02d: add peer [%s] to group [%s]", tc++, p->host, + g->name); + test_execute(test, "neighbor %s peer-group %s", p->host, g->name); + test_config_present(test, "neighbor %s %speer-group %s", p->host, + p->conf_if ? "interface " : "", g->name); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* Test Case: Re-add BGP peer to peer-group. */ + test_log(test, "case %02d: re-add peer [%s] to group [%s]", tc++, + p->host, g->name); + test_execute(test, "neighbor %s peer-group %s", p->host, g->name); + test_config_present(test, "neighbor %s %speer-group %s", p->host, + p->conf_if ? "interface " : "", g->name); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* Test Case: Set flag on BGP peer-group. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, + g->name); + test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, true, true); + + /* Test Case: Unset flag on BGP peer-group. */ + test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, + group_cmd, g->name); + test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); + + /* Test Case: Set flag on BGP peer-group. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, group_cmd, + g->name); + test_execute(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, true, true); + + /* Test Case: Re-set flag on BGP peer. */ + test_log(test, "case %02d: re-set %s [%s] on [%s]", tc++, type, + peer_cmd, p->host); + test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, true, true); + + /* Test Case: Unset flag on BGP peer. */ + test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, peer_cmd, + p->host); + test_execute(test, "%sneighbor %s %s", dcp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); + test_config_present(test, "%sneighbor %s %s", ecg, g->name, group_cmd); + test_process(test, pa, p, g->conf, false, true); + + /* Test Case: Unset flag on BGP peer-group. */ + test_log(test, "case %02d: unset %s [%s] on [%s]", tc++, type, + group_cmd, g->name); + test_execute(test, "%sneighbor %s %s", dcg, g->name, group_cmd); + test_config_absent(test, "neighbor %s %s", p->host, pa->cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, false, false); + + /* Test Case: Set flag on BGP peer. */ + test_log(test, "case %02d: set %s [%s] on [%s]", tc++, type, peer_cmd, + p->host); + test_execute(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_present(test, "%sneighbor %s %s", ecp, p->host, peer_cmd); + test_config_absent(test, "neighbor %s %s", g->name, pa->cmd); + test_process(test, pa, p, g->conf, true, false); +} + +static void bgp_startup(void) +{ + cmd_init(1); + zlog_aux_init("NONE: ", LOG_DEBUG); + zprivs_preinit(&bgpd_privs); + zprivs_init(&bgpd_privs); + + master = event_master_create(NULL); + nb_init(master, NULL, 0, false); + bgp_master_init(master, BGP_SOCKET_SNDBUF_SIZE, list_new()); + bgp_option_set(BGP_OPT_NO_LISTEN); + vrf_init(NULL, NULL, NULL, NULL); + frr_pthread_init(); + bgp_init(0); + bgp_pthreads_run(); +} + +static void bgp_shutdown(void) +{ + struct bgp *bgp; + struct listnode *node, *nnode; + + bgp_terminate(); + bgp_close(); + for (ALL_LIST_ELEMENTS(bm->bgp, node, nnode, bgp)) + bgp_delete(bgp); + bgp_dump_finish(); + bgp_route_finish(); + bgp_route_map_terminate(); + bgp_attr_finish(); + bgp_labels_finish(); + bgp_pthreads_finish(); + access_list_add_hook(NULL); + access_list_delete_hook(NULL); + access_list_reset(); + as_list_add_hook(NULL); + as_list_delete_hook(NULL); + bgp_filter_reset(); + prefix_list_add_hook(NULL); + prefix_list_delete_hook(NULL); + prefix_list_reset(); + community_list_terminate(bgp_clist); + vrf_terminate(); +#ifdef ENABLE_BGP_VNC + vnc_zebra_destroy(); +#endif + bgp_zebra_destroy(); + + bf_free(bm->rd_idspace); + list_delete(&bm->bgp); + memset(bm, 0, sizeof(*bm)); + + vty_terminate(); + cmd_terminate(); + nb_terminate(); + yang_terminate(); + zprivs_terminate(&bgpd_privs); + event_master_free(master); + master = NULL; +} + +int main(void) +{ + int i, ii; + struct list *pa_list; + struct test_peer_attr *pa, *pac; + struct listnode *node, *nnode; + + bgp_startup(); + + pa_list = list_new(); + i = 0; + while (test_peer_attrs[i].cmd) { + pa = &test_peer_attrs[i++]; + + /* Just copy the peer attribute structure for global flags. */ + if (is_attr_type_global(pa->type)) { + pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); + memcpy(pac, pa, sizeof(struct test_peer_attr)); + listnode_add(pa_list, pac); + continue; + } + + /* Fallback to default families if not specified. */ + if (!pa->families[0].afi && !pa->families[0].safi) + memcpy(&pa->families, test_default_families, + sizeof(test_default_families)); + + /* Add peer attribute definition for each address family. */ + ii = 0; + while (pa->families[ii].afi && pa->families[ii].safi) { + pac = XMALLOC(MTYPE_TMP, sizeof(struct test_peer_attr)); + memcpy(pac, pa, sizeof(struct test_peer_attr)); + + pac->afi = pa->families[ii].afi; + pac->safi = pa->families[ii].safi; + listnode_add(pa_list, pac); + + ii++; + } + } + + for (ALL_LIST_ELEMENTS(pa_list, node, nnode, pa)) { + char *desc; + struct test *test; + + /* Build test description string. */ + if (pa->afi && pa->safi) + desc = asprintfrr(MTYPE_TMP, "peer\\%s-%s\\%s", + str_from_afi(pa->afi), + safi2str(pa->safi), pa->cmd); + else + desc = asprintfrr(MTYPE_TMP, "peer\\%s", pa->cmd); + + /* Initialize new test instance. */ + test = test_new(desc, pa->o.use_ibgp, pa->o.use_iface_peer); + XFREE(MTYPE_TMP, desc); + + /* Execute tests and finish test instance. */ + test_peer_attr(test, pa); + test_finish(test); + + /* Print empty line as spacer. */ + printf("\n"); + + /* Free memory used for peer-attr declaration. */ + XFREE(MTYPE_TMP, pa); + } + + list_delete(&pa_list); + bgp_shutdown(); + + return 0; +} diff --git a/tests/bgpd/test_peer_attr.py b/tests/bgpd/test_peer_attr.py new file mode 100644 index 0000000..b1f88d2 --- /dev/null +++ b/tests/bgpd/test_peer_attr.py @@ -0,0 +1,200 @@ +import frrtest + + +class TestFlag(frrtest.TestMultiOut): + program = "./test_peer_attr" + + +# List of tests can be generated by executing: +# $> ./test_peer_attr 2>&1 | sed -n 's/\\/\\\\/g; s/\S\+ \[test\] \(.\+\)/TestFlag.okfail(\x27\1\x27)/pg' +# +TestFlag.okfail("peer\\advertisement-interval") +TestFlag.okfail("peer\\capability dynamic") +TestFlag.okfail("peer\\capability extended-nexthop") +# TestFlag.okfail('peer\\capability extended-nexthop') +TestFlag.okfail("peer\\description") +TestFlag.okfail("peer\\disable-connected-check") +TestFlag.okfail("peer\\dont-capability-negotiate") +TestFlag.okfail("peer\\capability fqdn") +TestFlag.okfail("peer\\local-as") +TestFlag.okfail("peer\\local-as 1 no-prepend") +TestFlag.okfail("peer\\local-as 1 no-prepend replace-as") +TestFlag.okfail("peer\\override-capability") +TestFlag.okfail("peer\\passive") +TestFlag.okfail("peer\\password") +TestFlag.okfail("peer\\shutdown") +TestFlag.okfail("peer\\strict-capability-match") +TestFlag.okfail("peer\\timers") +TestFlag.okfail("peer\\timers connect") +TestFlag.okfail("peer\\update-source") +TestFlag.okfail("peer\\update-source") +TestFlag.okfail("peer\\ipv4-unicast\\addpath") +TestFlag.okfail("peer\\ipv4-multicast\\addpath") +TestFlag.okfail("peer\\ipv6-unicast\\addpath") +TestFlag.okfail("peer\\ipv6-multicast\\addpath") +TestFlag.okfail("peer\\ipv4-unicast\\allowas-in") +TestFlag.okfail("peer\\ipv4-multicast\\allowas-in") +TestFlag.okfail("peer\\ipv6-unicast\\allowas-in") +TestFlag.okfail("peer\\ipv6-multicast\\allowas-in") +TestFlag.okfail("peer\\ipv4-unicast\\allowas-in origin") +TestFlag.okfail("peer\\ipv4-multicast\\allowas-in origin") +TestFlag.okfail("peer\\ipv6-unicast\\allowas-in origin") +TestFlag.okfail("peer\\ipv6-multicast\\allowas-in origin") +TestFlag.okfail("peer\\ipv4-unicast\\as-override") +TestFlag.okfail("peer\\ipv4-multicast\\as-override") +TestFlag.okfail("peer\\ipv6-unicast\\as-override") +TestFlag.okfail("peer\\ipv6-multicast\\as-override") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged as-path") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged as-path") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged as-path") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged as-path") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged next-hop") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged next-hop") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged next-hop") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged next-hop") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged med") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged med") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged med") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged med") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged as-path next-hop") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged as-path next-hop") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged as-path next-hop") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged as-path next-hop") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged as-path med") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged as-path med") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged as-path med") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged as-path med") +TestFlag.okfail("peer\\ipv4-unicast\\attribute-unchanged as-path next-hop med") +TestFlag.okfail("peer\\ipv4-multicast\\attribute-unchanged as-path next-hop med") +TestFlag.okfail("peer\\ipv6-unicast\\attribute-unchanged as-path next-hop med") +TestFlag.okfail("peer\\ipv6-multicast\\attribute-unchanged as-path next-hop med") +TestFlag.okfail("peer\\ipv4-unicast\\capability orf prefix-list send") +TestFlag.okfail("peer\\ipv4-multicast\\capability orf prefix-list send") +TestFlag.okfail("peer\\ipv6-unicast\\capability orf prefix-list send") +TestFlag.okfail("peer\\ipv6-multicast\\capability orf prefix-list send") +TestFlag.okfail("peer\\ipv4-unicast\\capability orf prefix-list receive") +TestFlag.okfail("peer\\ipv4-multicast\\capability orf prefix-list receive") +TestFlag.okfail("peer\\ipv6-unicast\\capability orf prefix-list receive") +TestFlag.okfail("peer\\ipv6-multicast\\capability orf prefix-list receive") +TestFlag.okfail("peer\\ipv4-unicast\\capability orf prefix-list both") +TestFlag.okfail("peer\\ipv4-multicast\\capability orf prefix-list both") +TestFlag.okfail("peer\\ipv6-unicast\\capability orf prefix-list both") +TestFlag.okfail("peer\\ipv6-multicast\\capability orf prefix-list both") +TestFlag.okfail("peer\\ipv4-unicast\\default-originate") +TestFlag.okfail("peer\\ipv4-multicast\\default-originate") +TestFlag.okfail("peer\\ipv6-unicast\\default-originate") +TestFlag.okfail("peer\\ipv6-multicast\\default-originate") +TestFlag.okfail("peer\\ipv4-unicast\\default-originate route-map") +TestFlag.okfail("peer\\ipv4-multicast\\default-originate route-map") +TestFlag.okfail("peer\\ipv6-unicast\\default-originate route-map") +TestFlag.okfail("peer\\ipv6-multicast\\default-originate route-map") +TestFlag.okfail("peer\\ipv4-unicast\\distribute-list") +TestFlag.okfail("peer\\ipv4-multicast\\distribute-list") +TestFlag.okfail("peer\\ipv6-unicast\\distribute-list") +TestFlag.okfail("peer\\ipv6-multicast\\distribute-list") +TestFlag.okfail("peer\\ipv4-unicast\\distribute-list") +TestFlag.okfail("peer\\ipv4-multicast\\distribute-list") +TestFlag.okfail("peer\\ipv6-unicast\\distribute-list") +TestFlag.okfail("peer\\ipv6-multicast\\distribute-list") +TestFlag.okfail("peer\\ipv4-unicast\\filter-list") +TestFlag.okfail("peer\\ipv4-multicast\\filter-list") +TestFlag.okfail("peer\\ipv6-unicast\\filter-list") +TestFlag.okfail("peer\\ipv6-multicast\\filter-list") +TestFlag.okfail("peer\\ipv4-unicast\\filter-list") +TestFlag.okfail("peer\\ipv4-multicast\\filter-list") +TestFlag.okfail("peer\\ipv6-unicast\\filter-list") +TestFlag.okfail("peer\\ipv6-multicast\\filter-list") +TestFlag.okfail("peer\\ipv4-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-unicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv6-multicast\\maximum-prefix") +TestFlag.okfail("peer\\ipv4-unicast\\next-hop-self") +TestFlag.okfail("peer\\ipv4-multicast\\next-hop-self") +TestFlag.okfail("peer\\ipv6-unicast\\next-hop-self") +TestFlag.okfail("peer\\ipv6-multicast\\next-hop-self") +TestFlag.okfail("peer\\ipv4-unicast\\next-hop-self force") +TestFlag.okfail("peer\\ipv4-multicast\\next-hop-self force") +TestFlag.okfail("peer\\ipv6-unicast\\next-hop-self force") +TestFlag.okfail("peer\\ipv6-multicast\\next-hop-self force") +TestFlag.okfail("peer\\ipv4-unicast\\prefix-list") +TestFlag.okfail("peer\\ipv4-multicast\\prefix-list") +TestFlag.okfail("peer\\ipv6-unicast\\prefix-list") +TestFlag.okfail("peer\\ipv6-multicast\\prefix-list") +TestFlag.okfail("peer\\ipv4-unicast\\prefix-list") +TestFlag.okfail("peer\\ipv4-multicast\\prefix-list") +TestFlag.okfail("peer\\ipv6-unicast\\prefix-list") +TestFlag.okfail("peer\\ipv6-multicast\\prefix-list") +TestFlag.okfail("peer\\ipv4-unicast\\remove-private-AS") +TestFlag.okfail("peer\\ipv4-multicast\\remove-private-AS") +TestFlag.okfail("peer\\ipv6-unicast\\remove-private-AS") +TestFlag.okfail("peer\\ipv6-multicast\\remove-private-AS") +TestFlag.okfail("peer\\ipv4-unicast\\remove-private-AS all") +TestFlag.okfail("peer\\ipv4-multicast\\remove-private-AS all") +TestFlag.okfail("peer\\ipv6-unicast\\remove-private-AS all") +TestFlag.okfail("peer\\ipv6-multicast\\remove-private-AS all") +TestFlag.okfail("peer\\ipv4-unicast\\remove-private-AS replace-AS") +TestFlag.okfail("peer\\ipv4-multicast\\remove-private-AS replace-AS") +TestFlag.okfail("peer\\ipv6-unicast\\remove-private-AS replace-AS") +TestFlag.okfail("peer\\ipv6-multicast\\remove-private-AS replace-AS") +TestFlag.okfail("peer\\ipv4-unicast\\remove-private-AS all replace-AS") +TestFlag.okfail("peer\\ipv4-multicast\\remove-private-AS all replace-AS") +TestFlag.okfail("peer\\ipv6-unicast\\remove-private-AS all replace-AS") +TestFlag.okfail("peer\\ipv6-multicast\\remove-private-AS all replace-AS") +TestFlag.okfail("peer\\ipv4-unicast\\route-map") +TestFlag.okfail("peer\\ipv4-multicast\\route-map") +TestFlag.okfail("peer\\ipv6-unicast\\route-map") +TestFlag.okfail("peer\\ipv6-multicast\\route-map") +TestFlag.okfail("peer\\ipv4-unicast\\route-map") +TestFlag.okfail("peer\\ipv4-multicast\\route-map") +TestFlag.okfail("peer\\ipv6-unicast\\route-map") +TestFlag.okfail("peer\\ipv6-multicast\\route-map") +TestFlag.okfail("peer\\ipv4-unicast\\route-reflector-client") +TestFlag.okfail("peer\\ipv4-multicast\\route-reflector-client") +TestFlag.okfail("peer\\ipv6-unicast\\route-reflector-client") +TestFlag.okfail("peer\\ipv6-multicast\\route-reflector-client") +TestFlag.okfail("peer\\ipv4-unicast\\route-server-client") +TestFlag.okfail("peer\\ipv4-multicast\\route-server-client") +TestFlag.okfail("peer\\ipv6-unicast\\route-server-client") +TestFlag.okfail("peer\\ipv6-multicast\\route-server-client") +TestFlag.okfail("peer\\ipv4-unicast\\send-community") +TestFlag.okfail("peer\\ipv4-multicast\\send-community") +TestFlag.okfail("peer\\ipv6-unicast\\send-community") +TestFlag.okfail("peer\\ipv6-multicast\\send-community") +TestFlag.okfail("peer\\ipv4-unicast\\send-community extended") +TestFlag.okfail("peer\\ipv4-multicast\\send-community extended") +TestFlag.okfail("peer\\ipv6-unicast\\send-community extended") +TestFlag.okfail("peer\\ipv6-multicast\\send-community extended") +TestFlag.okfail("peer\\ipv4-unicast\\send-community large") +TestFlag.okfail("peer\\ipv4-multicast\\send-community large") +TestFlag.okfail("peer\\ipv6-unicast\\send-community large") +TestFlag.okfail("peer\\ipv6-multicast\\send-community large") +TestFlag.okfail("peer\\ipv4-unicast\\soft-reconfiguration inbound") +TestFlag.okfail("peer\\ipv4-multicast\\soft-reconfiguration inbound") +TestFlag.okfail("peer\\ipv6-unicast\\soft-reconfiguration inbound") +TestFlag.okfail("peer\\ipv6-multicast\\soft-reconfiguration inbound") +TestFlag.okfail("peer\\ipv4-unicast\\unsuppress-map") +TestFlag.okfail("peer\\ipv4-multicast\\unsuppress-map") +TestFlag.okfail("peer\\ipv6-unicast\\unsuppress-map") +TestFlag.okfail("peer\\ipv6-multicast\\unsuppress-map") +TestFlag.okfail("peer\\ipv4-unicast\\weight") +TestFlag.okfail("peer\\ipv4-multicast\\weight") +TestFlag.okfail("peer\\ipv6-unicast\\weight") +TestFlag.okfail("peer\\ipv6-multicast\\weight") +TestFlag.okfail("peer\\ipv4-vpn\\accept-own") +TestFlag.okfail("peer\\ipv6-vpn\\accept-own") diff --git a/tests/helpers/c/main.c b/tests/helpers/c/main.c new file mode 100644 index 0000000..fdda7f1 --- /dev/null +++ b/tests/helpers/c/main.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +#include +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "lib_vty.h" + +extern void test_init(void); + +struct event_loop *master; + +struct option longopts[] = {{"daemon", no_argument, NULL, 'd'}, + {"config_file", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"vty_addr", required_argument, NULL, 'A'}, + {"vty_port", required_argument, NULL, 'P'}, + {"version", no_argument, NULL, 'v'}, + {0}}; + +DEFUN (daemon_exit, + daemon_exit_cmd, + "daemon-exit", + "Make the daemon exit\n") +{ + exit(0); +} + +static int timer_count; +static void test_timer(struct event *thread) +{ + int *count = EVENT_ARG(thread); + + printf("run %d of timer\n", (*count)++); + event_add_timer(master, test_timer, count, 5, NULL); +} + +static void test_timer_init(void) +{ + event_add_timer(master, test_timer, &timer_count, 10, NULL); +} + +static void test_vty_init(void) +{ + install_element(VIEW_NODE, &daemon_exit_cmd); +} + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +Daemon which does 'slow' things.\n\n\ +-d, --daemon Runs in daemon mode\n\ +-f, --config_file Set configuration file name\n\ +-A, --vty_addr Set vty's bind address\n\ +-P, --vty_port Set vty's port number\n\ +-v, --version Print program version\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + + +/* main routine. */ +int main(int argc, char **argv) +{ + char *p; + char *vty_addr = NULL; + int vty_port = 4000; + int daemon_mode = 0; + char *progname; + struct event thread; + char *config_file = NULL; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + /* master init. */ + master = event_master_create(NULL); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "dhf:A:P:v", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'f': + config_file = optarg; + break; + case 'd': + daemon_mode = 1; + break; + case 'A': + vty_addr = optarg; + break; + case 'P': + /* Deal with atoi() returning 0 on failure */ + if (strcmp(optarg, "0") == 0) { + vty_port = 0; + break; + } + vty_port = atoi(optarg); + vty_port = (vty_port ? vty_port : 4000); + break; + case 'v': + print_version(progname); + exit(0); + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* Library inits. */ + cmd_init(1); + vty_init(master, false); + lib_cmd_init(); + nb_init(master, NULL, 0, false); + + /* OSPF vty inits. */ + test_vty_init(); + + /* Change to the daemon program. */ + if (daemon_mode && daemon(0, 0) < 0) { + fprintf(stderr, "daemon failed: %s", strerror(errno)); + exit(1); + } + + /* Create VTY socket */ + vty_serv_start(vty_addr, vty_port, "/tmp/.heavy.sock"); + + /* Configuration file read*/ + if (!config_file) + usage(progname, 1); + vty_read_config(NULL, config_file, NULL); + + test_timer_init(); + + test_init(); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/helpers/c/prng.c b/tests/helpers/c/prng.c new file mode 100644 index 0000000..612c433 --- /dev/null +++ b/tests/helpers/c/prng.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Very simple prng to allow for randomized tests with reproducable + * results. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2017 Christian Franke + * + * This file is part of Quagga + */ + +#include + +#include +#include +#include + +#include "prng.h" + +struct prng { + uint64_t state; +}; + +struct prng *prng_new(unsigned long long seed) +{ + struct prng *rv = calloc(sizeof(*rv), 1); + assert(rv); + + rv->state = seed; + + return rv; +} + +/* + * This implementation has originally been provided to musl libc by + * Szabolcs Nagy in 2013 under the terms of + * the MIT license. + * It is a simple LCG which D.E. Knuth attributes to C.E. Haynes in + * TAOCP Vol2 3.3.4 + */ +int prng_rand(struct prng *prng) +{ + prng->state = 6364136223846793005ULL * prng->state + 1; + return prng->state >> 33; +} + +const char *prng_fuzz(struct prng *prng, const char *string, + const char *charset, unsigned int operations) +{ + static char buf[256]; + unsigned int charset_len; + unsigned int i; + unsigned int offset; + unsigned int op; + unsigned int character; + + assert(strlen(string) < sizeof(buf)); + + strncpy(buf, string, sizeof(buf)); + charset_len = strlen(charset); + + for (i = 0; i < operations; i++) { + offset = prng_rand(prng) % strlen(buf); + op = prng_rand(prng) % 3; + + switch (op) { + case 0: + /* replace */ + character = prng_rand(prng) % charset_len; + buf[offset] = charset[character]; + break; + case 1: + /* remove */ + memmove(buf + offset, buf + offset + 1, + strlen(buf) - offset); + break; + case 2: + /* insert */ + assert(strlen(buf) + 1 < sizeof(buf)); + + memmove(buf + offset + 1, buf + offset, + strlen(buf) + 1 - offset); + character = prng_rand(prng) % charset_len; + buf[offset] = charset[character]; + break; + } + } + return buf; +} + +void prng_free(struct prng *prng) +{ + free(prng); +} diff --git a/tests/helpers/c/prng.h b/tests/helpers/c/prng.h new file mode 100644 index 0000000..6b10bde --- /dev/null +++ b/tests/helpers/c/prng.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Very simple prng to allow for randomized tests with reproducable + * results. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ +#ifndef _PRNG_H +#define _PRNG_H + +struct prng; + +struct prng *prng_new(unsigned long long seed); +int prng_rand(struct prng *); +const char *prng_fuzz(struct prng *, const char *string, const char *charset, + unsigned int operations); +void prng_free(struct prng *); + +#endif diff --git a/tests/helpers/c/tests.h b/tests/helpers/c/tests.h new file mode 100644 index 0000000..40f17cc --- /dev/null +++ b/tests/helpers/c/tests.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test wrappers common header file + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing./ NetDEF, Inc. + * + * This file is part of Quagga + */ + +#ifndef _QUAGGA_TESTS_H +#define _QUAGGA_TESTS_H + +extern void test_init(void); +extern void test_init_cmd(void); + +#endif /* _QUAGGA_TESTS_H */ diff --git a/tests/helpers/python/frrsix.py b/tests/helpers/python/frrsix.py new file mode 100644 index 0000000..8af9647 --- /dev/null +++ b/tests/helpers/python/frrsix.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT +# +# Copyright (c) 2010-2017 Benjamin Peterson +# + +# +# This code is taken from the six python2 to python3 compatibility module +# + +import sys + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +if PY3: + import builtins + + exec_ = getattr(builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) diff --git a/tests/helpers/python/frrtest.py b/tests/helpers/python/frrtest.py new file mode 100644 index 0000000..3faa2a6 --- /dev/null +++ b/tests/helpers/python/frrtest.py @@ -0,0 +1,211 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Test helpers for FRR +# +# Copyright (C) 2017 by David Lamparter & Christian Franke, +# Open Source Routing / NetDEF Inc. +# +# This file is part of FRRouting (FRR) +# + +import subprocess +import sys +import re +import inspect +import os +import difflib + +import frrsix + +# +# These are the gritty internals of the TestMultiOut implementation. +# See below for the definition of actual TestMultiOut tests. +# + +srcbase = os.path.abspath(inspect.getsourcefile(frrsix)) +for i in range(0, 3): + srcbase = os.path.dirname(srcbase) + + +def binpath(srcpath): + return os.path.relpath(os.path.abspath(srcpath), srcbase) + + +class MultiTestFailure(Exception): + pass + + +class MetaTestMultiOut(type): + def __getattr__(cls, name): + if name.startswith("_"): + raise AttributeError + + internal_name = "_{}".format(name) + if internal_name not in dir(cls): + raise AttributeError + + def registrar(*args, **kwargs): + cls._add_test(getattr(cls, internal_name), *args, **kwargs) + + return registrar + + +@frrsix.add_metaclass(MetaTestMultiOut) +class _TestMultiOut(object): + def _run_tests(self): + if "tests_run" in dir(self.__class__) and self.tests_run: + return + self.__class__.tests_run = True + basedir = os.path.dirname(inspect.getsourcefile(type(self))) + program = os.path.join(basedir, self.program) + proc = subprocess.Popen([binpath(program)], stdout=subprocess.PIPE) + self.output, _ = proc.communicate("") + self.exitcode = proc.wait() + + self.__class__.testresults = {} + for test in self.tests: + try: + test(self) + except MultiTestFailure: + self.testresults[test] = sys.exc_info() + else: + self.testresults[test] = None + + def _exit_cleanly(self): + if self.exitcode != 0: + raise MultiTestFailure("Program did not terminate with exit code 0") + + @classmethod + def _add_test(cls, method, *args, **kwargs): + if "tests" not in dir(cls): + setattr(cls, "tests", []) + if method is not cls._exit_cleanly: + cls._add_test(cls._exit_cleanly) + + def matchfunction(self): + method(self, *args, **kwargs) + + cls.tests.append(matchfunction) + + def testfunction(self): + self._run_tests() + result = self.testresults[matchfunction] + if result is not None: + frrsix.reraise(*result) + + testname = re.sub(r"[^A-Za-z0-9]", "_", "%r%r" % (args, kwargs)) + testname = re.sub(r"__*", "_", testname) + testname = testname.strip("_") + if not testname: + testname = method.__name__.strip("_") + if "test_%s" % testname in dir(cls): + index = 2 + while "test_%s_%d" % (testname, index) in dir(cls): + index += 1 + testname = "%s_%d" % (testname, index) + setattr(cls, "test_%s" % testname, testfunction) + + +# +# This class houses the actual TestMultiOut tests types. +# If you want to add a new test type, you probably do it here. +# +# Say you want to add a test type called foobarlicious. Then define +# a function _foobarlicious here that takes self and the test arguments +# when called. That function should check the output in self.output +# to see whether it matches the expectation of foobarlicious with the +# given arguments and should then adjust self.output according to how +# much output it consumed. +# If the output doesn't meet the expectations, MultiTestFailure can be +# raised, however that should only be done after self.output has been +# modified according to consumed content. +# + +re_okfail = re.compile(r"(?:[3[12]m|^)?(?POK|failed)".encode("utf8"), re.MULTILINE) + + +class TestMultiOut(_TestMultiOut): + def _onesimple(self, line): + if type(line) is str: + line = line.encode("utf8") + idx = self.output.find(line) + if idx != -1: + self.output = self.output[idx + len(line) :] + else: + raise MultiTestFailure("%r could not be found" % line) + + def _okfail(self, line, okfail=re_okfail): + self._onesimple(line) + + m = okfail.search(self.output) + if m is None: + raise MultiTestFailure("OK/fail not found") + self.output = self.output[m.end() :] + + if m.group("ret") != "OK".encode("utf8"): + raise MultiTestFailure("Test output indicates failure") + + +# +# This class implements a test comparing the output of a program against +# an existing reference output +# + + +class TestRefMismatch(Exception): + def __init__(self, _test, outtext, reftext): + self.outtext = outtext + self.reftext = reftext + + def __str__(self): + rv = "Expected output and actual output differ:\n" + rv += "\n".join( + difflib.unified_diff( + self.reftext.splitlines(), + self.outtext.splitlines(), + "outtext", + "reftext", + lineterm="", + ) + ) + return rv + + +class TestExitNonzero(Exception): + pass + + +class TestRefOut(object): + def test_refout(self): + basedir = os.path.dirname(inspect.getsourcefile(type(self))) + program = os.path.join(basedir, self.program) + + if getattr(self, "built_refin", False): + refin = binpath(program) + ".in" + else: + refin = program + ".in" + if getattr(self, "built_refout", False): + refout = binpath(program) + ".refout" + else: + refout = program + ".refout" + + intext = "" + if os.path.exists(refin): + with open(refin, "rb") as f: + intext = f.read() + with open(refout, "rb") as f: + reftext = f.read() + + proc = subprocess.Popen( + [binpath(program)], stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + outtext, _ = proc.communicate(intext) + + # Get rid of newline problems (Windows vs Unix Style) + outtext_str = outtext.decode("utf8").replace("\r\n", "\n").replace("\r", "\n") + reftext_str = reftext.decode("utf8").replace("\r\n", "\n").replace("\r", "\n") + + if outtext_str != reftext_str: + raise TestRefMismatch(self, outtext_str, reftext_str) + if proc.wait() != 0: + raise TestExitNonzero(self) diff --git a/tests/isisd/.gitignore b/tests/isisd/.gitignore new file mode 100644 index 0000000..e124221 --- /dev/null +++ b/tests/isisd/.gitignore @@ -0,0 +1 @@ +/*_afl/* diff --git a/tests/isisd/subdir.am b/tests/isisd/subdir.am new file mode 100644 index 0000000..5adc162 --- /dev/null +++ b/tests/isisd/subdir.am @@ -0,0 +1,66 @@ +if !ISISD +PYTEST_IGNORE += --ignore=isisd/ +endif +ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD) +noinst_HEADERS += \ + tests/isisd/test_common.h \ + # end + + +if ISISD +check_PROGRAMS += tests/isisd/test_fuzz_isis_tlv +endif +tests_isisd_test_fuzz_isis_tlv_CFLAGS = $(TESTS_CFLAGS) -I$(top_builddir)/tests/isisd +tests_isisd_test_fuzz_isis_tlv_CPPFLAGS = $(TESTS_CPPFLAGS) -I$(top_builddir)/tests/isisd +tests_isisd_test_fuzz_isis_tlv_LDADD = $(ISISD_TEST_LDADD) +tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv.c tests/isisd/test_common.c +nodist_tests_isisd_test_fuzz_isis_tlv_SOURCES = tests/isisd/test_fuzz_isis_tlv_tests.h +EXTRA_DIST += \ + tests/isisd/test_fuzz_isis_tlv.py \ + tests/isisd/test_fuzz_isis_tlv_tests.h.gz \ + # end + +tests/isisd/test_fuzz_isis_tlv_tests.h: $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz + @$(MKDIR_P) tests/isisd + $(AM_V_GEN)gzip -d < $(top_srcdir)/tests/isisd/test_fuzz_isis_tlv_tests.h.gz > "$@" +CLEANFILES += tests/isisd/test_fuzz_isis_tlv_tests.h + +tests/isisd/tests_isisd_test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ + tests/isisd/test_fuzz_isis_tlv_tests.h +tests/isisd/test_fuzz_isis_tlv-test_fuzz_isis_tlv.$(OBJEXT): \ + tests/isisd/test_fuzz_isis_tlv_tests.h + + +if ISISD +check_PROGRAMS += tests/isisd/test_isis_lspdb +endif +tests_isisd_test_isis_lspdb_CFLAGS = $(TESTS_CFLAGS) +tests_isisd_test_isis_lspdb_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_isisd_test_isis_lspdb_LDADD = $(ISISD_TEST_LDADD) +tests_isisd_test_isis_lspdb_SOURCES = tests/isisd/test_isis_lspdb.c tests/isisd/test_common.c +EXTRA_DIST += tests/isisd/test_isis_lspdb.py + + +if ISISD +check_PROGRAMS += tests/isisd/test_isis_spf +endif +tests_isisd_test_isis_spf_CFLAGS = $(TESTS_CFLAGS) +tests_isisd_test_isis_spf_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_isisd_test_isis_spf_LDADD = $(ISISD_TEST_LDADD) +tests_isisd_test_isis_spf_SOURCES = tests/isisd/test_isis_spf.c tests/isisd/test_common.c tests/isisd/test_topologies.c +nodist_tests_isisd_test_isis_spf_SOURCES = yang/frr-isisd.yang.c +EXTRA_DIST += \ + tests/isisd/test_isis_spf.py \ + tests/isisd/test_isis_spf.in \ + tests/isisd/test_isis_spf.refout \ + # end + + +if ISISD +check_PROGRAMS += tests/isisd/test_isis_vertex_queue +endif +tests_isisd_test_isis_vertex_queue_CFLAGS = $(TESTS_CFLAGS) +tests_isisd_test_isis_vertex_queue_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_isisd_test_isis_vertex_queue_LDADD = $(ISISD_TEST_LDADD) +tests_isisd_test_isis_vertex_queue_SOURCES = tests/isisd/test_isis_vertex_queue.c tests/isisd/test_common.c +EXTRA_DIST += tests/isisd/test_isis_vertex_queue.py diff --git a/tests/isisd/test_common.c b/tests/isisd/test_common.c new file mode 100644 index 0000000..e474569 --- /dev/null +++ b/tests/isisd/test_common.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_mt.h" + +#include "test_common.h" + +struct event_loop *master; +struct zebra_privs_t isisd_privs; + +int isis_sock_init(struct isis_circuit *circuit) +{ + return 0; +} + +const struct isis_test_node * +test_topology_find_node(const struct isis_topology *topology, + const char *hostname, uint8_t pseudonode_id) +{ + for (size_t i = 0; topology->nodes[i].hostname[0]; i++) + if (strmatch(hostname, topology->nodes[i].hostname) + && pseudonode_id == topology->nodes[i].pseudonode_id) + return &topology->nodes[i]; + + return NULL; +} + +const struct isis_topology * +test_topology_find(struct isis_topology *test_topologies, uint16_t number) +{ + for (size_t i = 0; test_topologies[i].number; i++) + if (test_topologies[i].number == number) + return &test_topologies[i]; + + return NULL; +} + +static const struct isis_test_node * +test_find_adjacency(const struct isis_test_node *tnode, const char *hostname) +{ + for (size_t i = 0; tnode->adjacencies[i].hostname[0]; i++) { + const struct isis_test_adj *tadj; + + tadj = &tnode->adjacencies[i]; + if (strmatch(hostname, tadj->hostname)) + return tnode; + } + + return NULL; +} + +mpls_label_t test_topology_node_ldp_label(const struct isis_topology *topology, + struct in_addr router_id) +{ + for (size_t i = 0; topology->nodes[i].hostname[0]; i++) { + const struct isis_test_node *tnode = &topology->nodes[i]; + struct in_addr node_router_id; + + if (!tnode->router_id) + continue; + + (void)inet_pton(AF_INET, tnode->router_id, &node_router_id); + if (IPV4_ADDR_SAME(&router_id, &node_router_id)) + return (50000 + (i + 1) * 100); + } + + return MPLS_INVALID_LABEL; +} + +static struct isis_lsp *lsp_add(struct lspdb_head *lspdb, + struct isis_area *area, int level, + const uint8_t *sysid, uint8_t pseudonode_id) +{ + struct isis_lsp *lsp; + uint8_t lspid[ISIS_SYS_ID_LEN + 2]; + + memcpy(lspid, sysid, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(lspid) = pseudonode_id; + LSP_FRAGMENT(lspid) = 0; + + lsp = lsp_new(area, lspid, 6000, 1, 0, 0, NULL, level); + lsp->tlvs = isis_alloc_tlvs(); + lspdb_add(lspdb, lsp); + + return lsp; +} + +static void lsp_add_ip_reach(struct isis_lsp *lsp, + const struct isis_test_node *tnode, + const char *prefix_str, uint32_t *next_sid_index) +{ + struct prefix prefix; + struct sr_prefix_cfg pcfg = {}; + struct sr_prefix_cfg *pcfg_p[SR_ALGORITHM_COUNT] = {NULL}; + + if (str2prefix(prefix_str, &prefix) != 1) { + zlog_debug("%s: invalid network: %s", __func__, prefix_str); + return; + } + + if (CHECK_FLAG(tnode->flags, F_ISIS_TEST_NODE_SR)) { + pcfg_p[SR_ALGORITHM_SPF] = &pcfg; + + pcfg.sid = *next_sid_index; + *next_sid_index = *next_sid_index + 1; + pcfg.sid_type = SR_SID_VALUE_TYPE_INDEX; + pcfg.node_sid = true; + pcfg.last_hop_behavior = SR_LAST_HOP_BEHAVIOR_PHP; + } + + if (prefix.family == AF_INET) + isis_tlvs_add_extended_ip_reach(lsp->tlvs, + (struct prefix_ipv4 *)&prefix, + 10, false, pcfg_p); + else + isis_tlvs_add_ipv6_reach(lsp->tlvs, ISIS_MT_IPV6_UNICAST, + (struct prefix_ipv6 *)&prefix, 10, + false, pcfg_p); +} + +static void lsp_add_reach(struct isis_lsp *lsp, + const struct isis_test_node *tnode, + const uint8_t *ne_id, uint8_t pseudonode_id, + uint32_t metric, int family, mpls_label_t *next_label) +{ + uint8_t nodeid[ISIS_SYS_ID_LEN + 1]; + uint16_t mtid; + struct isis_ext_subtlvs *ext = NULL; + + memcpy(nodeid, ne_id, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(nodeid) = pseudonode_id; + + if (CHECK_FLAG(tnode->flags, F_ISIS_TEST_NODE_SR)) { + struct isis_adj_sid *adj_sid; + + adj_sid = XCALLOC(MTYPE_ISIS_SUBTLV, sizeof(*adj_sid)); + adj_sid->family = family; + SET_FLAG(adj_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_VFLG); + SET_FLAG(adj_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_LFLG); + if (family == AF_INET6) + SET_FLAG(adj_sid->flags, EXT_SUBTLV_LINK_ADJ_SID_FFLG); + adj_sid->weight = 0; + adj_sid->sid = *next_label; + *next_label = *next_label + 1; + + ext = isis_alloc_ext_subtlvs(); + isis_tlvs_add_adj_sid(ext, adj_sid); + } + + mtid = (family == AF_INET) ? ISIS_MT_IPV4_UNICAST + : ISIS_MT_IPV6_UNICAST; + + isis_tlvs_add_extended_reach(lsp->tlvs, mtid, nodeid, metric, ext); +} + +static void lsp_add_router_capability(struct isis_lsp *lsp, + const struct isis_test_node *tnode) +{ + struct isis_router_cap *cap; + + if (!tnode->router_id) + return; + + cap = isis_tlvs_init_router_capability(lsp->tlvs); + + if (inet_pton(AF_INET, tnode->router_id, &cap->router_id) != 1) { + zlog_debug("%s: invalid router-id: %s", __func__, + tnode->router_id); + return; + } + + if (CHECK_FLAG(tnode->flags, F_ISIS_TEST_NODE_SR)) { + cap->srgb.flags = + ISIS_SUBTLV_SRGB_FLAG_I | ISIS_SUBTLV_SRGB_FLAG_V; + cap->srgb.lower_bound = tnode->srgb.lower_bound + ? tnode->srgb.lower_bound + : SRGB_DFTL_LOWER_BOUND; + cap->srgb.range_size = tnode->srgb.range_size + ? tnode->srgb.range_size + : SRGB_DFTL_RANGE_SIZE; + cap->algo[0] = SR_ALGORITHM_SPF; + cap->algo[1] = SR_ALGORITHM_UNSET; + } + +} + +static void lsp_add_mt_router_info(struct isis_lsp *lsp, + const struct isis_test_node *tnode) +{ + if (tnode->protocols.ipv4) + isis_tlvs_add_mt_router_info(lsp->tlvs, ISIS_MT_IPV4_UNICAST, 0, + false); + if (tnode->protocols.ipv6) + isis_tlvs_add_mt_router_info(lsp->tlvs, ISIS_MT_IPV6_UNICAST, 0, + false); +} + +static void lsp_add_protocols_supported(struct isis_lsp *lsp, + const struct isis_test_node *tnode) +{ + struct nlpids nlpids = {}; + + if (!tnode->protocols.ipv4 && !tnode->protocols.ipv6) + return; + + if (tnode->protocols.ipv4) { + nlpids.nlpids[nlpids.count] = NLPID_IP; + nlpids.count++; + } + if (tnode->protocols.ipv6) { + nlpids.nlpids[nlpids.count] = NLPID_IPV6; + nlpids.count++; + } + isis_tlvs_set_protocols_supported(lsp->tlvs, &nlpids); +} + +static int topology_load_node_level(const struct isis_topology *topology, + const struct isis_test_node *tnode, + size_t tnode_index, struct isis_area *area, + struct lspdb_head *lspdb, int level) +{ + struct isis_lsp *lsp; + uint32_t next_sid_index = (tnode_index + 1) * 10; + mpls_label_t next_label = 16; + + lsp = lsp_add(lspdb, area, level, tnode->sysid, tnode->pseudonode_id); + lsp_add_mt_router_info(lsp, tnode); + lsp_add_protocols_supported(lsp, tnode); + lsp_add_router_capability(lsp, tnode); + + /* Add IP Reachability Information. */ + for (size_t i = 0; tnode->networks[i]; i++) { + if (i > MAX_NETWORKS) { + zlog_debug( + "%s: node has too many networks (maximum is %u)", + __func__, MAX_NETWORKS); + return -1; + } + lsp_add_ip_reach(lsp, tnode, tnode->networks[i], + &next_sid_index); + } + + /* Add IS Reachability Information. */ + for (size_t i = 0; tnode->adjacencies[i].hostname[0]; i++) { + const struct isis_test_adj *tadj; + const struct isis_test_node *tadj_node; + + if (i > MAX_ADJACENCIES) { + zlog_debug( + "%s: node has too many adjacencies (maximum is %u)", + __func__, MAX_ADJACENCIES); + return -1; + } + + tadj = &tnode->adjacencies[i]; + tadj_node = test_topology_find_node(topology, tadj->hostname, + tadj->pseudonode_id); + if (!tadj_node) { + zlog_debug( + "%s: node \"%s\" has an adjacency with non-existing node \"%s\"", + __func__, tnode->hostname, tadj->hostname); + return -1; + } + if (!test_find_adjacency(tadj_node, tnode->hostname)) { + zlog_debug( + "%s: node \"%s\" has an one-way adjacency with node \"%s\"", + __func__, tnode->hostname, tadj->hostname); + return -1; + } + + if (tnode->pseudonode_id || tadj_node->pseudonode_id + || (tnode->protocols.ipv4 && tadj_node->protocols.ipv4)) + lsp_add_reach(lsp, tnode, tadj_node->sysid, + tadj_node->pseudonode_id, tadj->metric, + AF_INET, &next_label); + if (tadj_node->pseudonode_id + || (tnode->protocols.ipv6 && tadj_node->protocols.ipv6)) + lsp_add_reach(lsp, tnode, tadj_node->sysid, + tadj_node->pseudonode_id, tadj->metric, + AF_INET6, &next_label); + } + + return 0; +} + +static int topology_load_node(const struct isis_topology *topology, + const struct isis_test_node *tnode, + size_t tnode_index, struct isis_area *area, + struct lspdb_head lspdb[]) +{ + int ret; + + isis_dynhn_insert(area->isis, tnode->sysid, tnode->hostname, + tnode->level); + + for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { + if ((tnode->level & level) == 0) + continue; + + ret = topology_load_node_level(topology, tnode, tnode_index, + area, &lspdb[level - 1], level); + if (ret != 0) + return ret; + } + + return 0; +} + +int test_topology_load(const struct isis_topology *topology, + struct isis_area *area, struct lspdb_head lspdb[]) +{ + for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) + lsp_db_init(&lspdb[level - 1]); + + for (size_t i = 0; topology->nodes[i].hostname[0]; i++) { + const struct isis_test_node *tnode = &topology->nodes[i]; + int ret; + + if (i > MAX_NODES) { + zlog_debug( + "%s: topology has too many nodes (maximum is %u)", + __func__, MAX_NODES); + return -1; + } + + ret = topology_load_node(topology, tnode, i, area, lspdb); + if (ret != 0) + return ret; + } + + return 0; +} diff --git a/tests/isisd/test_common.h b/tests/isisd/test_common.h new file mode 100644 index 0000000..f0c5493 --- /dev/null +++ b/tests/isisd/test_common.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#ifndef _COMMON_ISIS_H +#define _COMMON_ISIS_H + +#include "isisd/isisd.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" + +#define MAX_HOSTNAME 16 +#define MAX_NETWORKS 8 +#define MAX_ADJACENCIES 8 +#define MAX_NODES 12 + +#define SRGB_DFTL_LOWER_BOUND 16000 +#define SRGB_DFTL_RANGE_SIZE 8000 + +struct isis_test_adj { + char hostname[MAX_HOSTNAME]; + uint8_t pseudonode_id; + uint32_t metric; +}; + +struct isis_test_node { + char hostname[MAX_HOSTNAME]; + uint8_t sysid[ISIS_SYS_ID_LEN]; + uint8_t pseudonode_id; + int level; + struct { + bool ipv4; + bool ipv6; + } protocols; + const char *router_id; + struct { + uint32_t lower_bound; + uint32_t range_size; + } srgb; + const char *networks[MAX_NETWORKS + 1]; + struct isis_test_adj adjacencies[MAX_ADJACENCIES + 1]; + uint8_t flags; +}; +#define F_ISIS_TEST_NODE_SR 0x01 + +struct isis_topology { + uint16_t number; + struct isis_test_node nodes[MAX_NODES + 1]; +}; + +/* Prototypes. */ +extern int isis_sock_init(struct isis_circuit *circuit); +extern const struct isis_test_node * +test_topology_find_node(const struct isis_topology *topology, + const char *hostname, uint8_t pseudonode_id); +extern const struct isis_topology * +test_topology_find(struct isis_topology *test_topologies, uint16_t number); +extern mpls_label_t +test_topology_node_ldp_label(const struct isis_topology *topology, + struct in_addr router_id); +extern int test_topology_load(const struct isis_topology *topology, + struct isis_area *area, + struct lspdb_head lspdb[]); + +/* Global variables. */ +extern struct event_loop *master; +extern struct zebra_privs_t isisd_privs; +extern struct isis_topology test_topologies[]; + +#endif /* _COMMON_ISIS_H */ diff --git a/tests/isisd/test_fuzz_isis_tlv.c b/tests/isisd/test_fuzz_isis_tlv.c new file mode 100644 index 0000000..627ccfe --- /dev/null +++ b/tests/isisd/test_fuzz_isis_tlv.c @@ -0,0 +1,191 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "test_fuzz_isis_tlv_tests.h" + +#include + +#include "memory.h" +#include "sbuf.h" +#include "stream.h" +#include "frrevent.h" + +#include "isisd/isis_circuit.h" +#include "isisd/isis_tlvs.h" + +#include "test_common.h" + +#define TEST_STREAM_SIZE 1500 + +static bool atexit_registered; + +static void show_meminfo_at_exit(void) +{ + log_memstats(stderr, "isis fuzztest"); +} + +static int comp_line(const void *p1, const void *p2) +{ + return strcmp(*(char * const *)p1, *(char * const *)p2); +} + +static char *sortlines(char *in) +{ + size_t line_count = 1; + size_t rv_len = strlen(in) + 1; + size_t rv_pos = 0; + char *rv = XMALLOC(MTYPE_TMP, rv_len); + + for (char *c = in; *c; c++) { + if (*c == '\n') + line_count++; + } + + if (line_count == 1) { + strncpy(rv, in, rv_len); + return rv; + } + + char **lines = XCALLOC(MTYPE_TMP, sizeof(char *)*line_count); + char *saveptr = NULL; + size_t i = 0; + + for (char *line = strtok_r(in, "\n", &saveptr); line; + line = strtok_r(NULL, "\n", &saveptr)) { + lines[i++] = line; + assert(i <= line_count); + } + + line_count = i; + + qsort(lines, line_count, sizeof(char *), comp_line); + + for (i = 0; i < line_count; i++) { + int printf_rv = snprintf(rv + rv_pos, rv_len - rv_pos, "%s\n", lines[i]); + assert(printf_rv >= 0); + rv_pos += printf_rv; + } + + XFREE(MTYPE_TMP, lines); + return rv; +} + +static int test(FILE *input, FILE *output) +{ + struct stream *s = stream_new(TEST_STREAM_SIZE); + char buf[TEST_STREAM_SIZE]; + size_t bytes_read = 0; + + if (!atexit_registered) { + atexit(show_meminfo_at_exit); + atexit_registered = true; + } + + while (STREAM_WRITEABLE(s) && !feof(input)) { + bytes_read = fread(buf, 1, STREAM_WRITEABLE(s), input); + if (bytes_read == 0) + break; + stream_put(s, buf, bytes_read); + } + + if (bytes_read && !feof(input)) { + fprintf(output, "Too much input data.\n"); + stream_free(s); + return 1; + } + + stream_set_getp(s, 0); + struct isis_tlvs *tlvs; + const char *log; + int rv = isis_unpack_tlvs(STREAM_READABLE(s), s, &tlvs, &log); + + if (rv) { + fprintf(output, "Could not unpack TLVs:\n%s\n", log); + isis_free_tlvs(tlvs); + stream_free(s); + return 2; + } + + fprintf(output, "Unpack log:\n%s", log); + const char *s_tlvs = isis_format_tlvs(tlvs, NULL); + fprintf(output, "Unpacked TLVs:\n%s", s_tlvs); + + struct isis_item *orig_auth = tlvs->isis_auth.head; + tlvs->isis_auth.head = NULL; + s_tlvs = isis_format_tlvs(tlvs, NULL); + struct isis_tlvs *tlv_copy = isis_copy_tlvs(tlvs); + tlvs->isis_auth.head = orig_auth; + isis_free_tlvs(tlvs); + + struct stream *s2 = stream_new(TEST_STREAM_SIZE); + + if (isis_pack_tlvs(tlv_copy, s2, (size_t)-1, false, false)) { + fprintf(output, "Could not pack TLVs.\n"); + assert(0); + } + + stream_set_getp(s2, 0); + rv = isis_unpack_tlvs(STREAM_READABLE(s2), s2, &tlvs, &log); + if (rv) { + fprintf(output, "Could not unpack own TLVs:\n%s\n", log); + assert(0); + } + + char *orig_tlvs = XSTRDUP(MTYPE_TMP, s_tlvs); + s_tlvs = isis_format_tlvs(tlvs, NULL); + + if (strcmp(orig_tlvs, s_tlvs)) { + fprintf(output, + "Deserialized and Serialized LSP seem to differ.\n"); + fprintf(output, "Re-Unpacked TLVs:\n%s", s_tlvs); + assert(0); + } + + isis_free_tlvs(tlv_copy); + stream_free(s); + stream_free(s2); + + struct list *fragments = isis_fragment_tlvs(tlvs, 550); + isis_free_tlvs(tlvs); + if (!fragments) { + XFREE(MTYPE_TMP, orig_tlvs); + return 0; + } + + s = stream_new(550); + + struct sbuf fragment_format; + sbuf_init(&fragment_format, NULL, 0); + + struct listnode *node; + for (ALL_LIST_ELEMENTS_RO(fragments, node, tlvs)) { + stream_reset(s); + int rv = isis_pack_tlvs(tlvs, s, (size_t)-1, false, false); + if (rv) { + fprintf(output, "Could not pack fragment, too large.\n"); + assert(0); + } + sbuf_push(&fragment_format, 0, "%s", isis_format_tlvs(tlvs, NULL)); + isis_free_tlvs(tlvs); + } + list_delete(&fragments); + stream_free(s); + + char *fragment_content = sortlines((char *)sbuf_buf(&fragment_format)); + sbuf_free(&fragment_format); + char *orig_tlv_content = sortlines(orig_tlvs); + XFREE(MTYPE_TMP, orig_tlvs); + + if (strcmp(fragment_content, orig_tlv_content)) { + fprintf(output, "Fragmented and unfragmented LSP seem to differ.\n"); + fprintf(output, "Original:\n%s\nFragmented:\n%s\n", + orig_tlv_content, fragment_content); + assert(0); + } + + XFREE(MTYPE_TMP, fragment_content); + XFREE(MTYPE_TMP, orig_tlv_content); + + return 0; +} diff --git a/tests/isisd/test_fuzz_isis_tlv.py b/tests/isisd/test_fuzz_isis_tlv.py new file mode 100644 index 0000000..8fd20aa --- /dev/null +++ b/tests/isisd/test_fuzz_isis_tlv.py @@ -0,0 +1,32 @@ +import frrtest + +import pytest +import platform +import socket + + +## +# on musl, ntop compresses a single :0: -> :: which is against RFC +## +def inet_ntop_broken(): + addr = "1:2:3:4:0:6:7:8" + return ( + socket.inet_ntop(socket.AF_INET6, socket.inet_pton(socket.AF_INET6, addr)) + != addr + ) + + +if platform.uname()[0] == "SunOS" or inet_ntop_broken(): + + class TestFuzzIsisTLV: + @pytest.mark.skipif(True, reason="Test unsupported") + def test_exit_cleanly(self): + pass + + +else: + + class TestFuzzIsisTLV(frrtest.TestMultiOut): + program = "./test_fuzz_isis_tlv" + + TestFuzzIsisTLV.exit_cleanly() diff --git a/tests/isisd/test_fuzz_isis_tlv_tests.h.gz b/tests/isisd/test_fuzz_isis_tlv_tests.h.gz new file mode 100644 index 0000000..195a7dd Binary files /dev/null and b/tests/isisd/test_fuzz_isis_tlv_tests.h.gz differ diff --git a/tests/isisd/test_isis_lspdb.c b/tests/isisd/test_isis_lspdb.c new file mode 100644 index 0000000..cc95c4a --- /dev/null +++ b/tests/isisd/test_isis_lspdb.c @@ -0,0 +1,81 @@ +#include + +#include "isisd/isis_lsp.c" + +#include "test_common.h" + +static void test_lsp_build_list_nonzero_ht(void) +{ + uint8_t lsp_id1[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 + }; + uint8_t lsp_id_end[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x5f, 0x00 + }; + uint8_t lsp_id2[8] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00 + }; + + struct isis_area *area = calloc(sizeof(*area), 1); + + area->lsp_mtu = 1500; + + struct lspdb_head _lspdb, *lspdb = &_lspdb; + lsp_db_init(&_lspdb); + + struct isis_lsp *lsp1 = + lsp_new(area, lsp_id1, 6000, 1, 0, 0, NULL, ISIS_LEVEL2); + + lspdb_add(lspdb, lsp1); + + struct isis_lsp *lsp2 = + lsp_new(area, lsp_id2, 6000, 1, 0, 0, NULL, ISIS_LEVEL2); + + lspdb_add(lspdb, lsp2); + + struct list *list = list_new(); + + lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); + assert(list->count == 1); + assert(listgetdata(listhead(list)) == lsp1); + list_delete_all_node(list); + + lsp_id_end[5] = 0x03; + lsp_id_end[6] = 0x00; + + lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); + assert(list->count == 2); + assert(listgetdata(listhead(list)) == lsp1); + assert(listgetdata(listtail(list)) == lsp2); + list_delete_all_node(list); + + memcpy(lsp_id1, lsp_id2, sizeof(lsp_id1)); + + lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); + assert(list->count == 1); + assert(listgetdata(listhead(list)) == lsp2); + list_delete_all_node(list); + + lsp_id1[5] = 0x03; + lsp_id_end[5] = 0x04; + + lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); + assert(list->count == 0); + list_delete_all_node(list); + + lsp_id1[5] = 0x00; + + lsp_build_list_nonzero_ht(lspdb, lsp_id1, lsp_id_end, list); + assert(list->count == 2); + assert(listgetdata(listhead(list)) == lsp1); + assert(listgetdata(listtail(list)) == lsp2); + list_delete_all_node(list); +} + +int main(int argc, char **argv) +{ + struct isis *isis = NULL; + isis = calloc(sizeof(*isis), 1); + test_lsp_build_list_nonzero_ht(); + return 0; +} diff --git a/tests/isisd/test_isis_lspdb.py b/tests/isisd/test_isis_lspdb.py new file mode 100644 index 0000000..280ed1c --- /dev/null +++ b/tests/isisd/test_isis_lspdb.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestIsisLSPDB(frrtest.TestMultiOut): + program = "./test_isis_lspdb" + + +TestIsisLSPDB.exit_cleanly() diff --git a/tests/isisd/test_isis_spf.c b/tests/isisd/test_isis_spf.c new file mode 100644 index 0000000..4ea28cd --- /dev/null +++ b/tests/isisd/test_isis_spf.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#include +#include + +#include +#include "getopt.h" +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "log.h" +#include "vrf.h" +#include "yang.h" + +#include "isisd/isisd.h" +#include "isisd/isis_dynhn.h" +#include "isisd/isis_misc.h" +#include "isisd/isis_route.h" +#include "isisd/isis_spf.h" +#include "isisd/isis_spf_private.h" + +#include "test_common.h" + +enum test_type { + TEST_SPF = 1, + TEST_REVERSE_SPF, + TEST_LFA, + TEST_RLFA, + TEST_TI_LFA, +}; + +#define F_DISPLAY_LSPDB 0x01 +#define F_IPV4_ONLY 0x02 +#define F_IPV6_ONLY 0x04 +#define F_LEVEL1_ONLY 0x08 +#define F_LEVEL2_ONLY 0x10 + +static void test_run_spf(struct vty *vty, const struct isis_topology *topology, + const struct isis_test_node *root, + struct isis_area *area, struct lspdb_head *lspdb, + int level, int tree, bool reverse) +{ + struct isis_spftree *spftree; + enum spf_type spf_type; + + /* Run SPF. */ + spf_type = reverse ? SPF_TYPE_REVERSE : SPF_TYPE_FORWARD; + spftree = isis_spftree_new(area, lspdb, root->sysid, level, tree, + spf_type, F_SPFTREE_NO_ADJACENCIES, + SR_ALGORITHM_SPF); + isis_run_spf(spftree); + + /* Print the SPT and the corresponding routing table. */ + isis_print_spftree(vty, spftree); + isis_print_routes(vty, spftree, NULL, false, false); + + /* Cleanup SPF tree. */ + isis_spftree_del(spftree); +} + +static void test_run_lfa(struct vty *vty, const struct isis_topology *topology, + const struct isis_test_node *root, + struct isis_area *area, struct lspdb_head *lspdb, + int level, int tree, + struct lfa_protected_resource *protected_resource) +{ + struct isis_spftree *spftree_self; + uint8_t flags; + + /* Run forward SPF in the root node. */ + flags = F_SPFTREE_NO_ADJACENCIES; + spftree_self = + isis_spftree_new(area, lspdb, root->sysid, level, tree, + SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF); + isis_run_spf(spftree_self); + + /* Run forward SPF on all adjacent routers. */ + isis_spf_run_neighbors(spftree_self); + + /* Compute the LFA repair paths. */ + isis_lfa_compute(area, NULL, spftree_self, protected_resource); + + /* Print the SPT and the corresponding main/backup routing tables. */ + isis_print_spftree(vty, spftree_self); + vty_out(vty, "Main:\n"); + isis_print_routes(vty, spftree_self, NULL, false, false); + vty_out(vty, "Backup:\n"); + isis_print_routes(vty, spftree_self, NULL, false, true); + + /* Cleanup everything. */ + isis_spftree_del(spftree_self); +} + +static void test_run_rlfa(struct vty *vty, const struct isis_topology *topology, + const struct isis_test_node *root, + struct isis_area *area, struct lspdb_head *lspdb, + int level, int tree, + struct lfa_protected_resource *protected_resource) +{ + struct isis_spftree *spftree_self; + struct isis_spftree *spftree_reverse; + struct isis_spftree *spftree_pc; + struct isis_spf_node *spf_node, *node; + struct rlfa *rlfa; + uint8_t flags; + + /* Run forward SPF in the root node. */ + flags = F_SPFTREE_NO_ADJACENCIES; + spftree_self = + isis_spftree_new(area, lspdb, root->sysid, level, tree, + SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF); + isis_run_spf(spftree_self); + + /* Run reverse SPF in the root node. */ + spftree_reverse = isis_spf_reverse_run(spftree_self); + + /* Run forward SPF on all adjacent routers. */ + isis_spf_run_neighbors(spftree_self); + + /* Compute the local LFA repair paths. */ + isis_lfa_compute(area, NULL, spftree_self, protected_resource); + + /* Compute the remote LFA repair paths. */ + spftree_pc = isis_rlfa_compute(area, spftree_self, spftree_reverse, 0, + protected_resource); + + /* Print the extended P-space and Q-space. */ + vty_out(vty, "P-space (self):\n"); + RB_FOREACH (node, isis_spf_nodes, &spftree_pc->lfa.p_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + RB_FOREACH (spf_node, isis_spf_nodes, &spftree_self->adj_nodes) { + if (RB_EMPTY(isis_spf_nodes, &spf_node->lfa.p_space)) + continue; + vty_out(vty, "P-space (%s):\n", + print_sys_hostname(spf_node->sysid)); + RB_FOREACH (node, isis_spf_nodes, &spf_node->lfa.p_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + } + vty_out(vty, "Q-space:\n"); + RB_FOREACH (node, isis_spf_nodes, &spftree_pc->lfa.q_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + + /* Print the post-convergence SPT. */ + isis_print_spftree(vty, spftree_pc); + + /* + * Activate the computed RLFAs (if any) using artificial LDP labels for + * the PQ nodes. + */ + frr_each_safe (rlfa_tree, &spftree_self->lfa.remote.rlfas, rlfa) { + struct zapi_rlfa_response response = {}; + + response.pq_label = test_topology_node_ldp_label( + topology, rlfa->pq_address); + assert(response.pq_label != MPLS_INVALID_LABEL); + isis_rlfa_activate(spftree_self, rlfa, &response); + } + + /* Print the SPT and the corresponding main/backup routing tables. */ + isis_print_spftree(vty, spftree_self); + vty_out(vty, "Main:\n"); + isis_print_routes(vty, spftree_self, NULL, false, false); + vty_out(vty, "Backup:\n"); + isis_print_routes(vty, spftree_self, NULL, false, true); + + /* Cleanup everything. */ + isis_spftree_del(spftree_self); + isis_spftree_del(spftree_reverse); + isis_spftree_del(spftree_pc); +} + +static void test_run_ti_lfa(struct vty *vty, + const struct isis_topology *topology, + const struct isis_test_node *root, + struct isis_area *area, struct lspdb_head *lspdb, + int level, int tree, + struct lfa_protected_resource *protected_resource) +{ + struct isis_spftree *spftree_self; + struct isis_spftree *spftree_reverse; + struct isis_spftree *spftree_pc; + struct isis_spf_node *spf_node, *node; + uint8_t flags; + + /* Run forward SPF in the root node. */ + flags = F_SPFTREE_NO_ADJACENCIES; + spftree_self = + isis_spftree_new(area, lspdb, root->sysid, level, tree, + SPF_TYPE_FORWARD, flags, SR_ALGORITHM_SPF); + isis_run_spf(spftree_self); + + /* Run reverse SPF in the root node. */ + spftree_reverse = isis_spf_reverse_run(spftree_self); + + /* Run forward SPF on all adjacent routers. */ + isis_spf_run_neighbors(spftree_self); + + /* Compute the TI-LFA repair paths. */ + spftree_pc = isis_tilfa_compute(area, spftree_self, spftree_reverse, + protected_resource); + + /* Print the extended P-space and Q-space. */ + vty_out(vty, "P-space (self):\n"); + RB_FOREACH (node, isis_spf_nodes, &spftree_pc->lfa.p_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + RB_FOREACH (spf_node, isis_spf_nodes, &spftree_self->adj_nodes) { + if (RB_EMPTY(isis_spf_nodes, &spf_node->lfa.p_space)) + continue; + vty_out(vty, "P-space (%s):\n", + print_sys_hostname(spf_node->sysid)); + RB_FOREACH (node, isis_spf_nodes, &spf_node->lfa.p_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + } + vty_out(vty, "Q-space:\n"); + RB_FOREACH (node, isis_spf_nodes, &spftree_pc->lfa.q_space) + vty_out(vty, " %s\n", print_sys_hostname(node->sysid)); + vty_out(vty, "\n"); + + /* + * Print the post-convergence SPT and the corresponding routing table. + */ + isis_print_spftree(vty, spftree_pc); + isis_print_routes(vty, spftree_self, NULL, false, true); + + /* Cleanup everything. */ + isis_spftree_del(spftree_self); + isis_spftree_del(spftree_reverse); + isis_spftree_del(spftree_pc); +} + +static int test_run(struct vty *vty, const struct isis_topology *topology, + const struct isis_test_node *root, enum test_type test_type, + uint8_t flags, enum lfa_protection_type protection_type, + const char *fail_sysid_str, uint8_t fail_pseudonode_id) +{ + struct isis_area *area; + struct lfa_protected_resource protected_resource = {}; + uint8_t fail_id[ISIS_SYS_ID_LEN] = {}; + + /* Init topology. */ + area = isis_area_create("1", NULL); + memcpy(area->isis->sysid, root->sysid, sizeof(area->isis->sysid)); + area->is_type = IS_LEVEL_1_AND_2; + area->srdb.enabled = true; + if (test_topology_load(topology, area, area->lspdb) != 0) { + vty_out(vty, "%% Failed to load topology\n"); + return CMD_WARNING; + } + + /* Parse failed link/node. */ + if (fail_sysid_str) { + if (sysid2buff(fail_id, fail_sysid_str) == 0) { + struct isis_dynhn *dynhn; + + dynhn = dynhn_find_by_name(area->isis, fail_sysid_str); + if (dynhn == NULL) { + vty_out(vty, "Invalid system id %s\n", + fail_sysid_str); + return CMD_WARNING; + } + memcpy(fail_id, dynhn->id, ISIS_SYS_ID_LEN); + } + + protected_resource.type = protection_type; + memcpy(protected_resource.adjacency, fail_id, ISIS_SYS_ID_LEN); + LSP_PSEUDO_ID(protected_resource.adjacency) = + fail_pseudonode_id; + } + + for (int level = IS_LEVEL_1; level <= IS_LEVEL_2; level++) { + if (level == IS_LEVEL_1 && CHECK_FLAG(flags, F_LEVEL2_ONLY)) + continue; + if (level == IS_LEVEL_2 && CHECK_FLAG(flags, F_LEVEL1_ONLY)) + continue; + if ((root->level & level) == 0) + continue; + + /* Print the LDPDB. */ + if (CHECK_FLAG(flags, F_DISPLAY_LSPDB)) + show_isis_database_lspdb_vty(vty, area, level - 1, + &area->lspdb[level - 1], NULL, + ISIS_UI_LEVEL_DETAIL); + + for (int tree = SPFTREE_IPV4; tree <= SPFTREE_IPV6; tree++) { + if (tree == SPFTREE_IPV4 + && CHECK_FLAG(flags, F_IPV6_ONLY)) + continue; + if (tree == SPFTREE_IPV6 + && CHECK_FLAG(flags, F_IPV4_ONLY)) + continue; + + switch (test_type) { + case TEST_SPF: + test_run_spf(vty, topology, root, area, + &area->lspdb[level - 1], level, + tree, false); + break; + case TEST_REVERSE_SPF: + test_run_spf(vty, topology, root, area, + &area->lspdb[level - 1], level, + tree, true); + break; + case TEST_LFA: + test_run_lfa(vty, topology, root, area, + &area->lspdb[level - 1], level, + tree, &protected_resource); + break; + case TEST_RLFA: + test_run_rlfa(vty, topology, root, area, + &area->lspdb[level - 1], level, + tree, &protected_resource); + break; + case TEST_TI_LFA: + test_run_ti_lfa(vty, topology, root, area, + &area->lspdb[level - 1], level, + tree, &protected_resource); + break; + } + } + } + + /* Cleanup IS-IS area. */ + isis_area_destroy(area); + + return CMD_SUCCESS; +} + +DEFUN(test_isis, test_isis_cmd, + "test isis topology (1-14) root HOSTNAME\ + <\ + spf\ + |reverse-spf\ + |lfa system-id WORD [pseudonode-id <1-255>]\ + |remote-lfa system-id WORD [pseudonode-id <1-255>]\ + |ti-lfa system-id WORD [pseudonode-id <1-255>] [node-protection]\ + >\ + [display-lspdb] [] []", + "Test command\n" + "IS-IS routing protocol\n" + "Test topology\n" + "Test topology number\n" + "SPF root\n" + "SPF root hostname\n" + "Normal Shortest Path First\n" + "Reverse Shortest Path First\n" + "Classic LFA\n" + "System ID\n" + "System ID\n" + "Pseudonode-ID\n" + "Pseudonode-ID\n" + "Remote LFA\n" + "System ID\n" + "System ID\n" + "Pseudonode-ID\n" + "Pseudonode-ID\n" + "Topology Independent LFA\n" + "System ID\n" + "System ID\n" + "Pseudonode-ID\n" + "Pseudonode-ID\n" + "Node protection\n" + "Display the LSPDB\n" + "Do IPv4 processing only\n" + "Do IPv6 processing only\n" + "Skip L2 LSPs\n" + "Skip L1 LSPs\n") +{ + uint16_t topology_number; + const struct isis_topology *topology; + const struct isis_test_node *root; + enum test_type test_type; + enum lfa_protection_type protection_type = 0; + const char *fail_sysid_str = NULL; + uint8_t fail_pseudonode_id = 0; + uint8_t flags = 0; + int idx = 0; + + /* Load topology. */ + argv_find(argv, argc, "topology", &idx); + topology_number = atoi(argv[idx + 1]->arg); + topology = test_topology_find(test_topologies, topology_number); + if (!topology) { + vty_out(vty, "%% Topology \"%s\" not found\n", + argv[idx + 1]->arg); + return CMD_WARNING; + } + + /* Find root node. */ + argv_find(argv, argc, "root", &idx); + root = test_topology_find_node(topology, argv[idx + 1]->arg, 0); + if (!root) { + vty_out(vty, "%% Node \"%s\" not found\n", argv[idx + 1]->arg); + return CMD_WARNING; + } + + /* Parse test information. */ + if (argv_find(argv, argc, "spf", &idx)) + test_type = TEST_SPF; + else if (argv_find(argv, argc, "reverse-spf", &idx)) + test_type = TEST_REVERSE_SPF; + else if (argv_find(argv, argc, "lfa", &idx)) { + test_type = TEST_LFA; + + fail_sysid_str = argv[idx + 2]->arg; + if (argv_find(argv, argc, "pseudonode-id", &idx)) + fail_pseudonode_id = + strtoul(argv[idx + 1]->arg, NULL, 10); + protection_type = LFA_LINK_PROTECTION; + } else if (argv_find(argv, argc, "remote-lfa", &idx)) { + test_type = TEST_RLFA; + + fail_sysid_str = argv[idx + 2]->arg; + if (argv_find(argv, argc, "pseudonode-id", &idx)) + fail_pseudonode_id = + strtoul(argv[idx + 1]->arg, NULL, 10); + protection_type = LFA_LINK_PROTECTION; + } else if (argv_find(argv, argc, "ti-lfa", &idx)) { + test_type = TEST_TI_LFA; + + fail_sysid_str = argv[idx + 2]->arg; + if (argv_find(argv, argc, "pseudonode-id", &idx)) + fail_pseudonode_id = + strtoul(argv[idx + 1]->arg, NULL, 10); + if (argv_find(argv, argc, "node-protection", &idx)) + protection_type = LFA_NODE_PROTECTION; + else + protection_type = LFA_LINK_PROTECTION; + } else + return CMD_WARNING; + + /* Parse control flags. */ + if (argv_find(argv, argc, "display-lspdb", &idx)) + SET_FLAG(flags, F_DISPLAY_LSPDB); + if (argv_find(argv, argc, "ipv4-only", &idx)) + SET_FLAG(flags, F_IPV4_ONLY); + else if (argv_find(argv, argc, "ipv6-only", &idx)) + SET_FLAG(flags, F_IPV6_ONLY); + if (argv_find(argv, argc, "level-1-only", &idx)) + SET_FLAG(flags, F_LEVEL1_ONLY); + else if (argv_find(argv, argc, "level-2-only", &idx)) + SET_FLAG(flags, F_LEVEL2_ONLY); + + return test_run(vty, topology, root, test_type, flags, protection_type, + fail_sysid_str, fail_pseudonode_id); +} + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + + cmd_terminate(); + vty_terminate(); + yang_terminate(); + event_master_free(master); + + log_memstats(stderr, "test-isis-spf"); + if (!isexit) + exit(0); +} + +struct option longopts[] = {{"help", no_argument, NULL, 'h'}, + {"debug", no_argument, NULL, 'd'}, + {0}}; + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +isisd SPF test program.\n\n\ +-u, --debug Enable debugging\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + +int main(int argc, char **argv) +{ + char *p; + char *progname; + struct event thread; + bool debug = false; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "hd", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'd': + debug = true; + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* master init. */ + master = event_master_create(NULL); + isis_master_init(master); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + vty_init(master, false); + yang_init(true, false); + if (debug) + zlog_aux_init("NONE: ", LOG_DEBUG); + else + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + /* IS-IS inits. */ + yang_module_load("frr-isisd", NULL); + SET_FLAG(im->options, F_ISIS_UNIT_TEST); + debug_spf_events |= DEBUG_SPF_EVENTS; + debug_lfa |= DEBUG_LFA; + debug_events |= DEBUG_EVENTS; + debug_rte_events |= DEBUG_RTE_EVENTS; + + /* Install test command. */ + install_element(VIEW_NODE, &test_isis_cmd); + + /* Read input from .in file. */ + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/isisd/test_isis_spf.in b/tests/isisd/test_isis_spf.in new file mode 100644 index 0000000..f8f65ff --- /dev/null +++ b/tests/isisd/test_isis_spf.in @@ -0,0 +1,68 @@ +test isis topology 1 root rt1 spf +test isis topology 2 root rt1 spf +test isis topology 3 root rt1 spf ipv4-only +test isis topology 4 root rt1 spf ipv4-only +test isis topology 5 root rt1 spf ipv4-only +test isis topology 6 root rt1 spf ipv4-only +test isis topology 7 root rt1 spf ipv4-only +test isis topology 8 root rt1 spf ipv4-only +test isis topology 9 root rt1 spf +test isis topology 10 root rt1 spf +test isis topology 11 root rt1 spf +test isis topology 12 root rt1 spf ipv4-only +test isis topology 13 root rt1 spf ipv4-only + +test isis topology 4 root rt1 reverse-spf ipv4-only +test isis topology 11 root rt1 reverse-spf + +test isis topology 1 root rt1 lfa system-id rt2 +test isis topology 2 root rt4 lfa system-id rt1 pseudonode-id 1 +test isis topology 2 root rt4 lfa system-id rt6 +test isis topology 3 root rt1 lfa system-id rt2 +test isis topology 3 root rt1 lfa system-id rt3 +test isis topology 7 root rt1 lfa system-id rt4 +test isis topology 7 root rt7 lfa system-id rt8 +test isis topology 7 root rt8 lfa system-id rt11 +test isis topology 9 root rt3 lfa system-id rt1 +test isis topology 10 root rt8 lfa system-id rt5 +test isis topology 11 root rt3 lfa system-id rt5 +test isis topology 13 root rt4 lfa system-id rt3 +test isis topology 14 root rt1 lfa system-id rt1 pseudonode-id 1 +test isis topology 14 root rt1 lfa system-id rt2 +test isis topology 14 root rt5 lfa system-id rt4 + +test isis topology 1 root rt1 remote-lfa system-id rt2 +test isis topology 2 root rt5 remote-lfa system-id rt1 pseudonode-id 1 +test isis topology 3 root rt5 remote-lfa system-id rt4 ipv4-only +test isis topology 3 root rt5 remote-lfa system-id rt3 ipv4-only +test isis topology 5 root rt1 remote-lfa system-id rt2 ipv4-only +test isis topology 6 root rt4 remote-lfa system-id rt3 ipv4-only +test isis topology 7 root rt11 remote-lfa system-id rt8 ipv4-only +test isis topology 7 root rt6 remote-lfa system-id rt5 ipv4-only +test isis topology 8 root rt2 remote-lfa system-id rt5 ipv4-only +test isis topology 11 root rt2 remote-lfa system-id rt4 +test isis topology 13 root rt1 remote-lfa system-id rt3 ipv4-only + +test isis topology 1 root rt1 ti-lfa system-id rt2 +test isis topology 2 root rt1 ti-lfa system-id rt3 +test isis topology 2 root rt1 ti-lfa system-id rt1 pseudonode-id 1 +test isis topology 2 root rt5 ti-lfa system-id rt1 pseudonode-id 1 +test isis topology 3 root rt5 ti-lfa system-id rt4 ipv4-only +test isis topology 3 root rt5 ti-lfa system-id rt3 ipv4-only +test isis topology 4 root rt1 ti-lfa system-id rt2 ipv4-only +test isis topology 4 root rt4 ti-lfa system-id rt6 ipv4-only +test isis topology 5 root rt1 ti-lfa system-id rt2 ipv4-only +test isis topology 6 root rt4 ti-lfa system-id rt3 ipv4-only +test isis topology 7 root rt11 ti-lfa system-id rt8 ipv4-only +test isis topology 7 root rt6 ti-lfa system-id rt5 ipv4-only +test isis topology 8 root rt2 ti-lfa system-id rt1 ipv4-only +test isis topology 8 root rt2 ti-lfa system-id rt5 ipv4-only +test isis topology 9 root rt1 ti-lfa system-id rt3 +test isis topology 9 root rt1 ti-lfa system-id rt2 +test isis topology 9 root rt9 ti-lfa system-id rt5 +test isis topology 9 root rt9 ti-lfa system-id rt8 +test isis topology 10 root rt1 ti-lfa system-id rt2 +test isis topology 10 root rt1 ti-lfa system-id rt4 +test isis topology 11 root rt2 ti-lfa system-id rt4 +test isis topology 12 root rt1 ti-lfa system-id rt3 ipv4-only +test isis topology 13 root rt1 ti-lfa system-id rt3 ipv4-only diff --git a/tests/isisd/test_isis_spf.py b/tests/isisd/test_isis_spf.py new file mode 100644 index 0000000..f44fa70 --- /dev/null +++ b/tests/isisd/test_isis_spf.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestIsisSPF(frrtest.TestRefOut): + program = "./test_isis_spf" diff --git a/tests/isisd/test_isis_spf.refout b/tests/isisd/test_isis_spf.refout new file mode 100644 index 0000000..23d41b9 --- /dev/null +++ b/tests/isisd/test_isis_spf.refout @@ -0,0 +1,4800 @@ +test# test isis topology 1 root rt1 spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + rt3 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + - rt3 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt2 - rt6(4) + rt3 - + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::4/128 30 - rt2 16041 + 2001:db8::5/128 30 - rt3 16051 + 2001:db8::6/128 40 - rt2 16061 + - rt3 16061 + +test# test isis topology 2 root rt1 spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 10 rt5 - rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +rt1 +rt6 TE-IS 20 rt4 - rt4(4) + rt5 - rt5(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.2/32 IP TE 25 rt2 - rt2(4) +rt3 TE-IS 30 rt3 - rt1(4) +10.0.255.6/32 IP TE 30 rt4 - rt6(4) + rt5 - +10.0.255.3/32 IP TE 40 rt3 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 25 - rt2 implicit-null + 10.0.255.3/32 40 - rt3 implicit-null + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 30 - rt4 16060 + - rt5 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 10 rt5 - rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +rt1 +rt6 TE-IS 20 rt4 - rt4(4) + rt5 - rt5(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::2/128 IP6 internal 25 rt2 - rt2(4) +rt3 TE-IS 30 rt3 - rt1(4) +2001:db8::6/128 IP6 internal 30 rt4 - rt6(4) + rt5 - +2001:db8::3/128 IP6 internal 40 rt3 - rt3(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 25 - rt2 implicit-null + 2001:db8::3/128 40 - rt3 implicit-null + 2001:db8::4/128 20 - rt4 implicit-null + 2001:db8::5/128 20 - rt5 implicit-null + 2001:db8::6/128 30 - rt4 16061 + - rt5 16061 + +test# test isis topology 3 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt5 TE-IS 30 rt2 - rt4(4) +rt6 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 40 rt2 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 40 - rt2 16050 + 10.0.255.6/32 40 - rt2 16060 + +test# test isis topology 4 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt2 - rt6(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 40 - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + +test# test isis topology 5 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt2 - rt6(4) + rt3 - rt7(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) + rt3 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 40 - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + - rt3 16080 + +test# test isis topology 6 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) + rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - +10.0.255.4/32 IP TE 30 rt2 - rt4(4) + rt3 - +rt5 TE-IS 40 rt2 - rt6(4) + rt3 - +rt8 TE-IS 40 rt2 - rt6(4) + rt3 - +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + rt3 - +rt7 TE-IS 50 rt2 - rt5(4) + rt3 - rt8(4) +10.0.255.5/32 IP TE 50 rt2 - rt5(4) + rt3 - +10.0.255.8/32 IP TE 50 rt2 - rt8(4) + rt3 - +10.0.255.7/32 IP TE 60 rt2 - rt7(4) + rt3 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + - rt3 16040 + 10.0.255.5/32 50 - rt2 16050 + - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + - rt3 16060 + 10.0.255.7/32 60 - rt2 16070 + - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + - rt3 16080 + +test# test isis topology 7 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 20 rt4 - rt4(4) +rt7 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +rt2 TE-IS 30 rt4 - rt5(4) +rt6 TE-IS 30 rt4 - rt5(4) +rt8 TE-IS 30 rt4 - rt5(4) + rt7(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) +10.0.255.7/32 IP TE 30 rt4 - rt7(4) +rt10 TE-IS 40 rt4 - rt7(4) +rt3 TE-IS 40 rt4 - rt2(4) + rt6(4) +rt9 TE-IS 40 rt4 - rt8(4) +rt11 TE-IS 40 rt4 - rt8(4) +10.0.255.2/32 IP TE 40 rt4 - rt2(4) +10.0.255.6/32 IP TE 40 rt4 - rt6(4) +10.0.255.8/32 IP TE 40 rt4 - rt8(4) +rt12 TE-IS 50 rt4 - rt9(4) + rt11(4) +10.0.255.10/32 IP TE 50 rt4 - rt10(4) +10.0.255.3/32 IP TE 50 rt4 - rt3(4) +10.0.255.9/32 IP TE 50 rt4 - rt9(4) +10.0.255.11/32 IP TE 50 rt4 - rt11(4) +10.0.255.12/32 IP TE 60 rt4 - rt12(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 40 - rt4 16020 + 10.0.255.3/32 50 - rt4 16030 + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 30 - rt4 16050 + 10.0.255.6/32 40 - rt4 16060 + 10.0.255.7/32 30 - rt4 16070 + 10.0.255.8/32 40 - rt4 16080 + 10.0.255.9/32 50 - rt4 16090 + 10.0.255.10/32 50 - rt4 16100 + 10.0.255.11/32 50 - rt4 16110 + 10.0.255.12/32 60 - rt4 16120 + +test# test isis topology 8 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt3 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt2 - rt2(4) +rt7 TE-IS 20 rt4 - rt4(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +rt6 TE-IS 30 rt2 - rt3(4) + rt5(4) +rt8 TE-IS 30 rt2 - rt5(4) +rt10 TE-IS 30 rt4 - rt7(4) +10.0.255.3/32 IP TE 30 rt2 - rt3(4) +10.0.255.5/32 IP TE 30 rt2 - rt5(4) +10.0.255.7/32 IP TE 30 rt4 - rt7(4) +rt9 TE-IS 40 rt2 - rt8(4) +rt11 TE-IS 40 rt2 - rt8(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.8/32 IP TE 40 rt2 - rt8(4) +10.0.255.10/32 IP TE 40 rt4 - rt10(4) +rt12 TE-IS 50 rt2 - rt9(4) + rt11(4) +10.0.255.9/32 IP TE 50 rt2 - rt9(4) +10.0.255.11/32 IP TE 50 rt2 - rt11(4) +10.0.255.12/32 IP TE 60 rt2 - rt12(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 30 - rt2 16030 + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 30 - rt2 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 30 - rt4 16070 + 10.0.255.8/32 40 - rt2 16080 + 10.0.255.9/32 50 - rt2 16090 + 10.0.255.10/32 40 - rt4 16100 + 10.0.255.11/32 50 - rt2 16110 + 10.0.255.12/32 60 - rt2 16120 + +test# test isis topology 9 root rt1 spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt5 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +rt9 TE-IS 40 rt2 - rt5(4) +10.0.255.5/32 IP TE 40 rt2 - rt5(4) +rt6 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt7 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt8 TE-IS 50 rt2 - rt4(4) + rt9(4) +10.0.255.9/32 IP TE 50 rt2 - rt9(4) +10.0.255.6/32 IP TE 60 rt2 - rt6(4) +10.0.255.7/32 IP TE 60 rt2 - rt7(4) +10.0.255.8/32 IP TE 60 rt2 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 40 - rt2 16050 + 10.0.255.6/32 60 - rt2 16060 + 10.0.255.7/32 60 - rt2 16070 + 10.0.255.8/32 60 - rt2 16080 + 10.0.255.9/32 50 - rt2 16090 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt5 TE-IS 30 rt2 - rt4(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +rt9 TE-IS 40 rt2 - rt5(4) +2001:db8::5/128 IP6 internal 40 rt2 - rt5(4) +rt6 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt7 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt8 TE-IS 50 rt2 - rt4(4) + rt9(4) +2001:db8::9/128 IP6 internal 50 rt2 - rt9(4) +2001:db8::6/128 IP6 internal 60 rt2 - rt6(4) +2001:db8::7/128 IP6 internal 60 rt2 - rt7(4) +2001:db8::8/128 IP6 internal 60 rt2 - rt8(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::4/128 30 - rt2 16041 + 2001:db8::5/128 40 - rt2 16051 + 2001:db8::6/128 60 - rt2 16061 + 2001:db8::7/128 60 - rt2 16071 + 2001:db8::8/128 60 - rt2 16081 + 2001:db8::9/128 50 - rt2 16091 + +test# test isis topology 10 root rt1 spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt4 TE-IS 20 rt4 - rt1(4) +rt5 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt7 TE-IS 30 rt4 - rt4(4) +rt8 TE-IS 30 rt2 - rt5(4) +10.0.255.3/32 IP TE 30 rt3 - rt3(4) +10.0.255.4/32 IP TE 30 rt4 - rt4(4) +10.0.255.5/32 IP TE 30 rt2 - rt5(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) +10.0.255.7/32 IP TE 40 rt4 - rt7(4) +10.0.255.8/32 IP TE 40 rt2 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 30 - rt3 implicit-null + 10.0.255.4/32 30 - rt4 implicit-null + 10.0.255.5/32 30 - rt2 16050 + 10.0.255.6/32 40 - rt3 20060 + 10.0.255.7/32 40 - rt4 16070 + 10.0.255.8/32 40 - rt2 16080 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt4 TE-IS 20 rt4 - rt1(4) +rt5 TE-IS 20 rt2 - rt2(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt7 TE-IS 30 rt4 - rt4(4) +rt8 TE-IS 30 rt2 - rt5(4) +2001:db8::3/128 IP6 internal 30 rt3 - rt3(4) +2001:db8::4/128 IP6 internal 30 rt4 - rt4(4) +2001:db8::5/128 IP6 internal 30 rt2 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) +2001:db8::7/128 IP6 internal 40 rt4 - rt7(4) +2001:db8::8/128 IP6 internal 40 rt2 - rt8(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 30 - rt3 implicit-null + 2001:db8::4/128 30 - rt4 implicit-null + 2001:db8::5/128 30 - rt2 16051 + 2001:db8::6/128 40 - rt3 20061 + 2001:db8::7/128 40 - rt4 16071 + 2001:db8::8/128 40 - rt2 16081 + +test# test isis topology 11 root rt1 spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt2 pseudo_TE-IS 20 rt3 - rt3(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + rt3 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + - rt3 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt2 pseudo_TE-IS 20 rt3 - rt3(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt2 - rt6(4) + rt3 - + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::4/128 30 - rt2 16041 + 2001:db8::5/128 30 - rt3 16051 + 2001:db8::6/128 40 - rt2 16061 + - rt3 16061 + +test# test isis topology 12 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt2 - rt6(4) +rt9 TE-IS 40 rt3 - rt7(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +rt10 TE-IS 50 rt2 - rt8(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) +10.0.255.9/32 IP TE 50 rt3 - rt9(4) +10.0.255.10/32 IP TE 60 rt2 - rt10(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 40 - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + 10.0.255.9/32 50 - rt3 16090 + 10.0.255.10/32 60 - rt2 16100 + +test# test isis topology 13 root rt1 spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) + rt3 - rt3(4) +rt5 TE-IS 20 rt3 - rt3(4) +rt6 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) + rt6(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) + rt3 - +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + - rt3 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 30 - rt3 16060 + 10.0.255.7/32 40 - rt3 16070 + +test# +test# test isis topology 4 root rt1 reverse-spf ipv4-only +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt2 - rt6(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 40 - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + +test# test isis topology 11 root rt1 reverse-spf +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt2 pseudo_TE-IS 20 rt3 - rt3(4) +rt4 TE-IS 20 rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt4(4) + rt5(4) +10.0.255.4/32 IP TE 30 rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt3 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt2 pseudo_TE-IS 20 rt3 - rt3(4) +rt4 TE-IS 20 rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 20 rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt4(4) + rt5(4) +2001:db8::4/128 IP6 internal 30 rt4(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::5/128 30 - rt3 16051 + 2001:db8::6/128 40 - rt3 16061 + +test# +test# test isis topology 1 root rt1 lfa system-id rt2 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + rt3 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + - rt3 16060 + +Backup: +IS-IS L1 IPv4 routing table: + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt2 - rt6(4) + rt3 - + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::4/128 30 - rt2 16041 + 2001:db8::5/128 30 - rt3 16051 + 2001:db8::6/128 40 - rt2 16061 + - rt3 16061 + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 2 root rt4 lfa system-id rt1 pseudonode-id 1 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt1 TE-IS 10 rt1 - rt4(4) +rt5 TE-IS 10 rt5 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt5 - rt5(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +10.0.255.2/32 IP TE 35 rt1 - rt2(4) +rt3 TE-IS 40 rt1 - rt1(4) +10.0.255.3/32 IP TE 50 rt1 - rt3(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 35 - rt1 16020 + 10.0.255.3/32 50 - rt1 16030 + 10.0.255.4/32 0 - - - + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.2/32 50 - rt2 implicit-null + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt4 +2001:db8::4/128 IP6 internal 0 rt4(4) +rt1 TE-IS 10 rt1 - rt4(4) +rt5 TE-IS 10 rt5 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt5 - rt5(4) +2001:db8::1/128 IP6 internal 20 rt1 - rt1(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +2001:db8::2/128 IP6 internal 35 rt1 - rt2(4) +rt3 TE-IS 40 rt1 - rt1(4) +2001:db8::3/128 IP6 internal 50 rt1 - rt3(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 20 - rt1 implicit-null + 2001:db8::2/128 35 - rt1 16021 + 2001:db8::3/128 50 - rt1 16031 + 2001:db8::4/128 0 - - - + 2001:db8::5/128 20 - rt5 implicit-null + 2001:db8::6/128 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::2/128 50 - rt2 implicit-null + +test# test isis topology 2 root rt4 lfa system-id rt6 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt1 TE-IS 10 rt1 - rt4(4) +rt5 TE-IS 10 rt5 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt5 - rt5(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +10.0.255.2/32 IP TE 35 rt1 - rt2(4) +rt3 TE-IS 40 rt1 - rt1(4) +10.0.255.3/32 IP TE 50 rt1 - rt3(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 35 - rt1 16020 + 10.0.255.3/32 50 - rt1 16030 + 10.0.255.4/32 0 - - - + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.6/32 30 - rt5 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt4 +2001:db8::4/128 IP6 internal 0 rt4(4) +rt1 TE-IS 10 rt1 - rt4(4) +rt5 TE-IS 10 rt5 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt5 - rt5(4) +2001:db8::1/128 IP6 internal 20 rt1 - rt1(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +2001:db8::2/128 IP6 internal 35 rt1 - rt2(4) +rt3 TE-IS 40 rt1 - rt1(4) +2001:db8::3/128 IP6 internal 50 rt1 - rt3(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 20 - rt1 implicit-null + 2001:db8::2/128 35 - rt1 16021 + 2001:db8::3/128 50 - rt1 16031 + 2001:db8::4/128 0 - - - + 2001:db8::5/128 20 - rt5 implicit-null + 2001:db8::6/128 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::6/128 30 - rt5 16061 + +test# test isis topology 3 root rt1 lfa system-id rt2 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt5 TE-IS 30 rt2 - rt4(4) +rt6 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 40 rt2 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 40 - rt2 16050 + 10.0.255.6/32 40 - rt2 16060 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.2/32 30 - rt3 16020 + 10.0.255.4/32 40 - rt3 16040 + 10.0.255.5/32 50 - rt3 16050 + 10.0.255.6/32 50 - rt3 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 3 root rt1 lfa system-id rt3 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt5 TE-IS 30 rt2 - rt4(4) +rt6 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 40 rt2 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 40 - rt2 16050 + 10.0.255.6/32 40 - rt2 16060 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.3/32 30 - rt2 16030 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 7 root rt1 lfa system-id rt4 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 20 rt4 - rt4(4) +rt7 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +rt2 TE-IS 30 rt4 - rt5(4) +rt6 TE-IS 30 rt4 - rt5(4) +rt8 TE-IS 30 rt4 - rt5(4) + rt7(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) +10.0.255.7/32 IP TE 30 rt4 - rt7(4) +rt10 TE-IS 40 rt4 - rt7(4) +rt3 TE-IS 40 rt4 - rt2(4) + rt6(4) +rt9 TE-IS 40 rt4 - rt8(4) +rt11 TE-IS 40 rt4 - rt8(4) +10.0.255.2/32 IP TE 40 rt4 - rt2(4) +10.0.255.6/32 IP TE 40 rt4 - rt6(4) +10.0.255.8/32 IP TE 40 rt4 - rt8(4) +rt12 TE-IS 50 rt4 - rt9(4) + rt11(4) +10.0.255.10/32 IP TE 50 rt4 - rt10(4) +10.0.255.3/32 IP TE 50 rt4 - rt3(4) +10.0.255.9/32 IP TE 50 rt4 - rt9(4) +10.0.255.11/32 IP TE 50 rt4 - rt11(4) +10.0.255.12/32 IP TE 60 rt4 - rt12(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 40 - rt4 16020 + 10.0.255.3/32 50 - rt4 16030 + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 30 - rt4 16050 + 10.0.255.6/32 40 - rt4 16060 + 10.0.255.7/32 30 - rt4 16070 + 10.0.255.8/32 40 - rt4 16080 + 10.0.255.9/32 50 - rt4 16090 + 10.0.255.10/32 50 - rt4 16100 + 10.0.255.11/32 50 - rt4 16110 + 10.0.255.12/32 60 - rt4 16120 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.2/32 50 - rt2 implicit-null + 10.0.255.3/32 60 - rt2 16030 + 10.0.255.4/32 70 - rt2 16040 + 10.0.255.5/32 60 - rt2 16050 + 10.0.255.6/32 70 - rt2 16060 + 10.0.255.7/32 80 - rt2 16070 + 10.0.255.8/32 70 - rt2 16080 + 10.0.255.9/32 80 - rt2 16090 + 10.0.255.10/32 90 - rt2 16100 + 10.0.255.11/32 80 - rt2 16110 + 10.0.255.12/32 90 - rt2 16120 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +rt4 TE-IS 10 rt4 - rt1(4) +rt2 TE-IS 40 rt2 - rt1(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 7 root rt7 lfa system-id rt8 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt7 +10.0.255.7/32 IP internal 0 rt7(4) +rt4 TE-IS 10 rt4 - rt7(4) +rt8 TE-IS 10 rt8 - rt7(4) +rt10 TE-IS 20 rt10 - rt7(4) +rt1 TE-IS 20 rt4 - rt4(4) +rt5 TE-IS 20 rt4 - rt4(4) + rt8 - rt8(4) +rt9 TE-IS 20 rt8 - rt8(4) +rt11 TE-IS 20 rt8 - rt8(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.8/32 IP TE 20 rt8 - rt8(4) +rt2 TE-IS 30 rt4 - rt5(4) + rt8 - +rt6 TE-IS 30 rt4 - rt5(4) + rt8 - +rt12 TE-IS 30 rt8 - rt9(4) + rt11(4) +10.0.255.10/32 IP TE 30 rt10 - rt10(4) +10.0.255.1/32 IP TE 30 rt4 - rt1(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) + rt8 - +10.0.255.9/32 IP TE 30 rt8 - rt9(4) +10.0.255.11/32 IP TE 30 rt8 - rt11(4) +rt3 TE-IS 40 rt4 - rt2(4) + rt8 - rt6(4) +10.0.255.2/32 IP TE 40 rt4 - rt2(4) + rt8 - +10.0.255.6/32 IP TE 40 rt4 - rt6(4) + rt8 - +10.0.255.12/32 IP TE 40 rt8 - rt12(4) +10.0.255.3/32 IP TE 50 rt4 - rt3(4) + rt8 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 30 - rt4 16010 + 10.0.255.2/32 40 - rt4 16020 + - rt8 16020 + 10.0.255.3/32 50 - rt4 16030 + - rt8 16030 + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 30 - rt4 16050 + - rt8 16050 + 10.0.255.6/32 40 - rt4 16060 + - rt8 16060 + 10.0.255.7/32 0 - - - + 10.0.255.8/32 20 - rt8 implicit-null + 10.0.255.9/32 30 - rt8 16090 + 10.0.255.10/32 30 - rt10 implicit-null + 10.0.255.11/32 30 - rt8 16110 + 10.0.255.12/32 40 - rt8 16120 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------ + 10.0.255.8/32 50 - rt10 16080 + 10.0.255.9/32 60 - rt10 16090 + 10.0.255.11/32 40 - rt10 16110 + 10.0.255.12/32 50 - rt10 16120 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt7 +rt4 TE-IS 10 rt4 - rt7(4) +rt8 TE-IS 10 rt8 - rt7(4) +rt10 TE-IS 20 rt10 - rt7(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 7 root rt8 lfa system-id rt11 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt8 +10.0.255.8/32 IP internal 0 rt8(4) +rt5 TE-IS 10 rt5 - rt8(4) +rt7 TE-IS 10 rt7 - rt8(4) +rt9 TE-IS 10 rt9 - rt8(4) +rt11 TE-IS 10 rt11 - rt8(4) +rt2 TE-IS 20 rt5 - rt5(4) +rt4 TE-IS 20 rt5 - rt5(4) + rt7 - rt7(4) +rt6 TE-IS 20 rt5 - rt5(4) +rt12 TE-IS 20 rt9 - rt9(4) + rt11 - rt11(4) +rt10 TE-IS 20 rt11 - rt11(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.7/32 IP TE 20 rt7 - rt7(4) +10.0.255.9/32 IP TE 20 rt9 - rt9(4) +10.0.255.11/32 IP TE 20 rt11 - rt11(4) +rt3 TE-IS 30 rt5 - rt2(4) + rt6(4) +rt1 TE-IS 30 rt5 - rt4(4) + rt7 - +10.0.255.2/32 IP TE 30 rt5 - rt2(4) +10.0.255.4/32 IP TE 30 rt5 - rt4(4) + rt7 - +10.0.255.6/32 IP TE 30 rt5 - rt6(4) +10.0.255.12/32 IP TE 30 rt9 - rt12(4) + rt11 - +10.0.255.10/32 IP TE 30 rt11 - rt10(4) +10.0.255.3/32 IP TE 40 rt5 - rt3(4) +10.0.255.1/32 IP TE 40 rt5 - rt1(4) + rt7 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 40 - rt5 16010 + - rt7 16010 + 10.0.255.2/32 30 - rt5 16020 + 10.0.255.3/32 40 - rt5 16030 + 10.0.255.4/32 30 - rt5 16040 + - rt7 16040 + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 30 - rt5 16060 + 10.0.255.7/32 20 - rt7 implicit-null + 10.0.255.8/32 0 - - - + 10.0.255.9/32 20 - rt9 implicit-null + 10.0.255.10/32 30 - rt11 16100 + 10.0.255.11/32 20 - rt11 implicit-null + 10.0.255.12/32 30 - rt9 16120 + - rt11 16120 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------ + 10.0.255.10/32 40 - rt7 16100 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt8 +rt5 TE-IS 10 rt5 - rt8(4) +rt7 TE-IS 10 rt7 - rt8(4) +rt9 TE-IS 10 rt9 - rt8(4) +rt11 TE-IS 10 rt11 - rt8(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 9 root rt3 lfa system-id rt1 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt3 +10.0.255.3/32 IP internal 0 rt3(4) +rt1 TE-IS 10 rt1 - rt3(4) +rt2 TE-IS 20 rt1 - rt1(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +rt4 TE-IS 30 rt1 - rt2(4) +10.0.255.2/32 IP TE 30 rt1 - rt2(4) +rt5 TE-IS 40 rt1 - rt4(4) +10.0.255.4/32 IP TE 40 rt1 - rt4(4) +rt9 TE-IS 50 rt1 - rt5(4) +10.0.255.5/32 IP TE 50 rt1 - rt5(4) +rt6 TE-IS 60 rt1 - rt4(4) + rt9(4) +rt7 TE-IS 60 rt1 - rt4(4) + rt9(4) +rt8 TE-IS 60 rt1 - rt4(4) + rt9(4) +10.0.255.9/32 IP TE 60 rt1 - rt9(4) +10.0.255.6/32 IP TE 70 rt1 - rt6(4) +10.0.255.7/32 IP TE 70 rt1 - rt7(4) +10.0.255.8/32 IP TE 70 rt1 - rt8(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 30 - rt1 16020 + 10.0.255.3/32 0 - - - + 10.0.255.4/32 40 - rt1 16040 + 10.0.255.5/32 50 - rt1 16050 + 10.0.255.6/32 70 - rt1 16060 + 10.0.255.7/32 70 - rt1 16070 + 10.0.255.8/32 70 - rt1 16080 + 10.0.255.9/32 60 - rt1 16090 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 130 - rt4 16010 + 10.0.255.2/32 120 - rt4 16020 + 10.0.255.4/32 110 - rt4 implicit-null + 10.0.255.5/32 120 - rt4 16050 + 10.0.255.6/32 140 - rt4 16060 + 10.0.255.7/32 140 - rt4 16070 + 10.0.255.8/32 140 - rt4 16080 + 10.0.255.9/32 130 - rt4 16090 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt3 +2001:db8::3/128 IP6 internal 0 rt3(4) +rt1 TE-IS 10 rt1 - rt3(4) +rt2 TE-IS 20 rt1 - rt1(4) +2001:db8::1/128 IP6 internal 20 rt1 - rt1(4) +rt4 TE-IS 30 rt1 - rt2(4) +2001:db8::2/128 IP6 internal 30 rt1 - rt2(4) +rt5 TE-IS 40 rt1 - rt4(4) +2001:db8::4/128 IP6 internal 40 rt1 - rt4(4) +rt9 TE-IS 50 rt1 - rt5(4) +2001:db8::5/128 IP6 internal 50 rt1 - rt5(4) +rt6 TE-IS 60 rt1 - rt4(4) + rt9(4) +rt7 TE-IS 60 rt1 - rt4(4) + rt9(4) +rt8 TE-IS 60 rt1 - rt4(4) + rt9(4) +2001:db8::9/128 IP6 internal 60 rt1 - rt9(4) +2001:db8::6/128 IP6 internal 70 rt1 - rt6(4) +2001:db8::7/128 IP6 internal 70 rt1 - rt7(4) +2001:db8::8/128 IP6 internal 70 rt1 - rt8(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 20 - rt1 implicit-null + 2001:db8::2/128 30 - rt1 16021 + 2001:db8::3/128 0 - - - + 2001:db8::4/128 40 - rt1 16041 + 2001:db8::5/128 50 - rt1 16051 + 2001:db8::6/128 70 - rt1 16061 + 2001:db8::7/128 70 - rt1 16071 + 2001:db8::8/128 70 - rt1 16081 + 2001:db8::9/128 60 - rt1 16091 + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 130 - rt4 16011 + 2001:db8::2/128 120 - rt4 16021 + 2001:db8::4/128 110 - rt4 implicit-null + 2001:db8::5/128 120 - rt4 16051 + 2001:db8::6/128 140 - rt4 16061 + 2001:db8::7/128 140 - rt4 16071 + 2001:db8::8/128 140 - rt4 16081 + 2001:db8::9/128 130 - rt4 16091 + +test# test isis topology 10 root rt8 lfa system-id rt5 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt8 +10.0.255.8/32 IP internal 0 rt8(4) +rt5 TE-IS 10 rt5 - rt8(4) +rt2 TE-IS 20 rt5 - rt5(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +rt1 TE-IS 30 rt5 - rt2(4) +10.0.255.2/32 IP TE 30 rt5 - rt2(4) +10.0.255.1/32 IP TE 40 rt5 - rt1(4) +rt6 TE-IS 50 rt6 - rt8(4) +rt7 TE-IS 50 rt7 - rt8(4) +rt3 TE-IS 50 rt5 - rt1(4) +rt4 TE-IS 50 rt5 - rt1(4) +10.0.255.6/32 IP TE 60 rt6 - rt6(4) +10.0.255.7/32 IP TE 60 rt7 - rt7(4) +10.0.255.3/32 IP TE 60 rt5 - rt3(4) +10.0.255.4/32 IP TE 60 rt5 - rt4(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 40 - rt5 16010 + 10.0.255.2/32 30 - rt5 16020 + 10.0.255.3/32 60 - rt5 16030 + 10.0.255.4/32 60 - rt5 16040 + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 60 - rt6 implicit-null + 10.0.255.7/32 60 - rt7 implicit-null + 10.0.255.8/32 0 - - - + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 90 - rt6 16010 + - rt7 16010 + 10.0.255.2/32 100 - rt6 16020 + - rt7 16020 + 10.0.255.3/32 70 - rt6 16030 + - rt7 16030 + 10.0.255.4/32 70 - rt6 16040 + - rt7 16040 + 10.0.255.5/32 110 - rt6 16050 + - rt7 16050 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt8 +2001:db8::8/128 IP6 internal 0 rt8(4) +rt5 TE-IS 10 rt5 - rt8(4) +rt2 TE-IS 20 rt5 - rt5(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +rt1 TE-IS 30 rt5 - rt2(4) +2001:db8::2/128 IP6 internal 30 rt5 - rt2(4) +2001:db8::1/128 IP6 internal 40 rt5 - rt1(4) +rt6 TE-IS 50 rt6 - rt8(4) +rt7 TE-IS 50 rt7 - rt8(4) +rt3 TE-IS 50 rt5 - rt1(4) +rt4 TE-IS 50 rt5 - rt1(4) +2001:db8::6/128 IP6 internal 60 rt6 - rt6(4) +2001:db8::7/128 IP6 internal 60 rt7 - rt7(4) +2001:db8::3/128 IP6 internal 60 rt5 - rt3(4) +2001:db8::4/128 IP6 internal 60 rt5 - rt4(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 40 - rt5 16011 + 2001:db8::2/128 30 - rt5 16021 + 2001:db8::3/128 60 - rt5 16031 + 2001:db8::4/128 60 - rt5 16041 + 2001:db8::5/128 20 - rt5 implicit-null + 2001:db8::6/128 60 - rt6 implicit-null + 2001:db8::7/128 60 - rt7 implicit-null + 2001:db8::8/128 0 - - - + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::1/128 90 - rt6 16011 + - rt7 16011 + 2001:db8::2/128 100 - rt6 16021 + - rt7 16021 + 2001:db8::3/128 70 - rt6 16031 + - rt7 16031 + 2001:db8::4/128 70 - rt6 16041 + - rt7 16041 + 2001:db8::5/128 110 - rt6 16051 + - rt7 16051 + +test# test isis topology 11 root rt3 lfa system-id rt5 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt3 +10.0.255.3/32 IP internal 0 rt3(4) +rt1 TE-IS 10 rt1 - rt3(4) +rt2 TE-IS 10 rt2 - rt3(4) +rt5 TE-IS 10 rt5 - rt3(4) +rt2 pseudo_TE-IS 20 rt1 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) + rt5 - rt5(4) +rt6 TE-IS 20 rt5 - rt5(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) + rt5 - +10.0.255.6/32 IP TE 30 rt5 - rt6(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 0 - - - + 10.0.255.4/32 30 - rt2 16040 + - rt5 16040 + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 30 - rt5 16060 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.5/32 40 - rt2 16050 + 10.0.255.6/32 40 - rt2 16060 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt3 +2001:db8::3/128 IP6 internal 0 rt3(4) +rt1 TE-IS 10 rt1 - rt3(4) +rt2 TE-IS 10 rt2 - rt3(4) +rt5 TE-IS 10 rt5 - rt3(4) +rt2 pseudo_TE-IS 20 rt1 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) + rt5 - rt5(4) +rt6 TE-IS 20 rt5 - rt5(4) +2001:db8::1/128 IP6 internal 20 rt1 - rt1(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) + rt5 - +2001:db8::6/128 IP6 internal 30 rt5 - rt6(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 20 - rt1 implicit-null + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 0 - - - + 2001:db8::4/128 30 - rt2 16041 + - rt5 16041 + 2001:db8::5/128 20 - rt5 implicit-null + 2001:db8::6/128 30 - rt5 16061 + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::5/128 40 - rt2 16051 + 2001:db8::6/128 40 - rt2 16061 + +test# test isis topology 13 root rt4 lfa system-id rt3 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt2 TE-IS 10 rt2 - rt4(4) +rt3 TE-IS 10 rt3 - rt4(4) +rt1 TE-IS 20 rt2 - rt2(4) + rt3 - rt3(4) +rt5 TE-IS 20 rt3 - rt3(4) +rt6 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) + rt6(4) +10.0.255.1/32 IP TE 30 rt2 - rt1(4) + rt3 - +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 30 - rt2 16010 + - rt3 16010 + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 0 - - - + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 30 - rt3 16060 + 10.0.255.7/32 40 - rt3 16070 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.3/32 120 - rt5 16030 + 10.0.255.5/32 110 - rt5 implicit-null + 10.0.255.6/32 130 - rt5 16060 + 10.0.255.7/32 120 - rt5 16070 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt4 +rt2 TE-IS 10 rt2 - rt4(4) +rt3 TE-IS 10 rt3 - rt4(4) +rt5 TE-IS 100 rt5 - rt4(4) + +Main: +IS-IS L1 IPv6 routing table: + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 14 root rt1 lfa system-id rt1 pseudonode-id 1 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt1 +rt5 TE-IS 20 rt4 - rt4(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 - + 10.0.255.3/32 20 - rt3 - + 10.0.255.4/32 20 - rt4 - + 10.0.255.5/32 30 - rt4 - + +Backup: +IS-IS L1 IPv4 routing table: + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt1 +rt5 TE-IS 20 rt4 - rt4(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::5/128 IP6 internal 30 rt4 - rt5(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 - + 2001:db8::3/128 20 - rt3 - + 2001:db8::4/128 20 - rt4 - + 2001:db8::5/128 30 - rt4 - + +Backup: +IS-IS L1 IPv6 routing table: + +test# test isis topology 14 root rt1 lfa system-id rt2 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt1 +rt5 TE-IS 20 rt4 - rt4(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 - + 10.0.255.3/32 20 - rt3 - + 10.0.255.4/32 20 - rt4 - + 10.0.255.5/32 30 - rt4 - + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.2/32 30 - rt3 - + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt1 +rt5 TE-IS 20 rt4 - rt4(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::5/128 IP6 internal 30 rt4 - rt5(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 - + 2001:db8::3/128 20 - rt3 - + 2001:db8::4/128 20 - rt4 - + 2001:db8::5/128 30 - rt4 - + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::2/128 30 - rt3 - + +test# test isis topology 14 root rt5 lfa system-id rt4 +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt1 pseudo_TE-IS 20 rt4 - rt4(4) +rt1 TE-IS 20 rt4 - rt1(2) +rt3 TE-IS 20 rt4 - rt1(2) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +rt2 TE-IS 30 rt4 - rt1(4) + rt3(4) +10.0.255.1/32 IP TE 30 rt4 - rt1(4) +10.0.255.3/32 IP TE 30 rt4 - rt3(4) +10.0.255.2/32 IP TE 40 rt4 - rt2(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 30 - rt4 - + 10.0.255.2/32 40 - rt4 - + 10.0.255.3/32 30 - rt4 - + 10.0.255.4/32 20 - rt4 - + 10.0.255.5/32 0 - - - + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 70 - rt3 - + 10.0.255.2/32 70 - rt3 - + 10.0.255.3/32 60 - rt3 - + 10.0.255.4/32 70 - rt3 - + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt5 +2001:db8::5/128 IP6 internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt1 pseudo_TE-IS 20 rt4 - rt4(4) +rt1 TE-IS 20 rt4 - rt1(2) +rt3 TE-IS 20 rt4 - rt1(2) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +rt2 TE-IS 30 rt4 - rt1(4) + rt3(4) +2001:db8::1/128 IP6 internal 30 rt4 - rt1(4) +2001:db8::3/128 IP6 internal 30 rt4 - rt3(4) +2001:db8::2/128 IP6 internal 40 rt4 - rt2(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::1/128 30 - rt4 - + 2001:db8::2/128 40 - rt4 - + 2001:db8::3/128 30 - rt4 - + 2001:db8::4/128 20 - rt4 - + 2001:db8::5/128 0 - - - + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::1/128 70 - rt3 - + 2001:db8::2/128 70 - rt3 - + 2001:db8::3/128 60 - rt3 - + 2001:db8::4/128 70 - rt3 - + +test# +test# test isis topology 1 root rt1 remote-lfa system-id rt2 +P-space (self): + rt3 + rt5 + +P-space (rt3): + rt3 + rt5 + rt6 + +Q-space: + rt2 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt5(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt4 TE-IS 40 rt3 - rt6(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) +rt2 TE-IS 50 rt3 - rt4(4) +10.0.255.4/32 IP TE 50 rt3 - rt4(4) +10.0.255.2/32 IP TE 60 rt3 - rt2(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) + rt3 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + - rt3 16060 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.2/32 60 - rt3 50600/16020 + 10.0.255.4/32 50 - rt3 50600/16040 + +P-space (self): + rt3 + rt5 + +P-space (rt3): + rt3 + rt5 + rt6 + +Q-space: + rt2 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt5(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +rt4 TE-IS 40 rt3 - rt6(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) +rt2 TE-IS 50 rt3 - rt4(4) +2001:db8::4/128 IP6 internal 50 rt3 - rt4(4) +2001:db8::2/128 IP6 internal 60 rt3 - rt2(4) + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) + rt3 - rt5(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt2 - rt6(4) + rt3 - + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 0 - - - + 2001:db8::2/128 20 - rt2 implicit-null + 2001:db8::3/128 20 - rt3 implicit-null + 2001:db8::4/128 30 - rt2 16041 + 2001:db8::5/128 30 - rt3 16051 + 2001:db8::6/128 40 - rt2 16061 + - rt3 16061 + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 2001:db8::2/128 60 - rt3 50600/16021 + 2001:db8::4/128 50 - rt3 50600/16041 + +test# test isis topology 2 root rt5 remote-lfa system-id rt1 pseudonode-id 1 +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + +P-space (rt6): + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt1 pseudo_TE-IS 30 rt6 - rt4(4) +rt1 TE-IS 30 rt6 - rt1(2) +10.0.255.4/32 IP TE 30 rt6 - rt4(4) +rt3 TE-IS 40 rt3 - rt5(4) +10.0.255.1/32 IP TE 40 rt6 - rt1(4) +rt2 TE-IS 45 rt6 - rt1(4) +10.0.255.3/32 IP TE 50 rt3 - rt3(4) +10.0.255.2/32 IP TE 55 rt6 - rt2(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt1 TE-IS 10 rt1 - rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt4 - rt4(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +10.0.255.2/32 IP TE 35 rt1 - rt2(4) +rt3 TE-IS 40 rt3 - rt5(4) + rt1 - rt1(4) +10.0.255.3/32 IP TE 50 rt3 - rt3(4) + rt1 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 35 - rt1 16020 + 10.0.255.3/32 50 - rt3 implicit-null + - rt1 implicit-null + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 0 - - - + 10.0.255.6/32 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.1/32 40 - rt6 50400/16010 + 10.0.255.2/32 55 - rt6 50400/16020 + 10.0.255.4/32 30 - rt6 50400/16040 + +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + +P-space (rt6): + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt5 +2001:db8::5/128 IP6 internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +rt1 pseudo_TE-IS 30 rt6 - rt4(4) +rt1 TE-IS 30 rt6 - rt1(2) +2001:db8::4/128 IP6 internal 30 rt6 - rt4(4) +rt3 TE-IS 40 rt3 - rt5(4) +2001:db8::1/128 IP6 internal 40 rt6 - rt1(4) +rt2 TE-IS 45 rt6 - rt1(4) +2001:db8::3/128 IP6 internal 50 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 55 rt6 - rt2(4) + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt5 +2001:db8::5/128 IP6 internal 0 rt5(4) +rt1 TE-IS 10 rt1 - rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt1 pseudo_TE-IS 20 rt1 - rt1(4) + rt4 - rt4(4) +2001:db8::1/128 IP6 internal 20 rt1 - rt1(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +rt2 TE-IS 25 rt1 - rt1(4) +2001:db8::2/128 IP6 internal 35 rt1 - rt2(4) +rt3 TE-IS 40 rt3 - rt5(4) + rt1 - rt1(4) +2001:db8::3/128 IP6 internal 50 rt3 - rt3(4) + rt1 - + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 20 - rt1 implicit-null + 2001:db8::2/128 35 - rt1 16021 + 2001:db8::3/128 50 - rt3 implicit-null + - rt1 implicit-null + 2001:db8::4/128 20 - rt4 implicit-null + 2001:db8::5/128 0 - - - + 2001:db8::6/128 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 2001:db8::1/128 40 - rt6 50400/16011 + 2001:db8::2/128 55 - rt6 50400/16021 + 2001:db8::4/128 30 - rt6 50400/16041 + +test# test isis topology 3 root rt5 remote-lfa system-id rt4 ipv4-only +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + rt6 + +P-space (rt6): + rt1 + rt2 + rt3 + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt3 - rt5(4) +rt2 TE-IS 30 rt6 - rt4(4) +10.0.255.4/32 IP TE 30 rt6 - rt4(4) +rt1 TE-IS 40 rt3 - rt3(4) + rt6 - rt2(4) +10.0.255.3/32 IP TE 40 rt3 - rt3(4) +10.0.255.2/32 IP TE 40 rt6 - rt2(4) +10.0.255.1/32 IP TE 50 rt3 - rt1(4) + rt6 - + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt2 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt3 - rt5(4) + rt4 - rt2(4) +rt1 TE-IS 30 rt4 - rt2(4) +10.0.255.2/32 IP TE 30 rt4 - rt2(4) +10.0.255.3/32 IP TE 40 rt3 - rt3(4) + rt4 - +10.0.255.1/32 IP TE 40 rt4 - rt1(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 40 - rt4 16010 + 10.0.255.2/32 30 - rt4 16020 + 10.0.255.3/32 40 - rt3 implicit-null + - rt4 implicit-null + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 0 - - - + 10.0.255.6/32 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 50 - rt3 16010 + - rt6 16010 + 10.0.255.2/32 40 - rt3 16020 + - rt6 16020 + 10.0.255.4/32 30 - rt3 16040 + - rt6 16040 + +test# test isis topology 3 root rt5 remote-lfa system-id rt3 ipv4-only +P-space (self): + rt1 + rt2 + rt4 + rt6 + +P-space (rt4): + rt1 + rt2 + rt3 + rt4 + rt6 + +P-space (rt6): + rt1 + rt2 + rt3 + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt2 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt1 TE-IS 30 rt4 - rt2(4) +rt3 TE-IS 30 rt4 - rt2(4) +10.0.255.2/32 IP TE 30 rt4 - rt2(4) +10.0.255.1/32 IP TE 40 rt4 - rt1(4) +10.0.255.3/32 IP TE 40 rt4 - rt3(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt2 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt3 - rt5(4) + rt4 - rt2(4) +rt1 TE-IS 30 rt4 - rt2(4) +10.0.255.2/32 IP TE 30 rt4 - rt2(4) +10.0.255.3/32 IP TE 40 rt3 - rt3(4) + rt4 - +10.0.255.1/32 IP TE 40 rt4 - rt1(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 40 - rt4 16010 + 10.0.255.2/32 30 - rt4 16020 + 10.0.255.3/32 40 - rt3 implicit-null + - rt4 implicit-null + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 0 - - - + 10.0.255.6/32 20 - rt6 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + +test# test isis topology 5 root rt1 remote-lfa system-id rt2 ipv4-only +P-space (self): + rt3 + rt5 + rt7 + +P-space (rt3): + rt3 + rt5 + rt7 + rt8 + +Q-space: + rt2 + rt4 + rt6 + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt3 - rt7(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +rt6 TE-IS 50 rt3 - rt8(4) +10.0.255.8/32 IP TE 50 rt3 - rt8(4) +rt4 TE-IS 60 rt3 - rt6(4) +10.0.255.6/32 IP TE 60 rt3 - rt6(4) +rt2 TE-IS 70 rt3 - rt4(4) +10.0.255.4/32 IP TE 70 rt3 - rt4(4) +10.0.255.2/32 IP TE 80 rt3 - rt2(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt2 - rt4(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt2 - rt6(4) + rt3 - rt7(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) + rt3 - + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 40 - rt2 16060 + 10.0.255.7/32 40 - rt3 16070 + 10.0.255.8/32 50 - rt2 16080 + - rt3 16080 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.2/32 80 - rt3 50800/16020 + 10.0.255.4/32 70 - rt3 50800/16040 + 10.0.255.6/32 60 - rt3 50800/16060 + +test# test isis topology 6 root rt4 remote-lfa system-id rt3 ipv4-only +P-space (self): + rt2 + rt5 + rt6 + rt7 + rt8 + +P-space (rt2): + rt1 + rt2 + +P-space (rt6): + rt5 + rt6 + rt7 + rt8 + +Q-space: + rt1 + rt3 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt2 TE-IS 10 rt2 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt6 - rt6(4) +rt8 TE-IS 20 rt6 - rt6(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt2 - rt1(4) +rt7 TE-IS 30 rt6 - rt5(4) + rt8(4) +10.0.255.1/32 IP TE 30 rt2 - rt1(4) +10.0.255.5/32 IP TE 30 rt6 - rt5(4) +10.0.255.8/32 IP TE 30 rt6 - rt8(4) +10.0.255.3/32 IP TE 40 rt2 - rt3(4) +10.0.255.7/32 IP TE 40 rt6 - rt7(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt2 TE-IS 10 rt2 - rt4(4) +rt3 TE-IS 10 rt3 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 TE-IS 20 rt2 - rt2(4) + rt3 - rt3(4) +rt5 TE-IS 20 rt6 - rt6(4) +rt8 TE-IS 20 rt6 - rt6(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt7 TE-IS 30 rt6 - rt5(4) + rt8(4) +10.0.255.1/32 IP TE 30 rt2 - rt1(4) + rt3 - +10.0.255.5/32 IP TE 30 rt6 - rt5(4) +10.0.255.8/32 IP TE 30 rt6 - rt8(4) +10.0.255.7/32 IP TE 40 rt6 - rt7(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 30 - rt2 16010 + - rt3 16010 + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 0 - - - + 10.0.255.5/32 30 - rt6 16050 + 10.0.255.6/32 20 - rt6 implicit-null + 10.0.255.7/32 40 - rt6 16070 + 10.0.255.8/32 30 - rt6 16080 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.3/32 40 - rt2 50100/16030 + +test# test isis topology 7 root rt11 remote-lfa system-id rt8 ipv4-only +P-space (self): + rt10 + rt12 + +P-space (rt10): + rt1 + rt4 + rt7 + rt10 + +P-space (rt12): + rt9 + rt12 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt11 +10.0.255.11/32 IP internal 0 rt11(4) +rt10 TE-IS 10 rt10 - rt11(4) +rt12 TE-IS 10 rt12 - rt11(4) +rt9 TE-IS 20 rt12 - rt12(4) +10.0.255.10/32 IP TE 20 rt10 - rt10(4) +10.0.255.12/32 IP TE 20 rt12 - rt12(4) +rt7 TE-IS 30 rt10 - rt10(4) +rt8 TE-IS 30 rt12 - rt9(4) +10.0.255.9/32 IP TE 30 rt12 - rt9(4) +rt4 TE-IS 40 rt10 - rt7(4) +rt5 TE-IS 40 rt12 - rt8(4) +10.0.255.7/32 IP TE 40 rt10 - rt7(4) +10.0.255.8/32 IP TE 40 rt12 - rt8(4) +rt6 TE-IS 50 rt12 - rt9(4) + rt5(4) +rt1 TE-IS 50 rt10 - rt4(4) +rt2 TE-IS 50 rt12 - rt5(4) +10.0.255.4/32 IP TE 50 rt10 - rt4(4) +10.0.255.5/32 IP TE 50 rt12 - rt5(4) +rt3 TE-IS 60 rt12 - rt6(4) + rt2(4) +10.0.255.6/32 IP TE 60 rt12 - rt6(4) +10.0.255.1/32 IP TE 60 rt10 - rt1(4) +10.0.255.2/32 IP TE 60 rt12 - rt2(4) +10.0.255.3/32 IP TE 70 rt12 - rt3(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt11 +10.0.255.11/32 IP internal 0 rt11(4) +rt8 TE-IS 10 rt8 - rt11(4) +rt10 TE-IS 10 rt10 - rt11(4) +rt12 TE-IS 10 rt12 - rt11(4) +rt5 TE-IS 20 rt8 - rt8(4) +rt7 TE-IS 20 rt8 - rt8(4) +rt9 TE-IS 20 rt8 - rt8(4) + rt12 - rt12(4) +10.0.255.8/32 IP TE 20 rt8 - rt8(4) +10.0.255.10/32 IP TE 20 rt10 - rt10(4) +10.0.255.12/32 IP TE 20 rt12 - rt12(4) +rt2 TE-IS 30 rt8 - rt5(4) +rt4 TE-IS 30 rt8 - rt5(4) + rt7(4) +rt6 TE-IS 30 rt8 - rt5(4) +10.0.255.5/32 IP TE 30 rt8 - rt5(4) +10.0.255.7/32 IP TE 30 rt8 - rt7(4) +10.0.255.9/32 IP TE 30 rt8 - rt9(4) + rt12 - +rt3 TE-IS 40 rt8 - rt2(4) + rt6(4) +rt1 TE-IS 40 rt8 - rt4(4) +10.0.255.2/32 IP TE 40 rt8 - rt2(4) +10.0.255.4/32 IP TE 40 rt8 - rt4(4) +10.0.255.6/32 IP TE 40 rt8 - rt6(4) +10.0.255.3/32 IP TE 50 rt8 - rt3(4) +10.0.255.1/32 IP TE 50 rt8 - rt1(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 50 - rt8 16010 + 10.0.255.2/32 40 - rt8 16020 + 10.0.255.3/32 50 - rt8 16030 + 10.0.255.4/32 40 - rt8 16040 + 10.0.255.5/32 30 - rt8 16050 + 10.0.255.6/32 40 - rt8 16060 + 10.0.255.7/32 30 - rt8 16070 + 10.0.255.8/32 20 - rt8 implicit-null + 10.0.255.9/32 30 - rt8 16090 + - rt12 16090 + 10.0.255.10/32 20 - rt10 implicit-null + 10.0.255.11/32 0 - - - + 10.0.255.12/32 20 - rt12 implicit-null + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.1/32 60 - rt10 16010 + 10.0.255.2/32 60 - rt12 50900/16020 + 10.0.255.3/32 70 - rt12 50900/16030 + 10.0.255.4/32 50 - rt10 16040 + 10.0.255.5/32 50 - rt12 50900/16050 + 10.0.255.6/32 60 - rt12 50900/16060 + 10.0.255.7/32 40 - rt10 16070 + 10.0.255.8/32 40 - rt12 50900/16080 + +test# test isis topology 7 root rt6 remote-lfa system-id rt5 ipv4-only +P-space (self): + rt3 + +P-space (rt3): + rt2 + rt3 + +P-space (rt9): + rt1 + rt2 + rt4 + rt5 + rt7 + rt8 + rt9 + rt10 + rt11 + rt12 + +Q-space: + rt1 + rt2 + rt4 + rt5 + rt7 + rt8 + rt9 + rt10 + rt11 + rt12 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt6 +10.0.255.6/32 IP internal 0 rt6(4) +rt3 TE-IS 10 rt3 - rt6(4) +rt2 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt9 TE-IS 30 rt9 - rt6(4) +rt5 TE-IS 30 rt3 - rt2(4) +10.0.255.2/32 IP TE 30 rt3 - rt2(4) +rt8 TE-IS 40 rt9 - rt9(4) + rt3 - rt5(4) +rt12 TE-IS 40 rt9 - rt9(4) +rt4 TE-IS 40 rt3 - rt5(4) +10.0.255.9/32 IP TE 40 rt9 - rt9(4) +10.0.255.5/32 IP TE 40 rt3 - rt5(4) +rt7 TE-IS 50 rt9 - rt8(4) + rt3 - rt4(4) +rt11 TE-IS 50 rt9 - rt8(4) + rt3 - rt12(4) +rt1 TE-IS 50 rt3 - rt4(4) +10.0.255.8/32 IP TE 50 rt9 - rt8(4) + rt3 - +10.0.255.12/32 IP TE 50 rt9 - rt12(4) +10.0.255.4/32 IP TE 50 rt3 - rt4(4) +rt10 TE-IS 60 rt9 - rt11(4) + rt3 - +10.0.255.7/32 IP TE 60 rt9 - rt7(4) + rt3 - +10.0.255.11/32 IP TE 60 rt9 - rt11(4) + rt3 - +10.0.255.1/32 IP TE 60 rt3 - rt1(4) +10.0.255.10/32 IP TE 70 rt9 - rt10(4) + rt3 - + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt6 +10.0.255.6/32 IP internal 0 rt6(4) +rt3 TE-IS 10 rt3 - rt6(4) +rt5 TE-IS 10 rt5 - rt6(4) +rt2 TE-IS 20 rt3 - rt3(4) + rt5 - rt5(4) +rt4 TE-IS 20 rt5 - rt5(4) +rt8 TE-IS 20 rt5 - rt5(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +rt9 TE-IS 30 rt9 - rt6(4) + rt5 - rt8(4) +rt1 TE-IS 30 rt5 - rt4(4) +rt7 TE-IS 30 rt5 - rt4(4) + rt8(4) +rt11 TE-IS 30 rt5 - rt8(4) +10.0.255.2/32 IP TE 30 rt3 - rt2(4) + rt5 - +10.0.255.4/32 IP TE 30 rt5 - rt4(4) +10.0.255.8/32 IP TE 30 rt5 - rt8(4) +rt12 TE-IS 40 rt9 - rt9(4) + rt5 - rt11(4) +rt10 TE-IS 40 rt5 - rt11(4) +10.0.255.9/32 IP TE 40 rt9 - rt9(4) + rt5 - +10.0.255.1/32 IP TE 40 rt5 - rt1(4) +10.0.255.7/32 IP TE 40 rt5 - rt7(4) +10.0.255.11/32 IP TE 40 rt5 - rt11(4) +10.0.255.12/32 IP TE 50 rt9 - rt12(4) + rt5 - +10.0.255.10/32 IP TE 50 rt5 - rt10(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 40 - rt5 16010 + 10.0.255.2/32 30 - rt3 16020 + - rt5 16020 + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt5 16040 + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 0 - - - + 10.0.255.7/32 40 - rt5 16070 + 10.0.255.8/32 30 - rt5 16080 + 10.0.255.9/32 40 - rt9 implicit-null + - rt5 implicit-null + 10.0.255.10/32 50 - rt5 16100 + 10.0.255.11/32 40 - rt5 16110 + 10.0.255.12/32 50 - rt9 16120 + - rt5 16120 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------ + 10.0.255.1/32 80 - rt9 16010 + 10.0.255.4/32 70 - rt9 16040 + 10.0.255.5/32 60 - rt9 16050 + 10.0.255.7/32 60 - rt9 16070 + 10.0.255.8/32 50 - rt9 16080 + 10.0.255.10/32 70 - rt9 16100 + 10.0.255.11/32 60 - rt9 16110 + +test# test isis topology 8 root rt2 remote-lfa system-id rt5 ipv4-only +P-space (self): + rt1 + rt3 + rt4 + rt7 + rt10 + +P-space (rt1): + rt1 + rt4 + rt7 + rt10 + +P-space (rt3): + rt3 + rt6 + +Q-space: + rt5 + rt6 + rt8 + rt9 + rt11 + rt12 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt1 TE-IS 10 rt1 - rt2(4) +rt3 TE-IS 10 rt3 - rt2(4) +rt4 TE-IS 20 rt1 - rt1(4) +rt6 TE-IS 20 rt3 - rt3(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt1 - rt4(4) +rt5 TE-IS 30 rt3 - rt6(4) +10.0.255.4/32 IP TE 30 rt1 - rt4(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) +rt10 TE-IS 40 rt1 - rt7(4) +rt8 TE-IS 40 rt3 - rt5(4) +10.0.255.7/32 IP TE 40 rt1 - rt7(4) +10.0.255.5/32 IP TE 40 rt3 - rt5(4) +rt9 TE-IS 50 rt3 - rt8(4) +rt11 TE-IS 50 rt3 - rt8(4) +10.0.255.10/32 IP TE 50 rt1 - rt10(4) +10.0.255.8/32 IP TE 50 rt3 - rt8(4) +rt12 TE-IS 60 rt3 - rt9(4) + rt11(4) +10.0.255.9/32 IP TE 60 rt3 - rt9(4) +10.0.255.11/32 IP TE 60 rt3 - rt11(4) +10.0.255.12/32 IP TE 70 rt3 - rt12(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt1 TE-IS 10 rt1 - rt2(4) +rt3 TE-IS 10 rt3 - rt2(4) +rt5 TE-IS 10 rt5 - rt2(4) +rt4 TE-IS 20 rt1 - rt1(4) +rt6 TE-IS 20 rt3 - rt3(4) + rt5 - rt5(4) +rt8 TE-IS 20 rt5 - rt5(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +rt7 TE-IS 30 rt1 - rt4(4) +rt9 TE-IS 30 rt5 - rt8(4) +rt11 TE-IS 30 rt5 - rt8(4) +10.0.255.4/32 IP TE 30 rt1 - rt4(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) + rt5 - +10.0.255.8/32 IP TE 30 rt5 - rt8(4) +rt10 TE-IS 40 rt1 - rt7(4) +rt12 TE-IS 40 rt5 - rt9(4) + rt11(4) +10.0.255.7/32 IP TE 40 rt1 - rt7(4) +10.0.255.9/32 IP TE 40 rt5 - rt9(4) +10.0.255.11/32 IP TE 40 rt5 - rt11(4) +10.0.255.10/32 IP TE 50 rt1 - rt10(4) +10.0.255.12/32 IP TE 50 rt5 - rt12(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 20 - rt1 implicit-null + 10.0.255.2/32 0 - - - + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt1 16040 + 10.0.255.5/32 20 - rt5 implicit-null + 10.0.255.6/32 30 - rt3 16060 + - rt5 16060 + 10.0.255.7/32 40 - rt1 16070 + 10.0.255.8/32 30 - rt5 16080 + 10.0.255.9/32 40 - rt5 16090 + 10.0.255.10/32 50 - rt1 16100 + 10.0.255.11/32 40 - rt5 16110 + 10.0.255.12/32 50 - rt5 16120 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + --------------------------------------------------------- + 10.0.255.5/32 40 - rt3 50600/16050 + 10.0.255.8/32 50 - rt3 50600/16080 + 10.0.255.9/32 60 - rt3 50600/16090 + 10.0.255.11/32 60 - rt3 50600/16110 + 10.0.255.12/32 70 - rt3 50600/16120 + +test# test isis topology 11 root rt2 remote-lfa system-id rt4 +P-space (self): + +P-space (rt1): + rt1 + rt3 + rt5 + +P-space (rt3): + rt1 + rt3 + rt5 + rt6 + +Q-space: + rt1 + rt3 + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt1 TE-IS 50 rt1 - rt2(4) +rt3 TE-IS 50 rt3 - rt2(4) +rt2 +rt5 TE-IS 60 rt3 - rt3(4) +10.0.255.1/32 IP TE 60 rt1 - rt1(4) +10.0.255.3/32 IP TE 60 rt3 - rt3(4) +rt4 TE-IS 70 rt3 - rt5(4) +rt6 TE-IS 70 rt3 - rt5(4) +10.0.255.5/32 IP TE 70 rt3 - rt5(4) +10.0.255.4/32 IP TE 80 rt3 - rt4(4) +10.0.255.6/32 IP TE 80 rt3 - rt6(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt4 TE-IS 10 rt4 - rt2(4) +rt5 TE-IS 20 rt4 - rt4(4) +rt6 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +rt3 TE-IS 30 rt4 - rt5(4) +10.0.255.5/32 IP TE 30 rt4 - rt5(4) +10.0.255.6/32 IP TE 30 rt4 - rt6(4) +rt2 +rt1 TE-IS 40 rt4 - rt2(2) +10.0.255.3/32 IP TE 40 rt4 - rt3(4) +10.0.255.1/32 IP TE 50 rt4 - rt1(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 50 - rt4 16010 + 10.0.255.2/32 0 - - - + 10.0.255.3/32 40 - rt4 16030 + 10.0.255.4/32 20 - rt4 implicit-null + 10.0.255.5/32 30 - rt4 16050 + 10.0.255.6/32 30 - rt4 16060 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 60 - rt1 implicit-null + - rt3 16010 + 10.0.255.3/32 60 - rt1 16030 + - rt3 implicit-null + 10.0.255.4/32 80 - rt3 50500/16040 + 10.0.255.5/32 70 - rt1 16050 + - rt3 16050 + 10.0.255.6/32 80 - rt3 16060 + +P-space (self): + +P-space (rt1): + rt1 + rt3 + rt5 + +P-space (rt3): + rt1 + rt3 + rt5 + rt6 + +Q-space: + rt1 + rt3 + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt2 +2001:db8::2/128 IP6 internal 0 rt2(4) +rt1 TE-IS 50 rt1 - rt2(4) +rt3 TE-IS 50 rt3 - rt2(4) +rt2 +rt5 TE-IS 60 rt3 - rt3(4) +2001:db8::1/128 IP6 internal 60 rt1 - rt1(4) +2001:db8::3/128 IP6 internal 60 rt3 - rt3(4) +rt4 TE-IS 70 rt3 - rt5(4) +rt6 TE-IS 70 rt3 - rt5(4) +2001:db8::5/128 IP6 internal 70 rt3 - rt5(4) +2001:db8::4/128 IP6 internal 80 rt3 - rt4(4) +2001:db8::6/128 IP6 internal 80 rt3 - rt6(4) + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt2 +2001:db8::2/128 IP6 internal 0 rt2(4) +rt4 TE-IS 10 rt4 - rt2(4) +rt5 TE-IS 20 rt4 - rt4(4) +rt6 TE-IS 20 rt4 - rt4(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +rt3 TE-IS 30 rt4 - rt5(4) +2001:db8::5/128 IP6 internal 30 rt4 - rt5(4) +2001:db8::6/128 IP6 internal 30 rt4 - rt6(4) +rt2 +rt1 TE-IS 40 rt4 - rt2(2) +2001:db8::3/128 IP6 internal 40 rt4 - rt3(4) +2001:db8::1/128 IP6 internal 50 rt4 - rt1(4) + +Main: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 50 - rt4 16011 + 2001:db8::2/128 0 - - - + 2001:db8::3/128 40 - rt4 16031 + 2001:db8::4/128 20 - rt4 implicit-null + 2001:db8::5/128 30 - rt4 16051 + 2001:db8::6/128 30 - rt4 16061 + +Backup: +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 60 - rt1 implicit-null + - rt3 16011 + 2001:db8::3/128 60 - rt1 16031 + - rt3 implicit-null + 2001:db8::4/128 80 - rt3 50500/16041 + 2001:db8::5/128 70 - rt1 16051 + - rt3 16051 + 2001:db8::6/128 80 - rt3 16061 + +test# test isis topology 13 root rt1 remote-lfa system-id rt3 ipv4-only +P-space (self): + rt2 + +P-space (rt2): + rt2 + rt4 + +Q-space: + rt3 + rt4 + rt5 + rt6 + rt7 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt3 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +rt5 TE-IS 40 rt2 - rt3(4) +rt6 TE-IS 40 rt2 - rt3(4) +10.0.255.3/32 IP TE 40 rt2 - rt3(4) +rt7 TE-IS 50 rt2 - rt5(4) + rt6(4) +10.0.255.5/32 IP TE 50 rt2 - rt5(4) +10.0.255.6/32 IP TE 50 rt2 - rt6(4) +10.0.255.7/32 IP TE 60 rt2 - rt7(4) + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) + rt3 - rt3(4) +rt5 TE-IS 20 rt3 - rt3(4) +rt6 TE-IS 20 rt3 - rt3(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) + rt6(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) + rt3 - +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) + +Main: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 0 - - - + 10.0.255.2/32 20 - rt2 implicit-null + 10.0.255.3/32 20 - rt3 implicit-null + 10.0.255.4/32 30 - rt2 16040 + - rt3 16040 + 10.0.255.5/32 30 - rt3 16050 + 10.0.255.6/32 30 - rt3 16060 + 10.0.255.7/32 40 - rt3 16070 + +Backup: +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.3/32 40 - rt2 50400/16030 + 10.0.255.5/32 50 - rt2 50400/16050 + 10.0.255.6/32 50 - rt2 50400/16060 + 10.0.255.7/32 60 - rt2 50400/16070 + +test# +test# test isis topology 1 root rt1 ti-lfa system-id rt2 +P-space (self): + rt3 + rt5 + +P-space (rt3): + rt3 + rt5 + rt6 + +Q-space: + rt2 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt5(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt4 TE-IS 40 rt3 - rt6(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) +rt2 TE-IS 50 rt3 - rt4(4) +10.0.255.4/32 IP TE 50 rt3 - rt4(4) +10.0.255.2/32 IP TE 60 rt3 - rt2(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.2/32 60 - rt3 16060/16020 + 10.0.255.4/32 50 - rt3 16060/16040 + +P-space (self): + rt3 + rt5 + +P-space (rt3): + rt3 + rt5 + rt6 + +Q-space: + rt2 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt6 TE-IS 30 rt3 - rt5(4) +2001:db8::5/128 IP6 internal 30 rt3 - rt5(4) +rt4 TE-IS 40 rt3 - rt6(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) +rt2 TE-IS 50 rt3 - rt4(4) +2001:db8::4/128 IP6 internal 50 rt3 - rt4(4) +2001:db8::2/128 IP6 internal 60 rt3 - rt2(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 2001:db8::2/128 60 - rt3 16061/16021 + 2001:db8::4/128 50 - rt3 16061/16041 + +test# test isis topology 2 root rt1 ti-lfa system-id rt3 +P-space (self): + rt2 + rt4 + rt5 + rt6 + +P-space (rt2): + rt2 + +P-space (rt4): + rt2 + rt4 + rt5 + rt6 + +P-space (rt5): + rt2 + rt4 + rt5 + rt6 + +Q-space: + rt3 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 10 rt5 - rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +rt1 +rt6 TE-IS 20 rt4 - rt4(4) + rt5 - rt5(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.2/32 IP TE 25 rt2 - rt2(4) +10.0.255.6/32 IP TE 30 rt4 - rt6(4) + rt5 - +rt3 TE-IS 50 rt5 - rt5(4) +10.0.255.3/32 IP TE 60 rt5 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.3/32 60 - rt5 16050/18 + +P-space (self): + rt2 + rt4 + rt5 + rt6 + +P-space (rt2): + rt2 + +P-space (rt4): + rt2 + rt4 + rt5 + rt6 + +P-space (rt5): + rt2 + rt4 + rt5 + rt6 + +Q-space: + rt3 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt4 TE-IS 10 rt4 - rt1(4) +rt5 TE-IS 10 rt5 - rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +rt1 +rt6 TE-IS 20 rt4 - rt4(4) + rt5 - rt5(4) +2001:db8::4/128 IP6 internal 20 rt4 - rt4(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::2/128 IP6 internal 25 rt2 - rt2(4) +2001:db8::6/128 IP6 internal 30 rt4 - rt6(4) + rt5 - +rt3 TE-IS 50 rt5 - rt5(4) +2001:db8::3/128 IP6 internal 60 rt5 - rt3(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::3/128 60 - rt5 16051/19 + +test# test isis topology 2 root rt1 ti-lfa system-id rt1 pseudonode-id 1 +P-space (self): + rt2 + rt3 + +P-space (rt2): + rt2 + rt3 + +P-space (rt3): + rt2 + rt3 + +Q-space: + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +10.0.255.2/32 IP TE 25 rt2 - rt2(4) +rt3 TE-IS 30 rt3 - rt1(4) +10.0.255.3/32 IP TE 40 rt3 - rt3(4) +rt4 TE-IS 55 rt2 - rt2(4) +rt1 +rt6 TE-IS 65 rt2 - rt4(4) +rt5 TE-IS 65 rt2 - rt1(2) +10.0.255.4/32 IP TE 65 rt2 - rt4(4) +10.0.255.6/32 IP TE 75 rt2 - rt6(4) +10.0.255.5/32 IP TE 75 rt2 - rt5(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.4/32 65 - rt2 16020/18 + 10.0.255.5/32 75 - rt2 16020/18/16050 + 10.0.255.6/32 75 - rt2 16020/18/16060 + +P-space (self): + rt2 + rt3 + +P-space (rt2): + rt2 + rt3 + +P-space (rt3): + rt2 + rt3 + +Q-space: + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 15 rt2 - rt1(4) +2001:db8::2/128 IP6 internal 25 rt2 - rt2(4) +rt3 TE-IS 30 rt3 - rt1(4) +2001:db8::3/128 IP6 internal 40 rt3 - rt3(4) +rt4 TE-IS 55 rt2 - rt2(4) +rt1 +rt6 TE-IS 65 rt2 - rt4(4) +rt5 TE-IS 65 rt2 - rt1(2) +2001:db8::4/128 IP6 internal 65 rt2 - rt4(4) +2001:db8::6/128 IP6 internal 75 rt2 - rt6(4) +2001:db8::5/128 IP6 internal 75 rt2 - rt5(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------- + 2001:db8::4/128 65 - rt2 16021/19 + 2001:db8::5/128 75 - rt2 16021/19/16051 + 2001:db8::6/128 75 - rt2 16021/19/16061 + +test# test isis topology 2 root rt5 ti-lfa system-id rt1 pseudonode-id 1 +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + +P-space (rt6): + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt1 pseudo_TE-IS 30 rt6 - rt4(4) +rt1 TE-IS 30 rt6 - rt1(2) +10.0.255.4/32 IP TE 30 rt6 - rt4(4) +rt3 TE-IS 40 rt3 - rt5(4) +10.0.255.1/32 IP TE 40 rt6 - rt1(4) +rt2 TE-IS 45 rt6 - rt1(4) +10.0.255.3/32 IP TE 50 rt3 - rt3(4) +10.0.255.2/32 IP TE 55 rt6 - rt2(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.1/32 40 - rt6 16040/16010 + 10.0.255.2/32 55 - rt6 16040/16020 + 10.0.255.4/32 30 - rt6 16040 + +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + +P-space (rt6): + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt5 +2001:db8::5/128 IP6 internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +rt1 pseudo_TE-IS 30 rt6 - rt4(4) +rt1 TE-IS 30 rt6 - rt1(2) +2001:db8::4/128 IP6 internal 30 rt6 - rt4(4) +rt3 TE-IS 40 rt3 - rt5(4) +2001:db8::1/128 IP6 internal 40 rt6 - rt1(4) +rt2 TE-IS 45 rt6 - rt1(4) +2001:db8::3/128 IP6 internal 50 rt3 - rt3(4) +2001:db8::2/128 IP6 internal 55 rt6 - rt2(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 2001:db8::1/128 40 - rt6 16041/16011 + 2001:db8::2/128 55 - rt6 16041/16021 + 2001:db8::4/128 30 - rt6 16041 + +test# test isis topology 3 root rt5 ti-lfa system-id rt4 ipv4-only +P-space (self): + rt6 + +P-space (rt3): + rt1 + rt2 + rt3 + rt4 + rt6 + +P-space (rt6): + rt1 + rt2 + rt3 + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt4 TE-IS 20 rt6 - rt6(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt3 - rt5(4) +rt2 TE-IS 30 rt6 - rt4(4) +10.0.255.4/32 IP TE 30 rt6 - rt4(4) +rt1 TE-IS 40 rt3 - rt3(4) + rt6 - rt2(4) +10.0.255.3/32 IP TE 40 rt3 - rt3(4) +10.0.255.2/32 IP TE 40 rt6 - rt2(4) +10.0.255.1/32 IP TE 50 rt3 - rt1(4) + rt6 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.1/32 50 - rt3 16010 + - rt6 16010 + 10.0.255.2/32 40 - rt6 16020 + 10.0.255.4/32 30 - rt6 16040 + +test# test isis topology 3 root rt5 ti-lfa system-id rt3 ipv4-only +P-space (self): + rt1 + rt2 + rt4 + rt6 + +P-space (rt4): + rt1 + rt2 + rt3 + rt4 + rt6 + +P-space (rt6): + rt1 + rt2 + rt3 + rt4 + rt6 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt5 +10.0.255.5/32 IP internal 0 rt5(4) +rt4 TE-IS 10 rt4 - rt5(4) +rt6 TE-IS 10 rt6 - rt5(4) +rt2 TE-IS 20 rt4 - rt4(4) +10.0.255.4/32 IP TE 20 rt4 - rt4(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt1 TE-IS 30 rt4 - rt2(4) +rt3 TE-IS 30 rt4 - rt2(4) +10.0.255.2/32 IP TE 30 rt4 - rt2(4) +10.0.255.1/32 IP TE 40 rt4 - rt1(4) +10.0.255.3/32 IP TE 40 rt4 - rt3(4) + +IS-IS L1 IPv4 routing table: + +test# test isis topology 4 root rt1 ti-lfa system-id rt2 ipv4-only +P-space (self): + rt3 + rt5 + rt7 + +P-space (rt3): + rt3 + rt5 + rt7 + +Q-space: + rt2 + rt4 + rt6 + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +rt6 TE-IS 70 rt3 - rt5(4) +rt4 TE-IS 80 rt3 - rt6(4) +rt8 TE-IS 80 rt3 - rt6(4) +10.0.255.6/32 IP TE 80 rt3 - rt6(4) +rt2 TE-IS 90 rt3 - rt4(4) +10.0.255.4/32 IP TE 90 rt3 - rt4(4) +10.0.255.8/32 IP TE 90 rt3 - rt8(4) +10.0.255.2/32 IP TE 100 rt3 - rt2(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.2/32 100 - rt3 16050/17/16020 + 10.0.255.4/32 90 - rt3 16050/17/16040 + 10.0.255.6/32 80 - rt3 16050/17 + 10.0.255.8/32 90 - rt3 16050/17/16080 + +test# test isis topology 4 root rt4 ti-lfa system-id rt6 ipv4-only +P-space (self): + rt1 + rt2 + rt3 + rt5 + rt7 + +P-space (rt2): + rt1 + rt2 + rt3 + rt5 + rt7 + +Q-space: + rt6 + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt2 TE-IS 10 rt2 - rt4(4) +rt1 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt3 TE-IS 30 rt2 - rt1(4) +10.0.255.1/32 IP TE 30 rt2 - rt1(4) +rt5 TE-IS 40 rt2 - rt3(4) +10.0.255.3/32 IP TE 40 rt2 - rt3(4) +rt7 TE-IS 50 rt2 - rt5(4) +10.0.255.5/32 IP TE 50 rt2 - rt5(4) +10.0.255.7/32 IP TE 60 rt2 - rt7(4) +rt6 TE-IS 90 rt2 - rt5(4) +rt8 TE-IS 100 rt2 - rt6(4) +10.0.255.6/32 IP TE 100 rt2 - rt6(4) +10.0.255.8/32 IP TE 110 rt2 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.6/32 100 - rt2 16050/17 + 10.0.255.8/32 110 - rt2 16050/17/16080 + +test# test isis topology 5 root rt1 ti-lfa system-id rt2 ipv4-only +P-space (self): + rt3 + rt5 + rt7 + +P-space (rt3): + rt3 + rt5 + rt7 + rt8 + +Q-space: + rt2 + rt4 + rt6 + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +rt5 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt3 - rt5(4) +10.0.255.5/32 IP TE 30 rt3 - rt5(4) +rt8 TE-IS 40 rt3 - rt7(4) +10.0.255.7/32 IP TE 40 rt3 - rt7(4) +rt6 TE-IS 50 rt3 - rt8(4) +10.0.255.8/32 IP TE 50 rt3 - rt8(4) +rt4 TE-IS 60 rt3 - rt6(4) +10.0.255.6/32 IP TE 60 rt3 - rt6(4) +rt2 TE-IS 70 rt3 - rt4(4) +10.0.255.4/32 IP TE 70 rt3 - rt4(4) +10.0.255.2/32 IP TE 80 rt3 - rt2(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.2/32 80 - rt3 16080/16020 + 10.0.255.4/32 70 - rt3 16080/16040 + 10.0.255.6/32 60 - rt3 16080/16060 + +test# test isis topology 6 root rt4 ti-lfa system-id rt3 ipv4-only +P-space (self): + rt2 + rt5 + rt6 + rt7 + rt8 + +P-space (rt2): + rt1 + rt2 + +P-space (rt6): + rt5 + rt6 + rt7 + rt8 + +Q-space: + rt1 + rt3 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt4 +10.0.255.4/32 IP internal 0 rt4(4) +rt2 TE-IS 10 rt2 - rt4(4) +rt6 TE-IS 10 rt6 - rt4(4) +rt1 TE-IS 20 rt2 - rt2(4) +rt5 TE-IS 20 rt6 - rt6(4) +rt8 TE-IS 20 rt6 - rt6(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +rt3 TE-IS 30 rt2 - rt1(4) +rt7 TE-IS 30 rt6 - rt5(4) + rt8(4) +10.0.255.1/32 IP TE 30 rt2 - rt1(4) +10.0.255.5/32 IP TE 30 rt6 - rt5(4) +10.0.255.8/32 IP TE 30 rt6 - rt8(4) +10.0.255.3/32 IP TE 40 rt2 - rt3(4) +10.0.255.7/32 IP TE 40 rt6 - rt7(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.3/32 40 - rt2 16010/16030 + +test# test isis topology 7 root rt11 ti-lfa system-id rt8 ipv4-only +P-space (self): + rt10 + rt12 + +P-space (rt10): + rt1 + rt4 + rt7 + rt10 + +P-space (rt12): + rt9 + rt12 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt11 +10.0.255.11/32 IP internal 0 rt11(4) +rt10 TE-IS 10 rt10 - rt11(4) +rt12 TE-IS 10 rt12 - rt11(4) +rt9 TE-IS 20 rt12 - rt12(4) +10.0.255.10/32 IP TE 20 rt10 - rt10(4) +10.0.255.12/32 IP TE 20 rt12 - rt12(4) +rt7 TE-IS 30 rt10 - rt10(4) +rt8 TE-IS 30 rt12 - rt9(4) +10.0.255.9/32 IP TE 30 rt12 - rt9(4) +rt4 TE-IS 40 rt10 - rt7(4) +rt5 TE-IS 40 rt12 - rt8(4) +10.0.255.7/32 IP TE 40 rt10 - rt7(4) +10.0.255.8/32 IP TE 40 rt12 - rt8(4) +rt6 TE-IS 50 rt12 - rt9(4) + rt5(4) +rt1 TE-IS 50 rt10 - rt4(4) +rt2 TE-IS 50 rt12 - rt5(4) +10.0.255.4/32 IP TE 50 rt10 - rt4(4) +10.0.255.5/32 IP TE 50 rt12 - rt5(4) +rt3 TE-IS 60 rt12 - rt6(4) + rt2(4) +10.0.255.6/32 IP TE 60 rt12 - rt6(4) +10.0.255.1/32 IP TE 60 rt10 - rt1(4) +10.0.255.2/32 IP TE 60 rt12 - rt2(4) +10.0.255.3/32 IP TE 70 rt12 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.1/32 60 - rt10 16010 + 10.0.255.2/32 60 - rt12 16090/16020 + 10.0.255.3/32 70 - rt12 16090/16030 + 10.0.255.4/32 50 - rt10 16040 + 10.0.255.5/32 50 - rt12 16090/16050 + 10.0.255.6/32 60 - rt12 16090/16060 + 10.0.255.7/32 40 - rt10 16070 + 10.0.255.8/32 40 - rt12 16090/16080 + +test# test isis topology 7 root rt6 ti-lfa system-id rt5 ipv4-only +P-space (self): + rt3 + +P-space (rt3): + rt2 + rt3 + +P-space (rt9): + rt1 + rt2 + rt4 + rt5 + rt7 + rt8 + rt9 + rt10 + rt11 + rt12 + +Q-space: + rt1 + rt2 + rt4 + rt5 + rt7 + rt8 + rt9 + rt10 + rt11 + rt12 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt6 +10.0.255.6/32 IP internal 0 rt6(4) +rt3 TE-IS 10 rt3 - rt6(4) +rt2 TE-IS 20 rt3 - rt3(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt9 TE-IS 30 rt9 - rt6(4) +rt5 TE-IS 30 rt3 - rt2(4) +10.0.255.2/32 IP TE 30 rt3 - rt2(4) +rt8 TE-IS 40 rt9 - rt9(4) + rt3 - rt5(4) +rt12 TE-IS 40 rt9 - rt9(4) +rt4 TE-IS 40 rt3 - rt5(4) +10.0.255.9/32 IP TE 40 rt9 - rt9(4) +10.0.255.5/32 IP TE 40 rt3 - rt5(4) +rt7 TE-IS 50 rt9 - rt8(4) + rt3 - rt4(4) +rt11 TE-IS 50 rt9 - rt8(4) + rt3 - rt12(4) +rt1 TE-IS 50 rt3 - rt4(4) +10.0.255.8/32 IP TE 50 rt9 - rt8(4) + rt3 - +10.0.255.12/32 IP TE 50 rt9 - rt12(4) +10.0.255.4/32 IP TE 50 rt3 - rt4(4) +rt10 TE-IS 60 rt9 - rt11(4) + rt3 - +10.0.255.7/32 IP TE 60 rt9 - rt7(4) + rt3 - +10.0.255.11/32 IP TE 60 rt9 - rt11(4) + rt3 - +10.0.255.1/32 IP TE 60 rt3 - rt1(4) +10.0.255.10/32 IP TE 70 rt9 - rt10(4) + rt3 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + --------------------------------------------------------- + 10.0.255.1/32 60 - rt3 16020/16010 + 10.0.255.4/32 50 - rt3 16020/16040 + 10.0.255.5/32 40 - rt3 16020/16050 + 10.0.255.7/32 60 - rt9 16070 + - rt3 16070 + 10.0.255.8/32 50 - rt9 16080 + - rt3 16080 + 10.0.255.10/32 70 - rt9 16100 + - rt3 16100 + 10.0.255.11/32 60 - rt9 16110 + - rt3 16110 + +test# test isis topology 8 root rt2 ti-lfa system-id rt1 ipv4-only +P-space (self): + rt3 + rt5 + rt6 + rt8 + rt9 + rt11 + rt12 + +P-space (rt3): + rt3 + rt6 + +P-space (rt5): + rt5 + rt6 + rt8 + rt9 + rt11 + rt12 + +Q-space: + rt1 + rt4 + rt7 + rt10 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt3 TE-IS 10 rt3 - rt2(4) +rt5 TE-IS 10 rt5 - rt2(4) +rt6 TE-IS 20 rt3 - rt3(4) + rt5 - rt5(4) +rt8 TE-IS 20 rt5 - rt5(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +rt9 TE-IS 30 rt5 - rt8(4) +rt11 TE-IS 30 rt5 - rt8(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) + rt5 - +10.0.255.8/32 IP TE 30 rt5 - rt8(4) +rt12 TE-IS 40 rt5 - rt9(4) + rt11(4) +10.0.255.9/32 IP TE 40 rt5 - rt9(4) +10.0.255.11/32 IP TE 40 rt5 - rt11(4) +10.0.255.12/32 IP TE 50 rt5 - rt12(4) +rt10 TE-IS 60 rt5 - rt11(4) +rt7 TE-IS 70 rt5 - rt10(4) +10.0.255.10/32 IP TE 70 rt5 - rt10(4) +rt4 TE-IS 80 rt5 - rt7(4) +10.0.255.7/32 IP TE 80 rt5 - rt7(4) +rt1 TE-IS 90 rt5 - rt4(4) +10.0.255.4/32 IP TE 90 rt5 - rt4(4) +10.0.255.1/32 IP TE 100 rt5 - rt1(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 10.0.255.1/32 100 - rt5 16110/17/16010 + 10.0.255.4/32 90 - rt5 16110/17/16040 + 10.0.255.7/32 80 - rt5 16110/17/16070 + 10.0.255.10/32 70 - rt5 16110/17 + +test# test isis topology 8 root rt2 ti-lfa system-id rt5 ipv4-only +P-space (self): + rt1 + rt3 + rt4 + rt7 + rt10 + +P-space (rt1): + rt1 + rt4 + rt7 + rt10 + +P-space (rt3): + rt3 + rt6 + +Q-space: + rt5 + rt6 + rt8 + rt9 + rt11 + rt12 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt1 TE-IS 10 rt1 - rt2(4) +rt3 TE-IS 10 rt3 - rt2(4) +rt4 TE-IS 20 rt1 - rt1(4) +rt6 TE-IS 20 rt3 - rt3(4) +10.0.255.1/32 IP TE 20 rt1 - rt1(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt7 TE-IS 30 rt1 - rt4(4) +rt5 TE-IS 30 rt3 - rt6(4) +10.0.255.4/32 IP TE 30 rt1 - rt4(4) +10.0.255.6/32 IP TE 30 rt3 - rt6(4) +rt10 TE-IS 40 rt1 - rt7(4) +rt8 TE-IS 40 rt3 - rt5(4) +10.0.255.7/32 IP TE 40 rt1 - rt7(4) +10.0.255.5/32 IP TE 40 rt3 - rt5(4) +rt9 TE-IS 50 rt3 - rt8(4) +rt11 TE-IS 50 rt3 - rt8(4) +10.0.255.10/32 IP TE 50 rt1 - rt10(4) +10.0.255.8/32 IP TE 50 rt3 - rt8(4) +rt12 TE-IS 60 rt3 - rt9(4) + rt11(4) +10.0.255.9/32 IP TE 60 rt3 - rt9(4) +10.0.255.11/32 IP TE 60 rt3 - rt11(4) +10.0.255.12/32 IP TE 70 rt3 - rt12(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + --------------------------------------------------------- + 10.0.255.5/32 40 - rt3 16060/16050 + 10.0.255.8/32 50 - rt3 16060/16080 + 10.0.255.9/32 60 - rt3 16060/16090 + 10.0.255.11/32 60 - rt3 16060/16110 + 10.0.255.12/32 70 - rt3 16060/16120 + +test# test isis topology 9 root rt1 ti-lfa system-id rt3 +P-space (self): + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +P-space (rt2): + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +Q-space: + rt3 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt5 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +rt9 TE-IS 40 rt2 - rt5(4) +10.0.255.5/32 IP TE 40 rt2 - rt5(4) +rt6 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt7 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt8 TE-IS 50 rt2 - rt4(4) + rt9(4) +10.0.255.9/32 IP TE 50 rt2 - rt9(4) +10.0.255.6/32 IP TE 60 rt2 - rt6(4) +10.0.255.7/32 IP TE 60 rt2 - rt7(4) +10.0.255.8/32 IP TE 60 rt2 - rt8(4) +rt3 TE-IS 120 rt2 - rt4(4) +10.0.255.3/32 IP TE 130 rt2 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.3/32 130 - rt2 16040/18 + +P-space (self): + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +P-space (rt2): + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +Q-space: + rt3 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +rt5 TE-IS 30 rt2 - rt4(4) +2001:db8::4/128 IP6 internal 30 rt2 - rt4(4) +rt9 TE-IS 40 rt2 - rt5(4) +2001:db8::5/128 IP6 internal 40 rt2 - rt5(4) +rt6 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt7 TE-IS 50 rt2 - rt4(4) + rt9(4) +rt8 TE-IS 50 rt2 - rt4(4) + rt9(4) +2001:db8::9/128 IP6 internal 50 rt2 - rt9(4) +2001:db8::6/128 IP6 internal 60 rt2 - rt6(4) +2001:db8::7/128 IP6 internal 60 rt2 - rt7(4) +2001:db8::8/128 IP6 internal 60 rt2 - rt8(4) +rt3 TE-IS 120 rt2 - rt4(4) +2001:db8::3/128 IP6 internal 130 rt2 - rt3(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::3/128 130 - rt2 16041/19 + +test# test isis topology 9 root rt1 ti-lfa system-id rt2 +P-space (self): + rt3 + +P-space (rt3): + rt3 + +Q-space: + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +10.0.255.3/32 IP TE 20 rt3 - rt3(4) +rt4 TE-IS 110 rt3 - rt3(4) +rt2 TE-IS 120 rt3 - rt4(4) +rt5 TE-IS 120 rt3 - rt4(4) +10.0.255.4/32 IP TE 120 rt3 - rt4(4) +rt9 TE-IS 130 rt3 - rt5(4) +10.0.255.2/32 IP TE 130 rt3 - rt2(4) +10.0.255.5/32 IP TE 130 rt3 - rt5(4) +rt6 TE-IS 140 rt3 - rt4(4) + rt9(4) +rt7 TE-IS 140 rt3 - rt4(4) + rt9(4) +rt8 TE-IS 140 rt3 - rt4(4) + rt9(4) +10.0.255.9/32 IP TE 140 rt3 - rt9(4) +10.0.255.6/32 IP TE 150 rt3 - rt6(4) +10.0.255.7/32 IP TE 150 rt3 - rt7(4) +10.0.255.8/32 IP TE 150 rt3 - rt8(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.2/32 130 - rt3 16030/18/16020 + 10.0.255.4/32 120 - rt3 16030/18 + 10.0.255.5/32 130 - rt3 16030/18/16050 + 10.0.255.6/32 150 - rt3 16030/18/16060 + 10.0.255.7/32 150 - rt3 16030/18/16070 + 10.0.255.8/32 150 - rt3 16030/18/16080 + 10.0.255.9/32 140 - rt3 16030/18/16090 + +P-space (self): + rt3 + +P-space (rt3): + rt3 + +Q-space: + rt2 + rt4 + rt5 + rt6 + rt7 + rt8 + rt9 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 10 rt3 - rt1(4) +2001:db8::3/128 IP6 internal 20 rt3 - rt3(4) +rt4 TE-IS 110 rt3 - rt3(4) +rt2 TE-IS 120 rt3 - rt4(4) +rt5 TE-IS 120 rt3 - rt4(4) +2001:db8::4/128 IP6 internal 120 rt3 - rt4(4) +rt9 TE-IS 130 rt3 - rt5(4) +2001:db8::2/128 IP6 internal 130 rt3 - rt2(4) +2001:db8::5/128 IP6 internal 130 rt3 - rt5(4) +rt6 TE-IS 140 rt3 - rt4(4) + rt9(4) +rt7 TE-IS 140 rt3 - rt4(4) + rt9(4) +rt8 TE-IS 140 rt3 - rt4(4) + rt9(4) +2001:db8::9/128 IP6 internal 140 rt3 - rt9(4) +2001:db8::6/128 IP6 internal 150 rt3 - rt6(4) +2001:db8::7/128 IP6 internal 150 rt3 - rt7(4) +2001:db8::8/128 IP6 internal 150 rt3 - rt8(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------- + 2001:db8::2/128 130 - rt3 16031/19/16021 + 2001:db8::4/128 120 - rt3 16031/19 + 2001:db8::5/128 130 - rt3 16031/19/16051 + 2001:db8::6/128 150 - rt3 16031/19/16061 + 2001:db8::7/128 150 - rt3 16031/19/16071 + 2001:db8::8/128 150 - rt3 16031/19/16081 + 2001:db8::9/128 140 - rt3 16031/19/16091 + +test# test isis topology 9 root rt9 ti-lfa system-id rt5 +P-space (self): + rt6 + rt7 + rt8 + +P-space (rt6): + rt6 + +P-space (rt7): + rt7 + +P-space (rt8): + rt8 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt5 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt9 +10.0.255.9/32 IP internal 0 rt9(4) +rt6 TE-IS 10 rt6 - rt9(4) +rt7 TE-IS 10 rt7 - rt9(4) +rt8 TE-IS 10 rt8 - rt9(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +10.0.255.7/32 IP TE 20 rt7 - rt7(4) +10.0.255.8/32 IP TE 20 rt8 - rt8(4) +rt4 TE-IS 40 rt6 - rt6(4) + rt7 - rt7(4) + rt8 - rt8(4) +rt2 TE-IS 50 rt6 - rt4(4) + rt7 - + rt8 - +rt5 TE-IS 50 rt6 - rt4(4) + rt7 - + rt8 - +10.0.255.4/32 IP TE 50 rt6 - rt4(4) + rt7 - + rt8 - +rt1 TE-IS 60 rt6 - rt2(4) + rt7 - + rt8 - +10.0.255.2/32 IP TE 60 rt6 - rt2(4) + rt7 - + rt8 - +10.0.255.5/32 IP TE 60 rt6 - rt5(4) + rt7 - + rt8 - +rt3 TE-IS 70 rt6 - rt1(4) + rt7 - + rt8 - +10.0.255.1/32 IP TE 70 rt6 - rt1(4) + rt7 - + rt8 - +10.0.255.3/32 IP TE 80 rt6 - rt3(4) + rt7 - + rt8 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.1/32 70 - rt6 16060/16/16010 + - rt7 16070/16/16010 + - rt8 16080/16/16010 + 10.0.255.2/32 60 - rt6 16060/16/16020 + - rt7 16070/16/16020 + - rt8 16080/16/16020 + 10.0.255.3/32 80 - rt6 16060/16/16030 + - rt7 16070/16/16030 + - rt8 16080/16/16030 + 10.0.255.4/32 50 - rt6 16060/16 + - rt7 16070/16 + - rt8 16080/16 + 10.0.255.5/32 60 - rt6 16060/16/16050 + - rt7 16070/16/16050 + - rt8 16080/16/16050 + +P-space (self): + rt6 + rt7 + rt8 + +P-space (rt6): + rt6 + +P-space (rt7): + rt7 + +P-space (rt8): + rt8 + +Q-space: + rt1 + rt2 + rt3 + rt4 + rt5 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt9 +2001:db8::9/128 IP6 internal 0 rt9(4) +rt6 TE-IS 10 rt6 - rt9(4) +rt7 TE-IS 10 rt7 - rt9(4) +rt8 TE-IS 10 rt8 - rt9(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +2001:db8::7/128 IP6 internal 20 rt7 - rt7(4) +2001:db8::8/128 IP6 internal 20 rt8 - rt8(4) +rt4 TE-IS 40 rt6 - rt6(4) + rt7 - rt7(4) + rt8 - rt8(4) +rt2 TE-IS 50 rt6 - rt4(4) + rt7 - + rt8 - +rt5 TE-IS 50 rt6 - rt4(4) + rt7 - + rt8 - +2001:db8::4/128 IP6 internal 50 rt6 - rt4(4) + rt7 - + rt8 - +rt1 TE-IS 60 rt6 - rt2(4) + rt7 - + rt8 - +2001:db8::2/128 IP6 internal 60 rt6 - rt2(4) + rt7 - + rt8 - +2001:db8::5/128 IP6 internal 60 rt6 - rt5(4) + rt7 - + rt8 - +rt3 TE-IS 70 rt6 - rt1(4) + rt7 - + rt8 - +2001:db8::1/128 IP6 internal 70 rt6 - rt1(4) + rt7 - + rt8 - +2001:db8::3/128 IP6 internal 80 rt6 - rt3(4) + rt7 - + rt8 - + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------- + 2001:db8::1/128 70 - rt6 16061/17/16011 + - rt7 16071/17/16011 + - rt8 16081/17/16011 + 2001:db8::2/128 60 - rt6 16061/17/16021 + - rt7 16071/17/16021 + - rt8 16081/17/16021 + 2001:db8::3/128 80 - rt6 16061/17/16031 + - rt7 16071/17/16031 + - rt8 16081/17/16031 + 2001:db8::4/128 50 - rt6 16061/17 + - rt7 16071/17 + - rt8 16081/17 + 2001:db8::5/128 60 - rt6 16061/17/16051 + - rt7 16071/17/16051 + - rt8 16081/17/16051 + +test# test isis topology 9 root rt9 ti-lfa system-id rt8 +P-space (self): + rt1 + rt2 + rt3 + rt4 + rt5 + rt6 + rt7 + +P-space (rt5): + rt1 + rt2 + rt3 + rt4 + rt5 + +P-space (rt6): + rt6 + +P-space (rt7): + rt7 + +Q-space: + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt9 +10.0.255.9/32 IP internal 0 rt9(4) +rt5 TE-IS 10 rt5 - rt9(4) +rt6 TE-IS 10 rt6 - rt9(4) +rt7 TE-IS 10 rt7 - rt9(4) +rt4 TE-IS 20 rt5 - rt5(4) +10.0.255.5/32 IP TE 20 rt5 - rt5(4) +10.0.255.6/32 IP TE 20 rt6 - rt6(4) +10.0.255.7/32 IP TE 20 rt7 - rt7(4) +rt2 TE-IS 30 rt5 - rt4(4) +10.0.255.4/32 IP TE 30 rt5 - rt4(4) +rt1 TE-IS 40 rt5 - rt2(4) +10.0.255.2/32 IP TE 40 rt5 - rt2(4) +rt8 TE-IS 50 rt5 - rt4(4) +rt3 TE-IS 50 rt5 - rt1(4) +10.0.255.1/32 IP TE 50 rt5 - rt1(4) +10.0.255.8/32 IP TE 60 rt5 - rt8(4) +10.0.255.3/32 IP TE 60 rt5 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------- + 10.0.255.8/32 60 - rt5 16040/26 + +P-space (self): + rt1 + rt2 + rt3 + rt4 + rt5 + rt6 + rt7 + +P-space (rt5): + rt1 + rt2 + rt3 + rt4 + rt5 + +P-space (rt6): + rt6 + +P-space (rt7): + rt7 + +Q-space: + rt8 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt9 +2001:db8::9/128 IP6 internal 0 rt9(4) +rt5 TE-IS 10 rt5 - rt9(4) +rt6 TE-IS 10 rt6 - rt9(4) +rt7 TE-IS 10 rt7 - rt9(4) +rt4 TE-IS 20 rt5 - rt5(4) +2001:db8::5/128 IP6 internal 20 rt5 - rt5(4) +2001:db8::6/128 IP6 internal 20 rt6 - rt6(4) +2001:db8::7/128 IP6 internal 20 rt7 - rt7(4) +rt2 TE-IS 30 rt5 - rt4(4) +2001:db8::4/128 IP6 internal 30 rt5 - rt4(4) +rt1 TE-IS 40 rt5 - rt2(4) +2001:db8::2/128 IP6 internal 40 rt5 - rt2(4) +rt8 TE-IS 50 rt5 - rt4(4) +rt3 TE-IS 50 rt5 - rt1(4) +2001:db8::1/128 IP6 internal 50 rt5 - rt1(4) +2001:db8::8/128 IP6 internal 60 rt5 - rt8(4) +2001:db8::3/128 IP6 internal 60 rt5 - rt3(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------- + 2001:db8::8/128 60 - rt5 16041/27 + +test# test isis topology 10 root rt1 ti-lfa system-id rt2 +P-space (self): + rt3 + rt4 + rt6 + rt7 + +P-space (rt3): + rt3 + rt6 + +P-space (rt4): + rt4 + rt7 + +Q-space: + rt2 + rt5 + rt8 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt4 TE-IS 20 rt4 - rt1(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt7 TE-IS 30 rt4 - rt4(4) +10.0.255.3/32 IP TE 30 rt3 - rt3(4) +10.0.255.4/32 IP TE 30 rt4 - rt4(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) +10.0.255.7/32 IP TE 40 rt4 - rt7(4) +rt8 TE-IS 80 rt3 - rt6(4) + rt4 - rt7(4) +rt5 TE-IS 90 rt3 - rt8(4) + rt4 - +10.0.255.8/32 IP TE 90 rt3 - rt8(4) + rt4 - +rt2 TE-IS 100 rt3 - rt5(4) + rt4 - +10.0.255.5/32 IP TE 100 rt3 - rt5(4) + rt4 - +10.0.255.2/32 IP TE 110 rt3 - rt2(4) + rt4 - + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.2/32 110 - rt3 20060/18/16020 + - rt4 16070/18/16020 + 10.0.255.5/32 100 - rt3 20060/18/16050 + - rt4 16070/18/16050 + 10.0.255.8/32 90 - rt3 20060/18 + - rt4 16070/18 + +P-space (self): + rt3 + rt4 + rt6 + rt7 + +P-space (rt3): + rt3 + rt6 + +P-space (rt4): + rt4 + rt7 + +Q-space: + rt2 + rt5 + rt8 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt4 TE-IS 20 rt4 - rt1(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt7 TE-IS 30 rt4 - rt4(4) +2001:db8::3/128 IP6 internal 30 rt3 - rt3(4) +2001:db8::4/128 IP6 internal 30 rt4 - rt4(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) +2001:db8::7/128 IP6 internal 40 rt4 - rt7(4) +rt8 TE-IS 80 rt3 - rt6(4) + rt4 - rt7(4) +rt5 TE-IS 90 rt3 - rt8(4) + rt4 - +2001:db8::8/128 IP6 internal 90 rt3 - rt8(4) + rt4 - +rt2 TE-IS 100 rt3 - rt5(4) + rt4 - +2001:db8::5/128 IP6 internal 100 rt3 - rt5(4) + rt4 - +2001:db8::2/128 IP6 internal 110 rt3 - rt2(4) + rt4 - + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------- + 2001:db8::2/128 110 - rt3 20061/19/16021 + - rt4 16071/19/16021 + 2001:db8::5/128 100 - rt3 20061/19/16051 + - rt4 16071/19/16051 + 2001:db8::8/128 90 - rt3 20061/19 + - rt4 16071/19 + +test# test isis topology 10 root rt1 ti-lfa system-id rt4 +P-space (self): + rt2 + rt3 + rt5 + rt6 + rt8 + +P-space (rt2): + rt2 + rt5 + rt8 + +P-space (rt3): + rt3 + rt6 + +Q-space: + rt4 + rt7 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt5 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt8 TE-IS 30 rt2 - rt5(4) +10.0.255.3/32 IP TE 30 rt3 - rt3(4) +10.0.255.5/32 IP TE 30 rt2 - rt5(4) +10.0.255.6/32 IP TE 40 rt3 - rt6(4) +10.0.255.8/32 IP TE 40 rt2 - rt8(4) +rt7 TE-IS 80 rt2 - rt8(4) +rt4 TE-IS 90 rt2 - rt7(4) +10.0.255.7/32 IP TE 90 rt2 - rt7(4) +10.0.255.4/32 IP TE 100 rt2 - rt4(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.4/32 100 - rt2 16080/20/16040 + 10.0.255.7/32 90 - rt2 16080/20 + +P-space (self): + rt2 + rt3 + rt5 + rt6 + rt8 + +P-space (rt2): + rt2 + rt5 + rt8 + +P-space (rt3): + rt3 + rt6 + +Q-space: + rt4 + rt7 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt1 +2001:db8::1/128 IP6 internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt3 TE-IS 20 rt3 - rt1(4) +rt5 TE-IS 20 rt2 - rt2(4) +2001:db8::2/128 IP6 internal 20 rt2 - rt2(4) +rt6 TE-IS 30 rt3 - rt3(4) +rt8 TE-IS 30 rt2 - rt5(4) +2001:db8::3/128 IP6 internal 30 rt3 - rt3(4) +2001:db8::5/128 IP6 internal 30 rt2 - rt5(4) +2001:db8::6/128 IP6 internal 40 rt3 - rt6(4) +2001:db8::8/128 IP6 internal 40 rt2 - rt8(4) +rt7 TE-IS 80 rt2 - rt8(4) +rt4 TE-IS 90 rt2 - rt7(4) +2001:db8::7/128 IP6 internal 90 rt2 - rt7(4) +2001:db8::4/128 IP6 internal 100 rt2 - rt4(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------- + 2001:db8::4/128 100 - rt2 16081/21/16041 + 2001:db8::7/128 90 - rt2 16081/21 + +test# test isis topology 11 root rt2 ti-lfa system-id rt4 +P-space (self): + +P-space (rt1): + rt1 + rt3 + rt5 + +P-space (rt3): + rt1 + rt3 + rt5 + rt6 + +Q-space: + rt1 + rt3 + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt2 +10.0.255.2/32 IP internal 0 rt2(4) +rt1 TE-IS 50 rt1 - rt2(4) +rt3 TE-IS 50 rt3 - rt2(4) +rt2 +rt5 TE-IS 60 rt3 - rt3(4) +10.0.255.1/32 IP TE 60 rt1 - rt1(4) +10.0.255.3/32 IP TE 60 rt3 - rt3(4) +rt4 TE-IS 70 rt3 - rt5(4) +rt6 TE-IS 70 rt3 - rt5(4) +10.0.255.5/32 IP TE 70 rt3 - rt5(4) +10.0.255.4/32 IP TE 80 rt3 - rt4(4) +10.0.255.6/32 IP TE 80 rt3 - rt6(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ---------------------------------------------------------- + 10.0.255.1/32 60 - rt1 implicit-null + 10.0.255.3/32 60 - rt3 implicit-null + 10.0.255.4/32 80 - rt3 16050/16040 + 10.0.255.5/32 70 - rt3 16050 + 10.0.255.6/32 80 - rt3 16060 + +P-space (self): + +P-space (rt1): + rt1 + rt3 + rt5 + +P-space (rt3): + rt1 + rt3 + rt5 + rt6 + +Q-space: + rt1 + rt3 + rt4 + rt5 + rt6 + +IS-IS paths to level-1 routers that speak IPv6 +Vertex Type Metric Next-Hop Interface Parent +rt2 +2001:db8::2/128 IP6 internal 0 rt2(4) +rt1 TE-IS 50 rt1 - rt2(4) +rt3 TE-IS 50 rt3 - rt2(4) +rt2 +rt5 TE-IS 60 rt3 - rt3(4) +2001:db8::1/128 IP6 internal 60 rt1 - rt1(4) +2001:db8::3/128 IP6 internal 60 rt3 - rt3(4) +rt4 TE-IS 70 rt3 - rt5(4) +rt6 TE-IS 70 rt3 - rt5(4) +2001:db8::5/128 IP6 internal 70 rt3 - rt5(4) +2001:db8::4/128 IP6 internal 80 rt3 - rt4(4) +2001:db8::6/128 IP6 internal 80 rt3 - rt6(4) + +IS-IS L1 IPv6 routing table: + + Prefix Metric Interface Nexthop Label(s) + ------------------------------------------------------------ + 2001:db8::1/128 60 - rt1 implicit-null + 2001:db8::3/128 60 - rt3 implicit-null + 2001:db8::4/128 80 - rt3 16051/16041 + 2001:db8::5/128 70 - rt3 16051 + 2001:db8::6/128 80 - rt3 16061 + +test# test isis topology 12 root rt1 ti-lfa system-id rt3 ipv4-only +P-space (self): + rt2 + rt4 + rt6 + rt8 + rt10 + +P-space (rt2): + rt2 + rt4 + rt6 + rt8 + rt10 + +Q-space: + rt3 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt6 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +rt8 TE-IS 40 rt2 - rt6(4) +10.0.255.6/32 IP TE 40 rt2 - rt6(4) +rt10 TE-IS 50 rt2 - rt8(4) +10.0.255.8/32 IP TE 50 rt2 - rt8(4) +10.0.255.10/32 IP TE 60 rt2 - rt10(4) +rt7 TE-IS 140 rt2 - rt8(4) +rt9 TE-IS 150 rt2 - rt7(4) +10.0.255.7/32 IP TE 150 rt2 - rt7(4) +10.0.255.9/32 IP TE 160 rt2 - rt9(4) +rt5 TE-IS 340 rt2 - rt7(4) +10.0.255.5/32 IP TE 350 rt2 - rt5(4) +rt3 TE-IS 740 rt2 - rt5(4) +10.0.255.3/32 IP TE 750 rt2 - rt3(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + ----------------------------------------------------------- + 10.0.255.3/32 750 - rt2 16080/17/16/16 + 10.0.255.5/32 350 - rt2 16080/17/16 + 10.0.255.7/32 150 - rt2 16080/17 + 10.0.255.9/32 160 - rt2 16080/17/18 + +test# test isis topology 13 root rt1 ti-lfa system-id rt3 ipv4-only +P-space (self): + rt2 + +P-space (rt2): + rt2 + rt4 + +Q-space: + rt3 + rt4 + rt5 + rt6 + rt7 + +IS-IS paths to level-1 routers that speak IP +Vertex Type Metric Next-Hop Interface Parent +rt1 +10.0.255.1/32 IP internal 0 rt1(4) +rt2 TE-IS 10 rt2 - rt1(4) +rt4 TE-IS 20 rt2 - rt2(4) +10.0.255.2/32 IP TE 20 rt2 - rt2(4) +rt3 TE-IS 30 rt2 - rt4(4) +10.0.255.4/32 IP TE 30 rt2 - rt4(4) +rt5 TE-IS 40 rt2 - rt3(4) +rt6 TE-IS 40 rt2 - rt3(4) +10.0.255.3/32 IP TE 40 rt2 - rt3(4) +rt7 TE-IS 50 rt2 - rt5(4) + rt6(4) +10.0.255.5/32 IP TE 50 rt2 - rt5(4) +10.0.255.6/32 IP TE 50 rt2 - rt6(4) +10.0.255.7/32 IP TE 60 rt2 - rt7(4) + +IS-IS L1 IPv4 routing table: + + Prefix Metric Interface Nexthop Label(s) + -------------------------------------------------------- + 10.0.255.3/32 40 - rt2 16040/16030 + 10.0.255.5/32 50 - rt2 16040/16050 + 10.0.255.6/32 50 - rt2 16040/16060 + 10.0.255.7/32 60 - rt2 16040/16070 + +test# +end. diff --git a/tests/isisd/test_isis_vertex_queue.c b/tests/isisd/test_isis_vertex_queue.c new file mode 100644 index 0000000..d2d9f62 --- /dev/null +++ b/tests/isisd/test_isis_vertex_queue.c @@ -0,0 +1,106 @@ +#include + +#include "isisd/isis_spf.c" + +#include "test_common.h" + +static struct isis_vertex **vertices; +static size_t vertex_count; + +static void setup_test_vertices(void) +{ + struct isis_spftree t = { + }; + struct prefix_pair p = { + }; + uint8_t node_id[7]; + + vertices = XMALLOC(MTYPE_TMP, sizeof(*vertices) * 16); + + p.dest.family = AF_INET; + p.dest.prefixlen = 24; + inet_pton(AF_INET, "192.168.1.0", &p.dest.u.prefix4); + vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); + vertices[vertex_count]->d_N = 20; + vertex_count++; + + p.dest.family = AF_INET; + p.dest.prefixlen = 24; + inet_pton(AF_INET, "192.168.2.0", &p.dest.u.prefix4); + vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); + vertices[vertex_count]->d_N = 20; + vertex_count++; + + memset(node_id, 0, sizeof(node_id)); + node_id[6] = 1; + vertices[vertex_count] = isis_vertex_new(&t, node_id, + VTYPE_PSEUDO_TE_IS); + vertices[vertex_count]->d_N = 15; + vertex_count++; + + memset(node_id, 0, sizeof(node_id)); + node_id[5] = 2; + vertices[vertex_count] = isis_vertex_new(&t, node_id, + VTYPE_NONPSEUDO_TE_IS); + vertices[vertex_count]->d_N = 15; + vertex_count++; + + p.dest.family = AF_INET; + p.dest.prefixlen = 24; + inet_pton(AF_INET, "192.168.3.0", &p.dest.u.prefix4); + vertices[vertex_count] = isis_vertex_new(&t, &p, VTYPE_IPREACH_TE); + vertices[vertex_count]->d_N = 20; + vertex_count++; +}; + +static void cleanup_test_vertices(void) +{ + for (size_t i = 0; i < vertex_count; i++) + isis_vertex_del(vertices[i]); + XFREE(MTYPE_TMP, vertices); + vertex_count = 0; +} + +static void test_ordered(void) +{ + struct isis_vertex_queue q; + + isis_vertex_queue_init(&q, NULL, true); + for (size_t i = 0; i < vertex_count; i++) + isis_vertex_queue_insert(&q, vertices[i]); + + assert(isis_vertex_queue_count(&q) == vertex_count); + + for (size_t i = 0; i < vertex_count; i++) { + assert(isis_find_vertex(&q, &vertices[i]->N, vertices[i]->type) == vertices[i]); + } + + assert(isis_vertex_queue_pop(&q) == vertices[2]); + assert(isis_find_vertex(&q, &vertices[2]->N, vertices[2]->type) == NULL); + + assert(isis_vertex_queue_pop(&q) == vertices[3]); + assert(isis_find_vertex(&q, &vertices[3]->N, vertices[3]->type) == NULL); + + assert(isis_vertex_queue_pop(&q) == vertices[0]); + assert(isis_find_vertex(&q, &vertices[0]->N, vertices[0]->type) == NULL); + + assert(isis_vertex_queue_pop(&q) == vertices[1]); + assert(isis_find_vertex(&q, &vertices[1]->N, vertices[1]->type) == NULL); + + isis_vertex_queue_delete(&q, vertices[4]); + assert(isis_find_vertex(&q, &vertices[4]->N, vertices[4]->type) == NULL); + + assert(isis_vertex_queue_count(&q) == 0); + assert(isis_vertex_queue_pop(&q) == NULL); + + isis_vertex_queue_free(&q); +} + +int main(int argc, char **argv) +{ + setup_test_vertices(); + test_ordered(); + cleanup_test_vertices(); + + return 0; +} diff --git a/tests/isisd/test_isis_vertex_queue.py b/tests/isisd/test_isis_vertex_queue.py new file mode 100644 index 0000000..b9d2fc5 --- /dev/null +++ b/tests/isisd/test_isis_vertex_queue.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestIsisVertexQueue(frrtest.TestMultiOut): + program = "./test_isis_vertex_queue" + + +TestIsisVertexQueue.exit_cleanly() diff --git a/tests/isisd/test_topologies.c b/tests/isisd/test_topologies.c new file mode 100644 index 0000000..a0ac768 --- /dev/null +++ b/tests/isisd/test_topologies.c @@ -0,0 +1,3474 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 NetDEF, Inc. + * Renato Westphal + */ + +#include + +#include "isisd/isisd.h" + +#include "test_common.h" + +/* + * clang-format off + * + * All topologies have the following properties: + * - The System-ID is 0000.0000.000X, where X is the node number (in hex); + * - The Router-ID is 10.0.255.X, where X is the node number; + * - The default link metric is 10; + * - When SR is enabled, Adj-SIDs and Prefix-SIDs are generated automatically; + * - When SR is enabled, the default SRGB is [16000-23999] (can be overridden). + * + * Test topology 1: + * ================ + * + * +---------+ + * | | + * | RT1 | + * +----------+ +----------+ + * | | | | + * | +---------+ | + * | | + * | | + * | | + * +----+----+ +----+----+ + * | | | | + * | RT2 | | RT3 | + * | | | | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * +---+-+---+ +----+----+ + * | | | | + * | RT4 | | RT5 | + * | | | | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * | +---------+ | + * | | | | + * | | RT6 | | + * +----------+ +----------+ + * | | + * +---------+ + * + * Test topology 2: + * ================ + * + * +---------+ + * | | + * | RT1 | + * +----------+ +----------+ + * | | | | + * | +----+----+ | + * | | | + * 15 | | | 30 + * | | | + * +----+----+ | +----+----+ + * | | | | | + * | RT2 | | | RT3 | + * | | | | | + * | | | | | + * +----+----+ | +----+----+ + * | | | + * 40 | | | 40 + * | | | + * +----+----+ | +----+----+ + * | | | | | + * | RT4 | | | RT5 | + * | +----------+----------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * | +---------+ | + * | | | | + * | | RT6 | | + * +----------+ +----------+ + * | | + * +---------+ + * + * Test topology 3: + * ================ + * + * +---------+ + * | | + * | RT1 | + * +----------+ +----------+ + * | | | | + * | +---------+ | + * | | + * | | + * | | + * +----+----+ +----+----+ + * | | | | + * | RT2 | | RT3 | + * | +---------------------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | 30 + * | | + * +----+----+ +----+----+ + * | | | | + * | RT4 | | RT5 | + * | +---------------------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * | +---------+ | + * | | | | + * | | RT6 | | + * +----------+ +----------+ + * | | + * +---------+ + * + * Test topology 4: + * ================ + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT3 | | RT4 | + * | | | | + * | | | | + * +---+-----+ +------+--+ + * |^ | + * ||200 | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT5 | 50 | RT6 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT7 | | RT8 | + * | | | | + * | | | | + * +---------+ +---------+ + * + * Test topology 5: + * ================ + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT3 | | RT4 | + * | | | | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT5 | | RT6 | + * | | | | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT7 | | RT8 | + * | +---------------------+ | + * | | | | + * +---------+ +---------+ + * + * Test topology 6: + * ================ + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT3 | | RT4 | + * | +---------------------+ | + * | | | | + * +---------+ +------+--+ + * | + * | + * | + * +---------+ +------+--+ + * | | | | + * | RT5 | | RT6 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT7 | | RT8 | + * | +---------------------+ | + * | | | | + * +---------+ +---------+ + * + * Test topology 7: + * ================ + * + * +---------+ +---------+ +---------+ + * | | | | | | + * | RT1 | 40 | RT2 | | RT3 | + * | +---------------------+ +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +------+--+ + * | | | + * | | | + * | | | + * +---+-----+ +----+----+ +------+--+ + * | | | | | | + * | RT4 | | RT5 | | RT6 | + * | +---------------------+ +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +------+--+ + * | | | + * | | | 30 + * | | | + * +---+-----+ +----+----+ +------+--+ + * | | | | | | + * | RT7 | | RT8 | | RT9 | + * | +---------------------+ +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +------+--+ + * | | | + * | 20 | | + * | | | + * +---+-----+ +----+----+ +------+--+ + * | | | | | | + * | RT10 | | RT11 | | RT12 | + * | +---------------------+ +---------------------+ | + * | | | | | | + * +---------+ +---------+ +---------+ + * + * Test topology 8: + * ================ + * + * +---------+ +---------+ +---------+ + * | | | | | | + * | RT1 | | RT2 | | RT3 | + * | +---------------------+ +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +------+--+ + * | | | + * | | | + * | | | + * +---+-----+ +----+----+ +------+--+ + * | | | | | | + * | RT4 | | RT5 | | RT6 | + * | | | +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +---------+ + * | | + * | | + * | | + * +---+-----+ +----+----+ +---------+ + * | | | | | | + * | RT7 | | RT8 | | RT9 | + * | | | +---------------------+ | + * | | | | | | + * +---+-----+ +----+----+ +------+--+ + * | | | + * | | | + * | | | + * +---+-----+ +----+----+ +------+--+ + * | | | | | | + * | RT10 | | RT11 | | RT12 | + * | +---------------------+ +---------------------+ | + * | | 30 | | | | + * +---------+ +---------+ +---------+ + * + * Test topology 9: + * ================ + * + * +---------+ + * | | + * | RT1 | + * +----------+ +----------+ + * | | | | + * | +---------+ | + * | | + * | | + * | | + * +----+----+ +----+----+ + * | | | | + * | RT2 | | RT3 | + * | | | | + * | | | | + * +----+----+ +------+--+ + * | | + * | | + * | |100 + * | +---------+ | + * | | | | + * +----------+ RT4 +------------+ + * +----------------| |----------------+ + * | +-+ +--+ | + * | | +---------+ | | + * | | | | + * | |30 |30 |30 + * | | | | + * +----+----+ +----+----+ +----+----+ +----+----+ + * | | | | | | | | + * | RT5 | | RT6 | | RT7 | | RT8 | + * | | | | | | | | + * | | | | | | | | + * +----+----+ +----+----+ +----+----+ +----+----+ + * | | | | + * | | | | + * | | | | + * | | +---------+ | | + * | +-+ +--+ | + * +----------------+ RT9 +----------------+ + * | | + * | | + * +---------+ + * + * Test topology 10: + * ================ + * + * +---------+ + * | | + * | RT1 | + * +----------+ +----------+ + * | | | | + * | +----+----+ | + * | | | + * | |20 |20 + * | | | + * +----+----+ +----+----+ +----+----+ + * | | | | | | + * | RT2 | | RT3 | | RT4 | + * | | | | | | + * | | | | | | + * +----+----+ +----+----+ +----+----+ + * | | | + * | | | + * | | | + * +----+----+ +----+----+ +----+----+ + * | | | | | | + * | RT5 | | RT6 | | RT7 | + * | | | | | | + * | | | | | | + * +----+----+ +----+----+ +----+----+ + * | | | + * | |50 |50 + * | | | + * | +----+----+ | + * | | | | + * +----------+ RT8 +----------+ + * | | + * | | + * +---------+ + * + * Test topology 11: + * ================ + * + * +---------+ + * | | + * | RT1 | + * | | + * | | + * +----+----+ + * | + * | + * | + * +---------+ | +---------+ + * | | | | | + * | RT2 |50 | | RT3 | + * | +----------+----------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * +----+----+ +----+----+ + * | | | | + * | RT4 | | RT5 | + * | +---------------------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * | +---------+ | + * | | | | + * | | RT6 | | + * +----------+ +----------+ + * | | + * +---------+ + * + * Test topology 12: + * ================ + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +---------------------+ | + * | | | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT3 | | RT4 | + * | | | | + * | | | | + * +---+-----+ +------+--+ + * |^ | + * |400 | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT5 | | RT6 | + * | | | | + * | | | | + * +---+-----+ +------+--+ + * |^ | + * |200 | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT7 | | RT8 | + * | +---------------------+ | + * | | 100 | | + * +---+-----+ +------+--+ + * | | + * | | + * | | + * +---+-----+ +------+--+ + * | | | | + * | RT9 | | RT10 | + * | | | | + * | | | | + * +---------+ +---------+ + * + * Test topology 13: + * ================ + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +---------------------+ | + * | | | | + * +---+-----+ +----+----+ + * | | + * | | + * | | + * | +----+----+ + * | | | + * | +----------+ RT4 | + * | | | | + * +---+-----+ | | | + * | | | +----+----+ + * | RT3 +----------+ | + * | +----------+ |100 + * | | | | + * +---+-----+ | +----+----+ + * | | | | + * | | | RT5 | + * | +----------+ | + * | | | + * | +----+----+ + * | | + * | | + * | | + * +---+-----+ +----+----+ + * | | | | + * | RT6 | | RT7 | + * | +---------------------+ | + * | | | | + * +---------+ +---------+ + + * Test topology 14: + * ================= + * + * +---------+ +---------+ + * | | | | + * | RT1 | | RT2 | + * | +--------------+ | + * | | | | + * +----+----+ +----+----+ + * | | + * | | + * | | + * | +----+----+ + * | | | + * | | RT3 | + * +-------------------+ | + * | | | + * | +----+----+ + * | | + * | |50 + * | | + * +----+----+ +----+----+ + * | | | | + * | RT4 | | RT5 | + * | +--------------+ | + * | | | | + * +---------+ +---------+ + */ + +struct isis_topology test_topologies[] = { + { + .number = 1, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.6/32", + "2001:db8::6/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 2, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt2", + .metric = 15, + }, + { + .hostname = "rt3", + .metric = 30, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 15, + }, + { + .hostname = "rt4", + .metric = 40, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 30, + }, + { + .hostname = "rt5", + .metric = 40, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt2", + .metric = 40, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 40, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.6/32", + "2001:db8::6/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .pseudonode_id = 1, + .level = IS_LEVEL_1, + .adjacencies = { + { + .hostname = "rt1", + .metric = 0, + }, + { + .hostname = "rt4", + .metric = 0, + }, + { + .hostname = "rt5", + .metric = 0, + }, + }, + }, + }, + }, + { + .number = 3, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 30, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 30, + }, + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 4, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 200, + }, + { + .hostname = "rt6", + .metric = 50, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 50, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 5, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 6, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 7, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 40, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 40, + }, + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt9", + .metric = 30, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + { + .hostname = "rt10", + .metric = 20, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + { + .hostname = "rt9", + .metric = 10, + }, + { + .hostname = "rt11", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt9", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x09}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.9", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.9/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 30, + }, + { + .hostname = "rt8", + .metric = 10, + }, + { + .hostname = "rt12", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt10", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.10", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.10/32", + }, + .adjacencies = { + { + .hostname = "rt7", + .metric = 20, + }, + { + .hostname = "rt11", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt11", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0b}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.11", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.11/32", + }, + .adjacencies = { + { + .hostname = "rt8", + .metric = 10, + }, + { + .hostname = "rt10", + .metric = 10, + }, + { + .hostname = "rt12", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt12", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0c}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.12", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.12/32", + }, + .adjacencies = { + { + .hostname = "rt9", + .metric = 10, + }, + { + .hostname = "rt11", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 8, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt10", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt9", + .metric = 10, + }, + { + .hostname = "rt11", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt9", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x09}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.9", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.9/32", + }, + .adjacencies = { + { + .hostname = "rt8", + .metric = 10, + }, + { + .hostname = "rt12", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt10", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.10", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.10/32", + }, + .adjacencies = { + { + .hostname = "rt7", + .metric = 10, + }, + { + .hostname = "rt11", + .metric = 30, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt11", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0b}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.11", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.11/32", + }, + .adjacencies = { + { + .hostname = "rt8", + .metric = 10, + }, + { + .hostname = "rt10", + .metric = 30, + }, + { + .hostname = "rt12", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt12", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0c}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.12", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.12/32", + }, + .adjacencies = { + { + .hostname = "rt9", + .metric = 10, + }, + { + .hostname = "rt11", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 9, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 100, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 100, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 30, + }, + { + .hostname = "rt7", + .metric = 30, + }, + { + .hostname = "rt8", + .metric = 30, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt9", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.6/32", + "2001:db8::6/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 30, + }, + { + .hostname = "rt9", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.7/32", + "2001:db8::7/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 30, + }, + { + .hostname = "rt9", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.8/32", + "2001:db8::8/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 30, + }, + { + .hostname = "rt9", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt9", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x09}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.9", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.9/32", + "2001:db8::9/128", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 10, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 20, + }, + { + .hostname = "rt4", + .metric = 20, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .srgb = { + .lower_bound = 20000, + .range_size = 8000, + }, + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 20, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 20, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.6/32", + "2001:db8::6/128", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 50, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.7/32", + "2001:db8::7/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 50, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.8/32", + "2001:db8::8/128", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 50, + }, + { + .hostname = "rt7", + .metric = 50, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 11, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .pseudonode_id = 1, + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .pseudonode_id = 1, + .metric = 50, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.6/32", + "2001:db8::6/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .pseudonode_id = 1, + .level = IS_LEVEL_1, + .adjacencies = { + { + .hostname = "rt1", + .metric = 0, + }, + { + .hostname = "rt2", + .metric = 0, + }, + { + .hostname = "rt3", + .metric = 0, + }, + }, + }, + }, + }, + { + .number = 12, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 400, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 200, + }, + { + .hostname = "rt8", + .metric = 100, + }, + { + .hostname = "rt9", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt8", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.8", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.8/32", + }, + .adjacencies = { + { + .hostname = "rt6", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 100, + }, + { + .hostname = "rt10", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt9", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x09}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.9", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.9/32", + }, + .adjacencies = { + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt10", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0a}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.10", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.10/32", + }, + .adjacencies = { + { + .hostname = "rt8", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 13, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.1/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.2/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.3/32", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.4/32", + }, + .adjacencies = { + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 100, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.5/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt4", + .metric = 100, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt6", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.6", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.6/32", + }, + .adjacencies = { + { + .hostname = "rt3", + .metric = 10, + }, + { + .hostname = "rt7", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + { + .hostname = "rt7", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.7", + .protocols = { + .ipv4 = true, + }, + .networks = { + "10.0.255.7/32", + }, + .adjacencies = { + { + .hostname = "rt5", + .metric = 10, + }, + { + .hostname = "rt6", + .metric = 10, + }, + }, + .flags = F_ISIS_TEST_NODE_SR, + }, + }, + }, + { + .number = 14, + .nodes = { + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.1", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.1/32", + "2001:db8::1/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt2", + .metric = 10, + }, + }, + }, + { + .hostname = "rt2", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.2", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.2/32", + "2001:db8::2/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .metric = 20, + }, + { + .hostname = "rt3", + .metric = 10, + }, + }, + }, + { + .hostname = "rt3", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.3", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.3/32", + "2001:db8::3/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt2", + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 50, + }, + }, + }, + { + .hostname = "rt4", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.4", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.4/32", + "2001:db8::4/128", + }, + .adjacencies = { + { + .hostname = "rt1", + .pseudonode_id = 1, + .metric = 10, + }, + { + .hostname = "rt5", + .metric = 10, + }, + }, + }, + { + .hostname = "rt5", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05}, + .level = IS_LEVEL_1, + .router_id = "10.0.255.5", + .protocols = { + .ipv4 = true, + .ipv6 = true, + }, + .networks = { + "10.0.255.5/32", + "2001:db8::5/128", + }, + .adjacencies = { + { + .hostname = "rt4", + .metric = 10, + }, + { + .hostname = "rt3", + .metric = 50, + }, + }, + }, + { + .hostname = "rt1", + .sysid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .pseudonode_id = 1, + .level = IS_LEVEL_1, + .adjacencies = { + { + .hostname = "rt1", + .metric = 0, + }, + { + .hostname = "rt3", + .metric = 0, + }, + { + .hostname = "rt4", + .metric = 0, + }, + }, + }, + }, + }, + { + /* sentinel */ + }, +}; diff --git a/tests/lib/cli/.gitignore b/tests/lib/cli/.gitignore new file mode 100644 index 0000000..682e95f --- /dev/null +++ b/tests/lib/cli/.gitignore @@ -0,0 +1 @@ +/test_cli.refout diff --git a/tests/lib/cli/common_cli.c b/tests/lib/cli/common_cli.c new file mode 100644 index 0000000..f9f584f --- /dev/null +++ b/tests/lib/cli/common_cli.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * generic CLI test helper functions + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + */ + +#include +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "lib_vty.h" +#include "log.h" + +#include "common_cli.h" + +struct event_loop *master; + +int dump_args(struct vty *vty, const char *descr, int argc, + struct cmd_token *argv[]) +{ + int i; + vty_out(vty, "%s with %d args.\n", descr, argc); + for (i = 0; i < argc; i++) { + vty_out(vty, "[%02d] %s@%s: %s\n", i, argv[i]->text, + argv[i]->varname, argv[i]->arg); + } + + return CMD_SUCCESS; +} + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + cmd_terminate(); + vty_terminate(); + nb_terminate(); + yang_terminate(); + event_master_free(master); + + log_memstats(stderr, "testcli"); + if (!isexit) + exit(0); +} + +const struct frr_yang_module_info *const *test_yang_modules = NULL; +int test_log_prio = ZLOG_DISABLED; + +/* main routine. */ +int main(int argc, char **argv) +{ + struct event thread; + size_t yangcount; + + /* Set umask before anything for security */ + umask(0027); + + /* master init. */ + master = event_master_create(NULL); + + zlog_aux_init("NONE: ", test_log_prio); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + cmd_domainname_set("test.domain"); + + vty_init(master, false); + lib_cmd_init(); + + for (yangcount = 0; test_yang_modules && test_yang_modules[yangcount]; + yangcount++) + ; + nb_init(master, test_yang_modules, yangcount, false); + + test_init(argc, argv); + + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/lib/cli/common_cli.h b/tests/lib/cli/common_cli.h new file mode 100644 index 0000000..4a1de94 --- /dev/null +++ b/tests/lib/cli/common_cli.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * generic CLI test helper functions + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + */ + +#ifndef _COMMON_CLI_H +#define _COMMON_CLI_H + +#include "zebra.h" +#include "vty.h" +#include "command.h" +#include "northbound.h" + +extern const struct frr_yang_module_info *const *test_yang_modules; + +/* function to be implemented by test */ +extern void test_init(int argc, char **argv); + +/* functions provided by common cli + * (includes main()) + */ +extern struct event_loop *master; + +extern int test_log_prio; + +extern int dump_args(struct vty *vty, const char *descr, int argc, + struct cmd_token *argv[]); + +#define DUMMY_HELPSTR \ + "00\n01\n02\n03\n04\n05\n06\n07\n08\n09\n" \ + "10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n" \ + "20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n" +#define DUMMY_DEFUN(name, cmdstr) \ + DEFUN(name, name##_cmd, cmdstr, DUMMY_HELPSTR) \ + { \ + return dump_args(vty, #name, argc, argv); \ + } + +#endif /* _COMMON_CLI_H */ diff --git a/tests/lib/cli/test_cli.c b/tests/lib/cli/test_cli.c new file mode 100644 index 0000000..0314735 --- /dev/null +++ b/tests/lib/cli/test_cli.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLI/command dummy handling tester + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + */ + +#include + +#include "prefix.h" +#include "common_cli.h" + +DUMMY_DEFUN(cmd0, "arg ipv4 A.B.C.D"); +DUMMY_DEFUN(cmd1, "arg ipv4m A.B.C.D/M"); +DUMMY_DEFUN(cmd2, "arg ipv6 X:X::X:X$foo"); +DUMMY_DEFUN(cmd3, "arg ipv6m X:X::X:X/M"); +DUMMY_DEFUN(cmd4, "arg range (5-15)"); +DUMMY_DEFUN(cmd5, "pat a < a|b>"); +DUMMY_DEFUN(cmd6, "pat b "); +DUMMY_DEFUN(cmd7, "pat c A.B.C.D"); +DUMMY_DEFUN(cmd8, "pat d { foo A.B.C.D$foo|bar X:X::X:X$bar| baz } [final]"); +DUMMY_DEFUN(cmd9, "pat e [ WORD ]"); +DUMMY_DEFUN(cmd10, "pat f [key]"); +DUMMY_DEFUN(cmd11, "alt a WORD"); +DUMMY_DEFUN(cmd12, "alt a A.B.C.D"); +DUMMY_DEFUN(cmd13, "alt a X:X::X:X"); +DUMMY_DEFUN(cmd14, + "pat g { foo A.B.C.D$foo|foo|bar X:X::X:X$bar| baz } [final]"); +DUMMY_DEFUN(cmd15, "no pat g ![ WORD ]"); +DUMMY_DEFUN(cmd16, "[no] pat h {foo ![A.B.C.D$foo]|bar X:X::X:X$bar} final"); + +#include "tests/lib/cli/test_cli_clippy.c" + +DEFPY(magic_test, magic_test_cmd, + "magic (0-100) {ipv4net A.B.C.D/M|X:X::X:X$ipv6}", + "1\n2\n3\n4\n5\n") +{ + vty_out(vty, "def: %s\n", self->string); + vty_out(vty, "num: %ld\n", magic); + vty_out(vty, "ipv4: %pFX\n", ipv4net); + vty_out(vty, "ipv6: %pI6\n", &ipv6); + return CMD_SUCCESS; +} + +void test_init(int argc, char **argv) +{ + size_t repeat = argc > 1 ? strtoul(argv[1], NULL, 0) : 223; + + install_element(ENABLE_NODE, &cmd0_cmd); + install_element(ENABLE_NODE, &cmd1_cmd); + install_element(ENABLE_NODE, &cmd2_cmd); + install_element(ENABLE_NODE, &cmd3_cmd); + install_element(ENABLE_NODE, &cmd4_cmd); + install_element(ENABLE_NODE, &cmd5_cmd); + install_element(ENABLE_NODE, &cmd6_cmd); + install_element(ENABLE_NODE, &cmd7_cmd); + install_element(ENABLE_NODE, &cmd8_cmd); + install_element(ENABLE_NODE, &cmd9_cmd); + install_element(ENABLE_NODE, &cmd10_cmd); + install_element(ENABLE_NODE, &cmd11_cmd); + install_element(ENABLE_NODE, &cmd12_cmd); + install_element(ENABLE_NODE, &cmd13_cmd); + for (size_t i = 0; i < repeat; i++) { + uninstall_element(ENABLE_NODE, &cmd5_cmd); + install_element(ENABLE_NODE, &cmd5_cmd); + } + for (size_t i = 0; i < repeat; i++) { + uninstall_element(ENABLE_NODE, &cmd13_cmd); + install_element(ENABLE_NODE, &cmd13_cmd); + } + install_element(ENABLE_NODE, &cmd14_cmd); + install_element(ENABLE_NODE, &cmd15_cmd); + install_element(ENABLE_NODE, &cmd16_cmd); + install_element(ENABLE_NODE, &magic_test_cmd); +} diff --git a/tests/lib/cli/test_cli.in b/tests/lib/cli/test_cli.in new file mode 100644 index 0000000..bd685a6 --- /dev/null +++ b/tests/lib/cli/test_cli.in @@ -0,0 +1,105 @@ +echo this is a test message +echo foo bla ? baz +echo + +arg ipv4 1.2.3.4 +arg ipv4 1.2.?3.4 +arg ipv4 1.2.3 +arg ipv4 1.2.3.4.5 +arg ipv4 1.a.3.4 +arg ipv4 blah + +arg ipv4m 1.2.3.0/24 +arg ipv4m 1.2.?3.0/24 +arg ipv4m 1.2.3/9 +arg ipv4m 1.2.3.4.5/6 +arg ipv4m 1.a.3.4 +arg ipv4m blah +arg ipv4m 1.2.3.0/999 +arg ipv4m 1.2.3.0/a9 +arg ipv4m 1.2.3.0/9a + +arg ipv6 de4d:b33f::cafe +arg ipv6 de4d:b3?3f::caf?e +arg ipv6 de4d:b3 3f::caf?e +arg ipv6 de4d:b33f:z::cafe +arg ipv6 de4d:b33f:cafe: +arg ipv6 :: +arg ipv6 ::/ +arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 +arg ipv6 12::34::56 +arg ipv6m dead:beef:cafe::/64 +arg ipv6m dead:be?ef:cafe:?:/64 + +arg range 4 +arg range 5 +arg range 9? +arg range 15 +arg range 16 +arg range -1 +arg range 99999999999999999999999999999999999999999 + +arg ? + +pa +pat + +pat a +pat a a +pat a ?b +pat a c? +pat a a x + +pat c a +pat c a 1.2.3.4 +pat c b 2.3.4 +pat c c ?x + +pat d +pat d +pat d foo 1.2.3.4 +pat d foo +pat d noooo +pat d bar 1::2 +pat d bar 1::2 foo 3.4.5.6 +pat d ba?z +pat d foo 3.4.5.6 baz + +pat e +pat e f +pat e f g +pat e 1.2.3.4 + +pat f +pat f foo +pat f key + +no pat g +no pat g test +no pat g test more + +pat h foo ?1.2.3.4 final +no pat h foo ?1.2.3.4 final +pat h foo final +no pat h foo final +pat h bar final +no pat h bar final +pat h bar 1::2 final +no pat h bar 1::2 final +pat h bar 1::2 foo final +no pat h bar 1::2 foo final +pat h bar 1::2 foo 1.2.3.4 final +no pat h bar 1::2 foo 1.2.3.4 final + +alt a a?b +alt a 1 .2?.3.4 +alt a 1 :2? ::?3 + +conf t +do pat d baz +exit + +show run +conf t +hostname foohost +do show run diff --git a/tests/lib/cli/test_cli.py b/tests/lib/cli/test_cli.py new file mode 100644 index 0000000..6fdd6fa --- /dev/null +++ b/tests/lib/cli/test_cli.py @@ -0,0 +1,6 @@ +import frrtest + + +class TestCli(frrtest.TestRefOut): + program = "./test_cli" + built_refout = True diff --git a/tests/lib/cli/test_cli.refout.in b/tests/lib/cli/test_cli.refout.in new file mode 100644 index 0000000..8436581 --- /dev/null +++ b/tests/lib/cli/test_cli.refout.in @@ -0,0 +1,431 @@ +test# echo this is a test message +this is a test message +test# echo foo bla +% There is no matched command. +test# echo foo bla baz +foo bla baz +test# echo +% Command incomplete. +test# +test# arg ipv4 1.2.3.4 +cmd0 with 3 args. +[00] arg@(null): arg +[01] ipv4@(null): ipv4 +[02] A.B.C.D@ipv4: 1.2.3.4 +test# arg ipv4 1.2. + A.B.C.D 02 +test# arg ipv4 1.2.3.4 +cmd0 with 3 args. +[00] arg@(null): arg +[01] ipv4@(null): ipv4 +[02] A.B.C.D@ipv4: 1.2.3.4 +test# arg ipv4 1.2.3 +% [NONE] Unknown command: arg ipv4 1.2.3 +test# arg ipv4 1.2.3.4.5 +% [NONE] Unknown command: arg ipv4 1.2.3.4.5 +test# arg ipv4 1.a.3.4 +% [NONE] Unknown command: arg ipv4 1.a.3.4 +test# arg ipv4 blah +% [NONE] Unknown command: arg ipv4 blah +test# +test# arg ipv4m 1.2.3.0/24 +cmd1 with 3 args. +[00] arg@(null): arg +[01] ipv4m@(null): ipv4m +[02] A.B.C.D/M@ipv4m: 1.2.3.0/24 +test# arg ipv4m 1.2. + A.B.C.D/M 02 +test# arg ipv4m 1.2.3.0/24 +cmd1 with 3 args. +[00] arg@(null): arg +[01] ipv4m@(null): ipv4m +[02] A.B.C.D/M@ipv4m: 1.2.3.0/24 +test# arg ipv4m 1.2.3/9 +% [NONE] Unknown command: arg ipv4m 1.2.3/9 +test# arg ipv4m 1.2.3.4.5/6 +% [NONE] Unknown command: arg ipv4m 1.2.3.4.5/6 +test# arg ipv4m 1.a.3.4 +% [NONE] Unknown command: arg ipv4m 1.a.3.4 +test# arg ipv4m blah +% [NONE] Unknown command: arg ipv4m blah +test# arg ipv4m 1.2.3.0/999 +% [NONE] Unknown command: arg ipv4m 1.2.3.0/999 +test# arg ipv4m 1.2.3.0/a9 +% [NONE] Unknown command: arg ipv4m 1.2.3.0/a9 +test# arg ipv4m 1.2.3.0/9a +% [NONE] Unknown command: arg ipv4m 1.2.3.0/9a +test# +test# arg ipv6 de4d:b33f::cafe +cmd2 with 3 args. +[00] arg@(null): arg +[01] ipv6@(null): ipv6 +[02] X:X::X:X@foo: de4d:b33f::cafe +test# arg ipv6 de4d:b3 + X:X::X:X 02 +test# arg ipv6 de4d:b33f::caf + X:X::X:X 02 +test# arg ipv6 de4d:b33f::cafe +cmd2 with 3 args. +[00] arg@(null): arg +[01] ipv6@(null): ipv6 +[02] X:X::X:X@foo: de4d:b33f::cafe +test# arg ipv6 de4d:b3 +test# arg ipv6 de4d:b33f::caf + X:X::X:X 02 +test# arg ipv6 de4d:b33f::cafe +cmd2 with 3 args. +[00] arg@(null): arg +[01] ipv6@(null): ipv6 +[02] X:X::X:X@foo: de4d:b33f::cafe +test# arg ipv6 de4d:b33f:z::cafe +% [NONE] Unknown command: arg ipv6 de4d:b33f:z::cafe +test# arg ipv6 de4d:b33f:cafe: +% [NONE] Unknown command: arg ipv6 de4d:b33f:cafe: +test# arg ipv6 :: +cmd2 with 3 args. +[00] arg@(null): arg +[01] ipv6@(null): ipv6 +[02] X:X::X:X@foo: :: +test# arg ipv6 ::/ +% [NONE] Unknown command: arg ipv6 ::/ +test# arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 +% [NONE] Unknown command: arg ipv6 1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0:1:2:3:4:5:6:7:8:9:0 +test# arg ipv6 12::34::56 +% [NONE] Unknown command: arg ipv6 12::34::56 +test# arg ipv6m dead:beef:cafe::/64 +cmd3 with 3 args. +[00] arg@(null): arg +[01] ipv6m@(null): ipv6m +[02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 +test# arg ipv6m dead:be + X:X::X:X/M 02 +test# arg ipv6m dead:beef:cafe: + X:X::X:X/M 02 +test# arg ipv6m dead:beef:cafe::/64 +cmd3 with 3 args. +[00] arg@(null): arg +[01] ipv6m@(null): ipv6m +[02] X:X::X:X/M@ipv6m: dead:beef:cafe::/64 +test# +test# arg range 4 +% [NONE] Unknown command: arg range 4 +test# arg range 5 +cmd4 with 3 args. +[00] arg@(null): arg +[01] range@(null): range +[02] (5-15)@range: 5 +test# arg range 9 + (5-15) 02 +test# arg range 9 +cmd4 with 3 args. +[00] arg@(null): arg +[01] range@(null): range +[02] (5-15)@range: 9 +test# arg range 15 +cmd4 with 3 args. +[00] arg@(null): arg +[01] range@(null): range +[02] (5-15)@range: 15 +test# arg range 16 +% [NONE] Unknown command: arg range 16 +test# arg range -1 +% [NONE] Unknown command: arg range -1 +test# arg range 99999999999999999999999999999999999999999 +% [NONE] Unknown command: arg range 99999999999999999999999999999999999999999 +test# +test# arg + ipv4 01 + ipv4m 01 + ipv6 01 + ipv6m 01 + range 01 +test# arg +% Command incomplete. +test# +test# pa +test# papat +% Command incomplete. +test# pat +a b c d e f +g h +test# pat +% Command incomplete. +test# +test# pat a +% Command incomplete. +test# pat a a +cmd5 with 3 args. +[00] pat@(null): pat +[01] a@(null): a +[02] a@(null): a +test# pat a + a 02 + b 03 +test# pat a b +cmd5 with 3 args. +[00] pat@(null): pat +[01] a@(null): a +[02] b@(null): b +test# pat a c +% There is no matched command. +test# pat a c +% [NONE] Unknown command: pat a c +test# pat a a x +% [NONE] Unknown command: pat a a x +test# +test# pat c a +% Command incomplete. +test# pat c a 1.2.3.4 +cmd7 with 4 args. +[00] pat@(null): pat +[01] c@(null): c +[02] a@(null): a +[03] A.B.C.D@(null): 1.2.3.4 +test# pat c b 2.3.4 +% [NONE] Unknown command: pat c b 2.3.4 +test# pat c c + A.B.C.D 05 +test# pat c c x +% [NONE] Unknown command: pat c c x +test# +test# pat d +% Command incomplete. +test# pat d +bar baz foo +test# pat d +% Command incomplete. +test# pat d foo 1.2.3.4 +cmd8 with 4 args. +[00] pat@(null): pat +[01] d@(null): d +[02] foo@(null): foo +[03] A.B.C.D@foo: 1.2.3.4 +test# pat d foo +% Command incomplete. +test# pat d noooo +% [NONE] Unknown command: pat d noooo +test# pat d bar 1::2 +cmd8 with 4 args. +[00] pat@(null): pat +[01] d@(null): d +[02] bar@(null): bar +[03] X:X::X:X@bar: 1::2 +test# pat d bar 1::2 foo 3.4.5.6 +cmd8 with 6 args. +[00] pat@(null): pat +[01] d@(null): d +[02] bar@(null): bar +[03] X:X::X:X@bar: 1::2 +[04] foo@(null): foo +[05] A.B.C.D@foo: 3.4.5.6 +test# pat d ba + bar 04 + baz 06 +test# pat d baz +cmd8 with 3 args. +[00] pat@(null): pat +[01] d@(null): d +[02] baz@(null): baz +test# pat d foo 3.4.5.6 baz +cmd8 with 5 args. +[00] pat@(null): pat +[01] d@(null): d +[02] foo@(null): foo +[03] A.B.C.D@foo: 3.4.5.6 +[04] baz@(null): baz +test# +test# pat e +cmd9 with 2 args. +[00] pat@(null): pat +[01] e@(null): e +test# pat e f +cmd9 with 3 args. +[00] pat@(null): pat +[01] e@(null): e +[02] WORD@e: f +test# pat e f g +% [NONE] Unknown command: pat e f g +test# pat e 1.2.3.4 +cmd9 with 3 args. +[00] pat@(null): pat +[01] e@(null): e +[02] WORD@e: 1.2.3.4 +test# +test# pat f +cmd10 with 2 args. +[00] pat@(null): pat +[01] f@(null): f +test# pat f foo +% [NONE] Unknown command: pat f foo +test# pat f key +cmd10 with 3 args. +[00] pat@(null): pat +[01] f@(null): f +[02] key@(null): key +test# +test# no pat g +cmd15 with 3 args. +[00] no@(null): no +[01] pat@(null): pat +[02] g@(null): g +test# no pat g test +cmd15 with 4 args. +[00] no@(null): no +[01] pat@(null): pat +[02] g@(null): g +[03] WORD@g: test +test# no pat g test more +% [NONE] Unknown command: no pat g test more +test# +test# pat h foo + A.B.C.D 04 +test# pat h foo 1.2.3.4 final +cmd16 with 5 args. +[00] pat@(null): pat +[01] h@(null): h +[02] foo@(null): foo +[03] A.B.C.D@foo: 1.2.3.4 +[04] final@(null): final +test# no pat h foo + A.B.C.D 04 + bar 05 + final 07 +test# no pat h foo 1.2.3.4 final +cmd16 with 6 args. +[00] no@no: no +[01] pat@(null): pat +[02] h@(null): h +[03] foo@(null): foo +[04] A.B.C.D@foo: 1.2.3.4 +[05] final@(null): final +test# pat h foo final +% [NONE] Unknown command: pat h foo final +test# no pat h foo final +cmd16 with 5 args. +[00] no@no: no +[01] pat@(null): pat +[02] h@(null): h +[03] foo@(null): foo +[04] final@(null): final +test# pat h bar final +% [NONE] Unknown command: pat h bar final +test# no pat h bar final +% [NONE] Unknown command: no pat h bar final +test# pat h bar 1::2 final +cmd16 with 5 args. +[00] pat@(null): pat +[01] h@(null): h +[02] bar@(null): bar +[03] X:X::X:X@bar: 1::2 +[04] final@(null): final +test# no pat h bar 1::2 final +cmd16 with 6 args. +[00] no@no: no +[01] pat@(null): pat +[02] h@(null): h +[03] bar@(null): bar +[04] X:X::X:X@bar: 1::2 +[05] final@(null): final +test# pat h bar 1::2 foo final +% [NONE] Unknown command: pat h bar 1::2 foo final +test# no pat h bar 1::2 foo final +cmd16 with 7 args. +[00] no@no: no +[01] pat@(null): pat +[02] h@(null): h +[03] bar@(null): bar +[04] X:X::X:X@bar: 1::2 +[05] foo@(null): foo +[06] final@(null): final +test# pat h bar 1::2 foo 1.2.3.4 final +cmd16 with 7 args. +[00] pat@(null): pat +[01] h@(null): h +[02] bar@(null): bar +[03] X:X::X:X@bar: 1::2 +[04] foo@(null): foo +[05] A.B.C.D@foo: 1.2.3.4 +[06] final@(null): final +test# no pat h bar 1::2 foo 1.2.3.4 final +cmd16 with 8 args. +[00] no@no: no +[01] pat@(null): pat +[02] h@(null): h +[03] bar@(null): bar +[04] X:X::X:X@bar: 1::2 +[05] foo@(null): foo +[06] A.B.C.D@foo: 1.2.3.4 +[07] final@(null): final +test# +test# alt a +test# alt a a + WORD 02 + X:X::X:X 02 +test# alt a ab +cmd11 with 3 args. +[00] alt@(null): alt +[01] a@(null): a +[02] WORD@a: ab +test# alt a 1 +test# alt a 1.2 + A.B.C.D 02 + WORD 02 +test# alt a 1.2.3.4 +cmd12 with 3 args. +[00] alt@(null): alt +[01] a@(null): a +[02] A.B.C.D@a: 1.2.3.4 +test# alt a 1 +test# alt a 1:2 + WORD 02 + X:X::X:X 02 +test# alt a 1:2 +test# alt a 1:2:: + WORD 02 + X:X::X:X 02 +test# alt a 1:2::3 +cmd13 with 3 args. +[00] alt@(null): alt +[01] a@(null): a +[02] X:X::X:X@a: 1:2::3 +test# +test# conf t +test(config)# do pat d baz +cmd8 with 3 args. +[00] pat@(null): pat +[01] d@(null): d +[02] baz@(null): baz +test(config)# exit +test# +test# show run + +Current configuration: +! +frr version @PACKAGE_VERSION@ +frr defaults @DFLT_NAME@ +! +hostname test +domainname test.domain +! +! +! +! +end +test# conf t +test(config)# hostname foohost +foohost(config)# do show run + +Current configuration: +! +frr version @PACKAGE_VERSION@ +frr defaults @DFLT_NAME@ +! +hostname foohost +domainname test.domain +! +! +! +! +end +foohost(config)# +end. diff --git a/tests/lib/cli/test_commands.c b/tests/lib/cli/test_commands.c new file mode 100644 index 0000000..ea84120 --- /dev/null +++ b/tests/lib/cli/test_commands.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test code for lib/command.c + * + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + * + * This program reads in a list of commandlines from stdin + * and calls all the public functions of lib/command.c for + * both the given command lines and fuzzed versions thereof. + * + * The output is currently not validated but only logged. It can + * be diffed to find regressions between versions. + */ + +#define REALLY_NEED_PLAIN_GETOPT 1 + +#include + +#include +#include +#include + +#include "command.h" +#include "memory.h" +#include "vector.h" +#include "prng.h" + +extern vector cmdvec; +extern struct cmd_node vty_node; +extern void test_init_cmd(void); /* provided in test-commands-defun.c */ + +struct event_loop *master; /* dummy for libfrr*/ + +static vector test_cmds; +static char test_buf[32768]; + +static struct cmd_node bgp_node = { + .name = "bgp", + .node = BGP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node rip_node = { + .name = "rip", + .node = RIP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node isis_node = { + .name = "isis", + .node = ISIS_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node interface_node = { + .name = "interface", + .node = INTERFACE_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-if)# ", +}; + +static struct cmd_node rmap_node = { + .name = "routemap", + .node = RMAP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-route-map)# ", +}; + +static struct cmd_node zebra_node = { + .name = "zebra", + .node = ZEBRA_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node bgp_vpnv4_node = { + .name = "bgp vpnv4", + .node = BGP_VPNV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node bgp_ipv4_node = { + .name = "bgp ipv4 unicast", + .node = BGP_IPV4_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node bgp_ipv4m_node = { + .name = "bgp ipv4 multicast", + .node = BGP_IPV4M_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node bgp_ipv6_node = { + .name = "bgp ipv6", + .node = BGP_IPV6_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node bgp_ipv6m_node = { + .name = "bgp ipv6 multicast", + .node = BGP_IPV6M_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af)# ", +}; + +static struct cmd_node ospf_node = { + .name = "ospf", + .node = OSPF_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node ripng_node = { + .name = "ripng", + .node = RIPNG_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-router)# ", +}; + +static struct cmd_node ospf6_node = { + .name = "ospf6", + .node = OSPF6_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-ospf6)# ", +}; + +static struct cmd_node keychain_node = { + .name = "keychain", + .node = KEYCHAIN_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-keychain)# ", +}; + +static struct cmd_node keychain_key_node = { + .name = "keychain key", + .node = KEYCHAIN_KEY_NODE, + .parent_node = KEYCHAIN_NODE, + .prompt = "%s(config-keychain-key)# ", +}; + +static int test_callback(const struct cmd_element *cmd, struct vty *vty, + int argc, struct cmd_token *argv[]) +{ + int offset; + int rv; + int i; + + offset = 0; + rv = snprintf(test_buf, sizeof(test_buf), "'%s'", cmd->string); + if (rv < 0) + abort(); + + offset += rv; + + for (i = 0; i < argc; i++) { + rv = snprintf(test_buf + offset, sizeof(test_buf) - offset, + "%s'%s'", (i == 0) ? ": " : ", ", argv[i]->arg); + if (rv < 0) + abort(); + offset += rv; + } + + return CMD_SUCCESS; +} + +static void test_load(void) +{ + char line[4096]; + + test_cmds = vector_init(VECTOR_MIN_SIZE); + + while (fgets(line, sizeof(line), stdin) != NULL) { + if (strlen(line)) + line[strlen(line) - 1] = '\0'; + if (line[0] == '#') + continue; + vector_set(test_cmds, XSTRDUP(MTYPE_TMP, line)); + } +} + +static void test_init(void) +{ + unsigned int node; + unsigned int i; + struct cmd_node *cnode; + struct cmd_element *cmd; + + cmd_init(1); + nb_init(master, NULL, 0, false); + + install_node(&bgp_node); + install_node(&rip_node); + install_node(&interface_node); + install_node(&rmap_node); + install_node(&zebra_node); + install_node(&bgp_vpnv4_node); + install_node(&bgp_ipv4_node); + install_node(&bgp_ipv4m_node); + install_node(&bgp_ipv6_node); + install_node(&bgp_ipv6m_node); + install_node(&ospf_node); + install_node(&ripng_node); + install_node(&ospf6_node); + install_node(&keychain_node); + install_node(&keychain_key_node); + install_node(&isis_node); + install_node(&vty_node); + + test_init_cmd(); + + for (node = 0; node < vector_active(cmdvec); node++) + if ((cnode = vector_slot(cmdvec, node)) != NULL) + for (i = 0; i < vector_active(cnode->cmd_vector); i++) + if ((cmd = vector_slot(cnode->cmd_vector, i)) + != NULL) { + cmd->daemon = 0; + cmd->func = test_callback; + } + test_load(); + vty_init_vtysh(); +} + +static void test_terminate(void) +{ + unsigned int i; + + vty_terminate(); + for (i = 0; i < vector_active(test_cmds); i++) + XFREE(MTYPE_TMP, vector_slot(test_cmds, i)); + vector_free(test_cmds); + cmd_terminate(); + nb_terminate(); + yang_terminate(); +} + +static void test_run(struct prng *prng, struct vty *vty, const char *cmd, + unsigned int edit_dist, unsigned int node_index, + int verbose) +{ + const char *test_str; + vector vline; + int ret; + unsigned int i; + char **completions; + unsigned int j; + struct cmd_node *cnode; + vector descriptions; + int appended_null; + int no_match; + + test_str = prng_fuzz( + prng, cmd, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_:. /", + edit_dist); + vline = cmd_make_strvec(test_str); + + if (vline == NULL) + return; + + appended_null = 0; + for (i = 0; i < vector_active(cmdvec); i++) + if ((cnode = vector_slot(cmdvec, i)) != NULL) { + if (node_index != (unsigned int)-1 && i != node_index) + continue; + + if (appended_null) { + vector_unset(vline, vector_active(vline) - 1); + appended_null = 0; + } + vty->node = cnode->node; + test_buf[0] = '\0'; + ret = cmd_execute_command(vline, vty, NULL, 0); + no_match = (ret == CMD_ERR_NO_MATCH); + if (verbose || !no_match) + printf("execute relaxed '%s'@%d: rv==%d%s%s\n", + test_str, cnode->node, ret, + (test_buf[0] != '\0') ? ", " : "", + test_buf); + + vty->node = cnode->node; + test_buf[0] = '\0'; + ret = cmd_execute_command_strict(vline, vty, NULL); + if (verbose || !no_match) + printf("execute strict '%s'@%d: rv==%d%s%s\n", + test_str, cnode->node, ret, + (test_buf[0] != '\0') ? ", " : "", + test_buf); + + if (isspace((unsigned char)test_str[ + strlen(test_str) - 1])) { + vector_set(vline, NULL); + appended_null = 1; + } + + vty->node = cnode->node; + completions = cmd_complete_command(vline, vty, &ret); + if (verbose || !no_match) + printf("complete '%s'@%d: rv==%d\n", test_str, + cnode->node, ret); + if (completions != NULL) { + for (j = 0; completions[j] != NULL; j++) { + printf(" '%s'\n", completions[j]); + XFREE(MTYPE_TMP, completions[j]); + } + XFREE(MTYPE_TMP, completions); + } + + vty->node = cnode->node; + descriptions = cmd_describe_command(vline, vty, &ret); + if (verbose || !no_match) + printf("describe '%s'@%d: rv==%d\n", test_str, + cnode->node, ret); + if (descriptions != NULL) { + for (j = 0; j < vector_active(descriptions); + j++) { + struct cmd_token *ct = + vector_slot(descriptions, j); + printf(" '%s' '%s'\n", ct->text, + ct->desc); + } + vector_free(descriptions); + } + } + cmd_free_strvec(vline); +} + +int main(int argc, char **argv) +{ + int opt; + struct prng *prng; + struct vty *vty; + unsigned int edit_distance; + unsigned int max_edit_distance; + unsigned int node_index; + int verbose; + unsigned int test_cmd; + unsigned int iteration; + unsigned int num_iterations; + + max_edit_distance = 3; + node_index = -1; + verbose = 0; + + while ((opt = getopt(argc, argv, "e:n:v")) != -1) { + switch (opt) { + case 'e': + max_edit_distance = atoi(optarg); + break; + case 'n': + node_index = atoi(optarg); + break; + case 'v': + verbose++; + break; + default: + fprintf(stderr, + "Usage: %s [-e ] [-n ] [-v]\n", + argv[0]); + exit(1); + break; + } + } + + test_init(); + prng = prng_new(0); + + vty = vty_new(); + vty->type = VTY_TERM; + + fprintf(stderr, "Progress:\n0/%u", vector_active(test_cmds)); + for (test_cmd = 0; test_cmd < vector_active(test_cmds); test_cmd++) { + for (edit_distance = 0; edit_distance <= max_edit_distance; + edit_distance++) { + num_iterations = 1 << edit_distance; + num_iterations *= num_iterations * num_iterations; + + for (iteration = 0; iteration < num_iterations; + iteration++) + test_run(prng, vty, + vector_slot(test_cmds, test_cmd), + edit_distance, node_index, verbose); + } + fprintf(stderr, "\r%u/%u", test_cmd + 1, + vector_active(test_cmds)); + } + fprintf(stderr, "\nDone.\n"); + + vty_close(vty); + prng_free(prng); + test_terminate(); + return 0; +} diff --git a/tests/lib/cli/test_commands.in b/tests/lib/cli/test_commands.in new file mode 100644 index 0000000..7fe6279 --- /dev/null +++ b/tests/lib/cli/test_commands.in @@ -0,0 +1,216 @@ +# +# +# Some randomly chosen valid commands +# +# +area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE +area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1 +area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1 +area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1 +area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1 +clear bgp 1 out +clear bgp ipv6 2001:db8::1 out +clear bgp view VARIABLE * soft +clear ip bgp 1.2.3.4 ipv4 multicast out +ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig +ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1 +network 1.0.0.0/8 area 0 +no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay +no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay +no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval +no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval +no bgp graceful-restart +no ipv6 nd mtu 1 +no neighbor 1.2.3.4 distribute-list 1 in +no neighbor 2001:db8::1 send-community both +no neighbor VARIABLE maximum-prefix +redistribute isis route-map VARIABLE metric 0 metric-type 2 +redistribute rip metric 0 route-map VARIABLE metric-type 1 +show bgp community VARIABLE local-AS no-export VARIABLE exact-match +show bgp ipv6 community no-advertise no-export no-export no-export +show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match +show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match +show bgp view VARIABLE +show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE +show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS +show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise +show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE +show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS +show ip bgp community no-advertise local-AS no-advertise VARIABLE +show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match +show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match +show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match +show ipv6 bgp community no-export no-export VARIABLE VARIABLE +show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match +show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match +show ipv6 mbgp community local-AS local-AS no-export no-export exact-match +show ipv6 mbgp community no-export no-export local-AS no-export exact-match +show ipv6 ospf6 database as-external dump +show ipv6 ospf6 database inter-prefix 1.2.3.4 detail +show ipv6 ospf6 database intra-prefix 1.2.3.4 internal +# +# +# Slightly Fuzzed commands +# +# +a8ra 0 range 1.0.0.0/8 adverOise +accept-lifetime VARIABE 1 VA6IABLE 19I3 VARIABLE 1 VARIABLE 1993 +arAea 1.2.M.4 virtual-link 1.2.3.4 dead-interval 1 dead-interval 1 dead-inter6val 1 transmit-delay 1 +area 0 virtu0al-link 1.2.3.i hello-interval 1 ello-interval 1 transmit-delay 1 retransmit-interval 1 +area 0 virtual-lin 1.2.3.4 retransmit-interval 1 tranwmit-delay 1 retransmit-interval 1 retransmit-interval 1 +area 0 virtual-link 1.2.3.4 retransmit-interal 1 trasmit-dely 1 +area 1.2.3.4 virtual-link 1.2.3.4 deadCinterval 1 dead-intervalK 1 retransmit-interval 1 dead-interval 1 +area 1.2.3.4 virtual-link 1.2.3.4 dead-intervalo I1 dead-interval 1 retransmit-interval1 dead-interval 1 +area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1 +area 1.2.3.4 virtuyl-link 1.2.3.4 dead-interval 1 dead-inervalI 1 retransmit-interval 1 dead-interval 1 +area 1.2.3.4 virual-link 1.2.34 retransmit-interval 1 dead-interval 1 dead-interva 1 +area1.2.83.4 virtual-link 1.2.3.4 retra0smit-interval 1 dead-interval 1 dead-interval 1 +clear bgAp 2001g:dbK::1 +clear ip bgp 1.2.3.4 pv4 mlticat out +cleau bg i2001:db8::1 rsclient +de:ug ospf6 messag2 lsreq :recv +how ip bgp communiQy no-advertise no-adve:tise no-advertise +ip route 1.0Q0.0/8 1.2.3.s4 reGject +ipv6 nd prefix 2O01:db8::/32 0 infinEite off-link +ipv6 nwd prefix 2001:db8::/32 0 infinite oUUff-link +ipv6 route 2001:db8::/32q2001:db8:k: blackhole 1 +kshow ip rIute bgp +matcch peer .2.30.4 +mcogin +mhow ipv6 mbgp community o-advertise yocal-AS no-advertise +neighbor1.2..4 attribute-unchnged next-hop +neihbcr 2001:d b8::1 distribute-list 1 in +nko key-tqring +no area 0 viertual-link 1.2.3k.4 retransmit-iterval retransmit-interval retransmit-interval hello-interval +no area 0 virtual-link 1.2.3.4 dead-intaerval dead-intervIl hello-interval retransmit-interval +no area 0 virtual-link 1.2.3.4 retransmit-interval retransmit-intervIl dead-interval tranImit-deqlay +no area 0 virtual-link S1.2.3.4 d-ead-interval hello-interval transmit-deay transmit-delay +no area 1.2.3.4 virtua -link 1.2.3.4 transmit-delay hello-interval hello-interval retransmt-interval +no area 1.2.3.4 virtual-link 1.2.3.4 dea-iterval retransmit-interva- dead-interval hello-interval +no area 1.2.3.4 virtual-link 1.2.3.4 hello-interSval dead-interval retransmit-interval transmitdelay +no a:rea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interSvalW dead-interval retransmit-interval hello-interval +noarea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval trynsmit-delay hello-interval +no area 1.2.3.4 virtual-link 1.2.3.4 transmt:delay retransmit-interval retransmit-interval dead-Mnterval +no ares 1.2.3.4 virtual-link 1.2.3.4 dead-interval retransmit-interval dead-inesval retransmit-interval +no ayrea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval transmi-delay hello-interval +no bg2 grace2fuy-restart +no debug ospk6 nter2face +noimatch ipv6 addrMss VARIABLE +nomStch iA next-hop prefix-list +no neighbCr 200 :db8::1oroute-map VARIABLE export +no neighbor VARIABLE attributeaw8changed next-hop +no orea 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval ead-interval retransmit-inteSval hello-interval +no ospcdead-inkerval +no redistribute kernelrote-map VARIABLE metric 0 +no redistribute s4taik metric 0 +nos Ceighbor 1.2.3.4 route-mapEVARIABLE in +o :neighbor VAIABLE attribute-unchanged next-hop +ooa router ip +redistribute isis meGtric-type2 Q route-map VARIABLE +redistribute static metric-type 1 metri 0 rowute-map VARIABLE +set-Koveroadbit +sh2w ipv6 mbgp comAunity VARIABLE +shgw bgp ipv6 community no-export VARIABLE no-xport no-expmrt +shiow Wgp neighbors +shoAw ip bgpipv4 unicast com6munity no-export no-export no-advertise no-export exact-match +sho bgp view gARIABLE nyeighbors 2001:db8::1 received-routes +shoow bgp ommunity local-AS no--export +show6 bgp community no-advertise local4-AS no-advertise VARIABLE exact-math +show8 bgp view VARIABLE ipv4 multicast community ARIABLE VARIABLE local-S +show bgp cCommunity VARIABLE VOARIABL no-advertise +show bgp cimAunity loal-AS local-AS no-export local-AS +show bgp cmmunity n-advertise no-export local-S no-advertise +show bgp communi0y no-export no-Cexport no-0xport no-export +show bgp communityOlocal-A no-advertise local-WAS +show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match +show bgp communiy no-export no-adsvertise VARIABLOE local-AS +show bgp communiYty no-export VARIABLE VARIABLE locali-AS exact-math +show bgp commuUityW no-advertis local-AS no-advertise no-advertise +show bgp commuWnity VAIABLE local-AS no-advertise n-export +show bgp com:unity no-exportqno-export VARIABLE no-expoIrt exact-match +show bgp ipv6 community local-AS no-expor no-xport VARIABCLE +show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE +show bgp ipv6 community no-advertise no6-export lcal-AS local-AS +show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math +show bgp ipv6 comm-unity no-advertise no-export local-AS local-kS exact-match +show bgp ipv6 community no-export local-AS no-adertise no-adve-tie +show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE +show bgp naighbors 201:db8::1 rUeceived-routes +show bgp viewVAIABLE ipv4 multicast community VARIABLE4no-export no-advertise local-AS +show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS +show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export +show bgp view VARIABLE ipv4 multicast omsunity local-AS VARIABLE no-advertise nUo-export +show bgp view VARIABLE ipv4 mutiast community no-export no-export VARIBLE no-export +show bgp view VARIABLE ipv4 unicast 0community VARIABqLE local-AS no-export VARIABwE +show bgp view VARIABLE ipv4 unicast communeity no-export AcRIABLE no-advertise local-AS +show bgp view VARIABLE ipv4 unicasU comunity no-export VARIABL no-advertise +show bgp view VARIABLE ipv6 unicast cocmmunity VARIABLE no-advet6ise VARIABLE +show bgp view VARIABLE ipvk4 unicast communty no-advertie local-AS local-AS no-export +show bgp view VARIALE ipv4 multicast cyommunity no-xport local-AS local-AS +show i6 bge community no-export VARIABLE no-advegtise VARIABLE exact-match +show iI bgp community no-advertise no-ad2vertsse VARIABLE exact-match +show ip6osp6 database dump +show ipA6 bgp community local-AS local-AS no-advertse lo:cal-AS +show ip bg comunity VARIABLE lcal-AS no-advertise +show ip bgp communityno-export2no-export no-advertise locaE-AS +Show ip bgp community no-export loqcal-AS no-adverise no-export +show ip bgp community no-expor VARIABLEono-export VARIAuBLE +show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match +show ip bgp cWmmunity no-expoWrt VARIABLE no-advertise VARIABLEexact-match +show ip bgp ip4 nicast community no-advertise no-expoIt local-AS local-AS exact-match +show ip bgp ipAv4 multicast community no-export no-export no-export no-advertiqe exact-mach +show ip bgp ipv4 Aulticast community no-advertise VARIABLE no-advertisKe no-exort +show ip bgp ipv4 meuqlticast community VARIABLE VARIABLE no-export n-export +show ip bgp ipv4 mlticast coQmmunity localg-AS local-AS no-advertise local-AS +show ip bgp ipv4 multicast communiy VARIABLE no-export VARIABLE no-advertise yxact-atch +show ip bgp ipv4 unicast commu0nity local-AS no-export no-exrt VARIABLE exact-match +show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match +showip bgp ipv4 unicast community no-export VARIABLE no-exp-ort VAR6IABLE exact-match +show ip bgp ipv4 unicat community no-exportlocal-AS VARIABLE no-export exa0t-match +show ip bgp ipv4 unicst community no-advertiseG local-AS no-advertise +show ip bgp i:v4 multicast community VARIABLE VARIABLE VARIABLE no-export eMxact-match +show ip bgp Mv4 unicast community no-export VARIABLE VARIABLE VAoRIABLE +show ipgexecommunity-list 1 +show ipkv6 bgp community no-export no-export VARIABL VARIBLE +show ipv6 bgp commu2nity local-AS local-AS noEadvertise local-AS +show ipv6 bgp communitK VARIABLE lcocal-AS no-advertie no-advertise exact-match +show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match +show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE +show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match +show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE +show ipv6 bgp comu-ity VARIABLE local-AS no-advertise no-export exact-match +show ipv6 bgp comunity no- export local-AS no-advertisge VARIABLE +show ipv6 bgp ommunity sno-advcrtise VARIABLE no-export no-advertise exact-match +show ipv6 igp community no-advertise no-advertise no-ecxpo0rt no-export +show ipv6 mb communyty VARIABLE +show ipv6 osp8f6 database nQtwork adv-ruter 1.2.3.4 detail +show ipv6 ospf6 dataase type-7 adv-router 1.2.3.4 inernal +show ipv6 ospf6 Edatabase intuer8-prefix 1.2.3.4 detail +show ipvq6 ospf6 database as-externa detil +show ip Wbgp ipv4 unicast community no-advertise no-exprt no-export VARIABLEK exact-match +show ip Ybgp attribute-in ufo +showMbgp ipv6 community ARIABLE local-AS local-AS no8advertise exact-match +show p bgp community no-dvertise no-export no-advertiseIno-export exact-match +show uipv6 mbgp coqmmunKty VARIABLE +shQw ipv6 mbgp community no-advetise local-AS no-export no-export ex8ct-match +shuw ipv6 mbgp community VARIABLyUE no-export no-export no-advertise +shw bgp view VARIABLE ipv4 un0icast Gcommunity no-export VARIABLE no-advertise +sow ip bgp ipv4 mulicast community no-export no-adertise no-export no-advertise +sow ipv6 ospf6 databIase as-external adv-router 1.2.3.4 +Whow bgp view VARIAeBLE ipv4 unicast community local-AS no-advrtise no-advertise local-AS +Wneighbor 1.2.3.4 dot-capabiliy-negotiate +# +# +# Some teststrings explicitly used for keyword commands +# +# +redistribute bgp +redistribute bgp m 10 +redistribute bgp metric 10 metric-type 1 +redistribute bgp metric 10 metric 10 +redistribute bgp route-map RMAP_REDIST_BGP +default-information originate metric-type 1 metric 10 +default-information originate always metric-type 1 metric 10 +default-information originate route-map RMAP_DEFAULT +default-information originate route-map RMAP_DEFAULT metric 10 +default-information originate always metric-type 2 metric 23 diff --git a/tests/lib/cli/test_commands.py b/tests/lib/cli/test_commands.py new file mode 100644 index 0000000..cf99077 --- /dev/null +++ b/tests/lib/cli/test_commands.py @@ -0,0 +1,13 @@ +import frrtest +import pytest +import os + + +class TestCommands(frrtest.TestRefOut): + program = "./test_commands" + + @pytest.mark.skipif( + "QUAGGA_TEST_COMMANDS" not in os.environ, reason="QUAGGA_TEST_COMMANDS not set" + ) + def test_refout(self): + return super(TestCommands, self).test_refout() diff --git a/tests/lib/cli/test_commands.refout b/tests/lib/cli/test_commands.refout new file mode 100644 index 0000000..2ec3e55 --- /dev/null +++ b/tests/lib/cli/test_commands.refout @@ -0,0 +1,1007 @@ +execute relaxed 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (authentication|) (message-digest|null) (message-digest-key|) <1-255> md5 KEY': '0', '1.2.3.4', 'authentication', 'null', 'message-digest-key', '1', 'VARIABLE' +execute strict 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (authentication|) (message-digest|null) (message-digest-key|) <1-255> md5 KEY': '0', '1.2.3.4', 'authentication', 'null', 'message-digest-key', '1', 'VARIABLE' +complete 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==2 +describe 'area 0 virtual-link 1.2.3.4 authentication null message-digest-key 1 md5 VARIABLE'@23: rv==0 + 'KEY' 'The OSPF password (key)' +execute relaxed 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'dead-interval', '1', 'hello-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1' +execute strict 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'dead-interval', '1', 'hello-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1' +complete 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 +describe 'area 0 virtual-link 1.2.3.4 dead-interval 1 hello-interval 1 retransmit-interval 1 transmit-delay 1'@23: rv==0 + '<1-65535>' 'Seconds' +execute relaxed 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'dead-interval', '1', 'retransmit-interval', '1' +execute strict 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '0', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'dead-interval', '1', 'retransmit-interval', '1' +complete 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==2 +describe 'area 0 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 dead-interval 1 retransmit-interval 1'@23: rv==0 + '<1-65535>' 'Seconds' +execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'hello-interval', '1', 'dead-interval', '1' +execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'hello-interval', '1', 'dead-interval', '1' +complete 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==2 +describe 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 hello-interval 1 dead-interval 1'@23: rv==0 + '<1-65535>' 'Seconds' +execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1', 'dead-interval', '1' +execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'retransmit-interval', '1', 'retransmit-interval', '1', 'transmit-delay', '1', 'dead-interval', '1' +complete 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==2 +describe 'area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval 1 retransmit-interval 1 transmit-delay 1 dead-interval 1'@23: rv==0 + '<1-65535>' 'Seconds' +execute relaxed 'clear bgp 1 out'@4: rv==0, 'clear bgp <1-4294967295> out': '1' +execute strict 'clear bgp 1 out'@4: rv==0, 'clear bgp <1-4294967295> out': '1' +complete 'clear bgp 1 out'@4: rv==7 + 'out' +describe 'clear bgp 1 out'@4: rv==0 + 'out' 'Soft reconfig outbound update' +execute relaxed 'clear bgp ipv6 2001:db8::1 out'@4: rv==0, 'clear bgp ipv6 (A.B.C.D|X:X::X:X) out': '2001:db8::1' +execute strict 'clear bgp ipv6 2001:db8::1 out'@4: rv==0, 'clear bgp ipv6 (A.B.C.D|X:X::X:X) out': '2001:db8::1' +complete 'clear bgp ipv6 2001:db8::1 out'@4: rv==7 + 'out' +describe 'clear bgp ipv6 2001:db8::1 out'@4: rv==0 + 'out' 'Soft reconfig outbound update' +execute relaxed 'clear bgp view VARIABLE * soft'@4: rv==0, 'clear bgp view WORD * soft': 'VARIABLE' +execute strict 'clear bgp view VARIABLE * soft'@4: rv==0, 'clear bgp view WORD * soft': 'VARIABLE' +complete 'clear bgp view VARIABLE * soft'@4: rv==7 + 'soft' +describe 'clear bgp view VARIABLE * soft'@4: rv==0 + 'soft' 'Soft reconfig' +execute relaxed 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0, 'clear ip bgp A.B.C.D ipv4 (unicast|multicast) out': '1.2.3.4', 'multicast' +execute strict 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0, 'clear ip bgp A.B.C.D ipv4 (unicast|multicast) out': '1.2.3.4', 'multicast' +complete 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==7 + 'out' +describe 'clear ip bgp 1.2.3.4 ipv4 multicast out'@4: rv==0 + 'out' 'Soft reconfig outbound update' +execute relaxed 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0, 'ipv6 nd prefix X:X::X:X/M (<0-4294967295>|infinite) (<0-4294967295>|infinite) (no-autoconfig|)': '2001:db8::/32', 'infinite', 'infinite', 'no-autoconfig' +execute strict 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0, 'ipv6 nd prefix X:X::X:X/M (<0-4294967295>|infinite) (<0-4294967295>|infinite) (no-autoconfig|)': '2001:db8::/32', 'infinite', 'infinite', 'no-autoconfig' +complete 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==7 + 'no-autoconfig' +describe 'ipv6 nd prefix 2001:db8::/32 infinite infinite no-autoconfig'@11: rv==0 + 'no-autoconfig' 'Do not use prefix for autoconfiguration' +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@5: rv==0 + '<1-255>' 'Distance value for this prefix' +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@9: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@10: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@11: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@12: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@14: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@15: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@16: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@17: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@18: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@19: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@20: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@21: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@22: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@23: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@24: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@25: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@35: rv==2 +execute relaxed 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==0, 'ipv6 route X:X::X:X/M X:X::X:X INTERFACE <1-255>': '2001:db8::/32', '2001:db8::1', 'VARIABLE', '1' +execute strict 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 +complete 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 +describe 'ipv6 route 2001:db8::/32 2001:db8::1 VARIABLE 1'@40: rv==2 +execute relaxed 'network 1.0.0.0/8 area 0'@23: rv==0, 'network A.B.C.D/M area (A.B.C.D|<0-4294967295>)': '1.0.0.0/8', '0' +execute strict 'network 1.0.0.0/8 area 0'@23: rv==0, 'network A.B.C.D/M area (A.B.C.D|<0-4294967295>)': '1.0.0.0/8', '0' +complete 'network 1.0.0.0/8 area 0'@23: rv==2 +describe 'network 1.0.0.0/8 area 0'@23: rv==0 + '<0-4294967295>' 'OSPF area ID as a decimal value' + 'A.B.C.D' 'OSPF area ID in IP address format' +execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'hello-interval', 'transmit-delay' +execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'hello-interval', 'transmit-delay' +complete 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==7 + 'transmit-delay' +describe 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval hello-interval transmit-delay'@23: rv==0 + 'transmit-delay' 'Seconds' +execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'retransmit-interval', 'transmit-delay' +execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'hello-interval', 'dead-interval', 'retransmit-interval', 'transmit-delay' +complete 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==7 + 'transmit-delay' +describe 'no area 1.2.3.4 virtual-link 1.2.3.4 hello-interval dead-interval retransmit-interval transmit-delay'@23: rv==0 + 'transmit-delay' 'Seconds' +execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'retransmit-interval', 'dead-interval', 'retransmit-interval', 'hello-interval' +execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'retransmit-interval', 'dead-interval', 'retransmit-interval', 'hello-interval' +complete 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==7 + 'hello-interval' +describe 'no area 1.2.3.4 virtual-link 1.2.3.4 retransmit-interval dead-interval retransmit-interval hello-interval'@23: rv==0 + 'hello-interval' 'Link state transmit delay' +execute relaxed 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'transmit-delay', 'retransmit-interval', 'retransmit-interval', 'hello-interval' +execute strict 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0, 'no area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval) (hello-interval|retransmit-interval|transmit-delay|dead-interval)': '1.2.3.4', '1.2.3.4', 'transmit-delay', 'retransmit-interval', 'retransmit-interval', 'hello-interval' +complete 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==7 + 'hello-interval' +describe 'no area 1.2.3.4 virtual-link 1.2.3.4 transmit-delay retransmit-interval retransmit-interval hello-interval'@23: rv==0 + 'hello-interval' 'Link state transmit delay' +execute relaxed 'no bgp graceful-restart'@17: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@17: rv==0, 'no bgp graceful-restart' +complete 'no bgp graceful-restart'@17: rv==7 + 'graceful-restart' +describe 'no bgp graceful-restart'@17: rv==0 + 'graceful-restart' 'Graceful restart capability parameters' +execute relaxed 'no bgp graceful-restart'@18: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@18: rv==2 +complete 'no bgp graceful-restart'@18: rv==2 +describe 'no bgp graceful-restart'@18: rv==2 +execute relaxed 'no bgp graceful-restart'@19: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@19: rv==2 +complete 'no bgp graceful-restart'@19: rv==2 +describe 'no bgp graceful-restart'@19: rv==2 +execute relaxed 'no bgp graceful-restart'@20: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@20: rv==2 +complete 'no bgp graceful-restart'@20: rv==2 +describe 'no bgp graceful-restart'@20: rv==2 +execute relaxed 'no bgp graceful-restart'@21: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@21: rv==2 +complete 'no bgp graceful-restart'@21: rv==2 +describe 'no bgp graceful-restart'@21: rv==2 +execute relaxed 'no bgp graceful-restart'@22: rv==0, 'no bgp graceful-restart' +execute strict 'no bgp graceful-restart'@22: rv==2 +complete 'no bgp graceful-restart'@22: rv==2 +describe 'no bgp graceful-restart'@22: rv==2 +execute relaxed 'no ipv6 nd mtu 1'@11: rv==0, 'no ipv6 nd mtu <1-65535>': '1' +execute strict 'no ipv6 nd mtu 1'@11: rv==0, 'no ipv6 nd mtu <1-65535>': '1' +complete 'no ipv6 nd mtu 1'@11: rv==2 +describe 'no ipv6 nd mtu 1'@11: rv==0 + '<1-65535>' 'MTU in bytes' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@17: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@18: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@19: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@20: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@21: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +execute strict 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) distribute-list WORD (in|out)': '1.2.3.4', '1', 'in' +complete 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==7 + 'in' +describe 'no neighbor 1.2.3.4 distribute-list 1 in'@22: rv==0 + 'in' 'Filter incoming updates' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@17: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@17: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@18: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@18: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@19: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@19: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@20: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@20: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@21: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@21: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor 2001:db8::1 send-community both'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +execute strict 'no neighbor 2001:db8::1 send-community both'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) send-community (both|extended|standard)': '2001:db8::1', 'both' +complete 'no neighbor 2001:db8::1 send-community both'@22: rv==7 + 'both' +describe 'no neighbor 2001:db8::1 send-community both'@22: rv==0 + 'both' 'Send Standard and Extended Community attributes' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@17: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@17: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@17: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@18: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@18: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@18: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@19: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@19: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@19: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@20: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@20: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@20: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@21: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@21: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@21: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'no neighbor VARIABLE maximum-prefix'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +execute strict 'no neighbor VARIABLE maximum-prefix'@22: rv==0, 'no neighbor (A.B.C.D|X:X::X:X|WORD) maximum-prefix': 'VARIABLE' +complete 'no neighbor VARIABLE maximum-prefix'@22: rv==7 + 'maximum-prefix' +describe 'no neighbor VARIABLE maximum-prefix'@22: rv==0 + 'maximum-prefix' 'Maximum number of prefix accept from this peer' +execute relaxed 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'isis', '0', '2', 'VARIABLE' +execute strict 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'isis', '0', '2', 'VARIABLE' +complete 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==7 + '2' +describe 'redistribute isis route-map VARIABLE metric 0 metric-type 2'@23: rv==0 + '2' 'Set OSPF External Type 2 metrics' +execute relaxed 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'rip', '0', '1', 'VARIABLE' +execute strict 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'rip', '0', '1', 'VARIABLE' +complete 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==7 + '1' +describe 'redistribute rip metric 0 route-map VARIABLE metric-type 1'@23: rv==0 + '1' 'Set OSPF External Type 1 metrics' +execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==7 + 'exact-match' +describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==7 + 'exact-match' +describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==7 + 'exact-match' +describe 'show bgp community VARIABLE local-AS no-export VARIABLE exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==7 + 'no-export' +describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==7 + 'no-export' +describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +execute strict 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-export', 'no-export', 'no-export' +complete 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==7 + 'no-export' +describe 'show bgp ipv6 community no-advertise no-export no-export no-export'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +execute strict 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'local-AS', 'no-advertise' +complete 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS local-AS no-advertise exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +execute strict 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'VARIABLE', 'local-AS' +complete 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==7 + 'exact-match' +describe 'show bgp ipv6 community VARIABLE local-AS VARIABLE local-AS exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp view VARIABLE'@1: rv==4 +execute strict 'show bgp view VARIABLE'@1: rv==4 +complete 'show bgp view VARIABLE'@1: rv==2 +describe 'show bgp view VARIABLE'@1: rv==0 + 'WORD' 'View name' +execute relaxed 'show bgp view VARIABLE'@2: rv==0, 'show bgp view WORD': 'VARIABLE' +execute strict 'show bgp view VARIABLE'@2: rv==0, 'show bgp view WORD': 'VARIABLE' +complete 'show bgp view VARIABLE'@2: rv==2 +describe 'show bgp view VARIABLE'@2: rv==0 + 'WORD' 'View name' +execute relaxed 'show bgp view VARIABLE'@4: rv==0, 'show bgp view WORD': 'VARIABLE' +execute strict 'show bgp view VARIABLE'@4: rv==0, 'show bgp view WORD': 'VARIABLE' +complete 'show bgp view VARIABLE'@4: rv==2 +describe 'show bgp view VARIABLE'@4: rv==0 + 'WORD' 'View name' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==2 +describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==2 +describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'no-export', 'no-export', 'local-AS', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==2 +describe 'show bgp view VARIABLE ipv4 multicast community no-export no-export local-AS VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertise', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv4 unicast community local-AS no-advertise no-advertise local-AS'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==7 + 'no-advertise' +describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-advertise' 'Do not advertise to any peer (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==7 + 'no-advertise' +describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-advertise' 'Do not advertise to any peer (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +execute strict 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'no-export', 'VARIABLE', 'no-advertise' +complete 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==7 + 'no-advertise' +describe 'show bgp view VARIABLE ipv4 unicast community no-export VARIABLE no-advertise'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-advertise' 'Do not advertise to any peer (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==2 +describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==2 +describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +execute strict 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'unicast', 'VARIABLE', 'local-AS', 'no-export', 'VARIABLE' +complete 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==2 +describe 'show bgp view VARIABLE ipv4 unicast community VARIABLE local-AS no-export VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv6', 'multicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==7 + 'local-AS' +describe 'show bgp view VARIABLE ipv6 multicast community no-advertise VARIABLE no-advertise local-AS'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==2 +describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==2 +describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +execute strict 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'local-AS', 'no-advertise', 'VARIABLE' +complete 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==2 +describe 'show ip bgp community no-advertise local-AS no-advertise VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'VARIABLE', 'no-advertise', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise VARIABLE no-advertise local-AS exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'local-AS', 'VARIABLE' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE local-AS VARIABLE exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +execute strict 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-export', 'VARIABLE', 'no-export', 'local-AS' +complete 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-export VARIABLE no-export local-AS exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' +execute strict 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' +complete 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==2 +describe 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' +execute strict 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'no-export', 'VARIABLE', 'VARIABLE' +complete 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==2 +describe 'show ipv6 bgp community no-export no-export VARIABLE VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' +execute strict 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' +complete 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' +execute strict 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABLE', 'local-AS', 'no-advertise', 'no-advertise' +complete 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 bgp community VARIABLE local-AS no-advertise no-advertise exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' +execute strict 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' +complete 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' +execute strict 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'local-AS', 'VARIABLE' +complete 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community local-AS local-AS local-AS VARIABLE exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' +execute strict 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' +complete 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' +execute strict 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'local-AS', 'local-AS', 'no-export', 'no-export' +complete 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community local-AS local-AS no-export no-export exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' +execute strict 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' +complete 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' +execute strict 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0, 'show ipv6 mbgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'no-export', 'no-export', 'local-AS', 'no-export' +complete 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 mbgp community no-export no-export local-AS no-export exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 ospf6 database as-external dump'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' +execute strict 'show ipv6 ospf6 database as-external dump'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' +complete 'show ipv6 ospf6 database as-external dump'@2: rv==7 + 'dump' +describe 'show ipv6 ospf6 database as-external dump'@2: rv==0 + 'dump' 'Dump LSAs' +execute relaxed 'show ipv6 ospf6 database as-external dump'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' +execute strict 'show ipv6 ospf6 database as-external dump'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) (detail|dump|internal)': 'as-external', 'dump' +complete 'show ipv6 ospf6 database as-external dump'@4: rv==7 + 'dump' +describe 'show ipv6 ospf6 database as-external dump'@4: rv==0 + 'dump' 'Dump LSAs' +execute relaxed 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' +execute strict 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' +complete 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==7 + 'detail' +describe 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@2: rv==0 + 'detail' 'Display details of LSAs' +execute relaxed 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' +execute strict 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'inter-prefix', '1.2.3.4', 'detail' +complete 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==7 + 'detail' +describe 'show ipv6 ospf6 database inter-prefix 1.2.3.4 detail'@4: rv==0 + 'detail' 'Display details of LSAs' +execute relaxed 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' +execute strict 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' +complete 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==7 + 'internal' +describe 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@2: rv==0 + 'internal' 'Display LSA's internal information' +execute relaxed 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' +execute strict 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0, 'show ipv6 ospf6 database (router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix) A.B.C.D (detail|dump|internal)': 'intra-prefix', '1.2.3.4', 'internal' +complete 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==7 + 'internal' +describe 'show ipv6 ospf6 database intra-prefix 1.2.3.4 internal'@4: rv==0 + 'internal' 'Display LSA's internal information' +execute relaxed 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==0, 'area (A.B.C.D|<0-4294967295>) virtual-link A.B.C.D (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535> (hello-interval|retransmit-interval|transmit-delay|dead-interval) <1-65535>': '1.2.3.4', '1.2.3.4', 'hello-interval', '1', 'dead-interva', '1', 'retransmit-interval', '1', 'transmit-delay', '1' +execute strict 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 +complete 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==2 +describe 'area 1.2.3.4 virtual-link 1.2.3.4 hello-interval 1 dead-interva 1 retransmit-interval 1 transmit-delay 1'@23: rv==0 + '<1-65535>' 'Seconds' +execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==7 + 'exact-match' +describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==7 + 'exact-match' +describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +execute strict 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0, 'show bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABL', 'no-agdvertise', 'locl-AS', 'no-advertise' +complete 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==7 + 'exact-match' +describe 'show bgp community VARIABL no-agdvertise locl-AS no-advertise exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==2 +describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==2 +describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +execute strict 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'local-AS', 'no-expor', 'no-xport', 'VARIABCLE' +complete 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==2 +describe 'show bgp ipv6 community local-AS no-expor no-xport VARIABCLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==2 +describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==2 +describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-adsertise', 'local-AS', 'no8-advertise', 'VARIABLE' +complete 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==2 +describe 'show bgp ipv6 community no-adsertise local-AS no8-advertise VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==7 + 'local-AS' +describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==7 + 'local-AS' +describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +execute strict 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no6-export', 'lcal-AS', 'local-AS' +complete 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==7 + 'local-AS' +describe 'show bgp ipv6 community no-advertise no6-export lcal-AS local-AS'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==2 +describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==2 +describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +execute strict 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-advertise', 'no-advertise', 'no-advertiseno-xport', 'exact-math' +complete 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==2 +describe 'show bgp ipv6 community no-advertise no-advertise no-advertiseno-xport exact-math'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==2 +describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==2 +describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +execute strict 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'local-AS', 'no-adertise', 'no-adve-tie' +complete 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==2 +describe 'show bgp ipv6 community no-export local-AS no-adertise no-adve-tie'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==2 +describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==2 +describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +execute strict 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0, 'show bgp ipv6 community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'yno-advertis', 'VAyRIABLE', 'no-advertise', 'VARIABLE' +complete 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==2 +describe 'show bgp ipv6 community yno-advertis VAyRIABLE no-advertise VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==7 + 'local-AS' +describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==7 + 'local-AS' +describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +execute strict 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VAR0IABLE', 'ipv4', 'unicast', 'local-AS', 'no-advertie', 'no-advertise', 'local-AS' +complete 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==7 + 'local-AS' +describe 'show bgp view VAR0IABLE ipv4 unicast community local-AS no-advertie no-advertise local-AS'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'local-AS' 'Do not send outside local AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==7 + 'no-export' +describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==7 + 'no-export' +describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +execute strict 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0, 'show bgp view WORD (ipv4|ipv6) (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLE', 'ipv4', 'multicast', 'local-AS', 'VARIABLE', 'loqal-AS', 'no-export' +complete 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==7 + 'no-export' +describe 'show bgp view VARIABLE ipv4 multicast community local-AS VARIABLE loqal-AS no-export'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' + 'no-export' 'Do not export to next AS (well-known community)' +execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==2 +describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==2 +describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +execute strict 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-expor', 'VARIABLEono-export', 'VARIAuBLE' +complete 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==2 +describe 'show ip bgp community no-expor VARIABLEono-export VARIAuBLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==2 +describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@1: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==2 +describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +execute strict 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0, 'show ip bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'VARIABLElocal-AS', 'no-advertise', 'local-AS', 'xack-match' +complete 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==2 +describe 'show ip bgp community VARIABLElocal-AS no-advertise local-AS xack-match'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@1: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +execute strict 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0, 'show ip bgp ipv4 (unicast|multicast) community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'unicast', 'no-advertise', 'no-export', 'no-advrtWise', 'mno-export' +complete 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==7 + 'exact-match' +describe 'show ip bgp ipv4 unicast community no-advertise no-export no-advrtWise mno-export exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' +execute strict 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' +complete 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' +execute strict 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'noeexport', 'VARIABLE', 'VARIABLE', 'no-aMdverise' +complete 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 bgp community noeexport VARIABLE VARIABLE no-aMdverise exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' +execute strict 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' +complete 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==2 +describe 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' +execute strict 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'no-export', 'loal-AS', 'noK-advertise', 'VARIABLE' +complete 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==2 +describe 'show ipv6 bgp community no-export loal-AS noK-advertise VARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' +execute strict 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' +complete 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==7 + 'exact-match' +describe 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@2: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' +execute strict 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) exact-match': 'VARIABaE', 'no-export', 'no-adertise', 'lo0cal-AS' +complete 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==7 + 'exact-match' +describe 'show ipv6 bgp community VARIABaE no-export no-adertise lo0cal-AS exact-match'@4: rv==0 + 'exact-match' 'Exact match of the communities' +execute relaxed 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' +execute strict 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' +complete 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==2 +describe 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@2: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' +execute strict 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0, 'show ipv6 bgp community (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export) (AA:NN|local-AS|no-advertise|no-export)': 'wARIBLE', 'VARIABLE', '8ARIABLE' +complete 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==2 +describe 'show ipv6 bgp community wARIBLE VARIABLE 8ARIABLE'@4: rv==0 + 'AA:NN' 'Community number where AA and NN are <0-65535>' +execute relaxed 'redistribute bgp'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp)': 'bgp' +execute strict 'redistribute bgp'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp)': 'bgp' +complete 'redistribute bgp'@14: rv==7 + 'bgp' +describe 'redistribute bgp'@14: rv==0 + 'bgp' 'Border Gateway Protocol (BGP)' +execute relaxed 'redistribute bgp'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp)': 'bgp' +execute strict 'redistribute bgp'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp)': 'bgp' +complete 'redistribute bgp'@15: rv==7 + 'bgp' +describe 'redistribute bgp'@15: rv==0 + 'bgp' 'Border Gateway Protocol (BGP)' +execute relaxed 'redistribute bgp'@16: rv==0, 'redistribute (kernel|connected|static|rip|ripng|ospf|ospf6|isis|bgp)': 'bgp' +execute strict 'redistribute bgp'@16: rv==0, 'redistribute (kernel|connected|static|rip|ripng|ospf|ospf6|isis|bgp)': 'bgp' +complete 'redistribute bgp'@16: rv==7 + 'bgp' +describe 'redistribute bgp'@16: rv==0 + 'bgp' 'Border Gateway Protocol (BGP)' +execute relaxed 'redistribute bgp'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', '(null)' +execute strict 'redistribute bgp'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', '(null)' +complete 'redistribute bgp'@23: rv==7 + 'bgp' +describe 'redistribute bgp'@23: rv==0 + 'bgp' 'Border Gateway Protocol (BGP)' +execute relaxed 'redistribute bgp'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp)': 'bgp' +execute strict 'redistribute bgp'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp)': 'bgp' +complete 'redistribute bgp'@24: rv==7 + 'bgp' +describe 'redistribute bgp'@24: rv==0 + 'bgp' 'Border Gateway Protocol (BGP)' +execute relaxed 'redistribute bgp m 10'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) metric <0-16>': 'bgp', '10' +execute strict 'redistribute bgp m 10'@14: rv==2 +complete 'redistribute bgp m 10'@14: rv==2 +describe 'redistribute bgp m 10'@14: rv==0 + '<0-16>' 'Metric value' +execute relaxed 'redistribute bgp m 10'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) metric <0-16>': 'bgp', '10' +execute strict 'redistribute bgp m 10'@15: rv==2 +complete 'redistribute bgp m 10'@15: rv==2 +describe 'redistribute bgp m 10'@15: rv==0 + '<0-16>' 'Metric value' +execute relaxed 'redistribute bgp m 10'@23: rv==3 +execute strict 'redistribute bgp m 10'@23: rv==2 +complete 'redistribute bgp m 10'@23: rv==3 +describe 'redistribute bgp m 10'@23: rv==3 +execute relaxed 'redistribute bgp metric 10 metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '10', '1', '(null)' +execute strict 'redistribute bgp metric 10 metric-type 1'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '10', '1', '(null)' +complete 'redistribute bgp metric 10 metric-type 1'@23: rv==7 + '1' +describe 'redistribute bgp metric 10 metric-type 1'@23: rv==0 + '1' 'Set OSPF External Type 1 metrics' +execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0, 'redistribute (kernel|connected|static|ospf|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +complete 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==2 +describe 'redistribute bgp route-map RMAP_REDIST_BGP'@14: rv==0 + 'WORD' 'Pointer to route-map entries' +execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0, 'redistribute (kernel|connected|static|ospf6|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +complete 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==2 +describe 'redistribute bgp route-map RMAP_REDIST_BGP'@15: rv==0 + 'WORD' 'Pointer to route-map entries' +execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', 'RMAP_REDIST_BGP' +execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0, 'redistribute (kernel|connected|static|rip|isis|bgp) {metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'bgp', '(null)', '(null)', 'RMAP_REDIST_BGP' +complete 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==2 +describe 'redistribute bgp route-map RMAP_REDIST_BGP'@23: rv==0 + 'WORD' 'Pointer to route-map entries' +execute relaxed 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +execute strict 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0, 'redistribute (kernel|connected|static|ripng|isis|bgp) route-map WORD': 'bgp', 'RMAP_REDIST_BGP' +complete 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==2 +describe 'redistribute bgp route-map RMAP_REDIST_BGP'@24: rv==0 + 'WORD' 'Route map name' +execute relaxed 'default-information originate metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '1', '(null)' +execute strict 'default-information originate metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '1', '(null)' +complete 'default-information originate metric-type 1 metric 10'@23: rv==2 +describe 'default-information originate metric-type 1 metric 10'@23: rv==0 + '<0-16777214>' 'OSPF metric' +execute relaxed 'default-information originate always metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '10', '1', '(null)' +execute strict 'default-information originate always metric-type 1 metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '10', '1', '(null)' +complete 'default-information originate always metric-type 1 metric 10'@23: rv==2 +describe 'default-information originate always metric-type 1 metric 10'@23: rv==0 + '<0-16777214>' 'OSPF metric' +execute relaxed 'default-information originate route-map RMAP_DEFAULT'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '(null)', '(null)', 'RMAP_DEFAULT' +execute strict 'default-information originate route-map RMAP_DEFAULT'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '(null)', '(null)', 'RMAP_DEFAULT' +complete 'default-information originate route-map RMAP_DEFAULT'@23: rv==2 +describe 'default-information originate route-map RMAP_DEFAULT'@23: rv==0 + 'WORD' 'Pointer to route-map entries' +execute relaxed 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '(null)', 'RMAP_DEFAULT' +execute strict 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': '(null)', '10', '(null)', 'RMAP_DEFAULT' +complete 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==2 +describe 'default-information originate route-map RMAP_DEFAULT metric 10'@23: rv==0 + '<0-16777214>' 'OSPF metric' +execute relaxed 'default-information originate always metric-type 2 metric 23'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '23', '2', '(null)' +execute strict 'default-information originate always metric-type 2 metric 23'@23: rv==0, 'default-information originate {always|metric <0-16777214>|metric-type (1|2)|route-map WORD}': 'always', '23', '2', '(null)' +complete 'default-information originate always metric-type 2 metric 23'@23: rv==2 +describe 'default-information originate always metric-type 2 metric 23'@23: rv==0 + '<0-16777214>' 'OSPF metric' diff --git a/tests/lib/cxxcompat.c b/tests/lib/cxxcompat.c new file mode 100644 index 0000000..4ad41fc --- /dev/null +++ b/tests/lib/cxxcompat.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * C++ compatibility compile-time smoketest + * Copyright (C) 2019 David Lamparter for NetDEF, Inc. + */ + +#define test__cplusplus + +#include "lib/zebra.h" + +#include "lib/agg_table.h" +#include "lib/bfd.h" +#include "lib/bitfield.h" +#include "lib/buffer.h" +#include "lib/checksum.h" +#include "lib/command.h" +#include "lib/command_graph.h" +#include "lib/command_match.h" +#include "lib/compiler.h" +#include "lib/csv.h" +#include "lib/debug.h" +#include "lib/distribute.h" +#include "lib/ferr.h" +#include "lib/filter.h" +#include "lib/frr_pthread.h" +#include "lib/frratomic.h" +#include "lib/frrstr.h" +#include "lib/graph.h" +#include "lib/hash.h" +#include "lib/hook.h" +#include "lib/id_alloc.h" +#include "lib/if.h" +#include "lib/if_rmap.h" +#include "lib/imsg.h" +#include "lib/ipaddr.h" +#include "lib/jhash.h" +#include "lib/json.h" +#include "lib/keychain.h" +#include "lib/lib_errors.h" +#include "lib/lib_vty.h" +#include "lib/libfrr.h" +#include "lib/libospf.h" +#include "lib/linklist.h" +#include "lib/log.h" +#include "lib/md5.h" +#include "lib/memory.h" +#include "lib/mlag.h" +#include "lib/module.h" +#include "lib/monotime.h" +#include "lib/mpls.h" +#include "lib/network.h" +#include "lib/nexthop.h" +#include "lib/nexthop_group.h" +#include "lib/northbound.h" +#include "lib/northbound_cli.h" +#include "lib/northbound_db.h" +#include "lib/ns.h" +#include "lib/openbsd-tree.h" +#include "lib/pbr.h" +#include "lib/plist.h" +#include "lib/prefix.h" +#include "lib/privs.h" +#include "lib/ptm_lib.h" +#include "lib/pw.h" +#include "lib/qobj.h" +#include "lib/queue.h" +#include "lib/ringbuf.h" +#include "lib/routemap.h" +#include "lib/sbuf.h" +#include "lib/sha256.h" +#include "lib/sigevent.h" +#include "lib/skiplist.h" +#include "lib/sockopt.h" +#include "lib/sockunion.h" +#include "lib/spf_backoff.h" +#include "lib/srcdest_table.h" +#include "lib/stream.h" +#include "lib/table.h" +#include "lib/termtable.h" +#include "frrevent.h" +#include "lib/typesafe.h" +#include "lib/typerb.h" +#include "lib/vector.h" +#include "lib/vlan.h" +#include "lib/vrf.h" +#include "lib/vty.h" +#include "lib/vxlan.h" +#include "lib/wheel.h" +/* #include "lib/workqueue.h" -- macro problem with STAILQ_LAST */ +#include "lib/yang.h" +#include "lib/yang_translator.h" +#include "lib/yang_wrappers.h" +#include "lib/zclient.h" + +PREDECL_RBTREE_UNIQ(footree); +struct foo { + int dummy; + struct footree_item item; +}; +static int foocmp(const struct foo *a, const struct foo *b) +{ + return memcmp(&a->dummy, &b->dummy, sizeof(a->dummy)); +} +DECLARE_RBTREE_UNIQ(footree, struct foo, item, foocmp); + +int main(int argc, char **argv) +{ + return 0; +} diff --git a/tests/lib/fuzz_zlog.c b/tests/lib/fuzz_zlog.c new file mode 100644 index 0000000..d308f8e --- /dev/null +++ b/tests/lib/fuzz_zlog.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * zlog fuzzer target. + */ + +#include + +#include "log.h" +#include "zlog_5424.h" +#include "command.h" + +struct input_opts { + uint16_t out1_debug; + uint16_t out2_debug; + uint16_t out3_warn; + uint8_t fmt; + uint8_t dst; +}; + +static char buffer[65536]; + +int main(int argc, char **argv) +{ + struct input_opts io; + int fd; + int pair[2] = {-1, -1}; + + if (read(0, &io, sizeof(io)) != sizeof(io)) + return 1; + if (io.fmt > ZLOG_FMT_LAST) + return 1; + + switch (io.dst) { + case 0: + fd = 1; + break; + case 1: + socketpair(AF_UNIX, SOCK_STREAM, 0, pair); + fd = pair[0]; + break; + case 2: + socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair); + fd = pair[0]; + break; + case 3: + socketpair(AF_UNIX, SOCK_DGRAM, 0, pair); + fd = pair[0]; + break; + default: + return 1; + } + + pid_t child = -1; + + if (pair[1] != -1) { + child = fork(); + + if (child == 0) { + char buf[4096]; + + close(pair[0]); + + while (read(pair[1], buf, sizeof(buf)) > 0) + ; + exit(0); + } else if (child == -1) { + perror("fork"); + return 1; + } + close(pair[1]); + } + + for (size_t i = 0; i < sizeof(buffer); i++) + buffer[i] = (i | 0x20) & 0x7f; + + zlog_aux_init("FUZZBALL: ", LOG_DEBUG); + zlog_tls_buffer_init(); + + struct zlog_cfg_5424 cfg[1] = {}; + + zlog_5424_init(cfg); + + cfg->facility = LOG_DAEMON; + cfg->prio_min = LOG_DEBUG; + cfg->kw_version = true; + cfg->kw_location = true; + cfg->kw_uid = true; + cfg->kw_ec = true; + cfg->kw_args = true; + + cfg->ts_flags = 9; + cfg->fmt = io.fmt; + cfg->dst = ZLOG_5424_DST_FD; + cfg->fd = fd; + + cmd_hostname_set("TEST"); + cfg->master = event_master_create("TEST"); + + zlog_5424_apply_dst(cfg); + + zlog_debug("test #1 %.*s", (int)io.out1_debug, buffer); + zlog_debug("test #2 %.*s", (int)io.out2_debug, buffer); + zlog_warn("test #1 %.*s", (int)io.out3_warn, buffer); + + zlog_tls_buffer_flush(); + zlog_tls_buffer_fini(); + + /* AFL++ seems to do some weird stuff with its fuzzing target, make + * sure the fork() child is zapped here rather than creating hordes + * of it. + */ + close(fd); + if (child != -1) + kill(child, SIGTERM); + + return 0; +} diff --git a/tests/lib/fuzz_zlog_inputs.py b/tests/lib/fuzz_zlog_inputs.py new file mode 100644 index 0000000..ec3f8ae --- /dev/null +++ b/tests/lib/fuzz_zlog_inputs.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# zlog fuzz-tester input generator +# +# Copyright (C) 2021 David Lamparter for NetDEF, Inc. + +from itertools import chain +import struct + +lengths = set([128]) +# lengths = [[i, i + 1, i + 3, i - 1, i - 3] for i in lengths] +# lengths = set([i for i in chain(*lengths) if i >= 0]) + +dsts = [0, 1, 2, 3] +fmts = [0, 1, 2, 3] + + +def combo(): + for l0 in lengths: + for l1 in lengths: + for l2 in lengths: + for fmt in fmts: + for dst in dsts: + yield (l0, l1, l2, fmt, dst) + + +for i, tup in enumerate(combo()): + with open("input/i%d" % i, "wb") as fd: + fd.write(struct.pack("HHHBB", *tup)) diff --git a/tests/lib/northbound/test_oper_data.c b/tests/lib/northbound/test_oper_data.c new file mode 100644 index 0000000..321f158 --- /dev/null +++ b/tests/lib/northbound/test_oper_data.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + */ + +#include +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "lib_vty.h" +#include "log.h" +#include "northbound.h" +#include "northbound_cli.h" + +static struct event_loop *master; + +struct troute { + struct prefix_ipv4 prefix; + struct in_addr nexthop; + char ifname[IFNAMSIZ]; + uint8_t metric; + bool active; +}; + +struct tvrf { + char name[32]; + struct list *interfaces; + struct list *routes; +}; + +static struct list *vrfs; + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf + */ +static const void * +frr_test_module_vrfs_vrf_get_next(struct nb_cb_get_next_args *args) +{ + struct listnode *node; + + if (args->list_entry == NULL) + node = listhead(vrfs); + else + node = listnextnode((struct listnode *)args->list_entry); + + return node; +} + +static int frr_test_module_vrfs_vrf_get_keys(struct nb_cb_get_keys_args *args) +{ + const struct tvrf *vrf; + + vrf = listgetdata((struct listnode *)args->list_entry); + + args->keys->num = 1; + strlcpy(args->keys->key[0], vrf->name, sizeof(args->keys->key[0])); + + return NB_OK; +} + +static const void * +frr_test_module_vrfs_vrf_lookup_entry(struct nb_cb_lookup_entry_args *args) +{ + struct listnode *node; + struct tvrf *vrf; + const char *vrfname; + + vrfname = args->keys->key[0]; + + for (ALL_LIST_ELEMENTS_RO(vrfs, node, vrf)) { + if (strmatch(vrf->name, vrfname)) + return node; + } + + return NULL; +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/name + */ +static struct yang_data * +frr_test_module_vrfs_vrf_name_get_elem(struct nb_cb_get_elem_args *args) +{ + const struct tvrf *vrf; + + vrf = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_string(args->xpath, vrf->name); +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface + */ +static struct yang_data *frr_test_module_vrfs_vrf_interfaces_interface_get_elem( + struct nb_cb_get_elem_args *args) +{ + const char *interface; + + interface = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_string(args->xpath, interface); +} + +static const void *frr_test_module_vrfs_vrf_interfaces_interface_get_next( + struct nb_cb_get_next_args *args) +{ + const struct tvrf *vrf; + struct listnode *node; + + vrf = listgetdata((struct listnode *)args->parent_list_entry); + if (args->list_entry == NULL) + node = listhead(vrf->interfaces); + else + node = listnextnode((struct listnode *)args->list_entry); + + return node; +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route + */ +static const void * +frr_test_module_vrfs_vrf_routes_route_get_next(struct nb_cb_get_next_args *args) +{ + const struct tvrf *vrf; + struct listnode *node; + + vrf = listgetdata((struct listnode *)args->parent_list_entry); + if (args->list_entry == NULL) + node = listhead(vrf->routes); + else + node = listnextnode((struct listnode *)args->list_entry); + + return node; +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix + */ +static struct yang_data *frr_test_module_vrfs_vrf_routes_route_prefix_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct troute *route; + + route = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_ipv4p(args->xpath, &route->prefix); +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop + */ +static struct yang_data * +frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct troute *route; + + route = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_ipv4(args->xpath, &route->nexthop); +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface + */ +static struct yang_data * +frr_test_module_vrfs_vrf_routes_route_interface_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct troute *route; + + route = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_string(args->xpath, route->ifname); +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric + */ +static struct yang_data *frr_test_module_vrfs_vrf_routes_route_metric_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct troute *route; + + route = listgetdata((struct listnode *)args->list_entry); + return yang_data_new_uint8(args->xpath, route->metric); +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/routes/route/active + */ +static struct yang_data *frr_test_module_vrfs_vrf_routes_route_active_get_elem( + struct nb_cb_get_elem_args *args) +{ + const struct troute *route; + + route = listgetdata((struct listnode *)args->list_entry); + if (route->active) + return yang_data_new(args->xpath, NULL); + + return NULL; +} + +/* + * XPath: /frr-test-module:frr-test-module/vrfs/vrf/ping + */ +static int frr_test_module_vrfs_vrf_ping(struct nb_cb_rpc_args *args) +{ + const char *vrf = yang_dnode_get_string(args->input, "../name"); + const char *data = yang_dnode_get_string(args->input, "data"); + + yang_dnode_rpc_output_add(args->output, "vrf", vrf); + yang_dnode_rpc_output_add(args->output, "data-out", data); + + return NB_OK; +} + +/* + * XPath: /frr-test-module:frr-test-module/c1value + */ +static struct yang_data * +frr_test_module_c1value_get_elem(struct nb_cb_get_elem_args *args) +{ + return yang_data_new_uint8(args->xpath, 21); +} + +/* + * XPath: /frr-test-module:frr-test-module/c2cont/c2value + */ +static struct yang_data * +frr_test_module_c2cont_c2value_get_elem(struct nb_cb_get_elem_args *args) +{ + return yang_data_new_uint32(args->xpath, 0xAB010203); +} + +/* clang-format off */ +const struct frr_yang_module_info frr_test_module_info = { + .name = "frr-test-module", + .nodes = { + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf", + .cbs.get_next = frr_test_module_vrfs_vrf_get_next, + .cbs.get_keys = frr_test_module_vrfs_vrf_get_keys, + .cbs.lookup_entry = frr_test_module_vrfs_vrf_lookup_entry, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/name", + .cbs.get_elem = frr_test_module_vrfs_vrf_name_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/interfaces/interface", + .cbs.get_elem = frr_test_module_vrfs_vrf_interfaces_interface_get_elem, + .cbs.get_next = frr_test_module_vrfs_vrf_interfaces_interface_get_next, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route", + .cbs.get_next = frr_test_module_vrfs_vrf_routes_route_get_next, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/prefix", + .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_prefix_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/next-hop", + .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_next_hop_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/interface", + .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_interface_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/metric", + .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_metric_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/routes/route/active", + .cbs.get_elem = frr_test_module_vrfs_vrf_routes_route_active_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/ping", + .cbs.rpc = frr_test_module_vrfs_vrf_ping, + }, + { + .xpath = "/frr-test-module:frr-test-module/c1value", + .cbs.get_elem = frr_test_module_c1value_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/c2cont/c2value", + .cbs.get_elem = frr_test_module_c2cont_c2value_get_elem, + }, + { + .xpath = NULL, + }, + } +}; +/* clang-format on */ + +DEFUN(test_rpc, test_rpc_cmd, "test rpc", + "Test\n" + "RPC\n") +{ + struct lyd_node *output = NULL; + char xpath[XPATH_MAXLEN]; + int ret; + + snprintf(xpath, sizeof(xpath), + "/frr-test-module:frr-test-module/vrfs/vrf[name='testname']/ping"); + + nb_cli_rpc_enqueue(vty, "data", "testdata"); + + ret = nb_cli_rpc(vty, xpath, &output); + if (ret != CMD_SUCCESS) { + vty_out(vty, "RPC failed\n"); + return ret; + } + + vty_out(vty, "vrf %s data %s\n", yang_dnode_get_string(output, "vrf"), + yang_dnode_get_string(output, "data-out")); + + yang_dnode_free(output); + + return CMD_SUCCESS; +} + +static const struct frr_yang_module_info *const modules[] = { + &frr_test_module_info, +}; + +static void create_data(unsigned int num_vrfs, unsigned int num_interfaces, + unsigned int num_routes) +{ + struct prefix_ipv4 base_prefix; + struct in_addr base_nexthop; + + (void)str2prefix_ipv4("10.0.0.0/32", &base_prefix); + (void)inet_pton(AF_INET, "172.16.0.0", &base_nexthop); + + vrfs = list_new(); + + /* Create VRFs. */ + for (unsigned int i = 0; i < num_vrfs; i++) { + struct tvrf *vrf; + + vrf = XCALLOC(MTYPE_TMP, sizeof(*vrf)); + snprintf(vrf->name, sizeof(vrf->name), "vrf%u", i); + vrf->interfaces = list_new(); + vrf->routes = list_new(); + + /* Create interfaces. */ + for (unsigned int j = 0; j < num_interfaces; j++) { + char ifname[32]; + char *interface; + + snprintf(ifname, sizeof(ifname), "eth%u", j); + interface = XSTRDUP(MTYPE_TMP, ifname); + listnode_add(vrf->interfaces, interface); + } + + /* Create routes. */ + for (unsigned int j = 0; j < num_routes; j++) { + struct troute *route; + + route = XCALLOC(MTYPE_TMP, sizeof(*route)); + + memcpy(&route->prefix, &base_prefix, + sizeof(route->prefix)); + route->prefix.prefix.s_addr = + htonl(ntohl(route->prefix.prefix.s_addr) + j); + + memcpy(&route->nexthop, &base_nexthop, + sizeof(route->nexthop)); + route->nexthop.s_addr = + htonl(ntohl(route->nexthop.s_addr) + j); + + snprintf(route->ifname, sizeof(route->ifname), "eth%u", + j); + route->metric = j % 256; + route->active = (j % 2 == 0); + listnode_add(vrf->routes, route); + } + + listnode_add(vrfs, vrf); + } +} + +static void interface_delete(void *ptr) +{ + char *interface = ptr; + + XFREE(MTYPE_TMP, interface); +} + +static void route_delete(void *ptr) +{ + struct troute *route = ptr; + + XFREE(MTYPE_TMP, route); +} + +static void vrf_delete(void *ptr) +{ + struct tvrf *vrf = ptr; + + vrf->interfaces->del = interface_delete; + list_delete(&vrf->interfaces); + vrf->routes->del = route_delete; + list_delete(&vrf->routes); + XFREE(MTYPE_TMP, vrf); +} + +static void delete_data(void) +{ + vrfs->del = vrf_delete; + list_delete(&vrfs); +} + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + + delete_data(); + + cmd_terminate(); + vty_terminate(); + nb_terminate(); + yang_terminate(); + event_master_free(master); + + log_memstats(stderr, "test-nb-oper-data"); + if (!isexit) + exit(0); +} + +/* main routine. */ +int main(int argc, char **argv) +{ + struct event thread; + unsigned int num_vrfs = 2; + unsigned int num_interfaces = 4; + unsigned int num_routes = 6; + + if (argc > 1) + num_vrfs = atoi(argv[1]); + if (argc > 2) + num_interfaces = atoi(argv[2]); + if (argc > 3) + num_routes = atoi(argv[3]); + + /* Set umask before anything for security */ + umask(0027); + + /* master init. */ + master = event_master_create(NULL); + + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + vty_init(master, false); + lib_cmd_init(); + nb_init(master, modules, array_size(modules), false); + + install_element(ENABLE_NODE, &test_rpc_cmd); + + /* Create artificial data. */ + create_data(num_vrfs, num_interfaces, num_routes); + + /* Read input from .in file. */ + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in new file mode 100644 index 0000000..f7c44ca --- /dev/null +++ b/tests/lib/northbound/test_oper_data.in @@ -0,0 +1,2 @@ +show yang operational-data /frr-test-module:frr-test-module +test rpc diff --git a/tests/lib/northbound/test_oper_data.py b/tests/lib/northbound/test_oper_data.py new file mode 100644 index 0000000..a02bf05 --- /dev/null +++ b/tests/lib/northbound/test_oper_data.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestNbOperData(frrtest.TestRefOut): + program = "./test_oper_data" diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout new file mode 100644 index 0000000..7c56564 --- /dev/null +++ b/tests/lib/northbound/test_oper_data.refout @@ -0,0 +1,125 @@ +test# show yang operational-data /frr-test-module:frr-test-module +{ + "frr-test-module:frr-test-module": { + "vrfs": { + "vrf": [ + { + "name": "vrf0", + "interfaces": { + "interface": [ + "eth0", + "eth1", + "eth2", + "eth3" + ] + }, + "routes": { + "route": [ + { + "prefix": "10.0.0.0/32", + "next-hop": "172.16.0.0", + "interface": "eth0", + "metric": 0, + "active": [null] + }, + { + "prefix": "10.0.0.1/32", + "next-hop": "172.16.0.1", + "interface": "eth1", + "metric": 1 + }, + { + "prefix": "10.0.0.2/32", + "next-hop": "172.16.0.2", + "interface": "eth2", + "metric": 2, + "active": [null] + }, + { + "prefix": "10.0.0.3/32", + "next-hop": "172.16.0.3", + "interface": "eth3", + "metric": 3 + }, + { + "prefix": "10.0.0.4/32", + "next-hop": "172.16.0.4", + "interface": "eth4", + "metric": 4, + "active": [null] + }, + { + "prefix": "10.0.0.5/32", + "next-hop": "172.16.0.5", + "interface": "eth5", + "metric": 5 + } + ] + } + }, + { + "name": "vrf1", + "interfaces": { + "interface": [ + "eth0", + "eth1", + "eth2", + "eth3" + ] + }, + "routes": { + "route": [ + { + "prefix": "10.0.0.0/32", + "next-hop": "172.16.0.0", + "interface": "eth0", + "metric": 0, + "active": [null] + }, + { + "prefix": "10.0.0.1/32", + "next-hop": "172.16.0.1", + "interface": "eth1", + "metric": 1 + }, + { + "prefix": "10.0.0.2/32", + "next-hop": "172.16.0.2", + "interface": "eth2", + "metric": 2, + "active": [null] + }, + { + "prefix": "10.0.0.3/32", + "next-hop": "172.16.0.3", + "interface": "eth3", + "metric": 3 + }, + { + "prefix": "10.0.0.4/32", + "next-hop": "172.16.0.4", + "interface": "eth4", + "metric": 4, + "active": [null] + }, + { + "prefix": "10.0.0.5/32", + "next-hop": "172.16.0.5", + "interface": "eth5", + "metric": 5 + } + ] + } + } + ] + }, + "c1value": 21, + "c2cont": { + "c2value": 2868969987 + } + } +} +test# test rpc +vrf testname data testdata +test# +end. diff --git a/tests/lib/script1.lua b/tests/lib/script1.lua new file mode 100644 index 0000000..6361c96 --- /dev/null +++ b/tests/lib/script1.lua @@ -0,0 +1,54 @@ + +-- Positive testing + +function foo(a, b) + a = a + 1 + b = b + 1 + return { + a = a, + b = b, + } +end + +function bar(a, b) + a = a + 1 + b = b + 1 + c = 303 + return { + b = b, + c = c, + } +end + +function fact(n) + -- outer function must return a table + -- inner functions can be used to recurse or as helpers + function helper(m) + if m == 0 then + return 1 + else + return m * helper(m - 1) + end + end + return { + ans = helper(n) + } +end + +-- Negative testing + +function bad_return1() +end + +function bad_return2() + return 123 +end + +function bad_return3() + return {} +end + +function bad_return4() + error("Something bad!") +end + diff --git a/tests/lib/subdir.am b/tests/lib/subdir.am new file mode 100644 index 0000000..185b895 --- /dev/null +++ b/tests/lib/subdir.am @@ -0,0 +1,390 @@ +############################################################################## +if SCRIPTING +check_PROGRAMS += tests/lib/test_frrlua +endif +tests_lib_test_frrlua_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_frrlua_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_frrlua_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_frrlua_SOURCES = tests/lib/test_frrlua.c +EXTRA_DIST += tests/lib/test_frrlua.py + +if SCRIPTING +check_PROGRAMS += tests/lib/test_frrscript +endif +tests_lib_test_frrscript_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_frrscript_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_frrscript_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_frrscript_SOURCES = tests/lib/test_frrscript.c +EXTRA_tests_lib_test_frrscript_DEPENDENCIES = copy_script +EXTRA_DIST += tests/lib/test_frrscript.py tests/lib/script1.lua + +# For out-of-tree build, lua script needs to be in the build dir, rather than +# just available somewhere in the VPATH +copy_script: tests/lib/script1.lua + test -e tests/lib/script1.lua || \ + $(INSTALL_SCRIPT) $< tests/lib/script1.lua + +############################################################################## +GRPC_TESTS_LDADD = mgmtd/libmgmt_be_nb.la staticd/libstatic.a grpc/libfrrgrpc_pb.la $(GRPC_LIBS) $(ALL_TESTS_LDADD) $(LIBYANG_LIBS) -lm + +if GRPC +check_PROGRAMS += tests/lib/test_grpc +endif +tests_lib_test_grpc_CXXFLAGS = $(WERROR) $(TESTS_CXXFLAGS) +tests_lib_test_grpc_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_grpc_LDADD = $(GRPC_TESTS_LDADD) +tests_lib_test_grpc_SOURCES = tests/lib/test_grpc.cpp +nodist_tests_lib_test_grpc_SOURCES = \ + yang/frr-bfdd.yang.c \ + yang/frr-staticd.yang.c \ + # end + + +############################################################################## +if ZEROMQ +check_PROGRAMS += tests/lib/test_zmq +endif +tests_lib_test_zmq_CFLAGS = $(TESTS_CFLAGS) $(ZEROMQ_CFLAGS) +tests_lib_test_zmq_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_zmq_LDADD = lib/libfrrzmq.la $(ALL_TESTS_LDADD) $(ZEROMQ_LIBS) +tests_lib_test_zmq_SOURCES = tests/lib/test_zmq.c + + +############################################################################## +if CARES +check_PROGRAMS += tests/lib/test_resolver +endif +tests_lib_test_resolver_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_resolver_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_resolver_LDADD = $(ALL_TESTS_LDADD) lib/libfrrcares.la +tests_lib_test_resolver_SOURCES = tests/lib/test_resolver.c tests/lib/cli/common_cli.c + + +############################################################################## +noinst_HEADERS += \ + tests/helpers/c/prng.h \ + tests/helpers/c/tests.h \ + tests/lib/cli/common_cli.h \ + # end + + +check_PROGRAMS += tests/lib/cxxcompat +tests_lib_cxxcompat_CFLAGS = $(TESTS_CFLAGS) $(CXX_COMPAT_CFLAGS) $(WERROR) +tests_lib_cxxcompat_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_cxxcompat_SOURCES = tests/lib/cxxcompat.c +tests_lib_cxxcompat_LDADD = $(ALL_TESTS_LDADD) + + +check_PROGRAMS += tests/lib/fuzz_zlog +tests_lib_fuzz_zlog_CFLAGS = $(TESTS_CFLAGS) +tests_lib_fuzz_zlog_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_fuzz_zlog_LDADD = $(ALL_TESTS_LDADD) +tests_lib_fuzz_zlog_SOURCES = tests/lib/fuzz_zlog.c +EXTRA_DIST += tests/lib/fuzz_zlog_inputs.py + + +check_PROGRAMS += tests/lib/cli/test_cli +tests_lib_cli_test_cli_CFLAGS = $(TESTS_CFLAGS) +tests_lib_cli_test_cli_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_cli_test_cli_LDADD = $(ALL_TESTS_LDADD) +tests_lib_cli_test_cli_SOURCES = tests/lib/cli/test_cli.c tests/lib/cli/common_cli.c +clippy_scan += tests/lib/cli/test_cli.c +EXTRA_DIST += \ + tests/lib/cli/test_cli.in \ + tests/lib/cli/test_cli.py \ + tests/lib/cli/test_cli.refout \ + # end + + +check_PROGRAMS += tests/lib/cli/test_commands +tests_lib_cli_test_commands_CFLAGS = $(TESTS_CFLAGS) +tests_lib_cli_test_commands_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_cli_test_commands_LDADD = $(ALL_TESTS_LDADD) +nodist_tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands_defun.c +tests_lib_cli_test_commands_SOURCES = tests/lib/cli/test_commands.c tests/helpers/c/prng.c +tests/lib/cli/test_commands_defun.c: vtysh/vtysh_cmd.c + @$(MKDIR_P) tests/lib/cli + $(AM_V_GEN)sed \ + -e 's%"vtysh/vtysh\.h"%"tests/helpers/c/tests.h"%' \ + -e 's/vtysh_init_cmd/test_init_cmd/' \ + -e 's/VTYSH_[A-Z][A-Z_0-9]*/0/g' \ + < vtysh/vtysh_cmd.c \ + > "$@" +CLEANFILES += tests/lib/cli/test_commands_defun.c +EXTRA_DIST += \ + tests/lib/cli/test_commands.in \ + tests/lib/cli/test_commands.py \ + tests/lib/cli/test_commands.refout \ + # end + + +check_PROGRAMS += tests/lib/northbound/test_oper_data +tests_lib_northbound_test_oper_data_CFLAGS = $(TESTS_CFLAGS) +tests_lib_northbound_test_oper_data_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_northbound_test_oper_data_LDADD = $(ALL_TESTS_LDADD) +tests_lib_northbound_test_oper_data_SOURCES = tests/lib/northbound/test_oper_data.c +nodist_tests_lib_northbound_test_oper_data_SOURCES = yang/frr-test-module.yang.c +EXTRA_DIST += \ + tests/lib/northbound/test_oper_data.in \ + tests/lib/northbound/test_oper_data.py \ + tests/lib/northbound/test_oper_data.refout \ + # end + + +check_PROGRAMS += tests/lib/test_assert +tests_lib_test_assert_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_assert_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_assert_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_assert_SOURCES = tests/lib/test_assert.c +EXTRA_DIST += tests/lib/test_assert.py + + +check_PROGRAMS += tests/lib/test_atomlist +tests_lib_test_atomlist_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_atomlist_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_atomlist_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_atomlist_SOURCES = tests/lib/test_atomlist.c +EXTRA_DIST += tests/lib/test_atomlist.py + + +check_PROGRAMS += tests/lib/test_buffer +tests_lib_test_buffer_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_buffer_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_buffer_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_buffer_SOURCES = tests/lib/test_buffer.c + + +check_PROGRAMS += tests/lib/test_checksum +tests_lib_test_checksum_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_checksum_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_checksum_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_checksum_SOURCES = tests/lib/test_checksum.c tests/helpers/c/prng.c + + +check_PROGRAMS += tests/lib/test_darr +tests_lib_test_darr_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_darr_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_darr_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_darr_SOURCES = tests/lib/test_darr.c +EXTRA_DIST += tests/lib/test_darr.py + + +check_PROGRAMS += tests/lib/test_graph +tests_lib_test_graph_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_graph_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_graph_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_graph_SOURCES = tests/lib/test_graph.c +EXTRA_DIST += \ + tests/lib/test_graph.py \ + tests/lib/test_graph.refout \ + # end + + +check_PROGRAMS += tests/lib/test_heavy +tests_lib_test_heavy_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_heavy_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_heavy_LDADD = $(ALL_TESTS_LDADD) -lm +tests_lib_test_heavy_SOURCES = tests/lib/test_heavy.c tests/helpers/c/main.c + + +check_PROGRAMS += tests/lib/test_heavy_thread +tests_lib_test_heavy_thread_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_heavy_thread_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_heavy_thread_LDADD = $(ALL_TESTS_LDADD) -lm +tests_lib_test_heavy_thread_SOURCES = tests/lib/test_heavy_thread.c tests/helpers/c/main.c + + +check_PROGRAMS += tests/lib/test_heavy_wq +tests_lib_test_heavy_wq_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_heavy_wq_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_heavy_wq_LDADD = $(ALL_TESTS_LDADD) -lm +tests_lib_test_heavy_wq_SOURCES = tests/lib/test_heavy_wq.c tests/helpers/c/main.c + + +check_PROGRAMS += tests/lib/test_idalloc +tests_lib_test_idalloc_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_idalloc_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_idalloc_SOURCES = tests/lib/test_idalloc.c + + +check_PROGRAMS += tests/lib/test_memory +tests_lib_test_memory_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_memory_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_memory_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_memory_SOURCES = tests/lib/test_memory.c + + +check_PROGRAMS += tests/lib/test_nexthop_iter +tests_lib_test_nexthop_iter_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_nexthop_iter_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_nexthop_iter_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_nexthop_iter_SOURCES = tests/lib/test_nexthop_iter.c tests/helpers/c/prng.c +EXTRA_DIST += tests/lib/test_nexthop_iter.py + + +check_PROGRAMS += tests/lib/test_nexthop +tests_lib_test_nexthop_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_nexthop_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_nexthop_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_nexthop_SOURCES = tests/lib/test_nexthop.c +EXTRA_DIST += tests/lib/test_nexthop.py + + +check_PROGRAMS += tests/lib/test_ntop +tests_lib_test_ntop_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_ntop_CPPFLAGS = $(CPPFLAGS_BASE) # no assert override +tests_lib_test_ntop_LDADD = # none +tests_lib_test_ntop_SOURCES = tests/lib/test_ntop.c tests/helpers/c/prng.c +EXTRA_DIST += tests/lib/test_ntop.py + + +check_PROGRAMS += tests/lib/test_plist +tests_lib_test_plist_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_plist_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_plist_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_plist_SOURCES = tests/lib/test_plist.c tests/lib/cli/common_cli.c + + +check_PROGRAMS += tests/lib/test_prefix2str +tests_lib_test_prefix2str_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_prefix2str_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_prefix2str_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_prefix2str_SOURCES = tests/lib/test_prefix2str.c tests/helpers/c/prng.c +EXTRA_DIST += tests/lib/test_prefix2str.py + + +check_PROGRAMS += tests/lib/test_printfrr +tests_lib_test_printfrr_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_printfrr_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_printfrr_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_printfrr_SOURCES = tests/lib/test_printfrr.c +EXTRA_DIST += tests/lib/test_printfrr.py + + +check_PROGRAMS += tests/lib/test_privs +tests_lib_test_privs_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_privs_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_privs_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_privs_SOURCES = tests/lib/test_privs.c + + +check_PROGRAMS += tests/lib/test_ringbuf +tests_lib_test_ringbuf_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_ringbuf_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_ringbuf_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_ringbuf_SOURCES = tests/lib/test_ringbuf.c +EXTRA_DIST += tests/lib/test_ringbuf.py + + +check_PROGRAMS += tests/lib/test_segv +tests_lib_test_segv_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_segv_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_segv_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_segv_SOURCES = tests/lib/test_segv.c + + +check_PROGRAMS += tests/lib/test_seqlock +tests_lib_test_seqlock_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_seqlock_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_seqlock_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_seqlock_SOURCES = tests/lib/test_seqlock.c + + +check_PROGRAMS += tests/lib/test_sig +tests_lib_test_sig_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_sig_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_sig_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_sig_SOURCES = tests/lib/test_sig.c + + +check_PROGRAMS += tests/lib/test_skiplist +tests_lib_test_skiplist_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_skiplist_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_skiplist_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_skiplist_SOURCES = tests/lib/test_skiplist.c + + +check_PROGRAMS += tests/lib/test_srcdest_table +tests_lib_test_srcdest_table_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_srcdest_table_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_srcdest_table_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_srcdest_table_SOURCES = tests/lib/test_srcdest_table.c tests/helpers/c/prng.c +EXTRA_DIST += tests/lib/test_srcdest_table.py + + +check_PROGRAMS += tests/lib/test_stream +tests_lib_test_stream_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_stream_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_stream_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_stream_SOURCES = tests/lib/test_stream.c +EXTRA_DIST += \ + tests/lib/test_stream.py \ + tests/lib/test_stream.refout \ + # end + + +check_PROGRAMS += tests/lib/test_table +tests_lib_test_table_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_table_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_table_LDADD = $(ALL_TESTS_LDADD) -lm +tests_lib_test_table_SOURCES = tests/lib/test_table.c +EXTRA_DIST += tests/lib/test_table.py + + +check_PROGRAMS += tests/lib/test_timer_correctness +tests_lib_test_timer_correctness_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_timer_correctness_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_timer_correctness_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_timer_correctness_SOURCES = tests/lib/test_timer_correctness.c tests/helpers/c/prng.c +EXTRA_DIST += tests/lib/test_timer_correctness.py + + +check_PROGRAMS += tests/lib/test_timer_performance +tests_lib_test_timer_performance_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_timer_performance_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_timer_performance_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_timer_performance_SOURCES = tests/lib/test_timer_performance.c tests/helpers/c/prng.c + + +check_PROGRAMS += tests/lib/test_ttable +tests_lib_test_ttable_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_ttable_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_ttable_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_ttable_SOURCES = tests/lib/test_ttable.c +EXTRA_DIST += \ + tests/lib/test_ttable.py \ + tests/lib/test_ttable.refout \ + # end + + +check_PROGRAMS += tests/lib/test_typelist +tests_lib_test_typelist_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_typelist_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_typelist_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_typelist_SOURCES = tests/lib/test_typelist.c tests/helpers/c/prng.c +noinst_HEADERS += tests/lib/test_typelist.h +EXTRA_DIST += tests/lib/test_typelist.py + + +check_PROGRAMS += tests/lib/test_versioncmp +tests_lib_test_versioncmp_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_versioncmp_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_versioncmp_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_versioncmp_SOURCES = tests/lib/test_versioncmp.c +EXTRA_DIST += tests/lib/test_versioncmp.py + + +check_PROGRAMS += tests/lib/test_xref +tests_lib_test_xref_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_xref_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_xref_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_xref_SOURCES = tests/lib/test_xref.c +EXTRA_DIST += tests/lib/test_xref.py + + +check_PROGRAMS += tests/lib/test_zlog +tests_lib_test_zlog_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_zlog_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_zlog_LDADD = $(ALL_TESTS_LDADD) +tests_lib_test_zlog_SOURCES = tests/lib/test_zlog.c +EXTRA_DIST += tests/lib/test_zlog.py diff --git a/tests/lib/test_assert.c b/tests/lib/test_assert.c new file mode 100644 index 0000000..4440075 --- /dev/null +++ b/tests/lib/test_assert.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quick test for assert() + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* make sure this works with assert.h & nothing else. also check the include + * shadowing, we don't want to pick up system assert.h + */ +#include + +__attribute__((noinline)) +static void func_for_bt(int number) +{ + assert(number > 2); + assertf(number > 3, "(A) the number was %d", number); +} + +#include +#include "lib/zlog.h" +#include "frrevent.h" +#include "lib/sigevent.h" + +int main(int argc, char **argv) +{ + int number = 10; + struct event_loop *master; + + zlog_aux_init("NONE: ", LOG_DEBUG); + + if (argc > 1) + number = atoi(argv[1]); + + assert(number > 0); + assertf(number > 1, "(B) the number was %d", number); + + /* set up SIGABRT handler */ + master = event_master_create("test"); + signal_init(master, 0, NULL); + + func_for_bt(number); + assert(number > 4); + assertf(number > 5, "(C) the number was %d", number); + + assertf(number > 10, "(D) the number was %d", number); + return 0; +} diff --git a/tests/lib/test_assert.py b/tests/lib/test_assert.py new file mode 100644 index 0000000..67c88e6 --- /dev/null +++ b/tests/lib/test_assert.py @@ -0,0 +1,56 @@ +import frrtest +import os +import re +import subprocess +import inspect + +basedir = os.path.dirname(__file__) +program = os.path.join(basedir, "test_assert") + + +def check(number, rex=None): + proc = subprocess.Popen( + [frrtest.binpath(program), str(number)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + out, err = proc.communicate() + exitcode = proc.wait() + + if rex is None: + assert exitcode == 0 + else: + assert exitcode != 0 + + text = out.decode("US-ASCII") + err.decode("US-ASCII") + rex = re.compile(rex, re.M | re.S) + m = rex.search(text) + assert m is not None, "non-matching output: %s" % text + + +def test_assert_0(): + check(0, r"test_assert\.c:\d+.*number > 0") + + +def test_assert_1(): + check(1, r"test_assert\.c:\d+.*number > 1.*\(B\) the number was 1") + + +def test_assert_2(): + check(2, r"test_assert\.c:\d+.*number > 2") + + +def test_assert_3(): + check(3, r"test_assert\.c:\d+.*number > 3.*\(A\) the number was 3") + + +def test_assert_4(): + check(4, r"test_assert\.c:\d+.*number > 4") + + +def test_assert_10(): + check(10, r"test_assert\.c:\d+.*number > 10.*\(D\) the number was 10") + + +def test_assert_11(): + check(11) diff --git a/tests/lib/test_atomlist.c b/tests/lib/test_atomlist.c new file mode 100644 index 0000000..b50216c --- /dev/null +++ b/tests/lib/test_atomlist.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "atomlist.h" +#include "seqlock.h" +#include "monotime.h" +#include "printfrr.h" + +/* + * maybe test: + * - alist_del_hint + * - alist_next_safe + * - asort_del_hint + * - asort_next_safe + */ + +static struct seqlock sqlo; + +PREDECL_ATOMLIST(alist); +PREDECL_ATOMSORT_UNIQ(asort); +struct item { + uint64_t val1; + struct alist_item chain; + struct asort_item sortc; + uint64_t val2; +}; +DECLARE_ATOMLIST(alist, struct item, chain); + +static int icmp(const struct item *a, const struct item *b); +DECLARE_ATOMSORT_UNIQ(asort, struct item, sortc, icmp); + +static int icmp(const struct item *a, const struct item *b) +{ + if (a->val1 > b->val1) + return 1; + if (a->val1 < b->val1) + return -1; + return 0; +} + +#define NITEM 10000 +struct item itm[NITEM]; + +static struct alist_head ahead; +static struct asort_head shead; + +#define NTHREADS 4 +static struct testthread { + pthread_t pt; + struct seqlock sqlo; + size_t counter, nullops; +} thr[NTHREADS]; + +struct testrun { + struct testrun *next; + int lineno; + const char *desc; + ssize_t prefill; + bool sorted; + void (*func)(unsigned int offset); +}; +struct testrun *runs = NULL; + +#define NOCLEAR -1 + +#define deftestrun(name, _desc, _prefill, _sorted) \ +static void trfunc_##name(unsigned int offset); \ +struct testrun tr_##name = { \ + .desc = _desc, \ + .lineno = __LINE__, \ + .prefill = _prefill, \ + .func = &trfunc_##name, \ + .sorted = _sorted }; \ +static void __attribute__((constructor)) trsetup_##name(void) \ +{ \ + struct testrun **inspos = &runs; \ + while (*inspos && (*inspos)->lineno < tr_##name.lineno) \ + inspos = &(*inspos)->next; \ + tr_##name.next = *inspos; \ + *inspos = &tr_##name; \ +} \ +static void trfunc_##name(unsigned int offset) \ +{ \ + size_t i = 0, n = 0; + +#define endtestrun \ + thr[offset].counter = i; \ + thr[offset].nullops = n; \ +} + +deftestrun(add, "add vs. add", 0, false) + for (; i < NITEM / NTHREADS; i++) + alist_add_head(&ahead, &itm[i * NTHREADS + offset]); +endtestrun + +deftestrun(del, "del vs. del", NOCLEAR, false) + for (; i < NITEM / NTHREADS / 10; i++) + alist_del(&ahead, &itm[i * NTHREADS + offset]); +endtestrun + +deftestrun(addtail, "add_tail vs. add_tail", 0, false) + for (; i < NITEM / NTHREADS; i++) + alist_add_tail(&ahead, &itm[i * NTHREADS + offset]); +endtestrun + +deftestrun(pop, "pop vs. pop", NOCLEAR, false) + for (; i < NITEM / NTHREADS; ) + if (alist_pop(&ahead)) + i++; + else + n++; +endtestrun + +deftestrun(headN_vs_pop1, "add_head(N) vs. pop(1)", 1, false); + if (offset == 0) { + struct item *dr = NULL; + + for (i = n = 0; i < NITEM; ) { + dr = alist_pop(&ahead); + if (dr) + i++; + else + n++; + } + } else { + for (i = offset; i < NITEM; i += NTHREADS) + alist_add_head(&ahead, &itm[i]); + i = 0; + } +endtestrun + +deftestrun(head1_vs_popN, "add_head(1) vs. pop(N)", 0, false); + if (offset < NTHREADS - 1) { + struct item *dr = NULL; + + for (i = n = 0; i < NITEM / NTHREADS; ) { + dr = alist_pop(&ahead); + if (dr) + i++; + else + n++; + } + } else { + for (i = 0; i < NITEM; i++) + alist_add_head(&ahead, &itm[i]); + i = 0; + } +endtestrun + +deftestrun(headN_vs_popN, "add_head(N) vs. pop(N)", NTHREADS / 2, false) + if (offset < NTHREADS / 2) { + struct item *dr = NULL; + + for (i = n = 0; i < NITEM * 2 / NTHREADS; ) { + dr = alist_pop(&ahead); + if (dr) + i++; + else + n++; + } + } else { + for (i = offset; i < NITEM; i += NTHREADS) + alist_add_head(&ahead, &itm[i]); + i = 0; + } +endtestrun + +deftestrun(tailN_vs_pop1, "add_tail(N) vs. pop(1)", 1, false) + if (offset == 0) { + struct item *dr = NULL; + + for (i = n = 0; i < NITEM - (NITEM / NTHREADS); ) { + dr = alist_pop(&ahead); + if (dr) + i++; + else + n++; + } + } else { + for (i = offset; i < NITEM; i += NTHREADS) + alist_add_tail(&ahead, &itm[i]); + i = 0; + } +endtestrun + +deftestrun(tail1_vs_popN, "add_tail(1) vs. pop(N)", 0, false) + if (offset < NTHREADS - 1) { + struct item *dr = NULL; + + for (i = n = 0; i < NITEM / NTHREADS; ) { + dr = alist_pop(&ahead); + if (dr) + i++; + else + n++; + } + } else { + for (i = 0; i < NITEM; i++) + alist_add_tail(&ahead, &itm[i]); + i = 0; + } +endtestrun + +deftestrun(sort_add, "add_sort vs. add_sort", 0, true) + for (; i < NITEM / NTHREADS / 10; i++) + asort_add(&shead, &itm[i * NTHREADS + offset]); +endtestrun + +deftestrun(sort_del, "del_sort vs. del_sort", NOCLEAR, true) + for (; i < NITEM / NTHREADS / 10; i++) + asort_del(&shead, &itm[i * NTHREADS + offset]); +endtestrun + +deftestrun(sort_add_del, "add_sort vs. del_sort", NTHREADS / 2, true) + if (offset < NTHREADS / 2) { + for (; i < NITEM / NTHREADS / 10; i++) + asort_del(&shead, &itm[i * NTHREADS + offset]); + } else { + for (; i < NITEM / NTHREADS / 10; i++) + asort_add(&shead, &itm[i * NTHREADS + offset]); + } +endtestrun + +static void *thr1func(void *arg) +{ + struct testthread *p = arg; + unsigned int offset = (unsigned int)(p - &thr[0]); + seqlock_val_t sv; + struct testrun *tr; + + for (tr = runs; tr; tr = tr->next) { + sv = seqlock_bump(&p->sqlo) - SEQLOCK_INCR; + seqlock_wait(&sqlo, sv); + + tr->func(offset); + } + seqlock_bump(&p->sqlo); + + return NULL; +} + +static void clear_list(size_t prefill) +{ + size_t i; + + memset(&ahead, 0, sizeof(ahead)); + memset(&shead, 0, sizeof(shead)); + memset(itm, 0, sizeof(itm)); + for (i = 0; i < NITEM; i++) { + itm[i].val1 = itm[i].val2 = i; + if ((i % NTHREADS) < prefill) { + alist_add_tail(&ahead, &itm[i]); + asort_add(&shead, &itm[i]); + } + } +} + +static void run_tr(struct testrun *tr) +{ + const char *desc = tr->desc; + struct timeval tv; + int64_t delta; + seqlock_val_t sv; + size_t c = 0, s = 0, n = 0; + struct item *item, *prev, dummy; + + printfrr("[%02u] %35s %s\n", seqlock_cur(&sqlo) >> 2, "", desc); + fflush(stdout); + + if (tr->prefill != NOCLEAR) + clear_list(tr->prefill); + + monotime(&tv); + sv = seqlock_bump(&sqlo) - SEQLOCK_INCR; + for (size_t i = 0; i < NTHREADS; i++) { + seqlock_wait(&thr[i].sqlo, seqlock_cur(&sqlo)); + s += thr[i].counter; + n += thr[i].nullops; + thr[i].counter = 0; + thr[i].nullops = 0; + } + + delta = monotime_since(&tv, NULL); + if (tr->sorted) { + uint64_t prevval = 0; + + frr_each(asort, &shead, item) { + assert(item->val1 >= prevval); + prevval = item->val1; + c++; + } + assert(c == asort_count(&shead)); + } else { + prev = &dummy; + frr_each(alist, &ahead, item) { + assert(item != prev); + prev = item; + c++; + assert(c <= NITEM); + } + assert(c == alist_count(&ahead)); + } + printfrr("\033[1A[%02u] %9"PRId64"us c=%5zu s=%5zu n=%5zu %s\n", + sv >> 2, delta, c, s, n, desc); +} + +#ifdef BASIC_TESTS +static void dump(const char *lbl) +{ + struct item *item, *safe; + size_t ctr = 0; + + printfrr("dumping %s:\n", lbl); + frr_each_safe(alist, &ahead, item) { + printfrr("%s %3zu %p %3"PRIu64" %3"PRIu64"\n", lbl, ctr++, + (void *)item, item->val1, item->val2); + } +} + +static void basic_tests(void) +{ + size_t i; + + memset(&ahead, 0, sizeof(ahead)); + memset(itm, 0, sizeof(itm)); + for (i = 0; i < NITEM; i++) + itm[i].val1 = itm[i].val2 = i; + + assert(alist_first(&ahead) == NULL); + dump(""); + alist_add_head(&ahead, &itm[0]); + dump(""); + alist_add_head(&ahead, &itm[1]); + dump(""); + alist_add_tail(&ahead, &itm[2]); + dump(""); + alist_add_tail(&ahead, &itm[3]); + dump(""); + alist_del(&ahead, &itm[1]); + dump(""); + printfrr("POP: %p\n", alist_pop(&ahead)); + dump(""); + printfrr("POP: %p\n", alist_pop(&ahead)); + printfrr("POP: %p\n", alist_pop(&ahead)); + printfrr("POP: %p\n", alist_pop(&ahead)); + printfrr("POP: %p\n", alist_pop(&ahead)); + dump(""); +} +#else +#define basic_tests() do { } while (0) +#endif + +int main(int argc, char **argv) +{ + size_t i; + + basic_tests(); + + seqlock_init(&sqlo); + seqlock_acquire_val(&sqlo, SEQLOCK_STARTVAL); + + for (i = 0; i < NTHREADS; i++) { + seqlock_init(&thr[i].sqlo); + seqlock_acquire(&thr[i].sqlo, &sqlo); + thr[i].counter = 0; + thr[i].nullops = 0; + + pthread_create(&thr[i].pt, NULL, thr1func, &thr[i]); + } + + struct testrun *tr; + + for (tr = runs; tr; tr = tr->next) + run_tr(tr); + + for (i = 0; i < NTHREADS; i++) + pthread_join(thr[i].pt, NULL); + + return 0; +} diff --git a/tests/lib/test_atomlist.py b/tests/lib/test_atomlist.py new file mode 100644 index 0000000..719a2e7 --- /dev/null +++ b/tests/lib/test_atomlist.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestAtomlist(frrtest.TestMultiOut): + program = "./test_atomlist" + + +TestAtomlist.exit_cleanly() diff --git a/tests/lib/test_buffer.c b/tests/lib/test_buffer.c new file mode 100644 index 0000000..bfb4600 --- /dev/null +++ b/tests/lib/test_buffer.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2004 Paul Jakma + */ + +#include +#include +#include +#include + +struct event_loop *master; + +int main(int argc, char **argv) +{ + struct buffer *b1, *b2; + int n; + char junk[3]; + char c = 'a'; + + lib_cmd_init(); + + if ((argc != 2) || (sscanf(argv[1], "%d%1s", &n, junk) != 1)) { + fprintf(stderr, "Usage: %s \n", + *argv); + return 1; + } + + b1 = buffer_new(0); + b2 = buffer_new(1024); + + while (n-- > 0) { + buffer_put(b1, &c, 1); + buffer_put(b2, &c, 1); + if (c++ == 'z') + c = 'a'; + buffer_reset(b1); + buffer_reset(b2); + } + buffer_free(b1); + buffer_free(b2); + return 0; +} diff --git a/tests/lib/test_checksum.c b/tests/lib/test_checksum.c new file mode 100644 index 0000000..329ae60 --- /dev/null +++ b/tests/lib/test_checksum.c @@ -0,0 +1,572 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2008 Sun Microsystems, Inc. + */ + +#include +#include +#include + +#include "checksum.h" +#include "network.h" +#include "prng.h" + +struct event_loop *master; + +struct acc_vals { + int c0; + int c1; +}; + +struct csum_vals { + struct acc_vals a; + int x; + int y; +}; + +static struct csum_vals ospfd_vals, isisd_vals; + +typedef size_t testsz_t; +typedef uint16_t testoff_t; + +/* Fletcher Checksum -- Refer to RFC1008. */ +#define MODX 4102U + +/* The final reduction phase. + * This one should be the original ospfd version + */ +static uint16_t reduce_ospfd(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + + x = ((len - off - 1) * c0 - c1) % 255; + + if (x <= 0) + x += 255; + y = 510 - c0 - x; + if (y > 255) + y -= 255; + + /* take care endian issue. */ + return htons((x << 8) + y); +#undef x +#undef y +#undef c0 +#undef c1 +} + +/* slightly different concatenation */ +static uint16_t reduce_ospfd1(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + + x = ((len - off - 1) * c0 - c1) % 255; + if (x <= 0) + x += 255; + y = 510 - c0 - x; + if (y > 255) + y -= 255; + + /* take care endian issue. */ + return htons((x << 8) | (y & 0xff)); +#undef x +#undef y +#undef c0 +#undef c1 +} + +/* original isisd version */ +static uint16_t reduce_isisd(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + uint32_t mul; + + mul = (len - off) * (c0); + x = mul - c0 - c1; + y = c1 - mul - 1; + + if (y > 0) + y++; + if (x < 0) + x--; + + x %= 255; + y %= 255; + + if (x == 0) + x = 255; + if (y == 0) + y = 1; + + return htons((x << 8) | (y & 0xff)); + +#undef x +#undef y +#undef c0 +#undef c1 +} + +/* Is the -1 in y wrong perhaps? */ +static uint16_t reduce_isisd_yfix(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + uint32_t mul; + + mul = (len - off) * (c0); + x = mul - c0 - c1; + y = c1 - mul; + + if (y > 0) + y++; + if (x < 0) + x--; + + x %= 255; + y %= 255; + + if (x == 0) + x = 255; + if (y == 0) + y = 1; + + return htons((x << 8) | (y & 0xff)); + +#undef x +#undef y +#undef c0 +#undef c1 +} + +/* Move the mods yp */ +static uint16_t reduce_isisd_mod(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + uint32_t mul; + + mul = (len - off) * (c0); + x = mul - c1 - c0; + y = c1 - mul - 1; + + x %= 255; + y %= 255; + + if (y > 0) + y++; + if (x < 0) + x--; + + if (x == 0) + x = 255; + if (y == 0) + y = 1; + + return htons((x << 8) | (y & 0xff)); + +#undef x +#undef y +#undef c0 +#undef c1 +} + +/* Move the mods up + fix y */ +static uint16_t reduce_isisd_mody(struct csum_vals *vals, testsz_t len, + testoff_t off) +{ +#define x vals->x +#define y vals->y +#define c0 vals->a.c0 +#define c1 vals->a.c1 + uint32_t mul; + + mul = (len - off) * (c0); + x = mul - c0 - c1; + y = c1 - mul; + + x %= 255; + y %= 255; + + if (y > 0) + y++; + if (x < 0) + x--; + + if (x == 0) + x = 255; + if (y == 0) + y = 1; + + return htons((x << 8) | (y & 0xff)); + +#undef x +#undef y +#undef c0 +#undef c1 +} + +struct reductions_t { + const char *name; + uint16_t (*f)(struct csum_vals *, testsz_t, testoff_t); +} reducts[] = { + {.name = "ospfd", .f = reduce_ospfd}, + {.name = "ospfd-1", .f = reduce_ospfd1}, + {.name = "isisd", .f = reduce_isisd}, + {.name = "isisd-yfix", .f = reduce_isisd_yfix}, + {.name = "isisd-mod", .f = reduce_isisd_mod}, + {.name = "isisd-mody", .f = reduce_isisd_mody}, + {NULL, NULL}, +}; + +/* The original ospfd checksum */ +static uint16_t ospfd_checksum(uint8_t *buffer, testsz_t len, testoff_t off) +{ + uint8_t *sp, *ep, *p, *q; + int c0 = 0, c1 = 0; + int x, y; + uint16_t checksum, *csum; + + csum = (uint16_t *)(buffer + off); + *(csum) = 0; + + sp = buffer; + + for (ep = sp + len; sp < ep; sp = q) { + q = sp + MODX; + if (q > ep) + q = ep; + for (p = sp; p < q; p++) { + c0 += *p; + c1 += c0; + } + c0 %= 255; + c1 %= 255; + } + + ospfd_vals.a.c0 = c0; + ospfd_vals.a.c1 = c1; + + // printf ("%s: len %u, off %u, c0 %d, c1 %d\n", + // __func__, len, off, c0, c1); + + x = ((int)(len - off - 1) * (int)c0 - (int)c1) % 255; + + if (x <= 0) + x += 255; + y = 510 - c0 - x; + if (y > 255) + y -= 255; + + ospfd_vals.x = x; + ospfd_vals.y = y; + + buffer[off] = x; + buffer[off + 1] = y; + + /* take care endian issue. */ + checksum = htons((x << 8) | (y & 0xff)); + + return (checksum); +} + +/* the original, broken isisd checksum */ +static uint16_t iso_csum_create(uint8_t *buffer, testsz_t len, testoff_t off) +{ + + uint8_t *p; + int x; + int y; + uint32_t mul; + uint32_t c0; + uint32_t c1; + uint16_t checksum, *csum; + int i, init_len, partial_len; + + checksum = 0; + + csum = (uint16_t *)(buffer + off); + *(csum) = checksum; + + p = buffer; + c0 = 0; + c1 = 0; + init_len = len; + + while (len != 0) { + partial_len = MIN(len, MODX); + + for (i = 0; i < partial_len; i++) { + c0 = c0 + *(p++); + c1 += c0; + } + + c0 = c0 % 255; + c1 = c1 % 255; + + len -= partial_len; + } + + isisd_vals.a.c0 = c0; + isisd_vals.a.c1 = c1; + + mul = (init_len - off) * c0; + + x = mul - c1 - c0; + y = c1 - mul - 1; + + if (y > 0) + y++; + if (x < 0) + x--; + + x %= 255; + y %= 255; + + if (x == 0) + x = 255; + if (y == 0) + y = 1; + + isisd_vals.x = x; + isisd_vals.y = y; + + checksum = htons((x << 8) | (y & 0xFF)); + + *(csum) = checksum; + + /* return the checksum for user usage */ + return checksum; +} + +static int verify(uint8_t *buffer, testsz_t len) +{ + uint8_t *p; + uint32_t c0; + uint32_t c1; + int i, partial_len; + + p = buffer; + + c0 = 0; + c1 = 0; + + while (len) { + partial_len = MIN(len, 5803U); + + for (i = 0; i < partial_len; i++) { + c0 = c0 + *(p++); + c1 += c0; + } + c0 = c0 % 255; + c1 = c1 % 255; + + len -= partial_len; + } + + if (c0 == 0 && c1 == 0) + return 0; + + return 1; +} + +static int /* return checksum in low-order 16 bits */ + in_cksum_optimized(void *parg, int nbytes) +{ + unsigned short *ptr = parg; + register long sum; /* assumes long == 32 bits */ + register unsigned short answer; /* assumes unsigned short == 16 bits */ + register int count; + /* + * Our algorithm is simple, using a 32-bit accumulator (sum), + * we add sequential 16-bit words to it, and at the end, fold back + * all the carry bits from the top 16 bits into the lower 16 bits. + */ + + sum = 0; + count = nbytes >> 1; /* div by 2 */ + for (ptr--; count; --count) + sum += *++ptr; + + if (nbytes & 1) /* Odd */ + sum += *(uint8_t *)(++ptr); /* one byte only */ + + /* + * Add back carry outs from top 16 bits to low 16 bits. + */ + + sum = (sum >> 16) + (sum & 0xffff); /* add high-16 to low-16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* ones-complement, then truncate to 16 bits */ + return (answer); +} + + +static int /* return checksum in low-order 16 bits */ + in_cksum_rfc(void *parg, int count) +/* from RFC 1071 */ +{ + unsigned short *addr = parg; + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + register long sum = 0; + + while (count > 1) { + /* This is the inner loop */ + sum += *addr++; + count -= 2; + } + /* Add left-over byte, if any */ + if (count > 0) { + sum += *(uint8_t *)addr; + } + + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + return ~sum; +} + + +int main(int argc, char **argv) +{ +/* 60017 65629 702179 */ +#define MAXDATALEN 60017 +#define BUFSIZE MAXDATALEN + sizeof(uint16_t) + uint8_t buffer[BUFSIZE]; + int exercise = 0; +#define EXERCISESTEP 257 + struct prng *prng = prng_new(0); + + while (1) { + uint16_t ospfd, isisd, lib, in_csum, in_csum_res, in_csum_rfc; + int i; + + exercise += EXERCISESTEP; + exercise %= MAXDATALEN; + + printf("\rexercising length %d\033[K", exercise); + + for (i = 0; i < exercise; i++) + buffer[i] = prng_rand(prng); + + in_csum = in_cksum(buffer, exercise); + in_csum_res = in_cksum_optimized(buffer, exercise); + in_csum_rfc = in_cksum_rfc(buffer, exercise); + if (in_csum_res != in_csum || in_csum != in_csum_rfc) + printf("\nverify: in_chksum failed in_csum:%x, in_csum_res:%x,in_csum_rfc %x, len:%d\n", + in_csum, in_csum_res, in_csum_rfc, exercise); + + struct iovec iov[3]; + uint16_t in_csum_iov; + + iov[0].iov_base = buffer; + iov[0].iov_len = exercise / 2; + iov[1].iov_base = buffer + iov[0].iov_len; + iov[1].iov_len = exercise - iov[0].iov_len; + + in_csum_iov = in_cksumv(iov, 2); + if (in_csum_iov != in_csum) + printf("\nverify: in_cksumv failed, lens: %zu+%zu\n", + iov[0].iov_len, iov[1].iov_len); + + if (exercise >= 6) { + /* force split with byte leftover */ + iov[0].iov_base = buffer; + iov[0].iov_len = (exercise / 2) | 1; + iov[1].iov_base = buffer + iov[0].iov_len; + iov[1].iov_len = 2; + iov[2].iov_base = buffer + iov[0].iov_len + 2; + iov[2].iov_len = exercise - iov[0].iov_len - 2; + + in_csum_iov = in_cksumv(iov, 3); + if (in_csum_iov != in_csum) + printf("\nverify: in_cksumv failed, lens: %zu+%zu+%zu, got %04x, expected %04x\n", + iov[0].iov_len, iov[1].iov_len, + iov[2].iov_len, in_csum_iov, in_csum); + + /* force split without byte leftover */ + iov[0].iov_base = buffer; + iov[0].iov_len = (exercise / 2) & ~1UL; + iov[1].iov_base = buffer + iov[0].iov_len; + iov[1].iov_len = 2; + iov[2].iov_base = buffer + iov[0].iov_len + 2; + iov[2].iov_len = exercise - iov[0].iov_len - 2; + + in_csum_iov = in_cksumv(iov, 3); + if (in_csum_iov != in_csum) + printf("\nverify: in_cksumv failed, lens: %zu+%zu+%zu, got %04x, expected %04x\n", + iov[0].iov_len, iov[1].iov_len, + iov[2].iov_len, in_csum_iov, in_csum); + } + + if (exercise >= FLETCHER_CHECKSUM_VALIDATE) + continue; + + ospfd = ospfd_checksum(buffer, exercise + sizeof(uint16_t), + exercise); + if (verify(buffer, exercise + sizeof(uint16_t))) + printf("\nverify: ospfd failed\n"); + isisd = iso_csum_create(buffer, exercise + sizeof(uint16_t), + exercise); + if (verify(buffer, exercise + sizeof(uint16_t))) + printf("\nverify: isisd failed\n"); + lib = fletcher_checksum(buffer, exercise + sizeof(uint16_t), + exercise); + if (verify(buffer, exercise + sizeof(uint16_t))) + printf("\nverify: lib failed\n"); + + if (ospfd != lib) { + printf("\nMismatch in values at size %d\n" + "ospfd: 0x%04x\tc0: %d\tc1: %d\tx: %d\ty: %d\n" + "isisd: 0x%04x\tc0: %d\tc1: %d\tx: %d\ty: %d\n" + "lib: 0x%04x\n", + exercise, ospfd, ospfd_vals.a.c0, + ospfd_vals.a.c1, ospfd_vals.x, ospfd_vals.y, + isisd, isisd_vals.a.c0, isisd_vals.a.c1, + isisd_vals.x, isisd_vals.y, lib); + + /* Investigate reduction phase discrepencies */ + if (ospfd_vals.a.c0 == isisd_vals.a.c0 + && ospfd_vals.a.c1 == isisd_vals.a.c1) { + printf("\n"); + for (i = 0; reducts[i].name != NULL; i++) { + ospfd = reducts[i].f( + &ospfd_vals, + exercise + sizeof(uint16_t), + exercise); + printf("%20s: x: %02x, y %02x, checksum 0x%04x\n", + reducts[i].name, + ospfd_vals.x & 0xff, + ospfd_vals.y & 0xff, ospfd); + } + } + + printf("\n uint8_t testdata [] = {\n "); + for (i = 0; i < exercise; i++) { + printf("0x%02x,%s", buffer[i], + (i + 1) % 8 ? " " : "\n "); + } + printf("\n}\n"); + exit(1); + } + } +} diff --git a/tests/lib/test_darr.c b/tests/lib/test_darr.c new file mode 100644 index 0000000..74aedac --- /dev/null +++ b/tests/lib/test_darr.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * June 23 2023, Christian Hopps + * + * Copyright (c) 2023, LabN Consulting, L.L.C. + * + */ +#include +#include "darr.h" + +/* + * Public functions to test: + * [x] - darr_append + * [x] - darr_append_n + * [x] - darr_append_nz + * [x] - darr_cap + * [x] - darr_ensure_avail + * [x] - darr_ensure_cap + * [x] - darr_ensure_i + * [x] - darr_foreach_i + * [x] - darr_foreach_p + * [x] - darr_free + * [x] - darr_insert + * [ ] - darr_insertz + * [x] - darr_insert_n + * [x] - darr_insert_nz + * [x] - darr_in_sprintf + * [x] - darr_in_strcat + * [x] - darr_in_strcat_tail + * [ ] - darr_in_strcatf + * [ ] - darr_in_vstrcatf + * [x] - darr_in_strdup + * [x] - darr_in_strdup_cap + * [-] - darr_in_vsprintf + * [x] - darr_lasti + * [x] - darr_maxi + * [x] - darr_pop + * [x] - darr_push + * [ ] - darr_pushz + * [x] - darr_remove + * [x] - darr_remove_n + * [x] - darr_reset + * [x] - darr_setlen + * [x] - darr_set_strlen + * [x] - darr_sprintf + * [x] - darr_strdup + * [x] - darr_strdup_cap + * [x] - darr_strlen + * [x] - darr_strnul + * [ ] - darr_vsprintf + */ + +static void test_int(void) +{ + int z105[105] = {0}; + int a1[] = {0, 1, 2, 3, 4}; + int a2[] = {4, 3, 2, 1, 0}; + int *da1 = NULL; + int *da2 = NULL; + int *dap; + uint i; + + assert(darr_len(da1) == 0); + assert(darr_lasti(da1) == -1); + assert(darr_last(da1) == NULL); + assert(darr_end(da1) == NULL); + + darr_ensure_i(da1, 0); + da1[0] = 0; + assert(darr_len(da1) == 1); + assert(darr_cap(da1) == 1); + + *darr_ensure_i(da1, 1) = 1; + assert(darr_len(da1) == 2); + assert(darr_cap(da1) == 2); + + darr_ensure_i(da1, 4); + darr_foreach_i (da1, i) + da1[i] = i; + + assert(darr_len(da1) == 5); + assert(darr_lasti(da1) == 4); + /* minimum non-pow2 array size for long long and smaller */ + assert(darr_cap(da1) == 8); + assert(!memcmp(da1, a1, sizeof(a1))); + assert(&da1[darr_lasti(da1)] == darr_last(da1)); + + /* reverse the numbers */ + darr_foreach_p (da1, dap) + *dap = darr_end(da1) - dap - 1; + assert(!memcmp(da1, a2, sizeof(a2))); + + darr_append_n(da1, 100); + darr_foreach_p (da1, dap) + *dap = darr_end(da1) - dap - 1; + + darr_pop_n(da1, 100); + darr_append_nz(da1, 100); + assert(!memcmp(&da1[5], z105, _darr_esize(da1) * 100)); + + assert(darr_len(da1) == 105); + assert(darr_maxi(da1) == 127); + assert(darr_cap(da1) == 128); + + darr_setlen(da1, 102); + assert(darr_len(da1) == 102); + assert(darr_maxi(da1) == 127); + + int a3[] = { 0xdeadbeaf, 0x12345678 }; + + da1[0] = a3[0]; + da1[101] = a3[1]; + darr_remove_n(da1, 1, 100); + assert(darr_len(da1) == array_size(a3)); + assert(!memcmp(da1, a3, sizeof(a3))); + + da1[0] = a3[1]; + da1[1] = a3[0]; + + darr_insert_n(da1, 1, 100); + assert(darr_len(da1) == 102); + assert(da1[0] == a3[1]); + assert(da1[101] == a3[0]); + + darr_reset(da1); + assert(darr_len(da1) == 0); + assert(darr_maxi(da1) == 127); + assert(darr_cap(da1) == 128); + + /* we touch the length field of the freed block here somehow */ + darr_insert_n(da1, 100, 300); + assert(darr_len(da1) == 400); + assert(darr_cap(da1) == 512); + + da1[400 - 1] = 0x0BAD; + *darr_insert(da1, 0) = 0xF00D; + assert(da1[0] == 0xF00D); + assert(da1[400] == 0x0BAD); + assert(darr_len(da1) == 401); + assert(darr_cap(da1) == 512); + + darr_free(da1); + assert(da1 == NULL); + assert(darr_len(da1) == 0); + darr_setlen(da1, 0); + darr_reset(da1); + darr_free(da1); + + *darr_append(da2) = 0; + *darr_append(da2) = 1; + darr_push(da2, 2); + darr_push(da2, 3); + darr_push(da2, 4); + + assert(!memcmp(da2, a1, sizeof(a1))); + + assert(darr_pop(da2) == 4); + assert(darr_pop(da2) == 3); + assert(darr_pop(da2) == 2); + assert(darr_len(da2) == 2); + assert(darr_pop(da2) == 1); + assert(darr_pop(da2) == 0); + assert(darr_len(da2) == 0); + + darr_free(da2); +} + +static void test_struct(void) +{ + /* + *uwould like to use different sizes with padding but memcmp can't be + *used then. + */ + struct st { + long long a; + long long b; + }; + struct st z102[102] = {{0, 0}}; + struct st *da1 = NULL; + struct st *da2 = NULL; + struct st a1[] = { + {0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, + }; + uint i; + + darr_ensure_i(da1, 0); + da1[0].a = 0; + da1[0].b = 0; + assert(darr_len(da1) == 1); + assert(darr_cap(da1) == 1); + + darr_ensure_i(da1, 1)->a = 1; + darr_ensure_i(da1, 1)->b = 1; + assert(darr_len(da1) == 2); + assert(darr_cap(da1) == 2); + + darr_ensure_i(da1, 4); + da1[2].a = 2; + da1[2].b = 2; + + da1[3].a = 3; + da1[3].b = 3; + + da1[4].a = 4; + da1[4].b = 4; + + assert(darr_len(da1) == 5); + /* minimum non-pow2 array size for long long and smaller */ + assert(darr_cap(da1) == 8); + assert(!memcmp(da1, a1, sizeof(a1))); + + assert(darr_cap(da1) - darr_len(da1) == 3); + darr_ensure_avail(da1, 2); + assert(darr_cap(da1) == 8); + darr_ensure_avail(da1, 3); + assert(darr_cap(da1) == 8); + darr_ensure_avail(da1, 4); + assert(darr_cap(da1) == 16); + + darr_ensure_cap(da1, 16); + assert(darr_cap(da1) == 16); + + darr_ensure_cap(da1, 20); + assert(darr_cap(da1) == 32); + + darr_append_n(da1, 100); + + assert(darr_len(da1) == 105); + assert(darr_maxi(da1) == 127); + assert(darr_cap(da1) == 128); + + darr_setlen(da1, 102); + assert(darr_len(da1) == 102); + assert(darr_maxi(da1) == 127); + + struct st a2[] = { + {0xdeadbeaf, 0xdeadbeaf}, + {0x12345678, 0x12345678}, + }; + da1[0] = a2[0]; + da1[101] = a2[1]; + darr_remove_n(da1, 1, 100); + assert(darr_len(da1) == array_size(a2)); + assert(!memcmp(da1, a2, sizeof(a2))); + + da1[0] = a2[1]; + da1[1] = a2[0]; + + darr_insert_n(da1, 1, 100); + assert(darr_len(da1) == 102); + darr_foreach_i (da1, i) { + da1[i].a = i; + da1[i].b = i; + } + darr_remove_n(da1, 1, 100); + assert(darr_len(da1) == 2); + darr_insert_nz(da1, 1, 100); + assert(!memcmp(&da1[1], z102, 100 * sizeof(da1[0]))); + /* assert(da1[0] == a2[1]); */ + /* assert(da1[101] == a2[0]); */ + + darr_reset(da1); + assert(darr_len(da1) == 0); + assert(darr_maxi(da1) == 127); + assert(darr_cap(da1) == 128); + + /* we touch the length field of the freed block here somehow */ + darr_insert_n(da1, 100, 300); + + assert(darr_len(da1) == 400); + assert(darr_cap(da1) == 512); + + darr_free(da1); + assert(da1 == NULL); + + assert(darr_len(da1) == 0); + darr_setlen(da1, 0); + darr_reset(da1); + + darr_free(da1); + + struct st i0 = {0, 0}; + struct st i1 = {1, 1}; + struct st i2 = {2, 2}; + struct st i3 = {3, 3}; + struct st i4 = {4, 4}; + + *darr_append(da2) = i0; + *darr_append(da2) = i1; + darr_push(da2, i2); + darr_push(da2, i3); + darr_push(da2, i4); + + assert(!memcmp(da2, a1, sizeof(a1))); + + struct st p0, p1, p2, p3, p4; + + p4 = darr_pop(da2); + p3 = darr_pop(da2); + p2 = darr_pop(da2); + p1 = darr_pop(da2); + p0 = darr_pop(da2); + assert(darr_len(da2) == 0); + assert(p4.a == i4.a && p4.b == i4.b); + assert(p3.a == i3.a && p3.b == i3.b); + assert(p2.a == i2.a && p2.b == i2.b); + assert(p1.a == i1.a && p1.b == i1.b); + assert(p0.a == i0.a && p0.b == i0.b); + + darr_free(da2); +} + +static void test_string(void) +{ + const char *src = "ABCDE"; + const char *add = "FGHIJ"; + uint srclen = strlen(src); + uint addlen = strlen(add); + char *da1 = NULL; + char *da2 = NULL; + + assert(darr_strlen(da1) == 0); + + da1 = darr_strdup(src); + assert(darr_strlen(da1) == strlen(da1)); + assert(darr_strlen(da1) == srclen); + assert(darr_len(da1) == srclen + 1); + assert(darr_ilen(da1) == (int)srclen + 1); + assert(darr_cap(da1) >= 8); + assert(darr_last(da1) == darr_strnul(da1)); + assert(darr_strnul(da1) == da1 + darr_strlen(da1)); + + da2 = da1; + darr_in_strdup(da1, src); + assert(da1 == da2); + assert(darr_strlen(da1) == strlen(da1)); + assert(darr_strlen(da1) == srclen); + assert(darr_len(da1) == srclen + 1); + darr_free(da1); + assert(da1 == NULL); + + da1 = darr_strdup_cap(src, 128); + assert(darr_strlen(da1) == srclen); + assert(darr_cap(da1) >= 128); + + darr_in_strdup_cap(da1, src, 256); + assert(darr_strlen(da1) == srclen); + assert(darr_cap(da1) >= 256); + darr_free(da1); + + da1 = darr_strdup_cap(add, 2); + assert(darr_strlen(da1) == addlen); + assert(darr_cap(da1) >= 8); + + darr_in_strdup(da1, "ab"); + darr_in_strcat(da1, "/"); + darr_in_strcat(da1, "foo"); + assert(!strcmp("ab/foo", da1)); + darr_free(da1); + + da1 = darr_in_strcat(da1, "ab"); + darr_in_strcat(da1, "/"); + darr_in_strcat(da1, "foo"); + assert(!strcmp("ab/foo", da1)); + + darr_set_strlen(da1, 5); + assert(!strcmp("ab/fo", da1)); + darr_set_strlen(da1, 1); + assert(!strcmp("a", da1)); + + darr_in_strdup(da1, "ab"); + da2 = darr_strdup(add); + darr_in_strcat_tail(da1, da2); + assert(!strcmp("abHIJ", da1)); + assert(darr_strlen(da1) == 5); + assert(darr_len(da1) == 6); + darr_free(da1); + darr_free(da2); + + da1 = darr_strdup("abcde"); + da2 = darr_strdup(add); + darr_in_strcat_tail(da1, da2); + assert(!strcmp("abcde", da1)); + assert(darr_strlen(da1) == 5); + assert(darr_len(da1) == 6); + darr_free(da1); + darr_free(da2); + + da1 = darr_sprintf("0123456789: %08X", 0xDEADBEEF); + assert(!strcmp(da1, "0123456789: DEADBEEF")); + assert(darr_strlen(da1) == 20); + assert(darr_cap(da1) == 128); + da2 = da1; + darr_in_sprintf(da1, "9876543210: %08x", 0x0BADF00D); + assert(da1 == da2); + assert(!strcmp("9876543210: 0badf00d", da2)); + darr_free(da1); + da2 = NULL; + + da1 = NULL; + darr_in_sprintf(da1, "0123456789: %08X", 0xDEADBEEF); + assert(!strcmp(da1, "0123456789: DEADBEEF")); + assert(darr_strlen(da1) == 20); + assert(darr_cap(da1) == 128); + darr_free(da1); + + da1 = darr_sprintf("0123456789: %08x", 0xDEADBEEF); + darr_in_strcatf(da1, " 9876543210: %08x", 0x0BADF00D); + assert(!strcmp("0123456789: deadbeef 9876543210: 0badf00d", da1)); + darr_free(da1); + + da1 = darr_in_strcatf(da1, "0123456789: %08x", 0xDEADBEEF); + assert(!strcmp("0123456789: deadbeef", da1)); + darr_free(da1); +} + +int main(int argc, char **argv) +{ + test_int(); + test_struct(); + test_string(); +} diff --git a/tests/lib/test_darr.py b/tests/lib/test_darr.py new file mode 100644 index 0000000..dea3bdf --- /dev/null +++ b/tests/lib/test_darr.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestDarr(frrtest.TestMultiOut): + program = "./test_darr" + + +TestDarr.exit_cleanly() diff --git a/tests/lib/test_frrlua.c b/tests/lib/test_frrlua.c new file mode 100644 index 0000000..2760a27 --- /dev/null +++ b/tests/lib/test_frrlua.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * frrlua unit tests + * Copyright (C) 2021 Donald Lee + */ + +#include +#include "string.h" +#include "stdio.h" +#include "lib/frrlua.h" + +static void test_encode_decode(void) +{ + lua_State *L = luaL_newstate(); + + int a = 123; + int b = a; + + lua_pushintegerp(L, &a); + lua_decode_integerp(L, -1, &a); + assert(a == b); + assert(lua_gettop(L) == 0); + + long long ll_a = 123L; + long long ll_b = a; + + lua_pushlonglongp(L, &ll_a); + lua_decode_longlongp(L, -1, &ll_a); + assert(ll_a == ll_b); + assert(lua_gettop(L) == 0); + + time_t time_a = 100; + time_t time_b; + + lua_pushinteger(L, time_a); + time_b = lua_tointeger(L, -1); + lua_pop(L, 1); + assert(time_a == time_b); + assert(lua_gettop(L) == 0); + + char str_b[] = "Hello", str_a[6]; + + strlcpy(str_a, str_b, sizeof(str_b)); + lua_pushstring_wrapper(L, str_a); + lua_decode_stringp(L, -1, str_a); + assert(strncmp(str_a, str_b, sizeof(str_b)) == 0); + assert(lua_gettop(L) == 0); + + char p_b_str[] = "10.0.0.0/24", p_a_str[12]; + struct prefix p_a; + + strlcpy(p_a_str, p_b_str, sizeof(p_b_str)); + str2prefix(p_a_str, &p_a); + lua_pushprefix(L, &p_a); + lua_decode_prefix(L, -1, &p_a); + prefix2str(&p_a, p_a_str, sizeof(p_b_str)); + assert(strncmp(p_a_str, p_b_str, sizeof(p_b_str)) == 0); + assert(lua_gettop(L) == 0); + + struct interface ifp_a = {}; + struct interface ifp_b = ifp_a; + + lua_pushinterface(L, &ifp_a); + lua_decode_interface(L, -1, &ifp_a); + assert(strncmp(ifp_a.name, ifp_b.name, sizeof(ifp_b.name)) == 0); + assert(ifp_a.ifindex == ifp_b.ifindex); + assert(ifp_a.status == ifp_b.status); + assert(ifp_a.flags == ifp_b.flags); + assert(ifp_a.metric == ifp_b.metric); + assert(ifp_a.speed == ifp_b.speed); + assert(ifp_a.mtu == ifp_b.mtu); + assert(ifp_a.mtu6 == ifp_b.mtu6); + assert(ifp_a.bandwidth == ifp_b.bandwidth); + assert(ifp_a.link_ifindex == ifp_b.link_ifindex); + assert(ifp_a.ll_type == ifp_b.ll_type); + assert(lua_gettop(L) == 0); + + struct in_addr addr_a = {}; + struct in_addr addr_b = addr_a; + + lua_pushinaddr(L, &addr_a); + lua_decode_inaddr(L, -1, &addr_a); + assert(addr_a.s_addr == addr_b.s_addr); + assert(lua_gettop(L) == 0); + + struct in6_addr in6addr_a = {}; + struct in6_addr in6addr_b = in6addr_a; + + lua_pushin6addr(L, &in6addr_a); + lua_decode_in6addr(L, -1, &in6addr_a); + assert(in6addr_cmp(&in6addr_a, &in6addr_b) == 0); + assert(lua_gettop(L) == 0); + + union sockunion su_a, su_b; + + memset(&su_a, 0, sizeof(union sockunion)); + memset(&su_b, 0, sizeof(union sockunion)); + lua_pushsockunion(L, &su_a); + lua_decode_sockunion(L, -1, &su_a); + assert(sockunion_cmp(&su_a, &su_b) == 0); + assert(lua_gettop(L) == 0); +} + +int main(int argc, char **argv) +{ + test_encode_decode(); +} diff --git a/tests/lib/test_frrlua.py b/tests/lib/test_frrlua.py new file mode 100644 index 0000000..2f6ddc1 --- /dev/null +++ b/tests/lib/test_frrlua.py @@ -0,0 +1,14 @@ +import frrtest +import pytest + +if 'S["SCRIPTING_TRUE"]=""\n' not in open("../config.status").readlines(): + class TestFrrlua: + @pytest.mark.skipif(True, reason="Test unsupported") + def test_exit_cleanly(self): + pass +else: + + class TestFrrlua(frrtest.TestMultiOut): + program = "./test_frrlua" + + TestFrrlua.exit_cleanly() diff --git a/tests/lib/test_frrscript.c b/tests/lib/test_frrscript.c new file mode 100644 index 0000000..9698aea --- /dev/null +++ b/tests/lib/test_frrscript.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * frrscript unit tests + * Copyright (C) 2021 Donald Lee + */ + +#include + +#include "lib/frrscript.h" +#include "lib/frrlua.h" + +int main(int argc, char **argv) +{ + frrscript_init("./lib"); + struct frrscript *fs = frrscript_new("script1"); + int result; + + /* Positive testing */ + + long long a = 100, b = 200; + + result = frrscript_load(fs, "foo", NULL); + assert(result == 0); + result = frrscript_call(fs, "foo", ("a", &a), ("b", &b)); + assert(result == 0); + assert(a == 101); + assert(b == 201); + + a = 100, b = 200; + + result = frrscript_load(fs, "bar", NULL); + assert(result == 0); + result = frrscript_call(fs, "bar", ("a", &a), ("b", &b)); + assert(result == 0); + long long *cptr = frrscript_get_result(fs, "bar", "c", lua_tolonglongp); + + /* a should not occur in the returned table in script */ + assert(a == 100); + assert(b == 201); + assert(*cptr == 303); + XFREE(MTYPE_SCRIPT_RES, cptr); + + long long n = 5; + + result = frrscript_load(fs, "fact", NULL); + assert(result == 0); + result = frrscript_call(fs, "fact", ("n", &n)); + assert(result == 0); + long long *ansptr = + frrscript_get_result(fs, "fact", "ans", lua_tolonglongp); + assert(*ansptr == 120); + + /* check consecutive call + get_result without re-loading */ + n = 4; + result = frrscript_call(fs, "fact", ("n", &n)); + assert(result == 0); + ansptr = frrscript_get_result(fs, "fact", "ans", lua_tointegerp); + assert(*ansptr == 24); + + XFREE(MTYPE_SCRIPT_RES, ansptr); + + /* Negative testing */ + + /* Function does not exist in script file*/ + result = frrscript_load(fs, "does_not_exist", NULL); + assert(result == 1); + + /* Function was not (successfully) loaded */ + result = frrscript_call(fs, "does_not_exist", ("a", &a), ("b", &b)); + assert(result == 1); + + /* Get result from a function that was not loaded */ + long long *llptr = + frrscript_get_result(fs, "does_not_exist", "c", lua_tointegerp); + assert(llptr == NULL); + + /* Function returns void */ + result = frrscript_call(fs, "bad_return1"); + assert(result == 1); + + /* Function returns number */ + result = frrscript_call(fs, "bad_return2"); + assert(result == 1); + + /* Get non-existent result from a function */ + result = frrscript_call(fs, "bad_return3"); + assert(result == 1); + long long *cllptr = + frrscript_get_result(fs, "bad_return3", "c", lua_tointegerp); + assert(cllptr == NULL); + + /* Function throws exception */ + result = frrscript_call(fs, "bad_return4"); + assert(result == 1); + + frrscript_delete(fs); + + return 0; +} diff --git a/tests/lib/test_frrscript.py b/tests/lib/test_frrscript.py new file mode 100644 index 0000000..046d97b --- /dev/null +++ b/tests/lib/test_frrscript.py @@ -0,0 +1,14 @@ +import frrtest +import pytest + +if 'S["SCRIPTING_TRUE"]=""\n' not in open("../config.status").readlines(): + class TestFrrscript: + @pytest.mark.skipif(True, reason="Test unsupported") + def test_exit_cleanly(self): + pass +else: + + class TestFrrscript(frrtest.TestMultiOut): + program = "./test_frrscript" + + TestFrrscript.exit_cleanly() diff --git a/tests/lib/test_graph.c b/tests/lib/test_graph.c new file mode 100644 index 0000000..86af02a --- /dev/null +++ b/tests/lib/test_graph.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test graph data structure. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#include +#include +#include +#include + +#define NUMNODES 32 + +static void graph_custom_print_cb(struct graph_node *gn, struct buffer *buf) +{ + char nbuf[64]; + char *gname = gn->data; + + for (unsigned int i = 0; i < vector_active(gn->to); i++) { + struct graph_node *adj = vector_slot(gn->to, i); + char *name = adj->data; + + snprintf(nbuf, sizeof(nbuf), " n%s -> n%s;\n", gname, name); + buffer_putstr(buf, nbuf); + } +} + +int main(int argc, char **argv) +{ + struct graph *g = graph_new(); + struct graph_node *gn[NUMNODES]; + char names[NUMNODES][16]; + + /* create vertices */ + for (unsigned int i = 0; i < NUMNODES; i++) { + snprintf(names[i], sizeof(names[i]), "%u", i); + gn[i] = graph_new_node(g, names[i], NULL); + } + + /* create edges */ + for (unsigned int i = 1; i < NUMNODES - 1; i++) { + graph_add_edge(gn[0], gn[i]); + graph_add_edge(gn[i], gn[i + 1]); + } + graph_add_edge(gn[0], gn[NUMNODES - 1]); + graph_add_edge(gn[NUMNODES - 1], gn[1]); + + /* print DOT */ + char *dumped = graph_dump_dot(g, gn[0], graph_custom_print_cb); + + fprintf(stdout, "%s", dumped); + XFREE(MTYPE_TMP, dumped); + + /* remove some edges */ + for (unsigned int i = NUMNODES - 1; i > NUMNODES / 2; --i) + for (unsigned int j = 0; j < NUMNODES; j++) + graph_remove_edge(gn[i], gn[j]); + + /* remove some nodes */ + for (unsigned int i = 0; i < NUMNODES / 2; i++) + graph_delete_node(g, gn[i]); + + graph_delete_graph(g); +} diff --git a/tests/lib/test_graph.py b/tests/lib/test_graph.py new file mode 100644 index 0000000..b26986c --- /dev/null +++ b/tests/lib/test_graph.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestGraph(frrtest.TestRefOut): + program = "./test_graph" diff --git a/tests/lib/test_graph.refout b/tests/lib/test_graph.refout new file mode 100644 index 0000000..955f552 --- /dev/null +++ b/tests/lib/test_graph.refout @@ -0,0 +1,64 @@ +digraph { + n0 -> n1; + n0 -> n2; + n0 -> n3; + n0 -> n4; + n0 -> n5; + n0 -> n6; + n0 -> n7; + n0 -> n8; + n0 -> n9; + n0 -> n10; + n0 -> n11; + n0 -> n12; + n0 -> n13; + n0 -> n14; + n0 -> n15; + n0 -> n16; + n0 -> n17; + n0 -> n18; + n0 -> n19; + n0 -> n20; + n0 -> n21; + n0 -> n22; + n0 -> n23; + n0 -> n24; + n0 -> n25; + n0 -> n26; + n0 -> n27; + n0 -> n28; + n0 -> n29; + n0 -> n30; + n0 -> n31; + n31 -> n1; + n1 -> n2; + n2 -> n3; + n3 -> n4; + n4 -> n5; + n5 -> n6; + n6 -> n7; + n7 -> n8; + n8 -> n9; + n9 -> n10; + n10 -> n11; + n11 -> n12; + n12 -> n13; + n13 -> n14; + n14 -> n15; + n15 -> n16; + n16 -> n17; + n17 -> n18; + n18 -> n19; + n19 -> n20; + n20 -> n21; + n21 -> n22; + n22 -> n23; + n23 -> n24; + n24 -> n25; + n25 -> n26; + n26 -> n27; + n27 -> n28; + n28 -> n29; + n29 -> n30; + n30 -> n31; +} diff --git a/tests/lib/test_grpc.cpp b/tests/lib/test_grpc.cpp new file mode 100644 index 0000000..2023136 --- /dev/null +++ b/tests/lib/test_grpc.cpp @@ -0,0 +1,979 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * May 16 2021, Christian Hopps + * + * Copyright (c) 2021, LabN Consulting, L.L.C + */ + +#include +#include +#include + +#include "filter.h" +#include "frr_pthread.h" +#include "libfrr.h" +#include "routing_nb.h" +#include "northbound_cli.h" +#include "frrevent.h" +#include "vrf.h" +#include "vty.h" + +#include "staticd/static_debug.h" +#include "staticd/static_nb.h" +#include "staticd/static_vrf.h" +#include "staticd/static_vty.h" +#include "staticd/static_zebra.h" + +// GRPC C++ includes +#include +#include +#include +#include +#include +#include +#include +#include "grpc/frr-northbound.grpc.pb.h" + +DEFINE_HOOK(test_grpc_late_init, (struct event_loop * tm), (tm)); +DEFINE_KOOH(test_grpc_fini, (), ()); + +struct vty *vty; + +bool mpls_enabled; +struct event_loop *master; +struct zebra_privs_t static_privs = {0}; +struct frrmod_runtime *grpc_module; +char binpath[2 * MAXPATHLEN + 1]; + +extern const char *json_expect1; +extern const char *json_expect2; +extern const char *json_expect3; +extern const char *json_loadconf1; + +int test_dbg = 1; + +void inline test_debug(const std::string &s) +{ + if (test_dbg) + std::cout << s << std::endl; +} + +// static struct option_chain modules[] = {{ .arg = "grpc:50051" }] +// static struct option_chain **modnext = modules->next; + +static const struct frr_yang_module_info *const staticd_yang_modules[] = { + &frr_interface_info, &frr_filter_info, &frr_routing_info, + &frr_staticd_info, &frr_vrf_info, +}; + +static void grpc_thread_stop(struct event *thread); + +static void _err_print(const void *cookie, const char *errstr) +{ + std::cout << "Failed to load grpc module:" << errstr << std::endl; +} + +static void static_startup(void) +{ + // struct frrmod_runtime module; + // static struct option_chain *oc; + + cmd_init(1); + + zlog_aux_init("NONE: ", LOG_DEBUG); + zprivs_preinit(&static_privs); + zprivs_init(&static_privs); + + /* Load the server side module -- check libtool path first */ + std::string modpath = std::string(binpath) + std::string("../../lib/.libs"); + grpc_module = frrmod_load("grpc:50051", modpath.c_str(), 0, 0); + if (!grpc_module) { + modpath = std::string(binpath) + std::string("../../lib"); + grpc_module = frrmod_load("grpc:50051", modpath.c_str(), + _err_print, 0); + } + if (!grpc_module) { + modpath = std::string(binpath) + + std::string("../../../lib/.libs"); + grpc_module = frrmod_load("grpc:50051", modpath.c_str(), + _err_print, 0); + } + if (!grpc_module) { + modpath = std::string(binpath) + std::string("../../../lib"); + grpc_module = frrmod_load("grpc:50051", modpath.c_str(), + _err_print, 0); + } + if (!grpc_module) + exit(1); + + static_debug_init(); + + master = event_master_create(NULL); + nb_init(master, staticd_yang_modules, array_size(staticd_yang_modules), + false); + + static_zebra_init(); + vty_init(master, true); + static_vrf_init(); + static_vty_init(); + + hook_register(routing_conf_event, + routing_control_plane_protocols_name_validate); + hook_register(routing_create, + routing_control_plane_protocols_staticd_create); + hook_register(routing_destroy, + routing_control_plane_protocols_staticd_destroy); + + // Add a route + vty = vty_new(); + vty->type = vty::VTY_TERM; + vty_config_enter(vty, true, false, false); + + auto ret = cmd_execute(vty, "ip route 11.0.0.0/8 Null0", NULL, 0); + assert(!ret); + + ret = cmd_execute(vty, "end", NULL, 0); + assert(!ret); + + nb_cli_pending_commit_check(vty); + + frr_pthread_init(); + + // frr_config_fork(); + hook_call(test_grpc_late_init, master); +} + +static void static_shutdown(void) +{ + hook_call(test_grpc_fini); + vty_close(vty); + vrf_terminate(); + vty_terminate(); + cmd_terminate(); + nb_terminate(); + yang_terminate(); + event_master_free(master); + master = NULL; +} + +using frr::Northbound; +using grpc::Channel; +using grpc::ClientAsyncResponseReader; +using grpc::ClientContext; +using grpc::CompletionQueue; +using grpc::Status; + +class NorthboundClient +{ + public: + NorthboundClient(std::shared_ptr channel) + : stub_(frr::Northbound::NewStub(channel)) + { + } + + void Commit(uint32_t candidate_id) + { + frr::CommitRequest request; + frr::CommitResponse reply; + ClientContext context; + Status status; + + request.set_candidate_id(candidate_id); + + request.set_phase(frr::CommitRequest::ALL); + status = stub_->Commit(&context, request, &reply); + _throw_if_not_ok(status); +#if 0 + request.set_phase(frr::CommitRequest::VALIDATE); + status = stub_->Commit(&context, request, &reply); + _throw_if_not_ok(status); + + request.set_phase(frr::CommitRequest::PREPARE); + status = stub_->Commit(&context, request, &reply); + _throw_if_not_ok(status); + + request.set_phase(frr::CommitRequest::APPLY); + status = stub_->Commit(&context, request, &reply); + _throw_if_not_ok(status); +#endif + } + + uint32_t CreateCandidate() + { + frr::CreateCandidateRequest request; + frr::CreateCandidateResponse reply; + ClientContext context; + Status status; + + status = stub_->CreateCandidate(&context, request, &reply); + _throw_if_not_ok(status); + return reply.candidate_id(); + } + + void DeleteCandidate(uint32_t candidate_id) + { + frr::DeleteCandidateRequest request; + frr::DeleteCandidateResponse reply; + ClientContext context; + Status status; + + request.set_candidate_id(candidate_id); + status = stub_->DeleteCandidate(&context, request, &reply); + _throw_if_not_ok(status); + } + + void EditCandidate(uint32_t candidate_id, const std::string &path, + const std::string &value) + { + frr::EditCandidateRequest request; + frr::EditCandidateResponse reply; + ClientContext context; + + request.set_candidate_id(candidate_id); + frr::PathValue *pv = request.add_update(); + pv->set_path(path); + pv->set_value(value); + + Status status = stub_->EditCandidate(&context, request, &reply); + _throw_if_not_ok(status); + } + + std::string Get(const std::string &path, + frr::GetRequest::DataType dtype, frr::Encoding enc, + bool with_defaults) + { + frr::GetRequest request; + frr::GetResponse reply; + ClientContext context; + std::ostringstream ss; + + request.set_type(dtype); + request.set_encoding(enc); + request.set_with_defaults(with_defaults); + request.add_path(path); + + auto stream = stub_->Get(&context, request); + while (stream->Read(&reply)) { + ss << reply.data().data() << std::endl; + } + auto status = stream->Finish(); + _throw_if_not_ok(status); + return ss.str(); + } + + std::string GetCapabilities() + { + frr::GetCapabilitiesRequest request; + frr::GetCapabilitiesResponse reply; + ClientContext context; + + Status status = + stub_->GetCapabilities(&context, request, &reply); + _throw_if_not_ok(status); + + std::ostringstream ss; + ss << "Capabilities:" << std::endl + << "\tVersion: " << reply.frr_version() << std::endl + << "\tRollback Support: " << reply.rollback_support() + << std::endl + << "\tSupported Modules:"; + + for (int i = 0; i < reply.supported_modules_size(); i++) { + auto sm = reply.supported_modules(i); + ss << std::endl + << "\t\tName: \"" << sm.name() + << "\" Revision: " << sm.revision() << " Org: \"" + << sm.organization() << "\""; + } + + ss << std::endl << "\tSupported Encodings:"; + + for (int i = 0; i < reply.supported_encodings_size(); i++) { + auto se = reply.supported_encodings(i); + auto desc = + google::protobuf::GetEnumDescriptor(); + ss << std::endl + << "\t\t" << desc->FindValueByNumber(se)->name(); + } + + ss << std::endl; + + return ss.str(); + } + + void LoadToCandidate(uint32_t candidate_id, bool is_replace, + bool is_json, const std::string &data) + { + frr::LoadToCandidateRequest request; + frr::LoadToCandidateResponse reply; + frr::DataTree *dt = new frr::DataTree; + ClientContext context; + + request.set_candidate_id(candidate_id); + request.set_type(is_replace + ? frr::LoadToCandidateRequest::REPLACE + : frr::LoadToCandidateRequest::MERGE); + dt->set_encoding(is_json ? frr::JSON : frr::XML); + dt->set_data(data); + request.set_allocated_config(dt); + + Status status = + stub_->LoadToCandidate(&context, request, &reply); + _throw_if_not_ok(status); + } + + std::string ListTransactions() + { + frr::ListTransactionsRequest request; + frr::ListTransactionsResponse reply; + ClientContext context; + std::ostringstream ss; + + auto stream = stub_->ListTransactions(&context, request); + + while (stream->Read(&reply)) { + ss << "Tx ID: " << reply.id() + << " client: " << reply.client() + << " date: " << reply.date() + << " comment: " << reply.comment() << std::endl; + } + + auto status = stream->Finish(); + _throw_if_not_ok(status); + return ss.str(); + } + + private: + std::unique_ptr stub_; + + void _throw_if_not_ok(Status &status) + { + if (!status.ok()) + throw std::runtime_error( + std::to_string(status.error_code()) + ": " + + status.error_message()); + } +}; + + +bool stop = false; + +int grpc_client_test_stop(struct frr_pthread *fpt, void **result) +{ + test_debug("client: STOP pthread"); + + assert(fpt->running); + atomic_store_explicit(&fpt->running, false, memory_order_relaxed); + + test_debug("client: joining pthread"); + pthread_join(fpt->thread, result); + + test_debug("client: joined pthread"); + return 0; +} + +int find_first_diff(const std::string &s1, const std::string &s2) +{ + int s1len = s1.length(); + int s2len = s2.length(); + int mlen = std::min(s1len, s2len); + + for (int i = 0; i < mlen; i++) + if (s1[i] != s2[i]) + return i; + return s1len == s2len ? -1 : mlen; +} + +void assert_no_diff(const std::string &s1, const std::string &s2) +{ + int pos = find_first_diff(s1, s2); + if (pos == -1) + return; + std::cout << "not ok" << std::endl; + std::cout << "Same: " << s1.substr(0, pos) << std::endl; + std::cout << "Diff s1: " << s1.substr(pos) << std::endl; + std::cout << "Diff s2: " << s2.substr(pos) << std::endl; + assert(false); +} + +void assert_config_same(NorthboundClient &client, const std::string &compare) +{ + std::string confs = client.Get("/frr-routing:routing", + frr::GetRequest::ALL, frr::JSON, true); + assert_no_diff(confs, compare); + std::cout << "ok" << std::endl; +} + +void grpc_client_run_test(void) +{ + NorthboundClient client(grpc::CreateChannel( + "localhost:50051", grpc::InsecureChannelCredentials())); + + std::string reply = client.GetCapabilities(); + + uint32_t cid; + cid = client.CreateCandidate(); + std::cout << "CreateCandidate -> " << cid << std::endl; + assert(cid == 1); + client.DeleteCandidate(cid); + std::cout << "DeleteCandidate(" << cid << ")" << std::endl; + cid = client.CreateCandidate(); + assert(cid == 2); + std::cout << "CreateCandidate -> " << cid << std::endl; + + /* + * Get initial configuration + */ + std::cout << "Comparing initial config..."; + assert_config_same(client, json_expect1); + + /* + * Add config using EditCandidate + */ + + char xpath_buf[1024]; + strlcpy(xpath_buf, + "/frr-routing:routing/control-plane-protocols/" + "control-plane-protocol[type='frr-staticd:staticd']" + "[name='staticd'][vrf='default']/frr-staticd:staticd/route-list", + sizeof(xpath_buf)); + int slen = strlen(xpath_buf); + for (int i = 0; i < 4; i++) { + snprintf(xpath_buf + slen, sizeof(xpath_buf) - slen, + "[prefix='13.0.%d.0/24']" + "[afi-safi='frr-routing:ipv4-unicast']/" + "path-list[table-id='0'][distance='1']/" + "frr-nexthops/nexthop[nh-type='blackhole']" + "[vrf='default'][gateway=''][interface='(null)']", + i); + client.EditCandidate(cid, xpath_buf, ""); + } + client.Commit(cid); + std::cout << "Comparing EditCandidate config..."; + assert_config_same(client, json_expect2); + + client.DeleteCandidate(cid); + std::cout << "DeleteCandidate(" << cid << ")" << std::endl; + + /* + * Add config using LoadToCandidate + */ + + cid = client.CreateCandidate(); + std::cout << "CreateCandidate -> " << cid << std::endl; + + client.LoadToCandidate(cid, false, true, json_loadconf1); + client.Commit(cid); + + std::cout << "Comparing LoadToCandidate config..."; + assert_config_same(client, json_expect3); + + client.DeleteCandidate(cid); + std::cout << "DeleteCandidate(" << cid << ")" << std::endl; + + std::string ltxreply = client.ListTransactions(); + // std::cout << "client: pthread received: " << ltxreply << std::endl; +} + +void *grpc_client_test_start(void *arg) +{ + struct frr_pthread *fpt = (struct frr_pthread *)arg; + fpt->master->owner = pthread_self(); + frr_pthread_set_name(fpt); + frr_pthread_notify_running(fpt); + + try { + grpc_client_run_test(); + std::cout << "TEST PASSED" << std::endl; + } catch (std::exception &e) { + std::cout << "Exception in test: " << e.what() << std::endl; + } + + // Signal FRR event loop to stop + test_debug("client: pthread: adding event to stop us"); + event_add_event(master, grpc_thread_stop, NULL, 0, NULL); + + test_debug("client: pthread: DONE (returning)"); + + return NULL; +} + +static void grpc_thread_start(struct event *thread) +{ + struct frr_pthread_attr client = { + .start = grpc_client_test_start, + .stop = grpc_client_test_stop, + }; + + auto pth = frr_pthread_new(&client, "GRPC Client thread", "grpc"); + frr_pthread_run(pth, NULL); + frr_pthread_wait_running(pth); +} + +static void grpc_thread_stop(struct event *thread) +{ + std::cout << __func__ << ": frr_pthread_stop_all" << std::endl; + frr_pthread_stop_all(); + std::cout << __func__ << ": static_shutdown" << std::endl; + static_shutdown(); + std::cout << __func__ << ": exit cleanly" << std::endl; + exit(0); +} + +/* + * return abs path to this binary with trailing `/`. Does not parse path + * environment to find in path, which should not matter for unit testing. + */ +static int get_binpath(const char *argv0, char cwd[2 * MAXPATHLEN + 1]) +{ + const char *rch; + if (argv0[0] == '/') { + *cwd = 0; + rch = strrchr(argv0, '/'); + strlcpy(cwd, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1)); + return 0; + } + if (!(rch = strrchr(argv0, '/'))) { + /* Does not handle using PATH, shouldn't matter for test */ + errno = EINVAL; + return -1; + } + if (!getcwd(cwd, MAXPATHLEN)) + return -1; + int len = strlen(cwd); + cwd[len++] = '/'; + strlcpy(cwd + len, argv0, MIN(rch - argv0 + 2, 2 * MAXPATHLEN + 1)); + return 0; +} + +int main(int argc, char **argv) +{ + assert(argc >= 1); + if (get_binpath(argv[0], binpath) < 0) + exit(1); + + static_startup(); + + event_add_event(master, grpc_thread_start, NULL, 0, NULL); + + /* Event Loop */ + struct event thread; + while (event_fetch(master, &thread)) + event_call(&thread); + return 0; +} + +// clang-format off + +const char *json_expect1 = R"NONCE({ + "frr-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "frr-staticd:staticd", + "name": "staticd", + "vrf": "default", + "frr-staticd:staticd": { + "route-list": [ + { + "prefix": "11.0.0.0/8", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + } + ] + } + } + ] + } + }, + "frr-vrf:lib": { + "vrf": [ + { + "name": "default", + "state": { + "active": false + } + } + ] + } +} + +)NONCE"; + +const char *json_loadconf1 = R"NONCE( +{ + "frr-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "frr-staticd:staticd", + "name": "staticd", + "vrf": "default", + "frr-staticd:staticd": { + "route-list": [ + { + "prefix": "10.0.0.0/13", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)" + } + ] + } + } + ] + } + ] + } + } + ] + } + }, + "frr-vrf:lib": { + "vrf": [ + { + "name": "default" + } + ] + } +})NONCE"; + +const char *json_expect2 = R"NONCE({ + "frr-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "frr-staticd:staticd", + "name": "staticd", + "vrf": "default", + "frr-staticd:staticd": { + "route-list": [ + { + "prefix": "11.0.0.0/8", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.0.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.1.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.2.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.3.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + } + ] + } + } + ] + } + }, + "frr-vrf:lib": { + "vrf": [ + { + "name": "default", + "state": { + "active": false + } + } + ] + } +} + +)NONCE"; + +const char *json_expect3 = R"NONCE({ + "frr-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "frr-staticd:staticd", + "name": "staticd", + "vrf": "default", + "frr-staticd:staticd": { + "route-list": [ + { + "prefix": "11.0.0.0/8", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.0.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.1.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.2.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "13.0.3.0/24", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + }, + { + "prefix": "10.0.0.0/13", + "afi-safi": "frr-routing:ipv4-unicast", + "path-list": [ + { + "table-id": 0, + "distance": 1, + "tag": 0, + "frr-nexthops": { + "nexthop": [ + { + "nh-type": "blackhole", + "vrf": "default", + "gateway": "", + "interface": "(null)", + "bh-type": "null", + "onlink": false + } + ] + } + } + ] + } + ] + } + } + ] + } + }, + "frr-vrf:lib": { + "vrf": [ + { + "name": "default", + "state": { + "active": false + } + } + ] + } +} + +)NONCE"; diff --git a/tests/lib/test_grpc.py b/tests/lib/test_grpc.py new file mode 100644 index 0000000..7f722de --- /dev/null +++ b/tests/lib/test_grpc.py @@ -0,0 +1,33 @@ +import inspect +import os +import subprocess + +import frrtest +import pytest + + +class TestGRPC(object): + program = "./test_grpc" + + @pytest.mark.skipif( + 'S["GRPC_TRUE"]=""\n' not in open("../config.status").readlines(), + reason="GRPC not enabled", + ) + @pytest.mark.skipif( + not os.path.isdir("/usr/share/yang"), + reason="YANG models aren't installed in /usr/share/yang", + ) + def test_exits_cleanly(self): + basedir = os.path.dirname(inspect.getsourcefile(type(self))) + program = os.path.join(basedir, self.program) + proc = subprocess.Popen( + [frrtest.binpath(program)], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + output, _ = proc.communicate() + self.exitcode = proc.wait() + if self.exitcode != 0: + print("OUTPUT:\n" + output.decode("ascii")) + raise frrtest.TestExitNonzero(self) diff --git a/tests/lib/test_heavy.c b/tests/lib/test_heavy.c new file mode 100644 index 0000000..1e56940 --- /dev/null +++ b/tests/lib/test_heavy.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* This programme shows the effects of 'heavy' long-running functions + * on the cooperative threading model. + * + * Run it with a config file containing 'password whatever', telnet to it + * (it defaults to port 4000) and enter the 'clear foo string' command. + * then type whatever and observe that the vty interface is unresponsive + * for quite a period of time, due to the clear_something command + * taking a very long time to complete. + */ +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include + +#include "tests.h" + +enum { ITERS_FIRST = 0, + ITERS_ERR = 100, + ITERS_LATER = 400, + ITERS_PRINT = 10, + ITERS_MAX = 1000, +}; + +static void slow_func(struct vty *vty, const char *str, const int i) +{ + double x = 1; + int j; + + for (j = 0; j < 300; j++) + x += sin(x) * j; + + if ((i % ITERS_LATER) == 0) + printf("%s: %d, temporary error, save this somehow and do it later..\n", + __func__, i); + + if ((i % ITERS_ERR) == 0) + printf("%s: hard error\n", __func__); + + if ((i % ITERS_PRINT) == 0) + printf("%s did %d, x = %g\n", str, i, x); +} + +static void clear_something(struct vty *vty, const char *str) +{ + int i; + + /* this could be like iterating through 150k of route_table + * or worse, iterating through a list of peers, to bgp_stop them with + * each having 150k route tables to process... + */ + for (i = ITERS_FIRST; i < ITERS_MAX; i++) + slow_func(vty, str, i); +} + +DEFUN (clear_foo, + clear_foo_cmd, + "clear foo LINE...", + "clear command\n" + "arbitrary string\n") +{ + char *str; + if (!argc) { + vty_out(vty, "%% string argument required\n"); + return CMD_WARNING; + } + + str = argv_concat(argv, argc, 0); + + clear_something(vty, str); + XFREE(MTYPE_TMP, str); + return CMD_SUCCESS; +} + +static void slow_vty_init(void) +{ + install_element(VIEW_NODE, &clear_foo_cmd); +} + +void test_init(void) +{ + slow_vty_init(); +} diff --git a/tests/lib/test_heavy_thread.c b/tests/lib/test_heavy_thread.c new file mode 100644 index 0000000..4fb9ebf --- /dev/null +++ b/tests/lib/test_heavy_thread.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* This programme shows the effects of 'heavy' long-running functions + * on the cooperative threading model, as demonstrated by heavy.c, and how + * they can be mitigated using a background thread. + * + * Run it with a config file containing 'password whatever', telnet to it + * (it defaults to port 4000) and enter the 'clear foo string' command. + * then type whatever and observe that, unlike heavy.c, the vty interface + * remains responsive. + */ +#include +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "log.h" + +#include "tests.h" + +extern struct event_loop *master; + +enum { ITERS_FIRST = 0, + ITERS_ERR = 100, + ITERS_LATER = 400, + ITERS_PRINT = 10, + ITERS_MAX = 1000, +}; + +struct work_state { + struct vty *vty; + char *str; + int i; +}; + +static void slow_func(struct vty *vty, const char *str, const int i) +{ + double x = 1; + int j; + + for (j = 0; j < 300; j++) + x += sin(x) * j; + + if ((i % ITERS_LATER) == 0) + printf("%s: %d, temporary error, save this somehow and do it later..\n", + __func__, i); + + if ((i % ITERS_ERR) == 0) + printf("%s: hard error\n", __func__); + + if ((i % ITERS_PRINT) == 0) + printf("%s did %d, x = %g\n", str, i, x); +} + +static void clear_something(struct event *thread) +{ + struct work_state *ws = EVENT_ARG(thread); + + /* this could be like iterating through 150k of route_table + * or worse, iterating through a list of peers, to bgp_stop them with + * each having 150k route tables to process... + */ + while (ws->i < ITERS_MAX) { + slow_func(ws->vty, ws->str, ws->i); + ws->i++; + if (event_should_yield(thread)) { + event_add_timer_msec(master, clear_something, ws, 0, + NULL); + return; + } + } + + /* All done! */ + XFREE(MTYPE_TMP, ws->str); + XFREE(MTYPE_TMP, ws); +} + +DEFUN (clear_foo, + clear_foo_cmd, + "clear foo LINE...", + "clear command\n" + "arbitrary string\n") +{ + char *str; + struct work_state *ws; + + if (!argc) { + vty_out(vty, "%% string argument required\n"); + return CMD_WARNING; + } + + str = argv_concat(argv, argc, 0); + + ws = XMALLOC(MTYPE_TMP, sizeof(*ws)); + + ws->str = XSTRDUP(MTYPE_TMP, str); + + ws->vty = vty; + ws->i = ITERS_FIRST; + + event_add_timer_msec(master, clear_something, ws, 0, NULL); + + return CMD_SUCCESS; +} + +void test_init(void) +{ + install_element(VIEW_NODE, &clear_foo_cmd); +} diff --git a/tests/lib/test_heavy_wq.c b/tests/lib/test_heavy_wq.c new file mode 100644 index 0000000..8c2765c --- /dev/null +++ b/tests/lib/test_heavy_wq.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* This programme shows the effects of 'heavy' long-running functions + * on the cooperative threading model. + * + * Run it with a config file containing 'password whatever', telnet to it + * (it defaults to port 4000) and enter the 'clear foo string' command. + * then type whatever and observe that the vty interface is unresponsive + * for quite a period of time, due to the clear_something command + * taking a very long time to complete. + */ +#include + +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "log.h" +#include "workqueue.h" +#include + +#include "tests.h" + +DEFINE_MGROUP(TEST_HEAVYWQ, "heavy-wq test"); +DEFINE_MTYPE_STATIC(TEST_HEAVYWQ, WQ_NODE, "heavy_wq_node"); +DEFINE_MTYPE_STATIC(TEST_HEAVYWQ, WQ_NODE_STR, "heavy_wq_node->str"); + +extern struct event_loop *master; +static struct work_queue *heavy_wq; + +struct heavy_wq_node { + char *str; + int i; +}; + +enum { ITERS_FIRST = 0, + ITERS_ERR = 100, + ITERS_LATER = 400, + ITERS_PRINT = 10, + ITERS_MAX = 1000, +}; + +static void heavy_wq_add(struct vty *vty, const char *str, int i) +{ + struct heavy_wq_node *hn; + + hn = XCALLOC(MTYPE_WQ_NODE, sizeof(struct heavy_wq_node)); + + hn->i = i; + hn->str = XSTRDUP(MTYPE_WQ_NODE_STR, str); + + work_queue_add(heavy_wq, hn); + + return; +} + +static void slow_func_del(struct work_queue *wq, void *data) +{ + struct heavy_wq_node *hn = data; + assert(hn && hn->str); + printf("%s: %s\n", __func__, hn->str); + XFREE(MTYPE_WQ_NODE_STR, hn->str); + XFREE(MTYPE_WQ_NODE, hn); +} + +static wq_item_status slow_func(struct work_queue *wq, void *data) +{ + struct heavy_wq_node *hn = data; + double x = 1; + int j; + + assert(hn && hn->str); + + for (j = 0; j < 300; j++) + x += sin(x) * j; + + if ((hn->i % ITERS_PRINT) == 0) + printf("%s did %d, x = %g\n", hn->str, hn->i, x); + + return WQ_SUCCESS; +} + +static void clear_something(struct vty *vty, const char *str) +{ + int i; + + /* this could be like iterating through 150k of route_table + * or worse, iterating through a list of peers, to bgp_stop them with + * each having 150k route tables to process... + */ + for (i = ITERS_FIRST; i < ITERS_MAX; i++) + heavy_wq_add(vty, str, i); +} + +DEFUN (clear_foo, + clear_foo_cmd, + "clear foo LINE...", + "clear command\n" + "arbitrary string\n") +{ + char *str; + if (!argc) { + vty_out(vty, "%% string argument required\n"); + return CMD_WARNING; + } + + str = argv_concat(argv, argc, 0); + + clear_something(vty, str); + XFREE(MTYPE_TMP, str); + return CMD_SUCCESS; +} + +static int heavy_wq_init(void) +{ + heavy_wq = work_queue_new(master, "heavy_work_queue"); + + heavy_wq->spec.workfunc = &slow_func; + heavy_wq->spec.del_item_data = &slow_func_del; + heavy_wq->spec.max_retries = 3; + heavy_wq->spec.hold = 1000; + + return 0; +} + +void test_init(void) +{ + install_element(VIEW_NODE, &clear_foo_cmd); + heavy_wq_init(); +} diff --git a/tests/lib/test_idalloc.c b/tests/lib/test_idalloc.c new file mode 100644 index 0000000..ce1582b --- /dev/null +++ b/tests/lib/test_idalloc.c @@ -0,0 +1,197 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "id_alloc.h" + +#include +#include +#include +#include + +#define IDS_PER_PAGE (1<<(IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS)) +char allocated_markers[IDS_PER_PAGE*3]; + +int main(int argc, char **argv) +{ + int i, val; + uint32_t pg; + struct id_alloc *a; + + /* 1. Rattle test, shake it a little and make sure it doesn't make any + * noise :) + */ + a = idalloc_new("Rattle test"); + for (i = 0; i < 1000000; i++) + assert(idalloc_allocate(a) != 0); + + idalloc_destroy(a); + + /* 2. Reserve a few low IDs, make sure they are skipped by normal + * allocation. + */ + a = idalloc_new("Low Reservations"); + assert(idalloc_reserve(a, 1) == 1); + assert(idalloc_reserve(a, 3) == 3); + assert(idalloc_reserve(a, 5) == 5); + for (i = 0; i < 100; i++) { + val = idalloc_allocate(a); + assert(val != 1 && val != 3 && val != 5); + } + idalloc_destroy(a); + + /* 3. Single page testing. Check that IDs are kept unique, and all IDs + * in the existing page are allocated before a new page is added. + */ + memset(allocated_markers, 0, sizeof(allocated_markers)); + allocated_markers[IDALLOC_INVALID] = 1; + + a = idalloc_new("Single Page"); + + /* reserve the rest of the first page */ + for (i = 0; i < IDS_PER_PAGE - 1; i++) { + val = idalloc_allocate(a); + assert(val < IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + assert(a->capacity == IDS_PER_PAGE); + allocated_markers[val] = 1; + } + /* Check that the count is right */ + assert(a->allocated == IDS_PER_PAGE); + + /* Free some IDs out of the middle. */ + idalloc_free(a, 300); + allocated_markers[300] = 0; + idalloc_free(a, 400); + allocated_markers[400] = 0; + idalloc_free(a, 500); + allocated_markers[500] = 0; + + assert(a->allocated == IDS_PER_PAGE-3); + + /* Allocate the three IDs back and make sure they are pulled from the + * set just freed + */ + for (i = 0; i < 3; i++) { + val = idalloc_allocate(a); + assert(val < IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + assert(a->capacity == IDS_PER_PAGE); + allocated_markers[val] = 1; + } + idalloc_destroy(a); + + /* 4. Multi-page testing. */ + memset(allocated_markers, 0, sizeof(allocated_markers)); + allocated_markers[IDALLOC_INVALID] = 1; + + a = idalloc_new("Multi-page"); + + /* reserve the rest of the first page and all of the second and third */ + for (i = 0; i < 3 * IDS_PER_PAGE - 1; i++) { + val = idalloc_allocate(a); + assert(val < 3*IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + allocated_markers[val] = 1; + } + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE); + + /* Free two IDs from each page. */ + for (i = 0; i < 3; i++) { + idalloc_free(a, 7 + i*IDS_PER_PAGE); + allocated_markers[7 + i*IDS_PER_PAGE] = 0; + + idalloc_free(a, 4 + i*IDS_PER_PAGE); + allocated_markers[4 + i*IDS_PER_PAGE] = 0; + } + + assert(a->allocated == 3*IDS_PER_PAGE - 6); + + /* Allocate the six IDs back and make sure they are pulled from the set + * just freed. + */ + for (i = 0; i < 6; i++) { + val = idalloc_allocate(a); + assert(val < 3*IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + assert(a->capacity == 3*IDS_PER_PAGE); + allocated_markers[val] = 1; + } + + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE); + + /* Walk each allocated ID. Free it, then re-allocate it back. */ + for (i = 1; i < 3 * IDS_PER_PAGE - 1; i++) { + idalloc_free(a, i); + val = idalloc_allocate(a); + assert(val == i); + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE); + } + idalloc_destroy(a); + + /* 5. Weird Reservations + * idalloc_reserve exists primarily to black out low numbered IDs that + * are reserved for special cases. However, we will test it for more + * complex use cases to avoid unpleasant surprises. + */ + + memset(allocated_markers, 0, sizeof(allocated_markers)); + allocated_markers[IDALLOC_INVALID] = 1; + + a = idalloc_new("Weird Reservations"); + + /* Start with 3 pages fully allocated. */ + for (i = 0; i < 3 * IDS_PER_PAGE - 1; i++) { + val = idalloc_allocate(a); + assert(val < 3*IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + allocated_markers[val] = 1; + } + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE); + + /* Free a bit out of each of the three pages. Then reserve one of the + * three freed IDs. Finally, allocate the other two freed IDs. Do this + * each of three ways. (Reserve out of the first, seconds then third + * page.) + * The intent here is to exercise the rare cases on reserve_bit's + * linked-list removal in the case that it is not removing the first + * page with a free bit in its list of pages with free bits. + */ + + for (pg = 0; pg < 3; pg++) { + /* free a bit out of each of the three pages */ + for (i = 0; i < 3; i++) { + idalloc_free(a, i*IDS_PER_PAGE + 17); + allocated_markers[i*IDS_PER_PAGE + 17] = 0; + } + + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE-3); + + /* Reserve one of the freed IDs */ + assert(idalloc_reserve(a, pg*IDS_PER_PAGE + 17) == + pg*IDS_PER_PAGE + 17); + allocated_markers[pg*IDS_PER_PAGE + 17] = 1; + + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE-2); + + /* Allocate the other two back */ + for (i = 0; i < 2; i++) { + val = idalloc_allocate(a); + assert(val < 3*IDS_PER_PAGE); + assert(allocated_markers[val] == 0); + allocated_markers[val] = 1; + } + assert(a->capacity == 3*IDS_PER_PAGE); + assert(a->allocated == 3*IDS_PER_PAGE); + } + idalloc_destroy(a); + + puts("ID Allocator test successful.\n"); + return 0; +} diff --git a/tests/lib/test_idalloc.py b/tests/lib/test_idalloc.py new file mode 100644 index 0000000..e2186dc --- /dev/null +++ b/tests/lib/test_idalloc.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestIDAlloc(frrtest.TestMultiOut): + program = "./test_idalloc" + + +TestIDAlloc.onesimple("ID Allocator test successful.") diff --git a/tests/lib/test_memory.c b/tests/lib/test_memory.c new file mode 100644 index 0000000..aba4c35 --- /dev/null +++ b/tests/lib/test_memory.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +#include +#include + +DEFINE_MGROUP(TEST_MEMORY, "memory test"); +DEFINE_MTYPE_STATIC(TEST_MEMORY, TEST, "generic test mtype"); + +/* Memory torture tests + * + * Tests below are generic but comments are focused on interaction with + * Paul's proposed memory 'quick' cache, which may never be included in + * CVS + */ + +struct event_loop *master; + +#if 0 /* set to 1 to use system alloc directly */ +#undef XMALLOC +#undef XCALLOC +#undef XREALLOC +#undef XFREE +#define XMALLOC(T,S) malloc((S)) +#define XCALLOC(T,S) calloc(1, (S)) +#define XREALLOC(T,P,S) realloc((P),(S)) +#define XFREE(T,P) free((P)) +#endif + +#define TIMES 10 + +int main(int argc, char **argv) +{ + void *a[10]; + int i; + + printf("malloc x, malloc x, free, malloc x, free free\n\n"); + /* simple case, test cache */ + for (i = 0; i < TIMES; i++) { + a[0] = XMALLOC(MTYPE_TEST, 1024); + memset(a[0], 1, 1024); + a[1] = XMALLOC(MTYPE_TEST, 1024); + memset(a[1], 1, 1024); + XFREE(MTYPE_TEST, a[0]); /* should go to cache */ + a[0] = XMALLOC(MTYPE_TEST, + 1024); /* should be satisfied from cache */ + XFREE(MTYPE_TEST, a[0]); + XFREE(MTYPE_TEST, a[1]); + } + + printf("malloc x, malloc y, free x, malloc y, free free\n\n"); + /* cache should go invalid, valid, invalid, etc.. */ + for (i = 0; i < TIMES; i++) { + a[0] = XMALLOC(MTYPE_TEST, 512); + memset(a[0], 1, 512); + a[1] = XMALLOC(MTYPE_TEST, 1024); /* invalidate cache */ + memset(a[1], 1, 1024); + XFREE(MTYPE_TEST, a[0]); + a[0] = XMALLOC(MTYPE_TEST, 1024); + XFREE(MTYPE_TEST, a[0]); + XFREE(MTYPE_TEST, a[1]); + /* cache should become valid again on next request */ + } + + printf("calloc\n\n"); + /* test calloc */ + for (i = 0; i < TIMES; i++) { + a[0] = XCALLOC(MTYPE_TEST, 1024); + memset(a[0], 1, 1024); + a[1] = XCALLOC(MTYPE_TEST, 512); /* invalidate cache */ + memset(a[1], 1, 512); + XFREE(MTYPE_TEST, a[1]); + XFREE(MTYPE_TEST, a[0]); + /* alloc == 0, cache can become valid again on next request */ + } + + printf("calloc and realloc\n\n"); + /* check calloc + realloc */ + for (i = 0; i < TIMES; i++) { + printf("calloc a0 1024\n"); + a[0] = XCALLOC(MTYPE_TEST, 1024); + memset(a[0], 1, 1024 / 2); + + printf("calloc 1 1024\n"); + a[1] = XCALLOC(MTYPE_TEST, 1024); + memset(a[1], 1, 1024 / 2); + + printf("realloc 0 1024\n"); + a[3] = XREALLOC(MTYPE_TEST, a[0], 2048); /* invalidate cache */ + if (a[3] != NULL) + a[0] = a[3]; + memset(a[0], 1, 1024); + + printf("calloc 2 512\n"); + a[2] = XCALLOC(MTYPE_TEST, 512); + memset(a[2], 1, 512); + + printf("free 1 0 2\n"); + XFREE(MTYPE_TEST, a[1]); + XFREE(MTYPE_TEST, a[0]); + XFREE(MTYPE_TEST, a[2]); + /* alloc == 0, cache valid next request */ + } + return 0; +} diff --git a/tests/lib/test_nexthop.c b/tests/lib/test_nexthop.c new file mode 100644 index 0000000..84732d6 --- /dev/null +++ b/tests/lib/test_nexthop.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Nexthop module test. + * + * Copyright (C) 2021 by Volta Networks, Inc. + */ + +#include +#include + +static bool verbose; + +static void test_run_first(void) +{ + int ret, i; + struct nexthop *nh1, *nh2; + struct in_addr addr; + struct in6_addr addr6; + mpls_label_t labels[MPLS_MAX_LABELS]; + + /* Test comparison apis */ + + /* ifindex comparisons */ + nh1 = nexthop_from_ifindex(11, 0); + nh2 = nexthop_from_ifindex(12, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret < 0); + + nexthop_free(nh1); + nh1 = nexthop_from_ifindex(12, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh1); + nexthop_free(nh2); + + /* ipv4, vrf */ + addr.s_addr = 0x04030201; + nh1 = nexthop_from_ipv4(&addr, NULL, 0); + nh2 = nexthop_from_ipv4(&addr, NULL, 111); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh2); + + addr.s_addr = 0x04030202; + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh2); + + addr.s_addr = 0x04030201; + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + /* Weight */ + nh2->weight = 20; + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh1); + nexthop_free(nh2); + + /* ipv6 */ + memset(addr6.s6_addr, 0, sizeof(addr6.s6_addr)); + nh1 = nexthop_from_ipv6(&addr6, 0); + nh2 = nexthop_from_ipv6(&addr6, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh2); + + nh2 = nexthop_from_ipv6(&addr6, 1); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh2); + + addr6.s6_addr[14] = 1; + addr6.s6_addr[15] = 1; + nh2 = nexthop_from_ipv6(&addr6, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh1); + nexthop_free(nh2); + + /* Blackhole */ + nh1 = nexthop_from_blackhole(BLACKHOLE_REJECT, 0); + nh2 = nexthop_from_blackhole(BLACKHOLE_REJECT, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh2); + + nh2 = nexthop_from_blackhole(BLACKHOLE_NULL, 0); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + /* Labels */ + addr.s_addr = 0x04030201; + nh1 = nexthop_from_ipv4(&addr, NULL, 0); + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + + memset(labels, 0, sizeof(labels)); + labels[0] = 111; + labels[1] = 222; + + nexthop_add_labels(nh1, ZEBRA_LSP_STATIC, 2, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_add_labels(nh2, ZEBRA_LSP_STATIC, 2, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh2); + + /* LSP type isn't included */ + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + nexthop_add_labels(nh2, ZEBRA_LSP_LDP, 2, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh2); + + labels[2] = 333; + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + nexthop_add_labels(nh2, ZEBRA_LSP_LDP, 3, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + nexthop_free(nh1); + nexthop_free(nh2); + + nh1 = nexthop_from_ipv4(&addr, NULL, 0); + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + + for (i = 0; i < MPLS_MAX_LABELS; i++) + labels[i] = 111 * (i + 1); + + nexthop_add_labels(nh1, ZEBRA_LSP_LDP, MPLS_MAX_LABELS, labels); + nexthop_add_labels(nh2, ZEBRA_LSP_LDP, MPLS_MAX_LABELS, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret == 0); + + nexthop_free(nh2); + + /* Test very last label in stack */ + labels[15] = 999; + nh2 = nexthop_from_ipv4(&addr, NULL, 0); + nexthop_add_labels(nh2, ZEBRA_LSP_LDP, MPLS_MAX_LABELS, labels); + + ret = nexthop_cmp_basic(nh1, nh2); + assert(ret != 0); + + /* End */ + nexthop_free(nh1); + nexthop_free(nh2); +} + +int main(int argc, char **argv) +{ + if (argc >= 2 && !strcmp("-v", argv[1])) + verbose = true; + test_run_first(); + printf("Simple test passed.\n"); +} diff --git a/tests/lib/test_nexthop.py b/tests/lib/test_nexthop.py new file mode 100644 index 0000000..81bfa43 --- /dev/null +++ b/tests/lib/test_nexthop.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestNexthopIter(frrtest.TestMultiOut): + program = "./test_nexthop" + + +TestNexthopIter.onesimple("Simple test passed.") diff --git a/tests/lib/test_nexthop_iter.c b/tests/lib/test_nexthop_iter.c new file mode 100644 index 0000000..33ff116 --- /dev/null +++ b/tests/lib/test_nexthop_iter.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Recursive Nexthop Iterator test. + * This tests the ALL_NEXTHOPS macro. + * + * Copyright (C) 2012 by Open Source Routing. + * Copyright (C) 2012 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ + +#include +#include "zebra/rib.h" +#include "prng.h" + +struct event_loop *master; +static int verbose; + +static void str_append(char **buf, const char *repr) +{ + if (*buf) { + size_t new_size = strlen(*buf) + strlen(repr) + 1; + *buf = realloc(*buf, new_size); + assert(*buf); + (void)strlcat(*buf, repr, new_size); + } else { + *buf = strdup(repr); + assert(*buf); + } +} + +PRINTFRR(2, 3) +static void str_appendf(char **buf, const char *format, ...) +{ + va_list ap; + int rv; + char *pbuf; + + va_start(ap, format); + rv = vasprintf(&pbuf, format, ap); + va_end(ap); + assert(rv >= 0); + + str_append(buf, pbuf); + free(pbuf); +} + +/* This structure contains a nexthop chain + * and its expected representation */ +struct nexthop_chain { + /* Head of the chain */ + struct nexthop_group head; + /* Last nexthop in top chain */ + struct nexthop *current_top; + /* Last nexthop in current recursive chain */ + struct nexthop *current_recursive; + /* Expected string representation. */ + char *repr; +}; + +static struct nexthop_chain *nexthop_chain_new(void) +{ + struct nexthop_chain *rv; + + rv = calloc(sizeof(*rv), 1); + assert(rv); + return rv; +} + +static void nexthop_chain_add_top(struct nexthop_chain *nc) +{ + struct nexthop *nh; + + nh = calloc(sizeof(*nh), 1); + assert(nh); + + if (nc->head.nexthop) { + nc->current_top->next = nh; + nh->prev = nc->current_top; + nc->current_top = nh; + } else { + nc->head.nexthop = nc->current_top = nh; + } + nc->current_recursive = NULL; + str_appendf(&nc->repr, "%p\n", nh); +} + +static void add_string_representation(char **repr, struct nexthop *nh) +{ + struct nexthop *parent; + + /* add indentations first */ + parent = nh->rparent; + while (parent) { + str_appendf(repr, " "); + parent = parent->rparent; + } + str_appendf(repr, "%p\n", nh); +} + +static void start_recursive_chain(struct nexthop_chain *nc, struct nexthop *nh) +{ + SET_FLAG(nc->current_top->flags, NEXTHOP_FLAG_RECURSIVE); + nc->current_top->resolved = nh; + nh->rparent = nc->current_top; + nc->current_recursive = nh; +} +static void nexthop_chain_add_recursive(struct nexthop_chain *nc) +{ + struct nexthop *nh; + + nh = calloc(sizeof(*nh), 1); + assert(nh); + + assert(nc->current_top); + if (nc->current_recursive) { + nc->current_recursive->next = nh; + nh->prev = nc->current_recursive; + nh->rparent = nc->current_recursive->rparent; + nc->current_recursive = nh; + } else + start_recursive_chain(nc, nh); + + add_string_representation(&nc->repr, nh); +} + +static void nexthop_chain_add_recursive_level(struct nexthop_chain *nc) +{ + struct nexthop *nh; + + nh = calloc(sizeof(*nh), 1); + assert(nh); + + assert(nc->current_top); + if (nc->current_recursive) { + SET_FLAG(nc->current_recursive->flags, NEXTHOP_FLAG_RECURSIVE); + nc->current_recursive->resolved = nh; + nh->rparent = nc->current_recursive; + nc->current_recursive = nh; + } else + start_recursive_chain(nc, nh); + + add_string_representation(&nc->repr, nh); +} + +static void nexthop_clear_recursive(struct nexthop *tcur) +{ + if (!tcur) + return; + if (CHECK_FLAG(tcur->flags, NEXTHOP_FLAG_RECURSIVE)) + nexthop_clear_recursive(tcur->resolved); + if (tcur->next) + nexthop_clear_recursive(tcur->next); + free(tcur); +} +static void nexthop_chain_clear(struct nexthop_chain *nc) +{ + nexthop_clear_recursive(nc->head.nexthop); + nc->head.nexthop = nc->current_top = nc->current_recursive = NULL; + free(nc->repr); + nc->repr = NULL; +} + +static void nexthop_chain_free(struct nexthop_chain *nc) +{ + if (!nc) + return; + nexthop_chain_clear(nc); + free(nc); +} + +/* This function builds a string representation of + * the nexthop chain using the ALL_NEXTHOPS macro. + * It verifies that the ALL_NEXTHOPS macro iterated + * correctly over the nexthop chain by comparing the + * generated representation with the expected representation. + */ +static void nexthop_chain_verify_iter(struct nexthop_chain *nc) +{ + struct nexthop *nh; + char *repr = NULL; + + for (ALL_NEXTHOPS(nc->head, nh)) + add_string_representation(&repr, nh); + + if (repr && verbose) + printf("===\n%s", repr); + assert((!repr && !nc->repr) + || (repr && nc->repr && !strcmp(repr, nc->repr))); + free(repr); +} + +/* This test run builds a simple nexthop chain + * with some recursive nexthops and verifies that + * the iterator works correctly in each stage along + * the way. + */ +static void test_run_first(void) +{ + struct nexthop_chain *nc; + + nc = nexthop_chain_new(); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive_level(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_recursive(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_add_top(nc); + nexthop_chain_verify_iter(nc); + + nexthop_chain_free(nc); +} + +/* This test run builds numerous random + * nexthop chain configurations and verifies + * that the iterator correctly progresses + * through each. */ +static void test_run_prng(void) +{ + struct nexthop_chain *nc; + struct prng *prng; + int i; + + nc = nexthop_chain_new(); + prng = prng_new(0); + + for (i = 0; i < 1000000; i++) { + switch (prng_rand(prng) % 10) { + case 0: + nexthop_chain_clear(nc); + break; + case 1: + case 2: + case 3: + case 4: + case 5: + nexthop_chain_add_top(nc); + break; + case 6: + case 7: + case 8: + if (nc->current_top) + nexthop_chain_add_recursive(nc); + break; + case 9: + if (nc->current_top) + nexthop_chain_add_recursive_level(nc); + break; + } + nexthop_chain_verify_iter(nc); + } + nexthop_chain_free(nc); + prng_free(prng); +} + +int main(int argc, char **argv) +{ + if (argc >= 2 && !strcmp("-v", argv[1])) + verbose = 1; + test_run_first(); + printf("Simple test passed.\n"); + test_run_prng(); + printf("PRNG test passed.\n"); +} diff --git a/tests/lib/test_nexthop_iter.py b/tests/lib/test_nexthop_iter.py new file mode 100644 index 0000000..0c39dce --- /dev/null +++ b/tests/lib/test_nexthop_iter.py @@ -0,0 +1,9 @@ +import frrtest + + +class TestNexthopIter(frrtest.TestMultiOut): + program = "./test_nexthop_iter" + + +TestNexthopIter.onesimple("Simple test passed.") +TestNexthopIter.onesimple("PRNG test passed.") diff --git a/tests/lib/test_ntop.c b/tests/lib/test_ntop.c new file mode 100644 index 0000000..d357047 --- /dev/null +++ b/tests/lib/test_ntop.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * frr_inet_ntop() unit test + * Copyright (C) 2019 David Lamparter + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "tests/helpers/c/prng.h" + +/* NB: libfrr is NOT linked for this unit test! */ + +#define INET_NTOP_NO_OVERRIDE +#include "lib/ntop.c" + +int main(int argc, char **argv) +{ + size_t i, j, k, l; + struct in_addr i4; + struct in6_addr i6, i6check; + char buf1[64], buf2[64]; + const char *rv; + struct prng *prng; + + prng = prng_new(0); + /* IPv4 */ + for (i = 0; i < 1000; i++) { + i4.s_addr = prng_rand(prng); + assert(frr_inet_ntop(AF_INET, &i4, buf1, sizeof(buf1))); + assert(inet_ntop(AF_INET, &i4, buf2, sizeof(buf2))); + assert(!strcmp(buf1, buf2)); + } + + /* check size limit */ + for (i = 0; i < sizeof(buf1); i++) { + memset(buf2, 0xcc, sizeof(buf2)); + rv = frr_inet_ntop(AF_INET, &i4, buf2, i); + if (i < strlen(buf1) + 1) + assert(!rv); + else + assert(rv && !strcmp(buf1, buf2)); + } + + /* IPv6 */ + for (i = 0; i < 10000; i++) { + uint16_t *i6w = (uint16_t *)&i6; + for (j = 0; j < 8; j++) + i6w[j] = prng_rand(prng); + + /* clear some words */ + l = prng_rand(prng) & 7; + for (j = 0; j < l; j++) { + uint32_t num = __builtin_ctz(prng_rand(prng)); + uint32_t where = prng_rand(prng) & 7; + + for (k = where; k < where + num && k < 8; k++) + i6w[k] = 0; + } + + assert(frr_inet_ntop(AF_INET6, &i6, buf1, sizeof(buf1))); + assert(inet_ntop(AF_INET6, &i6, buf2, sizeof(buf2))); + if (strcmp(buf1, buf2)) + printf("%-40s (FRR) != (SYS) %-40s\n", buf1, buf2); + + assert(inet_pton(AF_INET6, buf1, &i6check)); + assert(!memcmp(&i6, &i6check, sizeof(i6))); + } + return 0; +} diff --git a/tests/lib/test_ntop.py b/tests/lib/test_ntop.py new file mode 100644 index 0000000..69c4353 --- /dev/null +++ b/tests/lib/test_ntop.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestNtop(frrtest.TestMultiOut): + program = "./test_ntop" + + +TestNtop.exit_cleanly() diff --git a/tests/lib/test_plist.c b/tests/lib/test_plist.c new file mode 100644 index 0000000..bfad766 --- /dev/null +++ b/tests/lib/test_plist.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple prefix list querying tool + * + * Copyright (C) 2021 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + */ + +#include + +#include "lib/plist.h" +#include "lib/filter.h" +#include "tests/lib/cli/common_cli.h" + +static const struct frr_yang_module_info *const my_yang_modules[] = { + &frr_filter_info, + NULL, +}; + +__attribute__((_CONSTRUCTOR(2000))) +static void test_yang_modules_set(void) +{ + test_yang_modules = my_yang_modules; +} + +void test_init(int argc, char **argv) +{ + prefix_list_init(); + filter_cli_init(); + + /* nothing else to do here, giving stand-alone access to the prefix + * list code's "debug prefix-list ..." command is the only purpose of + * this "test". + */ +} diff --git a/tests/lib/test_prefix2str.c b/tests/lib/test_prefix2str.c new file mode 100644 index 0000000..9d837ee --- /dev/null +++ b/tests/lib/test_prefix2str.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * prefix2str() unit test + * Copyright (C) 2019 David Lamparter + * Portions: + * Copyright (C) 2019 Cumulus Networks, Inc + * Quentin Young + */ +#include + +#include "lib/prefix.h" + +#include "tests/helpers/c/prng.h" + +int main(int argc, char **argv) +{ + size_t i, j, k, l; + struct in6_addr i6; + char buf1[64], buf2[64], ntopbuf[64]; + struct prng *prng; + struct prefix p = {}; + + prng = prng_new(0); + /* IPv4 */ + p.family = AF_INET; + for (i = 0; i < 1000; i++) { + p.u.prefix = prng_rand(prng); + p.prefixlen = prng_rand(prng) >> 26; + snprintf(buf1, sizeof(buf1), "%s/%d", + inet_ntop(AF_INET, &p.u.prefix4, ntopbuf, + sizeof(ntopbuf)), + p.prefixlen); + prefix2str(&p, buf2, sizeof(buf2)); + assert(!strcmp(buf1, buf2)); + fprintf(stdout, "%s\n", buf1); + } + + /* IPv6 */ + p.family = AF_INET6; + for (i = 0; i < 10000; i++) { + uint16_t *i6w = (uint16_t *)&i6; + for (j = 0; j < 8; j++) + i6w[j] = prng_rand(prng); + + /* clear some words */ + l = prng_rand(prng) & 7; + for (j = 0; j < l; j++) { + uint32_t num = __builtin_ctz(prng_rand(prng)); + uint32_t where = prng_rand(prng) & 7; + + for (k = where; k < where + num && k < 8; k++) + i6w[k] = 0; + } + + p.prefixlen = prng_rand(prng) >> 24; + memcpy(&p.u.prefix, &i6, sizeof(i6)); + snprintf(buf1, sizeof(buf1), "%s/%d", + inet_ntop(AF_INET6, &p.u.prefix6, ntopbuf, + sizeof(ntopbuf)), + p.prefixlen); + prefix2str(&p, buf2, sizeof(buf2)); + assert(!strcmp(buf1, buf2)); + fprintf(stdout, "%s\n", buf1); + } + + return 0; +} diff --git a/tests/lib/test_prefix2str.py b/tests/lib/test_prefix2str.py new file mode 100644 index 0000000..fd883ed --- /dev/null +++ b/tests/lib/test_prefix2str.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestPrefix2str(frrtest.TestMultiOut): + program = "./test_prefix2str" + + +TestPrefix2str.exit_cleanly() diff --git a/tests/lib/test_printfrr.c b/tests/lib/test_printfrr.c new file mode 100644 index 0000000..a81ebcd --- /dev/null +++ b/tests/lib/test_printfrr.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * printfrr() unit test + * Copyright (C) 2019 David Lamparter + */ + +#include "zebra.h" + +#include + +#include "lib/printfrr.h" +#include "lib/memory.h" +#include "lib/prefix.h" +#include "lib/nexthop.h" +#include "lib/asn.h" + +static int errors; + +static void printcmp(const char *fmt, ...) PRINTFRR(1, 2); +static void printcmp(const char *fmt, ...) +{ + va_list ap; + char buf[256], bufrr[256], *p; + int cmp; + memset(bufrr, 0xcc, sizeof(bufrr)); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + va_start(ap, fmt); + vsnprintfrr(bufrr, sizeof(bufrr), fmt, ap); + va_end(ap); + + cmp = strcmp(buf, bufrr); + + /* OS dependent "+nan" vs. "nan" */ + if (cmp && (p = strstr(bufrr, "+nan"))) { + p[0] = ' '; + if (!strcmp(buf, bufrr)) + cmp = 0; + p[0] = '+'; + } + printf("fmt: \"%s\"\nsys: \"%s\"\nfrr: \"%s\"\n%s\n\n", + fmt, buf, bufrr, cmp ? "ERROR" : "ok"); + + if (cmp) + errors++; +} + +static int printchk(const char *ref, const char *fmt, ...) PRINTFRR(2, 3); +static int printchk(const char *ref, const char *fmt, ...) +{ + va_list ap; + char bufrr[256]; + bool truncfail = false; + size_t i; + size_t expectlen; + + memset(bufrr, 0xcc, sizeof(bufrr)); + + va_start(ap, fmt); + expectlen = vsnprintfrr(NULL, 0, fmt, ap); + va_end(ap); + + va_start(ap, fmt); + vsnprintfrr(bufrr, 7, fmt, ap); + va_end(ap); + + if (strnlen(bufrr, 7) == 7) + truncfail = true; + if (strnlen(bufrr, 7) < 7 && strncmp(ref, bufrr, 6) != 0) + truncfail = true; + for (i = 7; i < sizeof(bufrr); i++) + if (bufrr[i] != (char)0xcc) { + truncfail = true; + break; + } + + if (truncfail) { + printf("truncation test FAILED:\n" + "fmt: \"%s\"\nref: \"%s\"\nfrr[:7]: \"%s\"\n%s\n\n", + fmt, ref, bufrr, strcmp(ref, bufrr) ? "ERROR" : "ok"); + errors++; + } + + struct fmt_outpos outpos[16]; + struct fbuf fb = { + .buf = bufrr, + .pos = bufrr, + .len = sizeof(bufrr) - 1, + .outpos = outpos, + .outpos_n = array_size(outpos), + }; + + va_start(ap, fmt); + vbprintfrr(&fb, fmt, ap); + fb.pos[0] = '\0'; + va_end(ap); + + printf("fmt: \"%s\"\nref: \"%s\"\nfrr: \"%s\"\n%s\n", + fmt, ref, bufrr, strcmp(ref, bufrr) ? "ERROR" : "ok"); + if (strcmp(ref, bufrr)) + errors++; + if (strlen(bufrr) != expectlen) { + printf("return value <> length mismatch\n"); + errors++; + } + + for (size_t i = 0; i < fb.outpos_i; i++) + printf("\t[%zu: %u..%u] = \"%.*s\"\n", i, + outpos[i].off_start, + outpos[i].off_end, + (int)(outpos[i].off_end - outpos[i].off_start), + bufrr + outpos[i].off_start); + printf("\n"); + return 0; +} + +static void test_va(const char *ref, const char *fmt, ...) PRINTFRR(2, 3); +static void test_va(const char *ref, const char *fmt, ...) +{ + struct va_format vaf; + va_list ap; + + va_start(ap, fmt); + vaf.fmt = fmt; + vaf.va = ≈ + + printchk(ref, "VA [%pVA] %s", &vaf, "--"); + + va_end(ap); +} + +int main(int argc, char **argv) +{ + size_t i; + float flts[] = { + 123.456789, + 23.456789e-30, + 3.456789e+30, + INFINITY, + NAN, + }; + uint64_t ui64 = 0xfeed1278cafef00d; + uint16_t i16 = -23456; + int_fast8_t if8 = 123; + struct in_addr ip; + char *p; + char buf[256]; + as_t asn; + + printcmp("%d %u %d %u", 123, 123, -456, -456); + printcmp("%lld %llu %lld %llu", 123LL, 123LL, -456LL, -456LL); + + printcmp("%-20s,%20s,%.20s", "test", "test", "test"); + printcmp("%-3s,%3s,%.3s", "test", "test", "test"); + printcmp("%-6.3s,%6.3s,%6.3s", "test", "test", "test"); + printcmp("%*s,%*s,%.*s", -3, "test", 3, "test", 3, "test"); + + for (i = 0; i < array_size(flts); i++) { + printcmp("%-6.3e,%6.3e,%+06.3e", flts[i], flts[i], flts[i]); + printcmp("%-6.3f,%6.3f,%+06.3f", flts[i], flts[i], flts[i]); + printcmp("%-6.3g,%6.3g,%+06.3g", flts[i], flts[i], flts[i]); + printcmp("%-6.3a,%6.3a,%+06.3a", flts[i], flts[i], flts[i]); + } + + printchk("-77385308584349683 18369358765125201933 feed1278cafef00d", + "%Ld %Lu %Lx", ui64, ui64, ui64); + + FMT_NSTD(printchk("11110000000011111010010111000011", "%b", 0xf00fa5c3)); + FMT_NSTD(printchk("0b01011010", "%#010b", 0x5a)); + +/* FMT_NSTD is conditional on the frr-format plugin being NOT enabled. + * However, the frr-format plugin does not support %wd/%wfd yet, so this needs + * to be unconditional. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" + printchk("123 -23456 feed1278cafef00d 9876", "%wf8d %w16d %w64x %d", + if8, i16, ui64, 9876); +#pragma GCC diagnostic pop + + inet_aton("192.168.1.2", &ip); + printchk("192.168.1.2", "%pI4", &ip); + printchk(" 192.168.1.2", "%20pI4", &ip); + printchk("192.168.1.2 ", "%-20pI4", &ip); + + printcmp("%p", &ip); + + test_va("VA [192.168.1.2 1234] --", "%pI4 %u", &ip, 1234); + + inet_aton("0.0.0.0", &ip); + printchk("0.0.0.0", "%pI4", &ip); + printchk("*", "%pI4s", &ip); + + snprintfrr(buf, sizeof(buf), "test%s", "#1"); + csnprintfrr(buf, sizeof(buf), "test%s", "#2"); + assert(strcmp(buf, "test#1test#2") == 0); + + p = asnprintfrr(MTYPE_TMP, buf, sizeof(buf), "test%s", "#3"); + assert(p == buf); + assert(strcmp(buf, "test#3") == 0); + + p = asnprintfrr(MTYPE_TMP, buf, 4, "test%s", "#4"); + assert(p != buf); + assert(strcmp(p, "test#4") == 0); + XFREE(MTYPE_TMP, p); + + p = asprintfrr(MTYPE_TMP, "test%s", "#5"); + assert(strcmp(p, "test#5") == 0); + XFREE(MTYPE_TMP, p); + + struct prefix pfx; + + str2prefix("192.168.1.23/24", &pfx); + printchk("192.168.1.23/24", "%pFX", &pfx); + printchk("192.168.1.23", "%pFXh", &pfx); + + str2prefix("2001:db8::1234/64", &pfx); + printchk("2001:db8::1234/64", "%pFX", &pfx); + printchk("2001:db8::1234", "%pFXh", &pfx); + + pfx.family = AF_UNIX; + printchk("UNK prefix", "%pFX", &pfx); + printchk("{prefix.af=AF_UNIX}", "%pFXh", &pfx); + + str2prefix_eth("02:ca:fe:f0:0d:1e/48", (struct prefix_eth *)&pfx); + printchk("02:ca:fe:f0:0d:1e/48", "%pFX", &pfx); + printchk("02:ca:fe:f0:0d:1e", "%pFXh", &pfx); + + struct prefix_sg sg; + SET_IPADDR_V4(&sg.src); + sg.src.ipaddr_v4.s_addr = INADDR_ANY; + sg.grp.s_addr = INADDR_ANY; + printchk("(*,*)", "%pPSG4", &sg); + + inet_aton("192.168.1.2", &sg.src.ipaddr_v4); + printchk("(192.168.1.2,*)", "%pPSG4", &sg); + + inet_aton("224.1.2.3", &sg.grp); + printchk("(192.168.1.2,224.1.2.3)", "%pPSG4", &sg); + + SET_IPADDR_NONE(&sg.src); + sg.src.ipaddr_v4.s_addr = INADDR_ANY; + printchk("(*,224.1.2.3)", "%pPSG4", &sg); + + SET_IPADDR_V6(&sg.src); + inet_pton(AF_INET6, "1:2:3:4::5", &sg.src.ipaddr_v6); + printchk("(1:2:3:4::5,224.1.2.3)", "%pPSG4", &sg); + + uint8_t randhex[] = { 0x12, 0x34, 0x00, 0xca, 0xfe, 0x00, 0xaa, 0x55 }; + + FMT_NSTD(printchk("12 34 00 ca fe 00 aa 55", "%.8pHX", randhex)); + FMT_NSTD(printchk("12 34 00 ca fe 00 aa 55", "%.*pHX", + (int)sizeof(randhex), randhex)); + FMT_NSTD(printchk("12 34 00 ca", "%.4pHX", randhex)); + + printchk("12 34 00 ca fe 00 aa 55", "%8pHX", randhex); + printchk("12 34 00 ca fe 00 aa 55", "%*pHX", + (int)sizeof(randhex), randhex); + printchk("12 34 00 ca", "%4pHX", randhex); + + printchk("", "%pHX", randhex); + + printchk("12:34:00:ca:fe:00:aa:55", "%8pHXc", randhex); + printchk("123400cafe00aa55", "%8pHXn", randhex); + + printchk("/test/pa\\ th/\\~spe\\ncial\\x01/file.name", "%pSE", + "/test/pa th/~spe\ncial\x01/file.name"); + printchk("/test/pa\\ th/\\~spe\\n", "%17pSE", + "/test/pa th/~spe\ncial\x01/file.name"); + + char nulltest[] = { 'n', 'u', 0, 'l', 'l' }; + + printchk("nu\\x00ll", "%5pSE", nulltest); + printchk("nu\\x00ll", "%*pSE", 5, nulltest); + + printchk("bl\\\"ah\\x01te[st\\nab]c", "%pSQ", + "bl\"ah\x01te[st\nab]c"); + printchk("\"bl\\\"ah\\x01te[st\\nab]c\"", "%pSQq", + "bl\"ah\x01te[st\nab]c"); + printchk("\"bl\\\"ah\\x01te[st\\x0aab\\]c\"", "%pSQqs", + "bl\"ah\x01te[st\nab]c"); + printchk("\"\"", "%pSQqn", ""); + printchk("\"\"", "%pSQqn", (char *)NULL); + printchk("(null)", "%pSQq", (char *)NULL); + + /* + * %pNH tests + * + * gateway addresses only for now: interfaces require more setup + */ + printchk("(null)", "%pNHcg", (struct nexthop *)NULL); + printchk("(null)", "%pNHci", (struct nexthop *)NULL); + + struct nexthop nh; + + memset(&nh, 0, sizeof(nh)); + + nh.type = NEXTHOP_TYPE_IPV4; + inet_aton("3.2.1.0", &nh.gate.ipv4); + printchk("3.2.1.0", "%pNHcg", &nh); + + nh.type = NEXTHOP_TYPE_IPV6; + inet_pton(AF_INET6, "fe2c::34", &nh.gate.ipv6); + printchk("fe2c::34", "%pNHcg", &nh); + + /* time printing */ + + /* need a non-UTC timezone for testing */ + setenv("TZ", "TEST-01:00", 1); + tzset(); + + struct timespec ts; + struct timeval tv; + time_t tt; + + ts.tv_sec = tv.tv_sec = tt = 1642015880; + ts.tv_nsec = 123456789; + tv.tv_usec = 234567; + + printchk("Wed Jan 12 20:31:20 2022", "%pTSR", &ts); + printchk("Wed Jan 12 20:31:20 2022", "%pTVR", &tv); + printchk("Wed Jan 12 20:31:20 2022", "%pTTR", &tt); + + FMT_NSTD(printchk("Wed Jan 12 20:31:20 2022", "%.3pTSR", &ts)); + + printchk("2022-01-12T20:31:20.123", "%pTSRi", &ts); + printchk("2022-01-12 20:31:20.123", "%pTSRip", &ts); + printchk("2022-01-12 20:31:20.123", "%pTSRpi", &ts); + FMT_NSTD(printchk("2022-01-12T20:31:20", "%.0pTSRi", &ts)); + FMT_NSTD(printchk("2022-01-12T20:31:20.123456789", "%.9pTSRi", &ts)); + FMT_NSTD(printchk("2022-01-12T20:31:20", "%.3pTTRi", &tt)); + + ts.tv_sec = tv.tv_sec = tt = 9 * 86400 + 12345; + + printchk("1w 2d 03:25:45.123", "%pTSIp", &ts); + printchk("1w2d03:25:45.123", "%pTSI", &ts); + printchk("1w2d03:25:45.234", "%pTVI", &tv); + printchk("1w2d03:25:45", "%pTTI", &tt); + + printchk("1w 2d 03h", "%pTVItp", &tv); + printchk("1w2d03h", "%pTSIt", &ts); + + printchk("219:25:45", "%pTVIh", &tv); + printchk("13165:45", "%pTVIm", &tv); + + ts.tv_sec = tv.tv_sec = tt = 1 * 86400 + 12345; + + printchk("1d 03:25:45.123", "%pTSIp", &ts); + printchk("1d03:25:45.234", "%pTVI", &tv); + + printchk("1d 03h 25m", "%pTVItp", &tv); + printchk("1d03h25m", "%pTSIt", &ts); + + printchk("98745.234", "%pTVId", &tv); + + printchk("27:25:45", "%pTVIh", &tv); + printchk("1645:45", "%pTVIm", &tv); + + ts.tv_sec = tv.tv_sec = tt = 12345; + + printchk("03:25:45.123", "%pTSIp", &ts); + printchk("03:25:45.123", "%pTSI", &ts); + printchk("03:25:45.234", "%pTVI", &tv); + printchk("03:25:45", "%pTTI", &tt); + + printchk("03:25:45", "%pTSItp", &ts); + printchk("03:25:45", "%pTVIt", &tv); + + printchk("12345.234", "%pTVId", &tv); + + printchk("03:25:45", "%pTVIh", &tv); + printchk("205:45", "%pTVIm", &tv); + + ts.tv_sec = tv.tv_sec = tt = 0; + + printchk("00:00:00.123", "%pTSIp", &ts); + printchk("00:00:00.123", "%pTSI", &ts); + printchk("00:00:00.234", "%pTVI", &tv); + printchk("00:00:00", "%pTTI", &tt); + + printchk("00:00:00", "%pTVItp", &tv); + printchk("00:00:00", "%pTSIt", &ts); + + printchk("0.234", "%pTVId", &tv); + printchk("0.234", "%pTVIdx", &tv); + printchk("-", "%pTTIdx", &tt); + + printchk("00:00:00", "%pTVIhx", &tv); + printchk("--:--:--", "%pTTIhx", &tt); + printchk("00:00", "%pTVImx", &tv); + printchk("--:--", "%pTTImx", &tt); + + ts.tv_sec = tv.tv_sec = tt = -10; + + printchk("-00:00:09.876", "%pTSIp", &ts); + printchk("-00:00:09.876", "%pTSI", &ts); + printchk("-00:00:09.765", "%pTVI", &tv); + printchk("-00:00:10", "%pTTI", &tt); + + printchk("-00:00:09", "%pTSItp", &ts); + printchk("-00:00:09", "%pTSIt", &ts); + printchk("-00:00:09", "%pTVIt", &tv); + printchk("-00:00:10", "%pTTIt", &tt); + + printchk("-9.765", "%pTVId", &tv); + printchk("-", "%pTVIdx", &tv); + + printchk("-00:00:09", "%pTSIh", &ts); + printchk("--:--:--", "%pTVIhx", &tv); + printchk("--:--:--", "%pTTIhx", &tt); + + printchk("-00:09", "%pTSIm", &ts); + printchk("--:--", "%pTVImx", &tv); + printchk("--:--", "%pTTImx", &tt); + /* ASN checks */ + asn = 65536; + printchk("1.0", "%pASD", &asn); + asn = 65400; + printchk("65400", "%pASP", &asn); + printchk("0.65400", "%pASE", &asn); + printchk("65400", "%pASD", &asn); + + return !!errors; +} diff --git a/tests/lib/test_printfrr.py b/tests/lib/test_printfrr.py new file mode 100644 index 0000000..b8ab89e --- /dev/null +++ b/tests/lib/test_printfrr.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestPrintfrr(frrtest.TestMultiOut): + program = "./test_printfrr" + + +TestPrintfrr.exit_cleanly() diff --git a/tests/lib/test_privs.c b/tests/lib/test_privs.c new file mode 100644 index 0000000..caf55c7 --- /dev/null +++ b/tests/lib/test_privs.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +#include +#include + +#include +#include "getopt.h" +#include "privs.h" +#include "memory.h" +#include "lib_vty.h" + +zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, ZCAP_DAC_OVERRIDE, +}; + +struct zebra_privs_t test_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = {{"help", no_argument, NULL, 'h'}, + {"user", required_argument, NULL, 'u'}, + {"group", required_argument, NULL, 'g'}, + {0}}; + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +Daemon which does 'slow' things.\n\n\ +-u, --user User to run as\n\ +-g, --group Group to run as\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + +struct event_loop *master; +/* main routine. */ +int main(int argc, char **argv) +{ + char *p; + char *progname; + struct zprivs_ids_t ids; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "hu:g:", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'u': + test_privs.user = optarg; + break; + case 'g': + test_privs.group = optarg; + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* Library inits. */ + lib_cmd_init(); + zprivs_preinit(&test_privs); + zprivs_init(&test_privs); + +#define PRIV_STATE() \ + ((test_privs.current_state() == ZPRIVS_RAISED) ? "Raised" : "Lowered") + + printf("%s\n", PRIV_STATE()); + frr_with_privs(&test_privs) { + printf("%s\n", PRIV_STATE()); + } + + printf("%s\n", PRIV_STATE()); + zprivs_get_ids(&ids); + + /* terminate privileges */ + zprivs_terminate(&test_privs); + + /* but these should continue to work... */ + printf("%s\n", PRIV_STATE()); + frr_with_privs(&test_privs) { + printf("%s\n", PRIV_STATE()); + } + + printf("%s\n", PRIV_STATE()); + zprivs_get_ids(&ids); + + printf("terminating\n"); + return 0; +} diff --git a/tests/lib/test_resolver.c b/tests/lib/test_resolver.c new file mode 100644 index 0000000..d72ff4f --- /dev/null +++ b/tests/lib/test_resolver.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * FRR c-ares integration test + * Copyright (C) 2021 David Lamparter for NetDEF, Inc. + */ + +/* this test is not run automatically since tests MUST NOT rely on any outside + * state. DNS is most definitely "outside state". A testbed may not have any + * internet connectivity at all. It may not have working DNS. Or worst of + * all, whatever name we use to test may have a temporary failure entirely + * beyond our control. + * + * The only way this test could be run in a testbed is with an all-local DNS + * setup, which considering the resolver code is rarely touched is not worth + * the time at all. Instead, after touching the resolver code, manually run + * this test and throw some names at it. + */ + +#include + +#include "vty.h" +#include "command.h" +#include "resolver.h" +#include "log.h" +#include "sockunion.h" + +#include "tests/lib/cli/common_cli.h" + +extern struct event_loop *master; + +static void resolver_result(struct resolver_query *resq, const char *errstr, + int numaddrs, union sockunion *addr) +{ + int i; + + if (numaddrs <= 0) { + zlog_warn("hostname resolution failed: %s", errstr); + return; + } + + for (i = 0; i < numaddrs; i++) + zlog_info("resolver result: %pSU", &addr[i]); +} + +struct resolver_query query; + +DEFUN (test_resolve, + test_resolve_cmd, + "resolve WORD", + "DNS resolver\n" + "Name to resolve\n") +{ + resolver_resolve(&query, AF_UNSPEC, 0, argv[1]->arg, resolver_result); + return CMD_SUCCESS; +} + +__attribute__((_CONSTRUCTOR(2000))) +static void test_setup(void) +{ + test_log_prio = LOG_DEBUG; +} + +void test_init(int argc, char **argv) +{ + resolver_init(master); + + install_element(VIEW_NODE, &test_resolve_cmd); +} diff --git a/tests/lib/test_ringbuf.c b/tests/lib/test_ringbuf.c new file mode 100644 index 0000000..0d7fed7 --- /dev/null +++ b/tests/lib/test_ringbuf.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Circular buffer tests. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#include +#include +#include "ringbuf.h" + +static void validate_state(struct ringbuf *buf, size_t size, size_t contains) +{ + assert(buf->size == size); + assert(ringbuf_remain(buf) == contains); + assert(ringbuf_space(buf) == buf->size - contains); + assert(buf->empty != (bool)contains); +} + +int main(int argc, char **argv) +{ + struct ringbuf *soil = ringbuf_new(BUFSIZ); + + validate_state(soil, BUFSIZ, 0); + + /* verify reset functionality on clean buffer */ + printf("Validating reset on empty buffer...\n"); + ringbuf_reset(soil); + + validate_state(soil, BUFSIZ, 0); + + /* put one byte */ + printf("Validating write...\n"); + uint8_t walnut = 47; + assert(ringbuf_put(soil, &walnut, sizeof(walnut)) == 1); + + validate_state(soil, BUFSIZ, 1); + + /* validate read limitations */ + printf("Validating read limits...\n"); + uint8_t nuts[2]; + assert(ringbuf_get(soil, &nuts, sizeof(nuts)) == 1); + + /* reset */ + printf("Validating reset on full buffer...\n"); + ringbuf_reset(soil); + validate_state(soil, BUFSIZ, 0); + + /* copy stack garbage to buffer */ + printf("Validating big write...\n"); + uint8_t compost[BUFSIZ]; + assert(ringbuf_put(soil, &compost, sizeof(compost)) == BUFSIZ); + + validate_state(soil, BUFSIZ, BUFSIZ); + assert(soil->start == 0); + assert(soil->end == 0); + + /* read 15 bytes of garbage */ + printf("Validating read...\n"); + assert(ringbuf_get(soil, &compost, 15) == 15); + + validate_state(soil, BUFSIZ, BUFSIZ - 15); + assert(soil->start == 15); + assert(soil->end == 0); + + /* put another 10 bytes and validate wraparound */ + printf("Validating wraparound...\n"); + assert(ringbuf_put(soil, &compost[BUFSIZ/2], 10) == 10); + + validate_state(soil, BUFSIZ, BUFSIZ - 15 + 10); + assert(soil->start == 15); + assert(soil->end == 10); + + /* put another 15 bytes and validate state */ + printf("Validating size limits...\n"); + assert(ringbuf_put(soil, &compost, 15) == 5); + validate_state(soil, BUFSIZ, BUFSIZ); + + /* read entire buffer */ + printf("Validating big read...\n"); + assert(ringbuf_get(soil, &compost, BUFSIZ) == BUFSIZ); + + validate_state(soil, BUFSIZ, 0); + assert(soil->empty == true); + assert(soil->start == soil->end); + assert(soil->start == 15); + + /* read empty buffer */ + printf("Validating empty read...\n"); + assert(ringbuf_get(soil, &compost, 1) == 0); + validate_state(soil, BUFSIZ, 0); + + /* reset, validate state */ + printf("Validating reset...\n"); + ringbuf_reset(soil); + validate_state(soil, BUFSIZ, 0); + assert(soil->start == 0); + assert(soil->end == 0); + + /* wipe, validate state */ + printf("Validating wipe...\n"); + memset(&compost, 0x00, sizeof(compost)); + ringbuf_wipe(soil); + assert(memcmp(&compost, soil->data, sizeof(compost)) == 0); + + /* validate maximum write */ + printf("Validating very big write...\n"); + const char flower[BUFSIZ * 2]; + assert(ringbuf_put(soil, &flower, sizeof(flower)) == BUFSIZ); + + validate_state(soil, BUFSIZ, BUFSIZ); + + /* wipe, validate state */ + printf("Validating wipe...\n"); + memset(&compost, 0x00, sizeof(compost)); + ringbuf_wipe(soil); + assert(memcmp(&compost, soil->data, sizeof(compost)) == 0); + + /* validate simple data encode / decode */ + const char *organ = "seed"; + printf("Encoding: '%s'\n", organ); + assert(ringbuf_put(soil, organ, strlen(organ)) == 4); + char water[strlen(organ) + 1]; + assert(ringbuf_get(soil, &water, strlen(organ)) == 4); + water[strlen(organ)] = '\0'; + printf("Retrieved: '%s'\n", water); + + validate_state(soil, BUFSIZ, 0); + + /* validate simple data encode / decode across ring boundary */ + soil->start = soil->size - 2; + soil->end = soil->start; + const char *phloem = "root"; + printf("Encoding: '%s'\n", phloem); + assert(ringbuf_put(soil, phloem, strlen(phloem)) == 4); + char xylem[strlen(phloem) + 1]; + assert(ringbuf_get(soil, &xylem, 100) == 4); + xylem[strlen(phloem)] = '\0'; + printf("Retrieved: '%s'\n", xylem); + + ringbuf_wipe(soil); + + /* validate simple data peek across ring boundary */ + soil->start = soil->size - 2; + soil->end = soil->start; + const char *cytoplasm = "tree"; + printf("Encoding: '%s'\n", cytoplasm); + assert(ringbuf_put(soil, cytoplasm, strlen(cytoplasm)) == 4); + char chloroplast[strlen(cytoplasm) + 1]; + assert(ringbuf_peek(soil, 2, &chloroplast[0], 100) == 2); + assert(ringbuf_peek(soil, 0, &chloroplast[2], 2) == 2); + chloroplast[strlen(cytoplasm)] = '\0'; + assert(!strcmp(chloroplast, "eetr")); + printf("Retrieved: '%s'\n", chloroplast); + + printf("Deleting...\n"); + ringbuf_del(soil); + + printf("Creating new buffer...\n"); + soil = ringbuf_new(15); + soil->start = soil->end = 7; + + /* validate data encode of excessive data */ + const char *twenty = "vascular plants----"; + char sixteen[16]; + printf("Encoding: %s\n", twenty); + assert(ringbuf_put(soil, twenty, strlen(twenty)) == 15); + assert(ringbuf_get(soil, sixteen, 20)); + sixteen[15] = '\0'; + printf("Retrieved: %s\n", sixteen); + assert(!strcmp(sixteen, "vascular plants")); + + printf("Deleting...\n"); + ringbuf_del(soil); + + printf("Done.\n"); + return 0; +} diff --git a/tests/lib/test_ringbuf.py b/tests/lib/test_ringbuf.py new file mode 100644 index 0000000..0cd9dee --- /dev/null +++ b/tests/lib/test_ringbuf.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestRingbuf(frrtest.TestMultiOut): + program = "./test_ringbuf" + + +TestRingbuf.exit_cleanly() diff --git a/tests/lib/test_segv.c b/tests/lib/test_segv.c new file mode 100644 index 0000000..5d2f451 --- /dev/null +++ b/tests/lib/test_segv.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SEGV / backtrace handling test. + * + * copied from test-sig.c + * + * Copyright (C) 2013 by David Lamparter, Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ + +#include +#include +#include "lib/log.h" +#include "lib/memory.h" + +struct frr_signal_t sigs[] = {}; + +struct event_loop *master; + +void func1(int *arg); +void func3(void); + +void func1(int *arg) +{ + int *null = NULL; + *null += 1; + *arg = 1; +} + +static void func2(size_t depth, int *arg) +{ + /* variable stack frame size */ + int buf[depth]; + for (size_t i = 0; i < depth; i++) + buf[i] = arg[i] + 1; + if (depth > 0) + func2(depth - 1, buf); + else + func1(&buf[0]); + for (size_t i = 0; i < depth; i++) + buf[i] = arg[i] + 2; +} + +void func3(void) +{ + int buf[6]; + func2(6, buf); +} + +static void threadfunc(struct event *thread) +{ + func3(); +} + +int main(void) +{ + master = event_master_create(NULL); + signal_init(master, array_size(sigs), sigs); + + zlog_aux_init("NONE: ", LOG_DEBUG); + + event_execute(master, threadfunc, 0, 0, NULL); + + exit(0); +} diff --git a/tests/lib/test_seqlock.c b/tests/lib/test_seqlock.c new file mode 100644 index 0000000..288d4a8 --- /dev/null +++ b/tests/lib/test_seqlock.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * basic test for seqlock + * + * Copyright (C) 2015 David Lamparter + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "monotime.h" +#include "seqlock.h" +#include "printfrr.h" + +static struct seqlock sqlo; +static pthread_t thr1; +static struct timeval start; + +static void writestr(const char *str) +{ + struct iovec iov[2]; + char buf[32]; + int64_t usec = monotime_since(&start, NULL); + + snprintfrr(buf, sizeof(buf), "[%02" PRId64 "] ", usec / 100000); + + iov[0].iov_base = buf; + iov[0].iov_len = strlen(buf); + iov[1].iov_base = (char *)str; + iov[1].iov_len = strlen(str); + writev(1, iov, 2); +} + +static void *thr1func(void *arg) +{ + assert(!seqlock_held(&sqlo)); + assert(seqlock_check(&sqlo, 1)); + seqlock_wait(&sqlo, 1); + writestr("thr1 (unheld)\n"); + + sleep(2); + + assert(seqlock_held(&sqlo)); + assert(seqlock_check(&sqlo, 1)); + seqlock_wait(&sqlo, 1); + writestr("thr1 @1\n"); + + seqlock_wait(&sqlo, 5); + writestr("thr1 @5\n"); + + seqlock_wait(&sqlo, 9); + writestr("thr1 @9\n"); + + seqlock_wait(&sqlo, 13); + writestr("thr1 @13\n"); + + seqlock_wait(&sqlo, 17); + writestr("thr1 @17\n"); + + seqlock_wait(&sqlo, 21); + writestr("thr1 @21\n"); + return NULL; +} + +int main(int argc, char **argv) +{ + monotime(&start); + + seqlock_init(&sqlo); + + assert(!seqlock_held(&sqlo)); + seqlock_acquire_val(&sqlo, 1); + assert(seqlock_held(&sqlo)); + + assert(seqlock_cur(&sqlo) == 1); + assert(seqlock_bump(&sqlo) == 1); + assert(seqlock_cur(&sqlo) == 5); + assert(seqlock_bump(&sqlo) == 5); + assert(seqlock_bump(&sqlo) == 9); + assert(seqlock_bump(&sqlo) == 13); + assert(seqlock_cur(&sqlo) == 17); + + assert(seqlock_held(&sqlo)); + seqlock_release(&sqlo); + assert(!seqlock_held(&sqlo)); + + pthread_create(&thr1, NULL, thr1func, NULL); + sleep(1); + + writestr("main @5\n"); + seqlock_acquire_val(&sqlo, 5); + sleep(2); + + writestr("main @9\n"); + seqlock_bump(&sqlo); + sleep(1); + + writestr("main @17\n"); + seqlock_acquire_val(&sqlo, 17); + sleep(1); + + writestr("main @release\n"); + seqlock_release(&sqlo); + sleep(1); +} diff --git a/tests/lib/test_sig.c b/tests/lib/test_sig.c new file mode 100644 index 0000000..2bd7f30 --- /dev/null +++ b/tests/lib/test_sig.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +#include +#include +#include "lib/log.h" +#include "lib/memory.h" + +static void sighup(void) +{ + printf("processed hup\n"); +} + +static void sigusr1(void) +{ + printf("processed usr1\n"); +} + +static void sigusr2(void) +{ + printf("processed usr2\n"); +} + +struct frr_signal_t sigs[] = {{ + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGUSR2, + .handler = &sigusr2, + }}; + +struct event_loop *master; +struct event t; + +int main(void) +{ + master = event_master_create(NULL); + signal_init(master, array_size(sigs), sigs); + + zlog_aux_init("NONE: ", LOG_DEBUG); + + while (event_fetch(master, &t)) + event_call(&t); + + exit(0); +} diff --git a/tests/lib/test_skiplist.c b/tests/lib/test_skiplist.c new file mode 100644 index 0000000..6af7ac5 --- /dev/null +++ b/tests/lib/test_skiplist.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2021, LabN Consulting, L.L.C + */ + +#include +#include + +static void sl_debug(struct skiplist *l) +{ + int i; + + if (!l) + return; + + printf("Skiplist %p has max level %d\n", l, l->level); + for (i = l->level; i >= 0; --i) + printf(" @%d: %d\n", i, l->level_stats[i]); +} + +static void *scramble(int i) +{ + uintptr_t result; + + result = (uintptr_t)(i & 0xff) << 24; + result |= (uintptr_t)i >> 8; + + return (void *)result; +} +#define sampleSize 65536 +static int sl_test(void) +{ + struct skiplist *l; + register int i, k; + void *keys[sampleSize]; + void *v = NULL; + int errors = 0; + + l = skiplist_new(SKIPLIST_FLAG_ALLOW_DUPLICATES, NULL, NULL); + + printf("%s: skiplist_new returned %p\n", __func__, l); + + for (i = 0; i < 4; i++) { + + for (k = 0; k < sampleSize; k++) { + if (!(k % 10000)) + printf("%s: (%d:%d)\n", __func__, i, k); + /* keys[k] = (void *)random(); */ + keys[k] = scramble(k); + if (skiplist_insert(l, keys[k], keys[k])) { + ++errors; + printf("error in insert #%d,#%d\n", i, k); + } + } + + printf("%s: inserts done\n", __func__); + sl_debug(l); + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 10000)) + printf("[%d:%d]\n", i, k); + /* keys[k] = (void *)random(); */ + if (skiplist_search(l, keys[k], &v)) { + ++errors; + printf("error in search #%d,#%d\n", i, k); + } + + if (v != keys[k]) { + ++errors; + printf("search returned wrong value\n"); + } + } + printf("%s: searches done\n", __func__); + + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 10000)) + printf("<%d:%d>\n", i, k); + /* keys[k] = (void *)random(); */ + if (skiplist_delete(l, keys[k], keys[k])) { + ++errors; + printf("error in delete\n"); + } + keys[k] = scramble(k ^ 0xf0f0f0f0); + if (skiplist_insert(l, keys[k], keys[k])) { + ++errors; + printf("error in insert #%d,#%d\n", i, k); + } + } + + printf("%s: del+inserts done\n", __func__); + sl_debug(l); + + for (k = 0; k < sampleSize; k++) { + + if (!(k % 10000)) + printf("{%d:%d}\n", i, k); + /* keys[k] = (void *)random(); */ + if (skiplist_delete_first(l)) { + ++errors; + printf("error in delete_first\n"); + } + } + } + + sl_debug(l); + + skiplist_free(l); + + return errors; +} + +int main(int argc, char **argv) +{ + int errors = sl_test(); + + if (errors) + return 1; + return 0; +} diff --git a/tests/lib/test_srcdest_table.c b/tests/lib/test_srcdest_table.c new file mode 100644 index 0000000..6d6c515 --- /dev/null +++ b/tests/lib/test_srcdest_table.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test srcdest table for correctness. + * + * Copyright (C) 2017 by David Lamparter & Christian Franke, + * Open Source Routing / NetDEF Inc. + * + * This file is part of FRRouting (FRR) + */ + +#include + +#include "hash.h" +#include "memory.h" +#include "prefix.h" +#include "prng.h" +#include "srcdest_table.h" +#include "table.h" + +/* Copied from ripngd/ripng_nexthop.h - maybe the whole s6_addr32 thing + * should be added by autoconf if not present? + */ +#ifndef s6_addr32 +#define s6_addr32 __u6_addr.__u6_addr32 +#endif /*s6_addr32*/ + +struct event_loop *master; + +/* This structure is copied from lib/srcdest_table.c to which it is + * private as far as other parts of Quagga are concerned. + */ +struct srcdest_rnode { + /* must be first in structure for casting to/from route_node */ + ROUTE_NODE_FIELDS; + + struct route_table *src_table; +}; + +struct test_state { + struct route_table *table; + struct hash *log; +}; + +static char *format_srcdest(const struct prefix_ipv6 *dst_p, + const struct prefix_ipv6 *src_p) +{ + char dst_str[BUFSIZ]; + char src_str[BUFSIZ]; + char *rv; + int ec; + + prefix2str((const struct prefix *)dst_p, dst_str, sizeof(dst_str)); + if (src_p && src_p->prefixlen) + prefix2str((const struct prefix *)src_p, src_str, + sizeof(src_str)); + else + src_str[0] = '\0'; + + ec = asprintf(&rv, "%s%s%s", dst_str, + (src_str[0] != '\0') ? " from " : "", src_str); + + assert(ec > 0); + return rv; +} + +static unsigned int log_key(const void *data) +{ + const struct prefix *hash_entry = data; + struct prefix_ipv6 *dst_p = (struct prefix_ipv6 *)&hash_entry[0]; + struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; + unsigned int hash = 0; + unsigned int i; + + hash = (hash * 33) ^ (unsigned int)dst_p->prefixlen; + for (i = 0; i < 4; i++) + hash = (hash * 33) ^ (unsigned int)dst_p->prefix.s6_addr32[i]; + + hash = (hash * 33) ^ (unsigned int)src_p->prefixlen; + if (src_p->prefixlen) + for (i = 0; i < 4; i++) + hash = (hash * 33) + ^ (unsigned int)src_p->prefix.s6_addr32[i]; + + return hash; +} + +static bool log_cmp(const void *a, const void *b) +{ + if (a == NULL || b == NULL) + return false; + + return !memcmp(a, b, 2 * sizeof(struct prefix)); +} + +static void log_free(void *data) +{ + XFREE(MTYPE_TMP, data); +} + +static void *log_alloc(void *data) +{ + void *rv = XMALLOC(MTYPE_TMP, 2 * sizeof(struct prefix)); + memcpy(rv, data, 2 * sizeof(struct prefix)); + return rv; +} + +static struct test_state *test_state_new(void) +{ + struct test_state *rv; + + rv = XCALLOC(MTYPE_TMP, sizeof(*rv)); + assert(rv); + + rv->table = srcdest_table_init(); + assert(rv->table); + + rv->log = hash_create(log_key, log_cmp, NULL); + return rv; +} + +static void test_state_free(struct test_state *test) +{ + route_table_finish(test->table); + hash_clean_and_free(&test->log, log_free); + XFREE(MTYPE_TMP, test); +} + +static void test_state_add_route(struct test_state *test, + struct prefix_ipv6 *dst_p, + struct prefix_ipv6 *src_p) +{ + struct route_node *rn = + srcdest_rnode_get(test->table, (struct prefix *)dst_p, src_p); + struct prefix hash_entry[2]; + + memset(hash_entry, 0, sizeof(hash_entry)); + memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); + memcpy(&hash_entry[1], src_p, sizeof(*src_p)); + + if (rn->info) { + route_unlock_node(rn); + assert(hash_lookup(test->log, hash_entry) != NULL); + return; + } else { + assert(hash_lookup(test->log, hash_entry) == NULL); + } + + rn->info = (void *)0xdeadbeef; + (void)hash_get(test->log, hash_entry, log_alloc); +}; + +static void test_state_del_route(struct test_state *test, + struct prefix_ipv6 *dst_p, + struct prefix_ipv6 *src_p) +{ + struct route_node *rn = srcdest_rnode_lookup( + test->table, (struct prefix *)dst_p, src_p); + struct prefix hash_entry[2]; + + memset(hash_entry, 0, sizeof(hash_entry)); + memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); + memcpy(&hash_entry[1], src_p, sizeof(*src_p)); + + if (!rn) { + assert(!hash_lookup(test->log, hash_entry)); + return; + } + + assert(rn->info == (void *)0xdeadbeef); + rn->info = NULL; + route_unlock_node(rn); + route_unlock_node(rn); + + struct prefix *hash_entry_intern = hash_release(test->log, hash_entry); + assert(hash_entry_intern != NULL); + XFREE(MTYPE_TMP, hash_entry_intern); +} + +static void verify_log(struct hash_bucket *bucket, void *arg) +{ + struct test_state *test = arg; + struct prefix *hash_entry = bucket->data; + struct prefix *dst_p = &hash_entry[0]; + struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; + struct route_node *rn = srcdest_rnode_lookup(test->table, dst_p, src_p); + + assert(rn); + assert(rn->info == (void *)0xdeadbeef); + + route_unlock_node(rn); +} + +static void dump_log(struct hash_bucket *bucket, void *arg) +{ + struct prefix *hash_entry = bucket->data; + struct prefix_ipv6 *dst_p = (struct prefix_ipv6 *)&hash_entry[0]; + struct prefix_ipv6 *src_p = (struct prefix_ipv6 *)&hash_entry[1]; + char *route_id = format_srcdest(dst_p, src_p); + + fprintf(stderr, " %s\n", route_id); + free(route_id); +} + +static void test_dump(struct test_state *test) +{ + fprintf(stderr, "Contents of hash table:\n"); + hash_iterate(test->log, dump_log, test); + fprintf(stderr, "\n"); +} + +static void test_failed(struct test_state *test, const char *message, + const struct prefix_ipv6 *dst_p, + const struct prefix_ipv6 *src_p) +{ + char *route_id = format_srcdest(dst_p, src_p); + + fprintf(stderr, "Test failed. Error: %s\n", message); + fprintf(stderr, "Route in question: %s\n", route_id); + free(route_id); + + test_dump(test); + assert(3 == 4); +} + +static void test_state_verify(struct test_state *test) +{ + struct route_node *rn; + struct prefix hash_entry[2]; + + memset(hash_entry, 0, sizeof(hash_entry)); + + /* Verify that there are no elements in the table which have never + * been added */ + for (rn = route_top(test->table); rn; rn = srcdest_route_next(rn)) { + const struct prefix_ipv6 *dst_p, *src_p; + + /* While we are iterating, we hold a lock on the current + * route_node, + * so all the lock counts we check for take that into account; + * in idle + * state all the numbers will be exactly one less. + * + * Also this makes quite some assumptions based on the current + * implementation details of route_table and srcdest_table - + * another + * valid implementation might trigger assertions here. + */ + + if (rnode_is_dstnode(rn)) { + struct srcdest_rnode *srn = (struct srcdest_rnode *)rn; + unsigned int expected_lock = 1; /* We are in the loop */ + + if (rn->info + != NULL) /* The route node is not internal */ + expected_lock++; + if (srn->src_table != NULL) /* There's a source table + associated with rn */ + expected_lock++; + + if (route_node_get_lock_count(rn) != expected_lock) + test_failed( + test, + "Dest rnode lock count doesn't match expected count!", + (struct prefix_ipv6 *)&rn->p, NULL); + } else { + unsigned int expected_lock = 1; /* We are in the loop */ + + if (rn->info + != NULL) /* The route node is not internal */ + expected_lock++; + + if (route_node_get_lock_count(rn) != expected_lock) { + srcdest_rnode_prefixes( + rn, (const struct prefix **)&dst_p, + (const struct prefix **)&src_p); + + test_failed( + test, + "Src rnode lock count doesn't match expected count!", + dst_p, src_p); + } + } + + if (!rn->info) + continue; + + assert(rn->info == (void *)0xdeadbeef); + + srcdest_rnode_prefixes(rn, (const struct prefix **)&dst_p, + (const struct prefix **)&src_p); + memcpy(&hash_entry[0], dst_p, sizeof(*dst_p)); + if (src_p) + memcpy(&hash_entry[1], src_p, sizeof(*src_p)); + else + memset(&hash_entry[1], 0, sizeof(hash_entry[1])); + + if (hash_lookup(test->log, hash_entry) == NULL) + test_failed(test, "Route is missing in hash", dst_p, + src_p); + } + + /* Verify that all added elements are still in the table */ + hash_iterate(test->log, verify_log, test); +} + +static void get_rand_prefix(struct prng *prng, struct prefix_ipv6 *p) +{ + int i; + + memset(p, 0, sizeof(*p)); + + for (i = 0; i < 4; i++) + p->prefix.s6_addr32[i] = prng_rand(prng); + p->prefixlen = prng_rand(prng) % 129; + p->family = AF_INET6; + + apply_mask(p); +} + +static void get_rand_prefix_pair(struct prng *prng, struct prefix_ipv6 *dst_p, + struct prefix_ipv6 *src_p) +{ + get_rand_prefix(prng, dst_p); + if ((prng_rand(prng) % 4) == 0) { + get_rand_prefix(prng, src_p); + if (src_p->prefixlen) + return; + } + + memset(src_p, 0, sizeof(*src_p)); +} + +static void test_state_add_rand_route(struct test_state *test, + struct prng *prng) +{ + struct prefix_ipv6 dst_p, src_p; + + get_rand_prefix_pair(prng, &dst_p, &src_p); + test_state_add_route(test, &dst_p, &src_p); +} + +static void test_state_del_rand_route(struct test_state *test, + struct prng *prng) +{ + struct prefix_ipv6 dst_p, src_p; + + get_rand_prefix_pair(prng, &dst_p, &src_p); + test_state_del_route(test, &dst_p, &src_p); +} + +static void test_state_del_one_route(struct test_state *test, struct prng *prng) +{ + unsigned int which_route; + + if (test->log->count == 0) + return; + + which_route = prng_rand(prng) % test->log->count; + + struct route_node *rn; + const struct prefix *dst_p, *src_p; + struct prefix_ipv6 dst6_p, src6_p; + + for (rn = route_top(test->table); rn; rn = srcdest_route_next(rn)) { + if (!rn->info) + continue; + if (!which_route) { + route_unlock_node(rn); + break; + } + which_route--; + } + + assert(rn); + srcdest_rnode_prefixes(rn, &dst_p, &src_p); + memcpy(&dst6_p, dst_p, sizeof(dst6_p)); + if (src_p) + memcpy(&src6_p, src_p, sizeof(src6_p)); + else + memset(&src6_p, 0, sizeof(src6_p)); + + test_state_del_route(test, &dst6_p, &src6_p); +} + +static void run_prng_test(void) +{ + struct test_state *test = test_state_new(); + struct prng *prng = prng_new(0); + size_t i; + + for (i = 0; i < 1000; i++) { + switch (prng_rand(prng) % 10) { + case 0: + case 1: + case 2: + case 3: + case 4: + test_state_add_rand_route(test, prng); + break; + case 5: + case 6: + case 7: + test_state_del_one_route(test, prng); + break; + case 8: + case 9: + test_state_del_rand_route(test, prng); + break; + } + test_state_verify(test); + } + + prng_free(prng); + test_state_free(test); +} + +int main(int argc, char *argv[]) +{ + run_prng_test(); + printf("PRNG Test successful.\n"); + return 0; +} diff --git a/tests/lib/test_srcdest_table.py b/tests/lib/test_srcdest_table.py new file mode 100644 index 0000000..d0dde6a --- /dev/null +++ b/tests/lib/test_srcdest_table.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestSrcdestTable(frrtest.TestMultiOut): + program = "./test_srcdest_table" + + +TestSrcdestTable.onesimple("PRNG Test successful.") diff --git a/tests/lib/test_stream.c b/tests/lib/test_stream.c new file mode 100644 index 0000000..d38dfe0 --- /dev/null +++ b/tests/lib/test_stream.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Simple stream test. + * + * Copyright (C) 2006 Sun Microsystems, Inc. + */ + +#include +#include +#include "frrevent.h" + +#include "printfrr.h" + +static unsigned long long ham = 0xdeadbeefdeadbeef; +struct event_loop *master; + +static void print_stream(struct stream *s) +{ + size_t getp = stream_get_getp(s); + + printfrr("endp: %zu, readable: %zu, writeable: %zu\n", + stream_get_endp(s), STREAM_READABLE(s), STREAM_WRITEABLE(s)); + + while (STREAM_READABLE(s)) { + printfrr("0x%x ", *stream_pnt(s)); + stream_forward_getp(s, 1); + } + + printfrr("\n"); + + /* put getp back to where it was */ + stream_set_getp(s, getp); +} + +int main(void) +{ + struct stream *s; + + s = stream_new(1024); + + stream_putc(s, ham); + stream_putw(s, ham); + stream_putl(s, ham); + stream_putq(s, ham); + + print_stream(s); + + stream_resize_inplace(&s, stream_get_endp(s)); + + print_stream(s); + + printfrr("c: 0x%hhx\n", stream_getc(s)); + printfrr("w: 0x%hx\n", stream_getw(s)); + printfrr("l: 0x%x\n", stream_getl(s)); + printfrr("q: 0x%" PRIx64 "\n", stream_getq(s)); + + return 0; +} diff --git a/tests/lib/test_stream.py b/tests/lib/test_stream.py new file mode 100644 index 0000000..11d902e --- /dev/null +++ b/tests/lib/test_stream.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestStream(frrtest.TestRefOut): + program = "./test_stream" diff --git a/tests/lib/test_stream.refout b/tests/lib/test_stream.refout new file mode 100644 index 0000000..cf52e13 --- /dev/null +++ b/tests/lib/test_stream.refout @@ -0,0 +1,8 @@ +endp: 15, readable: 15, writeable: 1009 +0xef 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef +endp: 15, readable: 15, writeable: 0 +0xef 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef 0xde 0xad 0xbe 0xef +c: 0xef +w: 0xbeef +l: 0xdeadbeef +q: 0xdeadbeefdeadbeef diff --git a/tests/lib/test_table.c b/tests/lib/test_table.c new file mode 100644 index 0000000..51bfda2 --- /dev/null +++ b/tests/lib/test_table.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routing table test + * Copyright (C) 2012 OSR. + * + * This file is part of Quagga + */ + +#include +#include "printfrr.h" +#include "prefix.h" +#include "table.h" + +/* + * test_node_t + * + * Information that is kept for each node in the radix tree. + */ +typedef struct test_node_t_ { + + /* + * Human readable representation of the string. Allocated using + * malloc()/dup(). + */ + char *prefix_str; +} test_node_t; + +struct event_loop *master; + +/* + * add_node + * + * Add the given prefix (passed in as a string) to the given table. + */ +static void add_node(struct route_table *table, const char *prefix_str) +{ + struct prefix_ipv4 p; + test_node_t *node; + struct route_node *rn; + + assert(prefix_str); + + if (str2prefix_ipv4(prefix_str, &p) <= 0) { + assert(0); + } + + rn = route_node_get(table, (struct prefix *)&p); + if (rn->info) { + assert(0); + return; + } + + node = malloc(sizeof(test_node_t)); + assert(node); + node->prefix_str = strdup(prefix_str); + assert(node->prefix_str); + rn->info = node; +} + +/* + * add_nodes + * + * Convenience function to add a bunch of nodes together. + * + * The arguments must be prefixes in string format, with a NULL as the + * last argument. + */ +static void add_nodes(struct route_table *table, ...) +{ + va_list arglist; + char *prefix; + + va_start(arglist, table); + + prefix = va_arg(arglist, char *); + while (prefix) { + add_node(table, prefix); + prefix = va_arg(arglist, char *); + } + + va_end(arglist); +} + +/* + * print_subtree + * + * Recursive function to print a route node and its children. + * + * @see print_table + */ +static void print_subtree(struct route_node *rn, const char *legend, + int indent_level) +{ + int i; + + /* + * Print this node first. + */ + for (i = 0; i < indent_level; i++) { + printf(" "); + } + + printfrr("%s: %pFX", legend, &rn->p); + if (!rn->info) { + printf(" (internal)"); + } + printf("\n"); + if (rn->l_left) { + print_subtree(rn->l_left, "Left", indent_level + 1); + } + if (rn->l_right) { + print_subtree(rn->l_right, "Right", indent_level + 1); + } +} + +/* + * print_table + * + * Function that prints out the internal structure of a route table. + */ +static void print_table(struct route_table *table) +{ + struct route_node *rn; + + rn = table->top; + + if (!rn) { + printf("\n"); + return; + } + + print_subtree(rn, "Top", 0); +} + +/* + * clear_table + * + * Remove all nodes from the given table. + */ +static void clear_table(struct route_table *table) +{ + route_table_iter_t iter; + struct route_node *rn; + test_node_t *node; + + route_table_iter_init(&iter, table); + + while ((rn = route_table_iter_next(&iter))) { + node = rn->info; + if (!node) { + continue; + } + rn->info = NULL; + route_unlock_node(rn); + free(node->prefix_str); + free(node); + } + + route_table_iter_cleanup(&iter); + + assert(table->top == NULL); +} + +/* + * verify_next_by_iterating + * + * Iterate over the tree to make sure that the first prefix after + * target_pfx is the expected one. Note that target_pfx may not be + * present in the tree. + */ +static void verify_next_by_iterating(struct route_table *table, + struct prefix *target_pfx, + struct prefix *next_pfx) +{ + route_table_iter_t iter; + struct route_node *rn; + + route_table_iter_init(&iter, table); + while ((rn = route_table_iter_next(&iter))) { + if (route_table_prefix_iter_cmp(&rn->p, target_pfx) > 0) { + assert(!prefix_cmp(&rn->p, next_pfx)); + break; + } + } + + if (!rn) { + assert(!next_pfx); + } + + route_table_iter_cleanup(&iter); +} + +/* + * verify_next + * + * Verifies that route_table_get_next() returns the expected result + * (result) for the prefix string 'target'. + */ +static void verify_next(struct route_table *table, const char *target, + const char *next) +{ + struct prefix_ipv4 target_pfx, next_pfx; + struct route_node *rn; + char result_buf[PREFIX2STR_BUFFER]; + + if (str2prefix_ipv4(target, &target_pfx) <= 0) { + assert(0); + } + + rn = route_table_get_next(table, (struct prefix *)&target_pfx); + if (rn) { + prefix2str(&rn->p, result_buf, sizeof(result_buf)); + } else { + snprintf(result_buf, sizeof(result_buf), "(Null)"); + } + + printf("\n"); + print_table(table); + printf("Verifying successor of %s. Expected: %s, Result: %s\n", target, + next ? next : "(Null)", result_buf); + + if (!rn) { + assert(!next); + verify_next_by_iterating(table, (struct prefix *)&target_pfx, + NULL); + return; + } + + assert(next); + + if (str2prefix_ipv4(next, &next_pfx) <= 0) { + assert(0); + } + + if (prefix_cmp(&rn->p, (struct prefix *)&next_pfx)) { + assert(0); + } + route_unlock_node(rn); + + verify_next_by_iterating(table, (struct prefix *)&target_pfx, + (struct prefix *)&next_pfx); +} + +/* + * test_get_next + */ +static void test_get_next(void) +{ + struct route_table *table; + + printf("\n\nTesting route_table_get_next()\n"); + table = route_table_init(); + + /* + * Target exists in tree, but has no successor. + */ + add_nodes(table, "1.0.1.0/24", NULL); + verify_next(table, "1.0.1.0/24", NULL); + clear_table(table); + + /* + * Target exists in tree, and there is a node in its left subtree. + */ + add_nodes(table, "1.0.1.0/24", "1.0.1.0/25", NULL); + verify_next(table, "1.0.1.0/24", "1.0.1.0/25"); + clear_table(table); + + /* + * Target exists in tree, and there is a node in its right subtree. + */ + add_nodes(table, "1.0.1.0/24", "1.0.1.128/25", NULL); + verify_next(table, "1.0.1.0/24", "1.0.1.128/25"); + clear_table(table); + + /* + * Target exists in the tree, next node is outside subtree. + */ + add_nodes(table, "1.0.1.0/24", "1.1.0.0/16", NULL); + verify_next(table, "1.0.1.0/24", "1.1.0.0/16"); + clear_table(table); + + /* + * The target node does not exist in the tree for all the test cases + * below this point. + */ + + /* + * There is no successor in the tree. + */ + add_nodes(table, "1.0.0.0/16", NULL); + verify_next(table, "1.0.1.0/24", NULL); + clear_table(table); + + /* + * There exists a node that would be in the target's left subtree. + */ + add_nodes(table, "1.0.0.0/16", "1.0.1.0/25", NULL); + verify_next(table, "1.0.1.0/24", "1.0.1.0/25"); + clear_table(table); + + /* + * There exists a node would be in the target's right subtree. + */ + add_nodes(table, "1.0.0.0/16", "1.0.1.128/25", NULL); + verify_next(table, "1.0.1.0/24", "1.0.1.128/25"); + clear_table(table); + + /* + * A search for the target reaches a node where there are no child + * nodes in the direction of the target (left), but the node has a + * right child. + */ + add_nodes(table, "1.0.0.0/16", "1.0.128.0/17", NULL); + verify_next(table, "1.0.0.0/17", "1.0.128.0/17"); + clear_table(table); + + /* + * A search for the target reaches a node with no children. We have + * to go upwards in the tree to find a successor. + */ + add_nodes(table, "1.0.0.0/16", "1.0.0.0/24", "1.0.1.0/24", + "1.0.128.0/17", NULL); + verify_next(table, "1.0.1.0/25", "1.0.128.0/17"); + clear_table(table); + + /* + * A search for the target reaches a node where neither the node nor + * the target prefix contain each other. + * + * In first case below the node succeeds the target. + * + * In the second case, the node comes before the target, so we have + * to go up the tree looking for a successor. + */ + add_nodes(table, "1.0.0.0/16", "1.0.1.0/24", NULL); + verify_next(table, "1.0.0.0/24", "1.0.1.0/24"); + clear_table(table); + + add_nodes(table, "1.0.0.0/16", "1.0.0.0/24", "1.0.1.0/25", + "1.0.128.0/17", NULL); + verify_next(table, "1.0.1.128/25", "1.0.128.0/17"); + clear_table(table); + + route_table_finish(table); +} + +/* + * verify_prefix_iter_cmp + */ +static void verify_prefix_iter_cmp(const char *p1, const char *p2, + int exp_result) +{ + struct prefix_ipv4 p1_pfx, p2_pfx; + int result; + + if (str2prefix_ipv4(p1, &p1_pfx) <= 0) { + assert(0); + } + + if (str2prefix_ipv4(p2, &p2_pfx) <= 0) { + assert(0); + } + + result = route_table_prefix_iter_cmp((struct prefix *)&p1_pfx, + (struct prefix *)&p2_pfx); + + printf("Verifying cmp(%s, %s) returns %d\n", p1, p2, exp_result); + + assert(exp_result == result); + + /* + * Also check the reverse comparison. + */ + result = route_table_prefix_iter_cmp((struct prefix *)&p2_pfx, + (struct prefix *)&p1_pfx); + + if (exp_result) { + exp_result = -exp_result; + } + + printf("Verifying cmp(%s, %s) returns %d\n", p1, p2, exp_result); + assert(result == exp_result); +} + +/* + * test_prefix_iter_cmp + * + * Tests comparison of prefixes according to order of iteration. + */ +static void test_prefix_iter_cmp(void) +{ + printf("\n\nTesting route_table_prefix_iter_cmp()\n"); + + verify_prefix_iter_cmp("1.0.0.0/8", "1.0.0.0/8", 0); + + verify_prefix_iter_cmp("1.0.0.0/8", "1.0.0.0/16", -1); + + verify_prefix_iter_cmp("1.0.0.0/16", "1.128.0.0/16", -1); +} + +/* + * verify_iter_with_pause + * + * Iterates over a tree using two methods: 'normal' iteration, and an + * iterator that pauses at each node. Verifies that the two methods + * yield the same results. + */ +static void verify_iter_with_pause(struct route_table *table) +{ + unsigned long num_nodes; + struct route_node *rn, *iter_rn; + route_table_iter_t iter_space; + route_table_iter_t *iter = &iter_space; + + route_table_iter_init(iter, table); + num_nodes = 0; + + for (rn = route_top(table); rn; rn = route_next(rn)) { + num_nodes++; + route_table_iter_pause(iter); + + assert(iter->current == NULL); + if (route_table_iter_started(iter)) { + assert(iter->state == RT_ITER_STATE_PAUSED); + } else { + assert(rn == table->top); + assert(iter->state == RT_ITER_STATE_INIT); + } + + iter_rn = route_table_iter_next(iter); + + /* + * Make sure both iterations return the same node. + */ + assert(rn == iter_rn); + } + + assert(num_nodes == route_table_count(table)); + + route_table_iter_pause(iter); + iter_rn = route_table_iter_next(iter); + + assert(iter_rn == NULL); + assert(iter->state == RT_ITER_STATE_DONE); + + assert(route_table_iter_next(iter) == NULL); + assert(iter->state == RT_ITER_STATE_DONE); + + route_table_iter_cleanup(iter); + + print_table(table); + printf("Verified pausing iteration on tree with %lu nodes\n", + num_nodes); +} + +/* + * test_iter_pause + */ +static void test_iter_pause(void) +{ + struct route_table *table; + int i, num_prefixes; + const char *prefixes[] = {"1.0.1.0/24", "1.0.1.0/25", "1.0.1.128/25", + "1.0.2.0/24", "2.0.0.0/8"}; + + num_prefixes = array_size(prefixes); + + printf("\n\nTesting that route_table_iter_pause() works as expected\n"); + table = route_table_init(); + for (i = 0; i < num_prefixes; i++) { + add_nodes(table, prefixes[i], NULL); + } + + verify_iter_with_pause(table); + + clear_table(table); + route_table_finish(table); +} + +/* + * run_tests + */ +static void run_tests(void) +{ + test_prefix_iter_cmp(); + test_get_next(); + test_iter_pause(); +} + +/* + * main + */ +int main(void) +{ + run_tests(); +} diff --git a/tests/lib/test_table.py b/tests/lib/test_table.py new file mode 100644 index 0000000..ee1849f --- /dev/null +++ b/tests/lib/test_table.py @@ -0,0 +1,12 @@ +import frrtest + + +class TestTable(frrtest.TestMultiOut): + program = "./test_table" + + +for i in range(6): + TestTable.onesimple("Verifying cmp") +for i in range(11): + TestTable.onesimple("Verifying successor") +TestTable.onesimple("Verified pausing") diff --git a/tests/lib/test_timer_correctness.c b/tests/lib/test_timer_correctness.c new file mode 100644 index 0000000..04c0516 --- /dev/null +++ b/tests/lib/test_timer_correctness.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test program to verify that scheduled timers are executed in the + * correct order. + * + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ + +#include + +#include +#include + +#include "memory.h" +#include "prng.h" +#include "frrevent.h" + +#define SCHEDULE_TIMERS 800 +#define REMOVE_TIMERS 200 + +#define TIMESTR_LEN strlen("4294967296.999999") + +struct event_loop *master; + +static size_t log_buf_len; +static size_t log_buf_pos; +static char *log_buf; + +static size_t expected_buf_len; +static size_t expected_buf_pos; +static char *expected_buf; + +static struct prng *prng; + +static struct event **timers; + +static int timers_pending; + +static void terminate_test(void) +{ + int exit_code; + + if (strcmp(log_buf, expected_buf)) { + fprintf(stderr, + "Expected output and received output differ.\n"); + fprintf(stderr, "---Expected output: ---\n%s", expected_buf); + fprintf(stderr, "---Actual output: ---\n%s", log_buf); + exit_code = 1; + } else { + printf("Expected output and actual output match.\n"); + exit_code = 0; + } + + event_master_free(master); + XFREE(MTYPE_TMP, log_buf); + XFREE(MTYPE_TMP, expected_buf); + prng_free(prng); + XFREE(MTYPE_TMP, timers); + + exit(exit_code); +} + +static void timer_func(struct event *thread) +{ + int rv; + + rv = snprintf(log_buf + log_buf_pos, log_buf_len - log_buf_pos, "%s\n", + (char *)thread->arg); + assert(rv >= 0); + log_buf_pos += rv; + assert(log_buf_pos < log_buf_len); + XFREE(MTYPE_TMP, thread->arg); + + timers_pending--; + if (!timers_pending) + terminate_test(); +} + +static int cmp_timeval(const void *a, const void *b) +{ + const struct timeval *ta = *(struct timeval * const *)a; + const struct timeval *tb = *(struct timeval * const *)b; + + if (timercmp(ta, tb, <)) + return -1; + if (timercmp(ta, tb, >)) + return 1; + return 0; +} + +int main(int argc, char **argv) +{ + int i, j; + struct event t; + struct timeval **alarms; + + master = event_master_create(NULL); + + log_buf_len = SCHEDULE_TIMERS * (TIMESTR_LEN + 1) + 1; + log_buf_pos = 0; + log_buf = XMALLOC(MTYPE_TMP, log_buf_len); + + expected_buf_len = SCHEDULE_TIMERS * (TIMESTR_LEN + 1) + 1; + expected_buf_pos = 0; + expected_buf = XMALLOC(MTYPE_TMP, expected_buf_len); + + prng = prng_new(0); + + timers = XCALLOC(MTYPE_TMP, SCHEDULE_TIMERS * sizeof(*timers)); + + for (i = 0; i < SCHEDULE_TIMERS; i++) { + long interval_msec; + int ret; + char *arg; + + /* Schedule timers to expire in 0..5 seconds */ + interval_msec = prng_rand(prng) % 5000; + arg = XMALLOC(MTYPE_TMP, TIMESTR_LEN + 1); + event_add_timer_msec(master, timer_func, arg, interval_msec, + &timers[i]); + ret = snprintf(arg, TIMESTR_LEN + 1, "%lld.%06lld", + (long long)timers[i]->u.sands.tv_sec, + (long long)timers[i]->u.sands.tv_usec); + assert(ret > 0); + assert((size_t)ret < TIMESTR_LEN + 1); + timers_pending++; + } + + for (i = 0; i < REMOVE_TIMERS; i++) { + int index; + + index = prng_rand(prng) % SCHEDULE_TIMERS; + if (!timers[index]) + continue; + + XFREE(MTYPE_TMP, timers[index]->arg); + event_cancel(&timers[index]); + timers_pending--; + } + + /* We create an array of pointers to the alarm times and sort + * that array. That sorted array is used to generate a string + * representing the expected "output" of the timers when they + * are run. */ + j = 0; + alarms = XCALLOC(MTYPE_TMP, timers_pending * sizeof(*alarms)); + for (i = 0; i < SCHEDULE_TIMERS; i++) { + if (!timers[i]) + continue; + alarms[j++] = &timers[i]->u.sands; + } + qsort(alarms, j, sizeof(*alarms), cmp_timeval); + for (i = 0; i < j; i++) { + int ret; + + ret = snprintf(expected_buf + expected_buf_pos, + expected_buf_len - expected_buf_pos, + "%lld.%06lld\n", (long long)alarms[i]->tv_sec, + (long long)alarms[i]->tv_usec); + assert(ret > 0); + expected_buf_pos += ret; + assert(expected_buf_pos < expected_buf_len); + } + XFREE(MTYPE_TMP, alarms); + + while (event_fetch(master, &t)) + event_call(&t); + + return 0; +} diff --git a/tests/lib/test_timer_correctness.py b/tests/lib/test_timer_correctness.py new file mode 100644 index 0000000..71f45f9 --- /dev/null +++ b/tests/lib/test_timer_correctness.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestTimerCorrectness(frrtest.TestMultiOut): + program = "./test_timer_correctness" + + +TestTimerCorrectness.onesimple("Expected output and actual output match.") diff --git a/tests/lib/test_timer_performance.c b/tests/lib/test_timer_performance.c new file mode 100644 index 0000000..3ace076 --- /dev/null +++ b/tests/lib/test_timer_performance.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test program which measures the time it takes to schedule and + * remove timers. + * + * Copyright (C) 2013 by Open Source Routing. + * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC") + * + * This file is part of Quagga + */ + +#include + +#include +#include + +#include "frrevent.h" +#include "prng.h" + +#define SCHEDULE_TIMERS 1000000 +#define REMOVE_TIMERS 500000 + +struct event_loop *master; + +static void dummy_func(struct event *thread) +{ +} + +int main(int argc, char **argv) +{ + struct prng *prng; + int i; + struct event **timers; + struct timeval tv_start, tv_lap, tv_stop; + unsigned long t_schedule, t_remove; + + master = event_master_create(NULL); + prng = prng_new(0); + timers = calloc(SCHEDULE_TIMERS, sizeof(*timers)); + + /* create thread structures so they won't be allocated during the + * time measurement */ + for (i = 0; i < SCHEDULE_TIMERS; i++) { + event_add_timer_msec(master, dummy_func, NULL, 0, &timers[i]); + } + for (i = 0; i < SCHEDULE_TIMERS; i++) + event_cancel(&timers[i]); + + monotime(&tv_start); + + for (i = 0; i < SCHEDULE_TIMERS; i++) { + long interval_msec; + + interval_msec = prng_rand(prng) % (100 * SCHEDULE_TIMERS); + event_add_timer_msec(master, dummy_func, NULL, interval_msec, + &timers[i]); + } + + monotime(&tv_lap); + + for (i = 0; i < REMOVE_TIMERS; i++) { + int index; + + index = prng_rand(prng) % SCHEDULE_TIMERS; + event_cancel(&timers[index]); + } + + monotime(&tv_stop); + + t_schedule = 1000 * (tv_lap.tv_sec - tv_start.tv_sec); + t_schedule += (tv_lap.tv_usec - tv_start.tv_usec) / 1000; + + t_remove = 1000 * (tv_stop.tv_sec - tv_lap.tv_sec); + t_remove += (tv_stop.tv_usec - tv_lap.tv_usec) / 1000; + + printf("Scheduling %d random timers took %lu.%03lu seconds.\n", + SCHEDULE_TIMERS, t_schedule / 1000, t_schedule % 1000); + printf("Removing %d random timers took %lu.%03lu seconds.\n", + REMOVE_TIMERS, t_remove / 1000, t_remove % 1000); + fflush(stdout); + + free(timers); + event_master_free(master); + prng_free(prng); + return 0; +} diff --git a/tests/lib/test_ttable.c b/tests/lib/test_ttable.c new file mode 100644 index 0000000..562ddf9 --- /dev/null +++ b/tests/lib/test_ttable.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ASCII table generator. + * Copyright (C) 2017 Cumulus Networks + * Quentin Young + */ +#include +#include +#include + +int main(int argc, char **argv) +{ + char *table; + + struct ttable *tt = ttable_new(&ttable_styles[TTSTYLE_ASCII]); + + /* test printf compatibility and dimension counters */ + ttable_add_row(tt, "%s|%s|%s", "Column 1", "Column 2", "Column 3"); + assert(tt->ncols == 3); + assert(tt->nrows == 1); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* add new row with 1 column, assert that it is not added */ + assert(ttable_add_row(tt, "%s", "Garbage") == NULL); + assert(tt->ncols == 3); + assert(tt->nrows == 1); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* add new row, assert that it is added */ + assert(ttable_add_row(tt, "%s|%s|%s", "a", "b", "c")); + assert(tt->ncols == 3); + assert(tt->nrows == 2); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* add empty row, assert that it is added */ + assert(ttable_add_row(tt, "||")); + assert(tt->ncols == 3); + assert(tt->nrows == 3); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* delete 1st row, assert that it is removed */ + ttable_del_row(tt, 0); + assert(tt->ncols == 3); + assert(tt->nrows == 2); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* delete last row, assert that it is removed */ + ttable_del_row(tt, 0); + assert(tt->ncols == 3); + assert(tt->nrows == 1); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* delete the remaining row, check dumping an empty table */ + ttable_del_row(tt, 0); + assert(tt->ncols == 0); + assert(tt->nrows == 0); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* add new row */ + ttable_add_row(tt, "%s|%s||%s|%9d", "slick", "black", "triple", 1337); + assert(tt->ncols == 5); + assert(tt->nrows == 1); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* add bigger row */ + ttable_add_row(tt, "%s|%s||%s|%s", + "nebula dusk session streets twilight pioneer beats yeah", + "prarie dog", "cornmeal", ":O -*_-*"); + assert(tt->ncols == 5); + assert(tt->nrows == 2); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* insert new row at beginning */ + ttable_insert_row(tt, 0, "%s|%s||%d|%lf", "converting", "vegetarians", + 2, 2015.0); + assert(tt->ncols == 5); + assert(tt->nrows == 3); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* insert new row at end */ + ttable_insert_row(tt, tt->nrows - 1, "%s|%s||%d|%ld", "converting", + "vegetarians", 1, 2003L); + assert(tt->ncols == 5); + assert(tt->nrows == 4); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* insert new row at middle */ + ttable_insert_row(tt, 1, "%s|%s||%s|%ld", "she", "pioneer", "aki", 1l); + assert(tt->ncols == 5); + assert(tt->nrows == 5); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* set alignment */ + ttable_align(tt, 0, 1, 2, 2, LEFT); + assert(tt->ncols == 5); + assert(tt->nrows == 5); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_align(tt, 0, 1, 5, 1, RIGHT); + assert(tt->ncols == 5); + assert(tt->nrows == 5); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* set padding */ + ttable_pad(tt, 0, 1, 1, 1, RIGHT, 2); + assert(tt->ncols == 5); + assert(tt->nrows == 5); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + ttable_pad(tt, 0, 0, 5, 4, LEFT, 2); + assert(tt->ncols == 5); + assert(tt->nrows == 5); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* restyle */ + tt->style.cell.border.bottom_on = false; + tt->style.cell.border.top_on = false; + tt->style.cell.border.right_on = false; + tt->style.cell.border.left_on = false; + ttable_restyle(tt); + + /* top & double bottom border for top row */ + ttable_rowseps(tt, 0, BOTTOM, true, '-'); + ttable_rowseps(tt, 1, TOP, true, '-'); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* column separators for leftmost column */ + ttable_colseps(tt, 0, RIGHT, true, '|'); + table = ttable_dump(tt, "\n"); + fprintf(stdout, "%s\n", table); + XFREE(MTYPE_TMP, table); + + /* delete table */ + ttable_del(tt); +} diff --git a/tests/lib/test_ttable.py b/tests/lib/test_ttable.py new file mode 100644 index 0000000..9151181 --- /dev/null +++ b/tests/lib/test_ttable.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestTTable(frrtest.TestRefOut): + program = "./test_ttable" diff --git a/tests/lib/test_ttable.refout b/tests/lib/test_ttable.refout new file mode 100644 index 0000000..fb59c0f --- /dev/null +++ b/tests/lib/test_ttable.refout @@ -0,0 +1,143 @@ + |--------------------------------| + | Column 1 | Column 2 | Column 3 | + |--------------------------------| + + |--------------------------------| + | Column 1 | Column 2 | Column 3 | + |--------------------------------| + + |--------------------------------| + | Column 1 | Column 2 | Column 3 | + |----------+----------+----------| + | a | b | c | + |--------------------------------| + + |--------------------------------| + | Column 1 | Column 2 | Column 3 | + |----------+----------+----------| + | a | b | c | + |----------+----------+----------| + | | | | + |--------------------------------| + + |-----------| + | a | b | c | + |---+---+---| + | | | | + |-----------| + + |--------| + | | | | + |--------| + + || + || + + |---------------------------------------| + | slick | black | | triple | 1337 | + |---------------------------------------| + + |------------------------------------------------------------------------------------------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+------------+--+----------+-----------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |------------------------------------------------------------------------------------------------| + + |---------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |---------------------------------------------------------------------------------------------------| + + |---------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |---------------------------------------------------------------------------------------------------| + + |---------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | she | pioneer | | aki | 1 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |---------------------------------------------------------------------------------------------------| + + |---------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | she | pioneer | | aki | 1 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |---------------------------------------------------------------------------------------------------| + + |---------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | she | pioneer | | aki | 1 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |---------------------------------------------------------+-------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |---------------------------------------------------------------------------------------------------| + + |----------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |---------------------------------------------------------+--------------+--+----------+-------------| + | she | pioneer | | aki | 1 | + |---------------------------------------------------------+--------------+--+----------+-------------| + | slick | black | | triple | 1337 | + |---------------------------------------------------------+--------------+--+----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |---------------------------------------------------------+--------------+--+----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |----------------------------------------------------------------------------------------------------| + + |--------------------------------------------------------------------------------------------------------| + | converting | vegetarians | | 2 | 2015.000000 | + |----------------------------------------------------------+---------------+---+-----------+-------------| + | she | pioneer | | aki | 1 | + |----------------------------------------------------------+---------------+---+-----------+-------------| + | slick | black | | triple | 1337 | + |----------------------------------------------------------+---------------+---+-----------+-------------| + | converting | vegetarians | | 1 | 2003 | + |----------------------------------------------------------+---------------+---+-----------+-------------| + | nebula dusk session streets twilight pioneer beats yeah | prarie dog | | cornmeal | :O -*_-* | + |--------------------------------------------------------------------------------------------------------| + + |-----------------------------------------------------------------------------------------------| + | converting vegetarians 2 2015.000000 | + |-----------------------------------------------------------------------------------------------| + |-----------------------------------------------------------------------------------------------| + | she pioneer aki 1 | + | slick black triple 1337 | + | converting vegetarians 1 2003 | + | nebula dusk session streets twilight pioneer beats yeah prarie dog cornmeal :O -*_-* | + |-----------------------------------------------------------------------------------------------| + + |------------------------------------------------------------------------------------------------| + | converting | vegetarians 2 2015.000000 | + |---------------------------------------------------------+--------------------------------------| + |---------------------------------------------------------+--------------------------------------| + | she | pioneer aki 1 | + | slick | black triple 1337 | + | converting | vegetarians 1 2003 | + | nebula dusk session streets twilight pioneer beats yeah | prarie dog cornmeal :O -*_-* | + |------------------------------------------------------------------------------------------------| + diff --git a/tests/lib/test_typelist.c b/tests/lib/test_typelist.c new file mode 100644 index 0000000..070a304 --- /dev/null +++ b/tests/lib/test_typelist.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2016-2018 David Lamparter, for NetDEF, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#define WNO_ATOMLIST_UNSAFE_FIND + +#include "typesafe.h" +#include "atomlist.h" +#include "memory.h" +#include "monotime.h" +#include "jhash.h" +#include "sha256.h" +#include "printfrr.h" + +#include "tests/helpers/c/prng.h" + +/* note: these macros are layered 2-deep because that makes the C + * preprocessor expand the "type" argument. Otherwise, you get + * "PREDECL_type" instead of "PREDECL_LIST" + */ +#define _concat(a, b) a ## b +#define concat(a, b) _concat(a, b) +#define _str(x) #x +#define str(x) _str(x) + +#define _PREDECL(type, ...) PREDECL_##type(__VA_ARGS__) +#define PREDECL(type, ...) _PREDECL(type, __VA_ARGS__) +#define _DECLARE(type, ...) DECLARE_##type(__VA_ARGS__) +#define DECLARE(type, ...) _DECLARE(type, __VA_ARGS__) + +#define T_SORTED (1 << 0) +#define T_UNIQ (1 << 1) +#define T_HASH (1 << 2) +#define T_HEAP (1 << 3) +#define T_ATOMIC (1 << 4) +#define T_REVERSE (1 << 5) + +#define _T_LIST (0) +#define _T_DLIST (0 | T_REVERSE) +#define _T_ATOMLIST (0 | T_ATOMIC) +#define _T_HEAP (T_SORTED | T_HEAP) +#define _T_SORTLIST_UNIQ (T_SORTED | T_UNIQ) +#define _T_SORTLIST_NONUNIQ (T_SORTED) +#define _T_HASH (T_SORTED | T_UNIQ | T_HASH) +#define _T_SKIPLIST_UNIQ (T_SORTED | T_UNIQ) +#define _T_SKIPLIST_NONUNIQ (T_SORTED) +#define _T_RBTREE_UNIQ (T_SORTED | T_UNIQ | T_REVERSE) +#define _T_RBTREE_NONUNIQ (T_SORTED | T_REVERSE) +#define _T_ATOMSORT_UNIQ (T_SORTED | T_UNIQ | T_ATOMIC) +#define _T_ATOMSORT_NONUNIQ (T_SORTED | T_ATOMIC) + +#define _T_TYPE(type) _T_##type +#define IS_SORTED(type) (_T_TYPE(type) & T_SORTED) +#define IS_UNIQ(type) (_T_TYPE(type) & T_UNIQ) +#define IS_HASH(type) (_T_TYPE(type) & T_HASH) +#define IS_HEAP(type) (_T_TYPE(type) & T_HEAP) +#define IS_ATOMIC(type) (_T_TYPE(type) & T_ATOMIC) +#define IS_REVERSE(type) (_T_TYPE(type) & T_REVERSE) + +static struct timeval ref, ref0; + +static void ts_start(void) +{ + monotime(&ref0); + monotime(&ref); +} +static void ts_ref(const char *text) +{ + int64_t us; + us = monotime_since(&ref, NULL); + printfrr("%7"PRId64"us %s\n", us, text); + monotime(&ref); +} +static void ts_end(void) +{ + int64_t us; + us = monotime_since(&ref0, NULL); + printfrr("%7"PRId64"us total\n", us); +} + +#define TYPE LIST +#include "test_typelist.h" + +#define TYPE DLIST +#include "test_typelist.h" + +#define TYPE ATOMLIST +#include "test_typelist.h" + +#define TYPE HEAP +#include "test_typelist.h" + +#define TYPE SORTLIST_UNIQ +#include "test_typelist.h" + +#define TYPE SORTLIST_NONUNIQ +#include "test_typelist.h" + +#define TYPE HASH +#include "test_typelist.h" + +#define TYPE HASH_collisions +#define REALTYPE HASH +#define SHITTY_HASH +#include "test_typelist.h" +#undef SHITTY_HASH + +#define TYPE SKIPLIST_UNIQ +#include "test_typelist.h" + +#define TYPE SKIPLIST_NONUNIQ +#include "test_typelist.h" + +#define TYPE RBTREE_UNIQ +#include "test_typelist.h" + +#define TYPE RBTREE_NONUNIQ +#include "test_typelist.h" + +#define TYPE ATOMSORT_UNIQ +#include "test_typelist.h" + +#define TYPE ATOMSORT_NONUNIQ +#include "test_typelist.h" + +int main(int argc, char **argv) +{ + srandom(1); + + test_LIST(); + test_DLIST(); + test_ATOMLIST(); + test_HEAP(); + test_SORTLIST_UNIQ(); + test_SORTLIST_NONUNIQ(); + test_HASH(); + test_HASH_collisions(); + test_SKIPLIST_UNIQ(); + test_SKIPLIST_NONUNIQ(); + test_RBTREE_UNIQ(); + test_RBTREE_NONUNIQ(); + test_ATOMSORT_UNIQ(); + test_ATOMSORT_NONUNIQ(); + + log_memstats_stderr("test: "); + return 0; +} diff --git a/tests/lib/test_typelist.h b/tests/lib/test_typelist.h new file mode 100644 index 0000000..80c4005 --- /dev/null +++ b/tests/lib/test_typelist.h @@ -0,0 +1,822 @@ +// SPDX-License-Identifier: ISC +/* + * Copyright (c) 2019 David Lamparter, for NetDEF, Inc. + */ + +/* C++ called, they want their templates back */ +#define item concat(item_, TYPE) +#define itm concat(itm_, TYPE) +#define itmswap concat(itmswap_, TYPE) +#define head concat(head_, TYPE) +#define list concat(TYPE, ) +#define list_head concat(TYPE, _head) +#define list_item concat(TYPE, _item) +#define list_cmp concat(TYPE, _cmp) +#define list_hash concat(TYPE, _hash) +#define list_init concat(TYPE, _init) +#define list_fini concat(TYPE, _fini) +#define list_const_first concat(TYPE, _const_first) +#define list_first concat(TYPE, _first) +#define list_const_next concat(TYPE, _const_next) +#define list_next concat(TYPE, _next) +#define list_next_safe concat(TYPE, _next_safe) +#define list_const_last concat(TYPE, _const_last) +#define list_last concat(TYPE, _last) +#define list_const_prev concat(TYPE, _const_prev) +#define list_prev concat(TYPE, _prev) +#define list_prev_safe concat(TYPE, _prev_safe) +#define list_count concat(TYPE, _count) +#define list_add concat(TYPE, _add) +#define list_add_head concat(TYPE, _add_head) +#define list_add_tail concat(TYPE, _add_tail) +#define list_add_after concat(TYPE, _add_after) +#define list_find concat(TYPE, _find) +#define list_find_lt concat(TYPE, _find_lt) +#define list_find_gteq concat(TYPE, _find_gteq) +#define list_member concat(TYPE, _member) +#define list_anywhere concat(TYPE, _anywhere) +#define list_del concat(TYPE, _del) +#define list_pop concat(TYPE, _pop) +#define list_swap_all concat(TYPE, _swap_all) + +#define ts_hash_head concat(ts_hash_head_, TYPE) + +#ifndef REALTYPE +#define REALTYPE TYPE +#endif + +PREDECL(REALTYPE, list); +struct item { + uint64_t val; + struct list_item itm; + int scratchpad; +}; + +#if IS_SORTED(REALTYPE) +static int list_cmp(const struct item *a, const struct item *b); + +#if IS_HASH(REALTYPE) +static uint32_t list_hash(const struct item *a); +DECLARE(REALTYPE, list, struct item, itm, list_cmp, list_hash); + +static uint32_t list_hash(const struct item *a) +{ +#ifdef SHITTY_HASH + /* crappy hash to get some hash collisions */ + return (a->val & 0xFF) ^ (a->val << 29) ^ 0x55AA0000U; +#else + return jhash_1word(a->val, 0xdeadbeef); +#endif +} + +#else +DECLARE(REALTYPE, list, struct item, itm, list_cmp); +#endif + +static int list_cmp(const struct item *a, const struct item *b) +{ + if (a->val > b->val) + return 1; + if (a->val < b->val) + return -1; + return 0; +} + +#else /* !IS_SORTED */ +DECLARE(REALTYPE, list, struct item, itm); +#endif + +#define NITEM 10000 +#define NITEM_SWAP 100 /* other container for swap */ +struct item itm[NITEM], itmswap[NITEM_SWAP]; +static struct list_head head = concat(INIT_, REALTYPE)(head); + +static void ts_hash_head(struct list_head *h, const char *text, + const char *expect) +{ + int64_t us = monotime_since(&ref, NULL); + SHA256_CTX ctx; + struct item *item; + unsigned i = 0; + uint8_t hash[32]; + char hashtext[65]; + uint32_t swap_count, count; + + count = list_count(h); + swap_count = htonl(count); + + SHA256_Init(&ctx); + SHA256_Update(&ctx, &swap_count, sizeof(swap_count)); + + frr_each (list, h, item) { + struct { + uint32_t val_upper, val_lower, index; + } hashitem = { + htonl(item->val >> 32), + htonl(item->val & 0xFFFFFFFFULL), + htonl(i), + }; + SHA256_Update(&ctx, &hashitem, sizeof(hashitem)); + i++; + assert(i <= count); + } + SHA256_Final(hash, &ctx); + + for (i = 0; i < sizeof(hash); i++) + sprintf(hashtext + i * 2, "%02x", hash[i]); + + printfrr("%7"PRId64"us %-25s %s%s\n", us, text, + expect ? " " : "*", hashtext); + if (expect && strcmp(expect, hashtext)) { + printfrr("%-21s %s\n", "EXPECTED:", expect); + assert(0); + } + monotime(&ref); +} +/* hashes will have different item ordering */ +#if IS_HASH(REALTYPE) || IS_HEAP(REALTYPE) +#define ts_hash(pos, csum) ts_hash_head(&head, pos, NULL) +#define ts_hashx(pos, csum) ts_hash_head(&head, pos, NULL) +#define ts_hash_headx(head, pos, csum) ts_hash_head(head, pos, NULL) +#else +#define ts_hash(pos, csum) ts_hash_head(&head, pos, csum) +#define ts_hashx(pos, csum) ts_hash_head(&head, pos, csum) +#define ts_hash_headx(head, pos, csum) ts_hash_head(head, pos, csum) +#endif + +static void concat(test_, TYPE)(void) +{ + size_t i, j, k, l; + struct prng *prng; + struct prng *prng_swap __attribute__((unused)); + struct item *item, *prev __attribute__((unused)); + struct item dummy __attribute__((unused)); + + memset(itm, 0, sizeof(itm)); + for (i = 0; i < NITEM; i++) + itm[i].val = i; + + memset(itmswap, 0, sizeof(itmswap)); + for (i = 0; i < NITEM_SWAP; i++) + itmswap[i].val = i; + + printfrr("%s start\n", str(TYPE)); + ts_start(); + + list_init(&head); + assert(list_first(&head) == NULL); +#if IS_REVERSE(REALTYPE) + assert(list_last(&head) == NULL); +#endif + + ts_hash("init", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); + +#if !IS_ATOMIC(REALTYPE) + assert(!list_member(&head, &itm[0])); + assert(!list_member(&head, &itm[1])); +#endif + +#if IS_SORTED(REALTYPE) + prng = prng_new(0); + k = 0; + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 0) { + list_add(&head, &itm[j]); + itm[j].scratchpad = 1; + k++; + } +#if !IS_HEAP(REALTYPE) + else + assert(list_add(&head, &itm[j]) == &itm[j]); +#endif + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hashx("fill", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); + +#if !IS_ATOMIC(REALTYPE) + struct list_head other; + + list_init(&other); + list_swap_all(&head, &other); + + assert(list_count(&head) == 0); + assert(!list_first(&head)); + assert(list_count(&other) == k); + assert(list_first(&other) != NULL); +#if IS_REVERSE(REALTYPE) + assert(!list_last(&head)); + assert(list_last(&other) != NULL); +#endif + ts_hash_headx( + &other, "swap1", + "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); + + prng_swap = prng_new(0x1234dead); + l = 0; + for (i = 0; i < NITEM_SWAP; i++) { + j = prng_rand(prng_swap) % NITEM_SWAP; + if (itmswap[j].scratchpad == 0) { + list_add(&head, &itmswap[j]); + itmswap[j].scratchpad = 1; + l++; + } +#if !IS_HEAP(REALTYPE) + else { + struct item *rv = list_add(&head, &itmswap[j]); + assert(rv == &itmswap[j]); + } +#endif + } + assert(list_count(&head) == l); + assert(list_first(&head) != NULL); + ts_hash_headx( + &head, "swap-fill", + "26df437174051cf305d1bbb62d779ee450ca764167a1e7a94be1aece420008e6"); + + list_swap_all(&head, &other); + + assert(list_count(&other) == l); + assert(list_first(&other)); + ts_hash_headx( + &other, "swap2a", + "26df437174051cf305d1bbb62d779ee450ca764167a1e7a94be1aece420008e6"); + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash_headx( + &head, "swap2b", + "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); + + while (list_pop(&other)) + ; + list_fini(&other); + prng_free(prng_swap); + + ts_ref("swap-cleanup"); +#endif /* !IS_ATOMIC */ + + k = 0; + +#if IS_ATOMIC(REALTYPE) + struct list_head *chead = &head; + struct item *citem, *cprev = NULL; + + frr_each(list, chead, citem) { +#else + const struct list_head *chead = &head; + const struct item *citem, *cprev = NULL; + + frr_each(list_const, chead, citem) { +#endif + +#if IS_HASH(REALTYPE) || IS_HEAP(REALTYPE) + /* hash table doesn't give sorting */ + (void)cprev; +#else + assert(!cprev || cprev->val < citem->val); +#if IS_REVERSE(REALTYPE) + assert(list_const_prev(chead, citem) == cprev); +#endif +#endif + cprev = citem; + k++; + } + assert(list_count(chead) == k); +#if IS_REVERSE(REALTYPE) + assert(cprev == list_const_last(chead)); +#endif + ts_ref("walk"); + +#if IS_REVERSE(REALTYPE) && !IS_HASH(REALTYPE) && !IS_HEAP(REALTYPE) + cprev = NULL; + k = 0; + + frr_rev_each(list_const, chead, citem) { + assert(!cprev || cprev->val > citem->val); + assert(list_const_next(chead, citem) == cprev); + + cprev = citem; + k++; + } + assert(list_count(chead) == k); + assert(cprev == list_const_first(chead)); + + ts_ref("reverse-walk"); +#endif + +#if IS_UNIQ(REALTYPE) + prng_free(prng); + prng = prng_new(0); + + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + dummy.val = j; + assert(list_find(&head, &dummy) == &itm[j]); + } + ts_ref("find"); + + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + memset(&dummy, 0, sizeof(dummy)); + dummy.val = j; + if (itm[j].scratchpad) + assert(list_add(&head, &dummy) == &itm[j]); + else { + assert(list_add(&head, &dummy) == NULL); + assert(list_del(&head, &dummy) != NULL); + } + } + ts_hashx("add-dup", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); + +#elif IS_HEAP(REALTYPE) + /* heap - partially sorted. */ + prev = NULL; + l = k / 4; + for (i = 0; i < l; i++) { + item = list_pop(&head); + if (prev) + assert(prev->val < item->val); + item->scratchpad = 0; + k--; + prev = item; + } + ts_hash("pop#1", NULL); + + for (i = 0; i < NITEM; i++) + assertf(list_member(&head, &itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_member(&head, &itm[i])); + ts_hash("member", NULL); + + l = k / 2; + for (; i < l; i++) { + item = list_pop(&head); + if (prev) + assert(prev->val < item->val); + item->scratchpad = 0; + k--; + prev = item; + } + ts_hash("pop#2", NULL); + +#else /* !IS_UNIQ(REALTYPE) && !IS_HEAP(REALTYPE) */ + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + memset(&dummy, 0, sizeof(dummy)); + dummy.val = j; + + list_add(&head, &dummy); + if (itm[j].scratchpad) { + struct item *lt, *gteq, dummy2; + + assert(list_next(&head, &itm[j]) == &dummy || + list_next(&head, &dummy) == &itm[j]); + + memset(&dummy2, 0, sizeof(dummy)); + dummy2.val = j; + lt = list_find_lt(&head, &dummy2); + gteq = list_find_gteq(&head, &dummy2); + + assert(gteq == &itm[j] || gteq == &dummy); + if (lt) + assert(list_next(&head, lt) == &itm[j] || + list_next(&head, lt) == &dummy); + else + assert(list_first(&head) == &itm[j] || + list_first(&head) == &dummy); + } else if (list_next(&head, &dummy)) + assert(list_next(&head, &dummy)->val > j); + assert(list_del(&head, &dummy) != NULL); + } + ts_hash("add-dup+find_{lt,gteq}", "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); +#endif +#if !IS_HASH(REALTYPE) && !IS_HEAP(REALTYPE) + prng_free(prng); + prng = prng_new(123456); + + l = 0; + for (i = 0; i < NITEM; i++) { + struct item *lt, *gteq, *tmp; + + j = prng_rand(prng) % NITEM; + dummy.val = j; + + lt = list_find_lt(&head, &dummy); + gteq = list_find_gteq(&head, &dummy); + + if (lt) { + assert(lt->val < j); + tmp = list_next(&head, lt); + assert(tmp == gteq); + assert(!tmp || tmp->val >= j); + } else + assert(gteq == list_first(&head)); + + if (gteq) + assert(gteq->val >= j); + } + ts_ref("find_{lt,gteq}"); +#endif /* !IS_HASH */ + + prng_free(prng); + prng = prng_new(0); + + l = 0; + for (i = 0; i < NITEM; i++) { + (void)prng_rand(prng); + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 1) { + assert(list_del(&head, &itm[j]) != NULL); + itm[j].scratchpad = 0; + l++; + } + } + assert(l + list_count(&head) == k); + ts_hashx("del", "cb2e5d80f08a803ef7b56c15e981b681adcea214bebc2f55e12e0bfb242b07ca"); + +#if !IS_ATOMIC(REALTYPE) + for (i = 0; i < NITEM; i++) + assertf(list_member(&head, &itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_member(&head, &itm[i])); + ts_hashx("member", "cb2e5d80f08a803ef7b56c15e981b681adcea214bebc2f55e12e0bfb242b07ca"); +#endif + + frr_each_safe(list, &head, item) { + assert(item->scratchpad != 0); + + if (item->val & 1) { + assert(list_del(&head, item) != NULL); + item->scratchpad = 0; + l++; + } + } + assert(l + list_count(&head) == k); + ts_hashx("frr_each_safe+del", "e0beb71dd963a75af05b722b8e71b61b304587d860c8accdc4349067542b86bb"); + +#else /* !IS_SORTED */ + prng = prng_new(0); + k = 0; + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 0) { + list_add_tail(&head, &itm[j]); + itm[j].scratchpad = 1; + k++; + } + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); +#if IS_REVERSE(REALTYPE) + assert(list_last(&head) != NULL); +#endif + ts_hash("fill / add_tail", "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); + +#if !IS_ATOMIC(REALTYPE) + struct list_head other; + + list_init(&other); + list_swap_all(&head, &other); + + assert(list_count(&head) == 0); + assert(!list_first(&head)); + assert(list_count(&other) == k); + assert(list_first(&other) != NULL); +#if IS_REVERSE(REALTYPE) + assert(!list_last(&head)); + assert(list_last(&other) != NULL); +#endif + ts_hash_head( + &other, "swap1", + "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); + + prng_swap = prng_new(0x1234dead); + l = 0; + for (i = 0; i < NITEM_SWAP; i++) { + j = prng_rand(prng_swap) % NITEM_SWAP; + if (itmswap[j].scratchpad == 0) { + list_add_tail(&head, &itmswap[j]); + itmswap[j].scratchpad = 1; + l++; + } + } + assert(list_count(&head) == l); + assert(list_first(&head) != NULL); + ts_hash_head( + &head, "swap-fill", + "833e6ae437e322dfbd36eda8cfc33a61109be735b43f15d256c05e52d1b01909"); + + list_swap_all(&head, &other); + + assert(list_count(&other) == l); + assert(list_first(&other)); + ts_hash_head( + &other, "swap2a", + "833e6ae437e322dfbd36eda8cfc33a61109be735b43f15d256c05e52d1b01909"); + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash_head( + &head, "swap2b", + "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); + + while (list_pop(&other)) + ; + list_fini(&other); + prng_free(prng_swap); + + ts_ref("swap-cleanup"); +#endif + + for (i = 0; i < NITEM / 2; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 1) { + assert(list_del(&head, &itm[j]) != NULL); + itm[j].scratchpad = 0; + k--; + } + } + ts_hash("del-prng", "86d568a95eb429dab3162976c5a5f3f75aabc835932cd682aa280b6923549564"); + +#if !IS_ATOMIC(REALTYPE) + for (i = 0; i < NITEM; i++) { + assertf(list_member(&head, &itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_member(&head, &itm[i])); + assertf(list_anywhere(&itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_anywhere(&itm[i])); + } + ts_hash("member", "86d568a95eb429dab3162976c5a5f3f75aabc835932cd682aa280b6923549564"); +#endif + + l = 0; + while (l < (k / 4) && (prev = list_pop(&head))) { + assert(prev->scratchpad != 0); + + prev->scratchpad = 0; + l++; + } + ts_hash("pop#1", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d"); + +#if !IS_ATOMIC(REALTYPE) + for (i = 0; i < NITEM; i++) { + assertf(list_member(&head, &itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_member(&head, &itm[i])); + assertf(list_anywhere(&itm[i]) == itm[i].scratchpad, + "%zu should:%d is:%d", i, itm[i].scratchpad, + list_anywhere(&itm[i])); + } + ts_hash("member", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d"); +#endif +#if IS_REVERSE(REALTYPE) + i = 0; + prev = NULL; + + frr_rev_each (list, &head, item) { + assert(item->scratchpad != 0); + assert(list_next(&head, item) == prev); + + i++; + prev = item; + } + assert(list_first(&head) == prev); + assert(list_count(&head) == i); + ts_hash("reverse-walk", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d"); +#endif + + while ((item = list_pop(&head))) { + assert(item->scratchpad != 0); + + item->scratchpad = 0; + l++; + } + assert(l == k); + assert(list_count(&head) == 0); + assert(list_first(&head) == NULL); + ts_hash("pop#2", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); + + prng_free(prng); + prng = prng_new(0x1e5a2d69); + + k = 0; + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 0) { + list_add_head(&head, &itm[j]); + itm[j].scratchpad = 1; + k++; + } + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash("fill / add_head", "3084d8f8a28b8c756ccc0a92d60d86f6d776273734ddc3f9e1d89526f5ca2795"); + + for (i = 0; i < NITEM / 2; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 1) { + assert(list_del(&head, &itm[j]) != NULL); + itm[j].scratchpad = 0; + k--; + } + } + ts_hash("del-prng", "dc916fa7ea4418792c7c8232d74df2887f9975ead4222f4b977be6bc0b52285e"); + + l = 0; + while ((item = list_pop(&head))) { + assert(item->scratchpad != 0); + + item->scratchpad = 0; + l++; + } + assert(l == k); + assert(list_count(&head) == 0); + assert(list_first(&head) == NULL); + ts_hash("pop", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); + + prng_free(prng); + prng = prng_new(0x692d1e5a); + + k = 0; + for (i = 0; i < NITEM; i++) { + j = prng_rand(prng) % NITEM; + if (itm[j].scratchpad == 0) { + if (prng_rand(prng) & 1) { + list_add_tail(&head, &itm[j]); + } else { + list_add_head(&head, &itm[j]); + } + itm[j].scratchpad = 1; + k++; + } + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash("fill / add_{head,tail}", "93fa180a575c96e4b6c3775c2de7843ee3254dd6ed5af699bbe155f994114b06"); + + for (i = 0; i < NITEM * 3; i++) { + int op = prng_rand(prng); + j = prng_rand(prng) % NITEM; + + if (op & 1) { + /* delete or pop */ + if (op & 2) { + item = list_pop(&head); + if (!item) + continue; + } else { + item = &itm[j]; + if (item->scratchpad == 0) + continue; + assert(list_del(&head, item) != NULL); + } + item->scratchpad = 0; + k--; + } else { + item = &itm[j]; + if (item->scratchpad != 0) + continue; + + item->scratchpad = 1; + k++; + + switch ((op >> 1) & 1) { + case 0: + list_add_head(&head, item); + break; + case 1: + list_add_tail(&head, item); + break; + default: + assert(0); + } + } + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash("prng add/del", "4909f31d06bb006efca4dfeebddb8de071733ddf502f89b6d532155208bbc6df"); + +#if !IS_ATOMIC(REALTYPE) + /* variant with add_after */ + + for (i = 0; i < NITEM * 3; i++) { + int op = prng_rand(prng); + j = prng_rand(prng) % NITEM; + + if (op & 1) { + /* delete or pop */ + if (op & 2) { + item = list_pop(&head); + if (!item) + continue; + } else { + item = &itm[j]; + if (item->scratchpad == 0) + continue; + assert(list_del(&head, item) != NULL); + } + item->scratchpad = 0; + k--; + } else { + item = &itm[j]; + if (item->scratchpad != 0) + continue; + + item->scratchpad = 1; + k++; + + switch ((op >> 1) & 3) { + case 0: + list_add_head(&head, item); + break; + case 1: + list_add_tail(&head, item); + break; + case 2: + case 3: + prev = NULL; + l = 0; + do { + j = prng_rand(prng) % NITEM; + prev = &itm[j]; + if (prev->scratchpad == 0 + || prev == item) + prev = NULL; + l++; + } while (!prev && l < 10); + list_add_after(&head, prev, item); + break; + default: + assert(0); + } + } + } + assert(list_count(&head) == k); + assert(list_first(&head) != NULL); + ts_hash("prng add/after/del", "84c5fc83294eabebb9808ccbba32a303c4fca084db87ed1277d2bae1f8c5bee4"); +#endif + + l = 0; +#endif + + while ((item = list_pop(&head))) { + assert(item->scratchpad != 0); + + item->scratchpad = 0; + l++; + } + assert(l == k); + assert(list_count(&head) == 0); + assert(list_first(&head) == NULL); + ts_hash("pop", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); + + list_fini(&head); + ts_ref("fini"); + ts_end(); + prng_free(prng); + printfrr("%s end\n", str(TYPE)); +} + +#undef ts_hash +#undef ts_hashx +#undef ts_hash_head +#undef ts_hash_headx + +#undef item +#undef itm +#undef itmswap +#undef head +#undef list +#undef list_head +#undef list_item +#undef list_cmp +#undef list_hash +#undef list_init +#undef list_fini +#undef list_first +#undef list_next +#undef list_next_safe +#undef list_const_first +#undef list_const_next +#undef list_last +#undef list_prev +#undef list_prev_safe +#undef list_const_last +#undef list_const_prev +#undef list_count +#undef list_add +#undef list_add_head +#undef list_add_tail +#undef list_add_after +#undef list_find +#undef list_find_lt +#undef list_find_gteq +#undef list_member +#undef list_anywhere +#undef list_del +#undef list_pop +#undef list_swap_all + +#undef REALTYPE +#undef TYPE diff --git a/tests/lib/test_typelist.py b/tests/lib/test_typelist.py new file mode 100644 index 0000000..fe3499c --- /dev/null +++ b/tests/lib/test_typelist.py @@ -0,0 +1,21 @@ +import frrtest + + +class TestTypelist(frrtest.TestMultiOut): + program = "./test_typelist" + + +TestTypelist.onesimple("LIST end") +TestTypelist.onesimple("DLIST end") +TestTypelist.onesimple("ATOMLIST end") +TestTypelist.onesimple("HEAP end") +TestTypelist.onesimple("SORTLIST_UNIQ end") +TestTypelist.onesimple("SORTLIST_NONUNIQ end") +TestTypelist.onesimple("HASH end") +TestTypelist.onesimple("HASH_collisions end") +TestTypelist.onesimple("SKIPLIST_UNIQ end") +TestTypelist.onesimple("SKIPLIST_NONUNIQ end") +TestTypelist.onesimple("RBTREE_UNIQ end") +TestTypelist.onesimple("RBTREE_NONUNIQ end") +TestTypelist.onesimple("ATOMSORT_UNIQ end") +TestTypelist.onesimple("ATOMSORT_NONUNIQ end") diff --git a/tests/lib/test_versioncmp.c b/tests/lib/test_versioncmp.c new file mode 100644 index 0000000..84ae06e --- /dev/null +++ b/tests/lib/test_versioncmp.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * frr_version_cmp() tests + * Copyright (C) 2018 David Lamparter for NetDEF, Inc. + */ +#include +#include + +static const char *rel(int x) +{ + if (x < 0) + return "<"; + if (x > 0) + return ">"; + return "=="; +} + +static int fail; + +static void compare(const char *a, const char *b, int expect) +{ + int result = frr_version_cmp(a, b); + + if (expect == result) + printf("\"%s\" %s \"%s\"\n", a, rel(result), b); + else { + printf("\"%s\" %s \"%s\", expected %s!\n", a, rel(result), b, + rel(expect)); + fail = 1; + } +} + +int main(int argc, char **argv) +{ + compare("", "", 0); + compare("1", "1", 0); + compare("1.0", "1.00", 0); + compare("10.0", "1", 1); + compare("10.0", "2", 1); + compare("2.1", "10.0", -1); + compare("1.1.1", "1.1.0", 1); + compare("1.0a", "1.0", 1); + compare("1.0a", "1.0b", -1); + compare("1.0a10", "1.0a2", 1); + compare("1.00a2", "1.0a2", 0); + compare("1.00a2", "1.0a3", -1); + compare("1.0-dev", "1.0", 1); + compare("1.0~foo", "1.0", -1); + compare("1.0~1", "1.0~0", 1); + compare("1.00~1", "1.0~0", 1); + printf("final tally: %s\n", fail ? "FAILED" : "ok"); + return fail; +} diff --git a/tests/lib/test_versioncmp.py b/tests/lib/test_versioncmp.py new file mode 100644 index 0000000..8ded53b --- /dev/null +++ b/tests/lib/test_versioncmp.py @@ -0,0 +1,8 @@ +import frrtest + + +class TestVersionCmp(frrtest.TestMultiOut): + program = "./test_versioncmp" + + +TestVersionCmp.exit_cleanly() diff --git a/tests/lib/test_xref.c b/tests/lib/test_xref.c new file mode 100644 index 0000000..fca0d47 --- /dev/null +++ b/tests/lib/test_xref.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * xref tests + * Copyright (C) 2020 David Lamparter for NetDEF, Inc. + */ + +#include +#include "xref.h" +#include "log.h" + +/* + * "lib/test_xref.c" (only 1 directory component included) + * "logging call" + * 0x00000003 (network byte order - LOG_ERR) + * 0x00000000 (network byte order - EC / zero here) + * + * note there are no '\0' terminators included for the strings + * + * SHA256 + * => 71a65ce6e81517f642c8f55fb2af6f181f7df54357913b5b577aa61a663fdd4c + * & 0f -> 0x01 'H' + * & f001 -> 0x07 '7' + * & 3e -> 0x13 'K' + * & c007 -> 0x12 'J' + * & f8 -> 0x0b 'B' + * etc. + * (for reference: base32ch[] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ") + * + * (bits are consumed starting with the lowest bit, and the first character + * only consumes 4 bits and has the 5th bit at 1) + */ + +static const char *expect_uid = "H7KJB-67TBH"; +static bool test_logcall(void) +{ + zlog_err("logging call"); + + return true; +} + +static void check_xref(const struct xref *xref, bool *found, bool *error) +{ + const char *file = xref->file, *p; + + p = strrchr(file, '/'); + if (p) + file = p + 1; + + if (strcmp(file, "test_xref.c")) + return; + if (xref->type != XREFT_LOGMSG) + return; + if (strcmp(xref->func, "test_logcall")) + return; + + printf("xref: %s:%d %s() type=%d uid=%s\n", + xref->file, xref->line, xref->func, xref->type, + xref->xrefdata ? xref->xrefdata->uid : "--"); + + if (*found) { + printf("duplicate xref!\n"); + *error = true; + } + + const struct xref_logmsg *logmsg; + + logmsg = container_of(xref, struct xref_logmsg, xref); + if (strcmp(logmsg->fmtstring, "logging call")) { + printf("log message mismatch!\n"); + *error = true; + } + if (logmsg->priority != LOG_ERR || logmsg->ec != 0) { + printf("metadata mismatch!\n"); + *error = true; + } + + *found = true; + + if (!xref->xrefdata) { + printf("no unique ID?\n"); + *error = true; + return; + } + + if (strcmp(xref->xrefdata->uid, expect_uid)) { + printf("unique ID mismatch, expected %s, got %s\n", + expect_uid, xref->xrefdata->uid); + *error = true; + } +} + +static bool test_lookup(void) +{ + struct xref_block *xb; + bool found = false, error = false; + + for (xb = xref_blocks; xb; xb = xb->next) { + const struct xref * const *xrefp; + + for (xrefp = xb->start; xrefp < xb->stop; xrefp++) { + const struct xref *xref = *xrefp; + + if (!xref) + continue; + + check_xref(xref, &found, &error); + } + } + return found && !error; +} + +bool (*tests[])(void) = { + test_lookup, + test_logcall, +}; + +XREF_SETUP(); + +int main(int argc, char **argv) +{ + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + for (unsigned int i = 0; i < array_size(tests); i++) + if (!tests[i]()) + return 1; + return 0; +} diff --git a/tests/lib/test_xref.py b/tests/lib/test_xref.py new file mode 100644 index 0000000..8c3db3e --- /dev/null +++ b/tests/lib/test_xref.py @@ -0,0 +1,6 @@ +import frrtest + +class TestXref(frrtest.TestMultiOut): + program = './test_xref' + +TestXref.exit_cleanly() diff --git a/tests/lib/test_zlog.c b/tests/lib/test_zlog.c new file mode 100644 index 0000000..95d9056 --- /dev/null +++ b/tests/lib/test_zlog.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Zlog tests. + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + */ +#include +#include +#include "log.h" +#include "network.h" + +/* maximum amount of data to hexdump */ +#define MAXDATA 16384 + +/* + * Test hexdump functionality. + * + * At the moment, not crashing is considered success. + */ +static bool test_zlog_hexdump(void) +{ + unsigned int nl = 1; + + do { + uint8_t d[nl]; + + for (unsigned int i = 0; i < nl; i++) + d[i] = frr_weak_random(); + zlog_hexdump(d, nl - 1); + + nl += 1 + (nl / 2); + } while (nl <= MAXDATA); + + return true; +} + +bool (*tests[])(void) = { + test_zlog_hexdump, +}; + +int main(int argc, char **argv) +{ + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + for (unsigned int i = 0; i < array_size(tests); i++) + if (!tests[i]()) + return 1; + return 0; +} diff --git a/tests/lib/test_zlog.py b/tests/lib/test_zlog.py new file mode 100644 index 0000000..2a2d54e --- /dev/null +++ b/tests/lib/test_zlog.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestZlog(frrtest.TestMultiOut): + program = "./test_zlog" diff --git a/tests/lib/test_zmq.c b/tests/lib/test_zmq.c new file mode 100644 index 0000000..2cd9d47 --- /dev/null +++ b/tests/lib/test_zmq.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ZeroMQ event test + * Copyright (C) 2017 David Lamparter, for NetDEF, Inc. + */ + +#include +#include "memory.h" +#include "sigevent.h" +#include "frr_zmq.h" + +DEFINE_MTYPE_STATIC(LIB, TESTBUF, "zmq test buffer"); +DEFINE_MTYPE_STATIC(LIB, ZMQMSG, "zmq message"); + +static struct event_loop *master; + +static void msg_buf_free(void *data, void *hint) +{ + XFREE(MTYPE_TESTBUF, data); +} + +static int recv_delim(void *zmqsock) +{ + /* receive delim */ + zmq_msg_t zdelim; + int more; + zmq_msg_init(&zdelim); + zmq_msg_recv(&zdelim, zmqsock, 0); + more = zmq_msg_more(&zdelim); + zmq_msg_close(&zdelim); + return more; +} +static void send_delim(void *zmqsock) +{ + /* Send delim */ + zmq_msg_t zdelim; + zmq_msg_init(&zdelim); + zmq_msg_send(&zdelim, zmqsock, ZMQ_SNDMORE); + zmq_msg_close(&zdelim); +} +static void run_client(int syncfd) +{ + int i, j; + char buf[32]; + char dummy; + void *zmqctx = NULL; + void *zmqsock; + int more; + + read(syncfd, &dummy, 1); + + zmqctx = zmq_ctx_new(); + zmq_ctx_set(zmqctx, ZMQ_IPV6, 1); + + zmqsock = zmq_socket(zmqctx, ZMQ_DEALER); + if (zmq_connect(zmqsock, "tcp://127.0.0.1:17171")) { + perror("zmq_connect"); + exit(1); + } + + /* single-part */ + for (i = 0; i < 8; i++) { + snprintf(buf, sizeof(buf), "msg #%d %c%c%c", i, 'a' + i, + 'b' + i, 'c' + i); + printf("client send: %s\n", buf); + fflush(stdout); + send_delim(zmqsock); + zmq_send(zmqsock, buf, strlen(buf) + 1, 0); + more = recv_delim(zmqsock); + while (more) { + zmq_recv(zmqsock, buf, sizeof(buf), 0); + printf("client recv: %s\n", buf); + size_t len = sizeof(more); + if (zmq_getsockopt(zmqsock, ZMQ_RCVMORE, &more, &len)) + break; + } + } + + /* multipart */ + for (i = 2; i < 5; i++) { + printf("---\n"); + send_delim(zmqsock); + zmq_msg_t part; + for (j = 1; j <= i; j++) { + char *dyn = XMALLOC(MTYPE_TESTBUF, 32); + + snprintf(dyn, 32, "part %d/%d", j, i); + printf("client send: %s\n", dyn); + fflush(stdout); + + zmq_msg_init_data(&part, dyn, strlen(dyn) + 1, + msg_buf_free, NULL); + zmq_msg_send(&part, zmqsock, j < i ? ZMQ_SNDMORE : 0); + } + + recv_delim(zmqsock); + do { + char *data; + + zmq_msg_recv(&part, zmqsock, 0); + data = zmq_msg_data(&part); + more = zmq_msg_more(&part); + printf("client recv (more: %d): %s\n", more, data); + } while (more); + zmq_msg_close(&part); + } + + /* write callback */ + printf("---\n"); + snprintf(buf, sizeof(buf), "Done receiving"); + printf("client send: %s\n", buf); + fflush(stdout); + send_delim(zmqsock); + zmq_send(zmqsock, buf, strlen(buf) + 1, 0); + /* wait for message from server */ + more = recv_delim(zmqsock); + while (more) { + zmq_recv(zmqsock, buf, sizeof(buf), 0); + printf("client recv: %s\n", buf); + size_t len = sizeof(more); + if (zmq_getsockopt(zmqsock, ZMQ_RCVMORE, &more, &len)) + break; + } + + zmq_close(zmqsock); + zmq_ctx_term(zmqctx); +} + +static struct frrzmq_cb *cb; + +static void recv_id_and_delim(void *zmqsock, zmq_msg_t *msg_id) +{ + /* receive id */ + zmq_msg_init(msg_id); + zmq_msg_recv(msg_id, zmqsock, 0); + /* receive delim */ + recv_delim(zmqsock); +} +static void send_id_and_delim(void *zmqsock, zmq_msg_t *msg_id) +{ + /* Send Id */ + zmq_msg_send(msg_id, zmqsock, ZMQ_SNDMORE); + send_delim(zmqsock); +} +static void serverwritefn(void *arg, void *zmqsock) +{ + zmq_msg_t *msg_id = (zmq_msg_t *)arg; + char buf[32] = "Test write callback"; + size_t i; + + for (i = 0; i < strlen(buf); i++) + buf[i] = toupper(buf[i]); + printf("server send: %s\n", buf); + fflush(stdout); + send_id_and_delim(zmqsock, msg_id); + zmq_send(zmqsock, buf, strlen(buf) + 1, 0); + + /* send just once */ + frrzmq_thread_cancel(&cb, &cb->write); + + zmq_msg_close(msg_id); + XFREE(MTYPE_ZMQMSG, msg_id); +} +static void serverpartfn(void *arg, void *zmqsock, zmq_msg_t *msg, + unsigned partnum) +{ + static int num = 0; + int more = zmq_msg_more(msg); + char *in = zmq_msg_data(msg); + size_t i; + zmq_msg_t reply; + char *out; + + /* Id */ + if (partnum == 0) { + send_id_and_delim(zmqsock, msg); + return; + } + /* Delim */ + if (partnum == 1) + return; + + + printf("server recv part %u (more: %d): %s\n", partnum, more, in); + fflush(stdout); + + out = XMALLOC(MTYPE_TESTBUF, strlen(in) + 1); + for (i = 0; i < strlen(in); i++) + out[i] = toupper(in[i]); + out[i] = '\0'; + zmq_msg_init_data(&reply, out, strlen(out) + 1, msg_buf_free, NULL); + zmq_msg_send(&reply, zmqsock, ZMQ_SNDMORE); + + if (more) + return; + + out = XMALLOC(MTYPE_TESTBUF, 32); + snprintf(out, 32, "msg# was %u", partnum); + zmq_msg_init_data(&reply, out, strlen(out) + 1, msg_buf_free, NULL); + zmq_msg_send(&reply, zmqsock, 0); + + zmq_msg_close(&reply); + + if (++num < 7) + return; + + /* write callback test */ + char buf[32]; + zmq_msg_t *msg_id = XMALLOC(MTYPE_ZMQMSG, sizeof(zmq_msg_t)); + recv_id_and_delim(zmqsock, msg_id); + zmq_recv(zmqsock, buf, sizeof(buf), 0); + printf("server recv: %s\n", buf); + fflush(stdout); + + frrzmq_event_add_write_msg(master, serverwritefn, NULL, msg_id, zmqsock, + &cb); +} + +static void serverfn(void *arg, void *zmqsock) +{ + static int num = 0; + + zmq_msg_t msg_id; + char buf[32]; + size_t i; + + recv_id_and_delim(zmqsock, &msg_id); + zmq_recv(zmqsock, buf, sizeof(buf), 0); + + printf("server recv: %s\n", buf); + fflush(stdout); + for (i = 0; i < strlen(buf); i++) + buf[i] = toupper(buf[i]); + send_id_and_delim(zmqsock, &msg_id); + zmq_msg_close(&msg_id); + zmq_send(zmqsock, buf, strlen(buf) + 1, 0); + + if (++num < 4) + return; + + /* change to multipart callback */ + frrzmq_thread_cancel(&cb, &cb->read); + frrzmq_thread_cancel(&cb, &cb->write); + + frrzmq_event_add_read_part(master, serverpartfn, NULL, NULL, zmqsock, + &cb); +} + +static void sigchld(void) +{ + printf("child exited.\n"); + frrzmq_thread_cancel(&cb, &cb->read); + frrzmq_thread_cancel(&cb, &cb->write); +} + +static struct frr_signal_t sigs[] = { + { + .signal = SIGCHLD, + .handler = sigchld, + }, +}; + +static void run_server(int syncfd) +{ + void *zmqsock; + char dummy = 0; + struct event t; + + master = event_master_create(NULL); + signal_init(master, array_size(sigs), sigs); + frrzmq_init(); + + zmqsock = zmq_socket(frrzmq_context, ZMQ_ROUTER); + if (zmq_bind(zmqsock, "tcp://*:17171")) { + perror("zmq_bind"); + exit(1); + } + + frrzmq_event_add_read_msg(master, serverfn, NULL, NULL, zmqsock, &cb); + + write(syncfd, &dummy, sizeof(dummy)); + while (event_fetch(master, &t)) + event_call(&t); + + zmq_close(zmqsock); + frrzmq_finish(); + event_master_free(master); + log_memstats_stderr("test"); +} + +int main(void) +{ + int syncpipe[2]; + pid_t child; + + if (pipe(syncpipe)) { + perror("pipe"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + exit(1); + } else if (child == 0) { + run_client(syncpipe[0]); + exit(0); + } + + run_server(syncpipe[1]); + exit(0); +} diff --git a/tests/lib/test_zmq.py b/tests/lib/test_zmq.py new file mode 100644 index 0000000..5f6189d --- /dev/null +++ b/tests/lib/test_zmq.py @@ -0,0 +1,14 @@ +import frrtest +import pytest +import os + + +class TestZMQ(frrtest.TestRefOut): + program = "./test_zmq" + + @pytest.mark.skipif( + 'S["ZEROMQ_TRUE"]=""\n' not in open("../config.status").readlines(), + reason="ZEROMQ not enabled", + ) + def test_refout(self): + return super(TestZMQ, self).test_refout() diff --git a/tests/lib/test_zmq.refout b/tests/lib/test_zmq.refout new file mode 100644 index 0000000..acac505 --- /dev/null +++ b/tests/lib/test_zmq.refout @@ -0,0 +1,67 @@ +client send: msg #0 abc +server recv: msg #0 abc +client recv: MSG #0 ABC +client send: msg #1 bcd +server recv: msg #1 bcd +client recv: MSG #1 BCD +client send: msg #2 cde +server recv: msg #2 cde +client recv: MSG #2 CDE +client send: msg #3 def +server recv: msg #3 def +client recv: MSG #3 DEF +client send: msg #4 efg +server recv part 2 (more: 0): msg #4 efg +client recv: MSG #4 EFG +client recv: msg# was 2 +client send: msg #5 fgh +server recv part 2 (more: 0): msg #5 fgh +client recv: MSG #5 FGH +client recv: msg# was 2 +client send: msg #6 ghi +server recv part 2 (more: 0): msg #6 ghi +client recv: MSG #6 GHI +client recv: msg# was 2 +client send: msg #7 hij +server recv part 2 (more: 0): msg #7 hij +client recv: MSG #7 HIJ +client recv: msg# was 2 +--- +client send: part 1/2 +client send: part 2/2 +server recv part 2 (more: 1): part 1/2 +server recv part 3 (more: 0): part 2/2 +client recv (more: 1): PART 1/2 +client recv (more: 1): PART 2/2 +client recv (more: 0): msg# was 3 +--- +client send: part 1/3 +client send: part 2/3 +client send: part 3/3 +server recv part 2 (more: 1): part 1/3 +server recv part 3 (more: 1): part 2/3 +server recv part 4 (more: 0): part 3/3 +client recv (more: 1): PART 1/3 +client recv (more: 1): PART 2/3 +client recv (more: 1): PART 3/3 +client recv (more: 0): msg# was 4 +--- +client send: part 1/4 +client send: part 2/4 +client send: part 3/4 +client send: part 4/4 +server recv part 2 (more: 1): part 1/4 +server recv part 3 (more: 1): part 2/4 +server recv part 4 (more: 1): part 3/4 +server recv part 5 (more: 0): part 4/4 +client recv (more: 1): PART 1/4 +client recv (more: 1): PART 2/4 +client recv (more: 1): PART 3/4 +client recv (more: 1): PART 4/4 +client recv (more: 0): msg# was 5 +--- +client send: Done receiving +server recv: Done receiving +server send: TEST WRITE CALLBACK +client recv: TEST WRITE CALLBACK +child exited. diff --git a/tests/ospf6d/subdir.am b/tests/ospf6d/subdir.am new file mode 100644 index 0000000..ef1f40c --- /dev/null +++ b/tests/ospf6d/subdir.am @@ -0,0 +1,19 @@ +if !OSPF6D +PYTEST_IGNORE += --ignore=ospf6d/ +endif +OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD) + + +if OSPF6D +check_PROGRAMS += tests/ospf6d/test_lsdb +endif +tests_ospf6d_test_lsdb_CFLAGS = $(TESTS_CFLAGS) +tests_ospf6d_test_lsdb_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_ospf6d_test_lsdb_LDADD = $(OSPF6_TEST_LDADD) +tests_ospf6d_test_lsdb_SOURCES = tests/ospf6d/test_lsdb.c tests/lib/cli/common_cli.c +clippy_scan += tests/ospf6d/test_lsdb.c +EXTRA_DIST += \ + tests/ospf6d/test_lsdb.py \ + tests/ospf6d/test_lsdb.in \ + tests/ospf6d/test_lsdb.refout \ + # end diff --git a/tests/ospf6d/test_lsdb.c b/tests/ospf6d/test_lsdb.c new file mode 100644 index 0000000..f9df735 --- /dev/null +++ b/tests/ospf6d/test_lsdb.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * CLI/command dummy handling tester + * + * Copyright (C) 2015 by David Lamparter, + * for Open Source Routing / NetDEF, Inc. + */ + +#include + +#include "prefix.h" +#include "vector.h" +#include "vty.h" + +#include "ospf6d/ospf6_lsa.h" +#include "ospf6d/ospf6_lsdb.h" + +#include "tests/lib/cli/common_cli.h" +#include "tests/ospf6d/test_lsdb_clippy.c" + +static struct ospf6_lsdb *lsdb; + +static struct ospf6_lsa **lsas = NULL; +static size_t lsa_count = 0; + +static void lsa_check_resize(size_t len) +{ + struct ospf6_lsa **templsas; + + if (lsa_count >= len) + return; + templsas = realloc(lsas, len * sizeof(lsas[0])); + if (templsas) + lsas = templsas; + else + return; + memset(lsas + lsa_count, 0, sizeof(lsas[0]) * (len - lsa_count)); + + lsa_count = len; +} + +DEFPY(lsa_set, lsa_set_cmd, + "lsa set (0-999999)$idx {type (0-65535)|id A.B.C.D|adv A.B.C.D}", + "LSA\n" + "set\n" + "LSA index in array\n" + "OSPF6 type code\n" + "OSPF6 type code\n" + "LS-ID\n" + "LS-ID\n" + "Advertising router\n" + "Advertising router\n") +{ + struct ospf6_lsa_header hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.type = htons(type); + hdr.id = id.s_addr; + hdr.adv_router = adv.s_addr; + + lsa_check_resize(idx + 1); + if (lsas[idx]) + ospf6_lsa_unlock(&lsas[idx]); + lsas[idx] = ospf6_lsa_create_headeronly(&hdr); + ospf6_lsa_lock(lsas[idx]); + return CMD_SUCCESS; +} + +DEFPY(lsa_drop, lsa_drop_cmd, + "lsa drop (0-999999)$idx", + "LSA\n" + "drop reference\n" + "LSA index in array\n") +{ + if ((size_t)idx >= lsa_count) + return CMD_SUCCESS; + if (lsas[idx]->lock != 1) + vty_out(vty, "refcount at %u\n", lsas[idx]->lock); + ospf6_lsa_unlock(&lsas[idx]); + lsas[idx] = NULL; + return CMD_SUCCESS; +} + + +DEFPY(lsdb_add, lsdb_add_cmd, + "lsdb add (0-999999)$idx", + "LSDB\n" + "insert LSA into LSDB\n" + "LSA index in array\n") +{ + ospf6_lsdb_add(lsas[idx], lsdb); + return CMD_SUCCESS; +} + +DEFPY(lsdb_remove, lsdb_remove_cmd, + "lsdb remove (0-999999)$idx", + "LSDB\n" + "remove LSA from LSDB\n" + "LSA index in array\n") +{ + ospf6_lsdb_remove(lsas[idx], lsdb); + return CMD_SUCCESS; +} + +static void lsa_show_oneline(struct vty *vty, struct ospf6_lsa *lsa) +{ + char adv_router[64], id[64]; + + if (!lsa) { + vty_out(vty, "lsa = NULL\n"); + return; + } + inet_ntop(AF_INET, &lsa->header->id, id, sizeof(id)); + inet_ntop(AF_INET, &lsa->header->adv_router, adv_router, + sizeof(adv_router)); + vty_out(vty, "type %u adv %s id %s\n", ntohs(lsa->header->type), + adv_router, id); +} + +DEFPY(lsdb_walk, lsdb_walk_cmd, + "lsdb walk", + "LSDB\n" + "walk entries\n") +{ + struct ospf6_lsa *lsa, *lsanext; + + unsigned cnt = 0; + for (ALL_LSDB(lsdb, lsa, lsanext)) { + lsa_show_oneline(vty, lsa); + cnt++; + } + vty_out(vty, "%u entries.\n", cnt); + return CMD_SUCCESS; +} + +DEFPY(lsdb_walk_type, lsdb_walk_type_cmd, + "lsdb walk type (0-65535)", + "LSDB\n" + "walk entries\n" + "entry type\n" + "entry type\n") +{ + struct ospf6_lsa *lsa; + unsigned cnt = 0; + type = htons(type); + for (ALL_LSDB_TYPED(lsdb, type, lsa)) { + lsa_show_oneline(vty, lsa); + cnt++; + } + vty_out(vty, "%u entries.\n", cnt); + return CMD_SUCCESS; +} + +DEFPY(lsdb_walk_type_adv, lsdb_walk_type_adv_cmd, + "lsdb walk type (0-65535) adv A.B.C.D", + "LSDB\n" + "walk entries\n" + "entry type\n" + "entry type\n" + "advertising router ID\n" + "advertising router ID\n") +{ + struct ospf6_lsa *lsa; + unsigned cnt = 0; + type = htons(type); + for (ALL_LSDB_TYPED_ADVRTR(lsdb, type, adv.s_addr, lsa)) { + lsa_show_oneline(vty, lsa); + cnt++; + } + vty_out(vty, "%u entries.\n", cnt); + return CMD_SUCCESS; +} + +DEFPY(lsdb_get, lsdb_get_cmd, + "lsdb type (0-65535) adv A.B.C.D id A.B.C.D", + "LSDB\n" + "get entry's successor\n" + "entry type\n" + "entry type\n" + "advertising router ID\n" + "advertising router ID\n" + "LS-ID\n" + "LS-ID\n") +{ + struct ospf6_lsa *lsa; + type = htons(type); + if (!strcmp(argv[1]->text, "get-next")) + lsa = ospf6_lsdb_lookup_next(type, id.s_addr, adv.s_addr, lsdb); + else + lsa = ospf6_lsdb_lookup(type, id.s_addr, adv.s_addr, lsdb); + lsa_show_oneline(vty, lsa); + return CMD_SUCCESS; +} + +DEFPY(lsa_refcounts, lsa_refcounts_cmd, + "lsa refcounts", + "LSA\n" + "show reference counts\n") +{ + for (size_t i = 0; i < lsa_count; i++) + if (lsas[i]) + vty_out(vty, "[%zu] %u\n", i, lsas[i]->lock); + return CMD_SUCCESS; +} + +DEFPY(lsdb_create, lsdb_create_cmd, + "lsdb create", + "LSDB\n" + "create LSDB\n") +{ + if (lsdb) + ospf6_lsdb_delete(lsdb); + lsdb = ospf6_lsdb_create(NULL); + return CMD_SUCCESS; +} + +DEFPY(lsdb_delete, lsdb_delete_cmd, + "lsdb delete", + "LSDB\n" + "delete LSDB\n") +{ + ospf6_lsdb_delete(lsdb); + lsdb = NULL; + return CMD_SUCCESS; +} + + +struct zebra_privs_t ospf6d_privs; + +void test_init(int argc, char **argv) +{ + ospf6_lsa_init(); + + install_element(ENABLE_NODE, &lsa_set_cmd); + install_element(ENABLE_NODE, &lsa_refcounts_cmd); + install_element(ENABLE_NODE, &lsa_drop_cmd); + + install_element(ENABLE_NODE, &lsdb_create_cmd); + install_element(ENABLE_NODE, &lsdb_delete_cmd); + + install_element(ENABLE_NODE, &lsdb_add_cmd); + install_element(ENABLE_NODE, &lsdb_remove_cmd); + install_element(ENABLE_NODE, &lsdb_walk_cmd); + install_element(ENABLE_NODE, &lsdb_walk_type_cmd); + install_element(ENABLE_NODE, &lsdb_walk_type_adv_cmd); + install_element(ENABLE_NODE, &lsdb_get_cmd); +} diff --git a/tests/ospf6d/test_lsdb.in b/tests/ospf6d/test_lsdb.in new file mode 100644 index 0000000..0475f3d --- /dev/null +++ b/tests/ospf6d/test_lsdb.in @@ -0,0 +1,72 @@ +lsa set 0 type 1 adv 1.2.3.4 id 0.0.0.1 +lsa set 1 type 1 adv 1.2.3.4 id 0.0.0.2 +lsa set 2 type 2 adv 1.2.3.4 id 0.0.0.3 +lsa set 3 type 2 adv 128.2.3.4 id 0.0.0.4 +lsa set 4 type 2 adv 128.2.3.4 id 0.0.0.5 +lsa set 5 type 3 adv 0.0.0.1 id 0.0.0.6 +lsa refcounts + +lsdb create + +lsdb walk +lsdb walk type 1 +lsdb walk type 2 +lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa refcounts + +lsdb add 0 +lsdb add 1 +lsa refcounts + +lsdb walk +lsdb walk type 1 +lsdb walk type 2 +lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa refcounts + +lsdb remove 0 +lsdb add 2 +lsdb add 3 +lsdb add 4 +lsa refcounts + +lsdb walk +lsdb walk type 1 +lsdb walk type 2 +lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa refcounts + +lsdb add 5 +lsa refcounts + +lsdb walk +lsdb walk type 1 +lsdb walk type 2 +lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa refcounts + +lsdb remove 1 +lsdb remove 5 +lsa refcounts + +lsdb walk +lsdb walk type 1 +lsdb walk type 2 +lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa refcounts + +lsdb delete + +lsa refcounts +lsa drop 0 +lsa drop 1 +lsa drop 2 +lsa drop 3 +lsa drop 4 +lsa drop 5 + diff --git a/tests/ospf6d/test_lsdb.py b/tests/ospf6d/test_lsdb.py new file mode 100644 index 0000000..6ada617 --- /dev/null +++ b/tests/ospf6d/test_lsdb.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestLSDB(frrtest.TestRefOut): + program = "./test_lsdb" diff --git a/tests/ospf6d/test_lsdb.refout b/tests/ospf6d/test_lsdb.refout new file mode 100644 index 0000000..8b17652 --- /dev/null +++ b/tests/ospf6d/test_lsdb.refout @@ -0,0 +1,192 @@ +test# lsa set 0 type 1 adv 1.2.3.4 id 0.0.0.1 +test# lsa set 1 type 1 adv 1.2.3.4 id 0.0.0.2 +test# lsa set 2 type 2 adv 1.2.3.4 id 0.0.0.3 +test# lsa set 3 type 2 adv 128.2.3.4 id 0.0.0.4 +test# lsa set 4 type 2 adv 128.2.3.4 id 0.0.0.5 +test# lsa set 5 type 3 adv 0.0.0.1 id 0.0.0.6 +test# lsa refcounts +[0] 1 +[1] 1 +[2] 1 +[3] 1 +[4] 1 +[5] 1 +test# +test# lsdb create +test# +test# lsdb walk +0 entries. +test# lsdb walk type 1 +0 entries. +test# lsdb walk type 2 +0 entries. +test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsa = NULL +test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa = NULL +test# lsa refcounts +[0] 1 +[1] 1 +[2] 1 +[3] 1 +[4] 1 +[5] 1 +test# +test# lsdb add 0 +test# lsdb add 1 +test# lsa refcounts +[0] 2 +[1] 2 +[2] 1 +[3] 1 +[4] 1 +[5] 1 +test# +test# lsdb walk +type 1 adv 1.2.3.4 id 0.0.0.1 +type 1 adv 1.2.3.4 id 0.0.0.2 +2 entries. +test# lsdb walk type 1 +type 1 adv 1.2.3.4 id 0.0.0.1 +type 1 adv 1.2.3.4 id 0.0.0.2 +2 entries. +test# lsdb walk type 2 +0 entries. +test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +type 1 adv 1.2.3.4 id 0.0.0.2 +test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +lsa = NULL +test# lsa refcounts +[0] 2 +[1] 2 +[2] 1 +[3] 1 +[4] 1 +[5] 1 +test# +test# lsdb remove 0 +test# lsdb add 2 +test# lsdb add 3 +test# lsdb add 4 +test# lsa refcounts +[0] 1 +[1] 2 +[2] 2 +[3] 2 +[4] 2 +[5] 1 +test# +test# lsdb walk +type 1 adv 1.2.3.4 id 0.0.0.2 +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +4 entries. +test# lsdb walk type 1 +type 1 adv 1.2.3.4 id 0.0.0.2 +1 entries. +test# lsdb walk type 2 +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +3 entries. +test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +type 1 adv 1.2.3.4 id 0.0.0.2 +test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +type 2 adv 1.2.3.4 id 0.0.0.3 +test# lsa refcounts +[0] 1 +[1] 2 +[2] 2 +[3] 2 +[4] 2 +[5] 1 +test# +test# lsdb add 5 +test# lsa refcounts +[0] 1 +[1] 2 +[2] 2 +[3] 2 +[4] 2 +[5] 2 +test# +test# lsdb walk +type 1 adv 1.2.3.4 id 0.0.0.2 +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +type 3 adv 0.0.0.1 id 0.0.0.6 +5 entries. +test# lsdb walk type 1 +type 1 adv 1.2.3.4 id 0.0.0.2 +1 entries. +test# lsdb walk type 2 +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +3 entries. +test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +type 1 adv 1.2.3.4 id 0.0.0.2 +test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +type 2 adv 1.2.3.4 id 0.0.0.3 +test# lsa refcounts +[0] 1 +[1] 2 +[2] 2 +[3] 2 +[4] 2 +[5] 2 +test# +test# lsdb remove 1 +test# lsdb remove 5 +test# lsa refcounts +[0] 1 +[1] 1 +[2] 2 +[3] 2 +[4] 2 +[5] 1 +test# +test# lsdb walk +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +3 entries. +test# lsdb walk type 1 +0 entries. +test# lsdb walk type 2 +type 2 adv 1.2.3.4 id 0.0.0.3 +type 2 adv 128.2.3.4 id 0.0.0.4 +type 2 adv 128.2.3.4 id 0.0.0.5 +3 entries. +test# lsdb get type 1 adv 1.2.3.4 id 0.0.0.2 +lsa = NULL +test# lsdb get-next type 1 adv 1.2.3.4 id 0.0.0.2 +type 2 adv 1.2.3.4 id 0.0.0.3 +test# lsa refcounts +[0] 1 +[1] 1 +[2] 2 +[3] 2 +[4] 2 +[5] 1 +test# +test# lsdb delete +test# +test# lsa refcounts +[0] 1 +[1] 1 +[2] 1 +[3] 1 +[4] 1 +[5] 1 +test# lsa drop 0 +test# lsa drop 1 +test# lsa drop 2 +test# lsa drop 3 +test# lsa drop 4 +test# lsa drop 5 +test# +test# +end. diff --git a/tests/ospfd/.gitignore b/tests/ospfd/.gitignore new file mode 100644 index 0000000..c659b64 --- /dev/null +++ b/tests/ospfd/.gitignore @@ -0,0 +1,3 @@ +/*_afl/* +test_ospf_spf +core diff --git a/tests/ospfd/common.c b/tests/ospfd/common.c new file mode 100644 index 0000000..e394186 --- /dev/null +++ b/tests/ospfd/common.c @@ -0,0 +1,249 @@ +#include + +#include "lib/stream.h" +#include "lib/vty.h" +#include "lib/mpls.h" +#include "lib/if.h" +#include "lib/table.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_sr.h" + +#include "common.h" + +struct event_loop *master; +struct zebra_privs_t ospfd_privs; + + +struct ospf_topology *test_find_topology(const char *name) +{ + if (strmatch(name, "topo1")) + return &topo1; + else if (strmatch(name, "topo2")) + return &topo2; + else if (strmatch(name, "topo3")) + return &topo3; + else if (strmatch(name, "topo4")) + return &topo4; + else if (strmatch(name, "topo5")) + return &topo5; + + return NULL; +} + +int sort_paths(const void **path1, const void **path2) +{ + const struct ospf_path *p1 = *path1; + const struct ospf_path *p2 = *path2; + + return (p1->nexthop.s_addr - p2->nexthop.s_addr); +} + +void print_route_table(struct vty *vty, struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *pnode; + struct ospf_path *path; + struct mpls_label_stack *label_stack; + char buf[MPLS_LABEL_STRLEN]; + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + if ((or = rn->info) == NULL) + continue; + + vty_out(vty, "N %-18pFX %-15pI4 %d\n", &rn->p, + & or->u.std.area_id, or->cost); + + list_sort(or->paths, sort_paths); + + for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, path)) { + if (path->nexthop.s_addr == 0) + continue; + + vty_out(vty, " -> %pI4 with adv router %pI4", + &path->nexthop, &path->adv_router); + + if (path->srni.backup_label_stack) { + label_stack = path->srni.backup_label_stack; + mpls_label2str(label_stack->num_labels, + label_stack->label, buf, + MPLS_LABEL_STRLEN, + ZEBRA_LSP_NONE, true); + vty_out(vty, " and backup path %s", buf); + } + vty_out(vty, "\n"); + } + } +} + +struct ospf_test_node *test_find_node(struct ospf_topology *topology, + const char *hostname) +{ + for (int i = 0; topology->nodes[i].hostname[0]; i++) + if (strmatch(hostname, topology->nodes[i].hostname)) + return &topology->nodes[i]; + + return NULL; +} + +static void inject_router_lsa(struct vty *vty, struct ospf *ospf, + struct ospf_topology *topology, + struct ospf_test_node *root, + struct ospf_test_node *tnode) +{ + struct ospf_area *area; + struct in_addr router_id; + struct in_addr adj_router_id; + struct prefix_ipv4 prefix; + struct in_addr data; + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + int length; + unsigned long putp; + uint16_t link_count; + struct ospf_test_node *tfound_adj_node; + struct ospf_test_adj *tadj; + bool is_self_lsa = false; + + area = ospf->backbone; + inet_aton(tnode->router_id, &router_id); + + if (strncmp(root->router_id, tnode->router_id, 256) == 0) + is_self_lsa = true; + + s = stream_new(OSPF_MAX_LSA_SIZE); + lsa_header_set(s, LSA_OPTIONS_GET(area) | LSA_OPTIONS_NSSA_GET(area), + OSPF_ROUTER_LSA, router_id, router_id); + + stream_putc(s, router_lsa_flags(area)); + stream_putc(s, 0); + + putp = stream_get_endp(s); + stream_putw(s, 0); + + for (link_count = 0; tnode->adjacencies[link_count].hostname[0]; + link_count++) { + tadj = &tnode->adjacencies[link_count]; + tfound_adj_node = test_find_node(topology, tadj->hostname); + str2prefix_ipv4(tnode->adjacencies[link_count].network, + &prefix); + + inet_aton(tfound_adj_node->router_id, &adj_router_id); + data.s_addr = prefix.prefix.s_addr; + link_info_set(&s, adj_router_id, data, + LSA_LINK_TYPE_POINTOPOINT, 0, tadj->metric); + + masklen2ip(prefix.prefixlen, &data); + link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0, + tadj->metric); + } + + /* Don't forget the node itself (just a stub) */ + str2prefix_ipv4(tnode->router_id, &prefix); + data.s_addr = 0xffffffff; + link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0, 0); + + /* Take twice the link count (for P2P and stub) plus the local stub */ + stream_putw_at(s, putp, (2 * link_count) + 1); + + length = stream_get_endp(s); + lsah = (struct lsa_header *)STREAM_DATA(s); + lsah->length = htons(length); + + new = ospf_lsa_new_and_data(length); + new->area = area; + new->vrf_id = area->ospf->vrf_id; + + if (is_self_lsa) + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + + memcpy(new->data, lsah, length); + stream_free(s); + + ospf_lsdb_add(area->lsdb, new); + + if (is_self_lsa) { + ospf_lsa_unlock(&area->router_lsa_self); + area->router_lsa_self = ospf_lsa_lock(new); + } +} + +static void inject_sr_db_entry(struct vty *vty, struct ospf_test_node *tnode, + struct ospf_topology *topology) +{ + struct ospf_test_node *tfound_adj_node; + struct ospf_test_adj *tadj; + struct in_addr router_id; + struct in_addr remote_id; + struct sr_node *srn; + struct sr_prefix *srp; + struct sr_link *srl; + int link_count; + + inet_aton(tnode->router_id, &router_id); + + srn = ospf_sr_node_create(&router_id); + + srn->srgb.range_size = 8000; + srn->srgb.lower_bound = 16000; + srn->msd = 16; + + srn->srlb.range_size = 1000; + srn->srlb.lower_bound = 15000; + + /* Prefix SID */ + srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + srp->adv_router = router_id; + srp->sid = tnode->label; + srp->srn = srn; + + listnode_add(srn->ext_prefix, srp); + + /* Adjacency SIDs for all adjacencies */ + for (link_count = 0; tnode->adjacencies[link_count].hostname[0]; + link_count++) { + tadj = &tnode->adjacencies[link_count]; + tfound_adj_node = test_find_node(topology, tadj->hostname); + + srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); + srl->adv_router = router_id; + + inet_aton(tfound_adj_node->router_id, &remote_id); + srl->remote_id = remote_id; + + srl->type = ADJ_SID; + srl->sid[0] = srn->srlb.lower_bound + tadj->label; + srl->srn = srn; + + listnode_add(srn->ext_link, srl); + } +} + +int topology_load(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, struct ospf *ospf) +{ + struct ospf_test_node *tnode; + + for (int i = 0; topology->nodes[i].hostname[0]; i++) { + tnode = &topology->nodes[i]; + + /* Inject a router LSA for each node, used for SPF */ + inject_router_lsa(vty, ospf, topology, root, tnode); + + /* + * SR information could also be inected via LSAs, but directly + * filling the SR DB with labels is just easier. + */ + inject_sr_db_entry(vty, tnode, topology); + } + + return 0; +} diff --git a/tests/ospfd/common.h b/tests/ospfd/common.h new file mode 100644 index 0000000..18e412b --- /dev/null +++ b/tests/ospfd/common.h @@ -0,0 +1,47 @@ +#ifndef _COMMON_OSPF_H +#define _COMMON_OSPF_H + +#define MAX_ADJACENCIES 8 +#define MAX_NODES 12 + +struct ospf_test_adj { + char hostname[256]; + char network[256]; + uint32_t metric; + mpls_label_t label; +}; + +struct ospf_test_node { + char hostname[256]; + const char *router_id; + mpls_label_t label; + struct ospf_test_adj adjacencies[MAX_ADJACENCIES + 1]; +}; + +struct ospf_topology { + struct ospf_test_node nodes[MAX_NODES + 1]; +}; + +/* Prototypes. */ +extern struct ospf_topology *test_find_topology(const char *name); +extern struct ospf_test_node *test_find_node(struct ospf_topology *topology, + const char *hostname); +extern int topology_load(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, struct ospf *ospf); + +/* Global variables. */ +extern struct event_loop *master; +extern struct ospf_topology topo1; +extern struct ospf_topology topo2; +extern struct ospf_topology topo3; +extern struct ospf_topology topo4; +extern struct ospf_topology topo5; +extern struct zebra_privs_t ospfd_privs; + +/* For stable order in unit tests */ +extern int sort_paths(const void **path1, const void **path2); + +/* Print the routing table */ +extern void print_route_table(struct vty *vty, struct route_table *rt); + +#endif /* _COMMON_OSPF_H */ diff --git a/tests/ospfd/subdir.am b/tests/ospfd/subdir.am new file mode 100644 index 0000000..5ed5b9d --- /dev/null +++ b/tests/ospfd/subdir.am @@ -0,0 +1,21 @@ +if !OSPFD +PYTEST_IGNORE += --ignore=ospfd/ +endif +OSPFD_TEST_LDADD = ospfd/libfrrospf.a $(ALL_TESTS_LDADD) +noinst_HEADERS += \ + tests/ospfd/common.h \ + # end + + +if OSPFD +check_PROGRAMS += tests/ospfd/test_ospf_spf +endif +tests_ospfd_test_ospf_spf_CFLAGS = $(TESTS_CFLAGS) +tests_ospfd_test_ospf_spf_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_ospfd_test_ospf_spf_LDADD = $(OSPFD_TEST_LDADD) +tests_ospfd_test_ospf_spf_SOURCES = tests/ospfd/test_ospf_spf.c tests/ospfd/common.c tests/ospfd/topologies.c +EXTRA_DIST += \ + tests/ospfd/test_ospf_spf.py \ + tests/ospfd/test_ospf_spf.in \ + tests/ospfd/test_ospf_spf.refout \ + # end diff --git a/tests/ospfd/test_ospf_spf.c b/tests/ospfd/test_ospf_spf.c new file mode 100644 index 0000000..9327631 --- /dev/null +++ b/tests/ospfd/test_ospf_spf.c @@ -0,0 +1,306 @@ +#include +#include + +#include "getopt.h" +#include "frrevent.h" +#include +#include "vty.h" +#include "command.h" +#include "log.h" +#include "vrf.h" +#include "table.h" +#include "mpls.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_ti_lfa.h" +#include "ospfd/ospf_vty.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_sr.h" + +#include "common.h" + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func); +DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item, + q_spaces_compare_func); + +static struct ospf *test_init(struct ospf_test_node *root) +{ + struct ospf *ospf; + struct ospf_area *area; + struct in_addr area_id; + struct in_addr router_id; + + ospf = ospf_new_alloc(0, VRF_DEFAULT_NAME); + + area_id.s_addr = OSPF_AREA_BACKBONE; + area = ospf_area_new(ospf, area_id); + listnode_add_sort(ospf->areas, area); + + inet_aton(root->router_id, &router_id); + ospf->router_id = router_id; + ospf->router_id_static = router_id; + ospf->ti_lfa_enabled = true; + + return ospf; +} + +static void test_run_spf(struct vty *vty, struct ospf *ospf, + enum protection_type protection_type, bool verbose) +{ + struct route_table *new_table, *new_rtrs; + struct route_table *all_rtrs = NULL; + struct ospf_area *area; + struct p_space *p_space; + struct q_space *q_space; + char label_buf[MPLS_LABEL_STRLEN]; + char res_buf[PROTECTED_RESOURCE_STRLEN]; + + /* Just use the backbone for testing */ + area = ospf->backbone; + + new_table = route_table_init(); + new_rtrs = route_table_init(); + all_rtrs = route_table_init(); + + /* dryrun true, root_node false */ + ospf_spf_calculate(area, area->router_lsa_self, new_table, all_rtrs, + new_rtrs, true, false); + + if (verbose) { + vty_out(vty, "SPF Tree without TI-LFA backup paths:\n\n"); + ospf_spf_print(vty, area->spf, 0); + + vty_out(vty, + "\nRouting Table without TI-LFA backup paths:\n\n"); + print_route_table(vty, new_table); + } + + if (verbose) + vty_out(vty, "\n... generating TI-LFA backup paths ...\n"); + + /* TI-LFA testrun */ + ospf_ti_lfa_generate_p_spaces(area, protection_type); + ospf_ti_lfa_insert_backup_paths(area, new_table); + + /* Print P/Q space information */ + if (verbose) { + vty_out(vty, "\nP and Q space info:\n"); + frr_each (p_spaces, area->p_spaces, p_space) { + ospf_print_protected_resource( + p_space->protected_resource, res_buf); + vty_out(vty, "\nP Space for root %pI4 and %s\n", + &p_space->root->id, res_buf); + ospf_spf_print(vty, p_space->root, 0); + + frr_each (q_spaces, p_space->q_spaces, q_space) { + vty_out(vty, + "\nQ Space for destination %pI4:\n", + &q_space->root->id); + ospf_spf_print(vty, q_space->root, 0); + if (q_space->label_stack) { + mpls_label2str( + q_space->label_stack + ->num_labels, + q_space->label_stack->label, + label_buf, MPLS_LABEL_STRLEN, + ZEBRA_LSP_NONE, true); + vty_out(vty, "\nLabel stack: %s\n", + label_buf); + } else { + vty_out(vty, + "\nLabel stack not generated!\n"); + } + } + + vty_out(vty, "\nPost-convergence SPF Tree:\n"); + ospf_spf_print(vty, p_space->pc_spf, 0); + } + } + + /* Cleanup */ + ospf_ti_lfa_free_p_spaces(area); + ospf_spf_cleanup(area->spf, area->spf_vertex_list); + + /* + * Print the new routing table which is augmented with TI-LFA backup + * paths (label stacks). + */ + if (verbose) + vty_out(vty, + "\n\nFinal Routing Table including backup paths:\n\n"); + + print_route_table(vty, new_table); +} + +static int test_run(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, + enum protection_type protection_type, bool verbose) +{ + struct ospf *ospf; + + ospf = test_init(root); + + /* Inject LSAs into the OSPF backbone according to the topology */ + if (topology_load(vty, topology, root, ospf)) { + vty_out(vty, "%% Failed to load topology\n"); + return CMD_WARNING; + } + + if (verbose) { + vty_out(vty, "\n"); + show_ip_ospf_database_summary(vty, ospf, 0, NULL); + } + + test_run_spf(vty, ospf, protection_type, verbose); + + return 0; +} + +DEFUN(test_ospf, test_ospf_cmd, + "test ospf topology WORD root HOSTNAME ti-lfa [node-protection] [verbose]", + "Test mode\n" + "Choose OSPF for SPF testing\n" + "Network topology to choose\n" + "Name of the network topology to choose\n" + "Root node to choose\n" + "Hostname of the root node to choose\n" + "Use Topology-Independent LFA\n" + "Use node protection (default is link protection)\n" + "Verbose output\n") +{ + struct ospf_topology *topology; + struct ospf_test_node *root; + enum protection_type protection_type = OSPF_TI_LFA_LINK_PROTECTION; + int idx = 0; + bool verbose = false; + + /* Parse topology. */ + argv_find(argv, argc, "topology", &idx); + topology = test_find_topology(argv[idx + 1]->arg); + if (!topology) { + vty_out(vty, "%% Topology not found\n"); + return CMD_WARNING; + } + + argv_find(argv, argc, "root", &idx); + root = test_find_node(topology, argv[idx + 1]->arg); + if (!root) { + vty_out(vty, "%% Root not found\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "node-protection", &idx)) + protection_type = OSPF_TI_LFA_NODE_PROTECTION; + + if (argv_find(argv, argc, "verbose", &idx)) + verbose = true; + + return test_run(vty, topology, root, protection_type, verbose); +} + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + + cmd_terminate(); + vty_terminate(); + event_master_free(master); + + if (!isexit) + exit(0); +} + +struct option longopts[] = {{"help", no_argument, NULL, 'h'}, + {"debug", no_argument, NULL, 'd'}, + {0} }; + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +ospfd SPF test program.\n\n\ +-u, --debug Enable debugging\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + +int main(int argc, char **argv) +{ + char *p; + char *progname; + struct event thread; + bool debug = false; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "hd", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'd': + debug = true; + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* master init. */ + master = event_master_create(NULL); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + vty_init(master, false); + if (debug) + zlog_aux_init("NONE: ", LOG_DEBUG); + else + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + /* Install test command. */ + install_element(VIEW_NODE, &test_ospf_cmd); + + /* needed for SR DB init */ + ospf_vty_init(); + ospf_sr_init(); + + term_debug_ospf_ti_lfa = 1; + + /* Read input from .in file. */ + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/ospfd/test_ospf_spf.in b/tests/ospfd/test_ospf_spf.in new file mode 100644 index 0000000..f1e7467 --- /dev/null +++ b/tests/ospfd/test_ospf_spf.in @@ -0,0 +1,10 @@ +test ospf topology topo1 root rt1 ti-lfa +test ospf topology topo1 root rt1 ti-lfa node-protection +test ospf topology topo2 root rt1 ti-lfa +test ospf topology topo2 root rt1 ti-lfa node-protection +test ospf topology topo3 root rt1 ti-lfa +test ospf topology topo3 root rt1 ti-lfa node-protection +test ospf topology topo4 root rt1 ti-lfa +test ospf topology topo4 root rt1 ti-lfa node-protection +test ospf topology topo5 root rt1 ti-lfa +test ospf topology topo5 root rt1 ti-lfa node-protection diff --git a/tests/ospfd/test_ospf_spf.py b/tests/ospfd/test_ospf_spf.py new file mode 100644 index 0000000..92a1c6a --- /dev/null +++ b/tests/ospfd/test_ospf_spf.py @@ -0,0 +1,4 @@ +import frrtest + +class TestOspfSPF(frrtest.TestRefOut): + program = './test_ospf_spf' diff --git a/tests/ospfd/test_ospf_spf.refout b/tests/ospfd/test_ospf_spf.refout new file mode 100644 index 0000000..d1e3c7b --- /dev/null +++ b/tests/ospfd/test_ospf_spf.refout @@ -0,0 +1,130 @@ +test# test ospf topology topo1 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 3.3.3.3/32 0.0.0.0 10 + -> 10.0.3.2 with adv router 3.3.3.3 and backup path 15001 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 + -> 10.0.3.2 with adv router 3.3.3.3 and backup path 15001 +N 10.0.3.0/24 0.0.0.0 10 +test# test ospf topology topo1 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 10 + -> 10.0.3.2 with adv router 3.3.3.3 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 + -> 10.0.3.2 with adv router 3.3.3.3 +N 10.0.3.0/24 0.0.0.0 10 +test# test ospf topology topo2 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.1.2 with adv router 3.3.3.3 and backup path 15002 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 10.0.3.0/24 0.0.0.0 30 +test# test ospf topology topo2 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.1.2 with adv router 3.3.3.3 and backup path 15002 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 30 +test# test ospf topology topo3 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo3 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo4 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030/15006 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 60 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030/15006 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo4 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 60 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo5 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 30 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004/15006 +N 10.0.1.0/24 0.0.0.0 40 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004/15006 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo5 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 30 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 40 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# +end. diff --git a/tests/ospfd/topologies.c b/tests/ospfd/topologies.c new file mode 100644 index 0000000..2dc611c --- /dev/null +++ b/tests/ospfd/topologies.c @@ -0,0 +1,575 @@ +#include + +#include "mpls.h" +#include "if.h" + +#include "ospfd/ospfd.h" + +#include "common.h" + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt2 eth-rt1| RT2 | + * | 1.1.1.1 +---------------------+ 2.2.2.2 | + * | | 10.0.1.0/24 | | + * +---------+ +---------+ + * |eth-rt3 eth-rt3| + * | | + * |10.0.3.0/24 | + * | | + * |eth-rt1 | + * +---------+ | + * | |eth-rt2 10.0.2.0/24| + * | RT3 +--------------------------+ + * | 3.3.3.3 | + * | | + * +---------+ + * + * Link Protection: + * P and Q spaces overlap here, hence just one P/Q node regardless of which + * link is protected. Hence the backup label stack just has one label. + * + * Node Protection: + * Obviously no backup paths involved. + */ +struct ospf_topology topo1 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt3", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 10, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 5, + }, + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 6, + }, + }, + }, + }, +}; + + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt2 eth-rt1| RT2 | + * | 1.1.1.1 +---------------------+ 2.2.2.2 | + * | | 10.0.1.0/24 (10) | | + * +---------+ +---------+ + * |eth-rt3 eth-rt3| + * | | + * |10.0.3.0/24 (30) | + * | | + * |eth-rt1 | + * +---------+ | + * | |eth-rt2 10.0.2.0/24|(10) + * | RT3 +--------------------------+ + * | 3.3.3.3 | + * | | + * +---------+ + * + * Link Protection: + * Regarding the subnet 10.0.1.0/24, the P space of RT1 is just RT1 itself + * while the Q space of RT3 consists of RT3 and RT2. Hence the P and Q + * nodes are disjunct (tricky: the root node is the P node here). For the + * backup label stack just one label is necessary. + * + * Node Protection: + * For protected node RT2 and route from RT1 to RT3 there is just the backup + * path consisting of the label 15002. + */ +struct ospf_topology topo2 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt3", + .network = + "10.0.3.1/24", + .metric = 30, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 10, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.3.2/24", + .metric = 30, + .label = 5, + }, + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 6, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 (10) | | + * +---------+ +---------+ + * |eth-rt2 eth-rt3| + * | | + * |10.0.1.0/24 (10) | + * | 10.0.3.0/24 (10) | + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 (20) | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * Link Protection: + * Regarding the protected subnet 10.0.4.0/24, the P and Q spaces for root RT1 + * and destination RT4 are disjunct and the P node is RT2 while RT3 is the Q + * node. Hence the backup label stack here is 16020/15004. Note that here the + * P and Q nodes are neither the root nor the destination nodes, so this is a + * case where you really need a label stack consisting of two labels. + * + * Node Protection: + * For the protected node RT4 and the route from RT1 to RT3 there is a backup + * path with the single label 15001. + */ +struct ospf_topology topo3 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 20, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 20, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 8, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 (10) | | + * +---------+ +---------+ + * |eth+rt2 eth-rt3| + * | | + * |10.0.1.0/24 (10) | + * | 10.0.3.0/24 (10) | + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 (40) | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * This case was specifically created for Node Protection with RT4 as + * protected node from the perspective of RT1. Note the weight of 40 + * on the link between RT2 and RT3. + * The P space of RT1 is just RT2 while the Q space of RT3 is empty. + * This means that the P and Q spaces are disjunct and there are two + * labels needed to get from RT1 to RT3. + */ +struct ospf_topology topo4 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 50, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 50, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 10, + .label = 8, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 | | + * +---------+ +---------+ + * |eth+rt2 eth-rt3| + * | | + * |10.0.1.0/24 | + * | 10.0.3.0/24| + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * Weights: + * - clockwise: 10 + * - counterclockwise: 40 + * + * This is an example where 3 (!) labels are needed for the protected + * link RT1<->RT2, e.g. the subnet 10.0.1.0/24, to reach RT4. + * + * Because the initial P and Q spaces will not be overlapping or + * adjacent for this case the TI-LFA will be applied recursively. + */ +struct ospf_topology topo5 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 40, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 40, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 40, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 40, + .label = 8, + }, + }, + }, + }, +}; diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 0000000..3c436ed --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +norecursedirs = topotests diff --git a/tests/runtests.py b/tests/runtests.py new file mode 100644 index 0000000..4677796 --- /dev/null +++ b/tests/runtests.py @@ -0,0 +1,6 @@ +import pytest +import sys +import os + +sys.path.append(os.path.join(os.path.dirname(__file__), "helpers", "python")) +raise SystemExit(pytest.main(sys.argv[1:])) diff --git a/tests/subdir.am b/tests/subdir.am new file mode 100644 index 0000000..ab322f7 --- /dev/null +++ b/tests/subdir.am @@ -0,0 +1,76 @@ +# +# tests +# + +# +# *sigh* - there is no way to get CPPFLAGS or CFLAGS for a group of files :( +# + +TESTS_CPPFLAGS = $(AM_CPPFLAGS) \ + -I$(top_srcdir)/tests/helpers/c \ + -I$(top_builddir)/tests/helpers/c \ + # end +TESTS_CFLAGS = \ + $(AC_CFLAGS) \ + $(LIBYANG_CFLAGS) \ + $(SAN_FLAGS) \ + # end +# note no -Werror + +TESTS_CXXFLAGS = \ + $(AC_CXXFLAGS) \ + $(LIBYANG_CFLAGS) \ + $(SAN_FLAGS) \ + # end +# note no -Werror + +ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP) + +EXTRA_DIST += \ + tests/runtests.py \ + tests/helpers/python/frrsix.py \ + tests/helpers/python/frrtest.py \ + # end + +check_PROGRAMS = +PYTEST_IGNORE = + +.PHONY: tests/tests.xml +tests/tests.xml: $(check_PROGRAMS) + ( cd tests; $(PYTHON) ../$(srcdir)/tests/runtests.py --junitxml=tests.xml -v ../$(srcdir)/tests $(PYTEST_IGNORE); ) +check: tests/tests.xml + +clean-local: clean-tests +.PHONY: clean-tests +clean-tests: + -rm -f tests/tests.xml + + +# CHEAT SHEET: +# +### conditional (if needed) - ONLY for "check_PROGRAMS +=" line! +# if DAEMON +# check_PROGRAMS += tests/daemon/test_foo +# endif +### CFLAGS/CPPFLAGS/LDADD as usual, extend on top of TESTS_XYZFLAGS +# tests_daemon_test_foo_CFLAGS = $(TESTS_CFLAGS) +# tests_daemon_test_foo_CPPFLAGS = $(TESTS_CPPFLAGS) +# tests_daemon_test_foo_LDADD = $(ALL_TESTS_LDADD) +# tests_daemon_test_foo_SOURCES = tests/daemon/test_foo.c +### don't forget "nodist_" for autogenerated source files, & add to CLEANFILES +# nodist_tests_daemon_test_foo_SOURCES = tests/daemon/test_foo_autogen.c +# CLEANFILES += tests/daemon/test_foo_autogen.c +### clippy_scan works normally +# clippy_scan += tests/daemon/test_foo.c +### header files for tests go into "noinst_HEADERS" +# noinst_HEADERS += tests/daemon/foo.h +### all python scripts & auxiliary files are added into EXTRA_DIST +# EXTRA_DIST += tests/daemon/test_foo.py +# + +include tests/bgpd/subdir.am +include tests/isisd/subdir.am +include tests/ospfd/subdir.am +include tests/ospf6d/subdir.am +include tests/zebra/subdir.am +include tests/lib/subdir.am diff --git a/tests/topotests/.gitignore b/tests/topotests/.gitignore new file mode 100644 index 0000000..b1e3c30 --- /dev/null +++ b/tests/topotests/.gitignore @@ -0,0 +1,4 @@ +.cache +__pycache__ +*.pyc +.pytest_cache diff --git a/tests/topotests/Dockerfile b/tests/topotests/Dockerfile new file mode 100644 index 0000000..1503e67 --- /dev/null +++ b/tests/topotests/Dockerfile @@ -0,0 +1,87 @@ +FROM ubuntu:18.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y \ + autoconf \ + binutils \ + bison \ + ca-certificates \ + flex \ + gdb \ + git \ + gpg \ + install-info \ + iputils-ping \ + iproute2 \ + less \ + libtool \ + libjson-c-dev \ + libpcre3-dev \ + libpython-dev \ + libpython3-dev \ + libreadline-dev \ + libc-ares-dev \ + libcap-dev \ + libelf-dev \ + man \ + mininet \ + pkg-config \ + python-pip \ + python3 \ + python3-dev \ + python3-sphinx \ + python3-pytest \ + rsync \ + strace \ + tcpdump \ + texinfo \ + tmux \ + valgrind \ + vim \ + wget \ + x11-xserver-utils \ + xterm \ + && pip install \ + exabgp==3.4.17 \ + "scapy>=2.4.2" \ + ipaddr \ + pytest \ + && rm -rf /var/lib/apt/lists/* + +RUN export DEBIAN_FRONTEND=noninteractive \ + && wget -qO- https://deb.frrouting.org/frr/keys.asc | apt-key add - \ + && echo "deb https://deb.frrouting.org/frr bionic frr-stable" > /etc/apt/sources.list.d/frr.list \ + && apt-get update \ + && apt-get install -y libyang-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN groupadd -r -g 92 frr \ + && groupadd -r -g 85 frrvty \ + && useradd -c "FRRouting suite" \ + -d /var/run/frr \ + -g frr \ + -G frrvty \ + -r \ + -s /sbin/nologin \ + frr \ + && useradd -d /var/run/exabgp/ \ + -s /bin/false \ + exabgp + +# Configure coredumps +RUN echo "" >> /etc/security/limits.conf; \ + echo "* soft core unlimited" >> /etc/security/limits.conf; \ + echo "root soft core unlimited" >> /etc/security/limits.conf; \ + echo "* hard core unlimited" >> /etc/security/limits.conf; \ + echo "root hard core unlimited" >> /etc/security/limits.conf + +# Copy run scripts to facilitate users wanting to run the tests +COPY docker/inner /opt/topotests + +ENV PATH "$PATH:/opt/topotests" + +RUN echo "cat /opt/topotests/motd.txt" >> /root/.profile && \ + echo "export PS1='(topotests) $PS1'" >> /root/.profile + +ENTRYPOINT [ "bash", "/opt/topotests/entrypoint.sh" ] diff --git a/tests/topotests/README.md b/tests/topotests/README.md new file mode 100644 index 0000000..d9d849b --- /dev/null +++ b/tests/topotests/README.md @@ -0,0 +1 @@ +Documentation is located in /doc/developer/topotests.rst diff --git a/tests/topotests/all_protocol_startup/r1/babeld.conf b/tests/topotests/all_protocol_startup/r1/babeld.conf new file mode 100644 index 0000000..3e119bf --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/babeld.conf @@ -0,0 +1,4 @@ +router babel + network 192.168.1.1 + network 192.168.2.1 +! \ No newline at end of file diff --git a/tests/topotests/all_protocol_startup/r1/bgpd.conf b/tests/topotests/all_protocol_startup/r1/bgpd.conf new file mode 100644 index 0000000..32dcb72 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/bgpd.conf @@ -0,0 +1,60 @@ +log file bgpd.log +! +! +router bgp 100 + bgp router-id 192.168.0.1 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.7.10 remote-as 100 + neighbor 192.168.7.10 timers 3 10 + neighbor 192.168.7.20 remote-as 200 + neighbor 192.168.7.20 timers 3 10 + neighbor fc00:0:0:8::1000 remote-as 100 + neighbor fc00:0:0:8::1000 timers 3 10 + neighbor fc00:0:0:8::2000 remote-as 200 + neighbor 192.168.7.10 description Transit_cogent + neighbor 192.168.7.20 description Client_Bibi_Full + neighbor fc00:0:0:8::1000 description Transit_cogent_v6 + neighbor fc00:0:0:8::2000 description Client_Toto_default + neighbor fc00:0:0:8::2000 timers 3 10 + ! + address-family ipv4 unicast + network 192.168.0.0/24 + neighbor 192.168.7.10 route-map bgp-map in + neighbor 192.168.7.10 filter-list bgp-filter-v4 out + neighbor 192.168.7.20 route-map bgp-map in + neighbor 192.168.7.20 filter-list bgp-filter-v4 out + exit-address-family + ! + address-family ipv6 unicast + network fc00::/64 + neighbor fc00:0:0:8::1000 activate + neighbor fc00:0:0:8::1000 route-map bgp-map in + neighbor fc00:0:0:8::1000 filter-list bgp-filter-v6 out + neighbor fc00:0:0:8::2000 activate + neighbor fc00:0:0:8::2000 route-map bgp-map in + neighbor fc00:0:0:8::2000 filter-list bgp-filter-v6 out + exit-address-family +! +! +ip prefix-list bgp-filter-v4 description dummy-test-prefix-list +ip prefix-list bgp-filter-v4 seq 5 permit 192.168.0.0/24 +! +ipv6 prefix-list bgp-filter-v4 seq 5 permit fc00::/64 +ipv6 prefix-list bgp-filter-v6 description dummy-test-prefix-list-v6 +! +route-map bgp-map permit 10 + set community 100:100 additive + set local-preference 100 +! +route-map bgp-map permit 20 + set metric 10 + set local-preference 200 +! +line vty +! + +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/r1/ip_nht.ref b/tests/topotests/all_protocol_startup/r1/ip_nht.ref new file mode 100644 index 0000000..a2f3d3b --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ip_nht.ref @@ -0,0 +1,74 @@ +VRF default: + Resolve via default: on +1.1.1.1 + resolved via static + is directly connected, r1-eth1 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.2 + resolved via static + is directly connected, r1-eth2 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.3 + resolved via static + is directly connected, r1-eth3 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.4 + resolved via static + is directly connected, r1-eth4 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.5 + resolved via static + is directly connected, r1-eth5 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.6 + resolved via static + is directly connected, r1-eth6 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.7 + resolved via static + is directly connected, r1-eth7 (vrf default), weight 1 + Client list: pbr(fd XX) +1.1.1.8 + resolved via static + is directly connected, r1-eth8 (vrf default), weight 1 + Client list: pbr(fd XX) +2.2.2.1 + unresolved + Client list: pbr(fd XX) +4.4.4.1 + unresolved + Client list: pbr(fd XX) +4.4.4.2 + unresolved + Client list: pbr(fd XX) +6.6.6.1 + unresolved + Client list: pbr(fd XX) +6.6.6.2 + unresolved + Client list: pbr(fd XX) +6.6.6.3 + unresolved + Client list: pbr(fd XX) +6.6.6.4 + unresolved + Client list: pbr(fd XX) +192.168.0.2 + resolved via connected + is directly connected, r1-eth0 (vrf default) + Client list: static(fd XX) +192.168.0.4 + resolved via connected + is directly connected, r1-eth0 (vrf default) + Client list: static(fd XX) +192.168.7.10 + resolved via connected + is directly connected, r1-eth7 (vrf default) + Client list: bgp(fd XX) +192.168.7.20(Connected) + resolved via connected + is directly connected, r1-eth7 (vrf default) + Client list: bgp(fd XX) +192.168.161.4 + unresolved + Client list: pbr(fd XX) diff --git a/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref b/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref new file mode 100644 index 0000000..a4a4aba --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ipv4_routes.ref @@ -0,0 +1,42 @@ +C>* 192.168.0.0/24 is directly connected, r1-eth0, XX:XX:XX +C>* 192.168.1.0/26 is directly connected, r1-eth1, XX:XX:XX +C>* 192.168.2.0/26 is directly connected, r1-eth2, XX:XX:XX +C>* 192.168.3.0/26 is directly connected, r1-eth3, XX:XX:XX +C>* 192.168.4.0/26 is directly connected, r1-eth4, XX:XX:XX +C>* 192.168.5.0/26 is directly connected, r1-eth5, XX:XX:XX +C>* 192.168.6.0/26 is directly connected, r1-eth6, XX:XX:XX +C>* 192.168.7.0/26 is directly connected, r1-eth7, XX:XX:XX +C>* 192.168.8.0/26 is directly connected, r1-eth8, XX:XX:XX +C>* 192.168.9.0/26 is directly connected, r1-eth9, XX:XX:XX +L>* 192.168.0.1/32 is directly connected, r1-eth0, XX:XX:XX +L>* 192.168.1.1/32 is directly connected, r1-eth1, XX:XX:XX +L>* 192.168.2.1/32 is directly connected, r1-eth2, XX:XX:XX +L>* 192.168.3.1/32 is directly connected, r1-eth3, XX:XX:XX +L>* 192.168.4.1/32 is directly connected, r1-eth4, XX:XX:XX +L>* 192.168.5.1/32 is directly connected, r1-eth5, XX:XX:XX +L>* 192.168.6.1/32 is directly connected, r1-eth6, XX:XX:XX +L>* 192.168.7.1/32 is directly connected, r1-eth7, XX:XX:XX +L>* 192.168.8.1/32 is directly connected, r1-eth8, XX:XX:XX +L>* 192.168.9.1/32 is directly connected, r1-eth9, XX:XX:XX +O 192.168.0.0/24 [110/10] is directly connected, r1-eth0, weight 1, XX:XX:XX +O 192.168.3.0/26 [110/10] is directly connected, r1-eth3, weight 1, XX:XX:XX +S>* 1.1.1.1/32 [1/0] is directly connected, r1-eth1, weight 1, XX:XX:XX +S>* 1.1.1.2/32 [1/0] is directly connected, r1-eth2, weight 1, XX:XX:XX +S>* 1.1.1.3/32 [1/0] is directly connected, r1-eth3, weight 1, XX:XX:XX +S>* 1.1.1.4/32 [1/0] is directly connected, r1-eth4, weight 1, XX:XX:XX +S>* 1.1.1.5/32 [1/0] is directly connected, r1-eth5, weight 1, XX:XX:XX +S>* 1.1.1.6/32 [1/0] is directly connected, r1-eth6, weight 1, XX:XX:XX +S>* 1.1.1.7/32 [1/0] is directly connected, r1-eth7, weight 1, XX:XX:XX +S>* 1.1.1.8/32 [1/0] is directly connected, r1-eth8, weight 1, XX:XX:XX +S>* 4.5.6.10/32 [1/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.11/32 [1/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.12/32 [1/0] is directly connected, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.13/32 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S>* 4.5.6.14/32 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S 4.5.6.15/32 [255/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX +S 4.5.6.16/32 [10/0] via 192.168.0.4, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.16/32 [5/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.17/32 [1/0] via 192.168.0.2, r1-eth0, weight 1, XX:XX:XX +S>* 4.5.6.7/32 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S>* 4.5.6.8/32 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S>* 4.5.6.9/32 [1/0] unreachable (ICMP unreachable), weight 1, XX:XX:XX diff --git a/tests/topotests/all_protocol_startup/r1/ipv6_nht.ref b/tests/topotests/all_protocol_startup/r1/ipv6_nht.ref new file mode 100644 index 0000000..100a36a --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ipv6_nht.ref @@ -0,0 +1,15 @@ +VRF default: + Resolve via default: on +fc00::2 + resolved via connected + is directly connected, r1-eth0 (vrf default) + Client list: static(fd XX) +fc00:0:0:8::1000 + resolved via connected + is directly connected, r1-eth8 (vrf default) + Client list: bgp(fd XX) +fc00:0:0:8::2000(Connected) + resolved via connected + is directly connected, r1-eth8 (vrf default) + Client list: bgp(fd XX) + diff --git a/tests/topotests/all_protocol_startup/r1/ipv6_routes.ref b/tests/topotests/all_protocol_startup/r1/ipv6_routes.ref new file mode 100644 index 0000000..a25b53a --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ipv6_routes.ref @@ -0,0 +1,39 @@ +C>* fc00:0:0:1::/64 is directly connected, r1-eth1, XX:XX:XX +C>* fc00:0:0:2::/64 is directly connected, r1-eth2, XX:XX:XX +C>* fc00:0:0:3::/64 is directly connected, r1-eth3, XX:XX:XX +C>* fc00:0:0:4::/64 is directly connected, r1-eth4, XX:XX:XX +C>* fc00:0:0:5::/64 is directly connected, r1-eth5, XX:XX:XX +C>* fc00:0:0:6::/64 is directly connected, r1-eth6, XX:XX:XX +C>* fc00:0:0:7::/64 is directly connected, r1-eth7, XX:XX:XX +C>* fc00:0:0:8::/64 is directly connected, r1-eth8, XX:XX:XX +C>* fc00:0:0:9::/64 is directly connected, r1-eth9, XX:XX:XX +C>* fc00::/64 is directly connected, r1-eth0, XX:XX:XX +C>* fe80::/64 is directly connected, lo, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth0, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth1, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth2, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth3, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth4, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth5, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth6, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth7, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth8, XX:XX:XX +C * fe80::/64 is directly connected, r1-eth9, XX:XX:XX +L>* fc00:0:0:1::1/128 is directly connected, r1-eth1, XX:XX:XX +L>* fc00:0:0:2::1/128 is directly connected, r1-eth2, XX:XX:XX +L>* fc00:0:0:3::1/128 is directly connected, r1-eth3, XX:XX:XX +L>* fc00:0:0:4::1/128 is directly connected, r1-eth4, XX:XX:XX +L>* fc00:0:0:5::1/128 is directly connected, r1-eth5, XX:XX:XX +L>* fc00:0:0:6::1/128 is directly connected, r1-eth6, XX:XX:XX +L>* fc00:0:0:7::1/128 is directly connected, r1-eth7, XX:XX:XX +L>* fc00:0:0:8::1/128 is directly connected, r1-eth8, XX:XX:XX +L>* fc00:0:0:9::1/128 is directly connected, r1-eth9, XX:XX:XX +L>* fc00::1/128 is directly connected, r1-eth0, XX:XX:XX +O fc00:0:0:4::/64 [110/10] is directly connected, r1-eth4, weight 1, XX:XX:XX +S>* 4:5::6:10/128 [1/0] via fc00::2, r1-eth0, weight 1, XX:XX:XX +S>* 4:5::6:11/128 [1/0] via fc00::2, r1-eth0, weight 1, XX:XX:XX +S>* 4:5::6:12/128 [1/0] is directly connected, r1-eth0, weight 1, XX:XX:XX +S 4:5::6:15/128 [255/0] via fc00::2, r1-eth0, weight 1, XX:XX:XX +S>* 4:5::6:7/128 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S>* 4:5::6:8/128 [1/0] unreachable (blackhole), weight 1, XX:XX:XX +S>* 4:5::6:9/128 [1/0] unreachable (ICMP unreachable), weight 1, XX:XX:XX diff --git a/tests/topotests/all_protocol_startup/r1/isisd.conf b/tests/topotests/all_protocol_startup/r1/isisd.conf new file mode 100644 index 0000000..8ceded8 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/isisd.conf @@ -0,0 +1,21 @@ +log file isisd.log +! +! debug isis events +! +! +interface r1-eth5 + ip router isis test + isis circuit-type level-1 +! +interface r1-eth6 + ipv6 router isis test + isis circuit-type level-2-only +! +! +router isis test + net 00.0001.00b0.64bc.43a0.00 + metric-style wide + log-adjacency-changes +! +line vty +! diff --git a/tests/topotests/all_protocol_startup/r1/ldpd.conf b/tests/topotests/all_protocol_startup/r1/ldpd.conf new file mode 100644 index 0000000..2358fb8 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ldpd.conf @@ -0,0 +1,25 @@ +log file ldpd.log +! +! debug mpls ldp event +! debug mpls ldp zebra +! +! +mpls ldp + router-id 192.168.0.1 + ! + address-family ipv4 + discovery transport-address 192.168.9.1 + ! + interface r1-eth9 + ! + ! + address-family ipv6 + discovery transport-address fc00:0:0:9::1 + ! + interface r1-eth9 + ! + ! +! +! +line vty +! diff --git a/tests/topotests/all_protocol_startup/r1/nhrpd.conf b/tests/topotests/all_protocol_startup/r1/nhrpd.conf new file mode 100644 index 0000000..74e0f12 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/nhrpd.conf @@ -0,0 +1 @@ +! \ No newline at end of file diff --git a/tests/topotests/all_protocol_startup/r1/ospf6d.conf b/tests/topotests/all_protocol_startup/r1/ospf6d.conf new file mode 100644 index 0000000..31c904b --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ospf6d.conf @@ -0,0 +1,20 @@ +log file ospf6d.log +! +! debug ospf6 lsa unknown +! debug ospf6 zebra +! debug ospf6 interface +! debug ospf6 neighbor +! +interface r1-eth4 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 1 +! +router ospf6 + ospf6 router-id 192.168.0.1 + log-adjacency-changes +! +line vty +! +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4 b/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4 new file mode 100644 index 0000000..9ce2f2e --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ospf6d.conf-pre-v4 @@ -0,0 +1,16 @@ +log file ospf6d.log +! +!debug ospf6 lsa unknown +!debug ospf6 zebra +!debug ospf6 interface +!debug ospf6 neighbor +! +interface r1-eth4 +! +router ospf6 + router-id 192.168.0.1 + log-adjacency-changes + interface r1-eth4 area 0.0.0.0 +! +line vty +! diff --git a/tests/topotests/all_protocol_startup/r1/ospfd.conf b/tests/topotests/all_protocol_startup/r1/ospfd.conf new file mode 100644 index 0000000..61af44a --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ospfd.conf @@ -0,0 +1,25 @@ +log file ospfd.log +! +! debug ospf event +! debug ospf zebra +! +! +interface r1-eth0 + ip ospf hello-interval 1 + ip ospf dead-interval 5 +! +interface r1-eth3 + ip ospf hello-interval 1 + ip ospf dead-interval 5 +! +router ospf + ospf router-id 192.168.0.1 + log-adjacency-changes + network 192.168.0.0/24 area 0.0.0.0 + network 192.168.3.0/24 area 0.0.0.0 +! +line vty +! +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/r1/pbrd.conf b/tests/topotests/all_protocol_startup/r1/pbrd.conf new file mode 100644 index 0000000..360fb13 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/pbrd.conf @@ -0,0 +1,10 @@ +log file pbrd.log + +nexthop-group A + nexthop 192.168.161.4 +! +pbr-map FOO seq 10 + match dst-ip 4.5.6.7/32 + match src-ip 6.7.8.8/32 + set nexthop-group A +! \ No newline at end of file diff --git a/tests/topotests/all_protocol_startup/r1/rip_status.ref b/tests/topotests/all_protocol_startup/r1/rip_status.ref new file mode 100644 index 0000000..4a5255f --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/rip_status.ref @@ -0,0 +1,15 @@ +Routing Protocol is "rip" + Sending updates every 30 seconds with +/-50%, next due in XX seconds + Timeout after 180 seconds, garbage collect after 120 seconds + Outgoing update filter list for all interface is not set + Incoming update filter list for all interface is not set + Default redistribution metric is 1 + Redistributing: + Default version control: send version 2, receive version 2 + Interface Send Recv Key-chain + r1-eth1 2 2 + Routing for Networks: + 192.168.1.0/26 + Routing Information Sources: + Gateway BadPackets BadRoutes Distance Last Update + Distance: (default is 120) diff --git a/tests/topotests/all_protocol_startup/r1/ripd.conf b/tests/topotests/all_protocol_startup/r1/ripd.conf new file mode 100644 index 0000000..0a06794 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ripd.conf @@ -0,0 +1,15 @@ +log file ripd.log +! +! debug rip events +! debug rip zebra +! +router rip + version 2 + network 192.168.1.0/26 +! +line vty +! + +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/r1/ripng_status.ref b/tests/topotests/all_protocol_startup/r1/ripng_status.ref new file mode 100644 index 0000000..5d67c14 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ripng_status.ref @@ -0,0 +1,14 @@ +Routing Protocol is "RIPng" + Sending updates every 30 seconds with +/-50%, next due in XX seconds + Timeout after 180 seconds, garbage collect after 120 seconds + Outgoing update filter list for all interface is not set + Incoming update filter list for all interface is not set + Default redistribution metric is 1 + Redistributing: + Default version control: send version 1, receive version 1 + Interface Send Recv + r1-eth2 1 1 + Routing for Networks: + fc00:0:0:2::/64 + Routing Information Sources: + Gateway BadPackets BadRoutes Distance Last Update diff --git a/tests/topotests/all_protocol_startup/r1/ripngd.conf b/tests/topotests/all_protocol_startup/r1/ripngd.conf new file mode 100644 index 0000000..d9d900f --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/ripngd.conf @@ -0,0 +1,14 @@ +log file ripngd.log +! +! debug ripng events +! debug ripng zebra +! +router ripng + network fc00:0:0:2::/64 +! +line vty +! + +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post4.1.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post4.1.ref new file mode 100644 index 0000000..eb4e51a --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post4.1.ref @@ -0,0 +1,9 @@ +BGP table version is 1, local router ID is 192.168.0.1, vrf id 0 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self +Origin codes: i - IGP, e - EGP, ? - incomplete +RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 192.168.0.0 0.0.0.0 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post5.0.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post5.0.ref new file mode 100644 index 0000000..8fe3ea4 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post5.0.ref @@ -0,0 +1,9 @@ +BGP table version is 1, local router ID is 192.168.0.1, vrf id 0 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self +Origin codes: i - IGP, e - EGP, ? - incomplete +RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 192.168.0.0/24 0.0.0.0 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post6.1.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post6.1.ref new file mode 100644 index 0000000..67b9071 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4-post6.1.ref @@ -0,0 +1,10 @@ +BGP table version is 1, local router ID is 192.168.0.1, vrf id 0 +Default local pref 100, local AS 100 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self +Origin codes: i - IGP, e - EGP, ? - incomplete +RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> 192.168.0.0/24 0.0.0.0 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4.ref new file mode 100644 index 0000000..4f21a57 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv4.ref @@ -0,0 +1,7 @@ +BGP table version is 1, local router ID is 192.168.0.1 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Origin codes: i - IGP, e - EGP, ? - incomplete + + Network Next Hop Metric LocPrf Weight Path + *> 192.168.0.0 0.0.0.0 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6-post4.1.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6-post4.1.ref new file mode 100644 index 0000000..69e44e7 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6-post4.1.ref @@ -0,0 +1,9 @@ +BGP table version is 1, local router ID is 192.168.0.1, vrf id 0 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self +Origin codes: i - IGP, e - EGP, ? - incomplete +RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> fc00::/64 :: 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6.ref new file mode 100644 index 0000000..77aab38 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6.ref @@ -0,0 +1,7 @@ +BGP table version is 1, local router ID is 192.168.0.1 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Origin codes: i - IGP, e - EGP, ? - incomplete + + Network Next Hop Metric LocPrf Weight Path + *> fc00::/64 :: 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_post6.1.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_post6.1.ref new file mode 100644 index 0000000..a99400a --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_post6.1.ref @@ -0,0 +1,10 @@ +BGP table version is 1, local router ID is 192.168.0.1, vrf id 0 +Default local pref 100, local AS 100 +Status codes: s suppressed, d damped, h history, u unsorted, * valid, > best, = multipath, + i internal, r RIB-failure, S Stale, R Removed +Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self +Origin codes: i - IGP, e - EGP, ? - incomplete +RPKI validation codes: V valid, I invalid, N Not found + + Network Next Hop Metric LocPrf Weight Path + *> fc00::/64 :: 0 32768 i diff --git a/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_summary.ref b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_summary.ref new file mode 100644 index 0000000..4464e23 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_bgp_ipv6_summary.ref @@ -0,0 +1,8 @@ +BGP router identifier 192.168.0.1, local AS number 100 VRF default vrf-id 0 +BGP table version 1 +RIB entries 1, using XXXX bytes of memory +Peers 2, using XXXX KiB of memory + +Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc +fc00:0:0:8::1000 4 100 0 0 0 0 0 never Active 0 Transit_cogent_v6 +fc00:0:0:8::2000 4 200 0 0 0 0 0 never Active 0 Client_Toto_default diff --git a/tests/topotests/all_protocol_startup/r1/show_ip_bgp_summary.ref b/tests/topotests/all_protocol_startup/r1/show_ip_bgp_summary.ref new file mode 100644 index 0000000..9baec12 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_ip_bgp_summary.ref @@ -0,0 +1,10 @@ +BGP router identifier 192.168.0.1, local AS number 100 VRF default vrf-id 0 +BGP table version 1 +RIB entries 1, using XXXX bytes of memory +Peers 4, using XXXX KiB of memory + +Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc +192.168.7.10 4 100 0 0 0 0 0 never Active 0 Transit_cogent +192.168.7.20 4 200 0 0 0 0 0 never Active 0 Client_Bibi_Full +fc00:0:0:8::1000 4 100 0 0 0 0 0 never Active 0 Transit_cogent_v6 +fc00:0:0:8::2000 4 200 0 0 0 0 0 never Active 0 Client_Toto_default diff --git a/tests/topotests/all_protocol_startup/r1/show_ip_ospf_interface.ref b/tests/topotests/all_protocol_startup/r1/show_ip_ospf_interface.ref new file mode 100644 index 0000000..f52b51d --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_ip_ospf_interface.ref @@ -0,0 +1,26 @@ +r1-eth0 is up + ifindex X, MTU 1500 bytes, BW XX Mbit + Internet Address 192.168.0.1/24, Broadcast 192.168.0.255, Area 0.0.0.0 + MTU mismatch detection: enabled + Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10 + Transmit Delay is 1 sec, State DR, Priority 1 + Designated Router (ID) 192.168.0.1 Interface Address 192.168.0.1/24 + No backup designated router on this network + Multicast group memberships: OSPFAllRouters OSPFDesignatedRouters + Timer intervals configured, Hello 1s, Dead 5s, Wait 5s, Retransmit 5 + Hello due in XX.XXXs + Neighbor Count is 0, Adjacent neighbor count is 0 + Graceful Restart hello delay: 10s +r1-eth3 is up + ifindex X, MTU 1500 bytes, BW XX Mbit + Internet Address 192.168.3.1/26, Broadcast 192.168.3.63, Area 0.0.0.0 + MTU mismatch detection: enabled + Router ID 192.168.0.1, Network Type BROADCAST, Cost: 10 + Transmit Delay is 1 sec, State DR, Priority 1 + Designated Router (ID) 192.168.0.1 Interface Address 192.168.3.1/26 + No backup designated router on this network + Multicast group memberships: OSPFAllRouters OSPFDesignatedRouters + Timer intervals configured, Hello 1s, Dead 5s, Wait 5s, Retransmit 5 + Hello due in XX.XXXs + Neighbor Count is 0, Adjacent neighbor count is 0 + Graceful Restart hello delay: 10s diff --git a/tests/topotests/all_protocol_startup/r1/show_ipv6_ospf6_interface b/tests/topotests/all_protocol_startup/r1/show_ipv6_ospf6_interface new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/all_protocol_startup/r1/show_ipv6_ospf6_interface.ref b/tests/topotests/all_protocol_startup/r1/show_ipv6_ospf6_interface.ref new file mode 100644 index 0000000..6fbf40b --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_ipv6_ospf6_interface.ref @@ -0,0 +1,46 @@ +lo is up, type LOOPBACK + Interface ID: 1 + OSPF not enabled on this interface +r1-eth0 is up, type BROADCAST + Interface ID: 2 + OSPF not enabled on this interface +r1-eth1 is up, type BROADCAST + Interface ID: 3 + OSPF not enabled on this interface +r1-eth2 is up, type BROADCAST + Interface ID: 4 + OSPF not enabled on this interface +r1-eth3 is up, type BROADCAST + Interface ID: 5 + OSPF not enabled on this interface +r1-eth4 is up, type BROADCAST + Interface ID: 6 + Internet Address: + inet : 192.168.4.1/26 + inet6: fc00:0:0:4::1/64 + inet6: fe80::XXXX:XXXX:XXXX:XXXX/64 + Instance ID 0, Interface MTU 1500 (autodetect: 1500) + MTU mismatch detection: enabled + Area ID 0.0.0.0, Cost 10 + State DR, Transmit Delay 1 sec, Priority 1 + Timer intervals configured: + Hello 10, Dead 40, Retransmit 5 + DR: 192.168.0.1 BDR: 0.0.0.0 + Number of I/F scoped LSAs is 1 + 0 Pending LSAs for LSUpdate in Time 00:00:00 [thread off] + 0 Pending LSAs for LSAck in Time 00:00:00 [thread off] +r1-eth5 is up, type BROADCAST + Interface ID: 7 + OSPF not enabled on this interface +r1-eth6 is up, type BROADCAST + Interface ID: 8 + OSPF not enabled on this interface +r1-eth7 is up, type BROADCAST + Interface ID: 9 + OSPF not enabled on this interface +r1-eth8 is up, type BROADCAST + Interface ID: 10 + OSPF not enabled on this interface +r1-eth9 is up, type BROADCAST + Interface ID: 11 + OSPF not enabled on this interface diff --git a/tests/topotests/all_protocol_startup/r1/show_isis_interface_detail.ref b/tests/topotests/all_protocol_startup/r1/show_isis_interface_detail.ref new file mode 100644 index 0000000..8659700 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_isis_interface_detail.ref @@ -0,0 +1,28 @@ +Area test: + Interface: r1-eth5, State: Up, Active, Circuit Id: 0xXX + Type: lan, Level: L1, SNPA: XXXX.XXXX.XXXX + Level-1 Information: + Metric: 10, Active neighbors: 0 + Hello interval: 3, Holddown count: 10, Padding: yes + CNSP interval: 10, PSNP interval: 2 + LAN Priority: 64, is not DIS + IP Prefix(es): + 192.168.5.1/26 + IPv6 Link-Locals: + fe80::XXXX:XXXX:XXXX:XXXX/64 + IPv6 Prefixes: + fc00:0:0:5::1/64 + + Interface: r1-eth6, State: Up, Active, Circuit Id: 0xXX + Type: lan, Level: L2, SNPA: XXXX.XXXX.XXXX + Level-2 Information: + Metric: 10, Active neighbors: 0 + Hello interval: 3, Holddown count: 10, Padding: yes + CNSP interval: 10, PSNP interval: 2 + LAN Priority: 64, is not DIS + IP Prefix(es): + 192.168.6.1/26 + IPv6 Link-Locals: + fe80::XXXX:XXXX:XXXX:XXXX/64 + IPv6 Prefixes: + fc00:0:0:6::1/64 diff --git a/tests/topotests/all_protocol_startup/r1/show_mpls_ldp_interface.ref b/tests/topotests/all_protocol_startup/r1/show_mpls_ldp_interface.ref new file mode 100644 index 0000000..c6bb01c --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_mpls_ldp_interface.ref @@ -0,0 +1,3 @@ +AF Interface State Uptime Hello Timers ac +ipv4 r1-eth9 ACTIVE xx:xx:xx 5/15 0 +ipv6 r1-eth9 ACTIVE xx:xx:xx 5/15 0 diff --git a/tests/topotests/all_protocol_startup/r1/show_route_map.ref b/tests/topotests/all_protocol_startup/r1/show_route_map.ref new file mode 100644 index 0000000..612d0a7 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/show_route_map.ref @@ -0,0 +1,72 @@ +ZEBRA: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +RIP: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +RIPNG: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +OSPF: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +OSPF6: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +BGP: +route-map: LIES Invoked: 0 Optimization: enabled Processed Change: false + deny, sequence 10 Invoked 0 + Match clauses: + interface notpresent + Set clauses: + Call clause: + Action: + Exit routemap +route-map: bgp-map Invoked: 0 Optimization: enabled Processed Change: false + permit, sequence 10 Invoked 0 + Match clauses: + Set clauses: + community 100:100 additive + local-preference 100 + Call clause: + Action: + Exit routemap + permit, sequence 20 Invoked 0 + Match clauses: + Set clauses: + metric 10 + local-preference 200 + Call clause: + Action: + Exit routemap +ISIS: diff --git a/tests/topotests/all_protocol_startup/r1/zebra.conf b/tests/topotests/all_protocol_startup/r1/zebra.conf new file mode 100644 index 0000000..c5ef796 --- /dev/null +++ b/tests/topotests/all_protocol_startup/r1/zebra.conf @@ -0,0 +1,120 @@ +log file zebra.log +! +hostname r1 +! +# Create the various blackhole route types +ip route 4.5.6.7/32 blackhole +ipv6 route 4:5::6:7/128 blackhole +ip route 4.5.6.8/32 Null0 +ipv6 route 4:5::6:8/128 Null0 +ip route 4.5.6.9/32 reject +ipv6 route 4:5::6:9/128 reject +# Test various spellings of NULL0 to make sure we accept them +ip route 4.5.6.13/32 null0 +ip route 4.5.6.14/32 NULL0 +# Create normal gateway routes +ip route 4.5.6.10/32 192.168.0.2 +ipv6 route 4:5::6:10/128 fc00:0:0:0::2 +# Create normal gateway + interface routes +ip route 4.5.6.11/32 192.168.0.2 r1-eth0 +ipv6 route 4:5::6:11/128 fc00:0:0:0::2 r1-eth0 +# Create ifname routes +ip route 4.5.6.12/32 r1-eth0 +ipv6 route 4:5::6:12/128 r1-eth0 +# Create a route that has a large admin distance +# an admin distance of 255 should be accepted +# by zebra but not installed. +ip route 4.5.6.15/32 192.168.0.2 255 +ipv6 route 4:5::6:15/128 fc00:0:0:0::2 255 +# Routes to put into a nexthop-group +ip route 1.1.1.1/32 r1-eth1 +ip route 1.1.1.2/32 r1-eth2 +ip route 1.1.1.3/32 r1-eth3 +ip route 1.1.1.4/32 r1-eth4 +ip route 1.1.1.5/32 r1-eth5 +ip route 1.1.1.6/32 r1-eth6 +ip route 1.1.1.7/32 r1-eth7 +ip route 1.1.1.8/32 r1-eth8 + +# Create a route that has overlapping distance +# so we have backups +ip route 4.5.6.16/32 192.168.0.2 5 +ip route 4.5.6.16/32 192.168.0.4 10 + +# Create routes that have different tags +# and how we handle it +ip route 4.5.6.17/32 192.168.0.2 tag 9000 +ip route 4.5.6.17/32 192.168.0.2 tag 10000 + +! +interface r1-eth0 + description to sw0 - no routing protocol + ip address 192.168.0.1/24 + ipv6 address fc00:0:0:0::1/64 +! +interface r1-eth1 + description to sw1 - RIP interface + ip address 192.168.1.1/26 + ipv6 address fc00:0:0:1::1/64 + no link-detect +! +interface r1-eth2 + description to sw2 - RIPng interface + ip address 192.168.2.1/26 + ipv6 address fc00:0:0:2::1/64 + no link-detect +! +interface r1-eth3 + description to sw3 - OSPFv2 interface + ip address 192.168.3.1/26 + ipv6 address fc00:0:0:3::1/64 + no link-detect +! +interface r1-eth4 + description to sw4 - OSPFv3 interface + ip address 192.168.4.1/26 + ipv6 address fc00:0:0:4::1/64 + no link-detect +! +interface r1-eth5 + description to sw5 - ISIS IPv4 interface + ip address 192.168.5.1/26 + ipv6 address fc00:0:0:5::1/64 + no link-detect +! +interface r1-eth6 + description to sw6 - ISIS IPv6 interface + ip address 192.168.6.1/26 + ipv6 address fc00:0:0:6::1/64 + no link-detect +! +interface r1-eth7 + description to sw7 - BGP IPv4 interface + ip address 192.168.7.1/26 + ipv6 address fc00:0:0:7::1/64 + no link-detect +! +interface r1-eth8 + description to sw8 - BGP IPv6 interface + ip address 192.168.8.1/26 + ipv6 address fc00:0:0:8::1/64 + no link-detect +! +interface r1-eth9 + description to sw9 - LDP interface + ip address 192.168.9.1/26 + ipv6 address fc00:0:0:9::1/64 + + no link-detect +! +! +ip forwarding +ipv6 forwarding +! +! +line vty +! + +route-map LIES deny 10 + match interface notpresent +! diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.dot b/tests/topotests/all_protocol_startup/test_all_protocol_startup.dot new file mode 100644 index 0000000..f39f8f8 --- /dev/null +++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.dot @@ -0,0 +1,61 @@ +## GraphViz file for test_all_protocol_startup +## +## Color coding: +######################### +## Main FRR: #f08080 red +## No protocol: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #33ff99 light green +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +## LDP IPv4 #fedbe2 light pink +##### Colors (see http://www.color-hex.com/) + +graph test_all_protocol_startup { + + // title + labelloc="t"; + label="Test Topologoy All Protocols Startup"; + + ###################### + # Routers + ###################### + + # Main FRR Router with all protocols + R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled]; + + ###################### + # Network Lists + ###################### + + SW0_STUB [label="SW0 (no protocol)\n192.168.1.0/24\nfc00:0:0:0::/64", fillcolor="#d0e0d0", style=filled]; + + SW1_RIP [label="SW1 RIP\n192.168.1.0/24\nfc00:0:0:1::/64", fillcolor="#19e3d9", style=filled]; + SW2_RIPNG [label="SW2 RIPng\n192.168.2.0/24\nfc00:0:0:2::/64", fillcolor="#fcb314", style=filled]; + SW3_OSPF [label="SW3 OSPFv2\n192.168.3.0/24\nfc00:0:0:3::/64", fillcolor="#32b835", style=filled]; + SW4_OSPFV3 [label="SW4 OSPFv3\n192.168.4.0/24\nfc00:0:0:4::/64", fillcolor="#19e3d9", style=filled]; + SW5_ISIS_V4 [label="SW5 ISIS IPv4\n192.168.5.0/24\nfc00:0:0:5::/64", fillcolor="#33ff99", style=filled]; + SW6_ISIS_V6 [label="SW6 ISIS IPv6\n192.168.6.0/24\nfc00:0:0:6::/64", fillcolor="#9a81ec", style=filled]; + SW7_BGP_V4 [label="SW7 BGP IPv4\n192.168.7.0/24\nfc00:0:0:7::/64", fillcolor="#eee3d3", style=filled]; + SW8_BGP_V6 [label="SW8 BGP IPv6\n192.168.8.0/24\nfc00:0:0:8::/64", fillcolor="#fdff00", style=filled]; + SW9_LDP [label="SW9 LDP\n192.168.9.0/24\nfc00:0:0:9::/64", fillcolor="#fedbe2", style=filled]; + + ###################### + # Network Connections + ###################### + R1 -- SW0_STUB [label = "eth0\n.1\n::1"]; + R1 -- SW1_RIP [label = "eth1\n.1\n::1"]; + R1 -- SW2_RIPNG [label = "eth2\n.1\n::1"]; + R1 -- SW3_OSPF [label = "eth3\n.1\n::1"]; + R1 -- SW4_OSPFV3 [label = "eth4\n.1\n::1"]; + R1 -- SW5_ISIS_V4 [label = "eth5\n.1\n::1"]; + R1 -- SW6_ISIS_V6 [label = "eth6\n.1\n::1"]; + R1 -- SW7_BGP_V4 [label = "eth7\n.1\n::1"]; + R1 -- SW8_BGP_V6 [label = "eth8\n.1\n::1"]; + R1 -- SW9_LDP [label = "eth9\n.1\n::1"]; + +} diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.pdf b/tests/topotests/all_protocol_startup/test_all_protocol_startup.pdf new file mode 100644 index 0000000..23f69bc Binary files /dev/null and b/tests/topotests/all_protocol_startup/test_all_protocol_startup.pdf differ diff --git a/tests/topotests/all_protocol_startup/test_all_protocol_startup.py b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py new file mode 100644 index 0000000..b4bc1e1 --- /dev/null +++ b/tests/topotests/all_protocol_startup/test_all_protocol_startup.py @@ -0,0 +1,1771 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_all_protocol_startup.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_all_protocol_startup.py: Test of all protocols at same time + +""" + +import os +import re +import sys +import pytest +import glob +from time import sleep + +pytestmark = [ + pytest.mark.babeld, + pytest.mark.bgpd, + pytest.mark.isisd, + pytest.mark.nhrpd, + pytest.mark.ospfd, + pytest.mark.pbrd, + pytest.mark.ripd, +] + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from lib import topotest +from lib.topogen import Topogen, get_topogen +from lib.common_config import ( + required_linux_kernel_version, +) + +from lib.topolog import logger +import json + +fatal_error = "" + + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + router = tgen.add_router("r1") + for i in range(0, 10): + tgen.add_switch("sw{}".format(i)).add_link(router) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + global fatal_error + + print("\n\n** {}: Setup Topology".format(module.__name__)) + print("******************************************\n") + + thisDir = os.path.dirname(os.path.realpath(__file__)) + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + net = tgen.net + + if net["r1"].get_routertype() != "frr": + fatal_error = "Test is only implemented for FRR" + sys.stderr.write("\n\nTest is only implemented for FRR - Skipping\n\n") + pytest.skip(fatal_error) + + # Starting Routers + # + # Main router + for i in range(1, 2): + net["r{}".format(i)].loadConf("mgmtd", "{}/r{}/zebra.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("zebra", "{}/r{}/zebra.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("ripd", "{}/r{}/ripd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("ripngd", "{}/r{}/ripngd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("ospfd", "{}/r{}/ospfd.conf".format(thisDir, i)) + if net["r1"].checkRouterVersion("<", "4.0"): + net["r{}".format(i)].loadConf( + "ospf6d", "{}/r{}/ospf6d.conf-pre-v4".format(thisDir, i) + ) + else: + net["r{}".format(i)].loadConf( + "ospf6d", "{}/r{}/ospf6d.conf".format(thisDir, i) + ) + net["r{}".format(i)].loadConf("isisd", "{}/r{}/isisd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("bgpd", "{}/r{}/bgpd.conf".format(thisDir, i)) + if net["r{}".format(i)].daemon_available("ldpd"): + # Only test LDPd if it's installed and Kernel >= 4.5 + net["r{}".format(i)].loadConf("ldpd", "{}/r{}/ldpd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("sharpd") + net["r{}".format(i)].loadConf("nhrpd", "{}/r{}/nhrpd.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("babeld", "{}/r{}/babeld.conf".format(thisDir, i)) + net["r{}".format(i)].loadConf("pbrd", "{}/r{}/pbrd.conf".format(thisDir, i)) + tgen.gears["r{}".format(i)].start() + + # For debugging after starting FRR daemons, uncomment the next line + # tgen.mininet_cli() + + +def teardown_module(module): + print("\n\n** {}: Shutdown Topology".format(module.__name__)) + print("******************************************\n") + tgen = get_topogen() + tgen.stop_topology() + + +def test_router_running(): + global fatal_error + tgen = get_topogen() + net = tgen.net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Check if FRR is running on each Router node") + print("******************************************\n") + sleep(5) + + # Starting Routers + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + # For debugging after starting FRR daemons, uncomment the next line + # tgen.mininet_cli() + + +def test_error_messages_vtysh(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Check for error messages on VTYSH") + print("******************************************\n") + + failures = 0 + for i in range(1, 2): + # + # First checking Standard Output + # + + # VTYSH output from router + vtystdout = ( + net["r{}".format(i)].cmd('vtysh -c "show version" 2> /dev/null').rstrip() + ) + + # Fix newlines (make them all the same) + vtystdout = ("\n".join(vtystdout.splitlines()) + "\n").rstrip() + # Drop everything starting with "FRRouting X.xx" message + vtystdout = re.sub(r"FRRouting [0-9]+.*", "", vtystdout, flags=re.DOTALL) + + if vtystdout == "": + print("r{} StdOut ok".format(i)) + + assert ( + vtystdout == "" + ), "Vtysh StdOut Output check failed for router r{}".format(i) + + # + # Second checking Standard Error + # + + # VTYSH StdErr output from router + vtystderr = ( + net["r{}".format(i)].cmd('vtysh -c "show version" > /dev/null').rstrip() + ) + + # Fix newlines (make them all the same) + vtystderr = ("\n".join(vtystderr.splitlines()) + "\n").rstrip() + # # Drop everything starting with "FRRouting X.xx" message + # vtystderr = re.sub(r"FRRouting [0-9]+.*", "", vtystderr, flags=re.DOTALL) + + if vtystderr == "": + print("r{} StdErr ok".format(i)) + + assert ( + vtystderr == "" + ), "Vtysh StdErr Output check failed for router r{}".format(i) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_error_messages_daemons(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + print( + "SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n" + ) + pytest.skip("Skipping test for Stderr output") + + print("\n\n** Check for error messages in daemons") + print("******************************************\n") + + error_logs = "" + + for i in range(1, 2): + log = net["r{}".format(i)].getStdErr("ripd") + if log: + error_logs += "r{} RIPd StdErr Output:\n".format(i) + error_logs += log + log = net["r{}".format(i)].getStdErr("ripngd") + if log: + error_logs += "r{} RIPngd StdErr Output:\n".format(i) + error_logs += log + log = net["r{}".format(i)].getStdErr("ospfd") + if log: + error_logs += "r{} OSPFd StdErr Output:\n".format(i) + error_logs += log + log = net["r{}".format(i)].getStdErr("ospf6d") + if log: + error_logs += "r{} OSPF6d StdErr Output:\n".format(i) + error_logs += log + log = net["r{}".format(i)].getStdErr("isisd") + # ISIS shows debugging enabled status on StdErr + # Remove these messages + log = re.sub(r"^IS-IS .* debugging is on.*", "", log).rstrip() + if log: + error_logs += "r{} ISISd StdErr Output:\n".format(i) + error_logs += log + log = net["r{}".format(i)].getStdErr("bgpd") + if log: + error_logs += "r{} BGPd StdErr Output:\n".format(i) + error_logs += log + if net["r{}".format(i)].daemon_available("ldpd"): + log = net["r{}".format(i)].getStdErr("ldpd") + if log: + error_logs += "r{} LDPd StdErr Output:\n".format(i) + error_logs += log + + log = net["r1"].getStdErr("nhrpd") + # NHRPD shows YANG model not embedded messages + # Ignore these + log = re.sub(r".*YANG model.*not embedded.*", "", log).rstrip() + if log: + error_logs += "r{} NHRPd StdErr Output:\n".format(i) + error_logs += log + + log = net["r1"].getStdErr("babeld") + if log: + error_logs += "r{} BABELd StdErr Output:\n".format(i) + error_logs += log + + log = net["r1"].getStdErr("pbrd") + if log: + error_logs += "r{} PBRd StdErr Output:\n".format(i) + error_logs += log + + log = net["r{}".format(i)].getStdErr("zebra") + if log: + error_logs += "r{} Zebra StdErr Output:\n".format(i) + error_logs += log + + if error_logs: + sys.stderr.write( + "Failed check for StdErr Output on daemons:\n{}\n".format(error_logs) + ) + + # Ignoring the issue if told to ignore (ie not yet fixed) + if error_logs != "": + if os.environ.get("bamboo_TOPOTESTS_ISSUE_349") == "IGNORE": + sys.stderr.write( + "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349\n" + ) + pytest.skip( + "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/349" + ) + + assert error_logs == "", "Daemons report errors to StdErr" + + +def test_converge_protocols(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + # We need loopback to have a link local so it always is the + # "selected" router for fe80::/64 when we static compare below. + print("Adding link-local to loopback for stable results") + cmd = ( + "mac=`cat /sys/class/net/lo/address`; echo lo: $mac;" + " [ -z \"$mac\" ] && continue; IFS=':'; set $mac; unset IFS;" + " ip address add dev lo scope link" + " fe80::$(printf %02x $((0x$1 ^ 2)))$2:${3}ff:fe$4:$5$6/64" + ) + net["r1"].cmd_raises(cmd) + + print("\n\n** Waiting for protocols convergence") + print("******************************************\n") + + # Not really implemented yet - just sleep 60 secs for now + sleep(5) + + # Make sure that all daemons are running + failures = 0 + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + print("Show that v4 routes are right\n") + v4_routesFile = "{}/r{}/ipv4_routes.ref".format(thisDir, i) + expected = ( + net["r{}".format(i)] + .cmd("sort {} 2> /dev/null".format(v4_routesFile)) + .rstrip() + ) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + actual = ( + net["r{}".format(i)] + .cmd( + "vtysh -c \"show ip route\" | sed -e '/^Codes: /,/^\\s*$/d' | sort 2> /dev/null" + ) + .rstrip() + ) + # Drop time in last update + actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + diff = topotest.get_textdiff( + actual, + expected, + title1="Actual IP Routing Table", + title2="Expected IP RoutingTable", + ) + if diff: + sys.stderr.write("r{} failed IP Routing table check:\n{}\n".format(i, diff)) + failures += 1 + else: + print("r{} ok".format(i)) + + assert failures == 0, "IP Routing table failed for r{}\n{}".format(i, diff) + + failures = 0 + + print("Show that v6 routes are right\n") + v6_routesFile = "{}/r{}/ipv6_routes.ref".format(thisDir, i) + expected = ( + net["r{}".format(i)] + .cmd("sort {} 2> /dev/null".format(v6_routesFile)) + .rstrip() + ) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + actual = ( + net["r{}".format(i)] + .cmd( + "vtysh -c \"show ipv6 route\" | sed -e '/^Codes: /,/^\\s*$/d' | sort 2> /dev/null" + ) + .rstrip() + ) + # Drop time in last update + actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + diff = topotest.get_textdiff( + actual, + expected, + title1="Actual IPv6 Routing Table", + title2="Expected IPv6 RoutingTable", + ) + if diff: + sys.stderr.write( + "r{} failed IPv6 Routing table check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert failures == 0, "IPv6 Routing table failed for r{}\n{}".format(i, diff) + + +def route_get_nhg_id(route_str): + net = get_topogen().net + output = net["r1"].cmd( + 'vtysh -c "show ip route {} nexthop-group"'.format(route_str) + ) + match = re.search(r"Nexthop Group ID: (\d+)", output) + assert match is not None, "Nexthop Group ID not found for sharpd route {}".format( + route_str + ) + + nhg_id = int(match.group(1)) + return nhg_id + + +def verify_nexthop_group(nhg_id, recursive=False, ecmp=0): + net = get_topogen().net + count = 0 + valid = None + ecmpcount = None + depends = None + resolved_id = None + installed = None + found = False + + while not found and count < 10: + count += 1 + # Verify NHG is valid/installed + output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) + valid = re.search(r"Valid", output) + if valid is None: + found = False + sleep(1) + continue + + if ecmp or recursive: + ecmpcount = re.search(r"Depends:.*\n", output) + if ecmpcount is None: + found = False + sleep(1) + continue + + # list of IDs in group + depends = re.findall(r"\((\d+)\)", ecmpcount.group(0)) + + if ecmp: + if len(depends) != ecmp: + found = False + sleep(1) + continue + else: + # If recursive, we need to look at its resolved group + if len(depends) != 1: + found = False + sleep(1) + continue + + resolved_id = int(depends[0]) + verify_nexthop_group(resolved_id, False) + else: + installed = re.search(r"Installed", output) + if installed is None: + found = False + sleep(1) + continue + found = True + + assert valid is not None, "Nexthop Group ID={} not marked Valid".format(nhg_id) + if ecmp or recursive: + assert ecmpcount is not None, "Nexthop Group ID={} has no depends".format( + nhg_id + ) + if ecmp: + assert ( + len(depends) == ecmp + ), "Nexthop Group ID={} doesn't match ecmp size".format(nhg_id) + else: + assert ( + len(depends) == 1 + ), "Nexthop Group ID={} should only have one recursive depend".format( + nhg_id + ) + else: + assert installed is not None, "Nexthop Group ID={} not marked Installed".format( + nhg_id + ) + + +def verify_route_nexthop_group(route_str, recursive=False, ecmp=0): + # Verify route and that zebra created NHGs for and they are valid/installed + nhg_id = route_get_nhg_id(route_str) + verify_nexthop_group(nhg_id, recursive, ecmp) + + +def test_nexthop_groups(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Verifying Nexthop Groups") + print("******************************************\n") + + ### Nexthop Group Tests + + ## Basic test + + # Create a lib nexthop-group + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group basic" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2"' + ) + + # Create with sharpd using nexthop-group + net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.1 nexthop-group basic 1"') + verify_route_nexthop_group("2.2.2.1/32") + + ## Connected + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group connected" -c "nexthop r1-eth1" -c "nexthop r1-eth2"' + ) + + net["r1"].cmd('vtysh -c "sharp install routes 2.2.2.2 nexthop-group connected 1"') + verify_route_nexthop_group("2.2.2.2/32") + + ## Recursive + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group basic-recursive" -c "nexthop 2.2.2.1"' + ) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 3.3.3.1 nexthop-group basic-recursive 1"' + ) + + verify_route_nexthop_group("3.3.3.1/32", True) + + ## Duplicate + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group duplicate" -c "nexthop 2.2.2.1" -c "nexthop 1.1.1.1"' + ) + + net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.2 nexthop-group duplicate 1"') + + verify_route_nexthop_group("3.3.3.2/32") + + ## Two 4-Way ECMP + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group fourA" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2" \ + -c "nexthop 1.1.1.3" -c "nexthop 1.1.1.4"' + ) + + net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.1 nexthop-group fourA 1"') + + verify_route_nexthop_group("4.4.4.1/32") + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group fourB" -c "nexthop 1.1.1.5" -c "nexthop 1.1.1.6" \ + -c "nexthop 1.1.1.7" -c "nexthop 1.1.1.8"' + ) + + net["r1"].cmd('vtysh -c "sharp install routes 4.4.4.2 nexthop-group fourB 1"') + + verify_route_nexthop_group("4.4.4.2/32") + + ## Recursive to 8-Way ECMP + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group eight-recursive" -c "nexthop 4.4.4.1" -c "nexthop 4.4.4.2"' + ) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 5.5.5.1 nexthop-group eight-recursive 1"' + ) + + verify_route_nexthop_group("5.5.5.1/32") + + ## 4-way ECMP Routes Pointing to Each Other + + # This is to check for a bug with NH resolution where + # routes would infintely resolve to each other blowing + # up the resolved-> nexthop pointer. + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group infinite-recursive" -c "nexthop 6.6.6.1" -c "nexthop 6.6.6.2" \ + -c "nexthop 6.6.6.3" -c "nexthop 6.6.6.4"' + ) + + # static route nexthops can recurse to + + net["r1"].cmd('vtysh -c "c t" -c "ip route 6.6.6.0/24 1.1.1.1"') + + # Make routes that point to themselves in ecmp + + net["r1"].cmd( + 'vtysh -c "sharp install routes 6.6.6.4 nexthop-group infinite-recursive 1"' + ) + sleep(5) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 6.6.6.3 nexthop-group infinite-recursive 1"' + ) + sleep(5) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 6.6.6.2 nexthop-group infinite-recursive 1"' + ) + sleep(5) + + net["r1"].cmd( + 'vtysh -c "sharp install routes 6.6.6.1 nexthop-group infinite-recursive 1"' + ) + + # Get routes and test if has too many (duplicate) nexthops + count = 0 + dups = [] + nhg_id = route_get_nhg_id("6.6.6.1/32") + while (len(dups) != 4) and count < 10: + output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) + + dups = re.findall(r"(via 1\.1\.1\.1)", output) + if len(dups) != 4: + count += 1 + sleep(1) + + # Should find 3, itself is inactive + assert ( + len(dups) == 4 + ), "Route 6.6.6.1/32 with Nexthop Group ID={} has wrong number of resolved nexthops".format( + nhg_id + ) + + ## Remove all NHG routes + + net["r1"].cmd('vtysh -c "sharp remove routes 2.2.2.1 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 2.2.2.2 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 3.3.3.1 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 3.3.3.2 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 4.4.4.1 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 4.4.4.2 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 5.5.5.1 1"') + net["r1"].cmd('vtysh -c "sharp remove routes 6.6.6.1 4"') + net["r1"].cmd('vtysh -c "c t" -c "no ip route 6.6.6.0/24 1.1.1.1"') + + +def test_rip_status(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying RIP status") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/rip_status.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show ip rip status" 2> /dev/null') + .rstrip() + ) + # Drop time in next due + actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual) + # Drop time in last update + actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual) + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual IP RIP status", + title2="expected IP RIP status", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed IP RIP status check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert failures == 0, "IP RIP status failed for router r{}:\n{}".format( + i, diff + ) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_ripng_status(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying RIPng status") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/ripng_status.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show ipv6 ripng status" 2> /dev/null') + .rstrip() + ) + # Mask out Link-Local mac address portion. They are random... + actual = re.sub(r" fe80::[0-9a-f:]+", " fe80::XXXX:XXXX:XXXX:XXXX", actual) + # Drop time in next due + actual = re.sub(r"in [0-9]+ seconds", "in XX seconds", actual) + # Drop time in last update + actual = re.sub(r" [0-2][0-9]:[0-5][0-9]:[0-5][0-9]", " XX:XX:XX", actual) + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual IPv6 RIPng status", + title2="expected IPv6 RIPng status", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed IPv6 RIPng status check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert failures == 0, "IPv6 RIPng status failed for router r{}:\n{}".format( + i, diff + ) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_ospfv2_interfaces(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying OSPFv2 interfaces") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/show_ip_ospf_interface.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show ip ospf interface" 2> /dev/null') + .rstrip() + ) + # Mask out Bandwidth portion. They may change.. + actual = re.sub(r"BW [0-9]+ Mbit", "BW XX Mbit", actual) + actual = re.sub(r"ifindex [0-9]+", "ifindex X", actual) + + # Drop time in next due + actual = re.sub(r"Hello due in [0-9\.]+s", "Hello due in XX.XXXs", actual) + actual = re.sub( + r"Hello due in [0-9\.]+ usecs", "Hello due in XX.XXXs", actual + ) + # Fix 'MTU mismatch detection: enabled' vs 'MTU mismatch detection:enabled' - accept both + actual = re.sub( + r"MTU mismatch detection:([a-z]+.*)", + r"MTU mismatch detection: \1", + actual, + ) + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW IP OSPF INTERFACE", + title2="expected SHOW IP OSPF INTERFACE", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed SHOW IP OSPF INTERFACE check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + # Ignoring the issue if told to ignore (ie not yet fixed) + if failures != 0: + if os.environ.get("bamboo_TOPOTESTS_ISSUE_348") == "IGNORE": + sys.stderr.write( + "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348\n" + ) + pytest.skip( + "Known issue - IGNORING. See https://github.com/FRRouting/frr/issues/348" + ) + + assert ( + failures == 0 + ), "SHOW IP OSPF INTERFACE failed for router r{}:\n{}".format(i, diff) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_isis_interfaces(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying ISIS interfaces") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/show_isis_interface_detail.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show isis interface detail" 2> /dev/null') + .rstrip() + ) + # Mask out Link-Local mac address portion. They are random... + actual = re.sub(r"fe80::[0-9a-f:]+", "fe80::XXXX:XXXX:XXXX:XXXX", actual) + # Mask out SNPA mac address portion. They are random... + actual = re.sub(r"SNPA: [0-9a-f\.]+", "SNPA: XXXX.XXXX.XXXX", actual) + # Mask out Circuit ID number + actual = re.sub(r"Circuit Id: 0x[0-9a-f]+", "Circuit Id: 0xXX", actual) + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW ISIS INTERFACE DETAIL", + title2="expected SHOW ISIS OSPF6 INTERFACE DETAIL", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed SHOW ISIS INTERFACE DETAIL check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert ( + failures == 0 + ), "SHOW ISIS INTERFACE DETAIL failed for router r{}:\n{}".format(i, diff) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_bgp_summary(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying BGP Summary") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/show_ip_bgp_summary.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected_original = open(refTableFile).read().rstrip() + + for arguments in [ + "", + "remote-as internal", + "remote-as external", + "remote-as 100", + "remote-as 123", + "neighbor 192.168.7.10", + "neighbor 192.168.7.10", + "neighbor fc00:0:0:8::1000", + "neighbor 10.0.0.1", + "terse", + "remote-as internal terse", + "remote-as external terse", + "remote-as 100 terse", + "remote-as 123 terse", + "neighbor 192.168.7.10 terse", + "neighbor 192.168.7.10 terse", + "neighbor fc00:0:0:8::1000 terse", + "neighbor 10.0.0.1 terse", + ]: + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd( + 'vtysh -c "show ip bgp summary ' + arguments + '" 2> /dev/null' + ) + .rstrip() + ) + + # Mask out "using XXiXX bytes" portion. They are random... + actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual) + # Mask out "using XiXXX KiB" portion. They are random... + actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual) + + # Remove extra summaries which exist with newer versions + + # Remove summary lines (changed recently) + actual = re.sub(r"Total number.*", "", actual) + actual = re.sub(r"Displayed.*", "", actual) + # Remove IPv4 Unicast Summary (Title only) + actual = re.sub(r"IPv4 Unicast Summary:", "", actual) + # Remove IPv4 Multicast Summary (all of it) + actual = re.sub(r"IPv4 Multicast Summary:", "", actual) + actual = re.sub(r"No IPv4 Multicast neighbor is configured", "", actual) + # Remove IPv4 VPN Summary (all of it) + actual = re.sub(r"IPv4 VPN Summary:", "", actual) + actual = re.sub(r"No IPv4 VPN neighbor is configured", "", actual) + # Remove IPv4 Encap Summary (all of it) + actual = re.sub(r"IPv4 Encap Summary:", "", actual) + actual = re.sub(r"No IPv4 Encap neighbor is configured", "", actual) + # Remove Unknown Summary (all of it) + actual = re.sub(r"Unknown Summary:", "", actual) + actual = re.sub(r"No Unknown neighbor is configured", "", actual) + # Make Connect/Active/Idle the same (change them all to Active) + actual = re.sub(r" Connect ", " Active ", actual) + actual = re.sub(r" Idle ", " Active ", actual) + + actual = re.sub(r"IPv4 labeled-unicast Summary:", "", actual) + actual = re.sub( + r"No IPv4 labeled-unicast neighbor is configured", "", actual + ) + + expected = expected_original + # apply argumentss on expected output + if "internal" in arguments or "remote-as 100" in arguments: + expected = re.sub(r".+\s+200\s+.+", "", expected) + elif "external" in arguments: + expected = re.sub(r".+\s+100\s+.+Active.+", "", expected) + elif "remote-as 123" in arguments: + expected = re.sub( + r"(192.168.7.(1|2)0|fc00:0:0:8::(1|2)000).+Active.+", + "", + expected, + ) + expected = re.sub(r"\nNeighbor.+Desc", "", expected) + expected = expected + "% No matching neighbor\n" + elif "192.168.7.10" in arguments: + expected = re.sub( + r"(192.168.7.20|fc00:0:0:8::(1|2)000).+Active.+", "", expected + ) + elif "fc00:0:0:8::1000" in arguments: + expected = re.sub( + r"(192.168.7.(1|2)0|fc00:0:0:8::2000).+Active.+", "", expected + ) + elif "10.0.0.1" in arguments: + expected = "No such neighbor in this view/vrf" + + if "terse" in arguments: + expected = re.sub(r"BGP table version .+", "", expected) + expected = re.sub(r"RIB entries .+", "", expected) + expected = re.sub(r"Peers [0-9]+, using .+", "", expected) + + # Strip empty lines + actual = actual.lstrip().rstrip() + expected = expected.lstrip().rstrip() + actual = re.sub(r"\n+", "\n", actual) + expected = re.sub(r"\n+", "\n", expected) + + # reapply initial formatting + if "terse" in arguments: + actual = re.sub(r" vrf-id 0\n", " vrf-id 0\n\n", actual) + expected = re.sub(r" vrf-id 0\n", " vrf-id 0\n\n", expected) + else: + actual = re.sub(r"KiB of memory\n", "KiB of memory\n\n", actual) + expected = re.sub(r"KiB of memory\n", "KiB of memory\n\n", expected) + + # realign expected neighbor columns if needed + try: + idx_actual = ( + re.search(r"(Neighbor\s+V\s+)", actual).group(1).find("V") + ) + idx_expected = ( + re.search(r"(Neighbor\s+V\s+)", expected).group(1).find("V") + ) + idx_diff = idx_expected - idx_actual + if idx_diff > 0: + # Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd + expected = re.sub(" " * idx_diff + "V ", "V ", expected) + # 192.168.7.10 4 100 0 0 0 0 0 never Active + expected = re.sub(" " * idx_diff + "4 ", "4 ", expected) + except AttributeError: + pass + + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW IP BGP SUMMARY " + arguments.upper(), + title2="expected SHOW IP BGP SUMMARY " + arguments.upper(), + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed SHOW IP BGP SUMMARY check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert ( + failures == 0 + ), "SHOW IP BGP SUMMARY failed for router r{}:\n{}".format(i, diff) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_bgp_ipv6_summary(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying BGP IPv6 Summary") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/show_bgp_ipv6_summary.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show bgp ipv6 summary" 2> /dev/null') + .rstrip() + ) + # Mask out "using XXiXX bytes" portion. They are random... + actual = re.sub(r"using [0-9]+ bytes", "using XXXX bytes", actual) + # Mask out "using XiXXX KiB" portion. They are random... + actual = re.sub(r"using [0-9]+ KiB", "using XXXX KiB", actual) + # + # Remove extra summaries which exist with newer versions + # + # Remove summary lines (changed recently) + actual = re.sub(r"Total number.*", "", actual) + actual = re.sub(r"Displayed.*", "", actual) + # Remove IPv4 Unicast Summary (Title only) + actual = re.sub(r"IPv6 Unicast Summary:", "", actual) + # Remove IPv4 Multicast Summary (all of it) + actual = re.sub(r"IPv6 Multicast Summary:", "", actual) + actual = re.sub(r"No IPv6 Multicast neighbor is configured", "", actual) + # Remove IPv4 VPN Summary (all of it) + actual = re.sub(r"IPv6 VPN Summary:", "", actual) + actual = re.sub(r"No IPv6 VPN neighbor is configured", "", actual) + # Remove IPv4 Encap Summary (all of it) + actual = re.sub(r"IPv6 Encap Summary:", "", actual) + actual = re.sub(r"No IPv6 Encap neighbor is configured", "", actual) + # Remove Unknown Summary (all of it) + actual = re.sub(r"Unknown Summary:", "", actual) + actual = re.sub(r"No Unknown neighbor is configured", "", actual) + # Make Connect/Active/Idle the same (change them all to Active) + actual = re.sub(r" Connect ", " Active ", actual) + actual = re.sub(r" Idle ", " Active ", actual) + + # Remove Labeled Unicast Summary (all of it) + actual = re.sub(r"IPv6 labeled-unicast Summary:", "", actual) + actual = re.sub( + r"No IPv6 labeled-unicast neighbor is configured", "", actual + ) + + # Strip empty lines + actual = actual.lstrip() + actual = actual.rstrip() + # + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW BGP IPv6 SUMMARY", + title2="expected SHOW BGP IPv6 SUMMARY", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed SHOW BGP IPv6 SUMMARY check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert ( + failures == 0 + ), "SHOW BGP IPv6 SUMMARY failed for router r{}:\n{}".format(i, diff) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_nht(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n**** Test that nexthop tracking is at least nominally working ****\n") + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + for i in range(1, 2): + nhtFile = "{}/r{}/ip_nht.ref".format(thisDir, i) + expected = open(nhtFile).read().rstrip() + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + actual = ( + net["r{}".format(i)].cmd('vtysh -c "show ip nht" 2> /dev/null').rstrip() + ) + actual = re.sub(r"fd [0-9]+", "fd XX", actual) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + diff = topotest.get_textdiff( + actual, + expected, + title1="Actual `show ip nht`", + title2="Expected `show ip nht`", + ) + + if diff: + assert 0, "r{} failed ip nht check:\n{}\n".format(i, diff) + else: + print("show ip nht is ok\n") + + nhtFile = "{}/r{}/ipv6_nht.ref".format(thisDir, i) + expected = open(nhtFile).read().rstrip() + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + actual = ( + net["r{}".format(i)].cmd('vtysh -c "show ipv6 nht" 2> /dev/null').rstrip() + ) + actual = re.sub(r"fd [0-9]+", "fd XX", actual) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + diff = topotest.get_textdiff( + actual, + expected, + title1="Actual `show ip nht`", + title2="Expected `show ip nht`", + ) + + if diff: + assert 0, "r{} failed ipv6 nht check:\n{}\n".format(i, diff) + else: + print("show ipv6 nht is ok\n") + + +def test_bgp_ipv4(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying BGP IPv4") + print("******************************************\n") + diffresult = {} + for i in range(1, 2): + success = 0 + for refTableFile in glob.glob("{}/r{}/show_bgp_ipv4*.ref".format(thisDir, i)): + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show bgp ipv4" 2> /dev/null') + .rstrip() + ) + # Remove summary line (changed recently) + actual = re.sub(r"Total number.*", "", actual) + actual = re.sub(r"Displayed.*", "", actual) + actual = actual.rstrip() + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW BGP IPv4", + title2="expected SHOW BGP IPv4", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + diffresult[refTableFile] = diff + else: + success = 1 + print("template {} matched: r{} ok".format(refTableFile, i)) + break + + if not success: + resultstr = "No template matched.\n" + for f in diffresult.keys(): + resultstr += ( + "template {}: r{} failed SHOW BGP IPv4 check:\n{}\n".format( + f, + i, + diffresult[f], + ) + ) + raise AssertionError( + "SHOW BGP IPv4 failed for router r{}:\n{}".format(i, resultstr) + ) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_bgp_ipv6(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying BGP IPv6") + print("******************************************\n") + diffresult = {} + for i in range(1, 2): + success = 0 + for refTableFile in glob.glob("{}/r{}/show_bgp_ipv6*.ref".format(thisDir, i)): + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show bgp ipv6" 2> /dev/null') + .rstrip() + ) + # Remove summary line (changed recently) + actual = re.sub(r"Total number.*", "", actual) + actual = re.sub(r"Displayed.*", "", actual) + actual = actual.rstrip() + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual SHOW BGP IPv6", + title2="expected SHOW BGP IPv6", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + diffresult[refTableFile] = diff + else: + success = 1 + print("template {} matched: r{} ok".format(refTableFile, i)) + + if not success: + resultstr = "No template matched.\n" + for f in diffresult.keys(): + resultstr += ( + "template {}: r{} failed SHOW BGP IPv6 check:\n{}\n".format( + f, + i, + diffresult[f], + ) + ) + raise AssertionError( + "SHOW BGP IPv6 failed for router r{}:\n{}".format(i, resultstr) + ) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_route_map(): + global fatal_error + net = get_topogen().net + + if fatal_error != "": + pytest.skip(fatal_error) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying some basic routemap forward references\n") + print("*******************************************************\n") + failures = 0 + for i in range(1, 2): + refroutemap = "{}/r{}/show_route_map.ref".format(thisDir, i) + if os.path.isfile(refroutemap): + expected = open(refroutemap).read().rstrip() + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show route-map" 2> /dev/null') + .rstrip() + ) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + diff = topotest.get_textdiff( + actual, + expected, + title1="actual show route-map", + title2="expected show route-map", + ) + + if diff: + sys.stderr.write( + "r{} failed show route-map command Check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + assert ( + failures == 0 + ), "Show route-map command failed for router r{}:\n{}".format(i, diff) + + +def test_nexthop_groups_with_route_maps(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Verifying Nexthop Groups With Route-Maps") + print("******************************************\n") + + ### Nexthop Group With Route-Map Tests + + # Create a lib nexthop-group + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group test" -c "nexthop 1.1.1.1" -c "nexthop 1.1.1.2"' + ) + + ## Route-Map Proto Source + + route_str = "2.2.2.1" + src_str = "192.168.0.1" + + net["r1"].cmd( + 'vtysh -c "c t" -c "route-map NH-SRC permit 111" -c "set src {}"'.format( + src_str + ) + ) + net["r1"].cmd('vtysh -c "c t" -c "ip protocol sharp route-map NH-SRC"') + + net["r1"].cmd( + 'vtysh -c "sharp install routes {} nexthop-group test 1"'.format(route_str) + ) + + verify_route_nexthop_group("{}/32".format(route_str)) + + # Only a valid test on linux using nexthop objects + if sys.platform.startswith("linux"): + output = net["r1"].cmd("ip route show {}/32".format(route_str)) + match = re.search(r"src {}".format(src_str), output) + assert match is not None, "Route {}/32 not installed with src {}".format( + route_str, + src_str, + ) + + # Remove NHG routes and route-map + net["r1"].cmd('vtysh -c "sharp remove routes {} 1"'.format(route_str)) + net["r1"].cmd('vtysh -c "c t" -c "no ip protocol sharp route-map NH-SRC"') + net["r1"].cmd( + 'vtysh -c "c t" -c "no route-map NH-SRC permit 111" # -c "set src {}"'.format( + src_str + ) + ) + net["r1"].cmd('vtysh -c "c t" -c "no route-map NH-SRC"') + + ## Route-Map Deny/Permit with same nexthop group + + permit_route_str = "3.3.3.1" + deny_route_str = "3.3.3.2" + + net["r1"].cmd( + 'vtysh -c "c t" -c "ip prefix-list NOPE seq 5 permit {}/32"'.format( + permit_route_str + ) + ) + net["r1"].cmd( + 'vtysh -c "c t" -c "route-map NOPE permit 111" -c "match ip address prefix-list NOPE"' + ) + net["r1"].cmd('vtysh -c "c t" -c "route-map NOPE deny 222"') + net["r1"].cmd('vtysh -c "c t" -c "ip protocol sharp route-map NOPE"') + + # This route should be permitted + net["r1"].cmd( + 'vtysh -c "sharp install routes {} nexthop-group test 1"'.format( + permit_route_str + ) + ) + + verify_route_nexthop_group("{}/32".format(permit_route_str)) + + # This route should be denied + net["r1"].cmd( + 'vtysh -c "sharp install routes {} nexthop-group test 1"'.format(deny_route_str) + ) + + nhg_id = route_get_nhg_id(deny_route_str) + output = net["r1"].cmd('vtysh -c "show nexthop-group rib {}"'.format(nhg_id)) + + match = re.search(r"Valid", output) + assert match is None, "Nexthop Group ID={} should not be marked Valid".format( + nhg_id + ) + + match = re.search(r"Installed", output) + assert match is None, "Nexthop Group ID={} should not be marked Installed".format( + nhg_id + ) + + # Remove NHG routes and route-map + net["r1"].cmd('vtysh -c "sharp remove routes {} 1"'.format(permit_route_str)) + net["r1"].cmd('vtysh -c "sharp remove routes {} 1"'.format(deny_route_str)) + net["r1"].cmd('vtysh -c "c t" -c "no ip protocol sharp route-map NOPE"') + net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE permit 111"') + net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE deny 222"') + net["r1"].cmd('vtysh -c "c t" -c "no route-map NOPE"') + net["r1"].cmd( + 'vtysh -c "c t" -c "no ip prefix-list NOPE seq 5 permit {}/32"'.format( + permit_route_str + ) + ) + + +def test_nexthop_group_replace(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Verifying Nexthop Groups") + print("******************************************\n") + + ### Nexthop Group Tests + + ## 2-Way ECMP Directly Connected + + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group replace" -c "nexthop 1.1.1.1 r1-eth1 onlink" -c "nexthop 1.1.1.2 r1-eth2 onlink"' + ) + + # At the moment there is absolutely no real easy way to query sharpd + # for the nexthop group actually installed. If it is not installed + # sharpd will just transmit the nexthops down instead of the nexthop + # group id. Leading to a situation where the replace is not actually + # being tested. So let's just wait some time here because this + # is hard and this test fails all the time + sleep(5) + + # Create with sharpd using nexthop-group + net["r1"].cmd('vtysh -c "sharp install routes 3.3.3.1 nexthop-group replace 1"') + + verify_route_nexthop_group("3.3.3.1/32") + + # Change the nexthop group + net["r1"].cmd( + 'vtysh -c "c t" -c "nexthop-group replace" -c "no nexthop 1.1.1.1 r1-eth1 onlink" -c "nexthop 1.1.1.3 r1-eth1 onlink" -c "nexthop 1.1.1.4 r1-eth4 onlink"' + ) + + # Verify it updated. We can just check install and ecmp count here. + verify_route_nexthop_group("3.3.3.1/32", False, 3) + + +def test_mpls_interfaces(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + # Skip if no LDP installed or old kernel + if net["r1"].daemon_available("ldpd") == False: + pytest.skip("No MPLS or kernel < 4.5") + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("\n\n** Verifying MPLS Interfaces") + print("******************************************\n") + failures = 0 + for i in range(1, 2): + refTableFile = "{}/r{}/show_mpls_ldp_interface.ref".format(thisDir, i) + if os.path.isfile(refTableFile): + # Read expected result from file + expected = open(refTableFile).read().rstrip() + # Fix newlines (make them all the same) + expected = ("\n".join(expected.splitlines()) + "\n").splitlines(1) + + # Actual output from router + actual = ( + net["r{}".format(i)] + .cmd('vtysh -c "show mpls ldp interface" 2> /dev/null') + .rstrip() + ) + # Mask out Timer in Uptime + actual = re.sub(r" [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ", " xx:xx:xx ", actual) + # Fix newlines (make them all the same) + actual = ("\n".join(actual.splitlines()) + "\n").splitlines(1) + + # Generate Diff + diff = topotest.get_textdiff( + actual, + expected, + title1="actual MPLS LDP interface status", + title2="expected MPLS LDP interface status", + ) + + # Empty string if it matches, otherwise diff contains unified diff + if diff: + sys.stderr.write( + "r{} failed MPLS LDP Interface status Check:\n{}\n".format(i, diff) + ) + failures += 1 + else: + print("r{} ok".format(i)) + + if failures > 0: + fatal_error = "MPLS LDP Interface status failed" + + assert ( + failures == 0 + ), "MPLS LDP Interface status failed for router r{}:\n{}".format(i, diff) + + # Make sure that all daemons are running + for i in range(1, 2): + fatal_error = net["r{}".format(i)].checkRouterRunning() + assert fatal_error == "", fatal_error + + +def test_resilient_nexthop_group(): + net = get_topogen().net + + result = required_linux_kernel_version("5.19") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >= 5.19") + + net["r1"].cmd( + 'vtysh -c "conf" -c "nexthop-group resilience" -c "resilient buckets 64 idle-timer 128 unbalanced-timer 256" -c "nexthop 1.1.1.1 r1-eth1 onlink" -c "nexthop 1.1.1.2 r1-eth2 onlink"' + ) + + output = net["r1"].cmd('vtysh -c "show nexthop-group rib sharp"') + buckets = re.findall(r"Buckets", output) + + output = net["r1"].cmd('vtysh -c "show nexthop-group rib sharp json"') + + joutput = json.loads(output) + + # Use the json output and collect the nhg id from it + + for nhgid in joutput: + n = joutput[nhgid] + if "buckets" in n: + break + + verify_nexthop_group(int(nhgid)) + assert len(buckets) == 1, "Resilient NHG not created in zebra" + + +def test_shutdown_check_stderr(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + print("\n\n** Verifying unexpected STDERR output from daemons") + print("******************************************\n") + + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + print( + "SKIPPED final check on StdErr output: Disabled (TOPOTESTS_CHECK_STDERR undefined)\n" + ) + pytest.skip("Skipping test for Stderr output") + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + print("thisDir=" + thisDir) + + net["r1"].stopRouter() + + log = net["r1"].getStdErr("ripd") + if log: + print("\nRIPd StdErr Log:\n" + log) + log = net["r1"].getStdErr("ripngd") + if log: + print("\nRIPngd StdErr Log:\n" + log) + log = net["r1"].getStdErr("ospfd") + if log: + print("\nOSPFd StdErr Log:\n" + log) + log = net["r1"].getStdErr("ospf6d") + if log: + print("\nOSPF6d StdErr Log:\n" + log) + log = net["r1"].getStdErr("isisd") + if log: + print("\nISISd StdErr Log:\n" + log) + log = net["r1"].getStdErr("bgpd") + if log: + print("\nBGPd StdErr Log:\n" + log) + + log = net["r1"].getStdErr("nhrpd") + if log: + print("\nNHRPd StdErr Log:\n" + log) + + log = net["r1"].getStdErr("pbrd") + if log: + print("\nPBRd StdErr Log:\n" + log) + + log = net["r1"].getStdErr("babeld") + if log: + print("\nBABELd StdErr Log:\n" + log) + + if net["r1"].daemon_available("ldpd"): + log = net["r1"].getStdErr("ldpd") + if log: + print("\nLDPd StdErr Log:\n" + log) + log = net["r1"].getStdErr("zebra") + if log: + print("\nZebra StdErr Log:\n" + log) + + +def test_shutdown_check_memleak(): + global fatal_error + net = get_topogen().net + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + if os.environ.get("TOPOTESTS_CHECK_MEMLEAK") is None: + print( + "SKIPPED final check on Memory leaks: Disabled (TOPOTESTS_CHECK_MEMLEAK undefined)\n" + ) + pytest.skip("Skipping test for memory leaks") + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + for i in range(1, 2): + net["r{}".format(i)].stopRouter() + net["r{}".format(i)].report_memory_leaks( + os.environ.get("TOPOTESTS_CHECK_MEMLEAK"), os.path.basename(__file__) + ) + + +if __name__ == "__main__": + # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli + # retval = pytest.main(["-s", "--tb=no"]) + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/analyze.py b/tests/topotests/analyze.py new file mode 100755 index 0000000..a1ac9a2 --- /dev/null +++ b/tests/topotests/analyze.py @@ -0,0 +1,433 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# July 9 2021, Christian Hopps +# +# Copyright (c) 2021, LabN Consulting, L.L.C. +# +import argparse +import atexit +import logging +import os +import re +import subprocess +import sys +import tempfile +from collections import OrderedDict + +import xmltodict + + +def get_range_list(rangestr): + result = [] + for e in rangestr.split(","): + e = e.strip() + if not e: + continue + if e.find("-") == -1: + result.append(int(e)) + else: + start, end = e.split("-") + result.extend(list(range(int(start), int(end) + 1))) + return result + + +def dict_range_(dct, rangestr, dokeys): + keys = list(dct.keys()) + if not rangestr or rangestr == "all": + for key in keys: + if dokeys: + yield key + else: + yield dct[key] + return + + dlen = len(keys) + for index in get_range_list(rangestr): + if index >= dlen: + break + key = keys[index] + if dokeys: + yield key + else: + yield dct[key] + + +def dict_range_keys(dct, rangestr): + return dict_range_(dct, rangestr, True) + + +def dict_range_values(dct, rangestr): + return dict_range_(dct, rangestr, False) + + +def get_summary(results): + ntest = int(results["@tests"]) + nfail = int(results["@failures"]) + nerror = int(results["@errors"]) + nskip = int(results["@skipped"]) + npass = ntest - nfail - nskip - nerror + return ntest, npass, nfail, nerror, nskip + + +def print_summary(results, args): + ntest, npass, nfail, nerror, nskip = (0, 0, 0, 0, 0) + for group in results: + _ntest, _npass, _nfail, _nerror, _nskip = get_summary(results[group]) + if args.verbose: + print( + f"Group: {group} Total: {_ntest} PASSED: {_npass}" + " FAIL: {_nfail} ERROR: {_nerror} SKIP: {_nskip}" + ) + ntest += _ntest + npass += _npass + nfail += _nfail + nerror += _nerror + nskip += _nskip + print(f"Total: {ntest} PASSED: {npass} FAIL: {nfail} ERROR: {nerror} SKIP: {nskip}") + + +def get_global_testcase(results): + for group in results: + for testcase in results[group]["testcase"]: + if "@file" not in testcase: + return testcase + return None + + +def get_filtered(tfilters, results, args): + if isinstance(tfilters, str) or tfilters is None: + tfilters = [tfilters] + found_files = OrderedDict() + for group in results: + if isinstance(results[group]["testcase"], list): + tlist = results[group]["testcase"] + else: + tlist = [results[group]["testcase"]] + for testcase in tlist: + for tfilter in tfilters: + if tfilter is None: + if ( + "failure" not in testcase + and "error" not in testcase + and "skipped" not in testcase + ): + break + elif tfilter in testcase: + break + else: + continue + # cname = testcase["@classname"] + fname = testcase.get("@file", "") + cname = testcase.get("@classname", "") + if not fname and not cname: + name = testcase.get("@name", "") + if not name: + continue + # If we had a failure at the module level we could be here. + fname = name.replace(".", "/") + ".py" + tcname = fname + else: + if not fname: + fname = cname.replace(".", "/") + ".py" + if "@name" not in testcase: + tcname = fname + else: + tcname = fname + "::" + testcase["@name"] + found_files[tcname] = testcase + return found_files + + +def search_testcase(testcase, regexp): + for key, val in testcase.items(): + if regexp.search(str(val)): + return True + return False + + +def dump_testcase(testcase): + s = "" + for key, val in testcase.items(): + if isinstance(val, str) or isinstance(val, float) or isinstance(val, int): + s += "{}: {}\n".format(key, val) + elif isinstance(val, list): + for k2, v2 in enumerate(val): + s += "{}: {}\n".format(k2, v2) + else: + for k2, v2 in val.items(): + s += "{}: {}\n".format(k2, v2) + return s + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-a", + "--save-xml", + action="store_true", + help=( + "Move [container:]/tmp/topotests/topotests.xml " + "to --results value if --results does not exist yet" + ), + ) + parser.add_argument( + "-A", + "--save", + action="store_true", + help=( + "Move [container:]/tmp/topotests{,.xml} " + "to --results value if --results does not exist yet" + ), + ) + parser.add_argument( + "-C", + "--container", + help="specify docker/podman container of the run", + ) + parser.add_argument( + "--use-podman", + action="store_true", + help="Use `podman` instead of `docker` for saving container data", + ) + parser.add_argument( + "-S", + "--select", + help=( + "select results combination of letters: " + "'e'rrored 'f'ailed 'p'assed 's'kipped. " + "Default is 'fe', unless --search or --time which default to 'efps'" + ), + ) + parser.add_argument( + "-R", + "--search", + help=( + "filter results to those which match a regex. " + "All test text is search unless restricted by --errmsg or --errtext" + ), + ) + parser.add_argument( + "-r", + "--results", + help="xml results file or directory containing xml results file", + ) + parser.add_argument("--rundir", help=argparse.SUPPRESS) + parser.add_argument( + "-E", + "--enumerate", + action="store_true", + help="enumerate each item (results scoped)", + ) + parser.add_argument( + "-T", "--test", help="select testcase at given ordinal from the enumerated list" + ) + parser.add_argument( + "--errmsg", action="store_true", help="print testcase error message" + ) + parser.add_argument( + "--errtext", action="store_true", help="print testcase error text" + ) + parser.add_argument( + "--full", action="store_true", help="print all logging for selected testcases" + ) + parser.add_argument("--time", action="store_true", help="print testcase run times") + + parser.add_argument("-s", "--summary", action="store_true", help="print summary") + parser.add_argument("-v", "--verbose", action="store_true", help="be verbose") + args = parser.parse_args() + + if args.save and args.save_xml: + logging.critical("Only one of --save or --save-xml allowed") + sys.exit(1) + + scount = bool(args.save) + bool(args.save_xml) + + # + # Saving/Archiving results + # + + docker_bin = "podman" if args.use_podman else "docker" + contid = "" + if args.container: + # check for container existence + contid = args.container + try: + # p = + subprocess.run( + f"{docker_bin} inspect {contid}", + check=True, + shell=True, + errors="ignore", + capture_output=True, + ) + except subprocess.CalledProcessError: + logging.critical(f"{docker_bin} container '{contid}' does not exist") + sys.exit(1) + # If you need container info someday... + # cont_info = json.loads(p.stdout) + + cppath = "/tmp/topotests" + if args.save_xml or scount == 0: + cppath += "/topotests.xml" + if contid: + cppath = contid + ":" + cppath + + tresfile = None + + if scount and args.results and not os.path.exists(args.results): + if not contid: + if not os.path.exists(cppath): + logging.critical(f"'{cppath}' doesn't exist to save") + sys.exit(1) + if args.save_xml: + subprocess.run(["cp", cppath, args.results]) + else: + subprocess.run(["mv", cppath, args.results]) + else: + try: + subprocess.run( + f"{docker_bin} cp {cppath} {args.results}", + check=True, + shell=True, + errors="ignore", + capture_output=True, + ) + except subprocess.CalledProcessError as error: + logging.critical(f"Can't {docker_bin} cp '{cppath}': %s", str(error)) + sys.exit(1) + + if "SUDO_USER" in os.environ: + subprocess.run(["chown", "-R", os.environ["SUDO_USER"], args.results]) + elif not args.results: + # User doesn't want to save results just use them inplace + if not contid: + if not os.path.exists(cppath): + logging.critical(f"'{cppath}' doesn't exist") + sys.exit(1) + args.results = cppath + else: + tresfile, tresname = tempfile.mkstemp( + suffix=".xml", prefix="topotests-", text=True + ) + atexit.register(lambda: os.unlink(tresname)) + os.close(tresfile) + try: + subprocess.run( + f"{docker_bin} cp {cppath} {tresname}", + check=True, + shell=True, + errors="ignore", + capture_output=True, + ) + except subprocess.CalledProcessError as error: + logging.critical(f"Can't {docker_bin} cp '{cppath}': %s", str(error)) + sys.exit(1) + args.results = tresname + + # + # Result option validation + # + + count = 0 + if args.errmsg: + count += 1 + if args.errtext: + count += 1 + if args.full: + count += 1 + if count > 1: + logging.critical("Only one of --full, --errmsg or --errtext allowed") + sys.exit(1) + + if args.time and count: + logging.critical("Can't use --full, --errmsg or --errtext with --time") + sys.exit(1) + + if args.enumerate and (count or args.time or args.test): + logging.critical( + "Can't use --enumerate with --errmsg, --errtext, --full, --test or --time" + ) + sys.exit(1) + + results = {} + ttfiles = [] + + if os.path.exists(os.path.join(args.results, "topotests.xml")): + args.results = os.path.join(args.results, "topotests.xml") + if not os.path.exists(args.results): + logging.critical("%s doesn't exist", args.results) + sys.exit(1) + + ttfiles = [args.results] + + for f in ttfiles: + m = re.match(r"tt-group-(\d+)/topotests.xml", f) + group = int(m.group(1)) if m else 0 + with open(f) as xml_file: + results[group] = xmltodict.parse(xml_file.read())["testsuites"]["testsuite"] + + search_re = re.compile(args.search) if args.search else None + + if args.select is None: + if search_re or args.time: + args.select = "efsp" + else: + args.select = "fe" + + filters = [] + if "e" in args.select: + filters.append("error") + if "f" in args.select: + filters.append("failure") + if "s" in args.select: + filters.append("skipped") + if "p" in args.select: + filters.append(None) + + found_files = get_filtered(filters, results, args) + + if search_re: + found_files = { + k: v for k, v in found_files.items() if search_testcase(v, search_re) + } + + if args.enumerate: + # print the selected test names with ordinal + print("\n".join(["{} {}".format(i, x) for i, x in enumerate(found_files)])) + elif args.test is None and count == 0 and not args.time: + # print the selected test names + print("\n".join([str(x) for x in found_files])) + else: + rangestr = args.test if args.test else "all" + for key in dict_range_keys(found_files, rangestr): + testcase = found_files[key] + if args.time: + text = testcase["@time"] + s = "{}: {}".format(text, key) + elif args.errtext: + if "error" in testcase: + errmsg = testcase["error"]["#text"] + elif "failure" in testcase: + errmsg = testcase["failure"]["#text"] + else: + errmsg = "none found" + s = "{}: {}".format(key, errmsg) + elif args.errmsg: + if "error" in testcase: + errmsg = testcase["error"]["@message"] + elif "failure" in testcase: + errmsg = testcase["failure"]["@message"] + else: + errmsg = "none found" + s = "{}: {}".format(key, errmsg) + else: + s = dump_testcase(testcase) + print(s) + + if args.summary: + print_summary(results, args) + + +if __name__ == "__main__": + main() diff --git a/tests/topotests/babel_topo1/r1/babeld.conf b/tests/topotests/babel_topo1/r1/babeld.conf new file mode 100644 index 0000000..4058362 --- /dev/null +++ b/tests/topotests/babel_topo1/r1/babeld.conf @@ -0,0 +1,20 @@ + +interface r1-eth0 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +interface r1-eth1 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +router babel + network r1-eth0 + network r1-eth1 + redistribute ipv4 connected + redistribute ipv6 connected +! +line vty +! + diff --git a/tests/topotests/babel_topo1/r1/show_ip_route.json_ref b/tests/topotests/babel_topo1/r1/show_ip_route.json_ref new file mode 100644 index 0000000..0d52b67 --- /dev/null +++ b/tests/topotests/babel_topo1/r1/show_ip_route.json_ref @@ -0,0 +1,80 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + } + ], + "192.168.2.0/24":[ + { + "prefix":"192.168.2.0/24", + "prefixLen":24, + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1" + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/babel_topo1/r1/zebra.conf b/tests/topotests/babel_topo1/r1/zebra.conf new file mode 100644 index 0000000..5eda3e2 --- /dev/null +++ b/tests/topotests/babel_topo1/r1/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! debug zebra rib detail +! +hostname r1 +! +interface r1-eth0 + ip address 192.168.1.1/24 + ipv6 address 192:168:1::1/64 +! +interface r1-eth1 + description to sw2 - babel interface + ip address 193.1.1.1/26 + ipv6 address 193:1:1::1/64 + no link-detect +! +ip forwarding +ipv6 forwarding +! +! +line vty +! + diff --git a/tests/topotests/babel_topo1/r2/babeld.conf b/tests/topotests/babel_topo1/r2/babeld.conf new file mode 100644 index 0000000..bae4e59 --- /dev/null +++ b/tests/topotests/babel_topo1/r2/babeld.conf @@ -0,0 +1,17 @@ +! +interface r2-eth0 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +interface r2-eth1 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +router babel + network r2-eth0 + network r2-eth1 + redistribute ipv4 connected + redistribute ipv6 connected +! diff --git a/tests/topotests/babel_topo1/r2/show_ip_route.json_ref b/tests/topotests/babel_topo1/r2/show_ip_route.json_ref new file mode 100644 index 0000000..ff3f6ab --- /dev/null +++ b/tests/topotests/babel_topo1/r2/show_ip_route.json_ref @@ -0,0 +1,80 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.1", + "afi":"ipv4", + "interfaceName":"r2-eth0", + "active":true + } + ] + } + ], + "192.168.2.0/24":[ + { + "prefix":"192.168.2.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.2", + "afi":"ipv4", + "interfaceName":"r2-eth1", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.2", + "afi":"ipv4", + "interfaceName":"r2-eth1", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r2-eth0", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r2-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/babel_topo1/r2/zebra.conf b/tests/topotests/babel_topo1/r2/zebra.conf new file mode 100644 index 0000000..301a8b2 --- /dev/null +++ b/tests/topotests/babel_topo1/r2/zebra.conf @@ -0,0 +1,23 @@ +log file zebra.log +! +hostname r2 +! +interface r2-eth0 + description to sw2 - babel interface + ip address 193.1.1.2/26 + ipv6 address 193:1:1::2/64 + no link-detect +! +interface r2-eth1 + description to sw3 - babel interface + ip address 193.1.2.1/24 + ipv6 address 193:1:2::1/64 + no link-detect +! +ip forwarding +ipv6 forwarding +! +! +line vty +! + diff --git a/tests/topotests/babel_topo1/r3/babeld.conf b/tests/topotests/babel_topo1/r3/babeld.conf new file mode 100644 index 0000000..e10e5aa --- /dev/null +++ b/tests/topotests/babel_topo1/r3/babeld.conf @@ -0,0 +1,16 @@ +! +interface r3-eth0 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +interface r3-eth1 + babel hello-interval 1000 + babel wired + babel update-interval 50 +! +router babel + network r3-eth0 + network r3-eth1 + redistribute ipv4 connected + redistribute ipv4 static diff --git a/tests/topotests/babel_topo1/r3/show_ip_route.json_ref b/tests/topotests/babel_topo1/r3/show_ip_route.json_ref new file mode 100644 index 0000000..7fe66fa --- /dev/null +++ b/tests/topotests/babel_topo1/r3/show_ip_route.json_ref @@ -0,0 +1,81 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.1", + "afi":"ipv4", + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ], + "192.168.2.0/24":[ + { + "prefix":"192.168.2.0/24", + "protocol":"static", + "selected":true, + "distance":1, + "nexthops":[ + { + "fib":true, + "ip":"192.168.3.10", + "afi":"ipv4", + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"babel", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.1", + "afi":"ipv4", + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/babel_topo1/r3/zebra.conf b/tests/topotests/babel_topo1/r3/zebra.conf new file mode 100644 index 0000000..34e8c62 --- /dev/null +++ b/tests/topotests/babel_topo1/r3/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +! +hostname r3 +! +interface r3-eth0 + description to sw4 - Stub interface + ip address 192.168.3.1/24 + ipv6 address 192:168:3::1/64 + no link-detect +! +interface r3-eth1 + description to sw3 - RIPv2 interface + ip address 193.1.2.2/24 + ipv6 address 193:1:2::2/64 + no link-detect +! +ip route 192.168.2.0/24 192.168.3.10 +ipv6 route 192:168:2::0/64 192:168:3::10 +! +ip forwarding +ipv6 forwarding +! +! +line vty +! diff --git a/tests/topotests/babel_topo1/test_babel_topo1.py b/tests/topotests/babel_topo1/test_babel_topo1.py new file mode 100644 index 0000000..decf0c2 --- /dev/null +++ b/tests/topotests/babel_topo1/test_babel_topo1.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_babel_topo1.py +# +# Copyright (c) 2017 by +# Cumulus Networks, Inc. +# Donald Sharp +# + +""" +test_babel_topo1.py: Testing BABEL + +""" + +import os +import re +import sys +import pytest +import json +from functools import partial + +pytestmark = [pytest.mark.babeld] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + # On main router + # First switch is for a dummy interface (for local network) + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["r1"]) + + # Switches for BABEL + # switch 2 switch is for connection to BABEL router + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # switch 4 is stub on remote BABEL router + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["r3"]) + + # switch 3 is between BABEL routers + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + "Setup topology" + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BABEL, os.path.join(CWD, "{}/babeld.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_converge_protocols(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sleep(10, "Waiting for BABEL convergence") + + +def runit(router, assertmsg, cmd, expfile): + logger.info(expfile) + + # Read expected result from file + expected = json.loads(open(expfile).read()) + + test_func = partial(topotest.router_json_cmp, router, cmd, expected) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, assertmsg + + +def test_zebra_ipv4_routingTable(): + "Test 'show ip route'" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + failures = 0 + router_list = tgen.routers().values() + for router in router_list: + assertmsg = "Zebra IPv4 Routing Table verification failed for router {}".format( + router.name + ) + refTableFile = "{}/{}/show_ip_route.json_ref".format(CWD, router.name) + runit(router, assertmsg, "show ip route json", refTableFile) + + +def test_shutdown_check_stderr(): + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + pytest.skip("Skipping test for Stderr output and memory leaks") + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying unexpected STDERR output from daemons") + + router_list = tgen.routers().values() + for router in router_list: + router.stop() + + log = tgen.net[router.name].getStdErr("babeld") + if log: + logger.error("BABELd StdErr Log:" + log) + log = tgen.net[router.name].getStdErr("zebra") + if log: + logger.error("Zebra StdErr Log:" + log) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_bgp_cbit_topo3/__init__.py b/tests/topotests/bfd_bgp_cbit_topo3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/bfdd.conf b/tests/topotests/bfd_bgp_cbit_topo3/r1/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/bgp_ipv6_routes_down.json b/tests/topotests/bfd_bgp_cbit_topo3/r1/bgp_ipv6_routes_down.json new file mode 100644 index 0000000..61be1df --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/bgp_ipv6_routes_down.json @@ -0,0 +1,53 @@ +{ + "vrfName": "default", + "routerId": "10.254.254.1", + "localAS": 101, + "routes": + { + "2001:db8:8::/64": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "2001:db8:8::", + "prefixLen": 64, + "network": "2001:db8:8::\/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "origin": "IGP", + "nexthops": [ + { + "ip": "::", + "afi": "ipv6", + "scope": "global", + "used": true + } + ] + } + ], + "2001:db8:9::/64": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "2001:db8:9::", + "prefixLen": 64, + "network": "2001:db8:9::\/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "origin": "IGP", + "nexthops": [ + { + "ip": "::", + "afi": "ipv6", + "scope": "global", + "used": true + } + ] + } + ] + } +} + diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/bgpd.conf b/tests/topotests/bfd_bgp_cbit_topo3/r1/bgpd.conf new file mode 100644 index 0000000..f8ad1f3 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/bgpd.conf @@ -0,0 +1,23 @@ +! debug bgp neighbor-events +router bgp 101 + bgp router-id 10.254.254.1 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 10 + bgp graceful-restart + neighbor 2001:db8:4::1 remote-as 102 + neighbor 2001:db8:4::1 timers 3 10 + neighbor 2001:db8:4::1 remote-as external + neighbor 2001:db8:4::1 bfd + neighbor 2001:db8:4::1 bfd check-control-plane-failure + neighbor 2001:db8:4::1 update-source 2001:db8:1::1 + neighbor 2001:db8:4::1 ebgp-multihop 5 + address-family ipv4 unicast + no neighbor 2001:db8:4::1 activate + exit-address-family + address-family ipv6 unicast + network 2001:db8:8::/64 + network 2001:db8:9::/64 + neighbor 2001:db8:4::1 activate + exit-address-family +! diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/ipv6_routes.json b/tests/topotests/bfd_bgp_cbit_topo3/r1/ipv6_routes.json new file mode 100644 index 0000000..36cdcc3 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/ipv6_routes.json @@ -0,0 +1,36 @@ +{ + "2001:db8:1::/64": [{ + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:1::/64", + "nexthops": [{ + "directlyConnected": true, + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:4::/64": [{ + "distance": 1, + "protocol": "static", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:4::/64", + "nexthops": [{ + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/peers.json b/tests/topotests/bfd_bgp_cbit_topo3/r1/peers.json new file mode 100644 index 0000000..b436d55 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/peers.json @@ -0,0 +1,17 @@ +[ + { + "multihop":true, + "peer":"2001:db8:4::1", + "local":"2001:db8:1::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/peers_down.json b/tests/topotests/bfd_bgp_cbit_topo3/r1/peers_down.json new file mode 100644 index 0000000..4984b52 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/peers_down.json @@ -0,0 +1,15 @@ +[ + { + "multihop":true, + "peer":"2001:db8:4::1", + "local":"2001:db8:1::1", + "status":"init", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r1/zebra.conf b/tests/topotests/bfd_bgp_cbit_topo3/r1/zebra.conf new file mode 100644 index 0000000..3a30cd4 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r1/zebra.conf @@ -0,0 +1,8 @@ +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ipv6 address 2001:db8:1::1/64 +! +ipv6 route 2001:db8:4::/64 2001:db8:1::2 + diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r2/zebra.conf b/tests/topotests/bfd_bgp_cbit_topo3/r2/zebra.conf new file mode 100644 index 0000000..0f70be1 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r2/zebra.conf @@ -0,0 +1,9 @@ +ip forwarding +ipv6 forwarding +! +interface r2-eth0 + ipv6 address 2001:db8:1::2/64 +! +interface r2-eth1 + ipv6 address 2001:db8:4::2/64 +! diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/bfdd.conf b/tests/topotests/bfd_bgp_cbit_topo3/r3/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/bgp_ipv6_routes_down.json b/tests/topotests/bfd_bgp_cbit_topo3/r3/bgp_ipv6_routes_down.json new file mode 100644 index 0000000..c0cb3c4 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/bgp_ipv6_routes_down.json @@ -0,0 +1,52 @@ +{ + "vrfName": "default", + "routerId": "10.254.254.3", + "localAS": 102, + "routes": + { + "2001:db8:6::/64": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "2001:db8:6::", + "prefixLen": 64, + "network": "2001:db8:6::\/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "origin": "IGP", + "nexthops": [ + { + "ip": "::", + "afi": "ipv6", + "scope": "global", + "used": true + } + ] + } + ], + "2001:db8:7::/64": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "2001:db8:7::", + "prefixLen": 64, + "network": "2001:db8:7::\/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "origin": "IGP", + "nexthops": [ + { + "ip": "::", + "afi": "ipv6", + "scope": "global", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/bgpd.conf b/tests/topotests/bfd_bgp_cbit_topo3/r3/bgpd.conf new file mode 100644 index 0000000..42953a0 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/bgpd.conf @@ -0,0 +1,29 @@ +! debug bgp neighbor-events +router bgp 102 + bgp router-id 10.254.254.3 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 10 + bgp graceful-restart + ! simulate NSF machine + bgp graceful-restart preserve-fw-state + bgp graceful-restart stalepath-time 900 + bgp graceful-restart restart-time 900 + neighbor 2001:db8:1::1 remote-as 101 + neighbor 2001:db8:1::1 timers 3 10 + neighbor 2001:db8:1::1 remote-as external + neighbor 2001:db8:1::1 update-source 2001:db8:4::1 + neighbor 2001:db8:1::1 bfd + neighbor 2001:db8:1::1 bfd check-control-plane-failure + neighbor 2001:db8:1::1 ebgp-multihop 5 + ! + address-family ipv4 unicast + no neighbor 2001:db8:1::1 activate + exit-address-family + ! + address-family ipv6 unicast + neighbor 2001:db8:1::1 activate + network 2001:db8:6::/64 + network 2001:db8:7::/64 + exit-address-family +! diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/ipv6_routes.json b/tests/topotests/bfd_bgp_cbit_topo3/r3/ipv6_routes.json new file mode 100644 index 0000000..09808cc --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/ipv6_routes.json @@ -0,0 +1,80 @@ +{ + "2001:db8:1::/64": [{ + "distance": 1, + "protocol": "static", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:1::/64", + "nexthops": [{ + "interfaceName": "r3-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "2001:db8:4::/64": [{ + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:4::/64", + "nexthops": [{ + "directlyConnected": true, + "interfaceName": "r3-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:8::/64": [{ + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:8::/64", + "nexthops": [{ + "ip":"2001:db8:1::1", + "active": true, + "afi": "ipv6", + "recursive":true + }, + { + "fib":true, + "ip":"2001:db8:4::2", + "afi": "ipv6", + "interfaceName":"r3-eth0" + } + ] + } + ], + "2001:db8:9::/64": [{ + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:9::/64", + "nexthops": [{ + "ip":"2001:db8:1::1", + "active": true, + "afi": "ipv6", + "recursive":true + }, + { + "fib":true, + "ip":"2001:db8:4::2", + "afi": "ipv6", + "interfaceName":"r3-eth0" + } + ] + } + ] +} diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/peers.json b/tests/topotests/bfd_bgp_cbit_topo3/r3/peers.json new file mode 100644 index 0000000..fc9e145 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/peers.json @@ -0,0 +1,17 @@ +[ + { + "multihop":true, + "peer":"2001:db8:1::1", + "local":"2001:db8:4::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/peers_down.json b/tests/topotests/bfd_bgp_cbit_topo3/r3/peers_down.json new file mode 100644 index 0000000..620c6dd --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/peers_down.json @@ -0,0 +1,15 @@ +[ + { + "multihop":true, + "peer":"2001:db8:1::1", + "local":"2001:db8:4::1", + "status":"down", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_bgp_cbit_topo3/r3/zebra.conf b/tests/topotests/bfd_bgp_cbit_topo3/r3/zebra.conf new file mode 100644 index 0000000..7759251 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/r3/zebra.conf @@ -0,0 +1,7 @@ +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ipv6 address 2001:db8:4::1/64 +! +ipv6 route 2001:db8:1::/64 2001:db8:4::2 diff --git a/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.dot b/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.dot new file mode 100644 index 0000000..270de82 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.dot @@ -0,0 +1,58 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo2"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n10.0.3.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + + r2 -- sw2 [label="eth1"]; + r3 -- sw2 [label="eth0"]; +} diff --git a/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.py b/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.py new file mode 100644 index 0000000..d478e99 --- /dev/null +++ b/tests/topotests/bfd_bgp_cbit_topo3/test_bfd_bgp_cbit_topo3.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_bgp_cbit_topo3.py +# +# Copyright (c) 2019 6WIND +# + +""" +test_bfd_bgp_cbit_topo3.py: Test the FRR BFD daemon with multihop and BGP +unnumbered. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.bfdd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)), + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + # Verify that we are using the proper version and that the BFD + # daemon exists. + for router in router_list.values(): + # Check for Version + if router.has_version("<", "5.1"): + tgen.set_error("Unsupported FRR version") + break + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged before checking for the BFD + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check IPv6 routing tables. + logger.info("Checking IPv6 routes for convergence") + for router in tgen.routers().values(): + if router.name == "r2": + continue + json_file = "{}/{}/ipv6_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ipv6 route json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bfd_connection(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up") + for router in tgen.routers().values(): + if router.name == "r2": + continue + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=32, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bfd_loss_intermediate(): + """ + Assert that BGP notices the BFD link down failure. + The BGP entries should be flushed as the C-bit is set in both directions. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + expected = {"as": 101, "peers": {"2001:db8:4::1": {"state": "Established"}}} + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv6 uni summ json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = '"r1" has not established bgp peering yet' + assert result is None, assertmsg + + # assert False + logger.info("removing IPv6 address from r2 to simulate loss of connectivity") + # Disable r2-eth0 ipv6 address + cmd = 'vtysh -c "configure terminal" -c "interface r2-eth1" -c "no ipv6 address 2001:db8:4::2/64"' + tgen.net["r2"].cmd(cmd) + + # Wait the minimum time we can before checking that BGP/BFD + # converged. + logger.info("waiting for BFD converge down") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + if router.name == "r2": + continue + json_file = "{}/{}/peers_down.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=32, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + logger.info("waiting for BGP entries to be removed") + for router in tgen.routers().values(): + if router.name == "r2": + continue + json_file = "{}/{}/bgp_ipv6_routes_down.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bgp ipv6 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + logger.info("Checking IPv6 routes on r1 should still be present") + for router in tgen.routers().values(): + if router.name == "r2": + continue + if router.name == "r3": + continue + json_file = "{}/r1/ipv6_routes.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ipv6 route json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bfd_comes_back_again(): + """ + Assert that BFD notices the bfd link up + and that ipv6 entries appear back + """ + tgen = get_topogen() + logger.info("re-adding IPv6 address from r2 to simulate connectivity is back") + # adds back r2-eth0 ipv6 address + cmd = 'vtysh -c "configure terminal" -c "interface r2-eth1" -c "ipv6 address 2001:db8:4::2/64"' + tgen.net["r2"].cmd(cmd) + + # Wait the minimum time we can before checking that BGP/BFD + # converged. + logger.info("waiting for BFD to converge up") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + if router.name == "r2": + continue + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=16, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_isis_topo1/__init__.py b/tests/topotests/bfd_isis_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_isis_topo1/rt1/bfdd.conf b/tests/topotests/bfd_isis_topo1/rt1/bfdd.conf new file mode 100644 index 0000000..dbcf23f --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/bfdd.conf @@ -0,0 +1,19 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 10.0.1.2 interface eth-rt2 + detect-multiplier 3 + receive-interval 300 + transmit-interval 300 + no shutdown + ! + peer 10.0.2.2 interface eth-rt3 + detect-multiplier 3 + receive-interval 300 + transmit-interval 300 + no shutdown + ! +! diff --git a/tests/topotests/bfd_isis_topo1/rt1/isisd.conf b/tests/topotests/bfd_isis_topo1/rt1/isisd.conf new file mode 100644 index 0000000..5ae236d --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/isisd.conf @@ -0,0 +1,36 @@ +log file isisd.log +log timestamp precision 3 +! +hostname rt1 +! +password 1 +! +! debug isis events +! debug isis route-events +! debug isis spf-events +! debug isis adj-packets +! debug isis lsp-sched +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt2 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 + isis bfd +! +interface eth-rt3 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 + isis network point-to-point + isis bfd +! +router isis 1 + lsp-gen-interval 2 + net 49.0000.0000.0000.0001.00 + is-type level-1 +! diff --git a/tests/topotests/bfd_isis_topo1/rt1/step1/show_ip_route.ref b/tests/topotests/bfd_isis_topo1/rt1/step1/show_ip_route.ref new file mode 100644 index 0000000..af6e45c --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step1/show_ip_route.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step1/show_ipv6_route.ref b/tests/topotests/bfd_isis_topo1/rt1/step1/show_ipv6_route.ref new file mode 100644 index 0000000..68d3fe2 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step1/show_ipv6_route.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step2/show_bfd_peers.ref b/tests/topotests/bfd_isis_topo1/rt1/step2/show_bfd_peers.ref new file mode 100644 index 0000000..cb4083d --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step2/show_bfd_peers.ref @@ -0,0 +1,16 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_healthy.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_healthy.ref new file mode 100644 index 0000000..cb4083d --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_healthy.ref @@ -0,0 +1,16 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt2_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt2_down.ref new file mode 100644 index 0000000..f00b9f3 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt2_down.ref @@ -0,0 +1,9 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt3_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt3_down.ref new file mode 100644 index 0000000..f5bd276 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_bfd_peers_rt3_down.ref @@ -0,0 +1,9 @@ +[ + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_healthy.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_healthy.ref new file mode 100644 index 0000000..af6e45c --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_healthy.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt2_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt2_down.ref new file mode 100644 index 0000000..b8366bc --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt2_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt3_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt3_down.ref new file mode 100644 index 0000000..42bd6ab --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ip_route_rt3_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_healthy.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_healthy.ref new file mode 100644 index 0000000..68d3fe2 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_healthy.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt2_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt2_down.ref new file mode 100644 index 0000000..200053c --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt2_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt3_down.ref b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt3_down.ref new file mode 100644 index 0000000..4297f16 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/step3/show_ipv6_route_rt3_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_isis_topo1/rt1/zebra.conf b/tests/topotests/bfd_isis_topo1/rt1/zebra.conf new file mode 100644 index 0000000..7e6f788 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt1/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +log timestamp precision 3 +! +hostname rt1 +! +! debug zebra kernel +! debug zebra packet +! debug zebra events +! debug zebra rib +! +interface lo + ip address 1.1.1.1/32 + ipv6 address ::ffff:0101:0101/128 +! +interface eth-rt2 + ip address 10.0.1.1/24 +! +interface eth-rt3 + ip address 10.0.2.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_isis_topo1/rt2/bfdd.conf b/tests/topotests/bfd_isis_topo1/rt2/bfdd.conf new file mode 100644 index 0000000..d5054aa --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt2/bfdd.conf @@ -0,0 +1,13 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 10.0.1.1 interface eth-rt1 + detect-multiplier 3 + receive-interval 300 + transmit-interval 300 + no shutdown + ! +! diff --git a/tests/topotests/bfd_isis_topo1/rt2/isisd.conf b/tests/topotests/bfd_isis_topo1/rt2/isisd.conf new file mode 100644 index 0000000..ff99f18 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt2/isisd.conf @@ -0,0 +1,31 @@ +log file isisd.log +! +hostname rt2 +! +password 1 +! +! debug isis events +! debug isis route-events +! debug isis spf-events +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 + isis bfd +! +interface eth-rt5 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +router isis 1 + lsp-gen-interval 2 + net 49.0000.0000.0000.0002.00 + is-type level-1 +! diff --git a/tests/topotests/bfd_isis_topo1/rt2/step2/show_bfd_peers.ref b/tests/topotests/bfd_isis_topo1/rt2/step2/show_bfd_peers.ref new file mode 100644 index 0000000..8a90649 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt2/step2/show_bfd_peers.ref @@ -0,0 +1,9 @@ +[ + { + "peer": "10.0.1.1", + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt2/zebra.conf b/tests/topotests/bfd_isis_topo1/rt2/zebra.conf new file mode 100644 index 0000000..5788e31 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt2/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt2 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 2.2.2.2/32 + ipv6 address ::ffff:0202:0202/128 +! +interface eth-rt1 + ip address 10.0.1.2/24 +! +interface eth-rt5 + ip address 10.0.3.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_isis_topo1/rt3/bfdd.conf b/tests/topotests/bfd_isis_topo1/rt3/bfdd.conf new file mode 100644 index 0000000..fd9a5e1 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt3/bfdd.conf @@ -0,0 +1,13 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 10.0.2.1 interface eth-rt1 + detect-multiplier 3 + receive-interval 300 + transmit-interval 300 + no shutdown + ! +! diff --git a/tests/topotests/bfd_isis_topo1/rt3/isisd.conf b/tests/topotests/bfd_isis_topo1/rt3/isisd.conf new file mode 100644 index 0000000..2ad1b9c --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt3/isisd.conf @@ -0,0 +1,33 @@ +log file isisd.log +! +hostname rt3 +! +password 1 +! +! debug isis events +! debug isis route-events +! debug isis spf-events +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 + isis network point-to-point + isis bfd +! +interface eth-rt4 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +router isis 1 + lsp-gen-interval 2 + net 49.0000.0000.0000.0003.00 + is-type level-1 +! + diff --git a/tests/topotests/bfd_isis_topo1/rt3/step2/show_bfd_peers.ref b/tests/topotests/bfd_isis_topo1/rt3/step2/show_bfd_peers.ref new file mode 100644 index 0000000..13eb2a2 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt3/step2/show_bfd_peers.ref @@ -0,0 +1,9 @@ +[ + { + "peer": "10.0.2.1", + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_isis_topo1/rt3/zebra.conf b/tests/topotests/bfd_isis_topo1/rt3/zebra.conf new file mode 100644 index 0000000..78eac2e --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt3/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt3 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 3.3.3.3/32 + ipv6 address ::ffff:0303:0303/128 +! +interface eth-rt1 + ip address 10.0.2.2/24 +! +interface eth-rt4 + ip address 10.0.4.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_isis_topo1/rt4/bfdd.conf b/tests/topotests/bfd_isis_topo1/rt4/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt4/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_isis_topo1/rt4/isisd.conf b/tests/topotests/bfd_isis_topo1/rt4/isisd.conf new file mode 100644 index 0000000..e170c19 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt4/isisd.conf @@ -0,0 +1,30 @@ +log file isisd.log +! +hostname rt4 +! +password 1 +! +! debug isis events +! debug isis route-events +! debug isis spf-events +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt3 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +interface eth-rt5 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +router isis 1 + lsp-gen-interval 2 + net 49.0000.0000.0000.0004.00 + is-type level-1 +! diff --git a/tests/topotests/bfd_isis_topo1/rt4/zebra.conf b/tests/topotests/bfd_isis_topo1/rt4/zebra.conf new file mode 100644 index 0000000..a6cb573 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt4/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt4 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 4.4.4.4/32 + ipv6 address ::ffff:0404:0404/128 +! +interface eth-rt3 + ip address 10.0.4.2/24 +! +interface eth-rt5 + ip address 10.0.5.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_isis_topo1/rt5/bfdd.conf b/tests/topotests/bfd_isis_topo1/rt5/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt5/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_isis_topo1/rt5/isisd.conf b/tests/topotests/bfd_isis_topo1/rt5/isisd.conf new file mode 100644 index 0000000..3caaca7 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt5/isisd.conf @@ -0,0 +1,30 @@ +log file isisd.log +! +hostname rt5 +! +password 1 +! +! debug isis events +! debug isis route-events +! debug isis spf-events +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-rt2 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +interface eth-rt4 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 10 +! +router isis 1 + lsp-gen-interval 2 + net 49.0000.0000.0000.0005.00 + is-type level-1 +! diff --git a/tests/topotests/bfd_isis_topo1/rt5/zebra.conf b/tests/topotests/bfd_isis_topo1/rt5/zebra.conf new file mode 100644 index 0000000..33473c9 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/rt5/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt5 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 5.5.5.5/32 + ipv6 address ::ffff:0505:0505/128 +! +interface eth-rt2 + ip address 10.0.3.2/24 +! +interface eth-rt4 + ip address 10.0.5.2/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_isis_topo1/test_bfd_isis_topo1.py b/tests/topotests/bfd_isis_topo1/test_bfd_isis_topo1.py new file mode 100644 index 0000000..09e6914 --- /dev/null +++ b/tests/topotests/bfd_isis_topo1/test_bfd_isis_topo1.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_isis_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_isis_topo1.py: + + +---------+ + | | + eth-rt2 (.1) | RT1 | eth-rt3 (.1) + +----------+ 1.1.1.1 +----------+ + | | | | + | +---------+ | + | | + | 10.0.2.0/24 | + | | + | eth-rt1 | (.2) + | 10.0.1.0/24 +----+----+ + | | | + | | RT3 | + | | 3.3.3.3 | + | | | + (.2) | eth-rt1 +----+----+ + +----+----+ eth-rt4 | (.1) + | | | + | RT2 | | + | 2.2.2.2 | 10.0.4.0/24 | + | | | + +----+----+ | + (.1) | eth-rt5 eth-rt3 | (.2) + | +----+----+ + | | | + | | RT4 | + | | 4.4.4.4 | + | | | + | +----+----+ + | 10.0.3.0/24 eth-rt5 | (.1) + | | + | | + | 10.0.5.0/24 | + | | + | +---------+ | + | | | | + +----------+ RT5 +----------+ + eth-rt2 (.2) | 5.5.5.5 | eth-rt4 (.2) + | | + +---------+ + +""" + +import os +import sys +import pytest +import json +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.isisd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("rt1:eth-rt2", "rt2:eth-rt1"), + "s2": ("rt1:eth-rt3", "rt3:eth-rt1"), + "s3": ("rt2:eth-rt5", "rt5:eth-rt2"), + "s4": ("rt3:eth-rt4", "rt4:eth-rt3"), + "s5": ("rt4:eth-rt5", "rt5:eth-rt4"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def print_cmd_result(rname, command): + print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False)) + + +def router_compare_json_output(rname, command, reference, count=120, wait=0.5): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=count, wait=wait) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +## TEST STEPS + + +def test_rib_isis_step1(): + logger.info("Test (step 1): verify RIB (IPv4 and IPv6) for IS-IS") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_compare_json_output( + "rt1", "show ip route isis json", "step1/show_ip_route.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route isis json", "step1/show_ipv6_route.ref" + ) + + +def test_bfd_isis_sessions_step2(): + logger.info("Test (step 2): verify BFD peers for IS-IS") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # BFD is just used on three routers + for rt in ["rt1", "rt2", "rt3"]: + router_compare_json_output( + rt, "show bfd peers json", "step2/show_bfd_peers.ref" + ) + + +def test_bfd_isis_interface_failure_rt2_step3(): + logger.info("Test (step 2): check failover handling when RT2 goes down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt2 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt2"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + # TODO: add check for array size + router_compare_json_output( + "rt1", "show ip route isis json", "step3/show_ip_route_rt2_down.ref", 20, 1 + ) + router_compare_json_output( + "rt1", "show ipv6 route isis json", "step3/show_ipv6_route_rt2_down.ref", 20, 1 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt2_down.ref", 20, 1 + ) + + # Check recovery, this can take some time + tgen.gears["rt2"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route isis json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route isis json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_bfd_isis_interface_failure_rt3_step3(): + logger.info("Test (step 2): check failover handling when RT2 goes down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt3 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt3"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + # TODO: add check for array size + router_compare_json_output( + "rt1", "show ip route isis json", "step3/show_ip_route_rt3_down.ref", 20, 1 + ) + router_compare_json_output( + "rt1", "show ipv6 route isis json", "step3/show_ipv6_route_rt3_down.ref", 20, 1 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt3_down.ref", 20, 1 + ) + + # Check recovery, this can take some time + tgen.gears["rt3"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route isis json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route isis json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_ospf_topo1/__init__.py b/tests/topotests/bfd_ospf_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_ospf_topo1/rt1/bfdd.conf b/tests/topotests/bfd_ospf_topo1/rt1/bfdd.conf new file mode 100644 index 0000000..f34f4ca --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/bfdd.conf @@ -0,0 +1,9 @@ +log file bfdd.log +log timestamp precision 3 +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd_ospf_topo1/rt1/ospf6d.conf b/tests/topotests/bfd_ospf_topo1/rt1/ospf6d.conf new file mode 100644 index 0000000..a8ce562 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/ospf6d.conf @@ -0,0 +1,25 @@ +log file ospf6d.log +log timestamp precision 3 +! +hostname rt1 +! +password 1 +! +interface eth-rt2 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt3 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +router ospf6 + ospf6 router-id 1.1.1.1 + redistribute connected +! diff --git a/tests/topotests/bfd_ospf_topo1/rt1/ospfd.conf b/tests/topotests/bfd_ospf_topo1/rt1/ospfd.conf new file mode 100644 index 0000000..72238cc --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/ospfd.conf @@ -0,0 +1,31 @@ +log file ospfd.log +log timestamp precision 3 +! +hostname rt1 +! +password 1 +! +! debug ospf event +! debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt2 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 + ip ospf bfd +! +interface eth-rt3 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 + ip ospf bfd +! +router ospf + ospf router-id 1.1.1.1 + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ip_route.ref b/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ip_route.ref new file mode 100644 index 0000000..f354eff --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ip_route.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ipv6_route.ref b/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ipv6_route.ref new file mode 100644 index 0000000..6465efb --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step1/show_ipv6_route.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step2/show_bfd_peers.ref b/tests/topotests/bfd_ospf_topo1/rt1/step2/show_bfd_peers.ref new file mode 100644 index 0000000..63f0d50 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step2/show_bfd_peers.ref @@ -0,0 +1,26 @@ +[ + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_healthy.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_healthy.ref new file mode 100644 index 0000000..42051f9 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_healthy.ref @@ -0,0 +1,28 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt2_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt2_down.ref new file mode 100644 index 0000000..d844ee6 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt2_down.ref @@ -0,0 +1,15 @@ +[ + { + "peer": "10.0.2.2", + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt3", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt3_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt3_down.ref new file mode 100644 index 0000000..3279908 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_bfd_peers_rt3_down.ref @@ -0,0 +1,15 @@ +[ + { + "peer": "10.0.1.2", + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt2", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_healthy.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_healthy.ref new file mode 100644 index 0000000..f354eff --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_healthy.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt2_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt2_down.ref new file mode 100644 index 0000000..43eecd0 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt2_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt3_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt3_down.ref new file mode 100644 index 0000000..409af63 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ip_route_rt3_down.ref @@ -0,0 +1,74 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_healthy.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_healthy.ref new file mode 100644 index 0000000..6465efb --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_healthy.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt2_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt2_down.ref new file mode 100644 index 0000000..cfb1ef1 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt2_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt3_down.ref b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt3_down.ref new file mode 100644 index 0000000..58b44da --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/step3/show_ipv6_route_rt3_down.ref @@ -0,0 +1,70 @@ +{ + "::ffff:202:202\/128":[ + { + "prefix":"::ffff:202:202\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:303:303\/128":[ + { + "prefix":"::ffff:303:303\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:404:404\/128":[ + { + "prefix":"::ffff:404:404\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ], + "::ffff:505:505\/128":[ + { + "prefix":"::ffff:505:505\/128", + "protocol":"ospf6", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_ospf_topo1/rt1/zebra.conf b/tests/topotests/bfd_ospf_topo1/rt1/zebra.conf new file mode 100644 index 0000000..7e6f788 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt1/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +log timestamp precision 3 +! +hostname rt1 +! +! debug zebra kernel +! debug zebra packet +! debug zebra events +! debug zebra rib +! +interface lo + ip address 1.1.1.1/32 + ipv6 address ::ffff:0101:0101/128 +! +interface eth-rt2 + ip address 10.0.1.1/24 +! +interface eth-rt3 + ip address 10.0.2.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_ospf_topo1/rt2/bfdd.conf b/tests/topotests/bfd_ospf_topo1/rt2/bfdd.conf new file mode 100644 index 0000000..5baea3c --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt2/bfdd.conf @@ -0,0 +1,7 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd_ospf_topo1/rt2/ospf6d.conf b/tests/topotests/bfd_ospf_topo1/rt2/ospf6d.conf new file mode 100644 index 0000000..f04d017 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt2/ospf6d.conf @@ -0,0 +1,23 @@ +log file ospf6d.log +! +hostname rt2 +! +password 1 +! +interface eth-rt1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt5 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 2.2.2.2 + redistribute connected +! diff --git a/tests/topotests/bfd_ospf_topo1/rt2/ospfd.conf b/tests/topotests/bfd_ospf_topo1/rt2/ospfd.conf new file mode 100644 index 0000000..c5f4262 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt2/ospfd.conf @@ -0,0 +1,29 @@ +log file ospfd.log +! +hostname rt2 +! +password 1 +! +! debug ospf event +! debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt1 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 + ip ospf bfd +! +interface eth-rt5 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +router ospf + ospf router-id 2.2.2.2 + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd_ospf_topo1/rt2/step2/show_bfd_peers.ref b/tests/topotests/bfd_ospf_topo1/rt2/step2/show_bfd_peers.ref new file mode 100644 index 0000000..d6df1eb --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt2/step2/show_bfd_peers.ref @@ -0,0 +1,14 @@ +[ + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt2/zebra.conf b/tests/topotests/bfd_ospf_topo1/rt2/zebra.conf new file mode 100644 index 0000000..5788e31 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt2/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt2 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 2.2.2.2/32 + ipv6 address ::ffff:0202:0202/128 +! +interface eth-rt1 + ip address 10.0.1.2/24 +! +interface eth-rt5 + ip address 10.0.3.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_ospf_topo1/rt3/bfdd.conf b/tests/topotests/bfd_ospf_topo1/rt3/bfdd.conf new file mode 100644 index 0000000..5baea3c --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt3/bfdd.conf @@ -0,0 +1,7 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd +! diff --git a/tests/topotests/bfd_ospf_topo1/rt3/ospf6d.conf b/tests/topotests/bfd_ospf_topo1/rt3/ospf6d.conf new file mode 100644 index 0000000..faf9754 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt3/ospf6d.conf @@ -0,0 +1,23 @@ +log file ospf6d.log +! +hostname rt3 +! +password 1 +! +interface eth-rt1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast + ipv6 ospf6 bfd +! +interface eth-rt4 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 3.3.3.3 + redistribute connected +! diff --git a/tests/topotests/bfd_ospf_topo1/rt3/ospfd.conf b/tests/topotests/bfd_ospf_topo1/rt3/ospfd.conf new file mode 100644 index 0000000..e487bdd --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt3/ospfd.conf @@ -0,0 +1,29 @@ +log file ospfd.log +! +hostname rt3 +! +password 1 +! +! debug ospf event +! debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt1 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 + ip ospf bfd +! +interface eth-rt4 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +router ospf + ospf router-id 3.3.3.3 + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd_ospf_topo1/rt3/step2/show_bfd_peers.ref b/tests/topotests/bfd_ospf_topo1/rt3/step2/show_bfd_peers.ref new file mode 100644 index 0000000..d6df1eb --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt3/step2/show_bfd_peers.ref @@ -0,0 +1,14 @@ +[ + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + }, + { + "interface": "eth-rt1", + "status": "up", + "diagnostic": "ok", + "remote-diagnostic": "ok" + } +] diff --git a/tests/topotests/bfd_ospf_topo1/rt3/zebra.conf b/tests/topotests/bfd_ospf_topo1/rt3/zebra.conf new file mode 100644 index 0000000..78eac2e --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt3/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt3 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 3.3.3.3/32 + ipv6 address ::ffff:0303:0303/128 +! +interface eth-rt1 + ip address 10.0.2.2/24 +! +interface eth-rt4 + ip address 10.0.4.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_ospf_topo1/rt4/bfdd.conf b/tests/topotests/bfd_ospf_topo1/rt4/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt4/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_ospf_topo1/rt4/ospf6d.conf b/tests/topotests/bfd_ospf_topo1/rt4/ospf6d.conf new file mode 100644 index 0000000..c96093b --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt4/ospf6d.conf @@ -0,0 +1,22 @@ +log file ospf6d.log +! +hostname rt4 +! +password 1 +! +interface eth-rt3 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast +! +interface eth-rt5 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 + ipv6 ospf6 network broadcast +! +router ospf6 + ospf6 router-id 4.4.4.4 + redistribute connected +! diff --git a/tests/topotests/bfd_ospf_topo1/rt4/ospfd.conf b/tests/topotests/bfd_ospf_topo1/rt4/ospfd.conf new file mode 100644 index 0000000..560904e --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt4/ospfd.conf @@ -0,0 +1,28 @@ +log file ospfd.log +! +hostname rt4 +! +password 1 +! +! debug ospf event +! debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt3 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt5 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +router ospf + ospf router-id 4.4.4.4 + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd_ospf_topo1/rt4/zebra.conf b/tests/topotests/bfd_ospf_topo1/rt4/zebra.conf new file mode 100644 index 0000000..a6cb573 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt4/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt4 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 4.4.4.4/32 + ipv6 address ::ffff:0404:0404/128 +! +interface eth-rt3 + ip address 10.0.4.2/24 +! +interface eth-rt5 + ip address 10.0.5.1/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_ospf_topo1/rt5/bfdd.conf b/tests/topotests/bfd_ospf_topo1/rt5/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt5/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_ospf_topo1/rt5/ospf6d.conf b/tests/topotests/bfd_ospf_topo1/rt5/ospf6d.conf new file mode 100644 index 0000000..6d40d17 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt5/ospf6d.conf @@ -0,0 +1,22 @@ +log file ospf6d.log +! +hostname rt5 +! +password 1 +! +interface eth-rt2 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 network broadcast + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 +! +interface eth-rt4 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 network broadcast + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 8 +! +router ospf6 + ospf6 router-id 5.5.5.5 + redistribute connected +! diff --git a/tests/topotests/bfd_ospf_topo1/rt5/ospfd.conf b/tests/topotests/bfd_ospf_topo1/rt5/ospfd.conf new file mode 100644 index 0000000..77f5445 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt5/ospfd.conf @@ -0,0 +1,28 @@ +log file ospfd.log +! +hostname rt5 +! +password 1 +! +! debug ospf event +! debug ospf zebra +! +interface lo + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt2 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +interface eth-rt4 + ip ospf area 0.0.0.0 + ip ospf hello-interval 2 + ip ospf dead-interval 8 +! +router ospf + ospf router-id 5.5.5.5 + router-info area 0.0.0.0 +! diff --git a/tests/topotests/bfd_ospf_topo1/rt5/zebra.conf b/tests/topotests/bfd_ospf_topo1/rt5/zebra.conf new file mode 100644 index 0000000..33473c9 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/rt5/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt5 +! +! debug zebra kernel +! debug zebra packet +! +interface lo + ip address 5.5.5.5/32 + ipv6 address ::ffff:0505:0505/128 +! +interface eth-rt2 + ip address 10.0.3.2/24 +! +interface eth-rt4 + ip address 10.0.5.2/24 +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bfd_ospf_topo1/test_bfd_ospf_topo1.py b/tests/topotests/bfd_ospf_topo1/test_bfd_ospf_topo1.py new file mode 100755 index 0000000..b9e8b73 --- /dev/null +++ b/tests/topotests/bfd_ospf_topo1/test_bfd_ospf_topo1.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_ospf_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_ospf_topo1.py: + + +---------+ + | | + eth-rt2 (.1) | RT1 | eth-rt3 (.1) + +----------+ 1.1.1.1 +----------+ + | | | | + | +---------+ | + | | + | 10.0.2.0/24 | + | | + | eth-rt1 | (.2) + | 10.0.1.0/24 +----+----+ + | | | + | | RT3 | + | | 3.3.3.3 | + | | | + (.2) | eth-rt1 +----+----+ + +----+----+ eth-rt4 | (.1) + | | | + | RT2 | | + | 2.2.2.2 | 10.0.4.0/24 | + | | | + +----+----+ | + (.1) | eth-rt5 eth-rt3 | (.2) + | +----+----+ + | | | + | | RT4 | + | | 4.4.4.4 | + | | | + | +----+----+ + | 10.0.3.0/24 eth-rt5 | (.1) + | | + | | + | 10.0.5.0/24 | + | | + | +---------+ | + | | | | + +----------+ RT5 +----------+ + eth-rt2 (.2) | 5.5.5.5 | eth-rt4 (.2) + | | + +---------+ + +""" + +import os +import sys +import pytest +import json +from time import sleep +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.ospfd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("rt1:eth-rt2", "rt2:eth-rt1"), + "s2": ("rt1:eth-rt3", "rt3:eth-rt1"), + "s3": ("rt2:eth-rt5", "rt5:eth-rt2"), + "s4": ("rt3:eth-rt4", "rt4:eth-rt3"), + "s5": ("rt4:eth-rt5", "rt5:eth-rt4"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def print_cmd_result(rname, command): + print(get_topogen().gears[rname].vtysh_cmd(command, isjson=False)) + + +def router_compare_json_output(rname, command, reference, count=40, wait=2): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 80 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=count, wait=wait) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +## TEST STEPS + + +def test_rib_ospf_step1(): + logger.info("Test (step 1): verify RIB for OSPF") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step1/show_ip_route.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step1/show_ipv6_route.ref" + ) + + +def test_bfd_ospf_sessions_step2(): + logger.info("Test (step 2): verify BFD peers for OSPF") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # BFD is just used on three routers + for rt in ["rt1", "rt2", "rt3"]: + router_compare_json_output( + rt, "show bfd peers json", "step2/show_bfd_peers.ref" + ) + + +def test_bfd_ospf_interface_failure_rt2_step3(): + logger.info("Test (step 3): Check failover handling with RT2 down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt2 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt2"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + topotest.sleep(2, "Wait for BFD down notification") + + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_rt2_down.ref", 10, 2 + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_rt2_down.ref", 10, 2 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt2_down.ref", 10, 2 + ) + + # Check recovery, this can take some time + tgen.gears["rt2"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_bfd_ospf_interface_failure_rt3_step3(): + logger.info("Test (step 3): Check failover handling with RT3 down") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Let's kill the interface on rt3 and see what happens with the RIB and BFD on rt1 + tgen.gears["rt3"].link_enable("eth-rt1", enabled=False) + + # By default BFD provides a recovery time of 900ms plus jitter, so let's wait + # initial 2 seconds to let the CI not suffer. + topotest.sleep(2, "Wait for BFD down notification") + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_rt3_down.ref", 10, 2 + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_rt3_down.ref", 10, 2 + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_rt3_down.ref", 10, 2 + ) + + # Check recovery, this can take some time + tgen.gears["rt3"].link_enable("eth-rt1", enabled=True) + + router_compare_json_output( + "rt1", "show ip route ospf json", "step3/show_ip_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show ipv6 route ospf json", "step3/show_ipv6_route_healthy.ref" + ) + router_compare_json_output( + "rt1", "show bfd peers json", "step3/show_bfd_peers_healthy.ref" + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_profiles_topo1/__init__.py b/tests/topotests/bfd_profiles_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_profiles_topo1/r1/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r1/bfd-peers-initial.json new file mode 100644 index 0000000..86a7e51 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r1/bfd-peers-initial.json @@ -0,0 +1,38 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r1-eth1", + "multihop": false, + "peer": "172.16.100.2", + "receive-interval": 800, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 800, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r1-eth0", + "multihop": false, + "peer": "172.16.0.1", + "receive-interval": 800, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 800, + "remote-transmit-interval": 800, + "status": "up", + "transmit-interval": 800, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r1/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r1/bfdd.conf new file mode 100644 index 0000000..c2ac9c1 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r1/bfdd.conf @@ -0,0 +1,15 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + profile slowtx + receive-interval 800 + transmit-interval 800 + echo receive-interval 400 + ! + peer 172.16.0.1 interface r1-eth0 + profile slowtx + no shutdown + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r1/ospfd.conf b/tests/topotests/bfd_profiles_topo1/r1/ospfd.conf new file mode 100644 index 0000000..373a0c5 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r1/ospfd.conf @@ -0,0 +1,11 @@ +interface r1-eth1 + ip ospf area 0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf bfd + ip ospf bfd profile slowtx +! +router ospf + ospf router-id 10.254.254.1 + redistribute connected +! diff --git a/tests/topotests/bfd_profiles_topo1/r1/zebra.conf b/tests/topotests/bfd_profiles_topo1/r1/zebra.conf new file mode 100644 index 0000000..4b7982b --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r1/zebra.conf @@ -0,0 +1,9 @@ +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ip address 172.16.0.2/24 +! +interface r1-eth1 + ip address 172.16.100.1/24 +! diff --git a/tests/topotests/bfd_profiles_topo1/r2/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r2/bfd-peers-initial.json new file mode 100644 index 0000000..503f776 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r2/bfd-peers-initial.json @@ -0,0 +1,40 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r2-eth0", + "multihop": false, + "peer": "172.16.0.2", + "receive-interval": 800, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 800, + "remote-transmit-interval": 800, + "remote-echo-receive-interval": 400, + "status": "up", + "transmit-interval": 800, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r2-eth1", + "multihop": false, + "peer": "172.16.1.1", + "receive-interval": 250, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 250, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r2/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r2/bfdd.conf new file mode 100644 index 0000000..b68eecb --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r2/bfdd.conf @@ -0,0 +1,19 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + profile slowtx + receive-interval 800 + transmit-interval 800 + ! + profile fasttx + receive-interval 250 + transmit-interval 250 + echo receive-interval disabled + ! + peer 172.16.0.2 interface r2-eth0 + profile slowtx + no shutdown + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r2/bgpd.conf b/tests/topotests/bfd_profiles_topo1/r2/bgpd.conf new file mode 100644 index 0000000..1aab1d1 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r2/bgpd.conf @@ -0,0 +1,21 @@ +! debug bgp neighbor-events +! +router bgp 100 + bgp router-id 10.254.254.2 + no bgp ebgp-requires-policy + neighbor 172.16.1.1 remote-as 100 + neighbor 172.16.1.1 timers 3 10 + neighbor 172.16.1.1 bfd profile fasttx + neighbor 2001:db8:2::2 remote-as 200 + neighbor 2001:db8:2::2 timers 3 10 + neighbor 2001:db8:2::2 ebgp-multihop 2 + neighbor 2001:db8:2::2 bfd profile slowtx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 172.16.1.1 activate + neighbor 2001:db8:2::2 activate + exit-address-family +! diff --git a/tests/topotests/bfd_profiles_topo1/r2/zebra.conf b/tests/topotests/bfd_profiles_topo1/r2/zebra.conf new file mode 100644 index 0000000..6acef13 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r2/zebra.conf @@ -0,0 +1,10 @@ +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ip address 172.16.0.1/24 +! +interface r2-eth1 + ip address 172.16.1.2/24 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bfd_profiles_topo1/r3/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r3/bfd-peers-initial.json new file mode 100644 index 0000000..d987a0a --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r3/bfd-peers-initial.json @@ -0,0 +1,40 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r3-eth0", + "multihop": false, + "peer": "172.16.1.2", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 250, + "remote-transmit-interval": 250, + "remote-echo-receive-interval": 0, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r3-eth1", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 250, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 250, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r3/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r3/bfdd.conf new file mode 100644 index 0000000..f3a86ed --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r3/bfdd.conf @@ -0,0 +1,10 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + profile fasttx + receive-interval 250 + transmit-interval 250 + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r3/bgpd.conf b/tests/topotests/bfd_profiles_topo1/r3/bgpd.conf new file mode 100644 index 0000000..65647b3 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r3/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 100 + bgp router-id 10.254.254.3 + neighbor 172.16.1.2 remote-as 100 + neighbor 172.16.1.2 timers 3 10 + neighbor 172.16.1.2 bfd profile DOES_NOT_EXIST + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 172.16.1.2 activate + exit-address-family + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r3/isisd.conf b/tests/topotests/bfd_profiles_topo1/r3/isisd.conf new file mode 100644 index 0000000..3bba2b0 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r3/isisd.conf @@ -0,0 +1,17 @@ +hostname r3 +! +! debug isis adj-packets +! debug isis events +! debug isis update-packets +! +interface r3-eth1 + ipv6 router isis lan + isis circuit-type level-1 + isis bfd + isis bfd profile fasttx +! +router isis lan + lsp-gen-interval 2 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00 + redistribute ipv6 connected level-1 +! diff --git a/tests/topotests/bfd_profiles_topo1/r3/zebra.conf b/tests/topotests/bfd_profiles_topo1/r3/zebra.conf new file mode 100644 index 0000000..2297bfa --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r3/zebra.conf @@ -0,0 +1,12 @@ +ipv6 forwarding +! +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ip address 172.16.1.1/24 + ipv6 address 2001:db8:1::1/64 +! +interface r3-eth1 + ipv6 address 2001:db8:2::1/64 +! diff --git a/tests/topotests/bfd_profiles_topo1/r4/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r4/bfd-peers-initial.json new file mode 100644 index 0000000..9ab7479 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/bfd-peers-initial.json @@ -0,0 +1,41 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r4-eth0", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 250, + "remote-transmit-interval": 250, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r4-eth1", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 250, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 300, + "remote-transmit-interval": 300, + "status": "up", + "transmit-interval": 250, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r4/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r4/bfdd.conf new file mode 100644 index 0000000..a5d1e25 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/bfdd.conf @@ -0,0 +1,10 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + profile fast-tx + receive-interval 250 + transmit-interval 250 + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r4/bgpd.conf b/tests/topotests/bfd_profiles_topo1/r4/bgpd.conf new file mode 100644 index 0000000..12d6827 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/bgpd.conf @@ -0,0 +1,18 @@ +! debug bgp neighbor-events +! +router bgp 200 + bgp router-id 10.254.254.4 + no bgp ebgp-requires-policy + neighbor 2001:db8:1::2 remote-as 100 + neighbor 2001:db8:1::2 timers 3 10 + neighbor 2001:db8:1::2 ebgp-multihop 2 + neighbor 2001:db8:1::2 bfd profile DOES_NOT_EXIST + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::2 activate + exit-address-family + ! +! diff --git a/tests/topotests/bfd_profiles_topo1/r4/isisd.conf b/tests/topotests/bfd_profiles_topo1/r4/isisd.conf new file mode 100644 index 0000000..18009ce --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/isisd.conf @@ -0,0 +1,17 @@ +hostname r4 +! +! debug isis adj-packets +! debug isis events +! debug isis update-packets +! +interface r4-eth0 + ipv6 router isis lan + isis circuit-type level-1 + isis bfd + isis bfd profile DOES_NOT_EXIST +! +router isis lan + lsp-gen-interval 2 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0002.00 + redistribute ipv6 connected level-1 +! diff --git a/tests/topotests/bfd_profiles_topo1/r4/ospf6d.conf b/tests/topotests/bfd_profiles_topo1/r4/ospf6d.conf new file mode 100644 index 0000000..948874c --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/ospf6d.conf @@ -0,0 +1,10 @@ +interface r4-eth1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 bfd profile fast-tx + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 10 +! +router ospf6 + ospf6 router-id 10.254.254.4 + redistribute connected +! diff --git a/tests/topotests/bfd_profiles_topo1/r4/zebra.conf b/tests/topotests/bfd_profiles_topo1/r4/zebra.conf new file mode 100644 index 0000000..753041f --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r4/zebra.conf @@ -0,0 +1,12 @@ +ipv6 forwarding +! +interface lo + ip address 10.254.254.4/32 +! +interface r4-eth0 + ipv6 address 2001:db8:2::2/64 +! +interface r4-eth1 + ip address 172.16.3.1/24 + ipv6 address 2001:db8:3::1/64 +! diff --git a/tests/topotests/bfd_profiles_topo1/r5/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r5/bfd-peers-initial.json new file mode 100644 index 0000000..0fe56d5 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r5/bfd-peers-initial.json @@ -0,0 +1,21 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r5-eth0", + "local": "*", + "multihop": false, + "peer": "*", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 250, + "remote-transmit-interval": 250, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r5/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r5/bfdd.conf new file mode 100644 index 0000000..670fd44 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r5/bfdd.conf @@ -0,0 +1,11 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + ! profile is commented out on purpose. + !profile fasttx + ! receive-interval 250 + ! transmit-interval 250 + !! +! diff --git a/tests/topotests/bfd_profiles_topo1/r5/ospf6d.conf b/tests/topotests/bfd_profiles_topo1/r5/ospf6d.conf new file mode 100644 index 0000000..f6e8dc3 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r5/ospf6d.conf @@ -0,0 +1,10 @@ +interface r5-eth0 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 bfd + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 10 +! +router ospf6 + ospf6 router-id 10.254.254.5 + redistribute connected +! diff --git a/tests/topotests/bfd_profiles_topo1/r5/zebra.conf b/tests/topotests/bfd_profiles_topo1/r5/zebra.conf new file mode 100644 index 0000000..de8ae16 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r5/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.5/32 +! +interface r5-eth0 + ipv6 address 2001:db8:3::2/64 +! diff --git a/tests/topotests/bfd_profiles_topo1/r6/bfd-peers-initial.json b/tests/topotests/bfd_profiles_topo1/r6/bfd-peers-initial.json new file mode 100644 index 0000000..ec973eb --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r6/bfd-peers-initial.json @@ -0,0 +1,20 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "id": "*", + "interface": "r6-eth0", + "multihop": false, + "peer": "172.16.100.1", + "receive-interval": 300, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-id": "*", + "remote-receive-interval": 800, + "remote-transmit-interval": 800, + "status": "up", + "transmit-interval": 300, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_profiles_topo1/r6/bfdd.conf b/tests/topotests/bfd_profiles_topo1/r6/bfdd.conf new file mode 100644 index 0000000..670fd44 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r6/bfdd.conf @@ -0,0 +1,11 @@ +! debug bfd peer +! debug bfd network +! debug bfd zebra +! +bfd + ! profile is commented out on purpose. + !profile fasttx + ! receive-interval 250 + ! transmit-interval 250 + !! +! diff --git a/tests/topotests/bfd_profiles_topo1/r6/ospfd.conf b/tests/topotests/bfd_profiles_topo1/r6/ospfd.conf new file mode 100644 index 0000000..d8fce34 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r6/ospfd.conf @@ -0,0 +1,10 @@ +interface r6-eth0 + ip ospf area 0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.6 + redistribute connected +! diff --git a/tests/topotests/bfd_profiles_topo1/r6/zebra.conf b/tests/topotests/bfd_profiles_topo1/r6/zebra.conf new file mode 100644 index 0000000..c0804b9 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/r6/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.6/32 +! +interface r6-eth0 + ip address 172.16.100.2/24 +! diff --git a/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.dot b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.dot new file mode 100644 index 0000000..a393609 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.dot @@ -0,0 +1,97 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-profiles-topo1"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + r5 [ + shape=doubleoctagon + label="r5", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n172.16.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n172.16.1.0/24\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n2001:db8:2::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw4 [ + shape=oval, + label="sw4\n2001:db8:3::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw5 [ + shape=oval, + label="sw5\n172.16.100.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + + r2 -- sw2 [label="eth1"]; + r3 -- sw2 [label="eth0"]; + + r3 -- sw3 [label="eth1"]; + r4 -- sw3 [label="eth0"]; + + r4 -- sw4 [label="eth1"]; + r5 -- sw4 [label="eth0"]; + + r1 -- sw5 [label="eth1"]; + r6 -- sw5 [label="eth0"]; +} diff --git a/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.png b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.png new file mode 100644 index 0000000..775fae1 Binary files /dev/null and b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.png differ diff --git a/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.py b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.py new file mode 100644 index 0000000..78841b3 --- /dev/null +++ b/tests/topotests/bfd_profiles_topo1/test_bfd_profiles_topo1.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_profiles_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_profiles_topo1.py: Test the FRR BFD profile protocol integration. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd, pytest.mark.isisd, pytest.mark.ospfd] + + +def setup_module(mod): + "Sets up the pytest environment" + + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3"), + "s3": ("r3", "r4"), + "s4": ("r4", "r5"), + "s5": ("r1", "r6"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + daemon_file = "{}/{}/bfdd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BFD, daemon_file) + + daemon_file = "{}/{}/bgpd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BGP, daemon_file) + + daemon_file = "{}/{}/isisd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_ISIS, daemon_file) + + daemon_file = "{}/{}/ospfd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF, daemon_file) + + daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF6, daemon_file) + + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_ZEBRA, daemon_file) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_wait_protocols_convergence(): + "Wait for all protocols to converge" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for protocols to converge") + + def expect_loopback_route(router, iptype, route, proto): + "Wait until route is present on RIB for protocol." + logger.info("waiting route {} in {}".format(route, router)) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + "show {} route json".format(iptype), + {route: [{"protocol": proto}]}, + ) + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = '"{}" OSPF convergence failure'.format(router) + assert result is None, assertmsg + + # Wait for R1 <-> R6 convergence. + expect_loopback_route("r1", "ip", "10.254.254.6/32", "ospf") + + # Wait for R6 <-> R1 convergence. + expect_loopback_route("r6", "ip", "10.254.254.1/32", "ospf") + + # Wait for R2 <-> R3 convergence. + expect_loopback_route("r2", "ip", "10.254.254.3/32", "bgp") + + # Wait for R3 <-> R2 convergence. + expect_loopback_route("r3", "ip", "10.254.254.2/32", "bgp") + + # Wait for R3 <-> R4 convergence. + expect_loopback_route("r3", "ipv6", "2001:db8:3::/64", "isis") + + # Wait for R4 <-> R3 convergence. + expect_loopback_route("r4", "ipv6", "2001:db8:1::/64", "isis") + + # Wait for R4 <-> R5 convergence. + expect_loopback_route("r4", "ipv6", "2001:db8:3::/64", "ospf6") + + # Wait for R5 <-> R4 convergence. + expect_loopback_route("r5", "ipv6", "2001:db8:2::/64", "ospf6") + + +def test_bfd_profile_values(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up and checking profile values") + + for router in tgen.routers().values(): + json_file = "{}/{}/bfd-peers-initial.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=12, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_topo1/__init__.py b/tests/topotests/bfd_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_topo1/r1/bfdd.conf b/tests/topotests/bfd_topo1/r1/bfdd.conf new file mode 100644 index 0000000..b9efbaf --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/bfdd.conf @@ -0,0 +1,11 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.2 + echo-mode + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo1/r1/bgp_prefixes.json b/tests/topotests/bfd_topo1/r1/bgp_prefixes.json new file mode 100644 index 0000000..1262f5e --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "102 103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "102 104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_topo1/r1/bgp_summary.json b/tests/topotests/bfd_topo1/r1/bgp_summary.json new file mode 100644 index 0000000..fa07d60 --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 101, + "peers": { + "192.168.0.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_topo1/r1/bgpd.conf b/tests/topotests/bfd_topo1/r1/bgpd.conf new file mode 100644 index 0000000..57bde1f --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 101 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.2 remote-as 102 + neighbor 192.168.0.2 timers 3 10 + neighbor 192.168.0.2 bfd + address-family ipv4 unicast + network 10.254.254.1/32 + exit-address-family +! diff --git a/tests/topotests/bfd_topo1/r1/peers.json b/tests/topotests/bfd_topo1/r1/peers.json new file mode 100644 index 0000000..f49768f --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/peers.json @@ -0,0 +1,8 @@ +[ + { + "remote-receive-interval": 1000, + "remote-transmit-interval": 500, + "peer": "192.168.0.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_topo1/r1/zebra.conf b/tests/topotests/bfd_topo1/r1/zebra.conf new file mode 100644 index 0000000..a14cd7a --- /dev/null +++ b/tests/topotests/bfd_topo1/r1/zebra.conf @@ -0,0 +1,3 @@ +interface r1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bfd_topo1/r2/bfdd.conf b/tests/topotests/bfd_topo1/r2/bfdd.conf new file mode 100644 index 0000000..0d1e17e --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/bfdd.conf @@ -0,0 +1,17 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.1 + receive-interval 1000 + transmit-interval 500 + echo-mode + no shutdown + ! + peer 192.168.1.1 + echo-mode + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo1/r2/bgp_prefixes.json b/tests/topotests/bfd_topo1/r2/bgp_prefixes.json new file mode 100644 index 0000000..0d47c0f --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.0.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.1", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.1.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.1", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.2.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.1", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_topo1/r2/bgp_summary.json b/tests/topotests/bfd_topo1/r2/bgp_summary.json new file mode 100644 index 0000000..c0ef11a --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/bgp_summary.json @@ -0,0 +1,19 @@ +{ + "ipv4Unicast": { + "as": 102, + "peers": { + "192.168.0.1": { + "remoteAs": 101, + "state": "Established" + }, + "192.168.1.1": { + "remoteAs": 103, + "state": "Established" + }, + "192.168.2.1": { + "remoteAs": 104, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_topo1/r2/bgpd.conf b/tests/topotests/bfd_topo1/r2/bgpd.conf new file mode 100644 index 0000000..50d75ab --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/bgpd.conf @@ -0,0 +1,16 @@ +router bgp 102 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.1 remote-as 101 + neighbor 192.168.0.1 timers 3 10 + neighbor 192.168.0.1 bfd + neighbor 192.168.1.1 remote-as 103 + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.1.1 bfd + neighbor 192.168.2.1 remote-as 104 + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.2.1 bfd + address-family ipv4 unicast + network 10.254.254.2/32 + exit-address-family +! diff --git a/tests/topotests/bfd_topo1/r2/peers.json b/tests/topotests/bfd_topo1/r2/peers.json new file mode 100644 index 0000000..267459c --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/peers.json @@ -0,0 +1,17 @@ +[ + { + "peer": "192.168.0.1", + "status": "up" + }, + { + "remote-echo-receive-interval": 100, + "peer": "192.168.1.1", + "status": "up" + }, + { + "remote-transmit-interval": 2000, + "remote-receive-interval": 2000, + "peer": "192.168.2.1", + "status": "up" + } +] diff --git a/tests/topotests/bfd_topo1/r2/zebra.conf b/tests/topotests/bfd_topo1/r2/zebra.conf new file mode 100644 index 0000000..568abe7 --- /dev/null +++ b/tests/topotests/bfd_topo1/r2/zebra.conf @@ -0,0 +1,9 @@ +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.2/24 +! +interface r2-eth2 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bfd_topo1/r3/bfdd.conf b/tests/topotests/bfd_topo1/r3/bfdd.conf new file mode 100644 index 0000000..e091a1c --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/bfdd.conf @@ -0,0 +1,12 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.1.2 + echo-interval 100 + echo-mode + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo1/r3/bgp_prefixes.json b/tests/topotests/bfd_topo1/r3/bgp_prefixes.json new file mode 100644 index 0000000..36fca17 --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "102 101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "102 104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_topo1/r3/bgp_summary.json b/tests/topotests/bfd_topo1/r3/bgp_summary.json new file mode 100644 index 0000000..d478333 --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 103, + "peers": { + "192.168.1.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_topo1/r3/bgpd.conf b/tests/topotests/bfd_topo1/r3/bgpd.conf new file mode 100644 index 0000000..ce6055d --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 103 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as 102 + neighbor 192.168.1.2 timers 3 10 + neighbor 192.168.1.2 bfd + address-family ipv4 unicast + network 10.254.254.3/32 + exit-address-family +! diff --git a/tests/topotests/bfd_topo1/r3/peers.json b/tests/topotests/bfd_topo1/r3/peers.json new file mode 100644 index 0000000..ef38008 --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/peers.json @@ -0,0 +1,6 @@ +[ + { + "peer": "192.168.1.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_topo1/r3/zebra.conf b/tests/topotests/bfd_topo1/r3/zebra.conf new file mode 100644 index 0000000..b4fd80f --- /dev/null +++ b/tests/topotests/bfd_topo1/r3/zebra.conf @@ -0,0 +1,3 @@ +interface r3-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bfd_topo1/r4/bfdd.conf b/tests/topotests/bfd_topo1/r4/bfdd.conf new file mode 100644 index 0000000..63dd738 --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/bfdd.conf @@ -0,0 +1,12 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.2.2 + transmit-interval 2000 + receive-interval 2000 + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo1/r4/bgp_prefixes.json b/tests/topotests/bfd_topo1/r4/bgp_prefixes.json new file mode 100644 index 0000000..efe7d47 --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "102 101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "102 103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_topo1/r4/bgp_summary.json b/tests/topotests/bfd_topo1/r4/bgp_summary.json new file mode 100644 index 0000000..7d81784 --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 104, + "peers": { + "192.168.2.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_topo1/r4/bgpd.conf b/tests/topotests/bfd_topo1/r4/bgpd.conf new file mode 100644 index 0000000..0d032b4 --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 104 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.2.2 remote-as 102 + neighbor 192.168.2.2 timers 3 10 + neighbor 192.168.2.2 bfd + address-family ipv4 unicast + network 10.254.254.4/32 + exit-address-family +! diff --git a/tests/topotests/bfd_topo1/r4/peers.json b/tests/topotests/bfd_topo1/r4/peers.json new file mode 100644 index 0000000..3714008 --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/peers.json @@ -0,0 +1,6 @@ +[ + { + "peer": "192.168.2.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_topo1/r4/zebra.conf b/tests/topotests/bfd_topo1/r4/zebra.conf new file mode 100644 index 0000000..afdd44b --- /dev/null +++ b/tests/topotests/bfd_topo1/r4/zebra.conf @@ -0,0 +1,3 @@ +interface r4-eth0 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bfd_topo1/test_bfd_topo1.dot b/tests/topotests/bfd_topo1/test_bfd_topo1.dot new file mode 100644 index 0000000..c84ace2 --- /dev/null +++ b/tests/topotests/bfd_topo1/test_bfd_topo1.dot @@ -0,0 +1,73 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo1"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n192.168.1.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n192.168.2.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0\n.1"]; + r2 -- sw1 [label="eth0\n.2"]; + + r3 -- sw2 [label="eth0\n.1"]; + r2 -- sw2 [label="eth1\n.2"]; + + r4 -- sw3 [label="eth0\n.1"]; + r2 -- sw3 [label="eth2\n.2"]; +} diff --git a/tests/topotests/bfd_topo1/test_bfd_topo1.jpg b/tests/topotests/bfd_topo1/test_bfd_topo1.jpg new file mode 100644 index 0000000..4d6d56e Binary files /dev/null and b/tests/topotests/bfd_topo1/test_bfd_topo1.jpg differ diff --git a/tests/topotests/bfd_topo1/test_bfd_topo1.py b/tests/topotests/bfd_topo1/test_bfd_topo1.py new file mode 100644 index 0000000..1b77493 --- /dev/null +++ b/tests/topotests/bfd_topo1/test_bfd_topo1.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_topo1.py: Test the FRR BFD daemon. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3"), + "s3": ("r2", "r4"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + # Verify that we are using the proper version and that the BFD + # daemon exists. + for router in router_list.values(): + # Check for Version + if router.has_version("<", "5.1"): + tgen.set_error("Unsupported FRR version") + break + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_bfd_connection(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up") + + for router in tgen.routers().values(): + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bgp_convergence(): + "Assert that BGP is converging." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers to go up") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_summary.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=125, wait=1.0) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_bgp_fast_convergence(): + "Assert that BGP is converging before setting a link down." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers converge") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_prefixes.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=40, wait=0.5) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_bfd_fast_convergence(): + """ + Assert that BFD notices the link down after simulating network + failure. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Disable r1-eth0 link. + tgen.gears["r1"].link_enable("r1-eth0", enabled=False) + + # Wait the minimum time we can before checking that BGP/BFD + # converged. + logger.info("waiting for BFD converge") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + # Load the same file as previous test, but expect R1 to be down. + if router.name == "r1": + for peer in expected: + if peer["peer"] == "192.168.0.2": + peer["status"] = "down" + else: + for peer in expected: + if peer["peer"] == "192.168.0.1": + peer["status"] = "down" + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert res is None, assertmsg + + +def test_bgp_fast_reconvergence(): + "Assert that BGP is converging after setting a link down." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for BGP re convergence") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_prefixes.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + + # Load the same file as previous test, but set networks to None + # to test absence. + if router.name == "r1": + expected["routes"]["10.254.254.2/32"] = None + expected["routes"]["10.254.254.3/32"] = None + expected["routes"]["10.254.254.4/32"] = None + else: + expected["routes"]["10.254.254.1/32"] = None + + test_func = partial( + topotest.router_json_cmp, router, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_topo2/__init__.py b/tests/topotests/bfd_topo2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_topo2/r1/bfdd.conf b/tests/topotests/bfd_topo2/r1/bfdd.conf new file mode 100644 index 0000000..df8baeb --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/bfdd.conf @@ -0,0 +1,10 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 2001:db8:4::1 multihop local-address 2001:db8:1::1 + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo2/r1/bgpd.conf b/tests/topotests/bfd_topo2/r1/bgpd.conf new file mode 100644 index 0000000..0918796 --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 101 + bgp router-id 10.254.254.1 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r1-eth0 interface peer-group r2g + neighbor r1-eth0 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo2/r1/ipv4_routes.json b/tests/topotests/bfd_topo2/r1/ipv4_routes.json new file mode 100644 index 0000000..650c0a8 --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/ipv4_routes.json @@ -0,0 +1,59 @@ +{ + "10.0.3.0/24": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r1/ipv6_routes.json b/tests/topotests/bfd_topo2/r1/ipv6_routes.json new file mode 100644 index 0000000..50c1f9a --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/ipv6_routes.json @@ -0,0 +1,53 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "active": true, + "afi": "ipv6" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r1/peers.json b/tests/topotests/bfd_topo2/r1/peers.json new file mode 100644 index 0000000..9bce991 --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/peers.json @@ -0,0 +1,31 @@ +[ + { + "multihop":true, + "peer":"2001:db8:4::1", + "local":"2001:db8:1::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + }, + { + "multihop":false, + "interface":"r1-eth0", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_topo2/r1/zebra.conf b/tests/topotests/bfd_topo2/r1/zebra.conf new file mode 100644 index 0000000..7fe5eb2 --- /dev/null +++ b/tests/topotests/bfd_topo2/r1/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bfd_topo2/r2/bfdd.conf b/tests/topotests/bfd_topo2/r2/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_topo2/r2/bgpd.conf b/tests/topotests/bfd_topo2/r2/bgpd.conf new file mode 100644 index 0000000..55d4856 --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 102 + bgp router-id 10.254.254.2 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r2-eth0 interface peer-group r2g + neighbor r2-eth0 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo2/r2/ipv4_routes.json b/tests/topotests/bfd_topo2/r2/ipv4_routes.json new file mode 100644 index 0000000..3d49b17 --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/ipv4_routes.json @@ -0,0 +1,92 @@ +{ + "10.0.3.0/24": [ + { + "distance": 110, + "protocol": "ospf", + "metric": 10, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "interfaceName": "r2-eth1" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth1", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.3/32": [ + { + "distance": 110, + "protocol": "ospf", + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.3/32", + "nexthops": [ + { + "interfaceName": "r2-eth1", + "ip": "10.0.3.1", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "nexthops": [ + { + "interfaceName": "r2-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r2/ipv6_routes.json b/tests/topotests/bfd_topo2/r2/ipv6_routes.json new file mode 100644 index 0000000..4f3c74c --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/ipv6_routes.json @@ -0,0 +1,53 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "metric": 10, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "interfaceName": "r2-eth2" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth2", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r2/ospf6d.conf b/tests/topotests/bfd_topo2/r2/ospf6d.conf new file mode 100644 index 0000000..524e2c9 --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/ospf6d.conf @@ -0,0 +1,11 @@ +interface r2-eth2 + ipv6 ospf6 area 0.0.0.1 + ipv6 ospf6 bfd + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 10 +! +router ospf6 + ospf6 router-id 10.254.254.2 + redistribute connected + redistribute bgp +! diff --git a/tests/topotests/bfd_topo2/r2/ospfd.conf b/tests/topotests/bfd_topo2/r2/ospfd.conf new file mode 100644 index 0000000..c786f1f --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/ospfd.conf @@ -0,0 +1,11 @@ +interface r2-eth1 + ip ospf area 0.0.0.1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.2 + redistribute connected + redistribute bgp +! diff --git a/tests/topotests/bfd_topo2/r2/peers.json b/tests/topotests/bfd_topo2/r2/peers.json new file mode 100644 index 0000000..ec2135c --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/peers.json @@ -0,0 +1,45 @@ +[ + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth0", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-receive-interval": 50, + "remote-diagnostic": "ok" + }, + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth2", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-receive-interval": 50, + "remote-diagnostic": "ok" + }, + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r2-eth1", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-receive-interval": 50, + "remote-diagnostic": "ok", + "peer": "10.0.3.1" + } +] diff --git a/tests/topotests/bfd_topo2/r2/zebra.conf b/tests/topotests/bfd_topo2/r2/zebra.conf new file mode 100644 index 0000000..cccbf65 --- /dev/null +++ b/tests/topotests/bfd_topo2/r2/zebra.conf @@ -0,0 +1,15 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ipv6 address 2001:db8:1::2/64 +! +interface r2-eth1 + ip address 10.0.3.2/24 +! +interface r2-eth2 + ipv6 address 2001:db8:4::2/64 +! diff --git a/tests/topotests/bfd_topo2/r3/bfdd.conf b/tests/topotests/bfd_topo2/r3/bfdd.conf new file mode 100644 index 0000000..ee7144d --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/bfdd.conf @@ -0,0 +1,5 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! diff --git a/tests/topotests/bfd_topo2/r3/ipv4_routes.json b/tests/topotests/bfd_topo2/r3/ipv4_routes.json new file mode 100644 index 0000000..e96fdc0 --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/ipv4_routes.json @@ -0,0 +1,93 @@ +{ + "10.0.3.0/24": [ + { + "distance": 110, + "protocol": "ospf", + "metric": 10, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "interfaceName": "r3-eth0" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.0.3.0/24", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r3-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.3/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.3/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.2/32": [ + { + "distance": 110, + "protocol": "ospf", + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.2/32", + "nexthops": [ + { + "interfaceName": "r3-eth0", + "ip": "10.0.3.2", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 110, + "protocol": "ospf", + "metric": 20, + "selected": true, + "installed": true, + "prefix": "10.254.254.1/32", + "nexthops": [ + { + "interfaceName": "r3-eth0", + "ip": "10.0.3.2", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv4" + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r3/ipv6_routes.json b/tests/topotests/bfd_topo2/r3/ipv6_routes.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/ipv6_routes.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/topotests/bfd_topo2/r3/ospfd.conf b/tests/topotests/bfd_topo2/r3/ospfd.conf new file mode 100644 index 0000000..932ab4d --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/ospfd.conf @@ -0,0 +1,10 @@ +interface r3-eth0 + ip ospf area 0.0.0.1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf bfd +! +router ospf + ospf router-id 10.254.254.3 + redistribute connected +! diff --git a/tests/topotests/bfd_topo2/r3/peers.json b/tests/topotests/bfd_topo2/r3/peers.json new file mode 100644 index 0000000..c19c980 --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/peers.json @@ -0,0 +1,17 @@ +[ + { + "status": "up", + "transmit-interval": 300, + "remote-receive-interval": 300, + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "diagnostic": "ok", + "multihop": false, + "interface": "r3-eth0", + "remote-transmit-interval": 300, + "receive-interval": 300, + "remote-echo-receive-interval": 50, + "remote-diagnostic": "ok", + "peer": "10.0.3.2" + } +] diff --git a/tests/topotests/bfd_topo2/r3/zebra.conf b/tests/topotests/bfd_topo2/r3/zebra.conf new file mode 100644 index 0000000..96fd08c --- /dev/null +++ b/tests/topotests/bfd_topo2/r3/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ip address 10.0.3.1/24 +! diff --git a/tests/topotests/bfd_topo2/r4/bfdd.conf b/tests/topotests/bfd_topo2/r4/bfdd.conf new file mode 100644 index 0000000..c1e8d28 --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/bfdd.conf @@ -0,0 +1,10 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 2001:db8:1::1 multihop local-address 2001:db8:4::1 + no shutdown + ! +! diff --git a/tests/topotests/bfd_topo2/r4/ipv4_routes.json b/tests/topotests/bfd_topo2/r4/ipv4_routes.json new file mode 100644 index 0000000..dc394aa --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/ipv4_routes.json @@ -0,0 +1,21 @@ +{ + "10.254.254.4/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "10.254.254.4/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r4/ipv6_routes.json b/tests/topotests/bfd_topo2/r4/ipv6_routes.json new file mode 100644 index 0000000..eb571d5 --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/ipv6_routes.json @@ -0,0 +1,53 @@ +{ + "2001:db8:4::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "metric": 10, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "active": true, + "directlyConnected": true, + "interfaceName": "r4-eth0" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "installed": true, + "prefix": "2001:db8:4::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r4-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "2001:db8:1::/64": [ + { + "distance": 110, + "protocol": "ospf6", + "metric": 20, + "selected": true, + "installed": true, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "interfaceName": "r4-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bfd_topo2/r4/ospf6d.conf b/tests/topotests/bfd_topo2/r4/ospf6d.conf new file mode 100644 index 0000000..2f38c51 --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/ospf6d.conf @@ -0,0 +1,10 @@ +interface r4-eth0 + ipv6 ospf6 area 0.0.0.1 + ipv6 ospf6 bfd + ipv6 ospf6 hello-interval 2 + ipv6 ospf6 dead-interval 10 +! +router ospf6 + ospf6 router-id 10.254.254.4 + redistribute connected +! diff --git a/tests/topotests/bfd_topo2/r4/peers.json b/tests/topotests/bfd_topo2/r4/peers.json new file mode 100644 index 0000000..dd26b9b --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/peers.json @@ -0,0 +1,31 @@ +[ + { + "multihop":true, + "peer":"2001:db8:1::1", + "local":"2001:db8:4::1", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval": 50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + }, + { + "multihop":false, + "interface":"r4-eth0", + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":300, + "transmit-interval":300, + "echo-receive-interval": 50, + "echo-transmit-interval":0, + "remote-receive-interval":300, + "remote-transmit-interval":300, + "remote-echo-receive-interval":50 + } +] diff --git a/tests/topotests/bfd_topo2/r4/zebra.conf b/tests/topotests/bfd_topo2/r4/zebra.conf new file mode 100644 index 0000000..e4f8fd8 --- /dev/null +++ b/tests/topotests/bfd_topo2/r4/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 10.254.254.4/32 +! +interface r4-eth0 + ipv6 address 2001:db8:4::1/64 +! diff --git a/tests/topotests/bfd_topo2/test_bfd_topo2.dot b/tests/topotests/bfd_topo2/test_bfd_topo2.dot new file mode 100644 index 0000000..6b68fb3 --- /dev/null +++ b/tests/topotests/bfd_topo2/test_bfd_topo2.dot @@ -0,0 +1,73 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo2"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n10.0.3.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n2001:db8:4::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + + r2 -- sw2 [label="eth1"]; + r3 -- sw2 [label="eth0"]; + + r2 -- sw3 [label="eth2"]; + r4 -- sw3 [label="eth0"]; +} diff --git a/tests/topotests/bfd_topo2/test_bfd_topo2.jpg b/tests/topotests/bfd_topo2/test_bfd_topo2.jpg new file mode 100644 index 0000000..35fe562 Binary files /dev/null and b/tests/topotests/bfd_topo2/test_bfd_topo2.jpg differ diff --git a/tests/topotests/bfd_topo2/test_bfd_topo2.py b/tests/topotests/bfd_topo2/test_bfd_topo2.py new file mode 100644 index 0000000..636dbf3 --- /dev/null +++ b/tests/topotests/bfd_topo2/test_bfd_topo2.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_topo2.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_topo2.py: Test the FRR BFD daemon with multihop and BGP +unnumbered. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd, pytest.mark.ospfd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3"), + "s3": ("r2", "r4"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) + router.load_config(TopoRouter.RD_ZEBRA, daemon_file) + + daemon_file = "{}/{}/bfdd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BFD, daemon_file) + + daemon_file = "{}/{}/bgpd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BGP, daemon_file) + + daemon_file = "{}/{}/ospfd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF, daemon_file) + + daemon_file = "{}/{}/ospf6d.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_OSPF6, daemon_file) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged before checking for the BFD + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check IPv4 routing tables. + logger.info("Checking IPv4 routes for convergence") + for router in tgen.routers().values(): + json_file = "{}/{}/ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ip route json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check IPv6 routing tables. + logger.info("Checking IPv6 routes for convergence") + for router in tgen.routers().values(): + json_file = "{}/{}/ipv6_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ipv6 route json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bfd_connection(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up") + + for router in tgen.routers().values(): + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_topo3/__init__.py b/tests/topotests/bfd_topo3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_topo3/r1/bfd-peers.json b/tests/topotests/bfd_topo3/r1/bfd-peers.json new file mode 100644 index 0000000..3ce8d97 --- /dev/null +++ b/tests/topotests/bfd_topo3/r1/bfd-peers.json @@ -0,0 +1,68 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "local": "2001:db8:1::1", + "minimum-ttl": 253, + "multihop": true, + "passive-mode": true, + "peer": "2001:db8:3::1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r1-eth0", + "local": "2001:db8:1::1", + "multihop": false, + "passive-mode": true, + "peer": "2001:db8:1::2", + "receive-interval": 600, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 600, + "remote-transmit-interval": 600, + "status": "up", + "uptime": "*", + "transmit-interval": 600 + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "local": "192.168.1.1", + "minimum-ttl": 254, + "multihop": true, + "passive-mode": true, + "peer": "192.168.2.1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + } +] diff --git a/tests/topotests/bfd_topo3/r1/bfdd.conf b/tests/topotests/bfd_topo3/r1/bfdd.conf new file mode 100644 index 0000000..60f129b --- /dev/null +++ b/tests/topotests/bfd_topo3/r1/bfdd.conf @@ -0,0 +1,17 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + profile fast-tx + receive-interval 600 + transmit-interval 600 + passive-mode + ! + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + passive-mode + ! +! diff --git a/tests/topotests/bfd_topo3/r1/bgpd.conf b/tests/topotests/bfd_topo3/r1/bgpd.conf new file mode 100644 index 0000000..4c75d66 --- /dev/null +++ b/tests/topotests/bfd_topo3/r1/bgpd.conf @@ -0,0 +1,23 @@ +router bgp 100 + no bgp ebgp-requires-policy + neighbor 2001:db8:1::2 remote-as internal + neighbor 2001:db8:1::2 timers 3 10 + neighbor 2001:db8:1::2 bfd profile fast-tx + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.2.1 ebgp-multihop 2 + neighbor 192.168.2.1 bfd profile slow-tx + neighbor 2001:db8:3::1 remote-as external + neighbor 2001:db8:3::1 timers 3 10 + neighbor 2001:db8:3::1 ebgp-multihop 3 + neighbor 2001:db8:3::1 bfd profile slow-tx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::2 activate + neighbor 192.168.2.1 activate + neighbor 2001:db8:3::1 activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo3/r1/zebra.conf b/tests/topotests/bfd_topo3/r1/zebra.conf new file mode 100644 index 0000000..64aee48 --- /dev/null +++ b/tests/topotests/bfd_topo3/r1/zebra.conf @@ -0,0 +1,10 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bfd_topo3/r2/bfd-peers.json b/tests/topotests/bfd_topo3/r2/bfd-peers.json new file mode 100644 index 0000000..a40f7e4 --- /dev/null +++ b/tests/topotests/bfd_topo3/r2/bfd-peers.json @@ -0,0 +1,46 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r2-eth0", + "local": "2001:db8:1::2", + "multihop": false, + "passive-mode": false, + "peer": "2001:db8:1::1", + "receive-interval": 600, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 600, + "remote-transmit-interval": 600, + "status": "up", + "uptime": "*", + "transmit-interval": 600 + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r2-eth1", + "local": "2001:db8:2::2", + "multihop": false, + "passive-mode": false, + "peer": "2001:db8:2::1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + } +] diff --git a/tests/topotests/bfd_topo3/r2/bfdd.conf b/tests/topotests/bfd_topo3/r2/bfdd.conf new file mode 100644 index 0000000..8297043 --- /dev/null +++ b/tests/topotests/bfd_topo3/r2/bfdd.conf @@ -0,0 +1,15 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + profile fast-tx + receive-interval 600 + transmit-interval 600 + ! + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + ! +! diff --git a/tests/topotests/bfd_topo3/r2/bgpd.conf b/tests/topotests/bfd_topo3/r2/bgpd.conf new file mode 100644 index 0000000..7522576 --- /dev/null +++ b/tests/topotests/bfd_topo3/r2/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 100 + no bgp ebgp-requires-policy + neighbor 2001:db8:1::1 remote-as internal + neighbor 2001:db8:1::1 timers 3 10 + neighbor 2001:db8:1::1 bfd profile fast-tx + neighbor 2001:db8:2::1 remote-as external + neighbor 2001:db8:2::1 timers 3 10 + neighbor 2001:db8:2::1 bfd profile slow-tx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::1 activate + neighbor 2001:db8:2::1 activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo3/r2/zebra.conf b/tests/topotests/bfd_topo3/r2/zebra.conf new file mode 100644 index 0000000..c7e22d4 --- /dev/null +++ b/tests/topotests/bfd_topo3/r2/zebra.conf @@ -0,0 +1,14 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 + ipv6 address 2001:db8:1::2/64 +! +interface r2-eth1 + ip address 192.168.2.2/24 + ipv6 address 2001:db8:2::2/64 +! diff --git a/tests/topotests/bfd_topo3/r3/bfd-peers.json b/tests/topotests/bfd_topo3/r3/bfd-peers.json new file mode 100644 index 0000000..2182b26 --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/bfd-peers.json @@ -0,0 +1,68 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r3-eth1", + "local": "2001:db8:3::2", + "multihop": false, + "passive-mode": false, + "peer": "2001:db8:3::1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r3-eth0", + "local": "2001:db8:2::1", + "multihop": false, + "passive-mode": false, + "peer": "2001:db8:2::2", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "local": "192.168.2.1", + "minimum-ttl": 254, + "multihop": true, + "passive-mode": false, + "peer": "192.168.1.1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000 + } +] diff --git a/tests/topotests/bfd_topo3/r3/bfd-static-down.json b/tests/topotests/bfd_topo3/r3/bfd-static-down.json new file mode 100644 index 0000000..60752d3 --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/bfd-static-down.json @@ -0,0 +1,12 @@ +{ + "path-list": { + "ipv4-multicast": [], + "ipv4-unicast": [], + "ipv6-unicast": [ + { + "prefix": "2001:db8:5::\/64", + "vrf": "default", + "installed": false + } + ] + }} diff --git a/tests/topotests/bfd_topo3/r3/bfd-static.json b/tests/topotests/bfd_topo3/r3/bfd-static.json new file mode 100644 index 0000000..c65060c --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/bfd-static.json @@ -0,0 +1,13 @@ +{ + "path-list": { + "ipv4-multicast": [], + "ipv4-unicast": [], + "ipv6-unicast": [ + { + "prefix": "2001:db8:5::\/64", + "vrf": "default", + "installed": true + } + ] + } +} diff --git a/tests/topotests/bfd_topo3/r3/bfdd.conf b/tests/topotests/bfd_topo3/r3/bfdd.conf new file mode 100644 index 0000000..51ce2ac --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/bfdd.conf @@ -0,0 +1,11 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + ! +! diff --git a/tests/topotests/bfd_topo3/r3/bgpd.conf b/tests/topotests/bfd_topo3/r3/bgpd.conf new file mode 100644 index 0000000..82adf8b --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 300 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.1.1 ebgp-multihop 2 + neighbor 192.168.1.1 bfd profile slow-tx + neighbor 2001:db8:2::2 remote-as external + neighbor 2001:db8:2::2 timers 3 10 + neighbor 2001:db8:2::2 bfd profile slow-tx + neighbor 2001:db8:3::1 remote-as external + neighbor 2001:db8:3::1 timers 3 10 + neighbor 2001:db8:3::1 bfd profile slow-tx + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 192.168.1.1 activate + neighbor 2001:db8:2::2 activate + neighbor 2001:db8:3::1 activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo3/r3/staticd.conf b/tests/topotests/bfd_topo3/r3/staticd.conf new file mode 100644 index 0000000..44f91f3 --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/staticd.conf @@ -0,0 +1 @@ +ipv6 route 2001:db8:5::/64 2001:db8:4::3 bfd multi-hop profile slow-tx diff --git a/tests/topotests/bfd_topo3/r3/zebra.conf b/tests/topotests/bfd_topo3/r3/zebra.conf new file mode 100644 index 0000000..14248fb --- /dev/null +++ b/tests/topotests/bfd_topo3/r3/zebra.conf @@ -0,0 +1,14 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.3/32 +! +interface r3-eth0 + ip address 192.168.2.1/24 + ipv6 address 2001:db8:2::1/64 +! +interface r3-eth1 + ip address 192.168.3.2/24 + ipv6 address 2001:db8:3::2/64 +! diff --git a/tests/topotests/bfd_topo3/r4/bfd-peers.json b/tests/topotests/bfd_topo3/r4/bfd-peers.json new file mode 100644 index 0000000..4f71d75 --- /dev/null +++ b/tests/topotests/bfd_topo3/r4/bfd-peers.json @@ -0,0 +1,90 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "local": "2001:db8:3::1", + "minimum-ttl": 253, + "multihop": true, + "passive-mode": false, + "peer": "2001:db8:1::1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000, + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "interface": "r4-eth0", + "local": "2001:db8:3::1", + "multihop": false, + "passive-mode": false, + "peer": "2001:db8:3::2", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "uptime": "*", + "transmit-interval": 2000, + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "multihop": false, + "passive-mode": false, + "peer": "192.168.4.3", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "transmit-interval": 2000, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "multihop": false, + "passive-mode": false, + "peer": "192.168.4.2", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "transmit-interval": 2000, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_topo3/r4/bfdd.conf b/tests/topotests/bfd_topo3/r4/bfdd.conf new file mode 100644 index 0000000..e5fc164 --- /dev/null +++ b/tests/topotests/bfd_topo3/r4/bfdd.conf @@ -0,0 +1,16 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + ! + profile slow-tx-mh + receive-interval 2000 + transmit-interval 2000 + minimum-ttl 250 + ! +! diff --git a/tests/topotests/bfd_topo3/r4/bgpd.conf b/tests/topotests/bfd_topo3/r4/bgpd.conf new file mode 100644 index 0000000..bfad78a --- /dev/null +++ b/tests/topotests/bfd_topo3/r4/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 400 + no bgp ebgp-requires-policy + neighbor 2001:db8:3::2 remote-as external + neighbor 2001:db8:3::2 timers 3 10 + neighbor 2001:db8:3::2 bfd profile slow-tx + neighbor 2001:db8:1::1 remote-as external + neighbor 2001:db8:1::1 timers 3 10 + neighbor 2001:db8:1::1 ebgp-multihop 3 + neighbor 2001:db8:1::1 bfd profile slow-tx-mh + address-family ipv4 unicast + redistribute connected + redistribute static + exit-address-family + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::1 activate + neighbor 2001:db8:3::2 activate + exit-address-family +! diff --git a/tests/topotests/bfd_topo3/r4/staticd.conf b/tests/topotests/bfd_topo3/r4/staticd.conf new file mode 100644 index 0000000..3b1c5bf --- /dev/null +++ b/tests/topotests/bfd_topo3/r4/staticd.conf @@ -0,0 +1,2 @@ +ip route 10.254.254.5/32 192.168.4.2 bfd profile slow-tx +ip route 10.254.254.6/32 192.168.4.3 bfd profile slow-tx diff --git a/tests/topotests/bfd_topo3/r4/zebra.conf b/tests/topotests/bfd_topo3/r4/zebra.conf new file mode 100644 index 0000000..2574941 --- /dev/null +++ b/tests/topotests/bfd_topo3/r4/zebra.conf @@ -0,0 +1,14 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.4/32 +! +interface r4-eth0 + ip address 192.168.3.1/24 + ipv6 address 2001:db8:3::1/64 +! +interface r4-eth1 + ip address 192.168.4.1/24 + ipv6 address 2001:db8:4::1/64 +! diff --git a/tests/topotests/bfd_topo3/r5/bfd-peers.json b/tests/topotests/bfd_topo3/r5/bfd-peers.json new file mode 100644 index 0000000..777b1dd --- /dev/null +++ b/tests/topotests/bfd_topo3/r5/bfd-peers.json @@ -0,0 +1,23 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "multihop": false, + "passive-mode": false, + "peer": "192.168.4.1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "transmit-interval": 2000, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_topo3/r5/bfdd.conf b/tests/topotests/bfd_topo3/r5/bfdd.conf new file mode 100644 index 0000000..ec62d8d --- /dev/null +++ b/tests/topotests/bfd_topo3/r5/bfdd.conf @@ -0,0 +1,11 @@ +!debug bfd network +!debug bfd peer +!debug bfd zebra +! +bfd + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + minimum-ttl 250 + ! +! diff --git a/tests/topotests/bfd_topo3/r5/staticd.conf b/tests/topotests/bfd_topo3/r5/staticd.conf new file mode 100644 index 0000000..9828cff --- /dev/null +++ b/tests/topotests/bfd_topo3/r5/staticd.conf @@ -0,0 +1,2 @@ +ip route 0.0.0.0/0 192.168.4.1 +ip route 10.254.254.4/32 192.168.4.1 bfd profile slow-tx diff --git a/tests/topotests/bfd_topo3/r5/zebra.conf b/tests/topotests/bfd_topo3/r5/zebra.conf new file mode 100644 index 0000000..f84ce7e --- /dev/null +++ b/tests/topotests/bfd_topo3/r5/zebra.conf @@ -0,0 +1,10 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.5/32 +! +interface r5-eth0 + ip address 192.168.4.2/24 + ipv6 address 2001:db8:4::2/64 +! diff --git a/tests/topotests/bfd_topo3/r6/bfd-peers.json b/tests/topotests/bfd_topo3/r6/bfd-peers.json new file mode 100644 index 0000000..4de451d --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/bfd-peers.json @@ -0,0 +1,46 @@ +[ + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "multihop": false, + "passive-mode": false, + "peer": "192.168.4.1", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "transmit-interval": 2000, + "uptime": "*", + "vrf": "default" + }, + { + "detect-multiplier": 3, + "diagnostic": "ok", + "echo-receive-interval": 50, + "echo-transmit-interval": 0, + "id": "*", + "local": "2001:db8:4::3", + "minimum-ttl": 2, + "multihop": true, + "passive-mode": false, + "peer": "2001:db8:3::2", + "receive-interval": 2000, + "remote-detect-multiplier": 3, + "remote-diagnostic": "ok", + "remote-echo-receive-interval": 50, + "remote-id": "*", + "remote-receive-interval": 2000, + "remote-transmit-interval": 2000, + "status": "up", + "transmit-interval": 2000, + "uptime": "*", + "vrf": "default" + } +] diff --git a/tests/topotests/bfd_topo3/r6/bfd-static-down.json b/tests/topotests/bfd_topo3/r6/bfd-static-down.json new file mode 100644 index 0000000..4dadff2 --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/bfd-static-down.json @@ -0,0 +1,19 @@ +{ + "path-list": { + "ipv4-multicast": [], + "ipv4-unicast": [ + { + "installed": true, + "prefix": "10.254.254.4/32", + "vrf": "default" + } + ], + "ipv6-unicast": [ + { + "prefix": "2001:db8:1::\/64", + "vrf": "default", + "installed": false + } + ] + } +} diff --git a/tests/topotests/bfd_topo3/r6/bfd-static.json b/tests/topotests/bfd_topo3/r6/bfd-static.json new file mode 100644 index 0000000..d042889 --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/bfd-static.json @@ -0,0 +1,19 @@ +{ + "path-list": { + "ipv4-multicast": [], + "ipv4-unicast": [ + { + "installed": true, + "prefix": "10.254.254.4/32", + "vrf": "default" + } + ], + "ipv6-unicast": [ + { + "prefix": "2001:db8:1::\/64", + "vrf": "default", + "installed": true + } + ] + } +} diff --git a/tests/topotests/bfd_topo3/r6/bfdd.conf b/tests/topotests/bfd_topo3/r6/bfdd.conf new file mode 100644 index 0000000..ec62d8d --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/bfdd.conf @@ -0,0 +1,11 @@ +!debug bfd network +!debug bfd peer +!debug bfd zebra +! +bfd + profile slow-tx + receive-interval 2000 + transmit-interval 2000 + minimum-ttl 250 + ! +! diff --git a/tests/topotests/bfd_topo3/r6/staticd.conf b/tests/topotests/bfd_topo3/r6/staticd.conf new file mode 100644 index 0000000..28da508 --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/staticd.conf @@ -0,0 +1,5 @@ +ip route 0.0.0.0/0 192.168.4.1 +ip route 10.254.254.4/32 192.168.4.1 bfd profile slow-tx +! +ipv6 route 2001:db8:3::/64 2001:db8:4::1 +ipv6 route 2001:db8:1::/64 2001:db8:3::2 bfd multi-hop source 2001:db8:4::3 profile slow-tx diff --git a/tests/topotests/bfd_topo3/r6/zebra.conf b/tests/topotests/bfd_topo3/r6/zebra.conf new file mode 100644 index 0000000..b8f2ea4 --- /dev/null +++ b/tests/topotests/bfd_topo3/r6/zebra.conf @@ -0,0 +1,10 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.6/32 +! +interface r6-eth0 + ip address 192.168.4.3/24 + ipv6 address 2001:db8:4::3/64 +! diff --git a/tests/topotests/bfd_topo3/test_bfd_topo3.dot b/tests/topotests/bfd_topo3/test_bfd_topo3.dot new file mode 100644 index 0000000..8d18783 --- /dev/null +++ b/tests/topotests/bfd_topo3/test_bfd_topo3.dot @@ -0,0 +1,95 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo3"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + r5 [ + shape=doubleoctagon + label="r5", + fillcolor="#f08080", + style=filled, + ]; + r6 [ + shape=doubleoctagon + label="r6", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n192.168.1.0/24\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n192.168.2.0/24\n2001:db8:2::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n192.168.3.0/24\n2001:db8:3::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + sw4 [ + shape=oval, + label="sw4\n192.168.4.0/24\n2001:db8:4::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0\n.1"]; + r2 -- sw1 [label="eth0\n.2"]; + + r3 -- sw2 [label="eth0\n.1"]; + r2 -- sw2 [label="eth1\n.2"]; + + r4 -- sw3 [label="eth0\n.1"]; + r3 -- sw3 [label="eth2\n.2"]; + + r4 -- sw4 [label="eth1\n.1"]; + r5 -- sw4 [label="eth0\n.2"]; + r6 -- sw4 [label="eth0\n.3"]; +} diff --git a/tests/topotests/bfd_topo3/test_bfd_topo3.jpg b/tests/topotests/bfd_topo3/test_bfd_topo3.jpg new file mode 100644 index 0000000..f100eae Binary files /dev/null and b/tests/topotests/bfd_topo3/test_bfd_topo3.jpg differ diff --git a/tests/topotests/bfd_topo3/test_bfd_topo3.py b/tests/topotests/bfd_topo3/test_bfd_topo3.py new file mode 100644 index 0000000..d7b2542 --- /dev/null +++ b/tests/topotests/bfd_topo3/test_bfd_topo3.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_topo3.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bfd_topo3.py: Test the FRR BFD daemon multi hop. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd] + + +def setup_module(mod): + "Sets up the pytest environment" + topodef = { + "s1": ("r1", "r2"), + "s2": ("r2", "r3"), + "s3": ("r3", "r4"), + "s4": ("r4", "r5", "r6"), + } + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + daemon_file = "{}/{}/bfdd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BFD, daemon_file) + + daemon_file = "{}/{}/zebra.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_ZEBRA, daemon_file) + + daemon_file = "{}/{}/bgpd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_BGP, daemon_file) + + daemon_file = "{}/{}/staticd.conf".format(CWD, rname) + if os.path.isfile(daemon_file): + router.load_config(TopoRouter.RD_STATIC, daemon_file) + + # Initialize all routers. + tgen.start_router() + + +def expect_static_bfd_output(router, filename): + "Load JSON file and compare with 'show bfd peer json'" + + tgen = get_topogen() + + logger.info("waiting BFD configuration on router {}".format(router)) + bfd_config = json.loads(open("{}/{}/{}.json".format(CWD, router, filename)).read()) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + "show bfd static route json", + bfd_config, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" BFD static route status failure'.format(router) + assert result is None, assertmsg + + +def expect_route_missing(router, iptype, route): + "Wait until route is present on RIB for protocol." + + tgen = get_topogen() + + logger.info("waiting route {} to disapear in {}".format(route, router)) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + "show {} route json".format(iptype), + {route: None}, + ) + rv, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" convergence failure'.format(router) + assert result is None, assertmsg + + +def test_wait_bgp_convergence(): + "Wait for BGP to converge" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for protocols to converge") + + def expect_loopback_route(router, iptype, route, proto): + "Wait until route is present on RIB for protocol." + logger.info("waiting route {} in {}".format(route, router)) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + "show {} route json".format(iptype), + {route: [{"protocol": proto}]}, + ) + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = '"{}" OSPF convergence failure'.format(router) + assert result is None, assertmsg + + # Wait for R1 <-> R2 convergence. + expect_loopback_route("r1", "ip", "10.254.254.2/32", "bgp") + # Wait for R1 <-> R3 convergence. + expect_loopback_route("r1", "ip", "10.254.254.3/32", "bgp") + # Wait for R1 <-> R4 convergence. + expect_loopback_route("r1", "ip", "10.254.254.4/32", "bgp") + # Wait for R1 <-> R5 convergence. + expect_loopback_route("r1", "ip", "10.254.254.5/32", "bgp") + # Wait for R1 <-> R6 convergence. + expect_loopback_route("r1", "ip", "10.254.254.6/32", "bgp") + + # Wait for R2 <-> R1 convergence. + expect_loopback_route("r2", "ip", "10.254.254.1/32", "bgp") + # Wait for R2 <-> R3 convergence. + expect_loopback_route("r2", "ip", "10.254.254.3/32", "bgp") + # Wait for R2 <-> R4 convergence. + expect_loopback_route("r2", "ip", "10.254.254.4/32", "bgp") + # Wait for R2 <-> R5 convergence. + expect_loopback_route("r2", "ip", "10.254.254.5/32", "bgp") + # Wait for R2 <-> R6 convergence. + expect_loopback_route("r2", "ip", "10.254.254.6/32", "bgp") + + # Wait for R3 <-> R1 convergence. + expect_loopback_route("r3", "ip", "10.254.254.1/32", "bgp") + # Wait for R3 <-> R2 convergence. + expect_loopback_route("r3", "ip", "10.254.254.2/32", "bgp") + # Wait for R3 <-> R4 convergence. + expect_loopback_route("r3", "ip", "10.254.254.4/32", "bgp") + # Wait for R3 <-> R5 convergence. + expect_loopback_route("r3", "ip", "10.254.254.5/32", "bgp") + # Wait for R3 <-> R6 convergence. + expect_loopback_route("r3", "ip", "10.254.254.6/32", "bgp") + + # Wait for R4 <-> R1 convergence. + expect_loopback_route("r4", "ip", "10.254.254.1/32", "bgp") + # Wait for R4 <-> R2 convergence. + expect_loopback_route("r4", "ip", "10.254.254.2/32", "bgp") + # Wait for R4 <-> R3 convergence. + expect_loopback_route("r4", "ip", "10.254.254.3/32", "bgp") + # Wait for R4 <-> R5 convergence. + expect_loopback_route("r4", "ip", "10.254.254.5/32", "static") + # Wait for R4 <-> R6 convergence. + expect_loopback_route("r4", "ip", "10.254.254.6/32", "static") + + # Wait for R5 <-> R6 convergence. + expect_loopback_route("r3", "ipv6", "2001:db8:5::/64", "static") + # Wait for R6 <-> R5 convergence. + expect_loopback_route("r6", "ipv6", "2001:db8:1::/64", "static") + + +def test_wait_bfd_convergence(): + "Wait for BFD to converge" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("test BFD configurations") + + def expect_bfd_configuration(router): + "Load JSON file and compare with 'show bfd peer json'" + logger.info("waiting BFD configuration on router {}".format(router)) + bfd_config = json.loads(open("{}/{}/bfd-peers.json".format(CWD, router)).read()) + test_func = partial( + topotest.router_json_cmp, + tgen.gears[router], + "show bfd peers json", + bfd_config, + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=1) + assertmsg = '"{}" BFD configuration failure'.format(router) + assert result is None, assertmsg + + expect_bfd_configuration("r1") + expect_bfd_configuration("r2") + expect_bfd_configuration("r3") + expect_bfd_configuration("r4") + expect_bfd_configuration("r5") + expect_bfd_configuration("r6") + + +def test_static_route_monitoring_convergence(): + "Test static route monitoring output." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("test BFD static route status") + + expect_static_bfd_output("r3", "bfd-static") + expect_static_bfd_output("r6", "bfd-static") + + +def test_static_route_monitoring_wrong_source(): + "Test that static monitoring fails if setting a wrong source." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("test route wrong ") + + tgen.gears["r3"].vtysh_cmd( + """ +configure +ipv6 route 2001:db8:5::/64 2001:db8:4::3 bfd multi-hop source 2001:db8:4::2 profile slow-tx +""" + ) + + expect_route_missing("r3", "ipv6", "2001:db8:5::/64") + + +def test_static_route_monitoring_unset_source(): + "Test that static monitoring fails if setting a wrong source." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("test route wrong ") + + tgen.gears["r3"].vtysh_cmd( + """ +configure +ipv6 route 2001:db8:5::/64 2001:db8:4::3 bfd multi-hop profile slow-tx +""" + ) + + expect_static_bfd_output("r3", "bfd-static") + expect_static_bfd_output("r6", "bfd-static") + + +def test_expect_static_rib_removal(): + "Test that route got removed from RIB (staticd and bgpd)." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Setting r4 link down ...") + + tgen.gears["r4"].link_enable("r4-eth0", False) + + expect_static_bfd_output("r3", "bfd-static-down") + expect_static_bfd_output("r6", "bfd-static-down") + + expect_route_missing("r1", "ip", "10.254.254.5/32") + expect_route_missing("r2", "ip", "10.254.254.5/32") + expect_route_missing("r3", "ip", "10.254.254.5/32") + expect_route_missing("r3", "ipv6", "2001:db8:5::/64") + expect_route_missing("r6", "ipv6", "2001:db8:1::/64") + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_vrf_topo1/__init__.py b/tests/topotests/bfd_vrf_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_vrf_topo1/r1/bfdd.conf b/tests/topotests/bfd_vrf_topo1/r1/bfdd.conf new file mode 100644 index 0000000..0476df7 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/bfdd.conf @@ -0,0 +1,15 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.2 vrf r1-bfd-cust1 + receive-interval 1000 + transmit-interval 1000 + echo-mode + echo transmit-interval 1000 + echo receive-interval 1000 + no shutdown + ! +! diff --git a/tests/topotests/bfd_vrf_topo1/r1/bgp_prefixes.json b/tests/topotests/bfd_vrf_topo1/r1/bgp_prefixes.json new file mode 100644 index 0000000..1262f5e --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "102 103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "102 104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.0.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r1/bgp_summary.json b/tests/topotests/bfd_vrf_topo1/r1/bgp_summary.json new file mode 100644 index 0000000..fa07d60 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 101, + "peers": { + "192.168.0.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r1/bgpd.conf b/tests/topotests/bfd_vrf_topo1/r1/bgpd.conf new file mode 100644 index 0000000..cf72f30 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 101 vrf r1-bfd-cust1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.2 remote-as 102 + neighbor 192.168.0.2 timers 3 10 +! neighbor 192.168.0.2 ebgp-multihop 10 + neighbor 192.168.0.2 bfd + address-family ipv4 unicast + network 10.254.254.1/32 + exit-address-family +! diff --git a/tests/topotests/bfd_vrf_topo1/r1/peers.json b/tests/topotests/bfd_vrf_topo1/r1/peers.json new file mode 100644 index 0000000..57cea71 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/peers.json @@ -0,0 +1,8 @@ +[ + { + "remote-receive-interval": 1000, + "remote-transmit-interval": 1000, + "peer": "192.168.0.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_vrf_topo1/r1/zebra.conf b/tests/topotests/bfd_vrf_topo1/r1/zebra.conf new file mode 100644 index 0000000..62ed36f --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r1/zebra.conf @@ -0,0 +1,3 @@ +interface r1-eth0 vrf r1-bfd-cust1 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bfd_vrf_topo1/r2/bfdd.conf b/tests/topotests/bfd_vrf_topo1/r2/bfdd.conf new file mode 100644 index 0000000..69edd15 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/bfdd.conf @@ -0,0 +1,23 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.1 vrf r2-bfd-cust1 + receive-interval 1000 + transmit-interval 1000 + echo-mode + echo transmit-interval 1000 + echo receive-interval 1000 + no shutdown + ! + peer 192.168.1.1 vrf r2-bfd-cust1 + receive-interval 1000 + transmit-interval 1000 + echo-mode + echo transmit-interval 1000 + echo receive-interval 1000 + no shutdown + ! +! diff --git a/tests/topotests/bfd_vrf_topo1/r2/bgp_prefixes.json b/tests/topotests/bfd_vrf_topo1/r2/bgp_prefixes.json new file mode 100644 index 0000000..0d47c0f --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.0.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.0.1", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.1.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.1", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.2.1", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.1", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r2/bgp_summary.json b/tests/topotests/bfd_vrf_topo1/r2/bgp_summary.json new file mode 100644 index 0000000..c0ef11a --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/bgp_summary.json @@ -0,0 +1,19 @@ +{ + "ipv4Unicast": { + "as": 102, + "peers": { + "192.168.0.1": { + "remoteAs": 101, + "state": "Established" + }, + "192.168.1.1": { + "remoteAs": 103, + "state": "Established" + }, + "192.168.2.1": { + "remoteAs": 104, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r2/bgpd.conf b/tests/topotests/bfd_vrf_topo1/r2/bgpd.conf new file mode 100644 index 0000000..132011c --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/bgpd.conf @@ -0,0 +1,16 @@ +router bgp 102 vrf r2-bfd-cust1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.1 remote-as 101 + neighbor 192.168.0.1 timers 3 10 + neighbor 192.168.0.1 bfd + neighbor 192.168.1.1 remote-as 103 + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.1.1 bfd + neighbor 192.168.2.1 remote-as 104 + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.2.1 bfd + address-family ipv4 unicast + network 10.254.254.2/32 + exit-address-family +! diff --git a/tests/topotests/bfd_vrf_topo1/r2/peers.json b/tests/topotests/bfd_vrf_topo1/r2/peers.json new file mode 100644 index 0000000..0a1c342 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/peers.json @@ -0,0 +1,17 @@ +[ + { + "peer": "192.168.0.1", + "status": "up" + }, + { + "remote-echo-receive-interval": 1000, + "peer": "192.168.1.1", + "status": "up" + }, + { + "remote-transmit-interval": 2000, + "remote-receive-interval": 2000, + "peer": "192.168.2.1", + "status": "up" + } +] diff --git a/tests/topotests/bfd_vrf_topo1/r2/zebra.conf b/tests/topotests/bfd_vrf_topo1/r2/zebra.conf new file mode 100644 index 0000000..1e817b1 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r2/zebra.conf @@ -0,0 +1,9 @@ +interface r2-eth0 vrf r2-bfd-cust1 + ip address 192.168.0.2/24 +! +interface r2-eth1 vrf r2-bfd-cust1 + ip address 192.168.1.2/24 +! +interface r2-eth2 vrf r2-bfd-cust1 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bfd_vrf_topo1/r3/bfdd.conf b/tests/topotests/bfd_vrf_topo1/r3/bfdd.conf new file mode 100644 index 0000000..00162b5 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/bfdd.conf @@ -0,0 +1,14 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.1.2 vrf r3-bfd-cust1 + receive-interval 1000 + transmit-interval 1000 + echo-interval 1000 + echo-mode + no shutdown + ! +! diff --git a/tests/topotests/bfd_vrf_topo1/r3/bgp_prefixes.json b/tests/topotests/bfd_vrf_topo1/r3/bgp_prefixes.json new file mode 100644 index 0000000..36fca17 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "102 101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.4/32": [ + { + "path": "102 104", + "prefix": "10.254.254.4", + "valid": true, + "peerId": "192.168.1.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.1.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r3/bgp_summary.json b/tests/topotests/bfd_vrf_topo1/r3/bgp_summary.json new file mode 100644 index 0000000..d478333 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 103, + "peers": { + "192.168.1.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r3/bgpd.conf b/tests/topotests/bfd_vrf_topo1/r3/bgpd.conf new file mode 100644 index 0000000..f764647 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 103 vrf r3-bfd-cust1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as 102 + neighbor 192.168.1.2 timers 3 10 + neighbor 192.168.1.2 bfd + address-family ipv4 unicast + network 10.254.254.3/32 + exit-address-family +! diff --git a/tests/topotests/bfd_vrf_topo1/r3/peers.json b/tests/topotests/bfd_vrf_topo1/r3/peers.json new file mode 100644 index 0000000..ef38008 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/peers.json @@ -0,0 +1,6 @@ +[ + { + "peer": "192.168.1.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_vrf_topo1/r3/zebra.conf b/tests/topotests/bfd_vrf_topo1/r3/zebra.conf new file mode 100644 index 0000000..e673459 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r3/zebra.conf @@ -0,0 +1,3 @@ +interface r3-eth0 vrf r3-bfd-cust1 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bfd_vrf_topo1/r4/bfdd.conf b/tests/topotests/bfd_vrf_topo1/r4/bfdd.conf new file mode 100644 index 0000000..119e5e5 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/bfdd.conf @@ -0,0 +1,12 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.2.2 vrf r4-bfd-cust1 + transmit-interval 2000 + receive-interval 2000 + no shutdown + ! +! diff --git a/tests/topotests/bfd_vrf_topo1/r4/bgp_prefixes.json b/tests/topotests/bfd_vrf_topo1/r4/bgp_prefixes.json new file mode 100644 index 0000000..efe7d47 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/bgp_prefixes.json @@ -0,0 +1,52 @@ +{ + "routes": { + "10.254.254.1/32": [ + { + "path": "102 101", + "prefix": "10.254.254.1", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.2/32": [ + { + "path": "102", + "prefix": "10.254.254.2", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ], + "10.254.254.3/32": [ + { + "path": "102 103", + "prefix": "10.254.254.3", + "valid": true, + "peerId": "192.168.2.2", + "prefixLen": 32, + "nexthops": [ + { + "ip": "192.168.2.2", + "used": true, + "afi": "ipv4" + } + ] + } + ] + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r4/bgp_summary.json b/tests/topotests/bfd_vrf_topo1/r4/bgp_summary.json new file mode 100644 index 0000000..7d81784 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/bgp_summary.json @@ -0,0 +1,11 @@ +{ + "ipv4Unicast": { + "as": 104, + "peers": { + "192.168.2.2": { + "remoteAs": 102, + "state": "Established" + } + } + } +} diff --git a/tests/topotests/bfd_vrf_topo1/r4/bgpd.conf b/tests/topotests/bfd_vrf_topo1/r4/bgpd.conf new file mode 100644 index 0000000..03353e4 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 104 vrf r4-bfd-cust1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.2.2 remote-as 102 + neighbor 192.168.2.2 timers 3 10 + neighbor 192.168.2.2 bfd + address-family ipv4 unicast + network 10.254.254.4/32 + exit-address-family +! diff --git a/tests/topotests/bfd_vrf_topo1/r4/peers.json b/tests/topotests/bfd_vrf_topo1/r4/peers.json new file mode 100644 index 0000000..3714008 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/peers.json @@ -0,0 +1,6 @@ +[ + { + "peer": "192.168.2.2", + "status": "up" + } +] diff --git a/tests/topotests/bfd_vrf_topo1/r4/zebra.conf b/tests/topotests/bfd_vrf_topo1/r4/zebra.conf new file mode 100644 index 0000000..15d3ec1 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/r4/zebra.conf @@ -0,0 +1,3 @@ +interface r4-eth0 vrf r4-bfd-cust1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.dot b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.dot new file mode 100644 index 0000000..c84ace2 --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.dot @@ -0,0 +1,73 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo1"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + r3 [ + shape=doubleoctagon + label="r3", + fillcolor="#f08080", + style=filled, + ]; + r4 [ + shape=doubleoctagon + label="r4", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw2 [ + shape=oval, + label="sw2\n192.168.1.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + sw3 [ + shape=oval, + label="sw3\n192.168.2.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0\n.1"]; + r2 -- sw1 [label="eth0\n.2"]; + + r3 -- sw2 [label="eth0\n.1"]; + r2 -- sw2 [label="eth1\n.2"]; + + r4 -- sw3 [label="eth0\n.1"]; + r2 -- sw3 [label="eth2\n.2"]; +} diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.jpg b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.jpg new file mode 100644 index 0000000..4d6d56e Binary files /dev/null and b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.jpg differ diff --git a/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py new file mode 100644 index 0000000..9e5a68f --- /dev/null +++ b/tests/topotests/bfd_vrf_topo1/test_bfd_vrf_topo1.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_vrf_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018 by +# Network Device Education Foundation, Inc. ("NetDEF") +# Copyright (c) 2019 by 6WIND +# + +""" +test_bfd_vrf_topo1.py: Test the FRR BFD daemon. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd] + + +def build_topo(tgen): + # Create 4 routers + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # check for zebra capability + for rname, router in router_list.items(): + if router.check_capability(TopoRouter.RD_ZEBRA, "--vrfwnetns") == False: + return pytest.skip( + "Skipping BFD Topo1 VRF NETNS feature. VRF NETNS backend not available on FRR" + ) + + if os.system("ip netns list") != 0: + return pytest.skip( + "Skipping BFD Topo1 VRF NETNS Test. NETNS not available on System" + ) + + logger.info("Testing with VRF Namespace support") + + for rname, router in router_list.items(): + # create VRF rx-bfd-cust1 and link rx-eth0 to rx-bfd-cust1 + ns = "{}-bfd-cust1".format(rname) + router.net.add_netns(ns) + router.net.set_intf_netns(rname + "-eth0", ns, up=True) + if rname == "r2": + router.net.set_intf_netns(rname + "-eth1", ns, up=True) + router.net.set_intf_netns(rname + "-eth2", ns, up=True) + + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)), + "--vrfwnetns", + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # Move interfaces out of vrf namespace and delete the namespace + router_list = tgen.routers() + for rname, router in router_list.items(): + if rname == "r2": + router.net.reset_intf_netns(rname + "-eth2") + router.net.reset_intf_netns(rname + "-eth1") + router.net.reset_intf_netns(rname + "-eth0") + router.net.delete_netns("{}-bfd-cust1".format(rname)) + tgen.stop_topology() + + +def test_bfd_connection(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bfd peers to go up") + for router in tgen.routers().values(): + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=16, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bgp_convergence(): + "Assert that BGP is converging." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers to go up") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_summary.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip bgp vrf {}-bfd-cust1 summary json".format(router.name), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=125, wait=1.0) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_bgp_fast_convergence(): + "Assert that BGP is converging before setting a link down." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers converge") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_prefixes.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip bgp vrf {}-bfd-cust1 json".format(router.name), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=40, wait=1) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_bfd_fast_convergence(): + """ + Assert that BFD notices the link down after simulating network + failure. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Disable r2-eth0 link + router2 = tgen.gears["r2"] + topotest.interface_set_status( + router2, "r2-eth0", ifaceaction=False, vrf_name="r2-bfd-cust1" + ) + + # Wait the minimum time we can before checking that BGP/BFD + # converged. + logger.info("waiting for BFD converge") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + json_file = "{}/{}/peers.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + # Load the same file as previous test, but expect R1 to be down. + if router.name == "r1": + for peer in expected: + if peer["peer"] == "192.168.0.2": + peer["status"] = "down" + else: + for peer in expected: + if peer["peer"] == "192.168.0.1": + peer["status"] = "down" + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=40, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert res is None, assertmsg + + +def test_bgp_fast_reconvergence(): + "Assert that BGP is converging after setting a link down." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for BGP re convergence") + + # Check that BGP converged quickly. + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_prefixes.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + + # Load the same file as previous test, but set networks to None + # to test absence. + if router.name == "r1": + expected["routes"]["10.254.254.2/32"] = None + expected["routes"]["10.254.254.3/32"] = None + expected["routes"]["10.254.254.4/32"] = None + else: + expected["routes"]["10.254.254.1/32"] = None + + test_func = partial( + topotest.router_json_cmp, + router, + "show ip bgp vrf {}-bfd-cust1 json".format(router.name), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=16, wait=1) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bfd_vrflite_topo1/__init__.py b/tests/topotests/bfd_vrflite_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bfd_vrflite_topo1/r1/bfd_peers_status.json b/tests/topotests/bfd_vrflite_topo1/r1/bfd_peers_status.json new file mode 100644 index 0000000..07e96d7 --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/r1/bfd_peers_status.json @@ -0,0 +1,96 @@ +[ + { + "multihop":false, + "peer":"192.168.0.2", + "vrf":"vrf1", + "passive-mode":false, + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":1000, + "transmit-interval":1000, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "detect-multiplier":3, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50, + "remote-detect-multiplier":3 + }, + { + "multihop":true, + "peer":"192.0.2.2", + "local":"192.0.2.1", + "vrf":"vrf2", + "passive-mode":false, + "minimum-ttl":254, + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":1000, + "transmit-interval":1000, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "detect-multiplier":3, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50, + "remote-detect-multiplier":3 + }, + { + "multihop":false, + "peer":"192.168.0.2", + "vrf":"vrf2", + "passive-mode":false, + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":1000, + "transmit-interval":1000, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "detect-multiplier":3, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50, + "remote-detect-multiplier":3 + }, + { + "multihop":false, + "peer":"192.168.0.2", + "vrf":"default", + "passive-mode":false, + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":1000, + "transmit-interval":1000, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "detect-multiplier":3, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50, + "remote-detect-multiplier":3 + }, + { + "multihop":true, + "peer":"192.0.2.2", + "local":"192.0.2.1", + "vrf":"vrf1", + "passive-mode":false, + "minimum-ttl":254, + "status":"up", + "diagnostic":"ok", + "remote-diagnostic":"ok", + "receive-interval":1000, + "transmit-interval":1000, + "echo-receive-interval":50, + "echo-transmit-interval":0, + "detect-multiplier":3, + "remote-receive-interval":1000, + "remote-transmit-interval":1000, + "remote-echo-receive-interval":50, + "remote-detect-multiplier":3 + } +] diff --git a/tests/topotests/bfd_vrflite_topo1/r1/bfdd.conf b/tests/topotests/bfd_vrflite_topo1/r1/bfdd.conf new file mode 100644 index 0000000..96e8ff4 --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/r1/bfdd.conf @@ -0,0 +1,26 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.2 vrf vrf1 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.168.0.2 vrf vrf2 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.168.0.2 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.0.2.2 multihop local-address 192.0.2.1 vrf vrf1 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.0.2.2 multihop local-address 192.0.2.1 vrf vrf2 + transmit-interval 1000 + receive-interval 1000 + ! \ No newline at end of file diff --git a/tests/topotests/bfd_vrflite_topo1/r1/zebra.conf b/tests/topotests/bfd_vrflite_topo1/r1/zebra.conf new file mode 100644 index 0000000..ebb4e63 --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/r1/zebra.conf @@ -0,0 +1,24 @@ +vrf vrf1 + ip route 192.0.2.2/32 192.168.0.2 +! +vrf vrf2 + ip route 192.0.2.2/32 192.168.0.2 +! +interface r1-eth0 vrf vrf3 + ip address 192.168.0.1/24 +! +interface r1-eth0.100 vrf vrf1 + ip address 192.168.0.1/24 +! +interface r1-eth0.200 vrf vrf2 + ip address 192.168.0.1/24 +! +interface r1-eth0.300 + ip address 192.168.0.1/24 +! +interface r1-loop1 vrf vrf1 + ip address 192.0.2.1/32 +! +interface r1-loop2 vrf vrf2 + ip address 192.0.2.1/32 +! \ No newline at end of file diff --git a/tests/topotests/bfd_vrflite_topo1/r2/bfdd.conf b/tests/topotests/bfd_vrflite_topo1/r2/bfdd.conf new file mode 100644 index 0000000..7b11a47 --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/r2/bfdd.conf @@ -0,0 +1,26 @@ +! +! debug bfd network +! debug bfd peer +! debug bfd zebra +! +bfd + peer 192.168.0.1 vrf vrf1 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.168.0.1 vrf vrf2 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.168.0.1 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.0.2.1 multihop local-address 192.0.2.2 vrf vrf1 + transmit-interval 1000 + receive-interval 1000 + ! + peer 192.0.2.1 multihop local-address 192.0.2.2 vrf vrf2 + transmit-interval 1000 + receive-interval 1000 + ! \ No newline at end of file diff --git a/tests/topotests/bfd_vrflite_topo1/r2/zebra.conf b/tests/topotests/bfd_vrflite_topo1/r2/zebra.conf new file mode 100644 index 0000000..d8b996e --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/r2/zebra.conf @@ -0,0 +1,24 @@ +vrf vrf1 + ip route 192.0.2.1/32 192.168.0.1 +! +vrf vrf2 + ip route 192.0.2.1/32 192.168.0.1 +! +interface r2-eth0 vrf vrf3 + ip address 192.168.0.2/24 +! +interface r2-eth0.100 vrf vrf1 + ip address 192.168.0.2/24 +! +interface r2-eth0.200 vrf vrf2 + ip address 192.168.0.2/24 +! +interface r2-eth0.300 + ip address 192.168.0.2/24 +! +interface r2-loop1 vrf vrf1 + ip address 192.0.2.2/32 +! +interface r2-loop2 vrf vrf2 + ip address 192.0.2.2/32 +! \ No newline at end of file diff --git a/tests/topotests/bfd_vrflite_topo1/test_bfd_vrflite_topo1.py b/tests/topotests/bfd_vrflite_topo1/test_bfd_vrflite_topo1.py new file mode 100644 index 0000000..fee5f2d --- /dev/null +++ b/tests/topotests/bfd_vrflite_topo1/test_bfd_vrflite_topo1.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bfd_vrflite_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018 by +# Network Device Education Foundation, Inc. ("NetDEF") +# Copyright (c) 2022 by 6WIND +# + +""" +test_bfd_vrflite_topo1.py: Test the FRR BFD daemon. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd] + + +def build_topo(tgen): + # Create 2 routers + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + logger.info("Testing with Linux VRF support and udp_l3mdev=0") + if os.system("echo 0 > /proc/sys/net/ipv4/udp_l3mdev_accept") != 0: + return pytest.skip( + "Skipping BFD vrflite Topo1 Test. Linux VRF not available on System" + ) + + for rname, router in router_list.items(): + router.net.add_l3vrf("vrf1", 10) + router.net.add_l3vrf("vrf2", 20) + router.net.add_l3vrf("vrf3", 30) + router.net.add_vlan(rname + "-eth0.100", rname + "-eth0", 100) + router.net.add_vlan(rname + "-eth0.200", rname + "-eth0", 200) + router.net.add_vlan(rname + "-eth0.300", rname + "-eth0", 300) + router.net.attach_iface_to_l3vrf(rname + "-eth0.100", "vrf1") + router.net.attach_iface_to_l3vrf(rname + "-eth0.200", "vrf2") + router.net.add_loop(rname + "-loop1") + router.net.add_loop(rname + "-loop2") + router.net.attach_iface_to_l3vrf(rname + "-loop1", "vrf1") + router.net.attach_iface_to_l3vrf(rname + "-loop2", "vrf2") + router.net.attach_iface_to_l3vrf(rname + "-eth0", "vrf3") + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # Move interfaces out of vrf namespace and delete the namespace + router_list = tgen.routers() + for rname, router in router_list.items(): + router.net.del_iface(rname + "-eth0.100") + router.net.del_iface(rname + "-eth0.200") + router.net.del_iface(rname + "-eth0.300") + router.net.del_iface(rname + "-loop1") + router.net.del_iface(rname + "-loop2") + + tgen.stop_topology() + + +def test_bfd_connection(): + "Assert that the BFD peers can find themselves." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("waiting for bfd peers to go up") + router = tgen.gears["r1"] + json_file = "{}/{}/bfd_peers_status.json".format(CWD, "r1") + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bfd peers json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=16, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_accept_own/__init__.py b/tests/topotests/bgp_accept_own/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_accept_own/ce1/bgpd.conf b/tests/topotests/bgp_accept_own/ce1/bgpd.conf new file mode 100644 index 0000000..44f95b9 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce1/bgpd.conf @@ -0,0 +1,12 @@ +! +!debug bgp updates +! +router bgp 65010 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/ce1/zebra.conf b/tests/topotests/bgp_accept_own/ce1/zebra.conf new file mode 100644 index 0000000..7863ae1 --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface ce1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/ce2/bgpd.conf b/tests/topotests/bgp_accept_own/ce2/bgpd.conf new file mode 100644 index 0000000..d60fdcf --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce2/bgpd.conf @@ -0,0 +1,12 @@ +! +!debug bgp updates +! +router bgp 65020 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/ce2/zebra.conf b/tests/topotests/bgp_accept_own/ce2/zebra.conf new file mode 100644 index 0000000..829967e --- /dev/null +++ b/tests/topotests/bgp_accept_own/ce2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.2/32 +! +interface ce2-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/pe1/bgpd.conf b/tests/topotests/bgp_accept_own/pe1/bgpd.conf new file mode 100644 index 0000000..1f7abac --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/bgpd.conf @@ -0,0 +1,50 @@ +! +!debug bgp updates +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp nht +! +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.101 remote-as internal + neighbor 10.10.10.101 update-source 10.10.10.10 + neighbor 10.10.10.101 timers 1 3 + neighbor 10.10.10.101 timers connect 1 + address-family ipv4 vpn + neighbor 10.10.10.101 activate + neighbor 10.10.10.101 attribute-unchanged + exit-address-family +! +router bgp 65001 vrf Customer + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + redistribute connected + label vpn export 250 + rd vpn export 192.168.1.2:2 + rt vpn import 192.168.1.2:2 + rt vpn export 192.168.1.2:2 + export vpn + import vpn + exit-address-family +! +router bgp 65001 vrf Service + bgp router-id 192.168.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + label vpn export 350 + rd vpn export 192.168.2.2:2 + rt vpn import 192.168.2.2:2 + rt vpn export 192.168.2.2:2 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_accept_own/pe1/ldpd.conf b/tests/topotests/bgp_accept_own/pe1/ldpd.conf new file mode 100644 index 0000000..7c1ea33 --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/ldpd.conf @@ -0,0 +1,12 @@ +mpls ldp + router-id 10.10.10.10 + ! + address-family ipv4 + discovery transport-address 10.10.10.10 + ! + interface pe1-eth2 + exit + ! + exit-address-family + ! +exit diff --git a/tests/topotests/bgp_accept_own/pe1/ospfd.conf b/tests/topotests/bgp_accept_own/pe1/ospfd.conf new file mode 100644 index 0000000..1a5e1a0 --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/ospfd.conf @@ -0,0 +1,7 @@ +interface pe1-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.10 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_accept_own/pe1/zebra.conf b/tests/topotests/bgp_accept_own/pe1/zebra.conf new file mode 100644 index 0000000..2b7aefa --- /dev/null +++ b/tests/topotests/bgp_accept_own/pe1/zebra.conf @@ -0,0 +1,18 @@ +! +interface lo + ip address 10.10.10.10/32 +! +interface pe1-eth0 vrf Customer + ip address 192.168.1.2/24 +! +interface pe1-eth1 vrf Service + ip address 192.168.2.2/24 +! +interface pe1-eth2 + ip address 10.0.1.1/24 +! +interface pe1-eth3 vrf Customer + ip address 192.0.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/rr1/bgpd.conf b/tests/topotests/bgp_accept_own/rr1/bgpd.conf new file mode 100644 index 0000000..ad0ee3e --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/bgpd.conf @@ -0,0 +1,25 @@ +! +!debug bgp updates +! +router bgp 65001 + bgp router-id 10.10.10.101 + bgp route-reflector allow-outbound-policy + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as internal + neighbor 10.10.10.10 update-source 10.10.10.101 + neighbor 10.10.10.10 timers 1 3 + neighbor 10.10.10.10 timers connect 1 + address-family ipv4 vpn + neighbor 10.10.10.10 activate + neighbor 10.10.10.10 route-reflector-client + neighbor 10.10.10.10 route-map pe1 out + exit-address-family +! +route-map pe1 permit 10 + set extcommunity rt 192.168.1.2:2 192.168.2.2:2 + set community 65001:111 accept-own additive + set ip next-hop unchanged +route-map pe1 permit 20 +exit +! diff --git a/tests/topotests/bgp_accept_own/rr1/ldpd.conf b/tests/topotests/bgp_accept_own/rr1/ldpd.conf new file mode 100644 index 0000000..0369901 --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/ldpd.conf @@ -0,0 +1,12 @@ +mpls ldp + router-id 10.10.10.101 + ! + address-family ipv4 + discovery transport-address 10.10.10.101 + ! + interface rr1-eth0 + exit + ! + exit-address-family + ! +exit diff --git a/tests/topotests/bgp_accept_own/rr1/ospfd.conf b/tests/topotests/bgp_accept_own/rr1/ospfd.conf new file mode 100644 index 0000000..b598246 --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/ospfd.conf @@ -0,0 +1,7 @@ +interface rr1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.101 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_accept_own/rr1/zebra.conf b/tests/topotests/bgp_accept_own/rr1/zebra.conf new file mode 100644 index 0000000..aa3f633 --- /dev/null +++ b/tests/topotests/bgp_accept_own/rr1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 10.10.10.101/32 +! +interface rr1-eth0 + ip address 10.0.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_accept_own/test_bgp_accept_own.py b/tests/topotests/bgp_accept_own/test_bgp_accept_own.py new file mode 100644 index 0000000..d294da0 --- /dev/null +++ b/tests/topotests/bgp_accept_own/test_bgp_accept_own.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" + +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("pe1") + tgen.add_router("rr1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["ce1"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["ce2"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["pe1"]) + switch.add_link(tgen.gears["rr1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["pe1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["pe1"] + rr1 = tgen.gears["rr1"] + + pe1.run("ip link add Customer type vrf table 1001") + pe1.run("ip link set up dev Customer") + pe1.run("ip link set pe1-eth0 master Customer") + pe1.run("ip link add Service type vrf table 1002") + pe1.run("ip link set up dev Service") + pe1.run("ip link set pe1-eth1 master Service") + pe1.run("ip link set pe1-eth3 master Customer") + pe1.run("sysctl -w net.mpls.conf.pe1-eth2.input=1") + rr1.run("sysctl -w net.mpls.conf.rr1-eth0.input=1") + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_accept_own(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["pe1"] + ce2 = tgen.gears["ce2"] + + step("Check if routes are not installed in PE1 from RR1 (due to ORIGINATOR_ID)") + + def _bgp_check_received_routes_due_originator_id(): + output = json.loads(pe1.vtysh_cmd("show bgp ipv4 vpn summary json")) + expected = {"peers": {"10.10.10.101": {"pfxRcd": 0, "pfxSnt": 5}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_due_originator_id) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed, received routes from RR1 regardless ORIGINATOR_ID" + + step("Enable ACCEPT_OWN for RR1") + + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 vpn + neighbor 10.10.10.101 accept-own + """ + ) + + step("Check if we received routes due to ACCEPT_OWN from RR1") + + def _bgp_check_received_routes_with_modified_rts(): + output = json.loads(pe1.vtysh_cmd("show bgp ipv4 vpn summary json")) + expected = {"peers": {"10.10.10.101": {"pfxRcd": 5, "pfxSnt": 5}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_with_modified_rts) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, didn't receive routes from RR1 with ACCEPT_OWN enabled" + + step( + "Check if 172.16.255.1/32 is imported into vrf Service due to modified RT list at RR1" + ) + + def _bgp_check_received_routes_with_changed_rts(): + output = json.loads( + pe1.vtysh_cmd("show bgp vrf Service ipv4 unicast 172.16.255.1/32 json") + ) + expected = { + "paths": [ + { + "community": {"string": "65001:111"}, + "extendedCommunity": { + "string": "RT:192.168.1.2:2 RT:192.168.2.2:2" + }, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_with_changed_rts) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, routes are not imported from RR1 with modified RT list" + + step("Check if 192.0.2.0/24 is imported to vrf Service from vrf Customer") + + def _bgp_check_imported_local_routes_from_vrf_service(): + output = json.loads( + pe1.vtysh_cmd("show ip route vrf Service 192.0.2.0/24 json") + ) + expected = { + "192.0.2.0/24": [ + { + "vrfName": "Service", + "table": 1002, + "installed": True, + "selected": True, + "nexthops": [ + { + "fib": True, + "vrf": "Customer", + "active": True, + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_imported_local_routes_from_vrf_service) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Failed, didn't import local route 192.0.2.0/24 from vrf Customer to vrf Service" + + step("Check if 172.16.255.1/32 is announced to CE2") + + def _bgp_check_received_routes_from_pe(): + output = json.loads(ce2.vtysh_cmd("show ip route 172.16.255.1/32 json")) + expected = { + "172.16.255.1/32": [ + { + "protocol": "bgp", + "installed": True, + "nexthops": [{"ip": "192.168.2.2"}], + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes_from_pe) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed, didn't receive 172.16.255.1/32 from PE1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_addpath_best_selected/__init__.py b/tests/topotests/bgp_addpath_best_selected/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_addpath_best_selected/r1/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r1/bgpd.conf new file mode 100644 index 0000000..ba10f7b --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r1/bgpd.conf @@ -0,0 +1,7 @@ +! +router bgp 65001 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers connect 5 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r1/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r2/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r2/bgpd.conf new file mode 100644 index 0000000..cdef611 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r2/bgpd.conf @@ -0,0 +1,27 @@ +router bgp 65002 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.7.7 remote-as external + neighbor 192.168.7.7 timers connect 5 + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers connect 5 + neighbor 192.168.2.3 weight 3 + neighbor 192.168.2.4 remote-as external + neighbor 192.168.2.4 timers connect 5 + neighbor 192.168.2.4 weight 4 + neighbor 192.168.2.5 remote-as external + neighbor 192.168.2.5 timers connect 5 + neighbor 192.168.2.5 weight 5 + neighbor 192.168.2.6 remote-as external + neighbor 192.168.2.6 timers connect 5 + neighbor 192.168.2.6 weight 6 + address-family ipv4 unicast + neighbor 192.168.1.1 addpath-tx-best-selected 1 + neighbor 192.168.1.1 prefix-list announce out + neighbor 192.168.7.7 addpath-tx-best-selected 2 + neighbor 192.168.7.7 prefix-list announce out + exit-address-family +! +ip prefix-list announce seq 5 permit 172.16.16.254/32 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r2/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r2/zebra.conf new file mode 100644 index 0000000..90587d2 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r2/zebra.conf @@ -0,0 +1,10 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! +int r2-eth2 + ip address 192.168.7.2/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r3/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r3/bgpd.conf new file mode 100644 index 0000000..98eb2e1 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r3/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65003 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_best_selected/r3/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r3/zebra.conf new file mode 100644 index 0000000..417a484 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r3/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r3-eth0 + ip address 192.168.2.3/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r4/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r4/bgpd.conf new file mode 100644 index 0000000..68245c4 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r4/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65004 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_best_selected/r4/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r4/zebra.conf new file mode 100644 index 0000000..241e386 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r4/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r4-eth0 + ip address 192.168.2.4/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r5/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r5/bgpd.conf new file mode 100644 index 0000000..f36e2bd --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r5/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65005 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_best_selected/r5/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r5/zebra.conf new file mode 100644 index 0000000..203d229 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r5/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r5-eth0 + ip address 192.168.2.5/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r6/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r6/bgpd.conf new file mode 100644 index 0000000..0d83ef8 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r6/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65006 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_best_selected/r6/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r6/zebra.conf new file mode 100644 index 0000000..894dd30 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r6/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r6-eth0 + ip address 192.168.2.6/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r7/bgpd.conf b/tests/topotests/bgp_addpath_best_selected/r7/bgpd.conf new file mode 100644 index 0000000..090846a --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r7/bgpd.conf @@ -0,0 +1,7 @@ +! +router bgp 65007 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.7.2 remote-as external + neighbor 192.168.7.2 timers connect 5 +! diff --git a/tests/topotests/bgp_addpath_best_selected/r7/zebra.conf b/tests/topotests/bgp_addpath_best_selected/r7/zebra.conf new file mode 100644 index 0000000..55c70ba --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/r7/zebra.conf @@ -0,0 +1,4 @@ +! +int r7-eth0 + ip address 192.168.7.7/24 +! diff --git a/tests/topotests/bgp_addpath_best_selected/test_bgp_addpath_best_selected.py b/tests/topotests/bgp_addpath_best_selected/test_bgp_addpath_best_selected.py new file mode 100644 index 0000000..2a610c9 --- /dev/null +++ b/tests/topotests/bgp_addpath_best_selected/test_bgp_addpath_best_selected.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if Add-Path best selected paths are announced per neighbor. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 8): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_addpath_best_selected(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast 172.16.16.254/32 json")) + expected = { + "paths": [ + { + "aspath": { + "string": "65006", + }, + "weight": 6, + }, + { + "aspath": { + "string": "65005", + }, + "weight": 5, + }, + { + "aspath": { + "string": "65004", + }, + "weight": 4, + }, + { + "aspath": { + "string": "65003", + }, + "weight": 3, + }, + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge initially" + + def check_bgp_advertised_routes_to_r1(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 neighbors 192.168.1.1 advertised-routes detail json" + ) + ) + expected = { + "advertisedRoutes": { + "172.16.16.254/32": { + "paths": [ + { + "aspath": { + "string": "65005", + } + }, + { + "aspath": { + "string": "65006", + } + }, + ] + } + }, + "totalPrefixCounter": 2, + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(check_bgp_advertised_routes_to_r1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Received more/less Add-Path best paths, but should be only 1+1 (real best path)" + + def check_bgp_advertised_routes_to_r7(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 neighbors 192.168.7.7 advertised-routes detail json" + ) + ) + expected = { + "advertisedRoutes": { + "172.16.16.254/32": { + "paths": [ + { + "aspath": { + "string": "65004", + } + }, + { + "aspath": { + "string": "65005", + } + }, + { + "aspath": { + "string": "65006", + } + }, + ] + } + }, + "totalPrefixCounter": 3, + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(check_bgp_advertised_routes_to_r7) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Received more/less Add-Path best paths, but should be only 2+1 (real best path)" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_addpath_paths_limit/__init__.py b/tests/topotests/bgp_addpath_paths_limit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf new file mode 100644 index 0000000..65beb7f --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r1/frr.conf @@ -0,0 +1,13 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.1.2 addpath-rx-paths-limit 2 + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf new file mode 100644 index 0000000..796b4d0 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r2/frr.conf @@ -0,0 +1,37 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! +int r2-eth2 + ip address 192.168.7.2/24 +! +router bgp 65002 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.7.7 remote-as external + neighbor 192.168.7.7 timers connect 5 + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers connect 5 + neighbor 192.168.2.3 weight 3 + neighbor 192.168.2.4 remote-as external + neighbor 192.168.2.4 timers connect 5 + neighbor 192.168.2.4 weight 4 + neighbor 192.168.2.5 remote-as external + neighbor 192.168.2.5 timers connect 5 + neighbor 192.168.2.5 weight 5 + neighbor 192.168.2.6 remote-as external + neighbor 192.168.2.6 timers connect 5 + neighbor 192.168.2.6 weight 6 + address-family ipv4 unicast + neighbor 192.168.1.1 addpath-tx-all-paths + neighbor 192.168.1.1 prefix-list announce out + neighbor 192.168.7.7 addpath-tx-all-paths + neighbor 192.168.7.7 prefix-list announce out + exit-address-family +! +ip prefix-list announce seq 5 permit 172.16.16.254/32 +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf new file mode 100644 index 0000000..4d834d3 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r3/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r3-eth0 + ip address 192.168.2.3/24 +! +router bgp 65003 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf new file mode 100644 index 0000000..01e0aa9 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r4/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r4-eth0 + ip address 192.168.2.4/24 +! +router bgp 65004 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf new file mode 100644 index 0000000..02bb847 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r5/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r5-eth0 + ip address 192.168.2.5/24 +! +router bgp 65005 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf new file mode 100644 index 0000000..39fdbcc --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r6/frr.conf @@ -0,0 +1,16 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r6-eth0 + ip address 192.168.2.6/24 +! +router bgp 65006 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf new file mode 100644 index 0000000..8c44566 --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/r7/frr.conf @@ -0,0 +1,13 @@ +! +int r7-eth0 + ip address 192.168.7.7/24 +! +router bgp 65007 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.7.2 remote-as external + neighbor 192.168.7.2 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.7.2 addpath-rx-paths-limit 3 + exit-address-family +! diff --git a/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py new file mode 100644 index 0000000..b7a1cfb --- /dev/null +++ b/tests/topotests/bgp_addpath_paths_limit/test_bgp_addpath_paths_limit.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +""" +Test if Paths-Limit capability works as expected. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 8): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r7"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_addpath_paths_limit(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r7 = tgen.gears["r7"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.7.7": { + "neighborCapabilities": { + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 0, + "receivedPathsLimit": 3, + } + } + } + }, + "192.168.1.1": { + "neighborCapabilities": { + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 0, + "receivedPathsLimit": 2, + } + } + } + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge initially" + + def _bgp_check_received_routes(router, expected): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 172.16.16.254/32 json") + ) + + if "paths" not in output: + return "No paths received" + + return topotest.json_cmp(len(output["paths"]), expected) + + test_func = functools.partial(_bgp_check_received_routes, r1, 2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Received routes count is not as expected" + + test_func = functools.partial(_bgp_check_received_routes, r7, 3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Received routes count is not as expected" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aggregate_address_matching_med/__init__.py b/tests/topotests/bgp_aggregate_address_matching_med/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r1/bgpd.conf b/tests/topotests/bgp_aggregate_address_matching_med/r1/bgpd.conf new file mode 100644 index 0000000..3559760 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r1/bgpd.conf @@ -0,0 +1,21 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.2 route-map r2 out + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.1/32 +ip prefix-list p1 seq 10 permit 172.16.255.2/32 +ip prefix-list p2 seq 15 permit 172.16.255.3/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set metric 300 +route-map r2 permit 20 + match ip address prefix-list p2 + set metric 400 +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r1/zebra.conf b/tests/topotests/bgp_aggregate_address_matching_med/r1/zebra.conf new file mode 100644 index 0000000..685adb3 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r1/zebra.conf @@ -0,0 +1,11 @@ +! +interface lo + ip address 172.16.255.1/32 + ip address 172.16.255.2/32 + ip address 172.16.255.3/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r2/bgpd.conf b/tests/topotests/bgp_aggregate_address_matching_med/r2/bgpd.conf new file mode 100644 index 0000000..9bc9a31 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r2/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 + address-family ipv4 unicast + aggregate-address 172.16.255.0/24 summary-only matching-MED-only + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r2/zebra.conf b/tests/topotests/bgp_aggregate_address_matching_med/r2/zebra.conf new file mode 100644 index 0000000..f229954 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r3/bgpd.conf b/tests/topotests/bgp_aggregate_address_matching_med/r3/bgpd.conf new file mode 100644 index 0000000..dfb5ac7 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r3/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/r3/zebra.conf b/tests/topotests/bgp_aggregate_address_matching_med/r3/zebra.conf new file mode 100644 index 0000000..11e06d4 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_matching_med/test_bgp_aggregate_address_matching_med.py b/tests/topotests/bgp_aggregate_address_matching_med/test_bgp_aggregate_address_matching_med.py new file mode 100644 index 0000000..5a4a5fb --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_matching_med/test_bgp_aggregate_address_matching_med.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if aggregate-address command works fine when suppressing summary-only +and using matching-MED-only together. +""" + +import os +import sys +import json +import pytest +import functools +from lib.common_config import ( + step, +) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_aggregate_address_matching_med(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r3 = tgen.gears["r3"] + + def _bgp_converge(): + output = json.loads(r3.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.0/24": None, + "172.16.255.1/32": [{"path": "65002 65001"}], + "172.16.255.2/32": [{"path": "65002 65001"}], + "172.16.255.3/32": [{"path": "65002 65001"}], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see unsuppressed routes from R2" + + step("Change MED for 172.16.255.3/32 from 400 to 300") + r1.vtysh_cmd( + """ + configure terminal + route-map r2 permit 20 + set metric 300 + """ + ) + + step("Check if 172.16.255.0/24 aggregated route was created and others suppressed") + + def _bgp_aggregated_summary_only_med_match(): + output = json.loads(r3.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.0/24": [{"path": "65002"}], + "172.16.255.1/32": None, + "172.16.255.2/32": None, + "172.16.255.3/32": None, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_aggregated_summary_only_med_match) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see unsuppressed routes from R2" + + step("Change MED for 172.16.255.3/32 back to 400 from 300") + r1.vtysh_cmd( + """ + configure terminal + route-map r2 permit 20 + set metric 400 + """ + ) + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see unsuppressed routes from R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aggregate_address_origin/__init__.py b/tests/topotests/bgp_aggregate_address_origin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aggregate_address_origin/r1/bgpd.conf b/tests/topotests/bgp_aggregate_address_origin/r1/bgpd.conf new file mode 100644 index 0000000..3486c64 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_origin/r1/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + aggregate-address 172.16.255.0/24 origin igp + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_origin/r1/zebra.conf b/tests/topotests/bgp_aggregate_address_origin/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_origin/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_origin/r2/bgpd.conf b/tests/topotests/bgp_aggregate_address_origin/r2/bgpd.conf new file mode 100644 index 0000000..b2d9455 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_origin/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_origin/r2/zebra.conf b/tests/topotests/bgp_aggregate_address_origin/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_origin/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_origin/test_bgp_aggregate-address_origin.py b/tests/topotests/bgp_aggregate_address_origin/test_bgp_aggregate-address_origin.py new file mode 100644 index 0000000..739685d --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_origin/test_bgp_aggregate-address_origin.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_aggregate-address_origin.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Donatas Abraitis +# + +""" +bgp_aggregate-address_origin.py: + +Test if works the following commands: +router bgp 65031 + address-family ipv4 unicast + aggregate-address 192.168.255.0/24 origin igp +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_aggregate_address_origin(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 3}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_aggregate_address_has_metric(router): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.255.0/24 json")) + expected = {"paths": [{"origin": "IGP"}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, 'Failed to see bgp convergence in "{}"'.format(router) + + test_func = functools.partial(_bgp_aggregate_address_has_metric, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert ( + result is None + ), 'Failed to see applied ORIGIN (igp) for aggregated prefix in "{}"'.format(router) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aggregate_address_route_map/__init__.py b/tests/topotests/bgp_aggregate_address_route_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aggregate_address_route_map/r1/bgpd.conf b/tests/topotests/bgp_aggregate_address_route_map/r1/bgpd.conf new file mode 100644 index 0000000..7fb55cf --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_route_map/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + aggregate-address 172.16.255.0/24 route-map aggr-rmap + exit-address-family +! +route-map aggr-rmap permit 10 + set metric 123 +! diff --git a/tests/topotests/bgp_aggregate_address_route_map/r1/zebra.conf b/tests/topotests/bgp_aggregate_address_route_map/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_route_map/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_route_map/r2/bgpd.conf b/tests/topotests/bgp_aggregate_address_route_map/r2/bgpd.conf new file mode 100644 index 0000000..b2d9455 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_route_map/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_route_map/r2/zebra.conf b/tests/topotests/bgp_aggregate_address_route_map/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_route_map/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py b/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py new file mode 100644 index 0000000..eeac714 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_aggregate-address_route-map.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +bgp_aggregate-address_route-map.py: + +Test if works the following commands: +router bgp 65031 + address-family ipv4 unicast + aggregate-address 192.168.255.0/24 route-map aggr-rmap + +route-map aggr-rmap permit 10 + set metric 123 +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_maximum_prefix_invalid(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 3}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_aggregate_address_has_metric(router, metric): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.255.0/24 json")) + expected = {"paths": [{"metric": metric}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see bgp convergence in r2" + + test_func = functools.partial(_bgp_aggregate_address_has_metric, r2, 123) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see applied metric for aggregated prefix in r2" + + r1.vtysh_cmd( + """ + configure terminal + route-map aggr-rmap permit 10 + set metric 666 + """ + ) + + test_func = functools.partial(_bgp_aggregate_address_has_metric, r2, 666) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see applied metric for aggregated prefix in r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aggregate_address_topo1/__init__.py b/tests/topotests/bgp_aggregate_address_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aggregate_address_topo1/exabgp.env b/tests/topotests/bgp_aggregate_address_topo1/exabgp.env new file mode 100644 index 0000000..28e6423 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/exabgp.env @@ -0,0 +1,53 @@ +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_aggregate_address_topo1/peer1/exabgp.cfg b/tests/topotests/bgp_aggregate_address_topo1/peer1/exabgp.cfg new file mode 100644 index 0000000..e0f6ab6 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/peer1/exabgp.cfg @@ -0,0 +1,21 @@ +neighbor 10.0.0.1 { + router-id 10.254.254.3; + local-address 10.0.0.2; + local-as 65001; + peer-as 65000; + static { + route 10.254.254.3/32 next-hop 10.0.0.2; + + route 192.168.0.1/32 next-hop 10.0.0.2 med 10; + route 192.168.0.2/32 next-hop 10.0.0.2 med 10; + route 192.168.0.3/32 next-hop 10.0.0.2 med 10; + + route 192.168.1.1/32 next-hop 10.0.0.2 med 10; + route 192.168.1.2/32 next-hop 10.0.0.2 med 10; + route 192.168.1.3/32 next-hop 10.0.0.2 med 20; + + route 192.168.2.1/32 next-hop 10.0.0.2; + route 192.168.2.2/32 next-hop 10.0.0.2; + route 192.168.2.3/32 next-hop 10.0.0.2; + } +} diff --git a/tests/topotests/bgp_aggregate_address_topo1/r1/bgpd.conf b/tests/topotests/bgp_aggregate_address_topo1/r1/bgpd.conf new file mode 100644 index 0000000..c7cf4a5 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/r1/bgpd.conf @@ -0,0 +1,30 @@ +! debug bgp updates +! +access-list acl-sup-one seq 5 permit 192.168.2.1/32 +access-list acl-sup-one seq 10 deny any +! +access-list acl-sup-two seq 5 permit 192.168.2.2/32 +access-list acl-sup-two seq 10 deny any +! +access-list acl-sup-three seq 5 permit 192.168.2.3/32 +access-list acl-sup-three seq 10 deny any +! +route-map rm-sup-one permit 10 + match ip address acl-sup-one +! +route-map rm-sup-two permit 10 + match ip address acl-sup-two +! +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 10.0.0.2 remote-as 65001 + neighbor 10.0.0.2 timers 3 10 + neighbor 10.0.1.2 remote-as internal + neighbor 10.0.1.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + aggregate-address 192.168.0.0/24 matching-MED-only + aggregate-address 192.168.1.0/24 matching-MED-only + aggregate-address 192.168.2.0/24 suppress-map rm-sup-one + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_topo1/r1/zebra.conf b/tests/topotests/bgp_aggregate_address_topo1/r1/zebra.conf new file mode 100644 index 0000000..931c73d --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/r1/zebra.conf @@ -0,0 +1,13 @@ +! +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ip address 10.0.0.1/24 +! +interface r1-eth1 + ip address 10.0.1.1/24 +! +ip forwarding +ipv6 forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_topo1/r2/bgpd.conf b/tests/topotests/bgp_aggregate_address_topo1/r2/bgpd.conf new file mode 100644 index 0000000..acacd86 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/r2/bgpd.conf @@ -0,0 +1,7 @@ +router bgp 65000 + neighbor 10.0.1.1 remote-as internal + neighbor 10.0.1.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_aggregate_address_topo1/r2/zebra.conf b/tests/topotests/bgp_aggregate_address_topo1/r2/zebra.conf new file mode 100644 index 0000000..38e0c44 --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/r2/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ip address 10.0.1.2/24 +! +ip forwarding +ipv6 forwarding +! diff --git a/tests/topotests/bgp_aggregate_address_topo1/test_bgp_aggregate_address_topo1.py b/tests/topotests/bgp_aggregate_address_topo1/test_bgp_aggregate_address_topo1.py new file mode 100644 index 0000000..370d01e --- /dev/null +++ b/tests/topotests/bgp_aggregate_address_topo1/test_bgp_aggregate_address_topo1.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_aggregate_address_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +Test BGP aggregate address features. +""" + +import os +import sys +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + r2 = tgen.add_router("r2") + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.2", defaultRoute="via 10.0.0.1") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(peer1) + + switch = tgen.add_switch("s2") + switch.add_link(r1) + switch.add_link(r2) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + router.start() + + router = tgen.gears["r2"] + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r2/zebra.conf")) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r2/bgpd.conf")) + router.start() + + peer = tgen.gears["peer1"] + peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env")) + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def expect_route(router_name, routes_expected): + "Helper function to avoid repeated code." + tgen = get_topogen() + test_func = functools.partial( + topotest.router_json_cmp, + tgen.gears[router_name], + "show ip route json", + routes_expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=120, wait=1) + assertmsg = '"{}" BGP convergence failure'.format(router_name) + assert result is None, assertmsg + + +def test_expect_convergence(): + "Test that BGP protocol converged." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for protocols to converge") + + def expect_loopback_route(router, iptype, route, proto): + "Wait until route is present on RIB for protocol." + logger.info("waiting route {} in {}".format(route, router)) + test_func = functools.partial( + topotest.router_json_cmp, + tgen.gears[router], + "show {} route json".format(iptype), + {route: [{"protocol": proto}]}, + ) + _, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + assertmsg = '"{}" BGP convergence failure'.format(router) + assert result is None, assertmsg + + expect_loopback_route("r2", "ip", "10.254.254.1/32", "bgp") + expect_loopback_route("r2", "ip", "10.254.254.3/32", "bgp") + + +def test_bgp_aggregate_address_matching_med_only(): + "Test that the command matching-MED-only works." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + routes_expected = { + # All MED matches, aggregation must exist. + "192.168.0.0/24": [{"protocol": "bgp", "metric": 0}], + "192.168.0.1/32": [{"protocol": "bgp", "metric": 10}], + "192.168.0.2/32": [{"protocol": "bgp", "metric": 10}], + "192.168.0.3/32": [{"protocol": "bgp", "metric": 10}], + # Non matching MED: aggregation must not exist. + "192.168.1.0/24": None, + "192.168.1.1/32": [{"protocol": "bgp", "metric": 10}], + "192.168.1.2/32": [{"protocol": "bgp", "metric": 10}], + "192.168.1.3/32": [{"protocol": "bgp", "metric": 20}], + } + + test_func = functools.partial( + topotest.router_json_cmp, + tgen.gears["r2"], + "show ip route json", + routes_expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"r2" BGP convergence failure' + assert result is None, assertmsg + + +def test_bgp_aggregate_address_match_and_suppress(): + "Test that the command matching-MED-only with suppression works." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r1"].vtysh_multicmd( + """ +configure terminal +router bgp 65000 +address-family ipv4 unicast +no aggregate-address 192.168.0.0/24 matching-MED-only +no aggregate-address 192.168.1.0/24 matching-MED-only +aggregate-address 192.168.0.0/24 matching-MED-only summary-only +aggregate-address 192.168.1.0/24 matching-MED-only summary-only +""" + ) + + routes_expected = { + # All MED matches, aggregation must exist. + "192.168.0.0/24": [{"protocol": "bgp", "metric": 0}], + "192.168.0.1/32": None, + "192.168.0.2/32": None, + "192.168.0.3/32": None, + # Non matching MED: aggregation must not exist. + "192.168.1.0/24": None, + "192.168.1.1/32": [{"protocol": "bgp", "metric": 10}], + "192.168.1.2/32": [{"protocol": "bgp", "metric": 10}], + "192.168.1.3/32": [{"protocol": "bgp", "metric": 20}], + } + + test_func = functools.partial( + topotest.router_json_cmp, + tgen.gears["r2"], + "show ip route json", + routes_expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=120, wait=1) + assertmsg = '"r2" BGP convergence failure' + assert result is None, assertmsg + + +def test_bgp_aggregate_address_suppress_map(): + "Test that the command suppress-map works." + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + expect_route( + "r2", + { + "192.168.2.0/24": [{"protocol": "bgp"}], + "192.168.2.1/32": None, + "192.168.2.2/32": [{"protocol": "bgp"}], + "192.168.2.3/32": [{"protocol": "bgp"}], + }, + ) + + # Change route map and test again. + tgen.gears["r1"].vtysh_multicmd( + """ +configure terminal +router bgp 65000 +address-family ipv4 unicast +no aggregate-address 192.168.2.0/24 suppress-map rm-sup-one +aggregate-address 192.168.2.0/24 suppress-map rm-sup-two +""" + ) + + expect_route( + "r2", + { + "192.168.2.0/24": [{"protocol": "bgp"}], + "192.168.2.1/32": [{"protocol": "bgp"}], + "192.168.2.2/32": None, + "192.168.2.3/32": [{"protocol": "bgp"}], + }, + ) + + +def test_bgp_aggregate_address_suppress_map_update_route_map(): + "Test that the suppress-map late route map creation works." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r1"].vtysh_multicmd( + """ +configure terminal +router bgp 65000 +address-family ipv4 unicast +no aggregate-address 192.168.2.0/24 suppress-map rm-sup-two +aggregate-address 192.168.2.0/24 suppress-map rm-sup-three +""" + ) + + expect_route( + "r2", + { + "192.168.2.0/24": [{"protocol": "bgp"}], + "192.168.2.1/32": [{"protocol": "bgp"}], + "192.168.2.2/32": [{"protocol": "bgp"}], + "192.168.2.3/32": [{"protocol": "bgp"}], + }, + ) + + # Create missing route map and test again. + tgen.gears["r1"].vtysh_multicmd( + """ +configure terminal +route-map rm-sup-three permit 10 +match ip address acl-sup-three +""" + ) + + expect_route( + "r2", + { + "192.168.2.0/24": [{"protocol": "bgp"}], + "192.168.2.1/32": [{"protocol": "bgp"}], + "192.168.2.2/32": [{"protocol": "bgp"}], + "192.168.2.3/32": None, + }, + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aggregator_zero/__init__.py b/tests/topotests/bgp_aggregator_zero/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aggregator_zero/exabgp.env b/tests/topotests/bgp_aggregator_zero/exabgp.env new file mode 100644 index 0000000..28e6423 --- /dev/null +++ b/tests/topotests/bgp_aggregator_zero/exabgp.env @@ -0,0 +1,53 @@ +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_aggregator_zero/peer1/exabgp.cfg b/tests/topotests/bgp_aggregator_zero/peer1/exabgp.cfg new file mode 100644 index 0000000..b3f2527 --- /dev/null +++ b/tests/topotests/bgp_aggregator_zero/peer1/exabgp.cfg @@ -0,0 +1,18 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.2; + local-address 10.0.0.2; + local-as 65001; + peer-as 65534; + + static { + route 192.168.100.101/32 { + aggregator (0:10.0.0.2); + next-hop 10.0.0.2; + } + + route 192.168.100.102/32 { + aggregator (65001:10.0.0.2); + next-hop 10.0.0.2; + } + } +} diff --git a/tests/topotests/bgp_aggregator_zero/r1/bgpd.conf b/tests/topotests/bgp_aggregator_zero/r1/bgpd.conf new file mode 100644 index 0000000..002a5c7 --- /dev/null +++ b/tests/topotests/bgp_aggregator_zero/r1/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65534 + no bgp ebgp-requires-policy + neighbor 10.0.0.2 remote-as external + neighbor 10.0.0.2 timers 3 10 +! diff --git a/tests/topotests/bgp_aggregator_zero/r1/zebra.conf b/tests/topotests/bgp_aggregator_zero/r1/zebra.conf new file mode 100644 index 0000000..22a26ac --- /dev/null +++ b/tests/topotests/bgp_aggregator_zero/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 10.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aggregator_zero/test_bgp_aggregator_zero.py b/tests/topotests/bgp_aggregator_zero/test_bgp_aggregator_zero.py new file mode 100644 index 0000000..d9ef3e1 --- /dev/null +++ b/tests/topotests/bgp_aggregator_zero/test_bgp_aggregator_zero.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if BGP UPDATE with AGGREGATOR AS attribute with value zero (0) +is continued to be processed, but AGGREGATOR attribute is discarded. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.2", defaultRoute="via 10.0.0.1") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(peer1) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + router.start() + + peer = tgen.gears["peer1"] + peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env")) + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_aggregator_zero(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp neighbor 10.0.0.2 json") + ) + expected = { + "10.0.0.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Failed bgp convergence in "{}"'.format(tgen.gears["r1"]) + + def _bgp_has_correct_aggregator_route_with_asn_0(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp 192.168.100.101/32 json") + ) + + if "aggregatorAs" in output["paths"][0].keys(): + return False + else: + return True + + assert ( + _bgp_has_correct_aggregator_route_with_asn_0() is True + ), 'Aggregator AS attribute with ASN 0 found in "{}"'.format(tgen.gears["r1"]) + + def _bgp_has_correct_aggregator_route_with_good_asn(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp 192.168.100.102/32 json") + ) + expected = {"paths": [{"aggregatorAs": 65001, "aggregatorId": "10.0.0.2"}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_has_correct_aggregator_route_with_good_asn) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Aggregator AS attribute not found in "{}"'.format( + tgen.gears["r1"] + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aigp/__init__.py b/tests/topotests/bgp_aigp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aigp/r1/bgpd.conf b/tests/topotests/bgp_aigp/r1/bgpd.conf new file mode 100644 index 0000000..74a0215 --- /dev/null +++ b/tests/topotests/bgp_aigp/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.2 remote-as internal + neighbor 10.0.0.2 update-source lo + neighbor 10.0.0.2 timers 1 3 + neighbor 10.0.0.2 timers connect 1 + neighbor 10.0.0.3 remote-as internal + neighbor 10.0.0.3 timers 1 3 + neighbor 10.0.0.3 timers connect 1 + neighbor 10.0.0.3 update-source lo +! diff --git a/tests/topotests/bgp_aigp/r1/ospfd.conf b/tests/topotests/bgp_aigp/r1/ospfd.conf new file mode 100644 index 0000000..38aa11d --- /dev/null +++ b/tests/topotests/bgp_aigp/r1/ospfd.conf @@ -0,0 +1,17 @@ +! +interface lo + ip ospf cost 10 +! +interface r1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 10 +! +interface r1-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 30 +! +router ospf + router-id 10.0.0.1 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_aigp/r1/zebra.conf b/tests/topotests/bgp_aigp/r1/zebra.conf new file mode 100644 index 0000000..0ed22d3 --- /dev/null +++ b/tests/topotests/bgp_aigp/r1/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.0.0.1/32 +! +interface r1-eth0 + ip address 192.168.12.1/24 +! +interface r1-eth1 + ip address 192.168.13.1/24 +! diff --git a/tests/topotests/bgp_aigp/r2/bgpd.conf b/tests/topotests/bgp_aigp/r2/bgpd.conf new file mode 100644 index 0000000..4db4687 --- /dev/null +++ b/tests/topotests/bgp_aigp/r2/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.1 remote-as internal + neighbor 10.0.0.1 timers 1 3 + neighbor 10.0.0.1 timers connect 1 + neighbor 10.0.0.1 route-reflector-client + neighbor 192.168.24.4 remote-as internal + neighbor 192.168.24.4 timers 1 3 + neighbor 192.168.24.4 timers connect 1 + neighbor 192.168.24.4 aigp + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_aigp/r2/ospfd.conf b/tests/topotests/bgp_aigp/r2/ospfd.conf new file mode 100644 index 0000000..ed31941 --- /dev/null +++ b/tests/topotests/bgp_aigp/r2/ospfd.conf @@ -0,0 +1,13 @@ +! +interface lo + ip ospf cost 10 +! +interface r2-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 10 +! +router ospf + router-id 10.0.0.2 + network 192.168.12.0/24 area 0 + network 10.0.0.2/32 area 0 diff --git a/tests/topotests/bgp_aigp/r2/zebra.conf b/tests/topotests/bgp_aigp/r2/zebra.conf new file mode 100644 index 0000000..6d6a557 --- /dev/null +++ b/tests/topotests/bgp_aigp/r2/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.0.0.2/32 +! +interface r2-eth0 + ip address 192.168.12.2/24 +! +interface r2-eth1 + ip address 192.168.24.2/24 +! diff --git a/tests/topotests/bgp_aigp/r3/bgpd.conf b/tests/topotests/bgp_aigp/r3/bgpd.conf new file mode 100644 index 0000000..5ab712e --- /dev/null +++ b/tests/topotests/bgp_aigp/r3/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.1 remote-as internal + neighbor 10.0.0.1 timers 1 3 + neighbor 10.0.0.1 timers connect 1 + neighbor 10.0.0.1 route-reflector-client + neighbor 192.168.35.5 remote-as internal + neighbor 192.168.35.5 timers 1 3 + neighbor 192.168.35.5 timers connect 1 + neighbor 192.168.35.5 aigp + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_aigp/r3/ospfd.conf b/tests/topotests/bgp_aigp/r3/ospfd.conf new file mode 100644 index 0000000..f971ad6 --- /dev/null +++ b/tests/topotests/bgp_aigp/r3/ospfd.conf @@ -0,0 +1,13 @@ +! +interface lo + ip ospf cost 10 +! +interface r3-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 30 +! +router ospf + router-id 10.0.0.3 + network 192.168.13.0/24 area 0 + network 10.0.0.3/32 area 0 diff --git a/tests/topotests/bgp_aigp/r3/zebra.conf b/tests/topotests/bgp_aigp/r3/zebra.conf new file mode 100644 index 0000000..82c87d5 --- /dev/null +++ b/tests/topotests/bgp_aigp/r3/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.0.0.3/32 +! +interface r3-eth0 + ip address 192.168.13.3/24 +! +interface r3-eth1 + ip address 192.168.35.3/24 +! diff --git a/tests/topotests/bgp_aigp/r4/bgpd.conf b/tests/topotests/bgp_aigp/r4/bgpd.conf new file mode 100644 index 0000000..aa88bac --- /dev/null +++ b/tests/topotests/bgp_aigp/r4/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.24.2 remote-as internal + neighbor 192.168.24.2 timers 1 3 + neighbor 192.168.24.2 timers connect 1 + neighbor 192.168.24.2 aigp + neighbor 192.168.24.2 route-reflector-client + neighbor 10.0.0.6 remote-as internal + neighbor 10.0.0.6 timers 1 3 + neighbor 10.0.0.6 timers connect 1 + neighbor 10.0.0.6 update-source lo + address-family ipv4 + redistribute connected + redistribute ospf + exit-address-family +! diff --git a/tests/topotests/bgp_aigp/r4/ospfd.conf b/tests/topotests/bgp_aigp/r4/ospfd.conf new file mode 100644 index 0000000..c9e6796 --- /dev/null +++ b/tests/topotests/bgp_aigp/r4/ospfd.conf @@ -0,0 +1,13 @@ +! +interface lo + ip ospf cost 10 +! +interface r4-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 20 +! +router ospf + router-id 10.0.0.4 + network 192.168.46.0/24 area 0 + network 10.0.0.4/32 area 0 diff --git a/tests/topotests/bgp_aigp/r4/zebra.conf b/tests/topotests/bgp_aigp/r4/zebra.conf new file mode 100644 index 0000000..5f544e8 --- /dev/null +++ b/tests/topotests/bgp_aigp/r4/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.0.0.4/32 +! +interface r4-eth0 + ip address 192.168.24.4/24 +! +interface r4-eth1 + ip address 192.168.46.4/24 +! diff --git a/tests/topotests/bgp_aigp/r5/bgpd.conf b/tests/topotests/bgp_aigp/r5/bgpd.conf new file mode 100644 index 0000000..4fde262 --- /dev/null +++ b/tests/topotests/bgp_aigp/r5/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.35.3 remote-as internal + neighbor 192.168.35.3 timers 1 3 + neighbor 192.168.35.3 timers connect 1 + neighbor 192.168.35.3 aigp + neighbor 192.168.35.3 route-reflector-client + neighbor 10.0.0.6 remote-as internal + neighbor 10.0.0.6 timers 1 3 + neighbor 10.0.0.6 timers connect 1 + neighbor 10.0.0.6 update-source lo + address-family ipv4 + redistribute connected + redistribute ospf + exit-address-family +! diff --git a/tests/topotests/bgp_aigp/r5/ospfd.conf b/tests/topotests/bgp_aigp/r5/ospfd.conf new file mode 100644 index 0000000..b853c74 --- /dev/null +++ b/tests/topotests/bgp_aigp/r5/ospfd.conf @@ -0,0 +1,13 @@ +! +interface lo + ip ospf cost 10 +! +interface r5-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 10 +! +router ospf + router-id 10.0.0.5 + network 192.168.56.0/24 area 0 + network 10.0.0.5/32 area 0 diff --git a/tests/topotests/bgp_aigp/r5/zebra.conf b/tests/topotests/bgp_aigp/r5/zebra.conf new file mode 100644 index 0000000..69b8bf2 --- /dev/null +++ b/tests/topotests/bgp_aigp/r5/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 10.0.0.5/32 +! +interface r5-eth0 + ip address 192.168.35.5/24 +! +interface r5-eth1 + ip address 192.168.56.5/24 +! diff --git a/tests/topotests/bgp_aigp/r6/bgpd.conf b/tests/topotests/bgp_aigp/r6/bgpd.conf new file mode 100644 index 0000000..2faae77 --- /dev/null +++ b/tests/topotests/bgp_aigp/r6/bgpd.conf @@ -0,0 +1,20 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.4 remote-as internal + neighbor 10.0.0.4 timers 1 3 + neighbor 10.0.0.4 timers connect 1 + neighbor 10.0.0.4 route-reflector-client + neighbor 10.0.0.4 update-source lo + neighbor 10.0.0.5 remote-as internal + neighbor 10.0.0.5 timers 1 3 + neighbor 10.0.0.5 timers connect 1 + neighbor 10.0.0.5 route-reflector-client + neighbor 10.0.0.5 update-source lo + neighbor 192.168.67.7 remote-as internal + neighbor 192.168.67.7 timers 1 3 + neighbor 192.168.67.7 timers connect 1 + address-family ipv4 + redistribute ospf + exit-address-family +! diff --git a/tests/topotests/bgp_aigp/r6/ospfd.conf b/tests/topotests/bgp_aigp/r6/ospfd.conf new file mode 100644 index 0000000..46b2933 --- /dev/null +++ b/tests/topotests/bgp_aigp/r6/ospfd.conf @@ -0,0 +1,17 @@ +! +interface lo + ip ospf cost 10 +! +interface r6-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 20 +! +interface r6-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 + ip ospf cost 10 +! +router ospf + router-id 10.0.0.6 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_aigp/r6/zebra.conf b/tests/topotests/bgp_aigp/r6/zebra.conf new file mode 100644 index 0000000..f8ca5f8 --- /dev/null +++ b/tests/topotests/bgp_aigp/r6/zebra.conf @@ -0,0 +1,13 @@ +! +interface lo + ip address 10.0.0.6/32 +! +interface r6-eth0 + ip address 192.168.46.6/24 +! +interface r6-eth1 + ip address 192.168.56.6/24 +! +interface r6-eth2 + ip address 192.168.67.6/24 +! diff --git a/tests/topotests/bgp_aigp/r7/bgpd.conf b/tests/topotests/bgp_aigp/r7/bgpd.conf new file mode 100644 index 0000000..639dcfe --- /dev/null +++ b/tests/topotests/bgp_aigp/r7/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.67.6 remote-as internal + neighbor 192.168.67.6 timers 1 3 + neighbor 192.168.67.6 timers connect 1 + address-family ipv4 + redistribute connected route-map rmap metric 71 + exit-address-family +! +ip prefix-list p71 seq 5 permit 10.0.0.71/32 +ip prefix-list p72 seq 5 permit 10.0.0.72/32 +! +route-map rmap permit 10 + match ip address prefix-list p71 + set aigp igp-metric +! +route-map rmap permit 20 + match ip address prefix-list p72 + set aigp 72 +exit +! diff --git a/tests/topotests/bgp_aigp/r7/zebra.conf b/tests/topotests/bgp_aigp/r7/zebra.conf new file mode 100644 index 0000000..4c05df8 --- /dev/null +++ b/tests/topotests/bgp_aigp/r7/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 10.0.0.7/32 + ip address 10.0.0.71/32 + ip address 10.0.0.72/32 +! +interface r7-eth0 + ip address 192.168.67.7/24 +! diff --git a/tests/topotests/bgp_aigp/test_bgp_aigp.py b/tests/topotests/bgp_aigp/test_bgp_aigp.py new file mode 100644 index 0000000..655e9ad --- /dev/null +++ b/tests/topotests/bgp_aigp/test_bgp_aigp.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +r7 sets aigp-metric for 10.0.0.71/32 to 71, and 72 for 10.0.0.72/32. + +r6 receives those routes with aigp-metric TLV. + +r2 and r3 receives those routes with aigp-metric TLV increased by 20, +and 30 appropriately. + +r1 receives routes with aigp-metric TLV 111,131 and 112,132 appropriately. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 8): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r6"]) + switch.add_link(tgen.gears["r7"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_aigp(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + r5 = tgen.gears["r5"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.0.0.71/32 json")) + expected = { + "paths": [ + { + "aigpMetric": 111, + "valid": True, + "nexthops": [{"hostname": "r3", "accessible": True}], + }, + { + "aigpMetric": 131, + "valid": True, + "bestpath": {"selectionReason": "Neighbor IP"}, + "nexthops": [{"hostname": "r2", "accessible": True}], + }, + ] + } + return topotest.json_cmp(output, expected) + + def _bgp_check_aigp_metric(router, prefix, aigp): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast {} json".format(prefix)) + ) + expected = {"paths": [{"aigpMetric": aigp, "valid": True}]} + return topotest.json_cmp(output, expected) + + def _bgp_check_aigp_metric_bestpath(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast 10.0.0.64/28 longer-prefixes json detail" + ) + ) + expected = { + "routes": { + "10.0.0.71/32": { + "paths": [ + { + "aigpMetric": 111, + "bestpath": {"selectionReason": "AIGP"}, + "valid": True, + "nexthops": [{"hostname": "r3", "accessible": True}], + }, + { + "aigpMetric": 131, + "valid": True, + "nexthops": [{"hostname": "r2", "accessible": True}], + }, + ], + }, + "10.0.0.72/32": { + "paths": [ + { + "aigpMetric": 112, + "bestpath": {"selectionReason": "AIGP"}, + "valid": True, + "nexthops": [{"hostname": "r3", "accessible": True}], + }, + { + "aigpMetric": 132, + "valid": True, + "nexthops": [{"hostname": "r2", "accessible": True}], + }, + ], + }, + } + } + return topotest.json_cmp(output, expected) + + # Initial converge, AIGP is not involved in best-path selection process + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "can't converge initially" + + # Enable `bgp bestpath aigp` + r1.vtysh_cmd( + """ + configure terminal + router bgp + bgp bestpath aigp + """ + ) + + # r4, 10.0.0.71/32 with aigp-metric 71 + test_func = functools.partial(_bgp_check_aigp_metric, r4, "10.0.0.71/32", 71) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "aigp-metric for 10.0.0.71/32 is not 71" + + # r5, 10.0.0.72/32 with aigp-metric 72 + test_func = functools.partial(_bgp_check_aigp_metric, r5, "10.0.0.72/32", 72) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "aigp-metric for 10.0.0.72/32 is not 72" + + # r2, 10.0.0.71/32 with aigp-metric 101 (71 + 30) + test_func = functools.partial(_bgp_check_aigp_metric, r2, "10.0.0.71/32", 101) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "aigp-metric for 10.0.0.71/32 is not 101" + + # r3, 10.0.0.72/32 with aigp-metric 92 (72 + 20) + test_func = functools.partial(_bgp_check_aigp_metric, r3, "10.0.0.72/32", 92) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "aigp-metric for 10.0.0.72/32 is not 92" + + # r1, check if AIGP is considered in best-path selection (lowest wins) + test_func = functools.partial(_bgp_check_aigp_metric_bestpath) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "AIGP attribute is not considered in best-path selection" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_always_compare_med/bgp_always_compare_med_topo1.json b/tests/topotests/bgp_always_compare_med/bgp_always_compare_med_topo1.json new file mode 100644 index 0000000..4156c6d --- /dev/null +++ b/tests/topotests/bgp_always_compare_med/bgp_always_compare_med_topo1.json @@ -0,0 +1,152 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "192.168.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start":{"ipv4":"192.168.0.0", "v4mask":24, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {}}}, + "r3": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }}}, + "r3": {"dest_link": {"r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }}} + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + }, + "static_routes":[ + { + "network":"192.168.20.1/32", + "next_hop":"Null0" + }, + { + "network":"192:168:20::1/128", + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r4": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r4": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r4": {}}}, + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r4": {}}}, + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_always_compare_med/test_bgp_always_compare_med_topo1.py b/tests/topotests/bgp_always_compare_med/test_bgp_always_compare_med_topo1.py new file mode 100644 index 0000000..fb72f43 --- /dev/null +++ b/tests/topotests/bgp_always_compare_med/test_bgp_always_compare_med_topo1.py @@ -0,0 +1,1118 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2023 by VMware, Inc. ("VMware") +# +# +################################################################################ +# Following tests are performed to validate BGP always compare MED functionality +################################################################################ +""" +1. Verify the BGP always compare MED functionality in between eBGP Peers +2. Verify the BGP always compare MED functionality in between eBGP Peers with by changing different AD values +3. Verify the BGP always compare MED functionality in between eBGP Peers by changing MED values in middle routers +4. Verify that BGP Always compare MED functionality by restarting BGP, Zebra and FRR services and clear BGP and + shutdown BGP neighbor +5. Verify BGP always compare MED functionality by performing shut/noshut on the interfaces in between BGP neighbors +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, + create_prefix_lists, + create_route_maps, + kill_router_daemons, + shutdown_bringup_interface, + stop_router, + start_router, + delete_route_maps, +) + +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, verify_bgp_rib, create_router_bgp, clear_bgp +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Reading the data from JSON File for topology creation +topo = None + +# Global variables +ADDR_TYPES = check_address_types() +NETWORK1_1 = {"ipv4": "192.168.20.1/32", "ipv6": "192:168:20::1/128"} +NETWORK1_2 = {"ipv4": "192.168.30.1/32", "ipv6": "192:168:30::1/128"} +NETWORK1_3 = {"ipv4": "192.168.40.1/32", "ipv6": "192:168:40::1/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_always_compare_med_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################## +# +# Local API +# +########################################################################################################## + + +def initial_configuration(tgen, tc_name): + """ + API to do initial set of configuration + """ + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + + step("Configure static routes in R4") + for addr_type in ADDR_TYPES: + input_static_r4 = { + "r4": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure redistribute static in R4") + input_static_redist_r4 = { + "r4": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + # Create prefix list + input_dict_23 = { + "r2": { + "prefix_lists": { + addr_type: { + "pf_ls_r2_{}".format(addr_type): [ + {"network": NETWORK1_1[addr_type], "action": "permit"} + ] + } + } + }, + "r3": { + "prefix_lists": { + "ipv4": { + "pf_ls_r3_{}".format(addr_type): [ + {"network": NETWORK1_1[addr_type], "action": "permit"} + ] + } + } + }, + } + result = create_prefix_lists(tgen, input_dict_23) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_23 = { + "r2": { + "route_maps": { + "RMAP_MED_R2": [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_ls_r2_{}".format(addr_type) + } + }, + "set": {"med": 300}, + } + ] + } + }, + "r3": { + "route_maps": { + "RMAP_MED_R3": [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_ls_r3_{}".format(addr_type) + } + }, + "set": {"med": 200}, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_23) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_r2_r3 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RMAP_MED_R2", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_MED_R3", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_r2_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +########################################################################################################## +# +# Testcases +# +########################################################################################################## + + +def test_verify_bgp_always_compare_med_functionality_bw_eBGP_peers_p0(request): + """ + Verify the BGP always compare MED functionality in between eBGP Peers + """ + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + initial_configuration(tgen, tc_name) + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + step( + "Verify that IPv4 and IPv6 eBGP neighbors are configured in between routers by following " + "commands and verify that best path chosen by lowest MED value" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'multi-path as-path relax' command at R1.") + configure_bgp = { + "r1": {"bgp": {"local_as": "100", "bestpath": {"aspath": "multipath-relax"}}} + } + result = create_router_bgp(tgen, topo, configure_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'multi-path as-path relax' command, " + "its also chooses lowest MED to reach destination." + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh1 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=[nh1, nh2]) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'bgp always-compare-med' command at R1.") + input_dict_r1 = {"r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": True}}} + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'bgp always-compare-med', its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Remove 'bgp always-compare-med' command at R1.") + input_dict_r1 = { + "r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": False}} + } + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that 'bgp always-compare-med' command is removed") + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh1 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=[nh1, nh2]) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Remove 'multi-path as-path relax' command at R1") + configure_bgp = { + "r1": { + "bgp": { + "local_as": "100", + "bestpath": {"aspath": "multipath-relax", "delete": True}, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify route selection after removing 'multi-path as-path relax' command") + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_always_compare_med_functionality_bw_eBGP_peers_by_changing_AD_values_p0( + request, +): + """ + Verify the BGP always compare MED functionality in between eBGP Peers with by changing different AD values. + """ + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + initial_configuration(tgen, tc_name) + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + step( + "Verify that IPv4 and IPv6 eBGP neighbors are configured in between routers by following " + "commands and verify that best path chosen by lowest MED value" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'bgp always-compare-med' command at R1.") + input_dict_r1 = {"r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": True}}} + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'bgp always-compare-med', its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure AD value=100 at R2 and AD value=200 at R3 towards R1") + input_dict_1 = { + "r2": { + "bgp": { + "local_as": 200, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 100, "ibgp": 100, "local": 100} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 100, "ibgp": 100, "local": 100} + } + }, + }, + } + }, + "r3": { + "bgp": { + "local_as": 300, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that inspite of AD values, always lowest MED value is getting " + "selected at destination router R1" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_always_compare_med_functionality_bw_eBGP_peers_by_changing_MED_values_p1( + request, +): + """ + Verify the BGP always compare MED functionality in between eBGP Peers by changing MED values in middle routers + """ + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + initial_configuration(tgen, tc_name) + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + step( + "Verify that IPv4 and IPv6 eBGP neighbors are configured in between routers by following " + "commands and verify that best path chosen by lowest MED value" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'multi-path as-path relax' command at R1.") + configure_bgp = { + "r1": {"bgp": {"local_as": "100", "bestpath": {"aspath": "multipath-relax"}}} + } + result = create_router_bgp(tgen, topo, configure_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'multi-path as-path relax' command, " + "its also chooses lowest MED to reach destination." + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh1 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=[nh1, nh2]) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'bgp always-compare-med' command at R1.") + input_dict_r1 = {"r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": True}}} + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'bgp always-compare-med', its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change the MED value 150 in R2 router.") + input_dict = {"r2": {"route_maps": ["RMAP_MED_R2"]}} + result = delete_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r2": { + "route_maps": { + "RMAP_MED_R2": [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_ls_r2_{}".format(addr_type) + } + }, + "set": {"med": 150}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that after changing MED, its chooses lowest MED value path") + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change the MED value 100 in R3 router.") + input_dict = {"r3": {"route_maps": ["RMAP_MED_R3"]}} + result = delete_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "RMAP_MED_R3": [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_ls_r3_{}".format(addr_type) + } + }, + "set": {"med": 100}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that after changing MED, its chooses lowest MED value path") + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_always_compare_med_functionality_by_restarting_daemons_clear_bgp_shut_neighbors_p1( + request, +): + """ + Verify that BGP Always compare MED functionality by restarting BGP, Zebra and FRR services and clear BGP and shutdown BGP neighbor + """ + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + initial_configuration(tgen, tc_name) + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + step( + "Verify that IPv4 and IPv6 eBGP neighbors are configured in between routers by following " + "commands and verify that best path chosen by lowest MED value" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'multi-path as-path relax' command at R1.") + configure_bgp = { + "r1": {"bgp": {"local_as": "100", "bestpath": {"aspath": "multipath-relax"}}} + } + result = create_router_bgp(tgen, topo, configure_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'multi-path as-path relax' command, " + "its also chooses lowest MED to reach destination." + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh1 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=[nh1, nh2]) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'bgp always-compare-med' command at R1.") + input_dict_r1 = {"r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": True}}} + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'bgp always-compare-med', its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restart the BGPd/Zebra/FRR service on R1") + for daemon in ["bgpd", "zebra", "frr"]: + if daemon == "frr": + stop_router(tgen, "r1") + start_router(tgen, "r1") + else: + kill_router_daemons(tgen, "r1", daemon) + + step( + "Verify after restarting dameons and frr services, its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Clear bgp on R1") + clear_bgp(tgen, None, "r1") + + step("Verify after clearing BGP, its chooses lowest MED value path") + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Perform BGP neighborship shut/no shut") + for action, keyword in zip([True, False], ["shut", "noshut"]): + for addr_type in ADDR_TYPES: + input_dict = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r1": {"shutdown": action}} + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify after {} BGP, its chooses lowest MED value path".format(keyword)) + if action: + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + else: + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_always_compare_med_functionality_by_shut_noshut_interfaces_bw_bgp_neighbors_p1( + request, +): + """ + Verify BGP always compare MED functionality by performing shut/noshut on the interfaces in between BGP neighbors + """ + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + initial_configuration(tgen, tc_name) + + step( + "Configure IPv4 and IPv6, eBGP neighbors between R1,R2 and R3 routers as per base config" + ) + step( + "Verify that IPv4 and IPv6 eBGP neighbors are configured in between routers by following " + "commands and verify that best path chosen by lowest MED value" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'multi-path as-path relax' command at R1.") + configure_bgp = { + "r1": {"bgp": {"local_as": "100", "bestpath": {"aspath": "multipath-relax"}}} + } + result = create_router_bgp(tgen, topo, configure_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'multi-path as-path relax' command, " + "its also chooses lowest MED to reach destination." + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh1 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=[nh1, nh2]) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure 'bgp always-compare-med' command at R1.") + input_dict_r1 = {"r1": {"bgp": {"local_as": "100", "bgp_always_compare_med": True}}} + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that after applying 'bgp always-compare-med', its chooses lowest MED value path" + ) + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for action, keyword in zip([False, True], ["Shut", "No Shut"]): + step( + "{} the interface on the link between R3 & R4 and R2 & R4 routers".format( + keyword + ) + ) + intf2_4 = topo["routers"]["r2"]["links"]["r4"]["interface"] + intf3_4 = topo["routers"]["r3"]["links"]["r4"]["interface"] + for dut, intf in zip(["r2", "r3"], [intf2_4, intf3_4]): + shutdown_bringup_interface(tgen, dut, intf, action) + + for addr_type in ADDR_TYPES: + input_static_r1 = { + "r1": {"static_routes": [{"network": NETWORK1_1[addr_type]}]} + } + nh = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + if action: + result = verify_bgp_rib(tgen, addr_type, "r1", input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_r1, next_hop=nh) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + else: + result = verify_bgp_rib( + tgen, addr_type, "r1", input_static_r1, expected=False + ) + assert ( + result is not True + ), "Testcase {} :Failed \n Routes are still present in BGP table\n Error {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, "r1", input_static_r1, next_hop=nh, expected=False + ) + assert ( + result is not True + ), "Testcase {} :Failed \n Routes are still present in FIB \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_as_allow_in/bgp_as_allow_in.json b/tests/topotests/bgp_as_allow_in/bgp_as_allow_in.json new file mode 100644 index 0000000..943876c --- /dev/null +++ b/tests/topotests/bgp_as_allow_in/bgp_as_allow_in.json @@ -0,0 +1,266 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "500", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_as_allow_in/test_bgp_as_allow_in.py b/tests/topotests/bgp_as_allow_in/test_bgp_as_allow_in.py new file mode 100644 index 0000000..c49a2e5 --- /dev/null +++ b/tests/topotests/bgp_as_allow_in/test_bgp_as_allow_in.py @@ -0,0 +1,957 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test bgp allowas-in functionality: + +- Verify that routes coming from same AS are accepted only when + '"allowas-in" is configuerd. +- Verify that "allowas-in" feature works per address-family/VRF + 'basis and doesn't impact the other AFIs. +- Verify that the if number of occurrences of AS number in path is + 'more than the configured allowas-in value then we do not accept + 'the route. +- Verify that when we advertise a network, learned from the same AS + 'via allowas-in command, to an iBGP neighbor we see multiple + 'occurrences. +- Verify that when we advertise a network, learned from the same AS + 'via allowas-in command, to an eBGP neighbor we see multiple + 'occurrences of our own AS based on configured value+1. +""" + +import os +import sys +import time +import json +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + create_route_maps, + check_address_types, + step, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, +) +from lib.topojson import build_topo_from_json, build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": "2.2.2.2/32", "ipv6": "22:22::2/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_as_allow_in.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global BGP_CONVERGENCE + global ADDR_TYPES + + # Api call verify whether BGP is converged + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_bgp_allowas_in_p0(request): + """ + Verify that routes coming from same AS are accepted only when + "allowas-in" is configuerd. + + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Advertise prefix 2.2.2.2/32 from Router-1(AS-200).") + step("Advertise an ipv6 prefix 22:22::2/128 from Router-1(AS-200).") + # configure static routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_4 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + 'Check BGP table of router R3 using "sh bgp ipv4" and "sh bgp ' + 'ipv6" command.' + ) + step( + "We should not see prefix advertised from R1 in R3's BGP " + "table without allowas-in." + ) + logger.info("Verifying %s routes on r3, route should not be present", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=NEXT_HOP_IP[addr_type], + protocol=protocol, + expected=False, + ) + assert result is not True, ( + "Testcase {} : Failed \n".format(tc_name) + + "Expected behavior: routes should not present in rib \n" + + "Error: {}".format(result) + ) + + step("Configure allowas-in on R3 for R2.") + step("We should see the prefix advertised from R1 in R3's BGP table.") + # Api call to enable allowas-in in bgp process. + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": {"number_occurences": 1} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_rib(tgen, addr_type, dut, input_dict_4, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_bgp_allowas_in_per_addr_family_p0(request): + """ + Verify that "allowas-in" feature works per address-family/VRF + basis and doesn't impact the other AFIs. + + """ + + # This test is applicable only for dual stack. + if "ipv4" not in ADDR_TYPES or "ipv6" not in ADDR_TYPES: + pytest.skip("NOT APPLICABLE") + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Advertise prefix 2.2.2.2/32 from Router-1(AS-200).") + step("Advertise an ipv6 prefix 22:22::2/128 from Router-1(AS-200).") + # configure static routes routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_4 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure allowas-in on R3 for R2 under IPv4 addr-family only") + # Api call to enable allowas-in in bgp process. + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"allowas-in": {"number_occurences": 1}} + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + static_route_ipv4 = { + "r1": { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": NEXT_HOP_IP["ipv4"]} + ] + } + } + + static_route_ipv6 = { + "r1": { + "static_routes": [ + {"network": NETWORK["ipv6"], "next_hop": NEXT_HOP_IP["ipv6"]} + ] + } + } + step("We should see R1 advertised prefix only in IPv4 AFI " "not in IPv6 AFI.") + result = verify_rib(tgen, "ipv4", dut, static_route_ipv4, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_rib( + tgen, "ipv6", dut, static_route_ipv6, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n".format(tc_name) + + "Expected behavior: routes are should not be present in ipv6 rib\n" + + " Error: {}".format(result) + ) + + step("Repeat the same test for IPv6 AFI.") + step("Configure allowas-in on R3 for R2 under IPv6 addr-family only") + # Api call to enable allowas-in in bgp process. + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": { + "number_occurences": 2, + "delete": True, + } + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"allowas-in": {"number_occurences": 2}} + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step("We should see R1 advertised prefix only in IPv6 AFI " "not in IPv4 AFI.") + result = verify_rib( + tgen, "ipv4", dut, static_route_ipv4, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n".format(tc_name) + + "Expected behavior: routes should not be present in ipv4 rib\n" + + " Error: {}".format(result) + ) + result = verify_rib(tgen, "ipv6", dut, static_route_ipv6, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_allowas_in_no_of_occurrences_p0(request): + """ + Verify that the if number of occurrences of AS number in path is + more than the configured allowas-in value then we do not accept + the route. + + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R1 to prepend AS 4 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": { + "as_num": "200 200 200 200", + "as_action": "prepend", + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R1") + # Configure neighbor for route map + input_dict_7 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step('Configure "allowas-in 4" on R3 for R2.') + # Api call to enable allowas-in in bgp process. + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": {"number_occurences": 4} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_rib( + tgen, addr_type, dut, static_routes, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n ".format(tc_name) + + "Expected behavior: routes are should not be present in rib\n" + + "Error: {}".format(result) + ) + + for addr_type in ADDR_TYPES: + step('Configure "allowas-in 5" on R3 for R2.') + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": {"number_occurences": 5} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + result = verify_rib(tgen, addr_type, dut, static_routes, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_bgp_allowas_in_sameastoibgp_p1(request): + """ + Verify that when we advertise a network, learned from the same AS + via allowas-in command, to an iBGP neighbor we see multiple + occurrences. + + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R2 to prepend AS 2 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r2": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": {"as_num": "200 200", "as_action": "prepend"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R2") + # Configure neighbor for route map + input_dict_7 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step('Configure "allowas-in 3" on R3 for R1.') + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": {"number_occurences": 3} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_1 = { + "r4": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "allowas-in": {"number_occurences": 3} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + dut = "r4" + path = "100 200 200 200" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, aspath=path) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_bgp_allowas_in_sameastoebgp_p1(request): + """ + Verify that when we advertise a network, learned from the same AS + via allowas-in command, to an eBGP neighbor we see multiple + occurrences of our own AS based on configured value+1. + + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R2 to prepend AS 2 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r2": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": {"as_num": "200 200", "as_action": "prepend"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R2") + # Configure neighbor for route map + input_dict_7 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step('Configure "allowas-in 3" on R3 for R1.') + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "allowas-in": {"number_occurences": 3} + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + static_routes = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + dut = "r5" + path = "200 100 200 200 200" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, aspath=path) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_as_override/__init__.py b/tests/topotests/bgp_as_override/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_as_override/r1/bgpd.conf b/tests/topotests/bgp_as_override/r1/bgpd.conf new file mode 100644 index 0000000..3cfb7a2 --- /dev/null +++ b/tests/topotests/bgp_as_override/r1/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_as_override/r1/zebra.conf b/tests/topotests/bgp_as_override/r1/zebra.conf new file mode 100644 index 0000000..63728eb --- /dev/null +++ b/tests/topotests/bgp_as_override/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.1.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_override/r2/bgpd.conf b/tests/topotests/bgp_as_override/r2/bgpd.conf new file mode 100644 index 0000000..5e3b0c7 --- /dev/null +++ b/tests/topotests/bgp_as_override/r2/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 +! diff --git a/tests/topotests/bgp_as_override/r2/zebra.conf b/tests/topotests/bgp_as_override/r2/zebra.conf new file mode 100644 index 0000000..5bdfd02 --- /dev/null +++ b/tests/topotests/bgp_as_override/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.1/30 +! +interface r2-eth1 + ip address 192.168.2.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_override/r3/bgpd.conf b/tests/topotests/bgp_as_override/r3/bgpd.conf new file mode 100644 index 0000000..6bbe56b --- /dev/null +++ b/tests/topotests/bgp_as_override/r3/bgpd.conf @@ -0,0 +1,13 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + neighbor 192.168.3.1 remote-as external + neighbor 192.168.3.1 timers 1 3 + neighbor 192.168.3.1 timers connect 1 + address-family ipv4 unicast + neighbor 192.168.3.1 as-override + exit-address-family +! diff --git a/tests/topotests/bgp_as_override/r3/zebra.conf b/tests/topotests/bgp_as_override/r3/zebra.conf new file mode 100644 index 0000000..77782be --- /dev/null +++ b/tests/topotests/bgp_as_override/r3/zebra.conf @@ -0,0 +1,9 @@ +! +interface r3-eth0 + ip address 192.168.2.2/30 +! +interface r3-eth1 + ip address 192.168.3.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_override/r4/bgpd.conf b/tests/topotests/bgp_as_override/r4/bgpd.conf new file mode 100644 index 0000000..1bdee08 --- /dev/null +++ b/tests/topotests/bgp_as_override/r4/bgpd.conf @@ -0,0 +1,7 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.3.2 remote-as external + neighbor 192.168.3.2 timers 1 3 + neighbor 192.168.3.2 timers connect 1 +! diff --git a/tests/topotests/bgp_as_override/r4/zebra.conf b/tests/topotests/bgp_as_override/r4/zebra.conf new file mode 100644 index 0000000..71dc595 --- /dev/null +++ b/tests/topotests/bgp_as_override/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.3.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_override/test_bgp_as_override.py b/tests/topotests/bgp_as_override/test_bgp_as_override.py new file mode 100644 index 0000000..7cb4f81 --- /dev/null +++ b/tests/topotests/bgp_as_override/test_bgp_as_override.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 7): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_as_override(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + + def _bgp_converge(): + output = json.loads(r3.vtysh_cmd("show ip bgp neighbor 192.168.2.1 json")) + expected = { + "192.168.2.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_as_override(): + output = json.loads(r4.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.1/32": [{"valid": True, "path": "65003 65002 65003"}] + } + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R4" + + step("Check if BGP as-override from R3 works") + test_func = functools.partial(_bgp_as_override) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see overriden ASN (65001) from R3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/__init__.py b/tests/topotests/bgp_as_wide_bgp_identifier/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r1/bgpd.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r1/bgpd.conf new file mode 100644 index 0000000..cb04749 --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r1/bgpd.conf @@ -0,0 +1,7 @@ +! exit1 +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r1/zebra.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r1/zebra.conf new file mode 100644 index 0000000..c060e14 --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r1/zebra.conf @@ -0,0 +1,6 @@ +! exit1 +interface r1-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r2/bgpd.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r2/bgpd.conf new file mode 100644 index 0000000..bed6885 --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r2/bgpd.conf @@ -0,0 +1,9 @@ +! spine +router bgp 65002 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.3 remote-as 65002 + neighbor 192.168.255.3 timers 3 10 +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r2/zebra.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r2/zebra.conf new file mode 100644 index 0000000..a45520f --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r2/zebra.conf @@ -0,0 +1,6 @@ +! spine +interface r2-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r3/bgpd.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r3/bgpd.conf new file mode 100644 index 0000000..384e617 --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r3/bgpd.conf @@ -0,0 +1,7 @@ +! exit2 +router bgp 65002 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/r3/zebra.conf b/tests/topotests/bgp_as_wide_bgp_identifier/r3/zebra.conf new file mode 100644 index 0000000..2f4dbc5 --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/r3/zebra.conf @@ -0,0 +1,6 @@ +! exit2 +interface r3-eth0 + ip address 192.168.255.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_as_wide_bgp_identifier/test_bgp_as_wide_bgp_identifier.py b/tests/topotests/bgp_as_wide_bgp_identifier/test_bgp_as_wide_bgp_identifier.py new file mode 100644 index 0000000..5c09a6b --- /dev/null +++ b/tests/topotests/bgp_as_wide_bgp_identifier/test_bgp_as_wide_bgp_identifier.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_as_wide_bgp_identifier.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Donatas Abraitis +# + +""" +rfc6286: Autonomous-System-Wide Unique BGP Identifier for BGP-4 +Test if 'Bad BGP Identifier' notification is sent only to +internal peers (autonomous-system-wide). eBGP peers are not +affected and should work. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_as_wide_bgp_identifier(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = {"192.168.255.1": {"bgpState": "Established"}} + return topotest.json_cmp(output, expected) + + def _bgp_failed(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "lastNotificationReason": "OPEN Message Error/Bad BGP Identifier" + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, tgen.gears["r1"]) + success, result = topotest.run_and_expect(test_func, None, count=260, wait=0.5) + + assert result is None, 'Failed to converge: "{}"'.format(tgen.gears["r1"]) + + test_func = functools.partial(_bgp_failed, tgen.gears["r3"]) + success, result = topotest.run_and_expect(test_func, None, count=260, wait=0.5) + + assert result is None, 'Bad BGP Identifier notification not sent: "{}"'.format( + tgen.gears["r3"] + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_asdot_regex/__init__.py b/tests/topotests/bgp_asdot_regex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_asdot_regex/r1/bgpd.conf b/tests/topotests/bgp_asdot_regex/r1/bgpd.conf new file mode 100644 index 0000000..4dd95dd --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r1/bgpd.conf @@ -0,0 +1,27 @@ +router bgp 1.1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.255.2 remote-as 1.2 + address-family ipv4 unicast + network 172.31.1.0/24 route-map rmapout + network 172.31.2.0/24 route-map rmapout + neighbor 192.168.255.2 route-map rmapin in + neighbor 192.168.255.2 activate + exit-address-family +exit +bgp as-path access-list only1_4 permit _1.4_ +bgp as-path access-list only65540 permit _65540_ +access-list 172313 permit 172.31.3.0/24 +access-list 172314 permit 172.31.4.0/24 +route-map rmapout permit 1 + set as-path prepend 1.4 +exit +route-map rmapin permit 1 + match ip address 172313 + match as-path only1_4 +exit +route-map rmapin permit 2 + match ip address 172314 + match as-path only65540 +exit + diff --git a/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json b/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json new file mode 100644 index 0000000..39ed61f --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r1/show_bgp_ipv4.json @@ -0,0 +1,79 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "192.168.255.1", + "defaultLocPrf": 100, + "localAS": "1.1", + "routes": { "172.31.1.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.1.0", + "prefixLen":24, + "network":"172.31.1.0/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"1.4", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +] +,"172.31.2.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.2.0", + "prefixLen":24, + "network":"172.31.2.0/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"1.4", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +] +,"172.31.3.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.3.0", + "prefixLen":24, + "network":"172.31.3.0/24", + "metric":0, + "weight":0, + "peerId":"192.168.255.2", + "path":"1.2 1.4", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.255.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +] + } } diff --git a/tests/topotests/bgp_asdot_regex/r1/zebra.conf b/tests/topotests/bgp_asdot_regex/r1/zebra.conf new file mode 100644 index 0000000..6e9b0b4 --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_asdot_regex/r2/bgpd.conf b/tests/topotests/bgp_asdot_regex/r2/bgpd.conf new file mode 100644 index 0000000..216dbd1 --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r2/bgpd.conf @@ -0,0 +1,26 @@ +router bgp 65538 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.255.1 remote-as 65537 + address-family ipv4 unicast + network 172.31.3.0/24 route-map rmapout + network 172.31.4.0/24 route-map rmapout + neighbor 192.168.255.1 route-map rmapin in + neighbor 192.168.255.1 activate + exit-address-family +exit +bgp as-path access-list only65540 permit _65540_ +bgp as-path access-list only1_4 permit _1.4_ +access-list 172311 permit 172.31.1.0/24 +access-list 172312 permit 172.31.2.0/24 +route-map rmapout permit 1 + set as-path prepend 65540 +exit +route-map rmapin permit 1 + match ip address 172311 + match as-path only65540 +exit +route-map rmapin permit 2 + match ip address 172312 + match as-path only1_4 +exit diff --git a/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json b/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json new file mode 100644 index 0000000..3013317 --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r2/show_bgp_ipv4.json @@ -0,0 +1,79 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "192.168.255.2", + "defaultLocPrf": 100, + "localAS": 65538, + "routes": { "172.31.1.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.1.0", + "prefixLen":24, + "network":"172.31.1.0/24", + "metric":0, + "weight":0, + "peerId":"192.168.255.1", + "path":"65537 65540", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.255.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +] +,"172.31.3.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.3.0", + "prefixLen":24, + "network":"172.31.3.0/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"65540", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +] +,"172.31.4.0/24": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"172.31.4.0", + "prefixLen":24, + "network":"172.31.4.0/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"65540", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +] + } } diff --git a/tests/topotests/bgp_asdot_regex/r2/zebra.conf b/tests/topotests/bgp_asdot_regex/r2/zebra.conf new file mode 100644 index 0000000..6c14de5 --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_asdot_regex/test_bgp_asdot_regex.py b/tests/topotests/bgp_asdot_regex/test_bgp_asdot_regex.py new file mode 100644 index 0000000..4883e84 --- /dev/null +++ b/tests/topotests/bgp_asdot_regex/test_bgp_asdot_regex.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python + +# +# test_bgp_asdot_regex.py +# Part of Topotests +# +# Copyright 2022 6WIND S.A. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_bgp_asdot_regex.py: + +Test how regex applies when asnotation to forge bgp config is based on dot or not. +""" + +import os +import sys +import json +import pytest +from functools import partial + +# add after imports, before defining classes or functions: +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_asdot_regex(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + logger.info("Check if neighbor sessions are up in {}".format(router1.name)) + test_func = partial(_bgp_converge, router1) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + assert result is None, 'Failed to see BGP convergence in "{}"'.format(router1.name) + + logger.info("BGP neighbor session is up in {}".format(router1.name)) + + logger.info("waiting for bgp peers exchanging UPDATES") + + for router in tgen.routers().values(): + ref_file = "{}/{}/show_bgp_ipv4.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show bgp ipv4 unicast json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=40, wait=2.5) + assertmsg = "{}: BGP UPDATE exchange failure".format(router.name) + assert res is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_aspath_zero/__init__.py b/tests/topotests/bgp_aspath_zero/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_aspath_zero/exabgp.env b/tests/topotests/bgp_aspath_zero/exabgp.env new file mode 100644 index 0000000..28e6423 --- /dev/null +++ b/tests/topotests/bgp_aspath_zero/exabgp.env @@ -0,0 +1,53 @@ +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_aspath_zero/peer1/exabgp.cfg b/tests/topotests/bgp_aspath_zero/peer1/exabgp.cfg new file mode 100644 index 0000000..fe9ea01 --- /dev/null +++ b/tests/topotests/bgp_aspath_zero/peer1/exabgp.cfg @@ -0,0 +1,17 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.2; + local-address 10.0.0.2; + local-as 65001; + peer-as 65534; + + static { + route 192.168.100.101/32 { + next-hop 10.0.0.2; + } + + route 192.168.100.102/32 { + as-path [65000 0 65001]; + next-hop 10.0.0.2; + } + } +} diff --git a/tests/topotests/bgp_aspath_zero/r1/bgpd.conf b/tests/topotests/bgp_aspath_zero/r1/bgpd.conf new file mode 100644 index 0000000..002a5c7 --- /dev/null +++ b/tests/topotests/bgp_aspath_zero/r1/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65534 + no bgp ebgp-requires-policy + neighbor 10.0.0.2 remote-as external + neighbor 10.0.0.2 timers 3 10 +! diff --git a/tests/topotests/bgp_aspath_zero/r1/zebra.conf b/tests/topotests/bgp_aspath_zero/r1/zebra.conf new file mode 100644 index 0000000..22a26ac --- /dev/null +++ b/tests/topotests/bgp_aspath_zero/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 10.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_aspath_zero/test_bgp_aspath_zero.py b/tests/topotests/bgp_aspath_zero/test_bgp_aspath_zero.py new file mode 100644 index 0000000..0f1a083 --- /dev/null +++ b/tests/topotests/bgp_aspath_zero/test_bgp_aspath_zero.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if BGP UPDATE with AS-PATH attribute with value zero (0) +is threated as withdrawal. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.2", defaultRoute="via 10.0.0.1") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(peer1) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + router.start() + + peer = tgen.gears["peer1"] + peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env")) + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_aggregator_zero(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp neighbor 10.0.0.2 json") + ) + expected = { + "10.0.0.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "More than one prefix seen at r1, SHOULD be only one." + + def _bgp_has_correct_routes_without_asn_0(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show ip bgp json")) + expected = {"routes": {"192.168.100.101/32": [{"valid": True}]}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_has_correct_routes_without_asn_0) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed listing 192.168.100.101/32, SHOULD be accepted." + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_auth/R1/bgpd.conf b/tests/topotests/bgp_auth/R1/bgpd.conf new file mode 100644 index 0000000..310841f --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65001 + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 password hello1 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 timers connect 5 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 password hello2 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + address-family ipv4 unicast + neighbor 2.2.2.2 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R1/bgpd_multi_vrf.conf b/tests/topotests/bgp_auth/R1/bgpd_multi_vrf.conf new file mode 100644 index 0000000..644e01c --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd_multi_vrf.conf @@ -0,0 +1,39 @@ +! debug bgp neighbor-events + +router bgp 65001 vrf blue + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 password blue1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password blue2 + address-family ipv4 unicast + neighbor 2.2.2.2 activate + neighbor 3.3.3.3 activate + +router bgp 65001 vrf red + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo2 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 password red1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo2 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password red2 + address-family ipv4 unicast + neighbor 2.2.2.2 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R1/bgpd_multi_vrf_prefix.conf b/tests/topotests/bgp_auth/R1/bgpd_multi_vrf_prefix.conf new file mode 100644 index 0000000..7e15720 --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd_multi_vrf_prefix.conf @@ -0,0 +1,37 @@ +router bgp 65001 vrf blue + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor TWO_GROUP_blue peer-group + neighbor TWO_GROUP_blue remote-as 65002 + neighbor TWO_GROUP_blue update-source 1.1.1.1 + neighbor TWO_GROUP_blue ebgp-multihop 3 + neighbor TWO_GROUP_blue password blue1 + neighbor THREE_GROUP_blue peer-group + neighbor THREE_GROUP_blue remote-as 65003 + neighbor THREE_GROUP_blue update-source 1.1.1.1 + neighbor THREE_GROUP_blue ebgp-multihop 3 + neighbor THREE_GROUP_blue password blue2 + bgp listen range 2.2.2.0/24 peer-group TWO_GROUP_blue + bgp listen range 3.3.3.0/24 peer-group THREE_GROUP_blue + address-family ipv4 unicast + neighbor TWO_GROUP_blue maximum-prefix 4294967295 + neighbor THREE_GROUP_blue maximum-prefix 4294967295 + +router bgp 65001 vrf red + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor TWO_GROUP_red peer-group + neighbor TWO_GROUP_red remote-as 65002 + neighbor TWO_GROUP_red update-source 1.1.1.1 + neighbor TWO_GROUP_red ebgp-multihop 3 + neighbor TWO_GROUP_red password red1 + neighbor THREE_GROUP_red peer-group + neighbor THREE_GROUP_red remote-as 65003 + neighbor THREE_GROUP_red update-source 1.1.1.1 + neighbor THREE_GROUP_red ebgp-multihop 3 + neighbor THREE_GROUP_red password red2 + bgp listen range 2.2.2.0/24 peer-group TWO_GROUP_red + bgp listen range 3.3.3.0/24 peer-group THREE_GROUP_red + address-family ipv4 unicast + neighbor TWO_GROUP_red maximum-prefix 4294967295 + neighbor THREE_GROUP_red maximum-prefix 4294967295 diff --git a/tests/topotests/bgp_auth/R1/bgpd_prefix.conf b/tests/topotests/bgp_auth/R1/bgpd_prefix.conf new file mode 100644 index 0000000..9200b05 --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65001 + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor TWO_GROUP peer-group + neighbor TWO_GROUP remote-as 65002 + neighbor TWO_GROUP update-source 1.1.1.1 + neighbor TWO_GROUP ebgp-multihop 3 + neighbor TWO_GROUP password hello1 + neighbor THREE_GROUP peer-group + neighbor THREE_GROUP remote-as 65003 + neighbor THREE_GROUP update-source 1.1.1.1 + neighbor THREE_GROUP ebgp-multihop 3 + neighbor THREE_GROUP password hello2 + bgp listen range 2.2.2.0/24 peer-group TWO_GROUP + bgp listen range 3.3.3.0/24 peer-group THREE_GROUP + address-family ipv4 unicast + neighbor TWO_GROUP maximum-prefix 4294967295 + neighbor THREE_GROUP maximum-prefix 4294967295 diff --git a/tests/topotests/bgp_auth/R1/bgpd_vrf.conf b/tests/topotests/bgp_auth/R1/bgpd_vrf.conf new file mode 100644 index 0000000..5799da1 --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd_vrf.conf @@ -0,0 +1,20 @@ +! debug bgp neighbor-events + +router bgp 65001 vrf blue + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 password hello1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password hello2 + address-family ipv4 unicast + neighbor 2.2.2.2 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R1/bgpd_vrf_prefix.conf b/tests/topotests/bgp_auth/R1/bgpd_vrf_prefix.conf new file mode 100644 index 0000000..d68951b --- /dev/null +++ b/tests/topotests/bgp_auth/R1/bgpd_vrf_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65001 vrf blue + timers bgp 3 9 + bgp router-id 1.1.1.1 + neighbor TWO_GROUP_blue peer-group + neighbor TWO_GROUP_blue remote-as 65002 + neighbor TWO_GROUP_blue update-source 1.1.1.1 + neighbor TWO_GROUP_blue ebgp-multihop 3 + neighbor TWO_GROUP_blue password hello1 + neighbor THREE_GROUP_blue peer-group + neighbor THREE_GROUP_blue remote-as 65003 + neighbor THREE_GROUP_blue update-source 1.1.1.1 + neighbor THREE_GROUP_blue ebgp-multihop 3 + neighbor THREE_GROUP_blue password hello2 + bgp listen range 2.2.2.0/24 peer-group TWO_GROUP_blue + bgp listen range 3.3.3.0/24 peer-group THREE_GROUP_blue + address-family ipv4 unicast + neighbor TWO_GROUP_blue maximum-prefix 4294967295 + neighbor THREE_GROUP_blue maximum-prefix 4294967295 diff --git a/tests/topotests/bgp_auth/R1/empty.conf b/tests/topotests/bgp_auth/R1/empty.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_auth/R1/ospfd.conf b/tests/topotests/bgp_auth/R1/ospfd.conf new file mode 100644 index 0000000..b28dd59 --- /dev/null +++ b/tests/topotests/bgp_auth/R1/ospfd.conf @@ -0,0 +1,22 @@ +interface R1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf + network 10.10.0.0/16 area 0 + network 10.20.0.0/16 area 0 + network 1.1.1.1/32 area 0 \ No newline at end of file diff --git a/tests/topotests/bgp_auth/R1/ospfd_multi_vrf.conf b/tests/topotests/bgp_auth/R1/ospfd_multi_vrf.conf new file mode 100644 index 0000000..b64bec8 --- /dev/null +++ b/tests/topotests/bgp_auth/R1/ospfd_multi_vrf.conf @@ -0,0 +1,26 @@ +interface R1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.10.0.0/16 area 0 + network 10.20.0.0/16 area 0 + network 1.1.1.1/32 area 0 +router ospf vrf red + network 10.10.0.0/16 area 0 + network 10.20.0.0/16 area 0 + network 1.1.1.1/32 area 0 diff --git a/tests/topotests/bgp_auth/R1/ospfd_vrf.conf b/tests/topotests/bgp_auth/R1/ospfd_vrf.conf new file mode 100644 index 0000000..deaf53d --- /dev/null +++ b/tests/topotests/bgp_auth/R1/ospfd_vrf.conf @@ -0,0 +1,22 @@ +interface R1-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R1-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.10.0.0/16 area 0 + network 10.20.0.0/16 area 0 + network 1.1.1.1/32 area 0 diff --git a/tests/topotests/bgp_auth/R1/zebra.conf b/tests/topotests/bgp_auth/R1/zebra.conf new file mode 100644 index 0000000..a0b062c --- /dev/null +++ b/tests/topotests/bgp_auth/R1/zebra.conf @@ -0,0 +1,20 @@ +! +interface lo + ip address 1.1.1.1/32 +interface lo1 vrf blue + ip address 1.1.1.1/32 +interface lo2 vrf red + ip address 1.1.1.1/32 +interface R1-eth0 + ip address 10.10.0.1/24 +interface R1-eth1 + ip address 10.20.0.1/24 +interface R1-eth2 vrf blue + ip address 10.10.0.1/24 +interface R1-eth3 vrf blue + ip address 10.20.0.1/24 +interface R1-eth4 vrf red + ip address 10.10.0.1/24 +interface R1-eth5 vrf red + ip address 10.20.0.1/24 +! diff --git a/tests/topotests/bgp_auth/R2/bgpd.conf b/tests/topotests/bgp_auth/R2/bgpd.conf new file mode 100644 index 0000000..2149c05 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65002 + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/bgpd_multi_vrf.conf b/tests/topotests/bgp_auth/R2/bgpd_multi_vrf.conf new file mode 100644 index 0000000..af88fe1 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd_multi_vrf.conf @@ -0,0 +1,37 @@ +router bgp 65002 vrf blue + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password blue1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password blue3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate + +router bgp 65002 vrf red + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo2 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password red1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo2 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password red3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/bgpd_multi_vrf_prefix.conf b/tests/topotests/bgp_auth/R2/bgpd_multi_vrf_prefix.conf new file mode 100644 index 0000000..af88fe1 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd_multi_vrf_prefix.conf @@ -0,0 +1,37 @@ +router bgp 65002 vrf blue + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password blue1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password blue3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate + +router bgp 65002 vrf red + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo2 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password red1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo2 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password red3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/bgpd_prefix.conf b/tests/topotests/bgp_auth/R2/bgpd_prefix.conf new file mode 100644 index 0000000..2149c05 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65002 + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/bgpd_vrf.conf b/tests/topotests/bgp_auth/R2/bgpd_vrf.conf new file mode 100644 index 0000000..03cadb3 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd_vrf.conf @@ -0,0 +1,18 @@ +router bgp 65002 vrf blue + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/bgpd_vrf_prefix.conf b/tests/topotests/bgp_auth/R2/bgpd_vrf_prefix.conf new file mode 100644 index 0000000..03cadb3 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/bgpd_vrf_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65002 vrf blue + timers bgp 3 9 + bgp router-id 2.2.2.2 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello1 + neighbor 3.3.3.3 remote-as 65003 + neighbor 3.3.3.3 update-source lo1 + neighbor 3.3.3.3 ebgp-multihop 3 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 timers connect 5 + neighbor 3.3.3.3 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 3.3.3.3 activate diff --git a/tests/topotests/bgp_auth/R2/empty.conf b/tests/topotests/bgp_auth/R2/empty.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_auth/R2/ospfd.conf b/tests/topotests/bgp_auth/R2/ospfd.conf new file mode 100644 index 0000000..78e78d6 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/ospfd.conf @@ -0,0 +1,22 @@ +interface R2-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf + network 10.10.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 2.2.2.2/32 area 0 diff --git a/tests/topotests/bgp_auth/R2/ospfd_multi_vrf.conf b/tests/topotests/bgp_auth/R2/ospfd_multi_vrf.conf new file mode 100644 index 0000000..81eb5d6 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/ospfd_multi_vrf.conf @@ -0,0 +1,26 @@ +interface R2-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.10.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 2.2.2.2/32 area 0 +router ospf vrf red + network 10.10.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 2.2.2.2/32 area 0 diff --git a/tests/topotests/bgp_auth/R2/ospfd_vrf.conf b/tests/topotests/bgp_auth/R2/ospfd_vrf.conf new file mode 100644 index 0000000..673d103 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/ospfd_vrf.conf @@ -0,0 +1,22 @@ +interface R2-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R2-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.10.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 2.2.2.2/32 area 0 diff --git a/tests/topotests/bgp_auth/R2/zebra.conf b/tests/topotests/bgp_auth/R2/zebra.conf new file mode 100644 index 0000000..fed4c27 --- /dev/null +++ b/tests/topotests/bgp_auth/R2/zebra.conf @@ -0,0 +1,20 @@ +! +interface lo + ip address 2.2.2.2/32 +interface lo1 vrf blue + ip address 2.2.2.2/32 +interface lo2 vrf red + ip address 2.2.2.2/32 +interface R2-eth0 + ip address 10.10.0.2/24 +interface R2-eth1 + ip address 10.30.0.2/24 +interface R2-eth2 vrf blue + ip address 10.10.0.2/24 +interface R2-eth3 vrf blue + ip address 10.30.0.2/24 +interface R2-eth4 vrf red + ip address 10.10.0.2/24 +interface R2-eth5 vrf red + ip address 10.30.0.2/24 +! diff --git a/tests/topotests/bgp_auth/R3/bgpd.conf b/tests/topotests/bgp_auth/R3/bgpd.conf new file mode 100644 index 0000000..ca9b838 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65003 + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/bgpd_multi_vrf.conf b/tests/topotests/bgp_auth/R3/bgpd_multi_vrf.conf new file mode 100644 index 0000000..81d0299 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd_multi_vrf.conf @@ -0,0 +1,37 @@ +router bgp 65003 vrf blue + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password blue2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password blue3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate + +router bgp 65003 vrf red + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo2 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password red2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo2 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password red3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/bgpd_multi_vrf_prefix.conf b/tests/topotests/bgp_auth/R3/bgpd_multi_vrf_prefix.conf new file mode 100644 index 0000000..81d0299 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd_multi_vrf_prefix.conf @@ -0,0 +1,37 @@ +router bgp 65003 vrf blue + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password blue2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password blue3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate + +router bgp 65003 vrf red + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo2 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password red2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo2 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password red3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/bgpd_prefix.conf b/tests/topotests/bgp_auth/R3/bgpd_prefix.conf new file mode 100644 index 0000000..ca9b838 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65003 + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/bgpd_vrf.conf b/tests/topotests/bgp_auth/R3/bgpd_vrf.conf new file mode 100644 index 0000000..f8323e0 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd_vrf.conf @@ -0,0 +1,18 @@ +router bgp 65003 vrf blue + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/bgpd_vrf_prefix.conf b/tests/topotests/bgp_auth/R3/bgpd_vrf_prefix.conf new file mode 100644 index 0000000..f8323e0 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/bgpd_vrf_prefix.conf @@ -0,0 +1,18 @@ +router bgp 65003 vrf blue + timers bgp 3 9 + bgp router-id 3.3.3.3 + neighbor 1.1.1.1 remote-as 65001 + neighbor 1.1.1.1 update-source lo1 + neighbor 1.1.1.1 ebgp-multihop 3 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 timers connect 5 + neighbor 1.1.1.1 password hello2 + neighbor 2.2.2.2 remote-as 65002 + neighbor 2.2.2.2 update-source lo1 + neighbor 2.2.2.2 ebgp-multihop 3 + neighbor 2.2.2.2 timers connect 5 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 password hello3 + address-family ipv4 unicast + neighbor 1.1.1.1 activate + neighbor 2.2.2.2 activate diff --git a/tests/topotests/bgp_auth/R3/empty.conf b/tests/topotests/bgp_auth/R3/empty.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_auth/R3/ospfd.conf b/tests/topotests/bgp_auth/R3/ospfd.conf new file mode 100644 index 0000000..befeadb --- /dev/null +++ b/tests/topotests/bgp_auth/R3/ospfd.conf @@ -0,0 +1,22 @@ +interface R3-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf + network 10.20.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 3.3.3.3/32 area 0 diff --git a/tests/topotests/bgp_auth/R3/ospfd_multi_vrf.conf b/tests/topotests/bgp_auth/R3/ospfd_multi_vrf.conf new file mode 100644 index 0000000..2b2abc6 --- /dev/null +++ b/tests/topotests/bgp_auth/R3/ospfd_multi_vrf.conf @@ -0,0 +1,26 @@ +interface R3-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.20.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 3.3.3.3/32 area 0 +router ospf vrf red + network 10.20.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 3.3.3.3/32 area 0 diff --git a/tests/topotests/bgp_auth/R3/ospfd_vrf.conf b/tests/topotests/bgp_auth/R3/ospfd_vrf.conf new file mode 100644 index 0000000..392d17a --- /dev/null +++ b/tests/topotests/bgp_auth/R3/ospfd_vrf.conf @@ -0,0 +1,22 @@ +interface R3-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth2 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth3 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth4 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +interface R3-eth5 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +router ospf vrf blue + network 10.20.0.0/16 area 0 + network 10.30.0.0/16 area 0 + network 3.3.3.3/32 area 0 diff --git a/tests/topotests/bgp_auth/R3/zebra.conf b/tests/topotests/bgp_auth/R3/zebra.conf new file mode 100644 index 0000000..d49c98b --- /dev/null +++ b/tests/topotests/bgp_auth/R3/zebra.conf @@ -0,0 +1,20 @@ +! +interface lo + ip address 3.3.3.3/32 +interface lo1 vrf blue + ip address 3.3.3.3/32 +interface lo2 vrf red + ip address 3.3.3.3/32 +interface R3-eth0 + ip address 10.20.0.3/24 +interface R3-eth1 + ip address 10.30.0.3/24 +interface R3-eth2 vrf blue + ip address 10.20.0.3/24 +interface R3-eth3 vrf blue + ip address 10.30.0.3/24 +interface R3-eth4 vrf red + ip address 10.20.0.3/24 +interface R3-eth5 vrf red + ip address 10.30.0.3/24 +! diff --git a/tests/topotests/bgp_auth/bgp_auth_common.py b/tests/topotests/bgp_auth/bgp_auth_common.py new file mode 100644 index 0000000..824498e --- /dev/null +++ b/tests/topotests/bgp_auth/bgp_auth_common.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_auth.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_auth.py: Test BGP Md5 Authentication + + +------+ + +--------| |--------+ + | +------| R1 |------+ | + | | -----| |----+ | | + | | | +------+ | | | + | | | | | | + +------+ +------+ + | |------------| | + | R2 |------------| R3 | + | |------------| | + +------+ +------+ + + +setup is 3 routers with 3 links between each each link in a different vrf +Default, blue and red respectively +Tests check various fiddling with passwords and checking that the peer +establishment is as expected and passwords are not leaked across sockets +for bgp instances +""" +# pylint: disable=C0413 + +import json +import os +import platform +import sys +from time import sleep + +from lib import common_config, topotest +from lib.common_config import ( + save_initial_config_on_routers, + reset_with_new_configs, +) +from lib.topogen import Topogen, TopoRouter, get_topogen + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def vrf_str(vrf): + if vrf == "": + vrf_str = "" + else: + vrf_str = "vrf {}".format(vrf) + + return vrf_str + + +def peer_name(rtr, prefix, vrf): + "generate VRF string for CLI" + if vrf == "": + vrf_str = "" + else: + vrf_str = "_" + vrf + + if prefix == "yes": + if rtr == "R2": + return "TWO_GROUP" + vrf_str + else: + return "THREE_GROUP" + vrf_str + else: + if rtr == "R2": + return "2.2.2.2" + else: + return "3.3.3.3" + + +def print_diag(vrf): + "print failure disagnostics" + + tgen = get_topogen() + router_list = tgen.routers() + for rname, router in router_list.items(): + print(rname + ":") + print(router.vtysh_cmd("show run")) + print(router.vtysh_cmd("show ip route {}".format(vrf_str(vrf)))) + print(router.vtysh_cmd("show bgp {} neighbor".format(vrf_str(vrf)))) + + +@common_config.retry(retry_timeout=190) +def _check_neigh_state(router, peer, state, vrf=""): + "check BGP neighbor state on a router" + + neigh_output = router.vtysh_cmd( + "show bgp {} neighbors {} json".format(vrf_str(vrf), peer) + ) + + peer_state = "Unknown" + neigh_output_json = json.loads(neigh_output) + if peer in neigh_output_json: + peer_state = neigh_output_json[peer]["bgpState"] + if peer_state == state: + return True + return "{} peer with {} expected state {} got {} ".format( + router.name, peer, state, peer_state + ) + + +def check_neigh_state(router, peer, state, vrf=""): + "check BGP neighbor state on a router" + + assertmsg = _check_neigh_state(router, peer, state, vrf) + assert assertmsg is True, assertmsg + + +def check_all_peers_established(vrf=""): + "standard check for extablished peers per vrf" + + tgen = get_topogen() + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + # do r1 last as he might be the dynamic one + check_neigh_state(r2, "1.1.1.1", "Established", vrf) + check_neigh_state(r2, "3.3.3.3", "Established", vrf) + check_neigh_state(r3, "1.1.1.1", "Established", vrf) + check_neigh_state(r3, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "3.3.3.3", "Established", vrf) + + +def check_vrf_peer_remove_passwords(vrf="", prefix="no"): + "selectively remove passwords checking state" + + tgen = get_topogen() + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + + check_all_peers_established(vrf) + + r1.vtysh_cmd( + "conf t\nrouter bgp 65001 {}\nno neighbor {} password".format( + vrf_str(vrf), peer_name("R2", prefix, vrf) + ) + ) + + check_neigh_state(r2, "1.1.1.1", "Connect", vrf) + check_neigh_state(r2, "3.3.3.3", "Established", vrf) + check_neigh_state(r3, "1.1.1.1", "Established", vrf) + check_neigh_state(r3, "2.2.2.2", "Established", vrf) + # don't check dynamic downed peers - they are removed + if prefix == "no": + check_neigh_state(r1, "2.2.2.2", "Connect", vrf) + check_neigh_state(r1, "3.3.3.3", "Established", vrf) + + r2.vtysh_cmd( + "conf t\nrouter bgp 65002 {}\nno neighbor 1.1.1.1 password".format(vrf_str(vrf)) + ) + check_all_peers_established(vrf) + + r1.vtysh_cmd( + "conf t\nrouter bgp 65001 {}\nno neighbor {} password".format( + vrf_str(vrf), peer_name("R3", prefix, vrf) + ) + ) + check_neigh_state(r2, "1.1.1.1", "Established", vrf) + check_neigh_state(r2, "3.3.3.3", "Established", vrf) + check_neigh_state(r3, "1.1.1.1", "Connect", vrf) + check_neigh_state(r3, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "2.2.2.2", "Established", vrf) + # don't check dynamic downed peers - they are removed + if prefix == "no": + check_neigh_state(r1, "3.3.3.3", "Connect", vrf) + + r3.vtysh_cmd( + "conf t\nrouter bgp 65003 {}\nno neighbor 1.1.1.1 password".format(vrf_str(vrf)) + ) + check_all_peers_established(vrf) + + r2.vtysh_cmd( + "conf t\nrouter bgp 65002 {}\nno neighbor 3.3.3.3 password".format(vrf_str(vrf)) + ) + check_neigh_state(r2, "1.1.1.1", "Established", vrf) + check_neigh_state(r2, "3.3.3.3", "Connect", vrf) + check_neigh_state(r3, "1.1.1.1", "Established", vrf) + check_neigh_state(r3, "2.2.2.2", "Connect", vrf) + check_neigh_state(r1, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "3.3.3.3", "Established", vrf) + + r3.vtysh_cmd( + "conf t\nrouter bgp 65003 {}\nno neighbor 2.2.2.2 password".format(vrf_str(vrf)) + ) + check_all_peers_established(vrf) + + +def check_vrf_peer_change_passwords(vrf="", prefix="no"): + "selectively change passwords checking state" + + tgen = get_topogen() + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + check_all_peers_established(vrf) + + r1.vtysh_cmd( + "conf t\nrouter bgp 65001 {}\nneighbor {} password change1".format( + vrf_str(vrf), peer_name("R2", prefix, vrf) + ) + ) + check_neigh_state(r2, "1.1.1.1", "Connect", vrf) + check_neigh_state(r2, "3.3.3.3", "Established", vrf) + check_neigh_state(r3, "1.1.1.1", "Established", vrf) + check_neigh_state(r3, "2.2.2.2", "Established", vrf) + # don't check dynamic downed peers - they are removed + if prefix == "no": + check_neigh_state(r1, "2.2.2.2", "Connect", vrf) + check_neigh_state(r1, "3.3.3.3", "Established", vrf) + + r2.vtysh_cmd( + "conf t\nrouter bgp 65002 {}\nneighbor 1.1.1.1 password change1".format( + vrf_str(vrf) + ) + ) + check_all_peers_established(vrf) + + r1.vtysh_cmd( + "conf t\nrouter bgp 65001 {}\nneighbor {} password change2".format( + vrf_str(vrf), peer_name("R3", prefix, vrf) + ) + ) + check_neigh_state(r2, "1.1.1.1", "Established", vrf) + check_neigh_state(r2, "3.3.3.3", "Established", vrf) + check_neigh_state(r3, "1.1.1.1", "Connect", vrf) + check_neigh_state(r3, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "2.2.2.2", "Established", vrf) + # don't check dynamic downed peers - they are removed + if prefix == "no": + check_neigh_state(r1, "3.3.3.3", "Connect", vrf) + + r3.vtysh_cmd( + "conf t\nrouter bgp 65003 {}\nneighbor 1.1.1.1 password change2".format( + vrf_str(vrf) + ) + ) + check_all_peers_established(vrf) + + r2.vtysh_cmd( + "conf t\nrouter bgp 65002 {}\nneighbor 3.3.3.3 password change3".format( + vrf_str(vrf) + ) + ) + check_neigh_state(r2, "1.1.1.1", "Established", vrf) + check_neigh_state(r2, "3.3.3.3", "Connect", vrf) + check_neigh_state(r3, "1.1.1.1", "Established", vrf) + check_neigh_state(r3, "2.2.2.2", "Connect", vrf) + check_neigh_state(r1, "2.2.2.2", "Established", vrf) + check_neigh_state(r1, "3.3.3.3", "Established", vrf) + + r3.vtysh_cmd( + "conf t\nrouter bgp 65003 {}\nneighbor 2.2.2.2 password change3".format( + vrf_str(vrf) + ) + ) + check_all_peers_established(vrf) diff --git a/tests/topotests/bgp_auth/test_bgp_auth1.py b/tests/topotests/bgp_auth/test_bgp_auth1.py new file mode 100644 index 0000000..9d47106 --- /dev/null +++ b/tests/topotests/bgp_auth/test_bgp_auth1.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_auth.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_auth.py: Test BGP Md5 Authentication + + +------+ + +--------| |--------+ + | +------| R1 |------+ | + | | -----| |----+ | | + | | | +------+ | | | + | | | | | | + +------+ +------+ + | |------------| | + | R2 |------------| R3 | + | |------------| | + +------+ +------+ + + +setup is 3 routers with 3 links between each each link in a different vrf +Default, blue and red respectively +Tests check various fiddling with passwords and checking that the peer +establishment is as expected and passwords are not leaked across sockets +for bgp instances +""" +# pylint: disable=C0413 + +import json +import os +import platform +import sys +from time import sleep + +import pytest +from lib import common_config, topotest +from lib.common_config import ( + save_initial_config_on_routers, + reset_with_new_configs, +) + +from bgp_auth_common import ( + check_all_peers_established, + check_vrf_peer_remove_passwords, + check_vrf_peer_change_passwords, +) +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def build_topo(tgen): + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + + # blue vrf + r1.cmd_raises("ip link add blue type vrf table 1001") + r1.cmd_raises("ip link set up dev blue") + r2.cmd_raises("ip link add blue type vrf table 1001") + r2.cmd_raises("ip link set up dev blue") + r3.cmd_raises("ip link add blue type vrf table 1001") + r3.cmd_raises("ip link set up dev blue") + + r1.cmd_raises("ip link add lo1 type dummy") + r1.cmd_raises("ip link set lo1 master blue") + r1.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link add lo1 type dummy") + r2.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link set lo1 master blue") + r3.cmd_raises("ip link add lo1 type dummy") + r3.cmd_raises("ip link set up dev lo1") + r3.cmd_raises("ip link set lo1 master blue") + + r1.cmd_raises("ip link set R1-eth2 master blue") + r1.cmd_raises("ip link set R1-eth3 master blue") + r2.cmd_raises("ip link set R2-eth2 master blue") + r2.cmd_raises("ip link set R2-eth3 master blue") + r3.cmd_raises("ip link set R3-eth2 master blue") + r3.cmd_raises("ip link set R3-eth3 master blue") + + r1.cmd_raises("ip link set up dev R1-eth2") + r1.cmd_raises("ip link set up dev R1-eth3") + r2.cmd_raises("ip link set up dev R2-eth2") + r2.cmd_raises("ip link set up dev R2-eth3") + r3.cmd_raises("ip link set up dev R3-eth2") + r3.cmd_raises("ip link set up dev R3-eth3") + + # red vrf + r1.cmd_raises("ip link add red type vrf table 1002") + r1.cmd_raises("ip link set up dev red") + r2.cmd_raises("ip link add red type vrf table 1002") + r2.cmd_raises("ip link set up dev red") + r3.cmd_raises("ip link add red type vrf table 1002") + r3.cmd_raises("ip link set up dev red") + + r1.cmd_raises("ip link add lo2 type dummy") + r1.cmd_raises("ip link set lo2 master red") + r1.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link add lo2 type dummy") + r2.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link set lo2 master red") + r3.cmd_raises("ip link add lo2 type dummy") + r3.cmd_raises("ip link set up dev lo2") + r3.cmd_raises("ip link set lo2 master red") + + r1.cmd_raises("ip link set R1-eth4 master red") + r1.cmd_raises("ip link set R1-eth5 master red") + r2.cmd_raises("ip link set R2-eth4 master red") + r2.cmd_raises("ip link set R2-eth5 master red") + r3.cmd_raises("ip link set R3-eth4 master red") + r3.cmd_raises("ip link set R3-eth5 master red") + + r1.cmd_raises("ip link set up dev R1-eth4") + r1.cmd_raises("ip link set up dev R1-eth5") + r2.cmd_raises("ip link set up dev R2-eth4") + r2.cmd_raises("ip link set up dev R2-eth5") + r3.cmd_raises("ip link set up dev R3-eth4") + r3.cmd_raises("ip link set up dev R3-eth5") + + r1.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r2.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r3.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF, "") + router.load_config(TopoRouter.RD_BGP, "") + + # After copying the configurations, this function loads configured daemons. + tgen.start_router() + + # Save the initial router config. reset_config_on_routers will return to this config. + save_initial_config_on_routers(tgen) + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_default_peer_established(tgen): + "default vrf 3 peers same password" + + reset_with_new_configs(tgen, "bgpd.conf", "ospfd.conf") + check_all_peers_established() + + +def test_default_peer_remove_passwords(tgen): + "selectively remove passwords checking state" + + reset_with_new_configs(tgen, "bgpd.conf", "ospfd.conf") + check_vrf_peer_remove_passwords() + + +def test_default_peer_change_passwords(tgen): + "selectively change passwords checking state" + + reset_with_new_configs(tgen, "bgpd.conf", "ospfd.conf") + check_vrf_peer_change_passwords() + + +def test_default_prefix_peer_established(tgen): + "default vrf 3 peers same password with prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_prefix.conf", "ospfd.conf") + check_all_peers_established() + + +def test_prefix_peer_remove_passwords(tgen): + "selectively remove passwords checking state with prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_prefix.conf", "ospfd.conf") + check_vrf_peer_remove_passwords(prefix="yes") + + +def test_memory_leak(tgen): + "Run the memory leak test and report results." + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_auth/test_bgp_auth2.py b/tests/topotests/bgp_auth/test_bgp_auth2.py new file mode 100644 index 0000000..6b92036 --- /dev/null +++ b/tests/topotests/bgp_auth/test_bgp_auth2.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_auth.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_auth.py: Test BGP Md5 Authentication + + +------+ + +--------| |--------+ + | +------| R1 |------+ | + | | -----| |----+ | | + | | | +------+ | | | + | | | | | | + +------+ +------+ + | |------------| | + | R2 |------------| R3 | + | |------------| | + +------+ +------+ + + +setup is 3 routers with 3 links between each each link in a different vrf +Default, blue and red respectively +Tests check various fiddling with passwords and checking that the peer +establishment is as expected and passwords are not leaked across sockets +for bgp instances +""" +# pylint: disable=C0413 + +import json +import os +import platform +import sys +from time import sleep + +import pytest +from lib import common_config, topotest +from lib.common_config import ( + save_initial_config_on_routers, + reset_with_new_configs, +) +from bgp_auth_common import ( + check_all_peers_established, + check_vrf_peer_remove_passwords, + check_vrf_peer_change_passwords, + check_all_peers_established, +) +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def build_topo(tgen): + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + + # blue vrf + r1.cmd_raises("ip link add blue type vrf table 1001") + r1.cmd_raises("ip link set up dev blue") + r2.cmd_raises("ip link add blue type vrf table 1001") + r2.cmd_raises("ip link set up dev blue") + r3.cmd_raises("ip link add blue type vrf table 1001") + r3.cmd_raises("ip link set up dev blue") + + r1.cmd_raises("ip link add lo1 type dummy") + r1.cmd_raises("ip link set lo1 master blue") + r1.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link add lo1 type dummy") + r2.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link set lo1 master blue") + r3.cmd_raises("ip link add lo1 type dummy") + r3.cmd_raises("ip link set up dev lo1") + r3.cmd_raises("ip link set lo1 master blue") + + r1.cmd_raises("ip link set R1-eth2 master blue") + r1.cmd_raises("ip link set R1-eth3 master blue") + r2.cmd_raises("ip link set R2-eth2 master blue") + r2.cmd_raises("ip link set R2-eth3 master blue") + r3.cmd_raises("ip link set R3-eth2 master blue") + r3.cmd_raises("ip link set R3-eth3 master blue") + + r1.cmd_raises("ip link set up dev R1-eth2") + r1.cmd_raises("ip link set up dev R1-eth3") + r2.cmd_raises("ip link set up dev R2-eth2") + r2.cmd_raises("ip link set up dev R2-eth3") + r3.cmd_raises("ip link set up dev R3-eth2") + r3.cmd_raises("ip link set up dev R3-eth3") + + # red vrf + r1.cmd_raises("ip link add red type vrf table 1002") + r1.cmd_raises("ip link set up dev red") + r2.cmd_raises("ip link add red type vrf table 1002") + r2.cmd_raises("ip link set up dev red") + r3.cmd_raises("ip link add red type vrf table 1002") + r3.cmd_raises("ip link set up dev red") + + r1.cmd_raises("ip link add lo2 type dummy") + r1.cmd_raises("ip link set lo2 master red") + r1.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link add lo2 type dummy") + r2.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link set lo2 master red") + r3.cmd_raises("ip link add lo2 type dummy") + r3.cmd_raises("ip link set up dev lo2") + r3.cmd_raises("ip link set lo2 master red") + + r1.cmd_raises("ip link set R1-eth4 master red") + r1.cmd_raises("ip link set R1-eth5 master red") + r2.cmd_raises("ip link set R2-eth4 master red") + r2.cmd_raises("ip link set R2-eth5 master red") + r3.cmd_raises("ip link set R3-eth4 master red") + r3.cmd_raises("ip link set R3-eth5 master red") + + r1.cmd_raises("ip link set up dev R1-eth4") + r1.cmd_raises("ip link set up dev R1-eth5") + r2.cmd_raises("ip link set up dev R2-eth4") + r2.cmd_raises("ip link set up dev R2-eth5") + r3.cmd_raises("ip link set up dev R3-eth4") + r3.cmd_raises("ip link set up dev R3-eth5") + + r1.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r2.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r3.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF, "") + router.load_config(TopoRouter.RD_BGP, "") + + # After copying the configurations, this function loads configured daemons. + tgen.start_router() + + # Save the initial router config. reset_config_on_routers will return to this config. + save_initial_config_on_routers(tgen) + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_vrf_prefix_peer_established(tgen): + "default vrf 3 peers same password with VRF prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_vrf_prefix.conf", "ospfd_vrf.conf") + check_all_peers_established("blue") + + +def test_vrf_prefix_peer_remove_passwords(tgen): + "selectively remove passwords checking state with VRF prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_vrf_prefix.conf", "ospfd_vrf.conf") + check_vrf_peer_remove_passwords(vrf="blue", prefix="yes") + + +def test_vrf_prefix_peer_change_passwords(tgen): + "selectively change passwords checking state with VRF prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_vrf_prefix.conf", "ospfd_vrf.conf") + check_vrf_peer_change_passwords(vrf="blue", prefix="yes") + + +def test_multiple_vrf_peer_established(tgen): + "default vrf 3 peers same password with multiple VRFs" + + reset_with_new_configs(tgen, "bgpd_multi_vrf.conf", "ospfd_multi_vrf.conf") + check_all_peers_established("blue") + check_all_peers_established("red") + + +def test_multiple_vrf_peer_remove_passwords(tgen): + "selectively remove passwords checking state with multiple VRFs" + + reset_with_new_configs(tgen, "bgpd_multi_vrf.conf", "ospfd_multi_vrf.conf") + check_vrf_peer_remove_passwords("blue") + check_all_peers_established("red") + check_vrf_peer_remove_passwords("red") + check_all_peers_established("blue") + + +def test_memory_leak(tgen): + "Run the memory leak test and report results." + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_auth/test_bgp_auth3.py b/tests/topotests/bgp_auth/test_bgp_auth3.py new file mode 100644 index 0000000..2237c6b --- /dev/null +++ b/tests/topotests/bgp_auth/test_bgp_auth3.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_auth.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_auth.py: Test BGP Md5 Authentication + + +------+ + +--------| |--------+ + | +------| R1 |------+ | + | | -----| |----+ | | + | | | +------+ | | | + | | | | | | + +------+ +------+ + | |------------| | + | R2 |------------| R3 | + | |------------| | + +------+ +------+ + + +setup is 3 routers with 3 links between each each link in a different vrf +Default, blue and red respectively +Tests check various fiddling with passwords and checking that the peer +establishment is as expected and passwords are not leaked across sockets +for bgp instances +""" +# pylint: disable=C0413 + +import json +import os +import platform +import sys +from time import sleep + +import pytest +from lib import common_config, topotest +from lib.common_config import ( + save_initial_config_on_routers, + reset_with_new_configs, +) +from bgp_auth_common import ( + check_vrf_peer_change_passwords, + check_all_peers_established, + check_vrf_peer_remove_passwords, +) +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def build_topo(tgen): + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + + # blue vrf + r1.cmd_raises("ip link add blue type vrf table 1001") + r1.cmd_raises("ip link set up dev blue") + r2.cmd_raises("ip link add blue type vrf table 1001") + r2.cmd_raises("ip link set up dev blue") + r3.cmd_raises("ip link add blue type vrf table 1001") + r3.cmd_raises("ip link set up dev blue") + + r1.cmd_raises("ip link add lo1 type dummy") + r1.cmd_raises("ip link set lo1 master blue") + r1.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link add lo1 type dummy") + r2.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link set lo1 master blue") + r3.cmd_raises("ip link add lo1 type dummy") + r3.cmd_raises("ip link set up dev lo1") + r3.cmd_raises("ip link set lo1 master blue") + + r1.cmd_raises("ip link set R1-eth2 master blue") + r1.cmd_raises("ip link set R1-eth3 master blue") + r2.cmd_raises("ip link set R2-eth2 master blue") + r2.cmd_raises("ip link set R2-eth3 master blue") + r3.cmd_raises("ip link set R3-eth2 master blue") + r3.cmd_raises("ip link set R3-eth3 master blue") + + r1.cmd_raises("ip link set up dev R1-eth2") + r1.cmd_raises("ip link set up dev R1-eth3") + r2.cmd_raises("ip link set up dev R2-eth2") + r2.cmd_raises("ip link set up dev R2-eth3") + r3.cmd_raises("ip link set up dev R3-eth2") + r3.cmd_raises("ip link set up dev R3-eth3") + + # red vrf + r1.cmd_raises("ip link add red type vrf table 1002") + r1.cmd_raises("ip link set up dev red") + r2.cmd_raises("ip link add red type vrf table 1002") + r2.cmd_raises("ip link set up dev red") + r3.cmd_raises("ip link add red type vrf table 1002") + r3.cmd_raises("ip link set up dev red") + + r1.cmd_raises("ip link add lo2 type dummy") + r1.cmd_raises("ip link set lo2 master red") + r1.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link add lo2 type dummy") + r2.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link set lo2 master red") + r3.cmd_raises("ip link add lo2 type dummy") + r3.cmd_raises("ip link set up dev lo2") + r3.cmd_raises("ip link set lo2 master red") + + r1.cmd_raises("ip link set R1-eth4 master red") + r1.cmd_raises("ip link set R1-eth5 master red") + r2.cmd_raises("ip link set R2-eth4 master red") + r2.cmd_raises("ip link set R2-eth5 master red") + r3.cmd_raises("ip link set R3-eth4 master red") + r3.cmd_raises("ip link set R3-eth5 master red") + + r1.cmd_raises("ip link set up dev R1-eth4") + r1.cmd_raises("ip link set up dev R1-eth5") + r2.cmd_raises("ip link set up dev R2-eth4") + r2.cmd_raises("ip link set up dev R2-eth5") + r3.cmd_raises("ip link set up dev R3-eth4") + r3.cmd_raises("ip link set up dev R3-eth5") + + r1.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r2.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r3.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF, "") + router.load_config(TopoRouter.RD_BGP, "") + + # After copying the configurations, this function loads configured daemons. + tgen.start_router() + + # Save the initial router config. reset_config_on_routers will return to this config. + save_initial_config_on_routers(tgen) + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_prefix_peer_change_passwords(tgen): + "selecively change passwords checkig state with prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_prefix.conf", "ospfd.conf") + check_vrf_peer_change_passwords(prefix="yes") + + +def test_vrf_peer_established(tgen): + "default vrf 3 peers same password with VRF config" + + # clean routers and load vrf config + reset_with_new_configs(tgen, "bgpd_vrf.conf", "ospfd_vrf.conf") + check_all_peers_established("blue") + + +def test_vrf_peer_remove_passwords(tgen): + "selectively remove passwords checking state with VRF config" + + reset_with_new_configs(tgen, "bgpd_vrf.conf", "ospfd_vrf.conf") + check_vrf_peer_remove_passwords(vrf="blue") + + +def test_vrf_peer_change_passwords(tgen): + "selectively change passwords checking state with VRF config" + + reset_with_new_configs(tgen, "bgpd_vrf.conf", "ospfd_vrf.conf") + check_vrf_peer_change_passwords(vrf="blue") + + +def test_memory_leak(tgen): + "Run the memory leak test and report results." + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_auth/test_bgp_auth4.py b/tests/topotests/bgp_auth/test_bgp_auth4.py new file mode 100644 index 0000000..d6fe425 --- /dev/null +++ b/tests/topotests/bgp_auth/test_bgp_auth4.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_auth.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_auth.py: Test BGP Md5 Authentication + + +------+ + +--------| |--------+ + | +------| R1 |------+ | + | | -----| |----+ | | + | | | +------+ | | | + | | | | | | + +------+ +------+ + | |------------| | + | R2 |------------| R3 | + | |------------| | + +------+ +------+ + + +setup is 3 routers with 3 links between each each link in a different vrf +Default, blue and red respectively +Tests check various fiddling with passwords and checking that the peer +establishment is as expected and passwords are not leaked across sockets +for bgp instances +""" +# pylint: disable=C0413 + +import json +import os +import platform +import sys +from time import sleep + +import pytest +from lib import common_config, topotest +from lib.common_config import ( + save_initial_config_on_routers, + reset_with_new_configs, +) +from bgp_auth_common import ( + check_vrf_peer_change_passwords, + check_all_peers_established, + check_vrf_peer_remove_passwords, +) +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def build_topo(tgen): + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R2"]) + tgen.add_link(tgen.gears["R1"], tgen.gears["R3"]) + tgen.add_link(tgen.gears["R2"], tgen.gears["R3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + r3 = tgen.gears["R3"] + + # blue vrf + r1.cmd_raises("ip link add blue type vrf table 1001") + r1.cmd_raises("ip link set up dev blue") + r2.cmd_raises("ip link add blue type vrf table 1001") + r2.cmd_raises("ip link set up dev blue") + r3.cmd_raises("ip link add blue type vrf table 1001") + r3.cmd_raises("ip link set up dev blue") + + r1.cmd_raises("ip link add lo1 type dummy") + r1.cmd_raises("ip link set lo1 master blue") + r1.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link add lo1 type dummy") + r2.cmd_raises("ip link set up dev lo1") + r2.cmd_raises("ip link set lo1 master blue") + r3.cmd_raises("ip link add lo1 type dummy") + r3.cmd_raises("ip link set up dev lo1") + r3.cmd_raises("ip link set lo1 master blue") + + r1.cmd_raises("ip link set R1-eth2 master blue") + r1.cmd_raises("ip link set R1-eth3 master blue") + r2.cmd_raises("ip link set R2-eth2 master blue") + r2.cmd_raises("ip link set R2-eth3 master blue") + r3.cmd_raises("ip link set R3-eth2 master blue") + r3.cmd_raises("ip link set R3-eth3 master blue") + + r1.cmd_raises("ip link set up dev R1-eth2") + r1.cmd_raises("ip link set up dev R1-eth3") + r2.cmd_raises("ip link set up dev R2-eth2") + r2.cmd_raises("ip link set up dev R2-eth3") + r3.cmd_raises("ip link set up dev R3-eth2") + r3.cmd_raises("ip link set up dev R3-eth3") + + # red vrf + r1.cmd_raises("ip link add red type vrf table 1002") + r1.cmd_raises("ip link set up dev red") + r2.cmd_raises("ip link add red type vrf table 1002") + r2.cmd_raises("ip link set up dev red") + r3.cmd_raises("ip link add red type vrf table 1002") + r3.cmd_raises("ip link set up dev red") + + r1.cmd_raises("ip link add lo2 type dummy") + r1.cmd_raises("ip link set lo2 master red") + r1.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link add lo2 type dummy") + r2.cmd_raises("ip link set up dev lo2") + r2.cmd_raises("ip link set lo2 master red") + r3.cmd_raises("ip link add lo2 type dummy") + r3.cmd_raises("ip link set up dev lo2") + r3.cmd_raises("ip link set lo2 master red") + + r1.cmd_raises("ip link set R1-eth4 master red") + r1.cmd_raises("ip link set R1-eth5 master red") + r2.cmd_raises("ip link set R2-eth4 master red") + r2.cmd_raises("ip link set R2-eth5 master red") + r3.cmd_raises("ip link set R3-eth4 master red") + r3.cmd_raises("ip link set R3-eth5 master red") + + r1.cmd_raises("ip link set up dev R1-eth4") + r1.cmd_raises("ip link set up dev R1-eth5") + r2.cmd_raises("ip link set up dev R2-eth4") + r2.cmd_raises("ip link set up dev R2-eth5") + r3.cmd_raises("ip link set up dev R3-eth4") + r3.cmd_raises("ip link set up dev R3-eth5") + + r1.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r2.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + r3.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept=1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_OSPF, "") + router.load_config(TopoRouter.RD_BGP, "") + + # After copying the configurations, this function loads configured daemons. + tgen.start_router() + + # Save the initial router config. reset_config_on_routers will return to this config. + save_initial_config_on_routers(tgen) + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_multiple_vrf_peer_change_passwords(tgen): + "selectively change passwords checking state with multiple VRFs" + + reset_with_new_configs(tgen, "bgpd_multi_vrf.conf", "ospfd_multi_vrf.conf") + check_vrf_peer_change_passwords("blue") + check_all_peers_established("red") + check_vrf_peer_change_passwords("red") + check_all_peers_established("blue") + + +def test_multiple_vrf_prefix_peer_established(tgen): + "default vrf 3 peers same password with multilpe VRFs and prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_multi_vrf_prefix.conf", "ospfd_multi_vrf.conf") + check_all_peers_established("blue") + check_all_peers_established("red") + + +def test_multiple_vrf_prefix_peer_remove_passwords(tgen): + "selectively remove passwords checking state with multiple vrfs and prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_multi_vrf_prefix.conf", "ospfd_multi_vrf.conf") + check_vrf_peer_remove_passwords(vrf="blue", prefix="yes") + check_all_peers_established("red") + check_vrf_peer_remove_passwords(vrf="red", prefix="yes") + check_all_peers_established("blue") + + +def test_multiple_vrf_prefix_peer_change_passwords(tgen): + "selectively change passwords checking state with multiple vrfs and prefix config" + + # only supported in kernel > 5.3 + if topotest.version_cmp(platform.release(), "5.3") < 0: + return + + reset_with_new_configs(tgen, "bgpd_multi_vrf_prefix.conf", "ospfd_multi_vrf.conf") + check_vrf_peer_change_passwords(vrf="blue", prefix="yes") + check_all_peers_established("red") + check_vrf_peer_change_passwords(vrf="red", prefix="yes") + check_all_peers_established("blue") + + +def test_memory_leak(tgen): + "Run the memory leak test and report results." + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_basic_functionality_topo1/__init__.py b/tests/topotests/bgp_basic_functionality_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_basic_functionality_topo1/bgp_basic_functionality.json b/tests/topotests/bgp_basic_functionality_topo1/bgp_basic_functionality.json new file mode 100644 index 0000000..ee1f1b7 --- /dev/null +++ b/tests/topotests/bgp_basic_functionality_topo1/bgp_basic_functionality.json @@ -0,0 +1,115 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {}}}, + "r3": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {}}}, + "r3": {"dest_link": {"r1": {}}} + } + } + } + } + } + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {"r3": {"dest_link": {"r4": {}}}}} + }, + "ipv6": { + "unicast": {"neighbor": {"r3": {"dest_link": {"r4": {}}}}} + } + } + } + } + } +} diff --git a/tests/topotests/bgp_basic_functionality_topo1/test_bgp_basic_functionality.py b/tests/topotests/bgp_basic_functionality_topo1/test_bgp_basic_functionality.py new file mode 100644 index 0000000..c97fc5f --- /dev/null +++ b/tests/topotests/bgp_basic_functionality_topo1/test_bgp_basic_functionality.py @@ -0,0 +1,1169 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP basic functionality: + +Test steps +- Create topology (setup module) + Creating 4 routers topology, r1, r2, r3 are in IBGP and + r3, r4 are in EBGP +- Bring up topology +- Verify for bgp to converge +- Modify/Delete and verify router-id +- Modify and verify bgp timers +- Create and verify static routes +- Modify and verify admin distance for existing static routes +- Test advertise network using network command +- Verify clear bgp +- Test bgp convergence with loopback interface +- Test advertise network using network command +- Verify routes not installed in zebra when /32 routes received + with loopback BGP session subnet +""" +# XXX clean up in later commit to avoid conflict on rebase +# pylint: disable=C0413 + +import os +import sys +import time +import pytest +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +from lib.bgp import ( + clear_bgp_and_verify, + create_router_bgp, + modify_as_number, + verify_as_numbers, + verify_bgp_convergence, + verify_bgp_rib, + verify_bgp_timers_and_functionality, + verify_router_id, +) +from lib.common_config import ( + addKernelRoute, + apply_raw_config, + check_address_types, + create_prefix_lists, + create_route_maps, + create_static_routes, + required_linux_kernel_version, + reset_config_on_routers, + start_topology, + step, + verify_admin_distance_for_static_routes, + verify_bgp_community, + verify_fib_routes, + verify_rib, + write_test_footer, + write_test_header, +) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global Variable +KEEPALIVETIMER = 2 +HOLDDOWNTIMER = 6 +r1_ipv4_loopback = "1.0.1.0/24" +r2_ipv4_loopback = "1.0.2.0/24" +r3_ipv4_loopback = "1.0.3.0/24" +r4_ipv4_loopback = "1.0.4.0/24" +r1_ipv6_loopback = "2001:db8:f::1:0/120" +r2_ipv6_loopback = "2001:db8:f::2:0/120" +r3_ipv6_loopback = "2001:db8:f::3:0/120" +r4_ipv6_loopback = "2001:db8:f::4:0/120" +NETWORK = { + "ipv4": ["100.1.1.1/32", "100.1.1.2/32"], + "ipv6": ["100::1/128", "100::2/128"], +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.15") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_basic_functionality.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_modify_and_delete_router_id(request): + """Test to modify, delete and verify router-id.""" + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Modify router id + input_dict = { + "r1": {"bgp": {"router_id": "12.12.12.12"}}, + "r2": {"bgp": {"router_id": "22.22.22.22"}}, + "r3": {"bgp": {"router_id": "33.33.33.33"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Verifying router id once modified + result = verify_router_id(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Delete router id + input_dict = { + "r1": {"bgp": {"del_router_id": True}}, + "r2": {"bgp": {"del_router_id": True}}, + "r3": {"bgp": {"del_router_id": True}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Verifying router id once deleted + # Once router-id is deleted, highest interface ip should become + # router-id + result = verify_router_id(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_config_with_4byte_as_number(request): + """ + Configure BGP with 4 byte ASN and verify it works fine + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + input_dict = { + "r1": {"bgp": {"local_as": 131079}}, + "r2": {"bgp": {"local_as": 131079}}, + "r3": {"bgp": {"local_as": 131079}}, + "r4": {"bgp": {"local_as": 131080}}, + } + result = modify_as_number(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_as_numbers(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_config_with_invalid_ASN_p2(request): + """ + Configure BGP with invalid ASN(ex - 0, reserved ASN) and verify test case + ended up with error + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to modify AS number + input_dict = { + "r1": { + "bgp": { + "local_as": 0, + } + }, + "r2": { + "bgp": { + "local_as": 0, + } + }, + "r3": { + "bgp": { + "local_as": 0, + } + }, + "r4": { + "bgp": { + "local_as": 64000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + assert ( + result is not True + ), "Expected BGP config is not created because of invalid ASNs: {}".format(result) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + result = verify_bgp_convergence(tgen, topo) + if result != True: + assert False, "Testcase " + tc_name + " :Failed \n Error: {}".format(result) + + write_test_footer(tc_name) + + +def test_BGP_config_with_2byteAS_and_4byteAS_number_p1(request): + """ + Configure BGP with 4 byte and 2 byte ASN and verify BGP is converged + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + result = verify_bgp_convergence(tgen, topo) + if result != True: + assert False, "Testcase " + tc_name + " :Failed \n Error: {}".format(result) + + # Api call to modify AS number + input_dict = { + "r1": {"bgp": {"local_as": 131079}}, + "r2": {"bgp": {"local_as": 131079}}, + "r3": {"bgp": {"local_as": 131079}}, + "r4": {"bgp": {"local_as": 111}}, + } + result = modify_as_number(tgen, topo, input_dict) + if result != True: + assert False, "Testcase " + tc_name + " :Failed \n Error: {}".format(result) + + result = verify_as_numbers(tgen, topo, input_dict) + if result != True: + assert False, "Testcase " + tc_name + " :Failed \n Error: {}".format(result) + + # Api call verify whether BGP is converged + result = verify_bgp_convergence(tgen, topo) + if result != True: + assert False, "Testcase " + tc_name + " :Failed \n Error: {}".format(result) + + write_test_footer(tc_name) + + +def test_bgp_timers_functionality(request): + """ + Test to modify bgp timers and verify timers functionality. + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to modify BGP timerse + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, deepcopy(input_dict)) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so timer modification would take place + clear_bgp_and_verify(tgen, topo, "r1") + + # Verifying bgp timers functionality + result = verify_bgp_timers_and_functionality(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_static_routes(request): + """Test to create and verify static routes.""" + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to create static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": "10.0.20.1/32", + "no_of_ip": 9, + "admin_distance": 100, + "next_hop": "10.0.0.2", + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + next_hop = ["10.0.0.2", "10.0.0.5"] + result = verify_rib( + tgen, "ipv4", dut, input_dict, next_hop=next_hop, protocol=protocol + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_admin_distance_for_existing_static_routes(request): + """Test to modify and verify admin distance for existing static routes.""" + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + input_dict = { + "r1": { + "static_routes": [ + { + "network": "10.0.20.1/32", + "admin_distance": 10, + "next_hop": "10.0.0.2", + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Verifying admin distance once modified + result = verify_admin_distance_for_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_advertise_network_using_network_command(request): + """Test advertise networks using network command.""" + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "20.0.0.0/32", "no_of_network": 10}, + {"network": "30.0.0.0/32", "no_of_network": 10}, + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r2" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_clear_bgp_and_verify(request): + """ + Created few static routes and verified all routes are learned via BGP + cleared BGP and verified all routes are intact + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # clear ip bgp + result = clear_bgp_and_verify(tgen, topo, "r1") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_attributes_with_vrf_default_keyword_p0(request): + """ + TC_9: + Verify BGP functionality for default vrf with + "vrf default" keyword. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure static routes and redistribute in BGP on R3") + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "no_of_ip": 4, + "next_hop": "Null0", + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Create a route-map to match a specific prefix and modify" + "BGP attributes for matched prefix" + ) + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "ABC": [ + { + "seqid": 10, + "action": "permit", + "network": NETWORK["ipv4"][0], + } + ] + }, + "ipv6": { + "XYZ": [ + { + "seqid": 100, + "action": "permit", + "network": NETWORK["ipv6"][0], + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + if addr_type == "ipv4": + pf_list = "ABC" + else: + pf_list = "XYZ" + + input_dict_6 = { + "r3": { + "route_maps": { + "BGP_ATTR_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": {addr_type: {"prefix_lists": pf_list}}, + "set": { + "aspath": {"as_num": 500, "as_action": "prepend"}, + "localpref": 500, + "origin": "egp", + "community": {"num": "500:500", "action": "additive"}, + "large_community": { + "num": "500:500:500", + "action": "additive", + }, + }, + }, + {"action": "permit", "seq_id": 20}, + ] + }, + "BGP_ATTR_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 100, + "match": {addr_type: {"prefix_lists": pf_list}}, + "set": { + "aspath": {"as_num": 500, "as_action": "prepend"}, + "localpref": 500, + "origin": "egp", + "community": {"num": "500:500", "action": "additive"}, + "large_community": { + "num": "500:500:500", + "action": "additive", + }, + }, + }, + {"action": "permit", "seq_id": 200}, + ], + } + } + + result = create_route_maps(tgen, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Apply the route-map on R3 in outbound direction for peer R4") + + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "BGP_ATTR_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "BGP_ATTR_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "verify modified attributes for specific prefix with 'vrf default'" + "keyword on R4" + ) + for addr_type in ADDR_TYPES: + dut = "r4" + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "vrf": "default", + "largeCommunity": "500:500:500", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "r4" + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "vrf": "default", + "community": "500:500", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_4 = {"largeCommunity": "500:500:500", "community": "500:500"} + + result = verify_bgp_community( + tgen, addr_type, dut, [NETWORK[addr_type][0]], input_dict_4 + ) + assert result is True, "Test case {} : Should fail \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_bgp_with_loopback_interface(request): + """ + Test BGP with loopback interface + + Adding keys:value pair "dest_link": "lo" and "source_link": "lo" + peer dict of input json file for all router's creating config using + loopback interface. Once BGP neighboship is up then verifying BGP + convergence + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for routerN in sorted(topo["routers"].keys()): + for bgp_neighbor in topo["routers"][routerN]["bgp"]["address_family"]["ipv4"][ + "unicast" + ]["neighbor"].keys(): + + # Adding ['source_link'] = 'lo' key:value pair + topo["routers"][routerN]["bgp"]["address_family"]["ipv4"]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = { + "lo": { + "source_link": "lo", + } + } + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + input_dict = { + "r1": { + "static_routes": [ + {"network": "1.0.2.17/32", "next_hop": "10.0.0.2"}, + {"network": "1.0.3.17/32", "next_hop": "10.0.0.6"}, + ] + }, + "r2": { + "static_routes": [ + {"network": "1.0.1.17/32", "next_hop": "10.0.0.1"}, + {"network": "1.0.3.17/32", "next_hop": "10.0.0.10"}, + ] + }, + "r3": { + "static_routes": [ + {"network": "1.0.1.17/32", "next_hop": "10.0.0.5"}, + {"network": "1.0.2.17/32", "next_hop": "10.0.0.9"}, + {"network": "1.0.4.17/32", "next_hop": "10.0.0.14"}, + ] + }, + "r4": {"static_routes": [{"network": "1.0.3.17/32", "next_hop": "10.0.0.13"}]}, + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Api call verify whether BGP is converged + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_with_loopback_with_same_subnet_p1(request): + """ + Verify routes not installed in zebra when /32 routes received + with loopback BGP session subnet + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + step("Delete BGP seesion created initially") + input_dict_r1 = { + "r1": {"bgp": {"delete": True}}, + "r2": {"bgp": {"delete": True}}, + "r3": {"bgp": {"delete": True}}, + "r4": {"bgp": {"delete": True}}, + } + result = create_router_bgp(tgen, topo, input_dict_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create BGP session over loop address") + topo_modify = deepcopy(topo) + + for routerN in sorted(topo["routers"].keys()): + for addr_type in ADDR_TYPES: + for bgp_neighbor in topo_modify["routers"][routerN]["bgp"][ + "address_family" + ][addr_type]["unicast"]["neighbor"].keys(): + + # Adding ['source_link'] = 'lo' key:value pair + topo_modify["routers"][routerN]["bgp"]["address_family"][addr_type][ + "unicast" + ]["neighbor"][bgp_neighbor]["dest_link"] = { + "lo": {"source_link": "lo", "ebgp_multihop": 2} + } + + result = create_router_bgp(tgen, topo_modify["routers"]) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Disable IPv6 BGP nbr from ipv4 address family") + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r1"]["bgp"]["local_as"]), + "address-family ipv4 unicast", + "no neighbor {} activate".format( + topo["routers"]["r2"]["links"]["lo"]["ipv6"].split("/")[0] + ), + "no neighbor {} activate".format( + topo["routers"]["r3"]["links"]["lo"]["ipv6"].split("/")[0] + ), + ] + }, + "r2": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r2"]["bgp"]["local_as"]), + "address-family ipv4 unicast", + "no neighbor {} activate".format( + topo["routers"]["r1"]["links"]["lo"]["ipv6"].split("/")[0] + ), + "no neighbor {} activate".format( + topo["routers"]["r3"]["links"]["lo"]["ipv6"].split("/")[0] + ), + ] + }, + "r3": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r3"]["bgp"]["local_as"]), + "address-family ipv4 unicast", + "no neighbor {} activate".format( + topo["routers"]["r1"]["links"]["lo"]["ipv6"].split("/")[0] + ), + "no neighbor {} activate".format( + topo["routers"]["r2"]["links"]["lo"]["ipv6"].split("/")[0] + ), + "no neighbor {} activate".format( + topo["routers"]["r4"]["links"]["lo"]["ipv6"].split("/")[0] + ), + ] + }, + "r4": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r4"]["bgp"]["local_as"]), + "address-family ipv4 unicast", + "no neighbor {} activate".format( + topo["routers"]["r3"]["links"]["lo"]["ipv6"].split("/")[0] + ), + ] + }, + } + + step("Configure kernel routes") + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + r1_ipv4_lo = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_ipv6_lo = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r2_ipv4_lo = topo["routers"]["r2"]["links"]["lo"]["ipv4"] + r2_ipv6_lo = topo["routers"]["r2"]["links"]["lo"]["ipv6"] + r3_ipv4_lo = topo["routers"]["r3"]["links"]["lo"]["ipv4"] + r3_ipv6_lo = topo["routers"]["r3"]["links"]["lo"]["ipv6"] + r4_ipv4_lo = topo["routers"]["r4"]["links"]["lo"]["ipv4"] + r4_ipv6_lo = topo["routers"]["r4"]["links"]["lo"]["ipv6"] + + r1_r2 = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + r2_r1 = topo["routers"]["r2"]["links"]["r1"]["ipv6"].split("/")[0] + r1_r3 = topo["routers"]["r1"]["links"]["r3"]["ipv6"].split("/")[0] + r3_r1 = topo["routers"]["r3"]["links"]["r1"]["ipv6"].split("/")[0] + r2_r3 = topo["routers"]["r2"]["links"]["r3"]["ipv6"].split("/")[0] + r3_r2 = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + r3_r4 = topo["routers"]["r3"]["links"]["r4"]["ipv6"].split("/")[0] + r4_r3 = topo["routers"]["r4"]["links"]["r3"]["ipv6"].split("/")[0] + + r1_r2_ipv4 = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + r2_r1_ipv4 = topo["routers"]["r2"]["links"]["r1"]["ipv4"].split("/")[0] + r1_r3_ipv4 = topo["routers"]["r1"]["links"]["r3"]["ipv4"].split("/")[0] + r3_r1_ipv4 = topo["routers"]["r3"]["links"]["r1"]["ipv4"].split("/")[0] + r2_r3_ipv4 = topo["routers"]["r2"]["links"]["r3"]["ipv4"].split("/")[0] + r3_r2_ipv4 = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + r3_r4_ipv4 = topo["routers"]["r3"]["links"]["r4"]["ipv4"].split("/")[0] + r4_r3_ipv4 = topo["routers"]["r4"]["links"]["r3"]["ipv4"].split("/")[0] + + r1_r2_intf = topo["routers"]["r1"]["links"]["r2"]["interface"] + r2_r1_intf = topo["routers"]["r2"]["links"]["r1"]["interface"] + r1_r3_intf = topo["routers"]["r1"]["links"]["r3"]["interface"] + r3_r1_intf = topo["routers"]["r3"]["links"]["r1"]["interface"] + r2_r3_intf = topo["routers"]["r2"]["links"]["r3"]["interface"] + r3_r2_intf = topo["routers"]["r3"]["links"]["r2"]["interface"] + r3_r4_intf = topo["routers"]["r3"]["links"]["r4"]["interface"] + r4_r3_intf = topo["routers"]["r4"]["links"]["r3"]["interface"] + + ipv4_list = [ + ("r1", r1_r2_intf, r2_ipv4_loopback), + ("r1", r1_r3_intf, r3_ipv4_loopback), + ("r2", r2_r1_intf, r1_ipv4_loopback), + ("r2", r2_r3_intf, r3_ipv4_loopback), + ("r3", r3_r1_intf, r1_ipv4_loopback), + ("r3", r3_r2_intf, r2_ipv4_loopback), + ("r3", r3_r4_intf, r4_ipv4_loopback), + ("r4", r4_r3_intf, r3_ipv4_loopback), + ] + + ipv6_list = [ + ("r1", r1_r2_intf, r2_ipv6_loopback, r2_r1), + ("r1", r1_r3_intf, r3_ipv6_loopback, r3_r1), + ("r2", r2_r1_intf, r1_ipv6_loopback, r1_r2), + ("r2", r2_r3_intf, r3_ipv6_loopback, r3_r2), + ("r3", r3_r1_intf, r1_ipv6_loopback, r1_r3), + ("r3", r3_r2_intf, r2_ipv6_loopback, r2_r3), + ("r3", r3_r4_intf, r4_ipv6_loopback, r4_r3), + ("r4", r4_r3_intf, r3_ipv6_loopback, r3_r4), + ] + + for dut, intf, loop_addr in ipv4_list: + result = addKernelRoute(tgen, dut, intf, loop_addr) + assert result is True, "Testcase {}:Failed \n Error: {}".format(tc_name, result) + + for dut, intf, loop_addr, next_hop in ipv6_list: + result = addKernelRoute(tgen, dut, intf, loop_addr, next_hop) + assert result is True, "Testcase {}:Failed \n Error: {}".format(tc_name, result) + + step("Configure static routes") + + input_dict = { + "r1": { + "static_routes": [ + {"network": r2_ipv4_loopback, "next_hop": r2_r1_ipv4}, + {"network": r3_ipv4_loopback, "next_hop": r3_r1_ipv4}, + {"network": r2_ipv6_loopback, "next_hop": r2_r1}, + {"network": r3_ipv6_loopback, "next_hop": r3_r1}, + ] + }, + "r2": { + "static_routes": [ + {"network": r1_ipv4_loopback, "next_hop": r1_r2_ipv4}, + {"network": r3_ipv4_loopback, "next_hop": r3_r2_ipv4}, + {"network": r1_ipv6_loopback, "next_hop": r1_r2}, + {"network": r3_ipv6_loopback, "next_hop": r3_r2}, + ] + }, + "r3": { + "static_routes": [ + {"network": r1_ipv4_loopback, "next_hop": r1_r3_ipv4}, + {"network": r2_ipv4_loopback, "next_hop": r2_r3_ipv4}, + {"network": r4_ipv4_loopback, "next_hop": r4_r3_ipv4}, + {"network": r1_ipv6_loopback, "next_hop": r1_r3}, + {"network": r2_ipv6_loopback, "next_hop": r2_r3}, + {"network": r4_ipv6_loopback, "next_hop": r4_r3}, + ] + }, + "r4": { + "static_routes": [ + {"network": r3_ipv4_loopback, "next_hop": r3_r4_ipv4}, + {"network": r3_ipv6_loopback, "next_hop": r3_r4}, + ] + }, + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP session convergence") + + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure redistribute connected on R2 and R4") + input_dict_1 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify Ipv4 and Ipv6 network installed in R1 RIB but not in FIB") + input_dict_r1 = { + "r1": { + "static_routes": [ + {"network": "1.0.2.17/32"}, + {"network": "2001:db8:f::2:17/128"}, + ] + } + } + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_fib_routes( + tgen, addr_type, dut, input_dict_r1, expected=False + ) # pylint: disable=E1123 + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + step("Verify Ipv4 and Ipv6 network installed in r3 RIB but not in FIB") + input_dict_r3 = { + "r3": { + "static_routes": [ + {"network": "1.0.4.17/32"}, + {"network": "2001:db8:f::4:17/128"}, + ] + } + } + dut = "r3" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_fib_routes( + tgen, addr_type, dut, input_dict_r1, expected=False + ) # pylint: disable=E1123 + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_bfd_down_cease_notification/__init__.py b/tests/topotests/bgp_bfd_down_cease_notification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r1/bfdd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r1/bfdd.conf new file mode 100644 index 0000000..0ae384e --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r1/bfdd.conf @@ -0,0 +1,6 @@ +bfd + peer 192.168.255.2 interface r1-eth0 + exit + ! +exit +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf new file mode 100644 index 0000000..e855f75 --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r1/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as external + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 timers connect 1 + neighbor 192.168.255.2 bfd + neighbor 192.168.255.2 passive + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r1/zebra.conf b/tests/topotests/bgp_bfd_down_cease_notification/r1/zebra.conf new file mode 100644 index 0000000..091794f --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r2/bfdd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r2/bfdd.conf new file mode 100644 index 0000000..dcd5033 --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r2/bfdd.conf @@ -0,0 +1,6 @@ +bfd + peer 192.168.255.1 interface r2-eth0 + exit + ! +exit +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf b/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf new file mode 100644 index 0000000..faf2c6b --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r2/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as external + neighbor 192.168.255.1 timers 3 10 + neighbor 192.168.255.1 timers connect 1 + neighbor 192.168.255.1 bfd + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/r2/zebra.conf b/tests/topotests/bgp_bfd_down_cease_notification/r2/zebra.conf new file mode 100644 index 0000000..1fcccbe --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.2/32 +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py b/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py new file mode 100644 index 0000000..0014298 --- /dev/null +++ b/tests/topotests/bgp_bfd_down_cease_notification/test_bgp_bfd_down_cease_notification.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_bfd_down_cease_notification.py +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if Cease/BFD Down notification message is sent/received +when the BFD is down. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import kill_router_daemons, step + +pytestmark = [pytest.mark.bfdd, pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BFD, os.path.join(CWD, "{}/bfdd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_bfd_down_notification(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + "peerBfdInfo": {"status": "Up"}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_bfd_down_notification(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "lastNotificationReason": "Cease/BFD Down", + "lastNotificationHardReset": True, + } + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed to see BGP convergence on R2" + + step("Kill bfdd on R2") + kill_router_daemons(tgen, "r2", ["bfdd"]) + + step("Check if we received Cease/BFD Down notification message") + test_func = functools.partial(_bgp_bfd_down_notification) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed to see BGP Cease/BFD Down notification message on R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_blackhole_community/__init__.py b/tests/topotests/bgp_blackhole_community/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_blackhole_community/r1/bgpd.conf b/tests/topotests/bgp_blackhole_community/r1/bgpd.conf new file mode 100644 index 0000000..574d199 --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r1/bgpd.conf @@ -0,0 +1,14 @@ +! +router bgp 65001 + timers bgp 3 9 + no bgp ebgp-requires-policy + neighbor r1-eth0 interface remote-as external + address-family ipv4 unicast + redistribute connected + neighbor r1-eth0 route-map r2 out + exit-address-family + ! +! +route-map r2 permit 10 + set community blackhole +! diff --git a/tests/topotests/bgp_blackhole_community/r1/zebra.conf b/tests/topotests/bgp_blackhole_community/r1/zebra.conf new file mode 100644 index 0000000..70dc5e5 --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r1/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +ip forwarding +! + diff --git a/tests/topotests/bgp_blackhole_community/r2/bgpd.conf b/tests/topotests/bgp_blackhole_community/r2/bgpd.conf new file mode 100644 index 0000000..2260613 --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r2/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor r2-eth0 interface remote-as external + neighbor r2-eth1 interface remote-as external + neighbor r2-eth2 interface remote-as internal +! diff --git a/tests/topotests/bgp_blackhole_community/r2/zebra.conf b/tests/topotests/bgp_blackhole_community/r2/zebra.conf new file mode 100644 index 0000000..cf6fb6d --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r2/zebra.conf @@ -0,0 +1,12 @@ +! +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.1/24 +! +interface r2-eth2 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_blackhole_community/r3/bgpd.conf b/tests/topotests/bgp_blackhole_community/r3/bgpd.conf new file mode 100644 index 0000000..d0c4b40 --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r3/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65003 + timers bgp 3 9 + no bgp ebgp-requires-policy + neighbor r3-eth0 interface remote-as external +! diff --git a/tests/topotests/bgp_blackhole_community/r3/zebra.conf b/tests/topotests/bgp_blackhole_community/r3/zebra.conf new file mode 100644 index 0000000..05ab56d --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_blackhole_community/r4/bgpd.conf b/tests/topotests/bgp_blackhole_community/r4/bgpd.conf new file mode 100644 index 0000000..eca12bd --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r4/bgpd.conf @@ -0,0 +1,13 @@ +! +router bgp 65002 + timers bgp 3 9 + no bgp ebgp-requires-policy + neighbor r4-eth0 interface remote-as internal +! +address-family ipv4 unicast + neighbor r4-eth0 route-map FOO in +exit-address-family +! +route-map FOO permit 10 + set ipv6 next-hop local fe80::202:ff:fe00:99 +exit diff --git a/tests/topotests/bgp_blackhole_community/r4/zebra.conf b/tests/topotests/bgp_blackhole_community/r4/zebra.conf new file mode 100644 index 0000000..e2ccaed --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py b/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py new file mode 100644 index 0000000..9f5c0ef --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if 172.16.255.254/32 tagged with BLACKHOLE community is not +re-advertised downstream outside local AS. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_blackhole_community(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd("show ip bgp 172.16.255.254/32 json") + ) + expected = {"paths": [{"community": {"list": ["blackhole", "noExport"]}}]} + return topotest.json_cmp(output, expected) + + def _bgp_no_advertise_ebgp(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd( + "show ip bgp neighbor r2-eth1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": {}, + "totalPrefixCounter": 0, + "filteredPrefixCounter": 0, + } + + return topotest.json_cmp(output, expected) + + def _bgp_no_advertise_ibgp(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd( + "show ip bgp neighbor r2-eth2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": {"172.16.255.254/32": {}}, + "totalPrefixCounter": 2, + } + + return topotest.json_cmp(output, expected) + + def _bgp_verify_nexthop_validity(): + output = json.loads(tgen.gears["r4"].vtysh_cmd("show bgp nexthop json")) + + expected = { + "ipv6": { + "fe80::202:ff:fe00:99": { + "valid": True, + "complete": True, + "igpMetric": 0, + "pathCount": 2, + "nexthops": [{"interfaceName": "r4-eth0"}], + }, + } + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Failed bgp convergence in "{}"'.format(tgen.gears["r2"]) + + step("Check if 172.16.255.254/32 is not advertised to eBGP peers") + + test_func = functools.partial(_bgp_no_advertise_ebgp) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert ( + result is None + ), 'Advertised blackhole tagged prefix to eBGP peers in "{}"'.format( + tgen.gears["r2"] + ) + + step("Check if 172.16.255.254/32 is advertised to iBGP peers") + test_func = functools.partial(_bgp_no_advertise_ibgp) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert ( + result is None + ), 'Withdrawn blackhole tagged prefix to iBGP peers in "{}"'.format( + tgen.gears["r2"] + ) + + step("Verify if the nexthop set via route-map on r4 is marked valid") + test_func = functools.partial(_bgp_verify_nexthop_validity) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, 'Nexthops are not valid "{}"'.format(tgen.gears["r4"]) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_bmp/__init__.py b/tests/topotests/bgp_bmp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_bmp/r1/bgpd.conf b/tests/topotests/bgp_bmp/r1/bgpd.conf new file mode 100644 index 0000000..24505de --- /dev/null +++ b/tests/topotests/bgp_bmp/r1/bgpd.conf @@ -0,0 +1,48 @@ +router bgp 65501 + bgp router-id 192.168.0.1 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65502 + neighbor 192:168::2 remote-as 65502 +! + bmp targets bmp1 + bmp connect 192.0.2.10 port 1789 min-retry 100 max-retry 10000 + exit +! + address-family ipv4 vpn + neighbor 192.168.0.2 activate + neighbor 192.168.0.2 soft-reconfiguration inbound + exit-address-family + address-family ipv6 vpn + neighbor 192:168::2 activate + neighbor 192:168::2 soft-reconfiguration inbound + exit-address-family + address-family ipv4 unicast + neighbor 192.168.0.2 activate + neighbor 192.168.0.2 soft-reconfiguration inbound + no neighbor 192:168::2 activate + exit-address-family +! + address-family ipv6 unicast + neighbor 192:168::2 activate + neighbor 192:168::2 soft-reconfiguration inbound + exit-address-family +! +router bgp 65502 vrf vrf1 + bgp router_id 192.168.0.1 + bgp log-neighbor-changes + address-family ipv4 unicast + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family + address-family ipv6 unicast + label vpn export 103 + rd vpn export 555:1 + rt vpn both 54:200 + export vpn + import vpn + exit-address-family +exit diff --git a/tests/topotests/bgp_bmp/r1/zebra.conf b/tests/topotests/bgp_bmp/r1/zebra.conf new file mode 100644 index 0000000..0b523c9 --- /dev/null +++ b/tests/topotests/bgp_bmp/r1/zebra.conf @@ -0,0 +1,7 @@ +interface r1-eth0 + ip address 192.0.2.1/24 +! +interface r1-eth1 + ip address 192.168.0.1/24 + ipv6 address 192:168::1/64 +! diff --git a/tests/topotests/bgp_bmp/r2/bgpd.conf b/tests/topotests/bgp_bmp/r2/bgpd.conf new file mode 100644 index 0000000..40e2cd5 --- /dev/null +++ b/tests/topotests/bgp_bmp/r2/bgpd.conf @@ -0,0 +1,46 @@ +router bgp 65502 + bgp router-id 192.168.0.2 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.0.1 remote-as 65501 + neighbor 192:168::1 remote-as 65501 +! + address-family ipv4 unicast + neighbor 192.168.0.1 activate + no neighbor 192:168::1 activate + redistribute connected + exit-address-family +! + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! + address-family ipv6 vpn + neighbor 192:168::1 activate + exit-address-family +! + address-family ipv6 unicast + neighbor 192:168::1 activate + redistribute connected + exit-address-family +! +router bgp 65502 vrf vrf1 + bgp router-id 192.168.0.2 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family + address-family ipv6 unicast + label vpn export 105 + rd vpn export 555:2 + rt vpn both 54:200 + export vpn + import vpn + exit-address-family +exit diff --git a/tests/topotests/bgp_bmp/r2/zebra.conf b/tests/topotests/bgp_bmp/r2/zebra.conf new file mode 100644 index 0000000..9d82bfe --- /dev/null +++ b/tests/topotests/bgp_bmp/r2/zebra.conf @@ -0,0 +1,8 @@ +interface r2-eth0 + ip address 192.168.0.2/24 + ipv6 address 192:168::2/64 +! +interface r2-eth1 + ip address 172.31.0.2/24 + ipv6 address 172:31::2/64 +! diff --git a/tests/topotests/bgp_bmp/test_bgp_bmp.py b/tests/topotests/bgp_bmp/test_bgp_bmp.py new file mode 100644 index 0000000..02de805 --- /dev/null +++ b/tests/topotests/bgp_bmp/test_bgp_bmp.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. +# Authored by Farid Mihoub +# + +""" +test_bgp_bmp.py: Test BGP BMP functionalities + + +------+ +------+ +------+ + | | | | | | + | BMP1 |------------| R1 |---------------| R2 | + | | | | | | + +------+ +------+ +------+ + +Setup two routers R1 and R2 with one link configured with IPv4 and +IPv6 addresses. +Configure BGP in R1 and R2 to exchange prefixes from +the latter to the first router. +Setup a link between R1 and the BMP server, activate the BMP feature in R1 +and ensure the monitored BGP sessions logs are well present on the BMP server. +""" + +from functools import partial +from ipaddress import ip_network +import json +import os +import platform +import pytest +import sys + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgp import verify_bgp_convergence_from_running_config +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + +# remember the last sequence number of the logging messages +SEQ = 0 + +PRE_POLICY = "pre-policy" +POST_POLICY = "post-policy" +LOC_RIB = "loc-rib" + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_bmp_server("bmp1", ip="192.0.2.10", defaultRoute="via 192.0.2.1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["bmp1"]) + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "r1-eth1", "r2-eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + "-M bmp", + ) + + tgen.start_router() + + logger.info("starting BMP servers") + for bmp_name, server in tgen.get_bmp_servers().items(): + server.start(log_file=os.path.join(tgen.logdir, bmp_name, "bmp.log")) + + +def teardown_module(_mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + result = verify_bgp_convergence_from_running_config(tgen, dut="r1") + assert result is True, "BGP is not converging" + + +def get_bmp_messages(): + """ + Read the BMP logging messages. + """ + messages = [] + tgen = get_topogen() + text_output = tgen.gears["bmp1"].run( + "cat {}".format(os.path.join(tgen.logdir, "bmp1", "bmp.log")) + ) + + for m in text_output.splitlines(): + # some output in the bash can break the message decoding + try: + messages.append(json.loads(m)) + except Exception as e: + logger.warning(str(e) + " message: {}".format(str(m))) + continue + + if not messages: + logger.error("Bad BMP log format, check your BMP server") + + return messages + + +def check_for_prefixes(expected_prefixes, bmp_log_type, policy, labels=None): + """ + Check for the presence of the given prefixes in the BMP server logs with + the given message type and the set policy. + """ + global SEQ + # we care only about the new messages + messages = [ + m for m in sorted(get_bmp_messages(), key=lambda d: d["seq"]) if m["seq"] > SEQ + ] + + # get the list of pairs (prefix, policy, seq) for the given message type + prefixes = [ + m["ip_prefix"] + for m in messages + if "ip_prefix" in m.keys() + and "bmp_log_type" in m.keys() + and m["bmp_log_type"] == bmp_log_type + and m["policy"] == policy + and ( + labels is None + or ( + m["ip_prefix"] in labels.keys() and m["label"] == labels[m["ip_prefix"]] + ) + ) + ] + + # check for prefixes + for ep in expected_prefixes: + if ep not in prefixes: + msg = "The prefix {} is not present in the {} log messages." + logger.debug(msg.format(ep, bmp_log_type)) + return False + + SEQ = messages[-1]["seq"] + return True + + +def set_bmp_policy(tgen, node, asn, target, safi, policy, vrf=None): + """ + Configure the bmp policy. + """ + vrf = " vrf {}" if vrf else "" + cmd = [ + "con t\n", + "router bgp {}{}\n".format(asn, vrf), + "bmp targets {}\n".format(target), + "bmp monitor ipv4 {} {}\n".format(safi, policy), + "bmp monitor ipv6 {} {}\n".format(safi, policy), + "end\n", + ] + tgen.gears[node].vtysh_cmd("".join(cmd)) + + +def configure_prefixes(tgen, node, asn, safi, prefixes, vrf=None, update=True): + """ + Configure the bgp prefixes. + """ + withdraw = "no " if not update else "" + vrf = " vrf {}".format(vrf) if vrf else "" + for p in prefixes: + ip = ip_network(p) + cmd = [ + "conf t\n", + "router bgp {}{}\n".format(asn, vrf), + "address-family ipv{} {}\n".format(ip.version, safi), + "{}network {}\n".format(withdraw, ip), + "exit-address-family\n", + ] + logger.debug("setting prefix: ipv{} {} {}".format(ip.version, safi, ip)) + tgen.gears[node].vtysh_cmd("".join(cmd)) + + +def unicast_prefixes(policy): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + """ + tgen = get_topogen() + set_bmp_policy(tgen, "r1", 65501, "bmp1", "unicast", policy) + + prefixes = ["172.31.0.15/32", "2111::1111/128"] + # add prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes) + + logger.info("checking for updated prefixes") + # check + test_func = partial(check_for_prefixes, prefixes, "update", policy) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the updated prefixes has been failed !." + + # withdraw prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, update=False) + logger.info("checking for withdrawed prefxies") + # check + test_func = partial(check_for_prefixes, prefixes, "withdraw", policy) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the withdrawed prefixes has been failed !." + + +def vpn_prefixes(policy): + """ + Setup the BMP monitor policy, Add and withdraw ipv4/v6 prefixes. + Check if the previous actions are logged in the BMP server with the right + message type and the right policy. + """ + tgen = get_topogen() + set_bmp_policy(tgen, "r1", 65501, "bmp1", "vpn", policy) + + prefixes = ["172.31.10.1/32", "2001::2222/128"] + + if policy == PRE_POLICY: + # labels are not yet supported in adj-RIB-in. Do not test for the moment + labels = None + else: + # "label vpn export" value in r2/bgpd.conf + labels = { + "172.31.10.1/32": 102, + "2001::2222/128": 105, + } + + # add prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, vrf="vrf1") + + logger.info("checking for updated prefixes") + # check + test_func = partial(check_for_prefixes, prefixes, "update", policy, labels=labels) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the updated prefixes has been failed !." + + # withdraw prefixes + configure_prefixes(tgen, "r2", 65502, "unicast", prefixes, vrf="vrf1", update=False) + logger.info("checking for withdrawed prefixes") + # check + test_func = partial(check_for_prefixes, prefixes, "withdraw", policy) + success, _ = topotest.run_and_expect(test_func, True, wait=0.5) + assert success, "Checking the withdrawed prefixes has been failed !." + + +def test_bmp_server_logging(): + """ + Assert the logging of the bmp server. + """ + + def check_for_log_file(): + tgen = get_topogen() + output = tgen.gears["bmp1"].run( + "ls {}".format(os.path.join(tgen.logdir, "bmp1")) + ) + if "bmp.log" not in output: + return False + return True + + success, _ = topotest.run_and_expect(check_for_log_file, True, wait=0.5) + assert success, "The BMP server is not logging" + + +def test_bmp_bgp_unicast(): + """ + Add/withdraw bgp unicast prefixes and check the bmp logs. + """ + logger.info("*** Unicast prefixes pre-policy logging ***") + unicast_prefixes(PRE_POLICY) + logger.info("*** Unicast prefixes post-policy logging ***") + unicast_prefixes(POST_POLICY) + logger.info("*** Unicast prefixes loc-rib logging ***") + unicast_prefixes(LOC_RIB) + + +def test_bmp_bgp_vpn(): + # check for the prefixes in the BMP server logging file + logger.info("***** VPN prefixes pre-policy logging *****") + vpn_prefixes(PRE_POLICY) + logger.info("***** VPN prefixes post-policy logging *****") + vpn_prefixes(POST_POLICY) + logger.info("***** VPN prefixes loc-rib logging *****") + vpn_prefixes(LOC_RIB) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_color_extcommunities/__init__.py b/tests/topotests/bgp_color_extcommunities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_color_extcommunities/r1/bgpd.conf b/tests/topotests/bgp_color_extcommunities/r1/bgpd.conf new file mode 100644 index 0000000..d4ca392 --- /dev/null +++ b/tests/topotests/bgp_color_extcommunities/r1/bgpd.conf @@ -0,0 +1,17 @@ +! +router bgp 65001 + bgp router-id 192.168.1.1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + address-family ipv4 unicast + network 10.10.10.10/24 route-map rmap + neighbor 192.168.1.2 route-map rmap out + neighbor 192.168.1.2 activate + exit-address-family +! +route-map rmap permit 10 + set extcommunity color 1 + set extcommunity rt 80:987 + set extcommunity color 100 55555 200 +exit diff --git a/tests/topotests/bgp_color_extcommunities/r1/zebra.conf b/tests/topotests/bgp_color_extcommunities/r1/zebra.conf new file mode 100644 index 0000000..42a8303 --- /dev/null +++ b/tests/topotests/bgp_color_extcommunities/r1/zebra.conf @@ -0,0 +1,3 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 diff --git a/tests/topotests/bgp_color_extcommunities/r2/bgpd.conf b/tests/topotests/bgp_color_extcommunities/r2/bgpd.conf new file mode 100644 index 0000000..2f83ada --- /dev/null +++ b/tests/topotests/bgp_color_extcommunities/r2/bgpd.conf @@ -0,0 +1,4 @@ +router bgp 65002 + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external diff --git a/tests/topotests/bgp_color_extcommunities/r2/zebra.conf b/tests/topotests/bgp_color_extcommunities/r2/zebra.conf new file mode 100644 index 0000000..cffe827 --- /dev/null +++ b/tests/topotests/bgp_color_extcommunities/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py b/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py new file mode 100644 index 0000000..e0c1b49 --- /dev/null +++ b/tests/topotests/bgp_color_extcommunities/test_bgp_color_extcommunities.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2022 6WIND S.A. +# Copyright 2023 6WIND S.A. +# François Dumontet +# + + +""" +test_bgp_color_extcommunity.py: Test the FRR BGP color extented +community feature +""" + +import os +import sys +import json +import functools +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + logger.info("setup_module") + + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_color_extended_communities(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": { + "192.168.1.2": { + "pfxSnt": 1, + "state": "Established", + }, + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed announcing 10.10.10.10/32 to r2" + + def _bgp_check_route(router, exists): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10 json")) + if exists: + expected = { + "prefix": "10.10.10.0/24", + "paths": [ + { + "valid": True, + "extendedCommunity": { + "string": "RT:80:987 Color:100 Color:200 Color:55555" + }, + } + ], + } + else: + expected = {} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_route, r2, True) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "10.10.10.0/24 ext community is correctly not installed, but SHOULD be" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_comm_list_delete/__init__.py b/tests/topotests/bgp_comm_list_delete/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_comm_list_delete/r1/bgpd.conf b/tests/topotests/bgp_comm_list_delete/r1/bgpd.conf new file mode 100644 index 0000000..12161d2 --- /dev/null +++ b/tests/topotests/bgp_comm_list_delete/r1/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected route-map r2-out + exit-address-family +! +route-map r2-out permit 10 + set community 111:111 222:222 333:333 444:444 +! diff --git a/tests/topotests/bgp_comm_list_delete/r1/zebra.conf b/tests/topotests/bgp_comm_list_delete/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_comm_list_delete/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_comm_list_delete/r2/bgpd.conf b/tests/topotests/bgp_comm_list_delete/r2/bgpd.conf new file mode 100644 index 0000000..33231b5 --- /dev/null +++ b/tests/topotests/bgp_comm_list_delete/r2/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 + neighbor 192.168.255.1 route-map r1-in in + exit-address-family +! +bgp community-list standard r1 permit 333:333 +! +route-map r1-in permit 10 + set comm-list r1 delete +! diff --git a/tests/topotests/bgp_comm_list_delete/r2/zebra.conf b/tests/topotests/bgp_comm_list_delete/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_comm_list_delete/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_comm_list_delete/test_bgp_comm-list_delete.py b/tests/topotests/bgp_comm_list_delete/test_bgp_comm-list_delete.py new file mode 100644 index 0000000..efc8f20 --- /dev/null +++ b/tests/topotests/bgp_comm_list_delete/test_bgp_comm-list_delete.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_comm-list_delete.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +bgp_comm-list_delete.py: + +Test if works the following commands: +route-map test permit 10 + set comm-list delete +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_maximum_prefix_invalid(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 2, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge initially" + + def _bgp_comm_list_delete(): + output = json.loads(r2.vtysh_cmd("show ip bgp 172.16.255.254/32 json")) + expected = {"paths": [{"community": {"list": ["333:333"]}}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_comm_list_delete) + _, result = topotest.run_and_expect(test_func, not None, count=60, wait=0.5) + assert result is not None, "333:333 community SHOULD be stripped from r1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_comm_list_match/__init__.py b/tests/topotests/bgp_comm_list_match/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_comm_list_match/r1/bgpd.conf b/tests/topotests/bgp_comm_list_match/r1/bgpd.conf new file mode 100644 index 0000000..bac8412 --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r1/bgpd.conf @@ -0,0 +1,28 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as external + neighbor 192.168.0.2 timers 1 3 + neighbor 192.168.0.2 timers connect 1 + address-family ipv4 + redistribute connected + neighbor 192.168.0.2 route-map r2 out + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.1/32 +ip prefix-list p3 seq 5 permit 172.16.255.3/32 +ip prefix-list p4 seq 5 permit 172.16.255.4/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set community 65001:1 65001:2 +route-map r2 permit 20 + match ip address prefix-list p3 + set community 65001:3 +route-map r2 permit 30 + match ip address prefix-list p4 + set community 65001:10 65001:12 65001:13 +exit +route-map r2 permit 40 +exit +! diff --git a/tests/topotests/bgp_comm_list_match/r1/zebra.conf b/tests/topotests/bgp_comm_list_match/r1/zebra.conf new file mode 100644 index 0000000..4219a7c --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 172.16.255.1/32 + ip address 172.16.255.2/32 + ip address 172.16.255.3/32 + ip address 172.16.255.4/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_comm_list_match/r2/bgpd.conf b/tests/topotests/bgp_comm_list_match/r2/bgpd.conf new file mode 100644 index 0000000..cb2f89e --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r2/bgpd.conf @@ -0,0 +1,24 @@ +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as external + neighbor 192.168.0.1 timers 1 3 + neighbor 192.168.0.1 timers connect 1 + neighbor 192.168.1.3 remote-as external + neighbor 192.168.1.3 timers 1 3 + neighbor 192.168.1.3 timers connect 1 + address-family ipv4 + neighbor 192.168.0.1 route-map r1 in + neighbor 192.168.0.1 soft-reconfiguration inbound + exit-address-family +! +bgp community-list 1 seq 5 permit 65001:1 65001:2 +bgp community-list 1 seq 10 permit 65001:3 +! +route-map r1 deny 10 + match community 1 +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_comm_list_match/r2/zebra.conf b/tests/topotests/bgp_comm_list_match/r2/zebra.conf new file mode 100644 index 0000000..7fe82ba --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_comm_list_match/r3/bgpd.conf b/tests/topotests/bgp_comm_list_match/r3/bgpd.conf new file mode 100644 index 0000000..e68a3e4 --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r3/bgpd.conf @@ -0,0 +1,21 @@ +! +!debug bgp updates +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 + neighbor 192.168.1.2 route-map r1 in + neighbor 192.168.1.2 soft-reconfiguration inbound + exit-address-family +! +bgp community-list 2 seq 10 permit 65001:12 +! +route-map r1 deny 10 + match community 2 any +exit +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_comm_list_match/r3/zebra.conf b/tests/topotests/bgp_comm_list_match/r3/zebra.conf new file mode 100644 index 0000000..755dd18 --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.1.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_comm_list_match/test_bgp_comm_list_match.py b/tests/topotests/bgp_comm_list_match/test_bgp_comm_list_match.py new file mode 100644 index 0000000..de69ea9 --- /dev/null +++ b/tests/topotests/bgp_comm_list_match/test_bgp_comm_list_match.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if BGP community-list works as OR if multiple community entries specified, +like: + +bgp community-list 1 seq 5 permit 65001:1 65002:2 +bgp community-list 1 seq 10 permit 65001:3 +! +route-map test deny 10 + match community 1 +route-map test permit 20 + +Here, we should deny routes in/out if the path has: +(65001:1 AND 65001:2) OR 65001:3. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_comm_list_match(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.0.1 filtered-routes json" + ) + ) + expected = { + "receivedRoutes": { + "172.16.255.1/32": { + "path": "65001", + }, + "172.16.255.3/32": { + "path": "65001", + }, + } + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge between R1 and R2") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to filter BGP UPDATES with community-list on R2" + + +def test_bgp_comm_list_match_any(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r3"] + + def _bgp_converge(): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 filtered-routes json" + ) + ) + expected = { + "receivedRoutes": { + "172.16.255.4/32": { + "path": "65002 65001", + }, + } + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge between R3 and R2") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to filter BGP UPDATES with community-list on R3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_communities_topo1/bgp_communities.json b/tests/topotests/bgp_communities_topo1/bgp_communities.json new file mode 100644 index 0000000..da6aec2 --- /dev/null +++ b/tests/topotests/bgp_communities_topo1/bgp_communities.json @@ -0,0 +1,175 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r0": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r0": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_communities_topo1/bgp_communities_topo2.json b/tests/topotests/bgp_communities_topo1/bgp_communities_topo2.json new file mode 100644 index 0000000..fa89f6b --- /dev/null +++ b/tests/topotests/bgp_communities_topo1/bgp_communities_topo2.json @@ -0,0 +1,191 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + } + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + } + } +} diff --git a/tests/topotests/bgp_communities_topo1/test_bgp_communities.py b/tests/topotests/bgp_communities_topo1/test_bgp_communities.py new file mode 100644 index 0000000..f3e03a0 --- /dev/null +++ b/tests/topotests/bgp_communities_topo1/test_bgp_communities.py @@ -0,0 +1,631 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test bgp community functionality: +- Verify routes are not advertised when NO-ADVERTISE Community is applied + +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + check_address_types, + step, + create_route_maps, + create_prefix_lists, + create_route_maps, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json +from copy import deepcopy + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": "2.2.2.2/32", "ipv6": "22:22::2/128"} +NEXT_HOP_IP = {} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >= 4.15") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_communities.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global BGP_CONVERGENCE + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_bgp_no_advertise_community_p0(request): + """ + Verify routes are not advertised when NO-ADVERTISE Community is applied + + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + NEXT_HOP_IP = { + "ipv4": topo["routers"]["r0"]["links"]["r1"]["ipv4"].split("/")[0], + "ipv6": topo["routers"]["r0"]["links"]["r1"]["ipv6"].split("/")[0], + } + + # configure static routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static and connected in Router BGP " "in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "BGP neighbors are up, static and connected route advertised from" + " R1 are present on R2 BGP table and RIB using show ip bgp and " + " show ip route" + ) + step( + "Static and connected route advertised from R1 are present on R3" + " BGP table and RIB using show ip bgp and show ip route" + ) + + dut = "r3" + protocol = "bgp" + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure prefix list P1 on R2 to permit route coming from R1") + # Create ip prefix list + input_dict_2 = { + "r2": { + "prefix_lists": { + addr_type: { + "pf_list_1_{}".format(addr_type): [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_3 = { + "r2": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: {"prefix_lists": "pf_list_1_" + addr_type} + }, + "set": {"community": {"num": "no-advertise"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Apply route-map RM1 on R2, R2 to R3 BGP neighbor with no" + " advertise community" + ) + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_match_pf_1_" + + addr_type, + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "After advertising no advertise community to BGP neighbor " + "static and connected router got removed from R3 verify using " + "show ip bgp & show ip route" + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n Expected: " + "Routes still present in {} router. Found: {}".format(tc_name, dut, result) + ) + + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} router. Found: {}".format( + tc_name, dut, result + ) + + step("Remove and Add no advertise community") + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_match_pf_1_" + + addr_type, + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "After removing no advertise community from BGP neighbor " + "static and connected router got advertised to R3 and " + "removing route-map, verify route using show ip bgp" + " and show ip route" + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + step("Repeat above steps when IBGP nbr configured between R1, R2 & R2, R3") + topo1 = deepcopy(topo) + + topo1["routers"]["r1"]["bgp"]["local_as"] = "100" + topo1["routers"]["r2"]["bgp"]["local_as"] = "100" + topo1["routers"]["r3"]["bgp"]["local_as"] = "100" + + for rtr in ["r1", "r2", "r3"]: + if "bgp" in topo1["routers"][rtr].keys(): + delete_bgp = {rtr: {"bgp": {"delete": True}}} + result = create_router_bgp(tgen, topo1, delete_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + config_bgp = { + rtr: {"bgp": {"local_as": topo1["routers"][rtr]["bgp"]["local_as"]}} + } + result = create_router_bgp(tgen, topo1, config_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + build_config_from_json(tgen, topo1, save_bkup=False) + + step("verify bgp convergence before starting test case") + + bgp_convergence = verify_bgp_convergence(tgen, topo1) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + # configure static routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static and connected in Router " "BGP in R1") + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "BGP neighbors are up, static and connected route advertised from" + " R1 are present on R2 BGP table and RIB using show ip bgp and " + " show ip route" + ) + step( + "Static and connected route advertised from R1 are present on R3" + " BGP table and RIB using show ip bgp and show ip route" + ) + + dut = "r2" + protocol = "bgp" + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure prefix list P1 on R2 to permit route coming from R1") + # Create ip prefix list + input_dict_2 = { + "r2": { + "prefix_lists": { + addr_type: { + "pf_list_1_{}".format(addr_type): [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_3 = { + "r2": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: {"prefix_lists": "pf_list_1_" + addr_type} + }, + "set": {"community": {"num": "no-advertise"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Apply route-map RM1 on R2, R2 to R3 BGP neighbor with no" + " advertise community" + ) + + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_match_pf_1_" + + addr_type, + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "After advertising no advertise community to BGP neighbor " + "static and connected router got removed from R3 verify using " + "show ip bgp & show ip route" + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + step("Remove and Add no advertise community") + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_match_pf_1_" + + addr_type, + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "After removing no advertise community from BGP neighbor " + "static and connected router got advertised to R3 and " + "removing route verify using show ip bgp and " + " show ip route" + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes still present in R3 router. Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_communities_topo1/test_bgp_communities_topo2.py b/tests/topotests/bgp_communities_topo1/test_bgp_communities_topo2.py new file mode 100644 index 0000000..324f53f --- /dev/null +++ b/tests/topotests/bgp_communities_topo1/test_bgp_communities_topo2.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test bgp community functionality: +1. Verify that BGP well known communities work fine for + eBGP and iBGP peers. + Well known communities tested: no-export, local-AS + +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + check_address_types, + step, + create_route_maps, + create_route_maps, + required_linux_kernel_version, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_community, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = { + "ipv4": ["192.0.2.1/32", "192.0.2.2/32"], + "ipv6": ["2001:DB8::1:1/128", "2001:DB8::1:2/128"], +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.14") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >= 4.14") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_communities_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global BGP_CONVERGENCE + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_bgp_no_export_local_as_communities_p0(request): + """ + Verify that BGP well known communities work fine for + eBGP and iBGP peers. + Well known communities tested: no-export, local-AS + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config: Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step("Configure static routes on R1 with next-hop as null0") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "null0"}] + } + } + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for comm_type in ["no-export", "local-AS"]: + + step("Create a route-map on R1 to set community as {}".format(comm_type)) + + seq_id = 10 + input_rmap = { + "r1": { + "route_maps": { + "rmap_wkc": [ + { + "action": "permit", + "seq_id": seq_id, + "set": {"community": {"num": "{}".format(comm_type)}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_rmap) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Apply route-map while redistributing static routes into BGP") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"route-map": "rmap_wkc"}, + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"route-map": "rmap_wkc"}, + } + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + + step("Verify that BGP prefixes on R1 have community: {}".format(comm_type)) + input_dict_4 = {"community": "{}".format(comm_type)} + for addr_type in ADDR_TYPES: + result = verify_bgp_community( + tgen, addr_type, "r1", NETWORK[addr_type], input_dict_4 + ) + assert result is True, "Test case {} : Should fail \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r1"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r1"]["links"]["r2"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that these prefixes, originated on R1, are not" + "received on R3 but received on R2" + ) + result = verify_rib( + tgen, + addr_type, + "r3", + input_dict_4, + next_hop=topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + expected=False, + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes are still present in rib of r3 \n " + "Found: {}".format(tc_name, result) + ) + + step("Remove route-map from redistribute static on R1") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static", "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static", "delete": True} + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure redistribute static") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that these prefixes, originated on R1, are now" + "received on both routers R2 and R3" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r1"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r1"]["links"]["r2"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r3", + input_dict_4, + next_hop=topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_community_alias/__init__.py b/tests/topotests/bgp_community_alias/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_community_alias/r1/bgpd.conf b/tests/topotests/bgp_community_alias/r1/bgpd.conf new file mode 100644 index 0000000..844ab28 --- /dev/null +++ b/tests/topotests/bgp_community_alias/r1/bgpd.conf @@ -0,0 +1,26 @@ +! +bgp send-extra-data zebra +! +bgp community alias 65001:1 community-r2-1 +bgp community alias 65002:2 community-r2-2 +bgp community alias 65001:1:1 large-community-r2-1 +! +bgp large-community-list expanded r2 seq 5 permit _65001:1:1_ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.2 route-map r2 in + exit-address-family +! +route-map r2 permit 10 + match alias community-r2-1 + set tag 10 +route-map r2 permit 20 + match alias community-r2-2 + set tag 20 +route-map r2 permit 30 + set tag 100 +! diff --git a/tests/topotests/bgp_community_alias/r1/zebra.conf b/tests/topotests/bgp_community_alias/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_community_alias/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_community_alias/r2/bgpd.conf b/tests/topotests/bgp_community_alias/r2/bgpd.conf new file mode 100644 index 0000000..7806077 --- /dev/null +++ b/tests/topotests/bgp_community_alias/r2/bgpd.conf @@ -0,0 +1,25 @@ +! +bgp send-extra-data zebra +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.1 route-map r1 out + exit-address-family +! +ip prefix-list p1 permit 172.16.16.1/32 +ip prefix-list p2 permit 172.16.16.2/32 +ip prefix-list p3 permit 172.16.16.3/32 +! +route-map r1 permit 10 + match ip address prefix-list p1 + set community 65001:1 65001:2 + set large-community 65001:1:1 65001:1:2 +route-map r1 permit 20 + match ip address prefix-list p2 + set community 65002:1 65002:2 +route-map r1 permit 30 + match ip address prefix-list p3 +! diff --git a/tests/topotests/bgp_community_alias/r2/zebra.conf b/tests/topotests/bgp_community_alias/r2/zebra.conf new file mode 100644 index 0000000..b8cb9ba --- /dev/null +++ b/tests/topotests/bgp_community_alias/r2/zebra.conf @@ -0,0 +1,9 @@ +! +int lo + ip address 172.16.16.1/32 + ip address 172.16.16.2/32 + ip address 172.16.16.3/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_community_alias/test_bgp-community-alias.py b/tests/topotests/bgp_community_alias/test_bgp-community-alias.py new file mode 100644 index 0000000..fdae9a3 --- /dev/null +++ b/tests/topotests/bgp_community_alias/test_bgp-community-alias.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if BGP community alias is visible in CLI outputs +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = pytest.mark.bgpd + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_community_alias(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "172.16.16.1/32": [ + { + "tag": 10, + "communities": "community-r2-1 65001:2", + "largeCommunities": "large-community-r2-1 65001:1:2", + } + ], + "172.16.16.2/32": [ + { + "tag": 20, + "communities": "65002:1 community-r2-2", + "largeCommunities": "", + } + ], + "172.16.16.3/32": [ + { + "tag": 100, + "communities": "", + "largeCommunities": "", + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP community aliases at r1" + + def _bgp_show_prefixes_by_alias(router): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast alias large-community-r2-1 json detail" + ) + ) + expected = { + "routes": { + "172.16.16.1/32": { + "paths": [ + { + "community": {"string": "community-r2-1 65001:2"}, + "largeCommunity": { + "string": "large-community-r2-1 65001:1:2" + }, + } + ] + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_show_prefixes_by_alias, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP prefixes by community alias at r1" + + def _bgp_show_prefixes_by_large_community_list(router): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast large-community-list r2 json") + ) + expected = {"routes": {"172.16.16.1/32": [{"valid": True}]}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_show_prefixes_by_large_community_list, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP prefixes by large community list at r1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_community_change_update/__init__.py b/tests/topotests/bgp_community_change_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_community_change_update/c1/bgpd.conf b/tests/topotests/bgp_community_change_update/c1/bgpd.conf new file mode 100644 index 0000000..24cf9df --- /dev/null +++ b/tests/topotests/bgp_community_change_update/c1/bgpd.conf @@ -0,0 +1,11 @@ +! +debug bgp updates +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 10.0.1.2 remote-as external + neighbor 10.0.1.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/c1/zebra.conf b/tests/topotests/bgp_community_change_update/c1/zebra.conf new file mode 100644 index 0000000..e3dbbc0 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/c1/zebra.conf @@ -0,0 +1,6 @@ +! +interface c1-eth0 + ip address 10.0.1.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py b/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py new file mode 100644 index 0000000..5ad15e0 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/test_bgp_community_change_update.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2020 by +# Donatas Abraitis +# + +r""" +Reference: https://www.cmand.org/communityexploration + + --y2-- + / | \ + c1 ---- x1 ---- y1 | z1 + \ | / + --y3-- + +1. z1 announces 192.168.255.254/32 to y2, y3. +2. y2 and y3 tags this prefix at ingress with appropriate +communities 65004:2 (y2) and 65004:3 (y3). +3. x1 filters all communities at the egress to c1. +4. Shutdown the link between y1 and y2. +5. y1 will generate a BGP UPDATE message regarding the next-hop change. +6. x1 will generate a BGP UPDATE message regarding community change. + +To avoid sending duplicate BGP UPDATE messages we should make sure +we send only actual route updates. In this example, x1 will skip +BGP UPDATE to c1 because the actual route is the same +(filtered communities - nothing changes). +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +from lib.common_config import step +from time import sleep + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("z1") + tgen.add_router("y1") + tgen.add_router("y2") + tgen.add_router("y3") + tgen.add_router("x1") + tgen.add_router("c1") + + # 10.0.1.0/30 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["c1"]) + switch.add_link(tgen.gears["x1"]) + + # 10.0.2.0/30 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["x1"]) + switch.add_link(tgen.gears["y1"]) + + # 10.0.3.0/30 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["y1"]) + switch.add_link(tgen.gears["y2"]) + + # 10.0.4.0/30 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["y1"]) + switch.add_link(tgen.gears["y3"]) + + # 10.0.5.0/30 + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["y2"]) + switch.add_link(tgen.gears["y3"]) + + # 10.0.6.0/30 + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["y2"]) + switch.add_link(tgen.gears["z1"]) + + # 10.0.7.0/30 + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["y3"]) + switch.add_link(tgen.gears["z1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_community_update_path_change(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge_initial(): + output = json.loads( + tgen.gears["c1"].vtysh_cmd("show ip bgp neighbor 10.0.1.2 json") + ) + expected = { + "10.0.1.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 8}}, + } + } + return topotest.json_cmp(output, expected) + + step("Check if an initial topology is converged") + test_func = functools.partial(_bgp_converge_initial) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in c1" + + step("Disable link between y1 and y2") + tgen.gears["y1"].run("ip link set dev y1-eth1 down") + + def _bgp_converge_link_disabled(): + output = json.loads(tgen.gears["y1"].vtysh_cmd("show ip bgp nei 10.0.3.2 json")) + expected = {"10.0.3.2": {"bgpState": "Active"}} + return topotest.json_cmp(output, expected) + + step("Check if a topology is converged after a link down between y1 and y2") + test_func = functools.partial(_bgp_converge_link_disabled) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in y1" + + def _bgp_check_for_duplicate_updates(): + duplicate = False + i = 0 + while i < 5: + if ( + len( + tgen.gears["c1"].run( + 'grep "10.0.1.2(x1) rcvd 192.168.255.254/32 IPv4 unicast...duplicate ignored" bgpd.log' + ) + ) + > 0 + ): + duplicate = True + i += 1 + sleep(0.5) + return duplicate + + step("Check if we see duplicate BGP UPDATE message in c1 (suppress-duplicates)") + assert ( + _bgp_check_for_duplicate_updates() == False + ), "Seen duplicate BGP UPDATE message in c1 from x1" + + step("Disable bgp suppress-duplicates at x1") + tgen.gears["x1"].run( + "vtysh -c 'conf' -c 'router bgp' -c 'no bgp suppress-duplicates'" + ) + + step("Enable link between y1 and y2") + tgen.gears["y1"].run("ip link set dev y1-eth1 up") + + def _bgp_converge_link_enabled(): + output = json.loads(tgen.gears["y1"].vtysh_cmd("show ip bgp nei 10.0.3.2 json")) + expected = { + "10.0.3.2": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": {"acceptedPrefixCounter": 5, "sentPrefixCounter": 4} + }, + } + } + return topotest.json_cmp(output, expected) + + step("Check if a topology is converged after a link up between y1 and y2") + test_func = functools.partial(_bgp_converge_link_enabled) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see bgp convergence in y1" + + step( + "Check if we see duplicate BGP UPDATE message in c1 (no bgp suppress-duplicates)" + ) + assert ( + _bgp_check_for_duplicate_updates() == True + ), "Didn't see duplicate BGP UPDATE message in c1 from x1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_community_change_update/x1/bgpd.conf b/tests/topotests/bgp_community_change_update/x1/bgpd.conf new file mode 100644 index 0000000..8d7bcb9 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/x1/bgpd.conf @@ -0,0 +1,20 @@ +! +debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 10.0.1.1 remote-as external + neighbor 10.0.1.1 timers 3 10 + neighbor 10.0.2.2 remote-as external + neighbor 10.0.2.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.1.1 route-map c1 out + exit-address-family +! +bgp community-list standard c1 seq 1 permit 65004:2 +bgp community-list standard c1 seq 2 permit 65004:3 +! +route-map c1 permit 10 + set comm-list c1 delete +! diff --git a/tests/topotests/bgp_community_change_update/x1/zebra.conf b/tests/topotests/bgp_community_change_update/x1/zebra.conf new file mode 100644 index 0000000..8b7c03f --- /dev/null +++ b/tests/topotests/bgp_community_change_update/x1/zebra.conf @@ -0,0 +1,9 @@ +! +interface x1-eth0 + ip address 10.0.1.2/30 +! +interface x1-eth1 + ip address 10.0.2.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y1/bgpd.conf b/tests/topotests/bgp_community_change_update/y1/bgpd.conf new file mode 100644 index 0000000..a010085 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.2.1 remote-as external + neighbor 10.0.2.1 timers 3 10 + neighbor 10.0.3.2 remote-as internal + neighbor 10.0.3.2 timers 3 10 + neighbor 10.0.4.2 remote-as internal + neighbor 10.0.4.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/y1/zebra.conf b/tests/topotests/bgp_community_change_update/y1/zebra.conf new file mode 100644 index 0000000..4096f2a --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y1/zebra.conf @@ -0,0 +1,12 @@ +! +interface y1-eth0 + ip address 10.0.2.2/30 +! +interface y1-eth1 + ip address 10.0.3.1/30 +! +interface y1-eth2 + ip address 10.0.4.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y2/bgpd.conf b/tests/topotests/bgp_community_change_update/y2/bgpd.conf new file mode 100644 index 0000000..27f1e81 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.3.1 remote-as internal + neighbor 10.0.3.1 timers 3 10 + neighbor 10.0.5.2 remote-as internal + neighbor 10.0.5.2 timers 3 10 + neighbor 10.0.6.2 remote-as external + neighbor 10.0.6.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.3.1 route-reflector-client + neighbor 10.0.5.2 route-reflector-client + neighbor 10.0.6.2 route-map z1 in + exit-address-family +! +route-map z1 permit 10 + set community 65004:2 +! diff --git a/tests/topotests/bgp_community_change_update/y2/zebra.conf b/tests/topotests/bgp_community_change_update/y2/zebra.conf new file mode 100644 index 0000000..7e9458a --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y2/zebra.conf @@ -0,0 +1,12 @@ +! +interface y2-eth0 + ip address 10.0.3.2/30 +! +interface y2-eth1 + ip address 10.0.5.1/30 +! +interface y2-eth2 + ip address 10.0.6.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/y3/bgpd.conf b/tests/topotests/bgp_community_change_update/y3/bgpd.conf new file mode 100644 index 0000000..8e57284 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y3/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 10.0.4.1 remote-as internal + neighbor 10.0.4.1 timers 3 10 + neighbor 10.0.5.1 remote-as internal + neighbor 10.0.5.1 timers 3 10 + neighbor 10.0.7.2 remote-as external + neighbor 10.0.7.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 10.0.4.1 route-reflector-client + neighbor 10.0.5.1 route-reflector-client + neighbor 10.0.7.2 route-map z1 in + exit-address-family +! +route-map z1 permit 10 + set community 65004:3 +! diff --git a/tests/topotests/bgp_community_change_update/y3/zebra.conf b/tests/topotests/bgp_community_change_update/y3/zebra.conf new file mode 100644 index 0000000..93dd87d --- /dev/null +++ b/tests/topotests/bgp_community_change_update/y3/zebra.conf @@ -0,0 +1,12 @@ +! +interface y3-eth0 + ip address 10.0.4.2/30 +! +interface y3-eth1 + ip address 10.0.5.2/30 +! +interface y3-eth2 + ip address 10.0.7.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_community_change_update/z1/bgpd.conf b/tests/topotests/bgp_community_change_update/z1/bgpd.conf new file mode 100644 index 0000000..2f470f1 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/z1/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65004 + no bgp ebgp-requires-policy + neighbor 10.0.6.1 remote-as external + neighbor 10.0.6.1 timers 3 10 + neighbor 10.0.7.1 remote-as external + neighbor 10.0.7.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_community_change_update/z1/zebra.conf b/tests/topotests/bgp_community_change_update/z1/zebra.conf new file mode 100644 index 0000000..7f6bbb6 --- /dev/null +++ b/tests/topotests/bgp_community_change_update/z1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 192.168.255.254/32 +! +interface z1-eth0 + ip address 10.0.6.2/30 +! +interface z1-eth1 + ip address 10.0.7.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement/__init__.py b/tests/topotests/bgp_conditional_advertisement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_conditional_advertisement/r1/bgpd.conf b/tests/topotests/bgp_conditional_advertisement/r1/bgpd.conf new file mode 100644 index 0000000..293b38c --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r1/bgpd.conf @@ -0,0 +1,31 @@ +! +ip prefix-list CUST seq 5 permit 10.139.224.0/20 +ip prefix-list DEFAULT seq 5 permit 0.0.0.0/0 +ip prefix-list PL1 seq 5 permit 192.0.2.1/32 +! +route-map CUST permit 10 + match ip address prefix-list CUST + set community 64671:501 +! +route-map RM1 permit 10 + match ip address prefix-list PL1 + set community 64952:3008 +! +route-map DEF permit 10 + match ip address prefix-list DEFAULT + set community 64848:3011 65011:200 65013:200 +! +router bgp 1 + bgp log-neighbor-changes + bgp conditional-advertisement timer 5 + no bgp ebgp-requires-policy + neighbor 10.10.10.2 remote-as 2 + ! + address-family ipv4 unicast + network 0.0.0.0/0 route-map DEF + network 192.0.2.1/32 route-map RM1 + network 192.0.2.5/32 + redistribute connected route-map CUST + neighbor 10.10.10.2 soft-reconfiguration inbound + exit-address-family +! diff --git a/tests/topotests/bgp_conditional_advertisement/r1/zebra.conf b/tests/topotests/bgp_conditional_advertisement/r1/zebra.conf new file mode 100644 index 0000000..bb887e4 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r1/zebra.conf @@ -0,0 +1,19 @@ +! +hostname Router1 +! +ip route 0.0.0.0/0 blackhole +ip route 192.0.2.1/32 blackhole +ip route 192.0.2.2/32 blackhole +ip route 192.0.2.3/32 blackhole +ip route 192.0.2.4/32 blackhole +ip route 192.0.2.5/32 blackhole +! +interface r1-eth0 + ip address 10.10.10.1/24 +! +interface lo + ip address 10.139.224.1/20 +! +ip forwarding +ipv6 forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement/r2/bgpd.conf b/tests/topotests/bgp_conditional_advertisement/r2/bgpd.conf new file mode 100644 index 0000000..73f837c --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r2/bgpd.conf @@ -0,0 +1,46 @@ +! +ip prefix-list DEFAULT seq 5 permit 192.0.2.5/32 +ip prefix-list DEFAULT seq 10 permit 192.0.2.1/32 +ip prefix-list EXIST seq 5 permit 10.10.10.10/32 +ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0 +ip prefix-list IP1 seq 5 permit 10.139.224.0/20 +ip prefix-list IP2 seq 5 permit 203.0.113.1/32 +! +bgp community-list standard DC-ROUTES seq 5 permit 64952:3008 +bgp community-list standard DC-ROUTES seq 10 permit 64671:501 +bgp community-list standard DC-ROUTES seq 15 permit 64950:3009 +bgp community-list standard DEFAULT-ROUTE seq 5 permit 65013:200 +! +route-map ADV-MAP-1 permit 10 + match ip address prefix-list IP1 +! +route-map ADV-MAP-1 permit 20 + match community DC-ROUTES +! +route-map ADV-MAP-2 permit 10 + match ip address prefix-list IP2 + set metric 911 +! +route-map EXIST-MAP permit 10 + match community DEFAULT-ROUTE + match ip address prefix-list DEFAULT-ROUTE +! +route-map RMAP-1 deny 10 + match ip address prefix-list IP1 +! +route-map RMAP-2 deny 10 + match ip address prefix-list IP2 +! +router bgp 2 + bgp log-neighbor-changes + bgp conditional-advertisement timer 5 + no bgp ebgp-requires-policy + neighbor 10.10.10.1 remote-as 1 + neighbor 10.10.20.3 remote-as 3 + ! + address-family ipv4 unicast + network 203.0.113.1/32 + neighbor 10.10.10.1 soft-reconfiguration inbound + neighbor 10.10.20.3 soft-reconfiguration inbound + exit-address-family +! diff --git a/tests/topotests/bgp_conditional_advertisement/r2/zebra.conf b/tests/topotests/bgp_conditional_advertisement/r2/zebra.conf new file mode 100644 index 0000000..434ab68 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r2/zebra.conf @@ -0,0 +1,15 @@ +! +hostname Router2 +! +interface r2-eth0 + ip address 10.10.10.2/24 +! +interface r2-eth1 + ip address 10.10.20.2/24 +! +interface lo + ip address 203.0.113.1/32 +! +ip forwarding +ipv6 forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement/r3/bgpd.conf b/tests/topotests/bgp_conditional_advertisement/r3/bgpd.conf new file mode 100644 index 0000000..f389f30 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r3/bgpd.conf @@ -0,0 +1,12 @@ +! +router bgp 3 + bgp log-neighbor-changes + bgp conditional-advertisement timer 5 + no bgp ebgp-requires-policy + neighbor 10.10.20.2 remote-as 2 + ! + address-family ipv4 unicast + neighbor 10.10.20.2 soft-reconfiguration inbound + exit-address-family +! + diff --git a/tests/topotests/bgp_conditional_advertisement/r3/zebra.conf b/tests/topotests/bgp_conditional_advertisement/r3/zebra.conf new file mode 100644 index 0000000..0dadfdb --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/r3/zebra.conf @@ -0,0 +1,12 @@ +! +hostname Router3 +! +interface r3-eth0 + ip address 10.10.20.3/24 +! +interface lo + ip address 198.51.100.1/32 +! +ip forwarding +ipv6 forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement/test_bgp_conditional_advertisement.py b/tests/topotests/bgp_conditional_advertisement/test_bgp_conditional_advertisement.py new file mode 100644 index 0000000..0128c88 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement/test_bgp_conditional_advertisement.py @@ -0,0 +1,1297 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_conditional_advertisement.py +# +# Copyright (c) 2020 by +# Samsung R&D Institute India - Bangalore. +# Madhurilatha Kuruganti +# + +""" +Test BGP conditional advertisement functionality. + + +--------+ +--------+ +--------+ + | | | | | | + | R1 |------------| R2 |------------| R3 | + | | | | | | + +--------+ +--------+ +--------+ + +R2 is DUT and peers with R1 and R3 in default bgp instance. + +Following tests are covered under BGP conditional advertisement functionality. +Conditional advertisement +------------------------- +TC11: R3 BGP convergence, without advertise-map configuration. + All routes are advertised to R3. + +TC21: exist-map routes present in R2's BGP table. + advertise-map routes present in R2's BGP table are advertised to R3. +TC22: exist-map routes not present in R2's BGP table + advertise-map routes present in R2's BGP table are withdrawn from R3. +TC23: advertise-map with exist-map configuration is removed from a peer + send normal BGP update to advertise previously withdrawn routes if any. + +TC31: non-exist-map routes not present in R2's BGP table + advertise-map routes present in R2's BGP table are advertised to R3. +TC32: non-exist-map routes present in R2's BGP table + advertise-map routes present in R2's BGP table are withdrawn from R3. +TC33: advertise-map with non-exist-map configuration is removed from a peer + send normal BGP update to advertisepreviously withdrawn routes if any. + +TC41: non-exist-map route-map configuration removed in R2. + advertise-map routes present in R2's BGP table are advertised to R3. +TC42: exist-map route-map configuration removed in R2 + advertise-map routes present in R2's BGP table are withdrawn from R3. + +Conditional advertisement(received routes) along with Route-map Filter +---------------------------------------------------------------------- +TC51: exist-map routes present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 except advertise-map routes. +TC52: exist-map routes present in R2's BGP table, without route-map filter. + All routes are advertised to R3 including advertise-map routes. +TC53: non-exist-map routes present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 including advertise-map routes. +TC54: non-exist-map routes present in R2's BGP table, without route-map filter. + All routes are advertised to R3 except advertise-map routes. + +TC61: exist-map routes not present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 including advertise-map routes. +TC62: exist-map routes not present in R2's BGP table, without route-map filter. + All routes are advertised to R3 except advertise-map routes. +TC63: non-exist-map routes not present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 except advertise-map routes. +TC64: non-exist-map routes not present in R2's BGP table, without route-map filter. + All routes are advertised to R3 including advertise-map routes. + +Conditional advertisement(attached routes) along with Route-map Filter +----------------------------------------------------------------- +TC71: exist-map routes present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 except advertise-map routes. +TC72: exist-map routes present in R2's BGP table, without route-map filter. + All routes are advertised to R3 including advertise-map routes. +TC73: non-exist-map routes present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 including advertise-map routes. +TC74: non-exist-map routes present in R2's BGP table, without route-map filter. + All routes are advertised to R3 except advertise-map routes. + +TC81: exist-map routes not present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 including advertise-map routes. +TC82: exist-map routes not present in R2's BGP table, without route-map filter. + All routes are advertised to R3 except advertise-map routes. +TC83: non-exist-map routes not present in R2's BGP table, with route-map filter. + All routes are withdrawn from R3 except advertise-map routes. +TC84: non-exist-map routes not present in R2's BGP table, without route-map filter. + All routes are advertised to R3 including advertise-map routes. + +TC91: exist-map routes present in R2's BGP table, with route-map filter and network. + All routes are advertised to R3 including advertise-map routes. +TC92: exist-map routes present in R2's BGP table, with route-map filter and no network. + All routes are advertised to R3 except advertise-map routes. +TC93: non-exist-map routes not present in R2's BGP table, with route-map filter and network. + All routes are advertised to R3 including advertise-map routes. +TC94: non-exist-map routes not present in R2's BGP table, with route-map filter and no network. + All routes are advertised to R3 except advertise-map routes. + +i.e. ++----------------+-------------------------+------------------------+ +| Routes in | exist-map status | advertise-map status | +| BGP table | | | ++----------------+-------------------------+------------------------+ +| Present | Condition matched | Advertise | ++----------------+-------------------------+------------------------+ +| Not Present | Condition not matched | Withdrawn | ++----------------+-------------------------+------------------------+ +| | non-exist-map status | advertise-map status | +| | | | ++----------------+-------------------------+------------------------+ +| Present | Condition matched | Withdrawn | ++----------------+-------------------------+------------------------+ +| Not Present | Condition not matched | Advertise | ++----------------+-------------------------+------------------------+ +Here in this topology, based on the default route presence in R2 and +the configured condition-map (exist-map/non-exist-map) 10.139.224.0/20 +will be either advertised/withdrawn to/from R3. +""" + +import os +import sys +import json +import time +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + r2 = tgen.add_router("r2") + r3 = tgen.add_router("r3") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(r2) + + switch = tgen.add_switch("s2") + switch.add_link(r2) + switch.add_link(r3) + + +def setup_module(mod): + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def all_routes_advertised(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": [{"protocol": "bgp"}], + "192.0.2.1/32": [{"protocol": "bgp"}], + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": [{"protocol": "bgp"}], + "203.0.113.1/32": [{"protocol": "bgp"}], + } + return topotest.json_cmp(output, expected) + + +def all_routes_withdrawn(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": None, + "192.0.2.5/32": None, + "10.139.224.0/20": None, + "203.0.113.1/32": None, + } + return topotest.json_cmp(output, expected) + + +def default_route_withdrawn(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": [{"protocol": "bgp"}], + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": [{"protocol": "bgp"}], + "203.0.113.1/32": [{"protocol": "bgp"}], + } + return topotest.json_cmp(output, expected) + + +# BGP conditional advertisement with route-maps +# EXIST-MAP, ADV-MAP-1 and RMAP-1 +def exist_map_routes_present(router): + return all_routes_advertised(router) + + +def exist_map_routes_not_present(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": None, + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": None, + "203.0.113.1/32": [{"protocol": "bgp"}], + } + return topotest.json_cmp(output, expected) + + +def non_exist_map_routes_present(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": [{"protocol": "bgp"}], + "192.0.2.1/32": None, + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": None, + "203.0.113.1/32": [{"protocol": "bgp"}], + } + return topotest.json_cmp(output, expected) + + +def non_exist_map_routes_not_present(router): + return default_route_withdrawn(router) + + +def exist_map_no_condition_route_map(router): + return non_exist_map_routes_present(router) + + +def non_exist_map_no_condition_route_map(router): + return all_routes_advertised(router) + + +def exist_map_routes_present_rmap_filter(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": [{"protocol": "bgp"}], + "192.0.2.5/32": None, + "10.139.224.0/20": [{"protocol": "bgp"}], + "203.0.113.1/32": None, + } + return topotest.json_cmp(output, expected) + + +def exist_map_routes_present_no_rmap_filter(router): + return all_routes_advertised(router) + + +def non_exist_map_routes_present_rmap_filter(router): + return all_routes_withdrawn(router) + + +def non_exist_map_routes_present_no_rmap_filter(router): + return non_exist_map_routes_present(router) + + +def exist_map_routes_not_present_rmap_filter(router): + return all_routes_withdrawn(router) + + +def exist_map_routes_not_present_no_rmap_filter(router): + return exist_map_routes_not_present(router) + + +def non_exist_map_routes_not_present_rmap_filter(router): + return exist_map_routes_present_rmap_filter(router) + + +def non_exist_map_routes_not_present_no_rmap_filter(router): + return non_exist_map_routes_not_present(router) + + +# BGP conditional advertisement with route-maps +# EXIST-MAP, ADV-MAP-2 and RMAP-2 +def exist_map_routes_not_present_rmap2_filter(router): + return all_routes_withdrawn(router) + + +def exist_map_routes_not_present_no_rmap2_filter(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": [{"protocol": "bgp"}], + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": [{"protocol": "bgp"}], + "203.0.113.1/32": None, + } + return topotest.json_cmp(output, expected) + + +def non_exist_map_routes_not_present_rmap2_filter(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": None, + "192.0.2.1/32": None, + "192.0.2.5/32": None, + "10.139.224.0/20": None, + "203.0.113.1/32": [{"protocol": "bgp", "metric": 911}], + } + return topotest.json_cmp(output, expected) + + +def non_exist_map_routes_not_present_no_rmap2_filter(router): + return non_exist_map_routes_not_present(router) + + +def exist_map_routes_present_rmap2_filter(router): + return non_exist_map_routes_not_present_rmap2_filter(router) + + +def exist_map_routes_present_no_rmap2_filter(router): + return all_routes_advertised(router) + + +def non_exist_map_routes_present_rmap2_filter(router): + return all_routes_withdrawn(router) + + +def non_exist_map_routes_present_no_rmap2_filter(router): + output = json.loads(router.vtysh_cmd("show ip route json")) + expected = { + "0.0.0.0/0": [{"protocol": "bgp"}], + "192.0.2.1/32": [{"protocol": "bgp"}], + "192.0.2.5/32": [{"protocol": "bgp"}], + "10.139.224.0/20": [{"protocol": "bgp"}], + "203.0.113.1/32": None, + } + return topotest.json_cmp(output, expected) + + +def exist_map_routes_present_rmap2_network(router): + return non_exist_map_routes_not_present_rmap2_filter(router) + + +def exist_map_routes_present_rmap2_no_network(router): + return all_routes_withdrawn(router) + + +def non_exist_map_routes_not_present_rmap2_network(router): + return non_exist_map_routes_not_present_rmap2_filter(router) + + +def non_exist_map_routes_not_present_rmap2_no_network(router): + return all_routes_withdrawn(router) + + +passed = "PASSED!!!" +failed = "FAILED!!!" + + +def test_bgp_conditional_advertisement_tc_1_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC11: R3 BGP convergence, without advertise-map configuration. + # All routes are advertised to R3. + test_func = functools.partial(all_routes_advertised, router3) + success, result = topotest.run_and_expect(test_func, None, count=130, wait=1) + + msg = 'TC11: "router3" BGP convergence - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_2_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC21: exist-map routes present in R2's BGP table. + # advertise-map routes present in R2's BGP table are advertised to R3. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 advertise-map ADV-MAP-1 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_routes_present, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC21: exist-map routes present in "router2" BGP table - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_2_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC22: exist-map routes not present in R2's BGP table + # advertise-map routes present in R2's BGP table are withdrawn from R3. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + no network 0.0.0.0/0 route-map DEF + """ + ) + + test_func = functools.partial(exist_map_routes_not_present, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC22: exist-map routes not present in "router2" BGP table - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_2_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC23: advertise-map with exist-map configuration is removed from a peer + # send normal BGP update to advertise previously withdrawn routes if any. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 advertise-map ADV-MAP-1 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(default_route_withdrawn, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC23: advertise-map with exist-map configuration is removed from peer - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_3_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC31: non-exist-map routes not present in R2's BGP table + # advertise-map routes present in R2's BGP table are advertised to R3. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 advertise-map ADV-MAP-1 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(non_exist_map_routes_not_present, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC31: non-exist-map routes not present in "router2" BGP table - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_3_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC32: non-exist-map routes present in R2's BGP table + # advertise-map routes present in R2's BGP table are withdrawn from R3. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + network 0.0.0.0/0 route-map DEF + """ + ) + + test_func = functools.partial(non_exist_map_routes_present, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC32: non-exist-map routes present in "router2" BGP table - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_3_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC33: advertise-map with non-exist-map configuration is removed from a peer + # send normal BGP update to advertisepreviously withdrawn routes if any. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 advertise-map ADV-MAP-1 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(all_routes_advertised, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = ( + "TC33: advertise-map with non-exist-map configuration is removed from a peer - " + ) + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_4_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC41: non-exist-map route-map configuration removed in R2. + # advertise-map routes present in R2's BGP table are advertised to R3. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 advertise-map ADV-MAP-1 non-exist-map EXIST-MAP + no route-map EXIST-MAP permit 10 + """ + ) + + test_func = functools.partial(non_exist_map_no_condition_route_map, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC41: non-exist-map route-map removed in "router2" - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_4_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC42: exist-map route-map configuration removed in R2 + # advertise-map routes present in R2's BGP table are withdrawn from R3. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 advertise-map ADV-MAP-1 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_no_condition_route_map, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = 'TC42: exist-map route-map removed in "router2" - ' + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_5_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC51: exist-map routes present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + route-map EXIST-MAP permit 10 + match community DEFAULT-ROUTE + match ip address prefix-list DEFAULT-ROUTE + ! + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-1 out + """ + ) + + test_func = functools.partial(exist_map_routes_present_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC51: exist-map routes present with route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_5_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC52: exist-map routes present in R2's BGP table, no route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-1 out + """ + ) + + test_func = functools.partial(exist_map_routes_present_no_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC52: exist-map routes present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_5_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC53: non-exist-map routes present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-1 out + neighbor 10.10.20.3 advertise-map ADV-MAP-1 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(non_exist_map_routes_present_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC53: non-exist-map routes present, with route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_5_4(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC54: non-exist-map routes present in R2's BGP table, no route-map filter. + # All routes are advertised to R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-1 out + """ + ) + + test_func = functools.partial(non_exist_map_routes_present_no_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC54: non-exist-map routes present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_6_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC61: exist-map routes not present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 including advertise-map routes. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + no network 0.0.0.0/0 route-map DEF + """ + ) + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-1 out + neighbor 10.10.20.3 advertise-map ADV-MAP-1 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_routes_not_present_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC61: exist-map routes not present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_6_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC62: exist-map routes not present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-1 out + """ + ) + + test_func = functools.partial(exist_map_routes_not_present_no_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC62: exist-map routes not present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_6_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC63: non-exist-map routes not present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-1 out + neighbor 10.10.20.3 advertise-map ADV-MAP-1 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(non_exist_map_routes_not_present_rmap_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC63: non-exist-map routes not present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_6_4(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC64: non-exist-map routes not present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-1 out + """ + ) + + test_func = functools.partial( + non_exist_map_routes_not_present_no_rmap_filter, router3 + ) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC64: non-exist-map routes not present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_7_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC71: exist-map routes present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 except advertise-map routes. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + network 0.0.0.0/0 route-map DEF + """ + ) + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-2 out + neighbor 10.10.20.3 advertise-map ADV-MAP-2 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_routes_present_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC71: exist-map routes present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_7_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC72: exist-map routes present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-2 out + """ + ) + + test_func = functools.partial(exist_map_routes_present_no_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC72: exist-map routes present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_7_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC73: non-exist-map routes present in R2's BGP table, with route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-2 out + neighbor 10.10.20.3 advertise-map ADV-MAP-2 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(non_exist_map_routes_present_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC73: non-exist-map routes present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_7_4(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC74: non-exist-map routes present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-2 out + """ + ) + + test_func = functools.partial(non_exist_map_routes_present_no_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC74: non-exist-map routes present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_8_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC81: exist-map routes not present in R2's BGP table, with route-map filter. + # All routes are withdrawn from R3 including advertise-map routes. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + no network 0.0.0.0/0 route-map DEF + """ + ) + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-2 out + neighbor 10.10.20.3 advertise-map ADV-MAP-2 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_routes_not_present_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC81: exist-map routes not present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_8_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC82: exist-map routes not present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-2 out + """ + ) + + test_func = functools.partial(exist_map_routes_not_present_no_rmap2_filter, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC82: exist-map routes not present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_8_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC83: non-exist-map routes not present in R2's BGP table, with route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-2 out + neighbor 10.10.20.3 advertise-map ADV-MAP-2 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial( + non_exist_map_routes_not_present_rmap2_filter, router3 + ) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC83: non-exist-map routes not present, route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_8_4(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC84: non-exist-map routes not present in R2's BGP table, without route-map filter. + # All routes are advertised to R3 including advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no neighbor 10.10.20.3 route-map RMAP-2 out + """ + ) + + test_func = functools.partial( + non_exist_map_routes_not_present_no_rmap2_filter, router3 + ) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC84: non-exist-map routes not present, no route-map filter - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_9_1(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC91: exist-map routes present in R2's BGP table, with route-map filter and network. + # All routes are advertised to R3 including advertise-map routes. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + network 0.0.0.0/0 route-map DEF + """ + ) + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + neighbor 10.10.20.3 route-map RMAP-2 out + neighbor 10.10.20.3 advertise-map ADV-MAP-2 exist-map EXIST-MAP + """ + ) + + test_func = functools.partial(exist_map_routes_present_rmap2_network, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC91: exist-map routes present, route-map filter and network - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_9_2(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC92: exist-map routes present in R2's BGP table, with route-map filter and no network. + # All routes are advertised to R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no network 203.0.113.1/32 + """ + ) + + test_func = functools.partial(exist_map_routes_present_rmap2_no_network, router3) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC92: exist-map routes present, route-map filter and no network - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_9_3(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC93: non-exist-map routes not present in R2's BGP table, with route-map filter and network. + # All routes are advertised to R3 including advertise-map routes. + router1.vtysh_cmd( + """ + configure terminal + router bgp 1 + address-family ipv4 unicast + no network 0.0.0.0/0 route-map DEF + """ + ) + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + network 203.0.113.1/32 + neighbor 10.10.20.3 advertise-map ADV-MAP-2 non-exist-map EXIST-MAP + """ + ) + + test_func = functools.partial( + non_exist_map_routes_not_present_rmap2_network, router3 + ) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC93: non-exist-map routes not present, route-map filter and network - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_bgp_conditional_advertisement_tc_9_4(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # TC94: non-exist-map routes not present in R2's BGP table, with route-map filter and no network. + # All routes are advertised to R3 except advertise-map routes. + router2.vtysh_cmd( + """ + configure terminal + router bgp 2 + address-family ipv4 unicast + no network 203.0.113.1/32 + """ + ) + + test_func = functools.partial( + non_exist_map_routes_not_present_rmap2_no_network, router3 + ) + success, result = topotest.run_and_expect(test_func, None, count=90, wait=1) + + msg = "TC94: non-exist-map routes not present, route-map filter and no network - " + assert result is None, msg + failed + + logger.info(msg + passed) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_conditional_advertisement_static_route/__init__.py b/tests/topotests/bgp_conditional_advertisement_static_route/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_conditional_advertisement_static_route/r1/frr.conf b/tests/topotests/bgp_conditional_advertisement_static_route/r1/frr.conf new file mode 100644 index 0000000..3e51337 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_static_route/r1/frr.conf @@ -0,0 +1,10 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as internal + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 +! diff --git a/tests/topotests/bgp_conditional_advertisement_static_route/r2/frr.conf b/tests/topotests/bgp_conditional_advertisement_static_route/r2/frr.conf new file mode 100644 index 0000000..3ced934 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_static_route/r2/frr.conf @@ -0,0 +1,40 @@ +! +!debug bgp conditional-advertisement +!debug bgp updates +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! +router bgp 65000 + no bgp ebgp-requires-policy + bgp conditional-advertisement timer 5 + neighbor 192.168.1.1 remote-as internal + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.2.1 remote-as internal + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + redistribute static + neighbor 192.168.1.1 advertise-map advertise-map exist-map exist-map + neighbor 192.168.1.1 route-map deny-all out + exit-address-family +! +ip route 10.10.10.1/32 r2-eth0 +ip route 10.10.10.2/32 r2-eth0 +! +ip prefix-list default seq 5 permit 0.0.0.0/0 +ip prefix-list advertise seq 5 permit 10.10.10.1/32 +! +route-map deny-all deny 10 +! +route-map exist-map permit 10 + match ip address prefix-list default +! +route-map advertise-map permit 10 + match ip address prefix-list advertise + set community 65000:1 +! diff --git a/tests/topotests/bgp_conditional_advertisement_static_route/r3/frr.conf b/tests/topotests/bgp_conditional_advertisement_static_route/r3/frr.conf new file mode 100644 index 0000000..a24a2cb --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_static_route/r3/frr.conf @@ -0,0 +1,19 @@ +! +int lo + ip address 10.10.10.1/32 + ip address 10.10.10.2/32 +! +int r3-eth0 + ip address 192.168.2.1/24 +! +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.2.2 remote-as internal + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.2.2 default-originate + exit-address-family +! diff --git a/tests/topotests/bgp_conditional_advertisement_static_route/test_bgp_conditional_advertisement_static_route.py b/tests/topotests/bgp_conditional_advertisement_static_route/test_bgp_conditional_advertisement_static_route.py new file mode 100644 index 0000000..e9114bd --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_static_route/test_bgp_conditional_advertisement_static_route.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if static route with BGP conditional advertisement works correctly +if we modify the prefix-lists. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2"), "s2": ("r2", "r3")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_conditional_advertisements_static_route(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.1/32": { + "valid": True, + } + }, + "totalPrefixCounter": 1, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step("Append prefix-list to advertise 10.10.10.2/32") + + r2.vtysh_cmd( + """ + configure terminal + ip prefix-list advertise seq 10 permit 10.10.10.2/32 + """ + ) + + def _bgp_check_advertised_after_update(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.1/32": { + "valid": True, + }, + "10.10.10.2/32": { + "valid": True, + }, + }, + "totalPrefixCounter": 2, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_advertised_after_update, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.2/32 is not advertised after prefix-list update" + + def _bgp_check_received_routes(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.1/32 json")) + expected = { + "paths": [ + { + "community": { + "string": "65000:1", + } + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_received_routes, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.1/32 does not have 65000:1 community attached" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/__init__.py b/tests/topotests/bgp_conditional_advertisement_track_peer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r1/bgpd.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r1/bgpd.conf new file mode 100644 index 0000000..1e98f4e --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r1/bgpd.conf @@ -0,0 +1,17 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.1.2 route-map r2 in + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.31/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set as-path replace 65003 +route-map r2 permit 20 + set as-path replace any +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r1/zebra.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r1/zebra.conf new file mode 100644 index 0000000..acf120b --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r2/bgpd.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/bgpd.conf new file mode 100644 index 0000000..66c3dc5 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/bgpd.conf @@ -0,0 +1,28 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + bgp conditional-advertisement timer 5 + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 + address-family ipv4 unicast + redistribute static + neighbor 192.168.1.1 advertise-map advertise exist-map exist + neighbor 192.168.1.1 route-map deny-all out + neighbor 192.168.2.1 route-map exist in + exit-address-family +! +ip prefix-list exist seq 5 permit 172.16.255.3/32 +ip prefix-list advertise seq 5 permit 172.16.255.2/32 +! +route-map advertise permit 10 + match ip address prefix-list advertise +exit +! +route-map exist permit 10 + match ip address prefix-list exist +exit +! +route-map deny-all deny 10 +exit diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r2/staticd.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/staticd.conf new file mode 100644 index 0000000..6c013e2 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/staticd.conf @@ -0,0 +1,3 @@ +! +ip route 172.16.255.2/32 r2-eth1 +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r2/zebra.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/zebra.conf new file mode 100644 index 0000000..f229954 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r3/bgpd.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r3/bgpd.conf new file mode 100644 index 0000000..5058d40 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r3/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 + neighbor 192.168.2.2 shutdown + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/r3/zebra.conf b/tests/topotests/bgp_conditional_advertisement_track_peer/r3/zebra.conf new file mode 100644 index 0000000..268163e --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/r3/zebra.conf @@ -0,0 +1,9 @@ +! +int lo + ip address 172.16.255.3/32 +! +interface r3-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_conditional_advertisement_track_peer/test_bgp_conditional_advertisement_track_peer.py b/tests/topotests/bgp_conditional_advertisement_track_peer/test_bgp_conditional_advertisement_track_peer.py new file mode 100644 index 0000000..e763072 --- /dev/null +++ b/tests/topotests/bgp_conditional_advertisement_track_peer/test_bgp_conditional_advertisement_track_peer.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Conditionally advertise 172.16.255.2/32 to r1, only if 172.16.255.3/32 +is received from r3. + +Also, withdraw if 172.16.255.3/32 disappears. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import ( + step, +) + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_conditional_advertisement_track_peer(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": {"172.16.255.2/32": None}, + "totalPrefixCounter": 0, + "filteredPrefixCounter": 0, + } + return topotest.json_cmp(output, expected) + + # Verify if R2 does not send any routes to R1 + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "R2 SHOULD not send any routes to R1" + + step("Enable session between R2 and R3") + r3.vtysh_cmd( + """ + configure terminal + router bgp + no neighbor 192.168.2.2 shutdown + """ + ) + + def _bgp_check_conditional_static_routes_from_r2(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.2/32": [{"valid": True, "nexthops": [{"hostname": "r2"}]}] + } + } + return topotest.json_cmp(output, expected) + + # Verify if R1 received 172.16.255.2/32 from R2 + test_func = functools.partial(_bgp_check_conditional_static_routes_from_r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "R1 SHOULD receive 172.16.255.2/32 from R2" + + step("Disable session between R2 and R3 again") + r3.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.2.2 shutdown + """ + ) + + # Verify if R2 is not sending any routes to R1 again + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "R2 SHOULD not send any routes to R1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_confed1/__init__.py b/tests/topotests/bgp_confed1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_confed1/r1/bgp_ipv4_unicast.json b/tests/topotests/bgp_confed1/r1/bgp_ipv4_unicast.json new file mode 100644 index 0000000..d3988eb --- /dev/null +++ b/tests/topotests/bgp_confed1/r1/bgp_ipv4_unicast.json @@ -0,0 +1,63 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"203.0.113.1", + "defaultLocPrf":100, + "localAS":100, + "routes":{"203.0.113.0/28":[ + { + "network":"203.0.113.0\/28", + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.16/28":[ + { + "network":"203.0.113.16\/28", + "peerId":"192.0.2.2", + "path":"300", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.2", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.32/28":[ + { + "network":"203.0.113.32\/28", + "peerId":"192.0.2.2", + "path":"300", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.2", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.48/28":[ + { + "network":"203.0.113.48\/28", + "peerId":"192.0.2.2", + "path":"300 400", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.2", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_confed1/r1/bgp_summary.json b/tests/topotests/bgp_confed1/r1/bgp_summary.json new file mode 100644 index 0000000..f999021 --- /dev/null +++ b/tests/topotests/bgp_confed1/r1/bgp_summary.json @@ -0,0 +1,13 @@ +{ + "ipv4Unicast":{ + "routerId":"203.0.113.1", + "as":100, + "peers":{ + "192.0.2.2":{ + "remoteAs": 300, + "state": "Established", + "peerState":"OK" + } + } + } +} diff --git a/tests/topotests/bgp_confed1/r1/bgpd.conf b/tests/topotests/bgp_confed1/r1/bgpd.conf new file mode 100644 index 0000000..107d2ad --- /dev/null +++ b/tests/topotests/bgp_confed1/r1/bgpd.conf @@ -0,0 +1,13 @@ +!debug bgp neighbor-events +!debug bgp nht +!debug bgp updates in +!debug bgp updates out +! +router bgp 100 + no bgp ebgp-requires-policy +! + neighbor 192.0.2.2 remote-as 300 + address-family ipv4 unicast + network 203.0.113.0/28 + exit-address-family +! diff --git a/tests/topotests/bgp_confed1/r1/isisd.conf b/tests/topotests/bgp_confed1/r1/isisd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_confed1/r1/zebra.conf b/tests/topotests/bgp_confed1/r1/zebra.conf new file mode 100644 index 0000000..5f76e74 --- /dev/null +++ b/tests/topotests/bgp_confed1/r1/zebra.conf @@ -0,0 +1,6 @@ +interface r1-eth0 + ip address 192.0.2.1/28 +! +interface lo + ip address 203.0.113.1/28 +! diff --git a/tests/topotests/bgp_confed1/r2/bgp_ipv4_unicast.json b/tests/topotests/bgp_confed1/r2/bgp_ipv4_unicast.json new file mode 100644 index 0000000..b4a0946 --- /dev/null +++ b/tests/topotests/bgp_confed1/r2/bgp_ipv4_unicast.json @@ -0,0 +1,63 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"203.0.113.17", + "defaultLocPrf":100, + "localAS":200, + "routes":{"203.0.113.0/28": [ + { + "network":"203.0.113.0\/28", + "peerId":"192.0.2.1", + "path":"100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.1", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.16/28":[ + { + "network":"203.0.113.16\/28", + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.32/28":[ + { + "network":"203.0.113.32\/28", + "peerId":"192.0.2.18", + "path":"(300)", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.18", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.48/28":[ + { + "network":"203.0.113.48\/28", + "peerId":"192.0.2.18", + "path":"(300) 400", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.50", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_confed1/r2/bgp_summary.json b/tests/topotests/bgp_confed1/r2/bgp_summary.json new file mode 100644 index 0000000..c2690a1 --- /dev/null +++ b/tests/topotests/bgp_confed1/r2/bgp_summary.json @@ -0,0 +1,18 @@ +{ + "ipv4Unicast":{ + "routerId":"203.0.113.17", + "as":200, + "peers":{ + "192.0.2.1":{ + "remoteAs":100, + "state":"Established", + "peerState":"OK" + }, + "192.0.2.18":{ + "remoteAs":300, + "state":"Established", + "peerState":"OK" + } + } + } +} diff --git a/tests/topotests/bgp_confed1/r2/bgpd.conf b/tests/topotests/bgp_confed1/r2/bgpd.conf new file mode 100644 index 0000000..ba2da41 --- /dev/null +++ b/tests/topotests/bgp_confed1/r2/bgpd.conf @@ -0,0 +1,19 @@ +!debug bgp neighbor-events +!debug bgp nht +!debug bgp updates in +!debug bgp updates out +! +router bgp 200 + bgp confederation identifier 300 + bgp confederation peers 300 + neighbor 192.0.2.1 remote-as 100 + neighbor 192.0.2.18 remote-as 300 + ! + address-family ipv4 unicast + network 203.0.113.16/28 + neighbor 192.0.2.1 route-map any in + neighbor 192.0.2.1 route-map any out + neighbor 192.0.2.18 default-originate + exit-address-family +! +route-map any permit 10 diff --git a/tests/topotests/bgp_confed1/r2/isisd.conf b/tests/topotests/bgp_confed1/r2/isisd.conf new file mode 100644 index 0000000..135bb00 --- /dev/null +++ b/tests/topotests/bgp_confed1/r2/isisd.conf @@ -0,0 +1,8 @@ +interface r2-eth1 + ip router isis 1 + isis circuit-type level-2-only + +router isis 1 + is-type level-2-only + net 49.0001.0002.0002.0002.00 + redistribute ipv4 connected level-2 diff --git a/tests/topotests/bgp_confed1/r2/zebra.conf b/tests/topotests/bgp_confed1/r2/zebra.conf new file mode 100644 index 0000000..85ebe9e --- /dev/null +++ b/tests/topotests/bgp_confed1/r2/zebra.conf @@ -0,0 +1,9 @@ +interface r2-eth0 + ip address 192.0.2.2/28 +! +interface r2-eth1 + ip address 192.0.2.17/28 +! +interface lo + ip address 203.0.113.17/28 +! diff --git a/tests/topotests/bgp_confed1/r3/bgp_ipv4_unicast.json b/tests/topotests/bgp_confed1/r3/bgp_ipv4_unicast.json new file mode 100644 index 0000000..a263a9f --- /dev/null +++ b/tests/topotests/bgp_confed1/r3/bgp_ipv4_unicast.json @@ -0,0 +1,77 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"203.0.113.33", + "defaultLocPrf":100, + "localAS":300, + "routes":{"0.0.0.0/0":[ + { + "network":"0.0.0.0\/0", + "peerId":"192.0.2.17", + "path":"(200)", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.17", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.0/28":[ + { + "network":"203.0.113.0\/28", + "peerId":"192.0.2.17", + "path":"(200) 100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.1", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.16/28":[ + { + "network":"203.0.113.16\/28", + "peerId":"192.0.2.17", + "path":"(200)", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.17", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.32/28":[ + { + "network":"203.0.113.32\/28", + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.48/28":[ + { + "network":"203.0.113.48\/28", + "peerId":"192.0.2.50", + "path":"400", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.50", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_confed1/r3/bgp_summary.json b/tests/topotests/bgp_confed1/r3/bgp_summary.json new file mode 100644 index 0000000..0cc0d53 --- /dev/null +++ b/tests/topotests/bgp_confed1/r3/bgp_summary.json @@ -0,0 +1,18 @@ +{ + "ipv4Unicast":{ + "routerId":"203.0.113.33", + "as":300, + "peers":{ + "192.0.2.17":{ + "remoteAs":200, + "state":"Established", + "peerState":"OK" + }, + "192.0.2.50":{ + "remoteAs":400, + "state":"Established", + "peerState":"OK" + } + } + } +} diff --git a/tests/topotests/bgp_confed1/r3/bgpd.conf b/tests/topotests/bgp_confed1/r3/bgpd.conf new file mode 100644 index 0000000..74d5fd6 --- /dev/null +++ b/tests/topotests/bgp_confed1/r3/bgpd.conf @@ -0,0 +1,17 @@ +!debug bgp neighbor-events +!debug bgp nht +!debug bgp updates in +!debug bgp updates out +! +router bgp 300 + no bgp ebgp-requires-policy + bgp confederation identifier 300 + bgp confederation peers 200 + neighbor 192.0.2.17 remote-as 200 + neighbor 192.0.2.50 remote-as 400 +! + address-family ipv4 unicast + network 203.0.113.32/28 + exit-address-family +! + diff --git a/tests/topotests/bgp_confed1/r3/isisd.conf b/tests/topotests/bgp_confed1/r3/isisd.conf new file mode 100644 index 0000000..a0b1200 --- /dev/null +++ b/tests/topotests/bgp_confed1/r3/isisd.conf @@ -0,0 +1,8 @@ +interface r3-eth1 + ip router isis 1 + isis circuit-type level-2-only + +router isis 1 + is-type level-2-only + net 49.0001.0003.0003.0003.00 + redistribute ipv4 connected level-2 diff --git a/tests/topotests/bgp_confed1/r3/zebra.conf b/tests/topotests/bgp_confed1/r3/zebra.conf new file mode 100644 index 0000000..555c8ed --- /dev/null +++ b/tests/topotests/bgp_confed1/r3/zebra.conf @@ -0,0 +1,10 @@ +! +interface r3-eth0 + ip address 192.0.2.49/28 +! +interface r3-eth1 + ip address 192.0.2.18/28 +! +interface lo + ip address 203.0.113.33/28 +! diff --git a/tests/topotests/bgp_confed1/r4/bgp_ipv4_unicast.json b/tests/topotests/bgp_confed1/r4/bgp_ipv4_unicast.json new file mode 100644 index 0000000..8a1f0b6 --- /dev/null +++ b/tests/topotests/bgp_confed1/r4/bgp_ipv4_unicast.json @@ -0,0 +1,77 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"203.0.113.49", + "defaultLocPrf":100, + "localAS":400, + "routes":{"0.0.0.0/0":[ + { + "network":"0.0.0.0\/0", + "peerId":"192.0.2.49", + "path":"300", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.49", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.0/28":[ + { + "network":"203.0.113.0\/28", + "peerId":"192.0.2.49", + "path":"300 100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.49", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.16/28":[ + { + "network":"203.0.113.16\/28", + "peerId":"192.0.2.49", + "path":"300", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.49", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.32/28":[ + { + "network":"203.0.113.32\/28", + "peerId":"192.0.2.49", + "path":"300", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.0.2.49", + "afi":"ipv4", + "used":true + } + ] + } +],"203.0.113.48/28":[ + { + "network":"203.0.113.48\/28", + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_confed1/r4/bgp_summary.json b/tests/topotests/bgp_confed1/r4/bgp_summary.json new file mode 100644 index 0000000..11a0c45 --- /dev/null +++ b/tests/topotests/bgp_confed1/r4/bgp_summary.json @@ -0,0 +1,13 @@ +{ + "ipv4Unicast":{ + "routerId":"203.0.113.49", + "as":400, + "peers":{ + "192.0.2.49":{ + "remoteAs":300, + "state":"Established", + "peerState":"OK" + } + } + } +} diff --git a/tests/topotests/bgp_confed1/r4/bgpd.conf b/tests/topotests/bgp_confed1/r4/bgpd.conf new file mode 100644 index 0000000..89a85e5 --- /dev/null +++ b/tests/topotests/bgp_confed1/r4/bgpd.conf @@ -0,0 +1,14 @@ +!debug bgp neighbor-events +!debug bgp nht +!debug bgp updates in +!debug bgp updates out +! +router bgp 400 + no bgp ebgp-requires-policy + bgp disable-ebgp-connected-route-check +! + neighbor 192.0.2.49 remote-as 300 + address-family ipv4 unicast + network 203.0.113.48/28 + exit-address-family +! diff --git a/tests/topotests/bgp_confed1/r4/isisd.conf b/tests/topotests/bgp_confed1/r4/isisd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_confed1/r4/zebra.conf b/tests/topotests/bgp_confed1/r4/zebra.conf new file mode 100644 index 0000000..28c448e --- /dev/null +++ b/tests/topotests/bgp_confed1/r4/zebra.conf @@ -0,0 +1,6 @@ +interface r4-eth0 + ip address 192.0.2.50/28 +! +interface lo + ip address 203.0.113.49/28 +! diff --git a/tests/topotests/bgp_confed1/test_bgp_confed1.py b/tests/topotests/bgp_confed1/test_bgp_confed1.py new file mode 100644 index 0000000..7b37f4f --- /dev/null +++ b/tests/topotests/bgp_confed1/test_bgp_confed1.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_confed1.py +# +# Copyright 2022 6WIND S.A. +# + +""" +test_bgp_confed1.py: Test the FRR BGP confederations with AS member +same as confederation Id, verify BGP prefixes and path distribution +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + "Assert that BGP is converging." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers to go up") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_summary.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=125, wait=2.0) + assertmsg = "{}: bgp did not converge".format(router.name) + assert res is None, assertmsg + + +def test_bgp_confed_ipv4_unicast(): + "Assert that BGP is exchanging BGP route." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp peers exchanging UPDATES") + + for router in tgen.routers().values(): + ref_file = "{}/{}/bgp_ipv4_unicast.json".format(CWD, router.name) + expected = json.loads(open(ref_file).read()) + test_func = partial( + topotest.router_json_cmp, router, "show bgp ipv4 unicast json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=40, wait=2.5) + assertmsg = "{}: BGP UPDATE exchange failure".format(router.name) + assert res is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_confederation_astype/__init__.py b/tests/topotests/bgp_confederation_astype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_confederation_astype/r1/bgpd.conf b/tests/topotests/bgp_confederation_astype/r1/bgpd.conf new file mode 100644 index 0000000..1859a1b --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65001 + no bgp ebgp-requires-policy + bgp confederation identifier 65300 + bgp confederation peers 65002 65003 + neighbor fabric peer-group + neighbor fabric remote-as external + neighbor 192.168.1.2 peer-group fabric + neighbor 192.168.2.2 remote-as external + address-family ipv4 unicast + neighbor fabric activate + exit-address-family +! diff --git a/tests/topotests/bgp_confederation_astype/r1/zebra.conf b/tests/topotests/bgp_confederation_astype/r1/zebra.conf new file mode 100644 index 0000000..608a241 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r1/zebra.conf @@ -0,0 +1,7 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +int r1-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_confederation_astype/r2/bgpd.conf b/tests/topotests/bgp_confederation_astype/r2/bgpd.conf new file mode 100644 index 0000000..697af97 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r2/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65002 + no bgp ebgp-requires-policy + no bgp network import-check + bgp confederation identifier 65300 + bgp confederation peers 65001 + neighbor fabric peer-group + neighbor fabric remote-as external + neighbor 192.168.1.1 peer-group fabric + address-family ipv4 unicast + network 172.16.255.254/32 + neighbor fabric activate + exit-address-family +! diff --git a/tests/topotests/bgp_confederation_astype/r2/zebra.conf b/tests/topotests/bgp_confederation_astype/r2/zebra.conf new file mode 100644 index 0000000..cffe827 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_confederation_astype/r3/bgpd.conf b/tests/topotests/bgp_confederation_astype/r3/bgpd.conf new file mode 100644 index 0000000..c1f93f6 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r3/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65003 + no bgp ebgp-requires-policy + no bgp network import-check + bgp confederation identifier 65300 + bgp confederation peers 65001 + neighbor 192.168.2.1 remote-as external + address-family ipv4 unicast + network 172.16.255.254/32 + exit-address-family +! diff --git a/tests/topotests/bgp_confederation_astype/r3/zebra.conf b/tests/topotests/bgp_confederation_astype/r3/zebra.conf new file mode 100644 index 0000000..e5a37c9 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/r3/zebra.conf @@ -0,0 +1,4 @@ +! +int r3-eth0 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_confederation_astype/test_bgp_confederation_astype.py b/tests/topotests/bgp_confederation_astype/test_bgp_confederation_astype.py new file mode 100644 index 0000000..7bc0050 --- /dev/null +++ b/tests/topotests/bgp_confederation_astype/test_bgp_confederation_astype.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if BGP confederation works properly when using +remote-as internal/external. + +Also, check if the same works with peer-groups as well. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2"), "s2": ("r1", "r3")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_confederation_astype(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peerCount": 2, + "peers": { + "192.168.1.2": { + "hostname": "r2", + "remoteAs": 65002, + "localAs": 65001, + "pfxRcd": 1, + "state": "Established", + }, + "192.168.2.2": { + "hostname": "r3", + "remoteAs": 65003, + "localAs": 65001, + "pfxRcd": 1, + "state": "Established", + }, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge" + + def _bgp_check_neighbors(): + output = json.loads(r1.vtysh_cmd("show bgp neighbors json")) + expected = { + "192.168.1.2": { + "nbrCommonAdmin": True, + "nbrConfedExternalLink": True, + "hostname": "r2", + }, + "192.168.2.2": { + "nbrCommonAdmin": True, + "nbrConfedExternalLink": True, + "hostname": "r3", + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_neighbors) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't see neighbors to be in BGP confederation" + + def _bgp_check_routes(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.254/32": [ + { + "valid": True, + "pathFrom": "external", + "path": "(65003)", + }, + { + "valid": True, + "pathFrom": "external", + "path": "(65002)", + }, + ] + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't see routes to be in BGP confederation" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dampening_per_peer/__init__.py b/tests/topotests/bgp_dampening_per_peer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_dampening_per_peer/r1/frr.conf b/tests/topotests/bgp_dampening_per_peer/r1/frr.conf new file mode 100644 index 0000000..4589955 --- /dev/null +++ b/tests/topotests/bgp_dampening_per_peer/r1/frr.conf @@ -0,0 +1,15 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 capability dynamic + ! + address-family ipv4 unicast + neighbor 192.168.1.2 dampening 1 1 1 1 + exit-address-family +! diff --git a/tests/topotests/bgp_dampening_per_peer/r2/frr.conf b/tests/topotests/bgp_dampening_per_peer/r2/frr.conf new file mode 100644 index 0000000..d68d13d --- /dev/null +++ b/tests/topotests/bgp_dampening_per_peer/r2/frr.conf @@ -0,0 +1,17 @@ +! +int lo + ip address 10.10.10.10/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_dampening_per_peer/test_bgp_dampening_per_peer.py b/tests/topotests/bgp_dampening_per_peer/test_bgp_dampening_per_peer.py new file mode 100644 index 0000000..2066d84 --- /dev/null +++ b/tests/topotests/bgp_dampening_per_peer/test_bgp_dampening_per_peer.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dampening_per_peer(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _converge(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) + expected = { + "paths": [ + { + "valid": True, + "nexthops": [ + { + "hostname": "r2", + "accessible": True, + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + #### + # Withdraw 10.10.10.10/32, and check if it's flagged as history. + #### + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + no redistribute connected + """ + ) + + def _check_bgp_dampening_history(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) + expected = { + "paths": [ + { + "dampeningHistoryEntry": True, + "nexthops": [ + { + "hostname": "r2", + "accessible": True, + } + ], + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _check_bgp_dampening_history, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.10/32 is not flagged as history entry" + + #### + # Reannounce 10.10.10.10/32, and check if it's flagged as dampened. + #### + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + redistribute connected + """ + ) + + def _check_bgp_dampening_dampened(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) + expected = { + "paths": [ + { + "valid": True, + "dampeningSuppressed": True, + "nexthops": [ + { + "hostname": "r2", + "accessible": True, + } + ], + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _check_bgp_dampening_dampened, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.10/32 is not flagged as dampened entry" + + #### + # Check if the route becomes non-dampened again after some time. + #### + def _check_bgp_dampening_undampened(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) + expected = { + "paths": [ + { + "valid": True, + "dampeningHistoryEntry": None, + "dampeningSuppressed": None, + "nexthops": [ + { + "hostname": "r2", + "accessible": True, + } + ], + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _check_bgp_dampening_undampened, + ) + _, result = topotest.run_and_expect(test_func, None, count=120, wait=10) + assert result is None, "10.10.10.10/32 is flagged as history/dampened" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_afi_safi/__init__.py b/tests/topotests/bgp_default_afi_safi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_afi_safi/r1/bgpd.conf b/tests/topotests/bgp_default_afi_safi/r1/bgpd.conf new file mode 100644 index 0000000..bf39152 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r1/bgpd.conf @@ -0,0 +1,3 @@ +! +router bgp 65001 +! diff --git a/tests/topotests/bgp_default_afi_safi/r1/zebra.conf b/tests/topotests/bgp_default_afi_safi/r1/zebra.conf new file mode 100644 index 0000000..6977651 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! + diff --git a/tests/topotests/bgp_default_afi_safi/r2/bgpd.conf b/tests/topotests/bgp_default_afi_safi/r2/bgpd.conf new file mode 100644 index 0000000..abbd1b8 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r2/bgpd.conf @@ -0,0 +1,5 @@ +! +router bgp 65001 + no bgp default ipv4-unicast + bgp default ipv6-unicast +! diff --git a/tests/topotests/bgp_default_afi_safi/r2/zebra.conf b/tests/topotests/bgp_default_afi_safi/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_afi_safi/r3/bgpd.conf b/tests/topotests/bgp_default_afi_safi/r3/bgpd.conf new file mode 100644 index 0000000..f3ec3f0 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r3/bgpd.conf @@ -0,0 +1,5 @@ +! +router bgp 65001 + no bgp default ipv4-unicast + bgp default l2vpn-evpn +! diff --git a/tests/topotests/bgp_default_afi_safi/r3/zebra.conf b/tests/topotests/bgp_default_afi_safi/r3/zebra.conf new file mode 100644 index 0000000..e9fdfb7 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.255.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_afi_safi/r4/bgpd.conf b/tests/topotests/bgp_default_afi_safi/r4/bgpd.conf new file mode 100644 index 0000000..8a6af55 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r4/bgpd.conf @@ -0,0 +1,5 @@ +! +router bgp 65001 + bgp default ipv6-unicast + bgp default l2vpn-evpn +! diff --git a/tests/topotests/bgp_default_afi_safi/r4/zebra.conf b/tests/topotests/bgp_default_afi_safi/r4/zebra.conf new file mode 100644 index 0000000..e9fdfb7 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.255.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_afi_safi/test_bgp-default-afi-safi.py b/tests/topotests/bgp_default_afi_safi/test_bgp-default-afi-safi.py new file mode 100644 index 0000000..05e0748 --- /dev/null +++ b/tests/topotests/bgp_default_afi_safi/test_bgp-default-afi-safi.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if `bgp default ipv4-unicast`, `bgp default ipv6-unicast` +and `bgp default l2vpn-evpn` commands work as expected. + +STEP 1: 'Check if neighbor 192.168.255.254 is enabled for ipv4 address-family only' +STEP 2: 'Check if neighbor 192.168.255.254 is enabled for ipv6 address-family only' +STEP 3: 'Check if neighbor 192.168.255.254 is enabled for l2vpn evpn address-family only' +STEP 4: 'Check if neighbor 192.168.255.254 is enabled for ipv4/ipv6 unicast and l2vpn evpn address-families' +""" + +import os +import sys +import json +import pytest + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_ipv4_ipv6_unicast(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check if neighbor 192.168.255.254 is enabled for ipv4 address-family only") + + def _bgp_neighbor_ipv4_af_only(): + tgen.gears["r1"].vtysh_cmd( + "conf t\nrouter bgp\nneighbor 192.168.255.254 remote-as external" + ) + + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp summary json")) + + if len(output.keys()) == 1 and "ipv4Unicast" in output: + return True + return False + + assert _bgp_neighbor_ipv4_af_only() == True + + step("Check if neighbor 192.168.255.254 is enabled for ipv6 address-family only") + + def _bgp_neighbor_ipv6_af_only(): + tgen.gears["r2"].vtysh_cmd( + "conf t\nrouter bgp\nneighbor 192.168.255.254 remote-as external" + ) + + output = json.loads(tgen.gears["r2"].vtysh_cmd("show bgp summary json")) + + if len(output.keys()) == 1 and "ipv6Unicast" in output: + return True + return False + + assert _bgp_neighbor_ipv6_af_only() == True + + step("Check if neighbor 192.168.255.254 is enabled for evpn address-family only") + + def _bgp_neighbor_evpn_af_only(): + tgen.gears["r3"].vtysh_cmd( + "conf t\nrouter bgp\nneighbor 192.168.255.254 remote-as external" + ) + + output = json.loads(tgen.gears["r3"].vtysh_cmd("show bgp summary json")) + + if len(output.keys()) == 1 and "l2VpnEvpn" in output: + return True + return False + + assert _bgp_neighbor_evpn_af_only() == True + + step( + "Check if neighbor 192.168.255.254 is enabled for ipv4/ipv6 unicast and evpn address-families" + ) + + def _bgp_neighbor_ipv4_ipv6_and_evpn_af(): + tgen.gears["r4"].vtysh_cmd( + "conf t\nrouter bgp\nneighbor 192.168.255.254 remote-as external" + ) + + output = json.loads(tgen.gears["r4"].vtysh_cmd("show bgp summary json")) + + if ( + len(output.keys()) == 3 + and "ipv4Unicast" in output + and "ipv6Unicast" in output + and "l2VpnEvpn" in output + ): + return True + return False + + assert _bgp_neighbor_ipv4_ipv6_and_evpn_af() == True + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/bgp_default_orginate_vrf.json b/tests/topotests/bgp_default_originate/bgp_default_orginate_vrf.json new file mode 100644 index 0000000..1a3d66b --- /dev/null +++ b/tests/topotests/bgp_default_originate/bgp_default_orginate_vrf.json @@ -0,0 +1,325 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "192.168.0.0", + "ipv4mask": 3024, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r0": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r0": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r0": {} + } + } + } + } + } + } + } + }, + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [ + { + "local_as": "1000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": {} + } + }, + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": {} + } + }, + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + ] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "2000", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + }, + { + "local_as": "1000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback", + "vrf": "RED" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "3000", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + }, + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [ + { + "local_as": "500", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_default_originate/bgp_default_originate_2links.json b/tests/topotests/bgp_default_originate/bgp_default_originate_2links.json new file mode 100644 index 0000000..9e98235 --- /dev/null +++ b/tests/topotests/bgp_default_originate/bgp_default_originate_2links.json @@ -0,0 +1,136 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "192.168.0.0", + "ipv4mask": 3024, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {"r1": {"dest_link": {"r0": {}}}}} + }, + "ipv6": { + "unicast": {"neighbor": {"r1": {"dest_link": {"r0": {}}}}} + } + } + } + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r0": {"dest_link": {"r1": {}}}, + "r2": {"dest_link": {"r1-link1": {}, "r1-link2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r0": {"dest_link": {"r1": {}}}, + "r2": {"dest_link": {"r1-link1": {}, "r1-link2": {}}} + } + } + } + } + } + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2-link1": {}, "r2-link2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2-link1": {}, "r2-link2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "500", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {"r3": {"dest_link": {"r4": {}}}}} + }, + "ipv6": { + "unicast": {"neighbor": {"r3": {"dest_link": {"r4": {}}}}} + } + } + } + } + } +} diff --git a/tests/topotests/bgp_default_originate/bgp_default_originate_topo1.json b/tests/topotests/bgp_default_originate/bgp_default_originate_topo1.json new file mode 100644 index 0000000..5fae34d --- /dev/null +++ b/tests/topotests/bgp_default_originate/bgp_default_originate_topo1.json @@ -0,0 +1,294 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "192.168.0.0", + "ipv4mask": 3024, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r0": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r0": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r0": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + }, + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r0": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"keepalivetimer": 1, + "holddowntimer": 3} + } + }, + "r3": { + "dest_link": { + "r2": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"keepalivetimer": 1, + "holddowntimer": 3} + } + }, + "r3": { + "dest_link": { + "r2": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"keepalivetimer": 1, + "holddowntimer": 3} + } + }, + "r4": { + "dest_link": { + "r3": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"keepalivetimer": 1, + "holddowntimer": 3} + } + }, + "r4": { + "dest_link": { + "r3": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "500", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {"keepalivetimer": 1, + "holddowntimer": 3} + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_default_originate/test_bgp_default_originate_2links.py b/tests/topotests/bgp_default_originate/test_bgp_default_originate_2links.py new file mode 100644 index 0000000..75e6656 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_bgp_default_originate_2links.py @@ -0,0 +1,1808 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +""" +Following tests are covered. +1. Verify default-originate route with default static and network command +2. Verify default-originate route with aggregate summary command +3. Verfiy default-originate behaviour in ecmp +""" +import os +import sys +import time +import pytest +import datetime +from copy import deepcopy +from lib.topolog import logger +from time import sleep + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger +from lib import topotest + +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, +) +from lib.common_config import ( + verify_fib_routes, + step, + create_prefix_lists, + run_frr_cmd, + create_route_maps, + shutdown_bringup_interface, + get_frr_ipv6_linklocal, + start_topology, + apply_raw_config, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, +) + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +NETWORK1_1 = {"ipv4": "198.51.1.1/32", "ipv6": "2001:DB8::1:1/128"} +NETWORK1_2 = {"ipv4": "198.51.1.2/32", "ipv6": "2001:DB8::1:2/128"} +NETWORK1_3 = {"ipv4": "198.51.1.3/32", "ipv6": "2001:DB8::1:3/128"} +NETWORK1_4 = {"ipv4": "198.51.1.4/32", "ipv6": "2001:DB8::1:4/128"} +NETWORK1_5 = {"ipv4": "198.51.1.5/32", "ipv6": "2001:DB8::1:5/128"} + +ipv4_uptime_dict = { + "r2": { + "static_routes": [ + {"network": "0.0.0.0/0"}, + ] + } +} + +ipv6_uptime_dict = { + "r2": { + "static_routes": [ + {"network": "::/0"}, + ] + } +} + +DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +pytestmark = [pytest.mark.bgpd] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_originate_2links.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_LINK1, DEFAULT_ROUTE_NXT_HOP_LINK2 + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2-link1"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2-link1"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2-link1"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_LINK1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r1"]["links"]["r2-link2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2-link2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2-link2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_LINK2 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Local API's +# +##################################################### + + +def get_rib_route_uptime(tgen, addr_type, dut, input_dict): + """ + Verify route uptime in RIB using "show ip route" + + Parameters + ---------- + * `tgen` : topogen object + * `addr_type` : ip type, ipv4/ipv6 + * `dut`: Device Under Test, for which user wants to test the data + * `input_dict` : input dict, has details of static routes + * `route_uptime`: uptime of the routes + + Usage + ----- + # Creating static routes for r1 + input_dict_r1 = { + "r1": { + "static_routes": [ + { + "network": "147.10.13.4/32" + }, + { + "network": "147.10.12.0/24" + }, + { + "network": "147.10.13.4/32" + }, + { + "network": "147.10.13.4/32" + }, + { + "network": "147.10.13.4/32" + } + ] + } + } + + + Returns + ------- + errormsg(str) or True + """ + + logger.info("Entering lib API: get_rib_route_uptime()") + route_time = [] + out_route_dict = {} + router_list = tgen.routers() + for routerInput in input_dict.keys(): + for router, rnode in router_list.items(): + if router != dut: + continue + + logger.info("Checking router %s RIB:", router) + + # Verifying RIB routes + if addr_type == "ipv4": + command = "show ip route" + else: + command = "show ipv6 route" + + if "static_routes" in input_dict[routerInput]: + static_routes = input_dict[routerInput]["static_routes"] + + for static_route in static_routes: + if "vrf" in static_route and static_route["vrf"] is not None: + + logger.info( + "[DUT: {}]: Verifying routes for VRF:" + " {}".format(router, static_route["vrf"]) + ) + cmd = "{} vrf {}".format(command, static_route["vrf"]) + + else: + cmd = "{}".format(command) + + cmd = "{} json".format(cmd) + + rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True) + + if bool(rib_routes_json) is False: + errormsg = "No route found in rib of router {}..".format(router) + return errormsg + network = static_route["network"] + route_time.append(rib_routes_json[network][0]["uptime"]) + + logger.info("Exiting lib API: get_rib_route_uptime()") + return route_time + + +def verify_the_uptime(time_stamp_before, time_stamp_after, incremented=None): + """ + time_stamp_before : string the time stamp captured + time_stamp_after : string the time stamp captured + """ + uptime_before = datetime.datetime.strptime(time_stamp_before[0], "%H:%M:%S") + uptime_after = datetime.datetime.strptime(time_stamp_after[0], "%H:%M:%S") + + if incremented == True: + if uptime_before < uptime_after: + logger.info( + " The Uptime [{}] is incremented than [{}].......PASSED ".format( + time_stamp_before, time_stamp_after + ) + ) + return True + else: + logger.error( + " The Uptime [{}] is expected to be incremented than [{}].......FAILED ".format( + time_stamp_before, time_stamp_after + ) + ) + return False + else: + logger.info( + " The Uptime [{}] is not incremented than [{}] ".format( + time_stamp_before, time_stamp_after + ) + ) + return True + + +def get_best_path_route_in_FIB(tgen, topo, dut, network): + """ + API to verify the best route in FIB and return the ipv4 and ipv6 nexthop for the given route + command + ======= + show ip route + show ipv6 route + params + ====== + dut : device under test : + network ; route (ip) to which the best route to be retrieved + Returns + ======== + on success : return dict with next hops for the best hop + on failure : return error message with boolean False + """ + is_ipv4_best_path_found = False + is_ipv6_best_path_found = False + rnode = tgen.routers()[dut] + ipv4_show_bgp_json = run_frr_cmd(rnode, "sh ip bgp json ", isjson=True) + ipv6_show_bgp_json = run_frr_cmd( + rnode, "sh ip bgp ipv6 unicast json ", isjson=True + ) + output_dict = {"ipv4": None, "ipv6": None} + ipv4_nxt_hop_count = len(ipv4_show_bgp_json["routes"][network["ipv4"]]) + for index in range(ipv4_nxt_hop_count): + if "bestpath" in ipv4_show_bgp_json["routes"][network["ipv4"]][index].keys(): + best_path_ip = ipv4_show_bgp_json["routes"][network["ipv4"]][index][ + "nexthops" + ][0]["ip"] + output_dict["ipv4"] = best_path_ip + logger.info( + "[DUT [{}]] Best path for the route {} is {} ".format( + dut, network["ipv4"], best_path_ip + ) + ) + is_ipv4_best_path_found = True + else: + logger.error("ERROR....! No Best Path Found in BGP RIB.... FAILED") + + ipv6_nxt_hop_count = len(ipv6_show_bgp_json["routes"][network["ipv6"]]) + for index in range(ipv6_nxt_hop_count): + if "bestpath" in ipv6_show_bgp_json["routes"][network["ipv6"]][index].keys(): + ip_add_count = len( + ipv6_show_bgp_json["routes"][network["ipv6"]][index]["nexthops"] + ) + for i_index in range(ip_add_count): + if ( + "global" + in ipv6_show_bgp_json["routes"][network["ipv6"]][index]["nexthops"][ + i_index + ]["scope"] + ): + best_path_ip = ipv6_show_bgp_json["routes"][network["ipv6"]][index][ + "nexthops" + ][i_index]["ip"] + output_dict["ipv6"] = best_path_ip + logger.info( + "[DUT [{}]] Best path for the route {} is {} ".format( + dut, network["ipv6"], best_path_ip + ) + ) + + else: + logger.error("ERROR....! No Best Path Found in BGP RIB.... FAILED") + if is_ipv4_best_path_found: + return output_dict + else: + logger.error("ERROR...! Unable to find the Best Path in the RIB") + return False + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_verify_bgp_default_originate_with_default_static_route_p1(request): + """ + Summary: "Verify default-originate route with default static and network command " + + """ + tgen = get_topogen() + global BGP_CONVERGENCE, DEFAULT_ROUTE_NXT_HOP_LINK1, DEFAULT_ROUTE_NXT_HOP_LINK2, DEFAULT_ROUTES + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure 2 link between R1 and R2") + step("Configure IPV4 and IPV6 EBGP between R1 and R2 both the links") + step("Configure default-originate on R1 IPv4 and IPv6 BGP session link-1 only ") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": {"r2": {"dest-link": "r1-link1"}} + } + }, + "ipv6": { + "unicast": { + "default_originate": {"r2": {"dest-link": "r1-link1"}} + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4/IPv6 default originate routes present on R2 nexthop as link-1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_LINK1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_LINK1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_LINK1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure network command on R1 (0.0.0.0/0 and 0::0/0) for IPv4 and IPv6 address family " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + {"network": [DEFAULT_ROUTES[addr_type]]} + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("No change on IPv4/IPv6 default-originate route advertised from link1") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify 0.0.0.0/0 and 0::0/0 route also get advertised from link-2 ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "Before removing default originate from R1 link -1 IPv4 and IPv6 address family taking the uptime snapshot" + ) + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("Remove default originate from R1 link -1 IPv4 and IPv6 address family ") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Routes must be learned from network command") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("After removing the default originate on R1 taking the uptime snapshot") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After removing the default-originate uptime should get reset for link-1 learn route" + ) + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before configuring default - originate") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + sleep(1) + + step( + "Configure default-originate on R1 link-1 again for IPv4 and IPv6 address family" + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify No change on R2 routing and BGP table for both the links ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking snapshot after configuring default - originate") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After configuring the default-originate uptime should not get reset for link-1 learn route" + ) + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=True) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=True) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before removing network 0.0.0.0 ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("Remove network command from R1 IPv4/IPv6 address family ") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "delete": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify 0.0.0.0/0 and 0::0/0 route get removed from link-2 and default-originate IPv4/IPv6 route learn on link-1" + ) + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Route from link2 is not expected \n Error: {}".format( + tc_name, result + ) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed\n Route from link2 is not expected \n Error: {}".format( + tc_name, result + ) + + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After removing default originate command on R1 verify that the uptime got reset on R2" + ) + + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before configuring static route network") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "Configure static default route for IPv4 and IPv6 (0.0.0.0/0 next-hop Null0 and 0::0/0 next-hop Null0) on R1" + ) + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": "0.0.0.0/0", + "next_hop": NEXT_HOP_IP["ipv4"], + }, + { + "network": "0::0/0", + "next_hop": NEXT_HOP_IP["ipv6"], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verifyIPv4 and IPv6 static routes are configure and up on R1 ") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": "0.0.0.0/0", + "next_hop": NEXT_HOP_IP["ipv4"], + }, + { + "network": "0::0/0", + "next_hop": NEXT_HOP_IP["ipv6"], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure redistribute static on IPv4 and IPv6 address family") + redistribute_static = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify No change on IPv4/IPv6 default-originate route advertised from link1") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("verify 0.0.0.0/0 and 0::0/0 route also get advertised from link-2 ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed\n Best Path sould be advertised in routes\n Error: {}".format( + tc_name, result + ) + + step("Taking uptime snapshot before removing default originate") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("Remove default-originate from link-1 from IPv4 and IPv6 neighbor ") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Taking uptime snapshot after removing default originate") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("verify the up time , up time should get reset ") + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify No change on IPv4/IPv6 default-originate route advertised from link1") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before configuring default originate") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + " Configure default-originate on link-1 again for IPv4 and IPv6 address family" + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify No change on IPv4/IPv6 default-originate route advertised from link1") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot after configuring default originate") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("After configuring the default originate the uptime should not get reset ") + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before removing redisctribute static ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + sleep(1) + + step("Remove redistribute static from IPv4 and IPv6 address family ") + input_dict_1 = { + "r1": { + "bgp": { + "local_as": get_dut_as_number(tgen, dut="r1"), + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + "ipv6": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify No change on IPv4/IPv6 default-originate route advertised from link1") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Taking uptime snapshot before removing redisctribute static ") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("After removing default originate the route uptime should get reset ") + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=True) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=True) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + +def test_verify_bgp_default_originate_with_aggregate_summary_p1(request): + """ + Summary: "Verify default-originate route with aggregate summary command" + """ + tgen = get_topogen() + global BGP_CONVERGENCE + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure default-originate on R1 IPv4 and IPv6 BGP session link-1 only") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": {"r2": {"dest-link": "r1-link1"}} + } + }, + "ipv6": { + "unicast": { + "default_originate": {"r2": {"dest-link": "r1-link1"}} + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4/IPv6 default originate routes present on R2 nexthop as link-1,on R2" + ) + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Configure 5 static route for IPv4 and IPv6 on R0") + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK1_3[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK1_4[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK1_5[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Before configuring the aggregate route taking uptime snapshot ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("Configure aggregate summary command for IPv4 and IPv6 address family ") + local_as = get_dut_as_number(tgen, dut="r1") + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(local_as), + "address-family ipv4 unicast", + "aggregate-address {} summary-only".format("0.0.0.0/0 "), + "exit-address-family", + "address-family ipv6 unicast", + "aggregate-address {} summary-only".format("0::0/0"), + "exit-address-family", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "verify that no change on IPv4/IPv6 default-originate route advertised from link1 0.0.0.0/0 and 0::0/0 route also get advertised from link-2 on R2" + ) + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("After configuring the aggregate route taking uptime snapshot ") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After Configuring the aggregate route uptime should get reset for link-1 learn route" + ) + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Before removing default originate taking uptime snapshot ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("Remove default originate from R1 link -1 IPv4 and IPv6 address family") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": {"dest-link": "r1-link1", "delete": True} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "verify that no change on IPv4/IPv6 default-originate route advertised from link1 0.0.0.0/0 and 0::0/0 route also get advertised from link-2 on R2" + ) + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("After removing default origin taking uptime snapshot ") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After removing the default-originate uptime should get reset for link-1 learn route" + ) + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Before Configuring default origin taking uptime snapshot ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "Configure default-originate on R1 link-1 again for IPv4 and IPv6 address family" + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": { + "dest-link": "r1-link1", + } + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("After Configuring default originate taking uptime snapshot") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step( + "After Configuring the default-originate uptime should get reset for link-1 learn route" + ) + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Before removing aggregate -summary command taking the uptime snapshot ") + uptime_before_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_before_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("remove aggregate summary command for IPv4 and IPv6 address family ") + local_as = get_dut_as_number(tgen, dut="r1") + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(local_as), + "address-family ipv4 unicast", + "no aggregate-address {} summary-only".format("0.0.0.0/0"), + "exit-address-family", + "address-family ipv6 unicast", + "no aggregate-address {} summary-only".format("0::0/0"), + "exit-address-family", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify Default-originate IPv4/IPv6 route learn on link-1 ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK1, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify 0.0.0.0/0 and 0::0/0 route get removed from link-2 ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_LINK2, + expected=False, + ) + assert result is not True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("After removing aggregate -summary command taking the uptime snapshot ") + uptime_after_ipv4 = get_rib_route_uptime(tgen, "ipv4", "r2", ipv4_uptime_dict) + uptime_after_ipv6 = get_rib_route_uptime(tgen, "ipv6", "r2", ipv6_uptime_dict) + + step("After removing aggregate command uptime should get reset ") + result = verify_the_uptime(uptime_before_ipv4, uptime_after_ipv4, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + result = verify_the_uptime(uptime_before_ipv6, uptime_after_ipv6, incremented=False) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + +def test_verify_default_originate_with_2way_ecmp_p2(request): + """ + Summary: "Verify default-originate route with 3 way ECMP and traffic " + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + global DEFAULT_ROUTES + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Populating next-hops details") + r1_r2_ipv4_neighbor_ips = [] + r1_r2_ipv6_neighbor_ips = [] + r1_link = None + for index in range(1, 3): + r1_link = "r1-link" + str(index) + r1_r2_ipv4_neighbor_ips.append( + topo["routers"]["r2"]["links"][r1_link]["ipv4"].split("/")[0] + ) + r1_r2_ipv6_neighbor_ips.append( + topo["routers"]["r2"]["links"][r1_link]["ipv6"].split("/")[0] + ) + + step( + "Configure default-originate on R1 for all the neighbor of IPv4 and IPv6 peers " + ) + local_as = get_dut_as_number(tgen, dut="r1") + for index in range(2): + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(local_as), + "address-family ipv4 unicast", + "neighbor {} default-originate".format( + r1_r2_ipv4_neighbor_ips[index] + ), + "exit-address-family", + "address-family ipv6 unicast", + "neighbor {} default-originate ".format( + r1_r2_ipv6_neighbor_ips[index] + ), + "exit-address-family", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + + r2_link = None + for index in range(1, 3): + r2_link = "r2-link" + str(index) + ipv4_nxt_hop = topo["routers"]["r1"]["links"][r2_link]["ipv4"].split("/")[0] + interface = topo["routers"]["r1"]["links"][r2_link]["interface"] + ipv6_link_local_nxt_hop = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local_nxt_hop} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ping R1 configure IPv4 and IPv6 loopback address from R2") + pingaddr = topo["routers"]["r1"]["links"]["lo"]["ipv4"].split("/")[0] + router = tgen.gears["r2"] + + def ping_router(): + output = router.run("ping -c 4 -w 4 {}".format(pingaddr)) + logger.info(output) + if " 0% packet loss" not in output: + return False + + _, res = topotest.run_and_expect(ping_router, None, count=10, wait=1) + logger.info("Ping from R1 to R2 ... success") + + step("Shuting up the active route") + network = {"ipv4": "0.0.0.0/0", "ipv6": "::/0"} + ipv_dict = get_best_path_route_in_FIB(tgen, topo, dut="r2", network=network) + dut_links = topo["routers"]["r1"]["links"] + active_interface = None + for key, values in dut_links.items(): + ipv4_address = dut_links[key]["ipv4"].split("/")[0] + ipv6_address = dut_links[key]["ipv6"].split("/")[0] + if ipv_dict["ipv4"] == ipv4_address and ipv_dict["ipv6"] == ipv6_address: + active_interface = dut_links[key]["interface"] + + logger.info( + "Shutting down the interface {} on router {} ".format(active_interface, "r1") + ) + shutdown_bringup_interface(tgen, "r1", active_interface, False) + + step("Verify the complete convergence to fail after shutting the interface") + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), " Testcase {} : After shuting down the interface Convergence is expected to be Failed".format( + tc_name + ) + + step( + "Verify routes from active best path is not received from r1 after shuting the interface" + ) + r2_link = None + for index in range(1, 3): + r2_link = "r2-link" + str(index) + ipv4_nxt_hop = topo["routers"]["r1"]["links"][r2_link]["ipv4"].split("/")[0] + interface = topo["routers"]["r1"]["links"][r2_link]["interface"] + ipv6_link_local_nxt_hop = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local_nxt_hop} + if index == 1: + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ping R1 configure IPv4 and IPv6 loopback address from R2") + pingaddr = topo["routers"]["r1"]["links"]["lo"]["ipv4"].split("/")[0] + router = tgen.gears["r2"] + output = router.run("ping -c 4 -w 4 {}".format(pingaddr)) + assert " 0% packet loss" in output, "Ping R1->R2 FAILED" + logger.info("Ping from R1 to R2 ... success") + + step("No Shuting up the active route") + + shutdown_bringup_interface(tgen, "r1", active_interface, True) + + step("Verify the complete convergence after bringup the interface") + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), " Testcase {} : After bringing up the interface complete convergence is expected ".format( + tc_name + ) + + step("Verify all the routes are received from r1 after no shuting the interface") + r2_link = None + for index in range(1, 3): + r2_link = "r2-link" + str(index) + ipv4_nxt_hop = topo["routers"]["r1"]["links"][r2_link]["ipv4"].split("/")[0] + interface = topo["routers"]["r1"]["links"][r2_link]["interface"] + ipv6_link_local_nxt_hop = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local_nxt_hop} + if index == 1: + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPv4 and IPv6 route-map with deny option on R2 to filter default route 0.0.0.0/0 and 0::0/0" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + input_dict_3 = { + "r2": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": DEFAULT_ROUTES["ipv4"], + "action": "permit", + } + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": DEFAULT_ROUTES["ipv6"], + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r2": { + "route_maps": { + "RMv4": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Apply route-map IN direction of R2 ( R2-R1) for IPv4 and IPv6 BGP neighbors") + r2_link = None + for index in range(1, 3): + r2_link = "r2-link" + str(index) + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + r2_link: { + "route_maps": [ + {"name": "RMv4", "direction": "in"} + ] + }, + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + r2_link: { + "route_maps": [ + {"name": "RMv6", "direction": "in"} + ] + }, + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("After applying the route-map the routes are not expected in RIB ") + r2_link = None + for index in range(1, 3): + r2_link = "r2-link" + str(index) + ipv4_nxt_hop = topo["routers"]["r1"]["links"][r2_link]["ipv4"].split("/")[0] + interface = topo["routers"]["r1"]["links"][r2_link]["interface"] + ipv6_link_local_nxt_hop = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local_nxt_hop} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_1.py b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_1.py new file mode 100644 index 0000000..50a1938 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_1.py @@ -0,0 +1,2679 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +""" +Following tests are covered. +1. Verify BGP default-originate route with IBGP peer +2. Verify BGP default-originate route with EBGP peer +3. Verify BGP default route when default-originate configured with route-map over IBGP peer +4. Verify BGP default route when default-originate configured with route-map over EBGP peer" + +""" +import os +import sys +import time +import pytest +from time import sleep +from copy import deepcopy +from lib.topolog import logger + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +from lib.bgp import ( + verify_bgp_convergence, + verify_graceful_restart, + create_router_bgp, + verify_router_id, + modify_as_number, + verify_as_numbers, + clear_bgp_and_verify, + clear_bgp, + verify_bgp_rib, + get_prefix_count_route, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, + verify_bgp_advertised_routes_from_neighbor, + verify_bgp_received_routes_from_neighbor, +) +from lib.common_config import ( + interface_status, + verify_prefix_lists, + verify_fib_routes, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, + step, + required_linux_kernel_version, + stop_router, + start_router, + create_route_maps, + create_prefix_lists, + get_frr_ipv6_linklocal, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, + delete_route_maps, +) + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 +# Global variables +NETWORK1_1 = {"ipv4": "1.1.1.1/32", "ipv6": "1::1/128"} +NETWORK1_2 = {"ipv4": "1.1.1.2/32", "ipv6": "1::2/128"} +NETWORK2_1 = {"ipv4": "2.1.1.1/32", "ipv6": "2::1/128"} +NETWORK2_2 = {"ipv4": "2.1.1.2/32", "ipv6": "2::2/128"} +NETWORK3_1 = {"ipv4": "3.1.1.1/32", "ipv6": "3::1/128"} +NETWORK3_2 = {"ipv4": "3.1.1.2/32", "ipv6": "3::2/128"} +NETWORK4_1 = {"ipv4": "4.1.1.1/32", "ipv6": "4::1/128"} +NETWORK4_2 = {"ipv4": "4.1.1.2/32", "ipv6": "4::2/128"} +NETWORK5_1 = {"ipv4": "5.1.1.1/32", "ipv6": "5::1/128"} +NETWORK5_2 = {"ipv4": "5.1.1.2/32", "ipv6": "5::2/128"} +DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +IPV4_RM = "RMVIPV4" +IPV6_RM = "RMVIPV6" + +IPV4_RM1 = "RMVIPV41" +IPV6_RM1 = "RMVIPV61" + +IPV4_RM2 = "RMVIPV42" +IPV6_RM2 = "RMVIPV62" + +IPV4_PL_1 = "PV41" +IPV4_PL_2 = "PV42" + +IPV6_PL_1 = "PV61" +IPV6_PL_2 = "PV62" + + +r1_ipv4_loopback = "1.0.1.0/24" +r2_ipv4_loopback = "1.0.2.0/24" +r3_ipv4_loopback = "1.0.3.0/24" +r4_ipv4_loopback = "1.0.4.0/24" +r1_ipv6_loopback = "2001:db8:f::1:0/120" +r2_ipv6_loopback = "2001:db8:f::2:0/120" +r3_ipv6_loopback = "2001:db8:f::3:0/120" +r4_ipv6_loopback = "2001:db8:f::4:0/120" + +r0_connected_address_ipv4 = "192.168.0.0/24" +r0_connected_address_ipv6 = "fd00::/64" +r1_connected_address_ipv4 = "192.168.1.0/24" +r1_connected_address_ipv6 = "fd00:0:0:1::/64" +r3_connected_address_ipv4 = "192.168.2.0/24" +r3_connected_address_ipv6 = "fd00:0:0:2::/64" +r4_connected_address_ipv4 = "192.168.3.0/24" +r4_connected_address_ipv6 = "fd00:0:0:3::/64" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_originate_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_R1, DEFAULT_ROUTE_NXT_HOP_R3 + global R0_NETWORK_LOOPBACK, R0_NETWORK_LOOPBACK_NXTHOP, R1_NETWORK_LOOPBACK, R1_NETWORK_LOOPBACK_NXTHOP + global R0_NETWORK_CONNECTED, R0_NETWORK_CONNECTED_NXTHOP, R1_NETWORK_CONNECTED, R1_NETWORK_CONNECTED_NXTHOP + global R4_NETWORK_LOOPBACK, R4_NETWORK_LOOPBACK_NXTHOP, R3_NETWORK_LOOPBACK, R3_NETWORK_LOOPBACK_NXTHOP + global R4_NETWORK_CONNECTED, R4_NETWORK_CONNECTED_NXTHOP, R3_NETWORK_CONNECTED, R3_NETWORK_CONNECTED_NXTHOP + + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + # There are the global varibles used through out the file these are acheived only after building the topology. + + r0_loopback_address_ipv4 = topo["routers"]["r0"]["links"]["lo"]["ipv4"] + r0_loopback_address_ipv4_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv4" + ].split("/")[0] + r0_loopback_address_ipv6 = topo["routers"]["r0"]["links"]["lo"]["ipv6"] + r0_loopback_address_ipv6_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv6" + ].split("/")[0] + + r1_loopback_address_ipv4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_loopback_address_ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r1_loopback_address_ipv6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_loopback_address_ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + r4_loopback_address_ipv4 = topo["routers"]["r4"]["links"]["lo"]["ipv4"] + r4_loopback_address_ipv4_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv4" + ].split("/")[0] + r4_loopback_address_ipv6 = topo["routers"]["r4"]["links"]["lo"]["ipv6"] + r4_loopback_address_ipv6_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv6" + ].split("/")[0] + + r3_loopback_address_ipv4 = topo["routers"]["r3"]["links"]["lo"]["ipv4"] + r3_loopback_address_ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r3_loopback_address_ipv6 = topo["routers"]["r3"]["links"]["lo"]["ipv6"] + r3_loopback_address_ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + R0_NETWORK_LOOPBACK = { + "ipv4": r0_loopback_address_ipv4, + "ipv6": r0_loopback_address_ipv6, + } + R0_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_LOOPBACK = { + "ipv4": r1_loopback_address_ipv4, + "ipv6": r1_loopback_address_ipv6, + } + R1_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R0_NETWORK_CONNECTED = { + "ipv4": r0_connected_address_ipv4, + "ipv6": r0_connected_address_ipv6, + } + R0_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_CONNECTED = { + "ipv4": r1_connected_address_ipv4, + "ipv6": r1_connected_address_ipv6, + } + R1_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R4_NETWORK_LOOPBACK = { + "ipv4": r4_loopback_address_ipv4, + "ipv6": r4_loopback_address_ipv6, + } + R4_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_LOOPBACK = { + "ipv4": r3_loopback_address_ipv4, + "ipv6": r3_loopback_address_ipv6, + } + R3_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + R4_NETWORK_CONNECTED = { + "ipv4": r4_connected_address_ipv4, + "ipv6": r4_connected_address_ipv6, + } + R4_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_CONNECTED = { + "ipv4": r3_connected_address_ipv4, + "ipv6": r3_connected_address_ipv6, + } + R3_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + # populating the nexthop for default routes + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_verify_bgp_default_originate_in_IBGP_p0(request): + """ + Verify BGP default-originate route with IBGP peer + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , IBGP neighbor between R1 and R2") + step("Configure IPv4 and IPv6 Loopback interface on R1, R0 and R2") + step("Configure IPv4 and IPv6 EBGP neighbor between R0 and R1") + + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + + input_dict = { + "r0": { + "bgp": { + "local_as": 1000, + } + }, + "r1": { + "bgp": { + "local_as": 2000, + } + }, + "r2": { + "bgp": { + "local_as": 2000, + } + }, + "r3": { + "bgp": { + "local_as": r3_local_as, + } + }, + "r4": { + "bgp": { + "local_as": r4_local_as, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert ( + BGP_CONVERGENCE is True + ), " Complete Convergence is expected after changing the ASN but failed to converge --> :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure IPv4 and IPv6 static route on R1 next-hop as NULL0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static routes {} on router R1 \n Error: {}".format( + tc_name, static_routes_input, result + ) + step("verify IPv4 and IPv6 static route are configured and up on R1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed \n After configuring the static routes {} , the routes are not found in FIB \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure redistribute static and connected on R0 and R1, for IPv4 and IPv6 address family " + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure the redistribute static configuration \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring redistribute command , verify static and connected routes ( loopback connected routes) are advertised on R2" + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : After redistributing static routes the routes {} expected in FIB but NOT FOUND ......! \n Error: {}".format( + tc_name, static_routes_input, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : After redistributing static routes the routes {} expected in RIB but NOT FOUND ......! \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Taking the snapshot of the prefix count before configuring the default originate" + ) + snapshot1 = get_prefix_count_route(tgen, topo, dut="r2", peer="r1") + + step( + "Configure Default originate on R1 for R1 to R2, for IPv4 and IPv6 BGP address family " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed Configuring default originate configuration. \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + " R1 static and loopback routes received on R2 BGP and FIB" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : post configuring the BGP Default originate configuration static and connected routes should not be effected but impacted on FIB .......! FAILED \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failedpost configuring the BGP Default originate configuration static and connected routes should not be effected but impacted on RIB......! FAILED \n Error: {}".format( + tc_name, result + ) + step( + "Verify default route for IPv4 and IPv6 present with path=igp metric =0 , local-preference= 100 " + ) + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + locPrf=100, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + step( + "Taking the snapshot2 of the prefix count after configuring the default originate" + ) + snapshot2 = get_prefix_count_route(tgen, topo, dut="r2", peer="r1") + + step("verifying the prefix count incrementing or not ") + isIPv4prefix_incremented = False + isIPv6prefix_incremented = False + if snapshot1["ipv4_count"] < snapshot2["ipv4_count"]: + isIPv4prefix_incremented = True + if snapshot1["ipv6_count"] < snapshot2["ipv6_count"]: + isIPv6prefix_incremented = True + + assert ( + isIPv4prefix_incremented is True + ), "Testcase {} : Failed Error: IPV4 Prefix is not incremented on receiveing ".format( + tc_name + ) + + assert ( + isIPv6prefix_incremented is True + ), "Testcase {} : Failed Error: IPV6 Prefix is not incremented on receiveing ".format( + tc_name + ) + write_test_footer(tc_name) + + +def test_verify_bgp_default_originate_in_EBGP_p0(request): + """ + Verify BGP default-originate route with EBGP peer + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , EBGP neighbor between R3 and R2") + step("Configure lPv4 and IPv6 Loopback interface on R3, R4 and R2") + step("Configure IPv4 and IPv6 IBGP neighbor between R4 and R3") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": r1_local_as, + } + }, + "r2": { + "bgp": { + "local_as": r2_local_as, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert ( + BGP_CONVERGENCE is True + ), "Complete convergence is expeceted after changing the ASN os the routes ..! :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step(" Configure IPv4 and IPv6 static route on R3 next-hop on R4 interface") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static routes ....! Failed \n Error: {}".format( + tc_name, result + ) + step("verify IPv4 and IPv6 static route are configured and up on R1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Route is not found in {} in FIB ......! Failed \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure redistribute static and connected on R3 and R4 for IPv4 and IPv6 address family " + ) + redistribute_static = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure redistribute configuratin \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring redistribute command , verify static and connected routes ( loopback connected routes) are advertised on R2" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : static & and connected routes are expected but not found in FIB .... ! \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : static & and connected routes are expected but not found in RIB .... ! \n Error: {}".format( + tc_name, result + ) + snapshot1 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3") + step( + "Configure Default originate on R3 for R3 to R2, on IPv4 and IPv6 BGP address family" + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate configuration \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 on both BGP RIB and FIB" + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : static route from R1 {} and default route from R3 is expected in R2 FIB .....! NOT FOUND \n Error: {}".format( + tc_name, NETWORK1_1, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert ( + result is True + ), "Testcase {} : static route from R1 {} and default route from R3 is expected in R2 RIB .....! NOT FOUND \n Error: {}".format( + tc_name, NETWORK1_1, result + ) + + step( + "Verify default route for IPv4 and IPv6 present with path = ebgp as path, metric =0 " + ) + # local preference will bgp not applicable for eBGP + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=0, + expected_aspath="4000", + ) + assert ( + result is True + ), "Testcase {} : Default route from R3 is expected with attributes in R2 RIB .....! NOT FOUND Error: {}".format( + tc_name, result + ) + + step( + "Taking the snapshot2 of the prefix count after configuring the default originate" + ) + snapshot2 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3") + step( + "Verify out-prefix count is incremented default route on IPv4 and IPv6 neighbor" + ) + isIPv4prefix_incremented = False + isIPv6prefix_incremented = False + if snapshot1["ipv4_count"] < snapshot2["ipv4_count"]: + isIPv4prefix_incremented = True + if snapshot1["ipv6_count"] < snapshot2["ipv6_count"]: + isIPv6prefix_incremented = True + + assert ( + isIPv4prefix_incremented is True + ), "Testcase {} : Failed Error: IPV4 Prefix is not incremented on receiveing ".format( + tc_name + ) + + assert ( + isIPv6prefix_incremented is True + ), "Testcase {} : Failed Error: IPV6 Prefix is not incremented on receiveing ".format( + tc_name + ) + write_test_footer(tc_name) + + +def test_verify_bgp_default_originate_in_IBGP_with_route_map_p0(request): + """ + test_verify_bgp_default_originate_in_IBGP_with_route_map_p0 + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , IBGP neighbor between R1 and R2") + step("Configure IPv4 and IPv6 , EBGP neighbor between R1 and R0") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": 1000, + } + }, + "r3": { + "bgp": { + "local_as": r3_local_as, + } + }, + "r4": { + "bgp": { + "local_as": r4_local_as, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert ( + BGP_CONVERGENCE is True + ), "Complete convergence is expected after changing ASN ....! ERROR :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure 2 IPv4 and 2 IPv6 Static route on R0 with next-hop as Null0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Static Configuration is Failed \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert ( + result is True + ), "Testcase {} : routes {} unable is not found in R0 FIB \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure redistribute static on IPv4 and IPv6 address family on R0 for R0 to R1 neighbor " + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure redistribute static configuration....! \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed... Routes {} expected in r0 FIB after configuring the redistribute config \n Error: {}".format( + tc_name, static_routes_input, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed... Routes {} expected in r0 RIB after configuring the redistribute config \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure IPv4 prefix-list Pv4 and and IPv6 prefix-list Pv6 on R1 to match BGP route Sv41, Sv42, IPv6 route Sv61 Sv62 permit " + ) + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix list \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPV4 and IPv6 route-map (RMv4 and RMv6 ) matching prefix-list (Pv4 and Pv6) respectively on R1" + ) + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate with route-map (RMv4 and RMv6) on R1, on BGP IPv4 and IPv6 address family " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate \n Error: {}".format( + tc_name, result + ) + + step("Verify the default route is received in BGP RIB and FIB") + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed...! Expected default route from R1 not found in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed...! Expected default route from R1 not found in RIB \n Error: {}".format( + tc_name, result + ) + step("Remove route-map RMv4 and RMv6 from default-originate command in R1") + NOTE = """ Configuring the default-originate should remove the previously applied default originate with condtional route-map""" + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to remove the default originate conditional route-map \n Error: {}".format( + tc_name, result + ) + + step( + "Verify BGP RIB and FIB After removing route-map , default route still present on R2" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default route from R1 is not found in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default route from R1 is not found in RIB \n Error: {}".format( + tc_name, result + ) + + step("Configure default-originate with route-map (RMv4 and RMv6) on R1 ") + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "default_originate": { + "r2": { + "route_map": "RMv4", + } + } + } + }, + "ipv6": { + "unicast": { + "default_originate": { + "r2": { + "route_map": "RMv6", + } + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the Default originate route-map \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default Route from R1 is not found in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default Route from R1 is not found in RIB \n Error: {}".format( + tc_name, result + ) + + step("Delete prefix list using no prefix-list") + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + "delete": True, + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + "delete": True, + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + "delete": True, + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + "delete": True, + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to delete the prefix list Error: {}".format( + tc_name, result + ) + + step( + "Verify BGP RIB and FIB After deleting prefix-list , verify IPv4 and IPv6 default route got removed from DUT " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed\n After deleteing prefix default route is not expected in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After deleteing prefix default route is not expected in RIB \n Error: {}".format( + tc_name, result + ) + + step("Configure prefix-list and delete route-map using no route-map") + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix lists Error: {}".format( + tc_name, result + ) + + step( + "After configuring the Prefixlist cross checking the BGP Default route is configured again , before deleting the route map" + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed Default route from R1 is expected in FIB but not found \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed Default route from R1 is expected in RIB but not found \n Error: {}".format( + tc_name, result + ) + + step("Deleting the routemap") + input_dict = {"r1": {"route_maps": ["RMv4", "RMv6"]}} + result = delete_route_maps(tgen, input_dict) + assert ( + result is True + ), "Testcase {} : Failed to delete the Route-map \n Error: {}".format( + tc_name, result + ) + + step( + "Verify BGP RIB and FIB ,After deleting route-map , verify IPv4 and IPv6 default route got removed from DUT" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After deleteing route-map default route is not expected in FIB \nError: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After deleteing route-map default route is not expected in RIB \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_default_originate_in_EBGP_with_route_map_p0(request): + """ + test_verify_bgp_default_originate_in_EBGP_with_route_map_p0 + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , EBGP neighbor between R3 and R2") + step("Configure IPv4 and IPv6 IBGP neighbor between R3 and R4") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": r1_local_as, + } + }, + "r2": { + "bgp": { + "local_as": r2_local_as, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Configure 2 IPv4 and 2 IPv6, Static route on R4 with next-hop as Null0 IPv4 route Sv41, Sv42, IPv6 route Sv61 Sv62" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static routes \n Error: {}".format( + tc_name, result + ) + step("verify IPv4 and IPv6 static route are configured and up on R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed Static route {} is not found in R4 FIB \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure redistribute static on IPv4 and IPv6 address family on R4 for R4 to R3 neighbo" + ) + redistribute_static = { + "r4": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure the redistribute static \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R3") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed static routes from R1 and R3 is not found in FIB \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed static routes from R1 and R3 is not found in RIB \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPv4 prefix-list Pv4 and and IPv6 prefix-list Pv6 on R3 so new route which is not present on R3" + ) + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK3_1["ipv4"], + "action": "permit", + } + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK3_1["ipv6"], + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix lists \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r3": {"prefix_lists": ["Pv4", "Pv6"]}} + result = verify_prefix_lists(tgen, input_dict) + assert ( + result is True + ), "Testcase {} : Failed ..! configured prefix lists {} are not found \n Error: {}".format( + tc_name, input_dict, result + ) + + step( + "Configure IPv4 and IPv6 route-map ( RMv4 and RMv6 ) matching prefix-list (Pv4 and Pv6 ) respectively on R3" + ) + input_dict_3 = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route-map \n Error: {}".format( + tc_name, result + ) + step( + "Taking the snapshot of the prefix count before configuring the default originate" + ) + snapshot1 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3") + step( + "Configure default-originate with IPv4 and IPv6 route-map (RMv4 and RMv6) on R3" + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure default-originate \n Error: {}".format( + tc_name, result + ) + + step("Verify the default route is NOT received in BGP RIB and FIB on R2 ") + step( + "After configuring default-originate command , verify default routes are not Received on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Default route is not expected due to deny in prefix list \nError: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \nDefault route is not expected due to deny in prefix list\n Error: {}".format( + tc_name, result + ) + + step("Add route Sv41, Sv42, IPv6 route Sv61 Sv62 on prefix list Pv4 and Pv6") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix lists Error: {}".format( + tc_name, result + ) + + step("Verify BGP default route for IPv4 and IPv6 is received on R2") + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default routes are expected in R2 FIB from R3 but not found ....! \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert ( + result is True + ), "Testcase {} : Failed Default routes are expected in R2 RIB from R3 but not found ....! \n Error: {}".format( + tc_name, result + ) + + step("Remove route Sv41, Sv42, IPv6 route Sv61 Sv62 on prefix list Pv4 and Pv6") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + "delete": True, + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + "delete": True, + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + "delete": True, + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + "delete": True, + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to remove prefix-lists from R3 Error: {}".format( + tc_name, result + ) + + step( + "After Removing route BGP default route for IPv4 and IPv6 is NOT received on R2" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After Removing route in prefix list the default route is not expected in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After Removing route in prefix list the default route is not expected in RIB\n Error: {}".format( + tc_name, result + ) + + step(" Add route Sv41, Sv42, IPv6 route Sv61 Sv62 on prefix list Pv4 and Pv6") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify BGP default route for IPv4 and IPv6 is received on R2") + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change IPv4 and IPv6 prefix-list permit and deny ") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + {"seqid": "1", "network": NETWORK1_1["ipv4"], "action": "deny"}, + {"seqid": "2", "network": NETWORK2_1["ipv4"], "action": "deny"}, + ] + }, + "ipv6": { + "Pv6": [ + {"seqid": "1", "network": NETWORK1_1["ipv6"], "action": "deny"}, + {"seqid": "2", "network": NETWORK2_1["ipv6"], "action": "deny"}, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify BGP default route for IPv4 and IPv6 is not received on R2") + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n after denying the prefix list default route is not expected in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n after denying the prefix list default route is not expected in RIB \n Error: {}".format( + tc_name, result + ) + + step("Change IPv4 and IPv6 prefix-list deny to permit ") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify BGP default route for IPv4 and IPv6 is received on R2") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Taking the snapshot2 of the prefix count after configuring the default originate" + ) + snapshot2 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3") + + step("verifying the prefix count incrementing or not ") + isIPv4prefix_incremented = False + isIPv6prefix_incremented = False + if snapshot1["ipv4_count"] < snapshot2["ipv4_count"]: + isIPv4prefix_incremented = True + if snapshot1["ipv6_count"] < snapshot2["ipv6_count"]: + isIPv6prefix_incremented = True + + assert ( + isIPv4prefix_incremented is True + ), "Testcase {} : Failed Error: IPV4 Prefix is not incremented on receiveing ".format( + tc_name + ) + + assert ( + isIPv6prefix_incremented is True + ), "Testcase {} : Failed Error: IPV6 Prefix is not incremented on receiveing ".format( + tc_name + ) + + step( + "Configure another IPv4 and IPv6 route-map and match same prefix-list (Sv41, Sv42, IPv6 route Sv61 Sv62) with deny statement " + ) + input_dict_3 = { + "r3": { + "route_maps": { + "RMv41": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv61": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Attach route-map on IPv4 and IP6 BGP neighbor on fly") + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv41"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv61"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "After attaching route-map verify IPv4 and IPv6 default route is withdrawn from the R2" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change the recently added Routemap from deny to permit") + input_dict_3 = { + "r3": { + "route_maps": { + "RMv41": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv61": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 default route is advertised from the R2") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Delete default-originate route-map command while configuring ( neighbor x.x.x default-originate) for IPv4 and IPv6 BGP neighbor " + ) + """ Configuring the Default originate on neighbor must remove the previously assigned deault-originate with routemap config """ + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify in running config from BGP that default-originate with route-map command is removed and default-originate command is still present and default route for IPv4 and IPv6 present in RIB and FIB" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate with conditional route-map command on IPv4 and IPv6 address family " + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv41"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv61"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify in running config from BGP that default-originate with route-map command is present and default route for IPv4 and IPv6 present" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Delete default originate with 'no bgp default-originate' from IPV4 and IPV6 address family " + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + " Verify in running config from BGP that default-originate complete CLI is removed for IPV4 and IPV6 address family and default originate routes got deleted" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Default Route is not expected in FIB \nError: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Default Route is not expected in RIB\nError: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_2.py b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_2.py new file mode 100644 index 0000000..4e8bda5 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_2.py @@ -0,0 +1,2433 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +""" +Following tests are covered. +5. Verify BGP default originate route-map with OUT route-map +6. Verify BGP default originate route-map with IN route-map +8. Verify BGP default route after removing default-originate +9. Verify default-originate route with GR +""" +import os +import sys +import time +import pytest +from time import sleep +from copy import deepcopy +from lib.topolog import logger + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +from lib.bgp import ( + verify_bgp_convergence, + verify_graceful_restart, + create_router_bgp, + verify_router_id, + modify_as_number, + verify_as_numbers, + clear_bgp_and_verify, + clear_bgp, + verify_bgp_rib, + get_prefix_count_route, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, + verify_bgp_advertised_routes_from_neighbor, + verify_bgp_received_routes_from_neighbor, +) +from lib.common_config import ( + interface_status, + verify_prefix_lists, + verify_fib_routes, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, + step, + required_linux_kernel_version, + stop_router, + start_router, + create_route_maps, + create_prefix_lists, + get_frr_ipv6_linklocal, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, + delete_route_maps, +) + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 +# Global variables +NETWORK1_1 = {"ipv4": "1.1.1.1/32", "ipv6": "1::1/128"} +NETWORK1_2 = {"ipv4": "1.1.1.2/32", "ipv6": "1::2/128"} +NETWORK2_1 = {"ipv4": "2.1.1.1/32", "ipv6": "2::1/128"} +NETWORK2_2 = {"ipv4": "2.1.1.2/32", "ipv6": "2::2/128"} +NETWORK3_1 = {"ipv4": "3.1.1.1/32", "ipv6": "3::1/128"} +NETWORK3_2 = {"ipv4": "3.1.1.2/32", "ipv6": "3::2/128"} +NETWORK4_1 = {"ipv4": "4.1.1.1/32", "ipv6": "4::1/128"} +NETWORK4_2 = {"ipv4": "4.1.1.2/32", "ipv6": "4::2/128"} +NETWORK5_1 = {"ipv4": "5.1.1.1/32", "ipv6": "5::1/128"} +NETWORK5_2 = {"ipv4": "5.1.1.2/32", "ipv6": "5::2/128"} +DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +IPV4_RM = "RMVIPV4" +IPV6_RM = "RMVIPV6" + +IPV4_RM1 = "RMVIPV41" +IPV6_RM1 = "RMVIPV61" + +IPV4_RM2 = "RMVIPV42" +IPV6_RM2 = "RMVIPV62" + +IPV4_PL_1 = "PV41" +IPV4_PL_2 = "PV42" + +IPV6_PL_1 = "PV61" +IPV6_PL_2 = "PV62" + + +r1_ipv4_loopback = "1.0.1.0/24" +r2_ipv4_loopback = "1.0.2.0/24" +r3_ipv4_loopback = "1.0.3.0/24" +r4_ipv4_loopback = "1.0.4.0/24" +r1_ipv6_loopback = "2001:db8:f::1:0/120" +r2_ipv6_loopback = "2001:db8:f::2:0/120" +r3_ipv6_loopback = "2001:db8:f::3:0/120" +r4_ipv6_loopback = "2001:db8:f::4:0/120" + +r0_connected_address_ipv4 = "192.168.0.0/24" +r0_connected_address_ipv6 = "fd00::/64" +r1_connected_address_ipv4 = "192.168.1.0/24" +r1_connected_address_ipv6 = "fd00:0:0:1::/64" +r3_connected_address_ipv4 = "192.168.2.0/24" +r3_connected_address_ipv6 = "fd00:0:0:2::/64" +r4_connected_address_ipv4 = "192.168.3.0/24" +r4_connected_address_ipv6 = "fd00:0:0:3::/64" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_originate_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_R1, DEFAULT_ROUTE_NXT_HOP_R3 + global R0_NETWORK_LOOPBACK, R0_NETWORK_LOOPBACK_NXTHOP, R1_NETWORK_LOOPBACK, R1_NETWORK_LOOPBACK_NXTHOP + global R0_NETWORK_CONNECTED, R0_NETWORK_CONNECTED_NXTHOP, R1_NETWORK_CONNECTED, R1_NETWORK_CONNECTED_NXTHOP + global R4_NETWORK_LOOPBACK, R4_NETWORK_LOOPBACK_NXTHOP, R3_NETWORK_LOOPBACK, R3_NETWORK_LOOPBACK_NXTHOP + global R4_NETWORK_CONNECTED, R4_NETWORK_CONNECTED_NXTHOP, R3_NETWORK_CONNECTED, R3_NETWORK_CONNECTED_NXTHOP + + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + # There are the global varibles used through out the file these are acheived only after building the topology. + + r0_loopback_address_ipv4 = topo["routers"]["r0"]["links"]["lo"]["ipv4"] + r0_loopback_address_ipv4_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv4" + ].split("/")[0] + r0_loopback_address_ipv6 = topo["routers"]["r0"]["links"]["lo"]["ipv6"] + r0_loopback_address_ipv6_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv6" + ].split("/")[0] + + r1_loopback_address_ipv4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_loopback_address_ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r1_loopback_address_ipv6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_loopback_address_ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + r4_loopback_address_ipv4 = topo["routers"]["r4"]["links"]["lo"]["ipv4"] + r4_loopback_address_ipv4_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv4" + ].split("/")[0] + r4_loopback_address_ipv6 = topo["routers"]["r4"]["links"]["lo"]["ipv6"] + r4_loopback_address_ipv6_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv6" + ].split("/")[0] + + r3_loopback_address_ipv4 = topo["routers"]["r3"]["links"]["lo"]["ipv4"] + r3_loopback_address_ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r3_loopback_address_ipv6 = topo["routers"]["r3"]["links"]["lo"]["ipv6"] + r3_loopback_address_ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + R0_NETWORK_LOOPBACK = { + "ipv4": r0_loopback_address_ipv4, + "ipv6": r0_loopback_address_ipv6, + } + R0_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_LOOPBACK = { + "ipv4": r1_loopback_address_ipv4, + "ipv6": r1_loopback_address_ipv6, + } + R1_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R0_NETWORK_CONNECTED = { + "ipv4": r0_connected_address_ipv4, + "ipv6": r0_connected_address_ipv6, + } + R0_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_CONNECTED = { + "ipv4": r1_connected_address_ipv4, + "ipv6": r1_connected_address_ipv6, + } + R1_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R4_NETWORK_LOOPBACK = { + "ipv4": r4_loopback_address_ipv4, + "ipv6": r4_loopback_address_ipv6, + } + R4_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_LOOPBACK = { + "ipv4": r3_loopback_address_ipv4, + "ipv6": r3_loopback_address_ipv6, + } + R3_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + R4_NETWORK_CONNECTED = { + "ipv4": r4_connected_address_ipv4, + "ipv6": r4_connected_address_ipv6, + } + R4_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_CONNECTED = { + "ipv4": r3_connected_address_ipv4, + "ipv6": r3_connected_address_ipv6, + } + R3_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + # populating the nexthop for default routes + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Local API's +# +##################################################### + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + return True + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_verify_bgp_default_originate_route_map_in_OUT_p1(request): + """ + test_verify_bgp_default_originate_route_map_in_OUT_p1 + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , EBGP neighbor between R3 and R2") + step("Configure IPv4 and IPv6 IBGP neighbor between R3 and R4") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": r1_local_as, + } + }, + "r2": { + "bgp": { + "local_as": r2_local_as, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Configure 2 IPv4 and 2 IPv6, Static route on R4 with next-hop as Null0 IPv4 route Sv41, Sv42, IPv6 route Sv61 Sv62" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure redistribute static knob on R4 , for R4 to R3 neighbor ") + redistribute_static = { + "r4": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + expected_routes = { + "ipv4": [ + {"network": NETWORK1_1["ipv4"], "nexthop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK2_1["ipv4"], "nexthop": NEXT_HOP_IP["ipv4"]}, + ], + "ipv6": [ + {"network": NETWORK1_1["ipv6"], "nexthop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK2_1["ipv6"], "nexthop": NEXT_HOP_IP["ipv4"]}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r4", peer="r3", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "After redistribute static verify the routes is recevied in router R3 in RIB and FIB" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPv4 prefix-list Pv4 and and IPv6 prefix-list Pv6 on R3 to match BGP route Sv41, IPv6 route Sv61 with permit option " + ) + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + } + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r3": {"prefix_lists": ["Pv4", "Pv6"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure IPv4 and IPv6 route-map RMv4 and RMv6 matching prefix-list Pv4 and Pv6 with permit option " + ) + input_dict_3 = { + "r3": { + "route_maps": { + "RM4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RM6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure IPv4 prefix-list Pv42 and and IPv6 prefix-list Pv62 on R3 to match BGP route Sv42, IPv6 route Sv62 with deny option" + ) + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv42": [ + {"seqid": "1", "network": NETWORK2_1["ipv4"], "action": "deny"} + ] + }, + "ipv6": { + "Pv62": [ + {"seqid": "1", "network": NETWORK2_1["ipv6"], "action": "deny"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r3": {"prefix_lists": ["Pv42", "Pv62"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure IPv4 and IPv6 route-map (RMv42 and RMv62 )matching prefix-list Pv42 and Pv62 with permit option " + ) + input_dict_3 = { + "r3": { + "route_maps": { + "RMv42": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv42"}}, + }, + ], + "RMv62": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv62"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Apply IPv4 and IPv6 route-map RMv4 and RMv6 with default-originate on R3 , for R3 to R2 peers and Apply IPv4 and IPv6 out route-map RMv42 and RMv62 on R3 , for R3 to R2 peers " + ) + local_as = get_dut_as_number(tgen, "r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RM4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RM6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + updated_topo = topo + updated_topo["routers"]["r0"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r0") + updated_topo["routers"]["r1"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r1") + updated_topo["routers"]["r2"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r2") + updated_topo["routers"]["r3"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r3") + updated_topo["routers"]["r4"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r4") + + step( + "Apply IPv4 and IPv6 route-map RMv42 and RMv62 on R3 (OUT Direction), for R3 to R2 peers " + ) + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "route_maps": [ + {"name": "RMv42", "direction": "out"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "route_maps": [ + {"name": "RMv62", "direction": "out"} + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, updated_topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + NOTE = """ + After applying route-map on neighbor verify default BGP route IPv4 IPv6 route populated in R2 BGP and routing table , verify using "show ip bgp json" "show ipv6 bgp json" "show ip route json" "show ip route json" + Sv42 and Sv62 route should not be present on R2 + """ + step(NOTE) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + + result = verify_fib_routes( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Static routes are not expected due to conditions \nError: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Static routes are not expected due to conditions\n Error: {}".format( + tc_name, result + ) + + step("Change IPv4 prefix-list Pv42 and and IPv6 prefix-list Pv62 deny to permit") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv42": [ + { + "seqid": "1", + "network": NETWORK2_1["ipv4"], + "action": "permit", + } + ] + }, + "ipv6": { + "Pv62": [ + { + "seqid": "1", + "network": NETWORK2_1["ipv6"], + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r3": {"prefix_lists": ["Pv42", "Pv62"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + NOTE = """Default BGP route and IPv4 ( Sv42) , IPv6 (Sv62) route populated in R2 BGP and routing table""" + step(NOTE) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("IPv4 prefix-list Pv4 and and IPv6 prefix-list Pv6 permit to deny ") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + {"seqid": "1", "network": NETWORK1_1["ipv4"], "action": "deny"} + ] + }, + "ipv6": { + "Pv6": [ + {"seqid": "1", "network": NETWORK1_1["ipv6"], "action": "deny"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + NOTE = """ + Verify default-originate route (IPv4 and IPv6 ) not present on R2 + IPv4 ( Sv42) , IPv6 (Sv62) route populated in R2 BGP + """ + step(NOTE) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n default-route in FIB is not expected due to conditions \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n default-route in RIB is not expected due to conditions \n Error: {}".format( + tc_name, result + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_default_originate_route_map_in_IN_p1(request): + """Verify BGP default originate route-map with IN route-map""" + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , EBGP neighbor between R1 and R2") + step("Configure IPv4 and IPv6 , IBGP neighbor between R1 and R0") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": 1000, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": r2_local_as, + } + }, + "r3": { + "bgp": { + "local_as": r3_local_as, + } + }, + "r4": { + "bgp": { + "local_as": r4_local_as, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Configure 2 IPv4 and 2 IPv6, Static route on R0 with next-hop as Null0 IPv4 route Sv41, Sv42, IPv6 route Sv61 Sv62" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verifyIPv4 and IPv6 static routes are configure and up on R0 ") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute static knob on R0 , for R0 to R1 IPv4 and IPv6 neighbor" + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 route received on R1 ") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPv4 prefix-list Pv4 and and IPv6 prefix-list Pv6 on R1 to match BGP route Sv41, Sv42, IPv6 route Sv61 Sv62" + ) + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + { + "seqid": "2", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R1") + input_dict = {"r1": {"prefix_lists": ["Pv4", "Pv6"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure IPv4 and IPv6 route-map RMv4 and RMv6 matching prefix-list Pv4 and Pv6 with deny option on R1" + ) + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Apply route-map IN direction in R1 (R1 to R0) IPv4 and IPv6 neighbor") + updated_topo = topo + updated_topo["routers"]["r0"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r0") + updated_topo["routers"]["r1"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r1") + updated_topo["routers"]["r2"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r2") + updated_topo["routers"]["r3"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r3") + updated_topo["routers"]["r4"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r4") + + local_as_r1 = get_dut_as_number(tgen, dut="r1") + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RMv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r0": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RMv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, updated_topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + STEP = "After applying route-map verify that IPv4 route Sv41, Sv42, IPv6 route Sv61 Sv62 should not present on R1 BGP and routing table " + step(STEP) + + step( + "After applying route-map verify that IPv4 route Sv41, Sv42, IPv6 route Sv61 Sv62 should not present on R1 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + + result = verify_fib_routes( + tgen, addr_type, "r1", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n default-route in FIB is not expected due to conditions \nError: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, addr_type, "r1", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n default-route in FIB is not expected due to conditions \nError: {}".format( + tc_name, result + ) + # Routes should come to dut but not the shown in RIB thus verifying using show ip bgp nbr xxx received route + step( + " Verify the received routes \n using 'show ip bgp nbr xxx received route' in Router R1" + ) + expected_routes = { + "ipv4": [ + {"network": NETWORK1_1["ipv4"], "nexthop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK2_1["ipv4"], "nexthop": NEXT_HOP_IP["ipv4"]}, + ], + "ipv6": [ + {"network": NETWORK1_1["ipv6"], "nexthop": NEXT_HOP_IP["ipv6"]}, + {"network": NETWORK2_1["ipv6"], "nexthop": NEXT_HOP_IP["ipv6"]}, + ], + } + result = verify_bgp_received_routes_from_neighbor( + tgen, topo, dut="r1", peer="r0", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure default-originate on R1 for R1 to R2 IPv4 and IPv6 neighbor ") + local_as_r1 = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as_r1, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Default originate knob is configured and default route advertised to R2 , verify on R1 " + ) + expected_routes = { + "ipv4": [ + {"network": "0.0.0.0/0", "nexthop": ""}, + ], + "ipv6": [ + {"network": "::/0", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r1", peer="r2", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify the Default route Route in FIB in R2") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change route-map RMv4 and RMv6 from deny to permit") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + NOTE = """After changing route-map to permit verify that IPv4 routes Sv41, Sv42, IPv6 routes Sv61 Sv62 present on R1 BGP and routing table , using "show ip route " "show ip bgp nbr xxx received route " "show ipv6 route " "show ipv6 bgp nbr xxx receied route """ + step(NOTE) + expected_routes = { + "ipv4": [{"network": NETWORK1_1["ipv4"], "nexthop": NEXT_HOP_IP["ipv4"]}], + "ipv6": [{"network": NETWORK1_1["ipv6"], "nexthop": NEXT_HOP_IP["ipv4"]}], + } + result = verify_bgp_received_routes_from_neighbor( + tgen, topo, dut="r1", peer="r0", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure default static route (IPv4 and IPv6) on R2 nexthop as R1 ") + NEXT_HOP_IP_R1 = {} + r1_r2_ipv4_neighbor = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + r1_r2_ipv6_neighbor = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + NEXT_HOP_IP_R1["ipv4"] = r1_r2_ipv4_neighbor + NEXT_HOP_IP_R1["ipv6"] = r1_r2_ipv6_neighbor + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": "0.0.0.0/0", + "next_hop": NEXT_HOP_IP_R1["ipv4"], + }, + { + "network": "0::0/0", + "next_hop": NEXT_HOP_IP_R1["ipv6"], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Static default route is taking preference over BGP default routes , BGP default route is inactive IN RIB and static is up and installed in RIB and FIB " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_nxt_hop} + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP[addr_type], + "protocol": "static", + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +def test_verify_default_originate_after_removing_default_originate_p1(request): + """Verify BGP default route after removing default-originate""" + + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure EBGP between R0 to R1 and IBGP between R1 to R2") + step("Configure EBGP between R2 to R3 and IBGP between R3 to R4") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": 2000, + } + }, + "r2": { + "bgp": { + "local_as": 2000, + } + }, + "r3": { + "bgp": { + "local_as": 5000, + } + }, + "r4": { + "bgp": { + "local_as": 5000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure IPv4 and IPv6 static route on R0 and R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + }, + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + }, + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R0 and R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Configure redistribute connected and static on R0 (R0-R1) on R4 ( R4-R3) IPv4 and IPv6 address family " + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 static route are configured and up on R1 and R3") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R1 and R3") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate on R1 for R1 to R2 neighbor for IPv4 and IPv6 peer " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "Verify all the static , connected and loopback routes from R0,R1,R3 and R4 is receieved on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify the Default Originate on R2 nexthop as R1") + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is expected but found in FIB -> {}".format( + tc_name, result + ) + + step( + "Configure default-originate on R3 for R3 to R2 neighbor for IPv4 and IPv6 peer " + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + STEP = """After configuring the Default Originate From R3 --> R2 + Both Default routes from R1 and R3 Should present in R2 BGP RIB + The Deafult Route from iBGP is prefferedover EBGP thus + Default Route From R1->r2 should only present in R2 FIB """ + step(STEP) + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: Only IBGP default originate is expected in FIB over EBGP {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "No change on static and connected routes which got advertised from R0, R1, R3 and R4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + " Remove default-originate on R1 for R1 to R2 neighbor for IPv4 and IPv6 peer " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify the Default Originate reoute from R1 to r2 is removed in R2 ") + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After removing the default originate the route should not be present in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After removing the default originate the route should not be present in RIB \n Error: {}".format( + tc_name, result + ) + + NOTE = """ after removing the Default originate from R1-->R2 + Verify the BGP Default route received from R3 is present in both BGP RIB and FIB on R2 + """ + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "No change on static and connected routes which got advertised from R0, R1, R3 and R4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Remove default-originate on R3 for R3 to R2 neighbor for IPv4 and IPv6 peer " + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"delete": True}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "After removing default originate , verify default IPv4 and IPv6 BGP routes removed on R2 from R1 ( next-hop as R3) " + ) + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After removing the default originate the route should not be present in FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After removing the default originate the route should not be present in RIB \n Error: {}".format( + tc_name, result + ) + step( + "No change on static and connected routes which got advertised from R0, R1, R3 and R4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +def test_verify_default_originate_route_with_GR_p1(request): + """ "Verify default-originate route with GR " """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPV4 and IPV6 IBGP between R1 and R2 ") + step("Configure IPV4 and IPV6 EBGP between R2 to R3 ") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": 1000, + } + }, + "r3": { + "bgp": { + "local_as": r3_local_as, + } + }, + "r4": { + "bgp": { + "local_as": r4_local_as, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Configure per peer Graceful restart on R2 ( restarting router) and R3 helper router " + ) + input_dict = { + "r2": { + "bgp": { + "local_as": get_dut_as_number(tgen, "r2"), + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + } + }, + "r3": { + "bgp": { + "local_as": get_dut_as_number(tgen, "r3"), + "graceful-restart": {"graceful-restart-helper": True}, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r2", peer="r3") + + step("verify Graceful restart at R2") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r3" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "Configure default-originate on R1 for R1-R2 neighbor for IPv4 and IPv6 BGP peers " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "R2 received default-originate routes and advertised it to R3 , verify on R2 and R3" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step(" Kill BGPd session on R2") + kill_router_daemons(tgen, "r2", ["bgpd"]) + start_router_daemons(tgen, "r2", ["bgpd"]) + + step("verify default route is relearned after clear bgp on R2 on BGP RIB and") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_3.py b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_3.py new file mode 100644 index 0000000..73b2c19 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_bgp_default_originate_topo1_3.py @@ -0,0 +1,2599 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +""" +Following tests are covered. +10. Verify default-originate route after BGP and FRR process restart +11. Verify default-originate route after shut/no shut and clear BGP neighbor +""" +import os +import sys +import time +import pytest +from lib.topolog import logger +import json + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + modify_as_number, + clear_bgp, + verify_bgp_rib, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, +) +from lib.common_config import ( + interface_status, + verify_prefix_lists, + verify_fib_routes, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, + step, + required_linux_kernel_version, + stop_router, + start_router, + create_route_maps, + create_prefix_lists, + get_frr_ipv6_linklocal, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, + retry, +) + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 +# Global variables +NETWORK1_1 = {"ipv4": "198.51.1.1/32", "ipv6": "2001:DB8::1:1/128"} +NETWORK2_1 = {"ipv4": "198.51.1.2/32", "ipv6": "2001:DB8::1:2/128"} +DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +r0_connected_address_ipv4 = "192.168.0.0/24" +r0_connected_address_ipv6 = "fd00::/64" +r1_connected_address_ipv4 = "192.168.1.0/24" +r1_connected_address_ipv6 = "fd00:0:0:1::/64" +r3_connected_address_ipv4 = "192.168.2.0/24" +r3_connected_address_ipv6 = "fd00:0:0:2::/64" +r4_connected_address_ipv4 = "192.168.3.0/24" +r4_connected_address_ipv6 = "fd00:0:0:3::/64" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_originate_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls micronet initialization functions. + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_R1, DEFAULT_ROUTE_NXT_HOP_R3 + global R0_NETWORK_LOOPBACK, R0_NETWORK_LOOPBACK_NXTHOP, R1_NETWORK_LOOPBACK + global R0_NETWORK_CONNECTED, R0_NETWORK_CONNECTED_NXTHOP, R1_NETWORK_CONNECTED, R1_NETWORK_CONNECTED_NXTHOP + global R4_NETWORK_LOOPBACK, R4_NETWORK_LOOPBACK_NXTHOP, R3_NETWORK_LOOPBACK + global R4_NETWORK_CONNECTED, R4_NETWORK_CONNECTED_NXTHOP, R3_NETWORK_CONNECTED, R3_NETWORK_CONNECTED_NXTHOP + + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + # There are the global varibles used through out the file these are acheived only after building the topology. + + r0_loopback_address_ipv4 = topo["routers"]["r0"]["links"]["lo"]["ipv4"] + r0_loopback_address_ipv4_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv4" + ].split("/")[0] + r0_loopback_address_ipv6 = topo["routers"]["r0"]["links"]["lo"]["ipv6"] + r0_loopback_address_ipv6_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv6" + ].split("/")[0] + + r1_loopback_address_ipv4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_loopback_address_ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r1_loopback_address_ipv6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_loopback_address_ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + r4_loopback_address_ipv4 = topo["routers"]["r4"]["links"]["lo"]["ipv4"] + r4_loopback_address_ipv4_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv4" + ].split("/")[0] + r4_loopback_address_ipv6 = topo["routers"]["r4"]["links"]["lo"]["ipv6"] + r4_loopback_address_ipv6_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv6" + ].split("/")[0] + + r3_loopback_address_ipv4 = topo["routers"]["r3"]["links"]["lo"]["ipv4"] + r3_loopback_address_ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r3_loopback_address_ipv6 = topo["routers"]["r3"]["links"]["lo"]["ipv6"] + r3_loopback_address_ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + R0_NETWORK_LOOPBACK = { + "ipv4": r0_loopback_address_ipv4, + "ipv6": r0_loopback_address_ipv6, + } + R0_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_LOOPBACK = { + "ipv4": r1_loopback_address_ipv4, + "ipv6": r1_loopback_address_ipv6, + } + + R0_NETWORK_CONNECTED = { + "ipv4": r0_connected_address_ipv4, + "ipv6": r0_connected_address_ipv6, + } + R0_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r0_loopback_address_ipv4_nxt_hop, + "ipv6": r0_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_CONNECTED = { + "ipv4": r1_connected_address_ipv4, + "ipv6": r1_connected_address_ipv6, + } + R1_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R4_NETWORK_LOOPBACK = { + "ipv4": r4_loopback_address_ipv4, + "ipv6": r4_loopback_address_ipv6, + } + R4_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_LOOPBACK = { + "ipv4": r3_loopback_address_ipv4, + "ipv6": r3_loopback_address_ipv6, + } + R4_NETWORK_CONNECTED = { + "ipv4": r4_connected_address_ipv4, + "ipv6": r4_connected_address_ipv6, + } + R4_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r4_loopback_address_ipv4_nxt_hop, + "ipv6": r4_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_CONNECTED = { + "ipv4": r3_connected_address_ipv4, + "ipv6": r3_connected_address_ipv6, + } + R3_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + # populating the nexthop for default routes + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_verify_default_originate_after_BGP_and_FRR_restart_p2(request): + """ + Summary: "Verify default-originate route after BGP and FRR process restart " + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure EBGP between R0 to R1 and IBGP between R1 to R2") + step("Configure EBGP between R2 to R3 and IBGP between R3 to R4") + input_dict = { + "r0": { + "bgp": { + "local_as": 999, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": 1000, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert ( + BGP_CONVERGENCE is True + ), " Failed convergence after chaning the AS number :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure IPv4 and IPv6 static route (Sv4 , Sv6) on R0 and (S1v4, S1v6)on R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static route on R0 \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static route on R4 \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed Route {} not found in R0 FIB \n Error: {}".format( + tc_name, NETWORK1_1, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed Route {} not found in R4 FIB \n Error: {}".format( + tc_name, NETWORK2_1, result + ) + + step( + "Configure redistribute connected and static on R0 (R0-R1) on R4 ( R4-R3) IPv4 and IPv6 address family" + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure the static route \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed : Redistributed routes from R0 is not learned in Router R1 RIB \n Error: {}".format( + tc_name, result + ) + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed : Redistributed routes from R0 is not learned in Router R1 FIB \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R3") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed : Redistributed routes from R4 is not learned in Router R3 RIB \n Error: {}".format( + tc_name, result + ) + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Redistributed routes from R4 is not learned in Router R3 FIB \n Error: {}".format( + tc_name, result + ) + + step("Configure IPv4 and IPv6 prefix-list on R1 for (Sv4 , Sv6) route") + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + } + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix lists \n Error: {}".format( + tc_name, result + ) + + step("Verify the Prefix - lists") + input_dict = {"r3": {"prefix_lists": ["Pv4", "Pv6"]}} + result = verify_prefix_lists(tgen, input_dict) + assert ( + result is True + ), "Testcase {} : Failed to verify the prefix lists in router R3 \n Error: {}".format( + tc_name, result + ) + + step("Configure IPv4 (RMv4) and IPv6 (RMv6) route-map on R1") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route-map \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default originate with route-map RMv4 and RMv6 for IPv4 and IPv6 bgp neighbors on R1 ( R1-R2) " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the default-originate in R1 towards R2 \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 default route received on R2 with R1 nexthop ") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + ) + assert ( + result is True + ), "Testcase {} : Failed : Default routes are not learned in R2 FIB \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + ) + assert ( + result is True + ), "Testcase {} : Failed : Default routes are not learned in R2 RIB\n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute connected and static on R1 IPv4 and IPv6 address family" + ) + redistribute_static = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 and IPv6 static and loopback route advertised from R4 and R0 are received on R2" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure default-originate on R3 for R3 to R2 IPv4 and IPv6 BGP neighbors ") + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + STEP = """After configuring the Default Originate From R3 --> R2 + Both Default routes from R1 and R3 Should present in R2 BGP RIB. + 'The Deafult Route from iBGP is preffered over EBGP' thus + Default Route From R1->r2 should only present in R2 FIB """ + step(STEP) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n IBGP default route should be preffered over EBGP default-originate \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify the default route from R1 is recieved both on RIB and FIB on R2") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify the static and loopback route advertised from R0 and R4 are received on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Allow for verification of the table version + # as that restarting bgp on one router will cause + # r2's version number should go up as that r1 + # is not directly connected to r2 + # where? + @retry(retry_timeout=60) + def verify_version_upgrade(dut, version): + dut_new_ipv4_uni_json = json.loads(dut.vtysh_cmd("show bgp ipv4 uni json")) + + logger.info( + "New version: {} comparing to old {}".format( + dut_new_ipv4_uni_json["tableVersion"], version + ) + ) + if version >= dut_new_ipv4_uni_json["tableVersion"]: + return False + + return True + + r2 = tgen.gears["r2"] + + r2_bgp_ipv4_uni_json = json.loads(r2.vtysh_cmd("show bgp ipv4 uni json")) + curr_version = r2_bgp_ipv4_uni_json["tableVersion"] + + step("BGP Daemon restart operation") + routers = ["r1", "r2"] + for dut in routers: + step( + "Restart BGPD process on {}, when all the processes are running use watchfrr ".format( + dut + ) + ) + + kill_router_daemons(tgen, dut, ["bgpd"]) + # Let's ensure that r2's version has upgraded and then + # let's check that the default route goes through + # r3's connection. + if dut == "r1": + step("Ensure that r2 prefers r3's default route at this point in time") + verify_version_upgrade(r2, curr_version) + # write code to ensure r1 neighbor is down + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n IBGP default route should be prefeered over EBGP \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + start_router_daemons(tgen, dut, ["bgpd"]) + + if dut == "r2": + + @retry(60) + def check_pfx_received_sent(dut): + output = json.loads(dut.vtysh_cmd("show bgp ipv4 uni summ json")) + + logger.info(output) + if output["peerCount"] != 2: + logger.info(output["peerCount"]) + logger.info("pc") + return False + + if output["peers"]["192.168.1.1"]["state"] != "Established": + logger.info("Not Established 192.168.1.1") + return False + + if output["peers"]["192.168.2.2"]["state"] != "Established": + logger.info("Not established 192.168.2.2") + return False + + if output["peers"]["192.168.1.1"]["pfxRcd"] != 6: + logger.info("1.1 prxRcd") + return False + + if output["peers"]["192.168.1.1"]["pfxSnt"] != 3: + logger.info("1.1 pfxsent") + return False + + if output["peers"]["192.168.2.2"]["pfxRcd"] != 4: + logger.info("2.2 pfxRcd") + return False + + if output["peers"]["192.168.2.2"]["pfxSnt"] != 9: + logger.info("2.2 pfxsnt") + return False + + return True + + check_pfx_received_sent(r2) + step( + "Verify the default route from R1 is is recieved both on RIB and FIB on R2" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify the static and loopback route advertised from R0 and R4 are received on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restarting FRR routers operation") + """ + NOTE : Verify that iBGP default route is preffered over eBGP default route + """ + routers = ["r1", "r2"] + for dut in routers: + step( + "Restart FRR router process on {}, when all the processes are running use watchfrr ".format( + dut + ) + ) + + stop_router(tgen, dut) + start_router(tgen, dut) + + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), " Testcase {} : After Restarting {} Convergence Failed".format(tc_name, dut) + + step("After restarting the FRR Router Verify the default originate ") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify the default route from R1 is is recieved both on RIB and FIB on R2" + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed\n IBGP default route should be preffered over EBGP default route \n Error: {}".format( + tc_name, result + ) + + step( + "Verify the static and loopback route advertised from R0 and R4 are received on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_default_originate_after_shut_no_shut_bgp_neighbor_p1(request): + """ + Summary: "Verify default-originate route after shut/no shut and clear BGP neighbor " + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure EBGP between R0 to R1 and IBGP between R1 to R2") + step("Configure EBGP between R2 to R3 and IBGP between R3 to R4") + input_dict = { + "r0": { + "bgp": { + "local_as": 999, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": 1000, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure one IPv4 and one IPv6 static route on R0 and R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static route configured on R0 and R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute connected and static on R0 (R0-R1) on R4 ( R4-R3) IPv4 and IPv6 address family" + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + }, + { + "redist_type": "connected", + }, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + }, + { + "redist_type": "connected", + }, + ] + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + }, + { + "redist_type": "connected", + }, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + }, + { + "redist_type": "connected", + }, + ] + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "connected", + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "connected", + } + ] + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 static route configured on R1 from R0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static route configured on R3 from R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate on R1 for R1 to R2 neighbor for IPv4 and IPv6 peer " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 bgp default route received on R2 nexthop as R1") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + step( + "After configuring default-originate command , verify default routes are advertised on R2 from R0 and R4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate on R3 for R3 to R2 neighbor for IPv4 and IPv6 peer" + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + STEP = """After configuring the Default Originate From R3 --> R2 + Both Default routes from R1 and R3 Should present in R2 BGP RIB. + 'The Deafult Route from iBGP is preffered over EBGP' thus + Default Route From R1->r2 should only present in R2 FIB """ + step(STEP) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n IBGP default route should be preffered over EBGP \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify the default route from R1 is recieved both on RIB and FIB on R2") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "After configuring default-originate command , verify static ,connected and loopback routes are advertised on R2 from R0 and R4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + # updating the topology with the updated AS-Number to avoid conflict in con configuring the AS + updated_topo = topo + updated_topo["routers"]["r0"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r0") + updated_topo["routers"]["r1"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r1") + updated_topo["routers"]["r2"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r2") + updated_topo["routers"]["r3"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r3") + updated_topo["routers"]["r4"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r4") + + step( + "Shut R1 to R2 IPv4 and IPv6 BGP neighbor from R1 IPv4 and IPv6 address family " + ) + + local_as = get_dut_as_number(tgen, dut="r1") + shut_neighbor = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"shutdown": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"shutdown": True}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r2"]["links"]["r1"]["interface"] + input_dict = {"r2": {"interface_list": [interface], "status": "down"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Bring down interface failed ! \n Error: {}".format( + tc_name, result + ) + + step( + "Verify IPv4 and IPv6 default static and loopback route which received from R1 are deleted from R2" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n after shutting down interface routes are not expected \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n after shutting down interface routes are not expected \n Error: {}".format( + tc_name, result + ) + + step("verify that No impact on IPv4 IPv6 and default route received from R3 ") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "No-Shut R1 to R2 IPv4 and IPv6 BGP neighbor from R1 IPv4 and IPv6 address family " + ) + local_as = get_dut_as_number(tgen, dut="r1") + shut_neighbor = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"shutdown": False}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"shutdown": False}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r2"]["links"]["r1"]["interface"] + input_dict = {"r2": {"interface_list": [interface], "status": "up"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Bring up interface failed ! \n Error: {}".format(tc_name, result) + + step( + "After no shut Verify IPv4 and IPv6 bgp default route next hop as R1 , static ,connected and loopback received on R2 from r0 and r4 " + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Shut R3 to R2 IPv4 and IPv6 BGP neighbor from R2 IPv4 and IPv6 address family" + ) + local_as = get_dut_as_number(tgen, dut="r3") + shut_neighbor = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"shutdown": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"shutdown": True}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r2"]["links"]["r3"]["interface"] + input_dict = {"r2": {"interface_list": [interface], "status": "down"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Bring down interface failed ! \n Error: {}".format( + tc_name, result + ) + + step( + "Verify IPv4 and IPv6 default static and loopback route which received from R3 are deleted from R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed\n After shutting down the interface routes are not expected \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After shutting down the interface routes are not expected \n Error: {}".format( + tc_name, result + ) + + step("Verify that Default route is removed i.e advertised from R3") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After shutting down the interface Default route are not expected \n Error: {}".format( + tc_name, result + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n After shutting down the interface Default route are not expected \n Error: {}".format( + tc_name, result + ) + + step("Verify that No impact on IPv4 IPv6 and default route received from R1") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_fib_routes( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "No-Shut R3 to R2 IPv4 and IPv6 BGP neighbor from R2 IPv4 and IPv6 address family" + ) + local_as = get_dut_as_number(tgen, dut="r3") + shut_neighbor = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"shutdown": False}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"shutdown": False}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r2"]["links"]["r3"]["interface"] + input_dict = {"r2": {"interface_list": [interface], "status": "up"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Bring up interface failed ! \n Error: {}".format(tc_name, result) + + step( + "Verify that a static ,connected and loopback routes are received from R0 and R4 on R2 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("verify that default route is received on R2 from R1") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify that default route is received on R2 from R3") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Clear IPv4 and IP6 BGP session from R2 and R1 one by one ") + routers = ["r1", "r2"] + for dut in routers: + for addr_type in ADDR_TYPES: + + clear_bgp(tgen, addr_type, dut) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("verify that default route is received on R2 from R3") + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + DEFAULT_ROUTE_NXT_HOP = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify the static , loopback and connected routes received from r0 and r4" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Shut BGP neighbor interface R2 (R2 to R1) link ") + intf_r2_r1 = topo["routers"]["r2"]["links"]["r1"]["interface"] + shutdown_bringup_interface(tgen, "r2", intf_r2_r1, False) + + step("Verify the bgp Convergence ") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + BGP_CONVERGENCE is not True + ), " :Failed After shutting interface BGP convergence is expected to be faileed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify that default route from R1 got deleted from BGP and RIB table") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed\n After shuting interface default route should be removed from RIB \n Error: {}".format( + tc_name, result + ) + + step("No - Shut BGP neighbor interface R2 (R2 to R1) link ") + intf_r2_r1 = topo["routers"]["r2"]["links"]["r1"]["interface"] + shutdown_bringup_interface(tgen, "r2", intf_r2_r1, True) + + step("Verify the bgp Convergence ") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step("verify that default route is received on R2 from R3") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify the static , loopback and connected routes received from r0 and r4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Shut link from R3 to R2 from R3") + intf_r3_r2 = topo["routers"]["r3"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, "r3", intf_r3_r2, False) + + step("Verify the bgp Convergence ") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + BGP_CONVERGENCE is not True + ), " :Failed \nAfter Shuting the interface BGP convegence is expected to be failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify that default route from R3 got deleted from BGP and RIB table") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("No-Shut link from R3 to R2 from R3") + + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + + DEFAULT_ROUTE_NXT_HOP_1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_nxt_hop} + + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + + DEFAULT_ROUTE_NXT_HOP_3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_nxt_hop} + + intf_r3_r2 = topo["routers"]["r3"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, "r3", intf_r3_r2, True) + + step("Verify the bgp Convergence ") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, expected=True) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step("verify that default route is received on R2 from R3") + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify the static , loopback and connected routes received from r0 and r4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R0_NETWORK_LOOPBACK[addr_type]], + "next_hop": R0_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R0_NETWORK_CONNECTED[addr_type]], + "next_hop": R0_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_LOOPBACK[addr_type]], + "next_hop": R4_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R4_NETWORK_CONNECTED[addr_type]], + "next_hop": R4_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_fib_routes(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/test_default_orginate_vrf.py b/tests/topotests/bgp_default_originate/test_default_orginate_vrf.py new file mode 100644 index 0000000..4dedac5 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_default_orginate_vrf.py @@ -0,0 +1,1377 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +import os +import sys +import time +import pytest +from time import sleep +from copy import deepcopy +from lib.topolog import logger + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +from lib.bgp import ( + verify_bgp_convergence, + verify_graceful_restart, + create_router_bgp, + verify_router_id, + modify_as_number, + verify_as_numbers, + clear_bgp_and_verify, + clear_bgp, + verify_bgp_rib, + get_prefix_count_route, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, + verify_bgp_advertised_routes_from_neighbor, + verify_bgp_received_routes_from_neighbor, +) +from lib.common_config import ( + interface_status, + verify_prefix_lists, + verify_rib, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, + step, + required_linux_kernel_version, + stop_router, + start_router, + create_route_maps, + create_prefix_lists, + get_frr_ipv6_linklocal, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, + delete_route_maps, +) + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 + + +# Global variables +NETWORK1_1 = {"ipv4": "198.51.1.1/32", "ipv6": "2001:DB8::1:1/128"} +NETWORK2_1 = {"ipv4": "198.51.1.2/32", "ipv6": "2001:DB8::1:2/128"} +NETWORK5_1 = {"ipv4": "198.51.1.3/32", "ipv6": "2001:DB8::1:3/128"} +NETWORK5_2 = {"ipv4": "198.51.1.4/32", "ipv6": "2001:DB8::1:4/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +r0_connected_address_ipv4 = "192.168.0.0/24" +r0_connected_address_ipv6 = "fd00::/64" +r1_connected_address_ipv4 = "192.168.1.0/24" +r1_connected_address_ipv6 = "fd00:0:0:1::/64" +r3_connected_address_ipv4 = "192.168.2.0/24" +r3_connected_address_ipv6 = "fd00:0:0:2::/64" +r4_connected_address_ipv4 = "192.168.3.0/24" +r4_connected_address_ipv6 = "fd00:0:0:3::/64" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_orginate_vrf.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_R1, DEFAULT_ROUTE_NXT_HOP_R3 + global R1_NETWORK_LOOPBACK, R1_NETWORK_LOOPBACK_NXTHOP + global R0_NETWORK_CONNECTED_NXTHOP, R1_NETWORK_CONNECTED, R1_NETWORK_CONNECTED_NXTHOP + global R3_NETWORK_LOOPBACK, R3_NETWORK_LOOPBACK_NXTHOP + global R3_NETWORK_CONNECTED, R3_NETWORK_CONNECTED_NXTHOP + + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + # There are the global varibles used through out the file these are acheived only after building the topology. + + r0_loopback_address_ipv4 = topo["routers"]["r0"]["links"]["lo"]["ipv4"] + r0_loopback_address_ipv4_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv4" + ].split("/")[0] + r0_loopback_address_ipv6 = topo["routers"]["r0"]["links"]["lo"]["ipv6"] + r0_loopback_address_ipv6_nxt_hop = topo["routers"]["r0"]["links"]["r1"][ + "ipv6" + ].split("/")[0] + + r1_loopback_address_ipv4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_loopback_address_ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r1_loopback_address_ipv6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_loopback_address_ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + r4_loopback_address_ipv4 = topo["routers"]["r4"]["links"]["lo"]["ipv4"] + r4_loopback_address_ipv4_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv4" + ].split("/")[0] + r4_loopback_address_ipv6 = topo["routers"]["r4"]["links"]["lo"]["ipv6"] + r4_loopback_address_ipv6_nxt_hop = topo["routers"]["r4"]["links"]["r3"][ + "ipv6" + ].split("/")[0] + + r3_loopback_address_ipv4 = topo["routers"]["r3"]["links"]["lo"]["ipv4"] + r3_loopback_address_ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv4" + ].split("/")[0] + r3_loopback_address_ipv6 = topo["routers"]["r3"]["links"]["lo"]["ipv6"] + r3_loopback_address_ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"][ + "ipv6" + ].split("/")[0] + + R1_NETWORK_LOOPBACK = { + "ipv4": r1_loopback_address_ipv4, + "ipv6": r1_loopback_address_ipv6, + } + R1_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R1_NETWORK_CONNECTED = { + "ipv4": r1_connected_address_ipv4, + "ipv6": r1_connected_address_ipv6, + } + R1_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r1_loopback_address_ipv4_nxt_hop, + "ipv6": r1_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_LOOPBACK = { + "ipv4": r3_loopback_address_ipv4, + "ipv6": r3_loopback_address_ipv6, + } + R3_NETWORK_LOOPBACK_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + R3_NETWORK_CONNECTED = { + "ipv4": r3_connected_address_ipv4, + "ipv6": r3_connected_address_ipv6, + } + R3_NETWORK_CONNECTED_NXTHOP = { + "ipv4": r3_loopback_address_ipv4_nxt_hop, + "ipv6": r3_loopback_address_ipv6_nxt_hop, + } + + # populating the nexthop for default routes + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### +def test_verify_default_originate_route_with_non_default_VRF_p1(request): + """ + "Verify default-originate route with non-default VRF" + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + # these steps are implemented as base toplopgy setup + step("Configure IPV4 and IPV6 IBGP between R1 and R2 default VRF") + step("Configure IPV4 and IPV6 EBGP between R2 to R3 non-default VRF (RED)") + step( + "Configure IPv4 and IP6 loopback address on R1 default and R3 non-default (RED) VRF" + ) + step("After changing the BGP AS Path Verify the BGP Convergence") + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + step( + "Configure IPv4 and IPv6 static route on R1 default and R3 non-default (RED) VRF with nexthop as Null ( different static route on each side)" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static routes in router R1 default vrf \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure static route in R3 non default vrf RED \n Error: {}".format( + tc_name, result + ) + + step( + "Verify IPv4 and IPv6 static route configured on R1 default vrf and R3 non-default (RED) vrf" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_rib(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed: Routes configured on vrf is not seen in R1 default VRF FIB \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + } + } + result = verify_rib(tgen, addr_type, "r3", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed : Routes configured in non-defaul vrf in R3 FIB is \n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute connected and static on R1 (R1-R2) and on R3 ( R2-R3 RED VRF) IPv4 and IPv6 address family " + ) + redistribute_static = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + "r3": { + "bgp": { + "local_as": 3000, + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + }, + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure the redistribute on R1 and R3 \n Error: {}".format( + tc_name, result + ) + + step( + "Verify IPv4 and IPv6 static route configured on R1 received as BGP routes on R2 default VRF " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Verify IPv4 and IPv6 static route configured on R3 received as BGP routes on R2 non-default VRF " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + "vrf": "RED", + }, + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate on R1 for R1 to R2 neighbor for IPv4 and IPv6 peer" + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + snapshot1 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3", vrf="RED") + + step( + "Configure default-originate on R3 for R3 to R2 neighbor (RED VRF) for IPv4 and IPv6 peer" + ) + + default_originate_config = { + "r3": { + "bgp": { + "local_as": "3000", + "vrf": "RED", + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 and IPv6 bgp default route and static route received on R2 VRF red nexthop as R3" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + "vrf": "RED", + }, + ] + } + } + result = verify_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verify Out-prefix count incremented for IPv4/IPv6 default route on VRF red") + snapshot2 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3", vrf="RED") + step("verifying the prefix count incrementing or not ") + isIPv4prefix_incremented = False + isIPv6prefix_incremented = False + if snapshot1["ipv4_count"] < snapshot2["ipv4_count"]: + isIPv4prefix_incremented = True + if snapshot1["ipv6_count"] < snapshot2["ipv6_count"]: + isIPv6prefix_incremented = True + + assert ( + isIPv4prefix_incremented is True + ), "Testcase {} : Failed Error: IPV4 Prefix is not incremented on receiveing ".format( + tc_name + ) + + step("Configure import VRF red on R2 for IPV4 and IPV6 BGP peer") + step("Importing the non-default vrf in default VRF ") + local_as = get_dut_as_number(tgen, "r2") + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "RED"}}}, + "ipv6": {"unicast": {"import": {"vrf": "RED"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "Verify VRF RED IPv4 and IPv6, default-originate, \n static and loopback route are imported to R2 default VRF table ,\n default-originate route coming from VRF red should not active on R2 default VRF table" + ) + step("verifying the static routes connected and loop back routes") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + STEP = """ After importing non defualt VRF into default vrf . + verify that the default originate from R1 --> R2(non -default) is preffered over R3 --> R2 + because the Default Route prefers iBGP over eBGP over + Default Route from R1 Should be present in BGP RIB and FIB + Default Route from R3 Should be present only in BGP RIB not in FIB + """ + step(STEP) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Configure import VRF default on R2 (R2-R3) RED VRF for IPV4 and IPV6 BGP peer" + ) + step("Importing the default vrf in non-default VRF ") + local_as = "2000" + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "vrf": "RED", + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "default"}}}, + "ipv6": {"unicast": {"import": {"vrf": "default"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "Default VR, IPv4 and IPv6 , default-originate, \n static and loopback route are imported to R2 VRF RED table \n, default-originate route coming from VRF red should not active on R2 default VRF table" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + "vrf": "RED", + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + "vrf": "RED", + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + "vrf": "RED", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + STEP = """ After importing defualt VRF into non default vrf . + verify that the default originate from R1 --> R2(non -default) is preffered over R3 --> R2 + because the Default Route prefers iBGP over eBGP over + Default Route from R1 Should be present in BGP RIB and FIB + Default Route from R3 Should be present only in BGP RIB not in FIB + """ + step(STEP) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + "vrf": "RED", + } + ] + } + } + + result = verify_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + "vrf": "RED", + } + ] + } + } + + result = verify_rib( + tgen, + addr_type, + "r2", + static_routes_input, + next_hop=DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + expected=False, + ) + assert result is not True, "Testcase {} : Failed {} \n Error: {}".format( + tc_name, STEP, result + ) + + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + "vrf": "RED", + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R3[addr_type], + "vrf": "RED", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Remove import VRF configure in step 8 and then remove import VRF configured on step 9" + ) + local_as = get_dut_as_number(tgen, "r2") + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "RED", "delete": True}}}, + "ipv6": {"unicast": {"import": {"vrf": "RED", "delete": True}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that the routes imported from non default VRF - RED is removed") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [R3_NETWORK_LOOPBACK[addr_type]], + "next_hop": R3_NETWORK_LOOPBACK_NXTHOP[addr_type], + }, + { + "network": [R3_NETWORK_CONNECTED[addr_type]], + "next_hop": R3_NETWORK_CONNECTED_NXTHOP[addr_type], + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n routes imported from non default VRF is not expected Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n routes imported from non default VRF is not expected \nError: {}".format( + tc_name, result + ) + + step( + "Remove import VRF configure in step 8 and then remove import VRF configured on step 9" + ) + local_as = "2000" + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": {"import": {"vrf": "default", "delete": True}} + }, + "ipv6": { + "unicast": {"import": {"vrf": "default", "delete": True}} + }, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step("Verify that the routes impoted from default VRF is removed") + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [R1_NETWORK_LOOPBACK[addr_type]], + "next_hop": R1_NETWORK_LOOPBACK_NXTHOP[addr_type], + "vrf": "RED", + }, + { + "network": [R1_NETWORK_CONNECTED[addr_type]], + "next_hop": R1_NETWORK_CONNECTED_NXTHOP[addr_type], + "vrf": "RED", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n routes impoted from default VRF is not expected \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n routes impoted from default VRF is not expected \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_default_originate_route_with_non_default_VRF_with_route_map_p1(request): + """ + "Verify default-originate route with non-default VRF with route-map import " + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure IPV4 and IPV6 static route on R0 with Null nexthop") + STEP = """ + Configure IPV4 and IPV6 EBGP session between R0 and R1 + Configure IPV4 and IPV6 static route on R0 with Null nexthop """ + step(STEP) + input_dict = { + "r0": {"bgp": {"local_as": 222, "vrf": "default"}}, + "r1": {"bgp": {"local_as": 333, "vrf": "default"}}, + } + result = modify_as_number(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configuring static route at R0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK5_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step(" Configure re-distribute static on R0 for R0 to R1 for IPV4 and IPV6 peer ") + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure default-originate on R1 for R1 to R2 neighbor for IPv4 and IPv6 peer" + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"default_originate": {"r2": {}}}}, + "ipv6": {"unicast": {"default_originate": {"r2": {}}}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + snapshot1 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3", vrf="RED") + + step("Verify IPv4 and IPv6 static received on R2 default VRF as BGP routes") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK5_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + " Configure IPv4 and IPv6 prefix-list of of route received from R1 on R2 and for 0.0.0.0/0 0::0/0 route" + ) + input_dict_3 = { + "r2": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK5_1["ipv4"], + "action": "permit", + }, + {"seqid": "2", "network": "0.0.0.0/0", "action": "permit"}, + { + "seqid": "3", + "network": NETWORK2_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK5_1["ipv6"], + "action": "permit", + }, + {"seqid": "2", "network": "0::0/0", "action": "permit"}, + { + "seqid": "3", + "network": NETWORK2_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r2": {"prefix_lists": ["Pv4", "Pv6"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure IPv4/IPv6 route-map on R2 with deny sequence using above prefix-list" + ) + input_dict_3 = { + "r2": { + "route_maps": { + "RMv4": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "deny", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + STEP = """ + import Route-map anf non-default VRF into defailt vrf + import vrf route-map RM1 + import vrf red + """ + step(STEP) + + local_as = get_dut_as_number(tgen, "r2") + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "RED"}}}, + "ipv6": {"unicast": {"import": {"vrf": "RED"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step(STEP) + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": local_as, + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "route-map RMv4"}}}, + "ipv6": {"unicast": {"import": {"vrf": "route-map RMv6"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "Verify IPv4 and IPv6 routes present on VRF red ( static , default-originate) should not get advertised to default VRF " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + }, + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n VRF red ( static , default-originate) should not get advertised to default VRF \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes_input, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n VRF red ( static , default-originate) should not get advertised to default VRF \nError: {}".format( + tc_name, result + ) + + step("Change route-map sequence deny to permit") + input_dict_3 = { + "r2": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 and IPv6 routes present on VRF red ( static , default-originate) should get advertised to default VRF" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": [DEFAULT_ROUTES[addr_type]], + "next_hop": DEFAULT_ROUTE_NXT_HOP_R1[addr_type], + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("verify Out-prefix count incremented for IPv4/IPv6 default route on VRF red") + snapshot2 = get_prefix_count_route(tgen, topo, dut="r2", peer="r3", vrf="RED") + step("verifying the prefix count incrementing or not ") + isIPv4prefix_incremented = False + isIPv6prefix_incremented = False + if snapshot1["ipv4_count"] <= snapshot2["ipv4_count"]: + isIPv4prefix_incremented = True + if snapshot1["ipv6_count"] <= snapshot2["ipv6_count"]: + isIPv6prefix_incremented = True + + assert ( + isIPv4prefix_incremented is True + ), "Testcase {} : Failed Error: IPV4 Prefix is not incremented on receiveing ".format( + tc_name + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate/test_default_originate_conditional_routemap.py b/tests/topotests/bgp_default_originate/test_default_originate_conditional_routemap.py new file mode 100644 index 0000000..f67a431 --- /dev/null +++ b/tests/topotests/bgp_default_originate/test_default_originate_conditional_routemap.py @@ -0,0 +1,2128 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Shreenidhi A R +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# +""" +Following scenerios are covered. +1. When there is change in route-map policy associated with default-originate, changes does not reflect. +2. When route-map associated with default-originate is deleted, default route doesn't get withdrawn +3. Update message is not being sent when only route-map is removed from the default-originate config. +4. SNT counter gets incremented on change of every policy associated with default-originate +5. Route-map with multiple match clauses causes inconsistencies with default-originate. +6. BGP-Default originate behaviour with BGP attributes +""" +import os +import sys +import time +import pytest +from copy import deepcopy +from lib.topolog import logger + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + get_prefix_count_route, + modify_as_number, + verify_bgp_rib, + get_dut_as_number, + verify_rib_default_route, + verify_fib_default_route, +) +from lib.common_config import ( + verify_fib_routes, + step, + required_linux_kernel_version, + create_route_maps, + interface_status, + create_prefix_lists, + get_frr_ipv6_linklocal, + start_topology, + write_test_header, + verify_prefix_lists, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_static_routes, + check_router_status, + delete_route_maps, +) + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers + +# Global variables +topo = None +NETWORK1_1 = {"ipv4": "198.51.1.1/32", "ipv6": "2001:DB8::1:1/128"} +DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_default_originate_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + global DEFAULT_ROUTES + global DEFAULT_ROUTE_NXT_HOP_R1, DEFAULT_ROUTE_NXT_HOP_R3 + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + interface = topo["routers"]["r1"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r1", intf=interface) + ipv4_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r1"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R1 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + interface = topo["routers"]["r3"]["links"]["r2"]["interface"] + ipv6_link_local = get_frr_ipv6_linklocal(tgen, "r3", intf=interface) + ipv4_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv4"].split("/")[0] + ipv6_nxt_hop = topo["routers"]["r3"]["links"]["r2"]["ipv6"].split("/")[0] + DEFAULT_ROUTE_NXT_HOP_R3 = {"ipv4": ipv4_nxt_hop, "ipv6": ipv6_link_local} + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_default_originate_delete_conditional_routemap(request): + """ + "scenerio covered": + 1. When there is change in route-map policy associated with default-originate, changes does not reflect. + 2. When route-map associated with default-originate is deleted, default route doesn't get withdrawn + 3. Update message is not being sent when only route-map is removed from the default-originate config. + 4. SNT counter gets incremented on change of every policy associated with default-originate + 5. Route-map with multiple match clauses causes inconsistencies with default-originate. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , IBGP neighbor between R1 and R2") + step("Configure IPv4 and IPv6 , EBGP neighbor between R1 and R0") + input_dict = { + "r0": { + "bgp": { + "local_as": 999, + } + }, + "r1": { + "bgp": { + "local_as": 1000, + } + }, + "r2": { + "bgp": { + "local_as": 1000, + } + }, + "r3": { + "bgp": { + "local_as": 2000, + } + }, + "r4": { + "bgp": { + "local_as": 3000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + + step("After changing the BGP remote as , Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert ( + BGP_CONVERGENCE is True + ), "Complete convergence is expected after changing ASN ....! ERROR :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Configure 1 IPv4 and 1 IPv6 Static route on R0 with next-hop as Null0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed to configure the static route \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are configured and up on R0") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r0", static_routes_input) + assert ( + result is True + ), "Testcase {} : routes {} not found in R0 FIB \n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure redistribute static on IPv4 and IPv6 address family on R0 for R0 to R1 neighbor " + ) + redistribute_static = { + "r0": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert ( + result is True + ), "Testcase {} : Failed to configure redistribute configuration....! \n Error: {}".format( + tc_name, result + ) + + step("verify IPv4 and IPv6 static route are received on R1") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r0": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed... Routes {} expected in r1 FIB after configuring the redistribute config on R0 \n Error: {}".format( + tc_name, static_routes_input, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", static_routes_input) + assert ( + result is True + ), "Testcase {} : Failed... Routes {} expected in r1 RIB after configuring the redistribute config on R0\n Error: {}".format( + tc_name, static_routes_input, result + ) + + step( + "Configure IPv4 prefix-list 'Pv4' and and IPv6 prefix-list 'Pv6' on R1 to match BGP route Sv41, IPv6 route Sv61 permit " + ) + input_dict_3 = { + "r1": { + "prefix_lists": { + "ipv4": { + "Pv4": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + }, + ] + }, + "ipv6": { + "Pv6": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + }, + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the prefix list \n Error: {}".format( + tc_name, result + ) + + step( + "Configure IPV4 and IPv6 route-map (RMv4 and RMv6) matching prefix-list (Pv4 and Pv6) respectively on R1" + ) + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + "set": { + "path": { + "as_num": "5555", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + "set": { + "path": { + "as_num": "5555", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "Configure default-originate with route-map (RMv4 and RMv6) on R1, on BGP IPv4 and IPv6 address family " + ) + local_as = get_dut_as_number(tgen, dut="r1") + default_originate_config = { + "r1": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate \n Error: {}".format( + tc_name, result + ) + + step( + "After configuring default-originate command , verify default routes are advertised on R2 " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + expected_aspath="5555", + ) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed to configure the default originate \n Error: {}".format( + tc_name, result + ) + + step("Changing the as-path policy of the existing route-map") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + "set": { + "path": { + "as_num": "6666", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + "set": { + "path": { + "as_num": "6666", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "Verify prefix sent count on R1 towards R2 \n Send count shoud not be incremented on change of existing (AS-path) policy " + ) + snapshot = get_prefix_count_route( + tgen, topo, dut="r1", peer="r2", link="r1", sent=True, received=False + ) + + ipv4_prefix_count = False + ipv6_prefix_count = False + if snapshot["ipv4_count"] == 2: + ipv4_prefix_count = True + if snapshot["ipv6_count"] == 2: + ipv6_prefix_count = True + + assert ( + ipv4_prefix_count is True + ), "Testcase {} : Failed Error: Expected sent Prefix is 2 but obtained {} ".format( + tc_name, ipv4_prefix_count + ) + assert ( + ipv6_prefix_count is True + ), "Testcase {} : Failed Error: Expected sent Prefix is 2 but obtained {} ".format( + tc_name, ipv6_prefix_count + ) + + step( + "After changing the as-path policy verify the new policy is advertised to router R2" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + expected_aspath="6666", + ) + assert ( + result is True + ), "Testcase {} : Default route with expected attributes is not found in BGP RIB \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Default route with expected attributes is not found in BGP FIB \n Error: {}".format( + tc_name, result + ) + + step("Remove the as-path policy from the route-map") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + "set": { + "path": { + "as_num": "6666", + "as_action": "prepend", + "delete": True, + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + "set": { + "path": { + "as_num": "6666", + "as_action": "prepend", + "delete": True, + } + }, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "After removing the route policy (AS-Path) verify that as-path is removed in r2 " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + ) + assert result is True, "Testcase {} : Failed ... ! \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed .... !\n Error: {}".format( + tc_name, result + ) + + step("Delete the route-map ") + + delete_routemap = {"r1": {"route_maps": ["RMv4", "RMv6"]}} + result = delete_route_maps(tgen, delete_routemap) + assert ( + result is True + ), "Testcase {} : Failed to delete the route-map\n Error: {}".format( + tc_name, result + ) + + step( + "After deleting route-map , verify the default route in FIB and RIB are removed " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : After removing the route-map the default-route is not removed from R2 RIB\n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : After removing the route-map the default-route is not removed from R2 FIB \n Error: {}".format( + tc_name, result + ) + + step("Create route-map with with sequnce number 10 ") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + "set": { + "path": { + "as_num": "9999", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + "set": { + "path": { + "as_num": "9999", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "After Configuring the route-map the dut is expected to receive the route policy (as-path) as 99999" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + expected_aspath="9999", + ) + assert result is True, "Testcase {} : Failed...! \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert result is True, "Testcase {} : Failed ...!\n Error: {}".format( + tc_name, result + ) + + step("Create another route-map with seq number less than the previous i. <10 ") + input_dict_3 = { + "r1": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "5", + "match": {"ipv4": {"prefix_lists": "Pv4"}}, + "set": { + "path": { + "as_num": "7777", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "5", + "match": {"ipv6": {"prefix_lists": "Pv6"}}, + "set": { + "path": { + "as_num": "7777", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert ( + result is True + ), "Testcase {} : Failed to configure the route map \n Error: {}".format( + tc_name, result + ) + + step( + "On creating new route-map the route-map with lower seq id should be considered " + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + metric=0, + expected_aspath="7777", + ) + assert ( + result is True + ), "Testcase {} : Route-map with lowest prefix is not considered \n Error: {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R1, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Route-map with lowest prefix is not considered \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_default_originate_after_BGP_attributes_p1(request): + """ + "Verify different BGP attributes with default-originate route " + """ + tgen = get_topogen() + global BGP_CONVERGENCE + global topo + # test case name + tc_name = request.node.name + write_test_header(tc_name) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + step("Configure IPv4 and IPv6 , EBGP neighbor between R3 and R2") + step("Configure IPv4 and IPv6 IBGP neighbor between R3 and R4") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": r1_local_as, + } + }, + "r2": { + "bgp": { + "local_as": r2_local_as, + } + }, + "r3": { + "bgp": { + "local_as": 4000, + } + }, + "r4": { + "bgp": { + "local_as": 4000, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Configure one IPv4 and one IPv6, Static route on R4 with next-hop as Null0 IPv4 route Sv41, IPv6 route Sv61 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes configured on R4 in FIB") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute static knob on R4 , for R4 to R3 neighbor for IPv4 and IPv6 address family " + ) + redistribute_static = { + "r4": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify After configuring redistribute static , verify route received in BGP table of R3" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + NOTE = """Configure 2 IPv4 prefix-list Pv41 Pv42 and and 2 IPv6 prefix-list Pv61 Pv62 on R3 to match BGP IPv4 route Sv41, 200.1.1.1/24 , IPv6 route Sv61 and 200::1/64""" + step(NOTE) + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "Pv41": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv4"], + "action": "permit", + } + ], + "Pv42": [ + {"seqid": "1", "network": "200.1.1.1/24", "action": "permit"} + ], + }, + "ipv6": { + "Pv61": [ + { + "seqid": "1", + "network": NETWORK1_1["ipv6"], + "action": "permit", + } + ], + "Pv62": [ + {"seqid": " 1", "network": "200::1/64", "action": "permit"} + ], + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify IPv4 and IPv6 Prefix list got configured on R3") + input_dict = {"r3": {"prefix_lists": ["Pv41", "Pv61", "Pv42", "Pv62"]}} + result = verify_prefix_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure 2 sequence of route-map for IPv4 seq1 permit Pv41 and seq2 permit Pv42 and for IPv6 seq1 permit Pv61 , seq2 permit Pv62 on R3" + ) + input_dict_3 = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv4": {"prefix_lists": "Pv41"}}, + }, + { + "action": "permit", + "seq_id": "2", + "match": {"ipv4": {"prefix_lists": "Pv42"}}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "match": {"ipv6": {"prefix_lists": "Pv61"}}, + }, + { + "action": "permit", + "seq_id": "2", + "match": {"ipv6": {"prefix_lists": "Pv62"}}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Apply on route-map seq1 set as-path prepend to 200 and route-map seq2 set as-path prepend to 300 for IPv4 and IPv6 route-map " + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "200", + "as_action": "prepend", + } + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "300", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "200", + "as_action": "prepend", + } + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "300", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + " Configure default-originate with IPv4 and IPv6 route-map on R3 for R3-R2 IPv4 and IPv6 BGP neighbor" + ) + + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 and IPv6 default route received on R2 with both the AS path on R2" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=0, + expected_aspath="4000 200", + ) + + step( + "Modify AS prepend path adding one more value 500 in route-map sequence 1 and 600 for route-map sequence 2 for IPv4 and IPv6 route-map" + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "500", + "as_action": "prepend", + } + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "600", + "as_action": "prepend", + } + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "500", + "as_action": "prepend", + } + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "600", + "as_action": "prepend", + } + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("As path 500 added to IPv4 and IPv6 default -originate route received on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=0, + expected_aspath="4000 500", + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "Apply on route-map seq1 set metric value to 70 and route-map seq2 set metric 80 IPv4 and IPv6 route-map" + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "metric": 70, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "metric": 80, + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "metric": 70, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "metric": 80, + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Configured metric value received on R2 along with as-path for IPv4 and IPv6 default routes " + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "::/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=70, + expected_aspath="4000 500", + ) + + step( + "Modify route-map seq1 configure metric 50 and route-map seq2 configure metric 100 IPv4 and IPv6 route-map " + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "metric": 50, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "metric": 100, + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "metric": 50, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "metric": 100, + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Configured metric value received on R2 along with as-path for IPv4 and IPv6 default routes " + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=50, + expected_aspath="4000 500", + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Delete AS-prepend from IP4 and IPv6 route-map configured on R3 ") + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "500", + "as_action": "prepend", + "delete": True, + }, + "delete": True, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "600", + "as_action": "prepend", + "delete": True, + }, + "delete": True, + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "path": { + "as_num": "500", + "as_action": "prepend", + "delete": True, + }, + "delete": True, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "path": { + "as_num": "600", + "as_action": "prepend", + "delete": True, + }, + "delete": True, + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify AS-prepend is deleted from default originate route and metric value only present on R2 for IPv4 and IPv6 default routes " + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=50, + expected_aspath="4000", + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Delete metric value from IP4 and IPv6 route-map configured on R3 ") + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": {"metric": 50, "delete": True}, + }, + { + "action": "permit", + "seq_id": "2", + "set": {"metric": 100, "delete": True}, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": {"metric": 50, "delete": True}, + }, + { + "action": "permit", + "seq_id": "2", + "set": {"metric": 100, "delete": True}, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Metric value deleted from IPv4 and IPv6 default route on R2 ,verify default routes " + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + metric=0, + expected_aspath="4000", + ) + + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + step("Change IPv4 and IPv6 , EBGP to IBGP neighbor between R3 and R2") + step("Change IPv4 and IPv6 IBGP to EBGP neighbor between R3 and R4") + r0_local_as = topo["routers"]["r0"]["bgp"]["local_as"] + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r2_local_as = topo["routers"]["r2"]["bgp"]["local_as"] + r3_local_as = topo["routers"]["r3"]["bgp"]["local_as"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + input_dict = { + "r0": { + "bgp": { + "local_as": r0_local_as, + } + }, + "r1": { + "bgp": { + "local_as": r1_local_as, + } + }, + "r2": { + "bgp": { + "local_as": 1111, + } + }, + "r3": { + "bgp": { + "local_as": 1111, + } + }, + "r4": { + "bgp": { + "local_as": 5555, + } + }, + } + result = modify_as_number(tgen, topo, input_dict) + try: + assert result is True + except AssertionError: + logger.info("Expected behaviour: {}".format(result)) + logger.info("BGP config is not created because of invalid ASNs") + step("After changing the BGP AS Path Verify the BGP Convergence") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + step( + "Configure one IPv4 and one IPv6, Static route on R4 with next-hop as Null0 IPv4 route Sv41, IPv6 route Sv61 " + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes configured on R4 in FIB") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r4", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure redistribute static knob on R4 , for R4 to R3 neighbor for IPv4 and IPv6 address family " + ) + redistribute_static = { + "r4": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, redistribute_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify After configuring redistribute static , verify route received in BGP table of R3" + ) + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + " Configure default-originate with IPv4 and IPv6 route-map on R3 for R3-R2 IPv4 and IPv6 BGP neighbor" + ) + local_as = get_dut_as_number(tgen, dut="r3") + default_originate_config = { + "r3": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv4"}}} + }, + "ipv6": { + "unicast": {"default_originate": {"r2": {"route_map": "RMv6"}}} + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, default_originate_config) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 and IPv6 default route received on R2 with both the AS path on R2" + ) + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "0::0/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + ) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "Configure local -preference to 50 on IPv4 and IPv6 route map seq1 and 60 on seq2" + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "locPrf": 50, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "locPrf": 60, + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "locPrf": 50, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "locPrf": 60, + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Configured metric value received on R2 along with as-path for IPv4 and IPv6 default routes " + ) + + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + locPrf=50, + ) + + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "Modify local preference value to 150 on IPv4 and IPv6 route map seq1 and 160 on seq2" + ) + route_map = { + "r3": { + "route_maps": { + "RMv4": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "locPrf": 150, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "locPrf": 160, + }, + }, + ], + "RMv6": [ + { + "action": "permit", + "seq_id": "1", + "set": { + "locPrf": 150, + }, + }, + { + "action": "permit", + "seq_id": "2", + "set": { + "locPrf": 160, + }, + }, + ], + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify Modified local-preference value received on R2 for IPv4 and IPv6 default routes " + ) + + DEFAULT_ROUTES = {"ipv4": "0.0.0.0/0", "ipv6": "::/0"} + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + locPrf=150, + ) + + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + # updating the topology with the updated AS-Number to avoid conflict in con configuring the AS + updated_topo = topo + updated_topo["routers"]["r0"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r0") + updated_topo["routers"]["r1"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r1") + updated_topo["routers"]["r2"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r2") + updated_topo["routers"]["r3"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r3") + updated_topo["routers"]["r4"]["bgp"]["local_as"] = get_dut_as_number(tgen, "r4") + + step( + "Shut IPv4/IPv6 BGP neighbor from R4 ( R4-R3) using 'neighbor x.x.x.x shut' command " + ) + local_as = get_dut_as_number(tgen, dut="r4") + shut_neighbor = { + "r4": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"shutdown": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"shutdown": True}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r3"]["links"]["r4"]["interface"] + input_dict = {"r1": {"interface_list": [interface], "status": "down"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Shut down the interface failed ! \n Error: {}".format( + tc_name, result + ) + + step("After shutting the interface verify the BGP convergence") + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n After shutting Down BGP convergence should Fail and return False \n Error: {}".format( + tc_name, result + ) + + step("verify default route deleted from R2 ") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After Shut down interface the default route is NOT expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After Shut down interface the default route is NOT expected but found in FIB -> {}".format( + tc_name, result + ) + + step( + "no Shut IPv4/IPv6 BGP neighbor from R4 ( R4-R3) using 'neighbor x.x.x.x shut' command " + ) + local_as = get_dut_as_number(tgen, dut="r4") + shut_neighbor = { + "r4": { + "bgp": { + "local_as": local_as, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"shutdown": False}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"shutdown": False}}} + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, updated_topo, shut_neighbor) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + interface = topo["routers"]["r3"]["links"]["r4"]["interface"] + input_dict = {"r1": {"interface_list": [interface], "status": "up"}} + + result = interface_status(tgen, topo, input_dict) + assert ( + result is True + ), "Testcase {} : Bring up interface failed ! \n Error: {}".format(tc_name, result) + + step("After no shutting the interface verify the BGP convergence") + result = verify_bgp_convergence(tgen, topo, expected=True) + assert ( + result is True + ), "Testcase {} : Failed \n After shutting Down BGP convergence should Fail and return False \n Error: {}".format( + tc_name, result + ) + + step("After no shut neighbor , verify default route relearn on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After no Shut down interface the default route is expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After Shut down interface the default route is expected but found in FIB -> {}".format( + tc_name, result + ) + + step("Remove IPv4/IPv6 static route configure on R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "delete": True, + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes removed on R4 in FIB") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, addr_type, "r4", static_routes_input, expected=False + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib( + tgen, addr_type, "r4", static_routes_input, expected=False + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("After removing static route , verify default route removed on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After removing static the default route is NOT expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After removing static the default route is NOT expected but found in FIB -> {}".format( + tc_name, result + ) + + step("Configuring the static route back in r4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes configured on R4 in FIB") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_fib_routes( + tgen, addr_type, "r4", static_routes_input, expected=True + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib( + tgen, addr_type, "r4", static_routes_input, expected=True + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("After adding static route back , verify default route learned on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After removing static the default route is expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After removing static the default route is expected but found in FIB -> {}".format( + tc_name, result + ) + + step("Deactivate IPv4 and IPv6 neighbor configured from R4 ( R4-R3)") + + configure_bgp_on_r1 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"deactivate": "ipv4"}}} + } + }, + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"deactivate": "ipv6"}}} + } + }, + }, + } + } + } + } + result = create_router_bgp(tgen, updated_topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("After deactivating the BGP neighbor , verify default route removed on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is NOT expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is NOT expected but found in FIB -> {}".format( + tc_name, result + ) + + step("Activate IPv4 and IPv6 neighbor configured from R4 ( R4-R3)") + + configure_bgp_on_r1 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"activate": "ipv4"}}} + } + }, + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {"activate": "ipv6"}}} + } + }, + }, + } + } + } + } + result = create_router_bgp(tgen, updated_topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp convergence.") + bgp_convergence = verify_bgp_convergence(tgen, updated_topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + step("After Activating the BGP neighbor , verify default route learned on R2") + result = verify_rib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is expected but found in RIB -> {}".format( + tc_name, result + ) + + result = verify_fib_default_route( + tgen, + topo, + dut="r2", + routes=DEFAULT_ROUTES, + expected_nexthop=DEFAULT_ROUTE_NXT_HOP_R3, + expected=True, + ) + assert ( + result is True + ), "Testcase {} : Failed \n Error: After Deactivating the BGP neighbor the default route is expected but found in FIB -> {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate_timer/__init__.py b/tests/topotests/bgp_default_originate_timer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_originate_timer/r1/bgpd.conf b/tests/topotests/bgp_default_originate_timer/r1/bgpd.conf new file mode 100644 index 0000000..f2a1c90 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r1/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65001 + no bgp ebgp-requires-policy + bgp default-originate timer 3600 + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 + neighbor 192.168.1.2 default-originate route-map default + exit-address-family +! +bgp community-list standard r3 seq 5 permit 65003:1 +! +route-map default permit 10 + match community r3 +exit diff --git a/tests/topotests/bgp_default_originate_timer/r1/zebra.conf b/tests/topotests/bgp_default_originate_timer/r1/zebra.conf new file mode 100644 index 0000000..3692361 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_default_originate_timer/r2/bgpd.conf b/tests/topotests/bgp_default_originate_timer/r2/bgpd.conf new file mode 100644 index 0000000..7ca65a9 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_default_originate_timer/r2/zebra.conf b/tests/topotests/bgp_default_originate_timer/r2/zebra.conf new file mode 100644 index 0000000..0c95656 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_default_originate_timer/r3/bgpd.conf b/tests/topotests/bgp_default_originate_timer/r3/bgpd.conf new file mode 100644 index 0000000..0a37913 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r3/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + redistribute connected route-map r1 + exit-address-family +! +route-map r1 permit 10 + set community 65003:1 +exit diff --git a/tests/topotests/bgp_default_originate_timer/r3/zebra.conf b/tests/topotests/bgp_default_originate_timer/r3/zebra.conf new file mode 100644 index 0000000..20801f9 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/r3/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.10.10.10/32 +! +interface r3-eth0 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_default_originate_timer/test_bgp_default_originate_timer.py b/tests/topotests/bgp_default_originate_timer/test_bgp_default_originate_timer.py new file mode 100644 index 0000000..b2ba936 --- /dev/null +++ b/tests/topotests/bgp_default_originate_timer/test_bgp_default_originate_timer.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Check if `bgp default-originate timer` commands takes an effect: +1. Set bgp default-originate timer 3600 +2. No default route is advertised because the timer is running for 3600 seconds +3. We reduce it to 10 seconds +4. Default route is advertised +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_timer(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + def _bgp_default_received_from_r1(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast 0.0.0.0/0 json")) + expected = { + "paths": [ + { + "nexthops": [ + { + "hostname": "r1", + "ip": "192.168.1.1", + } + ], + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_default_received_from_r1) + _, result = topotest.run_and_expect(test_func, not None, count=30, wait=1) + assert result is not None, "Seen default route received from r1, but should not" + + step("Set BGP default-originate timer to 10 seconds") + r1.vtysh_cmd( + """ + configure terminal + router bgp + bgp default-originate timer 10 + """ + ) + + step("Trigger BGP UPDATE from r3") + r3.vtysh_cmd( + """ + configure terminal + route-map r1 permit 10 + set metric 1 + """ + ) + + test_func = functools.partial(_bgp_default_received_from_r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Did not see default route received from r1, but should" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_originate_withdraw/__init__.py b/tests/topotests/bgp_default_originate_withdraw/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_originate_withdraw/r1/bgpd.conf b/tests/topotests/bgp_default_originate_withdraw/r1/bgpd.conf new file mode 100644 index 0000000..6813b02 --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 + neighbor 192.168.1.2 default-originate + exit-address-family +! diff --git a/tests/topotests/bgp_default_originate_withdraw/r1/zebra.conf b/tests/topotests/bgp_default_originate_withdraw/r1/zebra.conf new file mode 100644 index 0000000..3692361 --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_default_originate_withdraw/r2/bgpd.conf b/tests/topotests/bgp_default_originate_withdraw/r2/bgpd.conf new file mode 100644 index 0000000..60e6236 --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r2/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + network 192.168.2.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_default_originate_withdraw/r2/zebra.conf b/tests/topotests/bgp_default_originate_withdraw/r2/zebra.conf new file mode 100644 index 0000000..0c95656 --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_default_originate_withdraw/r3/bgpd.conf b/tests/topotests/bgp_default_originate_withdraw/r3/bgpd.conf new file mode 100644 index 0000000..547cf86 --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r3/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + network 0.0.0.0/0 + exit-address-family +! diff --git a/tests/topotests/bgp_default_originate_withdraw/r3/zebra.conf b/tests/topotests/bgp_default_originate_withdraw/r3/zebra.conf new file mode 100644 index 0000000..7ccdcfd --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/r3/zebra.conf @@ -0,0 +1,5 @@ +! +interface r3-eth0 + ip address 192.168.2.2/24 +! +ip route 0.0.0.0/0 Null0 diff --git a/tests/topotests/bgp_default_originate_withdraw/test_bgp_default_originate_withdraw.py b/tests/topotests/bgp_default_originate_withdraw/test_bgp_default_originate_withdraw.py new file mode 100644 index 0000000..e25f85a --- /dev/null +++ b/tests/topotests/bgp_default_originate_withdraw/test_bgp_default_originate_withdraw.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if bgpd do not crash if we use default-originate while +received a default route from the neighbor as well. 0.0.0.0/0 +MUST be kept in RIB even if we remove default-originate from +the neighbor. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_with_default_received(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_default_received_from_r3(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 0.0.0.0/0 json")) + expected = { + "paths": [ + { + "nexthops": [ + { + "hostname": "r3", + "ip": "192.168.2.2", + } + ], + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_default_received_from_r3) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see default route received from r3" + + def _bgp_advertised_default_originate_to_r2(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "bgpOriginatingDefaultNetwork": "0.0.0.0/0", + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_advertised_default_originate_to_r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see default-originate route advertised to r2" + + step("Disable default-originate for r2") + r1.vtysh_cmd( + """ + configure + router bgp + address-family ipv4 unicast + no neighbor 192.168.1.2 default-originate + """ + ) + + def _bgp_advertised_default_from_r3_to_r2(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "bgpOriginatingDefaultNetwork": None, + "advertisedRoutes": { + "0.0.0.0/0": { + "valid": True, + } + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_advertised_default_from_r3_to_r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see default route advertised to r2" + + step("Enable default-originate for r2") + r1.vtysh_cmd( + """ + configure + router bgp + address-family ipv4 unicast + neighbor 192.168.1.2 default-originate + do clear ip bgp * + """ + ) + + step("Check if default-originate route advertised to r2") + test_func = functools.partial(_bgp_advertised_default_originate_to_r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see default-originate route advertised to r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_route/__init__.py b/tests/topotests/bgp_default_route/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_route/r1/bgpd.conf b/tests/topotests/bgp_default_route/r1/bgpd.conf new file mode 100644 index 0000000..10ced36 --- /dev/null +++ b/tests/topotests/bgp_default_route/r1/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + network 0.0.0.0/1 + neighbor 192.168.255.2 default-originate + exit-address-family +! diff --git a/tests/topotests/bgp_default_route/r1/zebra.conf b/tests/topotests/bgp_default_route/r1/zebra.conf new file mode 100644 index 0000000..fbf97b0 --- /dev/null +++ b/tests/topotests/bgp_default_route/r1/zebra.conf @@ -0,0 +1,11 @@ +! +ip route 0.0.0.0/1 blackhole +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route/r2/bgpd.conf b/tests/topotests/bgp_default_route/r2/bgpd.conf new file mode 100644 index 0000000..6d1080c --- /dev/null +++ b/tests/topotests/bgp_default_route/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_default_route/r2/zebra.conf b/tests/topotests/bgp_default_route/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_default_route/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route/test_bgp_default-originate.py b/tests/topotests/bgp_default_route/test_bgp_default-originate.py new file mode 100644 index 0000000..333beb0 --- /dev/null +++ b/tests/topotests/bgp_default_route/test_bgp_default-originate.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2019-2020 by +# Donatas Abraitis +# + +""" +Test if default-originate works without route-map. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_if_received(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd("show ip bgp neighbor 192.168.255.1 json") + ) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_if_originated(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show ip bgp summary json")) + expected = {"ipv4Unicast": {"peers": {"192.168.255.2": {"pfxSnt": 2}}}} + return topotest.json_cmp(output, expected) + + def _bgp_route_is_valid(router, prefix): + output = json.loads(router.vtysh_cmd("show ip bgp {} json".format(prefix))) + expected = {"paths": [{"valid": True}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_if_received) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "No 0.0.0.0/0 at r2 from r1" + + test_func = functools.partial(_bgp_check_if_originated) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "No 0.0.0.0/0 from r1 to r2" + + test_func = functools.partial(_bgp_route_is_valid, tgen.gears["r2"], "0.0.0.0/0") + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see 0.0.0.0/0 in r2" + + test_func = functools.partial(_bgp_route_is_valid, tgen.gears["r2"], "0.0.0.0/1") + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see 0.0.0.0/1 in r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_route_route_map_match/__init__.py b/tests/topotests/bgp_default_route_route_map_match/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_route_route_map_match/r1/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match/r1/bgpd.conf new file mode 100644 index 0000000..97b440f --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match/r1/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + network 192.168.13.0/24 route-map internal + neighbor 192.168.255.2 default-originate route-map default + exit-address-family +! +bgp community-list standard default seq 5 permit 65000:1 +! +route-map default permit 10 + match community default +! +route-map internal permit 10 + set community 65000:1 +! diff --git a/tests/topotests/bgp_default_route_route_map_match/r1/zebra.conf b/tests/topotests/bgp_default_route_route_map_match/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match/r2/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match/r2/bgpd.conf new file mode 100644 index 0000000..00c96cc --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_default_route_route_map_match/r2/zebra.conf b/tests/topotests/bgp_default_route_route_map_match/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match/test_bgp_default-originate_route-map_match.py b/tests/topotests/bgp_default_route_route_map_match/test_bgp_default-originate_route-map_match.py new file mode 100644 index 0000000..9dcb5a1 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match/test_bgp_default-originate_route-map_match.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2019-2020 by +# Donatas Abraitis +# + +""" +Test if default-originate works with ONLY match operations. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_default_route_is_valid(router): + output = json.loads(router.vtysh_cmd("show ip bgp 0.0.0.0/0 json")) + expected = {"paths": [{"valid": True}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, 'Failed to see bgp convergence in "{}"'.format(router) + + test_func = functools.partial(_bgp_default_route_is_valid, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert ( + result is None + ), 'Failed to see applied metric for default route in "{}"'.format(router) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_route_route_map_match2/__init__.py b/tests/topotests/bgp_default_route_route_map_match2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_route_route_map_match2/r1/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match2/r1/bgpd.conf new file mode 100644 index 0000000..ee7a92f --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match2/r1/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.255.2 default-originate route-map default + exit-address-family +! +ip prefix-list r2 permit 10.0.0.0/22 +! +route-map default permit 10 + match ip address prefix-list r2 +! diff --git a/tests/topotests/bgp_default_route_route_map_match2/r1/zebra.conf b/tests/topotests/bgp_default_route_route_map_match2/r1/zebra.conf new file mode 100644 index 0000000..e2c399e --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match2/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match2/r2/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match2/r2/bgpd.conf new file mode 100644 index 0000000..00c96cc --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match2/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_default_route_route_map_match2/r2/zebra.conf b/tests/topotests/bgp_default_route_route_map_match2/r2/zebra.conf new file mode 100644 index 0000000..f355ab1 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match2/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 10.0.0.1/22 +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match2/test_bgp_default-originate_route-map_match2.py b/tests/topotests/bgp_default_route_route_map_match2/test_bgp_default-originate_route-map_match2.py new file mode 100644 index 0000000..965d348 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match2/test_bgp_default-originate_route-map_match2.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if default-originate works with conditional match. +If 10.0.0.0/22 is recived from r2, then we announce 0.0.0.0/0 +to r2. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_default_route_is_valid(router): + output = json.loads(router.vtysh_cmd("show ip bgp 0.0.0.0/0 json")) + expected = {"paths": [{"valid": True}]} + return topotest.json_cmp(output, expected) + + step("Converge network") + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see bgp convergence at r2" + + step("Withdraw 10.0.0.0/22 from R2") + router.vtysh_cmd( + "conf t\nrouter bgp\naddress-family ipv4\nno redistribute connected" + ) + + step("Check if we don't have 0.0.0.0/0 at R2") + test_func = functools.partial(_bgp_default_route_is_valid, router) + success, result = topotest.run_and_expect(test_func, not None, count=30, wait=0.5) + assert result is not None, "0.0.0.0/0 exists at r2" + + step("Announce 10.0.0.0/22 from R2") + router.vtysh_cmd("conf t\nrouter bgp\naddress-family ipv4\nredistribute connected") + + step("Check if we have 0.0.0.0/0 at R2") + test_func = functools.partial(_bgp_default_route_is_valid, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "0.0.0.0/0 does not exist at r2" + + step("Withdraw 10.0.0.0/22 from R2 again") + router.vtysh_cmd( + "conf t\nrouter bgp\naddress-family ipv4\nno redistribute connected" + ) + + step("Check if we don't have 0.0.0.0/0 at R2 again") + test_func = functools.partial(_bgp_default_route_is_valid, router) + success, result = topotest.run_and_expect(test_func, not None, count=30, wait=0.5) + assert result is not None, "0.0.0.0/0 exists at r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_route_route_map_match_set/__init__.py b/tests/topotests/bgp_default_route_route_map_match_set/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_route_route_map_match_set/r1/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match_set/r1/bgpd.conf new file mode 100644 index 0000000..32ac7c5 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match_set/r1/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + network 192.168.13.0/24 route-map internal + neighbor 192.168.255.2 default-originate route-map default + exit-address-family +! +bgp community-list standard default seq 5 permit 65000:1 +! +route-map default permit 10 + match community default + set metric 123 + set as-path prepend 65000 65000 65000 +! +route-map internal permit 10 + set community 65000:1 +! diff --git a/tests/topotests/bgp_default_route_route_map_match_set/r1/zebra.conf b/tests/topotests/bgp_default_route_route_map_match_set/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match_set/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match_set/r2/bgpd.conf b/tests/topotests/bgp_default_route_route_map_match_set/r2/bgpd.conf new file mode 100644 index 0000000..00c96cc --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match_set/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_default_route_route_map_match_set/r2/zebra.conf b/tests/topotests/bgp_default_route_route_map_match_set/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match_set/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_default_route_route_map_match_set/test_bgp_default-originate_route-map_match_set.py b/tests/topotests/bgp_default_route_route_map_match_set/test_bgp_default-originate_route-map_match_set.py new file mode 100644 index 0000000..f94620b --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_match_set/test_bgp_default-originate_route-map_match_set.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2020 by +# Donatas Abraitis +# + +""" +Test if default-originate works with match operations. +And verify if set operations work as well. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_default_route_has_metric(router): + output = json.loads(router.vtysh_cmd("show ip bgp 0.0.0.0/0 json")) + expected = { + "paths": [ + { + "aspath": {"string": "65000 65000 65000 65000"}, + "metric": 123, + "community": None, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, 'Failed to see bgp convergence in "{}"'.format(router) + + test_func = functools.partial(_bgp_default_route_has_metric, router) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert ( + result is None + ), 'Failed to see applied metric for default route in "{}"'.format(router) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_default_route_route_map_set/__init__.py b/tests/topotests/bgp_default_route_route_map_set/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_default_route_route_map_set/r1/bgpd.conf b/tests/topotests/bgp_default_route_route_map_set/r1/bgpd.conf new file mode 100644 index 0000000..c442e06 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r1/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as external + neighbor 192.168.255.2 timers 3 10 + neighbor PG peer-group + neighbor PG remote-as external + neighbor PG timers 3 10 + bgp listen range 192.168.255.0/24 peer-group PG + address-family ipv4 unicast + neighbor PG default-originate route-map default + neighbor 192.168.255.2 default-originate route-map default + exit-address-family +! +route-map default permit 10 + set metric 123 + set as-path prepend 65001 65001 65001 +! diff --git a/tests/topotests/bgp_default_route_route_map_set/r1/zebra.conf b/tests/topotests/bgp_default_route_route_map_set/r1/zebra.conf new file mode 100644 index 0000000..4af88e5 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! diff --git a/tests/topotests/bgp_default_route_route_map_set/r2/bgpd.conf b/tests/topotests/bgp_default_route_route_map_set/r2/bgpd.conf new file mode 100644 index 0000000..3a18a11 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as external + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_default_route_route_map_set/r2/zebra.conf b/tests/topotests/bgp_default_route_route_map_set/r2/zebra.conf new file mode 100644 index 0000000..c03dd7e --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! diff --git a/tests/topotests/bgp_default_route_route_map_set/r3/bgpd.conf b/tests/topotests/bgp_default_route_route_map_set/r3/bgpd.conf new file mode 100644 index 0000000..c477037 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r3/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as external + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_default_route_route_map_set/r3/zebra.conf b/tests/topotests/bgp_default_route_route_map_set/r3/zebra.conf new file mode 100644 index 0000000..5ae9daf --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/r3/zebra.conf @@ -0,0 +1,4 @@ +! +interface r3-eth0 + ip address 192.168.255.3/24 +! diff --git a/tests/topotests/bgp_default_route_route_map_set/test_bgp_default-originate_route-map_set.py b/tests/topotests/bgp_default_route_route_map_set/test_bgp_default-originate_route-map_set.py new file mode 100644 index 0000000..e633b61 --- /dev/null +++ b/tests/topotests/bgp_default_route_route_map_set/test_bgp_default-originate_route-map_set.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2019-2020 by +# Donatas Abraitis +# + +""" +Test if default-originate works with ONLY set operations. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_default_originate_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + def _bgp_converge(router, pfxCount): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": {"acceptedPrefixCounter": pfxCount} + }, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_default_route_has_metric(router): + output = json.loads(router.vtysh_cmd("show ip bgp 0.0.0.0/0 json")) + expected = { + "paths": [{"aspath": {"string": "65001 65001 65001 65001"}, "metric": 123}] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, r2, 1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to see bgp convergence in r2" + + test_func = functools.partial(_bgp_default_route_has_metric, r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to see applied metric for default route in r2" + + test_func = functools.partial(_bgp_converge, r3, 2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to see bgp convergence in r3" + + test_func = functools.partial(_bgp_default_route_has_metric, r3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to see applied metric for default route in r3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_disable_addpath_rx/__init__.py b/tests/topotests/bgp_disable_addpath_rx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_disable_addpath_rx/r1/bgpd.conf b/tests/topotests/bgp_disable_addpath_rx/r1/bgpd.conf new file mode 100644 index 0000000..44b009e --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r1/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 65001 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.1.2 disable-addpath-rx + exit-address-family +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r1/zebra.conf b/tests/topotests/bgp_disable_addpath_rx/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r2/bgpd.conf b/tests/topotests/bgp_disable_addpath_rx/r2/bgpd.conf new file mode 100644 index 0000000..8274e3f --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r2/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65002 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers connect 5 + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers connect 5 + neighbor 192.168.2.4 remote-as external + neighbor 192.168.2.4 timers connect 5 + address-family ipv4 unicast + neighbor 192.168.1.1 addpath-tx-all-paths + exit-address-family +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r2/zebra.conf b/tests/topotests/bgp_disable_addpath_rx/r2/zebra.conf new file mode 100644 index 0000000..e4a9074 --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r3/bgpd.conf b/tests/topotests/bgp_disable_addpath_rx/r3/bgpd.conf new file mode 100644 index 0000000..98eb2e1 --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r3/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65003 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r3/zebra.conf b/tests/topotests/bgp_disable_addpath_rx/r3/zebra.conf new file mode 100644 index 0000000..417a484 --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r3/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r3-eth0 + ip address 192.168.2.3/24 +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r4/bgpd.conf b/tests/topotests/bgp_disable_addpath_rx/r4/bgpd.conf new file mode 100644 index 0000000..68245c4 --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r4/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65004 + timers bgp 3 10 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers connect 5 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_disable_addpath_rx/r4/zebra.conf b/tests/topotests/bgp_disable_addpath_rx/r4/zebra.conf new file mode 100644 index 0000000..241e386 --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/r4/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.254/32 +! +int r4-eth0 + ip address 192.168.2.4/24 +! diff --git a/tests/topotests/bgp_disable_addpath_rx/test_disable_addpath_rx.py b/tests/topotests/bgp_disable_addpath_rx/test_disable_addpath_rx.py new file mode 100644 index 0000000..70562ce --- /dev/null +++ b/tests/topotests/bgp_disable_addpath_rx/test_disable_addpath_rx.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if AddPath RX direction is not negotiated via AddPath capability. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_disable_addpath_rx(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + step( + "Check if r2 advertised only 2 paths to r1 (despite addpath-tx-all-paths enabled on r2)." + ) + + def check_bgp_advertised_routes(router): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "172.16.16.254/32": { + "addrPrefix": "172.16.16.254", + "prefixLen": 32, + }, + "192.168.2.0/24": { + "addrPrefix": "192.168.2.0", + "prefixLen": 24, + }, + }, + "totalPrefixCounter": 2, + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(check_bgp_advertised_routes, r2) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "AddPath TX not working." + + step("Check if AddPath RX is disabled on r1 and we receive only 2 paths.") + + def check_bgp_disabled_addpath_rx(router): + output = json.loads(router.vtysh_cmd("show bgp neighbor 192.168.1.2 json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "addPath": { + "ipv4Unicast": {"txReceived": True, "rxReceived": True} + }, + }, + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(check_bgp_disabled_addpath_rx, r1) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "AddPath RX advertised, but should not." + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_distance_change/__init__.py b/tests/topotests/bgp_distance_change/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_distance_change/bgp_admin_dist.json b/tests/topotests/bgp_distance_change/bgp_admin_dist.json new file mode 100755 index 0000000..e6a20a6 --- /dev/null +++ b/tests/topotests/bgp_distance_change/bgp_admin_dist.json @@ -0,0 +1,402 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }, + "static_routes": [ + { + "network": "192.168.22.1/32", + "no_of_ip": 2, + "next_hop": "10.0.0.2" + }, + { + "network": "fc07:1::1/128", + "no_of_ip": 2, + "next_hop": "fd00::2" + }, + { + "network": "192.168.21.1/32", + "no_of_ip": 2, + "next_hop": "blackhole" + }, + { + "network": "fc07:150::1/128", + "no_of_ip": 2, + "next_hop": "blackhole" + } + ] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }, + "static_routes": [ + { + "network": "192.168.20.1/32", + "no_of_ip": 2, + "next_hop": "blackhole" + }, + { + "network": "fc07:50::1/128", + "no_of_ip": 2, + "next_hop": "blackhole" + }, + { + "network": "192.168.21.1/32", + "no_of_ip": 2, + "next_hop": "blackhole" + }, + { + "network": "fc07:150::1/128", + "no_of_ip": 2, + "next_hop": "blackhole" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_distance_change/bgp_admin_dist_vrf.json b/tests/topotests/bgp_distance_change/bgp_admin_dist_vrf.json new file mode 100755 index 0000000..5528b59 --- /dev/null +++ b/tests/topotests/bgp_distance_change/bgp_admin_dist_vrf.json @@ -0,0 +1,404 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + + "r2": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [{ + "local_as": "100", + "vrf": "RED", + + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }], + "static_routes": [ + { + "network": "192.168.22.1/32", + "no_of_ip": 2, + "next_hop": "10.0.0.2", + "vrf": "RED" + }, + { + "network": "fc07:1::1/128", + "no_of_ip": 2, + "next_hop": "fd00::2", + "vrf": "RED" + }, + { + "network": "192.168.21.1/32", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + }, + { + "network": "fc07:150::1/128", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + } + ] + }, + "r2": { + "links": { + + "r1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [{ + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + }] + }, + "r3": { + "links": { + + "r1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [{ + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + }] + }, + "r4": { + "links": { + + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [{ + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + }] + }, + "r5": { + "links": { + + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [{ + "local_as": "300", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }], + "static_routes": [ + { + "network": "192.168.20.1/32", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + }, + { + "network": "fc07:50::1/128", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + }, + { + "network": "192.168.21.1/32", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + }, + { + "network": "fc07:150::1/128", + "no_of_ip": 2, + "next_hop": "blackhole", + "vrf": "RED" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_distance_change/r1/bgpd.conf b/tests/topotests/bgp_distance_change/r1/bgpd.conf new file mode 100644 index 0000000..07cfe2e --- /dev/null +++ b/tests/topotests/bgp_distance_change/r1/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_distance_change/r1/zebra.conf b/tests/topotests/bgp_distance_change/r1/zebra.conf new file mode 100644 index 0000000..6e9b0b4 --- /dev/null +++ b/tests/topotests/bgp_distance_change/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_distance_change/r2/bgpd.conf b/tests/topotests/bgp_distance_change/r2/bgpd.conf new file mode 100644 index 0000000..9cd86dc --- /dev/null +++ b/tests/topotests/bgp_distance_change/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_distance_change/r2/zebra.conf b/tests/topotests/bgp_distance_change/r2/zebra.conf new file mode 100644 index 0000000..93e3590 --- /dev/null +++ b/tests/topotests/bgp_distance_change/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_distance_change/test_bgp_admin_dist.py b/tests/topotests/bgp_distance_change/test_bgp_admin_dist.py new file mode 100755 index 0000000..0bd3d28 --- /dev/null +++ b/tests/topotests/bgp_distance_change/test_bgp_admin_dist.py @@ -0,0 +1,1269 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +import sys +import time +import pytest +import inspect +import os + + +"""Following tests are covered to test bgp admin distance functionality. +TC_1: + Verify bgp admin distance functionality when static route is + configured same as ebgp learnt route + +TC_2: + Verify ebgp admin distance functionality with ECMP. + +TC_3: + Verify ibgp admin distance functionality when static route is + configured same as bgp learnt route. +TC_4: + Verify ibgp admin distance functionality with ECMP. + +TC_7: Chaos - Verify bgp admin distance functionality with chaos. +""" + +################################# +# TOPOLOGY +################################# +""" + + +-------+ + +--------- | R2 | + | +-------+ + |iBGP | + +-------+ | + | R1 | |iBGP + +-------+ | + | | + | iBGP +-------+ eBGP +-------+ + +---------- | R3 |----------| R4 | + +-------+ +-------+ + | + |eBGP + | + +-------+ + | R5 | + +-------+ + + +""" + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + step, + write_test_footer, + create_static_routes, + verify_rib, + create_route_maps, + create_prefix_lists, + check_address_types, + reset_config_on_routers, + check_router_status, + stop_router, + kill_router_daemons, + start_router_daemons, + start_router, + get_frr_ipv6_linklocal, + verify_fib_routes, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_best_path_as_per_admin_distance, + clear_bgp, +) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +# Global variables +topo = None +bgp_convergence = False +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +NETWORK = { + "ipv4": [ + "192.168.20.1/32", + "192.168.20.2/32", + "192.168.21.1/32", + "192.168.21.2/32", + "192.168.22.1/32", + "192.168.22.2/32", + ], + "ipv6": [ + "fc07:50::1/128", + "fc07:50::2/128", + "fc07:150::1/128", + "fc07:150::2/128", + "fc07:1::1/128", + "fc07:1::2/128", + ], +} + +ADDR_TYPES = check_address_types() + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_admin_dist.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """teardown_module. + + Teardown the pytest environment. + * `mod`: module name + """ + logger.info("Running teardown_module to delete topology") + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# Tests starting +##################################################### +def test_bgp_admin_distance_ebgp_ecmp_p0(): + """ + TC: 2 + Verify ebgp admin distance functionality with ECMP. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipping test case because of BGP Convergence failure at setup") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step("Configure static route in R4 and R5, redistribute in bgp") + + for addr_type in ADDR_TYPES: + + input_dict = { + "r4": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "Null0"}] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_dict = { + "r5": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "Null0"}] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that route is learnt in DUT via ebgp") + + # Verifying RIB routes + protocol = "bgp" + input_dict = topo["routers"] + dut = "r3" + nhop = {"ipv4": [], "ipv6": []} + nhop["ipv4"].append(topo["routers"]["r4"]["links"]["r3"]["ipv4"].split("/")[0]) + nhop["ipv4"].append(topo["routers"]["r5"]["links"]["r3"]["ipv4"].split("/")[0]) + nhop["ipv6"].append(get_frr_ipv6_linklocal(tgen, "r4", "r3-r4-eth1")) + nhop["ipv6"].append(get_frr_ipv6_linklocal(tgen, "r5", "r1-r3-eth1")) + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure the static route in R3 (Dut).") + + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that static route is selected as best route in zebra.") + + # Verifying RIB routes + protocol = "static" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step(" Configure the admin distance of 254 to static route in R3.") + + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "next_hop": "Null0", + "admin_distance": 254, + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that bgp routes are selected as best routes in zebra.") + protocol = "bgp" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that bgp routes are selected as best routes in zebra.") + # Verifying RIB routes + protocol = "bgp" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure bgp admin distance 10 with CLI in dut.") + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": {"distance": {"ebgp": 10, "ibgp": 254, "local": 254}} + }, + "ipv6": { + "unicast": {"distance": {"ebgp": 10, "ibgp": 254, "local": 254}} + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify ebgp routes have admin distance of 10 in dut.") + + protocol = "bgp" + input_dict = topo["routers"] + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, admin_distance=10 + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step( + "Configure route map with weight as 200 and apply to one of the " + "neighbor (R4 neighbor)." + ) + + # Create Prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_ls_1": [ + { + "seqid": 10, + "network": NETWORK["ipv4"][0], + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_1_ipv6": [ + { + "seqid": 100, + "network": NETWORK["ipv6"][0], + "le": "128", + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3 = { + "r3": { + "route_maps": { + "RMAP_WEIGHT": [ + { + "action": "permit", + "match": {"ipv4": {"prefix_lists": "pf_ls_1"}}, + "set": {"weight": 200}, + }, + { + "action": "permit", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"weight": 200}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that bgp route is selected as best on by zebra in r3.") + + protocol = "bgp" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, admin_distance=10 + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Static route should not be selected as best route.") + protocol = "static" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_fib_routes( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert ( + result4 is not True + ), "Testcase {} : Failed. Wrong route is selected as best route.\n Error: {}".format( + tc_name, result4 + ) + + step("Reconfigure the static route without admin distance") + + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "next_hop": "Null0", + "admin_distance": 254, + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that static route is installed as best route.") + protocol = "static" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, fib=True + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Unconfigure the static route in R3.") + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "next_hop": "Null0", + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that bgp route is selected as best on by zebra in r3.") + + protocol = "bgp" + dut = "r3" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Un configure the route map on R3.") + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify bgp routes installed in zebra.") + + # Verifying RIB routes + protocol = "bgp" + input_dict = topo["routers"] + dut = "r3" + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type][0], "next_hop": "Null0"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +def test_bgp_admin_distance_ibgp_p0(): + """ + TC: 3 + Verify bgp admin distance functionality when static route is + configured same as ibgp learnt route + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipping test case because of BGP Convergence failure at setup") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step("Configure bgp admin distance 200 with CLI in dut.") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 200 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": "192.168.22.1/32", + "admin_distance": 200, + }, + { + "network": "192.168.22.2/32", + "admin_distance": 200, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": "fc07:1::1/128", + "admin_distance": 200, + }, + { + "network": "fc07:1::2/128", + "admin_distance": 200, + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Modify the admin distance value to 150.") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 150, "ibgp": 150, "local": 150} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 150, "ibgp": 150, "local": 150} + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 150 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": "192.168.22.1/32", + "admin_distance": 150, + }, + { + "network": "192.168.22.2/32", + "admin_distance": 150, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": "fc07:1::1/128", + "admin_distance": 150, + }, + { + "network": "fc07:1::2/128", + "admin_distance": 150, + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Un configure the admin distance value on DUT") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": { + "ebgp": 150, + "ibgp": 150, + "local": 150, + "delete": True, + } + } + }, + "ipv6": { + "unicast": { + "distance": { + "ebgp": 150, + "ibgp": 150, + "local": 150, + "delete": True, + } + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have default admin distance in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": "192.168.22.1/32", + "admin_distance": 20, + }, + { + "network": "192.168.22.2/32", + "admin_distance": 20, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": "fc07:1::1/128", + "admin_distance": 20, + }, + { + "network": "fc07:1::2/128", + "admin_distance": 20, + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Learn the same route via ebgp and ibgp peer. Configure admin " + "distance of 200 in DUT for both ebgp and ibgp peer. " + ) + + step("Verify that ebgp route is preferred over ibgp.") + + # Verifying RIB routes + protocol = "bgp" + input_dict = topo["routers"] + + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure static route Without any admin distance") + + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "Null0"}] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects static route.") + protocol = "static" + + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "Null0"}] + } + } + + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure static route with admin distance of 253") + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure admin distance of 254 in bgp for route.") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that zebra selects static route.") + protocol = "static" + + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Delete the static route.") + for addr_type in ADDR_TYPES: + + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +def test_bgp_admin_distance_chaos_p2(): + """ + TC: 7 + Chaos - Verify bgp admin distance functionality with chaos. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipping test case because of BGP Convergence failure at setup") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step("Configure bgp admin distance 200 with CLI in dut.") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 200 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 200, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 200, + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restart frr on R3") + stop_router(tgen, "r3") + start_router(tgen, "r3") + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Verify ebgp and ibgp routes have admin distance of 200 in dut.") + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restart bgpd process on R3") + kill_router_daemons(tgen, "r3", ["bgpd"]) + start_router_daemons(tgen, "r3", ["bgpd"]) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Verify ebgp and ibgp routes have admin distance of 200 in dut.") + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Clear BGP") + for rtr in topo["routers"]: + clear_bgp(tgen, "ipv4", rtr) + clear_bgp(tgen, "ipv6", rtr) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_distance_change/test_bgp_admin_dist_vrf.py b/tests/topotests/bgp_distance_change/test_bgp_admin_dist_vrf.py new file mode 100755 index 0000000..bf30f2e --- /dev/null +++ b/tests/topotests/bgp_distance_change/test_bgp_admin_dist_vrf.py @@ -0,0 +1,887 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +import sys +import time +import pytest +import inspect +import os + +"""Following tests are covered to test bgp admin distance functionality. +TC_5: + Verify bgp admin distance functionality when static route is configured + same as bgp learnt route in user vrf. + +TC_6: Verify bgp admin distance functionality with ECMP in user vrf. + +TC_7: + Verify bgp admin distance functionality when routes are + imported between VRFs. +""" + +################################# +# TOPOLOGY +################################# +""" + + +-------+ + +--------- | R2 | + | +-------+ + |iBGP | + +-------+ | + | R1 | |iBGP + +-------+ | + | | + | iBGP +-------+ eBGP +-------+ + +---------- | R3 |----------| R4 | + +-------+ +-------+ + | + |eBGP + | + +-------+ + | R5 | + +-------+ + + +""" + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + step, + write_test_footer, + create_static_routes, + verify_rib, + check_address_types, + reset_config_on_routers, + check_router_status, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_best_path_as_per_admin_distance, +) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +# Global variables +topo = None +bgp_convergence = False +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +NETWORK = { + "ipv4": [ + "192.168.20.1/32", + "192.168.20.2/32", + "192.168.21.1/32", + "192.168.21.2/32", + "192.168.22.1/32", + "192.168.22.2/32", + ], + "ipv6": [ + "fc07:50::1/128", + "fc07:50::2/128", + "fc07:150::1/128", + "fc07:150::2/128", + "fc07:1::1/128", + "fc07:1::2/128", + ], +} +ADDR_TYPES = check_address_types() + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_admin_dist_vrf.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """teardown_module. + + Teardown the pytest environment. + * `mod`: module name + """ + logger.info("Running teardown_module to delete topology") + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# Tests starting +##################################################### + + +def test_bgp_admin_distance_ebgp_vrf_p0(): + """ + TC: 5 + Verify bgp admin distance functionality when static route is + configured same as ebgp learnt route + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipping test case because of BGP Convergence failure at setup") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step("Configure bgp admin distance 200 with CLI in dut.") + + input_dict_1 = { + "r3": { + "bgp": [ + { + "vrf": "RED", + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 200 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 200, + "vrf": "RED", + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 200, + "vrf": "RED", + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 200, + "vrf": "RED", + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 200, + "vrf": "RED", + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute, vrf="RED" + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Modify the admin distance value to 150.") + + input_dict_1 = { + "r3": { + "bgp": [ + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 150, "ibgp": 150, "local": 150} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 150, "ibgp": 150, "local": 150} + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 150 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 150, + "vrf": "RED", + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 150, + "vrf": "RED", + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 150, + "vrf": "RED", + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 150, + "vrf": "RED", + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute, vrf="RED" + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Un configure the admin distance value on DUT") + + input_dict_1 = { + "r3": { + "bgp": [ + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": { + "ebgp": 150, + "ibgp": 150, + "local": 150, + "delete": True, + } + } + }, + "ipv6": { + "unicast": { + "distance": { + "ebgp": 150, + "ibgp": 150, + "local": 150, + "delete": True, + } + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have default admin distance in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + {"network": NETWORK["ipv4"][0], "admin_distance": 20, "vrf": "RED"}, + {"network": NETWORK["ipv4"][1], "admin_distance": 20, "vrf": "RED"}, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + {"network": NETWORK["ipv6"][0], "admin_distance": 20, "vrf": "RED"}, + {"network": NETWORK["ipv6"][1], "admin_distance": 20, "vrf": "RED"}, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute, vrf="RED" + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure static route Without any admin distance") + + for addr_type in ADDR_TYPES: + # Create Static routes + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "vrf": "RED"} + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects static route.") + protocol = "static" + # dual stack changes + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "vrf": "RED"} + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure static route with admin distance of 253") + for addr_type in ADDR_TYPES: + # Create Static routes + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "vrf": "RED", + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "vrf": "RED", + } + ] + } + } + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure admin distance of 254 in bgp for route .") + + input_dict_1 = { + "r3": { + "bgp": [ + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 254, "ibgp": 254, "local": 254} + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that zebra selects static route.") + protocol = "static" + # dual stack changes + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "vrf": "RED", + } + ] + } + } + + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Configure admin distance of 255 in bgp for route in vrf red") + + input_dict_1 = { + "r3": { + "bgp": [ + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 255, "ibgp": 255, "local": 255} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 255, "ibgp": 255, "local": 255} + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that zebra selects static route.") + protocol = "static" + # dual stack changes + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "vrf": "RED", + } + ] + } + } + + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Delete the static route.") + for addr_type in ADDR_TYPES: + # Create Static routes + input_dict = { + "r3": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": "Null0", + "admin_distance": 253, + "delete": True, + "vrf": "RED", + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +def test_bgp_admin_distance_ebgp_with_imported_rtes_vrf_p0(): + """ + TC: 5 + Verify bgp admin distance functionality when static route is configured + same as bgp learnt route in user vrf. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipping test case because of BGP Convergence failure at setup") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + step("Configure bgp admin distance 200 with CLI in dut.") + step(" Import route from vrf to default vrf") + input_dict_1 = { + "r3": { + "bgp": [ + { + "vrf": "RED", + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + }, + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200}, + "import": {"vrf": "RED"}, + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200}, + "import": { + "vrf": "RED", + }, + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp routes have admin distance of 200 in dut.") + # Verifying best path + dut = "r3" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 200, + "vrf": "RED", + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 200, + "vrf": "RED", + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 200, + "vrf": "RED", + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 200, + "vrf": "RED", + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute, vrf="RED" + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that routes are getting imported without any issues and " + "routes are calculated and installed in rib." + ) + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 200, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 200, + }, + ] + } + }, + } + + step("Verify that zebra selects bgp route.") + protocol = "bgp" + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step(" Un configure import route vrf red inside default vrf.") + input_dict_1 = { + "r3": { + "bgp": [ + { + "vrf": "RED", + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200} + } + }, + }, + }, + { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200}, + "import": {"vrf": "RED", "delete": True}, + } + }, + "ipv6": { + "unicast": { + "distance": {"ebgp": 200, "ibgp": 200, "local": 200}, + "import": {"vrf": "RED", "delete": True}, + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict = { + "ipv4": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv4"][1], + "admin_distance": 200, + }, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"][0], + "admin_distance": 200, + }, + { + "network": NETWORK["ipv6"][1], + "admin_distance": 200, + }, + ] + } + }, + } + + step("Verify that route withdrawal happens properly.") + protocol = "bgp" + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib( + tgen, + addr_type, + dut, + input_dict[addr_type], + protocol=protocol, + expected=False, + ) + assert ( + result4 is not True + ), "Testcase {} : Failed \n Route is not withdrawn. Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_distance_change/test_bgp_distance_change.py b/tests/topotests/bgp_distance_change/test_bgp_distance_change.py new file mode 100644 index 0000000..2ca50aa --- /dev/null +++ b/tests/topotests/bgp_distance_change/test_bgp_distance_change.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_distance_change.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Donatas Abraitis +# + +""" +bgp_distance_change.py: + +Test if works the following commands: +router bgp 65031 + address-family ipv4 unicast + distance bgp 123 123 123 + +Changed distance should reflect to RIB after changes. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_maximum_prefix_invalid(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_distance_change(router): + router.vtysh_cmd( + """ + configure terminal + router bgp 65000 + address-family ipv4 unicast + distance bgp 123 123 123 + """ + ) + + def _bgp_check_distance_change(router): + output = json.loads(router.vtysh_cmd("show ip route 172.16.255.254/32 json")) + expected = {"172.16.255.254/32": [{"protocol": "bgp", "distance": 123}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + + assert result is None, 'Failed to see BGP convergence in "{}"'.format(router) + + _bgp_distance_change(router) + + test_func = functools.partial(_bgp_check_distance_change, router) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + + assert result is None, 'Failed to see applied BGP distance in RIB "{}"'.format( + router + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dont_capability_negotiate/__init__.py b/tests/topotests/bgp_dont_capability_negotiate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf b/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf new file mode 100644 index 0000000..2f76d59 --- /dev/null +++ b/tests/topotests/bgp_dont_capability_negotiate/r1/bgpd.conf @@ -0,0 +1,12 @@ +! +!debug bgp neighbor +! +router bgp 65001 + no bgp ebgp-requires-policy + bgp default show-hostname + bgp default show-nexthop-hostname + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 dont-capability-negotiate +! diff --git a/tests/topotests/bgp_dont_capability_negotiate/r1/zebra.conf b/tests/topotests/bgp_dont_capability_negotiate/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_dont_capability_negotiate/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_dont_capability_negotiate/r2/bgpd.conf b/tests/topotests/bgp_dont_capability_negotiate/r2/bgpd.conf new file mode 100644 index 0000000..06e1e54 --- /dev/null +++ b/tests/topotests/bgp_dont_capability_negotiate/r2/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_dont_capability_negotiate/r2/zebra.conf b/tests/topotests/bgp_dont_capability_negotiate/r2/zebra.conf new file mode 100644 index 0000000..dc15cf7 --- /dev/null +++ b/tests/topotests/bgp_dont_capability_negotiate/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.1/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_dont_capability_negotiate/test_bgp_dont_capability_negotiate.py b/tests/topotests/bgp_dont_capability_negotiate/test_bgp_dont_capability_negotiate.py new file mode 100644 index 0000000..8269322 --- /dev/null +++ b/tests/topotests/bgp_dont_capability_negotiate/test_bgp_dont_capability_negotiate.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if BGP connection is established if at least one peer +sets `dont-capability-negotiate`. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast summary json")) + expected = { + "peers": { + "192.168.1.2": { + "pfxRcd": 2, + "pfxSnt": 2, + "state": "Established", + "peerState": "OK", + } + } + } + return topotest.json_cmp(output, expected) + + +def test_bgp_dont_capability_negotiate(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + test_func = functools.partial(bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with dont-capability-negotiate" + + +def test_bgp_check_fqdn(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_check_fqdn(fqdn=None): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 172.16.16.1/32 json")) + expected = { + "paths": [ + { + "nexthops": [ + { + "hostname": fqdn, + } + ], + "peer": { + "hostname": fqdn, + }, + } + ] + } + return topotest.json_cmp(output, expected) + + step("Enable all capabilities") + r1.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + no neighbor 192.168.1.2 dont-capability-negotiate + end + clear bgp 192.168.1.2 + """ + ) + + step("Wait to converge") + test_func = functools.partial(bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with all capabilities" + + step("Make sure FQDN capability is set") + test_func = functools.partial(_bgp_check_fqdn, "r2") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "FQDN capability enabled, but r1 can't see it" + + step("Disable sending any capabilities from r2") + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + neighbor 192.168.1.1 dont-capability-negotiate + end + clear bgp 192.168.1.1 + """ + ) + + step("Wait to converge") + test_func = functools.partial(bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with dont-capability-negotiate" + + step("Make sure FQDN capability is reset") + test_func = functools.partial(_bgp_check_fqdn) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "FQDN capability disabled, but we still have a hostname" + + step("Re-enable sending any capability from r2") + r2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + address-family ipv4 unicast + no neighbor 192.168.1.1 dont-capability-negotiate + end + clear bgp 192.168.1.1 + """ + ) + step("Wait to converge") + tgen = get_topogen() + test_func = functools.partial(bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with all capabilities re enabled" + + step("Make sure FQDN capability is r2") + test_func = functools.partial(_bgp_check_fqdn, "r2") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "FQDN capability enabled, but r1 can't see it" + + step("Disable sending fqdn capability") + r2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + no neighbor 192.168.1.1 capability fqdn + end + clear bgp 192.168.1.1 + """ + ) + step("Wait to converge") + tgen = get_topogen() + test_func = functools.partial(bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with no capability fqdn" + + step("Make sure FQDN capability is reset") + test_func = functools.partial(_bgp_check_fqdn) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "FQDN capability disabled, but we still have a hostname" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/__init__.py b/tests/topotests/bgp_dynamic_capability/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_dynamic_capability/r1/frr.conf b/tests/topotests/bgp_dynamic_capability/r1/frr.conf new file mode 100644 index 0000000..c959462 --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/r1/frr.conf @@ -0,0 +1,22 @@ +! +!debug bgp neighbor +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp long-lived stale-time 10 + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 capability dynamic + ! + address-family ipv4 unicast + neighbor 192.168.1.2 addpath-tx-all-paths + neighbor 192.168.1.2 addpath-rx-paths-limit 10 + exit-address-family +! +ip prefix-list r2 seq 5 permit 10.10.10.10/32 +! diff --git a/tests/topotests/bgp_dynamic_capability/r2/frr.conf b/tests/topotests/bgp_dynamic_capability/r2/frr.conf new file mode 100644 index 0000000..3cc1f1f --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/r2/frr.conf @@ -0,0 +1,24 @@ +! +!debug bgp neighbor +! +int lo + ip address 10.10.10.10/32 + ip address 10.10.10.20/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + bgp graceful-restart + bgp long-lived stale-time 20 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.1.1 capability dynamic + neighbor 192.168.1.1 addpath-rx-paths-limit 20 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_addpath.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_addpath.py new file mode 100644 index 0000000..4d7d46c --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_addpath.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if Addpath/Paths-Limit capabilities are adjusted dynamically. +T1: Enable Addpath/Paths-Limit capabilities and check if they are exchanged dynamically +T2: Disable paths limit and check if it's exchanged dynamically +T3: Disable Addpath capability RX and check if it's exchanged dynamically +T4: Disable Addpath capability and check if it's exchanged dynamically +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_addpath_paths_limit(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "addPath": { + "ipv4Unicast": { + "txAdvertisedAndReceived": False, + "txAdvertised": True, + "txReceived": False, + "rxAdvertisedAndReceived": True, + "rxAdvertised": True, + "rxReceived": True, + } + }, + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 10, + "receivedPathsLimit": 20, + } + }, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + #### + # T1: Enable Addpath/Paths-Limit capabilities and check if they are exchanged dynamically + #### + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + neighbor 192.168.1.1 addpath-tx-all-paths + neighbor 192.168.1.1 addpath-rx-paths-limit 21 + """ + ) + + def _enable_addpath_paths_limit(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "addPath": { + "ipv4Unicast": { + "txAdvertisedAndReceived": True, + "txAdvertised": True, + "txReceived": True, + "rxAdvertisedAndReceived": True, + "rxAdvertised": True, + "rxReceived": True, + } + }, + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 10, + "receivedPathsLimit": 21, + } + }, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + "capabilityRecv": 2, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _enable_addpath_paths_limit, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Something went wrong when enabling Addpath/Paths-Limit capabilities" + + ### + # T2: Disable paths limit and check if it's exchanged dynamically + ### + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + no neighbor 192.168.1.1 addpath-rx-paths-limit + """ + ) + + def _disable_paths_limit(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "addPath": { + "ipv4Unicast": { + "txAdvertisedAndReceived": True, + "txAdvertised": True, + "txReceived": True, + "rxAdvertisedAndReceived": True, + "rxAdvertised": True, + "rxReceived": True, + } + }, + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 10, + "receivedPathsLimit": 0, + } + }, + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + "capabilityRecv": 3, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _disable_paths_limit, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Something went wrong after disabling paths limit" + + ### + # T3: Disable Addpath capability RX and check if it's exchanged dynamically + ### + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + neighbor 192.168.1.1 disable-addpath-rx + """ + ) + + def _disable_addpath_rx(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "addPath": { + "ipv4Unicast": { + "txAdvertisedAndReceived": True, + "txAdvertised": True, + "txReceived": True, + "rxAdvertisedAndReceived": False, + "rxAdvertised": True, + "rxReceived": False, + } + }, + "pathsLimit": { + "ipv4Unicast": { + "advertisedAndReceived": True, + "advertisedPathsLimit": 10, + "receivedPathsLimit": 0, + } + }, + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + "capabilityRecv": 4, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _disable_addpath_rx, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Something went wrong after disabling Addpath RX flags" + + ### + # T4: Disable Addpath capability and check if it's exchanged dynamically + ### + r1.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + no neighbor 192.168.1.2 addpath-tx-all-paths + """ + ) + + def _disable_addpath(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "addPath": { + "ipv4Unicast": { + "txAdvertisedAndReceived": False, + "txAdvertised": False, + "txReceived": True, + "rxAdvertisedAndReceived": False, + "rxAdvertised": True, + "rxReceived": False, + } + }, + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + "capabilitySent": 1, + "capabilityRecv": 4, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _disable_addpath, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Something went wrong when disabling Addpath capability" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_fqdn.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_fqdn.py new file mode 100644 index 0000000..26fae17 --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_fqdn.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +""" +Test if fqdn capability is exchanged dynamically. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_fqdn(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "hostName": { + "advHostName": "r1", + "rcvHostName": "r2", + }, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step("Disable fqdn capability and check if it's exchanged dynamically") + + # Clear message stats to check if we receive a notification or not after we + # disable fqdn capability. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + no neighbor 192.168.1.2 capability fqdn + """ + ) + + def _bgp_check_if_fqdn_capability_is_absent(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "hostName": { + "advHostName": None, + "rcvHostName": "r2", + }, + }, + "messageStats": { + "notificationsRecv": 0, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_fqdn_capability_is_absent, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to disable fqdn capability" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py new file mode 100644 index 0000000..d67bfea --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if BGP graceful restart / long-lived graceful restart capabilities +(restart time, stale time and notification flag) are exchanged dynamically +via BGP dynamic capability. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_graceful_restart(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "gracefulRestart": "advertisedAndReceived", + "longLivedGracefulRestart": "advertisedAndReceived", + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + "gracefulRestartInfo": { + "nBit": True, + "timers": { + "receivedRestartTimer": 120, + "configuredLlgrStaleTime": 10, + }, + "ipv4Unicast": { + "timers": { + "llgrStaleTime": 10, + } + }, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step( + "Change Graceful-Restart restart-time, LLGR stale-time and check if they changed dynamically" + ) + + # Clear message stats to check if we receive a notification or not after we + # change the settings fo LLGR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r2.vtysh_cmd( + """ + configure terminal + router bgp + bgp graceful-restart restart-time 123 + bgp long-lived-graceful-restart stale-time 5 + """ + ) + + def _bgp_check_if_session_not_reset_after_changing_gr_settings(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "gracefulRestart": "advertisedAndReceived", + "longLivedGracefulRestart": "advertisedAndReceived", + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + "gracefulRestartInfo": { + "nBit": True, + "timers": { + "receivedRestartTimer": 123, + "configuredLlgrStaleTime": 10, + }, + "ipv4Unicast": { + "timers": { + "llgrStaleTime": 5, + } + }, + }, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 2, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_session_not_reset_after_changing_gr_settings, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Session was reset after changing Graceful-Restart restart-time" + + step( + "Disable Graceful-Restart notification support, and check if it's changed dynamically" + ) + + # Clear message stats to check if we receive a notification or not after we + # disable graceful-restart notification support. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r2.vtysh_cmd( + """ + configure terminal + router bgp + no bgp graceful-restart notification + """ + ) + + def _bgp_check_if_session_not_reset_after_changing_notification(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "gracefulRestart": "advertisedAndReceived", + "longLivedGracefulRestart": "advertisedAndReceived", + }, + "gracefulRestartInfo": { + "nBit": False, + "timers": { + "receivedRestartTimer": 123, + "configuredLlgrStaleTime": 10, + }, + "ipv4Unicast": { + "timers": { + "llgrStaleTime": 5, + } + }, + }, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_session_not_reset_after_changing_notification, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Session was reset after changing Graceful-Restart notification support" + + # Clear message stats to check if we receive a notification or not after we + # disable GR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + bgp graceful-restart-disable + """ + ) + + def _bgp_check_if_gr_llgr_capability_is_absent(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "gracefulRestartCapability": "received", + "longLivedGracefulRestart": "received", + }, + "messageStats": { + "notificationsRecv": 0, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_gr_llgr_capability_is_absent, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to disable GR/LLGR capabilities" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_orf.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_orf.py new file mode 100644 index 0000000..9e1f26f --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_orf.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if ORF capability is adjusted dynamically. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_orf(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step( + "Apply incoming prefix-list to r1 and check if we advertise only 10.10.10.20/32 from r2" + ) + + # Clear message stats to check if we receive a notification or not after we + # enable ORF capability. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + neighbor 192.168.1.2 prefix-list r2 in + neighbor 192.168.1.2 capability orf prefix-list both + """ + ) + + r2.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + neighbor 192.168.1.1 capability orf prefix-list both + """ + ) + + def _bgp_check_if_session_not_reset(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + "capabilityRecv": 1, + "capabilitySent": 1, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 1, + "afDependentCap": { + "orfPrefixList": { + "sendMode": "advertisedAndReceived", + "recvMode": "advertisedAndReceived", + } + }, + "incomingUpdatePrefixFilterList": "r2", + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_session_not_reset, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Session was reset after setting up ORF capability" + + r1.vtysh_cmd( + """ + configure terminal + ip prefix-list r2 seq 5 permit 10.10.10.20/32 + """ + ) + + def _bgp_check_if_we_send_correct_prefix(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.1 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.20/32": { + "valid": True, + }, + }, + "totalPrefixCounter": 1, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_we_send_correct_prefix, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Only 10.10.10.20/32 SHOULD be advertised due to ORF filtering" + + # Clear message stats to check if we receive a notification or not after we + # disable ORF capability. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + address-family ipv4 unicast + no neighbor 192.168.1.2 capability orf prefix-list both + """ + ) + + def _bgp_check_if_orf_capability_is_absent(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + }, + "messageStats": { + "notificationsRecv": 0, + "notificationsSent": 0, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 1, + "afDependentCap": { + "orfPrefixList": { + "sendMode": "received", + "recvMode": "received", + } + }, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_orf_capability_is_absent, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to disable ORF capability" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py new file mode 100644 index 0000000..f6c1e25 --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if role capability is exchanged dynamically. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_role(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "localRole": "undefined", + "remoteRole": "undefined", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step("Set local-role and check if it's exchanged dynamically") + + # Clear message stats to check if we receive a notification or not after we + # change the role. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.1.2 local-role customer + """ + ) + + r2.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.1.1 local-role provider + """ + ) + + def _bgp_check_if_session_not_reset(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "localRole": "customer", + "remoteRole": "provider", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "role": "advertisedAndReceived", + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_session_not_reset, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Session was reset after setting role capability" + + # Clear message stats to check if we receive a notification or not after we + # change the role. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + no neighbor 192.168.1.2 local-role customer + """ + ) + + def _bgp_check_if_role_capability_is_absent(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "localRole": "undefined", + "remoteRole": "provider", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "role": "received", + }, + "messageStats": { + "notificationsRecv": 0, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_role_capability_is_absent, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to disable role capability" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py new file mode 100644 index 0000000..128283b --- /dev/null +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if software version capability is exchanged dynamically. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_software_version(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "softwareVersion": { + "advertisedSoftwareVersion": None, + "receivedSoftwareVersion": None, + }, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + step("Enable software version capability and check if it's exchanged dynamically") + + # Clear message stats to check if we receive a notification or not after we + # enable software-version capability. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.1.2 capability software-version + """ + ) + + r2.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.1.1 capability software-version + """ + ) + + def _bgp_check_if_session_not_reset(): + def _bgp_software_version(): + try: + versions = output["192.168.1.2"]["neighborCapabilities"][ + "softwareVersion" + ] + adv = versions["advertisedSoftwareVersion"] + rcv = versions["receivedSoftwareVersion"] + + if not adv and not rcv: + return "" + + pattern = "^FRRouting/\\d.+" + if re.search(pattern, adv) and re.search(pattern, rcv): + return adv, rcv + except: + return "" + + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + adv, rcv = _bgp_software_version() + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "softwareVersion": { + "advertisedSoftwareVersion": adv, + "receivedSoftwareVersion": rcv, + }, + }, + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 3, + } + }, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_session_not_reset, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "Session was reset after enabling software version capability" + + # Clear message stats to check if we receive a notification or not after we + # disable software-version capability. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") + r1.vtysh_cmd( + """ + configure terminal + router bgp + no neighbor 192.168.1.2 capability software-version + """ + ) + + def _bgp_check_if_software_version_capability_is_absent(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.1.2": { + "bgpState": "Established", + "neighborCapabilities": { + "dynamic": "advertisedAndReceived", + "softwareVersion": { + "advertisedSoftwareVersion": None, + }, + }, + "messageStats": { + "notificationsRecv": 0, + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_if_software_version_capability_is_absent, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to disable software version capability" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/__init__.py b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/bgpd.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/bgpd.conf new file mode 100644 index 0000000..c320bb5 --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/bgpd.conf @@ -0,0 +1,7 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.101 remote-as external + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/zebra.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/zebra.conf new file mode 100644 index 0000000..1782edc --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r1/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.1.1/32 +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/bgpd.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/bgpd.conf new file mode 100644 index 0000000..cb712e9 --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/bgpd.conf @@ -0,0 +1,3 @@ +router bgp 65103 + no bgp ebgp-requires-policy + neighbor 192.168.1.101 remote-as external diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/zebra.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/zebra.conf new file mode 100644 index 0000000..968171e --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.103/24 +! diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/bgpd.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/bgpd.conf new file mode 100644 index 0000000..a6e3260 --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 65000 + bgp router-id 192.168.1.101 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.103 remote-as external diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/zebra.conf b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/zebra.conf new file mode 100644 index 0000000..ddcf862 --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/r3/zebra.conf @@ -0,0 +1,4 @@ +! +int r3-eth0 + ip address 192.168.1.101/24 +! diff --git a/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/test_bgp-ebgp-common-subnet-nexthop-unchanged.py b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/test_bgp-ebgp-common-subnet-nexthop-unchanged.py new file mode 100644 index 0000000..d9ccd69 --- /dev/null +++ b/tests/topotests/bgp_ebgp_common_subnet_nexthop_unchanged/test_bgp-ebgp-common-subnet-nexthop-unchanged.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +https://tools.ietf.org/html/rfc4271 + +Check if NEXT_HOP attribute is not changed if peer X shares a +common subnet with this address. + +- Otherwise, if the route being announced was learned from an + external peer, the speaker can use an IP address of any + adjacent router (known from the received NEXT_HOP attribute) + that the speaker itself uses for local route calculation in + the NEXT_HOP attribute, provided that peer X shares a common + subnet with this address. This is a second form of "third + party" NEXT_HOP attribute. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_ebgp_common_subnet_nh_unchanged(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": { + "192.168.1.1": {"state": "Established"}, + "192.168.1.103": {"state": "Established"}, + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, r3) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Failed bgp convergence in "{}"'.format(r3) + + def _bgp_nh_unchanged(router): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.1.1/32 json")) + expected = {"paths": [{"nexthops": [{"ip": "192.168.1.1"}]}]} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_nh_unchanged, r2) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Wrong next-hop in "{}"'.format(r2) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ebgp_requires_policy/__init__.py b/tests/topotests/bgp_ebgp_requires_policy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ebgp_requires_policy/r1/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r1/bgpd.conf new file mode 100644 index 0000000..add37ee --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65000 + neighbor 192.168.255.2 remote-as 1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 500 + address-family ipv4 unicast + redistribute connected + neighbor 192.168.255.2 route-map outgoing out +! +ip prefix-list peer-out permit 172.16.255.254/32 +route-map outgoing permit 10 + match ip address prefix-list peer-out +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r1/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r2/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r2/bgpd.conf new file mode 100644 index 0000000..802c327 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 1000 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r2/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r3/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r3/bgpd.conf new file mode 100644 index 0000000..1280b42 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r3/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65000 + neighbor 192.168.255.2 remote-as 1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 500 + address-family ipv4 unicast + redistribute connected diff --git a/tests/topotests/bgp_ebgp_requires_policy/r3/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r3/zebra.conf new file mode 100644 index 0000000..39499a1 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r3/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r3-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r4/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r4/bgpd.conf new file mode 100644 index 0000000..802c327 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r4/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 1000 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r4/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r4/zebra.conf new file mode 100644 index 0000000..b859115 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r5/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r5/bgpd.conf new file mode 100644 index 0000000..60b29f6 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r5/bgpd.conf @@ -0,0 +1,7 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65000 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r5/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r5/zebra.conf new file mode 100644 index 0000000..7ef77a6 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r5/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r5-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r6/bgpd.conf b/tests/topotests/bgp_ebgp_requires_policy/r6/bgpd.conf new file mode 100644 index 0000000..7ad5d92 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r6/bgpd.conf @@ -0,0 +1,4 @@ +router bgp 65000 + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/r6/zebra.conf b/tests/topotests/bgp_ebgp_requires_policy/r6/zebra.conf new file mode 100644 index 0000000..1c617c4 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/r6/zebra.conf @@ -0,0 +1,6 @@ +! +interface r6-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ebgp_requires_policy/test_bgp_ebgp_requires_policy.py b/tests/topotests/bgp_ebgp_requires_policy/test_bgp_ebgp_requires_policy.py new file mode 100644 index 0000000..6e3b285 --- /dev/null +++ b/tests/topotests/bgp_ebgp_requires_policy/test_bgp_ebgp_requires_policy.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_ebgp_requires_policy.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Donatas Abraitis +# + +""" +bgp_ebgp_requires_policy.py: + +Test if eBGP sender without a filter applied to the peer is allowed +to send advertisements. + +Scenario 1: + r1 has a filter applied for outgoing direction, + r2 receives 192.168.255.1/32. + +Scenario 2: + r3 hasn't a filter appied for outgoing direction, + r4 does not receive 192.168.255.1/32. + +Scenario 3: + r5 and r6 establish iBGP session which in turn should ignore + RFC8212. All routes for both directions MUST work. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 7): + tgen.add_router("r{}".format(routern)) + + # Scenario 1. + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # Scenario 2. + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + # Scenario 3. + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r5"]) + switch.add_link(tgen.gears["r6"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_ebgp_requires_policy(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(router): + output = json.loads( + tgen.gears[router].vtysh_cmd("show ip bgp neighbor 192.168.255.1 json") + ) + expected = {"192.168.255.1": {"bgpState": "Established"}} + return topotest.json_cmp(output, expected) + + def _bgp_has_routes(router): + output = json.loads( + tgen.gears[router].vtysh_cmd( + "show ip bgp neighbor 192.168.255.1 routes json" + ) + ) + expected = {"routes": {"172.16.255.254/32": [{"valid": True}]}} + return topotest.json_cmp(output, expected) + + def _bgp_advertised_routes(router): + output = json.loads( + tgen.gears[router].vtysh_cmd( + "show ip bgp neighbor 192.168.255.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": {}, + "totalPrefixCounter": 0, + "filteredPrefixCounter": 0, + } + return topotest.json_cmp(output, expected) + + # Scenario 1. + logger.info("Scenario 1: r2 receives 192.168.255.1/32 from r1") + test_func = functools.partial(_bgp_converge, "r2") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "Failed bgp convergence (r2)" + + test_func = functools.partial(_bgp_has_routes, "r2") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "r2 does not receive 192.168.255.1/32" + + # Scenario 2. + logger.info("Scenario 2: r3 must not send 192.168.255.1/32 to r4") + test_func = functools.partial(_bgp_converge, "r4") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "Failed bgp convergence (r4)" + + test_func = functools.partial(_bgp_advertised_routes, "r3") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "r3 announced 192.168.255.1/32 to r4" + + # Scenario 3. + logger.info("Scenario 3: r6 receives 192.168.255.1/32 from r5 (iBGP)") + test_func = functools.partial(_bgp_converge, "r6") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "Failed bgp convergence (r6)" + + test_func = functools.partial(_bgp_has_routes, "r6") + success, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert success is True, "r6 does not receive 192.168.255.1/32" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ecmp_topo1/__init__.py b/tests/topotests/bgp_ecmp_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.dot b/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.dot new file mode 100644 index 0000000..90295e1 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.dot @@ -0,0 +1,206 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph ospf_ecmp_iBGP_topo1 { + label="bgp ecmp topo1 - eBGP with different AS numbers"; + labelloc="t"; + + # Routers + r1 [ + label="r1\nrtr-id 10.0.255.1/32", + shape=doubleoctagon, + fillcolor="#f08080", + style=filled, + ]; + + # 4 Switches for eBGP Peers + s1 [ + label="s1\n10.0.1.0/24", + shape=oval, + fillcolor="#d0e0d0", + style=filled, + ]; + s2 [ + label="s2\n10.0.2.0/24", + shape=oval, + fillcolor="#d0e0d0", + style=filled, + ]; + s3 [ + label="s3\n10.0.3.0/24", + shape=oval, + fillcolor="#d0e0d0", + style=filled, + ]; + s4 [ + label="s4\n10.0.4.0/24", + shape=oval, + fillcolor="#d0e0d0", + style=filled, + ]; + + # 20 ExaBGP Peers AS 101...120 + peer1 [ + label="eBGP peer1\nAS99\nrtr-id 10.0.1.101/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer2 [ + label="eBGP peer2\nAS99\nrtr-id 10.0.1.102/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer3 [ + label="eBGP peer3\nAS99\nrtr-id 10.0.1.103/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer4 [ + label="eBGP peer4\nAS99\nrtr-id 10.0.1.104/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer5 [ + label="eBGP peer5\nAS99\nrtr-id 10.0.1.105/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer6 [ + label="eBGP peer6\nAS99\nrtr-id 10.0.2.106/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer7 [ + label="eBGP peer7\nAS99\nrtr-id 10.0.2.107/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer8 [ + label="eBGP peer8\nAS99\nrtr-id 10.0.2.108/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer9 [ + label="eBGP peer9\nAS99\nrtr-id 10.0.2.109/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer10 [ + label="eBGP peer10\nAS99\nrtr-id 10.0.2.110/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer11 [ + label="eBGP peer11\nAS111\nrtr-id 10.0.3.111/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer12 [ + label="eBGP peer12\nAS112\nrtr-id 10.0.3.112/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer13 [ + label="eBGP peer13\nAS113\nrtr-id 10.0.3.113/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer14 [ + label="eBGP peer14\nAS114\nrtr-id 10.0.3.114/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer15 [ + label="eBGP peer15\nAS115\nrtr-id 10.0.3.115/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer16 [ + label="eBGP peer16\nAS116\nrtr-id 10.0.4.116/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer17 [ + label="eBGP peer17\nAS117\nrtr-id 10.0.4.117/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer18 [ + label="eBGP peer18\nAS118\nrtr-id 10.0.4.118/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer19 [ + label="eBGP peer19\nAS119\nrtr-id 10.0.4.119/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + peer20 [ + label="eBGP peer20\nAS120\nrtr-id 10.0.4.120/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + + # Connections + r1 -- s1 [label="eth0\n.1"]; + r1 -- s2 [label="eth1\n.1"]; + r1 -- s3 [label="eth2\n.1"]; + r1 -- s4 [label="eth3\n.1"]; + + peer1 -- s1 [label="eth0\n.101"]; + peer2 -- s1 [label="eth0\n.102"]; + peer3 -- s1 [label="eth0\n.103"]; + peer4 -- s1 [label="eth0\n.104"]; + peer5 -- s1 [label="eth0\n.105"]; + peer6 -- s2 [label="eth0\n.106"]; + peer7 -- s2 [label="eth0\n.107"]; + peer8 -- s2 [label="eth0\n.108"]; + peer9 -- s2 [label="eth0\n.109"]; + peer10 -- s2 [label="eth0\n.110"]; + peer11 -- s3 [label="eth0\n.111"]; + peer12 -- s3 [label="eth0\n.112"]; + peer13 -- s3 [label="eth0\n.113"]; + peer14 -- s3 [label="eth0\n.114"]; + peer15 -- s3 [label="eth0\n.115"]; + peer16 -- s4 [label="eth0\n.116"]; + peer17 -- s4 [label="eth0\n.117"]; + peer18 -- s4 [label="eth0\n.118"]; + peer19 -- s4 [label="eth0\n.119"]; + peer20 -- s4 [label="eth0\n.120"]; + + # Arrange network to make cleaner diagram + { rank=same peer1 peer2 peer3 peer4 peer5 } -- s1 -- { rank=same peer6 peer7 peer8 peer9 peer10 } -- s2 + -- { rank=same peer11 peer12 peer13 peer14 peer15 } -- s3 -- { rank=same peer16 peer17 peer18 peer19 peer20 } -- s4 + -- { rank=same r1 } [style=invis] +} diff --git a/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.pdf b/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.pdf new file mode 100644 index 0000000..b4d4f6a Binary files /dev/null and b/tests/topotests/bgp_ecmp_topo1/bgp-ecmp-topo1.pdf differ diff --git a/tests/topotests/bgp_ecmp_topo1/exabgp.env b/tests/topotests/bgp_ecmp_topo1/exabgp.env new file mode 100644 index 0000000..ec978c6 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/exabgp.env @@ -0,0 +1,55 @@ + +[exabgp.api] +ack = false +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_ecmp_topo1/peer1/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer1/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer1/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer1/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer1/exabgp.cfg new file mode 100644 index 0000000..97a024c --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer1/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 1 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 1; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.101; + local-address 10.0.1.101; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer10/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer10/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer10/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer10/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer10/exabgp.cfg new file mode 100644 index 0000000..e318162 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer10/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 10 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 10; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.110; + local-address 10.0.2.110; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer11/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer11/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer11/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer11/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer11/exabgp.cfg new file mode 100644 index 0000000..ea5bdcc --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer11/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 11 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 11; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.111; + local-address 10.0.3.111; + local-as 111; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer12/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer12/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer12/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer12/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer12/exabgp.cfg new file mode 100644 index 0000000..81fb503 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer12/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 12 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 12; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.112; + local-address 10.0.3.112; + local-as 112; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer13/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer13/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer13/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer13/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer13/exabgp.cfg new file mode 100644 index 0000000..4007841 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer13/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 13 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 13; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.113; + local-address 10.0.3.113; + local-as 113; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer14/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer14/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer14/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer14/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer14/exabgp.cfg new file mode 100644 index 0000000..afb8741 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer14/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 14 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 14; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.114; + local-address 10.0.3.114; + local-as 114; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer15/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer15/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer15/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer15/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer15/exabgp.cfg new file mode 100644 index 0000000..9a4ca7f --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer15/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 15 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 15; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.115; + local-address 10.0.3.115; + local-as 115; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer16/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer16/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer16/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer16/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer16/exabgp.cfg new file mode 100644 index 0000000..a0bb88a --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer16/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 16 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 16; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.116; + local-address 10.0.4.116; + local-as 116; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer17/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer17/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer17/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer17/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer17/exabgp.cfg new file mode 100644 index 0000000..870d33d --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer17/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 17 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 17; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.117; + local-address 10.0.4.117; + local-as 117; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer18/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer18/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer18/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer18/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer18/exabgp.cfg new file mode 100644 index 0000000..c5a1ca6 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer18/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 18 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 18; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.118; + local-address 10.0.4.118; + local-as 118; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer19/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer19/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer19/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer19/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer19/exabgp.cfg new file mode 100644 index 0000000..f662ccf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer19/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 19 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 19; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.119; + local-address 10.0.4.119; + local-as 119; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer2/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer2/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer2/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer2/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer2/exabgp.cfg new file mode 100644 index 0000000..673d6ec --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer2/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 2 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 2; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.102; + local-address 10.0.1.102; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer20/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer20/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer20/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer20/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer20/exabgp.cfg new file mode 100644 index 0000000..5ea2c91 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer20/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 20 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 20; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.120; + local-address 10.0.4.120; + local-as 120; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer3/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer3/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer3/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer3/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer3/exabgp.cfg new file mode 100644 index 0000000..47a25ca --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer3/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 3 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 3; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.103; + local-address 10.0.1.103; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer4/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer4/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer4/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer4/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer4/exabgp.cfg new file mode 100644 index 0000000..376ac5f --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer4/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 4 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 4; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.104; + local-address 10.0.1.104; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer5/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer5/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer5/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer5/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer5/exabgp.cfg new file mode 100644 index 0000000..878d728 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer5/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 5 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 5; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.105; + local-address 10.0.1.105; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer6/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer6/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer6/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer6/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer6/exabgp.cfg new file mode 100644 index 0000000..fc70267 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer6/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 6 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 6; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.106; + local-address 10.0.2.106; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer7/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer7/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer7/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer7/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer7/exabgp.cfg new file mode 100644 index 0000000..09827a8 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer7/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 7 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 7; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.107; + local-address 10.0.2.107; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer8/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer8/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer8/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer8/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer8/exabgp.cfg new file mode 100644 index 0000000..37c01da --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer8/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 8 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 8; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.108; + local-address 10.0.2.108; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/peer9/exa-send.py b/tests/topotests/bgp_ecmp_topo1/peer9/exa-send.py new file mode 100755 index 0000000..1a8e6bf --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer9/exa-send.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +if peer <= 10: + asnum = 99 +else: + asnum = peer + 100 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp\n" + % (i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes per PE - different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.202.%s.0/24 med 100 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.203.%s.0/24 med %i next-hop 10.0.%i.%i origin igp\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Announce numRoutes equal routes with different med per PE and different neighbor AS, but same source AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.204.%s.0/24 med %i next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (i, peer, (((peer - 1) / 5) + 1), peer + 100, asnum) + ) + stdout.flush() + +# Announce 2 different route per peer +stdout.write( + "announce route 10.205.%i.0/24 next-hop 10.0.%i.%i origin igp\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100) +) +stdout.write( + "announce route 10.206.%i.0/24 next-hop 10.0.%i.%i origin igp as-path [ %i 200 ]\n" + % (peer, (((peer - 1) / 5) + 1), peer + 100, asnum) +) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_ecmp_topo1/peer9/exabgp.cfg b/tests/topotests/bgp_ecmp_topo1/peer9/exabgp.cfg new file mode 100644 index 0000000..8fb5c58 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/peer9/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 9 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 9; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.109; + local-address 10.0.2.109; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_ecmp_topo1/r1/bgpd.conf b/tests/topotests/bgp_ecmp_topo1/r1/bgpd.conf new file mode 100644 index 0000000..49981ac --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/r1/bgpd.conf @@ -0,0 +1,51 @@ +! +hostname r1 +log file bgpd.log +! +router bgp 100 + bgp router-id 10.0.255.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 10.0.1.101 remote-as 99 + neighbor 10.0.1.101 timers 3 10 + neighbor 10.0.1.102 remote-as 99 + neighbor 10.0.1.102 timers 3 10 + neighbor 10.0.1.103 remote-as 99 + neighbor 10.0.1.103 timers 3 10 + neighbor 10.0.1.104 remote-as 99 + neighbor 10.0.1.104 timers 3 10 + neighbor 10.0.1.105 remote-as 99 + neighbor 10.0.1.105 timers 3 10 + neighbor 10.0.2.106 remote-as 99 + neighbor 10.0.2.106 timers 3 10 + neighbor 10.0.2.107 remote-as 99 + neighbor 10.0.2.107 timers 3 10 + neighbor 10.0.2.108 remote-as 99 + neighbor 10.0.2.108 timers 3 10 + neighbor 10.0.2.109 remote-as 99 + neighbor 10.0.2.109 timers 3 10 + neighbor 10.0.2.110 remote-as 99 + neighbor 10.0.2.110 timers 3 10 + neighbor 10.0.3.111 remote-as 111 + neighbor 10.0.3.111 timers 3 10 + neighbor 10.0.3.112 remote-as 112 + neighbor 10.0.3.112 timers 3 10 + neighbor 10.0.3.113 remote-as 113 + neighbor 10.0.3.113 timers 3 10 + neighbor 10.0.3.114 remote-as 114 + neighbor 10.0.3.114 timers 3 10 + neighbor 10.0.3.115 remote-as 115 + neighbor 10.0.3.115 timers 3 10 + neighbor 10.0.4.116 remote-as 116 + neighbor 10.0.4.116 timers 3 10 + neighbor 10.0.4.117 remote-as 117 + neighbor 10.0.4.117 timers 3 10 + neighbor 10.0.4.118 remote-as 118 + neighbor 10.0.4.118 timers 3 10 + neighbor 10.0.4.119 remote-as 119 + neighbor 10.0.4.119 timers 3 10 + neighbor 10.0.4.120 remote-as 120 + neighbor 10.0.4.120 timers 3 10 + ! +! + diff --git a/tests/topotests/bgp_ecmp_topo1/r1/summary.txt b/tests/topotests/bgp_ecmp_topo1/r1/summary.txt new file mode 100644 index 0000000..68de28a --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/r1/summary.txt @@ -0,0 +1,131 @@ +{ +"ipv4Unicast":{ + "routerId":"10.0.255.1", + "as":100, + "vrfName":"default", + "peerCount":20, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.102":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.103":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.104":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.105":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.106":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.107":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.108":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.109":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.110":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.111":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.112":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.113":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.114":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.115":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.116":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.117":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.118":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.119":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.120":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + } + }, + "totalPeers":20 +} +} diff --git a/tests/topotests/bgp_ecmp_topo1/r1/summary20.txt b/tests/topotests/bgp_ecmp_topo1/r1/summary20.txt new file mode 100644 index 0000000..4895cdb --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/r1/summary20.txt @@ -0,0 +1,129 @@ +{ + "routerId":"10.0.255.1", + "as":100, + "vrfName":"default", + "peerCount":20, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.102":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.103":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.104":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.1.105":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.106":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.107":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.108":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.109":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.2.110":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.111":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.112":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.113":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.114":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.3.115":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.116":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.117":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.118":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.119":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + }, + "10.0.4.120":{ + "outq":0, + "inq":0, + "pfxRcd":42, + "state":"Established" + } + }, + "totalPeers":20 +} diff --git a/tests/topotests/bgp_ecmp_topo1/r1/zebra.conf b/tests/topotests/bgp_ecmp_topo1/r1/zebra.conf new file mode 100644 index 0000000..77c76cd --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/r1/zebra.conf @@ -0,0 +1,16 @@ +! +hostname r1 +log file zebra.log +! +interface r1-eth0 + ip address 10.0.1.1/24 +! +interface r1-eth1 + ip address 10.0.2.1/24 +! +interface r1-eth2 + ip address 10.0.3.1/24 +! +interface r1-eth3 + ip address 10.0.4.1/24 +! diff --git a/tests/topotests/bgp_ecmp_topo1/test_bgp_ecmp_topo1.py b/tests/topotests/bgp_ecmp_topo1/test_bgp_ecmp_topo1.py new file mode 100644 index 0000000..7af3f48 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo1/test_bgp_ecmp_topo1.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC + +# +# test_bgp_ecmp_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bgp_ecmp_topo1.py: Test BGP topology with ECMP (Equal Cost MultiPath). +""" + +import json +import functools +import os +import sys +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +total_ebgp_peers = 20 + +##################################################### +# +# Network Topology Definition +# +##################################################### + + +def build_topo(tgen): + router = tgen.add_router("r1") + + # Setup Switches - 1 switch per 5 peering routers + for swNum in range(1, (total_ebgp_peers + 4) // 5 + 1): + switch = tgen.add_switch("s{}".format(swNum)) + switch.add_link(router) + + # Add 'total_ebgp_peers' number of eBGP ExaBGP neighbors + for peerNum in range(1, total_ebgp_peers + 1): + swNum = (peerNum - 1) // 5 + 1 + + peer_ip = "10.0.{}.{}".format(swNum, peerNum + 100) + peer_route = "via 10.0.{}.1".format(swNum) + peer = tgen.add_exabgp_peer( + "peer{}".format(peerNum), ip=peer_ip, defaultRoute=peer_route + ) + + switch = tgen.gears["s{}".format(swNum)] + switch.add_link(peer) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # Starting Routers + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.start() + + # Starting Hosts and init ExaBGP on each of them + topotest.sleep(10, "starting BGP on all {} peers".format(total_ebgp_peers)) + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, "exabgp.env") + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + del module + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + "Test for BGP topology convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Expected result + router = tgen.gears["r1"] + if router.has_version("<", "3.0"): + reffile = os.path.join(CWD, "r1/summary20.txt") + else: + reffile = os.path.join(CWD, "r1/summary.txt") + + expected = json.loads(open(reffile).read()) + + def _output_summary_cmp(router, cmd, data): + """ + Runs `cmd` that returns JSON data (normally the command ends + with 'json') and compare with `data` contents. + """ + output = router.vtysh_cmd(cmd, isjson=True) + return topotest.json_cmp(output, data) + + test_func = functools.partial( + _output_summary_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assertmsg = "BGP router network did not converge" + assert res is None, assertmsg + + +def test_bgp_ecmp(): + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + expect = { + "routerId": "10.0.255.1", + "routes": {}, + } + + for net in range(1, 5): + for subnet in range(0, 10): + netkey = "10.20{}.{}.0/24".format(net, subnet) + expect["routes"][netkey] = [] + for _ in range(0, 10): + peer = {"multipath": True, "valid": True} + expect["routes"][netkey].append(peer) + + test_func = functools.partial( + topotest.router_json_cmp, tgen.gears["r1"], "show ip bgp json", expect + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = 'expected multipath routes in "show ip bgp" output' + assert res is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ecmp_topo2/ebgp_ecmp_topo2.json b/tests/topotests/bgp_ecmp_topo2/ebgp_ecmp_topo2.json new file mode 100755 index 0000000..34f11c0 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo2/ebgp_ecmp_topo2.json @@ -0,0 +1,834 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link8": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link9": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link10": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link11": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link12": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link13": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link14": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link15": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link16": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link17": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link18": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link19": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link20": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link21": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link22": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link23": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link24": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link25": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link26": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link27": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link28": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link29": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link30": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link31": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link32": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "next_hop_self": true + }, + "r2-link2": { + "next_hop_self": true + }, + "r2-link3": { + "next_hop_self": true + }, + "r2-link4": { + "next_hop_self": true + }, + "r2-link5": { + "next_hop_self": true + }, + "r2-link6": { + "next_hop_self": true + }, + "r2-link7": { + "next_hop_self": true + }, + "r2-link8": { + "next_hop_self": true + }, + "r2-link9": { + "next_hop_self": true + }, + "r2-link10": { + "next_hop_self": true + }, + "r2-link11": { + "next_hop_self": true + }, + "r2-link12": { + "next_hop_self": true + }, + "r2-link13": { + "next_hop_self": true + }, + "r2-link14": { + "next_hop_self": true + }, + "r2-link15": { + "next_hop_self": true + }, + "r2-link16": { + "next_hop_self": true + }, + "r2-link17": { + "next_hop_self": true + }, + "r2-link18": { + "next_hop_self": true + }, + "r2-link19": { + "next_hop_self": true + }, + "r2-link20": { + "next_hop_self": true + }, + "r2-link21": { + "next_hop_self": true + }, + "r2-link22": { + "next_hop_self": true + }, + "r2-link23": { + "next_hop_self": true + }, + "r2-link24": { + "next_hop_self": true + }, + "r2-link25": { + "next_hop_self": true + }, + "r2-link26": { + "next_hop_self": true + }, + "r2-link27": { + "next_hop_self": true + }, + "r2-link28": { + "next_hop_self": true + }, + "r2-link29": { + "next_hop_self": true + }, + "r2-link30": { + "next_hop_self": true + }, + "r2-link31": { + "next_hop_self": true + }, + "r2-link32": { + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "next_hop_self": true + }, + "r2-link2": { + "next_hop_self": true + }, + "r2-link3": { + "next_hop_self": true + }, + "r2-link4": { + "next_hop_self": true + }, + "r2-link5": { + "next_hop_self": true + }, + "r2-link6": { + "next_hop_self": true + }, + "r2-link7": { + "next_hop_self": true + }, + "r2-link8": { + "next_hop_self": true + }, + "r2-link9": { + "next_hop_self": true + }, + "r2-link10": { + "next_hop_self": true + }, + "r2-link11": { + "next_hop_self": true + }, + "r2-link12": { + "next_hop_self": true + }, + "r2-link13": { + "next_hop_self": true + }, + "r2-link14": { + "next_hop_self": true + }, + "r2-link15": { + "next_hop_self": true + }, + "r2-link16": { + "next_hop_self": true + }, + "r2-link17": { + "next_hop_self": true + }, + "r2-link18": { + "next_hop_self": true + }, + "r2-link19": { + "next_hop_self": true + }, + "r2-link20": { + "next_hop_self": true + }, + "r2-link21": { + "next_hop_self": true + }, + "r2-link22": { + "next_hop_self": true + }, + "r2-link23": { + "next_hop_self": true + }, + "r2-link24": { + "next_hop_self": true + }, + "r2-link25": { + "next_hop_self": true + }, + "r2-link26": { + "next_hop_self": true + }, + "r2-link27": { + "next_hop_self": true + }, + "r2-link28": { + "next_hop_self": true + }, + "r2-link29": { + "next_hop_self": true + }, + "r2-link30": { + "next_hop_self": true + }, + "r2-link31": { + "next_hop_self": true + }, + "r2-link32": { + "next_hop_self": true + } + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link8": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link9": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link10": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link11": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link12": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link13": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link14": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link15": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link16": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link17": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link18": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link19": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link20": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link21": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link22": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link23": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link24": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link25": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link26": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link27": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link28": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link29": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link30": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link31": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link32": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": 32 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {}, + "r3-link9": {}, + "r3-link10": {}, + "r3-link11": {}, + "r3-link12": {}, + "r3-link13": {}, + "r3-link14": {}, + "r3-link15": {}, + "r3-link16": {}, + "r3-link17": {}, + "r3-link18": {}, + "r3-link19": {}, + "r3-link20": {}, + "r3-link21": {}, + "r3-link22": {}, + "r3-link23": {}, + "r3-link24": {}, + "r3-link25": {}, + "r3-link26": {}, + "r3-link27": {}, + "r3-link28": {}, + "r3-link29": {}, + "r3-link30": {}, + "r3-link31": {}, + "r3-link32": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": 32 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link5": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link6": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link7": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link8": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link9": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link10": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link11": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link12": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link13": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link14": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link15": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link16": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link17": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link18": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link19": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link20": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link21": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link22": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link23": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link24": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link25": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link26": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link27": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link28": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link29": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link30": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link31": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link32": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_ecmp_topo2/ibgp_ecmp_topo2.json b/tests/topotests/bgp_ecmp_topo2/ibgp_ecmp_topo2.json new file mode 100755 index 0000000..9eea907 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo2/ibgp_ecmp_topo2.json @@ -0,0 +1,844 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link8": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link9": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link10": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link11": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link12": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link13": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link14": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link15": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link16": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link17": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link18": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link19": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link20": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link21": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link22": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link23": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link24": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link25": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link26": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link27": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link28": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link29": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link30": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link31": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link32": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "next_hop_self": true + }, + "r2-link2": { + "next_hop_self": true + }, + "r2-link3": { + "next_hop_self": true + }, + "r2-link4": { + "next_hop_self": true + }, + "r2-link5": { + "next_hop_self": true + }, + "r2-link6": { + "next_hop_self": true + }, + "r2-link7": { + "next_hop_self": true + }, + "r2-link8": { + "next_hop_self": true + }, + "r2-link9": { + "next_hop_self": true + }, + "r2-link10": { + "next_hop_self": true + }, + "r2-link11": { + "next_hop_self": true + }, + "r2-link12": { + "next_hop_self": true + }, + "r2-link13": { + "next_hop_self": true + }, + "r2-link14": { + "next_hop_self": true + }, + "r2-link15": { + "next_hop_self": true + }, + "r2-link16": { + "next_hop_self": true + }, + "r2-link17": { + "next_hop_self": true + }, + "r2-link18": { + "next_hop_self": true + }, + "r2-link19": { + "next_hop_self": true + }, + "r2-link20": { + "next_hop_self": true + }, + "r2-link21": { + "next_hop_self": true + }, + "r2-link22": { + "next_hop_self": true + }, + "r2-link23": { + "next_hop_self": true + }, + "r2-link24": { + "next_hop_self": true + }, + "r2-link25": { + "next_hop_self": true + }, + "r2-link26": { + "next_hop_self": true + }, + "r2-link27": { + "next_hop_self": true + }, + "r2-link28": { + "next_hop_self": true + }, + "r2-link29": { + "next_hop_self": true + }, + "r2-link30": { + "next_hop_self": true + }, + "r2-link31": { + "next_hop_self": true + }, + "r2-link32": { + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "next_hop_self": true + }, + "r2-link2": { + "next_hop_self": true + }, + "r2-link3": { + "next_hop_self": true + }, + "r2-link4": { + "next_hop_self": true + }, + "r2-link5": { + "next_hop_self": true + }, + "r2-link6": { + "next_hop_self": true + }, + "r2-link7": { + "next_hop_self": true + }, + "r2-link8": { + "next_hop_self": true + }, + "r2-link9": { + "next_hop_self": true + }, + "r2-link10": { + "next_hop_self": true + }, + "r2-link11": { + "next_hop_self": true + }, + "r2-link12": { + "next_hop_self": true + }, + "r2-link13": { + "next_hop_self": true + }, + "r2-link14": { + "next_hop_self": true + }, + "r2-link15": { + "next_hop_self": true + }, + "r2-link16": { + "next_hop_self": true + }, + "r2-link17": { + "next_hop_self": true + }, + "r2-link18": { + "next_hop_self": true + }, + "r2-link19": { + "next_hop_self": true + }, + "r2-link20": { + "next_hop_self": true + }, + "r2-link21": { + "next_hop_self": true + }, + "r2-link22": { + "next_hop_self": true + }, + "r2-link23": { + "next_hop_self": true + }, + "r2-link24": { + "next_hop_self": true + }, + "r2-link25": { + "next_hop_self": true + }, + "r2-link26": { + "next_hop_self": true + }, + "r2-link27": { + "next_hop_self": true + }, + "r2-link28": { + "next_hop_self": true + }, + "r2-link29": { + "next_hop_self": true + }, + "r2-link30": { + "next_hop_self": true + }, + "r2-link31": { + "next_hop_self": true + }, + "r2-link32": { + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link8": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link9": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link10": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link11": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link12": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link13": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link14": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link15": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link16": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link17": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link18": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link19": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link20": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link21": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link22": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link23": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link24": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link25": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link26": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link27": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link28": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link29": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link30": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link31": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link32": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ibgp": 32 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {}, + "r3-link9": {}, + "r3-link10": {}, + "r3-link11": {}, + "r3-link12": {}, + "r3-link13": {}, + "r3-link14": {}, + "r3-link15": {}, + "r3-link16": {}, + "r3-link17": {}, + "r3-link18": {}, + "r3-link19": {}, + "r3-link20": {}, + "r3-link21": {}, + "r3-link22": {}, + "r3-link23": {}, + "r3-link24": {}, + "r3-link25": {}, + "r3-link26": {}, + "r3-link27": {}, + "r3-link28": {}, + "r3-link29": {}, + "r3-link30": {}, + "r3-link31": {}, + "r3-link32": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ibgp": 32 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link5": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link6": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link7": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link8": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link9": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link10": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link11": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link12": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link13": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link14": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link15": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link16": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link17": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link18": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link19": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link20": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link21": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link22": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link23": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link24": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link25": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link26": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link27": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link28": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link29": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link30": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link31": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link32": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_ecmp_topo2/test_ebgp_ecmp_topo2.py b/tests/topotests/bgp_ecmp_topo2/test_ebgp_ecmp_topo2.py new file mode 100644 index 0000000..8362776 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo2/test_ebgp_ecmp_topo2.py @@ -0,0 +1,746 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +""" +Following tests are covered to test ecmp functionality on EBGP. +1. Verify routes installed as per maximum-paths configuration (8/16/32) +2. Disable/Shut selected paths nexthops and verify other next are installed in + the RIB of DUT. Enable interfaces and verify RIB count. +3. Verify BGP table and RIB in DUT after clear BGP routes and neighbors. +4. Verify routes are cleared from BGP and RIB table of DUT when + redistribute static configuration is removed. +5. Shut BGP neighbors one by one and verify BGP and routing table updated + accordingly in DUT +6. Delete static routes and verify routers are cleared from BGP table and RIB + of DUT. +7. Verify routes are cleared from BGP and RIB table of DUT when advertise + network configuration is removed. +""" +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + interface_status, + reset_config_on_routers, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NEXT_HOPS = {"ipv4": [], "ipv6": []} +INTF_LIST_R3 = [] +INTF_LIST_R2 = [] +NETWORK = {"ipv4": "11.0.20.1/32", "ipv6": "1::/64"} +NEXT_HOP_IP = {"ipv4": "10.0.0.1", "ipv6": "fd00::1"} +BGP_CONVERGENCE = False + + +def setup_module(mod): + """ + Sets up the pytest environment. + + * `mod`: module name + """ + global NEXT_HOPS, INTF_LIST_R3, INTF_LIST_R2, TEST_STATIC + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.15") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/ebgp_ecmp_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # tgen.mininet_cli() + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + link_data = [ + val for links, val in topo["routers"]["r2"]["links"].items() if "r3" in links + ] + for adt in ADDR_TYPES: + NEXT_HOPS[adt] = [val[adt].split("/")[0] for val in link_data] + if adt == "ipv4": + NEXT_HOPS[adt] = sorted(NEXT_HOPS[adt], key=lambda x: int(x.split(".")[2])) + elif adt == "ipv6": + NEXT_HOPS[adt] = sorted( + NEXT_HOPS[adt], key=lambda x: int(x.split(":")[-3], 16) + ) + + INTF_LIST_R2 = [val["interface"].split("/")[0] for val in link_data] + INTF_LIST_R2 = sorted(INTF_LIST_R2, key=lambda x: int(x.split("eth")[1])) + + link_data = [ + val for links, val in topo["routers"]["r3"]["links"].items() if "r2" in links + ] + INTF_LIST_R3 = [val["interface"].split("/")[0] for val in link_data] + INTF_LIST_R3 = sorted(INTF_LIST_R3, key=lambda x: int(x.split("eth")[1])) + + # STATIC_ROUTE = True + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def static_or_nw(tgen, topo, tc_name, test_type, dut): + + if test_type == "redist_static": + input_dict_static = { + dut: { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK["ipv6"], "next_hop": NEXT_HOP_IP["ipv6"]}, + ] + } + } + logger.info("Configuring static route on router %s", dut) + result = create_static_routes(tgen, input_dict_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + } + } + } + } + + logger.info("Configuring redistribute static route on router %s", dut) + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + elif test_type == "advertise_nw": + input_dict_nw = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv4"]}] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv6"]}] + } + }, + } + } + } + } + + logger.info( + "Advertising networks %s %s from router %s", + NETWORK["ipv4"], + NETWORK["ipv6"], + dut, + ) + result = create_router_bgp(tgen, topo, input_dict_nw) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +@pytest.mark.parametrize("ecmp_num", ["8", "16", "32"]) +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_modify_ecmp_max_paths(request, ecmp_num, test_type): + """ + Verify routes installed as per maximum-paths + configuration (8/16/32). + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + input_dict = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": ecmp_num, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": ecmp_num, + } + } + }, + } + } + } + } + + logger.info("Configuring bgp maximum-paths %s on router r3", ecmp_num) + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + + # Only test the count of nexthops; the actual nexthop addresses + # can vary and are not deterministic. + # + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + count_only=True, + ) + + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("ecmp_num", ["8", "16", "32"]) +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_ecmp_after_clear_bgp(request, ecmp_num, test_type): + """Verify BGP table and RIB in DUT after clear BGP routes and neighbors""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Clear BGP + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, dut) + + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ecmp_remove_redistribute_static(request): + """Verify routes are cleared from BGP and RIB table of DUT when + redistribute static configuration is removed.""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, "redist_static", "r2") + for addr_type in ADDR_TYPES: + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + "ipv6": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + } + } + } + } + + logger.info("Remove redistribute static") + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3 are deleted", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + logger.info("Enable redistribute static") + input_dict_2 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_ecmp_shut_bgp_neighbor(request, test_type): + """Shut BGP neighbors one by one and verify BGP and routing table updated + accordingly in DUT""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + logger.info(INTF_LIST_R2) + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for intf_num in range(len(INTF_LIST_R2) + 1, 16): + intf_val = INTF_LIST_R2[intf_num : intf_num + 16] + + input_dict_1 = {"r2": {"interface_list": [intf_val], "status": "down"}} + logger.info("Shutting down neighbor interface {} on r2".format(intf_val)) + result = interface_status(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + if intf_num + 16 < 32: + check_hops = NEXT_HOPS[addr_type] + else: + check_hops = [] + + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, addr_type, dut, input_dict, next_hop=check_hops, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_1 = {"r2": {"interface_list": INTF_LIST_R2, "status": "up"}} + + logger.info("Enabling all neighbor interface {} on r2") + result = interface_status(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ecmp_remove_static_route(request): + """ + Delete static routes and verify routers are cleared from BGP table, + and RIB of DUT. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + + static_or_nw(tgen, topo, tc_name, "redist_static", "r2") + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "delete": True, + } + ] + } + } + + logger.info("Remove static routes") + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Verifying %s routes on r3 are removed", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_2, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_4 = { + "r2": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Enable static route") + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +def test_ecmp_remove_nw_advertise(request): + """ + Verify routes are cleared from BGP and RIB table of DUT, + when advertise network configuration is removed + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, "advertise_nw", "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_3 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": NETWORK["ipv4"], "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": NETWORK["ipv6"], "delete": True} + ] + } + }, + } + } + } + } + + logger.info("Withdraw advertised networks") + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + static_or_nw(tgen, topo, tc_name, "advertise_nw", "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ecmp_topo2/test_ibgp_ecmp_topo2.py b/tests/topotests/bgp_ecmp_topo2/test_ibgp_ecmp_topo2.py new file mode 100644 index 0000000..b347042 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo2/test_ibgp_ecmp_topo2.py @@ -0,0 +1,747 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +""" +Following tests are covered to test ecmp functionality on EBGP. +1. Verify routes installed as per maximum-paths configuration (8/16/32) +2. Disable/Shut selected paths nexthops and verify other next are installed in + the RIB of DUT. Enable interfaces and verify RIB count. +3. Verify BGP table and RIB in DUT after clear BGP routes and neighbors. +4. Verify routes are cleared from BGP and RIB table of DUT when + redistribute static configuration is removed. +5. Shut BGP neighbors one by one and verify BGP and routing table updated + accordingly in DUT +6. Delete static routes and verify routers are cleared from BGP table and RIB + of DUT. +7. Verify routes are cleared from BGP and RIB table of DUT when advertise + network configuration is removed. +""" +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + interface_status, + reset_config_on_routers, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NEXT_HOPS = {"ipv4": [], "ipv6": []} +INTF_LIST_R3 = [] +INTF_LIST_R2 = [] +NETWORK = {"ipv4": "11.0.20.1/32", "ipv6": "1::/64"} +NEXT_HOP_IP = {"ipv4": "10.0.0.1", "ipv6": "fd00::1"} +BGP_CONVERGENCE = False + + +def setup_module(mod): + """ + Sets up the pytest environment. + + * `mod`: module name + """ + global NEXT_HOPS, INTF_LIST_R3, INTF_LIST_R2, TEST_STATIC + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.15") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/ibgp_ecmp_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # tgen.mininet_cli() + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + link_data = [ + val for links, val in topo["routers"]["r2"]["links"].items() if "r3" in links + ] + for adt in ADDR_TYPES: + NEXT_HOPS[adt] = [val[adt].split("/")[0] for val in link_data] + if adt == "ipv4": + NEXT_HOPS[adt] = sorted(NEXT_HOPS[adt], key=lambda x: int(x.split(".")[2])) + elif adt == "ipv6": + NEXT_HOPS[adt] = sorted( + NEXT_HOPS[adt], key=lambda x: int(x.split(":")[-3], 16) + ) + + INTF_LIST_R2 = [val["interface"].split("/")[0] for val in link_data] + INTF_LIST_R2 = sorted(INTF_LIST_R2, key=lambda x: int(x.split("eth")[1])) + + link_data = [ + val for links, val in topo["routers"]["r3"]["links"].items() if "r2" in links + ] + INTF_LIST_R3 = [val["interface"].split("/")[0] for val in link_data] + INTF_LIST_R3 = sorted(INTF_LIST_R3, key=lambda x: int(x.split("eth")[1])) + + # STATIC_ROUTE = True + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def static_or_nw(tgen, topo, tc_name, test_type, dut): + + if test_type == "redist_static": + input_dict_static = { + dut: { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK["ipv6"], "next_hop": NEXT_HOP_IP["ipv6"]}, + ] + } + } + logger.info("Configuring static route on router %s", dut) + result = create_static_routes(tgen, input_dict_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + } + } + } + } + + logger.info("Configuring redistribute static route on router %s", dut) + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + elif test_type == "advertise_nw": + input_dict_nw = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv4"]}] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv6"]}] + } + }, + } + } + } + } + + logger.info( + "Advertising networks %s %s from router %s", + NETWORK["ipv4"], + NETWORK["ipv6"], + dut, + ) + result = create_router_bgp(tgen, topo, input_dict_nw) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +@pytest.mark.parametrize("ecmp_num", ["8", "16", "32"]) +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_modify_ecmp_max_paths(request, ecmp_num, test_type): + """ + Verify routes installed as per maximum-paths + configuration (8/16/32). + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + input_dict = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ibgp": ecmp_num, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ibgp": ecmp_num, + } + } + }, + } + } + } + } + + logger.info("Configuring bgp maximum-paths %s on router r3", ecmp_num) + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + + # Test only the count of nexthops, not the specific nexthop addresses - + # they're not deterministic + # + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + count_only=True, + ) + + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("ecmp_num", ["8", "16", "32"]) +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_ecmp_after_clear_bgp(request, ecmp_num, test_type): + """Verify BGP table and RIB in DUT after clear BGP routes and neighbors""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Clear BGP + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, dut) + + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type][: int(ecmp_num)], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ecmp_remove_redistribute_static(request): + """Verify routes are cleared from BGP and RIB table of DUT when + redistribute static configuration is removed.""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, "redist_static", "r2") + for addr_type in ADDR_TYPES: + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + "ipv6": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + } + } + } + } + + logger.info("Remove redistribute static") + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3 are deleted", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + logger.info("Enable redistribute static") + input_dict_2 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("test_type", ["redist_static", "advertise_nw"]) +def test_ecmp_shut_bgp_neighbor(request, test_type): + """Shut BGP neighbors one by one and verify BGP and routing table updated + accordingly in DUT""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + logger.info(INTF_LIST_R2) + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for intf_num in range(len(INTF_LIST_R2) + 1, 16): + intf_val = INTF_LIST_R2[intf_num : intf_num + 16] + + input_dict_1 = {"r2": {"interface_list": [intf_val], "status": "down"}} + logger.info("Shutting down neighbor interface {} on r2".format(intf_val)) + result = interface_status(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + if intf_num + 16 < 32: + check_hops = NEXT_HOPS[addr_type] + else: + check_hops = [] + + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, addr_type, dut, input_dict, next_hop=check_hops, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_1 = {"r2": {"interface_list": INTF_LIST_R2, "status": "up"}} + + logger.info("Enabling all neighbor interface {} on r2") + result = interface_status(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + static_or_nw(tgen, topo, tc_name, test_type, "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ecmp_remove_static_route(request): + """ + Delete static routes and verify routers are cleared from BGP table, + and RIB of DUT. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + + static_or_nw(tgen, topo, tc_name, "redist_static", "r2") + for addr_type in ADDR_TYPES: + input_dict_1 = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_1, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "delete": True, + } + ] + } + } + + logger.info("Remove static routes") + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Verifying %s routes on r3 are removed", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_2, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_4 = { + "r2": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Enable static route") + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ecmp_remove_nw_advertise(request): + """ + Verify routes are cleared from BGP and RIB table of DUT, + when advertise network configuration is removed + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, "advertise_nw", "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_3 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": NETWORK["ipv4"], "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": NETWORK["ipv6"], "delete": True} + ] + } + }, + } + } + } + } + + logger.info("Withdraw advertised networks") + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=[], + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected: Routes still present in {} RIB. Found: {}".format( + tc_name, dut, result + ) + + static_or_nw(tgen, topo, tc_name, "advertise_nw", "r2") + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + next_hop=NEXT_HOPS[addr_type], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json b/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json new file mode 100644 index 0000000..b01f902 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json @@ -0,0 +1,232 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + }, + "r2-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + }, + "r2-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ibgp": 2 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 60, + "holddowntimer": 180 + }, + "r3-link2": { + "keepalivetimer": 60, + "holddowntimer": 180 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ibgp": 2 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py b/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py new file mode 100644 index 0000000..366cf3d --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +""" +Following tests are covered to test ecmp functionality on iBGP. +1. Verify bgp fast-convergence functionality +""" +import os +import sys +import time +import pytest +import re +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + reset_config_on_routers, + shutdown_bringup_interface, + apply_raw_config, + start_topology, +) +from lib.topolog import logger +from lib.topojson import build_config_from_json +from lib.bgp import create_router_bgp, verify_bgp_convergence + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NEXT_HOPS = {"ipv4": [], "ipv6": []} +NETWORK = {"ipv4": "192.168.1.10/32", "ipv6": "fd00:0:0:1::10/128"} +NEXT_HOP_IP = {"ipv4": "10.0.0.1", "ipv6": "fd00::1"} +BGP_CONVERGENCE = False + + +def setup_module(mod): + """ + Sets up the pytest environment. + + * `mod`: module name + """ + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + # This function initiates the topology build with Topogen... + json_file = "{}/ibgp_ecmp_topo3.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + # STATIC_ROUTE = True + logger.info("Running setup_module() done") + + +def teardown_module(): + get_topogen().stop_topology() + + +def static_or_nw(tgen, topo, tc_name, test_type, dut): + + if test_type == "redist_static": + input_dict_static = { + dut: { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK["ipv6"], "next_hop": NEXT_HOP_IP["ipv6"]}, + ] + } + } + logger.info("Configuring static route on router %s", dut) + result = create_static_routes(tgen, input_dict_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + } + } + } + } + + logger.info("Configuring redistribute static route on router %s", dut) + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + elif test_type == "advertise_nw": + input_dict_nw = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv4"]}] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv6"]}] + } + }, + } + } + } + } + + logger.info( + "Advertising networks %s %s from router %s", + NETWORK["ipv4"], + NETWORK["ipv6"], + dut, + ) + result = create_router_bgp(tgen, topo, input_dict_nw) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +@pytest.mark.parametrize("test_type", ["redist_static"]) +def test_ecmp_fast_convergence(request, test_type, tgen, topo): + """This test is to verify bgp fast-convergence cli functionality""" + + tc_name = request.node.name + write_test_header(tc_name) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + intf1 = topo["routers"]["r2"]["links"]["r3-link1"]["interface"] + intf2 = topo["routers"]["r2"]["links"]["r3-link2"]["interface"] + + logger.info("Shutdown one of the link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, False) + + logger.info("Verify bgp neighbors are still up") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("Shutdown another link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf2, False) + + logger.info("Wait for 10 sec and make sure bgp neighbors are still up") + sleep(10) + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("No shut links b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, True) + shutdown_bringup_interface(tgen, "r2", intf2, True) + + logger.info("Ensure that the links are still up") + result = verify_bgp_convergence(tgen, topo) + + logger.info("Enable bgp fast-convergence cli") + raw_config = { + "r2": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r2"]["bgp"]["local_as"]), + "bgp fast-convergence", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + logger.info("Ensure BGP has processed the cli") + r2 = tgen.gears["r2"] + output = r2.vtysh_cmd("show run") + verify = re.search(r"fast-convergence", output) + assert verify is not None, "r2 does not have the fast convergence command yet" + + logger.info("Shutdown one link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, False) + + logger.info("Verify bgp neighbors goes down immediately") + result = verify_bgp_convergence(tgen, topo, dut="r2", expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: BGP should not be converged for {} \n " + "Found: {}".format(tc_name, "r2", result) + ) + + logger.info("Shutdown second link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf2, False) + + logger.info("Verify bgp neighbors goes down immediately") + result = verify_bgp_convergence(tgen, topo, dut="r2", expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: BGP should not be converged for {} \n " + "Found: {}".format(tc_name, "r2", result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_maximum_prefix/__init__.py b/tests/topotests/bgp_evpn_maximum_prefix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_maximum_prefix/c1/frr.conf b/tests/topotests/bgp_evpn_maximum_prefix/c1/frr.conf new file mode 100644 index 0000000..7476a37 --- /dev/null +++ b/tests/topotests/bgp_evpn_maximum_prefix/c1/frr.conf @@ -0,0 +1,4 @@ +! +int c1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_evpn_maximum_prefix/c2/frr.conf b/tests/topotests/bgp_evpn_maximum_prefix/c2/frr.conf new file mode 100644 index 0000000..a203daa --- /dev/null +++ b/tests/topotests/bgp_evpn_maximum_prefix/c2/frr.conf @@ -0,0 +1,4 @@ +! +int c2-eth0 + ip address 192.168.0.2/24 +! diff --git a/tests/topotests/bgp_evpn_maximum_prefix/r1/frr.conf b/tests/topotests/bgp_evpn_maximum_prefix/r1/frr.conf new file mode 100644 index 0000000..0534518 --- /dev/null +++ b/tests/topotests/bgp_evpn_maximum_prefix/r1/frr.conf @@ -0,0 +1,30 @@ +! +!debug bgp neighbor +!debug route-map detail +! +vni 10 +! +int lo + ip address 10.10.10.1/32 +! +int r1-eth1 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + network 10.10.10.10/32 + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.1.2 activate + advertise-all-vni + advertise ipv4 unicast + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_maximum_prefix/r2/frr.conf b/tests/topotests/bgp_evpn_maximum_prefix/r2/frr.conf new file mode 100644 index 0000000..353302b --- /dev/null +++ b/tests/topotests/bgp_evpn_maximum_prefix/r2/frr.conf @@ -0,0 +1,25 @@ +! +!debug bgp neighbor +! +int lo + ip address 10.10.10.2/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.1.1 activate + neighbor 192.168.1.1 maximum-prefix 2 + advertise-all-vni + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_maximum_prefix/test_bgp_evpn_maximum_prefix.py b/tests/topotests/bgp_evpn_maximum_prefix/test_bgp_evpn_maximum_prefix.py new file mode 100644 index 0000000..5469eff --- /dev/null +++ b/tests/topotests/bgp_evpn_maximum_prefix/test_bgp_evpn_maximum_prefix.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +import os +import re +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def setup_module(mod): + topodef = {"s1": ("c1", "r1"), "s2": ("r1", "r2"), "s3": ("r2", "c2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + tgen.net["r1"].cmd( + """ +ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.10.10.1 nolearning +ip link add name br10 type bridge +ip link set dev vxlan10 master br10 +ip link set dev r1-eth0 master br10 +ip link set up dev br10 +ip link set up dev vxlan10""" + ) + + tgen.net["r2"].cmd( + """ +ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.10.10.2 nolearning +ip link add name br10 type bridge +ip link set dev vxlan10 master br10 +ip link set dev r2-eth1 master br10 +ip link set up dev br10 +ip link set up dev vxlan10""" + ) + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_evpn_maximum_prefix(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp l2vpn evpn summary failed json")) + expected = { + "peers": { + "192.168.1.1": { + "lastNotificationReason": "Cease/Maximum Number of Prefixes Reached", + "lastResetDueTo": "BGP Notification send", + } + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Can't limit maximum-prefixes for EVPN routes" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_mh/evpn-mh-topo-tests.pdf b/tests/topotests/bgp_evpn_mh/evpn-mh-topo-tests.pdf new file mode 100644 index 0000000..8858e21 Binary files /dev/null and b/tests/topotests/bgp_evpn_mh/evpn-mh-topo-tests.pdf differ diff --git a/tests/topotests/bgp_evpn_mh/hostd11/evpn.conf b/tests/topotests/bgp_evpn_mh/hostd11/evpn.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd11/pim.conf b/tests/topotests/bgp_evpn_mh/hostd11/pim.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd11/zebra.conf b/tests/topotests/bgp_evpn_mh/hostd11/zebra.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd12/evpn.conf b/tests/topotests/bgp_evpn_mh/hostd12/evpn.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd12/pim.conf b/tests/topotests/bgp_evpn_mh/hostd12/pim.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd12/zebra.conf b/tests/topotests/bgp_evpn_mh/hostd12/zebra.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd21/evpn.conf b/tests/topotests/bgp_evpn_mh/hostd21/evpn.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd21/pim.conf b/tests/topotests/bgp_evpn_mh/hostd21/pim.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd21/zebra.conf b/tests/topotests/bgp_evpn_mh/hostd21/zebra.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd22/evpn.conf b/tests/topotests/bgp_evpn_mh/hostd22/evpn.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd22/pim.conf b/tests/topotests/bgp_evpn_mh/hostd22/pim.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/hostd22/zebra.conf b/tests/topotests/bgp_evpn_mh/hostd22/zebra.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_mh/leaf1/evpn.conf b/tests/topotests/bgp_evpn_mh/leaf1/evpn.conf new file mode 100644 index 0000000..d246517 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf1/evpn.conf @@ -0,0 +1,22 @@ +frr defaults datacenter +! +router bgp 65101 + timers bgp 3 10 + bgp router-id 192.168.100.13 + no bgp ebgp-requires-policy + neighbor 192.168.50.1 remote-as external + neighbor 192.168.51.1 remote-as external + neighbor 192.168.1.2 remote-as external + neighbor 192.168.2.2 remote-as external + neighbor 192.168.3.2 remote-as external + neighbor 192.168.4.2 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.50.1 activate + neighbor 192.168.51.1 activate + neighbor 192.168.1.2 activate + neighbor 192.168.2.2 activate + neighbor 192.168.3.2 activate + neighbor 192.168.4.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/leaf1/pim.conf b/tests/topotests/bgp_evpn_mh/leaf1/pim.conf new file mode 100644 index 0000000..a4de64d --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf1/pim.conf @@ -0,0 +1,26 @@ +#debug pim packet +#debug pim packet register +#debug pim event +#debug pim trace +ip pim ecmp +ip pim rp 192.168.100.13 +ip pim spt-switchover infinity-and-beyond +! +int leaf1-eth0 + ip pim +! +int leaf1-eth1 + ip pim +! +int leaf1-eth2 + ip pim +! +int leaf1-eth3 + ip pim +! +int leaf1-eth4 + ip pim +! +int leaf1-eth5 + ip pim +! diff --git a/tests/topotests/bgp_evpn_mh/leaf1/zebra.conf b/tests/topotests/bgp_evpn_mh/leaf1/zebra.conf new file mode 100644 index 0000000..f666f98 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf1/zebra.conf @@ -0,0 +1,30 @@ +# spine 1 connection +int leaf1-eth0 + description spine1 connection + ip addr 192.168.50.2/24 + +#spine 2 connection +int leaf1-eth1 + description spine2 connection + ip addr 192.168.51.2/24 + +#torm11 connection +int leaf1-eth2 + description torm11 connection + ip addr 192.168.1.1/24 +! +#torm12 connection +int leaf1-eth3 + description torm12 connection + ip addr 192.168.2.1/24 +! +#torm21 connection +int leaf1-eth4 + description torm21 connection + ip addr 192.168.3.1/24 +! +#torm22 connection +int leaf1-eth5 + descriptoin torm22 connection + ip addr 192.168.4.1/24 +! diff --git a/tests/topotests/bgp_evpn_mh/leaf2/evpn.conf b/tests/topotests/bgp_evpn_mh/leaf2/evpn.conf new file mode 100644 index 0000000..6855a43 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf2/evpn.conf @@ -0,0 +1,22 @@ +frr defaults datacenter +! +router bgp 65101 + timers bgp 3 10 + bgp router-id 192.168.100.14 + no bgp ebgp-requires-policy + neighbor 192.168.61.1 remote-as external + neighbor 192.168.51.1 remote-as external + neighbor 192.168.5.2 remote-as external + neighbor 192.168.6.2 remote-as external + neighbor 192.168.7.2 remote-as external + neighbor 192.168.8.2 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.61.1 activate + neighbor 192.168.51.1 activate + neighbor 192.168.5.2 activate + neighbor 192.168.6.2 activate + neighbor 192.168.7.2 activate + neighbor 192.168.8.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/leaf2/pim.conf b/tests/topotests/bgp_evpn_mh/leaf2/pim.conf new file mode 100644 index 0000000..3abd969 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf2/pim.conf @@ -0,0 +1,20 @@ +#debug pim packet register +#debug pim packet +#debug pim event +#debug pim trace +ip pim ecmp +ip pim rp 192.168.100.13 +ip pim spt-switchover infinity-and-beyond +! +int leaf2-eth0 + ip pim +! +int leaf2-eth1 + ip pim +! +int leaf2-eth2 + ip pim +! +int leaf2-eth3 + ip pim +! diff --git a/tests/topotests/bgp_evpn_mh/leaf2/zebra.conf b/tests/topotests/bgp_evpn_mh/leaf2/zebra.conf new file mode 100644 index 0000000..ff680cf --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/leaf2/zebra.conf @@ -0,0 +1,24 @@ +# spine1 connection +int leaf2-eth0 + ip addr 192.168.51.2/24 +! +# spine2 connection +int leaf2-eth1 + ip addr 192.168.61.2/24 +! +#torm11 connection +int leaf2-eth2 + ip addr 192.168.5.1/24 +! +#torm12 connection +int leaf2-eth3 + ip addr 192.168.6.1/24 +! +#torm21 connection +int leaf2-eth4 + ip addr 192.168.7.1/24 +! +#torm22 connection +int leaf2-eth5 + ip addr 192.168.8.1/24 +! diff --git a/tests/topotests/bgp_evpn_mh/spine1/evpn.conf b/tests/topotests/bgp_evpn_mh/spine1/evpn.conf new file mode 100644 index 0000000..7d6fef6 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine1/evpn.conf @@ -0,0 +1,14 @@ +frr defaults datacenter +! +router bgp 65001 + timers bgp 3 10 + bgp router-id 192.168.100.13 + no bgp ebgp-requires-policy + neighbor 192.168.50.2 remote-as external + neighbor 192.168.51.2 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.50.2 activate + neighbor 192.168.51.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/spine1/pim.conf b/tests/topotests/bgp_evpn_mh/spine1/pim.conf new file mode 100644 index 0000000..018d244 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine1/pim.conf @@ -0,0 +1,18 @@ +ip pim ecmp +ip pim rp 192.168.100.13 +#debug pim packets +#debug pim packet register +#debug pim event +#debug pim trace +#debug pim vxlan +ip pim spt-switchover infinity-and-beyond +! +int lo + ip pim +! +int spine1-eth0 + ip pim +! +int spine1-eth1 + ip pim +! diff --git a/tests/topotests/bgp_evpn_mh/spine1/zebra.conf b/tests/topotests/bgp_evpn_mh/spine1/zebra.conf new file mode 100644 index 0000000..607a5e8 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine1/zebra.conf @@ -0,0 +1,11 @@ +# leaf1 connection +int spine1-eth0 + ip addr 192.168.50.1/24 +! +# leaf2 connection +int spine1-eth1 + ip addr 192.168.51.1/24 +! +int lo + ip addr 192.168.100.13/32 + ip addr 192.168.100.100/32 diff --git a/tests/topotests/bgp_evpn_mh/spine2/evpn.conf b/tests/topotests/bgp_evpn_mh/spine2/evpn.conf new file mode 100644 index 0000000..c651ada --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine2/evpn.conf @@ -0,0 +1,14 @@ +frr defaults datacenter +! +router bgp 65001 + timers bgp 3 10 + bgp router-id 192.168.100.14 + no bgp ebgp-requires-policy + neighbor 192.168.60.2 remote-as external + neighbor 192.168.61.2 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.60.2 activate + neighbor 192.168.61.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/spine2/pim.conf b/tests/topotests/bgp_evpn_mh/spine2/pim.conf new file mode 100644 index 0000000..0ece2ff --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine2/pim.conf @@ -0,0 +1,13 @@ +ip pim ecmp +ip pim rp 192.168.100.13 +ip pim spt-switchover infinity-and-beyond +! +int lo + ip pim +! +int spine2-eth0 + ip pim +! +int spine2-eth1 + ip pim +! diff --git a/tests/topotests/bgp_evpn_mh/spine2/zebra.conf b/tests/topotests/bgp_evpn_mh/spine2/zebra.conf new file mode 100644 index 0000000..c8cec14 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/spine2/zebra.conf @@ -0,0 +1,13 @@ +# leaf1 connection +int spine2-eth0 + description leaf1 connection + ip addr 192.168.60.1/24 +! +# leaf2 connection +int spine2-eth1 + description leaf2 connection + ip addr 192.168.61.1/24 +! +int lo + ip addr 192.168.100.14/32 + ip addr 192.168.100.100/32 diff --git a/tests/topotests/bgp_evpn_mh/test_evpn_mh.py b/tests/topotests/bgp_evpn_mh/test_evpn_mh.py new file mode 100644 index 0000000..b033e9c --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/test_evpn_mh.py @@ -0,0 +1,826 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_evpn_mh.py +# +# Copyright (c) 2020 by +# Cumulus Networks, Inc. +# Anuradha Karuppiah +# + +""" +test_evpn_mh.py: Testing EVPN multihoming + +""" + +import os +import sys +import subprocess +from functools import partial + +import pytest +import json +import platform +from functools import partial + +pytestmark = [pytest.mark.bgpd, pytest.mark.pimd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest + +# Required to instantiate the topology builder class. +from lib.topogen import Topogen, TopoRouter, get_topogen + +##################################################### +## +## Network Topology Definition +## +## See topology picture at evpn-mh-topo-tests.pdf +##################################################### + + +def build_topo(tgen): + """ + EVPN Multihoming Topology - + 1. Two level CLOS + 2. Two spine switches - spine1, spine2 + 3. Two racks with Top-of-Rack switches per rack - tormx1, tormx2 + 4. Two dual attached hosts per-rack - hostdx1, hostdx2 + """ + + tgen.add_router("spine1") + tgen.add_router("spine2") + tgen.add_router("leaf1") + tgen.add_router("leaf2") + tgen.add_router("torm11") + tgen.add_router("torm12") + tgen.add_router("torm21") + tgen.add_router("torm22") + tgen.add_router("hostd11") + tgen.add_router("hostd12") + tgen.add_router("hostd21") + tgen.add_router("hostd22") + + # On main router + # First switch is for a dummy interface (for local network) + + ##################### spine1 ######################## + # spine1-eth0 is connected to leaf1-eth0 + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["spine1"]) + switch.add_link(tgen.gears["leaf1"]) + + # spine1-eth1 is connected to leaf2-eth0 + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["spine1"]) + switch.add_link(tgen.gears["leaf2"]) + + # spine2-eth0 is connected to leaf1-eth1 + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["spine2"]) + switch.add_link(tgen.gears["leaf1"]) + + # spine2-eth1 is connected to leaf2-eth1 + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["spine2"]) + switch.add_link(tgen.gears["leaf2"]) + + ################## leaf1 ########################## + # leaf1-eth2 is connected to torm11-eth0 + switch = tgen.add_switch("sw5") + switch.add_link(tgen.gears["leaf1"]) + switch.add_link(tgen.gears["torm11"]) + + # leaf1-eth3 is connected to torm12-eth0 + switch = tgen.add_switch("sw6") + switch.add_link(tgen.gears["leaf1"]) + switch.add_link(tgen.gears["torm12"]) + + # leaf1-eth4 is connected to torm21-eth0 + switch = tgen.add_switch("sw7") + switch.add_link(tgen.gears["leaf1"]) + switch.add_link(tgen.gears["torm21"]) + + # leaf1-eth5 is connected to torm22-eth0 + switch = tgen.add_switch("sw8") + switch.add_link(tgen.gears["leaf1"]) + switch.add_link(tgen.gears["torm22"]) + + ##################### leaf2 ######################## + # leaf2-eth2 is connected to torm11-eth1 + switch = tgen.add_switch("sw9") + switch.add_link(tgen.gears["leaf2"]) + switch.add_link(tgen.gears["torm11"]) + + # leaf2-eth3 is connected to torm12-eth1 + switch = tgen.add_switch("sw10") + switch.add_link(tgen.gears["leaf2"]) + switch.add_link(tgen.gears["torm12"]) + + # leaf2-eth4 is connected to torm21-eth1 + switch = tgen.add_switch("sw11") + switch.add_link(tgen.gears["leaf2"]) + switch.add_link(tgen.gears["torm21"]) + + # leaf2-eth5 is connected to torm22-eth1 + switch = tgen.add_switch("sw12") + switch.add_link(tgen.gears["leaf2"]) + switch.add_link(tgen.gears["torm22"]) + + ##################### torm11 ######################## + # torm11-eth2 is connected to hostd11-eth0 + switch = tgen.add_switch("sw13") + switch.add_link(tgen.gears["torm11"]) + switch.add_link(tgen.gears["hostd11"]) + + # torm11-eth3 is connected to hostd12-eth0 + switch = tgen.add_switch("sw14") + switch.add_link(tgen.gears["torm11"]) + switch.add_link(tgen.gears["hostd12"]) + + ##################### torm12 ######################## + # torm12-eth2 is connected to hostd11-eth1 + switch = tgen.add_switch("sw15") + switch.add_link(tgen.gears["torm12"]) + switch.add_link(tgen.gears["hostd11"]) + + # torm12-eth3 is connected to hostd12-eth1 + switch = tgen.add_switch("sw16") + switch.add_link(tgen.gears["torm12"]) + switch.add_link(tgen.gears["hostd12"]) + + ##################### torm21 ######################## + # torm21-eth2 is connected to hostd21-eth0 + switch = tgen.add_switch("sw17") + switch.add_link(tgen.gears["torm21"]) + switch.add_link(tgen.gears["hostd21"]) + + # torm21-eth3 is connected to hostd22-eth0 + switch = tgen.add_switch("sw18") + switch.add_link(tgen.gears["torm21"]) + switch.add_link(tgen.gears["hostd22"]) + + ##################### torm22 ######################## + # torm22-eth2 is connected to hostd21-eth1 + switch = tgen.add_switch("sw19") + switch.add_link(tgen.gears["torm22"]) + switch.add_link(tgen.gears["hostd21"]) + + # torm22-eth3 is connected to hostd22-eth1 + switch = tgen.add_switch("sw20") + switch.add_link(tgen.gears["torm22"]) + switch.add_link(tgen.gears["hostd22"]) + + +##################################################### +## +## Tests starting +## +##################################################### + +tor_ips = { + "torm11": "192.168.100.15", + "torm12": "192.168.100.16", + "torm21": "192.168.100.17", + "torm22": "192.168.100.18", +} + +svi_ips = { + "torm11": "45.0.0.2", + "torm12": "45.0.0.3", + "torm21": "45.0.0.4", + "torm22": "45.0.0.5", +} + +tor_ips_rack_1 = {"torm11": "192.168.100.15", "torm12": "192.168.100.16"} + +tor_ips_rack_2 = {"torm21": "192.168.100.17", "torm22": "192.168.100.18"} + +host_es_map = { + "hostd11": "03:44:38:39:ff:ff:01:00:00:01", + "hostd12": "03:44:38:39:ff:ff:01:00:00:02", + "hostd21": "03:44:38:39:ff:ff:02:00:00:01", + "hostd22": "03:44:38:39:ff:ff:02:00:00:02", +} + + +def config_bond(node, bond_name, bond_members, bond_ad_sys_mac, br): + """ + Used to setup bonds on the TORs and hosts for MH + """ + node.run("ip link add dev %s type bond mode 802.3ad" % bond_name) + node.run("ip link set dev %s type bond lacp_rate 1" % bond_name) + node.run("ip link set dev %s type bond miimon 100" % bond_name) + node.run("ip link set dev %s type bond xmit_hash_policy layer3+4" % bond_name) + node.run("ip link set dev %s type bond min_links 1" % bond_name) + node.run( + "ip link set dev %s type bond ad_actor_system %s" % (bond_name, bond_ad_sys_mac) + ) + + for bond_member in bond_members: + node.run("ip link set dev %s down" % bond_member) + node.run("ip link set dev %s master %s" % (bond_member, bond_name)) + node.run("ip link set dev %s up" % bond_member) + + node.run("ip link set dev %s up" % bond_name) + + # if bridge is specified add the bond as a bridge member + if br: + node.run(" ip link set dev %s master bridge" % bond_name) + node.run("/sbin/bridge link set dev %s priority 8" % bond_name) + node.run("/sbin/bridge vlan del vid 1 dev %s" % bond_name) + node.run("/sbin/bridge vlan del vid 1 untagged pvid dev %s" % bond_name) + node.run("/sbin/bridge vlan add vid 1000 dev %s" % bond_name) + node.run("/sbin/bridge vlan add vid 1000 untagged pvid dev %s" % bond_name) + + +def config_mcast_tunnel_termination_device(node): + """ + The kernel requires a device to terminate VxLAN multicast tunnels + when EVPN-PIM is used for flooded traffic + """ + node.run("ip link add dev ipmr-lo type dummy") + node.run("ip link set dev ipmr-lo mtu 16000") + node.run("ip link set dev ipmr-lo mode dormant") + node.run("ip link set dev ipmr-lo up") + + +def config_bridge(node): + """ + Create a VLAN aware bridge + """ + node.run("ip link add dev bridge type bridge stp_state 0") + node.run("ip link set dev bridge type bridge vlan_filtering 1") + node.run("ip link set dev bridge mtu 9216") + node.run("ip link set dev bridge type bridge ageing_time 1800") + node.run("ip link set dev bridge type bridge mcast_snooping 0") + node.run("ip link set dev bridge type bridge vlan_stats_enabled 1") + node.run("ip link set dev bridge up") + node.run("/sbin/bridge vlan add vid 1000 dev bridge self") + + +def config_vxlan(node, node_ip): + """ + Create a VxLAN device for VNI 1000 and add it to the bridge. + VLAN-1000 is mapped to VNI-1000. + """ + node.run("ip link add dev vx-1000 type vxlan id 1000 dstport 4789") + node.run("ip link set dev vx-1000 type vxlan nolearning") + node.run("ip link set dev vx-1000 type vxlan local %s" % node_ip) + node.run("ip link set dev vx-1000 type vxlan ttl 64") + node.run("ip link set dev vx-1000 mtu 9152") + node.run("ip link set dev vx-1000 type vxlan dev ipmr-lo group 239.1.1.100") + node.run("ip link set dev vx-1000 up") + + # bridge attrs + node.run("ip link set dev vx-1000 master bridge") + node.run("/sbin/bridge link set dev vx-1000 neigh_suppress on") + node.run("/sbin/bridge link set dev vx-1000 learning off") + node.run("/sbin/bridge link set dev vx-1000 priority 8") + node.run("/sbin/bridge vlan del vid 1 dev vx-1000") + node.run("/sbin/bridge vlan del vid 1 untagged pvid dev vx-1000") + node.run("/sbin/bridge vlan add vid 1000 dev vx-1000") + node.run("/sbin/bridge vlan add vid 1000 untagged pvid dev vx-1000") + + +def config_svi(node, svi_pip): + """ + Create an SVI for VLAN 1000 + """ + node.run("ip link add link bridge name vlan1000 type vlan id 1000 protocol 802.1q") + node.run("ip addr add %s/24 dev vlan1000" % svi_pip) + node.run("ip link set dev vlan1000 up") + node.run("/sbin/sysctl net.ipv4.conf.vlan1000.arp_accept=1") + node.run("ip link add link vlan1000 name vlan1000-v0 type macvlan mode private") + node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.accept_dad=0") + node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.dad_transmits") + node.run("/sbin/sysctl net.ipv6.conf.vlan1000-v0.dad_transmits=0") + node.run("ip link set dev vlan1000-v0 address 00:00:5e:00:01:01") + node.run("ip link set dev vlan1000-v0 up") + # metric 1024 is not working + node.run("ip addr add 45.0.0.1/24 dev vlan1000-v0") + + +def config_tor(tor_name, tor, tor_ip, svi_pip): + """ + Create the bond/vxlan-bridge on the TOR which acts as VTEP and EPN-PE + """ + # create a device for terminating VxLAN multicast tunnels + config_mcast_tunnel_termination_device(tor) + + # create a vlan aware bridge + config_bridge(tor) + + # create vxlan device and add it to bridge + config_vxlan(tor, tor_ip) + + # create hostbonds and add them to the bridge + if "torm1" in tor_name: + sys_mac = "44:38:39:ff:ff:01" + else: + sys_mac = "44:38:39:ff:ff:02" + bond_member = tor_name + "-eth2" + config_bond(tor, "hostbond1", [bond_member], sys_mac, "bridge") + + bond_member = tor_name + "-eth3" + config_bond(tor, "hostbond2", [bond_member], sys_mac, "bridge") + + # create SVI + config_svi(tor, svi_pip) + + +def config_tors(tgen, tors): + for tor_name in tors: + tor = tgen.gears[tor_name] + config_tor(tor_name, tor, tor_ips.get(tor_name), svi_ips.get(tor_name)) + + +def compute_host_ip_mac(host_name): + host_id = host_name.split("hostd")[1] + host_ip = "45.0.0." + host_id + "/24" + host_mac = "00:00:00:00:00:" + host_id + + return host_ip, host_mac + + +def config_host(host_name, host): + """ + Create the dual-attached bond on host nodes for MH + """ + bond_members = [] + bond_members.append(host_name + "-eth0") + bond_members.append(host_name + "-eth1") + bond_name = "torbond" + config_bond(host, bond_name, bond_members, "00:00:00:00:00:00", None) + + host_ip, host_mac = compute_host_ip_mac(host_name) + host.run("ip addr add %s dev %s" % (host_ip, bond_name)) + host.run("ip link set dev %s address %s" % (bond_name, host_mac)) + + +def config_hosts(tgen, hosts): + for host_name in hosts: + host = tgen.gears[host_name] + config_host(host_name, host) + + +def setup_module(module): + "Setup topology" + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + krel = platform.release() + if topotest.version_cmp(krel, "4.19") < 0: + tgen.errors = "kernel 4.19 needed for multihoming tests" + pytest.skip(tgen.errors) + + tors = [] + tors.append("torm11") + tors.append("torm12") + tors.append("torm21") + tors.append("torm22") + config_tors(tgen, tors) + + # tgen.mininet_cli() + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PIM, os.path.join(CWD, "{}/pim.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/evpn.conf".format(rname)) + ) + tgen.start_router() + + hosts = [] + hosts.append("hostd11") + hosts.append("hostd12") + hosts.append("hostd21") + hosts.append("hostd22") + config_hosts(tgen, hosts) + # tgen.mininet_cli() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def check_local_es(esi, vtep_ips, dut_name, down_vteps): + """ + Check if ES peers are setup correctly on local ESs + """ + peer_ips = [] + if "torm1" in dut_name: + tor_ips_rack = tor_ips_rack_1 + else: + tor_ips_rack = tor_ips_rack_2 + + for tor_name, tor_ip in tor_ips_rack.items(): + if dut_name not in tor_name: + peer_ips.append(tor_ip) + + # remove down VTEPs from the peer check list + peer_set = set(peer_ips) + down_vtep_set = set(down_vteps) + peer_set = peer_set - down_vtep_set + + vtep_set = set(vtep_ips) + diff = peer_set.symmetric_difference(vtep_set) + + return (esi, diff) if diff else None + + +def check_remote_es(esi, vtep_ips, dut_name, down_vteps): + """ + Verify list of PEs associated with a remote ES + """ + remote_ips = [] + + if "torm1" in dut_name: + tor_ips_rack = tor_ips_rack_2 + else: + tor_ips_rack = tor_ips_rack_1 + + for tor_name, tor_ip in tor_ips_rack.items(): + remote_ips.append(tor_ip) + + # remove down VTEPs from the remote check list + remote_set = set(remote_ips) + down_vtep_set = set(down_vteps) + remote_set = remote_set - down_vtep_set + + vtep_set = set(vtep_ips) + diff = remote_set.symmetric_difference(vtep_set) + + return (esi, diff) if diff else None + + +def check_es(dut): + """ + Verify list of PEs associated all ESs, local and remote + """ + bgp_es = dut.vtysh_cmd("show bgp l2vp evpn es json") + bgp_es_json = json.loads(bgp_es) + + result = None + + expected_es_set = set([v for k, v in host_es_map.items()]) + curr_es_set = [] + + # check is ES content is correct + for es in bgp_es_json: + esi = es["esi"] + curr_es_set.append(esi) + types = es["type"] + vtep_ips = [] + for vtep in es.get("vteps", []): + vtep_ips.append(vtep["vtep_ip"]) + + if "local" in types: + result = check_local_es(esi, vtep_ips, dut.name, []) + else: + result = check_remote_es(esi, vtep_ips, dut.name, []) + + if result: + return result + + # check if all ESs are present + curr_es_set = set(curr_es_set) + result = curr_es_set.symmetric_difference(expected_es_set) + + return result if result else None + + +def check_one_es(dut, esi, down_vteps): + """ + Verify list of PEs associated all ESs, local and remote + """ + bgp_es = dut.vtysh_cmd("show bgp l2vp evpn es %s json" % esi) + es = json.loads(bgp_es) + + if not es: + return "esi %s not found" % esi + + esi = es["esi"] + types = es["type"] + vtep_ips = [] + for vtep in es.get("vteps", []): + vtep_ips.append(vtep["vtep_ip"]) + + if "local" in types: + result = check_local_es(esi, vtep_ips, dut.name, down_vteps) + else: + result = check_remote_es(esi, vtep_ips, dut.name, down_vteps) + + return result + + +def test_evpn_es(): + """ + Two ES are setup on each rack. This test checks if - + 1. ES peer has been added to the local ES (via Type-1/EAD route) + 2. The remote ESs are setup with the right list of PEs (via Type-1) + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + dut_name = "torm11" + dut = tgen.gears[dut_name] + test_fn = partial(check_es, dut) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + + assertmsg = '"{}" ES content incorrect'.format(dut_name) + assert result is None, assertmsg + # tgen.mininet_cli() + + +def test_evpn_ead_update(): + """ + Flap a host link one the remote rack and check if the EAD updates + are sent/processed for the corresponding ESI + """ + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # dut on rack1 and host link flap on rack2 + dut_name = "torm11" + dut = tgen.gears[dut_name] + + remote_tor_name = "torm21" + remote_tor = tgen.gears[remote_tor_name] + + host_name = "hostd21" + host = tgen.gears[host_name] + esi = host_es_map.get(host_name) + + # check if the VTEP list is right to start with + down_vteps = [] + test_fn = partial(check_one_es, dut, esi, down_vteps) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" ES content incorrect'.format(dut_name) + assert result is None, assertmsg + + # down a remote host link and check if the EAD withdraw is rxed + # Note: LACP is not working as expected so I am temporarily shutting + # down the link on the remote TOR instead of the remote host + remote_tor.run("ip link set dev %s-%s down" % (remote_tor_name, "eth2")) + down_vteps.append(tor_ips.get(remote_tor_name)) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" ES incorrect after remote link down'.format(dut_name) + assert result is None, assertmsg + + # bring up remote host link and check if the EAD update is rxed + down_vteps.remove(tor_ips.get(remote_tor_name)) + remote_tor.run("ip link set dev %s-%s up" % (remote_tor_name, "eth2")) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" ES incorrect after remote link flap'.format(dut_name) + assert result is None, assertmsg + + # tgen.mininet_cli() + + +def ping_anycast_gw(tgen): + # ping the anycast gw from the local and remote hosts to populate + # the mac address on the PEs + python3_path = tgen.net.get_exec_path(["python3", "python"]) + script_path = os.path.abspath(os.path.join(CWD, "../lib/scapy_sendpkt.py")) + intf = "torbond" + ipaddr = "45.0.0.1" + ping_cmd = [ + python3_path, + script_path, + "--imports=Ether,ARP", + "--interface=" + intf, + 'Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="{}")'.format(ipaddr), + ] + for name in ("hostd11", "hostd21", "hostd12", "hostd22"): + host = tgen.net.hosts[name] + _, stdout, _ = host.cmd_status(ping_cmd, warn=False, stderr=subprocess.STDOUT) + stdout = stdout.strip() + if stdout: + host.logger.debug( + "%s: arping on %s for %s returned: %s", name, intf, ipaddr, stdout + ) + + +def check_mac(dut, vni, mac, m_type, esi, intf, ping_gw=False, tgen=None): + """ + checks if mac is present and if desination matches the one provided + """ + + if ping_gw: + ping_anycast_gw(tgen) + + out = dut.vtysh_cmd("show evpn mac vni %d mac %s json" % (vni, mac)) + + tmp_esi = None + mac_js = json.loads(out) + for mac, info in mac_js.items(): + tmp_esi = info.get("esi", "") + tmp_m_type = info.get("type", "") + tmp_intf = info.get("intf", "") if tmp_m_type == "local" else "" + if tmp_esi == esi and tmp_m_type == m_type and intf == intf: + return None + + return "invalid vni %d mac %s expected esi %s, %s m_type %s and intf %s out %s" % ( + vni, + mac, + tmp_esi, + esi, + m_type, + intf, + mac_js, + ) + + +def test_evpn_mac(): + """ + 1. Add a MAC on hostd11 and check if the MAC is synced between + torm11 and torm12. And installed as a local MAC. + 2. Add a MAC on hostd21 and check if the MAC is installed as a + remote MAC on torm11 and torm12 + """ + + tgen = get_topogen() + + local_host = tgen.gears["hostd11"] + remote_host = tgen.gears["hostd21"] + tors = [] + tors.append(tgen.gears["torm11"]) + tors.append(tgen.gears["torm12"]) + + vni = 1000 + + # check if the rack-1 host MAC is present on all rack-1 PEs + # and points to local access port + m_type = "local" + _, mac = compute_host_ip_mac(local_host.name) + esi = host_es_map.get(local_host.name) + intf = "hostbond1" + + for tor in tors: + test_fn = partial(check_mac, tor, vni, mac, m_type, esi, intf, True, tgen) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" local MAC content incorrect'.format(tor.name) + assert result is None, assertmsg + + # check if the rack-2 host MAC is present on all rack-1 PEs + # and points to the remote ES destination + m_type = "remote" + _, mac = compute_host_ip_mac(remote_host.name) + esi = host_es_map.get(remote_host.name) + intf = "" + + for tor in tors: + test_fn = partial(check_mac, tor, vni, mac, m_type, esi, intf) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" remote MAC content incorrect'.format(tor.name) + assert result is None, assertmsg + + +def check_df_role(dut, esi, role): + """ + Return error string if the df role on the dut is different + """ + es_json = dut.vtysh_cmd("show evpn es %s json" % esi) + es = json.loads(es_json) + + if not es: + return "esi %s not found" % esi + + flags = es.get("flags", []) + curr_role = "nonDF" if "nonDF" in flags else "DF" + + if curr_role != role: + return "%s is %s for %s" % (dut.name, curr_role, esi) + + return None + + +def test_evpn_df(): + """ + 1. Check the DF role on all the PEs on rack-1. + 2. Increase the DF preference on the non-DF and check if it becomes + the DF winner. + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # We will run the tests on just one ES + esi = host_es_map.get("hostd11") + intf = "hostbond1" + + tors = [] + tors.append(tgen.gears["torm11"]) + tors.append(tgen.gears["torm12"]) + df_node = "torm11" + + # check roles on rack-1 + for tor in tors: + role = "DF" if tor.name == df_node else "nonDF" + test_fn = partial(check_df_role, tor, esi, role) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" DF role incorrect'.format(tor.name) + assert result is None, assertmsg + + # change df preference on the nonDF to make it the df + torm12 = tgen.gears["torm12"] + torm12.vtysh_cmd("conf\ninterface %s\nevpn mh es-df-pref %d" % (intf, 60000)) + df_node = "torm12" + + # re-check roles on rack-1; we should have a new winner + for tor in tors: + role = "DF" if tor.name == df_node else "nonDF" + test_fn = partial(check_df_role, tor, esi, role) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" DF role incorrect'.format(tor.name) + assert result is None, assertmsg + + # tgen.mininet_cli() + + +def check_protodown_rc(dut, protodown_rc): + """ + check if specified protodown reason code is set + """ + + out = dut.vtysh_cmd("show evpn json") + + evpn_js = json.loads(out) + tmp_rc = evpn_js.get("protodownReasons", []) + + if protodown_rc: + if protodown_rc not in tmp_rc: + return "protodown %s missing in %s" % (protodown_rc, tmp_rc) + else: + if tmp_rc: + return "unexpected protodown rc %s" % (tmp_rc) + + return None + + +def test_evpn_uplink_tracking(): + """ + 1. Wait for access ports to come out of startup-delay + 2. disable uplinks and check if access ports have been protodowned + 3. enable uplinks and check if access ports have been moved out + of protodown + """ + + tgen = get_topogen() + + dut_name = "torm11" + dut = tgen.gears[dut_name] + + # wait for protodown rc to clear after startup + test_fn = partial(check_protodown_rc, dut, None) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" protodown rc incorrect'.format(dut_name) + assert result is None, assertmsg + + # disable the uplinks + dut.run("ip link set %s-eth0 down" % dut_name) + dut.run("ip link set %s-eth1 down" % dut_name) + + # check if the access ports have been protodowned + test_fn = partial(check_protodown_rc, dut, "uplinkDown") + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" protodown rc incorrect'.format(dut_name) + assert result is None, assertmsg + + # enable the uplinks + dut.run("ip link set %s-eth0 up" % dut_name) + dut.run("ip link set %s-eth1 up" % dut_name) + + # check if the access ports have been moved out of protodown + test_fn = partial(check_protodown_rc, dut, None) + _, result = topotest.run_and_expect(test_fn, None, count=20, wait=3) + assertmsg = '"{}" protodown rc incorrect'.format(dut_name) + assert result is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_mh/torm11/evpn.conf b/tests/topotests/bgp_evpn_mh/torm11/evpn.conf new file mode 100644 index 0000000..62b7ec5 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm11/evpn.conf @@ -0,0 +1,22 @@ +! +frr defaults datacenter +! +! debug bgp evpn mh es +! debug bgp evpn mh route +! debug bgp zebra +! +! +router bgp 65002 + timers bgp 3 10 + bgp router-id 192.168.100.15 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.5.1 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.1.1 activate + neighbor 192.168.5.1 activate + advertise-all-vni + advertise-svi-ip + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/torm11/pim.conf b/tests/topotests/bgp_evpn_mh/torm11/pim.conf new file mode 100644 index 0000000..a5d45da --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm11/pim.conf @@ -0,0 +1,19 @@ +! +#debug pim packet +#debug pim packet register +#debug pim trace +#debug pim event +#debug pim vxlan +ip pim ecmp +ip pim rp 192.168.100.13 239.1.1.0/24 +ip pim spt-switchover infinity-and-beyond +! +interface lo + ip igmp + ip pim +! +interface torm11-eth0 + ip pim +! +interface torm11-eth1 + ip pim diff --git a/tests/topotests/bgp_evpn_mh/torm11/zebra.conf b/tests/topotests/bgp_evpn_mh/torm11/zebra.conf new file mode 100644 index 0000000..a88370d --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm11/zebra.conf @@ -0,0 +1,27 @@ +! debug zebra evpn mh es +! debug zebra evpn mh mac +! debug zebra evpn mh neigh +! debug zebra evpn mh nh +! debug zebra vxlan +! +evpn mh startup-delay 1 +! +int torm11-eth0 + ip addr 192.168.1.2/24 + evpn mh uplink +! +int torm11-eth1 + ip addr 192.168.5.2/24 + evpn mh uplink +! +int lo + ip addr 192.168.100.15/32 +! +interface hostbond1 + evpn mh es-id 1 + evpn mh es-sys-mac 44:38:39:ff:ff:01 +! +interface hostbond2 + evpn mh es-id 2 + evpn mh es-sys-mac 44:38:39:ff:ff:01 +! diff --git a/tests/topotests/bgp_evpn_mh/torm12/evpn.conf b/tests/topotests/bgp_evpn_mh/torm12/evpn.conf new file mode 100644 index 0000000..3ceb974 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm12/evpn.conf @@ -0,0 +1,22 @@ +! +frr defaults datacenter +! +! debug bgp evpn mh es +! debug bgp evpn mh route +! debug bgp zebra +! +! +router bgp 65003 + timers bgp 3 10 + bgp router-id 192.168.100.16 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.6.1 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.2.1 activate + neighbor 192.168.6.1 activate + advertise-all-vni + advertise-svi-ip + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/torm12/pim.conf b/tests/topotests/bgp_evpn_mh/torm12/pim.conf new file mode 100644 index 0000000..7e09ba7 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm12/pim.conf @@ -0,0 +1,19 @@ +#debug pim packet +#debug pim packet register +#debug pim trace +#debug pim event +#debug pim vxlan +! +ip pim ecmp +ip pim rp 192.168.100.13 239.1.1.0/24 +ip pim spt-switchover infinity-and-beyond +! +interface lo + ip igmp + ip pim +! +interface torm12-eth0 + ip pim +! +interface torm12-eth1 + ip pim diff --git a/tests/topotests/bgp_evpn_mh/torm12/zebra.conf b/tests/topotests/bgp_evpn_mh/torm12/zebra.conf new file mode 100644 index 0000000..9532762 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm12/zebra.conf @@ -0,0 +1,28 @@ +! debug zebra evpn mh es +! debug zebra evpn mh mac +! debug zebra evpn mh neigh +! debug zebra evpn mh nh +! debug zebra vxlan +! +evpn mh startup-delay 1 +! +int torm12-eth0 + ip addr 192.168.2.2/24 + evpn mh uplink +! +int torm12-eth1 + ip addr 192.168.6.2/24 + evpn mh uplink +! +! +int lo + ip addr 192.168.100.16/32 +! +interface hostbond1 + evpn mh es-id 1 + evpn mh es-sys-mac 44:38:39:ff:ff:01 +! +interface hostbond2 + evpn mh es-id 2 + evpn mh es-sys-mac 44:38:39:ff:ff:01 +! diff --git a/tests/topotests/bgp_evpn_mh/torm21/evpn.conf b/tests/topotests/bgp_evpn_mh/torm21/evpn.conf new file mode 100644 index 0000000..ecaf85d --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm21/evpn.conf @@ -0,0 +1,22 @@ +! +frr defaults datacenter +! +! debug bgp evpn mh es +! debug bgp evpn mh route +! debug bgp zebra +! +! +router bgp 65004 + timers bgp 3 10 + bgp router-id 192.168.100.17 + no bgp ebgp-requires-policy + neighbor 192.168.3.1 remote-as external + neighbor 192.168.7.1 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.3.1 activate + neighbor 192.168.7.1 activate + advertise-all-vni + advertise-svi-ip + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/torm21/pim.conf b/tests/topotests/bgp_evpn_mh/torm21/pim.conf new file mode 100644 index 0000000..6996d74 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm21/pim.conf @@ -0,0 +1,19 @@ +#debug pim packet +#debug pim packet register +#debug pim trace +#debug pim event +#debug pim vxlan +! +ip pim ecmp +ip pim rp 192.168.100.13 239.1.1.0/24 +ip pim spt-switchover infinity-and-beyond +! +interface lo + ip igmp + ip pim +! +interface torm21-eth0 + ip pim +! +interface torm21-eth1 + ip pim diff --git a/tests/topotests/bgp_evpn_mh/torm21/zebra.conf b/tests/topotests/bgp_evpn_mh/torm21/zebra.conf new file mode 100644 index 0000000..6c75df7 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm21/zebra.conf @@ -0,0 +1,29 @@ +! debug zebra evpn mh es +! debug zebra evpn mh mac +! debug zebra evpn mh neigh +! debug zebra evpn mh nh +! debug zebra vxlan +! +evpn mh startup-delay 1 +! +int torm21-eth0 + ip addr 192.168.3.2/24 + evpn mh uplink +! +! +int torm21-eth1 + ip addr 192.168.7.2/24 + evpn mh uplink +! +! +int lo + ip addr 192.168.100.17/32 +! +interface hostbond1 + evpn mh es-id 1 + evpn mh es-sys-mac 44:38:39:ff:ff:02 +! +interface hostbond2 + evpn mh es-id 2 + evpn mh es-sys-mac 44:38:39:ff:ff:02 +! diff --git a/tests/topotests/bgp_evpn_mh/torm22/evpn.conf b/tests/topotests/bgp_evpn_mh/torm22/evpn.conf new file mode 100644 index 0000000..c7e1524 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm22/evpn.conf @@ -0,0 +1,21 @@ +! +frr defaults datacenter +! +! debug bgp evpn mh es +! debug bgp evpn mh route +! debug bgp zebra +! +router bgp 65005 + timers bgp 3 10 + bgp router-id 192.168.100.18 + no bgp ebgp-requires-policy + neighbor 192.168.4.1 remote-as external + neighbor 192.168.8.1 remote-as external + redistribute connected + address-family l2vpn evpn + neighbor 192.168.4.1 activate + neighbor 192.168.8.1 activate + advertise-all-vni + advertise-svi-ip + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_mh/torm22/pim.conf b/tests/topotests/bgp_evpn_mh/torm22/pim.conf new file mode 100644 index 0000000..6256e0e --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm22/pim.conf @@ -0,0 +1,19 @@ +#debug pim packet +#debug pim packet register +#debug pim trace +#debug pim event +#debug pim vxlan +! +ip pim ecmp +ip pim rp 192.168.100.13 239.1.1.0/24 +ip pim spt-switchover infinity-and-beyond +! +interface lo + ip igmp + ip pim +! +interface torm22-eth0 + ip pim +! +interface torm22-eth1 + ip pim diff --git a/tests/topotests/bgp_evpn_mh/torm22/zebra.conf b/tests/topotests/bgp_evpn_mh/torm22/zebra.conf new file mode 100644 index 0000000..4c94966 --- /dev/null +++ b/tests/topotests/bgp_evpn_mh/torm22/zebra.conf @@ -0,0 +1,29 @@ +! debug zebra evpn mh es +! debug zebra evpn mh mac +! debug zebra evpn mh neigh +! debug zebra evpn mh nh +! debug zebra vxlan +! +evpn mh startup-delay 1 +! +int torm22-eth0 + ip addr 192.168.4.2/24 + evpn mh uplink +! +! +int torm22-eth1 + ip addr 192.168.8.2/24 + evpn mh uplink +! +! +int lo + ip addr 192.168.100.18/32 +! +interface hostbond1 + evpn mh es-id 1 + evpn mh es-sys-mac 44:38:39:ff:ff:02 +! +interface hostbond2 + evpn mh es-id 2 + evpn mh es-sys-mac 44:38:39:ff:ff:02 +! diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_base.json new file mode 100644 index 0000000..2eeebad --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_base.json @@ -0,0 +1,192 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":32, + "ip":"50.0.1.11", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":128, + "ip":"50:0:1::11", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:62", + "weight":0, + "peerId":"10.0.1.2", + "path":"102", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:102:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.1]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.1]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.1", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.2]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.2]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.2", + "weight":0, + "peerId":"10.0.1.2", + "path":"102", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:102:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt2.json new file mode 100644 index 0000000..419bcc3 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt2.json @@ -0,0 +1,8 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]": null, + "[3]:[0]:[32]:[10.100.0.1]": null, + "[3]:[0]:[32]:[10.100.0.2]": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt5.json new file mode 100644 index 0000000..2eeebad --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vni_routes_no_rt5.json @@ -0,0 +1,192 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":32, + "ip":"50.0.1.11", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":128, + "ip":"50:0:1::11", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:62", + "weight":0, + "peerId":"10.0.1.2", + "path":"102", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:102:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.1]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.1]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.1", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:101:100" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.2]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.2]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.2", + "weight":0, + "peerId":"10.0.1.2", + "path":"102", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:102:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_base.json new file mode 100644 index 0000000..833f986 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_base.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100.0.0.21", + "prefixLen":32, + "network":"100.0.0.21\/32", + "metric":0, + "weight":0, + "peerId":"50.0.1.11", + "path":"111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50.0.1.11", + "afi":"ipv4", + "used":true + } + ] + } +] } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt2.json new file mode 100644 index 0000000..833f986 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt2.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100.0.0.21", + "prefixLen":32, + "network":"100.0.0.21\/32", + "metric":0, + "weight":0, + "peerId":"50.0.1.11", + "path":"111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50.0.1.11", + "afi":"ipv4", + "used":true + } + ] + } +] } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt5.json new file mode 100644 index 0000000..4a292bd --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv4_no_rt5.json @@ -0,0 +1,6 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": null } } diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_base.json new file mode 100644 index 0000000..3dc3fcf --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_base.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100::21", + "prefixLen":128, + "network":"100::21\/128", + "metric":0, + "weight":0, + "peerId":"50:0:1::11", + "path":"111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50:0:1::11", + "afi":"ipv6", + "scope":"global" + } + ] + } +] } } diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt2.json new file mode 100644 index 0000000..3dc3fcf --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt2.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100::21", + "prefixLen":128, + "network":"100::21\/128", + "metric":0, + "weight":0, + "peerId":"50:0:1::11", + "path":"111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50:0:1::11", + "afi":"ipv6", + "scope":"global" + } + ] + } +] } } diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt5.json new file mode 100644 index 0000000..6c11d89 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgp_vrf_ipv6_no_rt5.json @@ -0,0 +1,6 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.1", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": null } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgpd.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgpd.conf new file mode 100644 index 0000000..63aa99a --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/bgpd.conf @@ -0,0 +1,30 @@ +router bgp 101 + bgp router-id 10.100.0.1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.1.2 remote-as 102 + ! + address-family l2vpn evpn + neighbor 10.0.1.2 activate + advertise-all-vni + exit-address-family +! +router bgp 101 vrf vrf-blue + bgp router-id 10.100.0.1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 50.0.1.11 remote-as 111 + neighbor 50:0:1::11 remote-as 111 + ! + address-family ipv4 unicast + no neighbor 50:0:1::11 activate + exit-address-family + ! + address-family ipv6 unicast + neighbor 50:0:1::11 activate + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast gateway-ip + advertise ipv6 unicast gateway-ip + exit-address-family \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra.conf new file mode 100644 index 0000000..99a2e89 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra.conf @@ -0,0 +1,14 @@ +! +log file zebra.log +! +ip route 10.100.0.2/32 10.0.1.2 +! +vrf vrf-blue + vni 1000 prefix-routes-only + exit-vrf +! +interface lo + ip address 10.100.0.1/32 +interface PE1-eth0 + ip address 10.0.1.1/24 +! diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_base.json new file mode 100644 index 0000000..2dcf35d --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_base.json @@ -0,0 +1,56 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32":[ + { + "prefix":"100.0.0.21\/32", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"50.0.1.11", + "afi":"ipv4", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt2.json new file mode 100644 index 0000000..2dcf35d --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt2.json @@ -0,0 +1,56 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32":[ + { + "prefix":"100.0.0.21\/32", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"50.0.1.11", + "afi":"ipv4", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt5.json new file mode 100644 index 0000000..9c3091d --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv4_no_rt5.json @@ -0,0 +1,29 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32": null +} diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_base.json new file mode 100644 index 0000000..229c927 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_base.json @@ -0,0 +1,55 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128":[ + { + "prefix":"100::21\/128", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt2.json new file mode 100644 index 0000000..229c927 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt2.json @@ -0,0 +1,55 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128":[ + { + "prefix":"100::21\/128", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt5.json new file mode 100644 index 0000000..94f82e6 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE1/zebra_vrf_ipv6_no_rt5.json @@ -0,0 +1,29 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_base.json new file mode 100644 index 0000000..7b8d38e --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_base.json @@ -0,0 +1,192 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":32, + "ip":"50.0.1.11", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":128, + "ip":"50:0:1::11", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:62", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.1]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.1]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.1", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.2]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.2]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.2", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt2.json new file mode 100644 index 0000000..6273b3e --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt2.json @@ -0,0 +1,68 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]": null, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:62", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.1]": null, + "[3]:[0]:[32]:[10.100.0.2]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.2]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.2", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt5.json new file mode 100644 index 0000000..7b8d38e --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vni_routes_no_rt5.json @@ -0,0 +1,192 @@ +{ + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[32]:[50.0.1.11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":32, + "ip":"50.0.1.11", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:61]:[128]:[50:0:1::11]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:61", + "ipLen":128, + "ip":"50:0:1::11", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]":{ + "prefix":"[2]:[0]:[48]:[1a:2b:3c:4d:5e:62]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":2, + "ethTag":0, + "macLen":48, + "mac":"1a:2b:3c:4d:5e:62", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.1]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.1]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.1", + "weight":0, + "peerId":"10.0.1.1", + "path":"101", + "origin":"IGP", + "extendedCommunity":{ + "string":"RT:101:100 ET:8" + }, + "nexthops":[ + { + "ip":"10.100.0.1", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + }, + "[3]:[0]:[32]:[10.100.0.2]":{ + "prefix":"[3]:[0]:[32]:[10.100.0.2]", + "prefixLen":320, + "paths":[ + [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "routeType":3, + "ethTag":0, + "ipLen":32, + "ip":"10.100.0.2", + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "extendedCommunity":{ + "string":"ET:8 RT:102:100" + }, + "nexthops":[ + { + "ip":"10.100.0.2", + "afi":"ipv4", + "used":true + } + ] + } + ] + ] + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_base.json new file mode 100644 index 0000000..c03d701 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_base.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100.0.0.21", + "prefixLen":32, + "network":"100.0.0.21\/32", + "metric":0, + "weight":0, + "peerId":"10.0.1.1", + "path":"101 111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50.0.1.11", + "afi":"ipv4", + "used":true + } + ] + } +] } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt2.json new file mode 100644 index 0000000..7f1b8d2 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt2.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": [ + { + "valid":null, + "bestpath":null, + "pathFrom":"external", + "prefix":"100.0.0.21", + "prefixLen":32, + "network":"100.0.0.21\/32", + "metric":0, + "weight":0, + "peerId":"10.0.1.1", + "path":"101 111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50.0.1.11", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt5.json new file mode 100644 index 0000000..52e4311 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv4_no_rt5.json @@ -0,0 +1,6 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100.0.0.21/32": null } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_base.json new file mode 100644 index 0000000..1d90c9c --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_base.json @@ -0,0 +1,28 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"100::21", + "prefixLen":128, + "network":"100::21\/128", + "metric":0, + "weight":0, + "peerId":"10.0.1.1", + "path":"101 111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50:0:1::11", + "afi":"ipv6", + "scope":"global", + "used":true + } + ] + } +] } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt2.json new file mode 100644 index 0000000..a0e63c6 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt2.json @@ -0,0 +1,28 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": [ + { + "valid":null, + "bestpath":null, + "pathFrom":"external", + "prefix":"100::21", + "prefixLen":128, + "network":"100::21\/128", + "metric":0, + "weight":0, + "peerId":"10.0.1.1", + "path":"101 111", + "origin":"IGP", + "nexthops":[ + { + "ip":"50:0:1::11", + "afi":"ipv6", + "scope":"global", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt5.json new file mode 100644 index 0000000..789fe69 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgp_vrf_ipv6_no_rt5.json @@ -0,0 +1,6 @@ +{ + "vrfName": "vrf-blue", + "routerId": "10.100.0.2", + "defaultLocPrf": 100, + "localAS": 101, + "routes": { "100::21/128": null } } \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgpd.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgpd.conf new file mode 100644 index 0000000..59fee15 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 102 + bgp router-id 10.100.0.2 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.1.1 remote-as 101 + ! + address-family l2vpn evpn + neighbor 10.0.1.1 activate + advertise-all-vni + enable-resolve-overlay-index + exit-address-family +! +router bgp 101 vrf vrf-blue + bgp router-id 10.100.0.2 diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra.conf new file mode 100644 index 0000000..b78cdcc --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra.conf @@ -0,0 +1,14 @@ +! +log file zebra.log +! +ip route 10.100.0.1/32 10.0.1.1 +! +vrf vrf-blue + vni 1000 prefix-routes-only + exit-vrf +! +interface lo + ip address 10.100.0.2/32 +interface PE2-eth0 + ip address 10.0.1.2/24 +! diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_base.json new file mode 100644 index 0000000..b3a3640 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_base.json @@ -0,0 +1,56 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32":[ + { + "prefix":"100.0.0.21\/32", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":40, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"50.0.1.11", + "afi":"ipv4", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt2.json new file mode 100644 index 0000000..996fe52 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt2.json @@ -0,0 +1,29 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt5.json new file mode 100644 index 0000000..996fe52 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv4_no_rt5.json @@ -0,0 +1,29 @@ +{ + "50.0.1.0\/24":[ + { + "prefix":"50.0.1.0\/24", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100.0.0.21\/32": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_base.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_base.json new file mode 100644 index 0000000..d5be22a --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_base.json @@ -0,0 +1,56 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128":[ + { + "prefix":"100::21\/128", + "protocol":"bgp", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":40, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"50:0:1::11", + "afi":"ipv6", + "interfaceName":"br100", + "active":true, + "weight":1 + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt2.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt2.json new file mode 100644 index 0000000..94f82e6 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt2.json @@ -0,0 +1,29 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt5.json b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt5.json new file mode 100644 index 0000000..94f82e6 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/PE2/zebra_vrf_ipv6_no_rt5.json @@ -0,0 +1,29 @@ +{ + "50:0:1::\/48":[ + { + "prefix":"50:0:1::\/48", + "protocol":"connected", + "vrfName":"vrf-blue", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"br100", + "active":true + } + ] + } + ], + "100::21\/128": null +} \ No newline at end of file diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/__init__.py b/tests/topotests/bgp_evpn_overlay_index_gateway/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/host1/bgpd.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/host1/bgpd.conf new file mode 100644 index 0000000..7608ec9 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/host1/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 111 + bgp router-id 10.100.0.11 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 50.0.1.1 remote-as 101 + neighbor 50:0:1::1 remote-as 101 + ! + address-family ipv4 unicast + network 100.0.0.21/32 + no neighbor 50:0:1::1 activate + exit-address-family + ! + address-family ipv6 unicast + network 100::21/128 + neighbor 50:0:1::1 activate + exit-address-family + + diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/host1/zebra.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/host1/zebra.conf new file mode 100644 index 0000000..c8c832e --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/host1/zebra.conf @@ -0,0 +1,4 @@ +! +int host1-eth0 + ip address 50.0.1.11/24 + ipv6 address 50:0:1::11/48 diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/host2/bgpd.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/host2/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/host2/zebra.conf b/tests/topotests/bgp_evpn_overlay_index_gateway/host2/zebra.conf new file mode 100644 index 0000000..b9f80f1 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/host2/zebra.conf @@ -0,0 +1,4 @@ +! +int host2-eth0 + ip address 50.0.1.21/24 + ipv6 address 50:0:1::21/48 diff --git a/tests/topotests/bgp_evpn_overlay_index_gateway/test_bgp_evpn_overlay_index_gateway.py b/tests/topotests/bgp_evpn_overlay_index_gateway/test_bgp_evpn_overlay_index_gateway.py new file mode 100755 index 0000000..603f069 --- /dev/null +++ b/tests/topotests/bgp_evpn_overlay_index_gateway/test_bgp_evpn_overlay_index_gateway.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +test_bgp_evpn_overlay_index_gateway.py: Test EVPN gateway IP overlay index functionality +Following functionality is covered: + + +--------+ BGP +--------+ BGP +--------+ +--------+ + SN1 | | IPv4/v6 | | EVPN | | | | + ======+ Host1 +---------+ PE1 +------+ PE2 +------+ Host2 + + | | | | | | | | + +--------+ +--------+ +--------+ +--------+ + + Host1 is connected to PE1 and host2 is connected to PE2 + Host1 and PE1 have IPv4/v6 BGP sessions. + PE1 and PE2 gave EVPN session. + Host1 advertises IPv4/v6 prefixes to PE1. + PE1 advertises these prefixes to PE2 as EVPN type-5 routes. + Gateway IP for these EVPN type-5 routes is host1 IP. + Host1 MAC/IP is advertised by PE1 as EVPN type-2 route + +Following testcases are covered: +TC_1: +Check BGP and zebra states for above topology at PE1 and PE2. + +TC_2: +Stop advertising prefixes from host1. It should withdraw type-5 routes. Check states at PE1 and PE2 +Advertise the prefixes again. Check states. + +TC_3: +Shut down VxLAN interface at PE1. This should withdraw type-2 routes. Check states at PE1 and PE2. +Enable VxLAN interface again. Check states. +""" + +import os +import sys +import json +from functools import partial +import pytest +import time +import platform + +# Current Working Directory +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import ( + step, + write_test_header, + write_test_footer, +) + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +PES = ["PE1", "PE2"] +HOSTS = ["host1", "host2"] +PE_SUFFIX = {"PE1": "1", "PE2": "2"} +HOST_SUFFIX = {"host1": "1", "host2": "2"} +TRIGGERS = ["base", "no_rt5", "no_rt2"] + + +def build_topo(tgen): + # This function only purpose is to define allocation and relationship + # between routers and add links. + + # Create routers + for pe in PES: + tgen.add_router(pe) + for host in HOSTS: + tgen.add_router(host) + + krel = platform.release() + logger.info("Kernel version " + krel) + + # Add links + tgen.add_link(tgen.gears["PE1"], tgen.gears["PE2"], "PE1-eth0", "PE2-eth0") + tgen.add_link(tgen.gears["PE1"], tgen.gears["host1"], "PE1-eth1", "host1-eth0") + tgen.add_link(tgen.gears["PE2"], tgen.gears["host2"], "PE2-eth1", "host2-eth0") + + +def setup_module(mod): + "Sets up the pytest environment" + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + + kernelv = platform.release() + if topotest.version_cmp(kernelv, "4.15") < 0: + logger.info( + "For EVPN, kernel version should be minimum 4.15. Kernel present {}".format( + kernelv + ) + ) + return + + if topotest.version_cmp(kernelv, "4.15") == 0: + l3mdev_accept = 1 + logger.info("setting net.ipv4.tcp_l3mdev_accept={}".format(l3mdev_accept)) + else: + l3mdev_accept = 0 + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + tgen.start_topology() + + # Configure MAC address for hosts as these MACs are advertised with EVPN type-2 routes + for name in tgen.gears: + if name not in HOSTS: + continue + host = tgen.net[name] + + host_mac = "1a:2b:3c:4d:5e:6{}".format(HOST_SUFFIX[name]) + host.cmd_raises("ip link set dev {}-eth0 down".format(name)) + host.cmd_raises("ip link set dev {0}-eth0 address {1}".format(name, host_mac)) + host.cmd_raises("ip link set dev {}-eth0 up".format(name)) + + # Configure PE VxLAN and Bridge interfaces + for name in tgen.gears: + if name not in PES: + continue + pe = tgen.net[name] + + vtep_ip = "10.100.0.{}".format(PE_SUFFIX[name]) + bridge_ip = "50.0.1.{}/24".format(PE_SUFFIX[name]) + bridge_ipv6 = "50:0:1::{}/48".format(PE_SUFFIX[name]) + + pe.cmd_raises("ip link add vrf-blue type vrf table 10") + pe.cmd_raises("ip link set dev vrf-blue up") + pe.cmd_raises( + "ip link add vxlan100 type vxlan id 100 dstport 4789 local {}".format( + vtep_ip + ) + ) + pe.cmd_raises("ip link add name br100 type bridge stp_state 0") + pe.cmd_raises("ip link set dev vxlan100 master br100") + pe.cmd_raises("ip link set dev {}-eth1 master br100".format(name)) + pe.cmd_raises("ip addr add {} dev br100".format(bridge_ip)) + pe.cmd_raises("ip link set up dev br100") + pe.cmd_raises("ip link set up dev vxlan100") + pe.cmd_raises("ip link set up dev {}-eth1".format(name)) + pe.cmd_raises("ip link set dev br100 master vrf-blue") + pe.cmd_raises("ip -6 addr add {} dev br100".format(bridge_ipv6)) + + pe.cmd_raises( + "ip link add vxlan1000 type vxlan id 1000 dstport 4789 local {}".format( + vtep_ip + ) + ) + pe.cmd_raises("ip link add name br1000 type bridge stp_state 0") + pe.cmd_raises("ip link set dev vxlan1000 master br100") + pe.cmd_raises("ip link set up dev br1000") + pe.cmd_raises("ip link set up dev vxlan1000") + pe.cmd_raises("ip link set dev br1000 master vrf-blue") + + pe.cmd_raises("sysctl -w net.ipv4.ip_forward=1") + pe.cmd_raises("sysctl -w net.ipv6.conf.all.forwarding=1") + pe.cmd_raises("sysctl -w net.ipv4.udp_l3mdev_accept={}".format(l3mdev_accept)) + pe.cmd_raises("sysctl -w net.ipv4.tcp_l3mdev_accept={}".format(l3mdev_accept)) + + # For all registered routers, load the zebra configuration file + for name, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(name)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(name)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + logger.info("Running setup_module() done") + + time.sleep(10) + + +def teardown_module(mod): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def evpn_gateway_ip_show_op_check(trigger=" "): + """ + This function checks CLI O/P for commands mentioned in show_commands for a given trigger + :param trigger: Should be a trigger present in TRIGGERS + :return: Returns a tuple (result: None for success, retmsg: Log message to be printed on failure) + """ + tgen = get_topogen() + + if trigger not in TRIGGERS: + return "Unexpected trigger", "Unexpected trigger {}".format(trigger) + + show_commands = { + "bgp_vni_routes": "show bgp l2vpn evpn route vni 100 json", + "bgp_vrf_ipv4": "show bgp vrf vrf-blue ipv4 json", + "bgp_vrf_ipv6": "show bgp vrf vrf-blue ipv6 json", + "zebra_vrf_ipv4": "show ip route vrf vrf-blue json", + "zebra_vrf_ipv6": "show ipv6 route vrf vrf-blue json", + } + + for name, pe in tgen.gears.items(): + if name not in PES: + continue + + for cmd_key, command in show_commands.items(): + expected_op_file = "{0}/{1}/{2}_{3}.json".format( + CWD, name, cmd_key, trigger + ) + expected_op = json.loads(open(expected_op_file).read()) + + test_func = partial(topotest.router_json_cmp, pe, command, expected_op) + ret, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{0}" JSON output mismatch for {1}'.format(name, command) + if result is not None: + return result, assertmsg + + return None, "Pass" + + +def test_evpn_gateway_ip_basic_topo(request): + """ + Tets EVPN overlay index gateway IP functionality. VErify show O/Ps on PE1 and PE2 + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Temporarily Disabled + tgen.set_error( + "%s: Failing under new micronet framework, please debug and re-enable", tc_name + ) + + kernelv = platform.release() + if topotest.version_cmp(kernelv, "4.15") < 0: + logger.info("For EVPN, kernel version should be minimum 4.15") + write_test_footer(tc_name) + return + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check O/Ps for EVPN gateway IP overlay Index functionality at PE1 and PE2") + + result, assertmsg = evpn_gateway_ip_show_op_check("base") + + assert result is None, assertmsg + + write_test_footer(tc_name) + + +def test_evpn_gateway_ip_flap_rt5(request): + """ + Withdraw EVPN type-5 routes and check O/Ps at PE1 and PE2 + """ + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + kernelv = platform.release() + if topotest.version_cmp(kernelv, "4.15") < 0: + logger.info("For EVPN, kernel version should be minimum 4.15") + write_test_footer(tc_name) + return + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + h1 = tgen.gears["host1"] + + step("Withdraw type-5 routes") + + h1.run( + 'vtysh -c "config t" \ + -c "router bgp 111" \ + -c "address-family ipv4" \ + -c "no network 100.0.0.21/32"' + ) + h1.run( + 'vtysh -c "config t" \ + -c "router bgp 111" \ + -c "address-family ipv6" \ + -c "no network 100::21/128"' + ) + + result, assertmsg = evpn_gateway_ip_show_op_check("no_rt5") + assert result is None, assertmsg + + step("Advertise type-5 routes again") + + h1.run( + 'vtysh -c "config t" \ + -c "router bgp 111" \ + -c "address-family ipv4" \ + -c "network 100.0.0.21/32"' + ) + h1.run( + 'vtysh -c "config t" \ + -c "router bgp 111" \ + -c "address-family ipv6" \ + -c "network 100::21/128"' + ) + + result, assertmsg = evpn_gateway_ip_show_op_check("base") + + assert result is None, assertmsg + + write_test_footer(tc_name) + + +def test_evpn_gateway_ip_flap_rt2(request): + """ + Withdraw EVPN type-2 routes and check O/Ps at PE1 and PE2 + """ + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + kernelv = platform.release() + if topotest.version_cmp(kernelv, "4.15") < 0: + logger.info("For EVPN, kernel version should be minimum 4.15") + write_test_footer(tc_name) + return + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Shut down VxLAN interface at PE1 which results in withdraw of type-2 routes") + + pe1 = tgen.net["PE1"] + + pe1.cmd_raises("ip link set dev vxlan100 down") + + result, assertmsg = evpn_gateway_ip_show_op_check("no_rt2") + assert result is None, assertmsg + + step("Bring up VxLAN interface at PE1 and advertise type-2 routes again") + + pe1.cmd_raises("ip link set dev vxlan100 up") + + result, assertmsg = evpn_gateway_ip_show_op_check("base") + assert result is None, assertmsg + + write_test_footer(tc_name) + + +def test_memory_leak(): + """Run the memory leak test and report results""" + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_route_map_match/__init__.py b/tests/topotests/bgp_evpn_route_map_match/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_route_map_match/c1/frr.conf b/tests/topotests/bgp_evpn_route_map_match/c1/frr.conf new file mode 100644 index 0000000..7476a37 --- /dev/null +++ b/tests/topotests/bgp_evpn_route_map_match/c1/frr.conf @@ -0,0 +1,4 @@ +! +int c1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_evpn_route_map_match/c2/frr.conf b/tests/topotests/bgp_evpn_route_map_match/c2/frr.conf new file mode 100644 index 0000000..a203daa --- /dev/null +++ b/tests/topotests/bgp_evpn_route_map_match/c2/frr.conf @@ -0,0 +1,4 @@ +! +int c2-eth0 + ip address 192.168.0.2/24 +! diff --git a/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf b/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf new file mode 100644 index 0000000..4347052 --- /dev/null +++ b/tests/topotests/bgp_evpn_route_map_match/r1/frr.conf @@ -0,0 +1,44 @@ +! +!debug bgp neighbor +!debug route-map detail +! +vni 10 +! +int lo + ip address 10.10.10.1/32 +! +int r1-eth1 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + network 10.10.10.10/32 + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map r2 out + advertise-all-vni + advertise ipv4 unicast + exit-address-family +! +route-map r2 deny 10 + match evpn route-type macip +! +route-map r2 deny 20 + match ip address prefix-list pl + match evpn route-type prefix +! +route-map r2 permit 30 +! +ip prefix-list pl seq 5 permit 192.168.1.0/24 +ip prefix-list pl seq 10 permit 10.10.10.1/32 +ip prefix-list pl seq 15 permit 10.10.10.2/32 +! diff --git a/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf b/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf new file mode 100644 index 0000000..9ed298d --- /dev/null +++ b/tests/topotests/bgp_evpn_route_map_match/r2/frr.conf @@ -0,0 +1,24 @@ +! +!debug bgp neighbor +! +int lo + ip address 10.10.10.2/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family l2vpn evpn + neighbor 192.168.1.1 activate + advertise-all-vni + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py b/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py new file mode 100644 index 0000000..2df0fd0 --- /dev/null +++ b/tests/topotests/bgp_evpn_route_map_match/test_bgp_evpn_route_map_match.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if route-map match by EVPN route-type works. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("c1", "r1"), "s2": ("r1", "r2"), "s3": ("r2", "c2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + tgen.net["r1"].cmd( + """ +ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.10.10.1 nolearning +ip link add name br10 type bridge +ip link set dev vxlan10 master br10 +ip link set dev r1-eth0 master br10 +ip link set up dev br10 +ip link set up dev vxlan10""" + ) + + tgen.net["r2"].cmd( + """ +ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.10.10.2 nolearning +ip link add name br10 type bridge +ip link set dev vxlan10 master br10 +ip link set dev r2-eth1 master br10 +ip link set up dev br10 +ip link set up dev vxlan10""" + ) + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_evpn_route_map_match_route_type(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_converge(): + output = json.loads( + r1.vtysh_cmd( + "show bgp l2vpn evpn neighbor 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.1:1": { + "[5]:[0]:[32]:[10.10.10.10]": { + "valid": True, + } + }, + "10.10.10.2:2": { + "[3]:[0]:[32]:[10.10.10.2]": { + "valid": True, + } + }, + }, + "totalPrefixCounter": 2, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Filtered EVPN routes should not be advertised" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_rt5/__init__.py b/tests/topotests/bgp_evpn_rt5/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_rt5/r1/bgpd.conf b/tests/topotests/bgp_evpn_rt5/r1/bgpd.conf new file mode 100644 index 0000000..ccbeae6 --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r1/bgpd.conf @@ -0,0 +1,26 @@ +! debug bgp neighbor-events +! debug bgp updates +! debug bgp zebra +router bgp 65000 + bgp router-id 192.168.100.21 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor 192.168.100.41 remote-as 65000 + neighbor 192.168.100.41 capability extended-nexthop + ! + address-family l2vpn evpn + neighbor 192.168.100.41 activate + advertise-all-vni + exit-address-family +! +router bgp 65000 vrf r1-vrf-101 + bgp router-id 192.168.102.21 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + network 192.168.102.21/32 + exit-address-family + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family + ! diff --git a/tests/topotests/bgp_evpn_rt5/r1/zebra.conf b/tests/topotests/bgp_evpn_rt5/r1/zebra.conf new file mode 100644 index 0000000..4f1804c --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r1/zebra.conf @@ -0,0 +1,22 @@ +log stdout + +hostname r1 +password zebra + +! debug zebra vxlan +! debug zebra kernel +! debug zebra dplane +! debug zebra rib +log stdout +vrf r1-vrf-101 + vni 101 + exit-vrf +! +interface r1-eth0 + ip address 192.168.100.21/24 +! +interface loop101 vrf r1-vrf-101 + ip address 192.168.102.21/32 +! + + diff --git a/tests/topotests/bgp_evpn_rt5/r2/bgpd.conf b/tests/topotests/bgp_evpn_rt5/r2/bgpd.conf new file mode 100644 index 0000000..744c259 --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r2/bgpd.conf @@ -0,0 +1,27 @@ +! debug bgp neighbor-events +! debug bgp updates +! debug bgp zebra +router bgp 65000 + bgp router-id 192.168.100.41 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor 192.168.100.21 peer-group + neighbor 192.168.100.21 remote-as 65000 + neighbor 192.168.100.21 capability extended-nexthop + ! + address-family l2vpn evpn + neighbor 192.168.100.21 activate + advertise-all-vni + exit-address-family +! +router bgp 65000 vrf r2-vrf-101 + bgp router-id 192.168.101.41 + bgp log-neighbor-changes + no bgp network import-check + address-family ipv4 unicast + network 192.168.101.41/32 + exit-address-family + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family + ! diff --git a/tests/topotests/bgp_evpn_rt5/r2/zebra.conf b/tests/topotests/bgp_evpn_rt5/r2/zebra.conf new file mode 100644 index 0000000..7d19a5b --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/r2/zebra.conf @@ -0,0 +1,18 @@ +log stdout + +hostname r2 +password zebra + +! debug zebra vxlan + +vrf r2-vrf-101 + vni 101 + exit-vrf +! +interface loop101 vrf r2-vrf-101 + ip address 192.168.101.41/32 +! +interface r2-eth0 + ip address 192.168.100.41/24 +! + diff --git a/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py new file mode 100644 index 0000000..d9177b4 --- /dev/null +++ b/tests/topotests/bgp_evpn_rt5/test_bgp_evpn.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_evpn.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by 6WIND +# + +""" + test_bgp_evpn.py: Test the FRR BGP daemon with BGP IPv6 interface + with route advertisements on a separate netns. +""" + +import os +import sys +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + krel = platform.release() + if topotest.version_cmp(krel, "4.18") < 0: + logger.info( + 'BGP EVPN RT5 NETNS tests will not run (have kernel "{}", but it requires 4.18)'.format( + krel + ) + ) + return pytest.skip("Skipping BGP EVPN RT5 NETNS Test. Kernel not supported") + + # create VRF vrf-101 on R1 and R2 + # create loop101 + cmds_vrflite = [ + "ip link add {}-vrf-101 type vrf table 101", + "ip ru add oif {}-vrf-101 table 101", + "ip ru add iif {}-vrf-101 table 101", + "ip link set dev {}-vrf-101 up", + "ip link add loop101 type dummy", + "ip link set dev loop101 master {}-vrf-101", + "ip link set dev loop101 up", + ] + + cmds_r2 = [ # config routing 101 + "ip link add name bridge-101 up type bridge stp_state 0", + "ip link set bridge-101 master {}-vrf-101", + "ip link set dev bridge-101 up", + "ip link add name vxlan-101 type vxlan id 101 dstport 4789 dev r2-eth0 local 192.168.100.41", + "ip link set dev vxlan-101 master bridge-101", + "ip link set vxlan-101 up type bridge_slave learning off flood off mcast_flood off", + ] + + # cmds_r1_netns_method3 = [ + # "ip link add name vxlan-{1} type vxlan id {1} dstport 4789 dev {0}-eth0 local 192.168.100.21", + # "ip link set dev vxlan-{1} netns {0}-vrf-{1}", + # "ip netns exec {0}-vrf-{1} ip li set dev lo up", + # "ip netns exec {0}-vrf-{1} ip link add name bridge-{1} up type bridge stp_state 0", + # "ip netns exec {0}-vrf-{1} ip link set dev vxlan-{1} master bridge-{1}", + # "ip netns exec {0}-vrf-{1} ip link set bridge-{1} up", + # "ip netns exec {0}-vrf-{1} ip link set vxlan-{1} up", + # ] + + router = tgen.gears["r1"] + + ns = "r1-vrf-101" + tgen.net["r1"].add_netns(ns) + tgen.net["r1"].cmd_raises("ip link add loop101 type dummy") + tgen.net["r1"].set_intf_netns("loop101", ns, up=True) + + router = tgen.gears["r2"] + for cmd in cmds_vrflite: + logger.info("cmd to r2: " + cmd.format("r2")) + output = router.cmd_raises(cmd.format("r2")) + logger.info("result: " + output) + + for cmd in cmds_r2: + logger.info("cmd to r2: " + cmd.format("r2")) + output = router.cmd_raises(cmd.format("r2")) + logger.info("result: " + output) + + tgen.net["r1"].cmd_raises( + "ip link add name vxlan-101 type vxlan id 101 dstport 4789 dev r1-eth0 local 192.168.100.21" + ) + tgen.net["r1"].set_intf_netns("vxlan-101", "r1-vrf-101", up=True) + tgen.net["r1"].cmd_raises("ip -n r1-vrf-101 link set lo up") + tgen.net["r1"].cmd_raises( + "ip -n r1-vrf-101 link add name bridge-101 up type bridge stp_state 0" + ) + tgen.net["r1"].cmd_raises( + "ip -n r1-vrf-101 link set dev vxlan-101 master bridge-101" + ) + tgen.net["r1"].cmd_raises("ip -n r1-vrf-101 link set bridge-101 up") + tgen.net["r1"].cmd_raises("ip -n r1-vrf-101 link set vxlan-101 up") + + for rname, router in router_list.items(): + if rname == "r1": + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)), + "--vrfwnetns", + ) + else: + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.net["r1"].delete_netns("r1-vrf-101") + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + topotest.sleep(4, "waiting 4 seconds for bgp convergence") + # Check IPv4/IPv6 routing tables. + output = tgen.gears["r1"].vtysh_cmd("show bgp l2vpn evpn", isjson=False) + logger.info("==== result from show bgp l2vpn evpn") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd( + "show bgp l2vpn evpn route detail", isjson=False + ) + logger.info("==== result from show bgp l2vpn evpn route detail") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show bgp vrf r1-vrf-101 ipv4", isjson=False) + logger.info("==== result from show bgp vrf r1-vrf-101 ipv4") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show bgp vrf r1-vrf-101", isjson=False) + logger.info("==== result from show bgp vrf r1-vrf-101 ") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show ip route vrf r1-vrf-101", isjson=False) + logger.info("==== result from show ip route vrf r1-vrf-101") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show evpn vni detail", isjson=False) + logger.info("==== result from show evpn vni detail") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show evpn next-hops vni all", isjson=False) + logger.info("==== result from show evpn next-hops vni all") + logger.info(output) + output = tgen.gears["r1"].vtysh_cmd("show evpn rmac vni all", isjson=False) + logger.info("==== result from show evpn next-hops vni all") + logger.info(output) + # Check IPv4 and IPv6 connectivity between r1 and r2 ( routing vxlan evpn) + pingrouter = tgen.gears["r1"] + logger.info( + "Check Ping IPv4 from R1(r1-vrf-101) to R2(r2-vrf-101 = 192.168.101.41)" + ) + output = pingrouter.run("ip netns exec r1-vrf-101 ping 192.168.101.41 -f -c 1000") + logger.info(output) + if "1000 packets transmitted, 1000 received" not in output: + assertmsg = ( + "expected ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) should be ok" + ) + assert 0, assertmsg + else: + logger.info("Check Ping IPv4 from R1(r1-vrf-101) to R2(192.168.101.41) OK") + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf new file mode 100644 index 0000000..2db7edb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/ospfd.conf @@ -0,0 +1,13 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.20.20.20/32 area 0 +! +int P1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int P1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf new file mode 100644 index 0000000..95b5da8 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/P1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.20.20.20/32 +interface P1-eth0 + ip address 10.20.1.2/24 +interface P1-eth1 + ip address 10.20.2.2/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json new file mode 100644 index 0000000..9f93635 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgp.l2vpn.evpn.vni.json @@ -0,0 +1,19 @@ +{ + "vni":101, + "type":"L2", + "inKernel":"True", + "rd":"10.10.10.10:101", + "originatorIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "siteOfOrigin":"65000:0", + "advertiseGatewayMacip":"Disabled", + "advertiseSviMacIp":"Active", + "sviInterface":"br101", + "importRts":[ + "65000:101" + ], + "exportRts":[ + "65000:101" + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf new file mode 100644 index 0000000..e4d20b9 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.10.10.10 + no bgp default ipv4-unicast + neighbor 10.30.30.30 remote-as 65000 + neighbor 10.30.30.30 update-source lo + neighbor 10.30.30.30 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.30.30.30 activate + advertise-all-vni + advertise-svi-ip + vni 101 + rd 10.10.10.10:101 + route-target import 65000:101 + route-target export 65000:101 + exit-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json new file mode 100644 index 0000000..4bea8b3 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/evpn.vni.json @@ -0,0 +1,17 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"VRF-A", + "vxlanInterface":"vxlan101", + "vtepIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.30.30.30", + "flood":"HER" + } + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf new file mode 100644 index 0000000..f1c2b42 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.10.10.10/32 area 0 +! +int PE1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf new file mode 100644 index 0000000..e269947 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE1/zebra.conf @@ -0,0 +1,8 @@ +! +log file zebra.log +! +interface lo + ip address 10.10.10.10/32 +interface PE1-eth1 + ip address 10.20.1.1/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json new file mode 100644 index 0000000..63ac730 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgp.l2vpn.evpn.vni.json @@ -0,0 +1,19 @@ +{ + "vni":101, + "type":"L2", + "inKernel":"True", + "rd":"10.30.30.30:101", + "originatorIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "siteOfOrigin":"65000:0", + "advertiseGatewayMacip":"Disabled", + "advertiseSviMacIp":"Active", + "sviInterface":"br101", + "importRts":[ + "65000:101" + ], + "exportRts":[ + "65000:101" + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf new file mode 100644 index 0000000..9a0830d --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.30.30.30 + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as 65000 + neighbor 10.10.10.10 update-source lo + neighbor 10.10.10.10 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.10.10.10 activate + advertise-all-vni + advertise-svi-ip + vni 101 + rd 10.30.30.30:101 + route-target import 65000:101 + route-target export 65000:101 + exit-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json new file mode 100644 index 0000000..5566fff --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/evpn.vni.json @@ -0,0 +1,16 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"VRF-A", + "vxlanInterface":"vxlan101", + "vtepIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.10.10.10", + "flood":"HER" + } + ] +} diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf new file mode 100644 index 0000000..065c993 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.30.30.30/32 area 0 +! +int PE2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf new file mode 100644 index 0000000..9738916 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/PE2/zebra.conf @@ -0,0 +1,6 @@ +! +interface lo + ip address 10.30.30.30/32 +interface PE2-eth0 + ip address 10.20.2.3/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf new file mode 100644 index 0000000..91fae9e --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host1/zebra.conf @@ -0,0 +1,3 @@ +! +int host1-eth0 + ip address 10.10.1.55/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf new file mode 100644 index 0000000..df9adeb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/host2/zebra.conf @@ -0,0 +1,3 @@ +! +interface host2-eth0 + ip address 10.10.1.56/24 diff --git a/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py new file mode 100755 index 0000000..558f737 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_macvrf_soo_topo1/test_bgp_evpn_vxlan_macvrf_soo.py @@ -0,0 +1,839 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-or-later +# +# test_bgp_evpn_vxlan_macvrf_soo.py +# +# May 10 2023, Trey Aspelund +# +# Copyright (C) 2023 NVIDIA Corporation +# +# Test MAC-VRF Site-of-Origin feature. +# Ensure: +# - routes received with SoO are installed w/o "mac-vrf soo" config +# - invalid "mac-vrf soo" config is rejected +# - valid "mac-vrf soo" config is applied to local VNIs +# - valid "mac-vrf soo" is set for locally originated type-2/3 routes +# - routes received with SoO are unimported/uninstalled from L2VNI/zebra +# - routes received with SoO are unimported/uninstalled from L3VNI/RIB +# - routes received with SoO are still present in global EVPN loc-rib +# + +import os +import sys +import json +from functools import partial +from time import sleep +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def build_topo(tgen): + "Build function" + + # Create routers + tgen.add_router("P1") + tgen.add_router("PE1") + tgen.add_router("PE2") + tgen.add_router("host1") + tgen.add_router("host2") + + # Host1-PE1 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["host1"]) + switch.add_link(tgen.gears["PE1"]) + + # PE1-P1 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["PE1"]) + switch.add_link(tgen.gears["P1"]) + + # P1-PE2 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["P1"]) + switch.add_link(tgen.gears["PE2"]) + + # PE2-host2 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["PE2"]) + switch.add_link(tgen.gears["host2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + p1 = tgen.gears["P1"] + host1 = tgen.gears["host1"] + host2 = tgen.gears["host2"] + + # Setup PEs with: + # - vrf: VRF-A + # - l3vni 404: vxlan404 / br404 + # - l2vni 101: vxlan101 / br101 + + ## Setup VRF + # pe1 + pe1.run("ip link add VRF-A type vrf table 4000") + pe1.run("ip link set VRF-A up") + # pe2 + pe2.run("ip link add VRF-A type vrf table 4000") + pe2.run("ip link set VRF-A up") + + ## Setup L3VNI bridge/vxlan + # pe1 + pe1.run("ip link add name br404 type bridge stp_state 0") + pe1.run("ip link set dev br404 addr aa:bb:cc:00:11:ff") + pe1.run("ip link set dev br404 master VRF-A addrgenmode none") + pe1.run("ip link set dev br404 up") + pe1.run( + "ip link add vxlan404 type vxlan id 404 dstport 4789 local 10.10.10.10 nolearning" + ) + pe1.run("ip link set dev vxlan404 master br404 addrgenmode none") + pe1.run("ip link set dev vxlan404 type bridge_slave neigh_suppress on learning off") + pe1.run("ip link set dev vxlan404 up") + # pe2 + pe2.run("ip link add name br404 type bridge stp_state 0") + pe2.run("ip link set dev br404 addr aa:bb:cc:00:22:ff") + pe2.run("ip link set dev br404 master VRF-A addrgenmode none") + pe2.run("ip link set dev br404 up") + pe2.run( + "ip link add vxlan404 type vxlan id 404 dstport 4789 local 10.30.30.30 nolearning" + ) + pe2.run("ip link set dev vxlan404 master br404 addrgenmode none") + pe2.run("ip link set dev vxlan404 type bridge_slave neigh_suppress on learning off") + pe2.run("ip link set dev vxlan404 up") + + ## Setup L2VNI bridge/vxlan + L2 PE/CE link + # pe1 + pe1.run("ip link add name br101 type bridge stp_state 0") + pe1.run("ip addr add 10.10.1.1/24 dev br101") + pe1.run("ip link set dev br101 addr aa:bb:cc:00:11:aa") + pe1.run("ip link set dev br101 master VRF-A") + pe1.run("ip link set dev br101 up") + pe1.run( + "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.10.10.10 nolearning" + ) + pe1.run("ip link set dev vxlan101 master br101") + pe1.run("ip link set dev vxlan101 type bridge_slave neigh_suppress on learning off") + pe1.run("ip link set dev vxlan101 up") + pe1.run("ip link set dev PE1-eth0 master br101") + pe1.run("ip link set dev PE1-eth0 up") + # pe2 + pe2.run("ip link add name br101 type bridge stp_state 0") + pe2.run("ip addr add 10.10.1.3/24 dev br101") + pe2.run("ip link set dev br101 addr aa:bb:cc:00:22:ff") + pe2.run("ip link set dev br101 master VRF-A") + pe2.run("ip link set dev br101 up") + pe2.run( + "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.30.30.30 nolearning" + ) + pe2.run("ip link set dev vxlan101 master br101") + pe2.run("ip link set dev vxlan101 type bridge_slave neigh_suppress on learning off") + pe2.run("ip link set dev vxlan101 up") + pe2.run("ip link set dev PE2-eth1 master br101") + pe2.run("ip link set dev PE2-eth1 up") + + ## Enable IPv4 Routing + p1.run("sysctl -w net.ipv4.ip_forward=1") + pe1.run("sysctl -w net.ipv4.ip_forward=1") + pe2.run("sysctl -w net.ipv4.ip_forward=1") + + ## tell hosts to send GARP upon IPv4 addr assignment + host1.run("sysctl -w net.ipv4.conf.host1-eth0.arp_announce=1") + host2.run("sysctl -w net.ipv4.conf.host2-eth0.arp_announce=1") + + ## Load FRR config on all nodes and start topo + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def show_vni_json_elide_ifindex(pe, vni, expected): + output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True) + if "ifindex" in output_json: + output_json.pop("ifindex") + + return topotest.json_cmp(output_json, expected) + + +def check_vni_macs_present(tgen, router, vni, maclist): + result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True) + for rname, ifname in maclist: + m = tgen.net.macs[(rname, ifname)] + if m not in result["macs"]: + return "MAC ({}) for interface {} on {} missing on {} from {}".format( + m, ifname, rname, router.name, json.dumps(result, indent=4) + ) + return None + + +def test_pe1_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + + # Let's ensure that the hosts have actually tried talking to + # each other. Otherwise under certain startup conditions + # they may not actually do any l2 arp'ing and as such + # the bridges won't know about the hosts on their networks + host1 = tgen.gears["host1"] + host1.run("ping -c 1 10.10.1.56") + host2 = tgen.gears["host2"] + host2.run("ping -c 1 10.10.1.55") + + test_func = partial( + check_vni_macs_present, + tgen, + pe1, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe1.name) + + +def test_pe2_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe2 = tgen.gears["PE2"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe2.name) + assert result is None, assertmsg + + test_func = partial( + check_vni_macs_present, + tgen, + pe2, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe2.name) + + +def mac_learn_test(host, local): + "check the host MAC gets learned by the VNI" + + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + + mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + mac_output_json = json.loads(mac_output) + assertmsg = "Local MAC output does not match interface mac {}".format(mac) + assert mac_output_json[mac]["type"] == "local", assertmsg + + +def mac_test_local_remote(local, remote): + "test MAC transfer between local and remote" + + local_output = local.vtysh_cmd("show evpn mac vni all json") + remote_output = remote.vtysh_cmd("show evpn mac vni all json") + local_output_vni = local.vtysh_cmd("show evpn vni detail json") + local_output_json = json.loads(local_output) + remote_output_json = json.loads(remote_output) + local_output_vni_json = json.loads(local_output_vni) + + for vni in local_output_json: + mac_list = local_output_json[vni]["macs"] + for mac in mac_list: + if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101": + assertmsg = "JSON output mismatches local: {} remote: {}".format( + local_output_vni_json[0]["vtepIp"], + remote_output_json[vni]["macs"][mac]["remoteVtep"], + ) + assert ( + remote_output_json[vni]["macs"][mac]["remoteVtep"] + == local_output_vni_json[0]["vtepIp"] + ), assertmsg + + +def test_learning_pe1(): + "test MAC learning on PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + mac_learn_test(host1, pe1) + + +def test_learning_pe2(): + "test MAC learning on PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe2 = tgen.gears["PE2"] + mac_learn_test(host2, pe2) + + +def test_local_remote_mac_pe1(): + "Test MAC transfer PE1 local and PE2 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe1, pe2) + + +def test_local_remote_mac_pe2(): + "Test MAC transfer PE2 local and PE1 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe2, pe1) + + +def ip_learn_test(tgen, host, local, remote, ip_addr): + "check the host IP gets learned by the VNI" + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + print(host_output) + + # check we have a local association between the MAC and IP + local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + print(local_output) + local_output_json = json.loads(local_output) + mac_type = local_output_json[mac]["type"] + assertmsg = "Failed to learn local IP address on host {}".format(host.name) + assert local_output_json[mac]["neighbors"] != "none", assertmsg + learned_ip = local_output_json[mac]["neighbors"]["active"][0] + + assertmsg = "local learned mac wrong type: {} ".format(mac_type) + assert mac_type == "local", assertmsg + + assertmsg = ( + "learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + ) + assert ip_addr == learned_ip, assertmsg + + # now lets check the remote + count = 0 + converged = False + while count < 30: + remote_output = remote.vtysh_cmd( + "show evpn mac vni 101 mac {} json".format(mac) + ) + print(remote_output) + remote_output_json = json.loads(remote_output) + type = remote_output_json[mac]["type"] + if not remote_output_json[mac]["neighbors"] == "none": + # due to a kernel quirk, learned IPs can be inactive + if ( + remote_output_json[mac]["neighbors"]["active"] + or remote_output_json[mac]["neighbors"]["inactive"] + ): + converged = True + break + count += 1 + sleep(1) + + print("tries: {}".format(count)) + assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac) + # some debug for this failure + if not converged == True: + log_output = remote.run("cat zebra.log") + print(log_output) + + assert converged == True, assertmsg + if remote_output_json[mac]["neighbors"]["active"]: + learned_ip = remote_output_json[mac]["neighbors"]["active"][0] + else: + learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0] + assertmsg = "remote learned mac wrong type: {} ".format(type) + assert type == "remote", assertmsg + + assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + assert ip_addr == learned_ip, assertmsg + + +def test_ip_pe1_learn(): + "run the IP learn test for PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe2.vtysh_cmd("debug zebra vxlan") + # pe2.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host1.run("ping -c1 10.10.1.1") + ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55") + # tgen.mininet_cli() + + +def test_ip_pe2_learn(): + "run the IP learn test for PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe1.vtysh_cmd("debug zebra vxlan") + # pe1.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host2.run("ping -c1 10.10.1.3") + ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56") + # tgen.mininet_cli() + + +def is_installed(json_paths, soo): + """ + check if any path has been selected as best. + optionally check for matching SoO on bestpath. + """ + best = False + soo_present = False + for path in json_paths: + path = path[0] + # sometimes "bestpath" is a bool, other times it's a dict + # either way, the key isn't present when the bool is false... + # so we may as well just check for the key's existence + best = "bestpath" in path + path_keys = path.keys() + if best: + if soo: + soo_present = soo in path["extendedCommunity"]["string"] + break + return (best and soo_present) if soo else best + + +def change_soo(pe, soo, vni): + soo_cmd_str = "mac-vrf soo " + if soo: + soo_cmd_str += soo + else: + soo_cmd_str = "no " + soo_cmd_str + pe.vtysh_cmd( + """ + configure terminal + router bgp 65000 + address-family l2vpn evpn + {} + """.format( + soo_cmd_str + ) + ) + bgp_l2vni = get_bgp_l2vni_fields(pe, vni) + l2vni_soo = bgp_l2vni[2] + return l2vni_soo == soo + + +def get_evpn_rt_json_str(vni, rd, oip=None, mac=None, ip=None): + "convert evpn route fields into a route string + global/l2vni cli syntax" + # type-3 + if oip: + rt_str = "[3]:[0]:[32]:[{}]".format(oip) + global_rt_cmd = "show bgp l2vpn evpn route rd {} type 3 json".format(rd) + l2vni_rt_cmd = "show bgp vni {} type 3 vtep {} json".format(vni, oip) + # type-2 + else: + rt_str = "[2]:[0]:[48]:[{}]".format(mac) + global_rt_cmd = "show bgp l2vpn evpn route rd {} type 2".format(rd) + l2vni_rt_cmd = "show bgp vni {} type 2 mac {}".format(vni, mac) + if ip: + ip_len = 128 if ":" in ip else 32 + rt_str += ":[{}]:[{}]".format(ip_len, ip) + l2vni_rt_cmd = "show bgp vni {} type 2 ip {}".format(vni, ip) + global_rt_cmd += " json" + l2vni_rt_cmd += " json" + return [rt_str, global_rt_cmd, l2vni_rt_cmd] + + +def get_evpn_rt_json(pe, vni, rd, oip=None, mac=None, ip=None): + "get json global/l2vni json blobs for the corresponding evpn route" + rt = get_evpn_rt_json_str(vni, rd, oip, mac, ip) + rt_str = rt.pop(0) + global_rt_cmd = rt.pop(0) + l2vni_rt_cmd = rt.pop(0) + logger.info( + "collecting global/l2vni evpn routes for pfx {} on {}".format(rt_str, pe.name) + ) + global_rt_json = pe.vtysh_cmd(global_rt_cmd, isjson=True) + logger.info("global evpn route for pfx {} on {}".format(rt_str, pe.name)) + logger.info(global_rt_json) + l2vni_rt_json = pe.vtysh_cmd(l2vni_rt_cmd, isjson=True) + logger.info("l2vni evpn route for pfx {} on {}".format(rt_str, pe.name)) + logger.info(l2vni_rt_json) + return [rt_str, global_rt_json, l2vni_rt_json] + + +def get_bgp_l2vni_fields(pe, vni): + bgp_vni_output = pe.vtysh_cmd( + "show bgp l2vpn evpn vni {} json".format(vni), isjson=True + ) + rd = bgp_vni_output["rd"] + oip = bgp_vni_output["originatorIp"] + soo = bgp_vni_output["siteOfOrigin"] + return [rd, oip, soo] + + +def rt_test(pe, vni, rd, oip, mac, ip, soo): + """ + Check installation status of a given route. + @pe = router where bgp routes are collected from + @vni = l2vni + @rd = rd of the route + @oip = originator-ip, set only for type-3 route + @mac = nlri mac, set only for type-2 + @ip = nlri ip, optionally set for type-2 + @soo = MAC-VRF SoO string, set if SoO needs to be + on the rt to be considered installed. + """ + rt = get_evpn_rt_json(pe, vni, rd, oip, mac, ip) + rt_str = rt.pop(0) + rt_global_json = rt.pop(0) + rt_l2vni_json = rt.pop(0) + + if ( + not rt_global_json + or rd not in rt_global_json + or rt_str not in rt_global_json[rd] + ): + global_installed = False + else: + global_json_paths = rt_global_json[rd][rt_str]["paths"] + global_installed = is_installed(global_json_paths, soo) + if not rt_l2vni_json: + l2vni_installed = False + else: + if not oip: + # json for RT2s in l2vni don't key by route string + l2vni_json_paths = rt_l2vni_json["paths"] + l2vni_installed = is_installed(l2vni_json_paths, soo) + elif rt_str in rt_l2vni_json and "paths" in rt_l2vni_json[rt_str]: + l2vni_json_paths = rt_l2vni_json[rt_str]["paths"] + l2vni_installed = is_installed(l2vni_json_paths, soo) + else: + l2vni_installed = False + return [global_installed, l2vni_installed] + + +def test_macvrf_soo(): + "Test MAC-VRF Site-of-Origin on pe1" + l2vni = 101 + l3vni = 404 + soo = "65000:0" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + host2 = tgen.gears["host2"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + + # Collect pe2 RD/Originator-IP + pe2_bgp_vni = get_bgp_l2vni_fields(pe2, l2vni) + pe2_rd = pe2_bgp_vni[0] + pe2_oip = pe2_bgp_vni[1] + # Collect local addrs + h2_mac = host2.run("ip -br link show host2-eth0").split()[2] + h2_ip = host2.run("ip -4 -br addr show host2-eth0").split()[2].split("/")[0] + pe2_mac = pe2.run("ip -br link show br101").split()[2] + pe2_ip = pe2.run("ip -4 -br addr show br101").split()[2].split("/")[0] + # Route fields + pe2_svi_parms = [l2vni, pe2_rd, None, pe2_mac, pe2_ip] + pe2_imet_parms = [l2vni, pe2_rd, pe2_oip, None, None] + host2_mac_parms = [l2vni, pe2_rd, None, h2_mac, None] + host2_neigh_parms = [l2vni, pe2_rd, None, h2_mac, h2_ip] + # Route strings + pe2_svi_rt_str, _, _ = get_evpn_rt_json_str(*pe2_svi_parms) + pe2_imet_rt_str, _, _ = get_evpn_rt_json_str(*pe2_imet_parms) + host2_mac_rt_str, _, _ = get_evpn_rt_json_str(*host2_mac_parms) + host2_neigh_rt_str, _, _ = get_evpn_rt_json_str(*host2_neigh_parms) + + ## trigger mac/arp learn + host1.run("ping -c1 10.10.1.1") + host2.run("ping -c1 10.10.1.3") + + step("Test pe2/host2 routes are installed on pe1 (global/l2vni)") + + # expected state: + # - global table: present w/o soo + # - l2vni table: present w/o soo + assertmsg = "{} missing on {} in {}{} evpn table(s)" + global_parms = [pe2.name, "global", ""] + l2vni_parms = [pe2.name, "l2vni", l2vni] + # pe2's type-2 for l2vni 101 svi mac/ip + test_f = partial(rt_test, pe2, *pe2_svi_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms) + # pe2's type-3 for l2vni 101 + test_f = partial(rt_test, pe2, *pe2_imet_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms) + # mac-only type-2 for host2 + test_f = partial(rt_test, pe1, *host2_mac_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms) + # mac+ip type-2 for host2 + test_f = partial(rt_test, pe1, *host2_neigh_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms) + + step("Add valid SoO config to pe2") + test_f = partial(change_soo, pe2, soo, l2vni) + _, res = topotest.run_and_expect(test_f, True, count=10, wait=1) + assertmsg = "soo '{}' not properly applied on {}".format(soo, pe2.name) + assert res == True, assertmsg + + step("Test valid config applied to L2VNI on pe2") + ## expected state: + ## - global table: present w/ soo + ## - l2vni table: present w/ soo + assertmsg = "{} not originated with soo {} by {} in {}{} evpn table(s)" + global_parms = [soo, pe2.name, "global", ""] + l2vni_parms = [soo, pe2.name, "l2vni", l2vni] + # type-2 for l2vni 101 svi mac/ip + test_f = partial(rt_test, pe2, *pe2_svi_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms) + # type-3 for l2vni 101 + test_f = partial(rt_test, pe2, *pe2_imet_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms) + + step("Test invalid SoO config on pe2") + test_f = partial(change_soo, pe2, "1:1:1", l2vni) + _, res = topotest.run_and_expect(test_f, False, count=10, wait=1) + assertmsg = "soo '1:1:1' should not have been allowed on {}".format(pe2.name) + assert res == False, assertmsg + + step("Test valid SoO applied to host2 routes (mac-only + mac/ip) on pe2") + + ## expected state: + ## - global table: present w/ soo + ## - l2vni table: present w/ soo + assertmsg = "{} not originated with soo {} by {} in {}{} evpn table(s)" + global_parms = [soo, pe1.name, "global", ""] + l2vni_parms = [soo, pe1.name, "l2vni", l2vni] + # mac-only type-2 for host2 + test_f = partial(rt_test, pe2, *host2_mac_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms) + # mac+ip type-2 for host2 + test_f = partial(rt_test, pe2, *host2_neigh_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms) + + step("Add valid SoO to pe1") + test_f = partial(change_soo, pe1, soo, l2vni) + _, res = topotest.run_and_expect(test_f, True, count=10, wait=1) + assertmsg = "soo '{}' not properly applied on {}".format(soo, pe1.name) + assert res == True, assertmsg + + step("Test pe2's routes are filtered from l2vni on pe1.") + ## expected state: + ## - global table: present w/ soo + ## - l2vni table: not present + global_assertmsg = "{} with soo {} from {} missing from global evpn table" + l2vni_assertmsg = "{} with soo {} from {} not filtered from {}{} evpn table" + global_parms = [soo, pe1.name, "global", ""] + l2vni_parms = [soo, pe1.name, "l2vni", l2vni] + # pe2's svi route + test_f = partial(rt_test, pe1, *pe2_svi_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1) + assert res[0] == True, global_assertmsg.format(pe2_svi_rt_str, *global_parms) + assert res[1] == False, l2vni_assertmsg.format(pe2_svi_rt_str, *l2vni_parms) + # pe2's imet route + test_f = partial(rt_test, pe1, *pe2_imet_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1) + assert res[0] == True, global_assertmsg.format(pe2_imet_rt_str, *global_parms) + assert res[1] == False, l2vni_assertmsg.format(pe2_imet_rt_str, *l2vni_parms) + # mac-only type-2 for host2 + test_f = partial(rt_test, pe1, *host2_mac_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1) + assert res[0] == True, global_assertmsg.format(host2_mac_rt_str, *global_parms) + assert res[1] == False, l2vni_assertmsg.format(host2_mac_rt_str, *l2vni_parms) + # mac+ip type-2 for host2 + test_f = partial(rt_test, pe1, *host2_neigh_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, False], count=30, wait=1) + assert res[0] == True, global_assertmsg.format(host2_neigh_rt_str, *global_parms) + assert res[1] == False, l2vni_assertmsg.format(host2_neigh_rt_str, *l2vni_parms) + + step("Remove SoO from pe1") + test_f = partial(change_soo, pe1, "", l2vni) + _, res = topotest.run_and_expect(test_f, True, count=10, wait=1) + assertmsg = "soo '{}' not properly removed from {}".format(soo, pe1.name) + assert res == True, assertmsg + + step("Test pe2/host2 routes are installed on pe1 (global/l2vni)") + ## expected state: + ## - global table: present w/ soo + ## - l2vni table: present w/ soo + assertmsg = "{} with soo {} missing on {} in {}{} evpn table" + global_parms = [soo, pe1.name, "global", ""] + l2vni_parms = [soo, pe1.name, "l2vni", l2vni] + # pe2's type-2 for l2vni 101 svi mac/ip + test_f = partial(rt_test, pe1, *pe2_svi_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms) + # pe2's type-3 for l2vni 101 + test_f = partial(rt_test, pe1, *pe2_imet_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms) + # mac-only type-2 for host2 + test_f = partial(rt_test, pe1, *host2_mac_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms) + # mac+ip type-2 for host2 + test_f = partial(rt_test, pe1, *host2_neigh_parms, soo) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms) + + step("Remove SoO from pe2") + test_f = partial(change_soo, pe2, "", l2vni) + _, res = topotest.run_and_expect(test_f, True, count=10, wait=1) + assertmsg = "soo '{}' not properly removed from {}".format(soo, pe2.name) + assert res == True, assertmsg + + step("Test pe2's 'self' routes are installed on pe1 (global/l2vni)") + ## expected state: + ## - global table: present w/o soo + ## - l2vni table: present w/o soo + assertmsg = "{} missing on {} in {}{} evpn table(s)" + global_parms = [pe1.name, "global", ""] + l2vni_parms = [pe1.name, "l2vni", l2vni] + # pe2's type-2 for l2vni 101 svi mac/ip + test_f = partial(rt_test, pe1, *pe2_svi_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_svi_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_svi_rt_str, *l2vni_parms) + # pe2's type-3 for l2vni 101 + test_f = partial(rt_test, pe1, *pe2_imet_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(pe2_imet_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(pe2_imet_rt_str, *l2vni_parms) + # mac-only type-2 for host2 + test_f = partial(rt_test, pe1, *host2_mac_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_mac_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_mac_rt_str, *l2vni_parms) + # mac+ip type-2 for host2 + test_f = partial(rt_test, pe1, *host2_neigh_parms, None) + _, res = topotest.run_and_expect(test_f, [True, True], count=30, wait=1) + assert res[0] == True, assertmsg.format(host2_neigh_rt_str, *global_parms) + assert res[1] == True, assertmsg.format(host2_neigh_rt_str, *l2vni_parms) + + # tgen.mininet_cli() + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/ospfd.conf new file mode 100644 index 0000000..2db7edb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/ospfd.conf @@ -0,0 +1,13 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.20.20.20/32 area 0 +! +int P1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int P1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/zebra.conf new file mode 100644 index 0000000..95b5da8 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/P1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.20.20.20/32 +interface P1-eth0 + ip address 10.20.1.2/24 +interface P1-eth1 + ip address 10.20.2.2/24 diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json new file mode 100644 index 0000000..0b5b1a1 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.after.json @@ -0,0 +1 @@ +{"27.3.0.85:3":{"rd":"27.3.0.85:3","[5]:[0]:[16]:[10.27.0.0]":{"prefix":"[5]:[0]:[16]:[10.27.0.0]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":16,"ip":"10.27.0.0","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]}},"30.0.0.3:2":{"rd":"30.0.0.3:2","[5]:[0]:[32]:[4.4.4.1]":{"prefix":"[5]:[0]:[32]:[4.4.4.1]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"internal","routeType":5,"ethTag":0,"ipLen":32,"ip":"4.4.4.1","metric":0,"locPrf":100,"weight":0,"peerId":"10.30.30.30","path":"","origin":"incomplete","extendedCommunity":{"string":"RT:65000:300 ET:8 Rmac:44:20:00:ff:ff:01"},"nexthops":[{"ip":"10.30.30.30","hostname":"PE2","afi":"ipv4","used":true}]}]]}},"numPrefix":2,"numPaths":2} diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json new file mode 100644 index 0000000..67417b5 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgp.evpn.route.prefix.before.json @@ -0,0 +1 @@ +{"27.3.0.85:3":{"rd":"27.3.0.85:3","[5]:[0]:[24]:[10.27.7.0]":{"prefix":"[5]:[0]:[24]:[10.27.7.0]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":24,"ip":"10.27.7.0","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]},"[5]:[0]:[30]:[10.27.7.8]":{"prefix":"[5]:[0]:[30]:[10.27.7.8]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":30,"ip":"10.27.7.8","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]},"[5]:[0]:[32]:[10.27.7.22]":{"prefix":"[5]:[0]:[32]:[10.27.7.22]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"external","routeType":5,"ethTag":0,"ipLen":32,"ip":"10.27.7.22","metric":0,"weight":32768,"peerId":"(unspec)","path":"","origin":"incomplete","extendedCommunity":{"string":"ET:8 RT:65000:4000 Rmac:44:00:00:ff:ff:01"},"nexthops":[{"ip":"10.10.10.10","hostname":"PE1","afi":"ipv4","used":true}]}]]}},"30.0.0.3:2":{"rd":"30.0.0.3:2","[5]:[0]:[32]:[4.4.4.1]":{"prefix":"[5]:[0]:[32]:[4.4.4.1]","prefixLen":352,"paths":[[{"valid":true,"bestpath":true,"selectionReason":"First path received","pathFrom":"internal","routeType":5,"ethTag":0,"ipLen":32,"ip":"4.4.4.1","metric":0,"locPrf":100,"weight":0,"peerId":"10.30.30.30","path":"","origin":"incomplete","extendedCommunity":{"string":"RT:65000:300 ET:8 Rmac:44:20:00:ff:ff:01"},"nexthops":[{"ip":"10.30.30.30","hostname":"PE2","afi":"ipv4","used":true}]}]]}},"numPrefix":4,"numPaths":4} diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf new file mode 100644 index 0000000..b58219a --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/bgpd.conf @@ -0,0 +1,32 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.10.10.10 + no bgp default ipv4-unicast + neighbor 10.30.30.30 remote-as 65000 + neighbor 10.30.30.30 update-source lo + neighbor 10.30.30.30 timers 3 10 + address-family l2vpn evpn + neighbor 10.30.30.30 activate + advertise-all-vni + advertise-svi-ip +! +router bgp 65000 vrf vrf-red + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + route-target import *:300 + route-target import auto + exit-address-family +! +router bgp 65000 vrf vrf-purple + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family + +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/evpn.vni.json new file mode 100644 index 0000000..45d00c6 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/evpn.vni.json @@ -0,0 +1,16 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"default", + "vxlanInterface":"vxlan0", + "vtepIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "remoteVteps":[ + { + "ip":"10.30.30.30", + "flood":"HER" + } + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/ospfd.conf new file mode 100644 index 0000000..f1c2b42 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.10.10.10/32 area 0 +! +int PE1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf new file mode 100644 index 0000000..cea60a8 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE1/zebra.conf @@ -0,0 +1,19 @@ +! +log file zebra.log +! +vrf vrf-red + vni 100 + exit-vrf +! +vrf vrf-purple + vni 4000 + ip route 10.27.7.0/24 blackhole + ip route 10.27.7.22/32 blackhole + ip route 10.27.7.10/30 blackhole +exit-vrf +! +interface lo + ip address 10.10.10.10/32 +interface PE1-eth1 + ip address 10.20.1.1/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/bgpd.conf new file mode 100644 index 0000000..10809da --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/bgpd.conf @@ -0,0 +1,24 @@ +! +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.30.30.30 + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as 65000 + neighbor 10.10.10.10 update-source lo + neighbor 10.10.10.10 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.10.10.10 activate + advertise-all-vni + advertise-svi-ip +! +router bgp 65000 vrf vrf-blue + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/evpn.vni.json new file mode 100644 index 0000000..f480b56 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/evpn.vni.json @@ -0,0 +1,15 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"default", + "vxlanInterface":"vxlan0", + "vtepIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "remoteVteps":[ + { + "ip":"10.10.10.10", + "flood":"HER" + } + ] +} diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/ospfd.conf new file mode 100644 index 0000000..065c993 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.30.30.30/32 area 0 +! +int PE2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/zebra.conf new file mode 100644 index 0000000..cee4355 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/PE2/zebra.conf @@ -0,0 +1,17 @@ +vrf vrf-blue + vni 300 + exit-vrf +! +vrf vrf-red + vni 100 + exit-vrf +! +interface lo + ip address 10.30.30.30/32 +interface PE2-eth0 + ip address 10.20.2.3/24 +! +interface vrf-blue + ip address 30.0.0.3/24 +! +ip route 4.4.4.1/32 30.0.0.100 vrf vrf-blue diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/__init__.py b/tests/topotests/bgp_evpn_vxlan_svd_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/zebra.conf new file mode 100644 index 0000000..91fae9e --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host1/zebra.conf @@ -0,0 +1,3 @@ +! +int host1-eth0 + ip address 10.10.1.55/24 diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/zebra.conf new file mode 100644 index 0000000..df9adeb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/host2/zebra.conf @@ -0,0 +1,3 @@ +! +interface host2-eth0 + ip address 10.10.1.56/24 diff --git a/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py new file mode 100755 index 0000000..fd75c34 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_svd_topo1/test_bgp_evpn_vxlan_svd.py @@ -0,0 +1,624 @@ +#!/usr/bin/env python + +# +# test_bgp_evpn_vxlan_svd.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 NVIDIA Corporation +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC and route signalling over BGP +using Single Vxlan Device Configurtion +""" + +import os +import sys +import json +from functools import partial +from time import sleep +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # + # Create routers + tgen.add_router("P1") + tgen.add_router("PE1") + tgen.add_router("PE2") + tgen.add_router("host1") + tgen.add_router("host2") + + # Host1-PE1 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["host1"]) + switch.add_link(tgen.gears["PE1"]) + + # PE1-P1 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["PE1"]) + switch.add_link(tgen.gears["P1"]) + + # P1-PE2 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["P1"]) + switch.add_link(tgen.gears["PE2"]) + + # PE2-host2 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["PE2"]) + switch.add_link(tgen.gears["host2"]) + + +def setup_pe_router(tgen, pe_name, tunnel_local_ip, svi_ip, intf): + pe = tgen.gears[pe_name] + + # configure vlan aware bridge + pe.run("ip link add name bridge type bridge stp_state 0") + pe.run("ip link set dev bridge type bridge vlan_filtering 1") + pe.run("bridge vlan add vid 1 dev bridge self") + if pe_name == "PE1": + pe.run("ip link set dev bridge address 44:00:00:ff:ff:01") + if pe_name == "PE2": + pe.run("ip link set dev bridge address 44:20:00:ff:ff:01") + pe.run("ip link set dev bridge up") + + # setup svi + pe.run("ip link add link bridge name vlan1 type vlan id 1 protocol 802.1q") + pe.run("ip link set dev vlan1 up") + pe.run("ip addr add {0} dev vlan1".format(svi_ip)) + pe.run("/sbin/sysctl net.ipv4.conf.vlan1.arp_accept=1") + + # setup single vxlan device + pe.run( + "ip link add dev vxlan0 type vxlan dstport 4789 local {0} nolearning external".format( + tunnel_local_ip + ) + ) + pe.run("ip link set dev vxlan0 master bridge") + pe.run("bridge link set dev vxlan0 vlan_tunnel on") + pe.run("bridge link set dev vxlan0 neigh_suppress on") + pe.run("bridge link set dev vxlan0 learning off") + pe.run("bridge vlan add dev vxlan0 vid 1") + pe.run("bridge vlan add dev vxlan0 vid 1 tunnel_info id 101") + pe.run("ip link set up dev vxlan0") + + # VRF creation + pe.run("ip link add vrf-purple type vrf table 1003") + pe.run("ip link set dev vrf-purple up") + if pe_name == "PE1": + pe.run("ip link add vrf-blue type vrf table 1002") + pe.run("ip link set dev vrf-blue up") + pe.run("ip addr add 27.2.0.85/32 dev vrf-blue") + pe.run("ip addr add 27.3.0.85/32 dev vrf-purple") + if pe_name == "PE2": + pe.run("ip link add vrf-blue type vrf table 2400") + pe.run("ip link set dev vrf-blue up") + + # setup PE interface + pe.run("ip link set dev {0}-{1} master bridge".format(pe_name, intf)) + pe.run("bridge vlan del vid 1 dev {0}-{1}".format(pe_name, intf)) + pe.run("bridge vlan del vid 1 untagged pvid dev {0}-{1}".format(pe_name, intf)) + pe.run("bridge vlan add vid 1 dev {0}-{1}".format(pe_name, intf)) + pe.run("bridge vlan add vid 1 pvid untagged dev {0}-{1}".format(pe_name, intf)) + + # L3VNI 100 + pe.run("ip link add vrf-red type vrf table 1400") + pe.run("ip link add link bridge name vlan100 type vlan id 100 protocol 802.1q") + pe.run("ip link set dev vlan100 master vrf-blue") + pe.run("ip link set dev vlan100 up") + pe.run("bridge vlan add vid 100 dev bridge self") + pe.run("bridge vlan add dev vxlan0 vid 100") + pe.run("bridge vlan add dev vxlan0 vid 100 tunnel_info id 100") + + # L3VNI 4000 + pe.run("ip link add link bridge name vlan400 type vlan id 400 protocol 802.1q") + pe.run("ip link set dev vlan400 master vrf-purple") + pe.run("ip link set dev vlan400 up") + pe.run("bridge vlan add vid 400 dev bridge self") + pe.run("bridge vlan add dev vxlan0 vid 400") + pe.run("bridge vlan add dev vxlan0 vid 400 tunnel_info id 4000") + + # add a vrf for testing DVNI + if pe_name == "PE2": + pe.run("ip link add link bridge name vlan300 type vlan id 300 protocol 802.1q") + pe.run("ip link set dev vlan300 master vrf-blue") + pe.run("ip link set dev vlan300 up") + pe.run("bridge vlan add vid 300 dev bridge self") + pe.run("bridge vlan add dev vxlan0 vid 300") + pe.run("bridge vlan add dev vxlan0 vid 300 tunnel_info id 300") + + +def setup_p_router(tgen, p_name): + p1 = tgen.gears[p_name] + p1.run("sysctl -w net.ipv4.ip_forward=1") + + +def setup_module(mod): + "Sets up the pytest environment" + + result = required_linux_kernel_version("5.7") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >= 5.7") + + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + setup_pe_router(tgen, "PE1", "10.10.10.10", "10.10.1.1/24", "eth0") + setup_pe_router(tgen, "PE2", "10.30.30.30", "10.10.1.3/24", "eth1") + setup_p_router(tgen, "P1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registred routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # tgen.mininet_cli() + # This function tears down the whole topology. + tgen.stop_topology() + + +def show_vni_json_elide_ifindex(pe, vni, expected): + output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True) + + if "ifindex" in output_json: + output_json.pop("ifindex") + + return topotest.json_cmp(output_json, expected) + + +def show_bgp_l2vpn_evpn_route_type_prefix_json(pe, expected): + output_json = pe.vtysh_cmd( + "show bgp l2vpn evpn route type prefix json", isjson=True + ) + + return topotest.json_cmp(output_json, expected) + + +def check_vni_macs_present(tgen, router, vni, maclist): + result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True) + for rname, ifname in maclist: + m = tgen.net.macs[(rname, ifname)] + if m not in result["macs"]: + return "MAC ({}) for interface {} on {} missing on {} from {}".format( + m, ifname, rname, router.name, json.dumps(result, indent=4) + ) + return None + + +def check_flood_entry_present(pe, vni, vtep): + if not topotest.iproute2_is_fdb_get_capable(): + return None + + output = pe.run( + "bridge fdb get 00:00:00:00:00:00 dev vxlan0 vni {} self".format(vni) + ) + + if str(vtep) not in output: + return output + + return None + + +def test_pe1_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + + # Let's ensure that the hosts have actually tried talking to + # each other. Otherwise under certain startup conditions + # they may not actually do any l2 arp'ing and as such + # the bridges won't know about the hosts on their networks + host1 = tgen.gears["host1"] + host1.run("ping -c 1 10.10.1.56") + host2 = tgen.gears["host2"] + host2.run("ping -c 1 10.10.1.55") + + test_func = partial( + check_vni_macs_present, + tgen, + pe1, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe1.name) + + vtep = "10.30.30.30" + test_func = partial(check_flood_entry_present, pe1, 101, vtep) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe1.name, vtep) + assert result is None, assertmsg + + +def test_pe2_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe2 = tgen.gears["PE2"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe2.name) + assert result is None, assertmsg + + test_func = partial( + check_vni_macs_present, + tgen, + pe2, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe2.name) + + vtep = "10.10.10.10" + test_func = partial(check_flood_entry_present, pe2, 101, vtep) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{}" Flood FDB Entry for VTEP {} not found'.format(pe2.name, vtep) + assert result is None, assertmsg + + +def mac_learn_test(host, local): + "check the host MAC gets learned by the VNI" + + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + + mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + mac_output_json = json.loads(mac_output) + assertmsg = "Local MAC output does not match interface mac {}".format(mac) + assert mac_output_json[mac]["type"] == "local", assertmsg + + +def mac_test_local_remote(local, remote): + "test MAC transfer between local and remote" + + local_output = local.vtysh_cmd("show evpn mac vni all json") + remote_output = remote.vtysh_cmd("show evpn mac vni all json") + local_output_vni = local.vtysh_cmd("show evpn vni detail json") + local_output_json = json.loads(local_output) + remote_output_json = json.loads(remote_output) + local_output_vni_json = json.loads(local_output_vni) + + # breakpoint() + for vni in local_output_json: + if vni not in remote_output_json: + continue + + mac_list = local_output_json[vni]["macs"] + for mac in mac_list: + if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101": + assertmsg = "JSON output mismatches local: {} remote: {}".format( + local_output_vni_json[0]["vtepIp"], + remote_output_json[vni]["macs"][mac]["remoteVtep"], + ) + assert ( + remote_output_json[vni]["macs"][mac]["remoteVtep"] + == local_output_vni_json[0]["vtepIp"] + ), assertmsg + + +def test_learning_pe1(): + "test MAC learning on PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + mac_learn_test(host1, pe1) + + +def test_learning_pe2(): + "test MAC learning on PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe2 = tgen.gears["PE2"] + mac_learn_test(host2, pe2) + + +def test_local_remote_mac_pe1(): + "Test MAC transfer PE1 local and PE2 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe1, pe2) + + +def test_local_remote_mac_pe2(): + "Test MAC transfer PE2 local and PE1 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe2, pe1) + + +def ip_learn_test(tgen, host, local, remote, ip_addr): + "check the host IP gets learned by the VNI" + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + # print(host_output) + + # check we have a local association between the MAC and IP + local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + # print(local_output) + local_output_json = json.loads(local_output) + mac_type = local_output_json[mac]["type"] + assertmsg = "Failed to learn local IP address on host {}".format(host.name) + assert local_output_json[mac]["neighbors"] != "none", assertmsg + learned_ip = local_output_json[mac]["neighbors"]["active"][0] + + assertmsg = "local learned mac wrong type: {} ".format(mac_type) + assert mac_type == "local", assertmsg + + assertmsg = ( + "learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + ) + assert ip_addr == learned_ip, assertmsg + + # now lets check the remote + count = 0 + converged = False + while count < 30: + remote_output = remote.vtysh_cmd( + "show evpn mac vni 101 mac {} json".format(mac) + ) + # print(remote_output) + remote_output_json = json.loads(remote_output) + type = remote_output_json[mac]["type"] + if not remote_output_json[mac]["neighbors"] == "none": + # due to a kernel quirk, learned IPs can be inactive + if ( + remote_output_json[mac]["neighbors"]["active"] + or remote_output_json[mac]["neighbors"]["inactive"] + ): + converged = True + break + count += 1 + sleep(1) + + # print("tries: {}".format(count)) + assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac) + # some debug for this failure + if not converged == True: + log_output = remote.run("cat zebra.log") + # print(log_output) + + assert converged == True, assertmsg + if remote_output_json[mac]["neighbors"]["active"]: + learned_ip = remote_output_json[mac]["neighbors"]["active"][0] + else: + learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0] + assertmsg = "remote learned mac wrong type: {} ".format(type) + assert type == "remote", assertmsg + + assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + assert ip_addr == learned_ip, assertmsg + + +def test_ip_pe1_learn(): + "run the IP learn test for PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe2.vtysh_cmd("debug zebra vxlan") + # pe2.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host1.run("ping -c1 10.10.1.1") + ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55") + # tgen.mininet_cli() + + +def test_ip_pe2_learn(): + "run the IP learn test for PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe1.vtysh_cmd("debug zebra vxlan") + # pe1.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host2.run("ping -c1 10.10.1.3") + ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56") + # tgen.mininet_cli() + + +def show_dvni_route(pe, vni, prefix, vrf): + output = pe.vtysh_cmd("show ip route vrf {} {}".format(vrf, prefix)) + + if str(vni) not in output: + return output + + output = pe.run("ip route show vrf {} {}".format(vrf, prefix)) + + if str(vni) not in output: + return output + + return None + + +def test_dvni(): + "test Downstream VNI works as expected importing into PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + + prefix = "4.4.4.1/32" + test_func = partial(show_dvni_route, pe1, 300, prefix, "vrf-red") + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{}" DVNI route {} not found'.format(pe1.name, prefix) + assert result is None, assertmsg + # tgen.mininet_cli() + + +def test_pe_advertise_aggr_evpn_route(): + "BGP advertise aggregated Type-5 prefix route" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking BGP EVPN route contains non-aggregate prefixes") + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/bgp.evpn.route.prefix.before.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_bgp_l2vpn_evpn_route_type_prefix_json, pe1, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + assert result is None, assertmsg + + logger.info("Configure BGP aggregate-address summary-only under ipv4-unicast") + pe1.cmd( + 'vtysh -c "config t" -c "router bgp 65000 vrf vrf-purple" ' + + ' -c "address-family ipv4 unicast" ' + + ' -c "aggregate-address 10.27.0.0/16 summary-only" ' + ) + + logger.info("Checking BGP EVPN route contains aggregated prefix") + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/bgp.evpn.route.prefix.after.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_bgp_l2vpn_evpn_route_type_prefix_json, pe1, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/P1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/P1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/P1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/P1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/P1/ospfd.conf new file mode 100644 index 0000000..2db7edb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/P1/ospfd.conf @@ -0,0 +1,13 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.20.20.20/32 area 0 +! +int P1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int P1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/P1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_topo1/P1/zebra.conf new file mode 100644 index 0000000..95b5da8 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/P1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.20.20.20/32 +interface P1-eth0 + ip address 10.20.1.2/24 +interface P1-eth1 + ip address 10.20.2.2/24 diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/bgpd.conf new file mode 100644 index 0000000..dbbfc82 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.10.10.10 + no bgp default ipv4-unicast + neighbor 10.30.30.30 remote-as 65000 + neighbor 10.30.30.30 update-source lo + neighbor 10.30.30.30 timers 3 10 + address-family l2vpn evpn + neighbor 10.30.30.30 activate + advertise-all-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE1/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/evpn.vni.json new file mode 100644 index 0000000..eb8668b --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/evpn.vni.json @@ -0,0 +1,17 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"default", + "vxlanInterface":"vxlan101", + "vtepIp":"10.10.10.10", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.30.30.30", + "flood":"HER" + } + ] +} + diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/ospfd.conf new file mode 100644 index 0000000..f1c2b42 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.10.10.10/32 area 0 +! +int PE1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/zebra.conf new file mode 100644 index 0000000..e269947 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE1/zebra.conf @@ -0,0 +1,8 @@ +! +log file zebra.log +! +interface lo + ip address 10.10.10.10/32 +interface PE1-eth1 + ip address 10.20.1.1/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/bgpd.conf new file mode 100644 index 0000000..52f8687 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65000 + timers bgp 3 9 + bgp router-id 10.30.30.30 + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as 65000 + neighbor 10.10.10.10 update-source lo + neighbor 10.10.10.10 timers 3 10 + ! + address-family l2vpn evpn + neighbor 10.10.10.10 activate + advertise-all-vni + advertise-svi-ip diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE2/evpn.vni.json b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/evpn.vni.json new file mode 100644 index 0000000..befb416 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/evpn.vni.json @@ -0,0 +1,16 @@ +{ + "vni":101, + "type":"L2", + "tenantVrf":"default", + "vxlanInterface":"vxlan101", + "vtepIp":"10.30.30.30", + "mcastGroup":"0.0.0.0", + "advertiseGatewayMacip":"No", + "numRemoteVteps":1, + "remoteVteps":[ + { + "ip":"10.10.10.10", + "flood":"HER" + } + ] +} diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/ospfd.conf new file mode 100644 index 0000000..065c993 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/ospfd.conf @@ -0,0 +1,9 @@ +! +router ospf + network 10.20.0.0/16 area 0 + network 10.30.30.30/32 area 0 +! +int PE2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/PE2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/zebra.conf new file mode 100644 index 0000000..9738916 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/PE2/zebra.conf @@ -0,0 +1,6 @@ +! +interface lo + ip address 10.30.30.30/32 +interface PE2-eth0 + ip address 10.20.2.3/24 +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/__init__.py b/tests/topotests/bgp_evpn_vxlan_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host1/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host1/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host1/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host1/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host1/zebra.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host1/zebra.conf new file mode 100644 index 0000000..91fae9e --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host1/zebra.conf @@ -0,0 +1,3 @@ +! +int host1-eth0 + ip address 10.10.1.55/24 diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host2/bgpd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host2/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host2/ospfd.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host2/ospfd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host2/ospfd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/host2/zebra.conf b/tests/topotests/bgp_evpn_vxlan_topo1/host2/zebra.conf new file mode 100644 index 0000000..df9adeb --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/host2/zebra.conf @@ -0,0 +1,3 @@ +! +interface host2-eth0 + ip address 10.10.1.56/24 diff --git a/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py new file mode 100755 index 0000000..2884043 --- /dev/null +++ b/tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_evpn_vxlan.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_evpn_vxlan.py: Test VXLAN EVPN MAC a route signalling over BGP. +""" + +import os +import sys +import json +from functools import partial +from time import sleep +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # + # Create routers + tgen.add_router("P1") + tgen.add_router("PE1") + tgen.add_router("PE2") + tgen.add_router("host1") + tgen.add_router("host2") + + # Host1-PE1 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["host1"]) + switch.add_link(tgen.gears["PE1"]) + + # PE1-P1 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["PE1"]) + switch.add_link(tgen.gears["P1"]) + + # P1-PE2 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["P1"]) + switch.add_link(tgen.gears["PE2"]) + + # PE2-host2 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["PE2"]) + switch.add_link(tgen.gears["host2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + p1 = tgen.gears["P1"] + + # set up PE bridges with the EVPN member interfaces facing the CE hosts + pe1.run("ip link add name br101 type bridge stp_state 0") + pe1.run("ip addr add 10.10.1.1/24 dev br101") + pe1.run("ip link set dev br101 up") + pe1.run( + "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.10.10.10 nolearning" + ) + pe1.run("ip link set dev vxlan101 master br101") + pe1.run("ip link set up dev vxlan101") + pe1.run("ip link set dev PE1-eth0 master br101") + + pe2.run("ip link add name br101 type bridge stp_state 0") + pe2.run("ip addr add 10.10.1.3/24 dev br101") + pe2.run("ip link set dev br101 up") + pe2.run( + "ip link add vxlan101 type vxlan id 101 dstport 4789 local 10.30.30.30 nolearning" + ) + pe2.run("ip link set dev vxlan101 master br101") + pe2.run("ip link set up dev vxlan101") + pe2.run("ip link set dev PE2-eth1 master br101") + p1.run("sysctl -w net.ipv4.ip_forward=1") + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def show_vni_json_elide_ifindex(pe, vni, expected): + output_json = pe.vtysh_cmd("show evpn vni {} json".format(vni), isjson=True) + if "ifindex" in output_json: + output_json.pop("ifindex") + + return topotest.json_cmp(output_json, expected) + + +def check_vni_macs_present(tgen, router, vni, maclist): + result = router.vtysh_cmd("show evpn mac vni {} json".format(vni), isjson=True) + for rname, ifname in maclist: + m = tgen.net.macs[(rname, ifname)] + if m not in result["macs"]: + return "MAC ({}) for interface {} on {} missing on {} from {}".format( + m, ifname, rname, router.name, json.dumps(result, indent=4) + ) + return None + + +def test_pe1_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe1.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe1, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe1.name) + + # Let's ensure that the hosts have actually tried talking to + # each other. Otherwise under certain startup conditions + # they may not actually do any l2 arp'ing and as such + # the bridges won't know about the hosts on their networks + host1 = tgen.gears["host1"] + host1.run("ping -c 1 10.10.1.56") + host2 = tgen.gears["host2"] + host2.run("ping -c 1 10.10.1.55") + + test_func = partial( + check_vni_macs_present, + tgen, + pe1, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe1.name) + + +def test_pe2_converge_evpn(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe2 = tgen.gears["PE2"] + json_file = "{}/{}/evpn.vni.json".format(CWD, pe2.name) + expected = json.loads(open(json_file).read()) + + test_func = partial(show_vni_json_elide_ifindex, pe2, 101, expected) + _, result = topotest.run_and_expect(test_func, None, count=45, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(pe2.name) + assert result is None, assertmsg + + test_func = partial( + check_vni_macs_present, + tgen, + pe2, + 101, + (("host1", "host1-eth0"), ("host2", "host2-eth0")), + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + if result: + logger.warning("%s", result) + assert None, '"{}" missing expected MACs'.format(pe2.name) + + +def mac_learn_test(host, local): + "check the host MAC gets learned by the VNI" + + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + + mac_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + mac_output_json = json.loads(mac_output) + assertmsg = "Local MAC output does not match interface mac {}".format(mac) + assert mac_output_json[mac]["type"] == "local", assertmsg + + +def mac_test_local_remote(local, remote): + "test MAC transfer between local and remote" + + local_output = local.vtysh_cmd("show evpn mac vni all json") + remote_output = remote.vtysh_cmd("show evpn mac vni all json") + local_output_vni = local.vtysh_cmd("show evpn vni detail json") + local_output_json = json.loads(local_output) + remote_output_json = json.loads(remote_output) + local_output_vni_json = json.loads(local_output_vni) + + for vni in local_output_json: + mac_list = local_output_json[vni]["macs"] + for mac in mac_list: + if mac_list[mac]["type"] == "local" and mac_list[mac]["intf"] != "br101": + assertmsg = "JSON output mismatches local: {} remote: {}".format( + local_output_vni_json[0]["vtepIp"], + remote_output_json[vni]["macs"][mac]["remoteVtep"], + ) + assert ( + remote_output_json[vni]["macs"][mac]["remoteVtep"] + == local_output_vni_json[0]["vtepIp"] + ), assertmsg + + +def test_learning_pe1(): + "test MAC learning on PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + mac_learn_test(host1, pe1) + + +def test_learning_pe2(): + "test MAC learning on PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe2 = tgen.gears["PE2"] + mac_learn_test(host2, pe2) + + +def test_local_remote_mac_pe1(): + "Test MAC transfer PE1 local and PE2 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe1, pe2) + + +def test_local_remote_mac_pe2(): + "Test MAC transfer PE2 local and PE1 remote" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + mac_test_local_remote(pe2, pe1) + + # Memory leak test template + + +def ip_learn_test(tgen, host, local, remote, ip_addr): + "check the host IP gets learned by the VNI" + host_output = host.vtysh_cmd("show interface {}-eth0".format(host.name)) + int_lines = host_output.splitlines() + for line in int_lines: + line_items = line.split(": ") + if "HWaddr" in line_items[0]: + mac = line_items[1] + break + print(host_output) + + # check we have a local association between the MAC and IP + local_output = local.vtysh_cmd("show evpn mac vni 101 mac {} json".format(mac)) + print(local_output) + local_output_json = json.loads(local_output) + mac_type = local_output_json[mac]["type"] + assertmsg = "Failed to learn local IP address on host {}".format(host.name) + assert local_output_json[mac]["neighbors"] != "none", assertmsg + learned_ip = local_output_json[mac]["neighbors"]["active"][0] + + assertmsg = "local learned mac wrong type: {} ".format(mac_type) + assert mac_type == "local", assertmsg + + assertmsg = ( + "learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + ) + assert ip_addr == learned_ip, assertmsg + + # now lets check the remote + count = 0 + converged = False + while count < 30: + remote_output = remote.vtysh_cmd( + "show evpn mac vni 101 mac {} json".format(mac) + ) + print(remote_output) + remote_output_json = json.loads(remote_output) + type = remote_output_json[mac]["type"] + if not remote_output_json[mac]["neighbors"] == "none": + # due to a kernel quirk, learned IPs can be inactive + if ( + remote_output_json[mac]["neighbors"]["active"] + or remote_output_json[mac]["neighbors"]["inactive"] + ): + converged = True + break + count += 1 + sleep(1) + + print("tries: {}".format(count)) + assertmsg = "{} remote learned mac no address: {} ".format(host.name, mac) + # some debug for this failure + if not converged == True: + log_output = remote.run("cat zebra.log") + print(log_output) + + assert converged == True, assertmsg + if remote_output_json[mac]["neighbors"]["active"]: + learned_ip = remote_output_json[mac]["neighbors"]["active"][0] + else: + learned_ip = remote_output_json[mac]["neighbors"]["inactive"][0] + assertmsg = "remote learned mac wrong type: {} ".format(type) + assert type == "remote", assertmsg + + assertmsg = "remote learned address mismatch with configured address host: {} learned: {}".format( + ip_addr, learned_ip + ) + assert ip_addr == learned_ip, assertmsg + + +def test_ip_pe1_learn(): + "run the IP learn test for PE1" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host1 = tgen.gears["host1"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe2.vtysh_cmd("debug zebra vxlan") + # pe2.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host1.run("ping -c1 10.10.1.1") + ip_learn_test(tgen, host1, pe1, pe2, "10.10.1.55") + # tgen.mininet_cli() + + +def test_ip_pe2_learn(): + "run the IP learn test for PE2" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + host2 = tgen.gears["host2"] + pe1 = tgen.gears["PE1"] + pe2 = tgen.gears["PE2"] + # pe1.vtysh_cmd("debug zebra vxlan") + # pe1.vtysh_cmd("debug zebra kernel") + # lets populate that arp cache + host2.run("ping -c1 10.10.1.3") + ip_learn_test(tgen, host2, pe2, pe1, "10.10.1.56") + # tgen.mininet_cli() + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_extcomm_list_delete/__init__.py b/tests/topotests/bgp_extcomm_list_delete/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_extcomm_list_delete/r1/bgpd.conf b/tests/topotests/bgp_extcomm_list_delete/r1/bgpd.conf new file mode 100644 index 0000000..3394c1c --- /dev/null +++ b/tests/topotests/bgp_extcomm_list_delete/r1/bgpd.conf @@ -0,0 +1,20 @@ +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.255.2 remote-as 65001 + address-family ipv4 unicast + network 10.10.10.1/32 route-map r2-out-rt + network 10.10.10.2/32 route-map r2-out-soo + network 10.10.10.3/32 route-map r2-out-nt + redistribute connected + exit-address-family +! +route-map r2-out-rt permit 10 + set extcommunity rt 1.1.1.1:1 2.2.2.2:2 3.3.3.3:3 4.4.4.4:4 +! +route-map r2-out-soo permit 20 + set extcommunity soo 1.1.1.1:1 2.2.2.2:2 3.3.3.3:3 4.4.4.4:4 +! +route-map r2-out-nt permit 30 + set extcommunity nt 192.168.255.2:0 2.2.2.2:0 3.3.3.3:0 4.4.4.4:0 +! diff --git a/tests/topotests/bgp_extcomm_list_delete/r1/zebra.conf b/tests/topotests/bgp_extcomm_list_delete/r1/zebra.conf new file mode 100644 index 0000000..e2c399e --- /dev/null +++ b/tests/topotests/bgp_extcomm_list_delete/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_extcomm_list_delete/r2/bgpd.conf b/tests/topotests/bgp_extcomm_list_delete/r2/bgpd.conf new file mode 100644 index 0000000..ca497e6 --- /dev/null +++ b/tests/topotests/bgp_extcomm_list_delete/r2/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 + neighbor 192.168.255.1 route-map r1-in in + exit-address-family +! +route-map r1-in permit 10 +! diff --git a/tests/topotests/bgp_extcomm_list_delete/r2/zebra.conf b/tests/topotests/bgp_extcomm_list_delete/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_extcomm_list_delete/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_extcomm_list_delete/test_bgp_extcomm-list_delete.py b/tests/topotests/bgp_extcomm_list_delete/test_bgp_extcomm-list_delete.py new file mode 100644 index 0000000..a5e5bdc --- /dev/null +++ b/tests/topotests/bgp_extcomm_list_delete/test_bgp_extcomm-list_delete.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright 2023 6WIND S.A. +# Authored by Farid Mihoub +# + +""" +bgp_extcomm_list-delete.py: + +Test the following commands: +route-map test permit 10 + set extended-comm-list delete +""" + +import functools +import json +import os +import pytest +import re +import sys + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib import topotest + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + tgen = get_topogen() + r2 = tgen.gears["r2"] + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": { + "acceptedPrefixCounter": 4, + } + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge initially" + + +def _set_extcomm_list(gear, ecom_t, ecom): + "Set the extended community for deletion." + cmd = [ + "con t\n", + f"bgp extcommunity-list standard r1-{ecom_t} permit {ecom_t} {ecom}\n", + f"route-map r1-in permit 10\n", + f"set extended-comm-list r1-{ecom_t} delete\n", + ] + gear.vtysh_cmd("".join(cmd)) + + +def _bgp_extcomm_list_del_check(gear, prefix, ecom): + """ + Check the non-presense of the extended community for the given prefix. + """ + # get the extended community list attribute for the given prefix + output = json.loads(gear.vtysh_cmd(f"show ip bgp {prefix} json")) + ecoms = output.get("paths", [])[0].get("extendedCommunity", {}) + ecoms = ecoms.get("string") + + # ecoms might be None at the first time + if not ecoms: + return False + return re.search(ecom, ecoms) is None + + +def test_rt_extcomm_list_delete(): + tgen = get_topogen() + r2 = tgen.gears["r2"] + + # set the extended community for deletion + _set_extcomm_list(r2, "rt", "1.1.1.1:1") + + # check for the deletion of the extended community + test_func = functools.partial( + _bgp_extcomm_list_del_check, r2, "10.10.10.1/32", r"1.1.1.1:1" + ) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "RT extended community 1.1.1.1:1 was not stripped." + + +def test_soo_extcomm_list_delete(): + tgen = get_topogen() + r2 = tgen.gears["r2"] + + # set the extended community for deletion + _set_extcomm_list(r2, "soo", "2.2.2.2:2") + + # check for the deletion of the extended community + test_func = functools.partial( + _bgp_extcomm_list_del_check, r2, "10.10.10.2/32", r"2.2.2.2:2" + ) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "SoO extended community 2.2.2.2:2 was not stripped." + + +def test_nt_extcomm_list_delete(): + tgen = get_topogen() + r2 = tgen.gears["r2"] + + # set the extended community for deletion + _set_extcomm_list(r2, "nt", "3.3.3.3:0") + + # check for the deletion of the extended community + test_func = functools.partial( + _bgp_extcomm_list_del_check, r2, "10.10.10.3/32", r"3.3.3.3" + ) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "NT extended community 3.3.3.3:0 was not stripped." + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_extended_link_bandwidth/__init__.py b/tests/topotests/bgp_extended_link_bandwidth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_extended_link_bandwidth/r1/frr.conf b/tests/topotests/bgp_extended_link_bandwidth/r1/frr.conf new file mode 100644 index 0000000..d0c0813 --- /dev/null +++ b/tests/topotests/bgp_extended_link_bandwidth/r1/frr.conf @@ -0,0 +1,32 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as internal + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 extended-link-bandwidth + address-family ipv4 unicast + network 10.10.10.40/32 + network 10.10.10.100/32 + network 10.10.10.200/32 + neighbor 192.168.1.2 route-map r2 out + exit-address-family +! +ip prefix-list p40 seq 5 permit 10.10.10.40/32 +ip prefix-list p100 seq 5 permit 10.10.10.100/32 +ip prefix-list p200 seq 5 permit 10.10.10.200/32 +! +route-map r2 permit 10 + match ip address prefix-list p40 + set extcommunity bandwidth 40000 +route-map r2 permit 20 + match ip address prefix-list p100 + set extcommunity bandwidth 100000 +route-map r2 permit 30 + match ip address prefix-list p200 + set extcommunity bandwidth 200000 +exit diff --git a/tests/topotests/bgp_extended_link_bandwidth/r2/frr.conf b/tests/topotests/bgp_extended_link_bandwidth/r2/frr.conf new file mode 100644 index 0000000..5cad150 --- /dev/null +++ b/tests/topotests/bgp_extended_link_bandwidth/r2/frr.conf @@ -0,0 +1,10 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as internal + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_extended_link_bandwidth/test_bgp_extended_link_bandwidth.py b/tests/topotests/bgp_extended_link_bandwidth/test_bgp_extended_link_bandwidth.py new file mode 100644 index 0000000..e7058f5 --- /dev/null +++ b/tests/topotests/bgp_extended_link_bandwidth/test_bgp_extended_link_bandwidth.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2024 by +# Donatas Abraitis +# + +import os +import re +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_role(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "10.10.10.40/32": { + "paths": [ + { + "extendedIpv6Community": { + "string": "LB:65000:5000000000 (40.000 Gbps)", + } + } + ] + }, + "10.10.10.100/32": { + "paths": [ + { + "extendedIpv6Community": { + "string": "LB:65000:12500000000 (100.000 Gbps)", + } + } + ] + }, + "10.10.10.200/32": { + "paths": [ + { + "extendedIpv6Community": { + "string": "LB:65000:25000000000 (200.000 Gbps)", + } + } + ] + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't see link bandwidths as expected" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_extended_optional_parameters_length/__init__.py b/tests/topotests/bgp_extended_optional_parameters_length/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_extended_optional_parameters_length/r1/bgpd.conf b/tests/topotests/bgp_extended_optional_parameters_length/r1/bgpd.conf new file mode 100644 index 0000000..d83013c --- /dev/null +++ b/tests/topotests/bgp_extended_optional_parameters_length/r1/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 extended-optional-parameters +! diff --git a/tests/topotests/bgp_extended_optional_parameters_length/r1/zebra.conf b/tests/topotests/bgp_extended_optional_parameters_length/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_extended_optional_parameters_length/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_extended_optional_parameters_length/r2/bgpd.conf b/tests/topotests/bgp_extended_optional_parameters_length/r2/bgpd.conf new file mode 100644 index 0000000..e390d6e --- /dev/null +++ b/tests/topotests/bgp_extended_optional_parameters_length/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 extended-optional-parameters + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_extended_optional_parameters_length/r2/zebra.conf b/tests/topotests/bgp_extended_optional_parameters_length/r2/zebra.conf new file mode 100644 index 0000000..dc15cf7 --- /dev/null +++ b/tests/topotests/bgp_extended_optional_parameters_length/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.1/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_extended_optional_parameters_length/test_bgp_extended_optional_parameters_length.py b/tests/topotests/bgp_extended_optional_parameters_length/test_bgp_extended_optional_parameters_length.py new file mode 100644 index 0000000..a5db20e --- /dev/null +++ b/tests/topotests/bgp_extended_optional_parameters_length/test_bgp_extended_optional_parameters_length.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if Extended Optional Parameters Length encoding format works +if forced with a knob. +https://datatracker.ietf.org/doc/html/rfc9072 +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_extended_optional_parameters_length(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast summary json")) + expected = { + "peers": { + "192.168.1.2": { + "pfxRcd": 2, + "pfxSnt": 2, + "state": "Established", + "peerState": "OK", + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge with extended-optional-parameters" + + def _bgp_extended_optional_parameters_length(router): + output = json.loads(router.vtysh_cmd("show bgp neighbor 192.168.1.2 json")) + expected = {"192.168.1.2": {"extendedOptionalParametersLength": True}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_extended_optional_parameters_length, router) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't see Extended Optional Parameters Length to be used" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_features/r1/bgp_delayopen_neighbor.json b/tests/topotests/bgp_features/r1/bgp_delayopen_neighbor.json new file mode 100644 index 0000000..5caaeab --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgp_delayopen_neighbor.json @@ -0,0 +1,6 @@ +{ + "192.168.101.2":{ + "remoteAs":65100, + "bgpTimerDelayOpenTimeMsecs":240000 + } +} diff --git a/tests/topotests/bgp_features/r1/bgp_delayopen_summary_established.json b/tests/topotests/bgp_features/r1/bgp_delayopen_summary_established.json new file mode 100644 index 0000000..3ab3588 --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgp_delayopen_summary_established.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65000, + "peers":{ + "192.168.101.2":{ + "remoteAs":65100, + "state":"Established" + } + } +} +} diff --git a/tests/topotests/bgp_features/r1/bgp_delayopen_summary_shutdown.json b/tests/topotests/bgp_features/r1/bgp_delayopen_summary_shutdown.json new file mode 100644 index 0000000..9a41236 --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgp_delayopen_summary_shutdown.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65000, + "peers":{ + "192.168.101.2":{ + "remoteAs":65100, + "state":"Idle (Admin)" + } + } +} +} diff --git a/tests/topotests/bgp_features/r1/bgp_shutdown_summary.json b/tests/topotests/bgp_features/r1/bgp_shutdown_summary.json new file mode 100644 index 0000000..d986071 --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgp_shutdown_summary.json @@ -0,0 +1,19 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.0.1", + "as":65000, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.168.0.2":{ + "remoteAs":65000, + "state":"Idle (Admin)" + }, + "192.168.101.2":{ + "remoteAs":65100, + "state":"Idle (Admin)" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_features/r1/bgp_summary.json b/tests/topotests/bgp_features/r1/bgp_summary.json new file mode 100644 index 0000000..1ad7342 --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgp_summary.json @@ -0,0 +1,27 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.0.1", + "as":65000, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.168.0.2":{ + "hostname":"r2", + "remoteAs":65000, + "outq":0, + "inq":0, + "pfxRcd":7, + "state":"Established" + }, + "192.168.101.2":{ + "hostname":"r4", + "remoteAs":65100, + "outq":0, + "inq":0, + "pfxRcd":3, + "state":"Established" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_features/r1/bgpd.conf b/tests/topotests/bgp_features/r1/bgpd.conf new file mode 100644 index 0000000..74d1993 --- /dev/null +++ b/tests/topotests/bgp_features/r1/bgpd.conf @@ -0,0 +1,41 @@ +! +hostname r1 +log file bgpd.log +! +router bgp 65000 + timers bgp 3 10 + coalesce-time 0 + bgp router-id 192.168.0.1 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65000 + neighbor 192.168.0.2 timers 3 10 + neighbor 192.168.0.2 description Router R2 (iBGP) + neighbor 192.168.0.2 update-source lo + neighbor 192.168.0.2 timers connect 5 + neighbor 192.168.101.2 remote-as 65100 + neighbor 192.168.101.2 timers 3 10 + neighbor 192.168.101.2 description Router R4 (eBGP AS 65100) + neighbor 192.168.101.2 timers connect 5 + ! + address-family ipv4 unicast + network 192.168.0.0/24 + network 192.168.1.0/24 + network 192.168.2.0/24 + network 192.168.3.0/24 + network 192.168.6.0/24 + network 192.168.8.0/24 + neighbor 192.168.101.2 route-map testmap-in in + neighbor 192.168.101.2 route-map testmap-out out + exit-address-family +! +! +! +route-map testmap-in permit 999 +! +route-map testmap-out permit 999 +! +! +line vty +! + diff --git a/tests/topotests/bgp_features/r1/ip_route.json b/tests/topotests/bgp_features/r1/ip_route.json new file mode 100644 index 0000000..34c5803 --- /dev/null +++ b/tests/topotests/bgp_features/r1/ip_route.json @@ -0,0 +1,364 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.101.2", + "afi":"ipv4", + "interfaceName":"r1-eth3", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.0.1\/32":[ + { + "prefix":"192.168.0.1\/32", + "protocol":"ospf", + "distance":110, + "metric":0, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"lo", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.0.1\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"lo", + "active":true + } + ] + } + ], + "192.168.0.2\/32":[ + { + "prefix":"192.168.0.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":10, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.0.3\/32":[ + { + "prefix":"192.168.0.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":10, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "table":254, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + }, + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.3.0\/24":[ + { + "prefix":"192.168.3.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.3.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth2", + "active":true + } + ] + } + ], + "192.168.6.0\/24":[ + { + "prefix":"192.168.6.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.6.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + } + ], + "192.168.7.0\/24":[ + { + "prefix":"192.168.7.0\/24", + "protocol":"bgp", + "distance":200, + "metric":0, + "nexthops":[ + { + "flags":5, + "ip":"192.168.0.2", + "afi":"ipv4", + "active":true, + "recursive":true, + "weight":1 + }, + { + "flags":1, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.7.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.8.0\/24":[ + { + "prefix":"192.168.8.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.101.0\/24":[ + { + "prefix":"192.168.101.0\/24", + "protocol":"bgp", + "distance":20, + "metric":0, + "nexthops":[ + { + "flags":0, + "ip":"192.168.101.2", + "afi":"ipv4", + "weight":1 + } + ] + }, + { + "prefix":"192.168.101.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth3", + "active":true + } + ] + } + ], + "192.168.102.0\/24":[ + { + "prefix":"192.168.102.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.101.2", + "afi":"ipv4", + "interfaceName":"r1-eth3", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_features/r1/ip_route_norib.json b/tests/topotests/bgp_features/r1/ip_route_norib.json new file mode 100644 index 0000000..f6536d1 --- /dev/null +++ b/tests/topotests/bgp_features/r1/ip_route_norib.json @@ -0,0 +1,281 @@ +{ + "192.168.0.1\/32":[ + { + "prefix":"192.168.0.1\/32", + "protocol":"ospf", + "distance":110, + "metric":0, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"lo", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.0.1\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"lo", + "active":true + } + ] + } + ], + "192.168.0.2\/32":[ + { + "prefix":"192.168.0.2\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":10, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.0.3\/32":[ + { + "prefix":"192.168.0.3\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":10, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + }, + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.3.0\/24":[ + { + "prefix":"192.168.3.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.3.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth2", + "active":true + } + ] + } + ], + "192.168.6.0\/24":[ + { + "prefix":"192.168.6.0\/24", + "protocol":"ospf", + "distance":110, + "metric":10, + "nexthops":[ + { + "flags":1, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + }, + { + "prefix":"192.168.6.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + } + ], + "192.168.7.0\/24":[ + { + "prefix":"192.168.7.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.8.0\/24":[ + { + "prefix":"192.168.8.0\/24", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth2", + "active":true, + "weight":1 + } + ] + } + ], + "192.168.101.0\/24":[ + { + "prefix":"192.168.101.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth3", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_features/r1/ospf6d.conf b/tests/topotests/bgp_features/r1/ospf6d.conf new file mode 100644 index 0000000..3e6196e --- /dev/null +++ b/tests/topotests/bgp_features/r1/ospf6d.conf @@ -0,0 +1,21 @@ +log file ospf6d.log +! +! debug ospf6 neighbor +! +interface r1-lo + ipv6 ospf6 area 0.0.0.0 +! +interface r1-eth1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 10 +! +interface r1-eth2 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 10 +! +router ospf6 + ospf6 router-id 192.168.0.1 + log-adjacency-changes +! +line vty +! diff --git a/tests/topotests/bgp_features/r1/ospf_neighbor.json b/tests/topotests/bgp_features/r1/ospf_neighbor.json new file mode 100644 index 0000000..caf700d --- /dev/null +++ b/tests/topotests/bgp_features/r1/ospf_neighbor.json @@ -0,0 +1,16 @@ +{ + "neighbors":{ + "192.168.0.2":[ + { + "nbrPriority":5, + "converged":"Full" + } + ], + "192.168.0.3":[ + { + "nbrPriority":5, + "converged":"Full" + } + ] + } +} diff --git a/tests/topotests/bgp_features/r1/ospfd.conf b/tests/topotests/bgp_features/r1/ospfd.conf new file mode 100644 index 0000000..aef017f --- /dev/null +++ b/tests/topotests/bgp_features/r1/ospfd.conf @@ -0,0 +1,26 @@ +log file ospfd.log +! +! debug ospf event +! debug ospf zebra +! +interface r1-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +interface r1-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +router ospf + ospf router-id 192.168.0.1 + log-adjacency-changes + network 192.168.0.0/20 area 0.0.0.0 + timers throttle spf 0 0 0 + timers lsa min-arrival 10 + timers throttle lsa all 0 + refresh timer 10 +! +line vty +! diff --git a/tests/topotests/bgp_features/r1/show_bgp.json b/tests/topotests/bgp_features/r1/show_bgp.json new file mode 100644 index 0000000..45e0e17 --- /dev/null +++ b/tests/topotests/bgp_features/r1/show_bgp.json @@ -0,0 +1,350 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.1", + "defaultLocPrf": 100, + "localAS": 65000, + "routes": { "0.0.0.0/0": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"0.0.0.0", + "prefixLen":0, + "network":"0.0.0.0\/0", + "weight":0, + "peerId":"192.168.101.2", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r4", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.0.0/24": [ + { + "pathFrom":"external", + "prefix":"192.168.0.0", + "prefixLen":24, + "network":"192.168.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.1.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.1.0", + "prefixLen":24, + "network":"192.168.1.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.1.0", + "prefixLen":24, + "network":"192.168.1.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.2.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.2.0", + "prefixLen":24, + "network":"192.168.2.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.2.0", + "prefixLen":24, + "network":"192.168.2.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.3.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.3.0", + "prefixLen":24, + "network":"192.168.3.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.3.0", + "prefixLen":24, + "network":"192.168.3.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.6.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.6.0", + "prefixLen":24, + "network":"192.168.6.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.7.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"internal", + "prefix":"192.168.7.0", + "prefixLen":24, + "network":"192.168.7.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.8.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.8.0", + "prefixLen":24, + "network":"192.168.8.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.8.0", + "prefixLen":24, + "network":"192.168.8.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.101.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.101.0", + "prefixLen":24, + "network":"192.168.101.0\/24", + "metric":0, + "weight":0, + "peerId":"192.168.101.2", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r4", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.102.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.102.0", + "prefixLen":24, + "network":"192.168.102.0\/24", + "metric":0, + "weight":0, + "peerId":"192.168.101.2", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r4", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.201.0/24": [ + { + "pathFrom":"internal", + "prefix":"192.168.201.0", + "prefixLen":24, + "network":"192.168.201.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"65200", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.201.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.202.0/24": [ + { + "pathFrom":"internal", + "prefix":"192.168.202.0", + "prefixLen":24, + "network":"192.168.202.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.2", + "path":"65200", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.201.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_features/r1/show_bgp_metric_test.json b/tests/topotests/bgp_features/r1/show_bgp_metric_test.json new file mode 100644 index 0000000..1720572 --- /dev/null +++ b/tests/topotests/bgp_features/r1/show_bgp_metric_test.json @@ -0,0 +1,57 @@ +{ + "routerId": "192.168.0.1", + "routes": { + "192.168.1.0/24": [ + { + "valid":true, + "prefix":"192.168.1.0", + "prefixLen":24, + "metric":11, + "nexthops":[ + { + "ip":"192.168.0.2", + "used":true + } + ] + }, + { + "valid":true, + "prefix":"192.168.1.0", + "prefixLen":24, + "metric":0, + "nexthops":[ + { + "ip":"0.0.0.0", + "used":true + } + ] + } +],"192.168.101.0/24": [ + { + "prefix":"192.168.101.0", + "prefixLen":24, + "metric":111, + "peerId":"192.168.101.2" + } +],"192.168.102.0/24": [ + { + "prefix":"192.168.102.0", + "prefixLen":24, + "metric":0, + "peerId":"192.168.101.2" + } +],"192.168.201.0/24": [ + { + "prefix":"192.168.201.0", + "prefixLen":24, + "metric":210, + "peerId":"192.168.0.2" + } +],"192.168.202.0/24": [ + { + "prefix":"192.168.202.0", + "prefixLen":24, + "metric":11, + "peerId":"192.168.0.2" + } +] } } diff --git a/tests/topotests/bgp_features/r1/zebra.conf b/tests/topotests/bgp_features/r1/zebra.conf new file mode 100644 index 0000000..a4e51fd --- /dev/null +++ b/tests/topotests/bgp_features/r1/zebra.conf @@ -0,0 +1,29 @@ +! +hostname r1 +log file zebra.log +! +interface lo + ip address 192.168.0.1/32 + ipv6 address fc00::1/128 +! +interface r1-eth0 + description SW6 Stub Network + ip address 192.168.6.1/24 + ipv6 address fc00:0:0:6::1/64 +! +interface r1-eth1 + description SW0 R1-R2 OSPF & BGP Network + ip address 192.168.1.1/24 + ipv6 address fc00:0:0:1::1/64 +! +interface r1-eth2 + description SW2 R1-R3 OSPF Network + ip address 192.168.3.1/24 + ipv6 address fc00:0:0:3::1/64 +! +interface r1-eth3 + description SW4 R1-R4 eBGP Network + ip address 192.168.101.1/24 + ipv6 address fc00:100:0:1::1/64 +! +no ip nht resolve-via-default diff --git a/tests/topotests/bgp_features/r2/bgp_delayopen_neighbor.json b/tests/topotests/bgp_features/r2/bgp_delayopen_neighbor.json new file mode 100644 index 0000000..a74da03 --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_delayopen_neighbor.json @@ -0,0 +1,6 @@ +{ + "192.168.201.2":{ + "remoteAs":65200, + "bgpTimerDelayOpenTimeMsecs":60000 + } +} diff --git a/tests/topotests/bgp_features/r2/bgp_delayopen_summary_connect.json b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_connect.json new file mode 100644 index 0000000..c2b42ec --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_connect.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65000, + "peers":{ + "192.168.201.2":{ + "remoteAs":65200, + "state":"Connect" + } + } +} +} diff --git a/tests/topotests/bgp_features/r2/bgp_delayopen_summary_established.json b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_established.json new file mode 100644 index 0000000..77b6944 --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_established.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65000, + "peers":{ + "192.168.201.2":{ + "remoteAs":65200, + "state":"Established" + } + } +} +} diff --git a/tests/topotests/bgp_features/r2/bgp_delayopen_summary_shutdown.json b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_shutdown.json new file mode 100644 index 0000000..8f9476a --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_delayopen_summary_shutdown.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65000, + "peers":{ + "192.168.201.2":{ + "remoteAs":65200, + "state":"Idle (Admin)" + } + } +} +} diff --git a/tests/topotests/bgp_features/r2/bgp_shutdown_summary.json b/tests/topotests/bgp_features/r2/bgp_shutdown_summary.json new file mode 100644 index 0000000..b789263 --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_shutdown_summary.json @@ -0,0 +1,19 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.0.2", + "as":65000, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.168.0.1":{ + "remoteAs":65000, + "state":"Active" + }, + "192.168.201.2":{ + "remoteAs":65200, + "state":"Established" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_features/r2/bgp_summary.json b/tests/topotests/bgp_features/r2/bgp_summary.json new file mode 100644 index 0000000..30e0ef4 --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgp_summary.json @@ -0,0 +1,27 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.0.2", + "as":65000, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.168.0.1":{ + "hostname":"r1", + "remoteAs":65000, + "outq":0, + "inq":0, + "pfxRcd":8, + "state":"Established" + }, + "192.168.201.2":{ + "hostname":"r5", + "remoteAs":65200, + "outq":0, + "inq":0, + "pfxRcd":2, + "state":"Established" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_features/r2/bgpd.conf b/tests/topotests/bgp_features/r2/bgpd.conf new file mode 100644 index 0000000..99cec98 --- /dev/null +++ b/tests/topotests/bgp_features/r2/bgpd.conf @@ -0,0 +1,41 @@ +! +hostname r2 +log file bgpd.log +! +router bgp 65000 + bgp router-id 192.168.0.2 + timers bgp 3 10 + coalesce-time 0 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65000 + neighbor 192.168.0.1 timers 3 10 + neighbor 192.168.0.1 description Router R1 (iBGP) + neighbor 192.168.0.1 update-source lo + neighbor 192.168.0.1 timers connect 5 + neighbor 192.168.201.2 remote-as 65200 + neighbor 192.168.201.2 timers 3 10 + neighbor 192.168.201.2 description Router R5 (eBGP AS 65200) + neighbor 192.168.201.2 timers connect 5 + ! + address-family ipv4 unicast + network 192.168.0.0/24 + network 192.168.1.0/24 + network 192.168.2.0/24 + network 192.168.3.0/24 + network 192.168.7.0/24 + network 192.168.8.0/24 + neighbor 192.168.201.2 route-map testmap-in in + neighbor 192.168.201.2 route-map testmap-out out + exit-address-family +! +! +! +route-map testmap-in permit 999 +! +route-map testmap-out permit 999 +! +! +line vty +! + diff --git a/tests/topotests/bgp_features/r2/ospf6d.conf b/tests/topotests/bgp_features/r2/ospf6d.conf new file mode 100644 index 0000000..56aecd0 --- /dev/null +++ b/tests/topotests/bgp_features/r2/ospf6d.conf @@ -0,0 +1,21 @@ +log file ospf6d.log +! +! debug ospf6 neighbor +! +interface r2-lo + ipv6 ospf6 area 0.0.0.0 +! +interface r2-eth1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 5 +! +interface r2-eth2 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 10 +! +router ospf6 + ospf6 router-id 192.168.0.2 + log-adjacency-changes +! +line vty +! diff --git a/tests/topotests/bgp_features/r2/ospf_neighbor.json b/tests/topotests/bgp_features/r2/ospf_neighbor.json new file mode 100644 index 0000000..3a168ba --- /dev/null +++ b/tests/topotests/bgp_features/r2/ospf_neighbor.json @@ -0,0 +1,16 @@ +{ + "neighbors":{ + "192.168.0.1":[ + { + "nbrPriority":10, + "converged":"Full" + } + ], + "192.168.0.3":[ + { + "nbrPriority":5, + "converged":"Full" + } + ] + } +} diff --git a/tests/topotests/bgp_features/r2/ospfd.conf b/tests/topotests/bgp_features/r2/ospfd.conf new file mode 100644 index 0000000..7f043c9 --- /dev/null +++ b/tests/topotests/bgp_features/r2/ospfd.conf @@ -0,0 +1,26 @@ +log file ospfd.log +! +! debug ospf event +! debug ospf zebra +! +int r2-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 5 +! +int r2-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 10 +! +router ospf + ospf router-id 192.168.0.2 + log-adjacency-changes + network 192.168.0.0/20 area 0.0.0.0 + timers throttle spf 0 0 0 + timers lsa min-arrival 10 + timers throttle lsa all 0 + refresh timer 10 +! +line vty +! diff --git a/tests/topotests/bgp_features/r2/show_bgp.json b/tests/topotests/bgp_features/r2/show_bgp.json new file mode 100644 index 0000000..830d5a9 --- /dev/null +++ b/tests/topotests/bgp_features/r2/show_bgp.json @@ -0,0 +1,349 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.2", + "defaultLocPrf": 100, + "localAS": 65000, + "routes": { "0.0.0.0/0": [ + { + "pathFrom":"internal", + "prefix":"0.0.0.0", + "prefixLen":0, + "network":"0.0.0.0\/0", + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.0.0/24": [ + { + "pathFrom":"external", + "prefix":"192.168.0.0", + "prefixLen":24, + "network":"192.168.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.1.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.1.0", + "prefixLen":24, + "network":"192.168.1.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.1.0", + "prefixLen":24, + "network":"192.168.1.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.2.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.2.0", + "prefixLen":24, + "network":"192.168.2.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.2.0", + "prefixLen":24, + "network":"192.168.2.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.3.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.3.0", + "prefixLen":24, + "network":"192.168.3.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.3.0", + "prefixLen":24, + "network":"192.168.3.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.6.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"internal", + "prefix":"192.168.6.0", + "prefixLen":24, + "network":"192.168.6.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.7.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.7.0", + "prefixLen":24, + "network":"192.168.7.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.8.0/24": [ + { + "valid":true, + "pathFrom":"internal", + "prefix":"192.168.8.0", + "prefixLen":24, + "network":"192.168.8.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.0.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + }, + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.8.0", + "prefixLen":24, + "network":"192.168.8.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"IGP", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.101.0/24": [ + { + "pathFrom":"internal", + "prefix":"192.168.101.0", + "prefixLen":24, + "network":"192.168.101.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.102.0/24": [ + { + "pathFrom":"internal", + "prefix":"192.168.102.0", + "prefixLen":24, + "network":"192.168.102.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"192.168.0.1", + "path":"65100", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.2", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.201.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.201.0", + "prefixLen":24, + "network":"192.168.201.0\/24", + "metric":0, + "weight":0, + "peerId":"192.168.201.2", + "path":"65200", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.201.2", + "hostname":"r5", + "afi":"ipv4", + "used":true + } + ] + } +],"192.168.202.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.202.0", + "prefixLen":24, + "network":"192.168.202.0\/24", + "metric":0, + "weight":0, + "peerId":"192.168.201.2", + "path":"65200", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.201.2", + "hostname":"r5", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_features/r2/show_bgp_metric_test.json b/tests/topotests/bgp_features/r2/show_bgp_metric_test.json new file mode 100644 index 0000000..960f13d --- /dev/null +++ b/tests/topotests/bgp_features/r2/show_bgp_metric_test.json @@ -0,0 +1,57 @@ +{ + "routerId": "192.168.0.2", + "routes": { + "192.168.2.0/24": [ + { + "valid":true, + "prefix":"192.168.2.0", + "prefixLen":24, + "metric":0, + "nexthops":[ + { + "ip":"192.168.0.1", + "used":true + } + ] + }, + { + "valid":true, + "prefix":"192.168.2.0", + "prefixLen":24, + "metric":0, + "nexthops":[ + { + "ip":"0.0.0.0", + "used":true + } + ] + } +],"192.168.101.0/24": [ + { + "prefix":"192.168.101.0", + "prefixLen":24, + "metric":101, + "peerId":"192.168.0.1" + } +],"192.168.102.0/24": [ + { + "prefix":"192.168.102.0", + "prefixLen":24, + "metric":0, + "peerId":"192.168.0.1" + } +],"192.168.201.0/24": [ + { + "prefix":"192.168.201.0", + "prefixLen":24, + "metric":222, + "peerId":"192.168.201.2" + } +],"192.168.202.0/24": [ + { + "prefix":"192.168.202.0", + "prefixLen":24, + "metric":0, + "peerId":"192.168.201.2" + } +] } } diff --git a/tests/topotests/bgp_features/r2/zebra.conf b/tests/topotests/bgp_features/r2/zebra.conf new file mode 100644 index 0000000..1d427da --- /dev/null +++ b/tests/topotests/bgp_features/r2/zebra.conf @@ -0,0 +1,28 @@ +! +hostname r2 +log file zebra.log +! +interface lo + ip address 192.168.0.2/32 + ipv6 address fc00::2/128 +! +interface r2-eth0 + description SW7 Stub Network + ip address 192.168.7.1/24 + ipv6 address fc00:0:0:7::1/64 +! +interface r2-eth1 + description SW0 R1-R2 OSPF & BGP Network + ip address 192.168.1.2/24 + ipv6 address fc00:0:0:1::2/64 +! +interface r2-eth2 + description SW1 R2-R3 OSPF Network + ip address 192.168.2.1/24 + ipv6 address fc00:0:0:2::1/64 +! +interface r2-eth3 + description SW5 R2-R5 eBGP Network + ip address 192.168.201.1/24 + ipv6 address fc00:200:0:1::1/64 +! diff --git a/tests/topotests/bgp_features/r3/bgp_summary.json b/tests/topotests/bgp_features/r3/bgp_summary.json new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_features/r3/ospf6d.conf b/tests/topotests/bgp_features/r3/ospf6d.conf new file mode 100644 index 0000000..f15b9d9 --- /dev/null +++ b/tests/topotests/bgp_features/r3/ospf6d.conf @@ -0,0 +1,21 @@ +log file ospf6d.log +! +! debug ospf6 neighbor +! +interface r3-lo + ipv6 ospf6 area 0.0.0.0 +! +interface r3-eth1 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 5 +! +interface r3-eth2 + ipv6 ospf6 area 0.0.0.0 + ipv6 ospf6 priority 5 +! +router ospf6 + ospf6 router-id 192.168.0.3 + log-adjacency-changes +! +line vty +! diff --git a/tests/topotests/bgp_features/r3/ospf_neighbor.json b/tests/topotests/bgp_features/r3/ospf_neighbor.json new file mode 100644 index 0000000..9f8c059 --- /dev/null +++ b/tests/topotests/bgp_features/r3/ospf_neighbor.json @@ -0,0 +1,16 @@ +{ + "neighbors":{ + "192.168.0.1":[ + { + "nbrPriority":10, + "converged":"Full" + } + ], + "192.168.0.2":[ + { + "nbrPriority":10, + "converged":"Full" + } + ] + } +} diff --git a/tests/topotests/bgp_features/r3/ospfd.conf b/tests/topotests/bgp_features/r3/ospfd.conf new file mode 100644 index 0000000..c3399fd --- /dev/null +++ b/tests/topotests/bgp_features/r3/ospfd.conf @@ -0,0 +1,26 @@ +log file ospfd.log +! +! debug ospf event +! debug ospf zebra +! +int r3-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 5 +! +int r3-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 + ip ospf priority 5 +! +router ospf + ospf router-id 192.168.0.3 + log-adjacency-changes + network 192.168.0.0/20 area 0.0.0.0 + timers throttle spf 0 0 0 + timers lsa min-arrival 10 + timers throttle lsa all 0 + refresh timer 10 +! +line vty +! diff --git a/tests/topotests/bgp_features/r3/zebra.conf b/tests/topotests/bgp_features/r3/zebra.conf new file mode 100644 index 0000000..62ba04d --- /dev/null +++ b/tests/topotests/bgp_features/r3/zebra.conf @@ -0,0 +1,23 @@ +! +hostname r3 +log file zebra.log +! +interface lo + ip address 192.168.0.3/32 + ipv6 address fc00::3/128 +! +interface r3-eth0 + description SW8 Stub Network + ip address 192.168.8.1/24 + ipv6 address fc00:0:0:8::1/64 +! +interface r3-eth1 + description SW1 R2-R3 OSPF Network + ip address 192.168.2.2/24 + ipv6 address fc00:0:0:2::2/64 +! +interface r3-eth2 + description SW2 R1-R3 OSPF Network + ip address 192.168.3.2/24 + ipv6 address fc00:0:0:3::2/64 +! diff --git a/tests/topotests/bgp_features/r4/bgp_delayopen_summary_established.json b/tests/topotests/bgp_features/r4/bgp_delayopen_summary_established.json new file mode 100644 index 0000000..85caf55 --- /dev/null +++ b/tests/topotests/bgp_features/r4/bgp_delayopen_summary_established.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65100, + "peers":{ + "192.168.101.1":{ + "remoteAs":65000, + "state":"Established" + } + } +} +} diff --git a/tests/topotests/bgp_features/r4/bgp_delayopen_summary_shutdown.json b/tests/topotests/bgp_features/r4/bgp_delayopen_summary_shutdown.json new file mode 100644 index 0000000..cf784d8 --- /dev/null +++ b/tests/topotests/bgp_features/r4/bgp_delayopen_summary_shutdown.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65100, + "peers":{ + "192.168.101.1":{ + "remoteAs":65000, + "state":"Idle (Admin)" + } + } +} +} diff --git a/tests/topotests/bgp_features/r4/bgp_shutdown_summary.json b/tests/topotests/bgp_features/r4/bgp_shutdown_summary.json new file mode 100644 index 0000000..ede4dd6 --- /dev/null +++ b/tests/topotests/bgp_features/r4/bgp_shutdown_summary.json @@ -0,0 +1,14 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.100.1", + "as":65100, + "vrfName":"default", + "peerCount":1, + "peers":{ + "192.168.101.1":{ + "remoteAs":65000, + "state":"Active" } + }, + "totalPeers":1 +} +} diff --git a/tests/topotests/bgp_features/r4/bgp_summary.json b/tests/topotests/bgp_features/r4/bgp_summary.json new file mode 100644 index 0000000..c0dfe78 --- /dev/null +++ b/tests/topotests/bgp_features/r4/bgp_summary.json @@ -0,0 +1,18 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.100.1", + "as":65100, + "vrfName":"default", + "peerCount":1, + "peers":{ + "192.168.101.1":{ + "hostname":"r1", + "remoteAs":65000, + "outq":0, + "inq":0, + "pfxRcd":6, + "state":"Established" } + }, + "totalPeers":1 +} +} diff --git a/tests/topotests/bgp_features/r4/bgpd.conf b/tests/topotests/bgp_features/r4/bgpd.conf new file mode 100644 index 0000000..cdf8f4e --- /dev/null +++ b/tests/topotests/bgp_features/r4/bgpd.conf @@ -0,0 +1,34 @@ +! +hostname r4 +log file bgpd.log +! +router bgp 65100 + bgp router-id 192.168.100.1 + timers bgp 3 10 + coalesce-time 0 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.101.1 remote-as 65000 + neighbor 192.168.101.1 timers 3 10 + neighbor 192.168.101.1 description Router R1 (eBGP AS 65000) + neighbor 192.168.101.1 timers connect 5 + ! + address-family ipv4 unicast + network 192.168.100.0/24 + network 192.168.101.0/24 + network 192.168.102.0/24 + neighbor 192.168.101.1 default-originate + neighbor 192.168.101.1 route-map testmap-in in + neighbor 192.168.101.1 route-map testmap-out out + exit-address-family +! +! +! +route-map testmap-in permit 999 +! +route-map testmap-out permit 999 +! +! +line vty +! + diff --git a/tests/topotests/bgp_features/r4/show_bgp_metric_test.json b/tests/topotests/bgp_features/r4/show_bgp_metric_test.json new file mode 100644 index 0000000..2329498 --- /dev/null +++ b/tests/topotests/bgp_features/r4/show_bgp_metric_test.json @@ -0,0 +1,27 @@ +{ + "routerId": "192.168.100.1", + "localAS": 65100, + "routes": { + "192.168.1.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.1.0", + "prefixLen":24, + "network":"192.168.1.0\/24", + "metric":1011, + "weight":0, + "peerId":"192.168.101.1", + "path":"65000", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.101.1", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_features/r4/zebra.conf b/tests/topotests/bgp_features/r4/zebra.conf new file mode 100644 index 0000000..08e3e1a --- /dev/null +++ b/tests/topotests/bgp_features/r4/zebra.conf @@ -0,0 +1,18 @@ +! +hostname r4 +log file zebra.log +! +interface lo + ip address 192.168.100.1/32 + ipv6 address fc00:100::1/128 +! +interface r4-eth0 + description SW5 Stub Network + ip address 192.168.102.1/24 + ipv6 address fc00:100:0:2::1/64 +! +interface r4-eth1 + description SW0 R1-R2 OSPF & BGP Network + ip address 192.168.101.2/24 + ipv6 address fc00:100:0:1::2/64 +! diff --git a/tests/topotests/bgp_features/r5/bgp_delayopen_neighbor.json b/tests/topotests/bgp_features/r5/bgp_delayopen_neighbor.json new file mode 100644 index 0000000..4b97254 --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgp_delayopen_neighbor.json @@ -0,0 +1,6 @@ +{ + "192.168.201.1":{ + "remoteAs":65000, + "bgpTimerDelayOpenTimeMsecs":30000 + } +} diff --git a/tests/topotests/bgp_features/r5/bgp_delayopen_summary_connect.json b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_connect.json new file mode 100644 index 0000000..d7b4e77 --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_connect.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65200, + "peers":{ + "192.168.201.1":{ + "remoteAs":65000, + "state":"Connect" + } + } +} +} diff --git a/tests/topotests/bgp_features/r5/bgp_delayopen_summary_established.json b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_established.json new file mode 100644 index 0000000..15cfb19 --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_established.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65200, + "peers":{ + "192.168.201.1":{ + "remoteAs":65000, + "state":"Established" + } + } +} +} diff --git a/tests/topotests/bgp_features/r5/bgp_delayopen_summary_shutdown.json b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_shutdown.json new file mode 100644 index 0000000..94aceba --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgp_delayopen_summary_shutdown.json @@ -0,0 +1,11 @@ +{ +"ipv4Unicast":{ + "as":65200, + "peers":{ + "192.168.201.1":{ + "remoteAs":65000, + "state":"Idle (Admin)" + } + } +} +} diff --git a/tests/topotests/bgp_features/r5/bgp_summary.json b/tests/topotests/bgp_features/r5/bgp_summary.json new file mode 100644 index 0000000..b854af5 --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgp_summary.json @@ -0,0 +1,19 @@ +{ +"ipv4Unicast":{ + "routerId":"192.168.200.1", + "as":65200, + "vrfName":"default", + "peerCount":1, + "peers":{ + "192.168.201.1":{ + "hostname":"r2", + "remoteAs":65000, + "outq":0, + "inq":0, + "pfxRcd":6, + "state":"Established" + } + }, + "totalPeers":1 +} +} diff --git a/tests/topotests/bgp_features/r5/bgpd.conf b/tests/topotests/bgp_features/r5/bgpd.conf new file mode 100644 index 0000000..6368fed --- /dev/null +++ b/tests/topotests/bgp_features/r5/bgpd.conf @@ -0,0 +1,34 @@ +! +hostname r5 +log file bgpd.log +! +router bgp 65200 + bgp router-id 192.168.200.1 + timers bgp 3 10 + coalesce-time 0 + bgp log-neighbor-changes + no bgp ebgp-requires-policy + neighbor 192.168.201.1 remote-as 65000 + neighbor 192.168.201.1 timers 3 10 + neighbor 192.168.201.1 description Router R2 (eBGP AS 65000) + neighbor 192.168.201.1 timers connect 5 + ! + address-family ipv4 unicast + network 192.168.200.0/24 + network 192.168.201.0/24 + network 192.168.202.0/24 + neighbor 192.168.101.1 default-originate + neighbor 192.168.201.1 route-map testmap-in in + neighbor 192.168.201.1 route-map testmap-out out + exit-address-family +! +! +! +route-map testmap-in permit 999 +! +route-map testmap-out permit 999 +! +! +line vty +! + diff --git a/tests/topotests/bgp_features/r5/show_bgp_metric_test.json b/tests/topotests/bgp_features/r5/show_bgp_metric_test.json new file mode 100644 index 0000000..e6608b4 --- /dev/null +++ b/tests/topotests/bgp_features/r5/show_bgp_metric_test.json @@ -0,0 +1,27 @@ +{ + "routerId": "192.168.200.1", + "localAS": 65200, + "routes": { + "192.168.2.0/24": [ + { + "valid":true, + "bestpath":true, + "pathFrom":"external", + "prefix":"192.168.2.0", + "prefixLen":24, + "network":"192.168.2.0\/24", + "metric":2022, + "weight":0, + "peerId":"192.168.201.1", + "path":"65000", + "origin":"IGP", + "nexthops":[ + { + "ip":"192.168.201.1", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } +] } } diff --git a/tests/topotests/bgp_features/r5/zebra.conf b/tests/topotests/bgp_features/r5/zebra.conf new file mode 100644 index 0000000..4d9064a --- /dev/null +++ b/tests/topotests/bgp_features/r5/zebra.conf @@ -0,0 +1,18 @@ +! +hostname r5 +log file zebra.log +! +interface lo + ip address 192.168.200.1/32 + ipv6 address fc00:200::1/128 +! +interface r5-eth0 + description SW6 Stub Network + ip address 192.168.202.1/24 + ipv6 address fc00:200:0:2::1/64 +! +interface r5-eth1 + description SW0 R1-R2 OSPF & BGP Network + ip address 192.168.201.2/24 + ipv6 address fc00:200:0:1::2/64 +! diff --git a/tests/topotests/bgp_features/test_bgp_features.dot b/tests/topotests/bgp_features/test_bgp_features.dot new file mode 100644 index 0000000..70b126c --- /dev/null +++ b/tests/topotests/bgp_features/test_bgp_features.dot @@ -0,0 +1,83 @@ +## GraphViz file for test_all_protocol_startup +## +## Color coding: +######################### +## Main FRR: #f08080 red +## No protocol: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #33ff99 light green +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +## LDP IPv4 #fedbe2 light pink +##### Colors (see http://www.color-hex.com/) + +graph test_all_protocol_startup { + overlap=false; + constraint=false; + + // title + labelloc="t"; + label="Test Topologoy BGP Features"; + rankdir = TB; + + ###################### + # Routers + ###################### + + # Main FRR Router with all protocols + R4 [shape=doubleoctagon, label="R4 FRR\nAS 65100\nlo: 192.168.100.1/32\nfc00:100::1/128", fillcolor="#f08080", style=filled]; + R5 [shape=doubleoctagon, label="R5 FRR\nAS 65200\nlo: 192.168.200.1/32\nfc00:200::1/128", fillcolor="#f08080", style=filled]; + #{ rank = same {R4, R5}} + + R1 [shape=doubleoctagon, label="R1 FRR\nAS 65000\nlo: 192.168.0.1/32\nfc00::1/128", fillcolor="#f08080", style=filled]; + R2 [shape=doubleoctagon, label="R2 FRR\nAS 65000\nlo: 192.168.0.1/32\nfc00::1/128", fillcolor="#f08080", style=filled]; + #{ rank = same { R1, R2}} + + R3 [shape=doubleoctagon, label="R3 FRR\nAS 65000\nlo: 192.168.0.1/32\nfc00::1/128", fillcolor="#f08080", style=filled]; + + ###################### + # Network Lists + ###################### + + SW1_R1_R2 [label="SW1 OSPF & iBGP\n192.168.1.0/24\nfc00:0:0:1::/64", fillcolor="#32b835", style=filled]; + SW2_R2_R3 [label="SW2 OSPF\n192.168.2.0/24\nfc00:0:0:2::/64", fillcolor="#19e3d9", style=filled]; + SW3_R3_R1 [label="SW3 OSPF\n192.168.3.0/24\nfc00:0:0:3::/64", fillcolor="#19e3d9", style=filled]; + + SW4_R4_R1 [label="SW4 eBGP\n192.168.101.0/24\nfc00:100:0:1::/64", fillcolor="#fedbe2", style=filled]; + SW5_R5_R2 [label="SW5 eBGP\n192.168.201.0/24\nfc00:200:0:1::/64", fillcolor="#fedbe2", style=filled]; + #{ rank = same {SW4_R4_R1, SW5_R5_R2}} + + SW6_STUB_R1 [label="SW6\n192.168.6.0/24\nfc00:0:0:6::/64", fillcolor="#d0e0d0", style=filled]; + SW7_STUB_R2 [label="SW7\n192.168.7.0/24\nfc00:0:0:7::/64", fillcolor="#d0e0d0", style=filled]; + SW8_STUB_R3 [label="SW8\n192.168.8.0/24\nfc00:0:0:8::/64", fillcolor="#d0e0d0", style=filled]; + SW9_STUB_R4 [label="SW9\n192.168.102.0/24\nfc00:100:0:2::/64", fillcolor="#d0e0d0", style=filled]; + SW10_STUB_R5 [label="SW10\n192.168.202.0/24\nfc00:200:0:2::/64", fillcolor="#d0e0d0", style=filled]; + + + ###################### + # Network Connections + ###################### + + R1 -- SW6_STUB_R1 [label = "eth0\n.1\n::1"]; + R2 -- SW7_STUB_R2 [label = "eth0\n.1\n::1"]; + R3 -- SW8_STUB_R3 [label = "eth0\n.1\n::1"]; + R4 -- SW9_STUB_R4 [label = "eth0\n.1\n::1"]; + R5 -- SW10_STUB_R5 [label = "eth0\n.1\n::1"]; + + R1 -- SW1_R1_R2 [label = "eth1\n.1\n::1"]; + R1 -- SW3_R3_R1 [label = "eth2\n.1\n::1"]; + R2 -- SW1_R1_R2 [label = "eth1\n.2\n::2"]; + R2 -- SW2_R2_R3 [label = "eth2\n.1\n::1"]; + R3 -- SW2_R2_R3 [label = "eth1\n.2\n::2"]; + R3 -- SW3_R3_R1 [label = "eth2\n.2\n::2"]; + + R1 -- SW4_R4_R1 [label = "eth3\n.1\n::1"]; + R2 -- SW5_R5_R2 [label = "eth3\n.1\n::1"]; + R4 -- SW4_R4_R1 [label = "eth1\n.2\n::2"]; + R5 -- SW5_R5_R2 [label = "eth1\n.2\n::2"]; + +} diff --git a/tests/topotests/bgp_features/test_bgp_features.pdf b/tests/topotests/bgp_features/test_bgp_features.pdf new file mode 100644 index 0000000..cb52a54 Binary files /dev/null and b/tests/topotests/bgp_features/test_bgp_features.pdf differ diff --git a/tests/topotests/bgp_features/test_bgp_features.py b/tests/topotests/bgp_features/test_bgp_features.py new file mode 100644 index 0000000..43f4905 --- /dev/null +++ b/tests/topotests/bgp_features/test_bgp_features.py @@ -0,0 +1,1102 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_features.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bgp_features.py: Test various BGP features. +""" + +import json +import functools +import os +import sys +import pytest +import re +import time + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + +##################################################### +# +# Network Topology Definition +# +##################################################### + + +def build_topo(tgen): + for rtrNum in range(1, 6): + tgen.add_router("r{}".format(rtrNum)) + + # create ExaBGP peers + for peer_num in range(1, 5): + tgen.add_exabgp_peer( + "peer{}".format(peer_num), + ip="192.168.101.{}".format(peer_num + 2), + defaultRoute="via 192.168.101.1", + ) + + # Setup Switches and connections + for swNum in range(1, 11): + tgen.add_switch("sw{}".format(swNum)) + + # Add connections to stub switches + tgen.gears["r1"].add_link(tgen.gears["sw6"]) + tgen.gears["r2"].add_link(tgen.gears["sw7"]) + tgen.gears["r3"].add_link(tgen.gears["sw8"]) + tgen.gears["r4"].add_link(tgen.gears["sw9"]) + tgen.gears["r5"].add_link(tgen.gears["sw10"]) + + # Add connections to R1-R2-R3 core + tgen.gears["r1"].add_link(tgen.gears["sw1"]) + tgen.gears["r1"].add_link(tgen.gears["sw3"]) + tgen.gears["r2"].add_link(tgen.gears["sw1"]) + tgen.gears["r2"].add_link(tgen.gears["sw2"]) + tgen.gears["r3"].add_link(tgen.gears["sw2"]) + tgen.gears["r3"].add_link(tgen.gears["sw3"]) + + # Add connections to external R4/R5 Routers + tgen.gears["r1"].add_link(tgen.gears["sw4"]) + tgen.gears["r4"].add_link(tgen.gears["sw4"]) + tgen.gears["r2"].add_link(tgen.gears["sw5"]) + tgen.gears["r5"].add_link(tgen.gears["sw5"]) + + # Add ExaBGP peers to sw4 + tgen.gears["peer1"].add_link(tgen.gears["sw4"]) + tgen.gears["peer2"].add_link(tgen.gears["sw4"]) + tgen.gears["peer3"].add_link(tgen.gears["sw4"]) + tgen.gears["peer4"].add_link(tgen.gears["sw4"]) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # Starting Routers + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if os.path.exists(os.path.join(CWD, "{}/bgpd.conf".format(rname))): + logger.info("{} uses BGPd".format(rname)) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + if os.path.exists(os.path.join(CWD, "{}/ospfd.conf".format(rname))): + logger.info("{} uses OSPFd".format(rname)) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + if os.path.exists(os.path.join(CWD, "{}/ospf6d.conf".format(rname))): + logger.info("{} uses OSPF6d".format(rname)) + router.load_config( + TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname)) + ) + router.start() + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def test_ospf_convergence(): + "Test for OSPFv2 topology convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check Router r1, r2 & r3 OSPF + for rtrNum in range(1, 4): + logger.info("Checking OSPFv2 convergence on router r{}".format(rtrNum)) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/ospf_neighbor.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip ospf neighbor json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "OSPF router R{} did not converge".format(rtrNum) + assert res is None, assertmsg + + +def test_bgp_convergence(): + "Test for BGP topology convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check Router r1 & r2 BGP + for rtrNum in [1, 2, 4, 5]: + logger.info("Checking BGP IPv4 convergence on router r{}".format(rtrNum)) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "BGP router R{} did not converge".format(rtrNum) + assert res is None, assertmsg + + # tgen.mininet_cli() + + +def get_shut_msg_count(tgen): + shuts = {} + for rtrNum in [2, 4]: + shutmsg = tgen.net["r{}".format(rtrNum)].cmd_nostatus( + 'grep -c "NOTIFICATION.*Cease/Administrative Shutdown" bgpd.log', warn=False + ) + try: + shuts[rtrNum] = int(shutmsg.strip()) + except ValueError: + shuts[rtrNum] = 0 + return shuts + + +def test_bgp_shutdown(): + "Test BGP instance shutdown" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + shuts_before = get_shut_msg_count(tgen) + + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "bgp shutdown message ABCDabcd"' + ) + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2, 4]: + logger.info( + "Checking BGP Summary after shutdown of R1 BGP on router r{}".format(rtrNum) + ) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/bgp_shutdown_summary.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "BGP sessions on router R{} are in incorrect state (not down as expected?)".format( + rtrNum + ) + assert res is None, assertmsg + + shuts_after = get_shut_msg_count(tgen) + + for k in shuts_before: + assert shuts_before[k] + 1 == shuts_after[k] + + +def test_bgp_shutdown_message(): + "Test BGP Peer Shutdown Message" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rtrNum in [2, 4]: + logger.info("Checking BGP shutdown received on router r{}".format(rtrNum)) + + shut_message = tgen.net["r{}".format(rtrNum)].cmd( + 'grep -e "NOTIFICATION.*Cease/Administrative Shutdown.*ABCDabcd" bgpd.log' + ) + assertmsg = "BGP shutdown message not received on router R{}".format(rtrNum) + assert shut_message != "", assertmsg + + +def test_bgp_no_shutdown(): + "Test BGP instance no shutdown" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.net["r1"].cmd('vtysh -c "conf t" -c "router bgp 65000" -c "no bgp shutdown"') + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2, 4]: + logger.info( + "Checking BGP Summary after removing bgp shutdown on router r1 on router r{}".format( + rtrNum + ) + ) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "BGP sessions on router R{} are in incorrect state (not down as expected?)".format( + rtrNum + ) + assert res is None, assertmsg + + +def test_bgp_metric_config(): + "Test BGP Changing metric values in route-maps" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Configuring bgp route-maps on router r1 and r2 to update metric") + + # # Adding the following configuration to r1: + # router bgp 65000 + # address-family ipv4 unicast + # neighbor 192.168.0.2 route-map addmetric-in in + # neighbor 192.168.0.2 route-map addmetric-out out + # neighbor 192.168.101.2 route-map setmetric-in in + # neighbor 192.168.101.2 route-map setmetric-out out + # exit-address-family + # ! + # ip prefix-list net1 seq 10 permit 192.168.101.0/24 + # ip prefix-list net2 seq 20 permit 192.168.1.0/24 + # ! + # route-map setmetric-in permit 10 + # match ip address prefix-list net1 + # set metric 111 + # ! + # route-map setmetric-in permit 20 + # ! + # route-map setmetric-out permit 10 + # match ip address prefix-list net2 + # set metric 1011 + # ! + # route-map setmetric-out permit 20 + # ! + # route-map addmetric-in permit 10 + # set metric +11 + # ! + # route-map addmetric-out permit 10 + # set metric +12 + # ! + + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" ' + + '-c "address-family ipv4 unicast" ' + + '-c "neighbor 192.168.0.2 route-map addmetric-in in" ' + + '-c "neighbor 192.168.0.2 route-map addmetric-out out" ' + + '-c "neighbor 192.168.101.2 route-map setmetric-in in" ' + + '-c "neighbor 192.168.101.2 route-map setmetric-out out" ' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "ip prefix-list net1 seq 10 permit 192.168.101.0/24" ' + + '-c "ip prefix-list net2 seq 20 permit 192.168.1.0/24"' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map setmetric-in permit 10" ' + + '-c "match ip address prefix-list net1" ' + + '-c "set metric 111" ' + + '-c "route-map setmetric-in permit 20"' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map setmetric-out permit 10" ' + + '-c "match ip address prefix-list net2" ' + + '-c "set metric 1011" ' + + '-c "route-map setmetric-out permit 20"' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map addmetric-in permit 10" ' + + '-c "set metric +11"' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map addmetric-out permit 10" ' + + '-c "set metric +12"' + ) + + # # Adding the following configuration to r2: + # router bgp 65000 + # address-family ipv4 unicast + # neighbor 192.168.0.1 route-map subtractmetric-in in + # neighbor 192.168.0.1 route-map subtractmetric-out out + # neighbor 192.168.201.2 route-map setmetric-in in + # neighbor 192.168.201.2 route-map setmetric-out out + # exit-address-family + # ! + # ip prefix-list net1 seq 10 permit 192.168.201.0/24 + # ip prefix-list net2 seq 20 permit 192.168.2.0/24 + # ! + # route-map setmetric-in permit 10 + # match ip address prefix-list net1 + # set metric 222 + # ! + # route-map setmetric-in permit 20 + # ! + # route-map setmetric-out permit 10 + # match ip address prefix-list net2 + # set metric 2022 + # ! + # route-map setmetric-out permit 20 + # ! + # route-map subtractmetric-in permit 10 + # set metric -22 + # ! + # route-map subtractmetric-out permit 10 + # set metric -23 + # ! + + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" ' + + '-c "address-family ipv4 unicast" ' + + '-c "neighbor 192.168.0.1 route-map subtractmetric-in in" ' + + '-c "neighbor 192.168.0.1 route-map subtractmetric-out out" ' + + '-c "neighbor 192.168.201.2 route-map setmetric-in in" ' + + '-c "neighbor 192.168.201.2 route-map setmetric-out out" ' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "ip prefix-list net1 seq 10 permit 192.168.201.0/24" ' + + '-c "ip prefix-list net2 seq 20 permit 192.168.2.0/24" ' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map setmetric-in permit 10" ' + + '-c "match ip address prefix-list net1" ' + + '-c "set metric 222" ' + + '-c "route-map setmetric-in permit 20"' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map setmetric-out permit 10" ' + + '-c "match ip address prefix-list net2" ' + + '-c "set metric 2022" ' + + '-c "route-map setmetric-out permit 20"' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map subtractmetric-in permit 10" ' + + '-c "set metric -22"' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "route-map subtractmetric-out permit 10" ' + + '-c "set metric -23"' + ) + + # Clear IN the bgp neighbors to make sure the route-maps are applied + tgen.net["r1"].cmd( + 'vtysh -c "clear ip bgp 192.168.0.2 in" ' + '-c "clear ip bgp 192.168.101.2 in"' + ) + tgen.net["r2"].cmd( + 'vtysh -c "clear ip bgp 192.168.0.1 in" ' + '-c "clear ip bgp 192.168.201.2 in"' + ) + + # tgen.mininet_cli() + + # Checking BGP config - should show the bgp metric settings in the route-maps + logger.info("Checking BGP configuration for correct 'set metric' values") + + setmetric111 = ( + tgen.net["r1"].cmd('vtysh -c "show running" | grep "^ set metric 111"').rstrip() + ) + assertmsg = ( + "'set metric 111' configuration applied to R1, but not visible in configuration" + ) + assert setmetric111 == " set metric 111", assertmsg + + setmetric222 = ( + tgen.net["r2"].cmd('vtysh -c "show running" | grep "^ set metric 222"').rstrip() + ) + assertmsg = ( + "'set metric 222' configuration applied to R2, but not visible in configuration" + ) + assert setmetric222 == " set metric 222", assertmsg + + +def test_bgp_metric_add_config(): + "Test BGP Changing metric values in route-maps" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking BGP configuration for correct 'set metric' ADD value") + + setmetricP11 = ( + tgen.net["r1"].cmd('vtysh -c "show running" | grep "^ set metric +11"').rstrip() + ) + assertmsg = ( + "'set metric +11' configuration applied to R1, but not visible in configuration" + ) + assert setmetricP11 == " set metric +11", assertmsg + + +def test_bgp_metric_subtract_config(): + "Test BGP Changing metric values in route-maps" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking BGP configuration for correct 'set metric' SUBTRACT value") + + setmetricM22 = ( + tgen.net["r2"].cmd('vtysh -c "show running" | grep "^ set metric -22"').rstrip() + ) + assertmsg = ( + "'set metric -22' configuration applied to R2, but not visible in configuration" + ) + assert setmetricM22 == " set metric -22", assertmsg + + +def test_bgp_set_metric(): + "Test setting metrics" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Test absolute metric") + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2, 4, 5]: + logger.info("Checking metrics of BGP router on r{}".format(rtrNum)) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/show_bgp_metric_test.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "BGP metrics on router r{} wrong".format(rtrNum) + assert res is None, assertmsg + + +def test_bgp_remove_metric_rmaps(): + "Test removing route-maps with metric changes again" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Test absolute metric") + + # Remove metric route-maps and relevant comfiguration + + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" ' + + '-c "address-family ipv4 unicast" ' + + '-c "no neighbor 192.168.0.2 route-map addmetric-in in" ' + + '-c "no neighbor 192.168.0.2 route-map addmetric-out out" ' + + '-c "no neighbor 192.168.101.2 route-map setmetric-in in" ' + + '-c "no neighbor 192.168.101.2 route-map setmetric-out out" ' + ) + tgen.net["r1"].cmd( + 'vtysh -c "conf t" ' + + '-c "no ip prefix-list net1" ' + + '-c "no ip prefix-list net2"' + ) + tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-in" ') + tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-out" ') + tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-in" ') + tgen.net["r1"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-out" ') + + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" ' + + '-c "address-family ipv4 unicast" ' + + '-c "no neighbor 192.168.0.1 route-map subtractmetric-in in" ' + + '-c "no neighbor 192.168.0.1 route-map subtractmetric-out out" ' + + '-c "no neighbor 192.168.201.2 route-map setmetric-in in" ' + + '-c "no neighbor 192.168.201.2 route-map setmetric-out out" ' + ) + tgen.net["r2"].cmd( + 'vtysh -c "conf t" ' + + '-c "no ip prefix-list net1" ' + + '-c "no ip prefix-list net2" ' + ) + tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-in" ') + tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map setmetric-out" ') + tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-in" ') + tgen.net["r2"].cmd('vtysh -c "conf t" ' + '-c "no route-map addmetric-out" ') + + # Clear IN the bgp neighbors to make sure the route-maps are applied + tgen.net["r1"].cmd( + 'vtysh -c "clear ip bgp 192.168.0.2 in" ' + '-c "clear ip bgp 192.168.101.2 in"' + ) + tgen.net["r2"].cmd( + 'vtysh -c "clear ip bgp 192.168.0.1 in" ' + '-c "clear ip bgp 192.168.201.2 in"' + ) + + # tgen.mininet_cli() + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2]: + logger.info("Checking metrics of BGP router on r{}".format(rtrNum)) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/show_bgp.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = "BGP routes on router r{} are wrong after removing metric route-maps".format( + rtrNum + ) + assert res is None, assertmsg + + +def test_bgp_norib(): + "Test BGP disable RIB (Zebra) Route Install" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Configuring 'bgp no-rib' on router r1") + + tgen.net["r1"].cmd('vtysh -c "conf t" -c "bgp no-rib"') + + # Checking BGP config - should show the "bgp no-rib" under the router bgp section + logger.info("Checking BGP configuration for 'bgp no-rib'") + + norib_cfg = ( + tgen.net["r1"].cmd('vtysh -c "show running bgpd" | grep "^bgp no-rib"').rstrip() + ) + + assertmsg = "'bgp no-rib' configuration applied, but not visible in configuration" + assert norib_cfg == "bgp no-rib", assertmsg + + +def test_bgp_norib_routes(): + "Test Routes in Zebra and BGP with the 'bgp-norib' configuration" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Checking local BGP routes - they need to be gone from Zebra + logger.info("Checking Zebra routes after removing bgp shutdown on router r1") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/ip_route_norib.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip route json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "Zebra IPv4 Routes after configuring 'bgp no-rib' (There should be no BGP routes in Zebra anymore)" + assert res is None, assertmsg + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2, 4]: + logger.info( + "Checking BGP Summary after 'bgp no-rib' on router r1 on router r{}".format( + rtrNum + ) + ) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "BGP sessions on router R{} has incorrect routes after adding 'bgp no-rib on r1'".format( + rtrNum + ) + assert res is None, assertmsg + + # tgen.mininet_cli() + + +def test_bgp_disable_norib(): + "Test BGP disabling the no-RIB (Zebra) Route Install" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Configuring 'no bgp no-rib' on router r1") + + tgen.net["r1"].cmd('vtysh -c "conf t" -c "no bgp no-rib"') + + # Checking BGP config - should show the "bgp no-rib" under the router bgp section + logger.info("Checking BGP configuration for 'bgp no-rib'") + + norib_cfg = ( + tgen.net["r1"] + .cmd('vtysh -c "show running bgpd" | grep "^ bgp no-rib"') + .rstrip() + ) + + assertmsg = ( + "'no bgp no-rib'configuration applied, but still visible in configuration" + ) + assert norib_cfg == "", assertmsg + + +def test_bgp_disable_norib_routes(): + "Test Routes in Zebra and BGP with the 'bgp-norib' configuration" + + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Checking local BGP routes - they need to be gone from Zebra + logger.info("Checking Zebra routes after removing bgp shutdown on router r1") + + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/ip_route.json") + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip route json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "Zebra IPv4 Routes wrong after removing the 'bgp no-rib'" + assert res is None, assertmsg + + # Check BGP Summary on local and remote routers + for rtrNum in [1, 2, 4]: + logger.info( + "Checking BGP Summary after removing the 'bgp no-rib' on router r1 on router r{}".format( + rtrNum + ) + ) + + router = tgen.gears["r{}".format(rtrNum)] + reffile = os.path.join(CWD, "r{}/bgp_summary.json".format(rtrNum)) + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=30, wait=2) + assertmsg = "BGP sessions on router R{} has incorrect routes after removing 'bgp no-rib on r1'".format( + rtrNum + ) + assert res is None, assertmsg + + # tgen.mininet_cli() + + +def test_bgp_delayopen_without(): + "Optional test of BGP functionality and behaviour without DelayOpenTimer enabled to establish a reference for following tests" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # part 1: no delay r1 <=> no delay r4 + logger.info( + "Starting optional test of BGP functionality without DelayOpenTimer enabled to establish a reference for following tests" + ) + + # 1.1 enable peering shutdown + logger.info("Enable shutdown of peering between r1 and r4") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 shutdown"' + ) + tgen.net["r4"].cmd( + 'vtysh -c "conf t" -c "router bgp 65100" -c "neighbor 192.168.101.1 shutdown"' + ) + + # 1.2 wait for peers to shut down (poll output) + for router_num in [1, 4]: + logger.info( + "Checking BGP summary after enabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r{} did not shut down peer".format(router_num) + assert res is None, assertmsg + + # 1.3 disable peering shutdown + logger.info("Disable shutdown of peering between r1 and r4") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 shutdown"' + ) + tgen.net["r4"].cmd( + 'vtysh -c "conf t" -c "router bgp 65100" -c "no neighbor 192.168.101.1 shutdown"' + ) + + # 1.4 wait for peers to establish connection (poll output) + for router_num in [1, 4]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = ( + "BGP session on r{} did not establish a connection with peer".format( + router_num + ) + ) + assert res is None, assertmsg + + # tgen.mininet_cli() + + # end test_bgp_delayopen_without + + +def test_bgp_delayopen_singular(): + "Test of BGP functionality and behaviour with DelayOpenTimer enabled on one side of the peering" + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # part 2: delay 240s r1 <=> no delay r4 + logger.info( + "Starting test of BGP functionality and behaviour with DelayOpenTimer enabled on one side of the peering" + ) + + # 2.1 enable peering shutdown + logger.info("Enable shutdown of peering between r1 and r4") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 shutdown"' + ) + tgen.net["r4"].cmd( + 'vtysh -c "conf t" -c "router bgp 65100" -c "neighbor 192.168.101.1 shutdown"' + ) + + # 2.2 wait for peers to shut down (poll output) + for router_num in [1, 4]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r{} did not shut down peer".format(router_num) + assert res is None, assertmsg + + # 2.3 set delayopen on R1 to 240 + logger.info("Setting DelayOpenTime for neighbor r4 to 240 seconds on r1") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.101.2 timers delayopen 240"' + ) + + # 2.4 check config (poll output) + logger.info("Checking BGP neighbor configuration after setting DelayOpenTime on r1") + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/bgp_delayopen_neighbor.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show bgp neighbors json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r1 failed to set DelayOpenTime for r4" + assert res is None, assertmsg + + # 2.5 disable peering shutdown + logger.info("Disable shutdown of peering between r1 and r4") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 shutdown"' + ) + tgen.net["r4"].cmd( + 'vtysh -c "conf t" -c "router bgp 65100" -c "no neighbor 192.168.101.1 shutdown"' + ) + + # 2.6 wait for peers to establish connection (poll output) + for router_num in [1, 4]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = ( + "BGP session on r{} did not establish a connection with peer".format( + router_num + ) + ) + assert res is None, assertmsg + + # 2.7 unset delayopen on R1 + logger.info("Disabling DelayOpenTimer for neighbor r4 on r1") + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.101.2 timers delayopen"' + ) + + # 2.8 check config (poll output) + logger.info( + "Checking BGP neighbor configuration after disabling DelayOpenTimer on r1" + ) + delayopen_cfg = ( + tgen.net["r1"] + .cmd('vtysh -c "show bgp neighbors json" | grep "DelayOpenTimeMsecs"') + .rstrip() + ) + assertmsg = "BGP session on r1 failed disable DelayOpenTimer for peer r4" + assert delayopen_cfg == "", assertmsg + + # tgen.mininet_cli() + + # end test_bgp_delayopen_singular + + +def test_bgp_delayopen_dual(): + "Test of BGP functionality and behaviour with DelayOpenTimer enabled on both sides of the peering with different timer intervals" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # part 3: delay 60s R2 <=> delay 30s R5 + logger.info( + "Starting test of BGP functionality and behaviour with DelayOpenTimer enabled on both sides of the peering with different timer intervals" + ) + + # 3.1 enable peering shutdown + logger.info("Enable shutdown of peering between r2 and r5") + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.201.2 shutdown"' + ) + tgen.net["r5"].cmd( + 'vtysh -c "conf t" -c "router bgp 65200" -c "neighbor 192.168.201.1 shutdown"' + ) + + # 3.2 wait for peers to shut down (pool output) + for router_num in [2, 5]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_shutdown.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r{} did not shut down peer".format(router_num) + assert res is None, assertmsg + + # 3.3 set delayopen on R2 to 60s and on R5 to 30s + logger.info("Setting DelayOpenTime for neighbor r5 to 60 seconds on r2") + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "neighbor 192.168.201.2 timers delayopen 60"' + ) + logger.info("Setting DelayOpenTime for neighbor r2 to 30 seconds on r5") + tgen.net["r5"].cmd( + 'vtysh -c "conf t" -c "router bgp 65200" -c "neighbor 192.168.201.1 timers delayopen 30"' + ) + + # 3.4 check config (poll output) + for router_num in [2, 5]: + logger.info( + "Checking BGP neighbor configuration after setting DelayOpenTime on r{}i".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_neighbor.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show bgp neighbors json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r{} failed to set DelayOpenTime".format(router_num) + assert res is None, assertmsg + + ## 3.5 disable peering shutdown + logger.info("Disable shutdown of peering between r2 and r5") + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.201.2 shutdown"' + ) + tgen.net["r5"].cmd( + 'vtysh -c "conf t" -c "router bgp 65200" -c "no neighbor 192.168.201.1 shutdown"' + ) + + ## 3.6 wait for peers to reach connect or active state (poll output) + delay_start = int(time.time()) + for router_num in [2, 5]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_connect.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "BGP session on r{} did not enter Connect state with peer".format( + router_num + ) + assert res is None, assertmsg + + ## 3.7 wait for peers to establish connection (poll output) + for router_num in [2, 5]: + logger.info( + "Checking BGP summary after disabling shutdown of peering on r{}".format( + router_num + ) + ) + router = tgen.gears["r{}".format(router_num)] + reffile = os.path.join( + CWD, "r{}/bgp_delayopen_summary_established.json".format(router_num) + ) + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, router, "show ip bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=35, wait=1) + assertmsg = ( + "BGP session on r{} did not establish a connection with peer".format( + router_num + ) + ) + assert res is None, assertmsg + + delay_stop = int(time.time()) + assertmsg = "BGP peering between r2 and r5 was established before DelayOpenTimer (30sec) on r2 could expire" + assert (delay_stop - delay_start) >= 30, assertmsg + + # 3.8 unset delayopen on R2 and R5 + logger.info("Disabling DelayOpenTimer for neighbor r5 on r2") + tgen.net["r2"].cmd( + 'vtysh -c "conf t" -c "router bgp 65000" -c "no neighbor 192.168.201.2 timers delayopen"' + ) + logger.info("Disabling DelayOpenTimer for neighbor r2 on r5") + tgen.net["r5"].cmd( + 'vtysh -c "conf t" -c "router bgp 65200" -c "no neighbor 192.168.201.1 timers delayopen"' + ) + + # 3.9 check config (poll output) + for router_num in [2, 5]: + logger.info( + "Checking BGP neighbor configuration after disabling DelayOpenTimer on r{}".format( + router_num + ) + ) + delayopen_cfg = ( + tgen.net["r{}".format(router_num)] + .cmd('vtysh -c "show bgp neighbors json" | grep "DelayOpenTimeMsecs"') + .rstrip() + ) + assertmsg = "BGP session on r{} failed disable DelayOpenTimer".format( + router_num + ) + assert delayopen_cfg == "", assertmsg + + # tgen.mininet_cli() + + # end test_bgp_delayopen_dual + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_flowspec/__init__.py b/tests/topotests/bgp_flowspec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_flowspec/exabgp.env b/tests/topotests/bgp_flowspec/exabgp.env new file mode 100644 index 0000000..a328e04 --- /dev/null +++ b/tests/topotests/bgp_flowspec/exabgp.env @@ -0,0 +1,54 @@ + +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_flowspec/peer1/exabgp.cfg b/tests/topotests/bgp_flowspec/peer1/exabgp.cfg new file mode 100644 index 0000000..383a95b --- /dev/null +++ b/tests/topotests/bgp_flowspec/peer1/exabgp.cfg @@ -0,0 +1,33 @@ +neighbor 10.0.1.1 { +router-id 10.0.1.101; +hold-time 10; +local-address 10.0.1.101; +local-as 100; +peer-as 100; +flow { +route { +match { +source 1.1.1.2/32; +destination 3.3.3.3/32; +packet-length <200; +} +then { +redirect 50.0.0.2; +rate-limit 55; +} +} +#end route 1 +route { +match { +source 1::2/128/0; +destination 3::3/128/0; +packet-length <200; +} +then { +redirect 50::2; +rate-limit 55; +} +} +#end route 2 +} +} diff --git a/tests/topotests/bgp_flowspec/r1/bgpd.conf b/tests/topotests/bgp_flowspec/r1/bgpd.conf new file mode 100644 index 0000000..4b7a20f --- /dev/null +++ b/tests/topotests/bgp_flowspec/r1/bgpd.conf @@ -0,0 +1,19 @@ +! +hostname r1 +password zebra +log stdout debugging +router bgp 100 + bgp router-id 10.0.1.1 + neighbor 10.0.1.101 remote-as 100 + neighbor 10.0.1.101 timers 3 10 + neighbor 10.0.1.101 update-source 10.0.1.1 + address-family ipv6 flowspec + local-install r1-eth0 + neighbor 10.0.1.101 activate + exit-address-family + address-family ipv4 flowspec + local-install r1-eth0 + neighbor 10.0.1.101 activate + exit-address-family + ! +! diff --git a/tests/topotests/bgp_flowspec/r1/summary.txt b/tests/topotests/bgp_flowspec/r1/summary.txt new file mode 100644 index 0000000..82426f3 --- /dev/null +++ b/tests/topotests/bgp_flowspec/r1/summary.txt @@ -0,0 +1,50 @@ +{ +"ipv4Unicast":{ + "routerId":"10.0.1.1", + "as":100, + "vrfName":"default", + "peerCount":1, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":0, + "pfxSnt":0, + "state":"Established" + } + }, + "totalPeers":1 +}, +"ipv4Flowspec":{ + "routerId":"10.0.1.1", + "as":100, + "vrfName":"default", + "peerCount":1, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":1, + "pfxSnt":0, + "state":"Established" + } + }, + "totalPeers":1 +}, +"ipv6Flowspec":{ + "routerId":"10.0.1.1", + "as":100, + "vrfName":"default", + "peerCount":1, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":1, + "pfxSnt":0, + "state":"Established" + } + }, + "totalPeers":1 +} +} diff --git a/tests/topotests/bgp_flowspec/r1/zebra.conf b/tests/topotests/bgp_flowspec/r1/zebra.conf new file mode 100644 index 0000000..4b103cb --- /dev/null +++ b/tests/topotests/bgp_flowspec/r1/zebra.conf @@ -0,0 +1,9 @@ +! +hostname r1 +password zebra +ip table range 500 600 +interface r1-eth0 + ip address 10.0.1.1/24 + ipv6 address 1001::1/112 +! + diff --git a/tests/topotests/bgp_flowspec/test_bgp_flowspec_topo.py b/tests/topotests/bgp_flowspec/test_bgp_flowspec_topo.py new file mode 100644 index 0000000..57aeea8 --- /dev/null +++ b/tests/topotests/bgp_flowspec/test_bgp_flowspec_topo.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_flowspec_topo.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by 6WIND +# + +""" +test_bgp_flowspec_topo.py: Test BGP topology with Flowspec EBGP peering + + + +------+------+ + | peer1 | + | BGP peer 1 | + |192.168.0.161| + | | + +------+------+ + .2 | r1-eth0 + | + ~~~~~~~~~ + +---~~ s1 ~~------+ + ~~ ~~ + ~~~~~~~~~ + | 10.0.1.1 r1-eth0 + | 1001::1 r1-eth0 + +--------+--------+ + | r1 | + |BGP 192.168.0.162| + | | + | | + | | + +-----------------+ + +""" + +import json +import functools +import os +import sys +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + tgen.add_router("r1") + + # Setup Control Path Switch 1. r1-eth0 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + ## Add eBGP ExaBGP neighbors + peer_ip = "10.0.1.101" ## peer + peer_route = "via 10.0.1.1" ## router + peer = tgen.add_exabgp_peer("peer1", ip=peer_ip, defaultRoute=peer_route) + switch.add_link(peer) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + + tgen.start_topology() + # check for zebra capability + router = tgen.gears["r1"] + + # Get r1 reference and run Daemons + logger.info("Launching BGP and ZEBRA on r1") + router = tgen.gears["r1"] + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format("r1")) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format("r1")) + ) + router.start() + + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, "exabgp.env") + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_convergence(): + "Test for BGP topology convergence" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp convergence") + + # Expected result + router = tgen.gears["r1"] + reffile = os.path.join(CWD, "r1/summary.txt") + + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, router, "show bgp summary json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=210, wait=1) + assertmsg = "BGP router network did not converge" + assert res is None, assertmsg + + +def test_bgp_flowspec(): + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + logger.info("Check BGP FS entry for 3.3.3.3 with redirect IP") + output = router.vtysh_cmd( + "show bgp ipv4 flowspec 3.3.3.3", isjson=False, daemon="bgpd" + ) + logger.info(output) + if ( + "NH 50.0.0.2" not in output + or "FS:redirect IP" not in output + or "Packet Length < 200" not in output + ): + assertmsg = "traffic to 3.3.3.3 should have been detected as FS entry. NOK" + assert 0, assertmsg + else: + logger.info("Check BGP FS entry for 3.3.3.3 with redirect IP OK") + + logger.info("Check BGP FS entry for 3::3 with redirect IP") + output = router.vtysh_cmd( + "show bgp ipv6 flowspec 3::3", isjson=False, daemon="bgpd" + ) + logger.info(output) + if ( + "NH 50::2" not in output + or "FS:redirect IP" not in output + or "Packet Length < 200" not in output + ): + assertmsg = "traffic to 3::3 should have been detected as FS entry. NOK" + assert 0, assertmsg + else: + logger.info("Check BGP FS entry for 3::3 with redirect IP OK") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + ret = pytest.main(args) + + sys.exit(ret) diff --git a/tests/topotests/bgp_gr_functionality_topo1/bgp_gr_topojson_topo1.json b/tests/topotests/bgp_gr_functionality_topo1/bgp_gr_topojson_topo1.json new file mode 100644 index 0000000..7b3cac8 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo1/bgp_gr_topojson_topo1.json @@ -0,0 +1,115 @@ +{ + "ipv4base":"192.168.0.0", + "ipv4mask":24, + "ipv6base":"fd00::", + "ipv6mask":64, + "link_ip_start":{"ipv4":"192.168.0.0", "v4mask":24, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2-link1": {"ipv4":"auto", "ipv6":"auto"}, + "r2-link2": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + } + } + } + } + } + } + } + }, + "static_routes":[ + { + "network":"100.0.10.1/32", + "no_of_ip":5, + "next_hop":"192.168.1.2" + }, + { + "network":"1::1/128", + "no_of_ip":5, + "next_hop":"fd00:0:0:1::2" + }]}, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4":"auto", "ipv6":"auto"}, + "r1-link2": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + } + } + } + } + } + } + } + }, + "static_routes":[ + { + "network":"200.0.20.1/32", + "no_of_ip":5, + "next_hop":"192.168.1.1" + }, + { + "network":"2::1/128", + "no_of_ip":5, + "next_hop":"fd00:0:0:1::1" + }] + } + } +} diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-1.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-1.py new file mode 100644 index 0000000..5e2b2f3 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-1.py @@ -0,0 +1,1527 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 2 routers topology, r1, r2 in IBGP +- Bring up topology +- Verify for bgp to converge +- Configure BGP Garceful Restart on both the routers. + +1. Transition from Peer-level helper to Global Restarting +2. Transition from Peer-level helper to Global inherit helper +3. Transition from Peer-level restarting to Global inherit helper +4. Default GR functional mode is Helper. +5. Verify that the restarting node sets "R" bit while sending the + BGP open messages after the node restart, only if GR is enabled. +6. Verify if restarting node resets R bit in BGP open message + during normal BGP session flaps as well, even when GR restarting + mode is enabled. Here link flap happen due to interface UP/DOWN. +7. Verify if restarting node resets R bit in BGP + open message during normal BGP session flaps when GR is disabled. +8. Verify that restarting nodes set "F" bit while sending + the BGP open messages after it restarts, only when BGP GR is enabled. +9. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +10. Verify that GR helper routers keeps all the routes received + from restarting node if both the routers are configured as + GR restarting node. +11. Verify that GR helper routers delete all the routes + received from a node if both the routers are configured as GR + helper node. +12. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +13. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +14. Verify that restarting nodes reset "F" bit while sending + the BGP open messages after it's restarts, when BGP GR is **NOT** enabled. +15. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +16. Transition from Global Restarting to Disable and then Global + Disable to Restarting. +17. Transition from Global Helper to Disable and then Global + Disable to Helper. +18. Transition from Global Restart to Helper and then Global + Helper to Restart, Global Mode : GR Restarting + PerPeer Mode : GR Helper + GR Mode effective : GR Helper +19. Transition from Peer-level helper to Global Restarting, + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +20. Transition from Peer-level restart to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +21. Transition from Peer-level disabled to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Disabled + GR Mode effective : GR Disabled +22. Peer-level inherit from Global Restarting + Global Mode : GR Restart + PerPeer Mode : None + GR Mode effective : GR Restart +23. Transition from Peer-level disable to Global inherit helper + Global Mode : None + PerPeer Mode : GR Disable + GR Mode effective : GR Disable + +These tests have been broken up into 4 sub python scripts because +the totality of this run was fairly significant. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_f_bit, + verify_bgp_convergence, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + shutdown_bringup_interface, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +NEXT_HOP_IP = {"ipv4": "192.168.1.10", "ipv6": "fd00:0:0:1::10"} +NEXT_HOP_IP_1 = {"ipv4": "192.168.0.1", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "192.168.0.2", "ipv6": "fd00::2"} +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 20 +PREFERRED_NEXT_HOP = "link_local" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"]["r2-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def BGP_GR_TC_50_p1(request): + """ + Test Objective : Transition from Peer-level helper to Global inherit helper + Global Mode : None + PerPeer Mode : Helper + GR Mode effective : GR Helper + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 as GR helper node at per Peer-level for R2" + " and configure R2 as global restarting node." + ) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bring up BGP on R2 and remove Peer-level GR config from R1 ") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify on R2 that R1 still advertises GR capabilities as a helper node") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Start BGP on R2") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_51_p1(request): + """ + Test Objective : Transition from Peer-level restarting to Global inherit helper + Global Mode : None + PerPeer Mode : GR Restart + GR Mode effective : GR Restart + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure R1 as GR restarting node at per Peer-level for R2") + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, "r2", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB & R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, "r2", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bring up BGP on R1 and remove Peer-level GR config") + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": False} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGPd on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Start BGP on R2") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_53_p1(request): + """ + Test Objective : Default GR functional mode is Helper. + Global Mode : None + PerPeer Mode : None + GR Mode effective : GR Helper + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("configure R2 as global restarting node") + + input_dict = {"r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}} + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step( + "Verify on R2 that R1 advertises GR capabilities as a helper node based on inherit" + ) + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Kill BGPd on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Start BGP on R2") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_4_p0(request): + """ + Test Objective : Verify that the restarting node sets "R" bit while sending the + BGP open messages after the node restart, only if GR is enabled. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" " [Restart Mode]R1-----R2[Helper Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : R2 goes for reload ") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info( + "[Phase 3] : R2 is still down, restart time {} sec." + "So time verify the routes are present in BGP RIB and ZEBRA ".format( + GR_RESTART_TIMER + ) + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Phase 5] : R2 is about to come up now ") + start_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r1", peer="r2") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_5_1_2_p1(request): + """ + Test Objective : Verify if restarting node resets R bit in BGP open message + during normal BGP session flaps as well, even when GR restarting mode is enabled. + Here link flap happen due to interface UP/DOWN. + + """ + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" " [Restart Mode]R1-----R2[Restart Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : Now flap the link running the BGP session ") + # Shutdown interface + intf = "r2-r1-eth0" + shutdown_bringup_interface(tgen, "r2", intf) + + # Bring up Interface + shutdown_bringup_interface(tgen, "r2", intf, ifaceaction=True) + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r1", peer="r2") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : Restart BGPd on router R2. ") + kill_router_daemons(tgen, "r2", ["bgpd"]) + + start_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r1", peer="r2") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_6_1_2_p1(request): + """ + Test Objective : Verify if restarting node resets R bit in BGP + open message during normal BGP session flaps when GR is disabled. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" "[Restart Mode]R1-----R2[Helper Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 1] : Changing mode" "[Disable Mode]R1-----R2[Helper Mode]") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verify GR stats + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + } + + # here the verify_graceful_restart fro the neighbor would be + # "NotReceived" as the latest GR config is not yet applied. + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : Now flap the link running the BGP session ") + # Shutdown interface + intf = "r2-r1-eth0" + shutdown_bringup_interface(tgen, "r2", intf) + + # Bring up Interface + shutdown_bringup_interface(tgen, "r2", intf, ifaceaction=True) + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: R-bit should not be set to True in r2\n" + "Found: {}".format(tc_name, result) + ) + + logger.info("Restart BGPd on R2 ") + kill_router_daemons(tgen, "r2", ["bgpd"]) + + start_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: R-bit should not be set to True in r2\n" + "Found: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-2.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-2.py new file mode 100644 index 0000000..13c5ba5 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-2.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 2 routers topology, r1, r2 in IBGP +- Bring up topology +- Verify for bgp to converge +- Configure BGP Garceful Restart on both the routers. + +1. Transition from Peer-level helper to Global Restarting +2. Transition from Peer-level helper to Global inherit helper +3. Transition from Peer-level restarting to Global inherit helper +4. Default GR functional mode is Helper. +5. Verify that the restarting node sets "R" bit while sending the + BGP open messages after the node restart, only if GR is enabled. +6. Verify if restarting node resets R bit in BGP open message + during normal BGP session flaps as well, even when GR restarting + mode is enabled. Here link flap happen due to interface UP/DOWN. +7. Verify if restarting node resets R bit in BGP + open message during normal BGP session flaps when GR is disabled. +8. Verify that restarting nodes set "F" bit while sending + the BGP open messages after it restarts, only when BGP GR is enabled. +9. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +10. Verify that GR helper routers keeps all the routes received + from restarting node if both the routers are configured as + GR restarting node. +11. Verify that GR helper routers delete all the routes + received from a node if both the routers are configured as GR + helper node. +12. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +13. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +14. Verify that restarting nodes reset "F" bit while sending + the BGP open messages after it's restarts, when BGP GR is **NOT** enabled. +15. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +16. Transition from Global Restarting to Disable and then Global + Disable to Restarting. +17. Transition from Global Helper to Disable and then Global + Disable to Helper. +18. Transition from Global Restart to Helper and then Global + Helper to Restart, Global Mode : GR Restarting + PerPeer Mode : GR Helper + GR Mode effective : GR Helper +19. Transition from Peer-level helper to Global Restarting, + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +20. Transition from Peer-level restart to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +21. Transition from Peer-level disabled to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Disabled + GR Mode effective : GR Disabled +22. Peer-level inherit from Global Restarting + Global Mode : GR Restart + PerPeer Mode : None + GR Mode effective : GR Restart +23. Transition from Peer-level disable to Global inherit helper + Global Mode : None + PerPeer Mode : GR Disable + GR Mode effective : GR Disable + +These tests have been broken up into 4 sub python scripts because +the totality of this run was fairly significant. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_f_bit, + verify_bgp_convergence, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + shutdown_bringup_interface, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +NEXT_HOP_IP = {"ipv4": "192.168.1.10", "ipv6": "fd00:0:0:1::10"} +NEXT_HOP_IP_1 = {"ipv4": "192.168.0.1", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "192.168.0.2", "ipv6": "fd00::2"} +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 20 +PREFERRED_NEXT_HOP = "link_local" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"]["r2-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def test_BGP_GR_TC_8_p1(request): + """ + Test Objective : Verify that restarting nodes set "F" bit while sending + the BGP open messages after it restarts, only when BGP GR is enabled. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" " [Restart Mode]R1-----R2[Restart Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : R1 goes for reload ") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Phase 3] : R1 is about to come up now ") + start_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r2", peer="r1") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_f_bit(tgen, topo, addr_type, input_dict, dut="r2", peer="r1") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py new file mode 100644 index 0000000..1a8f830 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-3.py @@ -0,0 +1,2720 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 2 routers topology, r1, r2 in IBGP +- Bring up topology +- Verify for bgp to converge +- Configure BGP Garceful Restart on both the routers. + +1. Transition from Peer-level helper to Global Restarting +2. Transition from Peer-level helper to Global inherit helper +3. Transition from Peer-level restarting to Global inherit helper +4. Default GR functional mode is Helper. +5. Verify that the restarting node sets "R" bit while sending the + BGP open messages after the node restart, only if GR is enabled. +6. Verify if restarting node resets R bit in BGP open message + during normal BGP session flaps as well, even when GR restarting + mode is enabled. Here link flap happen due to interface UP/DOWN. +7. Verify if restarting node resets R bit in BGP + open message during normal BGP session flaps when GR is disabled. +8. Verify that restarting nodes set "F" bit while sending + the BGP open messages after it restarts, only when BGP GR is enabled. +9. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +10. Verify that GR helper routers keeps all the routes received + from restarting node if both the routers are configured as + GR restarting node. +11. Verify that GR helper routers delete all the routes + received from a node if both the routers are configured as GR + helper node. +12. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +13. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +14. Verify that restarting nodes reset "F" bit while sending + the BGP open messages after it's restarts, when BGP GR is **NOT** enabled. +15. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +16. Transition from Global Restarting to Disable and then Global + Disable to Restarting. +17. Transition from Global Helper to Disable and then Global + Disable to Helper. +18. Transition from Global Restart to Helper and then Global + Helper to Restart, Global Mode : GR Restarting + PerPeer Mode : GR Helper + GR Mode effective : GR Helper +19. Transition from Peer-level helper to Global Restarting, + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +20. Transition from Peer-level restart to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +21. Transition from Peer-level disabled to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Disabled + GR Mode effective : GR Disabled +22. Peer-level inherit from Global Restarting + Global Mode : GR Restart + PerPeer Mode : None + GR Mode effective : GR Restart +23. Transition from Peer-level disable to Global inherit helper + Global Mode : None + PerPeer Mode : GR Disable + GR Mode effective : GR Disable + +These tests have been broken up into 4 sub python scripts because +the totality of this run was fairly significant. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_f_bit, + verify_bgp_convergence, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + shutdown_bringup_interface, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +NEXT_HOP_IP = {"ipv4": "192.168.1.10", "ipv6": "fd00:0:0:1::10"} +NEXT_HOP_IP_1 = {"ipv4": "192.168.0.1", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "192.168.0.2", "ipv6": "fd00::2"} +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 20 +PREFERRED_NEXT_HOP = "link_local" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"]["r2-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def BGP_GR_TC_50_p1(request): + """ + Test Objective : Transition from Peer-level helper to Global inherit helper + Global Mode : None + PerPeer Mode : Helper + GR Mode effective : GR Helper + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 as GR helper node at per Peer-level for R2" + " and configure R2 as global restarting node." + ) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bring up BGP on R2 and remove Peer-level GR config from R1 ") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify on R2 that R1 still advertises GR capabilities as a helper node") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Start BGP on R2") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_19_p1(request): + """ + Test Objective : Verify that GR helper routers keeps all the routes received + from restarting node if both the routers are configured as GR restarting node. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info("[Phase 1] : Test Setup [Helper]R1-----R2[Restart] initialized ") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info( + "[Phase 2] : R1's Gr state cahnge to Graceful" + " Restart without resetting the session " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info( + "[Phase 3] : R2 is still down, restart time 120 sec." + " So time verify the routes are present in BGP RIB and ZEBRA " + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_20_p1(request): + """ + Test Objective : Verify that GR helper routers delete all the routes + received from a node if both the routers are configured as GR helper node. + """ + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info("[Phase 1] : Test Setup [Helper]R1-----R2[Helper] initialized ") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Phase 5] : R2 is about to come up now ") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_31_1_p1(request): + """ + After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" " [Helper Mode]R2-----R1[Restart Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : R1 Goes from Restart to Disable Mode ") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verify GR stats + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : R1 goes for reload ") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info( + "[Phase 3] : R1 is still down, restart time 120 sec." + " So time verify the routes are not present in BGP RIB and ZEBRA" + ) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Phase 4] : R1 is about to come up now ") + start_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Phase 5] : R1 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_31_2_p1(request): + """ + After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup " "[Disable Mode]R1-----R2[Helper Mode] initialized " + ) + + # Configure graceful-restart + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 2] : R1 Goes from Disable to Restart Mode ") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verify GR stats + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + } + + logger.info("[Phase 4] : R1 is UP and GR state is correct ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 3] : R1 goes for reload ") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info( + "[Phase 4] : R1 is still down, restart time 120 sec." + " So time verify the routes are present in BGP RIB and ZEBRA " + ) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Phase 6] : R1 is about to come up now ") + start_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Phase 4] : R1 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_9_p1(request): + """ + Test Objective : Verify that restarting nodes reset "F" bit while sending + the BGP open messages after it's restarts, when BGP GR is **NOT** enabled. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Phase 1] : Test Setup" " [Restart Mode]R1-----R2[Helper Mode] Initiliazed " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 2] : R2 goes for reload ") + kill_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info( + "[Phase 3] : R2 is still down, restart time 120 sec." + "So time verify the routes are present in BGP RIB and ZEBRA " + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Phase 5] : R2 is about to come up now ") + start_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), "BGP Convergence after BGPd restart" " :Failed \n Error:{}".format(result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r1", peer="r2") + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_f_bit( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: F-bit should not be set to True in r1\n" + "Found: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_17_p1(request): + """ + Test Objective : Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info("[Phase 1] : Test Setup [Disable]R1-----R2[Restart] " "Initiliazed ") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {"graceful-restart": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 2] : R2 goes for reload ") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info( + "[Phase 3] : R2 is still down, restart time 120 sec." + " So time verify the routes are present in BGP RIB and ZEBRA " + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Phase 5] : R2 is about to come up now ") + start_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("[Phase 4] : R2 is UP now, so time to collect GR stats ") + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), "BGP Convergence after BGPd restart" " :Failed \n Error:{}".format(result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_r_bit( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: R-bit should not be set to True in r1\n" + "Found: {}".format(tc_name, result) + ) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_43_p1(request): + """ + Test Objective : Transition from Global Restarting to Disable + and then Global Disable to Restarting. + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure R1 and R2 as GR restarting node in global level") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps BGP routes in zebra and R2 retains" + " the stale entry for received routes from R1" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Bring up BGPd on R1 and configure it as GR disabled node in global level") + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": False, + "graceful-restart-disable": True, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 doesn't advertise any GR capabilities") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-disable": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 flush all BGP routes from RIB & FIB and FIB and R2" + " does not retain stale entry for received routes from R1" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + protocol = "bgp" + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + step( + "Bring up BGPd on R1 and configure it as GR" " restarting node in global level" + ) + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = {"r1": {"bgp": {"graceful-restart": {"graceful-restart": True}}}} + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps BGP routes in zebra and R2" + " retains the stale entry for received routes from R1" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_44_p1(request): + """ + Test Objective : Transition from Global Helper to Disable + and then Global Disable to Helper. + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R2 as GR restating node in global level and" + " leave R1 without any GR related config" + ) + + input_dict = {"r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}} + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-helper": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step("Verify that R1 keeps stale entry for BGP routes when BGPd on R2 is down") + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Bring up BGPd on R2 and configure R1 as GR disabled node in global level") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-disable": True, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 doesn't advertise any GR capabilities") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-disable": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step("Verify that R1 does not retain stale entry for received routes from R2") + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + next_hop = NEXT_HOP_IP_2[addr_type] + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + step("Bring up BGPd on R2 and remove GR related config from R1 in global level") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-disable": False}}} + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-helper": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step("Verify that R1 keeps stale entry for BGP routes when BGPd on R2 is down") + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_45_p1(request): + """ + Test Objective : Transition from Global Restart to Helper + and then Global Helper to Restart. + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure R1 and R2 as GR restarting node in global level") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps BGP routes in zebra and R2" + " retains the stale entry for received routes from R1" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Bring up BGPd on R1 and remove GR related config in global level") + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": False, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart-helper": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step("Verify that R1 keeps stale entry for BGP routes when BGPd on R2 is down") + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Bring up BGPd on R2 and configure R1 as GR restarting node in global level") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps BGP routes in zebra and R2" + " retains the stale entry for received routes from R1" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py new file mode 100644 index 0000000..31aaa0b --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo1/test_bgp_gr_functionality_topo1-4.py @@ -0,0 +1,1685 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 2 routers topology, r1, r2 in IBGP +- Bring up topology +- Verify for bgp to converge +- Configure BGP Garceful Restart on both the routers. + +1. Transition from Peer-level helper to Global Restarting +2. Transition from Peer-level helper to Global inherit helper +3. Transition from Peer-level restarting to Global inherit helper +4. Default GR functional mode is Helper. +5. Verify that the restarting node sets "R" bit while sending the + BGP open messages after the node restart, only if GR is enabled. +6. Verify if restarting node resets R bit in BGP open message + during normal BGP session flaps as well, even when GR restarting + mode is enabled. Here link flap happen due to interface UP/DOWN. +7. Verify if restarting node resets R bit in BGP + open message during normal BGP session flaps when GR is disabled. +8. Verify that restarting nodes set "F" bit while sending + the BGP open messages after it restarts, only when BGP GR is enabled. +9. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +10. Verify that GR helper routers keeps all the routes received + from restarting node if both the routers are configured as + GR restarting node. +11. Verify that GR helper routers delete all the routes + received from a node if both the routers are configured as GR + helper node. +12. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +13. After BGP neighborship is established and GR capability is exchanged, + transition restarting router to disabled state and vice versa. +14. Verify that restarting nodes reset "F" bit while sending + the BGP open messages after it's restarts, when BGP GR is **NOT** enabled. +15. Verify that only GR helper routers keep the stale + route entries, not any GR disabled router. +16. Transition from Global Restarting to Disable and then Global + Disable to Restarting. +17. Transition from Global Helper to Disable and then Global + Disable to Helper. +18. Transition from Global Restart to Helper and then Global + Helper to Restart, Global Mode : GR Restarting + PerPeer Mode : GR Helper + GR Mode effective : GR Helper +19. Transition from Peer-level helper to Global Restarting, + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +20. Transition from Peer-level restart to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting +21. Transition from Peer-level disabled to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Disabled + GR Mode effective : GR Disabled +22. Peer-level inherit from Global Restarting + Global Mode : GR Restart + PerPeer Mode : None + GR Mode effective : GR Restart +23. Transition from Peer-level disable to Global inherit helper + Global Mode : None + PerPeer Mode : GR Disable + GR Mode effective : GR Disable + +These tests have been broken up into 4 sub python scripts because +the totality of this run was fairly significant. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_f_bit, + verify_bgp_convergence, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + shutdown_bringup_interface, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +NEXT_HOP_IP = {"ipv4": "192.168.1.10", "ipv6": "fd00:0:0:1::10"} +NEXT_HOP_IP_1 = {"ipv4": "192.168.0.1", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "192.168.0.2", "ipv6": "fd00::2"} +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 20 +PREFERRED_NEXT_HOP = "link_local" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"]["r2-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def BGP_GR_TC_50_p1(request): + """ + Test Objective : Transition from Peer-level helper to Global inherit helper + Global Mode : None + PerPeer Mode : Helper + GR Mode effective : GR Helper + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 as GR helper node at per Peer-level for R2" + " and configure R2 as global restarting node." + ) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a helper node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bring up BGP on R2 and remove Peer-level GR config from R1 ") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, "r1", neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify on R2 that R1 still advertises GR capabilities as a helper node") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, "r2", "r1", addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, "r2", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + next_hop = next_hop_per_address_family( + tgen, "r1", "r2", addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, "r1", input_topo, next_hop) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Start BGP on R2") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_46_p1(request): + """ + Test Objective : transition from Peer-level helper to Global Restarting + Global Mode : GR Restarting + PerPeer Mode : GR Helper + GR Mode effective : GR Helper + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 and R2 as GR restarting node in global" + " and helper in per-Peer-level" + ) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in RIB & FIB and R2 keeps stale entries in FIB using" + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "Bring up BGP on R1 and remove Peer-level GR config" + " from R1 following by a session reset" + ) + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-helper": False} + } + } + } + } + }, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB command and R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_47_p1(request): + """ + Test Objective : transition from Peer-level restart to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Restarting + GR Mode effective : GR Restarting + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure R1 and R2 as GR restarting node in global and per-Peer-level") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": True} + } + } + } + } + }, + }, + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB and R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "Bring up BGP on R1 and remove Peer-level GR" + " config from R1 following by a session reset" + ) + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart": False} + } + } + } + } + }, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 still advertises GR capabilities as a restarting node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB and R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_48_p1(request): + """ + Test Objective : transition from Peer-level disabled to Global Restart + Global Mode : GR Restarting + PerPeer Mode : GR Disabled + GR Mode effective : GR Disabled + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 as GR restarting node in global level and" + " GR Disabled in per-Peer-level" + ) + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + }, + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 does't advertise any GR capabilities") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step("Verify on R2 and R1 that none of the routers keep stale entries") + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + step("Bring up BGP on R1 and remove Peer-level GR config from R1") + + start_router_daemons(tgen, "r1", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": False} + } + } + } + } + }, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 advertises GR capabilities as a restarting node") + + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB and R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + # restart the daemon or we get warnings in the follow-on tests + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_49_p1(request): + """ + Test Objective : Peer-level inherit from Global Restarting + Global Mode : GR Restart + PerPeer Mode : None + GR Mode effective : GR Restart + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Configure R1 as GR restarting node in global level") + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step( + "Verify that R2 receives GR restarting capabilities" + " from R1 based on inheritence" + ) + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGPd on router R1") + + kill_router_daemons(tgen, "r1", ["bgpd"]) + + step( + "Verify that R1 keeps the stale entries in FIB and R2 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + peer = "r2" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r2" + peer = "r1" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def BGP_GR_TC_52_p1(request): + """ + Test Objective : Transition from Peer-level disable to Global inherit helper + Global Mode : None + PerPeer Mode : GR Disable + GR Mode effective : GR Disable + + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step( + "Configure R1 as GR disabled node at per Peer-level for R2" + " & R2 as GR restarting node" + ) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step("Verify on R2 that R1 does't advertise any GR capabilities") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 doesn't keep RIB & FIB entries." + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop, protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + step("Bring up BGP on R2 and remove Peer-level GR config from R1") + + start_router_daemons(tgen, "r2", ["bgpd"]) + + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"graceful-restart-disable": False} + } + } + } + } + }, + } + } + } + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + step( + "Verify on R2 that R1 advertises GR capabilities as a helper node from global inherit" + ) + + input_dict = { + "r1": {"bgp": {"graceful-restart": {"graceful-restart-helper": True}}}, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + step("Kill BGP on R2") + + kill_router_daemons(tgen, "r2", ["bgpd"]) + + step( + "Verify that R2 keeps the stale entries in FIB & R1 keeps stale entries in RIB & FIB" + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + peer = "r1" + protocol = "bgp" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_1 + ) + input_topo = {"r1": topo["routers"]["r1"]} + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + dut = "r1" + peer = "r2" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2 + ) + input_topo = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert ( + result is True + ), "Testcase {} :Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo2/bgp_gr_topojson_topo2.json b/tests/topotests/bgp_gr_functionality_topo2/bgp_gr_topojson_topo2.json new file mode 100644 index 0000000..75f192d --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo2/bgp_gr_topojson_topo2.json @@ -0,0 +1,334 @@ +{ + "ipv4base":"192.168.0.0", + "ipv4mask":24, + "ipv6base":"fd00::", + "ipv6mask":64, + "link_ip_start":{"ipv4":"192.168.0.0", "v4mask":24, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4":"auto", "ipv6":"auto"}, + "r3": {"ipv4":"auto", "ipv6":"auto"}, + "r5": {"ipv4":"auto", "ipv6":"auto"}, + "r6": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "101.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + }, + "r5": { + "dest_link": { + "r1": {} + } + }, + "r6": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "1::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + } + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + }, + "r5": { + "dest_link": { + "r1": { + } + } + }, + "r6": { + "dest_link": { + "r1": { + } + } + } + } + } + } + } + } + }, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "102.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "2::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r2": { + } + } + } + } + } + } + } + } + }, + "r3":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4":"auto", "ipv6":"auto"}, + "r4": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "103.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "3::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r3": { + } + } + }, + "r4": { + "dest_link": { + "r3": { + } + } + } + } + } + } + } + } + }, + "r4":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "104.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "4::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r3": { + "dest_link": { + "r4": { + } + } + } + } + } + } + } + } + }, + "r5":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as": "500", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "105.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r5": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "5::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r5": { + } + } + } + } + } + } + } + } + }, + "r6":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as": "600", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "106.0.20.1/32", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r6": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "6::1/128", + "no_of_network": 5 + } + ], + "neighbor": { + "r1": { + "dest_link": { + "r6": { + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-1.py b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-1.py new file mode 100644 index 0000000..cb6bf56 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-1.py @@ -0,0 +1,1497 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 7 routers topology +- Bring up topology +- Verify for bgp to converge +- Configure BGP Graceful Restart on both the routers. + +TC_1_2: + Verify that EOR message is sent out only after initial convergence + Verify whether EOR message is received from all the peers after restart +TC_3: + Verify the selection deferral timer functionality when EOR is not sent + by the helper router +TC_11: + Verify that selection-deferral timer sets the maximum time to + avoid deadlock during which the best-path +TC_10: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_15: + Test Objective : Test GR scenarios by enabling Graceful Restart + for multiple address families.. +TC_16: + Test Objective : Verify BGP-GR feature when restarting node + is a transit router for it's iBGP peers. +TC_18: + Test Objective : Verify that GR helper router deletes stale routes + received from restarting node, if GR capability is not present in +TC_19: + Test Objective : Verify that GR routers keeps all the routes + received from restarting node if both the routers are +TC_26: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_28: + Test Objective : Verify if helper node goes down before restarting + node comes up online, helper node sets the R-bit to avoid dead-lock +TC_29: + Test Objective : Change timers on the fly, and + verify if it takes immediate effect. +TC_33: + Test Objective : Helper router receives same prefixes from two + different routers (GR-restarting and GR-disabled). Keeps the +TC_34_1: + Test Objective : Restarting node doesn't preserve forwarding + state, helper router should not keep the stale entries. +TC_34_2: + Test Objective : Restarting node doesn't preserve the forwarding + state verify the behaviour on helper node, if it still keeps the +TC_32: + Test Objective : Restarting node is connected to multiple helper + nodes, one of them doesn't send EOR to restarting router. Verify +TC_37: + Test Objective : Verify if helper node restarts before sending the + EOR message, restarting node doesn't wait until stale path timer +TC_30: + Test Objective : Restarting node removes stale routes from Zebra + after receiving an EOR from helper router. + +""" + +import os +import sys +import time +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_eor, + verify_f_bit, + verify_bgp_convergence, + verify_gr_address_family, + modify_bgp_config_when_bgpd_down, + verify_graceful_restart_timers, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 5 +GR_SELECT_DEFER_TIMER = 5 +GR_STALEPATH_TIMER = 5 +PREFERRED_NEXT_HOP = "link_local" +NEXT_HOP_4 = ["192.168.1.1", "192.168.4.2"] +NEXT_HOP_6 = ["fd00:0:0:1::1", "fd00:0:0:4::2"] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + logger.info("configure_gr_followed_by_clear: dut %s peer %s", dut, peer) + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"][dut][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"][peer][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family(tgen, dut, peer, addr_type, next_hop_dict): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def test_BGP_GR_TC_1_2_p0(request): + """ + Verify that EOR message is sent out only after initial convergence + Verify whether EOR message is received from all the peers after restart + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "Verify EOR Sent and Received : BGP_GR_TC_1_2 >> " + "BGP GR [Helper Mode]R3-----R1[Restart Mode] " + ) + + # Configure graceful-restart + input_dict = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes received from router R3 + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("R1 goes for reload") + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("Starting bgpd process") + start_router_daemons(tgen, "r1", ["bgpd"]) + logger.info("R1 is UP Now") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes received from router R3 + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying EOR on restarting router + result = verify_eor(tgen, topo, addr_type, input_dict, dut="r3", peer="r1") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_3_p0(request): + """ + Verify the selection deferral timer functionality when EOR is not sent + by the helper router + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Verify route download to RIB: BGP_GR_TC_3 >> " + "BGP GR [Helper Mode]R1-----R2[Restart Mode] " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "disable-eor": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + "timer": {"select-defer-time": GR_SELECT_DEFER_TIMER}, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes received from router R1 + dut = "r2" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("R2 goes for reload ") + kill_router_daemons(tgen, "r2", ["bgpd"]) + + logger.info("R2 is about to come up now") + start_router_daemons(tgen, "r2", ["bgpd"]) + logger.info("R2 is UP Now") + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes received from router R1 + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verify EOR on restarting router + result = verify_eor( + tgen, topo, addr_type, input_dict, dut="r2", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r2\n" + "Found: {}".format(tc_name, result) + ) + + logger.info( + "Waiting for selection deferral timer({} sec)..".format(GR_SELECT_DEFER_TIMER) + ) + sleep(GR_SELECT_DEFER_TIMER) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + result = verify_rib(tgen, addr_type, "r2", input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_TC_11_p0(request): + """ + Verify that selection-deferral timer sets the maximum time to + avoid deadlock during which the best-path + selection process is deferred, after a peer session was restarted + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info("Verify EOR Sent after deferral timeout : BGP_GR_TC_11") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "select-defer-time": GR_SELECT_DEFER_TIMER, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}}, + "r3": {"dest_link": {"r1": {"graceful-restart": True}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}}, + "r3": {"dest_link": {"r1": {"graceful-restart": True}}}, + } + } + }, + }, + } + }, + "r3": { + "bgp": { + "graceful-restart": {"disable-eor": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1") + clear_bgp(tgen, addr_type, "r3") + + result = verify_bgp_convergence_from_running_config(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes received from router R1 + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("R1 goes for reload") + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("Starting bgpd process") + start_router_daemons(tgen, "r1", ["bgpd"]) + logger.info("R1 is UP Now") + + for addr_type in ADDR_TYPES: + # Verify EOR on restarting router + result = verify_eor( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r1\n" + "Found: {}".format(tc_name, result) + ) + + logger.info( + "Waiting for selection deferral timer({} sec).. ".format( + GR_SELECT_DEFER_TIMER + 2 + ) + ) + sleep(GR_SELECT_DEFER_TIMER + 2) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes received from router R1 + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying EOR on restarting router + result = verify_eor( + tgen, topo, addr_type, input_dict, dut="r3", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r3\n" + "Found: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_10_p2(request): + """ + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Test Setup: [Helper Mode]R3-----R1[Restart Mode] initialized") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "next_hop_self": True, + "graceful-restart": True, + "activate": "ipv6", + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "next_hop_self": True, + "graceful-restart": True, + "activate": "ipv4", + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "graceful-restart-helper": True, + "activate": "ipv6", + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "graceful-restart-helper": True, + "activate": "ipv4", + } + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + step( + "Verifying GR config and operational state for addr_type {}".format( + addr_type + ) + ) + + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r3" + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv4Unicast", + dut="r1", + peer="r3", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv6Unicast", + dut="r1", + peer="r3", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv4Unicast", + dut="r3", + peer="r1", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv6Unicast", + dut="r3", + peer="r1", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Killing bgpd on r1") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Starting bgpd on r1") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def BGP_GR_16_p2(request): + """ + Test Objective : Verify BGP-GR feature when restarting node + is a transit router for it's iBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Helper Mode]R3-----R1[Restart Mode] initialized" + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart": True, + "next_hop_self": True, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart": True, + "next_hop_self": True, + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info( + "[Step 2] : Test Setup " + "[Helper Mode]R3-----R1[Restart Mode]" + "--------R6[Helper Mode] initialized" + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_convergence_from_running_config(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_18_p1(request): + """ + Test Objective : Verify that GR helper router deletes stale routes + received from restarting node, if GR capability is not present in + restarting node's OPEN message. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Helper Mode]R6-----R1[Restart Mode] initialized" + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r6": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r6") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r6" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info( + "[Step 2] : Test Setup " + "[Helper Mode]R6-----R1[Restart Mode]" + "--------R2[Helper Mode] initialized" + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r2" + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 3] : Configure R1 to prevent sending EOR") + + # Modify graceful-restart config to prevent sending EOR + input_dict_3 = {"r1": {"bgp": {"graceful-restart": {"disable-eor": True}}}} + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_3) + + # Modify configuration to delete routes + network = {"ipv4": "101.0.20.1/32", "ipv6": "1::1/128"} + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 5, + "delete": True, + } + ] + } + } + } + } + } + } + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Modify graceful-restart config + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + }, + "r6": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + }, + "r6": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + }, + } + } + }, + } + } + } + } + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + logger.info("[Step 4] : Bring up the BGPd daemon on R1 for 30" " seconds..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying BGP RIB routes + dut = "r2" + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-2.py b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-2.py new file mode 100644 index 0000000..cf9a474 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-2.py @@ -0,0 +1,1194 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 7 routers topology +- Bring up topology +- Verify for bgp to converge +- Configure BGP Graceful Restart on both the routers. + +TC_1_2: + Verify that EOR message is sent out only after initial convergence + Verify whether EOR message is received from all the peers after restart +TC_3: + Verify the selection deferral timer functionality when EOR is not sent + by the helper router +TC_11: + Verify that selection-deferral timer sets the maximum time to + avoid deadlock during which the best-path +TC_10: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_15: + Test Objective : Test GR scenarios by enabling Graceful Restart + for multiple address families.. +TC_16: + Test Objective : Verify BGP-GR feature when restarting node + is a transit router for it's iBGP peers. +TC_18: + Test Objective : Verify that GR helper router deletes stale routes + received from restarting node, if GR capability is not present in +TC_19: + Test Objective : Verify that GR routers keeps all the routes + received from restarting node if both the routers are +TC_26: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_28: + Test Objective : Verify if helper node goes down before restarting + node comes up online, helper node sets the R-bit to avoid dead-lock +TC_29: + Test Objective : Change timers on the fly, and + verify if it takes immediate effect. +TC_33: + Test Objective : Helper router receives same prefixes from two + different routers (GR-restarting and GR-disabled). Keeps the +TC_34_1: + Test Objective : Restarting node doesn't preserve forwarding + state, helper router should not keep the stale entries. +TC_34_2: + Test Objective : Restarting node doesn't preserve the forwarding + state verify the behaviour on helper node, if it still keeps the +TC_32: + Test Objective : Restarting node is connected to multiple helper + nodes, one of them doesn't send EOR to restarting router. Verify +TC_37: + Test Objective : Verify if helper node restarts before sending the + EOR message, restarting node doesn't wait until stale path timer +TC_30: + Test Objective : Restarting node removes stale routes from Zebra + after receiving an EOR from helper router. + +""" + +import os +import sys +import time +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_eor, + verify_f_bit, + verify_bgp_convergence, + verify_gr_address_family, + modify_bgp_config_when_bgpd_down, + verify_graceful_restart_timers, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 5 +GR_SELECT_DEFER_TIMER = 5 +GR_STALEPATH_TIMER = 5 +PREFERRED_NEXT_HOP = "link_local" +NEXT_HOP_4 = ["192.168.1.1", "192.168.4.2"] +NEXT_HOP_6 = ["fd00:0:0:1::1", "fd00:0:0:4::2"] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + logger.info("configure_gr_followed_by_clear: dut %s peer %s", dut, peer) + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"][dut][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"][peer][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family(tgen, dut, peer, addr_type, next_hop_dict): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def test_BGP_GR_26_p2(request): + """ + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Helper Mode]R3-----R1[Restart Mode] initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart": True, + "next_hop_self": True, + "activate": "ipv6", + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart": True, + "next_hop_self": True, + "activate": "ipv4", + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "graceful-restart-helper": True, + "activate": "ipv6", + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "graceful-restart-helper": True, + "activate": "ipv4", + } + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r1" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes + dut = "r3" + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv4Unicast", + dut="r1", + peer="r3", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv6Unicast", + dut="r1", + peer="r3", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv4Unicast", + dut="r3", + peer="r1", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # verify multi address family + result = verify_gr_address_family( + tgen, + topo, + addr_type, + "ipv6Unicast", + dut="r3", + peer="r1", + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_28_p1(request): + """ + Test Objective : Verify if helper node goes down before restarting + node comes up online, helper node sets the R-bit to avoid dead-lock + till SDT expiry. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "Test Case: test_BGP_GR_chaos_28 :" + "[Helper Mode]R3-----R1[Restart Mode] initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 1] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 2] : Kill BGPd daemon on R3..") + + # Kill BGPd daemon on R3 + kill_router_daemons(tgen, "r3", ["bgpd"]) + + logger.info("[Step 3] : Start BGPd daemon on R1..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 4] : Start BGPd daemon on R3..") + + # Start BGPd daemon on R3 + start_router_daemons(tgen, "r3", ["bgpd"]) + + # Verify r_bit + for addr_type in ADDR_TYPES: + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r3", peer="r1") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_29_p1(request): + """ + Test Objective : Change timers on the fly, and + verify if it takes immediate effect. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_29" + " BGP GR [Helper Mode]R3-----R1[Restart Mode]" + " and [restart-time 150]R1 initialized" + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"timer": {"restart-time": GR_RESTART_TIMER}}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verify graceful-restart timers + input_dict_2 = { + "r1": { + "bgp": { + "graceful-restart": {"timer": {"restart-time": GR_RESTART_TIMER + 5}} + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r1": { + "bgp": { + "graceful-restart": {"timer": {"restart-time": GR_RESTART_TIMER}} + } + } + } + + result = verify_graceful_restart_timers( + tgen, topo, addr_type, input_dict_2, dut="r3", peer="r1" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes before shutting down BGPd daemon + dut = "r3" + input_dict = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 2] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 3] : Wait for {} seconds..".format(GR_RESTART_TIMER)) + + # Waiting for GR_RESTART_TIMER + sleep(GR_RESTART_TIMER) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes before shutting down BGPd daemon + input_dict = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Step 4] : Start BGPd daemon on R1..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_33_p1(request): + """ + Test Objective : Helper router receives same prefixes from two + different routers (GR-restarting and GR-disabled). Keeps the + stale entry only for GR-restarting node(next-hop is correct). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_33 " + "BGP GR " + "[Restart Mode]R1--R3[Helper Mode]--R4[Disabled Mode]" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 2] : Advertise same networks from R1 and R4..") + + # Api call to delete advertised networks + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "200.0.20.1/32", + "no_of_network": 2, + } + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "2001::1/128", "no_of_network": 2} + ] + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.0.20.1/32", "no_of_network": 2} + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "2001::1/128", "no_of_network": 2} + ] + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + peer1 = "r1" + peer2 = "r4" + intf1 = topo["routers"][peer1]["links"][dut]["interface"] + intf2 = topo["routers"][peer2]["links"][dut]["interface"] + + if addr_type == "ipv4": + next_hop_4 = NEXT_HOP_4 + result = verify_rib(tgen, addr_type, dut, input_dict_2, next_hop_4) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + if addr_type == "ipv6": + if "link_local" in PREFERRED_NEXT_HOP: + next_hop1 = get_frr_ipv6_linklocal(tgen, peer1, intf=intf1) + next_hop2 = get_frr_ipv6_linklocal(tgen, peer2, intf=intf2) + + next_hop_6 = [next_hop1, next_hop2] + else: + next_hop_6 = NEXT_HOP_6 + + result = verify_rib(tgen, addr_type, dut, input_dict_2, next_hop_6) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 3] : Kill BGPd daemon on R1 and R4..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + # Kill BGPd daemon on R4 + kill_router_daemons(tgen, "r4", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + next_hop_6 = ["fd00:0:0:1::1"] + if addr_type == "ipv4": + next_hop_4 = NEXT_HOP_4[0] + + result = verify_rib(tgen, addr_type, dut, input_dict_2, next_hop_4) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + if addr_type == "ipv6": + if "link_local" in PREFERRED_NEXT_HOP: + next_hop_6 = get_frr_ipv6_linklocal(tgen, peer1, intf=intf1) + else: + next_hop_6 = NEXT_HOP_6[0] + + result = verify_rib(tgen, addr_type, dut, input_dict_2, next_hop_6) + + # Verifying RIB routes + if addr_type == "ipv4": + next_hop_4 = NEXT_HOP_4[1] + result = verify_rib( + tgen, addr_type, dut, input_dict_2, next_hop_4, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + if addr_type == "ipv6": + if "link_local" in PREFERRED_NEXT_HOP: + next_hop_6 = get_frr_ipv6_linklocal(tgen, peer2, intf=intf2) + else: + next_hop_6 = NEXT_HOP_6[1] + + result = verify_rib( + tgen, addr_type, dut, input_dict_2, next_hop_6, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + logger.info("[Step 4] : Start BGPd daemon on R1 and R4..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + # Start BGPd daemon on R4 + start_router_daemons(tgen, "r4", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_34_2_p1(request): + """ + Test Objective : Restarting node doesn't preserve the forwarding + state verify the behaviour on helper node, if it still keeps the + stale routes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_34 " + "BGP GR " + "[Restart Mode]R1---R3[Helper Mode]" + ) + + logger.info("[Step 1] : Configure restarting" " router R1 to prevent ") + logger.info("[Step 2] : Reset the session" " between R1 and R3..") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True, "disable-eor": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verify f-bit before killing BGPd daemon + result = verify_f_bit(tgen, topo, addr_type, input_dict, "r3", "r1") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes after starting BGPd daemon + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 3] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 4] : Withdraw/delete the prefixes " "originated from R1..") + + # Api call to delete advertised networks + network = {"ipv4": "101.0.20.1/32", "ipv6": "1::1/128"} + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 5, + "delete": True, + } + ] + } + } + } + } + } + } + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 5] : Remove the CLI from R1's config to " "set the F-bit..") + + # Modify graceful-restart config not to set f-bit + # and write to /etc/frr + input_dict_2 = {"r1": {"bgp": {"graceful-restart": {"preserve-fw-state": False}}}} + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + logger.info("[Step 6] : Bring up the BGPd daemon on R1 again..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verify f-bit after starting BGPd daemon + result = verify_f_bit( + tgen, topo, addr_type, input_dict, "r3", "r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: F-bit should not be set to True in r3\n" + "Found: {}".format(tc_name, result) + ) + + # Verifying BGP RIB routes after starting BGPd daemon + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-3.py b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-3.py new file mode 100644 index 0000000..4746d71 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-3.py @@ -0,0 +1,1346 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 7 routers topology +- Bring up topology +- Verify for bgp to converge +- Configure BGP Graceful Restart on both the routers. + +TC_1_2: + Verify that EOR message is sent out only after initial convergence + Verify whether EOR message is received from all the peers after restart +TC_3: + Verify the selection deferral timer functionality when EOR is not sent + by the helper router +TC_11: + Verify that selection-deferral timer sets the maximum time to + avoid deadlock during which the best-path +TC_10: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_15: + Test Objective : Test GR scenarios by enabling Graceful Restart + for multiple address families.. +TC_16: + Test Objective : Verify BGP-GR feature when restarting node + is a transit router for it's iBGP peers. +TC_18: + Test Objective : Verify that GR helper router deletes stale routes + received from restarting node, if GR capability is not present in +TC_19: + Test Objective : Verify that GR routers keeps all the routes + received from restarting node if both the routers are +TC_26: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_28: + Test Objective : Verify if helper node goes down before restarting + node comes up online, helper node sets the R-bit to avoid dead-lock +TC_29: + Test Objective : Change timers on the fly, and + verify if it takes immediate effect. +TC_33: + Test Objective : Helper router receives same prefixes from two + different routers (GR-restarting and GR-disabled). Keeps the +TC_34_1: + Test Objective : Restarting node doesn't preserve forwarding + state, helper router should not keep the stale entries. +TC_34_2: + Test Objective : Restarting node doesn't preserve the forwarding + state verify the behaviour on helper node, if it still keeps the +TC_32: + Test Objective : Restarting node is connected to multiple helper + nodes, one of them doesn't send EOR to restarting router. Verify +TC_37: + Test Objective : Verify if helper node restarts before sending the + EOR message, restarting node doesn't wait until stale path timer +TC_30: + Test Objective : Restarting node removes stale routes from Zebra + after receiving an EOR from helper router. + +""" + +import os +import sys +import time +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_eor, + verify_f_bit, + verify_bgp_convergence, + verify_gr_address_family, + modify_bgp_config_when_bgpd_down, + verify_graceful_restart_timers, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 5 +GR_SELECT_DEFER_TIMER = 5 +GR_STALEPATH_TIMER = 5 +PREFERRED_NEXT_HOP = "link_local" +NEXT_HOP_4 = ["192.168.1.1", "192.168.4.2"] +NEXT_HOP_6 = ["fd00:0:0:1::1", "fd00:0:0:4::2"] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + logger.info("configure_gr_followed_by_clear: dut %s peer %s", dut, peer) + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"][dut][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"][peer][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family(tgen, dut, peer, addr_type, next_hop_dict): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def test_BGP_GR_chaos_34_1_p1(request): + """ + Test Objective : Restarting node doesn't preserve forwarding + state, helper router should not keep the stale entries. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_31 " + "BGP GR " + "[Restart Mode]R1---R3[Helper Mode]" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": { + "preserve-fw-state": True, + "timer": {"restart-time": GR_RESTART_TIMER}, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes after starting BGPd daemon + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info( + "[Step 1] : Remove the preserve-fw-state command" + " from restarting node R1's config" + ) + + # Configure graceful-restart to set f-bit as False + input_dict_2 = {"r1": {"bgp": {"graceful-restart": {"preserve-fw-state": False}}}} + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("[Step 2] : Reset the session between R1 and R3..") + + # Reset sessions + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1") + + result = verify_bgp_convergence_from_running_config(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Verify f-bit after starting BGPd daemon + result = verify_f_bit( + tgen, topo, addr_type, input_dict_2, "r3", "r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: F-bit should not be set to True in r3\n" + "Found: {}".format(tc_name, result) + ) + + logger.info("[Step 3] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + # Waiting for GR_RESTART_TIMER + logger.info("Waiting for {} sec..".format(GR_RESTART_TIMER)) + sleep(GR_RESTART_TIMER) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + input_dict = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_32_p1(request): + """ + Test Objective : Restarting node is connected to multiple helper + nodes, one of them doesn't send EOR to restarting router. Verify + that only after SDT restarting node send EOR to all helper peers + excluding the prefixes originated by faulty router. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_32 " + "BGP GR " + "[Restart Mode]R1---R3&R5[Helper Mode]" + ) + + logger.info( + "[Step 1] : Change the mode on R1 be a restarting" " node on global level" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"graceful-restart": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"next_hop_self": True}}}, + "r5": {"dest_link": {"r1": {"graceful-restart": True}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"next_hop_self": True}}}, + "r5": {"dest_link": {"r1": {"graceful-restart": True}}}, + } + } + }, + }, + } + }, + "r5": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r5": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r5": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r5") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r5" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying BGP RIB routes after starting BGPd daemon + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r5"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 2] : Kill BGPd daemon on R1..") + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 3] : Withdraw all the advertised prefixes from R5") + + # Api call to delete advertised networks + network = {"ipv4": "105.0.20.1/32", "ipv6": "5::1/128"} + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r5": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 5, + "delete": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info( + "[Step 4] : Stop the helper router R5 from sending EOR" " message using CLI" + ) + + # Modify graceful-restart config to prevent sending EOR + input_dict_3 = {"r5": {"bgp": {"graceful-restart": {"disable-eor": True}}}} + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("[Step 5] : Bring up the BGPd daemon on R1..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verify EOR is disabled + result = verify_eor( + tgen, topo, addr_type, input_dict_3, dut="r5", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r5\n" + "Found: {}".format(tc_name, result) + ) + + # Verifying BGP RIB routes after starting BGPd daemon + input_dict_1 = {key: topo["routers"][key] for key in ["r5"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_37_p1(request): + """ + Test Objective : Verify if helper node restarts before sending the + EOR message, restarting node doesn't wait until stale path timer + expiry to do the best path selection and sends an EOR + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_37 " + "BGP GR " + "[Restart Mode]R1---R3[Helper Mode]" + ) + + logger.info( + "[Step 1] : Configure restarting router R3 to prevent " "sending an EOR.." + ) + + logger.info("[Step 2] : Reset the session between R3 and R1..") + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "graceful-restart": {"disable-eor": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verify EOR is disabled + result = verify_eor( + tgen, topo, addr_type, input_dict, dut="r3", peer="r1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r3\n" + "Found: {}".format(tc_name, result) + ) + + # Verifying BGP RIB routes after starting BGPd daemon + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 3] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 4] : Start BGPd daemon on R1..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 5] : Kill BGPd daemon on R3..") + + # Kill BGPd daemon on R3 + kill_router_daemons(tgen, "r3", ["bgpd"]) + + # Modify graceful-restart config to prevent sending EOR + input_dict_2 = {"r3": {"bgp": {"graceful-restart": {"disable-eor": True}}}} + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + logger.info("[Step 6] : Start BGPd daemon on R3..") + + # Start BGPd daemon on R3 + start_router_daemons(tgen, "r3", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verify r_bit + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r1", peer="r3") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verify EOR is send from R1 to R3 + input_dict_3 = {"r1": {"bgp": {"graceful-restart": {"disable-eor": True}}}} + + result = verify_eor( + tgen, topo, addr_type, input_dict_3, dut="r1", peer="r3", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r1\n" + "Found: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_chaos_30_p1(request): + """ + Test Objective : Restarting node removes stale routes from Zebra + after receiving an EOR from helper router. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Test Case : test_BGP_GR_chaos_30 " + "BGP GR [Helper Mode]R3-----R1[Restart Mode] " + ) + + # Configure graceful-restart and timers + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"preserve-fw-state": True}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes before shutting down BGPd daemon + dut = "r1" + input_dict = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + logger.info("[Step 2] : Kill BGPd daemon on R1..") + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + logger.info("[Step 3] : Withdraw advertised prefixes from R3...") + + # Api call to delete advertised networks + network = {"ipv4": "103.0.20.1/32", "ipv6": "3::1/128"} + for addr_type in ADDR_TYPES: + input_dict = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 5, + "delete": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("[Step 4] : Start BGPd daemon on R1..") + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes before shutting down BGPd daemon + input_dict = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_BGP_GR_15_p2(request): + """ + Test Objective : Test GR scenarios by enabling Graceful Restart + for multiple address families.. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r6": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r6") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r6" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info( + "[Step 2] : Test Setup " + "[Helper Mode]R6-----R1[Restart Mode]" + "--------R2[Helper Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def BGP_GR_TC_7_p1(request): + """ + Verify that BGP restarting node deletes all the routes received from peer + if BGP Graceful capability is not present in BGP Open message from the + peer + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + " Verify route download to RIB: BGP_GR_TC_7 >> " + "BGP GR [Helper Mode]R3-----R1[Restart Mode] " + ) + + # Configure graceful-restart + input_dict = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r1": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r1": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes received from router R1 + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("R1 goes for reload") + kill_router_daemons(tgen, "r1", ["bgpd"]) + + # Change the configuration on router R1 + input_dict_2 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Change the configuration on R1 + network = {"ipv4": "103.0.20.1/32", "ipv6": "3::1/128"} + for addr_type in ADDR_TYPES: + input_dict_2 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 5, + "delete": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("R1 is about to come up now") + start_router_daemons(tgen, "r1", ["bgpd"]) + logger.info("R1 is UP Now") + + # Wait for RIB stale timeout + logger.info("Verify routes are not present" "in restart router") + + for addr_type in ADDR_TYPES: + # Verifying RIB routes + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r3"]} + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-4.py b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-4.py new file mode 100644 index 0000000..1c41df9 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo2/test_bgp_gr_functionality_topo2-4.py @@ -0,0 +1,1009 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +""" +Following tests are covered to test BGP Graceful Restart functionality. +Basic Common Test steps for all the test case below : +- Create topology (setup module) + Creating 7 routers topology +- Bring up topology +- Verify for bgp to converge +- Configure BGP Graceful Restart on both the routers. + +TC_1_2: + Verify that EOR message is sent out only after initial convergence + Verify whether EOR message is received from all the peers after restart +TC_3: + Verify the selection deferral timer functionality when EOR is not sent + by the helper router +TC_11: + Verify that selection-deferral timer sets the maximum time to + avoid deadlock during which the best-path +TC_10: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_15: + Test Objective : Test GR scenarios by enabling Graceful Restart + for multiple address families.. +TC_16: + Test Objective : Verify BGP-GR feature when restarting node + is a transit router for it's iBGP peers. +TC_18: + Test Objective : Verify that GR helper router deletes stale routes + received from restarting node, if GR capability is not present in +TC_19: + Test Objective : Verify that GR routers keeps all the routes + received from restarting node if both the routers are +TC_26: + Test Objective : Test GR scenarios on helper router by enabling + Graceful Restart for multiple address families. +TC_28: + Test Objective : Verify if helper node goes down before restarting + node comes up online, helper node sets the R-bit to avoid dead-lock +TC_29: + Test Objective : Change timers on the fly, and + verify if it takes immediate effect. +TC_33: + Test Objective : Helper router receives same prefixes from two + different routers (GR-restarting and GR-disabled). Keeps the +TC_34_1: + Test Objective : Restarting node doesn't preserve forwarding + state, helper router should not keep the stale entries. +TC_34_2: + Test Objective : Restarting node doesn't preserve the forwarding + state verify the behaviour on helper node, if it still keeps the +TC_32: + Test Objective : Restarting node is connected to multiple helper + nodes, one of them doesn't send EOR to restarting router. Verify +TC_37: + Test Objective : Verify if helper node restarts before sending the + EOR message, restarting node doesn't wait until stale path timer +TC_30: + Test Objective : Restarting node removes stale routes from Zebra + after receiving an EOR from helper router. + +These tests have been broken up into 4 sub python scripts because +the totality of run time for this script was greater than 10 minutes +""" + +import os +import sys +import time +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_eor, + verify_f_bit, + verify_bgp_convergence, + verify_gr_address_family, + modify_bgp_config_when_bgpd_down, + verify_graceful_restart_timers, + verify_bgp_convergence_from_running_config, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + step, + get_frr_ipv6_linklocal, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 5 +GR_SELECT_DEFER_TIMER = 5 +GR_STALEPATH_TIMER = 5 +PREFERRED_NEXT_HOP = "link_local" +NEXT_HOP_4 = ["192.168.1.1", "192.168.4.2"] +NEXT_HOP_6 = ["fd00:0:0:1::1", "fd00:0:0:4::2"] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_topojson_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + + logger.info("configure_gr_followed_by_clear: dut %s peer %s", dut, peer) + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][peer]["links"][dut][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, dut, neighbor=neighbor) + + for addr_type in ADDR_TYPES: + neighbor = topo["routers"][dut]["links"][peer][addr_type].split("/")[0] + clear_bgp(tgen, addr_type, peer, neighbor=neighbor) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family(tgen, dut, peer, addr_type, next_hop_dict): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}-link1".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +def test_BGP_GR_TC_23_p1(request): + """ + Verify that helper routers are deleting stale routes after stale route + timer's expiry. If all the routes are not received from restating node + after restart. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "Verify Stale Routes are deleted on helper: BGP_GR_TC_23 >> " + "BGP GR [Helper Mode]R1-----R2[Restart Mode] " + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "graceful-restart": {"timer": {"stalepath-time": GR_STALEPATH_TIMER}}, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + }, + } + }, + "r2": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + "preserve-fw-state": True, + }, + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"graceful-restart": True}}} + } + } + }, + }, + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes received from router R1 + dut = "r1" + input_dict_1 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("R2 goes for reload") + kill_router_daemons(tgen, "r2", ["bgpd"]) + + # Modify configuration to delete routes and include disable-eor + input_dict_3 = {"r2": {"bgp": {"graceful-restart": {"disable-eor": True}}}} + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_3) + + # Modify configuration to delete routes and include disable-eor + network = {"ipv4": "102.0.20.1/32", "ipv6": "2::1/128"} + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r2": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": network[addr_type], + "no_of_network": 3, + "delete": True, + } + ] + } + } + } + } + } + } + + result = modify_bgp_config_when_bgpd_down(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("BGPd comes up for r2") + start_router_daemons(tgen, "r2", ["bgpd"]) + + # Wait for stalepath timer + logger.info("Waiting for stalepath timer({} sec..)".format(GR_STALEPATH_TIMER)) + sleep(GR_STALEPATH_TIMER) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r2") + + # Verifying RIB routes + dut = "r1" + network = {"ipv4": "102.0.20.4/32", "ipv6": "2::4/128"} + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + {"network": network[addr_type], "no_of_network": 2} + ] + } + } + } + } + } + } + + # Verify EOR on helper router + result = verify_eor( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: EOR should not be set to True in r2\n" + "Found: {}".format(tc_name, result) + ) + + # Verifying BGP RIB routes received from router R1 + dut = "r1" + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_GR_20_p1(request): + """ + Test Objective : Verify that GR routers delete all the routes + received from a node if both the routers are configured as GR + helper node + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Restart Mode]R3-----R1[Restart Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_GR_21_p2(request): + """ + Test Objective : VVerify BGP-GR feature when helper node is + a transit router for it's eBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Helper Mode]R6-----R1[Restart Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r1": {"graceful-restart-disable": True} + } + } + } + } + }, + } + } + }, + "r6": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r6": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r6") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r6" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info( + "[Step 2] : Test Setup " + "[Restart Mode]R2-----[Helper Mode]R1[Disable Mode]" + "--------R6[Helper Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r2": { + "bgp": { + "graceful-restart": { + "graceful-restart": True, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes after bringing up BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r6" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_GR_22_p2(request): + """ + Test Objective : Verify BGP-GR feature when helper node + is a transit router for it's iBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Check router status + check_router_status(tgen) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + logger.info( + "[Step 1] : Test Setup " "[Helper Mode]R3-----R1[Restart Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart-disable": True, + "next_hop_self": True, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "graceful-restart-disable": True, + "next_hop_self": True, + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r3") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r3" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info( + "[Step 2] : Test Setup " + "[Restart Mode]R2-----[Helper Mode]R1[Disable Mode]" + "--------R3[Helper Mode] Initialized" + ) + + # Configure graceful-restart + input_dict = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {"graceful-restart-helper": True} + } + } + } + } + }, + } + } + }, + "r2": {"bgp": {"graceful-restart": {"graceful-restart": True}}}, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r1", peer="r2") + + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r1", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Kill BGPd daemon on R1 + kill_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Start BGPd daemon on R1 + start_router_daemons(tgen, "r2", ["bgpd"]) + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_1 = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r3" + input_dict_2 = {key: topo["routers"][key] for key in ["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes before shutting down BGPd daemon + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_functionality_topo3/bgp_gr_functionality_topo3.json b/tests/topotests/bgp_gr_functionality_topo3/bgp_gr_functionality_topo3.json new file mode 100644 index 0000000..84ddc61 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo3/bgp_gr_functionality_topo3.json @@ -0,0 +1,222 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "192.168.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + }, + "r2": { + "links": { + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + }, + "r3": { + "links": { + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_gr_functionality_topo3/test_bgp_gr_functionality_topo3.py b/tests/topotests/bgp_gr_functionality_topo3/test_bgp_gr_functionality_topo3.py new file mode 100644 index 0000000..593a8d6 --- /dev/null +++ b/tests/topotests/bgp_gr_functionality_topo3/test_bgp_gr_functionality_topo3.py @@ -0,0 +1,541 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. ("NetDEF") +# in this file. +# + +import os +import sys +import time +import pytest +from time import sleep + +import traceback +import ipaddress + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join("../")) +sys.path.append(os.path.join("../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +# Import topoJson from lib, to create topology and initial configuration +from lib.topojson import build_config_from_json +from lib.bgp import ( + clear_bgp, + verify_bgp_rib, + verify_graceful_restart, + create_router_bgp, + verify_r_bit, + verify_eor, + verify_f_bit, + verify_bgp_convergence, + verify_gr_address_family, + modify_bgp_config_when_bgpd_down, + verify_graceful_restart_timers, + verify_bgp_convergence_from_running_config, +) + +# Import common_config to use commomnly used APIs +from lib.common_config import ( + create_common_configuration, + InvalidCLIError, + retry, + generate_ips, + FRRCFG_FILE, + find_interface_with_greater_ip, + check_address_types, + validate_ip_address, + run_frr_cmd, + get_frr_ipv6_linklocal, +) + +from lib.common_config import ( + write_test_header, + reset_config_on_routers, + start_topology, + kill_router_daemons, + start_router_daemons, + verify_rib, + check_address_types, + write_test_footer, + check_router_status, + step, + get_frr_ipv6_linklocal, + create_static_routes, + required_linux_kernel_version, +) + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +BGP_CONVERGENCE = False +GR_RESTART_TIMER = 5 +GR_SELECT_DEFER_TIMER = 5 +GR_STALEPATH_TIMER = 5 +# Global variables +# STATIC_ROUTES=[] +NETWORK1_1 = {"ipv4": "192.0.2.1/32", "ipv6": "2001:DB8::1:1/128"} +NETWORK1_2 = {"ipv4": "192.0.2.2/32", "ipv6": "2001:DB8::2:1/128"} +NETWORK2_1 = {"ipv4": "192.0.2.3/32", "ipv6": "2001:DB8::3:1/128"} +NETWORK2_2 = {"ipv4": "192.0.2.4/32", "ipv6": "2001:DB8::4:1/128"} +NETWORK3_1 = {"ipv4": "192.0.2.5/32", "ipv6": "2001:DB8::5:1/128"} +NETWORK3_2 = {"ipv4": "192.0.2.6/32", "ipv6": "2001:DB8::6:1/128"} +NETWORK4_1 = {"ipv4": "192.0.2.7/32", "ipv6": "2001:DB8::7:1/128"} +NETWORK4_2 = {"ipv4": "192.0.2.8/32", "ipv6": "2001:DB8::8:1/128"} +NETWORK5_1 = {"ipv4": "192.0.2.9/32", "ipv6": "2001:DB8::9:1/128"} +NETWORK5_2 = {"ipv4": "192.0.2.10/32", "ipv6": "2001:DB8::10:1/128"} + +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +PREFERRED_NEXT_HOP = "link_local" + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + result = configure_gr_followed_by_clear(tgen, topo, dut) + assert result is True, \ + "Testcase {} :Failed \n Error {}". \ + format(tc_name, result) + """ + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, dut) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, peer) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + return True + + +def verify_stale_routes_list(tgen, addr_type, dut, input_dict): + """ + This API is use verify Stale routes on refering the network with next hop value + Parameters + ---------- + * `tgen`: topogen object + * `dut`: input dut router name + * `addr_type` : ip type ipv4/ipv6 + * `input_dict` : input dict, has details of static routes + Usage + ----- + dut = 'r1' + input_dict = { + "r3": { + "static_routes": [ + + { + "network": [NETWORK1_1[addr_type]], + "no_of_ip": 2, + "vrf": "RED" + } + ] + } + } + + result = verify_stale_routes_list(tgen, addr_type, dut, input_dict) + Returns + ------- + errormsg(str) or True + """ + logger.debug("Entering lib API: verify_stale_routes_list()") + router_list = tgen.routers() + additional_nexthops_in_required_nhs = [] + list1 = [] + list2 = [] + found_hops = [] + for routerInput in input_dict.keys(): + for router, rnode in router_list.items(): + if router != dut: + continue + # Verifying RIB routes + command = "show bgp" + # Static routes + sleep(2) + logger.info("Checking router {} BGP RIB:".format(dut)) + if "static_routes" in input_dict[routerInput]: + static_routes = input_dict[routerInput]["static_routes"] + for static_route in static_routes: + found_routes = [] + missing_routes = [] + st_found = False + nh_found = False + vrf = static_route.setdefault("vrf", None) + community = static_route.setdefault("community", None) + largeCommunity = static_route.setdefault("largeCommunity", None) + if vrf: + cmd = "{} vrf {} {}".format(command, vrf, addr_type) + if community: + cmd = "{} community {}".format(cmd, community) + if largeCommunity: + cmd = "{} large-community {}".format(cmd, largeCommunity) + else: + cmd = "{} {}".format(command, addr_type) + cmd = "{} json".format(cmd) + rib_routes_json = run_frr_cmd(rnode, cmd, isjson=True) + # Verifying output dictionary rib_routes_json is not empty + if bool(rib_routes_json) == False: + errormsg = "[DUT: {}]: No route found in rib of router".format( + router + ) + return errormsg + elif "warning" in rib_routes_json: + errormsg = "[DUT: {}]: {}".format( + router, rib_routes_json["warning"] + ) + return errormsg + network = static_route["network"] + if "no_of_ip" in static_route: + no_of_ip = static_route["no_of_ip"] + else: + no_of_ip = 1 + # Generating IPs for verification + ip_list = generate_ips(network, no_of_ip) + + for st_rt in ip_list: + st_rt = str(ipaddress.ip_network(st_rt)) + _addr_type = validate_ip_address(st_rt) + if _addr_type != addr_type: + continue + if st_rt in rib_routes_json["routes"]: + st_found = True + + found_routes.append(st_rt) + for mnh in range(0, len(rib_routes_json["routes"][st_rt])): + found_hops.append( + [ + rib_r["ip"] + for rib_r in rib_routes_json["routes"][st_rt][ + mnh + ]["nexthops"] + ] + ) + return found_hops + else: + return "error msg - no hops found" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_gr_functionality_topo3.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +################################################################################ +# +# TEST CASES +# +################################################################################ +def test_bgp_gr_stale_routes(request): + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + step("Verify the router failures") + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Creating 5 static Routes in Router R3 with NULL0 as Next hop") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("verifying Created Route at R3 in VRF default") + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict_1 = {"r3": topo["routers"]["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + # done + step("verifying Created Route at R2 in VRF default") + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + step("importing vrf RED on R2 under Address Family") + for addr_type in ADDR_TYPES: + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": 200, + "vrf": "RED", + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "default"}}} + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + # done + step("verifying static Routes at R2 in VRF RED") + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("verifying static Routes at R1 in VRF RED") + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_1 = {"r1": topo["routers"]["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Configuring Graceful restart at R2 and R3 ") + input_dict = { + "r2": { + "bgp": { + "local_as": "200", + "graceful-restart": { + "graceful-restart": True, + }, + } + }, + "r3": { + "bgp": {"local_as": "300", "graceful-restart": {"graceful-restart": True}} + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r2", peer="r3") + + step("verify Graceful restart at R2") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r2", peer="r3" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("verify Graceful restart at R3") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Configuring Graceful-restart-disable at R3") + input_dict = { + "r2": { + "bgp": { + "local_as": "200", + "graceful-restart": { + "graceful-restart": False, + }, + } + }, + "r3": { + "bgp": {"local_as": "300", "graceful-restart": {"graceful-restart": False}} + }, + } + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r3", peer="r2") + + step("Verify Graceful-restart-disable at R3") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for iteration in range(5): + step("graceful-restart-disable:True at R3") + input_dict = { + "r3": { + "bgp": { + "graceful-restart": { + "graceful-restart-disable": True, + } + } + } + } + configure_gr_followed_by_clear( + tgen, topo, input_dict, tc_name, dut="r3", peer="r2" + ) + + step("Verifying Routes at R2 on enabling GRD") + dut = "r2" + for addr_type in ADDR_TYPES: + input_dict_1 = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format( + tc_name, result + ) + + step("Verify stale Routes in Router R2 enabling GRD") + for addr_type in ADDR_TYPES: + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "no_of_ip": 2, + "vrf": "RED", + } + ] + } + } + bgp_rib_next_hops = verify_stale_routes_list( + tgen, addr_type, dut, verify_nh_for_static_rtes + ) + assert ( + len(bgp_rib_next_hops) == 1 + ) is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_rib_next_hops, expected=True + ) + + step("graceful-restart-disable:False at R3") + input_dict = { + "r3": { + "bgp": { + "graceful-restart": { + "graceful-restart-disable": False, + } + } + } + } + configure_gr_followed_by_clear( + tgen, topo, input_dict, tc_name, dut="r3", peer="r2" + ) + + step("Verifying Routes at R2 on disabling GRD") + dut = "r2" + for addr_type in ADDR_TYPES: + input_dict_1 = {"r2": topo["routers"]["r2"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format( + tc_name, result + ) + + step("Verify stale Routes in Router R2 on disabling GRD") + for addr_type in ADDR_TYPES: + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "no_of_ip": 2, + "vrf": "RED", + } + ] + } + } + bgp_rib_next_hops = verify_stale_routes_list( + tgen, addr_type, dut, verify_nh_for_static_rtes + ) + + stale_route_status = len(bgp_rib_next_hops) == 1 + assert ( + stale_route_status is True + ), "Testcase {} : Failed \n Error: {}".format( + tc_name, stale_route_status, expected=True + ) + write_test_footer(tc_name) diff --git a/tests/topotests/bgp_gr_notification/__init__.py b/tests/topotests/bgp_gr_notification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_gr_notification/r1/bgpd.conf b/tests/topotests/bgp_gr_notification/r1/bgpd.conf new file mode 100644 index 0000000..6119f67 --- /dev/null +++ b/tests/topotests/bgp_gr_notification/r1/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65001 + no bgp ebgp-requires-policy + bgp graceful-restart + neighbor 192.168.255.2 remote-as external + neighbor 192.168.255.2 timers 1 3 + neighbor 192.168.255.2 timers connect 1 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_gr_notification/r1/zebra.conf b/tests/topotests/bgp_gr_notification/r1/zebra.conf new file mode 100644 index 0000000..091794f --- /dev/null +++ b/tests/topotests/bgp_gr_notification/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gr_notification/r2/bgpd.conf b/tests/topotests/bgp_gr_notification/r2/bgpd.conf new file mode 100644 index 0000000..05e17f0 --- /dev/null +++ b/tests/topotests/bgp_gr_notification/r2/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65002 + no bgp ebgp-requires-policy + no bgp hard-administrative-reset + bgp graceful-restart + neighbor 192.168.255.1 remote-as external + neighbor 192.168.255.1 timers 1 3 + neighbor 192.168.255.1 timers connect 1 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_gr_notification/r2/zebra.conf b/tests/topotests/bgp_gr_notification/r2/zebra.conf new file mode 100644 index 0000000..1fcccbe --- /dev/null +++ b/tests/topotests/bgp_gr_notification/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.2/32 +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gr_notification/test_bgp_gr_notification.py b/tests/topotests/bgp_gr_notification/test_bgp_gr_notification.py new file mode 100644 index 0000000..2ffcb72 --- /dev/null +++ b/tests/topotests/bgp_gr_notification/test_bgp_gr_notification.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_gr_notification.py +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +TC1: Disable the link between R1-R2 and wait for HoldTimerExpire notification: + 1) Check if R2 sent HoldTimerExpired notification + 2) Check if the routes are retained at R2 +TC2: Trigger `clear bgp` (Administrative Reset): + `bgp hard-administrative-reset` disabled: + a) Check if Administrative Reset notification was sent from R2 + b) Routes should be retained on R1 +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_hold_timer_expired_gr(): + # TC1 + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _disable_link_r1_r2(): + r1.cmd_raises("ip link set down dev r1-eth0") + + def _enable_link_r1_r2(): + r1.cmd_raises("ip link set up dev r1-eth0") + + def _bgp_check_hold_timer_expire_reason(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "lastNotificationReason": "Hold Timer Expired", + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_hold_timer_expire_stale(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast 172.16.255.1/32 json")) + expected = { + "paths": [ + { + "stale": True, + "valid": True, + } + ] + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R2" + + step("Disable the link between R1-R2") + _disable_link_r1_r2() + + step("Check if R2 sent HoldTimerExpire notification to R1") + test_func = functools.partial(_bgp_check_hold_timer_expire_reason) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see Hold Timer Expired notification from R2 on R1" + + step("Check if the routes are retained at R2") + test_func = functools.partial(_bgp_check_hold_timer_expire_stale) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see retained stale routes on R2" + + step("Enable the link between R1-R2") + _enable_link_r1_r2() + + +def test_bgp_administrative_reset_gr(): + # TC2 + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_hard_reset(): + output = json.loads(r1.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "lastNotificationReason": "Cease/Administrative Reset", + "lastNotificationHardReset": False, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_gr_notification_stale(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 172.16.255.2/32 json")) + expected = { + "paths": [ + { + "stale": True, + "valid": True, + } + ] + } + return topotest.json_cmp(output, expected) + + def _bgp_clear_r1_and_shutdown(): + r2.vtysh_cmd( + """ + clear ip bgp 192.168.255.1 + configure terminal + router bgp + neighbor 192.168.255.1 shutdown + """ + ) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R2" + + step("Reset and shutdown R1") + _bgp_clear_r1_and_shutdown() + + step("Check if Hard Reset notification wasn't sent from R2") + test_func = functools.partial(_bgp_check_hard_reset) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to send Administrative Reset notification from R2" + + step("Check if stale routes are retained on R1") + test_func = functools.partial(_bgp_check_gr_notification_stale) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see retained stale routes on R1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_restart_retain_routes/__init__.py b/tests/topotests/bgp_gr_restart_retain_routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_gr_restart_retain_routes/r1/frr.conf b/tests/topotests/bgp_gr_restart_retain_routes/r1/frr.conf new file mode 100644 index 0000000..3d4d3a8 --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/r1/frr.conf @@ -0,0 +1,18 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp graceful-restart preserve-fw-state + neighbor 192.168.255.2 remote-as external + neighbor 192.168.255.2 timers 1 3 + neighbor 192.168.255.2 timers connect 1 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_gr_restart_retain_routes/r2/frr.conf b/tests/topotests/bgp_gr_restart_retain_routes/r2/frr.conf new file mode 100644 index 0000000..f5ba4ea --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/r2/frr.conf @@ -0,0 +1,13 @@ +no zebra nexthop kernel enable +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp graceful-restart preserve-fw-state + neighbor 192.168.255.1 remote-as external + neighbor 192.168.255.1 timers 1 3 + neighbor 192.168.255.1 timers connect 1 +! diff --git a/tests/topotests/bgp_gr_restart_retain_routes/r3/frr.conf b/tests/topotests/bgp_gr_restart_retain_routes/r3/frr.conf new file mode 100644 index 0000000..1d84ec6 --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/r3/frr.conf @@ -0,0 +1,19 @@ +no zebra nexthop kernel enable +! +interface lo + ip address 172.16.255.3/32 +! +interface r3-eth0 + ip address 192.168.34.3/24 +! +router bgp 65003 + no bgp ebgp-requires-policy + bgp graceful-restart preserve-fw-state + neighbor 192.168.34.4 remote-as external + neighbor 192.168.34.4 timers 1 3 + neighbor 192.168.34.4 timers connect 1 + neighbor 192.168.34.4 graceful-restart + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_gr_restart_retain_routes/r4/frr.conf b/tests/topotests/bgp_gr_restart_retain_routes/r4/frr.conf new file mode 100644 index 0000000..5890791 --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/r4/frr.conf @@ -0,0 +1,13 @@ +no zebra nexthop kernel enable +! +interface r4-eth0 + ip address 192.168.34.4/24 +! +router bgp 65004 + no bgp ebgp-requires-policy + bgp graceful-restart preserve-fw-state + neighbor 192.168.34.3 remote-as external + neighbor 192.168.34.3 timers 1 3 + neighbor 192.168.34.3 timers connect 1 + neighbor 192.168.34.3 graceful-restart +! diff --git a/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_per_neighbor_restart_retain_routes.py b/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_per_neighbor_restart_retain_routes.py new file mode 100644 index 0000000..2354c0c --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_per_neighbor_restart_retain_routes.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2024 by +# Donatas Abraitis +# + +""" +Test if routes are retained during BGP restarts using + Graceful Restart per-neighbor. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step, stop_router + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_gr_restart_retain_routes(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r4 = tgen.gears["r4"] + + def _bgp_converge(): + output = json.loads(r4.vtysh_cmd("show bgp ipv4 neighbors 192.168.34.3 json")) + expected = { + "192.168.34.3": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_bgp_retained_routes(): + output = json.loads(r4.vtysh_cmd("show bgp ipv4 unicast 172.16.255.3/32 json")) + expected = {"paths": [{"stale": True}]} + return topotest.json_cmp(output, expected) + + def _bgp_check_kernel_retained_routes(): + output = json.loads( + r4.cmd("ip -j route show 172.16.255.3/32 proto bgp dev r4-eth0") + ) + expected = [{"dst": "172.16.255.3", "gateway": "192.168.34.3", "metric": 20}] + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R4" + + step("Restart R3") + stop_router(tgen, "r3") + + step("Check if routes (BGP) are retained at R4") + test_func = functools.partial(_bgp_check_bgp_retained_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP retained routes on R4" + + step("Check if routes (Kernel) are retained at R4") + assert ( + _bgp_check_kernel_retained_routes() is None + ), "Failed to retain BGP routes in kernel on R4" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_restart_retain_routes.py b/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_restart_retain_routes.py new file mode 100644 index 0000000..abf737f --- /dev/null +++ b/tests/topotests/bgp_gr_restart_retain_routes/test_bgp_gr_restart_retain_routes.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if routes are retained during BGP restarts. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step, stop_router + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_gr_restart_retain_routes(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 neighbors 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_bgp_retained_routes(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast 172.16.255.1/32 json")) + expected = {"paths": [{"stale": True}]} + return topotest.json_cmp(output, expected) + + def _bgp_check_kernel_retained_routes(): + output = json.loads( + r2.cmd("ip -j route show 172.16.255.1/32 proto bgp dev r2-eth0") + ) + expected = [{"dst": "172.16.255.1", "gateway": "192.168.255.1", "metric": 20}] + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R2" + + step("Restart R1") + stop_router(tgen, "r1") + + step("Check if routes (BGP) are retained at R2") + test_func = functools.partial(_bgp_check_bgp_retained_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP retained routes on R2" + + step("Check if routes (Kernel) are retained at R2") + assert ( + _bgp_check_kernel_retained_routes() is None + ), "Failed to retain BGP routes in kernel on R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gshut/__init__.py b/tests/topotests/bgp_gshut/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_gshut/r1/bgp_route_1.json b/tests/topotests/bgp_gshut/r1/bgp_route_1.json new file mode 100644 index 0000000..f3921b2 --- /dev/null +++ b/tests/topotests/bgp_gshut/r1/bgp_route_1.json @@ -0,0 +1,12 @@ +{ + "prefix":"13.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "metric":0, + "locPrf":100, + "valid":true + } + ] +} + diff --git a/tests/topotests/bgp_gshut/r1/bgp_route_2.json b/tests/topotests/bgp_gshut/r1/bgp_route_2.json new file mode 100644 index 0000000..754a0ed --- /dev/null +++ b/tests/topotests/bgp_gshut/r1/bgp_route_2.json @@ -0,0 +1,17 @@ +{ + "prefix":"13.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "metric":0, + "locPrf":0, + "valid":true, + "community":{ + "string":"graceful-shutdown", + "list":[ + "gracefulShutdown" + ] + } + } + ] +} diff --git a/tests/topotests/bgp_gshut/r1/bgpd.conf b/tests/topotests/bgp_gshut/r1/bgpd.conf new file mode 100644 index 0000000..ab6f47a --- /dev/null +++ b/tests/topotests/bgp_gshut/r1/bgpd.conf @@ -0,0 +1,10 @@ +! exit1 +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.255.1 remote-as 65001 + neighbor 192.168.255.1 timers connect 10 + address-family ipv4 unicast + network 11.1.1.1/32 + exit-address-family +! diff --git a/tests/topotests/bgp_gshut/r1/zebra.conf b/tests/topotests/bgp_gshut/r1/zebra.conf new file mode 100644 index 0000000..9904bb4 --- /dev/null +++ b/tests/topotests/bgp_gshut/r1/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gshut/r2/bgp_sum_1.json b/tests/topotests/bgp_gshut/r2/bgp_sum_1.json new file mode 100644 index 0000000..9d8948a --- /dev/null +++ b/tests/topotests/bgp_gshut/r2/bgp_sum_1.json @@ -0,0 +1,15 @@ +{ +"ipv4Unicast":{ + "peers":{ + "192.168.254.2":{ + "remoteAs":65003, + "state":"Established" + }, + "192.168.255.2":{ + "remoteAs":65001, + "state":"Established" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_gshut/r2/bgp_sum_2.json b/tests/topotests/bgp_gshut/r2/bgp_sum_2.json new file mode 100644 index 0000000..7183db6 --- /dev/null +++ b/tests/topotests/bgp_gshut/r2/bgp_sum_2.json @@ -0,0 +1,15 @@ +{ +"ipv4Unicast":{ + "peers":{ + "192.168.252.2":{ + "remoteAs":65005, + "state":"Established" + }, + "192.168.253.2":{ + "remoteAs":65004, + "state":"Established" + } + }, + "totalPeers":2 +} +} diff --git a/tests/topotests/bgp_gshut/r2/bgpd.conf b/tests/topotests/bgp_gshut/r2/bgpd.conf new file mode 100644 index 0000000..b0ca4e6 --- /dev/null +++ b/tests/topotests/bgp_gshut/r2/bgpd.conf @@ -0,0 +1,20 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.254.2 remote-as 65003 + neighbor 192.168.255.2 timers connect 10 + neighbor 192.168.254.2 timers connect 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! +router bgp 65001 vrf vrf1 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.253.2 remote-as 65004 + neighbor 192.168.253.2 timers connect 10 + neighbor 192.168.252.2 remote-as 65005 + neighbor 192.168.252.2 timers connect 10 +! diff --git a/tests/topotests/bgp_gshut/r2/zebra.conf b/tests/topotests/bgp_gshut/r2/zebra.conf new file mode 100644 index 0000000..0da0501 --- /dev/null +++ b/tests/topotests/bgp_gshut/r2/zebra.conf @@ -0,0 +1,13 @@ +! spine +interface r2-eth0 + ip address 192.168.255.1/30 +! +interface r2-eth1 + ip address 192.168.254.1/30 +! +interface r2-eth2 vrf vrf1 + ip address 192.168.253.1/30 +! +interface r2-eth3 vrf vrf1 + ip address 192.168.252.1/30 +! diff --git a/tests/topotests/bgp_gshut/r3/bgp_route_1.json b/tests/topotests/bgp_gshut/r3/bgp_route_1.json new file mode 100644 index 0000000..94de01a --- /dev/null +++ b/tests/topotests/bgp_gshut/r3/bgp_route_1.json @@ -0,0 +1,9 @@ +{ + "prefix":"11.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "valid":true + } + ] +} diff --git a/tests/topotests/bgp_gshut/r3/bgp_route_2.json b/tests/topotests/bgp_gshut/r3/bgp_route_2.json new file mode 100644 index 0000000..f918265 --- /dev/null +++ b/tests/topotests/bgp_gshut/r3/bgp_route_2.json @@ -0,0 +1,16 @@ +{ + "prefix":"11.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "locPrf":0, + "valid":true, + "community":{ + "string":"graceful-shutdown", + "list":[ + "gracefulShutdown" + ] + } + } + ] +} diff --git a/tests/topotests/bgp_gshut/r3/bgpd.conf b/tests/topotests/bgp_gshut/r3/bgpd.conf new file mode 100644 index 0000000..5d7c0cd --- /dev/null +++ b/tests/topotests/bgp_gshut/r3/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 9 + neighbor 192.168.254.1 remote-as 65001 + neighbor 192.168.254.1 timers connect 10 + address-family ipv4 unicast + network 13.1.1.1/32 + exit-address-family +! diff --git a/tests/topotests/bgp_gshut/r3/zebra.conf b/tests/topotests/bgp_gshut/r3/zebra.conf new file mode 100644 index 0000000..f490d97 --- /dev/null +++ b/tests/topotests/bgp_gshut/r3/zebra.conf @@ -0,0 +1,9 @@ +! exit2 +interface lo + ip address 172.16.254.254/32 +! +interface r3-eth0 + ip address 192.168.254.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gshut/r4/bgpd.conf b/tests/topotests/bgp_gshut/r4/bgpd.conf new file mode 100644 index 0000000..375f383 --- /dev/null +++ b/tests/topotests/bgp_gshut/r4/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65004 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 9 + neighbor 192.168.253.1 remote-as 65001 + neighbor 192.168.253.1 timers connect 10 + address-family ipv4 unicast + network 14.1.1.1/32 + exit-address-family +! diff --git a/tests/topotests/bgp_gshut/r4/zebra.conf b/tests/topotests/bgp_gshut/r4/zebra.conf new file mode 100644 index 0000000..baba04c --- /dev/null +++ b/tests/topotests/bgp_gshut/r4/zebra.conf @@ -0,0 +1,9 @@ +! exit2 +interface lo + ip address 172.16.253.254/32 +! +interface r4-eth0 + ip address 192.168.253.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gshut/r5/bgp_route_1.json b/tests/topotests/bgp_gshut/r5/bgp_route_1.json new file mode 100644 index 0000000..4e6fd79 --- /dev/null +++ b/tests/topotests/bgp_gshut/r5/bgp_route_1.json @@ -0,0 +1,9 @@ +{ + "prefix":"14.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "valid":true + } + ] +} diff --git a/tests/topotests/bgp_gshut/r5/bgp_route_2.json b/tests/topotests/bgp_gshut/r5/bgp_route_2.json new file mode 100644 index 0000000..980d8de --- /dev/null +++ b/tests/topotests/bgp_gshut/r5/bgp_route_2.json @@ -0,0 +1,16 @@ +{ + "prefix":"14.1.1.1\/32", + "paths":[ + { + "origin":"IGP", + "locPrf":0, + "valid":true, + "community":{ + "string":"graceful-shutdown", + "list":[ + "gracefulShutdown" + ] + } + } + ] +} diff --git a/tests/topotests/bgp_gshut/r5/bgpd.conf b/tests/topotests/bgp_gshut/r5/bgpd.conf new file mode 100644 index 0000000..15b49f5 --- /dev/null +++ b/tests/topotests/bgp_gshut/r5/bgpd.conf @@ -0,0 +1,7 @@ +! +router bgp 65005 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.252.1 remote-as 65001 + neighbor 192.168.252.1 timers connect 10 +! diff --git a/tests/topotests/bgp_gshut/r5/zebra.conf b/tests/topotests/bgp_gshut/r5/zebra.conf new file mode 100644 index 0000000..c4cbd52 --- /dev/null +++ b/tests/topotests/bgp_gshut/r5/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface lo + ip address 172.16.252.254/32 +! +interface r5-eth0 + ip address 192.168.252.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_gshut/test_bgp_gshut.py b/tests/topotests/bgp_gshut/test_bgp_gshut.py new file mode 100644 index 0000000..61a0fe6 --- /dev/null +++ b/tests/topotests/bgp_gshut/test_bgp_gshut.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_gshut.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Vivek Venkatraman +# + +""" +Test the ability to initiate and stop BGP graceful shutdown. +Test both the vrf-specific and global configuration and operation. + +r1 +| +r2----r3 +| \ +| \ +r4 r5 + + +r2 is UUT and peers with r1 and r3 in default bgp instance and +with r4 and r5 in vrf vrf1. +r1-r2 peering is iBGP and the other peerings are eBGP. + +Check r2 initial convergence in default table +Define update-delay with max-delay in the default bgp instance on r2 +Shutdown peering on r1 toward r2 so that delay timers can be exercised +Clear bgp neighbors on r2 and then check for the 'in progress' indicator +Check that r2 only installs route learned from r4 after the max-delay timer expires +Define update-delay with max-delay and estabish-wait and check json output showing set +Clear neighbors on r2 and check that r3 installs route from r4 after establish-wait time +Remove update-delay timer on r2 to verify that it goes back to normal behavior +Clear neighbors on r2 and check that route install time on r2 does not delay +Define global bgp update-delay with max-delay and establish-wait on r2 +Check that r2 default instance and vrf1 have the max-delay and establish set +Clear neighbors on r2 and check route-install time is after the establish-wait timer + +Note that the keepalive/hold times were changed to 3/9 and the connect retry timer +to 10 to improve the odds the convergence timing in this test case is useful in the +event of packet loss. +""" + +import os +import re +import sys +import json +import pytest +import platform +from functools import partial + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 6): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r5"]) + + +def _run_cmd_and_check(router, cmd, results_file, retries=100, intvl=0.5): + json_file = "{}/{}".format(CWD, results_file) + expected = json.loads(open(json_file).read()) + test_func = partial(topotest.router_json_cmp, router, cmd, expected) + return topotest.run_and_expect(test_func, None, retries, intvl) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + krel = platform.release() + if topotest.version_cmp(krel, "4.5") < 0: + tgen.errors = "Linux kernel version of at least 4.5 needed for bgp-gshut tests" + pytest.skip(tgen.errors) + + # Configure vrf and its slaves in the kernel on r2 + r2 = tgen.gears["r2"] + r2.run("ip link add vrf1 type vrf table 1000") + r2.run("ip link set vrf1 up") + r2.run("ip link set r2-eth2 master vrf1") + r2.run("ip link set r2-eth3 master vrf1") + + # Load FRR config and initialize all routers + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + # Basic peering test to see if things are ok + _, result = _run_cmd_and_check(r2, "show ip bgp summary json", "r2/bgp_sum_1.json") + assertmsg = "R2: Basic sanity test after init failed -- global peerings not up" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r2, "show ip bgp vrf vrf1 summary json", "r2/bgp_sum_2.json" + ) + assertmsg = "R2: Basic sanity test after init failed -- VRF peerings not up" + assert result is None, assertmsg + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_gshut(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + r5 = tgen.gears["r5"] + + # Verify initial route states + logger.info("\nVerify initial route states") + + _, result = _run_cmd_and_check( + r1, "show ip bgp 13.1.1.1/32 json", "r1/bgp_route_1.json" + ) + assertmsg = "R1: Route 13.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r3, "show ip bgp 11.1.1.1/32 json", "r3/bgp_route_1.json" + ) + assertmsg = "R3: Route 11.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r5, "show ip bgp 14.1.1.1/32 json", "r5/bgp_route_1.json" + ) + assertmsg = "R5: Route 14.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + logger.info("\nInitial route states are as expected") + + # "Test #1: Enable BGP-wide graceful-shutdown on R2 and check routes on peers" + logger.info( + "\nTest #1: Enable BGP-wide graceful-shutdown on R2 and check routes on peers" + ) + + r2.vtysh_cmd( + """ + configure terminal + bgp graceful-shutdown + """ + ) + + # R1, R3 and R5 should see routes from R2 with GSHUT. In addition, + # R1 should see LOCAL_PREF of 0 + _, result = _run_cmd_and_check( + r1, "show ip bgp 13.1.1.1/32 json", "r1/bgp_route_2.json" + ) + assertmsg = "R1: Route 13.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r3, "show ip bgp 11.1.1.1/32 json", "r3/bgp_route_2.json" + ) + assertmsg = "R3: Route 11.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r5, "show ip bgp 14.1.1.1/32 json", "r5/bgp_route_2.json" + ) + assertmsg = "R5: Route 14.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + logger.info( + "\nTest #1: Successful, routes have GSHUT and/or LPREF of 0 as expected" + ) + + # "Test #2: Turn off BGP-wide graceful-shutdown on R2 and check routes on peers" + logger.info( + "\nTest #2: Turn off BGP-wide graceful-shutdown on R2 and check routes on peers" + ) + + r2.vtysh_cmd( + """ + configure terminal + no bgp graceful-shutdown + """ + ) + + # R1, R3 and R5 should see routes from R2 with their original attributes + _, result = _run_cmd_and_check( + r1, "show ip bgp 13.1.1.1/32 json", "r1/bgp_route_1.json" + ) + assertmsg = "R1: Route 13.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r3, "show ip bgp 11.1.1.1/32 json", "r3/bgp_route_1.json" + ) + assertmsg = "R3: Route 11.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r5, "show ip bgp 14.1.1.1/32 json", "r5/bgp_route_1.json" + ) + assertmsg = "R5: Route 14.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + logger.info( + "\nTest #2: Successful, routes have their original attributes with default LPREF and without GSHUT" + ) + + # "Test #3: Enable graceful-shutdown on R2 only in VRF1 and check routes on peers" + logger.info( + "\nTest #3: Enable graceful-shutdown on R2 only in VRF1 and check routes on peers" + ) + + r2.vtysh_cmd( + """ + configure terminal + router bgp 65001 vrf vrf1 + bgp graceful-shutdown + """ + ) + + # R1 and R3 should see no change to their routes + _, result = _run_cmd_and_check( + r1, "show ip bgp 13.1.1.1/32 json", "r1/bgp_route_1.json" + ) + assertmsg = "R1: Route 13.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r3, "show ip bgp 11.1.1.1/32 json", "r3/bgp_route_1.json" + ) + assertmsg = "R3: Route 11.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + # R5 should see routes from R2 with GSHUT. + _, result = _run_cmd_and_check( + r5, "show ip bgp 14.1.1.1/32 json", "r5/bgp_route_2.json" + ) + assertmsg = "R5: Route 14.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + logger.info("\nTest #3: Successful, only VRF peers like R5 see routes with GSHUT") + + # "Test #4: Try to enable BGP-wide graceful-shutdown on R2 while it is configured in VRF1" + logger.info( + "\nTest #4: Try to enable BGP-wide graceful-shutdown on R2 while it is configured in VRF1" + ) + + ret = r2.vtysh_cmd( + """ + configure terminal + bgp graceful-shutdown + """ + ) + + # This should fail + assertmsg = "R2: BGP-wide graceful-shutdown config not rejected even though it is enabled in VRF1" + assert ( + re.search("global graceful-shutdown not permitted", ret) is not None + ), assertmsg + + logger.info( + "\nTest #4: Successful, BGP-wide graceful-shutdown rejected as it is enabled in VRF" + ) + + # "Test #5: Turn off graceful-shutdown on R2 in VRF1 and check routes on peers" + logger.info( + "\nTest #5: Turn off graceful-shutdown on R2 in VRF1 and check routes on peers" + ) + + r2.vtysh_cmd( + """ + configure terminal + router bgp 65001 vrf vrf1 + no bgp graceful-shutdown + """ + ) + + # R1 and R3 should see no change to their routes + _, result = _run_cmd_and_check( + r1, "show ip bgp 13.1.1.1/32 json", "r1/bgp_route_1.json" + ) + assertmsg = "R1: Route 13.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + _, result = _run_cmd_and_check( + r3, "show ip bgp 11.1.1.1/32 json", "r3/bgp_route_1.json" + ) + assertmsg = "R3: Route 11.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + # R5 should see routes from R2 with original attributes. + _, result = _run_cmd_and_check( + r5, "show ip bgp 14.1.1.1/32 json", "r5/bgp_route_1.json" + ) + assertmsg = "R5: Route 14.1.1.1/32 not present or has unexpected params" + assert result is None, assertmsg + + logger.info( + "\nTest #5: Successful, routes have their original attributes with default LPREF and without GSHUT" + ) + + # tgen.mininet_cli() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json b/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json new file mode 100644 index 0000000..dcd5af8 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/ebgp_gshut_topo1.json @@ -0,0 +1,221 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.1.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + "static_routes":[ + { + "network":"100.0.10.1/32", + "no_of_ip":5, + "next_hop":"Null0" + }, + { + "network":"1::1/128", + "no_of_ip":5, + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json b/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json new file mode 100644 index 0000000..464e362 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/ibgp_gshut_topo1.json @@ -0,0 +1,221 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.1.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + "static_routes":[ + { + "network":"100.0.10.1/32", + "no_of_ip":5, + "next_hop":"Null0" + }, + { + "network":"1::1/128", + "no_of_ip":5, + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py b/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py new file mode 100644 index 0000000..5ec9db0 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/test_ebgp_gshut_topo1.py @@ -0,0 +1,593 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +""" +Following tests are covered to test ecmp functionality on BGP GSHUT. +1. Verify graceful-shutdown functionality with eBGP peers +2. Verify graceful-shutdown functionality when daemons + bgpd/zebra/staticd and frr services are restarted with eBGP peers +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + check_address_types, + reset_config_on_routers, + step, + get_frr_ipv6_linklocal, + kill_router_daemons, + start_router_daemons, + stop_router, + start_router, + create_route_maps, + create_bgp_community_lists, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_attributes, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NETWORK = {"ipv4": "100.0.10.1/32", "ipv6": "1::1/128"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.2.1", "ipv6": "fd00:0:0:1::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.4.2", "ipv6": "fd00:0:0:3::2"} +PREFERRED_NEXT_HOP = "link_local" +BGP_CONVERGENCE = False + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/ebgp_gshut_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +########################### +# Local APIs +########################### + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################### +# TESTCASES +########################### + + +def test_verify_graceful_shutdown_functionality_with_eBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality with eBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R3 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = { + "r1": { + "route_maps": { + "GSHUT-OUT": [{"set": {"locPrf": 0}}], + } + } + } + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_restarting_zebra_bgpd_staticd_frr_with_eBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality when daemons bgpd/zebra/staticd and + frr services are restarted with eBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R3 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Restart daemons and frr services") + + for daemon in ["bgpd", "zebra", "staticd", "frr"]: + if daemon != "frr": + kill_router_daemons(tgen, "r3", ["staticd"]) + start_router_daemons(tgen, "r3", ["staticd"]) + else: + stop_router(tgen, "r3") + start_router(tgen, "r3") + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify BGP routes on R3:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r3" + next_hop1 = next_hop_per_address_family( + tgen, "r3", "r1", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r3", "r4", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop2) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py b/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py new file mode 100644 index 0000000..1e0f726 --- /dev/null +++ b/tests/topotests/bgp_gshut_topo1/test_ibgp_gshut_topo1.py @@ -0,0 +1,640 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +""" +Following tests are covered to test ecmp functionality on BGP GSHUT. +1. Verify graceful-shutdown functionality with iBGP peers +2. Verify graceful-shutdown functionality after + deleting/re-adding route-map with iBGP peers +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + check_address_types, + reset_config_on_routers, + step, + get_frr_ipv6_linklocal, + create_route_maps, + create_bgp_community_lists, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_attributes, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NETWORK = {"ipv4": "100.0.10.1/32", "ipv6": "1::1/128"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.3.1", "ipv6": "fd00:0:0:3::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.4.1", "ipv6": "fd00:0:0:2::1"} +PREFERRED_NEXT_HOP = "link_local" +BGP_CONVERGENCE = False + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.16") + if result is not True: + pytest.skip("Kernel requirements are not met, kernel version should be >=4.16") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/ibgp_gshut_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +########################### +# Local APIs +########################### + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################### +# TESTCASES +########################### + + +def test_verify_graceful_shutdown_functionality_with_iBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality with iBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R4 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R4 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = { + "r1": { + "route_maps": { + "GSHUT-OUT": [{"set": {"locPrf": 0}}], + } + } + } + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_deleting_re_adding_route_map_with_iBGP_peers_p0(request): + """ + Verify graceful-shutdown functionality after deleting/re-adding route-map + with iBGP peers + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + reset_config_on_routers(tgen) + + step("Done in base config: Configure base config as per the topology") + step("Base config should be up, verify using BGP convergence") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Done in base config: Advertise prefixes from R1") + step("Verify BGP routes are received at R4 with best path from R3 to R1") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("On R1 configure:") + step("Create standard bgp community-list to permit graceful-shutdown:") + input_dict_1 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "GSHUT", + "value": "graceful-shutdown", + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route-map to set community GSHUT in OUT direction") + + input_dict_2 = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": {"community": {"num": "graceful-shutdown"}}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "GSHUT-OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "FRR is setting local-pref to 0 by-default on receiver GSHUT community, " + "below step is not needed, but keeping for reference" + ) + step( + "On R3, apply route-map IN direction to match GSHUT community " + "and set local-preference to 0." + ) + + step( + "Verify BGP convergence on R4 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = { + "r1": { + "route_maps": { + "GSHUT-OUT": [{"set": {"locPrf": 0}}], + } + } + } + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete route-map from R1") + del_rmap_dict = { + "r1": { + "route_maps": { + "GSHUT-OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "community": {"num": "graceful-shutdown", "delete": True} + }, + } + ] + } + } + } + + result = create_route_maps(tgen, del_rmap_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify BGP convergence on R3 and ensure that all neighbor state " + "is established" + ) + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("Ensure that best path is selected from R1->R3") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop=[next_hop1]) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=[next_hop1]) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Re-add route-map in R1") + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify BGP convergence on R3 and ensure all the neighbours state " + "is established" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP routes on R4:") + step("local pref for routes coming from R1 is set to 0.") + + for addr_type in ADDR_TYPES: + rmap_dict = {"r1": {"route_maps": {"GSHUT-OUT": [{"set": {"locPrf": 0}}]}}} + + static_routes = [NETWORK[addr_type]] + result = verify_bgp_attributes( + tgen, addr_type, dut, static_routes, "GSHUT-OUT", rmap_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Ensure that best path is selected from R4 to R3.") + + for addr_type in ADDR_TYPES: + dut = "r4" + next_hop1 = next_hop_per_address_family( + tgen, "r4", "r2", addr_type, NEXT_HOP_IP_1 + ) + next_hop2 = next_hop_per_address_family( + tgen, "r4", "r3", addr_type, NEXT_HOP_IP_2 + ) + + input_topo = {key: topo["routers"][key] for key in ["r1"]} + result = verify_bgp_rib( + tgen, addr_type, dut, input_topo, next_hop=[next_hop1, next_hop2] + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop=next_hop1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_instance_del_test/__init__.py b/tests/topotests/bgp_instance_del_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_instance_del_test/ce1 b/tests/topotests/bgp_instance_del_test/ce1 new file mode 120000 index 0000000..0924eb5 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/ce1 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/ce1 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/ce2 b/tests/topotests/bgp_instance_del_test/ce2 new file mode 120000 index 0000000..8c7a677 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/ce2 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/ce2 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/ce3 b/tests/topotests/bgp_instance_del_test/ce3 new file mode 120000 index 0000000..0abb8e5 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/ce3 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/ce3 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/ce4 b/tests/topotests/bgp_instance_del_test/ce4 new file mode 120000 index 0000000..ddee1ef --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/ce4 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/ce4 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/customize.py b/tests/topotests/bgp_instance_del_test/customize.py new file mode 120000 index 0000000..99fcf39 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/customize.py @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/customize.py \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/r1 b/tests/topotests/bgp_instance_del_test/r1 new file mode 120000 index 0000000..16babfa --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/r1 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/r1 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/r2 b/tests/topotests/bgp_instance_del_test/r2 new file mode 120000 index 0000000..e25b932 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/r2 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/r2 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/r3 b/tests/topotests/bgp_instance_del_test/r3 new file mode 120000 index 0000000..0d7c189 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/r3 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/r3 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/r4 b/tests/topotests/bgp_instance_del_test/r4 new file mode 120000 index 0000000..2d667d3 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/r4 @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/r4 \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/scripts b/tests/topotests/bgp_instance_del_test/scripts new file mode 120000 index 0000000..c46bf1f --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/scripts @@ -0,0 +1 @@ +../bgp_l3vpn_to_bgp_vrf/scripts \ No newline at end of file diff --git a/tests/topotests/bgp_instance_del_test/test_bgp_instance_del_test.py b/tests/topotests/bgp_instance_del_test/test_bgp_instance_del_test.py new file mode 100755 index 0000000..ddb5fe5 --- /dev/null +++ b/tests/topotests/bgp_instance_del_test/test_bgp_instance_del_test.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import sys +import pytest + +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + +from lib.ltemplate import * + + +pytestmark = [pytest.mark.bgpd, pytest.mark.ldpd, pytest.mark.ospfd] + + +def test_check_linux_vrf(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/check_linux_vrf.py", False, CliOnFail, CheckFunc) + + +def test_adjacencies(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/adjacencies.py", False, CliOnFail, CheckFunc) + + +def SKIP_test_add_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/add_routes.py", False, CliOnFail, CheckFunc) + + +def test_check_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/check_routes.py", False, CliOnFail, CheckFunc) + + +# manual data path setup test - remove once have bgp/zebra vrf path working +def test_check_linux_mpls(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/check_linux_mpls.py", False, CliOnFail, CheckFunc) + + +def test_del_bgp_instances(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/del_bgp_instances.py", False, CliOnFail, CheckFunc) + + +if __name__ == "__main__": + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/bgp_ipv4_class_e_peer/__init__.py b/tests/topotests/bgp_ipv4_class_e_peer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ipv4_class_e_peer/r1/bgpd.conf b/tests/topotests/bgp_ipv4_class_e_peer/r1/bgpd.conf new file mode 100644 index 0000000..bf0a68e --- /dev/null +++ b/tests/topotests/bgp_ipv4_class_e_peer/r1/bgpd.conf @@ -0,0 +1,12 @@ +! +allow-reserved-ranges +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 240.0.0.2 remote-as external + neighbor 240.0.0.2 timers 1 3 + neighbor 240.0.0.2 timers connect 1 + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_ipv4_class_e_peer/r1/zebra.conf b/tests/topotests/bgp_ipv4_class_e_peer/r1/zebra.conf new file mode 100644 index 0000000..d4ac46f --- /dev/null +++ b/tests/topotests/bgp_ipv4_class_e_peer/r1/zebra.conf @@ -0,0 +1,11 @@ +! +allow-reserved-ranges +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 240.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ipv4_class_e_peer/r2/bgpd.conf b/tests/topotests/bgp_ipv4_class_e_peer/r2/bgpd.conf new file mode 100644 index 0000000..7d08963 --- /dev/null +++ b/tests/topotests/bgp_ipv4_class_e_peer/r2/bgpd.conf @@ -0,0 +1,9 @@ +! +allow-reserved-ranges +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 240.0.0.1 remote-as external + neighbor 240.0.0.1 timers 1 3 + neighbor 240.0.0.1 timers connect 1 +! diff --git a/tests/topotests/bgp_ipv4_class_e_peer/r2/zebra.conf b/tests/topotests/bgp_ipv4_class_e_peer/r2/zebra.conf new file mode 100644 index 0000000..f0a350f --- /dev/null +++ b/tests/topotests/bgp_ipv4_class_e_peer/r2/zebra.conf @@ -0,0 +1,8 @@ +! +allow-reserved-ranges +! +interface r2-eth0 + ip address 240.0.0.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_ipv4_class_e_peer/test_bgp_ipv4_class_e_peer.py b/tests/topotests/bgp_ipv4_class_e_peer/test_bgp_ipv4_class_e_peer.py new file mode 100644 index 0000000..c7cb213 --- /dev/null +++ b/tests/topotests/bgp_ipv4_class_e_peer/test_bgp_ipv4_class_e_peer.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_ipv4_class_e_peer.py +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if the peering works by using IPv4 Class E IP ranges, and if +we don't treat next-hop as martian in such a case. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_ipv4_class_e_peer(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 240.0.0.1 json")) + expected = { + "240.0.0.1": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_next_hop_ipv4_class_e(): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 172.16.255.1/32 json") + ) + expected = { + "paths": [ + { + "valid": True, + "nexthops": [ + { + "ip": "240.0.0.1", + "accessible": True, + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R2" + + step("Check if IPv4 BGP peering works with Class E IP ranges") + test_func = functools.partial(_bgp_next_hop_ipv4_class_e) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see 172.16.255.1/32 via 240.0.0.1 on R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_ibgp_nbr.json b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_ibgp_nbr.json new file mode 100644 index 0000000..7f928b9 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_ibgp_nbr.json @@ -0,0 +1,85 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 24, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link5": {"ipv4": "auto", "ipv6": "auto"}} + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link5": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link0": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "activate": "ipv4", + "capability": "extended-nexthop" + } + } + } + } + } + } + }}}, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link0": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "200", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "activate": "ipv4", + "capability": "extended-nexthop" + } + } + }, + "r3": {"dest_link": {"r2": {}}}} + } + } + }}}, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "200", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + }}}, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}} + }}} diff --git a/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_nbr.json b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_nbr.json new file mode 100644 index 0000000..8e0f448 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_nbr.json @@ -0,0 +1,95 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 24, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link5": {"ipv4": "auto", "ipv6": "auto"}} + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link5": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link0": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + } + } + } + } + }}}, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link0": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "200", + "default_ipv4_unicast": "False", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": {"dest_link": {"r2": {"activate": "ipv4"}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + }, + "r3": {"dest_link": {"r2": {}}}} + } + }}}}, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "300", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + }}}, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r4": {}}}}}} + }}}}} diff --git a/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_unnumbered_nbr.json b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_unnumbered_nbr.json new file mode 100644 index 0000000..72d3a93 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ebgp_unnumbered_nbr.json @@ -0,0 +1,97 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 24, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link5": {"ipv4": "auto", "ipv6": "auto"}} + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link5": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link0": {"ipv4": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "activate": "ipv4", + "capability": "extended-nexthop", + "neighbor_type": "unnumbered" + } + } + } + } + } + } + }}}, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link0": {"ipv4": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "200", + "default_ipv4_unicast": "False", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": {"dest_link": {"r2": {"activate": "ipv4"}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "activate": "ipv4", + "capability": "extended-nexthop", + "neighbor_type": "unnumbered" + } + } + }, + "r3": {"dest_link": {"r2": {}}}} + } + }}}}, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "300", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + }}}, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r4": {}}}}}} + }}}}} diff --git a/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_nbr.json b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_nbr.json new file mode 100644 index 0000000..a7ea0c8 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_nbr.json @@ -0,0 +1,95 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 24, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r1-link5": {"ipv4": "auto", "ipv6": "auto"}} + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link3": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r0-link5": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link0": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + } + } + } + } + }}}, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link0": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": {"dest_link": {"r2": {"activate": "ipv4"}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + }, + "r3": {"dest_link": {"r2": {}}}} + } + }}}}, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "300", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + }}}, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r4": {}}}}}} + }}}}} diff --git a/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_unnumbered_nbr.json b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_unnumbered_nbr.json new file mode 100644 index 0000000..5e90d6b --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/rfc5549_ibgp_unnumbered_nbr.json @@ -0,0 +1,97 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 24, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:DB8:F::", "v6mask": 128}, + "routers": { + "r0": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link1": {"ipv4": "auto"}, + "r1-link2": {"ipv4": "auto"}, + "r1-link3": {"ipv4": "auto"}, + "r1-link4": {"ipv4": "auto"}, + "r1-link5": {"ipv4": "auto"}} + }, + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r0-link1": {"ipv4": "auto"}, + "r0-link2": {"ipv4": "auto"}, + "r0-link3": {"ipv4": "auto"}, + "r0-link4": {"ipv4": "auto"}, + "r0-link5": {"ipv4": "auto"}, + "r2-link0": {"ipv4": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "neighbor_type": "unnumbered", + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + } + } + } + } + }}}, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1-link0": {"ipv4": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto"}}, + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "False", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": {"dest_link": {"r2": {"activate": "ipv4"}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "neighbor_type": "unnumbered", + "capability": "extended-nexthop", + "activate": "ipv4" + } + } + }, + "r3": {"dest_link": {"r2": {}}}} + } + }}}}, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}}, + "bgp": { + "local_as": "300", + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + }}}, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto"}}, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r4": {}}}}}} + }}}}} diff --git a/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_ibgp_nbr.py b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_ibgp_nbr.py new file mode 100644 index 0000000..1873fb0 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_ibgp_nbr.py @@ -0,0 +1,950 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +"""RFC5549 Automation.""" +import os +import sys +import time +import pytest +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + get_frr_ipv6_linklocal, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + reset_config_on_routers, + step, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +topo = None + +# Global variables +NETWORK = { + "ipv4": [ + "11.0.20.1/32", + "11.0.20.2/32", + "11.0.20.3/32", + "11.0.20.4/32", + "11.0.20.5/32", + ], + "ipv6": ["1::1/128", "1::2/128", "1::3/128", "1::4/128", "1::5/128"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = { + "ipv4": ["10.0.0.1", "10.0.1.1", "10.0.2.1", "10.0.3.1", "10.0.4.1"], + "ipv6": ["Null0", "Null0", "Null0", "Null0", "Null0"], +} +NO_OF_RTES = 2 +NETWORK_CMD_IP = "1.0.1.17/32" +ADDR_TYPES = check_address_types() +TOPOOLOGY = """ + Please view in a fixed-width font such as Courier. + + +----+ + | R4 | + | | + +--+-+ + | ipv4 nbr + no bgp ebgp/ibgp | + | ebgp/ibgp + +----+ 5links +----+ 8links +--+-+ +----+ + |R0 +----------+ R1 +------------+ R2 | ipv6 nbr |R3 | + | +----------+ +------------+ +-------------+ | + +----+ +----+ ipv6 nbr +----+ +----+ +""" + +TESTCASES = """ +1. Verify Ipv4 route next hop is changed when advertised using +next hop -self command +2. Verify IPv4 route advertised to peer when IPv6 BGP session established + using peer-group +3. Verify IPv4 routes received with IPv6 nexthop are getting advertised + to another IBGP peer without changing the nexthop +4. Verify IPv4 routes advertised with correct nexthop when nexthop +unchange is configure on EBGP peers + """ + + +def setup_module(mod): + """Set up the pytest environment.""" + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/rfc5549_ebgp_ibgp_nbr.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def get_llip(onrouter, intf): + """ + API to get the link local ipv6 address of a particular interface + + Parameters + ---------- + * `fromnode`: Source node + * `tonode` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_llip('r1', 'r2-link0') + + Returns + ------- + 1) link local ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + tgen = get_topogen() + intf = topo["routers"][onrouter]["links"][intf]["interface"] + llip = get_frr_ipv6_linklocal(tgen, onrouter, intf) + if llip: + logger.info("llip ipv6 address to be set as NH is %s", llip) + return llip + return None + + +def get_glipv6(onrouter, intf): + """ + API to get the global ipv6 address of a particular interface + + Parameters + ---------- + * `onrouter`: Source node + * `intf` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_glipv6('r1', 'r2-link0') + + Returns + ------- + 1) global ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0] + if glipv6: + logger.info("Global ipv6 address to be set as NH is %s", glipv6) + return glipv6 + return None + + +# ################################## +# Test cases start here. +# ################################## +def test_ibgp_to_ibgp_p1(request): + """ + + Test Capability extended nexthop. + + Verify IPv4 routes received with IPv6 nexthop are getting advertised to + another IBGP peer without changing the nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + global topo + topo23 = deepcopy(topo) + build_config_from_json(tgen, topo23, save_bkup=False) + + step("Configure IPv6 EBGP session between R1 and R2 with " "global IPv6 address") + step("Configure IPv6 IBGP session betn R2 & R3 using IPv6 global address") + step("Enable capability extended-nexthop on both the IPv6 BGP peers") + step("Activate same IPv6 nbr from IPv4 unicast family") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + + # verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family. + bgp_convergence = verify_bgp_convergence(tgen, topo23) + assert bgp_convergence is True, "Testcase :Failed \n Error:" " {}".format( + bgp_convergence + ) + + step(" Configure 5 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo23, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + + gllip = get_llip("r1", "r2-link0") + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "activate": "ipv4", + "capability": "extended-nexthop", + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r3 = { + "r3": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "activate": "ipv4", + "capability": "extended-nexthop", + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 routes installed on R3 with global address without " + "changing the nexthop ( nexthop should IPv6 link local which is" + " received from R1)" + ) + gipv6 = get_glipv6("r1", "r2-link0") + dut = "r3" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gipv6, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gipv6 + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + write_test_footer(tc_name) + + +def test_ext_nh_cap_red_static_network_ibgp_peer_p1(request): + """ + + Test Extended capability next hop, with ibgp peer. + + Verify IPv4 routes advertise using "redistribute static" and + "network command" are received on EBGP peer with IPv6 nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + step( + " Configure IPv6 EBGP session between R1 & R2 with global IPv6 address" + " Enable capability extended-nexthop on the nbr from both the routers" + " Activate same IPv6 nbr from IPv4 unicast family" + ) + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "default_ipv4_unicast": "False", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "capability": "extended-nexthop", + "activate": "ipv4", + "next_hop_self": True, + "activate": "ipv4", + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r3 = { + "r3": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "capability": "extended-nexthop", + "activate": "ipv4", + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "default_ipv4_unicast": "False", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + gllip = get_llip("r1", "r2-link0") + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": gllip, + } + ] + } + } + + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + gllip = get_glipv6("r2", "r3") + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r3" + protocol = "bgp" + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": gllip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=gllip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_peer_group_p1(request): + """ + Test extended capability next hop with peer groups. + + Verify IPv4 routes received with IPv6 nexthop are getting advertised to + another IBGP peer without changing the nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + global topo + topo1 = deepcopy(topo) + step("Configure IPv6 EBGP session between R1 and R2 with " "global IPv6 address") + step("Configure IPv6 IBGP session betn R2 & R3 using IPv6 global address") + step("Enable capability extended-nexthop on both the IPv6 BGP peers") + step("Activate same IPv6 nbr from IPv4 unicast family") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "default_ipv4_unicast": "False", + "peer-group": { + "rfc5549": {"capability": "extended-nexthop", "remote-as": "200"} + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "activate": "ipv4", + "capability": "extended-nexthop", + "peer-group": "rfc5549", + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "default_ipv4_unicast": "False", + "peer-group": { + "rfc5549": {"capability": "extended-nexthop", "remote-as": "100"} + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "capability": "extended-nexthop", + "activate": "ipv4", + "peer-group": "rfc5549", + } + } + }, + "r3": {"dest_link": {"r2": {}}}, + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r3 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase :Failed \n Error:" " {}".format( + bgp_convergence + ) + + step(" Configure 2 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + + gllip = get_llip("r1", "r2-link0") + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "default_ipv4_unicast": "False", + "peer-group": { + "rfc5549": {"capability": "extended-nexthop", "remote-as": "200"} + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "activate": "ipv4", + "capability": "extended-nexthop", + "peer-group": "rfc5549", + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "default_ipv4_unicast": "False", + "peer-group": { + "rfc5549": {"capability": "extended-nexthop", "remote-as": "100"} + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link0": { + "capability": "extended-nexthop", + "activate": "ipv4", + "peer-group": "rfc5549", + } + } + }, + "r3": {"dest_link": {"r2": {}}}, + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r3 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"neighbor": {"r2": {"dest_link": {"r3": {}}}}}} + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase :Failed \n Error:" " {}".format( + bgp_convergence + ) + + step(" Configure 2 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + + gllip = get_llip("r1", "r2-link0") + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_nbr.py b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_nbr.py new file mode 100644 index 0000000..47cc378 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_nbr.py @@ -0,0 +1,617 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +"""RFC5549 Automation.""" +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + get_frr_ipv6_linklocal, + verify_rib, + create_static_routes, + check_address_types, + reset_config_on_routers, + step, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +topo = None + +# Global variables +NETWORK = { + "ipv4": [ + "11.0.20.1/32", + "11.0.20.2/32", + "11.0.20.3/32", + "11.0.20.4/32", + "11.0.20.5/32", + ], + "ipv6": ["1::1/128", "1::2/128", "1::3/128", "1::4/128", "1::5/128"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = { + "ipv4": ["10.0.0.1", "10.0.1.1", "10.0.2.1", "10.0.3.1", "10.0.4.1"], + "ipv6": ["Null0", "Null0", "Null0", "Null0", "Null0"], +} +NO_OF_RTES = 2 +NETWORK_CMD_IP = "1.0.1.17/32" +ADDR_TYPES = check_address_types() +BGP_CONVERGENCE_TIMEOUT = 10 +TOPOOLOGY = """ + Please view in a fixed-width font such as Courier. + +----+ + | R4 | + | | + +--+-+ + | ipv4 nbr + no bgp ebgp | + | ebgp/ibgp + +----+ 5links +----+ +--+-+ +----+ + |R0 +----------+ R1 | | R2 | ipv6 nbr |R3 | + | +----------+ +------------+ +-------------+ | + +----+ +----+ ipv6 nbr +----+ +----+ +""" + +TESTCASES = """ +TC6. Verify BGP speaker advertise IPv4 route to peer only if "extended + nexthop capability" is negotiated +TC7. Verify ipv4 route nexthop updated dynamically when in route-map is + applied on receiving BGP peer +TC8. Verify IPv4 routes advertise using "redistribute static" and "network + command" are received on EBGP peer with IPv6 nexthop +TC10. Verify IPv4 routes are deleted after un-configuring of "network +command" and "redistribute static knob" +TC18. Verify IPv4 routes installed with correct nexthop after deactivate + and activate neighbor from address family +TC19. Verify IPv4 route ping is working fine and nexhop installed in kernel + as IPv4 link-local address +TC24. Verify IPv4 prefix-list routes advertised to peer when prefix -list + applied in out direction +TC27. Verify IPv4 routes are intact after BGPd process restart +TC30. Verify Ipv4 route installed with correct next hop when same route + is advertised via IPV4 and IPv6 BGP peers +TC32. Verify IPv4 route received with IPv6 nexthop can be advertised to + another IPv4 BGP peers + """ + + +def setup_module(mod): + """Set up the pytest environment.""" + global topo, ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/rfc5549_ebgp_nbr.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment.""" + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def get_llip(onrouter, intf): + """ + API to get the link local ipv6 address of a particular interface + + Parameters + ---------- + * `fromnode`: Source node + * `tonode` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_llip('r1', 'r2-link0') + + Returns + ------- + 1) link local ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + tgen = get_topogen() + intf = topo["routers"][onrouter]["links"][intf]["interface"] + llip = get_frr_ipv6_linklocal(tgen, onrouter, intf) + if llip: + logger.info("llip ipv6 address to be set as NH is %s", llip) + return llip + return None + + +def get_glipv6(onrouter, intf): + """ + API to get the global ipv6 address of a particular interface + + Parameters + ---------- + * `onrouter`: Source node + * `intf` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_glipv6('r1', 'r2-link0') + + Returns + ------- + 1) global ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0] + if glipv6: + logger.info("Global ipv6 address to be set as NH is %s", glipv6) + return glipv6 + return None + + +# ################################## +# Test cases start here. +# ################################## + + +def test_ext_nh_cap_red_static_network_ebgp_peer_tc8_p0(request): + """ + + Test exted capability nexthop with route map in. + + Verify IPv4 routes advertise using "redistribute static" and + "network command" are received on EBGP peer with IPv6 nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + step("Configure IPv6 EBGP session between R1 and R2 with global" " IPv6 address") + reset_config_on_routers(tgen) + + step( + "Enable capability extended-nexthop on the nbr from both the " + " routers Activate same IPv6 nbr from IPv4 unicast family" + ) + step( + " Configure 2 IPv4 static " + "routes on R1 (nexthop for static route exists on different " + "link of R0" + ) + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv6"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv6"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "True", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + }, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + glip = get_llip("r1", "r2-link0") + assert glip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 and IPv6 routes advertised using static and network command " + "are received on R2 BGP & routing table , verify using show ip bgp " + "show ip route for IPv4 routes and show bgp ipv6,show ipv6 routes " + "for IPv6 routes ." + ) + + dut = "r2" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type][0], + "no_of_ip": 2, + "next_hop": glip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, addr_type, dut, verify_nh_for_static_rtes, next_hop=glip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_rib + ) + result = verify_rib( + tgen, + addr_type, + dut, + verify_nh_for_static_rtes, + next_hop=glip, + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify IPv4 routes are installed with IPv6 global nexthop of R1" + " R1 to R2 connected link" + ) + + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": glip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + +def test_ext_nh_cap_remove_red_static_network_ebgp_peer_tc10_p1(request): + """ + + Test exted capability nexthop with route map in. + + Verify IPv4 routes are deleted after un-configuring of + network command and redistribute static knob + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + step( + "Configure IPv6 EBGP session between R1 and R2 with global IPv6" + " address Enable capability extended-nexthop on the nbr from both" + " the routers , Activate same IPv6 nbr from IPv4 unicast family" + ) + step( + " Configure 2 IPv4 static routes " + " on R1 nexthop for static route exists on different link of R0" + ) + reset_config_on_routers(tgen) + + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Advertise static routes from IPv4 unicast family and IPv6 unicast" + " family respectively from R1. Configure loopback on R1 with IPv4 " + "address Advertise loobak from IPv4 unicast family using network " + "command from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "local_as": "100", + "default_ipv4_unicast": "True", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 and IPv6 routes advertised using static and network command are" + " received on R2 BGP and routing table , verify using show ip bgp" + " show ip route for IPv4 routes and show bgp, show ipv6 routes" + " for IPv6 routes ." + ) + + glipv6 = get_llip("r1", "r2-link0") + assert glipv6 is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "advertise_networks": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": get_glipv6, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=get_glipv6 + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, + "ipv4", + dut, + verify_nh_for_static_rtes, + next_hop=get_glipv6, + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 routes are installed with IPv6 global nexthop of R1 " + " R1 to R2 connected link" + ) + verify_nh_for_nw_cmd_rtes = { + "r1": { + "advertise_networks": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": glipv6, + } + ] + } + } + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glipv6, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + "ipv6": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": glipv6, + } + ] + } + } + + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=glipv6, expected=False + ) + assert ( + bgp_rib is not True + ), "Testcase {} : Failed \n Error: Routes still" " present in BGP rib".format( + tc_name + ) + result = verify_rib( + tgen, + "ipv4", + dut, + verify_nh_for_static_rtes, + next_hop=glipv6, + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: Routes " "still present in RIB".format(tc_name) + + step( + "After removing IPv4 routes from redistribute static those routes" + " are removed from R2, after re-advertising routes which are " + " advertised using network are still present in the on R2 with " + " IPv6 global nexthop, verify using show ip bgp and show ip routes" + ) + + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": glipv6, + } + ] + } + } + + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glipv6, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": NETWORK_CMD_IP, + "no_of_network": 1, + "delete": True, + } + ] + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + result = verify_rib( + tgen, + "ipv4", + dut, + verify_nh_for_nw_cmd_rtes, + next_hop=glipv6, + protocol=protocol, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Error: Routes still present in BGP rib".format( + tc_name + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_unnumbered_nbr.py b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_unnumbered_nbr.py new file mode 100644 index 0000000..395ef2d --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ebgp_unnumbered_nbr.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +"""RFC5549 Automation.""" +import os +import sys +import time +import pytest +import functools +import json + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + + +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +from lib.common_config import ( + write_test_header, + start_topology, + write_test_footer, + start_router, + stop_router, + verify_rib, + create_static_routes, + check_address_types, + reset_config_on_routers, + step, + get_frr_ipv6_linklocal, +) +from lib.topolog import logger +from lib.bgp import create_router_bgp, verify_bgp_convergence, verify_bgp_rib + +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +topo = None + +# Global variables +NO_OF_RTES = 2 +NETWORK_CMD_IP = "1.0.1.17/32" +NETWORK = { + "ipv4": [ + "11.0.20.1/32", + "11.0.20.2/32", + "11.0.20.3/32", + "11.0.20.4/32", + "11.0.20.5/32", + ], + "ipv6": ["1::1/128", "1::2/128", "1::3/128", "1::4/128", "1::5/128"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = { + "ipv4": ["10.0.0.1", "10.0.1.1", "10.0.2.1", "10.0.3.1", "10.0.4.1"], + "ipv6": ["Null0", "Null0", "Null0", "Null0", "Null0"], +} +INTF_LIST = [ + "r2-link0", + "r2-link1", + "r2-link2", + "r2-link3", + "r2-link4", + "r2-link5", + "r2-link6", + "r2-link7", +] +ADDR_TYPES = check_address_types() +TOPOOLOGY = """ + Please view in a fixed-width font such as Courier. + + +----+ + | R4 | + | | + +--+-+ + | ipv4 nbr + no bgp ebgp/ibgp | + | ebgp/ibgp + +----+ 5links +----+ 8links +--+-+ +----+ + |R0 +----------+ R1 +------------+ R2 | ipv6 nbr |R3 | + | +----------+ +------------+ +-------------+ | + +----+ +----+ ipv6 nbr +----+ +----+ +""" + +TESTCASES = """ +1. Verify IPv4 routes are advertised when IPv6 EBGP loopback session + established using Unnumbered interface +2. Verify IPv4 routes are installed with correct nexthop after +shut / no shut of nexthop and BGP peer interfaces +3. Verify IPv4 routes are intact after stop and start the FRR services + """ + + +def setup_module(mod): + """Set up the pytest environment.""" + global topo, ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/rfc5549_ebgp_unnumbered_nbr.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment.""" + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def get_llip(onrouter, intf): + """ + API to get the link local ipv6 address of a particular interface + + Parameters + ---------- + * `fromnode`: Source node + * `tonode` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_llip('r1', 'r2-link0') + + Returns + ------- + 1) link local ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + tgen = get_topogen() + intf = topo["routers"][onrouter]["links"][intf]["interface"] + llip = get_frr_ipv6_linklocal(tgen, onrouter, intf) + if llip: + logger.info("llip ipv6 address to be set as NH is %s", llip) + return llip + return None + + +def get_glipv6(onrouter, intf): + """ + API to get the global ipv6 address of a particular interface + + Parameters + ---------- + * `onrouter`: Source node + * `intf` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_glipv6('r1', 'r2-link0') + + Returns + ------- + 1) global ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0] + if glipv6: + logger.info("Global ipv6 address to be set as NH is %s", glipv6) + return glipv6 + return None + + +# ################################## +# Test cases start here. +# ################################## + + +def test_unnumbered_loopback_ebgp_nbr_p0(request): + """ + + Test extended capability nexthop with un numbered ebgp. + + Verify IPv4 routes are advertised when IPv6 EBGP loopback + session established using Unnumbered interface + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + + step("Configure IPv6 EBGP Unnumbered session between R1 and R2") + step("Enable capability extended-nexthop on both the IPv6 BGP peers") + step("Activate same IPv6 nbr from IPv4 unicast family") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step(" Configure 5 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + }, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + llip = get_llip("r1", "r2-link0") + assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, llip) + + dut = "r2" + protocol = "bgp" + for rte in range(0, NO_OF_RTES): + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK["ipv4"][rte], "no_of_ip": 1, "next_hop": llip} + ] + } + } + """ interface_list = ['r1-link0','r1-link1'] + nh_list =[] + for i in range(NO_OF_RTES): + nh_list.append(topo['routers']['r2']['links'][i][ + 'interface']) """ + bgp_rib = verify_rib( + tgen, + "ipv4", + dut, + # verify_nh_for_static_rtes, next_hop='r2-r1-eth0') + verify_nh_for_static_rtes, + next_hop=llip, + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_rib + ) + result = verify_rib( + tgen, + "ipv4", + dut, + verify_nh_for_static_rtes, + next_hop=llip, + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # verify the routes with nh as ext_nh + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": llip} + ] + } + } + + bgp_rib = verify_rib( + tgen, + "ipv4", + dut, + # verify_nh_for_nw_rtes, next_hop='r2-r1-eth0') + verify_nh_for_nw_rtes, + next_hop=llip, + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # stop/start -> restart FRR router and verify + stop_router(tgen, "r1") + stop_router(tgen, "r2") + start_router(tgen, "r1") + start_router(tgen, "r2") + step( + "After stop/start of FRR services , verify session up and routes " + "came up fine ,nh is proper using show bgp & show ipv6 route on R2 " + ) + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + llip = get_llip("r1", "r2-link0") + assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": llip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, + "ipv4", + dut, + # verify_nh_for_static_rtes, next_hop='r2-r1-eth0') + verify_nh_for_static_rtes, + next_hop=llip, + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": llip} + ] + } + } + bgp_rib = verify_rib( + tgen, + "ipv4", + dut, + # verify_nh_for_nw_rtes, next_hop='r2-r1-eth0') + verify_nh_for_nw_rtes, + next_hop=llip, + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + +def test_restart_frr_p2(request): + """ + + Test extended capability nexthop , restart frr. + + Verify IPv4 routes are intact after stop and start the FRR services + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + step("Configure IPv6 EBGP Unnumbered session between R1 and R2") + step("Enable capability extended-nexthop on both the IPv6 BGP peers") + step("Activate same IPv6 nbr from IPv4 unicast family") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step(" Configure 5 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + }, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + + llip = get_llip("r1", "r2-link0") + assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": llip, + } + ] + } + } + bgp_rib = verify_rib(tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": llip} + ] + } + } + + bgp_rib = verify_rib(tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # stop/start -> restart FRR router and verify + stop_router(tgen, "r1") + stop_router(tgen, "r2") + start_router(tgen, "r1") + start_router(tgen, "r2") + + step( + "After stop/start of FRR services , verify session up and routes " + "came up fine ,nh is proper using show bgp & show ipv6 route on R2 " + ) + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + llip = get_llip("r1", "r2-link0") + assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK["ipv4"][0], "no_of_ip": 1, "next_hop": llip} + ] + } + } + bgp_rib = verify_rib(tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # verify the routes with nh as ext_nh + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": llip} + ] + } + } + bgp_rib = verify_rib(tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + +def test_configure_gua_on_unnumbered_intf(request): + """ + Configure a global V6 address on an unnumbered interface on R1 + + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + + step("Configure IPv6 EBGP Unnumbered session between R1 and R2") + step("Enable capability extended-nexthop on both the IPv6 BGP peers") + step("Activate same IPv6 nbr from IPv4 unicast family") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + step("Verify bgp convergence as ipv6 nbr is enabled on ipv4 addr family.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step(" Configure 5 IPv4 static" " routes on R1, Nexthop as different links of R0") + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + }, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + r2 = tgen.gears["r2"] + + def bgp_prefix_received_gua_nh(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast 11.0.20.1/32 json")) + expected = { + "prefix": "11.0.20.1/32", + "paths": [ + { + "nexthops": [ + { + "ip": "5001:dead:beef::1", + "hostname": "r1", + "afi": "ipv6", + "scope": "global", + } + ] + } + ], + } + return topotest.json_cmp(output, expected) + + def bgp_prefix_received_v4_mapped_v6_nh(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast 11.0.20.1/32 json")) + expected = { + "prefix": "11.0.20.1/32", + "paths": [ + { + "nexthops": [ + { + "ip": "::ffff:a00:501", + "hostname": "r1", + "afi": "ipv6", + "scope": "global", + } + ] + } + ], + } + return topotest.json_cmp(output, expected) + + step("Configure a global V6 address on an unnumbered interface on R1") + output = tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-r2-eth5 + ipv6 address 5001:dead:beef::1/126 + ! + """ + ) + + # verify that r2 has received prefix with GUA as nexthop + test_func = functools.partial(bgp_prefix_received_gua_nh, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Testcase {} : Failed \n Error: Nexthop for prefix 11.0.20.1 \ + is not 5001:dead:beef::1".format( + tc_name + ) + + step("Configure a secondary global V6 address on an unnumbered interface on R1") + output = tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-r2-eth5 + ipv6 address 7771:dead:beef::1/126 + ! + """ + ) + # verify that r1 did not readvertise the prefix with secondary V6 address as the nexthop + test_func = functools.partial(bgp_prefix_received_gua_nh, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Testcase {} : Failed \n Error: Nexthop for prefix 11.0.20.1 \ + is not 5001:dead:beef::1".format( + tc_name + ) + + step("Unconfigure the secondary global V6 address from unnumbered interface on R1") + output = tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-r2-eth5 + no ipv6 address 7771:dead:beef::1/126 + ! + """ + ) + # verify that r1 still has the prefix with primary GUA as the nexthop + test_func = functools.partial(bgp_prefix_received_gua_nh, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Testcase {} : Failed \n Error: Nexthop for prefix 11.0.20.1 \ + is not 5001:dead:beef::1".format( + tc_name + ) + + step("Unconfigure the primary global V6 address from unnumbered interface on R1") + output = tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-r2-eth5 + no ipv6 address 5001:dead:beef::1/126 + ! + """ + ) + # verify that r1 has rcvd the prefix with v4-mapped-v6 address as the nexthop + test_func = functools.partial(bgp_prefix_received_v4_mapped_v6_nh, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Testcase {} : Failed \n Error: Nexthop for prefix 11.0.20.1 \ + is not ::ffff:a00:501".format( + tc_name + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_nbr.py b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_nbr.py new file mode 100644 index 0000000..56152b9 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_nbr.py @@ -0,0 +1,975 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +"""RFC5549 Automation.""" +import os +import sys +import time +import pytest +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + addKernelRoute, + write_test_footer, + create_prefix_lists, + verify_rib, + create_static_routes, + reset_config_on_routers, + step, + create_route_maps, + get_frr_ipv6_linklocal, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +topo = None +# Global variables +NETWORK = { + "ipv4": [ + "11.0.20.1/32", + "11.0.20.2/32", + "11.0.20.3/32", + "11.0.20.4/32", + "11.0.20.5/32", + ], + "ipv6": ["1::1/128", "1::2/128", "1::3/128", "1::4/128", "1::5/128"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = { + "ipv4": ["10.0.0.1", "10.0.1.1", "10.0.2.1", "10.0.3.1", "10.0.4.1"], + "ipv6": ["Null0", "Null0", "Null0", "Null0", "Null0"], +} +NETWORK_CMD_IP = "1.0.1.17/32" +NO_OF_RTES = 2 +TOPOOLOGY = """ + Please view in a fixed-width font such as Courier. + + +----+ + | R4 | + | | + +--+-+ + | ipv4 nbr + no bgp ebgp/ibgp | + | ebgp/ibgp + +----+ 5links +----+ +--+-+ +----+ + |R0 +----------+ R1 | | R2 | ipv6 nbr |R3 | + | +----------+ +------------+ +-------------+ | + +----+ +----+ ipv6 nbr +----+ +----+ +""" + +TESTCASES = """ +1. Verify IPv4 and IPv6 routes advertise using "redistribute static" + and "network command" are received on IBGP peer with IPv6 nexthop +2. Verify IPv4 routes are advertised and withdrawn when IPv6 IBGP session + established using loopback interface +3. Verify IPv4 routes are advertised to peer when static routes are + configured with ADMIN distance and tag option +4. Verify IPv4 routes advertised to peer when BGP session established + using link-local address + """ + + +def setup_module(mod): + """Set up the pytest environment.""" + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/rfc5549_ibgp_nbr.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment.""" + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def get_llip(onrouter, intf): + """ + API to get the link local ipv6 address of a particular interface + + Parameters + ---------- + * `fromnode`: Source node + * `tonode` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_llip('r1', 'r2-link0') + + Returns + ------- + 1) link local ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + tgen = get_topogen() + intf = topo["routers"][onrouter]["links"][intf]["interface"] + llip = get_frr_ipv6_linklocal(tgen, onrouter, intf) + + if llip: + logger.info("llip ipv6 address to be set as NH is %s", llip) + return llip + return None + + +def get_glipv6(onrouter, intf): + """ + API to get the global ipv6 address of a particular interface + + Parameters + ---------- + * `onrouter`: Source node + * `intf` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_glipv6('r1', 'r2-link0') + + Returns + ------- + 1) global ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0] + if glipv6: + logger.info("Global ipv6 address to be set as NH is %s", glipv6) + return glipv6 + return None + + +# ################################## +# Test cases start here. +# ################################## + + +def test_ext_nh_cap_red_static_network_ibgp_peer_p1(request): + """ + + Test extended capability nexthop with ibgp peer. + + Verify IPv4 and IPv6 routes advertise using "redistribute static" + and "network command" are received on IBGP peer with IPv6 nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + step( + "Configure IPv6 EBGP session between R1 and R2 with global IPv6" + " address Enable capability extended-nexthop on the nbr from both" + " the routers" + ) + step( + "Change ebgp to ibgp nbrs between r1 and r2 , Activate same IPv6" + " nbr from IPv4 unicast family " + ) + + step( + " Configure 5 IPv4 static routes" + " on R1 nexthop for static route exists on different link of R0" + ) + + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Advertise static routes from IPv4 unicast family and IPv6 unicast" + " family respectively from R1.Configure loopback on R1 with IPv4 addr" + " & Advertise loopback from IPv4 unicast family using network cmd " + " from R1" + ) + # this test case needs ipv6 routes to be configured + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + }, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + glip = get_llip("r1", "r2-link0") + assert glip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 and IPv6 routes advertised using static & network command are" + "received on R2 BGP and routing table , verify using show ip bgp" + "show ip route for IPv4 routes and show bgp, show ipv6 routes" + "for IPv6 routes ." + ) + + dut = "r2" + protocol = "bgp" + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": glip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=glip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=glip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify IPv4 routes are installed with IPv6 global nexthop of R1" + "R1 to R2 connected link" + ) + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": glip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=glip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_ext_nh_cap_admin_dist_tag_ibgp_peer_p1(request): + """ + + Test extended capability nexthop with admin distance and route tag. + + Verify IPv4 routes are advertised to peer when static routes + are configured with ADMIN distance and tag option + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + step( + "Configure IPv6 EBGP session between R1 and R2 with global IPv6" + " address Enable capability extended-nexthop on the nbr from both" + " the routers" + ) + step( + "Change ebgp to ibgp nbrs between r1 and r2 , Activate same IPv6" + " nbr from IPv4 unicast family " + ) + step( + " Configure 5 IPv4 static routes" + " on R1 nexthop for static route exists on different link of R0" + ) + count = 0 + for rte in range(0, NO_OF_RTES): + count += 1 + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + "admin_distance": 100 + count, + "tag": 4001 + count, + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Advertise static routes from IPv4 unicast family & IPv6 unicast" + " family respectively from R1.Configure loopback on R1 with IPv4 " + "address & Advertise loopback from IPv4 unicast family " + "using network cmd from R1" + ) + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}} + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + glip = get_llip("r1", "r2-link0") + assert glip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 and IPv6 routes advertised using static & network cmd are" + "received on R2 BGP and routing table , verify using show ip bgp" + "show ip route for IPv4 routes and show bgp, show ipv6 routes" + "for IPv6 routes ." + ) + + dut = "r2" + protocol = "bgp" + count = 0 + # verify the routes with nh as ext_nh + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": glip, + "admin_distance": 100 + count, + "tag": 4001 + count, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=glip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=glip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + count = 0 + for rte in range(0, NO_OF_RTES): + count += 10 + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 0 + count, + "action": "permit", + "network": NETWORK["ipv4"][rte], + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n " "Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_6 = { + "r3": { + "route_maps": { + "rmap_match_tag_1_{}".format("ipv4"): [ + { + "action": "deny", + "match": { + "ipv4": {"prefix_lists": "pf_list_1_{}".format("ipv4")} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_7 = { + "r1": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link0": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_ibgp_loopback_nbr_p1(request): + """ + Verify Extended capability nexthop with loopback interface. + + Verify IPv4 routes are advertised and withdrawn when IPv6 IBGP + session established using loopback interface + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + global topo + topo1 = deepcopy(topo) + reset_config_on_routers(tgen) + step("Configure IPv6 global address between R1 and R2") + step( + "Configure loopback on R1 and R2 and establish EBGP session " + "between R1 and R2 over loopback global ip" + ) + step("Configure static route on R1 and R2 for loopback reachability") + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + + for routerN in ["r1", "r2"]: + for addr_type in ["ipv6"]: + for bgp_neighbor in topo1["routers"][routerN]["bgp"]["address_family"][ + addr_type + ]["unicast"]["neighbor"].keys(): + # Adding ['source_link'] = 'lo' key:value pair + if bgp_neighbor == "r1" or bgp_neighbor == "r2": + topo1["routers"][routerN]["bgp"]["address_family"][addr_type][ + "unicast" + ]["neighbor"][bgp_neighbor]["dest_link"] = { + "lo": { + "source_link": "lo", + "ebgp_multihop": 2, + "capability": "extended-nexthop", + "activate": "ipv4", + } + } + # Creating configuration from JSON + build_config_from_json(tgen, topo1, save_bkup=False) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r1-link0": {"deactivate": "ipv6"}} + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": {"r2-link0": {"deactivate": "ipv6"}} + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r1-link0": {"deactivate": "ipv4"}} + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r2 = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": {"r2-link0": {"deactivate": "ipv4"}} + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + r2_lo_v4 = topo["routers"]["r2"]["links"]["lo"]["ipv4"] + r2_lo_v6 = topo["routers"]["r2"]["links"]["lo"]["ipv6"] + r1_lo_v4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_lo_v6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_r2_intf = topo["routers"]["r1"]["links"]["r2-link0"]["interface"] + r2_r1_intf = topo["routers"]["r2"]["links"]["r1-link0"]["interface"] + + r1_r2_v6_nh = topo["routers"]["r1"]["links"]["r2-link0"]["ipv6"].split("/")[0] + r2_r1_v6_nh = topo["routers"]["r2"]["links"]["r1-link0"]["ipv6"].split("/")[0] + + ipv4_list = [("r1", r1_r2_intf, [r2_lo_v4]), ("r2", r2_r1_intf, [r1_lo_v4])] + + ipv6_list = [ + ("r1", r1_r2_intf, [r2_lo_v6], r2_r1_v6_nh), + ("r2", r2_r1_intf, [r1_lo_v6], r1_r2_v6_nh), + ] + + for dut, intf, loop_addr in ipv4_list: + result = addKernelRoute(tgen, dut, intf, loop_addr) + # assert result is True, "Testcase {}:Failed \n Error: {}". \ + # format(tc_name, result) + + for dut, intf, loop_addr, next_hop in ipv6_list: + result = addKernelRoute(tgen, dut, intf, loop_addr, next_hop) + # assert result is True, "Testcase {}:Failed \n Error: {}". \ + # format(tc_name, result) + + r2_lo_v4 = topo["routers"]["r2"]["links"]["lo"]["ipv4"] + r2_lo_v6 = topo["routers"]["r2"]["links"]["lo"]["ipv6"] + r1_lo_v4 = topo["routers"]["r1"]["links"]["lo"]["ipv4"] + r1_lo_v6 = topo["routers"]["r1"]["links"]["lo"]["ipv6"] + r1_r2_intf = topo["routers"]["r1"]["links"]["r2-link0"]["interface"] + r2_r1_intf = topo["routers"]["r2"]["links"]["r1-link0"]["interface"] + + r1_r2_v6_nh = topo["routers"]["r1"]["links"]["r2-link0"]["ipv6"].split("/")[0] + r2_r1_v6_nh = topo["routers"]["r2"]["links"]["r1-link0"]["ipv6"].split("/")[0] + + r1_r2_v4_nh = topo["routers"]["r1"]["links"]["r2-link0"]["ipv4"].split("/")[0] + r2_r1_v4_nh = topo["routers"]["r2"]["links"]["r1-link0"]["ipv4"].split("/")[0] + + input_dict = { + "r1": { + "static_routes": [ + {"network": r2_lo_v4, "next_hop": r2_r1_v4_nh}, + {"network": r2_lo_v6, "next_hop": r2_r1_v6_nh}, + ] + }, + "r2": { + "static_routes": [ + {"network": r1_lo_v4, "next_hop": r1_r2_v4_nh}, + {"network": r1_lo_v6, "next_hop": r1_r2_v6_nh}, + ] + }, + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + # Api call verify whether BGP is converged + result = verify_bgp_convergence(tgen, topo1) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Enable cap ext nh on r1 and r2 and activate in ipv4 addr family") + configure_bgp_on_r1 = { + "r1": { + "default_ipv4_unicast": False, + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "lo": { + "activate": "ipv4", + "capability": "extended-nexthop", + } + } + } + } + } + } + } + }, + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r2 = { + "r2": { + "default_ipv4_unicast": False, + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "lo": { + "activate": "ipv4", + "capability": "extended-nexthop", + } + } + } + } + } + } + } + }, + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify bgp convergence.") + bgp_convergence = verify_bgp_convergence(tgen, topo1) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Configure 2 IPv4 static" " routes on R1, Nexthop as different links of R0") + + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise static routes from IPv4 unicast family and IPv6 " + "unicast family respectively from R1 using red static cmd " + "Advertise loopback from IPv4 unicast family using network command " + "from R1" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "IPv4 routes advertised using static and network command are " + " received on R2 BGP and routing table , " + "verify using show ip bgp, show ip route for IPv4 routes ." + ) + + gllip = (topo1["routers"]["r1"]["links"]["lo"]["ipv6"].split("/")[0]).lower() + assert gllip is not None, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": gllip, + } + ] + } + } + bgp_rib = verify_bgp_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip + ) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": gllip} + ] + } + } + bgp_rib = verify_bgp_rib(tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Remove IPv4 routes advertised using network command" + " from R1 and advertise again" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + { + "network": NETWORK_CMD_IP, + "no_of_network": 1, + "delete": True, + } + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + { + "network": NETWORK_CMD_IP, + "no_of_network": 1, + } + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "After removing IPv4 routes from network command , routes which are " + "advertised using redistribute static are still present in the on " + "R2 , verify using show ip bgp and show ip route" + ) + + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": gllip} + ] + } + } + bgp_rib = verify_bgp_rib(tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Remove IPv4 routes advertised using redistribute static" + " command from R1 and advertise again" + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + } + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}} + } + } + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + "After removing IPv4 routes from redistribute static , routes which" + " are advertised using network are still present in the on R2 , " + "verify using show ip bgp and show ip route" + ) + + verify_nh_for_nw_rtes = { + "r1": { + "static_routes": [ + {"network": NETWORK_CMD_IP, "no_of_ip": 1, "next_hop": gllip} + ] + } + } + bgp_rib = verify_bgp_rib(tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip) + assert bgp_rib is True, "Testcase {} : Failed \n Error: {}".format(tc_name, bgp_rib) + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_rtes, next_hop=gllip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_unnumbered_nbr.py b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_unnumbered_nbr.py new file mode 100644 index 0000000..526b267 --- /dev/null +++ b/tests/topotests/bgp_ipv4_over_ipv6/test_rfc5549_ibgp_unnumbered_nbr.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# + + +"""RFC5549 Automation.""" +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + step, + reset_config_on_routers, + get_frr_ipv6_linklocal, +) +from lib.topolog import logger +from lib.bgp import create_router_bgp, verify_bgp_convergence +from lib.topojson import build_config_from_json + +# Global variables +topo = None + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK_CMD_IP = "1.0.1.17/32" +NETWORK = { + "ipv4": [ + "11.0.20.1/32", + "11.0.20.2/32", + "11.0.20.3/32", + "11.0.20.4/32", + "11.0.20.5/32", + ], + "ipv6": ["1::1/128", "1::2/128", "1::3/128", "1::4/128", "1::5/128"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = { + "ipv4": ["10.0.0.1", "10.0.1.1", "10.0.2.1", "10.0.3.1", "10.0.4.1"], + "ipv6": ["Null0", "Null0", "Null0", "Null0", "Null0"], +} +ADDR_TYPES = check_address_types() +NO_OF_RTES = 2 +TOPOOLOGY = """ + Please view in a fixed-width font such as Courier. + +----+ + | R4 | + | | + +--+-+ + | ipv4 nbr + no bgp ebgp/ibgp | + | ebgp/ibgp + +----+ 2links +----+ 8links +--+-+ +----+ + |R0 +----------+ R1 + + R2 | ipv6 nbr |R3 | + | +----------+ +------------+ +-------------+ | + +----+ +----+ ipv6 nbr +----+ +----+ +""" + +TESTCASES = """ +1. Verify IPv4 routes are deleted after un-configuring "network command +" and "redistribute static knob" with Unnumbered IPv6 IBGP session + """ + + +def setup_module(mod): + """Set up the pytest environment.""" + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/rfc5549_ibgp_unnumbered_nbr.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment.""" + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def get_llip(onrouter, intf): + """ + API to get the link local ipv6 address of a particular interface + + Parameters + ---------- + * `fromnode`: Source node + * `tonode` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_llip('r1', 'r2-link0') + + Returns + ------- + 1) link local ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + tgen = get_topogen() + intf = topo["routers"][onrouter]["links"][intf]["interface"] + llip = get_frr_ipv6_linklocal(tgen, onrouter, intf) + + if llip: + logger.info("llip ipv6 address to be set as NH is %s", llip) + return llip + return None + + +def get_glipv6(onrouter, intf): + """ + API to get the global ipv6 address of a particular interface + + Parameters + ---------- + * `onrouter`: Source node + * `intf` : interface for which link local ip needs to be returned. + + Usage + ----- + result = get_glipv6('r1', 'r2-link0') + + Returns + ------- + 1) global ipv6 address from the interface. + 2) errormsg - when link local ip not found. + """ + glipv6 = (topo["routers"][onrouter]["links"][intf]["ipv6"]).split("/")[0] + if glipv6: + logger.info("Global ipv6 address to be set as NH is %s", glipv6) + return glipv6 + return None + + +# ################################## +# Test cases start here. +# ################################## + + +def test_ext_nh_cap_red_static_network_ebgp_peer_unnumbered_nbr_p1(request): + """ + + Test extended capability nexthop. + + Verify IPv4 routes advertise using "redistribute static" and + "network command" are received on EBGP peer with IPv6 nexthop + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + reset_config_on_routers(tgen) + step( + "Configure IPv6 IBGP Unnumbered session between R1 and R2 and enable " + "ipv6 nd ra-interval 10 in the interface" + ) + + step( + "Enable capability extended-nexthop" + "on the neighbor from both the routers and " + "ipv6 nd ra-interval 10 on link connected between R1 and R2" + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase :Failed \n Error:" " {}".format( + bgp_convergence + ) + + for rte in range(0, NO_OF_RTES): + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][rte], + "no_of_ip": 1, + "next_hop": NEXT_HOP["ipv4"][rte], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step( + "Advertise static routes from IPv4 unicast family and IPv6 unicast " + "family respectively from R1 " + "Configure loopback on R1 with IPv4 address Advertise loopback " + "from IPv4 unicast family using network cmd from R1 " + ) + + configure_bgp_on_r1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [{"redist_type": "static"}], + "advertise_networks": [ + {"network": NETWORK_CMD_IP, "no_of_network": 1} + ], + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, configure_bgp_on_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + llip = get_llip("r1", "r2-link0") + assert llip is not None, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step( + " IPv4 and IPv6 routes advertised using static and network command are" + " received on R2 BGP and routing table , verify using show ip bgp" + " show ip route for IPv4 routes and show bgp show ipv6 routes" + " for IPv6 routes ." + ) + + dut = "r2" + protocol = "bgp" + verify_nh_for_static_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK["ipv4"][0], + "no_of_ip": NO_OF_RTES, + "next_hop": llip, + } + ] + } + } + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_static_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + verify_nh_for_nw_cmd_rtes = { + "r1": { + "static_routes": [ + { + "network": NETWORK_CMD_IP, + "no_of_ip": 1, + "next_hop": llip, + } + ] + } + } + + result = verify_rib( + tgen, "ipv4", dut, verify_nh_for_nw_cmd_rtes, next_hop=llip, protocol=protocol + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv6_ll_peering/__init__.py b/tests/topotests/bgp_ipv6_ll_peering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf b/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf new file mode 100644 index 0000000..724cbf8 --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r1/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65001 + bgp router-id 10.0.0.1 + no bgp ebgp-requires-policy + neighbor fe80:1::2 remote-as external + neighbor fe80:1::2 timers 3 10 + neighbor fe80:1::2 interface r1-eth0 diff --git a/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf b/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf new file mode 100644 index 0000000..4e93d4f --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r1/zebra.conf @@ -0,0 +1,4 @@ +! +interface r1-eth0 + ipv6 address fe80:1::1/64 +! diff --git a/tests/topotests/bgp_ipv6_ll_peering/r2/bgpd.conf b/tests/topotests/bgp_ipv6_ll_peering/r2/bgpd.conf new file mode 100644 index 0000000..44f79df --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65002 + bgp router-id 10.0.0.2 + no bgp ebgp-requires-policy + neighbor fe80:1::1 remote-as external + neighbor fe80:1::1 timers 3 10 + neighbor fe80:1::1 interface r2-eth0 diff --git a/tests/topotests/bgp_ipv6_ll_peering/r2/zebra.conf b/tests/topotests/bgp_ipv6_ll_peering/r2/zebra.conf new file mode 100644 index 0000000..1e703cd --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ipv6 address fe80:1::2/64 +! diff --git a/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py b/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py new file mode 100644 index 0000000..ea974b5 --- /dev/null +++ b/tests/topotests/bgp_ipv6_ll_peering/test_bgp_ipv6_ll_peering.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Check if IPv6 Link-Local BGP peering works fine. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_ipv6_link_local_peering(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": { + "fe80:1::2": { + "state": "Established", + } + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP convergence on R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_ipv6_rtadv/__init__.py b/tests/topotests/bgp_ipv6_rtadv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/bgpd.conf b/tests/topotests/bgp_ipv6_rtadv/r1/bgpd.conf new file mode 100644 index 0000000..0918796 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r1/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 101 + bgp router-id 10.254.254.1 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r1-eth0 interface peer-group r2g + neighbor r1-eth0 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/ipv4_routes.json b/tests/topotests/bgp_ipv6_rtadv/r1/ipv4_routes.json new file mode 100644 index 0000000..73c7fbb --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r1/ipv4_routes.json @@ -0,0 +1,40 @@ +{ + "10.254.254.2/32": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "10.254.254.2/32", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "10.254.254.1/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/ipv6_routes.json b/tests/topotests/bgp_ipv6_rtadv/r1/ipv6_routes.json new file mode 100644 index 0000000..a50bffd --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r1/ipv6_routes.json @@ -0,0 +1,34 @@ +{ + "2001:db8:1::/64": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "interfaceName": "r1-eth0", + "active": true, + "afi": "ipv6" + } + ] + }, + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r1-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_ipv6_rtadv/r1/zebra.conf b/tests/topotests/bgp_ipv6_rtadv/r1/zebra.conf new file mode 100644 index 0000000..1397c7f --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r1/zebra.conf @@ -0,0 +1,9 @@ +! debug zebra packet recv +! debug zebra packet send +log stdout +interface lo + ip address 10.254.254.1/32 +! +interface r1-eth0 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bgp_ipv6_rtadv/r2/bgpd.conf b/tests/topotests/bgp_ipv6_rtadv/r2/bgpd.conf new file mode 100644 index 0000000..55d4856 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 102 + bgp router-id 10.254.254.2 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r2-eth0 interface peer-group r2g + neighbor r2-eth0 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bgp_ipv6_rtadv/r2/ipv4_routes.json b/tests/topotests/bgp_ipv6_rtadv/r2/ipv4_routes.json new file mode 100644 index 0000000..d4b1410 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r2/ipv4_routes.json @@ -0,0 +1,40 @@ +{ + "10.254.254.2/32": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "10.254.254.2/32", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "lo", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ], + "10.254.254.1/32": [ + { + "distance": 20, + "protocol": "bgp", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "10.254.254.1/32", + "nexthops": [ + { + "interfaceName": "r2-eth0", + "fib": true, + "flags": 3, + "active": true, + "afi": "ipv6" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_ipv6_rtadv/r2/ipv6_routes.json b/tests/topotests/bgp_ipv6_rtadv/r2/ipv6_routes.json new file mode 100644 index 0000000..e7bfb99 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r2/ipv6_routes.json @@ -0,0 +1,21 @@ +{ + "2001:db8:1::/64": [ + { + "distance": 0, + "protocol": "connected", + "metric": 0, + "selected": true, + "destSelected": true, + "prefix": "2001:db8:1::/64", + "nexthops": [ + { + "directlyConnected": true, + "interfaceName": "r2-eth0", + "fib": true, + "flags": 3, + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_ipv6_rtadv/r2/zebra.conf b/tests/topotests/bgp_ipv6_rtadv/r2/zebra.conf new file mode 100644 index 0000000..0131a11 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/r2/zebra.conf @@ -0,0 +1,9 @@ +ip forwarding +ipv6 forwarding +! +interface lo + ip address 10.254.254.2/32 +! +interface r2-eth0 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.dot b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.dot new file mode 100644 index 0000000..da67c29 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.dot @@ -0,0 +1,44 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo2"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + +} diff --git a/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py new file mode 100644 index 0000000..045ac91 --- /dev/null +++ b/tests/topotests/bgp_ipv6_rtadv/test_bgp_ipv6_rtadv.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_ipv6_rtadv.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by 6WIND +# + +""" + test_bgp_ipv6_rtadv.py: Test the FRR BGP daemon with BGP IPv6 interface + with route advertisements on a separate netns. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check IPv4 routing tables. + logger.info("Checking IPv4 routes for convergence") + for router in tgen.routers().values(): + json_file = "{}/{}/ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=160, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check IPv6 routing tables. + logger.info("Checking IPv6 routes for convergence") + for router in tgen.routers().values(): + json_file = "{}/{}/ipv6_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ipv6 route json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=160, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_l3vpn_label_export/__init__.py b/tests/topotests/bgp_l3vpn_label_export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_l3vpn_label_export/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_label_export/r1/bgpd.conf new file mode 100644 index 0000000..bb1ed4c --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r1/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 65001 + bgp router-id 192.0.2.1 + no bgp default ipv4-unicast + no bgp ebgp-requires-policy + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 2 + address-family ipv4 vpn + neighbor 192.0.2.2 activate + exit-address-family +! +router bgp 65001 vrf vrf1 + address-family ipv4 unicast + redistribute connected + label vpn export 1111 + rd vpn export 101:1 + rt vpn both 52:100 + import vpn + export vpn + exit-address-family +! diff --git a/tests/topotests/bgp_l3vpn_label_export/r1/ldpd.conf b/tests/topotests/bgp_l3vpn_label_export/r1/ldpd.conf new file mode 100644 index 0000000..04ae068 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r1/ldpd.conf @@ -0,0 +1,26 @@ +hostname r1 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 192.0.2.1 + ! + address-family ipv4 + discovery transport-address 192.0.2.1 + ! + interface r1-eth0 + ! + interface r1-eth1 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_label_export/r1/staticd.conf b/tests/topotests/bgp_l3vpn_label_export/r1/staticd.conf new file mode 100644 index 0000000..7f2f057 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r1/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.1.2 diff --git a/tests/topotests/bgp_l3vpn_label_export/r1/zebra.conf b/tests/topotests/bgp_l3vpn_label_export/r1/zebra.conf new file mode 100644 index 0000000..7bdacb1 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_l3vpn_label_export/r2/bgpd.conf b/tests/topotests/bgp_l3vpn_label_export/r2/bgpd.conf new file mode 100644 index 0000000..18a11cf --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r2/bgpd.conf @@ -0,0 +1,23 @@ +router bgp 65002 + bgp router-id 192.0.2.2 + no bgp default ipv4-unicast + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 65001 + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.1.1 ebgp-multihop 2 + neighbor 192.168.1.1 update-source 192.0.2.2 + address-family ipv4 vpn + neighbor 192.168.1.1 activate + exit-address-family +! +router bgp 65002 vrf vrf1 + address-family ipv4 unicast + redistribute connected + label vpn export 2222 + rd vpn export 102:1 + rt vpn both 52:100 + import vpn + export vpn + exit-address-family +! diff --git a/tests/topotests/bgp_l3vpn_label_export/r2/ldpd.conf b/tests/topotests/bgp_l3vpn_label_export/r2/ldpd.conf new file mode 100644 index 0000000..f4307f1 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r2/ldpd.conf @@ -0,0 +1,24 @@ +hostname r2 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 192.0.2.2 + ! + address-family ipv4 + discovery transport-address 192.0.2.2 + ! + interface r2-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_label_export/r2/staticd.conf b/tests/topotests/bgp_l3vpn_label_export/r2/staticd.conf new file mode 100644 index 0000000..e3f5d7d --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r2/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.1/32 192.168.1.1 diff --git a/tests/topotests/bgp_l3vpn_label_export/r2/zebra.conf b/tests/topotests/bgp_l3vpn_label_export/r2/zebra.conf new file mode 100644 index 0000000..40dfa98 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_l3vpn_label_export/test_bgp_l3vpn_label_export.py b/tests/topotests/bgp_l3vpn_label_export/test_bgp_l3vpn_label_export.py new file mode 100644 index 0000000..7c23a3e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_label_export/test_bgp_l3vpn_label_export.py @@ -0,0 +1,587 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by Louis Scalbert +# Copyright 2023 6WIND S.A. +# + +""" + +""" + +import os +import re +import sys +import json +import pytest +import functools + +from copy import deepcopy + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import kill_router_daemons, start_router_daemons, step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for rtr in [1, 2]: + tgen.add_router("r{}".format(rtr)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rtr in [1, 2]: + tgen.gears["r{}".format(rtr)].cmd("ip link add vrf1 type vrf table 10") + tgen.gears["r{}".format(rtr)].cmd("ip link set vrf1 up") + tgen.gears["r{}".format(rtr)].cmd( + "ip address add dev vrf1 192.0.3.{}/32".format(rtr) + ) + tgen.gears["r{}".format(rtr)].run( + "sysctl -w net.mpls.conf.r{}-eth0.input=1".format(rtr) + ) + tgen.gears["r{}".format(rtr)].run("sysctl -w net.mpls.conf.vrf1.input=1") + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def check_bgp_vpn_prefix(label, rname="r1", rd=None): + tgen = get_topogen() + + if rd: + output = json.loads( + tgen.gears[rname].vtysh_cmd( + "show bgp ipv4 vpn rd {} 192.0.3.2/32 json".format(rd) + ) + ) + else: + output = json.loads( + tgen.gears[rname].vtysh_cmd( + "show bgp vrf vrf1 ipv4 unicast 192.0.3.2/32 json" + ) + ) + + if label == "auto": + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.2"}], + }, + ] + } + elif label and not rd: + expected = { + "paths": [ + { + "valid": True, + "remoteLabel": label, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.2"}], + }, + ] + } + elif label and rd: + expected = { + "102:1": { + "prefix": "192.0.3.2/32", + "paths": [ + { + "valid": True, + "remoteLabel": label, + "nexthops": [{"ip": "0.0.0.0"}], + } + ], + } + } + else: + expected = {} + + return topotest.json_cmp(output, expected, exact=(label is None)) + + +def check_mpls_table(label, protocol): + tgen = get_topogen() + + if label == "auto": + cmd = "show mpls table json" + else: + cmd = "show mpls table {} json".format(label) + + output = json.loads(tgen.gears["r2"].vtysh_cmd(cmd)) + + if label == "auto" and protocol: + output_copy = deepcopy(output) + for key, data in output_copy.items(): + for nexthop in data.get("nexthops", []): + if nexthop.get("type", None) != protocol: + continue + output = data + break + + if protocol: + expected = { + "nexthops": [ + { + "type": protocol, + }, + ] + } + else: + expected = {} + + return topotest.json_cmp(output, expected, exact=(protocol is None)) + + +def check_mpls_ldp_binding(): + tgen = get_topogen() + + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show mpls ldp binding 192.0.2.2/32 json") + ) + expected = { + "bindings": [ + { + "prefix": "192.0.2.2/32", + "localLabel": "16", # first available label + "inUse": 1, + }, + ] + } + + return topotest.json_cmp(output, expected) + + +def test_convergence(): + "Test protocol convergence" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Check BGP and LDP convergence") + test_func = functools.partial(check_bgp_vpn_prefix, 2222) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefix on R1" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, 2222, "BGP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: [2222/2222]" in output, "Failed to see BGP label chunk" + + +def test_vpn_label_export_16(): + "Test that assigning the label value of 16 is not possible because it used by LDP" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export 16" + ) + + step("Check that label vpn export 16 fails") + test_func = functools.partial(check_bgp_vpn_prefix, None) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP prefix on R1" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, 2222, None) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp" not in output, "Unexpected BGP label chunk" + + +def test_vpn_label_export_2222(): + "Test that setting back the label value of 2222 works" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export 2222" + ) + + step("Check that label vpn export 2222 is OK") + test_func = functools.partial(check_bgp_vpn_prefix, 2222) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefix on R1" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, "auto", "BGP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: [2222/2222]" in output, "Failed to see BGP label chunk" + + +def test_vpn_label_export_auto(): + "Test that setting label vpn export auto works" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export auto" + ) + + step("Check that label vpn export auto is OK") + test_func = functools.partial(check_bgp_vpn_prefix, "auto") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefix on R1" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, "auto", "BGP") + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Failed to see BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " in output, "Failed to see BGP label chunk" + + +def test_vpn_label_export_no_auto(): + "Test that UNsetting label vpn export auto removes the prefix from R1 table and R2 LDP table" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv4 unicast 192.0.3.2/32 json") + ) + + auto_label = output.get("paths")[0].get("remoteLabel", None) + assert auto_label is not None, "Failed to fetch prefix label on R1" + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "no label vpn export auto" + ) + + step("Check that no label vpn export auto is OK") + test_func = functools.partial(check_bgp_vpn_prefix, 3, rname="r2", rd="102:1") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP prefix on R2" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, auto_label, None) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Unexpected BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " not in output, "Unexpected BGP label chunk" + + +def test_vpn_label_export_auto_back(): + "Test that setting back label vpn export auto works" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + output = json.loads( + tgen.gears["r2"].vtysh_cmd("show bgp vrf vrf1 ipv4 unicast 192.0.3.2/32 json") + ) + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export auto" + ) + + step("Check that label vpn export auto is OK") + test_func = functools.partial(check_bgp_vpn_prefix, "auto") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefix on R1" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, "auto", "BGP") + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Failed to see BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " in output, "Failed to see BGP label chunk" + + +def test_vpn_label_export_manual_from_auto(): + "Test that setting a manual label value from the BGP chunk range works" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 ipv4 unicast 192.0.3.2/32 json") + ) + + auto_label = output.get("paths")[0].get("remoteLabel", None) + assert auto_label is not None, "Failed to fetch prefix label on R1" + + auto_label = auto_label + 1 + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export {}".format(auto_label) + ) + + step("Check that label vpn export {} is OK".format(auto_label)) + test_func = functools.partial( + check_bgp_vpn_prefix, auto_label, rname="r2", rd="102:1" + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefix on R2" + + test_func = functools.partial(check_mpls_ldp_binding) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP binding on R2" + + test_func = functools.partial(check_mpls_table, 16, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + test_func = functools.partial(check_mpls_table, auto_label, "BGP") + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Failed to see BGP label on R2" + + output = tgen.net["r2"].cmd("vtysh -c 'show debugging label-table' | grep Proto") + assert re.match( + r"Proto ldp: \[16/(1[7-9]|[2-9]\d+|\d{3,})\]", output + ), "Failed to see LDP label chunk" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " in output, "Failed to see BGP label chunk" + + +def test_vpn_label_configure_dynamic_range(): + "Test that if a dynamic range is configured, then the next dynamic allocations will be done in that block" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + tgen.gears["r2"].vtysh_cmd("conf\n" "mpls label dynamic-block 500 1000\n") + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export auto" + ) + step("Check that label vpn export auto starting at 500 is OK") + test_func = functools.partial(check_bgp_vpn_prefix, 500, rname="r2", rd="102:1") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP prefix on R2" + + test_func = functools.partial(check_mpls_table, 500, "BGP") + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Unexpected BGP label on R2" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " in output, "Failed to see BGP label chunk" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) + + +def test_vpn_label_restart_ldp(): + "Test that if a dynamic range is configured, then when LDP restarts, it follows the new dynamic range" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_list = tgen.routers() + + step("Kill LDP on R2") + kill_router_daemons(tgen, "r2", ["ldpd"]) + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto ldp: " not in output, "Unexpected LDP label chunk" + + step("Bring up LDP on R2") + + start_router_daemons(tgen, "r2", ["ldpd"]) + + test_func = functools.partial(check_mpls_table, 628, "LDP") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see LDP label on R2" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto ldp: [628/691]" in output, "Failed to see LDP label chunk [628/691]" + assert "Proto ldp: [692/755]" in output, "Failed to see LDP label chunk [692/755]" + + +def test_vpn_label_unconfigure_dynamic_range(): + "Test that if the dynamic range is unconfigured, then the next dynamic allocations will be done at the first free place." + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + tgen.gears["r2"].vtysh_cmd("conf\n" "no mpls label dynamic-block 500 1000\n") + step("Check that unconfiguring label vpn export auto will remove BGP label chunk") + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "no label vpn export auto" + ) + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " not in output, "Unexpected BGP label chunk" + + tgen.gears["r2"].vtysh_cmd( + "conf\n" + "router bgp 65002 vrf vrf1\n" + "address-family ipv4 unicast\n" + "label vpn export auto" + ) + step("Check that label vpn export auto starting at 16 is OK") + test_func = functools.partial(check_bgp_vpn_prefix, 16, rname="r2", rd="102:1") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected BGP prefix on R2" + + test_func = functools.partial(check_mpls_table, 16, "BGP") + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, "Unexpected BGP label on R2" + + output = tgen.gears["r2"].vtysh_cmd("show debugging label-table") + assert "Proto bgp: " in output, "Failed to see BGP label chunk" diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/__init__.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf new file mode 100644 index 0000000..8b88534 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/bgpd.conf @@ -0,0 +1,32 @@ +frr defaults traditional +! +hostname ce1 +password zebra +log stdout notifications +log commands +router bgp 5226 + no bgp network import-check + bgp router-id 99.0.0.1 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5226 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 5.1.0.0/24 route-map rm-nh + network 5.1.1.0/24 route-map rm-nh + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.1 + set local-preference 123 + set metric 98 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! + +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf new file mode 100644 index 0000000..46831bb --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce1/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce1 +! +interface lo + ip address 99.0.0.1/32 +! +interface ce1-eth0 + description to r1 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf new file mode 100644 index 0000000..2accaae --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/bgpd.conf @@ -0,0 +1,32 @@ +frr defaults traditional +! +hostname ce2 +password zebra +log stdout notifications +log commands +router bgp 5226 + no bgp network import-check + bgp router-id 99.0.0.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5226 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 5.1.0.0/24 route-map rm-nh + network 5.1.1.0/24 route-map rm-nh + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.2 + set local-preference 100 + set metric 100 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! + +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf new file mode 100644 index 0000000..fb4d8cc --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce2/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce2 +! +interface lo + ip address 99.0.0.2/32 +! +interface ce2-eth0 + description to r3 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf new file mode 100644 index 0000000..42eff1b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/bgpd.conf @@ -0,0 +1,32 @@ +frr defaults traditional +! +hostname ce3 +password zebra +log stdout notifications +log commands +router bgp 5226 + no bgp network import-check + bgp router-id 99.0.0.3 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5226 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 5.1.2.0/24 route-map rm-nh + network 5.1.3.0/24 route-map rm-nh + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.3 + set local-preference 50 + set metric 200 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! + +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf new file mode 100644 index 0000000..77a1163 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/ce3/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce3 +! +interface lo + ip address 99.0.0.3/32 +! +interface ce3-eth0 + description to r4 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py new file mode 100644 index 0000000..41114b6 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/customize.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +r""" +customize.py: Simple FRR MPLS L3VPN test topology + + | + +----+----+ + | ce1 | + | 99.0.0.1| CE Router + +----+----+ + 192.168.1. | .2 ce1-eth0 + | .1 r1-eth4 + +---------+ + | r1 | + | 1.1.1.1 | PE Router + +----+----+ + | .1 r1-eth0 + | + ~~~~~~~~~~~~~ + ~~ sw0 ~~ + ~~ 10.0.1.0/24 ~~ + ~~~~~~~~~~~~~ + |10.0.1.0/24 + | + | .2 r2-eth0 + +----+----+ + | r2 | + | 2.2.2.2 | P router + +--+---+--+ + r2-eth2 .2 | | .2 r2-eth1 + ______/ \______ + / \ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ +~~ sw2 ~~ ~~ sw1 ~~ +~~ 10.0.3.0/24 ~~ ~~ 10.0.2.0/24 ~~ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ + | / | + \ _________/ | + \ / \ +r3-eth1 .3 | | .3 r3-eth0 | .4 r4-eth0 + +----+--+---+ +----+----+ + | r3 | | r4 | + | 3.3.3.3 | | 4.4.4.4 | PE Routers + +-----------+ +---------+ + 192.168.1. | .1 192.168.1. | .1 rX-eth4 + | .2 | .2 ceX-eth0 + +-----+-----+ +----+-----+ + | ce2 | | ce3 | + | 99.0.0.2 | | 99.0.0.3 | CE Routers + +-----+-----+ +----+-----+ + | | + +""" + +import os + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import get_topogen +from lib.topolog import logger +from lib.ltemplate import ltemplateRtrCmd + +# Required to instantiate the topology builder class. + + +CWD = os.path.dirname(os.path.realpath(__file__)) +# test name based on directory +TEST = os.path.basename(CWD) + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # Create P/PE routers + tgen.add_router("r1") + # check for mpls + if tgen.hasmpls != True: + logger.info("MPLS not available, tests will be skipped") + return + for routern in range(2, 5): + tgen.add_router("r{}".format(routern)) + # Create CE routers + for routern in range(1, 4): + tgen.add_router("ce{}".format(routern)) + + # CE/PE links + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "ce1-eth0", "r1-eth4") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r3"], "ce2-eth0", "r3-eth4") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r4"], "ce3-eth0", "r4-eth4") + + # Create a switch with just one router connected to it to simulate a + # empty network. + switch = {} + switch[0] = tgen.add_switch("sw0") + switch[0].add_link(tgen.gears["r1"], nodeif="r1-eth0") + switch[0].add_link(tgen.gears["r2"], nodeif="r2-eth0") + + switch[1] = tgen.add_switch("sw1") + switch[1].add_link(tgen.gears["r2"], nodeif="r2-eth1") + switch[1].add_link(tgen.gears["r3"], nodeif="r3-eth0") + switch[1].add_link(tgen.gears["r4"], nodeif="r4-eth0") + + switch[1] = tgen.add_switch("sw2") + switch[1].add_link(tgen.gears["r2"], nodeif="r2-eth2") + switch[1].add_link(tgen.gears["r3"], nodeif="r3-eth1") + + +def ltemplatePreRouterStartHook(): + cc = ltemplateRtrCmd() + tgen = get_topogen() + logger.info("pre router-start hook") + # check for mpls + if tgen.hasmpls != True: + logger.info("MPLS not available, skipping setup") + return False + logger.info("setup mpls input") + return True + + +def ltemplatePostRouterStartHook(): + logger.info("post router-start hook") + return True diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf new file mode 100644 index 0000000..a564da9 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/bgpd.conf @@ -0,0 +1,42 @@ +frr defaults traditional +! +hostname r1 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 1.1.1.1 + bgp cluster-id 1.1.1.1 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as 5226 + neighbor 192.168.1.2 update-source 192.168.1.1 + neighbor 192.168.1.2 route-reflector-client + neighbor 192.168.1.2 timers 3 10 + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 1.1.1.1 + neighbor 2.2.2.2 timers 3 10 +! + address-family ipv4 unicast + redistribute vnc-direct + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + no neighbor 2.2.2.2 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + vrf-policy cust1 + label 101 + rd 10:1 + rt both 52:100 + nexthop 192.168.1.1 + exit-vrf-policy +! + vnc export bgp mode group-nve + vnc export bgp group-nve group cust1 + vnc redistribute mode resolve-nve + vnc redistribute ipv4 bgp-direct + ! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf new file mode 100644 index 0000000..f7f2714 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ldpd.conf @@ -0,0 +1,23 @@ +hostname r1 +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 1.1.1.1 + ! + address-family ipv4 + discovery transport-address 1.1.1.1 + ! + interface r1-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf new file mode 100644 index 0000000..460a8fb --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/ospfd.conf @@ -0,0 +1,12 @@ +hostname r1 +log file ospfd.log +! +router ospf + router-id 1.1.1.1 + network 0.0.0.0/4 area 0 + redistribute static +! +int r1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf new file mode 100644 index 0000000..767e17e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r1/zebra.conf @@ -0,0 +1,27 @@ +log file zebra.log +! +hostname r1 +! +interface lo + mpls + ip address 1.1.1.1/32 +! +interface r1-eth0 + description to sw0 + mpls + ip address 10.0.1.1/24 + no link-detect +! +interface r1-eth4 + description to ce1 + mpls + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.1/32 192.168.1.2 +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf new file mode 100644 index 0000000..3167306 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/bgpd.conf @@ -0,0 +1,33 @@ +frr defaults traditional +! +hostname r2 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 2.2.2.2 + bgp cluster-id 2.2.2.2 + no bgp ebgp-requires-policy + neighbor 1.1.1.1 remote-as 5226 + neighbor 1.1.1.1 update-source 2.2.2.2 + neighbor 1.1.1.1 timers 3 10 + neighbor 3.3.3.3 remote-as 5226 + neighbor 3.3.3.3 update-source 2.2.2.2 + neighbor 3.3.3.3 timers 3 10 + neighbor 4.4.4.4 remote-as 5226 + neighbor 4.4.4.4 update-source 2.2.2.2 + neighbor 4.4.4.4 timers 3 10 + address-family ipv4 unicast + no neighbor 1.1.1.1 activate + no neighbor 3.3.3.3 activate + no neighbor 4.4.4.4 activate + exit-address-family + address-family ipv4 vpn + neighbor 1.1.1.1 activate + neighbor 1.1.1.1 route-reflector-client + neighbor 3.3.3.3 activate + neighbor 3.3.3.3 route-reflector-client + neighbor 4.4.4.4 activate + neighbor 4.4.4.4 route-reflector-client + exit-address-family +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf new file mode 100644 index 0000000..c4056e0 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ldpd.conf @@ -0,0 +1,25 @@ +hostname r2 +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 2.2.2.2 + ! + address-family ipv4 + discovery transport-address 2.2.2.2 + ! + interface r2-eth0 + ! + interface r2-eth1 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf new file mode 100644 index 0000000..dbed618 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/ospfd.conf @@ -0,0 +1,19 @@ +hostname r2 +log file ospfd.log +! +router ospf + router-id 2.2.2.2 + network 0.0.0.0/0 area 0 +! +int r2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf new file mode 100644 index 0000000..829ac96 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r2/zebra.conf @@ -0,0 +1,31 @@ +log file zebra.log +! +hostname r2 +! +interface lo + mpls + ip address 2.2.2.2/32 +! +interface r2-eth0 + description to sw0 + mpls + ip address 10.0.1.2/24 + no link-detect +! +interface r2-eth1 + description to sw1 + mpls + ip address 10.0.2.2/24 + no link-detect +! +interface r2-eth2 + description to sw2 + mpls + ip address 10.0.3.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf new file mode 100644 index 0000000..445da08 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/bgpd.conf @@ -0,0 +1,41 @@ +frr defaults traditional +! +hostname r3 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 3.3.3.3 + bgp cluster-id 3.3.3.3 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as 5226 + neighbor 192.168.1.2 update-source 192.168.1.2 + neighbor 192.168.1.2 route-reflector-client + neighbor 192.168.1.2 timers 3 10 + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 3.3.3.3 + neighbor 2.2.2.2 timers 3 10 +! + address-family ipv4 unicast + redistribute vnc-direct + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + no neighbor 2.2.2.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + vrf-policy cust1 + label 103 + rd 10:3 + rt both 52:100 + nexthop 192.168.1.1 + exit-vrf-policy +! + vnc export bgp mode group-nve + vnc export bgp group-nve group cust1 + vnc redistribute mode resolve-nve + vnc redistribute ipv4 bgp-direct +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf new file mode 100644 index 0000000..48956cb --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ldpd.conf @@ -0,0 +1,23 @@ +hostname r3 +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 3.3.3.3 + ! + address-family ipv4 + discovery transport-address 3.3.3.3 + ! + interface r3-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf new file mode 100644 index 0000000..0e64ed6 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/ospfd.conf @@ -0,0 +1,17 @@ +hostname r3 +password 1 +log file ospfd.log +! +router ospf + router-id 3.3.3.3 + network 0.0.0.0/4 area 0 + redistribute static +! +int r3-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r3-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf new file mode 100644 index 0000000..916dabf --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r3/zebra.conf @@ -0,0 +1,32 @@ +log file zebra.log +! +hostname r3 +! +interface lo + mpls + ip address 3.3.3.3/32 +! +interface r3-eth0 + description to sw1 + mpls + ip address 10.0.2.3/24 + no link-detect +! +interface r3-eth1 + description to sw2 + ip address 10.0.3.3/24 + no link-detect +! +interface r3-eth4 + description to ce2 + mpls + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.2/32 192.168.1.2 +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf new file mode 100644 index 0000000..1941352 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/bgpd.conf @@ -0,0 +1,41 @@ +frr defaults traditional +! +hostname r4 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 4.4.4.4 + bgp cluster-id 4.4.4.4 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as 5226 + neighbor 192.168.1.2 update-source 192.168.1.1 + neighbor 192.168.1.2 route-reflector-client + neighbor 192.168.1.2 timers 3 10 + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 4.4.4.4 + neighbor 2.2.2.2 timers 3 10 +! + address-family ipv4 unicast + redistribute vnc-direct + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + no neighbor 2.2.2.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + vrf-policy cust1 + label 104 + rd 10:4 + rt both 52:100 + nexthop 192.168.1.1 + exit-vrf-policy +! + vnc export bgp mode group-nve + vnc export bgp group-nve group cust1 + vnc redistribute mode resolve-nve + vnc redistribute ipv4 bgp-direct +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf new file mode 100644 index 0000000..1d04aa0 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ldpd.conf @@ -0,0 +1,23 @@ +hostname r4 +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 4.4.4.4 + ! + address-family ipv4 + discovery transport-address 4.4.4.4 + ! + interface r4-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf new file mode 100644 index 0000000..89e37df --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/ospfd.conf @@ -0,0 +1,12 @@ +hostname r4 +log file ospfd.log +! +router ospf + router-id 4.4.4.4 + network 0.0.0.0/4 area 0 + redistribute static +! +int r4-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf new file mode 100644 index 0000000..e08ac86 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/r4/zebra.conf @@ -0,0 +1,26 @@ +log file zebra.log +! +hostname r4 +! +interface lo + mpls + ip address 4.4.4.4/32 +! +interface r4-eth0 + description to sw1 + mpls + ip address 10.0.2.4/24 + no link-detect +! +interface r4-eth4 + description to ce3 + mpls + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.3/32 192.168.1.2 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py new file mode 100644 index 0000000..489c59f --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py @@ -0,0 +1,193 @@ +from lib.lutil import luCommand + +luCommand( + "r1", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" +) +luCommand( + "r3", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" +) +luCommand( + "r4", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" +) +luCommand("r1", 'vtysh -c "show bgp ipv4 uni"', "i 5.*i 5", "wait", "See CE routes") +luCommand("r3", 'vtysh -c "show bgp ipv4 uni"', "i 5.*i 5", "wait", "See CE routes") +luCommand("r4", 'vtysh -c "show bgp ipv4 uni"', "i 5.*i 5", "wait", "See CE routes") +luCommand("ce1", 'vtysh -c "show bgp ipv4 uni 5.1.0.0/24"', "", "none", "See CE routes") +luCommand("r1", 'vtysh -c "show bgp ipv4 uni 5.1.0.0/24"', "", "none", "See CE routes") +luCommand("ce2", 'vtysh -c "show bgp ipv4 uni 5.1.0.0/24"', "", "none", "See CE routes") +luCommand("r3", 'vtysh -c "show bgp ipv4 uni 5.1.0.0/24"', "", "none", "See CE routes") +luCommand("ce3", 'vtysh -c "show bgp ipv4 uni 5.1.2.0/24"', "", "none", "See CE routes") +luCommand("r4", 'vtysh -c "show bgp ipv4 uni 5.1.2.0/24"', "", "none", "See CE routes") + +luCommand( + "r1", 'vtysh -c "add vrf cust1 prefix 99.0.0.1/32"', ".", "none", "IP Address" +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "99.0.0.1", + "wait", + "Local Registration", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations imported"', + "2 out of 2 imported", + "wait", + "Imported Registrations", +) +luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.1/32", + "wait", + "See R1s static address", +) +luCommand( + "r4", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.1/32", + "wait", + "See R1s static address", +) +luCommand( + "r3", 'vtysh -c "show bgp ipv4 vpn rd 10:1"', "i 5.*i 5", "wait", "See R1s imports" +) +luCommand( + "r4", 'vtysh -c "show bgp ipv4 vpn rd 10:1"', "i 5.*i 5", "wait", "See R1s imports" +) + +luCommand( + "r3", 'vtysh -c "add vrf cust1 prefix 99.0.0.2/32"', ".", "none", "IP Address" +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "99.0.0.2", + "wait", + "Local Registration", +) +have2ndImports = luCommand( + "r3", + 'vtysh -c "show vnc registrations imported"', + "2 out of 2 imported", + "none", + "Imported Registrations", + 2, +) +if have2ndImports: + luCommand( + "r3", + 'vtysh -c "show vnc registrations imported"', + "2 out of 2 imported", + "pass", + "Imported Registrations", + ) +luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.2/32", + "wait", + "See R3s static address", +) +luCommand( + "r4", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.2/32", + "wait", + "See R3s static address", +) +if have2ndImports: + luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn rd 10:3"', + "i 5.*i 5", + "none", + "See R3s imports", + ) + luCommand( + "r4", + 'vtysh -c "show bgp ipv4 vpn rd 10:3"', + "i 5.*i 5", + "none", + "See R3s imports", + ) + +luCommand( + "r4", 'vtysh -c "add vrf cust1 prefix 99.0.0.3/32"', ".", "none", "IP Address" +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "99.0.0.3", + "wait", + "Local Registration", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations imported"', + "2 out of 2 imported", + "wait", + "Imported Registrations", +) +luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.3/32", + "wait", + "See R4s static address", +) +luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn"', + "i 99.0.0.3/32", + "wait", + "See R4s static address", +) +luCommand( + "r1", 'vtysh -c "show bgp ipv4 vpn rd 10:4"', "i 5.*i 5", "wait", "See R4s imports" +) +luCommand( + "r3", 'vtysh -c "show bgp ipv4 vpn rd 10:4"', "i 5.*i 5", "wait", "See R4s imports" +) + + +luCommand( + "r1", + 'vtysh -c "show vnc registrations remote"', + "5.1.2.0/24 .*5.1.3.0/24", + "wait", + "R4s registrations", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations remote"', + "5.1.2.0/24 .*5.1.3.0/24", + "wait", + "R4s registrations", +) +if have2ndImports: + luCommand( + "r1", + 'vtysh -c "show vnc registrations remote"', + "5.1.0.0/24 .*5.1.1.0/24", + "wait", + "Remote registrations", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations remote"', + "5.1.0.0/24 .*5.1.1.0/24", + "wait", + "Remote registrations", + ) +luCommand( + "r4", + 'vtysh -c "show vnc registrations remote"', + "5.1.0.0/24 .*5.1.1.0/24", + "wait", + "Remote registrations", +) +luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py new file mode 100644 index 0000000..c966660 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/adjacencies.py @@ -0,0 +1,89 @@ +from lib.lutil import luCommand + +luCommand("ce1", "ping 192.168.1.1 -c 1", " 0. packet loss", "pass", "CE->PE ping") +luCommand("ce2", "ping 192.168.1.1 -c 1", " 0. packet loss", "pass", "CE->PE ping") +luCommand("ce3", "ping 192.168.1.1 -c 1", " 0. packet loss", "pass", "CE->PE ping") +luCommand("ce1", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand("ce2", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand("ce3", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand( + "r1", + 'vtysh -c "show ip route ospf"', + "2.2.2.2", + "wait", + "OSPF Route has Arrived", + 60, +) +luCommand( + "r1", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r3", + 'vtysh -c "show ip route ospf"', + "2.2.2.2", + "wait", + "OSPF Route has Arrived", + 60, +) +luCommand( + "r3", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) + +luCommand( + "r4", + 'vtysh -c "show ip route ospf"', + "2.2.2.2", + "wait", + "OSPF Route has Arrived", + 60, +) +luCommand( + "r4", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r2", + 'vtysh -c "show bgp summary"', + " 00:0.* 00:0.* 00:0", + "wait", + "Core adjacencies up", + 180, +) +luCommand( + "r1", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r3", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r4", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r1", "ping 3.3.3.3 -c 1", " 0. packet loss", "wait", "PE->PE3 (loopback) ping" +) +luCommand( + "r1", "ping 4.4.4.4 -c 1", " 0. packet loss", "wait", "PE->PE4 (loopback) ping" +) +luCommand( + "r4", "ping 3.3.3.3 -c 1", " 0. packet loss", "wait", "PE->PE3 (loopback) ping" +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py new file mode 100644 index 0000000..af39a95 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/check_routes.py @@ -0,0 +1,55 @@ +from lib.lutil import luCommand + +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni"', + "7 routes and 7", + "wait", + "Local and remote routes", +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni"', + "7 routes and 9", + "wait", + "Local and remote routes", +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni"', + "7 routes and 7", + "wait", + "Local and remote routes", +) +luCommand( + "r1", 'vtysh -c "show bgp ipv4 uni"', "7 routes and 9", "pass", "Unicast SAFI" +) +luCommand( + "r2", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Unicast SAFI", +) +luCommand( + "r3", 'vtysh -c "show bgp ipv4 uni"', "7 routes and 9", "pass", "Unicast SAFI" +) +luCommand( + "r4", 'vtysh -c "show bgp ipv4 uni"', "7 routes and 9", "pass", "Unicast SAFI" +) +have2ndImports = luCommand( + "r3", + 'vtysh -c "show vnc registrations imported"', + "2 out of 2 imported", + "none", + "Imported Registrations", + 2, +) +if have2ndImports: + num = "9 routes and 9" +else: + num = "7 routes and 7" +luCommand("r1", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI") +luCommand("r2", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI") +luCommand("r3", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI") +luCommand("r4", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI") diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py new file mode 100644 index 0000000..38eac14 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/cleanup_all.py @@ -0,0 +1,114 @@ +from lib.lutil import luCommand + +luCommand( + "r1", + 'vtysh -c "clear vrf cust1 prefix 99.0.0.1/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r3", + 'vtysh -c "clear vrf cust1 prefix 99.0.0.2/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r4", + 'vtysh -c "clear vrf cust1 prefix 99.0.0.3/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "99.0.0.1", + "fail", + "Local Registration cleared", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "99.0.0.2", + "fail", + "Local Registration cleared", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "99.0.0.3", + "fail", + "Local Registration cleared", +) +luCommand( + "r1", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", +) +luCommand( + "r2", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Unicast SAFI", +) +luCommand( + "r3", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", +) +luCommand( + "r4", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", +) +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py new file mode 100755 index 0000000..7930f09 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/test_bgp_l3vpn_to_bgp_direct.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import sys +import pytest + +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) + +from lib.ltemplate import * + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def test_adjacencies(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/adjacencies.py", False, CliOnFail, CheckFunc) + + +def test_add_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/add_routes.py", False, CliOnFail, CheckFunc) + + +def test_check_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/check_routes.py", False, CliOnFail, CheckFunc) + + +def test_cleanup_all(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/cleanup_all.py", False, CliOnFail, CheckFunc) + + +if __name__ == "__main__": + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/__init__.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf new file mode 100644 index 0000000..ae57431 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/bgpd.conf @@ -0,0 +1,55 @@ +frr defaults traditional +! +hostname ce1 +password zebra +log stdout notifications +log commands +log file bgpd.log + +router bgp 5227 + no bgp network import-check + bgp router-id 99.0.0.1 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5227 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 99.0.0.1/32 + network 5.1.0.0/24 route-map rm-nh + network 5.1.1.0/24 route-map rm-nh + redistribute sharp route-map sharp-nh + network 6.0.1.0/24 route-map rm-nh + network 6.0.2.0/24 route-map rm-nh-same + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.1 + set local-preference 123 + set metric 98 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! +route-map sharp-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.1 + set local-preference 200 + set metric 200 + set large-community 90:12:34 + set extcommunity rt 80:987 + set community 0:65 +! +route-map rm-nh-same permit 10 + match ip address al-any + set ip next-hop 99.0.0.1 + set local-preference 100 + set metric 100 + set large-community 12:34:11 + set extcommunity rt 89:123 + set community 0:67 +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/sharpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/sharpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf new file mode 100644 index 0000000..46831bb --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce1/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce1 +! +interface lo + ip address 99.0.0.1/32 +! +interface ce1-eth0 + description to r1 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf new file mode 100644 index 0000000..599e2dd --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/bgpd.conf @@ -0,0 +1,55 @@ +frr defaults traditional +! +hostname ce2 +password zebra +log stdout notifications +log commands +log file bgpd.log + +router bgp 5227 + no bgp network import-check + bgp router-id 99.0.0.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5227 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 99.0.0.2/32 + network 5.1.0.0/24 route-map rm-nh + network 5.1.1.0/24 route-map rm-nh + redistribute sharp route-map sharp-nh + network 6.0.1.0/24 route-map rm-nh + network 6.0.2.0/24 route-map rm-nh-same + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.2 + set local-preference 100 + set metric 100 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! +route-map sharp-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.2 + set local-preference 200 + set metric 200 + set large-community 78:90:12 + set extcommunity rt 70:456 + set community 0:66 +! +route-map rm-nh-same permit 10 + match ip address al-any + set ip next-hop 99.0.0.2 + set local-preference 100 + set metric 100 + set large-community 12:34:12 + set extcommunity rt 89:123 + set community 0:67 +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/sharpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/sharpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf new file mode 100644 index 0000000..fb4d8cc --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce2/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce2 +! +interface lo + ip address 99.0.0.2/32 +! +interface ce2-eth0 + description to r3 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf new file mode 100644 index 0000000..e316de5 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/bgpd.conf @@ -0,0 +1,45 @@ +frr defaults traditional +! +hostname ce3 +password zebra +log stdout notifications +log commands +log file bgpd.log + +router bgp 5227 + no bgp network import-check + bgp router-id 99.0.0.3 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 5227 + neighbor 192.168.1.1 update-source 192.168.1.2 + neighbor 192.168.1.1 timers 3 10 + address-family ipv4 unicast + network 99.0.0.3/32 + network 5.1.2.0/24 route-map rm-nh + network 5.1.3.0/24 route-map rm-nh + network 6.0.1.0/24 route-map rm-nh + network 6.0.2.0/24 route-map rm-nh-same + neighbor 192.168.1.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.3 + set local-preference 50 + set metric 200 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! +route-map rm-nh-same permit 10 + match ip address al-any + set ip next-hop 99.0.0.3 + set local-preference 100 + set metric 100 + set large-community 12:34:13 + set extcommunity rt 89:123 + set community 0:67 +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf new file mode 100644 index 0000000..77a1163 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce3/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce3 +! +interface lo + ip address 99.0.0.3/32 +! +interface ce3-eth0 + description to r4 + ip address 192.168.1.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf new file mode 100644 index 0000000..60d9e93 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/bgpd.conf @@ -0,0 +1,45 @@ +frr defaults traditional +! +hostname ce4 +password zebra +log stdout notifications +log commands +log file bgpd.log + +router bgp 5228 vrf ce4-cust2 + no bgp network import-check + bgp router-id 99.0.0.4 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as 5228 + neighbor 192.168.2.1 update-source 192.168.2.2 + neighbor 192.168.2.1 timers 3 10 + address-family ipv4 unicast + network 99.0.0.4/32 + network 5.4.2.0/24 route-map rm-nh + network 5.4.3.0/24 route-map rm-nh + network 6.0.1.0/24 route-map rm-nh + network 6.0.2.0/24 route-map rm-nh-same + neighbor 192.168.2.1 activate + exit-address-family +! +access-list al-any permit any +! +route-map rm-nh permit 10 + match ip address al-any + set ip next-hop 99.0.0.4 + set local-preference 50 + set metric 200 + set large-community 12:34:56 + set extcommunity rt 89:123 + set community 0:67 +! +route-map rm-nh-same permit 10 + match ip address al-any + set ip next-hop 99.0.0.4 + set local-preference 100 + set metric 100 + set large-community 12:34:14 + set extcommunity rt 89:123 + set community 0:67 +! +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf new file mode 100644 index 0000000..e55c9e7 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/ce4/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname ce4 +! +interface ce4-cust2 + ip address 99.0.0.4/32 +! +interface ce4-eth0 + description to r4 + ip address 192.168.2.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py new file mode 100644 index 0000000..45c44ba --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +r""" +customize.py: Simple FRR MPLS L3VPN test topology + + | + +----+----+ + | ce1 | + | 99.0.0.1| CE Router + +----+----+ + 192.168.1. | .2 ce1-eth0 + | .1 r1-eth4 + +---------+ + | r1 | + | 1.1.1.1 | PE Router + +----+----+ + | .1 r1-eth0 + | + ~~~~~~~~~~~~~ + ~~ sw0 ~~ + ~~ 10.0.1.0/24 ~~ + ~~~~~~~~~~~~~ + |10.0.1.0/24 + | + | .2 r2-eth0 + +----+----+ + | r2 | + | 2.2.2.2 | P router + +--+---+--+ + r2-eth2 .2 | | .2 r2-eth1 + ______/ \______ + / \ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ +~~ sw2 ~~ ~~ sw1 ~~ +~~ 10.0.3.0/24 ~~ ~~ 10.0.2.0/24 ~~ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ + | / | + \ _________/ | + \ / \ +r3-eth1 .3 | | .3 r3-eth0 | .4 r4-eth0 + +----+--+---+ +----+----+ + | r3 | | r4 | r4-eth5 + | 3.3.3.3 | | 4.4.4.4 |-------+ PE Routers + +-----------+ +---------+ | +192.168.1.1 |r3.eth4 192.168.1.1 | r4-eth4 |192.168.2.1 + .2 | ceX-eth0 .2 | | .2 + +-----+-----+ +----+-----+ +----+-----+ + | ce2 | | ce3 | | ce4 | + | 99.0.0.2 | | 99.0.0.3 | | 99.0.0.4 | CE Routers + +-----+-----+ +----+-----+ +----+-----+ + | | | + +""" + +import os +import platform + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import get_topogen +from lib.topolog import logger +from lib.ltemplate import ltemplateRtrCmd + +# Required to instantiate the topology builder class. + + +CWD = os.path.dirname(os.path.realpath(__file__)) +# test name based on directory +TEST = os.path.basename(CWD) + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # Create P/PE routers + # check for mpls + tgen.add_router("r1") + if tgen.hasmpls != True: + logger.info("MPLS not available, tests will be skipped") + return + mach = platform.machine() + krel = platform.release() + if mach[:1] == "a" and topotest.version_cmp(krel, "4.11") < 0: + logger.info("Need Kernel version 4.11 to run on arm processor") + return + for routern in range(2, 5): + tgen.add_router("r{}".format(routern)) + # Create CE routers + for routern in range(1, 5): + tgen.add_router("ce{}".format(routern)) + + # CE/PE links + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "ce1-eth0", "r1-eth4") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r3"], "ce2-eth0", "r3-eth4") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r4"], "ce3-eth0", "r4-eth4") + tgen.add_link(tgen.gears["ce4"], tgen.gears["r4"], "ce4-eth0", "r4-eth5") + + # Create a switch with just one router connected to it to simulate a + # empty network. + switch = {} + switch[0] = tgen.add_switch("sw0") + switch[0].add_link(tgen.gears["r1"], nodeif="r1-eth0") + switch[0].add_link(tgen.gears["r2"], nodeif="r2-eth0") + + switch[1] = tgen.add_switch("sw1") + switch[1].add_link(tgen.gears["r2"], nodeif="r2-eth1") + switch[1].add_link(tgen.gears["r3"], nodeif="r3-eth0") + switch[1].add_link(tgen.gears["r4"], nodeif="r4-eth0") + + switch[1] = tgen.add_switch("sw2") + switch[1].add_link(tgen.gears["r2"], nodeif="r2-eth2") + switch[1].add_link(tgen.gears["r3"], nodeif="r3-eth1") + + +def ltemplatePreRouterStartHook(): + cc = ltemplateRtrCmd() + krel = platform.release() + tgen = get_topogen() + logger.info("pre router-start hook, kernel=" + krel) + + # check for mpls + if tgen.hasmpls != True: + logger.info("MPLS not available, skipping setup") + return False + # trace errors/unexpected output + cc.resetCounts() + # configure r2 mpls interfaces + intfs = ["lo", "r2-eth0", "r2-eth1", "r2-eth2"] + for intf in intfs: + cc.doCmd(tgen, "r2", "echo 1 > /proc/sys/net/mpls/conf/{}/input".format(intf)) + + # configure cust1 VRFs & MPLS + rtrs = ["r1", "r3", "r4"] + cmds = [ + "ip link add {0}-cust1 type vrf table 10", + "ip ru add oif {0}-cust1 table 10", + "ip ru add iif {0}-cust1 table 10", + "ip link set dev {0}-cust1 up", + ] + for rtr in rtrs: + for cmd in cmds: + cc.doCmd(tgen, rtr, cmd.format(rtr)) + cc.doCmd(tgen, rtr, "ip link set dev {0}-eth4 master {0}-cust1".format(rtr)) + intfs = [rtr + "-cust1", "lo", rtr + "-eth0", rtr + "-eth4"] + for intf in intfs: + cc.doCmd( + tgen, rtr, "echo 1 > /proc/sys/net/mpls/conf/{}/input".format(intf) + ) + logger.info( + "setup {0} vrf {0}-cust1, {0}-eth4. enabled mpls input.".format(rtr) + ) + # configure cust4 VRFs & MPLS + cmds = [ + "ip link add {0}-cust4 type vrf table 30", + "ip link set dev {0}-cust4 up", + "ip link add {0}-cust5 type vrf table 40", + "ip link set dev {0}-cust5 up", + ] + rtr = "r1" + for cmd in cmds: + cc.doCmd(tgen, rtr, cmd.format(rtr)) + logger.info("setup {0} vrf {0}-cust3 and{0}-cust4.".format(rtr)) + # configure cust2 VRFs & MPLS + rtrs = ["r4"] + cmds = [ + "ip link add {0}-cust2 type vrf table 20", + "ip ru add oif {0}-cust2 table 20", + "ip ru add iif {0}-cust2 table 20", + "ip link set dev {0}-cust2 up", + ] + for rtr in rtrs: + for cmd in cmds: + cc.doCmd(tgen, rtr, cmd.format(rtr)) + cc.doCmd(tgen, rtr, "ip link set dev {0}-eth5 master {0}-cust2".format(rtr)) + intfs = [rtr + "-cust2", rtr + "-eth5"] + for intf in intfs: + cc.doCmd( + tgen, rtr, "echo 1 > /proc/sys/net/mpls/conf/{}/input".format(intf) + ) + logger.info( + "setup {0} vrf {0}-cust2, {0}-eth5. enabled mpls input.".format(rtr) + ) + # put ce4-eth0 into a VRF (no default instance!) + rtrs = ["ce4"] + cmds = [ + "ip link add {0}-cust2 type vrf table 20", + "ip ru add oif {0}-cust2 table 20", + "ip ru add iif {0}-cust2 table 20", + "ip link set dev {0}-cust2 up", + ] + for rtr in rtrs: + for cmd in cmds: + cc.doCmd(tgen, rtr, cmd.format(rtr)) + cc.doCmd(tgen, rtr, "ip link set dev {0}-eth0 master {0}-cust2".format(rtr)) + if cc.getOutput() != 0: + InitSuccess = False + logger.info( + "Unexpected output seen ({} times, tests will be skipped".format( + cc.getOutput() + ) + ) + else: + rtrs = ["r1", "r3", "r4", "ce4"] + for rtr in rtrs: + logger.info("{} configured".format(rtr)) + cc.doCmd(tgen, rtr, "ip -d link show type vrf") + cc.doCmd(tgen, rtr, "ip link show") + InitSuccess = True + logger.info("VRF config successful!") + return InitSuccess + + +def ltemplatePostRouterStartHook(): + logger.info("post router-start hook") + return True diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf new file mode 100644 index 0000000..0d652da --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf @@ -0,0 +1,80 @@ +frr defaults traditional + +hostname r1 +password zebra +log stdout notifications +log commands + +log file bgpd.log + +#debug bgp vpn leak-to-vrf +#debug bgp vpn leak-from-vrf +#debug bgp vpn label +#debug bgp updates out + +router bgp 5226 + bgp router-id 1.1.1.1 + bgp cluster-id 1.1.1.1 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 1.1.1.1 + neighbor 2.2.2.2 timers 3 10 + + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family + + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family + + +router bgp 5227 vrf r1-cust1 + + bgp router-id 192.168.1.1 + no bgp ebgp-requires-policy + + neighbor 192.168.1.2 remote-as 5227 + neighbor 192.168.1.2 update-source 192.168.1.1 + neighbor 192.168.1.2 timers 3 10 + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + + label vpn export 101 + rd vpn export 10:1 + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family + +router bgp 5227 vrf r1-cust4 + no bgp network import-check + + bgp router-id 192.168.1.1 + + address-family ipv4 unicast + network 172.16.0.0/24 + + rd vpn export 10:14 + rt vpn export 52:100 + + import vpn + export vpn + exit-address-family + +router bgp 5227 vrf r1-cust5 + bgp router-id 192.168.1.1 + + address-family ipv4 unicast + network 172.16.1.1/32 + + label vpn export 105 + rd vpn export 10:15 + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf new file mode 100644 index 0000000..168b2d4 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ldpd.conf @@ -0,0 +1,24 @@ +hostname r1 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 1.1.1.1 + ! + address-family ipv4 + discovery transport-address 1.1.1.1 + ! + interface r1-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf new file mode 100644 index 0000000..460a8fb --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/ospfd.conf @@ -0,0 +1,12 @@ +hostname r1 +log file ospfd.log +! +router ospf + router-id 1.1.1.1 + network 0.0.0.0/4 area 0 + redistribute static +! +int r1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf new file mode 100644 index 0000000..9a5b0a6 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log + +hostname r1 +password zebra + +#debug zebra packet + +interface lo + ip address 1.1.1.1/32 + +interface r1-eth0 + description to sw0 + ip address 10.0.1.1/24 + no link-detect + +interface r1-eth4 + description to ce1 + ip address 192.168.1.1/24 + no link-detect + +interface r1-cust5 + ip address 172.16.1.1/32 + no link-detect + +ip forwarding + + +line vty + diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf new file mode 100644 index 0000000..edb3b69 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/bgpd.conf @@ -0,0 +1,35 @@ +frr defaults traditional + +hostname r2 +password zebra +log stdout notifications +log commands +log file bgpd.log + +router bgp 5226 + bgp router-id 2.2.2.2 + bgp cluster-id 2.2.2.2 + no bgp ebgp-requires-policy + neighbor 1.1.1.1 remote-as 5226 + neighbor 1.1.1.1 update-source 2.2.2.2 + neighbor 1.1.1.1 timers 3 10 + neighbor 3.3.3.3 remote-as 5226 + neighbor 3.3.3.3 update-source 2.2.2.2 + neighbor 3.3.3.3 timers 3 10 + neighbor 4.4.4.4 remote-as 5226 + neighbor 4.4.4.4 update-source 2.2.2.2 + neighbor 4.4.4.4 timers 3 10 + address-family ipv4 unicast + no neighbor 1.1.1.1 activate + no neighbor 3.3.3.3 activate + no neighbor 4.4.4.4 activate + exit-address-family + address-family ipv4 vpn + neighbor 1.1.1.1 activate + neighbor 1.1.1.1 route-reflector-client + neighbor 3.3.3.3 activate + neighbor 3.3.3.3 route-reflector-client + neighbor 4.4.4.4 activate + neighbor 4.4.4.4 route-reflector-client + exit-address-family +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf new file mode 100644 index 0000000..847be20 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ldpd.conf @@ -0,0 +1,26 @@ +hostname r2 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 2.2.2.2 + ! + address-family ipv4 + discovery transport-address 2.2.2.2 + ! + interface r2-eth0 + ! + interface r2-eth1 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf new file mode 100644 index 0000000..dbed618 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/ospfd.conf @@ -0,0 +1,19 @@ +hostname r2 +log file ospfd.log +! +router ospf + router-id 2.2.2.2 + network 0.0.0.0/0 area 0 +! +int r2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf new file mode 100644 index 0000000..dc4ef7e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r2/zebra.conf @@ -0,0 +1,28 @@ +log file zebra.log + +hostname r2 +password zebra +! +interface lo + ip address 2.2.2.2/32 +! +interface r2-eth0 + description to sw0 + ip address 10.0.1.2/24 + no link-detect +! +interface r2-eth1 + description to sw1 + ip address 10.0.2.2/24 + no link-detect +! +interface r2-eth2 + description to sw2 + ip address 10.0.3.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf new file mode 100644 index 0000000..6c9640e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/bgpd.conf @@ -0,0 +1,48 @@ +frr defaults traditional + +hostname r3 +password zebra +log stdout notifications +log commands +log file bgpd.log + +#debug bgp vpn label +router bgp 5226 + bgp router-id 3.3.3.3 + bgp cluster-id 3.3.3.3 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 3.3.3.3 + neighbor 2.2.2.2 timers 3 10 + + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family + + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family + +router bgp 5227 vrf r3-cust1 + + bgp router-id 192.168.1.1 + no bgp ebgp-requires-policy + + neighbor 192.168.1.2 remote-as 5227 + neighbor 192.168.1.2 update-source 192.168.1.1 + neighbor 192.168.1.2 timers 3 10 + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + + label vpn export 103 + rd vpn export 10:3 + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family + + +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf new file mode 100644 index 0000000..a620739 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ldpd.conf @@ -0,0 +1,24 @@ +hostname r3 +password zebra +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 3.3.3.3 + ! + address-family ipv4 + discovery transport-address 3.3.3.3 + ! + interface r3-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf new file mode 100644 index 0000000..0e64ed6 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/ospfd.conf @@ -0,0 +1,17 @@ +hostname r3 +password 1 +log file ospfd.log +! +router ospf + router-id 3.3.3.3 + network 0.0.0.0/4 area 0 + redistribute static +! +int r3-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r3-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf new file mode 100644 index 0000000..9ffc84f --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r3/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log + +hostname r3 +password zebra +! +interface lo + ip address 3.3.3.3/32 +! +interface r3-eth0 + description to sw1 + ip address 10.0.2.3/24 + no link-detect +! +interface r3-eth1 + description to sw2 + ip address 10.0.3.3/24 + no link-detect +! +interface r3-eth4 + description to ce2 + ip address 192.168.1.1/24 + no link-detect +! +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf new file mode 100644 index 0000000..ed76ed3 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/bgpd.conf @@ -0,0 +1,72 @@ +frr defaults traditional + +hostname r4 +password zebra +log stdout notifications +log commands +log file bgpd.log + +#debug bgp vpn label +#debug bgp nht +#debug bgp zebra + +router bgp 5226 + bgp router-id 4.4.4.4 + bgp cluster-id 4.4.4.4 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 update-source 4.4.4.4 + neighbor 2.2.2.2 timers 3 10 + + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family + + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family + +router bgp 5227 vrf r4-cust1 + + bgp router-id 192.168.1.1 + no bgp ebgp-requires-policy + + neighbor 192.168.1.2 remote-as 5227 + neighbor 192.168.1.2 update-source 192.168.1.1 + neighbor 192.168.1.2 timers 3 10 + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 next-hop-self + + label vpn export 1041 + rd vpn export 10:41 + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family + +router bgp 5228 vrf r4-cust2 + + bgp router-id 192.168.2.1 + no bgp ebgp-requires-policy + + neighbor 192.168.2.2 remote-as 5228 + neighbor 192.168.2.2 update-source 192.168.2.1 + neighbor 192.168.2.2 timers 3 10 + + address-family ipv4 unicast + neighbor 192.168.2.2 activate + neighbor 192.168.2.2 next-hop-self + + label vpn export 1042 + rd vpn export 10:42 + # note RT same as r4-cust1 for inter-vrf route leaking + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family + +end diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf new file mode 100644 index 0000000..617d3a7 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ldpd.conf @@ -0,0 +1,24 @@ +hostname r4 +password zebra +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 4.4.4.4 + ! + address-family ipv4 + discovery transport-address 4.4.4.4 + ! + interface r4-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf new file mode 100644 index 0000000..89e37df --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/ospfd.conf @@ -0,0 +1,12 @@ +hostname r4 +log file ospfd.log +! +router ospf + router-id 4.4.4.4 + network 0.0.0.0/4 area 0 + redistribute static +! +int r4-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf new file mode 100644 index 0000000..4f01a27 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r4/zebra.conf @@ -0,0 +1,28 @@ +log file zebra.log + +hostname r4 +password zebra +! +interface lo + ip address 4.4.4.4/32 +! +interface r4-eth0 + description to sw1 + ip address 10.0.2.4/24 + no link-detect +! +interface r4-eth4 + description to ce3 + ip address 192.168.1.1/24 + no link-detect +! +interface r4-eth5 + description to ce4 + ip address 192.168.2.1/24 + no link-detect +! +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py new file mode 100644 index 0000000..375bca8 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/add_routes.py @@ -0,0 +1,59 @@ +from lib.lutil import luCommand + +luCommand( + "r1", 'vtysh -c "add vrf r1-cust1 prefix 99.0.0.1/32"', ".", "none", "IP Address" +) +luCommand( + "r3", 'vtysh -c "add vrf r3-cust1 prefix 99.0.0.2/32"', ".", "none", "IP Address" +) +luCommand( + "r4", 'vtysh -c "add vrf r4-cust1 prefix 99.0.0.3/32"', ".", "none", "IP Address" +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "99.0.0.1", + "pass", + "Local Registration", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "99.0.0.2", + "pass", + "Local Registration", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "99.0.0.3", + "pass", + "Local Registration", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations remote"', + "4 out of 4", + "wait", + "Remote Registration", + 10, +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations remote"', + "6 out of 6", + "wait", + "Remote Registration", + 10, +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations remote"', + "4 out of 4", + "wait", + "Remote Registration", + 10, +) +luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py new file mode 100644 index 0000000..f514575 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/adjacencies.py @@ -0,0 +1,64 @@ +from lib.lutil import luCommand + +luCommand("ce1", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand("ce2", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand("ce3", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Adjacencies up", 180) +luCommand( + "ce4", 'vtysh -c "show bgp vrf all summary"', " 00:0", "wait", "Adjacencies up", 180 +) +luCommand( + "r1", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r3", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r4", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r2", + 'vtysh -c "show bgp summary"', + " 00:0.* 00:0.* 00:0", + "wait", + "Core adjacencies up", + 180, +) +luCommand( + "r1", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r3", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r4", 'vtysh -c "show bgp summary"', " 00:0", "wait", "Core adjacencies up", 180 +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf all summary"', + " 00:0.* 00:0.* 00:0", + "pass", + "All adjacencies up", +) +luCommand( + "r1", "ping 3.3.3.3 -c 1", " 0. packet loss", "wait", "PE->PE3 (loopback) ping" +) +luCommand( + "r1", "ping 4.4.4.4 -c 1", " 0. packet loss", "wait", "PE->PE4 (loopback) ping" +) +luCommand( + "r4", "ping 3.3.3.3 -c 1", " 0. packet loss", "wait", "PE->PE3 (loopback) ping" +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py new file mode 100644 index 0000000..91a7adf --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_mpls.py @@ -0,0 +1,83 @@ +from lib.lutil import luCommand, luLast +from lib import topotest + +ret = luCommand( + "r2", + "ip -M route show", + r"\d*(?= via inet 10.0.2.4 dev r2-eth1)", + "wait", + "See mpls route to r4", +) +found = luLast() + +if ret != False and found != None: + label4r4 = found.group(0) + luCommand("r2", "ip -M route show", ".", "pass", "See %s as label to r4" % label4r4) + ret = luCommand( + "r2", + "ip -M route show", + r"\d*(?= via inet 10.0.1.1 dev r2-eth0)", + "wait", + "See mpls route to r1", + ) + found = luLast() + +if ret != False and found != None: + label4r1 = found.group(0) + luCommand("r2", "ip -M route show", ".", "pass", "See %s as label to r1" % label4r1) + + luCommand( + "r1", + "ip route show vrf r1-cust1", + "99.0.0.4", + "pass", + "VRF->MPLS PHP route installed", + ) + luCommand( + "r4", + "ip route show vrf r4-cust2", + "99.0.0.1", + "pass", + "VRF->MPLS PHP route installed", + ) + + luCommand("r1", "ip -M route show", "101", "pass", "MPLS->VRF route installed") + luCommand("r4", "ip -M route show", "1041", "pass", "MPLS->VRF1 route installed") + luCommand("r4", "ip -M route show", "1042", "pass", "MPLS->VRF2 route installed") + + luCommand( + "ce1", + "ping 99.0.0.4 -I 99.0.0.1 -c 1", + " 0. packet loss", + "wait", + "CE->CE (loopback) ping - l3vpn+zebra case", + ) + # skip due to VRF weirdness + # luCommand('ce4', 'ping 99.0.0.1 -I 99.0.0.4 -c 1', + # ' 0. packet loss','wait','CE->CE (loopback) ping - l3vpn+zebra case') + + luCommand( + "ce1", + "ping 99.0.0.4 -I 99.0.0.1 -c 1", + " 0. packet loss", + "wait", + "CE->CE (loopback) ping", + ) + # luCommand('ce4', 'ping 99.0.0.1 -I 99.0.0.4 -c 1', + # ' 0. packet loss','wait','CE->CE (loopback) ping') + + luCommand("r3", "ip -M route show", "103", "pass", "MPLS->VRF route installed") + luCommand( + "ce2", + "ping 99.0.0.3 -I 99.0.0.2 -c 1", + " 0. packet loss", + "wait", + "CE2->CE3 (loopback) ping", + ) + luCommand( + "ce3", + "ping 99.0.0.4 -I 99.0.0.3 -c 1", + " 0. packet loss", + "wait", + "CE3->CE4 (loopback) ping", + ) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py new file mode 100644 index 0000000..75158b1 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_linux_vrf.py @@ -0,0 +1,74 @@ +from lib.lutil import luCommand + +rtrs = ["r1", "r3", "r4"] +for rtr in rtrs: + luCommand( + rtr, + "ip link show type vrf {}-cust1".format(rtr), + "cust1: .*UP", + "pass", + "VRF cust1 intf up", + ) + luCommand( + rtr, + "ip add show vrf {}-cust1".format(rtr), + "r..eth4.*UP", + "pass", + "VRF cust1 IP intf up", + ) + luCommand( + rtr, + "ip add show vrf {}-cust1".format(rtr), + "192.168", + "pass", + "VRF cust1 IP config", + ) + luCommand( + rtr, + "ip route show vrf {}-cust1".format(rtr), + "192.168...0/24 dev r.-eth", + "pass", + "VRF cust1 interface route", + ) +luCommand("r4", "ip link show type vrf r4-cust2", "cust2: .*UP", "pass", "VRF cust2 up") +luCommand( + "r4", + "ip add show vrf r4-cust2", + "r..eth5.*UP.* 192.168", + "pass", + "VRF cust1 IP config", +) +luCommand( + "r4", + "ip route show vrf r4-cust2", + "192.168...0/24 dev r.-eth", + "pass", + "VRF cust2 interface route", +) +rtrs = ["ce1", "ce2", "ce3"] +for rtr in rtrs: + luCommand( + rtr, + "ip route show", + "192.168...0/24 dev ce.-eth0", + "pass", + "CE interface route", + ) + luCommand(rtr, "ping 192.168.1.1 -c 1", " 0. packet loss", "wait", "CE->PE ping") +luCommand( + "ce4", "ip link show type vrf ce4-cust2", "cust2: .*UP", "pass", "VRF cust2 up" +) +luCommand( + "ce4", + "ip route show vrf ce4-cust2", + "192.168...0/24 dev ce.-eth0", + "pass", + "CE interface route", +) +luCommand( + "ce4", + "ping 192.168.2.1 -c 1 -I ce4-cust2", + " 0. packet loss", + "wait", + "CE4->PE4 ping", +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py new file mode 100644 index 0000000..217657d --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py @@ -0,0 +1,901 @@ +from lib.lutil import luCommand +from lib.bgprib import bgpribRequireVpnRoutes, bgpribRequireUnicastRoutes + +######################################################################## +# CE routers: contain routes they originate +######################################################################## +# +# mininet CLI commands +# ce1 vtysh -c "show bgp ipv4 uni" +# ce2 vtysh -c "show bgp ipv4 uni" +# ce3 vtysh -c "show bgp ipv4 uni" +# ce4 vtysh -c "show bgp ipv4 uni" + +want = [ + {"p": "5.1.0.0/24", "n": "99.0.0.1"}, + {"p": "5.1.1.0/24", "n": "99.0.0.1"}, + {"p": "6.0.1.0/24", "n": "99.0.0.1"}, + {"p": "6.0.2.0/24", "n": "99.0.0.1"}, + {"p": "99.0.0.1/32", "n": "0.0.0.0"}, +] +bgpribRequireUnicastRoutes("ce1", "ipv4", "", "Cust 1 routes in ce1", want) + +want = [ + {"p": "5.1.0.0/24", "n": "99.0.0.2"}, + {"p": "5.1.1.0/24", "n": "99.0.0.2"}, + {"p": "6.0.1.0/24", "n": "99.0.0.2"}, + {"p": "6.0.2.0/24", "n": "99.0.0.2"}, + {"p": "99.0.0.2/32", "n": "0.0.0.0"}, +] +bgpribRequireUnicastRoutes("ce2", "ipv4", "", "Cust 2 routes in ce1", want) + +want = [ + {"p": "5.1.2.0/24", "n": "99.0.0.3"}, + {"p": "5.1.3.0/24", "n": "99.0.0.3"}, + {"p": "6.0.1.0/24", "n": "99.0.0.3"}, + {"p": "6.0.2.0/24", "n": "99.0.0.3"}, + {"p": "99.0.0.3/32", "n": "0.0.0.0"}, +] +bgpribRequireUnicastRoutes("ce3", "ipv4", "", "Cust 3 routes in ce1", want) + +want = [ + {"p": "5.4.2.0/24", "n": "99.0.0.4"}, + {"p": "5.4.3.0/24", "n": "99.0.0.4"}, + {"p": "6.0.1.0/24", "n": "99.0.0.4"}, + {"p": "6.0.2.0/24", "n": "99.0.0.4"}, + {"p": "99.0.0.4/32", "n": "0.0.0.0"}, +] +bgpribRequireUnicastRoutes("ce4", "ipv4", "ce4-cust2", "Cust 4 routes in ce1", want) + + +######################################################################## +# PE routers: VRFs contain routes from locally-attached customer nets +######################################################################## +# +# r1 vtysh -c "show bgp vrf r1-cust1 ipv4" +# +want_r1_cust1_routes = [ + {"p": "5.1.0.0/24", "n": "99.0.0.1"}, + {"p": "5.1.1.0/24", "n": "99.0.0.1"}, + {"p": "6.0.1.0/24", "n": "99.0.0.1"}, + {"p": "6.0.2.0/24", "n": "99.0.0.1"}, + {"p": "172.16.0.0/24", "n": "0.0.0.0", "bp": True}, + {"p": "172.16.1.1/32", "n": "0.0.0.0", "bp": True}, + {"p": "99.0.0.1/32", "n": "192.168.1.2"}, +] +bgpribRequireUnicastRoutes( + "r1", "ipv4", "r1-cust1", "Customer 1 routes in r1 vrf", want_r1_cust1_routes +) + +want_r1_cust4_routes = [ + {"p": "172.16.0.0/24", "n": "0.0.0.0", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r1", "ipv4", "r1-cust4", "Customer 4 routes in r1 vrf", want_r1_cust4_routes +) + +want_r1_cust5_routes = [ + {"p": "172.16.1.1/32", "n": "0.0.0.0", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r1", "ipv4", "r1-cust5", "Customer 5 routes in r1 vrf", want_r1_cust5_routes +) + +want_r3_cust1_routes = [ + {"p": "5.1.0.0/24", "n": "99.0.0.2"}, + {"p": "5.1.1.0/24", "n": "99.0.0.2"}, + {"p": "6.0.1.0/24", "n": "99.0.0.2"}, + {"p": "6.0.2.0/24", "n": "99.0.0.2"}, + {"p": "99.0.0.2/32", "n": "192.168.1.2"}, +] +bgpribRequireUnicastRoutes( + "r3", "ipv4", "r3-cust1", "Customer 1 routes in r3 vrf", want_r3_cust1_routes +) + +want_r4_cust1_routes = [ + {"p": "5.1.2.0/24", "n": "99.0.0.3"}, + {"p": "5.1.3.0/24", "n": "99.0.0.3"}, + {"p": "6.0.1.0/24", "n": "99.0.0.3"}, + {"p": "6.0.2.0/24", "n": "99.0.0.3"}, + {"p": "99.0.0.3/32", "n": "192.168.1.2"}, +] +bgpribRequireUnicastRoutes( + "r4", "ipv4", "r4-cust1", "Customer 1 routes in r4 vrf", want_r4_cust1_routes +) + +want_r4_cust2_routes = [ + {"p": "5.4.2.0/24", "n": "99.0.0.4"}, + {"p": "5.4.3.0/24", "n": "99.0.0.4"}, + {"p": "6.0.1.0/24", "n": "99.0.0.4"}, + {"p": "6.0.2.0/24", "n": "99.0.0.4"}, + {"p": "99.0.0.4/32", "n": "192.168.2.2"}, +] +bgpribRequireUnicastRoutes( + "r4", "ipv4", "r4-cust2", "Customer 2 routes in r4 vrf", want_r4_cust2_routes +) + +######################################################################## +# PE routers: core unicast routes are empty +######################################################################## + +luCommand( + "r1", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Core Unicast SAFI clean", +) +luCommand( + "r2", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Core Unicast SAFI clean", +) +luCommand( + "r3", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Core Unicast SAFI clean", +) +luCommand( + "r4", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Core Unicast SAFI clean", +) + +######################################################################## +# PE routers: local ce-originated routes are leaked to vpn +######################################################################## + +# nhzero is for the new code that sets nh of locally-leaked routes to 0 +# nhzero = 1 +nhzero = 0 + +if nhzero: + luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn"', + "Distinguisher: *10:1.*5.1.0.0/24 *0.0.0.0 .*5.1.1.0/24 *0.0.0.0 .*99.0.0.1/32 *0.0.0.0 ", + "pass", + "vrf->vpn routes", + ) + luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn"', + "Distinguisher: *10:3.*5.1.0.0/24 *0.0.0.0 .*5.1.1.0/24 *0.0.0.0 .*99.0.0.2/32 *0.0.0.0 ", + "pass", + "vrf->vpn routes", + ) + want = [ + {"rd": "10:41", "p": "5.1.2.0/24", "n": "0.0.0.0"}, + {"rd": "10:41", "p": "5.1.3.0/24", "n": "0.0.0.0"}, + {"rd": "10:41", "p": "99.0.0.3/32", "n": "0.0.0.0"}, + {"rd": "10:42", "p": "5.4.2.0/24", "n": "0.0.0.0"}, + {"rd": "10:42", "p": "5.4.3.0/24", "n": "0.0.0.0"}, + {"rd": "10:42", "p": "99.0.0.4/32", "n": "0.0.0.0"}, + ] + bgpribRequireVpnRoutes("r4", "vrf->vpn routes", want) + +else: + luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn"', + r"Distinguisher: *10:1.*5.1.0.0/24 *99.0.0.1\b.*5.1.1.0/24 *99.0.0.1\b.*6.0.1.0/24 *99.0.0.1\b.*6.0.2.0/24 *99.0.0.1\b.*99.0.0.1/32 *192.168.1.2\b", + "pass", + "vrf->vpn routes", + ) + luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn"', + r"Distinguisher: *10:3.*5.1.0.0/24 *99.0.0.2\b.*5.1.1.0/24 *99.0.0.2\b.*6.0.1.0/24 *99.0.0.2\b.*6.0.2.0/24 *99.0.0.2\b.*99.0.0.2/32 *192.168.1.2\b", + "pass", + "vrf->vpn routes", + ) + want = [ + {"rd": "10:41", "p": "5.1.2.0/24", "n": "99.0.0.3"}, + {"rd": "10:41", "p": "5.1.3.0/24", "n": "99.0.0.3"}, + {"rd": "10:41", "p": "6.0.1.0/24", "n": "99.0.0.3"}, + {"rd": "10:41", "p": "6.0.2.0/24", "n": "99.0.0.3"}, + {"rd": "10:41", "p": "99.0.0.3/32", "n": "192.168.1.2"}, + {"rd": "10:42", "p": "5.4.2.0/24", "n": "99.0.0.4"}, + {"rd": "10:42", "p": "5.4.3.0/24", "n": "99.0.0.4"}, + {"rd": "10:42", "p": "6.0.1.0/24", "n": "99.0.0.4"}, + {"rd": "10:42", "p": "6.0.2.0/24", "n": "99.0.0.4"}, + {"rd": "10:42", "p": "99.0.0.4/32", "n": "192.168.2.2"}, + ] + bgpribRequireVpnRoutes("r4", "vrf->vpn routes", want) + +######################################################################## +# PE routers: exporting vrfs set MPLS vrf labels in kernel +######################################################################## + +luCommand( + "r1", 'vtysh -c "show mpls table"', " 101 *BGP *r1-cust1", "pass", "vrf labels" +) +luCommand( + "r3", 'vtysh -c "show mpls table"', " 103 *BGP *r3-cust1", "pass", "vrf labels" +) +luCommand( + "r4", + 'vtysh -c "show mpls table"', + " 1041 *BGP *r4-cust1 .*1042 *BGP *r4-cust2", + "pass", + "vrf labels", +) + +######################################################################## +# Core VPN router: all customer routes +######################################################################## + +want_rd_routes = [ + {"rd": "10:1", "p": "5.1.0.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "5.1.0.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "99.0.0.1/32", "n": "1.1.1.1"}, + {"rd": "10:3", "p": "5.1.0.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "5.1.0.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "99.0.0.2/32", "n": "3.3.3.3"}, + {"rd": "10:41", "p": "5.1.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "5.1.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "99.0.0.3/32", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "99.0.0.4/32", "n": "4.4.4.4"}, +] +bgpribRequireVpnRoutes("r2", "Customer routes in provider vpn core", want_rd_routes) + +######################################################################## +# PE routers: VPN routes from remote customers +######################################################################## +# +# r1 vtysh -c "show bgp ipv4 vpn" +# +want_r1_remote_vpn_routes = [ + {"rd": "10:3", "p": "5.1.0.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "5.1.1.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "99.0.0.2/32", "n": "3.3.3.3"}, + {"rd": "10:41", "p": "5.1.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "5.1.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "99.0.0.3/32", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "99.0.0.4/32", "n": "4.4.4.4"}, +] +bgpribRequireVpnRoutes( + "r1", "Remote Customer routes in R1 vpn", want_r1_remote_vpn_routes +) + +want_r3_remote_vpn_routes = [ + {"rd": "10:1", "p": "5.1.0.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "5.1.1.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "99.0.0.1/32", "n": "1.1.1.1"}, + {"rd": "10:41", "p": "5.1.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "5.1.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:41", "p": "99.0.0.3/32", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.2.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "5.4.3.0/24", "n": "4.4.4.4"}, + {"rd": "10:42", "p": "99.0.0.4/32", "n": "4.4.4.4"}, +] +bgpribRequireVpnRoutes( + "r3", "Remote Customer routes in R3 vpn", want_r3_remote_vpn_routes +) + +want_r4_remote_vpn_routes = [ + {"rd": "10:1", "p": "5.1.0.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "5.1.1.0/24", "n": "1.1.1.1"}, + {"rd": "10:1", "p": "99.0.0.1/32", "n": "1.1.1.1"}, + {"rd": "10:3", "p": "5.1.0.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "5.1.1.0/24", "n": "3.3.3.3"}, + {"rd": "10:3", "p": "99.0.0.2/32", "n": "3.3.3.3"}, +] +bgpribRequireVpnRoutes( + "r4", "Remote Customer routes in R4 vpn", want_r4_remote_vpn_routes +) + + +# r1 vtysh -c "show bgp vrf r1-cust1 ipv4" +######################################################################## +# PE routers: VRFs contain routes from remote customer nets +######################################################################## +# First let's spot check and ensure that some of the routes +# have showed up and been best path'd +# After the first two are good. It's probably ok +# to look at the rest of the routes in the vrf +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.1.0.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.0.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.1.1.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.1.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.1.2.0/24"', + "1 available, best", + "wait", + "Ensure 5.1.2.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.1.3.0/24"', + "1 available, best", + "wait", + "Ensure 5.1.3.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.4.2.0/24"', + "1 available, best", + "wait", + "Ensure 5.4.2.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 5.4.2.0/24"', + "1 available, best", + "wait", + "Ensure 5.4.3.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 6.0.1.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.1.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 6.0.2.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.2.0 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 99.0.0.1/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.1 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 99.0.0.2/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.2 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 99.0.0.3/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.3 shows up on r1", + 10, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 99.0.0.4/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.4 shows up on r1", + 10, +) +want_r1_remote_cust1_routes = [ + {"p": "5.1.0.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "5.1.0.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "5.1.1.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "5.1.2.0/24", "n": "4.4.4.4"}, + {"p": "5.1.3.0/24", "n": "4.4.4.4"}, + {"p": "5.4.2.0/24", "n": "4.4.4.4"}, + {"p": "5.4.3.0/24", "n": "4.4.4.4"}, + {"p": "6.0.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "4.4.4.4", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "6.0.2.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.2.0/24", "n": "4.4.4.4", "bp": False}, + {"p": "6.0.2.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "99.0.0.1/32", "n": "192.168.1.2", "bp": True}, + {"p": "99.0.0.2/32", "n": "3.3.3.3"}, + {"p": "99.0.0.3/32", "n": "4.4.4.4"}, + {"p": "99.0.0.4/32", "n": "4.4.4.4"}, +] +bgpribRequireUnicastRoutes( + "r1", + "ipv4", + "r1-cust1", + "Customer 1 routes in r1 vrf (2)", + want_r1_remote_cust1_routes, + debug=False, +) + + +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.1.0.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.0.0 shows up r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.1.1.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.1.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.1.2.0/24"', + "1 available, best", + "wait", + "Ensure 5.1.2.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.1.3.0/24"', + "1 available, best", + "wait", + "Ensure 5.1.3.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.4.3.0/24"', + "1 available, best", + "wait", + "Ensure 5.4.3.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.4.3.0/24"', + "1 available, best", + "wait", + "Ensure 5.4.3.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 5.4.3.0/24"', + "1 available, best", + "wait", + "Ensure 5.4.3.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 6.0.1.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.1.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 6.0.2.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.2.0 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 99.0.0.1/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.1 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 99.0.0.3/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.3 shows up on r3", + 10, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 99.0.0.4/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.4 shows up on r3", + 10, +) +want_r3_remote_cust1_routes = [ + {"p": "5.1.0.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.0.0/24", "n": "99.0.0.2", "bp": False}, + {"p": "5.1.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "99.0.0.2", "bp": False}, + {"p": "5.1.2.0/24", "n": "4.4.4.4", "bp": True}, + {"p": "5.1.3.0/24", "n": "4.4.4.4", "bp": True}, + {"p": "5.4.2.0/24", "n": "4.4.4.4", "bp": True}, + {"p": "5.4.3.0/24", "n": "4.4.4.4", "bp": True}, + {"p": "6.0.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "4.4.4.4", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.2", "bp": False}, + {"p": "6.0.2.0/24", "n": "1.1.1.1", "bp": False}, + {"p": "6.0.2.0/24", "n": "4.4.4.4", "bp": False}, + {"p": "6.0.2.0/24", "n": "99.0.0.2", "bp": True}, + {"p": "99.0.0.1/32", "n": "1.1.1.1", "bp": True}, + {"p": "99.0.0.3/32", "n": "4.4.4.4", "bp": True}, + {"p": "99.0.0.4/32", "n": "4.4.4.4", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r3", + "ipv4", + "r3-cust1", + "Customer 1 routes in r3 vrf (2)", + want_r3_remote_cust1_routes, + debug=False, +) + +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 5.1.0.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.0.0 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 5.1.1.0/24"', + "2 available, best", + "wait", + "Ensure 5.1.1.0 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 6.0.1.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.1.0 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 6.0.2.0/24"', + "4 available, best", + "wait", + "Ensure 6.0.2.0 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 99.0.0.1/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.1 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 99.0.0.2/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.2 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 99.0.0.3/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.3 shows up on r4", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni 99.0.0.4/32"', + "1 available, best", + "wait", + "Ensure 99.0.0.4 shows up on r4", + 10, +) +want_r4_remote_cust1_routes = [ + {"p": "5.1.0.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.0.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "5.1.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.4", "bp": False}, + {"p": "6.0.2.0/24", "n": "1.1.1.1", "bp": False}, + {"p": "6.0.2.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.2.0/24", "n": "99.0.0.3", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.4", "bp": False}, + {"p": "99.0.0.1/32", "n": "1.1.1.1", "bp": True}, + {"p": "99.0.0.2/32", "n": "3.3.3.3", "bp": True}, + {"p": "99.0.0.3/32", "n": "192.168.1.2", "bp": True}, + {"p": "99.0.0.4/32", "n": "192.168.2.2", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r4", + "ipv4", + "r4-cust1", + "Customer 1 routes in r4 vrf (2)", + want_r4_remote_cust1_routes, + debug=False, +) + +want_r4_remote_cust2_routes = [ + {"p": "5.1.0.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.0.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "5.1.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "1.1.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.3", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.4", "bp": False}, + {"p": "6.0.2.0/24", "n": "1.1.1.1", "bp": False}, + {"p": "6.0.2.0/24", "n": "3.3.3.3", "bp": False}, + {"p": "6.0.2.0/24", "n": "99.0.0.3", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.4", "bp": False}, + {"p": "99.0.0.1/32", "n": "1.1.1.1", "bp": True}, + {"p": "99.0.0.2/32", "n": "3.3.3.3", "bp": True}, + {"p": "99.0.0.3/32", "n": "192.168.1.2", "bp": True}, + {"p": "99.0.0.4/32", "n": "192.168.2.2", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r4", + "ipv4", + "r4-cust2", + "Customer 2 routes in r4 vrf (2)", + want_r4_remote_cust2_routes, + debug=False, +) + + +######################################################################### +# CE routers: contain routes from remote customer nets +######################################################################### +# ce1 vtysh -c "show bgp ipv4 uni" +# r1 vtysh -c "show bgp vrf r1-cust1 ipv4" +# r1 vtysh -c "show bgp vrf r1-cust1 ipv4 5.1.2.0/24" + +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni"', + "14 routes and 14", + "wait", + "Local and remote routes", + 10, +) +want = [ + {"p": "5.1.0.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "5.1.2.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.1.3.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.2.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.3.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "99.0.0.1", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.1", "bp": True}, +] +bgpribRequireUnicastRoutes( + "ce1", "ipv4", "", "Cust 1 routes from remote", want, debug=False +) + +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni"', + "14 routes and 17", + "wait", + "Local and remote routes", + 10, +) +want = [ + {"p": "5.1.0.0/24", "n": "192.168.1.1", "bp": False}, + {"p": "5.1.0.0/24", "n": "99.0.0.2", "bp": True}, + {"p": "5.1.1.0/24", "n": "192.168.1.1", "bp": False}, + {"p": "5.1.1.0/24", "n": "99.0.0.2", "bp": True}, + {"p": "5.1.2.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.1.3.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.2.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.3.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "192.168.1.1", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.2", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.2", "bp": True}, +] +bgpribRequireUnicastRoutes( + "ce2", "ipv4", "", "Cust 1 routes from remote", want, debug=False +) + +# human readable output for debugging +luCommand("r4", 'vtysh -c "show bgp vrf r4-cust1 ipv4 uni"') +luCommand("r4", 'vtysh -c "show bgp vrf r4-cust2 ipv4 uni"') +luCommand("r4", 'vtysh -c "show bgp ipv4 vpn"') +luCommand("r4", 'vtysh -c "show ip route vrf r4-cust1"') +luCommand("r4", 'vtysh -c "show ip route vrf r4-cust2"') + +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni"', + "14 routes and 15", + "wait", + "Local and remote routes", + 10, +) +# Requires bvl-bug-degenerate-no-label fix (FRR PR #2053) +want = [ + {"p": "5.1.0.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.2.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "5.4.3.0/24", "n": "192.168.1.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "192.168.1.1", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.3", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.3", "bp": True}, +] +bgpribRequireUnicastRoutes( + "ce3", "ipv4", "", "Cust 1 routes from remote", want, debug=False +) + +luCommand( + "ce4", + 'vtysh -c "show bgp vrf ce4-cust2 ipv4 uni"', + "14 routes and 16", + "wait", + "Local and remote routes", + 10, +) +want = [ + {"p": "5.1.0.0/24", "n": "192.168.2.1", "bp": True}, + {"p": "5.1.1.0/24", "n": "192.168.2.1", "bp": True}, + {"p": "5.1.2.0/24", "n": "192.168.2.1", "bp": True}, + {"p": "5.1.3.0/24", "n": "192.168.2.1", "bp": True}, + {"p": "6.0.1.0/24", "n": "192.168.2.1", "bp": False}, + {"p": "6.0.2.0/24", "n": "192.168.2.1", "bp": False}, + {"p": "6.0.1.0/24", "n": "99.0.0.4", "bp": True}, + {"p": "6.0.2.0/24", "n": "99.0.0.4", "bp": True}, +] +bgpribRequireUnicastRoutes( + "ce4", "ipv4", "ce4-cust2", "Cust 2 routes from remote", want, debug=False +) + +# verify details of exported/imported routes +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni 6.0.1.0"', + "1 available.*192.168.1.1.*99.0.0.1.*Community: 0:67.*Extended Community: RT:89:123.*Large Community: 12:34:56", + "pass", + "Redundant route 1 details", +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni 6.0.1.0"', + "2 available, best .*192.168.1.1.* Local.* 192.168.1.1 from 192.168.1.1 .192.168.1.1" + + ".* Origin IGP, metric 98, localpref 123, valid, internal" + + ".* Community: 0:67.* Extended Community: RT:52:100 RT:89:123.* Large Community: 12:34:56", + "pass", + "Redundant route 1 details", +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni 6.0.1.0"', + "2 available, best .*192.168.1.1.* Local.* 99.0.0.2 from 0.0.0.0 .99.0.0.2" + + ".* Origin IGP, metric 100, localpref 100, weight 32768, valid, sourced, local, best .Weight" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:56", + "pass", + "Redundant route 1 details", +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni 6.0.1.0"', + "2 available, best " + ".* Local.* 99.0.0.3 from 0.0.0.0 .99.0.0.3" + + ".* Origin IGP, metric 200, localpref 50, weight 32768, valid, sourced, local, best .Weight" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:56", + "pass", + "Redundant route 1 details", +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni 6.0.1.0"', + "2 available, best " + ".* Local.* 192.168.1.1 from 192.168.1.1 .192.168.1.1" + + ".* Origin IGP, metric 98, localpref 123, valid, internal" + + ".* Community: 0:67.* Extended Community: RT:52:100 RT:89:123.* Large Community: 12:34:56", + "pass", + "Redundant route 1 details", +) +luCommand( + "ce4", + 'vtysh -c "show bgp vrf ce4-cust2 ipv4 6.0.1.0 json"', + ( + '{"paths":[' + + '{"aspath":{"string":"Local"},"origin":"IGP","metric":200,"locPrf":50,' + + '"weight":32768,"valid":true,"sourced":true,"local":true,' + + '"bestpath":{"overall":true,"selectionReason":"Weight"},' + + '"community":{"string":"0:67"},"extendedCommunity":{"string":"RT:89:123"},' + + '"largeCommunity":{"string":"12:34:56"},' + + '"peer":{"peerId":"0.0.0.0","routerId":"99.0.0.4"}},' + + '{"aspath":{"string":"Local"},"origin":"IGP","metric":98,"locPrf":123,' + + '"valid":true,' + + '"community":{"string":"0:67"},"extendedCommunity":{' + + '"string":"RT:52:100 RT:89:123"},"largeCommunity":{"string":"12:34:56"},' + + '"peer":{"peerId":"192.168.2.1","routerId":"192.168.2.1"}}' + + "]}" + ), + "jsoncmp_pass", + "Redundant route 1 details", +) + +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni 6.0.2.0"', + "1 available, best .*192.168.1.1.* Local.* 99.0.0.1 from 0.0.0.0 .99.0.0.1" + + ".* Origin IGP, metric 100, localpref 100, weight 32768, valid, sourced, local, best .First path received" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:11", + "pass", + "Route 2 details", +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni 6.0.2.0"', + "1 available, best .*192.168.1.1.* Local.* 99.0.0.2 from 0.0.0.0 .99.0.0.2" + + ".* Origin IGP, metric 100, localpref 100, weight 32768, valid, sourced, local, best .First path received" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:12", + "pass", + "Route 2 details", +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni 6.0.2.0"', + "1 available, best .*192.168.1.1.* Local.* 99.0.0.3 from 0.0.0.0 .99.0.0.3" + + ".* Origin IGP, metric 100, localpref 100, weight 32768, valid, sourced, local, best .First path received" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:13", + "pass", + "Route 2 details", +) +luCommand( + "ce4", + 'vtysh -c "show bgp vrf ce4-cust2 ipv4 6.0.2.0"', + "2 available, best .*192.168.2.1.* Local.* 192.168.2.1 from 192.168.2.1 .192.168.2.1" + + ".* Origin IGP, metric 100, localpref 100, valid, internal" + + ".* Community: 0:67.* Extended Community: RT:52:100 RT:89:123.* Large Community: 12:34:13", + "pass", + "Redundant route 2 details", +) +luCommand( + "ce4", + 'vtysh -c "show bgp vrf ce4-cust2 ipv4 6.0.2.0"', + "2 available, best .*192.168.2.1.* Local.* 99.0.0.4 from 0.0.0.0 .99.0.0.4" + + ".* Origin IGP, metric 100, localpref 100, weight 32768, valid, sourced, local, best .Weight" + + ".* Community: 0:67.* Extended Community: RT:89:123.* Large Community: 12:34:14", + "pass", + "Redundant route 2 details", +) +luCommand( + "r1", + 'vtysh -c "show ip route vrf r1-cust5 5.1.0.0/24"', + "Known via .bgp., distance 200, .* vrf r1-cust5, best", + "pass", + "Recursive route leak details", +) +# done diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py new file mode 100644 index 0000000..a27b178 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/cleanup_all.py @@ -0,0 +1,120 @@ +from lib.lutil import luCommand + +luCommand( + "r1", + 'vtysh -c "clear vrf r1-cust1 prefix 99.0.0.1/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r3", + 'vtysh -c "clear vrf r3-cust1 prefix 99.0.0.2/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r4", + 'vtysh -c "clear vrf r3-cust1 prefix 99.0.0.3/32"', + ".", + "none", + "Cleared VRF route", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "99.0.0.1", + "fail", + "Local Registration cleared", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "99.0.0.2", + "fail", + "Local Registration cleared", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "99.0.0.3", + "fail", + "Local Registration cleared", +) +luCommand( + "r1", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", + 10, +) +luCommand( + "r2", + 'vtysh -c "show bgp ipv4 uni"', + "No BGP prefixes displayed", + "pass", + "Unicast SAFI", +) +luCommand( + "r3", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", + 10, +) +luCommand( + "r4", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Unicast SAFI updated", + 10, +) +luCommand( + "ce1", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", + 10, +) +luCommand( + "ce2", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", + 10, +) +luCommand( + "ce3", + 'vtysh -c "show bgp ipv4 uni"', + "2 routes and 2", + "wait", + "Local and remote routes", + 10, +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations remote"', + "Prefix ", + "fail", + "Remote Registration cleared", +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/del_bgp_instances.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/del_bgp_instances.py new file mode 100644 index 0000000..fcbc3df --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/del_bgp_instances.py @@ -0,0 +1,30 @@ +from lib.lutil import luCommand + +luCommand( + "r1", + '/usr/lib/frr/vtysh -c "conf ter" -c "no router bgp 5227 vrf r1-cust1" -c "no router bgp 5226"', + ".", + "none", + "Cleared bgp instances", +) +luCommand( + "r2", + '/usr/lib/frr/vtysh -c "conf ter" -c "no router bgp 5226"', + ".", + "none", + "Cleared bgp instances", +) +luCommand( + "r3", + '/usr/lib/frr/vtysh -c "conf ter" -c "no router bgp 5227 vrf r3-cust1" -c "no router bgp 5226"', + ".", + "none", + "Cleared bgp instances", +) +luCommand( + "r4", + '/usr/lib/frr/vtysh -c "conf ter" -c "no router bgp 5228 vrf r4-cust2" -c "no router bgp 5227 vrf r4-cust1" -c "no router bgp 5226"', + ".", + "none", + "Cleared bgp instances", +) diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/notification_check.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/notification_check.py new file mode 100644 index 0000000..73cd08f --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/notification_check.py @@ -0,0 +1,22 @@ +from lib.lutil import luCommand, luLast + +rtrs = ["ce1", "ce2", "ce3", "r1", "r2", "r3", "r4"] +for rtr in rtrs: + ret = luCommand( + rtr, + 'vtysh -c "show bgp neigh"', + "Notification received .([A-Za-z0-9/ ]*)", + "none", + "collect neighbor stats", + ) + found = luLast() + if ret != False and found != None: + val = found.group(1) + ret = luCommand( + rtr, + 'vtysh -c "show bgp neigh"', + "Notification received", + "fail", + "Notify RXed! {}".format(val), + ) +# done diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py new file mode 100644 index 0000000..190879c --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py @@ -0,0 +1,87 @@ +from lib.lutil import luCommand, luLast + +ret = luCommand( + "ce1", + 'vtysh -c "show ip route" | grep -c \\ 10\\.\\*/32', + "(.*)", + "pass", + "Looking for sharp routes", +) +found = luLast() +if ret != False and found != None: + num = int(found.group()) + luCommand( + "ce3", 'vtysh -c "show bgp sum"', ".", "pass", "See %s sharp routes" % num + ) + if num > 0: + rtrs = ["ce1", "ce2", "ce3"] + for rtr in rtrs: + luCommand( + rtr, + 'vtysh -c "show bgp ipv4 uni" | grep Display', + ".", + "none", + "BGP routes pre remove", + ) + luCommand( + rtr, + "ip route show | cat -n | tail", + ".", + "none", + "Linux routes pre remove", + ) + wait = 2 * num / 500 + luCommand( + "ce1", + 'vtysh -c "sharp remove routes 10.0.0.0 {}"'.format(num), + ".", + "none", + "Removing {} routes".format(num), + ) + luCommand( + "ce2", + 'vtysh -c "sharp remove routes 10.0.0.0 {}"'.format(num), + ".", + "none", + "Removing {} routes".format(num), + ) + for rtr in rtrs: + luCommand( + rtr, + 'vtysh -c "show bgp ipv4 uni" | grep Display', + " 14 route", + "wait", + "BGP routes removed", + wait, + wait_time=10, + ) + luCommand( + rtr, + 'vtysh -c "show bgp ipv4 uni"', + ".", + "none", + "BGP routes post remove", + ) + for rtr in rtrs: + luCommand( + rtr, + "ip route show | grep -c \\^10\\.", + "^0$", + "wait", + "Linux routes removed", + wait, + wait_time=10, + ) + luCommand(rtr, "ip route show", ".", "none", "Linux routes post remove") + rtrs = ["r1", "r3", "r4"] + for rtr in rtrs: + luCommand( + rtr, + "ip route show vrf {}-cust1 | grep -c \\^10\\.".format(rtr), + "^0$", + "wait", + "VRF route removed", + wait, + wait_time=10, + ) +# done diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py new file mode 100644 index 0000000..2ce4bc5 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_up.py @@ -0,0 +1,253 @@ +from lib.lutil import luCommand, luLast + +num = 50000 +b = int(num / (256 * 256)) +if b > 0: + r = num - b * (256 * 256) +else: + r = num +c = int(r / 256) +if c > 0: + d = r - c * 256 - 1 +else: + d = r +wait = 2 * num / 1000 +mem_z = {} +mem_b = {} +rtrs = ["ce1", "ce2", "ce3", "r1", "r2", "r3", "r4"] +for rtr in rtrs: + mem_z[rtr] = {"value": 0, "units": "unknown"} + mem_b[rtr] = {"value": 0, "units": "unknown"} + ret = luCommand( + rtr, + 'vtysh -c "show memory"', + r"zebra: System allocator statistics: Total heap allocated: *(\d*) ([A-Za-z]*) .*bgpd: System allocator statistics: Total heap allocated: *(\d*) ([A-Za-z]*)", + "none", + "collect bgpd memory stats", + ) + found = luLast() + if ret != False and found != None: + mem_z[rtr] = {"value": int(found.group(1)), "units": found.group(2)} + mem_b[rtr] = {"value": int(found.group(3)), "units": found.group(4)} + +luCommand( + "ce1", 'vtysh -c "show mem"', "qmem sharpd", "none", "check if sharpd running" +) +doSharp = False +found = luLast() +if ret != False and found != None: + if len(found.group()): + doSharp = True + +if doSharp != True: + luCommand( + "ce1", + 'vtysh -c "sharp data nexthop"', + ".", + "pass", + "sharpd NOT running, skipping test", + ) +else: + luCommand( + "ce1", + 'vtysh -c "sharp install routes 10.0.0.0 nexthop 99.0.0.1 {}"'.format(num), + "", + "pass", + "Adding {} routes".format(num), + ) + luCommand( + "ce2", + 'vtysh -c "sharp install routes 10.0.0.0 nexthop 99.0.0.2 {}"'.format(num), + "", + "pass", + "Adding {} routes".format(num), + ) + luCommand( + "ce1", + 'vtysh -c "show ip route summ" | grep "sharp" | cut -d " " -f 33', + str(num), + "wait", + "See all sharp routes in rib on ce1", + wait, + wait_time=10, + ) + luCommand( + "ce2", + 'vtysh -c "show ip route summ" | grep "sharp" | cut -d " " -f 33', + str(num), + "wait", + "See all sharp routes in rib on ce2", + wait, + wait_time=10, + ) + + rtrs = ["ce1", "ce2", "ce3"] + for rtr in rtrs: + luCommand( + rtr, + 'vtysh -c "show bgp ipv4 uni 10.{}.{}.{}"'.format(b, c, d), + "Last update:", + "wait", + "RXed last route, 10.{}.{}.{}".format(b, c, d), + wait, + wait_time=10, + ) + luCommand( + rtr, + 'vtysh -c "show bgp ipv4 uni" | grep -c 10\\.\\*/32', + str(num), + "wait", + "See all sharp routes in BGP", + wait, + wait_time=10, + ) + luCommand( + "r1", + 'vtysh -c "show bgp vrf r1-cust1 ipv4 uni 10.{}.{}.{}"'.format(b, c, d), + "99.0.0.1", + "wait", + "RXed -> 10.{}.{}.{} from CE1".format(b, c, d), + wait, + wait_time=10, + ) + luCommand( + "r3", + 'vtysh -c "show bgp vrf r3-cust1 ipv4 uni 10.{}.{}.{}"'.format(b, c, d), + "99.0.0.2", + "wait", + "RXed -> 10.{}.{}.{} from CE2".format(b, c, d), + wait, + wait_time=10, + ) + luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "99.0.0.1", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE1".format(b, c, d), + ) + luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "99.0.0.2", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE2".format(b, c, d), + ) + luCommand( + "r3", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "1.1.1.1", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE1".format(b, c, d), + ) + luCommand( + "r1", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "3.3.3.3", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE2".format(b, c, d), + ) + luCommand( + "r4", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "1.1.1.1", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE1".format(b, c, d), + ) + luCommand( + "r4", + 'vtysh -c "show bgp ipv4 vpn 10.{}.{}.{}"'.format(b, c, d), + "3.3.3.3", + "wait", + "see VPN safi -> 10.{}.{}.{} from CE2".format(b, c, d), + ) + rtrs = ["ce1", "ce2", "ce3"] + for rtr in rtrs: + luCommand( + rtr, + "ip route get 10.{}.{}.{}".format(b, c, d), + "dev", + "wait", + "Route to 10.{}.{}.{} available".format(b, c, d), + wait, + wait_time=10, + ) + luCommand( + rtr, + "ip route show | grep -c \\^10\\.", + str(num), + "wait", + "See {} linux routes".format(num), + wait, + wait_time=10, + ) + + rtrs = ["r1", "r3", "r4"] + for rtr in rtrs: + luCommand( + rtr, + "ip route get vrf {}-cust1 10.{}.{}.{}".format(rtr, b, c, d), + "dev", + "wait", + "VRF route available", + wait, + wait_time=10, + ) + luCommand( + rtr, + "ip route show vrf {}-cust1 | grep -c \\^10\\.".format(rtr), + str(num), + "wait", + "See {} linux routes".format(num), + wait, + wait_time=10, + ) + rtrs = ["ce1", "ce2", "ce3", "r1", "r2", "r3", "r4"] + for rtr in rtrs: + ret = luCommand( + rtr, + 'vtysh -c "show memory"', + r"zebra: System allocator statistics: Total heap allocated: *(\d*) ([A-Za-z]*) .*bgpd: System allocator statistics: Total heap allocated: *(\d*) ([A-Za-z]*)", + "none", + "collect bgpd memory stats", + ) + found = luLast() + if ret != False and found != None: + val_z = int(found.group(1)) + if mem_z[rtr]["units"] != found.group(2): + val_z *= 1000 + delta_z = val_z - int(mem_z[rtr]["value"]) + ave_z = float(delta_z) / float(num) + + val_b = int(found.group(3)) + if mem_b[rtr]["units"] != found.group(4): + val_b *= 1000 + delta_b = val_b - int(mem_b[rtr]["value"]) + ave_b = float(delta_b) / float(num) + luCommand( + rtr, + 'vtysh -c "show event cpu"', + ".", + "pass", + "BGPd heap: {0} {1} --> {2} {3} ({4} {1}/vpn route)".format( + mem_b[rtr]["value"], + mem_b[rtr]["units"], + found.group(3), + found.group(4), + round(ave_b, 4), + ), + ) + luCommand( + rtr, + 'vtysh -c "show event cpu"', + ".", + "pass", + "Zebra heap: {0} {1} --> {2} {3} ({4} {1}/vpn route)".format( + mem_z[rtr]["value"], + mem_z[rtr]["units"], + found.group(1), + found.group(2), + round(ave_z, 4), + ), + ) +# done diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py new file mode 100755 index 0000000..60d959f --- /dev/null +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/test_bgp_l3vpn_to_bgp_vrf.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import sys +import pytest + +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + +from lib.ltemplate import * + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def test_check_linux_vrf(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/check_linux_vrf.py", False, CliOnFail, CheckFunc) + + +def test_adjacencies(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/adjacencies.py", False, CliOnFail, CheckFunc) + + +def test_notification_check(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/notification_check.py", False, CliOnFail, CheckFunc) + + +def SKIP_test_add_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/add_routes.py", False, CliOnFail, CheckFunc) + + +def test_check_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/check_routes.py", False, CliOnFail, CheckFunc) + + +# manual data path setup test - remove once have bgp/zebra vrf path working +def test_check_linux_mpls(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/check_linux_mpls.py", False, CliOnFail, CheckFunc) + + +def test_check_scale_up(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/scale_up.py", False, CliOnFail, CheckFunc) + + +def test_check_scale_down(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1', iproute2='4.9')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True, iproute2=\'4.9\')' + ltemplateTest("scripts/scale_down.py", False, CliOnFail, CheckFunc) + + +def SKIP_test_cleanup_all(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('4.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'4.1\', cli=True)' + ltemplateTest("scripts/cleanup_all.py", False, CliOnFail, CheckFunc) + + +if __name__ == "__main__": + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/bgp_labeled_unicast_addpath/__init__.py b/tests/topotests/bgp_labeled_unicast_addpath/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r1/bgpd.conf b/tests/topotests/bgp_labeled_unicast_addpath/r1/bgpd.conf new file mode 100644 index 0000000..ae3812a --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r1/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65001 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 192.168.31.3 remote-as external + neighbor 192.168.31.3 timers 1 3 + neighbor 192.168.31.3 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.168.31.3 activate + exit-address-family +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r1/zebra.conf b/tests/topotests/bgp_labeled_unicast_addpath/r1/zebra.conf new file mode 100644 index 0000000..fbaccda --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.0.0.1/32 +! +interface r1-eth0 + ip address 192.168.31.1/24 +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r2/bgpd.conf b/tests/topotests/bgp_labeled_unicast_addpath/r2/bgpd.conf new file mode 100644 index 0000000..16dc1f3 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r2/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65002 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 192.168.32.3 remote-as external + neighbor 192.168.32.3 timers 1 3 + neighbor 192.168.32.3 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.168.32.3 activate + exit-address-family +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r2/zebra.conf b/tests/topotests/bgp_labeled_unicast_addpath/r2/zebra.conf new file mode 100644 index 0000000..8118d56 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r2/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.0.0.1/32 +! +interface r2-eth0 + ip address 192.168.32.2/24 +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r3/bgpd.conf b/tests/topotests/bgp_labeled_unicast_addpath/r3/bgpd.conf new file mode 100644 index 0000000..6dd8f7f --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r3/bgpd.conf @@ -0,0 +1,35 @@ +! +router bgp 65003 + no bgp default ipv4-unicast + no bgp ebgp-requires-policy + no bgp suppress-duplicates + bgp bestpath as-path multipath-relax + neighbor 192.168.31.1 remote-as external + neighbor 192.168.31.1 timers 1 3 + neighbor 192.168.31.1 timers connect 1 + neighbor 192.168.32.2 remote-as external + neighbor 192.168.32.2 timers 1 3 + neighbor 192.168.32.2 timers connect 1 + neighbor 192.168.34.4 remote-as external + neighbor 192.168.34.4 timers 1 3 + neighbor 192.168.34.4 timers connect 1 + neighbor 192.168.35.5 remote-as external + neighbor 192.168.35.5 timers 1 3 + neighbor 192.168.35.5 timers connect 1 + neighbor 192.168.35.5 shutdown + address-family ipv4 labeled-unicast + neighbor 192.168.31.1 activate + neighbor 192.168.32.2 activate + neighbor 192.168.35.5 activate + neighbor 192.168.34.4 activate + neighbor 192.168.34.4 route-map r4 out + neighbor 192.168.34.4 addpath-tx-all-paths + exit-address-family + ! +! +ip prefix-list r4 seq 5 permit 10.0.0.1/32 +! +route-map r4 permit 10 + match ip address prefix-list r4 +exit +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r3/zebra.conf b/tests/topotests/bgp_labeled_unicast_addpath/r3/zebra.conf new file mode 100644 index 0000000..838413a --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r3/zebra.conf @@ -0,0 +1,13 @@ +! +interface r3-eth0 + ip address 192.168.31.3/24 +! +interface r3-eth1 + ip address 192.168.32.3/24 +! +interface r3-eth2 + ip address 192.168.34.3/24 +! +interface r3-eth3 + ip address 192.168.35.3/24 +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r4/bgpd.conf b/tests/topotests/bgp_labeled_unicast_addpath/r4/bgpd.conf new file mode 100644 index 0000000..e137156 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r4/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65004 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 192.168.34.3 remote-as external + neighbor 192.168.34.3 timers 1 3 + neighbor 192.168.34.3 timers connect 1 + address-family ipv4 labeled-unicast + neighbor 192.168.34.3 activate + exit-address-family +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r4/zebra.conf b/tests/topotests/bgp_labeled_unicast_addpath/r4/zebra.conf new file mode 100644 index 0000000..2ec426a --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r4/zebra.conf @@ -0,0 +1,4 @@ +! +interface r4-eth0 + ip address 192.168.34.4/24 +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r5/bgpd.conf b/tests/topotests/bgp_labeled_unicast_addpath/r5/bgpd.conf new file mode 100644 index 0000000..5b38b5a --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r5/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65005 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 192.168.35.3 remote-as external + neighbor 192.168.35.3 timers 1 3 + neighbor 192.168.35.3 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.168.35.3 activate + exit-address-family +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/r5/zebra.conf b/tests/topotests/bgp_labeled_unicast_addpath/r5/zebra.conf new file mode 100644 index 0000000..6289e4f --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/r5/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 10.0.0.1/32 +! +interface r5-eth0 + ip address 192.168.35.5/24 +! diff --git a/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py b/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py new file mode 100644 index 0000000..f4bb487 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_addpath/test_bgp_labeled_unicast_addpath.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if labeled-unicast works correctly with addpath capability. +Initially R3 MUST announce 10.0.0.1/32 multipath(2) from R1 + R2. +Later, we enable R5 and 10.0.0.1/32 multipath(3) MUST be announced, +R1 + R2 + R5. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 6): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_addpath_labeled_unicast(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + + def _bgp_check_received_routes(pfxcount): + output = json.loads(r4.vtysh_cmd("show bgp ipv4 labeled-unicast summary json")) + expected = { + "peers": { + "192.168.34.3": { + "pfxRcd": pfxcount, + "state": "Established", + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_routes, 2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to receive labeled-unicast with addpath (multipath=2)" + + step("Enable BGP session for R5") + r3.vtysh_cmd( + """ + configure terminal + router bgp 65003 + no neighbor 192.168.35.5 shutdown + """ + ) + + test_func = functools.partial(_bgp_check_received_routes, 3) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to receive labeled-unicast with addpath (multipath=3)" + + step("Disable BGP session for R5") + r3.vtysh_cmd( + """ + configure terminal + router bgp 65003 + neighbor 192.168.35.5 shutdown + """ + ) + + test_func = functools.partial(_bgp_check_received_routes, 2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to receive labeled-unicast with addpath (multipath=2)" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/__init__.py b/tests/topotests/bgp_labeled_unicast_default_originate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/r1/bgpd.conf b/tests/topotests/bgp_labeled_unicast_default_originate/r1/bgpd.conf new file mode 100644 index 0000000..d0d1390 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_default_originate/r1/bgpd.conf @@ -0,0 +1,35 @@ +! +router bgp 65001 + no bgp default ipv4-unicast + no bgp default ipv6-unicast + no bgp ebgp-requires-policy + neighbor 192.168.12.2 remote-as external + neighbor 192.168.12.2 timers 1 3 + neighbor 192.168.12.2 timers connect 1 + neighbor 2001:db8:12::2 remote-as external + neighbor 2001:db8:12::2 timers 1 3 + neighbor 2001:db8:12::2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.168.12.2 activate + neighbor 192.168.12.2 default-originate route-map r2 + exit-address-family + ! + address-family ipv6 labeled-unicast + neighbor 2001:db8:12::2 activate + neighbor 2001:db8:12::2 default-originate route-map r2 + exit-address-family + ! +! +route-map r2 permit 10 + set community 65001:65001 + set metric 666 +exit +! diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/r1/zebra.conf b/tests/topotests/bgp_labeled_unicast_default_originate/r1/zebra.conf new file mode 100644 index 0000000..686b075 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_default_originate/r1/zebra.conf @@ -0,0 +1,5 @@ +! +interface r1-eth0 + ip address 192.168.12.1/24 + ipv6 address 2001:db8:12::1/64 +! diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/r2/bgpd.conf b/tests/topotests/bgp_labeled_unicast_default_originate/r2/bgpd.conf new file mode 100644 index 0000000..1498dff --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_default_originate/r2/bgpd.conf @@ -0,0 +1,19 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + no bgp default ipv6-unicast + neighbor 192.168.12.1 remote-as external + neighbor 192.168.12.1 timers 1 3 + neighbor 192.168.12.1 timers connect 1 + neighbor 2001:db8:12::1 remote-as external + neighbor 2001:db8:12::1 timers 1 3 + neighbor 2001:db8:12::1 timers connect 1 + address-family ipv4 labeled-unicast + neighbor 192.168.12.1 activate + exit-address-family + ! + address-family ipv6 labeled-unicast + neighbor 2001:db8:12::1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/r2/zebra.conf b/tests/topotests/bgp_labeled_unicast_default_originate/r2/zebra.conf new file mode 100644 index 0000000..cb5c55e --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_default_originate/r2/zebra.conf @@ -0,0 +1,5 @@ +! +interface r2-eth0 + ip address 192.168.12.2/24 + ipv6 address 2001:db8:12::2/64 +! diff --git a/tests/topotests/bgp_labeled_unicast_default_originate/test_bgp_labeled_unicast_default_originate.py b/tests/topotests/bgp_labeled_unicast_default_originate/test_bgp_labeled_unicast_default_originate.py new file mode 100644 index 0000000..34c23d9 --- /dev/null +++ b/tests/topotests/bgp_labeled_unicast_default_originate/test_bgp_labeled_unicast_default_originate.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if labeled-unicast works correctly with default-originate. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_labeled_unicast_default_originate(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_check_advertised_routes(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 labeled-unicast neighbors 192.168.12.2 advertised-routes json" + ) + ) + expected = { + "bgpOriginatingDefaultNetwork": "0.0.0.0/0", + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to advertise default route for labeled-unicast" + + def _bgp_check_received_ipv4_routes(): + output = json.loads( + r2.vtysh_cmd("show bgp ipv4 labeled-unicast 0.0.0.0/0 json") + ) + expected = { + "paths": [ + { + "valid": True, + "metric": 666, + "community": { + "string": "65001:65001", + }, + "remoteLabel": 0, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_ipv4_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to receive IPv4 default route for labeled-unicast" + + def _bgp_check_received_ipv6_routes(): + output = json.loads(r2.vtysh_cmd("show bgp ipv6 labeled-unicast ::/0 json")) + expected = { + "paths": [ + { + "valid": True, + "metric": 666, + "community": { + "string": "65001:65001", + }, + "remoteLabel": 2, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_ipv6_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to receive IPv6 default route for labeled-unicast" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_large_comm_list_match/__init__.py b/tests/topotests/bgp_large_comm_list_match/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_large_comm_list_match/r1/bgpd.conf b/tests/topotests/bgp_large_comm_list_match/r1/bgpd.conf new file mode 100644 index 0000000..1a91f0f --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r1/bgpd.conf @@ -0,0 +1,28 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as external + neighbor 192.168.0.2 timers 1 3 + neighbor 192.168.0.2 timers connect 1 + address-family ipv4 + redistribute connected + neighbor 192.168.0.2 route-map r2 out + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.1/32 +ip prefix-list p3 seq 5 permit 172.16.255.3/32 +ip prefix-list p4 seq 5 permit 172.16.255.4/32 +! +route-map r2 permit 10 + match ip address prefix-list p1 + set large-community 65001:1:1 65001:2:1 +route-map r2 permit 20 + match ip address prefix-list p3 + set large-community 65001:3:1 +route-map r2 permit 30 + match ip address prefix-list p4 + set large-community 65001:10:1 65001:12:1 65001:13:1 +exit +route-map r2 permit 40 +exit +! diff --git a/tests/topotests/bgp_large_comm_list_match/r1/zebra.conf b/tests/topotests/bgp_large_comm_list_match/r1/zebra.conf new file mode 100644 index 0000000..4219a7c --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 172.16.255.1/32 + ip address 172.16.255.2/32 + ip address 172.16.255.3/32 + ip address 172.16.255.4/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_large_comm_list_match/r2/bgpd.conf b/tests/topotests/bgp_large_comm_list_match/r2/bgpd.conf new file mode 100644 index 0000000..779b705 --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r2/bgpd.conf @@ -0,0 +1,24 @@ +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as external + neighbor 192.168.0.1 timers 1 3 + neighbor 192.168.0.1 timers connect 1 + neighbor 192.168.1.3 remote-as external + neighbor 192.168.1.3 timers 1 3 + neighbor 192.168.1.3 timers connect 1 + address-family ipv4 + neighbor 192.168.0.1 route-map r1 in + neighbor 192.168.0.1 soft-reconfiguration inbound + exit-address-family +! +bgp large-community-list 1 seq 5 permit 65001:1:1 65001:2:1 +bgp large-community-list 1 seq 10 permit 65001:3:1 +! +route-map r1 deny 10 + match large-community 1 +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_large_comm_list_match/r2/zebra.conf b/tests/topotests/bgp_large_comm_list_match/r2/zebra.conf new file mode 100644 index 0000000..7fe82ba --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_large_comm_list_match/r3/bgpd.conf b/tests/topotests/bgp_large_comm_list_match/r3/bgpd.conf new file mode 100644 index 0000000..e7cb76a --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r3/bgpd.conf @@ -0,0 +1,21 @@ +! +!debug bgp updates +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 + neighbor 192.168.1.2 route-map r1 in + neighbor 192.168.1.2 soft-reconfiguration inbound + exit-address-family +! +bgp large-community-list 2 seq 10 permit 65001:12:1 +! +route-map r1 deny 10 + match large-community 2 any +exit +route-map r1 permit 20 +exit +! diff --git a/tests/topotests/bgp_large_comm_list_match/r3/zebra.conf b/tests/topotests/bgp_large_comm_list_match/r3/zebra.conf new file mode 100644 index 0000000..755dd18 --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.1.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_large_comm_list_match/test_bgp_large_comm_list_match.py b/tests/topotests/bgp_large_comm_list_match/test_bgp_large_comm_list_match.py new file mode 100644 index 0000000..7023e3a --- /dev/null +++ b/tests/topotests/bgp_large_comm_list_match/test_bgp_large_comm_list_match.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright 2023 by 6WIND S.A. +# + +""" +Check if BGP large-community-list works +when used as match rule in incoming route-maps. + +- case 1 should deny incoming updates with large-community-list 1 +bgp large-community-list 1 seq 5 permit 65001:1:1 65001:2:1 +bgp large-community-list 1 seq 10 permit 65001:3:1 +! +route-map r1 deny 10 + match large-community 1 + +route-map test deny 10 + match community 1 + +- case 2 should deny incoming updates with any large-community-list 1 +bgp large-community-list 2 seq 10 permit 65001:12:1 +! +route-map r1 deny 10 + match large-community 2 any +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_large_comm_list_match(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.0.1 filtered-routes json" + ) + ) + expected = { + "receivedRoutes": { + "172.16.255.1/32": { + "path": "65001", + }, + "172.16.255.3/32": { + "path": "65001", + }, + } + } + return topotest.json_cmp(output, expected) + + step("BGP filtering check with large-community-list on R2") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to filter BGP UPDATES with large-community-list on R2" + + +def test_bgp_large_comm_list_match_any(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r3"] + + def _bgp_converge(): + output = json.loads( + router.vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 filtered-routes json" + ) + ) + expected = { + "receivedRoutes": { + "172.16.255.4/32": { + "path": "65002 65001", + }, + } + } + return topotest.json_cmp(output, expected) + + step("BGP filtering check with large-community-list on R3") + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Failed to filter BGP UPDATES with large-community-list on R3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_large_community/__init__.py b/tests/topotests/bgp_large_community/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_large_community/bgp_large_community_topo_1.json b/tests/topotests/bgp_large_community/bgp_large_community_topo_1.json new file mode 100644 index 0000000..902c01b --- /dev/null +++ b/tests/topotests/bgp_large_community/bgp_large_community_topo_1.json @@ -0,0 +1,262 @@ +{ + "ipv4base": "192.168.1.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.1.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + }, + "r3": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + }, + "r3": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "1000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r4": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r4": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "4000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": {} + } + }, + "r5": { + "dest_link": { + "r4-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": {} + } + }, + "r5": { + "dest_link": { + "r4-link1": {} + } + } + } + } + } + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "6000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r5-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r5-link1": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_large_community/bgp_large_community_topo_2.json b/tests/topotests/bgp_large_community/bgp_large_community_topo_2.json new file mode 100644 index 0000000..36dee39 --- /dev/null +++ b/tests/topotests/bgp_large_community/bgp_large_community_topo_2.json @@ -0,0 +1,344 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "1000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "1000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r6": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "4000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r6": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r6": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r6": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "5000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + }, + "r6": { + "dest_link": { + "r5": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + }, + "r6": { + "dest_link": { + "r5": {} + } + } + } + } + } + } + } + }, + "r6": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "6000000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": {} + } + }, + "r5": { + "dest_link": { + "r6": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": {} + } + }, + "r5": { + "dest_link": { + "r6": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py new file mode 100644 index 0000000..892b15a --- /dev/null +++ b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_1.py @@ -0,0 +1,1216 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + + +""" +Following tests are covered to test large-community/community functionality: +1. Verify if large community attribute can be configured only in correct + canonical format. +2. Verify that the community attribute value, which we have advertised are + received in correct format and values, at the receiving end. +3. Verify BGP Large Community attribute"s transitive property attribute. +4. Verify that BGP Large Communities attribute are malformed, if the length of + the BGP Large Communities Attribute value, expressed in octets, + is not a non-zero multiple of 12. +5. Verify if overriding large community values works fine. +6. Verify that large community values" aggregation works fine. +7. Standard community also work fine in conjunction with large-community. +8. Matching prefixes based on attributes other than prefix list and make use + of set clause (IPV6). +9. Matching prefixes based on attributes other than prefix list and make use + of set clause (IPV4). +10. Verify community and large-community list operations in route-map with all + clause (exact, all, any, regex) works. +11. Verify that any value in BGP Large communities for boundary values. +12. Clear BGP neighbor-ship and check if large community and community + attributes are getting re-populated. + +""" + +import pytest +import time +from os import path as os_path +import sys + +# Required to instantiate the topology builder class. +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + create_route_maps, + create_bgp_community_lists, + create_prefix_lists, + verify_bgp_community, + step, + check_address_types, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp_and_verify +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd] + + +# Save the Current Working Directory to find configuration files. +CWD = os_path.dirname(os_path.realpath(__file__)) +sys.path.append(os_path.join(CWD, "../")) +sys.path.append(os_path.join(CWD, "../lib/")) + + +# Global variables +bgp_convergence = False +NETWORK = { + "ipv4": ["200.50.2.0", "200.50.2.1", "200.50.2.0"], + "ipv6": ["1::1", "1::2", "1::0"], +} +MASK = {"ipv4": "32", "ipv6": "128"} +NET_MASK = {"ipv4": "24", "ipv6": "120"} +IPV4_NET = ["200.50.2.0"] +IPV6_NET = ["1::0"] +CONFIG_ROUTER_R1 = False +CONFIG_ROUTER_R2 = False +CONFIG_ROUTER_ADDITIVE = False +ADDR_TYPES = [] +LARGE_COMM = { + "r1": "1:1:1 1:2:1 1:3:1 1:4:1 1:5:1", + "r2": "2:1:1 2:2:1 2:3:1 2:4:1 2:5:1", + "mal_1": "1:1 1:2 1:3 1:4 1:5", + "pf_list_1": "0:0:1 0:0:10 0:0:100", + "pf_list_2": "0:0:2 0:0:20 0:0:200", + "agg_1": "0:0:1 0:0:2 0:0:10 0:0:20 0:0:100 0:0:200 2:1:1 " + "2:2:1 2:3:1 2:4:1 2:5:1", + "agg_2": "0:0:2 0:0:20 0:0:200 2:1:1 " "2:2:1 2:3:1 2:4:1 2:5:1", +} +STANDARD_COMM = { + "r1": "1:1 1:2 1:3 1:4 1:5", + "r2": "2:1 2:2 2:3 2:4 2:5", + "mal_1": "1 2 3 4 5", + "pf_list_1": "0:1 0:10 0:100", + "pf_list_2": "0:2 0:20 0:200", + "agg_1": "0:1 0:2 0:10 0:20 0:100 0:200 2:1 2:2 2:3 2:4 2:5", + "agg_2": "0:2 0:20 0:200 2:1 2:2 2:3 2:4 2:5", +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + global ADDR_TYPES + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_large_community_topo_1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + ##tgen.mininet_cli() + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + + ADDR_TYPES = check_address_types() + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def config_router_r1(tgen, topo, tc_name): + global CONFIG_ROUTER_R1 + + input_dict_1 = { + "r1": { + "route_maps": { + "LC1": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": LARGE_COMM["r1"]}, + "community": {"num": STANDARD_COMM["r1"]}, + }, + } + ] + } + } + } + + step("Configuring LC1 on r1") + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "%s/%s" + % (NETWORK["ipv4"][0], MASK["ipv4"]), + "no_of_network": 4, + } + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "route_maps": [ + {"name": "LC1", "direction": "out"} + ] + } + } + }, + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + {"name": "LC1", "direction": "out"} + ] + } + } + }, + }, + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "%s/%s" + % (NETWORK["ipv6"][0], MASK["ipv6"]), + "no_of_network": 4, + } + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "route_maps": [ + {"name": "LC1", "direction": "out"} + ] + } + } + }, + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + {"name": "LC1", "direction": "out"} + ] + } + } + }, + }, + } + }, + } + } + } + } + + step("Applying LC1 on r1 neighbors and advertising networks") + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + CONFIG_ROUTER_R1 = True + + +def config_router_r2(tgen, topo, tc_name): + global CONFIG_ROUTER_R2 + + input_dict = { + "r2": { + "route_maps": { + "LC2": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": LARGE_COMM["r2"]}, + "community": {"num": STANDARD_COMM["r2"]}, + }, + } + ] + } + } + } + + step("Configuring route-maps LC2 on r2") + result = create_route_maps(tgen, input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_1 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + {"name": "LC2", "direction": "out"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + {"name": "LC2", "direction": "out"} + ] + } + } + } + } + } + }, + } + } + } + } + + step("Applying LC2 on r2 neighbors in out direction") + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + CONFIG_ROUTER_R2 = True + + +def config_router_additive(tgen, topo, tc_name): + global CONFIG_ROUTER_ADDITIVE + + input_dict = { + "r2": { + "route_maps": { + "LC2": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": LARGE_COMM["r2"], + "action": "additive", + }, + "community": { + "num": STANDARD_COMM["r2"], + "action": "additive", + }, + }, + } + ] + } + } + } + + step("Configuring LC2 with community attributes as additive") + result = create_route_maps(tgen, input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + # tgen.mininet_cli() + CONFIG_ROUTER_ADDITIVE = True + + +def config_for_as_path(tgen, topo, tc_name): + config_router_r1(tgen, topo, tc_name) + + config_router_r2(tgen, topo, tc_name) + + # Create ipv6 prefix list + input_dict_1 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "%s/%s" % (NETWORK["ipv4"][0], MASK["ipv4"]), + "action": "permit", + } + ], + "pf_list_2": [ + { + "seqid": "10", + "network": "%s/%s" % (NETWORK["ipv4"][1], MASK["ipv4"]), + "action": "permit", + } + ], + }, + "ipv6": { + "pf_list_3": [ + { + "seqid": "10", + "network": "%s/%s" % (NETWORK["ipv6"][0], MASK["ipv6"]), + "action": "permit", + } + ], + "pf_list_4": [ + { + "seqid": "10", + "network": "%s/%s" % (NETWORK["ipv6"][1], MASK["ipv6"]), + "action": "permit", + } + ], + }, + } + } + } + + step("Configuring prefix-lists on r1 to filter networks") + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = { + "r1": { + "route_maps": { + "LC1": [ + { + "action": "permit", + "seq_id": 10, + "match": {"ipv4": {"prefix_lists": "pf_list_1"}}, + "set": { + "large_community": {"num": LARGE_COMM["pf_list_1"]}, + "community": {"num": STANDARD_COMM["pf_list_1"]}, + }, + }, + { + "action": "permit", + "seq_id": 20, + "match": {"ipv6": {"prefix_lists": "pf_list_3"}}, + "set": { + "large_community": {"num": LARGE_COMM["pf_list_1"]}, + "community": {"num": STANDARD_COMM["pf_list_1"]}, + }, + }, + { + "action": "permit", + "seq_id": 30, + "match": {"ipv4": {"prefix_lists": "pf_list_2"}}, + "set": { + "large_community": {"num": LARGE_COMM["pf_list_2"]}, + "community": {"num": STANDARD_COMM["pf_list_2"]}, + }, + }, + { + "action": "permit", + "seq_id": 40, + "match": {"ipv6": {"prefix_lists": "pf_list_4"}}, + "set": { + "large_community": {"num": LARGE_COMM["pf_list_2"]}, + "community": {"num": STANDARD_COMM["pf_list_2"]}, + }, + }, + ] + } + } + } + + step( + "Applying prefix-lists match in route-map LC1 on r1. Setting" + " community attritbute for filtered networks" + ) + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + config_router_additive(tgen, topo, tc_name) + + input_dict_3 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": LARGE_COMM["pf_list_1"], + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": STANDARD_COMM["pf_list_1"], + }, + ] + } + } + + step("Configuring bgp community lists on r4") + result = create_bgp_community_lists(tgen, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_4 = { + "r4": { + "route_maps": { + "LC4": [ + { + "action": "permit", + "seq_id": "10", + "match": { + "large_community_list": {"id": "ANY"}, + "community_list": {"id": "ANY"}, + }, + "set": {"path": {"as_num": "4000000", "as_action": "prepend"}}, + } + ] + } + } + } + + step("Applying community list on route-map on r4") + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_5 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r4-link1": { + "route_maps": [ + {"name": "LC4", "direction": "out"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r4-link1": { + "route_maps": [ + {"name": "LC4", "direction": "out"} + ] + } + } + } + } + } + }, + } + } + } + } + + step("Applying route-map LC4 out from r4 to r5 ") + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + +##################################################### +# +# Test cases +# +##################################################### +def test_large_community_set(request): + """ + Verify if large community attribute can be configured only in correct + canonical format. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # API call to modify router id + # input_dict dictionary to be provided to configure route_map + input_dict = { + "r1": { + "route_maps": { + "LC1": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": LARGE_COMM["r1"]}, + "community": {"num": STANDARD_COMM["r1"]}, + }, + } + ] + } + } + } + + step("Trying to set bgp communities") + result = create_route_maps(tgen, input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_large_community_advertise(request): + """ + Verify that the community attribute value, which we have advertised are + received in correct format and values, at the receiving end. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_router_r1(tgen, topo, tc_name) + + input_dict = { + "largeCommunity": LARGE_COMM["r1"], + "community": STANDARD_COMM["r1"], + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r2", [NETWORK[adt][0]], input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_community(tgen, adt, "r3", [NETWORK[adt][0]], input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_transitive(request): + """ + Verify BGP Large Community attribute"s transitive property attribute. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + config_router_r1(tgen, topo, tc_name) + + input_dict_1 = { + "largeCommunity": LARGE_COMM["r1"], + "community": STANDARD_COMM["r1"], + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r4", [NETWORK[adt][0]], input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_override(request): + """ + Verify if overriding large community values works fine. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_router_r1(tgen, topo, tc_name) + + config_router_r2(tgen, topo, tc_name) + + input_dict_3 = { + "largeCommunity": LARGE_COMM["r2"], + "community": STANDARD_COMM["r2"], + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r4", [NETWORK[adt][1]], input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_additive(request): + """ + Verify that large community values" aggregation works fine. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_router_r1(tgen, topo, tc_name) + + config_router_r2(tgen, topo, tc_name) + + config_router_additive(tgen, topo, tc_name) + + input_dict_1 = { + "largeCommunity": "%s %s" % (LARGE_COMM["r1"], LARGE_COMM["r2"]), + "community": "%s %s" % (STANDARD_COMM["r1"], STANDARD_COMM["r2"]), + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r4", [NETWORK[adt][0]], input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_match_as_path(request): + """ + Matching prefixes based on attributes other than prefix list and make use + of set clause. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_for_as_path(tgen, topo, tc_name) + + input_dict = { + "largeCommunity": "%s %s" % (LARGE_COMM["pf_list_1"], LARGE_COMM["r2"]), + "community": "%s %s" % (STANDARD_COMM["pf_list_1"], STANDARD_COMM["r2"]), + } + + input_dict_1 = { + "largeCommunity": "%s %s" % (LARGE_COMM["pf_list_2"], LARGE_COMM["r2"]), + "community": "%s %s" % (STANDARD_COMM["pf_list_2"], STANDARD_COMM["r2"]), + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r5", [NETWORK[adt][0]], input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_community( + tgen, adt, "r5", [NETWORK[adt][1]], input_dict_1, expected=False + ) + + assert result is not True, "Test case {} : Should fail \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_match_all(request): + """ + Verify community and large-community list operations in route-map with all + clause (exact, all, any, regex) works. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_router_r1(tgen, topo, tc_name) + + config_router_r2(tgen, topo, tc_name) + + config_router_additive(tgen, topo, tc_name) + + input_dict_1 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "1:1:1", + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "ALL", + "value": "1:1:1 1:2:1 1:3:1 1:4:1 1:5:1 2:1:1 2:2:1", + "large": True, + }, + { + "community_type": "expanded", + "action": "permit", + "name": "EXP_ALL", + "value": "1:1:1 1:2:1 1:3:1 1:4:1 1:5:1 2:[1-5]:1", + "large": True, + }, + ] + } + } + + step("Create bgp community lists for ANY, EXACT and EXP_ALL match") + + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = { + "r4": { + "route_maps": { + "LC4": [ + { + "action": "permit", + "seq_id": "10", + "match": {"large-community-list": {"id": "ANY"}}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"large-community-list": {"id": "EXACT"}}, + }, + { + "action": "permit", + "seq_id": "30", + "match": {"large-community-list": {"id": "EXP_ALL"}}, + }, + ] + } + } + } + + step("Applying bgp community lits on LC4 route-map") + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r4-link1": { + "route_maps": [ + {"name": "LC4", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r4-link1": { + "route_maps": [ + {"name": "LC4", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + } + } + + step("Apply route-mpa LC4 on r4 for r2 neighbor, direction 'in'") + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_4 = { + "largeCommunity": "1:1:1 1:2:1 1:3:1 1:4:1 1:5:1 2:1:1 2:2:1 2:3:1 " + "2:4:1 2:5:1" + } + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r4", [NETWORK[adt][0]], input_dict_4) + assert result is True, "Test case {} : Should fail \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +# @pytest.mark.skip(reason="as-set not working for ipv6") +def test_large_community_aggregate_network(request): + """ + Restart router and check if large community and community + attributes are getting re-populated. + """ + + tc_name = request.node.name + write_test_header(tc_name) + + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + config_for_as_path(tgen, topo, tc_name) + + input_dict = { + "community": STANDARD_COMM["agg_1"], + "largeCommunity": LARGE_COMM["agg_1"], + } + + input_dict_1 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "aggregate_address": [ + { + "network": "%s/%s" + % (NETWORK["ipv4"][2], NET_MASK["ipv4"]), + "as_set": True, + } + ] + } + }, + "ipv6": { + "unicast": { + "aggregate_address": [ + { + "network": "%s/%s" + % (NETWORK["ipv6"][2], NET_MASK["ipv6"]), + "as_set": True, + } + ] + } + }, + } + } + } + } + + step("Configuring aggregate address as-set on r2") + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + for adt in ADDR_TYPES: + result = verify_bgp_community( + tgen, adt, "r4", ["%s/%s" % (NETWORK[adt][2], NET_MASK[adt])], input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": "%s/%s" + % (NETWORK["ipv4"][0], MASK["ipv4"]), + "no_of_network": 1, + "delete": True, + } + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": "%s/%s" + % (NETWORK["ipv6"][0], MASK["ipv6"]), + "no_of_network": 1, + "delete": True, + } + ] + } + }, + } + } + } + } + + step("Stop advertising one of the networks") + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Test case {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "community": STANDARD_COMM["agg_2"], + "largeCommunity": LARGE_COMM["agg_2"], + } + + for adt in ADDR_TYPES: + step("Verifying bgp community values on r5 is also modified") + result = verify_bgp_community( + tgen, adt, "r4", ["%s/%s" % (NETWORK[adt][2], NET_MASK[adt])], input_dict_3 + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_boundary_values(request): + """ + Verify that any value in BGP Large communities for boundary values. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "0:-1", + } + ] + } + } + + step("Checking boundary value for community 0:-1") + result = create_bgp_community_lists(tgen, input_dict) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Checking community attribute 0:65536") + input_dict_2 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "0:65536", + } + ] + } + } + + step("Checking boundary value for community 0:65536") + result = create_bgp_community_lists(tgen, input_dict_2) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Checking boundary value for community 0:4294967296") + input_dict_3 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "0:4294967296", + "large": True, + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_3) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Checking boundary value for community 0:-1:1") + + input_dict_4 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "0:-1:1", + "large": True, + } + ] + } + } + + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + +def test_large_community_invalid_chars(request): + """ + BGP canonical lcommunities must only be digits + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "1:a:2", + "large": True, + } + ] + } + } + + step("Checking boundary value for community 1:a:2") + result = create_bgp_community_lists(tgen, input_dict) + assert result is not True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + +def test_large_community_after_clear_bgp(request): + """ + Clear BGP neighbor-ship and check if large community and community + attributes are getting re-populated. + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + config_router_r1(tgen, topo, tc_name) + + input_dict = {"largeCommunity": LARGE_COMM["r1"], "community": STANDARD_COMM["r1"]} + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r2", [NETWORK[adt][0]], input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Clearing BGP on r1") + clear_bgp_and_verify(tgen, topo, "r1") + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, "r2", [NETWORK[adt][0]], input_dict) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_large_community/test_bgp_large_community_topo_2.py b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_2.py new file mode 100644 index 0000000..0044dbb --- /dev/null +++ b/tests/topotests/bgp_large_community/test_bgp_large_community_topo_2.py @@ -0,0 +1,2220 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +test_bgp_large_community_topo_1.py: Test BGP large community. + +Following tests are covered: +1. Verify the standard large-community-lists can permit or deny + large community attribute only in the correct canonical format. +2. Verify the expanded large-community-lists can permit or deny + large community attribute both in the correct canonical format + as well as REG_EX. +3. Verify that we can modify a large-community-list is in use, + to add/remove attribute value and it takes immediate effect. +4. Verify that large community attribute gets advertised when + route-map is applied to a neighbor and cleared when route-map + is removed. +5. Verify that duplicate BGP Large Community values are NOT be transmitted. +6. Verify if we want to remove all the large-community attributes from a + set of prefix we can set the value as NONE. +7. Redistribute connected and static routes in BGP process with a route-map + appending/removing L-comm attributes. +8. Verify if we want to remove specific large-community values from + a set of prefix we can make use of DELETE operation based on L-comm list. +9. Verify that if community values are NOT be advertised to a specific + neighbour, we negate send-community command. + (Send-community all is enabled by default for all neighbors) +10. Verify that large-community lists can not be configured without providing + specific L-community values(for match/delete operation in a route-map). +11. Verify that Match_EXACT clause should pass only if all of the L-comm + values configured (horizontally) in the community list is present in + the prefix. There must be no additional L-communities in the prefix. +12. Verify that Match_ALL clause should pass only if ALL of the L-comm values + configured (horizontally) in the community list is present in the prefix. + There could be additional L-communities in the prefix that are not present + in the L-comm list. +13. Verify that Match_ANY clause should pass only if at-least any one L-comm + value configured(vertically) in large-community list, is present in prefixes. +14. Verify large-community lists operation in a route-map with match RegEx + statements. +""" + +import os +import sys +import pytest +import time + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +# Import topoJson from lib, to create topology and initial configuration +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + create_route_maps, + create_bgp_community_lists, + verify_bgp_community, + step, + verify_create_community_list, + delete_route_maps, + verify_route_maps, + create_static_routes, + check_address_types, + required_linux_kernel_version, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp_and_verify +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +bgp_convergence = False + +NETWORKS = {"ipv4": ["200.50.2.0/32"], "ipv6": ["1::1/128"]} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_large_community_topo_2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence, ADDR_TYPES + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + # Ipv4 + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + ADDR_TYPES = check_address_types() + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_create_bgp_standard_large_community_list(request): + """ + Create standard large-community-list and verify it can permit + or deny large community attribute only in the correct canonical + format. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + reset_config_on_routers(tgen) + + step("Create srtandard large community list") + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "LC_1_STD", + "value": "2:1:1 2:1:2 1:2:3", + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "LC_2_STD", + "value": "3:1:1 3:1:2", + "large": True, + }, + ] + } + } + result = create_bgp_community_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create srtandard large community list with in-correct values") + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "LC_1_STD_ERR", + "value": "0:0:0", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + ## TODO should fail + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_create_bgp_expanded_large_community_list(request): + """ + Create expanded large-community-list and verify it can permit + or deny large community attribute both in the correct canonical + format as well as REG_EX + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create expanded large community list") + input_dict = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "LC_1_EXP", + "value": "1:1:200 1:2:* 3:2:1", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_modify_large_community_lists_referenced_by_rmap(request): + """ + This test is to verify that we can modify a large-community-list + is in use, add/remove attribute value and it takes immediate effect. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create standard large community list") + input_dict_1 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "LC_DEL", + "value": "1:2:1 1:3:1 2:1:1 2:2:2 3:3:3", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_2 = { + "r1": { + "route_maps": { + "RM_R2_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "1:2:1 1:3:1 2:10:1 3:3:3 4:4:4 5:5:5", + "action": "additive", + } + }, + } + ] + } + }, + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": {"large_comm_list": {"id": "LC_DEL", "delete": True}}, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RM_R2_OUT", + "direction": "out", + } + ] + } + } + } + }, + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [{"network": "1::1/128"}], + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RM_R2_OUT", + "direction": "out", + } + ] + } + } + } + }, + } + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify Community-list") + dut = "r4" + input_dict_4 = {"largeCommunity": "2:10:1 4:4:4 5:5:5"} + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_apply_and_remove(request): + """ + This test is to verify that large community attribute gets advertised when + route-map is applied to a neighbor and cleared when route-map is removed + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_1 = { + "r4": { + "route_maps": { + "RM_LC1": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "200:200:1 200:200:10 200:200:20000", + "action": "additive", + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_LC1", "direction": "out"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_LC1", "direction": "out"} + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r6" + input_dict_4 = {"largeCommunity": "200:200:1 200:200:10 200:200:20000"} + + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete route map reference by community-list") + input_dict_3 = {"r4": {"route_maps": ["RM_LC1"]}} + result = delete_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify route map is deleted") + result = verify_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + for adt in ADDR_TYPES: + result = verify_bgp_community( + tgen, adt, dut, NETWORKS[adt], input_dict_4, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "largeCommunity is still present after deleting route-map \n Error: {}".format( + tc_name, result + ) + ) + + write_test_footer(tc_name) + + +def test_duplicate_large_community_list_attributes_not_transitive(request): + """ + This test is to verify that duplicate BGP Large Community values + are NOT be transmitted. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_1 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "0:0:1 0:0:10 0:0:100 2:0:1 2:0:2 2:0:3" + " 2:0:4 2:0:5", + "action": "additive", + } + }, + } + ], + "RM_R4_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "0:0:1 0:0:10 0:0:10000 2:0:1 2:0:2", + "action": "additive", + } + }, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + }, + "r6": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + }, + "r6": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r6" + input_dict_4 = { + "largeCommunity": "0:0:1 0:0:10 0:0:100 0:0:10000 2:0:1 2:0:2 2:0:3 2:0:4 2:0:5" + } + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_set_none(request): + """ + This test is to verify if we want to remove all the large-community + attributes from a set of prefix we can set the value as NONE. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_1 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "0:0:1 0:0:10 0:0:100 2:0:1 2:0:2 2:0:3" + " 2:0:4", + "action": "additive", + } + }, + } + ] + } + }, + "r6": { + "route_maps": { + "RM_R6_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": {"large_community": {"num": "none"}}, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + }, + "r6": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": { + "route_maps": [ + {"name": "RM_R6_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": { + "route_maps": [ + {"name": "RM_R6_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify Community-list") + dut = "r6" + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Community-list is still present \n Error: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_lcomm_lists_with_redistribute_static_connected_rmap(request): + """ + This test is to verify redistribute connected and static ipv4 routes + in BGP process with a route-map appending/removing L-comm attributes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("create static routes") + input_dict = { + "r1": { + "static_routes": [ + {"network": "200.50.2.0/32", "next_hop": "10.0.0.6"}, + {"network": "1::1/128", "next_hop": "fd00:0:0:1::2"}, + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("redistribute static routes") + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": "route-map RM_R2_OUT", + }, + { + "redist_type": "connected", + "attribute": "route-map RM_R2_OUT", + }, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": "route-map RM_R2_OUT", + }, + { + "redist_type": "connected", + "attribute": "route-map RM_R2_OUT", + }, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_3 = { + "r1": { + "route_maps": { + "RM_R2_OUT": [ + { + "action": "permit", + "set": {"large_community": {"num": "55:55:55 555:555:555"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list for static and connected ipv4 route on" " r2") + + input_dict_5 = {"largeCommunity": "55:55:55 555:555:555"} + + if "ipv4" in ADDR_TYPES: + dut = "r2" + networks = ["200.50.2.0/32", "1.0.1.17/32"] + result = verify_bgp_community(tgen, "ipv4", dut, networks, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify large-community-list for static and connected ipv4 route" " on r4") + dut = "r4" + networks = ["200.50.2.0/32", "1.0.1.17/32"] + result = verify_bgp_community(tgen, "ipv4", dut, networks, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + if "ipv6" in ADDR_TYPES: + step("Verify large-community-list for static and connected ipv6 route" " on r2") + dut = "r2" + networks = ["1::1/128", "2001:db8:f::1:17/128"] + result = verify_bgp_community(tgen, "ipv6", dut, networks, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify large-community-list for static and connected ipv6 route" " on r4") + dut = "r4" + networks = ["1::1/128", "2001:db8:f::1:17/128"] + result = verify_bgp_community(tgen, "ipv6", dut, networks, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_set_delete(request): + """ + This test is to verify if we want to remove specific large-community + values from a set of prefix we can make use of DELETE operation based + on L-comm list + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("configure route_map") + input_dict_2 = { + "r6": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "Test", + "value": "1:2:1 1:1:10 1:3:100", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_3 = { + "r6": { + "route_maps": { + "RM_R6_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": {"large_comm_list": {"id": "Test", "delete": True}}, + } + ] + } + }, + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "1:2:1 1:1:10 1:3:100 2:1:1 2:2:2 2:3:3" + " 2:4:4 2:5:5", + "action": "additive", + } + }, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + }, + "r6": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": { + "route_maps": [ + {"name": "RM_R6_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": { + "route_maps": [ + {"name": "RM_R6_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r6" + input_dict_5 = {"largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_no_send_community(request): + """ + This test is to verify if we want to remove specific large-community + values from a set of prefix we can make use of DELETE operation based + on L-comm list + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_2 = { + "r5": { + "route_maps": { + "RM_R6_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r5": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r5": { + "route_maps": [ + { + "name": "RM_R6_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": { + "r5": { + "route_maps": [ + { + "name": "RM_R6_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r6" + input_dict_4 = {"largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for no-send-community") + input_dict_5 = { + "r5": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": {"r5": {"no_send_community": "large"}} + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r6": { + "dest_link": {"r5": {"no_send_community": "large"}} + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify Community-list") + for adt in ADDR_TYPES: + result = verify_bgp_community( + tgen, adt, dut, NETWORKS[adt], input_dict_4, expected=False + ) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_create_large_community_lists_with_no_attribute_values(request): + """ + This test is to verify that large-community lists can not be + configured without providing specific L-community values + (for match/delete operation in a route-map). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create standard large commumity-list") + input_dict_1 = { + "r5": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "Test1", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_match_exact(request): + """ + This test is to verify that Match_EXACT clause should pass + only if all of the L-comm values configured (horizontally) + in the community list is present in the prefix. There must + be no additional L-communities in the prefix. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_2 = { + "r2": { + "route_maps": { + "RM_R4_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map and advertise networks") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create standard large commumity-list") + input_dict_4 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "EXACT", + "value": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_5 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "match": { + "large-community-list": ["EXACT"], + "match_exact": True, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_6 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r4" + input_dict_4 = {"largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_match_all(request): + """ + This test is to verify that Match_ALL clause should pass + only if ALL of the L-comm values configured (horizontally) + in the community list are present in the prefix. There + could be additional L-communities in the prefix that are + not present in the L-comm list. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_2 = { + "r2": { + "route_maps": { + "RM_R4_OUT": [ + { + "action": "permit", + "set": { + "large_community": { + "num": "1:1:1 1:2:3 2:1:1 2:2:2 2:3:3 2:4:4 2:5:5" + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create standard large commumity-list") + input_dict_4 = { + "r3": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ALL", + "value": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_5 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "match": {"large-community-list": {"id": "ALL"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_6 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r4" + input_dict_4 = {"largeCommunity": "1:1:1 1:2:3 2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_match_any(request): + """ + This test is to verify that Match_ANY clause should pass + only if at-least any one L-comm value configured(vertically) + in large-community list, is present in prefixes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_2 = { + "r2": { + "route_maps": { + "RM_R4_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": {"num": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create standard large commumity-list") + input_dict_4 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "2:1:1", + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "2:2:1", + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "2:3:1", + "large": True, + }, + { + "community_type": "standard", + "action": "permit", + "name": "ANY", + "value": "2:4:1", + "large": True, + }, + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_5 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "match": {"large-community-list": {"id": "ANY"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_6 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r4" + input_dict_7 = {"largeCommunity": "2:1:1 2:2:2 2:3:3 2:4:4 2:5:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_large_community_lists_with_rmap_match_regex(request): + """ + This test is to verify large-community lists" operation in a route-map + with match RegEx statements. Match clause should pass only if the + complete string of L-comm values are matched + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + step("Create route map") + input_dict_2 = { + "r2": { + "route_maps": { + "RM_R4_OUT": [ + { + "action": "permit", + "seq_id": "10", + "set": { + "large_community": { + "num": "1:1:1 1:1:2 2:1:3 2:1:4 2:1:5", + }, + "community": {"num": "1:1 1:2 1:3 1:4 1:5"}, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": "200.50.2.0/32"}] + } + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "RM_R4_OUT", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create standard large commumity-list") + input_dict_4 = { + "r4": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "ALL", + "value": "1:1:1 2:1:3 2:1:4 2:1:5", + "large": True, + }, + { + "community_type": "expanded", + "action": "permit", + "name": "EXP_ALL", + "value": "1:1:1 2:1:[3-5]", + "large": True, + }, + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP large community is created") + result = verify_create_community_list(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_5 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "10", + "match": { + "large_community_list": { + "id": "ALL", + }, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map") + input_dict_6 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [ + {"name": "RM_R4_IN", "direction": "in"} + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r4" + input_dict_7 = {"largeCommunity": "1:1:1 1:1:2 2:1:3 2:1:4 2:1:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community(tgen, adt, dut, NETWORKS[adt], input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete route map reference by community-list") + input_dict_3 = {"r4": {"route_maps": ["RM_R4_IN"]}} + result = delete_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create route map") + input_dict_5 = { + "r4": { + "route_maps": { + "RM_R4_IN": [ + { + "action": "permit", + "seq_id": "20", + "match": { + "large_community_list": { + "id": "EXP_ALL", + }, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("clear ip bgp") + result = clear_bgp_and_verify(tgen, topo, "r4") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify large-community-list") + dut = "r4" + input_dict_7 = {"largeCommunity": "1:1:1 1:1:2 2:1:3 2:1:4 2:1:5"} + for adt in ADDR_TYPES: + result = verify_bgp_community( + tgen, adt, dut, NETWORKS[adt], input_dict_7, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "largeCommunity is still present \n Error: {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_link_bw_ip/__init__.py b/tests/topotests/bgp_link_bw_ip/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgp-route-1.json b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-1.json new file mode 100644 index 0000000..3e3c35e --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-1.json @@ -0,0 +1,19 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65301:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgp-route-2.json b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-2.json new file mode 100644 index 0000000..f07e89b --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-2.json @@ -0,0 +1,32 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "extendedCommunity":{ + "string":"LB:65303:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65201:375000 (3.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgp-route-3.json b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-3.json new file mode 100644 index 0000000..3501d12 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-3.json @@ -0,0 +1,32 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "extendedCommunity":{ + "string":"LB:65303:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65301:250000 (2.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgp-route-4.json b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-4.json new file mode 100644 index 0000000..b1ed004 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-4.json @@ -0,0 +1,32 @@ +{ + "prefix":"198.10.1.11\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "extendedCommunity":{ + "string":"LB:65303:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65201:250000 (2.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgp-route-5.json b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-5.json new file mode 100644 index 0000000..89469b8 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgp-route-5.json @@ -0,0 +1,29 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "nexthops":[ + { + "ip":"11.1.1.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65201:375000 (3.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.1.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r1/bgpd.conf new file mode 100644 index 0000000..b1ec70d --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/bgpd.conf @@ -0,0 +1,12 @@ +hostname r1 +! +router bgp 65101 + bgp router-id 11.1.1.1 + no bgp ebgp-requires-policy + bgp bestpath as-path multipath-relax + neighbor 11.1.1.2 remote-as external + neighbor 11.1.1.2 timers 3 10 + neighbor 11.1.1.6 remote-as external + neighbor 11.1.1.6 timers 3 10 + neighbor 11.1.1.6 disable-link-bw-encoding-ieee +! diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-1.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-1.json new file mode 100644 index 0000000..bb50bc9 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-1.json @@ -0,0 +1,20 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.6", + "weight":85 + }, + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-2.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-2.json new file mode 100644 index 0000000..57726ce --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-2.json @@ -0,0 +1,20 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.6", + "weight":127 + }, + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-3.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-3.json new file mode 100644 index 0000000..f2368df --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-3.json @@ -0,0 +1,20 @@ +{ + "198.10.1.11\/32":[ + { + "prefix":"198.10.1.11\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.6", + "weight":127 + }, + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-4.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-4.json new file mode 100644 index 0000000..6b757ef --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-4.json @@ -0,0 +1,20 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.2", + "weight":1 + }, + { + "fib":true, + "ip":"11.1.1.6", + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-5.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-5.json new file mode 100644 index 0000000..641ecab --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-5.json @@ -0,0 +1,20 @@ +{ + "198.10.1.11\/32":[ + { + "prefix":"198.10.1.11\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.2", + "weight":1 + }, + { + "fib":true, + "ip":"11.1.1.6", + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-6.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-6.json new file mode 100644 index 0000000..7b4da2a --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-6.json @@ -0,0 +1,15 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-7.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-7.json new file mode 100644 index 0000000..3062d1c --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-7.json @@ -0,0 +1,15 @@ +{ + "198.10.1.11\/32":[ + { + "prefix":"198.10.1.11\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-8.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-8.json new file mode 100644 index 0000000..662b7f7 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-8.json @@ -0,0 +1,20 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.6", + "weight":1 + }, + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/ip-route-9.json b/tests/topotests/bgp_link_bw_ip/r1/ip-route-9.json new file mode 100644 index 0000000..d9b5a89 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/ip-route-9.json @@ -0,0 +1,20 @@ +{ + "198.10.1.11\/32":[ + { + "prefix":"198.10.1.11\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.1.6", + "weight":1 + }, + { + "fib":true, + "ip":"11.1.1.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/v4_route.json b/tests/topotests/bgp_link_bw_ip/r1/v4_route.json new file mode 100644 index 0000000..76c6396 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/v4_route.json @@ -0,0 +1,84 @@ +{ + "10.0.1.1\/32":[ + { + "prefix":"10.0.1.1\/32", + "protocol":"ospf", + "distance":110, + "metric":10, + "table":254, + "nexthops":[ + { + "flags":9, + "ip":"0.0.0.0", + "afi":"ipv4", + "interfaceName":"r1-eth0", + "active":true, + "onLink":true + } + ] + }, + { + "prefix":"10.0.1.1\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + } + ], + "10.0.3.4\/32":[ + { + "prefix":"10.0.3.4\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "10.0.20.1\/32":[ + { + "prefix":"10.0.20.1\/32", + "protocol":"ospf", + "selected":true, + "destSelected":true, + "distance":110, + "metric":20, + "installed":true, + "table":254, + "nexthops":[ + { + "flags":11, + "fib":true, + "ip":"10.0.3.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true, + "onLink":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r1/zebra.conf b/tests/topotests/bgp_link_bw_ip/r1/zebra.conf new file mode 100644 index 0000000..0fc81f9 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface r1-eth0 + ip address 11.1.1.1/30 +! +interface r1-eth1 + ip address 11.1.1.5/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r10/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r10/bgpd.conf new file mode 100644 index 0000000..022270f --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r10/bgpd.conf @@ -0,0 +1,17 @@ +hostname r10 +! +ip prefix-list redist seq 10 permit 0.0.0.0/0 ge 32 +! +route-map redist permit 10 + match ip address prefix-list redist +! +router bgp 65354 + bgp router-id 11.1.6.2 + no bgp ebgp-requires-policy + neighbor 11.1.6.1 remote-as external + neighbor 11.1.6.1 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected route-map redist + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r10/zebra.conf b/tests/topotests/bgp_link_bw_ip/r10/zebra.conf new file mode 100644 index 0000000..1a24fda --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r10/zebra.conf @@ -0,0 +1,6 @@ +interface r10-eth0 + ip address 11.1.6.2/30 +! +interface r10-eth1 + ip address 50.1.1.10/32 +! diff --git a/tests/topotests/bgp_link_bw_ip/r2/bgp-route-1.json b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-1.json new file mode 100644 index 0000000..3c38689 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-1.json @@ -0,0 +1,19 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65301:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.2.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/bgp-route-2.json b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-2.json new file mode 100644 index 0000000..1895cd8 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-2.json @@ -0,0 +1,19 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65301:250000 (2.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.2.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/bgp-route-3.json b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-3.json new file mode 100644 index 0000000..6289a2e --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/bgp-route-3.json @@ -0,0 +1,32 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "extendedIpv6Community":{ + "string":"LB:65302:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.2.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"LB:65301:250000 (2.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.2.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r2/bgpd.conf new file mode 100644 index 0000000..0c0e859 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/bgpd.conf @@ -0,0 +1,13 @@ +hostname r2 +! +router bgp 65201 + bgp router-id 11.1.2.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 11.1.1.1 remote-as external + neighbor 11.1.1.1 timers 3 10 + neighbor 11.1.2.2 remote-as external + neighbor 11.1.2.2 timers 3 10 + neighbor 11.1.2.6 remote-as external + neighbor 11.1.2.6 timers 3 10 +! diff --git a/tests/topotests/bgp_link_bw_ip/r2/ip-route-1.json b/tests/topotests/bgp_link_bw_ip/r2/ip-route-1.json new file mode 100644 index 0000000..131100a --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/ip-route-1.json @@ -0,0 +1,19 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "protocol":"bgp", + "selected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.2.2", + "interfaceName":"r2-eth1", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/ip-route-2.json b/tests/topotests/bgp_link_bw_ip/r2/ip-route-2.json new file mode 100644 index 0000000..53be117 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/ip-route-2.json @@ -0,0 +1,20 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.2.6", + "weight":127 + }, + { + "fib":true, + "ip":"11.1.2.2", + "weight":255 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/ip-route-3.json b/tests/topotests/bgp_link_bw_ip/r2/ip-route-3.json new file mode 100644 index 0000000..d0509bb --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/ip-route-3.json @@ -0,0 +1,15 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.2.2", + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r2/zebra.conf b/tests/topotests/bgp_link_bw_ip/r2/zebra.conf new file mode 100644 index 0000000..23573a1 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r2/zebra.conf @@ -0,0 +1,10 @@ +! +interface r2-eth0 + ip address 11.1.1.2/30 +! +interface r2-eth1 + ip address 11.1.2.1/30 +! +interface r2-eth2 + ip address 11.1.2.5/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r3/bgp-route-1.json b/tests/topotests/bgp_link_bw_ip/r3/bgp-route-1.json new file mode 100644 index 0000000..cddf127 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r3/bgp-route-1.json @@ -0,0 +1,29 @@ +{ + "prefix":"198.10.1.1/32", + "paths":[ + { + "aspath":{ + "string":"65303 65354", + "segments":[ + { + "type":"as-sequence", + "list":[ + 65303, + 65354 + ] + } + ], + "length":2 + }, + "valid":true, + "extendedCommunity":{ + "string":"LB:65303:125000 (1.000 Mbps)" + }, + "nexthops":[ + { + "ip":"11.1.3.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r3/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r3/bgpd.conf new file mode 100644 index 0000000..cfd3949 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r3/bgpd.conf @@ -0,0 +1,12 @@ +hostname r3 +! +router bgp 65202 + bgp router-id 11.1.3.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 11.1.1.5 remote-as external + neighbor 11.1.1.5 timers 3 10 + neighbor 11.1.3.2 remote-as external + neighbor 11.1.3.2 timers 3 10 + neighbor 11.1.3.2 disable-link-bw-encoding-ieee +! diff --git a/tests/topotests/bgp_link_bw_ip/r3/zebra.conf b/tests/topotests/bgp_link_bw_ip/r3/zebra.conf new file mode 100644 index 0000000..d667669 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r3/zebra.conf @@ -0,0 +1,7 @@ +! +interface r3-eth0 + ip address 11.1.1.6/30 +! +interface r3-eth1 + ip address 11.1.3.1/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r4/bgp-route-1.json b/tests/topotests/bgp_link_bw_ip/r4/bgp-route-1.json new file mode 100644 index 0000000..87d1ae0 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r4/bgp-route-1.json @@ -0,0 +1,23 @@ +{ + "prefix":"198.10.1.1\/32", + "paths":[ + { + "valid":true, + "multipath":true, + "nexthops":[ + { + "ip":"11.1.4.6" + } + ] + }, + { + "valid":true, + "multipath":true, + "nexthops":[ + { + "ip":"11.1.4.2" + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r4/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r4/bgpd.conf new file mode 100644 index 0000000..88b260f --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r4/bgpd.conf @@ -0,0 +1,32 @@ +! +log file bgpd.log +! +! debug bgp updates +! debug bgp zebra +! debug bgp bestpath 198.10.1.1/32 +! +hostname r4 +! +ip prefix-list anycast_ip seq 10 permit 198.10.1.0/24 le 32 +! +route-map anycast_ip permit 10 + match ip address prefix-list anycast_ip + set extcommunity bandwidth num-multipaths +! +route-map anycast_ip permit 20 +! +router bgp 65301 + bgp router-id 11.1.4.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 11.1.2.1 remote-as external + neighbor 11.1.2.1 timers 3 10 + neighbor 11.1.4.2 remote-as external + neighbor 11.1.4.2 timers 3 10 + neighbor 11.1.4.6 remote-as external + neighbor 11.1.4.6 timers 3 10 + ! + address-family ipv4 unicast + neighbor 11.1.2.1 route-map anycast_ip out + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r4/ip-route-1.json b/tests/topotests/bgp_link_bw_ip/r4/ip-route-1.json new file mode 100644 index 0000000..a9ccf07 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r4/ip-route-1.json @@ -0,0 +1,21 @@ +{ + "198.10.1.1\/32":[ + { + "prefix":"198.10.1.1\/32", + "protocol":"bgp", + "selected":true, + "nexthops":[ + { + "fib":true, + "ip":"11.1.4.2", + "weight":1 + }, + { + "fib":true, + "ip":"11.1.4.6", + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_link_bw_ip/r4/zebra.conf b/tests/topotests/bgp_link_bw_ip/r4/zebra.conf new file mode 100644 index 0000000..ef61f7e --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r4/zebra.conf @@ -0,0 +1,10 @@ +! +interface r4-eth0 + ip address 11.1.2.2/30 +! +interface r4-eth1 + ip address 11.1.4.1/30 +! +interface r4-eth2 + ip address 11.1.4.5/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r5/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r5/bgpd.conf new file mode 100644 index 0000000..e4ed92d --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r5/bgpd.conf @@ -0,0 +1,24 @@ +hostname r5 +! +ip prefix-list anycast_ip seq 10 permit 198.10.1.0/24 le 32 +! +route-map anycast_ip permit 10 + match ip address prefix-list anycast_ip + set extcommunity bandwidth num-multipaths +! +route-map anycast_ip permit 20 +! +router bgp 65302 + bgp router-id 11.1.5.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 11.1.2.5 remote-as external + neighbor 11.1.2.5 extended-link-bandwidth + neighbor 11.1.2.5 timers 3 10 + neighbor 11.1.5.2 remote-as external + neighbor 11.1.5.2 timers 3 10 + ! + address-family ipv4 unicast + neighbor 11.1.2.5 route-map anycast_ip out + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r5/zebra.conf b/tests/topotests/bgp_link_bw_ip/r5/zebra.conf new file mode 100644 index 0000000..66c6596 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r5/zebra.conf @@ -0,0 +1,7 @@ +! +interface r5-eth0 + ip address 11.1.2.6/30 +! +interface r5-eth1 + ip address 11.1.5.1/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r6/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r6/bgpd.conf new file mode 100644 index 0000000..89de8ee --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r6/bgpd.conf @@ -0,0 +1,24 @@ +hostname r6 +! +ip prefix-list anycast_ip seq 10 permit 198.10.1.0/24 le 32 +! +route-map anycast_ip permit 10 + match ip address prefix-list anycast_ip + set extcommunity bandwidth num-multipaths +! +route-map anycast_ip permit 20 +! +router bgp 65303 + bgp router-id 11.1.6.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 11.1.3.1 remote-as external + neighbor 11.1.3.1 timers 3 10 + neighbor 11.1.3.1 disable-link-bw-encoding-ieee + neighbor 11.1.6.2 remote-as external + neighbor 11.1.6.2 timers 3 10 + ! + address-family ipv4 unicast + neighbor 11.1.3.1 route-map anycast_ip out + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r6/zebra.conf b/tests/topotests/bgp_link_bw_ip/r6/zebra.conf new file mode 100644 index 0000000..66ff563 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r6/zebra.conf @@ -0,0 +1,7 @@ +! +interface r6-eth0 + ip address 11.1.3.2/30 +! +interface r6-eth1 + ip address 11.1.6.1/30 +! diff --git a/tests/topotests/bgp_link_bw_ip/r7/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r7/bgpd.conf new file mode 100644 index 0000000..39c0c81 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r7/bgpd.conf @@ -0,0 +1,17 @@ +hostname r7 +! +ip prefix-list redist seq 10 permit 0.0.0.0/0 ge 32 +! +route-map redist permit 10 + match ip address prefix-list redist +! +router bgp 65351 + bgp router-id 11.1.4.2 + no bgp ebgp-requires-policy + neighbor 11.1.4.1 remote-as external + neighbor 11.1.4.1 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected route-map redist + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r7/zebra.conf b/tests/topotests/bgp_link_bw_ip/r7/zebra.conf new file mode 100644 index 0000000..38e36ca --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r7/zebra.conf @@ -0,0 +1,6 @@ +interface r7-eth0 + ip address 11.1.4.2/30 +! +interface r7-eth1 + ip address 50.1.1.7/32 +! diff --git a/tests/topotests/bgp_link_bw_ip/r8/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r8/bgpd.conf new file mode 100644 index 0000000..ea1d546 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r8/bgpd.conf @@ -0,0 +1,17 @@ +hostname r8 +! +ip prefix-list redist seq 10 permit 0.0.0.0/0 ge 32 +! +route-map redist permit 10 + match ip address prefix-list redist +! +router bgp 65352 + bgp router-id 11.1.4.6 + no bgp ebgp-requires-policy + neighbor 11.1.4.5 remote-as external + neighbor 11.1.4.5 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected route-map redist + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r8/zebra.conf b/tests/topotests/bgp_link_bw_ip/r8/zebra.conf new file mode 100644 index 0000000..1369e19 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r8/zebra.conf @@ -0,0 +1,6 @@ +interface r8-eth0 + ip address 11.1.4.6/30 +! +interface r8-eth1 + ip address 50.1.1.8/32 +! diff --git a/tests/topotests/bgp_link_bw_ip/r9/bgpd.conf b/tests/topotests/bgp_link_bw_ip/r9/bgpd.conf new file mode 100644 index 0000000..a6066ee --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r9/bgpd.conf @@ -0,0 +1,17 @@ +hostname r9 +! +ip prefix-list redist seq 10 permit 0.0.0.0/0 ge 32 +! +route-map redist permit 10 + match ip address prefix-list redist +! +router bgp 65353 + bgp router-id 11.1.5.2 + no bgp ebgp-requires-policy + neighbor 11.1.5.1 remote-as external + neighbor 11.1.5.1 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected route-map redist + ! +! diff --git a/tests/topotests/bgp_link_bw_ip/r9/zebra.conf b/tests/topotests/bgp_link_bw_ip/r9/zebra.conf new file mode 100644 index 0000000..c73caf3 --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/r9/zebra.conf @@ -0,0 +1,6 @@ +interface r9-eth0 + ip address 11.1.5.2/30 +! +interface r9-eth1 + ip address 50.1.1.9/32 +! diff --git a/tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py b/tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py new file mode 100644 index 0000000..fd67b2e --- /dev/null +++ b/tests/topotests/bgp_link_bw_ip/test_bgp_linkbw_ip.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_linkbw_ip.py +# +# Copyright (c) 2020 by +# Cumulus Networks, Inc +# Vivek Venkatraman +# + +""" +test_bgp_linkbw_ip.py: Test weighted ECMP using BGP link-bandwidth +""" + +import os +import sys +from functools import partial +import pytest +import json + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + + +""" +This topology is for validating one of the primary use cases for +weighted ECMP (a.k.a. Unequal cost multipath) using BGP link-bandwidth: +https://tools.ietf.org/html/draft-mohanty-bess-ebgp-dmz + +The topology consists of two PODs. Pod-1 consists of a spine switch +and two leaf switches, with two servers attached to the first leaf and +one to the second leaf. Pod-2 consists of one spine and one leaf, with +one server connected to the leaf. The PODs are connected by a super-spine +switch. + +Note that the use of the term "switch" above is in keeping with common +data-center terminology. These devices are all regular routers; for +this scenario, the servers are also routers as they have to announce +anycast IP (VIP) addresses via BGP. +""" + + +def build_topo(tgen): + """ + Build function + + +------+ + | | + /| r7 |--- + / | 65351| + / +------+ + / + +------+ / +------+ + | |/ | | + /| r4 | | r8 |--- + / | 65301|------| 65352| + / +------+ +------+ + / + +------+ / +------+ +------+ + | |/ | | | | + | r2 | | r5 | | r9 |--- + | 65201|------| 65302|------| 65353| + +------+ +------+ +------+ + | + +------+ | + | |---------- + | r1 | + | 65101|---------- + +------+ | + | + +------+ +------+ +------+ + | | | | | | + | r3 |------| r6 |------| r10 |--- + | 65202| | 65303| | 65354| + +------+ +------+ +------+ + """ + + # Create 10 routers - 1 super-spine, 2 spines, 3 leafs + # and 4 servers + routers = {} + for i in range(1, 11): + routers[i] = tgen.add_router("r{}".format(i)) + + # Create 13 "switches" - to interconnect the above routers + switches = {} + for i in range(1, 14): + switches[i] = tgen.add_switch("s{}".format(i)) + + # Interconnect R1 (super-spine) to R2 and R3 (the two spines) + switches[1].add_link(tgen.gears["r1"]) + switches[1].add_link(tgen.gears["r2"]) + switches[2].add_link(tgen.gears["r1"]) + switches[2].add_link(tgen.gears["r3"]) + + # Interconnect R2 (spine in pod-1) to R4 and R5 (the associated + # leaf switches) + switches[3].add_link(tgen.gears["r2"]) + switches[3].add_link(tgen.gears["r4"]) + switches[4].add_link(tgen.gears["r2"]) + switches[4].add_link(tgen.gears["r5"]) + + # Interconnect R3 (spine in pod-2) to R6 (associated leaf) + switches[5].add_link(tgen.gears["r3"]) + switches[5].add_link(tgen.gears["r6"]) + + # Interconnect leaf switches to servers + switches[6].add_link(tgen.gears["r4"]) + switches[6].add_link(tgen.gears["r7"]) + switches[7].add_link(tgen.gears["r4"]) + switches[7].add_link(tgen.gears["r8"]) + switches[8].add_link(tgen.gears["r5"]) + switches[8].add_link(tgen.gears["r9"]) + switches[9].add_link(tgen.gears["r6"]) + switches[9].add_link(tgen.gears["r10"]) + + # Create empty networks for the servers + switches[10].add_link(tgen.gears["r7"]) + switches[11].add_link(tgen.gears["r8"]) + switches[12].add_link(tgen.gears["r9"]) + switches[13].add_link(tgen.gears["r10"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + # tgen.mininet_cli() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_linkbw_adv(): + "Test #1: Test BGP link-bandwidth advertisement based on number of multipaths" + logger.info( + "\nTest #1: Test BGP link-bandwidth advertisement based on number of multipaths" + ) + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + # Configure anycast IP on server r7 + logger.info("Configure anycast IP on server r7") + + tgen.net["r7"].cmd("ip addr add 198.10.1.1/32 dev r7-eth1") + + # Check on spine router r2 for link-bw advertisement by leaf router r4 + logger.info("Check on spine router r2 for link-bw advertisement by leaf router r4") + + json_file = "{}/r2/bgp-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + # Check on spine router r2 that default weight is used as there is no multipath + logger.info( + "Check on spine router r2 that default weight is used as there is no multipath" + ) + + json_file = "{}/r2/ip-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + # Check on super-spine router r1 that link-bw has been propagated by spine router r2 + logger.info( + "Check on super-spine router r1 that link-bw has been propagated by spine router r2" + ) + + json_file = "{}/r1/bgp-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +def test_bgp_cumul_linkbw(): + "Test #2: Test cumulative link-bandwidth propagation" + logger.info("\nTest #2: Test cumulative link-bandwidth propagation") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r4 = tgen.gears["r4"] + + # Configure anycast IP on additional server r8 + logger.info("Configure anycast IP on server r8") + + tgen.net["r8"].cmd("ip addr add 198.10.1.1/32 dev r8-eth1") + + # Check multipath on leaf router r4 + logger.info("Check multipath on leaf router r4") + + json_file = "{}/r4/bgp-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r4, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on leaf router r4" + assert result is None, assertmsg + + # Check regular ECMP is in effect on leaf router r4 + logger.info("Check regular ECMP is in effect on leaf router r4") + + json_file = "{}/r4/ip-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r4, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on leaf router r4" + assert result is None, assertmsg + + # Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths + logger.info( + "Check on spine router r2 that leaf has propagated the cumulative link-bw based on num-multipaths" + ) + + json_file = "{}/r2/bgp-route-2.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + +def test_weighted_ecmp(): + "Test #3: Test weighted ECMP - multipath with next hop weights" + logger.info("\nTest #3: Test weighted ECMP - multipath with next hop weights") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + # Configure anycast IP on additional server r9 + logger.info("Configure anycast IP on server r9") + + tgen.net["r9"].cmd("ip addr add 198.10.1.1/32 dev r9-eth1") + + # Check multipath on spine router r2 + logger.info("Check multipath on spine router r2") + json_file = "{}/r2/bgp-route-3.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + # Check weighted ECMP is in effect on the spine router r2 + logger.info("Check weighted ECMP is in effect on the spine router r2") + + json_file = "{}/r2/ip-route-2.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + # Configure anycast IP on additional server r10 + logger.info("Configure anycast IP on server r10") + + tgen.net["r10"].cmd("ip addr add 198.10.1.1/32 dev r10-eth1") + + # Check if bandwidth is properly encoded with non IEEE floatig-point (uint32) format on r3 + logger.info( + "Check if bandwidth is properly encoded with non IEEE floatig-point (uint32) format on r3" + ) + json_file = "{}/r3/bgp-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r3, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on r3" + assert result is None, assertmsg + + # Check multipath on super-spine router r1 + logger.info("Check multipath on super-spine router r1") + json_file = "{}/r1/bgp-route-2.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + # Check weighted ECMP is in effect on the super-spine router r1 + logger.info("Check weighted ECMP is in effect on the super-spine router r1") + json_file = "{}/r1/ip-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +def test_weighted_ecmp_link_flap(): + "Test #4: Test weighted ECMP rebalancing upon change (link flap)" + logger.info("\nTest #4: Test weighted ECMP rebalancing upon change (link flap)") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + # Bring down link on server r9 + logger.info("Bring down link on server r9") + + tgen.net["r9"].cmd("ip link set dev r9-eth1 down") + + # Check spine router r2 has only one path + logger.info("Check spine router r2 has only one path") + + json_file = "{}/r2/ip-route-3.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on spine router r2" + assert result is None, assertmsg + + # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1 + logger.info( + "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1" + ) + + json_file = "{}/r1/bgp-route-3.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-2.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + # Bring up link on server r9 + logger.info("Bring up link on server r9") + + tgen.net["r9"].cmd("ip link set dev r9-eth1 up") + + # Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1 + logger.info( + "Check link-bandwidth change and weighted ECMP rebalance on super-spine router r1" + ) + + json_file = "{}/r1/bgp-route-2.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-1.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +def test_weighted_ecmp_second_anycast_ip(): + "Test #5: Test weighted ECMP for a second anycast IP" + logger.info("\nTest #5: Test weighted ECMP for a second anycast IP") + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + # Configure anycast IP on additional server r7, r9 and r10 + logger.info("Configure anycast IP on server r7, r9 and r10") + + tgen.net["r7"].cmd("ip addr add 198.10.1.11/32 dev r7-eth1") + tgen.net["r9"].cmd("ip addr add 198.10.1.11/32 dev r9-eth1") + tgen.net["r10"].cmd("ip addr add 198.10.1.11/32 dev r10-eth1") + + # Check link-bandwidth and weighted ECMP on super-spine router r1 + logger.info("Check link-bandwidth and weighted ECMP on super-spine router r1") + + json_file = "{}/r1/bgp-route-4.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.11/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-3.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +def test_paths_with_and_without_linkbw(): + "Test #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP" + logger.info( + "\nTest #6: Test paths with and without link-bandwidth - receiver should resort to regular ECMP" + ) + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + + # Configure leaf router r6 to not advertise any link-bandwidth + logger.info("Configure leaf router r6 to not advertise any link-bandwidth") + + tgen.net["r6"].cmd( + 'vtysh -c "conf t" -c "router bgp 65303" -c "address-family ipv4 unicast" -c "no neighbor 11.1.3.1 route-map anycast_ip out"' + ) + + # Check link-bandwidth change on super-spine router r1 + logger.info("Check link-bandwidth change on super-spine router r1") + + json_file = "{}/r1/bgp-route-5.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show bgp ipv4 uni 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + # Check super-spine router r1 resorts to regular ECMP + logger.info("Check super-spine router r1 resorts to regular ECMP") + + json_file = "{}/r1/ip-route-4.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-5.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=50, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +def test_linkbw_handling_options(): + "Test #7: Test different options for processing link-bandwidth on the receiver" + logger.info( + "\nTest #7: Test different options for processing link-bandwidth on the receiver" + ) + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + r1 = tgen.gears["r1"] + + # Configure super-spine r1 to skip multipaths without link-bandwidth + logger.info("Configure super-spine r1 to skip multipaths without link-bandwidth") + + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth skip-missing"' + ) + + # Check super-spine router r1 resorts to only one path as other path is skipped + logger.info( + "Check super-spine router r1 resorts to only one path as other path is skipped" + ) + + json_file = "{}/r1/ip-route-6.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-7.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + # Configure super-spine r1 to use default-weight for multipaths without link-bandwidth + logger.info( + "Configure super-spine r1 to use default-weight for multipaths without link-bandwidth" + ) + + tgen.net["r1"].cmd( + 'vtysh -c "conf t" -c "router bgp 65101" -c "bgp bestpath bandwidth default-weight-for-missing"' + ) + + # Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth + logger.info( + "Check super-spine router r1 uses ECMP with weight 1 for path without link-bandwidth" + ) + + json_file = "{}/r1/ip-route-8.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.1/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + json_file = "{}/r1/ip-route-9.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, r1, "show ip route 198.10.1.11/32 json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=200, wait=0.5) + assertmsg = "JSON output mismatch on super-spine router r1" + assert result is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_listen_on_multiple_addresses/bgp_listen_on_multiple_addresses.json b/tests/topotests/bgp_listen_on_multiple_addresses/bgp_listen_on_multiple_addresses.json new file mode 100644 index 0000000..95de8cc --- /dev/null +++ b/tests/topotests/bgp_listen_on_multiple_addresses/bgp_listen_on_multiple_addresses.json @@ -0,0 +1,154 @@ +{ + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "1000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "2000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "2000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "3000", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_listen_on_multiple_addresses/test_bgp_listen_on_multiple_addresses.py b/tests/topotests/bgp_listen_on_multiple_addresses/test_bgp_listen_on_multiple_addresses.py new file mode 100755 index 0000000..988b579 --- /dev/null +++ b/tests/topotests/bgp_listen_on_multiple_addresses/test_bgp_listen_on_multiple_addresses.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_listen_on_multiple_addresses.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 by Boeing Defence Australia +# Adriano Marto Reis +# + +""" +test_bgp_listen_on_multiple_addresses.py: Test BGP daemon listening for +connections on multiple addresses. + + +------+ +------+ +------+ +------+ + | | | | | | | | + | r1 |--------| r2 |--------| r3 |--------| r4 | + | | | | | | | | + +------+ +------+ +------+ +------+ + + | | | | + | AS 1000 | AS 2000 | AS 3000 | + | | | | + +------------+--------------------------------+-------------+ +""" + +import os +import sys +import pytest + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topojson import linux_intf_config_from_json +from lib.common_config import start_topology +from lib.topotest import router_json_cmp, run_and_expect +from functools import partial + +pytestmark = [pytest.mark.bgpd] + + +LISTEN_ADDRESSES = { + "r1": ["10.0.0.1"], + "r2": ["10.0.0.2", "10.0.1.1"], + "r3": ["10.0.1.2", "10.0.2.1"], + "r4": ["10.0.2.2"], +} + + +def setup_module(mod): + "Sets up the test environment." + json_file = "{}/bgp_listen_on_multiple_addresses.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + + # Adds extra parameters to bgpd so they listen for connections on specific + # multiple addresses. + for router_name in tgen.routers().keys(): + tgen.net[router_name].daemons_options["bgpd"] = "-l " + " -l ".join( + LISTEN_ADDRESSES[router_name] + ) + + start_topology(tgen) + + linux_intf_config_from_json(tgen, topo) + + build_config_from_json(tgen, topo) + + +def teardown_module(_mod): + "Tears-down the test environment." + tgen = get_topogen() + tgen.stop_topology() + + +def test_peering(): + "Checks if the routers peer-up." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + _bgp_converge_initial("r1", "10.0.0.2") + _bgp_converge_initial("r2", "10.0.0.1") + _bgp_converge_initial("r2", "10.0.1.2") + _bgp_converge_initial("r3", "10.0.1.1") + _bgp_converge_initial("r3", "10.0.2.2") + _bgp_converge_initial("r4", "10.0.2.1") + + +def test_listening_address(): + """ + Checks if bgpd is only listening on the specified IP addresses. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for router in tgen.routers().values(): + # bgpd must not be listening on the default address. + output = router.run("netstat -nlt4 | grep 0.0.0.0:179") + assert output == "", "{}: bpgd is listening on 0.0.0.0:179".format(router.name) + + # bgpd must be listening on the specified addresses. + for address in LISTEN_ADDRESSES[router.name]: + output = router.run("netstat -nlt4 | grep {}:179".format(address)) + assert output != "", "{}: bpgd is not listening on {}:179".format( + router.name, address + ) + + +def _bgp_converge_initial(router_name, peer_address, timeout=180): + """ + Waits for the BGP connection between a given router and a given peer + (specified by its IP address) to be established. If the connection is + not established within a given timeout, then an exception is raised. + """ + tgen = get_topogen() + router = tgen.routers()[router_name] + expected = {"ipv4Unicast": {"peers": {peer_address: {"state": "Established"}}}} + + test_func = partial(router_json_cmp, router, "show ip bgp summary json", expected) + _, result = run_and_expect(test_func, None, count=timeout, wait=1) + assert result is None, "{}: Failed to establish connection with {}".format( + router_name, peer_address + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_llgr/__init__.py b/tests/topotests/bgp_llgr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_llgr/r0/bgpd.conf b/tests/topotests/bgp_llgr/r0/bgpd.conf new file mode 100644 index 0000000..a20e64f --- /dev/null +++ b/tests/topotests/bgp_llgr/r0/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65000 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp graceful-restart restart-time 0 + bgp long-lived-graceful-restart stale-time 20 + neighbor 192.168.0.2 remote-as external + neighbor 192.168.0.2 timers 3 10 + neighbor 192.168.0.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_llgr/r0/zebra.conf b/tests/topotests/bgp_llgr/r0/zebra.conf new file mode 100644 index 0000000..4d11b67 --- /dev/null +++ b/tests/topotests/bgp_llgr/r0/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.1.1/32 +! +int r0-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_llgr/r1/bgpd.conf b/tests/topotests/bgp_llgr/r1/bgpd.conf new file mode 100644 index 0000000..bf33eeb --- /dev/null +++ b/tests/topotests/bgp_llgr/r1/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65001 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp graceful-restart restart-time 0 + bgp long-lived-graceful-restart stale-time 20 + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_llgr/r1/zebra.conf b/tests/topotests/bgp_llgr/r1/zebra.conf new file mode 100644 index 0000000..1782edc --- /dev/null +++ b/tests/topotests/bgp_llgr/r1/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.1.1/32 +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_llgr/r2/bgpd.conf b/tests/topotests/bgp_llgr/r2/bgpd.conf new file mode 100644 index 0000000..6515ef8 --- /dev/null +++ b/tests/topotests/bgp_llgr/r2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 65002 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp long-lived-graceful-restart stale-time 20 + neighbor 192.168.0.1 remote-as external + neighbor 192.168.0.1 timers 3 10 + neighbor 192.168.0.1 timers connect 1 + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.2.1 timers connect 1 + neighbor PG peer-group + neighbor PG remote-as external + bgp listen range 192.168.3.0/24 peer-group PG + address-family ipv4 unicast + neighbor 192.168.1.1 weight 100 diff --git a/tests/topotests/bgp_llgr/r2/zebra.conf b/tests/topotests/bgp_llgr/r2/zebra.conf new file mode 100644 index 0000000..e5e88d4 --- /dev/null +++ b/tests/topotests/bgp_llgr/r2/zebra.conf @@ -0,0 +1,13 @@ +! +int r2-eth0 + ip address 192.168.0.2/24 +! +int r2-eth1 + ip address 192.168.1.2/24 +! +int r2-eth2 + ip address 192.168.2.2/24 +! +int r2-eth3 + ip address 192.168.3.2/24 +! diff --git a/tests/topotests/bgp_llgr/r3/bgpd.conf b/tests/topotests/bgp_llgr/r3/bgpd.conf new file mode 100644 index 0000000..dfe20c1 --- /dev/null +++ b/tests/topotests/bgp_llgr/r3/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65003 + bgp router-id 192.168.2.1 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp long-lived-graceful-restart stale-time 20 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 + neighbor 192.168.2.2 timers connect 1 diff --git a/tests/topotests/bgp_llgr/r3/zebra.conf b/tests/topotests/bgp_llgr/r3/zebra.conf new file mode 100644 index 0000000..8a7941b --- /dev/null +++ b/tests/topotests/bgp_llgr/r3/zebra.conf @@ -0,0 +1,4 @@ +! +int r3-eth0 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_llgr/r4/bgpd.conf b/tests/topotests/bgp_llgr/r4/bgpd.conf new file mode 100644 index 0000000..49ce387 --- /dev/null +++ b/tests/topotests/bgp_llgr/r4/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65004 + bgp router-id 192.168.3.1 + no bgp ebgp-requires-policy + bgp graceful-restart + bgp graceful-restart restart-time 0 + bgp long-lived-graceful-restart stale-time 30 + neighbor 192.168.3.2 remote-as external + neighbor 192.168.3.2 timers 3 10 + neighbor 192.168.3.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_llgr/r4/zebra.conf b/tests/topotests/bgp_llgr/r4/zebra.conf new file mode 100644 index 0000000..7ec20be --- /dev/null +++ b/tests/topotests/bgp_llgr/r4/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.1.2/32 +! +int r4-eth0 + ip address 192.168.3.1/24 +! diff --git a/tests/topotests/bgp_llgr/test_bgp_llgr.py b/tests/topotests/bgp_llgr/test_bgp_llgr.py new file mode 100644 index 0000000..d7897cf --- /dev/null +++ b/tests/topotests/bgp_llgr/test_bgp_llgr.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if BGP Long-lived Graceful Restart capability works: + Check if we can see 172.16.1.1/32 after initial converge in R3. + Check if we can see 172.16.1.1/32 as best selected due to higher weigth in R2. + Kill bgpd in R1. + Check if we can see 172.16.1.1/32 as stale in R2. + Check if we can see 172.16.1.1/32 depreferenced due to LLGR_STALE in R2. + Check if we can see 172.16.1.1/32 after R1 was killed in R3. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +from lib.common_config import ( + kill_router_daemons, + start_router_daemons, + step, +) + + +def build_topo(tgen): + for routern in range(0, 6): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s0") + switch.add_link(tgen.gears["r0"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + # Dynamic neighbor + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_llgr(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp json")) + expected = { + "routes": { + "172.16.1.1/32": [{"nexthops": [{"ip": "192.168.2.2", "used": True}]}], + "172.16.1.2/32": [{"nexthops": [{"ip": "192.168.2.2", "used": True}]}], + } + } + return topotest.json_cmp(output, expected) + + step("Check if we can see 172.16.1.1/32 after initial converge in R3") + test_func = functools.partial(_bgp_converge, r3) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see 172.16.1.1/32 in r3" + + def _bgp_weight_prefered_route(router): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.1.1/32 json")) + expected = { + "paths": [ + { + "bestpath": {"selectionReason": "Weight"}, + "nexthops": [ + { + "ip": "192.168.1.1", + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + step( + "Check if we can see 172.16.1.1/32 as best selected due to higher weigth in R2" + ) + test_func = functools.partial(_bgp_weight_prefered_route, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "Prefix 172.16.1.1/32 is not selected as bests path due to weight" + + step("Kill bgpd in R1") + kill_router_daemons(tgen, "r1", ["bgpd"]) + + def _bgp_stale_route(router, prefix): + output = json.loads(router.vtysh_cmd("show ip bgp {} json".format(prefix))) + expected = {"paths": [{"community": {"string": "llgr-stale"}, "stale": True}]} + return topotest.json_cmp(output, expected) + + step("Check if we can see 172.16.1.1/32 as stale in R2") + test_func = functools.partial(_bgp_stale_route, r2, "172.16.1.1/32") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Prefix 172.16.1.1/32 is not stale" + + def _bgp_llgr_depreference_route(router): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.1.1/32 json")) + expected = { + "paths": [ + { + "bestpath": {"selectionReason": "First path received"}, + "nexthops": [ + { + "ip": "192.168.0.1", + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + step("Check if we can see 172.16.1.1/32 depreferenced due to LLGR_STALE in R2") + test_func = functools.partial(_bgp_llgr_depreference_route, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Prefix 172.16.1.1/32 is not depreferenced due to LLGR_STALE" + + step("Check if we can see 172.16.1.1/32 after R1 was killed in R3") + test_func = functools.partial(_bgp_converge, r3) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see 172.16.1.1/32 in r3" + + step("Kill bgpd in R4 (dynamic peer)") + kill_router_daemons(tgen, "r4", ["bgpd"]) + + step("Check if we can see 172.16.1.2/32 after R4 (dynamic peer) was killed") + test_func = functools.partial(_bgp_stale_route, r2, "172.16.1.2/32") + _, result = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assert result is None, "Cannot see 172.16.1.2/32 in r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_as/__init__.py b/tests/topotests/bgp_local_as/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_local_as/r1/bgpd.conf b/tests/topotests/bgp_local_as/r1/bgpd.conf new file mode 100644 index 0000000..fa147d8 --- /dev/null +++ b/tests/topotests/bgp_local_as/r1/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as 65002 + neighbor 192.168.1.2 local-as 65002 + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor PG peer-group + neighbor PG remote-as 65003 + neighbor PG local-as 65003 + neighbor 192.168.2.2 peer-group PG + address-family ipv4 + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_local_as/r1/zebra.conf b/tests/topotests/bgp_local_as/r1/zebra.conf new file mode 100644 index 0000000..5b32fae --- /dev/null +++ b/tests/topotests/bgp_local_as/r1/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_local_as/r2/bgpd.conf b/tests/topotests/bgp_local_as/r2/bgpd.conf new file mode 100644 index 0000000..9c25bdf --- /dev/null +++ b/tests/topotests/bgp_local_as/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as internal + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_local_as/r2/zebra.conf b/tests/topotests/bgp_local_as/r2/zebra.conf new file mode 100644 index 0000000..0c95656 --- /dev/null +++ b/tests/topotests/bgp_local_as/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_local_as/r3/bgpd.conf b/tests/topotests/bgp_local_as/r3/bgpd.conf new file mode 100644 index 0000000..54ccd90 --- /dev/null +++ b/tests/topotests/bgp_local_as/r3/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as internal + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 +! diff --git a/tests/topotests/bgp_local_as/r3/zebra.conf b/tests/topotests/bgp_local_as/r3/zebra.conf new file mode 100644 index 0000000..d28dedd --- /dev/null +++ b/tests/topotests/bgp_local_as/r3/zebra.conf @@ -0,0 +1,4 @@ +! +interface r3-eth0 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_local_as/test_bgp_local_as.py b/tests/topotests/bgp_local_as/test_bgp_local_as.py new file mode 100644 index 0000000..9e5f146 --- /dev/null +++ b/tests/topotests/bgp_local_as/test_bgp_local_as.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" + +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_local_as_same_remote_as(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_local_as_same_remote_as(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd("show bgp ipv4 unicast 172.16.255.1/32 json") + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "Local"}, + "nexthops": [{"ip": "192.168.1.1", "hostname": "r1"}], + } + ] + } + return topotest.json_cmp(output, expected) + + step("Check if iBGP works when local-as == remote-as") + test_func = functools.partial(_bgp_check_local_as_same_remote_as) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R2" + + +def test_bgp_peer_group_local_as_same_remote_as(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_local_as_same_remote_as(): + output = json.loads( + tgen.gears["r3"].vtysh_cmd("show bgp ipv4 unicast 172.16.255.1/32 json") + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "Local"}, + "nexthops": [{"ip": "192.168.2.1", "hostname": "r1"}], + } + ] + } + return topotest.json_cmp(output, expected) + + step("Initial BGP converge") + test_func = functools.partial(_bgp_check_local_as_same_remote_as) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/__init__.py b/tests/topotests/bgp_local_as_dotplus_private_remove/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r1/bgpd.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r1/bgpd.conf new file mode 100644 index 0000000..1846df2 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r1/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 0.65000 as-notation dot+ + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 0.1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 0.500 + address-family ipv4 unicast + neighbor 192.168.255.2 remove-private-AS + redistribute connected diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r1/zebra.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r2/bgpd.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r2/bgpd.conf new file mode 100644 index 0000000..c9adfa4 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 0.1000 as-notation dot+ + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 0.500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r2/zebra.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r3/bgpd.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r3/bgpd.conf new file mode 100644 index 0000000..9a83127 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r3/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 3000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 500 + address-family ipv4 unicast + neighbor 192.168.255.2 remove-private-AS + redistribute connected diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r3/zebra.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r3/zebra.conf new file mode 100644 index 0000000..39499a1 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r3/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r3-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r4/bgpd.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r4/bgpd.conf new file mode 100644 index 0000000..c9adfa4 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r4/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 0.1000 as-notation dot+ + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 0.500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/r4/zebra.conf b/tests/topotests/bgp_local_as_dotplus_private_remove/r4/zebra.conf new file mode 100644 index 0000000..b859115 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_dotplus_private_remove/test_bgp_local_as_dotplus_private_remove.py b/tests/topotests/bgp_local_as_dotplus_private_remove/test_bgp_local_as_dotplus_private_remove.py new file mode 100644 index 0000000..930fd79 --- /dev/null +++ b/tests/topotests/bgp_local_as_dotplus_private_remove/test_bgp_local_as_dotplus_private_remove.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python + +# +# bgp_local_as_private_remove.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +bgp_local_as_private_remove.py: +Test if primary AS number is not removed in cases when `local-as` +used together with `remove-private-AS`. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_remove_private_as(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r4 = tgen.gears["r4"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge initially" + + def _bgp_as_path(router, asn_path, asn_length): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.255.254/32 json")) + expected = { + "paths": [ + { + "aspath": { + "string": asn_path, + "length": asn_length, + } + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_as_path, r2, "0.500", 1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Private ASNs not stripped" + + test_func = functools.partial(_bgp_as_path, r4, "0.500 0.3000", 2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Private ASNs not stripped" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_as_private_remove/__init__.py b/tests/topotests/bgp_local_as_private_remove/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_local_as_private_remove/r1/bgpd.conf b/tests/topotests/bgp_local_as_private_remove/r1/bgpd.conf new file mode 100644 index 0000000..16a9eea --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r1/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 500 + address-family ipv4 unicast + neighbor 192.168.255.2 remove-private-AS + redistribute connected diff --git a/tests/topotests/bgp_local_as_private_remove/r1/zebra.conf b/tests/topotests/bgp_local_as_private_remove/r1/zebra.conf new file mode 100644 index 0000000..0a283c0 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_private_remove/r2/bgpd.conf b/tests/topotests/bgp_local_as_private_remove/r2/bgpd.conf new file mode 100644 index 0000000..802c327 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 1000 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_local_as_private_remove/r2/zebra.conf b/tests/topotests/bgp_local_as_private_remove/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_private_remove/r3/bgpd.conf b/tests/topotests/bgp_local_as_private_remove/r3/bgpd.conf new file mode 100644 index 0000000..9a83127 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r3/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 3000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 1000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 local-as 500 + address-family ipv4 unicast + neighbor 192.168.255.2 remove-private-AS + redistribute connected diff --git a/tests/topotests/bgp_local_as_private_remove/r3/zebra.conf b/tests/topotests/bgp_local_as_private_remove/r3/zebra.conf new file mode 100644 index 0000000..39499a1 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r3/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r3-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_private_remove/r4/bgpd.conf b/tests/topotests/bgp_local_as_private_remove/r4/bgpd.conf new file mode 100644 index 0000000..802c327 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r4/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 1000 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 500 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_local_as_private_remove/r4/zebra.conf b/tests/topotests/bgp_local_as_private_remove/r4/zebra.conf new file mode 100644 index 0000000..b859115 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_local_as_private_remove/test_bgp_local_as_private_remove.py b/tests/topotests/bgp_local_as_private_remove/test_bgp_local_as_private_remove.py new file mode 100644 index 0000000..9d22a79 --- /dev/null +++ b/tests/topotests/bgp_local_as_private_remove/test_bgp_local_as_private_remove.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_local_as_private_remove.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +bgp_local_as_private_remove.py: +Test if primary AS number is not removed in cases when `local-as` +used together with `remove-private-AS`. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_remove_private_as(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r4 = tgen.gears["r4"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "bgpState": "Established", + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge initially" + + def _bgp_as_path(router, asn_path, asn_length): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.255.254/32 json")) + expected = { + "paths": [ + { + "aspath": { + "string": asn_path, + "length": asn_length, + } + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_as_path, r2, "500", 1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Private ASNs not stripped" + + test_func = functools.partial(_bgp_as_path, r4, "500 3000", 2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Private ASNs not stripped" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_agg.json b/tests/topotests/bgp_local_asn/bgp_local_asn_agg.json new file mode 100644 index 0000000..a842c9e --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_agg.json @@ -0,0 +1,147 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r1": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.1.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::1:0/128", + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.2.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::2:0/128", + "next_hop":"Null0" + }] + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_ecmp.json b/tests/topotests/bgp_local_asn/bgp_local_asn_ecmp.json new file mode 100644 index 0000000..7184679 --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_ecmp.json @@ -0,0 +1,317 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + + ], + "static_routes":[ + { + "network":"10.0.0.1/32", + "next_hop":"Null0" + }, + { + "network":"10::1/128", + "next_hop":"Null0" + } + ] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link8": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link8": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {}, + "r4-link8": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {}, + "r4-link8": {} + } + } + } + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_topo1.json b/tests/topotests/bgp_local_asn/bgp_local_asn_topo1.json new file mode 100644 index 0000000..da665be --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_topo1.json @@ -0,0 +1,132 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.1.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::1:0/128", + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_topo2.json b/tests/topotests/bgp_local_asn/bgp_local_asn_topo2.json new file mode 100644 index 0000000..51f9bb2 --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_topo2.json @@ -0,0 +1,117 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "12000100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + } + } + } + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "12000200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "12000300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "12000400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo1.json b/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo1.json new file mode 100644 index 0000000..d415b63 --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo1.json @@ -0,0 +1,152 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp":[ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + ], + "static_routes":[ + { + "network":"10.0.0.1/32", + "next_hop":"Null0" + }, + { + "network":"10::1/128", + "next_hop":"Null0" + }] + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto","vrf": "BLUE"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r3": {}}} + } + } + } + } + }, + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": {"r3": {}}} + } + } + } + } + } + ] + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto","vrf": "BLUE"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "400", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r4": {}}} + } + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo2.json b/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo2.json new file mode 100644 index 0000000..311ccce --- /dev/null +++ b/tests/topotests/bgp_local_asn/bgp_local_asn_vrf_topo2.json @@ -0,0 +1,128 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto","vrf": "RED"} + }, + "vrfs":[{"name": "RED","id": "1"}], + "bgp":[ + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + ] + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto","vrf": "RED"}, + "r4": {"ipv4": "auto", "ipv6": "auto","vrf": "BLUE"} + }, + "vrfs":[{"name": "RED","id": "1"}, + {"name": "BLUE","id": "2"}], + "bgp": + [ + { + "local_as": "300", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": {"r3": {}}} + } + } + } + } + }, + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": {"r3": {}}} + } + } + } + } + } + ] + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto","vrf": "BLUE"} + }, + "vrfs":[{"name": "BLUE","id": "1"}], + "bgp": + [ + { + "local_as": "400", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r4": {}}} + } + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_agg.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_agg.py new file mode 100644 index 0000000..c84fce6 --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_agg.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify the BGP Local AS functionality by aggregating routes in between eBGP Peers. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK_1_1 = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NETWORK_1_2 = {"ipv4": "10.1.2.0/32", "ipv6": "10:1::2:0/128"} +AGGREGATE_NW = {"ipv4": "10.1.0.0/16", "ipv6": "10:1::/96"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_agg.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +#################################################################################################################### +# +# Testcases +# +#################################################################################################################### + + +def test_verify_bgp_local_as_agg_in_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by aggregating routes in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Done in base config: Advertise prefix 10.1.1.0/24 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/120 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK_1_1[addr_type]}]} + } + + input_static_verify_r2 = { + "r2": {"static_routes": [{"network": NETWORK_1_2[addr_type]}]} + } + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", input_static_verify_r2) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure aggregate-address to summarise all the advertised routes.") + for addr_type in ADDR_TYPES: + route_aggregate = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + { + "network": AGGREGATE_NW[addr_type], + "summary": True, + "as_set": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that we see a summarised route on advertising router R3 " + "and receiving router R4 for both AFIs" + ) + + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + input_static_r1 = { + "r1": {"static_routes": [{"network": [NETWORK_1_1[addr_type]]}]} + } + + input_static_r2 = { + "r2": {"static_routes": [{"network": [NETWORK_1_2[addr_type]]}]} + } + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_agg_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1", "r2"], [input_static_r1, input_static_r2]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 {100,110,200} by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "{100,110,200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "{100,200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 {100,200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_ecmp.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_ecmp.py new file mode 100644 index 0000000..8a11570 --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_ecmp.py @@ -0,0 +1,511 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# +########################################################################################################################################## +# +# Testcases +# +########################################################################################################################################### +########################################################################################################################################### +# +# 1.10.1.7. Verify the BGP Local AS functionality with ECMP on 8 links by adding no-prepend and replace-as command in between eBGP Peers. +# +################################################################################################################################################# + +import os +import sys +import time +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, + verify_fib_routes, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_ecmp.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################################################## +# +# Testcases +# +########################################################################################################################################### + + +def test_verify_bgp_local_as_in_ecmp_EBGP_p0(request): + """ + Verify the BGP Local AS functionality with ECMP on 8 links by + adding no-prepend and replace-as command in between eBGP Peers. + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + dut = "r1" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": {"local_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = {"local_asn": {"local_as": "110"}} + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + dest_link = {} + for link_no in range(1, 9): + link = "r4-link" + str(link_no) + dest_link[link] = {"local_asn": {"remote_as": "110"}} + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r3": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = {"local_asn": {"local_as": "110"}} + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = { + "local_asn": {"local_as": "110", "no_prepend": True, "replace_as": True} + } + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo1.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo1.py new file mode 100644 index 0000000..25c9bee --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo1.py @@ -0,0 +1,3642 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +########################################################################################################## +# +# Functionality Testcases +# +########################################################################################################## +""" +1. Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between eBGP Peers. +2. Verify the BGP Local AS functionality by configuring 4 Byte AS at R3 and 2 Byte AS at R2 & R4 in between eBGP Peers. +3. Verify that BGP Local AS functionality by performing graceful restart in between eBGP Peers. +4. Verify the BGP Local AS functionality by adding another AS & by same AS with AS-Prepend command in between eBGP Peers. +4. Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between iBGP Peers. +5. Verify the BGP Local AS functionality with allowas-in in between iBGP Peers. +6. Verify that BGP Local AS functionality by performing shut/ noshut on the interfaces in between BGP neighbors. +7. Verify that BGP Local AS functionality by restarting BGP,Zebra and FRR services and + further restarting clear BGP * and shutdown BGP neighbor. +8. Verify the BGP Local AS functionality with different AS configurations. +9. Verify the BGP Local AS functionality with R3& R4 with different AS configurations. +""" + +import os +import sys +import time +import pytest +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + get_frr_ipv6_linklocal, + check_address_types, + check_router_status, + create_static_routes, + verify_fib_routes, + create_route_maps, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + clear_bgp_and_verify, + verify_bgp_rib, + modify_as_number, + create_router_bgp, + verify_bgp_advertised_routes_from_neighbor, + verify_graceful_restart, + verify_r_bit, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +NEXT_HOP_IP_GR = {"ipv4": "10.0.0.5", "ipv6": "fd00:0:0:1::2/64"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.0.101", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.0.102", "ipv6": "fd00::2"} + +BGP_CONVERGENCE = False +PREFERRED_NEXT_HOP = "link_local" +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################## +# +# Local APIs +# +########################################################################################################## + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################################################################################################## +# +# Testcases +# +########################################################################################################## + + +def test_verify_bgp_local_as_in_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by adding no-prepend and + replace-as command in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_4B_AS_mid_2B_AS_p0(request): + """ + Verify the BGP Local AS functionality by configuring 4 Byte AS + at R3 and 2 Byte AS at R2 & R4 in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": { + "remote_as": "12000110" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-12000110 is got added in the AS list 12000110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "12000110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "12000110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_GR_EBGP_p0(request): + """ + Verify that BGP Local AS functionality by performing graceful restart in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP_GR[addr_type], + } + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP_GR[addr_type], + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + """ + GR Steps : Helper BGP router R2, mark and unmark IPV4 routes + as stale as the restarting router R3 come up within the restart time + """ + # Create route-map to prefer global next-hop + input_dict = { + "r2": { + "route_maps": { + "rmap_global": [ + {"action": "permit", "set": {"ipv6": {"nexthop": "prefer-global"}}} + ] + } + }, + "r3": { + "route_maps": { + "rmap_global": [ + {"action": "permit", "set": {"ipv6": {"nexthop": "prefer-global"}}} + ] + } + }, + } + result = create_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_neigh_rm = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_neigh_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure graceful-restart + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "graceful-restart-helper": True, + "local_asn": {"remote_as": "110"}, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "graceful-restart-helper": True, + "local_asn": {"remote_as": "110"}, + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"graceful-restart": True}}} + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r3", peer="r2") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r2" + peer = "r3" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + input_topo = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, "bgp") + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 2] : R3 goes for reload ") + + kill_router_daemons(tgen, "r3", ["bgpd"]) + + logger.info( + "[Phase 3] : R3 is still down, restart time 120 sec." + " So time verify the routes are present in BGP RIB" + " and ZEBRA" + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 5] : R3 is about to come up now ") + start_router_daemons(tgen, "r3", ["bgpd"]) + + logger.info("[Phase 5] : R3 is UP Now ! ") + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), "BGP Convergence after BGPd restart" " :Failed \n Error:{}".format(result) + + # Verifying GR stats + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r2", peer="r3") + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_aspath_p0(request): + """ + Verify the BGP Local AS functionality by adding another AS & by same AS with AS-Prepend command in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R3 to prepend AS 2 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r3": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": {"as_num": "1000 1000", "as_action": "prepend"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R4") + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-300 is got replaced with 200 in the AS list 110 1000 1000 200 100 by following" + "commands at R3 router." + ) + dut = "r4" + aspath = "110 1000 1000 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_iBGP_p0(request): + """ + Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between iBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Modify AS Number for R3") + input_dict_modify_as_number = {"r3": {"bgp": {"local_as": 200}}} + result = modify_as_number(tgen, topo, input_dict_modify_as_number) + + step("Base config is done as part of JSON") + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "next_hop_self": True, + "local_asn": { + "remote_as": "200", + }, + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_allow_as_in_iBGP_p0(request): + """ + Verify the BGP Local AS functionality with allowas-in in between iBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Modidy AS Number for R4") + input_dict_modify_as_number = {"r4": {"bgp": {"local_as": 100}}} + result = modify_as_number(tgen, topo, input_dict_modify_as_number) + + step("Base config is done as part of JSON") + dut = "r1" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure allow-as at R4") + for addr_type in ADDR_TYPES: + allow_as_config_r4 = { + "r4": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "allowas-in": { + "number_occurences": 1 + } + } + } + } + } + } + } + } + } + ] + } + } + + step( + "Configuring allow-as for {} address-family on router R4 ".format(addr_type) + ) + result = create_router_bgp(tgen, topo, allow_as_config_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + # now modify the as in r4 and reconfig bgp in r3 with new remote as. + topo1 = deepcopy(topo) + topo1["routers"]["r4"]["bgp"]["local_as"] = "100" + + delete_bgp = {"r3": {"bgp": {"delete": True}}} + result = create_router_bgp(tgen, topo1, delete_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + build_config_from_json(tgen, topo1, save_bkup=False) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "100", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_port_reset_p0(request): + """ + Verify that BGP Local AS functionality by performing shut/ noshut on the interfaces in between BGP neighbors. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step("Api call to modfiy BGP timers at R3") + for addr_type in ADDR_TYPES: + input_dict_r3_timers = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_timers) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify advertised routes at R3 towards R4") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for count in range(1, 1): + step("Iteration {}".format(count)) + step("Shut down connecting interface between R3<<>>R4 on R3.") + + intf1 = topo["routers"]["r3"]["links"]["r4"]["interface"] + + interfaces = [intf1] + for intf in interfaces: + shutdown_bringup_interface(tgen, "r3", intf, False) + + step( + "On R3, all BGP peering in respective vrf instances go down" + " when the interface is shut" + ) + + result = verify_bgp_convergence(tgen, topo, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: BGP will not be converged \n " + "Error {}".format(tc_name, result) + ) + + step("BGP neighborship is verified after restart of r3") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_negative2_p0(request): + """ + Verify the BGP Local AS functionality with different AS configurations. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that AS-110 is not prepended in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + step("Verify that AS-300 is replaced with AS-110 at R3 router.") + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # configure negative scenarios + step("Configure local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "300"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + step("Configure another local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "110", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_negative3_p0(request): + """ + Verify the BGP Local AS functionality with R3& R4 with different AS configurations. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Perform Negative scenarios + step("Configure another local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "300"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_restart_daemons_p0(request): + """ + Verify that BGP Local AS functionality by restarting BGP,Zebra and FRR services and + further restarting clear BGP * and shutdown BGP neighbor. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["200", "400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100)." + ) + step("Verify that Static routes are redistributed in BGP process") + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Kill BGPd daemon on R3.") + kill_router_daemons(tgen, "r3", ["bgpd"]) + + step("Bring up BGPd daemon on R3.") + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify advertised routes at R3 towards R4") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-110 is not prepended in the AS list 200 100 by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Kill BGPd daemon on R3.") + kill_router_daemons(tgen, "r3", ["bgpd"]) + + step("Bring up BGPd daemon on R3.") + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "Verify that AS-110 is not prepended in the AS list 200 100 by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verified that AS-300 is got replaced with original AS-110 at R4 by following commands" + ) + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verified that AS-300 is got replaced with original AS-110 at R4 by following commands" + ) + dut = "r4" + aspath = "110 200 100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo2.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo2.py new file mode 100644 index 0000000..0c10c64 --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_topo2.py @@ -0,0 +1,686 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +########################################################################################################## +# +# Testcases +# +########################################################################################################## +########################################################################################################## +# +# 1.10.1.2. Verify the BGP Local AS functionality by configuring 4 Byte AS in between eBGP Peers. +# +# 1.10.1.4. Verify the BGP Local AS functionality by configuring Old AS(local as) in 2 bytes and New AS in 4 bytes in between eBGP Peers. +# +############################################################################################################### + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, + verify_bgp_advertised_routes_from_neighbor, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################## +# +# Testcases +# +########################################################################################################## + + +def test_verify_bgp_local_as_in_4_Byte_AS_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by configuring 4 Byte AS in between eBGP Peers. + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip( + ["r2", "r4"], ["12000200", "12000400"], ["r3", "r3"] + ): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": { + "remote_as": "12000110" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step( + "Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-12000100)." + ) + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-12000100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r2", "r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-12000110 is got added in the AS list 12000110 12000200 12000100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "12000110 12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "12000110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "12000110 12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_old_AS2_new_AS4_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by configuring Old AS(local as) in + 2 bytes and New AS in 4 bytes in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip( + ["r2", "r4"], ["12000200", "12000400"], ["r3", "r3"] + ): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: {"local_asn": {"remote_as": "110"}} + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step( + "Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-12000100)." + ) + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-12000100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 12000200 12000100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "110 12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "12000300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 12000200 12000100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo1.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo1.py new file mode 100644 index 0000000..ea6ab59 --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo1.py @@ -0,0 +1,1095 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +1. Verify the BGP Local AS functionality by adding new AS when dynamically import routes + from default vrf to non-default vrf with route map by adding AS by as-prepend command. +2. Verify the BGP Local AS functionality by adding new AS when dynamically import routes + from non-default vrf to default vrf and further advertised to eBGP peers. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, + verify_fib_routes, + create_route_maps, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_vrf_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +################################################################################################ +# +# Testcases +# +############################################################################################### + + +def test_verify_local_asn_ipv4_import_from_default_to_non_default_VRF_p0(request): + """ + Verify the BGP Local AS functionality by adding new AS when dynamically import routes + from default vrf to non-default vrf with route map by adding AS by as-prepend command. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + + # configure static routes + step("Advertise prefix 10.0.0.1/32 from Router-1(AS-100).") + step("Advertise an ipv6 prefix 10::1/128 from Router-1(AS-100).") + dut = "r2" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r2": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R2") + input_dict_static_route_redist = { + "r2": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure import vrf BLUE on R3 under IPv4 Address Family") + input_import_vrf_ipv4 = { + "r3": { + "bgp": [ + { + "local_as": 300, + "vrf": "BLUE", + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "default"}}}, + "ipv6": {"unicast": {"import": {"vrf": "default"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf_ipv4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 static routes received on R2") + for addr_type in ADDR_TYPES: + input_dict_static_route = { + "r2": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + result = verify_rib(tgen, addr_type, "r2", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r2", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r2", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "400", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 VRF BLUE & R4 VRF BLUE") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R3 to prepend AS 2 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r3": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": {"as_num": "1000 1000", "as_action": "prepend"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R4") + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 1000 1000 200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_local_asn_ipv4_import_from_non_default_to_default_VRF_p0(request): + """ + Verify the BGP Local AS functionality by adding new AS when dynamically import routes + from non-default vrf to default vrf and further advertised to eBGP peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Resetting the config from JSON") + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + # configure static routes + step("Advertise prefix 10.0.0.1/32 from Router-1(AS-100).") + step("Advertise an ipv6 prefix 10::1/128 from Router-1(AS-100).") + dut = "r4" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r4": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + } + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R4") + input_dict_static_route_redist = { + "r4": { + "bgp": { + "local_as": 400, + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure import from BLUE vrf to default vrf on R3 under IPv4 Address Family" + ) + input_import_vrf_ipv4 = { + "r3": { + "bgp": [ + { + "local_as": 300, + "vrf": "default", + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "BLUE"}}}, + "ipv6": {"unicast": {"import": {"vrf": "BLUE"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf_ipv4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 static routes received on R3 VRF BLUE & R4 VRF BLUE") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r4": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "400", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R2") + for addr_type in ADDR_TYPES: + input_dict_static_route_from_r4 = { + "r4": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + result = verify_rib(tgen, addr_type, "r2", input_dict_static_route_from_r4) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r2", input_dict_static_route_from_r4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes( + tgen, addr_type, "r2", input_dict_static_route_from_r4 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 400 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 400" + for addr_type in ADDR_TYPES: + input_static_r2 = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + dut = "r3" + aspath = "400" + for addr_type in ADDR_TYPES: + input_static_r2 = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r2" + aspath = "110 400" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r4": { + "static_routes": [ + { + "network": NETWORK[addr_type], + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo2.py b/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo2.py new file mode 100644 index 0000000..b1204bf --- /dev/null +++ b/tests/topotests/bgp_local_asn/test_bgp_local_asn_vrf_topo2.py @@ -0,0 +1,813 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +1. Verify the BGP Local AS functionality by adding new AS when leaking routes + from non-default VRF to non-default with route map by prefix lists. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, + create_prefix_lists, + verify_fib_routes, + create_route_maps, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_vrf_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +################################################################################################ +# +# Testcases +# +############################################################################################### + + +def test_verify_local_asn_ipv4_import_from_non_default_to_non_default_VRF_p0(request): + """ + Verify the BGP Local AS functionality by adding new AS when leaking routes + from non-default VRF to non-default with route map by prefix lists. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + + # configure static routes + step("Advertise prefix 10.1.1.0/32 from Router-1(AS-100).") + step("Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-100).") + dut = "r2" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R2") + input_dict_static_route_redist = { + "r2": { + "bgp": { + "local_as": 200, + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure import vrf BLUE from vrf RED on R3 under IPv4 Address Family") + input_import_vrf_ipv4 = { + "r3": { + "bgp": [ + { + "local_as": 300, + "vrf": "BLUE", + "address_family": { + "ipv4": {"unicast": {"import": {"vrf": "RED"}}}, + "ipv6": {"unicast": {"import": {"vrf": "RED"}}}, + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf_ipv4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify IPv4 and IPv6 static routes received on R3 VRF BLUE & R4 VRF BLUE") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "vrfs": [{"name": "RED", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "RED", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "vrfs": [{"name": "BLUE", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "110"}} + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "vrfs": [{"name": "RED", "id": "1"}], + "bgp": [ + { + "local_as": "200", + "vrf": "RED", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "vrfs": [{"name": "BLUE", "id": "1"}], + "bgp": [ + { + "local_as": "400", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "110"} + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 VRF BLUE & R4 VRF BLUE") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r2": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 110 200 100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "110 200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # configure the prefix-list and route-maps. + for adt in ADDR_TYPES: + # Create Static routes + input_dict_rm1 = { + "r2": { + "static_routes": [ + { + "network": NETWORK[adt], + "no_of_ip": 1, + "next_hop": NEXT_HOP_IP[adt], + "vrf": "RED", + } + ] + } + } + + result = create_static_routes(tgen, input_dict_rm1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_rm_rd = { + "r2": { + "bgp": { + "local_as": 200, + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_rm_rd) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_rm_pl = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "action": "permit", + "network": NETWORK["ipv4"], + } + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 100, + "action": "permit", + "network": NETWORK["ipv6"], + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_rm_pl) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_rm_r3 = { + "r3": { + "route_maps": { + "rmap_match_tag_1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + } + ], + "rmap_match_tag_2_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_2_{}".format(addr_type) + } + }, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_rm_r3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_conf_neighbor_rm = { + "r3": { + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + }, + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + }, + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv6", + "direction": "in", + }, + { + "name": "rmap_match_tag_1_ipv6", + "direction": "out", + }, + ] + } + } + } + } + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_conf_neighbor_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for adt in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + input_dict_r2_rib = { + "r2": { + "static_routes": [ + { + "network": [NETWORK[adt]], + "no_of_ip": 1, + "next_hop": NEXT_HOP_IP[adt], + "vrf": "RED", + } + ] + } + } + result = verify_rib(tgen, adt, dut, input_dict_r2_rib) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + input_dict_r2_rib = { + "r2": { + "static_routes": [ + { + "network": [NETWORK[adt]], + "no_of_ip": 1, + "next_hop": NEXT_HOP_IP[adt], + "vrf": "BLUE", + } + ] + } + } + result = verify_rib(tgen, adt, dut, input_dict_r2_rib) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "vrfs": [{"name": "RED", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "RED", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "vrfs": [{"name": "BLUE", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "vrfs": [{"name": "RED", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "RED", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "vrfs": [{"name": "BLUE", "id": "1"}], + "bgp": [ + { + "local_as": "300", + "vrf": "BLUE", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ], + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "110 200" + for addr_type in ADDR_TYPES: + input_static_r2 = { + "r2": {"static_routes": [{"network": NETWORK[addr_type], "vrf": "BLUE"}]} + } + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r2, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_agg.json b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_agg.json new file mode 100644 index 0000000..b481932 --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_agg.json @@ -0,0 +1,147 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r1": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.1.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::1:0/128", + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.200", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.2.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::2:0/128", + "next_hop":"Null0" + }] + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {}}}, + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_ecmp.json b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_ecmp.json new file mode 100644 index 0000000..afacab4 --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_ecmp.json @@ -0,0 +1,317 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "1.100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + + ], + "static_routes":[ + { + "network":"10.0.0.1/32", + "next_hop":"Null0" + }, + { + "network":"10::1/128", + "next_hop":"Null0" + } + ] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "1.200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link8": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "1.300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + "r3-link8": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link8": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "1.400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {}, + "r4-link8": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {}, + "r4-link8": {} + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_topo1.json b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_topo1.json new file mode 100644 index 0000000..02aacf7 --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/bgp_local_asn_dot_topo1.json @@ -0,0 +1,132 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": {"ipv4": "10.0.0.0", "v4mask": 30, "ipv6": "fd00::", "v6mask": 64}, + "lo_prefix": {"ipv4": "1.0.", "v4mask": 32, "ipv6": "2001:db8:f::", "v6mask": 128}, + "routers": { + "r1": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.100", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "r2": {"dest_link": {"r1": {}}} + } + } + } + } + }, + "static_routes":[ + { + "network":"10.1.1.0/32", + "next_hop":"Null0" + }, + { + "network":"10:1::1:0/128", + "next_hop":"Null0" + }] + }, + "r2": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {}}}, + "r3": {"dest_link": {"r2": {}}} + } + } + } + } + } + }, + "r3": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2": {"ipv4": "auto", "ipv6": "auto"}, + "r4": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {}}}, + "r4": {"dest_link": {"r3": {}}} + } + } + } + } + } + }, + "r4": { + "links": { + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "1.400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r4": {}}} + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_agg.py b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_agg.py new file mode 100644 index 0000000..cfaab9b --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_agg.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify the BGP Local AS functionality by aggregating routes in between eBGP Peers. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() +NETWORK_1_1 = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NETWORK_1_2 = {"ipv4": "10.1.2.0/32", "ipv6": "10:1::2:0/128"} +AGGREGATE_NW = {"ipv4": "10.1.0.0/16", "ipv6": "10:1::/96"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_dot_agg.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +#################################################################################################################### +# +# Testcases +# +#################################################################################################################### + + +def test_verify_bgp_local_as_agg_in_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by aggregating routes in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Done in base config: Advertise prefix 10.1.1.0/24 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/120 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK_1_1[addr_type]}]} + } + + input_static_verify_r2 = { + "r2": {"static_routes": [{"network": NETWORK_1_2[addr_type]}]} + } + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", input_static_verify_r2) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure aggregate-address to summarise all the advertised routes.") + for addr_type in ADDR_TYPES: + route_aggregate = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + { + "network": AGGREGATE_NW[addr_type], + "summary": True, + "as_set": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that we see a summarised route on advertising router R3 " + "and receiving router R4 for both AFIs" + ) + + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + input_static_r1 = { + "r1": {"static_routes": [{"network": [NETWORK_1_1[addr_type]]}]} + } + + input_static_r2 = { + "r2": {"static_routes": [{"network": [NETWORK_1_2[addr_type]]}]} + } + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_agg_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1", "r2"], [input_static_r1, input_static_r2]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 1.110 {1.100,1.110,1.200} by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "{1.100,1.110,1.200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "{1.100,1.200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 {1.100,1.200}" + for addr_type in ADDR_TYPES: + input_static_agg_r1 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + result = verify_bgp_rib( + tgen, addr_type, dut, input_static_agg_r1, aspath=aspath + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_ecmp.py b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_ecmp.py new file mode 100644 index 0000000..6937a61 --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_ecmp.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# +# +########################################################################################################################################## +# +# Testcases +# +########################################################################################################################################### +########################################################################################################################################### +# +# 1.10.1.7. Verify the BGP Local AS functionality with ECMP on 8 links by adding no-prepend and replace-as command in between eBGP Peers. +# +################################################################################################################################################# + +import os +import sys +import time +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + check_address_types, + check_router_status, + create_static_routes, + verify_fib_routes, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + verify_bgp_rib, + create_router_bgp, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_dot_ecmp.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################################################## +# +# Testcases +# +########################################################################################################################################### + + +def test_verify_bgp_local_as_in_ecmp_EBGP_p0(request): + """ + Verify the BGP Local AS functionality with ECMP on 8 links by + adding no-prepend and replace-as command in between eBGP Peers. + """ + + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + dut = "r1" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = {"local_asn": {"local_as": "1.110"}} + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + dest_link = {} + for link_no in range(1, 9): + link = "r4-link" + str(link_no) + dest_link[link] = {"local_asn": {"remote_as": "1.110"}} + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "1.400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r3": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-110 is got added in the AS list 1.110 1.200 1.100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = {"local_asn": {"local_as": "1.110"}} + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + dest_link = {} + for link_no in range(1, 9): + link = "r3-link" + str(link_no) + dest_link[link] = { + "local_asn": {"local_as": "1.110", "no_prepend": True, "replace_as": True} + } + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": {"r4": {"dest_link": dest_link}} + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_topo1.py b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_topo1.py new file mode 100644 index 0000000..bacef47 --- /dev/null +++ b/tests/topotests/bgp_local_asn_dot/test_bgp_local_asn_dot_topo1.py @@ -0,0 +1,3692 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +########################################################################################################## +# +# Functionality Testcases +# +########################################################################################################## +""" +1. Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between eBGP Peers. +2. Verify the BGP Local AS functionality by configuring 4 Byte AS at R3 and 2 Byte AS at R2 & R4 in between eBGP Peers. +3. Verify that BGP Local AS functionality by performing graceful restart in between eBGP Peers. +4. Verify the BGP Local AS functionality by adding another AS & by same AS with AS-Prepend command in between eBGP Peers. +4. Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between iBGP Peers. +5. Verify the BGP Local AS functionality with allowas-in in between iBGP Peers. +6. Verify that BGP Local AS functionality by performing shut/ noshut on the interfaces in between BGP neighbors. +7. Verify that BGP Local AS functionality by restarting BGP,Zebra and FRR services and + further restarting clear BGP * and shutdown BGP neighbor. +8. Verify the BGP Local AS functionality with different AS configurations. +9. Verify the BGP Local AS functionality with R3& R4 with different AS configurations. +""" + +import os +import sys +import time +import pytest +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + create_static_routes, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + get_frr_ipv6_linklocal, + check_address_types, + check_router_status, + create_static_routes, + verify_fib_routes, + create_route_maps, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + clear_bgp_and_verify, + verify_bgp_rib, + modify_as_number, + create_router_bgp, + verify_bgp_advertised_routes_from_neighbor, + verify_graceful_restart, + verify_r_bit, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK = {"ipv4": "10.1.1.0/32", "ipv6": "10:1::1:0/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +NEXT_HOP_IP_GR = {"ipv4": "10.0.0.5", "ipv6": "fd00:0:0:1::2/64"} +NEXT_HOP_IP_1 = {"ipv4": "10.0.0.101", "ipv6": "fd00::1"} +NEXT_HOP_IP_2 = {"ipv4": "10.0.0.102", "ipv6": "fd00::2"} + +BGP_CONVERGENCE = False +PREFERRED_NEXT_HOP = "link_local" +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_local_asn_dot_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +########################################################################################################## +# +# Local APIs +# +########################################################################################################## + + +def configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut, peer): + """ + This function groups the repetitive function calls into one function. + """ + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + return True + + +def next_hop_per_address_family( + tgen, dut, peer, addr_type, next_hop_dict, preferred_next_hop=PREFERRED_NEXT_HOP +): + """ + This function returns link_local or global next_hop per address-family + """ + intferface = topo["routers"][peer]["links"]["{}".format(dut)]["interface"] + if addr_type == "ipv6" and "link_local" in preferred_next_hop: + next_hop = get_frr_ipv6_linklocal(tgen, peer, intf=intferface) + else: + next_hop = next_hop_dict[addr_type] + + return next_hop + + +########################################################################################################## +# +# Testcases +# +########################################################################################################## + + +def test_verify_bgp_local_as_in_EBGP_p0(request): + """ + Verify the BGP Local AS functionality by adding no-prepend and + replace-as command in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_4B_AS_mid_4B_AS_p0(request): + """ + Verify the BGP Local AS functionality by configuring 4 Byte AS + at R3 and 4 Byte AS at R2 & R4 in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "183.2926" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": { + "remote_as": "183.2926" + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-183.2926 is got added in the AS list 183.2926 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "183.2926 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "183.2926", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "183.2926", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "183.2926 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_GR_EBGP_p0(request): + """ + Verify that BGP Local AS functionality by performing graceful restart in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP_GR[addr_type], + } + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "1.400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": NEXT_HOP_IP_GR[addr_type], + } + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + """ + GR Steps : Helper BGP router R2, mark and unmark IPV4 routes + as stale as the restarting router R3 come up within the restart time + """ + # Create route-map to prefer global next-hop + input_dict = { + "r2": { + "route_maps": { + "rmap_global": [ + {"action": "permit", "set": {"ipv6": {"nexthop": "prefer-global"}}} + ] + } + }, + "r3": { + "route_maps": { + "rmap_global": [ + {"action": "permit", "set": {"ipv6": {"nexthop": "prefer-global"}}} + ] + } + }, + } + result = create_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_neigh_rm = { + "r2": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_neigh_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure graceful-restart + input_dict = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "graceful-restart-helper": True, + "local_asn": {"remote_as": "1.110"}, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "graceful-restart-helper": True, + "local_asn": {"remote_as": "1.110"}, + } + } + } + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"graceful-restart": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3": {"graceful-restart": True}}} + } + } + }, + } + } + }, + } + + configure_gr_followed_by_clear(tgen, topo, input_dict, tc_name, dut="r3", peer="r2") + for addr_type in ADDR_TYPES: + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + dut = "r2" + peer = "r3" + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + input_topo = {key: topo["routers"][key] for key in ["r3"]} + result = verify_bgp_rib(tgen, addr_type, dut, input_topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, "bgp") + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 2] : R3 goes for reload ") + + kill_router_daemons(tgen, "r3", ["bgpd"]) + + logger.info( + "[Phase 3] : R3 is still down, restart time 120 sec." + " So time verify the routes are present in BGP RIB" + " and ZEBRA" + ) + + for addr_type in ADDR_TYPES: + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + logger.info("[Phase 5] : R3 is about to come up now ") + start_router_daemons(tgen, "r3", ["bgpd"]) + + logger.info("[Phase 5] : R3 is UP Now ! ") + + for addr_type in ADDR_TYPES: + result = verify_bgp_convergence(tgen, topo) + assert ( + result is True + ), "BGP Convergence after BGPd restart" " :Failed \n Error:{}".format(result) + + # Verifying GR stats + result = verify_graceful_restart( + tgen, topo, addr_type, input_dict, dut="r3", peer="r2" + ) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_r_bit(tgen, topo, addr_type, input_dict, dut="r2", peer="r3") + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying BGP RIB routes + next_hop = next_hop_per_address_family( + tgen, dut, peer, addr_type, NEXT_HOP_IP_2, preferred_next_hop="global" + ) + result = verify_bgp_rib(tgen, addr_type, dut, input_topo, next_hop) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + # Verifying RIB routes + protocol = "bgp" + result = verify_rib(tgen, addr_type, dut, input_topo, next_hop, protocol) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_aspath_p0(request): + """ + Verify the BGP Local AS functionality by adding another AS & by same AS with AS-Prepend command in between eBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-map on R3 to prepend AS 2 times.") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r3": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": { + "as_num": "1.1000 1.1000", + "as_action": "prepend", + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure route map in out direction on R4") + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "ASP_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-1.300 is got replaced with 1.200 in the AS list 1.110 1.1000 1.1000 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r4" + aspath = "1.110 1.1000 1.1000 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_iBGP_p0(request): + """ + Verify the BGP Local AS functionality by adding no-prepend and replace-as command in between iBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Modify AS Number for R3") + input_dict_modify_as_number = {"r3": {"bgp": {"local_as": "1.200"}}} + result = modify_as_number(tgen, topo, input_dict_modify_as_number) + + step("Base config is done as part of JSON") + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "1.400", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "next_hop_self": True, + "local_asn": { + "remote_as": "1.200", + }, + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_allow_as_in_iBGP_p0(request): + """ + Verify the BGP Local AS functionality with allowas-in in between iBGP Peers. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Modidy AS Number for R4") + input_dict_modify_as_number = {"r4": {"bgp": {"local_as": "1.100"}}} + result = modify_as_number(tgen, topo, input_dict_modify_as_number) + + step("Base config is done as part of JSON") + dut = "r1" + for addr_type in ADDR_TYPES: + # Enable static routes + input_dict_static_route = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_dict_static_route_redist = { + "r1": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + ] + } + } + result = create_router_bgp(tgen, topo, input_dict_static_route_redist) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify IPv4 and IPv6 static routes received on R1") + result = verify_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + result = verify_fib_routes(tgen, addr_type, "r1", input_dict_static_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure allow-as at R4") + for addr_type in ADDR_TYPES: + allow_as_config_r4 = { + "r4": { + "bgp": [ + { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "allowas-in": { + "number_occurences": 1 + } + } + } + } + } + } + } + } + } + ] + } + } + + step( + "Configuring allow-as for {} address-family on router R4 ".format(addr_type) + ) + result = create_router_bgp(tgen, topo, allow_as_config_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + # now modify the as in r4 and reconfig bgp in r3 with new remote as. + topo1 = deepcopy(topo) + topo1["routers"]["r4"]["bgp"]["local_as"] = "1.100" + + delete_bgp = {"r3": {"bgp": {"delete": True}}} + result = create_router_bgp(tgen, topo1, delete_bgp) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + build_config_from_json(tgen, topo1, save_bkup=False) + + step("Configure local-as at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R2 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r2_to_r3 = { + "r2": { + "bgp": [ + { + "local_as": "1.200", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r2_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure remote-as at R4 towards R3.") + for addr_type in ADDR_TYPES: + input_dict_r4_to_r3 = { + "r4": { + "bgp": [ + { + "local_as": "1.100", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_r4_to_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify IPv4 and IPv6 static routes received on R3 & R4") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + for dut in ["r3", "r4"]: + result = verify_fib_routes(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following " + " commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R2.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4.") + for addr_type in ADDR_TYPES: + input_dict_no_prep_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r2": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R2") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r2 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_rep_as_r3_to_r2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4") + for addr_type in ADDR_TYPES: + input_dict_no_prep_rep_as_r3_to_r4 = { + "r3": { + "bgp": [ + { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + ] + } + } + result = create_router_bgp(tgen, topo1, input_dict_no_prep_rep_as_r3_to_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo1) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_port_reset_p0(request): + """ + Verify that BGP Local AS functionality by performing shut/ noshut on the interfaces in between BGP neighbors. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step("Api call to modfiy BGP timers at R3") + for addr_type in ADDR_TYPES: + input_dict_r3_timers = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3_timers) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify advertised routes at R3 towards R4") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for count in range(1, 1): + step("Iteration {}".format(count)) + step("Shut down connecting interface between R3<<>>R4 on R3.") + + intf1 = topo["routers"]["r3"]["links"]["r4"]["interface"] + + interfaces = [intf1] + for intf in interfaces: + shutdown_bringup_interface(tgen, "r3", intf, False) + + step( + "On R3, all BGP peering in respective vrf instances go down" + " when the interface is shut" + ) + + result = verify_bgp_convergence(tgen, topo, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: BGP will not be converged \n " + "Error {}".format(tc_name, result) + ) + + step("BGP neighborship is verified after restart of r3") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_negative2_p0(request): + """ + Verify the BGP Local AS functionality with different AS configurations. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Verify advertised routes to R4 at R3") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that AS-1.110 is not prepended in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + step("Verify that AS-1.300 is replaced with AS-1.110 at R3 router.") + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # configure negative scenarios + step("Configure local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "1.300"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + step("Configure another local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.110", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "1.110"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_negative3_p0(request): + """ + Verify the BGP Local AS functionality with R3& R4 with different AS configurations. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Configure basic BGP Peerings between R1,R2,R3 and R4") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Perform Negative scenarios + step("Configure another local-as at R3 towards R4.") + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {"local_asn": {"local_as": "1.300"}} + } + } + } + } + } + }, + } + } + } + if "bgp" in topo["routers"]["r3"].keys(): + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is not True, ( + "Testcase {} :Failed \n " + "Expected Behaviour: Cannot have local-as same as BGP AS number \n " + "Error {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_verify_bgp_local_as_in_EBGP_restart_daemons_p0(request): + """ + Verify that BGP Local AS functionality by restarting BGP,Zebra and FRR services and + further restarting clear BGP * and shutdown BGP neighbor. + """ + tgen = get_topogen() + global BGP_CONVERGENCE + if BGP_CONVERGENCE != True: + pytest.skip("skipped because of BGP Convergence failure") + # test case name + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step("Base config is done as part of JSON") + step("Configure local-as at R3 towards R4.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": {"local_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for dut, asn, neighbor in zip(["r2", "r4"], ["1.200", "1.400"], ["r3", "r3"]): + input_dict_r2_r4 = { + dut: { + "bgp": { + "local_as": asn, + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + dut: { + "local_asn": {"remote_as": "1.110"} + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r2_r4) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + # configure static routes + step("Done in base config: Advertise prefix 10.1.1.0/32 from Router-1(AS-1.100).") + step( + "Done in base config: Advertise an ipv6 prefix 10:1::1:0/128 from Router-1(AS-1.100)." + ) + step("Verify that Static routes are redistributed in BGP process") + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + # Enable static routes + input_static_r1 = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": NEXT_HOP_IP[addr_type]} + ] + } + } + + logger.info("Configure static routes") + result = create_static_routes(tgen, input_static_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("configure redistribute static in Router BGP in R1") + input_static_redist_r1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_static_redist_r1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + for addr_type in ADDR_TYPES: + input_static_verify_r1 = { + "r1": {"static_routes": [{"network": NETWORK[addr_type]}]} + } + + result = verify_rib(tgen, addr_type, "r1", input_static_verify_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r3", "r4"]: + result = verify_rib(tgen, addr_type, dut, input_static_r1) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + for dut, input_routes in zip(["r1"], [input_static_r1]): + result = verify_rib(tgen, addr_type, dut, input_routes) + assert result is True, "Testcase {}: Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Kill BGPd daemon on R3.") + kill_router_daemons(tgen, "r3", ["bgpd"]) + + step("Bring up BGPd daemon on R3.") + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "Verify that AS-1.110 is got added in the AS list 1.110 1.200 1.100 by following" + "commands at R3 router." + ) + dut = "r3" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify advertised routes at R3 towards R4") + expected_routes = { + "ipv4": [ + {"network": "10.1.1.0/32", "nexthop": ""}, + ], + "ipv6": [ + {"network": "10:1::1:0/128", "nexthop": ""}, + ], + } + result = verify_bgp_advertised_routes_from_neighbor( + tgen, topo, dut="r3", peer="r4", expected_routes=expected_routes + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure local-as with no-prepend at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verify that AS-1.110 is not prepended in the AS list 1.200 1.100 by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Kill BGPd daemon on R3.") + kill_router_daemons(tgen, "r3", ["bgpd"]) + + step("Bring up BGPd daemon on R3.") + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "Verify that AS-1.110 is not prepended in the AS list 1.200 1.100 by following " + "commands at R3 router." + ) + dut = "r3" + aspath = "1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure local-as with no-prepend and replace-as at R3 towards R4 & R2.") + for addr_type in ADDR_TYPES: + for neighbor in ["r2", "r4"]: + input_dict_r3 = { + "r3": { + "bgp": { + "local_as": "1.300", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + neighbor: { + "dest_link": { + "r3": { + "local_asn": { + "local_as": "1.110", + "no_prepend": True, + "replace_as": True, + } + } + } + } + } + } + } + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict_r3) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("BGP neighborship is verified by following commands in R3 routers") + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "BGP convergence :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step( + "Verified that AS-1.300 is got replaced with original AS-1.110 at R4 by following commands" + ) + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verified that AS-1.300 is got replaced with original AS-1.110 at R4 by following commands" + ) + dut = "r4" + aspath = "1.110 1.200 1.100" + for addr_type in ADDR_TYPES: + input_static_r1 = {"r1": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib(tgen, addr_type, dut, input_static_r1, aspath=aspath) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf b/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf new file mode 100644 index 0000000..a31439c --- /dev/null +++ b/tests/topotests/bgp_lu_explicitnull/r1/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + bgp labeled-unicast explicit-null + neighbor 192.0.2.2 remote-as 65501 +! + address-family ipv4 unicast + no neighbor 192.0.2.2 activate + network 192.168.2.1/32 + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.0.2.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf b/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf new file mode 100644 index 0000000..b845748 --- /dev/null +++ b/tests/topotests/bgp_lu_explicitnull/r1/zebra.conf @@ -0,0 +1,6 @@ +interface r1-eth0 + ip address 192.0.2.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/32 +! \ No newline at end of file diff --git a/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf b/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf new file mode 100644 index 0000000..41c2b9b --- /dev/null +++ b/tests/topotests/bgp_lu_explicitnull/r2/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65501 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + bgp labeled-unicast explicit-null + neighbor 192.0.2.1 remote-as 65500 +! + address-family ipv4 unicast + no neighbor 192.0.2.1 activate + network 192.168.2.2/32 + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 192.0.2.1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf b/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf new file mode 100644 index 0000000..9a63961 --- /dev/null +++ b/tests/topotests/bgp_lu_explicitnull/r2/zebra.conf @@ -0,0 +1,6 @@ +interface r2-eth0 + ip address 192.0.2.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/32 +! diff --git a/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py b/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py new file mode 100644 index 0000000..0656e1e --- /dev/null +++ b/tests/topotests/bgp_lu_explicitnull/test_bgp_lu_explicitnull.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_explicitnull.py +# +# Part of NetDEF Topology Tests +# +# Copyright 2023 by 6WIND S.A. +# + +""" +test_bgp_lu_explicitnull.py: Test BGP LU label allocation +""" + +import os +import sys +import json +import functools +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +pytestmark = [pytest.mark.bgpd] + + +# Basic scenario for BGP-LU. Nodes are directly connected. +# The 192.168.2.2/32 prefix is advertised from r2 to r1 +# The explicit-null label should be used +# The 192.168.2.1/32 prefix is advertised from r1 to r2 +# The explicit-null label should be used +# Traffic from 192.168.2.1 to 192.168.2.2 should use explicit-null label +# +# AS65500 BGP-LU AS65501 +# +-----+ +-----+ +# | |.1 .2| | +# | 1 +----------------+ 2 + 192.168.0.2/32 +# | | 192.0.2.0/24 | | +# +-----+ +-----+ + + +def build_topo(tgen): + "Build function" + + # Create routers + tgen.add_router("r1") + tgen.add_router("r2") + + # r1-r2 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # r1 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + # r2 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + + # Skip if no mpls support + if not tgen.hasmpls: + logger.info("MPLS is not available, skipping test") + pytest.skip("MPLS is not available, skipping") + return + + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # Enable mpls input for routers, so we can ping + sval = "net.mpls.conf.{}.input" + topotest.sysctl_assure(router_list["r2"], sval.format("r2-eth0"), 1) + topotest.sysctl_assure(router_list["r1"], sval.format("r1-eth0"), 1) + + # For all registred routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def check_show_ip_label_prefix_found(router, ipversion, prefix, label): + output = json.loads( + router.vtysh_cmd("show {} route {} json".format(ipversion, prefix)) + ) + expected = { + prefix: [ + {"prefix": prefix, "nexthops": [{"fib": True, "labels": [int(label)]}]} + ] + } + return topotest.json_cmp(output, expected) + + +def test_converge_bgplu(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # tgen.mininet_cli(); + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + # Check r1 gets prefix 192.168.2.2/32 + test_func = functools.partial( + check_show_ip_label_prefix_found, + tgen.gears["r1"], + "ip", + "192.168.2.2/32", + "0", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, prefix 192.168.2.2/32 from r2 not present" + + # Check r2 gets prefix 192.168.2.1/32 + test_func = functools.partial( + check_show_ip_label_prefix_found, + tgen.gears["r2"], + "ip", + "192.168.2.1/32", + "0", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, prefix 192.168.2.1/32 from r1 not present" + + +def test_traffic_connectivity(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _check_ping(name, dest_addr, src_addr): + tgen = get_topogen() + output = tgen.gears[name].run( + "ping {} -c 1 -w 1 -I {}".format(dest_addr, src_addr) + ) + logger.info(output) + if " 0% packet loss" not in output: + return True + + logger.info("r1, check ping 192.168.2.2 from 192.168.2.1 is OK") + tgen = get_topogen() + func = functools.partial(_check_ping, "r1", "192.168.2.2", "192.168.2.1") + # tgen.mininet_cli() + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "r1, ping to 192.168.2.2 from 192.168.2.1 fails" + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_lu_topo1/R1/bgpd.conf b/tests/topotests/bgp_lu_topo1/R1/bgpd.conf new file mode 100644 index 0000000..a164152 --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R1/bgpd.conf @@ -0,0 +1,21 @@ +! +! debug bgp labelpool +! debug bgp zebra +! +router bgp 1 + bgp router-id 10.0.0.1 + timers bgp 3 9 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.2 remote-as 2 + neighbor 10.0.0.2 solo + neighbor 10.0.0.2 timers connect 10 +! + address-family ipv4 unicast + no neighbor 10.0.0.2 activate + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_topo1/R1/labelpool.summ.json b/tests/topotests/bgp_lu_topo1/R1/labelpool.summ.json new file mode 100644 index 0000000..c66571f --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R1/labelpool.summ.json @@ -0,0 +1,6 @@ +{ + "ledger":506, + "inUse":506, + "requests":0, + "labelChunks":3 +} diff --git a/tests/topotests/bgp_lu_topo1/R1/zebra.conf b/tests/topotests/bgp_lu_topo1/R1/zebra.conf new file mode 100644 index 0000000..f8a9ce4 --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R1/zebra.conf @@ -0,0 +1,6 @@ +! debug zebra events +! debug zebra dplane +! debug zebra mpls +! +interface R1-eth0 + ip address 10.0.0.1/24 diff --git a/tests/topotests/bgp_lu_topo1/R2/bgpd.conf b/tests/topotests/bgp_lu_topo1/R2/bgpd.conf new file mode 100644 index 0000000..d35bbb8 --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R2/bgpd.conf @@ -0,0 +1,23 @@ +! debug bgp labelpool +! debug bgp zebra +! +router bgp 2 + bgp router-id 10.0.0.2 + timers bgp 3 9 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.1.3 remote-as 2 + neighbor 10.0.1.3 update-source 10.0.1.2 + neighbor 10.0.1.3 timers connect 10 + neighbor 10.0.0.1 remote-as 1 + neighbor 10.0.0.1 timers connect 10 +! + address-family ipv4 unicast + neighbor 10.0.1.3 activate + no neighbor 10.0.0.1 activate + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_topo1/R2/labelpool.summ.json b/tests/topotests/bgp_lu_topo1/R2/labelpool.summ.json new file mode 100644 index 0000000..17b9acc --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R2/labelpool.summ.json @@ -0,0 +1,6 @@ +{ + "ledger":0, + "inUse":0, + "requests":0, + "labelChunks":0 +} diff --git a/tests/topotests/bgp_lu_topo1/R2/zebra.conf b/tests/topotests/bgp_lu_topo1/R2/zebra.conf new file mode 100644 index 0000000..083da3e --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R2/zebra.conf @@ -0,0 +1,11 @@ +! +! debug zebra events +! debug zebra dplane +! debug zebra mpls +! +interface R2-eth0 + ip address 10.0.0.2/24 +! +interface R2-eth1 + ip address 10.0.1.2/24 +! \ No newline at end of file diff --git a/tests/topotests/bgp_lu_topo1/R3/bgpd.conf b/tests/topotests/bgp_lu_topo1/R3/bgpd.conf new file mode 100644 index 0000000..31d26ea --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R3/bgpd.conf @@ -0,0 +1,523 @@ +log file /tmp/bgpd.log +! +! debug bgp updates +! +router bgp 2 + bgp router-id 10.0.1.3 + timers bgp 3 9 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.1.2 remote-as 2 + neighbor 10.0.1.2 timers connect 10 + ! + address-family ipv4 unicast + neighbor 10.0.1.2 activate + network 11.0.0.1/32 + network 11.0.0.2/32 + network 11.0.0.3/32 + network 11.0.0.4/32 + network 11.0.0.5/32 + network 11.0.0.6/32 + network 11.0.0.7/32 + network 11.0.0.8/32 + network 11.0.0.9/32 + network 11.0.0.10/32 + network 11.0.0.11/32 + network 11.0.0.12/32 + network 11.0.0.13/32 + network 11.0.0.14/32 + network 11.0.0.15/32 + network 11.0.0.16/32 + network 11.0.0.17/32 + network 11.0.0.18/32 + network 11.0.0.19/32 + network 11.0.0.20/32 + network 11.0.0.21/32 + network 11.0.0.22/32 + network 11.0.0.23/32 + network 11.0.0.24/32 + network 11.0.0.25/32 + network 11.0.0.26/32 + network 11.0.0.27/32 + network 11.0.0.28/32 + network 11.0.0.29/32 + network 11.0.0.30/32 + network 11.0.0.31/32 + network 11.0.0.32/32 + network 11.0.0.33/32 + network 11.0.0.34/32 + network 11.0.0.35/32 + network 11.0.0.36/32 + network 11.0.0.37/32 + network 11.0.0.38/32 + network 11.0.0.39/32 + network 11.0.0.40/32 + network 11.0.0.41/32 + network 11.0.0.42/32 + network 11.0.0.43/32 + network 11.0.0.44/32 + network 11.0.0.45/32 + network 11.0.0.46/32 + network 11.0.0.47/32 + network 11.0.0.48/32 + network 11.0.0.49/32 + network 11.0.0.50/32 + network 11.0.0.51/32 + network 11.0.0.52/32 + network 11.0.0.53/32 + network 11.0.0.54/32 + network 11.0.0.55/32 + network 11.0.0.56/32 + network 11.0.0.57/32 + network 11.0.0.58/32 + network 11.0.0.59/32 + network 11.0.0.60/32 + network 11.0.0.61/32 + network 11.0.0.62/32 + network 11.0.0.63/32 + network 11.0.0.64/32 + network 11.0.0.65/32 + network 11.0.0.66/32 + network 11.0.0.67/32 + network 11.0.0.68/32 + network 11.0.0.69/32 + network 11.0.0.70/32 + network 11.0.0.71/32 + network 11.0.0.72/32 + network 11.0.0.73/32 + network 11.0.0.74/32 + network 11.0.0.75/32 + network 11.0.0.76/32 + network 11.0.0.77/32 + network 11.0.0.78/32 + network 11.0.0.79/32 + network 11.0.0.80/32 + network 11.0.0.81/32 + network 11.0.0.82/32 + network 11.0.0.83/32 + network 11.0.0.84/32 + network 11.0.0.85/32 + network 11.0.0.86/32 + network 11.0.0.87/32 + network 11.0.0.88/32 + network 11.0.0.89/32 + network 11.0.0.90/32 + network 11.0.0.91/32 + network 11.0.0.92/32 + network 11.0.0.93/32 + network 11.0.0.94/32 + network 11.0.0.95/32 + network 11.0.0.96/32 + network 11.0.0.97/32 + network 11.0.0.98/32 + network 11.0.0.99/32 + network 11.0.0.100/32 + network 11.0.0.101/32 + network 11.0.0.102/32 + network 11.0.0.103/32 + network 11.0.0.104/32 + network 11.0.0.105/32 + network 11.0.0.106/32 + network 11.0.0.107/32 + network 11.0.0.108/32 + network 11.0.0.109/32 + network 11.0.0.110/32 + network 11.0.0.111/32 + network 11.0.0.112/32 + network 11.0.0.113/32 + network 11.0.0.114/32 + network 11.0.0.115/32 + network 11.0.0.116/32 + network 11.0.0.117/32 + network 11.0.0.118/32 + network 11.0.0.119/32 + network 11.0.0.120/32 + network 11.0.0.121/32 + network 11.0.0.122/32 + network 11.0.0.123/32 + network 11.0.0.124/32 + network 11.0.0.125/32 + network 11.0.0.126/32 + network 11.0.0.127/32 + network 11.0.0.128/32 + network 11.0.0.129/32 + network 11.0.0.130/32 + network 11.0.0.131/32 + network 11.0.0.132/32 + network 11.0.0.133/32 + network 11.0.0.134/32 + network 11.0.0.135/32 + network 11.0.0.136/32 + network 11.0.0.137/32 + network 11.0.0.138/32 + network 11.0.0.139/32 + network 11.0.0.140/32 + network 11.0.0.141/32 + network 11.0.0.142/32 + network 11.0.0.143/32 + network 11.0.0.144/32 + network 11.0.0.145/32 + network 11.0.0.146/32 + network 11.0.0.147/32 + network 11.0.0.148/32 + network 11.0.0.149/32 + network 11.0.0.150/32 + network 11.0.0.151/32 + network 11.0.0.152/32 + network 11.0.0.153/32 + network 11.0.0.154/32 + network 11.0.0.155/32 + network 11.0.0.156/32 + network 11.0.0.157/32 + network 11.0.0.158/32 + network 11.0.0.159/32 + network 11.0.0.160/32 + network 11.0.0.161/32 + network 11.0.0.162/32 + network 11.0.0.163/32 + network 11.0.0.164/32 + network 11.0.0.165/32 + network 11.0.0.166/32 + network 11.0.0.167/32 + network 11.0.0.168/32 + network 11.0.0.169/32 + network 11.0.0.170/32 + network 11.0.0.171/32 + network 11.0.0.172/32 + network 11.0.0.173/32 + network 11.0.0.174/32 + network 11.0.0.175/32 + network 11.0.0.176/32 + network 11.0.0.177/32 + network 11.0.0.178/32 + network 11.0.0.179/32 + network 11.0.0.180/32 + network 11.0.0.181/32 + network 11.0.0.182/32 + network 11.0.0.183/32 + network 11.0.0.184/32 + network 11.0.0.185/32 + network 11.0.0.186/32 + network 11.0.0.187/32 + network 11.0.0.188/32 + network 11.0.0.189/32 + network 11.0.0.190/32 + network 11.0.0.191/32 + network 11.0.0.192/32 + network 11.0.0.193/32 + network 11.0.0.194/32 + network 11.0.0.195/32 + network 11.0.0.196/32 + network 11.0.0.197/32 + network 11.0.0.198/32 + network 11.0.0.199/32 + network 11.0.0.200/32 + network 11.0.0.201/32 + network 11.0.0.202/32 + network 11.0.0.203/32 + network 11.0.0.204/32 + network 11.0.0.205/32 + network 11.0.0.206/32 + network 11.0.0.207/32 + network 11.0.0.208/32 + network 11.0.0.209/32 + network 11.0.0.210/32 + network 11.0.0.211/32 + network 11.0.0.212/32 + network 11.0.0.213/32 + network 11.0.0.214/32 + network 11.0.0.215/32 + network 11.0.0.216/32 + network 11.0.0.217/32 + network 11.0.0.218/32 + network 11.0.0.219/32 + network 11.0.0.220/32 + network 11.0.0.221/32 + network 11.0.0.222/32 + network 11.0.0.223/32 + network 11.0.0.224/32 + network 11.0.0.225/32 + network 11.0.0.226/32 + network 11.0.0.227/32 + network 11.0.0.228/32 + network 11.0.0.229/32 + network 11.0.0.230/32 + network 11.0.0.231/32 + network 11.0.0.232/32 + network 11.0.0.233/32 + network 11.0.0.234/32 + network 11.0.0.235/32 + network 11.0.0.236/32 + network 11.0.0.237/32 + network 11.0.0.238/32 + network 11.0.0.239/32 + network 11.0.0.240/32 + network 11.0.0.241/32 + network 11.0.0.242/32 + network 11.0.0.243/32 + network 11.0.0.244/32 + network 11.0.0.245/32 + network 11.0.0.246/32 + network 11.0.0.247/32 + network 11.0.0.248/32 + network 11.0.0.249/32 + network 11.0.0.250/32 + network 11.0.0.251/32 + network 11.0.0.252/32 + network 11.0.0.253/32 + network 11.0.1.1/32 + network 11.0.1.2/32 + network 11.0.1.3/32 + network 11.0.1.4/32 + network 11.0.1.5/32 + network 11.0.1.6/32 + network 11.0.1.7/32 + network 11.0.1.8/32 + network 11.0.1.9/32 + network 11.0.1.10/32 + network 11.0.1.11/32 + network 11.0.1.12/32 + network 11.0.1.13/32 + network 11.0.1.14/32 + network 11.0.1.15/32 + network 11.0.1.16/32 + network 11.0.1.17/32 + network 11.0.1.18/32 + network 11.0.1.19/32 + network 11.0.1.20/32 + network 11.0.1.21/32 + network 11.0.1.22/32 + network 11.0.1.23/32 + network 11.0.1.24/32 + network 11.0.1.25/32 + network 11.0.1.26/32 + network 11.0.1.27/32 + network 11.0.1.28/32 + network 11.0.1.29/32 + network 11.0.1.30/32 + network 11.0.1.31/32 + network 11.0.1.32/32 + network 11.0.1.33/32 + network 11.0.1.34/32 + network 11.0.1.35/32 + network 11.0.1.36/32 + network 11.0.1.37/32 + network 11.0.1.38/32 + network 11.0.1.39/32 + network 11.0.1.40/32 + network 11.0.1.41/32 + network 11.0.1.42/32 + network 11.0.1.43/32 + network 11.0.1.44/32 + network 11.0.1.45/32 + network 11.0.1.46/32 + network 11.0.1.47/32 + network 11.0.1.48/32 + network 11.0.1.49/32 + network 11.0.1.50/32 + network 11.0.1.51/32 + network 11.0.1.52/32 + network 11.0.1.53/32 + network 11.0.1.54/32 + network 11.0.1.55/32 + network 11.0.1.56/32 + network 11.0.1.57/32 + network 11.0.1.58/32 + network 11.0.1.59/32 + network 11.0.1.60/32 + network 11.0.1.61/32 + network 11.0.1.62/32 + network 11.0.1.63/32 + network 11.0.1.64/32 + network 11.0.1.65/32 + network 11.0.1.66/32 + network 11.0.1.67/32 + network 11.0.1.68/32 + network 11.0.1.69/32 + network 11.0.1.70/32 + network 11.0.1.71/32 + network 11.0.1.72/32 + network 11.0.1.73/32 + network 11.0.1.74/32 + network 11.0.1.75/32 + network 11.0.1.76/32 + network 11.0.1.77/32 + network 11.0.1.78/32 + network 11.0.1.79/32 + network 11.0.1.80/32 + network 11.0.1.81/32 + network 11.0.1.82/32 + network 11.0.1.83/32 + network 11.0.1.84/32 + network 11.0.1.85/32 + network 11.0.1.86/32 + network 11.0.1.87/32 + network 11.0.1.88/32 + network 11.0.1.89/32 + network 11.0.1.90/32 + network 11.0.1.91/32 + network 11.0.1.92/32 + network 11.0.1.93/32 + network 11.0.1.94/32 + network 11.0.1.95/32 + network 11.0.1.96/32 + network 11.0.1.97/32 + network 11.0.1.98/32 + network 11.0.1.99/32 + network 11.0.1.100/32 + network 11.0.1.101/32 + network 11.0.1.102/32 + network 11.0.1.103/32 + network 11.0.1.104/32 + network 11.0.1.105/32 + network 11.0.1.106/32 + network 11.0.1.107/32 + network 11.0.1.108/32 + network 11.0.1.109/32 + network 11.0.1.110/32 + network 11.0.1.111/32 + network 11.0.1.112/32 + network 11.0.1.113/32 + network 11.0.1.114/32 + network 11.0.1.115/32 + network 11.0.1.116/32 + network 11.0.1.117/32 + network 11.0.1.118/32 + network 11.0.1.119/32 + network 11.0.1.120/32 + network 11.0.1.121/32 + network 11.0.1.122/32 + network 11.0.1.123/32 + network 11.0.1.124/32 + network 11.0.1.125/32 + network 11.0.1.126/32 + network 11.0.1.127/32 + network 11.0.1.128/32 + network 11.0.1.129/32 + network 11.0.1.130/32 + network 11.0.1.131/32 + network 11.0.1.132/32 + network 11.0.1.133/32 + network 11.0.1.134/32 + network 11.0.1.135/32 + network 11.0.1.136/32 + network 11.0.1.137/32 + network 11.0.1.138/32 + network 11.0.1.139/32 + network 11.0.1.140/32 + network 11.0.1.141/32 + network 11.0.1.142/32 + network 11.0.1.143/32 + network 11.0.1.144/32 + network 11.0.1.145/32 + network 11.0.1.146/32 + network 11.0.1.147/32 + network 11.0.1.148/32 + network 11.0.1.149/32 + network 11.0.1.150/32 + network 11.0.1.151/32 + network 11.0.1.152/32 + network 11.0.1.153/32 + network 11.0.1.154/32 + network 11.0.1.155/32 + network 11.0.1.156/32 + network 11.0.1.157/32 + network 11.0.1.158/32 + network 11.0.1.159/32 + network 11.0.1.160/32 + network 11.0.1.161/32 + network 11.0.1.162/32 + network 11.0.1.163/32 + network 11.0.1.164/32 + network 11.0.1.165/32 + network 11.0.1.166/32 + network 11.0.1.167/32 + network 11.0.1.168/32 + network 11.0.1.169/32 + network 11.0.1.170/32 + network 11.0.1.171/32 + network 11.0.1.172/32 + network 11.0.1.173/32 + network 11.0.1.174/32 + network 11.0.1.175/32 + network 11.0.1.176/32 + network 11.0.1.177/32 + network 11.0.1.178/32 + network 11.0.1.179/32 + network 11.0.1.180/32 + network 11.0.1.181/32 + network 11.0.1.182/32 + network 11.0.1.183/32 + network 11.0.1.184/32 + network 11.0.1.185/32 + network 11.0.1.186/32 + network 11.0.1.187/32 + network 11.0.1.188/32 + network 11.0.1.189/32 + network 11.0.1.190/32 + network 11.0.1.191/32 + network 11.0.1.192/32 + network 11.0.1.193/32 + network 11.0.1.194/32 + network 11.0.1.195/32 + network 11.0.1.196/32 + network 11.0.1.197/32 + network 11.0.1.198/32 + network 11.0.1.199/32 + network 11.0.1.200/32 + network 11.0.1.201/32 + network 11.0.1.202/32 + network 11.0.1.203/32 + network 11.0.1.204/32 + network 11.0.1.205/32 + network 11.0.1.206/32 + network 11.0.1.207/32 + network 11.0.1.208/32 + network 11.0.1.209/32 + network 11.0.1.210/32 + network 11.0.1.211/32 + network 11.0.1.212/32 + network 11.0.1.213/32 + network 11.0.1.214/32 + network 11.0.1.215/32 + network 11.0.1.216/32 + network 11.0.1.217/32 + network 11.0.1.218/32 + network 11.0.1.219/32 + network 11.0.1.220/32 + network 11.0.1.221/32 + network 11.0.1.222/32 + network 11.0.1.223/32 + network 11.0.1.224/32 + network 11.0.1.225/32 + network 11.0.1.226/32 + network 11.0.1.227/32 + network 11.0.1.228/32 + network 11.0.1.229/32 + network 11.0.1.230/32 + network 11.0.1.231/32 + network 11.0.1.232/32 + network 11.0.1.233/32 + network 11.0.1.234/32 + network 11.0.1.235/32 + network 11.0.1.236/32 + network 11.0.1.237/32 + network 11.0.1.238/32 + network 11.0.1.239/32 + network 11.0.1.240/32 + network 11.0.1.241/32 + network 11.0.1.242/32 + network 11.0.1.243/32 + network 11.0.1.244/32 + network 11.0.1.245/32 + network 11.0.1.246/32 + network 11.0.1.247/32 + network 11.0.1.248/32 + network 11.0.1.249/32 + network 11.0.1.250/32 + network 11.0.1.251/32 + network 11.0.1.252/32 + network 11.0.1.253/32 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_lu_topo1/R3/zebra.conf b/tests/topotests/bgp_lu_topo1/R3/zebra.conf new file mode 100644 index 0000000..ea4a148 --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/R3/zebra.conf @@ -0,0 +1,9 @@ +log file /tmp/zebra.log +! +! debug zebra events +! debug zebra packet detail +! debug zebra mpls +! +interface R3-eth0 + ip address 10.0.1.3/24 +! diff --git a/tests/topotests/bgp_lu_topo1/test_bgp_lu.py b/tests/topotests/bgp_lu_topo1/test_bgp_lu.py new file mode 100644 index 0000000..7d59762 --- /dev/null +++ b/tests/topotests/bgp_lu_topo1/test_bgp_lu.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_lu.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_lu.py: Test BGP LU label allocation +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +# Basic scenario for BGP-LU. Nodes are directly connected. +# Node 3 is advertising many routes to 2, which advertises them +# as BGP-LU to 1; this way we get routes with actual labels, as +# opposed to implicit-null routes in the 2-node case. +# +# AS1 BGP-LU AS2 iBGP AS2 +# +-----+ +-----+ +-----+ +# | |.1 .2| |.2 .3| | +# | 1 +----------------+ 2 +-----------------+ 3 | +# | | 10.0.0.0/24 | | 10.0.1.0/24 | | +# +-----+ +-----+ +-----+ + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # + # Create routers + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + + # R1-R2 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["R1"]) + switch.add_link(tgen.gears["R2"]) + + # R2-R3 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["R2"]) + switch.add_link(tgen.gears["R3"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def check_labelpool(router): + json_file = "{}/{}/labelpool.summ.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bgp labelpool summary json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches - Did not converge'.format(router.name) + assert result is None, assertmsg + + +def test_converge_bgplu(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # tgen.mininet_cli(); + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + + check_labelpool(r1) + check_labelpool(r2) + + +def test_clear_bgplu(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # tgen.mininet_cli(); + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + + r1.vtysh_cmd("clear bgp 10.0.0.2") + check_labelpool(r1) + check_labelpool(r2) + + r2.vtysh_cmd("clear bgp 10.0.1.3") + check_labelpool(r1) + check_labelpool(r2) + + r1.vtysh_cmd("clear bgp 10.0.0.2") + r2.vtysh_cmd("clear bgp 10.0.1.3") + check_labelpool(r1) + check_labelpool(r2) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_lu_topo2/R1/bgpd.conf b/tests/topotests/bgp_lu_topo2/R1/bgpd.conf new file mode 100644 index 0000000..9fe4026 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R1/bgpd.conf @@ -0,0 +1,29 @@ +! +no log unique-id +! +! debug bgp labelpool +! debug bgp zebra +! +router bgp 1 + bgp router-id 10.0.0.1 + timers bgp 3 9 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.2 remote-as 2 +! neighbor 10.0.0.2 solo + neighbor 10.0.0.2 timers connect 10 + neighbor 10.0.4.4 remote-as 4 +! neighbor 10.0.4.4 solo + neighbor 10.0.4.4 timers connect 10 +! + address-family ipv4 unicast + no neighbor 10.0.0.2 activate + no neighbor 10.0.4.4 activate + redistribute connected + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.2 activate + neighbor 10.0.4.4 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_topo2/R1/labelpool.summ.json b/tests/topotests/bgp_lu_topo2/R1/labelpool.summ.json new file mode 100644 index 0000000..faeaa3e --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R1/labelpool.summ.json @@ -0,0 +1,6 @@ +{ + "ledger":51, + "inUse":51, + "requests":0, + "labelChunks":1 +} diff --git a/tests/topotests/bgp_lu_topo2/R1/zebra.conf b/tests/topotests/bgp_lu_topo2/R1/zebra.conf new file mode 100644 index 0000000..64c34a3 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R1/zebra.conf @@ -0,0 +1,13 @@ +! +no log unique-id +! +! debug zebra events +! debug zebra rib det +! debug zebra dplane +! debug zebra mpls +! +interface R1-eth0 + ip address 10.0.0.1/24 +! +interface R1-eth1 + ip address 10.0.4.1/24 diff --git a/tests/topotests/bgp_lu_topo2/R2/bgpd.conf b/tests/topotests/bgp_lu_topo2/R2/bgpd.conf new file mode 100644 index 0000000..917060c --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R2/bgpd.conf @@ -0,0 +1,27 @@ +! +no log unique-id +! +! debug bgp labelpool +! debug bgp zebra +! +router bgp 2 + bgp router-id 10.0.0.2 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 9 + neighbor 10.0.0.1 remote-as 1 + neighbor 10.0.0.1 timers connect 10 + neighbor 10.0.1.3 remote-as 2 + neighbor 10.0.1.3 update-source 10.0.1.2 + neighbor 10.0.1.3 timers connect 10 +! + address-family ipv4 unicast + network 10.0.0.0/24 + neighbor 10.0.1.3 activate + no neighbor 10.0.0.1 activate + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_topo2/R2/labelpool.summ.json b/tests/topotests/bgp_lu_topo2/R2/labelpool.summ.json new file mode 100644 index 0000000..5f9d8e6 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R2/labelpool.summ.json @@ -0,0 +1,6 @@ +{ + "ledger":1, + "inUse":1, + "requests":0, + "labelChunks":1 +} diff --git a/tests/topotests/bgp_lu_topo2/R2/zebra.conf b/tests/topotests/bgp_lu_topo2/R2/zebra.conf new file mode 100644 index 0000000..f465914 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R2/zebra.conf @@ -0,0 +1,14 @@ +! +no log unique-id +! +! debug zebra events +! debug zebra dplane +! debug zebra mpls +! debug zebra rib det +! +interface R2-eth0 + ip address 10.0.0.2/24 +! +interface R2-eth1 + ip address 10.0.1.2/24 +! \ No newline at end of file diff --git a/tests/topotests/bgp_lu_topo2/R3/bgpd.conf b/tests/topotests/bgp_lu_topo2/R3/bgpd.conf new file mode 100644 index 0000000..6443445 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R3/bgpd.conf @@ -0,0 +1,70 @@ +log file /tmp/bgpd.log +no log unique-id +! +! +! debug bgp updates +! +router bgp 2 + bgp router-id 10.0.1.3 + no bgp ebgp-requires-policy + no bgp network import-check + timers bgp 3 9 + neighbor 10.0.1.2 remote-as 2 + neighbor 10.0.1.2 timers connect 10 + ! + address-family ipv4 unicast + neighbor 10.0.1.2 activate + network 10.0.1.0/24 + network 11.0.0.1/32 + network 11.0.0.2/32 + network 11.0.0.3/32 + network 11.0.0.4/32 + network 11.0.0.5/32 + network 11.0.0.6/32 + network 11.0.0.7/32 + network 11.0.0.8/32 + network 11.0.0.9/32 + network 11.0.0.10/32 + network 11.0.0.11/32 + network 11.0.0.12/32 + network 11.0.0.13/32 + network 11.0.0.14/32 + network 11.0.0.15/32 + network 11.0.0.16/32 + network 11.0.0.17/32 + network 11.0.0.18/32 + network 11.0.0.19/32 + network 11.0.0.20/32 + network 11.0.0.21/32 + network 11.0.0.22/32 + network 11.0.0.23/32 + network 11.0.0.24/32 + network 11.0.0.25/32 + network 11.0.0.26/32 + network 11.0.0.27/32 + network 11.0.0.28/32 + network 11.0.0.29/32 + network 11.0.0.30/32 + network 11.0.0.31/32 + network 11.0.0.32/32 + network 11.0.0.33/32 + network 11.0.0.34/32 + network 11.0.0.35/32 + network 11.0.0.36/32 + network 11.0.0.37/32 + network 11.0.0.38/32 + network 11.0.0.39/32 + network 11.0.0.40/32 + network 11.0.0.41/32 + network 11.0.0.42/32 + network 11.0.0.43/32 + network 11.0.0.44/32 + network 11.0.0.45/32 + network 11.0.0.46/32 + network 11.0.0.47/32 + network 11.0.0.48/32 + network 11.0.0.49/32 + network 11.0.0.50/32 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_lu_topo2/R3/staticd.conf b/tests/topotests/bgp_lu_topo2/R3/staticd.conf new file mode 100644 index 0000000..867fc5a --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R3/staticd.conf @@ -0,0 +1,5 @@ +log file /tmp/staticd.log +no log unique-id +! +! +ip route 10.0.4.0/24 10.0.1.2 diff --git a/tests/topotests/bgp_lu_topo2/R3/zebra.conf b/tests/topotests/bgp_lu_topo2/R3/zebra.conf new file mode 100644 index 0000000..dd24deb --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R3/zebra.conf @@ -0,0 +1,11 @@ +log file /tmp/zebra.log +no log unique-id +! +! +! debug zebra events +! debug zebra packet detail +! debug zebra mpls +! +interface R3-eth0 + ip address 10.0.1.3/24 +! diff --git a/tests/topotests/bgp_lu_topo2/R4/bgpd.conf b/tests/topotests/bgp_lu_topo2/R4/bgpd.conf new file mode 100644 index 0000000..45c81fb --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R4/bgpd.conf @@ -0,0 +1,23 @@ +! +no log unique-id +! +! debug bgp labelpool +! debug bgp zebra +! +router bgp 4 + bgp router-id 10.0.4.4 + timers bgp 3 9 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.4.1 remote-as 1 + neighbor 10.0.4.1 solo + neighbor 10.0.4.1 timers connect 10 +! + address-family ipv4 unicast + no neighbor 10.0.4.1 activate + exit-address-family + ! + address-family ipv4 labeled-unicast + neighbor 10.0.4.1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_lu_topo2/R4/zebra.conf b/tests/topotests/bgp_lu_topo2/R4/zebra.conf new file mode 100644 index 0000000..53ffe51 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/R4/zebra.conf @@ -0,0 +1,9 @@ +no log unique-id +! +! debug zebra events +! debug zebra dplane +! debug zebra mpls +! debug zebra rib det +! +interface R4-eth0 + ip address 10.0.4.4/24 diff --git a/tests/topotests/bgp_lu_topo2/test_bgp_lu2.py b/tests/topotests/bgp_lu_topo2/test_bgp_lu2.py new file mode 100644 index 0000000..13e6582 --- /dev/null +++ b/tests/topotests/bgp_lu_topo2/test_bgp_lu2.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_lu2.py +# +# Part of FRR/NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# Copyright (c) 2021 by Nvidia, Inc. +# + +""" +test_bgp_lu2.py: Test BGP LU label allocation +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + +# +# Basic scenario for BGP-LU. Nodes are directly connected. +# Node 3 is advertising routes to 2, which advertises them +# as BGP-LU to 1; this way we get routes with actual labels, as +# opposed to implicit-null routes in the 2-node case. +# +# R2 is an LER, with MPLS towards R1, and IP towards R3. R1 is an LSR, with +# MPLS on both sides. +# +# +# AS4 BGP-LU AS1 BGP-LU AS2 iBGP AS2 +# +-----+ +-----+ +-----+ +-----+ +# | |.4 .1| |.1 .2| |.2 .3| | +# | 4 +-------------+ 1 +----------------+ 2 +-----------------+ 3 | +# | | 10.0.4.0/24 | | 10.0.0.0/24 | | 10.0.1.0/24 | | +# +-----+ +-----+ +-----+ +-----+ +# +# + + +def build_topo(tgen): + "Build function" + + # This function's only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # + # Create routers + tgen.add_router("R1") + tgen.add_router("R2") + tgen.add_router("R3") + tgen.add_router("R4") + + # R1-R2 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["R1"]) + switch.add_link(tgen.gears["R2"]) + + # R2-R3 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["R2"]) + switch.add_link(tgen.gears["R3"]) + + # R1-R4 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["R1"]) + switch.add_link(tgen.gears["R4"]) + + +def setup_module(mod): + "Sets up the pytest environment" + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + + # Skip if no mpls support + if not tgen.hasmpls: + logger.info("MPLS is not available, skipping test") + pytest.skip("MPLS is not available, skipping") + return + + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + + # Enable mpls input for routers, so we can ping + sval = "net.mpls.conf.{}.input" + topotest.sysctl_assure(router_list["R2"], sval.format("R2-eth0"), 1) + topotest.sysctl_assure(router_list["R1"], sval.format("R1-eth0"), 1) + topotest.sysctl_assure(router_list["R1"], sval.format("R1-eth1"), 1) + topotest.sysctl_assure(router_list["R4"], sval.format("R4-eth0"), 1) + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Have static config for R3 too + if router == router_list["R3"]: + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def check_labelpool(router): + json_file = "{}/{}/labelpool.summ.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, router, "show bgp labelpool summary json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=1) + assertmsg = '"{}" JSON output mismatches - Did not converge'.format(router.name) + assert result is None, assertmsg + + +def test_converge_bgplu(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # TODO -- enable for debugging + # tgen.mininet_cli() + + r1 = tgen.gears["R1"] + r2 = tgen.gears["R2"] + + check_labelpool(r1) + check_labelpool(r2) + + +def test_ping(): + "Simple ping tests" + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # + logger.info("Ping from R2 to R3") + router = tgen.gears["R2"] + output = router.run("ping -c 4 -w 4 {}".format("10.0.1.3")) + assert " 0% packet loss" in output, "Ping R2->R3 FAILED" + logger.info("Ping from R2 to R3 ... success") + + # + logger.info("Ping from R4 to R2") + router = tgen.gears["R4"] + output = router.run("ping -c 4 -w 4 {}".format("10.0.0.2")) + assert " 0% packet loss" in output, "Ping R4->R2 FAILED" + logger.info("Ping from R4 to R2 ... success") + + # + logger.info("Ping from R4 to R3") + router = tgen.gears["R4"] + output = router.run("ping -c 4 -w 4 {}".format("10.0.1.3")) + assert " 0% packet loss" in output, "Ping R4->R3 FAILED" + logger.info("Ping from R4 to R3 ... success") + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_max_med_on_startup/__init__.py b/tests/topotests/bgp_max_med_on_startup/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_max_med_on_startup/r1/bgpd.conf b/tests/topotests/bgp_max_med_on_startup/r1/bgpd.conf new file mode 100644 index 0000000..41bf963 --- /dev/null +++ b/tests/topotests/bgp_max_med_on_startup/r1/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65001 + bgp max-med on-startup 5 777 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_max_med_on_startup/r1/zebra.conf b/tests/topotests/bgp_max_med_on_startup/r1/zebra.conf new file mode 100644 index 0000000..7c2ed09 --- /dev/null +++ b/tests/topotests/bgp_max_med_on_startup/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_max_med_on_startup/r2/bgpd.conf b/tests/topotests/bgp_max_med_on_startup/r2/bgpd.conf new file mode 100644 index 0000000..187713d --- /dev/null +++ b/tests/topotests/bgp_max_med_on_startup/r2/bgpd.conf @@ -0,0 +1,7 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65001 + neighbor 192.168.255.1 timers 3 10 + ! +! diff --git a/tests/topotests/bgp_max_med_on_startup/r2/zebra.conf b/tests/topotests/bgp_max_med_on_startup/r2/zebra.conf new file mode 100644 index 0000000..fd45c48 --- /dev/null +++ b/tests/topotests/bgp_max_med_on_startup/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_max_med_on_startup/test_bgp_max_med_on_startup.py b/tests/topotests/bgp_max_med_on_startup/test_bgp_max_med_on_startup.py new file mode 100644 index 0000000..a9810ba --- /dev/null +++ b/tests/topotests/bgp_max_med_on_startup/test_bgp_max_med_on_startup.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_max_med_on_startup.py +# +# Copyright (c) 2022 Rubicon Communications, LLC. +# + +""" +Test whether `bgp max-med on-startup (5-86400) [(0-4294967295)]` is working +correctly. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_max_med_on_startup(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = {"192.168.255.1": {"bgpState": "Established"}} + return topotest.json_cmp(output, expected) + + def _bgp_has_routes(router, metric): + output = json.loads( + router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 routes json") + ) + expected = {"routes": {"172.16.255.254/32": [{"metric": metric}]}} + return topotest.json_cmp(output, expected) + + # Check session is established + test_func = functools.partial(_bgp_converge, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed bgp convergence on r2" + + # Check metric has value of max-med + test_func = functools.partial(_bgp_has_routes, router2, 777) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "r2 does not receive routes with metric 777" + + # Check that when the max-med timer expires, metric is updated + test_func = functools.partial(_bgp_has_routes, router2, 0) + success, result = topotest.run_and_expect(test_func, None, count=16, wait=0.5) + assert result is None, "r2 does not receive routes with metric 0" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/__init__.py b/tests/topotests/bgp_maximum_prefix_invalid_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/r1/bgpd.conf b/tests/topotests/bgp_maximum_prefix_invalid_update/r1/bgpd.conf new file mode 100644 index 0000000..271a5bb --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_invalid_update/r1/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 192.168.255.2 prefix-list r2 out + exit-address-family + ! +! +ip prefix-list r2 seq 5 permit 172.16.255.253/32 +ip prefix-list r2 seq 10 permit 172.16.255.254/32 +! diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/r1/zebra.conf b/tests/topotests/bgp_maximum_prefix_invalid_update/r1/zebra.conf new file mode 100644 index 0000000..68c5021 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_invalid_update/r1/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 172.16.255.254/32 + ip address 172.16.255.253/32 +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/r2/bgpd.conf b/tests/topotests/bgp_maximum_prefix_invalid_update/r2/bgpd.conf new file mode 100644 index 0000000..cb30808 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_invalid_update/r2/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 + neighbor 192.168.255.1 maximum-prefix 1 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/r2/zebra.conf b/tests/topotests/bgp_maximum_prefix_invalid_update/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_invalid_update/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_maximum_prefix_invalid_update/test_bgp_maximum_prefix_invalid_update.py b/tests/topotests/bgp_maximum_prefix_invalid_update/test_bgp_maximum_prefix_invalid_update.py new file mode 100644 index 0000000..c6bdbc3 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_invalid_update/test_bgp_maximum_prefix_invalid_update.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_local_as_private_remove.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +bgp_maximum_prefix_invalid_update.py: +Test if unnecesarry UPDATE message like below: + +[Error] Error parsing NLRI +%NOTIFICATION: sent to neighbor X.X.X.X 3/10 (UPDATE Message Error/Invalid Network Field) 0 bytes + +is not sent if maximum-prefix count is overflow. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_maximum_prefix_invalid(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_parsing_nlri(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = { + "192.168.255.1": { + "lastNotificationReason": "Cease/Maximum Number of Prefixes Reached", + "lastResetDueTo": "BGP Notification send", + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_parsing_nlri) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Didn't send NOTIFICATION when hitting maximum-prefix" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_maximum_prefix_out/__init__.py b/tests/topotests/bgp_maximum_prefix_out/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_maximum_prefix_out/r1/bgpd.conf b/tests/topotests/bgp_maximum_prefix_out/r1/bgpd.conf new file mode 100644 index 0000000..adb594b --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_out/r1/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + neighbor 192.168.255.1 maximum-prefix-out 2 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_maximum_prefix_out/r1/zebra.conf b/tests/topotests/bgp_maximum_prefix_out/r1/zebra.conf new file mode 100644 index 0000000..2416225 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_out/r1/zebra.conf @@ -0,0 +1,13 @@ +! +interface lo + ip address 172.16.255.250/32 + ip address 172.16.255.251/32 + ip address 172.16.255.252/32 + ip address 172.16.255.253/32 + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_maximum_prefix_out/r2/bgpd.conf b/tests/topotests/bgp_maximum_prefix_out/r2/bgpd.conf new file mode 100644 index 0000000..229de2e --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_out/r2/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_maximum_prefix_out/r2/zebra.conf b/tests/topotests/bgp_maximum_prefix_out/r2/zebra.conf new file mode 100644 index 0000000..08dd374 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_out/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_maximum_prefix_out/test_bgp_maximum_prefix_out.py b/tests/topotests/bgp_maximum_prefix_out/test_bgp_maximum_prefix_out.py new file mode 100644 index 0000000..0b346f6 --- /dev/null +++ b/tests/topotests/bgp_maximum_prefix_out/test_bgp_maximum_prefix_out.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_maximum_prefix_out.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Donatas Abraitis +# + +""" +Test if `neighbor maximum-prefix-out ` is working +correctly. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_maximum_prefix_out(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + # format (router to configure, command, expected received prefixes on r2) + tests = [ + # test of the initial config + (None, 2), + # modifying the max-prefix-out value + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 4", + 4, + ), + # removing the max-prefix-out value + ( + "router bgp\n address-family ipv4\n no neighbor 192.168.255.1 maximum-prefix-out", + 6, + ), + # setting a max-prefix-out value + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 3", + 3, + ), + # setting a max-prefix-out value - higher than the total number of prefix + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 8", + 6, + ), + # adding a new prefix + ("router bgp\n int lo\n ip address 172.16.255.249/32", 7), + # setting a max-prefix-out value - lower than the total number of prefix + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 1", + 1, + ), + # adding a new prefix + ("router bgp\n int lo\n ip address 172.16.255.248/32", 1), + # removing the max-prefix-out value + ( + "router bgp\n address-family ipv4\n no neighbor 192.168.255.1 maximum-prefix-out 1", + 8, + ), + # test setting the existing neighbor into a peer-group with a max-prefix-out value + ( + """ + router bgp + neighbor test peer-group + neighbor test remote-as 65002 + neighbor test timers 3 10 + address-family ipv4 + neighbor test maximum-prefix-out 3 + ! + neighbor 192.168.255.1 peer-group test + """, + 3, + ), + # max-prefix-out value of the neighbor must take the precedence + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 4", + 4, + ), + ( + "router bgp\n address-family ipv4\n no neighbor 192.168.255.1 maximum-prefix-out", + 3, + ), + ( + """ + router bgp + no neighbor 192.168.255.1 peer-group test + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 + """, + 8, + ), + ( + "router bgp\n address-family ipv4\n neighbor 192.168.255.1 maximum-prefix-out 5", + 5, + ), + # test setting the existing neighbor with a max-pref-out value into a peer-group with a max-pref-out value + ("router bgp\n neighbor 192.168.255.1 peer-group test", 5), + ( + "router bgp\n address-family ipv4\n no neighbor 192.168.255.1 maximum-prefix-out 5", + 3, + ), + ] + + def _bgp_converge(router, nb_prefixes): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": { + "ipv4Unicast": {"acceptedPrefixCounter": nb_prefixes} + }, + } + } + return topotest.json_cmp(output, expected) + + for test in tests: + cfg, exp_prfxs = test + if cfg: + cmd = ( + """ + configure terminal + %s + """ + % cfg + ) + router1.vtysh_cmd(cmd) + + test_func = functools.partial(_bgp_converge, router2, exp_prfxs) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, 'Failed bgp convergence in "{}"'.format(router2) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_minimum_holdtime/__init__.py b/tests/topotests/bgp_minimum_holdtime/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_minimum_holdtime/r1/bgpd.conf b/tests/topotests/bgp_minimum_holdtime/r1/bgpd.conf new file mode 100644 index 0000000..847a2d4 --- /dev/null +++ b/tests/topotests/bgp_minimum_holdtime/r1/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65000 + bgp minimum-holdtime 20 + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 timers connect 10 +! diff --git a/tests/topotests/bgp_minimum_holdtime/r1/zebra.conf b/tests/topotests/bgp_minimum_holdtime/r1/zebra.conf new file mode 100644 index 0000000..e2c399e --- /dev/null +++ b/tests/topotests/bgp_minimum_holdtime/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_minimum_holdtime/r2/bgpd.conf b/tests/topotests/bgp_minimum_holdtime/r2/bgpd.conf new file mode 100644 index 0000000..6d1080c --- /dev/null +++ b/tests/topotests/bgp_minimum_holdtime/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 +! diff --git a/tests/topotests/bgp_minimum_holdtime/r2/zebra.conf b/tests/topotests/bgp_minimum_holdtime/r2/zebra.conf new file mode 100644 index 0000000..606c17b --- /dev/null +++ b/tests/topotests/bgp_minimum_holdtime/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_minimum_holdtime/test_bgp_minimum_holdtime.py b/tests/topotests/bgp_minimum_holdtime/test_bgp_minimum_holdtime.py new file mode 100755 index 0000000..9f4d968 --- /dev/null +++ b/tests/topotests/bgp_minimum_holdtime/test_bgp_minimum_holdtime.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Takemasa Imada +# + +""" +Test if minimum-holdtime works. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_minimum_holdtime(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_neighbor_check_if_notification_sent(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp neighbor 192.168.255.2 json") + ) + expected = { + "192.168.255.2": { + "connectionsEstablished": 0, + "lastNotificationReason": "OPEN Message Error/Unacceptable Hold Time", + "lastResetDueTo": "BGP Notification send", + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_neighbor_check_if_notification_sent) + success, result = topotest.run_and_expect(test_func, None, count=40, wait=0.5) + assert result is None, "Failed to send notification message\n" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_multi_vrf_topo1/bgp_multi_vrf_topo1.json b/tests/topotests/bgp_multi_vrf_topo1/bgp_multi_vrf_topo1.json new file mode 100644 index 0000000..327744d --- /dev/null +++ b/tests/topotests/bgp_multi_vrf_topo1/bgp_multi_vrf_topo1.json @@ -0,0 +1,884 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "red1": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": {} + } + } + } + } + } + } + } + ] + }, + "blue1": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "BLUE_A", + "id": "1" + }, + { + "name": "BLUE_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": {} + } + } + } + } + } + } + } + ] + }, + "r1": { + "links": { + "red1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "red1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "blue1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "blue1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": {} + } + }, + "r2": { + "dest_link": { + "r1-link1": + { "next_hop_self": true } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": {} + } + }, + "r2": { + "dest_link": { + "r1-link1": + { "next_hop_self": true } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": {} + } + }, + "r2": { + "dest_link": { + "r1-link2": + { "next_hop_self": true } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": {} + } + }, + "r2": { + "dest_link": { + "r1-link2": + { "next_hop_self": true } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": {} + } + }, + "r2": { + "dest_link": { + "r1-link3": + { "next_hop_self": true } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": {} + } + }, + "r2": { + "dest_link": { + "r1-link3": + { "next_hop_self": true } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": {} + } + }, + "r2": { + "dest_link": { + "r1-link4": + { "next_hop_self": true } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": {} + } + }, + "r2": { + "dest_link": { + "r1-link4": + { "next_hop_self": true } + } + } + } + } + } + } + } + ] + }, + "r2": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": {} + } + }, + "r3": { + "dest_link": { + "r2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": {} + } + }, + "r3": { + "dest_link": { + "r2-link2": {} + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": {} + } + }, + "r3": { + "dest_link": { + "r2-link3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": {} + } + }, + "r3": { + "dest_link": { + "r2-link3": {} + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": {} + } + }, + "r3": { + "dest_link": { + "r2-link4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": {} + } + }, + "r3": { + "dest_link": { + "r2-link4": {} + } + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "red2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "red2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "blue2-link1": {"ipv4": "auto", "ipv6": "autor3", "vrf": "BLUE_A"}, + "blue2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "red2": { + "dest_link": { + "r3-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + }, + "red2": { + "dest_link": { + "r3-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": {} + } + }, + "red2": { + "dest_link": { + "r3-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": {} + } + }, + "red2": { + "dest_link": { + "r3-link2": {} + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": {} + } + }, + "blue2": { + "dest_link": { + "r3-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": {} + } + }, + "blue2": { + "dest_link": { + "r3-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": {} + } + }, + "blue2": { + "dest_link": { + "r3-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": {} + } + }, + "blue2": { + "dest_link": { + "r3-link2": {} + } + } + } + } + } + } + } + ] + }, + "red2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": {} + } + } + } + } + } + } + } + ] + }, + "blue2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "BLUE_A", + "id": "1" + }, + { + "name": "BLUE_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": {} + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_multi_vrf_topo1/test_bgp_multi_vrf_topo1.py b/tests/topotests/bgp_multi_vrf_topo1/test_bgp_multi_vrf_topo1.py new file mode 100644 index 0000000..0d92a3c --- /dev/null +++ b/tests/topotests/bgp_multi_vrf_topo1/test_bgp_multi_vrf_topo1.py @@ -0,0 +1,6277 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF: + +FUNC_1: + Within each VRF, each address must be unambiguous on DUT. +FUNC_2: + Different VRFs can have ambiguous/overlapping + addresses on DUT. +FUNC_3: + Create static routes(IPv4+IPv6) associated to specific VRFs + and verify on DUT that same prefixes are present in corresponding + routing table. +FUNC_4_&_5: + Each VRF should be mapped with a unique VLAN on DUT + for traffic segregation, when using a single physical interface. +FUNC_6: + Advertise same set of prefixes from different VRFs + and verify on remote router that these prefixes are not + leaking to each other +FUNC_7: + Redistribute Static routes and verify on remote routers + that routes are advertised within specific VRF instance, which + those static routes belong to. +FUNC_8: + Test end to end traffic isolation based on VRF tables. +FUNC_9: + Use static routes for inter-vrf communication + (route-leaking) on DUT. +FUNC_10: + Verify intra-vrf and inter-vrf communication between + iBGP peers. +FUNC_11: + Verify intra-vrf and inter-vrf communication + between eBGP peers. +FUNC_12_a: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_12_b: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_12_c: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_12_d: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_12_e: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_12_f: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. +FUNC_13: + Configure a route-map on DUT to match traffic based + on a VRF interfaces. +FUNC_14: + Test VRF-lite with Static+BGP originated routes. +FUNC_15: + Configure prefix-lists on DUT and apply to BGP peers to + permit/deny prefixes. +FUNC_16_1: + Configure a route-map on DUT to match traffic based various + match/set causes. +FUNC_16_2: + Configure a route-map on DUT to match traffic based various + match/set causes. +FUNC_16_3: + Configure a route-map on DUT to match traffic based various + match/set causes. +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import iproute2_is_vrf_capable +from lib.common_config import ( + step, + verify_rib, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_interface_in_kernel, + create_bgp_community_lists, + check_router_status, + apply_raw_config, + required_linux_kernel_version, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_rib, + create_router_bgp, + verify_bgp_community, + verify_bgp_convergence, + verify_best_path_as_per_bgp_attribute, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NETWORK1_1 = {"ipv4": "1.1.1.1/32", "ipv6": "1::1/128"} +NETWORK1_2 = {"ipv4": "1.1.1.2/32", "ipv6": "1::2/128"} +NETWORK2_1 = {"ipv4": "2.1.1.1/32", "ipv6": "2::1/128"} +NETWORK2_2 = {"ipv4": "2.1.1.2/32", "ipv6": "2::2/128"} +NETWORK3_1 = {"ipv4": "3.1.1.1/32", "ipv6": "3::1/128"} +NETWORK3_2 = {"ipv4": "3.1.1.2/32", "ipv6": "3::2/128"} +NETWORK4_1 = {"ipv4": "4.1.1.1/32", "ipv6": "4::1/128"} +NETWORK4_2 = {"ipv4": "4.1.1.2/32", "ipv6": "4::2/128"} +NETWORK5_1 = {"ipv4": "5.1.1.1/32", "ipv6": "5::1/128"} +NETWORK5_2 = {"ipv4": "5.1.1.2/32", "ipv6": "5::2/128"} +NETWORK6_1 = {"ipv4": "6.1.1.1/32", "ipv6": "6::1/128"} +NETWORK6_2 = {"ipv4": "6.1.1.2/32", "ipv6": "6::2/128"} +NETWORK7_1 = {"ipv4": "7.1.1.1/32", "ipv6": "7::1/128"} +NETWORK7_2 = {"ipv4": "7.1.1.2/32", "ipv6": "7::2/128"} +NETWORK8_1 = {"ipv4": "8.1.1.1/32", "ipv6": "8::1/128"} +NETWORK8_2 = {"ipv4": "8.1.1.2/32", "ipv6": "8::2/128"} + +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +LOOPBACK_1 = { + "ipv4": "10.10.10.10/32", + "ipv6": "10::10:10/128", +} +LOOPBACK_2 = { + "ipv4": "20.20.20.20/32", + "ipv6": "20::20:20/128", +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + # iproute2 needs to support VRFs for this suite to run. + if not iproute2_is_vrf_capable(): + pytest.skip("Installed iproute2 version does not support VRFs") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_multi_vrf_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_address_unambiguous_within_each_vrf_p0(request): + """ + FUNC_1: + Within each VRF, each address must be unambiguous on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure a set of static routes(IPv4+IPv6) in " "RED_A on router RED-1") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure the same static routes(IPv4+IPv6) with a TAG value" + "of 500 in RED_A on router RED-1" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 500, + "vrf": "RED_A", + } + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = { + "red1": { + "bgp": { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes(IPv4+IPv6) is overridden and doesn't" + " have duplicate entries within VRF RED_A on router RED-1" + ) + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 500, + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, tag=500) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Make sure routes are not present in global routing table") + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n Expected Behaviour: Routes are not " + "present on Global Routing table \n Error {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_ambiguous_overlapping_addresses_in_different_vrfs_p0(request): + """ + FUNC_2: + Different VRFs can have ambiguous/overlapping + addresses on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure a set of static routes(IPv4+IPv6) in vrf RED_A" "on router RED-1") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure the same static routes(IPv4+IPv6) with a" + " TAG value of 500 in vrf RED_B on router RED-1" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 500, + "vrf": "RED_B", + } + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that RED_A has the static routes without any" " TAG value") + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_1, tag=500, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are present with tag value 500 \n Error: {}".format(tc_name, result) + ) + logger.info("Expected Behavior: {}".format(result)) + + step( + "Verify that RED_B has the same routes with TAG value " + "500 on same device RED-1" + ) + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 500, + "vrf": "RED_B", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, tag=500) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Make sure routes are not present in global routing table") + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n Expected Behaviour: Routes are not " + "present on Global Routing table \n Error {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_static_routes_associated_to_specific_vrfs_p0(request): + """ + FUNC_3: + Create static routes(IPv4+IPv6) associated to specific VRFs + and verify on DUT that same prefixes are present in corresponding + routing table. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Configure a set of unique static(IPv4+IPv6) routes in vrf" + " RED_A on router RED-1" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure set of unique static routes(IPv4+IPv6) in vrf " + "RED_B on router RED-1" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes 1.x.x.x/32 and 1::x/128 appear " "in VRF RED_A table" + ) + step( + "Verify that static routes 2.x.x.x/32 and 2::x/128 appear " "in VRF RED_B table" + ) + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Verify that static routes 1.x.x.x/32 and 1::x/128 appear " + "in VRF BLUE_A table" + ) + step( + "Verify that static routes 2.x.x.x/32 and 2::x/128 appear " + "in VRF BLUE_B table" + ) + + for addr_type in ADDR_TYPES: + dut = "blue1" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Make sure routes are not present in global routing table") + + for addr_type in ADDR_TYPES: + dut = "blue1" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n Expected Behaviour: Routes are not " + "present on Global Routing table \n Error {}".format(tc_name, result) + ) + + write_test_footer(tc_name) + + +def test_vrf_with_unique_physical_interface_p0(request): + """ + FUNC_4_&_5: + Each VRF should be mapped with a unique VLAN on DUT + for traffic segregation, when using a single physical interface. + + Each VRF should be mapped to a unique physical + interface(without VLAN tagging) on DUT for traffic segregation. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "R1 is receiving routes in 4 VRFs instances " + "(RED_A, RED_B, BLUE_A, BLUE_B) from RED_1 and BLUE_1." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise a set of unique BGP prefixes(IPv4+IPv6) from " + "routers RED_1 & BLUE_1 in each VRF using static redistribution" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Each VRF table on R2 should maintain it's associated " + "routes and and accordingly install in zebra" + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_prefixes_leaking_p0(request): + """ + FUNC_6: + Advertise same set of prefixes from different VRFs + and verify on remote router that these prefixes are not + leaking to each other + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure a set of static routes(IPv4+IPv6) in vrf " "RED_A on router RED-1") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + }, + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + }, + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure a set of static routes(IPv4+IPv6) in vrf " "BLUE_A on router BLUE-1" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + }, + "blue1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + } + ] + }, + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure the same set of static routes with a " + "metric value of 123 in vrf RED_B on router RED-1" + ) + step( + "Configure the same set of static routes with a " + "metric value of 123 in vrf BLUE_B on router BLUE-1" + ) + + input_dict_3 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + }, + }, + ] + }, + "blue1": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R1 that RED_A doesn't receive any static " + "route with metric value 123" + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + }, + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + }, + } + + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + }, + "blue1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + } + ] + }, + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_dict_1, metric=123, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are present with metric value 123 \n Error: {}".format( + tc_name, result + ) + ) + logger.info("Expected Behavior: {}".format(result)) + + result = verify_rib(tgen, addr_type, dut, input_dict_2, metric=123) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, input_dict_2, metric=0, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are present with metric value 0 \n Error: {}".format( + tc_name, result + ) + ) + logger.info("Expected Behavior: {}".format(result)) + + write_test_footer(tc_name) + + +def test_static_routes_advertised_within_specific_vrf_p0(request): + """ + FUNC_7: + Redistribute Static routes and verify on remote routers + that routes are advertised within specific VRF instance, which + those static routes belong to. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise a set of unique BGP prefixes(IPv4+IPv6) " + "through static redistribution into VRF RED_A and RED_B" + " from router RED-1." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same as above set of BGP prefixes(IPv4+IPv6) " + "through static redistribution into VRF BLUE_A and BLUE_B" + " from router BLUE-1." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes are installed into vrfs RED_A" + "and RED_B tables only, not in global routing table of RED_1" + ) + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, protocol="static") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Verify that static routes are installed into vrfs BLUE_A and" + "BLUE_B tables only, not in global routing table of BLUE_1." + ) + + for addr_type in ADDR_TYPES: + dut = "blue1" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, protocol="static") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Verify on router R1, that each set of prefixes is received" + " into associated vrf tables only." + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_end_to_end_traffic_isolation_p0(request): + """ + FUNC_8: + Test end to end traffic isolation based on VRF tables. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1 " + "in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Use below commands to send prefixes with as-path prepend" + "VRF BLUE_A and BLUE_B from router BLUE-1." + ) + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "blue1": { + "route_maps": { + "ASP_{}".format(addr_type): [ + { + "action": "permit", + "set": {"path": {"as_num": 123, "as_action": "prepend"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Apply route-map to neighbours") + + input_dict_5 = { + "blue1": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "route_maps": [ + { + "name": "ASP_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "route_maps": [ + { + "name": "ASP_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "route_maps": [ + { + "name": "ASP_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "route_maps": [ + { + "name": "ASP_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R1 that BLUE_A and BLUE_B VRFs are receiving the" + " prefixes with as-path 123 prepended." + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_6 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_7 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Use below commands to send prefixes with as-path prepend VRF" + " BLUE_A and BLUE_B from router BLUE-1." + ) + + input_dict_6 = { + "red2": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + ] + }, + "blue2": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify that router RED-2 receives the prefixes in respective" " VRF tables.") + + for addr_type in ADDR_TYPES: + dut = "red2" + input_dict_6 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "blue2" + input_dict_7 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_static_routes_for_inter_vrf_route_leaking_p0(request): + """ + FUNC_9: + Use static routes for inter-vrf communication + (route-leaking) on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Configure unique loopback interfaces in VRFs RED_A " + "and RED_B on router RED_1." + ) + + for addr_type in ADDR_TYPES: + create_interface_in_kernel( + tgen, + "red1", + "loopback1", + LOOPBACK_1[addr_type], + "RED_A", + ) + create_interface_in_kernel( + tgen, + "red1", + "loopback2", + LOOPBACK_2[addr_type], + "RED_B", + ) + + step( + "Create a static routes in vrf RED_B on router RED_1 pointing" + " next-hop as interface's IP in vrf RED_A" + ) + + intf_red1_r11 = topo["routers"]["red1"]["links"]["r1-link1"]["interface"] + intf_red1_r10 = topo["routers"]["red1"]["links"]["r1-link2"]["interface"] + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_1[addr_type], + "interface": intf_red1_r10, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_A", + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes are installed into vrfs RED_A" + "and RED_B tables only, not in global routing table of RED_1" + ) + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_1[addr_type], + "interface": intf_red1_r10, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_A", + "vrf": "RED_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, protocol="static") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_inter_vrf_and_intra_vrf_communication_iBGP_p0(request): + """ + FUNC_10: + Verify intra-vrf and inter-vrf communication between + iBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Configure unique loopback IP(IPv4+IPv6) in vrf RED_A on router" + " R1 and advertise it in BGP process using redistribute " + "connected command." + ) + + for addr_type in ADDR_TYPES: + create_interface_in_kernel( + tgen, + "r1", + "loopback1", + LOOPBACK_1[addr_type], + "RED_A", + ) + + create_interface_in_kernel( + tgen, + "r1", + "loopback2", + LOOPBACK_2[addr_type], + "BLUE_A", + ) + + step( + "Create a static routes in vrf RED_B on router RED_1 pointing" + " next-hop as interface's IP in vrf RED_A" + ) + + intf_r2_r12 = topo["routers"]["r2"]["links"]["r1-link1"]["interface"] + intf_r2_r10 = topo["routers"]["r2"]["links"]["r1-link3"]["interface"] + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r2": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_r2_r10, + "nexthop_vrf": "BLUE_A", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_1[addr_type], + "interface": intf_r2_r12, + "nexthop_vrf": "RED_A", + "vrf": "BLUE_A", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute connected..") + + input_dict_3 = {} + for dut in ["r1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + VRFS = ["RED_A", "BLUE_A"] + AS_NUM = [100, 100] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["r2"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + VRFS = ["RED_A", "BLUE_A"] + AS_NUM = [100, 100] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes are installed into vrfs RED_A" + "and RED_B tables only, not in global routing table of RED_1" + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict = { + "r2": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_r2_r10, + "nexthop_vrf": "BLUE_A", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_1[addr_type], + "interface": intf_r2_r12, + "nexthop_vrf": "RED_A", + "vrf": "BLUE_A", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_inter_vrf_and_intra_vrf_communication_eBGP_p0(request): + """ + FUNC_11: + Verify intra-vrf and inter-vrf communication + between eBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Configure unique loopback IP(IPv4+IPv6) in vrf RED_A on router" + " R2 and advertise it in BGP process using redistribute " + "connected command." + ) + + step( + "Configure unique loopback IP(IPv4+IPv6) in vrf BLUE_A on router" + " R2 and advertise it in BGP process using redistribute " + "connected command." + ) + + for addr_type in ADDR_TYPES: + create_interface_in_kernel( + tgen, + "r2", + "loopback1", + LOOPBACK_1[addr_type], + "RED_A", + ) + create_interface_in_kernel( + tgen, + "r2", + "loopback2", + LOOPBACK_2[addr_type], + "BLUE_A", + ) + + step( + "Create a static routes in vrf RED_B on router RED_1 pointing" + " next-hop as interface's IP in vrf RED_A" + ) + + intf_r3_r21 = topo["routers"]["r3"]["links"]["r2-link1"]["interface"] + intf_r3_r23 = topo["routers"]["r3"]["links"]["r2-link3"]["interface"] + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_r3_r23, + "nexthop_vrf": "BLUE_A", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_1[addr_type], + "interface": intf_r3_r21, + "nexthop_vrf": "RED_A", + "vrf": "BLUE_A", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["r3"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + VRFS = ["RED_A", "BLUE_A"] + AS_NUM = [200, 200] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Redistribute connected..") + + input_dict_3 = {} + for dut in ["r2"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + VRFS = ["RED_A", "BLUE_A"] + AS_NUM = [100, 100] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes are installed into vrfs RED_A" + "and RED_B tables only, not in global routing table of RED_1" + ) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict = { + "r3": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_r3_r23, + "nexthop_vrf": "BLUE_A", + "vrf": "RED_A", + }, + { + "network": LOOPBACK_1[addr_type], + "interface": intf_r3_r21, + "nexthop_vrf": "RED_A", + "vrf": "BLUE_A", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_within_vrf_to_alter_bgp_attribute_nexthop_p0(request): + """ + FUNC_12_a: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise a set of BGP prefixes(IPv4+IPv6) from RED_1 and" + " RED_2 in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Delete nexthop-self configure from r1") + + input_dict_4 = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"next_hop_self": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {"next_hop_self": False} + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": {"next_hop_self": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": {"next_hop_self": False} + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": {"next_hop_self": False} + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": {"next_hop_self": False} + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": {"next_hop_self": False} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": {"next_hop_self": False} + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected Behaviour: Routes are rejected because nexthop-self config is deleted \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected Behaviour: Routes are rejected because nexthop-self config is deleted \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("attribute", ["locPrf", "weight", "metric"]) +def test_route_map_within_vrf_to_alter_bgp_attribute_p0(request, attribute): + """ + FUNC_12_b/c/d: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise a set of BGP prefixes(IPv4+IPv6) from RED_1 and" + " RED_2 in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + "red2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + "blue2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "red2", "blue1", "blue2"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure a route-maps to influence BGP parameters - " " Local Preference") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r2": { + "route_maps": { + "rmap_r1_{}".format(addr_type): [ + {"action": "permit", "set": {attribute: 120}} + ], + "rmap_r3_{}".format(addr_type): [ + {"action": "permit", "set": {attribute: 150}} + ], + } + } + } + + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map") + input_dict_4 = { + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r3_ipv4", + "direction": "in", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r3_ipv6", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r3_ipv4", + "direction": "in", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r3_ipv6", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r3_ipv4", + "direction": "in", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r3_ipv6", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r3_ipv4", + "direction": "in", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r3_ipv6", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + dut = "r2" + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict_1, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict_2, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_within_vrf_to_alter_bgp_attribute_aspath_p0(request): + """ + FUNC_12_e: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise a set of BGP prefixes(IPv4+IPv6) from RED_1 and" + " RED_2 in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + "red2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + "blue2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "red2", "blue1", "blue2"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure a route-maps to influence BGP parameters - " " Local Preference") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r2": { + "route_maps": { + "rmap_r1_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "path": {"as_num": "111 222", "as_action": "prepend"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map") + input_dict_4 = { + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link1": {}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link1": {}}}, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link2": {}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link2": {}}}, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link3": {}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link3": {}}}, + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link4": {}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r3": {"dest_link": {"r2-link4": {}}}, + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + dut = "r2" + attribute = "path" + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict_1, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict_2, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_within_vrf_to_alter_bgp_attribute_lcomm_p0(request): + """ + FUNC_12_f: + Configure route-maps within a VRF, to alter BGP attributes. + Verify that route-map doesn't affect any other VRF instances' + routing on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise a set of BGP prefixes(IPv4+IPv6) from RED_1 and" + " RED_2 in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + "red2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + "blue2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + }, + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "red2", "blue1", "blue2"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure a route-maps to influence BGP parameters - " " Large-community") + + step("Create standard large commumity-list in r2") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r2": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "rmap_lcomm_{}".format(addr_type), + "value": "1:1:1 1:2:3 2:1:1 2:2:2", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Create route-maps in red1 and r1") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "red1": { + "route_maps": { + "rmap_red1_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "large_community": {"num": "1:1:1 1:2:3 2:1:1 2:2:2"} + }, + } + ] + } + }, + "r2": { + "route_maps": { + "rmap_r1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + "large_community_list": { + "id": "rmap_lcomm_" + addr_type + } + }, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map in red1") + + input_dict_4 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbor for route map in r2") + + input_dict_4 = { + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "route_maps": [ + { + "name": "rmap_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "All the prefixes advertised from RED_1 and BLUE_1 should carry" + " attributes set by outbound route-maps within specific vrfs. " + "Router R1 should be able to match and permit/deny those " + "prefixes based on received attributes. Please use below " + "commands to verify." + ) + + input_dict = { + "largeCommunity": "1:1:1 1:2:3 2:1:1 2:2:2", + } + + for addr_type in ADDR_TYPES: + vrf = "RED_A" + routes = [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]] + result = verify_bgp_community(tgen, addr_type, "r2", routes, input_dict, vrf) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + vrf = "RED_B" + routes = [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]] + result = verify_bgp_community(tgen, addr_type, "r2", routes, input_dict, vrf) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_match_traffic_based_on_vrf_p0(request): + """ + FUNC_13: + Configure a route-map on DUT to match traffic based + on a VRF interfaces. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1 " + "in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Configure a route-map on R1 to match the prefixes " + "coming from vrf RED_A and set as-prepend to these routes." + ) + + input_dict_4 = { + "r1": { + "route_maps": { + "ABC": [ + { + "action": "permit", + "match": {"source-vrf": "RED_A"}, + "set": {"path": {"as_num": 1, "as_action": "prepend"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "On R1, import the routes form vrf RED_A and RED_B to BLUE_A and" + " apply the route-map under vrf BLUE_A while importing" + ) + + raw_config = { + "r1": { + "raw_config": [ + "router bgp 100 vrf BLUE_A", + "address-family ipv4 unicast", + "import vrf RED_A", + "import vrf RED_B", + "import vrf route-map ABC", + "address-family ipv6 unicast", + "import vrf RED_A", + "import vrf RED_B", + "import vrf route-map ABC", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step( + "All the prefixes advertised from RED_1 and BLUE_1 in vrfs " + "RED_B and BLUE_B must prepend the AS number in as-path on R2." + ) + + for addr_type in ADDR_TYPES: + input_dict_7 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_dict_7) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_vrf_lite_with_static_bgp_originated_routes_p0(request): + """ + FUNC_14: + Test VRF-lite with Static+BGP originated routes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_3 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK5_1["ipv4"]] + + [NETWORK5_2["ipv4"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK5_1["ipv6"]] + + [NETWORK5_2["ipv6"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK6_1["ipv4"]] + + [NETWORK6_2["ipv4"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK6_1["ipv6"]] + + [NETWORK6_2["ipv6"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + }, + }, + ] + }, + "blue1": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK7_1["ipv4"]] + + [NETWORK7_2["ipv4"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK7_1["ipv6"]] + + [NETWORK7_2["ipv6"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK8_1["ipv4"]] + + [NETWORK8_2["ipv4"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + { + "network": [NETWORK8_1["ipv6"]] + + [NETWORK8_2["ipv6"]] + } + ], + "redistribute": [{"redist_type": "static"}], + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Static routes must be installed in associated VRF" " table only.") + + for addr_type in ADDR_TYPES: + dut = "r1" + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "All the routers must receive advertised as well as " + "redistributed(static) prefixes in associated VRF tables." + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_prefix_list_to_permit_deny_prefixes_p0(request): + """ + FUNC_15: + Configure prefix-lists on DUT and apply to BGP peers to + permit/deny prefixes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify routes are present before applying prefix-list") + for addr_type in ADDR_TYPES: + dut = "r1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "On routers RED_1 and BLUE_1, configure prefix-lists to permit" + " 4 prefixes and deny 1 prefix x.x.x.5. Apply these in outbound" + "direction for each neighbour." + ) + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "red1": { + "prefix_lists": { + addr_type: { + "pflist_red1_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + }, + { + "seqid": 11, + "network": NETWORK2_1[addr_type], + "action": "permit", + }, + { + "seqid": 12, + "network": NETWORK1_2[addr_type], + "action": "deny", + }, + { + "seqid": 13, + "network": NETWORK2_2[addr_type], + "action": "deny", + }, + ] + } + } + }, + "blue1": { + "prefix_lists": { + addr_type: { + "pflist_blue1_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + }, + { + "seqid": 11, + "network": NETWORK2_1[addr_type], + "action": "permit", + }, + { + "seqid": 12, + "network": NETWORK1_2[addr_type], + "action": "deny", + }, + { + "seqid": 13, + "network": NETWORK2_2[addr_type], + "action": "deny", + }, + ] + } + } + }, + "r1": { + "prefix_lists": { + addr_type: { + "pflist_r1_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + }, + { + "seqid": 11, + "network": NETWORK2_1[addr_type], + "action": "deny", + }, + ] + } + } + }, + } + result = create_prefix_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_5 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "prefix_lists": [ + { + "name": "pflist_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "prefix_lists": [ + { + "name": "pflist_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "prefix_lists": [ + { + "name": "pflist_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "prefix_lists": [ + { + "name": "pflist_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + }, + "blue1": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "prefix_lists": [ + { + "name": "pflist_blue1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "prefix_lists": [ + { + "name": "pflist_blue1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "prefix_lists": [ + { + "name": "pflist_blue1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "prefix_lists": [ + { + "name": "pflist_blue1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, each BGP neighbor receives 1" + " prefixes in routing table and drops (x.x.x.2)." + ) + + for addr_type in ADDR_TYPES: + dut = "r1" + permitted_routes = { + "red1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "RED_A"}, + {"network": [NETWORK2_1[addr_type]], "vrf": "RED_B"}, + ] + } + } + + denied_routes = { + "red1": { + "static_routes": [ + {"network": [NETWORK1_2[addr_type]], "vrf": "RED_A"}, + {"network": [NETWORK2_2[addr_type]], "vrf": "RED_B"}, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, permitted_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, denied_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n" + "{}:Expected behaviour: Routes are denied by prefix-list \nError {}".format( + tc_name, result + ) + + step( + "On router R1, configure prefix-lists to permit 2 " + "prefixes(x.x.x.1-2) and deny 2 prefix(x.x.x.3-4). Apply" + " these in inbound direction for each neighbour." + ) + + input_dict_6 = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "prefix_lists": [ + { + "name": "pflist_r1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, each BGP neighbor installs" + " only 1 prefix (x.x.x.1)." + ) + for addr_type in ADDR_TYPES: + dut = "r2" + permitted_routes = { + "red1": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "RED_A"}] + } + } + + denied_routes = { + "red1": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "RED_A"}] + } + } + + result = verify_rib(tgen, addr_type, dut, permitted_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, denied_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nExpected behaviour: Routes are denied by prefix-list \nError {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_set_and_match_tag_p0(request): + """ + FUNC_16_1: + Configure a route-map on DUT to match traffic based various + match/set causes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 4001, + "vrf": "RED_A", + }, + { + "network": [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 4001, + "vrf": "BLUE_A", + }, + { + "network": [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure a route-maps to match tag") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "red1": { + "route_maps": { + "rmap1_{}".format(addr_type): [ + {"action": "permit", "match": {addr_type: {"tag": "4001"}}} + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map") + input_dict_4 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + dut = "r1" + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "tag": 4001, + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected Behavior: Routes are denied \nError {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_set_and_match_metric_p0(request): + """ + FUNC_16_2: + Configure a route-map on DUT to match traffic based various + match/set causes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + }, + ] + }, + "blue1": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": {"metric": 123}, + } + ] + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure a route-maps to match tag") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "route_maps": { + "rmap1_{}".format(addr_type): [ + {"action": "permit", "match": {"metric": 123}} + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map") + input_dict_4 = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that within vrf instances, BGP best path selection" + " algorithm remains intact and doesn't affect any other VRFs" + " routing decision." + ) + + dut = "r1" + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected Behavior: Routes are denied \nError {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_map_set_and_match_community_p0(request): + """ + FUNC_16_3: + Configure a route-map on DUT to match traffic based various + match/set causes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise same set of BGP prefixes(IPv4+IPv6) from BLUE_1 and" + "BLUE_2 in vrf instances(BLUE_A and BLUE_B)" + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Create community-list") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "rmap_lcomm_{}".format(addr_type), + "value": "1:1 1:2 1:3 1:4 1:5", + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure a route-maps to match tag") + + step("Create route-maps in red1 and r1") + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "red1": { + "route_maps": { + "rmap_red1_{}".format(addr_type): [ + { + "action": "permit", + "set": {"community": {"num": "1:1 1:2 1:3 1:4 1:5"}}, + } + ] + } + }, + "r1": { + "route_maps": { + "rmap1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + "community_list": {"id": "rmap_lcomm_" + addr_type} + }, + } + ] + } + }, + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure neighbor for route map") + input_dict_4 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "route_maps": [ + { + "name": "rmap_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap_red1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "route_maps": [ + { + "name": "rmap_red1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + }, + }, + ] + }, + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "route_maps": [ + { + "name": "rmap1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "All the prefixes advertised from RED_1 and BLUE_1 should carry" + " attributes set by outbound route-maps within specific vrfs. " + "Router R1 should be able to match and permit/deny those " + "prefixes based on received attributes. Please use below " + "commands to verify." + ) + + input_dict = { + "community": "1:1 1:2 1:3 1:4 1:5", + } + + for addr_type in ADDR_TYPES: + vrf = "RED_A" + routes = [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]] + result = verify_bgp_community(tgen, addr_type, "r1", routes, input_dict, vrf) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + vrf = "RED_B" + routes = [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]] + result = verify_bgp_community(tgen, addr_type, "r1", routes, input_dict, vrf) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_multi_vrf_topo2/bgp_multi_vrf_topo2.json b/tests/topotests/bgp_multi_vrf_topo2/bgp_multi_vrf_topo2.json new file mode 100644 index 0000000..bcee7e1 --- /dev/null +++ b/tests/topotests/bgp_multi_vrf_topo2/bgp_multi_vrf_topo2.json @@ -0,0 +1,1553 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "red1": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "red1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "blue1": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "BLUE_A", + "id": "1" + }, + { + "name": "BLUE_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "blue1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r1": { + "links": { + "red1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "red1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "blue1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "blue1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r4-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r4-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link1": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link1": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link2": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red1": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link2": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link3": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link3": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link4": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue1": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r1-link4": + { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + }, + "r4": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r2": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r4-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r4-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "red2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "red2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "blue2-link1": {"ipv4": "auto", "ipv6": "autor3", "vrf": "BLUE_A"}, + "blue2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "red2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "red2": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "red2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "red2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "blue2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "blue2": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "blue2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "blue2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }], + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r1-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + }, + { + "name": "BLUE_A", + "id": "3" + }, + { + "name": "BLUE_B", + "id": "4" + } + ], + "bgp": + [ + { + "local_as": "400", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "red2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED_B"} + }, + "vrfs":[ + { + "name": "RED_A", + "id": "1" + }, + { + "name": "RED_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "blue2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_A"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE_B"} + }, + "vrfs":[ + { + "name": "BLUE_A", + "id": "1" + }, + { + "name": "BLUE_B", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_multi_vrf_topo2/test_bgp_multi_vrf_topo2.py b/tests/topotests/bgp_multi_vrf_topo2/test_bgp_multi_vrf_topo2.py new file mode 100644 index 0000000..40a28fb --- /dev/null +++ b/tests/topotests/bgp_multi_vrf_topo2/test_bgp_multi_vrf_topo2.py @@ -0,0 +1,3839 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF: + +CHAOS_1: + Do a shut and no shut on connecting interface of DUT, + to see if all vrf instances clear their respective BGP tables + during the interface down and restores when interface brought +kCHAOS_3: + VRF leaking - next-hop interface is flapping. +CHAOS_5: + VRF - VLANs - Routing Table ID - combination testcase + on DUT. +CHAOS_9: + Verify that all vrf instances fall back + to backup path, if primary link goes down. +CHAOS_6: + Restart BGPd daemon on DUT to check if all the + routes in respective vrfs are reinstalled.. +CHAOS_2: + Delete a VRF instance from DUT and check if the routes get + deleted from subsequent neighbour routers and appears again once VRF + is re-added. +CHAOS_4: + Verify that VRF names are locally significant + to a router, and end to end connectivity depends on unique + virtual circuits (using VLANs or separate physical interfaces). +CHAOS_8: + Restart all FRR services (reboot DUT) to check if all + the routes in respective vrfs are reinstalled. +""" + +import os +import sys +import time +import pytest +from copy import deepcopy +from time import sleep + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import iproute2_is_vrf_capable +from lib.common_config import ( + step, + verify_rib, + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + create_route_maps, + shutdown_bringup_interface, + start_router_daemons, + create_static_routes, + create_vrf_cfg, + create_interfaces_cfg, + create_interface_in_kernel, + get_frr_ipv6_linklocal, + check_router_status, + apply_raw_config, + required_linux_kernel_version, + kill_router_daemons, + start_router_daemons, + stop_router, + start_router, +) + +from lib.topolog import logger +from lib.bgp import clear_bgp, verify_bgp_rib, create_router_bgp, verify_bgp_convergence +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +NETWORK1_1 = {"ipv4": "1.1.1.1/32", "ipv6": "1::1/128"} +NETWORK1_2 = {"ipv4": "1.1.1.2/32", "ipv6": "1::2/128"} +NETWORK2_1 = {"ipv4": "2.1.1.1/32", "ipv6": "2::1/128"} +NETWORK2_2 = {"ipv4": "2.1.1.2/32", "ipv6": "2::2/128"} +NETWORK3_1 = {"ipv4": "3.1.1.1/32", "ipv6": "3::1/128"} +NETWORK3_2 = {"ipv4": "3.1.1.2/32", "ipv6": "3::2/128"} +NETWORK4_1 = {"ipv4": "4.1.1.1/32", "ipv6": "4::1/128"} +NETWORK4_2 = {"ipv4": "4.1.1.2/32", "ipv6": "4::2/128"} +NETWORK9_1 = {"ipv4": "100.1.0.1/30", "ipv6": "100::1/126"} +NETWORK9_2 = {"ipv4": "100.1.0.2/30", "ipv6": "100::2/126"} + +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +LOOPBACK_2 = { + "ipv4": "20.20.20.20/32", + "ipv6": "20::20:20/128", +} + +MAX_PATHS = 2 +KEEPALIVETIMER = 1 +HOLDDOWNTIMER = 3 +PREFERRED_NEXT_HOP = "link_local" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.14") + if result is not True: + pytest.skip("Kernel requirements are not met") + + # iproute2 needs to support VRFs for this suite to run. + if not iproute2_is_vrf_capable(): + pytest.skip("Installed iproute2 version does not support VRFs") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_multi_vrf_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_vrf_with_multiple_links_p1(request): + """ + CHAOS_9: + Verify that all vrf instances fall back + to backup path, if primary link goes down. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Configure BGP neighborships(IPv4+IPv6) between R1 and R4 " + "using exact same link IPs for all 4 VRFs." + ) + + topo_modify = deepcopy(topo) + build_config_from_json(tgen, topo_modify) + + interfaces = ["link1", "link2", "link3", "link4"] + for interface in interfaces: + topo_modify["routers"]["r1"]["links"]["r4-{}".format(interface)][ + "delete" + ] = True + topo_modify["routers"]["r4"]["links"]["r1-{}".format(interface)][ + "delete" + ] = True + + step("Build interface config from json") + create_interfaces_cfg(tgen, topo_modify["routers"]) + + interfaces = ["link1", "link2", "link3", "link4"] + for interface in interfaces: + del topo_modify["routers"]["r1"]["links"]["r4-{}".format(interface)]["delete"] + del topo_modify["routers"]["r4"]["links"]["r1-{}".format(interface)]["delete"] + + r1_config = [] + r4_config = [] + for addr_type in ADDR_TYPES: + interfaces = ["link1", "link2", "link3", "link4"] + for interface in interfaces: + intf_name_r1 = topo_modify["routers"]["r1"]["links"][ + "r4-{}".format(interface) + ]["interface"] + topo_modify["routers"]["r1"]["links"]["r4-{}".format(interface)][ + addr_type + ] = NETWORK9_1[addr_type] + + intf_name_r4 = topo_modify["routers"]["r4"]["links"][ + "r1-{}".format(interface) + ]["interface"] + topo_modify["routers"]["r4"]["links"]["r1-{}".format(interface)][ + addr_type + ] = NETWORK9_2[addr_type] + + r1_config.append("interface {}".format(intf_name_r1)) + r4_config.append("interface {}".format(intf_name_r4)) + if addr_type == "ipv4": + r1_config.append("no ip address {}".format(NETWORK9_1[addr_type])) + r4_config.append("no ip address {}".format(NETWORK9_2[addr_type])) + else: + r1_config.append("no ipv6 address {}".format(NETWORK9_1[addr_type])) + r4_config.append("no ipv6 address {}".format(NETWORK9_2[addr_type])) + + step("Build interface config from json") + create_interfaces_cfg(tgen, topo_modify["routers"]) + + step("Create bgp config") + result = create_router_bgp(tgen, topo_modify["routers"]) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify BGP convergence") + + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + step( + "Advertise below prefixes in BGP using static redistribution" + " for both vrfs (RED_A and BLUE_A) on router R2.." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["r1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + VRFS = ["RED_A", "RED_B", "BLUE_A", "BLUE_B"] + AS_NUM = [100, 100, 100, 100] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo_modify, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Verify routes are installed with same nexthop in different" " VRFs") + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r4" + _input_dict = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + R1_NEXTHOP = topo_modify["routers"]["r1"]["links"]["r4-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, _input_dict, next_hop=R1_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + _input_dict = { + "r1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + } + } + + R1_NEXTHOP = topo_modify["routers"]["r1"]["links"]["r4-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, _input_dict, next_hop=R1_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + _input_dict = { + "r1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + } + } + + R1_NEXTHOP = topo_modify["routers"]["r1"]["links"]["r4-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, _input_dict, next_hop=R1_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + _input_dict = { + "r1": { + "static_routes": [ + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + } + ] + } + } + + R1_NEXTHOP = topo_modify["routers"]["r1"]["links"]["r4-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, _input_dict, next_hop=R1_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Configure a route-map on R3 to prepend as-path and apply" + " for neighbour router R2 in both vrfs, in inbound direction." + ) + + input_dict_4 = { + "r3": { + "route_maps": { + "ASP": [ + { + "action": "permit", + "set": {"path": {"as_num": 123, "as_action": "prepend"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Apply route-map to neighbours") + step( + "Configure ECMP on router R3 using 'max-path' command for both" + " VRFs RED_A and BLUE_A." + ) + + input_dict_5 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + } + ] + } + } + }, + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link4": { + "route_maps": [ + {"name": "ASP", "direction": "in"} + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo_modify, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r3" + peer = "r2" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + intf = topo_modify["routers"][peer]["links"]["r3-link1"]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + R2_NEXTHOP = get_frr_ipv6_linklocal(tgen, peer, intf=intf, vrf="RED_A") + else: + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link1"][ + addr_type + ].split("/")[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + intf = topo["routers"][peer]["links"]["r3-link3"]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + R2_NEXTHOP = get_frr_ipv6_linklocal(tgen, peer, intf=intf, vrf="BLUE_A") + else: + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link3"][ + addr_type + ].split("/")[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Configure ECMP on router R3 using max-path command for" + " both VRFs RED_A and BLUE_A." + ) + + input_dict_7 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": MAX_PATHS, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": MAX_PATHS, + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": MAX_PATHS, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": MAX_PATHS, + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo_modify, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("R3 should install prefixes from both next-hops (R2 and R4)") + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r3" + peer = "r2" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + intf = topo_modify["routers"][peer]["links"]["r3-link1"]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + R2_NEXTHOP = get_frr_ipv6_linklocal(tgen, peer, intf=intf, vrf="RED_A") + else: + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link1"][ + addr_type + ].split("/")[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + intf = topo_modify["routers"][peer]["links"]["r3-link3"]["interface"] + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + R2_NEXTHOP = get_frr_ipv6_linklocal(tgen, peer, intf=intf, vrf="BLUE_A") + else: + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link3"][ + addr_type + ].split("/")[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Shutdown interface between R2 and R3 for vrfs RED_A and " "BLUE_A.") + + intf1 = topo_modify["routers"]["r2"]["links"]["r3-link1"]["interface"] + intf2 = topo_modify["routers"]["r2"]["links"]["r3-link3"]["interface"] + + interfaces = [intf1, intf2] + for intf in interfaces: + shutdown_bringup_interface(tgen, "r2", intf, False) + + for addr_type in ADDR_TYPES: + dut = "r3" + peer = "r4" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link3"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Unshut the interfaces between R2 and R3 for vrfs RED_A and BLUE_A.") + + for intf in interfaces: + shutdown_bringup_interface(tgen, "r2", intf, True) + + for addr_type in ADDR_TYPES: + dut = "r3" + peer = "r2" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link3"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove route-map from R3 for vrfs RED_A and BLUE_A.") + + input_dict_6 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "ASP_ipv4", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "ASP_ipv6", + "direction": "in", + "delete": True, + }, + { + "name": "rmap_global", + "direction": "in", + }, + ] + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "route_maps": [ + { + "name": "ASP_ipv4", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link3": { + "route_maps": [ + { + "name": "ASP_ipv6", + "direction": "in", + "delete": True, + }, + { + "name": "rmap_global", + "direction": "in", + }, + ] + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo_modify, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo_modify) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + R2_NEXTHOP = topo_modify["routers"]["r2"]["links"]["r3-link3"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R2_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Shutdown links between between R2 and R3 for vrfs RED_A and" " BLUE_A.") + + for intf in interfaces: + shutdown_bringup_interface(tgen, "r2", intf, False) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "vrf": "RED_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link1"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "vrf": "BLUE_A", + } + ] + } + } + + R4_NEXTHOP = topo_modify["routers"]["r4"]["links"]["r3-link3"][addr_type].split( + "/" + )[0] + + result = verify_rib(tgen, addr_type, dut, input_dict, next_hop=R4_NEXTHOP) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bringup links between between R2 and R3 for vrfs RED_A and" " BLUE_A.") + + for intf in interfaces: + shutdown_bringup_interface(tgen, "r2", intf, True) + + step("Deleting manualy assigned ip address from router r1 and r4 interfaces") + raw_config = {"r1": {"raw_config": r1_config}, "r4": {"raw_config": r4_config}} + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_shut_noshut_p1(request): + """ + CHAOS_1: + Do a shut and no shut on connecting interface of DUT, + to see if all vrf instances clear their respective BGP tables + during the interface down and restores when interface brought + back up again. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Build interface config from json") + create_interfaces_cfg(tgen, topo["routers"]) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise unique prefixes in BGP using static redistribution" + " for both vrfs (RED_A and RED_B) on router RED_1." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique prefixes in BGP using static redistribution" + " for both vrfs (BLUE_A and BLUE_B) on router BLUE_1." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Api call to modify BGP timers") + + input_dict_4 = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + }, + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1", vrf=["RED_A", "RED_B", "BLUE_A", "BLUE_B"]) + + clear_bgp(tgen, addr_type, "r2", vrf=["RED_A", "RED_B", "BLUE_A", "BLUE_B"]) + + step("Shut down connecting interface between R1<<>>R2 on R1.") + step("Repeat step-3 and step-4 10 times.") + + for count in range(1, 2): + step("Iteration {}".format(count)) + step("Shut down connecting interface between R1<<>>R2 on R1.") + + intf1 = topo["routers"]["r1"]["links"]["r2-link1"]["interface"] + intf2 = topo["routers"]["r1"]["links"]["r2-link2"]["interface"] + intf3 = topo["routers"]["r1"]["links"]["r2-link3"]["interface"] + intf4 = topo["routers"]["r1"]["links"]["r2-link4"]["interface"] + + interfaces = [intf1, intf2, intf3, intf4] + for intf in interfaces: + shutdown_bringup_interface(tgen, "r1", intf, False) + + step( + "On R2, all BGP peering in respective vrf instances go down" + " when the interface is shut" + ) + + step("Sleeping for {}+1 sec..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nExpected Behaviour: BGP will not be converged \nError {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nExpected Behaviour: Routes are flushed out \nError {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nExpected Behaviour: Routes are flushed out \nError {}".format( + tc_name, result + ) + + step("Bring up connecting interface between R1<<>>R2 on R1.") + for intf in interfaces: + shutdown_bringup_interface(tgen, "r1", intf, True) + + step( + "R2 restores BGP peering and routing tables in all vrf " + "instances when interface brought back up again" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_vrf_vlan_routing_table_p1(request): + """ + CHAOS_5: + VRF - VLANs - Routing Table ID - combination testcase + on DUT. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique prefixes(IPv4+IPv6) in BGP using" + " network command for vrf RED_A on router R2" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r2": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = { + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that static routes(IPv4+IPv6) is overridden and doesn't" + " have duplicate entries within VRF RED_A on router RED-1" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict_1 = { + "r2": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Api call to modify BGP timers") + + input_dict_4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r3", vrf=["RED_A"]) + + step("Repeat for 5 times.") + + for count in range(1, 2): + step("Iteration {}..".format(count)) + step("Delete a specific VRF instance(RED_A) from router R3") + + input_dict = {"r3": {"vrfs": [{"name": "RED_A", "id": "1", "delete": True}]}} + + result = create_vrf_cfg(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Sleeping for {}+1 sec..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict_1 = { + "r2": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Expected Behaviour: Routes are cleaned \n Error {}".format( + tc_name, result + ) + + step("Add/reconfigure the same VRF instance again") + + result = create_vrf_cfg(tgen, {"r3": topo["routers"]["r3"]}) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "After deleting VRFs ipv6 addresses will be deleted from kernel " + " Adding back ipv6 addresses" + ) + + dut = "r3" + vrf = "RED_A" + + for c_link, c_data in topo["routers"][dut]["links"].items(): + if c_data["vrf"] != vrf: + continue + + intf_name = c_data["interface"] + intf_ipv6 = c_data["ipv6"] + + create_interface_in_kernel( + tgen, dut, intf_name, intf_ipv6, vrf, create=False + ) + + step("Sleeping for {}+1 sec..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + for addr_type in ADDR_TYPES: + dut = "r3" + input_dict_1 = { + "r2": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_vrf_route_leaking_next_hop_interface_flapping_p1(request): + """ + CHAOS_3: + VRF leaking - next-hop interface is flapping. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Create loopback interface") + + for addr_type in ADDR_TYPES: + create_interface_in_kernel( + tgen, + "red1", + "loopback2", + LOOPBACK_2[addr_type], + "RED_B", + ) + + intf_red1_r11 = topo["routers"]["red1"]["links"]["r1-link2"]["interface"] + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = { + "red1": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + step("VRF RED_A should install a route for vrf RED_B's " "loopback ip.") + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, protocol="static") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Repeat step-2 to 4 at least 5 times") + + for count in range(1, 2): + intf1 = topo["routers"]["red1"]["links"]["r1-link2"]["interface"] + + step( + "Iteration {}: Shutdown interface {} on router" + "RED_1.".format(count, intf1) + ) + shutdown_bringup_interface(tgen, "red1", intf1, False) + + step("Verify that RED_A removes static route from routing " "table.") + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib( + tgen, addr_type, dut, input_dict_1, protocol="static", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n Expected Behaviour: Routes are" + " not present Error {}".format(tc_name, result) + ) + + step("Bring up interface {} on router RED_1 again.".format(intf1)) + shutdown_bringup_interface(tgen, "red1", intf1, True) + + step( + "Verify that RED_A reinstalls static route pointing to " + "RED_B's IP in routing table again" + ) + + for addr_type in ADDR_TYPES: + dut = "red1" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": LOOPBACK_2[addr_type], + "interface": intf_red1_r11, + "nexthop_vrf": "RED_B", + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, protocol="static") + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_restart_bgpd_daemon_p1(request): + """ + CHAOS_6: + Restart BGPd daemon on DUT to check if all the + routes in respective vrfs are reinstalled.. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + reset_config_on_routers(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed\n Error {}".format(tc_name, result) + + step("Kill BGPd daemon on R1.") + kill_router_daemons(tgen, "r1", ["bgpd"]) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are still present in VRF RED_A and RED_B \n Error: {}".format( + tc_name, result + ) + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are still present in VRF BLUE_A and BLUE_B \n Error: {}".format( + tc_name, result + ) + ) + + step("Bring up BGPd daemon on R1.") + start_router_daemons(tgen, "r1", ["bgpd"]) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_delete_and_re_add_vrf_p1(request): + """ + CHAOS_2: + Delete a VRF instance from DUT and check if the routes get + deleted from subsequent neighbour routers and appears again once VRF + is re-added. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise unique prefixes in BGP using static redistribution" + "for both vrfs (RED_A and RED_B) on router RED_1" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique prefixes in BGP using static redistribution" + " for both vrfs (BLUE_A and BLUE_B) on router BLUE_1." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static for vrfs RED_A and RED_B and BLUE_A and BLUE_B") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verifying RIB and FIB before deleting VRFs") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Api call to modify BGP timers") + + input_dict_4 = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link3": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "100", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link4": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1", vrf=["RED_A", "RED_B", "BLUE_A", "BLUE_B"]) + + step("Delete vrfs RED_A and BLUE_A from R1.") + + input_dict = { + "r1": { + "vrfs": [ + {"name": "RED_A", "id": "1", "delete": True}, + {"name": "BLUE_A", "id": "3", "delete": True}, + ] + } + } + + result = create_vrf_cfg(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "R2 must not receive the prefixes(in respective vrfs)" + "originated from RED_1 and BLUE_1." + ) + + step("Wait for {}+1 sec..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + }, + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + }, + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n Expected Behaviour:" + " Routes are not present \n Error {}".format(tc_name, result) + ) + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert result is not True, ( + "Testcase {} :Failed \n Expected Behaviour:" + " Routes are not present \n Error {}".format(tc_name, result) + ) + + step("Add vrfs again RED_A and BLUE_A on R1.") + + result = create_vrf_cfg(tgen, {"r1": topo["routers"]["r1"]}) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + create_interfaces_cfg(tgen, {"r1": topo["routers"]["r1"]}) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "After deleting VRFs ipv6 addresses will be deleted from kernel " + " Adding back ipv6 addresses" + ) + + dut = "r1" + vrfs = ["RED_A", "BLUE_A"] + + for vrf in vrfs: + for c_link, c_data in topo["routers"][dut]["links"].items(): + if c_data["vrf"] != vrf: + continue + + intf_name = c_data["interface"] + intf_ipv6 = c_data["ipv6"] + + create_interface_in_kernel( + tgen, dut, intf_name, intf_ipv6, vrf, create=False + ) + + step( + "R2 should now receive the prefixes(in respective vrfs)" + "again. Check the debugging logs as well. For verification" + " use same commands as mention in step-3." + ) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {}: Failed\n Error {}".format(tc_name, result) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "r2" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_vrf_name_significance_p1(request): + """ + CHAOS_4: + Verify that VRF names are locally significant + to a router, and end to end connectivity depends on unique + virtual circuits (using VLANs or separate physical interfaces). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique prefixes in BGP using static redistribution" + "for both vrfs (RED_A and RED_B) on router RED_1" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique prefixes in BGP using static redistribution" + " for both vrfs (BLUE_A and BLUE_B) on router BLUE_1." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static for vrfs RED_A and RED_B and BLUE_A and BLUE_B") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure allowas-in on red2 and blue2") + + input_dict_4 = { + "red2": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + ] + }, + "blue2": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "allowas-in": {"number_occurences": 2} + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verifying RIB and FIB before deleting VRFs") + + for addr_type in ADDR_TYPES: + dut = "red2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "blue2" + input_dict_3 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + } + } + + input_dict_4 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Api call to modify BGP timers") + + input_dict_4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + }, + "red2": { + "bgp": [ + { + "local_as": "500", + "vrf": "RED_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "500", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "red2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + }, + "blue2": { + "bgp": [ + { + "local_as": "800", + "vrf": "BLUE_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "800", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "blue2-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r3", vrf=["RED_A", "RED_B", "BLUE_A", "BLUE_B"]) + + clear_bgp(tgen, addr_type, "red2", vrf=["RED_A", "RED_B"]) + + clear_bgp(tgen, addr_type, "blue2", vrf=["BLUE_A", "BLUE_B"]) + + step("Delete vrfs RED_A and BLUE_A from R3") + + input_dict = { + "r3": { + "vrfs": [ + {"name": "RED_A", "id": "1", "delete": True}, + {"name": "BLUE_A", "id": "3", "delete": True}, + ] + } + } + + result = create_vrf_cfg(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Waiting for {}+1..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + step("Verify RIB and FIB after deleting VRFs") + + for addr_type in ADDR_TYPES: + dut = "red2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert ( + result is not True + ), "Testcase {} :Failed \n Expected Behaviour: Routes are not present \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1, expected=False) + assert ( + result is not True + ), "Testcase {} :Failed \n Expected Behaviour: Routes are not present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + dut = "blue2" + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} :Failed \n Expected Behaviour: Routes are not present \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2, expected=False) + assert ( + result is not True + ), "Testcase {} :Failed \n Expected Behaviour: Routes are not present \n Error {}".format( + tc_name, result + ) + + step("Create 2 new VRFs PINK_A and GREY_A IN R3") + + topo_modify = deepcopy(topo) + topo_modify["routers"]["r3"]["vrfs"][0]["name"] = "PINK_A" + topo_modify["routers"]["r3"]["vrfs"][0]["id"] = "1" + topo_modify["routers"]["r3"]["vrfs"][2]["name"] = "GREY_A" + topo_modify["routers"]["r3"]["vrfs"][2]["id"] = "3" + + topo_modify["routers"]["r3"]["links"]["red2-link1"]["vrf"] = "PINK_A" + topo_modify["routers"]["r3"]["links"]["blue2-link1"]["vrf"] = "GREY_A" + + topo_modify["routers"]["r3"]["links"]["r2-link1"]["vrf"] = "PINK_A" + topo_modify["routers"]["r3"]["links"]["r2-link3"]["vrf"] = "GREY_A" + + topo_modify["routers"]["r3"]["links"]["r4-link1"]["vrf"] = "PINK_A" + topo_modify["routers"]["r3"]["links"]["r4-link3"]["vrf"] = "GREY_A" + + topo_modify["routers"]["r3"]["bgp"][0]["vrf"] = "PINK_A" + topo_modify["routers"]["r3"]["bgp"][2]["vrf"] = "GREY_A" + + result = create_vrf_cfg(tgen, {"r3": topo_modify["routers"]["r3"]}) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + create_interfaces_cfg(tgen, {"r3": topo_modify["routers"]["r3"]}) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = create_router_bgp(tgen, topo_modify["routers"]) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Api call to modify BGP timers") + + input_dict_4 = { + "r3": { + "bgp": [ + { + "local_as": "200", + "vrf": "PINK_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "RED_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "red2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "GREY_A", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + { + "local_as": "200", + "vrf": "BLUE_B", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "blue2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": KEEPALIVETIMER, + "holddowntimer": HOLDDOWNTIMER, + } + } + } + } + } + }, + }, + }, + ] + } + } + + result = create_router_bgp(tgen, topo_modify, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r3", vrf=["PINK_A", "RED_B", "GREY_A", "BLUE_B"]) + + step( + "After deleting VRFs ipv6 addresses will be deleted from kernel " + " Adding back ipv6 addresses" + ) + + dut = "r3" + vrfs = ["GREY_A", "PINK_A"] + + for vrf in vrfs: + for c_link, c_data in topo_modify["routers"][dut]["links"].items(): + if c_data["vrf"] != vrf: + continue + + intf_name = c_data["interface"] + intf_ipv6 = c_data["ipv6"] + + create_interface_in_kernel( + tgen, dut, intf_name, intf_ipv6, vrf, create=False + ) + + step("Waiting for {}+1 sec..".format(HOLDDOWNTIMER)) + sleep(HOLDDOWNTIMER + 1) + + step( + "Advertised prefixes should appear again in respective VRF" + " table on routers RED_2 and BLUE_2. Verify fib and rib entries" + ) + + for addr_type in ADDR_TYPES: + dut = "red2" + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + } + ] + } + } + + input_dict_2 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + dut = "blue2" + input_dict_3 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + } + ] + } + } + + input_dict_4 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_bgp_rib(tgen, addr_type, dut, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_restart_frr_services_p1(request): + """ + CHAOS_8: + Restart all FRR services (reboot DUT) to check if all + the routes in respective vrfs are reinstalled. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from RED_1" + " in vrf instances(RED_A and RED_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Advertise unique BGP prefixes(IPv4+IPv6) from BLUE_1 in" + " vrf instances(BLUE_A and BLUE_B)." + ) + + for addr_type in ADDR_TYPES: + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static..") + + input_dict_3 = {} + for dut in ["red1", "blue1"]: + temp = {dut: {"bgp": []}} + input_dict_3.update(temp) + + if "red" in dut: + VRFS = ["RED_A", "RED_B"] + AS_NUM = [500, 500] + elif "blue" in dut: + VRFS = ["BLUE_A", "BLUE_B"] + AS_NUM = [800, 800] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Restart frr on R1") + stop_router(tgen, "r1") + start_router(tgen, "r1") + + for addr_type in ADDR_TYPES: + dut = "r2" + + input_dict_1 = { + "red1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED_B", + }, + ] + } + } + + input_dict_2 = { + "blue1": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_A", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE_B", + }, + ] + } + } + + result = verify_rib(tgen, addr_type, dut, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, dut, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_multiview_topo1/README.md b/tests/topotests/bgp_multiview_topo1/README.md new file mode 100644 index 0000000..c1a1445 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/README.md @@ -0,0 +1,125 @@ +# Simple FRRouting Route-Server Test + +## Topology + +----------+ +----------+ +----------+ +----------+ +----------+ + | peer1 | | peer2 | | peer3 | | peer4 | | peer5 | + | AS 65001 | | AS 65002 | | AS 65003 | | AS 65004 | | AS 65005 | + +-----+----+ +-----+----+ +-----+----+ +-----+----+ +-----+----+ + | .1 | .2 | .3 | .4 | .5 + | ______/ / / _________/ + \ / ________________/ / / + | | / _________________________/ / +----------+ + | | | / __________________________/ ___| peer6 | + | | | | / ____________________________/.6 | AS 65006 | + | | | | | / _________________________ +----------+ + | | | | | | / __________________ \ +----------+ + | | | | | | | / \ \___| peer7 | + | | | | | | | | \ .7 | AS 65007 | + ~~~~~~~~~~~~~~~~~~~~~ \ +----------+ + ~~ SW1 ~~ \ +----------+ + ~~ Switch ~~ \_____| peer8 | + ~~ 172.16.1.0/24 ~~ .8 | AS 65008 | + ~~~~~~~~~~~~~~~~~~~~~ +----------+ + | + | .254 + +---------+---------+ + | FRR R1 | + | BGP Multi-View | + | Peer 1-3 > View 1 | + | Peer 4-5 > View 2 | + | Peer 6-8 > View 3 | + +---------+---------+ + | .1 + | + ~~~~~~~~~~~~~ Stub Network is redistributed + ~~ SW0 ~~ into each BGP view with different + ~~ 172.20.0.1/28 ~~ attributes (using route-map) + ~~ Stub Switch ~~ + ~~~~~~~~~~~~~ + +## FRR Configuration + +Full config as used is in r1 subdirectory + +Simplified `R1` config: + + hostname r1 + ! + interface r1-stub + description Stub Network + ip address 172.20.0.1/28 + no link-detect + ! + interface r1-eth0 + description to PE router - vlan1 + ip address 172.16.1.254/24 + no link-detect + ! + router bgp 100 view 1 + bgp router-id 172.30.1.1 + network 172.20.0.0/28 route-map local1 + timers bgp 60 180 + neighbor 172.16.1.1 remote-as 65001 + neighbor 172.16.1.2 remote-as 65002 + neighbor 172.16.1.5 remote-as 65005 + ! + router bgp 100 view 2 + bgp router-id 172.30.1.1 + network 172.20.0.0/28 route-map local2 + timers bgp 60 180 + neighbor 172.16.1.3 remote-as 65003 + neighbor 172.16.1.4 remote-as 65004 + ! + router bgp 100 view 3 + bgp router-id 172.30.1.1 + network 172.20.0.0/28 + timers bgp 60 180 + neighbor 172.16.1.6 remote-as 65006 + neighbor 172.16.1.7 remote-as 65007 + neighbor 172.16.1.8 remote-as 65008 + ! + route-map local1 permit 10 + set community 100:9999 additive + set metric 0 + ! + route-map local2 permit 10 + set as-path prepend 100 100 100 100 100 + set community 100:1 additive + set metric 9999 + ! + +## Tests executed + +### Check if FRR is running + +Test is executed by running + + vtysh -c "show logging" | grep "Logging configuration for" + +on router `R1`. This should return the logging information for all daemons registered +to Zebra and the list of running daemons is compared to the daemons started for this +test (`zebra` and `bgpd`) + +### Verify for BGP to converge + +BGP is expected to converge on each view within 60s total time. Convergence is verified by executing + + vtysh -c "show ip bgp view 1 summary" + vtysh -c "show ip bgp view 2 summary" + vtysh -c "show ip bgp view 3 summary" + +and expecting 11 routes seen in the last column for each peer. (Each peer sends 11 routes) + +### Verifying BGP Routing Tables + +Routing table is verified by running + + vtysh -c "show ip bgp view 1" + vtysh -c "show ip bgp view 2" + vtysh -c "show ip bgp view 3" + +and comparing the result against the stored table in the r1/show_ip_bgp_view_NN.ref files +(with NN 1, 2, 3) (A few header and trailer lines are cut/adjusted ahead of the compare to +adjust for different output based on recent changes) + + diff --git a/tests/topotests/bgp_multiview_topo1/exabgp.env b/tests/topotests/bgp_multiview_topo1/exabgp.env new file mode 100644 index 0000000..ec978c6 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/exabgp.env @@ -0,0 +1,55 @@ + +[exabgp.api] +ack = false +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_multiview_topo1/peer1/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer1/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer1/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg new file mode 100644 index 0000000..0303230 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer1/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 1 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 1; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.1; + local-address 172.16.1.1; + local-as 65001; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer2/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer2/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer2/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg new file mode 100644 index 0000000..13670c3 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer2/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 2 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 2; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.2; + local-address 172.16.1.2; + local-as 65002; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer3/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer3/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer3/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg new file mode 100644 index 0000000..0afc484 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer3/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 3 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 3; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.3; + local-address 172.16.1.3; + local-as 65003; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer4/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer4/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer4/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg new file mode 100644 index 0000000..0f5b536 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer4/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 4 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 4; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.4; + local-address 172.16.1.4; + local-as 65004; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer5/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer5/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer5/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg new file mode 100644 index 0000000..365aa6b --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer5/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 5 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 5; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.5; + local-address 172.16.1.5; + local-as 65005; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer6/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer6/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer6/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg new file mode 100644 index 0000000..1038033 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer6/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 6 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 6; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.6; + local-address 172.16.1.6; + local-as 65006; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer7/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer7/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer7/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg new file mode 100644 index 0000000..7411338 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer7/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 7 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 7; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.7; + local-address 172.16.1.7; + local-as 65007; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/peer8/exa-send.py b/tests/topotests/bgp_multiview_topo1/peer8/exa-send.py new file mode 100755 index 0000000..0e50d46 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer8/exa-send.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) + +# Announce numRoutes different routes per PE +for i in range(0, numRoutes): + stdout.write( + "announce route 10.%s.%s.0/24 med 100 community %i:1 next-hop 172.16.1.%i\n" + % ((peer + 100), i, peer, peer) + ) + stdout.flush() + +# Announce 1 overlapping route per peer +stdout.write("announce route 10.0.1.0/24 med %i next-hop 172.16.1.%i\n" % (peer, peer)) +stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg b/tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg new file mode 100644 index 0000000..17a4e49 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/peer8/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 8 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 8; + encoder text; +} + +neighbor 172.16.1.254 { + router-id 172.16.1.8; + local-address 172.16.1.8; + local-as 65008; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_multiview_topo1/r1/bgpd.conf b/tests/topotests/bgp_multiview_topo1/r1/bgpd.conf new file mode 100644 index 0000000..cd7f44a --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/r1/bgpd.conf @@ -0,0 +1,61 @@ +! +! Zebra configuration saved from vty +! 2015/12/24 21:46:33 +! +log file bgpd.log +! +!debug bgp events +!debug bgp keepalives +!debug bgp updates +!debug bgp fsm +!debug bgp filters +!debug bgp zebra +! +router bgp 100 view 1 + bgp router-id 172.30.1.1 + bgp always-compare-med + no bgp ebgp-requires-policy + network 172.20.0.0/28 route-map local1 + timers bgp 60 180 + neighbor 172.16.1.1 remote-as 65001 + neighbor 172.16.1.1 timers 3 10 + neighbor 172.16.1.2 remote-as 65002 + neighbor 172.16.1.2 timers 3 10 + neighbor 172.16.1.5 remote-as 65005 + neighbor 172.16.1.5 timers 3 10 +! +router bgp 100 view 2 + bgp router-id 172.30.1.1 + bgp always-compare-med + no bgp ebgp-requires-policy + network 172.20.0.0/28 route-map local2 + timers bgp 60 180 + neighbor 172.16.1.3 remote-as 65003 + neighbor 172.16.1.3 timers 3 10 + neighbor 172.16.1.4 remote-as 65004 + neighbor 172.16.1.4 timers 3 10 +! +router bgp 100 view 3 + bgp router-id 172.30.1.1 + bgp always-compare-med + no bgp ebgp-requires-policy + network 172.20.0.0/28 + timers bgp 60 180 + neighbor 172.16.1.6 remote-as 65006 + neighbor 172.16.1.6 timers 3 10 + neighbor 172.16.1.7 remote-as 65007 + neighbor 172.16.1.7 timers 3 10 + neighbor 172.16.1.8 remote-as 65008 + neighbor 172.16.1.8 timers 3 10 +! +route-map local1 permit 10 + set community 100:9999 additive + set metric 0 +! +route-map local2 permit 10 + set as-path prepend 100 100 100 100 100 + set community 100:1 additive + set metric 9999 +! +line vty +! diff --git a/tests/topotests/bgp_multiview_topo1/r1/view_1.json b/tests/topotests/bgp_multiview_topo1/r1/view_1.json new file mode 100644 index 0000000..137b8a3 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/r1/view_1.json @@ -0,0 +1,728 @@ +{ + "vrfName": "1", + "routerId": "172.30.1.1", + "defaultLocPrf": 100, + "localAS": 100, + "routes": { + "10.0.1.0/24": [ + { + "valid": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 5, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + }, + { + "valid": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 2, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + }, + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 1, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.1.0", + "prefixLen": 24, + "network": "10.101.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.2.0", + "prefixLen": 24, + "network": "10.101.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.3.0", + "prefixLen": 24, + "network": "10.101.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.4.0", + "prefixLen": 24, + "network": "10.101.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.5.0", + "prefixLen": 24, + "network": "10.101.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.6.0", + "prefixLen": 24, + "network": "10.101.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.7.0", + "prefixLen": 24, + "network": "10.101.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.8.0", + "prefixLen": 24, + "network": "10.101.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.101.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.101.9.0", + "prefixLen": 24, + "network": "10.101.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.1", + "path": "65001", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.0.0", + "prefixLen": 24, + "network": "10.102.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.1.0", + "prefixLen": 24, + "network": "10.102.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.2.0", + "prefixLen": 24, + "network": "10.102.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.3.0", + "prefixLen": 24, + "network": "10.102.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.4.0", + "prefixLen": 24, + "network": "10.102.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.5.0", + "prefixLen": 24, + "network": "10.102.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.6.0", + "prefixLen": 24, + "network": "10.102.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.7.0", + "prefixLen": 24, + "network": "10.102.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.8.0", + "prefixLen": 24, + "network": "10.102.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.102.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.102.9.0", + "prefixLen": 24, + "network": "10.102.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.2", + "path": "65002", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.0.0", + "prefixLen": 24, + "network": "10.105.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.1.0", + "prefixLen": 24, + "network": "10.105.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.2.0", + "prefixLen": 24, + "network": "10.105.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.3.0", + "prefixLen": 24, + "network": "10.105.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.4.0", + "prefixLen": 24, + "network": "10.105.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.5.0", + "prefixLen": 24, + "network": "10.105.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.6.0", + "prefixLen": 24, + "network": "10.105.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.7.0", + "prefixLen": 24, + "network": "10.105.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.8.0", + "prefixLen": 24, + "network": "10.105.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.105.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.105.9.0", + "prefixLen": 24, + "network": "10.105.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.5", + "path": "65005", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.5", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_multiview_topo1/r1/view_2.json b/tests/topotests/bgp_multiview_topo1/r1/view_2.json new file mode 100644 index 0000000..2ad28c5 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/r1/view_2.json @@ -0,0 +1,489 @@ +{ + "vrfName": "2", + "routerId": "172.30.1.1", + "defaultLocPrf": 100, + "localAS": 100, + "routes": { + "10.0.1.0/24": [ + { + "valid": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 4, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + }, + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 3, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.1.0", + "prefixLen": 24, + "network": "10.103.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.2.0", + "prefixLen": 24, + "network": "10.103.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.3.0", + "prefixLen": 24, + "network": "10.103.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.4.0", + "prefixLen": 24, + "network": "10.103.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.5.0", + "prefixLen": 24, + "network": "10.103.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.6.0", + "prefixLen": 24, + "network": "10.103.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.7.0", + "prefixLen": 24, + "network": "10.103.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.8.0", + "prefixLen": 24, + "network": "10.103.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.103.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.103.9.0", + "prefixLen": 24, + "network": "10.103.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.3", + "path": "65003", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.3", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.0.0", + "prefixLen": 24, + "network": "10.104.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.1.0", + "prefixLen": 24, + "network": "10.104.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.2.0", + "prefixLen": 24, + "network": "10.104.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.3.0", + "prefixLen": 24, + "network": "10.104.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.4.0", + "prefixLen": 24, + "network": "10.104.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.5.0", + "prefixLen": 24, + "network": "10.104.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.6.0", + "prefixLen": 24, + "network": "10.104.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.7.0", + "prefixLen": 24, + "network": "10.104.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.8.0", + "prefixLen": 24, + "network": "10.104.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.104.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.104.9.0", + "prefixLen": 24, + "network": "10.104.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.4", + "path": "65004", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.4", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_multiview_topo1/r1/view_3.json b/tests/topotests/bgp_multiview_topo1/r1/view_3.json new file mode 100644 index 0000000..d49694e --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/r1/view_3.json @@ -0,0 +1,728 @@ +{ + "vrfName": "3", + "routerId": "172.30.1.1", + "defaultLocPrf": 100, + "localAS": 100, + "routes": { + "10.0.1.0/24": [ + { + "valid": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 8, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + }, + { + "valid": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 7, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + }, + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.0.1.0", + "prefixLen": 24, + "network": "10.0.1.0/24", + "metric": 6, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.0.0", + "prefixLen": 24, + "network": "10.106.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.1.0", + "prefixLen": 24, + "network": "10.106.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.2.0", + "prefixLen": 24, + "network": "10.106.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.3.0", + "prefixLen": 24, + "network": "10.106.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.4.0", + "prefixLen": 24, + "network": "10.106.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.5.0", + "prefixLen": 24, + "network": "10.106.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.6.0", + "prefixLen": 24, + "network": "10.106.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.7.0", + "prefixLen": 24, + "network": "10.106.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.8.0", + "prefixLen": 24, + "network": "10.106.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.106.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.106.9.0", + "prefixLen": 24, + "network": "10.106.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.6", + "path": "65006", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.6", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.0.0", + "prefixLen": 24, + "network": "10.107.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.1.0", + "prefixLen": 24, + "network": "10.107.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.2.0", + "prefixLen": 24, + "network": "10.107.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.3.0", + "prefixLen": 24, + "network": "10.107.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.4.0", + "prefixLen": 24, + "network": "10.107.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.5.0", + "prefixLen": 24, + "network": "10.107.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.6.0", + "prefixLen": 24, + "network": "10.107.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.7.0", + "prefixLen": 24, + "network": "10.107.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.8.0", + "prefixLen": 24, + "network": "10.107.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.107.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.107.9.0", + "prefixLen": 24, + "network": "10.107.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.7", + "path": "65007", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.7", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.0.0", + "prefixLen": 24, + "network": "10.108.0.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.1.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.1.0", + "prefixLen": 24, + "network": "10.108.1.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.2.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.2.0", + "prefixLen": 24, + "network": "10.108.2.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.3.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.3.0", + "prefixLen": 24, + "network": "10.108.3.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.4.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.4.0", + "prefixLen": 24, + "network": "10.108.4.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.5.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.5.0", + "prefixLen": 24, + "network": "10.108.5.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.6.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.6.0", + "prefixLen": 24, + "network": "10.108.6.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.7.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.7.0", + "prefixLen": 24, + "network": "10.108.7.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.8.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.8.0", + "prefixLen": 24, + "network": "10.108.8.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.108.9.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "10.108.9.0", + "prefixLen": 24, + "network": "10.108.9.0/24", + "metric": 100, + "weight": 0, + "peerId": "172.16.1.8", + "path": "65008", + "origin": "IGP", + "nexthops": [ + { + "ip": "172.16.1.8", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_multiview_topo1/r1/zebra.conf b/tests/topotests/bgp_multiview_topo1/r1/zebra.conf new file mode 100644 index 0000000..86343f5 --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/r1/zebra.conf @@ -0,0 +1,23 @@ +! +! Zebra configuration saved from vty +! 2015/12/24 16:48:27 +! +log file zebra.log +! +hostname r1 +! +interface r1-stub + description Stub Network + ip address 172.20.0.1/28 + no link-detect +! +interface r1-eth0 + description to PE router - vlan1 + ip address 172.16.1.254/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py b/tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py new file mode 100644 index 0000000..2f3421d --- /dev/null +++ b/tests/topotests/bgp_multiview_topo1/test_bgp_multiview_topo1.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_multiview_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2016 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +r""" +test_bgp_multiview_topo1.py: Simple FRR Route-Server Test + ++----------+ +----------+ +----------+ +----------+ +----------+ +| peer1 | | peer2 | | peer3 | | peer4 | | peer5 | +| AS 65001 | | AS 65002 | | AS 65003 | | AS 65004 | | AS 65005 | ++-----+----+ +-----+----+ +-----+----+ +-----+----+ +-----+----+ + | .1 | .2 | .3 | .4 | .5 + | ______/ / / _________/ + \ / ________________/ / / + | | / _________________________/ / +----------+ + | | | / __________________________/ ___| peer6 | + | | | | / ____________________________/.6 | AS 65006 | + | | | | | / _________________________ +----------+ + | | | | | | / __________________ \ +----------+ + | | | | | | | / \ \___| peer7 | + | | | | | | | | \ .7 | AS 65007 | + ~~~~~~~~~~~~~~~~~~~~~ \ +----------+ + ~~ SW1 ~~ \ +----------+ + ~~ Switch ~~ \_____| peer8 | + ~~ 172.16.1.0/24 ~~ .8 | AS 65008 | + ~~~~~~~~~~~~~~~~~~~~~ +----------+ + | + | .254 + +---------+---------+ + | FRR R1 | + | BGP Multi-View | + | Peer 1-3 > View 1 | + | Peer 4-5 > View 2 | + | Peer 6-8 > View 3 | + +---------+---------+ + | .1 + | + ~~~~~~~~~~~~~ Stub Network is redistributed + ~~ SW0 ~~ into each BGP view with different + ~~ 172.20.0.1/28 ~~ attributes (using route-map) + ~~ Stub Switch ~~ + ~~~~~~~~~~~~~ +""" + +import json +import os +import sys +import pytest +import json +from time import sleep + +from functools import partial + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from lib import topotest +from lib.topogen import get_topogen, Topogen +from lib.common_config import step + + +pytestmark = [pytest.mark.bgpd] + + +fatal_error = "" + + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + # Setup Routers + router = tgen.add_router("r1") + + # Setup Provider BGP peers + peer = {} + for i in range(1, 9): + peer[i] = tgen.add_exabgp_peer( + "peer%s" % i, ip="172.16.1.%s/24" % i, defaultRoute="via 172.16.1.254" + ) + + # First switch is for a dummy interface (for local network) + switch = tgen.add_switch("sw0") + switch.add_link(router, nodeif="r1-stub") + + # Second switch is for connection to all peering routers + switch = tgen.add_switch("sw1") + switch.add_link(router, nodeif="r1-eth0") + for j in range(1, 9): + switch.add_link(peer[j], nodeif="peer%s-eth0" % j) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + thisDir = os.path.dirname(os.path.realpath(__file__)) + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # Starting Routers + router = tgen.net["r1"] + router.loadConf("zebra", "%s/r1/zebra.conf" % thisDir) + router.loadConf("bgpd", "%s/r1/bgpd.conf" % thisDir) + tgen.gears["r1"].start() + + # Starting PE Hosts and init ExaBGP on each of them + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + peer_dir = os.path.join(thisDir, pname) + env_file = os.path.join(thisDir, "exabgp.env") + peer.start(peer_dir, env_file) + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def test_router_running(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + +def test_bgp_converge(): + "Check for BGP converged on all peers and BGP views" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Wait for BGP to converge (All Neighbors in either Full or TwoWay State) + step("Verify for BGP to converge") + + timeout = 125 + while timeout > 0: + print("Timeout in %s: " % timeout), + sys.stdout.flush() + # Look for any node not yet converged + for i in range(1, 2): + for view in range(1, 4): + notConverged = tgen.net["r%s" % i].cmd( + r'vtysh -c "show ip bgp view %s summary" 2> /dev/null | grep ^[0-9] | grep -vP " 11\s+(\d+)"' + % view + ) + if notConverged: + print("Waiting for r%s, view %s" % (i, view)) + sys.stdout.flush() + break + if notConverged: + break + if notConverged: + sleep(5) + timeout -= 5 + else: + print("Done") + break + else: + # Bail out with error if a router fails to converge + bgpStatus = tgen.net["r%s" % i].cmd( + 'vtysh -c "show ip bgp view %s summary"' % view + ) + assert False, "BGP did not converge:\n%s" % bgpStatus + + tgen.routers_have_failure() + + +def test_bgp_routingTable(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + thisDir = os.path.dirname(os.path.realpath(__file__)) + + step("Verifying BGP Routing Tables") + + router = tgen.gears["r1"] + for view in range(1, 4): + json_file = "{}/{}/view_{}.json".format(thisDir, router.name, view) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip bgp view {} json".format(view), + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=5, wait=1) + assertmsg = "Routing Table verification failed for router {}, view {}".format( + router.name, view + ) + assert result is None, assertmsg + + tgen.routers_have_failure() + + +def test_shutdown_check_memleak(): + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli + # retval = pytest.main(["-s", "--tb=no"]) + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/bgp_node_target_extcommunities/__init__.py b/tests/topotests/bgp_node_target_extcommunities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/tests/topotests/bgp_node_target_extcommunities/r1/frr.conf @@ -0,0 +1,21 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + bgp router-id 192.168.1.1 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.3 remote-as external + neighbor 192.168.1.4 remote-as external + address-family ipv4 unicast + network 10.10.10.10/32 + neighbor 192.168.1.2 route-map rmap out + neighbor 192.168.1.3 route-map rmap out + neighbor 192.168.1.4 route-map rmap out + exit-address-family +! +route-map rmap permit 10 + set extcommunity nt 192.168.1.3:0 192.168.1.4:0 +exit diff --git a/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf new file mode 100644 index 0000000..09fda78 --- /dev/null +++ b/tests/topotests/bgp_node_target_extcommunities/r2/frr.conf @@ -0,0 +1,8 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external diff --git a/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf new file mode 100644 index 0000000..4883f1f --- /dev/null +++ b/tests/topotests/bgp_node_target_extcommunities/r3/frr.conf @@ -0,0 +1,8 @@ +! +int r3-eth0 + ip address 192.168.1.3/24 +! +router bgp 65003 + bgp router-id 192.168.1.3 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external diff --git a/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf b/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf new file mode 100644 index 0000000..f518bd1 --- /dev/null +++ b/tests/topotests/bgp_node_target_extcommunities/r4/frr.conf @@ -0,0 +1,8 @@ +! +int r4-eth0 + ip address 192.168.1.4/24 +! +router bgp 65004 + bgp router-id 192.168.1.4 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external diff --git a/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py b/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py new file mode 100644 index 0000000..23e820b --- /dev/null +++ b/tests/topotests/bgp_node_target_extcommunities/test_bgp_node_target_extcommunities.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if Node Target Extended Communities works. + +At r1 we set NT to 192.168.1.3 and 192.168.1.4 (this is the R3/R4 router-id), +and that means 10.10.10.10/32 MUST be installed on R3 and R4, but not on R2, +because this route does not have NT:192.168.1.2. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_node_target_extended_communities(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": { + "192.168.1.2": { + "pfxSnt": 1, + "state": "Established", + }, + "192.168.1.3": { + "pfxSnt": 1, + "state": "Established", + }, + "192.168.1.4": { + "pfxSnt": 1, + "state": "Established", + }, + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed announcing 10.10.10.10/32 to r2, r3, and r4" + + def _bgp_check_route(router, exists): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + if exists: + expected = { + "routes": { + "10.10.10.10/32": [ + { + "valid": True, + } + ] + } + } + else: + expected = { + "routes": { + "10.10.10.10/32": None, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_route, r3, True) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.10/32 is not installed, but SHOULD be" + + test_func = functools.partial(_bgp_check_route, r4, True) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.10/32 is not installed, but SHOULD be" + + test_func = functools.partial(_bgp_check_route, r2, False) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "10.10.10.10/32 is installed, but SHOULD NOT be" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_oad/__init__.py b/tests/topotests/bgp_oad/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_oad/r1/frr.conf b/tests/topotests/bgp_oad/r1/frr.conf new file mode 100644 index 0000000..39045ba --- /dev/null +++ b/tests/topotests/bgp_oad/r1/frr.conf @@ -0,0 +1,21 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 oad + neighbor 192.168.1.4 remote-as external + neighbor 192.168.1.4 timers 1 3 + neighbor 192.168.1.4 timers connect 1 + address-family ipv4 unicast + neighbor 192.168.1.4 route-map r4 in + exit-address-family +! +route-map r4 permit 10 + set local-preference 123 + set metric 123 +exit diff --git a/tests/topotests/bgp_oad/r2/frr.conf b/tests/topotests/bgp_oad/r2/frr.conf new file mode 100644 index 0000000..fdd23f3 --- /dev/null +++ b/tests/topotests/bgp_oad/r2/frr.conf @@ -0,0 +1,18 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.1.1 oad + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers 1 3 + neighbor 192.168.2.3 timers connect 1 + neighbor 192.168.2.3 oad +! diff --git a/tests/topotests/bgp_oad/r3/frr.conf b/tests/topotests/bgp_oad/r3/frr.conf new file mode 100644 index 0000000..02dd5ad --- /dev/null +++ b/tests/topotests/bgp_oad/r3/frr.conf @@ -0,0 +1,22 @@ +! +int lo + ip address 10.10.10.10/32 +! +int r3-eth0 + ip address 192.168.2.3/24 +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + neighbor 192.168.2.2 oad + ! + address-family ipv4 unicast + redistribute connected route-map connected + exit-address-family +! +route-map connected permit 10 + set local-preference 123 + set metric 123 +! diff --git a/tests/topotests/bgp_oad/r4/frr.conf b/tests/topotests/bgp_oad/r4/frr.conf new file mode 100644 index 0000000..83dcf39 --- /dev/null +++ b/tests/topotests/bgp_oad/r4/frr.conf @@ -0,0 +1,16 @@ +! +int r4-eth0 + ip address 192.168.1.4/24 +! +int r4-eth1 + ip address 192.168.4.4/24 +! +router bgp 65004 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.4.5 remote-as external + neighbor 192.168.4.5 timers 1 3 + neighbor 192.168.4.5 timers connect 1 +! diff --git a/tests/topotests/bgp_oad/r5/frr.conf b/tests/topotests/bgp_oad/r5/frr.conf new file mode 100644 index 0000000..f8e1609 --- /dev/null +++ b/tests/topotests/bgp_oad/r5/frr.conf @@ -0,0 +1,17 @@ +! +int lo + ip address 10.10.10.10/32 +! +int r5-eth0 + ip address 192.168.4.5/24 +! +router bgp 65005 + no bgp ebgp-requires-policy + neighbor 192.168.4.4 remote-as external + neighbor 192.168.4.4 timers 1 3 + neighbor 192.168.4.4 timers connect 1 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_oad/test_bgp_oad.py b/tests/topotests/bgp_oad/test_bgp_oad.py new file mode 100644 index 0000000..a2ca37a --- /dev/null +++ b/tests/topotests/bgp_oad/test_bgp_oad.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if local-preference is passed between different EBGP peers when +EBGP-OAD is configured. +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2", "r4"), "s2": ("r2", "r3"), "s3": ("r4", "r5")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_dynamic_capability_role(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast 10.10.10.10/32 json")) + expected = { + "paths": [ + { + "aspath": {"string": "65002 65003"}, + "metric": 123, + "locPrf": 123, + "peer": { + "hostname": "r2", + "type": "external (oad)", + }, + }, + { + "aspath": {"string": "65004 65005"}, + "metric": 123, + "locPrf": 123, + "bestpath": {"selectionReason": "Peer Type"}, + "peer": { + "hostname": "r4", + "type": "external", + }, + }, + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't converge" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_orf/__init__.py b/tests/topotests/bgp_orf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_orf/r1/bgpd.conf b/tests/topotests/bgp_orf/r1/bgpd.conf new file mode 100644 index 0000000..800bf1b --- /dev/null +++ b/tests/topotests/bgp_orf/r1/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.2 capability orf prefix-list both + exit-address-family +! diff --git a/tests/topotests/bgp_orf/r1/zebra.conf b/tests/topotests/bgp_orf/r1/zebra.conf new file mode 100644 index 0000000..85ab531 --- /dev/null +++ b/tests/topotests/bgp_orf/r1/zebra.conf @@ -0,0 +1,8 @@ +! +int lo + ip address 10.10.10.1/32 + ip address 10.10.10.2/32 +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_orf/r2/bgpd.conf b/tests/topotests/bgp_orf/r2/bgpd.conf new file mode 100644 index 0000000..8c488aa --- /dev/null +++ b/tests/topotests/bgp_orf/r2/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + address-family ipv4 unicast + neighbor 192.168.1.1 capability orf prefix-list both + neighbor 192.168.1.1 prefix-list r1 in + exit-address-family +! +ip prefix-list r1 seq 5 permit 10.10.10.1/32 diff --git a/tests/topotests/bgp_orf/r2/zebra.conf b/tests/topotests/bgp_orf/r2/zebra.conf new file mode 100644 index 0000000..cffe827 --- /dev/null +++ b/tests/topotests/bgp_orf/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_orf/test_bgp_orf.py b/tests/topotests/bgp_orf/test_bgp_orf.py new file mode 100644 index 0000000..47c0556 --- /dev/null +++ b/tests/topotests/bgp_orf/test_bgp_orf.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if BGP ORF filtering is working correctly when modifying +prefix-list. + +Initially advertise 10.10.10.1/32 from R1 to R2. Add new prefix +10.10.10.2/32 to r1 prefix list on R2. Test if we updated ORF +prefix-list correctly. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_orf(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge_r1(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json" + ) + ) + expected = {"advertisedRoutes": {"10.10.10.1/32": {}, "10.10.10.2/32": None}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge_r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't apply ORF from R1 to R2" + + def _bgp_converge_r2(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast summary json")) + expected = { + "peers": { + "192.168.1.1": { + "pfxRcd": 1, + "pfxSnt": 1, + "state": "Established", + "peerState": "OK", + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge_r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "ORF filtering is not working from R1 to R2" + + r2.vtysh_cmd( + """ + configure terminal + ip prefix-list r1 seq 10 permit 10.10.10.2/32 + """ + ) + + def _bgp_orf_changed_r1(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json" + ) + ) + expected = {"advertisedRoutes": {"10.10.10.1/32": {}, "10.10.10.2/32": {}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_orf_changed_r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't apply new ORF from R1 to R2" + + def _bgp_orf_changed_r2(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "10.10.10.1/32": [{"valid": True}], + "10.10.10.2/32": [{"valid": True}], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_orf_changed_r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "New ORF filtering is not working from R1 to R2" + + r2.vtysh_cmd( + """ + configure terminal + no ip prefix-list r1 seq 10 permit 10.10.10.2/32 + """ + ) + + test_func = functools.partial(_bgp_converge_r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't apply initial ORF from R1 to R2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_path_attribute_discard/__init__.py b/tests/topotests/bgp_path_attribute_discard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_attribute_discard/exabgp.env b/tests/topotests/bgp_path_attribute_discard/exabgp.env new file mode 100644 index 0000000..28e6423 --- /dev/null +++ b/tests/topotests/bgp_path_attribute_discard/exabgp.env @@ -0,0 +1,53 @@ +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg b/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg new file mode 100644 index 0000000..dccec7d --- /dev/null +++ b/tests/topotests/bgp_path_attribute_discard/peer1/exabgp.cfg @@ -0,0 +1,39 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.254; + local-address 10.0.0.254; + local-as 65254; + peer-as 65001; + + capability { + route-refresh; + } + + static { + route 192.168.100.101/32 { + atomic-aggregate; + community 65001:101; + next-hop 10.0.0.254; + } + + route 192.168.100.102/32 { + originator-id 10.0.0.254; + community 65001:102; + next-hop 10.0.0.254; + } + } +} + +neighbor 10.0.0.2 { + router-id 10.0.0.254; + local-address 10.0.0.254; + local-as 65254; + peer-as 65254; + + static { + route 192.168.100.101/32 { + # AIGP invalid attribute: flagged as transitive + optional. + attribute [0x1a 0xc0 0x00000064]; + next-hop 10.0.0.254; + } + } +} diff --git a/tests/topotests/bgp_path_attribute_discard/r1/frr.conf b/tests/topotests/bgp_path_attribute_discard/r1/frr.conf new file mode 100644 index 0000000..ae7fbdd --- /dev/null +++ b/tests/topotests/bgp_path_attribute_discard/r1/frr.conf @@ -0,0 +1,9 @@ +! +interface r1-eth0 + ip address 10.0.0.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 10.0.0.254 remote-as external + neighbor 10.0.0.254 timers 3 10 +! diff --git a/tests/topotests/bgp_path_attribute_discard/r2/frr.conf b/tests/topotests/bgp_path_attribute_discard/r2/frr.conf new file mode 100644 index 0000000..1dafbdd --- /dev/null +++ b/tests/topotests/bgp_path_attribute_discard/r2/frr.conf @@ -0,0 +1,10 @@ +! +interface r2-eth0 + ip address 10.0.0.2/24 +! +router bgp 65254 + no bgp ebgp-requires-policy + neighbor 10.0.0.254 remote-as internal + neighbor 10.0.0.254 timers 3 10 + neighbor 10.0.0.254 path-attribute discard 26 +! diff --git a/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py b/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py new file mode 100644 index 0000000..bd8cd8e --- /dev/null +++ b/tests/topotests/bgp_path_attribute_discard/test_bgp_path_attribute_discard.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if `neighbor path-attribute discard` command works correctly, +can discard unwanted attributes from UPDATE messages, and ignore them +by continuing to process UPDATE messages. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + r2 = tgen.add_router("r2") + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.254", defaultRoute="via 10.0.0.1") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(r2) + switch.add_link(peer1) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for _, (rname, router) in enumerate(tgen.routers().items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + peer = tgen.gears["peer1"] + peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env")) + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_path_attribute_discard(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "192.168.100.101/32": { + "paths": [ + { + "valid": True, + "atomicAggregate": True, + "community": { + "string": "65001:101", + }, + } + ], + }, + "192.168.100.102/32": { + "paths": [ + { + "valid": True, + "originatorId": None, + "community": { + "string": "65001:102", + }, + } + ], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed bgp convergence" + + step("Discard atomic-aggregate, and community attributes from peer1") + r1.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 10.0.0.254 path-attribute discard 6 8 + """ + ) + + def _bgp_check_if_attributes_discarded(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "192.168.100.101/32": { + "paths": [ + { + "valid": True, + "atomicAggregate": None, + "community": None, + } + ], + }, + "192.168.100.102/32": { + "paths": [ + { + "valid": True, + "originatorId": None, + "community": None, + } + ], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_if_attributes_discarded) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert ( + result is None + ), "Failed to discard path attributes (atomic-aggregate, community)" + + def _bgp_check_if_aigp_invalid_attribute_discarded(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "192.168.100.101/32": { + "paths": [ + { + "valid": True, + "aigpMetric": None, + } + ], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_if_aigp_invalid_attribute_discarded) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert ( + result is None + ), "Failed to discard AIGP invalid path attribute (iBGP session)" + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/__init__.py b/tests/topotests/bgp_path_attribute_treat_as_withdraw/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/bgpd.conf b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/bgpd.conf new file mode 100644 index 0000000..4286b98 --- /dev/null +++ b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/bgpd.conf @@ -0,0 +1,14 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 10.0.0.2 remote-as external + neighbor 10.0.0.2 timers 3 10 + address-family ipv4 unicast + network 10.10.10.10/32 route-map atomic + network 10.10.10.20/32 + exit-address-family +! +route-map atomic permit 10 + set atomic-aggregate +! diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/zebra.conf b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/zebra.conf new file mode 100644 index 0000000..51a1b26 --- /dev/null +++ b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r1/zebra.conf @@ -0,0 +1,4 @@ +! +interface r1-eth0 + ip address 10.0.0.1/24 +! diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/bgpd.conf b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/bgpd.conf new file mode 100644 index 0000000..2e63fd8 --- /dev/null +++ b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/bgpd.conf @@ -0,0 +1,6 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 10.0.0.1 remote-as external + neighbor 10.0.0.1 timers 3 10 +! diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/zebra.conf b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/zebra.conf new file mode 100644 index 0000000..12d3731 --- /dev/null +++ b/tests/topotests/bgp_path_attribute_treat_as_withdraw/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 10.0.0.2/24 +! diff --git a/tests/topotests/bgp_path_attribute_treat_as_withdraw/test_bgp_path_attribute_treat_as_withdraw.py b/tests/topotests/bgp_path_attribute_treat_as_withdraw/test_bgp_path_attribute_treat_as_withdraw.py new file mode 100644 index 0000000..a9d678a --- /dev/null +++ b/tests/topotests/bgp_path_attribute_treat_as_withdraw/test_bgp_path_attribute_treat_as_withdraw.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +Test if `neighbor path-attribute treat-as-withdraw` command works correctly, +can withdraw unwanted prefixes from BGP table. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + r2 = tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(r2) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + r1 = tgen.gears["r1"] + r1.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + r1.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + r1.start() + + r2 = tgen.gears["r2"] + r2.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r2/zebra.conf")) + r2.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r2/bgpd.conf")) + r2.start() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_path_attribute_treat_as_withdraw(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "10.10.10.10/32": { + "paths": [ + { + "valid": True, + "atomicAggregate": True, + } + ], + }, + "10.10.10.20/32": { + "paths": [ + { + "valid": True, + } + ], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence" + + step("Withdraw prefixes with atomic-aggregate from r1") + r2.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 10.0.0.1 path-attribute treat-as-withdraw 6 + """ + ) + + def _bgp_check_if_route_withdrawn(): + output = json.loads(r2.vtysh_cmd("show bgp ipv4 unicast json detail")) + expected = { + "routes": { + "10.10.10.10/32": None, + "10.10.10.20/32": { + "paths": [ + { + "valid": True, + } + ], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_if_route_withdrawn) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to withdraw prefixes with atomic-aggregate attribute" + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_path_attributes_topo1/__init__.py b/tests/topotests/bgp_path_attributes_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_attributes_topo1/bgp_path_attributes.json b/tests/topotests/bgp_path_attributes_topo1/bgp_path_attributes.json new file mode 100644 index 0000000..de2bffa --- /dev/null +++ b/tests/topotests/bgp_path_attributes_topo1/bgp_path_attributes.json @@ -0,0 +1,363 @@ +{ + "ipv4base":"10.0.0.0", + "ipv4mask":30, + "ipv6base":"fd00::", + "ipv6mask":64, + "link_ip_start":{"ipv4":"10.0.0.0", "v4mask":30, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + } + ] + }, + "bgp":{ + "local_as":"555", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp":{ + "local_as":"555", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + } + }, + "r3":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r5":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"555", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto"}, + "r6": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": { + "local_as": "666", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": {} + } + }, + "r6": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": {} + } + }, + "r6": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + }, + "r5":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3": {"ipv4": "auto", "ipv6": "auto"}, + "r7": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp":{ + "local_as":"666", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + }, + "r7": { + "dest_link": { + "r5": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + }, + "r7": { + "dest_link": { + "r5": {} + } + } + } + } + } + } + } + }, + "r6":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r4": {"ipv4": "auto", "ipv6": "auto"}, + "r7": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp":{ + "local_as":"777", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": {} + } + }, + "r7": { + "dest_link": { + "r6": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r6": {} + } + }, + "r7": { + "dest_link": { + "r6": {} + } + } + } + } + } + } + } + }, + "r7":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r5": {"ipv4": "auto", "ipv6": "auto"}, + "r6": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp":{ + "local_as":"888", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r7": {} + } + }, + "r6": { + "dest_link": { + "r7": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r7": {} + } + }, + "r6": { + "dest_link": { + "r7": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_path_attributes_topo1/test_bgp_path_attributes.py b/tests/topotests/bgp_path_attributes_topo1/test_bgp_path_attributes.py new file mode 100644 index 0000000..8504737 --- /dev/null +++ b/tests/topotests/bgp_path_attributes_topo1/test_bgp_path_attributes.py @@ -0,0 +1,1529 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Modified work Copyright (c) 2019 by VMware, Inc. ("VMware") +# Original work Copyright (c) 2018 by Network Device Education +# Foundation, Inc. ("NetDEF") +# + +""" +Following tests are covered to test AS-Path functionality: + +Setup module: +- Create topology (setup module) +- Bring up topology +- Verify BGP convergence + +Test cases: +1. Test next_hop attribute and verify best path is installed as per + reachable next_hop +2. Test aspath attribute and verify best path is installed as per + shortest AS-Path +3. Test localpref attribute and verify best path is installed as per + shortest local-preference +4. Test weight attribute and and verify best path is installed as per + highest weight +5. Test origin attribute and verify best path is installed as per + IGP>EGP>INCOMPLETE rule +6. Test med attribute and verify best path is installed as per lowest + med value +7. Test admin distance and verify best path is installed as per lowest + admin distance + +Teardown module: +- Bring down the topology +- stop routers + +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + create_prefix_lists, + create_route_maps, + check_address_types, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_best_path_as_per_bgp_attribute, + verify_best_path_as_per_admin_distance, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Address read from env variables +ADDR_TYPES = check_address_types() + + +#### +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: %s", testsuite_run_time) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_path_attributes.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Checking BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "setup_module :Failed \n Error:" " {}".format(result) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info("Testsuite end time: %s", time.asctime(time.localtime(time.time()))) + logger.info("=" * 40) + + +##################################################### +## +## Testcases +## +##################################################### + + +def test_next_hop_attribute(request): + """ + Verifying route are not getting installed in, as next_hop is + unreachable, Making next hop reachable using next_hop_self + command and verifying routes are installed. + """ + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r7": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r1" + protocol = "bgp" + # Verification should fail as nexthop-self is not enabled + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: " "{} routes are not present in RIB".format( + addr_type, tc_name + ) + + # Configure next-hop-self to bgp neighbor + input_dict_1 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r1" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_aspath_attribute(request): + "Verifying AS_PATH attribute functionality" + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r7": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "path" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify AS-Path and verify best path is changed + # Create Prefix list + + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_ls_1_ipv4": [ + { + "seqid": 10, + "network": "200.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_1_ipv6": [ + { + "seqid": 10, + "network": "200::/8", + "le": "128", + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3 = { + "r3": { + "route_maps": { + "RMAP_AS_PATH": [ + { + "action": "permit", + "match": {"ipv4": {"prefix_lists": "pf_ls_1_ipv4"}}, + "set": {"path": {"as_num": "111 222", "as_action": "prepend"}}, + }, + { + "action": "permit", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"path": {"as_num": "111 222", "as_action": "prepend"}}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_AS_PATH", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_AS_PATH", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "path" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_localpref_attribute(request): + "Verifying LOCAL PREFERENCE attribute functionality" + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r7": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Prefix list + input_dict_2 = { + "r2": { + "prefix_lists": { + "ipv4": { + "pf_ls_1_ipv4": [ + { + "seqid": 10, + "network": "200.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_1_ipv6": [ + { + "seqid": 10, + "network": "200::/8", + "le": "128", + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3 = { + "r2": { + "route_maps": { + "RMAP_LOCAL_PREF": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "pf_ls_1_ipv4"}}, + "set": {"locPrf": 1111}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"locPrf": 1111}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "RMAP_LOCAL_PREF", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "RMAP_LOCAL_PREF", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "locPrf" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify route map + input_dict_3 = { + "r2": { + "route_maps": { + "RMAP_LOCAL_PREF": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "pf_ls_1_ipv4"}}, + "set": {"locPrf": 50}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"locPrf": 50}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "locPrf" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_weight_attribute(request): + """ + Test configure/modify weight attribute and + verify best path is installed as per highest weight + """ + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r7": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_ls_1_ipv4": [ + { + "seqid": 10, + "network": "200.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_1_ipv6": [ + { + "seqid": 10, + "network": "200::/8", + "le": "128", + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3 = { + "r1": { + "route_maps": { + "RMAP_WEIGHT": [ + { + "action": "permit", + "seq_id": "5", + "match": {"ipv4": {"prefix_lists": "pf_ls_1_ipv4"}}, + "set": {"weight": 500}, + }, + { + "action": "permit", + "seq_id": "10", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"weight": 500}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "RMAP_WEIGHT", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "weight" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify route map + input_dict_3 = { + "r1": { + "route_maps": { + "RMAP_WEIGHT": [ + { + "action": "permit", + "seq_id": "5", + "match": {"ipv4": {"prefix_lists": "pf_ls_1_ipv4"}}, + "set": {"weight": 1000}, + }, + { + "action": "permit", + "seq_id": "10", + "match": {"ipv6": {"prefix_lists": "pf_ls_1_ipv6"}}, + "set": {"weight": 1000}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "weight" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r7": input_dict["r7"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_origin_attribute(request): + """ + Test origin attribute and verify best path is + installed as per IGP>EGP>INCOMPLETE rule + """ + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r2": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}} + } + } + }, + } + } + }, + "r5": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to create static routes + input_dict_3 = { + "r5": { + "static_routes": [ + {"network": "200.50.2.0/32", "next_hop": "Null0"}, + {"network": "200.60.2.0/32", "next_hop": "Null0"}, + {"network": "200:50:2::/128", "next_hop": "Null0"}, + {"network": "200:60:2::/128", "next_hop": "Null0"}, + ] + } + } + result = create_static_routes(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "origin" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, {"r4": input_dict["r4"]}, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_med_attribute(request): + """ + Test configure/modify MED attribute and verify best path + is installed as per lowest med value + """ + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + "r5": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [ + {"network": "200.50.2.0/32"}, + {"network": "200.60.2.0/32"}, + ] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [ + {"network": "200:50:2::/128"}, + {"network": "200:60:2::/128"}, + ] + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Prefix list + input_dict_2 = { + "r2": { + "prefix_lists": { + "ipv4": { + "pf_ls_r2_ipv4": [ + { + "seqid": 10, + "network": "200.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_r2_ipv6": [ + { + "seqid": 20, + "network": "200::/8", + "le": "128", + "action": "permit", + } + ] + }, + } + }, + "r3": { + "prefix_lists": { + "ipv4": { + "pf_ls_r3_ipv4": [ + { + "seqid": 10, + "network": "200.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_ls_r3_ipv6": [ + { + "seqid": 20, + "network": "200::/8", + "le": "128", + "action": "permit", + } + ] + }, + } + }, + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3 = { + "r2": { + "route_maps": { + "RMAP_MED_R2": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "pf_ls_r2_ipv4"}}, + "set": {"metric": 100}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"ipv6": {"prefix_lists": "pf_ls_r2_ipv6"}}, + "set": {"metric": 100}, + }, + ] + } + }, + "r3": { + "route_maps": { + "RMAP_MED_R3": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "pf_ls_r3_ipv4"}}, + "set": {"metric": 10}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"ipv6": {"prefix_lists": "pf_ls_r3_ipv6"}}, + "set": {"metric": 10}, + }, + ] + } + }, + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure neighbor for route map + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "RMAP_MED_R2", + "direction": "in", + } + ] + } + } + }, + "r1": {"dest_link": {"r2": {"next_hop_self": True}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "RMAP_MED_R2", + "direction": "in", + } + ] + } + } + }, + "r1": {"dest_link": {"r2": {"next_hop_self": True}}}, + } + } + }, + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}}, + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_MED_R3", + "direction": "in", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3": {"next_hop_self": True}}}, + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "RMAP_MED_R3", + "direction": "in", + } + ] + } + } + }, + } + } + }, + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "metric" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify route-map to set med value + input_dict_3 = { + "r3": { + "route_maps": { + "RMAP_MED_R3": [ + { + "action": "permit", + "seq_id": "10", + "match": {"ipv4": {"prefix_lists": "pf_ls_r3_ipv4"}}, + "set": {"metric": 200}, + }, + { + "action": "permit", + "seq_id": "20", + "match": {"ipv6": {"prefix_lists": "pf_ls_r3_ipv6"}}, + "set": {"metric": 200}, + }, + ] + } + } + } + + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "metric" + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_dict, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_admin_distance(request): + "Verifying admin distance functionality" + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Api call to create static routes + input_dict = { + "r2": { + "static_routes": [ + { + "network": "200.50.2.0/32", + "admin_distance": 80, + "next_hop": "10.0.0.14", + }, + { + "network": "200.50.2.0/32", + "admin_distance": 60, + "next_hop": "10.0.0.18", + }, + { + "network": "200:50:2::/128", + "admin_distance": 80, + "next_hop": "fd00::1", + }, + { + "network": "200:50:2::/128", + "admin_distance": 60, + "next_hop": "fd00::1", + }, + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + input_dict_2 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying best path + dut = "r1" + attribute = "admin_distance" + + input_dict = { + "ipv4": { + "r2": { + "static_routes": [ + { + "network": "200.50.2.0/32", + "admin_distance": 80, + "next_hop": "10.0.0.14", + }, + { + "network": "200.50.2.0/32", + "admin_distance": 60, + "next_hop": "10.0.0.18", + }, + ] + } + }, + "ipv6": { + "r2": { + "static_routes": [ + { + "network": "200:50:2::/128", + "admin_distance": 80, + "next_hop": "fd00::1", + }, + { + "network": "200:50:2::/128", + "admin_distance": 60, + "next_hop": "fd00::1", + }, + ] + } + }, + } + + for addr_type in ADDR_TYPES: + result = verify_best_path_as_per_admin_distance( + tgen, addr_type, dut, input_dict[addr_type], attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_path_selection/__init__.py b/tests/topotests/bgp_path_selection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_selection/r1/bgpd.conf b/tests/topotests/bgp_path_selection/r1/bgpd.conf new file mode 100644 index 0000000..08eb867 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r1/bgpd.conf @@ -0,0 +1,28 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 2 + neighbor 192.0.2.3 remote-as 65002 + neighbor 192.0.2.3 timers 1 3 + neighbor 192.0.2.3 timers connect 1 + neighbor 192.0.2.3 ebgp-multihop 2 + address-family ipv4 + redistribute connected + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.2 activate + neighbor 192.0.2.3 activate + exit-address-family +! +router bgp 65001 vrf vrf1 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + label vpn export 101 + rd vpn export 101:1 + rt vpn both 52:100 + import vpn + export vpn + exit-address-family +! \ No newline at end of file diff --git a/tests/topotests/bgp_path_selection/r1/ldpd.conf b/tests/topotests/bgp_path_selection/r1/ldpd.conf new file mode 100644 index 0000000..04ae068 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r1/ldpd.conf @@ -0,0 +1,26 @@ +hostname r1 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 192.0.2.1 + ! + address-family ipv4 + discovery transport-address 192.0.2.1 + ! + interface r1-eth0 + ! + interface r1-eth1 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_path_selection/r1/staticd.conf b/tests/topotests/bgp_path_selection/r1/staticd.conf new file mode 100644 index 0000000..a37f60a --- /dev/null +++ b/tests/topotests/bgp_path_selection/r1/staticd.conf @@ -0,0 +1,2 @@ +ip route 192.0.2.2/32 192.168.1.2 +ip route 192.0.2.3/32 192.168.2.2 \ No newline at end of file diff --git a/tests/topotests/bgp_path_selection/r1/zebra.conf b/tests/topotests/bgp_path_selection/r1/zebra.conf new file mode 100644 index 0000000..e860d6e --- /dev/null +++ b/tests/topotests/bgp_path_selection/r1/zebra.conf @@ -0,0 +1,11 @@ +! +interface lo + ip address 192.0.2.1/32 + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_path_selection/r2/bgpd.conf b/tests/topotests/bgp_path_selection/r2/bgpd.conf new file mode 100644 index 0000000..cc878d8 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65002 + no bgp network import-check + network 192.0.2.8/32 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 65001 + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.1.1 ebgp-multihop 2 + neighbor 192.168.1.1 update-source 192.0.2.2 + address-family ipv4 vpn + neighbor 192.168.1.1 activate + exit-address-family +! +router bgp 65002 vrf vrf1 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 102:1 + rt vpn both 52:100 + import vpn + export vpn + exit-address-family +! \ No newline at end of file diff --git a/tests/topotests/bgp_path_selection/r2/ldpd.conf b/tests/topotests/bgp_path_selection/r2/ldpd.conf new file mode 100644 index 0000000..67973ab --- /dev/null +++ b/tests/topotests/bgp_path_selection/r2/ldpd.conf @@ -0,0 +1,26 @@ +hostname r2 +log file ldpd.log +password zebra +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 192.0.2.2 + ! + address-family ipv4 + discovery transport-address 192.0.2.2 + ! + interface r2-eth0 + ! + interface r2-eth1 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_path_selection/r2/staticd.conf b/tests/topotests/bgp_path_selection/r2/staticd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_selection/r2/zebra.conf b/tests/topotests/bgp_path_selection/r2/zebra.conf new file mode 100644 index 0000000..40dfa98 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_path_selection/r3/bgpd.conf b/tests/topotests/bgp_path_selection/r3/bgpd.conf new file mode 100644 index 0000000..ca8d27a --- /dev/null +++ b/tests/topotests/bgp_path_selection/r3/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65002 + no bgp network import-check + network 192.0.2.8/32 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as 65001 + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + neighbor 192.168.2.1 ebgp-multihop 2 + neighbor 192.168.1.1 update-source 192.0.2.3 + address-family ipv4 vpn + neighbor 192.168.2.1 activate + exit-address-family +! +router bgp 65002 vrf vrf1 + bgp router-id 192.0.2.3 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + label vpn export 103 + rd vpn export 102:1 + rt vpn both 52:100 + import vpn + export vpn + exit-address-family +! \ No newline at end of file diff --git a/tests/topotests/bgp_path_selection/r3/ldpd.conf b/tests/topotests/bgp_path_selection/r3/ldpd.conf new file mode 100644 index 0000000..b282b08 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r3/ldpd.conf @@ -0,0 +1,24 @@ +hostname r3 +password zebra +log file ldpd.log +! +! debug mpls ldp zebra +! debug mpls ldp event +! debug mpls ldp errors +! debug mpls ldp messages recv +! debug mpls ldp messages sent +! debug mpls ldp discovery hello recv +! debug mpls ldp discovery hello sent +! +mpls ldp + router-id 192.0.2.3 + ! + address-family ipv4 + discovery transport-address 192.0.2.3 + ! + interface r3-eth0 + ! + ! +! +line vty +! diff --git a/tests/topotests/bgp_path_selection/r3/staticd.conf b/tests/topotests/bgp_path_selection/r3/staticd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_path_selection/r3/zebra.conf b/tests/topotests/bgp_path_selection/r3/zebra.conf new file mode 100644 index 0000000..a0473b6 --- /dev/null +++ b/tests/topotests/bgp_path_selection/r3/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_path_selection/test_bgp_path_selection.py b/tests/topotests/bgp_path_selection/test_bgp_path_selection.py new file mode 100644 index 0000000..30083b4 --- /dev/null +++ b/tests/topotests/bgp_path_selection/test_bgp_path_selection.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Louis Scalbert +# + +""" + +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for routern in range(1, 4): + tgen.gears["r{}".format(routern)].cmd("ip link add vrf1 type vrf table 10") + tgen.gears["r{}".format(routern)].cmd("ip link set vrf1 up") + tgen.gears["r{}".format(routern)].cmd( + "ip address add dev vrf1 {}.{}.{}.{}/32".format( + routern, routern, routern, routern + ) + ) + tgen.gears["r2"].cmd("ip address add dev vrf1 192.0.2.8/32") + tgen.gears["r3"].cmd("ip address add dev vrf1 192.0.2.8/32") + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + tgen.gears["r1"].cmd("ip route add 192.0.2.2 via 192.168.1.2 metric 20") + tgen.gears["r1"].cmd("ip route add 192.0.2.3 via 192.168.2.2 metric 20") + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_path_selection_ecmp(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_path_selection_ecmp(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show bgp ipv4 unicast 192.0.2.8/32 json") + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "65002"}, + "multipath": True, + "nexthops": [{"ip": "192.0.2.2", "metric": 20}], + }, + { + "valid": True, + "aspath": {"string": "65002"}, + "multipath": True, + "nexthops": [{"ip": "192.0.2.3", "metric": 20}], + }, + ] + } + + return topotest.json_cmp(output, expected) + + step("Check if two ECMP paths are present") + test_func = functools.partial(_bgp_check_path_selection_ecmp) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R1" + + +def test_bgp_path_selection_vpn_ecmp(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_path_selection_vpn_ecmp(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp vrf vrf1 ipv4 unicast 192.0.2.8/32 json" + ) + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "65002"}, + "multipath": True, + "nexthops": [{"ip": "192.0.2.2", "metric": 20}], + }, + { + "valid": True, + "aspath": {"string": "65002"}, + "multipath": True, + "nexthops": [{"ip": "192.0.2.3", "metric": 20}], + }, + ] + } + + return topotest.json_cmp(output, expected) + + step("Check if two ECMP paths are present") + test_func = functools.partial(_bgp_check_path_selection_vpn_ecmp) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R1" + + +def test_bgp_path_selection_metric(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_path_selection_metric(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show bgp ipv4 unicast 192.0.2.8/32 json") + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.2", "metric": 10}], + "bestpath": {"selectionReason": "IGP Metric"}, + }, + { + "valid": True, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.3", "metric": 20}], + }, + ] + } + + return topotest.json_cmp(output, expected) + + tgen.gears["r1"].cmd("ip route add 192.0.2.2 via 192.168.1.2 metric 10") + tgen.gears["r1"].cmd("ip route del 192.0.2.2 via 192.168.1.2 metric 20") + + step("Check if IGP metric best path is selected") + test_func = functools.partial(_bgp_check_path_selection_metric) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R1" + + +def test_bgp_path_selection_vpn_metric(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_path_selection_vpn_metric(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp vrf vrf1 ipv4 unicast 192.0.2.8/32 json" + ) + ) + expected = { + "paths": [ + { + "valid": True, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.2", "metric": 10}], + "bestpath": {"selectionReason": "IGP Metric"}, + }, + { + "valid": True, + "aspath": {"string": "65002"}, + "nexthops": [{"ip": "192.0.2.3", "metric": 20}], + }, + ] + } + + return topotest.json_cmp(output, expected) + + step("Check if IGP metric best path is selected") + test_func = functools.partial(_bgp_check_path_selection_vpn_metric) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on R1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_peer_graceful_shutdown/__init__.py b/tests/topotests/bgp_peer_graceful_shutdown/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r1/bgpd.conf b/tests/topotests/bgp_peer_graceful_shutdown/r1/bgpd.conf new file mode 100644 index 0000000..8e59098 --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r1/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r1/zebra.conf b/tests/topotests/bgp_peer_graceful_shutdown/r1/zebra.conf new file mode 100644 index 0000000..376a19a --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r1/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 10.10.10.1/32 +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r2/bgpd.conf b/tests/topotests/bgp_peer_graceful_shutdown/r2/bgpd.conf new file mode 100644 index 0000000..0ee1821 --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r2/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.2.2 remote-as internal + address-family ipv4 unicast + neighbor 192.168.2.2 next-hop-self + exit-address-family +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r2/zebra.conf b/tests/topotests/bgp_peer_graceful_shutdown/r2/zebra.conf new file mode 100644 index 0000000..67ca02f --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! +int r2-eth1 + ip address 192.168.2.1/24 +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r3/bgpd.conf b/tests/topotests/bgp_peer_graceful_shutdown/r3/bgpd.conf new file mode 100644 index 0000000..5945a02 --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r3/bgpd.conf @@ -0,0 +1,5 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as internal +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/r3/zebra.conf b/tests/topotests/bgp_peer_graceful_shutdown/r3/zebra.conf new file mode 100644 index 0000000..e5a37c9 --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/r3/zebra.conf @@ -0,0 +1,4 @@ +! +int r3-eth0 + ip address 192.168.2.2/24 +! diff --git a/tests/topotests/bgp_peer_graceful_shutdown/test_bgp_peer_graceful_shutdown.py b/tests/topotests/bgp_peer_graceful_shutdown/test_bgp_peer_graceful_shutdown.py new file mode 100644 index 0000000..9269826 --- /dev/null +++ b/tests/topotests/bgp_peer_graceful_shutdown/test_bgp_peer_graceful_shutdown.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if routes from R1 has local-preference set to 0 and graceful-shutdown +community. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2"), "s2": ("r2", "r3")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_orf(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + + def _bgp_converge(): + output = json.loads( + r2.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.2.2 advertised-routes json" + ) + ) + expected = {"advertisedRoutes": {"10.10.10.1/32": {"locPrf": 100}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge at R2" + + step("Mark routes from R1 as graceful-shutdown") + r2.vtysh_cmd( + """ + configure terminal + router bgp + neighbor 192.168.1.1 graceful-shutdown + """ + ) + + def _bgp_check_peer_graceful_shutdown(): + output = json.loads(r3.vtysh_cmd("show bgp ipv4 unicast 10.10.10.1/32 json")) + expected = { + "paths": [ + { + "locPrf": 0, + "community": {"string": "graceful-shutdown"}, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_peer_graceful_shutdown) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert ( + result is None + ), "local-preference is not 0 and/or graceful-shutdown community missing" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_peer_group/__init__.py b/tests/topotests/bgp_peer_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_peer_group/r1/bgpd.conf b/tests/topotests/bgp_peer_group/r1/bgpd.conf new file mode 100644 index 0000000..68d8e61 --- /dev/null +++ b/tests/topotests/bgp_peer_group/r1/bgpd.conf @@ -0,0 +1,12 @@ +! +router bgp 65001 + neighbor PG peer-group + neighbor PG remote-as external + neighbor PG timers 3 10 + neighbor 192.168.255.3 peer-group PG + neighbor r1-eth0 interface peer-group PG + neighbor PG1 peer-group + neighbor PG1 remote-as external + neighbor PG1 timers 3 20 + neighbor 192.168.251.2 peer-group PG1 +! diff --git a/tests/topotests/bgp_peer_group/r1/zebra.conf b/tests/topotests/bgp_peer_group/r1/zebra.conf new file mode 100644 index 0000000..16fd8c5 --- /dev/null +++ b/tests/topotests/bgp_peer_group/r1/zebra.conf @@ -0,0 +1,9 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +interface r1-eth1 + ip address 192.168.251.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_peer_group/r2/bgpd.conf b/tests/topotests/bgp_peer_group/r2/bgpd.conf new file mode 100644 index 0000000..d0e8f01 --- /dev/null +++ b/tests/topotests/bgp_peer_group/r2/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65002 + neighbor PG peer-group + neighbor PG remote-as external + neighbor PG timers 3 10 + neighbor r2-eth0 interface peer-group PG + neighbor PG1 peer-group + neighbor PG1 remote-as external + neighbor PG1 timers 3 20 + neighbor 192.168.251.1 peer-group PG1 +! diff --git a/tests/topotests/bgp_peer_group/r2/zebra.conf b/tests/topotests/bgp_peer_group/r2/zebra.conf new file mode 100644 index 0000000..c2ad956 --- /dev/null +++ b/tests/topotests/bgp_peer_group/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +interface r2-eth1 + ip address 192.168.251.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_peer_group/r3/bgpd.conf b/tests/topotests/bgp_peer_group/r3/bgpd.conf new file mode 100644 index 0000000..5a1340f --- /dev/null +++ b/tests/topotests/bgp_peer_group/r3/bgpd.conf @@ -0,0 +1,11 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor PG peer-group + neighbor PG remote-as external + neighbor PG timers 3 10 + neighbor 192.168.255.1 peer-group PG + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_peer_group/r3/zebra.conf b/tests/topotests/bgp_peer_group/r3/zebra.conf new file mode 100644 index 0000000..e9fdfb7 --- /dev/null +++ b/tests/topotests/bgp_peer_group/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.255.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_peer_group/test_bgp_peer-group.py b/tests/topotests/bgp_peer_group/test_bgp_peer-group.py new file mode 100644 index 0000000..5cbcd19 --- /dev/null +++ b/tests/topotests/bgp_peer_group/test_bgp_peer-group.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if peer-group works for numbered and unnumbered configurations. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_peer_group(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_peer_group_configured(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show ip bgp neighbor json")) + expected = { + "r1-eth0": {"peerGroup": "PG", "bgpState": "Established"}, + "192.168.255.3": {"peerGroup": "PG", "bgpState": "Established"}, + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Established"}, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_configured) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence in r1" + + def _bgp_peer_group_check_advertised_routes(): + output = json.loads( + tgen.gears["r3"].vtysh_cmd("show ip bgp neighbor PG advertised-routes json") + ) + expected = { + "advertisedRoutes": { + "192.168.255.0/24": { + "valid": True, + "best": True, + } + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_check_advertised_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed checking advertised routes from r3" + + +def test_bgp_peer_group_remote_as_del_readd(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + logger.info("Remove bgp peer-group PG1 remote-as neighbor should be retained") + r1.cmd( + 'vtysh -c "config t" -c "router bgp 65001" ' + + ' -c "no neighbor PG1 remote-as external" ' + ) + + def _bgp_peer_group_remoteas_del(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Active"}, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_remoteas_del) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence in r1" + + logger.info("Re-add bgp peer-group PG1 remote-as neighbor should be established") + r1.cmd( + 'vtysh -c "config t" -c "router bgp 65001" ' + + ' -c "neighbor PG1 remote-as external" ' + ) + + def _bgp_peer_group_remoteas_add(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Established"}, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_remoteas_add) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence in r1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/exabgp.env b/tests/topotests/bgp_peer_type_multipath_relax/exabgp.env new file mode 100644 index 0000000..989228a --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/exabgp.env @@ -0,0 +1,54 @@ + +[exabgp.api] +ack = false +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 5.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer1/exa_readpipe.py b/tests/topotests/bgp_peer_type_multipath_relax/peer1/exa_readpipe.py new file mode 100644 index 0000000..6c1f8b0 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer1/exa_readpipe.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"Helper script to read api commands from a pipe and feed them to ExaBGP" + +import sys + +if len(sys.argv) != 2: + sys.exit(1) +fifo = sys.argv[1] + +while True: + pipe = open(fifo, "r") + with pipe: + line = pipe.readline().strip() + if line != "": + sys.stdout.write("{}\n".format(line)) + sys.stdout.flush() + pipe.close() + +sys.exit(0) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer1/exabgp.cfg b/tests/topotests/bgp_peer_type_multipath_relax/peer1/exabgp.cfg new file mode 100644 index 0000000..e6606d2 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer1/exabgp.cfg @@ -0,0 +1,17 @@ +process announce-routes { + run /etc/exabgp/exa_readpipe.py /var/run/exabgp_peer1.in; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 1; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.2; + local-address 10.0.1.2; + local-as 64510; + peer-as 64510; + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer2/exa_readpipe.py b/tests/topotests/bgp_peer_type_multipath_relax/peer2/exa_readpipe.py new file mode 100644 index 0000000..6c1f8b0 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer2/exa_readpipe.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"Helper script to read api commands from a pipe and feed them to ExaBGP" + +import sys + +if len(sys.argv) != 2: + sys.exit(1) +fifo = sys.argv[1] + +while True: + pipe = open(fifo, "r") + with pipe: + line = pipe.readline().strip() + if line != "": + sys.stdout.write("{}\n".format(line)) + sys.stdout.flush() + pipe.close() + +sys.exit(0) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer2/exabgp.cfg b/tests/topotests/bgp_peer_type_multipath_relax/peer2/exabgp.cfg new file mode 100644 index 0000000..6a6ba0b --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer2/exabgp.cfg @@ -0,0 +1,17 @@ +process announce-routes { + run /etc/exabgp/exa_readpipe.py /var/run/exabgp_peer2.in; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 2; + encoder text; +} + +neighbor 10.0.2.1 { + router-id 10.0.2.2; + local-address 10.0.2.2; + local-as 64511; + peer-as 64511; + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer3/exa_readpipe.py b/tests/topotests/bgp_peer_type_multipath_relax/peer3/exa_readpipe.py new file mode 100644 index 0000000..6c1f8b0 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer3/exa_readpipe.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"Helper script to read api commands from a pipe and feed them to ExaBGP" + +import sys + +if len(sys.argv) != 2: + sys.exit(1) +fifo = sys.argv[1] + +while True: + pipe = open(fifo, "r") + with pipe: + line = pipe.readline().strip() + if line != "": + sys.stdout.write("{}\n".format(line)) + sys.stdout.flush() + pipe.close() + +sys.exit(0) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer3/exabgp.cfg b/tests/topotests/bgp_peer_type_multipath_relax/peer3/exabgp.cfg new file mode 100644 index 0000000..9714b1f --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer3/exabgp.cfg @@ -0,0 +1,17 @@ +process announce-routes { + run /etc/exabgp/exa_readpipe.py /var/run/exabgp_peer3.in; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 3; + encoder text; +} + +neighbor 10.0.3.1 { + router-id 10.0.3.2; + local-address 10.0.3.2; + local-as 64502; + peer-as 64501; + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer4/exa_readpipe.py b/tests/topotests/bgp_peer_type_multipath_relax/peer4/exa_readpipe.py new file mode 100644 index 0000000..6c1f8b0 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer4/exa_readpipe.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +"Helper script to read api commands from a pipe and feed them to ExaBGP" + +import sys + +if len(sys.argv) != 2: + sys.exit(1) +fifo = sys.argv[1] + +while True: + pipe = open(fifo, "r") + with pipe: + line = pipe.readline().strip() + if line != "": + sys.stdout.write("{}\n".format(line)) + sys.stdout.flush() + pipe.close() + +sys.exit(0) diff --git a/tests/topotests/bgp_peer_type_multipath_relax/peer4/exabgp.cfg b/tests/topotests/bgp_peer_type_multipath_relax/peer4/exabgp.cfg new file mode 100644 index 0000000..8c38a88 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/peer4/exabgp.cfg @@ -0,0 +1,17 @@ +process announce-routes { + run /etc/exabgp/exa_readpipe.py /var/run/exabgp_peer4.in; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 4; + encoder text; +} + +neighbor 10.0.4.1 { + router-id 10.0.4.2; + local-address 10.0.4.2; + local-as 64503; + peer-as 64501; + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/bgpd.conf b/tests/topotests/bgp_peer_type_multipath_relax/r1/bgpd.conf new file mode 100644 index 0000000..038f108 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/bgpd.conf @@ -0,0 +1,16 @@ +! +router bgp 64510 + bgp router-id 10.0.1.1 + no bgp ebgp-requires-policy + bgp confederation identifier 64501 + bgp confederation peers 64511 + bgp bestpath as-path multipath-relax + bgp bestpath compare-routerid + bgp bestpath peer-type multipath-relax + neighbor 10.0.1.2 remote-as 64510 + neighbor 10.0.3.2 remote-as 64502 + neighbor 10.0.4.2 remote-as 64503 + neighbor 10.0.5.2 remote-as 64511 +! +line vty +! diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/multipath.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/multipath.json new file mode 100644 index 0000000..11dad78 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/multipath.json @@ -0,0 +1,50 @@ +{ + "routes": { "203.0.113.0/30": [ + { + "valid":true, + "multipath":true, + "pathFrom":"external", + "peerId":"10.0.5.2" + }, + { + "valid":true, + "bestpath":true, + "selectionReason":"Peer Type", + "pathFrom":"external", + "peerId":"10.0.4.2" + }, + { + "valid":true, + "multipath":true, + "pathFrom":"internal", + "peerId":"10.0.1.2" + } +],"203.0.113.4/30": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"Confed Peer Type", + "pathFrom":"external", + "peerId":"10.0.5.2" + }, + { + "valid":true, + "multipath":true, + "pathFrom":"internal", + "peerId":"10.0.1.2" + } +],"203.0.113.8/30": [ + { + "valid":true, + "multipath":true, + "pathFrom":"external", + "peerId":"10.0.4.2" + }, + { + "valid":true, + "bestpath":true, + "selectionReason":"Router ID", + "pathFrom":"external", + "peerId":"10.0.3.2" + } +] } } diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/not-multipath.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/not-multipath.json new file mode 100644 index 0000000..c621832 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/not-multipath.json @@ -0,0 +1,50 @@ +{ + "routes": { "203.0.113.0/30": [ + { + "valid":true, + "multipath":null, + "pathFrom":"external", + "peerId":"10.0.5.2" + }, + { + "valid":true, + "bestpath":true, + "selectionReason":"Peer Type", + "pathFrom":"external", + "peerId":"10.0.4.2" + }, + { + "valid":true, + "multipath":null, + "pathFrom":"internal", + "peerId":"10.0.1.2" + } +],"203.0.113.4/30": [ + { + "valid":true, + "bestpath":true, + "selectionReason":"Confed Peer Type", + "pathFrom":"external", + "peerId":"10.0.5.2" + }, + { + "valid":true, + "multipath":null, + "pathFrom":"internal", + "peerId":"10.0.1.2" + } +],"203.0.113.8/30": [ + { + "valid":true, + "multipath":true, + "pathFrom":"external", + "peerId":"10.0.4.2" + }, + { + "valid":true, + "bestpath":true, + "selectionReason":"Router ID", + "pathFrom":"external", + "peerId":"10.0.3.2" + } +] } } diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-confed.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-confed.json new file mode 100644 index 0000000..22ec2c2 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-confed.json @@ -0,0 +1,33 @@ +{ + "203.0.113.0\/30":[ + { + "prefix":"203.0.113.0\/30", + "protocol":"bgp", + "installed":true, + "internalNextHopNum":4, + "internalNextHopActiveNum":4, + "nexthops":[ + { + "ip":"198.51.100.2", + "active":true, + "recursive":true + }, + { + "fib":true, + "ip":"10.0.3.2", + "active":true + }, + { + "ip":"198.51.100.10", + "active":true, + "recursive":true + }, + { + "fib":true, + "ip":"10.0.3.2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-iBGP.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-iBGP.json new file mode 100644 index 0000000..facddcd --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-eBGP-iBGP.json @@ -0,0 +1,33 @@ +{ + "203.0.113.0\/30":[ + { + "prefix":"203.0.113.0\/30", + "protocol":"bgp", + "installed":true, + "internalNextHopNum":4, + "internalNextHopActiveNum":4, + "nexthops":[ + { + "ip":"198.51.100.1", + "active":true, + "recursive":true + }, + { + "fib":true, + "ip":"10.0.3.2", + "active":true + }, + { + "ip":"198.51.100.10", + "active":true, + "recursive":true + }, + { + "fib":true, + "ip":"10.0.3.2", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-no-recursive.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-no-recursive.json new file mode 100644 index 0000000..5399cee --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-no-recursive.json @@ -0,0 +1,35 @@ +{ + "prefix":"203.0.113.0\/30", + "paths":[ + { + "valid":false, + "peer":{ + "peerId":"10.0.4.2", + "routerId":"10.0.4.2", + "type":"external" + } + }, + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true, + "selectionReason":"Confed Peer Type" + }, + "peer":{ + "peerId":"10.0.5.2", + "routerId":"10.0.5.2", + "type":"confed-external" + } + }, + { + "valid":true, + "multipath":true, + "peer":{ + "peerId":"10.0.1.2", + "routerId":"10.0.1.2", + "type":"confed-internal" + } + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-recursive.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-recursive.json new file mode 100644 index 0000000..7da95ae --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1-recursive.json @@ -0,0 +1,36 @@ +{ + "prefix":"203.0.113.0\/30", + "paths":[ + { + "valid":true, + "multipath":true, + "bestpath":{ + "overall":true, + "selectionReason":"Peer Type" + }, + "peer":{ + "peerId":"10.0.4.2", + "routerId":"10.0.4.2", + "type":"external" + } + }, + { + "valid":true, + "multipath":true, + "peer":{ + "peerId":"10.0.5.2", + "routerId":"10.0.5.2", + "type":"confed-external" + } + }, + { + "valid":true, + "multipath":true, + "peer":{ + "peerId":"10.0.1.2", + "routerId":"10.0.1.2", + "type":"confed-internal" + } + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1.json new file mode 100644 index 0000000..a90669a --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix1.json @@ -0,0 +1,33 @@ +{ + "prefix":"203.0.113.0\/30", + "paths":[ + { + "multipath":true, + "peer":{ + "peerId":"10.0.5.2", + "routerId":"10.0.5.2", + "type":"confed-external" + } + }, + { + "multipath":true, + "bestpath":{ + "overall":true, + "selectionReason":"Peer Type" + }, + "peer":{ + "peerId":"10.0.4.2", + "routerId":"10.0.4.2", + "type":"external" + } + }, + { + "multipath":true, + "peer":{ + "peerId":"10.0.1.2", + "routerId":"10.0.1.2", + "type":"confed-internal" + } + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json new file mode 100644 index 0000000..1bf38ef --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-ip-route.json @@ -0,0 +1,23 @@ +{ + "203.0.113.8\/30":[ + { + "prefix":"203.0.113.8\/30", + "protocol":"bgp", + "installed":true, + "internalNextHopNum":2, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "fib":true, + "ip":"10.0.3.2", + "active":true + }, + { + "fib":null, + "ip":"198.51.100.10", + "active":null + } + ] + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-no-recursive.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-no-recursive.json new file mode 100644 index 0000000..33d0f2d --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-no-recursive.json @@ -0,0 +1,21 @@ +{ + "prefix":"203.0.113.8\/30", + "paths":[ + { + "valid":false, + "peer":{ + "peerId":"10.0.4.2", + "routerId":"10.0.4.2", + "type":"external" + } + }, + { + "valid":true, + "peer":{ + "peerId":"10.0.3.2", + "routerId":"10.0.3.2", + "type":"external" + } + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-recursive.json b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-recursive.json new file mode 100644 index 0000000..6ac2512 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/prefix3-recursive.json @@ -0,0 +1,23 @@ +{ + "prefix":"203.0.113.8\/30", + "paths":[ + { + "valid":true, + "multipath":true, + "peer":{ + "peerId":"10.0.4.2", + "routerId":"10.0.4.2", + "type":"external" + } + }, + { + "valid":true, + "multipath":true, + "peer":{ + "peerId":"10.0.3.2", + "routerId":"10.0.3.2", + "type":"external" + } + } + ] +} diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r1/zebra.conf b/tests/topotests/bgp_peer_type_multipath_relax/r1/zebra.conf new file mode 100644 index 0000000..911aa1c --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r1/zebra.conf @@ -0,0 +1,27 @@ +! +hostname r1 +! +interface r1-eth0 + description ExaBGP iBGP peer1 + ip address 10.0.1.1/24 + no link-detect +! +interface r1-eth1 + description ExaBGP peer3 + ip address 10.0.3.1/24 + no link-detect +! +interface r1-eth2 + description ExaBGP peer4 + ip address 10.0.4.1/24 + no link-detect +! +interface r1-eth3 + description r2 confed peer + ip address 10.0.5.1/24 + no link-detect +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r2/bgpd.conf b/tests/topotests/bgp_peer_type_multipath_relax/r2/bgpd.conf new file mode 100644 index 0000000..2362a19 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r2/bgpd.conf @@ -0,0 +1,19 @@ +! +!log file bgpd.log +! +router bgp 64511 + bgp confederation identifier 64501 + bgp confederation peers 64510 + bgp router-id 10.0.5.2 + no bgp ebgp-requires-policy + neighbor 10.0.2.2 remote-as 64511 + neighbor 10.0.5.1 remote-as 64510 + ! + address-family ipv4 unicast + neighbor 10.0.5.1 route-map dropall in + exit-address-family +! +route-map dropall deny 10 +! +line vty +! diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r2/staticd.conf b/tests/topotests/bgp_peer_type_multipath_relax/r2/staticd.conf new file mode 100644 index 0000000..35ebe0d --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r2/staticd.conf @@ -0,0 +1,4 @@ +hostname r2 +! +ip route 198.51.100.0/24 10.0.2.2 +! diff --git a/tests/topotests/bgp_peer_type_multipath_relax/r2/zebra.conf b/tests/topotests/bgp_peer_type_multipath_relax/r2/zebra.conf new file mode 100644 index 0000000..900e7d4 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/r2/zebra.conf @@ -0,0 +1,19 @@ +! +! +hostname r2 +! +interface r2-eth0 + description ExaBGP peer + ip address 10.0.2.1/24 + no link-detect +! +interface r2-eth1 + description r1 confed peer + ip address 10.0.5.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py b/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py new file mode 100755 index 0000000..340df71 --- /dev/null +++ b/tests/topotests/bgp_peer_type_multipath_relax/test_bgp_peer-type_multipath-relax.py @@ -0,0 +1,462 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 Arista Networks, Inc. +# + +""" +test_bgp_peer-type_multipath-relax.py: + +Test the effects of the "bgp bestpath peer-type multipath-relax" command + +- enabling the command allows eBGP, iBGP, and confed routes to be multipath +- the choice of best path is not affected +- disabling the command removes iBGP/confed routes from multipath +- enabling the command does not forgive eBGP routes of the requirement + (when enabled) that next hops resolve over connected routes +- a mixed-type multipath next hop, when published to zebra, does not + require resolving next hops over connected routes +- with the command enabled, an all-eBGP multipath next hop still requires + resolving next hops over connected routes when published to zebra + +Topology used by the test: + + eBGP +------+ iBGP + peer1 ---- | r1 | ---- peer3 + | | +peer2 ---- r2 ---- | | ---- peer4 + iBGP confed +------+ eBGP + +r2 is present in this topology because ExaBGP does not currently support +confederations so we use FRR to advertise the required AS_CONFED_SEQUENCE. + +Routes are advertised from different peers to form interesting multipaths. + + peer1 peer2 peer3 peer4 multipath on r1 + +203.0.113.0/30 x x x all 3 +203.0.113.4/30 x x confed-iBGP +203.0.113.8/30 x x eBGP-only + +There is also a BGP-advertised route used only for recursively resolving +next hops. +""" + +import functools +import json +import os +import pytest +import sys + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Prefixes used in the test +prefix1 = "203.0.113.0/30" +prefix2 = "203.0.113.4/30" +prefix3 = "203.0.113.8/30" +# Next hops used for iBGP/confed routes +resolved_nh1 = "198.51.100.1" +resolved_nh2 = "198.51.100.2" +# BGP route used for recursive resolution +bgp_resolving_prefix = "198.51.100.0/24" +# Next hop that will require non-connected recursive resolution +ebgp_resolved_nh = "198.51.100.10" + + +def build_topo(tgen): + "Build function" + + # Set up routers + tgen.add_router("r1") # DUT + tgen.add_router("r2") + + # Set up peers + for peern in range(1, 5): + peer = tgen.add_exabgp_peer( + "peer{}".format(peern), + ip="10.0.{}.2/24".format(peern), + defaultRoute="via 10.0.{}.1".format(peern), + ) + if peern == 2: + tgen.add_link(tgen.gears["r2"], peer) + else: + tgen.add_link(tgen.gears["r1"], peer) + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # For all registered routers, load the zebra configuration file + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + # Start up exabgp peers + peers = tgen.exabgp_peers() + for peer in peers: + fifo_in = "/var/run/exabgp_{}.in".format(peer) + if os.path.exists(fifo_in): + os.remove(fifo_in) + os.mkfifo(fifo_in, 0o777) + logger.info("Starting ExaBGP on peer {}".format(peer)) + peer_dir = os.path.join(CWD, peer) + env_file = os.path.join(CWD, "exabgp.env") + peers[peer].start(peer_dir, env_file) + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def exabgp_cmd(peer, cmd): + pipe = open("/run/exabgp_{}.in".format(peer), "w") + with pipe: + pipe.write(cmd) + pipe.close() + + +def test_bgp_peer_type_multipath_relax_test1(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # Send a non-connected route to resolve others + exabgp_cmd( + "peer3", "announce route {} next-hop self\n".format(bgp_resolving_prefix) + ) + + # It seems that if you write to the exabgp socket too quickly in + # succession, requests get lost. So verify prefix1 now instead of + # after all the prefixes are advertised. + logger.info("Create and verify mixed-type multipaths") + exabgp_cmd( + "peer1", + "announce route {} next-hop {} as-path [ 64499 ]\n".format( + prefix1, resolved_nh1 + ), + ) + exabgp_cmd( + "peer2", + "announce route {} next-hop {} as-path [ 64499 ]\n".format( + prefix1, resolved_nh2 + ), + ) + exabgp_cmd("peer4", "announce route {} next-hop self\n".format(prefix1)) + reffile = os.path.join(CWD, "r1/prefix1.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip bgp {} json".format(prefix1), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Mixed-type multipath not found" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test2(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + logger.info("Create and verify eBGP and iBGP+confed multipaths") + exabgp_cmd( + "peer1", + "announce route {} next-hop {} as-path [ 64499 ]\n".format( + prefix2, resolved_nh1 + ), + ) + exabgp_cmd( + "peer2", + "announce route {} next-hop {} as-path [ 64499 ]\n".format( + prefix2, resolved_nh2 + ), + ) + exabgp_cmd("peer3", "announce route {} next-hop self".format(prefix3)) + exabgp_cmd("peer4", "announce route {} next-hop self".format(prefix3)) + reffile = os.path.join(CWD, "r1/multipath.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, r1, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Not all expected multipaths found" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test3(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + logger.info("Toggle peer-type multipath-relax and verify the changes") + r1.vtysh_cmd( + "conf\n router bgp 64510\n no bgp bestpath peer-type multipath-relax\n" + ) + # This file verifies "multipath" is not set + reffile = os.path.join(CWD, "r1/not-multipath.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, r1, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Disabling peer-type multipath-relax did not take effect" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test4(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + r1.vtysh_cmd("conf\n router bgp 64510\n bgp bestpath peer-type multipath-relax\n") + reffile = os.path.join(CWD, "r1/multipath.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, r1, "show ip bgp json", expected + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Reenabling peer-type multipath-relax did not take effect" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test5(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + logger.info("Check recursive resolution of eBGP next hops is not affected") + # eBGP next hop resolution rejects recursively resolved next hops by + # default, even with peer-type multipath-relax + exabgp_cmd( + "peer4", "announce route {} next-hop {}\n".format(prefix3, ebgp_resolved_nh) + ) + reffile = os.path.join(CWD, "r1/prefix3-no-recursive.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip bgp {} json".format(prefix3), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix3) + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test6(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + exabgp_cmd( + "peer4", "announce route {} next-hop {}\n".format(prefix1, ebgp_resolved_nh) + ) + reffile = os.path.join(CWD, "r1/prefix1-no-recursive.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip bgp {} json".format(prefix1), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix1) + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test7(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # When other config allows recursively resolved eBGP next hops, + # such next hops in all-eBGP multipaths should be valid + r1.vtysh_cmd("conf\n router bgp 64510\n neighbor 10.0.4.2 ebgp-multihop\n") + reffile = os.path.join(CWD, "r1/prefix3-recursive.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip bgp {} json".format(prefix3), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix3) + assert res is None, assertMsg + + reffile = os.path.join(CWD, "r1/prefix1-recursive.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip bgp {} json".format(prefix1), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "Recursive eBGP next hop not as expected for {}".format(prefix1) + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test8(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + logger.info("Check mixed-type multipath next hop recursive resolution in FIB") + # There are now two eBGP-learned routes with a recursively resolved next; + # hop; one is all-eBGP multipath, and the other is iBGP/eBGP/ + # confed-external. The peer-type multipath-relax feature only enables + # recursive resolution in FIB if any next hop is iBGP/confed-learned. The + # all-eBGP multipath will have only one valid next hop in the FIB. + reffile = os.path.join(CWD, "r1/prefix3-ip-route.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip route {} json".format(prefix3), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "FIB next hops mismatch for all-eBGP multipath" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test9(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # check confed-external enables recursively resolved next hops by itself + exabgp_cmd( + "peer1", + "withdraw route {} next-hop {} as-path [ 64499 ]\n".format( + prefix1, resolved_nh1 + ), + ) + reffile = os.path.join(CWD, "r1/prefix1-eBGP-confed.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip route {} json".format(prefix1), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "FIB next hops mismatch for eBGP+confed-external multipath" + assert res is None, assertMsg + + +def test_bgp_peer_type_multipath_relax_test10(): + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # check iBGP by itself + exabgp_cmd( + "peer1", + "announce route {} next-hop {} as-path [ 64499 ]\n".format( + prefix1, resolved_nh1 + ), + ) + exabgp_cmd( + "peer2", + "withdraw route {} next-hop {} as-path [ 64499 ]\n".format( + prefix1, resolved_nh2 + ), + ) + reffile = os.path.join(CWD, "r1/prefix1-eBGP-iBGP.json") + expected = json.loads(open(reffile).read()) + test_func = functools.partial( + topotest.router_json_cmp, + r1, + "show ip route {} json".format(prefix1), + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=10, wait=1) + assertMsg = "FIB next hops mismatch for eBGP+iBGP multipath" + assert res is None, assertMsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_prefix_list_any/__init__.py b/tests/topotests/bgp_prefix_list_any/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_prefix_list_any/r1/bgpd.conf b/tests/topotests/bgp_prefix_list_any/r1/bgpd.conf new file mode 100644 index 0000000..14c28ca --- /dev/null +++ b/tests/topotests/bgp_prefix_list_any/r1/bgpd.conf @@ -0,0 +1,15 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + neighbor 2001:db8:1::2 remote-as external + address-family ipv4 unicast + network 192.168.0.1/32 + no neighbor 2001:db8:1::2 activate + exit-address-family + address-family ipv6 unicast + neighbor 2001:db8:1::2 activate + network 2001:db8::1/128 + exit-address-family +! diff --git a/tests/topotests/bgp_prefix_list_any/r1/zebra.conf b/tests/topotests/bgp_prefix_list_any/r1/zebra.conf new file mode 100644 index 0000000..c01a8cf --- /dev/null +++ b/tests/topotests/bgp_prefix_list_any/r1/zebra.conf @@ -0,0 +1,5 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf b/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf new file mode 100644 index 0000000..7332059 --- /dev/null +++ b/tests/topotests/bgp_prefix_list_any/r2/bgpd.conf @@ -0,0 +1,50 @@ +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.1 remote-as external + neighbor 2001:db8:1::1 remote-as external + address-family ipv4 unicast + network 10.10.10.1/32 + network 10.10.10.2/32 + network 10.10.10.3/32 + network 10.10.10.10/32 + no neighbor 2001:db8:1::1 activate + neighbor 192.168.1.1 route-map r1-v4 out + exit-address-family + address-family ipv6 unicast + network 2001:db8:10::1/128 + network 2001:db8:10::2/128 + network 2001:db8:10::3/128 + network 2001:db8:10::10/128 + neighbor 2001:db8:1::1 activate + neighbor 2001:db8:1::1 route-map r1-v6 out + exit-address-family +! +ip prefix-list r1-1 seq 5 permit 10.10.10.1/32 +ip prefix-list r1-1 seq 10 permit 10.10.10.2/32 +ip prefix-list r1-1 seq 15 permit 10.10.10.3/32 +ip prefix-list r1-2 seq 5 permit 10.10.10.10/32 +! +ipv6 prefix-list r1-1 seq 5 permit 2001:db8:10::1/128 +ipv6 prefix-list r1-1 seq 10 permit 2001:db8:10::2/128 +ipv6 prefix-list r1-1 seq 15 permit 2001:db8:10::3/128 +ipv6 prefix-list r1-2 seq 5 permit 2001:db8:10::10/128 +! +route-map r1-v4 permit 10 + match ip address prefix-list r1-1 +exit +! +route-map r1-v4 permit 20 + match ip address prefix-list r1-2 +exit +! +route-map r1-v6 permit 10 + match ipv6 address prefix-list r1-1 +exit +! +route-map r1-v6 permit 20 + match ipv6 address prefix-list r1-2 +exit diff --git a/tests/topotests/bgp_prefix_list_any/r2/zebra.conf b/tests/topotests/bgp_prefix_list_any/r2/zebra.conf new file mode 100644 index 0000000..e90135c --- /dev/null +++ b/tests/topotests/bgp_prefix_list_any/r2/zebra.conf @@ -0,0 +1,5 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bgp_prefix_list_any/test_bgp_prefix_list_any.py b/tests/topotests/bgp_prefix_list_any/test_bgp_prefix_list_any.py new file mode 100644 index 0000000..5d6440c --- /dev/null +++ b/tests/topotests/bgp_prefix_list_any/test_bgp_prefix_list_any.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if route-map works correctly when modifying prefix-list +from deny to permit with any, and vice-versa. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_prefix_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_prefixes_sent(count): + output = json.loads(r2.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": {"192.168.1.1": {"pfxSnt": count, "state": "Established"}} + }, + "ipv6Unicast": { + "peers": {"2001:db8:1::1": {"pfxSnt": count, "state": "Established"}} + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_prefixes_sent, 4) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge initial topology" + + r2.vtysh_cmd( + """ + configure terminal + ip prefix-list r1-2 seq 5 deny any + ipv6 prefix-list r1-2 seq 5 deny any + """ + ) + + test_func = functools.partial(_bgp_prefixes_sent, 3) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Only 3 prefixes MUST be advertised, seeing more" + + r2.vtysh_cmd( + """ + configure terminal + ip prefix-list r1-2 seq 5 permit 10.10.10.10/32 + ipv6 prefix-list r1-2 seq 5 permit 2001:db8:10::10/128 + """ + ) + + test_func = functools.partial(_bgp_prefixes_sent, 4) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "More or less prefixes advertised to r1, MUST be 4" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_prefix_list_topo1/__init__.py b/tests/topotests/bgp_prefix_list_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_prefix_list_topo1/prefix_lists.json b/tests/topotests/bgp_prefix_list_topo1/prefix_lists.json new file mode 100644 index 0000000..3bb07ad --- /dev/null +++ b/tests/topotests/bgp_prefix_list_topo1/prefix_lists.json @@ -0,0 +1,123 @@ +{ + "address_types": ["ipv4"], + "ipv4base":"10.0.0.0", + "ipv4mask":30, + "ipv6base":"fd00::", + "ipv6mask":64, + "link_ip_start":{"ipv4":"10.0.0.0", "v4mask":30, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", "add_static_route":"yes"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", "add_static_route":"yes"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", "add_static_route":"yes"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r4":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", "add_static_route":"yes"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_prefix_list_topo1/prefix_modify.json b/tests/topotests/bgp_prefix_list_topo1/prefix_modify.json new file mode 100644 index 0000000..4a066ae --- /dev/null +++ b/tests/topotests/bgp_prefix_list_topo1/prefix_modify.json @@ -0,0 +1,120 @@ +{ + "address_types": ["ipv4"], + "ipv4base":"192.120.1.0", + "ipv4mask":24, + "link_ip_start":{"ipv4":"192.120.1.0", "v4mask":30, "ipv6":"fd00::", "v6mask":64}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "type": "loopback", "add_static_route":"yes"}, + "r2":{"ipv4":"auto"}, + "r3":{"ipv4":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback", "add_static_route":"yes"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3":{ + "links":{ + "lo": {"ipv4": "auto", "type": "loopback", "add_static_route":"yes"}, + "r1":{"ipv4":"auto"}, + "r2":{"ipv4":"auto"}, + "r4":{"ipv4":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4":{ + "links":{ + "lo": {"ipv4": "auto", "type": "loopback", "add_static_route":"yes"}, + "r3":{"ipv4":"auto"} + }, + "bgp":{ + "local_as":"200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_prefix_list_topo1/test_prefix_lists.py b/tests/topotests/bgp_prefix_list_topo1/test_prefix_lists.py new file mode 100644 index 0000000..f351dde --- /dev/null +++ b/tests/topotests/bgp_prefix_list_topo1/test_prefix_lists.py @@ -0,0 +1,1341 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test prefix-list functionality: + +Test steps +- Create topology (setup module) + Creating 4 routers topology, r1, r2, r3 are in IBGP and + r3, r4 are in EBGP +- Bring up topology +- Verify for bgp to converge + +IP prefix-list tests +- Test ip prefix-lists IN permit +- Test ip prefix-lists OUT permit +- Test ip prefix-lists IN deny and permit any +- Test delete ip prefix-lists +- Test ip prefix-lists OUT deny and permit any +- Test modify ip prefix-lists IN permit to deny +- Test modify ip prefix-lists IN deny to permit +- Test modify ip prefix-lists OUT permit to deny +- Test modify prefix-lists OUT deny to permit +- Test ip prefix-lists implicit deny +""" + +import sys +import time +import os +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + create_prefix_lists, + verify_prefix_lists, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp_and_verify +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +bgp_convergence = False + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/prefix_lists.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global BGP_CONVERGENCE + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_ip_prefix_lists_in_permit(request): + """ + Create ip prefix list and test permit prefixes IN direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "20.0.20.1/32", "no_of_ip": 1, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [{"seqid": 10, "network": "any", "action": "permit"}] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure bgp neighbor with prefix list + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "prefix_lists": [ + {"name": "pf_list_1", "direction": "in"} + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_ip_prefix_lists_out_permit(request): + """ + Create ip prefix list and test permit prefixes out direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 1, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Static routes + input_dict_1 = { + "r1": { + "static_routes": [ + {"network": "20.0.20.1/32", "no_of_ip": 1, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_5 = { + "r3": { + "static_routes": [ + {"network": "10.0.0.2/30", "no_of_ip": 1, "next_hop": "10.0.0.9"} + ] + } + } + result = create_static_routes(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + + # Create ip prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": 10, "network": "20.0.20.1/32", "action": "permit"} + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + # Configure bgp neighbor with prefix list + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "prefix_lists": [ + { + "name": "pf_list_1", + "direction": "out", + } + ] + } + } + } + }, + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ], + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict_1, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_ip_prefix_lists_in_deny_and_permit_any(request): + """ + Create ip prefix list and test permit/deny prefixes IN direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 1, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + # Create ip prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "10", "network": "10.0.20.1/32", "action": "deny"}, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure bgp neighbor with prefix list + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "prefix_lists": [ + {"name": "pf_list_1", "direction": "in"} + ] + } + } + } + } + } + } + } + } + }, + } + # Configure prefix list to bgp neighbor + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_delete_prefix_lists(request): + """ + Delete ip prefix list + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "10", "network": "10.0.20.1/32", "action": "deny"} + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_prefix_lists(tgen, input_dict_2) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Delete prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.20.1/32", + "action": "deny", + "delete": True, + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_ip_prefix_lists_out_deny_and_permit_any(request): + """ + Create ip prefix list and test deny/permit any prefixes OUT direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Static Routes + input_dict_1 = { + "r2": { + "static_routes": [ + {"network": "20.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.1"} + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + + # Create ip prefix list + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "deny", + }, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "prefix_lists": [ + { + "name": "pf_list_1", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict_1, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_modify_prefix_lists_in_permit_to_deny(request): + """ + Modify ip prefix list and test permit to deny prefixes IN direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_3 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "prefix_lists": [ + {"name": "pf_list_1", "direction": "in"} + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Modify prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "deny", + }, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_modify_prefix_lists_in_deny_to_permit(request): + """ + Modify ip prefix list and test deny to permit prefixes IN direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + + # Create ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "deny", + }, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "prefix_lists": [ + {"name": "pf_list_1", "direction": "in"} + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Modify ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_modify_prefix_lists_out_permit_to_deny(request): + """ + Modify ip prefix list and test permit to deny prefixes OUT direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + + # Create ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "prefix_lists": [ + { + "name": "pf_list_1", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Modify ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "deny", + }, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_modify_prefix_lists_out_deny_to_permit(request): + """ + Modify ip prefix list and test deny to permit prefixes OUT direction + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + # Create ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "deny", + }, + {"seqid": "11", "network": "any", "action": "permit"}, + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "prefix_lists": [ + { + "name": "pf_list_1", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Modify ip prefix list + input_dict_1 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_ip_prefix_lists_implicit_deny(request): + """ + Create ip prefix list and test implicit deny + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + # Create Static Routes + input_dict = { + "r1": { + "static_routes": [ + {"network": "10.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.2"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create Static Routes + input_dict_1 = { + "r2": { + "static_routes": [ + {"network": "20.0.20.1/32", "no_of_ip": 9, "next_hop": "10.0.0.1"} + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to redistribute static routes + # Create ip prefix list + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + { + "seqid": "10", + "network": "10.0.0.0/8", + "le": "32", + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Configure prefix list to bgp neighbor + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + }, + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "prefix_lists": [ + { + "name": "pf_list_1", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + result = verify_rib( + tgen, "ipv4", dut, input_dict_1, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_prefix_list_topo1/test_prefix_modify.py b/tests/topotests/bgp_prefix_list_topo1/test_prefix_modify.py new file mode 100644 index 0000000..541b9de --- /dev/null +++ b/tests/topotests/bgp_prefix_list_topo1/test_prefix_modify.py @@ -0,0 +1,404 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test prefix-list functionality: + +Test steps +- Create topology (setup module) + Creating 4 routers topology, r1, r2, r3 are in IBGP and + r3, r4 are in EBGP +- Bring up topology +- Verify for bgp to converge + +IP prefix-list tests +- Test modify prefix-list action +""" + +import sys +import time +import os +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + create_prefix_lists, + step, + create_route_maps, + check_router_status, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp + +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd] + + +# Global variables +bgp_convergence = False + +IPV4_PF3 = "192.168.0.0/18" +IPV4_PF4 = "192.150.10.0/24" +IPV4_PF5 = "192.168.10.1/32" +IPV4_PF6 = "192.168.10.10/32" +IPV4_PF7 = "192.168.10.0/24" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/prefix_lists.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global BGP_CONVERGENCE + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_bug_prefix_lists_deny_to_permit_p1(request): + """ + Verify modification of prefix-list action + """ + + tgen = get_topogen() + if BGP_CONVERGENCE is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + # base config + step("Configure IPV4 and IPv6 IBGP and EBGP session as mentioned in setup") + step("Configure static routes on R2 with Null 0 nexthop") + input_dict_1 = { + "r2": { + "static_routes": [ + {"network": IPV4_PF7, "no_of_ip": 1, "next_hop": "Null0"}, + {"network": IPV4_PF6, "no_of_ip": 1, "next_hop": "Null0"}, + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Advertise static route in BGP using redistribute static command") + input_dict_4 = { + "r2": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "All the static route advertised in R4 as BGP " + "routes verify using 'show ip bgp'and 'show bgp'" + ) + dut = "r4" + protocol = "bgp" + + input_dict_route = { + "r4": {"static_routes": [{"network": IPV4_PF7}, {"network": IPV4_PF6}]} + } + + result = verify_rib(tgen, "ipv4", dut, input_dict_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure IPv4 and IPv6 prefix-list") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": "5", "network": IPV4_PF7, "action": "deny"} + ], + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "10", "network": IPV4_PF7, "action": "permit"} + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "configure route-map seq to permit IPV4 prefix list and seq" + "2 to permit IPV6 prefix list and apply it to out direction on R3" + ) + + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1": [ + { + "action": "permit", + "match": {"ipv4": {"prefix_lists": "pf_list_1"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1", + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R4 should not have any IPv4 and IPv6 BGP routes using " + "show ip bgp show bgp" + ) + + dut = "r4" + protocol = "bgp" + + result = verify_rib( + tgen, "ipv4", dut, input_dict_route, protocol=protocol, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n" "Error : Routes are still present \n {}".format( + tc_name, result + ) + + step("Modify IPv4/IPv6 prefix-list sequence 5 to another value on R3") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "5", "network": IPV4_PF4, "action": "deny"} + ], + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify /24 and /120 routes present on" + "R4 BGP table using show ip bgp show bgp" + ) + input_dict = {"r4": {"static_routes": [{"network": IPV4_PF7}]}} + + dut = "r4" + protocol = "bgp" + + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Change prefix-list to same as original on R3") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "5", "network": IPV4_PF7, "action": "deny"} + ], + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify /24 and /120 routes removed on" + "R4 BGP table using show ip bgp show bgp" + ) + + result = verify_rib( + tgen, "ipv4", dut, input_dict, protocol=protocol, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n" "Error : Routes are still present \n {}".format( + tc_name, result + ) + + step("Modify IPv4/IPv6 prefix-list sequence 5 to another value") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "5", "network": IPV4_PF4, "action": "deny"} + ], + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Clear BGP on R3 and verify the routes") + clear_bgp(tgen, "ipv4", "r3") + + result = verify_rib(tgen, "ipv4", dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("On R3 add prefix-list permit any for IPv4 and IPv6 seq 15") + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1": [ + {"seqid": "15", "network": "any", "action": "permit"} + ], + } + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("verify /24 and /32 /120 and /128 routes are present on R4") + result = verify_rib(tgen, "ipv4", dut, input_dict_route) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_prefix_sid/__init__.py b/tests/topotests/bgp_prefix_sid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_prefix_sid/exabgp.env b/tests/topotests/bgp_prefix_sid/exabgp.env new file mode 100644 index 0000000..bb36af5 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/exabgp.env @@ -0,0 +1,54 @@ + +[exabgp.api] +ack = false +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg b/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg new file mode 100644 index 0000000..a5108ff --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/peer1/exabgp.cfg @@ -0,0 +1,101 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.101; + local-address 10.0.0.101; + local-as 2; + peer-as 1; + + family { + ipv4 nlri-mpls; + } + + static { + # ref: draft-ietf-idr-bgp-prefix-sid-27 + # + # IANA temporarily assigned the following: + # attribute code type (suggested value: 40) to + # the BGP Prefix-SID attribute + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Length | RESERVED | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Flags | Label Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Label Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # Figure. Label-Index TLV (Prefix-SID type-1) + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Length | Flags | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Flags | + # +-+-+-+-+-+-+-+-+ + # + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | SRGB 1 (6 octets) | + # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | SRGB n (6 octets) | + # | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # Figure. Originator SRGB TLV (Prefix-SID type-3) + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000001 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.1/32 next-hop 10.0.0.101 label [800001] attribute [0x28 0xc0 0x0100070000000000000103000800000c350000000a]; + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000002 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.2/32 next-hop 10.0.0.101 label [800002] attribute [0x28 0xc0 0x0100070000000000000203000800000c350000000a]; + + # ExaBGP generic-attribute binary pattern: + # Attribute-type: 0x28 (40:BGP_PREFIX_SID) + # Attribute-flag: 0xc0 (Option, Transitive) + # Attribute-body: Label-Index TLV and Originator SRGB TLV + # Label-Index TLV: 0x01000700000000000001 + # Type (08bit): 0x01 + # Length (16bit): 0x0007 + # RESERVED (08bit): 0x00 + # Flags (16bit): 0x0000 + # Label Index (32bit): 0x00000003 + # Originator SRGB TLV: 0x03000800000c350000000a + # Type (08bit): 0x03 + # Length (16bit): 0x0008 (nb-SRGB is 1) + # Flags (16bit): 0x0000 + # SRGB1 (48bit): 0x0c3500:0x00000a (800000-800010 is SRGB1) + route 3.0.0.3/32 next-hop 10.0.0.101 label [800003] attribute [0x28 0xc0 0x0100070000000000000303000800000c350000000a]; + } +} diff --git a/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg b/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg new file mode 100644 index 0000000..50ebdc2 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/peer2/exabgp.cfg @@ -0,0 +1,22 @@ +process receive-routes { + run /etc/exabgp/exa-receive.py --no-timestamp 2; + encoder json; +} + +neighbor 10.0.0.1 { + router-id 10.0.0.102; + local-address 10.0.0.102; + local-as 3; + peer-as 1; + + family { + ipv4 nlri-mpls; + } + api { + processes [ receive-routes ]; + receive { + parsed; + update; + } + } +} diff --git a/tests/topotests/bgp_prefix_sid/r1/bgpd.conf b/tests/topotests/bgp_prefix_sid/r1/bgpd.conf new file mode 100644 index 0000000..e02226f --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/r1/bgpd.conf @@ -0,0 +1,17 @@ +log stdout notifications +log commands +! +router bgp 1 + bgp router-id 10.0.0.1 + no bgp default ipv4-unicast + no bgp ebgp-requires-policy + neighbor 10.0.0.101 remote-as 2 + neighbor 10.0.0.101 timers 3 10 + neighbor 10.0.0.102 remote-as 3 + neighbor 10.0.0.102 timers 3 10 + ! + address-family ipv4 labeled-unicast + neighbor 10.0.0.101 activate + neighbor 10.0.0.102 activate + exit-address-family +! diff --git a/tests/topotests/bgp_prefix_sid/r1/zebra.conf b/tests/topotests/bgp_prefix_sid/r1/zebra.conf new file mode 100644 index 0000000..0cd2605 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/r1/zebra.conf @@ -0,0 +1,7 @@ +hostname r1 +! +interface r1-eth0 + ip address 10.0.0.1/24 + no shutdown +! +line vty diff --git a/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py b/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py new file mode 100644 index 0000000..1e6e731 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid/test_bgp_prefix_sid.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_prefix_sid.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by LINE Corporation +# Copyright (c) 2020 by Hiroki Shirokura +# + +""" +test_bgp_prefix_sid.py: Test BGP topology with EBGP on prefix-sid +""" + +import json +import os +import sys +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + router = tgen.add_router("r1") + switch = tgen.add_switch("s1") + switch.add_link(router) + + switch = tgen.gears["s1"] + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.101", defaultRoute="via 10.0.0.1") + peer2 = tgen.add_exabgp_peer("peer2", ip="10.0.0.102", defaultRoute="via 10.0.0.1") + switch.add_link(peer1) + switch.add_link(peer2) + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format("r1")) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format("r1")) + ) + router.start() + + logger.info("starting exaBGP on peer1") + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, "exabgp.env") + logger.info("Running ExaBGP peer") + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def test_r1_receive_and_advertise_prefix_sid_type1(): + tgen = get_topogen() + router = tgen.gears["r1"] + + def _check_type1_r1(router, prefix, remoteLabel, labelIndex): + output = router.vtysh_cmd( + "show bgp ipv4 labeled-unicast {} json".format(prefix) + ) + output = json.loads(output) + expected = { + "prefix": prefix, + "advertisedTo": {"10.0.0.101": {}, "10.0.0.102": {}}, + "paths": [ + { + "valid": True, + "remoteLabel": remoteLabel, + "labelIndex": labelIndex, + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_check_type1_r1, router, "3.0.0.1/32", 800001, 1) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_r1 in "{}"'.format(router) + + test_func = functools.partial(_check_type1_r1, router, "3.0.0.2/32", 800002, 2) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_r1 in "{}"'.format(router) + + +def exabgp_get_update_prefix(filename, afi, nexthop, prefix): + with open(filename) as f: + for line in f.readlines(): + output = json.loads(line) + ret = output.get("neighbor") + if ret is None: + continue + ret = ret.get("message") + if ret is None: + continue + ret = ret.get("update") + if ret is None: + continue + ret = ret.get("announce") + if ret is None: + continue + ret = ret.get(afi) + if ret is None: + continue + for nh in ret.get(nexthop, []): + if nh.get("nlri") == prefix: + return output + return "Not found" + + +def test_peer2_receive_prefix_sid_type1(): + tgen = get_topogen() + peer2 = tgen.gears["peer2"] + logfile = "{}/{}-received.log".format(peer2.gearlogdir, peer2.name) + + def _check_type1_peer2(prefix, label): + output = exabgp_get_update_prefix( + logfile, "ipv4 nlri-mpls", "10.0.0.101", prefix + ) + expected = { + "type": "update", + "neighbor": { + "address": { + "peer": "10.0.0.1", + }, + "message": { + "update": { + "announce": { + "ipv4 nlri-mpls": { + "10.0.0.101": [ + { + "nlri": prefix, + "label": [[label]], + } + ] + } + }, + } + }, + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_check_type1_peer2, "3.0.0.1/32", label=8001) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_peer2 in "{}"'.format("peer2") + + test_func = functools.partial(_check_type1_peer2, "3.0.0.2/32", label=8002) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, 'Failed _check_type1_peer2 in "{}"'.format("peer2") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + ret = pytest.main(args) + sys.exit(ret) diff --git a/tests/topotests/bgp_prefix_sid2/peer1/exabgp.cfg b/tests/topotests/bgp_prefix_sid2/peer1/exabgp.cfg new file mode 100644 index 0000000..7a291a3 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/peer1/exabgp.cfg @@ -0,0 +1,27 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.101; + local-address 10.0.0.101; + local-as 2; + peer-as 1; + + family { + ipv6 mpls-vpn; + } + + static { + route 2001:1::/64 { + rd 2:10; + next-hop 2001::2; + extended-community [ target:2:10 ]; + label 3; + attribute [0x28 0xc0 0x050019000100150020010db800010001000000000000000100ffff00 ]; + } + route 2001:2::/64 { + rd 2:10; + next-hop 2001::2; + extended-community [ target:2:10 ]; + label 3; + attribute [0x28 0xc0 0x050019000100150020010db800010001000000000000000100ffff00 ]; + } + } +} diff --git a/tests/topotests/bgp_prefix_sid2/peer1/exabgp.env b/tests/topotests/bgp_prefix_sid2/peer1/exabgp.env new file mode 100644 index 0000000..6c554f5 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/peer1/exabgp.env @@ -0,0 +1,53 @@ + +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_prefix_sid2/r1/bgpd.conf b/tests/topotests/bgp_prefix_sid2/r1/bgpd.conf new file mode 100644 index 0000000..b3ca0e1 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/r1/bgpd.conf @@ -0,0 +1,27 @@ +log stdout notifications +!log commands +! +!debug bgp zebra +!debug bgp neighbor-events +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 1 + bgp router-id 10.0.0.1 + no bgp default ipv4-unicast + no bgp ebgp-requires-policy + neighbor 10.0.0.101 remote-as 2 + neighbor 10.0.0.101 timers 3 10 + ! + address-family ipv6 vpn + neighbor 10.0.0.101 activate + neighbor 10.0.0.101 route-map DENY_ALL out + exit-address-family +! +route-map DENY_ALL deny 10 diff --git a/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry1.json b/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry1.json new file mode 100644 index 0000000..65c51f1 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry1.json @@ -0,0 +1,46 @@ +{ + "2:10":{ + "prefix":"2001:1::\/64", + "paths":[ + { + "aspath":{ + "string":"2", + "segments":[ + { + "type":"as-sequence", + "list":[ + 2 + ] + } + ], + "length":1 + }, + "origin":"IGP", + "valid":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"RT:2:10" + }, + "remoteLabel":3, + "remoteSid":"2001:db8:1:1::1", + "nexthops":[ + { + "ip":"2001::2", + "afi":"ipv6", + "scope":"global", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"10.0.0.101", + "routerId":"10.0.0.101", + "type":"external" + } + } + ] + } +} diff --git a/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry2.json b/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry2.json new file mode 100644 index 0000000..4a12c2a --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/r1/vpnv6_rib_entry2.json @@ -0,0 +1,46 @@ +{ + "2:10":{ + "prefix":"2001:2::\/64", + "paths":[ + { + "aspath":{ + "string":"2", + "segments":[ + { + "type":"as-sequence", + "list":[ + 2 + ] + } + ], + "length":1 + }, + "origin":"IGP", + "valid":true, + "bestpath":{ + "overall":true + }, + "extendedCommunity":{ + "string":"RT:2:10" + }, + "remoteLabel":3, + "remoteSid":"2001:db8:1:1::1", + "nexthops":[ + { + "ip":"2001::2", + "afi":"ipv6", + "scope":"global", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"10.0.0.101", + "routerId":"10.0.0.101", + "type":"external" + } + } + ] + } +} diff --git a/tests/topotests/bgp_prefix_sid2/r1/zebra.conf b/tests/topotests/bgp_prefix_sid2/r1/zebra.conf new file mode 100644 index 0000000..0cd2605 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/r1/zebra.conf @@ -0,0 +1,7 @@ +hostname r1 +! +interface r1-eth0 + ip address 10.0.0.1/24 + no shutdown +! +line vty diff --git a/tests/topotests/bgp_prefix_sid2/test_bgp_prefix_sid2.py b/tests/topotests/bgp_prefix_sid2/test_bgp_prefix_sid2.py new file mode 100755 index 0000000..d6b9432 --- /dev/null +++ b/tests/topotests/bgp_prefix_sid2/test_bgp_prefix_sid2.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_prefix_sid2.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by LINE Corporation +# Copyright (c) 2020 by Hiroki Shirokura +# + +""" +test_bgp_prefix_sid2.py: Test BGP topology with EBGP on prefix-sid +""" + +import json +import os +import sys +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + router = tgen.add_router("r1") + switch = tgen.add_switch("s1") + switch.add_link(router) + + switch = tgen.gears["s1"] + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.101", defaultRoute="via 10.0.0.1") + switch.add_link(peer1) + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format("r1")) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format("r1")) + ) + router.start() + + logger.info("starting exaBGP") + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + logger.info("starting exaBGP on {}".format(pname)) + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, pname, "exabgp.env") + logger.info("Running ExaBGP peer on {}".format(pname)) + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def test_r1_rib(): + def _check(name, cmd, expected_file): + logger.info("polling") + tgen = get_topogen() + router = tgen.gears[name] + output = json.loads(router.vtysh_cmd(cmd)) + expected = open_json_file("{}/{}".format(CWD, expected_file)) + return topotest.json_cmp(output, expected) + + def check(name, cmd, expected_file): + logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file)) + tgen = get_topogen() + func = functools.partial(_check, name, cmd, expected_file) + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "Failed" + + check("r1", "show bgp ipv6 vpn 2001:1::/64 json", "r1/vpnv6_rib_entry1.json") + check("r1", "show bgp ipv6 vpn 2001:2::/64 json", "r1/vpnv6_rib_entry2.json") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + ret = pytest.main(args) + sys.exit(ret) diff --git a/tests/topotests/bgp_recursive_route_ebgp_multi_hop/bgp_recursive_route_ebgp_multi_hop.json b/tests/topotests/bgp_recursive_route_ebgp_multi_hop/bgp_recursive_route_ebgp_multi_hop.json new file mode 100644 index 0000000..52995a0 --- /dev/null +++ b/tests/topotests/bgp_recursive_route_ebgp_multi_hop/bgp_recursive_route_ebgp_multi_hop.json @@ -0,0 +1,321 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r3": { + "dest_link": { + "r4": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + } + } +} diff --git a/tests/topotests/bgp_recursive_route_ebgp_multi_hop/test_bgp_recursive_route_ebgp_multi_hop.py b/tests/topotests/bgp_recursive_route_ebgp_multi_hop/test_bgp_recursive_route_ebgp_multi_hop.py new file mode 100644 index 0000000..88251fa --- /dev/null +++ b/tests/topotests/bgp_recursive_route_ebgp_multi_hop/test_bgp_recursive_route_ebgp_multi_hop.py @@ -0,0 +1,2407 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test bgp recursive route and ebgp +multi-hop functionality: + +1. Verify that BGP routes are installed in iBGP peer, only when there + is a recursive route for next-hop reachability. +2. Verify that any BGP prefix received with next hop as self-ip is + not installed in BGP RIB or FIB table. +3. Verify password authentication for eBGP and iBGP peers. +4. Verify that for a BGP prefix next-hop information doesn't change + when same prefix is received from another peer via recursive lookup. +5. Verify that BGP path attributes are present in CLI outputs and + JSON format, even if set to default. +6. Verifying the BGP peering between loopback and physical link's IP + of 2 peer routers. +7. Verify that BGP Active/Standby/Pre-emption/ECMP. +""" + +import os +import sys +import time +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + apply_raw_config, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + check_address_types, + step, + create_route_maps, + create_interface_in_kernel, + shutdown_bringup_interface, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_convergence_from_running_config, + modify_as_number, + verify_bgp_attributes, + clear_bgp, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Global variables +BGP_CONVERGENCE = False +KEEP_ALIVE_TIMER = 2 +HOLD_DOWN_TIMER = 6 +ADDR_TYPES = check_address_types() +NETWORK = { + "ipv4": ["100.1.1.1/32", "100.1.1.2/32"], + "ipv6": ["100::1/128", "100::2/128"], +} + +RECUR_NEXT_HOP = { + "N1": {"ipv4": "20.20.20.20/24", "ipv6": "20:20::20:20/120"}, + "N2": {"ipv4": "30.30.30.30/24", "ipv6": "30:30::30:30/120"}, + "N3": {"ipv4": "40.40.40.40/24", "ipv6": "40:40::40:40/120"}, +} + +CHANGED_NEXT_HOP = { + "4thOctate": {"ipv4": "10.0.1.250/24", "ipv6": "fd00:0:0:1::100/64"}, + "3rdOctate": {"ipv4": "10.0.10.2/24", "ipv6": "fd00:0:0:10::2/64"}, +} + +Loopabck_IP = { + "Lo_R1": {"ipv4": "1.1.1.1/32", "ipv6": "1:1::1:1/128"}, + "Lo_R4": {"ipv4": "4.4.4.4/32", "ipv6": "4:4::4:4/128"}, +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_recursive_route_ebgp_multi_hop.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error : {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_recursive_routes_iBGP_peer_p1(request): + """ + Verify that BGP routes are installed in iBGP peer, only + when there is a recursive route for next-hop reachability. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + dut = "r1" + protocol = "static" + + step( + "Configure static routes on R1 pointing next-hop as connected" + "link between R1 & R3's IP" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r3"]["links"]["r1"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = create_static_routes(tgen, input_dict_4) + + step( + "Verify on router R1 that these static routes are " + "installed in RIB+FIB of R1" + ) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Redistribute these static routes in BGP on router R1") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + + step( + "Verify on router R1 that these static routes are installed" + "in RIB table as well" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r3"]["links"]["r1"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Configure a static routes for next hop IP on R2 via multiple" + "recursive static routes" + ) + dut = "r2" + create_interface_in_kernel( + tgen, dut, "lo10", "40.40.40.50", netmask="255.255.255.0", create=True + ) + create_interface_in_kernel( + tgen, dut, "lo10", "40:40::40:50", netmask="120", create=True + ) + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r2": { + "static_routes": [ + { + "network": topo["routers"]["r3"]["links"]["r1"][addr_type], + "next_hop": RECUR_NEXT_HOP["N1"][addr_type].split("/")[0], + }, + { + "network": RECUR_NEXT_HOP["N1"][addr_type], + "next_hop": RECUR_NEXT_HOP["N2"][addr_type].split("/")[0], + }, + { + "network": RECUR_NEXT_HOP["N2"][addr_type], + "next_hop": RECUR_NEXT_HOP["N3"][addr_type].split("/")[0], + }, + ] + } + } + result = create_static_routes(tgen, input_dict_3) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("verify if redistributed routes are now installed in FIB of R2") + result = verify_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0], + protocol="bgp", + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("Delete 1 route from static recursive for the next-hop IP") + dut = "r2" + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r2": { + "static_routes": [ + { + "network": RECUR_NEXT_HOP["N1"][addr_type], + "next_hop": RECUR_NEXT_HOP["N2"][addr_type].split("/")[0], + "delete": True, + } + ] + } + } + result = create_static_routes(tgen, input_dict_3) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("Verify that redistributed routes are withdrawn from FIB of R2") + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0], + protocol="bgp", + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + step("Reconfigure the same static route on R2 again") + dut = "r2" + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r2": { + "static_routes": [ + { + "network": RECUR_NEXT_HOP["N1"][addr_type], + "next_hop": RECUR_NEXT_HOP["N2"][addr_type].split("/")[0], + } + ] + } + } + result = create_static_routes(tgen, input_dict_3) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("Verify that redistributed routes are again installed" "in FIB of R2") + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0], + protocol="bgp", + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("Configure static route with changed next-hop from same subnet") + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r3"]["links"]["r1"][ + addr_type + ].split("/")[0], + "delete": True, + }, + { + "network": NETWORK[addr_type], + "next_hop": CHANGED_NEXT_HOP["4thOctate"][addr_type].split("/")[ + 0 + ], + }, + ] + } + } + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_dict_4, protocol="static") + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Verify that redistributed routes are not withdrawn as changed" + "next-hop IP, belongs to the same subnet" + ) + result = verify_rib(tgen, addr_type, "r2", input_dict_4, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Configure static route with changed next-hop from different subnet") + dut = "r1" + create_interface_in_kernel( + tgen, dut, "lo10", "10.0.10.10", netmask="255.255.255.0", create=True + ) + create_interface_in_kernel( + tgen, dut, "lo10", "fd00:0:0:10::104", netmask="64", create=True + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": CHANGED_NEXT_HOP["4thOctate"][addr_type].split("/")[ + 0 + ], + "delete": True, + }, + { + "network": NETWORK[addr_type], + "next_hop": CHANGED_NEXT_HOP["3rdOctate"][addr_type].split("/")[ + 0 + ], + }, + ] + } + } + result = create_static_routes(tgen, input_dict_4) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_dict_4, protocol="static") + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Verify that redistributed routes are withdrawn as changed " + "next-hop IP, belongs to different subnet" + ) + result = verify_rib( + tgen, addr_type, "r2", input_dict_4, protocol="bgp", expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_next_hop_as_self_ip_p1(request): + """ + Verify that any BGP prefix received with next hop as + self-ip is not installed in BGP RIB or FIB table. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step( + "Configure static routes on R1 with a next-hop IP belonging" + "to the same subnet of R2's link IP." + ) + dut = "r1" + create_interface_in_kernel( + tgen, + dut, + "lo10", + topo["routers"]["r4"]["links"]["r2"]["ipv4"].split("/")[0], + netmask="255.255.255.0", + create=True, + ) + create_interface_in_kernel( + tgen, + dut, + "lo10", + topo["routers"]["r4"]["links"]["r2"]["ipv6"].split("/")[0], + netmask="64", + create=True, + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r4"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = create_static_routes(tgen, input_dict_4) + + step("Verify that static routes are installed in RIB and FIB of R1") + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_4, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + protocol="static", + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Redistribute static routes into BGP on R1") + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + + step( + "Verify that R2 denies the prefixes received in update message," + "as next-hop IP belongs to connected interface" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r4"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + step("Shut interface on R2 that has IP from the subnet as BGP next-hop") + intf_r2_r4 = topo["routers"]["r2"]["links"]["r4"]["interface"] + shutdown_bringup_interface(tgen, "r2", intf_r2_r4) + + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r2") + step( + "Verify that redistributed routes now appear only in BGP table," + "as next-hop IP is no more active on R2" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r4"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("No shutdown interface on R2 which was shut in previous step") + intf_r2_r4 = topo["routers"]["r2"]["links"]["r4"]["interface"] + shutdown_bringup_interface(tgen, "r2", intf_r2_r4, ifaceaction=True) + + step( + "Verify that R2 dosn't install prefixes RIB to FIB as next-hop" + "interface is up now" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": NETWORK[addr_type], + "next_hop": topo["routers"]["r2"]["links"]["r4"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = verify_bgp_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + result = verify_rib( + tgen, + addr_type, + "r2", + input_dict_4, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_next_hop_with_recursive_lookup_p1(request): + """ + Verify that for a BGP prefix next-hop information doesn't change + when same prefix is received from another peer via recursive lookup. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step("Verify that BGP peering comes up.") + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Do redistribute connected on router R3.") + input_dict_1 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify that R1 receives all connected") + for addr_type in ADDR_TYPES: + routes = { + "ipv4": ["1.0.3.17/32", "10.0.1.0/24", "10.0.3.0/24"], + "ipv6": ["2001:db8:f::3:17/128", "fd00:0:0:1::/64", "fd00:0:0:3::/64"], + } + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + result = verify_rib(tgen, addr_type, "r1", input_dict, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Configure a BGP neighborship between R1 and R4, directly via " + "eBGP multi-hop." + ) + r1_local_as = topo["routers"]["r1"]["bgp"]["local_as"] + r1_r3_addr = topo["routers"]["r1"]["links"]["r3"] + r4_local_as = topo["routers"]["r4"]["bgp"]["local_as"] + r4_r3_addr = topo["routers"]["r4"]["links"]["r3"] + ebgp_multi_hop = 3 + + for addr_type in ADDR_TYPES: + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(r1_local_as), + "neighbor {} remote-as {}".format( + r4_r3_addr[addr_type].split("/")[0], r4_local_as + ), + "neighbor {} timers {} {}".format( + r4_r3_addr[addr_type].split("/")[0], + KEEP_ALIVE_TIMER, + HOLD_DOWN_TIMER, + ), + "neighbor {} ebgp-multihop {}".format( + r4_r3_addr[addr_type].split("/")[0], ebgp_multi_hop + ), + ] + }, + "r4": { + "raw_config": [ + "router bgp {}".format(r4_local_as), + "neighbor {} remote-as {}".format( + r1_r3_addr[addr_type].split("/")[0], r1_local_as + ), + "neighbor {} timers {} {}".format( + r1_r3_addr[addr_type].split("/")[0], + KEEP_ALIVE_TIMER, + HOLD_DOWN_TIMER, + ), + "neighbor {} ebgp-multihop {}".format( + r1_r3_addr[addr_type].split("/")[0], ebgp_multi_hop + ), + ] + }, + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + if addr_type == "ipv4": + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(r1_local_as), + "address-family {} unicast".format(addr_type), + "no neighbor {} activate".format( + r4_r3_addr["ipv6"].split("/")[0] + ), + ] + }, + "r4": { + "raw_config": [ + "router bgp {}".format(r4_local_as), + "address-family {} unicast".format(addr_type), + "no neighbor {} activate".format( + r1_r3_addr["ipv6"].split("/")[0] + ), + ] + }, + } + else: + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(r1_local_as), + "address-family {} unicast".format(addr_type), + "neighbor {} activate".format( + r4_r3_addr[addr_type].split("/")[0] + ), + ] + }, + "r4": { + "raw_config": [ + "router bgp {}".format(r4_local_as), + "address-family {} unicast".format(addr_type), + "neighbor {} activate".format( + r1_r3_addr[addr_type].split("/")[0] + ), + ] + }, + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that BGP session between R1 and R4 comes up" "(recursively via R3).") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Do redistribute connected on router R4.") + input_dict_1 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify that R1 now receives BGP prefix of link r3-r4 via 2 " + "next-hops R3 and R4. however do not install with NHT R4 in FIB." + ) + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Clear bgp sessions from R1 using 'clear ip bgp *'") + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1") + + step( + "Verify that prefix of link r3-r4 is again learned via 2 " + "next-hops (from R3 and R4 directly)" + ) + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Remove redistribution from router R3.") + input_dict_1 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify that peering between R1-R4 goes down and prefix " + "of link r3-r4, with NHT R4 is withdrawn." + ) + + logger.info("Sleeping for holddowntimer: {}".format(HOLD_DOWN_TIMER)) + sleep(HOLD_DOWN_TIMER + 1) + + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n" "BGP is converged \n Error : {}".format( + tc_name, result + ) + logger.info("Expected behaviour: {}".format(result)) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Re-apply redistribution on R3.") + + input_dict_1 = { + "r3": { + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "connected"}]} + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify that peering between R1-R4 goes down and prefix " + "of link r3-r4 with NHT R4 is withdrawn." + ) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Remove redistribution from router R4.") + + input_dict_1 = { + "r4": { + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify that peering between R1-R4 doesn't go down but prefix " + "of link r3-r4 with NHT R4 is withdrawn." + ) + + logger.info("Sleeping for holddowntimer: {}".format(HOLD_DOWN_TIMER)) + sleep(HOLD_DOWN_TIMER + 1) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r4"]["links"]["r3"][addr_type].split("/")[0] + + result = verify_rib( + tgen, + addr_type, + "r1", + input_dict, + protocol="bgp", + next_hop=next_hop, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Route is still present \n Error : {}".format( + tc_name, result + ) + + step("Re-apply redistribution on R4.") + + input_dict_1 = { + "r4": { + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "connected", "delete": True} + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify that prefix of link r3-r4 is re-learned via NHT R4.") + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Toggle the interface on R3.") + + intf_r3_r4 = topo["routers"]["r3"]["links"]["r4"]["interface"] + shutdown_bringup_interface(tgen, "r3", intf_r3_r4) + + step( + "Verify that peering between R1-R4 goes down and comes up when " + "interface is toggled. Also prefix of link r3-r4(via both NHTs) is" + " withdrawn and re-learned accordingly." + ) + + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n" "BGP is converged \n Error : {}".format( + tc_name, result + ) + logger.info("Expected behaviour: {}".format(result)) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r4"]["links"]["r3"][addr_type].split("/")[0] + + result = verify_rib( + tgen, + addr_type, + "r1", + input_dict, + protocol="bgp", + next_hop=next_hop, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Route is still present \n Error : {}".format( + tc_name, result + ) + + shutdown_bringup_interface(tgen, "r3", intf_r3_r4, True) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Toggle the interface on R4.") + + intf_r4_r3 = topo["routers"]["r4"]["links"]["r3"]["interface"] + shutdown_bringup_interface(tgen, "r4", intf_r4_r3) + + step( + "Verify that peering between R1-R4 goes down and comes up when" + "interface is toggled. Also prefix of link r3-r4(via R4)" + " is withdrawn and re-learned accordingly." + ) + + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n" "BGP is converged \n Error : {}".format( + tc_name, result + ) + logger.info("Expected behaviour: {}".format(result)) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r4"]["links"]["r3"][addr_type].split("/")[0] + + result = verify_rib( + tgen, + addr_type, + "r1", + input_dict, + protocol="bgp", + next_hop=next_hop, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Route is still present \n Error : {}".format( + tc_name, result + ) + + shutdown_bringup_interface(tgen, "r4", intf_r4_r3, True) + + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + routes = {"ipv4": ["10.0.3.0/24"], "ipv6": ["fd00:0:0:3::/64"]} + + input_dict = {"r1": {"static_routes": [{"network": routes[addr_type]}]}} + next_hop = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + + result = verify_rib( + tgen, addr_type, "r1", input_dict, protocol="bgp", next_hop=next_hop + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_path_attributes_default_values_p1(request): + """ + Verify that BGP path attributes are present in CLI + outputs and JSON format, even if set to default. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config: Configure BGP neighborship, between R1-R2 & R1-R3") + reset_config_on_routers(tgen) + + step("Advertise a set of prefixes from R1 to both peers R2 and R3") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "null0"}] + } + } + result = create_static_routes(tgen, input_dict_1) + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_2) + + step( + "Verify that advertised prefixes are received on R4 and well" + "known attributes are present in the CLI and JSON outputs with" + "default values without any route-map config." + ) + for addr_type in ADDR_TYPES: + input_dict_3 = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib( + tgen, + addr_type, + "r4", + input_dict_3, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r4": { + "route_maps": { + "rmap_pf": [{"set": {"origin": "incomplete", "aspath": "300 100"}}] + } + } + } + + result = verify_bgp_attributes( + tgen, + addr_type, + "r4", + NETWORK[addr_type], + rmap_name="rmap_pf", + input_dict=input_dict_4, + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Configure a route-map to set below attribute value as 500" + "and apply on R4 in an inbound direction" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r4": { + "route_maps": { + "Path_Attribue": [ + { + "action": "permit", + "set": { + "path": {"as_num": 500, "as_action": "prepend"}, + "locPrf": 500, + "origin": "egp", + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + input_dict_5 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify that once the route-map is applied all the attributes" + "part of route-map, changes value to 500" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r4": { + "route_maps": { + "rmap_pf": [ + { + "set": { + "locPrf": 500, + "aspath": "500 300 100", + "origin": "EGP", + } + } + ] + } + } + } + result = verify_bgp_attributes( + tgen, + addr_type, + "r4", + NETWORK[addr_type], + rmap_name="rmap_pf", + input_dict=input_dict_4, + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step("Remove the route-map from R4") + input_dict_5 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step( + "Verify on R4 that well known attributes are present in the CLI &" + "JSON outputs again with default values without route-map config" + ) + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r4": { + "route_maps": { + "rmap_pf": [{"set": {"aspath": "300 100", "origin": "incomplete"}}] + } + } + } + result = verify_bgp_attributes( + tgen, + addr_type, + "r4", + NETWORK[addr_type], + rmap_name="rmap_pf", + input_dict=input_dict_4, + nexthop=None, + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_BGP_peering_bw_loopback_and_physical_p1(request): + """ + Verifying the BGP peering between loopback and + physical link's IP of 2 peer routers. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step("Configure a loopback interface on R1") + dut = "r1" + create_interface_in_kernel( + tgen, dut, "lo10", "1.1.1.1", netmask="255.255.255.255", create=True + ) + create_interface_in_kernel( + tgen, dut, "lo10", "1:1::1:1", netmask="128", create=True + ) + + step("Configure BGP session between R1's loopbak & R3") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": Loopabck_IP["Lo_R1"][addr_type], + "next_hop": topo["routers"]["r1"]["links"]["r3"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + result = verify_rib( + tgen, + addr_type, + "r3", + input_dict_1, + protocol="static", + next_hop=topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r1"]["bgp"]["local_as"]), + "address-family {} unicast".format(addr_type), + "neighbor {} update-source lo10".format( + topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + ), + "neighbor {} timers 1 3".format( + topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + ), + ] + }, + "r3": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r3"]["bgp"]["local_as"]), + "address-family {} unicast".format(addr_type), + "no neighbor {} remote-as {}".format( + topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + topo["routers"]["r1"]["bgp"]["local_as"], + ), + "neighbor {} remote-as {}".format( + Loopabck_IP["Lo_R1"][addr_type].split("/")[0], + topo["routers"]["r1"]["bgp"]["local_as"], + ), + "neighbor {} ebgp-multihop 3".format( + Loopabck_IP["Lo_R1"][addr_type].split("/")[0] + ), + ] + }, + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + if addr_type == "ipv6": + raw_config = { + "r3": { + "raw_config": [ + "router bgp {}".format( + topo["routers"]["r3"]["bgp"]["local_as"] + ), + "address-family {} unicast".format(addr_type), + "neighbor {} activate".format( + Loopabck_IP["Lo_R1"][addr_type].split("/")[0] + ), + ] + } + } + else: + raw_config = { + "r3": { + "raw_config": [ + "router bgp {}".format( + topo["routers"]["r3"]["bgp"]["local_as"] + ), + "address-family {} unicast".format(addr_type), + "no neighbor {} activate".format( + Loopabck_IP["Lo_R1"]["ipv6"].split("/")[0] + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that BGP neighborship between R1 and R3 comes up") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove ebgp-multihop command from R3") + for addr_type in ADDR_TYPES: + raw_config = { + "r3": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r3"]["bgp"]["local_as"]), + "no neighbor {} ebgp-multihop 3".format( + Loopabck_IP["Lo_R1"][addr_type].split("/")[0] + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that once eBGP multi-hop is removed, BGP session goes down") + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("Add ebgp-multihop command on R3 again") + for addr_type in ADDR_TYPES: + raw_config = { + "r3": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r3"]["bgp"]["local_as"]), + "neighbor {} ebgp-multihop 3".format( + Loopabck_IP["Lo_R1"][addr_type].split("/")[0] + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that BGP neighborship between R1 and R3 comes up") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove update-source command from R1") + for addr_type in ADDR_TYPES: + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r1"]["bgp"]["local_as"]), + "no neighbor {} update-source lo10".format( + topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that BGP session goes down, when update-source is removed") + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("Add update-source command on R1 again") + for addr_type in ADDR_TYPES: + raw_config = { + "r1": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r1"]["bgp"]["local_as"]), + "neighbor {} update-source lo10".format( + topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error : {}".format(tc_name, result) + + step("Verify that BGP neighborship between R1 and R3 comes up") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove static route from R3") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": Loopabck_IP["Lo_R1"][addr_type], + "next_hop": topo["routers"]["r1"]["links"]["r3"][ + addr_type + ].split("/")[0], + "delete": True, + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + result = verify_rib( + tgen, + addr_type, + "r3", + input_dict_1, + protocol="static", + next_hop=topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + sleep(3) + step("Verify that BGP session goes down, when static route is removed") + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("Add static route on R3 again") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": Loopabck_IP["Lo_R1"][addr_type], + "next_hop": topo["routers"]["r1"]["links"]["r3"][ + addr_type + ].split("/")[0], + } + ] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + result = verify_rib( + tgen, + addr_type, + "r3", + input_dict_1, + protocol="static", + next_hop=topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0], + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Verify that BGP neighborship between R1 and R3 comes up") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Toggle physical interface on R1") + intf_r1_r3 = topo["routers"]["r1"]["links"]["r3"]["interface"] + shutdown_bringup_interface(tgen, "r1", intf_r1_r3) + sleep(3) + step("Verify that BGP neighborship between R1 and R3 goes down") + result = verify_bgp_convergence_from_running_config(tgen, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + intf_r1_r3 = topo["routers"]["r1"]["links"]["r3"]["interface"] + shutdown_bringup_interface(tgen, "r1", intf_r1_r3, True) + + step("Verify that BGP neighborship between R1 and R3 comes up") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_BGP_active_standby_preemption_and_ecmp_p1(request): + """ + Verify that BGP Active/Standby/Pre-emption/ECMP. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step("Change the AS number on R2 as 200") + input_dict = {"r2": {"bgp": {"local_as": 200}}} + result = modify_as_number(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify BGP converge after changing the AS number on R2") + result = verify_bgp_convergence_from_running_config(tgen) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Advertise a set of prefixes from R1 to both peers R2 & R3") + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [{"network": NETWORK[addr_type], "next_hop": "null0"}] + } + } + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + input_dict_2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + "ipv6": {"unicast": {"redistribute": [{"redist_type": "static"}]}}, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify that R4 receives BGP prefixes via both peer routers R2 & R3") + for addr_type in ADDR_TYPES: + input_dict_3 = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_bgp_rib( + tgen, + addr_type, + "r4", + input_dict_3, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + ) + assert result is True, "Testcase {}: Failed \n Error : {}".format( + tc_name, result + ) + + step( + "Configure a route-map to set as-path attribute and" + "apply on R3 in an inbound direction:" + ) + + input_dict_4 = { + "r3": { + "route_maps": { + "Path_Attribue": [ + { + "action": "permit", + "set": {"path": {"as_num": 123, "as_action": "prepend"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + input_dict_5 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify on R4, BGP routes with shorter as-path are installed in FIB") + for addr_type in ADDR_TYPES: + dut = "r4" + protocol = "bgp" + input_dict_6 = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_6, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Shutdown BGP neighorship between R1-R2") + dut = "r4" + intf_r4_r2 = topo["routers"]["r4"]["links"]["r2"]["interface"] + shutdown_bringup_interface(tgen, dut, intf_r4_r2) + + step( + "Verify that prefixes from next-hop via R2 are withdrawn" + "and installed via next-hop as R3" + ) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_2, + next_hop=topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Do a no shut for BGP neighorship between R2-R4") + shutdown_bringup_interface(tgen, dut, intf_r4_r2, ifaceaction=True) + + step( + "Verify that prefixes from next-hop via R3 are withdrawn" + "from R4 and installed via next-hop as R2 (preemption)" + ) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict_2, + next_hop=topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove the route-map from R3's neighbor statement") + input_dict_5 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "Path_Attribue", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Configure multipath-relax and maximum-paths 2 on R4 for ECMP") + input_dict_8 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": {"unicast": {"maximum_paths": {"ebgp": 2}}}, + "ipv6": {"unicast": {"maximum_paths": {"ebgp": 2}}}, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_8) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + maxpath_relax = { + "r4": {"bgp": {"local_as": "400", "bestpath": {"aspath": "multipath-relax"}}} + } + + result = create_router_bgp(tgen, topo, maxpath_relax) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify FIB of R4, BGP prefixes with ECMP next-hop via R2 and R3") + for addr_type in ADDR_TYPES: + input_dict = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_rib( + tgen, + addr_type, + "r4", + input_dict, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Remove multipath-relax command from R4") + + del_maxpath_relax = { + "r4": { + "bgp": { + "local_as": "400", + "bestpath": {"aspath": "multipath-relax", "delete": True}, + } + } + } + + result = create_router_bgp(tgen, topo, del_maxpath_relax) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify that ECMP is no longer happening on R4.") + for addr_type in ADDR_TYPES: + input_dict = {"r4": {"static_routes": [{"network": NETWORK[addr_type]}]}} + result = verify_rib( + tgen, + addr_type, + "r4", + input_dict, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + step("Reconfigure multipath-relax command on R4") + result = create_router_bgp(tgen, topo, maxpath_relax) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify FIB of R4, BGP prefixes with ECMP next-hop via R2 and R3") + result = verify_rib( + tgen, + addr_type, + "r4", + input_dict, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove maximum-path 2 command from R4") + input_dict_8 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": 1, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": 1, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_8) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify that ECMP is no longer happening on R4") + result = verify_rib( + tgen, + addr_type, + "r4", + input_dict, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + step("Re-configure maximum-path 2 command on R4") + input_dict_8 = { + "r4": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ebgp": 2, + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ebgp": 2, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_8) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Verify FIB of R4, BGP prefixes with ECMP next-hop via R2 and R3") + result = verify_rib( + tgen, + addr_type, + "r4", + input_dict, + next_hop=[ + topo["routers"]["r2"]["links"]["r4"][addr_type].split("/")[0], + topo["routers"]["r3"]["links"]["r4"][addr_type].split("/")[0], + ], + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_password_authentication_for_eBGP_and_iBGP_peers_p1(request): + """ + Verify password authentication for eBGP and iBGP peers. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Initial config :Configure BGP neighborship between R1 and R3.") + reset_config_on_routers(tgen) + + step( + "Add a static route on R1 for loopbacks IP's reachability of R2, R3" + "and on R2 and R3 for loopback IP of R1" + ) + for addr_type in ADDR_TYPES: + nh1 = topo["routers"]["r3"]["links"]["r1"][addr_type].split("/")[0] + nh2 = topo["routers"]["r1"]["links"]["r2"][addr_type].split("/")[0] + nh3 = topo["routers"]["r1"]["links"]["r3"][addr_type].split("/")[0] + nh4 = topo["routers"]["r2"]["links"]["r1"][addr_type].split("/")[0] + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": topo["routers"]["r3"]["links"]["lo"][addr_type], + "next_hop": nh1, + } + ] + } + } + input_dict_2 = { + "r2": { + "static_routes": [ + { + "network": topo["routers"]["r1"]["links"]["lo"][addr_type], + "next_hop": nh2, + } + ] + } + } + input_dict_3 = { + "r3": { + "static_routes": [ + { + "network": topo["routers"]["r1"]["links"]["lo"][addr_type], + "next_hop": nh3, + } + ] + } + } + input_dict_4 = { + "r1": { + "static_routes": [ + { + "network": topo["routers"]["r2"]["links"]["lo"][addr_type], + "next_hop": nh4, + } + ] + } + } + dut_list = ["r1", "r2", "r3", "r1"] + nexthop_list = [nh1, nh2, nh3, nh4] + input_dict_list = [input_dict_1, input_dict_2, input_dict_3, input_dict_4] + for dut, next_hop, input_dict in zip(dut_list, nexthop_list, input_dict_list): + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Verify that static routes are installed in FIB of routers") + result = verify_rib( + tgen, addr_type, dut, input_dict, next_hop=next_hop, protocol="static" + ) + assert result is True, "Testcase {} : Failed \n Error : {}".format( + tc_name, result + ) + + step("Configure BGP sessions between R1-R2 and R1-R3 over loopback IPs") + for routerN in ["r1", "r3"]: + for addr_type in ADDR_TYPES: + if routerN == "r1": + bgp_neighbor = "r3" + elif routerN == "r3": + bgp_neighbor = "r1" + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = { + "lo": {"ebgp_multihop": 2, "source_link": "lo"} + } + build_config_from_json(tgen, topo, save_bkup=False) + + for routerN in ["r1", "r2"]: + for addr_type in ADDR_TYPES: + if routerN == "r1": + bgp_neighbor = "r2" + elif routerN == "r2": + bgp_neighbor = "r1" + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = {"lo": {"source_link": "lo"}} + build_config_from_json(tgen, topo, save_bkup=False) + + for routerN in ["r1", "r2", "r3"]: + for addr_type in ADDR_TYPES: + for bgp_neighbor in topo["routers"][routerN]["bgp"]["address_family"][ + addr_type + ]["unicast"]["neighbor"].keys(): + if routerN in ["r1", "r2", "r3"] and bgp_neighbor == "r4": + continue + if addr_type == "ipv4": + topo["routers"][routerN]["bgp"]["address_family"][addr_type][ + "unicast" + ]["neighbor"][bgp_neighbor]["dest_link"] = { + "lo": {"deactivate": "ipv6"} + } + elif addr_type == "ipv6": + topo["routers"][routerN]["bgp"]["address_family"][addr_type][ + "unicast" + ]["neighbor"][bgp_neighbor]["dest_link"] = { + "lo": {"deactivate": "ipv4"} + } + build_config_from_json(tgen, topo, save_bkup=False) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Configure authentication password on R1 for neighbor statements") + for bgp_neighbor in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"]["r1"]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = {"lo": {"password": "vmware"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step( + "Verify that both sessions go down as only R1 has password" + "configured but not peer routers" + ) + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("configure same password on R2 and R3") + for routerN in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ]["r1"]["dest_link"] = {"lo": {"password": "vmware"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step("Verify that all BGP sessions come up due to identical passwords") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Configure same password on R2 and R3, but in CAPs.") + for routerN in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ]["r1"]["dest_link"] = {"lo": {"password": "VMWARE"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step( + "Verify that BGP sessions do not come up as password" + "strings are in CAPs on R2 and R3" + ) + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("Configure same password on R2 and R3 without CAPs") + for routerN in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ]["r1"]["dest_link"] = {"lo": {"password": "vmware"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step("Verify all BGP sessions come up again due to identical passwords") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + step("Remove password from R1") + for bgp_neighbor in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"]["r1"]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = {"lo": {"no_password": "vmware"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step("Verify if password is removed from R1, both sessions go down again") + result = verify_bgp_convergence(tgen, topo, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n " "BGP is converged \n Error: {}".format( + tc_name, result + ) + + step("Configure alphanumeric password on R1 and peer routers R2,R3") + for bgp_neighbor in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"]["r1"]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ][bgp_neighbor]["dest_link"] = {"lo": {"password": "Vmware@123"}} + build_config_from_json(tgen, topo, save_bkup=False) + + for routerN in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + topo["routers"][routerN]["bgp"]["address_family"][addr_type]["unicast"][ + "neighbor" + ]["r1"]["dest_link"] = {"lo": {"password": "Vmware@123"}} + build_config_from_json(tgen, topo, save_bkup=False) + + step( + "Verify that sessions Come up irrespective of characters" + "used in password string" + ) + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error : {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_redistribute_table/__init__.py b/tests/topotests/bgp_redistribute_table/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_redistribute_table/r1/bgpd.conf b/tests/topotests/bgp_redistribute_table/r1/bgpd.conf new file mode 100644 index 0000000..c5e0fcd --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r1/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65501 + address-family ipv4 unicast + neighbor 192.168.0.2 activate + exit-address-family +! diff --git a/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_all_redistribute.json b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_all_redistribute.json new file mode 100644 index 0000000..e5a27f3 --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_all_redistribute.json @@ -0,0 +1,71 @@ +{ + "172.31.0.2/32": [ + { + "prefix": "172.31.0.2/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "172.31.0.10/32": [ + { + "prefix": "172.31.0.10/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "172.31.0.15/32": [ + { + "prefix": "172.31.0.15/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_redistribute.json b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_redistribute.json new file mode 100644 index 0000000..1304edd --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_with_redistribute.json @@ -0,0 +1,48 @@ +{ + "172.31.0.2/32": [ + { + "prefix": "172.31.0.2/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "172.31.0.10/32": [ + { + "prefix": "172.31.0.10/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_without_redistribute.json b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_without_redistribute.json new file mode 100644 index 0000000..74f594f --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r1/ipv4_routes_without_redistribute.json @@ -0,0 +1,25 @@ +{ + "172.31.0.2/32": [ + { + "prefix": "172.31.0.2/32", + "protocol": "bgp", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "active": true, + "weight": 1 + } + ] + } + ] +} diff --git a/tests/topotests/bgp_redistribute_table/r1/zebra.conf b/tests/topotests/bgp_redistribute_table/r1/zebra.conf new file mode 100644 index 0000000..abe6d39 --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r1/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r1-eth1 + ip address 172.31.0.1/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_redistribute_table/r2/bgpd.conf b/tests/topotests/bgp_redistribute_table/r2/bgpd.conf new file mode 100644 index 0000000..2ce7043 --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r2/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65501 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + network 172.31.0.2/32 + neighbor 192.168.0.1 activate + redistribute table-direct 2200 + exit-address-family +! diff --git a/tests/topotests/bgp_redistribute_table/r2/zebra.conf b/tests/topotests/bgp_redistribute_table/r2/zebra.conf new file mode 100644 index 0000000..89ad2ec --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/r2/zebra.conf @@ -0,0 +1,8 @@ +log stdout +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 172.31.0.2/32 + ip address 172.31.1.2/24 +! diff --git a/tests/topotests/bgp_redistribute_table/test_bgp_redistribute_table.py b/tests/topotests/bgp_redistribute_table/test_bgp_redistribute_table.py new file mode 100644 index 0000000..08b70ae --- /dev/null +++ b/tests/topotests/bgp_redistribute_table/test_bgp_redistribute_table.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_redistribute_table.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2023 by 6WIND +# + +""" + test_bgp_redistribute_table.py: Test the FRR BGP daemon with 'redistribute table-direct' +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.common_config import step +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def _router_json_cmp_exact_filter(router, cmd, expected): + output = router.vtysh_cmd(cmd) + logger.info("{}: {}\n{}".format(router.name, cmd, output)) + + json_output = json.loads(output) + + # filter out tableVersion, version, nhVrfId and vrfId + for route, attrs in json_output.items(): + for attr in attrs: + if "table" in attr: + attr.pop("table") + if "internalStatus" in attr: + attr.pop("internalStatus") + if "internalFlags" in attr: + attr.pop("internalFlags") + if "internalNextHopNum" in attr: + attr.pop("internalNextHopNum") + if "internalNextHopActiveNum" in attr: + attr.pop("internalNextHopActiveNum") + if "nexthopGroupId" in attr: + attr.pop("nexthopGroupId") + if "installedNexthopGroupId" in attr: + attr.pop("installedNexthopGroupId") + if "uptime" in attr: + attr.pop("uptime") + if "prefixLen" in attr: + attr.pop("prefixLen") + if "asPath" in attr: + attr.pop("asPath") + for nexthop in attr.get("nexthops", []): + if "flags" in nexthop: + nexthop.pop("flags") + if "interfaceIndex" in nexthop: + nexthop.pop("interfaceIndex") + + return topotest.json_cmp(json_output, expected, exact=True) + + +def _check_zebra_rib_r1(with_redistributed_route, with_second_route=False): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + with_str = "" if with_redistributed_route else "out" + + router = tgen.gears["r1"] + if with_redistributed_route: + with_str = "" + if with_second_route: + json_file = "{}/{}/ipv4_routes_with_all_redistribute.json".format( + CWD, router.name + ) + else: + json_file = "{}/{}/ipv4_routes_with_redistribute.json".format( + CWD, router.name + ) + else: + with_str = "out" + json_file = "{}/{}/ipv4_routes_without_redistribute.json".format( + CWD, router.name + ) + + step(f"Checking IPv4 routes for convergence on r1 with{with_str} kernel route") + expected = json.loads(open(json_file).read()) + test_func = partial( + _router_json_cmp_exact_filter, + router, + "show ip route bgp json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def _test_add_and_check_kernel_route_on_table_2200(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_list = tgen.routers() + + step("r2, adding new kernel route 172.31.0.10/32 on table 2200") + cmd = "ip route add 172.31.0.10/32 via 172.31.1.10 table 2200" + tgen.net["r2"].cmd(cmd) + + _check_zebra_rib_r1(True) + + +def test_step1_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Checking IPv4 routes for convergence on r1") + _check_zebra_rib_r1(False) + + +def test_step2_add_kernel_route_on_table_2200(): + """ + On r2, create a kernel route on table 2200 + * Check that the kernel route is redistributed to r1 + """ + _test_add_and_check_kernel_route_on_table_2200() + + +def test_step3_remove_kernel_route_on_table_2200(): + """ + On r2, remove a kernel route on table 2200 + * Check that the kernel route is no more redistributed to r1 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_list = tgen.routers() + + step("r2, remove a kernel route on table 2200") + cmd = "ip route delete 172.31.0.10/32 via 172.31.1.10 table 2200" + tgen.net["r2"].cmd(cmd) + + _check_zebra_rib_r1(False) + + +def test_step4_add_kernel_route_on_table_2200(): + """ + On r2, add a kernel route on table 2200 + * Check that the kernel route is redistributed to r1 + """ + _test_add_and_check_kernel_route_on_table_2200() + + +def test_step5_no_redistribute_table_2200(): + """ + On r2, unconfigure the 'no redistribute' service + * Check that the 'redistribute' command is not configured + * Check that the kernel route is not redistributed to r1 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + "configure terminal\nrouter bgp 65501\naddress-family ipv4 unicast\nno redistribute table-direct\n" + ) + + step("r2, check that the 'redistribute' command is not configured") + out = tgen.net["r2"].cmd( + "vtysh -c 'show running-config' | grep 'redistribute table-direct'" + ) + + if "redistribute" in out: + assert True, "r2, redistribute command still present" + + _check_zebra_rib_r1(False) + + +def test_step6_redistribute_table_2200(): + """ + On r2, configure the 'redistribute' service + * Check that the 'redistribute' command is configured + * Check that the kernel route is redistributed to r1 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + tgen.gears["r2"].vtysh_cmd( + "configure terminal\nrouter bgp 65501\naddress-family ipv4 unicast\nredistribute table-direct 2200\n" + ) + + step("r2, check that the 'redistribute' command is configured") + out = tgen.net["r2"].cmd( + "vtysh -c 'show running-config' | grep 'redistribute table-direct'" + ) + if "redistribute" not in out: + assert True, "r2, redistribute command still present" + + _check_zebra_rib_r1(True) + + +def test_step7_reset_bgp_instance_add_kernel_route_and_add_bgp(): + """ + On r2, remove BGP configuration, create a kernel route on table 2200, + then restore BGP configuration + * Check that the kernel route is redistributed to r1 + """ + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_list = tgen.routers() + + router = tgen.gears["r2"] + step("r2, removing r2 BGP configuration") + router.vtysh_cmd("configure terminal\nno router bgp 65501\n") + + step("r2, adding new kernel route 172.31.0.15/32 on table 2200") + cmd = "ip route add 172.31.0.15/32 via 172.31.1.100 table 2200" + tgen.net["r2"].cmd(cmd) + + router = tgen.gears["r2"] + step("r2, restoring r2 BGP configuration") + tgen.net["r2"].cmd("vtysh -f {}".format(os.path.join(CWD, "r2/bgpd.conf"))) + + _check_zebra_rib_r1(True, with_second_route=True) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_reject_as_sets/__init__.py b/tests/topotests/bgp_reject_as_sets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_reject_as_sets/r1/bgpd.conf b/tests/topotests/bgp_reject_as_sets/r1/bgpd.conf new file mode 100644 index 0000000..a28b612 --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r1/bgpd.conf @@ -0,0 +1,10 @@ +! exit1 +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_reject_as_sets/r1/zebra.conf b/tests/topotests/bgp_reject_as_sets/r1/zebra.conf new file mode 100644 index 0000000..9904bb4 --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r1/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_reject_as_sets/r2/bgpd.conf b/tests/topotests/bgp_reject_as_sets/r2/bgpd.conf new file mode 100644 index 0000000..4539617 --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r2/bgpd.conf @@ -0,0 +1,13 @@ +! spine +router bgp 65002 + bgp reject-as-sets + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.254.2 remote-as 65003 + neighbor 192.168.254.2 timers 3 10 + address-family ipv4 unicast + aggregate-address 172.16.0.0/16 as-set summary-only + exit-address-family + ! +! diff --git a/tests/topotests/bgp_reject_as_sets/r2/zebra.conf b/tests/topotests/bgp_reject_as_sets/r2/zebra.conf new file mode 100644 index 0000000..f0d357c --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r2/zebra.conf @@ -0,0 +1,9 @@ +! spine +interface r2-eth0 + ip address 192.168.255.1/30 +! +interface r2-eth1 + ip address 192.168.254.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_reject_as_sets/r3/bgpd.conf b/tests/topotests/bgp_reject_as_sets/r3/bgpd.conf new file mode 100644 index 0000000..1dde4f0 --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r3/bgpd.conf @@ -0,0 +1,11 @@ +! exit2 +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.254.1 remote-as 65002 + neighbor 192.168.254.1 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.254.1 allowas-in + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_reject_as_sets/r3/zebra.conf b/tests/topotests/bgp_reject_as_sets/r3/zebra.conf new file mode 100644 index 0000000..f490d97 --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/r3/zebra.conf @@ -0,0 +1,9 @@ +! exit2 +interface lo + ip address 172.16.254.254/32 +! +interface r3-eth0 + ip address 192.168.254.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_reject_as_sets/test_bgp_reject_as_sets.py b/tests/topotests/bgp_reject_as_sets/test_bgp_reject_as_sets.py new file mode 100644 index 0000000..97366eb --- /dev/null +++ b/tests/topotests/bgp_reject_as_sets/test_bgp_reject_as_sets.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_reject_as_sets.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Donatas Abraitis +# + +""" +Test if an aggregated route with AS_SET is not sent to peers. +Addressing draft-ietf-idr-deprecate-as-set-confed-set recommendations. + +BGP speakers conforming to this document (i.e., conformant BGP + speakers) MUST NOT locally generate BGP UPDATE messages containing + AS_SET or AS_CONFED_SET. Conformant BGP speakers SHOULD NOT send BGP + UPDATE messages containing AS_SET or AS_CONFED_SET. Upon receipt of + such messages, conformant BGP speakers SHOULD use the "Treat-as- + withdraw" error handling behavior as per [RFC7606]. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_reject_as_sets(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_has_aggregated_route_with_stripped_as_set(router): + output = json.loads(router.vtysh_cmd("show ip bgp 172.16.0.0/16 json")) + expected = { + "paths": [{"aspath": {"string": "Local", "segments": [], "length": 0}}] + } + return topotest.json_cmp(output, expected) + + def _bgp_announce_route_without_as_sets(router): + output = json.loads( + router.vtysh_cmd( + "show ip bgp neighbor 192.168.254.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "172.16.0.0/16": {"path": ""}, + "192.168.254.0/30": {"path": "65003"}, + "192.168.255.0/30": {"path": "65001"}, + }, + "totalPrefixCounter": 3, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Failed bgp convergence in "{}"'.format(router) + + test_func = functools.partial( + _bgp_has_aggregated_route_with_stripped_as_set, router + ) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert result is None, 'Failed to see an aggregated route in "{}"'.format(router) + + test_func = functools.partial(_bgp_announce_route_without_as_sets, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert ( + result is None + ), 'Route 172.16.0.0/16 should be sent without AS_SET to r3 "{}"'.format(router) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_remove_private_as/r1/bgpd.conf b/tests/topotests/bgp_remove_private_as/r1/bgpd.conf new file mode 100644 index 0000000..9953689 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r1/bgpd.conf @@ -0,0 +1,53 @@ +router bgp 65001 + bgp router-id 192.0.2.1 + no bgp network import-check + neighbor 203.0.113.1 remote-as 65002 + neighbor 203.0.113.1 description r2 + neighbor 203.0.113.1 timers 3 10 + neighbor 203.0.113.3 remote-as 5555 + neighbor 203.0.113.3 description r5 + neighbor 203.0.113.3 timers 3 10 +! + address-family ipv4 unicast + network 100.64.0.0/32 + network 100.64.0.1/32 + network 100.64.0.2/32 + network 100.64.0.3/32 + network 100.64.0.4/32 + network 100.64.0.5/32 + neighbor 203.0.113.1 route-map set-as-paths out + neighbor 203.0.113.3 route-map set-as-paths out + exit-address-family +! +ip prefix-list match-0 seq 5 permit 100.64.0.0/32 +ip prefix-list match-1 seq 5 permit 100.64.0.1/32 +ip prefix-list match-2 seq 5 permit 100.64.0.2/32 +ip prefix-list match-3 seq 5 permit 100.64.0.3/32 +ip prefix-list match-4 seq 5 permit 100.64.0.4/32 +! +! all private +! at r3/r4, as-path should only have r2's asn +route-map set-as-paths permit 10 + match ip address prefix-list match-0 + set as-path prepend 4200000000 4200000001 4200000002 +! +! all private, include r3's asn +! at r3/r4, as-path should only have r2's asn +route-map set-as-paths permit 20 + match ip address prefix-list match-1 + set as-path prepend 65003 4200000000 4200000001 4200000002 65003 +! +! mix of private/public +route-map set-as-paths permit 30 + match ip address prefix-list match-2 + set as-path prepend 4200000000 1000 4200000001 2000 4200000002 +! +! mix of private/public, include r3's asn multiple times +route-map set-as-paths permit 40 + match ip address prefix-list match-3 + set as-path prepend 65003 4200000000 1000 4200000001 2000 4200000002 65003 +! +! all public +route-map set-as-paths permit 50 + match ip address prefix-list match-4 + set as-path prepend 1000 2000 2000 3000 diff --git a/tests/topotests/bgp_remove_private_as/r1/zebra.conf b/tests/topotests/bgp_remove_private_as/r1/zebra.conf new file mode 100644 index 0000000..35c82d7 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r1/zebra.conf @@ -0,0 +1,10 @@ +! to r2 +interface r1-eth0 + ip address 203.0.113.0/31 +! +! to r5 +interface r1-eth1 + ip address 203.0.113.2/31 +! +ip forwarding +! diff --git a/tests/topotests/bgp_remove_private_as/r2/bgpd.conf b/tests/topotests/bgp_remove_private_as/r2/bgpd.conf new file mode 100644 index 0000000..9655046 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r2/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 65002 + bgp router-id 192.0.2.2 + neighbor 203.0.113.0 remote-as 65001 + neighbor 203.0.113.0 timers 3 10 + neighbor 203.0.113.0 description r1 + neighbor 203.0.113.4 remote-as 65003 + neighbor 203.0.113.4 solo + neighbor 203.0.113.4 timers 3 10 + neighbor 203.0.113.4 description r3 + neighbor 203.0.113.8 remote-as 4444 + neighbor 203.0.113.8 solo + neighbor 203.0.113.8 timers 3 10 + neighbor 203.0.113.8 description r4 +! + address-family ipv4 unicast + neighbor 203.0.113.0 route-map permit-all in + neighbor 203.0.113.4 route-map permit-all out + neighbor 203.0.113.8 route-map permit-all out + exit-address-family +! +route-map permit-all permit 10 +! diff --git a/tests/topotests/bgp_remove_private_as/r2/zebra.conf b/tests/topotests/bgp_remove_private_as/r2/zebra.conf new file mode 100644 index 0000000..0168614 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r2/zebra.conf @@ -0,0 +1,14 @@ +! to r1 +interface r2-eth0 + ip address 203.0.113.1/31 +! +! to r3 +interface r2-eth1 + ip address 203.0.113.5/31 +! +! to r4 +interface r2-eth2 + ip address 203.0.113.9/31 +! +ip forwarding +! diff --git a/tests/topotests/bgp_remove_private_as/r3/bgpd.conf b/tests/topotests/bgp_remove_private_as/r3/bgpd.conf new file mode 100644 index 0000000..0273d09 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r3/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 65003 + bgp router-id 192.0.2.3 + neighbor 203.0.113.5 remote-as 65002 + neighbor 203.0.113.5 timers 3 10 + neighbor 203.0.113.5 description r2 + neighbor 203.0.113.7 remote-as 5555 + neighbor 203.0.113.7 timers 3 10 + neighbor 203.0.113.7 description r5 +! + address-family ipv4 unicast + neighbor 203.0.113.5 route-map permit-all in + neighbor 203.0.113.5 allowas-in 10 + neighbor 203.0.113.5 soft-reconfiguration inbound + neighbor 203.0.113.7 route-map permit-all in + neighbor 203.0.113.7 allowas-in 10 + neighbor 203.0.113.7 soft-reconfiguration inbound + exit-address-family +! +route-map permit-all permit 10 diff --git a/tests/topotests/bgp_remove_private_as/r3/zebra.conf b/tests/topotests/bgp_remove_private_as/r3/zebra.conf new file mode 100644 index 0000000..e6a0ce3 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r3/zebra.conf @@ -0,0 +1,10 @@ +! to r2 +interface r3-eth0 + ip address 203.0.113.4/31 +! +! to r5 +interface r3-eth1 + ip address 203.0.113.6/31 +! +ip forwarding +! diff --git a/tests/topotests/bgp_remove_private_as/r4/bgpd.conf b/tests/topotests/bgp_remove_private_as/r4/bgpd.conf new file mode 100644 index 0000000..f480105 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r4/bgpd.conf @@ -0,0 +1,19 @@ +router bgp 4444 + bgp router-id 192.0.2.4 + neighbor 203.0.113.9 remote-as 65002 + neighbor 203.0.113.9 timers 3 10 + neighbor 203.0.113.9 description r2 + neighbor 203.0.113.11 remote-as 5555 + neighbor 203.0.113.11 timers 3 10 + neighbor 203.0.113.11 description r5 +! + address-family ipv4 unicast + neighbor 203.0.113.9 route-map permit-all in + neighbor 203.0.113.9 allowas-in 10 + neighbor 203.0.113.9 soft-reconfiguration inbound + neighbor 203.0.113.11 route-map permit-all in + neighbor 203.0.113.11 allowas-in 10 + neighbor 203.0.113.11 soft-reconfiguration inbound + exit-address-family +! +route-map permit-all permit 10 diff --git a/tests/topotests/bgp_remove_private_as/r4/zebra.conf b/tests/topotests/bgp_remove_private_as/r4/zebra.conf new file mode 100644 index 0000000..4de3300 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r4/zebra.conf @@ -0,0 +1,10 @@ +! to r2 +interface r4-eth0 + ip address 203.0.113.8/31 +! +! to r5 +interface r4-eth1 + ip address 203.0.113.10/31 +! +ip forwarding +! diff --git a/tests/topotests/bgp_remove_private_as/r5/bgpd.conf b/tests/topotests/bgp_remove_private_as/r5/bgpd.conf new file mode 100644 index 0000000..67b4d43 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r5/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 5555 + bgp router-id 192.0.2.5 + neighbor 203.0.113.2 remote-as 65001 + neighbor 203.0.113.2 timers 3 10 + neighbor 203.0.113.2 description r1 + neighbor 203.0.113.6 remote-as 65003 + neighbor 203.0.113.6 solo + neighbor 203.0.113.6 timers 3 10 + neighbor 203.0.113.6 description r3 + neighbor 203.0.113.10 remote-as 4444 + neighbor 203.0.113.10 solo + neighbor 203.0.113.10 timers 3 10 + neighbor 203.0.113.10 description r4 +! + address-family ipv4 unicast + neighbor 203.0.113.2 route-map permit-all in + neighbor 203.0.113.6 route-map permit-all out + neighbor 203.0.113.10 route-map permit-all out + exit-address-family +! +route-map permit-all permit 10 +! diff --git a/tests/topotests/bgp_remove_private_as/r5/zebra.conf b/tests/topotests/bgp_remove_private_as/r5/zebra.conf new file mode 100644 index 0000000..f02daf4 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/r5/zebra.conf @@ -0,0 +1,14 @@ +! to r1 +interface r5-eth0 + ip address 203.0.113.3/31 +! +! to r3 +interface r5-eth1 + ip address 203.0.113.7/31 +! +! to r4 +interface r5-eth2 + ip address 203.0.113.11/31 +! +ip forwarding +! diff --git a/tests/topotests/bgp_remove_private_as/test_bgp_remove_private_as.py b/tests/topotests/bgp_remove_private_as/test_bgp_remove_private_as.py new file mode 100644 index 0000000..e48f81c --- /dev/null +++ b/tests/topotests/bgp_remove_private_as/test_bgp_remove_private_as.py @@ -0,0 +1,415 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_remove_private_as.py +# +# Copyright (C) 2022 NVIDIA Corporation +# Trey Aspelund +# + +""" +test_bgp_remove_private_as.py tests the following conditions: +1. "remove-private-AS" strips all private ASNs from the AS-path unless: + a. the ASN belongs to the peer + b. the ASN is both local + private + c. the AS-path is not completely comprised of public ASNs +2. "remove-private-AS all" strips all private ASNs from the AS-path unless: + a. the ASN belongs to the peer + b. the ASN is both local + private +3. "remove-private-AS replace-AS" swaps private ASNs with local ASN unless: + a. the ASN belongs to the peer + b. the AS-path is not completely comprised of public ASNs +4. "remove-private-AS all replace-AS" swaps private ASNs with local ASN unless: + a. the ASN belongs to the peer + +All conditions are tested while the local ASN is private. +All conditions are tested while the local ASN is public. +All conditions are tested against an eBGP peer in a private ASN. +All conditions are tested against an eBGP peer in a public ASN. +""" + +import os +import sys +import json +import time +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from functools import partial + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + """ + We are effectively creating two hub/spoke topologies with r2 and r5 acting + as hubs. "remove-private-AS" will be configured on r2/r5 towards r3/r4, and + r1 will act as the originator of the test prefixes. AS-Path validation will + be done on r3/r4. + + Topology: + +-----+ +-----+ +-----+ + | r1 |----->|r2/r5|---->| r3 | + +-----+ +-----+ +-----+ + | + v + +-----+ + | r4 | + +-----+ + ASNs: + - r1: 65001 + - r2: 65002 + - r3: 65003 + - r4: 4444 + - r5: 5555 + """ + for routern in range(1, 6): + tgen.add_router(f"r{routern}") + + ####################### + # Connections to r2 + ####################### + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + ####################### + # Connections to r5 + ####################### + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r5"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r5"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, f"{rname}/zebra.conf") + ) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, f"{rname}/bgpd.conf")) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_remove_private_as(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Test routes + prefixes = [ + "100.64.0.0/32", + "100.64.0.1/32", + "100.64.0.2/32", + "100.64.0.3/32", + "100.64.0.4/32", + ] + + # r2/r5 are setup with remove-private-AS configs. + tx_routers = ["r2", "r5"] + + # We will validate the paths received by r3/r4. + rx_routers = ["r3", "r4"] + + # Config options for remove-private-AS + remove_types = [ + "remove-private-AS", + "remove-private-AS all", + "remove-private-AS replace-AS", + "remove-private-AS all replace-AS", + ] + + # Expected as-paths for each test route from the perspective of each + # rx_router, accounting for each variation of remove-private-AS. + # + # Structure: + # expected_paths = { + # rx_router: { + # remove_type: { + # tx_router: { + # prefix: "path" + # } + # } + # } + # } + expected_paths = { + "r3": { + "remove-private-AS": { + "r2": { + "100.64.0.0/32": "65002", + "100.64.0.1/32": "65002 65003 65003", + "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "65002 65001 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555", + "100.64.0.1/32": "5555 65003 65003", + "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "5555 65001 1000 2000 2000 3000", + }, + }, + "remove-private-AS all": { + "r2": { + "100.64.0.0/32": "65002", + "100.64.0.1/32": "65002 65003 65003", + "100.64.0.2/32": "65002 1000 2000", + "100.64.0.3/32": "65002 65003 1000 2000 65003", + "100.64.0.4/32": "65002 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555", + "100.64.0.1/32": "5555 65003 65003", + "100.64.0.2/32": "5555 1000 2000", + "100.64.0.3/32": "5555 65003 1000 2000 65003", + "100.64.0.4/32": "5555 1000 2000 2000 3000", + }, + }, + "remove-private-AS replace-AS": { + "r2": { + "100.64.0.0/32": "65002 65002 65002 65002 65002", + "100.64.0.1/32": "65002 65002 65003 65002 65002 65002 65003", + "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "65002 65001 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555 5555 5555 5555 5555", + "100.64.0.1/32": "5555 5555 65003 5555 5555 5555 65003", + "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "5555 65001 1000 2000 2000 3000", + }, + }, + "remove-private-AS all replace-AS": { + "r2": { + "100.64.0.0/32": "65002 65002 65002 65002 65002", + "100.64.0.1/32": "65002 65002 65003 65002 65002 65002 65003", + "100.64.0.2/32": "65002 65002 65002 1000 65002 2000 65002", + "100.64.0.3/32": "65002 65002 65003 65002 1000 65002 2000 65002 65003", + "100.64.0.4/32": "65002 65002 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555 5555 5555 5555 5555", + "100.64.0.1/32": "5555 5555 65003 5555 5555 5555 65003", + "100.64.0.2/32": "5555 5555 5555 1000 5555 2000 5555", + "100.64.0.3/32": "5555 5555 65003 5555 1000 5555 2000 5555 65003", + "100.64.0.4/32": "5555 5555 1000 2000 2000 3000", + }, + }, + }, + "r4": { + "remove-private-AS": { + "r2": { + "100.64.0.0/32": "65002", + "100.64.0.1/32": "65002", + "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "65002 65001 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555", + "100.64.0.1/32": "5555", + "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "5555 65001 1000 2000 2000 3000", + }, + }, + "remove-private-AS all": { + "r2": { + "100.64.0.0/32": "65002", + "100.64.0.1/32": "65002", + "100.64.0.2/32": "65002 1000 2000", + "100.64.0.3/32": "65002 1000 2000", + "100.64.0.4/32": "65002 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555", + "100.64.0.1/32": "5555", + "100.64.0.2/32": "5555 1000 2000", + "100.64.0.3/32": "5555 1000 2000", + "100.64.0.4/32": "5555 1000 2000 2000 3000", + }, + }, + "remove-private-AS replace-AS": { + "r2": { + "100.64.0.0/32": "65002 65002 65002 65002 65002", + "100.64.0.1/32": "65002 65002 65002 65002 65002 65002 65002", + "100.64.0.2/32": "65002 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "65002 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "65002 65001 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555 5555 5555 5555 5555", + "100.64.0.1/32": "5555 5555 5555 5555 5555 5555 5555", + "100.64.0.2/32": "5555 65001 4200000000 1000 4200000001 2000 4200000002", + "100.64.0.3/32": "5555 65001 65003 4200000000 1000 4200000001 2000 4200000002 65003", + "100.64.0.4/32": "5555 65001 1000 2000 2000 3000", + }, + }, + "remove-private-AS all replace-AS": { + "r2": { + "100.64.0.0/32": "65002 65002 65002 65002 65002", + "100.64.0.1/32": "65002 65002 65002 65002 65002 65002 65002", + "100.64.0.2/32": "65002 65002 65002 1000 65002 2000 65002", + "100.64.0.3/32": "65002 65002 65002 65002 1000 65002 2000 65002 65002", + "100.64.0.4/32": "65002 65002 1000 2000 2000 3000", + }, + "r5": { + "100.64.0.0/32": "5555 5555 5555 5555 5555", + "100.64.0.1/32": "5555 5555 5555 5555 5555 5555 5555", + "100.64.0.2/32": "5555 5555 5555 1000 5555 2000 5555", + "100.64.0.3/32": "5555 5555 5555 5555 1000 5555 2000 5555 5555", + "100.64.0.4/32": "5555 5555 1000 2000 2000 3000", + }, + }, + }, + } + + # Simple lookup of remote peer ip by routers in session (local --> remote). + # + # Structure: + # peer_to_ip = { + # local_rtr: { + # peer_rtr: peer_ip + # } + # } + peer_to_ip = { + "r1": {"r2": "203.0.113.1", "r5": "203.0.113.3"}, + "r2": {"r1": "203.0.113.0", "r3": "203.0.113.4", "r4": "203.0.113.8"}, + "r3": {"r2": "203.0.113.5", "r5": "203.0.113.7"}, + "r4": {"r2": "203.0.113.9", "r5": "203.0.113.11"}, + "r5": {"r1": "203.0.113.2", "r3": "203.0.113.6", "r4": "203.0.113.10"}, + } + + def __bgp_up(): + """Return True if all configured peers are Established.""" + for router in tx_routers: + output = json.loads( + tgen.gears[router].vtysh_cmd("show ip bgp summary json") + ) + numPeers = output["ipv4Unicast"]["totalPeers"] + numConverged = 0 + for peer_data in output["ipv4Unicast"]["peers"].values(): + if peer_data["state"] == "Established": + numConverged += 1 + if numConverged == numPeers: + return True + return False + + def __bgp_converged(): + """Return True if all prefixes have been received from tx_routers.""" + for router in rx_routers: + output = json.loads( + tgen.gears[router].vtysh_cmd("show ip bgp summary json") + ) + numPeers = output["ipv4Unicast"]["totalPeers"] + numConverged = 0 + for peer in tx_routers: + peer_ip = peer_to_ip[router][peer] + numPrefixes = output["ipv4Unicast"]["peers"][peer_ip]["pfxRcd"] + if numPrefixes == len(prefixes): + numConverged += 1 + if numConverged == numPeers: + return True + return False + + def _routers_up(tx_rtrs, rx_rtrs): + """Ensure all BGP sessions are up and all routes are installed.""" + # all sessions go through tx_routers, so ensure all their peers are up + test_func = partial(__bgp_up) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result == True, "Not all peers in Established state!" + + # ensure correct number of routes are installed + test_func = partial(__bgp_converged) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result == True, "Not all routes installed in time!" + + def _change_remove_type(new_type, op): + """Update config with next remove-private-AS config variant.""" + no = "no" if op == "del" else "" + for tr in tx_routers: + for rr in rx_routers: + p_ip = peer_to_ip[tr][rr] + tgen.gears[tr].vtysh_multicmd( + f""" + configure terminal + router bgp + address-family ipv4 unicast + {no} neighbor {p_ip} {new_type} + """ + ) + + def _validate_paths(remove_type): + """Compare actual AS-Path against expected AS-Path.""" + for rtr in rx_routers: + for peer in tx_routers: + p_ip = peer_to_ip[rtr][peer] + adj_rib_in = json.loads( + tgen.gears[rtr].vtysh_cmd( + f"show ip bgp neighbor {p_ip} received-routes json" + ) + ) + for pfx in prefixes: + good_path = expected_paths[rtr][remove_type][peer][pfx] + real_path = adj_rib_in["receivedRoutes"][pfx]["path"] + return real_path == good_path + + ####################### + # Begin Test + ####################### + + # make sure all peers come up and exchange routes + _routers_up(tx_routers, rx_routers) + + # test each variation of remove-private-AS + for rmv_type in remove_types: + _change_remove_type(rmv_type, "add") + + test_func = partial(_validate_paths, rmv_type) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result == True, "Not all routes have correct AS-Path values!" + + # each variation sets a separate peer flag in bgpd. we need to clear + # the old flag after each iteration so we only test the flags we expect. + _change_remove_type(rmv_type, "del") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_remove_private_as_route_map/__init__.py b/tests/topotests/bgp_remove_private_as_route_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_remove_private_as_route_map/r1/frr.conf b/tests/topotests/bgp_remove_private_as_route_map/r1/frr.conf new file mode 100644 index 0000000..b2dba7d --- /dev/null +++ b/tests/topotests/bgp_remove_private_as_route_map/r1/frr.conf @@ -0,0 +1,10 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 +! diff --git a/tests/topotests/bgp_remove_private_as_route_map/r2/frr.conf b/tests/topotests/bgp_remove_private_as_route_map/r2/frr.conf new file mode 100644 index 0000000..9c423ce --- /dev/null +++ b/tests/topotests/bgp_remove_private_as_route_map/r2/frr.conf @@ -0,0 +1,19 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 + ip address 192.168.2.1/32 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.1 route-map r1 out + neighbor 192.168.1.1 remove-private-AS all + exit-address-family +! +route-map r1 permit 10 + set as-path prepend 65123 4200000001 +! diff --git a/tests/topotests/bgp_remove_private_as_route_map/test_bgp_remove_private_as_route_map.py b/tests/topotests/bgp_remove_private_as_route_map/test_bgp_remove_private_as_route_map.py new file mode 100644 index 0000000..d9402f2 --- /dev/null +++ b/tests/topotests/bgp_remove_private_as_route_map/test_bgp_remove_private_as_route_map.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if private AS is removed from AS_PATH attribute when route-map is used (prepend). +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_remove_private_as_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _check_routes(): + output = json.loads(r1.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "192.168.2.1/32": [ + { + "valid": True, + "path": "65002", + } + ] + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _check_routes, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert ( + result is None + ), "65123 4200000001 ASNs should be removed from AS_PATH attribute" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_rfapi_basic_sanity/__init__.py b/tests/topotests/bgp_rfapi_basic_sanity/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/bgp_rfapi_basic_sanity/customize.py b/tests/topotests/bgp_rfapi_basic_sanity/customize.py new file mode 100644 index 0000000..c789fa8 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/customize.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2017-2018 by +# Network Device Education Foundation, Inc. ("NetDEF") +# Modified by LabN Consulting, L.L.C. +# + +r""" +customize.py: Simple FRR MPLS L3VPN test topology + + +---------+ + | r1 | + | 1.1.1.1 | PE Router + +----+----+ + | .1 r1-eth0 + | + ~~~~~~~~~~~~~ + ~~ sw0 ~~ + ~~ 10.0.1.0/24 ~~ + ~~~~~~~~~~~~~ + |10.0.1.0/24 + | + | .2 r2-eth0 + +----+----+ + | r2 | + | 2.2.2.2 | P router + +--+---+--+ + r2-eth2 .2 | | .2 r2-eth1 + ______/ \______ + / \ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ +~~ sw2 ~~ ~~ sw1 ~~ +~~ 10.0.3.0/24 ~~ ~~ 10.0.2.0/24 ~~ + ~~~~~~~~~~~~~ ~~~~~~~~~~~~~ + | / | + \ _________/ | + \ / \ +r3-eth1 .3 | | .3 r3-eth0 | .4 r4-eth0 + +----+--+---+ +----+----+ + | r3 | | r4 | + | 3.3.3.3 | | 4.4.4.4 | PE Routers + +-----------+ +---------+ + +""" + +import os + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import get_topogen +from lib.topolog import logger +from lib.ltemplate import ltemplateRtrCmd + +# Required to instantiate the topology builder class. + + +CWD = os.path.dirname(os.path.realpath(__file__)) +# test name based on directory +TEST = os.path.basename(CWD) + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # Create P/PE routers + tgen.add_router("r1") + for routern in range(2, 5): + tgen.add_router("r{}".format(routern)) + # Create a switch with just one router connected to it to simulate a + # empty network. + switch = {} + switch[0] = tgen.add_switch("sw0") + switch[0].add_link(tgen.gears["r1"], nodeif="r1-eth0") + switch[0].add_link(tgen.gears["r2"], nodeif="r2-eth0") + + switch[1] = tgen.add_switch("sw1") + switch[1].add_link(tgen.gears["r2"], nodeif="r2-eth1") + switch[1].add_link(tgen.gears["r3"], nodeif="r3-eth0") + switch[1].add_link(tgen.gears["r4"], nodeif="r4-eth0") + + switch[2] = tgen.add_switch("sw2") + switch[2].add_link(tgen.gears["r2"], nodeif="r2-eth2") + switch[2].add_link(tgen.gears["r3"], nodeif="r3-eth1") + + +def ltemplatePreRouterStartHook(): + cc = ltemplateRtrCmd() + tgen = get_topogen() + logger.info("pre router-start hook") + return True + + +def ltemplatePostRouterStartHook(): + logger.info("post router-start hook") + return True diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf new file mode 100644 index 0000000..f6e0baa --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r1/bgpd.conf @@ -0,0 +1,50 @@ +frr defaults traditional +! +hostname r1 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 1.1.1.1 + bgp cluster-id 1.1.1.1 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 1.1.1.1 +! + address-family ipv4 unicast + redistribute vnc-direct + no neighbor 2.2.2.2 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf new file mode 100644 index 0000000..460a8fb --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r1/ospfd.conf @@ -0,0 +1,12 @@ +hostname r1 +log file ospfd.log +! +router ospf + router-id 1.1.1.1 + network 0.0.0.0/4 area 0 + redistribute static +! +int r1-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf new file mode 100644 index 0000000..18f61e0 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r1/zebra.conf @@ -0,0 +1,24 @@ +log file zebra.log +! +hostname r1 +! +interface lo + ip address 1.1.1.1/32 +! +interface r1-eth0 + description to sw0 + ip address 10.0.1.1/24 + no link-detect +! +interface r1-eth4 + description to ce1 + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.1/32 192.168.1.2 +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf new file mode 100644 index 0000000..19050e6 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r2/bgpd.conf @@ -0,0 +1,33 @@ +frr defaults traditional +! +hostname r2 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 2.2.2.2 + bgp cluster-id 2.2.2.2 + no bgp ebgp-requires-policy + neighbor 1.1.1.1 remote-as 5226 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 update-source 2.2.2.2 + neighbor 3.3.3.3 remote-as 5226 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 update-source 2.2.2.2 + neighbor 4.4.4.4 remote-as 5226 + neighbor 4.4.4.4 timers 3 10 + neighbor 4.4.4.4 update-source 2.2.2.2 + address-family ipv4 unicast + no neighbor 1.1.1.1 activate + no neighbor 3.3.3.3 activate + no neighbor 4.4.4.4 activate + exit-address-family + address-family ipv4 vpn + neighbor 1.1.1.1 activate + neighbor 1.1.1.1 route-reflector-client + neighbor 3.3.3.3 activate + neighbor 3.3.3.3 route-reflector-client + neighbor 4.4.4.4 activate + neighbor 4.4.4.4 route-reflector-client + exit-address-family +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf new file mode 100644 index 0000000..dbed618 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r2/ospfd.conf @@ -0,0 +1,19 @@ +hostname r2 +log file ospfd.log +! +router ospf + router-id 2.2.2.2 + network 0.0.0.0/0 area 0 +! +int r2-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r2-eth2 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf new file mode 100644 index 0000000..dd1dbac --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r2/zebra.conf @@ -0,0 +1,27 @@ +log file zebra.log +! +hostname r2 +! +interface lo + ip address 2.2.2.2/32 +! +interface r2-eth0 + description to sw0 + ip address 10.0.1.2/24 + no link-detect +! +interface r2-eth1 + description to sw1 + ip address 10.0.2.2/24 + no link-detect +! +interface r2-eth2 + description to sw2 + ip address 10.0.3.2/24 + no link-detect +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf new file mode 100644 index 0000000..2210f24 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r3/bgpd.conf @@ -0,0 +1,48 @@ +frr defaults traditional +! +hostname r3 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 3.3.3.3 + bgp cluster-id 3.3.3.3 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 3.3.3.3 +! + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf new file mode 100644 index 0000000..0e64ed6 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r3/ospfd.conf @@ -0,0 +1,17 @@ +hostname r3 +password 1 +log file ospfd.log +! +router ospf + router-id 3.3.3.3 + network 0.0.0.0/4 area 0 + redistribute static +! +int r3-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! +int r3-eth1 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf new file mode 100644 index 0000000..9dbc290 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r3/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log +! +hostname r3 +! +interface lo + ip address 3.3.3.3/32 +! +interface r3-eth0 + description to sw1 + ip address 10.0.2.3/24 + no link-detect +! +interface r3-eth1 + description to sw2 + ip address 10.0.3.3/24 + no link-detect +! +interface r3-eth4 + description to ce2 + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.2/32 192.168.1.2 +! +ip forwarding +! +! +line vty +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf new file mode 100644 index 0000000..28b5f9c --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r4/bgpd.conf @@ -0,0 +1,49 @@ +frr defaults traditional +! +hostname r4 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 4.4.4.4 + bgp cluster-id 4.4.4.4 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 4.4.4.4 +! + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf new file mode 100644 index 0000000..89e37df --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r4/ospfd.conf @@ -0,0 +1,12 @@ +hostname r4 +log file ospfd.log +! +router ospf + router-id 4.4.4.4 + network 0.0.0.0/4 area 0 + redistribute static +! +int r4-eth0 + ip ospf hello-interval 2 + ip ospf dead-interval 10 +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf new file mode 100644 index 0000000..415f03d --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/r4/zebra.conf @@ -0,0 +1,23 @@ +log file zebra.log +! +hostname r4 +! +interface lo + ip address 4.4.4.4/32 +! +interface r4-eth0 + description to sw1 + ip address 10.0.2.4/24 + no link-detect +! +interface r4-eth4 + description to ce3 + ip address 192.168.1.1/24 + no link-detect +! +ip route 99.0.0.3/32 192.168.1.2 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py new file mode 100644 index 0000000..bc47dfc --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/add_routes.py @@ -0,0 +1,159 @@ +from lib.lutil import luCommand + +holddownFactorSet = luCommand( + "r1", + 'vtysh -c "show running"', + "rfp holddown-factor", + "none", + "Holddown factor set", +) +if not holddownFactorSet: + to = "-1" + cost = "" +else: + to = "6" + cost = "cost 50" +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev open vn 10.0.0.1 un 1.1.1.1"', + "rfapi_set_response_cb: status 0", + "pass", + "Opened RFAPI", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 11.11.11.11"', + "rc=2", + "pass", + "Clean query", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 11.11.11.0/24 lifetime {}"'.format( + to + ), + "", + "none", + "Prefix registered", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "1 out of 1", + "wait", + "Local registration", +) +luCommand("r1", 'vtysh -c "debug rfapi-dev response-omit-self off"', ".", "none") +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 11.11.11.11"', + "11.11.11.0/24", + "pass", + "Query self", +) + +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev open vn 10.0.0.2 un 2.2.2.2"', + "rfapi_set_response_cb: status 0", + "pass", + "Opened RFAPI", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 22.22.22.0/24 lifetime {}"'.format( + to + ), + "", + "none", + "Prefix registered", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "1 out of 1", + "wait", + "Local registration", +) +luCommand("r3", 'vtysh -c "debug rfapi-dev response-omit-self on"', ".", "none") +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 22.22.22.22"', + "rc=2", + "pass", + "Self excluded", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev open vn 10.0.1.2 un 2.1.1.2"', + "rfapi_set_response_cb: status 0", + "pass", + "Opened query only RFAPI", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev query vn 10.0.1.2 un 2.1.1.2 target 22.22.22.22"', + "22.22.22.0/24", + "pass", + "See local", +) + +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev open vn 10.0.0.3 un 3.3.3.3"', + "rfapi_set_response_cb: status 0", + "pass", + "Opened RFAPI", +) +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 33.33.33.0/24 lifetime {}"'.format( + to + ), + "", + "none", + "Prefix registered", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "1 out of 1", + "wait", + "Local registration", +) +luCommand("r4", 'vtysh -c "debug rfapi-dev response-omit-self off"', ".", "none") +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 33.33.33.33"', + "33.33.33.0/24", + "pass", + "Query self", +) + +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 11.11.11.0/24 lifetime {} {}"'.format( + to, cost + ), + "", + "none", + "MP Prefix registered", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "2 out of 2", + "wait", + "Local registration", +) +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 11.11.11.11"', + "11.11.11.0/24", + "pass", + "Query self MP", +) + +luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py new file mode 100644 index 0000000..9878cdc --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/adjacencies.py @@ -0,0 +1,50 @@ +from lib.lutil import luCommand + +luCommand( + "r1", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r3", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r4", "ping 2.2.2.2 -c 1", " 0. packet loss", "wait", "PE->P2 (loopback) ping", 60 +) +luCommand( + "r2", + 'vtysh -c "show bgp summary"', + " 00:0.* 00:0.* 00:0", + "wait", + "Core adjacencies up", + 180, +) +luCommand( + "r1", + 'vtysh -c "show bgp vrf all summary"', + " 00:0", + "wait", + "All adjacencies up", + 180, +) +luCommand( + "r3", + 'vtysh -c "show bgp vrf all summary"', + " 00:0", + "wait", + "All adjacencies up", + 180, +) +luCommand( + "r4", + 'vtysh -c "show bgp vrf all summary"', + " 00:0", + "wait", + "All adjacencies up", + 180, +) +luCommand( + "r1", "ping 3.3.3.3 -c 1", " 0. packet loss", "wait", "PE->PE3 (loopback) ping" +) +luCommand( + "r1", "ping 4.4.4.4 -c 1", " 0. packet loss", "wait", "PE->PE4 (loopback) ping" +) +# luCommand('r4','ping 3.3.3.3 -c 1',' 0. packet loss','wait','PE->PE3 (loopback) ping') diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py new file mode 100644 index 0000000..e68fac8 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_close.py @@ -0,0 +1,102 @@ +from lib.lutil import luCommand + +holddownFactorSet = luCommand( + "r1", + 'vtysh -c "show running"', + "rfp holddown-factor", + "none", + "Holddown factor set", +) +if not holddownFactorSet: + to = "-1" +else: + to = "1" +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev open vn 20.0.0.1 un 1.1.1.21"', + "rfapi_set_response_cb: status 0", + "pass", + "Opened RFAPI", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev register vn 20.0.0.1 un 1.1.1.21 prefix 111.111.111.0/24 lifetime {}"'.format( + to + ), + "", + "none", + "Prefix registered", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "111.111.111.0/24", + "wait", + "Local registration", + 1, +) +luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "111.111.111.0/24", + "wait", + "See registration", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "111.111.111.0/24", + "wait", + "See registration", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev close vn 20.0.0.1 un 1.1.1.21"', + "status 0", + "pass", + "Closed RFAPI", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3", + "wait", + "See cleanup", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3", + "wait", + "See cleanup", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 2", + "wait", + "See cleanup", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", + 20, +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", +) diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py new file mode 100644 index 0000000..24b3cba --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_routes.py @@ -0,0 +1,74 @@ +from lib.lutil import luCommand + +luCommand("r1", 'vtysh -c "show bgp ipv4 vpn"', "", "none", "VPN SAFI") +luCommand("r2", 'vtysh -c "show bgp ipv4 vpn"', "", "none", "VPN SAFI") +luCommand("r3", 'vtysh -c "show bgp ipv4 vpn"', "", "none", "VPN SAFI") +luCommand("r4", 'vtysh -c "show bgp ipv4 vpn"', "", "none", "VPN SAFI") +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3", + "wait", + "See all registrations", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3", + "wait", + "See all registrations", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 2", + "wait", + "See all registrations", +) +num = "4 routes and 4" +luCommand("r1", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI okay") +luCommand("r2", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI okay") +luCommand("r3", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI okay") +luCommand("r4", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI okay") +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 22.22.22.22"', + "pfx=", + "pass", + "Query R2s info", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 33.33.33.33"', + "pfx=", + "pass", + "Query R4s info", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 11.11.11.11"', + "11.11.11.0/24.*11.11.11.0/24.*", + "pass", + "Query R1s+R4s info", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.2 un 2.2.2.2 target 33.33.33.33"', + "pfx=", + "pass", + "Query R4s info", +) +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 11.11.11.11"', + "11.11.11.0/24.*11.11.11.0/24.*", + "pass", + "Query R1s+R4s info", +) +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 22.22.22.22"', + "pfx=", + "pass", + "Query R2s info", +) diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py new file mode 100644 index 0000000..f5c2db2 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/check_timeout.py @@ -0,0 +1,325 @@ +from lib.lutil import luCommand + +holddownFactorSet = luCommand( + "r1", + 'vtysh -c "show running"', + "rfp holddown-factor", + "none", + "Holddown factor set", +) +luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") +luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") +if not holddownFactorSet: + luCommand( + "r1", + 'vtysh -c "show vnc summary"', + ".", + "pass", + "Holddown factor not set -- skipping test", + ) +else: + # holddown time test + luCommand( + "r1", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 lifetime 10"', + "", + "none", + "Prefix registered", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "1.111.0.0/16", + "wait", + "Local registration", + ) + + luCommand( + "r3", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 lifetime 10"', + "", + "none", + "Prefix registered", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "1.222.0.0/16", + "wait", + "Local registration", + ) + + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Remotely: *Active: 4 ", + "wait", + "See registrations, L=10", + ) + + luCommand( + "r4", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.3 un 3.3.3.3 prefix 1.222.0.0/16 lifetime 5 cost 50"', + "", + "none", + "MP Prefix registered", + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations local"', + "1.222.0.0/16", + "wait", + "Local registration (MP prefix)", + ) + + luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") + luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") + + luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 1.111.111.111"', + "pfx=", + "pass", + "Query R1s info", + ) + luCommand( + "r4", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.3 un 3.3.3.3 target 1.222.222.222"', + "1.222.0.0/16.*1.222.0.0/16", + "pass", + "Query R3s+R4s info", + ) + + luCommand( + "r4", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 1.222.0.0/16"', + "", + "none", + "MP Prefix removed", + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 1 ", + "wait", + "MP prefix in holddown", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 1 ", + "wait", + "MP prefix in holddown", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 1 ", + "wait", + "MP prefix in holddown", + ) + luCommand( + "r1", + 'vtysh -c "debug rfapi-dev query vn 10.0.0.1 un 1.1.1.1 target 1.222.222.222"', + "1.222.0.0/16", + "pass", + "Query R3s info", + ) + luCommand( + "r1", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16"', + "", + "none", + "Prefix timeout", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations holddown"', + "1.111.0.0/16", + "wait", + "Local holddown", + 1, + ) + luCommand( + "r3", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16"', + "", + "none", + "Prefix timeout", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations holddown"', + "1.222.0.0/16", + "wait", + "Local holddown", + 1, + ) + luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") + luCommand("r4", 'vtysh -c "show vnc registrations"', ".", "none") + + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 2 ", + "wait", + "In holddown", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 2 ", + "wait", + "In holddown", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 2 ", + "wait", + "In holddown", + ) + + luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", + 20, + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "In Holddown: *Active: 0", + "wait", + "Out of holddown", + ) + + # kill test + luCommand( + "r1", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 lifetime 10"', + "", + "none", + "Prefix registered", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations local"', + "1.111.0.0/16", + "wait", + "Local registration", + ) + + luCommand( + "r3", + 'vtysh -c "debug rfapi-dev register vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 lifetime 10"', + "", + "none", + "Prefix registered", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations local"', + "1.222.0.0/16", + "wait", + "Local registration", + ) + + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Remotely: *Active: 4 ", + "wait", + "See registrations L=10 (pre-kill)", + 5, + ) + luCommand("r1", 'vtysh -c "show vnc registrations"', ".", "none") + luCommand("r3", 'vtysh -c "show vnc registrations"', ".", "none") + luCommand( + "r1", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 1.111.0.0/16 kill"', + "", + "none", + "Prefix kill", + ) + luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 4 .*In Holddown: *Active: 0", + "wait", + "Registration killed", + 1, + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 3 .*In Holddown: *Active: 1", + "wait", + "Remote in holddown", + 5, + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 3 .*In Holddown: *Active: 1", + "wait", + "Remote in holddown", + 5, + ) + + luCommand( + "r3", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 1.222.0.0/16 kill"', + "", + "none", + "Prefix kill", + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3 .*In Holddown: *Active: 1", + "wait", + "Registration killed", + 1, + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 2 .*In Holddown: *Active: 2", + "wait", + "Remote in holddown", + 5, + ) + + luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3 .*In Holddown: *Active: 0", + "wait", + "Out of holddown", + 20, + ) + luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 1 .* Remotely: *Active: 3 .*In Holddown: *Active: 0", + "wait", + "Out of holddown", + ) + luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 2 .* Remotely: *Active: 2 .*In Holddown: *Active: 0", + "wait", + "Out of holddown", + ) diff --git a/tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py b/tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py new file mode 100644 index 0000000..7201ac8 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/scripts/cleanup_all.py @@ -0,0 +1,124 @@ +from lib.lutil import luCommand + +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.1 un 1.1.1.1 prefix 11.11.11.0/24"', + "", + "none", + "Prefix removed", +) +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 ", + "wait", + "Local registration removed", +) +luCommand( + "r1", + 'vtysh -c "debug rfapi-dev close vn 10.0.0.1 un 1.1.1.1"', + "status 0", + "pass", + "Closed RFAPI", +) + +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.2 un 2.2.2.2 prefix 22.22.22.0/24"', + "", + "none", + "Prefix removed", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 ", + "wait", + "Local registration removed", +) +luCommand( + "r3", + 'vtysh -c "debug rfapi-dev close vn 10.0.0.2 un 2.2.2.2"', + "status 0", + "pass", + "Closed RFAPI", +) + +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 33.33.33.0/24"', + "", + "none", + "Prefix removed", +) +luCommand( + "r4", + 'vtysh -c "debug rfapi-dev unregister vn 10.0.0.3 un 3.3.3.3 prefix 11.11.11.0/24"', + "", + "none", + "MP prefix removed", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 ", + "wait", + "Local registration removed", +) +# luCommand('r4','vtysh -c "debug rfapi-dev close vn 10.0.0.3 un 3.3.3.3"','status 0', 'pass', 'Closed RFAPI') +luCommand("r4", 'vtysh -c "clear vnc nve *"', ".", "pass", "Cleared NVEs") + +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0", + "wait", + "All registrations cleared", +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0", + "wait", + "All registrations cleared", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0", + "wait", + "All registrations cleared", +) + +num = "0 exist" +luCommand("r1", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI clear") +luCommand("r2", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI clear") +luCommand("r3", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI clear") +luCommand("r4", 'vtysh -c "show bgp ipv4 vpn"', num, "pass", "VPN SAFI clear") + +luCommand( + "r1", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0 .*In Holddown: *Active: 0", + "wait", + "No holddowns", + 20, +) +luCommand( + "r3", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0 .*In Holddown: *Active: 0", + "wait", + "No holddowns", +) +luCommand( + "r4", + 'vtysh -c "show vnc registrations"', + "Locally: *Active: 0 .* Remotely: *Active: 0 .*In Holddown: *Active: 0", + "wait", + "No holddowns", +) + +luCommand("r1", 'vtysh -c "show vnc summary"', ".", "none") +luCommand("r3", 'vtysh -c "show vnc summary"', ".", "none") +luCommand("r4", 'vtysh -c "show vnc summary"', ".", "none") diff --git a/tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py b/tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py new file mode 100755 index 0000000..7534dce --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import sys +import pytest + +sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) + +from lib.ltemplate import * + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd] + + +def test_add_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/add_routes.py", False, CliOnFail, CheckFunc) + + +def test_adjacencies(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/adjacencies.py", False, CliOnFail, CheckFunc) + + +def test_check_routes(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/check_routes.py", False, CliOnFail, CheckFunc) + + +def test_check_close(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/check_close.py", False, CliOnFail, CheckFunc) + + +def test_check_timeout(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/check_timeout.py", False, CliOnFail, CheckFunc) + + +def test_cleanup_all(): + CliOnFail = None + # For debugging, uncomment the next line + # CliOnFail = 'tgen.mininet_cli' + CheckFunc = "ltemplateVersionCheck('3.1')" + # uncomment next line to start cli *before* script is run + # CheckFunc = 'ltemplateVersionCheck(\'3.1\', cli=True)' + ltemplateTest("scripts/cleanup_all.py", False, CliOnFail, CheckFunc) + + +if __name__ == "__main__": + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/__init__.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py new file mode 120000 index 0000000..a6b653a --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/customize.py @@ -0,0 +1 @@ +../bgp_rfapi_basic_sanity/customize.py \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf new file mode 100644 index 0000000..3196a16 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/bgpd.conf @@ -0,0 +1,51 @@ +frr defaults traditional +! +hostname r1 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 1.1.1.1 + bgp cluster-id 1.1.1.1 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 1.1.1.1 +! + address-family ipv4 unicast + redistribute vnc-direct + no neighbor 2.2.2.2 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 + rfp full-table-download off +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf new file mode 120000 index 0000000..d09b09e --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/ospfd.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r1/ospfd.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf new file mode 120000 index 0000000..728b1b9 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r1/zebra.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r1/zebra.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf new file mode 100644 index 0000000..19050e6 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/bgpd.conf @@ -0,0 +1,33 @@ +frr defaults traditional +! +hostname r2 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 2.2.2.2 + bgp cluster-id 2.2.2.2 + no bgp ebgp-requires-policy + neighbor 1.1.1.1 remote-as 5226 + neighbor 1.1.1.1 timers 3 10 + neighbor 1.1.1.1 update-source 2.2.2.2 + neighbor 3.3.3.3 remote-as 5226 + neighbor 3.3.3.3 timers 3 10 + neighbor 3.3.3.3 update-source 2.2.2.2 + neighbor 4.4.4.4 remote-as 5226 + neighbor 4.4.4.4 timers 3 10 + neighbor 4.4.4.4 update-source 2.2.2.2 + address-family ipv4 unicast + no neighbor 1.1.1.1 activate + no neighbor 3.3.3.3 activate + no neighbor 4.4.4.4 activate + exit-address-family + address-family ipv4 vpn + neighbor 1.1.1.1 activate + neighbor 1.1.1.1 route-reflector-client + neighbor 3.3.3.3 activate + neighbor 3.3.3.3 route-reflector-client + neighbor 4.4.4.4 activate + neighbor 4.4.4.4 route-reflector-client + exit-address-family +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf new file mode 120000 index 0000000..834554e --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/ospfd.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r2/ospfd.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf new file mode 120000 index 0000000..2fefda5 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r2/zebra.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r2/zebra.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf new file mode 100644 index 0000000..e74fc0b --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/bgpd.conf @@ -0,0 +1,49 @@ +frr defaults traditional +! +hostname r3 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 3.3.3.3 + bgp cluster-id 3.3.3.3 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 3.3.3.3 +! + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 + rfp full-table-download off +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf new file mode 120000 index 0000000..353b9ad --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/ospfd.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r3/ospfd.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf new file mode 120000 index 0000000..44a63cf --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r3/zebra.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r3/zebra.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf new file mode 100644 index 0000000..56474aa --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/bgpd.conf @@ -0,0 +1,50 @@ +frr defaults traditional +! +hostname r4 +password zebra +log stdout notifications +log commands +router bgp 5226 + bgp router-id 4.4.4.4 + bgp cluster-id 4.4.4.4 + no bgp ebgp-requires-policy + neighbor 2.2.2.2 remote-as 5226 + neighbor 2.2.2.2 timers 3 10 + neighbor 2.2.2.2 update-source 4.4.4.4 +! + address-family ipv4 unicast + no neighbor 2.2.2.2 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 2.2.2.2 activate + exit-address-family +! + rfp holddown-factor 0 + rfp full-table-download off +! + vnc defaults + rd auto:vn:123 + response-lifetime 45 + rt both 1000:1 1000:2 + exit-vnc +! + vnc nve-group red + prefix vn 10.0.0.0/8 + rd auto:vn:10 + rt both 1000:10 + exit-vnc +! + vnc nve-group blue + prefix vn 20.0.0.0/8 + rd auto:vn:20 + rt both 1000:20 + exit-vnc +! + vnc nve-group green + prefix vn 30.0.0.0/8 + rd auto:vn:20 + rt both 1000:30 + exit-vnc +! +end diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf new file mode 120000 index 0000000..3d7a0aa --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/ospfd.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r4/ospfd.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf new file mode 120000 index 0000000..c2fcd19 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/r4/zebra.conf @@ -0,0 +1 @@ +../../bgp_rfapi_basic_sanity/r4/zebra.conf \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/scripts b/tests/topotests/bgp_rfapi_basic_sanity_config2/scripts new file mode 120000 index 0000000..8ac974d --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/scripts @@ -0,0 +1 @@ +../bgp_rfapi_basic_sanity/scripts/ \ No newline at end of file diff --git a/tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py b/tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py new file mode 120000 index 0000000..54a67a8 --- /dev/null +++ b/tests/topotests/bgp_rfapi_basic_sanity_config2/test_bgp_rfapi_basic_sanity_config2.py @@ -0,0 +1 @@ +../bgp_rfapi_basic_sanity/test_bgp_rfapi_basic_sanity.py \ No newline at end of file diff --git a/tests/topotests/bgp_rmap_extcommunity_none/__init__.py b/tests/topotests/bgp_rmap_extcommunity_none/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_rmap_extcommunity_none/r1/bgpd.conf b/tests/topotests/bgp_rmap_extcommunity_none/r1/bgpd.conf new file mode 100644 index 0000000..8ccdf9c --- /dev/null +++ b/tests/topotests/bgp_rmap_extcommunity_none/r1/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external +! +route-map r2 permit 10 + set extcommunity none +! diff --git a/tests/topotests/bgp_rmap_extcommunity_none/r1/zebra.conf b/tests/topotests/bgp_rmap_extcommunity_none/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_rmap_extcommunity_none/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_rmap_extcommunity_none/r2/bgpd.conf b/tests/topotests/bgp_rmap_extcommunity_none/r2/bgpd.conf new file mode 100644 index 0000000..9d58078 --- /dev/null +++ b/tests/topotests/bgp_rmap_extcommunity_none/r2/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + address-family ipv4 unicast + redistribute connected + neighbor 192.168.1.1 route-map r1 out + exit-address-family +! +route-map r1 permit 10 + set community 123:123 + set extcommunity bandwidth 200 +! diff --git a/tests/topotests/bgp_rmap_extcommunity_none/r2/zebra.conf b/tests/topotests/bgp_rmap_extcommunity_none/r2/zebra.conf new file mode 100644 index 0000000..dc15cf7 --- /dev/null +++ b/tests/topotests/bgp_rmap_extcommunity_none/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 172.16.16.1/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_rmap_extcommunity_none/test_bgp_rmap_extcommunity_none.py b/tests/topotests/bgp_rmap_extcommunity_none/test_bgp_rmap_extcommunity_none.py new file mode 100644 index 0000000..ef7c94b --- /dev/null +++ b/tests/topotests/bgp_rmap_extcommunity_none/test_bgp_rmap_extcommunity_none.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if route-map extcommunity none works: + +route-map permit 10 + set extcommunity none +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_extcommunity_none(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 172.16.16.1/32 json") + ) + expected = { + "prefix": "172.16.16.1/32", + "paths": [ + { + "community": { + "string": "123:123", + }, + "extendedCommunity": {"string": "LB:65002:25000000 (200.000 Mbps)"}, + } + ], + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "BGP Converge failed" + + def _bgp_extcommunity_strip(router): + router.vtysh_cmd( + "conf t\nrouter bgp 65001\naddress-family ipv4\nneighbor 192.168.1.2 route-map r2 in" + ) + output = json.loads( + router.vtysh_cmd("show bgp ipv4 unicast 172.16.16.1/32 json") + ) + expected = { + "prefix": "172.16.16.1/32", + "paths": [ + { + "community": { + "string": "123:123", + }, + "extendedCommunity": None, + } + ], + } + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_extcommunity_strip, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to strip incoming extended communities from r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_roles_capability/__init__.py b/tests/topotests/bgp_roles_capability/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_roles_capability/r1/bgpd.conf b/tests/topotests/bgp_roles_capability/r1/bgpd.conf new file mode 100644 index 0000000..273f933 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r1/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 64501 + bgp router-id 192.168.2.1 + network 192.0.2.0/24 +! Correct role pair + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 local-role provider + neighbor 192.168.2.2 timers 3 10 +! Incorrect role pair + neighbor 192.168.3.2 remote-as external + neighbor 192.168.3.2 local-role provider + neighbor 192.168.3.2 timers 3 10 +! Missed neighbor role + neighbor 192.168.4.2 remote-as external + neighbor 192.168.4.2 local-role provider + neighbor 192.168.4.2 timers 3 10 +! Missed neighbor role with strict-mode + neighbor 192.168.5.2 remote-as external + neighbor 192.168.5.2 local-role provider strict-mode + neighbor 192.168.5.2 timers 3 10 +! Testing peer-groups + neighbor PG peer-group + neighbor PG remote-as external + neighbor PG local-role provider + neighbor PG timers 3 10 + neighbor 192.168.6.2 peer-group PG diff --git a/tests/topotests/bgp_roles_capability/r1/zebra.conf b/tests/topotests/bgp_roles_capability/r1/zebra.conf new file mode 100644 index 0000000..301a1e8 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r1/zebra.conf @@ -0,0 +1,18 @@ +! +interface r1-eth0 + ip address 192.168.2.1/24 +! +interface r1-eth1 + ip address 192.168.3.1/24 +! +interface r1-eth2 + ip address 192.168.4.1/24 +! +interface r1-eth3 + ip address 192.168.5.1/24 +! +interface r1-eth4 + ip address 192.168.6.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/r2/bgpd.conf b/tests/topotests/bgp_roles_capability/r2/bgpd.conf new file mode 100644 index 0000000..aa8ba72 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r2/bgpd.conf @@ -0,0 +1,5 @@ +router bgp 64502 + bgp router-id 192.168.2.2 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 local-role customer + neighbor 192.168.2.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_capability/r2/zebra.conf b/tests/topotests/bgp_roles_capability/r2/zebra.conf new file mode 100644 index 0000000..86a9784 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/r3/bgpd.conf b/tests/topotests/bgp_roles_capability/r3/bgpd.conf new file mode 100644 index 0000000..5ed93d6 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r3/bgpd.conf @@ -0,0 +1,4 @@ +router bgp 64503 + neighbor 192.168.3.1 remote-as external + neighbor 192.168.3.1 local-role peer + neighbor 192.168.3.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_capability/r3/zebra.conf b/tests/topotests/bgp_roles_capability/r3/zebra.conf new file mode 100644 index 0000000..9df578e --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.3.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/r4/bgpd.conf b/tests/topotests/bgp_roles_capability/r4/bgpd.conf new file mode 100644 index 0000000..118132d --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r4/bgpd.conf @@ -0,0 +1,3 @@ +router bgp 64504 + neighbor 192.168.4.1 remote-as external + neighbor 192.168.4.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_capability/r4/zebra.conf b/tests/topotests/bgp_roles_capability/r4/zebra.conf new file mode 100644 index 0000000..03632ee --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.4.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/r5/bgpd.conf b/tests/topotests/bgp_roles_capability/r5/bgpd.conf new file mode 100644 index 0000000..2f62fbf --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r5/bgpd.conf @@ -0,0 +1,3 @@ +router bgp 64505 + neighbor 192.168.5.1 remote-as external + neighbor 192.168.5.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_capability/r5/zebra.conf b/tests/topotests/bgp_roles_capability/r5/zebra.conf new file mode 100644 index 0000000..2e95d77 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r5/zebra.conf @@ -0,0 +1,6 @@ +! +interface r5-eth0 + ip address 192.168.5.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/r6/bgpd.conf b/tests/topotests/bgp_roles_capability/r6/bgpd.conf new file mode 100644 index 0000000..44b12fe --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r6/bgpd.conf @@ -0,0 +1,4 @@ +router bgp 64506 + neighbor 192.168.6.1 remote-as external + neighbor 192.168.6.1 local-role customer + neighbor 192.168.6.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_capability/r6/zebra.conf b/tests/topotests/bgp_roles_capability/r6/zebra.conf new file mode 100644 index 0000000..c8428c4 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/r6/zebra.conf @@ -0,0 +1,6 @@ +! +interface r6-eth0 + ip address 192.168.6.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_capability/roles_capability_stand.dot b/tests/topotests/bgp_roles_capability/roles_capability_stand.dot new file mode 100644 index 0000000..c0b5c81 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/roles_capability_stand.dot @@ -0,0 +1,15 @@ +graph roles_filtering_stand { + layout="circo" + label="roles capability stand" + fontsize="20" + + r1 [label="r1 provider"]; + r2 [label="r2"]; + r3 [label="r3"]; + r4 [label="r4"]; + r5 [label="r5"]; + r1 -- r2 [headlabel="customer", taillabel="provider"]; + r1 -- r3 [headlabel="peer", taillabel="provider"]; + r1 -- r4 [headlabel="?", taillabel="provider"]; + r1 -- r5 [headlabel="?", taillabel="provider", label="strict-mode"]; +} diff --git a/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg b/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg new file mode 100644 index 0000000..b8dea2f Binary files /dev/null and b/tests/topotests/bgp_roles_capability/roles_capability_stand.jpg differ diff --git a/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py b/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py new file mode 100644 index 0000000..52fda69 --- /dev/null +++ b/tests/topotests/bgp_roles_capability/test_bgp_roles_capability.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC +# +# test_bgp_roles_capability.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by Eugene Bogomazov +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bgp_roles_capability: test bgp roles negotiation +""" + +import json +import os +import sys +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +topodef = {f"s{i}": ("r1", f"r{i}") for i in range(2, 7)} + + +@pytest.fixture(scope="module") +def tgen(request): + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_BGP, "bgpd.conf") + tgen.start_router() + yield tgen + tgen.stop_topology() + + +@pytest.fixture(autouse=True) +def skip_on_failure(tgen): + if tgen.routers_have_failure(): + pytest.skip("skipped because of previous test failure") + + +def find_neighbor_status(router, neighbor_ip): + return json.loads(router.vtysh_cmd(f"show bgp neighbors {neighbor_ip} json"))[ + neighbor_ip + ] + + +def check_role_mismatch(router, neighbor_ip): + return is_role_mismatch(find_neighbor_status(router, neighbor_ip)) + + +def is_role_mismatch(neighbor_status): + return ( + neighbor_status["bgpState"] != "Established" + and neighbor_status.get("lastErrorCodeSubcode") == "020B" # <2, 11> + and "Role Mismatch" in neighbor_status.get("lastNotificationReason", "") + ) + + +def check_session_established(router, neighbor_ip): + neighbor_status = find_neighbor_status(router, neighbor_ip) + return neighbor_status["bgpState"] == "Established" + + +def test_correct_pair(tgen): + # provider-customer pair + router = tgen.gears["r1"] + neighbor_ip = "192.168.2.2" + check_r2_established = functools.partial( + check_session_established, router, neighbor_ip + ) + success, result = topotest.run_and_expect( + check_r2_established, True, count=20, wait=3 + ) + assert success, "Session with r2 is not Established" + + neighbor_status = find_neighbor_status(router, neighbor_ip) + assert neighbor_status["localRole"] == "provider" + assert neighbor_status["remoteRole"] == "customer" + assert ( + neighbor_status["neighborCapabilities"].get("role") == "advertisedAndReceived" + ) + + +def test_role_pair_mismatch(tgen): + # provider-peer mistmatch + router = tgen.gears["r3"] + neighbor_ip = "192.168.3.1" + check_r3_mismatch = functools.partial(check_role_mismatch, router, neighbor_ip) + success, result = topotest.run_and_expect(check_r3_mismatch, True, count=20, wait=3) + assert success, "Session between r1 and r3 was not correctly closed" + + +def test_single_role_advertising(tgen): + # provider-undefined pair; we set role + router = tgen.gears["r1"] + neighbor_ip = "192.168.4.2" + check_r4_established = functools.partial( + check_session_established, router, neighbor_ip + ) + success, result = topotest.run_and_expect( + check_r4_established, True, count=20, wait=3 + ) + assert success, "Session with r4 is not Established" + + neighbor_status = find_neighbor_status(router, neighbor_ip) + assert neighbor_status["localRole"] == "provider" + assert neighbor_status["remoteRole"] == "undefined" + assert neighbor_status["neighborCapabilities"].get("role") == "advertised" + + +def test_single_role_receiving(tgen): + # provider-undefined pair; we receive role + router = tgen.gears["r4"] + neighbor_ip = "192.168.4.1" + check_r1_established = functools.partial( + check_session_established, router, neighbor_ip + ) + success, result = topotest.run_and_expect( + check_r1_established, True, count=20, wait=3 + ) + assert success, "Session with r1 is not Established" + + neighbor_status = find_neighbor_status(router, neighbor_ip) + assert neighbor_status["localRole"] == "undefined" + assert neighbor_status["remoteRole"] == "provider" + assert neighbor_status["neighborCapabilities"].get("role") == "received" + + +def test_role_strict_mode(tgen): + # provider-undefined pair with strict-mode + router = tgen.gears["r5"] + neighbor_ip = "192.168.5.1" + check_r5_mismatch = functools.partial(check_role_mismatch, router, neighbor_ip) + success, result = topotest.run_and_expect(check_r5_mismatch, True, count=20, wait=3) + assert success, "Session between r1 and r5 was not correctly closed" + + +def test_correct_pair_peer_group(tgen): + # provider-customer pair (using peer-groups) + router = tgen.gears["r1"] + neighbor_ip = "192.168.6.2" + check_r6_established = functools.partial( + check_session_established, router, neighbor_ip + ) + success, _ = topotest.run_and_expect(check_r6_established, True, count=20, wait=3) + assert success, "Session with r6 is not Established" + + neighbor_status = find_neighbor_status(router, neighbor_ip) + assert neighbor_status["localRole"] == "provider" + assert neighbor_status["remoteRole"] == "customer" + assert ( + neighbor_status["neighborCapabilities"].get("role") == "advertisedAndReceived" + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_roles_filtering/__init__.py b/tests/topotests/bgp_roles_filtering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_roles_filtering/r1/bgpd.conf b/tests/topotests/bgp_roles_filtering/r1/bgpd.conf new file mode 100644 index 0000000..99f6211 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r1/bgpd.conf @@ -0,0 +1,12 @@ +! Provider on this side +router bgp 64501 + bgp router-id 192.168.1.1 + no bgp network import-check + network 192.0.2.1/32 + neighbor 192.168.1.2 remote-as 64510 + neighbor 192.168.1.2 local-role provider + neighbor 192.168.1.2 route-map ALLOW_ALL out + neighbor 192.168.1.2 route-map ALLOW_ALL in + neighbor 192.168.1.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r1/zebra.conf b/tests/topotests/bgp_roles_filtering/r1/zebra.conf new file mode 100644 index 0000000..acf120b --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r10/bgpd.conf b/tests/topotests/bgp_roles_filtering/r10/bgpd.conf new file mode 100644 index 0000000..f60bc6e --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r10/bgpd.conf @@ -0,0 +1,21 @@ +! Customer on other side +router bgp 64510 + bgp router-id 192.168.10.1 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as 64501 + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as 64502 + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.3.1 remote-as 64503 + neighbor 192.168.3.1 timers 3 10 + neighbor 192.168.4.1 remote-as 64504 + neighbor 192.168.4.1 timers 3 10 + neighbor 192.168.5.1 remote-as 64505 + neighbor 192.168.5.1 local-role provider + neighbor 192.168.5.1 timers 3 10 + neighbor 192.168.6.1 remote-as 64506 + neighbor 192.168.6.1 local-role peer + neighbor 192.168.6.1 timers 3 10 + neighbor 192.168.7.1 remote-as 64507 + neighbor 192.168.7.1 local-role customer + neighbor 192.168.7.1 timers 3 10 diff --git a/tests/topotests/bgp_roles_filtering/r10/zebra.conf b/tests/topotests/bgp_roles_filtering/r10/zebra.conf new file mode 100644 index 0000000..f2733fe --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r10/zebra.conf @@ -0,0 +1,24 @@ +! +interface r10-eth0 + ip address 192.168.1.2/24 +! +interface r10-eth1 + ip address 192.168.2.2/24 +! +interface r10-eth2 + ip address 192.168.3.2/24 +! +interface r10-eth3 + ip address 192.168.4.2/24 +! +interface r10-eth4 + ip address 192.168.5.2/24 +! +interface r10-eth5 + ip address 192.168.6.2/24 +! +interface r10-eth6 + ip address 192.168.7.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r2/bgpd.conf b/tests/topotests/bgp_roles_filtering/r2/bgpd.conf new file mode 100644 index 0000000..b6db8c1 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r2/bgpd.conf @@ -0,0 +1,12 @@ +! With peer on this side +router bgp 64502 + bgp router-id 192.168.2.1 + no bgp network import-check + network 192.0.2.2/32 + neighbor 192.168.2.2 remote-as 64510 + neighbor 192.168.2.2 local-role peer + neighbor 192.168.2.2 route-map ALLOW_ALL out + neighbor 192.168.2.2 route-map ALLOW_ALL in + neighbor 192.168.2.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r2/zebra.conf b/tests/topotests/bgp_roles_filtering/r2/zebra.conf new file mode 100644 index 0000000..f785ea1 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r3/bgpd.conf b/tests/topotests/bgp_roles_filtering/r3/bgpd.conf new file mode 100644 index 0000000..70f10b1 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r3/bgpd.conf @@ -0,0 +1,12 @@ +! Customer role on this side +router bgp 64503 + bgp router-id 192.168.3.1 + no bgp network import-check + network 192.0.2.3/32 + neighbor 192.168.3.2 remote-as 64510 + neighbor 192.168.3.2 local-role customer + neighbor 192.168.3.2 route-map ALLOW_ALL out + neighbor 192.168.3.2 route-map ALLOW_ALL in + neighbor 192.168.3.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r3/zebra.conf b/tests/topotests/bgp_roles_filtering/r3/zebra.conf new file mode 100644 index 0000000..b347257 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 192.168.3.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r4/bgpd.conf b/tests/topotests/bgp_roles_filtering/r4/bgpd.conf new file mode 100644 index 0000000..11e324e --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r4/bgpd.conf @@ -0,0 +1,11 @@ +! Without role on this side +router bgp 64504 + bgp router-id 192.168.4.1 + no bgp network import-check + network 192.0.2.4/32 + neighbor 192.168.4.2 remote-as 64510 + neighbor 192.168.4.2 route-map ALLOW_ALL out + neighbor 192.168.4.2 route-map ALLOW_ALL in + neighbor 192.168.4.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r4/zebra.conf b/tests/topotests/bgp_roles_filtering/r4/zebra.conf new file mode 100644 index 0000000..3543c08 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.4.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r5/bgpd.conf b/tests/topotests/bgp_roles_filtering/r5/bgpd.conf new file mode 100644 index 0000000..39d2a8d --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r5/bgpd.conf @@ -0,0 +1,11 @@ +! Provider on other side +router bgp 64505 + bgp router-id 192.168.5.1 + no bgp network import-check + network 192.0.2.5/32 + neighbor 192.168.5.2 remote-as 64510 + neighbor 192.168.5.2 route-map ALLOW_ALL out + neighbor 192.168.5.2 route-map ALLOW_ALL in + neighbor 192.168.5.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r5/zebra.conf b/tests/topotests/bgp_roles_filtering/r5/zebra.conf new file mode 100644 index 0000000..4a1c273 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r5/zebra.conf @@ -0,0 +1,6 @@ +! +interface r5-eth0 + ip address 192.168.5.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r6/bgpd.conf b/tests/topotests/bgp_roles_filtering/r6/bgpd.conf new file mode 100644 index 0000000..25e5cd8 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r6/bgpd.conf @@ -0,0 +1,11 @@ +! Peer on other side +router bgp 64506 + bgp router-id 192.168.6.1 + no bgp network import-check + network 192.0.2.6/32 + neighbor 192.168.6.2 remote-as 64510 + neighbor 192.168.6.2 route-map ALLOW_ALL out + neighbor 192.168.6.2 route-map ALLOW_ALL in + neighbor 192.168.6.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r6/zebra.conf b/tests/topotests/bgp_roles_filtering/r6/zebra.conf new file mode 100644 index 0000000..3644a69 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r6/zebra.conf @@ -0,0 +1,6 @@ +! +interface r6-eth0 + ip address 192.168.6.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/r7/bgpd.conf b/tests/topotests/bgp_roles_filtering/r7/bgpd.conf new file mode 100644 index 0000000..5f5f257 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r7/bgpd.conf @@ -0,0 +1,11 @@ +! Customer on other side +router bgp 64507 + bgp router-id 192.168.7.1 + no bgp network import-check + network 192.0.2.7/32 + neighbor 192.168.7.2 remote-as 64510 + neighbor 192.168.7.2 route-map ALLOW_ALL out + neighbor 192.168.7.2 route-map ALLOW_ALL in + neighbor 192.168.7.2 timers 3 10 + +route-map ALLOW_ALL permit 999 diff --git a/tests/topotests/bgp_roles_filtering/r7/zebra.conf b/tests/topotests/bgp_roles_filtering/r7/zebra.conf new file mode 100644 index 0000000..0407a48 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/r7/zebra.conf @@ -0,0 +1,6 @@ +! +interface r7-eth0 + ip address 192.168.7.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot new file mode 100644 index 0000000..df0f685 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.dot @@ -0,0 +1,21 @@ +graph roles_filtering_stand { + layout="circo" + label="roles filtering stand" + fontsize="20" + + r1 [label="r1 192.0.2.1/32"]; + r2 [label="r2 192.0.2.2/32"]; + r3 [label="r3 192.0.2.3/32"]; + r4 [label="r4 192.0.2.4/32"]; + r5 [label="r5 192.0.2.5/32"]; + r6 [label="r6 192.0.2.6/32"]; + r7 [label="r7 192.0.2.7/32"]; + r10 [label="r10 intermediate"]; + r10 -- r1 [headlabel="provider", taillabel="?"]; + r10 -- r2 [headlabel="peer", taillabel="?"]; + r10 -- r3 [headlabel="customer", taillabel="?"]; + r10 -- r4 [headlabel="?", taillabel="?"]; + r10 -- r5 [headlabel="?", taillabel="provider"]; + r10 -- r6 [headlabel="?", taillabel="peer"]; + r10 -- r7 [headlabel="?", taillabel="customer"]; +} diff --git a/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg new file mode 100644 index 0000000..dfedcf8 Binary files /dev/null and b/tests/topotests/bgp_roles_filtering/roles_filtering_stand.jpg differ diff --git a/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py b/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py new file mode 100644 index 0000000..b371586 --- /dev/null +++ b/tests/topotests/bgp_roles_filtering/test_bgp_roles_filtering.py @@ -0,0 +1,131 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC +# +# test_bgp_roles_filtering.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by Eugene Bogomazov +# Copyright (c) 2017 by +# Network Device Education Foundation, Inc. ("NetDEF") +# + +""" +test_bgp_roles_filtering: test leaks prevention and mitigation with roles +""" + +import json +import os +import sys +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.bgp import verify_bgp_convergence_from_running_config +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +topodef = {f"s{i}": (f"r{i}", "r10") for i in range(1, 8)} + + +@pytest.fixture(scope="module") +def tgen(request): + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config(TopoRouter.RD_ZEBRA, "zebra.conf") + router.load_config(TopoRouter.RD_BGP, "bgpd.conf") + tgen.start_router() + yield tgen + tgen.stop_topology() + + +@pytest.fixture(autouse=True) +def skip_on_failure(tgen): + if tgen.routers_have_failure(): + pytest.skip("skipped because of previous test failure") + + +def test_r10_routes(tgen): + # provider-undefine pair bur strict-mode was set + def _routes_half_converged(): + routes = json.loads(tgen.gears["r10"].vtysh_cmd("show bgp ipv4 json"))["routes"] + output = sorted(routes.keys()) + expected = [ + "192.0.2.1/32", + "192.0.2.2/32", + "192.0.2.3/32", + "192.0.2.4/32", + "192.0.2.5/32", + "192.0.2.6/32", + "192.0.2.7/32", + ] + return output == expected + + success, result = topotest.run_and_expect( + _routes_half_converged, True, count=20, wait=3 + ) + assert success, "Routes did not converged" + + routes_with_otc = list() + for number in range(1, 8): + prefix = f"192.0.2.{number}/32" + route_details = json.loads( + tgen.gears["r10"].vtysh_cmd(f"show bgp ipv4 {prefix} json") + ) + if route_details["paths"][0].get("otc") is not None: + routes_with_otc.append(prefix) + assert routes_with_otc == [ + "192.0.2.1/32", + "192.0.2.2/32", + "192.0.2.6/32", + "192.0.2.7/32", + ] + + +def test_r1_routes(tgen): + routes = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp ipv4 json"))["routes"] + routes_list = sorted(routes.keys()) + assert routes_list == [ + "192.0.2.1/32", # own + "192.0.2.3/32", + "192.0.2.4/32", + "192.0.2.5/32", + ] + + +def test_r6_routes(tgen): + routes = json.loads(tgen.gears["r6"].vtysh_cmd("show bgp ipv4 json"))["routes"] + routes_list = sorted(routes.keys()) + assert routes_list == [ + "192.0.2.3/32", + "192.0.2.4/32", + "192.0.2.5/32", + "192.0.2.6/32", # own + ] + + +def test_r4_routes(tgen): + routes = json.loads(tgen.gears["r4"].vtysh_cmd("show bgp ipv4 json"))["routes"] + routes_list = sorted(routes.keys()) + assert routes_list == [ + "192.0.2.1/32", + "192.0.2.2/32", + "192.0.2.3/32", + "192.0.2.4/32", + "192.0.2.5/32", + "192.0.2.6/32", + "192.0.2.7/32", + ] + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_aggregation/bgp_aggregation.json b/tests/topotests/bgp_route_aggregation/bgp_aggregation.json new file mode 100644 index 0000000..5520f67 --- /dev/null +++ b/tests/topotests/bgp_route_aggregation/bgp_aggregation.json @@ -0,0 +1,249 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "192.168.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "192.168.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": { + } + } + }, + "r3": { + "dest_link": { + "r1": { + } + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r4": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": { + } + } + }, + "r4": { + "dest_link": { + "r2": { + } + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + } + } + }, + "r4": { + "dest_link": { + "r3": { + } + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": {} + } + }, + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4": { + } + } + }, + "r3": { + "dest_link": { + "r4": { + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_route_aggregation/test_bgp_aggregation.py b/tests/topotests/bgp_route_aggregation/test_bgp_aggregation.py new file mode 100644 index 0000000..d50d67b --- /dev/null +++ b/tests/topotests/bgp_route_aggregation/test_bgp_aggregation.py @@ -0,0 +1,1163 @@ +#!/usr/bin/python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test bgp aggregation functionality: + +1. Verify route summarisation with summary-only for redistributed as well as + locally generated routes. +2. Verify route summarisation with as-set for redistributed routes. + +""" + +import os +import sys +import time +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Import topoJson from lib, to create topology and initial configuration +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + reset_config_on_routers, + verify_rib, + create_static_routes, + check_address_types, + step, + create_route_maps, + create_prefix_lists, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_community, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() + +NETWORK_1_1 = {"ipv4": "10.1.1.0/24", "ipv6": "10:1::1:0/120"} +NETWORK_1_2 = {"ipv4": "10.1.2.0/24", "ipv6": "10:1::2:0/120"} +NETWORK_1_3 = {"ipv4": "10.1.3.0/24", "ipv6": "10:1::3:0/120"} +NETWORK_1_4 = {"ipv4": "10.1.4.0/24", "ipv6": "10:1::4:0/120"} +NETWORK_1_5 = {"ipv4": "10.1.5.0/24", "ipv6": "10:1::5:0/120"} +NETWORK_2_1 = {"ipv4": "10.1.1.100/32", "ipv6": "10:1::1:0/124"} +NETWORK_2_2 = {"ipv4": "10.1.5.0/24", "ipv6": "10:1::5:0/120"} +NETWORK_2_3 = {"ipv4": "10.1.6.0/24", "ipv6": "10:1::6:0/120"} +NETWORK_2_4 = {"ipv4": "10.1.7.0/24", "ipv6": "10:1::7:0/120"} +NETWORK_3_1 = {"ipv4": "10.1.8.0/24", "ipv6": "10:1::8:0/120"} +NETWORK_4_1 = {"ipv4": "10.2.1.0/24", "ipv6": "10:2::1:0/120"} +NEXT_HOP = {"ipv4": "Null0", "ipv6": "Null0"} +AGGREGATE_NW = {"ipv4": "10.1.0.0/20", "ipv6": "10:1::/96"} + +COMMUNITY = [ + "0:1 0:10 0:100", + "0:2 0:20 0:200", + "0:3 0:30 0:300", + "0:4 0:40 0:400", + "0:5 0:50 0:500", + "0:1 0:2 0:3 0:4 0:5 0:10 0:20 0:30 0:40 0:50 0:100 0:200 0:300 0:400 0:500", + "0:3 0:4 0:5 0:30 0:40 0:50 0:300 0:400 0:500", + "0:6 0:60 0:600", + "0:7 0:70 0:700", + "0:3 0:4 0:5 0:6 0:30 0:40 0:50 0:60 0:300 0:400 0:500 0:600", +] + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_aggregation.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + for addr_type in ADDR_TYPES: + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Tests starting +# +##################################################### + + +def test_route_summarisation_with_summary_only_p1(request): + """ + Verify route summarisation with summary-only for redistributed as well as + locally generated routes. + """ + + tgen = get_topogen() + tc_name = request.node.name + reset_config_on_routers(tgen) + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Configure static routes on router R1 and redistribute in " "BGP process.") + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [ + NETWORK_1_1[addr_type], + NETWORK_1_2[addr_type], + NETWORK_1_3[addr_type], + ], + "next_hop": NEXT_HOP[addr_type], + } + ] + } + } + input_redistribute = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + + step("Configuring {} static routes on router R1 ".format(addr_type)) + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configuring redistribute static for {} address-family on router R1 ".format( + addr_type + ) + ) + + result = create_router_bgp(tgen, topo, input_redistribute) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [ + NETWORK_1_1[addr_type], + NETWORK_1_2[addr_type], + NETWORK_1_3[addr_type], + ] + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Advertise some prefixes using network command") + step( + "Additionally advertise 10.1.4.0/24 & 10.1.5.0/24 and " + "10:1::4:0/120 & 10:1::5:0/120 from R4 to R1." + ) + + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_2_1[addr_type], + NETWORK_2_2[addr_type], + NETWORK_2_3[addr_type], + NETWORK_2_4[addr_type], + ] + } + ] + } + } + } + } + }, + "r4": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_1_4[addr_type], + NETWORK_1_5[addr_type], + ] + } + ] + } + } + } + } + }, + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that advertised prefixes using network command are being " + "advertised in BGP process" + ) + + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_2_1[addr_type], + NETWORK_2_2[addr_type], + NETWORK_2_3[addr_type], + NETWORK_2_4[addr_type], + ] + } + ] + } + } + } + } + } + } + + result = verify_rib(tgen, addr_type, "r3", input_advertise) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure aggregate-address to summarise all the advertised routes.") + + for addr_type in ADDR_TYPES: + route_aggregate = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + { + "network": AGGREGATE_NW[addr_type], + "summary": True, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that we see 1 summarised route and remaining suppressed " + "routes on advertising router R1 and only 1 summarised route on " + "receiving router R3 for both AFIs." + ) + + for addr_type in ADDR_TYPES: + input_static_agg = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + + input_static = { + "r1": { + "static_routes": [ + { + "network": [ + NETWORK_1_1[addr_type], + NETWORK_1_2[addr_type], + NETWORK_1_3[addr_type], + ] + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", input_static_agg, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, "r3", input_static, protocol="bgp", expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n " "Routes are still present \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static_agg, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for action, value in zip(["removed", "add"], [True, False]): + step( + "{} static routes as below: " + "(no) ip route 10.1.1.0/24 and (no) ip route 10.1.2.0/24" + "(no) ipv6 route 10:1::1:0/120 and (no) ip route 10:1::2:0/120".format( + action + ) + ) + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [NETWORK_1_1[addr_type], NETWORK_1_2[addr_type]], + "next_hop": NEXT_HOP[addr_type], + "delete": value, + } + ] + } + } + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that there is no impact on R3, as summarised route remains " + "intact. However suppressed routes on R1 disappear and re-appear " + "based on {} static routes.".format(action) + ) + + for addr_type in ADDR_TYPES: + input_static_1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK_1_1[addr_type], NETWORK_1_2[addr_type]]} + ] + } + } + + input_static_2 = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + + if value: + result = verify_rib( + tgen, addr_type, "r1", input_static_1, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Routes are still present \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib(tgen, addr_type, "r1", input_static_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", input_static_2, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "{} prefixes using network command as below:" + "(no) network 10.1.6.1/24 and (no) network 10.1.7.1/24" + "(no) network 10:1::6:0/120 and (no) network 10:1::7:0/120".format(action) + ) + + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_2_3[addr_type], + NETWORK_2_4[addr_type], + ], + "delete": value, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that there is no impact on R3, as summarised route remains " + "intact. However suppressed routes on R1 disappear and re-appear " + "based on {} of network command.".format(action) + ) + + for addr_type in ADDR_TYPES: + input_advertise_1 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_2_3[addr_type], + NETWORK_2_4[addr_type], + ] + } + ] + } + } + } + } + } + } + + input_advertise_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + {"network": AGGREGATE_NW[addr_type]} + ] + } + } + } + } + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, "r1", input_advertise_1, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Routes are still present \n Error: {}".format(tc_name, result) + ) + else: + result = verify_bgp_rib(tgen, addr_type, "r1", input_advertise_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", input_advertise_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Add a new network each one from out of aggregation range and " + "other within aggregation range. " + ) + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + {"network": NETWORK_3_1[addr_type], "next_hop": NEXT_HOP[addr_type]} + ] + } + } + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_advertise = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": NETWORK_4_1[addr_type], + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_advertise) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when a network within aggregation range is added, " + "there is no impact on receiving router. However if a network " + "outside aggregation range is added/removed, R3 receives and " + "withdraws it accordingly." + ) + + for addr_type in ADDR_TYPES: + input_static = {"r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]}} + + result = verify_rib(tgen, addr_type, "r3", input_static, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_advertise_2 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "advertise_networks": [ + { + "network": [ + NETWORK_4_1[addr_type], + AGGREGATE_NW[addr_type], + ] + } + ] + } + } + } + } + } + } + + result = verify_rib(tgen, addr_type, "r3", input_advertise_2, protocol="bgp") + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for action, value in zip(["Delete", "Re-add"], [True, False]): + step("{} aggregation command from R1.".format(action)) + + for addr_type in ADDR_TYPES: + route_aggregate = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + { + "network": AGGREGATE_NW[addr_type], + "summary": True, + "delete": value, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on both routers that summarised route is withdrawn from R1 " + "and R3 when aggregate-address command is removed and appears again " + "when aggregate-address command is re-added. Check for both AFIs." + ) + + for addr_type in ADDR_TYPES: + input_static_agg = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + + if value: + result = verify_rib( + tgen, addr_type, "r1", input_static_agg, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Aggregated route is still present \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, "r3", input_static_agg, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Aggregated route is still present \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib(tgen, addr_type, "r1", input_static_agg) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", input_static_agg) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_route_summarisation_with_as_set_p1(request): + """ + Verify route summarisation with as-set for redistributed routes. + """ + + tgen = get_topogen() + tc_name = request.node.name + reset_config_on_routers(tgen) + write_test_header(tc_name) + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Configure static routes on router R1 and redistribute in " "BGP process.") + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [ + NETWORK_1_1[addr_type], + NETWORK_1_2[addr_type], + NETWORK_1_3[addr_type], + NETWORK_1_4[addr_type], + NETWORK_1_5[addr_type], + ], + "next_hop": NEXT_HOP[addr_type], + } + ] + } + } + input_redistribute = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + } + } + } + + step("Configuring {} static routes on router R1 ".format(addr_type)) + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configuring redistribute static for {} address-family on router R1 ".format( + addr_type + ) + ) + + result = create_router_bgp(tgen, topo, input_redistribute) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that Static routes are redistributed in BGP process") + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [ + NETWORK_1_1[addr_type], + NETWORK_1_2[addr_type], + NETWORK_1_3[addr_type], + NETWORK_1_4[addr_type], + NETWORK_1_5[addr_type], + ] + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure a route-map to attach a unique community attribute value " + "to each of these prefixes, while re-distributing static." + ) + + for addr_type in ADDR_TYPES: + for ( + pfx, + seq_id, + network, + ) in zip( + [1, 2, 3, 4, 5], + [10, 20, 30, 40, 50], + [NETWORK_1_1, NETWORK_1_2, NETWORK_1_3, NETWORK_1_4, NETWORK_1_5], + ): + prefix_list = { + "r1": { + "prefix_lists": { + addr_type: { + "pf_list_{}_{}".format(addr_type, pfx): [ + { + "seqid": seq_id, + "network": network[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, prefix_list) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Create route-map for applying prefix-list on r1") + + for addr_type in ADDR_TYPES: + for pfx, comm_id in zip([1, 2, 3, 4, 5], [0, 1, 2, 3, 4]): + route_map = { + "r1": { + "route_maps": { + "rmap_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_{}_{}".format( + addr_type, pfx + ) + } + }, + "set": {"community": {"num": COMMUNITY[comm_id]}}, + } + ] + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Re-configure redistribute static with route-map") + + for addr_type in ADDR_TYPES: + input_redistribute = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_{}".format(addr_type) + }, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, input_redistribute) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure aggregate-address to summarise all the advertised routes.") + + for addr_type in ADDR_TYPES: + route_aggregate = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + {"network": AGGREGATE_NW[addr_type], "as_set": True} + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that we see summarised route on router R3 with all the " + "community attribute values combined with that aggregate route." + ) + + for addr_type in ADDR_TYPES: + input_dict = {"community": COMMUNITY[5]} + result = verify_bgp_community( + tgen, addr_type, "r3", [AGGREGATE_NW[addr_type]], input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Remove static routes as below: " + "(no) ip route 10.1.1.0/24 blackhole " + "(no) ip route 10.1.2.0/24 blackhole " + "(no) ipv6 route 10:1::1:0/120 blackhole " + "(no) ipv6 route 10:1::2:0/120 blackhole " + ) + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [NETWORK_1_1[addr_type], NETWORK_1_2[addr_type]], + "next_hop": NEXT_HOP[addr_type], + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on R3 that whenever we remove the static routes, we still" + " see aggregated route however the corresponding community attribute" + "values are withdrawn." + ) + + for addr_type in ADDR_TYPES: + input_dict = {"community": COMMUNITY[6]} + result = verify_bgp_community( + tgen, addr_type, "r3", [AGGREGATE_NW[addr_type]], input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Add/remove a new network with community value, each one from out of " + "aggregation range and other within aggregation range. " + ) + + step( + "Add a new network each one from out of aggregation range and " + "other within aggregation range. " + ) + + for addr_type in ADDR_TYPES: + input_static = { + "r1": { + "static_routes": [ + { + "network": [NETWORK_3_1[addr_type], NETWORK_4_1[addr_type]], + "next_hop": NEXT_HOP[addr_type], + } + ] + } + } + + result = create_static_routes(tgen, input_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + for ( + pfx, + seq_id, + network, + ) in zip([6, 7], [60, 70], [NETWORK_3_1, NETWORK_4_1]): + prefix_list = { + "r1": { + "prefix_lists": { + addr_type: { + "pf_list_{}_{}".format(addr_type, pfx): [ + { + "seqid": seq_id, + "network": network[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, prefix_list) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Create route-map for applying prefix-list on r1") + + for addr_type in ADDR_TYPES: + for pfx, comm_id in zip([6, 7], [7, 8]): + route_map = { + "r1": { + "route_maps": { + "rmap_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_{}_{}".format( + addr_type, pfx + ) + } + }, + "set": {"community": {"num": COMMUNITY[comm_id]}}, + } + ] + } + } + } + + result = create_route_maps(tgen, route_map) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on R3 when route is added within the summary range, aggregated" + " route also has associated community value added. However if the route" + " is beyond the summary range the aggregated route would have no impact" + ) + + for addr_type in ADDR_TYPES: + input_dict = {"community": COMMUNITY[9]} + result = verify_bgp_community( + tgen, addr_type, "r3", [AGGREGATE_NW[addr_type]], input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + for action, value in zip(["Delete", "Re-add"], [True, False]): + step("{} aggregation command from R1.".format(action)) + + for addr_type in ADDR_TYPES: + route_aggregate = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "aggregate_address": [ + { + "network": AGGREGATE_NW[addr_type], + "as_set": True, + "delete": value, + } + ] + } + } + } + } + } + } + + result = create_router_bgp(tgen, topo, route_aggregate) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when as-set command is removed, we do not see community " + "attribute added to summarised route on R3. However when as-set option " + "is re-added, all the community attribute values must appear with " + "summarised route." + ) + + for addr_type in ADDR_TYPES: + input_static_agg = { + "r1": {"static_routes": [{"network": AGGREGATE_NW[addr_type]}]} + } + + if value: + result = verify_rib( + tgen, addr_type, "r1", input_static_agg, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Aggregated route is still present \n Error: {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, "r3", input_static_agg, expected=False + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Aggregated route is still present \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib(tgen, addr_type, "r1", input_static_agg) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", input_static_agg) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict = {"community": COMMUNITY[9]} + result = verify_bgp_community( + tgen, addr_type, "r3", [AGGREGATE_NW[addr_type]], input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map/bgp_route_map_topo1.json b/tests/topotests/bgp_route_map/bgp_route_map_topo1.json new file mode 100644 index 0000000..e892639 --- /dev/null +++ b/tests/topotests/bgp_route_map/bgp_route_map_topo1.json @@ -0,0 +1,187 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base":"10.0.0.0", + "ipv4mask":30, + "ipv6base":"fd00::", + "ipv6mask":64, + "link_ip_start":{"ipv4":"10.0.0.0", "v4mask":30, "ipv6":"fd00::", "v6mask":64}, + "lo_prefix":{"ipv4":"1.0.", "v4mask":32, "ipv6":"2001:DB8:F::", "v6mask":128}, + "routers":{ + "r1":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + }, + "r2":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r1":{"ipv4":"auto", "ipv6":"auto"}, + "r2":{"ipv4":"auto", "ipv6":"auto"}, + "r4":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4":{ + "links":{ + "lo": {"ipv4": "auto", "ipv6": "auto", "type": "loopback"}, + "r3":{"ipv4":"auto", "ipv6":"auto"} + }, + "bgp":{ + "local_as":"200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_route_map/bgp_route_map_topo2.json b/tests/topotests/bgp_route_map/bgp_route_map_topo2.json new file mode 100755 index 0000000..c22a4c3 --- /dev/null +++ b/tests/topotests/bgp_route_map/bgp_route_map_topo2.json @@ -0,0 +1,316 @@ +{ + "address_types": ["ipv4", "ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [{ + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [{ + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }, + + "static_routes": [{ + "network": "10.0.20.1/32", + "no_of_ip": 2, + "next_hop": "10.0.0.2" + }, + { + "network": "1::1/128", + "no_of_ip": 2, + "next_hop": "fd00::2" + }] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + } + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {} + } + } + } + } + } + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_route_map/test_route_map_topo1.py b/tests/topotests/bgp_route_map/test_route_map_topo1.py new file mode 100644 index 0000000..e300e8d --- /dev/null +++ b/tests/topotests/bgp_route_map/test_route_map_topo1.py @@ -0,0 +1,1393 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +import sys +import time +import pytest +import os + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_route_maps, + create_static_routes, + create_prefix_lists, + check_address_types, + reset_config_on_routers, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_attributes, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +################################# +# TOPOLOGY +################################# +""" + + +-------+ + +------- | R2 | + | +-------+ + | | + +-------+ | + | R1 | | + +-------+ | + | | + | +-------+ +-------+ + +---------- | R3 |----------| R4 | + +-------+ +-------+ + +""" + +################################# +# TEST SUMMARY +################################# +""" +Following tests are covered to test route-map functionality: +TC_34: + Verify if route-maps is applied in both inbound and + outbound direction to same neighbor/interface. +TC_36: + Test permit/deny statements operation in route-maps with a + permutation and combination of permit/deny in prefix-lists +TC_35: + Test multiple sequence numbers in a single route-map for different + match/set clauses. +TC_37: + Test add/remove route-maps with multiple set + clauses and without any match statement.(Set only) +TC_38: + Test add/remove route-maps with multiple match + clauses and without any set statement.(Match only) +""" + +# Global variables +bgp_convergence = False +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() + +# Global variables +bgp_convergence = False +NETWORK = {"ipv4": ["11.0.20.1/32", "20.0.20.1/32"], "ipv6": ["1::1/128", "2::1/128"]} +MASK = {"ipv4": "32", "ipv6": "128"} +NEXT_HOP = {"ipv4": "10.0.0.2", "ipv6": "fd00::2"} +ADDR_TYPES = check_address_types() + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + global ADDR_TYPES + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_route_map_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +def test_route_map_inbound_outbound_same_neighbor_p0(request): + """ + TC_34: + Verify if route-maps is applied in both inbound and + outbound direction to same neighbor/interface. + """ + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for adt in ADDR_TYPES: + + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK[adt][0], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r4": { + "static_routes": [ + { + "network": NETWORK[adt][1], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_5 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "action": "permit", + "network": NETWORK["ipv4"][0], + } + ], + "pf_list_2_ipv4": [ + { + "seqid": 10, + "action": "permit", + "network": NETWORK["ipv4"][1], + } + ], + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 100, + "action": "permit", + "network": NETWORK["ipv6"][0], + } + ], + "pf_list_2_ipv6": [ + { + "seqid": 100, + "action": "permit", + "network": NETWORK["ipv6"][1], + } + ], + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_6 = { + "r3": { + "route_maps": { + "rmap_match_tag_1_{}".format(addr_type): [ + { + "action": "deny", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + } + ], + "rmap_match_tag_2_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_2_{}".format(addr_type) + } + }, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv4", + "direction": "in", + }, + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + }, + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv6", + "direction": "in", + }, + { + "name": "rmap_match_tag_1_ipv6", + "direction": "out", + }, + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for adt in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict_2 = { + "r4": { + "static_routes": [ + { + "network": [NETWORK[adt][1]], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + + result = verify_rib( + tgen, adt, dut, input_dict_2, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} BGP RIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + dut = "r4" + input_dict = { + "r1": { + "static_routes": [ + { + "network": [NETWORK[adt][0]], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + result = verify_rib( + tgen, adt, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize( + "prefix_action, rmap_action", + [("permit", "permit"), ("permit", "deny"), ("deny", "permit"), ("deny", "deny")], +) +def test_route_map_with_action_values_combination_of_prefix_action_p0( + request, prefix_action, rmap_action +): + """ + TC_36: + Test permit/deny statements operation in route-maps with a permutation and + combination of permit/deny in prefix-lists + """ + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for adt in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK[adt][0], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Permit in perfix list and route-map + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": prefix_action} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": prefix_action} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": rmap_action, + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r3" + protocol = "bgp" + input_dict_2 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK[adt][0]], + "no_of_ip": 9, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + + # tgen.mininet_cli() + if "deny" in [prefix_action, rmap_action]: + result = verify_rib( + tgen, adt, dut, input_dict_2, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + else: + result = verify_rib(tgen, adt, dut, input_dict_2, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +def test_route_map_multiple_seq_different_match_set_clause_p0(request): + """ + TC_35: + Test multiple sequence numbers in a single route-map for different + match/set clauses. + """ + + tgen = get_topogen() + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for adt in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK[adt][0], + "no_of_ip": 1, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_2_{}".format(addr_type) + } + }, + "set": {"path": {"as_num": 500}}, + }, + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_2_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + }, + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for adt in ADDR_TYPES: + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = { + "r3": { + "route_maps": { + "rmap_match_pf_list1": [ + { + "set": { + "metric": 50, + } + } + ], + } + } + } + + static_routes = [NETWORK[adt][0]] + + time.sleep(2) + result = verify_bgp_attributes( + tgen, adt, dut, static_routes, "rmap_match_pf_list1", input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r4" + result = verify_bgp_attributes( + tgen, adt, dut, static_routes, "rmap_match_pf_list1", input_dict + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Testcase " + tc_name + " :Passed \n") + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_route_map_set_only_no_match_p0(request): + """ + TC_37: + Test add/remove route-maps with multiple set + clauses and without any match statement.(Set only) + """ + + tgen = get_topogen() + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for adt in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK[adt][0], + "no_of_ip": 1, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1": [ + { + "action": "permit", + "set": {"metric": 50, "locPrf": 150, "weight": 4000}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + time.sleep(2) + for adt in ADDR_TYPES: + input_dict_4 = { + "r3": { + "route_maps": { + "rmap_match_pf_1": [ + { + "action": "permit", + "set": { + "metric": 50, + }, + } + ] + } + } + } + # Verifying RIB routes + static_routes = [NETWORK[adt][0]] + result = verify_bgp_attributes( + tgen, adt, "r3", static_routes, "rmap_match_pf_1", input_dict_3 + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_attributes( + tgen, adt, "r4", static_routes, "rmap_match_pf_1", input_dict_4 + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Testcase " + tc_name + " :Passed \n") + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_route_map_match_only_no_set_p0(request): + """ + TC_38: + Test add/remove route-maps with multiple match + clauses and without any set statement.(Match only) + """ + + tgen = get_topogen() + # test case name + tc_name = request.node.name + write_test_header(tc_name) + + # Creating configuration from JSON + reset_config_on_routers(tgen) + + for adt in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + { + "network": NETWORK[adt][0], + "no_of_ip": 1, + "next_hop": NEXT_HOP[adt], + } + ] + } + } + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create ip prefix list + input_dict_2 = { + "r1": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r1": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "metric": 50, + "locPrf": 150, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create ip prefix list + input_dict_5 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_6 = { + "r3": { + "route_maps": { + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_7 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_7) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for adt in ADDR_TYPES: + # Verifying RIB routes + static_routes = [NETWORK[adt][0]] + result = verify_bgp_attributes( + tgen, adt, "r3", static_routes, "rmap_match_pf_1", input_dict_3 + ) + assert result is True, "Test case {} : Failed \n Error: {}".format( + tc_name, result + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map/test_route_map_topo2.py b/tests/topotests/bgp_route_map/test_route_map_topo2.py new file mode 100644 index 0000000..95906ec --- /dev/null +++ b/tests/topotests/bgp_route_map/test_route_map_topo2.py @@ -0,0 +1,3958 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +"""Following tests are covered to test route-map functionality. +TC_57: + Create route map to match prefix-list and permit inbound + and outbound prefixes and set criteria on match +TC_52: + Test modify set/match clauses in a route-map to see + if it takes immediate effect. +TC_61: + Delete the route maps. +TC_50_1: + Test modify/remove prefix-lists referenced by a + route-map for match statement. +TC_50_2: + Remove prefix-list referencec by route-map match cluase + and verifying it reflecting as intended +TC_51: + Add and remove community-list referencec by route-map match cluase + and verifying it reflecting as intended +TC_45: + Test multiple match statements as part of a route-map"s single + sequence number. (Logical OR-ed of multiple match statements) +TC_44: + Test multiple match statements as part of a route-map"s single + sequence number. (Logical AND of multiple match statements) +TC_41: + Test add/remove route-maps to specific neighbor and see if + it takes effect as intended +TC_56: + Test clear BGP sessions and interface flaps to see if + route-map properties are intact. +TC_46: + Verify if a blank sequence number can be create(without any + match/set clause) and check if it allows all the traffic/prefixes +TC_48: + Create route map setting local preference and weight to eBGP peeer + and metric to ibgp peer and verifying it should not get advertised +TC_43: + Test multiple set statements as part of a route-map"s + single sequence number. +TC_54: + Verify route-maps continue clause functionality. +TC_55: + Verify route-maps goto clause functionality. +TC_53: + Verify route-maps call clause functionality. +TC_58: + Create route map deny inbound and outbound prefixes on + match prefix list and set criteria on match +TC_59: + Create route map to permit inbound prefixes with filter + match tag and set criteria +TC_60 + Create route map to deny outbound prefixes with filter match tag, + and set criteria + +################################# +# TOPOLOGY +################################# + + +-------+ + +--------- | R2 | + | +-------+ + |iBGP | + +-------+ | + | R1 | |iBGP + +-------+ | + | | + | iBGP +-------+ eBGP +-------+ + +---------- | R3 |----------| R4 | + +-------+ +-------+ + | + |eBGP + | + +-------+ + | R5 | + +-------+ + + +""" + +import sys +import time +import pytest +import inspect +import os +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + create_static_routes, + verify_rib, + delete_route_maps, + create_bgp_community_lists, + create_route_maps, + create_prefix_lists, + verify_route_maps, + check_address_types, + verify_bgp_community, + shutdown_bringup_interface, + verify_prefix_lists, + reset_config_on_routers, + verify_create_community_list, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp_and_verify, + verify_bgp_attributes, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +# Global variables +bgp_convergence = False +NETWORK = {"ipv4": ["11.0.20.1/32", "11.0.20.2/32"], "ipv6": ["2::1/128", "2::2/128"]} + +bgp_convergence = False +BGP_CONVERGENCE = False +ADDR_TYPES = check_address_types() + + +def setup_module(mod): + """setup_module. + + Set up the pytest environment + * `mod`: module name + """ + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_route_map_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + global ADDR_TYPES + + # Don"t run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + logger.info("Running setup_module() done") + + +def teardown_module(mod): + """teardown_module. + + Teardown the pytest environment. + * `mod`: module name + """ + logger.info("Running teardown_module to delete topology") + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# Tests starting +##################################################### + + +def test_rmap_match_prefix_list_permit_in_and_outbound_prefixes_p0(): + """ + TC: 57 + Create route map to match prefix-list and permit inbound + and outbound prefixes and set criteria on match + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + } + ] + }, + } + } + } + + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + for addr_type in ADDR_TYPES: + # Create route map + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: {"prefix_lists": "pf_list_1_" + addr_type} + }, + "set": {"locPrf": 150, "weight": 100}, + }, + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: {"prefix_lists": "pf_list_1_" + addr_type} + }, + "set": {"metric": 50}, + }, + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + # dual stack changes + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result4 = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + # dual stack changes + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_modify_set_match_clauses_in_rmap_p0(): + """ + TC_52: + Test modify set/match clauses in a route-map to see + if it takes immediate effect. + """ + + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + } + ], + "pf_list_2_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ], + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + } + ], + "pf_list_2_ipv6": [ + {"seqid": 10, "network": "any", "action": "permit"} + ], + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + # dual stack changes + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result4 = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + # dual stack changes + for addr_type in ADDR_TYPES: + result4 = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify set/match clause of in-used route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 1000, + }, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 2000}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_delete_route_maps_p1(): + """ + TC_61: + Delete the route maps. + """ + + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_tag_1_{}".format(addr_type): [ + {"action": "deny", "match": {addr_type: {"tag": "4001"}}} + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Delete route maps + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"route_maps": ["rmap_match_tag_1_{}".format(addr_type)]}} + result = delete_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_route_maps(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_modify_prefix_list_referenced_by_rmap_p0(): + """ + TC_50_1: + Test modify/remove prefix-lists referenced by a + route-map for match statement. + """ + + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + } + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 100, + "network": "any", + "action": "permit", + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150, "weight": 100}, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Modify ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "deny"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "deny"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + sleep(5) + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_remove_prefix_list_referenced_by_rmap_p0(): + """ + TC_50_2: + Remove prefix-list referencec by route-map match cluase + and verifying it reflecting as intended + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Remove/Delete prefix list + input_dict_3 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + { + "seqid": 10, + "network": "any", + "action": "permit", + "delete": True, + } + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + { + "seqid": 100, + "network": "any", + "action": "permit", + "delete": True, + } + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + result = verify_prefix_lists(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_add_and_remove_community_list_referenced_by_rmap_p0(): + """ + TC_51: + Add and remove community-list referencec by route-map match cluase + and verifying it reflecting as intended + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Creating configuration from JSON + # build_config_from_json(tgen, topo) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_5 = { + "r1": { + "route_maps": { + "rm_r1_out_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "large_community": {"num": "1:1:1 1:2:3 2:1:1 2:2:2"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_6 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rm_r1_out_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rm_r1_out_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Create standard large commumity-list + input_dict_1 = { + "r3": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "rmap_lcomm_{}".format(addr_type), + "value": "1:1:1 1:2:3 2:1:1 2:2:2", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verify BGP large community is created + result = verify_create_community_list(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Create route map + input_dict_2 = { + "r3": { + "route_maps": { + "rm_r3_in_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "large-community-list": { + "id": "rmap_lcomm_" + addr_type + } + } + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_3 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rm_r3_in_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rm_r3_in_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_3) + + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + sleep(5) + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verify large-community-list + dut = "r3" + networks = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + input_dict_4 = {"largeCommunity": "1:1:1 1:2:3 2:1:1 2:2:2"} + for addr_type in ADDR_TYPES: + result = verify_bgp_community( + tgen, addr_type, dut, networks[addr_type], input_dict_4 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_multiple_match_statement_in_route_map_logical_ORed_p0(): + """ + TC_45: + Test multiple match statements as part of a route-map"s single + sequence number. (Logical OR-ed of multiple match statements) + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Api call to advertise networks + input_dict_nw1 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"advertise_networks": [{"network": "10.0.30.1/32"}]} + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "1::1/128"}]} + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_nw1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Api call to advertise networks + input_dict_nw2 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"advertise_networks": [{"network": "20.0.30.1/32"}]} + }, + "ipv6": { + "unicast": {"advertise_networks": [{"network": "2::1/128"}]} + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_nw2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_2_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_2_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_3_addr_type = {} + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150}, + } + ] + } + } + } + input_dict_3_addr_type[addr_type] = input_dict_3 + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 200}, + } + ] + } + } + } + input_dict_3_addr_type[addr_type] = input_dict_3 + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_6 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = {"ipv4": ["10.0.30.1/32"], "ipv6": ["1::1/128"]} + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, + addr_type, + dut, + routes[addr_type], + rmap_name, + input_dict_3_addr_type[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + routes = {"ipv4": ["20.0.30.1/32"], "ipv6": ["2::1/128"]} + for addr_type in ADDR_TYPES: + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_multiple_match_statement_in_route_map_logical_ANDed_p1(): + """ + TC_44: + Test multiple match statements as part of a route-map"s single + sequence number. (Logical AND of multiple match statements) + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_5 = { + "r1": { + "route_maps": { + "rm_r1_out_{}".format(addr_type): [ + { + "action": "permit", + "set": { + "large_community": {"num": "1:1:1 1:2:3 2:1:1 2:2:2"} + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_5) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + for addr_type in ADDR_TYPES: + input_dict_6 = { + "r1": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rm_r1_out_{}".format( + addr_type + ), + "direction": "out", + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_6) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + # Create standard large commumity-list + input_dict_1 = { + "r3": { + "bgp_community_lists": [ + { + "community_type": "standard", + "action": "permit", + "name": "rmap_lcomm_{}".format(addr_type), + "value": "1:1:1 1:2:3 2:1:1 2:2:2", + "large": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verify BGP large community is created + result = verify_create_community_list(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + # Create route map + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "large_community_list": { + "id": "rmap_lcomm_" + addr_type + } + } + }, + "set": { + "locPrf": 150, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + # Configure neighbor for route map + for addr_type in ADDR_TYPES: + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_{}".format( + addr_type + ), + "direction": "in", + } + ] + } + } + } + } + } + } + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + # sleep(10) + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_add_remove_rmap_to_specific_neighbor_p0(): + """ + TC_41: + Test add/remove route-maps to specific neighbor and see if + it takes effect as intended + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "deny"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "deny"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Remove applied rmap from neighbor + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_clear_bgp_and_flap_interface_to_verify_rmap_properties_p0(): + """ + TC_56: + Test clear BGP sessions and interface flaps to see if + route-map properties are intact. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "5", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150, "weight": 100}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # clear bgp, so config changes would be reflected + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Flap interface to see if route-map properties are intact + # Shutdown interface + dut = "r3" + intf = "r3-r1-eth0" + shutdown_bringup_interface(tgen, dut, intf, False) + + sleep(5) + + # Bringup interface + dut = "r3" + intf = "r3-r1-eth0" + shutdown_bringup_interface(tgen, dut, intf, True) + + # Verify BGP convergence once interface is up + result = verify_bgp_convergence(tgen, topo) + assert result is True, "setup_module :Failed \n Error:" " {}".format(result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_rmap_without_match_and_set_clause_p0(): + """ + TC_46: + Verify if a blank sequence number can be create(without any + match/set clause) and check if it allows all the traffic/prefixes + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_no_match_set_1_{}".format(addr_type): [ + {"action": "permit", "seq_id": "5"} + ], + "rmap_no_match_set_2_{}".format(addr_type): [ + {"action": "deny", "seq_id": "5"} + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_no_match_set_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_no_match_set_2_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_no_match_set_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_no_match_set_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_set_localpref_weight_to_ebgp_and_med_to_ibgp_peers_p0(): + """ + TC_48: + Create route map setting local preference and weight to eBGP peeer + and metric to ibgp peer and verifying it should not get advertised + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + input_dict_3_addr_type = {} + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150}, + } + ], + "rmap_match_pf_3_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"weight": 1000}, + } + ], + } + } + } + input_dict_3_addr_type[addr_type] = input_dict_3 + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv4", + "direction": "out", + } + ] + } + } + }, + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_3_ipv4", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + "r5": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_3_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + rmap_name = "rmap_match_pf_1" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r4" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + rmap_name = "rmap_match_pf_2" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + + result = verify_bgp_attributes( + tgen, + addr_type, + dut, + routes[addr_type], + rmap_name, + input_dict_3_addr_type[addr_type], + expected=False, + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: BGP attributes should not be set in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + dut = "r5" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + # Verifying BGP set attributes + dut = "r5" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + + rmap_name = "rmap_match_pf_3" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_3_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, + addr_type, + dut, + routes[addr_type], + rmap_name, + input_dict_3_addr_type[addr_type], + expected=False, + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: BGP attributes should not be set in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_multiple_set_on_single_sequence_in_rmap_p0(): + """ + TC_43: + Test multiple set statements as part of a route-map"s + single sequence number. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150, "weight": 100, "metric": 50}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + + rmap_name = "rmap_match_pf_1" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_route_maps_with_continue_clause_p0(): + """ + TC_54: + Verify route-maps continue clause functionality. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "10", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150}, + "continue": "30", + }, + { + "action": "permit", + "seq_id": "20", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 200}, + }, + { + "action": "permit", + "seq_id": "30", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 100}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + rmap_name = "rmap_match_pf_1" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + seq_id = {"ipv4": ["10", "30"], "ipv6": ["10", "30"]} + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, + addr_type, + dut, + routes[addr_type], + rmap_name, + input_dict_3, + seq_id[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_route_maps_with_goto_clause_p0(): + """ + TC_55: + Verify route-maps goto clause functionality. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": "10", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "goto": "30", + }, + { + "action": "permit", + "seq_id": "20", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 100}, + }, + { + "action": "permit", + "seq_id": "30", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 200}, + }, + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + # tgen.mininet_cli() + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + rmap_name = "rmap_match_pf_1" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + seq_id = {"ipv4": ["10", "30"], "ipv6": ["10", "30"]} + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, + addr_type, + dut, + routes[addr_type], + rmap_name, + input_dict_3, + seq_id[addr_type], + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_route_maps_with_call_clause_p0(): + """ + TC_53: + Verify route-maps call clause functionality. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"locPrf": 150}, + "call": "rmap_match_pf_2_{}".format(addr_type), + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 200}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv6", + "direction": "in", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Verifying BGP set attributes + dut = "r3" + routes = { + "ipv4": ["10.0.20.1/32", "10.0.20.2/32"], + "ipv6": ["1::1/128", "1::2/128"], + } + rmap_name = "rmap_match_pf_1" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_1_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + rmap_name = "rmap_match_pf_2" + for addr_type in ADDR_TYPES: + rmap_name = "rmap_match_pf_2_{}".format(addr_type) + result = verify_bgp_attributes( + tgen, addr_type, dut, routes[addr_type], rmap_name, input_dict_3 + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_create_rmap_match_prefix_list_to_deny_in_and_outbound_prefixes_p0(): + """ + TC_58: + Create route map deny inbound and outbound prefixes on + match prefix list and set criteria on match + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + # Create ip prefix list + input_dict_2 = { + "r3": { + "prefix_lists": { + "ipv4": { + "pf_list_1_ipv4": [ + {"seqid": 10, "network": "any", "action": "permit"} + ] + }, + "ipv6": { + "pf_list_1_ipv6": [ + {"seqid": 100, "network": "any", "action": "permit"} + ] + }, + } + } + } + result = create_prefix_lists(tgen, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Create route map + for addr_type in ADDR_TYPES: + input_dict_3 = { + "r3": { + "route_maps": { + "rmap_match_pf_1_{}".format(addr_type): [ + { + "action": "deny", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": { + "locPrf": 150, + }, + } + ], + "rmap_match_pf_2_{}".format(addr_type): [ + { + "action": "deny", + "match": { + addr_type: { + "prefix_lists": "pf_list_1_{}".format(addr_type) + } + }, + "set": {"metric": 50}, + } + ], + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_1_ipv4", + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r3": { + "route_maps": [ + { + "name": "rmap_match_pf_2_ipv6", + "direction": "out", + } + ] + } + } + }, + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + input_dict = topo["routers"] + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + # Verifying RIB routes + dut = "r4" + protocol = "bgp" + for addr_type in ADDR_TYPES: + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_create_rmap_to_match_tag_permit_inbound_prefixes_p0(): + """ + TC_59: + Create route map to permit inbound prefixes with filter + match tag and set criteria + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + for addr_type in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "tag": 4001} + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_3 = { + "r1": { + "route_maps": { + "rmap_match_tag_1_{}".format(addr_type): [ + {"action": "permit", "match": {addr_type: {"tag": "4001"}}} + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "tag": 4001} + ] + } + } + result = verify_rib(tgen, addr_type, dut, input_dict, protocol=protocol) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +def test_create_rmap_to_match_tag_deny_outbound_prefixes_p0(): + """ + TC_60 + Create route map to deny outbound prefixes with filter match tag, + and set criteria + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + reset_config_on_routers(tgen) + + for addr_type in ADDR_TYPES: + # Create Static routes + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "tag": 4001} + ] + } + } + + result = create_static_routes(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Api call to redistribute static routes + input_dict_1 = { + "r1": { + "bgp": { + "local_as": 100, + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"}, + {"redist_type": "connected"}, + ] + } + }, + }, + } + } + } + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Create route map + input_dict_3 = { + "r1": { + "route_maps": { + "rmap_match_tag_1_{}".format(addr_type): [ + {"action": "deny", "match": {addr_type: {"tag": "4001"}}} + ] + } + } + } + result = create_route_maps(tgen, input_dict_3) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + # Configure neighbor for route map + input_dict_4 = { + "r1": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv4", + "direction": "out", + } + ] + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1": { + "route_maps": [ + { + "name": "rmap_match_tag_1_ipv6", + "direction": "out", + } + ] + } + } + } + } + } + }, + } + } + } + } + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + for addr_type in ADDR_TYPES: + input_dict = { + "r1": { + "static_routes": [ + {"network": NETWORK[addr_type], "next_hop": "Null0", "tag": 4001} + ] + } + } + result = verify_rib( + tgen, addr_type, dut, input_dict, protocol=protocol, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} FIB \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + # Uncomment next line for debugging + # tgen.mininet_cli() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_delay_timer/__init__.py b/tests/topotests/bgp_route_map_delay_timer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf b/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf new file mode 100644 index 0000000..e5325c9 --- /dev/null +++ b/tests/topotests/bgp_route_map_delay_timer/r1/bgpd.conf @@ -0,0 +1,24 @@ +! +!debug bgp updates +!debug bgp neighbor +! +bgp route-map delay-timer 5 +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.1.2 remote-as external + address-family ipv4 unicast + network 10.10.10.1/32 + network 10.10.10.2/32 + network 10.10.10.3/32 + aggregate-address 10.10.10.0/24 summary-only + neighbor 192.168.1.2 unsuppress-map r2 + exit-address-family +! +ip prefix-list r1 seq 5 permit 10.10.10.1/32 +ip prefix-list r1 seq 10 permit 10.10.10.2/32 +! +route-map r2 permit 10 + match ip address prefix-list r1 +exit diff --git a/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf b/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_route_map_delay_timer/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf b/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf new file mode 100644 index 0000000..36653e6 --- /dev/null +++ b/tests/topotests/bgp_route_map_delay_timer/r2/bgpd.conf @@ -0,0 +1,4 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external +! diff --git a/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf b/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf new file mode 100644 index 0000000..cffe827 --- /dev/null +++ b/tests/topotests/bgp_route_map_delay_timer/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py b/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py new file mode 100644 index 0000000..f7a66fd --- /dev/null +++ b/tests/topotests/bgp_route_map_delay_timer/test_bgp_route_map_delay_timer.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" + +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_delay_timer(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge_1(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.0/24": {}, + "10.10.10.1/32": {}, + "10.10.10.2/32": {}, + "10.10.10.3/32": None, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge_1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "10.10.10.3/32 should not be advertised to r2" + + # Set route-map delay-timer to max value and remove 10.10.10.2/32. + # After this, r1 MUST do not announce updates immediately, and wait + # 600 seconds before withdrawing 10.10.10.2/32. + r2.vtysh_cmd( + """ + configure terminal + bgp route-map delay-timer 600 + no ip prefix-list r1 seq 10 permit 10.10.10.2/32 + """ + ) + + def _bgp_converge_2(): + output = json.loads( + r1.vtysh_cmd( + "show bgp ipv4 unicast neighbor 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.0/24": {}, + "10.10.10.1/32": {}, + "10.10.10.2/32": None, + "10.10.10.3/32": None, + } + } + return topotest.json_cmp(output, expected) + + # We are checking `not None` here to wait count*wait time and if we have different + # results than expected, it means good - 10.10.10.2/32 wasn't withdrawn immediately. + test_func = functools.partial(_bgp_converge_2) + _, result = topotest.run_and_expect(test_func, not None, count=60, wait=0.5) + assert ( + result is not None + ), "10.10.10.2/32 advertised, but should not be advertised to r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/__init__.py b/tests/topotests/bgp_route_map_match_ipv6_nexthop/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/bgpd.conf b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/bgpd.conf new file mode 100644 index 0000000..8b743bd --- /dev/null +++ b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/bgpd.conf @@ -0,0 +1,34 @@ +! +bgp send-extra-data zebra +! +ipv6 access-list nh1 permit 2001:db8:1::/64 +ipv6 access-list nh2 permit 2001:db8:2::/64 +ipv6 access-list nh3 permit 2001:db8:3::/64 +! +ipv6 prefix-list nh4 permit 2001:db8:5::/64 le 128 +! +router bgp 65001 + bgp router-id 10.10.10.1 + no bgp ebgp-requires-policy + neighbor 2001:db8::2 remote-as external + address-family ipv6 unicast + neighbor 2001:db8::2 activate + neighbor 2001:db8::2 route-map r2 in + exit-address-family +! +route-map r2 permit 10 + match ipv6 next-hop nh1 + set community 65002:1 +route-map r2 permit 20 + match ipv6 next-hop nh2 + set community 65002:2 +route-map r2 permit 30 + match ipv6 next-hop nh3 + set community 65002:3 +route-map r2 permit 40 + match ipv6 next-hop address 2001:db8:4::1 + set community 65002:4 +route-map r2 permit 50 + match ipv6 next-hop prefix-list nh4 + set community 65002:5 +! diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/zebra.conf b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/zebra.conf new file mode 100644 index 0000000..1d4374b --- /dev/null +++ b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ipv6 address 2001:db8::1/64 +! diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/bgpd.conf b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/bgpd.conf new file mode 100644 index 0000000..61c36d3 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/bgpd.conf @@ -0,0 +1,35 @@ +! +bgp send-extra-data zebra +! +router bgp 65002 + bgp router-id 10.10.10.2 + no bgp ebgp-requires-policy + neighbor 2001:db8::1 remote-as external + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8::1 activate + neighbor 2001:db8::1 route-map r1 out + exit-address-family +! +ipv6 prefix-list p1 permit 2001:db8:1::1/128 +ipv6 prefix-list p2 permit 2001:db8:2::1/128 +ipv6 prefix-list p3 permit 2001:db8:3::1/128 +ipv6 prefix-list p4 permit 2001:db8:4::1/128 +ipv6 prefix-list p5 permit 2001:db8:5::1/128 +! +route-map r1 permit 10 + match ipv6 address prefix-list p1 + set ipv6 next-hop global 2001:db8:1::1 +route-map r1 permit 20 + match ipv6 address prefix-list p2 + set ipv6 next-hop global 2001:db8:2::1 +route-map r1 permit 30 + match ipv6 address prefix-list p3 + set ipv6 next-hop global 2001:db8:3::1 +route-map r1 permit 40 + match ipv6 address prefix-list p4 + set ipv6 next-hop global 2001:db8:4::1 +route-map r1 permit 50 + match ipv6 address prefix-list p5 + set ipv6 next-hop global 2001:db8:5::1 +! diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/zebra.conf b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/zebra.conf new file mode 100644 index 0000000..e69345f --- /dev/null +++ b/tests/topotests/bgp_route_map_match_ipv6_nexthop/r2/zebra.conf @@ -0,0 +1,11 @@ +! +int lo + ipv6 address 2001:db8:1::1/128 + ipv6 address 2001:db8:2::1/128 + ipv6 address 2001:db8:3::1/128 + ipv6 address 2001:db8:4::1/128 + ipv6 address 2001:db8:5::1/128 +! +int r2-eth0 + ip address 2001:db8::2/64 +! diff --git a/tests/topotests/bgp_route_map_match_ipv6_nexthop/test_bgp_route_map_match_ipv6_nexthop.py b/tests/topotests/bgp_route_map_match_ipv6_nexthop/test_bgp_route_map_match_ipv6_nexthop.py new file mode 100644 index 0000000..93a514b --- /dev/null +++ b/tests/topotests/bgp_route_map_match_ipv6_nexthop/test_bgp_route_map_match_ipv6_nexthop.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if we can match BGP prefixes by next-hop which is +specified by an IPv6 Access-list, prefix-list or just an address. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_match_ipv6_next_hop_access_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ipv6 route json")) + expected = { + "2001:db8:1::1/128": [ + { + "communities": "65002:1", + } + ], + "2001:db8:2::1/128": [ + { + "communities": "65002:2", + } + ], + "2001:db8:3::1/128": [ + { + "communities": "65002:3", + } + ], + "2001:db8:4::1/128": [ + { + "communities": "65002:4", + } + ], + "2001:db8:5::1/128": [ + { + "communities": "65002:5", + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't match routes using ipv6 next-hop access-list" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_match_source_protocol/__init__.py b/tests/topotests/bgp_route_map_match_source_protocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf new file mode 100644 index 0000000..7c3efee --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf @@ -0,0 +1,32 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! +ip route 10.10.10.10/32 192.168.2.2 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 + redistribute connected + redistribute static + neighbor 192.168.1.2 route-map r2 out + neighbor 192.168.2.2 route-map r3 out + exit-address-family +! +route-map r2 permit 10 + match source-protocol static +route-map r3 permit 10 + match source-protocol connected +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf new file mode 100644 index 0000000..7213975 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf @@ -0,0 +1,10 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf new file mode 100644 index 0000000..4a1d830 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf @@ -0,0 +1,10 @@ +! +interface r3-eth0 + ip address 192.168.2.2/24 +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py new file mode 100644 index 0000000..2828796 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if r1 can announce only static routes to r2, and only connected +routes to r3 using `match source-protocol` with route-maps. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_match_source_protocol(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_advertised_routes_r2(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.10/32": { + "valid": True, + } + }, + "totalPrefixCounter": 1, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes_r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to filter routes by source-protocol for r2" + + def _bgp_check_advertised_routes_r3(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.2.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "192.168.1.0/24": { + "valid": True, + }, + "192.168.2.0/24": { + "valid": True, + }, + "172.16.255.1/32": { + "valid": True, + }, + }, + "totalPrefixCounter": 3, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes_r3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to filter routes by source-protocol for r3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_match_tag_untagged/__init__.py b/tests/topotests/bgp_route_map_match_tag_untagged/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_match_tag_untagged/r1/frr.conf b/tests/topotests/bgp_route_map_match_tag_untagged/r1/frr.conf new file mode 100644 index 0000000..13eced2 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_tag_untagged/r1/frr.conf @@ -0,0 +1,19 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + address-family ipv4 + redistribute static route-map untagged + exit-address-family +! +ip route 10.10.10.10/32 Null0 +ip route 10.10.10.20/32 Null0 tag 20 +! +route-map untagged permit 10 + match tag untagged + set tag 10 +route-map untagged permit 20 + match tag 20 + set tag untagged +exit diff --git a/tests/topotests/bgp_route_map_match_tag_untagged/test_bgp_route_map_match_tag_untagged.py b/tests/topotests/bgp_route_map_match_tag_untagged/test_bgp_route_map_match_tag_untagged.py new file mode 100644 index 0000000..7dd63fd --- /dev/null +++ b/tests/topotests/bgp_route_map_match_tag_untagged/test_bgp_route_map_match_tag_untagged.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2024 by +# Donatas Abraitis +# + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 2): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_match_tag_untagged(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_advertised_routes_r2(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show bgp ipv4 unicast detail json") + ) + expected = { + "routes": { + "10.10.10.10/32": [ + { + "tag": 10, + } + ], + "10.10.10.20/32": [ + { + "tag": None, + } + ], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes_r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Tags for static routes are not as expected" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_on_match_next/__init__.py b/tests/topotests/bgp_route_map_on_match_next/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_on_match_next/r1/bgpd.conf b/tests/topotests/bgp_route_map_on_match_next/r1/bgpd.conf new file mode 100644 index 0000000..b858fff --- /dev/null +++ b/tests/topotests/bgp_route_map_on_match_next/r1/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 65001 + no bgp network import-check + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + address-family ipv4 unicast + network 10.100.100.1/32 + exit-address-family + ! +! diff --git a/tests/topotests/bgp_route_map_on_match_next/r1/zebra.conf b/tests/topotests/bgp_route_map_on_match_next/r1/zebra.conf new file mode 100644 index 0000000..581e2e6 --- /dev/null +++ b/tests/topotests/bgp_route_map_on_match_next/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_route_map_on_match_next/r2/bgpd.conf b/tests/topotests/bgp_route_map_on_match_next/r2/bgpd.conf new file mode 100644 index 0000000..518d63d --- /dev/null +++ b/tests/topotests/bgp_route_map_on_match_next/r2/bgpd.conf @@ -0,0 +1,17 @@ +! +router bgp 65001 + neighbor 192.168.255.1 remote-as 65001 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.255.1 route-map RM in + exit-address-family + ! +! +route-map RM permit 10 + set weight 100 +exit +! +route-map RM permit 20 + set metric 20 +exit +! diff --git a/tests/topotests/bgp_route_map_on_match_next/r2/zebra.conf b/tests/topotests/bgp_route_map_on_match_next/r2/zebra.conf new file mode 100644 index 0000000..fd45c48 --- /dev/null +++ b/tests/topotests/bgp_route_map_on_match_next/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_route_map_on_match_next/test_bgp_route_map_on_match_next.py b/tests/topotests/bgp_route_map_on_match_next/test_bgp_route_map_on_match_next.py new file mode 100644 index 0000000..8fe45a3 --- /dev/null +++ b/tests/topotests/bgp_route_map_on_match_next/test_bgp_route_map_on_match_next.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_route_map_on_match_next.py +# +# Copyright (c) 2023 Rubicon Communications, LLC. +# + +""" +Test whether `on-match next` added to an existing route-map entry takes effect. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_on_match_next(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) + expected = {"192.168.255.1": {"bgpState": "Established"}} + return topotest.json_cmp(output, expected) + + def _bgp_has_routes(router, metric, weight): + output = json.loads( + router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 routes json") + ) + expected = { + "routes": {"10.100.100.1/32": [{"metric": metric, "weight": weight}]} + } + return topotest.json_cmp(output, expected) + + # Check thst session is established + test_func = functools.partial(_bgp_converge, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed bgp convergence on r2" + + # Check that metric is 0 and weight is 100 for the received prefix + test_func = functools.partial(_bgp_has_routes, router2, 0, 100) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "r2 does not receive routes with metric 0 and weight 100" + + # Update the route-map and add "on-match next" to entry 10 + cmd = """ + configure terminal + route-map RM permit 10 + on-match next + exit + """ + router2.vtysh_cmd(cmd) + + # Check that metric is 20 and weight is 100 for the received prefix + test_func = functools.partial(_bgp_has_routes, router2, 20, 100) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "r2 does not receive routes with metric 20 and weight 100" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_map_vpn_import/__init__.py b/tests/topotests/bgp_route_map_vpn_import/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf b/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf new file mode 100644 index 0000000..4aa11ec --- /dev/null +++ b/tests/topotests/bgp_route_map_vpn_import/r1/bgpd.conf @@ -0,0 +1,46 @@ +! +!debug bgp updates +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp nht +!debug route-map +! +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy +! +router bgp 65001 vrf Customer + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + rd vpn export 192.168.1.2:2 + rt vpn import 192.168.1.2:2 + rt vpn export 192.168.1.2:2 + export vpn + import vpn + exit-address-family +! +router bgp 65001 vrf Service + bgp router-id 192.168.2.2 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + rd vpn export 192.168.2.2:2 + rt vpn import 192.168.2.2:2 192.168.1.2:2 + rt vpn export 192.168.2.2:2 + route-map vpn import from-customer + export vpn + import vpn + exit-address-family +! +bgp extcommunity-list standard from-customer seq 5 permit rt 192.168.1.2:2 +! +ip prefix-list p1 seq 5 permit 192.0.2.0/24 +! +route-map from-customer permit 10 + match extcommunity from-customer + match ip address prefix-list p1 + set local-preference 123 +route-map from-customer permit 20 +! diff --git a/tests/topotests/bgp_route_map_vpn_import/r1/zebra.conf b/tests/topotests/bgp_route_map_vpn_import/r1/zebra.conf new file mode 100644 index 0000000..51966b2 --- /dev/null +++ b/tests/topotests/bgp_route_map_vpn_import/r1/zebra.conf @@ -0,0 +1,16 @@ +! +interface lo + ip address 10.10.10.10/32 +! +interface r1-eth0 vrf Customer + ip address 192.168.1.2/24 +! +interface r1-eth1 vrf Service + ip address 192.168.2.2/24 +! +interface r1-eth2 + ip address 10.0.1.1/24 +! +interface r1-eth3 vrf Customer + ip address 192.0.2.1/24 +! diff --git a/tests/topotests/bgp_route_map_vpn_import/test_bgp_route_map_vpn_import.py b/tests/topotests/bgp_route_map_vpn_import/test_bgp_route_map_vpn_import.py new file mode 100644 index 0000000..37082b4 --- /dev/null +++ b/tests/topotests/bgp_route_map_vpn_import/test_bgp_route_map_vpn_import.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if `route-map vpn import NAME` works by setting/matching via route-maps. +Routes from VRF Customer to VRF Service MUST be leaked and modified later +with `route-map vpn import`. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + r1 = tgen.gears["r1"] + + r1.run("ip link add Customer type vrf table 1001") + r1.run("ip link set up dev Customer") + r1.run("ip link set r1-eth0 master Customer") + r1.run("ip link add Service type vrf table 1002") + r1.run("ip link set up dev Service") + r1.run("ip link set r1-eth1 master Service") + r1.run("ip link set r1-eth3 master Customer") + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_vpn_import(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_check_received_leaked_with_vpn_import(): + output = json.loads(r1.vtysh_cmd("show bgp vrf Service ipv4 unicast json")) + expected = { + "routes": { + "192.0.2.0/24": [ + { + "locPrf": 123, + }, + ], + "192.168.1.0/24": [ + { + "locPrf": None, + } + ], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_received_leaked_with_vpn_import) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Failed, imported routes are not modified" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf b/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf new file mode 100644 index 0000000..1929dfa --- /dev/null +++ b/tests/topotests/bgp_route_origin_parser/pe1/bgpd.conf @@ -0,0 +1,2 @@ +router bgp 65001 + neighbor 192.168.2.1 remote-as external diff --git a/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py b/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py new file mode 100644 index 0000000..673efc2 --- /dev/null +++ b/tests/topotests/bgp_route_origin_parser/test_bgp_route_origin_parser.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-or-later +# +# April 03 2023, Trey Aspelund +# +# Copyright (C) 2023 NVIDIA Corporation +# +# Test if the CLI parser for RT/SoO ecoms correctly +# constrain user input to valid 4-byte ASN values. +# + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("pe1") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + pe1 = tgen.gears["pe1"] + pe1.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "pe1/bgpd.conf")) + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_origin_parser(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + pe1 = tgen.gears["pe1"] + + def _invalid_soo_accepted(): + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 unicast + neighbor 192.168.2.1 soo 4294967296:65 + """ + ) + run_cfg = pe1.vtysh_cmd("show run") + return "soo" in run_cfg + + def _max_soo_accepted(): + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 unicast + neighbor 192.168.2.1 soo 4294967295:65 + """ + ) + run_cfg = pe1.vtysh_cmd("show run") + return "soo 4294967295:65" in run_cfg + + def _invalid_rt_accepted(): + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 unicast + rt vpn both 4294967296:65 + """ + ) + run_cfg = pe1.vtysh_cmd("show run") + return "rt vpn" in run_cfg + + def _max_rt_accepted(): + pe1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + address-family ipv4 unicast + rt vpn both 4294967295:65 + """ + ) + run_cfg = pe1.vtysh_cmd("show run") + return "rt vpn both 4294967295:65" in run_cfg + + step( + "Configure invalid 4-byte value SoO (4294967296:65), this should not be accepted" + ) + test_func = functools.partial(_invalid_soo_accepted) + _, result = topotest.run_and_expect(test_func, False, count=30, wait=0.5) + assert result is False, "invalid 4-byte value of SoO accepted" + + step("Configure max 4-byte value SoO (4294967295:65), this should be accepted") + test_func = functools.partial(_max_soo_accepted) + _, result = topotest.run_and_expect(test_func, True, count=30, wait=0.5) + assert result is True, "max 4-byte value of SoO not accepted" + + step( + "Configure invalid 4-byte value RT (4294967296:65), this should not be accepted" + ) + test_func = functools.partial(_invalid_rt_accepted) + _, result = topotest.run_and_expect(test_func, False, count=30, wait=0.5) + assert result is False, "invalid 4-byte value of RT accepted" + + step("Configure max 4-byte value RT (4294967295:65), this should be accepted") + test_func = functools.partial(_max_rt_accepted) + _, result = topotest.run_and_expect(test_func, True, count=30, wait=0.5) + assert result is True, "max 4-byte value of RT not accepted" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_route_server_client/__init__.py b/tests/topotests/bgp_route_server_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_route_server_client/r1/bgpd.conf b/tests/topotests/bgp_route_server_client/r1/bgpd.conf new file mode 100644 index 0000000..e464e6c --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r1/bgpd.conf @@ -0,0 +1,13 @@ +! +router bgp 65001 + bgp router-id 10.10.10.1 + no bgp ebgp-requires-policy + no bgp enforce-first-as + neighbor 2001:db8:1::1 remote-as external + neighbor 2001:db8:1::1 timers 3 10 + neighbor 2001:db8:1::1 timers connect 5 + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_route_server_client/r1/zebra.conf b/tests/topotests/bgp_route_server_client/r1/zebra.conf new file mode 100644 index 0000000..08d0be6 --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r1/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ipv6 address 2001:db8:f::1/128 +! +int r1-eth0 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bgp_route_server_client/r2/bgpd.conf b/tests/topotests/bgp_route_server_client/r2/bgpd.conf new file mode 100644 index 0000000..3b0a24b --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r2/bgpd.conf @@ -0,0 +1,17 @@ +router bgp 65000 view RS + bgp router-id 10.10.10.2 + no bgp ebgp-requires-policy + neighbor 2001:db8:1::2 remote-as external + neighbor 2001:db8:1::2 timers 3 10 + neighbor 2001:db8:1::2 timers connect 5 + neighbor 2001:db8:3::2 remote-as external + neighbor 2001:db8:3::2 timers 3 10 + neighbor 2001:db8:3::2 timers connect 5 + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:1::2 activate + neighbor 2001:db8:3::2 activate + neighbor 2001:db8:1::2 route-server-client + neighbor 2001:db8:3::2 route-server-client + exit-address-family +! diff --git a/tests/topotests/bgp_route_server_client/r2/zebra.conf b/tests/topotests/bgp_route_server_client/r2/zebra.conf new file mode 100644 index 0000000..806bc4f --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r2/zebra.conf @@ -0,0 +1,7 @@ +! +int r2-eth0 + ipv6 address 2001:db8:1::1/64 +! +int r2-eth1 + ipv6 address 2001:db8:3::1/64 +! diff --git a/tests/topotests/bgp_route_server_client/r3/bgpd.conf b/tests/topotests/bgp_route_server_client/r3/bgpd.conf new file mode 100644 index 0000000..60a5ffc --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r3/bgpd.conf @@ -0,0 +1,12 @@ +! +router bgp 65003 + bgp router-id 10.10.10.3 + no bgp ebgp-requires-policy + neighbor 2001:db8:3::1 remote-as external + neighbor 2001:db8:3::1 timers 3 10 + neighbor 2001:db8:3::1 timers connect 5 + address-family ipv6 unicast + redistribute connected + neighbor 2001:db8:3::1 activate + exit-address-family +! diff --git a/tests/topotests/bgp_route_server_client/r3/zebra.conf b/tests/topotests/bgp_route_server_client/r3/zebra.conf new file mode 100644 index 0000000..b5511a4 --- /dev/null +++ b/tests/topotests/bgp_route_server_client/r3/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ipv6 address 2001:db8:f::3/128 +! +int r3-eth0 + ipv6 address 2001:db8:3::2/64 +! diff --git a/tests/topotests/bgp_route_server_client/test_bgp_route_server_client.py b/tests/topotests/bgp_route_server_client/test_bgp_route_server_client.py new file mode 100644 index 0000000..18b7831 --- /dev/null +++ b/tests/topotests/bgp_route_server_client/test_bgp_route_server_client.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2021 by +# Donatas Abraitis +# + +""" +Test if we send ONLY GUA address for route-server-client peers. +""" + +import os +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_server_client(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv6 unicast summary json")) + expected = {"peers": {"2001:db8:1::1": {"state": "Established", "pfxRcd": 2}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP sessions to be up" + + def _bgp_prefix_received(router): + output = json.loads(router.vtysh_cmd("show bgp 2001:db8:f::3/128 json")) + expected = { + "prefix": "2001:db8:f::3/128", + "paths": [{"nexthops": [{"ip": "2001:db8:3::2"}]}], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_prefix_received, r1) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP GUA next hop from r3 in r1" + + def _bgp_single_next_hop(router): + output = json.loads(router.vtysh_cmd("show bgp 2001:db8:f::3/128 json")) + return len(output["paths"][0]["nexthops"]) + + assert ( + _bgp_single_next_hop(r1) == 1 + ), "Not ONLY one Next Hop received for 2001:db8:f::3/128" + + def _bgp_gua_lla_next_hop(router): + output = json.loads(router.vtysh_cmd("show bgp view RS 2001:db8:f::3/128 json")) + expected = { + "prefix": "2001:db8:f::3/128", + "paths": [ + { + "nexthops": [ + { + "ip": "2001:db8:3::2", + "hostname": "r3", + "afi": "ipv6", + "scope": "global", + }, + {"hostname": "r3", "afi": "ipv6", "scope": "link-local"}, + ] + } + ], + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_gua_lla_next_hop, r2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Cannot see BGP LLA next hop from r3 in r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_rpki_topo1/__init__.py b/tests/topotests/bgp_rpki_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf new file mode 100644 index 0000000..437d393 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65530 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 3 + neighbor 192.0.2.2 update-source 192.0.2.1 + address-family ipv4 unicast + network 198.51.100.0/24 + network 203.0.113.0/24 + network 10.0.0.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_rpki_topo1/r1/rtrd.py b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py new file mode 100755 index 0000000..bca58a6 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/rtrd.py @@ -0,0 +1,330 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0-or-later + +# Copyright (C) 2023 Tomas Hlavacek (tmshlvck@gmail.com) + +from typing import List, Tuple, Callable, Type +import socket +import threading +import socketserver +import struct +import ipaddress +import csv +import os +import sys + +LISTEN_HOST, LISTEN_PORT = "0.0.0.0", 15432 +VRPS_FILE = os.path.join(sys.path[0], "vrps.csv") + + +def dbg(m: str): + print(m) + sys.stdout.flush() + + +class RTRDatabase(object): + def __init__(self, vrps_file: str) -> None: + self.last_serial = 0 + self.ann4 = [] + self.ann6 = [] + self.withdraw4 = [] + self.withdraw6 = [] + + with open(vrps_file, "r") as fh: + for rasn, rnet, rmaxlen, _ in csv.reader(fh): + try: + net = ipaddress.ip_network(rnet) + asn = int(rasn[2:]) + maxlen = int(rmaxlen) + if net.version == 6: + self.ann6.append((asn, str(net), maxlen)) + elif net.version == 4: + self.ann4.append((asn, str(net), maxlen)) + else: + raise ValueError(f"Unknown AFI: {net.version}") + except Exception as e: + dbg( + f"VRPS load: ignoring {str((rasn, rnet,rmaxlen))} because {str(e)}" + ) + + def get_serial(self) -> int: + return self.last_serial + + def set_serial(self, serial: int) -> None: + self.last_serial = serial + + def get_announcements4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann4 + else: + return [] + + def get_withdrawals4(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw4 + else: + return [] + + def get_announcements6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.ann6 + else: + return [] + + def get_withdrawals6(self, serial: int = 0) -> List[Tuple[int, str, int]]: + if serial > self.last_serial: + return self.withdraw6 + else: + return [] + + +class RTRConnHandler(socketserver.BaseRequestHandler): + PROTO_VERSION = 0 + + def setup(self) -> None: + self.session_id = 2345 + self.serial = 1024 + + dbg(f"New connection from: {str(self.client_address)} ") + # TODO: register for notifies + + def finish(self) -> None: + pass + # TODO: de-register + + HEADER_LEN = 8 + + def decode_header(self, buf: bytes) -> Tuple[int, int, int, int]: + # common header in all received packets + return struct.unpack("!BBHI", buf) + # reutnrs (proto_ver, pdu_type, sess_id, length) + + SERNOTIFY_TYPE = 0 + SERNOTIFY_LEN = 12 + + def send_sernotify(self, serial: int) -> None: + # serial notify PDU + dbg(f" None: + # cache response PDU + dbg(f"Serial query: {serial}") + if sess_id: + self.server.db.set_serial(serial) + else: + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals4(serial): + self.withdraw_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(serial): + self.announce_ipv6(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_withdrawals6(serial): + self.withdraw_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + RESET_TYPE = 2 + + def handle_reset(self): + dbg(">Reset") + self.session_id += 1 + self.server.db.set_serial(0) + self.send_cacheresponse() + + for asn, ipnet, maxlen in self.server.db.get_announcements4(self.serial): + self.announce_ipv4(ipnet, asn, maxlen) + + for asn, ipnet, maxlen in self.server.db.get_announcements6(self.serial): + self.announce_ipv6(ipnet, asn, maxlen) + + self.send_endofdata(self.serial) + + ERROR_TYPE = 10 + + def handle_error(self, buf: bytes): + dbg(f">Error: {str(buf)}") + self.server.shutdown() + self.server.stopped = True + raise ConnectionError("Received an RPKI error packet from FRR. Exiting") + + def handle(self): + while True: + b = self.request.recv(self.HEADER_LEN, socket.MSG_WAITALL) + if len(b) == 0: + break + proto_ver, pdu_type, sess_id, length = self.decode_header(b) + dbg( + f">Header proto_ver={proto_ver} pdu_type={pdu_type} sess_id={sess_id} length={length}" + ) + + if sess_id: + self.session_id = sess_id + + if pdu_type == self.SERIAL_QUERY_TYPE: + b = self.request.recv( + self.SERIAL_QUERY_LEN - self.HEADER_LEN, socket.MSG_WAITALL + ) + self.handle_serial_query(b, sess_id) + + elif pdu_type == self.RESET_TYPE: + self.handle_reset() + + elif pdu_type == self.ERROR_TYPE: + b = self.request.recv(length - self.HEADER_LEN, socket.MSG_WAITALL) + self.handle_error(b) + + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + def __init__( + self, bind: Tuple[str, int], handler: Type[RTRConnHandler], db: RTRDatabase + ) -> None: + super().__init__(bind, handler) + self.db = db + + +def main(): + db = RTRDatabase(VRPS_FILE) + server = ThreadedTCPServer((LISTEN_HOST, LISTEN_PORT), RTRConnHandler, db) + dbg(f"Server listening on {LISTEN_HOST} port {LISTEN_PORT}") + server.serve_forever() + + +if __name__ == "__main__": + if len(sys.argv) > 1: + f = open(sys.argv[1], "w") + sys.__stdout__ = f + sys.stdout = f + sys.__stderr__ = f + sys.stderr = f + + main() diff --git a/tests/topotests/bgp_rpki_topo1/r1/staticd.conf b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf new file mode 100644 index 0000000..7f2f057 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.1.2 diff --git a/tests/topotests/bgp_rpki_topo1/r1/vrps.csv b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv new file mode 100644 index 0000000..5a6e023 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/vrps.csv @@ -0,0 +1,3 @@ +ASN,IP Prefix,Max Length,Trust Anchor +AS65530,198.51.100.0/24,24,private +AS65530,203.0.113.0/24,24,private diff --git a/tests/topotests/bgp_rpki_topo1/r1/zebra.conf b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf new file mode 100644 index 0000000..b742b70 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r1/zebra.conf @@ -0,0 +1,6 @@ +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json new file mode 100644 index 0000000..a04e9ef --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_any.json @@ -0,0 +1,37 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json new file mode 100644 index 0000000..01e288c --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_notfound.json @@ -0,0 +1,7 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json new file mode 120000 index 0000000..2645bfa --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rmap_rpki_valid.json @@ -0,0 +1 @@ +bgp_table_rpki_valid.json \ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json new file mode 100644 index 0000000..5546d45 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_any.json @@ -0,0 +1,52 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json new file mode 100644 index 0000000..7b9a5c8 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_notfound.json @@ -0,0 +1,22 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "10.0.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.0.0.0", + "prefixLen": 24, + "network": "10.0.0.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json new file mode 100644 index 0000000..eb3852a --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgp_table_rpki_valid.json @@ -0,0 +1,35 @@ +{ + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65002, + "routes": { + "198.51.100.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "198.51.100.0", + "prefixLen": 24, + "network": "198.51.100.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ], + "203.0.113.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "203.0.113.0", + "prefixLen": 24, + "network": "203.0.113.0/24", + "metric": 0, + "weight": 0, + "path": "65530", + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf new file mode 100644 index 0000000..4de177d --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.0.2.1 remote-as 65530 + neighbor 192.0.2.1 timers connect 1 + neighbor 192.0.2.1 ebgp-multihop 3 + neighbor 192.0.2.1 update-source 192.0.2.2 + neighbor 192.168.4.4 remote-as internal + neighbor 192.168.4.4 timers 1 3 + neighbor 192.168.4.4 timers connect 1 + address-family ipv4 unicast + neighbor 192.168.4.4 next-hop-self + exit-address-family +! +router bgp 65002 vrf vrf10 + no bgp ebgp-requires-policy + neighbor 192.0.2.3 remote-as 65530 + neighbor 192.0.2.3 timers 1 3 + neighbor 192.0.2.3 timers connect 1 + neighbor 192.0.2.3 ebgp-multihop 3 + neighbor 192.0.2.3 update-source 192.0.2.2 +! +rpki + rpki retry_interval 5 + rpki cache tcp 192.0.2.1 15432 preference 1 +exit diff --git a/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json new file mode 100644 index 0000000..fbc5cc9 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/rpki_prefix_table.json @@ -0,0 +1,18 @@ +{ + "prefixes":[ + { + "prefix":"198.51.100.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + }, + { + "prefix":"203.0.113.0", + "prefixLenMin":24, + "prefixLenMax":24, + "asn":65530 + } + ], + "ipv4PrefixCount":2, + "ipv6PrefixCount":0 +} diff --git a/tests/topotests/bgp_rpki_topo1/r2/staticd.conf b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf new file mode 100644 index 0000000..de58dde --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/staticd.conf @@ -0,0 +1,5 @@ +ip route 192.0.2.1/32 192.168.1.1 +! +vrf vrf10 + ip route 192.0.2.3/32 192.168.2.3 +! diff --git a/tests/topotests/bgp_rpki_topo1/r2/zebra.conf b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf new file mode 100644 index 0000000..785dbc6 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r2/zebra.conf @@ -0,0 +1,15 @@ +interface lo + ip address 192.0.2.2/32 +! +interface vrf10 vrf vrf10 + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 vrf vrf10 + ip address 192.168.2.2/24 +! +interface r2-eth2 + ip address 192.168.4.2/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/r3/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r3/bgpd.conf new file mode 100644 index 0000000..596dc20 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r3/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65530 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 3 + neighbor 192.0.2.2 update-source 192.0.2.3 + address-family ipv4 unicast + network 198.51.100.0/24 + network 203.0.113.0/24 + network 10.0.0.0/24 + exit-address-family +! diff --git a/tests/topotests/bgp_rpki_topo1/r3/rtrd.py b/tests/topotests/bgp_rpki_topo1/r3/rtrd.py new file mode 120000 index 0000000..1c5871a --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r3/rtrd.py @@ -0,0 +1 @@ +../r1/rtrd.py \ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r3/staticd.conf b/tests/topotests/bgp_rpki_topo1/r3/staticd.conf new file mode 100644 index 0000000..6822f7e --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r3/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.2.2 diff --git a/tests/topotests/bgp_rpki_topo1/r3/vrps.csv b/tests/topotests/bgp_rpki_topo1/r3/vrps.csv new file mode 120000 index 0000000..8daa27f --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r3/vrps.csv @@ -0,0 +1 @@ +../r1/vrps.csv \ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r3/zebra.conf b/tests/topotests/bgp_rpki_topo1/r3/zebra.conf new file mode 100644 index 0000000..0975114 --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r3/zebra.conf @@ -0,0 +1,5 @@ +interface lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 192.168.2.3/24 \ No newline at end of file diff --git a/tests/topotests/bgp_rpki_topo1/r4/bgpd.conf b/tests/topotests/bgp_rpki_topo1/r4/bgpd.conf new file mode 100644 index 0000000..80dc9ca --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r4/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.4.2 remote-as internal + neighbor 192.168.4.2 timers 1 3 + neighbor 192.168.4.2 timers connect 1 +! diff --git a/tests/topotests/bgp_rpki_topo1/r4/zebra.conf b/tests/topotests/bgp_rpki_topo1/r4/zebra.conf new file mode 100644 index 0000000..ed793ae --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/r4/zebra.conf @@ -0,0 +1,4 @@ +! +interface r4-eth0 + ip address 192.168.4.4/24 +! diff --git a/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py new file mode 100644 index 0000000..f52b28a --- /dev/null +++ b/tests/topotests/bgp_rpki_topo1/test_bgp_rpki_topo1.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + " -M bgpd_rpki" if rname == "r2" else "", + ) + + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + + tgen.gears["r2"].run("ip link set r2-eth1 master vrf10") + + tgen.start_router() + + global rtrd_process + rtrd_process = {} + + for rname in ["r1", "r3"]: + rtr_path = os.path.join(CWD, rname) + log_dir = os.path.join(tgen.logdir, rname) + log_file = os.path.join(log_dir, "rtrd.log") + + tgen.gears[rname].cmd("chmod u+x {}/rtrd.py".format(rtr_path)) + rtrd_process[rname] = tgen.gears[rname].popen( + "{}/rtrd.py {}".format(rtr_path, log_file) + ) + + +def teardown_module(mod): + tgen = get_topogen() + + for rname in ["r1", "r3"]: + logger.info("{}: sending SIGTERM to rtrd RPKI server".format(rname)) + rtrd_process[rname].kill() + + tgen.stop_topology() + + +def show_rpki_prefixes(rname, expected, vrf=None): + tgen = get_topogen() + + if vrf: + cmd = "show rpki prefix-table vrf {} json".format(vrf) + else: + cmd = "show rpki prefix-table json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + return topotest.json_cmp(output, expected) + + +def show_bgp_ipv4_table_rpki(rname, rpki_state, expected, vrf=None): + tgen = get_topogen() + + cmd = "show bgp" + if vrf: + cmd += " vrf {}".format(vrf) + cmd += " ipv4 unicast" + if rpki_state: + cmd += " rpki {}".format(rpki_state) + cmd += " json" + + output = json.loads(tgen.gears[rname].vtysh_cmd(cmd)) + + expected_nb = len(expected.get("routes")) + output_nb = len(output.get("routes", {})) + + if expected_nb != output_nb: + return {"error": "expected {} prefixes. Got {}".format(expected_nb, output_nb)} + + return topotest.json_cmp(output, expected) + + +def test_show_bgp_rpki_prefixes(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + rname = "r2" + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, rname, rpki_state, expected_json + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_no_rpki_cache(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + def _show_rpki_no_connection(rname): + output = json.loads( + tgen.gears[rname].vtysh_cmd("show rpki cache-connection json") + ) + + return output == {"error": "No connection to RPKI cache server."} + + step("Remove RPKI server from configuration") + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + no rpki cache tcp 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI connection state") + + test_func = functools.partial(_show_rpki_no_connection, rname) + _, result = topotest.run_and_expect(test_func, True, count=60, wait=0.5) + assert result, "RPKI is still connected on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_reconnect(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + step("Restore RPKI server configuration") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +rpki + rpki cache tcp 192.0.2.1 15432 preference 1 +exit +""" + ) + + step("Check RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_route_map(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + step("Apply RPKI valid route-map on neighbor") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +route-map RPKI permit 10 + match rpki valid +! +router bgp 65002 + address-family ipv4 unicast + neighbor 192.0.2.1 route-map RPKI in +""" + ) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step("Check RPKI state of prefixes in BGP table: {}".format(rpki_state)) + else: + step("Check prefixes in BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rmap_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, + rname, + rpki_state, + expected_json, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_prefixes_vrf(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + step("Configure RPKI cache server on vrf10") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +vrf vrf10 + rpki + rpki cache tcp 192.0.2.3 15432 preference 1 + exit +exit +""" + ) + + step("Check vrf10 RPKI prefix table") + + expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(show_rpki_prefixes, rname, expected_json, vrf="vrf10") + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see RPKI prefixes on {}".format(rname) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step( + "Check RPKI state of prefixes in vrf10 BGP table: {}".format(rpki_state) + ) + else: + step("Check prefixes in vrf10 BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, rname, rpki_state, expected_json, vrf="vrf10" + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_show_bgp_rpki_route_map_vrf(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ["r1", "r3"]: + logger.info("{}: checking if rtrd is running".format(rname)) + if rtrd_process[rname].poll() is not None: + pytest.skip(tgen.errors) + + step("Apply RPKI valid route-map on vrf10 neighbor") + + rname = "r2" + tgen.gears[rname].vtysh_cmd( + """ +configure +router bgp 65002 vrf vrf10 + address-family ipv4 unicast + neighbor 192.0.2.3 route-map RPKI in +""" + ) + + for rpki_state in ["valid", "notfound", None]: + if rpki_state: + step( + "Check RPKI state of prefixes in vrf10 BGP table: {}".format(rpki_state) + ) + else: + step("Check prefixes in vrf10 BGP table") + expected = open( + os.path.join( + CWD, + "{}/bgp_table_rmap_rpki_{}.json".format( + rname, rpki_state if rpki_state else "any" + ), + ) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial( + show_bgp_ipv4_table_rpki, + rname, + rpki_state, + expected_json, + vrf="vrf10", + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Unexpected prefixes RPKI state on {}".format(rname) + + +def test_bgp_ecommunity_rpki(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r4 = tgen.gears["r4"] + + # Flush all the states what was before and try sending out the prefixes + # with RPKI extended community. + r2.vtysh_cmd("clear ip bgp 192.168.4.4 soft out") + + def _bgp_check_ecommunity_rpki(community=None): + output = json.loads(r4.vtysh_cmd("show bgp ipv4 unicast 198.51.100.0/24 json")) + expected = { + "paths": [ + { + "extendedCommunity": community, + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_ecommunity_rpki, {"string": "OVS:valid"}) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Didn't receive RPKI extended community" + + r2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + address-family ipv4 unicast + no neighbor 192.168.4.4 send-community extended rpki + """ + ) + + test_func = functools.partial(_bgp_check_ecommunity_rpki) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Received RPKI extended community" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_rr_ibgp/spine1/bgpd.conf b/tests/topotests/bgp_rr_ibgp/spine1/bgpd.conf new file mode 100644 index 0000000..cece3fc --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/spine1/bgpd.conf @@ -0,0 +1,11 @@ +hostname spine1 +router bgp 99 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as internal + neighbor 192.168.2.1 timers 3 10 + neighbor 192.168.4.2 remote-as internal + neighbor 192.168.4.2 timers 3 10 + address-family ipv4 uni + redistribute connected + neighbor 192.168.2.1 route-reflector-client + neighbor 192.168.4.2 route-reflector-client diff --git a/tests/topotests/bgp_rr_ibgp/spine1/show_ip_route.json_ref b/tests/topotests/bgp_rr_ibgp/spine1/show_ip_route.json_ref new file mode 100644 index 0000000..75ce1b1 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/spine1/show_ip_route.json_ref @@ -0,0 +1,144 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.2.1", + "afi":"ipv4", + "interfaceName":"spine1-eth0", + "active":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"spine1-eth0", + "active":true + } + ] + } + ], + "192.168.3.0\/24":[ + { + "prefix":"192.168.3.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.4.2", + "afi":"ipv4", + "interfaceName":"spine1-eth1", + "active":true + } + ] + } + ], + "192.168.4.0\/24":[ + { + "prefix":"192.168.4.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"spine1-eth1", + "active":true + } + ] + } + ], + "192.168.5.1\/32":[ + { + "prefix":"192.168.5.1\/32", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.2.1", + "afi":"ipv4", + "interfaceName":"spine1-eth0", + "active":true + } + ] + } + ], + "192.168.6.2\/32":[ + { + "prefix":"192.168.6.2\/32", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.4.2", + "afi":"ipv4", + "interfaceName":"spine1-eth1", + "active":true + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/topotests/bgp_rr_ibgp/spine1/zebra.conf b/tests/topotests/bgp_rr_ibgp/spine1/zebra.conf new file mode 100644 index 0000000..ea25462 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/spine1/zebra.conf @@ -0,0 +1,9 @@ +hostname spine1 +ip forwarding +ipv6 forwarding + +int spine1-eth0 + ip addr 192.168.2.3/24 + +int spine1-eth1 + ip addr 192.168.4.3/24 diff --git a/tests/topotests/bgp_rr_ibgp/test_bgp_rr_ibgp_topo1.py b/tests/topotests/bgp_rr_ibgp/test_bgp_rr_ibgp_topo1.py new file mode 100644 index 0000000..9984aba --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/test_bgp_rr_ibgp_topo1.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_rr_ibgp_topo1.py +# +# Copyright (c) 2019 by +# Cumulus Networks, Inc. +# Donald Sharp +# + +""" +test_bgp_rr_ibgp_topo1.py: Testing IBGP with RR and no IGP + +Ensure that a basic rr topology comes up and correctly passes +routes around + +""" + +import os +import sys +import pytest +import json + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + tgen.add_router("tor1") + tgen.add_router("tor2") + tgen.add_router("spine1") + + # First switch is for a dummy interface (for local network) + # on tor1 + # 192.168.1.0/24 + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["tor1"]) + + # 192.168.2.0/24 - tor1 <-> spine1 connection + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["tor1"]) + switch.add_link(tgen.gears["spine1"]) + + # 3rd switch is for a dummy interface (for local netwokr) + # 192.168.3.0/24 - tor2 + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["tor2"]) + + # 192.168.4.0/24 - tor2 <-> spine1 connection + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["tor2"]) + switch.add_link(tgen.gears["spine1"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + "Setup topology" + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + # tgen.mininet_cli() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_converge_protocols(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sleep(5, "Waiting for BGP_RR_IBGP convergence") + + +def test_bgp_rr_ibgp_routes(): + "Test Route Reflection" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Verify BGP_RR_IBGP Status + logger.info("Verifying BGP_RR_IBGP routes") + + +def test_zebra_ipv4_routingTable(): + "Test 'show ip route'" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + failures = 0 + router_list = tgen.routers().values() + for router in router_list: + output = router.vtysh_cmd("show ip route json", isjson=True) + refTableFile = "{}/{}/show_ip_route.json_ref".format(CWD, router.name) + expected = json.loads(open(refTableFile).read()) + + assertmsg = "Zebra IPv4 Routing Table verification failed for router {}".format( + router.name + ) + assert topotest.json_cmp(output, expected) is None, assertmsg + + +def test_shutdown_check_stderr(): + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + pytest.skip("Skipping test for Stderr output and memory leaks") + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying unexpected STDERR output from daemons") + + router_list = tgen.routers().values() + for router in router_list: + router.stop() + + log = tgen.net[router.name].getStdErr("bgpd") + if log: + logger.error("BGPd StdErr Log:" + log) + log = tgen.net[router.name].getStdErr("zebra") + if log: + logger.error("Zebra StdErr Log:" + log) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) + +# +# Auxiliary Functions +# diff --git a/tests/topotests/bgp_rr_ibgp/tor1/bgpd.conf b/tests/topotests/bgp_rr_ibgp/tor1/bgpd.conf new file mode 100644 index 0000000..1b9f150 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor1/bgpd.conf @@ -0,0 +1,6 @@ +hostname tor1 +router bgp 99 + no bgp ebgp-requires-policy + neighbor 192.168.2.3 remote-as internal + neighbor 192.168.2.3 timers 3 10 + redistribute connected diff --git a/tests/topotests/bgp_rr_ibgp/tor1/show_ip_route.json_ref b/tests/topotests/bgp_rr_ibgp/tor1/show_ip_route.json_ref new file mode 100644 index 0000000..6cfa024 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor1/show_ip_route.json_ref @@ -0,0 +1,157 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"tor1-eth0", + "active":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"tor1-eth1", + "active":true + } + ] + } + ], + "192.168.3.0\/24":[ + { + "prefix":"192.168.3.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":5, + "ip":"192.168.4.2", + "afi":"ipv4", + "active":true, + "recursive":true + }, + { + "flags":3, + "fib":true, + "ip":"192.168.2.3", + "afi":"ipv4", + "interfaceName":"tor1-eth1", + "active":true + } + ] + } + ], + "192.168.4.0\/24":[ + { + "prefix":"192.168.4.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.2.3", + "afi":"ipv4", + "interfaceName":"tor1-eth1", + "active":true + } + ] + } + ], + "192.168.5.1\/32":[ + { + "prefix":"192.168.5.1\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"lo", + "active":true + } + ] + } + ], + "192.168.6.2\/32":[ + { + "prefix":"192.168.6.2\/32", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":5, + "ip":"192.168.4.2", + "afi":"ipv4", + "active":true, + "recursive":true + }, + { + "flags":3, + "fib":true, + "ip":"192.168.2.3", + "afi":"ipv4", + "interfaceName":"tor1-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_rr_ibgp/tor1/zebra.conf b/tests/topotests/bgp_rr_ibgp/tor1/zebra.conf new file mode 100644 index 0000000..25b4fcf --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor1/zebra.conf @@ -0,0 +1,12 @@ +hostname tor1 +ip forwarding +ipv6 forwarding + +int tor1-eth0 + ip addr 192.168.1.1/24 + +int tor1-eth1 + ip addr 192.168.2.1/24 + +int lo + ip addr 192.168.5.1/32 diff --git a/tests/topotests/bgp_rr_ibgp/tor2/bgpd.conf b/tests/topotests/bgp_rr_ibgp/tor2/bgpd.conf new file mode 100644 index 0000000..3bdb359 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor2/bgpd.conf @@ -0,0 +1,6 @@ +hostname tor2 +router bgp 99 + no bgp ebgp-requires-policy + neighbor 192.168.4.3 remote-as internal + neighbor 192.168.4.3 timers 3 10 + redistribute connected diff --git a/tests/topotests/bgp_rr_ibgp/tor2/show_ip_route.json_ref b/tests/topotests/bgp_rr_ibgp/tor2/show_ip_route.json_ref new file mode 100644 index 0000000..d9e9290 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor2/show_ip_route.json_ref @@ -0,0 +1,157 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":5, + "ip":"192.168.2.1", + "afi":"ipv4", + "active":true, + "recursive":true + }, + { + "flags":3, + "fib":true, + "ip":"192.168.4.3", + "afi":"ipv4", + "interfaceName":"tor2-eth1", + "active":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.4.3", + "afi":"ipv4", + "interfaceName":"tor2-eth1", + "active":true + } + ] + } + ], + "192.168.3.0\/24":[ + { + "prefix":"192.168.3.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"tor2-eth0", + "active":true + } + ] + } + ], + "192.168.4.0\/24":[ + { + "prefix":"192.168.4.0\/24", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"tor2-eth1", + "active":true + } + ] + } + ], + "192.168.5.1\/32":[ + { + "prefix":"192.168.5.1\/32", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":200, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":13, + "nexthops":[ + { + "flags":5, + "ip":"192.168.2.1", + "afi":"ipv4", + "active":true, + "recursive":true + }, + { + "flags":3, + "fib":true, + "ip":"192.168.4.3", + "afi":"ipv4", + "interfaceName":"tor2-eth1", + "active":true + } + ] + } + ], + "192.168.6.2\/32":[ + { + "prefix":"192.168.6.2\/32", + "protocol":"connected", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "interfaceName":"lo", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_rr_ibgp/tor2/zebra.conf b/tests/topotests/bgp_rr_ibgp/tor2/zebra.conf new file mode 100644 index 0000000..e1a06b1 --- /dev/null +++ b/tests/topotests/bgp_rr_ibgp/tor2/zebra.conf @@ -0,0 +1,13 @@ +hostname tor2 +ip forwarding +ipv6 forwarding + +int tor2-eth0 + ip addr 192.168.3.2/24 + +int tor2-eth1 + ip addr 192.168.4.2/24 + + +int lo + ip addr 192.168.6.2/32 diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/__init__.py b/tests/topotests/bgp_sender_as_path_loop_detection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r1/bgpd.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r1/bgpd.conf new file mode 100644 index 0000000..409be74 --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r1/bgpd.conf @@ -0,0 +1,19 @@ +! exit1 +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.255.1 route-map prepend out + redistribute connected + exit-address-family + ! +! +ip prefix-list p1 seq 5 permit 172.16.255.253/32 +! +route-map prepend permit 10 + match ip address prefix-list p1 + set as-path prepend 65003 +! +route-map prepend permit 20 +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r1/zebra.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r1/zebra.conf new file mode 100644 index 0000000..74489a0 --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r1/zebra.conf @@ -0,0 +1,10 @@ +! exit1 +interface lo + ip address 172.16.255.253/32 + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r2/bgpd.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r2/bgpd.conf new file mode 100644 index 0000000..dcb52a2 --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r2/bgpd.conf @@ -0,0 +1,10 @@ +! spine +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.2 sender-as-path-loop-detection + neighbor 192.168.254.2 remote-as 65003 + neighbor 192.168.254.2 timers 3 10 + neighbor 192.168.254.2 sender-as-path-loop-detection +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r2/zebra.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r2/zebra.conf new file mode 100644 index 0000000..f0d357c --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r2/zebra.conf @@ -0,0 +1,9 @@ +! spine +interface r2-eth0 + ip address 192.168.255.1/30 +! +interface r2-eth1 + ip address 192.168.254.1/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r3/bgpd.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r3/bgpd.conf new file mode 100644 index 0000000..519273d --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r3/bgpd.conf @@ -0,0 +1,6 @@ +! exit2 +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.254.1 remote-as 65002 + neighbor 192.168.254.1 timers 3 10 +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/r3/zebra.conf b/tests/topotests/bgp_sender_as_path_loop_detection/r3/zebra.conf new file mode 100644 index 0000000..a10fe3a --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/r3/zebra.conf @@ -0,0 +1,6 @@ +! exit2 +interface r3-eth0 + ip address 192.168.254.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_sender_as_path_loop_detection/test_bgp_sender-as-path-loop-detection.py b/tests/topotests/bgp_sender_as_path_loop_detection/test_bgp_sender-as-path-loop-detection.py new file mode 100644 index 0000000..db6dbc6 --- /dev/null +++ b/tests/topotests/bgp_sender_as_path_loop_detection/test_bgp_sender-as-path-loop-detection.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_sender-as-path-loop-detection.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Donatas Abraitis +# + +""" +Test if neighbor sender-as-path-loop-detection +command works as expeced. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_sender_as_path_loop_detection(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_converge(): + output = json.loads(r2.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 3}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_has_route_from_r1(): + output = json.loads(r2.vtysh_cmd("show ip bgp 172.16.255.253/32 json")) + expected = { + "paths": [ + { + "aspath": { + "segments": [{"type": "as-sequence", "list": [65001, 65003]}], + "length": 2, + } + } + ] + } + return topotest.json_cmp(output, expected) + + def _bgp_suppress_route_to_r1(): + output = json.loads( + r2.vtysh_cmd("show ip bgp neighbor 192.168.255.2 advertised-routes json") + ) + expected = {"totalPrefixCounter": 0} + return topotest.json_cmp(output, expected) + + def _bgp_suppress_route_to_r3(): + output = json.loads( + r2.vtysh_cmd("show ip bgp neighbor 192.168.254.2 advertised-routes json") + ) + expected = {"totalPrefixCounter": 2} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed bgp to convergence" + + test_func = functools.partial(_bgp_has_route_from_r1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see a route from r1" + + test_func = functools.partial(_bgp_suppress_route_to_r3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Route 172.16.255.253/32 should not be sent to r3 from r2" + + test_func = functools.partial(_bgp_suppress_route_to_r1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Routes should not be sent to r1 from r2" + + +def test_remove_loop_detection_on_one_peer(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + def _bgp_reset_route_to_r1(): + output = json.loads( + r2.vtysh_cmd("show ip bgp neighbor 192.168.255.2 advertised-routes json") + ) + expected = {"totalPrefixCounter": 3} + return topotest.json_cmp(output, expected) + + r2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + no neighbor 192.168.255.2 sender-as-path-loop-detection + """ + ) + + r2.vtysh_cmd( + """ + clear bgp * + """ + ) + test_func = functools.partial(_bgp_reset_route_to_r1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed bgp to reset route" + + +def test_loop_detection_on_peer_group(): + tgen = get_topogen() + + r2 = tgen.gears["r2"] + + def _bgp_suppress_route_to_r1(): + output = json.loads( + r2.vtysh_cmd("show ip bgp neighbor 192.168.255.2 advertised-routes json") + ) + expected = {"totalPrefixCounter": 0} + return topotest.json_cmp(output, expected) + + def _bgp_suppress_route_to_r3(): + output = json.loads( + r2.vtysh_cmd("show ip bgp neighbor 192.168.254.2 advertised-routes json") + ) + expected = {"totalPrefixCounter": 2} + return topotest.json_cmp(output, expected) + + r2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + neighbor loop_group peer-group + neighbor 192.168.255.2 peer-group loop_group + neighbor loop_group sender-as-path-loop-detection + """ + ) + + r2.vtysh_cmd( + """ + clear bgp * + """ + ) + + test_func = functools.partial(_bgp_suppress_route_to_r3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Route 172.16.255.253/32 should not be sent to r3 from r2" + + test_func = functools.partial(_bgp_suppress_route_to_r1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Routes should not be sent to r1 from r2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_set_aspath_exclude/__init__.py b/tests/topotests/bgp_set_aspath_exclude/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf new file mode 100644 index 0000000..c70b493 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r1/bgpd.conf @@ -0,0 +1,26 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.1.2 route-map r2 in + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.31/32 +ip prefix-list p2 seq 5 permit 172.16.255.32/32 +ip prefix-list p3 seq 5 permit 172.16.255.30/32 +! +bgp as-path access-list FIRST permit ^65 +bgp as-path access-list SECOND permit 2$ + +route-map r2 permit 6 + match ip address prefix-list p2 + set as-path exclude as-path-access-list SECOND +route-map r2 permit 10 + match ip address prefix-list p1 + set as-path exclude 65003 +route-map r2 permit 20 + match ip address prefix-list p3 + set as-path exclude all +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf new file mode 100644 index 0000000..acf120b --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf new file mode 100644 index 0000000..23367f9 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r2/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf new file mode 100644 index 0000000..f229954 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf b/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf new file mode 100644 index 0000000..b7a7ced --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r3/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf b/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf new file mode 100644 index 0000000..5689315 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/r3/zebra.conf @@ -0,0 +1,11 @@ +! +int lo + ip address 172.16.255.30/32 + ip address 172.16.255.31/32 + ip address 172.16.255.32/32 +! +interface r3-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py b/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py new file mode 100644 index 0000000..85e7b96 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_exclude/test_bgp_set_aspath_exclude.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_set_aspath_exclude.py +# +# Copyright 2023 by 6WIND S.A. +# + +""" +Test if `set as-path exclude` is working correctly for route-maps. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +expected_1 = { + "routes": { + "172.16.255.30/32": [{"path": ""}], + "172.16.255.31/32": [{"path": "65002"}], + "172.16.255.32/32": [{"path": "65003"}], + } +} + +expected_2 = { + "routes": { + "172.16.255.30/32": [{"path": ""}], + "172.16.255.31/32": [{"path": "65002"}], + "172.16.255.32/32": [{"path": ""}], + } +} + +expected_3 = { + "routes": { + "172.16.255.30/32": [{"path": ""}], + "172.16.255.31/32": [{"path": "65002"}], + "172.16.255.32/32": [{"path": "65002 65003"}], + } +} + +expected_4 = { + "routes": { + "172.16.255.30/32": [{"path": ""}], + "172.16.255.31/32": [{"path": "65002"}], + "172.16.255.32/32": [{"path": "65002"}], + } +} + + +def bgp_converge(router, expected): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + + return topotest.json_cmp(output, expected) + + +def test_bgp_set_aspath_exclude(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with route-map" + + +def test_bgp_set_aspath_exclude_access_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + rname = "r1" + r1 = tgen.gears[rname] + # tgen.mininet_cli() + + r1.vtysh_cmd( + """ +conf + bgp as-path access-list FIRST permit ^65 + route-map r2 permit 6 + no set as-path exclude as-path-access-list SECOND + set as-path exclude as-path-access-list FIRST + """ + ) + # tgen.mininet_cli() + r1.vtysh_cmd( + """ +clear bgp * + """ + ) + + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed change of exclude rule in route map" + r1.vtysh_cmd( + """ +conf + route-map r2 permit 6 + no set as-path exclude as-path-access-list FIRST + set as-path exclude as-path-access-list SECOND + """ + ) + + # tgen.mininet_cli() + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_1) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed reverting exclude rule in route map" + + +def test_no_bgp_set_aspath_exclude_access_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + rname = "r1" + r1 = tgen.gears[rname] + + r1.vtysh_cmd( + """ +conf + no bgp as-path access-list SECOND permit 2$ + """ + ) + + r1.vtysh_cmd( + """ +clear bgp * + """ + ) + + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed to removing current accesslist" + + # tgen.mininet_cli() + r1.vtysh_cmd( + """ +conf + bgp as-path access-list SECOND permit 3$ + """ + ) + r1.vtysh_cmd( + """ +clear bgp * + """ + ) + + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_4) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed to renegotiate with peers 2" + + r1.vtysh_cmd( + """ +conf + route-map r2 permit 6 + no set as-path exclude as-path-access-list SECOND + """ + ) + + r1.vtysh_cmd( + """ +clear bgp * + """ + ) + + test_func = functools.partial(bgp_converge, tgen.gears["r1"], expected_3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed to renegotiate with peers 2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_set_aspath_replace/__init__.py b/tests/topotests/bgp_set_aspath_replace/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf b/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf new file mode 100644 index 0000000..f586c1f --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r1/bgpd.conf @@ -0,0 +1,18 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + address-family ipv4 unicast + neighbor 192.168.1.2 route-map r2 in + exit-address-family +! +ip prefix-list p1 seq 5 permit 172.16.255.31/32 +! +bgp route-map delay-timer 1 +route-map r2 permit 10 + match ip address prefix-list p1 + set as-path replace 65003 +route-map r2 permit 20 + set as-path replace any +! diff --git a/tests/topotests/bgp_set_aspath_replace/r1/zebra.conf b/tests/topotests/bgp_set_aspath_replace/r1/zebra.conf new file mode 100644 index 0000000..acf120b --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_replace/r2/bgpd.conf b/tests/topotests/bgp_set_aspath_replace/r2/bgpd.conf new file mode 100644 index 0000000..23367f9 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r2/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 +! diff --git a/tests/topotests/bgp_set_aspath_replace/r2/zebra.conf b/tests/topotests/bgp_set_aspath_replace/r2/zebra.conf new file mode 100644 index 0000000..f229954 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_replace/r3/bgpd.conf b/tests/topotests/bgp_set_aspath_replace/r3/bgpd.conf new file mode 100644 index 0000000..b7a7ced --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r3/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_set_aspath_replace/r3/zebra.conf b/tests/topotests/bgp_set_aspath_replace/r3/zebra.conf new file mode 100644 index 0000000..3fa6c64 --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/r3/zebra.conf @@ -0,0 +1,10 @@ +! +int lo + ip address 172.16.255.31/32 + ip address 172.16.255.32/32 +! +interface r3-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py b/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py new file mode 100644 index 0000000..c0e19fa --- /dev/null +++ b/tests/topotests/bgp_set_aspath_replace/test_bgp_set_aspath_replace.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_set_aspath_replace.py +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if `set as-path replace` is working correctly for route-maps. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_set_aspath_replace_test1(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65001"}], + "172.16.255.32/32": [{"path": "65001 65001"}], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with route-map" + + +def test_bgp_set_aspath_replace_test2(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Configuring r1 to replace the matching AS with a configured ASN") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nroute-map r2 permit 10\nset as-path replace 65003 65500\n", + isjson=False, + ) + router.vtysh_cmd( + "configure terminal\nroute-map r2 permit 20\nset as-path replace any 65501\n", + isjson=False, + ) + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65500"}], + "172.16.255.32/32": [{"path": "65501 65501"}], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert ( + result is None + ), "Failed overriding incoming AS-PATH with route-map replace with configured ASN" + + +def test_bgp_set_aspath_replace_access_list(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + rname = "r1" + r1 = tgen.gears[rname] + + r1.vtysh_cmd( + """ +conf + bgp as-path access-list FIRST permit ^65 + route-map r2 permit 20 + set as-path replace as-path-access-list FIRST 65002 + """ + ) + + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65500"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + def _bgp_regexp_1(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 1 route-map" + r1.vtysh_cmd( + """ +conf + bgp as-path access-list SECOND permit 2 + route-map r2 permit 10 + set as-path replace as-path-access-list SECOND 65001 + """ + ) + + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65001 65003"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 2 route-map" + + r1.vtysh_cmd( + """ +conf + bgp as-path access-list TER permit 3 + route-map r2 permit 10 + set as-path replace as-path-access-list TER + """ + ) + expected = { + "routes": { + "172.16.255.31/32": [{"path": "65002 65001"}], + "172.16.255.32/32": [{"path": "65002 65002"}], + } + } + + test_func = functools.partial(_bgp_regexp_1, tgen.gears["r1"]) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + + assert result is None, "Failed overriding incoming AS-PATH with regex 3 route-map" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/__init__.py b/tests/topotests/bgp_set_local_preference_add_subtract/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r1/bgpd.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r1/bgpd.conf new file mode 100644 index 0000000..57e2f58 --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r1/bgpd.conf @@ -0,0 +1,8 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65000 + neighbor 192.168.255.2 timers 3 10 + neighbor 192.168.255.3 remote-as 65000 + neighbor 192.168.255.3 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r1/zebra.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r1/zebra.conf new file mode 100644 index 0000000..6e9b0b4 --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r2/bgpd.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r2/bgpd.conf new file mode 100644 index 0000000..1f85a35 --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r2/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + network 10.10.10.2/32 route-map l2 + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + address-family ipv4 + redistribute connected + neighbor 192.168.255.1 route-map r1-out out + exit-address-family +! +route-map r1-out permit 10 + set local-preference +50 +route-map l2 permit 10 + set local-preference +10 diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r2/zebra.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r2/zebra.conf new file mode 100644 index 0000000..93e3590 --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r2/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r3/bgpd.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r3/bgpd.conf new file mode 100644 index 0000000..49b8f1c --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r3/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + network 10.10.10.3/32 route-map l3 + address-family ipv4 + redistribute connected + neighbor 192.168.255.1 route-map r1-out out + exit-address-family +! +route-map r1-out permit 10 + set local-preference -50 +route-map l3 permit 10 + set local-preference -10 diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/r3/zebra.conf b/tests/topotests/bgp_set_local_preference_add_subtract/r3/zebra.conf new file mode 100644 index 0000000..b5e060c --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/r3/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.254/32 +! +interface r3-eth0 + ip address 192.168.255.3/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_set_local_preference_add_subtract/test_bgp_set_local-preference_add_subtract.py b/tests/topotests/bgp_set_local_preference_add_subtract/test_bgp_set_local-preference_add_subtract.py new file mode 100644 index 0000000..292cf70 --- /dev/null +++ b/tests/topotests/bgp_set_local_preference_add_subtract/test_bgp_set_local-preference_add_subtract.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_set_local-preference_add_subtract.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Donatas Abraitis +# + +""" +bgp_set_local-preference_add_subtract.py: +Test if we can add/subtract the value to/from an existing +LOCAL_PREF in route-maps. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_set_local_preference(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 3}}, + }, + "192.168.255.3": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 3}}, + }, + } + return topotest.json_cmp(output, expected) + + def _bgp_check_local_preference(router): + output = json.loads(router.vtysh_cmd("show bgp ipv4 unicast json")) + expected = { + "routes": { + "10.10.10.2/32": [{"locPrf": 160}], + "10.10.10.3/32": [{"locPrf": 40}], + "172.16.255.254/32": [ + {"locPrf": 50, "nexthops": [{"ip": "192.168.255.3"}]}, + {"locPrf": 150, "nexthops": [{"ip": "192.168.255.2"}]}, + ], + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge, router) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + + assert result is None, 'Failed to see BGP convergence in "{}"'.format(router) + + test_func = functools.partial(_bgp_check_local_preference, router) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + + assert result is None, 'Failed to see applied BGP local-preference in "{}"'.format( + router + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/r2/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/bgpd.conf new file mode 100644 index 0000000..a550b4c --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/bgpd.conf @@ -0,0 +1,18 @@ +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + exit-address-family +! +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/r2/snmpd.conf b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/snmpd.conf new file mode 100644 index 0000000..032b93b --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/snmpd.conf @@ -0,0 +1,17 @@ +agentAddress 127.0.0.1,[::1] + +group public_group v1 public +group public_group v2c public +access public_group "" any noauth prefix all all none + +rocommunity public default + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/r2/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/zebra.conf new file mode 100644 index 0000000..d7dfd89 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/r2/zebra.conf @@ -0,0 +1,4 @@ +! +interface r2-eth0 + ip address 192.168.12.2/24 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/rr/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2_notification/rr/bgpd.conf new file mode 100644 index 0000000..145a8f0 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/rr/bgpd.conf @@ -0,0 +1,19 @@ +! +!debug bgp updates +! +router bgp 65004 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.2 remote-as external + neighbor 192.168.12.2 timers 1 3 + neighbor 192.168.12.2 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.12.2 activate + neighbor 192.168.12.2 addpath-tx-all-paths + neighbor 192.168.12.2 route-server-client + exit-address-family +! +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/rr/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2_notification/rr/zebra.conf new file mode 100644 index 0000000..eb8a486 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/rr/zebra.conf @@ -0,0 +1,4 @@ +! +interface rr-eth0 + ip address 192.168.12.4/24 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py b/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py new file mode 100755 index 0000000..c41ef30 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2_notification/test_bgp_snmp_bgp4v2_notification.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright 2024 6WIND S.A. +# + + +""" +Test BGP OPEN NOTIFY (Configuration mismatch) followed by snmpwalk. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib import topotest + +pytestmark = [pytest.mark.bgpd, pytest.mark.snmp] + + +def build_topo(tgen): + """Build function""" + + tgen.add_router("r2") + tgen.add_router("rr") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["rr"]) + + +def setup_module(mod): + snmpd = os.system("which snmpd") + if snmpd: + error_msg = "SNMP not installed - skipping" + pytest.skip(error_msg) + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + "-M snmp", + ) + router.load_config( + TopoRouter.RD_SNMP, + os.path.join(CWD, "{}/snmpd.conf".format(rname)), + "-Le -Ivacm_conf,usmConf,iquery -V -DAgentX", + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_open_notification_change_configuration(): + tgen = get_topogen() + + tgen.gears["rr"].vtysh_multicmd( + """ +configure terminal +router bgp 65004 +neighbor 192.168.12.2 password 8888" +""" + ) + tgen.net["r2"].cmd("snmpwalk -v 2c -c public 127.0.0.1 .1.3.6.1.4.1.7336.4.2.1") + tgen.gears["rr"].vtysh_multicmd( + """ +configure terminal +router bgp 65004 +no neighbor 192.168.12.2 password 8888" +""" + ) + + def _check_bgp_session(): + r2 = tgen.gears["r2"] + + output = json.loads(r2.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": {"peers": {"192.168.12.4": {"state": "Established"}}} + } + + return topotest.json_cmp(output, expected) + + test_func1 = functools.partial(_check_bgp_session) + _, result1 = topotest.run_and_expect(test_func1, None, count=120, wait=0.5) + + assert result1 is None, "Failed to verify the bgp session" + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf new file mode 100644 index 0000000..144466e --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r1/bgpd.conf @@ -0,0 +1,34 @@ +! +!debug bgp updates +! +router bgp 65001 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 + ! + address-family ipv4 unicast + network 10.0.0.0/31 route-map p1 + network 10.0.0.2/32 route-map p2 + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + network 10.10.10.10/32 + exit-address-family + address-family ipv6 unicast + network 2001:db8::1/128 route-map p1 + network 2001:db8:1::/56 route-map p2 + neighbor 2001:db8::12:4 activate + exit-address-family +! +route-map p1 permit 10 + set metric 1 +exit +route-map p2 permit 10 + set metric 2 + set origin incomplete +exit diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r1/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r1/zebra.conf new file mode 100644 index 0000000..0807e89 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r1/zebra.conf @@ -0,0 +1,5 @@ +! +interface r1-eth0 + ip address 192.168.12.1/24 + ipv6 address 2001:db8::12:1/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf new file mode 100644 index 0000000..55686f4 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/bgpd.conf @@ -0,0 +1,25 @@ +! +!debug bgp updates +! +router bgp 65002 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + + exit-address-family + address-family ipv6 unicast + neighbor 2001:db8::12:4 activate + exit-address-family +! +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmpd.conf new file mode 100644 index 0000000..f0957cc --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmpd.conf @@ -0,0 +1,26 @@ +agentAddress 127.0.0.1,[::1] + +group public_group v1 public +group public_group v2c public +access public_group "" any noauth prefix all all none + +rocommunity public default + +trapsess -v2c -c public 127.0.0.1 + +notificationEvent linkUpTrap linkUp ifIndex ifAdminStatus ifOperStatus +notificationEvent linkDownTrap linkDown ifIndex ifAdminStatus ifOperStatus + +monitor -r 2 -e linkUpTrap "Generate linkUp" ifOperStatus != 2 +monitor -r 2 -e linkDownTrap "Generate linkDown" ifOperStatus == 2 + + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmptrapd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmptrapd.conf new file mode 100644 index 0000000..f6e4abf --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/snmptrapd.conf @@ -0,0 +1,2 @@ +authCommunity net,log public +disableAuthorization yes diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r2/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r2/zebra.conf new file mode 100644 index 0000000..db6d700 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r2/zebra.conf @@ -0,0 +1,5 @@ +! +interface r2-eth0 + ip address 192.168.12.2/24 + ipv6 address 2001:db8::12:2/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf new file mode 100644 index 0000000..71dbda0 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r3/bgpd.conf @@ -0,0 +1,25 @@ +! +!debug bgp updates +! +router bgp 65003 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.4 remote-as external + neighbor 192.168.12.4 timers 1 3 + neighbor 192.168.12.4 timers connect 1 + neighbor 2001:db8::12:4 remote-as external + neighbor 2001:db8::12:4 timers 1 3 + neighbor 2001:db8::12:4 timers connect 1 + ! + address-family ipv4 unicast + neighbor 192.168.12.4 activate + neighbor 192.168.12.4 addpath-tx-all-paths + network 10.10.10.10/32 + exit-address-family + address-family ipv6 unicast + neighbor 2001:db8::12:4 activate + exit-address-family +! +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf new file mode 100644 index 0000000..398af65 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/r3/zebra.conf @@ -0,0 +1,5 @@ +! +interface r3-eth0 + ip address 192.168.12.3/24 + ipv6 address 2001:db8::12:3/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf b/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf new file mode 100644 index 0000000..5ebbde6 --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/rr/bgpd.conf @@ -0,0 +1,67 @@ +! +! debug bgp updates +! +router bgp 65004 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + neighbor 192.168.12.1 remote-as external + neighbor 192.168.12.1 timers 1 3 + neighbor 192.168.12.1 timers connect 1 + neighbor 192.168.12.2 remote-as external + neighbor 192.168.12.2 timers 1 3 + neighbor 192.168.12.2 timers connect 1 + neighbor 192.168.12.3 remote-as external + neighbor 192.168.12.3 timers 1 3 + neighbor 192.168.12.3 timers connect 1 + neighbor 2001:db8::12:1 remote-as external + neighbor 2001:db8::12:1 timers 1 3 + neighbor 2001:db8::12:1 timers connect 1 + neighbor 2001:db8::12:2 remote-as external + neighbor 2001:db8::12:2 timers 1 3 + neighbor 2001:db8::12:2 timers connect 1 + neighbor 2001:db8::12:3 remote-as external + neighbor 2001:db8::12:3 timers 1 3 + neighbor 2001:db8::12:3 timers connect 1 + ! + address-family ipv4 unicast + network 10.0.0.0/31 route-map p1 + network 10.0.0.2/32 route-map p2 + neighbor 192.168.12.1 activate + neighbor 192.168.12.2 activate + neighbor 192.168.12.2 addpath-tx-all-paths + neighbor 192.168.12.2 route-map r2-import in + neighbor 192.168.12.2 route-map r2-export out +! neighbor 192.168.12.2 soft-reconfiguration inbound + neighbor 192.168.12.3 activate + exit-address-family + address-family ipv6 unicast + network 2001:db8::1/128 route-map p1 + network 2001:db8:1::/56 route-map p2 + neighbor 2001:db8::12:1 activate + neighbor 2001:db8::12:2 activate + neighbor 2001:db8::12:2 addpath-tx-all-paths + neighbor 2001:db8::12:3 activate + exit-address-family + + +ip prefix-list r2-toto permit any + +route-map r2-import permit 10 + match ip address prefix-list r2-toto + +route-map r2-export permit 10 + match ip address prefix-list r2-toto +! +route-map p1 permit 10 + set metric 1 +exit +route-map p2 permit 10 + set metric 2 + set origin incomplete +exit + + + +agentx +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf b/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf new file mode 100644 index 0000000..092673b --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/rr/zebra.conf @@ -0,0 +1,5 @@ +! +interface rr-eth0 + ip address 192.168.12.4/24 + ipv6 address 2001:db8::12:4/64 +! diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py new file mode 100755 index 0000000..c296aaa --- /dev/null +++ b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 Donatas Abraitis +# + +""" +Test some of the BGP4V2-MIB entries. +""" + +import os +import sys +import json +from time import sleep +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.snmptest import SnmpTester +from lib import topotest +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.snmp] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("rr") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["rr"]) + + +def setup_module(mod): + snmpd = os.system("which snmpd") + if snmpd: + error_msg = "SNMP not installed - skipping" + pytest.skip(error_msg) + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + "-M snmp", + ) + + r2 = tgen.gears["r2"] + r2.load_config( + TopoRouter.RD_SNMP, + os.path.join(CWD, "{}/snmpd.conf".format(r2.name)), + "-Le -Ivacm_conf,usmConf,iquery -V -DAgentX", + ) + r2.load_config( + TopoRouter.RD_TRAP, + os.path.join(CWD, "{}/snmptrapd.conf".format(r2.name)), + " -On -OQ ", + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_snmp_bgp4v2(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + rr = tgen.gears["rr"] + + def _bgp_converge_summary(): + output = json.loads(r2.vtysh_cmd("show bgp summary json")) + expected = { + "ipv4Unicast": { + "peers": { + "192.168.12.4": { + "state": "Established", + "pfxRcd": 6, + } + } + }, + "ipv6Unicast": { + "peers": { + "2001:db8::12:4": { + "state": "Established", + "pfxRcd": 4, + } + } + }, + } + # tgen.mininet_cli() + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge_summary) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't see connections established" + + def _bgp_converge_prefixes(): + output = json.loads(r2.vtysh_cmd("show bgp all json")) + expected = { + "ipv4Unicast": { + "routes": { + "10.0.0.0/31": [ + { + "metric": 1, + "origin": "IGP", + } + ], + "10.0.0.2/32": [ + { + "metric": 2, + "origin": "incomplete", + } + ], + } + }, + "ipv6Unicast": { + "routes": { + "2001:db8::1/128": [ + { + "metric": 1, + "origin": "IGP", + } + ], + "2001:db8:1::/56": [ + { + "metric": 2, + "origin": "incomplete", + } + ], + } + }, + } + # tgen.mininet_cli() + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge_prefixes) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't see prefixes from R1" + + snmp = SnmpTester(r2, "localhost", "public", "2c", "-Ln -On") + + def _snmpwalk_remote_addr(): + expected = { + "1.3.6.1.3.5.1.1.2.1.5.1.1.192.168.12.4": "C0 A8 0C 04", + "1.3.6.1.3.5.1.1.2.1.5.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "20 01 0D B8 00 00 00 00 00 00 00 00 00 12 00 04", + } + + # bgp4V2PeerRemoteAddr + # tgen.mininet_cli() + output, _ = snmp.walk(".1.3.6.1.3.5.1.1.2.1.5") + return output == expected + + _, result = topotest.run_and_expect(_snmpwalk_remote_addr, True, count=10, wait=1) + assertmsg = "Can't fetch SNMP for bgp4V2PeerRemoteAddr" + assert result, assertmsg + + def _snmpwalk_peer_state(): + expected = { + "1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.4": "6", + "1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "6", + } + + # bgp4V2PeerState + output, _ = snmp.walk(".1.3.6.1.3.5.1.1.2.1.13") + return output == expected + + _, result = topotest.run_and_expect(_snmpwalk_peer_state, True, count=10, wait=1) + assertmsg = "Can't fetch SNMP for bgp4V2PeerState" + assert result, assertmsg + + def _snmpwalk_peer_last_error_code_received(): + expected = { + "1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.4": "0", + "1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4": "0", + } + + # bgp4V2PeerLastErrorCodeReceived + output, _ = snmp.walk(".1.3.6.1.3.5.1.1.3.1.1") + return output == expected + + _, result = topotest.run_and_expect( + _snmpwalk_peer_last_error_code_received, True, count=10, wait=1 + ) + assertmsg = "Can't fetch SNMP for bgp4V2PeerLastErrorCodeReceived" + assert result, assertmsg + + def _snmpwalk_origin(): + expected = { + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.10.10.10.32.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.10.10.10.32.1.192.168.12.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.0.31.1.192.168.12.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.4.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.1.1.1.10.0.0.2.32.1.192.168.12.4.2": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "3", + } + + # bgp4V2NlriOrigin + output, _ = snmp.walk(".1.3.6.1.3.5.1.1.9.1.9") + return output == expected + + _, result = topotest.run_and_expect(_snmpwalk_origin, True, count=10, wait=1) + assertmsg = "Can't fetch SNMP for bgp4V2NlriOrigin" + assert result, assertmsg + + def _snmpwalk_med(): + expected = { + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.10.10.10.32.1.192.168.12.4.1": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.10.10.10.32.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.0.31.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.4.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.1.1.1.10.0.0.2.32.1.192.168.12.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "0", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.2.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4.2": "0", + } + + # bgp4V2NlriMed + output, _ = snmp.walk(".1.3.6.1.3.5.1.1.9.1.17") + # tgen.mininet_cli() + return output == expected + + _, result = topotest.run_and_expect(_snmpwalk_med, True, count=10, wait=1) + assertmsg = "Can't fetch SNMP for bgp4V2NlriMed" + assert result, assertmsg + + # + # traps + # + + # + # bgp4 traps + # + def _snmptrap_ipv4(): + def __get_notif_bgp4_in_trap_file(router): + snmptrapfile = "{}/{}/snmptrapd.log".format(router.logdir, router.name) + outputfile = open(snmptrapfile).read() + output = snmp.get_notif_bgp4(outputfile) + + return output + + output = __get_notif_bgp4_in_trap_file(r2) + logger.info("output bgp4") + logger.info(output) + return snmp.is_notif_bgp4_valid(output, "192.168.12.4") + + # skip tests is SNMP not installed + if not os.path.isfile("/usr/sbin/snmptrapd"): + error_msg = "SNMP not installed - skipping" + pytest.skip(error_msg) + + rr.vtysh_cmd("clear bgp *") + _, result = topotest.run_and_expect(_snmptrap_ipv4, True, count=30, wait=1) + assertmsg = "Can't fetch SNMP trap for ipv4" + assert result, assertmsg + + # + # bgp4v2 traps + # + def _snmptrap_ipv6(): + def __get_notif_bgp4v2_in_trap_file(router): + snmptrapfile = "{}/{}/snmptrapd.log".format(router.logdir, router.name) + outputfile = open(snmptrapfile).read() + output = snmp.get_notif_bgp4v2(outputfile) + + return output + + # tgen.mininet_cli() + output = __get_notif_bgp4v2_in_trap_file(r2) + logger.info("output bgp4v2") + logger.info(output) + p_ipv4_addr = "1.192.168.12.4" + p_ipv6_addr = "2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.4" + return ( + snmp.is_notif_bgp4v2_valid(output, p_ipv4_addr, "Estab") + and snmp.is_notif_bgp4v2_valid(output, p_ipv6_addr, "Estab") + and snmp.is_notif_bgp4v2_valid(output, p_ipv4_addr, "Backward") + and snmp.is_notif_bgp4v2_valid(output, p_ipv6_addr, "Backward") + ) + + sleep(10) + r2.vtysh_cmd("conf\nbgp snmp traps bgp4-mibv2") + r2.vtysh_cmd("conf\nno bgp snmp traps rfc4273") + rr.vtysh_cmd("clear bgp *") + _, result = topotest.run_and_expect(_snmptrap_ipv6, True, count=60, wait=1) + assertmsg = "Can't fetch SNMP trap for ipv6" + assert result, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce1/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/bgpd.conf new file mode 100644 index 0000000..b598666 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/bgpd.conf @@ -0,0 +1,12 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65001 + timers bgp 3 9 + bgp router-id 192.168.100.10 + neighbor 192.168.100.20 remote-as 65001 + neighbor 192.168.100.20 update-source 192.168.100.10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce1/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce1/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/zebra.conf new file mode 100644 index 0000000..4a85798 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce1/zebra.conf @@ -0,0 +1,19 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface ce1-eth0 + ip address 192.168.100.10/24 + ipv6 address 2000:1:1:100::10/64 +! +! +interface lo + ip address 10.5.5.5/32 + ipv6 address 2000:5:5:5::5/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce2/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/bgpd.conf new file mode 100644 index 0000000..e388ccb --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/bgpd.conf @@ -0,0 +1,12 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65001 + bgp router-id 192.168.200.10 + timers bgp 3 9 + neighbor 192.168.200.20 remote-as 65001 + neighbor 192.168.200.20 update-source 192.168.200.10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce2/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce2/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/zebra.conf new file mode 100644 index 0000000..5e0aa5d --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce2/zebra.conf @@ -0,0 +1,19 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface ce2-eth0 + ip address 192.168.200.10/24 + ipv6 address 2000:1:1:200::10/64 +! +! +interface lo + ip address 10.6.6.6/32 + ipv6 address 2000:6:6:6::6/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce3/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/bgpd.conf new file mode 100644 index 0000000..e388ccb --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/bgpd.conf @@ -0,0 +1,12 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65001 + bgp router-id 192.168.200.10 + timers bgp 3 9 + neighbor 192.168.200.20 remote-as 65001 + neighbor 192.168.200.20 update-source 192.168.200.10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce3/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce3/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/zebra.conf new file mode 100644 index 0000000..fabc11e --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce3/zebra.conf @@ -0,0 +1,19 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface ce3-eth0 + ip address 192.168.200.10/24 + ipv6 address 2000:1:1:200::10/64 +! +! +interface lo + ip address 10.7.7.7/32 + ipv6 address 2000:7:7:7::7/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce4/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/bgpd.conf new file mode 100644 index 0000000..e388ccb --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/bgpd.conf @@ -0,0 +1,12 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65001 + bgp router-id 192.168.200.10 + timers bgp 3 9 + neighbor 192.168.200.20 remote-as 65001 + neighbor 192.168.200.20 update-source 192.168.200.10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce4/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/ce4/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/zebra.conf new file mode 100644 index 0000000..e369f41 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/ce4/zebra.conf @@ -0,0 +1,19 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface ce4-eth0 + ip address 192.168.34.10/24 + ipv6 address 2000:1:1:300::10/64 +! +! +interface lo + ip address 10.8.8.8/32 + ipv6 address 2000:8:8:8::8/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r1/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r1/bgpd.conf new file mode 100644 index 0000000..098e55d --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r1/bgpd.conf @@ -0,0 +1,48 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65000 + bgp router-id 10.1.1.1 + neighbor 10.4.4.4 remote-as 65000 + neighbor 10.4.4.4 update-source 10.1.1.1 + neighbor 10.4.4.4 timers connect 10 + ! + address-family ipv4 vpn + neighbor 10.4.4.4 activate + exit-address-family + +! +router bgp 65001 vrf VRF-a + bgp router-id 192.168.100.20 + timers bgp 3 9 + neighbor 192.168.100.10 remote-as 65001 + neighbor 192.168.100.10 update-source 192.168.100.20 + neighbor 192.168.200.10 remote-as 65001 + neighbor 192.168.200.10 update-source 192.168.200.20 + ! + address-family ipv4 unicast + redistribute connected + redistribute isis + label vpn export 1111 + rd vpn export 10:1 + rt vpn both 1:1 + export vpn + import vpn + exit-address-family + +router bgp 65002 vrf VRF-b + bgp router-id 192.168.10.20 + timers bgp 3 9 + neighbor 192.168.10.10 remote-as 65003 + neighbor 192.168.10.10 update-source 192.168.10.20 +! + address-family ipv4 unicast + redistribute connected + redistribute isis + label vpn export 6666 + rd vpn export 10:2 + rt vpn both 1:2 + export vpn + import vpn + exit-address-family + +agentx diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r1/isisd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r1/isisd.conf new file mode 100644 index 0000000..435abde --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r1/isisd.conf @@ -0,0 +1,46 @@ +log stdout debugging +! +! debug isis route-events +! debug isis events +! +interface r1-eth0 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r1-eth1 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r1-eth2 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface lo + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + isis passive + no isis hello padding +! +router isis ISIS1 + net 01.1111.0000.0000.0001.00 + is-type level-1 + topology ipv6-unicast +! +line vty +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r1/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r1/snmpd.conf new file mode 100644 index 0000000..d7886e5 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r1/snmpd.conf @@ -0,0 +1,20 @@ +agentAddress udp:161 + +com2sec public 10.1.1.1 public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +noRangeCheck yes + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r1/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r1/zebra.conf new file mode 100644 index 0000000..7228ae6 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r1/zebra.conf @@ -0,0 +1,33 @@ +log file zebra.log +! +interface r1-eth0 + ip address 192.168.12.12/24 + ipv6 address 2000:1:1:12::12/64 +! +interface r1-eth1 + ip address 192.168.13.13/24 + ipv6 address 2000:1:1:13::13/64 +! +interface r1-eth2 + ip address 192.168.14.14/24 + ipv6 address 2000:1:1:14::14/64 +! +interface r1-eth3 vrf VRF-a + ip address 192.168.100.20/24 + ipv6 address 2000:1:1:100::20/64 +! +interface r1-eth4 vrf VRF-a + ip address 192.168.200.20/24 + ipv6 address 2000:1:1:200::20/64 +! +interface r1-eth5 vrf VRF-b + ip address 192.168.300.20/24 + ipv6 address 2000:1:1:300::20/64 + +interface lo + ip address 10.1.1.1/32 + ipv6 address 2000:1:1:1::1/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r2/isisd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r2/isisd.conf new file mode 100644 index 0000000..be5e7f5 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r2/isisd.conf @@ -0,0 +1,37 @@ +log stdout debugging +! +! debug isis route-events +! debug isis events +! +interface r2-eth0 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r2-eth1 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface lo + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + isis passive + no isis hello padding +! +router isis ISIS1 + net 01.1111.0000.0000.0002.00 + is-type level-1 + topology ipv6-unicast +! +line vty +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r2/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r2/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r2/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r2/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r2/zebra.conf new file mode 100644 index 0000000..4fec8af --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r2/zebra.conf @@ -0,0 +1,24 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface r2-eth0 + ip address 192.168.12.21/24 + ipv6 address 2000:1:1:12::21/64 +! +interface r2-eth1 + ip address 192.168.23.23/24 + ipv6 address 2000:1:1:23::23/64 +! +! +interface lo + ip address 10.2.2.2/32 + ipv6 address 2000:2:2:2::2/128 +! +! +! +line vty +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r3/isisd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r3/isisd.conf new file mode 100644 index 0000000..6f3d8ec --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r3/isisd.conf @@ -0,0 +1,45 @@ +log stdout debugging +! +! debug isis route-events +! debug isis events +! +interface r3-eth0 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r3-eth1 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r3-eth2 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface lo + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + isis passive + no isis hello padding +! +router isis ISIS1 + net 01.1111.0000.0000.0003.00 + is-type level-1 + topology ipv6-unicast +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r3/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r3/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r3/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r3/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r3/zebra.conf new file mode 100644 index 0000000..e433995 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r3/zebra.conf @@ -0,0 +1,27 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface r3-eth0 + ip address 192.168.13.31/24 + ipv6 address 2000:1:1:13::31/64 +! +interface r3-eth1 + ip address 192.168.23.32/24 + ipv6 address 2000:1:1:23::32/64 +! +interface r3-eth2 + ip address 192.168.34.34/24 + ipv6 address 2000:1:1:34::34/64 +! +! +interface lo + ip address 10.3.3.3/32 + ipv6 address 2000:3:3:3::3/128 +! +! +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r4/bgpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r4/bgpd.conf new file mode 100644 index 0000000..2a834c7 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r4/bgpd.conf @@ -0,0 +1,43 @@ +log file /tmp/bgpd.log debugging +! +router bgp 65000 + bgp router-id 10.4.4.4 + timers bgp 3 9 + neighbor 10.1.1.1 remote-as 65000 + neighbor 10.1.1.1 update-source 10.4.4.4 + neighbor 10.1.1.1 timers connect 10 + ! + address-family ipv4 vpn + neighbor 10.1.1.1 activate + exit-address-family +! + + address-family ipv6 vpn + neighbor 10.1.1.1 activate + exit-address-family +! +router bgp 65001 vrf VRF-a + bgp router-id 192.168.200.20 + timers bgp 3 9 + neighbor 192.168.200.10 remote-as 65001 + neighbor 192.168.200.10 update-source 192.168.200.20 + ! + address-family ipv4 unicast + redistribute connected + redistribute isis + label vpn export 1111 + rd vpn export 10:3 + rt vpn both 1:1 + export vpn + import vpn + exit-address-family + + address-family ipv6 unicast + redistribute connected + redistribute isis + label vpn export 1111 + rd vpn export 10:3 + rt vpn both 1:2 + export vpn + import vpn + exit-address-family diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r4/isisd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r4/isisd.conf new file mode 100644 index 0000000..7d923b6 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r4/isisd.conf @@ -0,0 +1,36 @@ +log stdout debugging +! +! debug isis route-events +! debug isis events +! +interface r4-eth0 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface r4-eth1 + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + no isis hello padding + isis hello-interval 1 + isis hello-multiplier 10 + isis network point-to-point +! +interface lo + ip router isis ISIS1 + ipv6 router isis ISIS1 + isis circuit-type level-1 + isis passive + no isis hello padding +! +router isis ISIS1 + net 01.1111.0000.0000.0004.00 + is-type level-1 + topology ipv6-unicast +! +line vty diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r4/snmpd.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r4/snmpd.conf new file mode 100644 index 0000000..c8d0bab --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r4/snmpd.conf @@ -0,0 +1,18 @@ +agentAddress udp:161 + +com2sec public localhost public + +group public_group v1 public +group public_group v2c public + +access public_group "" any noauth prefix all all none + +view all included .1 + +iquerySecName frr +rouser frr + +master agentx + +agentXSocket /etc/frr/agentx +agentXPerms 777 755 root frr diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/r4/zebra.conf b/tests/topotests/bgp_snmp_mplsl3vpn/r4/zebra.conf new file mode 100644 index 0000000..14580e5 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/r4/zebra.conf @@ -0,0 +1,27 @@ +log file /tmp/zebra.log +log stdout +! +! debug zebra events +! debug zebra dplane +! +! +interface r4-eth0 + ip address 192.168.14.41/24 + ipv6 address 2000:1:1:14::41/64 +! +interface r4-eth1 + ip address 192.168.34.43/24 + ipv6 address 2000:1:1:34::43/64 +! +interface r4-eth2 vrf aaa + ip address 192.168.200.20/24 + ipv6 address 2000:1:1:200::20/64 +! +! +interface lo + ip address 10.4.4.4/32 + ipv6 address 2000:4:4:4::4/128 +! +! +line vty +! diff --git a/tests/topotests/bgp_snmp_mplsl3vpn/test_bgp_snmp_mplsvpn.py b/tests/topotests/bgp_snmp_mplsl3vpn/test_bgp_snmp_mplsvpn.py new file mode 100755 index 0000000..0131e12 --- /dev/null +++ b/tests/topotests/bgp_snmp_mplsl3vpn/test_bgp_snmp_mplsvpn.py @@ -0,0 +1,738 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_snmp_mplsl3vpn.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by Volta Networks +# + +""" +test_bgp_snmp_mplsl3vpn.py: Test mplsL3Vpn MIB [RFC4382]. +""" + +import os +import sys +from time import sleep +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.snmptest import SnmpTester +from lib import topotest + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd, pytest.mark.isisd, pytest.mark.snmp] + + +def build_topo(tgen): + "Build function" + + # This function only purpose is to define allocation and relationship + # between routers, switches and hosts. + # + # + # Create routers + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("r4") + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("ce3") + tgen.add_router("ce4") + + # r1-r2 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # r1-r3 + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + # r1-r4 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r4"]) + + # r1-ce1 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["ce1"]) + + # r1-ce3 + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["ce3"]) + + # r1-ce4 + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["ce4"]) + + # r1-dangling + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + + # r2-r3 + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + # r3-r4 + switch = tgen.add_switch("s9") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + # r4-ce2 + switch = tgen.add_switch("s10") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["ce2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + + # skip tests is SNMP not installed + snmpd = os.system("which snmpd") + if snmpd: + error_msg = "SNMP not installed - skipping" + pytest.skip(error_msg) + + # This function initiates the topology build with Topogen... + tgen = Topogen(build_topo, mod.__name__) + # ... and here it calls Mininet initialization functions. + tgen.start_topology() + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + r3 = tgen.gears["r3"] + r4 = tgen.gears["r4"] + + # setup VRF-a in r1 + r1.run("ip link add VRF-a type vrf table 1001") + r1.run("ip link set up dev VRF-a") + r1.run("ip link add VRF-b type vrf table 1002") + r1.run("ip link set up dev VRF-b") + r4.run("ip link add VRF-a type vrf table 1001") + r4.run("ip link set up dev VRF-a") + + # enslave vrf interfaces + r1.run("ip link set r1-eth3 master VRF-a") + r1.run("ip link set r1-eth4 master VRF-a") + r1.run("ip link set r1-eth5 master VRF-b") + r4.run("ip link set r4-eth1 master VRF-a") + + r1.run("sysctl -w net.ipv4.ip_forward=1") + r2.run("sysctl -w net.ipv4.ip_forward=1") + r3.run("sysctl -w net.ipv4.ip_forward=1") + r4.run("sysctl -w net.ipv4.ip_forward=1") + r1.run("sysctl -w net.mpls.conf.r1-eth0.input=1") + r1.run("sysctl -w net.mpls.conf.r1-eth1.input=1") + r1.run("sysctl -w net.mpls.conf.r1-eth2.input=1") + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, + os.path.join(CWD, "{}/bgpd.conf".format(rname)), + "-M snmp", + ) + router.load_config( + TopoRouter.RD_SNMP, + os.path.join(CWD, "{}/snmpd.conf".format(rname)), + "-Le -Ivacm_conf,usmConf,iquery -V -DAgentX,trap", + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +# SNMP utilities - maybe move to lib +def snmp_uint32_to_oid(val): + oid1 = int(val / 16777216) % 256 + oid2 = int(val / 65536) % 256 + oid3 = int(val / 256) % 256 + oid4 = int(val) % 256 + return "%(oid1)s.%(oid2)s.%(oid3)s.%(oid4)s" % locals() + + +def snmp_oid_to_uint32(oid): + values = oid.split(".") + return ( + (int(values[0]) * 16777216) + + (int(values[1]) * 65536) + + (int(values[2]) * 256) + + int(values[3]) + ) + + +def snmp_str_to_oid(str): + out_oid = "" + for char in str: + out_oid += "{}.".format(ord(char)) + return out_oid.rstrip(".") + + +def snmp_oid_to_str(oid): + out_str = "" + oids = oid.split(".") + for char in oids: + out_str += "{}".format(chr(int(char))) + return out_str + + +def snmp_rte_oid(vrf, dtype, dest, plen, policy, ntype, nhop=0): + oid_1 = snmp_str_to_oid(vrf) + oid_2 = dtype + oid_3 = dest + oid_4 = plen + oid_5 = "0.{}".format(policy) + oid_6 = ntype + if ntype == 0: + oid_7 = "" + else: + oid_7 = ".{}".format(nhop) + + return "{}.{}.{}.{}.{}.{}{}".format(oid_1, oid_2, oid_3, oid_4, oid_5, oid_6, oid_7) + + +def test_pe1_converge_evpn(): + "Wait for protocol convergence" + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + def _convergence(): + r1 = tgen.gears["r1"] + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + return r1_snmp.test_oid("bgpVersion", "10") + + _, result = topotest.run_and_expect(_convergence, True, count=20, wait=1) + assertmsg = "BGP SNMP does not seem to be running" + assert result, assertmsg + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + count = 0 + passed = False + while count < 125: + if r1_snmp.test_oid_walk("bgpPeerLocalAddr.10.4.4.4", ["10.1.1.1"]): + passed = True + break + count += 1 + sleep(1) + # tgen.mininet_cli() + assertmsg = "BGP Peer 10.4.4.4 did not connect" + assert passed, assertmsg + + +interfaces_up_test = { + "mplsL3VpnConfiguredVrfs": "2", + "mplsL3VpnActiveVrfs": "2", + "mplsL3VpnConnectedInterfaces": "3", + "mplsL3VpnNotificationEnable": "true(1)", + "mplsL3VpnVrfConfMaxPossRts": "0", + "mplsL3VpnVrfConfRteMxThrshTime": "0 seconds", + "mplsL3VpnIlllblRcvThrsh": "0", +} + +interfaces_down_test = { + "mplsL3VpnConfiguredVrfs": "2", + "mplsL3VpnActiveVrfs": "1", + "mplsL3VpnConnectedInterfaces": "3", + "mplsL3VpnNotificationEnable": "true(1)", + "mplsL3VpnVrfConfMaxPossRts": "0", + "mplsL3VpnVrfConfRteMxThrshTime": "0 seconds", + "mplsL3VpnIlllblRcvThrsh": "0", +} + + +def test_r1_mplsvpn_scalars(): + "check scalar values" + tgen = get_topogen() + r1 = tgen.gears["r1"] + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + for item in interfaces_up_test.keys(): + assertmsg = "{} should be {}: value {}".format( + item, interfaces_up_test[item], r1_snmp.get_next(item) + ) + assert r1_snmp.test_oid(item, interfaces_up_test[item]), assertmsg + + +def test_r1_mplsvpn_scalars_interface(): + "check scalar interface changing values" + tgen = get_topogen() + r1 = tgen.gears["r1"] + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + r1.vtysh_cmd("conf t\ninterface r1-eth3\nshutdown") + r1.vtysh_cmd("conf t\ninterface r1-eth4\nshutdown") + + for item in interfaces_up_test.keys(): + assertmsg = "{} should be {}: value {}".format( + item, interfaces_down_test[item], r1_snmp.get_next(item) + ) + assert r1_snmp.test_oid(item, interfaces_down_test[item]), assertmsg + + r1.vtysh_cmd("conf t\ninterface r1-eth3\nno shutdown") + r1.vtysh_cmd("conf t\ninterface r1-eth4\nno shutdown") + + for item in interfaces_up_test.keys(): + assertmsg = "{} should be {}: value {}".format( + item, interfaces_up_test[item], r1_snmp.get_next(item) + ) + assert r1_snmp.test_oid(item, interfaces_up_test[item]), assertmsg + + +def router_interface_get_ifindex(router, interface): + ifindex = 0 + r_int_output = router.vtysh_cmd( + "show interface {}-{}".format(router.name, interface) + ) + int_lines = r_int_output.splitlines() + for line in int_lines: + line_items = line.lstrip().split(" ") + if "index" in line_items[0]: + ifindex = line_items[1] + return ifindex + + +def generate_vrf_ifindex_oid(vrf, ifindex): + + intoid = snmp_uint32_to_oid(int(ifindex)) + vrfoid = snmp_str_to_oid(vrf) + oid = "{}.{}".format(vrfoid, intoid) + + return oid + + +def generate_vrf_index_type_oid(vrf, index, type): + vrfoid = snmp_str_to_oid(vrf) + intoid = snmp_uint32_to_oid(int(index)) + oid = "{}.{}.{}".format(vrfoid, intoid, type) + + return oid + + +iftable_up_test = { + "mplsL3VpnIfVpnClassification": ["enterprise(2)", "enterprise(2)", "enterprise(2)"], + "mplsL3VpnIfConfStorageType": ["volatile(2)", "volatile(2)", "volatile(2)"], + "mplsL3VpnIfConfRowStatus": ["active(1)", "active(1)", "active(1)"], +} + + +def get_timetick_val(time): + return int(time.split(" ")[0].lstrip("(").rstrip(")")) + + +def test_r1_mplsvpn_IfTable(): + "mplsL3VpnIf table values" + + tgen = get_topogen() + r1 = tgen.gears["r1"] + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + # tgen.mininet_cli() + eth3_ifindex = router_interface_get_ifindex(r1, "eth3") + eth4_ifindex = router_interface_get_ifindex(r1, "eth4") + eth5_ifindex = router_interface_get_ifindex(r1, "eth5") + + # get ifindex and make sure the oid is correct + + oids = [] + # generate oid + oids.append(generate_vrf_ifindex_oid("VRF-a", eth3_ifindex)) + oids.append(generate_vrf_ifindex_oid("VRF-a", eth4_ifindex)) + oids.append(generate_vrf_ifindex_oid("VRF-b", eth5_ifindex)) + + for item in iftable_up_test.keys(): + assertmsg = "{} should be {} oids {} full dict {}:".format( + item, iftable_up_test[item], oids, r1_snmp.walk(item) + ) + assert r1_snmp.test_oid_walk(item, iftable_up_test[item], oids), assertmsg + + # an inactive vrf should not affect these values + r1.cmd("ip link set r1-eth5 down") + + for item in iftable_up_test.keys(): + assertmsg = "{} should be {} oids {} full dict {}:".format( + item, iftable_up_test[item], oids, r1_snmp.walk(item) + ) + assert r1_snmp.test_oid_walk(item, iftable_up_test[item], oids), assertmsg + + r1.cmd("ip link set r1-eth5 up") + + +vrftable_test = { + "mplsL3VpnVrfDescription": ["VRF-a", "VRF-b"], + "mplsL3VpnVrfRD": ['"10:1"', '"10:2"'], + "mplsL3VpnVrfOperStatus": ["up(1)", "up(1)"], + "mplsL3VpnVrfActiveInterfaces": ["2", "1"], + "mplsL3VpnVrfAssociatedInterfaces": ["2", "1"], + "mplsL3VpnVrfConfMidRteThresh": ["0", "0"], + "mplsL3VpnVrfConfHighRteThresh": ["0", "0"], + "mplsL3VpnVrfConfMaxRoutes": ["0", "0"], + "mplsL3VpnVrfConfRowStatus": ["active(1)", "active(1)"], + "mplsL3VpnVrfConfAdminStatus": ["up(1)", "up(1)"], + "mplsL3VpnVrfConfStorageType": ["volatile(2)", "volatile(2)"], +} + + +def test_r1_mplsvpn_VrfTable(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + # tgen.mininet_cli() + + oids = [] + + oids.append(snmp_str_to_oid("VRF-a")) + oids.append(snmp_str_to_oid("VRF-b")) + + # check items + for item in vrftable_test.keys(): + assertmsg = "{} should be {} oids {} full dict {}:".format( + item, vrftable_test[item], oids, r1_snmp.walk(item) + ) + assert r1_snmp.test_oid_walk(item, vrftable_test[item], oids), assertmsg + + # check timetick set and stable + ts_a = r1_snmp.get("mplsL3VpnVrfCreationTime.{}".format(snmp_str_to_oid("VRF-a"))) + ts_b = r1_snmp.get("mplsL3VpnVrfCreationTime.{}".format(snmp_str_to_oid("VRF-b"))) + ts_val_a1 = get_timetick_val(ts_a) + ts_val_b1 = get_timetick_val(ts_b) + ts_a = r1_snmp.get("mplsL3VpnVrfCreationTime.{}".format(snmp_str_to_oid("VRF-a"))) + ts_b = r1_snmp.get("mplsL3VpnVrfCreationTime.{}".format(snmp_str_to_oid("VRF-b"))) + ts_val_a2 = get_timetick_val(ts_a) + ts_val_b2 = get_timetick_val(ts_b) + + assertmsg = "timestamp values for VRF-a do not match {} {}".format( + ts_val_a1, ts_val_a2 + ) + assert ts_val_a1 == ts_val_a2, assertmsg + assertmsg = "timestamp values for VRF-b do not match {} {}".format( + ts_val_b1, ts_val_b2 + ) + assert ts_val_b1 == ts_val_b2, assertmsg + + # take Last changed time, fiddle with active interfaces, ensure + # time changes and active interfaces change + ts_last = r1_snmp.get( + "mplsL3VpnVrfConfLastChanged.{}".format(snmp_str_to_oid("VRF-a")) + ) + ts_val_last_1 = get_timetick_val(ts_last) + r1.vtysh_cmd("conf t\ninterface r1-eth3\nshutdown") + active_int = r1_snmp.get( + "mplsL3VpnVrfActiveInterfaces.{}".format(snmp_str_to_oid("VRF-a")) + ) + assertmsg = "mplsL3VpnVrfActiveInterfaces incorrect should be 1 value {}".format( + active_int + ) + assert active_int == "1", assertmsg + + ts_last = r1_snmp.get( + "mplsL3VpnVrfConfLastChanged.{}".format(snmp_str_to_oid("VRF-a")) + ) + ts_val_last_2 = get_timetick_val(ts_last) + assertmsg = "mplsL3VpnVrfConfLastChanged does not update on interface change" + assert ts_val_last_2 > ts_val_last_1, assertmsg + r1.vtysh_cmd("conf t\ninterface r1-eth3\nno shutdown") + + # take Last changed time, fiddle with associated interfaces, ensure + # time changes and active interfaces change + ts_last = r1_snmp.get( + "mplsL3VpnVrfConfLastChanged.{}".format(snmp_str_to_oid("VRF-a")) + ) + ts_val_last_1 = get_timetick_val(ts_last) + r1.cmd("ip link set r1-eth6 master VRF-a") + r1.cmd("ip link set r1-eth6 up") + + associated_int = r1_snmp.get( + "mplsL3VpnVrfAssociatedInterfaces.{}".format(snmp_str_to_oid("VRF-a")) + ) + assertmsg = ( + "mplsL3VpnVrfAssociatedInterfaces incorrect should be 3 value {}".format( + associated_int + ) + ) + + assert associated_int == "3", assertmsg + ts_last = r1_snmp.get( + "mplsL3VpnVrfConfLastChanged.{}".format(snmp_str_to_oid("VRF-a")) + ) + ts_val_last_2 = get_timetick_val(ts_last) + assertmsg = "mplsL3VpnVrfConfLastChanged does not update on interface change" + assert ts_val_last_2 > ts_val_last_1, assertmsg + r1.cmd("ip link del r1-eth6 master VRF-a") + r1.cmd("ip link set r1-eth6 down") + + +rt_table_test = { + "mplsL3VpnVrfRT": ['"1:1"', '"1:2"'], + "mplsL3VpnVrfRTDescr": ["RT both for VRF VRF-a", "RT both for VRF VRF-b"], + "mplsL3VpnVrfRTRowStatus": ["active(1)", "active(1)"], + "mplsL3VpnVrfRTStorageType": ["volatile(2)", "volatile(2)"], +} + + +def test_r1_mplsvpn_VrfRT_table(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + oids = [] + oids.append(generate_vrf_index_type_oid("VRF-a", 1, 3)) + oids.append(generate_vrf_index_type_oid("VRF-b", 1, 3)) + + # check items + for item in rt_table_test.keys(): + print(item) + assertmsg = "{} should be {} oids {} full dict {}:".format( + item, rt_table_test[item], oids, r1_snmp.walk(item) + ) + assert r1_snmp.test_oid_walk(item, rt_table_test[item], oids), assertmsg + + +def test_r1_mplsvpn_perf_table(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + # tgen.mininet_cli() + oid_a = snmp_str_to_oid("VRF-a") + oid_b = snmp_str_to_oid("VRF-b") + + # poll for 10 seconds for routes to appear + count = 0 + passed = False + while count < 60: + if r1_snmp.test_oid_walk( + "mplsL3VpnVrfPerfCurrNumRoutes.{}".format(oid_a), ["7"] + ): + passed = True + break + count += 1 + sleep(1) + # tgen.mininet_cli() + assertmsg = "mplsL3VpnVrfPerfCurrNumRoutes shouold be 7 got {}".format( + r1_snmp.get("mplsL3VpnVrfPerfCurrNumRoutes.{}".format(oid_a)) + ) + assert passed, assertmsg + curr_a = int(r1_snmp.get("mplsL3VpnVrfPerfCurrNumRoutes.{}".format(oid_a))) + del_a = int(r1_snmp.get("mplsL3VpnVrfPerfRoutesDeleted.{}".format(oid_a))) + add_a = int(r1_snmp.get("mplsL3VpnVrfPerfRoutesAdded.{}".format(oid_a))) + + assertmsg = "FAIL curr{} does not equal added{} - deleted {}".format( + curr_a, add_a, del_a + ) + assert curr_a == (add_a - del_a), assertmsg + curr_b = int(r1_snmp.get("mplsL3VpnVrfPerfCurrNumRoutes.{}".format(oid_b))) + del_b = int(r1_snmp.get("mplsL3VpnVrfPerfRoutesDeleted.{}".format(oid_b))) + add_b = int(r1_snmp.get("mplsL3VpnVrfPerfRoutesAdded.{}".format(oid_b))) + assertmsg = "FAIL curr{} does not equal added{} - deleted {}".format( + curr_b, add_b, del_b + ) + assert curr_b == (add_b - del_b), assertmsg + + +rte_table_test = { + "mplsL3VpnVrfRteInetCidrDestType": [ + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + ], + "mplsL3VpnVrfRteInetCidrDest": [ + "0A 05 05 05", + "0A 07 07 07", + "C0 A8 22 00", + "C0 A8 64 00", + "C0 A8 64 00", + "C0 A8 C8 00", + "C0 A8 C8 00", + ], + "mplsL3VpnVrfRteInetCidrPfxLen": ["32", "32", "24", "24", "24", "24", "24"], + "mplsL3VpnVrfRteInetCidrNHopType": [ + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "ipv4(1)", + "unknown(0)", + "ipv4(1)", + "unknown(0)", + ], + "mplsL3VpnVrfRteInetCidrNextHop": [ + "C0 A8 64 0A", + "C0 A8 C8 0A", + "0A 04 04 04", + "C0 A8 64 0A", + '""', + "C0 A8 C8 0A", + '""', + ], + "mplsL3VpnVrfRteInetCidrType": [ + "local(3)", + "local(3)", + "remote(4)", + "local(3)", + "other(1)", + "local(3)", + "other(1)", + ], + "mplsL3VpnVrfRteInetCidrProto": [ + "bgp(14)", + "bgp(14)", + "bgp(14)", + "bgp(14)", + "local(2)", + "bgp(14)", + "local(2)", + ], + "mplsL3VpnVrfRteInetCidrNextHopAS": [ + "65001", + "65001", + "0", + "65001", + "0", + "65001", + "0", + ], + "mplsL3VpnVrfRteInetCidrMetric1": ["0", "0", "20", "0", "0", "0", "0"], + "mplsL3VpnVrfRteInetCidrMetric2": ["-1", "-1", "-1", "-1", "-1", "-1", "-1"], + "mplsL3VpnVrfRteInetCidrMetric3": ["-1", "-1", "-1", "-1", "-1", "-1", "-1"], + "mplsL3VpnVrfRteInetCidrMetric4": ["-1", "-1", "-1", "-1", "-1", "-1", "-1"], + "mplsL3VpnVrfRteInetCidrMetric5": ["-1", "-1", "-1", "-1", "-1", "-1", "-1"], + "mplsL3VpnVrfRteXCPointer": ["00", "00", "00", "00", "00", "00", "00"], + "mplsL3VpnVrfRteInetCidrStatus": [ + "active(1)", + "active(1)", + "active(1)", + "active(1)", + "active(1)", + "active(1)", + "active(1)", + ], +} + + +def test_r1_mplsvpn_rte_table(): + tgen = get_topogen() + + r1 = tgen.gears["r1"] + + r1_snmp = SnmpTester(r1, "10.1.1.1", "public", "2c") + + # tgen.mininet_cli() + oid_1 = snmp_rte_oid("VRF-a", 1, "10.5.5.5", 32, 0, 1, "192.168.100.10") + oid_2 = snmp_rte_oid("VRF-a", 1, "10.7.7.7", 32, 0, 1, "192.168.200.10") + oid_3 = snmp_rte_oid("VRF-a", 1, "192.168.34.0", 24, 0, 1, "10.4.4.4") + oid_4 = snmp_rte_oid("VRF-a", 1, "192.168.100.0", 24, 1, 1, "192.168.100.10") + oid_4_a = snmp_rte_oid("VRF-a", 1, "192.168.100.0", 24, 0, 1, "192.168.100.10") + oid_5 = snmp_rte_oid("VRF-a", 1, "192.168.100.0", 24, 0, 0) + oid_5_a = snmp_rte_oid("VRF-a", 1, "192.168.100.0", 24, 1, 0) + oid_6 = snmp_rte_oid("VRF-a", 1, "192.168.200.0", 24, 1, 1, "192.168.200.10") + oid_6_a = snmp_rte_oid("VRF-a", 1, "192.168.200.0", 24, 0, 1, "192.168.200.10") + oid_7 = snmp_rte_oid("VRF-a", 1, "192.168.200.0", 24, 0, 0) + oid_7_a = snmp_rte_oid("VRF-a", 1, "192.168.200.0", 24, 1, 0) + + oid_lists = [ + [oid_1, oid_2, oid_3, oid_4, oid_5, oid_6, oid_7], + [oid_1, oid_2, oid_3, oid_4_a, oid_5_a, oid_6, oid_7], + [oid_1, oid_2, oid_3, oid_4, oid_5, oid_6_a, oid_7_a], + [oid_1, oid_2, oid_3, oid_4_a, oid_5_a, oid_6_a, oid_7_a], + [oid_1, oid_2, oid_3, oid_4, oid_5, oid_6, oid_7], + [oid_1, oid_2, oid_3, oid_4_a, oid_5_a, oid_6, oid_7], + [oid_1, oid_2, oid_3, oid_4, oid_5, oid_6_a, oid_7_a], + [oid_1, oid_2, oid_3, oid_4_a, oid_5_a, oid_6_a, oid_7_a], + ] + + # check items + + passed = False + for oid_list in oid_lists: + passed = True + for item in rte_table_test.keys(): + print(item) + assertmsg = "{} should be {} oids {} full dict {}:".format( + item, rte_table_test[item], oid_list, r1_snmp.walk(item) + ) + if not r1_snmp.test_oid_walk(item, rte_table_test[item], oid_list): + passed = False + break + print( + "{} should be {} oids {} full dict {}:".format( + item, rte_table_test[item], oid_list, r1_snmp.walk(item) + ) + ) + if passed: + break + # generate ifindex row grabbing ifindices from vtysh + if passed: + ifindex_row = [ + router_interface_get_ifindex(r1, "eth3"), + router_interface_get_ifindex(r1, "eth4"), + router_interface_get_ifindex(r1, "eth2"), + router_interface_get_ifindex(r1, "eth3"), + "0", + router_interface_get_ifindex(r1, "eth4"), + "0", + ] + if not r1_snmp.test_oid_walk( + "mplsL3VpnVrfRteInetCidrIfIndex", ifindex_row, oid_list + ): + passed = False + + print("passed {}".format(passed)) + assert passed, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_software_version/__init__.py b/tests/topotests/bgp_software_version/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_software_version/r1/bgpd.conf b/tests/topotests/bgp_software_version/r1/bgpd.conf new file mode 100644 index 0000000..42d15f7 --- /dev/null +++ b/tests/topotests/bgp_software_version/r1/bgpd.conf @@ -0,0 +1,8 @@ +! +router bgp 65001 + no bgp ebgp-requires-policy + bgp default software-version-capability + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 +! diff --git a/tests/topotests/bgp_software_version/r1/zebra.conf b/tests/topotests/bgp_software_version/r1/zebra.conf new file mode 100644 index 0000000..b29940f --- /dev/null +++ b/tests/topotests/bgp_software_version/r1/zebra.conf @@ -0,0 +1,4 @@ +! +int r1-eth0 + ip address 192.168.1.1/24 +! diff --git a/tests/topotests/bgp_software_version/r2/bgpd.conf b/tests/topotests/bgp_software_version/r2/bgpd.conf new file mode 100644 index 0000000..c8ab017 --- /dev/null +++ b/tests/topotests/bgp_software_version/r2/bgpd.conf @@ -0,0 +1,7 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + neighbor 192.168.1.1 capability software-version +! diff --git a/tests/topotests/bgp_software_version/r2/zebra.conf b/tests/topotests/bgp_software_version/r2/zebra.conf new file mode 100644 index 0000000..cffe827 --- /dev/null +++ b/tests/topotests/bgp_software_version/r2/zebra.conf @@ -0,0 +1,4 @@ +! +int r2-eth0 + ip address 192.168.1.2/24 +! diff --git a/tests/topotests/bgp_software_version/test_bgp_software_version.py b/tests/topotests/bgp_software_version/test_bgp_software_version.py new file mode 100644 index 0000000..c867208 --- /dev/null +++ b/tests/topotests/bgp_software_version/test_bgp_software_version.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if Software Version capability works if forced with a knob. +Reference: https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability +""" + +import os +import re +import sys +import json +import pytest +import functools + +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + + +def setup_module(mod): + topodef = {"s1": ("r1", "r2")} + tgen = Topogen(topodef, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_software_version(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + def _bgp_converge(): + output = json.loads(r1.vtysh_cmd("show bgp summary json")) + expected = {"ipv4Unicast": {"peers": {"192.168.1.2": {"state": "Established"}}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_converge, + ) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't converge" + + def _bgp_check_software_version(): + output = json.loads(r1.vtysh_cmd("show bgp neighbor 192.168.1.2 json")) + + try: + versions = output["192.168.1.2"]["neighborCapabilities"]["softwareVersion"] + adv = versions["advertisedSoftwareVersion"] + rcv = versions["receivedSoftwareVersion"] + + if not adv and not rcv: + return False + + pattern = "^FRRouting/\\d.+" + if re.search(pattern, adv) and re.search(pattern, rcv): + return True + except: + return False + + return False + + assert _bgp_check_software_version(), "Neighbor's software version is n/a" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_soo/__init__.py b/tests/topotests/bgp_soo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_soo/cpe1/bgpd.conf b/tests/topotests/bgp_soo/cpe1/bgpd.conf new file mode 100644 index 0000000..a8984d4 --- /dev/null +++ b/tests/topotests/bgp_soo/cpe1/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 10.0.0.2 remote-as internal + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_soo/cpe1/zebra.conf b/tests/topotests/bgp_soo/cpe1/zebra.conf new file mode 100644 index 0000000..669cb91 --- /dev/null +++ b/tests/topotests/bgp_soo/cpe1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface cpe1-eth0 + ip address 192.168.1.1/24 +! +interface cpe1-eth1 + ip address 10.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_soo/cpe2/bgpd.conf b/tests/topotests/bgp_soo/cpe2/bgpd.conf new file mode 100644 index 0000000..19f7a24 --- /dev/null +++ b/tests/topotests/bgp_soo/cpe2/bgpd.conf @@ -0,0 +1,10 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + neighbor 10.0.0.1 remote-as internal + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_soo/cpe2/zebra.conf b/tests/topotests/bgp_soo/cpe2/zebra.conf new file mode 100644 index 0000000..52f36c0 --- /dev/null +++ b/tests/topotests/bgp_soo/cpe2/zebra.conf @@ -0,0 +1,9 @@ +! +interface cpe2-eth0 + ip address 192.168.2.1/24 +! +interface cpe2-eth1 + ip address 10.0.0.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_soo/pe1/bgpd.conf b/tests/topotests/bgp_soo/pe1/bgpd.conf new file mode 100644 index 0000000..04a6857 --- /dev/null +++ b/tests/topotests/bgp_soo/pe1/bgpd.conf @@ -0,0 +1,27 @@ +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.20 remote-as internal + neighbor 10.10.10.20 update-source 10.10.10.10 + address-family ipv4 vpn + neighbor 10.10.10.20 activate + exit-address-family +! +router bgp 65001 vrf RED + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + neighbor 192.168.1.1 as-override + neighbor 192.168.1.1 soo 65000:1 + label vpn export 1111 + rd vpn export 192.168.1.2:2 + rt vpn import 192.168.2.2:2 192.168.1.2:2 + rt vpn export 192.168.1.2:2 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_soo/pe1/ldpd.conf b/tests/topotests/bgp_soo/pe1/ldpd.conf new file mode 100644 index 0000000..fb40f06 --- /dev/null +++ b/tests/topotests/bgp_soo/pe1/ldpd.conf @@ -0,0 +1,10 @@ +mpls ldp + router-id 10.10.10.10 + ! + address-family ipv4 + discovery transport-address 10.10.10.10 + ! + interface pe1-eth1 + ! + ! +! diff --git a/tests/topotests/bgp_soo/pe1/ospfd.conf b/tests/topotests/bgp_soo/pe1/ospfd.conf new file mode 100644 index 0000000..34f0899 --- /dev/null +++ b/tests/topotests/bgp_soo/pe1/ospfd.conf @@ -0,0 +1,7 @@ +interface pe1-eth1 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.10 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_soo/pe1/zebra.conf b/tests/topotests/bgp_soo/pe1/zebra.conf new file mode 100644 index 0000000..cc8ff19 --- /dev/null +++ b/tests/topotests/bgp_soo/pe1/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 10.10.10.10/32 +! +interface pe1-eth0 vrf RED + ip address 192.168.1.2/24 +! +interface pe1-eth1 + ip address 10.0.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_soo/pe2/bgpd.conf b/tests/topotests/bgp_soo/pe2/bgpd.conf new file mode 100644 index 0000000..efebc02 --- /dev/null +++ b/tests/topotests/bgp_soo/pe2/bgpd.conf @@ -0,0 +1,31 @@ +router bgp 65001 + bgp router-id 10.10.10.20 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 10.10.10.10 remote-as internal + neighbor 10.10.10.10 update-source 10.10.10.20 + address-family ipv4 vpn + neighbor 10.10.10.10 activate + exit-address-family +! +router bgp 65001 vrf RED + bgp router-id 192.168.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + neighbor 192.168.2.1 as-override + neighbor 192.168.2.1 route-map cpe2-in in + label vpn export 2222 + rd vpn export 192.168.2.2:2 + rt vpn import 192.168.2.2:2 192.168.1.2:2 + rt vpn export 192.168.2.2:2 + export vpn + import vpn + exit-address-family +! +! To prefer internal MPLS route over eBGP +route-map cpe2-in permit 10 + set local-preference 50 +exit diff --git a/tests/topotests/bgp_soo/pe2/ldpd.conf b/tests/topotests/bgp_soo/pe2/ldpd.conf new file mode 100644 index 0000000..e2b5359 --- /dev/null +++ b/tests/topotests/bgp_soo/pe2/ldpd.conf @@ -0,0 +1,10 @@ +mpls ldp + router-id 10.10.10.20 + ! + address-family ipv4 + discovery transport-address 10.10.10.20 + ! + interface pe2-eth0 + ! + ! +! diff --git a/tests/topotests/bgp_soo/pe2/ospfd.conf b/tests/topotests/bgp_soo/pe2/ospfd.conf new file mode 100644 index 0000000..4c4b137 --- /dev/null +++ b/tests/topotests/bgp_soo/pe2/ospfd.conf @@ -0,0 +1,7 @@ +interface pe2-eth0 + ip ospf dead-interval 4 + ip ospf hello-interval 1 +! +router ospf + router-id 10.10.10.20 + network 0.0.0.0/0 area 0 diff --git a/tests/topotests/bgp_soo/pe2/zebra.conf b/tests/topotests/bgp_soo/pe2/zebra.conf new file mode 100644 index 0000000..8049a74 --- /dev/null +++ b/tests/topotests/bgp_soo/pe2/zebra.conf @@ -0,0 +1,12 @@ +! +interface lo + ip address 10.10.10.20/32 +! +interface pe2-eth1 vrf RED + ip address 192.168.2.2/24 +! +interface pe2-eth0 + ip address 10.0.1.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_soo/test_bgp_soo.py b/tests/topotests/bgp_soo/test_bgp_soo.py new file mode 100644 index 0000000..967bed0 --- /dev/null +++ b/tests/topotests/bgp_soo/test_bgp_soo.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Test if BGP SoO per neighbor works correctly. Routes having SoO +extended community MUST be rejected if the neighbor is configured +with soo (neighbor soo). +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("cpe1") + tgen.add_router("cpe2") + tgen.add_router("pe1") + tgen.add_router("pe2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["cpe1"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["pe1"]) + switch.add_link(tgen.gears["pe2"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["pe2"]) + switch.add_link(tgen.gears["cpe2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["cpe2"]) + switch.add_link(tgen.gears["cpe1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["pe1"] + pe2 = tgen.gears["pe2"] + + pe1.run("ip link add RED type vrf table 1001") + pe1.run("ip link set up dev RED") + pe2.run("ip link add RED type vrf table 1001") + pe2.run("ip link set up dev RED") + pe1.run("ip link set pe1-eth0 master RED") + pe2.run("ip link set pe2-eth1 master RED") + + pe1.run("sysctl -w net.ipv4.ip_forward=1") + pe2.run("sysctl -w net.ipv4.ip_forward=1") + pe1.run("sysctl -w net.mpls.conf.pe1-eth0.input=1") + pe2.run("sysctl -w net.mpls.conf.pe2-eth1.input=1") + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_soo(): + tgen = get_topogen() + + pe2 = tgen.gears["pe2"] + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_soo_unconfigured(): + output = json.loads( + pe2.vtysh_cmd( + "show bgp vrf RED ipv4 unicast neighbors 192.168.2.1 advertised-routes json" + ) + ) + expected = {"advertisedRoutes": {"172.16.255.1/32": {"path": "65001"}}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_soo_unconfigured) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see BGP convergence in pe2" + + step("Configure SoO (65000:1) for PE2 -- CPE2 session") + pe2.vtysh_cmd( + """ + configure terminal + router bgp 65001 vrf RED + address-family ipv4 unicast + neighbor 192.168.2.1 soo 65000:1 + """ + ) + + def _bgp_soo_configured(): + output = json.loads( + pe2.vtysh_cmd( + "show bgp vrf RED ipv4 unicast neighbors 192.168.2.1 advertised-routes json" + ) + ) + expected = {"advertisedRoutes": {"172.16.255.1/32": None}} + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_soo_configured) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "SoO filtering does not work from pe2" + + step("Configure SoO (65000:2) for PE2 -- CPE2 session") + pe2.vtysh_cmd( + """ + configure terminal + router bgp 65001 vrf RED + address-family ipv4 unicast + neighbor 192.168.2.1 soo 65000:2 + """ + ) + + test_func = functools.partial(_bgp_soo_unconfigured) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "SoO filtering does not work from pe2" + + step("Unconfigure SoO for PE2 -- CPE2 session") + pe2.vtysh_cmd( + """ + configure terminal + router bgp 65001 vrf RED + address-family ipv4 unicast + no neighbor 192.168.2.1 soo + """ + ) + + test_func = functools.partial(_bgp_soo_unconfigured) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "SoO filtering does not work from pe2" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6_sid_reachability/c11/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c11/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c11/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c11/staticd.conf new file mode 100644 index 0000000..bcf5a04 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c11/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.1.254 +ipv6 route ::/0 2001:1::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c11/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c11/zebra.conf new file mode 100644 index 0000000..0615cf9 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c11/zebra.conf @@ -0,0 +1,6 @@ +hostname c11 +! +interface eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c12/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c12/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c12/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c12/staticd.conf new file mode 100644 index 0000000..bcf5a04 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c12/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.1.254 +ipv6 route ::/0 2001:1::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c12/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c12/zebra.conf new file mode 100644 index 0000000..18985aa --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c12/zebra.conf @@ -0,0 +1,6 @@ +hostname c12 +! +interface eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c21/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c21/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c21/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c21/staticd.conf new file mode 100644 index 0000000..608e600 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c21/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c21/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c21/zebra.conf new file mode 100644 index 0000000..b8b70ac --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c21/zebra.conf @@ -0,0 +1,6 @@ +hostname c21 +! +interface eth0 + ip address 192.168.2.1/24 + ipv6 address 2001:2::1/64 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c22/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c22/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c22/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c22/staticd.conf new file mode 100644 index 0000000..277aae9 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c22/staticd.conf @@ -0,0 +1,5 @@ + +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! \ No newline at end of file diff --git a/tests/topotests/bgp_srv6_sid_reachability/c22/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c22/zebra.conf new file mode 100644 index 0000000..cc764cc --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c22/zebra.conf @@ -0,0 +1,9 @@ +hostname c22 +! +interface eth0 + ip address 192.168.2.1/24 + ipv6 address 2001:2::1/64 +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c31/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c31/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c31/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c31/staticd.conf new file mode 100644 index 0000000..0c88575 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c31/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.3.254 +ipv6 route ::/0 2001:3::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c31/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c31/zebra.conf new file mode 100644 index 0000000..3f75641 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c31/zebra.conf @@ -0,0 +1,6 @@ +hostname c31 +! +interface eth0 + ip address 192.168.3.1/24 + ipv6 address 2001:3::1/64 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c32/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/c32/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6_sid_reachability/c32/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/c32/staticd.conf new file mode 100644 index 0000000..0c88575 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c32/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.3.254 +ipv6 route ::/0 2001:3::ffff +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/c32/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/c32/zebra.conf new file mode 100644 index 0000000..c06a7d1 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/c32/zebra.conf @@ -0,0 +1,6 @@ +hostname c32 +! +interface eth0 + ip address 192.168.3.1/24 + ipv6 address 2001:3::1/64 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r1/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/r1/bgpd.conf new file mode 100644 index 0000000..573dbe0 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r1/bgpd.conf @@ -0,0 +1,61 @@ +frr defaults traditional +! +hostname r1 +password zebra +! +log commands +! +router bgp 65001 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:12::2 remote-as 65002 + neighbor 2001:db8:12::2 timers 3 10 + neighbor 2001:db8:12::2 timers connect 1 + neighbor 2001:db8:12::2 capability extended-nexthop + neighbor 2001:db8:13::3 remote-as 65001 + neighbor 2001:db8:13::3 timers 3 10 + neighbor 2001:db8:13::3 timers connect 1 + neighbor 2001:db8:13::3 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:12::2 activate + neighbor 2001:db8:13::3 activate + exit-address-family + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65001:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! +interface eth0 + mpls bgp forwarding +! +interface eth1 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r1/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/r1/staticd.conf new file mode 100644 index 0000000..49b64fd --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r1/staticd.conf @@ -0,0 +1,4 @@ +! +ipv6 route 2001:db8:2:2::/64 2001:db8:12::2 +ipv6 route 2001:db8:3:3::/64 2001:db8:13::3 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r1/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/r1/zebra.conf new file mode 100644 index 0000000..79dbf95 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r1/zebra.conf @@ -0,0 +1,32 @@ +log file zebra.log +! +hostname r1 +! +interface lo + ipv6 address 2001:db8:1:1::1/128 +! +interface eth0 + ipv6 address 2001:db8:12::1/64 +! +interface eth1 + ipv6 address 2001:db8:13::1/64 +! +interface eth2 vrf vrf10 + ip address 192.168.1.254/24 +! +interface eth3 vrf vrf20 + ip address 192.168.1.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:1:1::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r2/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/r2/bgpd.conf new file mode 100644 index 0000000..723d6fc --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r2/bgpd.conf @@ -0,0 +1,50 @@ +frr defaults traditional +! +hostname r2 +password zebra +! +log commands +! +router bgp 65002 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:12::1 remote-as 65001 + neighbor 2001:db8:12::1 timers 3 10 + neighbor 2001:db8:12::1 timers connect 1 + neighbor 2001:db8:12::1 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:12::1 activate + exit-address-family + ! +! +router bgp 65002 vrf vrf10 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65002:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65002 vrf vrf20 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65002:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r2/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/r2/staticd.conf new file mode 100644 index 0000000..8d80c1e --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r2/staticd.conf @@ -0,0 +1,4 @@ +! +ipv6 route 2001:db8:1:1::/64 2001:db8:12::1 +ipv6 route 2001:db8:3:3::/64 2001:db8:12::1 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r2/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/r2/zebra.conf new file mode 100644 index 0000000..09a65b9 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r2/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log +! +hostname r2 +! +interface lo + ipv6 address 2001:db8:2:2::1/128 +! +interface eth0 + ipv6 address 2001:db8:12::2/64 +! +interface eth1 vrf vrf10 + ip address 192.168.2.254/24 +! +interface eth2 vrf vrf20 + ip address 192.168.2.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:2:2::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r3/bgpd.conf b/tests/topotests/bgp_srv6_sid_reachability/r3/bgpd.conf new file mode 100644 index 0000000..c412cb6 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r3/bgpd.conf @@ -0,0 +1,50 @@ +frr defaults traditional +! +hostname r2 +password zebra +! +log commands +! +router bgp 65001 + bgp router-id 192.0.2.3 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:13::1 remote-as 65001 + neighbor 2001:db8:13::1 timers 3 10 + neighbor 2001:db8:13::1 timers connect 1 + neighbor 2001:db8:13::1 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:13::1 activate + exit-address-family + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.3 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65001:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r3/staticd.conf b/tests/topotests/bgp_srv6_sid_reachability/r3/staticd.conf new file mode 100644 index 0000000..9d672d5 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r3/staticd.conf @@ -0,0 +1,6 @@ +! +ipv6 route 2001:db8:12::/64 2001:db8:13::1 +! +ipv6 route 2001:db8:1:1::/64 2001:db8:13::1 +ipv6 route 2001:db8:2:2::/64 2001:db8:13::1 +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/r3/zebra.conf b/tests/topotests/bgp_srv6_sid_reachability/r3/zebra.conf new file mode 100644 index 0000000..a20cb76 --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/r3/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log +! +hostname r2 +! +interface lo + ipv6 address 2001:db8:3:3::1/128 +! +interface eth0 + ipv6 address 2001:db8:13::3/64 +! +interface eth1 vrf vrf10 + ip address 192.168.3.254/24 +! +interface eth2 vrf vrf20 + ip address 192.168.3.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:3:3::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py b/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py new file mode 100755 index 0000000..92315bc --- /dev/null +++ b/tests/topotests/bgp_srv6_sid_reachability/test_bgp_srv6_sid_reachability.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2023 by 6WIND +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + + tgen.add_router("c11") + tgen.add_router("c12") + tgen.add_router("c21") + tgen.add_router("c22") + tgen.add_router("c31") + tgen.add_router("c32") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["r3"], "eth1", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["c11"], "eth2", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["c12"], "eth3", "eth0") + tgen.add_link(tgen.gears["r2"], tgen.gears["c21"], "eth1", "eth0") + tgen.add_link(tgen.gears["r2"], tgen.gears["c22"], "eth2", "eth0") + tgen.add_link(tgen.gears["r3"], tgen.gears["c31"], "eth1", "eth0") + tgen.add_link(tgen.gears["r3"], tgen.gears["c32"], "eth2", "eth0") + + +def setup_module(mod): + result = required_linux_kernel_version("5.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + + tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + + tgen.gears["r3"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r3"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r3"].run("ip link set vrf10 up") + tgen.gears["r3"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r3"].run("ip link set vrf20 up") + tgen.gears["r3"].run("ip link set eth1 master vrf10") + tgen.gears["r3"].run("ip link set eth2 master vrf20") + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_ping(): + tgen = get_topogen() + + check_ping("c11", "192.168.2.1", True, 10, 1) + check_ping("c11", "192.168.3.1", True, 10, 1) + check_ping("c12", "192.168.2.1", True, 10, 1) + check_ping("c12", "192.168.3.1", True, 10, 1) + check_ping("c21", "192.168.3.1", True, 10, 1) + check_ping("c22", "192.168.3.1", True, 10, 1) + + +def test_sid_unreachable_nht(): + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + no ipv6 route 2001:db8:2:2::/64 2001:db8:12::2 + """ + ) + check_ping("c11", "192.168.2.1", False, 10, 1) + + +def test_sid_reachable_again_nht(): + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + ipv6 route 2001:db8:2:2::/64 2001:db8:12::2 + """ + ) + check_ping("c11", "192.168.2.1", True, 10, 1) + + +def test_sid_unreachable_bgp_update(): + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 65002 + no segment-routing srv6 + exit + router bgp 65002 vrf vrf10 + address-family ipv4 unicast + no sid vpn export 1 + """ + ) + check_ping("c11", "192.168.2.1", False, 10, 1) + + +def test_sid_reachable_again_bgp_update(): + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 65002 + segment-routing srv6 + locator default + exit + exit + router bgp 65002 vrf vrf10 + address-family ipv4 unicast + sid vpn export 1 + """ + ) + check_ping("c11", "192.168.2.1", True, 10, 1) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/staticd.conf new file mode 100644 index 0000000..bcf5a04 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.1.254 +ipv6 route ::/0 2001:1::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/zebra.conf new file mode 100644 index 0000000..0615cf9 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c11/zebra.conf @@ -0,0 +1,6 @@ +hostname c11 +! +interface eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/staticd.conf new file mode 100644 index 0000000..bcf5a04 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.1.254 +ipv6 route ::/0 2001:1::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/zebra.conf new file mode 100644 index 0000000..18985aa --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c12/zebra.conf @@ -0,0 +1,6 @@ +hostname c12 +! +interface eth0 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/staticd.conf new file mode 100644 index 0000000..608e600 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/zebra.conf new file mode 100644 index 0000000..b8b70ac --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c21/zebra.conf @@ -0,0 +1,6 @@ +hostname c21 +! +interface eth0 + ip address 192.168.2.1/24 + ipv6 address 2001:2::1/64 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/staticd.conf new file mode 100644 index 0000000..277aae9 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/staticd.conf @@ -0,0 +1,5 @@ + +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! \ No newline at end of file diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/zebra.conf new file mode 100644 index 0000000..cc764cc --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c22/zebra.conf @@ -0,0 +1,9 @@ +hostname c22 +! +interface eth0 + ip address 192.168.2.1/24 + ipv6 address 2001:2::1/64 +! +ip route 0.0.0.0/0 192.168.2.254 +ipv6 route ::/0 2001:2::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/staticd.conf new file mode 100644 index 0000000..0c88575 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.3.254 +ipv6 route ::/0 2001:3::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/zebra.conf new file mode 100644 index 0000000..3f75641 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c31/zebra.conf @@ -0,0 +1,6 @@ +hostname c31 +! +interface eth0 + ip address 192.168.3.1/24 + ipv6 address 2001:3::1/64 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/staticd.conf new file mode 100644 index 0000000..0c88575 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/staticd.conf @@ -0,0 +1,4 @@ +! +ip route 0.0.0.0/0 192.168.3.254 +ipv6 route ::/0 2001:3::ffff +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/zebra.conf new file mode 100644 index 0000000..c06a7d1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/c32/zebra.conf @@ -0,0 +1,6 @@ +hostname c32 +! +interface eth0 + ip address 192.168.3.1/24 + ipv6 address 2001:3::1/64 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/bgpd.conf new file mode 100644 index 0000000..e8c14ab --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/bgpd.conf @@ -0,0 +1,56 @@ +frr defaults traditional +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +router bgp 65001 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:12::2 remote-as 65002 + neighbor 2001:db8:12::2 timers 3 10 + neighbor 2001:db8:12::2 timers connect 1 + neighbor 2001:db8:12::2 capability extended-nexthop + neighbor 2001:db8:13::3 remote-as 65001 + neighbor 2001:db8:13::3 timers 3 10 + neighbor 2001:db8:13::3 timers connect 1 + neighbor 2001:db8:13::3 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:12::2 activate + neighbor 2001:db8:13::3 activate + exit-address-family + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65001:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/staticd.conf new file mode 100644 index 0000000..49b64fd --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/staticd.conf @@ -0,0 +1,4 @@ +! +ipv6 route 2001:db8:2:2::/64 2001:db8:12::2 +ipv6 route 2001:db8:3:3::/64 2001:db8:13::3 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/zebra.conf new file mode 100644 index 0000000..79dbf95 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r1/zebra.conf @@ -0,0 +1,32 @@ +log file zebra.log +! +hostname r1 +! +interface lo + ipv6 address 2001:db8:1:1::1/128 +! +interface eth0 + ipv6 address 2001:db8:12::1/64 +! +interface eth1 + ipv6 address 2001:db8:13::1/64 +! +interface eth2 vrf vrf10 + ip address 192.168.1.254/24 +! +interface eth3 vrf vrf20 + ip address 192.168.1.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:1:1::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/bgpd.conf new file mode 100644 index 0000000..88de281 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/bgpd.conf @@ -0,0 +1,51 @@ +frr defaults traditional +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +router bgp 65002 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:12::1 remote-as 65001 + neighbor 2001:db8:12::1 timers 3 10 + neighbor 2001:db8:12::1 timers connect 1 + neighbor 2001:db8:12::1 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:12::1 activate + exit-address-family + ! +! +router bgp 65002 vrf vrf10 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65002:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65002 vrf vrf20 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65002:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/staticd.conf new file mode 100644 index 0000000..8d80c1e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/staticd.conf @@ -0,0 +1,4 @@ +! +ipv6 route 2001:db8:1:1::/64 2001:db8:12::1 +ipv6 route 2001:db8:3:3::/64 2001:db8:12::1 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/zebra.conf new file mode 100644 index 0000000..09a65b9 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r2/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log +! +hostname r2 +! +interface lo + ipv6 address 2001:db8:2:2::1/128 +! +interface eth0 + ipv6 address 2001:db8:12::2/64 +! +interface eth1 vrf vrf10 + ip address 192.168.2.254/24 +! +interface eth2 vrf vrf20 + ip address 192.168.2.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:2:2::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/bgpd.conf new file mode 100644 index 0000000..443a64a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/bgpd.conf @@ -0,0 +1,51 @@ +frr defaults traditional +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +router bgp 65001 + bgp router-id 192.0.2.3 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:13::1 remote-as 65001 + neighbor 2001:db8:13::1 timers 3 10 + neighbor 2001:db8:13::1 timers connect 1 + neighbor 2001:db8:13::1 capability extended-nexthop + ! + segment-routing srv6 + locator default + ! + address-family ipv4 vpn + neighbor 2001:db8:13::1 activate + exit-address-family + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.3 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 1 + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.2 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export 2 + rd vpn export 65001:20 + rt vpn both 0:20 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/staticd.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/staticd.conf new file mode 100644 index 0000000..9d672d5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/staticd.conf @@ -0,0 +1,6 @@ +! +ipv6 route 2001:db8:12::/64 2001:db8:13::1 +! +ipv6 route 2001:db8:1:1::/64 2001:db8:13::1 +ipv6 route 2001:db8:2:2::/64 2001:db8:13::1 +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/zebra.conf new file mode 100644 index 0000000..a20cb76 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/r3/zebra.conf @@ -0,0 +1,29 @@ +log file zebra.log +! +hostname r2 +! +interface lo + ipv6 address 2001:db8:3:3::1/128 +! +interface eth0 + ipv6 address 2001:db8:13::3/64 +! +interface eth1 vrf vrf10 + ip address 192.168.3.254/24 +! +interface eth2 vrf vrf20 + ip address 192.168.3.254/24 +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:3:3::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_over_ipv6/test_bgp_srv6l3vpn_over_ipv6.py b/tests/topotests/bgp_srv6l3vpn_over_ipv6/test_bgp_srv6l3vpn_over_ipv6.py new file mode 100755 index 0000000..14b9ba8 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_over_ipv6/test_bgp_srv6l3vpn_over_ipv6.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + + tgen.add_router("c11") + tgen.add_router("c12") + tgen.add_router("c21") + tgen.add_router("c22") + tgen.add_router("c31") + tgen.add_router("c32") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["r3"], "eth1", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["c11"], "eth2", "eth0") + tgen.add_link(tgen.gears["r1"], tgen.gears["c12"], "eth3", "eth0") + tgen.add_link(tgen.gears["r2"], tgen.gears["c21"], "eth1", "eth0") + tgen.add_link(tgen.gears["r2"], tgen.gears["c22"], "eth2", "eth0") + tgen.add_link(tgen.gears["r3"], tgen.gears["c31"], "eth1", "eth0") + tgen.add_link(tgen.gears["r3"], tgen.gears["c32"], "eth2", "eth0") + + +def setup_module(mod): + result = required_linux_kernel_version("5.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + + tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + + tgen.gears["r3"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r3"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r3"].run("ip link set vrf10 up") + tgen.gears["r3"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r3"].run("ip link set vrf20 up") + tgen.gears["r3"].run("ip link set eth1 master vrf10") + tgen.gears["r3"].run("ip link set eth2 master vrf20") + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_ping(): + tgen = get_topogen() + + check_ping("c11", "192.168.2.1", True, 10, 1) + check_ping("c11", "192.168.3.1", True, 10, 1) + check_ping("c12", "192.168.2.1", True, 10, 1) + check_ping("c12", "192.168.3.1", True, 10, 1) + check_ping("c21", "192.168.3.1", True, 10, 1) + check_ping("c22", "192.168.3.1", True, 10, 1) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_route_leak/ce1/bgpd.conf new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_route_leak/ce1/zebra.conf new file mode 100644 index 0000000..823a56d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/ce1/zebra.conf @@ -0,0 +1,9 @@ +log file zebra.log +! +hostname ce1 +! +interface eth0 + ip address 172.16.0.1/24 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/bgpd.conf new file mode 100644 index 0000000..3d9c2cf --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/bgpd.conf @@ -0,0 +1,40 @@ +frr defaults traditional +! +hostname pe1 +password zebra +! +log stdout notifications +log commands +! +router bgp 65001 + bgp router-id 192.0.2.1 + ! + segment-routing srv6 + locator default + exit + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + redistribute connected + sid vpn export auto + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + rd vpn export 65001:20 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/default_ipv4_vpn.json b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/default_ipv4_vpn.json new file mode 100644 index 0000000..dc86d7c --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/default_ipv4_vpn.json @@ -0,0 +1,31 @@ +{ + "vrfName": "default", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "routeDistinguishers": { + "65001:10": { + "172.16.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.0", + "prefixLen": 24, + "network": "172.16.0.0\/24", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf10_ipv4_unicast.json b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf10_ipv4_unicast.json new file mode 100644 index 0000000..ce2d5c1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf10_ipv4_unicast.json @@ -0,0 +1,25 @@ +{ + "vrfName": "vrf10", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.0", + "prefixLen": 24, + "network": "172.16.0.0\/24", + "origin": "incomplete", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4.json b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4.json new file mode 100644 index 0000000..2ce936b --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4.json @@ -0,0 +1,22 @@ +{ + "172.16.0.0\/24": [ + { + "prefix": "172.16.0.0\/24", + "prefixLen": 24, + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "installed": true, + "nexthops": [ + { + "fib": true, + "directlyConnected": true, + "interfaceName": "vrf10", + "vrf": "vrf10", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4_unicast.json b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4_unicast.json new file mode 100644 index 0000000..6a88d39 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/results/vrf20_ipv4_unicast.json @@ -0,0 +1,27 @@ +{ + "vrfName": "vrf20", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.0/24": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.0", + "prefixLen": 24, + "network": "172.16.0.0\/24", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/zebra.conf new file mode 100644 index 0000000..52341fc --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/pe1/zebra.conf @@ -0,0 +1,27 @@ +log file zebra.log +! +hostname pe1 +! +interface lo + ip address 10.0.0.1/32 +! +interface eth0 vrf vrf10 + ip address 172.16.0.254/24 +! +line vty +! +segment-routing + srv6 + locators + locator default + prefix 2001:db8:2::/64 block-len 40 node-len 24 func-bits 16 + exit + ! + exit + ! + exit + ! +exit +! +end +! diff --git a/tests/topotests/bgp_srv6l3vpn_route_leak/test_bgp_srv6l3vpn_route_leak.py b/tests/topotests/bgp_srv6l3vpn_route_leak/test_bgp_srv6l3vpn_route_leak.py new file mode 100755 index 0000000..f0c9144 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_route_leak/test_bgp_srv6l3vpn_route_leak.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022, LINE Corporation +# Authored by Ryoga Saito +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("pe1") + tgen.add_router("ce1") + + tgen.add_link(tgen.gears["pe1"], tgen.gears["ce1"], "eth0", "eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["pe1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["pe1"].run("ip link set vrf10 up") + tgen.gears["pe1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["pe1"].run("ip link set vrf20 up") + tgen.gears["pe1"].run("ip link set eth0 master vrf10") + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(path): + try: + with open(path, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(path) + + +def check(name, command, checker): + tgen = get_topogen() + router = tgen.gears[name] + + def _check(): + try: + return checker(router.vtysh_cmd(command)) + except: + return False + + logger.info('[+] check {} "{}"'.format(name, command)) + _, result = topotest.run_and_expect(_check, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def check_vrf10_bgp_rib(output): + expected = open_json_file("%s/pe1/results/vrf10_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_default_bgp_vpn_rib(output): + expected = open_json_file("%s/pe1/results/default_ipv4_vpn.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_vrf20_bgp_rib(output): + expected = open_json_file("%s/pe1/results/vrf20_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_vrf20_rib(output): + expected = open_json_file("%s/pe1/results/vrf20_ipv4.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def test_rib(): + check("pe1", "show bgp vrf vrf10 ipv4 unicast json", check_vrf10_bgp_rib) + check("pe1", "show bgp ipv4 vpn json", check_default_bgp_vpn_rib) + check("pe1", "show bgp vrf vrf20 ipv4 unicast json", check_vrf20_bgp_rib) + check("pe1", "show ip route vrf vrf20 json", check_vrf20_rib) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf new file mode 100644 index 0000000..3459796 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json new file mode 100644 index 0000000..d19e315 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:1::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf new file mode 100644 index 0000000..bb5f93f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce1/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce1 +! +interface eth0 + ipv6 address 2001:1::2/64 + ip address 192.168.1.2/24 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:1::1 +ip route 0.0.0.0/0 192.168.1.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf new file mode 100644 index 0000000..8ed9978 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce2 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json new file mode 100644 index 0000000..35ff14e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:2::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf new file mode 100644 index 0000000..a52b83f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce2/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce2 +! +interface eth0 + ipv6 address 2001:2::2/64 + ip address 192.168.2.2/24 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:2::1 +ip route 0.0.0.0/0 192.168.2.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf new file mode 100644 index 0000000..a85d970 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce3 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json new file mode 100644 index 0000000..2f2931f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:3::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf new file mode 100644 index 0000000..beca0b1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce3/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce3 +! +interface eth0 + ipv6 address 2001:3::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:3::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf new file mode 100644 index 0000000..93fb32f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce4 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json new file mode 100644 index 0000000..8a98768 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:4::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf new file mode 100644 index 0000000..7b21074 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce4/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce4 +! +interface eth0 + ipv6 address 2001:4::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:4::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf new file mode 100644 index 0000000..2ab6f2d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce5 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json new file mode 100644 index 0000000..80ff52a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:5::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf new file mode 100644 index 0000000..b5ad48e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce5/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce5 +! +interface eth0 + ipv6 address 2001:5::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:5::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf new file mode 100644 index 0000000..e0b6540 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce6 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json new file mode 100644 index 0000000..ace6136 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:6::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf new file mode 100644 index 0000000..7d19d98 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/ce6/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce6 +! +interface eth0 + ipv6 address 2001:6::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:6::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf new file mode 100644 index 0000000..8805009 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/bgpd.conf @@ -0,0 +1,78 @@ +frr defaults traditional +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 1 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::2 remote-as 2 + neighbor 2001::2 timers 3 10 + neighbor 2001::2 timers connect 1 + neighbor 2001::2 update-source 2001::1 + neighbor 2001::2 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::2 activate + exit-address-family + ! + address-family ipv6 vpn + neighbor 2001::2 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 1 vrf vrf10 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 1:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! + address-family ipv4 unicast + network 192.168.1.0/24 + sid vpn export auto + rd vpn export 11:10 + rt vpn both 77:77 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 1 vrf vrf20 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 1:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json new file mode 100644 index 0000000..f054fab --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vpnv6_rib.json @@ -0,0 +1,168 @@ +{ + "vrfName": "default", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json new file mode 100644 index 0000000..9783c7e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_no_sid_rib.json @@ -0,0 +1,23 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json new file mode 100644 index 0000000..80c1acf --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_auto_sid_rib.json @@ -0,0 +1,53 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "labels":[ + 16 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:1::" + } + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json new file mode 100644 index 0000000..9783c7e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_no_sid_rib.json @@ -0,0 +1,23 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json new file mode 100644 index 0000000..07ca64b --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv4_manual_sid_rib.json @@ -0,0 +1,54 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"2001::2", + "afi":"ipv6", + "labels":[ + 128 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:8::" + } + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json new file mode 100644 index 0000000..6ac8dac --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_no_sid_rib.json @@ -0,0 +1,77 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json new file mode 100644 index 0000000..fac3d1d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_auto_sid_rib.json @@ -0,0 +1,107 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "labels":[ + 32 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:2::" + } + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json new file mode 100644 index 0000000..69ce312 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_no_sid_rib.json @@ -0,0 +1,77 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json new file mode 100644 index 0000000..04e2305 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_afv6_manual_sid_rib.json @@ -0,0 +1,106 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "labels":[ + 128 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:8::" + } + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json new file mode 100644 index 0000000..3cac156 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_auto_sid_rib.json @@ -0,0 +1,54 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"2001::2", + "afi":"ipv6", + "labels":[ + 16 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:1::" + } + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json new file mode 100644 index 0000000..163e9d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf4_manual_sid_rib.json @@ -0,0 +1,53 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "192.168.2.0\/24":[ + { + "prefix":"192.168.2.0\/24", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"2001::2", + "afi":"ipv6", + "labels":[ + 128 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:8::" + } + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json new file mode 100644 index 0000000..1313f20 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_auto_sid_rib.json @@ -0,0 +1,106 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "labels":[ + 16 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:1::" + } + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json new file mode 100644 index 0000000..51f249b --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf6_manual_sid_rib.json @@ -0,0 +1,106 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "labels":[ + 128 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:8::" + } + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json new file mode 100644 index 0000000..6ac8dac --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_auto_no_sid_rib.json @@ -0,0 +1,77 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} + diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json new file mode 100644 index 0000000..1c3dad0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_pervrf_manual_no_sid_rib.json @@ -0,0 +1,22 @@ +{ + "192.168.1.0\/24":[ + { + "prefix":"192.168.1.0\/24", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json new file mode 100644 index 0000000..9579bb1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf10_rib.json @@ -0,0 +1,112 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 32 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:2::" + } + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json new file mode 100644 index 0000000..25f146f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/vrf20_rib.json @@ -0,0 +1,106 @@ +{ + "2001:4::\/64":[ + { + "prefix":"2001:4::\/64", + "protocol":"bgp", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 48 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:3::" + } + } + ] + } + ], + "2001:5::\/64":[ + { + "prefix":"2001:5::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "2001:6::\/64":[ + { + "prefix":"2001:6::\/64", + "protocol":"bgp", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 48 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:2:2:3::" + } + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf new file mode 100644 index 0000000..be43805 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r1/zebra.conf @@ -0,0 +1,42 @@ +log file zebra.log +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::1/64 +! +interface eth1 vrf vrf10 + ipv6 address 2001:1::1/64 + ip address 192.168.1.1/24 +! +interface eth2 vrf vrf10 + ipv6 address 2001:3::1/64 +! +interface eth3 vrf vrf20 + ipv6 address 2001:5::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 block-len 40 node-len 24 func-bits 16 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:2:1::/64 2001::2 +ipv6 route 2001:db8:2:2::/64 2001::2 +ipv6 route 2001:db8:2:3::/64 2001::2 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf new file mode 100644 index 0000000..0770a96 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/bgpd.conf @@ -0,0 +1,79 @@ +frr defaults traditional +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp updates +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 2 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::1 remote-as 1 + neighbor 2001::1 update-source 2001::2 + neighbor 2001::1 timers 3 10 + neighbor 2001::1 timers connect 1 + neighbor 2001::1 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::1 activate + exit-address-family + ! + address-family ipv6 vpn + neighbor 2001::1 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 2 vrf vrf10 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 2:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! + address-family ipv4 unicast + network 192.168.2.0/24 + sid vpn export auto + rd vpn export 22:10 + rt vpn both 77:77 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 2 vrf vrf20 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 2:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +!! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json new file mode 100644 index 0000000..60bcb75 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vpnv6_rib.json @@ -0,0 +1,168 @@ +{ + "vrfName": "default", + "routerId": "192.0.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json new file mode 100644 index 0000000..446bb8e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf10_rib.json @@ -0,0 +1,106 @@ +{ + "2001:1::\/64":[ + { + "prefix":"2001:1::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 32 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:1:1:2::" + } + } + ] + } + ], + "2001:2::\/64":[ + { + "prefix":"2001:2::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "2001:3::\/64":[ + { + "prefix":"2001:3::\/64", + "protocol":"bgp", + "vrfName":"vrf10", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 32 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:1:1:2::" + } + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf10", + "distance":0, + "metric":0, + "installed":true, + "table":10, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json new file mode 100644 index 0000000..8bc2fc2 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/vrf20_rib.json @@ -0,0 +1,112 @@ +{ + "2001:4::\/64":[ + { + "prefix":"2001:4::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "2001:5::\/64":[ + { + "prefix":"2001:5::\/64", + "protocol":"bgp", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "afi":"ipv6", + "vrf":"default", + "active":true, + "labels":[ + 48 + ], + "weight":1, + "seg6local":{ + "action":"unspec" + }, + "seg6":{ + "segs":"2001:db8:1:1:3::" + } + } + ] + } + ], + "2001:6::\/64":[ + { + "prefix":"2001:6::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "selected":true, + "destSelected":true, + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ], + "fe80::\/64":[ + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + }, + { + "prefix":"fe80::\/64", + "protocol":"connected", + "vrfName":"vrf20", + "distance":0, + "metric":0, + "installed":true, + "table":20, + "nexthops":[ + { + "flags":3, + "fib":true, + "directlyConnected":true, + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf new file mode 100644 index 0000000..70627ad --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/r2/zebra.conf @@ -0,0 +1,42 @@ +log file zebra.log +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::2/64 +! +interface eth1 vrf vrf10 + ipv6 address 2001:2::1/64 + ip address 192.168.2.1/24 +! +interface eth2 vrf vrf20 + ipv6 address 2001:4::1/64 +! +interface eth3 vrf vrf20 + ipv6 address 2001:6::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:2:2::/64 block-len 40 node-len 24 func-bits 16 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:1:1::/64 2001::1 +ipv6 route 2001:db8:1:2::/64 2001::1 +ipv6 route 2001:db8:1:3::/64 2001::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py new file mode 100755 index 0000000..984cf97 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright 2023 6WIND S.A. +# Authored by Dmytro Shytyi +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping + + +def build_topo(tgen): + r""" + CE1 CE3 CE5 + (eth0) (eth0) (eth0) + :2 :2 :2 + | | | + 192.168.1.0 | | + /24 | | + 2001: 2001: 2001: + 1::/64 3::/64 5::/64 + | | | + :1 :1 :1 + +-(eth1)--(eth2)---(eth3)-+ + | \ / | | + | (vrf10) (vrf20) | + | R1 | + +----------(eth0)---------+ + :1 + | + 2001::/64 + | + :2 + (eth0) + +----------(eth0)--------------+ + | R2 | + | (vrf10) (vrf20) | + | / / \ | + +-(eth1)-----(eth2)-----(eth3)-+ + :1 :1 :1 + | | | + +------+ +------+ +------+ + / 2001: \ / 2001: \ / 2001: \ + / 2::/64 \ 4::/64 / \ 6::/64 / + /192.168.2.0| / \ / + \ /24 / \ | | | + +------+ +------+ +------+ + | | | + :2 :2 :2 + (eth0) (eth0) (eth0) + CE2 CE4 CE6 + """ + + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("ce3") + tgen.add_router("ce4") + tgen.add_router("ce5") + tgen.add_router("ce6") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r2"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r1"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce4"], tgen.gears["r2"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce5"], tgen.gears["r1"], "eth0", "eth3") + tgen.add_link(tgen.gears["ce6"], tgen.gears["r2"], "eth0", "eth3") + + +def setup_module(mod): + result = required_linux_kernel_version("5.11") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + router_list = tgen.routers() + for i, (rname, router) in enumerate(tgen.routers().items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("modprobe vrf") + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth1 master vrf10") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r1"].run("sysctl net.ipv4.conf.default.rp_filter=0") + tgen.gears["r1"].run("sysctl net.ipv4.conf.all.rp_filter=0") + tgen.gears["r1"].run("sysctl net.ipv4.conf.lo.rp_filter=0") + tgen.gears["r1"].run("sysctl net.ipv4.conf.eth0.rp_filter=0") + tgen.gears["r1"].run("sysctl net.ipv4.conf.eth1.rp_filter=0") + tgen.gears["r1"].run("sysctl net.ipv4.conf.vrf10.rp_filter=0") + + tgen.gears["r2"].run("modprobe vrf") + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + tgen.gears["r2"].run("ip link set eth3 master vrf20") + tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r2"].run("sysctl net.ipv4.conf.default.rp_filter=0") + tgen.gears["r2"].run("sysctl net.ipv4.conf.all.rp_filter=0") + tgen.gears["r2"].run("sysctl net.ipv4.conf.lo.rp_filter=0") + tgen.gears["r2"].run("sysctl net.ipv4.conf.eth0.rp_filter=0") + tgen.gears["r2"].run("sysctl net.ipv4.conf.eth1.rp_filter=0") + tgen.gears["r2"].run("sysctl net.ipv4.conf.vrf10.rp_filter=0") + tgen.start_router() + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + # Example: + # tgen=get_topogen() + # tgen.mininet_cli() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def check_rib(name, cmd, expected_file): + def _check(name, cmd, expected_file): + logger.info("polling") + tgen = get_topogen() + router = tgen.gears[name] + output = json.loads(router.vtysh_cmd(cmd)) + expected = open_json_file("{}/{}".format(CWD, expected_file)) + return topotest.json_cmp(output, expected) + + logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file)) + tgen = get_topogen() + func = functools.partial(_check, name, cmd, expected_file) + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib.json") + check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_rib.json") + check_rib("r1", "show ipv6 route vrf vrf20 json", "r1/vrf20_rib.json") + check_rib("r2", "show ipv6 route vrf vrf10 json", "r2/vrf10_rib.json") + check_rib("r2", "show ipv6 route vrf vrf20 json", "r2/vrf20_rib.json") + check_rib("ce1", "show ipv6 route json", "ce1/ipv6_rib.json") + check_rib("ce2", "show ipv6 route json", "ce2/ipv6_rib.json") + check_rib("ce3", "show ipv6 route json", "ce3/ipv6_rib.json") + check_rib("ce4", "show ipv6 route json", "ce4/ipv6_rib.json") + check_rib("ce5", "show ipv6 route json", "ce5/ipv6_rib.json") + check_rib("ce6", "show ipv6 route json", "ce6/ipv6_rib.json") + + +def test_ping(): + check_ping("ce1", "2001:2::2", True, 10, 0.5) + check_ping("ce1", "2001:3::2", True, 10, 0.5) + check_ping("ce1", "2001:4::2", False, 10, 0.5) + check_ping("ce1", "2001:5::2", False, 10, 0.5) + check_ping("ce1", "2001:6::2", False, 10, 0.5) + check_ping("ce4", "2001:1::2", False, 10, 0.5) + check_ping("ce4", "2001:2::2", False, 10, 0.5) + check_ping("ce4", "2001:3::2", False, 10, 0.5) + check_ping("ce4", "2001:5::2", True, 10, 0.5) + check_ping("ce4", "2001:6::2", True, 10, 0.5) + + +def test_sid_per_afv6_auto(): + check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_sid_rib.json") + check_ping("ce1", "2001:2::2", True, 10, 0.5) + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv6 unicast + no sid vpn export auto + """ + ) + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv6 unicast + sid vpn export auto + """ + ) + check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_sid_rib.json") + check_ping("ce1", "2001:2::2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv6 unicast + no sid vpn export auto + """ + ) + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_auto_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + + +def test_sid_per_afv6_manual(): + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv6 unicast + sid vpn export 8 + """ + ) + + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv6 unicast + no sid vpn export 8 + """ + ) + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_afv6_manual_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + + +def test_sid_per_afv4_auto(): + check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_sid_rib.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv4 unicast + no sid vpn export auto + """ + ) + + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_no_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv4 unicast + sid vpn export auto + """ + ) + + check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_sid_rib.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv4 unicast + no sid vpn export auto + """ + ) + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_auto_no_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + + +def test_sid_per_afv4_manual(): + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_no_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv4 unicast + sid vpn export 8 + """ + ) + + check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_sid_rib.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + address-family ipv4 unicast + no sid vpn export 8 + """ + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_afv4_manual_no_sid_rib.json" + ) + + +def test_sid_per_vrf_auto(): + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf_auto_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + sid vpn per-vrf export auto + """ + ) + + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf6_auto_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", True, 10, 0.5) + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf4_auto_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + no sid vpn per-vrf export auto + """ + ) + + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf_auto_no_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", False, 10, 0.5) + + +def test_sid_per_vrf_manual(): + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf_manual_no_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + sid vpn per-vrf export 8 + """ + ) + + check_rib( + "r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_pervrf6_manual_sid_rib.json" + ) + check_ping("ce1", "2001:2::2", True, 10, 0.5) + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf4_manual_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + + get_topogen().gears["r2"].vtysh_cmd( + """ + configure terminal + router bgp 2 vrf vrf10 + no sid vpn per-vrf export 8 + """ + ) + + check_rib( + "r1", "show ip route vrf vrf10 json", "r1/vrf10_pervrf_manual_no_sid_rib.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/bgpd.conf new file mode 100644 index 0000000..3459796 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/ipv6_rib.json new file mode 100644 index 0000000..d19e315 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:1::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/zebra.conf new file mode 100644 index 0000000..665808a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce1/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce1 +! +interface eth0 + ipv6 address 2001:1::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:1::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/bgpd.conf new file mode 100644 index 0000000..8ed9978 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce2 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/ipv6_rib.json new file mode 100644 index 0000000..35ff14e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:2::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/zebra.conf new file mode 100644 index 0000000..cc9b90a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce2/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce2 +! +interface eth0 + ipv6 address 2001:2::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:2::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/bgpd.conf new file mode 100644 index 0000000..a85d970 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce3 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/ipv6_rib.json new file mode 100644 index 0000000..2f2931f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:3::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/zebra.conf new file mode 100644 index 0000000..beca0b1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce3/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce3 +! +interface eth0 + ipv6 address 2001:3::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:3::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/bgpd.conf new file mode 100644 index 0000000..93fb32f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce4 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/ipv6_rib.json new file mode 100644 index 0000000..8a98768 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:4::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/zebra.conf new file mode 100644 index 0000000..7b21074 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce4/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce4 +! +interface eth0 + ipv6 address 2001:4::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:4::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/bgpd.conf new file mode 100644 index 0000000..2ab6f2d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce5 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/ipv6_rib.json new file mode 100644 index 0000000..80ff52a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:5::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/zebra.conf new file mode 100644 index 0000000..b5ad48e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce5/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce5 +! +interface eth0 + ipv6 address 2001:5::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:5::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/bgpd.conf new file mode 100644 index 0000000..e0b6540 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce6 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/ipv6_rib.json new file mode 100644 index 0000000..ace6136 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:6::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/zebra.conf new file mode 100644 index 0000000..7d19d98 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/ce6/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce6 +! +interface eth0 + ipv6 address 2001:6::2/64 +! +ip forwarding +ipv6 forwarding +! +ipv6 route ::/0 2001:6::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/bgpd.conf new file mode 100644 index 0000000..8079bb0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/bgpd.conf @@ -0,0 +1,65 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 1 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::2 remote-as 2 + neighbor 2001::2 timers 3 10 + neighbor 2001::2 timers connect 1 + ! + address-family ipv6 vpn + neighbor 2001::2 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 1 vrf vrf10 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 1:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 1 vrf vrf20 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 1:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json new file mode 100644 index 0000000..0fdd3d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_deleted.json new file mode 100644 index 0000000..f2df9be --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_deleted.json @@ -0,0 +1,160 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_recreated.json new file mode 100644 index 0000000..0fdd3d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vpnv6_rib_locator_recreated.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf10_rib.json new file mode 100644 index 0000000..141c1cb --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf10_rib.json @@ -0,0 +1,86 @@ +{ + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:100::" + } + } + ], + "asPath": "2" + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf20_rib.json new file mode 100644 index 0000000..e209980 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/vrf20_rib.json @@ -0,0 +1,92 @@ +{ + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:200::" + } + } + ], + "asPath": "2" + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:200::" + } + } + ], + "asPath": "2" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/zebra.conf new file mode 100644 index 0000000..c84106f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r1/zebra.conf @@ -0,0 +1,41 @@ +log file zebra.log +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +! debug zebra packet +! debug zebra dplane +! debug zebra kernel +! +interface eth0 + ipv6 address 2001::1/64 +! +interface eth1 vrf vrf10 + ipv6 address 2001:1::1/64 +! +interface eth2 vrf vrf10 + ipv6 address 2001:3::1/64 +! +interface eth3 vrf vrf20 + ipv6 address 2001:5::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 func-bits 8 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:2:1::/64 2001::2 +ipv6 route 2001:db8:2:2::/64 2001::2 +ipv6 route 2001:db8:2:3::/64 2001::2 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/bgpd.conf new file mode 100644 index 0000000..c2e8530 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/bgpd.conf @@ -0,0 +1,66 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp updates +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 2 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::1 remote-as 1 + neighbor 2001::1 timers 3 10 + neighbor 2001::1 timers connect 1 + ! + address-family ipv6 vpn + neighbor 2001::1 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 2 vrf vrf10 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 2:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 2 vrf vrf20 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + ! + address-family ipv6 unicast + sid vpn export auto + rd vpn export 2:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json new file mode 100644 index 0000000..03bbcc0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_deleted.json new file mode 100644 index 0000000..25cdf03 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_deleted.json @@ -0,0 +1,93 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_recreated.json new file mode 100644 index 0000000..03bbcc0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vpnv6_rib_locator_recreated.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf10_rib.json new file mode 100644 index 0000000..7f8a930 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf10_rib.json @@ -0,0 +1,92 @@ +{ + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:100::" + } + } + ], + "asPath": "1" + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:100::" + } + } + ], + "asPath": "1" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf20_rib.json new file mode 100644 index 0000000..104bdc3 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/vrf20_rib.json @@ -0,0 +1,86 @@ +{ + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:200::" + } + } + ], + "asPath": "1" + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/zebra.conf new file mode 100644 index 0000000..5c12429 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/r2/zebra.conf @@ -0,0 +1,41 @@ +log file zebra.log +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +! debug zebra packet +! debug zebra dplane +! debug zebra kernel +! +interface eth0 + ipv6 address 2001::2/64 +! +interface eth1 vrf vrf10 + ipv6 address 2001:2::1/64 +! +interface eth2 vrf vrf20 + ipv6 address 2001:4::1/64 +! +interface eth3 vrf vrf20 + ipv6 address 2001:6::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:2:2::/64 func-bits 8 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:1:1::/64 2001::1 +ipv6 route 2001:db8:1:2::/64 2001::1 +ipv6 route 2001:db8:1:3::/64 2001::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/test_bgp_srv6l3vpn_to_bgp_vrf.py b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/test_bgp_srv6l3vpn_to_bgp_vrf.py new file mode 100755 index 0000000..4afaeaf --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf/test_bgp_srv6l3vpn_to_bgp_vrf.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r""" + CE1 CE3 CE5 + (eth0) (eth0) (eth0) + :2 :2 :2 + | | | + 2001: 2001: 2001: + 1::/64 3::/64 5::/64 + | | | + :1 :1 :1 + +-(eth1)--(eth2)---(eth3)-+ + | \ / | | + | (vrf10) (vrf20) | + | R1 | + +----------(eth0)---------+ + :1 + | + 2001::/64 + | + :2 + (eth0) + +----------(eth0)--------------+ + | R2 | + | (vrf10) (vrf20) | + | / / \ | + +-(eth1)-----(eth2)-----(eth3)-+ + :1 :1 :1 + | | | + +------+ +------+ +------+ + / 2001: \ / 2001: \ / 2001: \ + \ 2::/64 / \ 4::/64 / \ 6::/64 / + +------+ +------+ +------+ + | | | + :2 :2 :2 + (eth0) (eth0) (eth0) + CE2 CE4 CE6 + """ + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("ce3") + tgen.add_router("ce4") + tgen.add_router("ce5") + tgen.add_router("ce6") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r2"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r1"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce4"], tgen.gears["r2"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce5"], tgen.gears["r1"], "eth0", "eth3") + tgen.add_link(tgen.gears["ce6"], tgen.gears["r2"], "eth0", "eth3") + + +def setup_module(mod): + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + router_list = tgen.routers() + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname)) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth1 master vrf10") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + tgen.gears["r2"].run("ip link set eth3 master vrf20") + tgen.start_router() + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def check_rib(name, cmd, expected_file): + def _check(name, cmd, expected_file): + logger.info("polling") + tgen = get_topogen() + router = tgen.gears[name] + output = json.loads(router.vtysh_cmd(cmd)) + expected = open_json_file("{}/{}".format(CWD, expected_file)) + return topotest.json_cmp(output, expected) + + logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file)) + tgen = get_topogen() + func = functools.partial(_check, name, cmd, expected_file) + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib.json") + check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10_rib.json") + check_rib("r1", "show ipv6 route vrf vrf20 json", "r1/vrf20_rib.json") + check_rib("r2", "show ipv6 route vrf vrf10 json", "r2/vrf10_rib.json") + check_rib("r2", "show ipv6 route vrf vrf20 json", "r2/vrf20_rib.json") + check_rib("ce1", "show ipv6 route json", "ce1/ipv6_rib.json") + check_rib("ce2", "show ipv6 route json", "ce2/ipv6_rib.json") + check_rib("ce3", "show ipv6 route json", "ce3/ipv6_rib.json") + check_rib("ce4", "show ipv6 route json", "ce4/ipv6_rib.json") + check_rib("ce5", "show ipv6 route json", "ce5/ipv6_rib.json") + check_rib("ce6", "show ipv6 route json", "ce6/ipv6_rib.json") + + +def test_ping(): + check_ping("ce1", "2001:2::2", True, 10, 1) + check_ping("ce1", "2001:3::2", True, 10, 1) + check_ping("ce1", "2001:4::2", False, 10, 1) + check_ping("ce1", "2001:5::2", False, 10, 1) + check_ping("ce1", "2001:6::2", False, 10, 1) + check_ping("ce4", "2001:1::2", False, 10, 1) + check_ping("ce4", "2001:2::2", False, 10, 1) + check_ping("ce4", "2001:3::2", False, 10, 1) + check_ping("ce4", "2001:5::2", True, 10, 1) + check_ping("ce4", "2001:6::2", True, 10, 1) + + +def test_locator_delete(): + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + locators + no locator loc1 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_locator_recreate(): + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "2001:2::2", True, 10, 1) + + +def test_bgp_locator_unset(): + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + no locator loc1 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_bgp_locator_reset(): + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + locator loc1 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "2001:2::2", True, 10, 1) + + +def test_bgp_srv6_unset(): + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + no segment-routing srv6 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_bgp_srv6_reset(): + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + locator loc1 + """ + ) + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "2001:2::2", True, 10, 1) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/bgpd.conf new file mode 100644 index 0000000..3459796 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/ip_rib.json new file mode 100644 index 0000000..1d33fee --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.1.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/zebra.conf new file mode 100644 index 0000000..447d1b4 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce1/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce1 +! +interface eth0 + ip address 192.168.1.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.1.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/bgpd.conf new file mode 100644 index 0000000..8ed9978 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce2 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/ip_rib.json new file mode 100644 index 0000000..a21f4a1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.2.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/zebra.conf new file mode 100644 index 0000000..1165225 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce2/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce2 +! +interface eth0 + ip address 192.168.2.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.2.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/bgpd.conf new file mode 100644 index 0000000..a85d970 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce3 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/ip_rib.json new file mode 100644 index 0000000..38a7807 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.3.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/zebra.conf new file mode 100644 index 0000000..299c659 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce3/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce3 +! +interface eth0 + ip address 192.168.3.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.3.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/bgpd.conf new file mode 100644 index 0000000..93fb32f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce4 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/ip_rib.json new file mode 100644 index 0000000..a0be78e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.4.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/zebra.conf new file mode 100644 index 0000000..30f3736 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce4/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce4 +! +interface eth0 + ip address 192.168.4.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.4.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/bgpd.conf new file mode 100644 index 0000000..2ab6f2d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce5 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/ip_rib.json new file mode 100644 index 0000000..dc338d5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.5.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/zebra.conf new file mode 100644 index 0000000..208dcb1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce5/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce5 +! +interface eth0 + ip address 192.168.5.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.5.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/bgpd.conf new file mode 100644 index 0000000..e0b6540 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce6 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/ip_rib.json new file mode 100644 index 0000000..4a603a5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.6.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/zebra.conf new file mode 100644 index 0000000..d68a008 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/ce6/zebra.conf @@ -0,0 +1,14 @@ +log file zebra.log +! +hostname ce6 +! +interface eth0 + ip address 192.168.6.2/24 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.6.1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/bgpd.conf new file mode 100644 index 0000000..12b9e8f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/bgpd.conf @@ -0,0 +1,67 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 1 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::2 remote-as 2 + neighbor 2001::2 timers 3 10 + neighbor 2001::2 timers connect 1 + neighbor 2001::2 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::2 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 1 vrf vrf10 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + ! + address-family ipv4 unicast + sid vpn export auto + nexthop vpn export 2001::1 + rd vpn export 1:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + ! + exit-address-family +! +router bgp 1 vrf vrf20 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + ! + address-family ipv4 unicast + sid vpn export auto + nexthop vpn export 2001::1 + rd vpn export 1:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vpnv4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vpnv4_rib.json new file mode 100644 index 0000000..7a4e0d7 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vpnv4_rib.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf10_rib.json new file mode 100644 index 0000000..8daa9b1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf10_rib.json @@ -0,0 +1,86 @@ +{ + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:100::" + } + } + ], + "asPath": "2" + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf20_rib.json new file mode 100644 index 0000000..6f123cf --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/vrf20_rib.json @@ -0,0 +1,92 @@ +{ + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:200::" + } + } + ], + "asPath": "2" + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:200::" + } + } + ], + "asPath": "2" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf new file mode 100644 index 0000000..f202493 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r1/zebra.conf @@ -0,0 +1,40 @@ +log file zebra.log +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::1/64 +! +interface eth1 vrf vrf10 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! +interface eth2 vrf vrf10 + ip address 192.168.3.1/24 +! +interface eth3 vrf vrf20 + ip address 192.168.5.1/24 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 func-bits 8 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:2:2::/64 2001::2 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/bgpd.conf new file mode 100644 index 0000000..db36c18 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/bgpd.conf @@ -0,0 +1,67 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp updates +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 2 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001::1 remote-as 1 + neighbor 2001::1 timers 3 10 + neighbor 2001::1 timers connect 1 + neighbor 2001::1 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::1 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 2 vrf vrf10 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + ! + address-family ipv4 unicast + sid vpn export auto + nexthop vpn export 2001::2 + rd vpn export 2:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 2 vrf vrf20 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + ! + address-family ipv4 unicast + sid vpn export auto + nexthop vpn export 2001::2 + rd vpn export 2:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vpnv4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vpnv4_rib.json new file mode 100644 index 0000000..0dcdec6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vpnv4_rib.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf10_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf10_rib.json new file mode 100644 index 0000000..6268031 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf10_rib.json @@ -0,0 +1,92 @@ +{ + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:100::" + } + } + ], + "asPath": "1" + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:100::" + } + } + ], + "asPath": "1" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf20_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf20_rib.json new file mode 100644 index 0000000..ffe2e07 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/vrf20_rib.json @@ -0,0 +1,86 @@ +{ + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:200::" + } + } + ], + "asPath": "1" + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf new file mode 100644 index 0000000..9dfed4f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/r2/zebra.conf @@ -0,0 +1,39 @@ +log file zebra.log +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::2/64 +! +interface eth1 vrf vrf10 + ip address 192.168.2.1/24 +! +interface eth2 vrf vrf20 + ip address 192.168.4.1/24 +! +interface eth3 vrf vrf20 + ip address 192.168.6.1/24 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:2:2::/64 func-bits 8 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:1:1::/64 2001::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/test_bgp_srv6l3vpn_to_bgp_vrf2.py b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/test_bgp_srv6l3vpn_to_bgp_vrf2.py new file mode 100755 index 0000000..914c29f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf2/test_bgp_srv6l3vpn_to_bgp_vrf2.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018, LabN Consulting, L.L.C. +# Authored by Lou Berger +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("ce3") + tgen.add_router("ce4") + tgen.add_router("ce5") + tgen.add_router("ce6") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r2"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r1"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce4"], tgen.gears["r2"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce5"], tgen.gears["r1"], "eth0", "eth3") + tgen.add_link(tgen.gears["ce6"], tgen.gears["r2"], "eth0", "eth3") + + +def setup_module(mod): + result = required_linux_kernel_version("5.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname)) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth1 master vrf10") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + + tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + tgen.gears["r2"].run("ip link set eth3 master vrf20") + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def check_rib(name, cmd, expected_file): + def _check(name, dest_addr, match): + logger.info("polling") + tgen = get_topogen() + router = tgen.gears[name] + output = json.loads(router.vtysh_cmd(cmd)) + expected = open_json_file("{}/{}".format(CWD, expected_file)) + return topotest.json_cmp(output, expected) + + logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file)) + tgen = get_topogen() + func = functools.partial(_check, name, cmd, expected_file) + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib.json") + check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10_rib.json") + check_rib("r1", "show ip route vrf vrf20 json", "r1/vrf20_rib.json") + check_rib("r2", "show ip route vrf vrf10 json", "r2/vrf10_rib.json") + check_rib("r2", "show ip route vrf vrf20 json", "r2/vrf20_rib.json") + check_rib("ce1", "show ip route json", "ce1/ip_rib.json") + check_rib("ce2", "show ip route json", "ce2/ip_rib.json") + check_rib("ce3", "show ip route json", "ce3/ip_rib.json") + check_rib("ce4", "show ip route json", "ce4/ip_rib.json") + check_rib("ce5", "show ip route json", "ce5/ip_rib.json") + check_rib("ce6", "show ip route json", "ce6/ip_rib.json") + + +def test_ping(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "192.168.3.2", True, 10, 0.5) + check_ping("ce1", "192.168.4.2", False, 10, 0.5) + check_ping("ce1", "192.168.5.2", False, 10, 0.5) + check_ping("ce1", "192.168.6.2", False, 10, 0.5) + check_ping("ce4", "192.168.1.2", False, 10, 0.5) + check_ping("ce4", "192.168.2.2", False, 10, 0.5) + check_ping("ce4", "192.168.3.2", False, 10, 0.5) + check_ping("ce4", "192.168.5.2", True, 10, 0.5) + check_ping("ce4", "192.168.6.2", True, 10, 0.5) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/bgpd.conf new file mode 100644 index 0000000..3459796 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ip_rib.json new file mode 100644 index 0000000..1d33fee --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.1.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ipv6_rib.json new file mode 100644 index 0000000..d19e315 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:1::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/zebra.conf new file mode 100644 index 0000000..58e851d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce1/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce1 +! +interface eth0 + ip address 192.168.1.2/24 + ipv6 address 2001:1::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.1.1 +ipv6 route ::/0 2001:1::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/bgpd.conf new file mode 100644 index 0000000..8ed9978 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce2 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ip_rib.json new file mode 100644 index 0000000..a21f4a1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.2.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ipv6_rib.json new file mode 100644 index 0000000..35ff14e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:2::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/zebra.conf new file mode 100644 index 0000000..0612c53 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce2/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce2 +! +interface eth0 + ip address 192.168.2.2/24 + ipv6 address 2001:2::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.2.1 +ipv6 route ::/0 2001:2::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/bgpd.conf new file mode 100644 index 0000000..a85d970 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce3 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ip_rib.json new file mode 100644 index 0000000..38a7807 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.3.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ipv6_rib.json new file mode 100644 index 0000000..2f2931f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:3::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/zebra.conf new file mode 100644 index 0000000..d08fdad --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce3/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce3 +! +interface eth0 + ip address 192.168.3.2/24 + ipv6 address 2001:3::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.3.1 +ipv6 route ::/0 2001:3::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/bgpd.conf new file mode 100644 index 0000000..93fb32f --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce4 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ip_rib.json new file mode 100644 index 0000000..a0be78e --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.4.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ipv6_rib.json new file mode 100644 index 0000000..8a98768 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:4::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/zebra.conf new file mode 100644 index 0000000..897ed46 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce4/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce4 +! +interface eth0 + ip address 192.168.4.2/24 + ipv6 address 2001:4::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.4.1 +ipv6 route ::/0 2001:4::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/bgpd.conf new file mode 100644 index 0000000..2ab6f2d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce5 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ip_rib.json new file mode 100644 index 0000000..dc338d5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.5.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ipv6_rib.json new file mode 100644 index 0000000..80ff52a --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:5::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/zebra.conf new file mode 100644 index 0000000..a290213 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce5/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce5 +! +interface eth0 + ip address 192.168.5.2/24 + ipv6 address 2001:5::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.5.1 +ipv6 route ::/0 2001:5::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/bgpd.conf new file mode 100644 index 0000000..e0b6540 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/bgpd.conf @@ -0,0 +1,8 @@ +frr defaults traditional +! +hostname ce6 +password zebra +! +log stdout notifications +log commands +log file bgpd.log diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ip_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ip_rib.json new file mode 100644 index 0000000..4a603a5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ip_rib.json @@ -0,0 +1,58 @@ +{ + "0.0.0.0/0": [ + { + "prefix": "0.0.0.0/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.6.1", + "afi": "ipv4", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ipv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ipv6_rib.json new file mode 100644 index 0000000..ace6136 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/ipv6_rib.json @@ -0,0 +1,58 @@ +{ + "::/0": [ + { + "prefix": "::/0", + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 73, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "2001:6::1", + "afi": "ipv6", + "interfaceName": "eth0", + "active": true, + "weight": 1 + } + ] + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "connected", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/zebra.conf new file mode 100644 index 0000000..5a83e90 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/ce6/zebra.conf @@ -0,0 +1,16 @@ +log file zebra.log +! +hostname ce6 +! +interface eth0 + ip address 192.168.6.2/24 + ipv6 address 2001:6::2/64 +! +ip forwarding +ipv6 forwarding +! +ip route 0.0.0.0/0 192.168.6.1 +ipv6 route ::/0 2001:6::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/bgpd.conf new file mode 100644 index 0000000..57c19e2 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/bgpd.conf @@ -0,0 +1,86 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 1 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + !no bgp default ipv4-unicast + neighbor 2001::2 remote-as 2 + neighbor 2001::2 timers 3 10 + neighbor 2001::2 timers connect 1 + neighbor 2001::2 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::2 activate + exit-address-family + ! + address-family ipv6 vpn + neighbor 2001::2 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 1 vrf vrf10 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + sid vpn per-vrf export auto + ! + address-family ipv4 unicast + nexthop vpn export 2001::1 + rd vpn export 1:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + rd vpn export 1:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 1 vrf vrf20 + bgp router-id 1.1.1.1 + no bgp ebgp-requires-policy + sid vpn per-vrf export auto + ! + address-family ipv4 unicast + nexthop vpn export 2001::1 + rd vpn export 1:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + rd vpn export 1:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib.json new file mode 100644 index 0000000..7a4e0d7 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_deleted.json new file mode 100644 index 0000000..5645540 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_deleted.json @@ -0,0 +1,90 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_recreated.json new file mode 100644 index 0000000..7a4e0d7 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_locator_recreated.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_disabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_disabled.json new file mode 100644 index 0000000..2050795 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_disabled.json @@ -0,0 +1,114 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_reenabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_reenabled.json new file mode 100644 index 0000000..7a4e0d7 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv4_rib_sid_vpn_export_reenabled.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib.json new file mode 100644 index 0000000..0fdd3d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_deleted.json new file mode 100644 index 0000000..f2df9be --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_deleted.json @@ -0,0 +1,160 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_recreated.json new file mode 100644 index 0000000..0fdd3d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_locator_recreated.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_disabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_disabled.json new file mode 100644 index 0000000..e289df1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_disabled.json @@ -0,0 +1,115 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_reenabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_reenabled.json new file mode 100644 index 0000000..0fdd3d6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vpnv6_rib_sid_vpn_export_reenabled.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "1.1.1.1", + "defaultLocPrf": 100, + "localAS": 1, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::2", + "path": "2", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v4_rib.json new file mode 100644 index 0000000..cc96f43 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v4_rib.json @@ -0,0 +1,86 @@ +{ + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:1::" + } + } + ], + "asPath": "2" + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v6_rib.json new file mode 100644 index 0000000..0c9ae73 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf10v6_rib.json @@ -0,0 +1,86 @@ +{ + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:1::" + } + } + ], + "asPath": "2" + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v4_rib.json new file mode 100644 index 0000000..cf0fd18 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v4_rib.json @@ -0,0 +1,92 @@ +{ + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:2::" + } + } + ], + "asPath": "2" + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:2::" + } + } + ], + "asPath": "2" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v6_rib.json new file mode 100644 index 0000000..e486e74 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/vrf20v6_rib.json @@ -0,0 +1,92 @@ +{ + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:2::" + } + } + ], + "asPath": "2" + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:2:2:2::" + } + } + ], + "asPath": "2" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf new file mode 100644 index 0000000..dd8a756 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r1/zebra.conf @@ -0,0 +1,42 @@ +log file zebra.log +! +hostname r1 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::1/64 +! +interface eth1 vrf vrf10 + ip address 192.168.1.1/24 + ipv6 address 2001:1::1/64 +! +interface eth2 vrf vrf10 + ip address 192.168.3.1/24 + ipv6 address 2001:3::1/64 +! +interface eth3 vrf vrf20 + ip address 192.168.5.1/24 + ipv6 address 2001:5::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:2:2::/64 2001::2 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/bgpd.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/bgpd.conf new file mode 100644 index 0000000..abf4971 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/bgpd.conf @@ -0,0 +1,87 @@ +frr defaults traditional +! +bgp send-extra-data zebra +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug bgp neighbor-events +!debug bgp zebra +!debug bgp vnc verbose +!debug bgp update-groups +!debug bgp updates in +!debug bgp updates out +!debug bgp updates +!debug bgp vpn label +!debug bgp vpn leak-from-vrf +!debug bgp vpn leak-to-vrf +!debug bgp vpn rmap-event +! +router bgp 2 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + !no bgp default ipv4-unicast + neighbor 2001::1 remote-as 1 + neighbor 2001::1 timers 3 10 + neighbor 2001::1 timers connect 1 + neighbor 2001::1 capability extended-nexthop + ! + address-family ipv4 vpn + neighbor 2001::1 activate + exit-address-family + ! + address-family ipv6 vpn + neighbor 2001::1 activate + exit-address-family + ! + segment-routing srv6 + locator loc1 + ! +! +router bgp 2 vrf vrf10 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + sid vpn per-vrf export auto + ! + address-family ipv4 unicast + nexthop vpn export 2001::2 + rd vpn export 2:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + rd vpn export 2:10 + rt vpn both 99:99 + import vpn + export vpn + redistribute connected + exit-address-family +! +router bgp 2 vrf vrf20 + bgp router-id 2.2.2.2 + no bgp ebgp-requires-policy + sid vpn per-vrf export auto + ! + address-family ipv4 unicast + nexthop vpn export 2001::2 + rd vpn export 2:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + rd vpn export 2:20 + rt vpn both 88:88 + import vpn + export vpn + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib.json new file mode 100644 index 0000000..0dcdec6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_deleted.json new file mode 100644 index 0000000..e3edee1 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_deleted.json @@ -0,0 +1,90 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_recreated.json new file mode 100644 index 0000000..0dcdec6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_locator_recreated.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_disabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_disabled.json new file mode 100644 index 0000000..a440ab4 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_disabled.json @@ -0,0 +1,116 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_reenabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_reenabled.json new file mode 100644 index 0000000..0dcdec6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv4_rib_sid_vpn_export_reenabled.json @@ -0,0 +1,166 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "192.168.1.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.1.0", + "prefixLen": 24, + "network": "192.168.1.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.3.0", + "prefixLen": 24, + "network": "192.168.3.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "192.168.5.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.5.0", + "prefixLen": 24, + "network": "192.168.5.0/24", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "192.168.2.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.2.0", + "prefixLen": 24, + "network": "192.168.2.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "192.168.4.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.4.0", + "prefixLen": 24, + "network": "192.168.4.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192.168.6.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "192.168.6.0", + "prefixLen": 24, + "network": "192.168.6.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "2001::2", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib.json new file mode 100644 index 0000000..03bbcc0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_deleted.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_deleted.json new file mode 100644 index 0000000..25cdf03 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_deleted.json @@ -0,0 +1,93 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_recreated.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_recreated.json new file mode 100644 index 0000000..03bbcc0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_locator_recreated.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_disabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_disabled.json new file mode 100644 index 0000000..5c70cf6 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_disabled.json @@ -0,0 +1,119 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_reenabled.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_reenabled.json new file mode 100644 index 0000000..03bbcc0 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vpnv6_rib_sid_vpn_export_reenabled.json @@ -0,0 +1,169 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId": "2.2.2.2", + "defaultLocPrf": 100, + "localAS": 2, + "routes": { + "routeDistinguishers": { + "1:10": { + "2001:1::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:1::", + "prefixLen": 64, + "network": "2001:1::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:3::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:3::", + "prefixLen": 64, + "network": "2001:3::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "1:20": { + "2001:5::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:5::", + "prefixLen": 64, + "network": "2001:5::/64", + "metric": 0, + "weight": 0, + "peerId": "2001::1", + "path": "1", + "origin": "incomplete", + "nexthops": [ + { + "ip": "2001::1", + "hostname": "r1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:10": { + "2001:2::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:2::", + "prefixLen": 64, + "network": "2001:2::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "2:20": { + "2001:4::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:4::", + "prefixLen": 64, + "network": "2001:4::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "2001:6::/64": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "2001:6::", + "prefixLen": 64, + "network": "2001:6::/64", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf20", + "nexthops": [ + { + "ip": "::", + "hostname": "r2", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v4_rib.json new file mode 100644 index 0000000..1534e98 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v4_rib.json @@ -0,0 +1,92 @@ +{ + "192.168.1.0/24": [ + { + "prefix": "192.168.1.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:1::" + } + } + ], + "asPath": "1" + } + ], + "192.168.2.0/24": [ + { + "prefix": "192.168.2.0/24", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "192.168.3.0/24": [ + { + "prefix": "192.168.3.0/24", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:1::" + } + } + ], + "asPath": "1" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v6_rib.json new file mode 100644 index 0000000..a2e329d --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf10v6_rib.json @@ -0,0 +1,92 @@ +{ + "2001:1::/64": [ + { + "prefix": "2001:1::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:1::" + } + } + ], + "asPath": "1" + } + ], + "2001:2::/64": [ + { + "prefix": "2001:2::/64", + "protocol": "connected", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth1", + "active": true + } + ] + } + ], + "2001:3::/64": [ + { + "prefix": "2001:3::/64", + "protocol": "bgp", + "vrfName": "vrf10", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 10, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:1::" + } + } + ], + "asPath": "1" + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v4_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v4_rib.json new file mode 100644 index 0000000..49d1861 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v4_rib.json @@ -0,0 +1,86 @@ +{ + "192.168.4.0/24": [ + { + "prefix": "192.168.4.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ], + "192.168.5.0/24": [ + { + "prefix": "192.168.5.0/24", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:2::" + } + } + ], + "asPath": "1" + } + ], + "192.168.6.0/24": [ + { + "prefix": "192.168.6.0/24", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v6_rib.json b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v6_rib.json new file mode 100644 index 0000000..f7433d5 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/vrf20v6_rib.json @@ -0,0 +1,86 @@ +{ + "2001:4::/64": [ + { + "prefix": "2001:4::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth2", + "active": true + } + ] + } + ], + "2001:5::/64": [ + { + "prefix": "2001:5::/64", + "protocol": "bgp", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "eth0", + "vrf": "default", + "active": true, + "weight": 1, + "seg6": { + "segs": "2001:db8:1:1:2::" + } + } + ], + "asPath": "1" + } + ], + "2001:6::/64": [ + { + "prefix": "2001:6::/64", + "protocol": "connected", + "vrfName": "vrf20", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "table": 20, + "internalStatus": 16, + "internalFlags": 8, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "eth3", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf new file mode 100644 index 0000000..3c9e4e3 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/r2/zebra.conf @@ -0,0 +1,42 @@ +log file zebra.log +! +hostname r2 +password zebra +! +log stdout notifications +log commands +! +!debug zebra packet +!debug zebra dplane +!debug zebra kernel +! +interface eth0 + ipv6 address 2001::2/64 +! +interface eth1 vrf vrf10 + ip address 192.168.2.1/24 + ipv6 address 2001:2::1/64 +! +interface eth2 vrf vrf20 + ip address 192.168.4.1/24 + ipv6 address 2001:4::1/64 +! +interface eth3 vrf vrf20 + ip address 192.168.6.1/24 + ipv6 address 2001:6::1/64 +! +segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:2:2::/64 + ! + ! +! +ip forwarding +ipv6 forwarding +! +ipv6 route 2001:db8:1:1::/64 2001::1 +! +line vty +! diff --git a/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/test_bgp_srv6l3vpn_to_bgp_vrf3.py b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/test_bgp_srv6l3vpn_to_bgp_vrf3.py new file mode 100644 index 0000000..8a7b558 --- /dev/null +++ b/tests/topotests/bgp_srv6l3vpn_to_bgp_vrf3/test_bgp_srv6l3vpn_to_bgp_vrf3.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022, University of Rome Tor Vergata +# Authored by Carmine Scarpitta +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version +from lib.checkping import check_ping, check_ping + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("ce1") + tgen.add_router("ce2") + tgen.add_router("ce3") + tgen.add_router("ce4") + tgen.add_router("ce5") + tgen.add_router("ce6") + + tgen.add_link(tgen.gears["r1"], tgen.gears["r2"], "eth0", "eth0") + tgen.add_link(tgen.gears["ce1"], tgen.gears["r1"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce2"], tgen.gears["r2"], "eth0", "eth1") + tgen.add_link(tgen.gears["ce3"], tgen.gears["r1"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce4"], tgen.gears["r2"], "eth0", "eth2") + tgen.add_link(tgen.gears["ce5"], tgen.gears["r1"], "eth0", "eth3") + tgen.add_link(tgen.gears["ce6"], tgen.gears["r2"], "eth0", "eth3") + + +def setup_module(mod): + result = required_linux_kernel_version("5.14") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname)) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["r1"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r1"].run("ip link set vrf10 up") + tgen.gears["r1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r1"].run("ip link set vrf20 up") + tgen.gears["r1"].run("ip link set eth1 master vrf10") + tgen.gears["r1"].run("ip link set eth2 master vrf10") + tgen.gears["r1"].run("ip link set eth3 master vrf20") + + tgen.gears["r2"].run("sysctl net.vrf.strict_mode=1") + tgen.gears["r2"].run("ip link add vrf10 type vrf table 10") + tgen.gears["r2"].run("ip link set vrf10 up") + tgen.gears["r2"].run("ip link add vrf20 type vrf table 20") + tgen.gears["r2"].run("ip link set vrf20 up") + tgen.gears["r2"].run("ip link set eth1 master vrf10") + tgen.gears["r2"].run("ip link set eth2 master vrf20") + tgen.gears["r2"].run("ip link set eth3 master vrf20") + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def check_rib(name, cmd, expected_file): + def _check(name, dest_addr, match): + logger.info("polling") + tgen = get_topogen() + router = tgen.gears[name] + output = json.loads(router.vtysh_cmd(cmd)) + expected = open_json_file("{}/{}".format(CWD, expected_file)) + return topotest.json_cmp(output, expected) + + logger.info('[+] check {} "{}" {}'.format(name, cmd, expected_file)) + tgen = get_topogen() + func = functools.partial(_check, name, cmd, expected_file) + success, result = topotest.run_and_expect(func, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib.json") + check_rib("r1", "show ip route vrf vrf10 json", "r1/vrf10v4_rib.json") + check_rib("r1", "show ip route vrf vrf20 json", "r1/vrf20v4_rib.json") + check_rib("r2", "show ip route vrf vrf10 json", "r2/vrf10v4_rib.json") + check_rib("r2", "show ip route vrf vrf20 json", "r2/vrf20v4_rib.json") + check_rib("ce1", "show ip route json", "ce1/ip_rib.json") + check_rib("ce2", "show ip route json", "ce2/ip_rib.json") + check_rib("ce3", "show ip route json", "ce3/ip_rib.json") + check_rib("ce4", "show ip route json", "ce4/ip_rib.json") + check_rib("ce5", "show ip route json", "ce5/ip_rib.json") + check_rib("ce6", "show ip route json", "ce6/ip_rib.json") + + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib.json") + check_rib("r1", "show ipv6 route vrf vrf10 json", "r1/vrf10v6_rib.json") + check_rib("r1", "show ipv6 route vrf vrf20 json", "r1/vrf20v6_rib.json") + check_rib("r2", "show ipv6 route vrf vrf10 json", "r2/vrf10v6_rib.json") + check_rib("r2", "show ipv6 route vrf vrf20 json", "r2/vrf20v6_rib.json") + check_rib("ce1", "show ipv6 route json", "ce1/ipv6_rib.json") + check_rib("ce2", "show ipv6 route json", "ce2/ipv6_rib.json") + check_rib("ce3", "show ipv6 route json", "ce3/ipv6_rib.json") + check_rib("ce4", "show ipv6 route json", "ce4/ipv6_rib.json") + check_rib("ce5", "show ipv6 route json", "ce5/ipv6_rib.json") + check_rib("ce6", "show ipv6 route json", "ce6/ipv6_rib.json") + + +def test_ping(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "192.168.3.2", True, 10, 0.5) + check_ping("ce1", "192.168.4.2", False, 10, 0.5) + check_ping("ce1", "192.168.5.2", False, 10, 0.5) + check_ping("ce1", "192.168.6.2", False, 10, 0.5) + check_ping("ce4", "192.168.1.2", False, 10, 0.5) + check_ping("ce4", "192.168.2.2", False, 10, 0.5) + check_ping("ce4", "192.168.3.2", False, 10, 0.5) + check_ping("ce4", "192.168.5.2", True, 10, 0.5) + check_ping("ce4", "192.168.6.2", True, 10, 0.5) + + check_ping("ce1", "2001:2::2", True, 10, 1) + check_ping("ce1", "2001:3::2", True, 10, 1) + check_ping("ce1", "2001:4::2", False, 10, 1) + check_ping("ce1", "2001:5::2", False, 10, 1) + check_ping("ce1", "2001:6::2", False, 10, 1) + check_ping("ce4", "2001:1::2", False, 10, 1) + check_ping("ce4", "2001:2::2", False, 10, 1) + check_ping("ce4", "2001:3::2", False, 10, 1) + check_ping("ce4", "2001:5::2", True, 10, 1) + check_ping("ce4", "2001:6::2", True, 10, 1) + + +def test_bgp_sid_vpn_export_disable(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 vrf vrf10 + segment-routing srv6 + no sid vpn per-vrf export + """ + ) + check_rib( + "r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_sid_vpn_export_disabled.json" + ) + check_rib( + "r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_sid_vpn_export_disabled.json" + ) + check_rib( + "r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_sid_vpn_export_disabled.json" + ) + check_rib( + "r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_sid_vpn_export_disabled.json" + ) + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_bgp_sid_vpn_export_reenable(): + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 vrf vrf10 + segment-routing srv6 + sid vpn per-vrf export auto + """ + ) + check_rib( + "r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_sid_vpn_export_reenabled.json" + ) + check_rib( + "r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_sid_vpn_export_reenabled.json" + ) + check_rib( + "r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_sid_vpn_export_reenabled.json" + ) + check_rib( + "r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_sid_vpn_export_reenabled.json" + ) + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + + +def test_locator_delete(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + locators + no locator loc1 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_deleted.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_locator_recreate(): + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + segment-routing + srv6 + locators + locator loc1 + prefix 2001:db8:1:1::/64 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_recreated.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + + +def test_bgp_locator_unset(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + no locator loc1 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_deleted.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_bgp_locator_reset(): + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + locator loc1 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_recreated.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + + +def test_bgp_srv6_unset(): + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + no segment-routing srv6 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_deleted.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_deleted.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_deleted.json") + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + + +def test_bgp_srv6_reset(): + check_ping("ce1", "192.168.2.2", False, 10, 0.5) + check_ping("ce1", "2001:2::2", False, 10, 1) + get_topogen().gears["r1"].vtysh_cmd( + """ + configure terminal + router bgp 1 + segment-routing srv6 + locator loc1 + """ + ) + check_rib("r1", "show bgp ipv4 vpn json", "r1/vpnv4_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv4 vpn json", "r2/vpnv4_rib_locator_recreated.json") + check_rib("r1", "show bgp ipv6 vpn json", "r1/vpnv6_rib_locator_recreated.json") + check_rib("r2", "show bgp ipv6 vpn json", "r2/vpnv6_rib_locator_recreated.json") + check_ping("ce1", "192.168.2.2", True, 10, 0.5) + check_ping("ce1", "2001:2::2", True, 10, 1) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json b/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json new file mode 100644 index 0000000..1a5ede2 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r1/bgp_ipv4_allowas.json @@ -0,0 +1,40 @@ +{ + "prefix":"192.168.1.1/32", + "paths":[ + { + "aspath":{ + "string":"2", + "segments":[ + { + "type":"as-sequence", + "list":[ + 2 + ] + } + ], + "length":1 + }, + "origin":"incomplete", + "metric":0, + "valid":true, + "bestpath":{ + "overall":true, + "selectionReason":"First path received" + }, + "nexthops":[ + { + "ip":"10.0.0.2", + "afi":"ipv4", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"10.0.0.2", + "routerId":"60.0.0.1", + "type":"external" + } + } + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r1/bgpd.conf b/tests/topotests/bgp_suppress_fib/r1/bgpd.conf new file mode 100644 index 0000000..69c563d --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r1/bgpd.conf @@ -0,0 +1,15 @@ +! exit1 +router bgp 1 + no bgp ebgp-requires-policy + neighbor 10.0.0.2 remote-as 2 + + address-family ipv4 unicast + redistribute static + neighbor 10.0.0.2 route-map rmap out + exit-address-family + +ip prefix-list plist seq 5 permit any + +route-map rmap permit 1 + match ip address prefix-list plist +! diff --git a/tests/topotests/bgp_suppress_fib/r1/zebra.conf b/tests/topotests/bgp_suppress_fib/r1/zebra.conf new file mode 100644 index 0000000..7b44216 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r1/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface r1-eth0 + ip address 10.0.0.1/30 +! +ip forwarding +! +ip route 40.0.0.0/8 blackhole +ip route 50.0.0.0/8 blackhole +! diff --git a/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json b/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json new file mode 100644 index 0000000..4a35abf --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/bgp_ipv4_allowas.json @@ -0,0 +1,68 @@ +{ + "prefix":"192.168.1.1/32", + "paths":[ + { + "aspath":{ + "string":"1 2", + "segments":[ + { + "type":"as-sequence", + "list":[ + 1, + 2 + ] + } + ], + "length":2 + }, + "origin":"incomplete", + "valid":true, + "fibInstalled":true, + "nexthops":[ + { + "ip":"10.0.0.1", + "afi":"ipv4", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"10.0.0.1", + "routerId":"10.0.0.1", + "type":"external" + } + }, + { + "aspath":{ + "string":"Local", + "segments":[ + ], + "length":0 + }, + "origin":"incomplete", + "metric":0, + "weight":32768, + "valid":true, + "sourced":true, + "bestpath":{ + "overall":true, + "selectionReason":"Weight" + }, + "fibInstalled":true, + "nexthops":[ + { + "ip":"10.0.0.10", + "afi":"ipv4", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"0.0.0.0", + "routerId":"60.0.0.1" + } + } + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf b/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf new file mode 100644 index 0000000..fb6980a --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/bgpd.allowas_in.conf @@ -0,0 +1,18 @@ +access-list access seq 10 permit 192.168.1.1/32 +! +ip route 192.168.1.1/32 10.0.0.10 +! +!debug bgp bestpath +!debug bgp nht +!debug bgp updates +!debug bgp update-groups +!debug bgp zebra +!debug zebra rib detail +! +router bgp 2 + address-family ipv4 uni + redistribute static + neighbor 10.0.0.10 allowas-in 1 + neighbor 10.0.0.1 allowas-in 1 + ! +! diff --git a/tests/topotests/bgp_suppress_fib/r2/bgpd.conf b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf new file mode 100644 index 0000000..129b812 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/bgpd.conf @@ -0,0 +1,11 @@ +!debug bgp updates +!debug bgp bestpath 40.0.0.0/8 +!debug bgp zebra +! +router bgp 2 + no bgp ebgp-requires-policy + bgp suppress-fib-pending + neighbor 10.0.0.1 remote-as 1 + neighbor 10.0.0.10 remote-as 3 + address-family ipv4 uni + network 60.0.0.0/24 diff --git a/tests/topotests/bgp_suppress_fib/r2/no_bgp_ipv4_allowas.json b/tests/topotests/bgp_suppress_fib/r2/no_bgp_ipv4_allowas.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/no_bgp_ipv4_allowas.json @@ -0,0 +1 @@ +{} diff --git a/tests/topotests/bgp_suppress_fib/r2/v4_override.json b/tests/topotests/bgp_suppress_fib/r2/v4_override.json new file mode 100644 index 0000000..f17907f --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/v4_override.json @@ -0,0 +1,20 @@ +{ + "40.0.0.0\/8":[ + { + "prefix":"40.0.0.0\/8", + "protocol":"static", + "vrfName":"default", + "selected":true, + "destSelected":true, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.0.10", + "afi":"ipv4", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r2/zebra.conf b/tests/topotests/bgp_suppress_fib/r2/zebra.conf new file mode 100644 index 0000000..6e8bce0 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r2/zebra.conf @@ -0,0 +1,16 @@ +! +interface lo + ip address 60.0.0.1/24 +! +interface r2-eth0 + ip address 10.0.0.2/30 +! +interface r2-eth1 + ip address 10.0.0.9/30 + +access-list access seq 5 permit 40.0.0.0/8 + +route-map LIMIT permit 10 + match ip address access + +ip protocol bgp route-map LIMIT diff --git a/tests/topotests/bgp_suppress_fib/r3/bgpd.conf b/tests/topotests/bgp_suppress_fib/r3/bgpd.conf new file mode 100644 index 0000000..11715d4 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 3 + no bgp ebgp-requires-policy + neighbor 10.0.0.9 remote-as 2 + +route-map rmap permit 1 + match ip address prefix-list plist + ! +! diff --git a/tests/topotests/bgp_suppress_fib/r3/v4_override.json b/tests/topotests/bgp_suppress_fib/r3/v4_override.json new file mode 100644 index 0000000..a35d49e --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/v4_override.json @@ -0,0 +1,4 @@ +{ + "0.0.0.0\/0":[ + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r3/v4_route.json b/tests/topotests/bgp_suppress_fib/r3/v4_route.json new file mode 100644 index 0000000..e255d07 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/v4_route.json @@ -0,0 +1,28 @@ +{ + "40.0.0.0\/8":[ + { + "prefix":"40.0.0.0\/8", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":8, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"10.0.0.9", + "afi":"ipv4", + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r3/v4_route2.json b/tests/topotests/bgp_suppress_fib/r3/v4_route2.json new file mode 100644 index 0000000..a35d49e --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/v4_route2.json @@ -0,0 +1,4 @@ +{ + "0.0.0.0\/0":[ + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r3/v4_route3.json b/tests/topotests/bgp_suppress_fib/r3/v4_route3.json new file mode 100644 index 0000000..ab8c3aa --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/v4_route3.json @@ -0,0 +1,23 @@ +{ + "60.0.0.0/24":[ + { + "prefix":"60.0.0.0/24", + "protocol":"bgp", + "selected":true, + "destSelected":true, + "distance":20, + "metric":0, + "installed":true, + "table":254, + "nexthops":[ + { + "fib":true, + "ip":"10.0.0.9", + "afi":"ipv4", + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_suppress_fib/r3/zebra.conf b/tests/topotests/bgp_suppress_fib/r3/zebra.conf new file mode 100644 index 0000000..793b043 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/r3/zebra.conf @@ -0,0 +1,6 @@ +! +interface r3-eth0 + ip address 10.0.0.10/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py b/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py new file mode 100644 index 0000000..c1bc640 --- /dev/null +++ b/tests/topotests/bgp_suppress_fib/test_bgp_suppress_fib.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_suppress_fib.py +# +# Copyright (c) 2019 by +# + +""" +""" + +import os +import sys +import json +import pytest +from functools import partial +from time import sleep +from lib.topolog import logger + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r3 = tgen.gears["r3"] + + json_file = "{}/r3/v4_route.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r3, + "show ip route 40.0.0.0 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"r3" JSON output mismatches' + assert result is None, assertmsg + + json_file = "{}/r3/v4_route2.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r3, + "show ip route 50.0.0.0 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, assertmsg + + json_file = "{}/r3/v4_route3.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r3, + "show ip route 60.0.0.0 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result is None, assertmsg + + +def test_bgp_better_admin_won(): + "A better Admin distance protocol may come along and knock us out" + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + r2.vtysh_cmd("conf\nip route 40.0.0.0/8 10.0.0.10") + + json_file = "{}/r2/v4_override.json".format(CWD) + expected = json.loads(open(json_file).read()) + + logger.info(expected) + test_func = partial( + topotest.router_json_cmp, r2, "show ip route 40.0.0.0 json", expected + ) + + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"r2" static route did not take over' + assert result is None, assertmsg + + r3 = tgen.gears["r3"] + + json_file = "{}/r3/v4_override.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, r3, "show ip route 40.0.0.0 json", expected + ) + + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"r3" route to 40.0.0.0 should have been lost' + assert result is None, assertmsg + + r2.vtysh_cmd("conf\nno ip route 40.0.0.0/8 10.0.0.10") + + json_file = "{}/r3/v4_route.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r3, + "show ip route 40.0.0.0 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"r3" route to 40.0.0.0 did not come back' + assert result is None, assertmsg + + +def test_bgp_allow_as_in(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + config_file = "{}/r2/bgpd.allowas_in.conf".format(CWD) + r2.run("vtysh -f {}".format(config_file)) + + json_file = "{}/r2/bgp_ipv4_allowas.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r2, + "show bgp ipv4 uni 192.168.1.1/32 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"r2" static redistribution failed into bgp' + assert result is None, assertmsg + + r1 = tgen.gears["r1"] + + json_file = "{}/r1/bgp_ipv4_allowas.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r1, + "show bgp ipv4 uni 192.168.1.1/32 json", + expected, + ) + + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"r1" 192.168.1.1/32 route should have arrived' + assert result is None, assertmsg + + r2.vtysh_cmd("conf\nno ip route 192.168.1.1/32 10.0.0.10") + + json_file = "{}/r2/no_bgp_ipv4_allowas.json".format(CWD) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, + r2, + "show bgp ipv4 uni 192.168.1.1/32 json", + expected, + ) + + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"r2" 192.168.1.1/32 route should be gone' + assert result is None, assertmsg + + +def test_local_vs_non_local(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r2 = tgen.gears["r2"] + + output = json.loads(r2.vtysh_cmd("show bgp ipv4 uni 60.0.0.0/24 json")) + paths = output["paths"] + for i in range(len(paths)): + if "fibPending" in paths[i]: + assert False, "Route 60.0.0.0/24 should not have fibPending" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_tcp_mss/__init__.py b/tests/topotests/bgp_tcp_mss/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_tcp_mss/bgp_vrf_tcp_mss.json b/tests/topotests/bgp_tcp_mss/bgp_vrf_tcp_mss.json new file mode 100644 index 0000000..17cee03 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/bgp_vrf_tcp_mss.json @@ -0,0 +1,222 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "20.20.20.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.10.10.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 24, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + }, + "r2": { + "links": { + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ], + "bgp": [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + }, + "r3": { + "links": { + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_tcp_mss/r1/bgpd.conf b/tests/topotests/bgp_tcp_mss/r1/bgpd.conf new file mode 100644 index 0000000..07cfe2e --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/r1/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.255.2 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_tcp_mss/r1/zebra.conf b/tests/topotests/bgp_tcp_mss/r1/zebra.conf new file mode 100644 index 0000000..6e9b0b4 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 192.168.255.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_tcp_mss/r2/bgpd.conf b/tests/topotests/bgp_tcp_mss/r2/bgpd.conf new file mode 100644 index 0000000..b2d9455 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/r2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65000 + neighbor 192.168.255.1 timers 3 10 + exit-address-family +! diff --git a/tests/topotests/bgp_tcp_mss/r2/zebra.conf b/tests/topotests/bgp_tcp_mss/r2/zebra.conf new file mode 100644 index 0000000..6c14de5 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/r2/zebra.conf @@ -0,0 +1,6 @@ +! +interface r2-eth0 + ip address 192.168.255.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_tcp_mss/test_bgp_tcp_mss.py b/tests/topotests/bgp_tcp_mss/test_bgp_tcp_mss.py new file mode 100644 index 0000000..4855d5c --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/test_bgp_tcp_mss.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_tcp_mss.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 by +# Abhinay Ramesh +# + +""" +bgp_tcp_mss.py: + +Test if works the following commands: +router bgp 65000 + neighbor 192.168.255.2 tcp-mss 500 + +Need to verify if the tcp-mss value is reflected in the TCP session. +""" + +import os +import sys +import json +import pytest +import functools + +# add after imports, before defining classes or functions: +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_tcp_mss(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 0}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_conf_tcp_mss(router, as_num, neigh): + router.vtysh_cmd( + """configure terminal + router bgp {0} + neighbor {1} tcp-mss 500""".format( + as_num, neigh + ) + ) + + def _bgp_clear_session(router): + router.vtysh_cmd("clear bgp *") + + def _bgp_check_neighbor_tcp_mss(router, neigh): + output = json.loads(router.vtysh_cmd("show bgp neighbor {} json".format(neigh))) + expected = { + "{}".format(neigh): {"bgpTcpMssConfigured": 500, "bgpTcpMssSynced": 488} + } + return topotest.json_cmp(output, expected) + + logger.info("Check if neighbor sessions are up in {}".format(router1.name)) + test_func = functools.partial(_bgp_converge, router1) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + assert result is None, 'Failed to see BGP convergence in "{}"'.format(router1.name) + + logger.info("BGP neighbor session is up in {}".format(router1.name)) + + logger.info( + "Configure tcp-mss 500 on {} and reset the session".format(router1.name) + ) + _bgp_conf_tcp_mss(router1, "65000", "192.168.255.2") + _bgp_clear_session(router1) + + logger.info( + "Configure tcp-mss 500 on {} and reset the session".format(router2.name) + ) + _bgp_conf_tcp_mss(router2, "65001", "192.168.255.1") + _bgp_clear_session(router2) + + logger.info( + "Check if neighbor session is up after reset in {}".format(router1.name) + ) + test_func = functools.partial(_bgp_converge, router1) + success, result = topotest.run_and_expect(test_func, None, count=15, wait=0.5) + assert result is None, 'Failed to see BGP convergence after reset in "{}"'.format( + router1.name + ) + + logger.info( + "Verify if TCP MSS value is synced with neighbor in {}".format(router1.name) + ) + test_func = functools.partial(_bgp_check_neighbor_tcp_mss, router1, "192.168.255.2") + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + result is None + ), 'Failed to sync TCP MSS value over BGP session in "{}"'.format(router1.name) + logger.info("TCP MSS value is synced with neighbor in {}".format(router1.name)) + + logger.info( + "Verify if TCP MSS value is synced with neighbor in {}".format(router2.name) + ) + test_func = functools.partial(_bgp_check_neighbor_tcp_mss, router2, "192.168.255.1") + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + result is None + ), 'Failed to sync TCP MSS value over BGP session in "{}"'.format(router2.name) + logger.info("TCP MSS value is synced with neighbor in {}".format(router2.name)) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_tcp_mss/test_bgp_vrf_tcp_mss.py b/tests/topotests/bgp_tcp_mss/test_bgp_vrf_tcp_mss.py new file mode 100644 index 0000000..aeb9bf5 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss/test_bgp_vrf_tcp_mss.py @@ -0,0 +1,748 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# bgp_tcp_mss.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 by +# Shreenidhi A R +# + +""" +bgp_tcp_mss_vrf.py: + +Need to verify if the tcp-mss value is reflected in the TCP session and in VRF. +""" + +import os +import sys +import json +import pytest +import functools +import platform +import socket +import subprocess + +# add after imports, before defining classes or functions: +pytestmark = [pytest.mark.bgpd] + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger +import time +from lib.bgp import ( + clear_bgp, + clear_bgp_and_verify, + create_router_bgp, + modify_as_number, + verify_as_numbers, + verify_bgp_convergence, + verify_bgp_rib, + verify_bgp_timers_and_functionality, + verify_router_id, + verify_tcp_mss, +) +from lib.common_config import ( + kill_router_daemons, + start_router_daemons, + addKernelRoute, + apply_raw_config, + check_address_types, + check_router_status, + create_prefix_lists, + create_route_maps, + create_static_routes, + required_linux_kernel_version, + reset_config_on_routers, + start_topology, + step, + verify_admin_distance_for_static_routes, + verify_bgp_community, + verify_fib_routes, + verify_rib, + write_test_footer, + write_test_header, +) + +# Global variables +NETWORK1_1 = {"ipv4": "1.1.1.1/32", "ipv6": "1::1/128"} +NETWORK1_2 = {"ipv4": "1.1.1.2/32", "ipv6": "1::2/128"} +NETWORK2_1 = {"ipv4": "2.1.1.1/32", "ipv6": "2::1/128"} +NETWORK2_2 = {"ipv4": "2.1.1.2/32", "ipv6": "2::2/128"} +NETWORK3_1 = {"ipv4": "3.1.1.1/32", "ipv6": "3::1/128"} +NETWORK3_2 = {"ipv4": "3.1.1.2/32", "ipv6": "3::2/128"} +NETWORK4_1 = {"ipv4": "4.1.1.1/32", "ipv6": "4::1/128"} +NETWORK4_2 = {"ipv4": "4.1.1.2/32", "ipv6": "4::2/128"} +NETWORK5_1 = {"ipv4": "5.1.1.1/32", "ipv6": "5::1/128"} +NETWORK5_2 = {"ipv4": "5.1.1.2/32", "ipv6": "5::2/128"} + +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} + +## File name +TCPDUMP_FILE = "test_tcp_packet_test.txt" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + global topo, TCPDUMP_FILE + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("4.15") + if result is not True: + pytest.skip("Kernel requirements are not met") + + testsuite_run_time = time.asctime(time.localtime(time.time())) + step("Testsuite start time: {}".format(testsuite_run_time)) + step("=" * 40) + + step("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_tcp_mss.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global ADDR_TYPES + global BGP_CONVERGENCE + ADDR_TYPES = check_address_types() + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + step("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + step("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + step("Testsuite end time: {}".format(time.asctime(time.localtime(time.time())))) + step("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_bgp_vrf_tcp_mss(request): + tgen = get_topogen() + tc_name = request.node.name + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Verify the router failures") + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configuring 5 static Routes in Router R3 with NULL0 as Next hop") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + result = create_static_routes(tgen, static_routes_input) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify the static Routes in R3 on default VRF") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + dut = "r3" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Verify the static Routes in R2 on default VRF") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + }, + ] + } + } + dut = "r2" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("importing default vrf on R2 under VRF RED Address Family") + for addr_type in ADDR_TYPES: + input_import_vrf = { + "r2": { + "bgp": [ + { + "local_as": 200, + "vrf": "RED", + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "default"}}} + }, + } + ] + } + } + result = create_router_bgp(tgen, topo, input_import_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify the static Routes in R2 on RED VRF") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + ] + } + } + dut = "r2" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Verify the static Routes in R1 on RED VRF") + for addr_type in ADDR_TYPES: + static_routes_input = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]] + [NETWORK1_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK2_1[addr_type]] + [NETWORK2_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK3_1[addr_type]] + [NETWORK3_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK4_1[addr_type]] + [NETWORK4_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + { + "network": [NETWORK5_1[addr_type]] + [NETWORK5_2[addr_type]], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + }, + ] + } + } + dut = "r1" + result = verify_bgp_rib(tgen, addr_type, dut, static_routes_input) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step("Enabling tcp-mss 150 on Router R1 in VRF RED") + TCP_MSS = 150 + raw_config = { + "r1": { + "raw_config": [ + "router bgp {} vrf {}".format( + topo["routers"]["r1"]["bgp"][0]["local_as"], + topo["routers"]["r1"]["bgp"][0]["vrf"], + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r1-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r1-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + }, + } + + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Clearing BGP on R1 and R2 ") + for addr_type in ADDR_TYPES: + clear_bgp(tgen, addr_type, "r1", vrf=topo["routers"]["r1"]["bgp"][0]["vrf"]) + clear_bgp(tgen, addr_type, "r2", vrf=topo["routers"]["r2"]["bgp"][1]["vrf"]) + + step("Verify the BGP Convergence at R1 & R2 after Clear BGP") + r1_convergence = verify_bgp_convergence(tgen, topo, dut="r1") + assert ( + r1_convergence is True + ), "BGP convergence after Clear BGP :Failed \n Error: {}".format(r1_convergence) + r2_convergence = verify_bgp_convergence(tgen, topo, dut="r2") + assert ( + r2_convergence is True + ), "BGP convergence after Clear BGP :Failed \n Error: {}".format(r2_convergence) + + step("Verify the TCP-MSS value on both Router R1 and R2") + for addr_type in ADDR_TYPES: + dut = "r1" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0], + TCP_MSS, + "RED", + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + + step("Enabling tcp-mss 500 between R2 and R3 of VRF Default") + TCP_MSS = 500 + raw_config = { + "r2": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r2"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + }, + "r3": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r3"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + }, + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Clear BGP at router R2 and R3") + for addr_type in ADDR_TYPES: + clear_bgp(tgen, topo, "r2", addr_type) + clear_bgp(tgen, topo, "r3", addr_type) + + step("Verify the BGP Convergence at R2 & R3 after Clear BGP") + r1_convergence = verify_bgp_convergence(tgen, topo, dut="r2") + assert ( + r1_convergence is True + ), "BGP convergence after Clear BGP :Failed \n Error: {}".format(r2_convergence) + r2_convergence = verify_bgp_convergence(tgen, topo, dut="r3") + assert ( + r2_convergence is True + ), "BGP convergence after Clear BGP :Failed \n Error: {}".format(r2_convergence) + + step("Verify the TCP-MSS value on both Router R2 and R3") + for addr_type in ADDR_TYPES: + dut = "r2" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + + dut = "r3" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + + step("Removing tcp-mss 150 between R1 and R2 of VRF RED ") + TCP_MSS = 150 + raw_config = { + "r1": { + "raw_config": [ + "router bgp {} vrf {}".format( + topo["routers"]["r1"]["bgp"][0]["local_as"], + topo["routers"]["r1"]["bgp"][0]["vrf"], + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r1-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r1-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + raw_config = { + "r2": { + "raw_config": [ + "router bgp {} vrf {}".format( + topo["routers"]["r2"]["bgp"][0]["local_as"], + topo["routers"]["r2"]["bgp"][1]["vrf"], + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r1"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r1"]["links"]["r2-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify the TCP-MSS value cleared on both Router R1 and R2") + for addr_type in ADDR_TYPES: + dut = "r1" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r1-link1"][addr_type].split("/")[0], + TCP_MSS, + "RED", + ) + assert ( + tcp_mss_result is not True + ), " TCP-MSS mismatch :Failed \n Error: {}".format(tcp_mss_result) + + dut = "r2" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r1"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + "RED", + ) + assert ( + tcp_mss_result is not True + ), " TCP-MSS mismatch :Failed \n Error: {}".format(tcp_mss_result) + + step("Removing tcp-mss 500 between R2 and R3 of VRF Default ") + TCP_MSS = 500 + raw_config = { + "r2": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r2"]["bgp"][0]["local_as"]), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + raw_config = { + "r3": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r3"]["bgp"][0]["local_as"]), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "no neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify the TCP-MSS value got cleared on both Router R2 and R3") + for addr_type in ADDR_TYPES: + dut = "r2" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert ( + tcp_mss_result is not True + ), " TCP-MSS mismatch :Failed \n Error: {}".format(tcp_mss_result) + + dut = "r3" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert ( + tcp_mss_result is not True + ), " TCP-MSS mismatch :Failed \n Error: {}".format(tcp_mss_result) + + step("Configuring different TCP-MSS R2 and R3 ") + TCP_MSS = 500 + raw_config = { + "r2": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r2"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + TCP_MSS = 300 + raw_config = { + "r3": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r3"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Verify the TCP-MSS value on both Router R2 and R3") + for addr_type in ADDR_TYPES: + TCP_MSS = 500 + dut = "r2" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + + TCP_MSS = 300 + dut = "r3" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + + step("Configure TCP_MSS > MTU on R2 and R3 and it should be 1460 ") + TCP_MSS = 4096 + REF_TCP_MSS = 1460 + raw_config = { + "r2": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r2"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r3"]["links"]["r2-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + raw_config = { + "r3": { + "raw_config": [ + "router bgp {} ".format(topo["routers"]["r3"]["bgp"][0]["local_as"]), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ), + "neighbor {} tcp-mss {}".format( + topo["routers"]["r2"]["links"]["r3-link1"]["ipv6"].split("/")[0], + TCP_MSS, + ), + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + step("Restarting BGP Daemon on R3") + + kill_router_daemons(tgen, "r3", ["bgpd"]) + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "Verify the configured TCP-MSS 4096 value on restarting both Daemon both Router R2 and R3 " + ) + for addr_type in ADDR_TYPES: + TCP_MSS = 4096 + dut = "r2" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r3"]["links"]["r2-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + dut = "r3" + tcp_mss_result = verify_tcp_mss( + tgen, + dut, + topo["routers"]["r2"]["links"]["r3-link1"]["ipv4"].split("/")[0], + TCP_MSS, + ) + assert tcp_mss_result is True, " TCP-MSS mismatch :Failed \n Error: {}".format( + tcp_mss_result + ) + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_tcp_mss_passive/__init__.py b/tests/topotests/bgp_tcp_mss_passive/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_tcp_mss_passive/r1/frr.conf b/tests/topotests/bgp_tcp_mss_passive/r1/frr.conf new file mode 100644 index 0000000..a0fcd52 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss_passive/r1/frr.conf @@ -0,0 +1,12 @@ +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.1.2 passive + neighbor 192.168.1.2 tcp-mss 300 +! diff --git a/tests/topotests/bgp_tcp_mss_passive/r2/frr.conf b/tests/topotests/bgp_tcp_mss_passive/r2/frr.conf new file mode 100644 index 0000000..7213975 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss_passive/r2/frr.conf @@ -0,0 +1,10 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_tcp_mss_passive/test_bgp_tcp_mss_passive.py b/tests/topotests/bgp_tcp_mss_passive/test_bgp_tcp_mss_passive.py new file mode 100644 index 0000000..cd405f7 --- /dev/null +++ b/tests/topotests/bgp_tcp_mss_passive/test_bgp_tcp_mss_passive.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if TCP MSS is synced with passive neighbor. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 3): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_tcp_mss_passive(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_tcp_mss_configured(router, neighbor, mss): + output = json.loads(router.vtysh_cmd("show bgp neighbors json")) + expected = { + neighbor: { + "bgpTcpMssConfigured": mss, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_tcp_mss_configured, tgen.gears["r1"], "192.168.1.2", 300 + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "r1 is not configured with TCP MSS 300" + + test_func = functools.partial( + _bgp_check_tcp_mss_configured, tgen.gears["r2"], "192.168.1.1", 0 + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "r2 is not configured with the default TCP MSS (1500)" + + def _bgp_check_tcp_mss_synced(router, neighbor, mss): + output = json.loads(router.vtysh_cmd("show bgp neighbors json")) + expected = { + neighbor: { + "bgpTcpMssSynced": mss, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial( + _bgp_check_tcp_mss_synced, tgen.gears["r1"], "192.168.1.2", 288 + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "r1 is not synced with TCP MSS 300" + + test_func = functools.partial( + _bgp_check_tcp_mss_synced, tgen.gears["r2"], "192.168.1.1", 288 + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "r2 is not synced with the default TCP MSS (1488)" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_unique_rid/bgp_unique_rid.json b/tests/topotests/bgp_unique_rid/bgp_unique_rid.json new file mode 100644 index 0000000..c42ce29 --- /dev/null +++ b/tests/topotests/bgp_unique_rid/bgp_unique_rid.json @@ -0,0 +1,505 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto", + "ospf": { + "area": "0.0.0.0", + "hello_interval": 1, + "dead_interval": 4 + } + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto", + "ospf": { + "area": "0.0.0.0", + "hello_interval": 1, + "dead_interval": 4 + } + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link7": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {}, + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + }, + "r4": { + "dest_link": { + "r3": {}, + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {} + } + }, + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + }, + "ospf": { + "router_id": "100.1.1.3", + "neighbors": { + "r4": {}, + "r5": {} + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "ospf": { + "area": "0.0.0.0", + "hello_interval": 1, + "dead_interval": 4 + } + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {}, + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {}, + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }, + "ospf": { + "router_id": "10.10.10.10", + "neighbors": { + "r3": {} + } + } + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "ospf": { + "area": "0.0.0.0", + "hello_interval": 1, + "dead_interval": 4 + } + } + }, + "bgp": { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }, + "ospf": { + "router_id": "100.1.1.5", + "neighbors": { + "r3": {} + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_unique_rid/bgp_unique_rid_vrf.json b/tests/topotests/bgp_unique_rid/bgp_unique_rid_vrf.json new file mode 100644 index 0000000..1e280f1 --- /dev/null +++ b/tests/topotests/bgp_unique_rid/bgp_unique_rid_vrf.json @@ -0,0 +1,529 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback", + "vrf": "GREEN" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + } + }, + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1": {} + } + }, + "r3": { + "dest_link": { + "r1": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + ], + "vrfs": [ + { + "name": "GREEN", + "id": "1" + } + ] + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback", + "vrf": "GREEN" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + } + }, + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2": {} + } + }, + "r3": { + "dest_link": { + "r2": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + ], + "vrfs": [ + { + "name": "GREEN", + "id": "1" + } + ] + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback", + "vrf": "GREEN" + }, + "r1": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + }, + "r2": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "GREEN" + }, + "r4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r5": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + }, + "r4-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r4-link7": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [ + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {}, + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3": {}, + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {} + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3": {} + } + }, + "r2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r5": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + ], + "vrfs": [ + { + "name": "RED", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ] + }, + "r4": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link3": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link4": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link5": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link6": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link7": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": [{ + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {}, + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4": {}, + "r4-link1": {}, + "r4-link2": {}, + "r4-link3": {}, + "r4-link4": {}, + "r4-link5": {}, + "r4-link6": {}, + "r4-link7": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + }] + }, + "r5": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback", + "vrf": "RED" + }, + "r3": { + "ipv4": "auto", + "ipv6": "auto", + "vrf": "RED" + } + }, + "bgp": [ + { + "local_as": "300", + "vrf":"RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5": {} + } + } + }, + "redistribute": [ + { + "redist_type": "static" + }, + { + "redist_type": "connected" + } + ] + } + } + } + } + ], + "vrfs": [ + { + "name": "RED", + "id": "1" + } + ] + } + } +} \ No newline at end of file diff --git a/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py new file mode 100644 index 0000000..9fbcdc2 --- /dev/null +++ b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid.py @@ -0,0 +1,1040 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +import sys +import time +import pytest +import inspect +import os +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +"""Following tests are covered to test bgp unique rid functionality. +1. Verify eBGP session when same and different router ID is configured. +2. Verify iBGP session when same and different router ID is configured. +3. Verify two different eBGP sessions initiated with same router ID. +4. Chaos - Verify bgp unique rid functionality in chaos scenarios. +5. Chaos - Verify bgp unique rid functionality when router reboots with same loopback id. +6. Chaos - Verify bgp unique rid functionality when router reboots without any ip addresses. +""" + +################################# +# TOPOLOGY +################################# +""" + + +-------+ + +--------- | R2 | + | +-------+ + |iBGP | + +-------+ | + | R1 | |iBGP + +-------+ | + | | + | iBGP +-------+ eBGP +-------+ + +---------- | R3 |========= | R4 | + +-------+ +-------+ + | + |eBGP + | + +-------+ + | R5 | + +-------+ + + +""" + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.ospfd, pytest.mark.staticd] + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + step, + write_test_footer, + verify_rib, + check_address_types, + reset_config_on_routers, + check_router_status, + stop_router, + kill_router_daemons, + start_router_daemons, + start_router, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp_and_verify, +) +from lib.ospf import verify_ospf_neighbor, clear_ospf + +# Global variables +topo = None +bgp_convergence = False +NETWORK = { + "ipv4": [ + "192.168.20.1/32", + "192.168.20.2/32", + "192.168.21.1/32", + "192.168.21.2/32", + "192.168.22.1/32", + "192.168.22.2/32", + ], + "ipv6": [ + "fc07:50::1/128", + "fc07:50::2/128", + "fc07:150::1/128", + "fc07:150::2/128", + "fc07:1::1/128", + "fc07:1::2/128", + ], +} + +bgp_convergence = False +ADDR_TYPES = check_address_types() +routerid = {"ipv4": "10.10.10.14", "ipv6": "fd00:0:0:3::2"} + + +def setup_module(mod): + """setup_module. + + Set up the pytest environment + * `mod`: module name + """ + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_unique_rid.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# Tests starting +##################################################### + + +def test_bgp_unique_rid_ebgp_p0(): + """ + TC: 1 + Verify eBGP session when same and different router ID is configured. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R4 and R3 10.10.10.10") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r4": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R5 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r5": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("modify the router id on r3 to different router id (11.11.11.11)") + input_dict = {"r3": {"bgp": {"router_id": "11.11.11.11"}}} + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Reset bgp process") + step("Verify neighbours are in ESTAB state.") + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Clear ip bgp process with *") + step("Verify neighbours are in ESTAB state.") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbours between R3 and R4 in EVPN address family.") + input_dict = { + "r3": { + "bgp": { + "address_family": { + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": {"unicast": {}}, + "ipv6": {"unicast": {}}, + } + } + } + } + } + }, + "r4": { + "bgp": { + "address_family": { + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": {"unicast": {}}, + "ipv6": {"unicast": {}}, + } + } + } + } + } + }, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_ibgp_p0(): + """ + TC: 2 + Verify iBGP session when same and different router ID is configured. + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R1 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r1": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in idle state.") + result = verify_bgp_convergence(tgen, topo, expected=False) + assert result is not True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Configure the same router id between R2 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r2": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in idle state.") + result = verify_bgp_convergence(tgen, topo, expected=False) + assert result is not True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("modify the router id on r3 to different router id (11.11.11.11)") + input_dict = {"r3": {"bgp": {"router_id": "11.11.11.11"}}} + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo, dut="r3") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Reset bgp process") + step("Verify neighbours are in ESTAB state.") + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Clear ip bgp process with *") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_multi_bgp_nbrs_p0(): + """ + TC: 3 + 3. Verify two different eBGP sessions initiated with same router ID + + """ + tgen = get_topogen() + global bgp_convergence, topo + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R3, R4 and R5 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r4": {"bgp": {"router_id": "10.10.10.10"}}, + "r5": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Configure the same IP address on on R4 and R5 loopback address and \ + change the neighborship to loopback neighbours between R3 to R4 \ + and R3 to R5 respectively." + ) + + topo1 = deepcopy(topo) + + for rtr in ["r4", "r5"]: + topo1["routers"][rtr]["links"]["lo"]["ipv4"] = "192.168.1.1/32" + + topo1["routers"]["r3"]["links"]["lo"]["ipv4"] = "192.168.1.3/32" + build_config_from_json(tgen, topo1, save_bkup=False) + + step( + "change the neighborship to loopback neighbours between R3 to R4 and R3 to R5 respectively." + ) + for rtr in ["r4", "r5"]: + configure_bgp_on_rtr = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"neighbor": {rtr: {"dest_link": {"lo": {}}}}} + } + } + }, + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_rtr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Change the IP address on the R4 loopback.") + topo1["routers"]["r4"]["links"]["lo"]["ipv4"] = "192.168.1.4/32" + build_config_from_json(tgen, topo1, save_bkup=False) + + step("Verify neighbours should be again in ESTAB state. (show ip bgp neighbours)") + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Clear ip bgp process with *") + result = clear_bgp_and_verify(tgen, topo, router="r3") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_chaos1_p2(): + """ + TC: 4 + 4. Chaos - Verify bgp unique rid functionality in chaos scenarios. + + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R3, R4 and R5 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r4": {"bgp": {"router_id": "10.10.10.10"}}, + "r5": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify eBGP session when same router ID is configured and bgpd process is restarted" + ) + + # restart bgpd router and verify + kill_router_daemons(tgen, "r3", ["bgpd"]) + start_router_daemons(tgen, "r3", ["bgpd"]) + + step( + "The session should be established between R3 & R4. " + "Once after restart bgp, neighbor should come back up ." + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step( + "Verify eBGP session when same router ID is configured and neighbor shutdown is issued and again no shutdown." + ) + + input_dict = { + "r3": { + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {"shutdown": True}, + "r3-link2": {"shutdown": True}, + "r3-link3": {"shutdown": True}, + "r3-link4": {"shutdown": True}, + "r3-link5": {"shutdown": True}, + "r3-link6": {"shutdown": True}, + "r3-link7": {"shutdown": True}, + } + }, + "r5": {"dest_link": {"r3": {"shutdown": True}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {"shutdown": True}, + "r3-link2": {"shutdown": True}, + "r3-link3": {"shutdown": True}, + "r3-link4": {"shutdown": True}, + "r3-link5": {"shutdown": True}, + "r3-link6": {"shutdown": True}, + "r3-link7": {"shutdown": True}, + } + }, + "r5": {"dest_link": {"r3": {"shutdown": True}}}, + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict = { + "r3": { + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {"shutdown": False}, + "r3-link2": {"shutdown": False}, + "r3-link3": {"shutdown": False}, + "r3-link4": {"shutdown": False}, + "r3-link5": {"shutdown": False}, + "r3-link6": {"shutdown": False}, + "r3-link7": {"shutdown": False}, + } + }, + "r5": {"dest_link": {"r3": {"shutdown": False}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {"shutdown": False}, + "r3-link2": {"shutdown": False}, + "r3-link3": {"shutdown": False}, + "r3-link4": {"shutdown": False}, + "r3-link5": {"shutdown": False}, + "r3-link6": {"shutdown": False}, + "r3-link7": {"shutdown": False}, + } + }, + "r5": {"dest_link": {"r3": {"shutdown": False}}}, + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "The session should be established between R3 & R4. " + "Once after restart bgp, neighbor should come back up ." + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step( + "Verify eBGP session when same router ID is configured and neighbor config is deleted & reconfigured." + ) + + input_dict = { + "r3": { + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + } + }, + "r5": {"dest_link": {"r3": {}}}, + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r3-link1": {}, + "r3-link2": {}, + "r3-link3": {}, + "r3-link4": {}, + "r3-link5": {}, + "r3-link6": {}, + "r3-link7": {}, + } + }, + "r5": {"dest_link": {"r3": {}}}, + } + } + }, + }, + } + } + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "The session should be established between R3 & R4. " + "Once after restart bgp, neighbor should come back up ." + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step( + "Verify eBGP session when same router ID is configured and FRR router is restarted." + ) + stop_router(tgen, "r3") + start_router(tgen, "r3") + + step( + "The session should be established between R3 & R4. " + "Once after restart bgp, neighbor should come back up ." + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step( + "Verify eBGP session when same router ID is configured and zebra process is restarted" + ) + + kill_router_daemons(tgen, "r3", ["zebra"]) + start_router_daemons(tgen, "r3", ["zebra"]) + + step( + "The session should be established between R3 & R4. " + "Once after restart bgp, neighbor should come back up ." + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_chaos3_p2(): + """ + TC: 4 + 4. Chaos - Verify bgp unique rid functionality when router reboots with same loopback id. + + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + global topo + topo1 = deepcopy(topo) + + for rtr in topo["routers"].keys(): + topo1["routers"][rtr]["links"]["lo"]["ipv4"] = "192.168.1.1/32" + + topo1["routers"]["r3"]["links"]["lo"]["ipv4"] = "192.168.1.3/32" + build_config_from_json(tgen, topo1, save_bkup=False) + + step("verify bgp convergence before starting test case") + + bgp_convergence = verify_bgp_convergence(tgen, topo1) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step( + "Configure loopback on R1 to R5 with IP address 1.1.1.1 on all the routers. Change neighborship on all the routers using loopback neighborship ids." + ) + for rtr in ["r1", "r2", "r4", "r5"]: + configure_bgp_on_rtr = { + "r3": { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"neighbor": {rtr: {"dest_link": {"lo": {}}}}} + } + } + }, + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_rtr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Reboot the router (restart frr) or using watch frr.") + stop_router(tgen, "r3") + start_router(tgen, "r3") + + step("Neighbors between R3, R4 and R3 to R5 should be in ESTB state.") + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Clear bgp process.") + clear_bgp_and_verify(tgen, topo, "r3") + + step("Neighbors between R3, R4 and R3 to R5 should be in ESTB state.") + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_chaos4_p2(): + """ + TC: 6 + 6. Chaos - Verify bgp unique rid functionality when router reboots without any ip addresses. + + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + reset_config_on_routers(tgen) + + global topo + topo1 = deepcopy(topo) + topo2 = deepcopy(topo) + + step( + "Configure base config as per the topology without loopback as well as Ip address on any of the interface." + ) + for rtr in topo["routers"].keys(): + for intf in topo["routers"][rtr]["links"].keys(): + topo1["routers"][rtr]["links"][intf].pop("ipv4") + topo1["routers"][rtr]["links"][intf].pop("ipv6") + + build_config_from_json(tgen, topo1, save_bkup=False) + + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Configure the ip addresses on the physical interfaces") + build_config_from_json(tgen, topo2, save_bkup=False) + + step("All the neighbors should be in ESTAB state.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Configure loopback addresses with higher IP address ") + build_config_from_json(tgen, topo, save_bkup=False) + + step("All the neighbors should be in ESTAB state.") + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Reboot the router (restart frr) or using watch frr.") + stop_router(tgen, "r3") + start_router(tgen, "r3") + + step("Neighbors between R3, R4 and R3 to R5 should be in ESTB state.") + bgp_convergence = verify_bgp_convergence(tgen, topo, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_chaos2_p2(): + """ + TC: 8 + 8. Chaos - Verify bgp unique rid functionality when ospf and bgp share the same router ids. + + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + step("Redistribute routes between bgp and ospf.") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut="r3") + assert ospf_covergence is True, "Testcase :Failed \n Error:" " {}".format( + ospf_covergence + ) + + step( + "Configure ospf between R3 and R4 with same router ids in both ospf and bgp 10.10.10.10 on R3 BGP and OSPF, and 10.10.10.10 in R4 BGP and 11.11.11.11 in R4 OSPF." + ) + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10"}}, + "r4": {"bgp": {"router_id": "10.10.10.10"}}, + "r5": {"bgp": {"router_id": "10.10.10.10"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "The session should be established between R3 & R4 between BGP process and neighborship should be full between OSPF too." + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + ospf_covergence = verify_ospf_neighbor(tgen, topo, dut="r3") + assert ospf_covergence is True, "Testcase :Failed \n Error:" " {}".format( + ospf_covergence + ) + + step("All the routes should be calculated and installed.") + # Verifying RIB routes + protocol = "bgp" + input_dict = topo["routers"] + verify_rib_rtes = { + "ipv4": { + "r3": { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": "Null0"}, + ] + } + }, + "ipv6": { + "r3": { + "static_routes": [ + { + "network": NETWORK["ipv6"], + "next_hop": "Null0", + } + ] + } + }, + } + dut = "r3" + for addr_type in ADDR_TYPES: + result4 = verify_rib( + tgen, + addr_type, + dut, + verify_rib_rtes, + protocol=protocol, + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Clear ospf process.") + clear_ospf(tgen, "r3") + + step("All the routes should be calculated and installed.") + for addr_type in ADDR_TYPES: + result4 = verify_rib( + tgen, + addr_type, + dut, + verify_rib_rtes, + protocol=protocol, + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step("Clear bgp process.") + clear_bgp_and_verify(tgen, topo, "r3") + + step("All the routes should be calculated and installed.") + for addr_type in ADDR_TYPES: + result4 = verify_rib( + tgen, + addr_type, + dut, + verify_rib_rtes, + protocol=protocol, + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + step( + "Configure ospf between R3 and R5. Configure static routes in R5 and redistribute static routes in ospf on R5." + ) + # Covered as base config. + + step("Verify routes are installed in R3 and R4 route tables.") + dut = "r4" + for addr_type in ADDR_TYPES: + result4 = verify_rib( + tgen, + addr_type, + dut, + verify_rib_rtes, + protocol=protocol, + ) + assert result4 is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result4 + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_unique_rid/test_bgp_unique_rid_vrf.py b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid_vrf.py new file mode 100644 index 0000000..2a9e42d --- /dev/null +++ b/tests/topotests/bgp_unique_rid/test_bgp_unique_rid_vrf.py @@ -0,0 +1,466 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +import sys +import time +import pytest +import inspect +import os +from copy import deepcopy + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +"""Following tests are covered to test bgp unique rid functionality. +1. Verify iBGP session when same and different router ID is configured in user VRF(GREEN). +2. Verify eBGP session when same and different router ID is configured in user vrf (VRF RED) +3. Verify two different eBGP sessions initiated with same router ID in user VRf (RED and GREEN) +""" + +################################# +# TOPOLOGY +################################# +""" + + +-------+ + +--------- | R2 | + | +-------+ + |iBGP | + +-------+ | + | R1 | |iBGP + +-------+ | + | | + | iBGP +-------+ eBGP +-------+ + +---------- | R3 |========= | R4 | + +-------+ +-------+ + | + |eBGP + | + +-------+ + | R5 | + +-------+ + + +""" + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topojson import build_config_from_json +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Required to instantiate the topology builder class. +from lib.common_config import ( + start_topology, + write_test_header, + step, + write_test_footer, + check_address_types, + reset_config_on_routers, + check_router_status, +) +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + clear_bgp_and_verify, +) + +# Global variables +topo = None +bgp_convergence = False +NETWORK = { + "ipv4": [ + "192.168.20.1/32", + "192.168.20.2/32", + "192.168.21.1/32", + "192.168.21.2/32", + "192.168.22.1/32", + "192.168.22.2/32", + ], + "ipv6": [ + "fc07:50::1/128", + "fc07:50::2/128", + "fc07:150::1/128", + "fc07:150::2/128", + "fc07:1::1/128", + "fc07:1::2/128", + ], +} + +bgp_convergence = False +ADDR_TYPES = check_address_types() + + +def setup_module(mod): + """setup_module. + + Set up the pytest environment + * `mod`: module name + """ + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_unique_rid_vrf.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Checking BGP convergence + global bgp_convergence + global ADDR_TYPES + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + bgp_convergence = verify_bgp_convergence(tgen, topo) + assert bgp_convergence is True, "setup_module :Failed \n Error:" " {}".format( + bgp_convergence + ) + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# Tests starting +##################################################### + + +def test_bgp_unique_rid_ebgp_vrf_p0(): + """ + TC: 1 + Verify iBGP session when same and different router ID is configured in user VRF(GREEN). + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R4 and R3 10.10.10.10") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + "r4": {"bgp": {"router_id": "10.10.10.10", "local_as": 200, "vrf": "RED"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R5 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + "r5": {"bgp": {"router_id": "10.10.10.10", "local_as": 300, "vrf": "RED"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("modify the router id on r3 to different router id (11.11.11.11)") + input_dict = { + "r3": {"bgp": {"router_id": "11.11.11.11", "local_as": 100, "vrf": "RED"}} + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Reset bgp process") + step("Verify neighbours are in ESTAB state.") + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, router="r3") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Clear ip bgp process with *") + step("Verify neighbours are in ESTAB state.") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Configure neighbours between R3 and R4 in EVPN address family.") + input_dict = { + "r3": { + "bgp": { + "local_as": 100, + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": {"unicast": {}}, + "ipv6": {"unicast": {}}, + } + } + } + }, + } + }, + "r4": { + "bgp": { + "local_as": 200, + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": {"unicast": {}}, + "ipv6": {"unicast": {}}, + } + } + } + }, + } + }, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_ibgp_vrf_p0(): + """ + TC: 2 + Verify eBGP session when same and different router ID is configured in user vrf (VRF RED) + """ + tgen = get_topogen() + global bgp_convergence + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R1 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + "r1": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R2 and R3 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + "r2": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("modify the router id on r3 to different router id (11.11.11.11)") + input_dict = { + "r3": {"bgp": {"router_id": "11.11.11.11", "local_as": 100, "vrf": "RED"}} + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo, dut="r3") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Reset bgp process") + step("Verify neighbours are in ESTAB state.") + dut = "r3" + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step("Clear ip bgp process with *") + result = clear_bgp_and_verify(tgen, topo, dut) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_bgp_unique_rid_multi_bgp_nbrs_vrf_p0(): + """ + TC: 3 + Verify two different eBGP sessions initiated with same router ID in user VRf (RED and GREEN) + + """ + tgen = get_topogen() + global bgp_convergence, topo + + if bgp_convergence is not True: + pytest.skip("skipped because of BGP Convergence failure") + + # test case name + tc_name = inspect.stack()[0][3] + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + + step("Configure base config as per the topology") + reset_config_on_routers(tgen) + + step( + "Base config should be up, verify using BGP convergence on all \ + the routers for IPv4 and IPv6 nbrs" + ) + + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure the same router id between R3, R4 and R5 (10.10.10.10)") + input_dict = { + "r3": {"bgp": {"router_id": "10.10.10.10", "local_as": 100, "vrf": "RED"}}, + "r4": {"bgp": {"router_id": "10.10.10.10", "local_as": 200, "vrf": "RED"}}, + "r5": {"bgp": {"router_id": "10.10.10.10", "local_as": 300, "vrf": "RED"}}, + } + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify neighbours are in ESTAB state.") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Configure the same IP address on on R4 and R5 loopback address and \ + change the neighborship to loopback neighbours between R3 to R4 \ + and R3 to R5 respectively." + ) + + topo1 = deepcopy(topo) + + for rtr in ["r4", "r5"]: + topo1["routers"][rtr]["links"]["lo"]["ipv4"] = "192.168.1.1/32" + + topo1["routers"]["r3"]["links"]["lo"]["ipv4"] = "192.168.1.3/32" + build_config_from_json(tgen, topo1, save_bkup=False) + + step( + "change the neighborship to loopback neighbours between R3 to R4 and R3 to R5 respectively." + ) + for rtr in ["r4", "r5"]: + configure_bgp_on_rtr = { + "r3": { + "bgp": { + "local_as": 100, + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {rtr: {"dest_link": {"lo": {}}}}} + } + }, + }, + } + } + result = create_router_bgp(tgen, topo1, configure_bgp_on_rtr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Change the IP address on the R4 loopback.") + topo1["routers"]["r4"]["links"]["lo"]["ipv4"] = "192.168.1.4/32" + build_config_from_json(tgen, topo1, save_bkup=False) + + step("Verify neighbours should be again in ESTAB state. (show ip bgp neighbours)") + bgp_convergence = verify_bgp_convergence(tgen, topo1, dut="r3") + assert bgp_convergence is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, bgp_convergence + ) + + step("Clear ip bgp process with *") + result = clear_bgp_and_verify(tgen, topo, router="r3") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_unnumbered/__init__.py b/tests/topotests/bgp_unnumbered/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_unnumbered/r1/bgpd.conf b/tests/topotests/bgp_unnumbered/r1/bgpd.conf new file mode 100644 index 0000000..a9d0ec8 --- /dev/null +++ b/tests/topotests/bgp_unnumbered/r1/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65001 + timers bgp 1 9 + no bgp ebgp-requires-policy + neighbor r1-eth0 interface remote-as external + address-family ipv4 unicast + exit-address-family + ! +! diff --git a/tests/topotests/bgp_unnumbered/r1/zebra.conf b/tests/topotests/bgp_unnumbered/r1/zebra.conf new file mode 100644 index 0000000..1cbaea4 --- /dev/null +++ b/tests/topotests/bgp_unnumbered/r1/zebra.conf @@ -0,0 +1,10 @@ +! +interface lo + ip address 172.16.250.254/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +ip forwarding +! + diff --git a/tests/topotests/bgp_unnumbered/r2/bgpd.conf b/tests/topotests/bgp_unnumbered/r2/bgpd.conf new file mode 100644 index 0000000..fd29cd3 --- /dev/null +++ b/tests/topotests/bgp_unnumbered/r2/bgpd.conf @@ -0,0 +1,9 @@ +! +router bgp 65002 + no bgp network import-check + no bgp ebgp-requires-policy + timers bgp 1 9 + neighbor r2-eth0 interface remote-as external + address-family ipv4 uni + network 172.16.255.254/32 +! diff --git a/tests/topotests/bgp_unnumbered/r2/zebra.conf b/tests/topotests/bgp_unnumbered/r2/zebra.conf new file mode 100644 index 0000000..cf6fb6d --- /dev/null +++ b/tests/topotests/bgp_unnumbered/r2/zebra.conf @@ -0,0 +1,12 @@ +! +interface r2-eth0 + ip address 192.168.0.2/24 +! +interface r2-eth1 + ip address 192.168.1.1/24 +! +interface r2-eth2 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_unnumbered/test_bgp_unnumbered.py b/tests/topotests/bgp_unnumbered/test_bgp_unnumbered.py new file mode 100644 index 0000000..2a53547 --- /dev/null +++ b/tests/topotests/bgp_unnumbered/test_bgp_unnumbered.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2022 by +# Donald Sharp +# + +""" +Test some bgp interface based issues that show up +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +# +# Test these events: +# a) create an unnumbered neighbor +# b) shutdown the interface +# c) remove the unnumbered peer in bgp and bgp does not crash +def test_bgp_unnumbered_removal(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_nexthop_cache(): + output = tgen.gears["r1"].vtysh_cmd("show bgp nexthop") + expected = "Current BGP nexthop cache:\n" + return output == expected + + def _bgp_converge(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp 172.16.255.254/32 json") + ) + expected = {"prefix": "172.16.255.254/32"} + + return topotest.json_cmp(output, expected) + + step("Ensure Convergence of BGP") + test_func = functools.partial(_bgp_converge) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + + assert result is None, 'Failed bgp convergence in "{}"'.format(tgen.gears["r2"]) + + step("Shutdown interface r1-eth0") + + tgen.gears["r1"].vtysh_cmd( + """ + configure + int r1-eth0 + shutdown + """ + ) + + step("Remove the neighbor from r1") + tgen.gears["r1"].vtysh_cmd( + """ + configure + router bgp + no neighbor r1-eth0 interface remote-as external + """ + ) + + step("Ensure that BGP does not crash") + test_func = functools.partial(_bgp_nexthop_cache) + success, result = topotest.run_and_expect(test_func, True, count=10, wait=1) + + assert result is True, "BGP did not crash on r1" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_update_delay/__init__.py b/tests/topotests/bgp_update_delay/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_update_delay/r1/bgpd.conf b/tests/topotests/bgp_update_delay/r1/bgpd.conf new file mode 100644 index 0000000..8ebb509 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r1/bgpd.conf @@ -0,0 +1,10 @@ +! exit1 +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.255.1 remote-as 65002 + neighbor 192.168.255.1 timers connect 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_update_delay/r1/zebra.conf b/tests/topotests/bgp_update_delay/r1/zebra.conf new file mode 100644 index 0000000..9904bb4 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r1/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface lo + ip address 172.16.255.254/32 +! +interface r1-eth0 + ip address 192.168.255.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_update_delay/r2/bgpd.conf b/tests/topotests/bgp_update_delay/r2/bgpd.conf new file mode 100644 index 0000000..438f995 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r2/bgpd.conf @@ -0,0 +1,18 @@ +! spine +router bgp 65002 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.255.2 remote-as 65001 + neighbor 192.168.254.2 remote-as 65003 + neighbor 192.168.253.2 remote-as 65004 + neighbor 192.168.255.2 timers connect 10 + neighbor 192.168.254.2 timers connect 10 + neighbor 192.168.253.2 timers connect 10 +! + router bgp 65002 vrf vrf1 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.252.2 remote-as 65005 + neighbor 192.168.252.2 timers connect 10 + ! +! diff --git a/tests/topotests/bgp_update_delay/r2/zebra.conf b/tests/topotests/bgp_update_delay/r2/zebra.conf new file mode 100644 index 0000000..1fcedaa --- /dev/null +++ b/tests/topotests/bgp_update_delay/r2/zebra.conf @@ -0,0 +1,16 @@ +! spine +interface r2-eth0 + ip address 192.168.255.1/30 +! +interface r2-eth1 + ip address 192.168.254.1/30 +! +interface r2-eth2 + ip address 192.168.253.1/30 +! +interface r2-eth3 + ip address 192.168.252.1/30 + vrf vrf1 +! +ip forwarding +! diff --git a/tests/topotests/bgp_update_delay/r3/bgpd.conf b/tests/topotests/bgp_update_delay/r3/bgpd.conf new file mode 100644 index 0000000..53e5178 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r3/bgpd.conf @@ -0,0 +1,10 @@ +! exit2 +router bgp 65003 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.254.1 remote-as 65002 + neighbor 192.168.254.1 timers connect 10 + address-family ipv4 unicast + redistribute connected + ! +! diff --git a/tests/topotests/bgp_update_delay/r3/zebra.conf b/tests/topotests/bgp_update_delay/r3/zebra.conf new file mode 100644 index 0000000..f490d97 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r3/zebra.conf @@ -0,0 +1,9 @@ +! exit2 +interface lo + ip address 172.16.254.254/32 +! +interface r3-eth0 + ip address 192.168.254.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_update_delay/r4/bgpd.conf b/tests/topotests/bgp_update_delay/r4/bgpd.conf new file mode 100644 index 0000000..34cb429 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r4/bgpd.conf @@ -0,0 +1,11 @@ +! exit2 +router bgp 65004 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.253.1 remote-as 65002 + neighbor 192.168.253.1 timers connect 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_update_delay/r4/zebra.conf b/tests/topotests/bgp_update_delay/r4/zebra.conf new file mode 100644 index 0000000..baba04c --- /dev/null +++ b/tests/topotests/bgp_update_delay/r4/zebra.conf @@ -0,0 +1,9 @@ +! exit2 +interface lo + ip address 172.16.253.254/32 +! +interface r4-eth0 + ip address 192.168.253.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_update_delay/r5/bgpd.conf b/tests/topotests/bgp_update_delay/r5/bgpd.conf new file mode 100644 index 0000000..66ecc70 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r5/bgpd.conf @@ -0,0 +1,11 @@ +! exit1 +router bgp 65005 + no bgp ebgp-requires-policy + timers bgp 3 9 + neighbor 192.168.252.1 remote-as 65002 + neighbor 192.168.252.1 timers connect 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_update_delay/r5/zebra.conf b/tests/topotests/bgp_update_delay/r5/zebra.conf new file mode 100644 index 0000000..8adf6f8 --- /dev/null +++ b/tests/topotests/bgp_update_delay/r5/zebra.conf @@ -0,0 +1,9 @@ +! exit1 +interface lo + ip address 172.16.252.254/32 +! +interface r1-eth0 + ip address 192.168.252.2/30 +! +ip forwarding +! diff --git a/tests/topotests/bgp_update_delay/test_bgp_update_delay.py b/tests/topotests/bgp_update_delay/test_bgp_update_delay.py new file mode 100644 index 0000000..4e66cf5 --- /dev/null +++ b/tests/topotests/bgp_update_delay/test_bgp_update_delay.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_update_delay.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by +# Don Slice +# + +""" +Test the ability to define update-delay to delay bestpath, rib install +and advertisement to peers when frr is started, restarted or "clear ip +bgp *" is performed. Test both the vrf-specific and global configuration +and operation. + +r1 +| +r2----r3 +| \ +| \ +r5 r4 + + +r2 is UUT and peers with r1, r3, and r4 in default bgp instance. +r2 peers with r5 in vrf vrf1. + +Check r2 initial convergence in default table +Define update-delay with max-delay in the default bgp instance on r2 +Shutdown peering on r1 toward r2 so that delay timers can be exercised +Clear bgp neighbors on r2 and then check for the 'in progress' indicator +Check that r2 only installs route learned from r4 after the max-delay timer expires +Define update-delay with max-delay and estabish-wait and check json output showing set +Clear neighbors on r2 and check that r3 installs route from r4 after establish-wait time +Remove update-delay timer on r2 to verify that it goes back to normal behavior +Clear neighbors on r2 and check that route install time on r2 does not delay +Define global bgp update-delay with max-delay and establish-wait on r2 +Check that r2 default instance and vrf1 have the max-delay and establish set +Clear neighbors on r2 and check route-install time is after the establish-wait timer + +Note that the keepalive/hold times were changed to 3/9 and the connect retry timer +to 10 to improve the odds the convergence timing in this test case is useful in the +event of packet loss. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +def build_topo(tgen): + for routern in range(1, 6): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r5"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_update_delay(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router1 = tgen.gears["r1"] + router2 = tgen.gears["r2"] + router3 = tgen.gears["r3"] + + # initial convergence without update-delay defined + def _bgp_converge(router): + output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.2 json")) + expected = { + "192.168.255.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 2}}, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_check_update_delay(router): + output = json.loads(router.vtysh_cmd("show ip bgp sum json")) + expected = {"ipv4Unicast": {"updateDelayLimit": 20}} + + return topotest.json_cmp(output, expected) + + def _bgp_check_update_delay_in_progress(router): + output = json.loads(router.vtysh_cmd("show ip bgp sum json")) + expected = {"ipv4Unicast": {"updateDelayInProgress": True}} + + return topotest.json_cmp(output, expected) + + def _bgp_check_route_install(router): + output = json.loads(router.vtysh_cmd("show ip route 172.16.253.254/32 json")) + expected = {"172.16.253.254/32": [{"protocol": "bgp"}]} + + return topotest.json_cmp(output, expected) + + def _bgp_check_update_delay_and_wait(router): + output = json.loads(router.vtysh_cmd("show ip bgp sum json")) + expected = { + "ipv4Unicast": {"updateDelayLimit": 20, "updateDelayEstablishWait": 10} + } + + return topotest.json_cmp(output, expected) + + def _bgp_check_update_delay(router): + output = json.loads(router.vtysh_cmd("show ip bgp sum json")) + expected = {"ipv4Unicast": {"updateDelayLimit": 20}} + + return topotest.json_cmp(output, expected) + + def _bgp_check_vrf_update_delay_and_wait(router): + output = json.loads(router.vtysh_cmd("show ip bgp vrf vrf1 sum json")) + expected = { + "ipv4Unicast": {"updateDelayLimit": 20, "updateDelayEstablishWait": 10} + } + + return topotest.json_cmp(output, expected) + + # Check r2 initial convergence in default table + test_func = functools.partial(_bgp_converge, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed bgp convergence in "{}"'.format(router2) + + # Define update-delay with max-delay in the default bgp instance on r2 + router2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + update-delay 20 + """ + ) + + # Shutdown peering on r1 toward r2 so that delay timers can be exercised + router1.vtysh_cmd( + """ + configure terminal + router bgp 65001 + neighbor 192.168.255.1 shut + """ + ) + + # Clear bgp neighbors on r2 and then check for the 'in progress' indicator + router2.vtysh_cmd("""clear ip bgp *""") + + test_func = functools.partial(_bgp_check_update_delay_in_progress, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed to set update-delay max-delay timer "{}"'.format( + router2 + ) + + # Check that r2 only installs route learned from r4 after the max-delay timer expires + test_func = functools.partial(_bgp_check_route_install, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed to install route after update-delay "{}"'.format( + router2 + ) + + # Define update-delay with max-delay and estabish-wait and check json output showing set + router2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + update-delay 20 10 + """ + ) + + test_func = functools.partial(_bgp_check_update_delay_and_wait, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert ( + result is None + ), 'Failed to set max-delay and establish-weight timers in "{}"'.format(router2) + + # Define update-delay with max-delay and estabish-wait and check json output showing set + router2.vtysh_cmd("""clear ip bgp *""") + + test_func = functools.partial(_bgp_check_route_install, router3) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert ( + result is None + ), 'Failed to installed advertised route after establish-wait timer espired "{}"'.format( + router2 + ) + + # Remove update-delay timer on r2 to verify that it goes back to normal behavior + router2.vtysh_cmd( + """ + configure terminal + router bgp 65002 + no update-delay + """ + ) + + # Clear neighbors on r2 and check that route install time on r2 does not delay + router2.vtysh_cmd("""clear ip bgp *""") + + test_func = functools.partial(_bgp_check_route_install, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed to remove update-delay delay timing "{}"'.format( + router2 + ) + + # Define global bgp update-delay with max-delay and establish-wait on r2 + router2.vtysh_cmd( + """ + configure terminal + bgp update-delay 20 10 + """ + ) + + # Check that r2 default instance and vrf1 have the max-delay and establish set + test_func = functools.partial(_bgp_check_update_delay_and_wait, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed to set update-delay in default instance "{}"'.format( + router2 + ) + + test_func = functools.partial(_bgp_check_vrf_update_delay_and_wait, router2) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, 'Failed to set update-delay in vrf1 "{}"'.format(router2) + + # Clear neighbors on r2 and check route-install time is after the establish-wait timer + router2.vtysh_cmd("""clear ip bgp *""") + + test_func = functools.partial(_bgp_check_route_install, router3) + success, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert ( + result is None + ), 'Failed to installed advertised route after establish-wait timer espired "{}"'.format( + router2 + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpn_5549_route_map/__init__.py b/tests/topotests/bgp_vpn_5549_route_map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpn_5549_route_map/cpe1/bgpd.conf b/tests/topotests/bgp_vpn_5549_route_map/cpe1/bgpd.conf new file mode 100644 index 0000000..013cd8c --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/cpe1/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/cpe1/zebra.conf b/tests/topotests/bgp_vpn_5549_route_map/cpe1/zebra.conf new file mode 100644 index 0000000..49dcfc3 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/cpe1/zebra.conf @@ -0,0 +1,9 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface cpe1-eth0 + ip address 192.168.1.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/cpe2/bgpd.conf b/tests/topotests/bgp_vpn_5549_route_map/cpe2/bgpd.conf new file mode 100644 index 0000000..d65d507 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/cpe2/bgpd.conf @@ -0,0 +1,6 @@ +router bgp 65000 + no bgp ebgp-requires-policy + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/cpe2/zebra.conf b/tests/topotests/bgp_vpn_5549_route_map/cpe2/zebra.conf new file mode 100644 index 0000000..a47319e --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/cpe2/zebra.conf @@ -0,0 +1,6 @@ +! +interface cpe2-eth0 + ip address 192.168.2.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe1/bgpd.conf b/tests/topotests/bgp_vpn_5549_route_map/pe1/bgpd.conf new file mode 100644 index 0000000..93da025 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe1/bgpd.conf @@ -0,0 +1,38 @@ +router bgp 65001 + bgp router-id 10.10.10.10 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:1::2 remote-as internal + neighbor 2001:db8:1::2 update-source 2001:db8:1::1 + neighbor 2001:db8:1::2 timers 1 3 + neighbor 2001:db8:1::2 timers connect 1 + neighbor 2001:db8:1::2 capability extended-nexthop + address-family ipv4 vpn + neighbor 2001:db8:1::2 activate + neighbor 2001:db8:1::2 route-map pe2 out + exit-address-family +! +router bgp 65001 vrf RED + bgp router-id 192.168.1.2 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 + address-family ipv4 unicast + label vpn export 1111 + rd vpn export 192.168.1.2:2 + rt vpn import 192.168.2.2:2 192.168.1.2:2 + rt vpn export 192.168.1.2:2 + export vpn + import vpn + exit-address-family +! +ip prefix-list cpe1 seq 5 permit 172.16.255.1/32 +! +route-map pe2 permit 10 + match ip address prefix-list cpe1 + set ipv6 vpn next-hop 2001:db8::1 +! +route-map pe2 permit 20 +exit +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe1/ldpd.conf b/tests/topotests/bgp_vpn_5549_route_map/pe1/ldpd.conf new file mode 100644 index 0000000..fb40f06 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe1/ldpd.conf @@ -0,0 +1,10 @@ +mpls ldp + router-id 10.10.10.10 + ! + address-family ipv4 + discovery transport-address 10.10.10.10 + ! + interface pe1-eth1 + ! + ! +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe1/ospf6d.conf b/tests/topotests/bgp_vpn_5549_route_map/pe1/ospf6d.conf new file mode 100644 index 0000000..0053d1e --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe1/ospf6d.conf @@ -0,0 +1,12 @@ +! +interface lo + ipv6 ospf6 area 0 +! +interface pe1-eth1 + ipv6 ospf6 area 0 + ipv6 ospf6 hello-interval 1 + ipv6 ospf6 dead-interval 3 +! +router ospf6 + ospf6 router-id 10.10.10.10 +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe1/zebra.conf b/tests/topotests/bgp_vpn_5549_route_map/pe1/zebra.conf new file mode 100644 index 0000000..da91055 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe1/zebra.conf @@ -0,0 +1,14 @@ +! +interface lo + ip address 10.10.10.10/32 + ipv6 address 2001:db8:1::1/128 +! +interface pe1-eth0 vrf RED + ip address 192.168.1.2/24 +! +interface pe1-eth1 + ip address 10.0.1.1/24 + ipv6 address 2001:db8::1/64 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe2/bgpd.conf b/tests/topotests/bgp_vpn_5549_route_map/pe2/bgpd.conf new file mode 100644 index 0000000..6db1eef --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe2/bgpd.conf @@ -0,0 +1,29 @@ +router bgp 65001 + bgp router-id 10.10.10.20 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 2001:db8:1::1 remote-as internal + neighbor 2001:db8:1::1 update-source 2001:db8:1::2 + neighbor 2001:db8:1::1 timers 1 3 + neighbor 2001:db8:1::1 timers connect 1 + neighbor 2001:db8:1::1 capability extended-nexthop + address-family ipv4 vpn + neighbor 2001:db8:1::1 activate + exit-address-family +! +router bgp 65001 vrf RED + bgp router-id 192.168.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 + address-family ipv4 unicast + label vpn export 2222 + rd vpn export 192.168.2.2:2 + rt vpn import 192.168.2.2:2 192.168.1.2:2 + rt vpn export 192.168.2.2:2 + export vpn + import vpn + exit-address-family +! + diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe2/ldpd.conf b/tests/topotests/bgp_vpn_5549_route_map/pe2/ldpd.conf new file mode 100644 index 0000000..e2b5359 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe2/ldpd.conf @@ -0,0 +1,10 @@ +mpls ldp + router-id 10.10.10.20 + ! + address-family ipv4 + discovery transport-address 10.10.10.20 + ! + interface pe2-eth0 + ! + ! +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe2/ospf6d.conf b/tests/topotests/bgp_vpn_5549_route_map/pe2/ospf6d.conf new file mode 100644 index 0000000..f79bb4f --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe2/ospf6d.conf @@ -0,0 +1,12 @@ +! +interface lo + ipv6 ospf6 area 0 +! +interface pe2-eth0 + ipv6 ospf6 area 0 + ipv6 ospf6 hello-interval 1 + ipv6 ospf6 dead-interval 3 +! +router ospf6 + ospf6 router-id 10.10.10.20 +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/pe2/zebra.conf b/tests/topotests/bgp_vpn_5549_route_map/pe2/zebra.conf new file mode 100644 index 0000000..19ef7bf --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/pe2/zebra.conf @@ -0,0 +1,14 @@ +! +interface lo + ip address 10.10.10.20/32 + ipv6 address 2001:db8:1::2/128 +! +interface pe2-eth1 vrf RED + ip address 192.168.2.2/24 +! +interface pe2-eth0 + ip address 10.0.1.2/24 + ipv6 address 2001:db8::2/64 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vpn_5549_route_map/test_bgp_vpn_5549_route_map.py b/tests/topotests/bgp_vpn_5549_route_map/test_bgp_vpn_5549_route_map.py new file mode 100644 index 0000000..eb29875 --- /dev/null +++ b/tests/topotests/bgp_vpn_5549_route_map/test_bgp_vpn_5549_route_map.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +Check if we can override VPN underlay next-hop from PE1 to PE2. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("cpe1") + tgen.add_router("cpe2") + tgen.add_router("pe1") + tgen.add_router("pe2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["cpe1"]) + switch.add_link(tgen.gears["pe1"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["pe1"]) + switch.add_link(tgen.gears["pe2"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["pe2"]) + switch.add_link(tgen.gears["cpe2"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["pe1"] + pe2 = tgen.gears["pe2"] + + pe1.run("ip link add RED type vrf table 1001") + pe1.run("ip link set up dev RED") + pe2.run("ip link add RED type vrf table 1001") + pe2.run("ip link set up dev RED") + pe1.run("ip link set pe1-eth0 master RED") + pe2.run("ip link set pe2-eth1 master RED") + + pe1.run("sysctl -w net.ipv4.ip_forward=1") + pe2.run("sysctl -w net.ipv4.ip_forward=1") + pe1.run("sysctl -w net.mpls.conf.pe1-eth0.input=1") + pe2.run("sysctl -w net.mpls.conf.pe2-eth1.input=1") + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF6, os.path.join(CWD, "{}/ospf6d.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_LDP, os.path.join(CWD, "{}/ldpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_vpn_5549(): + tgen = get_topogen() + + pe2 = tgen.gears["pe2"] + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_vpn_nexthop_changed(): + output = json.loads(pe2.vtysh_cmd("show bgp ipv4 vpn json")) + expected = { + "routes": { + "routeDistinguishers": { + "192.168.1.2:2": { + "172.16.255.1/32": [ + {"valid": True, "nexthops": [{"ip": "2001:db8::1"}]} + ], + "192.168.1.0/24": [ + {"valid": True, "nexthops": [{"ip": "2001:db8:1::1"}]} + ], + } + } + } + } + return topotest.json_cmp(output, expected) + + def _bgp_verify_v4_nexthop_validity(): + output = json.loads(tgen.gears["cpe1"].vtysh_cmd("show bgp nexthop json")) + expected = { + "ipv4": { + "192.168.1.2": { + "valid": True, + "complete": True, + "igpMetric": 0, + "pathCount": 0, + "nexthops": [{"interfaceName": "cpe1-eth0"}], + }, + } + } + return topotest.json_cmp(output, expected) + + def _bgp_verify_v6_global_nexthop_validity(): + output = json.loads(tgen.gears["pe2"].vtysh_cmd("show bgp nexthop json")) + expected = { + "ipv6": { + "2001:db8::1": { + "valid": True, + "complete": True, + "igpMetric": 0, + "pathCount": 2, + "nexthops": [{"interfaceName": "pe2-eth0"}], + }, + "2001:db8:1::1": { + "valid": True, + "complete": True, + "igpMetric": 10, + "pathCount": 2, + "peer": "2001:db8:1::1", + "nexthops": [{"interfaceName": "pe2-eth0"}], + }, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_vpn_nexthop_changed) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed overriding IPv6 next-hop for VPN underlay" + + test_func = functools.partial(_bgp_verify_v4_nexthop_validity) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "IPv4 nexthop is invalid" + + test_func = functools.partial(_bgp_verify_v6_global_nexthop_validity) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "IPv6 nexthop is invalid" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv4_asbr/__init__.py b/tests/topotests/bgp_vpnv4_asbr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv4_asbr/h1/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/h1/zebra.conf new file mode 100644 index 0000000..2237224 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/h1/zebra.conf @@ -0,0 +1,7 @@ +log stdout +ip route 172.31.1.0/24 172.31.0.1 +ip route 172.31.2.0/24 172.31.0.1 +interface h1-eth0 + ip address 172.31.0.10/24 +! + diff --git a/tests/topotests/bgp_vpnv4_asbr/h2/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/h2/zebra.conf new file mode 100644 index 0000000..d650bc8 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/h2/zebra.conf @@ -0,0 +1,6 @@ +log stdout +ip route 172.31.0.0/24 172.31.1.1 +interface h2-eth0 + ip address 172.31.1.10/24 +! + diff --git a/tests/topotests/bgp_vpnv4_asbr/h3/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/h3/zebra.conf new file mode 100644 index 0000000..5676485 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/h3/zebra.conf @@ -0,0 +1,6 @@ +log stdout +ip route 172.31.0.0/24 172.31.2.1 +interface h3-eth0 + ip address 172.31.2.10/24 +! + diff --git a/tests/topotests/bgp_vpnv4_asbr/r1/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_asbr/r1/bgp_ipv4_routes.json new file mode 100644 index 0000000..184ab31 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r1/bgp_ipv4_routes.json @@ -0,0 +1,49 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "172.31.0.10/32": [ + { + "prefix": "172.31.0.10", + "prefixLen": 32, + "network": "172.31.0.10\/32", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.3", + "afi": "ipv4", + "used": true + } + ] + }, + { + "prefix": "172.31.0.10", + "prefixLen": 32, + "network": "172.31.0.10\/32", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.1/32": [ + { + "prefix": "172.31.0.1", + "prefixLen": 32, + "network": "172.31.0.1\/32", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_asbr/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_asbr/r1/bgpd.conf new file mode 100644 index 0000000..473e56b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r1/bgpd.conf @@ -0,0 +1,30 @@ +router bgp 65500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp enforce-first-as + neighbor 192.0.2.100 remote-as 65500 + neighbor 192.0.2.100 update-source lo + neighbor 192.168.0.100 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.100 activate + no neighbor 192.0.2.100 activate + network 192.0.2.1/32 + exit-address-family + address-family ipv4 labeled-unicast + neighbor 192.168.0.100 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.100 activate + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv4_asbr/r1/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/r1/zebra.conf new file mode 100644 index 0000000..2f12b72 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r1/zebra.conf @@ -0,0 +1,10 @@ +log stdout +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth1 vrf vrf1 + ip address 172.31.0.1/24 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_vpnv4_asbr/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_asbr/r2/bgpd.conf new file mode 100644 index 0000000..c7244c0 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r2/bgpd.conf @@ -0,0 +1,32 @@ +!debug bgp nht +!debug bgp zebra +!debug bgp labelpool +router bgp 65500 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp enforce-first-as + neighbor 192.0.2.100 remote-as 65500 + neighbor 192.0.2.100 update-source lo + neighbor 192.168.0.100 remote-as 65500 + neighbor 192.168.1.200 remote-as 65502 + address-family ipv4 unicast + no neighbor 192.168.0.100 activate + no neighbor 192.168.1.200 activate + network 192.0.2.2/32 + exit-address-family + address-family ipv4 labeled-unicast + neighbor 192.168.0.100 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.100 activate + neighbor 192.0.2.100 next-hop-self + neighbor 192.168.1.200 activate + exit-address-family +! +interface r2-eth1 + mpls bgp forwarding + mpls bgp l3vpn-multi-domain-switching +! +interface r2-eth0 + mpls bgp l3vpn-multi-domain-switching +! diff --git a/tests/topotests/bgp_vpnv4_asbr/r2/ipv4_vpn_summary.json b/tests/topotests/bgp_vpnv4_asbr/r2/ipv4_vpn_summary.json new file mode 100644 index 0000000..d33c5f5 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r2/ipv4_vpn_summary.json @@ -0,0 +1,24 @@ +{ + "routerId":"192.0.2.2", + "as":65500, + "vrfId":0, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.0.2.100":{ + "remoteAs":65500, + "localAs":65500, + "version":4, + "state":"Established", + "peerState":"OK" + }, + "192.168.1.200":{ + "remoteAs":65502, + "localAs":65500, + "version":4, + "state":"Established", + "peerState":"OK" + } + }, + "totalPeers":2 +} diff --git a/tests/topotests/bgp_vpnv4_asbr/r2/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/r2/zebra.conf new file mode 100644 index 0000000..43508a4 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r2/zebra.conf @@ -0,0 +1,13 @@ +log stdout +ip route 192.168.1.3/32 r2-eth1 +interface lo + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.0.2/24 + mpls enable +! +interface r2-eth1 + ip address 192.168.1.2/24 + mpls enable +! diff --git a/tests/topotests/bgp_vpnv4_asbr/r3/bgpd.conf b/tests/topotests/bgp_vpnv4_asbr/r3/bgpd.conf new file mode 100644 index 0000000..b7592e4 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r3/bgpd.conf @@ -0,0 +1,26 @@ +router bgp 65501 + bgp router-id 192.0.2.3 + no bgp ebgp-requires-policy + no bgp enforce-first-as + neighbor 192.168.1.200 remote-as 65502 + address-family ipv4 unicast + no neighbor 192.168.1.200 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.1.200 activate + exit-address-family +! +router bgp 65501 vrf vrf1 + bgp router-id 192.0.2.3 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:3 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r3-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_asbr/r3/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/r3/zebra.conf new file mode 100644 index 0000000..6376785 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/r3/zebra.conf @@ -0,0 +1,14 @@ +log stdout +ip route 192.168.1.3/32 r3-eth0 +interface r3-eth1 vrf vrf1 + ip address 172.31.1.1/24 +! +interface r3-eth2 vrf vrf1 + ip address 172.31.2.1/24 +! +interface r3-eth3 vrf vrf1 + ip address 172.31.3.1/24 +! +interface r3-eth0 + ip address 192.168.1.3/24 +! diff --git a/tests/topotests/bgp_vpnv4_asbr/rr100/bgpd.conf b/tests/topotests/bgp_vpnv4_asbr/rr100/bgpd.conf new file mode 100644 index 0000000..845d71b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/rr100/bgpd.conf @@ -0,0 +1,29 @@ +router bgp 65500 + bgp router-id 192.0.2.100 + no bgp ebgp-requires-policy + neighbor 192.0.2.2 remote-as 65500 + neighbor 192.0.2.2 update-source lo + neighbor 192.168.0.2 remote-as 65500 + neighbor 192.0.2.1 remote-as 65500 + neighbor 192.0.2.1 update-source lo + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.1 activate + no neighbor 192.0.2.1 activate + no neighbor 192.168.0.2 activate + no neighbor 192.0.2.2 activate + network 192.0.2.100/32 + exit-address-family + address-family ipv4 labeled-unicast + neighbor 192.168.0.1 activate + neighbor 192.168.0.2 activate + neighbor 192.168.0.1 route-reflector-client + neighbor 192.168.0.2 route-reflector-client + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.1 activate + neighbor 192.0.2.2 activate + neighbor 192.0.2.1 route-reflector-client + neighbor 192.0.2.2 route-reflector-client + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv4_asbr/rr100/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/rr100/zebra.conf new file mode 100644 index 0000000..2fa5285 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/rr100/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface lo + ip address 192.0.2.100/32 +! +interface rr100-eth0 + ip address 192.168.0.100/24 +! diff --git a/tests/topotests/bgp_vpnv4_asbr/rs200/bgpd.conf b/tests/topotests/bgp_vpnv4_asbr/rs200/bgpd.conf new file mode 100644 index 0000000..fa3cb54 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/rs200/bgpd.conf @@ -0,0 +1,19 @@ +debug bgp nht +debug bgp zebra +debug bgp labelpool +router bgp 65502 + bgp router-id 192.0.2.200 + no bgp ebgp-requires-policy + neighbor 192.168.1.3 remote-as 65501 + neighbor 192.168.1.2 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.1.2 activate + no neighbor 192.168.1.3 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.1.3 activate + neighbor 192.168.1.2 activate + neighbor 192.168.1.3 route-server-client + neighbor 192.168.1.2 route-server-client + exit-address-family +! \ No newline at end of file diff --git a/tests/topotests/bgp_vpnv4_asbr/rs200/zebra.conf b/tests/topotests/bgp_vpnv4_asbr/rs200/zebra.conf new file mode 100644 index 0000000..98793ca --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/rs200/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface rs200-eth0 + ip address 192.168.1.200/24 +! diff --git a/tests/topotests/bgp_vpnv4_asbr/test_bgp_vpnv4_asbr.py b/tests/topotests/bgp_vpnv4_asbr/test_bgp_vpnv4_asbr.py new file mode 100644 index 0000000..39865eb --- /dev/null +++ b/tests/topotests/bgp_vpnv4_asbr/test_bgp_vpnv4_asbr.py @@ -0,0 +1,865 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_vpnv4_asbr.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2023 by 6WIND +# + +""" + test_bgp_vpnv4_asbr.py: Test the FRR BGP daemon with rfc4364 option 10b + r1, r2, and r100 are in an iBGP AS, while r2, r3 do an eBGP peering + h1 is a host behind r1 VRF1, and {h2,h3} are hosts behind r3 VRF1 + The test demonstrates the connectivity across the network between h1 and h3. + + + +----------+ +----+--------+ +--------+ +--------+-----+ + | |172.31.0.0|vrf | r1 |192.168.0.0/24| r2 |192.168.1.0/24|r3 | vrf | + | h1 +----------+ | 1+------+-------+ +------+-------+3 | +--- 172.31.3.0/24 + | 10 | |VRF1|AS65500 | | | AS65500| | |AS65501 |VRF1 | + +----------+ +-------------+ | +--------+ | +--------+--+-++ + 192.0.2.1 | 192.0.2.2 | 172| | + +----------+ +----+--------+ 31| | + |rr100 | |rs200/AS65502| 1| | + +----------+ +-------------+ 0| | + 192.0.2.100 +--------+ /24| | + | | +----------+----+ | + |h3 | | | | + |10 | | h2 | | + +---+----+ | 10 | | + | +----------+ | + |172.31.2.0/24 | + +--------------------------------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgpcheck import ( + check_show_bgp_vpn_prefix_found, + check_show_bgp_vpn_prefix_not_found, +) +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.checkping import check_ping + + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Allocate 8 devices + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + tgen.add_router("h1") + tgen.add_router("h2") + tgen.add_router("h3") + tgen.add_router("rr100") + tgen.add_router("rs200") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["rr100"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["h1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["rs200"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["h2"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["h3"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r3"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + + for rname in ("r1", "r3"): + for cmd in cmds_list: + input = cmd.format(rname) + logger.info("input: " + cmd) + output = tgen.net[rname].cmd(cmd.format(rname)) + logger.info("output: " + output) + + cmds_list = [ + "ip link set dev {0}-eth2 master vrf1", + "ip link set dev {0}-eth3 master vrf1", + ] + for cmd in cmds_list: + input = cmd.format("r3") + logger.info("input: " + input) + output = tgen.net["r3"].cmd(input) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + if rname in ("r1", "r2", "r3", "rr100", "rs200"): + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def bgp_vpnv4_prefix_check(router, rd, prefix, label, nexthop): + """ + Dump and check 'show bgp ipv4 vpn json' output. An assert is triggered in case test fails + * 'router': the router to check + * 'rd': The route distinguisher expected + * 'prefix': The prefix expected + * 'label': The label expected associated with the ('rd','prefix') tuple + * 'nexthop': The nexthop expected associated with the ('rd','prefix') tuple + """ + + def _check(router, prefix, rd, label, nexthop): + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + if not dump: + return "{0}, {1}, route distinguisher {2} not present".format( + router.name, prefix, rd + ) + for dumped_rd, pathes in dump.items(): + if dumped_rd != rd: + continue + for path in pathes["paths"]: + if "remoteLabel" not in path.keys(): + return "{0}, {1}, rd {2}, remoteLabel not present".format( + router.name, prefix, rd + ) + if str(path["remoteLabel"]) != label: + continue + + if "nexthops" not in path.keys(): + return "{0}, {1}, rd {2}, no nexthops present".format( + router.name, prefix, rd + ) + + for nh in path["nexthops"]: + if "ip" not in nh.keys(): + return "{0}, {1}, rd {2}, no ipv4 nexthop available".format( + router.name, prefix, rd + ) + if nh["ip"] != nexthop: + continue + return None + return "{0}, {1}, rd {2}, remoteLabel {3}, nexthop {4} not found".format( + router.name, prefix, rd, label, nexthop + ) + + func = functools.partial(_check, router, prefix, rd, label, nexthop) + success, result = topotest.run_and_expect(func, None, count=20, wait=0.5) + assert_msg = "{}, show bgp ipv4 vpn {}, rd {}, label {} nexthop {}".format( + router.name, prefix, rd, label, nexthop + ) + assert result is None, assert_msg + " not found" + logger.info(assert_msg + " found") + + +def mpls_table_get_entry(router, out_label, out_nexthop): + """ + Get the in_label from tuple (out_label, out_nexthop) + * 'router': the router to check + * 'out_label': The outgoing label expected + * 'out_nexthop': The outgoing nexthop expected + """ + dump = router.vtysh_cmd("show mpls table json", isjson=True) + for in_label, label_info in dump.items(): + for nh in label_info["nexthops"]: + if nh["type"] != "BGP" or "installed" not in nh.keys(): + continue + if "nexthop" in nh.keys(): + if nh["nexthop"] != out_nexthop: + continue + if "outLabelStack" in nh.keys(): + if out_label not in nh["outLabelStack"]: + continue + return in_label + return None + + +def mpls_table_check_entry(router, out_label, out_nexthop): + """ + Dump and check 'show mpls table json' output. An assert is triggered in case test fails + * 'router': the router to check + * 'out_label': The outgoing label expected + * 'out_nexthop': The outgoing nexthop expected + """ + logger.info("Checking MPLS labels on {}".format(router.name)) + dump = router.vtysh_cmd("show mpls table json", isjson=True) + for in_label, label_info in dump.items(): + for nh in label_info["nexthops"]: + if nh["type"] != "BGP" or "installed" not in nh.keys(): + continue + if "nexthop" in nh.keys(): + if nh["nexthop"] != out_nexthop: + continue + if "outLabelStack" in nh.keys(): + if out_label not in nh["outLabelStack"]: + continue + logger.info( + "{}, show mpls table, entry in_label {} out_label {} out_nexthop {} found".format( + router.name, in_label, nh["outLabelStack"], nh["nexthop"] + ) + ) + return None + return "{}, show mpls table, entry matching in_label {} out_label {} out_nexthop {} not found".format( + router.name, in_label, out_label, out_nexthop + ) + + +def check_show_mpls_table_entry_label_not_found(router, inlabel): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = {"inLabel": inlabel, "installed": True} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def check_show_bgp_vpn_ok(router, vpnv4_entries): + """ + Check on router that BGP l3vpn entries are present + Check there is an MPLS entry bound to that BGP L3VPN entry + Extract the Label value and check on the distributed router the BGP L3VPN entry + If check fail, an assert is triggered. + * 'router': the router to check BGP VPN RIB + * 'vpnv4_entries': dictionary that contains the list of prefixes, and the distributed router to look after + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + vpnv4_nexthops = {"r1": "192.0.2.2", "r3": "192.168.1.2"} + vpnv4_nht = {"192.0.2.1": "192.168.0.1", "192.168.1.3": "192.168.1.3"} + label_ip_entries = {} + + def _return_remote_label_nh_rd(router, prefix): + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + assert_msg = ( + "{}, prefix {} not available or label not found", + router.name, + prefix, + ) + assert dump, assert_msg + for rd, pathes in dump.items(): + for path in pathes["paths"]: + if "remoteLabel" not in path.keys(): + assert 0, assert_msg + for nh in path["nexthops"]: + if "ip" in nh.keys(): + return path["remoteLabel"], nh["ip"], rd + assert 0, assert_msg + + def _check_nexthop_available(router, prefix): + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + if not dump: + return "{0}, {1}, route distinguisher not present".format( + router.name, prefix + ) + for rd, pathes in dump.items(): + for path in pathes["paths"]: + if "remoteLabel" not in path.keys(): + return "{0}, {1}, remoteLabel not present".format( + router.name, prefix + ) + if "nexthops" not in path.keys(): + return "{0}, {1}, no nexthop available".format(router.name, prefix) + return None + + for prefix, rname_to_test in vpnv4_entries.items(): + func = functools.partial(_check_nexthop_available, router, prefix) + success, result = topotest.run_and_expect(func, None, count=20, wait=0.5) + assert result is None, "Failed to detect prefix {} on router {}".format( + prefix, router.name + ) + + for prefix, rname_to_test in vpnv4_entries.items(): + l3vpn_label, l3vpn_nh, l3vpn_rd = _return_remote_label_nh_rd(router, prefix) + logger.info( + "{0}, {1}, label value is {2}, nh is {3}".format( + router.name, prefix, l3vpn_label, l3vpn_nh + ) + ) + test_func = functools.partial( + mpls_table_check_entry, router, l3vpn_label, vpnv4_nht[l3vpn_nh] + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, result + + in_label = mpls_table_get_entry(router, l3vpn_label, vpnv4_nht[l3vpn_nh]) + label_ip_entries[prefix] = in_label + + bgp_vpnv4_prefix_check( + tgen.gears[rname_to_test], + l3vpn_rd, + prefix, + in_label, + vpnv4_nexthops[rname_to_test], + ) + + return label_ip_entries + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + Check that Labels are as expected in r1, r2,and r3 + Check ping connectivity between h1 and h2 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # check that r2 peerings are ok + logger.info("Checking BGP ipv4 vpn summary for r2") + router = tgen.gears["r2"] + json_file = "{}/{}/ipv4_vpn_summary.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 vpn summary json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_mpls_setup_ok(): + """ + tests for the r1 to r3 direction: checks for prefix=('172.31.1.0/24','172.31.2.0/24','172.31.3.0/24') + r2. get label from 'prefix' + check that r2. show mpls table has an entry with outbound label set to the label from 172.31.1.0/24 + r2. get label from mpls entry + check that r1: show bgp ipv4 vpn 172.31.1.0/24 has label from r2.mpls entry + tests for the r3 to r1 direction + r2. get label from 172.31.0.0/24 + check that r2. show mpls table has an entry with outbound label set that includes the label from 172.31.0.0/24 + r2. get label from mpls entry + check that r3: show bgp ipv4 vpn 172.31.0.0/24 has label from r2.mpls entry + check that h1. ping 172.31.1.10 (h2) is ok. + check that h1. ping 172.31.2.10 (h3) is ok. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r2"] + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + router.vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + vpnv4_checks = { + "172.31.1.0/24": "r1", + "172.31.2.0/24": "r1", + "172.31.3.0/24": "r1", + "172.31.0.0/24": "r3", + } + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on all devices".format( + router.name + ) + ) + check_show_bgp_vpn_ok(router, vpnv4_checks) + + logger.info("h1, check that ping from h1 to (h2,h3) is ok") + check_ping("h1", "172.31.1.10", True, 20, 0.5) + check_ping("h1", "172.31.2.10", True, 20, 0.5) + + +def test_r3_prefixes_removed(): + """ + Remove BGP redistributed updates from r3. + Check that the BGP VPN updates from the updates are not present on r2. + Check that the 'show bgp ipv4 vpn' and 'show mpls table' are ok for 172.31.3.0/24 + Remove the 172.31.3.0/24 update from BGP on r3. + Check that the BGP VPN updates from r3 are not present on r2. + Check that the 'show mpls table' entry previously seen disappeared + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r3"] + logger.info("{}, keeping only 172.31.3.0/24 network".format(router.name)) + router.vtysh_cmd("configure terminal\ninterface r3-eth1 vrf vrf1\nshutdown\n") + router.vtysh_cmd("configure terminal\ninterface r3-eth2 vrf vrf1\nshutdown\n") + + router = tgen.gears["r2"] + logger.info( + "{}, check that 'show bgp ipv4 vpn' has only 172.31.3.0/24 network from r3".format( + router.name + ) + ) + + for prefix in ("172.31.1.0/24", "172.31.2.0/24"): + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still present".format(router.name, prefix) + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + router.vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + prefix = "172.31.3.0/24" + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on r2 and on r1".format( + router.name + ) + ) + vpnv4_checks = { + prefix: "r1", + } + label_ip_entries = check_show_bgp_vpn_ok(router, vpnv4_checks) + + router = tgen.gears["r3"] + logger.info("{}, removing {} network".format(router.name, prefix)) + router.vtysh_cmd("configure terminal\ninterface r3-eth3 vrf vrf1\nshutdown\n") + + router = tgen.gears["r2"] + logger.info( + "{}, check that 'show bgp ipv4 vpn' has not {} network from r3".format( + router.name, prefix + ) + ) + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still present".format(router.name, prefix) + + logger.info( + "{}, check that 'show mpls table {}' is not present".format( + router.name, label_ip_entries[prefix] + ) + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, label_ip_entries[prefix] + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with in_label {} still present".format( + label_ip_entries[prefix] + ) + + +def test_r3_prefixes_added_back(): + """ + Add back the 172.31.3.0/24 network from r3 + Check on r2 that MPLS switching entry appears when the 1st BGP update is received + Check the IP connectivity (h1,h2) and (h1,h3) + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r3"] + prefix = "172.31.3.0/24" + logger.info("{}, restoring the {} network from r3".format(router.name, prefix)) + router.vtysh_cmd("configure terminal\ninterface r3-eth3 vrf vrf1\nno shutdown\n") + + router = tgen.gears["r2"] + logger.info( + "{}, check that 'show bgp ipv4 vpn' has {} network from r3".format( + router.name, prefix + ) + ) + + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} not present".format(router.name, prefix) + + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on r2 and on r1".format( + router.name + ) + ) + vpnv4_checks = { + prefix: "r1", + } + check_show_bgp_vpn_ok(router, vpnv4_checks) + + router = tgen.gears["r3"] + logger.info( + "{}, restoring the redistribute connected prefixes from r3".format(router.name) + ) + router.vtysh_cmd("configure terminal\ninterface r3-eth1 vrf vrf1\nno shutdown\n") + router.vtysh_cmd("configure terminal\ninterface r3-eth2 vrf vrf1\nno shutdown\n") + router = tgen.gears["r2"] + for prefix in ("172.31.1.0/24", "172.31.2.0/24"): + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} not present".format(router.name, prefix) + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + tgen.gears["r2"].vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + +def test_unconfigure_nexthop_change_nexthop_self(): + """ + Get the list of labels advertised from r2 to r1 + On r2, disable next-hop-self for 192.0.2.100 neighbor + Check that the list of labels are not present in 'show mpls table' + Check that r1 received the prefixes with the original (next-hop,label) + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + vpnv4_checks = { + "172.31.1.0/24": "r1", + "172.31.2.0/24": "r1", + "172.31.3.0/24": "r1", + } + logger.info( + "{}, Get the list of labels allocated for prefixes from r3".format(router.name) + ) + label_ip_entries = check_show_bgp_vpn_ok(router, vpnv4_checks) + + logger.info( + "{}, disable next-hop-self for 192.0.2.100 neighbor".format(router.name) + ) + router = tgen.gears["r2"] + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500\naddress-family ipv4 vpn\nno neighbor 192.0.2.100 next-hop-self\n" + ) + + for prefix, label in label_ip_entries.items(): + logger.info( + "{}, check mpls entry for {} with in_label {} is not present'".format( + router.name, prefix, label + ) + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, label + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry for {} with in_label {} still present".format( + prefix, label + ) + + router = tgen.gears["r1"] + for prefix, label in label_ip_entries.items(): + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:3", + label=label, + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, mpls vpn update {} label {} is present".format( + router.name, prefix, label + ) + for prefix, label in label_ip_entries.items(): + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:3", + nexthop="192.168.1.3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, mpls vpn update {} label {} is present".format( + router.name, prefix, label + ) + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + tgen.gears["r2"].vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + +def test_reconfigure_nexthop_change_nexthop_self(): + """ + Get the list of labels advertised from r2 to r1 + On r2, enable next-hop-self for 192.0.2.100 neighbor + Check that the list of labels are present in 'show mpls table' + Check that r1 received the prefixes with the original (next-hop,label) + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + logger.info("{}, enable next-hop-self for 192.0.2.100 neighbor".format(router.name)) + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500\naddress-family ipv4 vpn\nneighbor 192.0.2.100 next-hop-self\n" + ) + vpnv4_checks = { + "172.31.1.0/24": "r1", + "172.31.2.0/24": "r1", + "172.31.3.0/24": "r1", + } + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on r2 and on r1".format( + router.name + ) + ) + check_show_bgp_vpn_ok(router, vpnv4_checks) + + logger.info("h1, check that ping from h1 to (h2,h3) is ok") + check_ping("h1", "172.31.1.10", True, 20, 0.5) + check_ping("h1", "172.31.2.10", True, 20, 0.5) + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + router.vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + +def test_declare_vpn_network_with_different_label(): + """ + declare a vpnv4 network on r3. + check that a new VPNv4 entry is received on r2. + Check that the list of labels are present in 'show mpls table' + Check that r1 received the prefixes with the new (next-hop,label) + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r3"] + logger.info( + "{}, declare static 33.33.33.33/32 network rd 33:33 label 33".format( + router.name + ) + ) + router.vtysh_cmd( + "configure terminal\nrouter bgp 65501\nno bgp network import-check\n" + ) + router.vtysh_cmd( + "configure terminal\nrouter bgp 65501\naddress-family ipv4 vpn\nnetwork 33.33.33.33/32 rd 444:3 label 33\n" + ) + + router = tgen.gears["r2"] + vpnv4_entries = { + "172.31.1.0/24": None, + "172.31.2.0/24": None, + "172.31.3.0/24": None, + "33.33.33.33/32": 33, + } + + for prefix, label in vpnv4_entries.items(): + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:3", + label=label, + nexthop="192.168.1.3", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {}, label {} not present".format( + router.name, prefix, label + ) + + vpnv4_checks = { + "172.31.1.0/24": "r1", + "172.31.2.0/24": "r1", + "172.31.3.0/24": "r1", + "33.33.33.33/32": "r1", + } + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on r2 and on r1".format( + router.name + ) + ) + check_show_bgp_vpn_ok(router, vpnv4_checks) + + +def test_filter_vpn_network_from_r1(): + """ + Get the list of labels in 'show mpls table' + filter network from r1 + check that the vpnv4 entry on r2 is not present + Check that the associated mpls entry is not present + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + + vpnv4_checks = { + "172.31.0.0/24": "r3", + } + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on r2 and on r3".format( + router.name + ) + ) + label_ip_entries = check_show_bgp_vpn_ok(router, vpnv4_checks) + + for prefix, label in label_ip_entries.items(): + logger.info("{}, filter prefix {} from r1".format(router.name, prefix)) + router.vtysh_cmd( + "configure terminal\nroute-map rmap deny 1\nmatch ip next-hop address 192.0.2.1\n" + ) + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500\naddress-family ipv4 vpn\nneighbor 192.0.2.100 route-map rmap in\n" + ) + logger.info( + "{}, check that prefix {} is not present".format(router.name, prefix) + ) + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + "172.31.0.0/24", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {}, is still present".format( + router.name, prefix + ) + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + router.vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + logger.info( + "{}, check that show mpls table {} is not present".format( + router.name, label + ) + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, int(label) + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry for {} with in_label {} still present".format( + prefix, label + ) + + +def test_unfilter_vpn_network_from_r1(): + """ + unfilter network from r1 + check that the vpnv4 entry on r2 is present + Check that the list of labels are present in 'show mpls table' + Check that r3 received the prefixes with the new (next-hop,label) + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r2"] + prefix = "172.31.0.0/24" + + logger.info("{}, filter prefix {} from r1".format(router.name, prefix)) + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500\naddress-family ipv4 vpn\nno neighbor 192.0.2.100 route-map rmap in\n" + ) + + logger.info("{}, check that prefix {} is present".format(router.name, prefix)) + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, router, "ipv4", prefix, "444:1" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {}, is not present".format(router.name, prefix) + + vpnv4_checks = { + "172.31.0.0/24": "r3", + } + logger.info( + "{}, check that 'show bgp ipv4 vpn' and 'show mpls table' are set accordingly on all devices".format( + router.name + ) + ) + check_show_bgp_vpn_ok(router, vpnv4_checks) + + # diagnostic + logger.info("Dumping mplsvpn nexthop table") + router.vtysh_cmd("show bgp mplsvpn-nh-label-bind detail", isjson=False) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv4_ebgp/__init__.py b/tests/topotests/bgp_vpnv4_ebgp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv4_ebgp/r1/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_ebgp/r1/bgp_ipv4_routes.json new file mode 100644 index 0000000..184ab31 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r1/bgp_ipv4_routes.json @@ -0,0 +1,49 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "172.31.0.10/32": [ + { + "prefix": "172.31.0.10", + "prefixLen": 32, + "network": "172.31.0.10\/32", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.3", + "afi": "ipv4", + "used": true + } + ] + }, + { + "prefix": "172.31.0.10", + "prefixLen": 32, + "network": "172.31.0.10\/32", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.2", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.1/32": [ + { + "prefix": "172.31.0.1", + "prefixLen": 32, + "network": "172.31.0.1\/32", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_ebgp/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_ebgp/r1/bgpd.conf new file mode 100644 index 0000000..d8a45ce --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r1/bgpd.conf @@ -0,0 +1,29 @@ +bgp route-map delay-timer 1 +router bgp 65500 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65501 + neighbor 192.168.0.3 remote-as 65501 + address-family ipv4 unicast + no neighbor 192.168.0.3 activate + no neighbor 192.168.0.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.2 activate + neighbor 192.168.0.3 activate + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r1-eth0 + mpls bgp forwarding +! \ No newline at end of file diff --git a/tests/topotests/bgp_vpnv4_ebgp/r1/ipv4_routes.json b/tests/topotests/bgp_vpnv4_ebgp/r1/ipv4_routes.json new file mode 100644 index 0000000..79b020a --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r1/ipv4_routes.json @@ -0,0 +1,62 @@ +{ + "172.31.0.10/32": [ + { + "prefix": "172.31.0.10/32", + "prefixLen": 32, + "protocol": "bgp", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "vrf": "default", + "active": true, + "labels":[ + 102 + ] + }, + { + "flags": 3, + "fib": true, + "ip": "192.168.0.3", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "vrf": "default", + "active": true, + "labels":[ + 102 + ] + } + ] + } + ], + "172.31.0.1/32": [ + { + "prefix": "172.31.0.1/32", + "prefixLen": 32, + "protocol": "connected", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops":[ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r1-eth1", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vpnv4_ebgp/r1/zebra.conf b/tests/topotests/bgp_vpnv4_ebgp/r1/zebra.conf new file mode 100644 index 0000000..f626e44 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r1/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r1-eth1 vrf vrf1 + ip address 172.31.0.1/32 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! diff --git a/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_routes.json new file mode 100644 index 0000000..1fc3a4b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_routes.json @@ -0,0 +1,38 @@ +{ + "vrfName": "vrf1", + "localAS": 65501, + "routes": + { + "172.31.0.1/32": [ + { + "prefix": "172.31.0.1", + "prefixLen": 32, + "network": "172.31.0.1\/32", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.10/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.10", + "prefixLen": 32, + "network": "172.31.0.10\/32", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_vpn_route_1723101.json b/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_vpn_route_1723101.json new file mode 100644 index 0000000..2ed7631 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r2/bgp_ipv4_vpn_route_1723101.json @@ -0,0 +1,50 @@ +{ + "444:1":{ + "prefix":"172.31.0.1/32", + "advertisedTo":{ + "192.168.0.1":{ + } + }, + "paths":[ + { + "aspath":{ + "string":"65500", + "segments":[ + { + "type":"as-sequence", + "list":[ + 65500 + ] + } + ], + "length":1 + }, + "origin":"incomplete", + "metric":0, + "valid":true, + "bestpath":{ + "overall":true, + "selectionReason":"First path received" + }, + "extendedCommunity":{ + "string":"RT:52:101" + }, + "remoteLabel":102, + "nexthops":[ + { + "ip":"192.168.0.1", + "afi":"ipv4", + "metric":0, + "accessible":true, + "used":true + } + ], + "peer":{ + "peerId":"192.168.0.1", + "routerId":"192.0.2.1", + "type":"external" + } + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_ebgp/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_ebgp/r2/bgpd.conf new file mode 100644 index 0000000..e873469 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65501 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! +router bgp 65501 vrf vrf1 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r2-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_ebgp/r2/zebra.conf b/tests/topotests/bgp_vpnv4_ebgp/r2/zebra.conf new file mode 100644 index 0000000..bbc5240 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r2/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r2-eth1 vrf vrf1 + ip address 172.31.0.10/32 +! +interface r2-eth0 + ip address 192.168.0.2/24 +! diff --git a/tests/topotests/bgp_vpnv4_ebgp/r3/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_ebgp/r3/bgp_ipv4_routes.json new file mode 100644 index 0000000..19797dd --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r3/bgp_ipv4_routes.json @@ -0,0 +1,38 @@ +{ + "vrfName": "vrf1", + "localAS": 65501, + "routes": + { + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0\/24", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_ebgp/r3/bgpd.conf b/tests/topotests/bgp_vpnv4_ebgp/r3/bgpd.conf new file mode 100644 index 0000000..a327638 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r3/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65501 + bgp router-id 192.0.2.3 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! +router bgp 65502 vrf vrf1 + bgp router-id 192.0.2.3 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:3 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r3-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_ebgp/r3/zebra.conf b/tests/topotests/bgp_vpnv4_ebgp/r3/zebra.conf new file mode 100644 index 0000000..4412c04 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/r3/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r3-eth1 vrf vrf1 + ip address 172.31.0.10/32 +! +interface r3-eth0 + ip address 192.168.0.3/24 +! diff --git a/tests/topotests/bgp_vpnv4_ebgp/test_bgp_vpnv4_ebgp.py b/tests/topotests/bgp_vpnv4_ebgp/test_bgp_vpnv4_ebgp.py new file mode 100644 index 0000000..1898243 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_ebgp/test_bgp_vpnv4_ebgp.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_vpnv4_ebgp.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by 6WIND +# + +""" + test_bgp_vpnv4_ebgp.py: Test the FRR BGP daemon with EBGP direct connection +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.bgpcheck import ( + check_show_bgp_vpn_prefix_found, + check_show_bgp_vpn_prefix_not_found, +) +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r3") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r3"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + + for cmd in cmds_list: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r3") + logger.info("input: " + cmd) + output = tgen.net["r3"].cmd(cmd.format("r3")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + logger.info("Dump some context for r1") + router.vtysh_cmd("show bgp ipv4 vpn") + router.vtysh_cmd("show bgp summary") + router.vtysh_cmd("show bgp vrf vrf1 ipv4") + router.vtysh_cmd("show running-config") + router = tgen.gears["r2"] + logger.info("Dump some context for r2") + router.vtysh_cmd("show bgp ipv4 vpn") + router.vtysh_cmd("show bgp summary") + router.vtysh_cmd("show bgp vrf vrf1 ipv4") + router.vtysh_cmd("show running-config") + + # Check IPv4 routing tables on r1 + logger.info("Checking IPv4 routes for convergence on r1") + router = tgen.gears["r1"] + json_file = "{}/{}/ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + assert 0, "ipv4_routes.json file not found" + return + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route vrf vrf1 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP IPv4 routing tables on r1 + logger.info("Checking BGP IPv4 routes for convergence on r1") + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + assert 0, "bgp_ipv4_routes.json file not found" + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP IPv4 imported entry is not detected as local + # "selectionReason": "Locally configured route" + donna = tgen.gears["r1"].vtysh_cmd( + "show bgp vrf vrf1 ipv4 172.31.0.10/32 json", isjson=True + ) + routes = donna["paths"] + selectionReasonFound = False + for route in routes: + if "bestpath" not in route.keys(): + continue + if "selectionReason" not in route["bestpath"].keys(): + continue + + if "Locally configured route" == route["bestpath"]["selectionReason"]: + assert 0, "imported prefix has wrong reason detected" + + selectionReasonFound = True + + if not selectionReasonFound: + assertmsg = '"{}" imported prefix has wrong reason detected'.format(router.name) + assert False, assertmsg + + # Check BGP IPv4 routing tables on r2 not installed + logger.info("Checking BGP IPv4 routes for convergence on r2") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + assert 0, "bgp_ipv4_routes.json file not found" + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_export_route_target_empty(): + """ + Check that when removing 'rt vpn export' command, exported prefix is removed + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, Remove 'rt vpn export 52:100' command") + router.vtysh_cmd( + """ +configure terminal +router bgp 65500 vrf vrf1 + address-family ipv4 unicast + no rt vpn export 52:100 +""" + ) + + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is removed".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still present".format(router.name, prefix) + + +def test_export_route_target_with_routemap_with_export_route_target(): + """ + Check that when removing 'rt vpn export' command, exported prefix is added back + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, configuring route target with route-map with export route target") + router.vtysh_cmd( + """ +configure terminal +router bgp 65500 vrf vrf1 + address-family ipv4 unicast + route-map vpn export RMAP +! +route-map RMAP permit 1 + set extcommunity rt 52:100 +""" + ) + + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is added back".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still not present".format(router.name, prefix) + + +def test_export_route_target_with_routemap_without_export_route_target(): + """ + Check that when removing 'set extcommunity rt' command, prefix is removed + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, removing 'set extcommunity rt 52:100.") + router.vtysh_cmd( + """ +configure terminal +route-map RMAP permit 1 + no set extcommunity rt +""" + ) + + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is removed".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still present".format(router.name, prefix) + + +def test_export_route_target_with_default_command(): + """ + Add back route target with 'rt vpn export' command + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, detach route-map and re-add route target vpn export") + router.vtysh_cmd( + """ +configure terminal +router bgp 65500 vrf vrf1 + address-family ipv4 unicast + rt vpn export 52:100 +""" + ) + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is added back".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still not present".format(router.name, prefix) + + +def test_export_suppress_route_target_with_route_map_command(): + """ + Add back route target with 'rt vpn export' command + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, add an extended comm-list to delete 52:100") + + router.vtysh_cmd( + """ +configure terminal +bgp extcommunity-list 1 permit rt 52:100 +! +route-map RMAP permit 1 + set extended-comm-list 1 delete +""" + ) + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is removed".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_not_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still present".format(router.name, prefix) + + +def test_export_add_route_target_to_route_map_command(): + """ + Add route target with route-map so that route is added back + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + logger.info("r1, add an additional set extcommunity 52:101") + router.vtysh_cmd( + """ +configure terminal +route-map RMAP permit 1 + set extcommunity rt 52:101 +""" + ) + prefix = "172.31.0.1/32" + logger.info("r1, check that exported prefix {} is added back".format(prefix)) + test_func = partial( + check_show_bgp_vpn_prefix_found, + router, + "ipv4", + prefix, + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, vpnv4 update {} still not present".format(router.name, prefix) + + +def test_adj_rib_out_label_change(): + """ + Check that changing the VPN label on r1 + is propagated on r2 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Changing VPN label value to export") + dump = tgen.gears["r1"].vtysh_cmd( + """ +configure terminal + router bgp 65500 vrf vrf1 + address-family ipv4 unicast + label vpn export 102 +""" + ) + # Check BGP IPv4 route entry for 172.31.0.1 on r1 + logger.info("Checking BGP IPv4 routes for convergence on r1") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_ipv4_vpn_route_1723101.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 vpn 172.31.0.1/32 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_adj_rib_in_label_change(): + """ + Check that syncinig with ADJ-RIB-in on r2 + permits restoring the initial label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enable soft-reconfiguration inbound on r2") + + r2 = tgen.gears["r2"] + r2.vtysh_cmd( + """ +configure terminal +router bgp 65501 + address-family ipv4 vpn + neighbor 192.168.0.1 soft-reconfiguration inbound +""" + ) + + logger.info("Applying a deny-all route-map to input on r2") + r2.vtysh_cmd( + """ +configure terminal +route-map DENY-ALL deny 1 +! +router bgp 65501 + address-family ipv4 vpn + neighbor 192.168.0.1 route-map DENY-ALL in +""" + ) + + # check that 172.31.0.1 should not be present + logger.info("Check that received update 172.31.0.1 is not present") + + expected = {} + test_func = partial( + topotest.router_json_cmp, + r2, + "show bgp ipv4 vpn 172.31.0.1/32 json", + expected, + exact=True, + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.1 still present" + + +def test_adj_rib_in_label_change_remove_rmap(): + """ + Check that syncinig with ADJ-RIB-in on r2 + permits restoring the initial label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Removing the deny-all route-map from input on r2") + + r2 = tgen.gears["r2"] + r2.vtysh_cmd( + """ +configure terminal +router bgp 65501 + address-family ipv4 vpn + no neighbor 192.168.0.1 route-map DENY-ALL in +""" + ) + # Check BGP IPv4 route entry for 172.31.0.1 on r1 + logger.info( + "Checking that 172.31.0.1 BGP update is present and has valid label on r2" + ) + json_file = "{}/{}/bgp_ipv4_vpn_route_1723101.json".format(CWD, r2.name) + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + r2, + "show bgp ipv4 vpn 172.31.0.1/32 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(r2.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv4_gre/__init__.py b/tests/topotests/bgp_vpnv4_gre/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv4_gre/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_gre/r1/bgpd.conf new file mode 100644 index 0000000..295811b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r1/bgpd.conf @@ -0,0 +1,27 @@ +router bgp 65500 + bgp router-id 192.0.2.1 + neighbor 192.0.2.2 remote-as 65500 + neighbor 192.0.2.2 update-source 192.0.2.1 + address-family ipv4 unicast + no neighbor 192.0.2.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.2 activate + neighbor 192.0.2.2 route-map rmap in + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + distance bgp 21 201 41 + label vpn export 101 + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +route-map rmap permit 1 + set l3vpn next-hop encapsulation gre +! diff --git a/tests/topotests/bgp_vpnv4_gre/r1/ipv4_routes.json b/tests/topotests/bgp_vpnv4_gre/r1/ipv4_routes.json new file mode 100644 index 0000000..e57e21b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r1/ipv4_routes.json @@ -0,0 +1,50 @@ +{ + "10.200.0.0/24": [ + { + "prefix": "10.200.0.0/24", + "prefixLen": 24, + "protocol": "bgp", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 201, + "metric": 0, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "192.168.0.2", + "afi": "ipv4", + "interfaceName": "r1-gre0", + "vrf": "default", + "active": true, + "labels":[ + 102 + ] + } + ] + } + ], + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0/24", + "prefixLen": 24, + "protocol": "connected", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops":[ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r1-eth1", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vpnv4_gre/r1/zebra.conf b/tests/topotests/bgp_vpnv4_gre/r1/zebra.conf new file mode 100644 index 0000000..11780a8 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r1/zebra.conf @@ -0,0 +1,14 @@ +log stdout +ip route 192.0.2.2/32 192.168.0.2 +interface lo + ip address 192.0.2.1/32 +! +interface r1-gre0 + ip address 192.168.0.1/24 +! +interface r1-eth1 vrf vrf1 + ip address 10.201.0.1/24 +! +interface r1-eth0 + ip address 10.125.0.1/24 +! diff --git a/tests/topotests/bgp_vpnv4_gre/r2/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_gre/r2/bgp_ipv4_routes.json new file mode 100644 index 0000000..e50d5dd --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r2/bgp_ipv4_routes.json @@ -0,0 +1,38 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0\/24", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.0.2.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_gre/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_gre/r2/bgpd.conf new file mode 100644 index 0000000..bf05866 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r2/bgpd.conf @@ -0,0 +1,22 @@ +router bgp 65500 + bgp router-id 192.0.2.2 + neighbor 192.0.2.1 remote-as 65500 + neighbor 192.0.2.1 update-source 192.0.2.2 + address-family ipv4 unicast + no neighbor 192.0.2.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.0.2.1 activate + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv4_gre/r2/zebra.conf b/tests/topotests/bgp_vpnv4_gre/r2/zebra.conf new file mode 100644 index 0000000..de88a4b --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/r2/zebra.conf @@ -0,0 +1,14 @@ +log stdout +ip route 192.0.2.1/32 192.168.0.1 +interface lo + ip address 192.0.2.2/32 +! +interface r2-gre0 + ip address 192.168.0.2/24 +! +interface r2-eth1 vrf vrf1 + ip address 10.200.0.2/24 +! +interface r2-eth0 + ip address 10.125.0.2/24 +! diff --git a/tests/topotests/bgp_vpnv4_gre/test_bgp_vpnv4_gre.py b/tests/topotests/bgp_vpnv4_gre/test_bgp_vpnv4_gre.py new file mode 100644 index 0000000..31743c8 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_gre/test_bgp_vpnv4_gre.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_vpnv4_gre.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 by 6WIND +# + +""" + test_bgp_vpnv4_gre.py: Test the FRR BGP daemon with BGP IPv6 interface + with route advertisements on a separate netns. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 10 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + "ip tunnel add {0}-gre0 mode gre ttl 64 dev {0}-eth0 local 10.125.0.{1} remote 10.125.0.{2}", + "ip link set dev {0}-gre0 up", + "echo 1 > /proc/sys/net/mpls/conf/{0}-gre0/input", + ] + + for cmd in cmds_list: + input = cmd.format("r1", "1", "2") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1", "1", "2")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2", "2", "1") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2", "2", "1")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + logger.info("Dump some context for r1") + router.vtysh_cmd("show bgp ipv4 vpn") + router.vtysh_cmd("show bgp summary") + router.vtysh_cmd("show bgp vrf vrf1 ipv4") + router.vtysh_cmd("show running-config") + router = tgen.gears["r2"] + logger.info("Dump some context for r2") + router.vtysh_cmd("show bgp ipv4 vpn") + router.vtysh_cmd("show bgp summary") + router.vtysh_cmd("show bgp vrf vrf1 ipv4") + router.vtysh_cmd("show running-config") + + # Check IPv4 routing tables on r1 + logger.info("Checking IPv4 routes for convergence on r1") + router = tgen.gears["r1"] + json_file = "{}/{}/ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + assert 0, "ipv4_routes.json file not found" + return + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route vrf vrf1 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP IPv4 routing tables on r2 not installed + logger.info("Checking BGP IPv4 routes for convergence on r2") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + assert 0, "bgp_ipv4_routes.json file not found" + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=40, wait=2) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv4_noretain/__init__.py b/tests/topotests/bgp_vpnv4_noretain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_noretain/r1/bgpd.conf new file mode 100644 index 0000000..0709e43 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/bgpd.conf @@ -0,0 +1,46 @@ +router bgp 65500 + bgp router-id 192.0.2.1 + neighbor 10.125.0.2 remote-as 65500 + address-family ipv4 unicast + no neighbor 10.125.0.2 activate + label vpn export 100 + rd vpn export 192.0.2.1:0 + rt vpn import 192.0.2.2:400 + import vpn + exit-address-family + address-family ipv4 vpn + neighbor 10.125.0.2 activate + no bgp retain route-target all + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + label vpn export 101 + rd vpn export 192.0.2.1:1 + rt vpn import 192.0.2.2:100 + rt vpn export 192.0.2.1:100 + export vpn + import vpn + exit-address-family +! +router bgp 65500 vrf vrf3 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + label vpn export 103 + rd vpn export 192.0.2.1:3 + rt vpn export 192.0.2.1:300 + export vpn + exit-address-family +! +router bgp 65500 vrf vrf4 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + label vpn export 104 + rd vpn export 192.0.2.1:4 + rt vpn import 192.0.2.1:300 + import vpn + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_all.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_all.json new file mode 100644 index 0000000..648bf85 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_all.json @@ -0,0 +1,175 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"192.0.2.1", + "defaultLocPrf":100, + "localAS":65500, + "routes":{ + "routeDistinguishers":{ + "192.0.2.1:1":{ + "10.101.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.101.0.0", + "prefixLen":24, + "network":"10.101.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf1", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.1:3":{ + "10.103.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.103.0.0", + "prefixLen":24, + "network":"10.103.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf3", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:1":{ + "10.201.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.201.0.0", + "prefixLen":24, + "network":"10.201.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:2":{ + "10.202.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.202.0.0", + "prefixLen":24, + "network":"10.202.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:3":{ + "10.203.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.203.0.0", + "prefixLen":24, + "network":"10.203.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:4":{ + "10.204.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.204.0.0", + "prefixLen":24, + "network":"10.204.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init.json new file mode 100644 index 0000000..f01607a --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init.json @@ -0,0 +1,121 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"192.0.2.1", + "defaultLocPrf":100, + "localAS":65500, + "routes":{ + "routeDistinguishers":{ + "192.0.2.1:1":{ + "10.101.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.101.0.0", + "prefixLen":24, + "network":"10.101.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf1", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.1:3":{ + "10.103.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.103.0.0", + "prefixLen":24, + "network":"10.103.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf3", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:1":{ + "10.201.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.201.0.0", + "prefixLen":24, + "network":"10.201.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:4":{ + "10.204.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.204.0.0", + "prefixLen":24, + "network":"10.204.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf2.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf2.json new file mode 100644 index 0000000..6df6c69 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf2.json @@ -0,0 +1,148 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"192.0.2.1", + "defaultLocPrf":100, + "localAS":65500, + "routes":{ + "routeDistinguishers":{ + "192.0.2.1:1":{ + "10.101.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.101.0.0", + "prefixLen":24, + "network":"10.101.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf1", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.1:3":{ + "10.103.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.103.0.0", + "prefixLen":24, + "network":"10.103.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf3", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:1":{ + "10.201.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.201.0.0", + "prefixLen":24, + "network":"10.201.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:2":{ + "10.202.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.202.0.0", + "prefixLen":24, + "network":"10.202.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:4":{ + "10.204.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.204.0.0", + "prefixLen":24, + "network":"10.204.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf3.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf3.json new file mode 100644 index 0000000..7a17ff0 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vpn_routes_no_retain_init_plus_r2_vrf3.json @@ -0,0 +1,148 @@ +{ + "vrfId":0, + "vrfName":"default", + "routerId":"192.0.2.1", + "defaultLocPrf":100, + "localAS":65500, + "routes":{ + "routeDistinguishers":{ + "192.0.2.1:1":{ + "10.101.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.101.0.0", + "prefixLen":24, + "network":"10.101.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf1", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.1:3":{ + "10.103.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"external", + "prefix":"10.103.0.0", + "prefixLen":24, + "network":"10.103.0.0\/24", + "metric":0, + "weight":32768, + "peerId":"(unspec)", + "path":"", + "origin":"incomplete", + "announceNexthopSelf":true, + "nhVrfName":"vrf3", + "nexthops":[ + { + "ip":"0.0.0.0", + "hostname":"r1", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:1":{ + "10.201.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.201.0.0", + "prefixLen":24, + "network":"10.201.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:3":{ + "10.203.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.203.0.0", + "prefixLen":24, + "network":"10.203.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + }, + "192.0.2.2:4":{ + "10.204.0.0/24":[ + { + "valid":true, + "bestpath":true, + "selectionReason":"First path received", + "pathFrom":"internal", + "prefix":"10.204.0.0", + "prefixLen":24, + "network":"10.204.0.0\/24", + "metric":0, + "locPrf":100, + "weight":0, + "peerId":"10.125.0.2", + "path":"", + "origin":"incomplete", + "nexthops":[ + { + "ip":"10.125.0.2", + "hostname":"r2", + "afi":"ipv4", + "used":true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_init.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_init.json new file mode 100644 index 0000000..2769c6e --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_init.json @@ -0,0 +1,156 @@ +{ + "default": { + "vrfName": "default", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.204.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.204.0.0", + "prefixLen": 24, + "network": "10.204.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf1": { + "vrfName": "vrf1", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.201.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf3": { + "vrfName": "vrf3", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf4": { + "vrfName": "vrf4", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf3", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r1_vrf1.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r1_vrf1.json new file mode 100644 index 0000000..488dc4a --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r1_vrf1.json @@ -0,0 +1,190 @@ +{ + "default": { + "vrfName": "default", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.204.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.204.0.0", + "prefixLen": 24, + "network": "10.204.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf1": { + "vrfName": "vrf1", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.201.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf3": { + "vrfName": "vrf3", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf4": { + "vrfName": "vrf4", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf3", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf2": { + "vrfName": "vrf2", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf2.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf2.json new file mode 100644 index 0000000..b751756 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf2.json @@ -0,0 +1,188 @@ +{ + "default": { + "vrfName": "default", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.204.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.204.0.0", + "prefixLen": 24, + "network": "10.204.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf1": { + "vrfName": "vrf1", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.201.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf3": { + "vrfName": "vrf3", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf4": { + "vrfName": "vrf4", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf3", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf2": { + "vrfName": "vrf2", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.202.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.202.0.0", + "prefixLen": 24, + "network": "10.202.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf3.json b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf3.json new file mode 100644 index 0000000..49d4066 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/ipv4_vrf_all_routes_plus_r2_vrf3.json @@ -0,0 +1,188 @@ +{ + "default": { + "vrfName": "default", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.204.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.204.0.0", + "prefixLen": 24, + "network": "10.204.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf1": { + "vrfName": "vrf1", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.201.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf3": { + "vrfName": "vrf3", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf4": { + "vrfName": "vrf4", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf3", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + }, + "vrf2": { + "vrfName": "vrf2", + "routerId": "192.0.2.1", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "10.203.0.0/24": [ + { + "pathFrom": "external", + "prefix": "10.203.0.0", + "prefixLen": 24, + "network": "10.203.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "default", + "nexthops": [ + { + "ip": "10.125.0.2", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r1/zebra.conf b/tests/topotests/bgp_vpnv4_noretain/r1/zebra.conf new file mode 100644 index 0000000..f99cfaf --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r1/zebra.conf @@ -0,0 +1,16 @@ +log stdout +interface lo + ip address 192.0.2.1/32 +! +interface r1-gre0 + ip address 192.168.0.1/24 +! +interface r1-eth0 + ip address 10.125.0.1/24 +! +interface r1-eth1 vrf vrf1 + ip address 10.101.0.1/24 +! +interface r1-eth3 vrf vrf3 + ip address 10.103.0.1/24 +! \ No newline at end of file diff --git a/tests/topotests/bgp_vpnv4_noretain/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_noretain/r2/bgpd.conf new file mode 100644 index 0000000..729daef --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r2/bgpd.conf @@ -0,0 +1,54 @@ +router bgp 65500 + bgp router-id 192.0.2.2 + neighbor 10.125.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 10.125.0.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 10.125.0.1 activate + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 201 + rd vpn export 192.0.2.2:1 + rt vpn import 192.0.2.1:100 192.0.2.2:100 192.0.2.2:200 + rt vpn export 192.0.2.2:100 + export vpn + import vpn + exit-address-family +! +router bgp 65500 vrf vrf2 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 202 + rd vpn export 192.0.2.2:2 + rt vpn import 192.0.2.1:100 192.0.2.2:100 192.0.2.2:200 + rt vpn export 192.0.2.2:200 + export vpn + import vpn + exit-address-family +! +router bgp 65500 vrf vrf3 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 203 + rd vpn export 192.0.2.2:3 + rt vpn export 192.0.2.2:300 + export vpn + exit-address-family +! +router bgp 65500 vrf vrf4 + bgp router-id 192.0.2.2 + address-family ipv4 unicast + redistribute connected + label vpn export 204 + rd vpn export 192.0.2.2:4 + rt vpn export 192.0.2.2:400 + export vpn + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_routes_all.json b/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_routes_all.json new file mode 100644 index 0000000..d8b8e88 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_routes_all.json @@ -0,0 +1,177 @@ +{ + "vrfId": 0, + "vrfName": "default", + "routerId":"192.0.2.2", + "defaultLocPrf": 100, + "localAS": 65500, + "routes": { + "routeDistinguishers": { + "192.0.2.1:1": { + "10.101.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "internal", + "prefix": "10.101.0.0", + "prefixLen": 24, + "network": "10.101.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "10.125.0.1", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "10.125.0.1", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "192.0.2.1:3": { + "10.103.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "internal", + "prefix": "10.103.0.0", + "prefixLen": 24, + "network": "10.103.0.0/24", + "metric": 0, + "locPrf": 100, + "weight": 0, + "peerId": "10.125.0.1", + "path": "", + "origin": "incomplete", + "nexthops": [ + { + "ip": "10.125.0.1", + "hostname": "r1", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "192.0.2.2:1": { + "10.201.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "network": "10.201.0.0/24", + "prefixLen": 24, + "prefix": "10.201.0.0", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r2", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "192.0.2.2:2": { + "10.202.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "network": "10.202.0.0/24", + "prefixLen": 24, + "prefix": "10.202.0.0", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf2", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r2", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "192.0.2.2:3": { + "10.203.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.203.0.0", + "prefixLen": 24, + "network": "10.203.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf3", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r2", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "192.0.2.2:4": { + "10.204.0.0/24": [ + { + "valid": true, + "bestpath": true, + "selectionReason": "First path received", + "pathFrom": "external", + "prefix": "10.204.0.0", + "prefixLen": 24, + "network": "10.204.0.0/24", + "metric": 0, + "weight": 32768, + "peerId": "(unspec)", + "path": "", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf4", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "r2", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_summary.json b/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_summary.json new file mode 100644 index 0000000..a4408f1 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r2/ipv4_vpn_summary.json @@ -0,0 +1,17 @@ +{ + "routerId":"192.0.2.2", + "as":65500, + "vrfId":0, + "vrfName":"default", + "peerCount":1, + "peers":{ + "10.125.0.1":{ + "remoteAs":65500, + "localAs":65500, + "version":4, + "state":"Established", + "peerState":"OK" + } + }, + "totalPeers":1 +} diff --git a/tests/topotests/bgp_vpnv4_noretain/r2/zebra.conf b/tests/topotests/bgp_vpnv4_noretain/r2/zebra.conf new file mode 100644 index 0000000..f19ad9d --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/r2/zebra.conf @@ -0,0 +1,22 @@ +log stdout +interface lo + ip address 192.0.2.2/32 +! +interface r2-gre0 + ip address 192.168.0.2/24 +! +interface r2-eth0 + ip address 10.125.0.2/24 +! +interface r2-eth1 vrf vrf1 + ip address 10.201.0.2/24 +! +interface r2-eth2 vrf vrf2 + ip address 10.202.0.2/24 +! +interface r2-eth3 vrf vrf3 + ip address 10.203.0.1/24 +! +interface r2-eth4 vrf vrf4 + ip address 10.204.0.1/24 +! diff --git a/tests/topotests/bgp_vpnv4_noretain/test_bgp_vpnv4_noretain.py b/tests/topotests/bgp_vpnv4_noretain/test_bgp_vpnv4_noretain.py new file mode 100644 index 0000000..037dd40 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_noretain/test_bgp_vpnv4_noretain.py @@ -0,0 +1,567 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_vpnv4_noretain.py +# Part of NetDEF Topology Tests +# +# Copyright 2022 6WIND S.A. +# + +""" + test_bgp_vpnv4_noretain.py: Do not keep the VPNvx entries when no + VRF matches incoming VPNVx entries +""" + +import os +import sys +import json +from functools import partial +from copy import deepcopy +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s6") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s7") + switch.add_link(tgen.gears["r1"]) + + switch = tgen.add_switch("s8") + switch.add_link(tgen.gears["r2"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "modprobe mpls_router", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link add vrf1 type vrf table 10", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/vrf1/input", + "ip link add vrf2 type vrf table 20", + "ip link set dev vrf2 up", + "ip link set dev {0}-eth2 master vrf2", + "echo 1 > /proc/sys/net/mpls/conf/vrf2/input", + "ip link add vrf3 type vrf table 30", + "ip link set dev vrf3 up", + "ip link set dev {0}-eth3 master vrf3", + "echo 1 > /proc/sys/net/mpls/conf/vrf3/input", + "ip link add vrf4 type vrf table 40", + "ip link set dev vrf4 up", + "ip link set dev {0}-eth4 master vrf4", + "echo 1 > /proc/sys/net/mpls/conf/vrf4/input", + ] + + for cmd in cmds_list: + input = cmd.format("r1", "1", "2") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1", "1", "2")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2", "2", "1") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2", "2", "1")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def router_json_cmp_exact_filter(router, cmd, expected): + output = router.vtysh_cmd(cmd) + logger.info("{}: {}\n{}".format(router.name, cmd, output)) + + json_output = json.loads(output) + + # filter out tableVersion, version and nhVrfID + json_output.pop("tableVersion") + for rd, data in json_output["routes"]["routeDistinguishers"].items(): + for prefix, attrs in data.items(): + for attr in attrs: + if "nhVrfId" in attr: + attr.pop("nhVrfId") + if "version" in attr: + attr.pop("version") + + # filter out RD with no data (e.g. "444:3": {}) + json_tmp = deepcopy(json_output) + for rd, data in json_tmp["routes"]["routeDistinguishers"].items(): + if len(data.keys()) == 0: + json_output["routes"]["routeDistinguishers"].pop(rd) + + return topotest.json_cmp(json_output, expected, exact=True) + + +def router_vrf_json_cmp_exact_filter(router, cmd, expected): + output = router.vtysh_cmd(cmd) + logger.info("{}: {}\n{}".format(router.name, cmd, output)) + + json_output = json.loads(output) + + # filter out tableVersion, version, nhVrfId and vrfId + for vrf, data in json_output.items(): + if "vrfId" in data: + data.pop("vrfId") + if "tableVersion" in data: + data.pop("tableVersion") + if "routes" not in data: + continue + for route, attrs in data["routes"].items(): + for attr in attrs: + if "nhVrfId" in attr: + attr.pop("nhVrfId") + if "version" in attr: + attr.pop("version") + + # filter out VRF with no routes + json_tmp = deepcopy(json_output) + for vrf, data in json_tmp.items(): + if "routes" not in data or len(data["routes"].keys()) == 0: + json_output.pop(vrf) + + return topotest.json_cmp(json_output, expected, exact=True) + + +def check_show_bgp_ipv4_vpn(rname, json_file): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears[rname] + + logger.info("Checking VPNv4 routes for convergence on {}".format(rname)) + + json_file = "{}/{}/{}".format(CWD, router.name, json_file) + expected = json.loads(open(json_file).read()) + test_func = partial( + router_json_cmp_exact_filter, + router, + "show bgp ipv4 vpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def check_show_bgp_vrf_ipv4(rname, json_file): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears[rname] + + logger.info("Checking VRF IPv4 routes for convergence on {}".format(rname)) + + json_file = "{}/{}/{}".format(CWD, router.name, json_file) + expected = json.loads(open(json_file).read()) + test_func = partial( + router_vrf_json_cmp_exact_filter, + router, + "show bgp vrf all ipv4 unicast json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_protocols_convergence_step0(): + """ + Assert that all protocols have converged + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # check that r2 peerings are ok + logger.info("Checking BGP ipv4 vpn summary for r2") + router = tgen.gears["r2"] + json_file = "{}/{}/ipv4_vpn_summary.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 vpn summary json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_bgp_no_retain_step1(): + """ + Check bgp no retain route-target all on r1 + """ + + rname = "r1" + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_retain_step2(): + """ + Apply and check bgp retain route-target all on r1 + """ + rname = "r1" + cfg = """ +configure +router bgp 65500 + address-family ipv4 vpn + bgp retain route-target all +""" + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_all.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_no_retain_step3(): + """ + Apply and check no bgp retain route-target all on r1 + """ + rname = "r1" + + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure\nrouter bgp 65500\naddress-family ipv4 vpn\nno bgp retain route-target all\n" + ) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_no_retain_add_vrf2_step4(): + """ + Add vrf2 on r1 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + bgp router-id 192.0.2.1 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 192.0.2.1:200 + rt vpn import 192.0.2.2:200 + import vpn + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init_plus_r2_vrf2.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_plus_r2_vrf2.json") + + +def test_bgp_no_retain_unimport_vrf2_step5(): + """ + Unimport to vrf2 on r1 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + no import vpn + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_no_retain_import_vrf2_step6(): + """ + Re-import to vrf2 on r1 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + import vpn + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init_plus_r2_vrf2.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_plus_r2_vrf2.json") + + +def test_bgp_no_retain_import_vrf1_step7(): + """ + Import r1 vrf1 into r1 vrf2 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + rt vpn import 192.0.2.1:100 + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_plus_r1_vrf1.json") + + +def test_bgp_no_retain_import_vrf3_step8(): + """ + Import r2 vrf3 into r1 vrf2 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + rt vpn import 192.0.2.2:300 + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init_plus_r2_vrf3.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_plus_r2_vrf3.json") + + +def test_bgp_no_retain_unimport_vrf3_step9(): + """ + Un-import r2 vrf3 into r1 vrf2 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + no rt vpn import 192.0.2.2:300 + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_no_retain_import_vrf3_step10(): + """ + Import r2 vrf3 into r1 vrf2 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + rt vpn import 192.0.2.2:300 + exit-address-family +! +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init_plus_r2_vrf3.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_plus_r2_vrf3.json") + + +def test_bgp_no_retain_remove_vrf2_step11(): + """ + Remove BGP vrf2 on r1 and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +no router bgp 65500 vrf vrf2 +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_no_retain_init.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_bgp_retain_step12(): + """ + Configure retain and check bgp tables + """ + + rname = "r1" + cfg = """ +configure +router bgp 65500 + address-family ipv4 vpn + bgp retain route-target all +""" + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears[rname] + router.vtysh_cmd(cfg) + + check_show_bgp_ipv4_vpn(rname, "ipv4_vpn_routes_all.json") + check_show_bgp_ipv4_vpn("r2", "ipv4_vpn_routes_all.json") + + check_show_bgp_vrf_ipv4(rname, "ipv4_vrf_all_routes_init.json") + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/__init__.py b/tests/topotests/bgp_vpnv4_per_nexthop_label/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json new file mode 100644 index 0000000..31a1f3d --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgp_ipv4_routes_vrf1.json @@ -0,0 +1,143 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "192.168.0.2", + "afi": "ipv4", + "used": true + } + ] + } + + ], + "172.31.0.11/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.11", + "prefixLen":32, + "network":"172.31.0.11/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.12/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.12", + "prefixLen":32, + "network":"172.31.0.12/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.12", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.13/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.13", + "prefixLen":32, + "network":"172.31.0.13/32", + "peerId":"192.168.255.13", + "nexthops":[ + { + "ip":"192.168.255.13", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.14/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.14", + "prefixLen":32, + "network":"172.31.0.14/32", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192.0.2.14", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.15/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.15", + "prefixLen":32, + "network":"172.31.0.15/32", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192.0.2.12", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.20/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.20", + "prefixLen":32, + "network":"172.31.0.20/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ], + "172.31.0.111/32": [ + { + "valid":true, + "bestpath":true, + "prefix":"172.31.0.111", + "prefixLen":32, + "network":"172.31.0.111/32", + "peerId":"192.0.2.100", + "nexthops":[ + { + "ip":"192.0.2.11", + "afi":"ipv4", + "used":true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf new file mode 100644 index 0000000..35fb2ec --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/bgpd.conf @@ -0,0 +1,30 @@ +router bgp 65500 + bgp router-id 192.168.0.1 + no bgp ebgp-requires-policy + neighbor 192.168.0.2 remote-as 65501 + address-family ipv4 unicast + no neighbor 192.168.0.2 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.2 activate + neighbor 192.168.0.2 soft-reconfiguration inbound + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.168.0.1 + neighbor 192.0.2.100 remote-as 65500 + neighbor 192.168.255.13 remote-as 65500 + address-family ipv4 unicast + redistribute connected + redistribute static + label vpn export allocation-mode per-nexthop + label vpn export auto + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r1-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json new file mode 100644 index 0000000..da7d281 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/ipv4_routes.json @@ -0,0 +1,50 @@ +{ + "10.200.0.0/24": [ + { + "prefix": "10.200.0.0/24", + "prefixLen": 24, + "protocol": "bgp", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "nexthops": [ + { + "flags": 3, + "fib": true, + "ip": "10.125.0.2", + "afi": "ipv4", + "interfaceName": "r1-eth0", + "vrf": "default", + "active": true, + "labels":[ + 102 + ] + } + ] + } + ], + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0/24", + "prefixLen": 24, + "protocol": "connected", + "vrfName": "vrf1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops":[ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r1-eth1", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf new file mode 100644 index 0000000..2618595 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r1/zebra.conf @@ -0,0 +1,18 @@ +log stdout +debug zebra nht +!debug zebra kernel msgdump recv +!debug zebra dplane detailed +!debug zebra packet recv +interface r1-eth1 vrf vrf1 + ip address 192.0.2.1/24 +! +interface r1-eth2 vrf vrf1 + ip address 192.168.255.1/24 +! +interface r1-eth0 + ip address 192.168.0.1/24 +! +vrf vrf1 + ip route 172.31.0.14/32 192.0.2.14 + ip route 172.31.0.15/32 192.0.2.12 +exit-vrf diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf new file mode 100644 index 0000000..5da9151 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/bgpd.conf @@ -0,0 +1,11 @@ +router bgp 65500 + bgp router-id 192.0.2.11 + no bgp network import-check + neighbor 192.0.2.100 remote-as 65500 + address-family ipv4 unicast + network 172.31.0.11/32 + network 172.31.0.111/32 + network 172.31.0.20/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf new file mode 100644 index 0000000..a080757 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r11/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r11-eth0 + ip address 192.0.2.11/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf new file mode 100644 index 0000000..d3889f5 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65500 + bgp router-id 192.0.2.12 + no bgp network import-check + neighbor 192.0.2.100 remote-as 65500 + address-family ipv4 unicast + network 172.31.0.12/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf new file mode 100644 index 0000000..9ce3aba --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r12/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r12-eth0 + ip address 192.0.2.12/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf new file mode 100644 index 0000000..21dbb58 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/bgpd.conf @@ -0,0 +1,9 @@ +router bgp 65500 + bgp router-id 192.168.255.13 + no bgp network import-check + address-family ipv4 unicast + neighbor 192.168.255.1 remote-as 65500 + network 172.31.0.13/32 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf new file mode 100644 index 0000000..4d78b5f --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r13/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r13-eth0 + ip address 192.168.255.13/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json new file mode 100644 index 0000000..3407925 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_ipv4_routes.json @@ -0,0 +1,38 @@ +{ + "vrfName": "vrf1", + "localAS": 65501, + "routes": + { + "10.201.0.0/24": [ + { + "prefix": "10.201.0.0", + "prefixLen": 24, + "network": "10.201.0.0\/24", + "nhVrfName": "default", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json new file mode 100644 index 0000000..46f4a18 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgp_vpnv4_routes.json @@ -0,0 +1,187 @@ +{ + "vrfName": "default", + "localAS": 65501, + "routes": + { + "routeDistinguishers": + { + "444:1": + { + "172.31.0.11/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.11", + "prefixLen": 32, + "network": "172.31.0.11\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.12/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.12", + "prefixLen": 32, + "network": "172.31.0.12\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.13/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.13", + "prefixLen": 32, + "network": "172.31.0.13\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.14/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.14", + "prefixLen": 32, + "network": "172.31.0.14\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.15/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.15", + "prefixLen": 32, + "network": "172.31.0.15\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.20/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.20", + "prefixLen": 32, + "network": "172.31.0.20\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "172.31.0.111/32": [ + { + "valid": true, + "bestpath": true, + "prefix": "172.31.0.111", + "prefixLen": 32, + "network": "172.31.0.111\/32", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.0.2.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.0.2.0", + "prefixLen": 24, + "network": "192.0.2.0\/24", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ], + "192.168.255.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "192.168.255.0", + "prefixLen": 24, + "network": "192.168.255.0\/24", + "peerId": "192.168.0.1", + "nexthops": [ + { + "ip": "192.168.0.1", + "afi": "ipv4", + "used": true + } + ] + } + ] + }, + "444:2": + { + "10.200.0.0/24": [ + { + "valid": true, + "bestpath": true, + "prefix": "10.200.0.0", + "prefixLen": 24, + "network": "10.200.0.0\/24", + "peerId": "(unspec)", + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "0.0.0.0", + "afi": "ipv4", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf new file mode 100644 index 0000000..5fb7902 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65501 + bgp router-id 192.168.0.2 + no bgp ebgp-requires-policy + neighbor 192.168.0.1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192.168.0.1 activate + exit-address-family + address-family ipv4 vpn + neighbor 192.168.0.1 activate + exit-address-family +! +router bgp 65501 vrf vrf1 + bgp router-id 192.168.0.2 + address-family ipv4 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r2-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf new file mode 100644 index 0000000..b7283a3 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/r2/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r2-eth1 vrf vrf1 + ip address 10.200.0.2/24 +! +interface r2-eth0 + ip address 192.168.0.2/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf new file mode 100644 index 0000000..ff32314 --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65500 + bgp router-id 100.100.100.100 + no bgp network import-check + neighbor 192.0.2.1 remote-as 65500 + neighbor 192.0.2.11 remote-as 65500 + neighbor 192.0.2.12 remote-as 65500 + address-family ipv4 unicast + neighbor 192.0.2.1 route-reflector-client + neighbor 192.0.2.11 route-reflector-client + neighbor 192.0.2.12 route-reflector-client + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf new file mode 100644 index 0000000..315c22a --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/rr/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface rr-eth0 + ip address 192.0.2.100/24 +! diff --git a/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py b/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py new file mode 100644 index 0000000..d4c355a --- /dev/null +++ b/tests/topotests/bgp_vpnv4_per_nexthop_label/test_bgp_vpnv4_per_nexthop_label.py @@ -0,0 +1,822 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_vpnv4_per_nexthop_label.py +# +# Copyright 2023 6WIND S.A. +# + +""" + test_bgp_vpnv4_per_nexthop_label.py: Test the FRR BGP daemon using EBGP peering + Let us exchange VPNv4 updates between both devices + Updates from r1 will originate from the same RD, but will have separate + label values. + + +----------+ + | r11 | + |192.0.2.11+---+ + | | | +----+--------+ +----------+ + +----------+ | 192.0.2.1 |vrf | r1 |192.168.0.0/24| r2 | + +-------------------+ | 1+--------------+ | + +----------+ | |VRF1|AS65500 | | AS65501 | + | r12 | | +-------------+ | VPNV4| |VPNV4 | + |192.0.2.12+---+ |192.168.255.1+-+--+--------+ +----------+ + | | | + +----------+ | + | + +----------+ | + | r13 | | + |192.168. +---------+ + | 255.13 | + +----------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +pytestmark = [pytest.mark.bgpd] + +PREFIXES_R11 = ["172.31.0.11/32", "172.31.0.20/32", "172.31.0.111/32"] +PREFIXES_R12 = ["172.31.0.12/32", "172.31.0.15/32"] +PREFIXES_R13 = ["172.31.0.13/32"] +PREFIXES_REDIST = ["172.31.0.14/32"] +PREFIXES_CONNECTED = ["192.168.255.0/24", "192.0.2.0/24"] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r11") + tgen.add_router("r12") + tgen.add_router("r13") + tgen.add_router("r14") + tgen.add_router("rr") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r11"]) + switch.add_link(tgen.gears["r12"]) + switch.add_link(tgen.gears["rr"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r13"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r14"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + cmds_list_plus = [ + "ip link set dev {0}-eth2 master vrf1", + ] + + for cmd in cmds_list: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list_plus: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def check_bgp_vpnv4_prefix_presence(router, prefix): + "Check the presence of a prefix" + tgen = get_topogen() + + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + if not dump: + return "{}, prefix ipv4 vpn {} is not installed yet".format(router.name, prefix) + return None + + +def bgp_vpnv4_table_check(router, group, label_list=None, label_value_expected=None): + """ + Dump and check that vpnv4 entries have the same MPLS label value + * 'router': the router to check + * 'group': the list of prefixes to check. a single label value for the group has to be found + * 'label_list': check that the label values are not present in the vpnv4 entries + * that list is updated with the present label value + * 'label_value_expected': check that the mpls label read is the same as that value + """ + + stored_label_inited = False + for prefix in group: + test_func = functools.partial(check_bgp_vpnv4_prefix_presence, router, prefix) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, prefix ipv4 vpn {} is not installed yet".format( + router.name, prefix + ) + + dump = router.vtysh_cmd("show bgp ipv4 vpn {} json".format(prefix), isjson=True) + assert dump, "{0}, {1}, route distinguisher not present".format( + router.name, prefix + ) + for rd, pathes in dump.items(): + for path in pathes["paths"]: + assert ( + "remoteLabel" in path.keys() + ), "{0}, {1}, remoteLabel not present".format(router.name, prefix) + logger.info( + "{0}, {1}, label value is {2}".format( + router.name, prefix, path["remoteLabel"] + ) + ) + if stored_label_inited: + assert ( + path["remoteLabel"] == stored_label + ), "{0}, {1}, label value not expected one (expected {2}, observed {3}".format( + router.name, prefix, stored_label, path["remoteLabel"] + ) + else: + stored_label = path["remoteLabel"] + stored_label_inited = True + if label_list is not None: + assert ( + stored_label not in label_list + ), "{0}, {1}, label already detected in a previous prefix".format( + router.name, prefix + ) + label_list.add(stored_label) + + if label_value_expected: + assert ( + path["remoteLabel"] == label_value_expected + ), "{0}, {1}, label value not expected (expected {2}, observed {3}".format( + router.name, prefix, label_value_expected, path["remoteLabel"] + ) + + +def bgp_vpnv4_table_check_all(router, label_list=None, same=False): + """ + Dump and check that vpnv4 entries are correctly configured with specific label values + * 'router': the router to check + * 'label_list': check that the label values are not present in the vpnv4 entries + * that list is updated with the present label value found. + * 'same': by default, set to False. Addresses groups are classified by addresses. + * if set to True, all entries of all groups should have a unique label value + """ + if same: + bgp_vpnv4_table_check( + router, + group=PREFIXES_R11 + + PREFIXES_R12 + + PREFIXES_R13 + + PREFIXES_REDIST + + PREFIXES_CONNECTED, + label_list=label_list, + ) + else: + for group in ( + PREFIXES_R11, + PREFIXES_R12, + PREFIXES_R13, + PREFIXES_REDIST, + PREFIXES_CONNECTED, + ): + bgp_vpnv4_table_check(router, group=group, label_list=label_list) + + +def check_show_mpls_table(router, blacklist=None, label_list=None, whitelist=None): + nexthop_list = [] + if blacklist: + nexthop_list.append(blacklist) + + dump = router.vtysh_cmd("show mpls table json", isjson=True) + for in_label, label_info in dump.items(): + if label_list is not None: + label_list.add(in_label) + for nh in label_info["nexthops"]: + if "installed" not in nh.keys(): + return "{} {} is not installed yet on {}".format( + in_label, label_info, router.name + ) + if nh["installed"] != True or nh["type"] != "BGP": + return "{}, show mpls table, nexthop is not installed".format( + router.name + ) + if "nexthop" in nh.keys(): + if nh["nexthop"] in nexthop_list: + return "{}, show mpls table, duplicated or blacklisted nexthop address".format( + router.name + ) + nexthop_list.append(nh["nexthop"]) + elif "interface" in nh.keys(): + if nh["interface"] in nexthop_list: + return "{}, show mpls table, duplicated or blacklisted nexthop interface".format( + router.name + ) + nexthop_list.append(nh["interface"]) + else: + return "{}, show mpls table, entry with neither nexthop nor interface".format( + router.name + ) + + if whitelist: + for entry in whitelist: + if entry not in nexthop_list: + return "{}, show mpls table, entry with nexthop {} not present in nexthop list".format( + router.name, entry + ) + return None + + +def mpls_table_check(router, blacklist=None, label_list=None, whitelist=None): + """ + Dump and check 'show mpls table json' output. An assert is triggered in case test fails + * 'router': the router to check + * 'blacklist': the list of nexthops (IP or interface) that should not be on output + * 'label_list': the list of labels that should be in inLabel value + * 'whitelist': the list of nexthops (IP or interface) that should be on output + """ + logger.info("Checking MPLS labels on {}".format(router.name)) + # Check r2 removed 172.31.0.30 vpnv4 update + test_func = functools.partial( + check_show_mpls_table, router, blacklist, label_list, whitelist + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, MPLS labels check fail: {}".format(router.name, result) + + +def check_show_bgp_vpn_prefix_not_found(router, ipversion, prefix, rd, label=None): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + if label: + expected = {rd: {"prefix": prefix, "paths": [{"remoteLabel": label}]}} + else: + expected = {rd: {"prefix": prefix}} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def check_show_bgp_vpn_prefix_found(router, ipversion, prefix, rd): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + expected = {rd: {"prefix": prefix}} + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_found(router, inlabel, interface): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = { + "inLabel": inlabel, + "installed": True, + "nexthops": [{"interface": interface}], + } + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_not_found(router, inlabel): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = {"inlabel": inlabel, "installed": True} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def mpls_entry_get_interface(router, label): + """ + Assert that the label is in MPLS table + Assert an outgoing interface is programmed + return the outgoing interface + """ + outgoing_interface = None + + logger.info("Checking MPLS labels on {}".format(router.name)) + dump = router.vtysh_cmd("show mpls table {} json".format(label), isjson=True) + assert dump, "{0}, label {1} not present".format(router.name, label) + + for nh in dump["nexthops"]: + assert ( + "interface" in nh.keys() + ), "{}, show mpls table, nexthop interface not present for MPLS entry {}".format( + router.name, label + ) + + outgoing_interface = nh["interface"] + + return outgoing_interface + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check BGP IPv4 routing tables on VRF1 of r1 + logger.info("Checking BGP IPv4 routes for convergence on r1 VRF1") + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_ipv4_routes_vrf1.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv4 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + logger.info("Checking BGP VPNv4 routes for convergence on r2") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_vpnv4_routes.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv4 vpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP labels received on r2 + logger.info("Checking BGP VPNv4 labels on r2") + label_list = set() + bgp_vpnv4_table_check_all(tgen.gears["r2"], label_list) + + # Check MPLS labels received on r1 + mpls_table_check(tgen.gears["r1"], label_list) + + +def test_flapping_bgp_vrf_down(): + """ + Turn down a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Unpeering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nno neighbor 192.0.2.100\n", + isjson=False, + ) + + def _bgp_prefix_not_found(router, vrf, ipversion, prefix): + output = json.loads( + router.vtysh_cmd( + "show bgp vrf {} {} {} json".format(vrf, ipversion, prefix) + ) + ) + expected = {"prefix": prefix} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + # Check prefix from r11 is not present + test_func = functools.partial( + _bgp_prefix_not_found, tgen.gears["r1"], "vrf1", "ipv4", "172.31.0.11/32" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, prefix 172.31.0.11/32 from r11 did not disappear. r11 still connected to rr ?" + + # Check BGP updated received on r2 are not from r11 + logger.info("Checking BGP VPNv4 labels on r2") + for entry in PREFIXES_R11: + dump = tgen.gears["r2"].vtysh_cmd( + "show bgp ipv4 vpn {} json".format(entry), isjson=True + ) + for rd in dump: + assert False, "r2, {}, route distinguisher {} present".format(entry, rd) + + mpls_table_check(tgen.gears["r1"], blacklist=["192.0.2.11"]) + + +def test_flapping_bgp_vrf_up(): + """ + Turn up a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Peering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nneighbor 192.0.2.100 remote-as 65500\n", + isjson=False, + ) + + # Check r2 gets prefix 172.31.0.11/128 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.11/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, prefix 172.31.0.11/32 from r11 not present. r11 still disconnected from rr ?" + bgp_vpnv4_table_check_all(tgen.gears["r2"]) + + +def test_recursive_route(): + """ + Test static recursive route redistributed over BGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nip route 172.31.0.30/32 172.31.0.20\n", + isjson=False, + ) + logger.info("Checking BGP VPNv4 labels on r2") + + # Check r2 received vpnv4 update with 172.31.0.30 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.30/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.30 not found" + + bgp_vpnv4_table_check(tgen.gears["r2"], group=PREFIXES_R11 + ["172.31.0.30/32"]) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + logger.info("Dumping nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 nexthop detail", isjson=False) + + logger.info("Disabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nno ip route 172.31.0.30/32 172.31.0.20\n", + isjson=False, + ) + logger.info("Checking BGP VPNv4 labels on r2") + + # Check r2 removed 172.31.0.30 vpnv4 update + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.30/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.30 still present" + + +def test_prefix_changes_interface(): + """ + Test BGP update for a given prefix learnt on different interface + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling a 172.31.0.50/32 prefix for r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nnetwork 172.31.0.50/32", + isjson=False, + ) + + # Check r2 received vpnv4 update with 172.31.0.50 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.50 not found" + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + label_list = set() + bgp_vpnv4_table_check( + tgen.gears["r2"], + group=["172.31.0.11/32", "172.31.0.111/32", "172.31.0.50/32"], + label_list=label_list, + ) + + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r11 found" + + oldlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(oldlabel)) + old_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], oldlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + oldlabel, old_outgoing_interface + ) + ) + + logger.info("Moving the 172.31.0.50/32 prefix from r11 to r13") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nno network 172.31.0.50/32", + isjson=False, + ) + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nnetwork 172.31.0.50/32", + isjson=False, + ) + + # Check r2 removed 172.31.0.50 vpnv4 update with old label + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + label=oldlabel, + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, vpnv4 update 172.31.0.50 with old label {0} still present".format(oldlabel) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + # Check r2 received new 172.31.0.50 vpnv4 update + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv4", + "172.31.0.50/32", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv4 update 172.31.0.50 not found" + + label_list = set() + bgp_vpnv4_table_check( + tgen.gears["r2"], + group=PREFIXES_R13 + ["172.31.0.50/32"], + label_list=label_list, + ) + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r13 found" + + newlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(newlabel)) + new_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], newlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + newlabel, new_outgoing_interface + ) + ) + if old_outgoing_interface == new_outgoing_interface: + assert 0, "r1, outgoing interface did not change whereas BGP update moved" + + logger.info("Restoring state by removing the 172.31.0.50/32 prefix from r13") + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv4 unicast\nno network 172.31.0.50/32", + isjson=False, + ) + + +def test_changing_default_label_value(): + """ + Change the MPLS default value + Check that r1 VPNv4 entries have the 222 label value + Check that MPLS entry with old label value is no more present + Check that MPLS entry for local traffic has inLabel set to 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + # counting the number of labels used in the VPNv4 table + label_list = set() + logger.info("r1, vpnv4 table, check the number of labels used before modification") + bgp_vpnv4_table_check_all(router, label_list) + old_len = len(label_list) + assert ( + old_len != 1 + ), "r1, number of labels used should be greater than 1, oberved {} ".format(old_len) + + logger.info("r1, vrf1, changing the default MPLS label value to export to 222") + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nlabel vpn export 222\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 222 has vrf1 interface" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_found, router, 222, "vrf1" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 222 not found" + + # check label repartition is ok + logger.info("r1, vpnv4 table, check the number of labels used after modification") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list) + new_len = len(label_list) + assert ( + old_len == new_len + ), "r1, number of labels after modification differ from previous, observed {}, expected {} ".format( + new_len, old_len + ) + + logger.info( + "r1, vpnv4 table, check that prefixes that were using the vrf label have refreshed the label value to 222" + ) + bgp_vpnv4_table_check( + router, group=["192.168.255.0/24", "192.0.2.0/24"], label_value_expected=222 + ) + + +def test_unconfigure_allocation_mode_nexthop(): + """ + Test unconfiguring allocation mode per nexthop + Check that show mpls table has no entry with label 17 (previously used) + Check that all VPN updates on r1 should have label value moved to 222 + Check that show mpls table will only have 222 label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Unconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nno label vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is not present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv4 routes from r1 + logger.info("Checking vpnv4 routes on r1") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list=label_list, same=True) + assert len(label_list) == 1, "r1, multiple Label values found for vpnv4 updates" + + new_label = label_list.pop() + assert ( + new_label == 222 + ), "r1, wrong label value in VPNv4 table, expected 222, observed {}".format( + new_label + ) + + # Check mpls table with 222 value + logger.info("Checking MPLS values on show mpls table of r1") + label_list = set() + label_list.add(222) + mpls_table_check(router, label_list=label_list) + + +def test_reconfigure_allocation_mode_nexthop(): + """ + Test re-configuring allocation mode per nexthop + Check that show mpls table has no entry with label 17 + Check that all VPN updates on r1 should have multiple label values and not only 222 + Check that show mpls table will have multiple label values and not only 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Reconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv4 unicast\nlabel vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check that show mpls table has no entry with label 17 + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv4 routes from r1 + logger.info("Checking vpnv4 routes on r1") + label_list = set() + bgp_vpnv4_table_check_all(router, label_list=label_list) + assert len(label_list) != 1, "r1, only 1 label values found for vpnv4 updates" + + # Check mpls table with all values + logger.info("Checking MPLS values on show mpls table of r1") + mpls_table_check(router, label_list=label_list) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/__init__.py b/tests/topotests/bgp_vpnv6_per_nexthop_label/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgp_ipv6_routes_vrf1.json b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgp_ipv6_routes_vrf1.json new file mode 100644 index 0000000..39ba7dd --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgp_ipv6_routes_vrf1.json @@ -0,0 +1,186 @@ +{ + "vrfName": "vrf1", + "localAS": 65500, + "routes": + { + "10:200::/64": [ + { + "valid": true, + "bestpath": true, + "prefix": "10:200::", + "prefixLen": 64, + "network": "10:200::/64", + "nexthops": [ + { + "ip": "192:168::2", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::11/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::11", + "prefixLen":128, + "network":"172:31::11/128", + "peerId":"192:2::100", + "nexthops":[ + { + "ip":"192:2::11", + "afi":"ipv6", + "scope":"global" + } + ] + } + ], + "172:31::12/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::12", + "prefixLen":128, + "network":"172:31::12/128", + "peerId":"192:2::100", + "nexthops":[ + { + "ip":"192:2::12", + "afi":"ipv6", + "scope":"global", + "used":true + }, + { + "scope": "link-local" + } + ] + } + ], + "172:31::13/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::13", + "prefixLen":128, + "network":"172:31::13/128", + "peerId":"192:168::255:13", + "nexthops":[ + { + "ip":"192:168::255:13", + "afi":"ipv6", + "scope": "global", + "used":true + }, + { + "scope": "link-local" + } + ] + } + ], + "172:31::14/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::14", + "prefixLen":128, + "network":"172:31::14/128", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192:2::14", + "afi":"ipv6", + "used":true + } + ] + } + ], + "172:31::15/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::15", + "prefixLen":128, + "network":"172:31::15/128", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"192:2::12", + "afi":"ipv6", + "used":true + } + ] + } + ], + "172:31::20/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::20", + "prefixLen":128, + "network":"172:31::20/128", + "peerId":"192:2::100", + "nexthops":[ + { + "ip":"192:2::11", + "afi":"ipv6", + "scope":"global", + "used":true + } + ] + } + ], + "172:31::111/128": [ + { + "valid":true, + "bestpath":true, + "prefix":"172:31::111", + "prefixLen":128, + "network":"172:31::111/128", + "peerId":"192:2::100", + "nexthops":[ + { + "ip":"192:2::11", + "afi":"ipv6", + "scope":"global", + "used":true + } + ] + } + ], + "192:2::/64": [ + { + "valid":true, + "bestpath":true, + "prefix":"192:2::", + "prefixLen":64, + "network":"192:2::/64", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"::", + "afi":"ipv6", + "used":true + } + ] + } + ], + "192:168::255:0/112": [ + { + "valid":true, + "bestpath":true, + "prefix":"192:168::255:0", + "prefixLen":112, + "network":"192:168::255:0/112", + "peerId":"(unspec)", + "nexthops":[ + { + "ip":"::", + "afi":"ipv6", + "used":true + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgpd.conf new file mode 100644 index 0000000..6bb0a88 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/bgpd.conf @@ -0,0 +1,45 @@ +debug bgp vpn leak-from-vrf +debug bgp vpn label +debug bgp nht +debug bgp updates out +router bgp 65500 + bgp router-id 192.168.0.1 + no bgp ebgp-requires-policy + neighbor 192:168::2 remote-as 65501 + address-family ipv4 unicast + no neighbor 192:168::2 activate + exit-address-family + address-family ipv6 vpn + neighbor 192:168::2 activate + neighbor 192:168::2 soft-reconfiguration inbound + exit-address-family +! +router bgp 65500 vrf vrf1 + bgp router-id 192.168.0.1 + neighbor 192:2::100 remote-as 65500 + neighbor 192:168::255:13 remote-as 65500 + address-family ipv6 unicast + neighbor 192:2::100 activate + neighbor 192:2::100 route-map rmap in + neighbor 192:168::255:13 activate + neighbor 192:168::255:13 route-map rmap in + redistribute connected + redistribute static route-map rmap + label vpn export allocation-mode per-nexthop + label vpn export auto + rd vpn export 444:1 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r1-eth0 + mpls bgp forwarding +! +bgp community-list 1 seq 5 permit 10:10 +! +route-map rmap permit 1 + set ipv6 next-hop prefer-global +! +route-map rmap permit 2 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/zebra.conf new file mode 100644 index 0000000..bdad9ee --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r1/zebra.conf @@ -0,0 +1,18 @@ +log stdout +debug zebra nht +!debug zebra kernel msgdump recv +!debug zebra dplane detailed +!debug zebra packet recv +interface r1-eth1 vrf vrf1 + ipv6 address 192:2::1/64 +! +interface r1-eth2 vrf vrf1 + ipv6 address 192:168::255:1/112 +! +interface r1-eth0 + ip address 192:168::1/112 +! +vrf vrf1 + ipv6 route 172:31::14/128 192:2::14 + ipv6 route 172:31::15/128 192:2::12 +exit-vrf diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/bgpd.conf new file mode 100644 index 0000000..cb653d6 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 65500 + bgp router-id 11.11.11.11 + no bgp network import-check + neighbor 192:2::100 remote-as 65500 + address-family ipv4 unicast + no neighbor 192:2::100 activate + ! + address-family ipv6 unicast + neighbor 192:2::100 activate + network 172:31::11/128 + network 172:31::111/128 + network 172:31::20/128 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/zebra.conf new file mode 100644 index 0000000..a76080d --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r11/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r11-eth0 + ipv6 address 192:2::11/64 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/bgpd.conf new file mode 100644 index 0000000..d41fb18 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/bgpd.conf @@ -0,0 +1,13 @@ +router bgp 65500 + bgp router-id 12.12.12.12 + no bgp network import-check + neighbor 192:2::100 remote-as 65500 + address-family ipv4 unicast + no neighbor 192:2::100 activate + ! + address-family ipv6 unicast + neighbor 192:2::100 activate + network 172:31::12/128 + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/zebra.conf new file mode 100644 index 0000000..df9cae4 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r12/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r12-eth0 + ipv6 address 192:2::12/64 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/bgpd.conf new file mode 100644 index 0000000..0437882 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/bgpd.conf @@ -0,0 +1,12 @@ +router bgp 65500 + bgp router-id 13.13.13.13 + no bgp network import-check + neighbor 192:168::255:1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192:168::255:1 activate + exit-address-family + address-family ipv6 unicast + neighbor 192:168::255:1 activate + network 172:31::0:13/128 + exit-address-family +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/zebra.conf new file mode 100644 index 0000000..dfe5994 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r13/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface r13-eth0 + ipv6 address 192:168::255:13/112 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgp_vpnv6_routes.json b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgp_vpnv6_routes.json new file mode 100644 index 0000000..bb7d5c0 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgp_vpnv6_routes.json @@ -0,0 +1,187 @@ +{ + "vrfName": "default", + "localAS": 65501, + "routes": + { + "routeDistinguishers": + { + "444:1": + { + "172:31::11/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::11", + "prefixLen": 128, + "network": "172:31::11/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::12/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::12", + "prefixLen": 128, + "network": "172:31::12/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::13/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::13", + "prefixLen": 128, + "network": "172:31::13/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::14/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::14", + "prefixLen": 128, + "network": "172:31::14/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::15/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::15", + "prefixLen": 128, + "network": "172:31::15/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::20/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::20", + "prefixLen": 128, + "network": "172:31::20/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "172:31::111/128": [ + { + "valid": true, + "bestpath": true, + "prefix": "172:31::111", + "prefixLen": 128, + "network": "172:31::111/128", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192:2::/64": [ + { + "valid": true, + "bestpath": true, + "prefix": "192:2::", + "prefixLen": 64, + "network": "192:2::/64", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ], + "192:168::255:0/112": [ + { + "valid": true, + "bestpath": true, + "prefix": "192:168::255:0", + "prefixLen": 112, + "network": "192:168::255:0/112", + "peerId": "192:168::1", + "nexthops": [ + { + "ip": "192:168::1", + "afi": "ipv6", + "used": true + } + ] + } + ] + }, + "444:2": + { + "10:200::/64": [ + { + "valid": true, + "bestpath": true, + "prefix": "10:200::", + "prefixLen": 64, + "network": "10:200::/64", + "peerId": "(unspec)", + "nhVrfName": "vrf1", + "nexthops": [ + { + "ip": "::", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgpd.conf new file mode 100644 index 0000000..30e9959 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/bgpd.conf @@ -0,0 +1,25 @@ +router bgp 65501 + bgp router-id 192.168.0.2 + no bgp ebgp-requires-policy + neighbor 192:168::1 remote-as 65500 + address-family ipv4 unicast + no neighbor 192:168::1 activate + exit-address-family + address-family ipv6 vpn + neighbor 192:168::1 activate + exit-address-family +! +router bgp 65501 vrf vrf1 + bgp router-id 192.168.0.2 + address-family ipv6 unicast + redistribute connected + label vpn export 102 + rd vpn export 444:2 + rt vpn both 52:100 + export vpn + import vpn + exit-address-family +! +interface r2-eth0 + mpls bgp forwarding +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/zebra.conf new file mode 100644 index 0000000..47cee95 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/r2/zebra.conf @@ -0,0 +1,7 @@ +log stdout +interface r2-eth1 vrf vrf1 + ipv6 address 10:200::2/64 +! +interface r2-eth0 + ipv6 address 192:168::2/112 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/bgpd.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/bgpd.conf new file mode 100644 index 0000000..8c7664b --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/bgpd.conf @@ -0,0 +1,24 @@ +router bgp 65500 + bgp router-id 100.100.100.100 + no bgp network import-check + neighbor 192:2::1 remote-as 65500 + neighbor 192:2::11 remote-as 65500 + neighbor 192:2::12 remote-as 65500 + address-family ipv4 unicast + no neighbor 192:2::1 activate + no neighbor 192:2::11 activate + no neighbor 192:2::12 activate + ! + address-family ipv6 unicast + neighbor 192:2::1 activate + neighbor 192:2::1 route-reflector-client + neighbor 192:2::1 nexthop-local unchanged + neighbor 192:2::11 activate + neighbor 192:2::11 route-reflector-client + neighbor 192:2::11 nexthop-local unchanged + neighbor 192:2::12 activate + neighbor 192:2::12 route-reflector-client + neighbor 192:2::12 nexthop-local unchanged + exit-address-family +! + diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/zebra.conf b/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/zebra.conf new file mode 100644 index 0000000..94b82dc --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/rr/zebra.conf @@ -0,0 +1,4 @@ +log stdout +interface rr-eth0 + ipv6 address 192:2::100/64 +! diff --git a/tests/topotests/bgp_vpnv6_per_nexthop_label/test_bgp_vpnv6_per_nexthop_label.py b/tests/topotests/bgp_vpnv6_per_nexthop_label/test_bgp_vpnv6_per_nexthop_label.py new file mode 100644 index 0000000..3d5f8f6 --- /dev/null +++ b/tests/topotests/bgp_vpnv6_per_nexthop_label/test_bgp_vpnv6_per_nexthop_label.py @@ -0,0 +1,826 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# test_bgp_vpnv6_per_nexthop_label.py +# +# Copyright 2023 6WIND S.A. +# + +""" + test_bgp_vpnv6_per_nexthop_label.py: Test the FRR BGP daemon using EBGP peering + Let us exchange VPNv6 updates between both devices + Updates from r1 will originate from the same RD, but will have separate + label values. + + +----------+ + | r11 | + |192::2:11 +---+ + | | | +----+--------+ +----------+ + +----------+ | 192::2::1 |vrf | r1 |192:168::/112 | r2 | + +-------------------+ | 1+--------------+ | + +----------+ | |VRF1|AS65500 | | AS65501 | + | r12 | | +--------------+ | VPNV4| |VPNV4 | + |192::2:12 +---+ |192:168::255:1+-+--+--------+ +----------+ + | | | + +----------+ | + | + +----------+ | + | r13 | | + |192:168:: +--------+ + | 255:13 | + +----------+ +""" + +import os +import sys +import json +from functools import partial +import pytest +import functools + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +pytestmark = [pytest.mark.bgpd] + +PREFIXES_R11 = ["172:31::11/128", "172:31::20/128", "172:31::111/128"] +PREFIXES_R12 = ["172:31::12/128", "172:31::15/128"] +PREFIXES_REDIST_R14 = ["172:31::14/128"] +PREFIXES_CONNECTED = ["192:168::255:0/112", "192:2::/64"] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + tgen.add_router("r11") + tgen.add_router("r12") + tgen.add_router("r13") + tgen.add_router("r14") + tgen.add_router("rr") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r11"]) + switch.add_link(tgen.gears["r12"]) + switch.add_link(tgen.gears["rr"]) + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r13"]) + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r14"]) + + +def _populate_iface(): + tgen = get_topogen() + cmds_list = [ + "ip link add vrf1 type vrf table 10", + "echo 100000 > /proc/sys/net/mpls/platform_labels", + "ip link set dev vrf1 up", + "ip link set dev {0}-eth1 master vrf1", + "echo 1 > /proc/sys/net/mpls/conf/{0}-eth0/input", + ] + cmds_list_plus = [ + "ip link set dev {0}-eth2 master vrf1", + ] + + for cmd in cmds_list: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list_plus: + input = cmd.format("r1") + logger.info("input: " + cmd) + output = tgen.net["r1"].cmd(cmd.format("r1")) + logger.info("output: " + output) + + for cmd in cmds_list: + input = cmd.format("r2") + logger.info("input: " + cmd) + output = tgen.net["r2"].cmd(cmd.format("r2")) + logger.info("output: " + output) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + _populate_iface() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def check_bgp_vpnv6_prefix_presence(router, prefix): + "Check the presence of a prefix" + tgen = get_topogen() + + dump = router.vtysh_cmd("show bgp ipv6 vpn {} json".format(prefix), isjson=True) + if not dump: + return "{}, prefix ipv6 vpn {} is not installed yet".format(router.name, prefix) + return None + + +def bgp_vpnv6_table_check(router, group, label_list=None, label_value_expected=None): + """ + Dump and check that vpnv6 entries have the same MPLS label value + * 'router': the router to check + * 'group': the list of prefixes to check. a single label value for the group has to be found + * 'label_list': check that the label values are not present in the vpnv6 entries + * that list is updated with the present label value + * 'label_value_expected': check that the mpls label read is the same as that value + """ + + stored_label_inited = False + for prefix in group: + test_func = functools.partial(check_bgp_vpnv6_prefix_presence, router, prefix) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, prefix ipv6 vpn {} is not installed yet".format( + router.name, prefix + ) + + dump = router.vtysh_cmd("show bgp ipv6 vpn {} json".format(prefix), isjson=True) + for rd, pathes in dump.items(): + for path in pathes["paths"]: + assert ( + "remoteLabel" in path.keys() + ), "{0}, {1}, remoteLabel not present".format(router.name, prefix) + logger.info( + "{0}, {1}, label value is {2}".format( + router.name, prefix, path["remoteLabel"] + ) + ) + if stored_label_inited: + assert ( + path["remoteLabel"] == stored_label + ), "{0}, {1}, label value not expected one (expected {2}, observed {3}".format( + router.name, prefix, stored_label, path["remoteLabel"] + ) + else: + stored_label = path["remoteLabel"] + stored_label_inited = True + if label_list is not None: + assert ( + stored_label not in label_list + ), "{0}, {1}, label already detected in a previous prefix".format( + router.name, prefix + ) + label_list.add(stored_label) + + if label_value_expected: + assert ( + path["remoteLabel"] == label_value_expected + ), "{0}, {1}, label value not expected (expected {2}, observed {3}".format( + router.name, prefix, label_value_expected, path["remoteLabel"] + ) + + +def bgp_vpnv6_table_check_all(router, label_list=None, same=False): + """ + Dump and check that vpnv6 entries are correctly configured with specific label values + * 'router': the router to check + * 'label_list': check that the label values are not present in the vpnv6 entries + * that list is updated with the present label value found. + * 'same': by default, set to False. Addresses groups are classified by addresses. + * if set to True, all entries of all groups should have a unique label value + """ + if same: + bgp_vpnv6_table_check( + router, + group=PREFIXES_R11 + + PREFIXES_R12 + + PREFIXES_REDIST_R14 + + PREFIXES_CONNECTED, + label_list=label_list, + ) + else: + for group in ( + PREFIXES_R11, + PREFIXES_R12, + PREFIXES_REDIST_R14, + PREFIXES_CONNECTED, + ): + bgp_vpnv6_table_check(router, group=group, label_list=label_list) + + +def check_show_mpls_table(router, blacklist=None, label_list=None, whitelist=None): + nexthop_list = [] + if blacklist: + nexthop_list.append(blacklist) + + dump = router.vtysh_cmd("show mpls table json", isjson=True) + for in_label, label_info in dump.items(): + if label_list is not None: + label_list.add(in_label) + for nh in label_info["nexthops"]: + if "installed" not in nh.keys(): + return "{} {} is not installed yet on {}".format( + in_label, label_info, router.name + ) + if nh["installed"] != True or nh["type"] != "BGP": + return "{}, show mpls table, nexthop is not installed".format( + router.name + ) + if "nexthop" in nh.keys(): + if nh["nexthop"] in nexthop_list: + return "{}, show mpls table, duplicated or blacklisted nexthop address".format( + router.name + ) + nexthop_list.append(nh["nexthop"]) + elif "interface" in nh.keys(): + if nh["interface"] in nexthop_list: + return "{}, show mpls table, duplicated or blacklisted nexthop interface".format( + router.name + ) + nexthop_list.append(nh["interface"]) + else: + return "{}, show mpls table, entry with neither nexthop nor interface".format( + router.name + ) + + if whitelist: + for entry in whitelist: + if entry not in nexthop_list: + return "{}, show mpls table, entry with nexthop {} not present in nexthop list".format( + router.name, entry + ) + return None + + +def mpls_table_check(router, blacklist=None, label_list=None, whitelist=None): + """ + Dump and check 'show mpls table json' output. An assert is triggered in case test fails + * 'router': the router to check + * 'blacklist': the list of nexthops (IP or interface) that should not be on output + * 'label_list': the list of labels that should be in inLabel value + * 'whitelist': the list of nexthops (IP or interface) that should be on output + """ + logger.info("Checking MPLS labels on {}".format(router.name)) + logger.info("Checking MPLS labels on {}".format(router.name)) + # Check r2 removed 172.31.0.30 vpnv4 update + test_func = functools.partial( + check_show_mpls_table, router, blacklist, label_list, whitelist + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "{}, MPLS labels check fail: {}".format(router.name, result) + + +def check_show_bgp_vpn_prefix_not_found(router, ipversion, prefix, rd, label=None): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + if label: + expected = {rd: {"prefix": prefix, "paths": [{"remoteLabel": label}]}} + else: + expected = {rd: {"prefix": prefix}} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def check_show_bgp_vpn_prefix_found(router, ipversion, prefix, rd): + output = json.loads( + router.vtysh_cmd("show bgp {} vpn {} json".format(ipversion, prefix)) + ) + expected = {rd: {"prefix": prefix}} + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_found(router, inlabel, interface): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = { + "inLabel": inlabel, + "installed": True, + "nexthops": [{"interface": interface}], + } + return topotest.json_cmp(output, expected) + + +def check_show_mpls_table_entry_label_not_found(router, inlabel): + output = json.loads(router.vtysh_cmd("show mpls table {} json".format(inlabel))) + expected = {"inlabel": inlabel, "installed": True} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + +def mpls_entry_get_interface(router, label): + """ + Assert that the label is in MPLS table + Assert an outgoing interface is programmed + return the outgoing interface + """ + outgoing_interface = None + + logger.info("Checking MPLS labels on {}".format(router.name)) + dump = router.vtysh_cmd("show mpls table {} json".format(label), isjson=True) + assert dump, "{}, show mpls table, inLabel {} not found".format(router.name, label) + + for nh in dump["nexthops"]: + assert ( + "interface" in nh.keys() + ), "{}, show mpls table, nexthop interface not present for MPLS entry {}".format( + router.name, label + ) + + outgoing_interface = nh["interface"] + + return outgoing_interface + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check BGP IPv6 routing tables on VRF1 of r1 + logger.info("Checking BGP IPv6 routes for convergence on r1 VRF1") + router = tgen.gears["r1"] + json_file = "{}/{}/bgp_ipv6_routes_vrf1.json".format(CWD, router.name) + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp vrf vrf1 ipv6 json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=20, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + logger.info("Checking BGP VPNv6 routes for convergence on r2") + router = tgen.gears["r2"] + json_file = "{}/{}/bgp_vpnv6_routes.json".format(CWD, router.name) + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show bgp ipv6 vpn json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check BGP labels received on r2 + logger.info("Checking BGP VPNv6 labels on r2") + label_list = set() + bgp_vpnv6_table_check_all(tgen.gears["r2"], label_list) + + # Check MPLS labels received on r1 + mpls_table_check(tgen.gears["r1"], label_list) + + +def test_flapping_bgp_vrf_down(): + """ + Turn down a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Unpeering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nno neighbor 192:2::100\n", + isjson=False, + ) + + def _bgp_prefix_not_found(router, vrf, ipversion, prefix): + output = json.loads( + router.vtysh_cmd( + "show bgp vrf {} {} {} json".format(vrf, ipversion, prefix) + ) + ) + expected = {"prefix": prefix} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + # Check prefix from r11 is not present + test_func = functools.partial( + _bgp_prefix_not_found, tgen.gears["r1"], "vrf1", "ipv6", "172:31::11/128" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r1, prefix 172:31::11/128 from r11 did not disappear. r11 still connected to rr ?" + + # Check BGP updated received on r2 are not from r11 + logger.info("Checking BGP VPNv6 labels on r2") + for entry in PREFIXES_R11: + dump = tgen.gears["r2"].vtysh_cmd( + "show bgp ipv6 vpn {} json".format(entry), isjson=True + ) + for rd in dump: + assert False, "r2, {}, route distinguisher {} present".format(entry, rd) + + mpls_table_check(tgen.gears["r1"], blacklist=["192:2::11"]) + + +def test_flapping_bgp_vrf_up(): + """ + Turn up a remote BGP session + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + logger.info("Peering BGP on r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\nneighbor 192:2::100 remote-as 65500\n", + isjson=False, + ) + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp 65500\naddress-family ipv6 unicast\nneighbor 192:2::100 activate\n", + isjson=False, + ) + + # Check r2 gets prefix 172:31::11/128 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv6", + "172:31::11/128", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, prefix 172:31::11/128 from r11 not present. r11 still disconnected from rr ?" + bgp_vpnv6_table_check_all(tgen.gears["r2"]) + + +def test_recursive_route(): + """ + Test static recursive route redistributed over BGP + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nipv6 route 172:31::30/128 172:31::20\n", + isjson=False, + ) + logger.info("Checking BGP VPNv6 labels on r2") + # that route should be sent along with label for 192.0.2.11 + + def _prefix30_not_found(router): + output = json.loads(router.vtysh_cmd("show bgp ipv6 vpn 172:31::30/128 json")) + expected = {"444:1": {"prefix": "172:31::30/128"}} + ret = topotest.json_cmp(output, expected) + if ret is None: + return "not good" + return None + + def _prefix30_found(router): + output = json.loads(router.vtysh_cmd("show bgp ipv6 vpn 172:31::30/128 json")) + expected = {"444:1": {"prefix": "172:31::30/128"}} + return topotest.json_cmp(output, expected) + + # Check r2 received vpnv6 update with 172:31::30 + test_func = functools.partial(_prefix30_found, tgen.gears["r2"]) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, VPNv6 update 172:31::30 not found" + + # that route should be sent along with label for 192::2:11 + bgp_vpnv6_table_check( + tgen.gears["r2"], + group=PREFIXES_R11 + ["172:31::30/128"], + ) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + logger.info("Dumping nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 nexthop detail", isjson=False) + + logger.info("Disabling recursive static route") + tgen.gears["r1"].vtysh_cmd( + "configure terminal\nvrf vrf1\nno ipv6 route 172:31::30/128 172:31::20\n", + isjson=False, + ) + + # Check r2 removed 172:31::30 vpnv6 update + test_func = functools.partial(_prefix30_not_found, tgen.gears["r2"]) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, VPNv6 update 172:31::30 still present" + + +def test_prefix_changes_interface(): + """ + Test BGP update for a given prefix learnt on different interface + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Enabling a 172:31::50/128 prefix for r11") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv6 unicast\nnetwork 172:31::50/128", + isjson=False, + ) + + # Check r2 received vpnv6 update with 172:31::50 + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv6", + "172:31::50/128", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, VPNv6 update 172:31::50 not found" + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + label_list = set() + bgp_vpnv6_table_check( + tgen.gears["r2"], + group=PREFIXES_R11 + ["172:31::50/128"], + label_list=label_list, + ) + + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r11 found" + + oldlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(oldlabel)) + old_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], oldlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + oldlabel, old_outgoing_interface + ) + ) + + logger.info("Moving the 172:31::50/128 prefix from r11 to r13") + tgen.gears["r11"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv6 unicast\nno network 172:31::50/128", + isjson=False, + ) + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv6 unicast\nnetwork 172:31::50/128", + isjson=False, + ) + + # Check r2 removed 172:31::50 vpnv6 update with old label + test_func = functools.partial( + check_show_bgp_vpn_prefix_not_found, + tgen.gears["r2"], + "ipv6", + "172:31::50/128", + "444:1", + label=oldlabel, + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert ( + success + ), "r2, vpnv6 update 172:31::50 with old label {0} still present".format(oldlabel) + + # diagnostic + logger.info("Dumping label nexthop table") + tgen.gears["r1"].vtysh_cmd("show bgp vrf vrf1 label-nexthop detail", isjson=False) + + # Check r2 received new 172:31::50 vpnv6 update + test_func = functools.partial( + check_show_bgp_vpn_prefix_found, + tgen.gears["r2"], + "ipv6", + "172:31::50/128", + "444:1", + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r2, vpnv6 update 172:31::50 not found" + + label_list = set() + bgp_vpnv6_table_check( + tgen.gears["r2"], + group=["172:31::13/128", "172:31::50/128"], + label_list=label_list, + ) + assert ( + len(label_list) == 1 + ), "Multiple Label values found for updates from r13 found" + + newlabel = label_list.pop() + logger.info("r1, getting the outgoing interface used by label {}".format(newlabel)) + new_outgoing_interface = mpls_entry_get_interface(tgen.gears["r1"], newlabel) + logger.info( + "r1, outgoing interface used by label {} is {}".format( + newlabel, new_outgoing_interface + ) + ) + if old_outgoing_interface == new_outgoing_interface: + assert 0, "r1, outgoing interface did not change whereas BGP update moved" + + logger.info("Restoring state by removing the 172:31::50/128 prefix from r13") + tgen.gears["r13"].vtysh_cmd( + "configure terminal\nrouter bgp\naddress-family ipv6 unicast\nno network 172:31::50/128", + isjson=False, + ) + + +def test_changing_default_label_value(): + """ + Change the MPLS default value + Check that r1 VPNv6 entries have the 222 label value + Check that MPLS entry with old label value is no more present + Check that MPLS entry for local traffic has inLabel set to 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router = tgen.gears["r1"] + + # counting the number of labels used in the VPNv6 table + label_list = set() + logger.info("r1, VPNv6 table, check the number of labels used before modification") + bgp_vpnv6_table_check_all(router, label_list) + old_len = len(label_list) + assert ( + old_len != 1 + ), "r1, number of labels used should be greater than 1, oberved {} ".format(old_len) + + logger.info("r1, vrf1, changing the default MPLS label value to export to 222") + router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv6 unicast\nlabel vpn export 222\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 222 has vrf1 interface" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_found, router, 222, "vrf1" + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 222 not found" + + # check label repartition is ok + logger.info("r1, VPNv6 table, check the number of labels used after modification") + label_list = set() + bgp_vpnv6_table_check_all(router, label_list) + new_len = len(label_list) + assert ( + old_len == new_len + ), "r1, number of labels after modification differ from previous, observed {}, expected {} ".format( + new_len, old_len + ) + + logger.info( + "r1, VPNv6 table, check that prefixes that were using the vrf label have refreshed the label value to 222" + ) + bgp_vpnv6_table_check(router, group=PREFIXES_CONNECTED, label_value_expected=222) + + +def test_unconfigure_allocation_mode_nexthop(): + """ + Test unconfiguring allocation mode per nexthop + Check on r2 that new MPLS label values have been propagated + Check that show mpls table has no entry with label 17 (previously used) + Check that all VPN updates on r1 should have label value moved to 222 + Check that show mpls table will only have 222 label value + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Unconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + dump = router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv6 unicast\nno label vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check r1 updated the MPLS entry with the 222 label value + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is not present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv6 routes from r1 + logger.info("Checking VPNv6 routes on r1") + label_list = set() + bgp_vpnv6_table_check_all(router, label_list=label_list, same=True) + assert len(label_list) == 1, "r1, multiple Label values found for VPNv6 updates" + + new_label = label_list.pop() + assert ( + new_label == 222 + ), "r1, wrong label value in VPNv6 table, expected 222, observed {}".format( + new_label + ) + + # Check mpls table with 222 value + logger.info("Checking MPLS values on show mpls table of r1") + label_list = set() + label_list.add(222) + mpls_table_check(router, label_list=label_list) + + +def test_reconfigure_allocation_mode_nexthop(): + """ + Test re-configuring allocation mode per nexthop + Check that show mpls table has no entry with label 17 + Check that all VPN updates on r1 should have multiple label values and not only 222 + Check that show mpls table will have multiple label values and not only 222 + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Reconfiguring allocation mode per nexthop") + router = tgen.gears["r1"] + dump = router.vtysh_cmd( + "configure terminal\nrouter bgp 65500 vrf vrf1\naddress-family ipv6 unicast\nlabel vpn export allocation-mode per-nexthop\n", + isjson=False, + ) + + # Check that show mpls table has no entry with label 17 + logger.info( + "r1, mpls table, check that MPLS entry with inLabel set to 17 is present" + ) + test_func = functools.partial( + check_show_mpls_table_entry_label_not_found, router, 17 + ) + success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert success, "r1, mpls entry with label 17 still present" + + # Check vpnv6 routes from r1 + logger.info("Checking VPNv6 routes on r1") + label_list = set() + bgp_vpnv6_table_check_all(router, label_list=label_list) + assert len(label_list) != 1, "r1, only 1 label values found for VPNv6 updates" + + # Check mpls table with all values + logger.info("Checking MPLS values on show mpls table of r1") + mpls_table_check(router, label_list=label_list) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_different_asn/__init__.py b/tests/topotests/bgp_vrf_different_asn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vrf_different_asn/r1/frr.conf b/tests/topotests/bgp_vrf_different_asn/r1/frr.conf new file mode 100644 index 0000000..b325dfb --- /dev/null +++ b/tests/topotests/bgp_vrf_different_asn/r1/frr.conf @@ -0,0 +1,18 @@ +! +vrf vrf100 + vni 10100 +exit-vrf +! +interface r1-eth0 vrf vrf100 + ip address 192.168.1.1/24 +! +router bgp 65000 + address-family ipv4 unicast + import vrf vrf100 + exit-address-family +! +router bgp 65100 vrf vrf100 + address-family ipv4 unicast + redistribute connected + exit-address-family +! diff --git a/tests/topotests/bgp_vrf_different_asn/test_bgp_vrf_different_asn.py b/tests/topotests/bgp_vrf_different_asn/test_bgp_vrf_different_asn.py new file mode 100644 index 0000000..9a1a9ec --- /dev/null +++ b/tests/topotests/bgp_vrf_different_asn/test_bgp_vrf_different_asn.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2024 by +# Donatas Abraitis +# + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 2): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + r1 = tgen.gears["r1"] + r1.run("ip link add vrf100 type vrf table 1001") + r1.run("ip link set up dev vrf100") + r1.run("ip link set r1-eth0 master vrf100") + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_vrf_different_asn(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_instances(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp vrf all json")) + expected = { + "default": { + "vrfName": "default", + "localAS": 65000, + }, + "vrf100": { + "vrfName": "vrf100", + "localAS": 65100, + }, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_instances) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't see vrf100 to be under 65100 ASN" + + def _bgp_check_imported_route(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip route 192.168.1.0/24 json") + ) + expected = { + "192.168.1.0/24": [ + { + "installed": True, + "selected": True, + "nexthops": [ + { + "interfaceName": "vrf100", + "vrf": "vrf100", + "active": True, + } + ], + } + ] + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_imported_route) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Can't see 192.168.1.0/24 being imported into a default VRF" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo1.json b/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo1.json new file mode 100644 index 0000000..b1d7d09 --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo1.json @@ -0,0 +1,563 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["11.11.11.1/32", "11.11.11.11/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["11:11::1/128", "11:11::11/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["10.10.10.10/32", "10.10.10.100/32"], + "next_hop":"Null0" + }, + { + "network": ["10:10::10/128", "10:10::100/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["22.22.22.2/32", "22.22.22.22/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["22:22::2/128", "22:22::22/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["20.20.20.20/32", "20.20.20.200/32"], + "next_hop":"Null0" + }, + { + "network": ["20:20::20/128", "20:20::200/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["30.30.30.3/32", "30.30.30.30/32"], + "next_hop":"Null0" + }, + { + "network": ["30:30::3/128", "30:30::30/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["40.40.40.4/32", "40.40.40.40/32"], + "next_hop":"Null0" + }, + { + "network": ["40:40::4/128", "40:40::40/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + } + } +} diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo2.json b/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo2.json new file mode 100644 index 0000000..b1d7d09 --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak/bgp_vrf_dynamic_route_leak_topo2.json @@ -0,0 +1,563 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["11.11.11.1/32", "11.11.11.11/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["11:11::1/128", "11:11::11/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["10.10.10.10/32", "10.10.10.100/32"], + "next_hop":"Null0" + }, + { + "network": ["10:10::10/128", "10:10::100/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["22.22.22.2/32", "22.22.22.22/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["22:22::2/128", "22:22::22/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["20.20.20.20/32", "20.20.20.200/32"], + "next_hop":"Null0" + }, + { + "network": ["20:20::20/128", "20:20::200/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["30.30.30.3/32", "30.30.30.30/32"], + "next_hop":"Null0" + }, + { + "network": ["30:30::3/128", "30:30::30/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["40.40.40.4/32", "40.40.40.40/32"], + "next_hop":"Null0" + }, + { + "network": ["40:40::4/128", "40:40::40/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + } + } +} diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo1.py b/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo1.py new file mode 100644 index 0000000..c34c3eb --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo1.py @@ -0,0 +1,1903 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: + +1. Verify that dynamically imported routes are further advertised + to iBGP peers(peer in cluster). +2. Verify matching a prefix based on community attribute and + importing it by stripping off this value +3. Verify the route-map operation along with dynamic import command. +4. Verifying the JSON outputs for all supported commands +""" + +import os +import sys +import time +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + step, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_bgp_community_lists, + create_interface_in_kernel, + check_router_status, + verify_cli_json, + verify_fib_routes, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.10/32", "ipv6": "10:10::10/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} +NETWORK3_3 = {"ipv4": "50.50.50.5/32", "ipv6": "50:50::5/128"} +NETWORK3_4 = {"ipv4": "50.50.50.50/32", "ipv6": "50:50::50/128"} + +NETWORK4_1 = {"ipv4": "40.40.40.4/32", "ipv6": "40:40::4/128"} +NETWORK4_2 = {"ipv4": "40.40.40.40/32", "ipv6": "40:40::40/128"} +NETWORK4_3 = {"ipv4": "50.50.50.5/32", "ipv6": "50:50::5/128"} +NETWORK4_4 = {"ipv4": "50.50.50.50/32", "ipv6": "50:50::50/128"} + +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +LOOPBACK_1 = { + "ipv4": "10.0.0.7/24", + "ipv6": "fd00:0:0:1::7/64", +} +LOOPBACK_2 = { + "ipv4": "10.0.0.16/24", + "ipv6": "fd00:0:0:3::5/64", +} +PREFERRED_NEXT_HOP = "global" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Local APIs +# +##################################################### + + +def disable_route_map_to_prefer_global_next_hop(tgen, topo): + """ + This API is to remove prefer global route-map applied on neighbors + + Parameter: + ---------- + * `tgen` : Topogen object + * `topo` : Input JSON data + + Returns: + -------- + True/errormsg + + """ + + logger.info("Remove prefer-global rmap applied on neighbors") + input_dict = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "300", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r4": { + "bgp": [ + { + "local_as": "400", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "400", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + return True + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_imported_routes_advertised_to_iBGP_peer_p0(request): + """ + TC5_FUNC_5: + 1.5.5. Verify that dynamically imported routes are further advertised + to iBGP peers(peer in cluster). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + + step( + "Redistribute configured static routes into BGP process" " on R1 and R3/R4" + ) + + input_dict_1 = {} + DUT = ["r1", "r3", "r4"] + VRFS = ["default", "default", "default"] + AS_NUM = [100, 300, 400] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Verify that R1 receives BGP routes from R3 and R4 in " "vrf default.") + + input_routes_r3 = { + "r3": { + "static_routes": [ + { + "network": [ + NETWORK3_1[addr_type], + NETWORK3_2[addr_type], + NETWORK3_3[addr_type], + NETWORK3_4[addr_type], + ] + } + ] + } + } + + input_routes_r4 = { + "r4": { + "static_routes": [ + { + "network": [ + NETWORK4_1[addr_type], + NETWORK4_2[addr_type], + NETWORK4_3[addr_type], + NETWORK4_4[addr_type], + ] + } + ] + } + } + + DUT = ["r1", "r2"] + INPUT_DICT = [input_routes_r3, input_routes_r4] + + for dut, routes in zip(DUT, INPUT_DICT): + result = verify_bgp_rib(tgen, addr_type, dut, routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, dut, routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Import from default vrf into vrf ISR on R1") + + input_dict_isr = {} + DUT = ["r1", "r2"] + VRFS = ["ISR", "ISR"] + AS_NUM = [100, 100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "default"}}} + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify that default vrf's imported routes are installed " + "in RIB/FIB of vrf ISR on R1:" + ) + + input_routes_r3 = { + "r3": { + "static_routes": [ + { + "network": [ + NETWORK3_1[addr_type], + NETWORK3_2[addr_type], + NETWORK3_3[addr_type], + NETWORK3_4[addr_type], + ], + "vrf": "ISR", + } + ] + } + } + + input_routes_r4 = { + "r4": { + "static_routes": [ + { + "network": [ + NETWORK4_1[addr_type], + NETWORK4_2[addr_type], + NETWORK4_3[addr_type], + NETWORK4_4[addr_type], + ], + "vrf": "ISR", + } + ] + } + } + + INPUT_DICT_VRF = [input_routes_r3, input_routes_r4] + + for routes in INPUT_DICT_VRF: + result = verify_bgp_rib(tgen, addr_type, "r1", routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_fib_routes(tgen, addr_type, "r1", routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + intf_r2_r1 = topo["routers"]["r2"]["links"]["r1-link1"] + for addr_type in ADDR_TYPES: + + step( + "Create a loopback10 interface on R1 with below IP address and " + "associate with vrf ISR:" + ) + + create_interface_in_kernel( + tgen, + "r1", + "loopback2", + LOOPBACK_2[addr_type], + "ISR", + ) + + for addr_type in ADDR_TYPES: + + step( + "On router R1 Change the next-hop of static routes in vrf " + "ISR to LOOPBACK_2" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_3[addr_type], NETWORK1_4[addr_type]], + "next_hop": "Null0", + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_routes_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_3[addr_type], NETWORK1_4[addr_type]], + "next_hop": (intf_r2_r1[addr_type]).split("/")[0], + } + ] + } + } + + result = create_static_routes(tgen, input_routes_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify that, though R1 originating BGP routes with next-hop" + " 24.1.1.2/24::1:2, which is local to R2(but in default vrf)" + ", R2 must receives and install all routes from R1 in vrf ISR." + ) + step( + "Verify on R2, that it now rejects 10.10.10.x routes originated " + "from R1. As next-hop IP is local to R2's vrf ISR." + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_3[addr_type], NETWORK1_4[addr_type]], + "vrf": "ISR", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Routes are still present \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("On router R1 delete static routes in vrf ISR to LOOPBACK_1") + + input_routes_r1 = { + "r1": { + "static_routes": [ + { + "network": [NETWORK1_3[addr_type], NETWORK1_4[addr_type]], + "next_hop": (intf_r2_r1[addr_type]).split("/")[0], + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, input_routes_r1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_dynamic_imported_matching_prefix_based_on_community_list_p0(request): + """ + TC7_FUNC_7: + 1.5.7. Verify matching a prefix based on community attribute and + importing it by stripping off this value + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + + step( + "Configure route-map to set community attribute for a specific" + "prefix on R1 in vrf ISR" + ) + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pflist_ABC_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_cl = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": "100:100", + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_XYZ_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pflist_ABC_{}".format(addr_type) + } + }, + "set": {"community": {"num": "100:100"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Apply this route-map on R1 to vrf ISR while redistributing the" + " prefixes into BGP" + ) + + input_dict_1 = {} + DUT = ["r1"] + VRFS = ["ISR"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + } + ] + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Configure another route-map for filtering the prefixes based on" + " community attribute while importing into default vrf" + ) + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": "none"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Apply the route-map while Importing vrf ISR's prefixes into " + "default vrf on router R1:" + ) + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify on R1 that only prefixes with community value 100:100" + "in vrf ISR are imported to vrf default. While importing, the" + " community value has been stripped off:" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_dict_comm = {"community": "100:100"} + + result = verify_bgp_community( + tgen, + addr_type, + dut, + [NETWORK1_1[addr_type]], + input_dict_comm, + expected=False, + ) + assert ( + result is not True + ), "Testcase {} : Failed \n Error: Commnunity is not stipped off, {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Remove/re-add route-map XYZ from redistribution.") + + input_dict_1 = {} + DUT = ["r1"] + VRFS = ["ISR"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + "delete": True, + } + ] + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify that all the routes disappear from vrf default when " + "route-map is removed from redistribution, and appear again " + "when route-map is re-added to redistribution in vrf ISR." + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_dict_1 = {} + DUT = ["r1"] + VRFS = ["ISR"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + } + ] + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Remove/re-add route-map IMP form import statement.") + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type), + "delete": True, + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify that when route-map IMP is removed all the prefixes of" + " vrf ISR are imported to vrf default. However when route-map " + "IMP is re-added only 11.11.11.1 and 11:11::1 (with community " + "value) are imported." + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Delete/Re-add prefix-list ABC.") + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pflist_ABC_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + "delete": True, + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + input_dict_pf["r1"]["prefix_lists"][addr_type][ + "pflist_ABC_{}".format(addr_type) + ][0]["delete"] = False + + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Delete/Re-add community-list COMM.") + + input_dict_cl = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": "100:100", + "delete": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + input_dict_cl["r1"]["bgp_community_lists"][0]["delete"] = False + + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Delete/Re-add route-map XYZ.") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_XYZ_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pflist_ABC_{}".format(addr_type) + } + }, + "set": {"community": {"num": "100:100"}}, + "delete": True, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + input_dict_rm["r1"]["route_maps"]["rmap_XYZ_{}".format(addr_type)][0][ + "delete" + ] = False + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Delete/Re-add route-map IMP.") + + input_dict_rm2 = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": "none"}}, + "delete": True, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + input_dict_rm2["r1"]["route_maps"]["rmap_IMP_{}".format(addr_type)][0][ + "delete" + ] = False + + result = create_route_maps(tgen, input_dict_rm2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_routemap_operatons_with_dynamic_import_p0(request): + """ + TC8_FUNC_8: + 1.5.8. Verify the route-map operation along with dynamic import command. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + + step( + "Configure route-map to set community attribute for a specific" + "prefix on R1 in vrf ISR" + ) + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pflist_ABC_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_cl = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": "100:100", + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_XYZ_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pflist_ABC_{}".format(addr_type) + } + }, + "set": {"community": {"num": "100:100"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Apply this route-map on R1 to vrf ISR while redistributing the" + " prefixes into BGP" + ) + + input_dict_1 = {} + DUT = ["r1"] + VRFS = ["ISR"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + } + ] + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Configure another route-map for filtering the prefixes based on" + " community attribute while importing into default vrf" + ) + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": "500:500"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Apply the route-map while Importing vrf ISR's prefixes into " + "default vrf on router R1:" + ) + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify on R1 that only prefixes with community value 100:100" + "in vrf ISR are imported to vrf default. While importing, the" + " community value has been stripped off:" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Applying route-map first followed by import VRF command.") + step( + "Apply the route-map while Importing vrf ISR's prefixes into " + "default vrf on router R1:" + ) + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": {"import": {"vrf": "ISR", "delete": True}} + } + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Verify that until 'import VRF command' is not configured, " + "routes are not imported. After configuring 'import VRF command'" + " repeat step-4 for verification" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still present \n {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step("Delete/re-add import vrf ISR command multiple times in default" "vrf.") + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": {"import": {"vrf": "ISR", "delete": True}} + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when import vrf ISR command is deleted, " + "all routes of vrf ISR disappear from default vrf and " + "when it's re-configured, repeat step-4 for verification." + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Routes are still present, Error {}".format( + tc_name, result + ) + + input_dict_isr["r1"]["bgp"][0]["address_family"][addr_type]["unicast"][ + "import" + ]["delete"] = False + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + + step( + "Delete and re-configure route-map IMP from global config when " + "import and route-maps are applied in a ISR vrf." + ) + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": "500:500"}}, + "delete": True, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Routes are still present, Error {}".format( + tc_name, result + ) + + input_dict_rm["r1"]["route_maps"]["rmap_IMP_{}".format(addr_type)][0][ + "delete" + ] = False + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_comm = {"community": "500:500"} + + result = verify_bgp_community( + tgen, addr_type, dut, [NETWORK1_1[addr_type]], input_dict_comm + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_verify_cli_json_p1(request): + """ + TC8_FUNC_9: + 1.5.9. Verifying the JSON outputs for all supported commands: + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + input_dict = { + "r1": { + "cli": [ + "show bgp vrf default ipv4 summary", + "show bgp vrf all ipv6 summary", + "show bgp neighbors", + ] + } + } + + result = verify_cli_json(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo2.py b/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo2.py new file mode 100644 index 0000000..32643c2 --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak/test_bgp_vrf_dynamic_route_leak_topo2.py @@ -0,0 +1,890 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: + +1. Verify that Changing route-map configurations(match/set clauses) on + the fly it takes immediate effect. +2. Verify BGP best path selection algorithm works fine when + routes are imported from ISR to default vrf and vice versa. +""" + +import os +import sys +import json +import time +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + step, + create_route_maps, + create_prefix_lists, + create_bgp_community_lists, + check_router_status, + get_frr_ipv6_linklocal, + shutdown_bringup_interface, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_attributes, + verify_best_path_as_per_bgp_attribute, + verify_bgp_rib, +) +from lib.topojson import build_topo_from_json, build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK3_3 = {"ipv4": "50.50.50.5/32", "ipv6": "50:50::5/128"} +NETWORK3_4 = {"ipv4": "50.50.50.50/32", "ipv6": "50:50::50/128"} + +PREFERRED_NEXT_HOP = "global" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_bgp_best_path_with_dynamic_import_p0(request): + """ + TC6_FUNC_6: + 1.5.6. Verify BGP best path selection algorithm works fine when + routes are imported from ISR to default vrf and vice versa. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + step( + "Redistribute configured static routes into BGP process" " on R1/R2 and R3" + ) + + input_dict_1 = {} + DUT = ["r1", "r2", "r3", "r4"] + VRFS = ["ISR", "ISR", "default", "default"] + AS_NUM = [100, 100, 300, 400] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Import from default vrf into vrf ISR on R1 and R2 as below") + + input_dict_vrf = {} + DUT = ["r1", "r2"] + VRFS = ["ISR", "ISR"] + AS_NUM = [100, 100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_vrf.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "default"}}} + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_default = {} + DUT = ["r1", "r2"] + VRFS = ["default", "default"] + AS_NUM = [100, 100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_default.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_default) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify ECMP/Next-hop/Imported routes Vs Locally originated " + "routes/eBGP routes vs iBGP routes --already covered in almost" + " all tests" + ) + + for addr_type in ADDR_TYPES: + step("Verify Pre-emption") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r4_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Shutdown interface connected to r1 from r4:") + shutdown_bringup_interface(tgen, "r4", intf_r4_r1, False) + + for addr_type in ADDR_TYPES: + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + step("Verify next-hop is changed") + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r3_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bringup interface connected to r1 from r4:") + shutdown_bringup_interface(tgen, "r4", intf_r4_r1, True) + + for addr_type in ADDR_TYPES: + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + step("Verify next-hop is not chnaged aftr shutdown:") + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r3_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Active-Standby scenario(as-path prepend and Local pref)") + + for addr_type in ADDR_TYPES: + step("Create prefix-list") + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pf_ls_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK3_4[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set localpref 500") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 500}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Create route-map to match prefix-list and set localpref 600") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 20, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 600}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_rma = { + "r1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_PATH1_{}".format( + addr_type + ), + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_PATH2_{}".format( + addr_type + ), + "direction": "in", + } + ] + } + } + }, + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rma) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r1" + attribute = "locPrf" + + for addr_type in ADDR_TYPES: + step("Verify bestpath is installed as per highest localpref") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set localpref 700") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 700}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Verify bestpath is changed as per highest localpref") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set as-path prepend") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 20, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": { + "localpref": 700, + "path": {"as_num": "111", "as_action": "prepend"}, + }, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + attribute = "path" + + for addr_type in ADDR_TYPES: + step("Verify bestpath is changed as per shortest as-path") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_modify_route_map_match_set_clauses_p1(request): + """ + TC13_CHAOS_4: + 1.5.13. Verify that Changing route-map configurations(match/set clauses) on + the fly it takes immediate effect. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + step( + "Configure route-map to set community attribute for a specific" + "prefix on R1 in vrf ISR" + ) + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pflist_ABC_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_cl = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": "100:100", + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_XYZ_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pflist_ABC_{}".format(addr_type) + } + }, + "set": {"community": {"num": "100:100"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step( + "Apply this route-map on R1 to vrf ISR while redistributing the" + " prefixes into BGP" + ) + + input_dict_1 = {} + DUT = ["r1"] + VRFS = ["ISR"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + } + ] + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step( + "Configure another route-map for filtering the prefixes based on" + " community attribute while importing into default vrf" + ) + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": "none"}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step( + "Apply the route-map while Importing vrf ISR's prefixes into " + "default vrf on router R1:" + ) + + input_dict_isr = {} + DUT = ["r1"] + VRFS = ["default"] + AS_NUM = [100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_isr.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": { + "import": { + "vrf": "route-map rmap_IMP_{}".format(addr_type) + } + } + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_isr) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step( + "Verify on R1 that only prefixes with community value 100:100" + "in vrf ISR are imported to vrf default. While importing, the" + " community value has been stripped off:" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Add set clause in route-map IMP:") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": {"community_list": {"id": "COMM"}}, + "set": { + "large_community": {"num": "100:100:100"}, + "locPrf": 500, + "path": {"as_num": "100 100", "as_action": "prepend"}, + }, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step( + "Verify that as we continue adding different attributes " + "step-by-step in route-map IMP those attributes gets " + "attached to prefixes:" + ) + + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + input_dict_comm = {"largeCommunity": "100:100:100"} + + result = verify_bgp_community( + tgen, addr_type, dut, [NETWORK1_1[addr_type]], input_dict_comm + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + input_rmap = { + "r1": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [{"set": {"locPrf": 500}}] + } + } + } + + result = verify_bgp_attributes( + tgen, + addr_type, + "r1", + [NETWORK1_1[addr_type]], + rmap_name="rmap_IMP_{}".format(addr_type), + input_dict=input_rmap, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Change community-list to match a different value then " "100:100.") + + input_dict_cl = { + "r1": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": "100:100", + "delete": True, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_routes_r1 = { + "r1": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "default"} + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r1", input_routes_r1, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \n Error : Routes are still " "present {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/bgp_vrf_dynamic_route_leak_topo3.json b/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/bgp_vrf_dynamic_route_leak_topo3.json new file mode 100644 index 0000000..9c73baf --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/bgp_vrf_dynamic_route_leak_topo3.json @@ -0,0 +1,1088 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r3-link1": {"ipv4": "13.1.1.1/24", "ipv6": "13::1:1/120", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "13.1.1.1/24", "ipv6": "13::1:1/120", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "13.1.1.2/24", "ipv6": "13::1:2/120", "vrf": "RED"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r1-link3": {"ipv4": "13.1.1.2/24", "ipv6": "13::1:2/120", "vrf": "GREEN"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r4-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r5-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r5-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r5-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r5-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": {} + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": {} + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r5": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": {} + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/test_bgp_vrf_dynamic_route_leak_topo3.py b/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/test_bgp_vrf_dynamic_route_leak_topo3.py new file mode 100644 index 0000000..726afcb --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo3/test_bgp_vrf_dynamic_route_leak_topo3.py @@ -0,0 +1,1798 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify that with multiple tenant VRFs, dynamically imported routes are + further advertised to eBGP peers. +2. Verify the route-map operations along with dynamic import command +3. Verify that deleting static routes from originating VRF also deletes + routes from other VRFs and peers. +4. Verify that deleting and adding "import" command multiple times shows + consistent results. +""" + +import os +import sys +import time +import pytest +import platform +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_bgp_community_lists, + get_frr_ipv6_linklocal, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.1/32", "ipv6": "10:10::1/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} +NETWORK1_5 = {"ipv4": "110.110.110.1/32", "ipv6": "110:110::1/128"} +NETWORK1_6 = {"ipv4": "110.110.110.100/32", "ipv6": "110:110::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} +NETWORK2_5 = {"ipv4": "220.220.220.20/32", "ipv6": "220:220::20/128"} +NETWORK2_6 = {"ipv4": "220.220.220.200/32", "ipv6": "220:220::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} + +PREFIX_LIST = { + "ipv4": ["11.11.11.1", "22.22.22.2", "22.22.22.22"], + "ipv6": ["11:11::1", "22:22::2", "22:22::22"], +} +PREFERRED_NEXT_HOP = "global" +VRF_LIST = ["RED", "BLUE", "GREEN"] +COMM_VAL_1 = "100:100" +COMM_VAL_2 = "500:500" +COMM_VAL_3 = "600:600" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo3.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_import_routes_advertised_to_ebgp_peers_p0(request): + """ + Verify that with multiple tenant VRFs, dynamically imported routes are + further advertised to eBGP peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R2 and R3 and redistribute in BGP for " + "BLUE and RED vrf instances" + ) + for dut, network in zip( + ["r2", "r3"], [[NETWORK1_1, NETWORK1_2], [NETWORK2_1, NETWORK2_2]] + ): + for vrf_name, network_vrf in zip(["RED", "BLUE"], network): + step("Configure static route for VRF : {} on {}".format(vrf_name, dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [network_vrf[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut, as_num in zip(["r2", "r3"], ["2", "3"]): + for vrf_name in ["RED", "BLUE"]: + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + ) + + redist_dict = { + dut: { + "bgp": [ + {"vrf": vrf_name, "local_as": as_num, "address_family": temp} + ] + } + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R2 and R3 has installed redistributed routes in BLUE " + "and RED vrfs" + ) + for dut, network in zip( + ["r2", "r3"], [[NETWORK2_1, NETWORK2_2], [NETWORK1_1, NETWORK1_2]] + ): + for vrf_name, network_vrf in zip(["RED", "BLUE"], network): + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [network_vrf[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Import BLUE vrf's route in tenant vrf RED on R2 and then import " + "vrf RED's routes into BLUE vrf on R3" + ) + + for dut, as_num, vrf_name, vrf_import in zip( + ["r2", "r3"], ["2", "3"], ["RED", "BLUE"], ["BLUE", "RED"] + ): + step("Import vrf {} int vrf {}, on router {}".format(vrf_import, vrf_name, dut)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_import}}}}) + + import_dict = { + dut: { + "bgp": [{"vrf": vrf_name, "local_as": as_num, "address_family": temp}] + } + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R2's vrf RED and R3's vrf BLUE has installed 4 set of " + "prefixes. Routes imported from BLUE vrf (originated R2's & received " + "from R3). Vrf RED's local routes (originated by R2's & received " + "from R3)" + ) + step( + "Verify that R2 and R3 has installed redistributed routes in BLUE " + "and RED vrfs" + ) + + for dut, vrf_name in zip(["r2", "r3"], ["RED", "BLUE"]): + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Additionally, R2 receives R3's BLUE vrf's prefixes and then import " + "into vrf RED. These imported routes are advertised back to " + "(originator)R3 but now in vrf RED, however R3 doesn't install these " + "in vrf RED. Denied due to own AS" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import vrf BLUE from vrf RED's instance on R2.") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": "BLUE", "delete": True}}}} + ) + + import_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + step( + "Verify on R3 that, there is no change in FIB of vrf BLUE and R2's " + "BLUE vrf originated routes are removed from vrf RED on R3." + ) + for vrf_name in ["RED", "BLUE"]: + for addr_type in ADDR_TYPES: + if vrf_name == "RED": + network_vrf = [NETWORK1_1[addr_type], NETWORK2_1[addr_type]] + elif vrf_name == "BLUE": + network_vrf = [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ] + static_routes = { + "r3": { + "static_routes": [ + { + "network": network_vrf, + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import vrf BLUE from vrf RED's instance on R2.") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "BLUE"}}}}) + + import_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "All the routes described in earlier step should be added, once " + "import command on R2 is re-added." + ) + for dut, vrf_name in zip(["r2", "r3"], ["RED", "BLUE"]): + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import vrf RED from BLUE vrf on R3") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": "RED", "delete": True}}}} + ) + + import_dict = { + "r3": {"bgp": [{"vrf": "BLUE", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R2 that, there is no change in FIB of vrf RED and R3's " + "vrf RED's originated routes are removed from vrf BLUE on R2." + ) + for vrf_name in ["RED", "BLUE"]: + for addr_type in ADDR_TYPES: + if vrf_name == "BLUE": + network_vrf = [NETWORK1_2[addr_type], NETWORK2_2[addr_type]] + elif vrf_name == "RED": + network_vrf = [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ] + static_routes = { + "r2": { + "static_routes": [ + { + "network": network_vrf, + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Add import vrf RED from BLUE vrf on R3") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "RED"}}}}) + + import_dict = { + "r3": {"bgp": [{"vrf": "BLUE", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "All the routes described in earlier step should be added, once " + "import command on R2 is re-added." + ) + for dut, vrf_name in zip(["r2", "r3"], ["RED", "BLUE"]): + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK1_2[addr_type], + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + ], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_dynamic_imported_matching_prefix_based_on_community_list_p0(request): + """ + Verify the route-map operations along with dynamic import command + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R3 for vrf RED and redistribute in BGP " "instance" + ) + for vrf_name, networks in zip( + ["RED", "BLUE"], [[NETWORK1_1, NETWORK1_2], [NETWORK2_1, NETWORK2_2]] + ): + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [networks[0][addr_type], networks[1][addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure route-map to set community attribute for a specific " "prefix on R3" + ) + for addr_type in ADDR_TYPES: + input_dict_pf = { + "r3": { + "prefix_lists": { + addr_type: { + "pflist_ABC_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK1_1[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_cl = { + "r3": { + "bgp_community_lists": [ + { + "community_type": "expanded", + "action": "permit", + "name": "COMM", + "value": COMM_VAL_1, + } + ] + } + } + result = create_bgp_community_lists(tgen, input_dict_cl) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r3": { + "route_maps": { + "rmap_XYZ_{}".format(addr_type): [ + { + "action": "permit", + "match": { + addr_type: { + "prefix_lists": "pflist_ABC_{}".format(addr_type) + } + }, + "set": {"community": {"num": COMM_VAL_1}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Apply this route-map on R3 to set community under vrf RED/BLUE " + "while redistributing the prefixes into BGP" + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "redistribute": [ + { + "redist_type": "static", + "attribute": { + "route-map": "rmap_XYZ_{}".format(addr_type) + }, + } + ] + } + } + } + ) + + for vrf_name in ["RED", "BLUE"]: + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that specific prefixes matched in route-map have community " + "attribute value 100:100 tagged" + ) + input_dict_comm = {"community": COMM_VAL_1} + for addr_type in ADDR_TYPES: + result = verify_bgp_community( + tgen, addr_type, "r3", [NETWORK1_1[addr_type]], input_dict_comm, vrf="RED" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Configure a route-map for filtering the prefixes based on community " + "attribute while importing into default vrf" + ) + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r3": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": COMM_VAL_2}}, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Apply the route-map while Importing vrf RED/BLUE's prefixes into " + "GREEN vrf on router R3" + ) + temp = {} + for vrf_name in ["RED", "BLUE"]: + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + inport_dict = { + "r3": {"bgp": [{"vrf": "GREEN", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, inport_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "import": {"vrf": "route-map rmap_IMP_{}".format(addr_type)} + } + } + } + ) + + inport_dict = { + "r3": {"bgp": [{"vrf": "GREEN", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, inport_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_comm = {"community": COMM_VAL_2} + step( + "Verify on R3 that only prefixes with community value {} in vrf RED " + "are imported to vrf GREEN. While importing, the community value " + "has been changed to {}".format(COMM_VAL_1, COMM_VAL_2) + ) + + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "GREEN"}] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + static_routes = { + "r3": { + "static_routes": [ + { + "network": [ + NETWORK2_1[addr_type], + NETWORK2_2[addr_type], + NETWORK1_2[addr_type], + ], + "vrf": "GREEN", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_bgp_community( + tgen, addr_type, "r3", [NETWORK1_1[addr_type]], input_dict_comm, vrf="GREEN" + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Delete", "Add"], [True, False]): + step("{} import vrf RED/BLUE command one by one from vrf GREEN".format(action)) + temp = {} + for vrf_name in ["RED", "BLUE"]: + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": {"import": {"vrf": vrf_name, "delete": value}} + } + } + ) + + inport_dict = { + "r3": {"bgp": [{"vrf": "GREEN", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, inport_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when import vrf RED/BLUE is {} one by one, all " + "routes of respective vrf disappear from vrf GREEN without " + "affecting (BLUE/RED) routes".format(action.lower()) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "GREEN"} + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes["r3"]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, "r3", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes["r3"]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Delete", "Re-add"], [True, False]): + step( + "{} route-map IMP from global config when import and route-maps " + "are applied in vrf GREEN".format(action) + ) + for addr_type in ADDR_TYPES: + input_dict_rm = { + "r3": { + "route_maps": { + "rmap_IMP_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": {"community_list": {"id": "COMM"}}, + "set": {"community": {"num": COMM_VAL_2}}, + "delete": value, + } + ] + } + } + } + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when import vrf RED/BLUE is {} one by one, all " + "routes of respective vrf disappear from vrf GREEN without " + "affecting (BLUE/RED) routes".format(action.lower()) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": "GREEN"} + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes["r3"]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, "r3", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes["r3"]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_dynamic_import_routes_delete_static_route_p1(request): + """ + Verify that deleting static routes from originating VRF also deletes + routes from other VRFs and peers. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R3 for each tenant vrf and redistribute " + "in respective BGP instance" + ) + vrf_list = VRF_LIST + ["default"] + for vrf_name, network in zip( + vrf_list, [NETWORK1_1, NETWORK2_1, NETWORK3_1, NETWORK1_2] + ): + step("Configure static route for VRF : {}".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name, network in zip(vrf_list, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step( + "Verify that R3 has installed redistributed routes in respective " + "vrfs: {}".format(vrf_name) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import routes among vrfs as mentioned below on router R3") + + for vrf_name, vrf_import in zip( + ["GREEN", "BLUE", "default"], ["RED", "GREEN", "BLUE"] + ): + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_import}}}}) + + import_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name, vrf_import, installed, not_installed in zip( + ["BLUE", "default"], + ["GREEN", "BLUE"], + [NETWORK3_1, NETWORK2_1], + [NETWORK1_1, NETWORK3_1], + ): + step( + "Verify that only locally originated routes of vrf {} are " + "advertised to vrf {}".format(vrf_import, vrf_name) + ) + + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [installed[addr_type]], "vrf": vrf_name} + ] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Verify that non local originated routes {} of vrf {} are " + "not advertised to vrf {}".format( + not_installed[addr_type], vrf_import, vrf_name + ) + ) + + static_routes = { + "r3": { + "static_routes": [ + {"network": [not_installed[addr_type]], "vrf": vrf_name} + ] + } + } + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes["r2"]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes["r2"]["static_routes"][0]["network"] + ) + + step("Delete static routes from vrf RED") + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + step( + "Verify on R2 and R3, that only vrf RED and GREEN's RIB/FIB withdraw " + "deleted routes" + ) + for dut in ["r2", "r3"]: + step( + "Verify on {}, that only vrf RED and GREEN's RIB/FIB withdraw " + "deleted routes".format(dut) + ) + for vrf_name in ["RED", "GREEN"]: + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": vrf_name} + ] + } + } + result = verify_bgp_rib( + tgen, addr_type, "r2", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes["r2"]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, "r2", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + step("Delete static routes from vrf BLUE") + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": "blackhole", + "vrf": "BLUE", + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r2", "r3"]: + step( + "Verify on {}, that only default and BLUE vrf's RIB/FIB " + "withdraw deleted routes".format(dut) + ) + for vrf_name in ["BLUE", "default"]: + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK2_1[addr_type]], "vrf": vrf_name} + ] + } + } + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + step("Delete static routes from vrf default") + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_2[addr_type]], + "next_hop": "blackhole", + "delete": True, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r2", "r3"]: + step( + "Verify on {}, that only default vrf RIB/FIB withdraw deleted " + "routes".format(dut) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK1_2[addr_type]], "vrf": vrf_name} + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Add back all the routes that were deleted") + for vrf_name, network in zip( + vrf_list, [NETWORK1_1, NETWORK2_1, NETWORK3_1, NETWORK1_2] + ): + step("Configure static route for VRF : {}".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name, network in zip(vrf_list, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step( + "Verify that R3 has installed redistributed routes in respective " + "vrfs: {}".format(vrf_name) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_dynamic_import_routes_add_delete_import_command_p1(request): + """ + Verify that deleting and adding "import" command multiple times shows + consistent results. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R2 for vrf RED and redistribute in " + "respective BGP instance" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF RED") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that R2 has installed redistributed routes in respective " "vrfs only") + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "RED"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import vrf RED's routes into vrf GREEN on R2") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "RED"}}}}) + + import_dict = { + "r2": {"bgp": [{"vrf": "GREEN", "local_as": 2, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R2, that it installs imported routes from vrf RED to vrf " + "GREEN's RIB/FIB" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("On R3 import routes from vrfs GREEN to default") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "GREEN"}}}}) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now imported into vrf default " + "of R3, next-hop pointing to vrf GREEN" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + + next_hop_1 = topo["routers"]["r2"]["links"]["r3-link3"][addr_type].split("/")[0] + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_1 + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, next_hop=next_hop_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Delete import command from R3's default vrf instance for both " + "address-families 1 by 1 (ipv4/ipv6)" + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": "GREEN", "delete": True}}}} + ) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now removed from vrf " + "default on R3, however vrf GREEN still retains those" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Delete import command from R2's vrf GREEN instance for both " + "address-families 1 by 1 (ipv4/ipv6)" + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": "RED", "delete": True}}}} + ) + + import_dict = { + "r2": {"bgp": [{"vrf": "GREEN", "local_as": 2, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + step( + "Verify that R2's vrf RED routes are now removed from vrf GREEN " + "on R2 & R3 as well" + ) + for dut in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + {"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"} + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + + step( + "Add import command from R3's default vrf instance for both " + "address-families 1 by 1 (ipv4/ipv6)" + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "GREEN"}}}}) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that there are no routes installed on R3's vrf default " "RIB/FIB.") + for addr_type in ADDR_TYPES: + static_routes = { + "r3": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Add import command from R2's vrf GREEN instance for both " + "address-families 1 by 1 (ipv4/ipv6)." + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "RED"}}}}) + + import_dict = { + "r2": {"bgp": [{"vrf": "GREEN", "local_as": 2, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now imported into vrf " + "default of R3, next-hop pointing to vrf GREEN" + ) + for dut in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + {"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"} + ] + } + } + if dut == "r3": + next_hop_1 = topo["routers"]["r2"]["links"]["r3-link3"][ + addr_type + ].split("/")[0] + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, next_hop=next_hop_1 + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + if dut == "r3": + result = verify_rib( + tgen, addr_type, dut, static_routes, next_hop=next_hop_1 + ) + else: + result = verify_rib(tgen, addr_type, dut, static_routes) + + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Delete import command from R3's default vrf instance for both " + "address-families 1 by 1 (ipv4/ipv6)." + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": "GREEN", "delete": True}}}} + ) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now removed from vrf " + "default on R3, however vrf GREEN still retains those." + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + static_routes = { + "r2": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Delete redistribute static from R2 for vrf RED") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + } + } + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now removed from vrf GREEN " + "on R2 & R3 as well." + ) + for dut in ["r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + {"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"} + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + + step( + "Add import command from R3's default vrf instance for both " + "address-families 1 by 1 (ipv4/ipv6)." + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "GREEN"}}}}) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that there are no routes installed on R3's vrf default " "RIB/FIB") + for addr_type in ADDR_TYPES: + static_routes = { + "r3": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed \nError {}\n" "Routes {} still in BGP table".format( + tc_name, result, static_routes["r3"]["static_routes"][0]["network"] + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Add redistribute static from R2 for vrf RED") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that R2's vrf RED routes are now imported into vrf " + "default of R3, next-hop pointing to vrf GREEN" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + next_hop_1 = topo["routers"]["r2"]["links"]["r3-link3"][addr_type].split("/")[0] + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_1 + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, next_hop=next_hop_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/bgp_vrf_dynamic_route_leak_topo4.json b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/bgp_vrf_dynamic_route_leak_topo4.json new file mode 100644 index 0000000..9c73baf --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/bgp_vrf_dynamic_route_leak_topo4.json @@ -0,0 +1,1088 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r3-link1": {"ipv4": "13.1.1.1/24", "ipv6": "13::1:1/120", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "13.1.1.1/24", "ipv6": "13::1:1/120", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "13.1.1.2/24", "ipv6": "13::1:2/120", "vrf": "RED"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r1-link3": {"ipv4": "13.1.1.2/24", "ipv6": "13::1:2/120", "vrf": "GREEN"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r4-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r5-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r5-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r5-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r5-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": {} + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": {} + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r5": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": {} + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-1.py b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-1.py new file mode 100644 index 0000000..45d7b03 --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-1.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify recursive import among Tenant VRFs. +2. Verify that dynamic import works fine between two different Tenant VRFs. + When next-hop IPs are same across all VRFs. + When next-hop IPs are different across all VRFs. +3. Verify that with multiple tenant VRFs, dynamic import works fine between + Tenant VRFs to default VRF. + When next-hop IPs and prefixes are same across all VRFs. + When next-hop IPs and prefixes are different across all VRFs. +""" + +import os +import sys +import time +import pytest +import platform +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_bgp_community_lists, + get_frr_ipv6_linklocal, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.1/32", "ipv6": "10:10::1/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} +NETWORK1_5 = {"ipv4": "110.110.110.1/32", "ipv6": "110:110::1/128"} +NETWORK1_6 = {"ipv4": "110.110.110.100/32", "ipv6": "110:110::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} +NETWORK2_5 = {"ipv4": "220.220.220.20/32", "ipv6": "220:220::20/128"} +NETWORK2_6 = {"ipv4": "220.220.220.200/32", "ipv6": "220:220::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} + +PREFIX_LIST = { + "ipv4": ["11.11.11.1", "22.22.22.2", "22.22.22.22"], + "ipv6": ["11:11::1", "22:22::2", "22:22::22"], +} +PREFERRED_NEXT_HOP = "global" +VRF_LIST = ["RED", "BLUE", "GREEN"] +COMM_VAL_1 = "100:100" +COMM_VAL_2 = "500:500" +COMM_VAL_3 = "600:600" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo4.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_import_recursive_import_tenant_vrf_p1(request): + """ + Verify recursive import among Tenant VRFs. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R2 for vrf RED and redistribute in " + "respective BGP instance" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF RED") + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify that R2 has installed redistributed routes in vrf RED only") + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "RED"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import vrf RED's routes into vrf GREEN on R2") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "RED"}}}}) + + import_dict = { + "r2": {"bgp": [{"vrf": "GREEN", "local_as": 2, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R2, that it installs imported routes from vrf RED to vrf " + "GREEN's RIB/FIB pointing next-hop to vrf RED" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "GREEN"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r2", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("On R3 import routes from vrf GREEN to vrf default") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "GREEN"}}}}) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R3, that it installs imported routes from vrf GREEN to " + "vrf default RIB/FIB pointing next-hop to vrf GREEN. " + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": {"static_routes": [{"network": [NETWORK2_1[addr_type]]}]} + } + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("On R4 import routes from vrf default to vrf BLUE") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "default"}}}}) + + import_dict = { + "r4": {"bgp": [{"vrf": "BLUE", "local_as": 4, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on R4, that it installs imported routes from vrf default to " + "vrf BLUE RIB/FIB pointing next-hop to vrf default." + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r4": { + "static_routes": [{"network": [NETWORK2_1[addr_type]], "vrf": "BLUE"}] + } + } + result = verify_bgp_rib(tgen, addr_type, "r4", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for dut, vrf_name, vrf_import, as_num in zip( + ["r2", "r4"], ["GREEN", "BLUE"], ["RED", "default"], [2, 4] + ): + for action, value in zip(["Delete", "Re-add"], [True, False]): + step("{} the import command on {} router".format(action, dut)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": {"import": {"vrf": vrf_import, "delete": value}} + } + } + ) + + import_dict = { + dut: { + "bgp": [ + {"vrf": vrf_name, "local_as": as_num, "address_family": temp} + ] + } + } + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + static_routes = { + "r4": { + "static_routes": [ + {"network": [NETWORK2_1[addr_type]], "vrf": "BLUE"} + ] + } + } + if value: + result = verify_bgp_rib( + tgen, addr_type, "r4", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes["r4"]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, "r4", static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes["r4"]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, "r4", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-2.py b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-2.py new file mode 100644 index 0000000..d29edf5 --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-2.py @@ -0,0 +1,923 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify recursive import among Tenant VRFs. +2. Verify that dynamic import works fine between two different Tenant VRFs. + When next-hop IPs are same across all VRFs. + When next-hop IPs are different across all VRFs. +3. Verify that with multiple tenant VRFs, dynamic import works fine between + Tenant VRFs to default VRF. + When next-hop IPs and prefixes are same across all VRFs. + When next-hop IPs and prefixes are different across all VRFs. +""" + +import os +import sys +import time +import pytest +import platform +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_bgp_community_lists, + get_frr_ipv6_linklocal, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.1/32", "ipv6": "10:10::1/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} +NETWORK1_5 = {"ipv4": "110.110.110.1/32", "ipv6": "110:110::1/128"} +NETWORK1_6 = {"ipv4": "110.110.110.100/32", "ipv6": "110:110::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} +NETWORK2_5 = {"ipv4": "220.220.220.20/32", "ipv6": "220:220::20/128"} +NETWORK2_6 = {"ipv4": "220.220.220.200/32", "ipv6": "220:220::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} + +PREFIX_LIST = { + "ipv4": ["11.11.11.1", "22.22.22.2", "22.22.22.22"], + "ipv6": ["11:11::1", "22:22::2", "22:22::22"], +} +PREFERRED_NEXT_HOP = "global" +VRF_LIST = ["RED", "BLUE", "GREEN"] +COMM_VAL_1 = "100:100" +COMM_VAL_2 = "500:500" +COMM_VAL_3 = "600:600" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo4.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_import_routes_between_two_tenant_vrf_p0(request): + """ + Verify that dynamic import works fine between two different Tenant VRFs. + + When next-hop IPs are same across all VRFs. + When next-hop IPs are different across all VRFs. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R3 for each vrf and redistribute in " + "respective BGP instance" + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step("Configure static route for VRF : {}".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step( + "Verify that R3 has installed redistributed routes in respective " + "vrfs: {}".format(vrf_name) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import from vrf GREEN+BLUE into vrf RED on R3") + + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + import_dict = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on R1, that it installs all the routes(local+imported) in " + "vrf RED's RIB/FIB and doesn't get confuse with next-hop attribute, " + "as all vrfs on R1 are using same IP address for next-hop" + ) + + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + + next_hop_1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[0] + result = verify_bgp_rib( + tgen, addr_type, "r1", static_routes, next_hop=next_hop_1 + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r1", static_routes, next_hop=next_hop_1) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import vrf GREEN/BLUE/Both command from vrf RED's instance on" " R3") + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": vrf_name, "delete": True}}}} + ) + + import_dict = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that R1,R2 & R3 withdraw imported routes from vrf RED's RIB") + for dut in ["r1", "r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type], NETWORK3_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in Route table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + step("Add import vrf GREEN/BLUE/Both command from vrf RED's instance on " "R3") + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + import_dict = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r1", "r2", "r3"]: + step("Verify that {} reinstall imported routes from vrf RED's RIB".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type], NETWORK3_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Shut", "No shut"], [True, False]): + step( + "{} the neighborship between R1-R3 and R1-R2 for vrf GREEN, BLUE " + "and default".format(action) + ) + bgp_disable = {"r3": {"bgp": []}} + for vrf_name in ["GREEN", "BLUE", "default"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": {"r3-link1": {"shutdown": value}} + }, + "r2": { + "dest_link": {"r3-link1": {"shutdown": value}} + }, + } + } + } + } + ) + + bgp_disable["r3"]["bgp"].append( + {"vrf": vrf_name, "local_as": 3, "address_family": temp} + ) + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify RIB/FIB of vrf RED will be unchanged on all 3 routers") + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value, status in zip( + ["Shut", "No shut"], [True, False], ["Withdraw", "Reinstall"] + ): + step("{} the neighborship between R1-R3 and R1-R2 for vrf RED".format(action)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3-link1": {"shutdown": value}}}, + "r2": {"dest_link": {"r3-link1": {"shutdown": value}}}, + } + } + } + } + ) + + bgp_disable = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1 and R2 {} all the routes from RED vrf's RIB and" + " FIB".format(status) + ) + for dut in ["r1", "r2"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import command from router R3 and configure the same on R2") + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": vrf_name, "delete": True}}}} + ) + + import_dict = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that once import commands are removed from R3, all imported " + "routes are withdrawn from RIB/FIB of vrf RED on R1/R2/R3" + ) + + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type], NETWORK3_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + + step( + "Configure static routes on R2 for each vrf and redistribute in " + "respective BGP instance" + ) + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step("Configure static route for VRF : {}".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": vrf_name, "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Remove redistribute static route on BGP VRF : {} on r3".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + } + } + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + import_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify after import commands are re-configured on R2's vrf RED, all " + "those routes are installed again in vrf RED of R1,R2,R3" + ) + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Remove/add import vrf GREEN/BLUE/both command from vrf RED's " "instance on R2" + ) + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": vrf_name, "delete": True}}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify that R1,R2 & R3 withdraw imported routes from vrf RED's RIB") + for dut in ["r1", "r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type], NETWORK3_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert ( + result is not True + ), "Testcase {} : Failed Error {}" "Routes {} still in Route table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + + step("Add import vrf GREEN/BLUE/Both command from vrf RED's instance on " "R2") + for vrf_name in ["BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + redist_dict = { + "r2": {"bgp": [{"vrf": "RED", "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r1", "r2", "r3"]: + step("Verify that {} reinstall imported routes from vrf RED's RIB".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [NETWORK2_1[addr_type], NETWORK3_1[addr_type]], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Shut", "No shut"], [True, False]): + step( + "{} the neighborship between R2-R3 for vrf GREEN, BLUE and default".format( + action + ) + ) + bgp_disable = {"r2": {"bgp": []}} + for vrf_name in ["GREEN", "BLUE", "default"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r2-link1": {"shutdown": value}} + } + } + } + } + } + ) + + bgp_disable["r2"]["bgp"].append( + {"vrf": vrf_name, "local_as": 2, "address_family": temp} + ) + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify RIB/FIB of vrf RED will be unchanged on all 3 routers") + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value, status in zip( + ["Shut", "No shut"], [True, False], ["Withdraw", "Reinstall"] + ): + step("{} the neighborship between R2-R3 for vrf RED".format(action)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r2": {"dest_link": {"r3-link1": {"shutdown": value}}} + } + } + } + } + ) + + bgp_disable = { + "r3": {"bgp": [{"vrf": "RED", "local_as": 3, "address_family": temp}]} + } + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1 and R2 {} all the routes from RED vrf's RIB and" + " FIB".format(status) + ) + for dut in ["r1", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + "vrf": "RED", + } + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-3.py b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-3.py new file mode 100644 index 0000000..c118ffc --- /dev/null +++ b/tests/topotests/bgp_vrf_dynamic_route_leak_topo4/test_bgp_vrf_dynamic_route_leak_topo4-3.py @@ -0,0 +1,925 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP Multi-VRF Dynamic Route Leaking: +1. Verify recursive import among Tenant VRFs. +2. Verify that dynamic import works fine between two different Tenant VRFs. + When next-hop IPs are same across all VRFs. + When next-hop IPs are different across all VRFs. +3. Verify that with multiple tenant VRFs, dynamic import works fine between + Tenant VRFs to default VRF. + When next-hop IPs and prefixes are same across all VRFs. + When next-hop IPs and prefixes are different across all VRFs. +""" + +import os +import sys +import time +import pytest +import platform +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + create_static_routes, + create_prefix_lists, + create_bgp_community_lists, + get_frr_ipv6_linklocal, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.1/32", "ipv6": "10:10::1/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} +NETWORK1_5 = {"ipv4": "110.110.110.1/32", "ipv6": "110:110::1/128"} +NETWORK1_6 = {"ipv4": "110.110.110.100/32", "ipv6": "110:110::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} +NETWORK2_5 = {"ipv4": "220.220.220.20/32", "ipv6": "220:220::20/128"} +NETWORK2_6 = {"ipv4": "220.220.220.200/32", "ipv6": "220:220::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} + +PREFIX_LIST = { + "ipv4": ["11.11.11.1", "22.22.22.2", "22.22.22.22"], + "ipv6": ["11:11::1", "22:22::2", "22:22::22"], +} +PREFERRED_NEXT_HOP = "global" +VRF_LIST = ["RED", "BLUE", "GREEN"] +COMM_VAL_1 = "100:100" +COMM_VAL_2 = "500:500" +COMM_VAL_3 = "600:600" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_dynamic_route_leak_topo4.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_import_routes_between_tenant_to_default_vrf_p0(request): + """ + Verify that with multiple tenant VRFs, dynamic import works fine between + Tenant VRFs to default VRF. + + When next-hop IPs and prefixes are same across all VRFs. + When next-hop IPs and prefixes are different across all VRFs. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + reset_config_on_routers(tgen) + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Configure static routes on R3 for each vrf and redistribute in " + "respective BGP instance" + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step("Configure static route for VRF : {}".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step( + "Verify that R3 has installed redistributed routes in respective " + "vrfs: {}".format(vrf_name) + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import all tenant vrfs(GREEN+BLUE+RED) in default vrf on R3") + + for vrf_name in ["RED", "BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + redist_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on R3 that it installs all the routes(imported from tenant " + "VRFs) in default vrf. Additionally, verify that R1 & R2 also " + "receive these routes from R3 and install in default vrf, next-hop " + "pointing to R3" + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + } + ] + } + } + + for dut in ["r2", "r1"]: + next_hop_val = topo["routers"]["r3"]["links"]["{}-link4".format(dut)][ + addr_type + ].split("/")[0] + + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, next_hop=next_hop_val + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, next_hop=next_hop_val + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_bgp_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value, status in zip( + ["Remove", "Add"], [True, False], ["withdraw", "re-install"] + ): + step( + "{} import vrf GREEN/BLUE/RED/all command from default vrf " + "instance on R3".format(action) + ) + for vrf_name in ["RED", "BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": {"import": {"vrf": vrf_name, "delete": value}} + } + } + ) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1,R2 & R3 {} imported routes from GREEN/BLUE/RED/all" + " in default vrf's RIB".format(status) + ) + for dut in ["r1", "r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Shut", "No shut"], [True, False]): + step( + "{} the neighborship between R1-R3 and R1-R2 for vrf RED, GREEN " + "and BLUE".format(action) + ) + bgp_disable = {"r3": {"bgp": []}} + for vrf_name in ["RED", "GREEN", "BLUE"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r1": { + "dest_link": {"r3-link4": {"shutdown": value}} + }, + "r2": { + "dest_link": {"r3-link4": {"shutdown": value}} + }, + } + } + } + } + ) + + bgp_disable["r3"]["bgp"].append( + {"vrf": vrf_name, "local_as": 3, "address_family": temp} + ) + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that when peering is shutdown for tenant vrfs, it " + "doesn't impact the RIB/FIB of default vrf on router R1 and R2" + ) + for dut in ["r1", "r2"]: + step("Verify RIB/FIB for default vrf on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value, status in zip( + ["Shut", "No shut"], [True, False], ["Withdraw", "Reinstall"] + ): + step( + "{} the neighborship between R1-R3 and R2-R3 for default vrf".format(action) + ) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r1": {"dest_link": {"r3-link4": {"shutdown": value}}}, + "r2": {"dest_link": {"r3-link4": {"shutdown": value}}}, + } + } + } + } + ) + + bgp_disable = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1 and R2 {} all the routes from default vrf's RIB" + " and FIB".format(status) + ) + for dut in ["r1", "r2"]: + step("Verify RIB/FIB for default vrf on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import command from router R3 and configure the same on R2") + temp = {} + for vrf_name in VRF_LIST: + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": vrf_name, "delete": True}}}} + ) + + import_dict = {"r3": {"bgp": [{"local_as": 3, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that once import commands are removed from R3, all imported " + "routes are withdrawn from RIB/FIB of default vrf on R1/R2/R3" + ) + + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for default vrf on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for vrf_name, network in zip(VRF_LIST, [NETWORK1_1, NETWORK2_1, NETWORK3_1]): + step("Configure static route for VRF : {} on r2".format(vrf_name)) + for addr_type in ADDR_TYPES: + static_routes = { + "r2": { + "static_routes": [ + { + "network": [network[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r2": {"bgp": [{"vrf": vrf_name, "local_as": 2, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Remove redistribute static route on BGP VRF : {} on r3".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "redistribute": [{"redist_type": "static", "delete": True}] + } + } + } + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for vrf_name in ["RED", "BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + import_dict = {"r2": {"bgp": [{"local_as": 2, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify after import commands are re-configured on R2's vrf RED, all " + "those routes are installed again in default vrf of R1,R2,R3" + ) + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Remove import vrf RED/GREEN/BLUE/all one by one from default vrf" " on R2") + for vrf_name in ["RED", "BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"import": {"vrf": vrf_name, "delete": True}}}} + ) + + import_dict = {"r2": {"bgp": [{"local_as": 2, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1, R2 and R3 withdraws imported routes from default " + "vrf's RIB and FIB " + ) + for dut in ["r1", "r2", "r3"]: + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, result, static_routes[dut]["static_routes"][0]["network"] + ) + ) + + result = verify_rib(tgen, addr_type, dut, static_routes, expected=False) + assert result is not True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Add import vrf RED/GREEN/BLUE/all one by one from default vrf on R2") + for vrf_name in ["RED", "BLUE", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": vrf_name}}}}) + + import_dict = {"r2": {"bgp": [{"local_as": 2, "address_family": temp}]}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut in ["r1", "r2", "r3"]: + step("Verify that {} reinstall imported routes from vrf RED's RIB".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value in zip(["Shut", "No shut"], [True, False]): + step( + "{} the neighborship between R2-R3 for vrf GREEN, BLUE and RED".format( + action + ) + ) + bgp_disable = {"r2": {"bgp": []}} + for vrf_name in ["GREEN", "BLUE", "RED"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": {"r2-link4": {"shutdown": value}} + } + } + } + } + } + ) + + bgp_disable["r2"]["bgp"].append( + {"vrf": vrf_name, "local_as": 2, "address_family": temp} + ) + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify RIB/FIB of vrf RED will be unchanged on all 3 routers") + for dut in ["r1", "r2", "r3"]: + step("Verify RIB/FIB for vrf RED on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + for action, value, status in zip( + ["Shut", "No shut"], [True, False], ["Withdraw", "Reinstall"] + ): + step("{} the neighborship between R2-R3 for default vrf".format(action)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "neighbor": { + "r3": {"dest_link": {"r2-link4": {"shutdown": value}}} + } + } + } + } + ) + + bgp_disable = {"r2": {"bgp": [{"local_as": 2, "address_family": temp}]}} + result = create_router_bgp(tgen, topo, bgp_disable) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R1 and R2 {} all the routes from default vrfs RIB and" + " FIB".format(status) + ) + for dut in ["r1", "r3"]: + step("Verify RIB/FIB for default vrf on {}".format(dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [ + NETWORK1_1[addr_type], + NETWORK2_1[addr_type], + NETWORK3_1[addr_type], + ], + "next_hop": "blackhole", + } + ] + } + } + + if value: + result = verify_bgp_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \nError {}\n" + "Routes {} still in BGP table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + + result = verify_rib( + tgen, addr_type, dut, static_routes, expected=False + ) + assert result is not True, ( + "Testcase {} : Failed Error {}" + "Routes {} still in Route table".format( + tc_name, + result, + static_routes[dut]["static_routes"][0]["network"], + ) + ) + else: + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf new file mode 100644 index 0000000..eb2563d --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf @@ -0,0 +1,22 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log commands +! +router bgp 65002 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor eth0 interface + neighbor eth0 remote-as external + neighbor eth0 timers connect 1 + ! + address-family ipv4 unicast + neighbor eth0 activate + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf new file mode 100644 index 0000000..a163295 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf @@ -0,0 +1,13 @@ +log file zebra.log +! +hostname ce1 +! +interface lo + ip address 172.16.0.1/32 +! +interface eth0 + ipv6 nd ra-interval 1 + no ipv6 nd suppress-ra +! +line vty +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf new file mode 100644 index 0000000..bbc5ae5 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf @@ -0,0 +1,40 @@ +frr defaults traditional +! +hostname pe1 +password zebra +! +log stdout notifications +log commands +! +router bgp 65001 + bgp router-id 192.0.2.1 + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor eth0 interface + neighbor eth0 remote-as external + neighbor eth0 timers connect 1 + ! + address-family ipv4 unicast + neighbor eth0 activate + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + rd vpn export 65001:20 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json new file mode 100644 index 0000000..9516016 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json @@ -0,0 +1,32 @@ +{ + "vrfName": "default", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "routeDistinguishers": { + "65001:10": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json new file mode 100644 index 0000000..768bffb --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json @@ -0,0 +1,32 @@ +{ + "vrfName": "vrf10", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "nexthops": [ + { + "hostname": "ce1", + "afi": "ipv6", + "scope": "global", + "used": true + }, + { + "hostname": "ce1", + "afi": "ipv6", + "scope": "link-local" + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json new file mode 100644 index 0000000..1e93715 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json @@ -0,0 +1,34 @@ +{ + "vrfName": "vrf20", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv6", + "scope": "global", + "used": true + }, + { + "hostname": "pe1", + "afi": "ipv6", + "scope": "link-local" + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf new file mode 100644 index 0000000..d40041a --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf @@ -0,0 +1,10 @@ +log file zebra.log +! +hostname pe1 +! +interface eth0 vrf vrf10 + ipv6 nd ra-interval 1 + no ipv6 nd suppress-ra +! +line vty +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py b/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py new file mode 100755 index 0000000..b208192 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright (c) 2022, LINE Corporation +# Authored by Ryoga Saito +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("pe1") + tgen.add_router("ce1") + + tgen.add_link(tgen.gears["pe1"], tgen.gears["ce1"], "eth0", "eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.gears["pe1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["pe1"].run("ip link set vrf10 up") + tgen.gears["pe1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["pe1"].run("ip link set vrf20 up") + tgen.gears["pe1"].run("ip link set eth0 master vrf10") + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(path): + try: + with open(path, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(path) + + +def check_vrf10_rib(output): + expected = open_json_file("%s/pe1/results/vrf10_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_default_vpn_rib(output): + expected = open_json_file("%s/pe1/results/default_ipv4_vpn.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_vrf20_rib(output): + expected = open_json_file("%s/pe1/results/vrf20_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check(name, command, checker): + tgen = get_topogen() + router = tgen.gears[name] + + def _check(): + try: + return checker(router.vtysh_cmd(command)) + except: + return False + + logger.info('[+] check {} "{}"'.format(name, command)) + _, result = topotest.run_and_expect(_check, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check("pe1", "show bgp vrf vrf10 ipv4 unicast json", check_vrf10_rib) + check("pe1", "show bgp ipv4 vpn json", check_default_vpn_rib) + check("pe1", "show bgp vrf vrf20 ipv4 unicast json", check_vrf20_rib) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/__init__.py b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/bgpd.conf b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/bgpd.conf new file mode 100644 index 0000000..9e0a4a8 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/bgpd.conf @@ -0,0 +1,31 @@ +! +router bgp 65500 +exit +! +router bgp 65500 vrf vrf1 + bgp router-id 10.0.0.1 + no bgp network import-check + address-family ipv4 unicast + network 192.168.100.100/32 route-map rm + rd vpn export 65500:10001 + rt vpn import 65500:10000 65500:10990 + rt vpn export 65500:10000 + export vpn + import vpn + exit-address-family +exit +! +router bgp 65500 vrf vrf2 + address-family ipv4 unicast + rd vpn export 65500:11001 + rt vpn import 65500:11000 65500:11990 + rt vpn export 65500:11000 + export vpn + import vpn + exit-address-family +exit +! +route-map rm permit 10 + set extcommunity rt 65500:10100 65500:11990 +exit +! diff --git a/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/zebra.conf b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/zebra.conf new file mode 100644 index 0000000..22a26ac --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 + ip address 10.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/test_bgp_vrf_leaking_rt_change_route_maps.py b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/test_bgp_vrf_leaking_rt_change_route_maps.py new file mode 100644 index 0000000..42cbf1e --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_rt_change_route_maps/test_bgp_vrf_leaking_rt_change_route_maps.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2022 by +# Donatas Abraitis +# + +""" +If we overwrite import/export RT list via route-maps or even flush by using +`set extcommunity none`, then we must withdraw old paths from VRFs to avoid +stale paths. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("r1") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router = tgen.gears["r1"] + router.cmd_raises("ip link add vrf1 type vrf table 10") + router.cmd_raises("ip link set up dev vrf1") + router.cmd_raises("ip link add vrf2 type vrf table 20") + router.cmd_raises("ip link set up dev vrf2") + router.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + router.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + router.start() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_vrf_leaking_rt_change_route_maps(): + tgen = get_topogen() + + router = tgen.gears["r1"] + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_path(): + output = json.loads(router.vtysh_cmd("show bgp vrf vrf2 ipv4 unicast json")) + expected = {"routes": {"192.168.100.100/32": [{"nhVrfName": "vrf1"}]}} + return topotest.json_cmp(output, expected) + + step("Initial converge") + test_func = functools.partial(_bgp_check_path) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Can't see 192.168.100.100/32 leaked from vrf1 into vrf2." + + step("Overwrite RT list (remove rt 65500:11990 from route-map)") + router.vtysh_cmd( + """ + config terminal + route-map rm permit 10 + set extcommunity rt 65500:10100 + exit + """ + ) + + step("Check if 192.168.100.100/32 was removed from vrf2") + test_func = functools.partial(_bgp_check_path) + _, result = topotest.run_and_expect(test_func, not None, count=20, wait=0.5) + assert result is not None, "192.168.100.100/32 still exists in vrf2 as stale." + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo1.json b/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo1.json new file mode 100644 index 0000000..b1d7d09 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo1.json @@ -0,0 +1,563 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["11.11.11.1/32", "11.11.11.11/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["11:11::1/128", "11:11::11/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["10.10.10.10/32", "10.10.10.100/32"], + "next_hop":"Null0" + }, + { + "network": ["10:10::10/128", "10:10::100/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "ISR"}, + "r3-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "ISR", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "next_hop_self": true, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["22.22.22.2/32", "22.22.22.22/32"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["22:22::2/128", "22:22::22/128"], + "next_hop":"Null0", + "vrf": "ISR" + }, + { + "network": ["20.20.20.20/32", "20.20.20.200/32"], + "next_hop":"Null0" + }, + { + "network": ["20:20::20/128", "20:20::200/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "300", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["30.30.30.3/32", "30.30.30.30/32"], + "next_hop":"Null0" + }, + { + "network": ["30:30::3/128", "30:30::30/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "bgp": + [ + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "400", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network": ["40.40.40.4/32", "40.40.40.40/32"], + "next_hop":"Null0" + }, + { + "network": ["40:40::4/128", "40:40::40/128"], + "next_hop":"Null0" + }, + { + "network": ["50.50.50.5/32", "50.50.50.50/32"], + "next_hop":"Null0" + }, + { + "network": ["50:50::5/128", "50:50::50/128"], + "next_hop":"Null0" + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + } + } +} diff --git a/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo2.json b/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo2.json new file mode 100644 index 0000000..0b13882 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_best_path_test/bgp_vrf_lite_best_path_topo2.json @@ -0,0 +1,1088 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "r3-link1": {"ipv4": "192.168.1.1/24", "ipv6": "fd00:0:0:1::1/120", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "192.168.1.1/24", "ipv6": "fd00:0:0:1::1/120", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "1", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r2": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r3": { + "links": { + "r1-link1": {"ipv4": "192.168.1.2/24", "ipv6": "fd00:0:0:1::2/120", "vrf": "RED"}, + "r1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r1-link3": {"ipv4": "192.168.1.2/24", "ipv6": "fd00:0:0:1::2/120", "vrf": "GREEN"}, + "r1-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r2-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r4-link4": {"ipv4": "auto", "ipv6": "auto"}, + "r5-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r5-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r5-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r5-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": {} + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link1": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": {} + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link2": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link2": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link3": { + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link3": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r2": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r4": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + }, + "r5": { + "dest_link": { + "r3-link4": { + "keepalivetimer": 1, + "holddowntimer": 3, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + ], + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + } + }, + "r4": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r4-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r5": { + "links": { + "r3-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r3-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r3-link3": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "r3-link4": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + {"name": "RED", "id": "1"}, + {"name": "BLUE", "id": "2"}, + {"name": "GREEN", "id": "3"} + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "3", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link3": {} + } + } + } + } + } + } + }, + { + "local_as": "3", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r5-link4": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo1.py b/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo1.py new file mode 100644 index 0000000..f352196 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo1.py @@ -0,0 +1,879 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP VRF Lite: + +1. Verify BGP best path selection algorithm works fine when +routes are imported from ISR to default vrf and vice versa. +""" + +import os +import sys +import time +import pytest +import platform + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + step, + create_route_maps, + create_prefix_lists, + check_router_status, + get_frr_ipv6_linklocal, + shutdown_bringup_interface, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_community, + verify_bgp_rib, + clear_bgp, + verify_best_path_as_per_bgp_attribute, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.10/32", "ipv6": "10:10::10/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} +NETWORK3_3 = {"ipv4": "50.50.50.5/32", "ipv6": "50:50::5/128"} +NETWORK3_4 = {"ipv4": "50.50.50.50/32", "ipv6": "50:50::50/128"} + +NETWORK4_1 = {"ipv4": "40.40.40.4/32", "ipv6": "40:40::4/128"} +NETWORK4_2 = {"ipv4": "40.40.40.40/32", "ipv6": "40:40::40/128"} +NETWORK4_3 = {"ipv4": "50.50.50.5/32", "ipv6": "50:50::5/128"} +NETWORK4_4 = {"ipv4": "50.50.50.50/32", "ipv6": "50:50::50/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +LOOPBACK_1 = { + "ipv4": "10.0.0.7/24", + "ipv6": "fd00:0:0:1::7/64", + "ipv4_mask": "255.255.255.0", + "ipv6_mask": None, +} +LOOPBACK_2 = { + "ipv4": "10.0.0.16/24", + "ipv6": "fd00:0:0:3::5/64", + "ipv4_mask": "255.255.255.0", + "ipv6_mask": None, +} +PREFERRED_NEXT_HOP = "global" + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_lite_best_path_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def disable_route_map_to_prefer_global_next_hop(tgen, topo): + """ + This API is to remove prefer global route-map applied on neighbors + + Parameter: + ---------- + * `tgen` : Topogen object + * `topo` : Input JSON data + + Returns: + -------- + True/errormsg + + """ + + logger.info("Remove prefer-global rmap applied on neighbors") + input_dict = { + "r1": { + "bgp": [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r2": { + "bgp": [ + { + "local_as": "100", + "vrf": "ISR", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "100", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "r2-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r3": { + "bgp": [ + { + "local_as": "300", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "300", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + "r4": { + "bgp": [ + { + "local_as": "400", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r4-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + { + "local_as": "400", + "address_family": { + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r4-link1": { + "route_maps": [ + { + "name": "rmap_global", + "direction": "in", + "delete": True, + } + ] + } + } + } + } + } + } + }, + }, + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + return True + + +def test_bgp_best_path_with_dynamic_import_p0(request): + """ + 1.5.6. Verify BGP best path selection algorithm works fine when + routes are imported from ISR to default vrf and vice versa. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + build_config_from_json(tgen, topo) + + if tgen.routers_have_failure(): + check_router_status(tgen) + + for addr_type in ADDR_TYPES: + step( + "Redistribute configured static routes into BGP process" " on R1/R2 and R3" + ) + + input_dict_1 = {} + DUT = ["r1", "r2", "r3", "r4"] + VRFS = ["ISR", "ISR", "default", "default"] + AS_NUM = [100, 100, 300, 400] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_1.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_1) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Import from default vrf into vrf ISR on R1 and R2 as below") + + input_dict_vrf = {} + DUT = ["r1", "r2"] + VRFS = ["ISR", "ISR"] + AS_NUM = [100, 100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_vrf.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "default"}}} + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_vrf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_default = {} + DUT = ["r1", "r2"] + VRFS = ["default", "default"] + AS_NUM = [100, 100] + + for dut, vrf, as_num in zip(DUT, VRFS, AS_NUM): + temp = {dut: {"bgp": []}} + input_dict_default.update(temp) + + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + addr_type: {"unicast": {"import": {"vrf": "ISR"}}} + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_default) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify ECMP/Next-hop/Imported routes Vs Locally originated " + "routes/eBGP routes vs iBGP routes --already covered in almost" + " all tests" + ) + + for addr_type in ADDR_TYPES: + step("Verify Pre-emption") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r4_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Shutdown interface connected to r1 from r4:") + shutdown_bringup_interface(tgen, "r4", intf_r4_r1, False) + + for addr_type in ADDR_TYPES: + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + step("Verify next-hop is changed") + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r3_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Bringup interface connected to r1 from r4:") + shutdown_bringup_interface(tgen, "r4", intf_r4_r1, True) + + for addr_type in ADDR_TYPES: + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_3[addr_type]]}]} + } + + intf_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"]["interface"] + intf_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"]["interface"] + + if addr_type == "ipv6" and "link_local" in PREFERRED_NEXT_HOP: + nh_r3_r1 = get_frr_ipv6_linklocal(tgen, "r3", intf=intf_r3_r1) + nh_r4_r1 = get_frr_ipv6_linklocal(tgen, "r4", intf=intf_r4_r1) + else: + nh_r3_r1 = topo["routers"]["r3"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + nh_r4_r1 = topo["routers"]["r4"]["links"]["r1-link1"][addr_type].split("/")[ + 0 + ] + + step("Verify next-hop is not chnaged aftr shutdown:") + result = verify_bgp_rib( + tgen, addr_type, "r1", input_routes_r3, next_hop=[nh_r3_r1] + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Active-Standby scenario(as-path prepend and Local pref)") + + for addr_type in ADDR_TYPES: + step("Create prefix-list") + + input_dict_pf = { + "r1": { + "prefix_lists": { + addr_type: { + "pf_ls_{}".format(addr_type): [ + { + "seqid": 10, + "network": NETWORK3_4[addr_type], + "action": "permit", + } + ] + } + } + } + } + result = create_prefix_lists(tgen, input_dict_pf) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set localpref 500") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 500}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Create route-map to match prefix-list and set localpref 600") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 20, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 600}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_rma = { + "r1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + addr_type: { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_PATH1_{}".format( + addr_type + ), + "direction": "in", + } + ] + } + } + }, + "r4": { + "dest_link": { + "r1-link1": { + "route_maps": [ + { + "name": "rmap_PATH2_{}".format( + addr_type + ), + "direction": "in", + } + ] + } + } + }, + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rma) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + dut = "r1" + attribute = "locPrf" + + for addr_type in ADDR_TYPES: + step("Verify bestpath is installed as per highest localpref") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set localpref 700") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH1_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 10, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": {"locPrf": 700}, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Verify bestpath is changed as per highest localpref") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + step("Create route-map to match prefix-list and set as-path prepend") + + input_dict_rm = { + "r1": { + "route_maps": { + "rmap_PATH2_{}".format(addr_type): [ + { + "action": "permit", + "seq_id": 20, + "match": { + addr_type: { + "prefix_lists": "pf_ls_{}".format(addr_type) + } + }, + "set": { + "localpref": 700, + "path": {"as_num": "111", "as_action": "prepend"}, + }, + } + ] + } + } + } + + result = create_route_maps(tgen, input_dict_rm) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + attribute = "path" + + for addr_type in ADDR_TYPES: + step("Verify bestpath is changed as per shortest as-path") + + input_routes_r3 = { + "r3": {"static_routes": [{"network": [NETWORK3_4[addr_type]]}]} + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, dut, input_routes_r3, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo2.py b/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo2.py new file mode 100644 index 0000000..3cb3180 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_best_path_test/test_bgp_vrf_lite_best_path_topo2.py @@ -0,0 +1,534 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2021 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test BGP VRF Lite: +1. Verify that locally imported routes are selected as best path over eBGP imported routes + peers. +2. Verify ECMP for imported routes from different VRFs. +""" + +import os +import sys +import time +import pytest +import platform +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topotest import version_cmp + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_static_routes, + check_router_status, + apply_raw_config, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_bgp_rib, + verify_bgp_bestpath, +) +from lib.topojson import build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "11.11.11.1/32", "ipv6": "11:11::1/128"} +NETWORK1_2 = {"ipv4": "11.11.11.11/32", "ipv6": "11:11::11/128"} +NETWORK1_3 = {"ipv4": "10.10.10.1/32", "ipv6": "10:10::1/128"} +NETWORK1_4 = {"ipv4": "10.10.10.100/32", "ipv6": "10:10::100/128"} +NETWORK1_5 = {"ipv4": "110.110.110.1/32", "ipv6": "110:110::1/128"} +NETWORK1_6 = {"ipv4": "110.110.110.100/32", "ipv6": "110:110::100/128"} + +NETWORK2_1 = {"ipv4": "22.22.22.2/32", "ipv6": "22:22::2/128"} +NETWORK2_2 = {"ipv4": "22.22.22.22/32", "ipv6": "22:22::22/128"} +NETWORK2_3 = {"ipv4": "20.20.20.20/32", "ipv6": "20:20::20/128"} +NETWORK2_4 = {"ipv4": "20.20.20.200/32", "ipv6": "20:20::200/128"} +NETWORK2_5 = {"ipv4": "220.220.220.20/32", "ipv6": "220:220::20/128"} +NETWORK2_6 = {"ipv4": "220.220.220.200/32", "ipv6": "220:220::200/128"} + +NETWORK3_1 = {"ipv4": "30.30.30.3/32", "ipv6": "30:30::3/128"} +NETWORK3_2 = {"ipv4": "30.30.30.30/32", "ipv6": "30:30::30/128"} + +PREFIX_LIST = { + "ipv4": ["11.11.11.1", "22.22.22.2", "22.22.22.22"], + "ipv6": ["11:11::1", "22:22::2", "22:22::22"], +} +PREFERRED_NEXT_HOP = "global" +VRF_LIST = ["RED", "BLUE", "GREEN"] +COMM_VAL_1 = "100:100" +COMM_VAL_2 = "500:500" +COMM_VAL_3 = "600:600" +BESTPATH = {"ipv4": "0.0.0.0", "ipv6": "::"} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/bgp_vrf_lite_best_path_topo2.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Run these tests for kernel version 4.19 or above + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + "BGP vrf dynamic route leak tests will not run " + '(have kernel "{}", but it requires >= 4.19)'.format(platform.release()) + ) + pytest.skip(error_msg) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def test_dynamic_import_ecmp_imported_routed_diffrent_vrfs_p0(request): + """ + Verify ECMP for imported routes from different VRFs. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step( + "Configure same static routes in tenant vrfs RED and GREEN on router " + "R3 and redistribute in respective BGP process" + ) + + for vrf_name in ["RED", "GREEN"]: + for addr_type in ADDR_TYPES: + if vrf_name == "GREEN": + next_hop_vrf = topo["routers"]["r1"]["links"]["r3-link3"][ + addr_type + ].split("/")[0] + else: + next_hop_vrf = topo["routers"]["r2"]["links"]["r3-link1"][ + addr_type + ].split("/")[0] + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": next_hop_vrf, + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + {addr_type: {"unicast": {"redistribute": [{"redist_type": "static"}]}}} + ) + + redist_dict = { + "r3": {"bgp": [{"vrf": vrf_name, "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that configured static routes are installed in respective " + "BGP table for vrf RED & GREEN" + ) + for vrf_name in ["RED", "GREEN"]: + for addr_type in ADDR_TYPES: + if vrf_name == "GREEN": + next_hop_vrf = topo["routers"]["r1"]["links"]["r3-link3"][ + addr_type + ].split("/")[0] + else: + next_hop_vrf = topo["routers"]["r2"]["links"]["r3-link1"][ + addr_type + ].split("/")[0] + static_routes = { + "r3": { + "static_routes": [ + {"network": [NETWORK1_1[addr_type]], "vrf": vrf_name} + ] + } + } + + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import vrf RED and GREEN into default vrf and Configure ECMP") + bgp_val = [] + for vrf_name in ["RED", "GREEN"]: + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": { + "import": {"vrf": vrf_name}, + "maximum_paths": {"ebgp": 2}, + } + } + } + ) + + bgp_val.append({"local_as": 3, "address_family": temp}) + + import_dict = {"r3": {"bgp": bgp_val}} + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure bgp bestpath on router r3") + r3_raw_config = { + "r3": {"raw_config": ["router bgp 3", "bgp bestpath as-path multipath-relax"]} + } + result = apply_raw_config(tgen, r3_raw_config) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that routes are imported with two different next-hop vrfs " + "and IPs. Additionally R3 must do ECMP for both the routes." + ) + + for addr_type in ADDR_TYPES: + next_hop_vrf = [ + topo["routers"]["r2"]["links"]["r3-link1"][addr_type].split("/")[0], + topo["routers"]["r1"]["links"]["r3-link3"][addr_type].split("/")[0], + ] + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + } + ] + } + } + + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step( + "Now change the next-hop of static routes in vrf RED and GREEN to " + "same IP address" + ) + for addr_type in ADDR_TYPES: + next_hop_vrf = topo["routers"]["r1"]["links"]["r3-link3"][addr_type].split("/")[ + 0 + ] + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + "next_hop": next_hop_vrf, + "vrf": "RED", + }, + { + "network": [NETWORK1_1[addr_type]], + "next_hop": topo["routers"]["r2"]["links"]["r3-link1"][ + addr_type + ].split("/")[0], + "vrf": "RED", + "delete": True, + }, + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that now routes are imported with two different next-hop " + "vrfs but same IPs. Additionally R3 must do ECMP for both the routes" + ) + + for addr_type in ADDR_TYPES: + next_hop_vrf = [ + topo["routers"]["r1"]["links"]["r3-link3"][addr_type].split("/")[0], + topo["routers"]["r1"]["links"]["r3-link3"][addr_type].split("/")[0], + ] + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_1[addr_type]], + } + ] + } + } + + result = verify_bgp_rib( + tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf + ) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes, next_hop=next_hop_vrf) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_locally_imported_routes_selected_as_bestpath_over_ebgp_imported_routes_p0( + request, +): + """ + Verify ECMP for imported routes from different VRFs. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + if tgen.routers_have_failure(): + check_router_status(tgen) + reset_config_on_routers(tgen) + + step( + "Configure same static routes on R2 and R3 vrfs and redistribute in BGP " + "for GREEN and RED vrf instances" + ) + for dut, network in zip( + ["r2", "r3"], [[NETWORK1_1, NETWORK1_2], [NETWORK1_1, NETWORK1_2]] + ): + for vrf_name, network_vrf in zip(["RED", "GREEN"], network): + step("Configure static route for VRF : {} on {}".format(vrf_name, dut)) + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [network_vrf[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + + result = create_static_routes(tgen, static_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for dut, as_num in zip(["r2", "r3"], ["2", "3"]): + for vrf_name in ["RED", "GREEN"]: + step("Redistribute static route on BGP VRF : {}".format(vrf_name)) + temp = {} + for addr_type in ADDR_TYPES: + temp.update( + { + addr_type: { + "unicast": {"redistribute": [{"redist_type": "static"}]} + } + } + ) + + redist_dict = { + dut: { + "bgp": [ + {"vrf": vrf_name, "local_as": as_num, "address_family": temp} + ] + } + } + + result = create_router_bgp(tgen, topo, redist_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that R2 and R3 has installed redistributed routes in default " + "and RED vrfs and GREEN respectively:" + ) + for dut, network in zip( + ["r2", "r3"], [[NETWORK1_1, NETWORK1_2], [NETWORK1_1, NETWORK1_2]] + ): + for vrf_name, network_vrf in zip(["RED", "GREEN"], network): + for addr_type in ADDR_TYPES: + static_routes = { + dut: { + "static_routes": [ + { + "network": [network_vrf[addr_type]], + "next_hop": "blackhole", + "vrf": vrf_name, + } + ] + } + } + result = verify_bgp_rib(tgen, addr_type, dut, static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + step("Import vrf RED's route in vrf GREEN on R3") + temp = {} + for addr_type in ADDR_TYPES: + temp.update({addr_type: {"unicast": {"import": {"vrf": "RED"}}}}) + + import_dict = { + "r3": {"bgp": [{"vrf": "GREEN", "local_as": 3, "address_family": temp}]} + } + + result = create_router_bgp(tgen, topo, import_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that locally imported routes are installed over eBGP imported" + " routes from VRF RED into VRF GREEN" + ) + for addr_type in ADDR_TYPES: + static_routes = { + "r3": { + "static_routes": [ + { + "network": [NETWORK1_2[addr_type]], + "next_hop": "blackhole", + "vrf": "GREEN", + } + ] + } + } + + input_routes = { + "r3": { + addr_type: [ + { + "network": NETWORK1_2[addr_type], + "bestpath": BESTPATH[addr_type], + "vrf": "GREEN", + } + ] + } + } + + result = verify_bgp_bestpath(tgen, addr_type, input_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r3", static_routes) + assert result is True, "Testcase {} : Failed \n Error {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/__init__.py b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/bgpd.conf b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/bgpd.conf new file mode 100644 index 0000000..0033bc2 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/bgpd.conf @@ -0,0 +1,15 @@ +router bgp 101 vrf r1-cust1 + bgp router-id 10.254.254.1 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r1-eth0 interface peer-group r2g + neighbor r1-eth0 timers 3 10 + address-family ipv4 unicast + redistribute connected + exit-address-family + address-family ipv6 unicast + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv4_routes.json b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv4_routes.json new file mode 100644 index 0000000..1c7500b --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv4_routes.json @@ -0,0 +1,44 @@ +{ + "10.254.254.2/32": [ + { + "prefix": "10.254.254.2/32", + "protocol": "bgp", + "vrfName": "r1-cust1", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "r1-eth0", + "active": true + } + ] + } + ], + "10.254.254.1/32": [ + { + "prefix": "10.254.254.1/32", + "protocol": "connected", + "vrfName": "r1-cust1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "loop1", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv6_routes.json b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv6_routes.json new file mode 100644 index 0000000..0e1de87 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/ipv6_routes.json @@ -0,0 +1,37 @@ +{ + "2001:db8:1::/64": [ + { + "prefix": "2001:db8:1::/64", + "protocol": "bgp", + "vrfName": "r1-cust1", + "distance": 20, + "metric": 0, + "nexthops": [ + { + "afi": "ipv6", + "interfaceName": "r1-eth0", + "active": true + } + ] + }, + { + "prefix": "2001:db8:1::/64", + "protocol": "connected", + "vrfName": "r1-cust1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r1-eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/zebra.conf b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/zebra.conf new file mode 100644 index 0000000..74359a5 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r1/zebra.conf @@ -0,0 +1,9 @@ +! debug zebra packet recv +! debug zebra packet send +log stdout +interface loop1 vrf r1-cust1 + ip address 10.254.254.1/32 +! +interface r1-eth0 vrf r1-cust1 + ipv6 address 2001:db8:1::1/64 +! diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/bgpd.conf b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/bgpd.conf new file mode 100644 index 0000000..183157e --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/bgpd.conf @@ -0,0 +1,18 @@ +router bgp 102 vrf r2-cust1 + bgp router-id 10.254.254.2 + no bgp ebgp-requires-policy + neighbor r2g peer-group + neighbor r2g remote-as external + neighbor r2g bfd + neighbor r2-eth0 interface peer-group r2g + neighbor r2-eth0 timers 3 10 + ! + address-family ipv4 unicast + redistribute connected + exit-address-family + ! + address-family ipv6 unicast + redistribute connected + neighbor r2g activate + exit-address-family +! diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv4_routes.json b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv4_routes.json new file mode 100644 index 0000000..4d7b289 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv4_routes.json @@ -0,0 +1,44 @@ +{ + "10.254.254.2/32": [ + { + "prefix": "10.254.254.2/32", + "protocol": "connected", + "vrfName": "r2-cust1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "loop1", + "active": true + } + ] + } + ], + "10.254.254.1/32": [ + { + "prefix": "10.254.254.1/32", + "protocol": "bgp", + "vrfName": "r2-cust1", + "selected": true, + "destSelected": true, + "distance": 20, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "afi": "ipv6", + "interfaceName": "r2-eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv6_routes.json b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv6_routes.json new file mode 100644 index 0000000..805b57d --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/ipv6_routes.json @@ -0,0 +1,23 @@ +{ + "2001:db8:1::/64": [ + { + "prefix": "2001:db8:1::/64", + "protocol": "connected", + "vrfName": "r2-cust1", + "selected": true, + "destSelected": true, + "distance": 0, + "metric": 0, + "installed": true, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "r2-eth0", + "active": true + } + ] + } + ] +} diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/zebra.conf b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/zebra.conf new file mode 100644 index 0000000..c3795ab --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/r2/zebra.conf @@ -0,0 +1,9 @@ +ip forwarding +ipv6 forwarding +! +interface loop1 vrf r2-cust1 + ip address 10.254.254.2/32 +! +interface r2-eth0 vrf r2-cust1 + ipv6 address 2001:db8:1::2/64 +! diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.dot b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.dot new file mode 100644 index 0000000..da67c29 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.dot @@ -0,0 +1,44 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="bfd-topo2"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + sw1 [ + shape=oval, + label="sw1\n2001:db8:1::/64", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- sw1 [label="eth0"]; + r2 -- sw1 [label="eth0"]; + +} diff --git a/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.py b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.py new file mode 100644 index 0000000..32239e9 --- /dev/null +++ b/tests/topotests/bgp_vrf_lite_ipv6_rtadv/test_bgp_vrf_lite_ipv6_rtadv.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_ipv6_rtadv.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2019 by 6WIND +# + +""" + test_bgp_ipv6_rtadv.py: Test the FRR BGP daemon with BGP IPv6 interface + with route advertisements on a separate netns. +""" + +import os +import sys +import json +from functools import partial +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + # Create 2 routers. + tgen.add_router("r1") + tgen.add_router("r2") + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + +def setup_module(mod): + "Sets up the pytest environment" + + # Required linux kernel version for this suite to run. + result = required_linux_kernel_version("5.0") + if result is not True: + pytest.skip("Kernel requirements are not met") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + logger.info("Testing with VRF Lite support") + + cmds = [ + "ip link add {0}-cust1 type vrf table 1001", + "ip link add loop1 type dummy", + "ip link set loop1 master {0}-cust1", + "ip link set {0}-eth0 master {0}-cust1", + ] + + for rname, router in router_list.items(): + for cmd in cmds: + output = tgen.net[rname].cmd(cmd.format(rname)) + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + tgen.stop_topology() + + +def test_protocols_convergence(): + """ + Assert that all protocols have converged + statuses as they depend on it. + """ + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Check IPv4 routing tables. + logger.info("Checking IPv4 routes for convergence") + + for router in tgen.routers().values(): + json_file = "{}/{}/ipv4_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ip route vrf {}-cust1 json".format(router.name), + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=160, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + # Check IPv6 routing tables. + logger.info("Checking IPv6 routes for convergence") + for router in tgen.routers().values(): + json_file = "{}/{}/ipv6_routes.json".format(CWD, router.name) + if not os.path.isfile(json_file): + logger.info("skipping file {}".format(json_file)) + continue + + expected = json.loads(open(json_file).read()) + test_func = partial( + topotest.router_json_cmp, + router, + "show ipv6 route vrf {}-cust1 json".format(router.name), + expected, + ) + _, result = topotest.run_and_expect(test_func, None, count=160, wait=0.5) + assertmsg = '"{}" JSON output mismatches'.format(router.name) + assert result is None, assertmsg + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_md5_peering/__init__.py b/tests/topotests/bgp_vrf_md5_peering/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vrf_md5_peering/exabgp.env b/tests/topotests/bgp_vrf_md5_peering/exabgp.env new file mode 100644 index 0000000..28e6423 --- /dev/null +++ b/tests/topotests/bgp_vrf_md5_peering/exabgp.env @@ -0,0 +1,53 @@ +[exabgp.api] +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_vrf_md5_peering/peer1/exabgp.cfg b/tests/topotests/bgp_vrf_md5_peering/peer1/exabgp.cfg new file mode 100644 index 0000000..11a827a --- /dev/null +++ b/tests/topotests/bgp_vrf_md5_peering/peer1/exabgp.cfg @@ -0,0 +1,13 @@ +neighbor 10.0.0.1 { + router-id 10.0.0.2; + local-address 10.0.0.2; + local-as 65001; + peer-as 65534; + md5-password test123; + + static { + route 192.168.100.1/32 { + next-hop 10.0.0.2; + } + } +} diff --git a/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf b/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf new file mode 100644 index 0000000..9f2ee19 --- /dev/null +++ b/tests/topotests/bgp_vrf_md5_peering/r1/bgpd.conf @@ -0,0 +1,11 @@ +! +!debug bgp neighbor +! +router bgp 65534 vrf public + bgp router-id 10.0.0.1 + no bgp ebgp-requires-policy + neighbor 10.0.0.2 remote-as external + neighbor 10.0.0.2 timers 3 10 + neighbor 10.0.0.2 timers connect 1 + neighbor 10.0.0.2 password test123 +! diff --git a/tests/topotests/bgp_vrf_md5_peering/r1/zebra.conf b/tests/topotests/bgp_vrf_md5_peering/r1/zebra.conf new file mode 100644 index 0000000..0c183ae --- /dev/null +++ b/tests/topotests/bgp_vrf_md5_peering/r1/zebra.conf @@ -0,0 +1,6 @@ +! +interface r1-eth0 vrf public + ip address 10.0.0.1/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_vrf_md5_peering/test_bgp_vrf_md5_peering.py b/tests/topotests/bgp_vrf_md5_peering/test_bgp_vrf_md5_peering.py new file mode 100644 index 0000000..eefe586 --- /dev/null +++ b/tests/topotests/bgp_vrf_md5_peering/test_bgp_vrf_md5_peering.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis +# + +""" +Test if BGP MD5 basic authentication works per-VRF. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + r1 = tgen.add_router("r1") + peer1 = tgen.add_exabgp_peer("peer1", ip="10.0.0.2", defaultRoute="via 10.0.0.1") + + switch = tgen.add_switch("s1") + switch.add_link(r1) + switch.add_link(peer1) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + r1 = tgen.gears["r1"] + r1.load_config(TopoRouter.RD_ZEBRA, os.path.join(CWD, "r1/zebra.conf")) + r1.load_config(TopoRouter.RD_BGP, os.path.join(CWD, "r1/bgpd.conf")) + r1.start() + + peer = tgen.gears["peer1"] + peer.start(os.path.join(CWD, "peer1"), os.path.join(CWD, "exabgp.env")) + + # VRF 'public' + r1.cmd_raises("ip link add public type vrf table 1001") + r1.cmd_raises("ip link set up dev public") + r1.cmd_raises("ip link set r1-eth0 master public") + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_vrf_md5_peering(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_converge(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd("show ip bgp vrf public neighbor 10.0.0.2 json") + ) + expected = { + "10.0.0.2": { + "bgpState": "Established", + "addressFamilyInfo": {"ipv4Unicast": {"acceptedPrefixCounter": 1}}, + } + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_converge) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + + assert result is None, "Can't peer with md5 per-VRF" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_netns/__init__.py b/tests/topotests/bgp_vrf_netns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot new file mode 100644 index 0000000..2b1f72b --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.dot @@ -0,0 +1,50 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph bgp_vrf_netns_eBGP_topo1 { + label="bgp vrf netns topo1 - eBGP with different AS numbers"; + labelloc="t"; + + # Routers + r1 [ + label="r1\nrtr-id 10.0.255.1/32", + shape=doubleoctagon, + fillcolor="#f08080", + style=filled, + ]; + + # 1 Switch for eBGP Peers + s1 [ + label="s1\n10.0.1.0/24", + shape=oval, + fillcolor="#d0e0d0", + style=filled, + ]; + + # 1 ExaBGP Peers AS 101 + peer1 [ + label="eBGP peer1\nAS101\nrtr-id 10.0.1.101/32", + shape=rectangle, + fillcolor="#eee3d3", + style=filled, + ]; + + # Connections + r1 -- s1 [label="eth0\n.1"]; + + peer1 -- s1 [label="eth0\n.101"]; + + # Arrange network to make cleaner diagram + { rank=same peer1 } -- s1 -- { rank=same r1 } [style=invis] +} diff --git a/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf new file mode 100644 index 0000000..6da2288 Binary files /dev/null and b/tests/topotests/bgp_vrf_netns/bgp-vrf-netns-topo.pdf differ diff --git a/tests/topotests/bgp_vrf_netns/exabgp.env b/tests/topotests/bgp_vrf_netns/exabgp.env new file mode 100644 index 0000000..ec978c6 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/exabgp.env @@ -0,0 +1,55 @@ + +[exabgp.api] +ack = false +encoder = text +highres = false +respawn = false +socket = '' + +[exabgp.bgp] +openwait = 60 + +[exabgp.cache] +attributes = true +nexthops = true + +[exabgp.daemon] +daemonize = true +pid = '/var/run/exabgp/exabgp.pid' +user = 'exabgp' +##daemonize = false + +[exabgp.log] +all = false +configuration = true +daemon = true +destination = '/var/log/exabgp.log' +enable = true +level = INFO +message = false +network = true +packets = false +parser = false +processes = true +reactor = true +rib = false +routes = false +short = false +timers = false + +[exabgp.pdb] +enable = false + +[exabgp.profile] +enable = false +file = '' + +[exabgp.reactor] +speed = 1.0 + +[exabgp.tcp] +acl = false +bind = '' +delay = 0 +once = false +port = 179 diff --git a/tests/topotests/bgp_vrf_netns/peer1/exa-send.py b/tests/topotests/bgp_vrf_netns/peer1/exa-send.py new file mode 100755 index 0000000..c6a4499 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/peer1/exa-send.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +""" +exa-send.py: Send a few testroutes with ExaBGP +""" + +from sys import stdout, argv +from time import sleep + +sleep(5) + +# 1st arg is peer number +# 2nd arg is number of routes to send +peer = int(argv[1]) +numRoutes = int(argv[2]) +asnum = 99 + +# Announce numRoutes equal routes per PE - different neighbor AS +for i in range(0, numRoutes): + stdout.write( + "announce route 10.201.%s.0/24 med 100 community %i:1 next-hop 10.0.%i.%i\n" + % (i, i, (((peer - 1) / 5) + 1), peer + 100) + ) + stdout.flush() + +# Loop endlessly to allow ExaBGP to continue running +while True: + sleep(1) diff --git a/tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg b/tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg new file mode 100644 index 0000000..97a024c --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/peer1/exabgp.cfg @@ -0,0 +1,18 @@ +process announce-routes { + run /etc/exabgp/exa-send.py 1 10; + encoder text; +} + +process receive-routes { + run /etc/exabgp/exa-receive.py 1; + encoder text; +} + +neighbor 10.0.1.1 { + router-id 10.0.1.101; + local-address 10.0.1.101; + local-as 99; + peer-as 100; + capability {graceful-restart;} + api {processes [ announce-routes, receive-routes ];} +} diff --git a/tests/topotests/bgp_vrf_netns/r1/bgpd.conf b/tests/topotests/bgp_vrf_netns/r1/bgpd.conf new file mode 100644 index 0000000..572dce7 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/r1/bgpd.conf @@ -0,0 +1,10 @@ +! +router bgp 100 vrf r1-bgp-cust1 + bgp router-id 10.0.1.1 + bgp bestpath as-path multipath-relax + no bgp ebgp-requires-policy + neighbor 10.0.1.101 remote-as 99 + neighbor 10.0.1.101 timers 3 10 + ! +! + diff --git a/tests/topotests/bgp_vrf_netns/r1/summary.txt b/tests/topotests/bgp_vrf_netns/r1/summary.txt new file mode 100644 index 0000000..819f261 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/r1/summary.txt @@ -0,0 +1,17 @@ +{ +"ipv4Unicast":{ + "routerId":"10.0.1.1", + "as":100, + "vrfName":"r1-bgp-cust1", + "peerCount":1, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":10, + "state":"Established" + } + }, + "totalPeers":1 +} +} diff --git a/tests/topotests/bgp_vrf_netns/r1/summary20.txt b/tests/topotests/bgp_vrf_netns/r1/summary20.txt new file mode 100644 index 0000000..ea04a56 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/r1/summary20.txt @@ -0,0 +1,15 @@ +{ + "routerId":"10.0.1.1", + "as":100, + "vrfName":"re1-bgp-cust1", + "peerCount":1, + "peers":{ + "10.0.1.101":{ + "outq":0, + "inq":0, + "pfxRcd":10, + "state":"Established" + } + }, + "totalPeers":1 +} diff --git a/tests/topotests/bgp_vrf_netns/r1/zebra.conf b/tests/topotests/bgp_vrf_netns/r1/zebra.conf new file mode 100644 index 0000000..ed4edfa --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface r1-eth0 vrf r1-bgp-cust1 + ip address 10.0.1.1/24 +! +line vty +! +! debug vrf diff --git a/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py new file mode 100644 index 0000000..028bc35 --- /dev/null +++ b/tests/topotests/bgp_vrf_netns/test_bgp_vrf_netns_topo.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp_vrf_netns_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2018 by 6WIND +# + +""" +test_bgp_vrf_netns_topo1.py: Test BGP topology with EBGP on NETNS VRF +""" + +import json +import os +import sys +import functools +import pytest + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +pytestmark = [pytest.mark.bgpd] + + +total_ebgp_peers = 1 +CustomizeVrfWithNetns = True + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + tgen.add_router("r1") + + # Setup Switches + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + # Add eBGP ExaBGP neighbors + peer_ip = "10.0.1.101" + peer_route = "via 10.0.1.1" + peer = tgen.add_exabgp_peer("peer1", ip=peer_ip, defaultRoute=peer_route) + switch = tgen.gears["s1"] + switch.add_link(peer) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # Get r1 reference + router = tgen.gears["r1"] + + # check for zebra capability + if CustomizeVrfWithNetns == True: + if router.check_capability(TopoRouter.RD_ZEBRA, "--vrfwnetns") == False: + return pytest.skip( + "Skipping BGP VRF NETNS Test. VRF NETNS backend not available on FRR" + ) + if os.system("ip netns list") != 0: + return pytest.skip( + "Skipping BGP VRF NETNS Test. NETNS not available on System" + ) + # retrieve VRF backend kind + if CustomizeVrfWithNetns == True: + logger.info("Testing with VRF Namespace support") + + # create VRF r1-bgp-cust1 + # move r1-eth0 to VRF r1-bgp-cust1 + + ns = "{}-bgp-cust1".format("r1") + router.net.add_netns(ns) + router.net.set_intf_netns("r1-eth0", ns, up=True) + + # run daemons + router.load_config(TopoRouter.RD_MGMTD, None, "--vrfwnetns") + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format("r1")), + "--vrfwnetns", + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format("r1")) + ) + + logger.info("Launching BGP and ZEBRA") + # BGP and ZEBRA start without underlying VRF + router.start() + # Starting Hosts and init ExaBGP on each of them + logger.info("starting exaBGP on peer1") + peer_list = tgen.exabgp_peers() + for pname, peer in peer_list.items(): + peer_dir = os.path.join(CWD, pname) + env_file = os.path.join(CWD, "exabgp.env") + logger.info("Running ExaBGP peer") + peer.start(peer_dir, env_file) + logger.info(pname) + + +def teardown_module(module): + tgen = get_topogen() + + # Move interfaces out of vrf namespace and delete the namespace + tgen.net["r1"].reset_intf_netns("r1-eth0") + tgen.net["r1"].delete_netns("r1-bgp-cust1") + + tgen.stop_topology() + + +def test_bgp_vrf_learn(): + "Test daemon learnt VRF context" + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Expected result + output = tgen.gears["r1"].vtysh_cmd("show vrf", isjson=False) + logger.info("output is: {}".format(output)) + + output = tgen.gears["r1"].vtysh_cmd("show bgp vrfs", isjson=False) + logger.info("output is: {}".format(output)) + + +def test_bgp_convergence(): + "Test for BGP topology convergence" + tgen = get_topogen() + + # uncomment if you want to troubleshoot + # tgen.mininet_cli() + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("waiting for bgp convergence") + + # Expected result + router = tgen.gears["r1"] + if router.has_version("<", "3.0"): + reffile = os.path.join(CWD, "r1/summary20.txt") + else: + reffile = os.path.join(CWD, "r1/summary.txt") + + expected = json.loads(open(reffile).read()) + + test_func = functools.partial( + topotest.router_json_cmp, + router, + "show bgp vrf r1-bgp-cust1 summary json", + expected, + ) + _, res = topotest.run_and_expect(test_func, None, count=90, wait=0.5) + assertmsg = "BGP router network did not converge" + assert res is None, assertmsg + + +def test_bgp_vrf_netns(): + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + expect = { + "routerId": "10.0.1.1", + "routes": {}, + } + + for subnet in range(0, 10): + netkey = "10.201.{}.0/24".format(subnet) + expect["routes"][netkey] = [] + peer = {"valid": True} + expect["routes"][netkey].append(peer) + + test_func = functools.partial( + topotest.router_json_cmp, + tgen.gears["r1"], + "show ip bgp vrf r1-bgp-cust1 ipv4 json", + expect, + ) + _, res = topotest.run_and_expect(test_func, None, count=12, wait=0.5) + assertmsg = 'expected routes in "show ip bgp vrf r1-bgp-cust1 ipv4" output' + assert res is None, assertmsg + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + ret = pytest.main(args) + + sys.exit(ret) diff --git a/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf new file mode 100644 index 0000000..397f793 --- /dev/null +++ b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf @@ -0,0 +1,33 @@ +hostname r1 + +router bgp 99 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + import vrf DONNA + ! +! +router bgp 99 vrf DONNA + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + import vrf EVA + import vrf ZITA + import vrf default + ! +! +router bgp 99 vrf EVA + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + import vrf DONNA + import vrf ZITA + ! +! +router bgp 99 vrf ZITA + no bgp ebgp-requires-policy + no bgp network import-check + address-family ipv4 unicast + network 172.16.101.0/24 + ! +! diff --git a/tests/topotests/bgp_vrf_route_leak_basic/r1/zebra.conf b/tests/topotests/bgp_vrf_route_leak_basic/r1/zebra.conf new file mode 100644 index 0000000..4de9e89 --- /dev/null +++ b/tests/topotests/bgp_vrf_route_leak_basic/r1/zebra.conf @@ -0,0 +1,28 @@ +hostname r1 + +int dummy0 + ip address 10.0.4.1/24 + no shut +! +int dummy1 + ip address 10.0.0.1/24 + no shut +! +int dummy2 + ip address 10.0.1.1/24 + no shut +! +int dummy3 + ip address 10.0.2.1/24 + no shut +! +int dummy4 + ip address 10.0.3.1/24 + no shut +! +int EVA + no shut +! +int DONNA + no shut +! diff --git a/tests/topotests/bgp_vrf_route_leak_basic/setup_vrfs b/tests/topotests/bgp_vrf_route_leak_basic/setup_vrfs new file mode 100644 index 0000000..f62c5cd --- /dev/null +++ b/tests/topotests/bgp_vrf_route_leak_basic/setup_vrfs @@ -0,0 +1,17 @@ +#!/bin/bash + +ip link add DONNA type vrf table 1001 +ip link add EVA type vrf table 1002 + +ip link add dummy0 type dummy # vrf default +ip link add dummy1 type dummy +ip link add dummy2 type dummy +ip link add dummy3 type dummy +ip link add dummy4 type dummy + +ip link set dummy1 master DONNA +ip link set dummy2 master EVA +ip link set dummy3 master DONNA +ip link set dummy4 master EVA + + diff --git a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py new file mode 100644 index 0000000..6d4b436 --- /dev/null +++ b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py @@ -0,0 +1,521 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_bgp-vrf-route-leak-basic.py +# +# Copyright (c) 2018 Cumulus Networks, Inc. +# Donald Sharp +# + +""" +test_bgp-vrf-route-leak-basic.py.py: Test basic vrf route leaking +""" + +import os +import sys +from functools import partial +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.checkping import check_ping + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + "Build function" + + for routern in range(1, 2): + tgen.add_router("r{}".format(routern)) + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # For all registered routers, load the zebra configuration file + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/setup_vrfs".format(CWD)) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + # After loading the configurations, this function loads configured daemons. + tgen.start_router() + # tgen.mininet_cli() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_vrf_route_leak_donna(): + logger.info("Ensure that routes are leaked back and forth") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # Test DONNA VRF. + expect = { + "10.0.0.0/24": [ + { + "protocol": "connected", + } + ], + "10.0.1.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "EVA", + "vrf": "EVA", + "active": True, + }, + ], + }, + ], + "10.0.2.0/24": [{"protocol": "connected"}], + "10.0.3.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "EVA", + "vrf": "EVA", + "active": True, + }, + ], + }, + ], + "10.0.4.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "dummy0", + "vrf": "default", + "active": True, + }, + ], + }, + ], + "172.16.101.0/24": [ + { + "protocol": "bgp", + "selected": None, + "nexthops": [ + { + "fib": None, + "interfaceName": "unknown", + "vrf": "Unknown", + "active": None, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_eva(): + logger.info("Ensure that routes are leaked back and forth") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # Test EVA VRF. + expect = { + "10.0.0.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "DONNA", + "vrf": "DONNA", + "active": True, + }, + ], + }, + ], + "10.0.1.0/24": [ + { + "protocol": "connected", + } + ], + "10.0.2.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "DONNA", + "vrf": "DONNA", + "active": True, + }, + ], + }, + ], + "10.0.3.0/24": [ + { + "protocol": "connected", + } + ], + "172.16.101.0/24": [ + { + "protocol": "bgp", + "selected": None, + "nexthops": [ + { + "fib": None, + "interfaceName": "unknown", + "vrf": "Unknown", + "active": None, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf EVA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF EVA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_default(): + logger.info("Ensure that routes are leaked back and forth") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + # Test default VRF. + expect = { + "10.0.0.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "DONNA", + "vrf": "DONNA", + "active": True, + }, + ], + }, + ], + "10.0.2.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "DONNA", + "vrf": "DONNA", + "active": True, + }, + ], + }, + ], + "10.0.4.0/24": [ + { + "protocol": "connected", + } + ], + } + + test_func = partial(topotest.router_json_cmp, r1, "show ip route json", expect) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF default check failed:\n{}".format(diff) + + +def test_ping(): + "Simple ping tests" + + tgen = get_topogen() + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + logger.info("Ping from default to DONNA") + check_ping("r1", "10.0.0.1", True, 10, 0.5, source_addr="10.0.4.1") + + +def test_vrf_route_leak_donna_after_eva_down(): + logger.info("Ensure that route states change after EVA interface goes down") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r1.vtysh_cmd( + """ +configure +interface EVA + shutdown +""" + ) + + # Test DONNA VRF. + expect = { + "10.0.1.0/24": [ + { + "protocol": "bgp", + "selected": None, + "nexthops": [ + { + "fib": None, + "interfaceName": "EVA", + "vrf": "EVA", + "active": None, + }, + ], + }, + ], + "10.0.3.0/24": [ + { + "protocol": "bgp", + "selected": None, + "nexthops": [ + { + "fib": None, + "interfaceName": "EVA", + "vrf": "EVA", + "active": None, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + """ + Check that "show ip route vrf DONNA json" and the JSON at key "DONNA" of + "show ip route vrf all json" gives the same result. + """ + + def check_vrf_table(router, vrf, expect): + output = router.vtysh_cmd("show ip route vrf all json", isjson=True) + vrf_table = output.get(vrf, {}) + + return topotest.json_cmp(vrf_table, expect) + + test_func = partial(check_vrf_table, r1, "DONNA", expect) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_donna_after_eva_up(): + logger.info("Ensure that route states change after EVA interface goes up") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r1.vtysh_cmd( + """ +configure +interface EVA + no shutdown +""" + ) + + # Test DONNA VRF. + expect = { + "10.0.1.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "EVA", + "vrf": "EVA", + "active": True, + }, + ], + }, + ], + "10.0.3.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "EVA", + "vrf": "EVA", + "active": True, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_donna_add_vrf_zita(): + logger.info("Add VRF ZITA and ensure that the route from VRF ZITA is updated") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r1.cmd("ip link add ZITA type vrf table 1003") + + # Test DONNA VRF. + expect = { + "172.16.101.0/24": [ + { + "protocol": "bgp", + "selected": None, + "nexthops": [ + { + "fib": None, + "interfaceName": "ZITA", + "vrf": "ZITA", + "active": None, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_donna_set_zita_up(): + logger.info("Set VRF ZITA up and ensure that the route from VRF ZITA is updated") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r1.vtysh_cmd( + """ +configure +interface ZITA + no shutdown +""" + ) + + # Test DONNA VRF. + expect = { + "172.16.101.0/24": [ + { + "protocol": "bgp", + "selected": True, + "nexthops": [ + { + "fib": True, + "interfaceName": "ZITA", + "vrf": "ZITA", + "active": True, + }, + ], + }, + ], + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_vrf_route_leak_donna_delete_vrf_zita(): + logger.info("Delete VRF ZITA and ensure that the route from VRF ZITA is deleted") + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + r1.cmd("ip link delete ZITA") + + # Test DONNA VRF. + expect = { + "172.16.101.0/24": None, + } + + test_func = partial( + topotest.router_json_cmp, r1, "show ip route vrf DONNA json", expect + ) + result, diff = topotest.run_and_expect(test_func, None, count=10, wait=0.5) + assert result, "BGP VRF DONNA check failed:\n{}".format(diff) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/config_timing/r1/staticd.conf b/tests/topotests/config_timing/r1/staticd.conf new file mode 100644 index 0000000..0f9f97c --- /dev/null +++ b/tests/topotests/config_timing/r1/staticd.conf @@ -0,0 +1 @@ +log timestamp precision 3 diff --git a/tests/topotests/config_timing/r1/zebra.conf b/tests/topotests/config_timing/r1/zebra.conf new file mode 100644 index 0000000..b4dc338 --- /dev/null +++ b/tests/topotests/config_timing/r1/zebra.conf @@ -0,0 +1,16 @@ +log timestamp precision 3 + +ip prefix-list ANY permit 0.0.0.0/0 le 32 +ipv6 prefix-list ANY seq 10 permit any + +route-map RM-NONE4 deny 10 + +route-map RM-NONE6 deny 10 + +interface r1-eth0 + ip address 100.0.0.1/24 + ipv6 address 2102::1/64 +exit + +ip protocol static route-map RM-NONE4 +ipv6 protocol static route-map RM-NONE6 diff --git a/tests/topotests/config_timing/test_config_timing.py b/tests/topotests/config_timing/test_config_timing.py new file mode 100644 index 0000000..52d196f --- /dev/null +++ b/tests/topotests/config_timing/test_config_timing.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC +# +# June 2 2021, Christian Hopps +# +# Copyright (c) 2021, LabN Consulting, L.L.C. +# Copyright (c) 2019-2020 by +# Donatas Abraitis +# + +""" +Test the timing of config operations. + +The initial add of 10k routes is used as a baseline for timing and all future +operations are expected to complete in under 2 times that baseline. This is a +lot of slop; however, the pre-batching code some of these operations (e.g., +adding the same set of 10k routes) would take 100 times longer, so the intention +is to catch those types of regressions. +""" + +import datetime +import ipaddress +import math +import os +import sys +import pytest +from lib import topotest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.staticd] + + +def build_topo(tgen): + tgen.add_router("r1") + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, "{}/zebra.conf".format(rname)), + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def get_ip_networks(super_prefix, base_count, count): + count_log2 = math.log(base_count, 2) + if count_log2 != int(count_log2): + count_log2 = int(count_log2) + 1 + else: + count_log2 = int(count_log2) + network = ipaddress.ip_network(super_prefix) + return tuple(network.subnets(count_log2))[0:count] + + +def test_static_timing(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def do_config( + base_count, + count, + bad_indices, + base_delta, + d_multiplier, + add=True, + do_ipv6=False, + super_prefix=None, + en_dbg=False, + ): + router_list = tgen.routers() + tot_delta = float(0) + + optype = "adding" if add else "removing" + iptype = "IPv6" if do_ipv6 else "IPv4" + if super_prefix is None: + super_prefix = "2001::/48" if do_ipv6 else "10.0.0.0/8" + via = "lo" + optyped = "added" if add else "removed" + + for rname, router in router_list.items(): + router.logger.info("{} {} static {} routes".format(optype, count, iptype)) + + # Generate config file. + config_file = os.path.join( + router.logdir, rname, "{}-routes-{}.conf".format(iptype.lower(), optype) + ) + with open(config_file, "w") as f: + for i, net in enumerate( + get_ip_networks(super_prefix, base_count, count) + ): + if i in bad_indices: + if add: + f.write("ip route {} {} bad_input\n".format(net, via)) + else: + f.write("no ip route {} {} bad_input\n".format(net, via)) + elif add: + f.write("ip route {} {}\n".format(net, via)) + else: + f.write("no ip route {} {}\n".format(net, via)) + + # Enable debug + if en_dbg: + router.vtysh_cmd("debug northbound callbacks configuration") + + # Load config file. + load_command = 'vtysh -f "{}"'.format(config_file) + tstamp = datetime.datetime.now() + output = router.run(load_command) + delta = (datetime.datetime.now() - tstamp).total_seconds() + tot_delta += delta + + router.logger.debug( + "\nvtysh command => {}\nvtysh output <= {}\nin {}s".format( + load_command, output, delta + ) + ) + + limit_delta = base_delta * d_multiplier + logger.info( + "{} {} {} static routes under {} in {}s (limit: {}s)".format( + optyped, count, iptype.lower(), super_prefix, tot_delta, limit_delta + ) + ) + if limit_delta: + assert tot_delta <= limit_delta + + return tot_delta + + # Number of static routes + router = tgen.gears["r1"] + output = router.net.cmd_legacy("vtysh -h | grep address-sanitizer", warn=False) + if output == "": + logger.info("No Address Sanitizer, generating 10000 routes") + prefix_count = 10000 + else: + logger.info("Address Sanitizer build, only testing 50 routes") + prefix_count = 50 + + prefix_base = [ + ["10.0.0.0/8", "11.0.0.0/8"], + ["2100:1111:2220::/44", "2100:3333:4440::/44"], + ] + + # This apparently needed to allow for various mgmtd/staticd/zebra connections to form + # which then SLOWS execution down. If we don't include this value then the + # initial, baseline establishing, time is 2 time faster (e.g., 5s instead of 10s), + # but all later runs are slower and fail. + # + # This should be done differently based on actual facts. + topotest.sleep(5) + + bad_indices = [] + for ipv6 in [False, True]: + base_delta = do_config( + prefix_count, + prefix_count, + bad_indices, + 0, + 0, + True, + ipv6, + prefix_base[ipv6][0], + ) + + # Another set of same number of prefixes + do_config( + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][1], + ) + + # Duplicate config + do_config( + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][0], + ) + + # Remove 1/2 of duplicate + do_config( + prefix_count, + prefix_count // 2, + bad_indices, + base_delta, + 3, + False, + ipv6, + prefix_base[ipv6][0], + ) + + # Add all back in so 1/2 replicate 1/2 new + do_config( + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + True, + ipv6, + prefix_base[ipv6][0], + ) + + # remove all + delta = do_config( + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + False, + ipv6, + prefix_base[ipv6][0], + ) + delta += do_config( + prefix_count, + prefix_count, + bad_indices, + base_delta, + 3, + False, + ipv6, + prefix_base[ipv6][1], + ) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py new file mode 100755 index 0000000..a231513 --- /dev/null +++ b/tests/topotests/conftest.py @@ -0,0 +1,812 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +""" +Topotest conftest.py file. +""" +# pylint: disable=consider-using-f-string + +import contextlib +import glob +import logging +import os +import re +import resource +import subprocess +import sys +import time +from pathlib import Path + +import lib.fixtures +import pytest +from lib.common_config import generate_support_bundle +from lib.micronet_compat import Mininet +from lib.topogen import diagnose_env, get_topogen +from lib.topolog import get_test_logdir, logger +from lib.topotest import json_cmp_result +from munet import cli +from munet.base import Commander, proc_error +from munet.cleanup import cleanup_current, cleanup_previous +from munet.config import ConfigOptionsProxy +from munet.testing.util import pause_test + +from lib import topolog, topotest + +try: + # Used by munet native tests + from munet.testing.fixtures import event_loop, unet # pylint: disable=all # noqa + + @pytest.fixture(scope="module") + def rundir_module(pytestconfig): + d = os.path.join(pytestconfig.option.rundir, get_test_logdir()) + logging.debug("rundir_module: test module rundir %s", d) + return d + +except (AttributeError, ImportError): + pass + + +# Remove this and use munet version when we move to pytest_asyncio +@contextlib.contextmanager +def chdir(ndir, desc=""): + odir = os.getcwd() + os.chdir(ndir) + if desc: + logging.debug("%s: chdir from %s to %s", desc, odir, ndir) + try: + yield + finally: + if desc: + logging.debug("%s: chdir back from %s to %s", desc, ndir, odir) + os.chdir(odir) + + +@contextlib.contextmanager +def log_handler(basename, logpath): + topolog.logstart(basename, logpath) + try: + yield + finally: + topolog.logfinish(basename, logpath) + + +def is_main_runner(): + return "PYTEST_XDIST_WORKER" not in os.environ + + +def pytest_addoption(parser): + """ + Add topology-only option to the topology tester. This option makes pytest + only run the setup_module() to setup the topology without running any tests. + """ + parser.addoption( + "--asan-abort", + action="store_true", + help="Configure address sanitizer to abort process on error", + ) + + parser.addoption( + "--cli-on-error", + action="store_true", + help="Mininet cli on test failure", + ) + + parser.addoption( + "--cov-topotest", + action="store_true", + help="Enable reporting of coverage", + ) + + parser.addoption( + "--cov-frr-build-dir", + help="Dir of coverage-enable build being run, default is the source dir", + ) + + parser.addoption( + "--gdb-breakpoints", + metavar="SYMBOL[,SYMBOL...]", + help="Comma-separated list of functions to set gdb breakpoints on", + ) + + parser.addoption( + "--gdb-daemons", + metavar="DAEMON[,DAEMON...]", + help="Comma-separated list of daemons to spawn gdb on, or 'all'", + ) + + parser.addoption( + "--gdb-routers", + metavar="ROUTER[,ROUTER...]", + help="Comma-separated list of routers to spawn gdb on, or 'all'", + ) + + parser.addoption( + "--gdb-use-emacs", + action="store_true", + help="Use emacsclient to run gdb instead of a shell", + ) + + parser.addoption( + "--logd", + action="append", + metavar="DAEMON[,ROUTER[,...]", + help=( + "Tail-F the DAEMON log file on all or a subset of ROUTERs." + " Option can be given multiple times." + ), + ) + + parser.addoption( + "--memleaks", + action="store_true", + help="Report memstat results as errors", + ) + + parser.addoption( + "--pause", + action="store_true", + help="Pause after each test", + ) + + parser.addoption( + "--pause-at-end", + action="store_true", + help="Pause before taking munet down", + ) + + parser.addoption( + "--pause-on-error", + action="store_true", + help="Do not pause after (disables default when --shell or -vtysh given)", + ) + + parser.addoption( + "--no-pause-on-error", + dest="pause_on_error", + action="store_false", + help="Do not pause after (disables default when --shell or -vtysh given)", + ) + + parser.addoption( + "--pcap", + default="", + metavar="NET[,NET...]", + help="Comma-separated list of networks to capture packets on, or 'all'", + ) + + parser.addoption( + "--perf", + action="append", + metavar="DAEMON[,ROUTER[,...]", + help=( + "Collect performance data from given DAEMON on all or a subset of ROUTERs." + " Option can be given multiple times." + ), + ) + + parser.addoption( + "--perf-options", + metavar="OPTS", + default="-g", + help="Options to pass to `perf record`.", + ) + + parser.addoption( + "--rr-daemons", + metavar="DAEMON[,DAEMON...]", + help="Comma-separated list of daemons to run `rr` on, or 'all'", + ) + + parser.addoption( + "--rr-routers", + metavar="ROUTER[,ROUTER...]", + help="Comma-separated list of routers to run `rr` on, or 'all'", + ) + + parser.addoption( + "--rr-options", + metavar="OPTS", + help="Options to pass to `rr record`.", + ) + + rundir_help = "directory for running in and log files" + parser.addini("rundir", rundir_help, default="/tmp/topotests") + parser.addoption("--rundir", metavar="DIR", help=rundir_help) + + parser.addoption( + "--shell", + metavar="ROUTER[,ROUTER...]", + help="Comma-separated list of routers to spawn shell on, or 'all'", + ) + + parser.addoption( + "--shell-on-error", + action="store_true", + help="Spawn shell on all routers on test failure", + ) + + parser.addoption( + "--strace-daemons", + metavar="DAEMON[,DAEMON...]", + help="Comma-separated list of daemons to strace, or 'all'", + ) + + parser.addoption( + "--topology-only", + action="store_true", + default=False, + help="Only set up this topology, don't run tests", + ) + + parser.addoption( + "--valgrind-extra", + action="store_true", + help="Generate suppression file, and enable more precise (slower) valgrind checks", + ) + + parser.addoption( + "--valgrind-leak-kinds", + metavar="KIND[,KIND...]", + help="Comma-separated list of valgrind leak kinds or 'all'", + ) + + parser.addoption( + "--valgrind-memleaks", + action="store_true", + help="Run all daemons under valgrind for memleak detection", + ) + + parser.addoption( + "--vtysh", + metavar="ROUTER[,ROUTER...]", + help="Comma-separated list of routers to spawn vtysh on, or 'all'", + ) + + parser.addoption( + "--vtysh-on-error", + action="store_true", + help="Spawn vtysh on all routers on test failure", + ) + + +def check_for_valgrind_memleaks(): + assert topotest.g_pytest_config.option.valgrind_memleaks + + leaks = [] + tgen = get_topogen() # pylint: disable=redefined-outer-name + latest = [] + existing = [] + if tgen is not None: + logdir = tgen.logdir + if hasattr(tgen, "valgrind_existing_files"): + existing = tgen.valgrind_existing_files + latest = glob.glob(os.path.join(logdir, "*.valgrind.*")) + latest = [x for x in latest if "core" not in x] + + daemons = set() + for vfile in latest: + if vfile in existing: + continue + # do not consider memleaks from parent fork (i.e., owned by root) + if os.stat(vfile).st_uid == 0: + existing.append(vfile) # do not check again + logger.debug("Skipping valgrind file %s owned by root", vfile) + continue + logger.debug("Checking valgrind file %s not owned by root", vfile) + with open(vfile, encoding="ascii") as vf: + vfcontent = vf.read() + match = re.search(r"ERROR SUMMARY: (\d+) errors", vfcontent) + if match: + existing.append(vfile) # have summary don't check again + if match and match.group(1) != "0": + emsg = "{} in {}".format(match.group(1), vfile) + leaks.append(emsg) + daemon = re.match(r".*\.valgrind\.(.*)\.\d+", vfile).group(1) + daemons.add("{}({})".format(daemon, match.group(1))) + + if tgen is not None: + tgen.valgrind_existing_files = existing + + if leaks: + logger.error("valgrind memleaks found:\n\t%s", "\n\t".join(leaks)) + pytest.fail("valgrind memleaks found for daemons: " + " ".join(daemons)) + + +def check_for_memleaks(): + leaks = [] + tgen = get_topogen() # pylint: disable=redefined-outer-name + latest = [] + existing = [] + if tgen is not None: + logdir = tgen.logdir + if hasattr(tgen, "memstat_existing_files"): + existing = tgen.memstat_existing_files + latest = glob.glob(os.path.join(logdir, "*/*.err")) + + daemons = [] + for vfile in latest: + if vfile in existing: + continue + with open(vfile, encoding="ascii") as vf: + vfcontent = vf.read() + num = vfcontent.count("memstats:") + if num: + existing.append(vfile) # have summary don't check again + emsg = "{} types in {}".format(num, vfile) + leaks.append(emsg) + daemon = re.match(r".*test[a-z_A-Z0-9\+]*/(.*)\.err", vfile).group(1) + daemons.append("{}({})".format(daemon, num)) + + if tgen is not None: + tgen.memstat_existing_files = existing + + if leaks: + logger.error("memleaks found:\n\t%s", "\n\t".join(leaks)) + pytest.fail("memleaks found for daemons: " + " ".join(daemons)) + + +def check_for_core_dumps(): + tgen = get_topogen() # pylint: disable=redefined-outer-name + if not tgen: + return + + if not hasattr(tgen, "existing_core_files"): + tgen.existing_core_files = set() + existing = tgen.existing_core_files + + cores = glob.glob(os.path.join(tgen.logdir, "*/*.dmp")) + latest = {x for x in cores if x not in existing} + if latest: + existing |= latest + tgen.existing_core_files = existing + + emsg = "New core[s] found: " + ", ".join(latest) + logger.error(emsg) + pytest.fail(emsg) + + +def check_for_backtraces(): + tgen = get_topogen() # pylint: disable=redefined-outer-name + if not tgen: + return + + if not hasattr(tgen, "existing_backtrace_files"): + tgen.existing_backtrace_files = {} + existing = tgen.existing_backtrace_files + + latest = glob.glob(os.path.join(tgen.logdir, "*/*.log")) + backtraces = [] + for vfile in latest: + with open(vfile, encoding="ascii") as vf: + vfcontent = vf.read() + btcount = vfcontent.count("Backtrace:") + if not btcount: + continue + if vfile not in existing: + existing[vfile] = 0 + if btcount == existing[vfile]: + continue + existing[vfile] = btcount + backtraces.append(vfile) + + if backtraces: + emsg = "New backtraces found in: " + ", ".join(backtraces) + logger.error(emsg) + pytest.fail(emsg) + + +@pytest.fixture(autouse=True, scope="module") +def module_autouse(request): + basename = get_test_logdir(request.node.nodeid, True) + logdir = Path(topotest.g_pytest_config.option.rundir) / basename + logpath = logdir / "exec.log" + + subprocess.check_call("mkdir -p -m 1777 {}".format(logdir), shell=True) + + with log_handler(basename, logpath): + sdir = os.path.dirname(os.path.realpath(request.fspath)) + with chdir(sdir, "module autouse fixture"): + yield + + +@pytest.fixture(autouse=True, scope="module") +def module_check_memtest(request): + yield + if request.config.option.valgrind_memleaks: + if get_topogen() is not None: + check_for_valgrind_memleaks() + if request.config.option.memleaks: + if get_topogen() is not None: + check_for_memleaks() + + +# +# Disable per test function logging as FRR CI system can't handle it. +# +# @pytest.fixture(autouse=True, scope="function") +# def function_autouse(request): +# # For tests we actually use the logdir name as the logfile base +# logbase = get_test_logdir(nodeid=request.node.nodeid, module=False) +# logbase = os.path.join(topotest.g_pytest_config.option.rundir, logbase) +# logpath = Path(logbase) +# path = Path(f"{logpath.parent}/exec-{logpath.name}.log") +# subprocess.check_call("mkdir -p -m 1777 {}".format(logpath.parent), shell=True) +# with log_handler(request.node.nodeid, path): +# yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item: pytest.Item) -> None: + "Hook the function that is called to execute the test." + + # For topology only run the CLI then exit + if item.config.option.topology_only: + get_topogen().cli() + pytest.exit("exiting after --topology-only") + + # Let the default pytest_runtest_call execute the test function + yield + + check_for_backtraces() + check_for_core_dumps() + + # Check for leaks if requested + if item.config.option.valgrind_memleaks: + check_for_valgrind_memleaks() + + if item.config.option.memleaks: + check_for_memleaks() + + +def pytest_assertrepr_compare(op, left, right): + """ + Show proper assertion error message for json_cmp results. + """ + del op + + json_result = left + if not isinstance(json_result, json_cmp_result): + json_result = right + if not isinstance(json_result, json_cmp_result): + return None + + return json_result.gen_report() + + +def setup_coverage(config): + commander = Commander("pytest") + if config.option.cov_frr_build_dir: + bdir = Path(config.option.cov_frr_build_dir).resolve() + output = commander.cmd_raises(f"find {bdir} -name zebra_nb.gcno").strip() + else: + # Support build sub-directory of main source dir + bdir = Path(__file__).resolve().parent.parent.parent + output = commander.cmd_raises(f"find {bdir} -name zebra_nb.gcno").strip() + m = re.match(f"({bdir}.*)/zebra/zebra_nb.gcno", output) + if not m: + logger.warning( + "No coverage data files (*.gcno) found, try specifying --cov-frr-build-dir" + ) + return + + bdir = Path(m.group(1)) + # Save so we can get later from g_pytest_config + rundir = Path(config.option.rundir).resolve() + gcdadir = rundir / "gcda" + os.environ["FRR_BUILD_DIR"] = str(bdir) + os.environ["GCOV_PREFIX_STRIP"] = str(len(bdir.parts) - 1) + os.environ["GCOV_PREFIX"] = str(gcdadir) + + if is_main_runner(): + commander.cmd_raises(f"find {bdir} -name '*.gc??' -exec chmod o+r {{}} +") + commander.cmd_raises(f"mkdir -p {gcdadir}") + commander.cmd_raises(f"chown -R root:frr {gcdadir}") + commander.cmd_raises(f"chmod 2775 {gcdadir}") + + +def pytest_configure(config): + """ + Assert that the environment is correctly configured, and get extra config. + """ + topotest.g_pytest_config = ConfigOptionsProxy(config) + + if config.getoption("--collect-only"): + return + + if "PYTEST_XDIST_WORKER" not in os.environ: + os.environ["PYTEST_XDIST_MODE"] = config.getoption("dist", "no") + os.environ["PYTEST_TOPOTEST_WORKER"] = "" + is_xdist = os.environ["PYTEST_XDIST_MODE"] != "no" + is_worker = False + wname = "" + else: + wname = os.environ["PYTEST_XDIST_WORKER"] + os.environ["PYTEST_TOPOTEST_WORKER"] = wname + is_xdist = True + is_worker = True + + resource.setrlimit( + resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY) + ) + # ----------------------------------------------------- + # Set some defaults for the pytest.ini [pytest] section + # --------------------------------------------------- + + rundir = config.option.rundir + if not rundir: + rundir = config.getini("rundir") + if not rundir: + rundir = "/tmp/topotests" + config.option.rundir = rundir + + if not config.getoption("--junitxml"): + config.option.xmlpath = os.path.join(rundir, "topotests.xml") + xmlpath = config.option.xmlpath + + # Save an existing topotest.xml + if os.path.exists(xmlpath): + fmtime = time.localtime(os.path.getmtime(xmlpath)) + suffix = "-" + time.strftime("%Y%m%d%H%M%S", fmtime) + commander = Commander("pytest") + mv_path = commander.get_exec_path("mv") + commander.cmd_status([mv_path, xmlpath, xmlpath + suffix]) + + # Set the log_file (exec) to inside the rundir if not specified + if not config.getoption("--log-file") and not config.getini("log_file"): + config.option.log_file = os.path.join(rundir, "exec.log") + + # Handle pytest-xdist each worker get's it's own top level log file + # `exec-worker-N.log` + if wname: + wname = wname.replace("gw", "worker-") + cpath = Path(config.option.log_file).absolute() + config.option.log_file = f"{cpath.parent}/{cpath.stem}-{wname}{cpath.suffix}" + elif is_xdist: + cpath = Path(config.option.log_file).absolute() + config.option.log_file = f"{cpath.parent}/{cpath.stem}-xdist{cpath.suffix}" + + # Turn on live logging if user specified verbose and the config has a CLI level set + if config.getoption("--verbose") and not is_xdist and not config.getini("log_cli"): + if config.getoption("--log-cli-level", None) is None: + # By setting the CLI option to the ini value it enables log_cli=1 + cli_level = config.getini("log_cli_level") + if cli_level is not None: + config.option.log_cli_level = cli_level + + have_tmux = bool(os.getenv("TMUX", "")) + have_screen = not have_tmux and bool(os.getenv("STY", "")) + have_xterm = not have_tmux and not have_screen and bool(os.getenv("DISPLAY", "")) + have_windows = have_tmux or have_screen or have_xterm + have_windows_pause = have_tmux or have_xterm + xdist_no_windows = is_xdist and not is_worker and not have_windows_pause + + def assert_feature_windows(b, feature): + if b and xdist_no_windows: + pytest.exit( + "{} use requires byobu/TMUX/XTerm under dist {}".format( + feature, os.environ["PYTEST_XDIST_MODE"] + ) + ) + elif b and not is_xdist and not have_windows: + pytest.exit("{} use requires byobu/TMUX/SCREEN/XTerm".format(feature)) + + # + # Check for window capability if given options that require window + # + assert_feature_windows(config.option.gdb_routers, "GDB") + assert_feature_windows(config.option.gdb_daemons, "GDB") + assert_feature_windows(config.option.cli_on_error, "--cli-on-error") + assert_feature_windows(config.option.shell, "--shell") + assert_feature_windows(config.option.shell_on_error, "--shell-on-error") + assert_feature_windows(config.option.vtysh, "--vtysh") + assert_feature_windows(config.option.vtysh_on_error, "--vtysh-on-error") + + if config.option.topology_only and is_xdist: + pytest.exit("Cannot use --topology-only with distributed test mode") + + # Check environment now that we have config + if not diagnose_env(rundir): + pytest.exit("environment has errors, please read the logs in %s" % rundir) + + # slave TOPOTESTS_CHECK_MEMLEAK to memleaks flag + if config.option.memleaks: + if "TOPOTESTS_CHECK_MEMLEAK" not in os.environ: + os.environ["TOPOTESTS_CHECK_MEMLEAK"] = "/dev/null" + else: + if "TOPOTESTS_CHECK_MEMLEAK" in os.environ: + del os.environ["TOPOTESTS_CHECK_MEMLEAK"] + if "TOPOTESTS_CHECK_STDERR" in os.environ: + del os.environ["TOPOTESTS_CHECK_STDERR"] + + if config.option.cov_topotest: + setup_coverage(config) + + +@pytest.fixture(autouse=True, scope="session") +def session_autouse(): + # Aligns logs nicely + logging.addLevelName(logging.WARNING, " WARN") + logging.addLevelName(logging.INFO, " INFO") + + is_main = is_main_runner() + + logger.debug("Before the run (is_main: %s)", is_main) + if is_main: + cleanup_previous() + yield + if is_main: + cleanup_current() + logger.debug("After the run (is_main: %s)", is_main) + + +def pytest_runtest_setup(item): + module = item.parent.module + script_dir = os.path.abspath(os.path.dirname(module.__file__)) + os.environ["PYTEST_TOPOTEST_SCRIPTDIR"] = script_dir + os.environ["CONFIGDIR"] = script_dir + + +def pytest_exception_interact(node, call, report): + generate_support_bundle() + + +def pytest_runtest_makereport(item, call): + "Log all assert messages to default logger with error level" + + pause = bool(item.config.getoption("--pause")) + title = "unset" + + if call.excinfo is None: + error = False + else: + parent = item.parent + modname = parent.module.__name__ + + # Treat skips as non errors, don't pause after + if call.excinfo.typename == "Skipped": + pause = False + error = False + logger.info( + 'test skipped at "{}/{}": {}'.format( + modname, item.name, call.excinfo.value + ) + ) + else: + error = True + # Handle assert failures + parent._previousfailed = item # pylint: disable=W0212 + logger.error( + 'test failed at "{}/{}": {}'.format( + modname, item.name, call.excinfo.value + ) + ) + title = "{}/{}".format(modname, item.name) + + # We want to pause, if requested, on any error not just test cases + # (e.g., call.when == "setup") + if not pause: + pause = item.config.option.pause_on_error or item.config.option.pause + + # (topogen) Set topology error to avoid advancing in the test. + tgen = get_topogen() # pylint: disable=redefined-outer-name + if tgen is not None: + # This will cause topogen to report error on `routers_have_failure`. + tgen.set_error("{}/{}".format(modname, item.name)) + + commander = Commander("pytest") + isatty = sys.stdout.isatty() + error_cmd = None + + if error and item.config.option.vtysh_on_error: + error_cmd = commander.get_exec_path(["vtysh"]) + elif error and item.config.option.shell_on_error: + error_cmd = os.getenv("SHELL", commander.get_exec_path(["bash"])) + + if error_cmd: + is_tmux = bool(os.getenv("TMUX", "")) + is_screen = not is_tmux and bool(os.getenv("STY", "")) + is_xterm = not is_tmux and not is_screen and bool(os.getenv("DISPLAY", "")) + + channel = None + win_info = None + wait_for_channels = [] + wait_for_procs = [] + # Really would like something better than using this global here. + # Not all tests use topogen though so get_topogen() won't work. + for node in Mininet.g_mnet_inst.hosts.values(): + pause = True + + if is_tmux: + channel = ( + "{}-{}".format(os.getpid(), Commander.tmux_wait_gen) + if not isatty + else None + ) + Commander.tmux_wait_gen += 1 + wait_for_channels.append(channel) + + pane_info = node.run_in_window( + error_cmd, + new_window=win_info is None, + background=True, + title="{} ({})".format(title, node.name), + name=title, + tmux_target=win_info, + wait_for=channel, + ) + if is_tmux: + if win_info is None: + win_info = pane_info + elif is_xterm: + assert isinstance(pane_info, subprocess.Popen) + wait_for_procs.append(pane_info) + + # Now wait on any channels + for channel in wait_for_channels: + logger.debug("Waiting on TMUX channel %s", channel) + commander.cmd_raises([commander.get_exec_path("tmux"), "wait", channel]) + for p in wait_for_procs: + logger.debug("Waiting on TMUX xterm process %s", p) + o, e = p.communicate() + if p.wait(): + logger.warning("xterm proc failed: %s:", proc_error(p, o, e)) + + if error and item.config.option.cli_on_error: + # Really would like something better than using this global here. + # Not all tests use topogen though so get_topogen() won't work. + if Mininet.g_mnet_inst: + cli.cli(Mininet.g_mnet_inst, title=title, background=False) + else: + logger.error("Could not launch CLI b/c no mininet exists yet") + + if pause and isatty: + pause_test() + + +def coverage_finish(terminalreporter, config): + commander = Commander("pytest") + rundir = Path(config.option.rundir).resolve() + bdir = Path(os.environ["FRR_BUILD_DIR"]) + gcdadir = Path(os.environ["GCOV_PREFIX"]) + + logger.info("Creating .gcno ssymlink from '%s' to '%s'", gcdadir, bdir) + commander.cmd_raises( + f"cd {gcdadir}; bdir={bdir}" + + """ +for f in $(find . -name '*.gcda'); do + f=${f#./}; + f=${f%.gcda}.gcno; + ln -fs $bdir/$f $f; + touch -h -r $bdir/$f $f; + echo $f; +done""" + ) + + # Get the results into a summary file + data_file = rundir / "coverage.info" + logger.info("Gathering coverage data into: %s", data_file) + commander.cmd_raises( + f"lcov --directory {gcdadir} --capture --output-file {data_file}" + ) + + # Get coverage info filtered to a specific set of files + report_file = rundir / "coverage.info" + logger.debug("Generating coverage summary from: %s\n%s", report_file) + output = commander.cmd_raises(f"lcov --summary {data_file}") + logger.info("\nCOVERAGE-SUMMARY-START\n%s\nCOVERAGE-SUMMARY-END", output) + terminalreporter.write( + f"\nCOVERAGE-SUMMARY-START\n{output}\nCOVERAGE-SUMMARY-END\n" + ) + + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + # Only run if we are the top level test runner + is_xdist_worker = "PYTEST_XDIST_WORKER" in os.environ + if config.option.cov_topotest and not is_xdist_worker: + coverage_finish(terminalreporter, config) + + +# +# Add common fixtures available to all tests as parameters +# + +tgen = pytest.fixture(lib.fixtures.tgen) +topo = pytest.fixture(lib.fixtures.topo) diff --git a/tests/topotests/cspf_topo1/r1/isisd.conf b/tests/topotests/cspf_topo1/r1/isisd.conf new file mode 100644 index 0000000..2dc7329 --- /dev/null +++ b/tests/topotests/cspf_topo1/r1/isisd.conf @@ -0,0 +1,35 @@ +! +hostname r1 +! +interface lo + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis passive +! +interface r1-eth0 + ip router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r1-eth1 + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +router isis TE + net 49.0000.0000.0000.0001.00 + is-type level-2-only + topology ipv6-unicast + lsp-timers gen-interval 2 refresh-interval 10 max-lifetime 350 + mpls-te on + mpls-te router-address 10.0.255.1 + mpls-te router-address ipv6 2001:db8::1 + mpls-te export +! + diff --git a/tests/topotests/cspf_topo1/r1/sharpd.conf b/tests/topotests/cspf_topo1/r1/sharpd.conf new file mode 100644 index 0000000..465034f --- /dev/null +++ b/tests/topotests/cspf_topo1/r1/sharpd.conf @@ -0,0 +1,2 @@ +! +! diff --git a/tests/topotests/cspf_topo1/r1/zebra.conf b/tests/topotests/cspf_topo1/r1/zebra.conf new file mode 100644 index 0000000..3b615b0 --- /dev/null +++ b/tests/topotests/cspf_topo1/r1/zebra.conf @@ -0,0 +1,28 @@ +! +hostname r1 +! +interface lo + ip address 10.0.255.1/32 + ipv6 address 2001:db8::1/128 +! +interface r1-eth0 + ip address 10.0.0.1/24 + link-params + metric 20 + delay 10000 + max-bw 10e+10 + ava-bw 1.25e+08 + enable + exit-link-params +! +interface r1-eth1 + ip address 10.0.1.1/24 + ipv6 address 2001:db8:1::1:1/64 + link-params + metric 10 + delay 20000 + enable + exit-link-params +! +ip forwarding +! diff --git a/tests/topotests/cspf_topo1/r2/isisd.conf b/tests/topotests/cspf_topo1/r2/isisd.conf new file mode 100644 index 0000000..59bb3be --- /dev/null +++ b/tests/topotests/cspf_topo1/r2/isisd.conf @@ -0,0 +1,50 @@ +! +hostname r2 +! +! debug isis te-events +! +interface lo + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis passive +! +interface r2-eth0 + ip router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r2-eth1 + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r2-eth2 + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r2-eth3 + ip router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +router isis TE + net 49.0000.0000.0000.0002.00 + is-type level-2-only + topology ipv6-unicast + lsp-timers gen-interval 2 refresh-interval 10 max-lifetime 350 + mpls-te on + mpls-te router-address 10.0.255.2 + mpls-te router-address ipv6 2001:db8::2 +! diff --git a/tests/topotests/cspf_topo1/r2/zebra.conf b/tests/topotests/cspf_topo1/r2/zebra.conf new file mode 100644 index 0000000..55d9563 --- /dev/null +++ b/tests/topotests/cspf_topo1/r2/zebra.conf @@ -0,0 +1,46 @@ +! +hostname r2 +! +interface lo + ip address 10.0.255.2/32 + ipv6 address 2001:db8::2/128 +! +interface r2-eth0 + ip address 10.0.0.2/24 + link-params + metric 20 + delay 10000 + enable + exit-link-params +! +interface r2-eth1 + ip address 10.0.1.2/24 + ipv6 address 2001:db8:1::1:2/64 + link-params + metric 10 + delay 20000 + enable + exit-link-params +! +interface r2-eth2 + ip address 10.0.3.2/24 + ipv6 address 2001:db8:3::3:2/64 + link-params + metric 40 + delay 40000 + enable + exit-link-params +! +interface r2-eth3 + ip address 10.0.4.2/24 + ipv6 address 2001:db8:4::4:2/64 + link-params + metric 25 + delay 25000 + max-bw 10e+10 + use-bw 1.25e+8 + enable + exit-link-params +! +ip forwarding +! diff --git a/tests/topotests/cspf_topo1/r3/isisd.conf b/tests/topotests/cspf_topo1/r3/isisd.conf new file mode 100644 index 0000000..7d3e4de --- /dev/null +++ b/tests/topotests/cspf_topo1/r3/isisd.conf @@ -0,0 +1,36 @@ +! +hostname r3 +! +! debug isis te-events +! +interface lo + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis passive +! +interface r3-eth0 + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r3-eth1 + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +! +router isis TE + net 49.0000.0000.0000.0003.00 + is-type level-2-only + topology ipv6-unicast + lsp-timers gen-interval 2 refresh-interval 10 max-lifetime 350 + mpls-te on + mpls-te router-address 10.0.255.3 + mpls-te router-address ipv6 2001:db8::3 +! diff --git a/tests/topotests/cspf_topo1/r3/zebra.conf b/tests/topotests/cspf_topo1/r3/zebra.conf new file mode 100644 index 0000000..29a4c51 --- /dev/null +++ b/tests/topotests/cspf_topo1/r3/zebra.conf @@ -0,0 +1,27 @@ +! +hostname r3 +! +interface lo + ip address 10.0.255.3/32 + ipv6 address 2001:db8::3/128 +! +interface r3-eth0 + ip address 10.0.3.3/24 + ipv6 address 2001:db8:3::3:3/64 + link-params + metric 25 + delay 25000 + enable + admin-grp 0x20 + exit-link-params +! +interface r3-eth1 + ipv6 address 2001:db8:5::4:3/64 + link-params + enable + metric 10 + delay 10000 + exit-link-params +! +ip forwarding +! diff --git a/tests/topotests/cspf_topo1/r4/isisd.conf b/tests/topotests/cspf_topo1/r4/isisd.conf new file mode 100644 index 0000000..674b738 --- /dev/null +++ b/tests/topotests/cspf_topo1/r4/isisd.conf @@ -0,0 +1,42 @@ +! +hostname r4 +! +! debug isis te-events +! debug isis sr-events +! debug isis lsp-gen +! +interface lo + ip router isis TE + ipv6 router isis TE + isis circuit-type level-2-only + isis passive +! +interface r4-eth0 + ip router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +interface r4-eth1 + ipv6 router isis TE + isis circuit-type level-2-only + isis network point-to-point + isis hello-interval 1 + isis hello-multiplier 10 +! +! +router isis TE + net 49.0000.0000.0000.0004.00 + is-type level-2-only + topology ipv6-unicast + lsp-timers gen-interval 2 refresh-interval 10 max-lifetime 350 + mpls-te on + mpls-te router-address 10.0.255.4 + mpls-te router-address ipv6 2001:db8::4 + segment-routing on + segment-routing global-block 10000 19999 local-block 5000 5999 + segment-routing node-msd 12 + segment-routing prefix 10.0.255.4/32 index 400 no-php-flag + segment-routing prefix 2001:db8:ffff::4/128 index 1400 no-php-flag +! diff --git a/tests/topotests/cspf_topo1/r4/zebra.conf b/tests/topotests/cspf_topo1/r4/zebra.conf new file mode 100644 index 0000000..bf5306d --- /dev/null +++ b/tests/topotests/cspf_topo1/r4/zebra.conf @@ -0,0 +1,26 @@ +! +hostname r4 +! +interface lo + ip address 10.0.255.4/32 + ipv6 address 2001:db8::4/128 +! +interface r4-eth0 + ip address 10.0.4.4/24 + ipv6 address 2001:db8:4::2:4/64 + link-params + metric 40 + delay 40000 + enable + exit-link-params +! +interface r4-eth1 + ipv6 address 2001:db8:5::3:4/64 + link-params + metric 10 + delay 10000 + enable + exit-link-params +! +ip forwarding +! diff --git a/tests/topotests/cspf_topo1/reference/cspf-failed-dst.txt b/tests/topotests/cspf_topo1/reference/cspf-failed-dst.txt new file mode 100644 index 0000000..df792ce --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-failed-dst.txt @@ -0,0 +1 @@ +Path computation failed: 2 diff --git a/tests/topotests/cspf_topo1/reference/cspf-failed-same.txt b/tests/topotests/cspf_topo1/reference/cspf-failed-same.txt new file mode 100644 index 0000000..48f6fd9 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-failed-same.txt @@ -0,0 +1 @@ +Path computation failed: 3 diff --git a/tests/topotests/cspf_topo1/reference/cspf-failed-src.txt b/tests/topotests/cspf_topo1/reference/cspf-failed-src.txt new file mode 100644 index 0000000..62d00cc --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-failed-src.txt @@ -0,0 +1 @@ +Path computation failed: 1 diff --git a/tests/topotests/cspf_topo1/reference/cspf-failed.txt b/tests/topotests/cspf_topo1/reference/cspf-failed.txt new file mode 100644 index 0000000..de53a93 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-failed.txt @@ -0,0 +1 @@ +Path computation failed: 0 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv4-delay.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv4-delay.txt new file mode 100644 index 0000000..2cc0428 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv4-delay.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 35000 + Edges: 10.0.0.2 10.0.4.4 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv4-metric.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv4-metric.txt new file mode 100644 index 0000000..060a39c --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv4-metric.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 20 + Edges: 10.0.0.2 10.0.4.4 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv4-te-metric.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv4-te-metric.txt new file mode 100644 index 0000000..6d98306 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv4-te-metric.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 35 + Edges: 10.0.1.2 10.0.4.4 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv6-delay.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv6-delay.txt new file mode 100644 index 0000000..b65869b --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv6-delay.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 70000 + Edges: 2001:db8:1::1:2 2001:db8:3::3:3 2001:db8:5::3:4 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv6-metric.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv6-metric.txt new file mode 100644 index 0000000..5acbb74 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv6-metric.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 30 + Edges: 2001:db8:1::1:2 2001:db8:3::3:3 2001:db8:5::3:4 diff --git a/tests/topotests/cspf_topo1/reference/cspf-ipv6-te-metric.txt b/tests/topotests/cspf_topo1/reference/cspf-ipv6-te-metric.txt new file mode 100644 index 0000000..2290a04 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/cspf-ipv6-te-metric.txt @@ -0,0 +1,3 @@ +Path computation success + Cost: 60 + Edges: 2001:db8:1::1:2 2001:db8:3::3:3 2001:db8:5::3:4 diff --git a/tests/topotests/cspf_topo1/reference/sharp-ted.json b/tests/topotests/cspf_topo1/reference/sharp-ted.json new file mode 100644 index 0000000..359b655 --- /dev/null +++ b/tests/topotests/cspf_topo1/reference/sharp-ted.json @@ -0,0 +1,858 @@ +{ + "ted":{ + "name":"Sharp", + "key":1, + "verticesCount":4, + "edgesCount":14, + "subnetsCount":22, + "vertices":[ + { + "vertex-id":1, + "status":"Sync", + "origin":"ISIS_L2", + "name":"r1", + "router-id":"10.0.255.1", + "router-id-v6":"2001:db8::1" + }, + { + "vertex-id":2, + "status":"Sync", + "origin":"ISIS_L2", + "name":"r2", + "router-id":"10.0.255.2", + "router-id-v6":"2001:db8::2" + }, + { + "vertex-id":3, + "status":"Sync", + "origin":"ISIS_L2", + "name":"r3", + "router-id":"10.0.255.3", + "router-id-v6":"2001:db8::3" + }, + { + "vertex-id":4, + "status":"Sync", + "origin":"ISIS_L2", + "name":"r4", + "router-id":"10.0.255.4", + "router-id-v6":"2001:db8::4", + "segment-routing":{ + "srgb-size":10000, + "srgb-lower":10000, + "algorithms":[ + { + "0":"SPF" + } + ], + "srlb-size":1000, + "srlb-lower":5000, + "msd":12 + } + } + ], + "edges":[ + { + "edge-id":"2001:db8:1::1:1", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "local-vertex-id":1, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address-v6":"2001:db8:1::1:1", + "remote-address-v6":"2001:db8:1::1:2", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":20000 + } + }, + { + "edge-id":"2001:db8:1::1:2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":1, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address-v6":"2001:db8:1::1:2", + "remote-address-v6":"2001:db8:1::1:1", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":20000 + } + }, + { + "edge-id":"2001:db8:3::3:2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":3, + "metric":10, + "edge-attributes":{ + "te-metric":40, + "local-address-v6":"2001:db8:3::3:2", + "remote-address-v6":"2001:db8:3::3:3", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":40000 + } + }, + { + "edge-id":"2001:db8:3::3:3", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "local-vertex-id":3, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":25, + "admin-group":32, + "local-address-v6":"2001:db8:3::3:3", + "remote-address-v6":"2001:db8:3::3:2", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":25000 + } + }, + { + "edge-id":"2001:db8:5::3:4", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "local-vertex-id":4, + "remote-vertex-id":3, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address-v6":"2001:db8:5::3:4", + "remote-address-v6":"2001:db8:5::4:3", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":10000 + }, + "segment-routing":[ + { + "flags":"0xb0", + "weight":0 + } + ] + }, + { + "edge-id":"2001:db8:5::4:3", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "local-vertex-id":3, + "remote-vertex-id":4, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address-v6":"2001:db8:5::4:3", + "remote-address-v6":"2001:db8:5::3:4", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":10000 + } + }, + { + "edge-id":"10.0.0.1", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "local-vertex-id":1, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":20, + "local-address":"10.0.0.1", + "remote-address":"10.0.0.2", + "max-link-bandwidth":99999997952, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":10000, + "available-bandwidth":125000000.0 + } + }, + { + "edge-id":"10.0.0.2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":1, + "metric":10, + "edge-attributes":{ + "te-metric":20, + "local-address":"10.0.0.2", + "remote-address":"10.0.0.1", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":10000 + } + }, + { + "edge-id":"10.0.1.1", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "local-vertex-id":1, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address":"10.0.1.1", + "remote-address":"10.0.1.2", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":20000 + } + }, + { + "edge-id":"10.0.1.2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":1, + "metric":10, + "edge-attributes":{ + "te-metric":10, + "local-address":"10.0.1.2", + "remote-address":"10.0.1.1", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":20000 + } + }, + { + "edge-id":"10.0.3.2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":3, + "metric":10, + "edge-attributes":{ + "te-metric":40, + "local-address":"10.0.3.2", + "remote-address":"10.0.3.3", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":40000 + } + }, + { + "edge-id":"10.0.3.3", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "local-vertex-id":3, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":25, + "admin-group":32, + "local-address":"10.0.3.3", + "remote-address":"10.0.3.2", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":25000 + } + }, + { + "edge-id":"10.0.4.2", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "local-vertex-id":2, + "remote-vertex-id":4, + "metric":10, + "edge-attributes":{ + "te-metric":25, + "local-address":"10.0.4.2", + "remote-address":"10.0.4.4", + "max-link-bandwidth":99999997952, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":25000, + "utilized-bandwidth":125000000.0 + } + }, + { + "edge-id":"10.0.4.4", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "local-vertex-id":4, + "remote-vertex-id":2, + "metric":10, + "edge-attributes":{ + "te-metric":40, + "local-address":"10.0.4.4", + "remote-address":"10.0.4.2", + "max-link-bandwidth":1250000, + "max-resv-link-bandwidth":1250000, + "unreserved-bandwidth":[ + { + "class-type-0":1250000 + }, + { + "class-type-1":1250000 + }, + { + "class-type-2":1250000 + }, + { + "class-type-3":1250000 + }, + { + "class-type-4":1250000 + }, + { + "class-type-5":1250000 + }, + { + "class-type-6":1250000 + }, + { + "class-type-7":1250000 + } + ], + "delay":40000 + }, + "segment-routing":[ + { + "flags":"0x30", + "weight":0 + } + ] + } + ], + "subnets":[ + { + "subnet-id":"10.0.0.1/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "vertex-id":1, + "metric":10 + }, + { + "subnet-id":"10.0.0.2/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"10.0.1.1/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "vertex-id":1, + "metric":10 + }, + { + "subnet-id":"10.0.1.2/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"10.0.3.2/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"10.0.3.3/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "vertex-id":3, + "metric":10 + }, + { + "subnet-id":"10.0.4.2/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"10.0.4.4/24", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "vertex-id":4, + "metric":10 + }, + { + "subnet-id":"10.0.255.1/32", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "vertex-id":1, + "metric":10 + }, + { + "subnet-id":"10.0.255.2/32", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"10.0.255.3/32", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "vertex-id":3, + "metric":10 + }, + { + "subnet-id":"10.0.255.4/32", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "vertex-id":4, + "metric":10, + "segment-routing":{ + "pref-sid":400, + "algo":0, + "flags":"0x60" + } + }, + { + "subnet-id":"2001:db8:1::1:1/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "vertex-id":1, + "metric":10 + }, + { + "subnet-id":"2001:db8:1::1:2/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"2001:db8:3::3:2/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"2001:db8:3::3:3/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "vertex-id":3, + "metric":10 + }, + { + "subnet-id":"2001:db8:5::3:4/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "vertex-id":4, + "metric":10 + }, + { + "subnet-id":"2001:db8:5::4:3/64", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "vertex-id":3, + "metric":10 + }, + { + "subnet-id":"2001:db8::1/128", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0001", + "vertex-id":1, + "metric":10 + }, + { + "subnet-id":"2001:db8::2/128", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0002", + "vertex-id":2, + "metric":10 + }, + { + "subnet-id":"2001:db8::3/128", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0003", + "vertex-id":3, + "metric":10 + }, + { + "subnet-id":"2001:db8::4/128", + "status":"Sync", + "origin":"ISIS_L2", + "advertised-router":"0000.0000.0004", + "vertex-id":4, + "metric":10 + } + ] + } +} diff --git a/tests/topotests/cspf_topo1/test_cspf_topo1.py b/tests/topotests/cspf_topo1/test_cspf_topo1.py new file mode 100644 index 0000000..072a2dd --- /dev/null +++ b/tests/topotests/cspf_topo1/test_cspf_topo1.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_cspf_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2022 by Orange +# Author: Olivier Dugeon +# + +""" +test_cspf_topo1.py: Test the FRR Constraint Shortest Path First algorithm. + + +------------+ + | | + | R1 | + | 10.0.225.1 | + | | + +------------+ + r1-eth0| |r1-eth1 + | | + 10.0.0.0/24| |10.0.1.0/24 + | |2001:db8:1:/64 + | | + r2-eth0| |r2-eth1 + +------------+ +------------+ + | | | | + | R2 |r2-eth2 r3-eth0| R3 | + | 10.0.255.2 +------------------+ 10.0.255.3 | + | | 10.0.3.0/24 | | + +------------+ 2001:db8:3:/64 +------+-----+ + r2-eth3| r3-eth1| + | | + 10.0.4.0/24| | + | | + | | + r4-eth0| 2001:db8:5:/64| + +------------+ | + | | | + | R4 |r4-eth1 | + | 10.0.255.4 +-------------------------+ + | | + +------------+ + +""" + +import os +import sys +import json +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 + +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# and Finally pytest +import pytest + +pytestmark = [pytest.mark.isisd] + + +def build_topo(tgen): + "Build function" + + # Create 4 routers + for routern in range(1, 5): + tgen.add_router("r{}".format(routern)) + + # Interconect router 1 and 2 with 2 links + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # Interconect router 3 and 2 + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r2"]) + + # Interconect router 4 and 2 + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["r4"]) + switch.add_link(tgen.gears["r2"]) + + # Interconnect router 3 and 4 + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["r3"]) + switch.add_link(tgen.gears["r4"]) + + +def setup_module(mod): + "Sets up the pytest environment" + + logger.info("\n\n---- Starting CSPF tests ----\n") + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, os.path.join(CWD, "{}/isisd.conf".format(rname)) + ) + if rname == "r1": + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format("r1")) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(): + "Teardown the pytest environment" + + tgen = get_topogen() + tgen.stop_topology() + + logger.info("\n\n---- CSPF tests End ----\n") + + +def compare_ted_json_output(tgen, rname, fileref): + "Compare TED JSON output" + + logger.info('Comparing router "%s" TED output', rname) + + filename = "{}/reference/{}".format(CWD, fileref) + expected = json.loads(open(filename).read()) + command = "show sharp ted json" + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=60, wait=2) + assertmsg = '"{}" TED JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +def compare_cspf_output(tgen, rname, fileref, src, dst, cost, bw=""): + "Compare CSPF output" + + logger.info('Comparing router "%s" CSPF output', rname) + + filename = "{}/reference/{}".format(CWD, fileref) + expected = open(filename).read() + command = "show sharp cspf source {} destination {} {} {}".format( + src, dst, cost, bw + ) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial( + topotest.router_output_cmp, tgen.gears[rname], command, expected + ) + result, diff = topotest.run_and_expect(test_func, "", count=5, wait=2) + assert result, "CSPF output mismatches the expected result on {}:\n{}".format( + rname, diff + ) + + +def setup_testcase(msg): + "Setup test case" + + logger.info(msg) + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + return tgen + + +# Note that all routers must discover the same Network Topology, so the same TED. + + +def test_step1(): + "Step1: Check initial topology" + + tgen = setup_testcase("Step1: test initial IS-IS TE Data Base import") + tgen.net["r1"].cmd('vtysh -c "sharp import-te"') + + compare_ted_json_output(tgen, "r1", "sharp-ted.json") + + +def test_step2(): + "Step2: Test CSPF from r1 to r4 for IPv4 with various metric" + + tgen = setup_testcase("Step2: CSPF(r1, r4, IPv4)") + + compare_cspf_output( + tgen, "r1", "cspf-ipv4-metric.txt", "10.0.0.1", "10.0.255.4", "metric 50" + ) + compare_cspf_output( + tgen, "r1", "cspf-ipv4-te-metric.txt", "10.0.255.1", "10.0.4.4", "te-metric 50" + ) + compare_cspf_output( + tgen, "r1", "cspf-ipv4-delay.txt", "10.0.255.1", "10.0.255.4", "delay 50000" + ) + compare_cspf_output( + tgen, + "r1", + "cspf-ipv4-delay.txt", + "10.0.255.1", + "10.0.255.4", + "delay 50000", + "rsv 7 1000000", + ) + + +def test_step3(): + "Step3: Test CSPF from r1 to r4 for IPv6 with various metric" + + tgen = setup_testcase("Step2: CSPF(r1, r4, IPv6)") + + compare_cspf_output( + tgen, + "r1", + "cspf-ipv6-metric.txt", + "2001:db8:1::1:1", + "2001:db8::4", + "metric 50", + ) + compare_cspf_output( + tgen, + "r1", + "cspf-ipv6-te-metric.txt", + "2001:db8::1", + "2001:db8:5::3:4", + "te-metric 80", + ) + compare_cspf_output( + tgen, "r1", "cspf-ipv6-delay.txt", "2001:db8::1", "2001:db8::4", "delay 80000" + ) + compare_cspf_output( + tgen, + "r1", + "cspf-ipv6-delay.txt", + "2001:db8::1", + "2001:db8::4", + "delay 80000", + "rsv 7 1000000", + ) + + +def test_step4(): + "Step4: Test CSPF from r1 to r4 with no possible path" + + tgen = setup_testcase("Step2: CSPF(r1, r4, failure)") + + compare_cspf_output( + tgen, "r1", "cspf-failed.txt", "10.0.255.1", "10.0.255.4", "metric 10" + ) + compare_cspf_output( + tgen, "r1", "cspf-failed.txt", "2001:db8::1", "2001:db8::4", "te-metric 50" + ) + compare_cspf_output( + tgen, "r1", "cspf-failed.txt", "10.0.255.1", "10.0.255.4", "delay 5000" + ) + compare_cspf_output( + tgen, + "r1", + "cspf-failed.txt", + "2001:db8::1", + "2001:db8::4", + "delay 80000", + "rsv 7 10000000", + ) + compare_cspf_output( + tgen, "r1", "cspf-failed-src.txt", "10.0.0.3", "10.0.255.4", "metric 10" + ) + compare_cspf_output( + tgen, "r1", "cspf-failed-dst.txt", "10.0.0.1", "10.0.4.40", "metric 10" + ) + compare_cspf_output( + tgen, "r1", "cspf-failed-same.txt", "10.0.0.1", "10.0.0.1", "metric 10" + ) + + +def test_memory_leak(): + "Run the memory leak test and report results." + + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/docker/README.md b/tests/topotests/docker/README.md new file mode 100644 index 0000000..2b40994 --- /dev/null +++ b/tests/topotests/docker/README.md @@ -0,0 +1,72 @@ +# Topotests in Docker + +## Quickstart + +If you have Docker installed, you can run the topotests in Docker. +The easiest way to do this, is to use the make targets from this +repository. + +Your current user needs to have access to the Docker daemon. Alternatively +you can run these commands as root. + +```console +make topotests +``` + +This command will pull the most recent topotests image from dockerhub, compile FRR inside +of it, and run the topotests. + +## Advanced Usage + +Internally, the topotests make target uses a shell script to pull the image and spawn the docker +container. + +There are several environment variables which can be used to modify the behavior +of the script, these can be listed by calling it with `-h`: + +```console +./tests/topotests/docker/frr-topotests.sh -h +``` + +For example, a volume is used to cache build artifacts between multiple runs +of the image. If you need to force a complete recompile, you can set `TOPOTEST_CLEAN`: + +```console +TOPOTEST_CLEAN=1 ./tests/topotests/docker/frr-topotests.sh +``` + +By default, `frr-topotests.sh` will build frr and run pytest. If you append +arguments and the first one starts with `/` or `./`, they will replace the call to +pytest. If the appended arguments do not match this patttern, they will be provided to +pytest as arguments. + +So, to run a specific test with more verbose logging: + +```console +./tests/topotests/docker/frr-topotests.sh -vv -s all-protocol-startup/test_all_protocol_startup.py +``` + +And to compile FRR but drop into a shell instead of running pytest: + +```console +./tests/topotests/docker/frr-topotests.sh /bin/bash +``` + +## Development + +The docker image just includes all the components to run the topotests, but not the topotests +themselves. So if you just want to write tests and don't want to make changes to the environment +provided by the docker image. You don't need to build your own docker image if you do not want to. + +When developing new tests, there is one caveat though: The startup script of the container will +run a `git-clean` on its copy of the FRR tree to avoid any pollution of the container with build +artefacts from the host. This will also result in your newly written tests being unavailable in the +container unless at least added to the index with `git-add`. + +If you do want to test changes to the docker image, you can locally build the image and run the tests +without pulling from the registry using the following commands: + +```console +make topotests-build +TOPOTEST_PULL=0 make topotests +``` diff --git a/tests/topotests/docker/build.sh b/tests/topotests/docker/build.sh new file mode 100755 index 0000000..aec2058 --- /dev/null +++ b/tests/topotests/docker/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +cd "$(dirname "$0")"/.. + +exec docker build --pull \ + --compress \ + -t frrouting/topotests:latest \ + . diff --git a/tests/topotests/docker/frr-topotests.sh b/tests/topotests/docker/frr-topotests.sh new file mode 100755 index 0000000..ce373d9 --- /dev/null +++ b/tests/topotests/docker/frr-topotests.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +set -e + +if [[ "$1" = "-h" ]] || [[ "$1" = "--help" ]]; then + cat >&2 <<-EOF + + This script runs the FRRouting topotests on the FRR tree + in the current working directory. + + Usage: $0 [args...] + + If any arguments are provided and the first argument starts with / or ./ + the arguments are interpreted as command and will be executed instead + of pytest. + + Behavior can be further modified by the following environment variables: + + TOPOTEST_AUTOLOAD If set to 1, the script will try to load necessary + kernel modules without asking for confirmation first. + + TOPOTEST_NOLOAD If set to 1, don't try to load necessary kernel + modules and don't even ask. + + TOPOTEST_BUILDCACHE Docker volume used for caching multiple FRR builds + over container runs. By default a + \`topotest-buildcache\` volume will be created for + that purpose. + + TOPOTEST_CLEAN Clean all previous build artifacts prior to + building. Disabled by default, set to 1 to enable. + + TOPOTEST_DOC Build the documentation associated with FRR. + Disabled by default, set to 1 to enable. + + TOPOTEST_FRR If set, don't test the FRR in the current working + directory, but the one at the given path. + + TOPOTEST_LOGS If set, don't use \`/tmp/topotest_logs\` directory + but use the provided path instead. + + TOPOTEST_OPTIONS These options are appended to the docker-run + command for starting the tests. + + TOPOTEST_PULL If set to 0, don't try to pull the most recent + version of the docker image from dockerhub. + + TOPOTEST_SANITIZER Controls whether to use the address sanitizer. + Enabled by default, set to 0 to disable. + + TOPOTEST_VERBOSE Show detailed build output. + Enabled by default, set to 0 to disable. + + EOF + exit 1 +fi + +# +# These two modules are needed to run the MPLS tests. +# They are often not automatically loaded. +# +# We cannot load them from the container since we don't +# have host kernel modules available there. If we load +# them from the host however, they can be used just fine. +# + +export PATH="$PATH:/sbin:/usr/sbin:/usr/local/sbin" + +if [ "$TOPOTEST_NOLOAD" != "1" ]; then + for module in mpls-router mpls-iptunnel; do + if modprobe -n $module 2> /dev/null; then + : + else + # If the module doesn't exist, we cannot do anything about it + continue + fi + + if [ $(grep -c ${module/-/_} /proc/modules) -ne 0 ]; then + # If the module is loaded, we don't have to do anything + continue + fi + + if [ "$TOPOTEST_AUTOLOAD" != "1" ]; then + echo "To run all the possible tests, we need to load $module." + echo -n "Do you want to proceed? [y/n] " + read answer + if [ x"$answer" != x"y" ]; then + echo "Not loading." + continue + fi + fi + + if [ x"$(whoami)" = x"root" ]; then + modprobe $module + else + sudo modprobe $module + fi + done +fi + +if [ -z "$TOPOTEST_LOGS" ]; then + mkdir -p /tmp/topotest_logs + TOPOTEST_LOGS="/tmp/topotest_logs" +fi + +if [ -z "$TOPOTEST_FRR" ]; then + TOPOTEST_FRR="$(git rev-parse --show-toplevel || true)" + if [ -z "$TOPOTEST_FRR" ]; then + echo "Could not determine base of FRR tree." >&2 + echo "frr-topotests only works if you have your tree in git." >&2 + exit 1 + fi + git -C "$TOPOTEST_FRR" ls-files -z > "${TOPOTEST_LOGS}/git-ls-files" +fi + +if [ -z "$TOPOTEST_BUILDCACHE" ]; then + TOPOTEST_BUILDCACHE=topotest-buildcache + docker volume inspect "${TOPOTEST_BUILDCACHE}" &> /dev/null \ + || docker volume create "${TOPOTEST_BUILDCACHE}" +fi + +if [ "${TOPOTEST_PULL:-1}" = "1" ]; then + docker pull frrouting/topotests:latest +fi + +if [[ -n "$TMUX" ]]; then + TMUX_OPTIONS="-v $(dirname $TMUX):$(dirname $TMUX) -e TMUX=$TMUX -e TMUX_PANE=$TMUX_PANE" +fi + +if [[ -n "$STY" ]]; then + SCREEN_OPTIONS="-v /run/screen:/run/screen -e STY=$STY" +fi +set -- --rm -i \ + -v "$HOME:$HOME:ro" \ + -v "$TOPOTEST_LOGS:/tmp" \ + -v "$TOPOTEST_FRR:/root/host-frr:ro" \ + -v "$TOPOTEST_BUILDCACHE:/root/persist" \ + -e "TOPOTEST_CLEAN=$TOPOTEST_CLEAN" \ + -e "TOPOTEST_VERBOSE=$TOPOTEST_VERBOSE" \ + -e "TOPOTEST_DOC=$TOPOTEST_DOC" \ + -e "TOPOTEST_SANITIZER=$TOPOTEST_SANITIZER" \ + --privileged \ + $SCREEN_OPTINS \ + $TMUX_OPTIONS \ + $TOPOTEST_OPTIONS \ + frrouting/topotests:latest "$@" + +if [ -t 0 ]; then + set -- -t "$@" +fi + +exec docker run "$@" diff --git a/tests/topotests/docker/inner/compile_frr.sh b/tests/topotests/docker/inner/compile_frr.sh new file mode 100755 index 0000000..4a88dc6 --- /dev/null +++ b/tests/topotests/docker/inner/compile_frr.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +set -e + +# Load shared functions +CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. $CDIR/funcs.sh + +# +# Script begin +# + +if [ "${TOPOTEST_CLEAN}" != "0" ]; then + log_info "Cleaning FRR builddir..." + rm -rf $FRR_BUILD_DIR &> /dev/null +fi + +log_info "Syncing FRR source with host..." +mkdir -p $FRR_BUILD_DIR +rsync -a --info=progress2 \ + --from0 --files-from=/tmp/git-ls-files \ + --chown root:root \ + $FRR_HOST_DIR/. $FRR_BUILD_DIR/ + +cd "$FRR_BUILD_DIR" || \ + log_fatal "failed to find frr directory" + +if [ "${TOPOTEST_VERBOSE}" != "0" ]; then + exec 3>&1 +else + exec 3>/dev/null +fi + +log_info "Building FRR..." + +if [ ! -e configure ]; then + bash bootstrap.sh >&3 || \ + log_fatal "failed to bootstrap configuration" +fi + +if [ "${TOPOTEST_DOC}" != "0" ]; then + EXTRA_CONFIGURE+=" --enable-doc " +else + EXTRA_CONFIGURE+=" --disable-doc " +fi + +if [ ! -e Makefile ]; then + if [ "${TOPOTEST_SANITIZER}" != "0" ]; then + export CC="gcc" + export CFLAGS="-O1 -g -fsanitize=address -fno-omit-frame-pointer" + export LDFLAGS="-g -fsanitize=address -ldl" + touch .address_sanitizer + else + rm -f .address_sanitizer + fi + + bash configure >&3 \ + --enable-static-bin \ + --enable-static \ + --enable-shared \ + --enable-dev-build \ + --with-moduledir=/usr/lib/frr/modules \ + --prefix=/usr \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --sbindir=/usr/lib/frr \ + --enable-multipath=0 \ + --enable-fpm \ + --enable-sharpd \ + $EXTRA_CONFIGURE \ + --with-pkg-extra-version=-topotests \ + || log_fatal "failed to configure the sources" +fi + +# if '.address_sanitizer' file exists it means we are using address sanitizer. +if [ -f .address_sanitizer ]; then + make -C lib CFLAGS="-g -O2" LDFLAGS="-g" clippy >&3 +fi + +make -j$(cpu_count) >&3 || \ + log_fatal "failed to build the sources" + +make install >/dev/null || \ + log_fatal "failed to install frr" diff --git a/tests/topotests/docker/inner/entrypoint.sh b/tests/topotests/docker/inner/entrypoint.sh new file mode 100755 index 0000000..44e16db --- /dev/null +++ b/tests/topotests/docker/inner/entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +# Load shared functions +CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. $CDIR/funcs.sh + +set -e + +# +# Script begin +# +"${CDIR}/compile_frr.sh" +"${CDIR}/openvswitch.sh" + +cd "${FRR_BUILD_DIR}/tests/topotests" + +log_info "Setting permissions on /tmp so we can generate logs" +chmod 1777 /tmp + +if [ $# -eq 0 ] || ([[ "$1" != /* ]] && [[ "$1" != ./* ]]); then + export TOPOTESTS_CHECK_MEMLEAK=/tmp/memleak_ + export TOPOTESTS_CHECK_STDERR=Yes + set -- pytest \ + --junitxml /tmp/topotests.xml \ + "$@" +fi + +exec "$@" diff --git a/tests/topotests/docker/inner/funcs.sh b/tests/topotests/docker/inner/funcs.sh new file mode 100755 index 0000000..4ebacbb --- /dev/null +++ b/tests/topotests/docker/inner/funcs.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +FRR_HOST_DIR=/root/host-frr +FRR_BUILD_DIR=/root/persist/frr-build + +if [ ! -L "/root/frr" ]; then + ln -s $FRR_BUILD_DIR /root/frr +fi + +[ -z $TOPOTEST_CLEAN ] && TOPOTEST_CLEAN=0 +[ -z $TOPOTEST_VERBOSE ] && TOPOTEST_VERBOSE=1 +[ -z $TOPOTEST_DOC ] && TOPOTEST_DOC=0 +[ -z $TOPOTEST_SANITIZER ] && TOPOTEST_SANITIZER=1 + +log_info() { + local msg=$1 + + echo -e "=> $msg" +} + +log_error() { + local msg=$1 + + echo -e "E: $msg" 2>&1 +} + +log_warning() { + local msg=$1 + + echo -e "W: $msg" 2>&1 +} + +log_fatal() { + local msg=$1 + + echo -e "F: $msg" 2>&1 + + exit 1 +} + +cpu_count() { + local cpu_count + + cpu_count=$(cat /proc/cpuinfo | grep -w processor | wc -l) + if [ $? -eq 0 ]; then + echo -n $cpu_count + else + echo -n 2 + fi +} diff --git a/tests/topotests/docker/inner/motd.txt b/tests/topotests/docker/inner/motd.txt new file mode 100644 index 0000000..1e2f34f --- /dev/null +++ b/tests/topotests/docker/inner/motd.txt @@ -0,0 +1,15 @@ +Welcome to the topotests container. + +Here are some useful tips: +* After changing the FRR/Topotests sources, you may rebuild them + using the command `compile_frr.sh`. The build command has the + following environment variables: + - TOPOTEST_CLEAN: whether we should distclean or not (disabled by default) + - TOPOTEST_VERBOSE: show build messages (enabled by default) + - TOPOTEST_DOC: whether we should build docs or not (disabled by default) + - TOPOTEST_SANITIZER: whether we should use the address sanitizer (enabled by default) + + Usage example: env TOPOTEST_CLEAN=1 compile_frr.sh + +* The topotests log directory can be found on your host machine on + `/tmp/topotests_logs`. diff --git a/tests/topotests/docker/inner/openvswitch.sh b/tests/topotests/docker/inner/openvswitch.sh new file mode 100755 index 0000000..926a25c --- /dev/null +++ b/tests/topotests/docker/inner/openvswitch.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT +# +# Copyright 2018 Network Device Education Foundation, Inc. ("NetDEF") + +# Load shared functions +CDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. $CDIR/funcs.sh + +# +# Script begin +# + +log_info "Configuring OpenvSwitch...." + +# Configure OpenvSwitch so we are able to run mininet +mkdir -p /var/run/openvswitch +ovsdb-tool create /etc/openvswitch/conf.db \ + /usr/share/openvswitch/vswitch.ovsschema +ovsdb-server /etc/openvswitch/conf.db \ + --remote=punix:/var/run/openvswitch/db.sock \ + --remote=ptcp:6640 --pidfile=ovsdb-server.pid >/dev/null 2>/dev/null & \ + disown +ovs-vswitchd >/dev/null 2>/dev/null & disown + +sleep 2 + +ovs-vsctl --no-wait -- init +ovs_version=$(ovs-vsctl -V | grep ovs-vsctl | awk '{print $4}') +ovs_db_version=$(\ + ovsdb-tool schema-version /usr/share/openvswitch/vswitch.ovsschema) +ovs-vsctl --no-wait -- set Open_vSwitch . db-version="${ovs_db_version}" +ovs-vsctl --no-wait -- set Open_vSwitch . ovs-version="${ovs_version}" +ovs-vsctl --no-wait -- set Open_vSwitch . system-type="docker-ovs" +ovs-vsctl --no-wait -- set Open_vSwitch . system-version="0.1" +ovs-vsctl --no-wait -- \ + set Open_vSwitch . external-ids:system-id=`cat /proc/sys/kernel/random/uuid` +ovs-vsctl --no-wait -- set-manager ptcp:6640 +ovs-appctl -t ovsdb-server \ + ovsdb-server/add-remote db:Open_vSwitch,Open_vSwitch,manager_options diff --git a/tests/topotests/eigrp_topo1/r1/eigrpd.conf b/tests/topotests/eigrp_topo1/r1/eigrpd.conf new file mode 100644 index 0000000..6c800ab --- /dev/null +++ b/tests/topotests/eigrp_topo1/r1/eigrpd.conf @@ -0,0 +1,8 @@ +log file eigrpd.log +! +router eigrp 1 + network 0.0.0.0/0 +! +line vty +! + diff --git a/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.json b/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.json new file mode 100644 index 0000000..be0fdcf --- /dev/null +++ b/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.json @@ -0,0 +1,32 @@ +{ + "P": { + "192.168.1.0/24": { + "fd": "28160", + "interface": " r1-eth0", + "serno": "0", + "successors": "1", + "via": "Connected" + }, + "192.168.3.0/24": { + "fd": "33280", + "interface": " r1-eth1", + "serno": "0", + "successors": "1", + "via": "193.1.1.2 (33280/30720)" + }, + "193.1.1.0/26": { + "fd": "28160", + "interface": " r1-eth1", + "serno": "0", + "successors": "1", + "via": "Connected" + }, + "193.1.2.0/24": { + "fd": "30720", + "interface": " r1-eth1", + "serno": "0", + "successors": "1", + "via": "193.1.1.2 (30720/28160)" + } + } +} diff --git a/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.ref b/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.ref new file mode 100644 index 0000000..a2d7b33 --- /dev/null +++ b/tests/topotests/eigrp_topo1/r1/show_ip_eigrp.ref @@ -0,0 +1,14 @@ + +EIGRP Topology Table for AS(1)/ID(193.1.1.1) + +Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply + r - reply Status, s - sia Status + +P 193.1.1.0/26, 1 successors, FD is 28160, serno: 0 + via Connected, r1-eth1 +P 192.168.1.0/24, 1 successors, FD is 28160, serno: 0 + via Connected, r1-eth0 +P 193.1.2.0/24, 1 successors, FD is 30720, serno: 0 + via 193.1.1.2 (30720/28160), r1-eth1 +P 192.168.3.0/24, 1 successors, FD is 33280, serno: 0 + via 193.1.1.2 (33280/30720), r1-eth1 diff --git a/tests/topotests/eigrp_topo1/r1/show_ip_route.json_ref b/tests/topotests/eigrp_topo1/r1/show_ip_route.json_ref new file mode 100644 index 0000000..26fa7ca --- /dev/null +++ b/tests/topotests/eigrp_topo1/r1/show_ip_route.json_ref @@ -0,0 +1,90 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + }, + { + "prefix":"192.168.1.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth0", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"eigrp", + "selected":true, + "metric":33280, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + }, + { + "prefix":"193.1.1.0/26", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"eigrp", + "selected":true, + "metric":30720, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.2", + "afi":"ipv4", + "interfaceName":"r1-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/eigrp_topo1/r1/zebra.conf b/tests/topotests/eigrp_topo1/r1/zebra.conf new file mode 100644 index 0000000..51579a7 --- /dev/null +++ b/tests/topotests/eigrp_topo1/r1/zebra.conf @@ -0,0 +1,20 @@ +log file zebra.log +! debug zebra rib detail +! +hostname r1 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + description to sw2 - RIPv2 interface + ip address 193.1.1.1/26 + no link-detect +! +ip forwarding +ipv6 forwarding +! +! +line vty +! + diff --git a/tests/topotests/eigrp_topo1/r2/eigrpd.conf b/tests/topotests/eigrp_topo1/r2/eigrpd.conf new file mode 100644 index 0000000..56c747d --- /dev/null +++ b/tests/topotests/eigrp_topo1/r2/eigrpd.conf @@ -0,0 +1,7 @@ +log file eigrpd.log +! +! +router eigrp 1 + network 193.1.1.0/26 + network 193.1.2.0/24 + diff --git a/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.json b/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.json new file mode 100644 index 0000000..ae9f441 --- /dev/null +++ b/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.json @@ -0,0 +1,32 @@ +{ + "P": { + "192.168.1.0/24": { + "fd": "30720", + "interface": " r2-eth0", + "serno": "0", + "successors": "1", + "via": "193.1.1.1 (30720/28160)" + }, + "192.168.3.0/24": { + "fd": "30720", + "interface": " r2-eth1", + "serno": "0", + "successors": "1", + "via": "193.1.2.2 (30720/28160)" + }, + "193.1.1.0/26": { + "fd": "28160", + "interface": " r2-eth0", + "serno": "0", + "successors": "1", + "via": "Connected" + }, + "193.1.2.0/24": { + "fd": "28160", + "interface": " r2-eth1", + "serno": "0", + "successors": "1", + "via": "Connected" + } + } +} diff --git a/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.ref b/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.ref new file mode 100644 index 0000000..cce49cd --- /dev/null +++ b/tests/topotests/eigrp_topo1/r2/show_ip_eigrp.ref @@ -0,0 +1,14 @@ + +EIGRP Topology Table for AS(1)/ID(193.1.2.1) + +Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply + r - reply Status, s - sia Status + +P 193.1.1.0/26, 1 successors, FD is 28160, serno: 0 + via Connected, r2-eth0 +P 192.168.1.0/24, 1 successors, FD is 30720, serno: 0 + via 193.1.1.1 (30720/28160), r2-eth0 +P 193.1.2.0/24, 1 successors, FD is 28160, serno: 0 + via Connected, r2-eth1 +P 192.168.3.0/24, 1 successors, FD is 30720, serno: 0 + via 193.1.2.2 (30720/28160), r2-eth1 diff --git a/tests/topotests/eigrp_topo1/r2/show_ip_route.json_ref b/tests/topotests/eigrp_topo1/r2/show_ip_route.json_ref new file mode 100644 index 0000000..71c931b --- /dev/null +++ b/tests/topotests/eigrp_topo1/r2/show_ip_route.json_ref @@ -0,0 +1,90 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"eigrp", + "selected":true, + "metric":30720, + "nexthops":[ + { + "fib":true, + "ip":"193.1.1.1", + "afi":"ipv4", + "interfaceName":"r2-eth0", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"eigrp", + "selected":true, + "metric":30720, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.2", + "afi":"ipv4", + "interfaceName":"r2-eth1", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r2-eth0", + "active":true + } + ] + }, + { + "prefix":"193.1.1.0/26", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r2-eth0", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r2-eth1", + "active":true + } + ] + }, + { + "prefix":"193.1.2.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r2-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/eigrp_topo1/r2/zebra.conf b/tests/topotests/eigrp_topo1/r2/zebra.conf new file mode 100644 index 0000000..c440f3a --- /dev/null +++ b/tests/topotests/eigrp_topo1/r2/zebra.conf @@ -0,0 +1,21 @@ +log file zebra.log +! +hostname r2 +! +interface r2-eth0 + description to sw2 - RIPv2 interface + ip address 193.1.1.2/26 + no link-detect +! +interface r2-eth1 + description to sw3 - RIPv1 interface + ip address 193.1.2.1/24 + no link-detect +! +ip forwarding +ipv6 forwarding +! +! +line vty +! + diff --git a/tests/topotests/eigrp_topo1/r3/eigrpd.conf b/tests/topotests/eigrp_topo1/r3/eigrpd.conf new file mode 100644 index 0000000..53ad1bb --- /dev/null +++ b/tests/topotests/eigrp_topo1/r3/eigrpd.conf @@ -0,0 +1,6 @@ +log file eigrpd.log +! +! +router eigrp 1 + network 0.0.0.0/0 + diff --git a/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.json b/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.json new file mode 100644 index 0000000..83db66c --- /dev/null +++ b/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.json @@ -0,0 +1,32 @@ +{ + "P": { + "192.168.1.0/24": { + "fd": "33280", + "interface": " r3-eth1", + "serno": "0", + "successors": "1", + "via": "193.1.2.1 (33280/30720)" + }, + "192.168.3.0/24": { + "fd": "28160", + "interface": " r3-eth0", + "serno": "0", + "successors": "1", + "via": "Connected" + }, + "193.1.1.0/26": { + "fd": "30720", + "interface": " r3-eth1", + "serno": "0", + "successors": "1", + "via": "193.1.2.1 (30720/28160)" + }, + "193.1.2.0/24": { + "fd": "28160", + "interface": " r3-eth1", + "serno": "0", + "successors": "1", + "via": "Connected" + } + } +} diff --git a/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.ref b/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.ref new file mode 100644 index 0000000..70f3d3f --- /dev/null +++ b/tests/topotests/eigrp_topo1/r3/show_ip_eigrp.ref @@ -0,0 +1,14 @@ + +EIGRP Topology Table for AS(1)/ID(193.1.2.2) + +Codes: P - Passive, A - Active, U - Update, Q - Query, R - Reply + r - reply Status, s - sia Status + +P 193.1.1.0/26, 1 successors, FD is 30720, serno: 0 + via 193.1.2.1 (30720/28160), r3-eth1 +P 192.168.1.0/24, 1 successors, FD is 33280, serno: 0 + via 193.1.2.1 (33280/30720), r3-eth1 +P 193.1.2.0/24, 1 successors, FD is 28160, serno: 0 + via Connected, r3-eth1 +P 192.168.3.0/24, 1 successors, FD is 28160, serno: 0 + via Connected, r3-eth0 diff --git a/tests/topotests/eigrp_topo1/r3/show_ip_route.json_ref b/tests/topotests/eigrp_topo1/r3/show_ip_route.json_ref new file mode 100644 index 0000000..5e0b79d --- /dev/null +++ b/tests/topotests/eigrp_topo1/r3/show_ip_route.json_ref @@ -0,0 +1,108 @@ +{ + "192.168.1.0/24":[ + { + "prefix":"192.168.1.0/24", + "protocol":"eigrp", + "selected":true, + "metric":33280, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.1", + "afi":"ipv4", + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ], + "192.168.2.0/24":[ + { + "prefix":"192.168.2.0/24", + "protocol":"static", + "selected":true, + "distance":1, + "metric":0, + "nexthops":[ + { + "fib":true, + "ip":"192.168.3.10", + "afi":"ipv4", + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ], + "192.168.3.0/24":[ + { + "prefix":"192.168.3.0/24", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r3-eth0", + "active":true + } + ] + }, + { + "prefix":"192.168.3.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r3-eth0", + "active":true + } + ] + } + ], + "193.1.1.0/26":[ + { + "prefix":"193.1.1.0/26", + "protocol":"eigrp", + "selected":true, + "metric":30720, + "nexthops":[ + { + "fib":true, + "ip":"193.1.2.1", + "afi":"ipv4", + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ], + "193.1.2.0/24":[ + { + "prefix":"193.1.2.0/24", + "protocol":"eigrp", + "metric":28160, + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"r3-eth1", + "active":true + } + ] + }, + { + "prefix":"193.1.2.0/24", + "protocol":"connected", + "selected":true, + "nexthops":[ + { + "fib":true, + "directlyConnected":true, + "interfaceName":"r3-eth1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/eigrp_topo1/r3/zebra.conf b/tests/topotests/eigrp_topo1/r3/zebra.conf new file mode 100644 index 0000000..7f145b4 --- /dev/null +++ b/tests/topotests/eigrp_topo1/r3/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname r3 +! +interface r3-eth0 + description to sw4 - Stub interface + ip address 192.168.3.1/24 + no link-detect +! +interface r3-eth1 + description to sw3 - RIPv2 interface + ip address 193.1.2.2/24 + no link-detect +! +ip route 192.168.2.0/24 192.168.3.10 +! +ip forwarding +ipv6 forwarding +! +! +line vty +! diff --git a/tests/topotests/eigrp_topo1/test_eigrp_topo1.dot b/tests/topotests/eigrp_topo1/test_eigrp_topo1.dot new file mode 100644 index 0000000..ca3a0fe --- /dev/null +++ b/tests/topotests/eigrp_topo1/test_eigrp_topo1.dot @@ -0,0 +1,62 @@ +## GraphViz file for test_eigrp_topo1 +## +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## EIGRP: #696969 +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph test_eigrp_topo1 { + overlap=false; + constraint=false; + + // title + labelloc="t"; + label="Test Topologoy EIGRP Topo1"; + + ###################### + # Routers + ###################### + + # Main FRR Router with all protocols + R1 [shape=doubleoctagon, label="R1 FRR\nMain Router", fillcolor="#f08080", style=filled]; + + # EIGRP Routers + R2 [shape=doubleoctagon, label="R2 FRR\nEIGRP Router", fillcolor="#19e3d9", style=filled]; + R3 [shape=doubleoctagon, label="R3 FRR\nEIGRP Router", fillcolor="#19e3d9", style=filled]; + + ###################### + # Network Lists + ###################### + + SW1_R1_stub [label="SW1\n192.168.1.0/24", fillcolor="#d0e0d0", style=filled]; + + # EIGRP Networks + SW2_R1_R2 [label="SW2\nEIGRPv2\n193.1.1.0/26", fillcolor="#d0e0d0", style=filled]; + SW3_R2_R3 [label="SW3\nEIGRPv1\n193.1.2.0/24", fillcolor="#d0e0d0", style=filled]; + SW4_R3 [label="SW4\n192.168.3.0/24", fillcolor="#d0e0d0", style=filled]; + Net_R3_remote [label="Static Net\n192.168.2.0/24"]; + + ###################### + # Network Connections + ###################### + R1 -- SW1_R1_stub [label = "eth0\n.1\n::1"]; + + # EIGRP Network + R1 -- SW2_R1_R2 [label = "eth1\n.1"]; + SW2_R1_R2 -- R2 [label = "eth0\n.2"]; + R2 -- SW3_R2_R3 [label = "eth1\n.1"]; + SW3_R2_R3 -- R3 [label = "eth1\n.2"]; + R3 -- SW4_R3 [label = "eth0\n.1"]; + SW4_R3 -- Net_R3_remote [label = ".10"]; + +} diff --git a/tests/topotests/eigrp_topo1/test_eigrp_topo1.py b/tests/topotests/eigrp_topo1/test_eigrp_topo1.py new file mode 100644 index 0000000..b3152f4 --- /dev/null +++ b/tests/topotests/eigrp_topo1/test_eigrp_topo1.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_eigrp_topo1.py +# +# Copyright (c) 2017 by +# Cumulus Networks, Inc. +# Donald Sharp +# + +""" +test_eigrp_topo1.py: Testing EIGRP + +""" + +import os +import re +import sys +import pytest +import json + +pytestmark = [pytest.mark.eigrpd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + # On main router + # First switch is for a dummy interface (for local network) + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["r1"]) + + # Switches for EIGRP + # switch 2 switch is for connection to EIGRP router + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + # switch 4 is stub on remote EIGRP router + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["r3"]) + + # switch 3 is between EIGRP routers + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + "Setup topology" + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_EIGRP, os.path.join(CWD, "{}/eigrpd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_converge_protocols(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sleep(5, "Waiting for EIGRP convergence") + + +def test_eigrp_routes(): + "Test EIGRP 'show ip eigrp'" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Verify EIGRP Status + logger.info("Verifying EIGRP routes") + + router_list = tgen.routers().values() + for router in router_list: + refTableFile = "{}/{}/show_ip_eigrp.json".format(CWD, router.name) + + # Read expected result from file + expected = json.loads(open(refTableFile).read()) + + # Actual output from router + actual = ip_eigrp_topo(router) + + assertmsg = '"show ip eigrp topo" mismatches on {}'.format(router.name) + assert topotest.json_cmp(actual, expected) is None, assertmsg + + +def test_zebra_ipv4_routingTable(): + "Test 'show ip route'" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + failures = 0 + router_list = tgen.routers().values() + for router in router_list: + output = router.vtysh_cmd("show ip route json", isjson=True) + refTableFile = "{}/{}/show_ip_route.json_ref".format(CWD, router.name) + expected = json.loads(open(refTableFile).read()) + + assertmsg = "Zebra IPv4 Routing Table verification failed for router {}".format( + router.name + ) + assert topotest.json_cmp(output, expected) is None, assertmsg + + +def test_shut_interface_and_recover(): + "Test shutdown of an interface and recovery of the interface" + + tgen = get_topogen() + router = tgen.gears["r1"] + router.run("ip link set r1-eth1 down") + topotest.sleep(5, "Waiting for EIGRP convergence") + router.run("ip link set r1-eth1 up") + + +def test_shutdown_check_stderr(): + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + pytest.skip("Skipping test for Stderr output and memory leaks") + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying unexpected STDERR output from daemons") + + router_list = tgen.routers().values() + for router in router_list: + router.stop() + + log = tgen.net[router.name].getStdErr("eigrpd") + if log: + logger.error("EIGRPd StdErr Log:" + log) + log = tgen.net[router.name].getStdErr("zebra") + if log: + logger.error("Zebra StdErr Log:" + log) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) + + +# +# Auxiliary Functions +# +def ip_eigrp_topo(node): + """ + Parse 'show ip eigrp topo' from `node` and returns a dict with the + result. + + Example: + { + 'P': { + '192.168.1.0/24': { + 'sucessors': 1, + 'fd': 112233, + 'serno': 0, + 'via': 'Connected', + 'interface': 'eth0', + }, + '192.168.2.0/24': { + 'sucessors': 1, + 'fd': 112234, + 'serno': 0, + 'via': 'Connected', + 'interface': 'eth1', + } + } + } + """ + output = topotest.normalize_text(node.vtysh_cmd("show ip eigrp topo")).splitlines() + result = {} + for idx, line in enumerate(output): + columns = line.split(" ", 1) + + # Parse the following format into python dicts + # code A.B.C.D/E, X successors, FD is Y, serno: Z + # via FOO, interface-name + code = columns[0] + if code not in ["P", "A", "U", "Q", "R", "r", "s"]: + continue + + if code not in result: + result[code] = {} + + # Split network from the rest + columns = columns[1].split(",") + + # Parse first line data + network = columns[0] + result[code][network] = {} + for column in columns: + # Skip the network column + if column == columns[0]: + continue + + match = re.search(r"(\d+) successors", column) + if match is not None: + result[code][network]["successors"] = match.group(1) + continue + + match = re.search(r"FD is (\d+)", column) + if match is not None: + result[code][network]["fd"] = match.group(1) + continue + + match = re.search(r"serno: (\d+)", column) + if match is not None: + result[code][network]["serno"] = match.group(1) + continue + + # Parse second line data + nextline = output[idx + 1] + columns = topotest.normalize_text(nextline).split(",") + for column in columns: + match = re.search(r"via (.+)", column) + if match is not None: + result[code][network]["via"] = match.group(1) + continue + + match = re.search(r"(.+)", column) + if match is not None: + result[code][network]["interface"] = match.group(1) + continue + + return result diff --git a/tests/topotests/evpn_pim_1/host1/bgpd.conf b/tests/topotests/evpn_pim_1/host1/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host1/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/evpn_pim_1/host1/pimd.conf b/tests/topotests/evpn_pim_1/host1/pimd.conf new file mode 100644 index 0000000..63a44c1 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host1/pimd.conf @@ -0,0 +1,4 @@ +int lo +! + + diff --git a/tests/topotests/evpn_pim_1/host1/zebra.conf b/tests/topotests/evpn_pim_1/host1/zebra.conf new file mode 100644 index 0000000..45ad031 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host1/zebra.conf @@ -0,0 +1,5 @@ +int host1-eth0 + ip addr 192.168.3.4/24 + +int lo + ip addr 192.168.100.4/32 diff --git a/tests/topotests/evpn_pim_1/host2/bgpd.conf b/tests/topotests/evpn_pim_1/host2/bgpd.conf new file mode 100644 index 0000000..cdf4cb4 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host2/bgpd.conf @@ -0,0 +1 @@ +! diff --git a/tests/topotests/evpn_pim_1/host2/pimd.conf b/tests/topotests/evpn_pim_1/host2/pimd.conf new file mode 100644 index 0000000..63a44c1 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host2/pimd.conf @@ -0,0 +1,4 @@ +int lo +! + + diff --git a/tests/topotests/evpn_pim_1/host2/zebra.conf b/tests/topotests/evpn_pim_1/host2/zebra.conf new file mode 100644 index 0000000..bfae530 --- /dev/null +++ b/tests/topotests/evpn_pim_1/host2/zebra.conf @@ -0,0 +1,5 @@ +int host-eth0 + ip addr 192.168.4.5/24 + +int lo + ip addr 192.168.100.5/32 diff --git a/tests/topotests/evpn_pim_1/leaf1/bgpd.conf b/tests/topotests/evpn_pim_1/leaf1/bgpd.conf new file mode 100644 index 0000000..97fd866 --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf1/bgpd.conf @@ -0,0 +1,11 @@ + +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 3 10 + redistribute connected + address-family l2vpn evpn + neighbor 192.168.1.1 activate + advertise-all-vni + ! +! diff --git a/tests/topotests/evpn_pim_1/leaf1/pimd.conf b/tests/topotests/evpn_pim_1/leaf1/pimd.conf new file mode 100644 index 0000000..b54aada --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf1/pimd.conf @@ -0,0 +1,16 @@ +! debug pim events +! debug pim nht +! debug pim zebra +ip pim rp 192.168.100.1 +ip pim join-prune-interval 5 +! +int lo + ip pim +! +int leaf1-eth0 + ip pim +! +int leaf1-eth1 + ip pim + ip igmp + diff --git a/tests/topotests/evpn_pim_1/leaf1/zebra.conf b/tests/topotests/evpn_pim_1/leaf1/zebra.conf new file mode 100644 index 0000000..581cc6e --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf1/zebra.conf @@ -0,0 +1,6 @@ +int leaf1-eth0 + ip addr 192.168.1.2/24 +int leaf1-eth1 + ip addr 192.168.3.2/24 +int lo + ip addr 192.168.100.2/32 diff --git a/tests/topotests/evpn_pim_1/leaf2/bgpd.conf b/tests/topotests/evpn_pim_1/leaf2/bgpd.conf new file mode 100644 index 0000000..91d9bd8 --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf2/bgpd.conf @@ -0,0 +1,11 @@ + +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 3 10 + redistribute connected + address-family l2vpn evpn + neighbor 192.168.2.1 activate + advertise-all-vni + ! +! diff --git a/tests/topotests/evpn_pim_1/leaf2/pimd.conf b/tests/topotests/evpn_pim_1/leaf2/pimd.conf new file mode 100644 index 0000000..d775b80 --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf2/pimd.conf @@ -0,0 +1,14 @@ +ip pim rp 192.168.100.1 +ip pim join-prune-interval 5 +! +int lo + ip pim +! +int leaf2-eth0 + ip pim +! +int leaf2-eth1 + ip pim + ip igmp +! + diff --git a/tests/topotests/evpn_pim_1/leaf2/zebra.conf b/tests/topotests/evpn_pim_1/leaf2/zebra.conf new file mode 100644 index 0000000..1bcf8e1 --- /dev/null +++ b/tests/topotests/evpn_pim_1/leaf2/zebra.conf @@ -0,0 +1,6 @@ +int leaf2-eth0 + ip addr 192.168.2.3/24 +int leaf2-eth1 + ip addr 192.168.4.3/24 +int lo + ip addr 192.168.100.3/32 diff --git a/tests/topotests/evpn_pim_1/spine/bgp.summ.json b/tests/topotests/evpn_pim_1/spine/bgp.summ.json new file mode 100644 index 0000000..b5b03e8 --- /dev/null +++ b/tests/topotests/evpn_pim_1/spine/bgp.summ.json @@ -0,0 +1,38 @@ +{ + "routerId":"192.168.100.1", + "as":65001, + "vrfName":"default", + "peerCount":2, + "peers":{ + "192.168.1.2":{ + "remoteAs":65002, + "version":4, + "outq":0, + "inq":0, + "pfxRcd":3, + "pfxSnt":7, + "state":"Established", + "connectionsEstablished":1, + "connectionsDropped":0, + "idType":"ipv4" + }, + "192.168.2.3":{ + "remoteAs":65003, + "version":4, + "outq":0, + "inq":0, + "pfxRcd":3, + "pfxSnt":7, + "state":"Established", + "connectionsEstablished":1, + "connectionsDropped":0, + "idType":"ipv4" + } + }, + "failedPeers":0, + "totalPeers":2, + "dynamicPeers":0, + "bestPath":{ + "multiPathRelax":"false" + } +} diff --git a/tests/topotests/evpn_pim_1/spine/bgpd.conf b/tests/topotests/evpn_pim_1/spine/bgpd.conf new file mode 100644 index 0000000..81ab802 --- /dev/null +++ b/tests/topotests/evpn_pim_1/spine/bgpd.conf @@ -0,0 +1,13 @@ + +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 3 10 + neighbor 192.168.2.3 remote-as external + neighbor 192.168.2.3 timers 3 10 + redistribute connected + address-family l2vpn evpn + neighbor 192.168.1.2 activate + neighbor 192.168.2.3 activate + exit-address-family +! diff --git a/tests/topotests/evpn_pim_1/spine/join-info.json b/tests/topotests/evpn_pim_1/spine/join-info.json new file mode 100644 index 0000000..3d135fb --- /dev/null +++ b/tests/topotests/evpn_pim_1/spine/join-info.json @@ -0,0 +1,34 @@ +{ + "spine-eth0":{ + "name":"spine-eth0", + "state":"up", + "address":"192.168.1.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.1.1.1":{ + "*":{ + "source":"*", + "group":"239.1.1.1", + "prune":"--:--", + "channelJoinName":"JOIN" + } + } + }, + "spine-eth1":{ + "name":"spine-eth1", + "state":"up", + "address":"192.168.2.1", + "flagMulticast":true, + "flagBroadcast":true, + "lanDelayEnabled":true, + "239.1.1.1":{ + "*":{ + "source":"*", + "group":"239.1.1.1", + "prune":"--:--", + "channelJoinName":"JOIN" + } + } + } +} diff --git a/tests/topotests/evpn_pim_1/spine/pimd.conf b/tests/topotests/evpn_pim_1/spine/pimd.conf new file mode 100644 index 0000000..12c6d6f --- /dev/null +++ b/tests/topotests/evpn_pim_1/spine/pimd.conf @@ -0,0 +1,14 @@ +ip pim rp 192.168.100.1 +ip pim join-prune-interval 5 +! +int lo + ip pim +! +int spine-eth0 + ip pim +! +int spine-eth1 + ip pim +! + + diff --git a/tests/topotests/evpn_pim_1/spine/zebra.conf b/tests/topotests/evpn_pim_1/spine/zebra.conf new file mode 100644 index 0000000..2cb7194 --- /dev/null +++ b/tests/topotests/evpn_pim_1/spine/zebra.conf @@ -0,0 +1,8 @@ +int spine-eth0 + ip addr 192.168.1.1/24 +! +int spine-eth1 + ip addr 192.168.2.1/24 +! +int lo + ip addr 192.168.100.1/32 diff --git a/tests/topotests/evpn_pim_1/test_evpn_pim_topo1.py b/tests/topotests/evpn_pim_1/test_evpn_pim_topo1.py new file mode 100644 index 0000000..cfcd4a1 --- /dev/null +++ b/tests/topotests/evpn_pim_1/test_evpn_pim_topo1.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_evpn-pim_topo1.py +# +# Copyright (c) 2017 by +# Cumulus Networks, Inc. +# Donald Sharp +# + +""" +test_evpn_pim_topo1.py: Testing evpn-pim + +""" + +import os +import sys +import pytest +import json +from functools import partial + +pytestmark = [pytest.mark.pimd, pytest.mark.bgpd] + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +def build_topo(tgen): + tgen.add_router("spine") + tgen.add_router("leaf1") + tgen.add_router("leaf2") + tgen.add_router("host1") + tgen.add_router("host2") + + # On main router + # First switch is for a dummy interface (for local network) + # spine-eth0 is connected to leaf1-eth0 + switch = tgen.add_switch("sw1") + switch.add_link(tgen.gears["spine"]) + switch.add_link(tgen.gears["leaf1"]) + + # spine-eth1 is connected to leaf2-eth0 + switch = tgen.add_switch("sw2") + switch.add_link(tgen.gears["spine"]) + switch.add_link(tgen.gears["leaf2"]) + + # leaf1-eth1 is connected to host1-eth0 + switch = tgen.add_switch("sw3") + switch.add_link(tgen.gears["leaf1"]) + switch.add_link(tgen.gears["host1"]) + + # leaf2-eth1 is connected to host2-eth0 + switch = tgen.add_switch("sw4") + switch.add_link(tgen.gears["leaf2"]) + switch.add_link(tgen.gears["host2"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(module): + "Setup topology" + tgen = Topogen(build_topo, module.__name__) + tgen.start_topology() + + leaf1 = tgen.gears["leaf1"] + leaf2 = tgen.gears["leaf2"] + + leaf1.run("brctl addbr brleaf1") + leaf2.run("brctl addbr brleaf2") + leaf1.run("ip link set dev brleaf1 up") + leaf2.run("ip link set dev brleaf2 up") + leaf1.run( + "ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev leaf1-eth1 dstport 4789" + ) + leaf2.run( + "ip link add vxlan0 type vxlan id 42 group 239.1.1.1 dev leaf2-eth1 dstport 4789" + ) + leaf1.run("brctl addif brleaf1 vxlan0") + leaf2.run("brctl addif brleaf2 vxlan0") + leaf1.run("ip link set up dev vxlan0") + leaf2.run("ip link set up dev vxlan0") + # tgen.mininet_cli() + # This is a sample of configuration loading. + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_PIM, os.path.join(CWD, "{}/pimd.conf".format(rname)) + ) + tgen.start_router() + # tgen.mininet_cli() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_converge_protocols(): + "Wait for protocol convergence" + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + spine = tgen.gears["spine"] + json_file = "{}/{}/bgp.summ.json".format(CWD, spine.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, spine, "show bgp ipv4 uni summ json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=125, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(spine.name) + assert result is None, assertmsg + # tgen.mininet_cli() + + +def test_multicast_groups_on_rp(): + "Ensure the multicast groups show up on the spine" + # This test implicitly tests the auto mcast groups + # of the created vlans and then the auto-joins that + # pim will do to the RP( spine ) + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + spine = tgen.gears["spine"] + json_file = "{}/{}/join-info.json".format(CWD, spine.name) + expected = json.loads(open(json_file).read()) + + test_func = partial( + topotest.router_json_cmp, spine, "show ip pim join json", expected + ) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assertmsg = '"{}" JSON output mismatches'.format(spine.name) + assert result is None, assertmsg + # tgen.mininet_cli() + + +def test_shutdown_check_stderr(): + if os.environ.get("TOPOTESTS_CHECK_STDERR") is None: + pytest.skip("Skipping test for Stderr output and memory leaks") + + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info("Verifying unexpected STDERR output from daemons") + + router_list = tgen.routers().values() + for router in router_list: + router.stop() + + log = tgen.net[router.name].getStdErr("pimd") + if log: + logger.error("PIMd StdErr Log:" + log) + log = tgen.net[router.name].getStdErr("bgpd") + if log: + logger.error("BGPd StdErr Log:" + log) + log = tgen.net[router.name].getStdErr("zebra") + if log: + logger.error("Zebra StdErr Log:" + log) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/evpn_type5_test_topo1/__init__.py b/tests/topotests/evpn_type5_test_topo1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json b/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json new file mode 100644 index 0000000..14842da --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/evpn_type5_chaos_topo1.json @@ -0,0 +1,887 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "e1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"10.1.1.1/32", + "next_hop":"Null0", + "vrf": "RED" + }, + { + "network":"10::1/128", + "next_hop":"Null0", + "vrf": "RED" + } + ] + }, + "r2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "e1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": {} + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"20.1.1.1/32", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"20::1/128", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"30.1.1.1/32", + "next_hop":"Null0", + "vrf": "GREEN" + }, + { + "network":"30::1/128", + "next_hop":"Null0", + "vrf": "GREEN" + } + ] + }, + "e1": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + }, + "d2": { + "dest_link": { + "e1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4":{ + "e1-link1": "activate" + } + }, + "d2": { + "ipv4":{ + "e1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + } + ] + }, + "d1": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d1-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "d2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d2-link1": { + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d2-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": {} + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "d1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "d2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": {} + } + }, + "d2": { + "dest_link": { + "r3": {} + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "d1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": {} + } + }, + "d2": { + "dest_link": { + "r4-link1": {} + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": {} + } + }, + "d2": { + "dest_link": { + "r4-link2": {} + } + } + } + } + } + } + } + ] + } + } +} diff --git a/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json b/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json new file mode 100644 index 0000000..dd41270 --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/evpn_type5_topo1.json @@ -0,0 +1,1004 @@ +{ + "address_types": ["ipv4","ipv6"], + "ipv4base": "10.0.0.0", + "ipv4mask": 30, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 30, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:db8:f::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "e1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "1", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"10.1.1.1/32", + "next_hop":"Null0", + "vrf": "RED" + }, + { + "network":"10::1/128", + "next_hop":"Null0", + "vrf": "RED" + } + ] + }, + "r2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "e1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "2", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "2", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "redistribute": [ + {"redist_type": "static"} + ], + "neighbor": { + "e1": { + "dest_link": { + "r2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ], + "static_routes":[ + { + "network":"20.1.1.1/32", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"20::1/128", + "next_hop":"Null0", + "vrf": "BLUE" + }, + { + "network":"30.1.1.1/32", + "next_hop":"Null0", + "vrf": "GREEN" + }, + { + "network":"30::1/128", + "next_hop":"Null0", + "vrf": "GREEN" + } + ] + }, + "e1": { + "links": { + "r1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "e1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "e1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "e1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "deactivate": "ipv4" + } + } + }, + "d2": { + "dest_link": { + "e1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4":{ + "e1-link1": "activate" + } + }, + "d2": { + "ipv4":{ + "e1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + } + ] + }, + "d1": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3, + "deactivate": "ipv4" + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d1-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d1-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "d2": { + "links": { + "e1-link1": {"ipv4": "auto", "ipv6": "auto"}, + "r3": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "r4-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "r4-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1", + "vni": 75100 + }, + { + "name": "BLUE", + "id": "2", + "vni": 75200 + }, + { + "name": "GREEN", + "id": "3", + "vni": 75300 + } + ], + "bgp": + [ + { + "local_as": "200", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "e1": { + "dest_link": { + "d2-link1": { + "deactivate": "ipv4", + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4":{ + "d2-link1": "activate" + } + } + }, + "advertise-all-vni": true + } + } + } + }, + { + "local_as": "200", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r3": { + "dest_link": { + "d2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + }, + { + "local_as": "200", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r4": { + "dest_link": { + "d2-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "l2vpn": { + "evpn": { + "advertise": { + "ipv4": { + "unicast": {} + }, + "ipv6": { + "unicast": {} + } + } + } + } + } + } + ] + }, + "r3": { + "links": { + "d1": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"}, + "d2": {"ipv4": "auto", "ipv6": "auto", "vrf": "RED"} + }, + "vrfs":[ + { + "name": "RED", + "id": "1" + } + ], + "bgp": + [ + { + "local_as": "3", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r3": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + }, + "r4": { + "links": { + "d1-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d1-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"}, + "d2-link1": {"ipv4": "auto", "ipv6": "auto", "vrf": "BLUE"}, + "d2-link2": {"ipv4": "auto", "ipv6": "auto", "vrf": "GREEN"} + }, + "vrfs":[ + { + "name": "BLUE", + "id": "1" + }, + { + "name": "GREEN", + "id": "2" + } + ], + "bgp": + [ + { + "local_as": "4", + "vrf": "BLUE", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r4-link1": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + }, + { + "local_as": "4", + "vrf": "GREEN", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "d1": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + }, + "d2": { + "dest_link": { + "r4-link2": { + "keepalivetimer": 1, + "holddowntimer": 3 + } + } + } + } + } + } + } + } + ] + } + } +} + diff --git a/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py new file mode 100644 index 0000000..4586866 --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_chaos_topo1.py @@ -0,0 +1,1007 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test EVPN-Type5 functionality: +1. In absence of an overlay index all IP-Prefixes(RT-5) + are advertised with default values for below parameters: + --> Ethernet Tag ID = GW IP address = ESI=0 +2. EVPN CLI output and JSON format validation. +3. RT verification(auto) +""" + +import os +import sys +import time +import pytest +import platform +from copy import deepcopy + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topotest import version_cmp +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_static_routes, + create_vrf_cfg, + check_router_status, + configure_vxlan, + configure_brctl, + verify_vrf_vni, + verify_cli_json, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_attributes_for_evpn_routes, +) +from lib.topojson import build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Reading the data from JSON File for topology creation +# Global variables +TCPDUMP_FILE = "evpn_log.txt" +NETWORK1_1 = {"ipv4": "10.1.1.1/32", "ipv6": "10::1/128"} +NETWORK1_2 = {"ipv4": "40.1.1.1/32", "ipv6": "40::1/128"} +NETWORK1_3 = {"ipv4": "40.1.1.2/32", "ipv6": "40::2/128"} +NETWORK1_4 = {"ipv4": "40.1.1.3/32", "ipv6": "40::3/128"} +NETWORK2_1 = {"ipv4": "20.1.1.1/32", "ipv6": "20::1/128"} +NETWORK3_1 = {"ipv4": "30.1.1.1/32", "ipv6": "30::1/128"} +NETWORK4_1 = {"ipv4": "100.1.1.1/32 ", "ipv6": "100::100/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +VNI_1 = 75100 +VNI_2 = 75200 +VNI_3 = 75300 +MAC_1 = "00:80:48:ba:d1:00" +MAC_2 = "00:80:48:ba:d1:01" +MAC_3 = "00:80:48:ba:d1:02" +BRCTL_1 = "br100" +BRCTL_2 = "br200" +BRCTL_3 = "br300" +VXLAN_1 = "vxlan75100" +VXLAN_2 = "vxlan75200" +VXLAN_3 = "vxlan75300" +BRIDGE_INTF1 = "120.0.0.1" +BRIDGE_INTF2 = "120.0.0.2" +BRIDGE_INTF3 = "120.0.0.3" +MULTICAST_MAC1 = "01:00:5e:00:52:02" + +VXLAN = { + "vxlan_name": [VXLAN_1, VXLAN_2, VXLAN_3], + "vxlan_id": [75100, 75200, 75300], + "dstport": 4789, + "local_addr": {"e1": BRIDGE_INTF1, "d1": BRIDGE_INTF2, "d2": BRIDGE_INTF3}, + "learning": "no", +} +BRCTL = { + "brctl_name": [BRCTL_1, BRCTL_2, BRCTL_3], + "addvxlan": [VXLAN_1, VXLAN_2, VXLAN_3], + "vrf": ["RED", "BLUE", "GREEN"], + "stp": [0, 0, 0], +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/evpn_type5_chaos_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + global topo + topo = tgen.json_topo + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + 'EVPN tests will not run (have kernel "{}", ' + "but it requires >= 4.19)".format(platform.release()) + ) + pytest.skip(error_msg) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Pre-requisite config for testsuite") + prerequisite_config_for_test_suite(tgen) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def prerequisite_config_for_test_suite(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Configure vxlan, bridge interface") + for dut in ["e1", "d1", "d2"]: + step("[DUT: ]Configure vxlan") + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + step("Configure default routes") + add_default_routes(tgen) + + +def add_default_routes(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Add default routes..") + + default_routes = { + "e1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["d1"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["d2"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d2": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + } + + result = create_static_routes(tgen, default_routes) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + +def test_verify_overlay_index_p1(request): + """ + In absence of an overlay index all IP-Prefixes(RT-5) + are advertised with default values for below parameters: + --> Ethernet Tag ID = GW IP address = ESI=0 + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Following steps are taken care in base config:") + step( + "Configure BGP neighborship for both address families" + "(IPv4 & IPv6) between Edge-1 and VFN routers(R1 and R2)" + ) + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step("Advertise VRF routes as in EVPN address family from Edge-1 " "router.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify: Prefixes are received in all VRFs on Edge-1 router.") + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that EVPN routes, received on DCG-1 and DCG-2 do not " + "carry any overlay index and these indexes are set to default " + "value=0. " + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d1", input_routes, ethTag=0 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes, ethTag=0 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_evpn_cli_json_available_p1(request): + """ + EVPN CLI output and JSON format validation. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Need to verify below CLIs and associated JSON format " "outputs:") + + input_dict = { + "e1": { + "cli": [ + "show evpn vni detail", + "show bgp l2vpn evpn all overlay", + "show bgp l2vpn evpn vni", + ] + } + } + + result = verify_cli_json(tgen, input_dict) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_RT_verification_auto_p0(request): + """ + RT verification(auto) + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise overlapping prefixes from VNFs R1 and R2 in all VRFs " + "RED, GREEN and BLUE 100.1.1.1/32 and 100::100/128" + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that Edge-1 receives same prefixes in all 3 VRFs via " + "corresponding next-hop in associated VRF sh bgp vrf all" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure 4-byte local AS number on Edge-1 and establish EVPN " + "neighborship with DCG-1 & DCG-2." + ) + + topo_local = deepcopy(topo) + + step("Delete BGP config for vrf RED.") + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = {} + for dut in ["e1"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + INDEX = [0, 1, 2, 3] + VRFS = ["RED", "BLUE", "GREEN", None] + AS_NUM = [100, 100, 100, 100] + + for index, vrf, as_num in zip(INDEX, VRFS, AS_NUM): + topo_local["routers"][dut]["bgp"][index]["local_as"] = 4294967293 + if vrf: + temp[dut]["bgp"].append( + {"local_as": as_num, "vrf": vrf, "delete": True} + ) + else: + temp[dut]["bgp"].append({"local_as": as_num, "delete": True}) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = create_router_bgp(tgen, topo_local["routers"]) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": VNI_1}, + {"name": "BLUE", "vni": VNI_2}, + {"name": "GREEN", "vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that all overlapping prefixes across different VRFs are " + "advertised in EVPN with unique RD value(auto derived)." + ) + step( + "Verify that FRR uses only the lower 2 bytes of ASN+VNI for auto " + "derived RT value." + ) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + input_routes_2 = { + "r2": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "BLUE"}]} + } + input_routes_3 = { + "r2": { + "static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "GREEN"}] + } + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_1, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_1, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_2, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_2, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_3, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes_3, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-1(iBGP peer) automatically imports the prefixes" + " from EVPN address-family to respective VRFs." + ) + step( + "Verify if DCG-2(eBGP peer) automatically imports the prefixes " + "from EVPN address-family to respective VRFs or not." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK4_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Change the VNI number for all 3 VRFs on Edge-1 as:" + "RED : 75400, GREEN: 75500, BLUE: 75600" + ) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": 75400}, + {"name": "BLUE", "vni": 75500}, + {"name": "GREEN", "vni": 75600}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete configured vxlan") + dut = "e1" + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + "delete": True, + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configured vxlan") + VXLAN["vxlan_id"] = [75400, 75500, 75600] + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number.." + ) + + input_dict = { + "e1": { + "vrfs": [ + {"RED": {"vni": 75400}}, + {"BLUE": {"vni": 75500}}, + {"GREEN": {"vni": 75600}}, + ] + } + } + + result = verify_vrf_vni(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify on DCG-2 that prefixes are not imported from EVPN " + "address-family to VRFs as RT values are different on sending(" + "edge-1) and receiving(DCG-2) end." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d2", result) + ) + + step( + "Revert back to original VNI number for all 3 VRFs on Edge-1 " + "as: RED : 75100, GREEN: 75200, BLUE: 75300" + ) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "no_vni": 75400}, + {"name": "BLUE", "no_vni": 75500}, + {"name": "GREEN", "no_vni": 75600}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = { + "e1": { + "vrfs": [ + {"name": "RED", "vni": VNI_1}, + {"name": "BLUE", "vni": VNI_2}, + {"name": "GREEN", "vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete configured vxlan") + dut = "e1" + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + "delete": True, + } + ] + } + } + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configured vxlan") + VXLAN["vxlan_id"] = [75100, 75200, 75300] + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on Edge-1 that auto derived RT value has changed for " + "each VRF based on VNI number." + ) + step( + "Verify that DCG-1(iBGP peer) automatically imports the prefixes" + " from EVPN address-family to respective VRFs." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "e1", input_routes, rt="auto", rt_peer="e1" + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Test with smaller VNI numbers (1-75000)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": VNI_1}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "vni": 111}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that DCG-2 receives EVPN prefixes along with auto " + "derived RT values(based on smaller VNI numbers)" + ) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes_1, rt="auto", rt_peer="e1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Malformed Auto-RT value should not be accepted in {} \n " + "Found: {}".format(tc_name, "d2", result) + ) + + step("Configure VNI number more than boundary limit (16777215)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": 111}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "vni": 16777215}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("CLI error for malformed VNI.") + input_dict = { + "e1": { + "vrfs": [{"RED": {"vni": 16777215, "routerMac": "None", "state": "Down"}}] + } + } + + result = verify_vrf_vni(tgen, input_dict) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_routes_1 = { + "r1": {"static_routes": [{"network": NETWORK4_1[addr_type], "vrf": "RED"}]} + } + + result = verify_attributes_for_evpn_routes( + tgen, topo, "d2", input_routes_1, rt="auto", rt_peer="e1", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Malformed Auto-RT value should not be accepted in {} \n " + "Found: {}".format(tc_name, "d2", result) + ) + + step("Un-configure VNI number more than boundary limit (16777215)") + + input_dict_vni = {"e1": {"vrfs": [{"name": "RED", "no_vni": 16777215}]}} + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py new file mode 100644 index 0000000..10d216a --- /dev/null +++ b/tests/topotests/evpn_type5_test_topo1/test_evpn_type5_topo1.py @@ -0,0 +1,2322 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2020 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, +# Inc. ("NetDEF") in this file. +# + +""" +Following tests are covered to test EVPN-Type5 functionality: + +1. RD verification (manual/auto). +2. RT verification(manual) +3. In an active/standby EVPN implementation, if active DCG goes down, + secondary takes over. +4. EVPN routes are advertised/withdrawn, based on VNFs + advertising/withdrawing IP prefixes. +5. Route-map operations for EVPN address family. +6. BGP attributes for EVPN address-family. +""" + +import os +import sys +import json +import time +import pytest +import platform +from copy import deepcopy + + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../lib/")) + +# Required to instantiate the topology builder class. + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topotest import version_cmp +from lib.topogen import Topogen, get_topogen + +from lib.common_config import ( + start_topology, + write_test_header, + check_address_types, + write_test_footer, + reset_config_on_routers, + verify_rib, + step, + create_route_maps, + create_static_routes, + create_vrf_cfg, + check_router_status, + apply_raw_config, + configure_vxlan, + configure_brctl, + create_interface_in_kernel, + kill_router_daemons, + start_router_daemons, +) + +from lib.topolog import logger +from lib.bgp import ( + verify_bgp_convergence, + create_router_bgp, + verify_best_path_as_per_bgp_attribute, + verify_attributes_for_evpn_routes, + verify_evpn_routes, +) +from lib.topojson import build_topo_from_json, build_config_from_json + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + +# Global variables +NETWORK1_1 = {"ipv4": "10.1.1.1/32", "ipv6": "10::1/128"} +NETWORK1_2 = {"ipv4": "40.1.1.1/32", "ipv6": "40::1/128"} +NETWORK1_3 = {"ipv4": "40.1.1.2/32", "ipv6": "40::2/128"} +NETWORK1_4 = {"ipv4": "40.1.1.3/32", "ipv6": "40::3/128"} +NETWORK2_1 = {"ipv4": "20.1.1.1/32", "ipv6": "20::1/128"} +NETWORK3_1 = {"ipv4": "30.1.1.1/32", "ipv6": "30::1/128"} +NETWORK4_1 = {"ipv4": "100.1.1.1/32 ", "ipv6": "100::100/128"} +NEXT_HOP_IP = {"ipv4": "Null0", "ipv6": "Null0"} +VNI_1 = 75100 +VNI_2 = 75200 +VNI_3 = 75300 +MAC_1 = "00:80:48:ba:d1:00" +MAC_2 = "00:80:48:ba:d1:01" +MAC_3 = "00:80:48:ba:d1:02" +BRCTL_1 = "br100" +BRCTL_2 = "br200" +BRCTL_3 = "br300" +VXLAN_1 = "vxlan75100" +VXLAN_2 = "vxlan75200" +VXLAN_3 = "vxlan75300" +BRIDGE_INTF1 = "120.0.0.1" +BRIDGE_INTF2 = "120.0.0.2" +BRIDGE_INTF3 = "120.0.0.3" + +VXLAN = { + "vxlan_name": [VXLAN_1, VXLAN_2, VXLAN_3], + "vxlan_id": [75100, 75200, 75300], + "dstport": 4789, + "local_addr": {"e1": BRIDGE_INTF1, "d1": BRIDGE_INTF2, "d2": BRIDGE_INTF3}, + "learning": "no", +} +BRCTL = { + "brctl_name": [BRCTL_1, BRCTL_2, BRCTL_3], + "addvxlan": [VXLAN_1, VXLAN_2, VXLAN_3], + "vrf": ["RED", "BLUE", "GREEN"], + "stp": [0, 0, 0], +} + + +def setup_module(mod): + """ + Sets up the pytest environment + + * `mod`: module name + """ + + global topo + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + json_file = "{}/evpn_type5_topo1.json".format(CWD) + tgen = Topogen(json_file, mod.__name__) + topo = tgen.json_topo + + # ... and here it calls Mininet initialization functions. + + # Starting topology, create tmp files which are loaded to routers + # to start daemons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + if version_cmp(platform.release(), "4.19") < 0: + error_msg = ( + 'EVPN tests will not run (have kernel "{}", ' + "but it requires >= 4.19)".format(platform.release()) + ) + pytest.skip(error_msg) + + global BGP_CONVERGENCE + global ADDR_TYPES + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error: {}".format( + BGP_CONVERGENCE + ) + + logger.info("Pre-requisite config for testsuite") + prerequisite_config_for_test_suite(tgen) + + logger.info("Running setup_module() done") + + +def teardown_module(): + """Teardown the pytest environment""" + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + logger.info( + "Testsuite end time: {}".format(time.asctime(time.localtime(time.time()))) + ) + logger.info("=" * 40) + + +##################################################### +# +# Testcases +# +##################################################### + + +def prerequisite_config_for_test_suite(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Configure vxlan, bridge interface") + for dut in ["e1", "d1", "d2"]: + step("[DUT: ]Configure vxlan") + vxlan_input = { + dut: { + "vxlan": [ + { + "vxlan_name": VXLAN["vxlan_name"], + "vxlan_id": VXLAN["vxlan_id"], + "dstport": VXLAN["dstport"], + "local_addr": VXLAN["local_addr"][dut], + "learning": VXLAN["learning"], + } + ] + } + } + + result = configure_vxlan(tgen, vxlan_input) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + step("Configure bridge interface") + brctl_input = { + dut: { + "brctl": [ + { + "brctl_name": BRCTL["brctl_name"], + "addvxlan": BRCTL["addvxlan"], + "vrf": BRCTL["vrf"], + "stp": BRCTL["stp"], + } + ] + } + } + result = configure_brctl(tgen, topo, brctl_input) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + step("Configure default routes") + add_default_routes(tgen) + + +def add_default_routes(tgen): + """ + API to do prerequisite config for testsuite + + parameters: + ----------- + * `tgen`: topogen object + """ + + step("Add default routes..") + + default_routes = { + "e1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["d1"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["d2"]["links"]["e1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d1": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["d2"]), + "next_hop": topo["routers"]["e1"]["links"]["d1-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + "d2": { + "static_routes": [ + { + "network": "{}/32".format(VXLAN["local_addr"]["d1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + { + "network": "{}/32".format(VXLAN["local_addr"]["e1"]), + "next_hop": topo["routers"]["e1"]["links"]["d2-link1"][ + "ipv4" + ].split("/")[0], + }, + ] + }, + } + + result = create_static_routes(tgen, default_routes) + assert result is True, "Testcase :Failed \n Error: {}".format(result) + + +def test_RD_verification_manual_and_auto_p0(request): + """ + RD verification (manual/auto). + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step( + "Advertise vrf RED's routes in EVPN address family from Edge-1 router" + ", without manual configuration of RD." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step("Verify on DCG-1 and DCG-2:") + step("EVPN route for 10.1.1.1/32 has auto-assigned RD value.") + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="auto", rd_peer="e1" + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Configure RD for vrf RED manually as 50.50.50.50:50 and " + "advertise vrf RED's routes in EVPN address family from " + "Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": {"l2vpn": {"evpn": {"rd": "50.50.50.50:50"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("EVPN route for vrf RED has RD value as 50.50.50.50:50") + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="50.50.50.50:50" + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Configure RD for vrf RED manually as 100.100.100.100:100 and " + "advertise vrf RED's routes in EVPN address family from Edge-1 " + "router." + ) + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": {"evpn": {"rd": "100.100.100.100:100"}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for vrf RED is overridden with RD value as " "100.100.100.100:100." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rd="100.100.100.100:100" + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Configure RD for vrf BLUE manually same as vrf RED " + "(100.100.100.100:100) and advertise vrf RED and BLUE's routes " + "in EVPN address family from Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": {"evpn": {"rd": "100.100.100.100:100"}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Delete manually configured RD and advertise vrf RED's routes " + "in EVPN address family from Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": {"evpn": {"no rd": "100.100.100.100:100"}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Configure same RD value for vrf GREEN, as auto generated RD " + "value for vrf RED on Edge-1 router." + ) + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"rd": "10.0.0.33:1"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Delete auto configured RD value from vrf RED in EVPN " "address family.") + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"no rd": "10.0.0.33:1"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Configure RD value as 100.100.100:100") + + input_dict_rd = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": {"l2vpn": {"evpn": {"rd": "100.100.100:100"}}}, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rd) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_RT_verification_manual_p0(request): + """ + RT verification(manual) + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + step("Advertise VRF routes as in EVPN address family from Edge-1 " "router.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure RT for vrf RED manually as export 100:100 " + "and advertise vrf RED's routes in EVPN address family" + " from Edge-1 router." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "ipv4": { + "unicast": {"neighbor": {"r1": {"dest_link": {"e1": {}}}}} + }, + "ipv6": { + "unicast": {"neighbor": {"r1": {"dest_link": {"e1": {}}}}} + }, + "l2vpn": { + "evpn": {"route-target": {"export": [{"value": "100:100"}]}} + }, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on dcg-1 and dcg-2, EVPN route for 10.1.1.1/32" + " and 10::1/128 have RT value as 100:100." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt="100:100" + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Configure RT for vrf RED manually as export 500:500 and" + " advertise vrf RED's routes in EVPN address family from" + " e1 router." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": {"route-target": {"export": [{"value": "500:500"}]}} + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on dcg-1 and dcg-2, EVPN route for 10.1.1.1/32" + " and 10::1/128 have RT value as 500:500." + ) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt=["100:100", "500:500"] + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Import RT value 100:100 and 500:500 in vrf BLUE manually on" + " peer router DCG-1 and DCG-2." + ) + + input_dict_rt = { + "d1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [ + {"value": "100:100"}, + {"value": "500:500"}, + ] + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [ + {"value": "100:100"}, + {"value": "500:500"}, + ] + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for 10.1.1.1/32 and 10::1 should be installed " + "in vrf BLUE on DCG-1 and DCG-2 and further advertised to " + "VNF router." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "BLUE"}] + } + } + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error {}".format(tc_name, result) + + step( + "Delete import RT value 500:500 in vrf BLUE manually on " + "peer router DCG-1 and DCG-2." + ) + + input_dict_rt = { + "d1": { + "bgp": [ + { + "local_as": "100", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [{"value": "500:500", "delete": True}] + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "vrf": "BLUE", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "import": [{"value": "500:500", "delete": True}] + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt=["100:100", "500:500"] + ) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step("Delete RT export value 100:100 for vrf RED on Edge-1") + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "export": [{"value": "100:100", "delete": True}] + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "EVPN route for 10.1.1.1/32 and 10::1 should be withdrawn " + "from vrf BLUE on DCG-1,DCG-2 and VNF router." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r1": { + "static_routes": [{"network": [NETWORK1_1[addr_type]], "vrf": "BLUE"}] + } + } + result = verify_rib(tgen, addr_type, "d1", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d1", result) + ) + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d2", result) + ) + + step( + "Configure RT value as 100:100000010000010000101010 to check " + "the boundary value." + ) + + input_dict_rt = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "RED", + "address_family": { + "l2vpn": { + "evpn": { + "route-target": { + "export": [ + {"value": "100:100000010000010000101010"} + ] + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_rt) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "CLI error: RT value: 100:100000010000010000101010 should not " "be configured" + ) + + dut = "e1" + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_attributes_for_evpn_routes( + tgen, topo, dut, input_routes, rt="100:100000010000010000101010", expected=False + ) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: RT value out of boundary error in {} \n " + "Found: {}".format(tc_name, dut, result) + ) + + write_test_footer(tc_name) + + +def test_active_standby_evpn_implementation_p1(request): + """ + In an active/standby EVPN implementation, if active DCG goes down, + secondary takes over. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Taken care in base config: Configure BGP neighborship for both " + "address families(IPv4 & IPv6) between DCG-1/DCG-2 and VFN routers" + "(R3 and R4)." + ) + + step( + "BGP neighborships come up within defined VRFs. Please use below " + "command: sh bgp vrf all summary" + ) + + result = verify_bgp_convergence(tgen, topo, "d1") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + result = verify_bgp_convergence(tgen, topo, "d2") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Advertise prefixes from VNF routers R3 and R4 in associated " + "VRFs for both address-families." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Redistribute static in (IPv4 and IPv6) address-family " + "on Edge-1 for all VRFs." + ) + + input_dict_2 = {} + for dut in ["r3", "r4"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + if dut == "r3": + VRFS = ["RED"] + AS_NUM = [3] + if dut == "r4": + VRFS = ["BLUE", "GREEN"] + AS_NUM = [4, 4] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Prefixes are received in respective VRFs on DCG-1/DCG-2.") + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "d1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Taken care in base config: Advertise VRF routes in EVPN " + "address-family from DCG-1 and DCG-2 router." + ) + + step("Verify on Edge-1 that EVPN routes are installed via next-hop " "as DCG-2.") + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + if addr_type == "ipv4": + result = verify_rib( + tgen, addr_type, "e1", input_routes, next_hop=BRIDGE_INTF2 + ) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + else: + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure 'next-hop self' on DCG-1 for peer Edge-1 in EVPN " "address-family." + ) + + input_dict_3 = { + "d1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4": {"d1-link1": {"next_hop_self": True}} + } + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + logger.info( + "Creating route-map so ipv6 glpbal ip wpuld be preferred " "as next-hop" + ) + + step( + "Verify on Edge-1 that EVPN routes are now preferred via " + "next-hop as DCG-1(iBGP) due to shortest AS-Path." + ) + + for addr_type in ADDR_TYPES: + + logger.info("Verifying only ipv4 routes") + if addr_type != "ipv4": + continue + + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + next_hop = topo["routers"]["d1"]["links"]["e1-link1"]["ipv4"].split("/")[0] + + result = verify_rib(tgen, addr_type, "e1", input_routes, next_hop=next_hop) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +def test_evpn_routes_from_VNFs_p1(request): + """ + EVPN routes are advertised/withdrawn, based on VNFs + advertising/withdrawing IP prefixes. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Taken care in base config: Advertise VNFs'(R1 and R2) " + "originated routes in EVPN address-family from Edge-1 to " + "DCG-1 and DCG-2 routers." + ) + step( + "Taken care in base config: Advertise IPv4 and IPv6 routes " + "from default vrf in EVPN address-family from Edge-1." + ) + + step( + "Verify on DCG-2 that VNF routes are received in respective " + "VRFs along with auto derived RD/RT values 'show bgp l2vpn evpn'" + ) + for dut in ["d1", "d2"]: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_evpn_routes(tgen, topo, dut, input_routes) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_evpn_routes(tgen, topo, dut, input_routes) + assert result is True, "Testcase {} on {} :Failed \n Error: {}".format( + tc_name, dut, result + ) + + step( + "Verify on R3 and R4 that DCG-2 further advertises all EVPN " + "routes to corresponding VRFs." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 receives EVPN routes associated to default " + "VRF and install in default IP routing table as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Withdraw the IP prefixes from VFN(R1).") + dut = "r1" + input_dict_2 = {} + static_routes = topo["routers"][dut]["static_routes"] + for static_route in static_routes: + static_route["delete"] = True + temp = {dut: {"static_routes": [static_route]}} + input_dict_2.update(temp) + + result = create_static_routes(tgen, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 removes EVPN routes corresponding to vrf RED and " + "send an withdraw to VNF(R3) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d2", result) + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "r3", result) + ) + + step("Re-advertise IP prefixes from VFN(R1).") + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 receives EVPN routes corresponding to vrf RED " + "again and send an update to VNF(R3) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + for addr_type in ADDR_TYPES: + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_rib(tgen, addr_type, "r3", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Delete vrf BLUE from router Edge-1") + input_dict_3 = {"e1": {"vrfs": [{"name": "BLUE", "id": "2", "delete": True}]}} + + result = create_vrf_cfg(tgen, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that DCG-2 removes EVPN routes corresponding to " + "vrf BLUE and send an withdraw to VNF(R4) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = { + "r2": {"static_routes": [{"network": NETWORK2_1[addr_type], "vrf": "BLUE"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d2", result) + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "r4", result) + ) + + step("Add vrf BLUE on router Edge-1 again.") + interface = topo["routers"]["e1"]["links"]["r2-link1"]["interface"] + input_dict_3 = { + "e1": { + "links": { + "r2-link1": { + "interface": interface, + "ipv4": "auto", + "ipv6": "auto", + "vrf": "BLUE", + } + }, + "vrfs": [{"name": "BLUE", "id": "2"}], + } + } + result = create_vrf_cfg(tgen, input_dict_3) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + logger.info( + "After deleting VRFs ipv6 addresses wil be deleted " + "from kernel Adding back ipv6 addresses" + ) + dut = "e1" + vrfs = ["BLUE"] + + for vrf in vrfs: + for c_link, c_data in topo["routers"][dut]["links"].items(): + if "vrf" in c_data: + if c_data["vrf"] != vrf: + continue + + intf_name = c_data["interface"] + intf_ipv6 = c_data["ipv6"] + + create_interface_in_kernel( + tgen, dut, intf_name, intf_ipv6, vrf, create=False + ) + + result = verify_bgp_convergence(tgen, topo, dut) + assert result is True, "Failed to converge on {}".format(dut) + + step( + "Verify that DCG-2 receives EVPN routes corresponding to " + "vrf BLUE again and send an update to VNF(R4) as well." + ) + for addr_type in ADDR_TYPES: + input_routes = { + "r2": {"static_routes": [{"network": NETWORK2_1[addr_type], "vrf": "BLUE"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Withdraw IPv6 address-family in EVPN advertisements for " "VRF GREEN") + addr_type = "ipv6" + input_dict_4 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "l2vpn": { + "evpn": { + "advertise": {addr_type: {"unicast": {"delete": True}}} + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that EVPN routes (IPv6)associated with vrf GREEN are " + "withdrawn from DCG-2 and VNF R4." + ) + input_routes = { + "r2": {"static_routes": [{"network": NETWORK3_1[addr_type], "vrf": "GREEN"}]} + } + + result = verify_rib(tgen, addr_type, "d2", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "d2", result) + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes, expected=False) + assert result is not True, ( + "Testcase {} : Failed \n " + "Expected: Routes should not be present in {} RIB \n " + "Found: {}".format(tc_name, "r4", result) + ) + + step("Advertise IPv6 address-family in EVPN advertisements " "for VRF GREEN.") + addr_type = "ipv6" + input_dict_4 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "vrf": "GREEN", + "address_family": { + "l2vpn": {"evpn": {"advertise": {addr_type: {"unicast": {}}}}} + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_4) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + for addr_type in ADDR_TYPES: + input_routes = { + "r2": { + "static_routes": [{"network": NETWORK3_1[addr_type], "vrf": "GREEN"}] + } + } + + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + result = verify_rib(tgen, addr_type, "r4", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize( + "attribute", [{"route-type": "prefix"}, {"vni": VNI_1}, {"rt": "300:300"}] +) +def test_route_map_operations_for_evpn_address_family_p1(request, attribute): + """ + Route-map operations for EVPN address family. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise VRF routes in EVPN address family from Edge-1 router." + " Configure a route-map on e1 to filter EVPN routes based on" + " below keywords: route-type: prefix" + ) + + for key, value in attribute.items(): + if key == "rt": + logger.info("Creating extcommunity using raw_config") + raw_config = { + "d2": { + "raw_config": [ + "bgp extcommunity-list standard ECOMM300 permit {} {}".format( + key, value + ) + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format( + tc_name, result + ) + + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "set": {"extcommunity": {key: value}}} + ] + } + }, + "d2": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"extcommunity": "ECOMM300"}} + ] + } + }, + } + + else: + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"evpn": {key: value}}} + ] + } + }, + "d2": { + "route_maps": { + "rmap_route_type": [ + {"action": "permit", "match": {"evpn": {key: value}}} + ] + } + }, + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "d2": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_route_type", + "direction": "out", + } + ] + } + } + } + } + } + } + }, + } + ] + }, + "d2": { + "bgp": [ + { + "local_as": "200", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "e1": { + "ipv4": { + "d2-link1": { + "route_maps": [ + { + "name": "rmap_route_type", + "direction": "in", + } + ] + } + } + } + } + } + } + }, + } + ] + }, + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router DCG-2 that EVPN routes corresponding to all " + "VRFs are received. As all EVPN routes are type-5 only." + ) + + input_routes = {key: topo["routers"][key] for key in ["r1"]} + result = verify_evpn_routes(tgen, topo, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + input_routes = {key: topo["routers"][key] for key in ["r2"]} + result = verify_evpn_routes(tgen, topo, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + write_test_footer(tc_name) + + +def test_evpn_address_family_with_graceful_restart_p0(request): + """ + Verify Graceful-restart function for EVPN address-family. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Redistribute static in (IPv4 and IPv6) address-family " + "on Edge-1 for all VRFs." + ) + + input_dict_2 = {} + for dut in ["r3", "r4"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + if dut == "r3": + VRFS = ["RED"] + AS_NUM = [3] + if dut == "r4": + VRFS = ["BLUE", "GREEN"] + AS_NUM = [4, 4] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router Edge-1 that EVPN routes corresponding to " + "all VRFs are received from both routers DCG-1 and DCG-2" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure DCG-2 as GR restarting node for EVPN session between" + " DCG-2 and EDGE-1, following by a session reset using 'clear bgp *'" + " command." + ) + + input_dict_gr = { + "d2": { + "bgp": [ + { + "local_as": "200", + "graceful-restart": { + "graceful-restart": True, + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_gr) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + step( + "Verify that DCG-2 changes it's role to GR-restarting router " + "and EDGE-1 becomes the GR-helper." + ) + + step("Kill BGPd daemon on DCG-2.") + kill_router_daemons(tgen, "d2", ["bgpd"]) + + step( + "Verify that EDGE-1 keep stale entries for EVPN RT-5 routes " + "received from DCG-2 before the restart." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + } + } + result = verify_evpn_routes(tgen, topo, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 keeps BGP routes in Zebra until BGPd " + "comes up or end of 'rib-stale-time'" + ) + + step("Start BGPd daemon on DCG-2.") + start_router_daemons(tgen, "d2", ["bgpd"]) + + step("Verify that EDGE-1 removed all the stale entries.") + for addr_type in ADDR_TYPES: + input_routes = { + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + } + } + result = verify_evpn_routes(tgen, topo, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Verify that DCG-2 refresh zebra with EVPN routes. " + "(no significance of 'rib-stale-time'" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + } + } + result = verify_rib(tgen, addr_type, "d2", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +@pytest.mark.parametrize("attribute", ["locPrf", "weight", "path"]) +def test_bgp_attributes_for_evpn_address_family_p1(request, attribute): + """ + BGP attributes for EVPN address-family. + """ + + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + check_router_status(tgen) + reset_config_on_routers(tgen) + add_default_routes(tgen) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step( + "Advertise prefixes from VNF routers R1 and R2 in associated " + "VRFs for both address-family." + ) + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r1": { + "static_routes": [ + { + "network": NETWORK1_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r2": { + "static_routes": [ + { + "network": NETWORK2_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK3_1[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + topo_local = deepcopy(topo) + + logger.info("Modifying topology b/w e1 and d1 from iBGP to eBGP") + step("Delete BGP config for vrf RED.") + + if attribute == "locPrf": + input_dict_vni = { + "d1": { + "vrfs": [ + {"name": "RED", "no_vni": VNI_1}, + {"name": "BLUE", "no_vni": VNI_2}, + {"name": "GREEN", "no_vni": VNI_3}, + ] + } + } + result = create_vrf_cfg(tgen, topo, input_dict=input_dict_vni) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = {} + for dut in ["d1"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + INDEX = [0, 1, 2, 3] + VRFS = ["RED", "BLUE", "GREEN", None] + AS_NUM = [100, 100, 100, 100] + + for index, vrf, as_num in zip(INDEX, VRFS, AS_NUM): + topo_local["routers"][dut]["bgp"][index]["local_as"] = 200 + if vrf: + temp[dut]["bgp"].append( + {"local_as": as_num, "vrf": vrf, "delete": True} + ) + else: + temp[dut]["bgp"].append({"local_as": as_num, "delete": True}) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} on d1 :Failed \n Error: {}".format( + tc_name, result + ) + + result = create_router_bgp(tgen, topo_local["routers"]) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step("Advertise VRF routes in EVPN address-family from DCG-1 " "and DCG-2 routers.") + + for addr_type in ADDR_TYPES: + input_dict_1 = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = create_static_routes(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Redistribute static in (IPv4 and IPv6) address-family " + "on Edge-1 for all VRFs." + ) + + input_dict_2 = {} + for dut in ["r3", "r4"]: + temp = {dut: {"bgp": []}} + input_dict_2.update(temp) + + if dut == "r3": + VRFS = ["RED"] + AS_NUM = [3] + if dut == "r4": + VRFS = ["BLUE", "GREEN"] + AS_NUM = [4, 4] + + for vrf, as_num in zip(VRFS, AS_NUM): + temp[dut]["bgp"].append( + { + "local_as": as_num, + "vrf": vrf, + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + }, + } + ) + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router Edge-1 that EVPN routes corresponding to " + "all VRFs are received from both routers DCG-1 and DCG-2" + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_rib(tgen, addr_type, "e1", input_routes) + assert result is True, "Testcase {} :Failed \n Error: {}".format( + tc_name, result + ) + + step( + "Configure a route-map on Edge-1 to modify below BGP attributes " + "for EVPN address-family:" + ) + + if attribute == "path": + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_d1": [ + { + "action": "permit", + "set": { + attribute: { + "as_num": "123 231 321", + "as_action": "prepend", + } + }, + } + ], + "rmap_d2": [ + { + "action": "permit", + "set": { + attribute: {"as_num": "121", "as_action": "prepend"} + }, + } + ], + } + } + } + else: + input_dict_1 = { + "e1": { + "route_maps": { + "rmap_d1": [{"action": "permit", "set": {attribute: 120}}], + "rmap_d2": [{"action": "permit", "set": {attribute: 150}}], + } + } + } + result = create_route_maps(tgen, input_dict_1) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + input_dict_2 = { + "e1": { + "bgp": [ + { + "local_as": "100", + "address_family": { + "l2vpn": { + "evpn": { + "neighbor": { + "d1": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_d1", + "direction": "in", + } + ] + } + } + }, + "d2": { + "ipv4": { + "e1-link1": { + "route_maps": [ + { + "name": "rmap_d2", + "direction": "in", + } + ] + } + } + }, + } + } + } + }, + } + ] + } + } + + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step( + "Verify on router Edge-1 that EVPN routes are preferred via" + " DCG-1 or DCG-2 based on best path selection criteria " + "(according to the configured BGP attribute values in route-map)." + ) + + for addr_type in ADDR_TYPES: + input_routes = { + "r3": { + "static_routes": [ + { + "network": NETWORK1_2[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "RED", + } + ] + }, + "r4": { + "static_routes": [ + { + "network": NETWORK1_3[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "BLUE", + }, + { + "network": NETWORK1_4[addr_type], + "next_hop": NEXT_HOP_IP[addr_type], + "vrf": "GREEN", + }, + ] + }, + } + + result = verify_best_path_as_per_bgp_attribute( + tgen, addr_type, "e1", input_routes, attribute + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/example_munet/munet.yaml b/tests/topotests/example_munet/munet.yaml new file mode 100644 index 0000000..34e1470 --- /dev/null +++ b/tests/topotests/example_munet/munet.yaml @@ -0,0 +1,17 @@ +version: 1 +topology: + ipv6-enable: true + networks-autonumber: true + networks: + - name: net1 + - name: net2 + nodes: + - name: r1 + kind: frr + connections: ["net1"] + - name: r2 + kind: frr + connections: ["net1", "net2"] + - name: r3 + kind: frr + connections: ["net2"] diff --git a/tests/topotests/example_munet/r1/daemons b/tests/topotests/example_munet/r1/daemons new file mode 100644 index 0000000..a454c95 --- /dev/null +++ b/tests/topotests/example_munet/r1/daemons @@ -0,0 +1,6 @@ +zebra=1 +staticd=1 +vtysh_enable=1 +watchfrr_enable=1 +zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log" +staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log" diff --git a/tests/topotests/example_munet/r1/frr.conf b/tests/topotests/example_munet/r1/frr.conf new file mode 100644 index 0000000..468bda5 --- /dev/null +++ b/tests/topotests/example_munet/r1/frr.conf @@ -0,0 +1,7 @@ +log file /var/log/frr/frr.log +service integrated-vtysh-config + +interface eth0 + ip address 10.0.1.1/24 + +ip route 10.0.0.0/8 blackhole diff --git a/tests/topotests/example_munet/r1/vtysh.conf b/tests/topotests/example_munet/r1/vtysh.conf new file mode 100644 index 0000000..f863f56 --- /dev/null +++ b/tests/topotests/example_munet/r1/vtysh.conf @@ -0,0 +1 @@ +service integrated-vtysh-config \ No newline at end of file diff --git a/tests/topotests/example_munet/r2/daemons b/tests/topotests/example_munet/r2/daemons new file mode 100644 index 0000000..a454c95 --- /dev/null +++ b/tests/topotests/example_munet/r2/daemons @@ -0,0 +1,6 @@ +zebra=1 +staticd=1 +vtysh_enable=1 +watchfrr_enable=1 +zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log" +staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log" diff --git a/tests/topotests/example_munet/r2/frr.conf b/tests/topotests/example_munet/r2/frr.conf new file mode 100644 index 0000000..77d9892 --- /dev/null +++ b/tests/topotests/example_munet/r2/frr.conf @@ -0,0 +1,10 @@ +log file /var/log/frr/frr.log +service integrated-vtysh-config + +interface eth0 + ip address 10.0.1.2/24 + +interface eth1 + ip address 10.0.2.2/24 + +ip route 10.0.0.0/8 blackhole diff --git a/tests/topotests/example_munet/r2/vtysh.conf b/tests/topotests/example_munet/r2/vtysh.conf new file mode 100644 index 0000000..f863f56 --- /dev/null +++ b/tests/topotests/example_munet/r2/vtysh.conf @@ -0,0 +1 @@ +service integrated-vtysh-config \ No newline at end of file diff --git a/tests/topotests/example_munet/r3/daemons b/tests/topotests/example_munet/r3/daemons new file mode 100644 index 0000000..a454c95 --- /dev/null +++ b/tests/topotests/example_munet/r3/daemons @@ -0,0 +1,6 @@ +zebra=1 +staticd=1 +vtysh_enable=1 +watchfrr_enable=1 +zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log" +staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log" diff --git a/tests/topotests/example_munet/r3/frr.conf b/tests/topotests/example_munet/r3/frr.conf new file mode 100644 index 0000000..e0839e6 --- /dev/null +++ b/tests/topotests/example_munet/r3/frr.conf @@ -0,0 +1,7 @@ +log file /var/log/frr/frr.log +service integrated-vtysh-config + +interface eth0 + ip address 10.0.2.3/24 + +ip route 10.0.0.0/8 blackhole diff --git a/tests/topotests/example_munet/r3/vtysh.conf b/tests/topotests/example_munet/r3/vtysh.conf new file mode 100644 index 0000000..f863f56 --- /dev/null +++ b/tests/topotests/example_munet/r3/vtysh.conf @@ -0,0 +1 @@ +service integrated-vtysh-config \ No newline at end of file diff --git a/tests/topotests/example_munet/test_munet.py b/tests/topotests/example_munet/test_munet.py new file mode 100644 index 0000000..0d9599f --- /dev/null +++ b/tests/topotests/example_munet/test_munet.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# April 23 2023, Christian Hopps +# +# Copyright (c) 2023, LabN Consulting, L.L.C. +# +async def test_native_test(unet): + o = unet.hosts["r1"].cmd_nostatus("ip addr") + print(o) diff --git a/tests/topotests/example_test/__init__.py b/tests/topotests/example_test/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/tests/topotests/example_test/r1/zebra.conf b/tests/topotests/example_test/r1/zebra.conf new file mode 100644 index 0000000..b733b7b --- /dev/null +++ b/tests/topotests/example_test/r1/zebra.conf @@ -0,0 +1,8 @@ +interface r1-eth0 + ip address 192.168.1.1/24 + +interface r1-eth1 + ip address 192.168.2.1/24 + +interface r1-eth2 + ip address 192.168.3.1/24 \ No newline at end of file diff --git a/tests/topotests/example_test/r2/zebra.conf b/tests/topotests/example_test/r2/zebra.conf new file mode 100644 index 0000000..c0921f5 --- /dev/null +++ b/tests/topotests/example_test/r2/zebra.conf @@ -0,0 +1,4 @@ +interface r2-eth0 + ip address 192.168.1.2/24 +interface r2-eth1 + ip address 192.168.3.2/24 diff --git a/tests/topotests/example_test/test_example.py b/tests/topotests/example_test/test_example.py new file mode 100755 index 0000000..30c3d24 --- /dev/null +++ b/tests/topotests/example_test/test_example.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +import subprocess +import sys +import os +import time + +import pytest + +fatal_error = "" + + +def setup_module(module): + print("setup_module module:%s" % module.__name__) + + +def teardown_module(module): + print("teardown_module module:%s" % module.__name__) + + +def setup_function(function): + print("setup_function function:%s" % function.__name__) + + +def teardown_function(function): + print("teardown_function function:%s" % function.__name__) + + +def test_numbers_compare(): + a = 12 + print("Dummy Output") + assert a == 12 + + +def test_fail_example(): + assert True, "Some Text with explaination in case of failure" + + +@pytest.mark.xfail +def test_ls_exits_zero(): + "Tests for ls command on invalid file" + + global fatal_error + + proc = subprocess.Popen( + ["ls", "/some/nonexistant/file"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + stdout, stderr = proc.communicate() + + if proc.returncode != 0: + # Mark this as a fatal error which skips some other tests on failure + fatal_error = "test_fail_example failed" + assert proc.returncode == 0, "Return Code is non-Zero:\n%s" % stderr + + +def test_skipped_on_fatalerror(): + global fatal_error + + # Skip if previous fatal error condition is raised + if fatal_error != "": + pytest.skip(fatal_error) + + assert True, "Some Text with explaination in case of failure" + + +if __name__ == "__main__": + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/tests/topotests/example_test/test_template.dot b/tests/topotests/example_test/test_template.dot new file mode 100644 index 0000000..b5e1202 --- /dev/null +++ b/tests/topotests/example_test/test_template.dot @@ -0,0 +1,51 @@ +## Color coding: +######################### +## Main FRR: #f08080 red +## Switches: #d0e0d0 gray +## RIP: #19e3d9 Cyan +## RIPng: #fcb314 dark yellow +## OSPFv2: #32b835 Green +## OSPFv3: #19e3d9 Cyan +## ISIS IPv4 #fcb314 dark yellow +## ISIS IPv6 #9a81ec purple +## BGP IPv4 #eee3d3 beige +## BGP IPv6 #fdff00 yellow +##### Colors (see http://www.color-hex.com/) + +graph template { + label="template"; + + # Routers + r1 [ + shape=doubleoctagon, + label="r1", + fillcolor="#f08080", + style=filled, + ]; + r2 [ + shape=doubleoctagon + label="r2", + fillcolor="#f08080", + style=filled, + ]; + + # Switches + s1 [ + shape=oval, + label="s1\n192.168.0.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + s2 [ + shape=oval, + label="s2\n192.168.1.0/24", + fillcolor="#d0e0d0", + style=filled, + ]; + + # Connections + r1 -- s1 [label="eth0\n.1"]; + + r1 -- s2 [label="eth1\n.100"]; + r2 -- s2 [label="eth0\n.1"]; +} diff --git a/tests/topotests/example_test/test_template.jpg b/tests/topotests/example_test/test_template.jpg new file mode 100644 index 0000000..b01ef73 Binary files /dev/null and b/tests/topotests/example_test/test_template.jpg differ diff --git a/tests/topotests/example_test/test_template.py b/tests/topotests/example_test/test_template.py new file mode 100644 index 0000000..5728eba --- /dev/null +++ b/tests/topotests/example_test/test_template.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +#